diff mbox

[resend] spi core: Provide means to instantiate devices through sysfs

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

Commit Message

Guenter Roeck Sept. 17, 2012, 11:51 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.

ping: Any interest ?

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

Comments

Mark Brown Sept. 22, 2012, 4:11 p.m. UTC | #1
On Mon, Sep 17, 2012 at 04:51:20PM -0700, Guenter Roeck wrote:
> The I2C core provides a means to instantiate devices from userspace
> using sysfs attributes. Provide the same mechanism for SPI devices.

So, unlike I2C this is only going to work for a subset of controllers -
anything that relies on GPIOs for chip select will need more data to add
the chip selects for example.  This means I'm having a hard time getting
enthused about the idea, it seems like it might be a support burden.

------------------------------------------------------------------------------
How fast is your code?
3 out of 4 devs don\\\'t know how their code performs in production.
Find out how slow your code is with AppDynamics Lite.
http://ad.doubleclick.net/clk;262219672;13503038;z?
http://info.appdynamics.com/FreeJavaPerformanceDownload.html
Guenter Roeck Sept. 22, 2012, 6:56 p.m. UTC | #2
On Sat, Sep 22, 2012 at 12:11:33PM -0400, Mark Brown wrote:
> On Mon, Sep 17, 2012 at 04:51:20PM -0700, Guenter Roeck wrote:
> > The I2C core provides a means to instantiate devices from userspace
> > using sysfs attributes. Provide the same mechanism for SPI devices.
> 
> So, unlike I2C this is only going to work for a subset of controllers -
> anything that relies on GPIOs for chip select will need more data to add
> the chip selects for example.  This means I'm having a hard time getting
> enthused about the idea, it seems like it might be a support burden.
> 
Yes, obviously that model can not be used if SPI chip select is handled outside
the SPI master driver.

Anyway, no problem. That the code is useful for me doesn't mean it has to be
useful for others. I'll make it available as branch on my linux tree on github.

Thanks,
Guenter

------------------------------------------------------------------------------
How fast is your code?
3 out of 4 devs don\\\'t know how their code performs in production.
Find out how slow your code is with AppDynamics Lite.
http://ad.doubleclick.net/clk;262219672;13503038;z?
http://info.appdynamics.com/FreeJavaPerformanceDownload.html
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.