18c0984e5SSebastian Reichel /* 28c0984e5SSebastian Reichel * Battery measurement code for WM97xx 38c0984e5SSebastian Reichel * 48c0984e5SSebastian Reichel * based on tosa_battery.c 58c0984e5SSebastian Reichel * 68c0984e5SSebastian Reichel * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com> 78c0984e5SSebastian Reichel * 88c0984e5SSebastian Reichel * This program is free software; you can redistribute it and/or modify 98c0984e5SSebastian Reichel * it under the terms of the GNU General Public License version 2 as 108c0984e5SSebastian Reichel * published by the Free Software Foundation. 118c0984e5SSebastian Reichel * 128c0984e5SSebastian Reichel */ 138c0984e5SSebastian Reichel 148c0984e5SSebastian Reichel #include <linux/init.h> 158c0984e5SSebastian Reichel #include <linux/kernel.h> 168c0984e5SSebastian Reichel #include <linux/module.h> 178c0984e5SSebastian Reichel #include <linux/platform_device.h> 188c0984e5SSebastian Reichel #include <linux/power_supply.h> 198c0984e5SSebastian Reichel #include <linux/wm97xx.h> 208c0984e5SSebastian Reichel #include <linux/spinlock.h> 218c0984e5SSebastian Reichel #include <linux/interrupt.h> 228c0984e5SSebastian Reichel #include <linux/gpio.h> 238c0984e5SSebastian Reichel #include <linux/irq.h> 248c0984e5SSebastian Reichel #include <linux/slab.h> 258c0984e5SSebastian Reichel 268c0984e5SSebastian Reichel static struct work_struct bat_work; 278c0984e5SSebastian Reichel static DEFINE_MUTEX(work_lock); 288c0984e5SSebastian Reichel static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN; 298c0984e5SSebastian Reichel static enum power_supply_property *prop; 308c0984e5SSebastian Reichel 318c0984e5SSebastian Reichel static unsigned long wm97xx_read_bat(struct power_supply *bat_ps) 328c0984e5SSebastian Reichel { 336480af49SRobert Jarzmik struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps); 348c0984e5SSebastian Reichel 358c0984e5SSebastian Reichel return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev.parent), 368c0984e5SSebastian Reichel pdata->batt_aux) * pdata->batt_mult / 378c0984e5SSebastian Reichel pdata->batt_div; 388c0984e5SSebastian Reichel } 398c0984e5SSebastian Reichel 408c0984e5SSebastian Reichel static unsigned long wm97xx_read_temp(struct power_supply *bat_ps) 418c0984e5SSebastian Reichel { 426480af49SRobert Jarzmik struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps); 438c0984e5SSebastian Reichel 448c0984e5SSebastian Reichel return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev.parent), 458c0984e5SSebastian Reichel pdata->temp_aux) * pdata->temp_mult / 468c0984e5SSebastian Reichel pdata->temp_div; 478c0984e5SSebastian Reichel } 488c0984e5SSebastian Reichel 498c0984e5SSebastian Reichel static int wm97xx_bat_get_property(struct power_supply *bat_ps, 508c0984e5SSebastian Reichel enum power_supply_property psp, 518c0984e5SSebastian Reichel union power_supply_propval *val) 528c0984e5SSebastian Reichel { 536480af49SRobert Jarzmik struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps); 548c0984e5SSebastian Reichel 558c0984e5SSebastian Reichel switch (psp) { 568c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_STATUS: 578c0984e5SSebastian Reichel val->intval = bat_status; 588c0984e5SSebastian Reichel break; 598c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_TECHNOLOGY: 608c0984e5SSebastian Reichel val->intval = pdata->batt_tech; 618c0984e5SSebastian Reichel break; 628c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_VOLTAGE_NOW: 638c0984e5SSebastian Reichel if (pdata->batt_aux >= 0) 648c0984e5SSebastian Reichel val->intval = wm97xx_read_bat(bat_ps); 658c0984e5SSebastian Reichel else 668c0984e5SSebastian Reichel return -EINVAL; 678c0984e5SSebastian Reichel break; 688c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_TEMP: 698c0984e5SSebastian Reichel if (pdata->temp_aux >= 0) 708c0984e5SSebastian Reichel val->intval = wm97xx_read_temp(bat_ps); 718c0984e5SSebastian Reichel else 728c0984e5SSebastian Reichel return -EINVAL; 738c0984e5SSebastian Reichel break; 748c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_VOLTAGE_MAX: 758c0984e5SSebastian Reichel if (pdata->max_voltage >= 0) 768c0984e5SSebastian Reichel val->intval = pdata->max_voltage; 778c0984e5SSebastian Reichel else 788c0984e5SSebastian Reichel return -EINVAL; 798c0984e5SSebastian Reichel break; 808c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_VOLTAGE_MIN: 818c0984e5SSebastian Reichel if (pdata->min_voltage >= 0) 828c0984e5SSebastian Reichel val->intval = pdata->min_voltage; 838c0984e5SSebastian Reichel else 848c0984e5SSebastian Reichel return -EINVAL; 858c0984e5SSebastian Reichel break; 868c0984e5SSebastian Reichel case POWER_SUPPLY_PROP_PRESENT: 878c0984e5SSebastian Reichel val->intval = 1; 888c0984e5SSebastian Reichel break; 898c0984e5SSebastian Reichel default: 908c0984e5SSebastian Reichel return -EINVAL; 918c0984e5SSebastian Reichel } 928c0984e5SSebastian Reichel return 0; 938c0984e5SSebastian Reichel } 948c0984e5SSebastian Reichel 958c0984e5SSebastian Reichel static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps) 968c0984e5SSebastian Reichel { 978c0984e5SSebastian Reichel schedule_work(&bat_work); 988c0984e5SSebastian Reichel } 998c0984e5SSebastian Reichel 1008c0984e5SSebastian Reichel static void wm97xx_bat_update(struct power_supply *bat_ps) 1018c0984e5SSebastian Reichel { 1028c0984e5SSebastian Reichel int old_status = bat_status; 1036480af49SRobert Jarzmik struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps); 1048c0984e5SSebastian Reichel 1058c0984e5SSebastian Reichel mutex_lock(&work_lock); 1068c0984e5SSebastian Reichel 1078c0984e5SSebastian Reichel bat_status = (pdata->charge_gpio >= 0) ? 1088c0984e5SSebastian Reichel (gpio_get_value(pdata->charge_gpio) ? 1098c0984e5SSebastian Reichel POWER_SUPPLY_STATUS_DISCHARGING : 1108c0984e5SSebastian Reichel POWER_SUPPLY_STATUS_CHARGING) : 1118c0984e5SSebastian Reichel POWER_SUPPLY_STATUS_UNKNOWN; 1128c0984e5SSebastian Reichel 1138c0984e5SSebastian Reichel if (old_status != bat_status) { 1148c0984e5SSebastian Reichel pr_debug("%s: %i -> %i\n", bat_ps->desc->name, old_status, 1158c0984e5SSebastian Reichel bat_status); 1168c0984e5SSebastian Reichel power_supply_changed(bat_ps); 1178c0984e5SSebastian Reichel } 1188c0984e5SSebastian Reichel 1198c0984e5SSebastian Reichel mutex_unlock(&work_lock); 1208c0984e5SSebastian Reichel } 1218c0984e5SSebastian Reichel 1228c0984e5SSebastian Reichel static struct power_supply *bat_psy; 1238c0984e5SSebastian Reichel static struct power_supply_desc bat_psy_desc = { 1248c0984e5SSebastian Reichel .type = POWER_SUPPLY_TYPE_BATTERY, 1258c0984e5SSebastian Reichel .get_property = wm97xx_bat_get_property, 1268c0984e5SSebastian Reichel .external_power_changed = wm97xx_bat_external_power_changed, 1278c0984e5SSebastian Reichel .use_for_apm = 1, 1288c0984e5SSebastian Reichel }; 1298c0984e5SSebastian Reichel 1308c0984e5SSebastian Reichel static void wm97xx_bat_work(struct work_struct *work) 1318c0984e5SSebastian Reichel { 1328c0984e5SSebastian Reichel wm97xx_bat_update(bat_psy); 1338c0984e5SSebastian Reichel } 1348c0984e5SSebastian Reichel 1358c0984e5SSebastian Reichel static irqreturn_t wm97xx_chrg_irq(int irq, void *data) 1368c0984e5SSebastian Reichel { 1378c0984e5SSebastian Reichel schedule_work(&bat_work); 1388c0984e5SSebastian Reichel return IRQ_HANDLED; 1398c0984e5SSebastian Reichel } 1408c0984e5SSebastian Reichel 1418c0984e5SSebastian Reichel #ifdef CONFIG_PM 1428c0984e5SSebastian Reichel static int wm97xx_bat_suspend(struct device *dev) 1438c0984e5SSebastian Reichel { 1448c0984e5SSebastian Reichel flush_work(&bat_work); 1458c0984e5SSebastian Reichel return 0; 1468c0984e5SSebastian Reichel } 1478c0984e5SSebastian Reichel 1488c0984e5SSebastian Reichel static int wm97xx_bat_resume(struct device *dev) 1498c0984e5SSebastian Reichel { 1508c0984e5SSebastian Reichel schedule_work(&bat_work); 1518c0984e5SSebastian Reichel return 0; 1528c0984e5SSebastian Reichel } 1538c0984e5SSebastian Reichel 1548c0984e5SSebastian Reichel static const struct dev_pm_ops wm97xx_bat_pm_ops = { 1558c0984e5SSebastian Reichel .suspend = wm97xx_bat_suspend, 1568c0984e5SSebastian Reichel .resume = wm97xx_bat_resume, 1578c0984e5SSebastian Reichel }; 1588c0984e5SSebastian Reichel #endif 1598c0984e5SSebastian Reichel 1608c0984e5SSebastian Reichel static int wm97xx_bat_probe(struct platform_device *dev) 1618c0984e5SSebastian Reichel { 1628c0984e5SSebastian Reichel int ret = 0; 1638c0984e5SSebastian Reichel int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ 1648c0984e5SSebastian Reichel int i = 0; 1656480af49SRobert Jarzmik struct wm97xx_batt_pdata *pdata = dev->dev.platform_data; 1666480af49SRobert Jarzmik struct power_supply_config cfg = {}; 1678c0984e5SSebastian Reichel 1686480af49SRobert Jarzmik if (!pdata) { 1698c0984e5SSebastian Reichel dev_err(&dev->dev, "No platform data supplied\n"); 1708c0984e5SSebastian Reichel return -EINVAL; 1718c0984e5SSebastian Reichel } 1728c0984e5SSebastian Reichel 1736480af49SRobert Jarzmik cfg.drv_data = pdata; 1748c0984e5SSebastian Reichel 1758c0984e5SSebastian Reichel if (dev->id != -1) 1768c0984e5SSebastian Reichel return -EINVAL; 1778c0984e5SSebastian Reichel 1788c0984e5SSebastian Reichel if (gpio_is_valid(pdata->charge_gpio)) { 1798c0984e5SSebastian Reichel ret = gpio_request(pdata->charge_gpio, "BATT CHRG"); 1808c0984e5SSebastian Reichel if (ret) 1818c0984e5SSebastian Reichel goto err; 1828c0984e5SSebastian Reichel ret = gpio_direction_input(pdata->charge_gpio); 1838c0984e5SSebastian Reichel if (ret) 1848c0984e5SSebastian Reichel goto err2; 1858c0984e5SSebastian Reichel ret = request_irq(gpio_to_irq(pdata->charge_gpio), 1868c0984e5SSebastian Reichel wm97xx_chrg_irq, 0, 1878c0984e5SSebastian Reichel "AC Detect", dev); 1888c0984e5SSebastian Reichel if (ret) 1898c0984e5SSebastian Reichel goto err2; 1908c0984e5SSebastian Reichel props++; /* POWER_SUPPLY_PROP_STATUS */ 1918c0984e5SSebastian Reichel } 1928c0984e5SSebastian Reichel 1938c0984e5SSebastian Reichel if (pdata->batt_tech >= 0) 1948c0984e5SSebastian Reichel props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ 1958c0984e5SSebastian Reichel if (pdata->temp_aux >= 0) 1968c0984e5SSebastian Reichel props++; /* POWER_SUPPLY_PROP_TEMP */ 1978c0984e5SSebastian Reichel if (pdata->batt_aux >= 0) 1988c0984e5SSebastian Reichel props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ 1998c0984e5SSebastian Reichel if (pdata->max_voltage >= 0) 2008c0984e5SSebastian Reichel props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ 2018c0984e5SSebastian Reichel if (pdata->min_voltage >= 0) 2028c0984e5SSebastian Reichel props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ 2038c0984e5SSebastian Reichel 204*6396bb22SKees Cook prop = kcalloc(props, sizeof(*prop), GFP_KERNEL); 2058c0984e5SSebastian Reichel if (!prop) { 2068c0984e5SSebastian Reichel ret = -ENOMEM; 2078c0984e5SSebastian Reichel goto err3; 2088c0984e5SSebastian Reichel } 2098c0984e5SSebastian Reichel 2108c0984e5SSebastian Reichel prop[i++] = POWER_SUPPLY_PROP_PRESENT; 2118c0984e5SSebastian Reichel if (pdata->charge_gpio >= 0) 2128c0984e5SSebastian Reichel prop[i++] = POWER_SUPPLY_PROP_STATUS; 2138c0984e5SSebastian Reichel if (pdata->batt_tech >= 0) 2148c0984e5SSebastian Reichel prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; 2158c0984e5SSebastian Reichel if (pdata->temp_aux >= 0) 2168c0984e5SSebastian Reichel prop[i++] = POWER_SUPPLY_PROP_TEMP; 2178c0984e5SSebastian Reichel if (pdata->batt_aux >= 0) 2188c0984e5SSebastian Reichel prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; 2198c0984e5SSebastian Reichel if (pdata->max_voltage >= 0) 2208c0984e5SSebastian Reichel prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; 2218c0984e5SSebastian Reichel if (pdata->min_voltage >= 0) 2228c0984e5SSebastian Reichel prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; 2238c0984e5SSebastian Reichel 2248c0984e5SSebastian Reichel INIT_WORK(&bat_work, wm97xx_bat_work); 2258c0984e5SSebastian Reichel 2268c0984e5SSebastian Reichel if (!pdata->batt_name) { 2278c0984e5SSebastian Reichel dev_info(&dev->dev, "Please consider setting proper battery " 2288c0984e5SSebastian Reichel "name in platform definition file, falling " 2298c0984e5SSebastian Reichel "back to name \"wm97xx-batt\"\n"); 2308c0984e5SSebastian Reichel bat_psy_desc.name = "wm97xx-batt"; 2318c0984e5SSebastian Reichel } else 2328c0984e5SSebastian Reichel bat_psy_desc.name = pdata->batt_name; 2338c0984e5SSebastian Reichel 2348c0984e5SSebastian Reichel bat_psy_desc.properties = prop; 2358c0984e5SSebastian Reichel bat_psy_desc.num_properties = props; 2368c0984e5SSebastian Reichel 2376480af49SRobert Jarzmik bat_psy = power_supply_register(&dev->dev, &bat_psy_desc, &cfg); 2388c0984e5SSebastian Reichel if (!IS_ERR(bat_psy)) { 2398c0984e5SSebastian Reichel schedule_work(&bat_work); 2408c0984e5SSebastian Reichel } else { 2418c0984e5SSebastian Reichel ret = PTR_ERR(bat_psy); 2428c0984e5SSebastian Reichel goto err4; 2438c0984e5SSebastian Reichel } 2448c0984e5SSebastian Reichel 2458c0984e5SSebastian Reichel return 0; 2468c0984e5SSebastian Reichel err4: 2478c0984e5SSebastian Reichel kfree(prop); 2488c0984e5SSebastian Reichel err3: 2498c0984e5SSebastian Reichel if (gpio_is_valid(pdata->charge_gpio)) 2508c0984e5SSebastian Reichel free_irq(gpio_to_irq(pdata->charge_gpio), dev); 2518c0984e5SSebastian Reichel err2: 2528c0984e5SSebastian Reichel if (gpio_is_valid(pdata->charge_gpio)) 2538c0984e5SSebastian Reichel gpio_free(pdata->charge_gpio); 2548c0984e5SSebastian Reichel err: 2558c0984e5SSebastian Reichel return ret; 2568c0984e5SSebastian Reichel } 2578c0984e5SSebastian Reichel 2588c0984e5SSebastian Reichel static int wm97xx_bat_remove(struct platform_device *dev) 2598c0984e5SSebastian Reichel { 2606480af49SRobert Jarzmik struct wm97xx_batt_pdata *pdata = dev->dev.platform_data; 2618c0984e5SSebastian Reichel 2628c0984e5SSebastian Reichel if (pdata && gpio_is_valid(pdata->charge_gpio)) { 2638c0984e5SSebastian Reichel free_irq(gpio_to_irq(pdata->charge_gpio), dev); 2648c0984e5SSebastian Reichel gpio_free(pdata->charge_gpio); 2658c0984e5SSebastian Reichel } 2668c0984e5SSebastian Reichel cancel_work_sync(&bat_work); 2678c0984e5SSebastian Reichel power_supply_unregister(bat_psy); 2688c0984e5SSebastian Reichel kfree(prop); 2698c0984e5SSebastian Reichel return 0; 2708c0984e5SSebastian Reichel } 2718c0984e5SSebastian Reichel 2728c0984e5SSebastian Reichel static struct platform_driver wm97xx_bat_driver = { 2738c0984e5SSebastian Reichel .driver = { 2748c0984e5SSebastian Reichel .name = "wm97xx-battery", 2758c0984e5SSebastian Reichel #ifdef CONFIG_PM 2768c0984e5SSebastian Reichel .pm = &wm97xx_bat_pm_ops, 2778c0984e5SSebastian Reichel #endif 2788c0984e5SSebastian Reichel }, 2798c0984e5SSebastian Reichel .probe = wm97xx_bat_probe, 2808c0984e5SSebastian Reichel .remove = wm97xx_bat_remove, 2818c0984e5SSebastian Reichel }; 2828c0984e5SSebastian Reichel 2838c0984e5SSebastian Reichel module_platform_driver(wm97xx_bat_driver); 2848c0984e5SSebastian Reichel 2858c0984e5SSebastian Reichel MODULE_LICENSE("GPL"); 2868c0984e5SSebastian Reichel MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); 2878c0984e5SSebastian Reichel MODULE_DESCRIPTION("WM97xx battery driver"); 288