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 (gpio_is_valid(pdata->charge_gpio)) { 179 ret = gpio_request(pdata->charge_gpio, "BATT CHRG"); 180 if (ret) 181 goto err; 182 ret = gpio_direction_input(pdata->charge_gpio); 183 if (ret) 184 goto err2; 185 ret = request_irq(gpio_to_irq(pdata->charge_gpio), 186 wm97xx_chrg_irq, 0, 187 "AC Detect", dev); 188 if (ret) 189 goto err2; 190 props++; /* POWER_SUPPLY_PROP_STATUS */ 191 } 192 193 if (pdata->batt_tech >= 0) 194 props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ 195 if (pdata->temp_aux >= 0) 196 props++; /* POWER_SUPPLY_PROP_TEMP */ 197 if (pdata->batt_aux >= 0) 198 props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ 199 if (pdata->max_voltage >= 0) 200 props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ 201 if (pdata->min_voltage >= 0) 202 props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ 203 204 prop = kcalloc(props, sizeof(*prop), GFP_KERNEL); 205 if (!prop) { 206 ret = -ENOMEM; 207 goto err3; 208 } 209 210 prop[i++] = POWER_SUPPLY_PROP_PRESENT; 211 if (pdata->charge_gpio >= 0) 212 prop[i++] = POWER_SUPPLY_PROP_STATUS; 213 if (pdata->batt_tech >= 0) 214 prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; 215 if (pdata->temp_aux >= 0) 216 prop[i++] = POWER_SUPPLY_PROP_TEMP; 217 if (pdata->batt_aux >= 0) 218 prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; 219 if (pdata->max_voltage >= 0) 220 prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; 221 if (pdata->min_voltage >= 0) 222 prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; 223 224 INIT_WORK(&bat_work, wm97xx_bat_work); 225 226 if (!pdata->batt_name) { 227 dev_info(&dev->dev, "Please consider setting proper battery " 228 "name in platform definition file, falling " 229 "back to name \"wm97xx-batt\"\n"); 230 bat_psy_desc.name = "wm97xx-batt"; 231 } else 232 bat_psy_desc.name = pdata->batt_name; 233 234 bat_psy_desc.properties = prop; 235 bat_psy_desc.num_properties = props; 236 237 bat_psy = power_supply_register(&dev->dev, &bat_psy_desc, &cfg); 238 if (!IS_ERR(bat_psy)) { 239 schedule_work(&bat_work); 240 } else { 241 ret = PTR_ERR(bat_psy); 242 goto err4; 243 } 244 245 return 0; 246 err4: 247 kfree(prop); 248 err3: 249 if (gpio_is_valid(pdata->charge_gpio)) 250 free_irq(gpio_to_irq(pdata->charge_gpio), dev); 251 err2: 252 if (gpio_is_valid(pdata->charge_gpio)) 253 gpio_free(pdata->charge_gpio); 254 err: 255 return ret; 256 } 257 258 static int wm97xx_bat_remove(struct platform_device *dev) 259 { 260 struct wm97xx_batt_pdata *pdata = dev->dev.platform_data; 261 262 if (pdata && gpio_is_valid(pdata->charge_gpio)) { 263 free_irq(gpio_to_irq(pdata->charge_gpio), dev); 264 gpio_free(pdata->charge_gpio); 265 } 266 cancel_work_sync(&bat_work); 267 power_supply_unregister(bat_psy); 268 kfree(prop); 269 return 0; 270 } 271 272 static struct platform_driver wm97xx_bat_driver = { 273 .driver = { 274 .name = "wm97xx-battery", 275 #ifdef CONFIG_PM 276 .pm = &wm97xx_bat_pm_ops, 277 #endif 278 }, 279 .probe = wm97xx_bat_probe, 280 .remove = wm97xx_bat_remove, 281 }; 282 283 module_platform_driver(wm97xx_bat_driver); 284 285 MODULE_LICENSE("GPL"); 286 MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); 287 MODULE_DESCRIPTION("WM97xx battery driver"); 288