diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 0bdd5fe31d66..7a2cb275db6a 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -1067,6 +1067,12 @@ config SPI_SLAVE_SYSTEM_CONTROL SPI slave handler to allow remote control of system reboot, power off, halt, and suspend. +config SPI_SLAVE_ROCKCHIP_OBJ + tristate "Rockchip SPI slave inter transmission protocol demo" + help + SPI slave with a rockchip protocol specification for SPI slave + transmission, work with the corresponding master driver spidev_rkmst. + endif # SPI_SLAVE config SPI_DYNAMIC diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 6816188b2b47..d3f108247d7d 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -136,3 +136,5 @@ obj-$(CONFIG_SPI_AMD) += spi-amd.o # SPI slave protocol handlers obj-$(CONFIG_SPI_SLAVE_TIME) += spi-slave-time.o obj-$(CONFIG_SPI_SLAVE_SYSTEM_CONTROL) += spi-slave-system-control.o +obj-$(CONFIG_SPI_SLAVE_ROCKCHIP_OBJ) += spidev-rkslv.o +obj-$(CONFIG_SPI_SLAVE_ROCKCHIP_OBJ) += spidev-rkmst.o diff --git a/drivers/spi/spidev-rkmst.c b/drivers/spi/spidev-rkmst.c new file mode 100644 index 000000000000..a969adf5ec91 --- /dev/null +++ b/drivers/spi/spidev-rkmst.c @@ -0,0 +1,636 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2023 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define SPI_OBJ_MAX_XFER_SIZE 0x1040 +#define SPI_OBJ_APP_RAM_SIZE 0x10000 + +#define SPI_OBJ_CTRL_MSG_SIZE 0x8 +#define SPI_OBJ_CTRL_CMD_INIT 0x99 +#define SPI_OBJ_CTRL_CMD_READ 0x3A +#define SPI_OBJ_CTRL_CMD_WRITE 0x4B +#define SPI_OBJ_CTRL_CMD_DUPLEX 0x5C + +#define SPI_OBJ_DEFAULT_TIMEOUT_US 100000 + +struct spi_obj_ctrl { + u16 cmd; + u16 addr; + u32 data; +}; + +struct spidev_rkmst_data { + struct device *dev; + struct spi_device *spi; + char *ctrlbuf; + char *rxbuf; + char *txbuf; + struct gpio_desc *ready; + int ready_irqnum; + bool ready_status; + bool verbose; + struct miscdevice misc_dev; +}; + +static u32 bit_per_word = 8; + +static inline void spidev_mst_slave_ready_status(struct spidev_rkmst_data *spidev, bool status) +{ + spidev->ready_status = status; +} + +static irqreturn_t spidev_mst_slave_ready_interrupt(int irq, void *arg) +{ + struct spidev_rkmst_data *spidev = (struct spidev_rkmst_data *)arg; + + spidev_mst_slave_ready_status(spidev, true); + + return IRQ_HANDLED; +} + +static bool spidev_mst_check_slave_ready(struct spidev_rkmst_data *spidev) +{ + return spidev->ready_status; +} + +static int spidev_mst_wait_for_slave_ready(struct spidev_rkmst_data *spidev, + u32 timeout_us) +{ + bool ready; + int ret; + + ret = read_poll_timeout(spidev_mst_check_slave_ready, ready, + ready, 100, timeout_us + 100, false, spidev); + if (ret) { + dev_err(&spidev->spi->dev, "timeout and reset slave\n"); + + return -ETIMEDOUT; + } + + return true; +} + +static int spidev_mst_write(struct spidev_rkmst_data *spidev, const void *txbuf, size_t n) +{ + struct spi_device *spi = spidev->spi; + struct spi_transfer t = { + .tx_buf = txbuf, + .len = n, + .bits_per_word = bit_per_word, + }; + struct spi_message m; + int ret; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + ret = spidev_mst_wait_for_slave_ready(spidev, SPI_OBJ_DEFAULT_TIMEOUT_US); + if (ret < 0) + return ret; + + spidev_mst_slave_ready_status(spidev, false); + ret = spi_sync(spi, &m); + + return ret; +} + +static int spidev_mst_write_bypass(struct spidev_rkmst_data *spidev, const void *txbuf, size_t n) +{ + struct spi_device *spi = spidev->spi; + struct spi_transfer t = { + .tx_buf = txbuf, + .len = n, + .bits_per_word = bit_per_word, + }; + struct spi_message m; + int ret; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + ret = spi_sync(spi, &m); + + return ret; +} + +static int spidev_mst_read(struct spidev_rkmst_data *spidev, void *rxbuf, size_t n) +{ + struct spi_device *spi = spidev->spi; + struct spi_transfer t = { + .rx_buf = rxbuf, + .len = n, + .bits_per_word = bit_per_word, + }; + struct spi_message m; + int ret; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + ret = spidev_mst_wait_for_slave_ready(spidev, SPI_OBJ_MAX_XFER_SIZE); + if (ret < 0) + return ret; + + spidev_mst_slave_ready_status(spidev, false); + ret = spi_sync(spi, &m); + + return ret; +} + +static int spidev_slv_write_and_read(struct spidev_rkmst_data *spidev, + const void *tx_buf, void *rx_buf, + size_t len) +{ + struct spi_device *spi = spidev->spi; + struct spi_transfer t = { + .tx_buf = tx_buf, + .rx_buf = rx_buf, + .len = len, + }; + struct spi_message m; + int ret; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + ret = spidev_mst_wait_for_slave_ready(spidev, SPI_OBJ_MAX_XFER_SIZE); + if (ret < 0) + return ret; + + spidev_mst_slave_ready_status(spidev, false); + ret = spi_sync(spi, &m); + + return ret; +} + +static void spidev_rkmst_reset_slave(struct spidev_rkmst_data *spidev) +{ + struct spi_obj_ctrl *ctrl = (struct spi_obj_ctrl *)spidev->ctrlbuf; + + ctrl->cmd = SPI_OBJ_CTRL_CMD_INIT; + + spidev_mst_write_bypass(spidev, ctrl, SPI_OBJ_MAX_XFER_SIZE); + msleep(100); + spidev_mst_write_bypass(spidev, ctrl, SPI_OBJ_MAX_XFER_SIZE); +} + + +static int spidev_rkmst_ctrl(struct spidev_rkmst_data *spidev, u32 cmd, u16 addr, u32 data) +{ + struct spi_obj_ctrl *ctrl = (struct spi_obj_ctrl *)spidev->ctrlbuf; + struct spi_device *spi = spidev->spi; + int ret; + + if (spidev->verbose) + dev_err(&spi->dev, "ctrl cmd=%x addr=0x%x data=0x%x\n", cmd, addr, data); + + /* ctrl_xfer */ + ctrl->cmd = cmd; + ctrl->addr = addr; + ctrl->data = data; + ret = spidev_mst_write(spidev, ctrl, SPI_OBJ_CTRL_MSG_SIZE); + if (ret) { + dev_err(&spi->dev, "ctrl cmd=%x addr=0x%x data=0x%x, ret=%d\n", cmd, addr, data, ret); + return -EIO; + } + + return 0; +} + +static int spidev_rkmst_xfer(struct spidev_rkmst_data *spidev, void *tx, + void *rx, u16 addr, u32 len) +{ + struct spi_device *spi = spidev->spi; + int ret; + u32 cmd; + + if (tx && rx) + cmd = SPI_OBJ_CTRL_CMD_DUPLEX; + else if (rx) + cmd = SPI_OBJ_CTRL_CMD_READ; + else if (tx) + cmd = SPI_OBJ_CTRL_CMD_WRITE; + else + return -EINVAL; + + /* ctrl_xfer */ + ret = spidev_rkmst_ctrl(spidev, cmd, addr, len); + if (ret) { + spidev_rkmst_reset_slave(spidev); + return -EIO; + } + + if (spidev->verbose) + dev_err(&spi->dev, "xfer len=0x%x\n", len); + /* data_xfer */ + switch (cmd) { + case SPI_OBJ_CTRL_CMD_READ: + ret = spidev_mst_read(spidev, rx, len); + if (ret) + goto err_out; + break; + case SPI_OBJ_CTRL_CMD_WRITE: + ret = spidev_mst_write(spidev, tx, len); + if (ret) + goto err_out; + break; + case SPI_OBJ_CTRL_CMD_DUPLEX: + ret = spidev_slv_write_and_read(spidev, tx, rx, len); + if (ret) + goto err_out; + break; + default: + dev_err(&spi->dev, "%s unknown\n", __func__); + } + + return 0; +err_out: + dev_err(&spi->dev, "xfer cmd=%x addr=0x%x len=0x%x, ret=%d\n", + cmd, addr, len, ret); + + return ret; +} + +static ssize_t spidev_rkmst_misc_write(struct file *filp, const char __user *buf, + size_t n, loff_t *offset) +{ + struct spidev_rkmst_data *spidev; + struct spi_device *spi; + int argc = 0, i; + char tmp[64]; + char *argv[16]; + char *cmd, *data; + + if (n >= 64) + return -EINVAL; + + spidev = filp->private_data; + + if (!spidev) + return -ESHUTDOWN; + + spi = spidev->spi; + 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, "verbose")) { + int val; + + if (argc < 1) + return -EINVAL; + + if (kstrtoint(argv[0], 0, &val)) + return -EINVAL; + + if (val == 1) + spidev->verbose = true; + else + spidev->verbose = false; + } else if (!strcmp(cmd, "init")) { + spidev_rkmst_ctrl(spidev, SPI_OBJ_CTRL_CMD_INIT, 0x55AA, 0x1234567); + } else if (!strcmp(cmd, "read")) { + int addr, len; + + if (argc < 2) + return -EINVAL; + + if (kstrtoint(argv[0], 0, &addr)) + return -EINVAL; + if (kstrtoint(argv[1], 0, &len)) + return -EINVAL; + + if (!len) { + dev_err(&spi->dev, "param invalid,%s %s\n", argv[0], argv[1]); + return -EINVAL; + } + + if (addr + len > SPI_OBJ_APP_RAM_SIZE) { + dev_err(&spi->dev, "rxbuf print out of size\n"); + return -EINVAL; + } + + spidev_rkmst_xfer(spidev, NULL, spidev->rxbuf, addr, len); + + print_hex_dump(KERN_ERR, "m-r: ", + DUMP_PREFIX_OFFSET, + 16, + 1, + spidev->rxbuf, + len, + 1); + } else if (!strcmp(cmd, "write")) { + int addr, len; + + if (argc < 2) + return -EINVAL; + + if (kstrtoint(argv[0], 0, &addr)) + return -EINVAL; + if (kstrtoint(argv[1], 0, &len)) + return -EINVAL; + + if (!len) { + dev_err(&spi->dev, "param invalid,%s %s\n", argv[0], argv[1]); + return -EINVAL; + } + + if (addr + len > SPI_OBJ_APP_RAM_SIZE) { + dev_err(&spi->dev, "rxbuf print out of size\n"); + return -EINVAL; + } + + for (i = 0; i < len; i++) + spidev->txbuf[i] = i & 0xFF; + ((u32 *)spidev->txbuf)[0] = addr; + + spidev_rkmst_xfer(spidev, spidev->txbuf, NULL, addr, len); + } else if (!strcmp(cmd, "duplex")) { + int addr, len; + + if (argc < 2) + return -EINVAL; + + if (kstrtoint(argv[0], 0, &addr)) + return -EINVAL; + if (kstrtoint(argv[1], 0, &len)) + return -EINVAL; + + if (!len) { + dev_err(&spi->dev, "param invalid,%s %s\n", argv[0], argv[1]); + return -EINVAL; + } + + if (addr + len > SPI_OBJ_APP_RAM_SIZE) { + dev_err(&spi->dev, "rxbuf print out of size\n"); + return -EINVAL; + } + + for (i = 0; i < len; i++) + spidev->txbuf[i] = i & 0xFF; + ((u32 *)spidev->txbuf)[0] = addr; + + spidev_rkmst_xfer(spidev, spidev->txbuf, spidev->rxbuf, addr, len); + + print_hex_dump(KERN_ERR, "m-d: ", + DUMP_PREFIX_OFFSET, + 16, + 1, + spidev->rxbuf, + len, + 1); + } else if (!strcmp(cmd, "autotest")) { + int addr = 0, len, loops, i; + unsigned long long bytes = 0; + unsigned long us = 0; + ktime_t start_time; + ktime_t end_time; + ktime_t cost_time; + char *tempbuf; + + if (argc < 2) + return -EINVAL; + + if (kstrtoint(argv[0], 0, &len)) + return -EINVAL; + + if (kstrtoint(argv[1], 0, &loops)) + return -EINVAL; + + if (!len) { + dev_err(&spi->dev, "param invalid,%s %s\n", argv[0], argv[1]); + return -EINVAL; + } + + if (len > SPI_OBJ_APP_RAM_SIZE) { + dev_err(&spi->dev, "rxbuf print out of size\n"); + return -EINVAL; + } + + tempbuf = kzalloc(len, GFP_KERNEL); + if (!tempbuf) + return -ENOMEM; + + prandom_bytes(tempbuf, len); + spidev_rkmst_xfer(spidev, tempbuf, NULL, addr, len); + start_time = ktime_get(); + for (i = 0; i < loops; i++) { + prandom_bytes(spidev->txbuf, len); + spidev_rkmst_xfer(spidev, spidev->txbuf, spidev->rxbuf, addr, len); + if (memcmp(spidev->rxbuf, tempbuf, len)) { + dev_err(&spi->dev, "dulplex autotest failed, loops=%d\n", i); + print_hex_dump(KERN_ERR, "m-d-t: ", + DUMP_PREFIX_OFFSET, + 16, + 1, + spidev->txbuf, + len, + 1); + print_hex_dump(KERN_ERR, "m-d-r: ", + DUMP_PREFIX_OFFSET, + 16, + 1, + spidev->rxbuf, + len, + 1); + print_hex_dump(KERN_ERR, "m-d-c: ", + DUMP_PREFIX_OFFSET, + 16, + 1, + tempbuf, + len, + 1); + break; + } + memcpy(tempbuf, spidev->txbuf, len); + } + end_time = ktime_get(); + cost_time = ktime_sub(end_time, start_time); + us = ktime_to_us(cost_time); + + bytes = (u64)len * (u64)loops * 1000; + do_div(bytes, us); + if (i >= loops) + dev_err(&spi->dev, "dulplex test pass, cost=%ldus, speed=%lldKB/S, %ldus/loops\n", + us, bytes, us / loops); + + start_time = ktime_get(); + for (i = 0; i < loops; i++) { + prandom_bytes(spidev->txbuf, len); + spidev_rkmst_xfer(spidev, spidev->txbuf, NULL, addr, len); + spidev_rkmst_xfer(spidev, NULL, spidev->rxbuf, addr, len); + if (memcmp(spidev->rxbuf, spidev->txbuf, len)) { + dev_err(&spi->dev, "read/write autotest failed, loops=%d\n", i); + print_hex_dump(KERN_ERR, "m-d-t: ", + DUMP_PREFIX_OFFSET, + 16, + 1, + spidev->txbuf, + len, + 1); + print_hex_dump(KERN_ERR, "m-d-r: ", + DUMP_PREFIX_OFFSET, + 16, + 1, + spidev->rxbuf, + len, + 1); + break; + } + } + end_time = ktime_get(); + cost_time = ktime_sub(end_time, start_time); + us = ktime_to_us(cost_time); + + bytes = (u64)len * (u64)loops * 2 * 1000; /* multi 2 for both write and read */ + do_div(bytes, us); + if (i >= loops) + dev_err(&spi->dev, "read/write test pass, cost=%ldus, speed=%lldKB/S, %ldus/loops\n", + us, bytes, us / loops); + kfree(tempbuf); + } else { + dev_err(&spi->dev, "unknown command\n"); + } + + return n; +} + +static int spidev_rkmst_misc_open(struct inode *inode, struct file *filp) +{ + struct miscdevice *miscdev = filp->private_data; + struct spidev_rkmst_data *spidev; + + spidev = container_of(miscdev, struct spidev_rkmst_data, misc_dev); + filp->private_data = spidev; + + return 0; +} + +static const struct file_operations spidev_rkmst_misc_fops = { + .write = spidev_rkmst_misc_write, + .open = spidev_rkmst_misc_open, +}; + +static int spidev_rkmst_probe(struct spi_device *spi) +{ + struct spidev_rkmst_data *spidev = NULL; + int ret; + + if (!spi) + return -ENOMEM; + + spidev = devm_kzalloc(&spi->dev, sizeof(struct spidev_rkmst_data), GFP_KERNEL); + if (!spidev) + return -ENOMEM; + + spidev->ctrlbuf = devm_kzalloc(&spi->dev, SPI_OBJ_MAX_XFER_SIZE, GFP_KERNEL); + if (!spidev->ctrlbuf) + return -ENOMEM; + + spidev->rxbuf = devm_kzalloc(&spi->dev, SPI_OBJ_APP_RAM_SIZE, GFP_KERNEL | GFP_DMA); + if (!spidev->rxbuf) + return -ENOMEM; + + spidev->txbuf = devm_kzalloc(&spi->dev, SPI_OBJ_MAX_XFER_SIZE, GFP_KERNEL); + if (!spidev->txbuf) + return -ENOMEM; + + spidev->spi = spi; + spidev->dev = &spi->dev; + + spidev_mst_slave_ready_status(spidev, false); + spidev->ready = devm_gpiod_get_optional(&spi->dev, "ready", GPIOD_IN); + if (IS_ERR(spidev->ready)) + return dev_err_probe(&spi->dev, PTR_ERR(spidev->ready), + "invalid ready-gpios property in node\n"); + + spidev->ready_irqnum = gpiod_to_irq(spidev->ready); + ret = devm_request_irq(&spi->dev, spidev->ready_irqnum, spidev_mst_slave_ready_interrupt, + IRQF_TRIGGER_FALLING, "spidev-mst-ready-in", spidev); + if (ret < 0) { + dev_err(&spi->dev, "request ready irq failed\n"); + return ret; + } + dev_set_drvdata(&spi->dev, spidev); + + dev_err(&spi->dev, "mode=%d, max_speed_hz=%d\n", spi->mode, spi->max_speed_hz); + + spidev->misc_dev.minor = MISC_DYNAMIC_MINOR; + spidev->misc_dev.name = "spidev_rkmst_misc"; + spidev->misc_dev.fops = &spidev_rkmst_misc_fops; + spidev->misc_dev.parent = &spi->dev; + ret = misc_register(&spidev->misc_dev); + if (ret) { + dev_err(&spi->dev, "fail to register misc device\n"); + return ret; + } + + spidev_rkmst_reset_slave(spidev); + + return 0; +} + +static int spidev_rkmst_remove(struct spi_device *spi) +{ + struct spidev_rkmst_data *spidev = dev_get_drvdata(&spi->dev); + + misc_deregister(&spidev->misc_dev); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id spidev_rkmst_dt_match[] = { + { .compatible = "rockchip,spi-obj-master", }, + {}, +}; +MODULE_DEVICE_TABLE(of, spidev_rkmst_dt_match); + +#endif /* CONFIG_OF */ + +static struct spi_driver spidev_rkmst_driver = { + .driver = { + .name = "spidev_rkmst", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(spidev_rkmst_dt_match), + }, + .probe = spidev_rkmst_probe, + .remove = spidev_rkmst_remove, +}; +module_spi_driver(spidev_rkmst_driver); + +MODULE_AUTHOR("Jon Lin "); +MODULE_DESCRIPTION("ROCKCHIP SPI Object Master Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:spidev_rkmst"); diff --git a/drivers/spi/spidev-rkslv.c b/drivers/spi/spidev-rkslv.c new file mode 100644 index 000000000000..0ff4415ff7e3 --- /dev/null +++ b/drivers/spi/spidev-rkslv.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2023 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SPI_OBJ_MAX_XFER_SIZE 0x1040 +#define SPI_OBJ_APP_RAM_SIZE 0x10000 + +#define SPI_OBJ_CTRL_MSG_SIZE 0x8 +#define SPI_OBJ_CTRL_CMD_INIT 0x99 +#define SPI_OBJ_CTRL_CMD_READ 0x3A +#define SPI_OBJ_CTRL_CMD_WRITE 0x4B +#define SPI_OBJ_CTRL_CMD_DUPLEX 0x5C + +struct spi_obj_ctrl { + u16 cmd; + u16 addr; + u32 data; +}; + +struct spidev_rkslv_data { + struct device *dev; + struct spi_device *spi; + char *ctrlbuf; + char *appmem; + char *tempbuf; + bool verbose; + struct task_struct *tsk; + bool tsk_run; + struct miscdevice misc_dev; +}; + +static u32 bit_per_word = 8; + +static int spidev_slv_write(struct spidev_rkslv_data *spidev, const void *txbuf, size_t n) +{ + int ret = -1; + struct spi_device *spi = spidev->spi; + struct spi_transfer t = { + .tx_buf = txbuf, + .len = n, + .bits_per_word = bit_per_word, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(spi, &m); + + return ret; +} + +static int spidev_slv_read(struct spidev_rkslv_data *spidev, void *rxbuf, size_t n) +{ + int ret = -1; + struct spi_device *spi = spidev->spi; + struct spi_transfer t = { + .rx_buf = rxbuf, + .len = n, + .bits_per_word = bit_per_word, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(spi, &m); + + return ret; +} + +static int spidev_slv_write_and_read(struct spidev_rkslv_data *spidev, const void *tx_buf, + void *rx_buf, size_t len) +{ + struct spi_device *spi = spidev->spi; + struct spi_transfer t = { + .tx_buf = tx_buf, + .rx_buf = rx_buf, + .len = len, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + return spi_sync(spi, &m); +} + +static ssize_t spidev_rkslv_misc_write(struct file *filp, const char __user *buf, + size_t n, loff_t *offset) +{ + struct spidev_rkslv_data *spidev; + struct spi_device *spi; + int argc = 0; + char tmp[64]; + char *argv[16]; + char *cmd, *data; + + if (n >= 64) + return -EINVAL; + + spidev = filp->private_data; + + if (!spidev) + return -ESHUTDOWN; + + spi = spidev->spi; + 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, "verbose")) { + int val; + + if (argc < 1) + return -EINVAL; + + if (kstrtoint(argv[0], 0, &val)) + return -EINVAL; + + if (val == 1) + spidev->verbose = true; + else + spidev->verbose = false; + } else if (!strcmp(cmd, "appmem")) { + int addr, len; + + if (argc < 2) + return -EINVAL; + + if (kstrtoint(argv[0], 0, &addr)) + return -EINVAL; + if (kstrtoint(argv[1], 0, &len)) + return -EINVAL; + + if (!len) { + dev_err(&spi->dev, "param invalid,%s %s\n", argv[0], argv[1]); + return -EINVAL; + } + + if (addr + len > SPI_OBJ_APP_RAM_SIZE) { + dev_err(&spi->dev, "appmem print out of size\n"); + return -EINVAL; + } + + print_hex_dump(KERN_ERR, "APPMEM: ", + DUMP_PREFIX_OFFSET, + 16, + 1, + spidev->appmem + addr, + len, + 1); + } else { + dev_err(&spi->dev, "unknown command\n"); + } + + return n; +} + +static int spidev_rkslv_misc_open(struct inode *inode, struct file *filp) +{ + struct miscdevice *miscdev = filp->private_data; + struct spidev_rkslv_data *spidev; + + spidev = container_of(miscdev, struct spidev_rkslv_data, misc_dev); + filp->private_data = spidev; + + return 0; +} + +static const struct file_operations spidev_rkslv_misc_fops = { + .write = spidev_rkslv_misc_write, + .open = spidev_rkslv_misc_open, +}; + +static int spidev_rkslv_xfer(struct spidev_rkslv_data *spidev) +{ + char *ctrlbuf = spidev->ctrlbuf, *appmem = spidev->appmem, *tempbuf = spidev->tempbuf; + struct spi_obj_ctrl *ctrl; + struct spi_device *spi = spidev->spi; + u32 len; + int ret; + + memset(spidev->ctrlbuf, 0, SPI_OBJ_CTRL_MSG_SIZE); + ret = spidev_slv_read(spidev, spidev->ctrlbuf, SPI_OBJ_CTRL_MSG_SIZE); + if (ret) { + dev_err(&spi->dev, "%s ctrl\n", __func__); + return -EIO; + } + + ctrl = (struct spi_obj_ctrl *)ctrlbuf; + if (spidev->verbose) + dev_err(&spi->dev, "ctrl cmd=%x addr=0x%x data=0x%x\n", + ctrl->cmd, ctrl->addr, ctrl->data); + + switch (ctrl->cmd) { + case SPI_OBJ_CTRL_CMD_INIT: + return 0; + case SPI_OBJ_CTRL_CMD_READ: + len = ctrl->data; + ret = spidev_slv_write(spidev, appmem + ctrl->addr, len); + if (ret) { + dev_err(&spi->dev, "%s cmd=%x addr=0x%x data=0x%x\n", + __func__, ctrl->cmd, ctrl->addr, ctrl->data); + return -EIO; + } + break; + case SPI_OBJ_CTRL_CMD_WRITE: + len = ctrl->data; + ret = spidev_slv_read(spidev, appmem + ctrl->addr, len); + if (ret) { + dev_err(&spi->dev, "%s cmd=%x addr=0x%x data=0x%x\n", + __func__, ctrl->cmd, ctrl->addr, ctrl->data); + return -EIO; + } + if (spidev->verbose) { + print_hex_dump(KERN_ERR, "s-r: ", + DUMP_PREFIX_OFFSET, + 16, + 1, + appmem + ctrl->addr, + len, + 1); + } + break; + case SPI_OBJ_CTRL_CMD_DUPLEX: + len = ctrl->data; + ret = spidev_slv_write_and_read(spidev, appmem + ctrl->addr, tempbuf, len); + if (ret) { + dev_err(&spi->dev, "%s cmd=%x addr=0x%x data=0x%x\n", + __func__, ctrl->cmd, ctrl->addr, ctrl->data); + return -EIO; + } + if (spidev->verbose) { + print_hex_dump(KERN_ERR, "s-d-t: ", + DUMP_PREFIX_OFFSET, + 16, + 1, + appmem + ctrl->addr, + len, + 1); + print_hex_dump(KERN_ERR, "s-d-r: ", + DUMP_PREFIX_OFFSET, + 16, + 1, + tempbuf, + len, + 1); + } + memcpy(appmem + ctrl->addr, tempbuf, len); + break; + default: + if (spidev->verbose) + dev_err(&spi->dev, "%s unknown\n", __func__); + return 0; + } + + if (spidev->verbose) + dev_err(&spi->dev, "xfer len=0x%x\n", ctrl->data); + + return 0; +} + +static int spidev_rkslv_ctrl_receiver_thread(void *p) +{ + struct spidev_rkslv_data *spidev = (struct spidev_rkslv_data *)p; + + while (spidev->tsk_run) + spidev_rkslv_xfer(spidev); + + return 0; +} + +static int spidev_rkslv_probe(struct spi_device *spi) +{ + struct spidev_rkslv_data *spidev = NULL; + int ret; + + if (!spi) + return -ENOMEM; + + spidev = devm_kzalloc(&spi->dev, sizeof(struct spidev_rkslv_data), GFP_KERNEL); + if (!spidev) + return -ENOMEM; + + spidev->ctrlbuf = devm_kzalloc(&spi->dev, SPI_OBJ_MAX_XFER_SIZE, GFP_KERNEL); + if (!spidev->ctrlbuf) + return -ENOMEM; + + spidev->appmem = devm_kzalloc(&spi->dev, SPI_OBJ_APP_RAM_SIZE, GFP_KERNEL | GFP_DMA); + if (!spidev->appmem) + return -ENOMEM; + + spidev->tempbuf = devm_kzalloc(&spi->dev, SPI_OBJ_MAX_XFER_SIZE, GFP_KERNEL); + if (!spidev->tempbuf) + return -ENOMEM; + + spidev->spi = spi; + spidev->dev = &spi->dev; + dev_set_drvdata(&spi->dev, spidev); + + dev_err(&spi->dev, "mode=%d, max_speed_hz=%d\n", spi->mode, spi->max_speed_hz); + + spidev->misc_dev.minor = MISC_DYNAMIC_MINOR; + spidev->misc_dev.name = "spidev_rkslv_misc"; + spidev->misc_dev.fops = &spidev_rkslv_misc_fops; + spidev->misc_dev.parent = &spi->dev; + ret = misc_register(&spidev->misc_dev); + if (ret) { + dev_err(&spi->dev, "fail to register misc device\n"); + return ret; + } + + spidev->tsk_run = true; + spidev->tsk = kthread_run(spidev_rkslv_ctrl_receiver_thread, spidev, "spidev-rkslv"); + if (IS_ERR(spidev->tsk)) { + dev_err(&spi->dev, "start spidev-rkslv thread failed\n"); + return PTR_ERR(spidev->tsk); + } + + return 0; +} + +static int spidev_rkslv_remove(struct spi_device *spi) +{ + struct spidev_rkslv_data *spidev = dev_get_drvdata(&spi->dev); + + spidev->tsk_run = false; + spi_slave_abort(spi); + kthread_stop(spidev->tsk); + misc_deregister(&spidev->misc_dev); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id spidev_rkslv_dt_match[] = { + { .compatible = "rockchip,spi-obj-slave", }, + {}, +}; +MODULE_DEVICE_TABLE(of, spidev_rkslv_dt_match); + +#endif /* CONFIG_OF */ + +static struct spi_driver spidev_rkmst_driver = { + .driver = { + .name = "spidev_rkslv", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(spidev_rkslv_dt_match), + }, + .probe = spidev_rkslv_probe, + .remove = spidev_rkslv_remove, +}; +module_spi_driver(spidev_rkmst_driver); + +MODULE_AUTHOR("Jon Lin "); +MODULE_DESCRIPTION("ROCKCHIP SPI Object Slave Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:spidev_rkslv");