Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * Dumb driver for LiIon batteries using TWL4030 madc.
0004  *
0005  * Copyright 2013 Golden Delicious Computers
0006  * Lukas Märdian <lukas@goldelico.com>
0007  *
0008  * Based on dumb driver for gta01 battery
0009  * Copyright 2009 Openmoko, Inc
0010  * Balaji Rao <balajirrao@openmoko.org>
0011  */
0012 
0013 #include <linux/module.h>
0014 #include <linux/param.h>
0015 #include <linux/delay.h>
0016 #include <linux/workqueue.h>
0017 #include <linux/platform_device.h>
0018 #include <linux/power_supply.h>
0019 #include <linux/slab.h>
0020 #include <linux/sort.h>
0021 #include <linux/power/twl4030_madc_battery.h>
0022 #include <linux/iio/consumer.h>
0023 
0024 struct twl4030_madc_battery {
0025     struct power_supply *psy;
0026     struct twl4030_madc_bat_platform_data *pdata;
0027     struct iio_channel *channel_temp;
0028     struct iio_channel *channel_ichg;
0029     struct iio_channel *channel_vbat;
0030 };
0031 
0032 static enum power_supply_property twl4030_madc_bat_props[] = {
0033     POWER_SUPPLY_PROP_PRESENT,
0034     POWER_SUPPLY_PROP_STATUS,
0035     POWER_SUPPLY_PROP_TECHNOLOGY,
0036     POWER_SUPPLY_PROP_VOLTAGE_NOW,
0037     POWER_SUPPLY_PROP_CURRENT_NOW,
0038     POWER_SUPPLY_PROP_CAPACITY,
0039     POWER_SUPPLY_PROP_CHARGE_FULL,
0040     POWER_SUPPLY_PROP_CHARGE_NOW,
0041     POWER_SUPPLY_PROP_TEMP,
0042     POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
0043 };
0044 
0045 static int madc_read(struct iio_channel *channel)
0046 {
0047     int val, err;
0048     err = iio_read_channel_processed(channel, &val);
0049     if (err < 0)
0050         return err;
0051 
0052     return val;
0053 }
0054 
0055 static int twl4030_madc_bat_get_charging_status(struct twl4030_madc_battery *bt)
0056 {
0057     return (madc_read(bt->channel_ichg) > 0) ? 1 : 0;
0058 }
0059 
0060 static int twl4030_madc_bat_get_voltage(struct twl4030_madc_battery *bt)
0061 {
0062     return madc_read(bt->channel_vbat);
0063 }
0064 
0065 static int twl4030_madc_bat_get_current(struct twl4030_madc_battery *bt)
0066 {
0067     return madc_read(bt->channel_ichg) * 1000;
0068 }
0069 
0070 static int twl4030_madc_bat_get_temp(struct twl4030_madc_battery *bt)
0071 {
0072     return madc_read(bt->channel_temp) * 10;
0073 }
0074 
0075 static int twl4030_madc_bat_voltscale(struct twl4030_madc_battery *bat,
0076                     int volt)
0077 {
0078     struct twl4030_madc_bat_calibration *calibration;
0079     int i, res = 0;
0080 
0081     /* choose charging curve */
0082     if (twl4030_madc_bat_get_charging_status(bat))
0083         calibration = bat->pdata->charging;
0084     else
0085         calibration = bat->pdata->discharging;
0086 
0087     if (volt > calibration[0].voltage) {
0088         res = calibration[0].level;
0089     } else {
0090         for (i = 0; calibration[i+1].voltage >= 0; i++) {
0091             if (volt <= calibration[i].voltage &&
0092                     volt >= calibration[i+1].voltage) {
0093                 /* interval found - interpolate within range */
0094                 res = calibration[i].level -
0095                     ((calibration[i].voltage - volt) *
0096                     (calibration[i].level -
0097                     calibration[i+1].level)) /
0098                     (calibration[i].voltage -
0099                     calibration[i+1].voltage);
0100                 break;
0101             }
0102         }
0103     }
0104     return res;
0105 }
0106 
0107 static int twl4030_madc_bat_get_property(struct power_supply *psy,
0108                     enum power_supply_property psp,
0109                     union power_supply_propval *val)
0110 {
0111     struct twl4030_madc_battery *bat = power_supply_get_drvdata(psy);
0112 
0113     switch (psp) {
0114     case POWER_SUPPLY_PROP_STATUS:
0115         if (twl4030_madc_bat_voltscale(bat,
0116                 twl4030_madc_bat_get_voltage(bat)) > 95)
0117             val->intval = POWER_SUPPLY_STATUS_FULL;
0118         else {
0119             if (twl4030_madc_bat_get_charging_status(bat))
0120                 val->intval = POWER_SUPPLY_STATUS_CHARGING;
0121             else
0122                 val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
0123         }
0124         break;
0125     case POWER_SUPPLY_PROP_VOLTAGE_NOW:
0126         val->intval = twl4030_madc_bat_get_voltage(bat) * 1000;
0127         break;
0128     case POWER_SUPPLY_PROP_TECHNOLOGY:
0129         val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
0130         break;
0131     case POWER_SUPPLY_PROP_CURRENT_NOW:
0132         val->intval = twl4030_madc_bat_get_current(bat);
0133         break;
0134     case POWER_SUPPLY_PROP_PRESENT:
0135         /* assume battery is always present */
0136         val->intval = 1;
0137         break;
0138     case POWER_SUPPLY_PROP_CHARGE_NOW: {
0139             int percent = twl4030_madc_bat_voltscale(bat,
0140                     twl4030_madc_bat_get_voltage(bat));
0141             val->intval = (percent * bat->pdata->capacity) / 100;
0142             break;
0143         }
0144     case POWER_SUPPLY_PROP_CAPACITY:
0145         val->intval = twl4030_madc_bat_voltscale(bat,
0146                     twl4030_madc_bat_get_voltage(bat));
0147         break;
0148     case POWER_SUPPLY_PROP_CHARGE_FULL:
0149         val->intval = bat->pdata->capacity;
0150         break;
0151     case POWER_SUPPLY_PROP_TEMP:
0152         val->intval = twl4030_madc_bat_get_temp(bat);
0153         break;
0154     case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: {
0155             int percent = twl4030_madc_bat_voltscale(bat,
0156                     twl4030_madc_bat_get_voltage(bat));
0157             /* in mAh */
0158             int chg = (percent * (bat->pdata->capacity/1000))/100;
0159 
0160             /* assume discharge with 400 mA (ca. 1.5W) */
0161             val->intval = (3600l * chg) / 400;
0162             break;
0163         }
0164     default:
0165         return -EINVAL;
0166     }
0167 
0168     return 0;
0169 }
0170 
0171 static void twl4030_madc_bat_ext_changed(struct power_supply *psy)
0172 {
0173     power_supply_changed(psy);
0174 }
0175 
0176 static const struct power_supply_desc twl4030_madc_bat_desc = {
0177     .name           = "twl4030_battery",
0178     .type           = POWER_SUPPLY_TYPE_BATTERY,
0179     .properties     = twl4030_madc_bat_props,
0180     .num_properties     = ARRAY_SIZE(twl4030_madc_bat_props),
0181     .get_property       = twl4030_madc_bat_get_property,
0182     .external_power_changed = twl4030_madc_bat_ext_changed,
0183 
0184 };
0185 
0186 static int twl4030_cmp(const void *a, const void *b)
0187 {
0188     return ((struct twl4030_madc_bat_calibration *)b)->voltage -
0189         ((struct twl4030_madc_bat_calibration *)a)->voltage;
0190 }
0191 
0192 static int twl4030_madc_battery_probe(struct platform_device *pdev)
0193 {
0194     struct twl4030_madc_battery *twl4030_madc_bat;
0195     struct twl4030_madc_bat_platform_data *pdata = pdev->dev.platform_data;
0196     struct power_supply_config psy_cfg = {};
0197     int ret = 0;
0198 
0199     twl4030_madc_bat = devm_kzalloc(&pdev->dev, sizeof(*twl4030_madc_bat),
0200                 GFP_KERNEL);
0201     if (!twl4030_madc_bat)
0202         return -ENOMEM;
0203 
0204     twl4030_madc_bat->channel_temp = iio_channel_get(&pdev->dev, "temp");
0205     if (IS_ERR(twl4030_madc_bat->channel_temp)) {
0206         ret = PTR_ERR(twl4030_madc_bat->channel_temp);
0207         goto err;
0208     }
0209 
0210     twl4030_madc_bat->channel_ichg = iio_channel_get(&pdev->dev, "ichg");
0211     if (IS_ERR(twl4030_madc_bat->channel_ichg)) {
0212         ret = PTR_ERR(twl4030_madc_bat->channel_ichg);
0213         goto err_temp;
0214     }
0215 
0216     twl4030_madc_bat->channel_vbat = iio_channel_get(&pdev->dev, "vbat");
0217     if (IS_ERR(twl4030_madc_bat->channel_vbat)) {
0218         ret = PTR_ERR(twl4030_madc_bat->channel_vbat);
0219         goto err_ichg;
0220     }
0221 
0222     /* sort charging and discharging calibration data */
0223     sort(pdata->charging, pdata->charging_size,
0224         sizeof(struct twl4030_madc_bat_calibration),
0225         twl4030_cmp, NULL);
0226     sort(pdata->discharging, pdata->discharging_size,
0227         sizeof(struct twl4030_madc_bat_calibration),
0228         twl4030_cmp, NULL);
0229 
0230     twl4030_madc_bat->pdata = pdata;
0231     platform_set_drvdata(pdev, twl4030_madc_bat);
0232     psy_cfg.drv_data = twl4030_madc_bat;
0233     twl4030_madc_bat->psy = power_supply_register(&pdev->dev,
0234                               &twl4030_madc_bat_desc,
0235                               &psy_cfg);
0236     if (IS_ERR(twl4030_madc_bat->psy)) {
0237         ret = PTR_ERR(twl4030_madc_bat->psy);
0238         goto err_vbat;
0239     }
0240 
0241     return 0;
0242 
0243 err_vbat:
0244     iio_channel_release(twl4030_madc_bat->channel_vbat);
0245 err_ichg:
0246     iio_channel_release(twl4030_madc_bat->channel_ichg);
0247 err_temp:
0248     iio_channel_release(twl4030_madc_bat->channel_temp);
0249 err:
0250     return ret;
0251 }
0252 
0253 static int twl4030_madc_battery_remove(struct platform_device *pdev)
0254 {
0255     struct twl4030_madc_battery *bat = platform_get_drvdata(pdev);
0256 
0257     power_supply_unregister(bat->psy);
0258 
0259     iio_channel_release(bat->channel_vbat);
0260     iio_channel_release(bat->channel_ichg);
0261     iio_channel_release(bat->channel_temp);
0262 
0263     return 0;
0264 }
0265 
0266 static struct platform_driver twl4030_madc_battery_driver = {
0267     .driver = {
0268         .name = "twl4030_madc_battery",
0269     },
0270     .probe  = twl4030_madc_battery_probe,
0271     .remove = twl4030_madc_battery_remove,
0272 };
0273 module_platform_driver(twl4030_madc_battery_driver);
0274 
0275 MODULE_LICENSE("GPL");
0276 MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>");
0277 MODULE_DESCRIPTION("twl4030_madc battery driver");
0278 MODULE_ALIAS("platform:twl4030_madc_battery");