109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
28c0984e5SSebastian Reichel /*
38c0984e5SSebastian Reichel * Dumb driver for LiIon batteries using TWL4030 madc.
48c0984e5SSebastian Reichel *
58c0984e5SSebastian Reichel * Copyright 2013 Golden Delicious Computers
68c0984e5SSebastian Reichel * Lukas Märdian <lukas@goldelico.com>
78c0984e5SSebastian Reichel *
88c0984e5SSebastian Reichel * Based on dumb driver for gta01 battery
98c0984e5SSebastian Reichel * Copyright 2009 Openmoko, Inc
108c0984e5SSebastian Reichel * Balaji Rao <balajirrao@openmoko.org>
118c0984e5SSebastian Reichel */
128c0984e5SSebastian Reichel
138c0984e5SSebastian Reichel #include <linux/module.h>
148c0984e5SSebastian Reichel #include <linux/param.h>
158c0984e5SSebastian Reichel #include <linux/delay.h>
168c0984e5SSebastian Reichel #include <linux/workqueue.h>
178c0984e5SSebastian Reichel #include <linux/platform_device.h>
188c0984e5SSebastian Reichel #include <linux/power_supply.h>
198c0984e5SSebastian Reichel #include <linux/slab.h>
208c0984e5SSebastian Reichel #include <linux/sort.h>
218c0984e5SSebastian Reichel #include <linux/power/twl4030_madc_battery.h>
228c0984e5SSebastian Reichel #include <linux/iio/consumer.h>
238c0984e5SSebastian Reichel
248c0984e5SSebastian Reichel struct twl4030_madc_battery {
258c0984e5SSebastian Reichel struct power_supply *psy;
268c0984e5SSebastian Reichel struct twl4030_madc_bat_platform_data *pdata;
278c0984e5SSebastian Reichel struct iio_channel *channel_temp;
288c0984e5SSebastian Reichel struct iio_channel *channel_ichg;
298c0984e5SSebastian Reichel struct iio_channel *channel_vbat;
308c0984e5SSebastian Reichel };
318c0984e5SSebastian Reichel
328c0984e5SSebastian Reichel static enum power_supply_property twl4030_madc_bat_props[] = {
338c0984e5SSebastian Reichel POWER_SUPPLY_PROP_PRESENT,
348c0984e5SSebastian Reichel POWER_SUPPLY_PROP_STATUS,
358c0984e5SSebastian Reichel POWER_SUPPLY_PROP_TECHNOLOGY,
368c0984e5SSebastian Reichel POWER_SUPPLY_PROP_VOLTAGE_NOW,
378c0984e5SSebastian Reichel POWER_SUPPLY_PROP_CURRENT_NOW,
388c0984e5SSebastian Reichel POWER_SUPPLY_PROP_CAPACITY,
398c0984e5SSebastian Reichel POWER_SUPPLY_PROP_CHARGE_FULL,
408c0984e5SSebastian Reichel POWER_SUPPLY_PROP_CHARGE_NOW,
418c0984e5SSebastian Reichel POWER_SUPPLY_PROP_TEMP,
428c0984e5SSebastian Reichel POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
438c0984e5SSebastian Reichel };
448c0984e5SSebastian Reichel
madc_read(struct iio_channel * channel)458c0984e5SSebastian Reichel static int madc_read(struct iio_channel *channel)
468c0984e5SSebastian Reichel {
478c0984e5SSebastian Reichel int val, err;
488c0984e5SSebastian Reichel err = iio_read_channel_processed(channel, &val);
498c0984e5SSebastian Reichel if (err < 0)
508c0984e5SSebastian Reichel return err;
518c0984e5SSebastian Reichel
528c0984e5SSebastian Reichel return val;
538c0984e5SSebastian Reichel }
548c0984e5SSebastian Reichel
twl4030_madc_bat_get_charging_status(struct twl4030_madc_battery * bt)558c0984e5SSebastian Reichel static int twl4030_madc_bat_get_charging_status(struct twl4030_madc_battery *bt)
568c0984e5SSebastian Reichel {
578c0984e5SSebastian Reichel return (madc_read(bt->channel_ichg) > 0) ? 1 : 0;
588c0984e5SSebastian Reichel }
598c0984e5SSebastian Reichel
twl4030_madc_bat_get_voltage(struct twl4030_madc_battery * bt)608c0984e5SSebastian Reichel static int twl4030_madc_bat_get_voltage(struct twl4030_madc_battery *bt)
618c0984e5SSebastian Reichel {
628c0984e5SSebastian Reichel return madc_read(bt->channel_vbat);
638c0984e5SSebastian Reichel }
648c0984e5SSebastian Reichel
twl4030_madc_bat_get_current(struct twl4030_madc_battery * bt)658c0984e5SSebastian Reichel static int twl4030_madc_bat_get_current(struct twl4030_madc_battery *bt)
668c0984e5SSebastian Reichel {
678c0984e5SSebastian Reichel return madc_read(bt->channel_ichg) * 1000;
688c0984e5SSebastian Reichel }
698c0984e5SSebastian Reichel
twl4030_madc_bat_get_temp(struct twl4030_madc_battery * bt)708c0984e5SSebastian Reichel static int twl4030_madc_bat_get_temp(struct twl4030_madc_battery *bt)
718c0984e5SSebastian Reichel {
728c0984e5SSebastian Reichel return madc_read(bt->channel_temp) * 10;
738c0984e5SSebastian Reichel }
748c0984e5SSebastian Reichel
twl4030_madc_bat_voltscale(struct twl4030_madc_battery * bat,int volt)758c0984e5SSebastian Reichel static int twl4030_madc_bat_voltscale(struct twl4030_madc_battery *bat,
768c0984e5SSebastian Reichel int volt)
778c0984e5SSebastian Reichel {
788c0984e5SSebastian Reichel struct twl4030_madc_bat_calibration *calibration;
798c0984e5SSebastian Reichel int i, res = 0;
808c0984e5SSebastian Reichel
818c0984e5SSebastian Reichel /* choose charging curve */
828c0984e5SSebastian Reichel if (twl4030_madc_bat_get_charging_status(bat))
838c0984e5SSebastian Reichel calibration = bat->pdata->charging;
848c0984e5SSebastian Reichel else
858c0984e5SSebastian Reichel calibration = bat->pdata->discharging;
868c0984e5SSebastian Reichel
878c0984e5SSebastian Reichel if (volt > calibration[0].voltage) {
888c0984e5SSebastian Reichel res = calibration[0].level;
898c0984e5SSebastian Reichel } else {
908c0984e5SSebastian Reichel for (i = 0; calibration[i+1].voltage >= 0; i++) {
918c0984e5SSebastian Reichel if (volt <= calibration[i].voltage &&
928c0984e5SSebastian Reichel volt >= calibration[i+1].voltage) {
938c0984e5SSebastian Reichel /* interval found - interpolate within range */
948c0984e5SSebastian Reichel res = calibration[i].level -
958c0984e5SSebastian Reichel ((calibration[i].voltage - volt) *
968c0984e5SSebastian Reichel (calibration[i].level -
978c0984e5SSebastian Reichel calibration[i+1].level)) /
988c0984e5SSebastian Reichel (calibration[i].voltage -
998c0984e5SSebastian Reichel calibration[i+1].voltage);
1008c0984e5SSebastian Reichel break;
1018c0984e5SSebastian Reichel }
1028c0984e5SSebastian Reichel }
1038c0984e5SSebastian Reichel }
1048c0984e5SSebastian Reichel return res;
1058c0984e5SSebastian Reichel }
1068c0984e5SSebastian Reichel
twl4030_madc_bat_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)1078c0984e5SSebastian Reichel static int twl4030_madc_bat_get_property(struct power_supply *psy,
1088c0984e5SSebastian Reichel enum power_supply_property psp,
1098c0984e5SSebastian Reichel union power_supply_propval *val)
1108c0984e5SSebastian Reichel {
1118c0984e5SSebastian Reichel struct twl4030_madc_battery *bat = power_supply_get_drvdata(psy);
1128c0984e5SSebastian Reichel
1138c0984e5SSebastian Reichel switch (psp) {
1148c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_STATUS:
1158c0984e5SSebastian Reichel if (twl4030_madc_bat_voltscale(bat,
1168c0984e5SSebastian Reichel twl4030_madc_bat_get_voltage(bat)) > 95)
1178c0984e5SSebastian Reichel val->intval = POWER_SUPPLY_STATUS_FULL;
1188c0984e5SSebastian Reichel else {
1198c0984e5SSebastian Reichel if (twl4030_madc_bat_get_charging_status(bat))
1208c0984e5SSebastian Reichel val->intval = POWER_SUPPLY_STATUS_CHARGING;
1218c0984e5SSebastian Reichel else
1228c0984e5SSebastian Reichel val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
1238c0984e5SSebastian Reichel }
1248c0984e5SSebastian Reichel break;
1258c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_VOLTAGE_NOW:
1268c0984e5SSebastian Reichel val->intval = twl4030_madc_bat_get_voltage(bat) * 1000;
1278c0984e5SSebastian Reichel break;
1288c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_TECHNOLOGY:
1298c0984e5SSebastian Reichel val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
1308c0984e5SSebastian Reichel break;
1318c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_CURRENT_NOW:
1328c0984e5SSebastian Reichel val->intval = twl4030_madc_bat_get_current(bat);
1338c0984e5SSebastian Reichel break;
1348c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_PRESENT:
1358c0984e5SSebastian Reichel /* assume battery is always present */
1368c0984e5SSebastian Reichel val->intval = 1;
1378c0984e5SSebastian Reichel break;
1388c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_CHARGE_NOW: {
1398c0984e5SSebastian Reichel int percent = twl4030_madc_bat_voltscale(bat,
1408c0984e5SSebastian Reichel twl4030_madc_bat_get_voltage(bat));
1418c0984e5SSebastian Reichel val->intval = (percent * bat->pdata->capacity) / 100;
1428c0984e5SSebastian Reichel break;
1438c0984e5SSebastian Reichel }
1448c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_CAPACITY:
1458c0984e5SSebastian Reichel val->intval = twl4030_madc_bat_voltscale(bat,
1468c0984e5SSebastian Reichel twl4030_madc_bat_get_voltage(bat));
1478c0984e5SSebastian Reichel break;
1488c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_CHARGE_FULL:
1498c0984e5SSebastian Reichel val->intval = bat->pdata->capacity;
1508c0984e5SSebastian Reichel break;
1518c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_TEMP:
1528c0984e5SSebastian Reichel val->intval = twl4030_madc_bat_get_temp(bat);
1538c0984e5SSebastian Reichel break;
1548c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: {
1558c0984e5SSebastian Reichel int percent = twl4030_madc_bat_voltscale(bat,
1568c0984e5SSebastian Reichel twl4030_madc_bat_get_voltage(bat));
1578c0984e5SSebastian Reichel /* in mAh */
1588c0984e5SSebastian Reichel int chg = (percent * (bat->pdata->capacity/1000))/100;
1598c0984e5SSebastian Reichel
1608c0984e5SSebastian Reichel /* assume discharge with 400 mA (ca. 1.5W) */
1618c0984e5SSebastian Reichel val->intval = (3600l * chg) / 400;
1628c0984e5SSebastian Reichel break;
1638c0984e5SSebastian Reichel }
1648c0984e5SSebastian Reichel default:
1658c0984e5SSebastian Reichel return -EINVAL;
1668c0984e5SSebastian Reichel }
1678c0984e5SSebastian Reichel
1688c0984e5SSebastian Reichel return 0;
1698c0984e5SSebastian Reichel }
1708c0984e5SSebastian Reichel
1718c0984e5SSebastian Reichel static const struct power_supply_desc twl4030_madc_bat_desc = {
1728c0984e5SSebastian Reichel .name = "twl4030_battery",
1738c0984e5SSebastian Reichel .type = POWER_SUPPLY_TYPE_BATTERY,
1748c0984e5SSebastian Reichel .properties = twl4030_madc_bat_props,
1758c0984e5SSebastian Reichel .num_properties = ARRAY_SIZE(twl4030_madc_bat_props),
1768c0984e5SSebastian Reichel .get_property = twl4030_madc_bat_get_property,
177*32fe18d0SHans de Goede .external_power_changed = power_supply_changed,
1788c0984e5SSebastian Reichel };
1798c0984e5SSebastian Reichel
twl4030_cmp(const void * a,const void * b)1808c0984e5SSebastian Reichel static int twl4030_cmp(const void *a, const void *b)
1818c0984e5SSebastian Reichel {
1828c0984e5SSebastian Reichel return ((struct twl4030_madc_bat_calibration *)b)->voltage -
1838c0984e5SSebastian Reichel ((struct twl4030_madc_bat_calibration *)a)->voltage;
1848c0984e5SSebastian Reichel }
1858c0984e5SSebastian Reichel
twl4030_madc_battery_probe(struct platform_device * pdev)1868c0984e5SSebastian Reichel static int twl4030_madc_battery_probe(struct platform_device *pdev)
1878c0984e5SSebastian Reichel {
1888c0984e5SSebastian Reichel struct twl4030_madc_battery *twl4030_madc_bat;
1898c0984e5SSebastian Reichel struct twl4030_madc_bat_platform_data *pdata = pdev->dev.platform_data;
1908c0984e5SSebastian Reichel struct power_supply_config psy_cfg = {};
1918c0984e5SSebastian Reichel int ret = 0;
1928c0984e5SSebastian Reichel
1938c0984e5SSebastian Reichel twl4030_madc_bat = devm_kzalloc(&pdev->dev, sizeof(*twl4030_madc_bat),
1948c0984e5SSebastian Reichel GFP_KERNEL);
1958c0984e5SSebastian Reichel if (!twl4030_madc_bat)
1968c0984e5SSebastian Reichel return -ENOMEM;
1978c0984e5SSebastian Reichel
1988c0984e5SSebastian Reichel twl4030_madc_bat->channel_temp = iio_channel_get(&pdev->dev, "temp");
1998c0984e5SSebastian Reichel if (IS_ERR(twl4030_madc_bat->channel_temp)) {
2008c0984e5SSebastian Reichel ret = PTR_ERR(twl4030_madc_bat->channel_temp);
2018c0984e5SSebastian Reichel goto err;
2028c0984e5SSebastian Reichel }
2038c0984e5SSebastian Reichel
2048c0984e5SSebastian Reichel twl4030_madc_bat->channel_ichg = iio_channel_get(&pdev->dev, "ichg");
2058c0984e5SSebastian Reichel if (IS_ERR(twl4030_madc_bat->channel_ichg)) {
2068c0984e5SSebastian Reichel ret = PTR_ERR(twl4030_madc_bat->channel_ichg);
2078c0984e5SSebastian Reichel goto err_temp;
2088c0984e5SSebastian Reichel }
2098c0984e5SSebastian Reichel
2108c0984e5SSebastian Reichel twl4030_madc_bat->channel_vbat = iio_channel_get(&pdev->dev, "vbat");
2118c0984e5SSebastian Reichel if (IS_ERR(twl4030_madc_bat->channel_vbat)) {
2128c0984e5SSebastian Reichel ret = PTR_ERR(twl4030_madc_bat->channel_vbat);
2138c0984e5SSebastian Reichel goto err_ichg;
2148c0984e5SSebastian Reichel }
2158c0984e5SSebastian Reichel
2168c0984e5SSebastian Reichel /* sort charging and discharging calibration data */
2178c0984e5SSebastian Reichel sort(pdata->charging, pdata->charging_size,
2188c0984e5SSebastian Reichel sizeof(struct twl4030_madc_bat_calibration),
2198c0984e5SSebastian Reichel twl4030_cmp, NULL);
2208c0984e5SSebastian Reichel sort(pdata->discharging, pdata->discharging_size,
2218c0984e5SSebastian Reichel sizeof(struct twl4030_madc_bat_calibration),
2228c0984e5SSebastian Reichel twl4030_cmp, NULL);
2238c0984e5SSebastian Reichel
2248c0984e5SSebastian Reichel twl4030_madc_bat->pdata = pdata;
2258c0984e5SSebastian Reichel platform_set_drvdata(pdev, twl4030_madc_bat);
2268c0984e5SSebastian Reichel psy_cfg.drv_data = twl4030_madc_bat;
2278c0984e5SSebastian Reichel twl4030_madc_bat->psy = power_supply_register(&pdev->dev,
2288c0984e5SSebastian Reichel &twl4030_madc_bat_desc,
2298c0984e5SSebastian Reichel &psy_cfg);
2308c0984e5SSebastian Reichel if (IS_ERR(twl4030_madc_bat->psy)) {
2318c0984e5SSebastian Reichel ret = PTR_ERR(twl4030_madc_bat->psy);
2328c0984e5SSebastian Reichel goto err_vbat;
2338c0984e5SSebastian Reichel }
2348c0984e5SSebastian Reichel
2358c0984e5SSebastian Reichel return 0;
2368c0984e5SSebastian Reichel
2378c0984e5SSebastian Reichel err_vbat:
2388c0984e5SSebastian Reichel iio_channel_release(twl4030_madc_bat->channel_vbat);
2398c0984e5SSebastian Reichel err_ichg:
2408c0984e5SSebastian Reichel iio_channel_release(twl4030_madc_bat->channel_ichg);
2418c0984e5SSebastian Reichel err_temp:
2428c0984e5SSebastian Reichel iio_channel_release(twl4030_madc_bat->channel_temp);
2438c0984e5SSebastian Reichel err:
2448c0984e5SSebastian Reichel return ret;
2458c0984e5SSebastian Reichel }
2468c0984e5SSebastian Reichel
twl4030_madc_battery_remove(struct platform_device * pdev)2478c0984e5SSebastian Reichel static int twl4030_madc_battery_remove(struct platform_device *pdev)
2488c0984e5SSebastian Reichel {
2498c0984e5SSebastian Reichel struct twl4030_madc_battery *bat = platform_get_drvdata(pdev);
2508c0984e5SSebastian Reichel
2518c0984e5SSebastian Reichel power_supply_unregister(bat->psy);
2528c0984e5SSebastian Reichel
2538c0984e5SSebastian Reichel iio_channel_release(bat->channel_vbat);
2548c0984e5SSebastian Reichel iio_channel_release(bat->channel_ichg);
2558c0984e5SSebastian Reichel iio_channel_release(bat->channel_temp);
2568c0984e5SSebastian Reichel
2578c0984e5SSebastian Reichel return 0;
2588c0984e5SSebastian Reichel }
2598c0984e5SSebastian Reichel
2608c0984e5SSebastian Reichel static struct platform_driver twl4030_madc_battery_driver = {
2618c0984e5SSebastian Reichel .driver = {
2628c0984e5SSebastian Reichel .name = "twl4030_madc_battery",
2638c0984e5SSebastian Reichel },
2648c0984e5SSebastian Reichel .probe = twl4030_madc_battery_probe,
2658c0984e5SSebastian Reichel .remove = twl4030_madc_battery_remove,
2668c0984e5SSebastian Reichel };
2678c0984e5SSebastian Reichel module_platform_driver(twl4030_madc_battery_driver);
2688c0984e5SSebastian Reichel
2698c0984e5SSebastian Reichel MODULE_LICENSE("GPL");
2708c0984e5SSebastian Reichel MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>");
2718c0984e5SSebastian Reichel MODULE_DESCRIPTION("twl4030_madc battery driver");
2728c0984e5SSebastian Reichel MODULE_ALIAS("platform:twl4030_madc_battery");
273