// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2014-2021 MediaTek Inc. * Author: Chaotian.Jing */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SDIO_CCCR_MTK_DDR208 0xF2 #define SUCCESS 0 #define FAIL 1 enum { Read = 0, Write = 1, Tune = 2, Hqa_read = 3, Hqa_write = 4, Reset = 5, Stress_read = 6, Stress_write = 7, Stress_test = 8, Func_ctrl = 9, Desense_read, Desense_write }; static struct mmc_host *host; /** * define some count timer. */ #define KAL_TIME_INTERVAL_DECLARATION() struct timeval __rTs, __rTe #define KAL_REC_TIME_START() do_gettimeofday(&__rTs) #define KAL_REC_TIME_END() do_gettimeofday(&__rTe) #define KAL_GET_TIME_INTERVAL() \ ((__rTe.tv_sec * USEC_PER_SEC + __rTe.tv_usec) - \ (__rTs.tv_sec * USEC_PER_SEC + __rTs.tv_usec)) /** * sdio_proc_show dispaly the common cccr and cis. */ static int sdio_proc_show(struct seq_file *m, void *v) { struct mmc_card *card; struct sdio_cccr cccr; WARN_ON(!host); card = host->card; cccr = card->cccr; seq_puts(m, "\n=========================================\n"); seq_puts(m, "common cccr:\n"); seq_printf(m, "sdio_vsn:%x, sd_vsn:%x, multi_block%x.\n" "low_speed:%x, wide_bus:%x, hight_power:%x.\n" "high_speed:%x, disable_cd:%x.\n", cccr.sdio_vsn, cccr.sd_vsn, cccr.multi_block, cccr.low_speed, cccr.wide_bus, cccr.high_power, cccr.high_speed, cccr.disable_cd); seq_puts(m, "common cis:\n"); seq_printf(m, "vendor: %x, device:%x, blksize:%x, max_dtr:%x\n", card->cis.vendor, card->cis.device, card->cis.blksize, card->cis.max_dtr); seq_puts(m, "read cmd format:\n"); seq_puts(m, "echo 0 0xReg 0xfunction> /proc/sdio\n"); seq_puts(m, "write cmd format:\n"); seq_puts(m, "echo 1 0xReg 0xfunction 0xValue> /proc/sdio\n"); seq_puts(m, "tune cmd format:\n"); seq_puts(m, "echo 2 0xloop_cycles > /proc/sdio\n"); seq_puts(m, "multi read cmd format:\n"); seq_puts(m, "echo 3 0x13 0x0 > /proc/sdio\n"); seq_puts(m, "multi write cmd format:\n"); seq_puts(m, "echo 4 0x13 0x0 0xvalue > /proc/sdio\n"); seq_puts(m, "Notice:value is the read result!\n"); seq_puts(m, "reset sdio cmd format:\n"); seq_puts(m, "echo 5 0x0 0x0 > /proc/sdio\n"); seq_puts(m, "throughput read cmd format:\n"); seq_puts(m, "echo 6 0xb0 0x1 > /proc/sdio\n"); seq_puts(m, "throughtput write cmd format:\n"); seq_puts(m, "echo 7 0xb0 0x1 > /proc/sdio\n"); seq_puts(m, "stress test cmd format:\n"); seq_puts(m, "echo 8 0x0 0x1 > /proc/sdio\n"); seq_puts(m, "function enable cmd format:\n"); seq_puts(m, "echo 9 0x0/0x1 0xfunc_number > /proc/sdio\n"); seq_puts(m, "de-sense read cmd format:\n"); seq_puts(m, "echo a 0xb0 0x1 > /proc/sdio\n"); seq_puts(m, "de-sense write cmd format:\n"); seq_puts(m, "echo b 0xb0 0x1 > /proc/sdio\n"); seq_puts(m, "=========================================\n"); return 0; } static int sdio_tuning(void) { int err = 0; err = mmc_send_tuning(host, MMC_SEND_TUNING_BLOCK, NULL); return err; } /* * This function can be used to do HQA.SDIO SHPA for read. * HQA: for SDR104 mode, it is better to set 0xb8(func1) to 0x5a. * HQA: for SD3.0 plus, it is better to set 0xb8(func1) to 0x33cc. */ static int multi_read(struct sdio_func *func, struct mmc_host *host) { int err, i; u32 data, value, count_rw = 0; unsigned long total = 0; unsigned char *fac_buf = NULL; KAL_TIME_INTERVAL_DECLARATION(); __kernel_suseconds_t usec; func->cur_blksize = 0x200; fac_buf = kmalloc(0x400, GFP_KERNEL); if (!fac_buf) return FAIL; /* This function can be used to do HQA.SDIO SHPA */ data = sdio_f0_readb(func, SDIO_CCCR_MTK_DDR208, &err); if (err) { kfree(fac_buf); return FAIL; } else if ((data & 0x3) == 0x3) { value = 0x33cc33cc; err = sdio_writesb(func, 0x0, &value, 0x4); dev_info(mmc_dev(host), "[sd3.0+]: value is 0x33cc\n"); } else { value = 0x5a5a5a5a; //err = sdio_writesb(func, 0x0, &value, 0x4); dev_info(mmc_dev(host), "[sd3.0]: value is 0x5a5a\n"); } //value = 0; //err = sdio_readsb(func, &value, 0x0, 0x4); i = 0; /* byte to bit */ total = 0x400 * 0x10; do { if (i % 0x10 == 0) KAL_REC_TIME_START(); //dev_info(mmc_dev(host), //"[sd3.0]: value is 0x5a5a try read\n"); err = sdio_readsb(func, fac_buf, 0x0, 0x400); //dev_info(mmc_dev(host), //"[sd3.0]: value is 0x5a5a try read end\n"); if (err) count_rw = count_rw + 1; i = i + 1; if ((i / 0x10) && (i % 0x10 == 0)) { KAL_REC_TIME_END(); usec = KAL_GET_TIME_INTERVAL(); dev_info(mmc_dev(host), "read: %lu Kbps, err:%x.\n", total / (usec / USEC_PER_MSEC), count_rw); } } while (i < 0x10); kfree(fac_buf); return SUCCESS; } /** * This function can be used to do de-sense.HQA.SHPA for write. * HQA: for SDR104 mode, it is better to set 0xb8(func1) to 0x5a. * HQA: for SD3.0 plus, it is better to set 0xb8(func1) to 0x33cc. * de_sense: data is random or not. */ static int multi_write(struct sdio_func *func, struct mmc_host *host, int de_sense) { int err, i; u32 data, count_rw = 0; unsigned long total = 0; unsigned char *fac_buf = NULL; KAL_TIME_INTERVAL_DECLARATION(); __kernel_suseconds_t usec; func->cur_blksize = 0x200; fac_buf = kmalloc(0x400, GFP_KERNEL); if (!fac_buf) return FAIL; /* This function can be used to do de-sense.HQA.SHPA */ data = sdio_f0_readb(func, SDIO_CCCR_MTK_DDR208, &err); if (err) { kfree(fac_buf); return FAIL; } if (de_sense) { //err = wait_for_random_bytes(); if (!err) dev_info(mmc_dev(host), "wait random bytes success.\n"); //get_random_bytes(fac_buf, 0x10000); } else { memset(fac_buf, 0x5a, 0x400); dev_info(mmc_dev(host), "[sd3.0]: value is 0x5a5a\n"); } i = 0; /* byte to bit */ total = 0x400 * 0x10; do { if (i % 0x10 == 0) KAL_REC_TIME_START(); err = sdio_writesb(func, 0x0, fac_buf, 0x400); if (err) count_rw = count_rw + 1; i = i + 1; if ((i / 0x10) && (i % 0x10 == 0)) { KAL_REC_TIME_END(); usec = KAL_GET_TIME_INTERVAL(); dev_info(mmc_dev(host), "write: %lu Kbps, err:%x\n", total / (usec / USEC_PER_MSEC), count_rw); } } while (i < 0x10); kfree(fac_buf); return SUCCESS; } static int sdio_stress_test(struct sdio_func *func, struct mmc_host *host) { int ret; while (1) { ret = multi_write(func, host, 0); sdio_release_host(func); if (ret) return ret; msleep(20); sdio_claim_host(func); ret = multi_read(func, host); if (ret) return ret; } } /** * sdio_proc_write - read/write sdio function register. */ static ssize_t sdio_proc_write(struct file *file, const char *buf, size_t count, loff_t *f_pos) { struct mmc_card *card; struct sdio_func *func; struct mmc_ios *ios; char *cmd_buf, *str_hand; unsigned char *fac_buf = NULL; unsigned int cmd, addr, fn, value, hqa_result, offset, option; unsigned char result; int i = 0, ret; unsigned long count_r = 0, count_w = 0, total = 0; KAL_TIME_INTERVAL_DECLARATION(); __kernel_suseconds_t usec; WARN_ON(!host); ios = &host->ios; card = host->card; cmd_buf = kzalloc((count + 1), GFP_KERNEL); if (!cmd_buf) return count; str_hand = kzalloc(2, GFP_KERNEL); if (!str_hand) { kfree(cmd_buf); return count; } func = kzalloc(sizeof(struct sdio_func), GFP_KERNEL); if (!func) { kfree(cmd_buf); kfree(str_hand); return count; } ret = copy_from_user(cmd_buf, buf, count); if (ret < 0) goto end; *(cmd_buf + count) = '\0'; ret = sscanf(cmd_buf, "%x %x %x %x %x %x", &cmd, &addr, &fn, &value, &offset, &option); if (ret == 0) { ret = sscanf(cmd_buf, "%s", str_hand); if (ret == 0) dev_info(mmc_dev(host), "please enter cmd.\n"); goto end; } if (cmd == Tune) fn = 0; /* Judge whether request fn is over the max functions. */ if (fn > card->sdio_funcs) { dev_info(mmc_dev(host), "the fn is over the max sdio funcs.\n"); goto end; } if (fn) { /** * The test read/write api don't need more func * information. So we just use the card & func->num * to the new struct func. */ if (card->sdio_func[fn - 1]) { func->card = card; func->num = card->sdio_func[fn - 1]->num; func->tuples = card->sdio_func[fn - 1]->tuples; func->tmpbuf = card->sdio_func[fn - 1]->tmpbuf; hqa_result = card->sdio_func[fn - 1]->max_blksize; func->max_blksize = hqa_result; if ((cmd == Hqa_read) || (cmd == Hqa_write)) { func->cur_blksize = 8; } else if ((cmd == Stress_write) || (cmd == Stress_read) || (cmd == Desense_write) || (cmd == Desense_read)) { func->cur_blksize = 0x200; fac_buf = kmalloc(0x40000, GFP_KERNEL); if (!fac_buf) goto end; memset(fac_buf, 0x3c, 0x40000); } else { func->cur_blksize = 1; } } else { dev_info(mmc_dev(host), "func %d is null,.\n", fn); } } else { /** * function 0 should not need struct func. * but the api need the parameter, so we create * the a new func for function 0. */ func->card = card; func->tuples = card->tuples; func->num = 0; func->max_blksize = 16; if ((cmd == Hqa_read) || (cmd == Hqa_write)) func->cur_blksize = 16; else func->cur_blksize = 1; func->tmpbuf = kmalloc(func->cur_blksize, GFP_KERNEL); if (!func->tmpbuf) goto end; memset(func->tmpbuf, 0, func->cur_blksize); } sdio_claim_host(func); switch (cmd) { case Read: dev_info(mmc_dev(host), "read addr:%x, fn:%d.\n", addr, fn); ret = 0; if (fn == 0) result = sdio_f0_readb(func, addr, &ret); else result = sdio_readb(func, addr, &ret); if (ret) dev_info(mmc_dev(host), "Read fail(%d).\n", ret); else dev_info(mmc_dev(host), "f%d reg(%x) is 0x%02x.\n", func->num, addr, result); break; case Write: dev_info(mmc_dev(host), "write addr:%x, value:%x, fn:%d.\n", addr, (u8)value, fn); ret = 0; if (fn == 0) /* (0xF0 - 0xFF) are permiited for func0 */ sdio_f0_writeb(func, (u8)value, addr, &ret); else sdio_writeb(func, (u8)value, addr, &ret); if (ret) dev_info(mmc_dev(host), "write fail(%d).\n", ret); else dev_info(mmc_dev(host), "write success(%d).\n", ret); break; case Tune: value = 0; do { result = sdio_tuning(); if (result) value = value + 1; i = i + 1; } while (i < 64); if (value) dev_info(mmc_dev(host), "test fail.\n"); else dev_info(mmc_dev(host), "test well.\n"); break; case Hqa_read: dev_info(mmc_dev(host), "read addr:%x, fn %d\n", addr, fn); hqa_result = 0; ret = 0; hqa_result = sdio_readl(func, addr, &ret); if (ret) dev_info(mmc_dev(host), "Read f%d reg(%x) fail(%d).\n", func->num, addr, ret); dev_info(mmc_dev(host), "Read result: 0x%02x.\n", hqa_result); break; case Hqa_write: dev_info(mmc_dev(host), "write addr:%x, value:%x, fn %d\n", addr, value, fn); hqa_result = 0; ret = 0; sdio_writel(func, value, addr, &ret); if (ret) dev_info(mmc_dev(host), "write f%d reg(%x) fail(%d).\n", func->num, addr, ret); dev_info(mmc_dev(host), "write success(%d).\n", ret); break; case Reset: mmc_hw_reset(host); break; case Stress_read: i = 0; /* byte to bit */ total = 0x6400000 * 8; dev_info(mmc_dev(host), "START test SDIO read throughput\n"); KAL_REC_TIME_START(); do { ret = sdio_readsb(func, fac_buf, addr, 0x40000); if (ret) count_r = count_r + 1; i = i + 1; } while (i < 0x1900); KAL_REC_TIME_END(); usec = KAL_GET_TIME_INTERVAL(); dev_info(mmc_dev(host), "%lu Kbps, err:%lx\n", total / (usec / USEC_PER_MSEC) * 0x10, count_r); break; case Stress_write: i = 0; /* byte to bit */ total = 0x6400000 * 8; dev_info(mmc_dev(host), "START test SDIO write throughput\n"); KAL_REC_TIME_START(); do { ret = sdio_writesb(func, addr, fac_buf, 0x40000); if (ret) count_w = count_w + 1; i = i + 1; } while (i < 0x1900); KAL_REC_TIME_END(); usec = KAL_GET_TIME_INTERVAL(); dev_info(mmc_dev(host), "%lu Kbps, err:%lx\n", total / (usec / USEC_PER_MSEC) * 0x10, count_w); break; case Stress_test: ret = sdio_stress_test(func, host); if (ret) { dev_info(mmc_dev(host), "IO Stress Test Fail!!!\n"); sdio_release_host(func); goto end; } break; case Func_ctrl: /* addr is func ctrl value in this case*/ if (addr) ret = sdio_enable_func(func); else ret = sdio_disable_func(func); break; case Desense_read: i = 0; memset(fac_buf, 0x3c, 0x40000); // init 0xb0 to pattern 0x3c firstly ret = sdio_writesb(func, 0xb8, fac_buf, 0x40000); dev_info(mmc_dev(host), "START test SDIO read, pattern: 0x3c\n"); dev_info(mmc_dev(host), "reading data..\n"); do { ret = sdio_readsb(func, fac_buf, addr, 0x40000); if (ret) count_r = count_r + 1; i = i + 1; } while (i < 0x1900); i = 0; dev_info(mmc_dev(host), "Get data start with:\n"); for (; i < 0x10; i++) { //if (i && !(i%8)) // dev_info(mmc_dev(host), "\n"); dev_info(mmc_dev(host), "0x%x ", ((int *)fac_buf)[i]); } dev_info(mmc_dev(host), "stop reading data..\n"); break; case Desense_write: i = 0; dev_info(mmc_dev(host), "Generating random data...\n"); get_random_bytes(fac_buf, 0x40000); dev_info(mmc_dev(host), "random data start with:\n"); for (; i < 0x10; i++) { //if (i && !(i%8)) // dev_info(mmc_dev(host), "\n"); dev_info(mmc_dev(host), "0x%x ", ((int *)fac_buf)[i]); } i = 0; dev_info(mmc_dev(host), "START SDIO desensen write test, pattern: random\n"); dev_info(mmc_dev(host), "writing random data...\n"); do { ret = sdio_writesb(func, addr, fac_buf, 0x40000); if (ret) count_w = count_w + 1; i = i + 1; } while (i < 0x1900); dev_info(mmc_dev(host), "stop writing data..\n"); break; default: dev_info(mmc_dev(host), "cmd is not valid.\n"); break; } sdio_release_host(func); end: kfree(str_hand); kfree(cmd_buf); kfree(func); return count; } /** * open function show some stable information. */ static int sdio_proc_open(struct inode *inode, struct file *file) { return single_open(file, sdio_proc_show, inode->i_private); } /** * sdio pre is our own function. * seq or single pre is the kernel function. */ static const struct file_operations sdio_proc_fops = { .owner = THIS_MODULE, .open = sdio_proc_open, .release = single_release, .write = sdio_proc_write, .read = seq_read, .llseek = seq_lseek, }; int sdio_proc_init(struct mmc_host *host_init) { struct proc_dir_entry *prEntry; host = host_init; prEntry = proc_create("sdio", 0660, NULL, &sdio_proc_fops); if (prEntry) dev_info(mmc_dev(host), "/proc/sdio is created.\n"); else dev_info(mmc_dev(host), "create /proc/sdio failed.\n"); return 0; }