diff mbox

spi core: Provide means to instantiate devices through sysfs

Message ID 1347121065-31879-1-git-send-email-linux@roeck-us.net (mailing list archive)
State Superseded, archived
Headers show

Commit Message

Guenter Roeck Sept. 8, 2012, 4:17 p.m. UTC
The I2C core provides a means to instantiate devices from userspace
using sysfs attributes. Provide the same mechanism for SPI devices.

Signed-off-by: Guenter Roeck <linux@roeck-us.net>
---
This helped me tremendously for testing new SPI master and client drivers.
Maybe it is useful for others as well.

 Documentation/spi/spi-summary |   48 +++++++++++++
 drivers/spi/spi.c             |  159 +++++++++++++++++++++++++++++++++++++++++
 include/linux/spi/spi.h       |    3 +
 3 files changed, 210 insertions(+)
diff mbox

Patch

diff --git a/Documentation/spi/spi-summary b/Documentation/spi/spi-summary
index 7312ec1..5b59992 100644
--- a/Documentation/spi/spi-summary
+++ b/Documentation/spi/spi-summary
@@ -331,6 +331,54 @@  configurations will also be dynamic.  Fortunately, such devices all support
 basic device identification probes, so they should hotplug normally.
 
 
+DECLARE SLAVE DEVICES FROM USER-SPACE
+
+In general, the kernel should know which SPI devices are connected and
+what addresses they live at. However, in certain cases, it does not, so a
+sysfs interface was added to let the user provide the information. This
+interface is made of 2 attribute files which are created in every SPI master
+directory: new_device and delete_device. Both files are write only and you
+must write the right parameters to them in order to properly instantiate,
+respectively delete, a SPI device.
+
+File new_device takes several parameters:
+
+Primary parameters are the name of the SPI device (a string), the SPI chip
+select (a number, expressed in decimal or hexadecimal), the SPI bus speed
+(a number, expressed in decimal or hexadecimal), and the SPI mode (a number,
+expressed in decimal or hexadecimal). SPI device name, chip select, and speed
+are mandatory. The mode parameter is optional and will be initialized with 0 if
+not provided.
+
+For at25 type EEPROMs, additional parameters must be provided in < >. Those are
+the EEPROM name (a string), the EEPROM capacity in bytes (a number, expressed in
+decimal or hexadecimal), the EEPROM write page size (a number, expressed in
+decimal or hexadecimal), and the EEPROM flags (a number, expressed in decimal or
+hexadecimal).
+
+File delete_device takes a single parameter: the chip select of the SPI
+device. As no two devices can live at the same chip select on a given SPI
+master, the chip select is sufficient to uniquely identify the device to be
+deleted.
+
+Examples:
+	cd /sys/class/spi_master/spi0
+		Instantiate devices on SPI master 0
+
+	echo "lm70 0 400000" > new_device
+		Instantiate LM70 on CS0
+
+	echo "at25 1 1000000 0 < at25160b 2048 32 0x02 >" > new_device
+		Instantiate at25 driver with AT25160B on CS1
+
+	echo "sst25vf080b 2 1000000 3" > new_device
+		Instantiate SST25VF080B on CS2, using SPI mode 3
+
+While this interface should only be used when in-kernel device declaration
+can't be done, it can be helpful if you are developing a driver on a test board,
+where you soldered the SPI device yourself, or in hot-plug situations.
+
+
 How do I write an "SPI Protocol Driver"?
 ----------------------------------------
 Most SPI drivers are currently kernel drivers, but there's also support
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 84c2861..da9ea04 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -30,11 +30,13 @@ 
 #include <linux/slab.h>
 #include <linux/mod_devicetable.h>
 #include <linux/spi/spi.h>
+#include <linux/spi/eeprom.h>
 #include <linux/pm_runtime.h>
 #include <linux/export.h>
 #include <linux/sched.h>
 #include <linux/delay.h>
 #include <linux/kthread.h>
+#include <linux/stat.h>
 
 static void spidev_release(struct device *dev)
 {
@@ -229,6 +231,161 @@  struct bus_type spi_bus_type = {
 };
 EXPORT_SYMBOL_GPL(spi_bus_type);
 
+/*
+ * Let users instantiate SPI devices through sysfs. This can be used when
+ * platform initialization code doesn't contain the proper data for
+ * whatever reason, or for testing.
+ *
+ * Parameter checking may look overzealous, but we really don't want
+ * the user to provide incorrect parameters.
+ */
+static ssize_t
+spi_sysfs_new_device(struct device *dev, struct device_attribute *attr,
+		     const char *buf, size_t count)
+{
+	struct spi_master *master = container_of(dev, struct spi_master, dev);
+	struct spi_board_info info;
+	struct spi_eeprom eeprom;
+	struct spi_device *spi;
+	char end, d1, d2;
+	unsigned int cs, speed, mode = 0;
+	unsigned int len, pagesize, flags;
+	int res;
+
+	dev_warn(dev,
+		 "The new_device interface is still experimental and may change in a near future\n");
+
+	memset(&info, 0, sizeof(struct spi_board_info));
+	memset(&eeprom, 0, sizeof(struct spi_eeprom));
+
+	/* Parse parameters, reject extra parameters */
+	res = sscanf(buf, " %32s %u %u %x %c %10s %u %u %x %c%c",
+		     info.modalias, &cs, &speed, &mode,
+		     &d1, eeprom.name, &len, &pagesize, &flags, &d2,
+		     &end);
+	/* Must have at least name (modalias), chip select, and frequency */
+	if (res < 3) {
+		dev_err(dev, "new_device: Can't parse SPI data\n");
+		return -EINVAL;
+	}
+	if (mode & ~master->mode_bits) {
+		dev_err(dev, "new_device: Unsupported mode\n");
+		return -EINVAL;
+	}
+	if (cs >= master->num_chipselect) {
+		dev_err(dev, "new_device: Bad chipselect\n");
+		return -EINVAL;
+	}
+	if (speed == 0) {
+		dev_err(dev, "new_device: Bad speed\n");
+		return -EINVAL;
+	}
+	if (!strcmp(info.modalias, "at25")) {
+		/* For EEPROMs, all parameters must be provided and valid */
+		if (res != 11 || d1 != '<' || d2 != '>' || end != '\n' ||
+		    !len || !pagesize || !flags || (flags & ~0x1f)) {
+			dev_err(dev, "new_device: Can't parse EEPROM data\n");
+			return -EINVAL;
+		}
+		eeprom.byte_len = len;
+		eeprom.page_size = pagesize;
+		eeprom.flags = flags;
+		info.platform_data = &eeprom;
+	} else if (res > 4) {
+		dev_err(dev, "new_device: Extra parameters\n");
+		return -EINVAL;
+	}
+	info.chip_select = cs;
+	info.mode = mode;
+	info.max_speed_hz = speed;
+
+	spi = spi_new_device(master, &info);
+	if (!spi)
+		return -EINVAL;
+
+	/* Keep track of the added device */
+	mutex_lock(&master->bus_lock_mutex);
+	list_add_tail(&spi->detected, &master->userspace_devices);
+	mutex_unlock(&master->bus_lock_mutex);
+	dev_info(dev,
+		 "new_device: Instantiated device %s:%u (speed=%u, mode=%u)\n",
+		 info.modalias, info.chip_select, info.max_speed_hz, info.mode);
+
+	return count;
+}
+
+/*
+ * And of course let the users delete the devices they instantiated, if
+ * they got it wrong. This interface can only be used to delete devices
+ * instantiated by spi_sysfs_new_device above. This guarantees that we
+ * don't delete devices to which some kernel code still has references.
+ *
+ * Parameter checking may look overzealous, but we really don't want
+ * the user to delete the wrong device.
+ */
+static ssize_t
+spi_sysfs_delete_device(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	struct spi_master *master = container_of(dev, struct spi_master, dev);
+	struct spi_device *spi, *next;
+	unsigned int cs;
+	char end;
+	int res;
+
+	/* Parse parameters, reject extra parameters */
+	res = sscanf(buf, "%u%c", &cs, &end);
+	if (res < 1) {
+		dev_err(dev, "delete_device: Can't parse SPI chip select\n");
+		return -EINVAL;
+	}
+	if (res > 1 && end != '\n') {
+		dev_err(dev, "delete_device: Extra parameters\n");
+		return -EINVAL;
+	}
+
+	/* Make sure the device was added through sysfs */
+	res = -ENOENT;
+	mutex_lock(&master->bus_lock_mutex);
+	list_for_each_entry_safe(spi, next, &master->userspace_devices,
+				 detected) {
+		if (spi->chip_select == cs) {
+			dev_info(dev, "delete_device: Deleting device %s:%u\n",
+				 spi->modalias, spi->chip_select);
+			list_del(&spi->detected);
+			spi_unregister_device(spi);
+			res = count;
+			break;
+		}
+	}
+	mutex_unlock(&master->bus_lock_mutex);
+
+	if (res < 0)
+		dev_err(dev, "delete_device: Can't find device in list\n");
+	return res;
+}
+
+static DEVICE_ATTR(new_device, S_IWUSR, NULL, spi_sysfs_new_device);
+static DEVICE_ATTR(delete_device, S_IWUSR, NULL, spi_sysfs_delete_device);
+
+static struct attribute *spi_master_attrs[] = {
+	&dev_attr_new_device.attr,
+	&dev_attr_delete_device.attr,
+	NULL
+};
+
+static struct attribute_group spi_master_attr_group = {
+	.attrs		= spi_master_attrs,
+};
+
+static const struct attribute_group *spi_master_attr_groups[] = {
+	&spi_master_attr_group,
+	NULL
+};
+
+static struct device_type spi_master_type = {
+	.groups		= spi_master_attr_groups,
+};
 
 static int spi_drv_probe(struct device *dev)
 {
@@ -584,6 +741,7 @@  static int spi_init_queue(struct spi_master *master)
 	struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };
 
 	INIT_LIST_HEAD(&master->queue);
+	INIT_LIST_HEAD(&master->userspace_devices);
 	spin_lock_init(&master->queue_lock);
 
 	master->running = false;
@@ -939,6 +1097,7 @@  struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
 	master->bus_num = -1;
 	master->num_chipselect = 1;
 	master->dev.class = &spi_master_class;
+	master->dev.type = &spi_master_type;
 	master->dev.parent = get_device(dev);
 	spi_master_set_devdata(master, &master[1]);
 
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index fa702ae..dac46bc 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -91,6 +91,8 @@  struct spi_device {
 	void			*controller_data;
 	char			modalias[SPI_NAME_SIZE];
 
+	struct list_head	detected;
+
 	/*
 	 * likely need more hooks for more protocol options affecting how
 	 * the controller talks to each chip, like:
@@ -273,6 +275,7 @@  struct spi_master {
 	struct device	dev;
 
 	struct list_head list;
+	struct list_head userspace_devices;
 
 	/* other than negative (== assign one dynamically), bus_num is fully
 	 * board-specific.  usually that simplifies to being SOC-specific.