diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2009-06-24 14:35:57 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-06-24 14:35:57 -0700 |
commit | f27884aeadff9654f4a1e8a05dd92f4b140afe29 (patch) | |
tree | 22db0c732fcc4e68be25190454631ee1d249e295 | |
parent | c6223048259006759237d826219f0fa4f312fb47 (diff) | |
parent | a35d01a5d2ac533edab94a8e3b6749ab213c91c5 (diff) | |
download | linux-f27884aeadff9654f4a1e8a05dd92f4b140afe29.tar.gz linux-f27884aeadff9654f4a1e8a05dd92f4b140afe29.tar.bz2 linux-f27884aeadff9654f4a1e8a05dd92f4b140afe29.zip |
Merge git://git.infradead.org/battery-2.6
* git://git.infradead.org/battery-2.6:
da9030_battery: Fix race between event handler and monitor
Add MAX17040 Fuel Gauge driver
w1: ds2760_battery: add support for sleep mode feature
w1: ds2760: add support for EEPROM read and write
ds2760_battery: cleanups in ds2760_battery_probe()
-rw-r--r-- | drivers/power/Kconfig | 8 | ||||
-rw-r--r-- | drivers/power/Makefile | 3 | ||||
-rw-r--r-- | drivers/power/da9030_battery.c | 19 | ||||
-rw-r--r-- | drivers/power/ds2760_battery.c | 42 | ||||
-rw-r--r-- | drivers/power/max17040_battery.c | 309 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2760.c | 30 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2760.h | 7 | ||||
-rw-r--r-- | include/linux/max17040_battery.h | 19 |
8 files changed, 420 insertions, 17 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 33da1127992a..7eda34838bfe 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -82,6 +82,14 @@ config BATTERY_DA9030 Say Y here to enable support for batteries charger integrated into DA9030 PMIC. +config BATTERY_MAX17040 + tristate "Maxim MAX17040 Fuel Gauge" + depends on I2C + help + MAX17040 is fuel-gauge systems for lithium-ion (Li+) batteries + in handheld and portable equipment. The MAX17040 is configured + to operate with a single lithium cell + config CHARGER_PCF50633 tristate "NXP PCF50633 MBC" depends on MFD_PCF50633 diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 2fcf41d13e5c..daf3179689aa 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -25,4 +25,5 @@ obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o -obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
\ No newline at end of file +obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o +obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o diff --git a/drivers/power/da9030_battery.c b/drivers/power/da9030_battery.c index 1662bb0f23a5..3364198134a1 100644 --- a/drivers/power/da9030_battery.c +++ b/drivers/power/da9030_battery.c @@ -22,8 +22,6 @@ #include <linux/debugfs.h> #include <linux/seq_file.h> -#define DA9030_STATUS_CHDET (1 << 3) - #define DA9030_FAULT_LOG 0x0a #define DA9030_FAULT_LOG_OVER_TEMP (1 << 7) #define DA9030_FAULT_LOG_VBAT_OVER (1 << 4) @@ -244,6 +242,8 @@ static void da9030_set_charge(struct da9030_charger *charger, int on) } da903x_write(charger->master, DA9030_CHARGE_CONTROL, val); + + power_supply_changed(&charger->psy); } static void da9030_charger_check_state(struct da9030_charger *charger) @@ -258,6 +258,12 @@ static void da9030_charger_check_state(struct da9030_charger *charger) da9030_set_charge(charger, 1); } } else { + /* Charger has been pulled out */ + if (!charger->chdet) { + da9030_set_charge(charger, 0); + return; + } + if (charger->adc.vbat_res >= charger->thresholds.vbat_charge_stop) { da9030_set_charge(charger, 0); @@ -395,13 +401,11 @@ static int da9030_battery_event(struct notifier_block *nb, unsigned long event, { struct da9030_charger *charger = container_of(nb, struct da9030_charger, nb); - int status; switch (event) { case DA9030_EVENT_CHDET: - status = da903x_query_status(charger->master, - DA9030_STATUS_CHDET); - da9030_set_charge(charger, status); + cancel_delayed_work_sync(&charger->work); + schedule_work(&charger->work.work); break; case DA9030_EVENT_VBATMON: da9030_battery_vbat_event(charger); @@ -565,7 +569,8 @@ static int da9030_battery_remove(struct platform_device *dev) da903x_unregister_notifier(charger->master, &charger->nb, DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON | DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT); - cancel_delayed_work(&charger->work); + cancel_delayed_work_sync(&charger->work); + da9030_set_charge(charger, 0); power_supply_unregister(&charger->psy); kfree(charger); diff --git a/drivers/power/ds2760_battery.c b/drivers/power/ds2760_battery.c index a52d4a11652d..520b5c49ff30 100644 --- a/drivers/power/ds2760_battery.c +++ b/drivers/power/ds2760_battery.c @@ -62,6 +62,10 @@ static unsigned int cache_time = 1000; module_param(cache_time, uint, 0644); MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); +static unsigned int pmod_enabled; +module_param(pmod_enabled, bool, 0644); +MODULE_PARM_DESC(pmod_enabled, "PMOD enable bit"); + /* Some batteries have their rated capacity stored a N * 10 mAh, while * others use an index into this table. */ static int rated_capacities[] = { @@ -259,6 +263,17 @@ static void ds2760_battery_update_status(struct ds2760_device_info *di) power_supply_changed(&di->bat); } +static void ds2760_battery_write_status(struct ds2760_device_info *di, + char status) +{ + if (status == di->raw[DS2760_STATUS_REG]) + return; + + w1_ds2760_write(di->w1_dev, &status, DS2760_STATUS_WRITE_REG, 1); + w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); + w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1); +} + static void ds2760_battery_work(struct work_struct *work) { struct ds2760_device_info *di = container_of(work, @@ -342,9 +357,9 @@ static enum power_supply_property ds2760_battery_props[] = { static int ds2760_battery_probe(struct platform_device *pdev) { + char status; int retval = 0; struct ds2760_device_info *di; - struct ds2760_platform_data *pdata; di = kzalloc(sizeof(*di), GFP_KERNEL); if (!di) { @@ -354,14 +369,13 @@ static int ds2760_battery_probe(struct platform_device *pdev) platform_set_drvdata(pdev, di); - pdata = pdev->dev.platform_data; - di->dev = &pdev->dev; - di->w1_dev = pdev->dev.parent; - di->bat.name = dev_name(&pdev->dev); - di->bat.type = POWER_SUPPLY_TYPE_BATTERY; - di->bat.properties = ds2760_battery_props; - di->bat.num_properties = ARRAY_SIZE(ds2760_battery_props); - di->bat.get_property = ds2760_battery_get_property; + di->dev = &pdev->dev; + di->w1_dev = pdev->dev.parent; + di->bat.name = dev_name(&pdev->dev); + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = ds2760_battery_props; + di->bat.num_properties = ARRAY_SIZE(ds2760_battery_props); + di->bat.get_property = ds2760_battery_get_property; di->bat.external_power_changed = ds2760_battery_external_power_changed; @@ -373,6 +387,16 @@ static int ds2760_battery_probe(struct platform_device *pdev) goto batt_failed; } + /* enable sleep mode feature */ + ds2760_battery_read_status(di); + status = di->raw[DS2760_STATUS_REG]; + if (pmod_enabled) + status |= DS2760_STATUS_PMOD; + else + status &= ~DS2760_STATUS_PMOD; + + ds2760_battery_write_status(di, status); + INIT_DELAYED_WORK(&di->monitor_work, ds2760_battery_work); di->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); if (!di->monitor_wqueue) { diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c new file mode 100644 index 000000000000..87b98bf27ae1 --- /dev/null +++ b/drivers/power/max17040_battery.c @@ -0,0 +1,309 @@ +/* + * max17040_battery.c + * fuel-gauge systems for lithium-ion (Li+) batteries + * + * Copyright (C) 2009 Samsung Electronics + * Minkyu Kang <mk7.kang@samsung.com> + * + * 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 <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/power_supply.h> +#include <linux/max17040_battery.h> + +#define MAX17040_VCELL_MSB 0x02 +#define MAX17040_VCELL_LSB 0x03 +#define MAX17040_SOC_MSB 0x04 +#define MAX17040_SOC_LSB 0x05 +#define MAX17040_MODE_MSB 0x06 +#define MAX17040_MODE_LSB 0x07 +#define MAX17040_VER_MSB 0x08 +#define MAX17040_VER_LSB 0x09 +#define MAX17040_RCOMP_MSB 0x0C +#define MAX17040_RCOMP_LSB 0x0D +#define MAX17040_CMD_MSB 0xFE +#define MAX17040_CMD_LSB 0xFF + +#define MAX17040_DELAY 1000 +#define MAX17040_BATTERY_FULL 95 + +struct max17040_chip { + struct i2c_client *client; + struct delayed_work work; + struct power_supply battery; + struct max17040_platform_data *pdata; + + /* State Of Connect */ + int online; + /* battery voltage */ + int vcell; + /* battery capacity */ + int soc; + /* State Of Charge */ + int status; +}; + +static int max17040_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max17040_chip *chip = container_of(psy, + struct max17040_chip, battery); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = chip->status; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = chip->online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = chip->vcell; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = chip->soc; + break; + default: + return -EINVAL; + } + return 0; +} + +static int max17040_write_reg(struct i2c_client *client, int reg, u8 value) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, value); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static int max17040_read_reg(struct i2c_client *client, int reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static void max17040_reset(struct i2c_client *client) +{ + max17040_write_reg(client, MAX17040_CMD_MSB, 0x54); + max17040_write_reg(client, MAX17040_CMD_LSB, 0x00); +} + +static void max17040_get_vcell(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + u8 msb; + u8 lsb; + + msb = max17040_read_reg(client, MAX17040_VCELL_MSB); + lsb = max17040_read_reg(client, MAX17040_VCELL_LSB); + + chip->vcell = (msb << 4) + (lsb >> 4); +} + +static void max17040_get_soc(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + u8 msb; + u8 lsb; + + msb = max17040_read_reg(client, MAX17040_SOC_MSB); + lsb = max17040_read_reg(client, MAX17040_SOC_LSB); + + chip->soc = msb; +} + +static void max17040_get_version(struct i2c_client *client) +{ + u8 msb; + u8 lsb; + + msb = max17040_read_reg(client, MAX17040_VER_MSB); + lsb = max17040_read_reg(client, MAX17040_VER_LSB); + + dev_info(&client->dev, "MAX17040 Fuel-Gauge Ver %d%d\n", msb, lsb); +} + +static void max17040_get_online(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + + if (chip->pdata->battery_online) + chip->online = chip->pdata->battery_online(); + else + chip->online = 1; +} + +static void max17040_get_status(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + + if (!chip->pdata->charger_online || !chip->pdata->charger_enable) { + chip->status = POWER_SUPPLY_STATUS_UNKNOWN; + return; + } + + if (chip->pdata->charger_online()) { + if (chip->pdata->charger_enable()) + chip->status = POWER_SUPPLY_STATUS_CHARGING; + else + chip->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + chip->status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + if (chip->soc > MAX17040_BATTERY_FULL) + chip->status = POWER_SUPPLY_STATUS_FULL; +} + +static void max17040_work(struct work_struct *work) +{ + struct max17040_chip *chip; + + chip = container_of(work, struct max17040_chip, work.work); + + max17040_get_vcell(chip->client); + max17040_get_soc(chip->client); + max17040_get_online(chip->client); + max17040_get_status(chip->client); + + schedule_delayed_work(&chip->work, MAX17040_DELAY); +} + +static enum power_supply_property max17040_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static int __devinit max17040_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct max17040_chip *chip; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, chip); + + chip->battery.name = "battery"; + chip->battery.type = POWER_SUPPLY_TYPE_BATTERY; + chip->battery.get_property = max17040_get_property; + chip->battery.properties = max17040_battery_props; + chip->battery.num_properties = ARRAY_SIZE(max17040_battery_props); + + ret = power_supply_register(&client->dev, &chip->battery); + if (ret) { + dev_err(&client->dev, "failed: power supply register\n"); + i2c_set_clientdata(client, NULL); + kfree(chip); + return ret; + } + + max17040_reset(client); + max17040_get_version(client); + + INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work); + schedule_delayed_work(&chip->work, MAX17040_DELAY); + + return 0; +} + +static int __devexit max17040_remove(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + + power_supply_unregister(&chip->battery); + cancel_delayed_work(&chip->work); + i2c_set_clientdata(client, NULL); + kfree(chip); + return 0; +} + +#ifdef CONFIG_PM + +static int max17040_suspend(struct i2c_client *client, + pm_message_t state) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + + cancel_delayed_work(&chip->work); + return 0; +} + +static int max17040_resume(struct i2c_client *client) +{ + struct max17040_chip *chip = i2c_get_clientdata(client); + + schedule_delayed_work(&chip->work, MAX17040_DELAY); + return 0; +} + +#else + +#define max17040_suspend NULL +#define max17040_resume NULL + +#endif /* CONFIG_PM */ + +static const struct i2c_device_id max17040_id[] = { + { "max17040", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max17040_id); + +static struct i2c_driver max17040_i2c_driver = { + .driver = { + .name = "max17040", + }, + .probe = max17040_probe, + .remove = __devexit_p(max17040_remove), + .suspend = max17040_suspend, + .resume = max17040_resume, + .id_table = max17040_id, +}; + +static int __init max17040_init(void) +{ + return i2c_add_driver(&max17040_i2c_driver); +} +module_init(max17040_init); + +static void __exit max17040_exit(void) +{ + i2c_del_driver(&max17040_i2c_driver); +} +module_exit(max17040_exit); + +MODULE_AUTHOR("Minkyu Kang <mk7.kang@samsung.com>"); +MODULE_DESCRIPTION("MAX17040 Fuel Gauge"); +MODULE_LICENSE("GPL"); diff --git a/drivers/w1/slaves/w1_ds2760.c b/drivers/w1/slaves/w1_ds2760.c index 1f09d4e4144c..59f708efe25f 100644 --- a/drivers/w1/slaves/w1_ds2760.c +++ b/drivers/w1/slaves/w1_ds2760.c @@ -68,6 +68,34 @@ int w1_ds2760_write(struct device *dev, char *buf, int addr, size_t count) return w1_ds2760_io(dev, buf, addr, count, 1); } +static int w1_ds2760_eeprom_cmd(struct device *dev, int addr, int cmd) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + + if (!dev) + return -EINVAL; + + mutex_lock(&sl->master->mutex); + + if (w1_reset_select_slave(sl) == 0) { + w1_write_8(sl->master, cmd); + w1_write_8(sl->master, addr); + } + + mutex_unlock(&sl->master->mutex); + return 0; +} + +int w1_ds2760_store_eeprom(struct device *dev, int addr) +{ + return w1_ds2760_eeprom_cmd(dev, addr, W1_DS2760_COPY_DATA); +} + +int w1_ds2760_recall_eeprom(struct device *dev, int addr) +{ + return w1_ds2760_eeprom_cmd(dev, addr, W1_DS2760_RECALL_DATA); +} + static ssize_t w1_ds2760_read_bin(struct kobject *kobj, struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) @@ -200,6 +228,8 @@ static void __exit w1_ds2760_exit(void) EXPORT_SYMBOL(w1_ds2760_read); EXPORT_SYMBOL(w1_ds2760_write); +EXPORT_SYMBOL(w1_ds2760_store_eeprom); +EXPORT_SYMBOL(w1_ds2760_recall_eeprom); module_init(w1_ds2760_init); module_exit(w1_ds2760_exit); diff --git a/drivers/w1/slaves/w1_ds2760.h b/drivers/w1/slaves/w1_ds2760.h index f1302429cb02..58e774141568 100644 --- a/drivers/w1/slaves/w1_ds2760.h +++ b/drivers/w1/slaves/w1_ds2760.h @@ -25,6 +25,10 @@ #define DS2760_PROTECTION_REG 0x00 #define DS2760_STATUS_REG 0x01 + #define DS2760_STATUS_IE (1 << 2) + #define DS2760_STATUS_SWEN (1 << 3) + #define DS2760_STATUS_RNAOP (1 << 4) + #define DS2760_STATUS_PMOD (1 << 5) #define DS2760_EEPROM_REG 0x07 #define DS2760_SPECIAL_FEATURE_REG 0x08 #define DS2760_VOLTAGE_MSB 0x0c @@ -38,6 +42,7 @@ #define DS2760_EEPROM_BLOCK0 0x20 #define DS2760_ACTIVE_FULL 0x20 #define DS2760_EEPROM_BLOCK1 0x30 +#define DS2760_STATUS_WRITE_REG 0x31 #define DS2760_RATED_CAPACITY 0x32 #define DS2760_CURRENT_OFFSET_BIAS 0x33 #define DS2760_ACTIVE_EMPTY 0x3b @@ -46,5 +51,7 @@ extern int w1_ds2760_read(struct device *dev, char *buf, int addr, size_t count); extern int w1_ds2760_write(struct device *dev, char *buf, int addr, size_t count); +extern int w1_ds2760_store_eeprom(struct device *dev, int addr); +extern int w1_ds2760_recall_eeprom(struct device *dev, int addr); #endif /* !__w1_ds2760_h__ */ diff --git a/include/linux/max17040_battery.h b/include/linux/max17040_battery.h new file mode 100644 index 000000000000..ad97b06cf930 --- /dev/null +++ b/include/linux/max17040_battery.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2009 Samsung Electronics + * Minkyu Kang <mk7.kang@samsung.com> + * + * 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 __MAX17040_BATTERY_H_ +#define __MAX17040_BATTERY_H_ + +struct max17040_platform_data { + int (*battery_online)(void); + int (*charger_online)(void); + int (*charger_enable)(void); +}; + +#endif |