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 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 228 static struct platform_driver twl4030_madc_battery_driver = { 229 .driver = { 230 .name = "twl4030_madc_battery", 231 }, 232 .probe = twl4030_madc_battery_probe, 233 }; 234 module_platform_driver(twl4030_madc_battery_driver); 235 236 MODULE_LICENSE("GPL"); 237 MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>"); 238 MODULE_DESCRIPTION("twl4030_madc battery driver"); 239 MODULE_ALIAS("platform:twl4030_madc_battery"); 240