1 // SPDX-License-Identifier: GPL-2.0-only
3 * Dumb driver for LiIon batteries using TWL4030 madc.
5 * Copyright 2013 Golden Delicious Computers
6 * Lukas Märdian <lukas@goldelico.com>
8 * Based on dumb driver for gta01 battery
9 * Copyright 2009 Openmoko, Inc
10 * Balaji Rao <balajirrao@openmoko.org>
13 #include <linux/module.h>
14 #include <linux/param.h>
15 #include <linux/delay.h>
16 #include <linux/workqueue.h>
17 #include <linux/platform_device.h>
18 #include <linux/power_supply.h>
19 #include <linux/slab.h>
20 #include <linux/sort.h>
21 #include <linux/power/twl4030_madc_battery.h>
22 #include <linux/iio/consumer.h>
24 struct twl4030_madc_battery
{
25 struct power_supply
*psy
;
26 struct twl4030_madc_bat_platform_data
*pdata
;
27 struct iio_channel
*channel_temp
;
28 struct iio_channel
*channel_ichg
;
29 struct iio_channel
*channel_vbat
;
32 static enum power_supply_property twl4030_madc_bat_props
[] = {
33 POWER_SUPPLY_PROP_PRESENT
,
34 POWER_SUPPLY_PROP_STATUS
,
35 POWER_SUPPLY_PROP_TECHNOLOGY
,
36 POWER_SUPPLY_PROP_VOLTAGE_NOW
,
37 POWER_SUPPLY_PROP_CURRENT_NOW
,
38 POWER_SUPPLY_PROP_CAPACITY
,
39 POWER_SUPPLY_PROP_CHARGE_FULL
,
40 POWER_SUPPLY_PROP_CHARGE_NOW
,
41 POWER_SUPPLY_PROP_TEMP
,
42 POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW
,
45 static int madc_read(struct iio_channel
*channel
)
48 err
= iio_read_channel_processed(channel
, &val
);
55 static int twl4030_madc_bat_get_charging_status(struct twl4030_madc_battery
*bt
)
57 return (madc_read(bt
->channel_ichg
) > 0) ? 1 : 0;
60 static int twl4030_madc_bat_get_voltage(struct twl4030_madc_battery
*bt
)
62 return madc_read(bt
->channel_vbat
);
65 static int twl4030_madc_bat_get_current(struct twl4030_madc_battery
*bt
)
67 return madc_read(bt
->channel_ichg
) * 1000;
70 static int twl4030_madc_bat_get_temp(struct twl4030_madc_battery
*bt
)
72 return madc_read(bt
->channel_temp
) * 10;
75 static int twl4030_madc_bat_voltscale(struct twl4030_madc_battery
*bat
,
78 struct twl4030_madc_bat_calibration
*calibration
;
81 /* choose charging curve */
82 if (twl4030_madc_bat_get_charging_status(bat
))
83 calibration
= bat
->pdata
->charging
;
85 calibration
= bat
->pdata
->discharging
;
87 if (volt
> calibration
[0].voltage
) {
88 res
= calibration
[0].level
;
90 for (i
= 0; calibration
[i
+1].voltage
>= 0; i
++) {
91 if (volt
<= calibration
[i
].voltage
&&
92 volt
>= calibration
[i
+1].voltage
) {
93 /* interval found - interpolate within range */
94 res
= calibration
[i
].level
-
95 ((calibration
[i
].voltage
- volt
) *
96 (calibration
[i
].level
-
97 calibration
[i
+1].level
)) /
98 (calibration
[i
].voltage
-
99 calibration
[i
+1].voltage
);
107 static int twl4030_madc_bat_get_property(struct power_supply
*psy
,
108 enum power_supply_property psp
,
109 union power_supply_propval
*val
)
111 struct twl4030_madc_battery
*bat
= power_supply_get_drvdata(psy
);
114 case POWER_SUPPLY_PROP_STATUS
:
115 if (twl4030_madc_bat_voltscale(bat
,
116 twl4030_madc_bat_get_voltage(bat
)) > 95)
117 val
->intval
= POWER_SUPPLY_STATUS_FULL
;
119 if (twl4030_madc_bat_get_charging_status(bat
))
120 val
->intval
= POWER_SUPPLY_STATUS_CHARGING
;
122 val
->intval
= POWER_SUPPLY_STATUS_DISCHARGING
;
125 case POWER_SUPPLY_PROP_VOLTAGE_NOW
:
126 val
->intval
= twl4030_madc_bat_get_voltage(bat
) * 1000;
128 case POWER_SUPPLY_PROP_TECHNOLOGY
:
129 val
->intval
= POWER_SUPPLY_TECHNOLOGY_LION
;
131 case POWER_SUPPLY_PROP_CURRENT_NOW
:
132 val
->intval
= twl4030_madc_bat_get_current(bat
);
134 case POWER_SUPPLY_PROP_PRESENT
:
135 /* assume battery is always present */
138 case POWER_SUPPLY_PROP_CHARGE_NOW
: {
139 int percent
= twl4030_madc_bat_voltscale(bat
,
140 twl4030_madc_bat_get_voltage(bat
));
141 val
->intval
= (percent
* bat
->pdata
->capacity
) / 100;
144 case POWER_SUPPLY_PROP_CAPACITY
:
145 val
->intval
= twl4030_madc_bat_voltscale(bat
,
146 twl4030_madc_bat_get_voltage(bat
));
148 case POWER_SUPPLY_PROP_CHARGE_FULL
:
149 val
->intval
= bat
->pdata
->capacity
;
151 case POWER_SUPPLY_PROP_TEMP
:
152 val
->intval
= twl4030_madc_bat_get_temp(bat
);
154 case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW
: {
155 int percent
= twl4030_madc_bat_voltscale(bat
,
156 twl4030_madc_bat_get_voltage(bat
));
158 int chg
= (percent
* (bat
->pdata
->capacity
/1000))/100;
160 /* assume discharge with 400 mA (ca. 1.5W) */
161 val
->intval
= (3600l * chg
) / 400;
171 static void twl4030_madc_bat_ext_changed(struct power_supply
*psy
)
173 power_supply_changed(psy
);
176 static const struct power_supply_desc twl4030_madc_bat_desc
= {
177 .name
= "twl4030_battery",
178 .type
= POWER_SUPPLY_TYPE_BATTERY
,
179 .properties
= twl4030_madc_bat_props
,
180 .num_properties
= ARRAY_SIZE(twl4030_madc_bat_props
),
181 .get_property
= twl4030_madc_bat_get_property
,
182 .external_power_changed
= twl4030_madc_bat_ext_changed
,
186 static int twl4030_cmp(const void *a
, const void *b
)
188 return ((struct twl4030_madc_bat_calibration
*)b
)->voltage
-
189 ((struct twl4030_madc_bat_calibration
*)a
)->voltage
;
192 static int twl4030_madc_battery_probe(struct platform_device
*pdev
)
194 struct twl4030_madc_battery
*twl4030_madc_bat
;
195 struct twl4030_madc_bat_platform_data
*pdata
= pdev
->dev
.platform_data
;
196 struct power_supply_config psy_cfg
= {};
199 twl4030_madc_bat
= devm_kzalloc(&pdev
->dev
, sizeof(*twl4030_madc_bat
),
201 if (!twl4030_madc_bat
)
204 twl4030_madc_bat
->channel_temp
= iio_channel_get(&pdev
->dev
, "temp");
205 if (IS_ERR(twl4030_madc_bat
->channel_temp
)) {
206 ret
= PTR_ERR(twl4030_madc_bat
->channel_temp
);
210 twl4030_madc_bat
->channel_ichg
= iio_channel_get(&pdev
->dev
, "ichg");
211 if (IS_ERR(twl4030_madc_bat
->channel_ichg
)) {
212 ret
= PTR_ERR(twl4030_madc_bat
->channel_ichg
);
216 twl4030_madc_bat
->channel_vbat
= iio_channel_get(&pdev
->dev
, "vbat");
217 if (IS_ERR(twl4030_madc_bat
->channel_vbat
)) {
218 ret
= PTR_ERR(twl4030_madc_bat
->channel_vbat
);
222 /* sort charging and discharging calibration data */
223 sort(pdata
->charging
, pdata
->charging_size
,
224 sizeof(struct twl4030_madc_bat_calibration
),
226 sort(pdata
->discharging
, pdata
->discharging_size
,
227 sizeof(struct twl4030_madc_bat_calibration
),
230 twl4030_madc_bat
->pdata
= pdata
;
231 platform_set_drvdata(pdev
, twl4030_madc_bat
);
232 psy_cfg
.drv_data
= twl4030_madc_bat
;
233 twl4030_madc_bat
->psy
= power_supply_register(&pdev
->dev
,
234 &twl4030_madc_bat_desc
,
236 if (IS_ERR(twl4030_madc_bat
->psy
)) {
237 ret
= PTR_ERR(twl4030_madc_bat
->psy
);
244 iio_channel_release(twl4030_madc_bat
->channel_vbat
);
246 iio_channel_release(twl4030_madc_bat
->channel_ichg
);
248 iio_channel_release(twl4030_madc_bat
->channel_temp
);
253 static int twl4030_madc_battery_remove(struct platform_device
*pdev
)
255 struct twl4030_madc_battery
*bat
= platform_get_drvdata(pdev
);
257 power_supply_unregister(bat
->psy
);
259 iio_channel_release(bat
->channel_vbat
);
260 iio_channel_release(bat
->channel_ichg
);
261 iio_channel_release(bat
->channel_temp
);
266 static struct platform_driver twl4030_madc_battery_driver
= {
268 .name
= "twl4030_madc_battery",
270 .probe
= twl4030_madc_battery_probe
,
271 .remove
= twl4030_madc_battery_remove
,
273 module_platform_driver(twl4030_madc_battery_driver
);
275 MODULE_LICENSE("GPL");
276 MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>");
277 MODULE_DESCRIPTION("twl4030_madc battery driver");
278 MODULE_ALIAS("platform:twl4030_madc_battery");