diff options
Diffstat (limited to 'drivers/gpio/gpio-tps65219.c')
| -rw-r--r-- | drivers/gpio/gpio-tps65219.c | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-tps65219.c b/drivers/gpio/gpio-tps65219.c new file mode 100644 index 000000000000..158f63bcf10c --- /dev/null +++ b/drivers/gpio/gpio-tps65219.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * GPIO driver for TI TPS65214/TPS65215/TPS65219 PMICs + * + * Copyright (C) 2022, 2025 Texas Instruments Incorporated - http://www.ti.com/ + */ + +#include <linux/bits.h> +#include <linux/gpio/driver.h> +#include <linux/mfd/tps65219.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define TPS65219_GPIO0_DIR_MASK BIT(3) +#define TPS65214_GPIO0_DIR_MASK BIT(1) +#define TPS6521X_GPIO0_OFFSET 2 +#define TPS6521X_GPIO0_IDX 0 + +/* + * TPS65214 GPIO mapping + * Linux gpio offset 0 -> GPIO (pin16) -> bit_offset 2 + * Linux gpio offset 1 -> GPO1 (pin9 ) -> bit_offset 0 + * + * TPS65215 & TPS65219 GPIO mapping + * Linux gpio offset 0 -> GPIO (pin16) -> bit_offset 2 + * Linux gpio offset 1 -> GPO1 (pin8 ) -> bit_offset 0 + * Linux gpio offset 2 -> GPO2 (pin17) -> bit_offset 1 + */ + +struct tps65219_gpio { + int (*change_dir)(struct gpio_chip *gc, unsigned int offset, unsigned int dir); + struct gpio_chip gpio_chip; + struct tps65219 *tps; +}; + +static int tps65214_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct tps65219_gpio *gpio = gpiochip_get_data(gc); + int ret, val; + + if (offset != TPS6521X_GPIO0_IDX) + return GPIO_LINE_DIRECTION_OUT; + + ret = regmap_read(gpio->tps->regmap, TPS65219_REG_GENERAL_CONFIG, &val); + if (ret) + return ret; + + return !(val & TPS65214_GPIO0_DIR_MASK); +} + +static int tps65219_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct tps65219_gpio *gpio = gpiochip_get_data(gc); + int ret, val; + + if (offset != TPS6521X_GPIO0_IDX) + return GPIO_LINE_DIRECTION_OUT; + + ret = regmap_read(gpio->tps->regmap, TPS65219_REG_MFP_1_CONFIG, &val); + if (ret) + return ret; + + return !!(val & TPS65219_GPIO0_DIR_MASK); +} + +static int tps65219_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct tps65219_gpio *gpio = gpiochip_get_data(gc); + struct device *dev = gpio->tps->dev; + int ret, val; + + if (offset != TPS6521X_GPIO0_IDX) { + dev_err(dev, "GPIO%d is output only, cannot get\n", offset); + return -ENOTSUPP; + } + + ret = regmap_read(gpio->tps->regmap, TPS65219_REG_MFP_CTRL, &val); + if (ret) + return ret; + + ret = !!(val & BIT(TPS65219_MFP_GPIO_STATUS_MASK)); + dev_warn(dev, "GPIO%d = %d, MULTI_DEVICE_ENABLE, not a standard GPIO\n", offset, ret); + + /* + * Depending on NVM config, return an error if direction is output, otherwise the GPIO0 + * status bit. + */ + + if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) + return -ENOTSUPP; + + return ret; +} + +static int tps65219_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct tps65219_gpio *gpio = gpiochip_get_data(gc); + int v, mask, bit; + + bit = (offset == TPS6521X_GPIO0_IDX) ? TPS6521X_GPIO0_OFFSET : offset - 1; + + mask = BIT(bit); + v = value ? mask : 0; + + return regmap_update_bits(gpio->tps->regmap, + TPS65219_REG_GENERAL_CONFIG, mask, v); +} + +static int tps65219_gpio_change_direction(struct gpio_chip *gc, unsigned int offset, + unsigned int direction) +{ + struct tps65219_gpio *gpio = gpiochip_get_data(gc); + struct device *dev = gpio->tps->dev; + + /* + * Documentation is stating that GPIO0 direction must not be changed in Linux: + * Table 8-34. MFP_1_CONFIG(3): MULTI_DEVICE_ENABLE, should only be changed in INITIALIZE + * state (prior to ON Request). + * Set statically by NVM, changing direction in application can cause a hang. + * Below can be used for test purpose only. + */ + +#if 0 + int ret = regmap_update_bits(gpio->tps->regmap, TPS65219_REG_MFP_1_CONFIG, + TPS65219_GPIO0_DIR_MASK, direction); + if (ret) { + dev_err(dev, + "GPIO DEBUG enabled: Fail to change direction to %u for GPIO%d.\n", + direction, offset); + return ret; + } +#endif + + dev_err(dev, + "GPIO%d direction set by NVM, change to %u failed, not allowed by specification\n", + offset, direction); + + return -ENOTSUPP; +} + +static int tps65214_gpio_change_direction(struct gpio_chip *gc, unsigned int offset, + unsigned int direction) +{ + struct tps65219_gpio *gpio = gpiochip_get_data(gc); + struct device *dev = gpio->tps->dev; + int val, ret; + + /** + * Verified if GPIO or GPO in parent function + * Masked value: 0 = GPIO, 1 = VSEL + */ + ret = regmap_read(gpio->tps->regmap, TPS65219_REG_MFP_1_CONFIG, &val); + if (ret) + return ret; + + ret = !!(val & BIT(TPS65219_GPIO0_DIR_MASK)); + if (ret) + dev_err(dev, "GPIO%d configured as VSEL, not GPIO\n", offset); + + ret = regmap_update_bits(gpio->tps->regmap, TPS65219_REG_GENERAL_CONFIG, + TPS65214_GPIO0_DIR_MASK, direction); + if (ret) + dev_err(dev, "Fail to change direction to %u for GPIO%d.\n", direction, offset); + + return ret; +} + +static int tps65219_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + struct tps65219_gpio *gpio = gpiochip_get_data(gc); + struct device *dev = gpio->tps->dev; + + if (offset != TPS6521X_GPIO0_IDX) { + dev_err(dev, "GPIO%d is output only, cannot change to input\n", offset); + return -ENOTSUPP; + } + + if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_IN) + return 0; + + return gpio->change_dir(gc, offset, GPIO_LINE_DIRECTION_IN); +} + +static int tps65219_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct tps65219_gpio *gpio = gpiochip_get_data(gc); + + tps65219_gpio_set(gc, offset, value); + if (offset != TPS6521X_GPIO0_IDX) + return 0; + + if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) + return 0; + + return gpio->change_dir(gc, offset, GPIO_LINE_DIRECTION_OUT); +} + +static const struct gpio_chip tps65214_template_chip = { + .label = "tps65214-gpio", + .owner = THIS_MODULE, + .get_direction = tps65214_gpio_get_direction, + .direction_input = tps65219_gpio_direction_input, + .direction_output = tps65219_gpio_direction_output, + .get = tps65219_gpio_get, + .set = tps65219_gpio_set, + .base = -1, + .ngpio = 2, + .can_sleep = true, +}; + +static const struct gpio_chip tps65219_template_chip = { + .label = "tps65219-gpio", + .owner = THIS_MODULE, + .get_direction = tps65219_gpio_get_direction, + .direction_input = tps65219_gpio_direction_input, + .direction_output = tps65219_gpio_direction_output, + .get = tps65219_gpio_get, + .set = tps65219_gpio_set, + .base = -1, + .ngpio = 3, + .can_sleep = true, +}; + +static int tps65219_gpio_probe(struct platform_device *pdev) +{ + enum pmic_id chip = platform_get_device_id(pdev)->driver_data; + struct tps65219 *tps = dev_get_drvdata(pdev->dev.parent); + struct tps65219_gpio *gpio; + + gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + if (chip == TPS65214) { + gpio->gpio_chip = tps65214_template_chip; + gpio->change_dir = tps65214_gpio_change_direction; + } else if (chip == TPS65219) { + gpio->gpio_chip = tps65219_template_chip; + gpio->change_dir = tps65219_gpio_change_direction; + } else { + return -ENODATA; + } + + gpio->tps = tps; + gpio->gpio_chip.parent = tps->dev; + + return devm_gpiochip_add_data(&pdev->dev, &gpio->gpio_chip, gpio); +} + +static const struct platform_device_id tps6521x_gpio_id_table[] = { + { "tps65214-gpio", TPS65214 }, + { "tps65219-gpio", TPS65219 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, tps6521x_gpio_id_table); + +static struct platform_driver tps65219_gpio_driver = { + .driver = { + .name = "tps65219-gpio", + }, + .probe = tps65219_gpio_probe, + .id_table = tps6521x_gpio_id_table, +}; +module_platform_driver(tps65219_gpio_driver); + +MODULE_AUTHOR("Jonathan Cormier <jcormier@criticallink.com>"); +MODULE_DESCRIPTION("TPS65214/TPS65215/TPS65219 GPIO driver"); +MODULE_LICENSE("GPL"); |
