1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * GPIO driver for TI TPS65214/TPS65215/TPS65219 PMICs 4 * 5 * Copyright (C) 2022, 2025 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 TPS65214_GPIO0_DIR_MASK BIT(1) 17 #define TPS6521X_GPIO0_OFFSET 2 18 #define TPS6521X_GPIO0_IDX 0 19 20 /* 21 * TPS65214 GPIO mapping 22 * Linux gpio offset 0 -> GPIO (pin16) -> bit_offset 2 23 * Linux gpio offset 1 -> GPO1 (pin9 ) -> bit_offset 0 24 * 25 * TPS65215 & TPS65219 GPIO mapping 26 * Linux gpio offset 0 -> GPIO (pin16) -> bit_offset 2 27 * Linux gpio offset 1 -> GPO1 (pin8 ) -> bit_offset 0 28 * Linux gpio offset 2 -> GPO2 (pin17) -> bit_offset 1 29 */ 30 31 struct tps65219_gpio { 32 int (*change_dir)(struct gpio_chip *gc, unsigned int offset, unsigned int dir); 33 struct gpio_chip gpio_chip; 34 struct tps65219 *tps; 35 }; 36 37 static int tps65214_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) 38 { 39 struct tps65219_gpio *gpio = gpiochip_get_data(gc); 40 int ret, val; 41 42 if (offset != TPS6521X_GPIO0_IDX) 43 return GPIO_LINE_DIRECTION_OUT; 44 45 ret = regmap_read(gpio->tps->regmap, TPS65219_REG_GENERAL_CONFIG, &val); 46 if (ret) 47 return ret; 48 49 return !(val & TPS65214_GPIO0_DIR_MASK); 50 } 51 52 static int tps65219_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) 53 { 54 struct tps65219_gpio *gpio = gpiochip_get_data(gc); 55 int ret, val; 56 57 if (offset != TPS6521X_GPIO0_IDX) 58 return GPIO_LINE_DIRECTION_OUT; 59 60 ret = regmap_read(gpio->tps->regmap, TPS65219_REG_MFP_1_CONFIG, &val); 61 if (ret) 62 return ret; 63 64 return !!(val & TPS65219_GPIO0_DIR_MASK); 65 } 66 67 static int tps65219_gpio_get(struct gpio_chip *gc, unsigned int offset) 68 { 69 struct tps65219_gpio *gpio = gpiochip_get_data(gc); 70 struct device *dev = gpio->tps->dev; 71 int ret, val; 72 73 if (offset != TPS6521X_GPIO0_IDX) { 74 dev_err(dev, "GPIO%d is output only, cannot get\n", offset); 75 return -ENOTSUPP; 76 } 77 78 ret = regmap_read(gpio->tps->regmap, TPS65219_REG_MFP_CTRL, &val); 79 if (ret) 80 return ret; 81 82 ret = !!(val & BIT(TPS65219_MFP_GPIO_STATUS_MASK)); 83 dev_warn(dev, "GPIO%d = %d, MULTI_DEVICE_ENABLE, not a standard GPIO\n", offset, ret); 84 85 /* 86 * Depending on NVM config, return an error if direction is output, otherwise the GPIO0 87 * status bit. 88 */ 89 90 if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) 91 return -ENOTSUPP; 92 93 return ret; 94 } 95 96 static int tps65219_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) 97 { 98 struct tps65219_gpio *gpio = gpiochip_get_data(gc); 99 int v, mask, bit; 100 101 bit = (offset == TPS6521X_GPIO0_IDX) ? TPS6521X_GPIO0_OFFSET : offset - 1; 102 103 mask = BIT(bit); 104 v = value ? mask : 0; 105 106 return regmap_update_bits(gpio->tps->regmap, 107 TPS65219_REG_GENERAL_CONFIG, mask, v); 108 } 109 110 static int tps65219_gpio_change_direction(struct gpio_chip *gc, unsigned int offset, 111 unsigned int direction) 112 { 113 struct tps65219_gpio *gpio = gpiochip_get_data(gc); 114 struct device *dev = gpio->tps->dev; 115 116 /* 117 * Documentation is stating that GPIO0 direction must not be changed in Linux: 118 * Table 8-34. MFP_1_CONFIG(3): MULTI_DEVICE_ENABLE, should only be changed in INITIALIZE 119 * state (prior to ON Request). 120 * Set statically by NVM, changing direction in application can cause a hang. 121 * Below can be used for test purpose only. 122 */ 123 124 #if 0 125 int ret = regmap_update_bits(gpio->tps->regmap, TPS65219_REG_MFP_1_CONFIG, 126 TPS65219_GPIO0_DIR_MASK, direction); 127 if (ret) { 128 dev_err(dev, 129 "GPIO DEBUG enabled: Fail to change direction to %u for GPIO%d.\n", 130 direction, offset); 131 return ret; 132 } 133 #endif 134 135 dev_err(dev, 136 "GPIO%d direction set by NVM, change to %u failed, not allowed by specification\n", 137 offset, direction); 138 139 return -ENOTSUPP; 140 } 141 142 static int tps65214_gpio_change_direction(struct gpio_chip *gc, unsigned int offset, 143 unsigned int direction) 144 { 145 struct tps65219_gpio *gpio = gpiochip_get_data(gc); 146 struct device *dev = gpio->tps->dev; 147 int val, ret; 148 149 /** 150 * Verified if GPIO or GPO in parent function 151 * Masked value: 0 = GPIO, 1 = VSEL 152 */ 153 ret = regmap_read(gpio->tps->regmap, TPS65219_REG_MFP_1_CONFIG, &val); 154 if (ret) 155 return ret; 156 157 ret = !!(val & BIT(TPS65219_GPIO0_DIR_MASK)); 158 if (ret) 159 dev_err(dev, "GPIO%d configured as VSEL, not GPIO\n", offset); 160 161 ret = regmap_update_bits(gpio->tps->regmap, TPS65219_REG_GENERAL_CONFIG, 162 TPS65214_GPIO0_DIR_MASK, direction); 163 if (ret) 164 dev_err(dev, "Fail to change direction to %u for GPIO%d.\n", direction, offset); 165 166 return ret; 167 } 168 169 static int tps65219_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) 170 { 171 struct tps65219_gpio *gpio = gpiochip_get_data(gc); 172 struct device *dev = gpio->tps->dev; 173 174 if (offset != TPS6521X_GPIO0_IDX) { 175 dev_err(dev, "GPIO%d is output only, cannot change to input\n", offset); 176 return -ENOTSUPP; 177 } 178 179 if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_IN) 180 return 0; 181 182 return gpio->change_dir(gc, offset, GPIO_LINE_DIRECTION_IN); 183 } 184 185 static int tps65219_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value) 186 { 187 struct tps65219_gpio *gpio = gpiochip_get_data(gc); 188 189 tps65219_gpio_set(gc, offset, value); 190 if (offset != TPS6521X_GPIO0_IDX) 191 return 0; 192 193 if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) 194 return 0; 195 196 return gpio->change_dir(gc, offset, GPIO_LINE_DIRECTION_OUT); 197 } 198 199 static const struct gpio_chip tps65214_template_chip = { 200 .label = "tps65214-gpio", 201 .owner = THIS_MODULE, 202 .get_direction = tps65214_gpio_get_direction, 203 .direction_input = tps65219_gpio_direction_input, 204 .direction_output = tps65219_gpio_direction_output, 205 .get = tps65219_gpio_get, 206 .set = tps65219_gpio_set, 207 .base = -1, 208 .ngpio = 2, 209 .can_sleep = true, 210 }; 211 212 static const struct gpio_chip tps65219_template_chip = { 213 .label = "tps65219-gpio", 214 .owner = THIS_MODULE, 215 .get_direction = tps65219_gpio_get_direction, 216 .direction_input = tps65219_gpio_direction_input, 217 .direction_output = tps65219_gpio_direction_output, 218 .get = tps65219_gpio_get, 219 .set = tps65219_gpio_set, 220 .base = -1, 221 .ngpio = 3, 222 .can_sleep = true, 223 }; 224 225 static int tps65219_gpio_probe(struct platform_device *pdev) 226 { 227 enum pmic_id chip = platform_get_device_id(pdev)->driver_data; 228 struct tps65219 *tps = dev_get_drvdata(pdev->dev.parent); 229 struct tps65219_gpio *gpio; 230 231 gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); 232 if (!gpio) 233 return -ENOMEM; 234 235 if (chip == TPS65214) { 236 gpio->gpio_chip = tps65214_template_chip; 237 gpio->change_dir = tps65214_gpio_change_direction; 238 } else if (chip == TPS65219) { 239 gpio->gpio_chip = tps65219_template_chip; 240 gpio->change_dir = tps65219_gpio_change_direction; 241 } else { 242 return -ENODATA; 243 } 244 245 gpio->tps = tps; 246 gpio->gpio_chip.parent = tps->dev; 247 248 return devm_gpiochip_add_data(&pdev->dev, &gpio->gpio_chip, gpio); 249 } 250 251 static const struct platform_device_id tps6521x_gpio_id_table[] = { 252 { "tps65214-gpio", TPS65214 }, 253 { "tps65219-gpio", TPS65219 }, 254 { /* sentinel */ } 255 }; 256 MODULE_DEVICE_TABLE(platform, tps6521x_gpio_id_table); 257 258 static struct platform_driver tps65219_gpio_driver = { 259 .driver = { 260 .name = "tps65219-gpio", 261 }, 262 .probe = tps65219_gpio_probe, 263 .id_table = tps6521x_gpio_id_table, 264 }; 265 module_platform_driver(tps65219_gpio_driver); 266 267 MODULE_AUTHOR("Jonathan Cormier <jcormier@criticallink.com>"); 268 MODULE_DESCRIPTION("TPS65214/TPS65215/TPS65219 GPIO driver"); 269 MODULE_LICENSE("GPL"); 270