OSDN Git Service

Merge branches 'ib-mfd-gpio-i2c-3.19', 'ib-mfd-iio-3.19' and 'ib-mfd-regulator-v3...
authorLee Jones <lee.jones@linaro.org>
Tue, 25 Nov 2014 16:18:03 +0000 (16:18 +0000)
committerLee Jones <lee.jones@linaro.org>
Tue, 25 Nov 2014 16:18:03 +0000 (16:18 +0000)
Immutable branch between MFD, Regulator and Clk, due for v3.19

17 files changed:
drivers/gpio/Kconfig
drivers/gpio/Makefile
drivers/gpio/gpio-dln2.c [new file with mode: 0644]
drivers/i2c/busses/Kconfig
drivers/i2c/busses/Makefile
drivers/i2c/busses/i2c-dln2.c [new file with mode: 0644]
drivers/iio/adc/Kconfig
drivers/iio/adc/Makefile
drivers/iio/adc/axp288_adc.c [new file with mode: 0644]
drivers/mfd/Kconfig
drivers/mfd/Makefile
drivers/mfd/axp20x.c
drivers/mfd/dln2.c [new file with mode: 0644]
include/linux/mfd/axp20x.h
include/linux/mfd/core.h
include/linux/mfd/dln2.h [new file with mode: 0644]
include/linux/of.h

index 0959ca9..23dfd5f 100644 (file)
@@ -905,4 +905,16 @@ config GPIO_VIPERBOARD
           River Tech's viperboard.h for detailed meaning
           of the module parameters.
 
+config GPIO_DLN2
+       tristate "Diolan DLN2 GPIO support"
+       depends on MFD_DLN2
+       select GPIOLIB_IRQCHIP
+
+       help
+         Select this option to enable GPIO driver for the Diolan DLN2
+         board.
+
+         This driver can also be built as a module. If so, the module
+         will be called gpio-dln2.
+
 endif
index e5d346c..e60677b 100644 (file)
@@ -26,6 +26,7 @@ obj-$(CONFIG_GPIO_CRYSTAL_COVE)       += gpio-crystalcove.o
 obj-$(CONFIG_GPIO_DA9052)      += gpio-da9052.o
 obj-$(CONFIG_GPIO_DA9055)      += gpio-da9055.o
 obj-$(CONFIG_GPIO_DAVINCI)     += gpio-davinci.o
+obj-$(CONFIG_GPIO_DLN2)                += gpio-dln2.o
 obj-$(CONFIG_GPIO_DWAPB)       += gpio-dwapb.o
 obj-$(CONFIG_GPIO_EM)          += gpio-em.o
 obj-$(CONFIG_GPIO_EP93XX)      += gpio-ep93xx.o
diff --git a/drivers/gpio/gpio-dln2.c b/drivers/gpio/gpio-dln2.c
new file mode 100644 (file)
index 0000000..978b51e
--- /dev/null
@@ -0,0 +1,553 @@
+/*
+ * Driver for the Diolan DLN-2 USB-GPIO adapter
+ *
+ * Copyright (c) 2014 Intel Corporation
+ *
+ * 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, version 2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/irqdomain.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/dln2.h>
+
+#define DLN2_GPIO_ID                   0x01
+
+#define DLN2_GPIO_GET_PIN_COUNT                DLN2_CMD(0x01, DLN2_GPIO_ID)
+#define DLN2_GPIO_SET_DEBOUNCE         DLN2_CMD(0x04, DLN2_GPIO_ID)
+#define DLN2_GPIO_GET_DEBOUNCE         DLN2_CMD(0x05, DLN2_GPIO_ID)
+#define DLN2_GPIO_PORT_GET_VAL         DLN2_CMD(0x06, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_GET_VAL          DLN2_CMD(0x0B, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_SET_OUT_VAL      DLN2_CMD(0x0C, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_GET_OUT_VAL      DLN2_CMD(0x0D, DLN2_GPIO_ID)
+#define DLN2_GPIO_CONDITION_MET_EV     DLN2_CMD(0x0F, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_ENABLE           DLN2_CMD(0x10, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_DISABLE          DLN2_CMD(0x11, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_SET_DIRECTION    DLN2_CMD(0x13, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_GET_DIRECTION    DLN2_CMD(0x14, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_SET_EVENT_CFG    DLN2_CMD(0x1E, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_GET_EVENT_CFG    DLN2_CMD(0x1F, DLN2_GPIO_ID)
+
+#define DLN2_GPIO_EVENT_NONE           0
+#define DLN2_GPIO_EVENT_CHANGE         1
+#define DLN2_GPIO_EVENT_LVL_HIGH       2
+#define DLN2_GPIO_EVENT_LVL_LOW                3
+#define DLN2_GPIO_EVENT_CHANGE_RISING  0x11
+#define DLN2_GPIO_EVENT_CHANGE_FALLING  0x21
+#define DLN2_GPIO_EVENT_MASK           0x0F
+
+#define DLN2_GPIO_MAX_PINS 32
+
+struct dln2_irq_work {
+       struct work_struct work;
+       struct dln2_gpio *dln2;
+       int pin;
+       int type;
+};
+
+struct dln2_gpio {
+       struct platform_device *pdev;
+       struct gpio_chip gpio;
+
+       /*
+        * Cache pin direction to save us one transfer, since the hardware has
+        * separate commands to read the in and out values.
+        */
+       DECLARE_BITMAP(output_enabled, DLN2_GPIO_MAX_PINS);
+
+       DECLARE_BITMAP(irqs_masked, DLN2_GPIO_MAX_PINS);
+       DECLARE_BITMAP(irqs_enabled, DLN2_GPIO_MAX_PINS);
+       DECLARE_BITMAP(irqs_pending, DLN2_GPIO_MAX_PINS);
+       struct dln2_irq_work *irq_work;
+};
+
+struct dln2_gpio_pin {
+       __le16 pin;
+};
+
+struct dln2_gpio_pin_val {
+       __le16 pin __packed;
+       u8 value;
+};
+
+static int dln2_gpio_get_pin_count(struct platform_device *pdev)
+{
+       int ret;
+       __le16 count;
+       int len = sizeof(count);
+
+       ret = dln2_transfer_rx(pdev, DLN2_GPIO_GET_PIN_COUNT, &count, &len);
+       if (ret < 0)
+               return ret;
+       if (len < sizeof(count))
+               return -EPROTO;
+
+       return le16_to_cpu(count);
+}
+
+static int dln2_gpio_pin_cmd(struct dln2_gpio *dln2, int cmd, unsigned pin)
+{
+       struct dln2_gpio_pin req = {
+               .pin = cpu_to_le16(pin),
+       };
+
+       return dln2_transfer_tx(dln2->pdev, cmd, &req, sizeof(req));
+}
+
+static int dln2_gpio_pin_val(struct dln2_gpio *dln2, int cmd, unsigned int pin)
+{
+       int ret;
+       struct dln2_gpio_pin req = {
+               .pin = cpu_to_le16(pin),
+       };
+       struct dln2_gpio_pin_val rsp;
+       int len = sizeof(rsp);
+
+       ret = dln2_transfer(dln2->pdev, cmd, &req, sizeof(req), &rsp, &len);
+       if (ret < 0)
+               return ret;
+       if (len < sizeof(rsp) || req.pin != rsp.pin)
+               return -EPROTO;
+
+       return rsp.value;
+}
+
+static int dln2_gpio_pin_get_in_val(struct dln2_gpio *dln2, unsigned int pin)
+{
+       int ret;
+
+       ret = dln2_gpio_pin_val(dln2, DLN2_GPIO_PIN_GET_VAL, pin);
+       if (ret < 0)
+               return ret;
+       return !!ret;
+}
+
+static int dln2_gpio_pin_get_out_val(struct dln2_gpio *dln2, unsigned int pin)
+{
+       int ret;
+
+       ret = dln2_gpio_pin_val(dln2, DLN2_GPIO_PIN_GET_OUT_VAL, pin);
+       if (ret < 0)
+               return ret;
+       return !!ret;
+}
+
+static void dln2_gpio_pin_set_out_val(struct dln2_gpio *dln2,
+                                     unsigned int pin, int value)
+{
+       struct dln2_gpio_pin_val req = {
+               .pin = cpu_to_le16(pin),
+               .value = value,
+       };
+
+       dln2_transfer_tx(dln2->pdev, DLN2_GPIO_PIN_SET_OUT_VAL, &req,
+                        sizeof(req));
+}
+
+#define DLN2_GPIO_DIRECTION_IN         0
+#define DLN2_GPIO_DIRECTION_OUT                1
+
+static int dln2_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+       struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
+       struct dln2_gpio_pin req = {
+               .pin = cpu_to_le16(offset),
+       };
+       struct dln2_gpio_pin_val rsp;
+       int len = sizeof(rsp);
+       int ret;
+
+       ret = dln2_gpio_pin_cmd(dln2, DLN2_GPIO_PIN_ENABLE, offset);
+       if (ret < 0)
+               return ret;
+
+       /* cache the pin direction */
+       ret = dln2_transfer(dln2->pdev, DLN2_GPIO_PIN_GET_DIRECTION,
+                           &req, sizeof(req), &rsp, &len);
+       if (ret < 0)
+               return ret;
+       if (len < sizeof(rsp) || req.pin != rsp.pin) {
+               ret = -EPROTO;
+               goto out_disable;
+       }
+
+       switch (rsp.value) {
+       case DLN2_GPIO_DIRECTION_IN:
+               clear_bit(offset, dln2->output_enabled);
+               return 0;
+       case DLN2_GPIO_DIRECTION_OUT:
+               set_bit(offset, dln2->output_enabled);
+               return 0;
+       default:
+               ret = -EPROTO;
+               goto out_disable;
+       }
+
+out_disable:
+       dln2_gpio_pin_cmd(dln2, DLN2_GPIO_PIN_DISABLE, offset);
+       return ret;
+}
+
+static void dln2_gpio_free(struct gpio_chip *chip, unsigned offset)
+{
+       struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
+
+       dln2_gpio_pin_cmd(dln2, DLN2_GPIO_PIN_DISABLE, offset);
+}
+
+static int dln2_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+       struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
+
+       if (test_bit(offset, dln2->output_enabled))
+               return GPIOF_DIR_OUT;
+
+       return GPIOF_DIR_IN;
+}
+
+static int dln2_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+       struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
+       int dir;
+
+       dir = dln2_gpio_get_direction(chip, offset);
+       if (dir < 0)
+               return dir;
+
+       if (dir == GPIOF_DIR_IN)
+               return dln2_gpio_pin_get_in_val(dln2, offset);
+
+       return dln2_gpio_pin_get_out_val(dln2, offset);
+}
+
+static void dln2_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+       struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
+
+       dln2_gpio_pin_set_out_val(dln2, offset, value);
+}
+
+static int dln2_gpio_set_direction(struct gpio_chip *chip, unsigned offset,
+                                  unsigned dir)
+{
+       struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
+       struct dln2_gpio_pin_val req = {
+               .pin = cpu_to_le16(offset),
+               .value = dir,
+       };
+       int ret;
+
+       ret = dln2_transfer_tx(dln2->pdev, DLN2_GPIO_PIN_SET_DIRECTION,
+                              &req, sizeof(req));
+       if (ret < 0)
+               return ret;
+
+       if (dir == DLN2_GPIO_DIRECTION_OUT)
+               set_bit(offset, dln2->output_enabled);
+       else
+               clear_bit(offset, dln2->output_enabled);
+
+       return ret;
+}
+
+static int dln2_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+       return dln2_gpio_set_direction(chip, offset, DLN2_GPIO_DIRECTION_IN);
+}
+
+static int dln2_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
+                                     int value)
+{
+       return dln2_gpio_set_direction(chip, offset, DLN2_GPIO_DIRECTION_OUT);
+}
+
+static int dln2_gpio_set_debounce(struct gpio_chip *chip, unsigned offset,
+                                 unsigned debounce)
+{
+       struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
+       __le32 duration = cpu_to_le32(debounce);
+
+       return dln2_transfer_tx(dln2->pdev, DLN2_GPIO_SET_DEBOUNCE,
+                               &duration, sizeof(duration));
+}
+
+static int dln2_gpio_set_event_cfg(struct dln2_gpio *dln2, unsigned pin,
+                                  unsigned type, unsigned period)
+{
+       struct {
+               __le16 pin;
+               u8 type;
+               __le16 period;
+       } __packed req = {
+               .pin = cpu_to_le16(pin),
+               .type = type,
+               .period = cpu_to_le16(period),
+       };
+
+       return dln2_transfer_tx(dln2->pdev, DLN2_GPIO_PIN_SET_EVENT_CFG,
+                               &req, sizeof(req));
+}
+
+static void dln2_irq_work(struct work_struct *w)
+{
+       struct dln2_irq_work *iw = container_of(w, struct dln2_irq_work, work);
+       struct dln2_gpio *dln2 = iw->dln2;
+       u8 type = iw->type & DLN2_GPIO_EVENT_MASK;
+
+       if (test_bit(iw->pin, dln2->irqs_enabled))
+               dln2_gpio_set_event_cfg(dln2, iw->pin, type, 0);
+       else
+               dln2_gpio_set_event_cfg(dln2, iw->pin, DLN2_GPIO_EVENT_NONE, 0);
+}
+
+static void dln2_irq_enable(struct irq_data *irqd)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+       struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
+       int pin = irqd_to_hwirq(irqd);
+
+       set_bit(pin, dln2->irqs_enabled);
+       schedule_work(&dln2->irq_work[pin].work);
+}
+
+static void dln2_irq_disable(struct irq_data *irqd)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+       struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
+       int pin = irqd_to_hwirq(irqd);
+
+       clear_bit(pin, dln2->irqs_enabled);
+       schedule_work(&dln2->irq_work[pin].work);
+}
+
+static void dln2_irq_mask(struct irq_data *irqd)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+       struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
+       int pin = irqd_to_hwirq(irqd);
+
+       set_bit(pin, dln2->irqs_masked);
+}
+
+static void dln2_irq_unmask(struct irq_data *irqd)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+       struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
+       struct device *dev = dln2->gpio.dev;
+       int pin = irqd_to_hwirq(irqd);
+
+       if (test_and_clear_bit(pin, dln2->irqs_pending)) {
+               int irq;
+
+               irq = irq_find_mapping(dln2->gpio.irqdomain, pin);
+               if (!irq) {
+                       dev_err(dev, "pin %d not mapped to IRQ\n", pin);
+                       return;
+               }
+
+               generic_handle_irq(irq);
+       }
+}
+
+static int dln2_irq_set_type(struct irq_data *irqd, unsigned type)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+       struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
+       int pin = irqd_to_hwirq(irqd);
+
+       switch (type) {
+       case IRQ_TYPE_LEVEL_HIGH:
+               dln2->irq_work[pin].type = DLN2_GPIO_EVENT_LVL_HIGH;
+               break;
+       case IRQ_TYPE_LEVEL_LOW:
+               dln2->irq_work[pin].type = DLN2_GPIO_EVENT_LVL_LOW;
+               break;
+       case IRQ_TYPE_EDGE_BOTH:
+               dln2->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE;
+               break;
+       case IRQ_TYPE_EDGE_RISING:
+               dln2->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE_RISING;
+               break;
+       case IRQ_TYPE_EDGE_FALLING:
+               dln2->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE_FALLING;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static struct irq_chip dln2_gpio_irqchip = {
+       .name = "dln2-irq",
+       .irq_enable = dln2_irq_enable,
+       .irq_disable = dln2_irq_disable,
+       .irq_mask = dln2_irq_mask,
+       .irq_unmask = dln2_irq_unmask,
+       .irq_set_type = dln2_irq_set_type,
+};
+
+static void dln2_gpio_event(struct platform_device *pdev, u16 echo,
+                           const void *data, int len)
+{
+       int pin, irq;
+       const struct {
+               __le16 count;
+               __u8 type;
+               __le16 pin;
+               __u8 value;
+       } __packed *event = data;
+       struct dln2_gpio *dln2 = platform_get_drvdata(pdev);
+
+       if (len < sizeof(*event)) {
+               dev_err(dln2->gpio.dev, "short event message\n");
+               return;
+       }
+
+       pin = le16_to_cpu(event->pin);
+       if (pin >= dln2->gpio.ngpio) {
+               dev_err(dln2->gpio.dev, "out of bounds pin %d\n", pin);
+               return;
+       }
+
+       irq = irq_find_mapping(dln2->gpio.irqdomain, pin);
+       if (!irq) {
+               dev_err(dln2->gpio.dev, "pin %d not mapped to IRQ\n", pin);
+               return;
+       }
+
+       if (!test_bit(pin, dln2->irqs_enabled))
+               return;
+       if (test_bit(pin, dln2->irqs_masked)) {
+               set_bit(pin, dln2->irqs_pending);
+               return;
+       }
+
+       switch (dln2->irq_work[pin].type) {
+       case DLN2_GPIO_EVENT_CHANGE_RISING:
+               if (event->value)
+                       generic_handle_irq(irq);
+               break;
+       case DLN2_GPIO_EVENT_CHANGE_FALLING:
+               if (!event->value)
+                       generic_handle_irq(irq);
+               break;
+       default:
+               generic_handle_irq(irq);
+       }
+}
+
+static int dln2_gpio_probe(struct platform_device *pdev)
+{
+       struct dln2_gpio *dln2;
+       struct device *dev = &pdev->dev;
+       int pins;
+       int i, ret;
+
+       pins = dln2_gpio_get_pin_count(pdev);
+       if (pins < 0) {
+               dev_err(dev, "failed to get pin count: %d\n", pins);
+               return pins;
+       }
+       if (pins > DLN2_GPIO_MAX_PINS) {
+               pins = DLN2_GPIO_MAX_PINS;
+               dev_warn(dev, "clamping pins to %d\n", DLN2_GPIO_MAX_PINS);
+       }
+
+       dln2 = devm_kzalloc(&pdev->dev, sizeof(*dln2), GFP_KERNEL);
+       if (!dln2)
+               return -ENOMEM;
+
+       dln2->irq_work = devm_kcalloc(&pdev->dev, pins,
+                                     sizeof(struct dln2_irq_work), GFP_KERNEL);
+       if (!dln2->irq_work)
+               return -ENOMEM;
+       for (i = 0; i < pins; i++) {
+               INIT_WORK(&dln2->irq_work[i].work, dln2_irq_work);
+               dln2->irq_work[i].pin = i;
+               dln2->irq_work[i].dln2 = dln2;
+       }
+
+       dln2->pdev = pdev;
+
+       dln2->gpio.label = "dln2";
+       dln2->gpio.dev = dev;
+       dln2->gpio.owner = THIS_MODULE;
+       dln2->gpio.base = -1;
+       dln2->gpio.ngpio = pins;
+       dln2->gpio.exported = true;
+       dln2->gpio.can_sleep = true;
+       dln2->gpio.irq_not_threaded = true;
+       dln2->gpio.set = dln2_gpio_set;
+       dln2->gpio.get = dln2_gpio_get;
+       dln2->gpio.request = dln2_gpio_request;
+       dln2->gpio.free = dln2_gpio_free;
+       dln2->gpio.get_direction = dln2_gpio_get_direction;
+       dln2->gpio.direction_input = dln2_gpio_direction_input;
+       dln2->gpio.direction_output = dln2_gpio_direction_output;
+       dln2->gpio.set_debounce = dln2_gpio_set_debounce;
+
+       platform_set_drvdata(pdev, dln2);
+
+       ret = gpiochip_add(&dln2->gpio);
+       if (ret < 0) {
+               dev_err(dev, "failed to add gpio chip: %d\n", ret);
+               goto out;
+       }
+
+       ret = gpiochip_irqchip_add(&dln2->gpio, &dln2_gpio_irqchip, 0,
+                                  handle_simple_irq, IRQ_TYPE_NONE);
+       if (ret < 0) {
+               dev_err(dev, "failed to add irq chip: %d\n", ret);
+               goto out_gpiochip_remove;
+       }
+
+       ret = dln2_register_event_cb(pdev, DLN2_GPIO_CONDITION_MET_EV,
+                                    dln2_gpio_event);
+       if (ret) {
+               dev_err(dev, "failed to register event cb: %d\n", ret);
+               goto out_gpiochip_remove;
+       }
+
+       return 0;
+
+out_gpiochip_remove:
+       gpiochip_remove(&dln2->gpio);
+out:
+       return ret;
+}
+
+static int dln2_gpio_remove(struct platform_device *pdev)
+{
+       struct dln2_gpio *dln2 = platform_get_drvdata(pdev);
+       int i;
+
+       dln2_unregister_event_cb(pdev, DLN2_GPIO_CONDITION_MET_EV);
+       for (i = 0; i < dln2->gpio.ngpio; i++)
+               flush_work(&dln2->irq_work[i].work);
+       gpiochip_remove(&dln2->gpio);
+
+       return 0;
+}
+
+static struct platform_driver dln2_gpio_driver = {
+       .driver.name    = "dln2-gpio",
+       .probe          = dln2_gpio_probe,
+       .remove         = dln2_gpio_remove,
+};
+
+module_platform_driver(dln2_gpio_driver);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com");
+MODULE_DESCRIPTION("Driver for the Diolan DLN2 GPIO interface");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:dln2-gpio");
index 917c358..b4d135c 100644 (file)
@@ -881,6 +881,16 @@ config I2C_DIOLAN_U2C
          This driver can also be built as a module.  If so, the module
          will be called i2c-diolan-u2c.
 
+config I2C_DLN2
+       tristate "Diolan DLN-2 USB I2C adapter"
+       depends on MFD_DLN2
+       help
+        If you say yes to this option, support will be included for Diolan
+        DLN2, a USB to I2C interface.
+
+        This driver can also be built as a module.  If so, the module
+        will be called i2c-dln2.
+
 config I2C_PARPORT
        tristate "Parallel port adapter"
        depends on PARPORT
index 78d56c5..cdac7f1 100644 (file)
@@ -87,6 +87,7 @@ obj-$(CONFIG_I2C_RCAR)                += i2c-rcar.o
 
 # External I2C/SMBus adapter drivers
 obj-$(CONFIG_I2C_DIOLAN_U2C)   += i2c-diolan-u2c.o
+obj-$(CONFIG_I2C_DLN2)         += i2c-dln2.o
 obj-$(CONFIG_I2C_PARPORT)      += i2c-parport.o
 obj-$(CONFIG_I2C_PARPORT_LIGHT)        += i2c-parport-light.o
 obj-$(CONFIG_I2C_ROBOTFUZZ_OSIF)       += i2c-robotfuzz-osif.o
diff --git a/drivers/i2c/busses/i2c-dln2.c b/drivers/i2c/busses/i2c-dln2.c
new file mode 100644 (file)
index 0000000..b3fb86a
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ * Driver for the Diolan DLN-2 USB-I2C adapter
+ *
+ * Copyright (c) 2014 Intel Corporation
+ *
+ * Derived from:
+ *  i2c-diolan-u2c.c
+ *  Copyright (c) 2010-2011 Ericsson AB
+ *
+ * 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, version 2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/dln2.h>
+
+#define DLN2_I2C_MODULE_ID             0x03
+#define DLN2_I2C_CMD(cmd)              DLN2_CMD(cmd, DLN2_I2C_MODULE_ID)
+
+/* I2C commands */
+#define DLN2_I2C_GET_PORT_COUNT                DLN2_I2C_CMD(0x00)
+#define DLN2_I2C_ENABLE                        DLN2_I2C_CMD(0x01)
+#define DLN2_I2C_DISABLE               DLN2_I2C_CMD(0x02)
+#define DLN2_I2C_IS_ENABLED            DLN2_I2C_CMD(0x03)
+#define DLN2_I2C_WRITE                 DLN2_I2C_CMD(0x06)
+#define DLN2_I2C_READ                  DLN2_I2C_CMD(0x07)
+#define DLN2_I2C_SCAN_DEVICES          DLN2_I2C_CMD(0x08)
+#define DLN2_I2C_PULLUP_ENABLE         DLN2_I2C_CMD(0x09)
+#define DLN2_I2C_PULLUP_DISABLE                DLN2_I2C_CMD(0x0A)
+#define DLN2_I2C_PULLUP_IS_ENABLED     DLN2_I2C_CMD(0x0B)
+#define DLN2_I2C_TRANSFER              DLN2_I2C_CMD(0x0C)
+#define DLN2_I2C_SET_MAX_REPLY_COUNT   DLN2_I2C_CMD(0x0D)
+#define DLN2_I2C_GET_MAX_REPLY_COUNT   DLN2_I2C_CMD(0x0E)
+
+#define DLN2_I2C_MAX_XFER_SIZE         256
+#define DLN2_I2C_BUF_SIZE              (DLN2_I2C_MAX_XFER_SIZE + 16)
+
+struct dln2_i2c {
+       struct platform_device *pdev;
+       struct i2c_adapter adapter;
+       u8 port;
+       /*
+        * Buffer to hold the packet for read or write transfers. One is enough
+        * since we can't have multiple transfers in parallel on the i2c bus.
+        */
+       void *buf;
+};
+
+static int dln2_i2c_enable(struct dln2_i2c *dln2, bool enable)
+{
+       u16 cmd;
+       struct {
+               u8 port;
+       } tx;
+
+       tx.port = dln2->port;
+
+       if (enable)
+               cmd = DLN2_I2C_ENABLE;
+       else
+               cmd = DLN2_I2C_DISABLE;
+
+       return dln2_transfer_tx(dln2->pdev, cmd, &tx, sizeof(tx));
+}
+
+static int dln2_i2c_write(struct dln2_i2c *dln2, u8 addr,
+                         u8 *data, u16 data_len)
+{
+       int ret;
+       struct {
+               u8 port;
+               u8 addr;
+               u8 mem_addr_len;
+               __le32 mem_addr;
+               __le16 buf_len;
+               u8 buf[DLN2_I2C_MAX_XFER_SIZE];
+       } __packed *tx = dln2->buf;
+       unsigned len;
+
+       BUILD_BUG_ON(sizeof(*tx) > DLN2_I2C_BUF_SIZE);
+
+       tx->port = dln2->port;
+       tx->addr = addr;
+       tx->mem_addr_len = 0;
+       tx->mem_addr = 0;
+       tx->buf_len = cpu_to_le16(data_len);
+       memcpy(tx->buf, data, data_len);
+
+       len = sizeof(*tx) + data_len - DLN2_I2C_MAX_XFER_SIZE;
+       ret = dln2_transfer_tx(dln2->pdev, DLN2_I2C_WRITE, tx, len);
+       if (ret < 0)
+               return ret;
+
+       return data_len;
+}
+
+static int dln2_i2c_read(struct dln2_i2c *dln2, u16 addr, u8 *data,
+                        u16 data_len)
+{
+       int ret;
+       struct {
+               u8 port;
+               u8 addr;
+               u8 mem_addr_len;
+               __le32 mem_addr;
+               __le16 buf_len;
+       } __packed tx;
+       struct {
+               __le16 buf_len;
+               u8 buf[DLN2_I2C_MAX_XFER_SIZE];
+       } __packed *rx = dln2->buf;
+       unsigned rx_len = sizeof(*rx);
+
+       BUILD_BUG_ON(sizeof(*rx) > DLN2_I2C_BUF_SIZE);
+
+       tx.port = dln2->port;
+       tx.addr = addr;
+       tx.mem_addr_len = 0;
+       tx.mem_addr = 0;
+       tx.buf_len = cpu_to_le16(data_len);
+
+       ret = dln2_transfer(dln2->pdev, DLN2_I2C_READ, &tx, sizeof(tx),
+                           rx, &rx_len);
+       if (ret < 0)
+               return ret;
+       if (rx_len < sizeof(rx->buf_len) + data_len)
+               return -EPROTO;
+       if (le16_to_cpu(rx->buf_len) != data_len)
+               return -EPROTO;
+
+       memcpy(data, rx->buf, data_len);
+
+       return data_len;
+}
+
+static int dln2_i2c_xfer(struct i2c_adapter *adapter,
+                        struct i2c_msg *msgs, int num)
+{
+       struct dln2_i2c *dln2 = i2c_get_adapdata(adapter);
+       struct i2c_msg *pmsg;
+       struct device *dev = &dln2->adapter.dev;
+       int i;
+
+       for (i = 0; i < num; i++) {
+               int ret;
+
+               pmsg = &msgs[i];
+
+               if (pmsg->len > DLN2_I2C_MAX_XFER_SIZE) {
+                       dev_warn(dev, "maximum transfer size exceeded\n");
+                       return -EOPNOTSUPP;
+               }
+
+               if (pmsg->flags & I2C_M_RD) {
+                       ret = dln2_i2c_read(dln2, pmsg->addr, pmsg->buf,
+                                           pmsg->len);
+                       if (ret < 0)
+                               return ret;
+
+                       pmsg->len = ret;
+               } else {
+                       ret = dln2_i2c_write(dln2, pmsg->addr, pmsg->buf,
+                                            pmsg->len);
+                       if (ret != pmsg->len)
+                               return -EPROTO;
+               }
+       }
+
+       return num;
+}
+
+static u32 dln2_i2c_func(struct i2c_adapter *a)
+{
+       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
+               I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
+               I2C_FUNC_SMBUS_I2C_BLOCK;
+}
+
+static const struct i2c_algorithm dln2_i2c_usb_algorithm = {
+       .master_xfer = dln2_i2c_xfer,
+       .functionality = dln2_i2c_func,
+};
+
+static int dln2_i2c_probe(struct platform_device *pdev)
+{
+       int ret;
+       struct dln2_i2c *dln2;
+       struct device *dev = &pdev->dev;
+       struct dln2_platform_data *pdata = dev_get_platdata(&pdev->dev);
+
+       dln2 = devm_kzalloc(dev, sizeof(*dln2), GFP_KERNEL);
+       if (!dln2)
+               return -ENOMEM;
+
+       dln2->buf = devm_kmalloc(dev, DLN2_I2C_BUF_SIZE, GFP_KERNEL);
+       if (!dln2->buf)
+               return -ENOMEM;
+
+       dln2->pdev = pdev;
+       dln2->port = pdata->port;
+
+       /* setup i2c adapter description */
+       dln2->adapter.owner = THIS_MODULE;
+       dln2->adapter.class = I2C_CLASS_HWMON;
+       dln2->adapter.algo = &dln2_i2c_usb_algorithm;
+       dln2->adapter.dev.parent = dev;
+       i2c_set_adapdata(&dln2->adapter, dln2);
+       snprintf(dln2->adapter.name, sizeof(dln2->adapter.name), "%s-%s-%d",
+                "dln2-i2c", dev_name(pdev->dev.parent), dln2->port);
+
+       platform_set_drvdata(pdev, dln2);
+
+       /* initialize the i2c interface */
+       ret = dln2_i2c_enable(dln2, true);
+       if (ret < 0) {
+               dev_err(dev, "failed to initialize adapter: %d\n", ret);
+               return ret;
+       }
+
+       /* and finally attach to i2c layer */
+       ret = i2c_add_adapter(&dln2->adapter);
+       if (ret < 0) {
+               dev_err(dev, "failed to add I2C adapter: %d\n", ret);
+               goto out_disable;
+       }
+
+       return 0;
+
+out_disable:
+       dln2_i2c_enable(dln2, false);
+
+       return ret;
+}
+
+static int dln2_i2c_remove(struct platform_device *pdev)
+{
+       struct dln2_i2c *dln2 = platform_get_drvdata(pdev);
+
+       i2c_del_adapter(&dln2->adapter);
+       dln2_i2c_enable(dln2, false);
+
+       return 0;
+}
+
+static struct platform_driver dln2_i2c_driver = {
+       .driver.name    = "dln2-i2c",
+       .probe          = dln2_i2c_probe,
+       .remove         = dln2_i2c_remove,
+};
+
+module_platform_driver(dln2_i2c_driver);
+
+MODULE_AUTHOR("Laurentiu Palcu <laurentiu.palcu@intel.com>");
+MODULE_DESCRIPTION("Driver for the Diolan DLN2 I2C master interface");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:dln2-i2c");
index 88bdc8f..bc4e787 100644 (file)
@@ -127,6 +127,14 @@ config AT91_ADC
        help
          Say yes here to build support for Atmel AT91 ADC.
 
+config AXP288_ADC
+       tristate "X-Powers AXP288 ADC driver"
+       depends on MFD_AXP20X
+       help
+         Say yes here to have support for X-Powers power management IC (PMIC) ADC
+         device. Depending on platform configuration, this general purpose ADC can
+         be used for sampling sensors such as thermal resistors.
+
 config EXYNOS_ADC
        tristate "Exynos ADC driver support"
        depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || (OF && COMPILE_TEST)
index cb88a6a..f30093f 100644 (file)
@@ -14,6 +14,7 @@ obj-$(CONFIG_AD7793) += ad7793.o
 obj-$(CONFIG_AD7887) += ad7887.o
 obj-$(CONFIG_AD799X) += ad799x.o
 obj-$(CONFIG_AT91_ADC) += at91_adc.o
+obj-$(CONFIG_AXP288_ADC) += axp288_adc.o
 obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o
 obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
 obj-$(CONFIG_MAX1027) += max1027.o
diff --git a/drivers/iio/adc/axp288_adc.c b/drivers/iio/adc/axp288_adc.c
new file mode 100644 (file)
index 0000000..08bcfb0
--- /dev/null
@@ -0,0 +1,261 @@
+/*
+ * axp288_adc.c - X-Powers AXP288 PMIC ADC Driver
+ *
+ * Copyright (C) 2014 Intel Corporation
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * 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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.        See the GNU
+ * General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/axp20x.h>
+#include <linux/platform_device.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/machine.h>
+#include <linux/iio/driver.h>
+
+#define AXP288_ADC_EN_MASK             0xF1
+#define AXP288_ADC_TS_PIN_GPADC                0xF2
+#define AXP288_ADC_TS_PIN_ON           0xF3
+
+enum axp288_adc_id {
+       AXP288_ADC_TS,
+       AXP288_ADC_PMIC,
+       AXP288_ADC_GP,
+       AXP288_ADC_BATT_CHRG_I,
+       AXP288_ADC_BATT_DISCHRG_I,
+       AXP288_ADC_BATT_V,
+       AXP288_ADC_NR_CHAN,
+};
+
+struct axp288_adc_info {
+       int irq;
+       struct regmap *regmap;
+};
+
+static const struct iio_chan_spec const axp288_adc_channels[] = {
+       {
+               .indexed = 1,
+               .type = IIO_TEMP,
+               .channel = 0,
+               .address = AXP288_TS_ADC_H,
+               .datasheet_name = "TS_PIN",
+       }, {
+               .indexed = 1,
+               .type = IIO_TEMP,
+               .channel = 1,
+               .address = AXP288_PMIC_ADC_H,
+               .datasheet_name = "PMIC_TEMP",
+       }, {
+               .indexed = 1,
+               .type = IIO_TEMP,
+               .channel = 2,
+               .address = AXP288_GP_ADC_H,
+               .datasheet_name = "GPADC",
+       }, {
+               .indexed = 1,
+               .type = IIO_CURRENT,
+               .channel = 3,
+               .address = AXP20X_BATT_CHRG_I_H,
+               .datasheet_name = "BATT_CHG_I",
+               .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+       }, {
+               .indexed = 1,
+               .type = IIO_CURRENT,
+               .channel = 4,
+               .address = AXP20X_BATT_DISCHRG_I_H,
+               .datasheet_name = "BATT_DISCHRG_I",
+               .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+       }, {
+               .indexed = 1,
+               .type = IIO_VOLTAGE,
+               .channel = 5,
+               .address = AXP20X_BATT_V_H,
+               .datasheet_name = "BATT_V",
+               .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+       },
+};
+
+#define AXP288_ADC_MAP(_adc_channel_label, _consumer_dev_name, \
+               _consumer_channel)                              \
+       {                                                       \
+               .adc_channel_label = _adc_channel_label,        \
+               .consumer_dev_name = _consumer_dev_name,        \
+               .consumer_channel = _consumer_channel,          \
+       }
+
+/* for consumer drivers */
+static struct iio_map axp288_adc_default_maps[] = {
+       AXP288_ADC_MAP("TS_PIN", "axp288-batt", "axp288-batt-temp"),
+       AXP288_ADC_MAP("PMIC_TEMP", "axp288-pmic", "axp288-pmic-temp"),
+       AXP288_ADC_MAP("GPADC", "axp288-gpadc", "axp288-system-temp"),
+       AXP288_ADC_MAP("BATT_CHG_I", "axp288-chrg", "axp288-chrg-curr"),
+       AXP288_ADC_MAP("BATT_DISCHRG_I", "axp288-chrg", "axp288-chrg-d-curr"),
+       AXP288_ADC_MAP("BATT_V", "axp288-batt", "axp288-batt-volt"),
+       {},
+};
+
+static int axp288_adc_read_channel(int *val, unsigned long address,
+                               struct regmap *regmap)
+{
+       u8 buf[2];
+
+       if (regmap_bulk_read(regmap, address, buf, 2))
+               return -EIO;
+       *val = (buf[0] << 4) + ((buf[1] >> 4) & 0x0F);
+
+       return IIO_VAL_INT;
+}
+
+static int axp288_adc_set_ts(struct regmap *regmap, unsigned int mode,
+                               unsigned long address)
+{
+       /* channels other than GPADC do not need to switch TS pin */
+       if (address != AXP288_GP_ADC_H)
+               return 0;
+
+       return regmap_write(regmap, AXP288_ADC_TS_PIN_CTRL, mode);
+}
+
+static int axp288_adc_read_raw(struct iio_dev *indio_dev,
+                       struct iio_chan_spec const *chan,
+                       int *val, int *val2, long mask)
+{
+       int ret;
+       struct axp288_adc_info *info = iio_priv(indio_dev);
+
+       mutex_lock(&indio_dev->mlock);
+       switch (mask) {
+       case IIO_CHAN_INFO_RAW:
+               if (axp288_adc_set_ts(info->regmap, AXP288_ADC_TS_PIN_GPADC,
+                                       chan->address)) {
+                       dev_err(&indio_dev->dev, "GPADC mode\n");
+                       ret = -EINVAL;
+                       break;
+               }
+               ret = axp288_adc_read_channel(val, chan->address, info->regmap);
+               if (axp288_adc_set_ts(info->regmap, AXP288_ADC_TS_PIN_ON,
+                                               chan->address))
+                       dev_err(&indio_dev->dev, "TS pin restore\n");
+               break;
+       case IIO_CHAN_INFO_PROCESSED:
+               ret = axp288_adc_read_channel(val, chan->address, info->regmap);
+               break;
+       default:
+               ret = -EINVAL;
+       }
+       mutex_unlock(&indio_dev->mlock);
+
+       return ret;
+}
+
+static int axp288_adc_set_state(struct regmap *regmap)
+{
+       /* ADC should be always enabled for internal FG to function */
+       if (regmap_write(regmap, AXP288_ADC_TS_PIN_CTRL, AXP288_ADC_TS_PIN_ON))
+               return -EIO;
+
+       return regmap_write(regmap, AXP20X_ADC_EN1, AXP288_ADC_EN_MASK);
+}
+
+static const struct iio_info axp288_adc_iio_info = {
+       .read_raw = &axp288_adc_read_raw,
+       .driver_module = THIS_MODULE,
+};
+
+static int axp288_adc_probe(struct platform_device *pdev)
+{
+       int ret;
+       struct axp288_adc_info *info;
+       struct iio_dev *indio_dev;
+       struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
+
+       indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
+       if (!indio_dev)
+               return -ENOMEM;
+
+       info = iio_priv(indio_dev);
+       info->irq = platform_get_irq(pdev, 0);
+       if (info->irq < 0) {
+               dev_err(&pdev->dev, "no irq resource?\n");
+               return info->irq;
+       }
+       platform_set_drvdata(pdev, indio_dev);
+       info->regmap = axp20x->regmap;
+       /*
+        * Set ADC to enabled state at all time, including system suspend.
+        * otherwise internal fuel gauge functionality may be affected.
+        */
+       ret = axp288_adc_set_state(axp20x->regmap);
+       if (ret) {
+               dev_err(&pdev->dev, "unable to enable ADC device\n");
+               return ret;
+       }
+
+       indio_dev->dev.parent = &pdev->dev;
+       indio_dev->name = pdev->name;
+       indio_dev->channels = axp288_adc_channels;
+       indio_dev->num_channels = ARRAY_SIZE(axp288_adc_channels);
+       indio_dev->info = &axp288_adc_iio_info;
+       indio_dev->modes = INDIO_DIRECT_MODE;
+       ret = iio_map_array_register(indio_dev, axp288_adc_default_maps);
+       if (ret < 0)
+               return ret;
+
+       ret = iio_device_register(indio_dev);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "unable to register iio device\n");
+               goto err_array_unregister;
+       }
+       return 0;
+
+err_array_unregister:
+       iio_map_array_unregister(indio_dev);
+
+       return ret;
+}
+
+static int axp288_adc_remove(struct platform_device *pdev)
+{
+       struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+
+       iio_device_unregister(indio_dev);
+       iio_map_array_unregister(indio_dev);
+
+       return 0;
+}
+
+static struct platform_device_id axp288_adc_id_table[] = {
+       { .name = "axp288_adc" },
+       {},
+};
+
+static struct platform_driver axp288_adc_driver = {
+       .probe = axp288_adc_probe,
+       .remove = axp288_adc_remove,
+       .id_table = axp288_adc_id_table,
+       .driver = {
+               .name = "axp288_adc",
+       },
+};
+
+MODULE_DEVICE_TABLE(platform, axp288_adc_id_table);
+
+module_platform_driver(axp288_adc_driver);
+
+MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>");
+MODULE_DESCRIPTION("X-Powers AXP288 ADC Driver");
+MODULE_LICENSE("GPL");
index 1456ea7..fdbb401 100644 (file)
@@ -74,7 +74,8 @@ config MFD_AXP20X
        select REGMAP_IRQ
        depends on I2C=y
        help
-         If you say Y here you get support for the X-Powers AXP202 and AXP209.
+         If you say Y here you get support for the X-Powers AXP202, AXP209 and
+         AXP288 power management IC (PMIC).
          This driver include only the core APIs. You have to select individual
          components like regulators or the PEK (Power Enable Key) under the
          corresponding menus.
@@ -183,6 +184,16 @@ config MFD_DA9063
          Additional drivers must be enabled in order to use the functionality
          of the device.
 
+config MFD_DLN2
+       tristate "Diolan DLN2 support"
+       select MFD_CORE
+       depends on USB
+       help
+         This adds support for Diolan USB-I2C/SPI/GPIO Master Adapter
+         DLN-2. Additional drivers such as I2C_DLN2, GPIO_DLN2,
+         etc. must be enabled in order to use the functionality of
+         the device.
+
 config MFD_MC13XXX
        tristate
        depends on (SPI_MASTER || I2C)
index 8bd54b1..10bbd0b 100644 (file)
@@ -174,6 +174,7 @@ obj-$(CONFIG_MFD_STW481X)   += stw481x.o
 obj-$(CONFIG_MFD_IPAQ_MICRO)   += ipaq-micro.o
 obj-$(CONFIG_MFD_MENF21BMC)    += menf21bmc.o
 obj-$(CONFIG_MFD_HI6421_PMIC)  += hi6421-pmic-core.o
+obj-$(CONFIG_MFD_DLN2)         += dln2.o
 
 intel-soc-pmic-objs            := intel_soc_pmic_core.o intel_soc_pmic_crc.o
 obj-$(CONFIG_INTEL_SOC_PMIC)   += intel-soc-pmic.o
index 6231adb..971b0eb 100644 (file)
@@ -1,9 +1,9 @@
 /*
- * axp20x.c - MFD core driver for the X-Powers AXP202 and AXP209
+ * axp20x.c - MFD core driver for the X-Powers' Power Management ICs
  *
- * AXP20x comprises an adaptive USB-Compatible PWM charger, 2 BUCK DC-DC
- * converters, LDOs, multiple 12-bit ADCs of voltage, current and temperature
- * as well as configurable GPIOs.
+ * AXP20x typically comprises an adaptive USB-Compatible PWM charger, BUCK DC-DC
+ * converters, LDOs, multiple 12-bit ADCs of voltage, current and temperature
+ * as well as configurable GPIOs.
  *
  * Author: Carlo Caione <carlo@caione.org>
  *
 #include <linux/mfd/core.h>
 #include <linux/of_device.h>
 #include <linux/of_irq.h>
+#include <linux/acpi.h>
 
 #define AXP20X_OFF     0x80
 
+static const char const *axp20x_model_names[] = {
+       "AXP202",
+       "AXP209",
+       "AXP288",
+};
+
 static const struct regmap_range axp20x_writeable_ranges[] = {
        regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ5_STATE),
        regmap_reg_range(AXP20X_DCDC_MODE, AXP20X_FG_RES),
@@ -47,6 +54,25 @@ static const struct regmap_access_table axp20x_volatile_table = {
        .n_yes_ranges   = ARRAY_SIZE(axp20x_volatile_ranges),
 };
 
+static const struct regmap_range axp288_writeable_ranges[] = {
+       regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ6_STATE),
+       regmap_reg_range(AXP20X_DCDC_MODE, AXP288_FG_TUNE5),
+};
+
+static const struct regmap_range axp288_volatile_ranges[] = {
+       regmap_reg_range(AXP20X_IRQ1_EN, AXP20X_IPSOUT_V_HIGH_L),
+};
+
+static const struct regmap_access_table axp288_writeable_table = {
+       .yes_ranges     = axp288_writeable_ranges,
+       .n_yes_ranges   = ARRAY_SIZE(axp288_writeable_ranges),
+};
+
+static const struct regmap_access_table axp288_volatile_table = {
+       .yes_ranges     = axp288_volatile_ranges,
+       .n_yes_ranges   = ARRAY_SIZE(axp288_volatile_ranges),
+};
+
 static struct resource axp20x_pek_resources[] = {
        {
                .name   = "PEK_DBR",
@@ -61,6 +87,39 @@ static struct resource axp20x_pek_resources[] = {
        },
 };
 
+static struct resource axp288_battery_resources[] = {
+       {
+               .start = AXP288_IRQ_QWBTU,
+               .end   = AXP288_IRQ_QWBTU,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .start = AXP288_IRQ_WBTU,
+               .end   = AXP288_IRQ_WBTU,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .start = AXP288_IRQ_QWBTO,
+               .end   = AXP288_IRQ_QWBTO,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .start = AXP288_IRQ_WBTO,
+               .end   = AXP288_IRQ_WBTO,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .start = AXP288_IRQ_WL2,
+               .end   = AXP288_IRQ_WL2,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .start = AXP288_IRQ_WL1,
+               .end   = AXP288_IRQ_WL1,
+               .flags = IORESOURCE_IRQ,
+       },
+};
+
 static const struct regmap_config axp20x_regmap_config = {
        .reg_bits       = 8,
        .val_bits       = 8,
@@ -70,47 +129,96 @@ static const struct regmap_config axp20x_regmap_config = {
        .cache_type     = REGCACHE_RBTREE,
 };
 
-#define AXP20X_IRQ(_irq, _off, _mask) \
-       [AXP20X_IRQ_##_irq] = { .reg_offset = (_off), .mask = BIT(_mask) }
+static const struct regmap_config axp288_regmap_config = {
+       .reg_bits       = 8,
+       .val_bits       = 8,
+       .wr_table       = &axp288_writeable_table,
+       .volatile_table = &axp288_volatile_table,
+       .max_register   = AXP288_FG_TUNE5,
+       .cache_type     = REGCACHE_RBTREE,
+};
+
+#define INIT_REGMAP_IRQ(_variant, _irq, _off, _mask)                   \
+       [_variant##_IRQ_##_irq] = { .reg_offset = (_off), .mask = BIT(_mask) }
 
 static const struct regmap_irq axp20x_regmap_irqs[] = {
-       AXP20X_IRQ(ACIN_OVER_V,         0, 7),
-       AXP20X_IRQ(ACIN_PLUGIN,         0, 6),
-       AXP20X_IRQ(ACIN_REMOVAL,        0, 5),
-       AXP20X_IRQ(VBUS_OVER_V,         0, 4),
-       AXP20X_IRQ(VBUS_PLUGIN,         0, 3),
-       AXP20X_IRQ(VBUS_REMOVAL,        0, 2),
-       AXP20X_IRQ(VBUS_V_LOW,          0, 1),
-       AXP20X_IRQ(BATT_PLUGIN,         1, 7),
-       AXP20X_IRQ(BATT_REMOVAL,        1, 6),
-       AXP20X_IRQ(BATT_ENT_ACT_MODE,   1, 5),
-       AXP20X_IRQ(BATT_EXIT_ACT_MODE,  1, 4),
-       AXP20X_IRQ(CHARG,               1, 3),
-       AXP20X_IRQ(CHARG_DONE,          1, 2),
-       AXP20X_IRQ(BATT_TEMP_HIGH,      1, 1),
-       AXP20X_IRQ(BATT_TEMP_LOW,       1, 0),
-       AXP20X_IRQ(DIE_TEMP_HIGH,       2, 7),
-       AXP20X_IRQ(CHARG_I_LOW,         2, 6),
-       AXP20X_IRQ(DCDC1_V_LONG,        2, 5),
-       AXP20X_IRQ(DCDC2_V_LONG,        2, 4),
-       AXP20X_IRQ(DCDC3_V_LONG,        2, 3),
-       AXP20X_IRQ(PEK_SHORT,           2, 1),
-       AXP20X_IRQ(PEK_LONG,            2, 0),
-       AXP20X_IRQ(N_OE_PWR_ON,         3, 7),
-       AXP20X_IRQ(N_OE_PWR_OFF,        3, 6),
-       AXP20X_IRQ(VBUS_VALID,          3, 5),
-       AXP20X_IRQ(VBUS_NOT_VALID,      3, 4),
-       AXP20X_IRQ(VBUS_SESS_VALID,     3, 3),
-       AXP20X_IRQ(VBUS_SESS_END,       3, 2),
-       AXP20X_IRQ(LOW_PWR_LVL1,        3, 1),
-       AXP20X_IRQ(LOW_PWR_LVL2,        3, 0),
-       AXP20X_IRQ(TIMER,               4, 7),
-       AXP20X_IRQ(PEK_RIS_EDGE,        4, 6),
-       AXP20X_IRQ(PEK_FAL_EDGE,        4, 5),
-       AXP20X_IRQ(GPIO3_INPUT,         4, 3),
-       AXP20X_IRQ(GPIO2_INPUT,         4, 2),
-       AXP20X_IRQ(GPIO1_INPUT,         4, 1),
-       AXP20X_IRQ(GPIO0_INPUT,         4, 0),
+       INIT_REGMAP_IRQ(AXP20X, ACIN_OVER_V,            0, 7),
+       INIT_REGMAP_IRQ(AXP20X, ACIN_PLUGIN,            0, 6),
+       INIT_REGMAP_IRQ(AXP20X, ACIN_REMOVAL,           0, 5),
+       INIT_REGMAP_IRQ(AXP20X, VBUS_OVER_V,            0, 4),
+       INIT_REGMAP_IRQ(AXP20X, VBUS_PLUGIN,            0, 3),
+       INIT_REGMAP_IRQ(AXP20X, VBUS_REMOVAL,           0, 2),
+       INIT_REGMAP_IRQ(AXP20X, VBUS_V_LOW,             0, 1),
+       INIT_REGMAP_IRQ(AXP20X, BATT_PLUGIN,            1, 7),
+       INIT_REGMAP_IRQ(AXP20X, BATT_REMOVAL,           1, 6),
+       INIT_REGMAP_IRQ(AXP20X, BATT_ENT_ACT_MODE,      1, 5),
+       INIT_REGMAP_IRQ(AXP20X, BATT_EXIT_ACT_MODE,     1, 4),
+       INIT_REGMAP_IRQ(AXP20X, CHARG,                  1, 3),
+       INIT_REGMAP_IRQ(AXP20X, CHARG_DONE,             1, 2),
+       INIT_REGMAP_IRQ(AXP20X, BATT_TEMP_HIGH,         1, 1),
+       INIT_REGMAP_IRQ(AXP20X, BATT_TEMP_LOW,          1, 0),
+       INIT_REGMAP_IRQ(AXP20X, DIE_TEMP_HIGH,          2, 7),
+       INIT_REGMAP_IRQ(AXP20X, CHARG_I_LOW,            2, 6),
+       INIT_REGMAP_IRQ(AXP20X, DCDC1_V_LONG,           2, 5),
+       INIT_REGMAP_IRQ(AXP20X, DCDC2_V_LONG,           2, 4),
+       INIT_REGMAP_IRQ(AXP20X, DCDC3_V_LONG,           2, 3),
+       INIT_REGMAP_IRQ(AXP20X, PEK_SHORT,              2, 1),
+       INIT_REGMAP_IRQ(AXP20X, PEK_LONG,               2, 0),
+       INIT_REGMAP_IRQ(AXP20X, N_OE_PWR_ON,            3, 7),
+       INIT_REGMAP_IRQ(AXP20X, N_OE_PWR_OFF,           3, 6),
+       INIT_REGMAP_IRQ(AXP20X, VBUS_VALID,             3, 5),
+       INIT_REGMAP_IRQ(AXP20X, VBUS_NOT_VALID,         3, 4),
+       INIT_REGMAP_IRQ(AXP20X, VBUS_SESS_VALID,        3, 3),
+       INIT_REGMAP_IRQ(AXP20X, VBUS_SESS_END,          3, 2),
+       INIT_REGMAP_IRQ(AXP20X, LOW_PWR_LVL1,           3, 1),
+       INIT_REGMAP_IRQ(AXP20X, LOW_PWR_LVL2,           3, 0),
+       INIT_REGMAP_IRQ(AXP20X, TIMER,                  4, 7),
+       INIT_REGMAP_IRQ(AXP20X, PEK_RIS_EDGE,           4, 6),
+       INIT_REGMAP_IRQ(AXP20X, PEK_FAL_EDGE,           4, 5),
+       INIT_REGMAP_IRQ(AXP20X, GPIO3_INPUT,            4, 3),
+       INIT_REGMAP_IRQ(AXP20X, GPIO2_INPUT,            4, 2),
+       INIT_REGMAP_IRQ(AXP20X, GPIO1_INPUT,            4, 1),
+       INIT_REGMAP_IRQ(AXP20X, GPIO0_INPUT,            4, 0),
+};
+
+/* some IRQs are compatible with axp20x models */
+static const struct regmap_irq axp288_regmap_irqs[] = {
+       INIT_REGMAP_IRQ(AXP288, VBUS_FALL,              0, 2),
+       INIT_REGMAP_IRQ(AXP288, VBUS_RISE,              0, 3),
+       INIT_REGMAP_IRQ(AXP288, OV,                     0, 4),
+
+       INIT_REGMAP_IRQ(AXP288, DONE,                   1, 2),
+       INIT_REGMAP_IRQ(AXP288, CHARGING,               1, 3),
+       INIT_REGMAP_IRQ(AXP288, SAFE_QUIT,              1, 4),
+       INIT_REGMAP_IRQ(AXP288, SAFE_ENTER,             1, 5),
+       INIT_REGMAP_IRQ(AXP288, ABSENT,                 1, 6),
+       INIT_REGMAP_IRQ(AXP288, APPEND,                 1, 7),
+
+       INIT_REGMAP_IRQ(AXP288, QWBTU,                  2, 0),
+       INIT_REGMAP_IRQ(AXP288, WBTU,                   2, 1),
+       INIT_REGMAP_IRQ(AXP288, QWBTO,                  2, 2),
+       INIT_REGMAP_IRQ(AXP288, WBTO,                   2, 3),
+       INIT_REGMAP_IRQ(AXP288, QCBTU,                  2, 4),
+       INIT_REGMAP_IRQ(AXP288, CBTU,                   2, 5),
+       INIT_REGMAP_IRQ(AXP288, QCBTO,                  2, 6),
+       INIT_REGMAP_IRQ(AXP288, CBTO,                   2, 7),
+
+       INIT_REGMAP_IRQ(AXP288, WL2,                    3, 0),
+       INIT_REGMAP_IRQ(AXP288, WL1,                    3, 1),
+       INIT_REGMAP_IRQ(AXP288, GPADC,                  3, 2),
+       INIT_REGMAP_IRQ(AXP288, OT,                     3, 7),
+
+       INIT_REGMAP_IRQ(AXP288, GPIO0,                  4, 0),
+       INIT_REGMAP_IRQ(AXP288, GPIO1,                  4, 1),
+       INIT_REGMAP_IRQ(AXP288, POKO,                   4, 2),
+       INIT_REGMAP_IRQ(AXP288, POKL,                   4, 3),
+       INIT_REGMAP_IRQ(AXP288, POKS,                   4, 4),
+       INIT_REGMAP_IRQ(AXP288, POKN,                   4, 5),
+       INIT_REGMAP_IRQ(AXP288, POKP,                   4, 6),
+       INIT_REGMAP_IRQ(AXP288, TIMER,                  4, 7),
+
+       INIT_REGMAP_IRQ(AXP288, MV_CHNG,                5, 0),
+       INIT_REGMAP_IRQ(AXP288, BC_USB_CHNG,            5, 1),
 };
 
 static const struct of_device_id axp20x_of_match[] = {
@@ -128,16 +236,39 @@ static const struct i2c_device_id axp20x_i2c_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, axp20x_i2c_id);
 
+static struct acpi_device_id axp20x_acpi_match[] = {
+       {
+               .id = "INT33F4",
+               .driver_data = AXP288_ID,
+       },
+       { },
+};
+MODULE_DEVICE_TABLE(acpi, axp20x_acpi_match);
+
 static const struct regmap_irq_chip axp20x_regmap_irq_chip = {
        .name                   = "axp20x_irq_chip",
        .status_base            = AXP20X_IRQ1_STATE,
        .ack_base               = AXP20X_IRQ1_STATE,
        .mask_base              = AXP20X_IRQ1_EN,
-       .num_regs               = 5,
+       .mask_invert            = true,
+       .init_ack_masked        = true,
        .irqs                   = axp20x_regmap_irqs,
        .num_irqs               = ARRAY_SIZE(axp20x_regmap_irqs),
+       .num_regs               = 5,
+
+};
+
+static const struct regmap_irq_chip axp288_regmap_irq_chip = {
+       .name                   = "axp288_irq_chip",
+       .status_base            = AXP20X_IRQ1_STATE,
+       .ack_base               = AXP20X_IRQ1_STATE,
+       .mask_base              = AXP20X_IRQ1_EN,
        .mask_invert            = true,
        .init_ack_masked        = true,
+       .irqs                   = axp288_regmap_irqs,
+       .num_irqs               = ARRAY_SIZE(axp288_regmap_irqs),
+       .num_regs               = 6,
+
 };
 
 static struct mfd_cell axp20x_cells[] = {
@@ -150,36 +281,155 @@ static struct mfd_cell axp20x_cells[] = {
        },
 };
 
+static struct resource axp288_adc_resources[] = {
+       {
+               .name  = "GPADC",
+               .start = AXP288_IRQ_GPADC,
+               .end   = AXP288_IRQ_GPADC,
+               .flags = IORESOURCE_IRQ,
+       },
+};
+
+static struct resource axp288_charger_resources[] = {
+       {
+               .start = AXP288_IRQ_OV,
+               .end   = AXP288_IRQ_OV,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .start = AXP288_IRQ_DONE,
+               .end   = AXP288_IRQ_DONE,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .start = AXP288_IRQ_CHARGING,
+               .end   = AXP288_IRQ_CHARGING,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .start = AXP288_IRQ_SAFE_QUIT,
+               .end   = AXP288_IRQ_SAFE_QUIT,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .start = AXP288_IRQ_SAFE_ENTER,
+               .end   = AXP288_IRQ_SAFE_ENTER,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .start = AXP288_IRQ_QCBTU,
+               .end   = AXP288_IRQ_QCBTU,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .start = AXP288_IRQ_CBTU,
+               .end   = AXP288_IRQ_CBTU,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .start = AXP288_IRQ_QCBTO,
+               .end   = AXP288_IRQ_QCBTO,
+               .flags = IORESOURCE_IRQ,
+       },
+       {
+               .start = AXP288_IRQ_CBTO,
+               .end   = AXP288_IRQ_CBTO,
+               .flags = IORESOURCE_IRQ,
+       },
+};
+
+static struct mfd_cell axp288_cells[] = {
+       {
+               .name = "axp288_adc",
+               .num_resources = ARRAY_SIZE(axp288_adc_resources),
+               .resources = axp288_adc_resources,
+       },
+       {
+               .name = "axp288_charger",
+               .num_resources = ARRAY_SIZE(axp288_charger_resources),
+               .resources = axp288_charger_resources,
+       },
+       {
+               .name = "axp288_battery",
+               .num_resources = ARRAY_SIZE(axp288_battery_resources),
+               .resources = axp288_battery_resources,
+       },
+};
+
 static struct axp20x_dev *axp20x_pm_power_off;
 static void axp20x_power_off(void)
 {
+       if (axp20x_pm_power_off->variant == AXP288_ID)
+               return;
+
        regmap_write(axp20x_pm_power_off->regmap, AXP20X_OFF_CTRL,
                     AXP20X_OFF);
 }
 
+static int axp20x_match_device(struct axp20x_dev *axp20x, struct device *dev)
+{
+       const struct acpi_device_id *acpi_id;
+       const struct of_device_id *of_id;
+
+       if (dev->of_node) {
+               of_id = of_match_device(axp20x_of_match, dev);
+               if (!of_id) {
+                       dev_err(dev, "Unable to match OF ID\n");
+                       return -ENODEV;
+               }
+               axp20x->variant = (long) of_id->data;
+       } else {
+               acpi_id = acpi_match_device(dev->driver->acpi_match_table, dev);
+               if (!acpi_id || !acpi_id->driver_data) {
+                       dev_err(dev, "Unable to match ACPI ID and data\n");
+                       return -ENODEV;
+               }
+               axp20x->variant = (long) acpi_id->driver_data;
+       }
+
+       switch (axp20x->variant) {
+       case AXP202_ID:
+       case AXP209_ID:
+               axp20x->nr_cells = ARRAY_SIZE(axp20x_cells);
+               axp20x->cells = axp20x_cells;
+               axp20x->regmap_cfg = &axp20x_regmap_config;
+               axp20x->regmap_irq_chip = &axp20x_regmap_irq_chip;
+               break;
+       case AXP288_ID:
+               axp20x->cells = axp288_cells;
+               axp20x->nr_cells = ARRAY_SIZE(axp288_cells);
+               axp20x->regmap_cfg = &axp288_regmap_config;
+               axp20x->regmap_irq_chip = &axp288_regmap_irq_chip;
+               break;
+       default:
+               dev_err(dev, "unsupported AXP20X ID %lu\n", axp20x->variant);
+               return -EINVAL;
+       }
+       dev_info(dev, "AXP20x variant %s found\n",
+               axp20x_model_names[axp20x->variant]);
+
+       return 0;
+}
+
 static int axp20x_i2c_probe(struct i2c_client *i2c,
                         const struct i2c_device_id *id)
 {
        struct axp20x_dev *axp20x;
-       const struct of_device_id *of_id;
        int ret;
 
        axp20x = devm_kzalloc(&i2c->dev, sizeof(*axp20x), GFP_KERNEL);
        if (!axp20x)
                return -ENOMEM;
 
-       of_id = of_match_device(axp20x_of_match, &i2c->dev);
-       if (!of_id) {
-               dev_err(&i2c->dev, "Unable to setup AXP20X data\n");
-               return -ENODEV;
-       }
-       axp20x->variant = (long) of_id->data;
+       ret = axp20x_match_device(axp20x, &i2c->dev);
+       if (ret)
+               return ret;
 
        axp20x->i2c_client = i2c;
        axp20x->dev = &i2c->dev;
        dev_set_drvdata(axp20x->dev, axp20x);
 
-       axp20x->regmap = devm_regmap_init_i2c(i2c, &axp20x_regmap_config);
+       axp20x->regmap = devm_regmap_init_i2c(i2c, axp20x->regmap_cfg);
        if (IS_ERR(axp20x->regmap)) {
                ret = PTR_ERR(axp20x->regmap);
                dev_err(&i2c->dev, "regmap init failed: %d\n", ret);
@@ -188,15 +438,15 @@ static int axp20x_i2c_probe(struct i2c_client *i2c,
 
        ret = regmap_add_irq_chip(axp20x->regmap, i2c->irq,
                                  IRQF_ONESHOT | IRQF_SHARED, -1,
-                                 &axp20x_regmap_irq_chip,
+                                 axp20x->regmap_irq_chip,
                                  &axp20x->regmap_irqc);
        if (ret) {
                dev_err(&i2c->dev, "failed to add irq chip: %d\n", ret);
                return ret;
        }
 
-       ret = mfd_add_devices(axp20x->dev, -1, axp20x_cells,
-                             ARRAY_SIZE(axp20x_cells), NULL, 0, NULL);
+       ret = mfd_add_devices(axp20x->dev, -1, axp20x->cells,
+                       axp20x->nr_cells, NULL, 0, NULL);
 
        if (ret) {
                dev_err(&i2c->dev, "failed to add MFD devices: %d\n", ret);
@@ -234,6 +484,7 @@ static struct i2c_driver axp20x_i2c_driver = {
                .name   = "axp20x",
                .owner  = THIS_MODULE,
                .of_match_table = of_match_ptr(axp20x_of_match),
+               .acpi_match_table = ACPI_PTR(axp20x_acpi_match),
        },
        .probe          = axp20x_i2c_probe,
        .remove         = axp20x_i2c_remove,
diff --git a/drivers/mfd/dln2.c b/drivers/mfd/dln2.c
new file mode 100644 (file)
index 0000000..559e6cc
--- /dev/null
@@ -0,0 +1,769 @@
+/*
+ * Driver for the Diolan DLN-2 USB adapter
+ *
+ * Copyright (c) 2014 Intel Corporation
+ *
+ * Derived from:
+ *  i2c-diolan-u2c.c
+ *  Copyright (c) 2010-2011 Ericsson AB
+ *
+ * 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, version 2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/dln2.h>
+#include <linux/rculist.h>
+
+struct dln2_header {
+       __le16 size;
+       __le16 id;
+       __le16 echo;
+       __le16 handle;
+};
+
+struct dln2_response {
+       struct dln2_header hdr;
+       __le16 result;
+};
+
+#define DLN2_GENERIC_MODULE_ID         0x00
+#define DLN2_GENERIC_CMD(cmd)          DLN2_CMD(cmd, DLN2_GENERIC_MODULE_ID)
+#define CMD_GET_DEVICE_VER             DLN2_GENERIC_CMD(0x30)
+#define CMD_GET_DEVICE_SN              DLN2_GENERIC_CMD(0x31)
+
+#define DLN2_HW_ID                     0x200
+#define DLN2_USB_TIMEOUT               200     /* in ms */
+#define DLN2_MAX_RX_SLOTS              16
+#define DLN2_MAX_URBS                  16
+#define DLN2_RX_BUF_SIZE               512
+
+enum dln2_handle {
+       DLN2_HANDLE_EVENT = 0,          /* don't change, hardware defined */
+       DLN2_HANDLE_CTRL,
+       DLN2_HANDLE_GPIO,
+       DLN2_HANDLE_I2C,
+       DLN2_HANDLES
+};
+
+/*
+ * Receive context used between the receive demultiplexer and the transfer
+ * routine. While sending a request the transfer routine will look for a free
+ * receive context and use it to wait for a response and to receive the URB and
+ * thus the response data.
+ */
+struct dln2_rx_context {
+       /* completion used to wait for a response */
+       struct completion done;
+
+       /* if non-NULL the URB contains the response */
+       struct urb *urb;
+
+       /* if true then this context is used to wait for a response */
+       bool in_use;
+};
+
+/*
+ * Receive contexts for a particular DLN2 module (i2c, gpio, etc.). We use the
+ * handle header field to identify the module in dln2_dev.mod_rx_slots and then
+ * the echo header field to index the slots field and find the receive context
+ * for a particular request.
+ */
+struct dln2_mod_rx_slots {
+       /* RX slots bitmap */
+       DECLARE_BITMAP(bmap, DLN2_MAX_RX_SLOTS);
+
+       /* used to wait for a free RX slot */
+       wait_queue_head_t wq;
+
+       /* used to wait for an RX operation to complete */
+       struct dln2_rx_context slots[DLN2_MAX_RX_SLOTS];
+
+       /* avoid races between alloc/free_rx_slot and dln2_rx_transfer */
+       spinlock_t lock;
+};
+
+struct dln2_dev {
+       struct usb_device *usb_dev;
+       struct usb_interface *interface;
+       u8 ep_in;
+       u8 ep_out;
+
+       struct urb *rx_urb[DLN2_MAX_URBS];
+       void *rx_buf[DLN2_MAX_URBS];
+
+       struct dln2_mod_rx_slots mod_rx_slots[DLN2_HANDLES];
+
+       struct list_head event_cb_list;
+       spinlock_t event_cb_lock;
+
+       bool disconnect;
+       int active_transfers;
+       wait_queue_head_t disconnect_wq;
+       spinlock_t disconnect_lock;
+};
+
+struct dln2_event_cb_entry {
+       struct list_head list;
+       u16 id;
+       struct platform_device *pdev;
+       dln2_event_cb_t callback;
+};
+
+int dln2_register_event_cb(struct platform_device *pdev, u16 id,
+                          dln2_event_cb_t event_cb)
+{
+       struct dln2_dev *dln2 = dev_get_drvdata(pdev->dev.parent);
+       struct dln2_event_cb_entry *i, *entry;
+       unsigned long flags;
+       int ret = 0;
+
+       entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+       if (!entry)
+               return -ENOMEM;
+
+       entry->id = id;
+       entry->callback = event_cb;
+       entry->pdev = pdev;
+
+       spin_lock_irqsave(&dln2->event_cb_lock, flags);
+
+       list_for_each_entry(i, &dln2->event_cb_list, list) {
+               if (i->id == id) {
+                       ret = -EBUSY;
+                       break;
+               }
+       }
+
+       if (!ret)
+               list_add_rcu(&entry->list, &dln2->event_cb_list);
+
+       spin_unlock_irqrestore(&dln2->event_cb_lock, flags);
+
+       if (ret)
+               kfree(entry);
+
+       return ret;
+}
+EXPORT_SYMBOL(dln2_register_event_cb);
+
+void dln2_unregister_event_cb(struct platform_device *pdev, u16 id)
+{
+       struct dln2_dev *dln2 = dev_get_drvdata(pdev->dev.parent);
+       struct dln2_event_cb_entry *i;
+       unsigned long flags;
+       bool found = false;
+
+       spin_lock_irqsave(&dln2->event_cb_lock, flags);
+
+       list_for_each_entry(i, &dln2->event_cb_list, list) {
+               if (i->id == id) {
+                       list_del_rcu(&i->list);
+                       found = true;
+                       break;
+               }
+       }
+
+       spin_unlock_irqrestore(&dln2->event_cb_lock, flags);
+
+       if (found) {
+               synchronize_rcu();
+               kfree(i);
+       }
+}
+EXPORT_SYMBOL(dln2_unregister_event_cb);
+
+/*
+ * Returns true if a valid transfer slot is found. In this case the URB must not
+ * be resubmitted immediately in dln2_rx as we need the data when dln2_transfer
+ * is woke up. It will be resubmitted there.
+ */
+static bool dln2_transfer_complete(struct dln2_dev *dln2, struct urb *urb,
+                                  u16 handle, u16 rx_slot)
+{
+       struct device *dev = &dln2->interface->dev;
+       struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[handle];
+       struct dln2_rx_context *rxc;
+       bool valid_slot = false;
+
+       if (rx_slot >= DLN2_MAX_RX_SLOTS)
+               goto out;
+
+       rxc = &rxs->slots[rx_slot];
+
+       /*
+        * No need to disable interrupts as this lock is not taken in interrupt
+        * context elsewhere in this driver. This function (or its callers) are
+        * also not exported to other modules.
+        */
+       spin_lock(&rxs->lock);
+       if (rxc->in_use && !rxc->urb) {
+               rxc->urb = urb;
+               complete(&rxc->done);
+               valid_slot = true;
+       }
+       spin_unlock(&rxs->lock);
+
+out:
+       if (!valid_slot)
+               dev_warn(dev, "bad/late response %d/%d\n", handle, rx_slot);
+
+       return valid_slot;
+}
+
+static void dln2_run_event_callbacks(struct dln2_dev *dln2, u16 id, u16 echo,
+                                    void *data, int len)
+{
+       struct dln2_event_cb_entry *i;
+
+       rcu_read_lock();
+
+       list_for_each_entry_rcu(i, &dln2->event_cb_list, list) {
+               if (i->id == id) {
+                       i->callback(i->pdev, echo, data, len);
+                       break;
+               }
+       }
+
+       rcu_read_unlock();
+}
+
+static void dln2_rx(struct urb *urb)
+{
+       struct dln2_dev *dln2 = urb->context;
+       struct dln2_header *hdr = urb->transfer_buffer;
+       struct device *dev = &dln2->interface->dev;
+       u16 id, echo, handle, size;
+       u8 *data;
+       int len;
+       int err;
+
+       switch (urb->status) {
+       case 0:
+               /* success */
+               break;
+       case -ECONNRESET:
+       case -ENOENT:
+       case -ESHUTDOWN:
+       case -EPIPE:
+               /* this urb is terminated, clean up */
+               dev_dbg(dev, "urb shutting down with status %d\n", urb->status);
+               return;
+       default:
+               dev_dbg(dev, "nonzero urb status received %d\n", urb->status);
+               goto out;
+       }
+
+       if (urb->actual_length < sizeof(struct dln2_header)) {
+               dev_err(dev, "short response: %d\n", urb->actual_length);
+               goto out;
+       }
+
+       handle = le16_to_cpu(hdr->handle);
+       id = le16_to_cpu(hdr->id);
+       echo = le16_to_cpu(hdr->echo);
+       size = le16_to_cpu(hdr->size);
+
+       if (size != urb->actual_length) {
+               dev_err(dev, "size mismatch: handle %x cmd %x echo %x size %d actual %d\n",
+                       handle, id, echo, size, urb->actual_length);
+               goto out;
+       }
+
+       if (handle >= DLN2_HANDLES) {
+               dev_warn(dev, "invalid handle %d\n", handle);
+               goto out;
+       }
+
+       data = urb->transfer_buffer + sizeof(struct dln2_header);
+       len = urb->actual_length - sizeof(struct dln2_header);
+
+       if (handle == DLN2_HANDLE_EVENT) {
+               dln2_run_event_callbacks(dln2, id, echo, data, len);
+       } else {
+               /* URB will be re-submitted in _dln2_transfer (free_rx_slot) */
+               if (dln2_transfer_complete(dln2, urb, handle, echo))
+                       return;
+       }
+
+out:
+       err = usb_submit_urb(urb, GFP_ATOMIC);
+       if (err < 0)
+               dev_err(dev, "failed to resubmit RX URB: %d\n", err);
+}
+
+static void *dln2_prep_buf(u16 handle, u16 cmd, u16 echo, const void *obuf,
+                          int *obuf_len, gfp_t gfp)
+{
+       int len;
+       void *buf;
+       struct dln2_header *hdr;
+
+       len = *obuf_len + sizeof(*hdr);
+       buf = kmalloc(len, gfp);
+       if (!buf)
+               return NULL;
+
+       hdr = (struct dln2_header *)buf;
+       hdr->id = cpu_to_le16(cmd);
+       hdr->size = cpu_to_le16(len);
+       hdr->echo = cpu_to_le16(echo);
+       hdr->handle = cpu_to_le16(handle);
+
+       memcpy(buf + sizeof(*hdr), obuf, *obuf_len);
+
+       *obuf_len = len;
+
+       return buf;
+}
+
+static int dln2_send_wait(struct dln2_dev *dln2, u16 handle, u16 cmd, u16 echo,
+                         const void *obuf, int obuf_len)
+{
+       int ret = 0;
+       int len = obuf_len;
+       void *buf;
+       int actual;
+
+       buf = dln2_prep_buf(handle, cmd, echo, obuf, &len, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       ret = usb_bulk_msg(dln2->usb_dev,
+                          usb_sndbulkpipe(dln2->usb_dev, dln2->ep_out),
+                          buf, len, &actual, DLN2_USB_TIMEOUT);
+
+       kfree(buf);
+
+       return ret;
+}
+
+static bool find_free_slot(struct dln2_dev *dln2, u16 handle, int *slot)
+{
+       struct dln2_mod_rx_slots *rxs;
+       unsigned long flags;
+
+       if (dln2->disconnect) {
+               *slot = -ENODEV;
+               return true;
+       }
+
+       rxs = &dln2->mod_rx_slots[handle];
+
+       spin_lock_irqsave(&rxs->lock, flags);
+
+       *slot = find_first_zero_bit(rxs->bmap, DLN2_MAX_RX_SLOTS);
+
+       if (*slot < DLN2_MAX_RX_SLOTS) {
+               struct dln2_rx_context *rxc = &rxs->slots[*slot];
+
+               set_bit(*slot, rxs->bmap);
+               rxc->in_use = true;
+       }
+
+       spin_unlock_irqrestore(&rxs->lock, flags);
+
+       return *slot < DLN2_MAX_RX_SLOTS;
+}
+
+static int alloc_rx_slot(struct dln2_dev *dln2, u16 handle)
+{
+       int ret;
+       int slot;
+
+       /*
+        * No need to timeout here, the wait is bounded by the timeout in
+        * _dln2_transfer.
+        */
+       ret = wait_event_interruptible(dln2->mod_rx_slots[handle].wq,
+                                      find_free_slot(dln2, handle, &slot));
+       if (ret < 0)
+               return ret;
+
+       return slot;
+}
+
+static void free_rx_slot(struct dln2_dev *dln2, u16 handle, int slot)
+{
+       struct dln2_mod_rx_slots *rxs;
+       struct urb *urb = NULL;
+       unsigned long flags;
+       struct dln2_rx_context *rxc;
+
+       rxs = &dln2->mod_rx_slots[handle];
+
+       spin_lock_irqsave(&rxs->lock, flags);
+
+       clear_bit(slot, rxs->bmap);
+
+       rxc = &rxs->slots[slot];
+       rxc->in_use = false;
+       urb = rxc->urb;
+       rxc->urb = NULL;
+       reinit_completion(&rxc->done);
+
+       spin_unlock_irqrestore(&rxs->lock, flags);
+
+       if (urb) {
+               int err;
+               struct device *dev = &dln2->interface->dev;
+
+               err = usb_submit_urb(urb, GFP_KERNEL);
+               if (err < 0)
+                       dev_err(dev, "failed to resubmit RX URB: %d\n", err);
+       }
+
+       wake_up_interruptible(&rxs->wq);
+}
+
+static int _dln2_transfer(struct dln2_dev *dln2, u16 handle, u16 cmd,
+                         const void *obuf, unsigned obuf_len,
+                         void *ibuf, unsigned *ibuf_len)
+{
+       int ret = 0;
+       int rx_slot;
+       struct dln2_response *rsp;
+       struct dln2_rx_context *rxc;
+       struct device *dev = &dln2->interface->dev;
+       const unsigned long timeout = DLN2_USB_TIMEOUT * HZ / 1000;
+       struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[handle];
+       int size;
+
+       spin_lock(&dln2->disconnect_lock);
+       if (!dln2->disconnect)
+               dln2->active_transfers++;
+       else
+               ret = -ENODEV;
+       spin_unlock(&dln2->disconnect_lock);
+
+       if (ret)
+               return ret;
+
+       rx_slot = alloc_rx_slot(dln2, handle);
+       if (rx_slot < 0) {
+               ret = rx_slot;
+               goto out_decr;
+       }
+
+       ret = dln2_send_wait(dln2, handle, cmd, rx_slot, obuf, obuf_len);
+       if (ret < 0) {
+               dev_err(dev, "USB write failed: %d\n", ret);
+               goto out_free_rx_slot;
+       }
+
+       rxc = &rxs->slots[rx_slot];
+
+       ret = wait_for_completion_interruptible_timeout(&rxc->done, timeout);
+       if (ret <= 0) {
+               if (!ret)
+                       ret = -ETIMEDOUT;
+               goto out_free_rx_slot;
+       } else {
+               ret = 0;
+       }
+
+       if (dln2->disconnect) {
+               ret = -ENODEV;
+               goto out_free_rx_slot;
+       }
+
+       /* if we got here we know that the response header has been checked */
+       rsp = rxc->urb->transfer_buffer;
+       size = le16_to_cpu(rsp->hdr.size);
+
+       if (size < sizeof(*rsp)) {
+               ret = -EPROTO;
+               goto out_free_rx_slot;
+       }
+
+       if (le16_to_cpu(rsp->result) > 0x80) {
+               dev_dbg(dev, "%d received response with error %d\n",
+                       handle, le16_to_cpu(rsp->result));
+               ret = -EREMOTEIO;
+               goto out_free_rx_slot;
+       }
+
+       if (!ibuf)
+               goto out_free_rx_slot;
+
+       if (*ibuf_len > size - sizeof(*rsp))
+               *ibuf_len = size - sizeof(*rsp);
+
+       memcpy(ibuf, rsp + 1, *ibuf_len);
+
+out_free_rx_slot:
+       free_rx_slot(dln2, handle, rx_slot);
+out_decr:
+       spin_lock(&dln2->disconnect_lock);
+       dln2->active_transfers--;
+       spin_unlock(&dln2->disconnect_lock);
+       if (dln2->disconnect)
+               wake_up(&dln2->disconnect_wq);
+
+       return ret;
+}
+
+int dln2_transfer(struct platform_device *pdev, u16 cmd,
+                 const void *obuf, unsigned obuf_len,
+                 void *ibuf, unsigned *ibuf_len)
+{
+       struct dln2_platform_data *dln2_pdata;
+       struct dln2_dev *dln2;
+       u16 handle;
+
+       dln2 = dev_get_drvdata(pdev->dev.parent);
+       dln2_pdata = dev_get_platdata(&pdev->dev);
+       handle = dln2_pdata->handle;
+
+       return _dln2_transfer(dln2, handle, cmd, obuf, obuf_len, ibuf,
+                             ibuf_len);
+}
+EXPORT_SYMBOL(dln2_transfer);
+
+static int dln2_check_hw(struct dln2_dev *dln2)
+{
+       int ret;
+       __le32 hw_type;
+       int len = sizeof(hw_type);
+
+       ret = _dln2_transfer(dln2, DLN2_HANDLE_CTRL, CMD_GET_DEVICE_VER,
+                            NULL, 0, &hw_type, &len);
+       if (ret < 0)
+               return ret;
+       if (len < sizeof(hw_type))
+               return -EREMOTEIO;
+
+       if (le32_to_cpu(hw_type) != DLN2_HW_ID) {
+               dev_err(&dln2->interface->dev, "Device ID 0x%x not supported\n",
+                       le32_to_cpu(hw_type));
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+static int dln2_print_serialno(struct dln2_dev *dln2)
+{
+       int ret;
+       __le32 serial_no;
+       int len = sizeof(serial_no);
+       struct device *dev = &dln2->interface->dev;
+
+       ret = _dln2_transfer(dln2, DLN2_HANDLE_CTRL, CMD_GET_DEVICE_SN, NULL, 0,
+                            &serial_no, &len);
+       if (ret < 0)
+               return ret;
+       if (len < sizeof(serial_no))
+               return -EREMOTEIO;
+
+       dev_info(dev, "Diolan DLN2 serial %u\n", le32_to_cpu(serial_no));
+
+       return 0;
+}
+
+static int dln2_hw_init(struct dln2_dev *dln2)
+{
+       int ret;
+
+       ret = dln2_check_hw(dln2);
+       if (ret < 0)
+               return ret;
+
+       return dln2_print_serialno(dln2);
+}
+
+static void dln2_free_rx_urbs(struct dln2_dev *dln2)
+{
+       int i;
+
+       for (i = 0; i < DLN2_MAX_URBS; i++) {
+               usb_kill_urb(dln2->rx_urb[i]);
+               usb_free_urb(dln2->rx_urb[i]);
+               kfree(dln2->rx_buf[i]);
+       }
+}
+
+static void dln2_free(struct dln2_dev *dln2)
+{
+       dln2_free_rx_urbs(dln2);
+       usb_put_dev(dln2->usb_dev);
+       kfree(dln2);
+}
+
+static int dln2_setup_rx_urbs(struct dln2_dev *dln2,
+                             struct usb_host_interface *hostif)
+{
+       int i;
+       int ret;
+       const int rx_max_size = DLN2_RX_BUF_SIZE;
+       struct device *dev = &dln2->interface->dev;
+
+       for (i = 0; i < DLN2_MAX_URBS; i++) {
+               dln2->rx_buf[i] = kmalloc(rx_max_size, GFP_KERNEL);
+               if (!dln2->rx_buf[i])
+                       return -ENOMEM;
+
+               dln2->rx_urb[i] = usb_alloc_urb(0, GFP_KERNEL);
+               if (!dln2->rx_urb[i])
+                       return -ENOMEM;
+
+               usb_fill_bulk_urb(dln2->rx_urb[i], dln2->usb_dev,
+                                 usb_rcvbulkpipe(dln2->usb_dev, dln2->ep_in),
+                                 dln2->rx_buf[i], rx_max_size, dln2_rx, dln2);
+
+               ret = usb_submit_urb(dln2->rx_urb[i], GFP_KERNEL);
+               if (ret < 0) {
+                       dev_err(dev, "failed to submit RX URB: %d\n", ret);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static struct dln2_platform_data dln2_pdata_gpio = {
+       .handle = DLN2_HANDLE_GPIO,
+};
+
+/* Only one I2C port seems to be supported on current hardware */
+static struct dln2_platform_data dln2_pdata_i2c = {
+       .handle = DLN2_HANDLE_I2C,
+       .port = 0,
+};
+
+static const struct mfd_cell dln2_devs[] = {
+       {
+               .name = "dln2-gpio",
+               .platform_data = &dln2_pdata_gpio,
+               .pdata_size = sizeof(struct dln2_platform_data),
+       },
+       {
+               .name = "dln2-i2c",
+               .platform_data = &dln2_pdata_i2c,
+               .pdata_size = sizeof(struct dln2_platform_data),
+       },
+};
+
+static void dln2_disconnect(struct usb_interface *interface)
+{
+       struct dln2_dev *dln2 = usb_get_intfdata(interface);
+       int i, j;
+
+       /* don't allow starting new transfers */
+       spin_lock(&dln2->disconnect_lock);
+       dln2->disconnect = true;
+       spin_unlock(&dln2->disconnect_lock);
+
+       /* cancel in progress transfers */
+       for (i = 0; i < DLN2_HANDLES; i++) {
+               struct dln2_mod_rx_slots *rxs = &dln2->mod_rx_slots[i];
+               unsigned long flags;
+
+               spin_lock_irqsave(&rxs->lock, flags);
+
+               /* cancel all response waiters */
+               for (j = 0; j < DLN2_MAX_RX_SLOTS; j++) {
+                       struct dln2_rx_context *rxc = &rxs->slots[j];
+
+                       if (rxc->in_use)
+                               complete(&rxc->done);
+               }
+
+               spin_unlock_irqrestore(&rxs->lock, flags);
+       }
+
+       /* wait for transfers to end */
+       wait_event(dln2->disconnect_wq, !dln2->active_transfers);
+
+       mfd_remove_devices(&interface->dev);
+
+       dln2_free(dln2);
+}
+
+static int dln2_probe(struct usb_interface *interface,
+                     const struct usb_device_id *usb_id)
+{
+       struct usb_host_interface *hostif = interface->cur_altsetting;
+       struct device *dev = &interface->dev;
+       struct dln2_dev *dln2;
+       int ret;
+       int i, j;
+
+       if (hostif->desc.bInterfaceNumber != 0 ||
+           hostif->desc.bNumEndpoints < 2)
+               return -ENODEV;
+
+       dln2 = kzalloc(sizeof(*dln2), GFP_KERNEL);
+       if (!dln2)
+               return -ENOMEM;
+
+       dln2->ep_out = hostif->endpoint[0].desc.bEndpointAddress;
+       dln2->ep_in = hostif->endpoint[1].desc.bEndpointAddress;
+       dln2->usb_dev = usb_get_dev(interface_to_usbdev(interface));
+       dln2->interface = interface;
+       usb_set_intfdata(interface, dln2);
+       init_waitqueue_head(&dln2->disconnect_wq);
+
+       for (i = 0; i < DLN2_HANDLES; i++) {
+               init_waitqueue_head(&dln2->mod_rx_slots[i].wq);
+               spin_lock_init(&dln2->mod_rx_slots[i].lock);
+               for (j = 0; j < DLN2_MAX_RX_SLOTS; j++)
+                       init_completion(&dln2->mod_rx_slots[i].slots[j].done);
+       }
+
+       spin_lock_init(&dln2->event_cb_lock);
+       spin_lock_init(&dln2->disconnect_lock);
+       INIT_LIST_HEAD(&dln2->event_cb_list);
+
+       ret = dln2_setup_rx_urbs(dln2, hostif);
+       if (ret)
+               goto out_cleanup;
+
+       ret = dln2_hw_init(dln2);
+       if (ret < 0) {
+               dev_err(dev, "failed to initialize hardware\n");
+               goto out_cleanup;
+       }
+
+       ret = mfd_add_hotplug_devices(dev, dln2_devs, ARRAY_SIZE(dln2_devs));
+       if (ret != 0) {
+               dev_err(dev, "failed to add mfd devices to core\n");
+               goto out_cleanup;
+       }
+
+       return 0;
+
+out_cleanup:
+       dln2_free(dln2);
+
+       return ret;
+}
+
+static const struct usb_device_id dln2_table[] = {
+       { USB_DEVICE(0xa257, 0x2013) },
+       { }
+};
+
+MODULE_DEVICE_TABLE(usb, dln2_table);
+
+static struct usb_driver dln2_driver = {
+       .name = "dln2",
+       .probe = dln2_probe,
+       .disconnect = dln2_disconnect,
+       .id_table = dln2_table,
+};
+
+module_usb_driver(dln2_driver);
+
+MODULE_AUTHOR("Octavian Purdila <octavian.purdila@intel.com>");
+MODULE_DESCRIPTION("Core driver for the Diolan DLN2 interface adapter");
+MODULE_LICENSE("GPL v2");
index d0e31a2..81589d1 100644 (file)
@@ -14,6 +14,8 @@
 enum {
        AXP202_ID = 0,
        AXP209_ID,
+       AXP288_ID,
+       NR_AXP20X_VARIANTS,
 };
 
 #define AXP20X_DATACACHE(m)            (0x04 + (m))
@@ -49,11 +51,13 @@ enum {
 #define AXP20X_IRQ3_EN                 0x42
 #define AXP20X_IRQ4_EN                 0x43
 #define AXP20X_IRQ5_EN                 0x44
+#define AXP20X_IRQ6_EN                 0x45
 #define AXP20X_IRQ1_STATE              0x48
 #define AXP20X_IRQ2_STATE              0x49
 #define AXP20X_IRQ3_STATE              0x4a
 #define AXP20X_IRQ4_STATE              0x4b
 #define AXP20X_IRQ5_STATE              0x4c
+#define AXP20X_IRQ6_STATE              0x4d
 
 /* ADC */
 #define AXP20X_ACIN_V_ADC_H            0x56
@@ -116,6 +120,15 @@ enum {
 #define AXP20X_CC_CTRL                 0xb8
 #define AXP20X_FG_RES                  0xb9
 
+/* AXP288 specific registers */
+#define AXP288_PMIC_ADC_H               0x56
+#define AXP288_PMIC_ADC_L               0x57
+#define AXP288_ADC_TS_PIN_CTRL          0x84
+
+#define AXP288_PMIC_ADC_EN              0x84
+#define AXP288_FG_TUNE5                        0xed
+
+
 /* Regulators IDs */
 enum {
        AXP20X_LDO1 = 0,
@@ -169,12 +182,58 @@ enum {
        AXP20X_IRQ_GPIO0_INPUT,
 };
 
+enum axp288_irqs {
+       AXP288_IRQ_VBUS_FALL     = 2,
+       AXP288_IRQ_VBUS_RISE,
+       AXP288_IRQ_OV,
+       AXP288_IRQ_FALLING_ALT,
+       AXP288_IRQ_RISING_ALT,
+       AXP288_IRQ_OV_ALT,
+       AXP288_IRQ_DONE          = 10,
+       AXP288_IRQ_CHARGING,
+       AXP288_IRQ_SAFE_QUIT,
+       AXP288_IRQ_SAFE_ENTER,
+       AXP288_IRQ_ABSENT,
+       AXP288_IRQ_APPEND,
+       AXP288_IRQ_QWBTU,
+       AXP288_IRQ_WBTU,
+       AXP288_IRQ_QWBTO,
+       AXP288_IRQ_WBTO,
+       AXP288_IRQ_QCBTU,
+       AXP288_IRQ_CBTU,
+       AXP288_IRQ_QCBTO,
+       AXP288_IRQ_CBTO,
+       AXP288_IRQ_WL2,
+       AXP288_IRQ_WL1,
+       AXP288_IRQ_GPADC,
+       AXP288_IRQ_OT            = 31,
+       AXP288_IRQ_GPIO0,
+       AXP288_IRQ_GPIO1,
+       AXP288_IRQ_POKO,
+       AXP288_IRQ_POKL,
+       AXP288_IRQ_POKS,
+       AXP288_IRQ_POKN,
+       AXP288_IRQ_POKP,
+       AXP288_IRQ_TIMER,
+       AXP288_IRQ_MV_CHNG,
+       AXP288_IRQ_BC_USB_CHNG,
+};
+
+#define AXP288_TS_ADC_H                0x58
+#define AXP288_TS_ADC_L                0x59
+#define AXP288_GP_ADC_H                0x5a
+#define AXP288_GP_ADC_L                0x5b
+
 struct axp20x_dev {
        struct device                   *dev;
        struct i2c_client               *i2c_client;
        struct regmap                   *regmap;
        struct regmap_irq_chip_data     *regmap_irqc;
        long                            variant;
+       int                             nr_cells;
+       struct mfd_cell                 *cells;
+       const struct regmap_config      *regmap_cfg;
+       const struct regmap_irq_chip    *regmap_irq_chip;
 };
 
 #endif /* __LINUX_MFD_AXP20X_H */
index 73e1709..a76bc10 100644 (file)
@@ -111,6 +111,13 @@ extern int mfd_add_devices(struct device *parent, int id,
                           struct resource *mem_base,
                           int irq_base, struct irq_domain *irq_domain);
 
+static inline int mfd_add_hotplug_devices(struct device *parent,
+               const struct mfd_cell *cells, int n_devs)
+{
+       return mfd_add_devices(parent, PLATFORM_DEVID_AUTO, cells, n_devs,
+                       NULL, 0, NULL);
+}
+
 extern void mfd_remove_devices(struct device *parent);
 
 #endif
diff --git a/include/linux/mfd/dln2.h b/include/linux/mfd/dln2.h
new file mode 100644 (file)
index 0000000..004b245
--- /dev/null
@@ -0,0 +1,103 @@
+#ifndef __LINUX_USB_DLN2_H
+#define __LINUX_USB_DLN2_H
+
+#define DLN2_CMD(cmd, id)              ((cmd) | ((id) << 8))
+
+struct dln2_platform_data {
+       u16 handle;             /* sub-driver handle (internally used only) */
+       u8 port;                /* I2C/SPI port */
+};
+
+/**
+ * dln2_event_cb_t - event callback function signature
+ *
+ * @pdev - the sub-device that registered this callback
+ * @echo - the echo header field received in the message
+ * @data - the data payload
+ * @len  - the data payload length
+ *
+ * The callback function is called in interrupt context and the data payload is
+ * only valid during the call. If the user needs later access of the data, it
+ * must copy it.
+ */
+
+typedef void (*dln2_event_cb_t)(struct platform_device *pdev, u16 echo,
+                               const void *data, int len);
+
+/**
+ * dl2n_register_event_cb - register a callback function for an event
+ *
+ * @pdev - the sub-device that registers the callback
+ * @event - the event for which to register a callback
+ * @event_cb - the callback function
+ *
+ * @return 0 in case of success, negative value in case of error
+ */
+int dln2_register_event_cb(struct platform_device *pdev, u16 event,
+                          dln2_event_cb_t event_cb);
+
+/**
+ * dln2_unregister_event_cb - unregister the callback function for an event
+ *
+ * @pdev - the sub-device that registered the callback
+ * @event - the event for which to register a callback
+ */
+void dln2_unregister_event_cb(struct platform_device *pdev, u16 event);
+
+/**
+ * dln2_transfer - issue a DLN2 command and wait for a response and the
+ * associated data
+ *
+ * @pdev - the sub-device which is issuing this transfer
+ * @cmd - the command to be sent to the device
+ * @obuf - the buffer to be sent to the device; it can be NULL if the user
+ *     doesn't need to transmit data with this command
+ * @obuf_len - the size of the buffer to be sent to the device
+ * @ibuf - any data associated with the response will be copied here; it can be
+ *     NULL if the user doesn't need the response data
+ * @ibuf_len - must be initialized to the input buffer size; it will be modified
+ *     to indicate the actual data transferred;
+ *
+ * @return 0 for success, negative value for errors
+ */
+int dln2_transfer(struct platform_device *pdev, u16 cmd,
+                 const void *obuf, unsigned obuf_len,
+                 void *ibuf, unsigned *ibuf_len);
+
+/**
+ * dln2_transfer_rx - variant of @dln2_transfer() where TX buffer is not needed
+ *
+ * @pdev - the sub-device which is issuing this transfer
+ * @cmd - the command to be sent to the device
+ * @ibuf - any data associated with the response will be copied here; it can be
+ *     NULL if the user doesn't need the response data
+ * @ibuf_len - must be initialized to the input buffer size; it will be modified
+ *     to indicate the actual data transferred;
+ *
+ * @return 0 for success, negative value for errors
+ */
+
+static inline int dln2_transfer_rx(struct platform_device *pdev, u16 cmd,
+                                  void *ibuf, unsigned *ibuf_len)
+{
+       return dln2_transfer(pdev, cmd, NULL, 0, ibuf, ibuf_len);
+}
+
+/**
+ * dln2_transfer_tx - variant of @dln2_transfer() where RX buffer is not needed
+ *
+ * @pdev - the sub-device which is issuing this transfer
+ * @cmd - the command to be sent to the device
+ * @obuf - the buffer to be sent to the device; it can be NULL if the
+ *     user doesn't need to transmit data with this command
+ * @obuf_len - the size of the buffer to be sent to the device
+ *
+ * @return 0 for success, negative value for errors
+ */
+static inline int dln2_transfer_tx(struct platform_device *pdev, u16 cmd,
+                                  const void *obuf, unsigned obuf_len)
+{
+       return dln2_transfer(pdev, cmd, obuf, obuf_len, NULL, NULL);
+}
+
+#endif
index 6545e7a..27b3ba1 100644 (file)
@@ -866,4 +866,15 @@ static inline int of_changeset_update_property(struct of_changeset *ocs,
 /* CONFIG_OF_RESOLVE api */
 extern int of_resolve_phandles(struct device_node *tree);
 
+/**
+ * of_system_has_poweroff_source - Tells if poweroff-source is found for device_node
+ * @np: Pointer to the given device_node
+ *
+ * return true if present false otherwise
+ */
+static inline bool of_system_has_poweroff_source(const struct device_node *np)
+{
+       return of_property_read_bool(np, "poweroff-source");
+}
+
 #endif /* _LINUX_OF_H */