| /* |
| * QTests for NPCM7xx SD-3.0 / MMC-4.51 Host Controller |
| * |
| * Copyright (c) 2022 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 "hw/sd/npcm7xx_sdhci.h" |
| |
| #include "libqtest.h" |
| #include "libqtest-single.h" |
| #include "libqos/sdhci-cmd.h" |
| |
| #define NPCM7XX_REG_SIZE 0x100 |
| #define NPCM7XX_MMC_BA 0xF0842000 |
| #define NPCM7XX_BLK_SIZE 512 |
| #define NPCM7XX_TEST_IMAGE_SIZE (1 << 20) |
| |
| char *sd_path; |
| |
| static QTestState *setup_sd_card(void) |
| { |
| uint16_t rca; |
| |
| QTestState *qts = qtest_initf( |
| "-machine kudo-bmc " |
| "-device sd-card,drive=drive0 " |
| "-drive id=drive0,if=none,file=%s,format=raw,auto-read-only=off", |
| sd_path); |
| |
| qtest_writew(qts, NPCM7XX_MMC_BA + SDHC_SWRST, SDHC_RESET_ALL); |
| qtest_writew(qts, NPCM7XX_MMC_BA + SDHC_CLKCON, |
| SDHC_CLOCK_SDCLK_EN | SDHC_CLOCK_INT_STABLE | |
| SDHC_CLOCK_INT_EN); |
| sdhci_cmd_regs(qts, NPCM7XX_MMC_BA, 0, 0, 0, 0, SDHC_APP_CMD); |
| sdhci_cmd_regs(qts, NPCM7XX_MMC_BA, 0, 0, 0x41200000, 0, (41 << 8)); |
| sdhci_cmd_regs(qts, NPCM7XX_MMC_BA, 0, 0, 0, 0, SDHC_ALL_SEND_CID); |
| sdhci_cmd_regs(qts, NPCM7XX_MMC_BA, 0, 0, 0, 0, SDHC_SEND_RELATIVE_ADDR |
| | SDHC_CMD_RESPONSE); |
| rca = qtest_readl(qts, NPCM7XX_MMC_BA + SDHC_RSPREG0) >> 16; |
| sdhci_cmd_regs(qts, NPCM7XX_MMC_BA, 0, 0, rca << 16, 0, |
| SDHC_SELECT_DESELECT_CARD); |
| |
| return qts; |
| } |
| |
| static void write_sdread(QTestState *qts, const char *msg) |
| { |
| int fd, ret; |
| size_t len = strlen(msg); |
| char *rmsg = g_malloc(len); |
| |
| /* write message to sd */ |
| fd = open(sd_path, O_WRONLY); |
| g_assert(fd >= 0); |
| ret = write(fd, msg, len); |
| close(fd); |
| g_assert(ret == len); |
| |
| /* read message using sdhci */ |
| ret = sdhci_read_cmd(qts, NPCM7XX_MMC_BA, rmsg, len); |
| g_assert(ret == len); |
| g_assert(!memcmp(rmsg, msg, len)); |
| |
| g_free(rmsg); |
| } |
| |
| /* Check MMC can read values from sd */ |
| static void test_read_sd(void) |
| { |
| QTestState *qts = setup_sd_card(); |
| |
| write_sdread(qts, "hello world"); |
| write_sdread(qts, "goodbye"); |
| |
| qtest_quit(qts); |
| } |
| |
| static void sdwrite_read(QTestState *qts, const char *msg) |
| { |
| int fd, ret; |
| size_t len = strlen(msg); |
| char *rmsg = g_malloc(len); |
| |
| /* write message using sdhci */ |
| sdhci_write_cmd(qts, NPCM7XX_MMC_BA, msg, len, NPCM7XX_BLK_SIZE); |
| |
| /* read message from sd */ |
| fd = open(sd_path, O_RDONLY); |
| g_assert(fd >= 0); |
| ret = read(fd, rmsg, len); |
| close(fd); |
| g_assert(ret == len); |
| |
| g_assert(!memcmp(rmsg, msg, len)); |
| |
| g_free(rmsg); |
| } |
| |
| /* Check MMC can write values to sd */ |
| static void test_write_sd(void) |
| { |
| QTestState *qts = setup_sd_card(); |
| |
| sdwrite_read(qts, "hello world"); |
| sdwrite_read(qts, "goodbye"); |
| |
| qtest_quit(qts); |
| } |
| |
| /* Check SDHCI has correct default values. */ |
| static void test_reset(void) |
| { |
| QTestState *qts = qtest_init("-machine kudo-bmc"); |
| uint64_t addr = NPCM7XX_MMC_BA; |
| uint64_t end_addr = addr + NPCM7XX_REG_SIZE; |
| uint16_t prstvals_resets[] = {NPCM7XX_PRSTVALS_0_RESET, |
| NPCM7XX_PRSTVALS_1_RESET, |
| 0, |
| NPCM7XX_PRSTVALS_3_RESET, |
| 0, |
| 0}; |
| int i; |
| uint32_t mask; |
| |
| while (addr < end_addr) { |
| switch (addr - NPCM7XX_MMC_BA) { |
| case SDHC_PRNSTS: |
| /* |
| * ignores bits 20 to 24: they are changed when reading registers |
| */ |
| mask = 0x1f00000; |
| g_assert_cmphex(qtest_readl(qts, addr) | mask, ==, |
| NPCM7XX_PRSNTS_RESET | mask); |
| addr += 4; |
| break; |
| case SDHC_BLKGAP: |
| g_assert_cmphex(qtest_readb(qts, addr), ==, NPCM7XX_BLKGAP_RESET); |
| addr += 1; |
| break; |
| case SDHC_CAPAB: |
| g_assert_cmphex(qtest_readq(qts, addr), ==, NPCM7XX_CAPAB_RESET); |
| addr += 8; |
| break; |
| case SDHC_MAXCURR: |
| g_assert_cmphex(qtest_readq(qts, addr), ==, NPCM7XX_MAXCURR_RESET); |
| addr += 8; |
| break; |
| case SDHC_HCVER: |
| g_assert_cmphex(qtest_readw(qts, addr), ==, NPCM7XX_HCVER_RESET); |
| addr += 2; |
| break; |
| case NPCM7XX_PRSTVALS: |
| for (i = 0; i < NPCM7XX_PRSTVALS_SIZE; ++i) { |
| g_assert_cmphex(qtest_readw(qts, addr + 2 * i), ==, |
| prstvals_resets[i]); |
| } |
| addr += NPCM7XX_PRSTVALS_SIZE * 2; |
| break; |
| default: |
| g_assert_cmphex(qtest_readb(qts, addr), ==, 0); |
| addr += 1; |
| } |
| } |
| |
| qtest_quit(qts); |
| } |
| |
| static void drive_destroy(void) |
| { |
| unlink(sd_path); |
| g_free(sd_path); |
| } |
| |
| static void drive_create(void) |
| { |
| int fd, ret; |
| GError *error = NULL; |
| |
| /* Create a temporary raw image */ |
| fd = g_file_open_tmp("sdhci_XXXXXX", &sd_path, &error); |
| if (fd == -1) { |
| fprintf(stderr, "unable to create sdhci file: %s\n", error->message); |
| g_error_free(error); |
| } |
| g_assert(sd_path != NULL); |
| |
| ret = ftruncate(fd, NPCM7XX_TEST_IMAGE_SIZE); |
| g_assert_cmpint(ret, ==, 0); |
| g_message("%s", sd_path); |
| close(fd); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int ret; |
| |
| drive_create(); |
| |
| g_test_init(&argc, &argv, NULL); |
| |
| qtest_add_func("npcm7xx_sdhci/reset", test_reset); |
| qtest_add_func("npcm7xx_sdhci/write_sd", test_write_sd); |
| qtest_add_func("npcm7xx_sdhci/read_sd", test_read_sd); |
| |
| ret = g_test_run(); |
| drive_destroy(); |
| return ret; |
| } |