| .. | .. |
|---|
| 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 | |
|---|