1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * GPIO driver for TI TPS65219 PMICs
4  *
5  * Copyright (C) 2022 Texas Instruments Incorporated - http://www.ti.com/
6  */
7 
8 #include <linux/bits.h>
9 #include <linux/gpio/driver.h>
10 #include <linux/mfd/tps65219.h>
11 #include <linux/module.h>
12 #include <linux/platform_device.h>
13 #include <linux/regmap.h>
14 
15 #define TPS65219_GPIO0_DIR_MASK		BIT(3)
16 #define TPS65219_GPIO0_OFFSET		2
17 #define TPS65219_GPIO0_IDX		0
18 #define TPS65219_GPIO_DIR_IN		1
19 #define TPS65219_GPIO_DIR_OUT		0
20 
21 struct tps65219_gpio {
22 	struct gpio_chip gpio_chip;
23 	struct tps65219 *tps;
24 };
25 
tps65219_gpio_get_direction(struct gpio_chip * gc,unsigned int offset)26 static int tps65219_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
27 {
28 	struct tps65219_gpio *gpio = gpiochip_get_data(gc);
29 	int ret, val;
30 
31 	if (offset != TPS65219_GPIO0_IDX)
32 		return GPIO_LINE_DIRECTION_OUT;
33 
34 	ret = regmap_read(gpio->tps->regmap, TPS65219_REG_MFP_1_CONFIG, &val);
35 	if (ret)
36 		return ret;
37 
38 	return !!(val & TPS65219_GPIO0_DIR_MASK);
39 }
40 
tps65219_gpio_get(struct gpio_chip * gc,unsigned int offset)41 static int tps65219_gpio_get(struct gpio_chip *gc, unsigned int offset)
42 {
43 	struct tps65219_gpio *gpio = gpiochip_get_data(gc);
44 	struct device *dev = gpio->tps->dev;
45 	int ret, val;
46 
47 	if (offset != TPS65219_GPIO0_IDX) {
48 		dev_err(dev, "GPIO%d is output only, cannot get\n", offset);
49 		return -ENOTSUPP;
50 	}
51 
52 	ret = regmap_read(gpio->tps->regmap, TPS65219_REG_MFP_CTRL, &val);
53 	if (ret)
54 		return ret;
55 
56 	ret = !!(val & BIT(TPS65219_MFP_GPIO_STATUS_MASK));
57 	dev_warn(dev, "GPIO%d = %d, MULTI_DEVICE_ENABLE, not a standard GPIO\n", offset, ret);
58 
59 	/*
60 	 * Depending on NVM config, return an error if direction is output, otherwise the GPIO0
61 	 * status bit.
62 	 */
63 
64 	if (tps65219_gpio_get_direction(gc, offset) == TPS65219_GPIO_DIR_OUT)
65 		return -ENOTSUPP;
66 
67 	return ret;
68 }
69 
tps65219_gpio_set(struct gpio_chip * gc,unsigned int offset,int value)70 static void tps65219_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
71 {
72 	struct tps65219_gpio *gpio = gpiochip_get_data(gc);
73 	struct device *dev = gpio->tps->dev;
74 	int v, mask, bit;
75 
76 	bit = (offset == TPS65219_GPIO0_IDX) ? TPS65219_GPIO0_OFFSET : offset - 1;
77 
78 	mask = BIT(bit);
79 	v = value ? mask : 0;
80 
81 	if (regmap_update_bits(gpio->tps->regmap, TPS65219_REG_GENERAL_CONFIG, mask, v))
82 		dev_err(dev, "GPIO%d, set to value %d failed.\n", offset, value);
83 }
84 
tps65219_gpio_change_direction(struct gpio_chip * gc,unsigned int offset,unsigned int direction)85 static int tps65219_gpio_change_direction(struct gpio_chip *gc, unsigned int offset,
86 					  unsigned int direction)
87 {
88 	struct tps65219_gpio *gpio = gpiochip_get_data(gc);
89 	struct device *dev = gpio->tps->dev;
90 
91 	/*
92 	 * Documentation is stating that GPIO0 direction must not be changed in Linux:
93 	 * Table 8-34. MFP_1_CONFIG(3): MULTI_DEVICE_ENABLE, should only be changed in INITIALIZE
94 	 * state (prior to ON Request).
95 	 * Set statically by NVM, changing direction in application can cause a hang.
96 	 * Below can be used for test purpose only.
97 	 */
98 
99 	if (IS_ENABLED(CONFIG_DEBUG_GPIO)) {
100 		int ret = regmap_update_bits(gpio->tps->regmap, TPS65219_REG_MFP_1_CONFIG,
101 					     TPS65219_GPIO0_DIR_MASK, direction);
102 		if (ret) {
103 			dev_err(dev,
104 				"GPIO DEBUG enabled: Fail to change direction to %u for GPIO%d.\n",
105 				direction, offset);
106 			return ret;
107 		}
108 	}
109 
110 	dev_err(dev,
111 		"GPIO%d direction set by NVM, change to %u failed, not allowed by specification\n",
112 		 offset, direction);
113 
114 	return -ENOTSUPP;
115 }
116 
tps65219_gpio_direction_input(struct gpio_chip * gc,unsigned int offset)117 static int tps65219_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
118 {
119 	struct tps65219_gpio *gpio = gpiochip_get_data(gc);
120 	struct device *dev = gpio->tps->dev;
121 
122 	if (offset != TPS65219_GPIO0_IDX) {
123 		dev_err(dev, "GPIO%d is output only, cannot change to input\n", offset);
124 		return -ENOTSUPP;
125 	}
126 
127 	if (tps65219_gpio_get_direction(gc, offset) == TPS65219_GPIO_DIR_IN)
128 		return 0;
129 
130 	return tps65219_gpio_change_direction(gc, offset, TPS65219_GPIO_DIR_IN);
131 }
132 
tps65219_gpio_direction_output(struct gpio_chip * gc,unsigned int offset,int value)133 static int tps65219_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value)
134 {
135 	tps65219_gpio_set(gc, offset, value);
136 	if (offset != TPS65219_GPIO0_IDX)
137 		return 0;
138 
139 	if (tps65219_gpio_get_direction(gc, offset) == TPS65219_GPIO_DIR_OUT)
140 		return 0;
141 
142 	return tps65219_gpio_change_direction(gc, offset, TPS65219_GPIO_DIR_OUT);
143 }
144 
145 static const struct gpio_chip tps65219_template_chip = {
146 	.label			= "tps65219-gpio",
147 	.owner			= THIS_MODULE,
148 	.get_direction		= tps65219_gpio_get_direction,
149 	.direction_input	= tps65219_gpio_direction_input,
150 	.direction_output	= tps65219_gpio_direction_output,
151 	.get			= tps65219_gpio_get,
152 	.set			= tps65219_gpio_set,
153 	.base			= -1,
154 	.ngpio			= 3,
155 	.can_sleep		= true,
156 };
157 
tps65219_gpio_probe(struct platform_device * pdev)158 static int tps65219_gpio_probe(struct platform_device *pdev)
159 {
160 	struct tps65219 *tps = dev_get_drvdata(pdev->dev.parent);
161 	struct tps65219_gpio *gpio;
162 
163 	gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
164 	if (!gpio)
165 		return -ENOMEM;
166 
167 	gpio->tps = tps;
168 	gpio->gpio_chip = tps65219_template_chip;
169 	gpio->gpio_chip.parent = tps->dev;
170 
171 	return devm_gpiochip_add_data(&pdev->dev, &gpio->gpio_chip, gpio);
172 }
173 
174 static struct platform_driver tps65219_gpio_driver = {
175 	.driver = {
176 		.name = "tps65219-gpio",
177 	},
178 	.probe = tps65219_gpio_probe,
179 };
180 module_platform_driver(tps65219_gpio_driver);
181 
182 MODULE_ALIAS("platform:tps65219-gpio");
183 MODULE_AUTHOR("Jonathan Cormier <jcormier@criticallink.com>");
184 MODULE_DESCRIPTION("TPS65219 GPIO driver");
185 MODULE_LICENSE("GPL");
186