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