Re: [PATCH v2 2/4] gpio: add viperboard gpio driver

From: Lars Poeschel
Date: Tue Oct 23 2012 - 11:24:28 EST


On Tuesday 16 October 2012 at 19:11:10, Linus Walleij wrote:
> On Tue, Oct 16, 2012 at 3:38 PM, Lars Poeschel <poeschel@xxxxxxxxxxx> wrote:
> > I had a look at regmap. This is interesting. But there is no regmap_bus
> > implementation for usb. Are you pointing me in this direction ? ;-)
>
> I was more thinking about whether it would be useful to use for this
> MFD hub. So that for this MFD and it's children, you would use
> regmap to:
>
> 1) Cache registers
> 2) Marshall the register read/writes across USB
>
> I don't think it would be applicable for USB, as reading & writing
> registers across USB is a pretty odd usecase. (IIRC using control
> transfers
> right?)

Yes, right.

> > And it is not guaranteed, that this "address" and "value"
> > are at the same position for other usb adapters. And this is getting even
> > worse, if I think of reading values out of the viperboard...
>
> I was mainly thinking about the Viperboard MFD complex.

Ok, I tried to implement it (at least for the gpioa part of the driver).
It does not work. I got stuck. There are three problems I could not solve:

To be able to use the caching, I have to implement a volatile_reg function. I
have to do this, because caching can only happen, when the gpio pin is an
output. If it is an input, I want to read the pin - caching has to be turned
off. So in my vprbrd_volatile_reg function I have to check if a pin is set to
output and return false or true. I can not query a regmap register inside that
function if a pin is an output or not, this would deadlock, because there is
a mutex lock inside regmap, so I have to cache this outside of regmap.
This is a bit strange, to use the cache, setup another own cache. (1st
problem)
I chose my per device structure struct vprbrd_gpio to hold this cache, but I
can not reach it. You see in the patch, I try to container_of up to my
structure. This does not work, because struct regmap is not public available.
(2nd problem)
Setting the direction to output is an atomic set direction and set value.
The register number is different from setting the value only. So after a
successful call, I want to update the cache of output value using
regcache_write. This is also not publicy available api. The only thing I could
do is invalidate the whole cache using regcache_mark_dirty. (3rd problem)

I attach my current working state here. This is NOT WORKING and only as
reference.

Regards,
Lars
---
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 8382dc8..48be4ba 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -636,4 +636,17 @@ config GPIO_MSIC
Enable support for GPIO on intel MSIC controllers found in
intel MID devices

+comment "USB GPIO expanders:"
+
+config GPIO_VIPERBOARD
+ tristate "Viperboard GPIO a & b support"
+ depends on MFD_VIPERBOARD && USB
+ help
+ Say yes here to access the GPIO signals of Nano River
+ Technologies Viperboard. There are two GPIO chips on the
+ board: gpioa and gpiob.
+ See viperboard API specification and Nano
+ River Tech's viperboard.h for detailed meaning
+ of the module parameters.
+
endif
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 0ffaa84..71cc896 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_GPIO_TPS65910) += gpio-tps65910.o
obj-$(CONFIG_GPIO_TPS65912) += gpio-tps65912.o
obj-$(CONFIG_GPIO_TWL4030) += gpio-twl4030.o
obj-$(CONFIG_GPIO_UCB1400) += gpio-ucb1400.o
+obj-$(CONFIG_GPIO_VIPERBOARD) += gpio-viperboard.o
obj-$(CONFIG_GPIO_VR41XX) += gpio-vr41xx.o
obj-$(CONFIG_GPIO_VT8500) += gpio-vt8500.o
obj-$(CONFIG_GPIO_VX855) += gpio-vx855.o
diff --git a/drivers/gpio/gpio-viperboard.c b/drivers/gpio/gpio-viperboard.c
new file mode 100644
index 0000000..2dc6c9f
--- /dev/null
+++ b/drivers/gpio/gpio-viperboard.c
@@ -0,0 +1,618 @@
+/*
+ * Nano River Technologies viperboard GPIO lib driver
+ *
+ * (C) 2012 by Lemonage GmbH
+ * Author: Lars Poeschel <poeschel@xxxxxxxxxxx>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <linux/usb.h>
+#include <linux/gpio.h>
+
+#include <linux/mfd/viperboard.h>
+
+#define VPRBRD_GPIOA_CLK_1MHZ 0
+#define VPRBRD_GPIOA_CLK_100KHZ 1
+#define VPRBRD_GPIOA_CLK_10KHZ 2
+#define VPRBRD_GPIOA_CLK_1KHZ 3
+#define VPRBRD_GPIOA_CLK_100HZ 4
+#define VPRBRD_GPIOA_CLK_10HZ 5
+
+#define VPRBRD_GPIOA_FREQ_DEFAULT 1000
+
+#define VPRBRD_GPIOA_CMD_CONT 0x00
+#define VPRBRD_GPIOA_CMD_PULSE 0x01
+#define VPRBRD_GPIOA_CMD_PWM 0x02
+#define VPRBRD_GPIOA_CMD_SETOUT 0x03
+#define VPRBRD_GPIOA_CMD_SETIN 0x04
+#define VPRBRD_GPIOA_CMD_SETINT 0x05
+#define VPRBRD_GPIOA_CMD_GETIN 0x06
+
+#define VPRBRD_GPIOB_CMD_SETDIR 0x00
+#define VPRBRD_GPIOB_CMD_SETVAL 0x01
+
+#define VPRBRD_GPIOA_NUM_PINS 16
+#define VPRBRD_GPIOA_REG_VAL_BASE 0
+#define VPRBRD_GPIOA_REG_VAL_MAX (1 * VPRBRD_GPIOA_NUM_PINS - 1)
+#define VPRBRD_GPIOA_REG_DIR_BASE (VPRBRD_GPIOA_REG_VAL_MAX + 1)
+#define VPRBRD_GPIOA_REG_DIR_MAX (2 * VPRBRD_GPIOA_NUM_PINS - 1)
+#define VPRBRD_GPIOA_NUM_REGS (VPRBRD_GPIOA_REG_DIR_MAX + 1)
+
+struct vprbrd_gpioa_msg {
+ u8 cmd;
+ u8 clk;
+ u8 offset;
+ u8 t1;
+ u8 t2;
+ u8 invert;
+ u8 pwmlevel;
+ u8 outval;
+ u8 risefall;
+ u8 answer;
+ u8 __fill;
+} __packed;
+
+struct vprbrd_gpiob_msg {
+ u8 cmd;
+ u16 val;
+ u16 mask;
+} __packed;
+
+struct vprbrd_gpio {
+ struct gpio_chip gpioa; /* gpio a related things */
+ u32 gpioa_out;
+ u32 gpioa_val;
+ struct gpio_chip gpiob; /* gpio b related things */
+ u32 gpiob_out;
+ u32 gpiob_val;
+ struct vprbrd *vb;
+ struct regmap *regmap;
+};
+
+/* gpioa sampling clock module parameter */
+static unsigned char gpioa_clk;
+static unsigned int gpioa_freq = VPRBRD_GPIOA_FREQ_DEFAULT;
+module_param(gpioa_freq, uint, 0);
+MODULE_PARM_DESC(gpioa_freq, "gpio a sampling freq in Hz (default is 1000Hz)"
+ "valid values: 10, 100, 1000, 10000, 100000, 1000000");
+
+/* ----- begin of gipo a chip -------------------------------------------- */
+
+static int regmap_vb_gpio_write(void *context, const void *data, size_t count)
+{
+ struct vprbrd *vb = (struct vprbrd *)context;
+ struct vprbrd_gpioa_msg *gamsg = (struct vprbrd_gpioa_msg *)vb->buf;
+ int ret;
+ const u8 reg = ((const u8 *)data)[0];
+ const u8 val = ((const u8 *)data)[1];
+
+ dev_dbg(&vb->pdev.dev, "%s count:%i reg:%i, val:%i\n", __FUNCTION__, count, reg, val);
+
+ if (count != 2)
+ return -ENOTSUPP;
+
+ mutex_lock(&vb->lock);
+
+ gamsg->clk = 0x00;
+ gamsg->t1 = 0x00;
+ gamsg->t2 = 0x00;
+ gamsg->invert = 0x00;
+ gamsg->pwmlevel = 0x00;
+ gamsg->outval = val;
+ gamsg->risefall = 0x00;
+ gamsg->answer = 0x00;
+ gamsg->__fill = 0x00;
+
+ if (reg >= VPRBRD_GPIOA_REG_VAL_BASE &&
+ reg <= VPRBRD_GPIOA_REG_VAL_MAX) {
+ gamsg->cmd = VPRBRD_GPIOA_CMD_SETOUT;
+ gamsg->offset = reg;
+ gamsg->outval = val;
+ } else if (reg >= VPRBRD_GPIOA_REG_DIR_BASE &&
+ reg <= VPRBRD_GPIOA_REG_DIR_MAX) {
+ gamsg->offset = reg - VPRBRD_GPIOA_REG_DIR_BASE;
+ if (val > 0) {
+ gamsg->cmd = VPRBRD_GPIOA_CMD_SETIN;
+ gamsg->clk = gpioa_clk;
+ } else {
+ gamsg->cmd = VPRBRD_GPIOA_CMD_SETOUT;
+ }
+ }
+ ret = usb_control_msg(vb->usb_dev, usb_sndctrlpipe(vb->usb_dev, 0),
+ VPRBRD_USB_REQUEST_GPIOA, VPRBRD_USB_TYPE_OUT, 0x0000,
+ 0x0000, gamsg, sizeof(struct vprbrd_gpioa_msg),
+ VPRBRD_USB_TIMEOUT_MS);
+
+ mutex_unlock(&vb->lock);
+
+ if (ret != sizeof(struct vprbrd_gpioa_msg))
+ return -EREMOTEIO;
+
+ return 0;
+}
+
+static int regmap_vb_gpio_read(void *context, const void *reg_buf,
+ size_t reg_size, void *val_buf, size_t val_size)
+{
+ struct vprbrd *vb = (struct vprbrd *)context;
+ struct vprbrd_gpioa_msg *gamsg = (struct vprbrd_gpioa_msg *)vb->buf;
+ int ret, error = 0;
+ const u8 *reg8 = reg_buf;
+
+ dev_dbg(&vb->pdev.dev, "%s reg_size:%i val_size:%i\n", __FUNCTION__, reg_size, val_size);
+
+ if (val_size != 1 || reg_size != 1)
+ return -ENOTSUPP;
+
+ mutex_lock(&vb->lock);
+
+ gamsg->cmd = VPRBRD_GPIOA_CMD_GETIN;
+ gamsg->clk = 0x00;
+ gamsg->offset = reg8[0];
+ gamsg->t1 = 0x00;
+ gamsg->t2 = 0x00;
+ gamsg->invert = 0x00;
+ gamsg->pwmlevel = 0x00;
+ gamsg->outval = 0x00;
+ gamsg->risefall = 0x00;
+ gamsg->answer = 0x00;
+ gamsg->__fill = 0x00;
+
+ ret = usb_control_msg(vb->usb_dev, usb_sndctrlpipe(vb->usb_dev, 0),
+ VPRBRD_USB_REQUEST_GPIOA, VPRBRD_USB_TYPE_OUT, 0x0000,
+ 0x0000, gamsg, sizeof(struct vprbrd_gpioa_msg),
+ VPRBRD_USB_TIMEOUT_MS);
+ if (ret != sizeof(struct vprbrd_gpioa_msg))
+ error = -EREMOTEIO;
+
+ ret = usb_control_msg(vb->usb_dev, usb_rcvctrlpipe(vb->usb_dev, 0),
+ VPRBRD_USB_REQUEST_GPIOA, VPRBRD_USB_TYPE_IN, 0x0000,
+ 0x0000, gamsg, sizeof(struct vprbrd_gpioa_msg),
+ VPRBRD_USB_TIMEOUT_MS);
+ *(u8 *)val_buf = gamsg->answer & 0x01;
+
+ dev_dbg(&vb->pdev.dev, "%s gamsg->answer:%i val:%i\n", __FUNCTION__, gamsg->answer, *(u8 *)val_buf);
+
+ mutex_unlock(&vb->lock);
+
+ if (ret != sizeof(struct vprbrd_gpioa_msg))
+ error = -EREMOTEIO;
+
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static const struct regmap_bus regmap_vb_gpio = {
+ .write = regmap_vb_gpio_write,
+ .read = regmap_vb_gpio_read,
+};
+
+static bool vprbrd_volatile_reg(struct device *device, unsigned int reg)
+{
+ struct regmap *regmap = container_of(device, struct regmap, dev);
+ struct vprbrd_gpio *gpio =
+ container_of(regmap, struct vprbrd_gpio, regmap);
+
+ dev_dbg(device, "%s reg:%i\n", __FUNCTION__, reg);
+
+ /* if the pin is an output, we are not volatile and can cache */
+ if (gpio->gpioa_out & (1 << reg))
+ return false;
+
+ return true;
+}
+
+static struct reg_default vprbrd_regdefaults[] = {
+ {0x00, 0x00}, /* 0x00 - 0x0f pin values */
+ {0x01, 0x00},
+ {0x02, 0x00},
+ {0x03, 0x00},
+ {0x04, 0x00},
+ {0x05, 0x00},
+ {0x06, 0x00},
+ {0x07, 0x00},
+ {0x08, 0x00},
+ {0x09, 0x00},
+ {0x0a, 0x00},
+ {0x0b, 0x00},
+ {0x0c, 0x00},
+ {0x0d, 0x00},
+ {0x0e, 0x00},
+ {0x0f, 0x00},
+ {0x10, 0x00}, /* 0x10 - 0x1f pin direction */
+ {0x11, 0x00},
+ {0x12, 0x00},
+ {0x13, 0x00},
+ {0x14, 0x00},
+ {0x15, 0x00},
+ {0x16, 0x00},
+ {0x17, 0x00},
+ {0x18, 0x00},
+ {0x19, 0x00},
+ {0x1a, 0x00},
+ {0x1b, 0x00},
+ {0x1c, 0x00},
+ {0x1d, 0x00},
+ {0x1e, 0x00},
+ {0x1f, 0x00},
+};
+
+static const struct regmap_config vprbrd_regmap_config = {
+ .name = "gpio_regmap",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_reg = vprbrd_volatile_reg,
+ .max_register = VPRBRD_GPIOA_NUM_REGS,
+ .reg_defaults = vprbrd_regdefaults,
+ .num_reg_defaults = ARRAY_SIZE(vprbrd_regdefaults),
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int vprbrd_gpioa_get(struct gpio_chip *chip,
+ unsigned offset)
+{
+ int ret;
+ struct vprbrd_gpio *gpio =
+ container_of(chip, struct vprbrd_gpio, gpioa);
+ unsigned int val;
+
+ ret = regmap_read(gpio->regmap, offset, &val);
+
+ dev_dbg(chip->dev, "%s offset:%i val:%x ret:%i\n", __FUNCTION__, offset, val, ret);
+
+ if (ret < 0)
+ return ret;
+ else
+ return val;
+}
+
+static void vprbrd_gpioa_set(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ int ret;
+ struct vprbrd_gpio *gpio =
+ container_of(chip, struct vprbrd_gpio, gpioa);
+
+ dev_dbg(chip->dev, "%s offset:%i, value:%i\n", __FUNCTION__, offset, value);
+
+ ret = regmap_write(gpio->regmap, offset, value);
+ if (ret < 0)
+ dev_err(chip->dev, "usb error setting pin value\n");
+}
+
+static int vprbrd_gpioa_direction_input(struct gpio_chip *chip,
+ unsigned offset)
+{
+ int ret;
+ struct vprbrd_gpio *gpio =
+ container_of(chip, struct vprbrd_gpio, gpioa);
+
+ dev_dbg(chip->dev, "%s offset:%i\n", __FUNCTION__, offset);
+
+ ret = regmap_write(gpio->regmap,
+ offset + VPRBRD_GPIOA_REG_DIR_BASE, 0);
+
+ if (ret < 0) {
+ dev_err(chip->dev, "usb error setting pin to output\n");
+ return ret;
+ }
+
+ gpio->gpioa_out &= ~(1 << offset);
+
+ return 0;
+}
+
+static int vprbrd_gpioa_direction_output(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ int ret;
+ struct vprbrd_gpio *gpio =
+ container_of(chip, struct vprbrd_gpio, gpioa);
+
+ dev_dbg(chip->dev, "%s offset:%i, value:%i\n", __FUNCTION__, offset, value);
+
+ ret = regmap_write(gpio->regmap,
+ offset + VPRBRD_GPIOA_REG_DIR_BASE, value);
+
+ if (ret < 0) {
+ dev_err(chip->dev, "usb error setting pin to output\n");
+ return ret;
+ }
+
+ gpio->gpioa_out |= (1 << offset);
+ ret = regcache_write(gpio->regmap, offset, value);
+
+ return 0;
+}
+
+/* ----- end of gpio a chip ---------------------------------------------- */
+
+/* ----- begin of gipo b chip -------------------------------------------- */
+
+static int vprbrd_gpiob_setdir(struct vprbrd *vb, unsigned offset,
+ unsigned dir)
+{
+ struct vprbrd_gpiob_msg *gbmsg = (struct vprbrd_gpiob_msg *)vb->buf;
+ int ret;
+
+ gbmsg->cmd = VPRBRD_GPIOB_CMD_SETDIR;
+ gbmsg->val = cpu_to_be16(dir << offset);
+ gbmsg->mask = cpu_to_be16(0x0001 << offset);
+
+ ret = usb_control_msg(vb->usb_dev, usb_sndctrlpipe(vb->usb_dev, 0),
+ VPRBRD_USB_REQUEST_GPIOB, VPRBRD_USB_TYPE_OUT, 0x0000,
+ 0x0000, gbmsg, sizeof(struct vprbrd_gpiob_msg),
+ VPRBRD_USB_TIMEOUT_MS);
+
+ if (ret != sizeof(struct vprbrd_gpiob_msg))
+ return -EREMOTEIO;
+
+ return 0;
+}
+
+static int vprbrd_gpiob_get(struct gpio_chip *chip,
+ unsigned offset)
+{
+ int ret;
+ u16 val;
+ struct vprbrd_gpio *gpio =
+ container_of(chip, struct vprbrd_gpio, gpiob);
+ struct vprbrd *vb = gpio->vb;
+ struct vprbrd_gpiob_msg *gbmsg = (struct vprbrd_gpiob_msg *)vb->buf;
+
+ /* if io is set to output, just return the saved value */
+ if (gpio->gpiob_out & (1 << offset))
+ return gpio->gpiob_val & (1 << offset);
+
+ mutex_lock(&vb->lock);
+
+ ret = usb_control_msg(vb->usb_dev, usb_rcvctrlpipe(vb->usb_dev, 0),
+ VPRBRD_USB_REQUEST_GPIOB, VPRBRD_USB_TYPE_IN, 0x0000,
+ 0x0000, gbmsg, sizeof(struct vprbrd_gpiob_msg),
+ VPRBRD_USB_TIMEOUT_MS);
+ val = gbmsg->val;
+
+ mutex_unlock(&vb->lock);
+
+ if (ret != sizeof(struct vprbrd_gpiob_msg))
+ return ret;
+
+ /* cache the read values */
+ gpio->gpiob_val = be16_to_cpu(val);
+
+ return (gpio->gpiob_val >> offset) & 0x1;
+}
+
+static void vprbrd_gpiob_set(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ int ret;
+ struct vprbrd_gpio *gpio =
+ container_of(chip, struct vprbrd_gpio, gpiob);
+ struct vprbrd *vb = gpio->vb;
+ struct vprbrd_gpiob_msg *gbmsg = (struct vprbrd_gpiob_msg *)vb->buf;
+
+ if (gpio->gpiob_out & (1 << offset)) {
+ if (value)
+ gpio->gpiob_val |= (1 << offset);
+ else
+ gpio->gpiob_val &= ~(1 << offset);
+
+ mutex_lock(&vb->lock);
+
+ gbmsg->cmd = VPRBRD_GPIOB_CMD_SETVAL;
+ gbmsg->val = cpu_to_be16(value << offset);
+ gbmsg->mask = cpu_to_be16(0x0001 << offset);
+
+ ret = usb_control_msg(vb->usb_dev,
+ usb_sndctrlpipe(vb->usb_dev, 0),
+ VPRBRD_USB_REQUEST_GPIOB, VPRBRD_USB_TYPE_OUT,
+ 0x0000, 0x0000, gbmsg,
+ sizeof(struct vprbrd_gpiob_msg), VPRBRD_USB_TIMEOUT_MS);
+
+ mutex_unlock(&vb->lock);
+
+ if (ret != sizeof(struct vprbrd_gpiob_msg))
+ dev_err(chip->dev, "usb error setting pin value\n");
+ }
+}
+
+static int vprbrd_gpiob_direction_input(struct gpio_chip *chip,
+ unsigned offset)
+{
+ int ret;
+ struct vprbrd_gpio *gpio =
+ container_of(chip, struct vprbrd_gpio, gpiob);
+ struct vprbrd *vb = gpio->vb;
+
+ gpio->gpiob_out &= ~(1 << offset);
+
+ mutex_lock(&vb->lock);
+
+ ret = vprbrd_gpiob_setdir(vb, offset, 0);
+
+ mutex_unlock(&vb->lock);
+
+ if (ret)
+ dev_err(chip->dev, "usb error setting pin to input\n");
+
+ return ret;
+}
+
+static int vprbrd_gpiob_direction_output(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ int ret;
+ struct vprbrd_gpio *gpio =
+ container_of(chip, struct vprbrd_gpio, gpiob);
+ struct vprbrd *vb = gpio->vb;
+
+ gpio->gpiob_out |= (1 << offset);
+ if (value)
+ gpio->gpiob_val |= (1 << offset);
+ else
+ gpio->gpiob_val &= ~(1 << offset);
+
+ mutex_lock(&vb->lock);
+
+ ret = vprbrd_gpiob_setdir(vb, offset, 1);
+ if (ret)
+ dev_err(chip->dev, "usb error setting pin to output\n");
+
+ mutex_unlock(&vb->lock);
+
+ vprbrd_gpiob_set(chip, offset, value);
+
+ return ret;
+}
+
+/* ----- end of gpio b chip ---------------------------------------------- */
+
+static int __devinit vprbrd_gpio_probe(struct platform_device *pdev)
+{
+ struct vprbrd *vb = dev_get_drvdata(pdev->dev.parent);
+ struct vprbrd_gpio *vb_gpio;
+ int ret, ret_err;
+
+ vb_gpio = kzalloc(sizeof(*vb_gpio), GFP_KERNEL);
+ if (vb_gpio == NULL)
+ return -ENOMEM;
+
+ vb_gpio->vb = vb;
+ /* registering gpio a */
+ vb_gpio->gpioa.label = "viperboard gpio a";
+ vb_gpio->gpioa.dev = &pdev->dev;
+ vb_gpio->gpioa.owner = THIS_MODULE;
+ vb_gpio->gpioa.base = -1;
+ vb_gpio->gpioa.ngpio = 16;
+ vb_gpio->gpioa.can_sleep = 1;
+ vb_gpio->gpioa.set = vprbrd_gpioa_set;
+ vb_gpio->gpioa.get = vprbrd_gpioa_get;
+ vb_gpio->gpioa.direction_input = vprbrd_gpioa_direction_input;
+ vb_gpio->gpioa.direction_output = vprbrd_gpioa_direction_output;
+ ret = gpiochip_add(&vb_gpio->gpioa);
+ if (ret < 0) {
+ dev_err(vb_gpio->gpioa.dev, "could not add gpio a");
+ goto err_gpioa;
+ }
+
+ /* registering gpio b */
+ vb_gpio->gpiob.label = "viperboard gpio b";
+ vb_gpio->gpiob.dev = &pdev->dev;
+ vb_gpio->gpiob.owner = THIS_MODULE;
+ vb_gpio->gpiob.base = -1;
+ vb_gpio->gpiob.ngpio = 16;
+ vb_gpio->gpiob.can_sleep = 1;
+ vb_gpio->gpiob.set = vprbrd_gpiob_set;
+ vb_gpio->gpiob.get = vprbrd_gpiob_get;
+ vb_gpio->gpiob.direction_input = vprbrd_gpiob_direction_input;
+ vb_gpio->gpiob.direction_output = vprbrd_gpiob_direction_output;
+ ret = gpiochip_add(&vb_gpio->gpiob);
+ if (ret < 0) {
+ dev_err(vb_gpio->gpiob.dev, "could not add gpio b");
+ goto err_gpiob;
+ }
+
+ platform_set_drvdata(pdev, vb_gpio);
+
+ vb_gpio->regmap = devm_regmap_init(vb_gpio->gpioa.dev,
+ &regmap_vb_gpio, vb, &vprbrd_regmap_config);
+ if (IS_ERR(vb_gpio->regmap)) {
+ ret = PTR_ERR(vb_gpio->regmap);
+ dev_err(vb_gpio->gpioa.dev,
+ "regmap_init failed with err: %d\n", ret);
+ goto err_gpiob;
+ }
+
+ return ret;
+
+err_gpiob:
+ ret_err = gpiochip_remove(&vb_gpio->gpioa);
+
+err_gpioa:
+ kfree(vb_gpio);
+ return ret;
+}
+
+static int __devexit vprbrd_gpio_remove(struct platform_device *pdev)
+{
+ struct vprbrd_gpio *vb_gpio = platform_get_drvdata(pdev);
+ int ret;
+
+/*
+ ret = regmap_exit(vbgpio->regmap);
+ if (ret == 0)
+*/
+ ret = gpiochip_remove(&vb_gpio->gpiob);
+ if (ret == 0)
+ ret = gpiochip_remove(&vb_gpio->gpioa);
+ if (ret == 0)
+ kfree(vb_gpio);
+
+ return ret;
+}
+
+static struct platform_driver vprbrd_gpio_driver = {
+ .driver.name = "viperboard-gpio",
+ .driver.owner = THIS_MODULE,
+ .probe = vprbrd_gpio_probe,
+ .remove = __devexit_p(vprbrd_gpio_remove),
+};
+
+static int __init vprbrd_gpio_init(void)
+{
+ switch (gpioa_freq) {
+ case 1000000:
+ gpioa_clk = VPRBRD_GPIOA_CLK_1MHZ;
+ break;
+ case 100000:
+ gpioa_clk = VPRBRD_GPIOA_CLK_100KHZ;
+ break;
+ case 10000:
+ gpioa_clk = VPRBRD_GPIOA_CLK_10KHZ;
+ break;
+ case 1000:
+ gpioa_clk = VPRBRD_GPIOA_CLK_1KHZ;
+ break;
+ case 100:
+ gpioa_clk = VPRBRD_GPIOA_CLK_100HZ;
+ break;
+ case 10:
+ gpioa_clk = VPRBRD_GPIOA_CLK_10HZ;
+ break;
+ default:
+ pr_warn("invalid gpioa_freq (%d)\n", gpioa_freq);
+ gpioa_clk = VPRBRD_GPIOA_CLK_1KHZ;
+ }
+
+ return platform_driver_register(&vprbrd_gpio_driver);
+}
+subsys_initcall(vprbrd_gpio_init);
+
+static void __exit vprbrd_gpio_exit(void)
+{
+ platform_driver_unregister(&vprbrd_gpio_driver);
+}
+module_exit(vprbrd_gpio_exit);
+
+MODULE_AUTHOR("Lars Poeschel <poeschel@xxxxxxxxxxx>");
+MODULE_DESCRIPTION("GPIO driver for Nano River Techs Viperboard");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:viperboard-gpio");
diff --git a/drivers/mfd/viperboard.c b/drivers/mfd/viperboard.c
index de368b7..9ad06cd 100644
--- a/drivers/mfd/viperboard.c
+++ b/drivers/mfd/viperboard.c
@@ -49,6 +49,9 @@ static void vprbrd_free(struct vprbrd *dev)
}

static struct mfd_cell vprbrd_devs[] = {
+ {
+ .name = "viperboard-gpio",
+ },
};

static int vprbrd_probe(struct usb_interface *interface,
diff --git a/include/linux/mfd/viperboard.h b/include/linux/mfd/viperboard.h
index 1c23c79..d3c9ad2 100644
--- a/include/linux/mfd/viperboard.h
+++ b/include/linux/mfd/viperboard.h
@@ -44,6 +44,8 @@
#define VPRBRD_USB_TIMEOUT_MS 100
#define VPRBRD_USB_REQUEST_MAJOR 0xea
#define VPRBRD_USB_REQUEST_MINOR 0xeb
+#define VPRBRD_USB_REQUEST_GPIOA 0xed
+#define VPRBRD_USB_REQUEST_GPIOB 0xdd

struct __packed vprbrd_i2c_write_hdr {
u8 cmd;
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/