[PATCHv2 2/2] basic-mmio-gpio: add support for device tree probing

Jamie Iles jamie at jamieiles.com
Fri Jul 29 01:25:42 EST 2011


This patch adds support for basic-mmio-gpio controllers to be
instantiated from the device tree.  The binding supports devices with
multiple banks.

v2:
	- Added more detail to the binding.
	- Added CONFIG_OF guards.
	- Use regoffset-* properties for each register in each bank
	  relative to the registers of the controller.

Cc: Grant Likely <grant.likely at secretlab.ca>
Cc: Anton Vorontsov <cbouatmailru at gmail.com>
Signed-off-by: Jamie Iles <jamie at jamieiles.com>
---
 .../devicetree/bindings/gpio/basic-mmio-gpio.txt   |   85 ++++++++
 drivers/gpio/basic_mmio_gpio.c                     |  229 +++++++++++++++-----
 include/linux/basic_mmio_gpio.h                    |   78 ++++++-
 3 files changed, 331 insertions(+), 61 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/gpio/basic-mmio-gpio.txt

diff --git a/Documentation/devicetree/bindings/gpio/basic-mmio-gpio.txt b/Documentation/devicetree/bindings/gpio/basic-mmio-gpio.txt
new file mode 100644
index 0000000..3c4edf5
--- /dev/null
+++ b/Documentation/devicetree/bindings/gpio/basic-mmio-gpio.txt
@@ -0,0 +1,85 @@
+Basic MMIO GPIO controller
+
+This binding allows lots of common GPIO controllers to use a generic GPIO
+driver.  The top level GPIO node describes the registers for the controller
+and high level properties such as endianness and register width.  Each bank in
+the controller is represented as a child node.
+
+Required properties:
+- compatible : "basic-mmio-gpio"
+- reg : The register window for the GPIO device.  If the device has multiple
+  banks then this window should cover all of the banks.
+- basic-mmio-gpio,reg-io-width : The width of the registers in the controller
+  (in bytes).
+- #address-cells : should be set to 1.
+- #size-cells : should be set to 0.  The addresses of the child nodes are the
+  bank numbers and the child nodes themselves represent the banks in the
+  controller.
+
+Optional properties:
+- basic-mmio-gpio,big-endian : the registers are in big-endian byte ordering.
+  If present then the first gpio in the controller occupies the MSB for
+  each register.
+
+Basic MMIO GPIO controller bank
+
+Required properties:
+- compatible : "basic-mmio-gpio-bank"
+- gpio-controller : Marks the node as a GPIO controller.
+- #gpio-cells : Should be two.  The first cell is the pin number and the
+  second cell encodes optional flags (currently unused).
+- basic-mmio-gpio,nr-gpio : The number of GPIO pins in the bank.
+- regoffset-dat : The offset from the beginning of the controller for the
+  "dat" register for this bank.  This register is read to get the value of the
+  GPIO pins, and if there is no regoffset-set property then it is also used to
+  set the value of the pins.
+
+Optional properties:
+- regoffset-set : The offset from the beginning of the controller for the
+  "set" register for this bank.  If present then the GPIO values are set
+  through this register (writing a 1 bit sets the GPIO high).  If there is no
+  regoffset-clr property then writing a 0 bit to this register will set the
+  pin to a low value.
+- regoffset-clr : The offset from the beginning of the controller for the
+  "clr" register for this bank.  Writing a 1 bit to this register will set the
+  GPIO to a low output value.
+- regoffset-dirout : The offset from the beginning of the controller for the
+  "dirout" register for this bank.  Writing a 1 bit to this register sets the
+  pin to be an output pin, writing a zero sets the pin to be an input.
+- regoffset-dirin : The offset from the beginning of the controller for the
+  "dirin" register for this bank.  Writing a 1 bit to this register sets the
+  pin to be an input pin, writing a zero sets the pin to be an output.
+
+Examples:
+
+gpio: gpio at 20000 {
+	compatible = "picoxcell,picoxcell-gpio", "basic-mmio-gpio";
+	reg = <0x20000 0x1000>;
+	#address-cells = <1>;
+	#size-cells = <0>;
+	basic-mmio-gpio,reg-io-width = <4>;
+
+	banka: gpio-controller at 0 {
+		compatible = "picochip,picoxcell-gpio-bank",
+			     "basic-mmio-gpio-bank";
+		gpio-controller;
+		#gpio-cells = <2>;
+		basic-mmio-gpio,nr-gpio = <8>;
+
+		regoffset-dat = <0x50>;
+		regoffset-set = <0x00>;
+		regoffset-dirout = <0x04>;
+	};
+
+	bankb: gpio-controller at 1 {
+		compatible = "picochip,picoxcell-gpio-bank",
+			     "basic-mmio-gpio-bank";
+		gpio-controller;
+		#gpio-cells = <2>;
+		basic-mmio-gpio,nr-gpio = <16>;
+
+		regoffset-dat = <0x54>;
+		regoffset-set = <0x0c>;
+		regoffset-dirout = <0x10>;
+	};
+};
diff --git a/drivers/gpio/basic_mmio_gpio.c b/drivers/gpio/basic_mmio_gpio.c
index 8152e9f..50044c3 100644
--- a/drivers/gpio/basic_mmio_gpio.c
+++ b/drivers/gpio/basic_mmio_gpio.c
@@ -61,6 +61,9 @@ o        `                     ~~~~\___/~~~~    ` controller in FPGA is ,.`
 #include <linux/platform_device.h>
 #include <linux/mod_devicetable.h>
 #include <linux/basic_mmio_gpio.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
 
 static void bgpio_write8(void __iomem *reg, unsigned long data)
 {
@@ -353,56 +356,65 @@ static int bgpio_setup_direction(struct bgpio_chip *bgc,
 
 int __devexit bgpio_remove(struct bgpio_chip *bgc)
 {
-	int err = gpiochip_remove(&bgc->gc);
+	int err;
+
+#ifdef CONFIG_OF
+	if (bgc->gc.of_node)
+		of_node_put(bgc->gc.of_node);
+#endif /* CONFIG_OF */
 
+	err = gpiochip_remove(&bgc->gc);
 	kfree(bgc);
 
 	return err;
 }
 EXPORT_SYMBOL_GPL(bgpio_remove);
 
-int __devinit bgpio_init(struct bgpio_chip *bgc,
-			 struct device *dev,
-			 unsigned long sz,
-			 void __iomem *dat,
-			 void __iomem *set,
-			 void __iomem *clr,
-			 void __iomem *dirout,
-			 void __iomem *dirin,
-			 bool big_endian)
+int bgpio_init_info(struct bgpio_chip *bgc, struct device *dev,
+		    const struct bgpio_info *info)
 {
 	int ret;
 
-	if (!is_power_of_2(sz))
+	if (!is_power_of_2(info->sz))
 		return -EINVAL;
 
-	bgc->bits = sz * 8;
+	bgc->bits = info->sz * 8;
 	if (bgc->bits > BITS_PER_LONG)
 		return -EINVAL;
 
 	spin_lock_init(&bgc->lock);
 	bgc->gc.dev = dev;
 	bgc->gc.label = dev_name(dev);
-	bgc->gc.base = -1;
-	bgc->gc.ngpio = bgc->bits;
-
-	ret = bgpio_setup_io(bgc, dat, set, clr);
+	bgc->gc.base = info->base;
+	bgc->gc.ngpio = info->ngpio;
+#ifdef CONFIG_OF
+	bgc->gc.of_node = info->of_node;
+	bgc->gc.of_gpio_n_cells = 2;
+	bgc->gc.of_xlate = of_gpio_simple_xlate;
+#endif /* CONFIG_OF */
+
+	ret = bgpio_setup_io(bgc, info->dat, info->set, info->clr);
 	if (ret)
 		return ret;
 
-	ret = bgpio_setup_accessors(dev, bgc, big_endian);
+	ret = bgpio_setup_accessors(dev, bgc, info->be);
 	if (ret)
 		return ret;
 
-	ret = bgpio_setup_direction(bgc, dirout, dirin);
+	ret = bgpio_setup_direction(bgc, info->dirout, info->dirin);
 	if (ret)
 		return ret;
 
 	bgc->data = bgc->read_reg(bgc->reg_dat);
 
+#ifdef CONFIG_OF
+	if (bgc->gc.of_node)
+		of_node_get(bgc->gc.of_node);
+#endif /* CONFIG_OF */
+
 	return ret;
 }
-EXPORT_SYMBOL_GPL(bgpio_init);
+EXPORT_SYMBOL_GPL(bgpio_init_info);
 
 #ifdef CONFIG_GPIO_BASIC_MMIO
 
@@ -444,73 +456,185 @@ static void __iomem *bgpio_map(struct platform_device *pdev,
 	return ret;
 }
 
-static int __devinit bgpio_pdev_probe(struct platform_device *pdev)
+static int bgpio_add_info(struct platform_device *pdev,
+			  const struct bgpio_info *info)
+{
+	int err;
+	struct bgpio_chip *bgc = devm_kzalloc(&pdev->dev, sizeof(*bgc),
+					      GFP_KERNEL);
+	struct list_head *banks = platform_get_drvdata(pdev);
+
+	if (!bgc)
+		return -ENOMEM;
+
+	err = bgpio_init_info(bgc, &pdev->dev, info);
+	if (err)
+		return err;
+
+	list_add_tail(&bgc->head, banks);
+
+	return gpiochip_add(&bgc->gc);
+}
+
+static int bgpio_platform_probe(struct platform_device *pdev)
 {
-	struct device *dev = &pdev->dev;
 	struct resource *r;
-	void __iomem *dat;
-	void __iomem *set;
-	void __iomem *clr;
-	void __iomem *dirout;
-	void __iomem *dirin;
-	unsigned long sz;
-	bool be;
 	int err;
-	struct bgpio_chip *bgc;
-	struct bgpio_pdata *pdata = dev_get_platdata(dev);
+	struct bgpio_pdata *pdata = dev_get_platdata(&pdev->dev);
+	struct bgpio_info info = {};
 
 	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dat");
 	if (!r)
 		return -EINVAL;
 
-	sz = resource_size(r);
+	info.sz = resource_size(r);
+	info.ngpio = info.sz * 8;
+	info.base = -1;
 
-	dat = bgpio_map(pdev, "dat", sz, &err);
-	if (!dat)
+	info.dat = bgpio_map(pdev, "dat", info.sz, &err);
+	if (!info.dat)
 		return err ? err : -EINVAL;
 
-	set = bgpio_map(pdev, "set", sz, &err);
+	info.set = bgpio_map(pdev, "set", info.sz, &err);
 	if (err)
 		return err;
 
-	clr = bgpio_map(pdev, "clr", sz, &err);
+	info.clr = bgpio_map(pdev, "clr", info.sz, &err);
 	if (err)
 		return err;
 
-	dirout = bgpio_map(pdev, "dirout", sz, &err);
+	info.dirout = bgpio_map(pdev, "dirout", info.sz, &err);
 	if (err)
 		return err;
 
-	dirin = bgpio_map(pdev, "dirin", sz, &err);
+	info.dirin = bgpio_map(pdev, "dirin", info.sz, &err);
 	if (err)
 		return err;
 
-	be = !strcmp(platform_get_device_id(pdev)->name, "basic-mmio-gpio-be");
-
-	bgc = devm_kzalloc(&pdev->dev, sizeof(*bgc), GFP_KERNEL);
-	if (!bgc)
-		return -ENOMEM;
-
-	err = bgpio_init(bgc, dev, sz, dat, set, clr, dirout, dirin, be);
-	if (err)
-		return err;
+	info.be = !strcmp(platform_get_device_id(pdev)->name,
+			   "basic-mmio-gpio-be");
 
 	if (pdata) {
-		bgc->gc.base = pdata->base;
+		info.base = pdata->base;
 		if (pdata->ngpio > 0)
-			bgc->gc.ngpio = pdata->ngpio;
+			info.ngpio = pdata->ngpio;
 	}
 
-	platform_set_drvdata(pdev, bgc);
+	return bgpio_add_info(pdev, &info);
+}
 
-	return gpiochip_add(&bgc->gc);
+static void bgpio_remove_all_banks(struct platform_device *pdev)
+{
+	struct bgpio_chip *bgc;
+	struct list_head *banks = platform_get_drvdata(pdev);
+
+	list_for_each_entry(bgc, banks, head)
+		bgpio_remove(bgc);
+}
+
+#ifdef CONFIG_OF
+static int bgpio_of_add_one_bank(struct platform_device *pdev,
+				 struct device_node *np, void __iomem *iobase,
+				 size_t reg_width_bytes, bool be)
+{
+	struct bgpio_info info = {
+		.sz = reg_width_bytes,
+		.base = -1,
+		.be = be,
+	};
+	u32 val;
+
+	if (of_property_read_u32(np, "regoffset-dat", &val))
+		return -EINVAL;
+	info.dat = iobase + val;
+
+	if (!of_property_read_u32(np, "regoffset-set", &val))
+		info.set = iobase + val;
+	if (!of_property_read_u32(np, "regoffset-clr", &val))
+		info.clr = iobase + val;
+	if (!of_property_read_u32(np, "regoffset-dirout", &val))
+		info.dirout = iobase + val;
+	if (!of_property_read_u32(np, "regoffset-dirin", &val))
+		info.dirin = iobase + val;
+
+	if (of_property_read_u32(np, "basic-mmio-gpio,nr-gpio", &val))
+		return -EINVAL;
+
+	info.ngpio = val;
+	info.of_node = np;
+
+	return bgpio_add_info(pdev, &info);
+}
+
+static int bgpio_of_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	void __iomem *iobase = of_iomap(np, 0);
+	int err = 0;
+	u32 val;
+	size_t reg_width_bytes;
+	bool be;
+
+	if (!iobase)
+		return -EIO;
+
+	if (of_property_read_u32(np, "basic-mmio-gpio,reg-io-width", &val))
+		return -EINVAL;
+	reg_width_bytes = val;
+
+	be = of_get_property(np, "basic-mmio-gpio,big-endian", NULL) ?
+		true : false;
+
+	for_each_compatible_node(np, NULL, "basic-mmio-gpio-bank") {
+		err = bgpio_of_add_one_bank(pdev, np, iobase,
+					    reg_width_bytes, be);
+		if (err)
+			goto out_remove;
+	}
+
+	return 0;
+
+out_remove:
+	bgpio_remove_all_banks(pdev);
+
+	return err;
+}
+
+static const struct of_device_id bgpio_of_id_table[] = {
+	{ .compatible = "basic-mmio-gpio", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, bgpio_of_id_table);
+#else /* CONFIG_OF */
+static inline int bgpio_of_probe(struct platform_device *pdev)
+{
+	return -ENODEV;
+}
+
+#define bgpio_of_id_table NULL
+#endif /* CONFIG_OF */
+
+static int __devinit bgpio_pdev_probe(struct platform_device *pdev)
+{
+	struct list_head *banks = devm_kzalloc(&pdev->dev, sizeof(*banks),
+					       GFP_KERNEL);
+
+	if (!banks)
+		return -ENOMEM;
+	INIT_LIST_HEAD(banks);
+	platform_set_drvdata(pdev, banks);
+
+	if (platform_get_device_id(pdev))
+		return bgpio_platform_probe(pdev);
+	else
+		return bgpio_of_probe(pdev);
 }
 
 static int __devexit bgpio_pdev_remove(struct platform_device *pdev)
 {
-	struct bgpio_chip *bgc = platform_get_drvdata(pdev);
+	bgpio_remove_all_banks(pdev);
 
-	return bgpio_remove(bgc);
+	return 0;
 }
 
 static const struct platform_device_id bgpio_id_table[] = {
@@ -523,6 +647,7 @@ MODULE_DEVICE_TABLE(platform, bgpio_id_table);
 static struct platform_driver bgpio_driver = {
 	.driver = {
 		.name = "basic-mmio-gpio",
+		.of_match_table = bgpio_of_id_table,
 	},
 	.id_table = bgpio_id_table,
 	.probe = bgpio_pdev_probe,
diff --git a/include/linux/basic_mmio_gpio.h b/include/linux/basic_mmio_gpio.h
index 98999cf..6df1766 100644
--- a/include/linux/basic_mmio_gpio.h
+++ b/include/linux/basic_mmio_gpio.h
@@ -56,6 +56,9 @@ struct bgpio_chip {
 
 	/* Shadowed direction registers to clear/set direction safely. */
 	unsigned long dir;
+
+	/* List to store multiple banks for a single device. */
+	struct list_head head;
 };
 
 static inline struct bgpio_chip *to_bgpio_chip(struct gpio_chip *gc)
@@ -64,14 +67,71 @@ static inline struct bgpio_chip *to_bgpio_chip(struct gpio_chip *gc)
 }
 
 int __devexit bgpio_remove(struct bgpio_chip *bgc);
-int __devinit bgpio_init(struct bgpio_chip *bgc,
-			 struct device *dev,
-			 unsigned long sz,
-			 void __iomem *dat,
-			 void __iomem *set,
-			 void __iomem *clr,
-			 void __iomem *dirout,
-			 void __iomem *dirin,
-			 bool big_endian);
+
+/**
+ * struct bgpio_info - generic gpio chip descriptor
+ *
+ * @dat: the data register, used to read the gpio values and set them if
+ *	@set == NULL.
+ * @set: the set register, used to set the value of a GPIO pin (write a 1 bit
+ *	to set high).  Writing a 0 bit will set the pin to low if @clr ==
+ *	NULL.
+ * @clr: the clear register, used to set the value of a pin to low.
+ * @dirout: the direction out register.  Writing a 1 bit here will set the
+ *	pin to an output.  Writing a 0 bit will set the gpio to be an input
+ *	pin.
+ * @dirin: the direction in register.  Writing a 1 bit here will set the
+ *	pin to an input.  Writing a 0 bit will set the gpio to be an output
+ *	pin.
+ * @sz: the width of the registers in bytes.
+ * @be: set to true to indicate the registers are big endian.
+ * @ngpio: the number of pins in the bank.
+ * @base: the base Linux GPIO number to use for the gpio_chip.
+ * @of_node: the device tree node associated with the bank.
+ *
+ * This structure defines the properties of a GPIO controller for use with
+ * bgpio_init_info().  This should be populated with the registers, sizes and
+ * other characteristics then passed to bgpio_init_info() to intitialise a
+ * bgpio_chip ready for registration.
+ */
+struct bgpio_info {
+	void __iomem *dat;
+	void __iomem *set;
+	void __iomem *clr;
+	void __iomem *dirout;
+	void __iomem *dirin;
+	unsigned long sz;
+	bool be;
+	int ngpio;
+	int base;
+	struct device_node *of_node;
+};
+
+int bgpio_init_info(struct bgpio_chip *bgc, struct device *dev,
+		    const struct bgpio_info *info);
+
+static inline int bgpio_init(struct bgpio_chip *bgc,
+			     struct device *dev,
+			     unsigned long sz,
+			     void __iomem *dat,
+			     void __iomem *set,
+			     void __iomem *clr,
+			     void __iomem *dirout,
+			     void __iomem *dirin,
+			     bool big_endian)
+{
+	struct bgpio_info info = {
+		.dat = dat,
+		.set = set,
+		.clr = clr,
+		.dirout = dirout,
+		.dirin = dirin,
+		.sz = sz,
+		.ngpio = sz * 8,
+		.base = -1,
+	};
+
+	return bgpio_init_info(bgc, dev, &info);
+}
 
 #endif /* __BASIC_MMIO_GPIO_H */
-- 
1.7.4.1



More information about the devicetree-discuss mailing list