[PATCH v3 4/6] pinctrl: Qualcomm SPMI PMIC pin controller driver
From: Ivan T. Ivanov
Date: Mon Aug 11 2014 - 11:43:38 EST
From: "Ivan T. Ivanov" <iivanov@xxxxxxxxxx>
This is the pinctrl, pinmux, pinconf and gpiolib driver for the
Qualcomm GPIO and MPP sub-function blocks found in the PMIC chips.
Signed-off-by: Ivan T. Ivanov <iivanov@xxxxxxxxxx>
---
drivers/pinctrl/qcom/Kconfig | 12 +
drivers/pinctrl/qcom/Makefile | 1 +
drivers/pinctrl/qcom/pinctrl-spmi-pmic.c | 1532 ++++++++++++++++++++++++++++++
3 files changed, 1545 insertions(+)
create mode 100644 drivers/pinctrl/qcom/pinctrl-spmi-pmic.c
diff --git a/drivers/pinctrl/qcom/Kconfig b/drivers/pinctrl/qcom/Kconfig
index 6bd4e4d..8a24fae 100644
--- a/drivers/pinctrl/qcom/Kconfig
+++ b/drivers/pinctrl/qcom/Kconfig
@@ -39,6 +39,18 @@ config PINCTRL_MSM8X74
This is the pinctrl, pinmux, pinconf and gpiolib driver for the
Qualcomm TLMM block found in the Qualcomm 8974 platform.
+config PINCTRL_SPMI_PMIC
+ tristate "Qualcomm SPMI PMIC pin controller driver"
+ depends on GPIOLIB && OF
+ select PINMUX
+ select PINCONF
+ select GENERIC_PINCONF
+ help
+ This is the pinctrl, pinmux, pinconf and gpiolib driver for the
+ Qualcomm GPIO and MPP blocks found in the Qualcomm PMIC's chips,
+ which are using SPMI for communication with SoC. Example PMIC's
+ devices are pm8841, pm8941 and pma8084.
+
config PINCTRL_SSBI_PMIC
tristate "Qualcomm SSBI PMIC pin controller driver"
depends on GPIOLIB && OF
diff --git a/drivers/pinctrl/qcom/Makefile b/drivers/pinctrl/qcom/Makefile
index 8faa2ca..d098058 100644
--- a/drivers/pinctrl/qcom/Makefile
+++ b/drivers/pinctrl/qcom/Makefile
@@ -4,4 +4,5 @@ obj-$(CONFIG_PINCTRL_APQ8064) += pinctrl-apq8064.o
obj-$(CONFIG_PINCTRL_IPQ8064) += pinctrl-ipq8064.o
obj-$(CONFIG_PINCTRL_MSM8960) += pinctrl-msm8960.o
obj-$(CONFIG_PINCTRL_MSM8X74) += pinctrl-msm8x74.o
+obj-$(CONFIG_PINCTRL_SPMI_PMIC) += pinctrl-spmi-pmic.o
obj-$(CONFIG_PINCTRL_SSBI_PMIC) += pinctrl-ssbi-pmic.o
diff --git a/drivers/pinctrl/qcom/pinctrl-spmi-pmic.c b/drivers/pinctrl/qcom/pinctrl-spmi-pmic.c
new file mode 100644
index 0000000..ba19979
--- /dev/null
+++ b/drivers/pinctrl/qcom/pinctrl-spmi-pmic.c
@@ -0,0 +1,1532 @@
+/* Copyright (c) 2012-2014, The Linux Foundation. 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/export.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <dt-bindings/pinctrl/qcom,pmic-mpp.h>
+#include <dt-bindings/pinctrl/qcom,pmic-gpio.h>
+
+#include "../core.h"
+#include "../pinctrl-utils.h"
+
+/*
+ * Mode select - indicates whether the pin should be input, output, or both
+ * for GPIOs. MPP pins also support bidirectional, analog input, analog output
+ * and current sink.
+ */
+#define QPNP_PIN_MODE_DIG_IN 0
+#define QPNP_PIN_MODE_DIG_OUT 1
+#define QPNP_PIN_MODE_DIG_IN_OUT 2
+#define QPNP_PIN_MODE_BIDIR 3
+#define QPNP_PIN_MODE_AIN 4
+#define QPNP_PIN_MODE_AOUT 5
+#define QPNP_PIN_MODE_SINK 6
+
+/*
+ * Voltage select (GPIO, MPP) - 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
+ * To be used with "power-func = <>"
+ */
+#define QPNP_PIN_VIN_4CH_INVALID 5
+#define QPNP_PIN_VIN_8CH_INVALID 8
+
+/*
+ * Analog Output - Set the analog output reference.
+ * See PM8XXX_MPP_AOUT_XXX. To be used with "qcom,aout = <>"
+ */
+#define QPNP_MPP_AOUT_INVALID 8
+
+/*
+ * Analog Input - Set the func for analog input.
+ * See PM8XXX_MPP_AIN_XXX. To be used with "qcom,ain = <>"
+ */
+#define QPNP_MPP_AIN_INVALID 8
+
+/*
+ * Output type - indicates pin should be configured as CMOS or
+ * open drain.
+ */
+#define QPNP_GPIO_OUT_BUF_CMOS 0
+#define QPNP_GPIO_OUT_BUF_OPEN_DRAIN_NMOS 1
+#define QPNP_GPIO_OUT_BUF_OPEN_DRAIN_PMOS 2
+
+/*
+ * Pull Up Values - it indicates whether a pull up or pull down
+ * should be applied. If a pull-up is required the current strength needs
+ * to be specified. Current values of 30uA, 1.5uA, 31.5uA, 1.5uA with 30uA
+ * boost are supported.
+ * Note that the hardware ignores this configuration if the GPIO is not set
+ * to input or output open-drain mode.
+ */
+#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
+
+/*
+ * Pull Up Values - it indicates whether a pull-up should be
+ * applied for bidirectional mode only. The hardware ignores the
+ * configuration when operating in other modes.
+ */
+#define QPNP_MPP_PULL_UP_0P6KOHM 0
+#define QPNP_MPP_PULL_UP_10KOHM 1
+#define QPNP_MPP_PULL_UP_30KOHM 2
+#define QPNP_MPP_PULL_UP_OPEN 3
+
+/* Out Strength (GPIO) - the amount of current supplied for an output GPIO */
+#define QPNP_GPIO_STRENGTH_LOW 1
+#define QPNP_GPIO_STRENGTH_MED 2
+#define QPNP_GPIO_STRENGTH_HIGH 3
+
+/*
+ * Master enable (GPIO, MPP) - Enable features within the pin block based on
+ * configurations. QPNP_PIN_MASTER_DISABLE = Completely disable the pin
+ * lock and let the pin float with high impedance regardless of other settings.
+ */
+#define QPNP_PIN_MASTER_DISABLE 0
+#define QPNP_PIN_MASTER_ENABLE 1
+
+/* revision registers base address offsets */
+#define QPNP_REG_DIG_MINOR_REV 0x0
+#define QPNP_REG_DIG_MAJOR_REV 0x1
+#define QPNP_REG_ANA_MINOR_REV 0x2
+
+/* type registers base address offsets */
+#define QPNP_REG_TYPE 0x4
+#define QPNP_REG_SUBTYPE 0x5
+
+/* GPIO peripheral type and subtype values */
+#define QPNP_GPIO_TYPE 0x10
+#define QPNP_GPIO_SUBTYPE_GPIO_4CH 0x1
+#define QPNP_GPIO_SUBTYPE_GPIOC_4CH 0x5
+#define QPNP_GPIO_SUBTYPE_GPIO_8CH 0x9
+#define QPNP_GPIO_SUBTYPE_GPIOC_8CH 0xd
+
+/* mpp peripheral type and subtype values */
+#define QPNP_MPP_TYPE 0x11
+#define QPNP_MPP_SUBTYPE_4CH_NO_ANA_OUT 0x3
+#define QPNP_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT 0x4
+#define QPNP_MPP_SUBTYPE_4CH_NO_SINK 0x5
+#define QPNP_MPP_SUBTYPE_ULT_4CH_NO_SINK 0x6
+#define QPNP_MPP_SUBTYPE_4CH_FULL_FUNC 0x7
+#define QPNP_MPP_SUBTYPE_8CH_FULL_FUNC 0xf
+
+#define QPNP_REG_STATUS1 0x8
+#define QPNP_REG_STATUS1_VAL_MASK 0x1
+#define QPNP_REG_STATUS1_GPIO_EN_REV0_MASK 0x2
+#define QPNP_REG_STATUS1_GPIO_EN_MASK 0x80
+#define QPNP_REG_STATUS1_MPP_EN_MASK 0x80
+
+/* control register base address offsets */
+#define QPNP_REG_MODE_CTL 0x40
+#define QPNP_REG_DIG_VIN_CTL 0x41
+#define QPNP_REG_DIG_PULL_CTL 0x42
+#define QPNP_REG_DIG_IN_CTL 0x43
+#define QPNP_REG_DIG_OUT_CTL 0x45
+#define QPNP_REG_EN_CTL 0x46
+#define QPNP_REG_AOUT_CTL 0x4b
+#define QPNP_REG_AIN_CTL 0x4a
+#define QPNP_REG_SINK_CTL 0x4c
+
+/* QPNP_REG_MODE_CTL */
+#define QPNP_REG_MODE_INVERT_SHIFT 0
+#define QPNP_REG_MODE_INVERT_MASK 0x1
+#define QPNP_REG_MODE_FUNCTION_SHIFT 1
+#define QPNP_REG_MODE_FUNCTION_MASK 0x6
+#define QPNP_REG_MODE_SEL_SHIFT 4
+#define QPNP_REG_MODE_SEL_MASK 0x70
+
+/* QPNP_REG_DIG_VIN_CTL */
+#define QPNP_REG_VIN_SHIFT 0
+#define QPNP_REG_VIN_MASK 0x7
+
+/* QPNP_REG_DIG_PULL_CTL */
+#define QPNP_REG_PULL_SHIFT 0
+#define QPNP_REG_PULL_MASK 0x7
+
+/* QPNP_REG_DIG_OUT_CTL */
+#define QPNP_REG_OUT_STRENGTH_SHIFT 0
+#define QPNP_REG_OUT_STRENGTH_MASK 0x3
+#define QPNP_REG_OUT_TYPE_SHIFT 4
+#define QPNP_REG_OUT_TYPE_MASK 0x30
+
+/* QPNP_REG_EN_CTL */
+#define QPNP_REG_MASTER_EN_SHIFT 7
+#define QPNP_REG_MASTER_EN_MASK 0x80
+
+/* QPNP_REG_AOUT_CTL */
+#define QPNP_REG_AOUT_REF_SHIFT 0
+#define QPNP_REG_AOUT_REF_MASK 0x7
+
+/* QPNP_REG_AIN_CTL */
+#define QPNP_REG_AIN_ROUTE_SHIFT 0
+#define QPNP_REG_AIN_ROUTE_MASK 0x7
+
+/* QPNP_REG_SINK_CTL */
+#define QPNP_REG_CS_OUT_SHIFT 0
+#define QPNP_REG_CS_OUT_MASK 0x7
+
+#define QPNP_PIN_PHYSICAL_OFFSET 1
+
+/* Qualcomm specific pin configurations */
+#define QPNP_PINCONF_PULL_UP (PIN_CONFIG_END + 1)
+#define QPNP_PINCONF_STRENGTH (PIN_CONFIG_END + 2)
+#define QPNP_PINCONF_AMUX_ROUTE (PIN_CONFIG_END + 3)
+#define QPNP_PINCONF_VREFENCE (PIN_CONFIG_END + 4)
+#define QPNP_PINCONF_MODE (PIN_CONFIG_END + 5)
+
+struct qpnp_chipinfo {
+ unsigned npins;
+ unsigned base;
+ unsigned type;
+};
+
+struct qpnp_padinfo {
+ u16 offset; /* address offset in SPMI device */
+ int irq;
+ const char *name; /* pin name */
+ unsigned int modes; /* supported modes: DI, DO, DIO, AI, AO... */
+ unsigned int type; /* peripheral hardware type */
+ unsigned int subtype; /* peripheral hardware subtype */
+ unsigned int major; /* digital major version */
+};
+
+#define QPNP_REG_ADDR(pad, reg) ((pad)->offset + reg)
+#define QPNP_GET(buff, shift, mask) ((buff & mask) >> shift)
+
+struct qpnp_pinctrl {
+ struct device *dev;
+ struct regmap *map;
+ struct pinctrl_dev *ctrl;
+ struct pinctrl_desc desc;
+ struct gpio_chip chip;
+
+ struct qpnp_padinfo *pads;
+ const struct qpnp_chipinfo *info;
+ const char *const *groups;
+ const char *const *functions;
+};
+
+static inline struct qpnp_pinctrl *to_qpnp_pinctrl(struct gpio_chip *chip)
+{
+ return container_of(chip, struct qpnp_pinctrl, chip);
+};
+
+struct qpnp_pinbindings {
+ const char *property;
+ unsigned param;
+ u32 default_value;
+};
+
+struct qpnp_pinattrib {
+ unsigned addr;
+ unsigned shift;
+ unsigned mask;
+ unsigned val;
+};
+
+static struct qpnp_pinbindings qpnp_pinbindings[] = {
+ /* QCOM_BIAS_PULL_UP_30... */
+ {"qcom,pull-up-strength", QPNP_PINCONF_PULL_UP, 0},
+ /* QCOM_DRIVE_STRENGTH_NO... */
+ {"qcom,drive-strength", QPNP_PINCONF_STRENGTH, 0},
+ /* PMIC_MPP_AMUX_ROUTE_CH5 ... */
+ {"qcom,amux-route", QPNP_PINCONF_AMUX_ROUTE, 0},
+ /* PMIC_MPP_VREFERENCE_1V25 ... */
+ {"qcom,vrefence", QPNP_PINCONF_VREFENCE, 0},
+ /* PMIC_MPP_MODE.. */
+ {"qcom,mode", QPNP_PINCONF_MODE, 0},
+};
+
+static const char * const qpnp_gpio_groups[] = {
+ "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6", "gpio7", "gpio8",
+ "gpio9", "gpio10", "gpio11", "gpio12", "gpio13", "gpio14", "gpio15",
+ "gpio16", "gpio17", "gpio18", "gpio19", "gpio20", "gpio21", "gpio22",
+ "gpio23", "gpio24", "gpio25", "gpio26", "gpio27", "gpio28", "gpio29",
+ "gpio30", "gpio31", "gpio32", "gpio33", "gpio34", "gpio35", "gpio36",
+};
+
+static const char * const qpnp_mpp_groups[] = {
+ "mpp1", "mpp2", "mpp3", "mpp4", "mpp5", "mpp6", "mpp7", "mpp8",
+};
+
+static const char * const qpnp_gpio_functions[] = {
+ PMIC_GPIO_FUNC_NORMAL, PMIC_GPIO_FUNC_PAIRED,
+ PMIC_GPIO_FUNC_FUNC1, PMIC_GPIO_FUNC_FUNC2,
+ PMIC_GPIO_FUNC_DTEST1, PMIC_GPIO_FUNC_DTEST2,
+ PMIC_GPIO_FUNC_DTEST3, PMIC_GPIO_FUNC_DTEST4,
+};
+
+static const char * const qpnp_mpp_functions[] = {
+ PMIC_MPP_FUNC_NORMAL, PMIC_MPP_FUNC_PAIRED,
+ "reserved1", "reserved2",
+ PMIC_MPP_FUNC_DTEST1, PMIC_MPP_FUNC_DTEST2,
+ PMIC_MPP_FUNC_DTEST3, PMIC_MPP_FUNC_DTEST4,
+};
+
+static inline struct qpnp_padinfo *qpnp_get_desc(struct qpnp_pinctrl *qctrl,
+ unsigned pin)
+{
+ if (pin >= qctrl->desc.npins) {
+ dev_warn(qctrl->dev, "invalid pin number %d", pin);
+ return NULL;
+ }
+
+ return &qctrl->pads[pin];
+}
+
+static int qpnp_conv_to_pin(struct qpnp_pinctrl *qctrl,
+ struct qpnp_padinfo *pad, unsigned param,
+ unsigned val)
+{
+ struct qpnp_pinattrib attr[3];
+ unsigned int type, subtype;
+ int nattrs = 1, idx, ret;
+
+ type = pad->type;
+ subtype = pad->subtype;
+
+ switch (param) {
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ if (type != QPNP_GPIO_TYPE)
+ return -ENXIO;
+ attr[0].addr = QPNP_REG_DIG_OUT_CTL;
+ attr[0].shift = QPNP_REG_OUT_TYPE_SHIFT;
+ attr[0].mask = QPNP_REG_OUT_TYPE_MASK;
+ attr[0].val = QPNP_GPIO_OUT_BUF_CMOS;
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ if (type != QPNP_GPIO_TYPE)
+ return -ENXIO;
+ if (subtype == QPNP_GPIO_SUBTYPE_GPIOC_4CH ||
+ subtype == QPNP_GPIO_SUBTYPE_GPIOC_8CH)
+ return -EINVAL;
+ attr[0].addr = QPNP_REG_DIG_OUT_CTL;
+ attr[0].shift = QPNP_REG_OUT_TYPE_SHIFT;
+ attr[0].mask = QPNP_REG_OUT_TYPE_MASK;
+ attr[0].val = QPNP_GPIO_OUT_BUF_OPEN_DRAIN_NMOS;
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_SOURCE:
+ if (type != QPNP_GPIO_TYPE)
+ return -ENXIO;
+ if (subtype == QPNP_GPIO_SUBTYPE_GPIOC_4CH ||
+ subtype == QPNP_GPIO_SUBTYPE_GPIOC_8CH)
+ return -EINVAL;
+ attr[0].addr = QPNP_REG_DIG_OUT_CTL;
+ attr[0].shift = QPNP_REG_OUT_TYPE_SHIFT;
+ attr[0].mask = QPNP_REG_OUT_TYPE_MASK;
+ attr[0].val = QPNP_GPIO_OUT_BUF_OPEN_DRAIN_PMOS;
+ break;
+ case PIN_CONFIG_BIAS_DISABLE:
+ attr[0].addr = QPNP_REG_DIG_PULL_CTL;
+ attr[0].shift = QPNP_REG_PULL_SHIFT;
+ attr[0].mask = QPNP_REG_PULL_MASK;
+ if (type == QPNP_GPIO_TYPE)
+ attr[0].val = QPNP_GPIO_PULL_NO;
+ else
+ attr[0].val = QPNP_MPP_PULL_UP_OPEN;
+ break;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ if (type != QPNP_MPP_TYPE)
+ return -EINVAL;
+ switch (val) {
+ case 0:
+ val = QPNP_MPP_PULL_UP_OPEN;
+ break;
+ case 600:
+ val = QPNP_MPP_PULL_UP_0P6KOHM;
+ break;
+ case 10000:
+ val = QPNP_MPP_PULL_UP_10KOHM;
+ break;
+ case 30000:
+ val = QPNP_MPP_PULL_UP_30KOHM;
+ break;
+ default:
+ return -EINVAL;
+ }
+ attr[0].addr = QPNP_REG_DIG_PULL_CTL;
+ attr[0].shift = QPNP_REG_PULL_SHIFT;
+ attr[0].mask = QPNP_REG_PULL_MASK;
+ attr[0].val = val;
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ if (type != QPNP_GPIO_TYPE)
+ return -EINVAL;
+ attr[0].addr = QPNP_REG_DIG_PULL_CTL;
+ attr[0].shift = QPNP_REG_PULL_SHIFT;
+ attr[0].mask = QPNP_REG_PULL_MASK;
+ if (val)
+ attr[0].val = QPNP_GPIO_PULL_DN;
+ else
+ attr[0].val = QPNP_GPIO_PULL_NO;
+ break;
+ case PIN_CONFIG_POWER_SOURCE:
+ if (val >= QPNP_PIN_VIN_8CH_INVALID)
+ return -EINVAL;
+ if (val >= QPNP_PIN_VIN_4CH_INVALID) {
+ if (type == QPNP_GPIO_TYPE &&
+ (subtype == QPNP_GPIO_SUBTYPE_GPIO_4CH ||
+ subtype == QPNP_GPIO_SUBTYPE_GPIOC_4CH))
+ return -EINVAL;
+ if (type == QPNP_MPP_TYPE &&
+ (subtype == QPNP_MPP_SUBTYPE_4CH_NO_ANA_OUT ||
+ subtype == QPNP_MPP_SUBTYPE_4CH_NO_SINK ||
+ subtype == QPNP_MPP_SUBTYPE_4CH_FULL_FUNC ||
+ subtype == QPNP_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT ||
+ subtype == QPNP_MPP_SUBTYPE_ULT_4CH_NO_SINK))
+ return -EINVAL;
+ }
+ attr[0].addr = QPNP_REG_DIG_VIN_CTL;
+ attr[0].shift = QPNP_REG_VIN_SHIFT;
+ attr[0].mask = QPNP_REG_VIN_MASK;
+ attr[0].val = val;
+ break;
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ if (type != QPNP_MPP_TYPE)
+ return -EINVAL;
+ if (subtype == QPNP_MPP_SUBTYPE_4CH_NO_SINK ||
+ subtype == QPNP_MPP_SUBTYPE_ULT_4CH_NO_SINK)
+ return -ENXIO;
+ if (val > 50) /* mA */
+ return -EINVAL;
+ attr[0].addr = QPNP_REG_SINK_CTL;
+ attr[0].shift = QPNP_REG_CS_OUT_SHIFT;
+ attr[0].mask = QPNP_REG_CS_OUT_MASK;
+ attr[0].val = (val / 5) - 1;
+ break;
+ case PIN_CONFIG_INPUT_ENABLE:
+ nattrs = 2;
+ attr[0].addr = QPNP_REG_MODE_CTL;
+ attr[0].shift = QPNP_REG_MODE_SEL_SHIFT;
+ attr[0].mask = QPNP_REG_MODE_SEL_MASK;
+ attr[0].val = QPNP_PIN_MODE_DIG_IN;
+ attr[1].addr = QPNP_REG_EN_CTL;
+ attr[1].shift = QPNP_REG_MASTER_EN_SHIFT;
+ attr[1].mask = QPNP_REG_MASTER_EN_MASK;
+ attr[1].val = 1;
+ if (val)
+ break;
+ /* Fallthrough */
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ attr[1].addr = QPNP_REG_EN_CTL;
+ attr[1].shift = QPNP_REG_MASTER_EN_SHIFT;
+ attr[1].mask = QPNP_REG_MASTER_EN_MASK;
+ attr[1].val = 0;
+ break;
+ case PIN_CONFIG_OUTPUT:
+ nattrs = 3;
+ attr[0].addr = QPNP_REG_MODE_CTL;
+ attr[0].shift = QPNP_REG_MODE_INVERT_SHIFT;
+ attr[0].mask = QPNP_REG_MODE_INVERT_MASK;
+ attr[0].val = !!val;
+ attr[1].addr = QPNP_REG_MODE_CTL;
+ attr[1].shift = QPNP_REG_MODE_SEL_SHIFT;
+ attr[1].mask = QPNP_REG_MODE_SEL_MASK;
+ attr[1].val = QPNP_PIN_MODE_DIG_OUT;
+ attr[2].addr = QPNP_REG_EN_CTL;
+ attr[2].shift = QPNP_REG_MASTER_EN_SHIFT;
+ attr[2].mask = QPNP_REG_MASTER_EN_MASK;
+ attr[2].val = 1;
+ break;
+ case QPNP_PINCONF_PULL_UP:
+ if (type != QPNP_GPIO_TYPE)
+ return -EINVAL;
+ switch (val) {
+ default:
+ return -EINVAL;
+ case 0:
+ val = QPNP_GPIO_PULL_NO;
+ break;
+ case PMIC_GPIO_PULL_UP_30:
+ val = QPNP_GPIO_PULL_UP_30;
+ break;
+ case PMIC_GPIO_PULL_UP_1P5:
+ val = QPNP_GPIO_PULL_UP_1P5;
+ break;
+ case PMIC_GPIO_PULL_UP_31P5:
+ val = QPNP_GPIO_PULL_UP_31P5;
+ break;
+ case PMIC_GPIO_PULL_UP_1P5_30:
+ val = QPNP_GPIO_PULL_UP_1P5_30;
+ break;
+ }
+ attr[0].addr = QPNP_REG_DIG_PULL_CTL;
+ attr[0].shift = QPNP_REG_PULL_SHIFT;
+ attr[0].mask = QPNP_REG_PULL_MASK;
+ attr[0].val = val;
+ break;
+ case QPNP_PINCONF_STRENGTH:
+ if (type != QPNP_GPIO_TYPE)
+ return -EINVAL;
+ switch (val) {
+ default:
+ case PMIC_GPIO_STRENGTH_NO:
+ return -EINVAL;
+ case PMIC_GPIO_STRENGTH_LOW:
+ attr[0].val = QPNP_GPIO_STRENGTH_LOW;
+ break;
+ case PMIC_GPIO_STRENGTH_MED:
+ attr[0].val = QPNP_GPIO_STRENGTH_MED;
+ break;
+ case PMIC_GPIO_STRENGTH_HIGH:
+ attr[0].val = QPNP_GPIO_STRENGTH_HIGH;
+ break;
+ }
+ attr[0].addr = QPNP_REG_DIG_OUT_CTL;
+ attr[0].shift = QPNP_REG_OUT_STRENGTH_SHIFT;
+ attr[0].mask = QPNP_REG_OUT_STRENGTH_MASK;
+ break;
+ case QPNP_PINCONF_VREFENCE:
+ if (type != QPNP_MPP_TYPE)
+ return -ENXIO;
+ if (val >= QPNP_MPP_AOUT_INVALID)
+ return -EINVAL;
+ if (subtype == QPNP_MPP_SUBTYPE_4CH_NO_ANA_OUT ||
+ subtype == QPNP_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT)
+ return -ENXIO;
+ attr[0].addr = QPNP_REG_AOUT_CTL;
+ attr[0].shift = QPNP_REG_AOUT_REF_SHIFT;
+ attr[0].mask = QPNP_REG_AOUT_REF_MASK;
+ attr[0].val = val;
+ break;
+ case QPNP_PINCONF_AMUX_ROUTE:
+ if (type != QPNP_MPP_TYPE)
+ return -ENXIO;
+ if (val >= QPNP_MPP_AIN_INVALID)
+ return -EINVAL;
+ attr[0].addr = QPNP_REG_AIN_CTL;
+ attr[0].shift = QPNP_REG_AIN_ROUTE_SHIFT;
+ attr[0].mask = QPNP_REG_AIN_ROUTE_MASK;
+ attr[0].val = val;
+ break;
+ case QPNP_PINCONF_MODE:
+ if ((pad->modes & BIT(val)) == 0)
+ return -ENXIO;
+ attr[0].addr = QPNP_REG_MODE_CTL;
+ attr[0].shift = QPNP_REG_MODE_SEL_SHIFT;
+ attr[0].mask = QPNP_REG_MODE_SEL_MASK;
+ attr[0].val = val;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ for (idx = 0; idx < nattrs; idx++) {
+ /* add base offset */
+ attr[idx].addr = QPNP_REG_ADDR(pad, attr[idx].addr);
+ ret = regmap_update_bits(qctrl->map, attr[idx].addr,
+ attr[idx].mask,
+ attr[idx].val << attr[idx].shift);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+
+static int qpnp_conv_from_pin(struct qpnp_pinctrl *qctrl,
+ struct qpnp_padinfo *pad,
+ unsigned param, unsigned *val)
+{
+ struct qpnp_pinattrib attr;
+ unsigned int type, subtype, field;
+ unsigned int addr, buff;
+ int ret;
+
+ *val = 0;
+ type = pad->type;
+ subtype = pad->subtype;
+
+ switch (param) {
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ case PIN_CONFIG_DRIVE_OPEN_SOURCE:
+ if (type != QPNP_GPIO_TYPE)
+ return -ENXIO;
+ attr.addr = QPNP_REG_DIG_OUT_CTL;
+ attr.shift = QPNP_REG_OUT_TYPE_SHIFT;
+ attr.mask = QPNP_REG_OUT_TYPE_MASK;
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ if (type != QPNP_GPIO_TYPE)
+ return -ENXIO;
+ /* Fallthrough */
+ case PIN_CONFIG_BIAS_DISABLE:
+ case PIN_CONFIG_BIAS_PULL_UP:
+ attr.addr = QPNP_REG_DIG_PULL_CTL;
+ attr.shift = QPNP_REG_PULL_SHIFT;
+ attr.mask = QPNP_REG_PULL_MASK;
+ break;
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ attr.addr = QPNP_REG_EN_CTL;
+ attr.shift = QPNP_REG_MASTER_EN_SHIFT;
+ attr.mask = QPNP_REG_MASTER_EN_MASK;
+ break;
+ case PIN_CONFIG_POWER_SOURCE:
+ attr.addr = QPNP_REG_DIG_VIN_CTL;
+ attr.shift = QPNP_REG_VIN_SHIFT;
+ attr.mask = QPNP_REG_VIN_MASK;
+ break;
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ if (type != QPNP_MPP_TYPE)
+ return -ENXIO;
+ attr.addr = QPNP_REG_SINK_CTL;
+ attr.shift = QPNP_REG_CS_OUT_SHIFT;
+ attr.mask = QPNP_REG_CS_OUT_MASK;
+ break;
+ case PIN_CONFIG_INPUT_ENABLE:
+ attr.addr = QPNP_REG_EN_CTL;
+ attr.shift = QPNP_REG_MASTER_EN_SHIFT;
+ attr.mask = QPNP_REG_MASTER_EN_MASK;
+ break;
+ case PIN_CONFIG_OUTPUT:
+ attr.addr = QPNP_REG_MODE_CTL;
+ attr.shift = QPNP_REG_MODE_INVERT_SHIFT;
+ attr.mask = QPNP_REG_MODE_INVERT_MASK;
+ break;
+ case QPNP_PINCONF_PULL_UP:
+ if (type != QPNP_GPIO_TYPE)
+ return -ENXIO;
+ attr.addr = QPNP_REG_DIG_PULL_CTL;
+ attr.shift = QPNP_REG_PULL_SHIFT;
+ attr.mask = QPNP_REG_PULL_MASK;
+ break;
+ case QPNP_PINCONF_STRENGTH:
+ if (type != QPNP_GPIO_TYPE)
+ return -ENXIO;
+ attr.addr = QPNP_REG_DIG_OUT_CTL;
+ attr.shift = QPNP_REG_OUT_STRENGTH_SHIFT;
+ attr.mask = QPNP_REG_OUT_STRENGTH_MASK;
+ break;
+ case QPNP_PINCONF_VREFENCE:
+ if (type != QPNP_MPP_TYPE)
+ return -ENXIO;
+ if (subtype == QPNP_MPP_SUBTYPE_4CH_NO_ANA_OUT ||
+ subtype == QPNP_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT)
+ return -ENXIO;
+ attr.addr = QPNP_REG_AOUT_CTL;
+ attr.shift = QPNP_REG_AOUT_REF_SHIFT;
+ attr.mask = QPNP_REG_AOUT_REF_MASK;
+ break;
+ case QPNP_PINCONF_AMUX_ROUTE:
+ if (type != QPNP_MPP_TYPE)
+ return -ENXIO;
+ attr.addr = QPNP_REG_AIN_CTL;
+ attr.shift = QPNP_REG_AIN_ROUTE_SHIFT;
+ attr.mask = QPNP_REG_AIN_ROUTE_MASK;
+ break;
+ case QPNP_PINCONF_MODE:
+ attr.addr = QPNP_REG_MODE_CTL;
+ attr.shift = QPNP_REG_MODE_SEL_SHIFT;
+ attr.mask = QPNP_REG_MODE_SEL_MASK;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ addr = QPNP_REG_ADDR(pad, attr.addr);
+ ret = regmap_read(qctrl->map, addr, &buff);
+ if (ret < 0)
+ return ret;
+
+ field = QPNP_GET(buff, attr.shift, attr.mask);
+
+ switch (param) {
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ if (field == QPNP_GPIO_OUT_BUF_CMOS)
+ *val = 1;
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ if (field == QPNP_GPIO_OUT_BUF_OPEN_DRAIN_NMOS)
+ *val = 1;
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_SOURCE:
+ if (field == QPNP_GPIO_OUT_BUF_OPEN_DRAIN_PMOS)
+ *val = 1;
+ break;
+ case PIN_CONFIG_BIAS_DISABLE:
+ if (type == QPNP_GPIO_TYPE) {
+ if (field == QPNP_GPIO_PULL_NO)
+ *val = 1;
+ } else {
+ if (field == QPNP_MPP_PULL_UP_OPEN)
+ *val = 1;
+ }
+ break;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ switch (field) {
+ default:
+ case QPNP_MPP_PULL_UP_OPEN:
+ *val = 0;
+ break;
+ case QPNP_MPP_PULL_UP_0P6KOHM:
+ *val = 600;
+ break;
+ case QPNP_MPP_PULL_UP_10KOHM:
+ *val = 10000;
+ break;
+ case QPNP_MPP_PULL_UP_30KOHM:
+ *val = 30000;
+ break;
+ }
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ if (field == QPNP_GPIO_PULL_DN)
+ *val = 1;
+ break;
+ case PIN_CONFIG_POWER_SOURCE:
+ *val = field;
+ break;
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ *val = (field + 1) * 5;
+ break;
+ case PIN_CONFIG_INPUT_ENABLE:
+ *val = field;
+ break;
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ if (field == QPNP_PIN_MASTER_DISABLE)
+ *val = 1;
+ break;
+ case PIN_CONFIG_OUTPUT:
+ *val = field;
+ break;
+ case QPNP_PINCONF_PULL_UP:
+ switch (field) {
+ case QPNP_GPIO_PULL_NO:
+ field = 0;
+ break;
+ case QPNP_GPIO_PULL_UP_30:
+ field = PMIC_GPIO_PULL_UP_30;
+ break;
+ case QPNP_GPIO_PULL_UP_1P5:
+ field = PMIC_GPIO_PULL_UP_1P5;
+ break;
+ case QPNP_GPIO_PULL_UP_31P5:
+ field = PMIC_GPIO_PULL_UP_31P5;
+ break;
+
+ case QPNP_GPIO_PULL_UP_1P5_30:
+ field = PMIC_GPIO_PULL_UP_1P5_30;
+ break;
+ }
+ *val = field;
+ break;
+ case QPNP_PINCONF_STRENGTH:
+ switch (field) {
+ case QPNP_GPIO_STRENGTH_HIGH:
+ field = PMIC_GPIO_STRENGTH_HIGH;
+ break;
+ case QPNP_GPIO_STRENGTH_MED:
+ field = PMIC_GPIO_STRENGTH_MED;
+ break;
+ case QPNP_GPIO_STRENGTH_LOW:
+ field = PMIC_GPIO_STRENGTH_LOW;
+ break;
+ }
+ *val = field;
+ break;
+ case QPNP_PINCONF_MODE:
+ case QPNP_PINCONF_VREFENCE:
+ case QPNP_PINCONF_AMUX_ROUTE:
+ *val = field;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qpnp_get_groups_count(struct pinctrl_dev *pctldev)
+{
+ struct qpnp_pinctrl *qpctrl = pinctrl_dev_get_drvdata(pctldev);
+
+ /* Every PIN is a group */
+ return qpctrl->desc.npins;
+}
+
+static const char *qpnp_get_group_name(struct pinctrl_dev *pctldev,
+ unsigned pin)
+{
+ struct qpnp_pinctrl *qpctrl = pinctrl_dev_get_drvdata(pctldev);
+
+ /* Every PIN is a group */
+ return qpctrl->desc.pins[pin].name;
+}
+
+static int qpnp_get_group_pins(struct pinctrl_dev *pctldev,
+ unsigned pin,
+ const unsigned **pins,
+ unsigned *num_pins)
+{
+ struct qpnp_pinctrl *qpctrl = pinctrl_dev_get_drvdata(pctldev);
+
+ /* Every PIN is a group */
+ *pins = &qpctrl->desc.pins[pin].number;
+ *num_pins = 1;
+ return 0;
+}
+
+static int qpnp_parse_dt_config(struct device_node *np,
+ struct pinctrl_dev *pctldev,
+ unsigned long **configs, unsigned int *nconfs)
+{
+ struct qpnp_pinbindings *par;
+ unsigned long cfg;
+ int ret, idx;
+ u32 val;
+
+ for (idx = 0; idx < ARRAY_SIZE(qpnp_pinbindings); idx++) {
+
+ par = &qpnp_pinbindings[idx];
+ ret = of_property_read_u32(np, par->property, &val);
+
+ /* property not found */
+ if (ret == -EINVAL)
+ continue;
+
+ /* use default value, when no value is specified */
+ if (ret)
+ val = par->default_value;
+
+ dev_dbg(pctldev->dev, "found %s with value %u\n",
+ par->property, val);
+
+ cfg = pinconf_to_config_packed(par->param, val);
+
+ ret = pinctrl_utils_add_config(pctldev, configs, nconfs, cfg);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int qpnp_dt_subnode_to_map(struct pinctrl_dev *pctldev,
+ struct device_node *np,
+ struct pinctrl_map **map,
+ unsigned *reserv, unsigned *nmaps,
+ enum pinctrl_map_type type)
+{
+ unsigned long *configs = NULL;
+ unsigned nconfs = 0;
+ struct property *prop;
+ const char *group;
+ int ret;
+
+ ret = qpnp_parse_dt_config(np, pctldev, &configs, &nconfs);
+ if (ret < 0)
+ return ret;
+
+ if (!nconfs)
+ return 0;
+
+ ret = of_property_count_strings(np, "pins");
+ if (ret < 0)
+ goto exit;
+
+ ret = pinctrl_utils_reserve_map(pctldev, map, reserv,
+ nmaps, ret);
+ if (ret < 0)
+ goto exit;
+
+ of_property_for_each_string(np, "pins", prop, group) {
+ ret = pinctrl_utils_add_map_configs(pctldev, map,
+ reserv, nmaps, group, configs, nconfs, type);
+ if (ret < 0)
+ break;
+ }
+exit:
+ kfree(configs);
+ return ret;
+}
+
+static int qpnp_dt_node_to_map(struct pinctrl_dev *pctldev,
+ struct device_node *np_config,
+ struct pinctrl_map **map,
+ unsigned *nmaps)
+{
+ struct device_node *np;
+ enum pinctrl_map_type type;
+ unsigned reserv;
+ int ret;
+
+ ret = 0;
+ *map = NULL;
+ *nmaps = 0;
+ reserv = 0;
+ type = PIN_MAP_TYPE_CONFIGS_GROUP;
+
+ for_each_child_of_node(np_config, np) {
+
+ ret = pinconf_generic_dt_subnode_to_map(pctldev, np, map,
+ &reserv, nmaps, type);
+ if (ret)
+ break;
+
+ ret = qpnp_dt_subnode_to_map(pctldev, np, map, &reserv,
+ nmaps, type);
+ if (ret)
+ break;
+ }
+
+ if (ret < 0)
+ pinctrl_utils_dt_free_map(pctldev, *map, *nmaps);
+
+ return ret;
+}
+
+static const struct pinctrl_ops qpnp_pinctrl_ops = {
+ .get_groups_count = qpnp_get_groups_count,
+ .get_group_name = qpnp_get_group_name,
+ .get_group_pins = qpnp_get_group_pins,
+ .dt_node_to_map = qpnp_dt_node_to_map,
+ .dt_free_map = pinctrl_utils_dt_free_map,
+};
+
+static int qpnp_get_functions_count(struct pinctrl_dev *pctldev)
+{
+ return ARRAY_SIZE(qpnp_gpio_functions);
+}
+
+static const char *qpnp_get_function_name(struct pinctrl_dev *pctldev,
+ unsigned function)
+{
+ struct qpnp_pinctrl *qctrl = pinctrl_dev_get_drvdata(pctldev);
+
+ return qctrl->functions[function];
+}
+
+static int qpnp_get_function_groups(struct pinctrl_dev *pctldev,
+ unsigned function,
+ const char *const **groups,
+ unsigned *const num_qgroups)
+{
+ struct qpnp_pinctrl *qctrl = pinctrl_dev_get_drvdata(pctldev);
+
+ *groups = qctrl->groups;
+ *num_qgroups = qctrl->desc.npins;
+ return 0;
+}
+
+static int qpnp_pinmux_enable(struct pinctrl_dev *pctldev,
+ unsigned function,
+ unsigned pin)
+{
+ struct qpnp_pinctrl *qctrl = pinctrl_dev_get_drvdata(pctldev);
+ struct qpnp_padinfo *pad;
+ unsigned int addr, val, mask;
+ int ret;
+
+ pad = qpnp_get_desc(qctrl, pin);
+ if (!pad)
+ return -EINVAL;
+
+ addr = QPNP_REG_ADDR(pad, QPNP_REG_MODE_CTL);
+ val = function << QPNP_REG_MODE_FUNCTION_SHIFT;
+ mask = QPNP_REG_MODE_FUNCTION_MASK;
+ ret = regmap_update_bits(qctrl->map, addr, mask, val);
+ if (ret)
+ return ret;
+
+ addr = QPNP_REG_ADDR(pad, QPNP_REG_EN_CTL);
+ val = BIT(QPNP_REG_MASTER_EN_SHIFT);
+ mask = QPNP_REG_MASTER_EN_MASK;
+ ret = regmap_update_bits(qctrl->map, addr, mask, val);
+
+ return ret;
+}
+
+static const struct pinmux_ops qpnp_pinmux_ops = {
+ .get_functions_count = qpnp_get_functions_count,
+ .get_function_name = qpnp_get_function_name,
+ .get_function_groups = qpnp_get_function_groups,
+ .enable = qpnp_pinmux_enable,
+};
+
+static int qpnp_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct qpnp_pinctrl *qctrl = to_qpnp_pinctrl(chip);
+ struct qpnp_padinfo *pad;
+ unsigned int val, en_mask, buff, addr;
+ int ret;
+
+ pad = qpnp_get_desc(qctrl, offset);
+ if (!pad)
+ return -EINVAL;
+
+ addr = QPNP_REG_ADDR(pad, QPNP_REG_MODE_CTL);
+ ret = regmap_read(qctrl->map, addr, &buff);
+ if (ret < 0)
+ return ret;
+
+ /* GPIO val is from RT status if input is enabled */
+ if ((buff & QPNP_REG_MODE_SEL_MASK) ==
+ (QPNP_PIN_MODE_DIG_IN << QPNP_REG_MODE_SEL_SHIFT)) {
+
+ addr = QPNP_REG_ADDR(pad, QPNP_REG_STATUS1);
+ ret = regmap_read(qctrl->map, addr, &val);
+ if (ret < 0)
+ return ret;
+
+ if (pad->type == QPNP_GPIO_TYPE && pad->major == 0)
+ en_mask = QPNP_REG_STATUS1_GPIO_EN_REV0_MASK;
+ else if (pad->type == QPNP_GPIO_TYPE &&
+ pad->major > 0)
+ en_mask = QPNP_REG_STATUS1_GPIO_EN_MASK;
+ else /* MPP */
+ en_mask = QPNP_REG_STATUS1_MPP_EN_MASK;
+
+ if (!(val & en_mask))
+ return -EPERM;
+
+ ret = val & QPNP_REG_STATUS1_VAL_MASK;
+
+ } else {
+ ret = buff & QPNP_REG_MODE_INVERT_MASK;
+ ret = ret >> QPNP_REG_MODE_INVERT_SHIFT;
+ }
+
+ return !!ret;
+}
+
+static void qpnp_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct qpnp_pinctrl *qctrl = to_qpnp_pinctrl(chip);
+ struct qpnp_padinfo *pad;
+ unsigned int addr, buff;
+
+ pad = qpnp_get_desc(qctrl, offset);
+ if (!pad)
+ return;
+
+ addr = QPNP_REG_ADDR(pad, QPNP_REG_MODE_CTL);
+ buff = !!value << QPNP_REG_MODE_INVERT_SHIFT;
+
+ regmap_update_bits(qctrl->map, addr, QPNP_REG_MODE_INVERT_MASK, buff);
+}
+
+static int qpnp_config_get(struct pinctrl_dev *pctldev,
+ unsigned int pin,
+ unsigned long *config)
+{
+ struct qpnp_pinctrl *qctrl = pinctrl_dev_get_drvdata(pctldev);
+ unsigned param = pinconf_to_config_param(*config);
+ struct qpnp_padinfo *pad;
+ unsigned arg;
+ int ret;
+
+ pad = qpnp_get_desc(qctrl, pin);
+ if (!pad)
+ return -EINVAL;
+
+ /* Convert pinconf values to register values */
+ ret = qpnp_conv_from_pin(qctrl, pad, param, &arg);
+ if (ret)
+ return ret;
+
+ /* Convert register value to pinconf value */
+ *config = pinconf_to_config_packed(param, arg);
+ return 0;
+}
+
+static int qpnp_config_set(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *configs, unsigned nconfs)
+{
+ struct qpnp_pinctrl *qctrl = pinctrl_dev_get_drvdata(pctldev);
+ struct qpnp_padinfo *pad;
+ unsigned param;
+ unsigned arg;
+ int idx, ret;
+
+ pad = qpnp_get_desc(qctrl, pin);
+ if (!pad)
+ return -EINVAL;
+
+ for (idx = 0; idx < nconfs; idx++) {
+ param = pinconf_to_config_param(configs[idx]);
+ arg = pinconf_to_config_argument(configs[idx]);
+
+ /* Convert pinconf values to register values */
+ ret = qpnp_conv_to_pin(qctrl, pad, param, arg);
+ if (ret < 0) {
+ dev_err(pctldev->dev, "config %u = %u failed\n",
+ param, arg);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void qpnp_config_dbg_show(struct pinctrl_dev *pctldev,
+ struct seq_file *seq, unsigned pin)
+{
+ struct qpnp_pinctrl *qctrl = pinctrl_dev_get_drvdata(pctldev);
+ unsigned int en, buff, pull, out, drive, addr, mode, func, inv;
+ struct qpnp_padinfo *pad;
+ const char *name;
+ int ret;
+
+ static const char *const modes[] = {
+ "di", "do", "dio", "ai", "ao", "aio", "cs"
+ };
+ static const char * const biases[] = {
+ "pull-up 30uA", "pull-up 1.5uA", "pull-up 31.5uA",
+ "pull-up 1.5uA + 30uA boost", "pull-down 10uA", "no pull"
+ };
+ static const char * const buffer_types[] = {
+ "push-pull", "open-drain", "open-func"
+ };
+ static const char * const strengths[] = {
+ "no", "low", "medium", "high"
+ };
+
+ pad = qpnp_get_desc(qctrl, pin);
+ if (!pad)
+ return;
+
+ name = pad->name;
+
+ addr = QPNP_REG_ADDR(pad, QPNP_REG_MODE_CTL);
+ ret = regmap_read(qctrl->map, addr, &buff);
+ if (ret < 0) {
+ seq_printf(seq, " %-8s: read error %d", name, ret);
+ return;
+ }
+
+ mode = QPNP_GET(buff, QPNP_REG_MODE_SEL_SHIFT,
+ QPNP_REG_MODE_SEL_MASK);
+ func = QPNP_GET(buff, QPNP_REG_MODE_FUNCTION_SHIFT,
+ QPNP_REG_MODE_FUNCTION_MASK);
+ inv = QPNP_GET(buff, QPNP_REG_MODE_INVERT_SHIFT,
+ QPNP_REG_MODE_INVERT_MASK);
+
+ addr = QPNP_REG_ADDR(pad, QPNP_REG_DIG_PULL_CTL);
+ ret = regmap_read(qctrl->map, addr, &buff);
+ if (ret < 0) {
+ seq_printf(seq, " %-8s: read error %d", name, ret);
+ return;
+ }
+ pull = QPNP_GET(buff, QPNP_REG_PULL_SHIFT, QPNP_REG_PULL_MASK);
+
+ addr = QPNP_REG_ADDR(pad, QPNP_REG_DIG_OUT_CTL);
+ ret = regmap_read(qctrl->map, addr, &buff);
+ if (ret < 0) {
+ seq_printf(seq, " %-8s: read error %d", name, ret);
+ return;
+ }
+ out = QPNP_GET(buff, QPNP_REG_OUT_TYPE_SHIFT, QPNP_REG_OUT_TYPE_MASK);
+ drive = QPNP_GET(buff, QPNP_REG_OUT_STRENGTH_SHIFT,
+ QPNP_REG_OUT_STRENGTH_MASK);
+
+ addr = QPNP_REG_ADDR(pad, QPNP_REG_EN_CTL);
+ ret = regmap_read(qctrl->map, addr, &buff);
+ if (ret < 0) {
+ seq_printf(seq, " %-8s: read error %d", name, ret);
+ return;
+ }
+ en = QPNP_GET(buff, QPNP_REG_MASTER_EN_SHIFT, QPNP_REG_MASTER_EN_MASK);
+
+ seq_printf(seq, " %-8s: %-4s", name, modes[mode]);
+ seq_printf(seq, " %-7s", qctrl->functions[func]);
+ seq_printf(seq, " %-24s %-12s", biases[pull], buffer_types[out]);
+ seq_printf(seq, " %-6s %-4s", strengths[drive], inv ? "inv" : "");
+ seq_printf(seq, " %s", !en ? "high-Z" : "");
+
+}
+
+static const struct pinconf_ops qpnp_pinconf_ops = {
+ .pin_config_group_get = qpnp_config_get,
+ .pin_config_group_set = qpnp_config_set,
+ .pin_config_group_dbg_show = qpnp_config_dbg_show,
+};
+
+static int qpnp_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ struct qpnp_pinctrl *qctrl = to_qpnp_pinctrl(chip);
+ unsigned long config;
+
+ config = pinconf_to_config_packed(PIN_CONFIG_INPUT_ENABLE, 1);
+
+ return qpnp_config_set(qctrl->ctrl, offset, &config, 1);
+}
+
+static int qpnp_direction_output(struct gpio_chip *chip,
+ unsigned offset, int val)
+{
+ struct qpnp_pinctrl *qctrl = to_qpnp_pinctrl(chip);
+ unsigned long config;
+
+ config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, val);
+ return qpnp_config_set(qctrl->ctrl, offset, &config, 1);
+}
+
+static int qpnp_request(struct gpio_chip *chip, unsigned offset)
+{
+ return pinctrl_request_gpio(chip->base + offset);
+}
+
+static void qpnp_free(struct gpio_chip *chip, unsigned offset)
+{
+ pinctrl_free_gpio(chip->base + offset);
+}
+
+static int qpnp_of_xlate(struct gpio_chip *chip,
+ const struct of_phandle_args *gpio_desc, u32 *flags)
+{
+ struct qpnp_pinctrl *qctrl = to_qpnp_pinctrl(chip);
+ struct qpnp_padinfo *pad;
+ unsigned pin = gpio_desc->args[0] - QPNP_PIN_PHYSICAL_OFFSET;
+
+ if (chip->of_gpio_n_cells < 2)
+ return -EINVAL;
+
+ pad = qpnp_get_desc(qctrl, pin);
+ if (!pad)
+ return -EINVAL;
+
+ if (flags)
+ *flags = gpio_desc->args[1];
+
+ return pin;
+}
+
+static int qpnp_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ struct qpnp_pinctrl *qctrl = to_qpnp_pinctrl(chip);
+ struct qpnp_padinfo *pad;
+
+ pad = qpnp_get_desc(qctrl, offset);
+ if (!pad)
+ return -EINVAL;
+
+ return pad->irq;
+}
+
+static void qpnp_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+ struct qpnp_pinctrl *qctrl = to_qpnp_pinctrl(chip);
+ unsigned idx;
+
+ for (idx = 0; idx < chip->ngpio; idx++) {
+ qpnp_config_dbg_show(qctrl->ctrl, s, idx);
+ seq_puts(s, "\n");
+ }
+}
+
+static const struct gpio_chip qpnp_gpio_template = {
+ .direction_input = qpnp_direction_input,
+ .direction_output = qpnp_direction_output,
+ .get = qpnp_get,
+ .set = qpnp_set,
+ .request = qpnp_request,
+ .free = qpnp_free,
+ .of_xlate = qpnp_of_xlate,
+ .to_irq = qpnp_to_irq,
+ .dbg_show = qpnp_dbg_show,
+};
+
+static int qpnp_control_init(struct qpnp_pinctrl *qctrl,
+ struct qpnp_padinfo *pad)
+{
+ pad->modes = 0;
+
+ if (pad->type == QPNP_GPIO_TYPE) {
+ switch (pad->subtype) {
+ case QPNP_GPIO_SUBTYPE_GPIO_4CH:
+ case QPNP_GPIO_SUBTYPE_GPIOC_4CH:
+ case QPNP_GPIO_SUBTYPE_GPIO_8CH:
+ case QPNP_GPIO_SUBTYPE_GPIOC_8CH:
+
+ /* only GPIO is supported*/
+ pad->modes |= BIT(QPNP_PIN_MODE_DIG_IN);
+ pad->modes |= BIT(QPNP_PIN_MODE_DIG_OUT);
+ pad->modes |= BIT(QPNP_PIN_MODE_DIG_IN_OUT);
+ break;
+ default:
+ dev_err(qctrl->dev, "invalid GPIO subtype 0x%x\n",
+ pad->subtype);
+ return -EINVAL;
+ }
+
+ } else if (pad->type == QPNP_MPP_TYPE) {
+
+ switch (pad->subtype) {
+ case QPNP_MPP_SUBTYPE_4CH_NO_SINK:
+ case QPNP_MPP_SUBTYPE_ULT_4CH_NO_SINK:
+
+ /* Current sink not supported*/
+ pad->modes |= BIT(QPNP_PIN_MODE_DIG_IN);
+ pad->modes |= BIT(QPNP_PIN_MODE_DIG_OUT);
+ pad->modes |= BIT(QPNP_PIN_MODE_DIG_IN_OUT);
+ pad->modes |= BIT(QPNP_PIN_MODE_BIDIR);
+ pad->modes |= BIT(QPNP_PIN_MODE_AIN);
+ pad->modes |= BIT(QPNP_PIN_MODE_AOUT);
+ break;
+ case QPNP_MPP_SUBTYPE_4CH_NO_ANA_OUT:
+ case QPNP_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT:
+
+ /* Analog output not supported */
+ pad->modes |= BIT(QPNP_PIN_MODE_DIG_IN);
+ pad->modes |= BIT(QPNP_PIN_MODE_DIG_OUT);
+ pad->modes |= BIT(QPNP_PIN_MODE_DIG_IN_OUT);
+ pad->modes |= BIT(QPNP_PIN_MODE_BIDIR);
+ pad->modes |= BIT(QPNP_PIN_MODE_AIN);
+ pad->modes |= BIT(QPNP_PIN_MODE_SINK);
+ break;
+ case QPNP_MPP_SUBTYPE_4CH_FULL_FUNC:
+ case QPNP_MPP_SUBTYPE_8CH_FULL_FUNC:
+
+ pad->modes |= BIT(QPNP_PIN_MODE_DIG_IN);
+ pad->modes |= BIT(QPNP_PIN_MODE_DIG_OUT);
+ pad->modes |= BIT(QPNP_PIN_MODE_DIG_IN_OUT);
+ pad->modes |= BIT(QPNP_PIN_MODE_BIDIR);
+ pad->modes |= BIT(QPNP_PIN_MODE_AIN);
+ pad->modes |= BIT(QPNP_PIN_MODE_AOUT);
+ pad->modes |= BIT(QPNP_PIN_MODE_SINK);
+ break;
+ default:
+ dev_err(qctrl->dev, "invalid MPP subtype 0x%x\n",
+ pad->subtype);
+ return -EINVAL;
+ }
+ } else {
+ dev_err(qctrl->dev, "invalid type 0x%x\n", pad->type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qpnp_discover(struct platform_device *pdev,
+ struct qpnp_pinctrl *qctrl)
+{
+ struct device *dev = qctrl->dev;
+ struct pinctrl_pin_desc *desc, *descs;
+ struct qpnp_padinfo *pad, *pads;
+ unsigned int addr, npins;
+ int idx, ret;
+
+ npins = qctrl->info->npins;
+ pads = devm_kcalloc(dev, npins, sizeof(*pads), GFP_KERNEL);
+ if (!pads)
+ return -ENOMEM;
+
+ descs = devm_kcalloc(dev, npins, sizeof(*descs), GFP_KERNEL);
+ if (!descs)
+ return -ENOMEM;
+
+ for (idx = 0; idx < npins; idx++) {
+
+ pad = &pads[idx];
+ desc = &descs[idx];
+
+ pad->irq = platform_get_irq(pdev, idx);
+ if (pad->irq < 0)
+ return pad->irq;
+
+ pad->offset = qctrl->info->base + (idx * 0x100);
+
+ addr = QPNP_REG_ADDR(pad, QPNP_REG_DIG_MAJOR_REV);
+ ret = regmap_read(qctrl->map, addr, &pad->major);
+ if (ret < 0)
+ return ret;
+
+ addr = QPNP_REG_ADDR(pad, QPNP_REG_TYPE);
+ ret = regmap_read(qctrl->map, addr, &pad->type);
+ if (ret < 0)
+ return ret;
+
+ if (pad->type != qctrl->info->type) {
+ dev_err(dev, "Expected %x, found %x\n",
+ qctrl->info->type, pad->type);
+ return -EINVAL;
+ }
+
+ addr = QPNP_REG_ADDR(pad, QPNP_REG_SUBTYPE);
+ ret = regmap_read(qctrl->map, addr, &pad->subtype);
+ if (ret < 0)
+ return ret;
+
+ ret = qpnp_control_init(qctrl, pad);
+ if (ret)
+ return ret;
+
+ pad->name = qctrl->groups[idx];
+ desc->number = idx;
+ desc->name = pad->name;
+ }
+
+ qctrl->pads = pads;
+
+ qctrl->chip = qpnp_gpio_template;
+ qctrl->chip.dev = dev;
+ qctrl->chip.base = -1;
+ qctrl->chip.ngpio = qctrl->info->npins;
+ qctrl->chip.label = dev_name(dev);
+ qctrl->chip.of_gpio_n_cells = 2;
+ qctrl->chip.can_sleep = true;
+
+ qctrl->desc.pctlops = &qpnp_pinctrl_ops,
+ qctrl->desc.pmxops = &qpnp_pinmux_ops,
+ qctrl->desc.confops = &qpnp_pinconf_ops,
+ qctrl->desc.owner = THIS_MODULE,
+ qctrl->desc.name = dev_name(dev);
+ qctrl->desc.pins = descs;
+ qctrl->desc.npins = npins;
+
+ qctrl->ctrl = pinctrl_register(&qctrl->desc, dev, qctrl);
+ if (!qctrl->ctrl)
+ return -ENODEV;
+
+ ret = gpiochip_add(&qctrl->chip);
+ if (ret) {
+ dev_err(qctrl->dev, "can't add gpio chip\n");
+ goto err_chip;
+ }
+
+ ret = gpiochip_add_pin_range(&qctrl->chip, dev_name(dev),
+ 0, 0, npins);
+ if (ret) {
+ dev_err(dev, "failed to add pin range\n");
+ goto err_range;
+ }
+
+ return 0;
+
+err_chip:
+ pinctrl_unregister(qctrl->ctrl);
+
+err_range:
+ gpiochip_remove(&qctrl->chip);
+
+ return ret;
+
+}
+
+static const struct of_device_id qpnp_pinctrl_of_match[];
+
+static int qpnp_pinctrl_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct qpnp_chipinfo *qchip;
+ struct qpnp_pinctrl *qctrl;
+
+ qctrl = devm_kzalloc(dev, sizeof(*qctrl), GFP_KERNEL);
+ if (!qctrl)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, qctrl);
+
+ qchip = of_match_node(qpnp_pinctrl_of_match, dev->of_node)->data;
+
+ qctrl->info = qchip;
+ qctrl->dev = &pdev->dev;
+ qctrl->map = dev_get_regmap(dev->parent, NULL);
+
+ if (qchip->type == QPNP_GPIO_TYPE) {
+ if (WARN_ON(qchip->npins > ARRAY_SIZE(qpnp_gpio_groups)))
+ return -EINVAL;
+ qctrl->groups = qpnp_gpio_groups;
+ qctrl->functions = qpnp_gpio_functions;
+ } else {
+ if (WARN_ON(qchip->npins > ARRAY_SIZE(qpnp_mpp_groups)))
+ return -EINVAL;
+ qctrl->groups = qpnp_mpp_groups;
+ qctrl->functions = qpnp_mpp_functions;
+ }
+
+ return qpnp_discover(pdev, qctrl);
+}
+
+static int qpnp_pinctrl_remove(struct platform_device *pdev)
+{
+ struct qpnp_pinctrl *qctrl = platform_get_drvdata(pdev);
+
+ gpiochip_remove(&qctrl->chip);
+ pinctrl_unregister(qctrl->ctrl);
+
+ return 0;
+}
+
+static const struct qpnp_chipinfo qpnp_pm8841_mpp_info = {
+ .npins = 4,
+ .base = 0xa000,
+ .type = QPNP_MPP_TYPE,
+};
+
+static const struct qpnp_chipinfo qpnp_pm8941_gpio_info = {
+ .npins = 36,
+ .base = 0xc000,
+ .type = QPNP_GPIO_TYPE,
+};
+
+static const struct qpnp_chipinfo qpnp_pm8941_mpp_info = {
+ .npins = 8,
+ .base = 0xa000,
+ .type = QPNP_MPP_TYPE,
+};
+
+static const struct qpnp_chipinfo qpnp_pma8084_mpp_info = {
+ .npins = 4,
+ .base = 0xa000,
+ .type = QPNP_MPP_TYPE,
+};
+
+static const struct qpnp_chipinfo qpnp_pma8084_gpio_info = {
+ .npins = 22,
+ .base = 0xc000,
+ .type = QPNP_GPIO_TYPE,
+};
+
+static const struct of_device_id qpnp_pinctrl_of_match[] = {
+ { .compatible = "qcom,pm8941-gpio", .data = &qpnp_pm8941_gpio_info },
+ { .compatible = "qcom,pm8941-mpp", .data = &qpnp_pm8941_mpp_info },
+ { .compatible = "qcom,pm8841-mpp", .data = &qpnp_pm8841_mpp_info },
+ { .compatible = "qcom,pma8084-gpio", .data = &qpnp_pma8084_gpio_info },
+ { .compatible = "qcom,pma8084-mpp", .data = &qpnp_pma8084_mpp_info },
+ { },
+};
+MODULE_DEVICE_TABLE(of, qpnp_pinctrl_of_match);
+
+static struct platform_driver qpnp_pinctrl_driver = {
+ .driver = {
+ .name = "spmi-pmic-pinctrl",
+ .owner = THIS_MODULE,
+ .of_match_table = qpnp_pinctrl_of_match,
+ },
+ .probe = qpnp_pinctrl_probe,
+ .remove = qpnp_pinctrl_remove,
+};
+module_platform_driver(qpnp_pinctrl_driver);
+
+MODULE_AUTHOR("Ivan T. Ivanov <iivanov@xxxxxxxxxx>");
+MODULE_DESCRIPTION("Qualcomm SPMI PMIC pin control driver");
+MODULE_ALIAS("platform:spmi-pmic-pinctrl");
+MODULE_LICENSE("GPL v2");
--
1.8.3.2
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/