All of lore.kernel.org
 help / color / mirror / Atom feed
From: Srinivas Kandagatla <srinivas.kandagatla-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
To: "Ivan T. Ivanov"
	<iivanov-NEYub+7Iv8PQT0dZR+AlfA@public.gmane.org>,
	Linus Walleij
	<linus.walleij-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>,
	Grant Likely
	<grant.likely-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>,
	Rob Herring <robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Cc: Bjorn Andersson
	<bjorn.andersson-/MT0OVThwyLZJqsBc5GL+g@public.gmane.org>,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Subject: Re: [PATCH v3 2/6] pinctrl: Introduce pinctrl driver for Qualcomm SSBI PMIC's
Date: Wed, 20 Aug 2014 09:06:11 +0100	[thread overview]
Message-ID: <53F456F3.3000309@linaro.org> (raw)
In-Reply-To: <1407771634-14946-3-git-send-email-iivanov-NEYub+7Iv8PQT0dZR+AlfA@public.gmane.org>

Hi Bjorn,

Two things which I noticed while trying out this driver to drive a reset 
line.

1> gpio numbering for pinconf vs gpio are not consistent, they differ by 
an offset of 1.

For example to control GPIO43 I had to do something like this in pinconf:

wlan_default_gpios: wlan-gpios {
	pios {
		pins = "gpio43";
		function = "normal";
		bias-disable;
		power-source = <PM8921_GPIO_S4>;
	};
};


for same pin for gpio I had to do:
	reset-gpio = <&pm8921_gpio 42 GPIO_ACTIVE_LOW>;

This offset by 1 is bit confusing and can easily lead to errors which 
are hard to find.

I think this should be easy to fix..

2> Looking back at v3.4 kernel, for gpio modes, BIT(0) of bank 0 is set 
to enable gpio mode. without this bit driver does not work for output pins.

here is the patch which fixes this:

 From 5a5c5171a3371cf38ccd157922ea140bfd0253d5 Mon Sep 17 00:00:00 2001
From: Srinivas Kandagatla <srinivas.kandagatla-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Date: Wed, 20 Aug 2014 07:18:03 +0100
Subject: [PATCH] pinctrl:qcom:ssbi: Enable gpio mode

Looking back at 3.4 kernel driver, it looks like the gpio enable bit is
missing in this driver.

This patch is just a hack to get the wlan reset line working.

Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
---
  drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c | 4 +++-
  1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c 
b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c
index 27e5ec9..2633ea1 100644
--- a/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c
+++ b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c
@@ -49,6 +49,7 @@
  #define SSBI_REG_ADDR_GPIO_BASE		0x150
  #define SSBI_REG_ADDR_GPIO(n)		(SSBI_REG_ADDR_GPIO_BASE + n)

+#define	PM8XXX_GPIO_MODE_ENABLE		BIT(0)
  #define PM8XXX_GPIO_WRITE		BIT(7)

  #define PM8XXX_MAX_GPIOS		44
@@ -493,7 +494,8 @@ static int pm8xxx_gpio_config_set(struct pinctrl_dev 
*pctldev,
  	}

  	if (banks & BIT(0))
-		pm8xxx_gpio_write(pctrl, offset, 0, pin->power_source << 1);
+		pm8xxx_gpio_write(pctrl, offset, 0, pin->power_source << 1 |
+				  PM8XXX_GPIO_MODE_ENABLE);

  	if (banks & BIT(1)) {
  		val = pin->direction << 2;
-- 
2.0.2

with this change am able to reset WLAN chip on IFC6410 which is 
connected to gpio43 of pmic.

thanks,
srini
On 11/08/14 16:40, Ivan T. Ivanov wrote:
> From: Bjorn Andersson <bjorn.andersson-/MT0OVThwyLZJqsBc5GL+g@public.gmane.org>
>
> This introduces a pinctrl, pinconf, pinmux and gpio driver for the gpio
> block found in pm8018, pm8038, pm8058, pm8917 and pm8921 pmics from
> Qualcomm.
>
> Signed-off-by: Bjorn Andersson <bjorn.andersson-/MT0OVThwyLZJqsBc5GL+g@public.gmane.org>
> Signed-off-by: Ivan T. Ivanov <iivanov-NEYub+7Iv8PQT0dZR+AlfA@public.gmane.org>
> ---
>   drivers/pinctrl/qcom/Kconfig             |  11 +
>   drivers/pinctrl/qcom/Makefile            |   1 +
>   drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c | 870 +++++++++++++++++++++++++++++++
>   3 files changed, 882 insertions(+)
>   create mode 100644 drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c
>
> diff --git a/drivers/pinctrl/qcom/Kconfig b/drivers/pinctrl/qcom/Kconfig
> index d160a71..6bd4e4d 100644
> --- a/drivers/pinctrl/qcom/Kconfig
> +++ b/drivers/pinctrl/qcom/Kconfig
> @@ -39,4 +39,15 @@ 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_SSBI_PMIC
> +       tristate "Qualcomm SSBI PMIC pin controller driver"
> +       depends on GPIOLIB && OF
> +       select PINCONF
> +       select PINMUX
> +       select GENERIC_PINCONF
> +       help
> +         This is the pinctrl, pinmux, pinconf and gpiolib driver for the
> +         Qualcomm GPIO blocks found in the pm8018, pm8038, pm8058, pm8917 and
> +         pm8921 pmics from Qualcomm.
> +
>   endif
> diff --git a/drivers/pinctrl/qcom/Makefile b/drivers/pinctrl/qcom/Makefile
> index 2a02602..8faa2ca 100644
> --- a/drivers/pinctrl/qcom/Makefile
> +++ b/drivers/pinctrl/qcom/Makefile
> @@ -4,3 +4,4 @@ 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_SSBI_PMIC) += pinctrl-ssbi-pmic.o
> diff --git a/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c
> new file mode 100644
> index 0000000..9a1b443
> --- /dev/null
> +++ b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c
> @@ -0,0 +1,870 @@
> +/*
> + * Copyright (c) 2014, Sony Mobile Communications AB.
> + * Copyright (c) 2013, 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/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pinctrl/pinctrl.h>
> +#include <linux/pinctrl/pinmux.h>
> +#include <linux/pinctrl/pinconf.h>
> +#include <linux/pinctrl/pinconf-generic.h>
> +#include <linux/slab.h>
> +#include <linux/regmap.h>
> +#include <linux/gpio.h>
> +#include <linux/mfd/pm8921-core.h>
> +
> +#include <dt-bindings/pinctrl/qcom,pmic-gpio.h>
> +
> +#include "../core.h"
> +#include "../pinconf.h"
> +#include "../pinctrl-utils.h"
> +
> +/* direction */
> +#define PM8XXX_GPIO_DIR_OUT		BIT(0)
> +#define PM8XXX_GPIO_DIR_IN		BIT(1)
> +
> +/* output buffer */
> +#define PM8XXX_GPIO_PUSH_PULL		0
> +#define PM8XXX_GPIO_OPEN_DRAIN		1
> +
> +/* bias */
> +#define PM8XXX_GPIO_BIAS_PU_30		0
> +#define PM8XXX_GPIO_BIAS_PU_1P5		1
> +#define PM8XXX_GPIO_BIAS_PU_31P5	2
> +#define PM8XXX_GPIO_BIAS_PU_1P5_30	3
> +#define PM8XXX_GPIO_BIAS_PD		4
> +#define PM8XXX_GPIO_BIAS_NP		5
> +
> +/* GPIO registers */
> +#define SSBI_REG_ADDR_GPIO_BASE		0x150
> +#define SSBI_REG_ADDR_GPIO(n)		(SSBI_REG_ADDR_GPIO_BASE + n)
> +
> +#define PM8XXX_GPIO_WRITE		BIT(7)
> +
> +#define PM8XXX_MAX_GPIOS		44
> +
> +/* Qualcomm specific pin configurations */
> +#define PM8XXX_PINCONF_PULL_UP		(PIN_CONFIG_END + 1)
> +#define PM8XXX_PINCONF_STRENGTH		(PIN_CONFIG_END + 2)
> +
> +struct pm8xxx_pinbindings {
> +	const char *property;
> +	unsigned param;
> +	u32 default_value;
> +};
> +static struct pm8xxx_pinbindings pm8xxx_pinbindings[] = {
> +	/* PMIC_GPIO_PULL_UP_30...  */
> +	{"qcom,pull-up-strength",	PM8XXX_PINCONF_PULL_UP, 0},
> +	/* PMIC_GPIO_STRENGTH_NO... */
> +	{"qcom,drive-strength",		PM8XXX_PINCONF_STRENGTH, 0},
> +};
> +
> +struct pm8xxx_gpio_pin {
> +	int irq;
> +
> +	u8 power_source;
> +	u8 direction;
> +	u8 output_buffer;
> +	u8 output_value;
> +	u8 bias;
> +	u8 output_strength;
> +	u8 disable;
> +	u8 function;
> +	u8 non_inverted;
> +};
> +
> +struct pm8xxx_gpio_data {
> +	int ngpio;
> +};
> +
> +struct pm8xxx_gpio {
> +	struct device *dev;
> +	struct regmap *regmap;
> +	struct pinctrl_dev *pctrl;
> +	struct gpio_chip chip;
> +
> +	const struct pm8xxx_gpio_data *data;
> +
> +	struct pm8xxx_gpio_pin pins[PM8XXX_MAX_GPIOS];
> +};
> +
> +static inline struct pm8xxx_gpio *to_pm8xxx_gpio(struct gpio_chip *chip)
> +{
> +	return container_of(chip, struct pm8xxx_gpio, chip);
> +};
> +
> +static const char * const pm8xxx_gpio_groups[PM8XXX_MAX_GPIOS] = {
> +	"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",
> +	"gpio37", "gpio38", "gpio39", "gpio40", "gpio41", "gpio42", "gpio43",
> +	"gpio44",
> +};
> +
> +static const char * const pm8xxx_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 int pm8xxx_gpio_read(struct pm8xxx_gpio *pctrl, int pin, int bank)
> +{
> +	int reg = SSBI_REG_ADDR_GPIO(pin);
> +	unsigned int val = bank << 4;
> +	int ret;
> +
> +	ret = regmap_write(pctrl->regmap, reg, val);
> +	if (ret) {
> +		dev_err(pctrl->dev,
> +			"failed to select bank %d of pin %d\n", bank, pin);
> +		return ret;
> +	}
> +
> +	ret = regmap_read(pctrl->regmap, reg, &val);
> +	if (ret) {
> +		dev_err(pctrl->dev,
> +			"failed to read register %d of pin %d\n", bank, pin);
> +		return ret;
> +	}
> +
> +	return val;
> +}
> +
> +static int pm8xxx_gpio_write(struct pm8xxx_gpio *pctrl,
> +			     int pin, int bank, u8 val)
> +{
> +	int ret;
> +
> +	val |= PM8XXX_GPIO_WRITE;
> +	val |= bank << 4;
> +
> +	ret = regmap_write(pctrl->regmap, SSBI_REG_ADDR_GPIO(pin), val);
> +	if (ret)
> +		dev_err(pctrl->dev, "failed to write register\n");
> +
> +	return ret;
> +}
> +
> +static int pm8xxx_gpio_get_groups_count(struct pinctrl_dev *pctldev)
> +{
> +	struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
> +
> +	return pctrl->data->ngpio;
> +}
> +
> +static const char *pm8xxx_gpio_get_group_name(struct pinctrl_dev *pctldev,
> +				      unsigned group)
> +{
> +	return pm8xxx_gpio_groups[group];
> +}
> +
> +static int pm8xxx_parse_dt_config(struct device *dev, struct device_node *np,
> +			unsigned long **configs, unsigned int *nconfigs)
> +{
> +	struct pm8xxx_pinbindings *par;
> +	unsigned long cfg[ARRAY_SIZE(pm8xxx_pinbindings)];
> +	unsigned int ncfg = 0;
> +	int ret, idx;
> +	u32 val;
> +
> +	if (!np)
> +		return -EINVAL;
> +
> +	for (idx = 0; idx < ARRAY_SIZE(pm8xxx_pinbindings); idx++) {
> +		par = &pm8xxx_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(dev, "found %s with value %u\n", par->property, val);
> +		cfg[ncfg] = pinconf_to_config_packed(par->param, val);
> +		ncfg++;
> +	}
> +
> +	ret = 0;
> +
> +	/* no configs found at qchip->npads */
> +	if (ncfg == 0) {
> +		*configs = NULL;
> +		*nconfigs = 0;
> +		goto out;
> +	}
> +
> +	/*
> +	 * Now limit the number of configs to the real number of
> +	 * found properties.
> +	 */
> +	*configs = kcalloc(ncfg, sizeof(unsigned long), GFP_KERNEL);
> +	if (!*configs) {
> +		ret = -ENOMEM;
> +		goto out;
> +	}
> +
> +	memcpy(*configs, cfg, ncfg * sizeof(unsigned long));
> +	*nconfigs = ncfg;
> +
> +out:
> +	return ret;
> +}
> +
> +static int pm8xxx_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 num_configs = 0;
> +	struct property *prop;
> +	const char *group;
> +	int ret;
> +
> +	ret = pm8xxx_parse_dt_config(pctldev->dev, np, &configs, &num_configs);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (!num_configs)
> +		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,
> +				num_configs, type);
> +		if (ret < 0)
> +			break;
> +	}
> +exit:
> +	kfree(configs);
> +	return ret;
> +}
> +
> +static int pm8xxx_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 = pm8xxx_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 pm8xxx_gpio_pinctrl_ops = {
> +	.get_groups_count	= pm8xxx_gpio_get_groups_count,
> +	.get_group_name		= pm8xxx_gpio_get_group_name,
> +	.dt_node_to_map		= pm8xxx_dt_node_to_map,
> +	.dt_free_map		= pinctrl_utils_dt_free_map,
> +};
> +
> +static int pm8xxx_get_functions_count(struct pinctrl_dev *pctldev)
> +{
> +	return ARRAY_SIZE(pm8xxx_gpio_functions);
> +}
> +
> +static const char *pm8xxx_get_function_name(struct pinctrl_dev *pctldev,
> +					 unsigned function)
> +{
> +	return pm8xxx_gpio_functions[function];
> +}
> +
> +static int pm8xxx_get_function_groups(struct pinctrl_dev *pctldev,
> +				   unsigned function,
> +				   const char * const **groups,
> +				   unsigned * const num_groups)
> +{
> +	struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
> +
> +	*groups = pm8xxx_gpio_groups;
> +	*num_groups = pctrl->data->ngpio;
> +	return 0;
> +}
> +
> +static int pm8xxx_pinmux_enable(struct pinctrl_dev *pctldev,
> +			     unsigned function,
> +			     unsigned group)
> +{
> +	struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[group];
> +	u8 val;
> +
> +	pin->function = function;
> +	val = pin->function << 1;
> +
> +	pm8xxx_gpio_write(pctrl, group, 4, val);
> +
> +	return 0;
> +}
> +
> +static const struct pinmux_ops pm8xxx_pinmux_ops = {
> +	.get_functions_count	= pm8xxx_get_functions_count,
> +	.get_function_name	= pm8xxx_get_function_name,
> +	.get_function_groups	= pm8xxx_get_function_groups,
> +	.enable			= pm8xxx_pinmux_enable,
> +};
> +
> +static int pm8xxx_gpio_config_get(struct pinctrl_dev *pctldev,
> +			  unsigned int offset,
> +			  unsigned long *config)
> +{
> +	struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset];
> +	unsigned param = pinconf_to_config_param(*config);
> +	unsigned arg;
> +
> +	switch (param) {
> +	case PIN_CONFIG_BIAS_DISABLE:
> +		arg = pin->bias == PM8XXX_GPIO_BIAS_NP;
> +		break;
> +	case PIN_CONFIG_BIAS_PULL_DOWN:
> +		arg = pin->bias == PM8XXX_GPIO_BIAS_PD;
> +		break;
> +	case PM8XXX_PINCONF_PULL_UP:
> +		if (pin->bias >= PM8XXX_GPIO_BIAS_PU_30 &&
> +		    pin->bias <= PM8XXX_GPIO_BIAS_PU_1P5_30)
> +			arg = PMIC_GPIO_PULL_UP_30 + pin->bias;
> +		else
> +			arg = 0;
> +		break;
> +	case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
> +		arg = pin->disable;
> +		break;
> +	case PIN_CONFIG_INPUT_ENABLE:
> +		arg = pin->direction == PM8XXX_GPIO_DIR_IN;
> +		break;
> +	case PIN_CONFIG_OUTPUT:
> +		arg = pin->output_value;
> +		break;
> +	case PIN_CONFIG_POWER_SOURCE:
> +		arg = pin->power_source;
> +		break;
> +	case PM8XXX_PINCONF_STRENGTH:
> +		arg = pin->output_strength;
> +		break;
> +	case PIN_CONFIG_DRIVE_PUSH_PULL:
> +		arg = pin->output_buffer == PM8XXX_GPIO_PUSH_PULL;
> +		break;
> +	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
> +		arg = pin->output_buffer == PM8XXX_GPIO_OPEN_DRAIN;
> +		break;
> +	default:
> +		dev_err(pctrl->dev,
> +			"unsupported config parameter: %x\n",
> +			param);
> +		return -EINVAL;
> +	}
> +
> +	*config = pinconf_to_config_packed(param, arg);
> +
> +	return 0;
> +}
> +
> +static int pm8xxx_gpio_config_set(struct pinctrl_dev *pctldev,
> +				  unsigned int offset,
> +				  unsigned long *configs,
> +				  unsigned num_configs)
> +{
> +	struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset];
> +	unsigned param;
> +	unsigned arg;
> +	unsigned i;
> +	u8 banks = 0;
> +	u8 val;
> +
> +	for (i = 0; i < num_configs; i++) {
> +		param = pinconf_to_config_param(configs[i]);
> +		arg = pinconf_to_config_argument(configs[i]);
> +
> +		switch (param) {
> +		case PIN_CONFIG_BIAS_DISABLE:
> +			pin->bias = PM8XXX_GPIO_BIAS_NP;
> +			banks |= BIT(2);
> +			pin->disable = 0;
> +			banks |= BIT(3);
> +			break;
> +		case PIN_CONFIG_BIAS_PULL_DOWN:
> +			pin->bias = PM8XXX_GPIO_BIAS_PD;
> +			banks |= BIT(2);
> +			pin->disable = 0;
> +			banks |= BIT(3);
> +			break;
> +		case PM8XXX_PINCONF_PULL_UP:
> +			if (arg < PMIC_GPIO_PULL_UP_30 ||
> +			    arg > PMIC_GPIO_PULL_UP_1P5_30) {
> +				dev_err(pctrl->dev, "invalid pull-up level\n");
> +				return -EINVAL;
> +			}
> +			pin->bias = arg - PM8XXX_GPIO_BIAS_PU_30;
> +			banks |= BIT(2);
> +			pin->disable = 0;
> +			banks |= BIT(3);
> +			break;
> +		case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
> +			pin->disable = 1;
> +			banks |= BIT(3);
> +			break;
> +		case PIN_CONFIG_INPUT_ENABLE:
> +			pin->direction = PM8XXX_GPIO_DIR_IN;
> +			banks |= BIT(1);
> +			break;
> +		case PIN_CONFIG_OUTPUT:
> +			pin->direction = PM8XXX_GPIO_DIR_OUT;
> +			pin->output_value = !!arg;
> +			banks |= BIT(1);
> +			break;
> +		case PIN_CONFIG_POWER_SOURCE:
> +			pin->power_source = arg;
> +			banks |= BIT(0);
> +			break;
> +		case PM8XXX_PINCONF_STRENGTH:
> +			if (arg > PMIC_GPIO_STRENGTH_LOW) {
> +				dev_err(pctrl->dev, "invalid drive strength\n");
> +				return -EINVAL;
> +			}
> +			pin->output_strength = arg;
> +			banks |= BIT(3);
> +			break;
> +		case PIN_CONFIG_DRIVE_PUSH_PULL:
> +			pin->output_buffer = PM8XXX_GPIO_PUSH_PULL;
> +			banks |= BIT(1);
> +			break;
> +		case PIN_CONFIG_DRIVE_OPEN_DRAIN:
> +			pin->output_buffer = PM8XXX_GPIO_OPEN_DRAIN;
> +			banks |= BIT(1);
> +			break;
> +		default:
> +			dev_err(pctrl->dev,
> +					"unsupported config parameter: %x\n",
> +					param);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	if (banks & BIT(0))
> +		pm8xxx_gpio_write(pctrl, offset, 0, pin->power_source << 1);
> +
> +	if (banks & BIT(1)) {
> +		val = pin->direction << 2;
> +		val |= pin->output_buffer << 1;
> +		val |= pin->output_value;
> +		pm8xxx_gpio_write(pctrl, offset, 1, val);
> +	}
> +
> +	if (banks & BIT(2)) {
> +		val = pin->bias << 1;
> +		pm8xxx_gpio_write(pctrl, offset, 2, val);
> +	}
> +
> +	if (banks & BIT(3)) {
> +		val = pin->output_strength << 2;
> +		val |= pin->disable;
> +		pm8xxx_gpio_write(pctrl, offset, 3, val);
> +	}
> +
> +	if (banks & BIT(4)) {
> +		val = pin->function << 1;
> +		pm8xxx_gpio_write(pctrl, offset, 4, val);
> +	}
> +
> +	if (banks & BIT(5)) {
> +		val = 0;
> +		if (pin->non_inverted)
> +			val |= BIT(3);
> +		pm8xxx_gpio_write(pctrl, offset, 5, val);
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct pinconf_ops pm8xxx_gpio_pinconf_ops = {
> +	.pin_config_group_get = pm8xxx_gpio_config_get,
> +	.pin_config_group_set = pm8xxx_gpio_config_set,
> +};
> +
> +static struct pinctrl_desc pm8xxx_gpio_desc = {
> +	.pctlops = &pm8xxx_gpio_pinctrl_ops,
> +	.pmxops = &pm8xxx_pinmux_ops,
> +	.confops = &pm8xxx_gpio_pinconf_ops,
> +	.owner = THIS_MODULE,
> +};
> +
> +static int pm8xxx_gpio_direction_input(struct gpio_chip *chip,
> +				       unsigned offset)
> +{
> +	struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
> +	u8 val;
> +
> +	pin->direction = PM8XXX_GPIO_DIR_IN;
> +	val = pin->direction << 2;
> +
> +	pm8xxx_gpio_write(pctrl, offset, 1, val);
> +
> +	return 0;
> +}
> +
> +static int pm8xxx_gpio_direction_output(struct gpio_chip *chip,
> +					unsigned offset,
> +					int value)
> +{
> +	struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
> +	u8 val;
> +
> +	pin->direction = PM8XXX_GPIO_DIR_OUT;
> +	pin->output_value = !!value;
> +
> +	val = pin->direction << 2;
> +	val |= pin->output_buffer << 1;
> +	val |= pin->output_value;
> +
> +	pm8xxx_gpio_write(pctrl, offset, 1, val);
> +
> +	return 0;
> +}
> +
> +static int pm8xxx_gpio_get(struct gpio_chip *chip, unsigned offset)
> +{
> +	struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
> +
> +	if (pin->direction == PM8XXX_GPIO_DIR_OUT)
> +		return pin->output_value;
> +
> +	return pm8xxx_read_irq_status(pin->irq);
> +}
> +
> +static void pm8xxx_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
> +{
> +	struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(gc);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
> +	u8 val;
> +
> +	pin->output_value = !!value;
> +
> +	val = pin->direction << 2;
> +	val |= pin->output_buffer << 1;
> +	val |= pin->output_value;
> +
> +	pm8xxx_gpio_write(pctrl, offset, 1, val);
> +}
> +
> +static int pm8xxx_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
> +{
> +	struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
> +
> +	return pin->irq;
> +}
> +
> +#ifdef CONFIG_DEBUG_FS
> +#include <linux/seq_file.h>
> +
> +static void pm8xxx_gpio_dbg_show_one(struct seq_file *s,
> +				  struct pinctrl_dev *pctldev,
> +				  struct gpio_chip *chip,
> +				  unsigned offset,
> +				  unsigned gpio)
> +{
> +	struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset];
> +
> +	static const char * const directions[] = {
> +		"off", "out", "in", "both"
> +	};
> +	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"
> +	};
> +	static const char * const strengths[] = {
> +		"no", "high", "medium", "low"
> +	};
> +
> +	seq_printf(s, " gpio%-2d:", offset + 1);
> +	if (pin->disable) {
> +		seq_puts(s, " ---");
> +	} else {
> +		seq_printf(s, " %-4s", directions[pin->direction]);
> +		seq_printf(s, " %-7s", pm8xxx_gpio_functions[pin->function]);
> +		seq_printf(s, " VIN%d", pin->power_source);
> +		seq_printf(s, " %-27s", biases[pin->bias]);
> +		seq_printf(s, " %-10s", buffer_types[pin->output_buffer]);
> +		seq_printf(s, " %-4s", pin->output_value ? "high" : "low");
> +		seq_printf(s, " %-7s", strengths[pin->output_strength]);
> +		if (!pin->non_inverted)
> +			seq_puts(s, " inverted");
> +	}
> +}
> +
> +static void pm8xxx_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
> +{
> +	unsigned gpio = chip->base;
> +	unsigned i;
> +
> +	for (i = 0; i < chip->ngpio; i++, gpio++) {
> +		pm8xxx_gpio_dbg_show_one(s, NULL, chip, i, gpio);
> +		seq_puts(s, "\n");
> +	}
> +}
> +
> +#else
> +#define msm_gpio_dbg_show NULL
> +#endif
> +
> +static struct gpio_chip pm8xxx_gpio_template = {
> +	.direction_input = pm8xxx_gpio_direction_input,
> +	.direction_output = pm8xxx_gpio_direction_output,
> +	.get = pm8xxx_gpio_get,
> +	.set = pm8xxx_gpio_set,
> +	.to_irq = pm8xxx_gpio_to_irq,
> +	.dbg_show = pm8xxx_gpio_dbg_show,
> +	.owner = THIS_MODULE,
> +};
> +
> +static int pm8xxx_gpio_populate(struct pm8xxx_gpio *pctrl)
> +{
> +	struct pm8xxx_gpio_pin *pin;
> +	int val;
> +	int i;
> +
> +	for (i = 0; i < pctrl->data->ngpio; i++) {
> +		pin = &pctrl->pins[i];
> +
> +		val = pm8xxx_gpio_read(pctrl, i, 0);
> +		if (val < 0)
> +			return val;
> +
> +		pin->power_source = (val >> 1) & 0x7;
> +
> +		val = pm8xxx_gpio_read(pctrl, i, 1);
> +		if (val < 0)
> +			return val;
> +
> +		pin->direction = (val >> 2) & 0x3;
> +		pin->output_buffer = !!(val & BIT(1));
> +		pin->output_value = val & BIT(0);
> +
> +		val = pm8xxx_gpio_read(pctrl, i, 2);
> +		if (val < 0)
> +			return val;
> +
> +		pin->bias = (val >> 1) & 0x7;
> +
> +		val = pm8xxx_gpio_read(pctrl, i, 3);
> +		if (val < 0)
> +			return val;
> +
> +		pin->output_strength = (val >> 2) & 0x3;
> +		pin->disable = val & BIT(0);
> +
> +		val = pm8xxx_gpio_read(pctrl, i, 4);
> +		if (val < 0)
> +			return val;
> +
> +		pin->function = (val >> 1) & 0x7;
> +
> +		val = pm8xxx_gpio_read(pctrl, i, 5);
> +		if (val < 0)
> +			return val;
> +
> +		pin->non_inverted = !!(val & BIT(3));
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct pm8xxx_gpio_data pm8018_gpio_data = {
> +	.ngpio = 6,
> +};
> +
> +static const struct pm8xxx_gpio_data pm8038_gpio_data = {
> +	.ngpio = 12,
> +};
> +
> +static const struct pm8xxx_gpio_data pm8058_gpio_data = {
> +	.ngpio = 40,
> +};
> +static const struct pm8xxx_gpio_data pm8917_gpio_data = {
> +	.ngpio = 38,
> +};
> +
> +static const struct pm8xxx_gpio_data pm8921_gpio_data = {
> +	.ngpio = 44,
> +};
> +
> +static const struct of_device_id pm8xxx_gpio_of_match[] = {
> +	{ .compatible = "qcom,pm8018-gpio", .data = &pm8018_gpio_data },
> +	{ .compatible = "qcom,pm8038-gpio", .data = &pm8038_gpio_data },
> +	{ .compatible = "qcom,pm8058-gpio", .data = &pm8058_gpio_data },
> +	{ .compatible = "qcom,pm8917-gpio", .data = &pm8917_gpio_data },
> +	{ .compatible = "qcom,pm8921-gpio", .data = &pm8921_gpio_data },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, pm8xxx_gpio_of_match);
> +
> +static int pm8xxx_gpio_probe(struct platform_device *pdev)
> +{
> +	const struct of_device_id *match;
> +	struct pm8xxx_gpio *pctrl;
> +	int ret;
> +	int i;
> +
> +	match = of_match_node(pm8xxx_gpio_of_match, pdev->dev.of_node);
> +	if (!match)
> +		return -ENXIO;
> +
> +	pctrl = devm_kzalloc(&pdev->dev, sizeof(*pctrl), GFP_KERNEL);
> +	if (!pctrl)
> +		return -ENOMEM;
> +
> +	pctrl->dev = &pdev->dev;
> +	pctrl->data = match->data;
> +
> +	BUG_ON(pctrl->data->ngpio > PM8XXX_MAX_GPIOS);
> +
> +	pctrl->chip = pm8xxx_gpio_template;
> +	pctrl->chip.base = -1;
> +	pctrl->chip.dev = &pdev->dev;
> +	pctrl->chip.of_node = pdev->dev.of_node;
> +	pctrl->chip.label = dev_name(pctrl->dev);
> +	pctrl->chip.ngpio = pctrl->data->ngpio;
> +
> +	pctrl->regmap = dev_get_regmap(pdev->dev.parent, NULL);
> +	if (!pctrl->regmap) {
> +		dev_err(&pdev->dev, "parent regmap unavailable\n");
> +		return -ENXIO;
> +	}
> +
> +	for (i = 0; i < pctrl->data->ngpio; i++) {
> +		ret = platform_get_irq(pdev, i);
> +		if (ret < 0) {
> +			dev_err(&pdev->dev,
> +				"missing interrupts for pin %d\n", i);
> +			return ret;
> +		}
> +
> +		pctrl->pins[i].irq = ret;
> +	}
> +
> +	ret = pm8xxx_gpio_populate(pctrl);
> +	if (ret)
> +		return ret;
> +
> +	pm8xxx_gpio_desc.name = dev_name(&pdev->dev);
> +	pctrl->pctrl = pinctrl_register(&pm8xxx_gpio_desc, &pdev->dev, pctrl);
> +	if (!pctrl->pctrl) {
> +		dev_err(&pdev->dev, "couldn't register pm8xxx gpio driver\n");
> +		return -ENODEV;
> +	}
> +
> +	ret = gpiochip_add(&pctrl->chip);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed register gpiochip\n");
> +		goto unregister_pinctrl;
> +	}
> +
> +	ret = gpiochip_add_pin_range(&pctrl->chip,
> +				     dev_name(pctrl->dev),
> +				     1, 0, pctrl->data->ngpio);
> +	if (ret) {
> +		dev_err(pctrl->dev, "failed to add pin range\n");
> +		goto unregister_gpiochip;
> +	}
> +
> +	platform_set_drvdata(pdev, pctrl);
> +
> +	dev_dbg(&pdev->dev, "Qualcomm pm8xxx gpio driver probed\n");
> +
> +	return 0;
> +
> +unregister_pinctrl:
> +	pinctrl_unregister(pctrl->pctrl);
> +
> +unregister_gpiochip:
> +	if (gpiochip_remove(&pctrl->chip))
> +		dev_err(&pdev->dev, "unable to unregister gpiochip\n");
> +
> +	return ret;
> +}
> +
> +static int pm8xxx_gpio_remove(struct platform_device *pdev)
> +{
> +	struct pm8xxx_gpio *pctrl = platform_get_drvdata(pdev);
> +
> +	gpiochip_remove(&pctrl->chip);
> +
> +	pinctrl_unregister(pctrl->pctrl);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver pm8xxx_gpio_driver = {
> +	.driver = {
> +		.name = "ssbi-pmic-gpio",
> +		.owner = THIS_MODULE,
> +		.of_match_table = pm8xxx_gpio_of_match,
> +	},
> +	.probe = pm8xxx_gpio_probe,
> +	.remove = pm8xxx_gpio_remove,
> +};
> +
> +module_platform_driver(pm8xxx_gpio_driver);
> +
> +MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson-/MT0OVThwyLZJqsBc5GL+g@public.gmane.org>");
> +MODULE_DESCRIPTION("Qualcomm SSBI PMIC GPIO driver");
> +MODULE_LICENSE("GPL v2");
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

WARNING: multiple messages have this Message-ID (diff)
From: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
To: "Ivan T. Ivanov" <iivanov@mm-sol.com>,
	Linus Walleij <linus.walleij@linaro.org>,
	Grant Likely <grant.likely@linaro.org>,
	Rob Herring <robh+dt@kernel.org>
Cc: Bjorn Andersson <bjorn.andersson@sonymobile.com>,
	linux-arm-msm@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH v3 2/6] pinctrl: Introduce pinctrl driver for Qualcomm SSBI PMIC's
Date: Wed, 20 Aug 2014 09:06:11 +0100	[thread overview]
Message-ID: <53F456F3.3000309@linaro.org> (raw)
In-Reply-To: <1407771634-14946-3-git-send-email-iivanov@mm-sol.com>

Hi Bjorn,

Two things which I noticed while trying out this driver to drive a reset 
line.

1> gpio numbering for pinconf vs gpio are not consistent, they differ by 
an offset of 1.

For example to control GPIO43 I had to do something like this in pinconf:

wlan_default_gpios: wlan-gpios {
	pios {
		pins = "gpio43";
		function = "normal";
		bias-disable;
		power-source = <PM8921_GPIO_S4>;
	};
};


for same pin for gpio I had to do:
	reset-gpio = <&pm8921_gpio 42 GPIO_ACTIVE_LOW>;

This offset by 1 is bit confusing and can easily lead to errors which 
are hard to find.

I think this should be easy to fix..

2> Looking back at v3.4 kernel, for gpio modes, BIT(0) of bank 0 is set 
to enable gpio mode. without this bit driver does not work for output pins.

here is the patch which fixes this:

 From 5a5c5171a3371cf38ccd157922ea140bfd0253d5 Mon Sep 17 00:00:00 2001
From: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Date: Wed, 20 Aug 2014 07:18:03 +0100
Subject: [PATCH] pinctrl:qcom:ssbi: Enable gpio mode

Looking back at 3.4 kernel driver, it looks like the gpio enable bit is
missing in this driver.

This patch is just a hack to get the wlan reset line working.

Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
---
  drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c | 4 +++-
  1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c 
b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c
index 27e5ec9..2633ea1 100644
--- a/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c
+++ b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c
@@ -49,6 +49,7 @@
  #define SSBI_REG_ADDR_GPIO_BASE		0x150
  #define SSBI_REG_ADDR_GPIO(n)		(SSBI_REG_ADDR_GPIO_BASE + n)

+#define	PM8XXX_GPIO_MODE_ENABLE		BIT(0)
  #define PM8XXX_GPIO_WRITE		BIT(7)

  #define PM8XXX_MAX_GPIOS		44
@@ -493,7 +494,8 @@ static int pm8xxx_gpio_config_set(struct pinctrl_dev 
*pctldev,
  	}

  	if (banks & BIT(0))
-		pm8xxx_gpio_write(pctrl, offset, 0, pin->power_source << 1);
+		pm8xxx_gpio_write(pctrl, offset, 0, pin->power_source << 1 |
+				  PM8XXX_GPIO_MODE_ENABLE);

  	if (banks & BIT(1)) {
  		val = pin->direction << 2;
-- 
2.0.2

with this change am able to reset WLAN chip on IFC6410 which is 
connected to gpio43 of pmic.

thanks,
srini
On 11/08/14 16:40, Ivan T. Ivanov wrote:
> From: Bjorn Andersson <bjorn.andersson@sonymobile.com>
>
> This introduces a pinctrl, pinconf, pinmux and gpio driver for the gpio
> block found in pm8018, pm8038, pm8058, pm8917 and pm8921 pmics from
> Qualcomm.
>
> Signed-off-by: Bjorn Andersson <bjorn.andersson@sonymobile.com>
> Signed-off-by: Ivan T. Ivanov <iivanov@mm-sol.com>
> ---
>   drivers/pinctrl/qcom/Kconfig             |  11 +
>   drivers/pinctrl/qcom/Makefile            |   1 +
>   drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c | 870 +++++++++++++++++++++++++++++++
>   3 files changed, 882 insertions(+)
>   create mode 100644 drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c
>
> diff --git a/drivers/pinctrl/qcom/Kconfig b/drivers/pinctrl/qcom/Kconfig
> index d160a71..6bd4e4d 100644
> --- a/drivers/pinctrl/qcom/Kconfig
> +++ b/drivers/pinctrl/qcom/Kconfig
> @@ -39,4 +39,15 @@ 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_SSBI_PMIC
> +       tristate "Qualcomm SSBI PMIC pin controller driver"
> +       depends on GPIOLIB && OF
> +       select PINCONF
> +       select PINMUX
> +       select GENERIC_PINCONF
> +       help
> +         This is the pinctrl, pinmux, pinconf and gpiolib driver for the
> +         Qualcomm GPIO blocks found in the pm8018, pm8038, pm8058, pm8917 and
> +         pm8921 pmics from Qualcomm.
> +
>   endif
> diff --git a/drivers/pinctrl/qcom/Makefile b/drivers/pinctrl/qcom/Makefile
> index 2a02602..8faa2ca 100644
> --- a/drivers/pinctrl/qcom/Makefile
> +++ b/drivers/pinctrl/qcom/Makefile
> @@ -4,3 +4,4 @@ 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_SSBI_PMIC) += pinctrl-ssbi-pmic.o
> diff --git a/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c
> new file mode 100644
> index 0000000..9a1b443
> --- /dev/null
> +++ b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c
> @@ -0,0 +1,870 @@
> +/*
> + * Copyright (c) 2014, Sony Mobile Communications AB.
> + * Copyright (c) 2013, 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/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pinctrl/pinctrl.h>
> +#include <linux/pinctrl/pinmux.h>
> +#include <linux/pinctrl/pinconf.h>
> +#include <linux/pinctrl/pinconf-generic.h>
> +#include <linux/slab.h>
> +#include <linux/regmap.h>
> +#include <linux/gpio.h>
> +#include <linux/mfd/pm8921-core.h>
> +
> +#include <dt-bindings/pinctrl/qcom,pmic-gpio.h>
> +
> +#include "../core.h"
> +#include "../pinconf.h"
> +#include "../pinctrl-utils.h"
> +
> +/* direction */
> +#define PM8XXX_GPIO_DIR_OUT		BIT(0)
> +#define PM8XXX_GPIO_DIR_IN		BIT(1)
> +
> +/* output buffer */
> +#define PM8XXX_GPIO_PUSH_PULL		0
> +#define PM8XXX_GPIO_OPEN_DRAIN		1
> +
> +/* bias */
> +#define PM8XXX_GPIO_BIAS_PU_30		0
> +#define PM8XXX_GPIO_BIAS_PU_1P5		1
> +#define PM8XXX_GPIO_BIAS_PU_31P5	2
> +#define PM8XXX_GPIO_BIAS_PU_1P5_30	3
> +#define PM8XXX_GPIO_BIAS_PD		4
> +#define PM8XXX_GPIO_BIAS_NP		5
> +
> +/* GPIO registers */
> +#define SSBI_REG_ADDR_GPIO_BASE		0x150
> +#define SSBI_REG_ADDR_GPIO(n)		(SSBI_REG_ADDR_GPIO_BASE + n)
> +
> +#define PM8XXX_GPIO_WRITE		BIT(7)
> +
> +#define PM8XXX_MAX_GPIOS		44
> +
> +/* Qualcomm specific pin configurations */
> +#define PM8XXX_PINCONF_PULL_UP		(PIN_CONFIG_END + 1)
> +#define PM8XXX_PINCONF_STRENGTH		(PIN_CONFIG_END + 2)
> +
> +struct pm8xxx_pinbindings {
> +	const char *property;
> +	unsigned param;
> +	u32 default_value;
> +};
> +static struct pm8xxx_pinbindings pm8xxx_pinbindings[] = {
> +	/* PMIC_GPIO_PULL_UP_30...  */
> +	{"qcom,pull-up-strength",	PM8XXX_PINCONF_PULL_UP, 0},
> +	/* PMIC_GPIO_STRENGTH_NO... */
> +	{"qcom,drive-strength",		PM8XXX_PINCONF_STRENGTH, 0},
> +};
> +
> +struct pm8xxx_gpio_pin {
> +	int irq;
> +
> +	u8 power_source;
> +	u8 direction;
> +	u8 output_buffer;
> +	u8 output_value;
> +	u8 bias;
> +	u8 output_strength;
> +	u8 disable;
> +	u8 function;
> +	u8 non_inverted;
> +};
> +
> +struct pm8xxx_gpio_data {
> +	int ngpio;
> +};
> +
> +struct pm8xxx_gpio {
> +	struct device *dev;
> +	struct regmap *regmap;
> +	struct pinctrl_dev *pctrl;
> +	struct gpio_chip chip;
> +
> +	const struct pm8xxx_gpio_data *data;
> +
> +	struct pm8xxx_gpio_pin pins[PM8XXX_MAX_GPIOS];
> +};
> +
> +static inline struct pm8xxx_gpio *to_pm8xxx_gpio(struct gpio_chip *chip)
> +{
> +	return container_of(chip, struct pm8xxx_gpio, chip);
> +};
> +
> +static const char * const pm8xxx_gpio_groups[PM8XXX_MAX_GPIOS] = {
> +	"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",
> +	"gpio37", "gpio38", "gpio39", "gpio40", "gpio41", "gpio42", "gpio43",
> +	"gpio44",
> +};
> +
> +static const char * const pm8xxx_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 int pm8xxx_gpio_read(struct pm8xxx_gpio *pctrl, int pin, int bank)
> +{
> +	int reg = SSBI_REG_ADDR_GPIO(pin);
> +	unsigned int val = bank << 4;
> +	int ret;
> +
> +	ret = regmap_write(pctrl->regmap, reg, val);
> +	if (ret) {
> +		dev_err(pctrl->dev,
> +			"failed to select bank %d of pin %d\n", bank, pin);
> +		return ret;
> +	}
> +
> +	ret = regmap_read(pctrl->regmap, reg, &val);
> +	if (ret) {
> +		dev_err(pctrl->dev,
> +			"failed to read register %d of pin %d\n", bank, pin);
> +		return ret;
> +	}
> +
> +	return val;
> +}
> +
> +static int pm8xxx_gpio_write(struct pm8xxx_gpio *pctrl,
> +			     int pin, int bank, u8 val)
> +{
> +	int ret;
> +
> +	val |= PM8XXX_GPIO_WRITE;
> +	val |= bank << 4;
> +
> +	ret = regmap_write(pctrl->regmap, SSBI_REG_ADDR_GPIO(pin), val);
> +	if (ret)
> +		dev_err(pctrl->dev, "failed to write register\n");
> +
> +	return ret;
> +}
> +
> +static int pm8xxx_gpio_get_groups_count(struct pinctrl_dev *pctldev)
> +{
> +	struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
> +
> +	return pctrl->data->ngpio;
> +}
> +
> +static const char *pm8xxx_gpio_get_group_name(struct pinctrl_dev *pctldev,
> +				      unsigned group)
> +{
> +	return pm8xxx_gpio_groups[group];
> +}
> +
> +static int pm8xxx_parse_dt_config(struct device *dev, struct device_node *np,
> +			unsigned long **configs, unsigned int *nconfigs)
> +{
> +	struct pm8xxx_pinbindings *par;
> +	unsigned long cfg[ARRAY_SIZE(pm8xxx_pinbindings)];
> +	unsigned int ncfg = 0;
> +	int ret, idx;
> +	u32 val;
> +
> +	if (!np)
> +		return -EINVAL;
> +
> +	for (idx = 0; idx < ARRAY_SIZE(pm8xxx_pinbindings); idx++) {
> +		par = &pm8xxx_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(dev, "found %s with value %u\n", par->property, val);
> +		cfg[ncfg] = pinconf_to_config_packed(par->param, val);
> +		ncfg++;
> +	}
> +
> +	ret = 0;
> +
> +	/* no configs found at qchip->npads */
> +	if (ncfg == 0) {
> +		*configs = NULL;
> +		*nconfigs = 0;
> +		goto out;
> +	}
> +
> +	/*
> +	 * Now limit the number of configs to the real number of
> +	 * found properties.
> +	 */
> +	*configs = kcalloc(ncfg, sizeof(unsigned long), GFP_KERNEL);
> +	if (!*configs) {
> +		ret = -ENOMEM;
> +		goto out;
> +	}
> +
> +	memcpy(*configs, cfg, ncfg * sizeof(unsigned long));
> +	*nconfigs = ncfg;
> +
> +out:
> +	return ret;
> +}
> +
> +static int pm8xxx_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 num_configs = 0;
> +	struct property *prop;
> +	const char *group;
> +	int ret;
> +
> +	ret = pm8xxx_parse_dt_config(pctldev->dev, np, &configs, &num_configs);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (!num_configs)
> +		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,
> +				num_configs, type);
> +		if (ret < 0)
> +			break;
> +	}
> +exit:
> +	kfree(configs);
> +	return ret;
> +}
> +
> +static int pm8xxx_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 = pm8xxx_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 pm8xxx_gpio_pinctrl_ops = {
> +	.get_groups_count	= pm8xxx_gpio_get_groups_count,
> +	.get_group_name		= pm8xxx_gpio_get_group_name,
> +	.dt_node_to_map		= pm8xxx_dt_node_to_map,
> +	.dt_free_map		= pinctrl_utils_dt_free_map,
> +};
> +
> +static int pm8xxx_get_functions_count(struct pinctrl_dev *pctldev)
> +{
> +	return ARRAY_SIZE(pm8xxx_gpio_functions);
> +}
> +
> +static const char *pm8xxx_get_function_name(struct pinctrl_dev *pctldev,
> +					 unsigned function)
> +{
> +	return pm8xxx_gpio_functions[function];
> +}
> +
> +static int pm8xxx_get_function_groups(struct pinctrl_dev *pctldev,
> +				   unsigned function,
> +				   const char * const **groups,
> +				   unsigned * const num_groups)
> +{
> +	struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
> +
> +	*groups = pm8xxx_gpio_groups;
> +	*num_groups = pctrl->data->ngpio;
> +	return 0;
> +}
> +
> +static int pm8xxx_pinmux_enable(struct pinctrl_dev *pctldev,
> +			     unsigned function,
> +			     unsigned group)
> +{
> +	struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[group];
> +	u8 val;
> +
> +	pin->function = function;
> +	val = pin->function << 1;
> +
> +	pm8xxx_gpio_write(pctrl, group, 4, val);
> +
> +	return 0;
> +}
> +
> +static const struct pinmux_ops pm8xxx_pinmux_ops = {
> +	.get_functions_count	= pm8xxx_get_functions_count,
> +	.get_function_name	= pm8xxx_get_function_name,
> +	.get_function_groups	= pm8xxx_get_function_groups,
> +	.enable			= pm8xxx_pinmux_enable,
> +};
> +
> +static int pm8xxx_gpio_config_get(struct pinctrl_dev *pctldev,
> +			  unsigned int offset,
> +			  unsigned long *config)
> +{
> +	struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset];
> +	unsigned param = pinconf_to_config_param(*config);
> +	unsigned arg;
> +
> +	switch (param) {
> +	case PIN_CONFIG_BIAS_DISABLE:
> +		arg = pin->bias == PM8XXX_GPIO_BIAS_NP;
> +		break;
> +	case PIN_CONFIG_BIAS_PULL_DOWN:
> +		arg = pin->bias == PM8XXX_GPIO_BIAS_PD;
> +		break;
> +	case PM8XXX_PINCONF_PULL_UP:
> +		if (pin->bias >= PM8XXX_GPIO_BIAS_PU_30 &&
> +		    pin->bias <= PM8XXX_GPIO_BIAS_PU_1P5_30)
> +			arg = PMIC_GPIO_PULL_UP_30 + pin->bias;
> +		else
> +			arg = 0;
> +		break;
> +	case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
> +		arg = pin->disable;
> +		break;
> +	case PIN_CONFIG_INPUT_ENABLE:
> +		arg = pin->direction == PM8XXX_GPIO_DIR_IN;
> +		break;
> +	case PIN_CONFIG_OUTPUT:
> +		arg = pin->output_value;
> +		break;
> +	case PIN_CONFIG_POWER_SOURCE:
> +		arg = pin->power_source;
> +		break;
> +	case PM8XXX_PINCONF_STRENGTH:
> +		arg = pin->output_strength;
> +		break;
> +	case PIN_CONFIG_DRIVE_PUSH_PULL:
> +		arg = pin->output_buffer == PM8XXX_GPIO_PUSH_PULL;
> +		break;
> +	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
> +		arg = pin->output_buffer == PM8XXX_GPIO_OPEN_DRAIN;
> +		break;
> +	default:
> +		dev_err(pctrl->dev,
> +			"unsupported config parameter: %x\n",
> +			param);
> +		return -EINVAL;
> +	}
> +
> +	*config = pinconf_to_config_packed(param, arg);
> +
> +	return 0;
> +}
> +
> +static int pm8xxx_gpio_config_set(struct pinctrl_dev *pctldev,
> +				  unsigned int offset,
> +				  unsigned long *configs,
> +				  unsigned num_configs)
> +{
> +	struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset];
> +	unsigned param;
> +	unsigned arg;
> +	unsigned i;
> +	u8 banks = 0;
> +	u8 val;
> +
> +	for (i = 0; i < num_configs; i++) {
> +		param = pinconf_to_config_param(configs[i]);
> +		arg = pinconf_to_config_argument(configs[i]);
> +
> +		switch (param) {
> +		case PIN_CONFIG_BIAS_DISABLE:
> +			pin->bias = PM8XXX_GPIO_BIAS_NP;
> +			banks |= BIT(2);
> +			pin->disable = 0;
> +			banks |= BIT(3);
> +			break;
> +		case PIN_CONFIG_BIAS_PULL_DOWN:
> +			pin->bias = PM8XXX_GPIO_BIAS_PD;
> +			banks |= BIT(2);
> +			pin->disable = 0;
> +			banks |= BIT(3);
> +			break;
> +		case PM8XXX_PINCONF_PULL_UP:
> +			if (arg < PMIC_GPIO_PULL_UP_30 ||
> +			    arg > PMIC_GPIO_PULL_UP_1P5_30) {
> +				dev_err(pctrl->dev, "invalid pull-up level\n");
> +				return -EINVAL;
> +			}
> +			pin->bias = arg - PM8XXX_GPIO_BIAS_PU_30;
> +			banks |= BIT(2);
> +			pin->disable = 0;
> +			banks |= BIT(3);
> +			break;
> +		case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
> +			pin->disable = 1;
> +			banks |= BIT(3);
> +			break;
> +		case PIN_CONFIG_INPUT_ENABLE:
> +			pin->direction = PM8XXX_GPIO_DIR_IN;
> +			banks |= BIT(1);
> +			break;
> +		case PIN_CONFIG_OUTPUT:
> +			pin->direction = PM8XXX_GPIO_DIR_OUT;
> +			pin->output_value = !!arg;
> +			banks |= BIT(1);
> +			break;
> +		case PIN_CONFIG_POWER_SOURCE:
> +			pin->power_source = arg;
> +			banks |= BIT(0);
> +			break;
> +		case PM8XXX_PINCONF_STRENGTH:
> +			if (arg > PMIC_GPIO_STRENGTH_LOW) {
> +				dev_err(pctrl->dev, "invalid drive strength\n");
> +				return -EINVAL;
> +			}
> +			pin->output_strength = arg;
> +			banks |= BIT(3);
> +			break;
> +		case PIN_CONFIG_DRIVE_PUSH_PULL:
> +			pin->output_buffer = PM8XXX_GPIO_PUSH_PULL;
> +			banks |= BIT(1);
> +			break;
> +		case PIN_CONFIG_DRIVE_OPEN_DRAIN:
> +			pin->output_buffer = PM8XXX_GPIO_OPEN_DRAIN;
> +			banks |= BIT(1);
> +			break;
> +		default:
> +			dev_err(pctrl->dev,
> +					"unsupported config parameter: %x\n",
> +					param);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	if (banks & BIT(0))
> +		pm8xxx_gpio_write(pctrl, offset, 0, pin->power_source << 1);
> +
> +	if (banks & BIT(1)) {
> +		val = pin->direction << 2;
> +		val |= pin->output_buffer << 1;
> +		val |= pin->output_value;
> +		pm8xxx_gpio_write(pctrl, offset, 1, val);
> +	}
> +
> +	if (banks & BIT(2)) {
> +		val = pin->bias << 1;
> +		pm8xxx_gpio_write(pctrl, offset, 2, val);
> +	}
> +
> +	if (banks & BIT(3)) {
> +		val = pin->output_strength << 2;
> +		val |= pin->disable;
> +		pm8xxx_gpio_write(pctrl, offset, 3, val);
> +	}
> +
> +	if (banks & BIT(4)) {
> +		val = pin->function << 1;
> +		pm8xxx_gpio_write(pctrl, offset, 4, val);
> +	}
> +
> +	if (banks & BIT(5)) {
> +		val = 0;
> +		if (pin->non_inverted)
> +			val |= BIT(3);
> +		pm8xxx_gpio_write(pctrl, offset, 5, val);
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct pinconf_ops pm8xxx_gpio_pinconf_ops = {
> +	.pin_config_group_get = pm8xxx_gpio_config_get,
> +	.pin_config_group_set = pm8xxx_gpio_config_set,
> +};
> +
> +static struct pinctrl_desc pm8xxx_gpio_desc = {
> +	.pctlops = &pm8xxx_gpio_pinctrl_ops,
> +	.pmxops = &pm8xxx_pinmux_ops,
> +	.confops = &pm8xxx_gpio_pinconf_ops,
> +	.owner = THIS_MODULE,
> +};
> +
> +static int pm8xxx_gpio_direction_input(struct gpio_chip *chip,
> +				       unsigned offset)
> +{
> +	struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
> +	u8 val;
> +
> +	pin->direction = PM8XXX_GPIO_DIR_IN;
> +	val = pin->direction << 2;
> +
> +	pm8xxx_gpio_write(pctrl, offset, 1, val);
> +
> +	return 0;
> +}
> +
> +static int pm8xxx_gpio_direction_output(struct gpio_chip *chip,
> +					unsigned offset,
> +					int value)
> +{
> +	struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
> +	u8 val;
> +
> +	pin->direction = PM8XXX_GPIO_DIR_OUT;
> +	pin->output_value = !!value;
> +
> +	val = pin->direction << 2;
> +	val |= pin->output_buffer << 1;
> +	val |= pin->output_value;
> +
> +	pm8xxx_gpio_write(pctrl, offset, 1, val);
> +
> +	return 0;
> +}
> +
> +static int pm8xxx_gpio_get(struct gpio_chip *chip, unsigned offset)
> +{
> +	struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
> +
> +	if (pin->direction == PM8XXX_GPIO_DIR_OUT)
> +		return pin->output_value;
> +
> +	return pm8xxx_read_irq_status(pin->irq);
> +}
> +
> +static void pm8xxx_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
> +{
> +	struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(gc);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
> +	u8 val;
> +
> +	pin->output_value = !!value;
> +
> +	val = pin->direction << 2;
> +	val |= pin->output_buffer << 1;
> +	val |= pin->output_value;
> +
> +	pm8xxx_gpio_write(pctrl, offset, 1, val);
> +}
> +
> +static int pm8xxx_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
> +{
> +	struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
> +
> +	return pin->irq;
> +}
> +
> +#ifdef CONFIG_DEBUG_FS
> +#include <linux/seq_file.h>
> +
> +static void pm8xxx_gpio_dbg_show_one(struct seq_file *s,
> +				  struct pinctrl_dev *pctldev,
> +				  struct gpio_chip *chip,
> +				  unsigned offset,
> +				  unsigned gpio)
> +{
> +	struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
> +	struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset];
> +
> +	static const char * const directions[] = {
> +		"off", "out", "in", "both"
> +	};
> +	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"
> +	};
> +	static const char * const strengths[] = {
> +		"no", "high", "medium", "low"
> +	};
> +
> +	seq_printf(s, " gpio%-2d:", offset + 1);
> +	if (pin->disable) {
> +		seq_puts(s, " ---");
> +	} else {
> +		seq_printf(s, " %-4s", directions[pin->direction]);
> +		seq_printf(s, " %-7s", pm8xxx_gpio_functions[pin->function]);
> +		seq_printf(s, " VIN%d", pin->power_source);
> +		seq_printf(s, " %-27s", biases[pin->bias]);
> +		seq_printf(s, " %-10s", buffer_types[pin->output_buffer]);
> +		seq_printf(s, " %-4s", pin->output_value ? "high" : "low");
> +		seq_printf(s, " %-7s", strengths[pin->output_strength]);
> +		if (!pin->non_inverted)
> +			seq_puts(s, " inverted");
> +	}
> +}
> +
> +static void pm8xxx_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
> +{
> +	unsigned gpio = chip->base;
> +	unsigned i;
> +
> +	for (i = 0; i < chip->ngpio; i++, gpio++) {
> +		pm8xxx_gpio_dbg_show_one(s, NULL, chip, i, gpio);
> +		seq_puts(s, "\n");
> +	}
> +}
> +
> +#else
> +#define msm_gpio_dbg_show NULL
> +#endif
> +
> +static struct gpio_chip pm8xxx_gpio_template = {
> +	.direction_input = pm8xxx_gpio_direction_input,
> +	.direction_output = pm8xxx_gpio_direction_output,
> +	.get = pm8xxx_gpio_get,
> +	.set = pm8xxx_gpio_set,
> +	.to_irq = pm8xxx_gpio_to_irq,
> +	.dbg_show = pm8xxx_gpio_dbg_show,
> +	.owner = THIS_MODULE,
> +};
> +
> +static int pm8xxx_gpio_populate(struct pm8xxx_gpio *pctrl)
> +{
> +	struct pm8xxx_gpio_pin *pin;
> +	int val;
> +	int i;
> +
> +	for (i = 0; i < pctrl->data->ngpio; i++) {
> +		pin = &pctrl->pins[i];
> +
> +		val = pm8xxx_gpio_read(pctrl, i, 0);
> +		if (val < 0)
> +			return val;
> +
> +		pin->power_source = (val >> 1) & 0x7;
> +
> +		val = pm8xxx_gpio_read(pctrl, i, 1);
> +		if (val < 0)
> +			return val;
> +
> +		pin->direction = (val >> 2) & 0x3;
> +		pin->output_buffer = !!(val & BIT(1));
> +		pin->output_value = val & BIT(0);
> +
> +		val = pm8xxx_gpio_read(pctrl, i, 2);
> +		if (val < 0)
> +			return val;
> +
> +		pin->bias = (val >> 1) & 0x7;
> +
> +		val = pm8xxx_gpio_read(pctrl, i, 3);
> +		if (val < 0)
> +			return val;
> +
> +		pin->output_strength = (val >> 2) & 0x3;
> +		pin->disable = val & BIT(0);
> +
> +		val = pm8xxx_gpio_read(pctrl, i, 4);
> +		if (val < 0)
> +			return val;
> +
> +		pin->function = (val >> 1) & 0x7;
> +
> +		val = pm8xxx_gpio_read(pctrl, i, 5);
> +		if (val < 0)
> +			return val;
> +
> +		pin->non_inverted = !!(val & BIT(3));
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct pm8xxx_gpio_data pm8018_gpio_data = {
> +	.ngpio = 6,
> +};
> +
> +static const struct pm8xxx_gpio_data pm8038_gpio_data = {
> +	.ngpio = 12,
> +};
> +
> +static const struct pm8xxx_gpio_data pm8058_gpio_data = {
> +	.ngpio = 40,
> +};
> +static const struct pm8xxx_gpio_data pm8917_gpio_data = {
> +	.ngpio = 38,
> +};
> +
> +static const struct pm8xxx_gpio_data pm8921_gpio_data = {
> +	.ngpio = 44,
> +};
> +
> +static const struct of_device_id pm8xxx_gpio_of_match[] = {
> +	{ .compatible = "qcom,pm8018-gpio", .data = &pm8018_gpio_data },
> +	{ .compatible = "qcom,pm8038-gpio", .data = &pm8038_gpio_data },
> +	{ .compatible = "qcom,pm8058-gpio", .data = &pm8058_gpio_data },
> +	{ .compatible = "qcom,pm8917-gpio", .data = &pm8917_gpio_data },
> +	{ .compatible = "qcom,pm8921-gpio", .data = &pm8921_gpio_data },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, pm8xxx_gpio_of_match);
> +
> +static int pm8xxx_gpio_probe(struct platform_device *pdev)
> +{
> +	const struct of_device_id *match;
> +	struct pm8xxx_gpio *pctrl;
> +	int ret;
> +	int i;
> +
> +	match = of_match_node(pm8xxx_gpio_of_match, pdev->dev.of_node);
> +	if (!match)
> +		return -ENXIO;
> +
> +	pctrl = devm_kzalloc(&pdev->dev, sizeof(*pctrl), GFP_KERNEL);
> +	if (!pctrl)
> +		return -ENOMEM;
> +
> +	pctrl->dev = &pdev->dev;
> +	pctrl->data = match->data;
> +
> +	BUG_ON(pctrl->data->ngpio > PM8XXX_MAX_GPIOS);
> +
> +	pctrl->chip = pm8xxx_gpio_template;
> +	pctrl->chip.base = -1;
> +	pctrl->chip.dev = &pdev->dev;
> +	pctrl->chip.of_node = pdev->dev.of_node;
> +	pctrl->chip.label = dev_name(pctrl->dev);
> +	pctrl->chip.ngpio = pctrl->data->ngpio;
> +
> +	pctrl->regmap = dev_get_regmap(pdev->dev.parent, NULL);
> +	if (!pctrl->regmap) {
> +		dev_err(&pdev->dev, "parent regmap unavailable\n");
> +		return -ENXIO;
> +	}
> +
> +	for (i = 0; i < pctrl->data->ngpio; i++) {
> +		ret = platform_get_irq(pdev, i);
> +		if (ret < 0) {
> +			dev_err(&pdev->dev,
> +				"missing interrupts for pin %d\n", i);
> +			return ret;
> +		}
> +
> +		pctrl->pins[i].irq = ret;
> +	}
> +
> +	ret = pm8xxx_gpio_populate(pctrl);
> +	if (ret)
> +		return ret;
> +
> +	pm8xxx_gpio_desc.name = dev_name(&pdev->dev);
> +	pctrl->pctrl = pinctrl_register(&pm8xxx_gpio_desc, &pdev->dev, pctrl);
> +	if (!pctrl->pctrl) {
> +		dev_err(&pdev->dev, "couldn't register pm8xxx gpio driver\n");
> +		return -ENODEV;
> +	}
> +
> +	ret = gpiochip_add(&pctrl->chip);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed register gpiochip\n");
> +		goto unregister_pinctrl;
> +	}
> +
> +	ret = gpiochip_add_pin_range(&pctrl->chip,
> +				     dev_name(pctrl->dev),
> +				     1, 0, pctrl->data->ngpio);
> +	if (ret) {
> +		dev_err(pctrl->dev, "failed to add pin range\n");
> +		goto unregister_gpiochip;
> +	}
> +
> +	platform_set_drvdata(pdev, pctrl);
> +
> +	dev_dbg(&pdev->dev, "Qualcomm pm8xxx gpio driver probed\n");
> +
> +	return 0;
> +
> +unregister_pinctrl:
> +	pinctrl_unregister(pctrl->pctrl);
> +
> +unregister_gpiochip:
> +	if (gpiochip_remove(&pctrl->chip))
> +		dev_err(&pdev->dev, "unable to unregister gpiochip\n");
> +
> +	return ret;
> +}
> +
> +static int pm8xxx_gpio_remove(struct platform_device *pdev)
> +{
> +	struct pm8xxx_gpio *pctrl = platform_get_drvdata(pdev);
> +
> +	gpiochip_remove(&pctrl->chip);
> +
> +	pinctrl_unregister(pctrl->pctrl);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver pm8xxx_gpio_driver = {
> +	.driver = {
> +		.name = "ssbi-pmic-gpio",
> +		.owner = THIS_MODULE,
> +		.of_match_table = pm8xxx_gpio_of_match,
> +	},
> +	.probe = pm8xxx_gpio_probe,
> +	.remove = pm8xxx_gpio_remove,
> +};
> +
> +module_platform_driver(pm8xxx_gpio_driver);
> +
> +MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
> +MODULE_DESCRIPTION("Qualcomm SSBI PMIC GPIO driver");
> +MODULE_LICENSE("GPL v2");
>

  parent reply	other threads:[~2014-08-20  8:06 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2014-08-11 15:40 [PATCH v3 0/6] Qualcomm PMIC pin controller drivers Ivan T. Ivanov
2014-08-11 15:40 ` [PATCH v3 1/6] pinctrl: Device tree bindings for Qualcomm pm8xxx gpio block Ivan T. Ivanov
2014-08-16 15:24   ` Daniel
2014-08-18  7:16     ` Ivan T. Ivanov
2014-08-20 22:10       ` Bjorn Andersson
2014-08-20 22:27   ` Bjorn Andersson
2014-08-25 13:14     ` Ivan T. Ivanov
2014-08-11 15:40 ` [PATCH v3 2/6] pinctrl: Introduce pinctrl driver for Qualcomm SSBI PMIC's Ivan T. Ivanov
     [not found]   ` <1407771634-14946-3-git-send-email-iivanov-NEYub+7Iv8PQT0dZR+AlfA@public.gmane.org>
2014-08-20  8:06     ` Srinivas Kandagatla [this message]
2014-08-20  8:06       ` Srinivas Kandagatla
2014-08-20 21:28       ` Bjorn Andersson
2014-08-20 22:13         ` Bjorn Andersson
2014-11-03  8:48           ` Srinivas Kandagatla
2014-11-04  2:25             ` Bjorn Andersson
2014-08-11 15:40 ` [PATCH v3 3/6] pinctrl: Add documentation for SPMI PMIC pinctrl driver bindings Ivan T. Ivanov
2014-08-13 14:32   ` Stephen Boyd
2014-08-25 14:07     ` Ivan T. Ivanov
2014-08-11 15:40 ` [PATCH v3 4/6] pinctrl: Qualcomm SPMI PMIC pin controller driver Ivan T. Ivanov
2014-08-21  6:16   ` Bjorn Andersson
     [not found]     ` <20140821061607.GF16274-/MT0OVThwyLZJqsBc5GL+g@public.gmane.org>
2014-08-25 14:00       ` Ivan T. Ivanov
2014-08-25 14:00         ` Ivan T. Ivanov
2014-08-11 15:40 ` [PATCH v3 5/6] ARM: dts: qcom: Add PM8941 and PM8841 pinctrl nodes Ivan T. Ivanov
2014-08-14  7:33   ` Pramod Gurav
2014-08-25 14:06     ` Ivan T. Ivanov
2014-08-11 15:40 ` [PATCH v3 6/6] ARM: dts: qcom: Add APQ8074 Dragonboard PMIC GPIO bindings Ivan T. Ivanov
2014-08-20 23:06   ` Bjorn Andersson
2014-08-25 14:04     ` Ivan T. Ivanov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=53F456F3.3000309@linaro.org \
    --to=srinivas.kandagatla-qsej5fyqhm4dnm+yrofe0a@public.gmane.org \
    --cc=bjorn.andersson-/MT0OVThwyLZJqsBc5GL+g@public.gmane.org \
    --cc=devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=grant.likely-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org \
    --cc=iivanov-NEYub+7Iv8PQT0dZR+AlfA@public.gmane.org \
    --cc=linus.walleij-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org \
    --cc=linux-arm-msm-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.