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.lik...@secretlab.ca>
Cc: Anton Vorontsov <cbouatmai...@gmail.com>
Signed-off-by: Jamie Iles <ja...@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@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@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@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

_______________________________________________
devicetree-discuss mailing list
devicetree-discuss@lists.ozlabs.org
https://lists.ozlabs.org/listinfo/devicetree-discuss

Reply via email to