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/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 24 struct 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 32 static 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 45 static 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 55 static 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 60 static int twl4030_madc_bat_get_voltage(struct twl4030_madc_battery *bt) 61 { 62 return madc_read(bt->channel_vbat); 63 } 64 65 static int twl4030_madc_bat_get_current(struct twl4030_madc_battery *bt) 66 { 67 return madc_read(bt->channel_ichg) * 1000; 68 } 69 70 static int twl4030_madc_bat_get_temp(struct twl4030_madc_battery *bt) 71 { 72 return madc_read(bt->channel_temp) * 10; 73 } 74 75 static 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 107 static 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 171 static 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 180 static 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 186 static 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 int ret = 0; 192 193 twl4030_madc_bat = devm_kzalloc(&pdev->dev, sizeof(*twl4030_madc_bat), 194 GFP_KERNEL); 195 if (!twl4030_madc_bat) 196 return -ENOMEM; 197 198 twl4030_madc_bat->channel_temp = iio_channel_get(&pdev->dev, "temp"); 199 if (IS_ERR(twl4030_madc_bat->channel_temp)) { 200 ret = PTR_ERR(twl4030_madc_bat->channel_temp); 201 goto err; 202 } 203 204 twl4030_madc_bat->channel_ichg = iio_channel_get(&pdev->dev, "ichg"); 205 if (IS_ERR(twl4030_madc_bat->channel_ichg)) { 206 ret = PTR_ERR(twl4030_madc_bat->channel_ichg); 207 goto err_temp; 208 } 209 210 twl4030_madc_bat->channel_vbat = iio_channel_get(&pdev->dev, "vbat"); 211 if (IS_ERR(twl4030_madc_bat->channel_vbat)) { 212 ret = PTR_ERR(twl4030_madc_bat->channel_vbat); 213 goto err_ichg; 214 } 215 216 /* sort charging and discharging calibration data */ 217 sort(pdata->charging, pdata->charging_size, 218 sizeof(struct twl4030_madc_bat_calibration), 219 twl4030_cmp, NULL); 220 sort(pdata->discharging, pdata->discharging_size, 221 sizeof(struct twl4030_madc_bat_calibration), 222 twl4030_cmp, NULL); 223 224 twl4030_madc_bat->pdata = pdata; 225 platform_set_drvdata(pdev, twl4030_madc_bat); 226 psy_cfg.drv_data = twl4030_madc_bat; 227 twl4030_madc_bat->psy = power_supply_register(&pdev->dev, 228 &twl4030_madc_bat_desc, 229 &psy_cfg); 230 if (IS_ERR(twl4030_madc_bat->psy)) { 231 ret = PTR_ERR(twl4030_madc_bat->psy); 232 goto err_vbat; 233 } 234 235 return 0; 236 237 err_vbat: 238 iio_channel_release(twl4030_madc_bat->channel_vbat); 239 err_ichg: 240 iio_channel_release(twl4030_madc_bat->channel_ichg); 241 err_temp: 242 iio_channel_release(twl4030_madc_bat->channel_temp); 243 err: 244 return ret; 245 } 246 247 static void twl4030_madc_battery_remove(struct platform_device *pdev) 248 { 249 struct twl4030_madc_battery *bat = platform_get_drvdata(pdev); 250 251 power_supply_unregister(bat->psy); 252 253 iio_channel_release(bat->channel_vbat); 254 iio_channel_release(bat->channel_ichg); 255 iio_channel_release(bat->channel_temp); 256 } 257 258 static struct platform_driver twl4030_madc_battery_driver = { 259 .driver = { 260 .name = "twl4030_madc_battery", 261 }, 262 .probe = twl4030_madc_battery_probe, 263 .remove_new = twl4030_madc_battery_remove, 264 }; 265 module_platform_driver(twl4030_madc_battery_driver); 266 267 MODULE_LICENSE("GPL"); 268 MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>"); 269 MODULE_DESCRIPTION("twl4030_madc battery driver"); 270 MODULE_ALIAS("platform:twl4030_madc_battery"); 271