From 9ad63986c606c60e2e916b1b96f22991f966d9cc Mon Sep 17 00:00:00 2001 From: Dima Zavin Date: Sun, 10 Jul 2011 16:01:15 -0700 Subject: pda_power: Add support for using otg transceiver events If the platform data sets the use_otg_notifier flag, the driver will now register an otg notifier callback and listen to transceiver events for AC/USB plug-in events instead. This would normally be used by not specifying is_xx_online callbacks and not specifying any irqs so the state machine is completely driven from OTG xceiver events. Signed-off-by: Dima Zavin Signed-off-by: Anton Vorontsov --- include/linux/pda_power.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'include') diff --git a/include/linux/pda_power.h b/include/linux/pda_power.h index c9e4d814ff77..2bb62bf296ac 100644 --- a/include/linux/pda_power.h +++ b/include/linux/pda_power.h @@ -35,6 +35,8 @@ struct pda_power_pdata { unsigned int polling_interval; /* msecs, default is 2000 */ unsigned long ac_max_uA; /* current to draw when on AC */ + + bool use_otg_notifier; }; #endif /* __PDA_POWER_H__ */ -- cgit v1.2.3 From 92de378b739115c8afaae5cd3f25159406bb9914 Mon Sep 17 00:00:00 2001 From: Philip Rakity Date: Fri, 25 Nov 2011 23:19:37 +0400 Subject: max8925_power: No temperature interrupts if temperature not connected Brownstone does not have temperature reading circuit hooked up. This leads to spurious interrupts. Allow the platform layer to indicate no temperature circuit and do not activate interrupts if no temperature control is set Signed-off-by: Philip Rakity Signed-off-by: Anton Vorontsov --- drivers/power/max8925_power.c | 12 ++++++++---- include/linux/mfd/max8925.h | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c index b16bd71f24fb..83b827cf004b 100644 --- a/drivers/power/max8925_power.c +++ b/drivers/power/max8925_power.c @@ -78,6 +78,7 @@ struct max8925_power_info { unsigned batt_detect:1; /* detecing MB by ID pin */ unsigned topoff_threshold:2; unsigned fast_charge:3; + unsigned no_temp_support:1; int (*set_charger) (int); }; @@ -116,7 +117,7 @@ static irqreturn_t max8925_charger_handler(int irq, void *data) case MAX8925_IRQ_VCHG_DC_F: info->ac_online = 0; __set_charger(info, 0); - dev_dbg(chip->dev, "Adapter is removal\n"); + dev_dbg(chip->dev, "Adapter removed\n"); break; case MAX8925_IRQ_VCHG_USB_R: info->usb_online = 1; @@ -126,7 +127,7 @@ static irqreturn_t max8925_charger_handler(int irq, void *data) case MAX8925_IRQ_VCHG_USB_F: info->usb_online = 0; __set_charger(info, 0); - dev_dbg(chip->dev, "USB is removal\n"); + dev_dbg(chip->dev, "USB removed\n"); break; case MAX8925_IRQ_VCHG_THM_OK_F: /* Battery is not ready yet */ @@ -369,8 +370,10 @@ static __devinit int max8925_init_charger(struct max8925_chip *chip, REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_OVP, "usb-ovp"); REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_F, "usb-remove"); REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_R, "usb-insert"); - REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_R, "batt-temp-in-range"); - REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_F, "batt-temp-out-range"); + if (!info->no_temp_support) { + REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_R, "batt-temp-in-range"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_F, "batt-temp-out-range"); + } REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_F, "vsys-high"); REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_R, "vsys-low"); REQUEST_IRQ(MAX8925_IRQ_VCHG_RST, "charger-reset"); @@ -477,6 +480,7 @@ static __devinit int max8925_power_probe(struct platform_device *pdev) info->topoff_threshold = pdata->topoff_threshold; info->fast_charge = pdata->fast_charge; info->set_charger = pdata->set_charger; + info->no_temp_support = pdata->no_temp_support; max8925_init_charger(chip, info); return 0; diff --git a/include/linux/mfd/max8925.h b/include/linux/mfd/max8925.h index 5259dfe8c585..69ec8f0bd490 100644 --- a/include/linux/mfd/max8925.h +++ b/include/linux/mfd/max8925.h @@ -223,6 +223,7 @@ struct max8925_power_pdata { unsigned batt_detect:1; unsigned topoff_threshold:2; unsigned fast_charge:3; /* charge current */ + unsigned no_temp_support:1; /* set if no temperature detect */ }; /* -- cgit v1.2.3 From 5ba1fa0ae288e93179d54e3c59b2241eb1709f0c Mon Sep 17 00:00:00 2001 From: Philip Rakity Date: Fri, 25 Nov 2011 23:24:03 +0400 Subject: max8925_power: Do not detect ac insert if handled by other code On brownstone rev 4 ac-insert detect is handled by vbus. allow the platform code to configure the disabling of insert by setting no_insert_detect. Signed-off-by: Philip Rakity Signed-off-by: Anton Vorontsov --- drivers/power/max8925_power.c | 8 ++++++-- include/linux/mfd/max8925.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c index 83b827cf004b..be2d563cb315 100644 --- a/drivers/power/max8925_power.c +++ b/drivers/power/max8925_power.c @@ -79,6 +79,7 @@ struct max8925_power_info { unsigned topoff_threshold:2; unsigned fast_charge:3; unsigned no_temp_support:1; + unsigned no_insert_detect:1; int (*set_charger) (int); }; @@ -365,8 +366,10 @@ static __devinit int max8925_init_charger(struct max8925_chip *chip, int ret; REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_OVP, "ac-ovp"); - REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove"); - REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert"); + if (!info->no_insert_detect) { + REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove"); + REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert"); + } REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_OVP, "usb-ovp"); REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_F, "usb-remove"); REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_R, "usb-insert"); @@ -481,6 +484,7 @@ static __devinit int max8925_power_probe(struct platform_device *pdev) info->fast_charge = pdata->fast_charge; info->set_charger = pdata->set_charger; info->no_temp_support = pdata->no_temp_support; + info->no_insert_detect = pdata->no_insert_detect; max8925_init_charger(chip, info); return 0; diff --git a/include/linux/mfd/max8925.h b/include/linux/mfd/max8925.h index 69ec8f0bd490..e742e044e2eb 100644 --- a/include/linux/mfd/max8925.h +++ b/include/linux/mfd/max8925.h @@ -224,6 +224,7 @@ struct max8925_power_pdata { unsigned topoff_threshold:2; unsigned fast_charge:3; /* charge current */ unsigned no_temp_support:1; /* set if no temperature detect */ + unsigned no_insert_detect:1; /* set if no ac insert detect */ }; /* -- cgit v1.2.3 From 72af5a4b9cc9c4527f2967e0283bee632237c26e Mon Sep 17 00:00:00 2001 From: Philip Rakity Date: Fri, 25 Nov 2011 23:11:06 +0400 Subject: max8925_power: Remove support for irq bits that do not exist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The max8925 cannot return usb status.  The bits       [MAX8925_IRQ_VCHG_USB_OVP] = {               .reg            = MAX8925_CHG_IRQ1,               .mask_reg       = MAX8925_CHG_IRQ1_MASK,               .offs           = 1 << 3,       },       [MAX8925_IRQ_VCHG_USB_F] =  {               .reg            = MAX8925_CHG_IRQ1,               .mask_reg       = MAX8925_CHG_IRQ1_MASK,               .offs           = 1 << 4,       },       [MAX8925_IRQ_VCHG_USB_R] = {               .reg            = MAX8925_CHG_IRQ1,               .mask_reg       = MAX8925_CHG_IRQ1_MASK,               .offs           = 1 << 5,       }, do not exist in the irq register. Signed-off-by: Philip Rakity Signed-off-by: Anton Vorontsov --- drivers/mfd/max8925-core.c | 15 --------------- drivers/power/max8925_power.c | 13 ------------- include/linux/mfd/max8925.h | 3 --- 3 files changed, 31 deletions(-) (limited to 'include') diff --git a/drivers/mfd/max8925-core.c b/drivers/mfd/max8925-core.c index e1e59c92f758..ca881efedf75 100644 --- a/drivers/mfd/max8925-core.c +++ b/drivers/mfd/max8925-core.c @@ -210,21 +210,6 @@ static struct max8925_irq_data max8925_irqs[] = { .mask_reg = MAX8925_CHG_IRQ1_MASK, .offs = 1 << 2, }, - [MAX8925_IRQ_VCHG_USB_OVP] = { - .reg = MAX8925_CHG_IRQ1, - .mask_reg = MAX8925_CHG_IRQ1_MASK, - .offs = 1 << 3, - }, - [MAX8925_IRQ_VCHG_USB_F] = { - .reg = MAX8925_CHG_IRQ1, - .mask_reg = MAX8925_CHG_IRQ1_MASK, - .offs = 1 << 4, - }, - [MAX8925_IRQ_VCHG_USB_R] = { - .reg = MAX8925_CHG_IRQ1, - .mask_reg = MAX8925_CHG_IRQ1_MASK, - .offs = 1 << 5, - }, [MAX8925_IRQ_VCHG_THM_OK_R] = { .reg = MAX8925_CHG_IRQ2, .mask_reg = MAX8925_CHG_IRQ2_MASK, diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c index be2d563cb315..cbc7a0b6da52 100644 --- a/drivers/power/max8925_power.c +++ b/drivers/power/max8925_power.c @@ -120,16 +120,6 @@ static irqreturn_t max8925_charger_handler(int irq, void *data) __set_charger(info, 0); dev_dbg(chip->dev, "Adapter removed\n"); break; - case MAX8925_IRQ_VCHG_USB_R: - info->usb_online = 1; - __set_charger(info, 1); - dev_dbg(chip->dev, "USB inserted\n"); - break; - case MAX8925_IRQ_VCHG_USB_F: - info->usb_online = 0; - __set_charger(info, 0); - dev_dbg(chip->dev, "USB removed\n"); - break; case MAX8925_IRQ_VCHG_THM_OK_F: /* Battery is not ready yet */ dev_dbg(chip->dev, "Battery temperature is out of range\n"); @@ -370,9 +360,6 @@ static __devinit int max8925_init_charger(struct max8925_chip *chip, REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove"); REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert"); } - REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_OVP, "usb-ovp"); - REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_F, "usb-remove"); - REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_R, "usb-insert"); if (!info->no_temp_support) { REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_R, "batt-temp-in-range"); REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_F, "batt-temp-out-range"); diff --git a/include/linux/mfd/max8925.h b/include/linux/mfd/max8925.h index e742e044e2eb..10aeaf8bfb94 100644 --- a/include/linux/mfd/max8925.h +++ b/include/linux/mfd/max8925.h @@ -167,9 +167,6 @@ enum { MAX8925_IRQ_VCHG_DC_OVP, MAX8925_IRQ_VCHG_DC_F, MAX8925_IRQ_VCHG_DC_R, - MAX8925_IRQ_VCHG_USB_OVP, - MAX8925_IRQ_VCHG_USB_F, - MAX8925_IRQ_VCHG_USB_R, MAX8925_IRQ_VCHG_THM_OK_R, MAX8925_IRQ_VCHG_THM_OK_F, MAX8925_IRQ_VCHG_SYSLOW_F, -- cgit v1.2.3 From e7a5f6d55991fb3b3214f435681ee2db96320395 Mon Sep 17 00:00:00 2001 From: Philip Rakity Date: Mon, 29 Aug 2011 09:32:04 -0700 Subject: max8925_power: Enable power change notifications The power core infrastructure allow external power change events to be passed to drivers what are listed in the supplied_to call back field. Enable this feature by allowing the supplied_to field to be passed to the driver. This feature will enable drivers named in the supplied_to field that have a external_power_changed callback to be notified when power was been turned on or off. Signed-off-by: Philip Rakity Signed-off-by: Anton Vorontsov --- drivers/power/max8925_power.c | 5 +++++ include/linux/mfd/max8925.h | 2 ++ 2 files changed, 7 insertions(+) (limited to 'include') diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c index cbc7a0b6da52..377d1e633b4a 100644 --- a/drivers/power/max8925_power.c +++ b/drivers/power/max8925_power.c @@ -441,6 +441,8 @@ static __devinit int max8925_power_probe(struct platform_device *pdev) info->ac.properties = max8925_ac_props; info->ac.num_properties = ARRAY_SIZE(max8925_ac_props); info->ac.get_property = max8925_ac_get_prop; + info->ac.supplied_to = pdata->supplied_to; + info->ac.num_supplicants = pdata->num_supplicants; ret = power_supply_register(&pdev->dev, &info->ac); if (ret) goto out; @@ -451,6 +453,9 @@ static __devinit int max8925_power_probe(struct platform_device *pdev) info->usb.properties = max8925_usb_props; info->usb.num_properties = ARRAY_SIZE(max8925_usb_props); info->usb.get_property = max8925_usb_get_prop; + info->usb.supplied_to = pdata->supplied_to; + info->usb.num_supplicants = pdata->num_supplicants; + ret = power_supply_register(&pdev->dev, &info->usb); if (ret) goto out_usb; diff --git a/include/linux/mfd/max8925.h b/include/linux/mfd/max8925.h index 10aeaf8bfb94..b8e6d9449086 100644 --- a/include/linux/mfd/max8925.h +++ b/include/linux/mfd/max8925.h @@ -222,6 +222,8 @@ struct max8925_power_pdata { unsigned fast_charge:3; /* charge current */ unsigned no_temp_support:1; /* set if no temperature detect */ unsigned no_insert_detect:1; /* set if no ac insert detect */ + char **supplied_to; + int num_supplicants; }; /* -- cgit v1.2.3 From 25a0bc2dfc2ea732f40af2dae52426ead66ae76e Mon Sep 17 00:00:00 2001 From: Jeremy Fitzhardinge Date: Wed, 7 Dec 2011 11:24:20 -0800 Subject: power_supply: add SCOPE attribute to power supplies This adds a "scope" attribute to a power_supply, which indicates how much of the system it powers. It appears in sysfs as "scope" or in the uevent file as POWER_SUPPLY_SCOPE=. There are presently three possible values: Unknown - unknown power topology System - the power supply powers the whole system Device - it powers a specific device, or tree of devices A power supply which doesn't have a "scope" attribute should be assumed to have "System" scope. In general, usermode should assume that loss of all System-scoped power supplies will power off the whole system, but any single one is sufficient to power the system. Signed-off-by: Jeremy Fitzhardinge Cc: Richard Hughes --- drivers/power/power_supply_sysfs.c | 6 ++++++ include/linux/power_supply.h | 7 +++++++ 2 files changed, 13 insertions(+) (limited to 'include') diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index e15d4c9d3988..21178ebfe51a 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -63,6 +63,9 @@ static ssize_t power_supply_show_property(struct device *dev, static char *capacity_level_text[] = { "Unknown", "Critical", "Low", "Normal", "High", "Full" }; + static char *scope_text[] = { + "Unknown", "System", "Device" + }; ssize_t ret = 0; struct power_supply *psy = dev_get_drvdata(dev); const ptrdiff_t off = attr - power_supply_attrs; @@ -95,6 +98,8 @@ static ssize_t power_supply_show_property(struct device *dev, return sprintf(buf, "%s\n", capacity_level_text[value.intval]); else if (off == POWER_SUPPLY_PROP_TYPE) return sprintf(buf, "%s\n", type_text[value.intval]); + else if (off == POWER_SUPPLY_PROP_SCOPE) + return sprintf(buf, "%s\n", scope_text[value.intval]); else if (off >= POWER_SUPPLY_PROP_MODEL_NAME) return sprintf(buf, "%s\n", value.strval); @@ -167,6 +172,7 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(time_to_full_now), POWER_SUPPLY_ATTR(time_to_full_avg), POWER_SUPPLY_ATTR(type), + POWER_SUPPLY_ATTR(scope), /* Properties of type `const char *' */ POWER_SUPPLY_ATTR(model_name), POWER_SUPPLY_ATTR(manufacturer), diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 204c18dfdc9e..040a7b08e7c7 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -74,6 +74,12 @@ enum { POWER_SUPPLY_CAPACITY_LEVEL_FULL, }; +enum { + POWER_SUPPLY_SCOPE_UNKNOWN = 0, + POWER_SUPPLY_SCOPE_SYSTEM, + POWER_SUPPLY_SCOPE_DEVICE, +}; + enum power_supply_property { /* Properties of type `int' */ POWER_SUPPLY_PROP_STATUS = 0, @@ -116,6 +122,7 @@ enum power_supply_property { POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */ + POWER_SUPPLY_PROP_SCOPE, /* Properties of type `const char *' */ POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, -- cgit v1.2.3 From 8351665195cec6d2b73cce8b66f02d6dde246a8e Mon Sep 17 00:00:00 2001 From: Jeremy Fitzhardinge Date: Wed, 7 Dec 2011 09:15:45 -0800 Subject: power_supply: allow a power supply to explicitly point to powered device If a power supply has a scope of "Device", then allow the power supply to indicate what device it actually powers. This is represented in the power supply's sysfs directory as a symlink named "powers", which points to the sysfs directory of the powered device. If the device has children, then the sub-devices are also powered by the same power supply. Signed-off-by: Jeremy Fitzhardinge Cc: Richard Hughes --- drivers/power/power_supply_core.c | 7 +++++++ include/linux/power_supply.h | 1 + 2 files changed, 8 insertions(+) (limited to 'include') diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c index 329b46b2327d..b10c121244e5 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -147,6 +147,12 @@ struct power_supply *power_supply_get_by_name(char *name) } EXPORT_SYMBOL_GPL(power_supply_get_by_name); +int power_supply_powers(struct power_supply *psy, struct device *dev) +{ + return sysfs_create_link_nowarn(&psy->dev->kobj, &dev->kobj, "powers"); +} +EXPORT_SYMBOL_GPL(power_supply_powers); + static void power_supply_dev_release(struct device *dev) { pr_debug("device: '%s': %s\n", dev_name(dev), __func__); @@ -202,6 +208,7 @@ EXPORT_SYMBOL_GPL(power_supply_register); void power_supply_unregister(struct power_supply *psy) { cancel_work_sync(&psy->changed_work); + sysfs_remove_link(&psy->dev->kobj, "powers"); power_supply_remove_triggers(psy); device_unregister(psy->dev); } diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 040a7b08e7c7..2e3c8279b3b0 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -218,6 +218,7 @@ static inline int power_supply_is_system_supplied(void) { return -ENOSYS; } extern int power_supply_register(struct device *parent, struct power_supply *psy); extern void power_supply_unregister(struct power_supply *psy); +extern int power_supply_powers(struct power_supply *psy, struct device *dev); /* For APM emulation, think legacy userspace. */ extern struct class *power_supply_class; -- cgit v1.2.3 From 3bb3dbbd56ea39e5537db8f8041ea95d28f16a7f Mon Sep 17 00:00:00 2001 From: Donggeun Kim Date: Tue, 27 Dec 2011 18:47:48 +0900 Subject: power_supply: Add initial Charger-Manager driver Because battery health monitoring should be done even when suspended, it needs to wake up and suspend periodically. Thus, userspace battery monitoring may incur too much overhead; every device and task is woken up periodically. Charger Manager uses suspend-again to provide in-suspend monitoring. This patch allows to monitor battery health in-suspend state. Signed-off-by: Donggeun Kim Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Signed-off-by: Anton Vorontsov --- Documentation/power/charger-manager.txt | 149 ++++++ drivers/power/Kconfig | 10 + drivers/power/Makefile | 1 + drivers/power/charger-manager.c | 779 ++++++++++++++++++++++++++++++++ include/linux/power/charger-manager.h | 130 ++++++ 5 files changed, 1069 insertions(+) create mode 100644 Documentation/power/charger-manager.txt create mode 100644 drivers/power/charger-manager.c create mode 100644 include/linux/power/charger-manager.h (limited to 'include') diff --git a/Documentation/power/charger-manager.txt b/Documentation/power/charger-manager.txt new file mode 100644 index 000000000000..081489f3db25 --- /dev/null +++ b/Documentation/power/charger-manager.txt @@ -0,0 +1,149 @@ +Charger Manager + (C) 2011 MyungJoo Ham , GPL + +Charger Manager provides in-kernel battery charger management that +requires temperature monitoring during suspend-to-RAM state +and where each battery may have multiple chargers attached and the userland +wants to look at the aggregated information of the multiple chargers. + +Charger Manager is a platform_driver with power-supply-class entries. +An instance of Charger Manager (a platform-device created with Charger-Manager) +represents an independent battery with chargers. If there are multiple +batteries with their own chargers acting independently in a system, +the system may need multiple instances of Charger Manager. + +1. Introduction +=============== + +Charger Manager supports the following: + +* Support for multiple chargers (e.g., a device with USB, AC, and solar panels) + A system may have multiple chargers (or power sources) and some of + they may be activated at the same time. Each charger may have its + own power-supply-class and each power-supply-class can provide + different information about the battery status. This framework + aggregates charger-related information from multiple sources and + shows combined information as a single power-supply-class. + +* Support for in suspend-to-RAM polling (with suspend_again callback) + While the battery is being charged and the system is in suspend-to-RAM, + we may need to monitor the battery health by looking at the ambient or + battery temperature. We can accomplish this by waking up the system + periodically. However, such a method wakes up devices unncessary for + monitoring the battery health and tasks, and user processes that are + supposed to be kept suspended. That, in turn, incurs unnecessary power + consumption and slow down charging process. Or even, such peak power + consumption can stop chargers in the middle of charging + (external power input < device power consumption), which not + only affects the charging time, but the lifespan of the battery. + + Charger Manager provides a function "cm_suspend_again" that can be + used as suspend_again callback of platform_suspend_ops. If the platform + requires tasks other than cm_suspend_again, it may implement its own + suspend_again callback that calls cm_suspend_again in the middle. + Normally, the platform will need to resume and suspend some devices + that are used by Charger Manager. + +2. Global Charger-Manager Data related with suspend_again +======================================================== +In order to setup Charger Manager with suspend-again feature +(in-suspend monitoring), the user should provide charger_global_desc +with setup_charger_manager(struct charger_global_desc *). +This charger_global_desc data for in-suspend monitoring is global +as the name suggests. Thus, the user needs to provide only once even +if there are multiple batteries. If there are multiple batteries, the +multiple instances of Charger Manager share the same charger_global_desc +and it will manage in-suspend monitoring for all instances of Charger Manager. + +The user needs to provide all the two entries properly in order to activate +in-suspend monitoring: + +struct charger_global_desc { + +char *rtc_name; + : The name of rtc (e.g., "rtc0") used to wakeup the system from + suspend for Charger Manager. The alarm interrupt (AIE) of the rtc + should be able to wake up the system from suspend. Charger Manager + saves and restores the alarm value and use the previously-defined + alarm if it is going to go off earlier than Charger Manager so that + Charger Manager does not interfere with previously-defined alarms. + +bool (*rtc_only_wakeup)(void); + : This callback should let CM know whether + the wakeup-from-suspend is caused only by the alarm of "rtc" in the + same struct. If there is any other wakeup source triggered the + wakeup, it should return false. If the "rtc" is the only wakeup + reason, it should return true. +}; + +3. How to setup suspend_again +============================= +Charger Manager provides a function "extern bool cm_suspend_again(void)". +When cm_suspend_again is called, it monitors every battery. The suspend_ops +callback of the system's platform_suspend_ops can call cm_suspend_again +function to know whether Charger Manager wants to suspend again or not. +If there are no other devices or tasks that want to use suspend_again +feature, the platform_suspend_ops may directly refer to cm_suspend_again +for its suspend_again callback. + +The cm_suspend_again() returns true (meaning "I want to suspend again") +if the system was woken up by Charger Manager and the polling +(in-suspend monitoring) results in "normal". + +4. Charger-Manager Data (struct charger_desc) +============================================= +For each battery charged independently from other batteries (if a series of +batteries are charged by a single charger, they are counted as one independent +battery), an instance of Charger Manager is attached to it. + +struct charger_desc { + +enum polling_modes polling_mode; + : CM_POLL_DISABLE: do not poll this battery. + CM_POLL_ALWAYS: always poll this battery. + CM_POLL_EXTERNAL_POWER_ONLY: poll this battery if and only if + an external power source is attached. + CM_POLL_CHARGING_ONLY: poll this battery if and only if the + battery is being charged. + +unsigned int polling_interval_ms; + : Required polling interval in ms. Charger Manager will poll + this battery every polling_interval_ms or more frequently. + +enum data_source battery_present; + CM_FUEL_GAUGE: get battery presence information from fuel gauge. + CM_CHARGER_STAT: get battery presence from chargers. + +char **psy_charger_stat; + : An array ending with NULL that has power-supply-class names of + chargers. Each power-supply-class should provide "PRESENT" (if + battery_present is "CM_CHARGER_STAT"), "ONLINE" (shows whether an + external power source is attached or not), and "STATUS" (shows whether + the battery is {"FULL" or not FULL} or {"FULL", "Charging", + "Discharging", "NotCharging"}). + +int num_charger_regulators; +struct regulator_bulk_data *charger_regulators; + : Regulators representing the chargers in the form for + regulator framework's bulk functions. + +char *psy_fuel_gauge; + : Power-supply-class name of the fuel gauge. + +int (*temperature_out_of_range)(int *mC); + : This callback returns 0 if the temperature is safe for charging, + a positive number if it is too hot to charge, and a negative number + if it is too cold to charge. With the variable mC, the callback returns + the temperature in 1/1000 of centigrade. +}; + +5. Other Considerations +======================= + +At the charger/battery-related events such as battery-pulled-out, +charger-pulled-out, charger-inserted, DCIN-over/under-voltage, charger-stopped, +and others critical to chargers, the system should be configured to wake up. +At least the following should wake up the system from a suspend: +a) charger-on/off b) external-power-in/out c) battery-in/out (while charging) + +It is usually accomplished by configuring the PMIC as a wakeup source. diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 57de051a74b3..363f4d1ae067 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -235,6 +235,16 @@ config CHARGER_GPIO This driver can be build as a module. If so, the module will be called gpio-charger. +config CHARGER_MANAGER + bool "Battery charger manager for multiple chargers" + depends on REGULATOR && RTC_CLASS + help + Say Y to enable charger-manager support, which allows multiple + chargers attached to a battery and multiple batteries attached to a + system. The charger-manager also can monitor charging status in + runtime and in suspend-to-RAM by waking up the system periodically + with help of suspend_again support. + config CHARGER_MAX8997 tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" depends on MFD_MAX8997 && REGULATOR_MAX8997 diff --git a/drivers/power/Makefile b/drivers/power/Makefile index b4af13dd8b66..d3b24e99acbe 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -36,5 +36,6 @@ obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o +obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c new file mode 100644 index 000000000000..727a259ea46c --- /dev/null +++ b/drivers/power/charger-manager.c @@ -0,0 +1,779 @@ +/* + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * MyungJoo Ham + * + * This driver enables to monitor battery health and control charger + * during suspend-to-mem. + * Charger manager depends on other devices. register this later than + * the depending devices. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +**/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for + * delayed works so that we can run delayed works with CM_JIFFIES_SMALL + * without any delays. + */ +#define CM_JIFFIES_SMALL (2) + +/* If y is valid (> 0) and smaller than x, do x = y */ +#define CM_MIN_VALID(x, y) x = (((y > 0) && ((x) > (y))) ? (y) : (x)) + +/* + * Regard CM_RTC_SMALL (sec) is small enough to ignore error in invoking + * rtc alarm. It should be 2 or larger + */ +#define CM_RTC_SMALL (2) + +#define UEVENT_BUF_SIZE 32 + +static LIST_HEAD(cm_list); +static DEFINE_MUTEX(cm_list_mtx); + +/* About in-suspend (suspend-again) monitoring */ +static struct rtc_device *rtc_dev; +/* + * Backup RTC alarm + * Save the wakeup alarm before entering suspend-to-RAM + */ +static struct rtc_wkalrm rtc_wkalarm_save; +/* Backup RTC alarm time in terms of seconds since 01-01-1970 00:00:00 */ +static unsigned long rtc_wkalarm_save_time; +static bool cm_suspended; +static bool cm_rtc_set; +static unsigned long cm_suspend_duration_ms; + +/* Global charger-manager description */ +static struct charger_global_desc *g_desc; /* init with setup_charger_manager */ + +/** + * is_batt_present - See if the battery presents in place. + * @cm: the Charger Manager representing the battery. + */ +static bool is_batt_present(struct charger_manager *cm) +{ + union power_supply_propval val; + bool present = false; + int i, ret; + + switch (cm->desc->battery_present) { + case CM_FUEL_GAUGE: + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_PRESENT, &val); + if (ret == 0 && val.intval) + present = true; + break; + case CM_CHARGER_STAT: + for (i = 0; cm->charger_stat[i]; i++) { + ret = cm->charger_stat[i]->get_property( + cm->charger_stat[i], + POWER_SUPPLY_PROP_PRESENT, &val); + if (ret == 0 && val.intval) { + present = true; + break; + } + } + break; + } + + return present; +} + +/** + * is_ext_pwr_online - See if an external power source is attached to charge + * @cm: the Charger Manager representing the battery. + * + * Returns true if at least one of the chargers of the battery has an external + * power source attached to charge the battery regardless of whether it is + * actually charging or not. + */ +static bool is_ext_pwr_online(struct charger_manager *cm) +{ + union power_supply_propval val; + bool online = false; + int i, ret; + + /* If at least one of them has one, it's yes. */ + for (i = 0; cm->charger_stat[i]; i++) { + ret = cm->charger_stat[i]->get_property( + cm->charger_stat[i], + POWER_SUPPLY_PROP_ONLINE, &val); + if (ret == 0 && val.intval) { + online = true; + break; + } + } + + return online; +} + +/** + * is_charging - Returns true if the battery is being charged. + * @cm: the Charger Manager representing the battery. + */ +static bool is_charging(struct charger_manager *cm) +{ + int i, ret; + bool charging = false; + union power_supply_propval val; + + /* If there is no battery, it cannot be charged */ + if (!is_batt_present(cm)) + return false; + + /* If at least one of the charger is charging, return yes */ + for (i = 0; cm->charger_stat[i]; i++) { + /* 1. The charger sholuld not be DISABLED */ + if (cm->emergency_stop) + continue; + if (!cm->charger_enabled) + continue; + + /* 2. The charger should be online (ext-power) */ + ret = cm->charger_stat[i]->get_property( + cm->charger_stat[i], + POWER_SUPPLY_PROP_ONLINE, &val); + if (ret) { + dev_warn(cm->dev, "Cannot read ONLINE value from %s.\n", + cm->desc->psy_charger_stat[i]); + continue; + } + if (val.intval == 0) + continue; + + /* + * 3. The charger should not be FULL, DISCHARGING, + * or NOT_CHARGING. + */ + ret = cm->charger_stat[i]->get_property( + cm->charger_stat[i], + POWER_SUPPLY_PROP_STATUS, &val); + if (ret) { + dev_warn(cm->dev, "Cannot read STATUS value from %s.\n", + cm->desc->psy_charger_stat[i]); + continue; + } + if (val.intval == POWER_SUPPLY_STATUS_FULL || + val.intval == POWER_SUPPLY_STATUS_DISCHARGING || + val.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) + continue; + + /* Then, this is charging. */ + charging = true; + break; + } + + return charging; +} + +/** + * is_polling_required - Return true if need to continue polling for this CM. + * @cm: the Charger Manager representing the battery. + */ +static bool is_polling_required(struct charger_manager *cm) +{ + switch (cm->desc->polling_mode) { + case CM_POLL_DISABLE: + return false; + case CM_POLL_ALWAYS: + return true; + case CM_POLL_EXTERNAL_POWER_ONLY: + return is_ext_pwr_online(cm); + case CM_POLL_CHARGING_ONLY: + return is_charging(cm); + default: + dev_warn(cm->dev, "Incorrect polling_mode (%d)\n", + cm->desc->polling_mode); + } + + return false; +} + +/** + * try_charger_enable - Enable/Disable chargers altogether + * @cm: the Charger Manager representing the battery. + * @enable: true: enable / false: disable + * + * Note that Charger Manager keeps the charger enabled regardless whether + * the charger is charging or not (because battery is full or no external + * power source exists) except when CM needs to disable chargers forcibly + * bacause of emergency causes; when the battery is overheated or too cold. + */ +static int try_charger_enable(struct charger_manager *cm, bool enable) +{ + int err = 0, i; + struct charger_desc *desc = cm->desc; + + /* Ignore if it's redundent command */ + if (enable && cm->charger_enabled) + return 0; + if (!enable && !cm->charger_enabled) + return 0; + + if (enable) { + if (cm->emergency_stop) + return -EAGAIN; + err = regulator_bulk_enable(desc->num_charger_regulators, + desc->charger_regulators); + } else { + /* + * Abnormal battery state - Stop charging forcibly, + * even if charger was enabled at the other places + */ + err = regulator_bulk_disable(desc->num_charger_regulators, + desc->charger_regulators); + + for (i = 0; i < desc->num_charger_regulators; i++) { + if (regulator_is_enabled( + desc->charger_regulators[i].consumer)) { + regulator_force_disable( + desc->charger_regulators[i].consumer); + dev_warn(cm->dev, + "Disable regulator(%s) forcibly.\n", + desc->charger_regulators[i].supply); + } + } + } + + if (!err) + cm->charger_enabled = enable; + + return err; +} + +/** + * uevent_notify - Let users know something has changed. + * @cm: the Charger Manager representing the battery. + * @event: the event string. + * + * If @event is null, it implies that uevent_notify is called + * by resume function. When called in the resume function, cm_suspended + * should be already reset to false in order to let uevent_notify + * notify the recent event during the suspend to users. While + * suspended, uevent_notify does not notify users, but tracks + * events so that uevent_notify can notify users later after resumed. + */ +static void uevent_notify(struct charger_manager *cm, const char *event) +{ + static char env_str[UEVENT_BUF_SIZE + 1] = ""; + static char env_str_save[UEVENT_BUF_SIZE + 1] = ""; + + if (cm_suspended) { + /* Nothing in suspended-event buffer */ + if (env_str_save[0] == 0) { + if (!strncmp(env_str, event, UEVENT_BUF_SIZE)) + return; /* status not changed */ + strncpy(env_str_save, event, UEVENT_BUF_SIZE); + return; + } + + if (!strncmp(env_str_save, event, UEVENT_BUF_SIZE)) + return; /* Duplicated. */ + else + strncpy(env_str_save, event, UEVENT_BUF_SIZE); + + return; + } + + if (event == NULL) { + /* No messages pending */ + if (!env_str_save[0]) + return; + + strncpy(env_str, env_str_save, UEVENT_BUF_SIZE); + kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE); + env_str_save[0] = 0; + + return; + } + + /* status not changed */ + if (!strncmp(env_str, event, UEVENT_BUF_SIZE)) + return; + + /* save the status and notify the update */ + strncpy(env_str, event, UEVENT_BUF_SIZE); + kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE); + + dev_info(cm->dev, event); +} + +/** + * _cm_monitor - Monitor the temperature and return true for exceptions. + * @cm: the Charger Manager representing the battery. + * + * Returns true if there is an event to notify for the battery. + * (True if the status of "emergency_stop" changes) + */ +static bool _cm_monitor(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + int temp = desc->temperature_out_of_range(&cm->last_temp_mC); + + dev_dbg(cm->dev, "monitoring (%2.2d.%3.3dC)\n", + cm->last_temp_mC / 1000, cm->last_temp_mC % 1000); + + /* It has been stopped or charging already */ + if (!!temp == !!cm->emergency_stop) + return false; + + if (temp) { + cm->emergency_stop = temp; + if (!try_charger_enable(cm, false)) { + if (temp > 0) + uevent_notify(cm, "OVERHEAT"); + else + uevent_notify(cm, "COLD"); + } + } else { + cm->emergency_stop = 0; + if (!try_charger_enable(cm, true)) + uevent_notify(cm, "CHARGING"); + } + + return true; +} + +/** + * cm_monitor - Monitor every battery. + * + * Returns true if there is an event to notify from any of the batteries. + * (True if the status of "emergency_stop" changes) + */ +static bool cm_monitor(void) +{ + bool stop = false; + struct charger_manager *cm; + + mutex_lock(&cm_list_mtx); + + list_for_each_entry(cm, &cm_list, entry) + stop = stop || _cm_monitor(cm); + + mutex_unlock(&cm_list_mtx); + + return stop; +} + +/** + * cm_setup_timer - For in-suspend monitoring setup wakeup alarm + * for suspend_again. + * + * Returns true if the alarm is set for Charger Manager to use. + * Returns false if + * cm_setup_timer fails to set an alarm, + * cm_setup_timer does not need to set an alarm for Charger Manager, + * or an alarm previously configured is to be used. + */ +static bool cm_setup_timer(void) +{ + struct charger_manager *cm; + unsigned int wakeup_ms = UINT_MAX; + bool ret = false; + + mutex_lock(&cm_list_mtx); + + list_for_each_entry(cm, &cm_list, entry) { + /* Skip if polling is not required for this CM */ + if (!is_polling_required(cm) && !cm->emergency_stop) + continue; + if (cm->desc->polling_interval_ms == 0) + continue; + CM_MIN_VALID(wakeup_ms, cm->desc->polling_interval_ms); + } + + mutex_unlock(&cm_list_mtx); + + if (wakeup_ms < UINT_MAX && wakeup_ms > 0) { + pr_info("Charger Manager wakeup timer: %u ms.\n", wakeup_ms); + if (rtc_dev) { + struct rtc_wkalrm tmp; + unsigned long time, now; + unsigned long add = DIV_ROUND_UP(wakeup_ms, 1000); + + /* + * Set alarm with the polling interval (wakeup_ms) + * except when rtc_wkalarm_save comes first. + * However, the alarm time should be NOW + + * CM_RTC_SMALL or later. + */ + tmp.enabled = 1; + rtc_read_time(rtc_dev, &tmp.time); + rtc_tm_to_time(&tmp.time, &now); + if (add < CM_RTC_SMALL) + add = CM_RTC_SMALL; + time = now + add; + + ret = true; + + if (rtc_wkalarm_save.enabled && + rtc_wkalarm_save_time && + rtc_wkalarm_save_time < time) { + if (rtc_wkalarm_save_time < now + CM_RTC_SMALL) + time = now + CM_RTC_SMALL; + else + time = rtc_wkalarm_save_time; + + /* The timer is not appointed by CM */ + ret = false; + } + + pr_info("Waking up after %lu secs.\n", + time - now); + + rtc_time_to_tm(time, &tmp.time); + rtc_set_alarm(rtc_dev, &tmp); + cm_suspend_duration_ms += wakeup_ms; + return ret; + } + } + + if (rtc_dev) + rtc_set_alarm(rtc_dev, &rtc_wkalarm_save); + return false; +} + +/** + * cm_suspend_again - Determine whether suspend again or not + * + * Returns true if the system should be suspended again + * Returns false if the system should be woken up + */ +bool cm_suspend_again(void) +{ + struct charger_manager *cm; + bool ret = false; + + if (!g_desc || !g_desc->rtc_only_wakeup || !g_desc->rtc_only_wakeup() || + !cm_rtc_set) + return false; + + if (cm_monitor()) + goto out; + + ret = true; + mutex_lock(&cm_list_mtx); + list_for_each_entry(cm, &cm_list, entry) { + if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) || + cm->status_save_batt != is_batt_present(cm)) + ret = false; + } + mutex_unlock(&cm_list_mtx); + + cm_rtc_set = cm_setup_timer(); +out: + /* It's about the time when the non-CM appointed timer goes off */ + if (rtc_wkalarm_save.enabled) { + unsigned long now; + struct rtc_time tmp; + + rtc_read_time(rtc_dev, &tmp); + rtc_tm_to_time(&tmp, &now); + + if (rtc_wkalarm_save_time && + now + CM_RTC_SMALL >= rtc_wkalarm_save_time) + return false; + } + return ret; +} +EXPORT_SYMBOL_GPL(cm_suspend_again); + +/** + * setup_charger_manager - initialize charger_global_desc data + * @gd: pointer to instance of charger_global_desc + */ +int setup_charger_manager(struct charger_global_desc *gd) +{ + if (!gd) + return -EINVAL; + + if (rtc_dev) + rtc_class_close(rtc_dev); + rtc_dev = NULL; + g_desc = NULL; + + if (!gd->rtc_only_wakeup) { + pr_err("The callback rtc_only_wakeup is not given.\n"); + return -EINVAL; + } + + if (gd->rtc_name) { + rtc_dev = rtc_class_open(gd->rtc_name); + if (IS_ERR_OR_NULL(rtc_dev)) { + rtc_dev = NULL; + /* Retry at probe. RTC may be not registered yet */ + } + } else { + pr_warn("No wakeup timer is given for charger manager." + "In-suspend monitoring won't work.\n"); + } + + g_desc = gd; + return 0; +} +EXPORT_SYMBOL_GPL(setup_charger_manager); + +static int charger_manager_probe(struct platform_device *pdev) +{ + struct charger_desc *desc = dev_get_platdata(&pdev->dev); + struct charger_manager *cm; + int ret = 0, i = 0; + + if (g_desc && !rtc_dev && g_desc->rtc_name) { + rtc_dev = rtc_class_open(g_desc->rtc_name); + if (IS_ERR_OR_NULL(rtc_dev)) { + rtc_dev = NULL; + dev_err(&pdev->dev, "Cannot get RTC %s.\n", + g_desc->rtc_name); + ret = -ENODEV; + goto err_alloc; + } + } + + if (!desc) { + dev_err(&pdev->dev, "No platform data (desc) found.\n"); + ret = -ENODEV; + goto err_alloc; + } + + cm = kzalloc(sizeof(struct charger_manager), GFP_KERNEL); + if (!cm) { + dev_err(&pdev->dev, "Cannot allocate memory.\n"); + ret = -ENOMEM; + goto err_alloc; + } + + /* Basic Values. Unspecified are Null or 0 */ + cm->dev = &pdev->dev; + cm->desc = kzalloc(sizeof(struct charger_desc), GFP_KERNEL); + if (!cm->desc) { + dev_err(&pdev->dev, "Cannot allocate memory.\n"); + ret = -ENOMEM; + goto err_alloc_desc; + } + memcpy(cm->desc, desc, sizeof(struct charger_desc)); + cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */ + + if (!desc->charger_regulators || desc->num_charger_regulators < 1) { + ret = -EINVAL; + dev_err(&pdev->dev, "charger_regulators undefined.\n"); + goto err_no_charger; + } + + if (!desc->psy_charger_stat || !desc->psy_charger_stat[0]) { + dev_err(&pdev->dev, "No power supply defined.\n"); + ret = -EINVAL; + goto err_no_charger_stat; + } + + /* Counting index only */ + while (desc->psy_charger_stat[i]) + i++; + + cm->charger_stat = kzalloc(sizeof(struct power_supply *) * (i + 1), + GFP_KERNEL); + if (!cm->charger_stat) { + ret = -ENOMEM; + goto err_no_charger_stat; + } + + for (i = 0; desc->psy_charger_stat[i]; i++) { + cm->charger_stat[i] = power_supply_get_by_name( + desc->psy_charger_stat[i]); + if (!cm->charger_stat[i]) { + dev_err(&pdev->dev, "Cannot find power supply " + "\"%s\"\n", + desc->psy_charger_stat[i]); + ret = -ENODEV; + goto err_chg_stat; + } + } + + cm->fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge); + if (!cm->fuel_gauge) { + dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n", + desc->psy_fuel_gauge); + ret = -ENODEV; + goto err_chg_stat; + } + + if (desc->polling_interval_ms == 0 || + msecs_to_jiffies(desc->polling_interval_ms) <= CM_JIFFIES_SMALL) { + dev_err(&pdev->dev, "polling_interval_ms is too small\n"); + ret = -EINVAL; + goto err_chg_stat; + } + + if (!desc->temperature_out_of_range) { + dev_err(&pdev->dev, "there is no temperature_out_of_range\n"); + ret = -EINVAL; + goto err_chg_stat; + } + + platform_set_drvdata(pdev, cm); + + ret = regulator_bulk_get(&pdev->dev, desc->num_charger_regulators, + desc->charger_regulators); + if (ret) { + dev_err(&pdev->dev, "Cannot get charger regulators.\n"); + goto err_chg_stat; + } + + ret = try_charger_enable(cm, true); + if (ret) { + dev_err(&pdev->dev, "Cannot enable charger regulators\n"); + goto err_chg_enable; + } + + /* Add to the list */ + mutex_lock(&cm_list_mtx); + list_add(&cm->entry, &cm_list); + mutex_unlock(&cm_list_mtx); + + return 0; + +err_chg_enable: + if (desc->charger_regulators) + regulator_bulk_free(desc->num_charger_regulators, + desc->charger_regulators); +err_chg_stat: + kfree(cm->charger_stat); +err_no_charger_stat: +err_no_charger: + kfree(cm->desc); +err_alloc_desc: + kfree(cm); +err_alloc: + return ret; +} + +static int __devexit charger_manager_remove(struct platform_device *pdev) +{ + struct charger_manager *cm = platform_get_drvdata(pdev); + struct charger_desc *desc = cm->desc; + + /* Remove from the list */ + mutex_lock(&cm_list_mtx); + list_del(&cm->entry); + mutex_unlock(&cm_list_mtx); + + if (desc->charger_regulators) + regulator_bulk_free(desc->num_charger_regulators, + desc->charger_regulators); + kfree(cm->charger_stat); + kfree(cm->desc); + kfree(cm); + + return 0; +} + +const struct platform_device_id charger_manager_id[] = { + { "charger-manager", 0 }, + { }, +}; + +static int cm_suspend_prepare(struct device *dev) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, + dev); + struct charger_manager *cm = platform_get_drvdata(pdev); + + if (!cm_suspended) { + if (rtc_dev) { + struct rtc_time tmp; + unsigned long now; + + rtc_read_alarm(rtc_dev, &rtc_wkalarm_save); + rtc_read_time(rtc_dev, &tmp); + + if (rtc_wkalarm_save.enabled) { + rtc_tm_to_time(&rtc_wkalarm_save.time, + &rtc_wkalarm_save_time); + rtc_tm_to_time(&tmp, &now); + if (now > rtc_wkalarm_save_time) + rtc_wkalarm_save_time = 0; + } else { + rtc_wkalarm_save_time = 0; + } + } + cm_suspended = true; + } + + cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm); + cm->status_save_batt = is_batt_present(cm); + + if (!cm_rtc_set) { + cm_suspend_duration_ms = 0; + cm_rtc_set = cm_setup_timer(); + } + + return 0; +} + +static void cm_suspend_complete(struct device *dev) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, + dev); + struct charger_manager *cm = platform_get_drvdata(pdev); + + if (cm_suspended) { + if (rtc_dev) { + struct rtc_wkalrm tmp; + + rtc_read_alarm(rtc_dev, &tmp); + rtc_wkalarm_save.pending = tmp.pending; + rtc_set_alarm(rtc_dev, &rtc_wkalarm_save); + } + cm_suspended = false; + cm_rtc_set = false; + } + + uevent_notify(cm, NULL); +} + +static const struct dev_pm_ops charger_manager_pm = { + .prepare = cm_suspend_prepare, + .complete = cm_suspend_complete, +}; + +static struct platform_driver charger_manager_driver = { + .driver = { + .name = "charger-manager", + .owner = THIS_MODULE, + .pm = &charger_manager_pm, + }, + .probe = charger_manager_probe, + .remove = __devexit_p(charger_manager_remove), + .id_table = charger_manager_id, +}; + +static int __init charger_manager_init(void) +{ + return platform_driver_register(&charger_manager_driver); +} +late_initcall(charger_manager_init); + +static void __exit charger_manager_cleanup(void) +{ + platform_driver_unregister(&charger_manager_driver); +} +module_exit(charger_manager_cleanup); + +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_DESCRIPTION("Charger Manager"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("charger-manager"); diff --git a/include/linux/power/charger-manager.h b/include/linux/power/charger-manager.h new file mode 100644 index 000000000000..102c5b3f3325 --- /dev/null +++ b/include/linux/power/charger-manager.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * MyungJoo.Ham + * + * Charger Manager. + * This framework enables to control and multiple chargers and to + * monitor charging even in the context of suspend-to-RAM with + * an interface combining the chargers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +**/ + +#ifndef _CHARGER_MANAGER_H +#define _CHARGER_MANAGER_H + +#include + +enum data_source { + CM_FUEL_GAUGE, + CM_CHARGER_STAT, +}; + +enum polling_modes { + CM_POLL_DISABLE = 0, + CM_POLL_ALWAYS, + CM_POLL_EXTERNAL_POWER_ONLY, + CM_POLL_CHARGING_ONLY, +}; + +/** + * struct charger_global_desc + * @rtc_name: the name of RTC used to wake up the system from suspend. + * @rtc_only_wakeup: + * If the system is woken up by waekup-sources other than the RTC or + * callbacks, Charger Manager should recognize with + * rtc_only_wakeup() returning false. + * If the RTC given to CM is the only wakeup reason, + * rtc_only_wakeup should return true. + */ +struct charger_global_desc { + char *rtc_name; + + bool (*rtc_only_wakeup)(void); +}; + +/** + * struct charger_desc + * @polling_mode: + * Determine which polling mode will be used + * @polling_interval_ms: interval in millisecond at which + * charger manager will monitor battery health + * @battery_present: + * Specify where information for existance of battery can be obtained + * @psy_charger_stat: the names of power-supply for chargers + * @num_charger_regulator: the number of entries in charger_regulators + * @charger_regulators: array of regulator_bulk_data for chargers + * @psy_fuel_gauge: the name of power-supply for fuel gauge + * @temperature_out_of_range: + * Determine whether the status is overheat or cold or normal. + * return_value > 0: overheat + * return_value == 0: normal + * return_value < 0: cold + */ +struct charger_desc { + enum polling_modes polling_mode; + unsigned int polling_interval_ms; + + enum data_source battery_present; + + char **psy_charger_stat; + + int num_charger_regulators; + struct regulator_bulk_data *charger_regulators; + + char *psy_fuel_gauge; + + int (*temperature_out_of_range)(int *mC); +}; + +#define PSY_NAME_MAX 30 + +/** + * struct charger_manager + * @entry: entry for list + * @dev: device pointer + * @desc: instance of charger_desc + * @fuel_gauge: power_supply for fuel gauge + * @charger_stat: array of power_supply for chargers + * @charger_enabled: the state of charger + * @emergency_stop: + * When setting true, stop charging + * @last_temp_mC: the measured temperature in milli-Celsius + * @status_save_ext_pwr_inserted: + * saved status of external power before entering suspend-to-RAM + * @status_save_batt: + * saved status of battery before entering suspend-to-RAM + */ +struct charger_manager { + struct list_head entry; + struct device *dev; + struct charger_desc *desc; + + struct power_supply *fuel_gauge; + struct power_supply **charger_stat; + + bool charger_enabled; + + int emergency_stop; + int last_temp_mC; + + bool status_save_ext_pwr_inserted; + bool status_save_batt; +}; + +#ifdef CONFIG_CHARGER_MANAGER +extern int setup_charger_manager(struct charger_global_desc *gd); +extern bool cm_suspend_again(void); +#else +static void __maybe_unused setup_charger_manager(struct charger_global_desc *gd) +{ } + +static bool __maybe_unused cm_suspend_again(void) +{ + return false; +} +#endif + +#endif /* _CHARGER_MANAGER_H */ -- cgit v1.2.3 From ad3d13eee78ec44194bf919a37e2f711e53cbdf0 Mon Sep 17 00:00:00 2001 From: Donggeun Kim Date: Tue, 27 Dec 2011 18:47:49 +0900 Subject: power_supply: Charger-Manager: Add properties for power-supply-class Charger Manager provides power-supply-class aggregating information from multiple chargers and a fuel-gauge. Signed-off-by: Donggeun Kim Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Signed-off-by: Anton Vorontsov --- Documentation/power/charger-manager.txt | 14 ++ drivers/power/charger-manager.c | 295 +++++++++++++++++++++++++++++++- include/linux/power/charger-manager.h | 17 ++ 3 files changed, 325 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/Documentation/power/charger-manager.txt b/Documentation/power/charger-manager.txt index 081489f3db25..fdcca991df30 100644 --- a/Documentation/power/charger-manager.txt +++ b/Documentation/power/charger-manager.txt @@ -98,6 +98,11 @@ battery), an instance of Charger Manager is attached to it. struct charger_desc { +char *psy_name; + : The power-supply-class name of the battery. Default is + "battery" if psy_name is NULL. Users can access the psy entries + at "/sys/class/power_supply/[psy_name]/". + enum polling_modes polling_mode; : CM_POLL_DISABLE: do not poll this battery. CM_POLL_ALWAYS: always poll this battery. @@ -106,6 +111,12 @@ enum polling_modes polling_mode; CM_POLL_CHARGING_ONLY: poll this battery if and only if the battery is being charged. +unsigned int fullbatt_uV; + : If specified with a non-zero value, Charger Manager assumes + that the battery is full (capacity = 100) if the battery is not being + charged and the battery voltage is equal to or greater than + fullbatt_uV. + unsigned int polling_interval_ms; : Required polling interval in ms. Charger Manager will poll this battery every polling_interval_ms or more frequently. @@ -131,10 +142,13 @@ char *psy_fuel_gauge; : Power-supply-class name of the fuel gauge. int (*temperature_out_of_range)(int *mC); +bool measure_battery_temp; : This callback returns 0 if the temperature is safe for charging, a positive number if it is too hot to charge, and a negative number if it is too cold to charge. With the variable mC, the callback returns the temperature in 1/1000 of centigrade. + The source of temperature can be battery or ambient one according to + the value of measure_battery_temp. }; 5. Other Considerations diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 727a259ea46c..0378d019efae 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -121,6 +121,32 @@ static bool is_ext_pwr_online(struct charger_manager *cm) return online; } +/** + * get_batt_uV - Get the voltage level of the battery + * @cm: the Charger Manager representing the battery. + * @uV: the voltage level returned. + * + * Returns 0 if there is no error. + * Returns a negative value on error. + */ +static int get_batt_uV(struct charger_manager *cm, int *uV) +{ + union power_supply_propval val; + int ret; + + if (cm->fuel_gauge) + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &val); + else + return -ENODEV; + + if (ret) + return ret; + + *uV = val.intval; + return 0; +} + /** * is_charging - Returns true if the battery is being charged. * @cm: the Charger Manager representing the battery. @@ -369,6 +395,208 @@ static bool cm_monitor(void) return stop; } +static int charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct charger_manager *cm = container_of(psy, + struct charger_manager, charger_psy); + struct charger_desc *desc = cm->desc; + int i, ret = 0, uV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (is_charging(cm)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (is_ext_pwr_online(cm)) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_HEALTH: + if (cm->emergency_stop > 0) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (cm->emergency_stop < 0) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + if (is_batt_present(cm)) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = get_batt_uV(cm, &i); + val->intval = i; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CURRENT_NOW, val); + break; + case POWER_SUPPLY_PROP_TEMP: + /* in thenth of centigrade */ + if (cm->last_temp_mC == INT_MIN) + desc->temperature_out_of_range(&cm->last_temp_mC); + val->intval = cm->last_temp_mC / 100; + if (!desc->measure_battery_temp) + ret = -ENODEV; + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + /* in thenth of centigrade */ + if (cm->last_temp_mC == INT_MIN) + desc->temperature_out_of_range(&cm->last_temp_mC); + val->intval = cm->last_temp_mC / 100; + if (desc->measure_battery_temp) + ret = -ENODEV; + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (!cm->fuel_gauge) { + ret = -ENODEV; + break; + } + + if (!is_batt_present(cm)) { + /* There is no battery. Assume 100% */ + val->intval = 100; + break; + } + + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CAPACITY, val); + if (ret) + break; + + if (val->intval > 100) { + val->intval = 100; + break; + } + if (val->intval < 0) + val->intval = 0; + + /* Do not adjust SOC when charging: voltage is overrated */ + if (is_charging(cm)) + break; + + /* + * If the capacity value is inconsistent, calibrate it base on + * the battery voltage values and the thresholds given as desc + */ + ret = get_batt_uV(cm, &uV); + if (ret) { + /* Voltage information not available. No calibration */ + ret = 0; + break; + } + + if (desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV && + !is_charging(cm)) { + val->intval = 100; + break; + } + + break; + case POWER_SUPPLY_PROP_ONLINE: + if (is_ext_pwr_online(cm)) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (cm->fuel_gauge) { + if (cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_FULL, val) == 0) + break; + } + + if (is_ext_pwr_online(cm)) { + /* Not full if it's charging. */ + if (is_charging(cm)) { + val->intval = 0; + break; + } + /* + * Full if it's powered but not charging andi + * not forced stop by emergency + */ + if (!cm->emergency_stop) { + val->intval = 1; + break; + } + } + + /* Full if it's over the fullbatt voltage */ + ret = get_batt_uV(cm, &uV); + if (!ret && desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV && + !is_charging(cm)) { + val->intval = 1; + break; + } + + /* Full if the cap is 100 */ + if (cm->fuel_gauge) { + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CAPACITY, val); + if (!ret && val->intval >= 100 && !is_charging(cm)) { + val->intval = 1; + break; + } + } + + val->intval = 0; + ret = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + if (is_charging(cm)) { + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_NOW, + val); + if (ret) { + val->intval = 1; + ret = 0; + } else { + /* If CHARGE_NOW is supplied, use it */ + val->intval = (val->intval > 0) ? + val->intval : 1; + } + } else { + val->intval = 0; + } + break; + default: + return -EINVAL; + } + return ret; +} + +#define NUM_CHARGER_PSY_OPTIONAL (4) +static enum power_supply_property default_charger_props[] = { + /* Guaranteed to provide */ + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CHARGE_FULL, + /* + * Optional properties are: + * POWER_SUPPLY_PROP_CHARGE_NOW, + * POWER_SUPPLY_PROP_CURRENT_NOW, + * POWER_SUPPLY_PROP_TEMP, and + * POWER_SUPPLY_PROP_TEMP_AMBIENT, + */ +}; + +static struct power_supply psy_default = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = default_charger_props, + .num_properties = ARRAY_SIZE(default_charger_props), + .get_property = charger_get_property, +}; + /** * cm_setup_timer - For in-suspend monitoring setup wakeup alarm * for suspend_again. @@ -532,6 +760,7 @@ static int charger_manager_probe(struct platform_device *pdev) struct charger_desc *desc = dev_get_platdata(&pdev->dev); struct charger_manager *cm; int ret = 0, i = 0; + union power_supply_propval val; if (g_desc && !rtc_dev && g_desc->rtc_name) { rtc_dev = rtc_class_open(g_desc->rtc_name); @@ -626,11 +855,68 @@ static int charger_manager_probe(struct platform_device *pdev) platform_set_drvdata(pdev, cm); + memcpy(&cm->charger_psy, &psy_default, + sizeof(psy_default)); + if (!desc->psy_name) { + strncpy(cm->psy_name_buf, psy_default.name, + PSY_NAME_MAX); + } else { + strncpy(cm->psy_name_buf, desc->psy_name, PSY_NAME_MAX); + } + cm->charger_psy.name = cm->psy_name_buf; + + /* Allocate for psy properties because they may vary */ + cm->charger_psy.properties = kzalloc(sizeof(enum power_supply_property) + * (ARRAY_SIZE(default_charger_props) + + NUM_CHARGER_PSY_OPTIONAL), + GFP_KERNEL); + if (!cm->charger_psy.properties) { + dev_err(&pdev->dev, "Cannot allocate for psy properties.\n"); + ret = -ENOMEM; + goto err_chg_stat; + } + memcpy(cm->charger_psy.properties, default_charger_props, + sizeof(enum power_supply_property) * + ARRAY_SIZE(default_charger_props)); + cm->charger_psy.num_properties = psy_default.num_properties; + + /* Find which optional psy-properties are available */ + if (!cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_NOW, &val)) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_CHARGE_NOW; + cm->charger_psy.num_properties++; + } + if (!cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CURRENT_NOW, + &val)) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_CURRENT_NOW; + cm->charger_psy.num_properties++; + } + if (!desc->measure_battery_temp) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_TEMP_AMBIENT; + cm->charger_psy.num_properties++; + } + if (desc->measure_battery_temp) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_TEMP; + cm->charger_psy.num_properties++; + } + + ret = power_supply_register(NULL, &cm->charger_psy); + if (ret) { + dev_err(&pdev->dev, "Cannot register charger-manager with" + " name \"%s\".\n", cm->charger_psy.name); + goto err_register; + } + ret = regulator_bulk_get(&pdev->dev, desc->num_charger_regulators, desc->charger_regulators); if (ret) { dev_err(&pdev->dev, "Cannot get charger regulators.\n"); - goto err_chg_stat; + goto err_bulk_get; } ret = try_charger_enable(cm, true); @@ -650,6 +936,10 @@ err_chg_enable: if (desc->charger_regulators) regulator_bulk_free(desc->num_charger_regulators, desc->charger_regulators); +err_bulk_get: + power_supply_unregister(&cm->charger_psy); +err_register: + kfree(cm->charger_psy.properties); err_chg_stat: kfree(cm->charger_stat); err_no_charger_stat: @@ -674,6 +964,9 @@ static int __devexit charger_manager_remove(struct platform_device *pdev) if (desc->charger_regulators) regulator_bulk_free(desc->num_charger_regulators, desc->charger_regulators); + + power_supply_unregister(&cm->charger_psy); + kfree(cm->charger_psy.properties); kfree(cm->charger_stat); kfree(cm->desc); kfree(cm); diff --git a/include/linux/power/charger-manager.h b/include/linux/power/charger-manager.h index 102c5b3f3325..4f75e531c112 100644 --- a/include/linux/power/charger-manager.h +++ b/include/linux/power/charger-manager.h @@ -47,8 +47,12 @@ struct charger_global_desc { /** * struct charger_desc + * @psy_name: the name of power-supply-class for charger manager * @polling_mode: * Determine which polling mode will be used + * @fullbatt_uV: voltage in microvolt + * If it is not being charged and VBATT >= fullbatt_uV, + * it is assumed to be full. * @polling_interval_ms: interval in millisecond at which * charger manager will monitor battery health * @battery_present: @@ -62,11 +66,18 @@ struct charger_global_desc { * return_value > 0: overheat * return_value == 0: normal * return_value < 0: cold + * @measure_battery_temp: + * true: measure battery temperature + * false: measure ambient temperature */ struct charger_desc { + char *psy_name; + enum polling_modes polling_mode; unsigned int polling_interval_ms; + unsigned int fullbatt_uV; + enum data_source battery_present; char **psy_charger_stat; @@ -77,6 +88,7 @@ struct charger_desc { char *psy_fuel_gauge; int (*temperature_out_of_range)(int *mC); + bool measure_battery_temp; }; #define PSY_NAME_MAX 30 @@ -92,6 +104,8 @@ struct charger_desc { * @emergency_stop: * When setting true, stop charging * @last_temp_mC: the measured temperature in milli-Celsius + * @psy_name_buf: the name of power-supply-class for charger manager + * @charger_psy: power_supply for charger manager * @status_save_ext_pwr_inserted: * saved status of external power before entering suspend-to-RAM * @status_save_batt: @@ -110,6 +124,9 @@ struct charger_manager { int emergency_stop; int last_temp_mC; + char psy_name_buf[PSY_NAME_MAX + 1]; + struct power_supply charger_psy; + bool status_save_ext_pwr_inserted; bool status_save_batt; }; -- cgit v1.2.3 From 9b8872273af6983b246252a6508fa7cf34c69d6e Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Wed, 30 Nov 2011 23:08:33 -0800 Subject: power_supply: Add "unknown" in power supply type For the default value of power supply type, "unknown" is added. With default prop value, supply type property can be displayed as default - "Unknown". Signed-off-by: Milo(Woogyom) Kim Signed-off-by: Anton Vorontsov --- drivers/power/power_supply_sysfs.c | 2 +- include/linux/power_supply.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index 58cc4906d216..939e2e432553 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -42,7 +42,7 @@ static ssize_t power_supply_show_property(struct device *dev, struct device_attribute *attr, char *buf) { static char *type_text[] = { - "Battery", "UPS", "Mains", "USB", + "Unknown", "Battery", "UPS", "Mains", "USB", "USB_DCP", "USB_CDP", "USB_ACA" }; static char *status_text[] = { diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 204c18dfdc9e..9c83e04f6a43 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -123,7 +123,8 @@ enum power_supply_property { }; enum power_supply_type { - POWER_SUPPLY_TYPE_BATTERY = 0, + POWER_SUPPLY_TYPE_UNKNOWN = 0, + POWER_SUPPLY_TYPE_BATTERY, POWER_SUPPLY_TYPE_UPS, POWER_SUPPLY_TYPE_MAINS, POWER_SUPPLY_TYPE_USB, /* Standard Downstream Port */ -- cgit v1.2.3 From 620b2736696743fc785b2fc63dbc0fe69cbfe3a7 Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Wed, 7 Sep 2011 01:56:14 -0700 Subject: lp8727_charger: Add header file Oops, forgot to 'git add' it. [AV] Signed-off-by: Woogyom Kim Signed-off-by: Anton Vorontsov --- include/linux/lp8727.h | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100755 include/linux/lp8727.h (limited to 'include') diff --git a/include/linux/lp8727.h b/include/linux/lp8727.h new file mode 100755 index 000000000000..3ec558959a11 --- /dev/null +++ b/include/linux/lp8727.h @@ -0,0 +1,54 @@ +/* + * lp8727.h - Driver for LP8727 Micro/Mini USB IC with intergrated charger + * + * Copyright (C) 2011 National Semiconductor + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _LP8727_H +#define _LP8727_H + +enum lp8727_eoc_level { + EOC_5P, + EOC_10P, + EOC_16P, + EOC_20P, + EOC_25P, + EOC_33P, + EOC_50P, +}; + +enum lp8727_ichg { + ICHG_90mA, + ICHG_100mA, + ICHG_400mA, + ICHG_450mA, + ICHG_500mA, + ICHG_600mA, + ICHG_700mA, + ICHG_800mA, + ICHG_900mA, + ICHG_1000mA, +}; + +struct lp8727_chg_param { + /* end of charge level setting */ + enum lp8727_eoc_level eoc_level; + /* charging current */ + enum lp8727_ichg ichg; +}; + +struct lp8727_platform_data { + u8(*get_batt_present) (void); + u16(*get_batt_level) (void); + u8(*get_batt_capacity) (void); + u8(*get_batt_temp) (void); + struct lp8727_chg_param ac; + struct lp8727_chg_param usb; +}; + +#endif -- cgit v1.2.3 From e57b432d0c91a4c472b2826b21524bdbbf2688d1 Mon Sep 17 00:00:00 2001 From: "Milo(Woogyom) Kim" Date: Wed, 4 Jan 2012 16:23:11 +0400 Subject: lp8727_charger: Some minor fixes for the header Pointer coding style changes : add space between return type and function pointer ex) u8(*get_batt_present) (void) -> u8 (*get_batt_present) (void) Signed-off-by: Woogyom Kim Signed-off-by: Anton Vorontsov --- include/linux/lp8727.h | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/linux/lp8727.h b/include/linux/lp8727.h index 3ec558959a11..d21fa2865bf4 100755 --- a/include/linux/lp8727.h +++ b/include/linux/lp8727.h @@ -1,12 +1,9 @@ /* - * lp8727.h - Driver for LP8727 Micro/Mini USB IC with intergrated charger - * * Copyright (C) 2011 National Semiconductor * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. - * */ #ifndef _LP8727_H @@ -43,10 +40,10 @@ struct lp8727_chg_param { }; struct lp8727_platform_data { - u8(*get_batt_present) (void); - u16(*get_batt_level) (void); - u8(*get_batt_capacity) (void); - u8(*get_batt_temp) (void); + u8 (*get_batt_present)(void); + u16 (*get_batt_level)(void); + u8 (*get_batt_capacity)(void); + u8 (*get_batt_temp)(void); struct lp8727_chg_param ac; struct lp8727_chg_param usb; }; -- cgit v1.2.3 From c78f2b64963654419a8cd3b7e264251860e9f9eb Mon Sep 17 00:00:00 2001 From: Rhyland Klein Date: Mon, 5 Dec 2011 17:50:45 -0800 Subject: bq20z75: Rename to sbs-battery This driver for the bq20z75 implemented the register spec defined by the SBS standard. As this is not unique to this the TI part this was originally written for, we can generalize this driver to show its support for any SBS compliant battery. Signed-off-by: Rhyland Klein Signed-off-by: Anton Vorontsov --- drivers/power/Kconfig | 8 +- drivers/power/Makefile | 2 +- drivers/power/bq20z75.c | 871 -------------------------------------- drivers/power/sbs-battery.c | 871 ++++++++++++++++++++++++++++++++++++++ include/linux/power/bq20z75.h | 42 -- include/linux/power/sbs-battery.h | 42 ++ 6 files changed, 918 insertions(+), 918 deletions(-) delete mode 100644 drivers/power/bq20z75.c create mode 100644 drivers/power/sbs-battery.c delete mode 100644 include/linux/power/bq20z75.h create mode 100644 include/linux/power/sbs-battery.h (limited to 'include') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 3bd2ed86fea2..e24485f35384 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -116,12 +116,12 @@ config BATTERY_WM97XX help Say Y to enable support for battery measured by WM97xx aux port. -config BATTERY_BQ20Z75 - tristate "TI BQ20z75 gas gauge" +config BATTERY_SBS + tristate "SBS Compliant gas gauge" depends on I2C help - Say Y to include support for TI BQ20z75 SBS-compliant - gas gauge and protection IC. + Say Y to include support for SBS battery driver for SBS-compliant + gas gauges. config BATTERY_BQ27x00 tristate "BQ27x00 battery driver" diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 9a78b1dd570b..9c3bbf76a2bf 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -22,7 +22,7 @@ obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o -obj-$(CONFIG_BATTERY_BQ20Z75) += bq20z75.o +obj-$(CONFIG_BATTERY_SBS) += sbs-battery.o obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o diff --git a/drivers/power/bq20z75.c b/drivers/power/bq20z75.c deleted file mode 100644 index ce95ff791016..000000000000 --- a/drivers/power/bq20z75.c +++ /dev/null @@ -1,871 +0,0 @@ -/* - * Gas Gauge driver for TI's BQ20Z75 - * - * Copyright (c) 2010, 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -enum { - REG_MANUFACTURER_DATA, - REG_TEMPERATURE, - REG_VOLTAGE, - REG_CURRENT, - REG_CAPACITY, - REG_TIME_TO_EMPTY, - REG_TIME_TO_FULL, - REG_STATUS, - REG_CYCLE_COUNT, - REG_SERIAL_NUMBER, - REG_REMAINING_CAPACITY, - REG_REMAINING_CAPACITY_CHARGE, - REG_FULL_CHARGE_CAPACITY, - REG_FULL_CHARGE_CAPACITY_CHARGE, - REG_DESIGN_CAPACITY, - REG_DESIGN_CAPACITY_CHARGE, - REG_DESIGN_VOLTAGE, -}; - -/* Battery Mode defines */ -#define BATTERY_MODE_OFFSET 0x03 -#define BATTERY_MODE_MASK 0x8000 -enum bq20z75_battery_mode { - BATTERY_MODE_AMPS, - BATTERY_MODE_WATTS -}; - -/* manufacturer access defines */ -#define MANUFACTURER_ACCESS_STATUS 0x0006 -#define MANUFACTURER_ACCESS_SLEEP 0x0011 - -/* battery status value bits */ -#define BATTERY_DISCHARGING 0x40 -#define BATTERY_FULL_CHARGED 0x20 -#define BATTERY_FULL_DISCHARGED 0x10 - -#define BQ20Z75_DATA(_psp, _addr, _min_value, _max_value) { \ - .psp = _psp, \ - .addr = _addr, \ - .min_value = _min_value, \ - .max_value = _max_value, \ -} - -static const struct bq20z75_device_data { - enum power_supply_property psp; - u8 addr; - int min_value; - int max_value; -} bq20z75_data[] = { - [REG_MANUFACTURER_DATA] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535), - [REG_TEMPERATURE] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535), - [REG_VOLTAGE] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000), - [REG_CURRENT] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, - 32767), - [REG_CAPACITY] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0E, 0, 100), - [REG_REMAINING_CAPACITY] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535), - [REG_REMAINING_CAPACITY_CHARGE] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_NOW, 0x0F, 0, 65535), - [REG_FULL_CHARGE_CAPACITY] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535), - [REG_FULL_CHARGE_CAPACITY_CHARGE] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535), - [REG_TIME_TO_EMPTY] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, - 65535), - [REG_TIME_TO_FULL] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, - 65535), - [REG_STATUS] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535), - [REG_CYCLE_COUNT] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_CYCLE_COUNT, 0x17, 0, 65535), - [REG_DESIGN_CAPACITY] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0, - 65535), - [REG_DESIGN_CAPACITY_CHARGE] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0, - 65535), - [REG_DESIGN_VOLTAGE] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0, - 65535), - [REG_SERIAL_NUMBER] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535), -}; - -static enum power_supply_property bq20z75_properties[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_CYCLE_COUNT, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, - POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, - POWER_SUPPLY_PROP_SERIAL_NUMBER, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_ENERGY_NOW, - POWER_SUPPLY_PROP_ENERGY_FULL, - POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, -}; - -struct bq20z75_info { - struct i2c_client *client; - struct power_supply power_supply; - struct bq20z75_platform_data *pdata; - bool is_present; - bool gpio_detect; - bool enable_detection; - int irq; - int last_state; - int poll_time; - struct delayed_work work; - int ignore_changes; -}; - -static int bq20z75_read_word_data(struct i2c_client *client, u8 address) -{ - struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); - s32 ret = 0; - int retries = 1; - - if (bq20z75_device->pdata) - retries = max(bq20z75_device->pdata->i2c_retry_count + 1, 1); - - while (retries > 0) { - ret = i2c_smbus_read_word_data(client, address); - if (ret >= 0) - break; - retries--; - } - - if (ret < 0) { - dev_dbg(&client->dev, - "%s: i2c read at address 0x%x failed\n", - __func__, address); - return ret; - } - - return le16_to_cpu(ret); -} - -static int bq20z75_write_word_data(struct i2c_client *client, u8 address, - u16 value) -{ - struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); - s32 ret = 0; - int retries = 1; - - if (bq20z75_device->pdata) - retries = max(bq20z75_device->pdata->i2c_retry_count + 1, 1); - - while (retries > 0) { - ret = i2c_smbus_write_word_data(client, address, - le16_to_cpu(value)); - if (ret >= 0) - break; - retries--; - } - - if (ret < 0) { - dev_dbg(&client->dev, - "%s: i2c write to address 0x%x failed\n", - __func__, address); - return ret; - } - - return 0; -} - -static int bq20z75_get_battery_presence_and_health( - struct i2c_client *client, enum power_supply_property psp, - union power_supply_propval *val) -{ - s32 ret; - struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); - - if (psp == POWER_SUPPLY_PROP_PRESENT && - bq20z75_device->gpio_detect) { - ret = gpio_get_value( - bq20z75_device->pdata->battery_detect); - if (ret == bq20z75_device->pdata->battery_detect_present) - val->intval = 1; - else - val->intval = 0; - bq20z75_device->is_present = val->intval; - return ret; - } - - /* Write to ManufacturerAccess with - * ManufacturerAccess command and then - * read the status */ - ret = bq20z75_write_word_data(client, - bq20z75_data[REG_MANUFACTURER_DATA].addr, - MANUFACTURER_ACCESS_STATUS); - if (ret < 0) { - if (psp == POWER_SUPPLY_PROP_PRESENT) - val->intval = 0; /* battery removed */ - return ret; - } - - ret = bq20z75_read_word_data(client, - bq20z75_data[REG_MANUFACTURER_DATA].addr); - if (ret < 0) - return ret; - - if (ret < bq20z75_data[REG_MANUFACTURER_DATA].min_value || - ret > bq20z75_data[REG_MANUFACTURER_DATA].max_value) { - val->intval = 0; - return 0; - } - - /* Mask the upper nibble of 2nd byte and - * lower byte of response then - * shift the result by 8 to get status*/ - ret &= 0x0F00; - ret >>= 8; - if (psp == POWER_SUPPLY_PROP_PRESENT) { - if (ret == 0x0F) - /* battery removed */ - val->intval = 0; - else - val->intval = 1; - } else if (psp == POWER_SUPPLY_PROP_HEALTH) { - if (ret == 0x09) - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - else if (ret == 0x0B) - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - else if (ret == 0x0C) - val->intval = POWER_SUPPLY_HEALTH_DEAD; - else - val->intval = POWER_SUPPLY_HEALTH_GOOD; - } - - return 0; -} - -static int bq20z75_get_battery_property(struct i2c_client *client, - int reg_offset, enum power_supply_property psp, - union power_supply_propval *val) -{ - struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); - s32 ret; - - ret = bq20z75_read_word_data(client, - bq20z75_data[reg_offset].addr); - if (ret < 0) - return ret; - - /* returned values are 16 bit */ - if (bq20z75_data[reg_offset].min_value < 0) - ret = (s16)ret; - - if (ret >= bq20z75_data[reg_offset].min_value && - ret <= bq20z75_data[reg_offset].max_value) { - val->intval = ret; - if (psp != POWER_SUPPLY_PROP_STATUS) - return 0; - - if (ret & BATTERY_FULL_CHARGED) - val->intval = POWER_SUPPLY_STATUS_FULL; - else if (ret & BATTERY_FULL_DISCHARGED) - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - else if (ret & BATTERY_DISCHARGING) - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; - else - val->intval = POWER_SUPPLY_STATUS_CHARGING; - - if (bq20z75_device->poll_time == 0) - bq20z75_device->last_state = val->intval; - else if (bq20z75_device->last_state != val->intval) { - cancel_delayed_work_sync(&bq20z75_device->work); - power_supply_changed(&bq20z75_device->power_supply); - bq20z75_device->poll_time = 0; - } - } else { - if (psp == POWER_SUPPLY_PROP_STATUS) - val->intval = POWER_SUPPLY_STATUS_UNKNOWN; - else - val->intval = 0; - } - - return 0; -} - -static void bq20z75_unit_adjustment(struct i2c_client *client, - enum power_supply_property psp, union power_supply_propval *val) -{ -#define BASE_UNIT_CONVERSION 1000 -#define BATTERY_MODE_CAP_MULT_WATT (10 * BASE_UNIT_CONVERSION) -#define TIME_UNIT_CONVERSION 60 -#define TEMP_KELVIN_TO_CELSIUS 2731 - switch (psp) { - case POWER_SUPPLY_PROP_ENERGY_NOW: - case POWER_SUPPLY_PROP_ENERGY_FULL: - case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: - /* bq20z75 provides energy in units of 10mWh. - * Convert to µWh - */ - val->intval *= BATTERY_MODE_CAP_MULT_WATT; - break; - - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - case POWER_SUPPLY_PROP_CURRENT_NOW: - case POWER_SUPPLY_PROP_CHARGE_NOW: - case POWER_SUPPLY_PROP_CHARGE_FULL: - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - val->intval *= BASE_UNIT_CONVERSION; - break; - - case POWER_SUPPLY_PROP_TEMP: - /* bq20z75 provides battery temperature in 0.1K - * so convert it to 0.1°C - */ - val->intval -= TEMP_KELVIN_TO_CELSIUS; - break; - - case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: - case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: - /* bq20z75 provides time to empty and time to full in minutes. - * Convert to seconds - */ - val->intval *= TIME_UNIT_CONVERSION; - break; - - default: - dev_dbg(&client->dev, - "%s: no need for unit conversion %d\n", __func__, psp); - } -} - -static enum bq20z75_battery_mode -bq20z75_set_battery_mode(struct i2c_client *client, - enum bq20z75_battery_mode mode) -{ - int ret, original_val; - - original_val = bq20z75_read_word_data(client, BATTERY_MODE_OFFSET); - if (original_val < 0) - return original_val; - - if ((original_val & BATTERY_MODE_MASK) == mode) - return mode; - - if (mode == BATTERY_MODE_AMPS) - ret = original_val & ~BATTERY_MODE_MASK; - else - ret = original_val | BATTERY_MODE_MASK; - - ret = bq20z75_write_word_data(client, BATTERY_MODE_OFFSET, ret); - if (ret < 0) - return ret; - - return original_val & BATTERY_MODE_MASK; -} - -static int bq20z75_get_battery_capacity(struct i2c_client *client, - int reg_offset, enum power_supply_property psp, - union power_supply_propval *val) -{ - s32 ret; - enum bq20z75_battery_mode mode = BATTERY_MODE_WATTS; - - if (power_supply_is_amp_property(psp)) - mode = BATTERY_MODE_AMPS; - - mode = bq20z75_set_battery_mode(client, mode); - if (mode < 0) - return mode; - - ret = bq20z75_read_word_data(client, bq20z75_data[reg_offset].addr); - if (ret < 0) - return ret; - - if (psp == POWER_SUPPLY_PROP_CAPACITY) { - /* bq20z75 spec says that this can be >100 % - * even if max value is 100 % */ - val->intval = min(ret, 100); - } else - val->intval = ret; - - ret = bq20z75_set_battery_mode(client, mode); - if (ret < 0) - return ret; - - return 0; -} - -static char bq20z75_serial[5]; -static int bq20z75_get_battery_serial_number(struct i2c_client *client, - union power_supply_propval *val) -{ - int ret; - - ret = bq20z75_read_word_data(client, - bq20z75_data[REG_SERIAL_NUMBER].addr); - if (ret < 0) - return ret; - - ret = sprintf(bq20z75_serial, "%04x", ret); - val->strval = bq20z75_serial; - - return 0; -} - -static int bq20z75_get_property_index(struct i2c_client *client, - enum power_supply_property psp) -{ - int count; - for (count = 0; count < ARRAY_SIZE(bq20z75_data); count++) - if (psp == bq20z75_data[count].psp) - return count; - - dev_warn(&client->dev, - "%s: Invalid Property - %d\n", __func__, psp); - - return -EINVAL; -} - -static int bq20z75_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret = 0; - struct bq20z75_info *bq20z75_device = container_of(psy, - struct bq20z75_info, power_supply); - struct i2c_client *client = bq20z75_device->client; - - switch (psp) { - case POWER_SUPPLY_PROP_PRESENT: - case POWER_SUPPLY_PROP_HEALTH: - ret = bq20z75_get_battery_presence_and_health(client, psp, val); - if (psp == POWER_SUPPLY_PROP_PRESENT) - return 0; - break; - - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; - break; - - case POWER_SUPPLY_PROP_ENERGY_NOW: - case POWER_SUPPLY_PROP_ENERGY_FULL: - case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: - case POWER_SUPPLY_PROP_CHARGE_NOW: - case POWER_SUPPLY_PROP_CHARGE_FULL: - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - case POWER_SUPPLY_PROP_CAPACITY: - ret = bq20z75_get_property_index(client, psp); - if (ret < 0) - break; - - ret = bq20z75_get_battery_capacity(client, ret, psp, val); - break; - - case POWER_SUPPLY_PROP_SERIAL_NUMBER: - ret = bq20z75_get_battery_serial_number(client, val); - break; - - case POWER_SUPPLY_PROP_STATUS: - case POWER_SUPPLY_PROP_CYCLE_COUNT: - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - case POWER_SUPPLY_PROP_CURRENT_NOW: - case POWER_SUPPLY_PROP_TEMP: - case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: - case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - ret = bq20z75_get_property_index(client, psp); - if (ret < 0) - break; - - ret = bq20z75_get_battery_property(client, ret, psp, val); - break; - - default: - dev_err(&client->dev, - "%s: INVALID property\n", __func__); - return -EINVAL; - } - - if (!bq20z75_device->enable_detection) - goto done; - - if (!bq20z75_device->gpio_detect && - bq20z75_device->is_present != (ret >= 0)) { - bq20z75_device->is_present = (ret >= 0); - power_supply_changed(&bq20z75_device->power_supply); - } - -done: - if (!ret) { - /* Convert units to match requirements for power supply class */ - bq20z75_unit_adjustment(client, psp, val); - } - - dev_dbg(&client->dev, - "%s: property = %d, value = %x\n", __func__, psp, val->intval); - - if (ret && bq20z75_device->is_present) - return ret; - - /* battery not present, so return NODATA for properties */ - if (ret) - return -ENODATA; - - return 0; -} - -static irqreturn_t bq20z75_irq(int irq, void *devid) -{ - struct power_supply *battery = devid; - - power_supply_changed(battery); - - return IRQ_HANDLED; -} - -static void bq20z75_external_power_changed(struct power_supply *psy) -{ - struct bq20z75_info *bq20z75_device; - - bq20z75_device = container_of(psy, struct bq20z75_info, power_supply); - - if (bq20z75_device->ignore_changes > 0) { - bq20z75_device->ignore_changes--; - return; - } - - /* cancel outstanding work */ - cancel_delayed_work_sync(&bq20z75_device->work); - - schedule_delayed_work(&bq20z75_device->work, HZ); - bq20z75_device->poll_time = bq20z75_device->pdata->poll_retry_count; -} - -static void bq20z75_delayed_work(struct work_struct *work) -{ - struct bq20z75_info *bq20z75_device; - s32 ret; - - bq20z75_device = container_of(work, struct bq20z75_info, work.work); - - ret = bq20z75_read_word_data(bq20z75_device->client, - bq20z75_data[REG_STATUS].addr); - /* if the read failed, give up on this work */ - if (ret < 0) { - bq20z75_device->poll_time = 0; - return; - } - - if (ret & BATTERY_FULL_CHARGED) - ret = POWER_SUPPLY_STATUS_FULL; - else if (ret & BATTERY_FULL_DISCHARGED) - ret = POWER_SUPPLY_STATUS_NOT_CHARGING; - else if (ret & BATTERY_DISCHARGING) - ret = POWER_SUPPLY_STATUS_DISCHARGING; - else - ret = POWER_SUPPLY_STATUS_CHARGING; - - if (bq20z75_device->last_state != ret) { - bq20z75_device->poll_time = 0; - power_supply_changed(&bq20z75_device->power_supply); - return; - } - if (bq20z75_device->poll_time > 0) { - schedule_delayed_work(&bq20z75_device->work, HZ); - bq20z75_device->poll_time--; - return; - } -} - -#if defined(CONFIG_OF) - -#include -#include - -static const struct of_device_id bq20z75_dt_ids[] = { - { .compatible = "ti,bq20z75" }, - { } -}; -MODULE_DEVICE_TABLE(i2c, bq20z75_dt_ids); - -static struct bq20z75_platform_data *bq20z75_of_populate_pdata( - struct i2c_client *client) -{ - struct device_node *of_node = client->dev.of_node; - struct bq20z75_platform_data *pdata = client->dev.platform_data; - enum of_gpio_flags gpio_flags; - int rc; - u32 prop; - - /* verify this driver matches this device */ - if (!of_node) - return NULL; - - /* if platform data is set, honor it */ - if (pdata) - return pdata; - - /* first make sure at least one property is set, otherwise - * it won't change behavior from running without pdata. - */ - if (!of_get_property(of_node, "ti,i2c-retry-count", NULL) && - !of_get_property(of_node, "ti,poll-retry-count", NULL) && - !of_get_property(of_node, "ti,battery-detect-gpios", NULL)) - goto of_out; - - pdata = devm_kzalloc(&client->dev, sizeof(struct bq20z75_platform_data), - GFP_KERNEL); - if (!pdata) - goto of_out; - - rc = of_property_read_u32(of_node, "ti,i2c-retry-count", &prop); - if (!rc) - pdata->i2c_retry_count = prop; - - rc = of_property_read_u32(of_node, "ti,poll-retry-count", &prop); - if (!rc) - pdata->poll_retry_count = prop; - - if (!of_get_property(of_node, "ti,battery-detect-gpios", NULL)) { - pdata->battery_detect = -1; - goto of_out; - } - - pdata->battery_detect = of_get_named_gpio_flags(of_node, - "ti,battery-detect-gpios", 0, &gpio_flags); - - if (gpio_flags & OF_GPIO_ACTIVE_LOW) - pdata->battery_detect_present = 0; - else - pdata->battery_detect_present = 1; - -of_out: - return pdata; -} -#else -#define bq20z75_dt_ids NULL -static struct bq20z75_platform_data *bq20z75_of_populate_pdata( - struct i2c_client *client) -{ - return client->dev.platform_data; -} -#endif - -static int __devinit bq20z75_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct bq20z75_info *bq20z75_device; - struct bq20z75_platform_data *pdata = client->dev.platform_data; - int rc; - int irq; - - bq20z75_device = kzalloc(sizeof(struct bq20z75_info), GFP_KERNEL); - if (!bq20z75_device) - return -ENOMEM; - - bq20z75_device->client = client; - bq20z75_device->enable_detection = false; - bq20z75_device->gpio_detect = false; - bq20z75_device->power_supply.name = "battery"; - bq20z75_device->power_supply.type = POWER_SUPPLY_TYPE_BATTERY; - bq20z75_device->power_supply.properties = bq20z75_properties; - bq20z75_device->power_supply.num_properties = - ARRAY_SIZE(bq20z75_properties); - bq20z75_device->power_supply.get_property = bq20z75_get_property; - /* ignore first notification of external change, it is generated - * from the power_supply_register call back - */ - bq20z75_device->ignore_changes = 1; - bq20z75_device->last_state = POWER_SUPPLY_STATUS_UNKNOWN; - bq20z75_device->power_supply.external_power_changed = - bq20z75_external_power_changed; - - pdata = bq20z75_of_populate_pdata(client); - - if (pdata) { - bq20z75_device->gpio_detect = - gpio_is_valid(pdata->battery_detect); - bq20z75_device->pdata = pdata; - } - - i2c_set_clientdata(client, bq20z75_device); - - if (!bq20z75_device->gpio_detect) - goto skip_gpio; - - rc = gpio_request(pdata->battery_detect, dev_name(&client->dev)); - if (rc) { - dev_warn(&client->dev, "Failed to request gpio: %d\n", rc); - bq20z75_device->gpio_detect = false; - goto skip_gpio; - } - - rc = gpio_direction_input(pdata->battery_detect); - if (rc) { - dev_warn(&client->dev, "Failed to get gpio as input: %d\n", rc); - gpio_free(pdata->battery_detect); - bq20z75_device->gpio_detect = false; - goto skip_gpio; - } - - irq = gpio_to_irq(pdata->battery_detect); - if (irq <= 0) { - dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); - gpio_free(pdata->battery_detect); - bq20z75_device->gpio_detect = false; - goto skip_gpio; - } - - rc = request_irq(irq, bq20z75_irq, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - dev_name(&client->dev), &bq20z75_device->power_supply); - if (rc) { - dev_warn(&client->dev, "Failed to request irq: %d\n", rc); - gpio_free(pdata->battery_detect); - bq20z75_device->gpio_detect = false; - goto skip_gpio; - } - - bq20z75_device->irq = irq; - -skip_gpio: - - rc = power_supply_register(&client->dev, &bq20z75_device->power_supply); - if (rc) { - dev_err(&client->dev, - "%s: Failed to register power supply\n", __func__); - goto exit_psupply; - } - - dev_info(&client->dev, - "%s: battery gas gauge device registered\n", client->name); - - INIT_DELAYED_WORK(&bq20z75_device->work, bq20z75_delayed_work); - - bq20z75_device->enable_detection = true; - - return 0; - -exit_psupply: - if (bq20z75_device->irq) - free_irq(bq20z75_device->irq, &bq20z75_device->power_supply); - if (bq20z75_device->gpio_detect) - gpio_free(pdata->battery_detect); - - kfree(bq20z75_device); - - return rc; -} - -static int __devexit bq20z75_remove(struct i2c_client *client) -{ - struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); - - if (bq20z75_device->irq) - free_irq(bq20z75_device->irq, &bq20z75_device->power_supply); - if (bq20z75_device->gpio_detect) - gpio_free(bq20z75_device->pdata->battery_detect); - - power_supply_unregister(&bq20z75_device->power_supply); - - cancel_delayed_work_sync(&bq20z75_device->work); - - kfree(bq20z75_device); - bq20z75_device = NULL; - - return 0; -} - -#if defined CONFIG_PM -static int bq20z75_suspend(struct i2c_client *client, - pm_message_t state) -{ - struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); - s32 ret; - - if (bq20z75_device->poll_time > 0) - cancel_delayed_work_sync(&bq20z75_device->work); - - /* write to manufacturer access with sleep command */ - ret = bq20z75_write_word_data(client, - bq20z75_data[REG_MANUFACTURER_DATA].addr, - MANUFACTURER_ACCESS_SLEEP); - if (bq20z75_device->is_present && ret < 0) - return ret; - - return 0; -} -#else -#define bq20z75_suspend NULL -#endif -/* any smbus transaction will wake up bq20z75 */ -#define bq20z75_resume NULL - -static const struct i2c_device_id bq20z75_id[] = { - { "bq20z75", 0 }, - {} -}; -MODULE_DEVICE_TABLE(i2c, bq20z75_id); - -static struct i2c_driver bq20z75_battery_driver = { - .probe = bq20z75_probe, - .remove = __devexit_p(bq20z75_remove), - .suspend = bq20z75_suspend, - .resume = bq20z75_resume, - .id_table = bq20z75_id, - .driver = { - .name = "bq20z75-battery", - .of_match_table = bq20z75_dt_ids, - }, -}; - -static int __init bq20z75_battery_init(void) -{ - return i2c_add_driver(&bq20z75_battery_driver); -} -module_init(bq20z75_battery_init); - -static void __exit bq20z75_battery_exit(void) -{ - i2c_del_driver(&bq20z75_battery_driver); -} -module_exit(bq20z75_battery_exit); - -MODULE_DESCRIPTION("BQ20z75 battery monitor driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c new file mode 100644 index 000000000000..ce95ff791016 --- /dev/null +++ b/drivers/power/sbs-battery.c @@ -0,0 +1,871 @@ +/* + * Gas Gauge driver for TI's BQ20Z75 + * + * Copyright (c) 2010, 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +enum { + REG_MANUFACTURER_DATA, + REG_TEMPERATURE, + REG_VOLTAGE, + REG_CURRENT, + REG_CAPACITY, + REG_TIME_TO_EMPTY, + REG_TIME_TO_FULL, + REG_STATUS, + REG_CYCLE_COUNT, + REG_SERIAL_NUMBER, + REG_REMAINING_CAPACITY, + REG_REMAINING_CAPACITY_CHARGE, + REG_FULL_CHARGE_CAPACITY, + REG_FULL_CHARGE_CAPACITY_CHARGE, + REG_DESIGN_CAPACITY, + REG_DESIGN_CAPACITY_CHARGE, + REG_DESIGN_VOLTAGE, +}; + +/* Battery Mode defines */ +#define BATTERY_MODE_OFFSET 0x03 +#define BATTERY_MODE_MASK 0x8000 +enum bq20z75_battery_mode { + BATTERY_MODE_AMPS, + BATTERY_MODE_WATTS +}; + +/* manufacturer access defines */ +#define MANUFACTURER_ACCESS_STATUS 0x0006 +#define MANUFACTURER_ACCESS_SLEEP 0x0011 + +/* battery status value bits */ +#define BATTERY_DISCHARGING 0x40 +#define BATTERY_FULL_CHARGED 0x20 +#define BATTERY_FULL_DISCHARGED 0x10 + +#define BQ20Z75_DATA(_psp, _addr, _min_value, _max_value) { \ + .psp = _psp, \ + .addr = _addr, \ + .min_value = _min_value, \ + .max_value = _max_value, \ +} + +static const struct bq20z75_device_data { + enum power_supply_property psp; + u8 addr; + int min_value; + int max_value; +} bq20z75_data[] = { + [REG_MANUFACTURER_DATA] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535), + [REG_TEMPERATURE] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535), + [REG_VOLTAGE] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000), + [REG_CURRENT] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, + 32767), + [REG_CAPACITY] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0E, 0, 100), + [REG_REMAINING_CAPACITY] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535), + [REG_REMAINING_CAPACITY_CHARGE] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_NOW, 0x0F, 0, 65535), + [REG_FULL_CHARGE_CAPACITY] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535), + [REG_FULL_CHARGE_CAPACITY_CHARGE] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535), + [REG_TIME_TO_EMPTY] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, + 65535), + [REG_TIME_TO_FULL] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, + 65535), + [REG_STATUS] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535), + [REG_CYCLE_COUNT] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_CYCLE_COUNT, 0x17, 0, 65535), + [REG_DESIGN_CAPACITY] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0, + 65535), + [REG_DESIGN_CAPACITY_CHARGE] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0, + 65535), + [REG_DESIGN_VOLTAGE] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0, + 65535), + [REG_SERIAL_NUMBER] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535), +}; + +static enum power_supply_property bq20z75_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, +}; + +struct bq20z75_info { + struct i2c_client *client; + struct power_supply power_supply; + struct bq20z75_platform_data *pdata; + bool is_present; + bool gpio_detect; + bool enable_detection; + int irq; + int last_state; + int poll_time; + struct delayed_work work; + int ignore_changes; +}; + +static int bq20z75_read_word_data(struct i2c_client *client, u8 address) +{ + struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + s32 ret = 0; + int retries = 1; + + if (bq20z75_device->pdata) + retries = max(bq20z75_device->pdata->i2c_retry_count + 1, 1); + + while (retries > 0) { + ret = i2c_smbus_read_word_data(client, address); + if (ret >= 0) + break; + retries--; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "%s: i2c read at address 0x%x failed\n", + __func__, address); + return ret; + } + + return le16_to_cpu(ret); +} + +static int bq20z75_write_word_data(struct i2c_client *client, u8 address, + u16 value) +{ + struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + s32 ret = 0; + int retries = 1; + + if (bq20z75_device->pdata) + retries = max(bq20z75_device->pdata->i2c_retry_count + 1, 1); + + while (retries > 0) { + ret = i2c_smbus_write_word_data(client, address, + le16_to_cpu(value)); + if (ret >= 0) + break; + retries--; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "%s: i2c write to address 0x%x failed\n", + __func__, address); + return ret; + } + + return 0; +} + +static int bq20z75_get_battery_presence_and_health( + struct i2c_client *client, enum power_supply_property psp, + union power_supply_propval *val) +{ + s32 ret; + struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + + if (psp == POWER_SUPPLY_PROP_PRESENT && + bq20z75_device->gpio_detect) { + ret = gpio_get_value( + bq20z75_device->pdata->battery_detect); + if (ret == bq20z75_device->pdata->battery_detect_present) + val->intval = 1; + else + val->intval = 0; + bq20z75_device->is_present = val->intval; + return ret; + } + + /* Write to ManufacturerAccess with + * ManufacturerAccess command and then + * read the status */ + ret = bq20z75_write_word_data(client, + bq20z75_data[REG_MANUFACTURER_DATA].addr, + MANUFACTURER_ACCESS_STATUS); + if (ret < 0) { + if (psp == POWER_SUPPLY_PROP_PRESENT) + val->intval = 0; /* battery removed */ + return ret; + } + + ret = bq20z75_read_word_data(client, + bq20z75_data[REG_MANUFACTURER_DATA].addr); + if (ret < 0) + return ret; + + if (ret < bq20z75_data[REG_MANUFACTURER_DATA].min_value || + ret > bq20z75_data[REG_MANUFACTURER_DATA].max_value) { + val->intval = 0; + return 0; + } + + /* Mask the upper nibble of 2nd byte and + * lower byte of response then + * shift the result by 8 to get status*/ + ret &= 0x0F00; + ret >>= 8; + if (psp == POWER_SUPPLY_PROP_PRESENT) { + if (ret == 0x0F) + /* battery removed */ + val->intval = 0; + else + val->intval = 1; + } else if (psp == POWER_SUPPLY_PROP_HEALTH) { + if (ret == 0x09) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (ret == 0x0B) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (ret == 0x0C) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } + + return 0; +} + +static int bq20z75_get_battery_property(struct i2c_client *client, + int reg_offset, enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + s32 ret; + + ret = bq20z75_read_word_data(client, + bq20z75_data[reg_offset].addr); + if (ret < 0) + return ret; + + /* returned values are 16 bit */ + if (bq20z75_data[reg_offset].min_value < 0) + ret = (s16)ret; + + if (ret >= bq20z75_data[reg_offset].min_value && + ret <= bq20z75_data[reg_offset].max_value) { + val->intval = ret; + if (psp != POWER_SUPPLY_PROP_STATUS) + return 0; + + if (ret & BATTERY_FULL_CHARGED) + val->intval = POWER_SUPPLY_STATUS_FULL; + else if (ret & BATTERY_FULL_DISCHARGED) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (ret & BATTERY_DISCHARGING) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else + val->intval = POWER_SUPPLY_STATUS_CHARGING; + + if (bq20z75_device->poll_time == 0) + bq20z75_device->last_state = val->intval; + else if (bq20z75_device->last_state != val->intval) { + cancel_delayed_work_sync(&bq20z75_device->work); + power_supply_changed(&bq20z75_device->power_supply); + bq20z75_device->poll_time = 0; + } + } else { + if (psp == POWER_SUPPLY_PROP_STATUS) + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + else + val->intval = 0; + } + + return 0; +} + +static void bq20z75_unit_adjustment(struct i2c_client *client, + enum power_supply_property psp, union power_supply_propval *val) +{ +#define BASE_UNIT_CONVERSION 1000 +#define BATTERY_MODE_CAP_MULT_WATT (10 * BASE_UNIT_CONVERSION) +#define TIME_UNIT_CONVERSION 60 +#define TEMP_KELVIN_TO_CELSIUS 2731 + switch (psp) { + case POWER_SUPPLY_PROP_ENERGY_NOW: + case POWER_SUPPLY_PROP_ENERGY_FULL: + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + /* bq20z75 provides energy in units of 10mWh. + * Convert to µWh + */ + val->intval *= BATTERY_MODE_CAP_MULT_WATT; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CHARGE_NOW: + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval *= BASE_UNIT_CONVERSION; + break; + + case POWER_SUPPLY_PROP_TEMP: + /* bq20z75 provides battery temperature in 0.1K + * so convert it to 0.1°C + */ + val->intval -= TEMP_KELVIN_TO_CELSIUS; + break; + + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + /* bq20z75 provides time to empty and time to full in minutes. + * Convert to seconds + */ + val->intval *= TIME_UNIT_CONVERSION; + break; + + default: + dev_dbg(&client->dev, + "%s: no need for unit conversion %d\n", __func__, psp); + } +} + +static enum bq20z75_battery_mode +bq20z75_set_battery_mode(struct i2c_client *client, + enum bq20z75_battery_mode mode) +{ + int ret, original_val; + + original_val = bq20z75_read_word_data(client, BATTERY_MODE_OFFSET); + if (original_val < 0) + return original_val; + + if ((original_val & BATTERY_MODE_MASK) == mode) + return mode; + + if (mode == BATTERY_MODE_AMPS) + ret = original_val & ~BATTERY_MODE_MASK; + else + ret = original_val | BATTERY_MODE_MASK; + + ret = bq20z75_write_word_data(client, BATTERY_MODE_OFFSET, ret); + if (ret < 0) + return ret; + + return original_val & BATTERY_MODE_MASK; +} + +static int bq20z75_get_battery_capacity(struct i2c_client *client, + int reg_offset, enum power_supply_property psp, + union power_supply_propval *val) +{ + s32 ret; + enum bq20z75_battery_mode mode = BATTERY_MODE_WATTS; + + if (power_supply_is_amp_property(psp)) + mode = BATTERY_MODE_AMPS; + + mode = bq20z75_set_battery_mode(client, mode); + if (mode < 0) + return mode; + + ret = bq20z75_read_word_data(client, bq20z75_data[reg_offset].addr); + if (ret < 0) + return ret; + + if (psp == POWER_SUPPLY_PROP_CAPACITY) { + /* bq20z75 spec says that this can be >100 % + * even if max value is 100 % */ + val->intval = min(ret, 100); + } else + val->intval = ret; + + ret = bq20z75_set_battery_mode(client, mode); + if (ret < 0) + return ret; + + return 0; +} + +static char bq20z75_serial[5]; +static int bq20z75_get_battery_serial_number(struct i2c_client *client, + union power_supply_propval *val) +{ + int ret; + + ret = bq20z75_read_word_data(client, + bq20z75_data[REG_SERIAL_NUMBER].addr); + if (ret < 0) + return ret; + + ret = sprintf(bq20z75_serial, "%04x", ret); + val->strval = bq20z75_serial; + + return 0; +} + +static int bq20z75_get_property_index(struct i2c_client *client, + enum power_supply_property psp) +{ + int count; + for (count = 0; count < ARRAY_SIZE(bq20z75_data); count++) + if (psp == bq20z75_data[count].psp) + return count; + + dev_warn(&client->dev, + "%s: Invalid Property - %d\n", __func__, psp); + + return -EINVAL; +} + +static int bq20z75_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct bq20z75_info *bq20z75_device = container_of(psy, + struct bq20z75_info, power_supply); + struct i2c_client *client = bq20z75_device->client; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_HEALTH: + ret = bq20z75_get_battery_presence_and_health(client, psp, val); + if (psp == POWER_SUPPLY_PROP_PRESENT) + return 0; + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + + case POWER_SUPPLY_PROP_ENERGY_NOW: + case POWER_SUPPLY_PROP_ENERGY_FULL: + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + case POWER_SUPPLY_PROP_CHARGE_NOW: + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + case POWER_SUPPLY_PROP_CAPACITY: + ret = bq20z75_get_property_index(client, psp); + if (ret < 0) + break; + + ret = bq20z75_get_battery_capacity(client, ret, psp, val); + break; + + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + ret = bq20z75_get_battery_serial_number(client, val); + break; + + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_CYCLE_COUNT: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_TEMP: + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + ret = bq20z75_get_property_index(client, psp); + if (ret < 0) + break; + + ret = bq20z75_get_battery_property(client, ret, psp, val); + break; + + default: + dev_err(&client->dev, + "%s: INVALID property\n", __func__); + return -EINVAL; + } + + if (!bq20z75_device->enable_detection) + goto done; + + if (!bq20z75_device->gpio_detect && + bq20z75_device->is_present != (ret >= 0)) { + bq20z75_device->is_present = (ret >= 0); + power_supply_changed(&bq20z75_device->power_supply); + } + +done: + if (!ret) { + /* Convert units to match requirements for power supply class */ + bq20z75_unit_adjustment(client, psp, val); + } + + dev_dbg(&client->dev, + "%s: property = %d, value = %x\n", __func__, psp, val->intval); + + if (ret && bq20z75_device->is_present) + return ret; + + /* battery not present, so return NODATA for properties */ + if (ret) + return -ENODATA; + + return 0; +} + +static irqreturn_t bq20z75_irq(int irq, void *devid) +{ + struct power_supply *battery = devid; + + power_supply_changed(battery); + + return IRQ_HANDLED; +} + +static void bq20z75_external_power_changed(struct power_supply *psy) +{ + struct bq20z75_info *bq20z75_device; + + bq20z75_device = container_of(psy, struct bq20z75_info, power_supply); + + if (bq20z75_device->ignore_changes > 0) { + bq20z75_device->ignore_changes--; + return; + } + + /* cancel outstanding work */ + cancel_delayed_work_sync(&bq20z75_device->work); + + schedule_delayed_work(&bq20z75_device->work, HZ); + bq20z75_device->poll_time = bq20z75_device->pdata->poll_retry_count; +} + +static void bq20z75_delayed_work(struct work_struct *work) +{ + struct bq20z75_info *bq20z75_device; + s32 ret; + + bq20z75_device = container_of(work, struct bq20z75_info, work.work); + + ret = bq20z75_read_word_data(bq20z75_device->client, + bq20z75_data[REG_STATUS].addr); + /* if the read failed, give up on this work */ + if (ret < 0) { + bq20z75_device->poll_time = 0; + return; + } + + if (ret & BATTERY_FULL_CHARGED) + ret = POWER_SUPPLY_STATUS_FULL; + else if (ret & BATTERY_FULL_DISCHARGED) + ret = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (ret & BATTERY_DISCHARGING) + ret = POWER_SUPPLY_STATUS_DISCHARGING; + else + ret = POWER_SUPPLY_STATUS_CHARGING; + + if (bq20z75_device->last_state != ret) { + bq20z75_device->poll_time = 0; + power_supply_changed(&bq20z75_device->power_supply); + return; + } + if (bq20z75_device->poll_time > 0) { + schedule_delayed_work(&bq20z75_device->work, HZ); + bq20z75_device->poll_time--; + return; + } +} + +#if defined(CONFIG_OF) + +#include +#include + +static const struct of_device_id bq20z75_dt_ids[] = { + { .compatible = "ti,bq20z75" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bq20z75_dt_ids); + +static struct bq20z75_platform_data *bq20z75_of_populate_pdata( + struct i2c_client *client) +{ + struct device_node *of_node = client->dev.of_node; + struct bq20z75_platform_data *pdata = client->dev.platform_data; + enum of_gpio_flags gpio_flags; + int rc; + u32 prop; + + /* verify this driver matches this device */ + if (!of_node) + return NULL; + + /* if platform data is set, honor it */ + if (pdata) + return pdata; + + /* first make sure at least one property is set, otherwise + * it won't change behavior from running without pdata. + */ + if (!of_get_property(of_node, "ti,i2c-retry-count", NULL) && + !of_get_property(of_node, "ti,poll-retry-count", NULL) && + !of_get_property(of_node, "ti,battery-detect-gpios", NULL)) + goto of_out; + + pdata = devm_kzalloc(&client->dev, sizeof(struct bq20z75_platform_data), + GFP_KERNEL); + if (!pdata) + goto of_out; + + rc = of_property_read_u32(of_node, "ti,i2c-retry-count", &prop); + if (!rc) + pdata->i2c_retry_count = prop; + + rc = of_property_read_u32(of_node, "ti,poll-retry-count", &prop); + if (!rc) + pdata->poll_retry_count = prop; + + if (!of_get_property(of_node, "ti,battery-detect-gpios", NULL)) { + pdata->battery_detect = -1; + goto of_out; + } + + pdata->battery_detect = of_get_named_gpio_flags(of_node, + "ti,battery-detect-gpios", 0, &gpio_flags); + + if (gpio_flags & OF_GPIO_ACTIVE_LOW) + pdata->battery_detect_present = 0; + else + pdata->battery_detect_present = 1; + +of_out: + return pdata; +} +#else +#define bq20z75_dt_ids NULL +static struct bq20z75_platform_data *bq20z75_of_populate_pdata( + struct i2c_client *client) +{ + return client->dev.platform_data; +} +#endif + +static int __devinit bq20z75_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bq20z75_info *bq20z75_device; + struct bq20z75_platform_data *pdata = client->dev.platform_data; + int rc; + int irq; + + bq20z75_device = kzalloc(sizeof(struct bq20z75_info), GFP_KERNEL); + if (!bq20z75_device) + return -ENOMEM; + + bq20z75_device->client = client; + bq20z75_device->enable_detection = false; + bq20z75_device->gpio_detect = false; + bq20z75_device->power_supply.name = "battery"; + bq20z75_device->power_supply.type = POWER_SUPPLY_TYPE_BATTERY; + bq20z75_device->power_supply.properties = bq20z75_properties; + bq20z75_device->power_supply.num_properties = + ARRAY_SIZE(bq20z75_properties); + bq20z75_device->power_supply.get_property = bq20z75_get_property; + /* ignore first notification of external change, it is generated + * from the power_supply_register call back + */ + bq20z75_device->ignore_changes = 1; + bq20z75_device->last_state = POWER_SUPPLY_STATUS_UNKNOWN; + bq20z75_device->power_supply.external_power_changed = + bq20z75_external_power_changed; + + pdata = bq20z75_of_populate_pdata(client); + + if (pdata) { + bq20z75_device->gpio_detect = + gpio_is_valid(pdata->battery_detect); + bq20z75_device->pdata = pdata; + } + + i2c_set_clientdata(client, bq20z75_device); + + if (!bq20z75_device->gpio_detect) + goto skip_gpio; + + rc = gpio_request(pdata->battery_detect, dev_name(&client->dev)); + if (rc) { + dev_warn(&client->dev, "Failed to request gpio: %d\n", rc); + bq20z75_device->gpio_detect = false; + goto skip_gpio; + } + + rc = gpio_direction_input(pdata->battery_detect); + if (rc) { + dev_warn(&client->dev, "Failed to get gpio as input: %d\n", rc); + gpio_free(pdata->battery_detect); + bq20z75_device->gpio_detect = false; + goto skip_gpio; + } + + irq = gpio_to_irq(pdata->battery_detect); + if (irq <= 0) { + dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); + gpio_free(pdata->battery_detect); + bq20z75_device->gpio_detect = false; + goto skip_gpio; + } + + rc = request_irq(irq, bq20z75_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&client->dev), &bq20z75_device->power_supply); + if (rc) { + dev_warn(&client->dev, "Failed to request irq: %d\n", rc); + gpio_free(pdata->battery_detect); + bq20z75_device->gpio_detect = false; + goto skip_gpio; + } + + bq20z75_device->irq = irq; + +skip_gpio: + + rc = power_supply_register(&client->dev, &bq20z75_device->power_supply); + if (rc) { + dev_err(&client->dev, + "%s: Failed to register power supply\n", __func__); + goto exit_psupply; + } + + dev_info(&client->dev, + "%s: battery gas gauge device registered\n", client->name); + + INIT_DELAYED_WORK(&bq20z75_device->work, bq20z75_delayed_work); + + bq20z75_device->enable_detection = true; + + return 0; + +exit_psupply: + if (bq20z75_device->irq) + free_irq(bq20z75_device->irq, &bq20z75_device->power_supply); + if (bq20z75_device->gpio_detect) + gpio_free(pdata->battery_detect); + + kfree(bq20z75_device); + + return rc; +} + +static int __devexit bq20z75_remove(struct i2c_client *client) +{ + struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + + if (bq20z75_device->irq) + free_irq(bq20z75_device->irq, &bq20z75_device->power_supply); + if (bq20z75_device->gpio_detect) + gpio_free(bq20z75_device->pdata->battery_detect); + + power_supply_unregister(&bq20z75_device->power_supply); + + cancel_delayed_work_sync(&bq20z75_device->work); + + kfree(bq20z75_device); + bq20z75_device = NULL; + + return 0; +} + +#if defined CONFIG_PM +static int bq20z75_suspend(struct i2c_client *client, + pm_message_t state) +{ + struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + s32 ret; + + if (bq20z75_device->poll_time > 0) + cancel_delayed_work_sync(&bq20z75_device->work); + + /* write to manufacturer access with sleep command */ + ret = bq20z75_write_word_data(client, + bq20z75_data[REG_MANUFACTURER_DATA].addr, + MANUFACTURER_ACCESS_SLEEP); + if (bq20z75_device->is_present && ret < 0) + return ret; + + return 0; +} +#else +#define bq20z75_suspend NULL +#endif +/* any smbus transaction will wake up bq20z75 */ +#define bq20z75_resume NULL + +static const struct i2c_device_id bq20z75_id[] = { + { "bq20z75", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, bq20z75_id); + +static struct i2c_driver bq20z75_battery_driver = { + .probe = bq20z75_probe, + .remove = __devexit_p(bq20z75_remove), + .suspend = bq20z75_suspend, + .resume = bq20z75_resume, + .id_table = bq20z75_id, + .driver = { + .name = "bq20z75-battery", + .of_match_table = bq20z75_dt_ids, + }, +}; + +static int __init bq20z75_battery_init(void) +{ + return i2c_add_driver(&bq20z75_battery_driver); +} +module_init(bq20z75_battery_init); + +static void __exit bq20z75_battery_exit(void) +{ + i2c_del_driver(&bq20z75_battery_driver); +} +module_exit(bq20z75_battery_exit); + +MODULE_DESCRIPTION("BQ20z75 battery monitor driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/power/bq20z75.h b/include/linux/power/bq20z75.h deleted file mode 100644 index 1398eb004e83..000000000000 --- a/include/linux/power/bq20z75.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Gas Gauge driver for TI's BQ20Z75 - * - * Copyright (c) 2010, 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. - */ - -#ifndef __LINUX_POWER_BQ20Z75_H_ -#define __LINUX_POWER_BQ20Z75_H_ - -#include -#include - -/** - * struct bq20z75_platform_data - platform data for bq20z75 devices - * @battery_detect: GPIO which is used to detect battery presence - * @battery_detect_present: gpio state when battery is present (0 / 1) - * @i2c_retry_count: # of times to retry on i2c IO failure - * @poll_retry_count: # of times to retry looking for new status after - * external change notification - */ -struct bq20z75_platform_data { - int battery_detect; - int battery_detect_present; - int i2c_retry_count; - int poll_retry_count; -}; - -#endif diff --git a/include/linux/power/sbs-battery.h b/include/linux/power/sbs-battery.h new file mode 100644 index 000000000000..1398eb004e83 --- /dev/null +++ b/include/linux/power/sbs-battery.h @@ -0,0 +1,42 @@ +/* + * Gas Gauge driver for TI's BQ20Z75 + * + * Copyright (c) 2010, 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. + */ + +#ifndef __LINUX_POWER_BQ20Z75_H_ +#define __LINUX_POWER_BQ20Z75_H_ + +#include +#include + +/** + * struct bq20z75_platform_data - platform data for bq20z75 devices + * @battery_detect: GPIO which is used to detect battery presence + * @battery_detect_present: gpio state when battery is present (0 / 1) + * @i2c_retry_count: # of times to retry on i2c IO failure + * @poll_retry_count: # of times to retry looking for new status after + * external change notification + */ +struct bq20z75_platform_data { + int battery_detect; + int battery_detect_present; + int i2c_retry_count; + int poll_retry_count; +}; + +#endif -- cgit v1.2.3 From 3ddca062f8d71724529b0d52609994c9886f1a18 Mon Sep 17 00:00:00 2001 From: Rhyland Klein Date: Mon, 5 Dec 2011 17:50:46 -0800 Subject: sbs-battery: Rename internals to new name Now that this driver is named more generally, this change updates the internal variables, defines and functions to use this new name. Signed-off-by: Rhyland Klein Signed-off-by: Anton Vorontsov --- drivers/power/sbs-battery.c | 423 ++++++++++++++++++-------------------- include/linux/power/sbs-battery.h | 10 +- 2 files changed, 209 insertions(+), 224 deletions(-) (limited to 'include') diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c index ce95ff791016..00bd9e079e80 100644 --- a/drivers/power/sbs-battery.c +++ b/drivers/power/sbs-battery.c @@ -1,5 +1,5 @@ /* - * Gas Gauge driver for TI's BQ20Z75 + * Gas Gauge driver for SBS Compliant Batteries * * Copyright (c) 2010, NVIDIA Corporation. * @@ -28,7 +28,7 @@ #include #include -#include +#include enum { REG_MANUFACTURER_DATA, @@ -53,7 +53,7 @@ enum { /* Battery Mode defines */ #define BATTERY_MODE_OFFSET 0x03 #define BATTERY_MODE_MASK 0x8000 -enum bq20z75_battery_mode { +enum sbs_battery_mode { BATTERY_MODE_AMPS, BATTERY_MODE_WATTS }; @@ -67,62 +67,56 @@ enum bq20z75_battery_mode { #define BATTERY_FULL_CHARGED 0x20 #define BATTERY_FULL_DISCHARGED 0x10 -#define BQ20Z75_DATA(_psp, _addr, _min_value, _max_value) { \ +#define SBS_DATA(_psp, _addr, _min_value, _max_value) { \ .psp = _psp, \ .addr = _addr, \ .min_value = _min_value, \ .max_value = _max_value, \ } -static const struct bq20z75_device_data { +static const struct chip_data { enum power_supply_property psp; u8 addr; int min_value; int max_value; -} bq20z75_data[] = { +} sbs_data[] = { [REG_MANUFACTURER_DATA] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535), + SBS_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535), [REG_TEMPERATURE] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535), + SBS_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535), [REG_VOLTAGE] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000), + SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000), [REG_CURRENT] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, - 32767), + SBS_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 32767), [REG_CAPACITY] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0E, 0, 100), + SBS_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0E, 0, 100), [REG_REMAINING_CAPACITY] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535), + SBS_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535), [REG_REMAINING_CAPACITY_CHARGE] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_NOW, 0x0F, 0, 65535), + SBS_DATA(POWER_SUPPLY_PROP_CHARGE_NOW, 0x0F, 0, 65535), [REG_FULL_CHARGE_CAPACITY] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535), + SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535), [REG_FULL_CHARGE_CAPACITY_CHARGE] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535), + SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535), [REG_TIME_TO_EMPTY] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, - 65535), + SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, 65535), [REG_TIME_TO_FULL] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, - 65535), + SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, 65535), [REG_STATUS] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535), + SBS_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535), [REG_CYCLE_COUNT] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_CYCLE_COUNT, 0x17, 0, 65535), + SBS_DATA(POWER_SUPPLY_PROP_CYCLE_COUNT, 0x17, 0, 65535), [REG_DESIGN_CAPACITY] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0, - 65535), + SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0, 65535), [REG_DESIGN_CAPACITY_CHARGE] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0, - 65535), + SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0, 65535), [REG_DESIGN_VOLTAGE] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0, - 65535), + SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0, 65535), [REG_SERIAL_NUMBER] = - BQ20Z75_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535), + SBS_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535), }; -static enum power_supply_property bq20z75_properties[] = { +static enum power_supply_property sbs_properties[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, @@ -144,10 +138,10 @@ static enum power_supply_property bq20z75_properties[] = { POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, }; -struct bq20z75_info { +struct sbs_info { struct i2c_client *client; struct power_supply power_supply; - struct bq20z75_platform_data *pdata; + struct sbs_platform_data *pdata; bool is_present; bool gpio_detect; bool enable_detection; @@ -158,14 +152,14 @@ struct bq20z75_info { int ignore_changes; }; -static int bq20z75_read_word_data(struct i2c_client *client, u8 address) +static int sbs_read_word_data(struct i2c_client *client, u8 address) { - struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + struct sbs_info *chip = i2c_get_clientdata(client); s32 ret = 0; int retries = 1; - if (bq20z75_device->pdata) - retries = max(bq20z75_device->pdata->i2c_retry_count + 1, 1); + if (chip->pdata) + retries = max(chip->pdata->i2c_retry_count + 1, 1); while (retries > 0) { ret = i2c_smbus_read_word_data(client, address); @@ -184,15 +178,15 @@ static int bq20z75_read_word_data(struct i2c_client *client, u8 address) return le16_to_cpu(ret); } -static int bq20z75_write_word_data(struct i2c_client *client, u8 address, +static int sbs_write_word_data(struct i2c_client *client, u8 address, u16 value) { - struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + struct sbs_info *chip = i2c_get_clientdata(client); s32 ret = 0; int retries = 1; - if (bq20z75_device->pdata) - retries = max(bq20z75_device->pdata->i2c_retry_count + 1, 1); + if (chip->pdata) + retries = max(chip->pdata->i2c_retry_count + 1, 1); while (retries > 0) { ret = i2c_smbus_write_word_data(client, address, @@ -212,44 +206,41 @@ static int bq20z75_write_word_data(struct i2c_client *client, u8 address, return 0; } -static int bq20z75_get_battery_presence_and_health( +static int sbs_get_battery_presence_and_health( struct i2c_client *client, enum power_supply_property psp, union power_supply_propval *val) { s32 ret; - struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + struct sbs_info *chip = i2c_get_clientdata(client); if (psp == POWER_SUPPLY_PROP_PRESENT && - bq20z75_device->gpio_detect) { - ret = gpio_get_value( - bq20z75_device->pdata->battery_detect); - if (ret == bq20z75_device->pdata->battery_detect_present) + chip->gpio_detect) { + ret = gpio_get_value(chip->pdata->battery_detect); + if (ret == chip->pdata->battery_detect_present) val->intval = 1; else val->intval = 0; - bq20z75_device->is_present = val->intval; + chip->is_present = val->intval; return ret; } /* Write to ManufacturerAccess with * ManufacturerAccess command and then * read the status */ - ret = bq20z75_write_word_data(client, - bq20z75_data[REG_MANUFACTURER_DATA].addr, - MANUFACTURER_ACCESS_STATUS); + ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, + MANUFACTURER_ACCESS_STATUS); if (ret < 0) { if (psp == POWER_SUPPLY_PROP_PRESENT) val->intval = 0; /* battery removed */ return ret; } - ret = bq20z75_read_word_data(client, - bq20z75_data[REG_MANUFACTURER_DATA].addr); + ret = sbs_read_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr); if (ret < 0) return ret; - if (ret < bq20z75_data[REG_MANUFACTURER_DATA].min_value || - ret > bq20z75_data[REG_MANUFACTURER_DATA].max_value) { + if (ret < sbs_data[REG_MANUFACTURER_DATA].min_value || + ret > sbs_data[REG_MANUFACTURER_DATA].max_value) { val->intval = 0; return 0; } @@ -279,24 +270,23 @@ static int bq20z75_get_battery_presence_and_health( return 0; } -static int bq20z75_get_battery_property(struct i2c_client *client, +static int sbs_get_battery_property(struct i2c_client *client, int reg_offset, enum power_supply_property psp, union power_supply_propval *val) { - struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + struct sbs_info *chip = i2c_get_clientdata(client); s32 ret; - ret = bq20z75_read_word_data(client, - bq20z75_data[reg_offset].addr); + ret = sbs_read_word_data(client, sbs_data[reg_offset].addr); if (ret < 0) return ret; /* returned values are 16 bit */ - if (bq20z75_data[reg_offset].min_value < 0) + if (sbs_data[reg_offset].min_value < 0) ret = (s16)ret; - if (ret >= bq20z75_data[reg_offset].min_value && - ret <= bq20z75_data[reg_offset].max_value) { + if (ret >= sbs_data[reg_offset].min_value && + ret <= sbs_data[reg_offset].max_value) { val->intval = ret; if (psp != POWER_SUPPLY_PROP_STATUS) return 0; @@ -310,12 +300,12 @@ static int bq20z75_get_battery_property(struct i2c_client *client, else val->intval = POWER_SUPPLY_STATUS_CHARGING; - if (bq20z75_device->poll_time == 0) - bq20z75_device->last_state = val->intval; - else if (bq20z75_device->last_state != val->intval) { - cancel_delayed_work_sync(&bq20z75_device->work); - power_supply_changed(&bq20z75_device->power_supply); - bq20z75_device->poll_time = 0; + if (chip->poll_time == 0) + chip->last_state = val->intval; + else if (chip->last_state != val->intval) { + cancel_delayed_work_sync(&chip->work); + power_supply_changed(&chip->power_supply); + chip->poll_time = 0; } } else { if (psp == POWER_SUPPLY_PROP_STATUS) @@ -327,7 +317,7 @@ static int bq20z75_get_battery_property(struct i2c_client *client, return 0; } -static void bq20z75_unit_adjustment(struct i2c_client *client, +static void sbs_unit_adjustment(struct i2c_client *client, enum power_supply_property psp, union power_supply_propval *val) { #define BASE_UNIT_CONVERSION 1000 @@ -338,7 +328,7 @@ static void bq20z75_unit_adjustment(struct i2c_client *client, case POWER_SUPPLY_PROP_ENERGY_NOW: case POWER_SUPPLY_PROP_ENERGY_FULL: case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: - /* bq20z75 provides energy in units of 10mWh. + /* sbs provides energy in units of 10mWh. * Convert to µWh */ val->intval *= BATTERY_MODE_CAP_MULT_WATT; @@ -354,7 +344,7 @@ static void bq20z75_unit_adjustment(struct i2c_client *client, break; case POWER_SUPPLY_PROP_TEMP: - /* bq20z75 provides battery temperature in 0.1K + /* sbs provides battery temperature in 0.1K * so convert it to 0.1°C */ val->intval -= TEMP_KELVIN_TO_CELSIUS; @@ -362,7 +352,7 @@ static void bq20z75_unit_adjustment(struct i2c_client *client, case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: - /* bq20z75 provides time to empty and time to full in minutes. + /* sbs provides time to empty and time to full in minutes. * Convert to seconds */ val->intval *= TIME_UNIT_CONVERSION; @@ -374,13 +364,12 @@ static void bq20z75_unit_adjustment(struct i2c_client *client, } } -static enum bq20z75_battery_mode -bq20z75_set_battery_mode(struct i2c_client *client, - enum bq20z75_battery_mode mode) +static enum sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client, + enum sbs_battery_mode mode) { int ret, original_val; - original_val = bq20z75_read_word_data(client, BATTERY_MODE_OFFSET); + original_val = sbs_read_word_data(client, BATTERY_MODE_OFFSET); if (original_val < 0) return original_val; @@ -392,68 +381,67 @@ bq20z75_set_battery_mode(struct i2c_client *client, else ret = original_val | BATTERY_MODE_MASK; - ret = bq20z75_write_word_data(client, BATTERY_MODE_OFFSET, ret); + ret = sbs_write_word_data(client, BATTERY_MODE_OFFSET, ret); if (ret < 0) return ret; return original_val & BATTERY_MODE_MASK; } -static int bq20z75_get_battery_capacity(struct i2c_client *client, +static int sbs_get_battery_capacity(struct i2c_client *client, int reg_offset, enum power_supply_property psp, union power_supply_propval *val) { s32 ret; - enum bq20z75_battery_mode mode = BATTERY_MODE_WATTS; + enum sbs_battery_mode mode = BATTERY_MODE_WATTS; if (power_supply_is_amp_property(psp)) mode = BATTERY_MODE_AMPS; - mode = bq20z75_set_battery_mode(client, mode); + mode = sbs_set_battery_mode(client, mode); if (mode < 0) return mode; - ret = bq20z75_read_word_data(client, bq20z75_data[reg_offset].addr); + ret = sbs_read_word_data(client, sbs_data[reg_offset].addr); if (ret < 0) return ret; if (psp == POWER_SUPPLY_PROP_CAPACITY) { - /* bq20z75 spec says that this can be >100 % + /* sbs spec says that this can be >100 % * even if max value is 100 % */ val->intval = min(ret, 100); } else val->intval = ret; - ret = bq20z75_set_battery_mode(client, mode); + ret = sbs_set_battery_mode(client, mode); if (ret < 0) return ret; return 0; } -static char bq20z75_serial[5]; -static int bq20z75_get_battery_serial_number(struct i2c_client *client, +static char sbs_serial[5]; +static int sbs_get_battery_serial_number(struct i2c_client *client, union power_supply_propval *val) { int ret; - ret = bq20z75_read_word_data(client, - bq20z75_data[REG_SERIAL_NUMBER].addr); + ret = sbs_read_word_data(client, sbs_data[REG_SERIAL_NUMBER].addr); if (ret < 0) return ret; - ret = sprintf(bq20z75_serial, "%04x", ret); - val->strval = bq20z75_serial; + ret = sprintf(sbs_serial, "%04x", ret); + val->strval = sbs_serial; return 0; } -static int bq20z75_get_property_index(struct i2c_client *client, +static int sbs_get_property_index(struct i2c_client *client, enum power_supply_property psp) { int count; - for (count = 0; count < ARRAY_SIZE(bq20z75_data); count++) - if (psp == bq20z75_data[count].psp) + for (count = 0; count < ARRAY_SIZE(sbs_data); count++) + if (psp == sbs_data[count].psp) return count; dev_warn(&client->dev, @@ -462,19 +450,19 @@ static int bq20z75_get_property_index(struct i2c_client *client, return -EINVAL; } -static int bq20z75_get_property(struct power_supply *psy, +static int sbs_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { int ret = 0; - struct bq20z75_info *bq20z75_device = container_of(psy, - struct bq20z75_info, power_supply); - struct i2c_client *client = bq20z75_device->client; + struct sbs_info *chip = container_of(psy, + struct sbs_info, power_supply); + struct i2c_client *client = chip->client; switch (psp) { case POWER_SUPPLY_PROP_PRESENT: case POWER_SUPPLY_PROP_HEALTH: - ret = bq20z75_get_battery_presence_and_health(client, psp, val); + ret = sbs_get_battery_presence_and_health(client, psp, val); if (psp == POWER_SUPPLY_PROP_PRESENT) return 0; break; @@ -490,15 +478,15 @@ static int bq20z75_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CHARGE_FULL: case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: case POWER_SUPPLY_PROP_CAPACITY: - ret = bq20z75_get_property_index(client, psp); + ret = sbs_get_property_index(client, psp); if (ret < 0) break; - ret = bq20z75_get_battery_capacity(client, ret, psp, val); + ret = sbs_get_battery_capacity(client, ret, psp, val); break; case POWER_SUPPLY_PROP_SERIAL_NUMBER: - ret = bq20z75_get_battery_serial_number(client, val); + ret = sbs_get_battery_serial_number(client, val); break; case POWER_SUPPLY_PROP_STATUS: @@ -509,11 +497,11 @@ static int bq20z75_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - ret = bq20z75_get_property_index(client, psp); + ret = sbs_get_property_index(client, psp); if (ret < 0) break; - ret = bq20z75_get_battery_property(client, ret, psp, val); + ret = sbs_get_battery_property(client, ret, psp, val); break; default: @@ -522,25 +510,25 @@ static int bq20z75_get_property(struct power_supply *psy, return -EINVAL; } - if (!bq20z75_device->enable_detection) + if (!chip->enable_detection) goto done; - if (!bq20z75_device->gpio_detect && - bq20z75_device->is_present != (ret >= 0)) { - bq20z75_device->is_present = (ret >= 0); - power_supply_changed(&bq20z75_device->power_supply); + if (!chip->gpio_detect && + chip->is_present != (ret >= 0)) { + chip->is_present = (ret >= 0); + power_supply_changed(&chip->power_supply); } done: if (!ret) { /* Convert units to match requirements for power supply class */ - bq20z75_unit_adjustment(client, psp, val); + sbs_unit_adjustment(client, psp, val); } dev_dbg(&client->dev, "%s: property = %d, value = %x\n", __func__, psp, val->intval); - if (ret && bq20z75_device->is_present) + if (ret && chip->is_present) return ret; /* battery not present, so return NODATA for properties */ @@ -550,7 +538,7 @@ done: return 0; } -static irqreturn_t bq20z75_irq(int irq, void *devid) +static irqreturn_t sbs_irq(int irq, void *devid) { struct power_supply *battery = devid; @@ -559,36 +547,35 @@ static irqreturn_t bq20z75_irq(int irq, void *devid) return IRQ_HANDLED; } -static void bq20z75_external_power_changed(struct power_supply *psy) +static void sbs_external_power_changed(struct power_supply *psy) { - struct bq20z75_info *bq20z75_device; + struct sbs_info *chip; - bq20z75_device = container_of(psy, struct bq20z75_info, power_supply); + chip = container_of(psy, struct sbs_info, power_supply); - if (bq20z75_device->ignore_changes > 0) { - bq20z75_device->ignore_changes--; + if (chip->ignore_changes > 0) { + chip->ignore_changes--; return; } /* cancel outstanding work */ - cancel_delayed_work_sync(&bq20z75_device->work); + cancel_delayed_work_sync(&chip->work); - schedule_delayed_work(&bq20z75_device->work, HZ); - bq20z75_device->poll_time = bq20z75_device->pdata->poll_retry_count; + schedule_delayed_work(&chip->work, HZ); + chip->poll_time = chip->pdata->poll_retry_count; } -static void bq20z75_delayed_work(struct work_struct *work) +static void sbs_delayed_work(struct work_struct *work) { - struct bq20z75_info *bq20z75_device; + struct sbs_info *chip; s32 ret; - bq20z75_device = container_of(work, struct bq20z75_info, work.work); + chip = container_of(work, struct sbs_info, work.work); - ret = bq20z75_read_word_data(bq20z75_device->client, - bq20z75_data[REG_STATUS].addr); + ret = sbs_read_word_data(chip->client, sbs_data[REG_STATUS].addr); /* if the read failed, give up on this work */ if (ret < 0) { - bq20z75_device->poll_time = 0; + chip->poll_time = 0; return; } @@ -601,14 +588,14 @@ static void bq20z75_delayed_work(struct work_struct *work) else ret = POWER_SUPPLY_STATUS_CHARGING; - if (bq20z75_device->last_state != ret) { - bq20z75_device->poll_time = 0; - power_supply_changed(&bq20z75_device->power_supply); + if (chip->last_state != ret) { + chip->poll_time = 0; + power_supply_changed(&chip->power_supply); return; } - if (bq20z75_device->poll_time > 0) { - schedule_delayed_work(&bq20z75_device->work, HZ); - bq20z75_device->poll_time--; + if (chip->poll_time > 0) { + schedule_delayed_work(&chip->work, HZ); + chip->poll_time--; return; } } @@ -618,17 +605,18 @@ static void bq20z75_delayed_work(struct work_struct *work) #include #include -static const struct of_device_id bq20z75_dt_ids[] = { +static const struct of_device_id sbs_dt_ids[] = { + { .compatible = "sbs,sbs-battery" }, { .compatible = "ti,bq20z75" }, { } }; -MODULE_DEVICE_TABLE(i2c, bq20z75_dt_ids); +MODULE_DEVICE_TABLE(i2c, sbs_dt_ids); -static struct bq20z75_platform_data *bq20z75_of_populate_pdata( +static struct sbs_platform_data *sbs_of_populate_pdata( struct i2c_client *client) { struct device_node *of_node = client->dev.of_node; - struct bq20z75_platform_data *pdata = client->dev.platform_data; + struct sbs_platform_data *pdata = client->dev.platform_data; enum of_gpio_flags gpio_flags; int rc; u32 prop; @@ -644,31 +632,31 @@ static struct bq20z75_platform_data *bq20z75_of_populate_pdata( /* first make sure at least one property is set, otherwise * it won't change behavior from running without pdata. */ - if (!of_get_property(of_node, "ti,i2c-retry-count", NULL) && - !of_get_property(of_node, "ti,poll-retry-count", NULL) && - !of_get_property(of_node, "ti,battery-detect-gpios", NULL)) + if (!of_get_property(of_node, "sbs,i2c-retry-count", NULL) && + !of_get_property(of_node, "sbs,poll-retry-count", NULL) && + !of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) goto of_out; - pdata = devm_kzalloc(&client->dev, sizeof(struct bq20z75_platform_data), + pdata = devm_kzalloc(&client->dev, sizeof(struct sbs_platform_data), GFP_KERNEL); if (!pdata) goto of_out; - rc = of_property_read_u32(of_node, "ti,i2c-retry-count", &prop); + rc = of_property_read_u32(of_node, "sbs,i2c-retry-count", &prop); if (!rc) pdata->i2c_retry_count = prop; - rc = of_property_read_u32(of_node, "ti,poll-retry-count", &prop); + rc = of_property_read_u32(of_node, "sbs,poll-retry-count", &prop); if (!rc) pdata->poll_retry_count = prop; - if (!of_get_property(of_node, "ti,battery-detect-gpios", NULL)) { + if (!of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) { pdata->battery_detect = -1; goto of_out; } pdata->battery_detect = of_get_named_gpio_flags(of_node, - "ti,battery-detect-gpios", 0, &gpio_flags); + "sbs,battery-detect-gpios", 0, &gpio_flags); if (gpio_flags & OF_GPIO_ACTIVE_LOW) pdata->battery_detect_present = 0; @@ -679,60 +667,57 @@ of_out: return pdata; } #else -#define bq20z75_dt_ids NULL -static struct bq20z75_platform_data *bq20z75_of_populate_pdata( +#define sbs_dt_ids NULL +static struct sbs_platform_data *sbs_of_populate_pdata( struct i2c_client *client) { return client->dev.platform_data; } #endif -static int __devinit bq20z75_probe(struct i2c_client *client, +static int __devinit sbs_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct bq20z75_info *bq20z75_device; - struct bq20z75_platform_data *pdata = client->dev.platform_data; + struct sbs_info *chip; + struct sbs_platform_data *pdata = client->dev.platform_data; int rc; int irq; - bq20z75_device = kzalloc(sizeof(struct bq20z75_info), GFP_KERNEL); - if (!bq20z75_device) + chip = kzalloc(sizeof(struct sbs_info), GFP_KERNEL); + if (!chip) return -ENOMEM; - bq20z75_device->client = client; - bq20z75_device->enable_detection = false; - bq20z75_device->gpio_detect = false; - bq20z75_device->power_supply.name = "battery"; - bq20z75_device->power_supply.type = POWER_SUPPLY_TYPE_BATTERY; - bq20z75_device->power_supply.properties = bq20z75_properties; - bq20z75_device->power_supply.num_properties = - ARRAY_SIZE(bq20z75_properties); - bq20z75_device->power_supply.get_property = bq20z75_get_property; + chip->client = client; + chip->enable_detection = false; + chip->gpio_detect = false; + chip->power_supply.name = "battery"; + chip->power_supply.type = POWER_SUPPLY_TYPE_BATTERY; + chip->power_supply.properties = sbs_properties; + chip->power_supply.num_properties = ARRAY_SIZE(sbs_properties); + chip->power_supply.get_property = sbs_get_property; /* ignore first notification of external change, it is generated * from the power_supply_register call back */ - bq20z75_device->ignore_changes = 1; - bq20z75_device->last_state = POWER_SUPPLY_STATUS_UNKNOWN; - bq20z75_device->power_supply.external_power_changed = - bq20z75_external_power_changed; + chip->ignore_changes = 1; + chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN; + chip->power_supply.external_power_changed = sbs_external_power_changed; - pdata = bq20z75_of_populate_pdata(client); + pdata = sbs_of_populate_pdata(client); if (pdata) { - bq20z75_device->gpio_detect = - gpio_is_valid(pdata->battery_detect); - bq20z75_device->pdata = pdata; + chip->gpio_detect = gpio_is_valid(pdata->battery_detect); + chip->pdata = pdata; } - i2c_set_clientdata(client, bq20z75_device); + i2c_set_clientdata(client, chip); - if (!bq20z75_device->gpio_detect) + if (!chip->gpio_detect) goto skip_gpio; rc = gpio_request(pdata->battery_detect, dev_name(&client->dev)); if (rc) { dev_warn(&client->dev, "Failed to request gpio: %d\n", rc); - bq20z75_device->gpio_detect = false; + chip->gpio_detect = false; goto skip_gpio; } @@ -740,7 +725,7 @@ static int __devinit bq20z75_probe(struct i2c_client *client, if (rc) { dev_warn(&client->dev, "Failed to get gpio as input: %d\n", rc); gpio_free(pdata->battery_detect); - bq20z75_device->gpio_detect = false; + chip->gpio_detect = false; goto skip_gpio; } @@ -748,25 +733,25 @@ static int __devinit bq20z75_probe(struct i2c_client *client, if (irq <= 0) { dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); gpio_free(pdata->battery_detect); - bq20z75_device->gpio_detect = false; + chip->gpio_detect = false; goto skip_gpio; } - rc = request_irq(irq, bq20z75_irq, + rc = request_irq(irq, sbs_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - dev_name(&client->dev), &bq20z75_device->power_supply); + dev_name(&client->dev), &chip->power_supply); if (rc) { dev_warn(&client->dev, "Failed to request irq: %d\n", rc); gpio_free(pdata->battery_detect); - bq20z75_device->gpio_detect = false; + chip->gpio_detect = false; goto skip_gpio; } - bq20z75_device->irq = irq; + chip->irq = irq; skip_gpio: - rc = power_supply_register(&client->dev, &bq20z75_device->power_supply); + rc = power_supply_register(&client->dev, &chip->power_supply); if (rc) { dev_err(&client->dev, "%s: Failed to register power supply\n", __func__); @@ -776,96 +761,96 @@ skip_gpio: dev_info(&client->dev, "%s: battery gas gauge device registered\n", client->name); - INIT_DELAYED_WORK(&bq20z75_device->work, bq20z75_delayed_work); + INIT_DELAYED_WORK(&chip->work, sbs_delayed_work); - bq20z75_device->enable_detection = true; + chip->enable_detection = true; return 0; exit_psupply: - if (bq20z75_device->irq) - free_irq(bq20z75_device->irq, &bq20z75_device->power_supply); - if (bq20z75_device->gpio_detect) + if (chip->irq) + free_irq(chip->irq, &chip->power_supply); + if (chip->gpio_detect) gpio_free(pdata->battery_detect); - kfree(bq20z75_device); + kfree(chip); return rc; } -static int __devexit bq20z75_remove(struct i2c_client *client) +static int __devexit sbs_remove(struct i2c_client *client) { - struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + struct sbs_info *chip = i2c_get_clientdata(client); - if (bq20z75_device->irq) - free_irq(bq20z75_device->irq, &bq20z75_device->power_supply); - if (bq20z75_device->gpio_detect) - gpio_free(bq20z75_device->pdata->battery_detect); + if (chip->irq) + free_irq(chip->irq, &chip->power_supply); + if (chip->gpio_detect) + gpio_free(chip->pdata->battery_detect); - power_supply_unregister(&bq20z75_device->power_supply); + power_supply_unregister(&chip->power_supply); - cancel_delayed_work_sync(&bq20z75_device->work); + cancel_delayed_work_sync(&chip->work); - kfree(bq20z75_device); - bq20z75_device = NULL; + kfree(chip); + chip = NULL; return 0; } #if defined CONFIG_PM -static int bq20z75_suspend(struct i2c_client *client, +static int sbs_suspend(struct i2c_client *client, pm_message_t state) { - struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + struct sbs_info *chip = i2c_get_clientdata(client); s32 ret; - if (bq20z75_device->poll_time > 0) - cancel_delayed_work_sync(&bq20z75_device->work); + if (chip->poll_time > 0) + cancel_delayed_work_sync(&chip->work); /* write to manufacturer access with sleep command */ - ret = bq20z75_write_word_data(client, - bq20z75_data[REG_MANUFACTURER_DATA].addr, + ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, MANUFACTURER_ACCESS_SLEEP); - if (bq20z75_device->is_present && ret < 0) + if (chip->is_present && ret < 0) return ret; return 0; } #else -#define bq20z75_suspend NULL +#define sbs_suspend NULL #endif -/* any smbus transaction will wake up bq20z75 */ -#define bq20z75_resume NULL +/* any smbus transaction will wake up sbs */ +#define sbs_resume NULL -static const struct i2c_device_id bq20z75_id[] = { +static const struct i2c_device_id sbs_id[] = { { "bq20z75", 0 }, + { "sbs-battery", 1 }, {} }; -MODULE_DEVICE_TABLE(i2c, bq20z75_id); - -static struct i2c_driver bq20z75_battery_driver = { - .probe = bq20z75_probe, - .remove = __devexit_p(bq20z75_remove), - .suspend = bq20z75_suspend, - .resume = bq20z75_resume, - .id_table = bq20z75_id, +MODULE_DEVICE_TABLE(i2c, sbs_id); + +static struct i2c_driver sbs_battery_driver = { + .probe = sbs_probe, + .remove = __devexit_p(sbs_remove), + .suspend = sbs_suspend, + .resume = sbs_resume, + .id_table = sbs_id, .driver = { - .name = "bq20z75-battery", - .of_match_table = bq20z75_dt_ids, + .name = "sbs-battery", + .of_match_table = sbs_dt_ids, }, }; -static int __init bq20z75_battery_init(void) +static int __init sbs_battery_init(void) { - return i2c_add_driver(&bq20z75_battery_driver); + return i2c_add_driver(&sbs_battery_driver); } -module_init(bq20z75_battery_init); +module_init(sbs_battery_init); -static void __exit bq20z75_battery_exit(void) +static void __exit sbs_battery_exit(void) { - i2c_del_driver(&bq20z75_battery_driver); + i2c_del_driver(&sbs_battery_driver); } -module_exit(bq20z75_battery_exit); +module_exit(sbs_battery_exit); -MODULE_DESCRIPTION("BQ20z75 battery monitor driver"); +MODULE_DESCRIPTION("SBS battery monitor driver"); MODULE_LICENSE("GPL"); diff --git a/include/linux/power/sbs-battery.h b/include/linux/power/sbs-battery.h index 1398eb004e83..2b0a9d9ff57e 100644 --- a/include/linux/power/sbs-battery.h +++ b/include/linux/power/sbs-battery.h @@ -1,5 +1,5 @@ /* - * Gas Gauge driver for TI's BQ20Z75 + * Gas Gauge driver for SBS Compliant Gas Gauges * * Copyright (c) 2010, NVIDIA Corporation. * @@ -18,21 +18,21 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef __LINUX_POWER_BQ20Z75_H_ -#define __LINUX_POWER_BQ20Z75_H_ +#ifndef __LINUX_POWER_SBS_BATTERY_H_ +#define __LINUX_POWER_SBS_BATTERY_H_ #include #include /** - * struct bq20z75_platform_data - platform data for bq20z75 devices + * struct sbs_platform_data - platform data for sbs devices * @battery_detect: GPIO which is used to detect battery presence * @battery_detect_present: gpio state when battery is present (0 / 1) * @i2c_retry_count: # of times to retry on i2c IO failure * @poll_retry_count: # of times to retry looking for new status after * external change notification */ -struct bq20z75_platform_data { +struct sbs_platform_data { int battery_detect; int battery_detect_present; int i2c_retry_count; -- cgit v1.2.3 From 34aed73df3a9e75e313a7510b201f6755ae3e6bc Mon Sep 17 00:00:00 2001 From: Heiko Stübner Date: Thu, 29 Dec 2011 12:52:07 +0100 Subject: s3c_adc_battery: Average over more than one adc sample Some sources for adc battery information provide only inaccurate results where the read value differs from the real value with positive and negative offsets. For such sources it can be more accurate to collect two or more value sample and use the average of all collected values. This patch adds pdata options volt_samples, current_samples and backup_volt_samples to specifiy the number of samples to collect, reads the specified number of samples and calculates the average of those. For unset sample-number-values a default of 1 is assumed. Signed-off-by: Heiko Stuebner Signed-off-by: Anton Vorontsov --- drivers/power/s3c_adc_battery.c | 25 ++++++++++++++++++++++--- include/linux/s3c_adc_battery.h | 4 ++++ 2 files changed, 26 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/power/s3c_adc_battery.c b/drivers/power/s3c_adc_battery.c index e687ee7f18f2..8b804a566756 100644 --- a/drivers/power/s3c_adc_battery.c +++ b/drivers/power/s3c_adc_battery.c @@ -47,6 +47,22 @@ static void s3c_adc_bat_ext_power_changed(struct power_supply *psy) msecs_to_jiffies(JITTER_DELAY)); } +static int gather_samples(struct s3c_adc_client *client, int num, int channel) +{ + int value, i; + + /* default to 1 if nothing is set */ + if (num < 1) + num = 1; + + value = 0; + for (i = 0; i < num; i++) + value += s3c_adc_read(client, channel); + value /= num; + + return value; +} + static enum power_supply_property s3c_adc_backup_bat_props[] = { POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_MIN, @@ -67,7 +83,8 @@ static int s3c_adc_backup_bat_get_property(struct power_supply *psy, if (bat->volt_value < 0 || jiffies_to_msecs(jiffies - bat->timestamp) > BAT_POLL_INTERVAL) { - bat->volt_value = s3c_adc_read(bat->client, + bat->volt_value = gather_samples(bat->client, + bat->pdata->backup_volt_samples, bat->pdata->backup_volt_channel); bat->volt_value *= bat->pdata->backup_volt_mult; bat->timestamp = jiffies; @@ -139,9 +156,11 @@ static int s3c_adc_bat_get_property(struct power_supply *psy, if (bat->volt_value < 0 || bat->cur_value < 0 || jiffies_to_msecs(jiffies - bat->timestamp) > BAT_POLL_INTERVAL) { - bat->volt_value = s3c_adc_read(bat->client, + bat->volt_value = gather_samples(bat->client, + bat->pdata->volt_samples, bat->pdata->volt_channel) * bat->pdata->volt_mult; - bat->cur_value = s3c_adc_read(bat->client, + bat->cur_value = gather_samples(bat->client, + bat->pdata->current_samples, bat->pdata->current_channel) * bat->pdata->current_mult; bat->timestamp = jiffies; } diff --git a/include/linux/s3c_adc_battery.h b/include/linux/s3c_adc_battery.h index fbe58b7e63eb..99dadbffdd4f 100644 --- a/include/linux/s3c_adc_battery.h +++ b/include/linux/s3c_adc_battery.h @@ -25,6 +25,10 @@ struct s3c_adc_bat_pdata { const unsigned int current_channel; const unsigned int backup_volt_channel; + const unsigned int volt_samples; + const unsigned int current_samples; + const unsigned int backup_volt_samples; + const unsigned int volt_mult; const unsigned int current_mult; const unsigned int backup_volt_mult; -- cgit v1.2.3