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 DEFINE_MUTEX(bat_lock); 29static struct work_struct bat_work; 30static struct mutex work_lock; 31static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN; 32static enum power_supply_property *prop; 33 34static unsigned long wm97xx_read_bat(struct power_supply *bat_ps) 35{ 36 struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data; 37 struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; 38 39 return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev->parent), 40 pdata->batt_aux) * pdata->batt_mult / 41 pdata->batt_div; 42} 43 44static unsigned long wm97xx_read_temp(struct power_supply *bat_ps) 45{ 46 struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data; 47 struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; 48 49 return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev->parent), 50 pdata->temp_aux) * pdata->temp_mult / 51 pdata->temp_div; 52} 53 54static int wm97xx_bat_get_property(struct power_supply *bat_ps, 55 enum power_supply_property psp, 56 union power_supply_propval *val) 57{ 58 struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data; 59 struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; 60 61 switch (psp) { 62 case POWER_SUPPLY_PROP_STATUS: 63 val->intval = bat_status; 64 break; 65 case POWER_SUPPLY_PROP_TECHNOLOGY: 66 val->intval = pdata->batt_tech; 67 break; 68 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 69 if (pdata->batt_aux >= 0) 70 val->intval = wm97xx_read_bat(bat_ps); 71 else 72 return -EINVAL; 73 break; 74 case POWER_SUPPLY_PROP_TEMP: 75 if (pdata->temp_aux >= 0) 76 val->intval = wm97xx_read_temp(bat_ps); 77 else 78 return -EINVAL; 79 break; 80 case POWER_SUPPLY_PROP_VOLTAGE_MAX: 81 if (pdata->max_voltage >= 0) 82 val->intval = pdata->max_voltage; 83 else 84 return -EINVAL; 85 break; 86 case POWER_SUPPLY_PROP_VOLTAGE_MIN: 87 if (pdata->min_voltage >= 0) 88 val->intval = pdata->min_voltage; 89 else 90 return -EINVAL; 91 break; 92 case POWER_SUPPLY_PROP_PRESENT: 93 val->intval = 1; 94 break; 95 default: 96 return -EINVAL; 97 } 98 return 0; 99} 100 101static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps) 102{ 103 schedule_work(&bat_work); 104} 105 106static void wm97xx_bat_update(struct power_supply *bat_ps) 107{ 108 int old_status = bat_status; 109 struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data; 110 struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; 111 112 mutex_lock(&work_lock); 113 114 bat_status = (pdata->charge_gpio >= 0) ? 115 (gpio_get_value(pdata->charge_gpio) ? 116 POWER_SUPPLY_STATUS_DISCHARGING : 117 POWER_SUPPLY_STATUS_CHARGING) : 118 POWER_SUPPLY_STATUS_UNKNOWN; 119 120 if (old_status != bat_status) { 121 pr_debug("%s: %i -> %i\n", bat_ps->name, old_status, 122 bat_status); 123 power_supply_changed(bat_ps); 124 } 125 126 mutex_unlock(&work_lock); 127} 128 129static struct power_supply bat_ps = { 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_ps); 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_scheduled_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 __devinit 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 mutex_init(&work_lock); 185 186 if (!pdata) { 187 dev_err(&dev->dev, "No platform_data supplied\n"); 188 return -EINVAL; 189 } 190 191 if (gpio_is_valid(pdata->charge_gpio)) { 192 ret = gpio_request(pdata->charge_gpio, "BATT CHRG"); 193 if (ret) 194 goto err; 195 ret = gpio_direction_input(pdata->charge_gpio); 196 if (ret) 197 goto err2; 198 ret = request_irq(gpio_to_irq(pdata->charge_gpio), 199 wm97xx_chrg_irq, IRQF_DISABLED, 200 "AC Detect", dev); 201 if (ret) 202 goto err2; 203 props++; /* POWER_SUPPLY_PROP_STATUS */ 204 } 205 206 if (pdata->batt_tech >= 0) 207 props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ 208 if (pdata->temp_aux >= 0) 209 props++; /* POWER_SUPPLY_PROP_TEMP */ 210 if (pdata->batt_aux >= 0) 211 props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ 212 if (pdata->max_voltage >= 0) 213 props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ 214 if (pdata->min_voltage >= 0) 215 props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ 216 217 prop = kzalloc(props * sizeof(*prop), GFP_KERNEL); 218 if (!prop) 219 goto err3; 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_ps.name = "wm97xx-batt"; 242 } else 243 bat_ps.name = pdata->batt_name; 244 245 bat_ps.properties = prop; 246 bat_ps.num_properties = props; 247 248 ret = power_supply_register(&dev->dev, &bat_ps); 249 if (!ret) 250 schedule_work(&bat_work); 251 else 252 goto err4; 253 254 return 0; 255err4: 256 kfree(prop); 257err3: 258 if (gpio_is_valid(pdata->charge_gpio)) 259 free_irq(gpio_to_irq(pdata->charge_gpio), dev); 260err2: 261 if (gpio_is_valid(pdata->charge_gpio)) 262 gpio_free(pdata->charge_gpio); 263err: 264 return ret; 265} 266 267static int __devexit wm97xx_bat_remove(struct platform_device *dev) 268{ 269 struct wm97xx_pdata *wmdata = dev->dev.platform_data; 270 struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; 271 272 if (pdata && gpio_is_valid(pdata->charge_gpio)) { 273 free_irq(gpio_to_irq(pdata->charge_gpio), dev); 274 gpio_free(pdata->charge_gpio); 275 } 276 flush_scheduled_work(); 277 power_supply_unregister(&bat_ps); 278 kfree(prop); 279 return 0; 280} 281 282static struct platform_driver wm97xx_bat_driver = { 283 .driver = { 284 .name = "wm97xx-battery", 285 .owner = THIS_MODULE, 286#ifdef CONFIG_PM 287 .pm = &wm97xx_bat_pm_ops, 288#endif 289 }, 290 .probe = wm97xx_bat_probe, 291 .remove = __devexit_p(wm97xx_bat_remove), 292}; 293 294static int __init wm97xx_bat_init(void) 295{ 296 return platform_driver_register(&wm97xx_bat_driver); 297} 298 299static void __exit wm97xx_bat_exit(void) 300{ 301 platform_driver_unregister(&wm97xx_bat_driver); 302} 303 304module_init(wm97xx_bat_init); 305module_exit(wm97xx_bat_exit); 306 307MODULE_LICENSE("GPL"); 308MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); 309MODULE_DESCRIPTION("WM97xx battery driver"); 310