240 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Dumb driver for LiIon batteries using TWL4030 madc.4 *5 * Copyright 2013 Golden Delicious Computers6 * Lukas Märdian <lukas@goldelico.com>7 *8 * Based on dumb driver for gta01 battery9 * Copyright 2009 Openmoko, Inc10 * 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 24struct 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 32static 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 45static int madc_read(struct iio_channel *channel)46{47 int val, err;48 err = iio_read_channel_processed(channel, &val);49 if (err < 0)50 return err;51 52 return val;53}54 55static int twl4030_madc_bat_get_charging_status(struct twl4030_madc_battery *bt)56{57 return (madc_read(bt->channel_ichg) > 0) ? 1 : 0;58}59 60static int twl4030_madc_bat_get_voltage(struct twl4030_madc_battery *bt)61{62 return madc_read(bt->channel_vbat);63}64 65static int twl4030_madc_bat_get_current(struct twl4030_madc_battery *bt)66{67 return madc_read(bt->channel_ichg) * 1000;68}69 70static int twl4030_madc_bat_get_temp(struct twl4030_madc_battery *bt)71{72 return madc_read(bt->channel_temp) * 10;73}74 75static 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(bat))83 calibration = bat->pdata->charging;84 else85 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 107static 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 twl4030_madc_bat_get_voltage(bat)) > 95)117 val->intval = POWER_SUPPLY_STATUS_FULL;118 else {119 if (twl4030_madc_bat_get_charging_status(bat))120 val->intval = POWER_SUPPLY_STATUS_CHARGING;121 else122 val->intval = POWER_SUPPLY_STATUS_DISCHARGING;123 }124 break;125 case POWER_SUPPLY_PROP_VOLTAGE_NOW:126 val->intval = twl4030_madc_bat_get_voltage(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(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 twl4030_madc_bat_get_voltage(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 twl4030_madc_bat_get_voltage(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(bat);153 break;154 case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: {155 int percent = twl4030_madc_bat_voltscale(bat,156 twl4030_madc_bat_get_voltage(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 171static 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 180static 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 186static 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 192 twl4030_madc_bat = devm_kzalloc(&pdev->dev, sizeof(*twl4030_madc_bat),193 GFP_KERNEL);194 if (!twl4030_madc_bat)195 return -ENOMEM;196 197 twl4030_madc_bat->channel_temp = devm_iio_channel_get(&pdev->dev, "temp");198 if (IS_ERR(twl4030_madc_bat->channel_temp))199 return PTR_ERR(twl4030_madc_bat->channel_temp);200 201 twl4030_madc_bat->channel_ichg = devm_iio_channel_get(&pdev->dev, "ichg");202 if (IS_ERR(twl4030_madc_bat->channel_ichg))203 return PTR_ERR(twl4030_madc_bat->channel_ichg);204 205 twl4030_madc_bat->channel_vbat = devm_iio_channel_get(&pdev->dev, "vbat");206 if (IS_ERR(twl4030_madc_bat->channel_vbat))207 return PTR_ERR(twl4030_madc_bat->channel_vbat);208 209 /* sort charging and discharging calibration data */210 sort(pdata->charging, pdata->charging_size,211 sizeof(struct twl4030_madc_bat_calibration),212 twl4030_cmp, NULL);213 sort(pdata->discharging, pdata->discharging_size,214 sizeof(struct twl4030_madc_bat_calibration),215 twl4030_cmp, NULL);216 217 twl4030_madc_bat->pdata = pdata;218 psy_cfg.drv_data = twl4030_madc_bat;219 twl4030_madc_bat->psy = devm_power_supply_register(&pdev->dev,220 &twl4030_madc_bat_desc,221 &psy_cfg);222 if (IS_ERR(twl4030_madc_bat->psy))223 return PTR_ERR(twl4030_madc_bat->psy);224 225 return 0;226}227 228static struct platform_driver twl4030_madc_battery_driver = {229 .driver = {230 .name = "twl4030_madc_battery",231 },232 .probe = twl4030_madc_battery_probe,233};234module_platform_driver(twl4030_madc_battery_driver);235 236MODULE_LICENSE("GPL");237MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>");238MODULE_DESCRIPTION("twl4030_madc battery driver");239MODULE_ALIAS("platform:twl4030_madc_battery");240