/*drivers/spi/spi-rockchip-test.c -spi test driver * * * 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. */ /* how to test spi * echo write 0 10 255 > /dev/spi_misc_test * echo write 0 10 255 init.rc > /dev/spi_misc_test * echo read 0 10 255 > /dev/spi_misc_test * echo loop 0 10 255 > /dev/spi_misc_test * echo setspeed 0 1000000 > /dev/spi_misc_test */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_SPI_DEV_NUM 10 #define SPI_MAX_SPEED_HZ 12000000 struct spi_test_data { struct device *dev; struct spi_device *spi; char *rx_buf; int rx_len; char *tx_buf; int tx_len; }; static struct spi_test_data *g_spi_test_data[MAX_SPI_DEV_NUM]; static u32 bit_per_word = 8; int spi_write_slt(int id, const void *txbuf, size_t n) { int ret = -1; struct spi_device *spi = NULL; struct spi_transfer t = { .tx_buf = txbuf, .len = n, .bits_per_word = bit_per_word, }; struct spi_message m; if (id >= MAX_SPI_DEV_NUM) return ret; if (!g_spi_test_data[id]) { pr_err("g_spi.%d is NULL\n", id); return ret; } else { spi = g_spi_test_data[id]->spi; } spi_message_init(&m); spi_message_add_tail(&t, &m); ret = spi_sync(spi, &m); if (m.actual_length && m.actual_length != n) pr_err("%s len=%zu actual_length=%d\n", __func__, n, m.actual_length); return ret; } int spi_read_slt(int id, void *rxbuf, size_t n) { int ret = -1; struct spi_device *spi = NULL; struct spi_transfer t = { .rx_buf = rxbuf, .len = n, .bits_per_word = bit_per_word, }; struct spi_message m; if (id >= MAX_SPI_DEV_NUM) return ret; if (!g_spi_test_data[id]) { pr_err("g_spi.%d is NULL\n", id); return ret; } else { spi = g_spi_test_data[id]->spi; } spi_message_init(&m); spi_message_add_tail(&t, &m); ret = spi_sync(spi, &m); if (m.actual_length && m.actual_length != n) pr_err("%s len=%zu actual_length=%d\n", __func__, n, m.actual_length); return ret; } int spi_write_then_read_slt(int id, const void *txbuf, unsigned int n_tx, void *rxbuf, unsigned int n_rx) { int ret = -1; struct spi_device *spi = NULL; if (id >= MAX_SPI_DEV_NUM) return ret; if (!g_spi_test_data[id]) { pr_err("g_spi.%d is NULL\n", id); return ret; } else { spi = g_spi_test_data[id]->spi; } ret = spi_write_then_read(spi, txbuf, n_tx, rxbuf, n_rx); return ret; } int spi_write_and_read_slt(int id, const void *tx_buf, void *rx_buf, size_t len) { int ret = -1; struct spi_device *spi = NULL; struct spi_transfer t = { .tx_buf = tx_buf, .rx_buf = rx_buf, .len = len, }; struct spi_message m; if (id >= MAX_SPI_DEV_NUM) return ret; if (!g_spi_test_data[id]) { pr_err("g_spi.%d is NULL\n", id); return ret; } else { spi = g_spi_test_data[id]->spi; } spi_message_init(&m); spi_message_add_tail(&t, &m); return spi_sync(spi, &m); } static ssize_t spi_test_write(struct file *file, const char __user *buf, size_t n, loff_t *offset) { int argc = 0, i; char tmp[64]; char *argv[16]; char *cmd, *data; unsigned int id = 0, times = 0, size = 0; unsigned long us = 0, bytes = 0; char *txbuf = NULL, *rxbuf = NULL; ktime_t start_time; ktime_t end_time; ktime_t cost_time; memset(tmp, 0, sizeof(tmp)); if (copy_from_user(tmp, buf, n)) return -EFAULT; cmd = tmp; data = tmp; while (data < (tmp + n)) { data = strstr(data, " "); if (!data) break; *data = 0; argv[argc] = ++data; argc++; if (argc >= 16) break; } tmp[n - 1] = 0; if (!strcmp(cmd, "setspeed")) { int id = 0, val; struct spi_device *spi = NULL; sscanf(argv[0], "%d", &id); sscanf(argv[1], "%d", &val); if (id >= MAX_SPI_DEV_NUM) return n; if (!g_spi_test_data[id]) { pr_err("g_spi.%d is NULL\n", id); return n; } else { spi = g_spi_test_data[id]->spi; } spi->max_speed_hz = val; } else if (!strcmp(cmd, "write")) { sscanf(argv[0], "%d", &id); sscanf(argv[1], "%d", ×); sscanf(argv[2], "%d", &size); txbuf = kzalloc(size, GFP_KERNEL); if (!txbuf) { printk("spi write alloc buf size %d fail\n", size); return n; } for (i = 0; i < size; i++) txbuf[i] = i % 256; start_time = ktime_get(); for (i = 0; i < times; i++) spi_write_slt(id, txbuf, size); end_time = ktime_get(); cost_time = ktime_sub(end_time, start_time); us = ktime_to_us(cost_time); bytes = size * times * 1; bytes = bytes * 1000 / us; printk("spi write %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes); kfree(txbuf); } else if (!strcmp(cmd, "read")) { sscanf(argv[0], "%d", &id); sscanf(argv[1], "%d", ×); sscanf(argv[2], "%d", &size); rxbuf = kzalloc(size, GFP_KERNEL); if (!rxbuf) { printk("spi read alloc buf size %d fail\n", size); return n; } start_time = ktime_get(); for (i = 0; i < times; i++) spi_read_slt(id, rxbuf, size); end_time = ktime_get(); cost_time = ktime_sub(end_time, start_time); us = ktime_to_us(cost_time); bytes = size * times * 1; bytes = bytes * 1000 / us; printk("spi read %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes); print_hex_dump(KERN_ERR, "SPI RX: ", DUMP_PREFIX_OFFSET, 16, 1, rxbuf, size, 1); kfree(rxbuf); } else if (!strcmp(cmd, "loop")) { sscanf(argv[0], "%d", &id); sscanf(argv[1], "%d", ×); sscanf(argv[2], "%d", &size); txbuf = kzalloc(size, GFP_KERNEL); if (!txbuf) { printk("spi tx alloc buf size %d fail\n", size); return n; } rxbuf = kzalloc(size, GFP_KERNEL); if (!rxbuf) { kfree(txbuf); printk("spi rx alloc buf size %d fail\n", size); return n; } for (i = 0; i < size; i++) txbuf[i] = i % 256; start_time = ktime_get(); for (i = 0; i < times; i++) { spi_write_and_read_slt(id, txbuf, rxbuf, size); if (memcmp(txbuf, rxbuf, size)) { printk("spi loop test fail\n"); break; } } end_time = ktime_get(); cost_time = ktime_sub(end_time, start_time); us = ktime_to_us(cost_time); bytes = size * times; bytes = bytes * 1000 / us; printk("spi loop %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes); kfree(txbuf); kfree(rxbuf); } else if (!strcmp(cmd, "config")) { int width, mode; sscanf(argv[0], "%d", &id); sscanf(argv[1], "%d", &width); sscanf(argv[2], "mode=0x%x", &mode); if (width == 16) bit_per_word = 16; else bit_per_word = 8; if (mode) { g_spi_test_data[id]->spi->mode = mode; spi_setup(g_spi_test_data[id]->spi); } } else { printk("echo id number size > /dev/spi_misc_test\n"); printk("echo write 0 10 255 > /dev/spi_misc_test\n"); printk("echo write 0 10 255 init.rc > /dev/spi_misc_test\n"); printk("echo read 0 10 255 > /dev/spi_misc_test\n"); printk("echo loop 0 10 255 > /dev/spi_misc_test\n"); printk("echo setspeed 0 1000000 > /dev/spi_misc_test\n"); printk("echo config 0 8 mode=0xb > /dev/spi_misc_test\n"); } return n; } static const struct file_operations spi_test_fops = { .write = spi_test_write, }; static struct miscdevice spi_test_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "spi_misc_test", .fops = &spi_test_fops, }; static int rockchip_spi_test_probe(struct spi_device *spi) { int ret; int id = 0; struct spi_test_data *spi_test_data = NULL; if (!spi) return -ENOMEM; spi_test_data = (struct spi_test_data *)kzalloc(sizeof(struct spi_test_data), GFP_KERNEL); if (!spi_test_data) { dev_err(&spi->dev, "ERR: no memory for spi_test_data\n"); return -ENOMEM; } spi->bits_per_word = 8; spi_test_data->spi = spi; spi_test_data->dev = &spi->dev; ret = spi_setup(spi); if (ret < 0) { dev_err(spi_test_data->dev, "ERR: fail to setup spi\n"); return -1; } if (device_property_read_u32(&spi->dev, "id", &id)) { dev_warn(&spi->dev, "fail to get id, default set 0\n"); id = 0; } g_spi_test_data[id] = spi_test_data; printk("%s:name=%s,bus_num=%d,cs=%d,mode=%d,speed=%d\n", __func__, spi->modalias, spi->master->bus_num, spi->chip_select, spi->mode, spi->max_speed_hz); return ret; } static void rockchip_spi_test_remove(struct spi_device *spi) { printk("%s\n", __func__); } #ifdef CONFIG_OF static const struct of_device_id rockchip_spi_test_dt_match[] = { { .compatible = "rockchip,spi_test_bus0_cs0", }, { .compatible = "rockchip,spi_test_bus0_cs1", }, { .compatible = "rockchip,spi_test_bus1_cs0", }, { .compatible = "rockchip,spi_test_bus1_cs1", }, { .compatible = "rockchip,spi_test_bus2_cs0", }, { .compatible = "rockchip,spi_test_bus2_cs1", }, { .compatible = "rockchip,spi_test_bus3_cs0", }, { .compatible = "rockchip,spi_test_bus3_cs1", }, { .compatible = "rockchip,spi_test_bus4_cs0", }, { .compatible = "rockchip,spi_test_bus4_cs1", }, {}, }; MODULE_DEVICE_TABLE(of, rockchip_spi_test_dt_match); #endif /* CONFIG_OF */ static const struct spi_device_id spi_rockchip_test_spi_ids[] = { { .name = "spi_test_bus0_cs0" }, { .name = "spi_test_bus0_cs1" }, { .name = "spi_test_bus1_cs0" }, { .name = "spi_test_bus1_cs1" }, { .name = "spi_test_bus2_cs0" }, { .name = "spi_test_bus2_cs1" }, { .name = "spi_test_bus3_cs0" }, { .name = "spi_test_bus3_cs1" }, { .name = "spi_test_bus4_cs0" }, { .name = "spi_test_bus4_cs1" }, {}, }; MODULE_DEVICE_TABLE(spi, spi_rockchip_test_spi_ids); static struct spi_driver spi_rockchip_test_driver = { .driver = { .name = "spi_test", .owner = THIS_MODULE, .of_match_table = of_match_ptr(rockchip_spi_test_dt_match), }, .probe = rockchip_spi_test_probe, .remove = rockchip_spi_test_remove, .id_table = spi_rockchip_test_spi_ids, }; static int __init spi_rockchip_test_init(void) { int ret = 0; misc_register(&spi_test_misc); ret = spi_register_driver(&spi_rockchip_test_driver); return ret; } module_init(spi_rockchip_test_init); static void __exit spi_rockchip_test_exit(void) { misc_deregister(&spi_test_misc); return spi_unregister_driver(&spi_rockchip_test_driver); } module_exit(spi_rockchip_test_exit); MODULE_AUTHOR("Luo Wei "); MODULE_AUTHOR("Huibin Hong "); MODULE_DESCRIPTION("ROCKCHIP SPI TEST Driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("spi:spi_test");