| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * GPIO driver for TI TPS65219 PMICs | 
 |  * | 
 |  * Copyright (C) 2022 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 TPS65219_GPIO0_OFFSET		2 | 
 | #define TPS65219_GPIO0_IDX		0 | 
 | #define TPS65219_GPIO_DIR_IN		1 | 
 | #define TPS65219_GPIO_DIR_OUT		0 | 
 |  | 
 | struct tps65219_gpio { | 
 | 	struct gpio_chip gpio_chip; | 
 | 	struct tps65219 *tps; | 
 | }; | 
 |  | 
 | 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 != TPS65219_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 != TPS65219_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) == TPS65219_GPIO_DIR_OUT) | 
 | 		return -ENOTSUPP; | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static void tps65219_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) | 
 | { | 
 | 	struct tps65219_gpio *gpio = gpiochip_get_data(gc); | 
 | 	struct device *dev = gpio->tps->dev; | 
 | 	int v, mask, bit; | 
 |  | 
 | 	bit = (offset == TPS65219_GPIO0_IDX) ? TPS65219_GPIO0_OFFSET : offset - 1; | 
 |  | 
 | 	mask = BIT(bit); | 
 | 	v = value ? mask : 0; | 
 |  | 
 | 	if (regmap_update_bits(gpio->tps->regmap, TPS65219_REG_GENERAL_CONFIG, mask, v)) | 
 | 		dev_err(dev, "GPIO%d, set to value %d failed.\n", offset, value); | 
 | } | 
 |  | 
 | 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 (IS_ENABLED(CONFIG_DEBUG_GPIO)) { | 
 | 		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; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	dev_err(dev, | 
 | 		"GPIO%d direction set by NVM, change to %u failed, not allowed by specification\n", | 
 | 		 offset, direction); | 
 |  | 
 | 	return -ENOTSUPP; | 
 | } | 
 |  | 
 | 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 != TPS65219_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) == TPS65219_GPIO_DIR_IN) | 
 | 		return 0; | 
 |  | 
 | 	return tps65219_gpio_change_direction(gc, offset, TPS65219_GPIO_DIR_IN); | 
 | } | 
 |  | 
 | static int tps65219_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value) | 
 | { | 
 | 	tps65219_gpio_set(gc, offset, value); | 
 | 	if (offset != TPS65219_GPIO0_IDX) | 
 | 		return 0; | 
 |  | 
 | 	if (tps65219_gpio_get_direction(gc, offset) == TPS65219_GPIO_DIR_OUT) | 
 | 		return 0; | 
 |  | 
 | 	return tps65219_gpio_change_direction(gc, offset, TPS65219_GPIO_DIR_OUT); | 
 | } | 
 |  | 
 | 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) | 
 | { | 
 | 	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; | 
 |  | 
 | 	gpio->tps = tps; | 
 | 	gpio->gpio_chip = tps65219_template_chip; | 
 | 	gpio->gpio_chip.parent = tps->dev; | 
 |  | 
 | 	return devm_gpiochip_add_data(&pdev->dev, &gpio->gpio_chip, gpio); | 
 | } | 
 |  | 
 | static struct platform_driver tps65219_gpio_driver = { | 
 | 	.driver = { | 
 | 		.name = "tps65219-gpio", | 
 | 	}, | 
 | 	.probe = tps65219_gpio_probe, | 
 | }; | 
 | module_platform_driver(tps65219_gpio_driver); | 
 |  | 
 | MODULE_ALIAS("platform:tps65219-gpio"); | 
 | MODULE_AUTHOR("Jonathan Cormier <jcormier@criticallink.com>"); | 
 | MODULE_DESCRIPTION("TPS65219 GPIO driver"); | 
 | MODULE_LICENSE("GPL"); |