// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include "spicc_test.h" static LIST_HEAD(testdev_list); int spicc_make_argv(char *s, int argvsz, char *argv[], char *delim) { char *tok; int i; /* split into argv */ for (i = 0; i < argvsz; i++) { tok = strsep(&s, delim); if (!tok) break; if (*tok == '\0') tok = strsep(&s, delim); tok = strim(tok); argv[i] = tok; } return i; } int spicc_getopt(int argc, char *argv[], char *name, long *value, char **str, unsigned int base) { long v; char *s; int i, ret; for (i = 0; i < argc; i++) { s = argv[i] + strlen(name); ret = memcmp(name, argv[i], strlen(name)); if (!ret && ((*s == ' ') || (*s == '\0'))) { if (value) { strreplace(s + 1, 'n', '-'); ret = kstrtol(s + 1, base, &v); if (!ret) *value = v; } if (str) *str = s + 1; return ret; } } return -EINVAL; } void spicc_strtohex(char *str, int pass, u8 *buf, int len, bool fill_next) { char *token; unsigned long v; int i; /* pass over */ for (i = 0; i < pass; i++) strsep(&str, ", "); /* filled buffer with str data */ for (i = 0; i < len; i++) { token = strsep(&str, ", "); if (token == 0 || kstrtoul(token, 16, &v)) break; buf[i] = (u8)(v & 0xff); } /* set first tx data default 1 if no any str data */ if (i == 0) { buf[0] = 0x1; i++; } /* fill next buffer incrementally */ for (; i < len && fill_next; i++) buf[i] = buf[i - 1] + 1; } int spicc_compare(u8 *buf1, u8 *buf2, int len, bool pr_diff) { int i, diff = 0; for (i = 0; i < len; i++) { if (buf1[i] != buf2[i]) { diff++; if (pr_diff) pr_info("[%d]: 0x%x, 0x%x\n", i, buf1[i], buf2[i]); } } pr_info("total %d, diff %d\n", len, diff); return diff; } static void testdev_print_xfer(struct test_device *testdev) { struct spi_transfer *xfer; list_for_each_entry(xfer, &testdev->msg.transfers, transfer_list) { if (xfer && xfer->rx_buf) { if (testdev->hexdump) spicc_hexdump(xfer->rx_buf, xfer->len); if (testdev->compare && xfer->tx_buf) spicc_compare((u8 *)xfer->tx_buf, xfer->rx_buf, xfer->len, testdev->pr_diff); } } } static void testdev_clean_rxbuf(struct test_device *testdev) { struct spi_transfer *xfer; list_for_each_entry(xfer, &testdev->msg.transfers, transfer_list) { if (xfer && xfer->rx_buf) memset(xfer->rx_buf, 0, xfer->len); } } static struct spi_transfer * testdev_get_current_xfer(struct test_device *testdev) { struct spi_transfer *xfer = NULL; list_for_each_entry(xfer, &testdev->msg.transfers, transfer_list) { if (list_is_last(&xfer->transfer_list, &testdev->msg.transfers)) break; } return xfer; } ssize_t testdev_dump(struct test_device *testdev, char *buf) { struct spi_device *spi = testdev->spi; struct spicc_controller_data *cdata; ssize_t len; cdata = (struct spicc_controller_data *)spi->controller_data; len = snprintf(buf, PAGE_SIZE, "hexdump: %d\n", testdev->hexdump); len += snprintf(buf + len, PAGE_SIZE, "compare: %d\n", testdev->compare); len += snprintf(buf + len, PAGE_SIZE, "pr_diff: %d\n", testdev->pr_diff); if (spi->cs_gpiod[0]) len += snprintf(buf + len, PAGE_SIZE, "cs_gpio: %d\n", desc_to_gpio(spi->cs_gpiod[0])); len += snprintf(buf + len, PAGE_SIZE, "cs: %d\n", spi_get_chipselect(spi, 0)); len += snprintf(buf + len, PAGE_SIZE, "speed: %d\n", spi->max_speed_hz); len += snprintf(buf + len, PAGE_SIZE, "mode: 0x%x\n", spi->mode); len += snprintf(buf + len, PAGE_SIZE, "bw: %d\n", spi->bits_per_word); len += snprintf(buf + len, PAGE_SIZE, "use_dirspi: %d\n", cdata->use_dirspi); len += snprintf(buf + len, PAGE_SIZE, "timing_en: %d\n", cdata->timing_en); len += snprintf(buf + len, PAGE_SIZE, "ss_leading_gap: %d\n", cdata->ss_leading_gap); len += snprintf(buf + len, PAGE_SIZE, "ss_trailing_gap: %d\n", cdata->ss_trailing_gap); len += snprintf(buf + len, PAGE_SIZE, "tx_tuning: %d\n", cdata->tx_tuning); len += snprintf(buf + len, PAGE_SIZE, "rx_tuning: %d\n", cdata->rx_tuning); len += snprintf(buf + len, PAGE_SIZE, "miso_latency: %d, enable %d\n", cdata->miso_latency, cdata->miso_latency_en); len += snprintf(buf + len, PAGE_SIZE, "dummy: %d\n", cdata->dummy_ctl); len += snprintf(buf + len, PAGE_SIZE, "word_gap: %d\n", cdata->word_gap); len += snprintf(buf + len, PAGE_SIZE, "mosi_idle_level: %d\n", cdata->mosi_idle_level); len += snprintf(buf + len, PAGE_SIZE, "pclk_rate: %d\n", cdata->pclk_rate); len += snprintf(buf + len, PAGE_SIZE, "nxfers: %d\n", testdev->nxfers); len += snprintf(buf + len, PAGE_SIZE, "split_xfer_en: %d\n", cdata->split_xfer_en); return len; } struct test_device *testdev_get(struct device *dev) { struct test_device *testdev; list_for_each_entry(testdev, &testdev_list, list) { if (!strcmp(dev_name(dev), dev_name(&testdev->spi->controller->dev))) return testdev; } return NULL; } static int testdev_thread(void *data) { struct test_device *testdev = (struct test_device *)data; int count = 0, ret; while (1) { wait_for_completion_interruptible(&testdev->xfer_start); if (!atomic_read(&testdev->status)) { count++; atomic_set(&testdev->status, 1); ret = spi_sync(testdev->spi, &testdev->msg); if (ret) { dev_err(&testdev->spi->controller->dev, "ret %d, count %d\n", ret, count); count = 0; } atomic_set(&testdev->status, ret); } if (kthread_should_stop()) { dev_info(&testdev->spi->controller->dev, "exit thread, count %d\n", count); break; } } return 0; } static enum hrtimer_restart testdev_timer_handle(struct hrtimer *t) { struct test_device *testdev; testdev = container_of(t, struct test_device, timer); complete(&testdev->xfer_start); hrtimer_forward_now(&testdev->timer, ms_to_ktime(testdev->interval)); return HRTIMER_RESTART; } struct test_device *testdev_new(struct device *dev, int argc, char *argv[]) { struct test_device *testdev; struct spi_controller *ctlr; struct spi_device *spi; u8 idx; testdev = testdev_get(dev); if (testdev) { dev_warn(dev, "testdev exist already\n"); return testdev; } ctlr = container_of(dev, struct spi_controller, dev); testdev = kzalloc(sizeof(*testdev), GFP_KERNEL); spi = spi_alloc_device(ctlr); if (IS_ERR_OR_NULL(spi)) { kfree(testdev); dev_err(dev, "spi device alloc failed\n"); return NULL; } testdev->spi = spi; spi->controller_data = (void *)&testdev->cdata; spi->controller_state = NULL; memset(spi->controller_data, 0, sizeof(struct spicc_controller_data)); for (idx = 0; idx < SPI_CS_CNT_MAX; idx++) spi_set_chipselect(spi, idx, SPI_INVALID_CS); spi->cs_index_mask = BIT(0); spi->max_speed_hz = 10000000; spi->bits_per_word = 8; spi->mode = 0; spi->cs_setup.unit = -ENODEV; spi->cs_hold.unit = -ENODEV; spi_message_init(&testdev->msg); if (testdev_setup(testdev, argc, argv)) { spi_dev_put(spi); kfree(testdev); testdev = NULL; dev_err(dev, "testdev new failed\n"); } else { spi_add_device(spi); list_add_tail(&testdev->list, &testdev_list); dev_info(dev, "testdev new success\n"); } return testdev; } int testdev_free(struct test_device *testdev) { if (device_is_registered(&testdev->spi->dev)) spi_unregister_device(testdev->spi); else spi_dev_put(testdev->spi); testdev_free_xfer(testdev); list_del(&testdev->list); dev_info(&testdev->spi->controller->dev, "testdev free\n"); kfree(testdev); return 0; } int testdev_setup(struct test_device *testdev, int argc, char *argv[]) { struct spi_device *spi; struct spicc_controller_data *cdata; char *data_str; long v; int ret; spi = testdev->spi; if (!spicc_getopt(argc, argv, "hexdump", &v, NULL, 10)) testdev->hexdump = !!v; if (!spicc_getopt(argc, argv, "compare", &v, NULL, 10)) testdev->compare = !!v; if (!spicc_getopt(argc, argv, "pr_diff", &v, NULL, 10)) testdev->pr_diff = !!v; if (!spicc_getopt(argc, argv, "cs_gpio", &v, NULL, 10)) spi->cs_gpiod[0] = (v > 0) ? gpio_to_desc(v) : NULL; if (!spicc_getopt(argc, argv, "cs", NULL, &data_str, 0)) spicc_strtohex(data_str, 0, spi->chip_select, SPI_CS_CNT_MAX, false); if (!spicc_getopt(argc, argv, "speed", &v, NULL, 10)) { struct spi_transfer *xfer; spi->max_speed_hz = v; list_for_each_entry(xfer, &testdev->msg.transfers, transfer_list) { xfer->speed_hz = v; } } if (!spicc_getopt(argc, argv, "mode", &v, NULL, 16)) spi->mode = v; if (!spicc_getopt(argc, argv, "bw", &v, NULL, 10)) spi->bits_per_word = v; cdata = (struct spicc_controller_data *)spi->controller_data; if (!spicc_getopt(argc, argv, "use_dirspi", &v, NULL, 10)) cdata->use_dirspi = v; if (!spicc_getopt(argc, argv, "timing_en", &v, NULL, 10)) cdata->timing_en = v; if (!spicc_getopt(argc, argv, "ss_leading_gap", &v, NULL, 10)) cdata->ss_leading_gap = v; if (!spicc_getopt(argc, argv, "ss_trailing_gap", &v, NULL, 10)) cdata->ss_trailing_gap = v; if (!spicc_getopt(argc, argv, "tx_tuning", &v, NULL, 10)) cdata->tx_tuning = v; if (!spicc_getopt(argc, argv, "rx_tuning", &v, NULL, 10)) cdata->rx_tuning = v; if (!spicc_getopt(argc, argv, "dummy", &v, NULL, 10)) cdata->dummy_ctl = v; if (!spicc_getopt(argc, argv, "dc_mode", &v, NULL, 10)) cdata->dc_mode = v; if (!spicc_getopt(argc, argv, "dc_level", &v, NULL, 10)) cdata->dc_level = v; if (!spicc_getopt(argc, argv, "read_turn_around", &v, NULL, 10)) cdata->read_turn_around = v; if (!spicc_getopt(argc, argv, "dma_trig_delay", &v, NULL, 10)) cdata->dma_trig_delay = v; if (!spicc_getopt(argc, argv, "use_ctrl_cs", &v, NULL, 10)) cdata->use_ctrl_cs = v; if (!spicc_getopt(argc, argv, "miso_latency", &v, NULL, 10)) cdata->miso_latency = v; if (!spicc_getopt(argc, argv, "miso_latency_en", &v, NULL, 10)) cdata->miso_latency_en = v; if (!spicc_getopt(argc, argv, "cs_setup", &v, NULL, 10)) spi->cs_setup.value = v; if (!spicc_getopt(argc, argv, "cs_setup_unit", &v, NULL, 10)) spi->cs_setup.unit = v; if (!spicc_getopt(argc, argv, "cs_hold", &v, NULL, 10)) spi->cs_hold.value = v; if (!spicc_getopt(argc, argv, "cs_hold_unit", &v, NULL, 10)) spi->cs_hold.unit = v; if (!spicc_getopt(argc, argv, "word_gap", &v, NULL, 10)) cdata->word_gap = v; if (!spicc_getopt(argc, argv, "mosi_idle_level", &v, NULL, 10)) cdata->mosi_idle_level = v; if (!spicc_getopt(argc, argv, "pclk_rate", &v, NULL, 10)) cdata->pclk_rate = v; if (!spicc_getopt(argc, argv, "split_xfer_en", &v, NULL, 10)) cdata->split_xfer_en = v; ret = spi_setup(spi); dev_info(&spi->controller->dev, "spi device setup %s\n", ret ? "failed" : "success"); return ret; } int testdev_new_xfer(struct test_device *testdev, int argc, char *argv[]) { struct device *dev; struct spi_transfer *xfer; char *data_str; long v; bool vm = false, coherent = false; dev = testdev->spi->controller->dev.parent; xfer = kzalloc(sizeof(*xfer), GFP_KERNEL); if (!spicc_getopt(argc, argv, "vm", NULL, NULL, 0)) vm = true; if (!spicc_getopt(argc, argv, "coherent", NULL, NULL, 0)) coherent = true; if (!spicc_getopt(argc, argv, "speed", &v, NULL, 10)) xfer->speed_hz = v; if (!spicc_getopt(argc, argv, "bw", &v, NULL, 10)) xfer->bits_per_word = v; if (!spicc_getopt(argc, argv, "txnbits", &v, NULL, 10)) xfer->tx_nbits = v; if (!spicc_getopt(argc, argv, "rxnbits", &v, NULL, 10)) xfer->rx_nbits = v; if (!spicc_getopt(argc, argv, "cs_change", NULL, NULL, 0)) xfer->cs_change = true; if (!spicc_getopt(argc, argv, "len", &v, NULL, 10)) xfer->len = v; if (!xfer->len) { dev_err(dev, "data length invalid\n"); return -EINVAL; } if (spicc_getopt(argc, argv, "notx", NULL, NULL, 0)) { if (vm) { xfer->tx_buf = vmalloc(xfer->len); } else if (coherent) { xfer->tx_buf = dma_alloc_coherent(dev, xfer->len, &xfer->tx_dma, GFP_KERNEL | GFP_DMA); } else { xfer->tx_buf = kzalloc(xfer->len, GFP_KERNEL | GFP_DMA); } if (IS_ERR_OR_NULL(xfer->tx_buf)) { dev_err(dev, "alloc tx buf failed\n"); return -ENOMEM; } spicc_getopt(argc, argv, "data", NULL, &data_str, 0); spicc_strtohex(data_str, 0, (u8 *)xfer->tx_buf, xfer->len, true); } if (spicc_getopt(argc, argv, "norx", NULL, NULL, 0)) { if (vm) { xfer->rx_buf = vmalloc(xfer->len); } else if (coherent) { xfer->rx_buf = dma_alloc_coherent(dev, xfer->len, &xfer->rx_dma, GFP_KERNEL | GFP_DMA); } else { xfer->rx_buf = kzalloc(xfer->len, GFP_KERNEL | GFP_DMA); } if (IS_ERR_OR_NULL(xfer->rx_buf)) { dev_err(dev, "alloc rx buf failed\n"); kfree(xfer->tx_buf); return -ENOMEM; } } if (!xfer->tx_buf && !xfer->rx_buf) { dev_err(dev, "either tx or rx must be exist\n"); return -EINVAL; } spi_message_add_tail(xfer, &testdev->msg); testdev->nxfers++; dev_info(dev, "add a xfer success(%s)\n", vm ? "vm" : (coherent ? "coherent" : "kalloc")); return 0; } void testdev_free_xfer(struct test_device *testdev) { struct device *dev = testdev->spi->controller->dev.parent; struct spi_transfer *xfer; list_for_each_entry(xfer, &testdev->msg.transfers, transfer_list) { if (xfer->tx_buf) { if (is_vmalloc_addr(xfer->tx_buf)) vfree(xfer->tx_buf); else if (xfer->tx_dma) dma_free_coherent(dev, xfer->len, (void *)xfer->tx_buf, xfer->tx_dma); else kfree(xfer->tx_buf); } if (xfer->rx_buf) { if (is_vmalloc_addr(xfer->rx_buf)) vfree(xfer->rx_buf); else if (xfer->rx_dma) dma_free_coherent(dev, xfer->len, xfer->rx_buf, xfer->rx_dma); else kfree(xfer->rx_buf); } kfree(xfer); } spi_message_init(&testdev->msg); testdev->nxfers = 0; dev_info(dev, "all xfers free\n"); } static void testdev_complete(void *arg) { complete(arg); } int testdev_run(struct test_device *testdev, int argc, char *argv[]) { struct device *dev; struct spi_device *spi; struct spicc_controller_data *cdata; struct spi_transfer *xfer; char *data_str; long v; unsigned long t1, t2; DECLARE_COMPLETION_ONSTACK(done); int ret = -EIO; spi = testdev->spi; dev = &testdev->spi->controller->dev; testdev_clean_rxbuf(testdev); cdata = (struct spicc_controller_data *)spi->controller_data; if (!spicc_getopt(argc, argv, "cs", &v, NULL, 0)) spi->cs_index_mask = BIT(v); else spi->cs_index_mask = BIT(0); if (!spicc_getopt(argc, argv, "spi_sync", NULL, NULL, 0)) { ret = spi_sync(spi, &testdev->msg); if (!ret) { dev_info(dev, "spi_sync test success\n"); testdev_print_xfer(testdev); } else { dev_info(dev, "spi_sync test failed\n"); } } else if (cdata->dirspi_async && !spicc_getopt(argc, argv, "dirspi_async", NULL, NULL, 0)) { xfer = testdev_get_current_xfer(testdev); ret = cdata->dirspi_async(testdev->spi, xfer->tx_dma, xfer->rx_dma, xfer->len, testdev_complete, &done); if (ret > 0) { ret = wait_for_completion_timeout(&done, msecs_to_jiffies(200)); ret = ret ? 0 : -ETIMEDOUT; } if (!ret) { dev_info(dev, "dirspi_async test success\n"); testdev_print_xfer(testdev); } else { dev_info(dev, "dirspi_async test failed\n"); } } else if (cdata->dirspi_sync && !spicc_getopt(argc, argv, "dirspi_sync", NULL, NULL, 0)) { xfer = testdev_get_current_xfer(testdev); ret = cdata->dirspi_sync(testdev->spi, xfer->tx_dma, xfer->rx_dma, xfer->len); if (!ret) { dev_info(dev, "dirspi_sync test success\n"); testdev_print_xfer(testdev); } else { dev_info(dev, "dirspi_sync test failed\n"); } } else if (cdata->dirspi_xfer && !spicc_getopt(argc, argv, "dirspi_xfer", NULL, NULL, 0)) { xfer = testdev_get_current_xfer(testdev); t1 = ktime_get_ns(); ret = cdata->dirspi_xfer(testdev->spi, (u8 *)xfer->tx_buf, xfer->rx_buf, xfer->len); t2 = ktime_get_ns(); dev_info(dev, "time consume %lu ns\n", t2 - t1); if (!ret) { dev_info(dev, "dirspi_xfer test success\n"); testdev_print_xfer(testdev); } else { dev_info(dev, "dirspi_xfer test failed\n"); } } else if (cdata->dirspi_stop && !spicc_getopt(argc, argv, "dirspi_stop", NULL, NULL, 0)) { cdata->dirspi_stop(spi); ret = 0; dev_info(dev, "dirspi stop!\n"); } else if (cdata->dirspi_dma_trig && !spicc_getopt(argc, argv, "trig", &v, NULL, 0)) { xfer = testdev_get_current_xfer(testdev); ret = cdata->dirspi_dma_trig(spi, xfer->tx_dma, xfer->rx_dma, xfer->len, v); dev_info(dev, "set trig mode %lu\n", v); } else if (cdata->dirspi_dma_trig_start && !spicc_getopt(argc, argv, "trig_start", NULL, NULL, 0)) { ret = cdata->dirspi_dma_trig_start(spi); dev_info(dev, "trig start!\n"); } else if (cdata->dirspi_dma_trig_stop && !spicc_getopt(argc, argv, "trig_stop", NULL, NULL, 0)) { ret = cdata->dirspi_dma_trig_stop(spi); dev_info(dev, "trig stop!\n"); } else if (cdata->dirspi_dma_trig_release && !spicc_getopt(argc, argv, "trig_release", NULL, NULL, 0)) { ret = cdata->dirspi_dma_trig_release(spi); dev_info(dev, "trig released!\n"); } else if (cdata->dirspi_busy_proc && !spicc_getopt(argc, argv, "busy_proc", NULL, NULL, 0)) { ret = cdata->dirspi_busy_proc(spi); dev_info(dev, "busy proc done! (%d)\n", ret); } if (!spicc_getopt(argc, argv, "update_tx_data", NULL, &data_str, 0)) { xfer = testdev_get_current_xfer(testdev); if (xfer) { spicc_strtohex(data_str, 0, (u8 *)xfer->tx_buf, xfer->len, true); dev_info(&spi->controller->dev, "tx data updated\n"); } return 0; } if (!spicc_getopt(argc, argv, "run_thread", &v, NULL, 0)) { if (testdev->kthread) { if (atomic_read(&testdev->status) < 0) atomic_set(&testdev->status, 0); dev_warn(dev, "thread exist already\n"); return 0; } init_completion(&testdev->xfer_start); testdev->kthread = kthread_run(testdev_thread, (void *)testdev, "spi-test"); hrtimer_init(&testdev->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); testdev->timer.function = testdev_timer_handle; testdev->interval = v; atomic_set(&testdev->status, 0); hrtimer_start(&testdev->timer, ms_to_ktime(v), HRTIMER_MODE_REL); dev_info(dev, "run %lu ms timer thread\n", v); return 0; } if (!spicc_getopt(argc, argv, "stop_thread", NULL, NULL, 0)) { if (!testdev->kthread) { dev_warn(dev, "no thread\n"); return 0; } kthread_stop(testdev->kthread); testdev->kthread = NULL; hrtimer_cancel(&testdev->timer); dev_info(dev, "stop thread\n"); return 0; } if (ret == -EIO) dev_err(dev, "entry unsupport\n"); return ret; } #define TEST_PARAM_NUM 5 int test(struct device *dev, const char *buf) { unsigned int cs_gpio, speed, mode, bits_per_word, num; u8 *tx_buf, *rx_buf; char *kstr; int ret; struct spi_transfer t; struct spi_message m; struct spi_controller *ctlr; if (sscanf(buf, "%d%d%x%d%d", &cs_gpio, &speed, &mode, &bits_per_word, &num) != TEST_PARAM_NUM) { dev_err(dev, "error format\n"); return -EIO; } kstr = kstrdup(buf, GFP_KERNEL); tx_buf = kzalloc(num, GFP_KERNEL | GFP_DMA); rx_buf = kzalloc(num, GFP_KERNEL | GFP_DMA); if (IS_ERR(kstr) || IS_ERR(tx_buf) || IS_ERR(rx_buf)) { dev_err(dev, "failed to alloc tx rx buffer\n"); ret = -ENOMEM; goto test_end2; } /* pass over "cs_gpio speed mode bits_per_word num" */ spicc_strtohex(kstr, TEST_PARAM_NUM, tx_buf, num, true); spi_message_init(&m); ctlr = container_of(dev, struct spi_controller, dev); m.spi = spi_alloc_device(ctlr); if (IS_ERR_OR_NULL(m.spi)) { dev_err(dev, "spi alloc failed\n"); ret = -ENODEV; goto test_end; } m.spi->cs_gpiod[0] = (cs_gpio > 0) ? gpio_to_desc(cs_gpio) : NULL; m.spi->max_speed_hz = speed; m.spi->mode = mode & 0xffff; m.spi->bits_per_word = bits_per_word; if (spi_setup(m.spi)) { dev_err(dev, "setup failed\n"); ret = -EIO; goto test_end; } memset(&t, 0, sizeof(t)); t.tx_buf = (void *)tx_buf; t.rx_buf = (void *)rx_buf; t.len = num; spi_message_add_tail(&t, &m); ret = spi_sync(m.spi, &m); if (!ret) spicc_compare(tx_buf, rx_buf, num, !!(mode & BIT(16))); dev_info(dev, "%s ret %d\n", __func__, ret); test_end: //spi_cleanup(m.spi); spi_dev_put(m.spi); test_end2: kfree(kstr); kfree(tx_buf); kfree(rx_buf); return ret; }