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