[PATCH -next 1/4] spi: mockup: Add SPI controller testing driver

From: Wei Yongjun
Date: Fri Aug 26 2022 - 10:26:14 EST


This enables SPI controller Testing driver, which provides a way
to test SPI drivers.

This is accomplished by executing the following command:

$ echo adcxx1s 0 > /sys/class/spi_master/spi0/new_device

The name of the target driver and its chip select were used to
instantiate the device.

$ ls /sys/bus/spi/devices/spi0.0/hwmon/hwmon0/ -l
total 0
lrwxrwxrwx 1 root root 0 Aug 10 08:58 device -> ../../../spi0.0
drwxr-xr-x 2 root root 0 Aug 10 08:58 power
lrwxrwxrwx 1 root root 0 Aug 10 08:58 subsystem -> ../../../../../../../../class/hwmon
-rw-r--r-- 1 root root 4096 Aug 10 08:58 uevent

Remove target device by executing the following command:
$ echo 0 > /sys/class/spi_master/spi0/delete_device

Signed-off-by: Wei Yongjun <weiyongjun1@xxxxxxxxxx>
---
drivers/spi/Kconfig | 12 +++
drivers/spi/Makefile | 1 +
drivers/spi/spi-mockup.c | 221 +++++++++++++++++++++++++++++++++++++++
3 files changed, 234 insertions(+)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index d1bb62f7368b..b58a1bd7999d 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -1158,6 +1158,18 @@ config SPI_TLE62X0
sysfs interface, with each line presented as a kind of GPIO
exposing both switch control and diagnostic feedback.

+config SPI_MOCKUP
+ tristate "SPI controller Testing Driver"
+ depends on OF
+ help
+ This enables SPI controller testing driver, which provides a way to
+ test SPI subsystem.
+
+ If you do build this module, be sure to read the notes and warnings
+ in <file:Documentation/spi/spi-mockup.rst>.
+
+ If you don't know what to do here, definitely say N.
+
#
# Add new SPI protocol masters in alphabetical order above this line
#
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 4b34e855c841..be87f49098b6 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_SPI_MEM) += spi-mem.o
obj-$(CONFIG_SPI_MUX) += spi-mux.o
obj-$(CONFIG_SPI_SPIDEV) += spidev.o
obj-$(CONFIG_SPI_LOOPBACK_TEST) += spi-loopback-test.o
+obj-$(CONFIG_SPI_MOCKUP) += spi-mockup.o

# SPI master controller drivers (bus)
obj-$(CONFIG_SPI_ALTERA) += spi-altera-platform.o
diff --git a/drivers/spi/spi-mockup.c b/drivers/spi/spi-mockup.c
new file mode 100644
index 000000000000..06cf9d122d64
--- /dev/null
+++ b/drivers/spi/spi-mockup.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SPI controller Testing Driver
+ *
+ * Copyright(c) 2022 Huawei Technologies Co., Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+
+#define MOCKUP_CHIPSELECT_MAX 8
+
+struct mockup_spi {
+ struct mutex lock;
+ struct spi_device *devs[MOCKUP_CHIPSELECT_MAX];
+};
+
+static struct spi_master *to_spi_master(struct device *dev)
+{
+ return container_of(dev, struct spi_master, dev);
+}
+
+static ssize_t
+new_device_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct spi_master *master = to_spi_master(dev);
+ struct spi_board_info info;
+ struct spi_device *slave;
+ struct mockup_spi *mock;
+ char *blank, end;
+ int status;
+
+ memset(&info, 0, sizeof(struct spi_board_info));
+
+ blank = strchr(buf, ' ');
+ if (!blank) {
+ dev_err(dev, "%s: Extra parameters\n", "new_device");
+ return -EINVAL;
+ }
+
+ if (blank - buf > SPI_NAME_SIZE - 1) {
+ dev_err(dev, "%s: Invalid device name\n", "new_device");
+ return -EINVAL;
+ }
+
+ memcpy(info.modalias, buf, blank - buf);
+
+ status = sscanf(++blank, "%hi%c", &info.chip_select, &end);
+ if (status < 1) {
+ dev_err(dev, "%s: Can't parse SPI chipselect\n", "new_device");
+ return -EINVAL;
+ }
+
+ if (status > 1 && end != '\n') {
+ dev_err(dev, "%s: Extra parameters\n", "new_device");
+ return -EINVAL;
+ }
+
+ if (info.chip_select >= master->num_chipselect) {
+ dev_err(dev, "%s: Invalid chip_select\n", "new_device");
+ return -EINVAL;
+ }
+
+ mock = spi_master_get_devdata(master);
+ mutex_lock(&mock->lock);
+
+ if (mock->devs[info.chip_select]) {
+ dev_err(dev, "%s: Chipselect %d already in use\n",
+ "new_device", info.chip_select);
+ mutex_unlock(&mock->lock);
+ return -EINVAL;
+ }
+
+ slave = spi_new_device(master, &info);
+ if (!slave) {
+ mutex_unlock(&mock->lock);
+ return -ENOMEM;
+ }
+ mock->devs[info.chip_select] = slave;
+
+ mutex_unlock(&mock->lock);
+
+ dev_info(dev, "%s: Instantiated device %s at 0x%02x\n", "new_device",
+ info.modalias, info.chip_select);
+
+ return count;
+}
+static DEVICE_ATTR_WO(new_device);
+
+static ssize_t
+delete_device_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct spi_master *master = to_spi_master(dev);
+ struct spi_device *slave;
+ struct mockup_spi *mock;
+ unsigned short chip;
+ char end;
+ int res;
+
+ /* Parse parameters, reject extra parameters */
+ res = sscanf(buf, "%hi%c", &chip, &end);
+ if (res < 1) {
+ dev_err(dev, "%s: Can't parse SPI address\n", "delete_device");
+ return -EINVAL;
+ }
+ if (res > 1 && end != '\n') {
+ dev_err(dev, "%s: Extra parameters\n", "delete_device");
+ return -EINVAL;
+ }
+
+ if (chip >= master->num_chipselect) {
+ dev_err(dev, "%s: Invalid chip_select\n", "delete_device");
+ return -EINVAL;
+ }
+
+ mock = spi_master_get_devdata(master);
+ mutex_lock(&mock->lock);
+
+ slave = mock->devs[chip];
+ if (!slave) {
+ mutex_unlock(&mock->lock);
+ dev_err(dev, "%s: Invalid chip_select\n", "delete_device");
+ return -ENOENT;
+ }
+
+ dev_info(dev, "%s: Deleting device %s at 0x%02hx\n", "delete_device",
+ dev_name(&slave->dev), chip);
+
+ spi_unregister_device(slave);
+ mock->devs[chip] = NULL;
+
+ mutex_unlock(&mock->lock);
+
+ return count;
+}
+static DEVICE_ATTR_WO(delete_device);
+
+static struct attribute *spi_mockup_attrs[] = {
+ &dev_attr_new_device.attr,
+ &dev_attr_delete_device.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(spi_mockup);
+
+static int spi_mockup_transfer(struct spi_master *master,
+ struct spi_message *msg)
+{
+ msg->status = 0;
+ spi_finalize_current_message(master);
+
+ return 0;
+}
+
+static int spi_mockup_probe(struct platform_device *pdev)
+{
+ struct spi_master *master;
+ struct mockup_spi *mock;
+ int ret;
+
+ master = spi_alloc_master(&pdev->dev, sizeof(struct mockup_spi));
+ if (!master) {
+ pr_err("failed to alloc spi master\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, master);
+
+ master->dev.of_node = pdev->dev.of_node;
+ master->dev.groups = spi_mockup_groups;
+ master->num_chipselect = MOCKUP_CHIPSELECT_MAX;
+ master->mode_bits = SPI_MODE_USER_MASK;
+ master->bus_num = 0;
+ master->transfer_one_message = spi_mockup_transfer;
+
+ mock = spi_master_get_devdata(master);
+ mutex_init(&mock->lock);
+
+ ret = spi_register_master(master);
+ if (ret) {
+ spi_master_put(master);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int spi_mockup_remove(struct platform_device *pdev)
+{
+ struct spi_master *master = platform_get_drvdata(pdev);
+
+ spi_unregister_master(master);
+
+ return 0;
+}
+
+static const struct of_device_id spi_mockup_match[] = {
+ { .compatible = "spi-mockup", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, spi_mockup_match);
+
+static struct platform_driver spi_mockup_driver = {
+ .probe = spi_mockup_probe,
+ .remove = spi_mockup_remove,
+ .driver = {
+ .name = "spi-mockup",
+ .of_match_table = spi_mockup_match,
+ },
+};
+module_platform_driver(spi_mockup_driver);
+
+MODULE_AUTHOR("Wei Yongjun <weiyongjun1@xxxxxxxxxx>");
+MODULE_DESCRIPTION("SPI controller Testing Driver");
+MODULE_LICENSE("GPL");
--
2.34.1