msm: qpnp: Add gpiolib support for PMIC GPIOs
Add a gpio_chip driver to support the Qualcomm SPMI PMIC
architecture called QPNP. The driver supports Device Tree
and allows a device_node to be registered as a gpio-controller.
The driver also specifies APIs to allow a non-Device Tree user
the ability to configure the PMIC GPIOs.
This driver does not handle interrupts for GPIOs directly.
Instead, that work is handled by the existing qpnp-int driver.
This is feasible since the interrupt register map for all
QPNP peripherals is the same.
Change-Id: I04eb39d9855b0957f0647010fcb203ec2fc83c7c
Signed-off-by: Michael Bohan <mbohan@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/spmi/msm-qpnp-gpio.txt b/Documentation/devicetree/bindings/spmi/msm-qpnp-gpio.txt
new file mode 100644
index 0000000..f07d3c2
--- /dev/null
+++ b/Documentation/devicetree/bindings/spmi/msm-qpnp-gpio.txt
@@ -0,0 +1,142 @@
+* msm-qpnp-gpio
+
+msm-qpnp-gpio is a GPIO chip driver for the MSM SPMI implementation.
+It creates a spmi_device for every spmi-dev-container block of device_nodes.
+These device_nodes contained within specify the PMIC GPIO number associated
+with each GPIO chip. The driver will map these to Linux GPIO numbers.
+
+[PMIC GPIO Device Declarations]
+
+-Root Node-
+
+Required properties :
+ - spmi-dev-container : Used to specify the following child nodes as part of the
+ same SPMI device.
+ - gpio-controller : Specify as gpio-contoller. All child nodes will belong to this
+ gpio_chip.
+ - #gpio-cells: We encode a PMIC GPIO number and a 32-bit flag field to
+ specify the gpio configuration. This must be set to '2'.
+ - #address-cells: Specify one address field. This must be set to '1'.
+ - #size-cells: Specify one size-cell. This must be set to '1'.
+ - compatible = "qcom,qpnp-gpio" : Specify driver matching for this driver.
+
+-Child Nodes-
+
+Required properties :
+ - reg : Specify the spmi offset and size for this gpio device.
+ - qcom,qpnp-gpio-num : Specify the PMIC GPIO number for this gpio device.
+
+Optional properties :
+ - qcom,qpnp-gpio-cfg : Specify the PMIC gpio configuration.
+ The format of this configuration is specified in a tuple of 9 entries.
+ These entries should be specified in the same order as the entries listed
+ in this following discription.
+
+ @direction: indicates whether the gpio should be input, output, or
+ both.
+ QPNP_GPIO_DIR_OUT = 1,
+ QPNP_GPIO_DIR_IN = 2,
+ QPNP_GPIO_DIR_BOTH = 3
+
+ @output_type: indicates gpio should be configured as CMOS or open
+ drain.
+ QPNP_GPIO_OUT_BUF_OPEN_DRAIN = 1,
+ QPNP_GPIO_OUT_BUF_CMOS = 0
+
+ @output_value: The gpio output value of the gpio line - 0 or 1
+ @pull: Indicates whether a pull up or pull down should be
+ applied. If a pullup is required the current strength
+ needs to be specified. Current values of 30uA, 1.5uA,
+ 31.5uA, 1.5uA with 30uA boost are supported.
+ QPNP_GPIO_PULL_UP_30 = 0,
+ QPNP_GPIO_PULL_UP_1P5 = 1,
+ QPNP_GPIO_PULL_UP_31P5 = 2,
+ QPNP_GPIO_PULL_UP_1P5_30 = 3,
+ QPNP_GPIO_PULL_DN = 4,
+ QPNP_GPIO_PULL_NO = 5
+
+ @vin_sel: specifies the voltage level when the output is set to 1.
+ For an input gpio specifies the voltage level at which
+ the input is interpreted as a logical 1.
+ QPNP_GPIO_VIN0 = 0,
+ QPNP_GPIO_VIN1 = 1,
+ QPNP_GPIO_VIN2 = 2,
+ QPNP_GPIO_VIN3 = 3,
+ QPNP_GPIO_VIN4 = 4,
+ QPNP_GPIO_VIN5 = 5,
+ QPNP_GPIO_VIN6 = 6,
+ QPNP_GPIO_VIN7 = 7
+
+ @out_strength: the amount of current supplied for an output gpio.
+ QPNP_GPIO_OUT_STRENGTH_HIGH = 1,
+ QPNP_GPIO_OUT_STRENGTH_MED = 2,
+ QPNP_GPIO_OUT_STRENGTH_LOW = 3
+
+ @source_sel: choose alternate function for the gpio. Certain gpios
+ can be paired (shorted) with each other. Some gpio pin
+ can act as alternate functions.
+ QPNP_GPIO_FUNC_NORMAL = 0,
+ QPNP_GPIO_FUNC_PAIRED = 1
+ QPNP_GPIO_FUNC_1 = 2,
+ QPNP_GPIO_FUNC_3 = 3,
+ QPNP_GPIO_DTEST1 = 4,
+ QPNP_GPIO_DTEST2 = 5,
+ QPNP_GPIO_DTEST3 = 6,
+ QPNP_GPIO_DTEST4 = 7
+
+ @inv_int_pol: Invert polarity before feeding the line to the interrupt
+ module in pmic. This feature will almost be never used
+ since the pm8xxx interrupt block can detect both edges
+ and both levels.
+ @master_en: 1 = Enable features within the GPIO block based on
+ configurations.
+ 0 = Completely disable the GPIO block and let the pin
+ float with high impedance regardless of other settings.
+
+[PMIC GPIO clients]
+
+Required properties :
+ - gpios : Contains 3 fields of the form <&gpio_controller pmic_gpio_num flags>
+
+[Example]
+
+qpnp: qcom,spmi@fc4c0000 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-controller;
+ #interrupt-cells = <3>;
+
+ qcom,pm8941@0 {
+ spmi-slave-container;
+ reg = <0x0>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ pm8941_gpios: gpios {
+ spmi-dev-container;
+ compatible = "qcom,qpnp-gpio";
+ gpio-controller;
+ #gpio-cells = <2>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ qcom,pm8941_gpio1@0xc000 {
+ reg = <0xc000 0x100>;
+ qcom,qpnp-gpio-num = <62>;
+ };
+
+ qcom,pm8941_gpio2@0xc100 {
+ reg = <0xc100 0x100>;
+ qcom,qpnp-gpio-num = <20>;
+ qcom,qpnp-gpio-cfg = <0x1 0x1 0x1 0x2 0x3 0x2 0x0 0x0 0x1>;
+ };
+ };
+
+ qcom,testgpio@1000 {
+ compatible = "qcom,qpnp-testgpio";
+ reg = <0x1000 0x1000>;
+ gpios = <&pm8941_gpios 62 0x0 &pm8941_gpios 20 0x1>;
+ };
+ };
+ };
+};
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 8f3263e..8328ee2 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -477,4 +477,12 @@
This option enables support for on-chip GPIO found on Qualcomm PM8xxx
PMICs through RPC.
+config GPIO_QPNP
+ depends on ARCH_MSMCOPPER
+ depends on OF_SPMI
+ depends on MSM_QPNP_INT
+ tristate "Qualcomm QPNP GPIO support"
+ help
+ Say 'y' here to include support for the Qualcomm QPNP gpio
+ support. QPNP is a SPMI based PMIC implementation.
endif
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 83972f1..db19ac8 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -51,3 +51,4 @@
obj-$(CONFIG_GPIO_PM8XXX) += pm8xxx-gpio.o
obj-$(CONFIG_GPIO_PM8XXX_MPP) += pm8xxx-mpp.o
obj-$(CONFIG_GPIO_PM8XXX_RPC) += gpio-pm8xxx-rpc.o
+obj-$(CONFIG_GPIO_QPNP) += qpnp-gpio.o
diff --git a/drivers/gpio/qpnp-gpio.c b/drivers/gpio/qpnp-gpio.c
new file mode 100644
index 0000000..b09b040
--- /dev/null
+++ b/drivers/gpio/qpnp-gpio.c
@@ -0,0 +1,706 @@
+/* Copyright (c) 2012, Code Aurora Forum. 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 version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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/interrupt.h>
+#include <linux/types.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/qpnp/gpio.h>
+
+#include <mach/qpnp.h>
+
+#define Q_REG_ADDR(q_spec, reg_index) \
+ ((q_spec)->offset + reg_index)
+
+#define Q_REG_STATUS1 0x8
+#define Q_NUM_CTL_REGS 5
+
+/* control register base address offsets */
+#define Q_REG_IO_CTL1 0x42
+#define Q_REG_INPUT_CTL1 0x43
+#define Q_REG_OUTPUT_CTL1 0x44
+#define Q_REG_OUTPUT_CTL2 0x45
+#define Q_REG_EN_CTL1 0x46
+
+/* control register regs array indices */
+#define Q_REG_I_IO_CTL1 0
+#define Q_REG_I_INPUT_CTL1 1
+#define Q_REG_I_OUTPUT_CTL1 2
+#define Q_REG_I_OUTPUT_CTL2 3
+#define Q_REG_I_EN_CTL1 4
+
+/* control register configuration */
+#define Q_REG_VIN_SHIFT 0
+#define Q_REG_VIN_MASK 0x7
+#define Q_REG_PULL_SHIFT 4
+#define Q_REG_PULL_MASK 0x70
+#define Q_REG_INPUT_EN_SHIFT 7
+#define Q_REG_INPUT_EN_MASK 0x80
+#define Q_REG_OUT_STRENGTH_SHIFT 0
+#define Q_REG_OUT_STRENGTH_MASK 0x3
+#define Q_REG_OUT_TYPE_SHIFT 6
+#define Q_REG_OUT_TYPE_MASK 0x40
+#define Q_REG_OUT_INVERT_SHIFT 0
+#define Q_REG_OUT_INVERT_MASK 0x1
+#define Q_REG_SRC_SEL_SHIFT 1
+#define Q_REG_SRC_SEL_MASK 0xE
+#define Q_REG_OUTPUT_EN_SHIFT 7
+#define Q_REG_OUTPUT_EN_MASK 0x80
+#define Q_REG_MASTER_EN_SHIFT 7
+#define Q_REG_MASTER_EN_MASK 0x80
+
+
+struct qpnp_gpio_spec {
+ uint8_t slave; /* 0-15 */
+ uint16_t offset; /* 0-255 */
+ uint32_t gpio_chip_idx; /* offset from gpio_chip base */
+ int irq; /* logical IRQ number */
+ u8 regs[Q_NUM_CTL_REGS]; /* Control regs */
+};
+
+struct qpnp_gpio_chip {
+ struct gpio_chip gpio_chip;
+ struct spmi_device *spmi;
+ struct qpnp_gpio_spec **pmic_gpios;
+ struct qpnp_gpio_spec **chip_gpios;
+ uint32_t pmic_gpio_lowest;
+ uint32_t pmic_gpio_highest;
+ struct device_node *int_ctrl;
+ struct list_head chip_list;
+};
+
+static LIST_HEAD(qpnp_gpio_chips);
+static DEFINE_MUTEX(qpnp_gpio_chips_lock);
+
+static inline void qpnp_pmic_gpio_set_spec(struct qpnp_gpio_chip *q_chip,
+ uint32_t pmic_gpio,
+ struct qpnp_gpio_spec *spec)
+{
+ q_chip->pmic_gpios[pmic_gpio - q_chip->pmic_gpio_lowest] = spec;
+}
+
+static inline struct qpnp_gpio_spec *qpnp_pmic_gpio_get_spec(
+ struct qpnp_gpio_chip *q_chip,
+ uint32_t pmic_gpio)
+{
+ if (pmic_gpio < q_chip->pmic_gpio_lowest ||
+ pmic_gpio > q_chip->pmic_gpio_highest)
+ return NULL;
+
+ return q_chip->pmic_gpios[pmic_gpio - q_chip->pmic_gpio_lowest];
+}
+
+static inline struct qpnp_gpio_spec *qpnp_chip_gpio_get_spec(
+ struct qpnp_gpio_chip *q_chip,
+ uint32_t chip_gpio)
+{
+ if (chip_gpio > q_chip->gpio_chip.ngpio)
+ return NULL;
+
+ return q_chip->chip_gpios[chip_gpio];
+}
+
+static inline void qpnp_chip_gpio_set_spec(struct qpnp_gpio_chip *q_chip,
+ uint32_t chip_gpio,
+ struct qpnp_gpio_spec *spec)
+{
+ q_chip->chip_gpios[chip_gpio] = spec;
+}
+
+int qpnp_gpio_config(int gpio, struct qpnp_gpio_cfg *param)
+{
+ int rc, chip_offset;
+ struct qpnp_gpio_chip *q_chip;
+ struct qpnp_gpio_spec *q_spec = NULL;
+ struct gpio_chip *gpio_chip;
+
+ if (param == NULL)
+ return -EINVAL;
+
+ mutex_lock(&qpnp_gpio_chips_lock);
+ list_for_each_entry(q_chip, &qpnp_gpio_chips, chip_list) {
+ gpio_chip = &q_chip->gpio_chip;
+ if (gpio >= gpio_chip->base
+ && gpio < gpio_chip->base + gpio_chip->ngpio) {
+ chip_offset = gpio - gpio_chip->base;
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, chip_offset);
+ if (WARN_ON(!q_spec)) {
+ mutex_unlock(&qpnp_gpio_chips_lock);
+ return -ENODEV;
+ }
+ break;
+ }
+ }
+ mutex_unlock(&qpnp_gpio_chips_lock);
+ if (!q_spec) {
+ pr_err("gpio %d not handled by any pmic\n", gpio);
+ return -EINVAL;
+ }
+
+ q_spec->regs[Q_REG_I_IO_CTL1] = (param->vin_sel <<
+ Q_REG_VIN_SHIFT) & Q_REG_VIN_MASK;
+ q_spec->regs[Q_REG_I_IO_CTL1] |= (param->pull <<
+ Q_REG_PULL_SHIFT) & Q_REG_PULL_MASK;
+ q_spec->regs[Q_REG_I_INPUT_CTL1] = ((param->direction &
+ QPNP_GPIO_DIR_IN) ? ((1 << Q_REG_INPUT_EN_SHIFT)) : 0);
+
+ if (param->direction & QPNP_GPIO_DIR_OUT) {
+ q_spec->regs[Q_REG_I_OUTPUT_CTL1] = (param->out_strength
+ << Q_REG_OUT_STRENGTH_SHIFT) & Q_REG_OUT_STRENGTH_MASK;
+ q_spec->regs[Q_REG_I_OUTPUT_CTL1] |= (param->output_type
+ << Q_REG_OUT_TYPE_SHIFT) & Q_REG_OUT_TYPE_MASK;
+ } else {
+ q_spec->regs[Q_REG_I_OUTPUT_CTL1] = 0;
+ }
+
+ if (param->direction & QPNP_GPIO_DIR_OUT) {
+ q_spec->regs[Q_REG_I_OUTPUT_CTL2] = (param->inv_int_pol
+ << Q_REG_OUT_INVERT_SHIFT) & Q_REG_OUT_INVERT_MASK;
+ q_spec->regs[Q_REG_I_OUTPUT_CTL2] |= (param->src_select
+ << Q_REG_SRC_SEL_SHIFT) & Q_REG_SRC_SEL_MASK;
+ q_spec->regs[Q_REG_I_OUTPUT_CTL2] |= (1 <<
+ Q_REG_OUTPUT_EN_SHIFT) & Q_REG_OUTPUT_EN_MASK;
+ } else {
+ q_spec->regs[Q_REG_I_OUTPUT_CTL2] = 0;
+ }
+
+ q_spec->regs[Q_REG_I_EN_CTL1] = (param->master_en <<
+ Q_REG_MASTER_EN_SHIFT) & Q_REG_MASTER_EN_MASK;
+
+ rc = spmi_ext_register_writel(q_chip->spmi->ctrl, q_spec->slave,
+ Q_REG_ADDR(q_spec, Q_REG_IO_CTL1),
+ &q_spec->regs[Q_REG_I_IO_CTL1], Q_NUM_CTL_REGS);
+ if (rc)
+ dev_err(&q_chip->spmi->dev, "%s: unable to write master"
+ " enable\n", __func__);
+
+ return rc;
+}
+EXPORT_SYMBOL(qpnp_gpio_config);
+
+int qpnp_gpio_map_gpio(uint16_t slave_id, uint32_t pmic_gpio)
+{
+ struct qpnp_gpio_chip *q_chip;
+ struct qpnp_gpio_spec *q_spec = NULL;
+
+ mutex_lock(&qpnp_gpio_chips_lock);
+ list_for_each_entry(q_chip, &qpnp_gpio_chips, chip_list) {
+ if (q_chip->spmi->sid != slave_id)
+ continue;
+ if (q_chip->pmic_gpio_lowest <= pmic_gpio &&
+ q_chip->pmic_gpio_highest >= pmic_gpio) {
+ q_spec = qpnp_pmic_gpio_get_spec(q_chip, pmic_gpio);
+ mutex_unlock(&qpnp_gpio_chips_lock);
+ if (WARN_ON(!q_spec))
+ return -ENODEV;
+ return q_chip->gpio_chip.base + q_spec->gpio_chip_idx;
+ }
+ }
+ mutex_unlock(&qpnp_gpio_chips_lock);
+ return -EINVAL;
+}
+EXPORT_SYMBOL(qpnp_gpio_map_gpio);
+
+static int qpnp_gpio_to_irq(struct gpio_chip *gpio_chip, unsigned offset)
+{
+ struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev);
+ struct qpnp_gpio_spec *q_spec;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (!q_spec)
+ return -EINVAL;
+
+ return q_spec->irq;
+}
+
+static int qpnp_gpio_get(struct gpio_chip *gpio_chip, unsigned offset)
+{
+ int rc, ret_val;
+ struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev);
+ struct qpnp_gpio_spec *q_spec = NULL;
+ u8 buf[1];
+
+ if (WARN_ON(!q_chip))
+ return -ENODEV;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (WARN_ON(!q_spec))
+ return -ENODEV;
+
+ /* gpio val is from RT status iff input is enabled */
+ if (q_spec->regs[Q_REG_I_INPUT_CTL1] & Q_REG_INPUT_EN_MASK) {
+ /* INT_RT_STS */
+ rc = spmi_ext_register_readl(q_chip->spmi->ctrl, q_spec->slave,
+ Q_REG_ADDR(q_spec, Q_REG_STATUS1),
+ &buf[0], 1);
+ return buf[0];
+
+ } else {
+ ret_val = (q_spec->regs[Q_REG_I_OUTPUT_CTL2] &
+ Q_REG_OUT_INVERT_MASK) >> Q_REG_OUT_INVERT_SHIFT;
+ return ret_val;
+ }
+
+ return 0;
+}
+
+static int __qpnp_gpio_set(struct qpnp_gpio_chip *q_chip,
+ struct qpnp_gpio_spec *q_spec, int value)
+{
+ int rc;
+
+ if (!q_chip || !q_spec)
+ return -EINVAL;
+
+ q_spec->regs[Q_REG_I_OUTPUT_CTL2] &= ~(1 << Q_REG_OUT_INVERT_SHIFT);
+
+ if (value)
+ q_spec->regs[Q_REG_I_OUTPUT_CTL2] |=
+ (1 << Q_REG_OUT_INVERT_SHIFT);
+
+ rc = spmi_ext_register_writel(q_chip->spmi->ctrl, q_spec->slave,
+ Q_REG_ADDR(q_spec, Q_REG_OUTPUT_CTL2),
+ &q_spec->regs[Q_REG_I_OUTPUT_CTL2], 1);
+ if (rc)
+ dev_err(&q_chip->spmi->dev, "%s: spmi write failed\n",
+ __func__);
+ return rc;
+}
+
+
+static void qpnp_gpio_set(struct gpio_chip *gpio_chip,
+ unsigned offset, int value)
+{
+ struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev);
+ struct qpnp_gpio_spec *q_spec;
+
+ if (WARN_ON(!q_chip))
+ return;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (WARN_ON(!q_spec))
+ return;
+
+ __qpnp_gpio_set(q_chip, q_spec, value);
+}
+
+static int qpnp_gpio_set_direction(struct qpnp_gpio_chip *q_chip,
+ struct qpnp_gpio_spec *q_spec, int direction)
+{
+ int rc;
+
+ if (!q_chip || !q_spec)
+ return -EINVAL;
+
+ if (direction & QPNP_GPIO_DIR_IN) {
+ q_spec->regs[Q_REG_I_INPUT_CTL1] |=
+ (1 << Q_REG_INPUT_EN_SHIFT);
+ q_spec->regs[Q_REG_I_OUTPUT_CTL2] &=
+ ~(1 << Q_REG_OUTPUT_EN_SHIFT);
+ } else {
+ q_spec->regs[Q_REG_I_INPUT_CTL1] &=
+ ~(1 << Q_REG_INPUT_EN_SHIFT);
+ q_spec->regs[Q_REG_I_OUTPUT_CTL2] |=
+ (1 << Q_REG_OUTPUT_EN_SHIFT);
+ }
+
+ rc = spmi_ext_register_writel(q_chip->spmi->ctrl, q_spec->slave,
+ Q_REG_ADDR(q_spec, Q_REG_INPUT_CTL1),
+ &q_spec->regs[Q_REG_I_INPUT_CTL1], 3);
+ return rc;
+}
+
+static int qpnp_gpio_direction_input(struct gpio_chip *gpio_chip,
+ unsigned offset)
+{
+ struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev);
+ struct qpnp_gpio_spec *q_spec;
+
+ if (WARN_ON(!q_chip))
+ return -ENODEV;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (WARN_ON(!q_spec))
+ return -ENODEV;
+
+ return qpnp_gpio_set_direction(q_chip, q_spec, QPNP_GPIO_DIR_IN);
+}
+
+static int qpnp_gpio_direction_output(struct gpio_chip *gpio_chip,
+ unsigned offset,
+ int val)
+{
+ int rc;
+ struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev);
+ struct qpnp_gpio_spec *q_spec;
+
+ if (WARN_ON(!q_chip))
+ return -ENODEV;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (WARN_ON(!q_spec))
+ return -ENODEV;
+
+ rc = __qpnp_gpio_set(q_chip, q_spec, val);
+ if (rc)
+ return rc;
+
+ rc = qpnp_gpio_set_direction(q_chip, q_spec, QPNP_GPIO_DIR_OUT);
+
+ return rc;
+}
+
+static int qpnp_gpio_of_gpio_xlate(struct gpio_chip *gpio_chip,
+ struct device_node *np,
+ const void *gpio_spec, u32 *flags)
+{
+ struct qpnp_gpio_chip *q_chip = dev_get_drvdata(gpio_chip->dev);
+ struct qpnp_gpio_spec *q_spec;
+ const __be32 *gpio = gpio_spec;
+ u32 n = be32_to_cpup(gpio);
+
+ if (WARN_ON(gpio_chip->of_gpio_n_cells < 2)) {
+ pr_err("%s: of_gpio_n_cells < 2\n", __func__);
+ return -EINVAL;
+ }
+
+ q_spec = qpnp_pmic_gpio_get_spec(q_chip, n);
+ if (!q_spec) {
+ pr_err("%s: no such PMIC gpio %u in device topology\n",
+ __func__, n);
+ return -EINVAL;
+ }
+
+ if (flags)
+ *flags = be32_to_cpu(gpio[1]);
+
+ return q_spec->gpio_chip_idx;
+}
+
+static int qpnp_gpio_config_default(struct spmi_device *spmi,
+ const __be32 *prop, int gpio)
+{
+ struct qpnp_gpio_cfg param;
+ int rc;
+
+ dev_dbg(&spmi->dev, "%s: p[0]: 0x%x p[1]: 0x%x p[2]: 0x%x p[3]:"
+ " 0x%x p[4]: 0x%x p[5]: 0x%x p[6]: 0x%x p[7]: 0x%x"
+ " p[8]: 0x%x\n", __func__,
+ be32_to_cpup(&prop[0]), be32_to_cpup(&prop[1]),
+ be32_to_cpup(&prop[2]), be32_to_cpup(&prop[3]),
+ be32_to_cpup(&prop[4]), be32_to_cpup(&prop[5]),
+ be32_to_cpup(&prop[6]), be32_to_cpup(&prop[7]),
+ be32_to_cpup(&prop[8]));
+
+ param.direction = be32_to_cpup(&prop[0]);
+ param.output_type = be32_to_cpup(&prop[1]);
+ param.output_value = be32_to_cpup(&prop[2]);
+ param.pull = be32_to_cpup(&prop[3]);
+ param.vin_sel = be32_to_cpup(&prop[4]);
+ param.out_strength = be32_to_cpup(&prop[5]);
+ param.src_select = be32_to_cpup(&prop[6]);
+ param.inv_int_pol = be32_to_cpup(&prop[7]);
+ param.master_en = be32_to_cpup(&prop[8]);
+
+ rc = qpnp_gpio_config(gpio, ¶m);
+ if (rc)
+ dev_err(&spmi->dev, "%s: unable to set default config for"
+ " gpio %d\n", __func__, gpio);
+ return rc;
+}
+
+static int qpnp_gpio_free_chip(struct qpnp_gpio_chip *q_chip)
+{
+ struct spmi_device *spmi = q_chip->spmi;
+ int rc, i;
+
+ if (q_chip->chip_gpios)
+ for (i = 0; i < spmi->num_dev_node; i++)
+ kfree(q_chip->chip_gpios[i]);
+
+ mutex_lock(&qpnp_gpio_chips_lock);
+ list_del(&q_chip->chip_list);
+ mutex_unlock(&qpnp_gpio_chips_lock);
+ rc = gpiochip_remove(&q_chip->gpio_chip);
+ if (rc)
+ dev_err(&q_chip->spmi->dev, "%s: unable to remove gpio\n",
+ __func__);
+ kfree(q_chip->chip_gpios);
+ kfree(q_chip->pmic_gpios);
+ kfree(q_chip);
+ return rc;
+}
+
+static int qpnp_gpio_probe(struct spmi_device *spmi)
+{
+ struct qpnp_gpio_chip *q_chip;
+ struct resource *res;
+ struct qpnp_gpio_spec *q_spec;
+ const __be32 *prop;
+ int i, rc, ret, gpio, len;
+ int lowest_gpio = INT_MAX, highest_gpio = INT_MIN;
+ u32 intspec[3];
+
+ q_chip = kzalloc(sizeof(*q_chip), GFP_KERNEL);
+ if (!q_chip) {
+ dev_err(&spmi->dev, "%s: Can't allocate gpio_chip\n",
+ __func__);
+ return -ENOMEM;
+ }
+ q_chip->spmi = spmi;
+ dev_set_drvdata(&spmi->dev, q_chip);
+
+ mutex_lock(&qpnp_gpio_chips_lock);
+ list_add(&q_chip->chip_list, &qpnp_gpio_chips);
+ mutex_unlock(&qpnp_gpio_chips_lock);
+
+ /* first scan through nodes to find the range required for allocation */
+ for (i = 0; i < spmi->num_dev_node; i++) {
+ prop = of_get_property(spmi->dev_node[i].of_node,
+ "qcom,qpnp-gpio-num", &len);
+ if (!prop) {
+ dev_err(&spmi->dev, "%s: unable to get"
+ " qcom,qpnp-gpio-num property\n", __func__);
+ ret = -EINVAL;
+ goto err_probe;
+ } else if (len != sizeof(__be32)) {
+ dev_err(&spmi->dev, "%s: Invalid qcom,qpnp-gpio-num"
+ " property\n", __func__);
+ ret = -EINVAL;
+ goto err_probe;
+ }
+
+ gpio = be32_to_cpup(prop);
+ if (gpio < lowest_gpio)
+ lowest_gpio = gpio;
+ if (gpio > highest_gpio)
+ highest_gpio = gpio;
+ }
+
+ if (highest_gpio < lowest_gpio) {
+ dev_err(&spmi->dev, "%s: no device nodes specified in"
+ " topology\n", __func__);
+ ret = -EINVAL;
+ goto err_probe;
+ } else if (lowest_gpio == 0) {
+ dev_err(&spmi->dev, "%s: 0 is not a valid PMIC GPIO\n",
+ __func__);
+ ret = -EINVAL;
+ goto err_probe;
+ }
+
+ q_chip->pmic_gpio_lowest = lowest_gpio;
+ q_chip->pmic_gpio_highest = highest_gpio;
+
+ /* allocate gpio lookup tables */
+ q_chip->pmic_gpios = kzalloc(sizeof(struct qpnp_gpio_spec *) *
+ highest_gpio - lowest_gpio + 1,
+ GFP_KERNEL);
+ q_chip->chip_gpios = kzalloc(sizeof(struct qpnp_gpio_spec *) *
+ spmi->num_dev_node, GFP_KERNEL);
+ if (!q_chip->pmic_gpios || !q_chip->chip_gpios) {
+ dev_err(&spmi->dev, "%s: unable to allocate memory\n",
+ __func__);
+ ret = -ENOMEM;
+ goto err_probe;
+ }
+
+ /* get interrupt controller device_node */
+ q_chip->int_ctrl = of_irq_find_parent(spmi->dev.of_node);
+ if (!q_chip->int_ctrl) {
+ dev_err(&spmi->dev, "%s: Can't find interrupt parent\n",
+ __func__);
+ ret = -EINVAL;
+ goto err_probe;
+ }
+
+ /* now scan through again and populate the lookup table */
+ for (i = 0; i < spmi->num_dev_node; i++) {
+ res = qpnp_get_resource(spmi, i, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&spmi->dev, "%s: node %s is missing has no"
+ " base address definition\n",
+ __func__, spmi->dev_node[i].of_node->full_name);
+ }
+
+ prop = of_get_property(spmi->dev_node[i].of_node,
+ "qcom,qpnp-gpio-num", &len);
+ if (!prop) {
+ dev_err(&spmi->dev, "%s: unable to get"
+ " qcom,qpnp-gpio-num property\n", __func__);
+ ret = -EINVAL;
+ goto err_probe;
+ } else if (len != sizeof(__be32)) {
+ dev_err(&spmi->dev, "%s: Invalid qcom,qpnp-gpio-num"
+ " property\n", __func__);
+ ret = -EINVAL;
+ goto err_probe;
+ }
+ gpio = be32_to_cpup(prop);
+
+ q_spec = kzalloc(sizeof(struct qpnp_gpio_spec),
+ GFP_KERNEL);
+ if (!q_spec) {
+ dev_err(&spmi->dev, "%s: unable to allocate"
+ " memory\n",
+ __func__);
+ ret = -ENOMEM;
+ goto err_probe;
+ }
+
+ q_spec->slave = spmi->sid;
+ q_spec->offset = res->start;
+ q_spec->gpio_chip_idx = i;
+
+ /* call into irq_domain to get irq mapping */
+ intspec[0] = q_chip->spmi->sid;
+ intspec[1] = (q_spec->offset >> 8) & 0xFF;
+ intspec[2] = 0;
+ q_spec->irq = irq_create_of_mapping(q_chip->int_ctrl,
+ intspec, 3);
+ if (!q_spec->irq) {
+ dev_err(&spmi->dev, "%s: invalid irq for gpio"
+ " %u\n", __func__, gpio);
+ ret = -EINVAL;
+ goto err_probe;
+ }
+ /* initialize lookup table entries */
+ qpnp_pmic_gpio_set_spec(q_chip, gpio, q_spec);
+ qpnp_chip_gpio_set_spec(q_chip, i, q_spec);
+ }
+
+ q_chip->gpio_chip.base = -1;
+ q_chip->gpio_chip.ngpio = spmi->num_dev_node;
+ q_chip->gpio_chip.label = "qpnp-gpio";
+ q_chip->gpio_chip.direction_input = qpnp_gpio_direction_input;
+ q_chip->gpio_chip.direction_output = qpnp_gpio_direction_output;
+ q_chip->gpio_chip.to_irq = qpnp_gpio_to_irq;
+ q_chip->gpio_chip.get = qpnp_gpio_get;
+ q_chip->gpio_chip.set = qpnp_gpio_set;
+ q_chip->gpio_chip.dev = &spmi->dev;
+ q_chip->gpio_chip.of_xlate = qpnp_gpio_of_gpio_xlate;
+ q_chip->gpio_chip.of_gpio_n_cells = 2;
+ q_chip->gpio_chip.can_sleep = 0;
+
+ rc = gpiochip_add(&q_chip->gpio_chip);
+ if (rc) {
+ dev_err(&spmi->dev, "%s: Can't add gpio chip, rc = %d\n",
+ __func__, rc);
+ ret = rc;
+ goto err_probe;
+ }
+
+ /* now configure gpio defaults if they exist */
+ for (i = 0; i < spmi->num_dev_node; i++) {
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, i);
+ if (WARN_ON(!q_spec))
+ return -ENODEV;
+
+ /* It's not an error to not config a default */
+ prop = of_get_property(spmi->dev_node[i].of_node,
+ "qcom,qpnp-gpio-cfg", &len);
+ /* 9 data values constitute one tuple */
+ if (prop && (len != (9 * sizeof(__be32)))) {
+ dev_err(&spmi->dev, "%s: invalid format for"
+ " qcom,qpnp-gpio-cfg property\n",
+ __func__);
+ ret = -EINVAL;
+ goto err_probe;
+ } else if (prop) {
+ rc = qpnp_gpio_config_default(spmi, prop,
+ q_chip->gpio_chip.base + i);
+ if (rc) {
+ ret = rc;
+ goto err_probe;
+ }
+ } else {
+ /* initialize with hardware defaults */
+ rc = spmi_ext_register_readl(
+ q_chip->spmi->ctrl, q_spec->slave,
+ Q_REG_ADDR(q_spec, Q_REG_IO_CTL1),
+ &q_spec->regs[Q_REG_I_IO_CTL1],
+ Q_NUM_CTL_REGS);
+ q_spec->regs[Q_REG_I_EN_CTL1] |=
+ (1 << Q_REG_MASTER_EN_SHIFT);
+ rc = spmi_ext_register_writel(
+ q_chip->spmi->ctrl, q_spec->slave,
+ Q_REG_ADDR(q_spec, Q_REG_EN_CTL1),
+ &q_spec->regs[Q_REG_EN_CTL1], 1);
+ if (rc) {
+ dev_err(&spmi->dev, "%s: spmi write"
+ " failed\n", __func__);
+ ret = rc;
+ goto err_probe;
+ }
+ }
+ }
+
+ dev_dbg(&spmi->dev, "%s: gpio_chip registered between %d-%u\n",
+ __func__, q_chip->gpio_chip.base,
+ (q_chip->gpio_chip.base + q_chip->gpio_chip.ngpio) - 1);
+ return 0;
+
+err_probe:
+ qpnp_gpio_free_chip(q_chip);
+ return ret;
+}
+
+static int qpnp_gpio_remove(struct spmi_device *spmi)
+{
+ struct qpnp_gpio_chip *q_chip = dev_get_drvdata(&spmi->dev);
+
+ return qpnp_gpio_free_chip(q_chip);
+}
+
+static struct of_device_id spmi_match_table[] = {
+ { .compatible = "qcom,qpnp-gpio",
+ },
+ {}
+};
+
+static const struct spmi_device_id qpnp_gpio_id[] = {
+ { "qcom,qpnp-gpio", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(spmi, qpnp_gpio_id);
+
+static struct spmi_driver qpnp_gpio_driver = {
+ .driver = {
+ .name = "qcom,qpnp-gpio",
+ .of_match_table = spmi_match_table,
+ },
+ .probe = qpnp_gpio_probe,
+ .remove = qpnp_gpio_remove,
+ .id_table = qpnp_gpio_id,
+};
+
+static int __init qpnp_gpio_init(void)
+{
+ return spmi_driver_register(&qpnp_gpio_driver);
+}
+
+static void __exit qpnp_gpio_exit(void)
+{
+}
+
+MODULE_AUTHOR(
+ "Michael Bohan <mbohan@codeaurora.org>");
+MODULE_DESCRIPTION("QPNP PMIC gpio driver");
+MODULE_LICENSE("GPLv2");
+
+module_init(qpnp_gpio_init);
+module_exit(qpnp_gpio_exit);
diff --git a/include/linux/qpnp/gpio.h b/include/linux/qpnp/gpio.h
new file mode 100644
index 0000000..0447a08
--- /dev/null
+++ b/include/linux/qpnp/gpio.h
@@ -0,0 +1,114 @@
+/* Copyright (c) 2012, Code Aurora Forum. 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 version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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 <mach/qpnp.h>
+
+#define QPNP_GPIO_DIR_OUT 1
+#define QPNP_GPIO_DIR_IN 2
+#define QPNP_GPIO_DIR_BOTH (QPNP_GPIO_DIR_OUT | QPNP_GPIO_DIR_IN)
+
+#define QPNP_GPIO_OUT_BUF_OPEN_DRAIN 1
+#define QPNP_GPIO_OUT_BUF_CMOS 0
+
+#define QPNP_GPIO_VIN0 0
+#define QPNP_GPIO_VIN1 1
+#define QPNP_GPIO_VIN2 2
+#define QPNP_GPIO_VIN3 3
+#define QPNP_GPIO_VIN4 4
+#define QPNP_GPIO_VIN5 5
+#define QPNP_GPIO_VIN6 6
+#define QPNP_GPIO_VIN7 7
+
+#define QPNP_GPIO_PULL_UP_30 0
+#define QPNP_GPIO_PULL_UP_1P5 1
+#define QPNP_GPIO_PULL_UP_31P5 2
+#define QPNP_GPIO_PULL_UP_1P5_30 3
+#define QPNP_GPIO_PULL_DN 4
+#define QPNP_GPIO_PULL_NO 5
+
+#define QPNP_GPIO_OUT_STRENGTH_LOW 1
+#define QPNP_GPIO_OUT_STRENGTH_MED 2
+#define QPNP_GPIO_OUT_STRENGTH_HIGH 3
+
+#define QPNP_GPIO_FUNC_NORMAL 0
+#define QPNP_GPIO_FUNC_PAIRED 1
+#define QPNP_GPIO_FUNC_1 2
+#define QPNP_GPIO_FUNC_2 3
+#define QPNP_GPIO_DTEST1 4
+#define QPNP_GPIO_DTEST2 5
+#define QPNP_GPIO_DTEST3 6
+#define QPNP_GPIO_DTEST4 7
+
+/**
+ * struct qpnp_gpio_cfg - structure to specify gpio configurtion values
+ * @direction: indicates whether the gpio should be input, output, or
+ * both. Should be of the type QPNP_GPIO_DIR_*
+ * @output_type: indicates gpio should be configured as CMOS or open
+ * drain. Should be of the type QPNP_GPIO_OUT_BUF_*
+ * @output_value: The gpio output value of the gpio line - 0 or 1
+ * @pull: Indicates whether a pull up or pull down should be
+ * applied. If a pullup is required the current strength
+ * needs to be specified. Current values of 30uA, 1.5uA,
+ * 31.5uA, 1.5uA with 30uA boost are supported. This value
+ * should be one of the QPNP_GPIO_PULL_*
+ * @vin_sel: specifies the voltage level when the output is set to 1.
+ * For an input gpio specifies the voltage level at which
+ * the input is interpreted as a logical 1.
+ * @out_strength: the amount of current supplied for an output gpio,
+ * should be of the type QPNP_GPIO_STRENGTH_*
+ * @source_sel: choose alternate function for the gpio. Certain gpios
+ * can be paired (shorted) with each other. Some gpio pin
+ * can act as alternate functions. This parameter should
+ * be of type QPNP_GPIO_FUNC_*
+ * @inv_int_pol: Invert polarity before feeding the line to the interrupt
+ * module in pmic. This feature will almost be never used
+ * since the pm8xxx interrupt block can detect both edges
+ * and both levels.
+ * @master_en: 1 = Enable features within the GPIO block based on
+ * configurations.
+ * 0 = Completely disable the GPIO block and let the pin
+ * float with high impedance regardless of other settings.
+ */
+struct qpnp_gpio_cfg {
+ int direction;
+ int output_type;
+ int output_value;
+ int pull;
+ int vin_sel;
+ int out_strength;
+ int src_select;
+ int inv_int_pol;
+ int master_en;
+};
+
+/**
+ * qpnp_gpio_config - Apply gpio configuration for Linux gpio
+ * @gpio: Linux gpio number to configure.
+ * @param: parameters to configure.
+ *
+ * This routine takes a Linux gpio number that corresponds with a
+ * PMIC gpio and applies the configuration specified in 'param'.
+ * This gpio number can be ascertained by of_get_gpio_flags() or
+ * the qpnp_gpio_map_gpio() API.
+ */
+int qpnp_gpio_config(int gpio, struct qpnp_gpio_cfg *param);
+
+/**
+ * qpnp_gpio_map_gpio - Obtain Linux GPIO number from device spec
+ * @slave_id: slave_id of the spmi_device for the gpio in question.
+ * @pmic_gpio: PMIC gpio number to lookup.
+ *
+ * This routine is used in legacy configurations that do not support
+ * Device Tree. If you are using Device Tree, you should not use this.
+ * For such cases, use of_get_gpio() instead.
+ */
+int qpnp_gpio_map_gpio(uint16_t slave_id, uint32_t pmic_gpio);