1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * AXP20X and AXP22X PMICs' ACIN power supply driver
4 *
5 * Copyright (C) 2016 Free Electrons
6 * Quentin Schulz <quentin.schulz@free-electrons.com>
7 */
8
9#include <linux/device.h>
10#include <linux/init.h>
11#include <linux/interrupt.h>
12#include <linux/kernel.h>
13#include <linux/mfd/axp20x.h>
14#include <linux/module.h>
15#include <linux/of.h>
16#include <linux/platform_device.h>
17#include <linux/pm.h>
18#include <linux/power_supply.h>
19#include <linux/regmap.h>
20#include <linux/slab.h>
21#include <linux/iio/consumer.h>
22
23#define AXP20X_PWR_STATUS_ACIN_PRESENT BIT(7)
24#define AXP20X_PWR_STATUS_ACIN_AVAIL BIT(6)
25
26#define AXP813_ACIN_PATH_SEL BIT(7)
27#define AXP813_ACIN_PATH_SEL_TO_BIT(x) (!!(x) << 7)
28
29#define AXP813_VHOLD_MASK GENMASK(5, 3)
30#define AXP813_VHOLD_UV_TO_BIT(x) ((((x) / 100000) - 40) << 3)
31#define AXP813_VHOLD_REG_TO_UV(x) \
32 (((((x) & AXP813_VHOLD_MASK) >> 3) + 40) * 100000)
33
34#define AXP813_CURR_LIMIT_MASK GENMASK(2, 0)
35#define AXP813_CURR_LIMIT_UA_TO_BIT(x) (((x) / 500000) - 3)
36#define AXP813_CURR_LIMIT_REG_TO_UA(x) \
37 ((((x) & AXP813_CURR_LIMIT_MASK) + 3) * 500000)
38
39#define DRVNAME "axp20x-ac-power-supply"
40
41struct axp20x_ac_power {
42 struct regmap *regmap;
43 struct power_supply *supply;
44 struct iio_channel *acin_v;
45 struct iio_channel *acin_i;
46 bool has_acin_path_sel;
47 unsigned int num_irqs;
48 unsigned int irqs[] __counted_by(num_irqs);
49};
50
51static irqreturn_t axp20x_ac_power_irq(int irq, void *devid)
52{
53 struct axp20x_ac_power *power = devid;
54
55 power_supply_changed(psy: power->supply);
56
57 return IRQ_HANDLED;
58}
59
60static int axp20x_ac_power_get_property(struct power_supply *psy,
61 enum power_supply_property psp,
62 union power_supply_propval *val)
63{
64 struct axp20x_ac_power *power = power_supply_get_drvdata(psy);
65 int ret, reg;
66
67 switch (psp) {
68 case POWER_SUPPLY_PROP_HEALTH:
69 ret = regmap_read(map: power->regmap, AXP20X_PWR_INPUT_STATUS, val: &reg);
70 if (ret)
71 return ret;
72
73 if (reg & AXP20X_PWR_STATUS_ACIN_PRESENT) {
74 val->intval = POWER_SUPPLY_HEALTH_GOOD;
75 return 0;
76 }
77
78 val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
79 return 0;
80
81 case POWER_SUPPLY_PROP_PRESENT:
82 ret = regmap_read(map: power->regmap, AXP20X_PWR_INPUT_STATUS, val: &reg);
83 if (ret)
84 return ret;
85
86 val->intval = !!(reg & AXP20X_PWR_STATUS_ACIN_PRESENT);
87 return 0;
88
89 case POWER_SUPPLY_PROP_ONLINE:
90 ret = regmap_read(map: power->regmap, AXP20X_PWR_INPUT_STATUS, val: &reg);
91 if (ret)
92 return ret;
93
94 val->intval = !!(reg & AXP20X_PWR_STATUS_ACIN_AVAIL);
95
96 /* ACIN_PATH_SEL disables ACIN even if ACIN_AVAIL is set. */
97 if (val->intval && power->has_acin_path_sel) {
98 ret = regmap_read(map: power->regmap, AXP813_ACIN_PATH_CTRL,
99 val: &reg);
100 if (ret)
101 return ret;
102
103 val->intval = !!(reg & AXP813_ACIN_PATH_SEL);
104 }
105
106 return 0;
107
108 case POWER_SUPPLY_PROP_VOLTAGE_NOW:
109 ret = iio_read_channel_processed(chan: power->acin_v, val: &val->intval);
110 if (ret)
111 return ret;
112
113 /* IIO framework gives mV but Power Supply framework gives uV */
114 val->intval *= 1000;
115
116 return 0;
117
118 case POWER_SUPPLY_PROP_CURRENT_NOW:
119 ret = iio_read_channel_processed(chan: power->acin_i, val: &val->intval);
120 if (ret)
121 return ret;
122
123 /* IIO framework gives mA but Power Supply framework gives uA */
124 val->intval *= 1000;
125
126 return 0;
127
128 case POWER_SUPPLY_PROP_VOLTAGE_MIN:
129 ret = regmap_read(map: power->regmap, AXP813_ACIN_PATH_CTRL, val: &reg);
130 if (ret)
131 return ret;
132
133 val->intval = AXP813_VHOLD_REG_TO_UV(reg);
134
135 return 0;
136
137 case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
138 ret = regmap_read(map: power->regmap, AXP813_ACIN_PATH_CTRL, val: &reg);
139 if (ret)
140 return ret;
141
142 val->intval = AXP813_CURR_LIMIT_REG_TO_UA(reg);
143 /* AXP813 datasheet defines values 11x as 4000mA */
144 if (val->intval > 4000000)
145 val->intval = 4000000;
146
147 return 0;
148
149 default:
150 return -EINVAL;
151 }
152
153 return -EINVAL;
154}
155
156static int axp813_ac_power_set_property(struct power_supply *psy,
157 enum power_supply_property psp,
158 const union power_supply_propval *val)
159{
160 struct axp20x_ac_power *power = power_supply_get_drvdata(psy);
161
162 switch (psp) {
163 case POWER_SUPPLY_PROP_ONLINE:
164 return regmap_update_bits(map: power->regmap, AXP813_ACIN_PATH_CTRL,
165 AXP813_ACIN_PATH_SEL,
166 AXP813_ACIN_PATH_SEL_TO_BIT(val->intval));
167
168 case POWER_SUPPLY_PROP_VOLTAGE_MIN:
169 if (val->intval < 4000000 || val->intval > 4700000)
170 return -EINVAL;
171
172 return regmap_update_bits(map: power->regmap, AXP813_ACIN_PATH_CTRL,
173 AXP813_VHOLD_MASK,
174 AXP813_VHOLD_UV_TO_BIT(val->intval));
175
176 case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
177 if (val->intval < 1500000 || val->intval > 4000000)
178 return -EINVAL;
179
180 return regmap_update_bits(map: power->regmap, AXP813_ACIN_PATH_CTRL,
181 AXP813_CURR_LIMIT_MASK,
182 AXP813_CURR_LIMIT_UA_TO_BIT(val->intval));
183
184 default:
185 return -EINVAL;
186 }
187
188 return -EINVAL;
189}
190
191static int axp813_ac_power_prop_writeable(struct power_supply *psy,
192 enum power_supply_property psp)
193{
194 return psp == POWER_SUPPLY_PROP_ONLINE ||
195 psp == POWER_SUPPLY_PROP_VOLTAGE_MIN ||
196 psp == POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT;
197}
198
199static enum power_supply_property axp20x_ac_power_properties[] = {
200 POWER_SUPPLY_PROP_HEALTH,
201 POWER_SUPPLY_PROP_PRESENT,
202 POWER_SUPPLY_PROP_ONLINE,
203 POWER_SUPPLY_PROP_VOLTAGE_NOW,
204 POWER_SUPPLY_PROP_CURRENT_NOW,
205};
206
207static enum power_supply_property axp22x_ac_power_properties[] = {
208 POWER_SUPPLY_PROP_HEALTH,
209 POWER_SUPPLY_PROP_PRESENT,
210 POWER_SUPPLY_PROP_ONLINE,
211};
212
213static enum power_supply_property axp813_ac_power_properties[] = {
214 POWER_SUPPLY_PROP_HEALTH,
215 POWER_SUPPLY_PROP_PRESENT,
216 POWER_SUPPLY_PROP_ONLINE,
217 POWER_SUPPLY_PROP_VOLTAGE_MIN,
218 POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
219};
220
221static const struct power_supply_desc axp20x_ac_power_desc = {
222 .name = "axp20x-ac",
223 .type = POWER_SUPPLY_TYPE_MAINS,
224 .properties = axp20x_ac_power_properties,
225 .num_properties = ARRAY_SIZE(axp20x_ac_power_properties),
226 .get_property = axp20x_ac_power_get_property,
227};
228
229static const struct power_supply_desc axp22x_ac_power_desc = {
230 .name = "axp22x-ac",
231 .type = POWER_SUPPLY_TYPE_MAINS,
232 .properties = axp22x_ac_power_properties,
233 .num_properties = ARRAY_SIZE(axp22x_ac_power_properties),
234 .get_property = axp20x_ac_power_get_property,
235};
236
237static const struct power_supply_desc axp813_ac_power_desc = {
238 .name = "axp813-ac",
239 .type = POWER_SUPPLY_TYPE_MAINS,
240 .properties = axp813_ac_power_properties,
241 .num_properties = ARRAY_SIZE(axp813_ac_power_properties),
242 .property_is_writeable = axp813_ac_power_prop_writeable,
243 .get_property = axp20x_ac_power_get_property,
244 .set_property = axp813_ac_power_set_property,
245};
246
247static const char * const axp20x_irq_names[] = {
248 "ACIN_PLUGIN",
249 "ACIN_REMOVAL",
250};
251
252struct axp_data {
253 const struct power_supply_desc *power_desc;
254 const char * const *irq_names;
255 unsigned int num_irq_names;
256 bool acin_adc;
257 bool acin_path_sel;
258};
259
260static const struct axp_data axp20x_data = {
261 .power_desc = &axp20x_ac_power_desc,
262 .irq_names = axp20x_irq_names,
263 .num_irq_names = ARRAY_SIZE(axp20x_irq_names),
264 .acin_adc = true,
265 .acin_path_sel = false,
266};
267
268static const struct axp_data axp22x_data = {
269 .power_desc = &axp22x_ac_power_desc,
270 .irq_names = axp20x_irq_names,
271 .num_irq_names = ARRAY_SIZE(axp20x_irq_names),
272 .acin_adc = false,
273 .acin_path_sel = false,
274};
275
276static const struct axp_data axp813_data = {
277 .power_desc = &axp813_ac_power_desc,
278 .irq_names = axp20x_irq_names,
279 .num_irq_names = ARRAY_SIZE(axp20x_irq_names),
280 .acin_adc = false,
281 .acin_path_sel = true,
282};
283
284#ifdef CONFIG_PM_SLEEP
285static int axp20x_ac_power_suspend(struct device *dev)
286{
287 struct axp20x_ac_power *power = dev_get_drvdata(dev);
288 int i = 0;
289
290 /*
291 * Allow wake via ACIN_PLUGIN only.
292 *
293 * As nested threaded IRQs are not automatically disabled during
294 * suspend, we must explicitly disable the remainder of the IRQs.
295 */
296 if (device_may_wakeup(dev: &power->supply->dev))
297 enable_irq_wake(irq: power->irqs[i++]);
298 while (i < power->num_irqs)
299 disable_irq(irq: power->irqs[i++]);
300
301 return 0;
302}
303
304static int axp20x_ac_power_resume(struct device *dev)
305{
306 struct axp20x_ac_power *power = dev_get_drvdata(dev);
307 int i = 0;
308
309 if (device_may_wakeup(dev: &power->supply->dev))
310 disable_irq_wake(irq: power->irqs[i++]);
311 while (i < power->num_irqs)
312 enable_irq(irq: power->irqs[i++]);
313
314 return 0;
315}
316#endif
317
318static SIMPLE_DEV_PM_OPS(axp20x_ac_power_pm_ops, axp20x_ac_power_suspend,
319 axp20x_ac_power_resume);
320
321static int axp20x_ac_power_probe(struct platform_device *pdev)
322{
323 struct axp20x_dev *axp20x = dev_get_drvdata(dev: pdev->dev.parent);
324 struct power_supply_config psy_cfg = {};
325 struct axp20x_ac_power *power;
326 const struct axp_data *axp_data;
327 int i, irq, ret;
328
329 if (!of_device_is_available(device: pdev->dev.of_node))
330 return -ENODEV;
331
332 if (!axp20x) {
333 dev_err(&pdev->dev, "Parent drvdata not set\n");
334 return -EINVAL;
335 }
336
337 axp_data = of_device_get_match_data(dev: &pdev->dev);
338
339 power = devm_kzalloc(dev: &pdev->dev,
340 struct_size(power, irqs, axp_data->num_irq_names),
341 GFP_KERNEL);
342 if (!power)
343 return -ENOMEM;
344
345 if (axp_data->acin_adc) {
346 power->acin_v = devm_iio_channel_get(dev: &pdev->dev, consumer_channel: "acin_v");
347 if (IS_ERR(ptr: power->acin_v)) {
348 if (PTR_ERR(ptr: power->acin_v) == -ENODEV)
349 return -EPROBE_DEFER;
350 return PTR_ERR(ptr: power->acin_v);
351 }
352
353 power->acin_i = devm_iio_channel_get(dev: &pdev->dev, consumer_channel: "acin_i");
354 if (IS_ERR(ptr: power->acin_i)) {
355 if (PTR_ERR(ptr: power->acin_i) == -ENODEV)
356 return -EPROBE_DEFER;
357 return PTR_ERR(ptr: power->acin_i);
358 }
359 }
360
361 power->regmap = dev_get_regmap(dev: pdev->dev.parent, NULL);
362 power->has_acin_path_sel = axp_data->acin_path_sel;
363 power->num_irqs = axp_data->num_irq_names;
364
365 platform_set_drvdata(pdev, data: power);
366
367 psy_cfg.of_node = pdev->dev.of_node;
368 psy_cfg.drv_data = power;
369
370 power->supply = devm_power_supply_register(parent: &pdev->dev,
371 desc: axp_data->power_desc,
372 cfg: &psy_cfg);
373 if (IS_ERR(ptr: power->supply))
374 return PTR_ERR(ptr: power->supply);
375
376 /* Request irqs after registering, as irqs may trigger immediately */
377 for (i = 0; i < axp_data->num_irq_names; i++) {
378 irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]);
379 if (irq < 0)
380 return irq;
381
382 power->irqs[i] = regmap_irq_get_virq(data: axp20x->regmap_irqc, irq);
383 ret = devm_request_any_context_irq(dev: &pdev->dev, irq: power->irqs[i],
384 handler: axp20x_ac_power_irq, irqflags: 0,
385 DRVNAME, dev_id: power);
386 if (ret < 0) {
387 dev_err(&pdev->dev, "Error requesting %s IRQ: %d\n",
388 axp_data->irq_names[i], ret);
389 return ret;
390 }
391 }
392
393 return 0;
394}
395
396static const struct of_device_id axp20x_ac_power_match[] = {
397 {
398 .compatible = "x-powers,axp202-ac-power-supply",
399 .data = &axp20x_data,
400 }, {
401 .compatible = "x-powers,axp221-ac-power-supply",
402 .data = &axp22x_data,
403 }, {
404 .compatible = "x-powers,axp813-ac-power-supply",
405 .data = &axp813_data,
406 }, { /* sentinel */ }
407};
408MODULE_DEVICE_TABLE(of, axp20x_ac_power_match);
409
410static struct platform_driver axp20x_ac_power_driver = {
411 .probe = axp20x_ac_power_probe,
412 .driver = {
413 .name = DRVNAME,
414 .of_match_table = axp20x_ac_power_match,
415 .pm = &axp20x_ac_power_pm_ops,
416 },
417};
418
419module_platform_driver(axp20x_ac_power_driver);
420
421MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
422MODULE_DESCRIPTION("AXP20X and AXP22X PMICs' AC power supply driver");
423MODULE_LICENSE("GPL");
424

source code of linux/drivers/power/supply/axp20x_ac_power.c