[PATCHv4] gpio-generic: add support for device tree probing

Jamie Iles jamie at jamieiles.com
Thu Aug 4 23:38:24 EST 2011


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

v4:
	- Add support for templates that check the DT has the correct
	  regoffset-* properties.
v3:
	- Remove extraneous of_node_{get,put}().
	- Rename basic-mmio-gpio,reg-io-width to reg-io-width.
	- Use vendor specific compatible values for now.
	- Only add child banks, and allocate banks as an array.
	- Remove bgpio_init_info() and populate bgpio_chip directly.
	- Rename properties to gpio-generic,* to match the driver name.
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>
---

Grant,

Here's what I've managed to come up with so far for the templating
mechanism mentioned before.  At the moment this makes sure that we have
all of the regoffset-* properties for the controller and we don't have
any extras that we weren't expecting.  This would also be a nice way to
override some of the gpio_chip callbacks for quirky controllers if we
needed to.

I've left the compatible property in the banks because (a) that's what
some of the powerpc ones do, and (b) that's where the gpio-controller
property is.  I'm happy to remove this if you think it's appropriate
though.

Jamie

 .../devicetree/bindings/gpio/gpio-generic.txt      |   85 +++++++
 drivers/gpio/gpio-generic.c                        |  261 +++++++++++++++++++-
 2 files changed, 333 insertions(+), 13 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/gpio/gpio-generic.txt

diff --git a/Documentation/devicetree/bindings/gpio/gpio-generic.txt b/Documentation/devicetree/bindings/gpio/gpio-generic.txt
new file mode 100644
index 0000000..91c9441
--- /dev/null
+++ b/Documentation/devicetree/bindings/gpio/gpio-generic.txt
@@ -0,0 +1,85 @@
+Generic 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 : Must be one of:
+  - "snps,dw-apb-gpio" : Synopsys DesignWare APB GPIO controller
+- reg : The register window for the GPIO device.  If the device has multiple
+  banks then this window should cover all of the banks.
+- gpio-generic,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:
+- gpio-generic,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.
+
+Generic MMIO GPIO controller bank
+
+Required properties:
+- compatible : Must be one of:
+  - "snps,dw-apb-gpio-bank" : Synopsys DesignWare APB GPIO controller 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).
+- gpio-generic,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 = "snps,dw-apb-gpio";
+	reg = <0x20000 0x1000>;
+	#address-cells = <1>;
+	#size-cells = <0>;
+	gpio-generic,reg-io-width = <4>;
+
+	banka: gpio-controller at 0 {
+		compatible = "snps,dw-apb-gpio";
+		gpio-controller;
+		#gpio-cells = <2>;
+		gpio-generic,nr-gpio = <8>;
+
+		regoffset-dat = <0x50>;
+		regoffset-set = <0x00>;
+		regoffset-dirout = <0x04>;
+	};
+
+	bankb: gpio-controller at 1 {
+		compatible = "snps,dw-apb-gpio";
+		gpio-controller;
+		#gpio-cells = <2>;
+		gpio-generic,nr-gpio = <16>;
+
+		regoffset-dat = <0x54>;
+		regoffset-set = <0x0c>;
+		regoffset-dirout = <0x10>;
+	};
+};
diff --git a/drivers/gpio/gpio-generic.c b/drivers/gpio/gpio-generic.c
index 231714d..b5650fd 100644
--- a/drivers/gpio/gpio-generic.c
+++ b/drivers/gpio/gpio-generic.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)
 {
@@ -444,7 +447,29 @@ static void __iomem *bgpio_map(struct platform_device *pdev,
 	return ret;
 }
 
-static int __devinit bgpio_pdev_probe(struct platform_device *pdev)
+struct bgpio_drvdata {
+	struct bgpio_chip	*banks;
+	unsigned int		nr_banks;
+};
+
+static struct bgpio_drvdata *bgpio_alloc_banks(struct device *dev,
+					       unsigned int nr_banks)
+{
+	struct bgpio_drvdata *banks;
+
+	banks = devm_kzalloc(dev, sizeof(*banks), GFP_KERNEL);
+	if (!banks)
+		return NULL;
+
+	banks->banks = devm_kzalloc(dev, sizeof(*banks->banks) * nr_banks,
+				    GFP_KERNEL);
+	if (!banks->banks)
+		return NULL;
+
+	return banks;
+}
+
+static int bgpio_platform_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
 	struct resource *r;
@@ -456,8 +481,12 @@ static int __devinit bgpio_pdev_probe(struct platform_device *pdev)
 	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_drvdata *banks = bgpio_alloc_banks(&pdev->dev, 1);
+
+	if (!banks)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, banks);
 
 	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dat");
 	if (!r)
@@ -487,30 +516,235 @@ static int __devinit bgpio_pdev_probe(struct platform_device *pdev)
 
 	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);
+	banks->nr_banks = 1;
+	err = bgpio_init(&banks->banks[0], dev, sz, dat, set, clr, dirout,
+			 dirin, be);
 	if (err)
 		return err;
 
 	if (pdata) {
-		bgc->gc.base = pdata->base;
+		banks->banks[0].gc.base = pdata->base;
 		if (pdata->ngpio > 0)
-			bgc->gc.ngpio = pdata->ngpio;
+			banks->banks[0].gc.ngpio = pdata->ngpio;
+	}
+
+	return gpiochip_add(&banks->banks[0].gc);
+}
+
+static void bgpio_remove_all_banks(struct platform_device *pdev)
+{
+	struct bgpio_drvdata *banks = platform_get_drvdata(pdev);
+	unsigned int m;
+
+	for (m = 0; m < banks->nr_banks; ++m)
+		bgpio_remove(&banks->banks[m]);
+}
+
+#ifdef CONFIG_OF
+enum gpio_generic_of_reg_type {
+	GPIO_GENERIC_REG_DAT,
+	GPIO_GENERIC_REG_SET,
+	GPIO_GENERIC_REG_CLR,
+	GPIO_GENERIC_REG_DIROUT,
+	GPIO_GENERIC_REG_DIRIN,
+	GPIO_GENERIC_NUM_REG_TYPES
+};
+
+static const char *
+bgpio_of_reg_prop_names[GPIO_GENERIC_NUM_REG_TYPES] = {
+	"regoffset-dat",
+	"regoffset-set",
+	"regoffset-clr",
+	"regoffset-dirout",
+	"regoffset-dirin",
+};
+
+struct gpio_generic_of_template {
+	unsigned long reg_mask;		/*
+					 * Bitmask of the registers required
+					 * for the given compatible string.
+					 */
+};
+
+#define TEMPLATE_REG(_name) \
+	(1 << (GPIO_GENERIC_REG_ ## _name))
+
+static inline bool template_has_reg(const struct gpio_generic_of_template *t,
+				    int type)
+{
+	return t->reg_mask & (1 << type);
+}
+
+static void __iomem *
+bgpio_of_get_reg(struct device *dev, struct device_node *np,
+		 void __iomem *base, int type,
+		 const struct gpio_generic_of_template *template)
+{
+	u32 offs;
+	const char *prop;
+	int err;
+
+	if (type >= GPIO_GENERIC_NUM_REG_TYPES)
+		return ERR_PTR(-EINVAL);
+	prop = bgpio_of_reg_prop_names[type];
+
+	err = of_property_read_u32(np, prop, &offs);
+	if (err) {
+		if (!template_has_reg(template, type))
+			return NULL;
+		dev_err(dev, "missing %s property\n", prop);
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (!template_has_reg(template, type)) {
+		dev_err(dev, "%s property invalid for this controller\n",
+			prop);
+		return ERR_PTR(-EINVAL);
+	}
+
+	return base + offs;
+}
+
+static int
+bgpio_of_add_one_bank(struct platform_device *pdev, struct bgpio_chip *bgc,
+		      struct device_node *np, void __iomem *iobase,
+		      size_t reg_width_bytes, bool be,
+		      const struct gpio_generic_of_template *template)
+{
+	void __iomem *dat = NULL;
+	void __iomem *set = NULL;
+	void __iomem *clr = NULL;
+	void __iomem *dirout = NULL;
+	void __iomem *dirin = NULL;
+	u32 val;
+	int err;
+
+	dat = bgpio_of_get_reg(&pdev->dev, np, iobase, GPIO_GENERIC_REG_DAT,
+			       template);
+	if (IS_ERR(dat))
+		return PTR_ERR(dat);
+
+	set = bgpio_of_get_reg(&pdev->dev, np, iobase, GPIO_GENERIC_REG_SET,
+			       template);
+	if (IS_ERR(set))
+		return PTR_ERR(set);
+
+	clr = bgpio_of_get_reg(&pdev->dev, np, iobase, GPIO_GENERIC_REG_CLR,
+			       template);
+	if (IS_ERR(clr))
+		return PTR_ERR(clr);
+
+	dirout = bgpio_of_get_reg(&pdev->dev, np, iobase,
+				  GPIO_GENERIC_REG_DIROUT, template);
+	if (IS_ERR(dirout))
+		return PTR_ERR(dirout);
+
+	dirin = bgpio_of_get_reg(&pdev->dev, np, iobase, GPIO_GENERIC_REG_DIRIN,
+			       template);
+	if (IS_ERR(dirin))
+		return PTR_ERR(dirin);
+
+	if (of_property_read_u32(np, "gpio-generic,nr-gpio", &val)) {
+		dev_err(&pdev->dev, "missing gpio-generic,nr-gpio property\n");
+		return -EINVAL;
 	}
 
-	platform_set_drvdata(pdev, bgc);
+	err = bgpio_init(bgc, &pdev->dev, reg_width_bytes, dat, set, clr,
+			 dirout, dirin, be);
+	if (err)
+		return err;
+
+	bgc->gc.ngpio = val;
+	bgc->gc.of_node = np;
 
 	return gpiochip_add(&bgc->gc);
 }
 
+static struct gpio_generic_of_template snps_dw_apb_template = {
+	.reg_mask = TEMPLATE_REG(DAT) |
+		    TEMPLATE_REG(SET) |
+		    TEMPLATE_REG(DIROUT),
+};
+
+static const struct of_device_id bgpio_of_id_table[] = {
+	{ .compatible = "snps,dw-apb-gpio", .data = &snps_dw_apb_template },
+	{},
+};
+MODULE_DEVICE_TABLE(of, bgpio_of_id_table);
+
+static int bgpio_of_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	void __iomem *iobase;
+	int err = 0;
+	u32 val;
+	size_t reg_width_bytes;
+	bool be;
+	int nr_banks = 0;
+	struct bgpio_drvdata *banks;
+	const struct of_device_id *match;
+
+	match = of_match_node(bgpio_of_id_table, np);
+
+	iobase = of_iomap(np, 0);
+	if (!iobase)
+		return -EIO;
+
+	if (of_property_read_u32(np, "reg-io-width", &val))
+		return -EINVAL;
+	reg_width_bytes = val;
+
+	be = of_get_property(np, "gpio-generic,big-endian", NULL) ?
+		true : false;
+
+	for_each_child_of_node(pdev->dev.of_node, np)
+		++nr_banks;
+
+	banks = bgpio_alloc_banks(&pdev->dev, nr_banks);
+	if (!banks)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, banks);
+	banks->nr_banks = 0;
+
+	for_each_child_of_node(pdev->dev.of_node, np) {
+		err = bgpio_of_add_one_bank(pdev,
+					    &banks->banks[banks->nr_banks],
+					    np, iobase, reg_width_bytes, be,
+					    match->data);
+		if (err)
+			goto out_remove;
+		++banks->nr_banks;
+	}
+
+	return 0;
+
+out_remove:
+	bgpio_remove_all_banks(pdev);
+
+	return err;
+}
+#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)
+{
+	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 +757,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,
-- 
1.7.4.1



More information about the devicetree-discuss mailing list