diff options
Diffstat (limited to 'drivers/power/max17085_battery.c')
-rw-r--r-- | drivers/power/max17085_battery.c | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/drivers/power/max17085_battery.c b/drivers/power/max17085_battery.c new file mode 100644 index 000000000000..05889cfc527a --- /dev/null +++ b/drivers/power/max17085_battery.c @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2012 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * MAX17085 Battery driver + */ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <mach/gpio.h> + +struct max17085_chip { + struct device *dev; + + /*gpio*/ + int pwr_good; + int ac_in; + int charge_now; + int charge_done; + + struct power_supply ac; + struct power_supply bat; + struct delayed_work work; + + int online; + int health; + int status; + int voltage_uV; + int cap; +}; + +#define MAX17085_DELAY 3000 +#define MAX17085_DEF_TEMP 30 + +static int max17085_bat_get_mfr(struct power_supply *psy, + union power_supply_propval *val) +{ + val->strval = "unknown"; + return 0; +} + +static int max17085_bat_get_tech(struct power_supply *psy, + union power_supply_propval *val) +{ + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + return 0; +} + +static int max17085_bat_get_cap(struct power_supply *psy, + union power_supply_propval *val) +{ + struct max17085_chip *chip = container_of(psy, + struct max17085_chip, bat); + + val->intval = chip->cap; + return 0; +} + +static int max17085_bat_get_volt(struct power_supply *psy, + union power_supply_propval *val) +{ + struct max17085_chip *chip = container_of(psy, + struct max17085_chip, bat); + + val->intval = chip->voltage_uV; + return 0; +} + +static int max17085_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct max17085_chip *chip = container_of(psy, + struct max17085_chip, bat); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = chip->status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = chip->health; + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = max17085_bat_get_cap(psy, val); + if (ret) + return ret; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = max17085_bat_get_volt(psy, val); + if (ret) + return ret; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + ret = max17085_bat_get_mfr(psy, val); + if (ret) + return ret; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + ret = max17085_bat_get_tech(psy, val); + if (ret) + return ret; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = MAX17085_DEF_TEMP; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int max17085_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max17085_chip *chip = container_of(psy, + struct max17085_chip, ac); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = chip->online; + break; + default: + break; + } + + return 0; +} + +static void max17085_get_online(struct max17085_chip *chip) +{ + int level = gpio_get_value(chip->ac_in); + int cur_online = !level; + + if (chip->online != cur_online) { + chip->online = cur_online; + power_supply_changed(&chip->ac); + } +} + +static void max17085_get_health(struct max17085_chip *chip) +{ + int level = gpio_get_value(chip->pwr_good); + + if (level && (chip->voltage_uV >= 0)) + chip->health = POWER_SUPPLY_HEALTH_GOOD; + else + chip->health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; +} + +extern int da9052_adc_read(unsigned char channel); +#define VOLT_REG_TO_MV(val) ((val * 2500) / 1024) +#define BATT_TO_ADC_SCALE 11 +#define DA9052_ADCMAN_ADCIN6 6 +static void max17085_get_volt(struct max17085_chip *chip) +{ + int val; + val = da9052_adc_read(DA9052_ADCMAN_ADCIN6); + + /* Ignore lower 2 bits out of 10 bits due to noise */ + val = val & 0x3fc; + if (val > 0) + chip->voltage_uV = VOLT_REG_TO_MV(val)*1000*BATT_TO_ADC_SCALE; + else + chip->voltage_uV = -1; +} + +#define BATT_EMPTY_MV 9955 +#define BATT_FULL_MV 12000 +static void max17085_get_cap(struct max17085_chip *chip) +{ + int old_cap = chip->cap; + + if (chip->voltage_uV > BATT_EMPTY_MV*1000) { + chip->cap = (chip->voltage_uV/1000 - BATT_EMPTY_MV) * 100/ + (BATT_FULL_MV - BATT_EMPTY_MV); + if (chip->cap > 100) + chip->cap = 100; + } else + chip->cap = 0; + + if (chip->cap != old_cap) + power_supply_changed(&chip->bat); +} + +static void max17085_update_status(struct max17085_chip *chip) +{ + int old_status = chip->status; + + if (chip->online) { + if (chip->voltage_uV/1000 < BATT_FULL_MV) + chip->status = + POWER_SUPPLY_STATUS_CHARGING; + else + chip->status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + } else + chip->status = POWER_SUPPLY_STATUS_DISCHARGING; + + if (chip->voltage_uV/1000 >= BATT_FULL_MV) + chip->status = POWER_SUPPLY_STATUS_FULL; + + if (old_status != POWER_SUPPLY_STATUS_UNKNOWN && + old_status != chip->status) + power_supply_changed(&chip->bat); + + if (chip->cap < 20) { + gpio_set_value(chip->charge_now, 1); + gpio_set_value(chip->charge_done, 0); + } else if (chip->cap < 80) { + gpio_set_value(chip->charge_now, 1); + gpio_set_value(chip->charge_done, 1); + } else { + gpio_set_value(chip->charge_now, 0); + gpio_set_value(chip->charge_done, 1); + } +} + +static void max17085_work(struct work_struct *work) +{ + struct max17085_chip *chip = container_of(work, + struct max17085_chip, work.work); + + max17085_get_online(chip); + max17085_get_volt(chip); + max17085_get_health(chip); + max17085_get_cap(chip); + max17085_update_status(chip); + + schedule_delayed_work(&chip->work, msecs_to_jiffies(MAX17085_DELAY)); +} + +static enum power_supply_property max17085_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static enum power_supply_property max17085_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int max17085_bat_probe(struct platform_device *pdev) +{ + int ret = 0; + struct max17085_chip *chip; + struct resource *res; + + chip = kzalloc(sizeof(struct max17085_chip), GFP_KERNEL); + if (!chip) { + ret = -ENOMEM; + goto chip_alloc_failed; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_IO, "pwr-good"); + if (res == NULL) { + ret = -EINVAL; + goto resource_failed; + } + chip->pwr_good = res->start; + res = platform_get_resource_byname(pdev, + IORESOURCE_IO, "ac-in"); + if (res == NULL) { + ret = -EINVAL; + goto resource_failed; + } + chip->ac_in = res->start; + res = platform_get_resource_byname(pdev, + IORESOURCE_IO, "charge-now"); + if (res == NULL) { + ret = -EINVAL; + goto resource_failed; + } + chip->charge_now = res->start; + res = platform_get_resource_byname(pdev, + IORESOURCE_IO, "charge-done"); + if (res == NULL) { + ret = -EINVAL; + goto resource_failed; + } + chip->charge_done = res->start; + + chip->dev = &pdev->dev; + chip->bat.name = "battery"; + chip->bat.type = POWER_SUPPLY_TYPE_BATTERY; + chip->bat.properties = max17085_bat_props; + chip->bat.num_properties = ARRAY_SIZE(max17085_bat_props); + chip->bat.get_property = max17085_bat_get_property; + chip->bat.use_for_apm = 1; + + chip->ac.name = "ac"; + chip->ac.type = POWER_SUPPLY_TYPE_MAINS; + chip->ac.properties = max17085_ac_props; + chip->ac.num_properties = ARRAY_SIZE(max17085_ac_props); + chip->ac.get_property = max17085_ac_get_property; + + platform_set_drvdata(pdev, chip); + + ret = power_supply_register(&pdev->dev, &chip->ac); + if (ret) { + dev_err(chip->dev, "failed to register ac\n"); + goto register_ac_failed; + } + + ret = power_supply_register(&pdev->dev, &chip->bat); + if (ret) { + dev_err(chip->dev, "failed to register battery\n"); + goto register_batt_failed; + } + + max17085_get_online(chip); + + INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17085_work); + schedule_delayed_work(&chip->work, msecs_to_jiffies(MAX17085_DELAY)); + + return ret; + +register_batt_failed: + power_supply_unregister(&chip->ac); +register_ac_failed: +resource_failed: + kfree(chip); +chip_alloc_failed: + return ret; +} + +static int max17085_bat_remove(struct platform_device *pdev) +{ + struct max17085_chip *chip = platform_get_drvdata(pdev); + + cancel_delayed_work(&chip->work); + power_supply_unregister(&chip->bat); + power_supply_unregister(&chip->ac); + kfree(chip); + + return 0; +} + +static void max17085_bat_shutdown(struct platform_device *pdev) +{ + struct max17085_chip *chip = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&chip->work); + gpio_set_value(chip->charge_now, 0); + gpio_set_value(chip->charge_done, 0); +} + +static struct platform_driver max17085_bat_driver = { + .probe = max17085_bat_probe, + .remove = max17085_bat_remove, + .shutdown = max17085_bat_shutdown, + .driver = { + .name = "max17085_bat", + }, + +}; + +static int __devinit max17085_bat_init(void) +{ + return platform_driver_register(&max17085_bat_driver); +} + +static void __devexit max17085_bat_exit(void) +{ + platform_driver_unregister(&max17085_bat_driver); +} + +module_init(max17085_bat_init); +module_exit(max17085_bat_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MAX17085 battery driver"); +MODULE_LICENSE("GPL"); |