wm97xx_battery.c (6711B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Battery measurement code for WM97xx 4 * 5 * based on tosa_battery.c 6 * 7 * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com> 8 */ 9 10#include <linux/init.h> 11#include <linux/kernel.h> 12#include <linux/module.h> 13#include <linux/platform_device.h> 14#include <linux/power_supply.h> 15#include <linux/wm97xx.h> 16#include <linux/spinlock.h> 17#include <linux/interrupt.h> 18#include <linux/gpio/consumer.h> 19#include <linux/irq.h> 20#include <linux/slab.h> 21 22static struct work_struct bat_work; 23static struct gpio_desc *charge_gpiod; 24static DEFINE_MUTEX(work_lock); 25static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN; 26static enum power_supply_property *prop; 27 28static unsigned long wm97xx_read_bat(struct power_supply *bat_ps) 29{ 30 struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps); 31 32 return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev.parent), 33 pdata->batt_aux) * pdata->batt_mult / 34 pdata->batt_div; 35} 36 37static unsigned long wm97xx_read_temp(struct power_supply *bat_ps) 38{ 39 struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps); 40 41 return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev.parent), 42 pdata->temp_aux) * pdata->temp_mult / 43 pdata->temp_div; 44} 45 46static int wm97xx_bat_get_property(struct power_supply *bat_ps, 47 enum power_supply_property psp, 48 union power_supply_propval *val) 49{ 50 struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps); 51 52 switch (psp) { 53 case POWER_SUPPLY_PROP_STATUS: 54 val->intval = bat_status; 55 break; 56 case POWER_SUPPLY_PROP_TECHNOLOGY: 57 val->intval = pdata->batt_tech; 58 break; 59 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 60 if (pdata->batt_aux >= 0) 61 val->intval = wm97xx_read_bat(bat_ps); 62 else 63 return -EINVAL; 64 break; 65 case POWER_SUPPLY_PROP_TEMP: 66 if (pdata->temp_aux >= 0) 67 val->intval = wm97xx_read_temp(bat_ps); 68 else 69 return -EINVAL; 70 break; 71 case POWER_SUPPLY_PROP_VOLTAGE_MAX: 72 if (pdata->max_voltage >= 0) 73 val->intval = pdata->max_voltage; 74 else 75 return -EINVAL; 76 break; 77 case POWER_SUPPLY_PROP_VOLTAGE_MIN: 78 if (pdata->min_voltage >= 0) 79 val->intval = pdata->min_voltage; 80 else 81 return -EINVAL; 82 break; 83 case POWER_SUPPLY_PROP_PRESENT: 84 val->intval = 1; 85 break; 86 default: 87 return -EINVAL; 88 } 89 return 0; 90} 91 92static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps) 93{ 94 schedule_work(&bat_work); 95} 96 97static void wm97xx_bat_update(struct power_supply *bat_ps) 98{ 99 int old_status = bat_status; 100 101 mutex_lock(&work_lock); 102 103 bat_status = (charge_gpiod) ? 104 (gpiod_get_value(charge_gpiod) ? 105 POWER_SUPPLY_STATUS_DISCHARGING : 106 POWER_SUPPLY_STATUS_CHARGING) : 107 POWER_SUPPLY_STATUS_UNKNOWN; 108 109 if (old_status != bat_status) { 110 pr_debug("%s: %i -> %i\n", bat_ps->desc->name, old_status, 111 bat_status); 112 power_supply_changed(bat_ps); 113 } 114 115 mutex_unlock(&work_lock); 116} 117 118static struct power_supply *bat_psy; 119static struct power_supply_desc bat_psy_desc = { 120 .type = POWER_SUPPLY_TYPE_BATTERY, 121 .get_property = wm97xx_bat_get_property, 122 .external_power_changed = wm97xx_bat_external_power_changed, 123 .use_for_apm = 1, 124}; 125 126static void wm97xx_bat_work(struct work_struct *work) 127{ 128 wm97xx_bat_update(bat_psy); 129} 130 131static irqreturn_t wm97xx_chrg_irq(int irq, void *data) 132{ 133 schedule_work(&bat_work); 134 return IRQ_HANDLED; 135} 136 137#ifdef CONFIG_PM 138static int wm97xx_bat_suspend(struct device *dev) 139{ 140 flush_work(&bat_work); 141 return 0; 142} 143 144static int wm97xx_bat_resume(struct device *dev) 145{ 146 schedule_work(&bat_work); 147 return 0; 148} 149 150static const struct dev_pm_ops wm97xx_bat_pm_ops = { 151 .suspend = wm97xx_bat_suspend, 152 .resume = wm97xx_bat_resume, 153}; 154#endif 155 156static int wm97xx_bat_probe(struct platform_device *dev) 157{ 158 int ret = 0; 159 int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ 160 int i = 0; 161 struct wm97xx_batt_pdata *pdata = dev->dev.platform_data; 162 struct power_supply_config cfg = {}; 163 164 if (!pdata) { 165 dev_err(&dev->dev, "No platform data supplied\n"); 166 return -EINVAL; 167 } 168 169 cfg.drv_data = pdata; 170 171 if (dev->id != -1) 172 return -EINVAL; 173 174 charge_gpiod = devm_gpiod_get_optional(&dev->dev, NULL, GPIOD_IN); 175 if (IS_ERR(charge_gpiod)) 176 return dev_err_probe(&dev->dev, 177 PTR_ERR(charge_gpiod), 178 "failed to get charge GPIO\n"); 179 if (charge_gpiod) { 180 gpiod_set_consumer_name(charge_gpiod, "BATT CHRG"); 181 ret = request_irq(gpiod_to_irq(charge_gpiod), 182 wm97xx_chrg_irq, 0, 183 "AC Detect", dev); 184 if (ret) 185 return dev_err_probe(&dev->dev, ret, 186 "failed to request GPIO irq\n"); 187 props++; /* POWER_SUPPLY_PROP_STATUS */ 188 } 189 190 if (pdata->batt_tech >= 0) 191 props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ 192 if (pdata->temp_aux >= 0) 193 props++; /* POWER_SUPPLY_PROP_TEMP */ 194 if (pdata->batt_aux >= 0) 195 props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ 196 if (pdata->max_voltage >= 0) 197 props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ 198 if (pdata->min_voltage >= 0) 199 props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ 200 201 prop = kcalloc(props, sizeof(*prop), GFP_KERNEL); 202 if (!prop) { 203 ret = -ENOMEM; 204 goto err3; 205 } 206 207 prop[i++] = POWER_SUPPLY_PROP_PRESENT; 208 if (charge_gpiod) 209 prop[i++] = POWER_SUPPLY_PROP_STATUS; 210 if (pdata->batt_tech >= 0) 211 prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; 212 if (pdata->temp_aux >= 0) 213 prop[i++] = POWER_SUPPLY_PROP_TEMP; 214 if (pdata->batt_aux >= 0) 215 prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; 216 if (pdata->max_voltage >= 0) 217 prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; 218 if (pdata->min_voltage >= 0) 219 prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; 220 221 INIT_WORK(&bat_work, wm97xx_bat_work); 222 223 if (!pdata->batt_name) { 224 dev_info(&dev->dev, "Please consider setting proper battery " 225 "name in platform definition file, falling " 226 "back to name \"wm97xx-batt\"\n"); 227 bat_psy_desc.name = "wm97xx-batt"; 228 } else 229 bat_psy_desc.name = pdata->batt_name; 230 231 bat_psy_desc.properties = prop; 232 bat_psy_desc.num_properties = props; 233 234 bat_psy = power_supply_register(&dev->dev, &bat_psy_desc, &cfg); 235 if (!IS_ERR(bat_psy)) { 236 schedule_work(&bat_work); 237 } else { 238 ret = PTR_ERR(bat_psy); 239 goto err4; 240 } 241 242 return 0; 243err4: 244 kfree(prop); 245err3: 246 if (charge_gpiod) 247 free_irq(gpiod_to_irq(charge_gpiod), dev); 248 return ret; 249} 250 251static int wm97xx_bat_remove(struct platform_device *dev) 252{ 253 if (charge_gpiod) 254 free_irq(gpiod_to_irq(charge_gpiod), dev); 255 cancel_work_sync(&bat_work); 256 power_supply_unregister(bat_psy); 257 kfree(prop); 258 return 0; 259} 260 261static struct platform_driver wm97xx_bat_driver = { 262 .driver = { 263 .name = "wm97xx-battery", 264#ifdef CONFIG_PM 265 .pm = &wm97xx_bat_pm_ops, 266#endif 267 }, 268 .probe = wm97xx_bat_probe, 269 .remove = wm97xx_bat_remove, 270}; 271 272module_platform_driver(wm97xx_bat_driver); 273 274MODULE_LICENSE("GPL"); 275MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); 276MODULE_DESCRIPTION("WM97xx battery driver");