diff options
author | Gary King <gking@nvidia.com> | 2010-05-28 14:49:26 -0700 |
---|---|---|
committer | Gary King <gking@nvidia.com> | 2010-05-28 15:01:01 -0700 |
commit | 7aabd805f4869ad992bb0506ea98b30d26195cef (patch) | |
tree | d6f77e87ba6d4507bee95a5eebebd70c12584204 /drivers/power | |
parent | 50d0593fb729f5995d07f47a69593743d9010646 (diff) |
power: add battery driver for tegra PMU ODM kit
registers itself with the same "tegra_battery" driver name as the
NvEc battery, so disallow platforms from selecting both the ODM battery
and the NvEc battery
fix the nvec_battery driver compilation to only occur when it is selected,
rather than whenever the NvEc infrastructure is enabled
Change-Id: I9716824d0882d8cd8e693ebdad6bcc96c3f035c5
Reviewed-on: http://git-master/r/1822
Reviewed-by: Gary King <gking@nvidia.com>
Tested-by: Gary King <gking@nvidia.com>
Diffstat (limited to 'drivers/power')
-rw-r--r-- | drivers/power/Kconfig | 9 | ||||
-rw-r--r-- | drivers/power/Makefile | 3 | ||||
-rw-r--r-- | drivers/power/tegra_odm_battery.c | 492 |
3 files changed, 502 insertions, 2 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index e89c62ef7214..51e9fa59d629 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -104,9 +104,16 @@ config BATTERY_MAX17040 in handheld and portable equipment. The MAX17040 is configured to operate with a single lithium cell +config TEGRA_BATTERY_ODM + boolean "NVIDIA Tegra ODM kit battery driver (PMU controlled)" + depends on ARCH_TEGRA && TEGRA_ODM_KIT + help + Say Y to enable battery support for Tegra using the NVIDIA ODM kit + PMU adaptation interface + config TEGRA_BATTERY_NVEC boolean "Battery connected to NVIDIA Tegra NvEc embedded controller" - depends on ARCH_TEGRA && TEGRA_NVEC + depends on ARCH_TEGRA && TEGRA_NVEC && !TEGRA_BATTERY_ODM help Say Y to include battery support for batteries connected to an NVIDIA Tegra NvEc-compliant embedded controller diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 552ae086893c..8d1f27f358fb 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -28,5 +28,6 @@ obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o -obj-$(CONFIG_TEGRA_NVEC) += nvec_battery.o +obj-$(CONFIG_TEGRA_BATTERY_ODM) += tegra_odm_battery.o +obj-$(CONFIG_TEGRA_BATTERY_NVEC) += nvec_battery.o obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o diff --git a/drivers/power/tegra_odm_battery.c b/drivers/power/tegra_odm_battery.c new file mode 100644 index 000000000000..9f77f1ff0702 --- /dev/null +++ b/drivers/power/tegra_odm_battery.c @@ -0,0 +1,492 @@ +/* + * drivers/power/tegra_odm_battery.c + * + * Battery driver for batteries implemented using NVIDIA Tegra ODM kit PMU + * adaptation interface + * + * Copyright (c) 2009, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define NV_DEBUG 0 + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/debugfs.h> +#include <linux/power_supply.h> +#include <linux/wakelock.h> + +#include "nvcommon.h" +#include "nvos.h" +#include "nvrm_pmu.h" +#include "mach/nvrm_linux.h" // for s_hRmGlobal + +#define NVBATTERY_POLLING_INTERVAL 30000 /* 30 Seconds */ + +typedef enum { + NvCharger_Type_Battery = 0, + NvCharger_Type_USB, + NvCharger_Type_AC, + NvCharger_Type_Num, + NvCharger_Type_Force32 = 0x7FFFFFFF +} NvCharger_Type; + +typedef enum { + NvCharge_Control_Charging_Disable = 0, + NvCharge_Control_Charging_Enable, + NvCharge_Control_Num, + NvCharge_Control_Force32 = 0x7FFFFFFF +} NvCharge_Control; + +static enum power_supply_property tegra_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_TEMP +}; + +static enum power_supply_property tegra_power_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *supply_list[] = { + "battery", +}; + +static int tegra_power_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val); + +static int tegra_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val); + +static struct power_supply tegra_supplies[] = { + { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = tegra_battery_properties, + .num_properties = ARRAY_SIZE(tegra_battery_properties), + .get_property = tegra_battery_get_property, + }, + { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB, + .supplied_to = supply_list, + .num_supplicants = ARRAY_SIZE(supply_list), + .properties = tegra_power_properties, + .num_properties = ARRAY_SIZE(tegra_power_properties), + .get_property = tegra_power_get_property, + }, + { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .supplied_to = supply_list, + .num_supplicants = ARRAY_SIZE(supply_list), + .properties = tegra_power_properties, + .num_properties = ARRAY_SIZE(tegra_power_properties), + .get_property = tegra_power_get_property, + }, +}; + +typedef struct tegra_battery_dev { + struct timer_list battery_poll_timer; + NvU32 batt_id; /* battery ID from ADC */ + NvU32 batt_vol; /* voltage from ADC */ + NvU32 batt_temp; /* temperature (degrees C) */ + NvU32 batt_current; /* current from ADC */ + NvU32 charging_source; /* 0: no cable, 1:usb, 2:AC */ + NvU32 charging_enabled; /* 0: Disable, 1: Enable */ + NvU32 full_bat; /* max capacity of battery (mAh) */ + NvU32 BatteryLifePercent; + NvU32 BatteryLifeTime; + NvU32 BatteryMahConsumed; + NvU32 ACLineStatus; + NvU32 batt_status_poll_period; + NvBool present; +} tegra_battery_dev; + +static tegra_battery_dev *batt_dev; + +static ssize_t tegra_battery_show_property( + struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", batt_dev->batt_status_poll_period); +} + +static ssize_t tegra_battery_store_property( + struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + unsigned int value = 0; + value = simple_strtoul(buf, NULL, 0); + batt_dev->batt_status_poll_period = value; + mod_timer(&(batt_dev->battery_poll_timer), + jiffies + msecs_to_jiffies(batt_dev->batt_status_poll_period)); + return count; +} + +static struct device_attribute tegra_battery_attr = { + .attr = { .name = "status_poll_period", .mode = S_IRUGO | S_IWUSR, + .owner = THIS_MODULE }, + .show = tegra_battery_show_property, + .store = tegra_battery_store_property, +}; + +static void tegra_get_battery_tech(int *Value, + NvRmPmuBatteryInstance NvBatteryInst) +{ + NvRmPmuBatteryChemistry Chemistry = {0}; + + NvRmPmuGetBatteryChemistry(s_hRmGlobal, NvBatteryInst, &Chemistry); + + switch(Chemistry) + { + case NvRmPmuBatteryChemistry_NICD: + *Value = POWER_SUPPLY_TECHNOLOGY_NiCd; + break; + + case NvRmPmuBatteryChemistry_NIMH: + *Value = POWER_SUPPLY_TECHNOLOGY_NiMH; + break; + + case NvRmPmuBatteryChemistry_LION: + *Value = POWER_SUPPLY_TECHNOLOGY_LION; + break; + + case NvRmPmuBatteryChemistry_LIPOLY: + *Value = POWER_SUPPLY_TECHNOLOGY_LIPO; + break; + + default: + *Value = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + break; + } +} + +static void tegra_battery_convert(NvRmPmuBatteryData *pPmuData) +{ + if (pPmuData->batteryLifePercent == NVODM_BATTERY_DATA_UNKNOWN) + pPmuData->batteryLifePercent = 0; + + if (pPmuData->batteryLifeTime == NVODM_BATTERY_DATA_UNKNOWN) + pPmuData->batteryLifeTime = 0; + + if (pPmuData->batteryVoltage == NVODM_BATTERY_DATA_UNKNOWN) + pPmuData->batteryVoltage = 0; + + if (pPmuData->batteryCurrent == NVODM_BATTERY_DATA_UNKNOWN) + pPmuData->batteryCurrent = 0; + + if (pPmuData->batteryMahConsumed == NVODM_BATTERY_DATA_UNKNOWN) + pPmuData->batteryMahConsumed = 0; + + if (pPmuData->batteryTemperature == NVODM_BATTERY_DATA_UNKNOWN) + pPmuData->batteryTemperature = 0; +} + +static int tegra_battery_data(NvRmPmuBatteryInstance NvBatteryInst) +{ + NvRmPmuBatteryData pPmuData = {0}; + + if (!NvRmPmuGetBatteryData(s_hRmGlobal, NvBatteryInst, &pPmuData)) + return -1; + + if (NvBatteryInst == NvRmPmuBatteryInst_Main) { + tegra_battery_convert(&pPmuData); + batt_dev->batt_vol = pPmuData.batteryVoltage; + batt_dev->batt_vol *= 1000; // Convert volt to mV + batt_dev->batt_current = pPmuData.batteryCurrent; + batt_dev->batt_temp = pPmuData.batteryTemperature; + batt_dev->batt_temp *= 10; // FIXME : Why is this here? + batt_dev->BatteryLifePercent = pPmuData.batteryLifePercent; + batt_dev->BatteryLifeTime = pPmuData.batteryLifeTime; + batt_dev->BatteryMahConsumed = pPmuData.batteryMahConsumed; + } + + return 0; +} + +static int tegra_get_ac_status(NvCharger_Type *charger) +{ + NvRmPmuAcLineStatus ACStatus = NvRmPmuAcLine_Offline; + + if (!NvRmPmuGetAcLineStatus(s_hRmGlobal, &ACStatus)) + return -1; + + if (ACStatus == NvRmPmuAcLine_Offline) + batt_dev->ACLineStatus = NV_FALSE; + else if (ACStatus == NvRmPmuAcLine_Online) { + batt_dev->ACLineStatus = NV_TRUE; + batt_dev->charging_source = NvCharger_Type_AC; + if (charger) + *charger = batt_dev->charging_source; + } else + batt_dev->ACLineStatus = NV_FALSE; + + return 0; +} + +static int tegra_power_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + NvCharger_Type charger; + + switch (psp) { + + case POWER_SUPPLY_PROP_ONLINE: + if (tegra_get_ac_status(&charger)) + return -EINVAL; + + if (psy->type == POWER_SUPPLY_TYPE_MAINS) + val->intval = (charger == NvCharger_Type_AC); + else if (psy->type == POWER_SUPPLY_TYPE_USB) + val->intval = (charger == NvCharger_Type_USB); + else + val->intval = 0; + break; + + default: + return -EINVAL; + } + return 0; +} + +static int tegra_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + NvU8 state[4] = { 0 }; + int batt_tech = 0; + + switch (psp) { + + case POWER_SUPPLY_PROP_STATUS: + if (!NvRmPmuGetBatteryStatus(s_hRmGlobal, + NvRmPmuBatteryInst_Main, state)) + return -EINVAL; + + switch(state[0]) { + + case NVODM_BATTERY_STATUS_HIGH: + val->intval = POWER_SUPPLY_STATUS_FULL; + batt_dev->present = NV_TRUE; + break; + + case NVODM_BATTERY_STATUS_NO_BATTERY: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + batt_dev->present = NV_FALSE; + break; + + case NVODM_BATTERY_STATUS_CHARGING: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + batt_dev->present = NV_TRUE; + batt_dev->charging_enabled = NV_TRUE; + batt_dev->charging_source = NvCharger_Type_AC; + break; + + case NVODM_BATTERY_STATUS_UNKNOWN: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + batt_dev->present = NV_FALSE; + break; + + case NVODM_BATTERY_STATUS_LOW: + case NVODM_BATTERY_STATUS_CRITICAL: + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + batt_dev->present = NV_TRUE; + break; + + default: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + batt_dev->present = NV_FALSE; + break; + } + break; + + case POWER_SUPPLY_PROP_PRESENT: + val->intval = batt_dev->present; + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + if (batt_dev->present) + tegra_get_battery_tech(&batt_tech, + NvRmPmuBatteryInst_Main); + else + batt_tech = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + + val->intval = batt_tech; + break; + + case POWER_SUPPLY_PROP_HEALTH: + if (batt_dev->present) + val->intval = POWER_SUPPLY_HEALTH_GOOD; + else + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_TEMP: + if (!batt_dev->present) { + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + return 0; + } + if (tegra_battery_data(NvRmPmuBatteryInst_Main)) + return -EINVAL; + + if (psp == POWER_SUPPLY_PROP_CAPACITY) + val->intval = batt_dev->BatteryLifePercent; + else if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW) + val->intval = batt_dev->batt_vol; + if (psp == POWER_SUPPLY_PROP_TEMP) + val->intval = batt_dev->batt_temp; + break; + + default: + return -EINVAL; + } + return 0; +} + +static void tegra_battery_poll_timer_func(unsigned long unused) +{ + power_supply_changed(&tegra_supplies[NvCharger_Type_Battery]); + power_supply_changed(&tegra_supplies[NvCharger_Type_USB]); + power_supply_changed(&tegra_supplies[NvCharger_Type_AC]); + + mod_timer(&(batt_dev->battery_poll_timer), + jiffies + msecs_to_jiffies(batt_dev->batt_status_poll_period)); +} + +static int tegra_battery_probe(struct platform_device *pdev) +{ + int i, rc; + + + batt_dev = kzalloc(sizeof(*batt_dev), GFP_KERNEL); + if (!batt_dev) { + return -ENOMEM; + } + memset(batt_dev, 0, sizeof(*batt_dev)); + + /* Assume battery is present at start */ + batt_dev->present = 1; + batt_dev->batt_id = 0; + batt_dev->charging_source = NvCharger_Type_AC; + batt_dev->charging_enabled = NvCharge_Control_Charging_Enable; + + for (i = 0; i < ARRAY_SIZE(tegra_supplies); i++) { + rc = power_supply_register(&pdev->dev, &tegra_supplies[i]); + if (rc) { + printk(KERN_ERR "Failed to register power supply\n"); + while (i--) + power_supply_unregister(&tegra_supplies[i]); + kfree(batt_dev); + return rc; + } + } + + printk(KERN_INFO "%s: battery driver registered\n", pdev->name); + + batt_dev->batt_status_poll_period = NVBATTERY_POLLING_INTERVAL; + setup_timer(&(batt_dev->battery_poll_timer), tegra_battery_poll_timer_func, 0); + mod_timer(&(batt_dev->battery_poll_timer), + jiffies + msecs_to_jiffies(batt_dev->batt_status_poll_period)); + + rc = device_create_file(&pdev->dev, &tegra_battery_attr); + if (rc) { + for (i = 0; i < ARRAY_SIZE(tegra_supplies); i++) { + power_supply_unregister(&tegra_supplies[i]); + } + + del_timer_sync(&(batt_dev->battery_poll_timer)); + + pr_err("tegra_battery_probe:device_create_file FAILED"); + return rc; + } + + return 0; +} + +static int tegra_battery_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tegra_supplies); i++) { + power_supply_unregister(&tegra_supplies[i]); + } + + if (batt_dev) { + device_remove_file(&pdev->dev, &tegra_battery_attr); + + del_timer_sync(&(batt_dev->battery_poll_timer)); + kfree(batt_dev); + batt_dev = NULL; + } + + return 0; +} + +static int tegra_battery_suspend(struct platform_device *dev, + pm_message_t state) +{ + /* Kill the Battery Polling timer */ + del_timer_sync(&batt_dev->battery_poll_timer); + return 0; +} + +static int tegra_battery_resume(struct platform_device *dev) +{ + /*Create Battery Polling timer */ + setup_timer(&batt_dev->battery_poll_timer, tegra_battery_poll_timer_func, 0); + mod_timer(&batt_dev->battery_poll_timer, + jiffies + msecs_to_jiffies(batt_dev->batt_status_poll_period)); + return 0; +} + +static struct platform_driver tegra_battery_driver = +{ + .probe = tegra_battery_probe, + .remove = tegra_battery_remove, + .suspend= tegra_battery_suspend, + .resume = tegra_battery_resume, + .driver = { + .name = "tegra_battery", + .owner = THIS_MODULE, + }, +}; + +static int __init tegra_battery_init(void) +{ + platform_driver_register(&tegra_battery_driver); + return 0; +} + +static void __exit tegra_battery_exit(void) +{ + platform_driver_unregister(&tegra_battery_driver); +} + +module_init(tegra_battery_init); +module_exit(tegra_battery_exit); +MODULE_DESCRIPTION("TEGRA Battery Driver"); |