dm: sunxi: Modify the GPIO driver to support driver model

This adds driver model support to the sunxi GPIO driver, using the device
tree to trigger binding of the driver. The driver will still operate
without driver model too.

Signed-off-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
diff --git a/drivers/gpio/sunxi_gpio.c b/drivers/gpio/sunxi_gpio.c
index 0c50a8f..44135e5 100644
--- a/drivers/gpio/sunxi_gpio.c
+++ b/drivers/gpio/sunxi_gpio.c
@@ -11,9 +11,25 @@
  */
 
 #include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <fdtdec.h>
+#include <malloc.h>
 #include <asm/io.h>
 #include <asm/gpio.h>
+#include <dm/device-internal.h>
 
+DECLARE_GLOBAL_DATA_PTR;
+
+#define SUNXI_GPIOS_PER_BANK	SUNXI_GPIO_A_NR
+
+struct sunxi_gpio_platdata {
+	struct sunxi_gpio *regs;
+	const char *bank_name;	/* Name of bank, e.g. "B" */
+	int gpio_count;
+};
+
+#ifndef CONFIG_DM_GPIO
 static int sunxi_gpio_output(u32 pin, u32 val)
 {
 	u32 dat;
@@ -100,3 +116,157 @@
 		return -1;
 	return group * 32 + pin;
 }
+#endif
+
+#ifdef CONFIG_DM_GPIO
+static int sunxi_gpio_direction_input(struct udevice *dev, unsigned offset)
+{
+	struct sunxi_gpio_platdata *plat = dev_get_platdata(dev);
+
+	sunxi_gpio_set_cfgbank(plat->regs, offset, SUNXI_GPIO_INPUT);
+
+	return 0;
+}
+
+static int sunxi_gpio_direction_output(struct udevice *dev, unsigned offset,
+				       int value)
+{
+	struct sunxi_gpio_platdata *plat = dev_get_platdata(dev);
+	u32 num = GPIO_NUM(offset);
+
+	sunxi_gpio_set_cfgbank(plat->regs, offset, SUNXI_GPIO_OUTPUT);
+	clrsetbits_le32(&plat->regs->dat, 1 << num, value ? (1 << num) : 0);
+
+	return 0;
+}
+
+static int sunxi_gpio_get_value(struct udevice *dev, unsigned offset)
+{
+	struct sunxi_gpio_platdata *plat = dev_get_platdata(dev);
+	u32 num = GPIO_NUM(offset);
+	unsigned dat;
+
+	dat = readl(&plat->regs->dat);
+	dat >>= num;
+
+	return dat & 0x1;
+}
+
+static int sunxi_gpio_set_value(struct udevice *dev, unsigned offset,
+				int value)
+{
+	struct sunxi_gpio_platdata *plat = dev_get_platdata(dev);
+	u32 num = GPIO_NUM(offset);
+
+	clrsetbits_le32(&plat->regs->dat, 1 << num, value ? (1 << num) : 0);
+	return 0;
+}
+
+static int sunxi_gpio_get_function(struct udevice *dev, unsigned offset)
+{
+	struct sunxi_gpio_platdata *plat = dev_get_platdata(dev);
+	int func;
+
+	func = sunxi_gpio_get_cfgbank(plat->regs, offset);
+	if (func == SUNXI_GPIO_OUTPUT)
+		return GPIOF_OUTPUT;
+	else if (func == SUNXI_GPIO_INPUT)
+		return GPIOF_INPUT;
+	else
+		return GPIOF_FUNC;
+}
+
+static const struct dm_gpio_ops gpio_sunxi_ops = {
+	.direction_input	= sunxi_gpio_direction_input,
+	.direction_output	= sunxi_gpio_direction_output,
+	.get_value		= sunxi_gpio_get_value,
+	.set_value		= sunxi_gpio_set_value,
+	.get_function		= sunxi_gpio_get_function,
+};
+
+/**
+ * Returns the name of a GPIO bank
+ *
+ * GPIO banks are named A, B, C, ...
+ *
+ * @bank:	Bank number (0, 1..n-1)
+ * @return allocated string containing the name
+ */
+static char *gpio_bank_name(int bank)
+{
+	char *name;
+
+	name = malloc(2);
+	if (name) {
+		name[0] = 'A' + bank;
+		name[1] = '\0';
+	}
+
+	return name;
+}
+
+static int gpio_sunxi_probe(struct udevice *dev)
+{
+	struct sunxi_gpio_platdata *plat = dev_get_platdata(dev);
+	struct gpio_dev_priv *uc_priv = dev->uclass_priv;
+
+	/* Tell the uclass how many GPIOs we have */
+	if (plat) {
+		uc_priv->gpio_count = plat->gpio_count;
+		uc_priv->bank_name = plat->bank_name;
+	}
+
+	return 0;
+}
+/**
+ * We have a top-level GPIO device with no actual GPIOs. It has a child
+ * device for each Sunxi bank.
+ */
+static int gpio_sunxi_bind(struct udevice *parent)
+{
+	struct sunxi_gpio_platdata *plat = parent->platdata;
+	struct sunxi_gpio_reg *ctlr;
+	int bank;
+	int ret;
+
+	/* If this is a child device, there is nothing to do here */
+	if (plat)
+		return 0;
+
+	ctlr = (struct sunxi_gpio_reg *)fdtdec_get_addr(gd->fdt_blob,
+						   parent->of_offset, "reg");
+	for (bank = 0; bank < SUNXI_GPIO_BANKS; bank++) {
+		struct sunxi_gpio_platdata *plat;
+		struct udevice *dev;
+
+		plat = calloc(1, sizeof(*plat));
+		if (!plat)
+			return -ENOMEM;
+		plat->regs = &ctlr->gpio_bank[bank];
+		plat->bank_name = gpio_bank_name(bank);
+		plat->gpio_count = SUNXI_GPIOS_PER_BANK;
+
+		ret = device_bind(parent, parent->driver,
+					plat->bank_name, plat, -1, &dev);
+		if (ret)
+			return ret;
+		dev->of_offset = parent->of_offset;
+	}
+
+	return 0;
+}
+
+static const struct udevice_id sunxi_gpio_ids[] = {
+	{ .compatible = "allwinner,sun7i-a20-pinctrl" },
+	{ }
+};
+
+U_BOOT_DRIVER(gpio_sunxi) = {
+	.name	= "gpio_sunxi",
+	.id	= UCLASS_GPIO,
+	.ops	= &gpio_sunxi_ops,
+	.of_match = sunxi_gpio_ids,
+	.bind	= gpio_sunxi_bind,
+	.probe	= gpio_sunxi_probe,
+};
+#endif
diff --git a/include/configs/sunxi-common.h b/include/configs/sunxi-common.h
index 4d88991..316e7d9 100644
--- a/include/configs/sunxi-common.h
+++ b/include/configs/sunxi-common.h
@@ -29,6 +29,7 @@
 
 #if !defined(CONFIG_SPL_BUILD) && defined(CONFIG_DM)
 # define CONFIG_CMD_DM
+# define CONFIG_DM_GPIO
 #endif
 
 /*