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(&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 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 ret = -ENOMEM; 217 goto err3; 218 } 219 220 prop[i++] = POWER_SUPPLY_PROP_PRESENT; 221 if (pdata->charge_gpio >= 0) 222 prop[i++] = POWER_SUPPLY_PROP_STATUS; 223 if (pdata->batt_tech >= 0) 224 prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; 225 if (pdata->temp_aux >= 0) 226 prop[i++] = POWER_SUPPLY_PROP_TEMP; 227 if (pdata->batt_aux >= 0) 228 prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; 229 if (pdata->max_voltage >= 0) 230 prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; 231 if (pdata->min_voltage >= 0) 232 prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; 233 234 INIT_WORK(&bat_work, wm97xx_bat_work); 235 236 if (!pdata->batt_name) { 237 dev_info(&dev->dev, "Please consider setting proper battery " 238 "name in platform definition file, falling " 239 "back to name \"wm97xx-batt\"\n"); 240 bat_ps.name = "wm97xx-batt"; 241 } else 242 bat_ps.name = pdata->batt_name; 243 244 bat_ps.properties = prop; 245 bat_ps.num_properties = props; 246 247 ret = power_supply_register(&dev->dev, &bat_ps); 248 if (!ret) 249 schedule_work(&bat_work); 250 else 251 goto err4; 252 253 return 0; 254err4: 255 kfree(prop); 256err3: 257 if (gpio_is_valid(pdata->charge_gpio)) 258 free_irq(gpio_to_irq(pdata->charge_gpio), dev); 259err2: 260 if (gpio_is_valid(pdata->charge_gpio)) 261 gpio_free(pdata->charge_gpio); 262err: 263 return ret; 264} 265 266static int wm97xx_bat_remove(struct platform_device *dev) 267{ 268 struct wm97xx_pdata *wmdata = dev->dev.platform_data; 269 struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; 270 271 if (pdata && gpio_is_valid(pdata->charge_gpio)) { 272 free_irq(gpio_to_irq(pdata->charge_gpio), dev); 273 gpio_free(pdata->charge_gpio); 274 } 275 cancel_work_sync(&bat_work); 276 power_supply_unregister(&bat_ps); 277 kfree(prop); 278 return 0; 279} 280 281static struct platform_driver wm97xx_bat_driver = { 282 .driver = { 283 .name = "wm97xx-battery", 284 .owner = THIS_MODULE, 285#ifdef CONFIG_PM 286 .pm = &wm97xx_bat_pm_ops, 287#endif 288 }, 289 .probe = wm97xx_bat_probe, 290 .remove = wm97xx_bat_remove, 291}; 292 293module_platform_driver(wm97xx_bat_driver); 294 295MODULE_LICENSE("GPL"); 296MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); 297MODULE_DESCRIPTION("WM97xx battery driver"); 298