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 | |
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(map: gpio->tps->regmap, TPS65219_REG_MFP_1_CONFIG, val: &val); |
35 | if (ret) |
36 | return ret; |
37 | |
38 | return !!(val & TPS65219_GPIO0_DIR_MASK); |
39 | } |
40 | |
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(map: gpio->tps->regmap, TPS65219_REG_MFP_CTRL, val: &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 | |
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(map: gpio->tps->regmap, TPS65219_REG_GENERAL_CONFIG, mask, val: v)) |
82 | dev_err(dev, "GPIO%d, set to value %d failed.\n" , offset, value); |
83 | } |
84 | |
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 0 |
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 | #endif |
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 | |
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 | |
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 | |
158 | static int tps65219_gpio_probe(struct platform_device *pdev) |
159 | { |
160 | struct tps65219 *tps = dev_get_drvdata(dev: pdev->dev.parent); |
161 | struct tps65219_gpio *gpio; |
162 | |
163 | gpio = devm_kzalloc(dev: &pdev->dev, size: 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 | |