1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * AXP20X and AXP22X PMICs' ACIN power supply driver
5 * Copyright (C) 2016 Free Electrons
6 * Quentin Schulz <quentin.schulz@free-electrons.com>
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>
16 #include <linux/of_device.h>
17 #include <linux/platform_device.h>
18 #include <linux/power_supply.h>
19 #include <linux/regmap.h>
20 #include <linux/slab.h>
21 #include <linux/iio/consumer.h>
23 #define AXP20X_PWR_STATUS_ACIN_PRESENT BIT(7)
24 #define AXP20X_PWR_STATUS_ACIN_AVAIL BIT(6)
26 #define AXP813_VHOLD_MASK GENMASK(5, 3)
27 #define AXP813_VHOLD_UV_TO_BIT(x) ((((x) / 100000) - 40) << 3)
28 #define AXP813_VHOLD_REG_TO_UV(x) \
29 (((((x) & AXP813_VHOLD_MASK) >> 3) + 40) * 100000)
31 #define AXP813_CURR_LIMIT_MASK GENMASK(2, 0)
32 #define AXP813_CURR_LIMIT_UA_TO_BIT(x) (((x) / 500000) - 3)
33 #define AXP813_CURR_LIMIT_REG_TO_UA(x) \
34 ((((x) & AXP813_CURR_LIMIT_MASK) + 3) * 500000)
36 #define DRVNAME "axp20x-ac-power-supply"
38 struct axp20x_ac_power
{
39 struct regmap
*regmap
;
40 struct power_supply
*supply
;
41 struct iio_channel
*acin_v
;
42 struct iio_channel
*acin_i
;
45 static irqreturn_t
axp20x_ac_power_irq(int irq
, void *devid
)
47 struct axp20x_ac_power
*power
= devid
;
49 power_supply_changed(power
->supply
);
54 static int axp20x_ac_power_get_property(struct power_supply
*psy
,
55 enum power_supply_property psp
,
56 union power_supply_propval
*val
)
58 struct axp20x_ac_power
*power
= power_supply_get_drvdata(psy
);
62 case POWER_SUPPLY_PROP_HEALTH
:
63 ret
= regmap_read(power
->regmap
, AXP20X_PWR_INPUT_STATUS
, ®
);
67 if (reg
& AXP20X_PWR_STATUS_ACIN_PRESENT
) {
68 val
->intval
= POWER_SUPPLY_HEALTH_GOOD
;
72 val
->intval
= POWER_SUPPLY_HEALTH_UNKNOWN
;
75 case POWER_SUPPLY_PROP_PRESENT
:
76 ret
= regmap_read(power
->regmap
, AXP20X_PWR_INPUT_STATUS
, ®
);
80 val
->intval
= !!(reg
& AXP20X_PWR_STATUS_ACIN_PRESENT
);
83 case POWER_SUPPLY_PROP_ONLINE
:
84 ret
= regmap_read(power
->regmap
, AXP20X_PWR_INPUT_STATUS
, ®
);
88 val
->intval
= !!(reg
& AXP20X_PWR_STATUS_ACIN_AVAIL
);
91 case POWER_SUPPLY_PROP_VOLTAGE_NOW
:
92 ret
= iio_read_channel_processed(power
->acin_v
, &val
->intval
);
96 /* IIO framework gives mV but Power Supply framework gives uV */
101 case POWER_SUPPLY_PROP_CURRENT_NOW
:
102 ret
= iio_read_channel_processed(power
->acin_i
, &val
->intval
);
106 /* IIO framework gives mA but Power Supply framework gives uA */
111 case POWER_SUPPLY_PROP_VOLTAGE_MIN
:
112 ret
= regmap_read(power
->regmap
, AXP813_ACIN_PATH_CTRL
, ®
);
116 val
->intval
= AXP813_VHOLD_REG_TO_UV(reg
);
120 case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT
:
121 ret
= regmap_read(power
->regmap
, AXP813_ACIN_PATH_CTRL
, ®
);
125 val
->intval
= AXP813_CURR_LIMIT_REG_TO_UA(reg
);
126 /* AXP813 datasheet defines values 11x as 4000mA */
127 if (val
->intval
> 4000000)
128 val
->intval
= 4000000;
139 static int axp813_ac_power_set_property(struct power_supply
*psy
,
140 enum power_supply_property psp
,
141 const union power_supply_propval
*val
)
143 struct axp20x_ac_power
*power
= power_supply_get_drvdata(psy
);
146 case POWER_SUPPLY_PROP_VOLTAGE_MIN
:
147 if (val
->intval
< 4000000 || val
->intval
> 4700000)
150 return regmap_update_bits(power
->regmap
, AXP813_ACIN_PATH_CTRL
,
152 AXP813_VHOLD_UV_TO_BIT(val
->intval
));
154 case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT
:
155 if (val
->intval
< 1500000 || val
->intval
> 4000000)
158 return regmap_update_bits(power
->regmap
, AXP813_ACIN_PATH_CTRL
,
159 AXP813_CURR_LIMIT_MASK
,
160 AXP813_CURR_LIMIT_UA_TO_BIT(val
->intval
));
169 static int axp813_ac_power_prop_writeable(struct power_supply
*psy
,
170 enum power_supply_property psp
)
172 return psp
== POWER_SUPPLY_PROP_VOLTAGE_MIN
||
173 psp
== POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT
;
176 static enum power_supply_property axp20x_ac_power_properties
[] = {
177 POWER_SUPPLY_PROP_HEALTH
,
178 POWER_SUPPLY_PROP_PRESENT
,
179 POWER_SUPPLY_PROP_ONLINE
,
180 POWER_SUPPLY_PROP_VOLTAGE_NOW
,
181 POWER_SUPPLY_PROP_CURRENT_NOW
,
184 static enum power_supply_property axp22x_ac_power_properties
[] = {
185 POWER_SUPPLY_PROP_HEALTH
,
186 POWER_SUPPLY_PROP_PRESENT
,
187 POWER_SUPPLY_PROP_ONLINE
,
190 static enum power_supply_property axp813_ac_power_properties
[] = {
191 POWER_SUPPLY_PROP_HEALTH
,
192 POWER_SUPPLY_PROP_PRESENT
,
193 POWER_SUPPLY_PROP_ONLINE
,
194 POWER_SUPPLY_PROP_VOLTAGE_MIN
,
195 POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT
,
198 static const struct power_supply_desc axp20x_ac_power_desc
= {
200 .type
= POWER_SUPPLY_TYPE_MAINS
,
201 .properties
= axp20x_ac_power_properties
,
202 .num_properties
= ARRAY_SIZE(axp20x_ac_power_properties
),
203 .get_property
= axp20x_ac_power_get_property
,
206 static const struct power_supply_desc axp22x_ac_power_desc
= {
208 .type
= POWER_SUPPLY_TYPE_MAINS
,
209 .properties
= axp22x_ac_power_properties
,
210 .num_properties
= ARRAY_SIZE(axp22x_ac_power_properties
),
211 .get_property
= axp20x_ac_power_get_property
,
214 static const struct power_supply_desc axp813_ac_power_desc
= {
216 .type
= POWER_SUPPLY_TYPE_MAINS
,
217 .properties
= axp813_ac_power_properties
,
218 .num_properties
= ARRAY_SIZE(axp813_ac_power_properties
),
219 .property_is_writeable
= axp813_ac_power_prop_writeable
,
220 .get_property
= axp20x_ac_power_get_property
,
221 .set_property
= axp813_ac_power_set_property
,
225 const struct power_supply_desc
*power_desc
;
229 static const struct axp_data axp20x_data
= {
230 .power_desc
= &axp20x_ac_power_desc
,
234 static const struct axp_data axp22x_data
= {
235 .power_desc
= &axp22x_ac_power_desc
,
239 static const struct axp_data axp813_data
= {
240 .power_desc
= &axp813_ac_power_desc
,
244 static int axp20x_ac_power_probe(struct platform_device
*pdev
)
246 struct axp20x_dev
*axp20x
= dev_get_drvdata(pdev
->dev
.parent
);
247 struct power_supply_config psy_cfg
= {};
248 struct axp20x_ac_power
*power
;
249 const struct axp_data
*axp_data
;
250 static const char * const irq_names
[] = { "ACIN_PLUGIN", "ACIN_REMOVAL",
254 if (!of_device_is_available(pdev
->dev
.of_node
))
258 dev_err(&pdev
->dev
, "Parent drvdata not set\n");
262 power
= devm_kzalloc(&pdev
->dev
, sizeof(*power
), GFP_KERNEL
);
266 axp_data
= of_device_get_match_data(&pdev
->dev
);
268 if (axp_data
->acin_adc
) {
269 power
->acin_v
= devm_iio_channel_get(&pdev
->dev
, "acin_v");
270 if (IS_ERR(power
->acin_v
)) {
271 if (PTR_ERR(power
->acin_v
) == -ENODEV
)
272 return -EPROBE_DEFER
;
273 return PTR_ERR(power
->acin_v
);
276 power
->acin_i
= devm_iio_channel_get(&pdev
->dev
, "acin_i");
277 if (IS_ERR(power
->acin_i
)) {
278 if (PTR_ERR(power
->acin_i
) == -ENODEV
)
279 return -EPROBE_DEFER
;
280 return PTR_ERR(power
->acin_i
);
284 power
->regmap
= dev_get_regmap(pdev
->dev
.parent
, NULL
);
286 platform_set_drvdata(pdev
, power
);
288 psy_cfg
.of_node
= pdev
->dev
.of_node
;
289 psy_cfg
.drv_data
= power
;
291 power
->supply
= devm_power_supply_register(&pdev
->dev
,
292 axp_data
->power_desc
,
294 if (IS_ERR(power
->supply
))
295 return PTR_ERR(power
->supply
);
297 /* Request irqs after registering, as irqs may trigger immediately */
298 for (i
= 0; irq_names
[i
]; i
++) {
299 irq
= platform_get_irq_byname(pdev
, irq_names
[i
]);
301 dev_warn(&pdev
->dev
, "No IRQ for %s: %d\n",
305 irq
= regmap_irq_get_virq(axp20x
->regmap_irqc
, irq
);
306 ret
= devm_request_any_context_irq(&pdev
->dev
, irq
,
307 axp20x_ac_power_irq
, 0,
310 dev_warn(&pdev
->dev
, "Error requesting %s IRQ: %d\n",
317 static const struct of_device_id axp20x_ac_power_match
[] = {
319 .compatible
= "x-powers,axp202-ac-power-supply",
320 .data
= &axp20x_data
,
322 .compatible
= "x-powers,axp221-ac-power-supply",
323 .data
= &axp22x_data
,
325 .compatible
= "x-powers,axp813-ac-power-supply",
326 .data
= &axp813_data
,
327 }, { /* sentinel */ }
329 MODULE_DEVICE_TABLE(of
, axp20x_ac_power_match
);
331 static struct platform_driver axp20x_ac_power_driver
= {
332 .probe
= axp20x_ac_power_probe
,
335 .of_match_table
= axp20x_ac_power_match
,
339 module_platform_driver(axp20x_ac_power_driver
);
341 MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
342 MODULE_DESCRIPTION("AXP20X and AXP22X PMICs' AC power supply driver");
343 MODULE_LICENSE("GPL");