.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
---|
1 | 2 | /* |
---|
2 | 3 | * AXP20X and AXP22X PMICs' ACIN power supply driver |
---|
3 | 4 | * |
---|
4 | 5 | * Copyright (C) 2016 Free Electrons |
---|
5 | 6 | * Quentin Schulz <quentin.schulz@free-electrons.com> |
---|
6 | | - * |
---|
7 | | - * This program is free software; you can redistribute it and/or modify it |
---|
8 | | - * under the terms of the GNU General Public License as published by the |
---|
9 | | - * Free Software Foundation; either version 2 of the License, or (at your |
---|
10 | | - * option) any later version. |
---|
11 | 7 | */ |
---|
12 | 8 | |
---|
13 | 9 | #include <linux/device.h> |
---|
.. | .. |
---|
19 | 15 | #include <linux/of.h> |
---|
20 | 16 | #include <linux/of_device.h> |
---|
21 | 17 | #include <linux/platform_device.h> |
---|
| 18 | +#include <linux/pm.h> |
---|
22 | 19 | #include <linux/power_supply.h> |
---|
23 | 20 | #include <linux/regmap.h> |
---|
24 | 21 | #include <linux/slab.h> |
---|
.. | .. |
---|
27 | 24 | #define AXP20X_PWR_STATUS_ACIN_PRESENT BIT(7) |
---|
28 | 25 | #define AXP20X_PWR_STATUS_ACIN_AVAIL BIT(6) |
---|
29 | 26 | |
---|
| 27 | +#define AXP813_ACIN_PATH_SEL BIT(7) |
---|
| 28 | +#define AXP813_ACIN_PATH_SEL_TO_BIT(x) (!!(x) << 7) |
---|
| 29 | + |
---|
| 30 | +#define AXP813_VHOLD_MASK GENMASK(5, 3) |
---|
| 31 | +#define AXP813_VHOLD_UV_TO_BIT(x) ((((x) / 100000) - 40) << 3) |
---|
| 32 | +#define AXP813_VHOLD_REG_TO_UV(x) \ |
---|
| 33 | + (((((x) & AXP813_VHOLD_MASK) >> 3) + 40) * 100000) |
---|
| 34 | + |
---|
| 35 | +#define AXP813_CURR_LIMIT_MASK GENMASK(2, 0) |
---|
| 36 | +#define AXP813_CURR_LIMIT_UA_TO_BIT(x) (((x) / 500000) - 3) |
---|
| 37 | +#define AXP813_CURR_LIMIT_REG_TO_UA(x) \ |
---|
| 38 | + ((((x) & AXP813_CURR_LIMIT_MASK) + 3) * 500000) |
---|
| 39 | + |
---|
30 | 40 | #define DRVNAME "axp20x-ac-power-supply" |
---|
31 | 41 | |
---|
32 | 42 | struct axp20x_ac_power { |
---|
.. | .. |
---|
34 | 44 | struct power_supply *supply; |
---|
35 | 45 | struct iio_channel *acin_v; |
---|
36 | 46 | struct iio_channel *acin_i; |
---|
| 47 | + bool has_acin_path_sel; |
---|
| 48 | + unsigned int num_irqs; |
---|
| 49 | + unsigned int irqs[]; |
---|
37 | 50 | }; |
---|
38 | 51 | |
---|
39 | 52 | static irqreturn_t axp20x_ac_power_irq(int irq, void *devid) |
---|
.. | .. |
---|
80 | 93 | return ret; |
---|
81 | 94 | |
---|
82 | 95 | val->intval = !!(reg & AXP20X_PWR_STATUS_ACIN_AVAIL); |
---|
| 96 | + |
---|
| 97 | + /* ACIN_PATH_SEL disables ACIN even if ACIN_AVAIL is set. */ |
---|
| 98 | + if (val->intval && power->has_acin_path_sel) { |
---|
| 99 | + ret = regmap_read(power->regmap, AXP813_ACIN_PATH_CTRL, |
---|
| 100 | + ®); |
---|
| 101 | + if (ret) |
---|
| 102 | + return ret; |
---|
| 103 | + |
---|
| 104 | + val->intval = !!(reg & AXP813_ACIN_PATH_SEL); |
---|
| 105 | + } |
---|
| 106 | + |
---|
83 | 107 | return 0; |
---|
84 | 108 | |
---|
85 | 109 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
---|
.. | .. |
---|
102 | 126 | |
---|
103 | 127 | return 0; |
---|
104 | 128 | |
---|
| 129 | + case POWER_SUPPLY_PROP_VOLTAGE_MIN: |
---|
| 130 | + ret = regmap_read(power->regmap, AXP813_ACIN_PATH_CTRL, ®); |
---|
| 131 | + if (ret) |
---|
| 132 | + return ret; |
---|
| 133 | + |
---|
| 134 | + val->intval = AXP813_VHOLD_REG_TO_UV(reg); |
---|
| 135 | + |
---|
| 136 | + return 0; |
---|
| 137 | + |
---|
| 138 | + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
---|
| 139 | + ret = regmap_read(power->regmap, AXP813_ACIN_PATH_CTRL, ®); |
---|
| 140 | + if (ret) |
---|
| 141 | + return ret; |
---|
| 142 | + |
---|
| 143 | + val->intval = AXP813_CURR_LIMIT_REG_TO_UA(reg); |
---|
| 144 | + /* AXP813 datasheet defines values 11x as 4000mA */ |
---|
| 145 | + if (val->intval > 4000000) |
---|
| 146 | + val->intval = 4000000; |
---|
| 147 | + |
---|
| 148 | + return 0; |
---|
| 149 | + |
---|
105 | 150 | default: |
---|
106 | 151 | return -EINVAL; |
---|
107 | 152 | } |
---|
108 | 153 | |
---|
109 | 154 | return -EINVAL; |
---|
| 155 | +} |
---|
| 156 | + |
---|
| 157 | +static int axp813_ac_power_set_property(struct power_supply *psy, |
---|
| 158 | + enum power_supply_property psp, |
---|
| 159 | + const union power_supply_propval *val) |
---|
| 160 | +{ |
---|
| 161 | + struct axp20x_ac_power *power = power_supply_get_drvdata(psy); |
---|
| 162 | + |
---|
| 163 | + switch (psp) { |
---|
| 164 | + case POWER_SUPPLY_PROP_ONLINE: |
---|
| 165 | + return regmap_update_bits(power->regmap, AXP813_ACIN_PATH_CTRL, |
---|
| 166 | + AXP813_ACIN_PATH_SEL, |
---|
| 167 | + AXP813_ACIN_PATH_SEL_TO_BIT(val->intval)); |
---|
| 168 | + |
---|
| 169 | + case POWER_SUPPLY_PROP_VOLTAGE_MIN: |
---|
| 170 | + if (val->intval < 4000000 || val->intval > 4700000) |
---|
| 171 | + return -EINVAL; |
---|
| 172 | + |
---|
| 173 | + return regmap_update_bits(power->regmap, AXP813_ACIN_PATH_CTRL, |
---|
| 174 | + AXP813_VHOLD_MASK, |
---|
| 175 | + AXP813_VHOLD_UV_TO_BIT(val->intval)); |
---|
| 176 | + |
---|
| 177 | + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
---|
| 178 | + if (val->intval < 1500000 || val->intval > 4000000) |
---|
| 179 | + return -EINVAL; |
---|
| 180 | + |
---|
| 181 | + return regmap_update_bits(power->regmap, AXP813_ACIN_PATH_CTRL, |
---|
| 182 | + AXP813_CURR_LIMIT_MASK, |
---|
| 183 | + AXP813_CURR_LIMIT_UA_TO_BIT(val->intval)); |
---|
| 184 | + |
---|
| 185 | + default: |
---|
| 186 | + return -EINVAL; |
---|
| 187 | + } |
---|
| 188 | + |
---|
| 189 | + return -EINVAL; |
---|
| 190 | +} |
---|
| 191 | + |
---|
| 192 | +static int axp813_ac_power_prop_writeable(struct power_supply *psy, |
---|
| 193 | + enum power_supply_property psp) |
---|
| 194 | +{ |
---|
| 195 | + return psp == POWER_SUPPLY_PROP_ONLINE || |
---|
| 196 | + psp == POWER_SUPPLY_PROP_VOLTAGE_MIN || |
---|
| 197 | + psp == POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT; |
---|
110 | 198 | } |
---|
111 | 199 | |
---|
112 | 200 | static enum power_supply_property axp20x_ac_power_properties[] = { |
---|
.. | .. |
---|
121 | 209 | POWER_SUPPLY_PROP_HEALTH, |
---|
122 | 210 | POWER_SUPPLY_PROP_PRESENT, |
---|
123 | 211 | POWER_SUPPLY_PROP_ONLINE, |
---|
| 212 | +}; |
---|
| 213 | + |
---|
| 214 | +static enum power_supply_property axp813_ac_power_properties[] = { |
---|
| 215 | + POWER_SUPPLY_PROP_HEALTH, |
---|
| 216 | + POWER_SUPPLY_PROP_PRESENT, |
---|
| 217 | + POWER_SUPPLY_PROP_ONLINE, |
---|
| 218 | + POWER_SUPPLY_PROP_VOLTAGE_MIN, |
---|
| 219 | + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, |
---|
124 | 220 | }; |
---|
125 | 221 | |
---|
126 | 222 | static const struct power_supply_desc axp20x_ac_power_desc = { |
---|
.. | .. |
---|
139 | 235 | .get_property = axp20x_ac_power_get_property, |
---|
140 | 236 | }; |
---|
141 | 237 | |
---|
| 238 | +static const struct power_supply_desc axp813_ac_power_desc = { |
---|
| 239 | + .name = "axp813-ac", |
---|
| 240 | + .type = POWER_SUPPLY_TYPE_MAINS, |
---|
| 241 | + .properties = axp813_ac_power_properties, |
---|
| 242 | + .num_properties = ARRAY_SIZE(axp813_ac_power_properties), |
---|
| 243 | + .property_is_writeable = axp813_ac_power_prop_writeable, |
---|
| 244 | + .get_property = axp20x_ac_power_get_property, |
---|
| 245 | + .set_property = axp813_ac_power_set_property, |
---|
| 246 | +}; |
---|
| 247 | + |
---|
| 248 | +static const char * const axp20x_irq_names[] = { |
---|
| 249 | + "ACIN_PLUGIN", |
---|
| 250 | + "ACIN_REMOVAL", |
---|
| 251 | +}; |
---|
| 252 | + |
---|
142 | 253 | struct axp_data { |
---|
143 | 254 | const struct power_supply_desc *power_desc; |
---|
| 255 | + const char * const *irq_names; |
---|
| 256 | + unsigned int num_irq_names; |
---|
144 | 257 | bool acin_adc; |
---|
| 258 | + bool acin_path_sel; |
---|
145 | 259 | }; |
---|
146 | 260 | |
---|
147 | 261 | static const struct axp_data axp20x_data = { |
---|
148 | | - .power_desc = &axp20x_ac_power_desc, |
---|
149 | | - .acin_adc = true, |
---|
| 262 | + .power_desc = &axp20x_ac_power_desc, |
---|
| 263 | + .irq_names = axp20x_irq_names, |
---|
| 264 | + .num_irq_names = ARRAY_SIZE(axp20x_irq_names), |
---|
| 265 | + .acin_adc = true, |
---|
| 266 | + .acin_path_sel = false, |
---|
150 | 267 | }; |
---|
151 | 268 | |
---|
152 | 269 | static const struct axp_data axp22x_data = { |
---|
153 | | - .power_desc = &axp22x_ac_power_desc, |
---|
154 | | - .acin_adc = false, |
---|
| 270 | + .power_desc = &axp22x_ac_power_desc, |
---|
| 271 | + .irq_names = axp20x_irq_names, |
---|
| 272 | + .num_irq_names = ARRAY_SIZE(axp20x_irq_names), |
---|
| 273 | + .acin_adc = false, |
---|
| 274 | + .acin_path_sel = false, |
---|
155 | 275 | }; |
---|
| 276 | + |
---|
| 277 | +static const struct axp_data axp813_data = { |
---|
| 278 | + .power_desc = &axp813_ac_power_desc, |
---|
| 279 | + .irq_names = axp20x_irq_names, |
---|
| 280 | + .num_irq_names = ARRAY_SIZE(axp20x_irq_names), |
---|
| 281 | + .acin_adc = false, |
---|
| 282 | + .acin_path_sel = true, |
---|
| 283 | +}; |
---|
| 284 | + |
---|
| 285 | +#ifdef CONFIG_PM_SLEEP |
---|
| 286 | +static int axp20x_ac_power_suspend(struct device *dev) |
---|
| 287 | +{ |
---|
| 288 | + struct axp20x_ac_power *power = dev_get_drvdata(dev); |
---|
| 289 | + int i = 0; |
---|
| 290 | + |
---|
| 291 | + /* |
---|
| 292 | + * Allow wake via ACIN_PLUGIN only. |
---|
| 293 | + * |
---|
| 294 | + * As nested threaded IRQs are not automatically disabled during |
---|
| 295 | + * suspend, we must explicitly disable the remainder of the IRQs. |
---|
| 296 | + */ |
---|
| 297 | + if (device_may_wakeup(&power->supply->dev)) |
---|
| 298 | + enable_irq_wake(power->irqs[i++]); |
---|
| 299 | + while (i < power->num_irqs) |
---|
| 300 | + disable_irq(power->irqs[i++]); |
---|
| 301 | + |
---|
| 302 | + return 0; |
---|
| 303 | +} |
---|
| 304 | + |
---|
| 305 | +static int axp20x_ac_power_resume(struct device *dev) |
---|
| 306 | +{ |
---|
| 307 | + struct axp20x_ac_power *power = dev_get_drvdata(dev); |
---|
| 308 | + int i = 0; |
---|
| 309 | + |
---|
| 310 | + if (device_may_wakeup(&power->supply->dev)) |
---|
| 311 | + disable_irq_wake(power->irqs[i++]); |
---|
| 312 | + while (i < power->num_irqs) |
---|
| 313 | + enable_irq(power->irqs[i++]); |
---|
| 314 | + |
---|
| 315 | + return 0; |
---|
| 316 | +} |
---|
| 317 | +#endif |
---|
| 318 | + |
---|
| 319 | +static SIMPLE_DEV_PM_OPS(axp20x_ac_power_pm_ops, axp20x_ac_power_suspend, |
---|
| 320 | + axp20x_ac_power_resume); |
---|
156 | 321 | |
---|
157 | 322 | static int axp20x_ac_power_probe(struct platform_device *pdev) |
---|
158 | 323 | { |
---|
.. | .. |
---|
160 | 325 | struct power_supply_config psy_cfg = {}; |
---|
161 | 326 | struct axp20x_ac_power *power; |
---|
162 | 327 | const struct axp_data *axp_data; |
---|
163 | | - static const char * const irq_names[] = { "ACIN_PLUGIN", "ACIN_REMOVAL", |
---|
164 | | - NULL }; |
---|
165 | 328 | int i, irq, ret; |
---|
166 | 329 | |
---|
167 | 330 | if (!of_device_is_available(pdev->dev.of_node)) |
---|
.. | .. |
---|
172 | 335 | return -EINVAL; |
---|
173 | 336 | } |
---|
174 | 337 | |
---|
175 | | - power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL); |
---|
| 338 | + axp_data = of_device_get_match_data(&pdev->dev); |
---|
| 339 | + |
---|
| 340 | + power = devm_kzalloc(&pdev->dev, |
---|
| 341 | + struct_size(power, irqs, axp_data->num_irq_names), |
---|
| 342 | + GFP_KERNEL); |
---|
176 | 343 | if (!power) |
---|
177 | 344 | return -ENOMEM; |
---|
178 | | - |
---|
179 | | - axp_data = of_device_get_match_data(&pdev->dev); |
---|
180 | 345 | |
---|
181 | 346 | if (axp_data->acin_adc) { |
---|
182 | 347 | power->acin_v = devm_iio_channel_get(&pdev->dev, "acin_v"); |
---|
.. | .. |
---|
195 | 360 | } |
---|
196 | 361 | |
---|
197 | 362 | power->regmap = dev_get_regmap(pdev->dev.parent, NULL); |
---|
| 363 | + power->has_acin_path_sel = axp_data->acin_path_sel; |
---|
| 364 | + power->num_irqs = axp_data->num_irq_names; |
---|
198 | 365 | |
---|
199 | 366 | platform_set_drvdata(pdev, power); |
---|
200 | 367 | |
---|
.. | .. |
---|
208 | 375 | return PTR_ERR(power->supply); |
---|
209 | 376 | |
---|
210 | 377 | /* Request irqs after registering, as irqs may trigger immediately */ |
---|
211 | | - for (i = 0; irq_names[i]; i++) { |
---|
212 | | - irq = platform_get_irq_byname(pdev, irq_names[i]); |
---|
| 378 | + for (i = 0; i < axp_data->num_irq_names; i++) { |
---|
| 379 | + irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]); |
---|
213 | 380 | if (irq < 0) { |
---|
214 | | - dev_warn(&pdev->dev, "No IRQ for %s: %d\n", |
---|
215 | | - irq_names[i], irq); |
---|
216 | | - continue; |
---|
| 381 | + dev_err(&pdev->dev, "No IRQ for %s: %d\n", |
---|
| 382 | + axp_data->irq_names[i], irq); |
---|
| 383 | + return irq; |
---|
217 | 384 | } |
---|
218 | | - irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); |
---|
219 | | - ret = devm_request_any_context_irq(&pdev->dev, irq, |
---|
| 385 | + power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq); |
---|
| 386 | + ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i], |
---|
220 | 387 | axp20x_ac_power_irq, 0, |
---|
221 | 388 | DRVNAME, power); |
---|
222 | | - if (ret < 0) |
---|
223 | | - dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n", |
---|
224 | | - irq_names[i], ret); |
---|
| 389 | + if (ret < 0) { |
---|
| 390 | + dev_err(&pdev->dev, "Error requesting %s IRQ: %d\n", |
---|
| 391 | + axp_data->irq_names[i], ret); |
---|
| 392 | + return ret; |
---|
| 393 | + } |
---|
225 | 394 | } |
---|
226 | 395 | |
---|
227 | 396 | return 0; |
---|
.. | .. |
---|
234 | 403 | }, { |
---|
235 | 404 | .compatible = "x-powers,axp221-ac-power-supply", |
---|
236 | 405 | .data = &axp22x_data, |
---|
| 406 | + }, { |
---|
| 407 | + .compatible = "x-powers,axp813-ac-power-supply", |
---|
| 408 | + .data = &axp813_data, |
---|
237 | 409 | }, { /* sentinel */ } |
---|
238 | 410 | }; |
---|
239 | 411 | MODULE_DEVICE_TABLE(of, axp20x_ac_power_match); |
---|
.. | .. |
---|
241 | 413 | static struct platform_driver axp20x_ac_power_driver = { |
---|
242 | 414 | .probe = axp20x_ac_power_probe, |
---|
243 | 415 | .driver = { |
---|
244 | | - .name = DRVNAME, |
---|
245 | | - .of_match_table = axp20x_ac_power_match, |
---|
| 416 | + .name = DRVNAME, |
---|
| 417 | + .of_match_table = axp20x_ac_power_match, |
---|
| 418 | + .pm = &axp20x_ac_power_pm_ops, |
---|
246 | 419 | }, |
---|
247 | 420 | }; |
---|
248 | 421 | |
---|