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