Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v3.6 295 lines 7.3 kB view raw
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->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_ps = { 129 .type = POWER_SUPPLY_TYPE_BATTERY, 130 .get_property = wm97xx_bat_get_property, 131 .external_power_changed = wm97xx_bat_external_power_changed, 132 .use_for_apm = 1, 133}; 134 135static void wm97xx_bat_work(struct work_struct *work) 136{ 137 wm97xx_bat_update(&bat_ps); 138} 139 140static irqreturn_t wm97xx_chrg_irq(int irq, void *data) 141{ 142 schedule_work(&bat_work); 143 return IRQ_HANDLED; 144} 145 146#ifdef CONFIG_PM 147static int wm97xx_bat_suspend(struct device *dev) 148{ 149 flush_work_sync(&bat_work); 150 return 0; 151} 152 153static int wm97xx_bat_resume(struct device *dev) 154{ 155 schedule_work(&bat_work); 156 return 0; 157} 158 159static const struct dev_pm_ops wm97xx_bat_pm_ops = { 160 .suspend = wm97xx_bat_suspend, 161 .resume = wm97xx_bat_resume, 162}; 163#endif 164 165static int __devinit wm97xx_bat_probe(struct platform_device *dev) 166{ 167 int ret = 0; 168 int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ 169 int i = 0; 170 struct wm97xx_pdata *wmdata = dev->dev.platform_data; 171 struct wm97xx_batt_pdata *pdata; 172 173 if (!wmdata) { 174 dev_err(&dev->dev, "No platform data supplied\n"); 175 return -EINVAL; 176 } 177 178 pdata = wmdata->batt_pdata; 179 180 if (dev->id != -1) 181 return -EINVAL; 182 183 if (!pdata) { 184 dev_err(&dev->dev, "No platform_data supplied\n"); 185 return -EINVAL; 186 } 187 188 if (gpio_is_valid(pdata->charge_gpio)) { 189 ret = gpio_request(pdata->charge_gpio, "BATT CHRG"); 190 if (ret) 191 goto err; 192 ret = gpio_direction_input(pdata->charge_gpio); 193 if (ret) 194 goto err2; 195 ret = request_irq(gpio_to_irq(pdata->charge_gpio), 196 wm97xx_chrg_irq, 0, 197 "AC Detect", dev); 198 if (ret) 199 goto err2; 200 props++; /* POWER_SUPPLY_PROP_STATUS */ 201 } 202 203 if (pdata->batt_tech >= 0) 204 props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ 205 if (pdata->temp_aux >= 0) 206 props++; /* POWER_SUPPLY_PROP_TEMP */ 207 if (pdata->batt_aux >= 0) 208 props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ 209 if (pdata->max_voltage >= 0) 210 props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ 211 if (pdata->min_voltage >= 0) 212 props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ 213 214 prop = kzalloc(props * sizeof(*prop), GFP_KERNEL); 215 if (!prop) 216 goto err3; 217 218 prop[i++] = POWER_SUPPLY_PROP_PRESENT; 219 if (pdata->charge_gpio >= 0) 220 prop[i++] = POWER_SUPPLY_PROP_STATUS; 221 if (pdata->batt_tech >= 0) 222 prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; 223 if (pdata->temp_aux >= 0) 224 prop[i++] = POWER_SUPPLY_PROP_TEMP; 225 if (pdata->batt_aux >= 0) 226 prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; 227 if (pdata->max_voltage >= 0) 228 prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; 229 if (pdata->min_voltage >= 0) 230 prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; 231 232 INIT_WORK(&bat_work, wm97xx_bat_work); 233 234 if (!pdata->batt_name) { 235 dev_info(&dev->dev, "Please consider setting proper battery " 236 "name in platform definition file, falling " 237 "back to name \"wm97xx-batt\"\n"); 238 bat_ps.name = "wm97xx-batt"; 239 } else 240 bat_ps.name = pdata->batt_name; 241 242 bat_ps.properties = prop; 243 bat_ps.num_properties = props; 244 245 ret = power_supply_register(&dev->dev, &bat_ps); 246 if (!ret) 247 schedule_work(&bat_work); 248 else 249 goto err4; 250 251 return 0; 252err4: 253 kfree(prop); 254err3: 255 if (gpio_is_valid(pdata->charge_gpio)) 256 free_irq(gpio_to_irq(pdata->charge_gpio), dev); 257err2: 258 if (gpio_is_valid(pdata->charge_gpio)) 259 gpio_free(pdata->charge_gpio); 260err: 261 return ret; 262} 263 264static int __devexit wm97xx_bat_remove(struct platform_device *dev) 265{ 266 struct wm97xx_pdata *wmdata = dev->dev.platform_data; 267 struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; 268 269 if (pdata && gpio_is_valid(pdata->charge_gpio)) { 270 free_irq(gpio_to_irq(pdata->charge_gpio), dev); 271 gpio_free(pdata->charge_gpio); 272 } 273 cancel_work_sync(&bat_work); 274 power_supply_unregister(&bat_ps); 275 kfree(prop); 276 return 0; 277} 278 279static struct platform_driver wm97xx_bat_driver = { 280 .driver = { 281 .name = "wm97xx-battery", 282 .owner = THIS_MODULE, 283#ifdef CONFIG_PM 284 .pm = &wm97xx_bat_pm_ops, 285#endif 286 }, 287 .probe = wm97xx_bat_probe, 288 .remove = __devexit_p(wm97xx_bat_remove), 289}; 290 291module_platform_driver(wm97xx_bat_driver); 292 293MODULE_LICENSE("GPL"); 294MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); 295MODULE_DESCRIPTION("WM97xx battery driver");