Skip to content
Snippets Groups Projects
pm8916_gpio.c 7.43 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * 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 {
    
    	uint32_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),
    };