1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Dumb driver for LiIon batteries using TWL4030 madc. |
4 | * |
5 | * Copyright 2013 Golden Delicious Computers |
6 | * Lukas Märdian <lukas@goldelico.com> |
7 | * |
8 | * Based on dumb driver for gta01 battery |
9 | * Copyright 2009 Openmoko, Inc |
10 | * Balaji Rao <balajirrao@openmoko.org> |
11 | */ |
12 | |
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> |
23 | |
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; |
30 | }; |
31 | |
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, |
43 | }; |
44 | |
45 | static int madc_read(struct iio_channel *channel) |
46 | { |
47 | int val, err; |
48 | err = iio_read_channel_processed(chan: channel, val: &val); |
49 | if (err < 0) |
50 | return err; |
51 | |
52 | return val; |
53 | } |
54 | |
55 | static int twl4030_madc_bat_get_charging_status(struct twl4030_madc_battery *bt) |
56 | { |
57 | return (madc_read(channel: bt->channel_ichg) > 0) ? 1 : 0; |
58 | } |
59 | |
60 | static int twl4030_madc_bat_get_voltage(struct twl4030_madc_battery *bt) |
61 | { |
62 | return madc_read(channel: bt->channel_vbat); |
63 | } |
64 | |
65 | static int twl4030_madc_bat_get_current(struct twl4030_madc_battery *bt) |
66 | { |
67 | return madc_read(channel: bt->channel_ichg) * 1000; |
68 | } |
69 | |
70 | static int twl4030_madc_bat_get_temp(struct twl4030_madc_battery *bt) |
71 | { |
72 | return madc_read(channel: bt->channel_temp) * 10; |
73 | } |
74 | |
75 | static int twl4030_madc_bat_voltscale(struct twl4030_madc_battery *bat, |
76 | int volt) |
77 | { |
78 | struct twl4030_madc_bat_calibration *calibration; |
79 | int i, res = 0; |
80 | |
81 | /* choose charging curve */ |
82 | if (twl4030_madc_bat_get_charging_status(bt: bat)) |
83 | calibration = bat->pdata->charging; |
84 | else |
85 | calibration = bat->pdata->discharging; |
86 | |
87 | if (volt > calibration[0].voltage) { |
88 | res = calibration[0].level; |
89 | } else { |
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); |
100 | break; |
101 | } |
102 | } |
103 | } |
104 | return res; |
105 | } |
106 | |
107 | static int twl4030_madc_bat_get_property(struct power_supply *psy, |
108 | enum power_supply_property psp, |
109 | union power_supply_propval *val) |
110 | { |
111 | struct twl4030_madc_battery *bat = power_supply_get_drvdata(psy); |
112 | |
113 | switch (psp) { |
114 | case POWER_SUPPLY_PROP_STATUS: |
115 | if (twl4030_madc_bat_voltscale(bat, |
116 | volt: twl4030_madc_bat_get_voltage(bt: bat)) > 95) |
117 | val->intval = POWER_SUPPLY_STATUS_FULL; |
118 | else { |
119 | if (twl4030_madc_bat_get_charging_status(bt: bat)) |
120 | val->intval = POWER_SUPPLY_STATUS_CHARGING; |
121 | else |
122 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; |
123 | } |
124 | break; |
125 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
126 | val->intval = twl4030_madc_bat_get_voltage(bt: bat) * 1000; |
127 | break; |
128 | case POWER_SUPPLY_PROP_TECHNOLOGY: |
129 | val->intval = POWER_SUPPLY_TECHNOLOGY_LION; |
130 | break; |
131 | case POWER_SUPPLY_PROP_CURRENT_NOW: |
132 | val->intval = twl4030_madc_bat_get_current(bt: bat); |
133 | break; |
134 | case POWER_SUPPLY_PROP_PRESENT: |
135 | /* assume battery is always present */ |
136 | val->intval = 1; |
137 | break; |
138 | case POWER_SUPPLY_PROP_CHARGE_NOW: { |
139 | int percent = twl4030_madc_bat_voltscale(bat, |
140 | volt: twl4030_madc_bat_get_voltage(bt: bat)); |
141 | val->intval = (percent * bat->pdata->capacity) / 100; |
142 | break; |
143 | } |
144 | case POWER_SUPPLY_PROP_CAPACITY: |
145 | val->intval = twl4030_madc_bat_voltscale(bat, |
146 | volt: twl4030_madc_bat_get_voltage(bt: bat)); |
147 | break; |
148 | case POWER_SUPPLY_PROP_CHARGE_FULL: |
149 | val->intval = bat->pdata->capacity; |
150 | break; |
151 | case POWER_SUPPLY_PROP_TEMP: |
152 | val->intval = twl4030_madc_bat_get_temp(bt: bat); |
153 | break; |
154 | case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: { |
155 | int percent = twl4030_madc_bat_voltscale(bat, |
156 | volt: twl4030_madc_bat_get_voltage(bt: bat)); |
157 | /* in mAh */ |
158 | int chg = (percent * (bat->pdata->capacity/1000))/100; |
159 | |
160 | /* assume discharge with 400 mA (ca. 1.5W) */ |
161 | val->intval = (3600l * chg) / 400; |
162 | break; |
163 | } |
164 | default: |
165 | return -EINVAL; |
166 | } |
167 | |
168 | return 0; |
169 | } |
170 | |
171 | static const struct power_supply_desc twl4030_madc_bat_desc = { |
172 | .name = "twl4030_battery" , |
173 | .type = POWER_SUPPLY_TYPE_BATTERY, |
174 | .properties = twl4030_madc_bat_props, |
175 | .num_properties = ARRAY_SIZE(twl4030_madc_bat_props), |
176 | .get_property = twl4030_madc_bat_get_property, |
177 | .external_power_changed = power_supply_changed, |
178 | }; |
179 | |
180 | static int twl4030_cmp(const void *a, const void *b) |
181 | { |
182 | return ((struct twl4030_madc_bat_calibration *)b)->voltage - |
183 | ((struct twl4030_madc_bat_calibration *)a)->voltage; |
184 | } |
185 | |
186 | static int twl4030_madc_battery_probe(struct platform_device *pdev) |
187 | { |
188 | struct twl4030_madc_battery *twl4030_madc_bat; |
189 | struct twl4030_madc_bat_platform_data *pdata = pdev->dev.platform_data; |
190 | struct power_supply_config psy_cfg = {}; |
191 | int ret = 0; |
192 | |
193 | twl4030_madc_bat = devm_kzalloc(dev: &pdev->dev, size: sizeof(*twl4030_madc_bat), |
194 | GFP_KERNEL); |
195 | if (!twl4030_madc_bat) |
196 | return -ENOMEM; |
197 | |
198 | twl4030_madc_bat->channel_temp = iio_channel_get(dev: &pdev->dev, consumer_channel: "temp" ); |
199 | if (IS_ERR(ptr: twl4030_madc_bat->channel_temp)) { |
200 | ret = PTR_ERR(ptr: twl4030_madc_bat->channel_temp); |
201 | goto err; |
202 | } |
203 | |
204 | twl4030_madc_bat->channel_ichg = iio_channel_get(dev: &pdev->dev, consumer_channel: "ichg" ); |
205 | if (IS_ERR(ptr: twl4030_madc_bat->channel_ichg)) { |
206 | ret = PTR_ERR(ptr: twl4030_madc_bat->channel_ichg); |
207 | goto err_temp; |
208 | } |
209 | |
210 | twl4030_madc_bat->channel_vbat = iio_channel_get(dev: &pdev->dev, consumer_channel: "vbat" ); |
211 | if (IS_ERR(ptr: twl4030_madc_bat->channel_vbat)) { |
212 | ret = PTR_ERR(ptr: twl4030_madc_bat->channel_vbat); |
213 | goto err_ichg; |
214 | } |
215 | |
216 | /* sort charging and discharging calibration data */ |
217 | sort(base: pdata->charging, num: pdata->charging_size, |
218 | size: sizeof(struct twl4030_madc_bat_calibration), |
219 | cmp_func: twl4030_cmp, NULL); |
220 | sort(base: pdata->discharging, num: pdata->discharging_size, |
221 | size: sizeof(struct twl4030_madc_bat_calibration), |
222 | cmp_func: twl4030_cmp, NULL); |
223 | |
224 | twl4030_madc_bat->pdata = pdata; |
225 | platform_set_drvdata(pdev, data: twl4030_madc_bat); |
226 | psy_cfg.drv_data = twl4030_madc_bat; |
227 | twl4030_madc_bat->psy = power_supply_register(parent: &pdev->dev, |
228 | desc: &twl4030_madc_bat_desc, |
229 | cfg: &psy_cfg); |
230 | if (IS_ERR(ptr: twl4030_madc_bat->psy)) { |
231 | ret = PTR_ERR(ptr: twl4030_madc_bat->psy); |
232 | goto err_vbat; |
233 | } |
234 | |
235 | return 0; |
236 | |
237 | err_vbat: |
238 | iio_channel_release(chan: twl4030_madc_bat->channel_vbat); |
239 | err_ichg: |
240 | iio_channel_release(chan: twl4030_madc_bat->channel_ichg); |
241 | err_temp: |
242 | iio_channel_release(chan: twl4030_madc_bat->channel_temp); |
243 | err: |
244 | return ret; |
245 | } |
246 | |
247 | static void twl4030_madc_battery_remove(struct platform_device *pdev) |
248 | { |
249 | struct twl4030_madc_battery *bat = platform_get_drvdata(pdev); |
250 | |
251 | power_supply_unregister(psy: bat->psy); |
252 | |
253 | iio_channel_release(chan: bat->channel_vbat); |
254 | iio_channel_release(chan: bat->channel_ichg); |
255 | iio_channel_release(chan: bat->channel_temp); |
256 | } |
257 | |
258 | static struct platform_driver twl4030_madc_battery_driver = { |
259 | .driver = { |
260 | .name = "twl4030_madc_battery" , |
261 | }, |
262 | .probe = twl4030_madc_battery_probe, |
263 | .remove_new = twl4030_madc_battery_remove, |
264 | }; |
265 | module_platform_driver(twl4030_madc_battery_driver); |
266 | |
267 | MODULE_LICENSE("GPL" ); |
268 | MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>" ); |
269 | MODULE_DESCRIPTION("twl4030_madc battery driver" ); |
270 | MODULE_ALIAS("platform:twl4030_madc_battery" ); |
271 | |