diff options
| author | Mateusz Kulikowski <mateusz.kulikowski@gmail.com> | 2016-03-31 23:12:31 +0200 | 
|---|---|---|
| committer | Tom Rini <trini@konsulko.com> | 2016-04-01 17:18:13 -0400 | 
| commit | 120800df720095a3c60c72a6bdcf6904dc3c4b20 (patch) | |
| tree | 0b31215304a939c7a785bd796a7ac8c90126a5c3 /drivers/gpio | |
| parent | c2f74c8f53bd2333d85d5063e9d043382df2a987 (diff) | |
gpio: Add support for Qualcomm PM8916 gpios
This driver supports GPIOs present on PM8916 PMIC.
There are 2 device drivers inside:
- GPIO driver (4 "generic" GPIOs)
- Keypad driver that presents itself as GPIO with 2 inputs (power and reset)
Signed-off-by: Mateusz Kulikowski <mateusz.kulikowski@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
Tested-by: Simon Glass <sjg@chromium.org>
Diffstat (limited to 'drivers/gpio')
| -rw-r--r-- | drivers/gpio/Kconfig | 10 | ||||
| -rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
| -rw-r--r-- | drivers/gpio/pm8916_gpio.c | 302 | 
3 files changed, 313 insertions, 0 deletions
| diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 4d9e74c009e..f56a60621ff 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -69,6 +69,16 @@ config MSM_GPIO  	  - APQ8016  	  - MSM8916 +config PM8916_GPIO +	bool "Qualcomm PM8916 PMIC GPIO/keypad driver" +	depends on DM_GPIO && PMIC_PM8916 +	help +	  Support for GPIO pins and power/reset buttons found on +	  Qualcomm PM8916 PMIC. +	  Default name for GPIO bank is "pm8916". +	  Power and reset buttons are placed in "pm8916_key" bank and +          have gpio numbers 0 and 1 respectively. +  config ROCKCHIP_GPIO  	bool "Rockchip GPIO driver"  	depends on DM_GPIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 4162c3c702d..4f071c45172 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -51,3 +51,4 @@ obj-$(CONFIG_HIKEY_GPIO)	+= hi6220_gpio.o  obj-$(CONFIG_PIC32_GPIO)	+= pic32_gpio.o  obj-$(CONFIG_MVEBU_GPIO)	+= mvebu_gpio.o  obj-$(CONFIG_MSM_GPIO)		+= msm_gpio.o +obj-$(CONFIG_PM8916_GPIO)	+= pm8916_gpio.o diff --git a/drivers/gpio/pm8916_gpio.c b/drivers/gpio/pm8916_gpio.c new file mode 100644 index 00000000000..1abab7fef17 --- /dev/null +++ b/drivers/gpio/pm8916_gpio.c @@ -0,0 +1,302 @@ +/* + * Qualcomm pm8916 pmic gpio driver - part of Qualcomm PM8916 PMIC + * + * (C) Copyright 2015 Mateusz Kulikowski <mateusz.kulikowski@gmail.com> + * + * SPDX-License-Identifier:	GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <power/pmic.h> +#include <spmi/spmi.h> +#include <asm/io.h> +#include <asm/gpio.h> +#include <linux/bitops.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* Register offset for each gpio */ +#define REG_OFFSET(x)          ((x) * 0x100) + +/* Register maps */ + +/* Type and subtype are shared for all pm8916 peripherals */ +#define REG_TYPE               0x4 +#define REG_SUBTYPE            0x5 + +#define REG_STATUS             0x08 +#define REG_STATUS_VAL_MASK    0x1 + +/* MODE_CTL */ +#define REG_CTL           0x40 +#define REG_CTL_MODE_MASK       0x70 +#define REG_CTL_MODE_INPUT      0x00 +#define REG_CTL_MODE_INOUT      0x20 +#define REG_CTL_MODE_OUTPUT     0x10 +#define REG_CTL_OUTPUT_MASK     0x0F + +#define REG_DIG_VIN_CTL        0x41 +#define REG_DIG_VIN_VIN0       0 + +#define REG_DIG_PULL_CTL       0x42 +#define REG_DIG_PULL_NO_PU     0x5 + +#define REG_DIG_OUT_CTL        0x45 +#define REG_DIG_OUT_CTL_CMOS   (0x0 << 4) +#define REG_DIG_OUT_CTL_DRIVE_L 0x1 + +#define REG_EN_CTL             0x46 +#define REG_EN_CTL_ENABLE      (1 << 7) + +struct pm8916_gpio_bank { +	uint16_t pid; /* Peripheral ID on SPMI bus */ +}; + +static int pm8916_gpio_set_direction(struct udevice *dev, unsigned offset, +				     bool input, int value) +{ +	struct pm8916_gpio_bank *priv = dev_get_priv(dev); +	uint32_t gpio_base = priv->pid + REG_OFFSET(offset); +	int ret; + +	/* Disable the GPIO */ +	ret = pmic_clrsetbits(dev->parent, gpio_base + REG_EN_CTL, +			      REG_EN_CTL_ENABLE, 0); +	if (ret < 0) +		return ret; + +	/* Select the mode */ +	if (input) +		ret = pmic_reg_write(dev->parent, gpio_base + REG_CTL, +				     REG_CTL_MODE_INPUT); +	else +		ret = pmic_reg_write(dev->parent, gpio_base + REG_CTL, +				     REG_CTL_MODE_INOUT | (value ? 1 : 0)); +	if (ret < 0) +		return ret; + +	/* Set the right pull (no pull) */ +	ret = pmic_reg_write(dev->parent, gpio_base + REG_DIG_PULL_CTL, +			     REG_DIG_PULL_NO_PU); +	if (ret < 0) +		return ret; + +	/* Configure output pin drivers if needed */ +	if (!input) { +		/* Select the VIN - VIN0, pin is input so it doesn't matter */ +		ret = pmic_reg_write(dev->parent, gpio_base + REG_DIG_VIN_CTL, +				     REG_DIG_VIN_VIN0); +		if (ret < 0) +			return ret; + +		/* Set the right dig out control */ +		ret = pmic_reg_write(dev->parent, gpio_base + REG_DIG_OUT_CTL, +				     REG_DIG_OUT_CTL_CMOS | +				     REG_DIG_OUT_CTL_DRIVE_L); +		if (ret < 0) +			return ret; +	} + +	/* Enable the GPIO */ +	return pmic_clrsetbits(dev->parent, gpio_base + REG_EN_CTL, 0, +			       REG_EN_CTL_ENABLE); +} + +static int pm8916_gpio_direction_input(struct udevice *dev, unsigned offset) +{ +	return pm8916_gpio_set_direction(dev, offset, true, 0); +} + +static int pm8916_gpio_direction_output(struct udevice *dev, unsigned offset, +					int value) +{ +	return pm8916_gpio_set_direction(dev, offset, false, value); +} + +static int pm8916_gpio_get_function(struct udevice *dev, unsigned offset) +{ +	struct pm8916_gpio_bank *priv = dev_get_priv(dev); +	uint32_t gpio_base = priv->pid + REG_OFFSET(offset); +	int reg; + +	/* Set the output value of the gpio */ +	reg = pmic_reg_read(dev->parent, gpio_base + REG_CTL); +	if (reg < 0) +		return reg; + +	switch (reg & REG_CTL_MODE_MASK) { +	case REG_CTL_MODE_INPUT: +		return GPIOF_INPUT; +	case REG_CTL_MODE_INOUT: /* Fallthrough */ +	case REG_CTL_MODE_OUTPUT: +		return GPIOF_OUTPUT; +	default: +		return GPIOF_UNKNOWN; +	} +} + +static int pm8916_gpio_get_value(struct udevice *dev, unsigned offset) +{ +	struct pm8916_gpio_bank *priv = dev_get_priv(dev); +	uint32_t gpio_base = priv->pid + REG_OFFSET(offset); +	int reg; + +	reg = pmic_reg_read(dev->parent, gpio_base + REG_STATUS); +	if (reg < 0) +		return reg; + +	return !!(reg & REG_STATUS_VAL_MASK); +} + +static int pm8916_gpio_set_value(struct udevice *dev, unsigned offset, +				 int value) +{ +	struct pm8916_gpio_bank *priv = dev_get_priv(dev); +	uint32_t gpio_base = priv->pid + REG_OFFSET(offset); + +	/* Set the output value of the gpio */ +	return pmic_clrsetbits(dev->parent, gpio_base + REG_CTL, +			       REG_CTL_OUTPUT_MASK, !!value); +} + +static const struct dm_gpio_ops pm8916_gpio_ops = { +	.direction_input	= pm8916_gpio_direction_input, +	.direction_output	= pm8916_gpio_direction_output, +	.get_value		= pm8916_gpio_get_value, +	.set_value		= pm8916_gpio_set_value, +	.get_function		= pm8916_gpio_get_function, +}; + +static int pm8916_gpio_probe(struct udevice *dev) +{ +	struct pm8916_gpio_bank *priv = dev_get_priv(dev); +	int reg; + +	priv->pid = dev_get_addr(dev); +	if (priv->pid == FDT_ADDR_T_NONE) +		return -EINVAL; + +	/* Do a sanity check */ +	reg = pmic_reg_read(dev->parent, priv->pid + REG_TYPE); +	if (reg != 0x10) +		return -ENODEV; + +	reg = pmic_reg_read(dev->parent, priv->pid + REG_SUBTYPE); +	if (reg != 0x5) +		return -ENODEV; + +	return 0; +} + +static int pm8916_gpio_ofdata_to_platdata(struct udevice *dev) +{ +	struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); + +	uc_priv->gpio_count = fdtdec_get_int(gd->fdt_blob, dev->of_offset, +					     "gpio-count", 0); +	uc_priv->bank_name = fdt_getprop(gd->fdt_blob, dev->of_offset, +					 "gpio-bank-name", NULL); +	if (uc_priv->bank_name == NULL) +		uc_priv->bank_name = "pm8916"; + +	return 0; +} + +static const struct udevice_id pm8916_gpio_ids[] = { +	{ .compatible = "qcom,pm8916-gpio" }, +	{ } +}; + +U_BOOT_DRIVER(gpio_pm8916) = { +	.name	= "gpio_pm8916", +	.id	= UCLASS_GPIO, +	.of_match = pm8916_gpio_ids, +	.ofdata_to_platdata = pm8916_gpio_ofdata_to_platdata, +	.probe	= pm8916_gpio_probe, +	.ops	= &pm8916_gpio_ops, +	.priv_auto_alloc_size = sizeof(struct pm8916_gpio_bank), +}; + + +/* Add pmic buttons as GPIO as well - there is no generic way for now */ +#define PON_INT_RT_STS                        0x10 +#define KPDPWR_ON_INT_BIT                     0 +#define RESIN_ON_INT_BIT                      1 + +static int pm8941_pwrkey_get_function(struct udevice *dev, unsigned offset) +{ +	return GPIOF_INPUT; +} + +static int pm8941_pwrkey_get_value(struct udevice *dev, unsigned offset) +{ +	struct pm8916_gpio_bank *priv = dev_get_priv(dev); + +	int reg = pmic_reg_read(dev->parent, priv->pid + PON_INT_RT_STS); + +	if (reg < 0) +		return 0; + +	switch (offset) { +	case 0: /* Power button */ +		return (reg & BIT(KPDPWR_ON_INT_BIT)) != 0; +		break; +	case 1: /* Reset button */ +	default: +		return (reg & BIT(RESIN_ON_INT_BIT)) != 0; +		break; +	} +} + +static const struct dm_gpio_ops pm8941_pwrkey_ops = { +	.get_value		= pm8941_pwrkey_get_value, +	.get_function		= pm8941_pwrkey_get_function, +}; + +static int pm8941_pwrkey_probe(struct udevice *dev) +{ +	struct pm8916_gpio_bank *priv = dev_get_priv(dev); +	int reg; + +	priv->pid = dev_get_addr(dev); +	if (priv->pid == FDT_ADDR_T_NONE) +		return -EINVAL; + +	/* Do a sanity check */ +	reg = pmic_reg_read(dev->parent, priv->pid + REG_TYPE); +	if (reg != 0x1) +		return -ENODEV; + +	reg = pmic_reg_read(dev->parent, priv->pid + REG_SUBTYPE); +	if (reg != 0x1) +		return -ENODEV; + +	return 0; +} + +static int pm8941_pwrkey_ofdata_to_platdata(struct udevice *dev) +{ +	struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); + +	uc_priv->gpio_count = 2; +	if (uc_priv->bank_name == NULL) +		uc_priv->bank_name = "pm8916_key"; + +	return 0; +} + +static const struct udevice_id pm8941_pwrkey_ids[] = { +	{ .compatible = "qcom,pm8916-pwrkey" }, +	{ } +}; + +U_BOOT_DRIVER(pwrkey_pm8941) = { +	.name	= "pwrkey_pm8916", +	.id	= UCLASS_GPIO, +	.of_match = pm8941_pwrkey_ids, +	.ofdata_to_platdata = pm8941_pwrkey_ofdata_to_platdata, +	.probe	= pm8941_pwrkey_probe, +	.ops	= &pm8941_pwrkey_ops, +	.priv_auto_alloc_size = sizeof(struct pm8916_gpio_bank), +}; | 
