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/i2c/twl4030-madc.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 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 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 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(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 void twl4030_madc_bat_ext_changed(struct power_supply *psy) 172{ 173 power_supply_changed(psy); 174} 175 176static const struct power_supply_desc twl4030_madc_bat_desc = { 177 .name = "twl4030_battery", 178 .type = POWER_SUPPLY_TYPE_BATTERY, 179 .properties = twl4030_madc_bat_props, 180 .num_properties = ARRAY_SIZE(twl4030_madc_bat_props), 181 .get_property = twl4030_madc_bat_get_property, 182 .external_power_changed = twl4030_madc_bat_ext_changed, 183 184}; 185 186static int twl4030_cmp(const void *a, const void *b) 187{ 188 return ((struct twl4030_madc_bat_calibration *)b)->voltage - 189 ((struct twl4030_madc_bat_calibration *)a)->voltage; 190} 191 192static int twl4030_madc_battery_probe(struct platform_device *pdev) 193{ 194 struct twl4030_madc_battery *twl4030_madc_bat; 195 struct twl4030_madc_bat_platform_data *pdata = pdev->dev.platform_data; 196 struct power_supply_config psy_cfg = {}; 197 int ret = 0; 198 199 twl4030_madc_bat = devm_kzalloc(&pdev->dev, sizeof(*twl4030_madc_bat), 200 GFP_KERNEL); 201 if (!twl4030_madc_bat) 202 return -ENOMEM; 203 204 twl4030_madc_bat->channel_temp = iio_channel_get(&pdev->dev, "temp"); 205 if (IS_ERR(twl4030_madc_bat->channel_temp)) { 206 ret = PTR_ERR(twl4030_madc_bat->channel_temp); 207 goto err; 208 } 209 210 twl4030_madc_bat->channel_ichg = iio_channel_get(&pdev->dev, "ichg"); 211 if (IS_ERR(twl4030_madc_bat->channel_ichg)) { 212 ret = PTR_ERR(twl4030_madc_bat->channel_ichg); 213 goto err_temp; 214 } 215 216 twl4030_madc_bat->channel_vbat = iio_channel_get(&pdev->dev, "vbat"); 217 if (IS_ERR(twl4030_madc_bat->channel_vbat)) { 218 ret = PTR_ERR(twl4030_madc_bat->channel_vbat); 219 goto err_ichg; 220 } 221 222 /* sort charging and discharging calibration data */ 223 sort(pdata->charging, pdata->charging_size, 224 sizeof(struct twl4030_madc_bat_calibration), 225 twl4030_cmp, NULL); 226 sort(pdata->discharging, pdata->discharging_size, 227 sizeof(struct twl4030_madc_bat_calibration), 228 twl4030_cmp, NULL); 229 230 twl4030_madc_bat->pdata = pdata; 231 platform_set_drvdata(pdev, twl4030_madc_bat); 232 psy_cfg.drv_data = twl4030_madc_bat; 233 twl4030_madc_bat->psy = power_supply_register(&pdev->dev, 234 &twl4030_madc_bat_desc, 235 &psy_cfg); 236 if (IS_ERR(twl4030_madc_bat->psy)) { 237 ret = PTR_ERR(twl4030_madc_bat->psy); 238 goto err_vbat; 239 } 240 241 return 0; 242 243err_vbat: 244 iio_channel_release(twl4030_madc_bat->channel_vbat); 245err_ichg: 246 iio_channel_release(twl4030_madc_bat->channel_ichg); 247err_temp: 248 iio_channel_release(twl4030_madc_bat->channel_temp); 249err: 250 return ret; 251} 252 253static int twl4030_madc_battery_remove(struct platform_device *pdev) 254{ 255 struct twl4030_madc_battery *bat = platform_get_drvdata(pdev); 256 257 power_supply_unregister(bat->psy); 258 259 iio_channel_release(bat->channel_vbat); 260 iio_channel_release(bat->channel_ichg); 261 iio_channel_release(bat->channel_temp); 262 263 return 0; 264} 265 266static struct platform_driver twl4030_madc_battery_driver = { 267 .driver = { 268 .name = "twl4030_madc_battery", 269 }, 270 .probe = twl4030_madc_battery_probe, 271 .remove = twl4030_madc_battery_remove, 272}; 273module_platform_driver(twl4030_madc_battery_driver); 274 275MODULE_LICENSE("GPL"); 276MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>"); 277MODULE_DESCRIPTION("twl4030_madc battery driver"); 278MODULE_ALIAS("platform:twl4030_madc_battery"); 279