| /* |
| * MMC Host Controller Commands |
| * |
| * Copyright (c) 2021 Google LLC |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * for more details. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "sdhci-cmd.h" |
| #include "../libqtest.h" |
| |
| static ssize_t read_fifo(QTestState *qts, uint64_t reg, char *msg, size_t count) |
| { |
| uint32_t mask = 0xff; |
| size_t index = 0; |
| uint32_t msg_frag; |
| int size; |
| while (index < count) { |
| size = count - index; |
| if (size > 4) { |
| size = 4; |
| } |
| msg_frag = qtest_readl(qts, reg); |
| while (size > 0) { |
| msg[index] = msg_frag & mask; |
| if (msg[index++] == 0) { |
| return index; |
| } |
| msg_frag >>= 8; |
| --size; |
| } |
| } |
| return index; |
| } |
| |
| static void write_fifo(QTestState *qts, uint64_t reg, const char *msg, |
| size_t count) |
| { |
| size_t index = 0; |
| uint32_t msg_frag; |
| int size; |
| int frag_i; |
| while (index < count) { |
| size = count - index; |
| if (size > 4) { |
| size = 4; |
| } |
| msg_frag = 0; |
| frag_i = 0; |
| while (frag_i < size) { |
| msg_frag |= ((uint32_t)msg[index++]) << (frag_i * 8); |
| ++frag_i; |
| } |
| qtest_writel(qts, reg, msg_frag); |
| } |
| } |
| |
| static void fill_block(QTestState *qts, uint64_t reg, int count) |
| { |
| while (--count >= 0) { |
| qtest_writel(qts, reg, 0); |
| } |
| } |
| |
| void sdhci_cmd_regs(QTestState *qts, uint64_t base_addr, uint16_t blksize, |
| uint16_t blkcnt, uint32_t argument, uint16_t trnmod, |
| uint16_t cmdreg) |
| { |
| qtest_writew(qts, base_addr + SDHC_BLKSIZE, blksize); |
| qtest_writew(qts, base_addr + SDHC_BLKCNT, blkcnt); |
| qtest_writel(qts, base_addr + SDHC_ARGUMENT, argument); |
| qtest_writew(qts, base_addr + SDHC_TRNMOD, trnmod); |
| qtest_writew(qts, base_addr + SDHC_CMDREG, cmdreg); |
| } |
| |
| ssize_t sdhci_read_cmd(QTestState *qts, uint64_t base_addr, char *msg, |
| size_t count) |
| { |
| sdhci_cmd_regs(qts, base_addr, count, 1, 0, |
| SDHC_TRNS_MULTI | SDHC_TRNS_READ | SDHC_TRNS_BLK_CNT_EN, |
| SDHC_READ_MULTIPLE_BLOCK | SDHC_CMD_DATA_PRESENT); |
| |
| /* read sd fifo_buffer */ |
| ssize_t bytes_read = read_fifo(qts, base_addr + SDHC_BDATA, msg, count); |
| |
| sdhci_cmd_regs(qts, base_addr, 0, 0, 0, |
| SDHC_TRNS_MULTI | SDHC_TRNS_READ | SDHC_TRNS_BLK_CNT_EN, |
| SDHC_STOP_TRANSMISSION); |
| |
| return bytes_read; |
| } |
| |
| void sdhci_write_cmd(QTestState *qts, uint64_t base_addr, const char *msg, |
| size_t count, size_t blksize) |
| { |
| sdhci_cmd_regs(qts, base_addr, blksize, 1, 0, |
| SDHC_TRNS_MULTI | SDHC_TRNS_WRITE | SDHC_TRNS_BLK_CNT_EN, |
| SDHC_WRITE_MULTIPLE_BLOCK | SDHC_CMD_DATA_PRESENT); |
| |
| /* write to sd fifo_buffer */ |
| write_fifo(qts, base_addr + SDHC_BDATA, msg, count); |
| fill_block(qts, base_addr + SDHC_BDATA, (blksize - count) / 4); |
| |
| sdhci_cmd_regs(qts, base_addr, 0, 0, 0, |
| SDHC_TRNS_MULTI | SDHC_TRNS_WRITE | SDHC_TRNS_BLK_CNT_EN, |
| SDHC_STOP_TRANSMISSION); |
| } |