summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/of/Kconfig6
-rw-r--r--drivers/of/Makefile1
-rw-r--r--drivers/of/base.c88
-rw-r--r--drivers/of/of_i2c.c64
-rw-r--r--drivers/of/of_spi.c93
-rw-r--r--drivers/spi/spi.c139
-rw-r--r--include/linux/of.h1
-rw-r--r--include/linux/of_spi.h18
-rw-r--r--include/linux/spi/spi.h12
9 files changed, 317 insertions, 105 deletions
diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig
index 1d7ec312934..f821dbc952a 100644
--- a/drivers/of/Kconfig
+++ b/drivers/of/Kconfig
@@ -13,3 +13,9 @@ config OF_I2C
depends on PPC_OF && I2C
help
OpenFirmware I2C accessors
+
+config OF_SPI
+ def_tristate SPI
+ depends on OF && PPC_OF && SPI
+ help
+ OpenFirmware SPI accessors
diff --git a/drivers/of/Makefile b/drivers/of/Makefile
index 548772e871f..4c3c6f8e36f 100644
--- a/drivers/of/Makefile
+++ b/drivers/of/Makefile
@@ -2,3 +2,4 @@ obj-y = base.o
obj-$(CONFIG_OF_DEVICE) += device.o platform.o
obj-$(CONFIG_OF_GPIO) += gpio.o
obj-$(CONFIG_OF_I2C) += of_i2c.o
+obj-$(CONFIG_OF_SPI) += of_spi.o
diff --git a/drivers/of/base.c b/drivers/of/base.c
index 23ffb7c0caf..ad8ac1a8af2 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -385,3 +385,91 @@ struct device_node *of_find_matching_node(struct device_node *from,
return np;
}
EXPORT_SYMBOL(of_find_matching_node);
+
+/**
+ * of_modalias_table: Table of explicit compatible ==> modalias mappings
+ *
+ * This table allows particulare compatible property values to be mapped
+ * to modalias strings. This is useful for busses which do not directly
+ * understand the OF device tree but are populated based on data contained
+ * within the device tree. SPI and I2C are the two current users of this
+ * table.
+ *
+ * In most cases, devices do not need to be listed in this table because
+ * the modalias value can be derived directly from the compatible table.
+ * However, if for any reason a value cannot be derived, then this table
+ * provides a method to override the implicit derivation.
+ *
+ * At the moment, a single table is used for all bus types because it is
+ * assumed that the data size is small and that the compatible values
+ * should already be distinct enough to differentiate between SPI, I2C
+ * and other devices.
+ */
+struct of_modalias_table {
+ char *of_device;
+ char *modalias;
+};
+static struct of_modalias_table of_modalias_table[] = {
+ /* Empty for now; add entries as needed */
+};
+
+/**
+ * of_modalias_node - Lookup appropriate modalias for a device node
+ * @node: pointer to a device tree node
+ * @modalias: Pointer to buffer that modalias value will be copied into
+ * @len: Length of modalias value
+ *
+ * Based on the value of the compatible property, this routine will determine
+ * an appropriate modalias value for a particular device tree node. Three
+ * separate methods are used to derive a modalias value.
+ *
+ * First method is to lookup the compatible value in of_modalias_table.
+ * Second is to look for a "linux,<modalias>" entry in the compatible list
+ * and used that for modalias. Third is to strip off the manufacturer
+ * prefix from the first compatible entry and use the remainder as modalias
+ *
+ * This routine returns 0 on success
+ */
+int of_modalias_node(struct device_node *node, char *modalias, int len)
+{
+ int i, cplen;
+ const char *compatible;
+ const char *p;
+
+ /* 1. search for exception list entry */
+ for (i = 0; i < ARRAY_SIZE(of_modalias_table); i++) {
+ compatible = of_modalias_table[i].of_device;
+ if (!of_device_is_compatible(node, compatible))
+ continue;
+ strlcpy(modalias, of_modalias_table[i].modalias, len);
+ return 0;
+ }
+
+ compatible = of_get_property(node, "compatible", &cplen);
+ if (!compatible)
+ return -ENODEV;
+
+ /* 2. search for linux,<modalias> entry */
+ p = compatible;
+ while (cplen > 0) {
+ if (!strncmp(p, "linux,", 6)) {
+ p += 6;
+ strlcpy(modalias, p, len);
+ return 0;
+ }
+
+ i = strlen(p) + 1;
+ p += i;
+ cplen -= i;
+ }
+
+ /* 3. take first compatible entry and strip manufacturer */
+ p = strchr(compatible, ',');
+ if (!p)
+ return -ENODEV;
+ p++;
+ strlcpy(modalias, p, len);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(of_modalias_node);
+
diff --git a/drivers/of/of_i2c.c b/drivers/of/of_i2c.c
index 344e1b03dd8..6a98dc8aa30 100644
--- a/drivers/of/of_i2c.c
+++ b/drivers/of/of_i2c.c
@@ -16,62 +16,6 @@
#include <linux/of_i2c.h>
#include <linux/module.h>
-struct i2c_driver_device {
- char *of_device;
- char *i2c_type;
-};
-
-static struct i2c_driver_device i2c_devices[] = {
-};
-
-static int of_find_i2c_driver(struct device_node *node,
- struct i2c_board_info *info)
-{
- int i, cplen;
- const char *compatible;
- const char *p;
-
- /* 1. search for exception list entry */
- for (i = 0; i < ARRAY_SIZE(i2c_devices); i++) {
- if (!of_device_is_compatible(node, i2c_devices[i].of_device))
- continue;
- if (strlcpy(info->type, i2c_devices[i].i2c_type,
- I2C_NAME_SIZE) >= I2C_NAME_SIZE)
- return -ENOMEM;
-
- return 0;
- }
-
- compatible = of_get_property(node, "compatible", &cplen);
- if (!compatible)
- return -ENODEV;
-
- /* 2. search for linux,<i2c-type> entry */
- p = compatible;
- while (cplen > 0) {
- if (!strncmp(p, "linux,", 6)) {
- p += 6;
- if (strlcpy(info->type, p,
- I2C_NAME_SIZE) >= I2C_NAME_SIZE)
- return -ENOMEM;
- return 0;
- }
-
- i = strlen(p) + 1;
- p += i;
- cplen -= i;
- }
-
- /* 3. take fist compatible entry and strip manufacturer */
- p = strchr(compatible, ',');
- if (!p)
- return -ENODEV;
- p++;
- if (strlcpy(info->type, p, I2C_NAME_SIZE) >= I2C_NAME_SIZE)
- return -ENOMEM;
- return 0;
-}
-
void of_register_i2c_devices(struct i2c_adapter *adap,
struct device_node *adap_node)
{
@@ -83,6 +27,9 @@ void of_register_i2c_devices(struct i2c_adapter *adap,
const u32 *addr;
int len;
+ if (of_modalias_node(node, info.type, sizeof(info.type)) < 0)
+ continue;
+
addr = of_get_property(node, "reg", &len);
if (!addr || len < sizeof(int) || *addr > (1 << 10) - 1) {
printk(KERN_ERR
@@ -92,11 +39,6 @@ void of_register_i2c_devices(struct i2c_adapter *adap,
info.irq = irq_of_parse_and_map(node, 0);
- if (of_find_i2c_driver(node, &info) < 0) {
- irq_dispose_mapping(info.irq);
- continue;
- }
-
info.addr = *addr;
request_module(info.type);
diff --git a/drivers/of/of_spi.c b/drivers/of/of_spi.c
new file mode 100644
index 00000000000..b01eec026f6
--- /dev/null
+++ b/drivers/of/of_spi.c
@@ -0,0 +1,93 @@
+/*
+ * SPI OF support routines
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ *
+ * Support routines for deriving SPI device attachments from the device
+ * tree.
+ */
+
+#include <linux/of.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/of_spi.h>
+
+/**
+ * of_register_spi_devices - Register child devices onto the SPI bus
+ * @master: Pointer to spi_master device
+ * @np: parent node of SPI device nodes
+ *
+ * Registers an spi_device for each child node of 'np' which has a 'reg'
+ * property.
+ */
+void of_register_spi_devices(struct spi_master *master, struct device_node *np)
+{
+ struct spi_device *spi;
+ struct device_node *nc;
+ const u32 *prop;
+ int rc;
+ int len;
+
+ for_each_child_of_node(np, nc) {
+ /* Alloc an spi_device */
+ spi = spi_alloc_device(master);
+ if (!spi) {
+ dev_err(&master->dev, "spi_device alloc error for %s\n",
+ nc->full_name);
+ spi_dev_put(spi);
+ continue;
+ }
+
+ /* Select device driver */
+ if (of_modalias_node(nc, spi->modalias,
+ sizeof(spi->modalias)) < 0) {
+ dev_err(&master->dev, "cannot find modalias for %s\n",
+ nc->full_name);
+ spi_dev_put(spi);
+ continue;
+ }
+
+ /* Device address */
+ prop = of_get_property(nc, "reg", &len);
+ if (!prop || len < sizeof(*prop)) {
+ dev_err(&master->dev, "%s has no 'reg' property\n",
+ nc->full_name);
+ spi_dev_put(spi);
+ continue;
+ }
+ spi->chip_select = *prop;
+
+ /* Mode (clock phase/polarity/etc.) */
+ if (of_find_property(nc, "spi-cpha", NULL))
+ spi->mode |= SPI_CPHA;
+ if (of_find_property(nc, "spi-cpol", NULL))
+ spi->mode |= SPI_CPOL;
+
+ /* Device speed */
+ prop = of_get_property(nc, "spi-max-frequency", &len);
+ if (!prop || len < sizeof(*prop)) {
+ dev_err(&master->dev, "%s has no 'spi-max-frequency' property\n",
+ nc->full_name);
+ spi_dev_put(spi);
+ continue;
+ }
+ spi->max_speed_hz = *prop;
+
+ /* IRQ */
+ spi->irq = irq_of_parse_and_map(nc, 0);
+
+ /* Store a pointer to the node in the device structure */
+ of_node_get(nc);
+ spi->dev.archdata.of_node = nc;
+
+ /* Register the new device */
+ request_module(spi->modalias);
+ rc = spi_add_device(spi);
+ if (rc) {
+ dev_err(&master->dev, "spi_device register error %s\n",
+ nc->full_name);
+ spi_dev_put(spi);
+ }
+
+ }
+}
+EXPORT_SYMBOL(of_register_spi_devices);
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index ecca4a6a6f9..964124b60db 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -178,6 +178,96 @@ struct boardinfo {
static LIST_HEAD(board_list);
static DEFINE_MUTEX(board_lock);
+/**
+ * spi_alloc_device - Allocate a new SPI device
+ * @master: Controller to which device is connected
+ * Context: can sleep
+ *
+ * Allows a driver to allocate and initialize a spi_device without
+ * registering it immediately. This allows a driver to directly
+ * fill the spi_device with device parameters before calling
+ * spi_add_device() on it.
+ *
+ * Caller is responsible to call spi_add_device() on the returned
+ * spi_device structure to add it to the SPI master. If the caller
+ * needs to discard the spi_device without adding it, then it should
+ * call spi_dev_put() on it.
+ *
+ * Returns a pointer to the new device, or NULL.
+ */
+struct spi_device *spi_alloc_device(struct spi_master *master)
+{
+ struct spi_device *spi;
+ struct device *dev = master->dev.parent;
+
+ if (!spi_master_get(master))
+ return NULL;
+
+ spi = kzalloc(sizeof *spi, GFP_KERNEL);
+ if (!spi) {
+ dev_err(dev, "cannot alloc spi_device\n");
+ spi_master_put(master);
+ return NULL;
+ }
+
+ spi->master = master;
+ spi->dev.parent = dev;
+ spi->dev.bus = &spi_bus_type;
+ spi->dev.release = spidev_release;
+ device_initialize(&spi->dev);
+ return spi;
+}
+EXPORT_SYMBOL_GPL(spi_alloc_device);
+
+/**
+ * spi_add_device - Add spi_device allocated with spi_alloc_device
+ * @spi: spi_device to register
+ *
+ * Companion function to spi_alloc_device. Devices allocated with
+ * spi_alloc_device can be added onto the spi bus with this function.
+ *
+ * Returns 0 on success; non-zero on failure
+ */
+int spi_add_device(struct spi_device *spi)
+{
+ struct device *dev = spi->master->dev.parent;
+ int status;
+
+ /* Chipselects are numbered 0..max; validate. */
+ if (spi->chip_select >= spi->master->num_chipselect) {
+ dev_err(dev, "cs%d >= max %d\n",
+ spi->chip_select,
+ spi->master->num_chipselect);
+ return -EINVAL;
+ }
+
+ /* Set the bus ID string */
+ snprintf(spi->dev.bus_id, sizeof spi->dev.bus_id,
+ "%s.%u", spi->master->dev.bus_id,
+ spi->chip_select);
+
+ /* drivers may modify this initial i/o setup */
+ status = spi->master->setup(spi);
+ if (status < 0) {
+ dev_err(dev, "can't %s %s, status %d\n",
+ "setup", spi->dev.bus_id, status);
+ return status;
+ }
+
+ /* driver core catches callers that misbehave by defining
+ * devices that already exist.
+ */
+ status = device_add(&spi->dev);
+ if (status < 0) {
+ dev_err(dev, "can't %s %s, status %d\n",
+ "add", spi->dev.bus_id, status);
+ return status;
+ }
+
+ dev_dbg(dev, "registered child %s\n", spi->dev.bus_id);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spi_add_device);
/**
* spi_new_device - instantiate one new SPI device
@@ -197,7 +287,6 @@ struct spi_device *spi_new_device(struct spi_master *master,
struct spi_board_info *chip)
{
struct spi_device *proxy;
- struct device *dev = master->dev.parent;
int status;
/* NOTE: caller did any chip->bus_num checks necessary.
@@ -207,66 +296,28 @@ struct spi_device *spi_new_device(struct spi_master *master,
* suggests syslogged diagnostics are best here (ugh).
*/
- /* Chipselects are numbered 0..max; validate. */
- if (chip->chip_select >= master->num_chipselect) {
- dev_err(dev, "cs%d > max %d\n",
- chip->chip_select,
- master->num_chipselect);
- return NULL;
- }
-
- if (!spi_master_get(master))
+ proxy = spi_alloc_device(master);
+ if (!proxy)
return NULL;
WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias));
- proxy = kzalloc(sizeof *proxy, GFP_KERNEL);
- if (!proxy) {
- dev_err(dev, "can't alloc dev for cs%d\n",
- chip->chip_select);
- goto fail;
- }
- proxy->master = master;
proxy->chip_select = chip->chip_select;
proxy->max_speed_hz = chip->max_speed_hz;
proxy->mode = chip->mode;
proxy->irq = chip->irq;
strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));
-
- snprintf(proxy->dev.bus_id, sizeof proxy->dev.bus_id,
- "%s.%u", master->dev.bus_id,
- chip->chip_select);
- proxy->dev.parent = dev;
- proxy->dev.bus = &spi_bus_type;
proxy->dev.platform_data = (void *) chip->platform_data;
proxy->controller_data = chip->controller_data;
proxy->controller_state = NULL;
- proxy->dev.release = spidev_release;
- /* drivers may modify this initial i/o setup */
- status = master->setup(proxy);
+ status = spi_add_device(proxy);
if (status < 0) {
- dev_err(dev, "can't %s %s, status %d\n",
- "setup", proxy->dev.bus_id, status);
- goto fail;
+ spi_dev_put(proxy);
+ return NULL;
}
- /* driver core catches callers that misbehave by defining
- * devices that already exist.
- */
- status = device_register(&proxy->dev);
- if (status < 0) {
- dev_err(dev, "can't %s %s, status %d\n",
- "add", proxy->dev.bus_id, status);
- goto fail;
- }
- dev_dbg(dev, "registered child %s\n", proxy->dev.bus_id);
return proxy;
-
-fail:
- spi_master_put(master);
- kfree(proxy);
- return NULL;
}
EXPORT_SYMBOL_GPL(spi_new_device);
diff --git a/include/linux/of.h b/include/linux/of.h
index 59a61bdc98b..79886ade070 100644
--- a/include/linux/of.h
+++ b/include/linux/of.h
@@ -70,5 +70,6 @@ extern int of_n_addr_cells(struct device_node *np);
extern int of_n_size_cells(struct device_node *np);
extern const struct of_device_id *of_match_node(
const struct of_device_id *matches, const struct device_node *node);
+extern int of_modalias_node(struct device_node *node, char *modalias, int len);
#endif /* _LINUX_OF_H */
diff --git a/include/linux/of_spi.h b/include/linux/of_spi.h
new file mode 100644
index 00000000000..5f71ee8c086
--- /dev/null
+++ b/include/linux/of_spi.h
@@ -0,0 +1,18 @@
+/*
+ * OpenFirmware SPI support routines
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ *
+ * Support routines for deriving SPI device attachments from the device
+ * tree.
+ */
+
+#ifndef __LINUX_OF_SPI_H
+#define __LINUX_OF_SPI_H
+
+#include <linux/of.h>
+#include <linux/spi/spi.h>
+
+extern void of_register_spi_devices(struct spi_master *master,
+ struct device_node *np);
+
+#endif /* __LINUX_OF_SPI */
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index a9cc29d4665..4be01bb4437 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -778,8 +778,20 @@ spi_register_board_info(struct spi_board_info const *info, unsigned n)
* use spi_new_device() to describe each device. You can also call
* spi_unregister_device() to start making that device vanish, but
* normally that would be handled by spi_unregister_master().
+ *
+ * You can also use spi_alloc_device() and spi_add_device() to use a two
+ * stage registration sequence for each spi_device. This gives the caller
+ * some more control over the spi_device structure before it is registered,
+ * but requires that caller to initialize fields that would otherwise
+ * be defined using the board info.
*/
extern struct spi_device *
+spi_alloc_device(struct spi_master *master);
+
+extern int
+spi_add_device(struct spi_device *spi);
+
+extern struct spi_device *
spi_new_device(struct spi_master *, struct spi_board_info *);
static inline void