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 | |
41 | struct 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 | |
51 | static 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 | |
60 | static 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: ®); |
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: ®); |
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: ®); |
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: ®); |
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: ®); |
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: ®); |
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 | |
156 | static 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 | |
191 | static 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 | |
199 | static 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 | |
207 | static 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 | |
213 | static 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 | |
221 | static 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 | |
229 | static 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 | |
237 | static 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 | |
247 | static const char * const axp20x_irq_names[] = { |
248 | "ACIN_PLUGIN" , |
249 | "ACIN_REMOVAL" , |
250 | }; |
251 | |
252 | struct 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 | |
260 | static 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 | |
268 | static 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 | |
276 | static 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 |
285 | static 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 | |
304 | static 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 | |
318 | static SIMPLE_DEV_PM_OPS(axp20x_ac_power_pm_ops, axp20x_ac_power_suspend, |
319 | axp20x_ac_power_resume); |
320 | |
321 | static 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 | |
396 | static 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 | }; |
408 | MODULE_DEVICE_TABLE(of, axp20x_ac_power_match); |
409 | |
410 | static 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 | |
419 | module_platform_driver(axp20x_ac_power_driver); |
420 | |
421 | MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>" ); |
422 | MODULE_DESCRIPTION("AXP20X and AXP22X PMICs' AC power supply driver" ); |
423 | MODULE_LICENSE("GPL" ); |
424 | |