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