Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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/delay.h>
15#include <linux/platform_device.h>
16#include <linux/power_supply.h>
17#include <linux/slab.h>
18#include <linux/sort.h>
19#include <linux/power/twl4030_madc_battery.h>
20#include <linux/iio/consumer.h>
21
22struct twl4030_madc_battery {
23 struct power_supply *psy;
24 struct twl4030_madc_bat_platform_data *pdata;
25 struct iio_channel *channel_temp;
26 struct iio_channel *channel_ichg;
27 struct iio_channel *channel_vbat;
28};
29
30static enum power_supply_property twl4030_madc_bat_props[] = {
31 POWER_SUPPLY_PROP_PRESENT,
32 POWER_SUPPLY_PROP_STATUS,
33 POWER_SUPPLY_PROP_TECHNOLOGY,
34 POWER_SUPPLY_PROP_VOLTAGE_NOW,
35 POWER_SUPPLY_PROP_CURRENT_NOW,
36 POWER_SUPPLY_PROP_CAPACITY,
37 POWER_SUPPLY_PROP_CHARGE_FULL,
38 POWER_SUPPLY_PROP_CHARGE_NOW,
39 POWER_SUPPLY_PROP_TEMP,
40 POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
41};
42
43static int madc_read(struct iio_channel *channel)
44{
45 int val, err;
46 err = iio_read_channel_processed(channel, &val);
47 if (err < 0)
48 return err;
49
50 return val;
51}
52
53static int twl4030_madc_bat_get_charging_status(struct twl4030_madc_battery *bt)
54{
55 return (madc_read(bt->channel_ichg) > 0) ? 1 : 0;
56}
57
58static int twl4030_madc_bat_get_voltage(struct twl4030_madc_battery *bt)
59{
60 return madc_read(bt->channel_vbat);
61}
62
63static int twl4030_madc_bat_get_current(struct twl4030_madc_battery *bt)
64{
65 return madc_read(bt->channel_ichg) * 1000;
66}
67
68static int twl4030_madc_bat_get_temp(struct twl4030_madc_battery *bt)
69{
70 return madc_read(bt->channel_temp) * 10;
71}
72
73static int twl4030_madc_bat_voltscale(struct twl4030_madc_battery *bat,
74 int volt)
75{
76 struct twl4030_madc_bat_calibration *calibration;
77 int i, res = 0;
78
79 /* choose charging curve */
80 if (twl4030_madc_bat_get_charging_status(bat))
81 calibration = bat->pdata->charging;
82 else
83 calibration = bat->pdata->discharging;
84
85 if (volt > calibration[0].voltage) {
86 res = calibration[0].level;
87 } else {
88 for (i = 0; calibration[i+1].voltage >= 0; i++) {
89 if (volt <= calibration[i].voltage &&
90 volt >= calibration[i+1].voltage) {
91 /* interval found - interpolate within range */
92 res = calibration[i].level -
93 ((calibration[i].voltage - volt) *
94 (calibration[i].level -
95 calibration[i+1].level)) /
96 (calibration[i].voltage -
97 calibration[i+1].voltage);
98 break;
99 }
100 }
101 }
102 return res;
103}
104
105static int twl4030_madc_bat_get_property(struct power_supply *psy,
106 enum power_supply_property psp,
107 union power_supply_propval *val)
108{
109 struct twl4030_madc_battery *bat = power_supply_get_drvdata(psy);
110
111 switch (psp) {
112 case POWER_SUPPLY_PROP_STATUS:
113 if (twl4030_madc_bat_voltscale(bat,
114 twl4030_madc_bat_get_voltage(bat)) > 95)
115 val->intval = POWER_SUPPLY_STATUS_FULL;
116 else {
117 if (twl4030_madc_bat_get_charging_status(bat))
118 val->intval = POWER_SUPPLY_STATUS_CHARGING;
119 else
120 val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
121 }
122 break;
123 case POWER_SUPPLY_PROP_VOLTAGE_NOW:
124 val->intval = twl4030_madc_bat_get_voltage(bat) * 1000;
125 break;
126 case POWER_SUPPLY_PROP_TECHNOLOGY:
127 val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
128 break;
129 case POWER_SUPPLY_PROP_CURRENT_NOW:
130 val->intval = twl4030_madc_bat_get_current(bat);
131 break;
132 case POWER_SUPPLY_PROP_PRESENT:
133 /* assume battery is always present */
134 val->intval = 1;
135 break;
136 case POWER_SUPPLY_PROP_CHARGE_NOW: {
137 int percent = twl4030_madc_bat_voltscale(bat,
138 twl4030_madc_bat_get_voltage(bat));
139 val->intval = (percent * bat->pdata->capacity) / 100;
140 break;
141 }
142 case POWER_SUPPLY_PROP_CAPACITY:
143 val->intval = twl4030_madc_bat_voltscale(bat,
144 twl4030_madc_bat_get_voltage(bat));
145 break;
146 case POWER_SUPPLY_PROP_CHARGE_FULL:
147 val->intval = bat->pdata->capacity;
148 break;
149 case POWER_SUPPLY_PROP_TEMP:
150 val->intval = twl4030_madc_bat_get_temp(bat);
151 break;
152 case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: {
153 int percent = twl4030_madc_bat_voltscale(bat,
154 twl4030_madc_bat_get_voltage(bat));
155 /* in mAh */
156 int chg = (percent * (bat->pdata->capacity/1000))/100;
157
158 /* assume discharge with 400 mA (ca. 1.5W) */
159 val->intval = (3600l * chg) / 400;
160 break;
161 }
162 default:
163 return -EINVAL;
164 }
165
166 return 0;
167}
168
169static const struct power_supply_desc twl4030_madc_bat_desc = {
170 .name = "twl4030_battery",
171 .type = POWER_SUPPLY_TYPE_BATTERY,
172 .properties = twl4030_madc_bat_props,
173 .num_properties = ARRAY_SIZE(twl4030_madc_bat_props),
174 .get_property = twl4030_madc_bat_get_property,
175 .external_power_changed = power_supply_changed,
176};
177
178static int twl4030_cmp(const void *a, const void *b)
179{
180 return ((struct twl4030_madc_bat_calibration *)b)->voltage -
181 ((struct twl4030_madc_bat_calibration *)a)->voltage;
182}
183
184static int twl4030_madc_battery_probe(struct platform_device *pdev)
185{
186 struct twl4030_madc_battery *twl4030_madc_bat;
187 struct twl4030_madc_bat_platform_data *pdata = pdev->dev.platform_data;
188 struct power_supply_config psy_cfg = {};
189
190 twl4030_madc_bat = devm_kzalloc(&pdev->dev, sizeof(*twl4030_madc_bat),
191 GFP_KERNEL);
192 if (!twl4030_madc_bat)
193 return -ENOMEM;
194
195 twl4030_madc_bat->channel_temp = devm_iio_channel_get(&pdev->dev, "temp");
196 if (IS_ERR(twl4030_madc_bat->channel_temp))
197 return PTR_ERR(twl4030_madc_bat->channel_temp);
198
199 twl4030_madc_bat->channel_ichg = devm_iio_channel_get(&pdev->dev, "ichg");
200 if (IS_ERR(twl4030_madc_bat->channel_ichg))
201 return PTR_ERR(twl4030_madc_bat->channel_ichg);
202
203 twl4030_madc_bat->channel_vbat = devm_iio_channel_get(&pdev->dev, "vbat");
204 if (IS_ERR(twl4030_madc_bat->channel_vbat))
205 return PTR_ERR(twl4030_madc_bat->channel_vbat);
206
207 /* sort charging and discharging calibration data */
208 sort(pdata->charging, pdata->charging_size,
209 sizeof(struct twl4030_madc_bat_calibration),
210 twl4030_cmp, NULL);
211 sort(pdata->discharging, pdata->discharging_size,
212 sizeof(struct twl4030_madc_bat_calibration),
213 twl4030_cmp, NULL);
214
215 twl4030_madc_bat->pdata = pdata;
216 psy_cfg.drv_data = twl4030_madc_bat;
217 twl4030_madc_bat->psy = devm_power_supply_register(&pdev->dev,
218 &twl4030_madc_bat_desc,
219 &psy_cfg);
220 if (IS_ERR(twl4030_madc_bat->psy))
221 return PTR_ERR(twl4030_madc_bat->psy);
222
223 return 0;
224}
225
226static struct platform_driver twl4030_madc_battery_driver = {
227 .driver = {
228 .name = "twl4030_madc_battery",
229 },
230 .probe = twl4030_madc_battery_probe,
231};
232module_platform_driver(twl4030_madc_battery_driver);
233
234MODULE_LICENSE("GPL");
235MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>");
236MODULE_DESCRIPTION("twl4030_madc battery driver");
237MODULE_ALIAS("platform:twl4030_madc_battery");