From ec46475f3e3163dd80bfee086fa71b36455ecc0b Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Thu, 19 Aug 2010 15:09:36 +0300 Subject: [PATCH 01/17] power_supply: Add isp1704 charger detection driver NXP ISP1704 is Battery Charging Specification 1.0 compliant USB transceiver. This adds a power supply driver for ISP1704 and ISP1707 USB transceivers. Signed-off-by: Heikki Krogerus Signed-off-by: Anton Vorontsov --- drivers/power/Kconfig | 7 + drivers/power/Makefile | 1 + drivers/power/isp1704_charger.c | 369 ++++++++++++++++++++++++++++++++ 3 files changed, 377 insertions(+) create mode 100644 drivers/power/isp1704_charger.c diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 07343568a12e..c2a1b8b713f6 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -166,4 +166,11 @@ config BATTERY_INTEL_MID Say Y here to enable the battery driver on Intel MID platforms. +config CHARGER_ISP1704 + tristate "ISP1704 USB Charger Detection" + depends on USB_OTG_UTILS + help + Say Y to enable support for USB Charger Detection with + ISP1707/ISP1704 USB transceivers. + endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 10143aaf4ee3..c73d381037a0 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -37,3 +37,4 @@ obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o +obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o diff --git a/drivers/power/isp1704_charger.c b/drivers/power/isp1704_charger.c new file mode 100644 index 000000000000..72512185f3e2 --- /dev/null +++ b/drivers/power/isp1704_charger.c @@ -0,0 +1,369 @@ +/* + * ISP1704 USB Charger Detection driver + * + * Copyright (C) 2010 Nokia 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Vendor specific Power Control register */ +#define ISP1704_PWR_CTRL 0x3d +#define ISP1704_PWR_CTRL_SWCTRL (1 << 0) +#define ISP1704_PWR_CTRL_DET_COMP (1 << 1) +#define ISP1704_PWR_CTRL_BVALID_RISE (1 << 2) +#define ISP1704_PWR_CTRL_BVALID_FALL (1 << 3) +#define ISP1704_PWR_CTRL_DP_WKPU_EN (1 << 4) +#define ISP1704_PWR_CTRL_VDAT_DET (1 << 5) +#define ISP1704_PWR_CTRL_DPVSRC_EN (1 << 6) +#define ISP1704_PWR_CTRL_HWDETECT (1 << 7) + +#define NXP_VENDOR_ID 0x04cc + +static u16 isp170x_id[] = { + 0x1704, + 0x1707, +}; + +struct isp1704_charger { + struct device *dev; + struct power_supply psy; + struct otg_transceiver *otg; + struct notifier_block nb; + struct work_struct work; + + char model[7]; + unsigned present:1; +}; + +/* + * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger + * is actually a dedicated charger, the following steps need to be taken. + */ +static inline int isp1704_charger_verify(struct isp1704_charger *isp) +{ + int ret = 0; + u8 r; + + /* Reset the transceiver */ + r = otg_io_read(isp->otg, ULPI_FUNC_CTRL); + r |= ULPI_FUNC_CTRL_RESET; + otg_io_write(isp->otg, ULPI_FUNC_CTRL, r); + usleep_range(1000, 2000); + + /* Set normal mode */ + r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK); + otg_io_write(isp->otg, ULPI_FUNC_CTRL, r); + + /* Clear the DP and DM pull-down bits */ + r = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN; + otg_io_write(isp->otg, ULPI_CLR(ULPI_OTG_CTRL), r); + + /* Enable strong pull-up on DP (1.5K) and reset */ + r = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET; + otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL), r); + usleep_range(1000, 2000); + + /* Read the line state */ + if (!otg_io_read(isp->otg, ULPI_DEBUG)) { + /* Disable strong pull-up on DP (1.5K) */ + otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL), + ULPI_FUNC_CTRL_TERMSELECT); + return 1; + } + + /* Is it a charger or PS/2 connection */ + + /* Enable weak pull-up resistor on DP */ + otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL), + ISP1704_PWR_CTRL_DP_WKPU_EN); + + /* Disable strong pull-up on DP (1.5K) */ + otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL), + ULPI_FUNC_CTRL_TERMSELECT); + + /* Enable weak pull-down resistor on DM */ + otg_io_write(isp->otg, ULPI_SET(ULPI_OTG_CTRL), + ULPI_OTG_CTRL_DM_PULLDOWN); + + /* It's a charger if the line states are clear */ + if (!(otg_io_read(isp->otg, ULPI_DEBUG))) + ret = 1; + + /* Disable weak pull-up resistor on DP */ + otg_io_write(isp->otg, ULPI_CLR(ISP1704_PWR_CTRL), + ISP1704_PWR_CTRL_DP_WKPU_EN); + + return ret; +} + +static inline int isp1704_charger_detect(struct isp1704_charger *isp) +{ + unsigned long timeout; + u8 r; + int ret = 0; + + /* set SW control bit in PWR_CTRL register */ + otg_io_write(isp->otg, ISP1704_PWR_CTRL, + ISP1704_PWR_CTRL_SWCTRL); + + /* enable manual charger detection */ + r = (ISP1704_PWR_CTRL_SWCTRL | ISP1704_PWR_CTRL_DPVSRC_EN); + otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL), r); + usleep_range(1000, 2000); + + timeout = jiffies + msecs_to_jiffies(300); + do { + /* Check if there is a charger */ + if (otg_io_read(isp->otg, ISP1704_PWR_CTRL) + & ISP1704_PWR_CTRL_VDAT_DET) { + ret = isp1704_charger_verify(isp); + break; + } + } while (!time_after(jiffies, timeout)); + + return ret; +} + +static void isp1704_charger_work(struct work_struct *data) +{ + int detect; + struct isp1704_charger *isp = + container_of(data, struct isp1704_charger, work); + + /* + * FIXME Only supporting dedicated chargers even though isp1704 can + * detect HUB and HOST chargers. If the device has already been + * enumerated, the detection will break the connection. + */ + if (isp->otg->state != OTG_STATE_B_IDLE) + return; + + /* disable data pullups */ + if (isp->otg->gadget) + usb_gadget_disconnect(isp->otg->gadget); + + /* detect charger */ + detect = isp1704_charger_detect(isp); + if (detect) { + isp->present = detect; + power_supply_changed(&isp->psy); + } + + /* enable data pullups */ + if (isp->otg->gadget) + usb_gadget_connect(isp->otg->gadget); +} + +static int isp1704_notifier_call(struct notifier_block *nb, + unsigned long event, void *unused) +{ + struct isp1704_charger *isp = + container_of(nb, struct isp1704_charger, nb); + + switch (event) { + case USB_EVENT_VBUS: + schedule_work(&isp->work); + break; + case USB_EVENT_NONE: + if (isp->present) { + isp->present = 0; + power_supply_changed(&isp->psy); + } + break; + default: + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +static int isp1704_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct isp1704_charger *isp = + container_of(psy, struct isp1704_charger, psy); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = isp->present; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = isp->model; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "NXP"; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property power_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static inline int isp1704_test_ulpi(struct isp1704_charger *isp) +{ + int vendor; + int product; + int i; + int ret = -ENODEV; + + /* Test ULPI interface */ + ret = otg_io_write(isp->otg, ULPI_SCRATCH, 0xaa); + if (ret < 0) + return ret; + + ret = otg_io_read(isp->otg, ULPI_SCRATCH); + if (ret < 0) + return ret; + + if (ret != 0xaa) + return -ENODEV; + + /* Verify the product and vendor id matches */ + vendor = otg_io_read(isp->otg, ULPI_VENDOR_ID_LOW); + vendor |= otg_io_read(isp->otg, ULPI_VENDOR_ID_HIGH) << 8; + if (vendor != NXP_VENDOR_ID) + return -ENODEV; + + product = otg_io_read(isp->otg, ULPI_PRODUCT_ID_LOW); + product |= otg_io_read(isp->otg, ULPI_PRODUCT_ID_HIGH) << 8; + + for (i = 0; i < ARRAY_SIZE(isp170x_id); i++) { + if (product == isp170x_id[i]) { + sprintf(isp->model, "isp%x", product); + return product; + } + } + + dev_err(isp->dev, "product id %x not matching known ids", product); + + return -ENODEV; +} + +static int __devinit isp1704_charger_probe(struct platform_device *pdev) +{ + struct isp1704_charger *isp; + int ret = -ENODEV; + + isp = kzalloc(sizeof *isp, GFP_KERNEL); + if (!isp) + return -ENOMEM; + + isp->otg = otg_get_transceiver(); + if (!isp->otg) + goto fail0; + + ret = isp1704_test_ulpi(isp); + if (ret < 0) + goto fail1; + + isp->dev = &pdev->dev; + platform_set_drvdata(pdev, isp); + + isp->psy.name = "isp1704"; + isp->psy.type = POWER_SUPPLY_TYPE_USB; + isp->psy.properties = power_props; + isp->psy.num_properties = ARRAY_SIZE(power_props); + isp->psy.get_property = isp1704_charger_get_property; + + ret = power_supply_register(isp->dev, &isp->psy); + if (ret) + goto fail1; + + /* + * REVISIT: using work in order to allow the otg notifications to be + * made atomically in the future. + */ + INIT_WORK(&isp->work, isp1704_charger_work); + + isp->nb.notifier_call = isp1704_notifier_call; + + ret = otg_register_notifier(isp->otg, &isp->nb); + if (ret) + goto fail2; + + dev_info(isp->dev, "registered with product id %s\n", isp->model); + + return 0; +fail2: + power_supply_unregister(&isp->psy); +fail1: + otg_put_transceiver(isp->otg); +fail0: + kfree(isp); + + dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret); + + return ret; +} + +static int __devexit isp1704_charger_remove(struct platform_device *pdev) +{ + struct isp1704_charger *isp = platform_get_drvdata(pdev); + + otg_unregister_notifier(isp->otg, &isp->nb); + power_supply_unregister(&isp->psy); + otg_put_transceiver(isp->otg); + kfree(isp); + + return 0; +} + +static struct platform_driver isp1704_charger_driver = { + .driver = { + .name = "isp1704_charger", + }, + .probe = isp1704_charger_probe, + .remove = __devexit_p(isp1704_charger_remove), +}; + +static int __init isp1704_charger_init(void) +{ + return platform_driver_register(&isp1704_charger_driver); +} +module_init(isp1704_charger_init); + +static void __exit isp1704_charger_exit(void) +{ + platform_driver_unregister(&isp1704_charger_driver); +} +module_exit(isp1704_charger_exit); + +MODULE_ALIAS("platform:isp1704_charger"); +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("ISP170x USB Charger driver"); +MODULE_LICENSE("GPL"); From fd0964c5f1bdaa979a7ea5cfc9af2de9b0a1e766 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Thu, 19 Aug 2010 15:09:37 +0300 Subject: [PATCH 02/17] omap: rx51: Add support for USB chargers This enables isp1704 power supply driver on RX51, allowing USB charger detection with N900. Signed-off-by: Heikki Krogerus Acked-by: Tony Lindgren Signed-off-by: Anton Vorontsov --- arch/arm/mach-omap2/board-rx51-peripherals.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/mach-omap2/board-rx51-peripherals.c b/arch/arm/mach-omap2/board-rx51-peripherals.c index 9a5eb87425fc..a4b1f8bdff43 100644 --- a/arch/arm/mach-omap2/board-rx51-peripherals.c +++ b/arch/arm/mach-omap2/board-rx51-peripherals.c @@ -104,6 +104,10 @@ static struct spi_board_info rx51_peripherals_spi_board_info[] __initdata = { }, }; +static struct platform_device rx51_charger_device = { + .name = "isp1704_charger", +}; + #if defined(CONFIG_KEYBOARD_GPIO) || defined(CONFIG_KEYBOARD_GPIO_MODULE) #define RX51_GPIO_CAMERA_LENS_COVER 110 @@ -909,5 +913,6 @@ void __init rx51_peripherals_init(void) spi_register_board_info(rx51_peripherals_spi_board_info, ARRAY_SIZE(rx51_peripherals_spi_board_info)); omap2_hsmmc_init(mmc); + platform_device_register(&rx51_charger_device); } From 2d98dae206daad05d794255b15bd0eb31c9294d0 Mon Sep 17 00:00:00 2001 From: Ian Lartey Date: Wed, 1 Sep 2010 18:09:27 +0100 Subject: [PATCH 03/17] wm831x_power: Remove duplicate chg mask Reported-by: Julia Lawall Signed-off-by: Ian Lartey Acked-by: Mark Brown Signed-off-by: Anton Vorontsov --- drivers/power/wm831x_power.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/power/wm831x_power.c b/drivers/power/wm831x_power.c index fbcc36dae470..a57a94150cb8 100644 --- a/drivers/power/wm831x_power.c +++ b/drivers/power/wm831x_power.c @@ -267,7 +267,6 @@ static void wm831x_config_battery(struct wm831x *wm831x) ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_1, WM831X_CHG_ENA_MASK | WM831X_CHG_FAST_MASK | - WM831X_CHG_ITERM_MASK | WM831X_CHG_ITERM_MASK, reg1); if (ret != 0) From a7640bfa10c558b7cbabb4b98d6bc356d3c2ec55 Mon Sep 17 00:00:00 2001 From: Rhyland Klein Date: Sun, 5 Sep 2010 15:31:23 -0700 Subject: [PATCH 04/17] power_supply: Add driver for TI BQ20Z75 gas gauge IC This driver depends on I2C and uses SMBUS for communication with the host. Signed-off-by: Rhyland Klein Reviewed-by: Mark Brown Reviewed-by: Jean Delvare Signed-off-by: Anton Vorontsov --- drivers/power/Kconfig | 7 + drivers/power/Makefile | 1 + drivers/power/bq20z75.c | 385 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 393 insertions(+) create mode 100644 drivers/power/bq20z75.c diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index c2a1b8b713f6..4d9a637539b6 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -109,6 +109,13 @@ config BATTERY_WM97XX help Say Y to enable support for battery measured by WM97xx aux port. +config BATTERY_BQ20Z75 + tristate "TI BQ20z75 gas gauge" + depends on I2C + help + Say Y to include support for TI BQ20z75 SBS-compliant + gas gauge and protection IC. + config BATTERY_BQ27x00 tristate "BQ27x00 battery driver" depends on I2C diff --git a/drivers/power/Makefile b/drivers/power/Makefile index c73d381037a0..2799a8418186 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -29,6 +29,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_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 new file mode 100644 index 000000000000..a1c7ae2ec9f8 --- /dev/null +++ b/drivers/power/bq20z75.c @@ -0,0 +1,385 @@ +/* + * 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 + +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 +}; + +/* manufacturer access defines */ +#define MANUFACTURER_ACCESS_STATUS 0x0006 +#define MANUFACTURER_ACCESS_SLEEP 0x0011 + +/* battery status value bits */ +#define BATTERY_CHARGING 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_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_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, +}; + +struct bq20z75_info { + struct i2c_client *client; + struct power_supply power_supply; +}; + +static int bq20z75_get_battery_presence_and_health( + struct i2c_client *client, enum power_supply_property psp, + union power_supply_propval *val) +{ + s32 ret; + + /* Write to ManufacturerAccess with + * ManufacturerAccess command and then + * read the status */ + ret = i2c_smbus_write_word_data(client, + bq20z75_data[REG_MANUFACTURER_DATA].addr, + MANUFACTURER_ACCESS_STATUS); + if (ret < 0) { + dev_err(&client->dev, + "%s: i2c write for battery presence failed\n", + __func__); + return -ENODEV; + } + + ret = i2c_smbus_read_word_data(client, + bq20z75_data[REG_MANUFACTURER_DATA].addr); + if (ret < 0) { + dev_err(&client->dev, + "%s: i2c read for battery presence failed\n", + __func__); + return -EIO; + } + + 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) +{ + s32 ret; + + ret = i2c_smbus_read_word_data(client, + bq20z75_data[reg_offset].addr); + if (ret < 0) { + dev_err(&client->dev, + "%s: i2c read for %d failed\n", __func__, reg_offset); + return -EIO; + } + + if (ret >= bq20z75_data[reg_offset].min_value && + ret <= bq20z75_data[reg_offset].max_value) { + val->intval = ret; + if (psp == POWER_SUPPLY_PROP_STATUS) { + if (ret & BATTERY_CHARGING) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else 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 + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } + /* bq20z75 provides battery tempreture in 0.1°K + * so convert it to °C */ + else if (psp == POWER_SUPPLY_PROP_TEMP) + val->intval = ret - 2731; + } else { + if (psp == POWER_SUPPLY_PROP_STATUS) + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + else + val->intval = 0; + } + + return 0; +} + +static int bq20z75_get_battery_capacity(struct i2c_client *client, + union power_supply_propval *val) +{ + s32 ret; + + ret = i2c_smbus_read_byte_data(client, bq20z75_data[REG_CAPACITY].addr); + if (ret < 0) { + dev_err(&client->dev, + "%s: i2c read for %d failed\n", __func__, REG_CAPACITY); + return -EIO; + } + + /* bq20z75 spec says that this can be >100 % + * even if max value is 100 % */ + val->intval = min(ret, 100); + + return 0; +} + +static int bq20z75_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int count; + int ret; + 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 (ret) + return ret; + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + + case POWER_SUPPLY_PROP_CAPACITY: + ret = bq20z75_get_battery_capacity(client, val); + if (ret) + return ret; + 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_SERIAL_NUMBER: + for (count = 0; count < ARRAY_SIZE(bq20z75_data); count++) { + if (psp == bq20z75_data[count].psp) + break; + } + + ret = bq20z75_get_battery_property(client, count, psp, val); + if (ret) + return ret; + break; + + default: + dev_err(&client->dev, + "%s: INVALID property\n", __func__); + return -EINVAL; + } + + dev_dbg(&client->dev, + "%s: property = %d, value = %d\n", __func__, psp, val->intval); + + return 0; +} + +static int bq20z75_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bq20z75_info *bq20z75_device; + int rc; + + bq20z75_device = kzalloc(sizeof(struct bq20z75_info), GFP_KERNEL); + if (!bq20z75_device) + return -ENOMEM; + + bq20z75_device->client = client; + 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; + + i2c_set_clientdata(client, bq20z75_device); + + rc = power_supply_register(&client->dev, &bq20z75_device->power_supply); + if (rc) { + dev_err(&client->dev, + "%s: Failed to register power supply\n", __func__); + kfree(bq20z75_device); + return rc; + } + + dev_info(&client->dev, + "%s: battery gas gauge device registered\n", client->name); + + return 0; +} + +static int bq20z75_remove(struct i2c_client *client) +{ + struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client); + + power_supply_unregister(&bq20z75_device->power_supply); + kfree(bq20z75_device); + bq20z75_device = NULL; + + return 0; +} + +#if defined CONFIG_PM +static int bq20z75_suspend(struct i2c_client *client, + pm_message_t state) +{ + s32 ret; + + /* write to manufacturer access with sleep command */ + ret = i2c_smbus_write_word_data(client, + bq20z75_data[REG_MANUFACTURER_DATA].addr, + MANUFACTURER_ACCESS_SLEEP); + if (ret < 0) { + dev_err(&client->dev, + "%s: i2c write for %d failed\n", + __func__, MANUFACTURER_ACCESS_SLEEP); + return -EIO; + } + + 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 }, + {} +}; + +static struct i2c_driver bq20z75_battery_driver = { + .probe = bq20z75_probe, + .remove = bq20z75_remove, + .suspend = bq20z75_suspend, + .resume = bq20z75_resume, + .id_table = bq20z75_id, + .driver = { + .name = "bq20z75-battery", + }, +}; + +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"); From 305da8f504dc82ccc0e0ca9aa1889de9e2517090 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Tue, 21 Sep 2010 16:35:57 +0800 Subject: [PATCH 05/17] pcf50633-charger: Add missing sysfs_remove_group() sysfs entries should be removed when unload the module. Signed-off-by: Axel Lin Signed-off-by: Anton Vorontsov --- drivers/power/pcf50633-charger.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/pcf50633-charger.c b/drivers/power/pcf50633-charger.c index 066f994e6fe5..4fa52e1781a2 100644 --- a/drivers/power/pcf50633-charger.c +++ b/drivers/power/pcf50633-charger.c @@ -456,6 +456,7 @@ static int __devexit pcf50633_mbc_remove(struct platform_device *pdev) for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++) pcf50633_free_irq(mbc->pcf, mbc_irq_handlers[i]); + sysfs_remove_group(&pdev->dev.kobj, &mbc_attr_group); power_supply_unregister(&mbc->usb); power_supply_unregister(&mbc->adapter); power_supply_unregister(&mbc->ac); From f6127b8645207b4cb43e7456214c6add7c37cdc1 Mon Sep 17 00:00:00 2001 From: Martin Schwidefsky Date: Thu, 23 Sep 2010 15:21:36 +0200 Subject: [PATCH 06/17] ds2760_battery: Fix W1 and W1_SLAVE_DS2760 dependency Fix this Kconfig warning on allmodconfig for s390: warning: (BATTERY_DS2760 && POWER_SUPPLY) selects W1 which has unmet direct dependencies (HAS_IOMEM) Signed-off-by: Martin Schwidefsky Signed-off-by: Anton Vorontsov --- drivers/power/Kconfig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 4d9a637539b6..0e98b20d4c22 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -64,8 +64,7 @@ config TEST_POWER config BATTERY_DS2760 tristate "DS2760 battery driver (HP iPAQ & others)" - select W1 - select W1_SLAVE_DS2760 + depends on W1 && W1_SLAVE_DS2760 help Say Y here to enable support for batteries with ds2760 chip. From 7cfbb29466633e6ecdc14f76a693c8478c2b22af Mon Sep 17 00:00:00 2001 From: "Richard A. Smith" Date: Sat, 25 Sep 2010 19:19:26 +0100 Subject: [PATCH 07/17] olpc_battery: Fix endian neutral breakage for s16 values When the driver was updated to be endian neutral (8e9c7716c) the signed part of the s16 values was lost. This is because be16_to_cpu() returns an unsigned value. This patch casts the values back to a s16 number prior to the the implicit cast up to an int. Signed-off-by: Richard A. Smith Signed-off-by: Daniel Drake Cc: Signed-off-by: Anton Vorontsov --- drivers/power/olpc_battery.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/power/olpc_battery.c b/drivers/power/olpc_battery.c index aafc1c506eda..5bc1dcf7785e 100644 --- a/drivers/power/olpc_battery.c +++ b/drivers/power/olpc_battery.c @@ -271,14 +271,14 @@ static int olpc_bat_get_property(struct power_supply *psy, if (ret) return ret; - val->intval = (int)be16_to_cpu(ec_word) * 9760L / 32; + val->intval = (s16)be16_to_cpu(ec_word) * 9760L / 32; break; case POWER_SUPPLY_PROP_CURRENT_AVG: ret = olpc_ec_cmd(EC_BAT_CURRENT, NULL, 0, (void *)&ec_word, 2); if (ret) return ret; - val->intval = (int)be16_to_cpu(ec_word) * 15625L / 120; + val->intval = (s16)be16_to_cpu(ec_word) * 15625L / 120; break; case POWER_SUPPLY_PROP_CAPACITY: ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1); @@ -299,7 +299,7 @@ static int olpc_bat_get_property(struct power_supply *psy, if (ret) return ret; - val->intval = (int)be16_to_cpu(ec_word) * 100 / 256; + val->intval = (s16)be16_to_cpu(ec_word) * 100 / 256; break; case POWER_SUPPLY_PROP_TEMP_AMBIENT: ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2); @@ -313,7 +313,7 @@ static int olpc_bat_get_property(struct power_supply *psy, if (ret) return ret; - val->intval = (int)be16_to_cpu(ec_word) * 6250 / 15; + val->intval = (s16)be16_to_cpu(ec_word) * 6250 / 15; break; case POWER_SUPPLY_PROP_SERIAL_NUMBER: ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8); From a01bce6ad970d181797dffff5649e1e6989aa88b Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Mon, 27 Sep 2010 15:07:12 +0800 Subject: [PATCH 08/17] ds2760_battery: Add missing kfree(di) in ds2760_battery_remove() Signed-off-by: Axel Lin Signed-off-by: Anton Vorontsov --- drivers/power/ds2760_battery.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/ds2760_battery.c b/drivers/power/ds2760_battery.c index 4d3b27228a2e..b3c01c16a164 100644 --- a/drivers/power/ds2760_battery.c +++ b/drivers/power/ds2760_battery.c @@ -586,6 +586,7 @@ static int ds2760_battery_remove(struct platform_device *pdev) &di->set_charged_work); destroy_workqueue(di->monitor_wqueue); power_supply_unregister(&di->bat); + kfree(di); return 0; } From 5f29895f3e8cd4c4b25613c3dd64b68124a1762f Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Mon, 27 Sep 2010 15:08:31 +0800 Subject: [PATCH 09/17] jz4740-battery: Add missing kfree(jz_battery) in jz_battery_remove() Signed-off-by: Axel Lin Acked-By: Lars-Peter Clausen Signed-off-by: Anton Vorontsov --- drivers/power/jz4740-battery.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/jz4740-battery.c b/drivers/power/jz4740-battery.c index 20c4b952e9bd..a8108a73593e 100644 --- a/drivers/power/jz4740-battery.c +++ b/drivers/power/jz4740-battery.c @@ -383,6 +383,7 @@ static int __devexit jz_battery_remove(struct platform_device *pdev) iounmap(jz_battery->base); release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem)); + kfree(jz_battery); return 0; } From 9c99f08991b38bf6ab62ffe9013ee46ff41d66d0 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Mon, 27 Sep 2010 15:10:11 +0800 Subject: [PATCH 10/17] wm831x_power: Add missing kfree(wm831x_power) in wm831x_power_remove() Signed-off-by: Axel Lin Acked-by: Mark Brown Signed-off-by: Anton Vorontsov --- drivers/power/wm831x_power.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/wm831x_power.c b/drivers/power/wm831x_power.c index a57a94150cb8..ddf8cf5f3204 100644 --- a/drivers/power/wm831x_power.c +++ b/drivers/power/wm831x_power.c @@ -611,6 +611,7 @@ static __devexit int wm831x_power_remove(struct platform_device *pdev) power_supply_unregister(&wm831x_power->battery); power_supply_unregister(&wm831x_power->wall); power_supply_unregister(&wm831x_power->usb); + kfree(wm831x_power); return 0; } From d3ab61ecbab2b8950108b3541bc9eda515d605e0 Mon Sep 17 00:00:00 2001 From: Rhyland Klein Date: Tue, 21 Sep 2010 15:33:55 -0700 Subject: [PATCH 11/17] bq20z75: Add support for more power supply properties This patch adds properties to support devicekit power. Also, create IO wrapper functions and fix some issues found while testing, including unit conversions to match the power_supply types. Signed-off-by: Rhyland Klein Signed-off-by: Anton Vorontsov --- drivers/power/bq20z75.c | 204 ++++++++++++++++++++++++++++++---------- 1 file changed, 156 insertions(+), 48 deletions(-) diff --git a/drivers/power/bq20z75.c b/drivers/power/bq20z75.c index a1c7ae2ec9f8..492da27e1a47 100644 --- a/drivers/power/bq20z75.c +++ b/drivers/power/bq20z75.c @@ -36,7 +36,11 @@ enum { REG_TIME_TO_FULL, REG_STATUS, REG_CYCLE_COUNT, - REG_SERIAL_NUMBER + REG_SERIAL_NUMBER, + REG_REMAINING_CAPACITY, + REG_FULL_CHARGE_CAPACITY, + REG_DESIGN_CAPACITY, + REG_DESIGN_VOLTAGE, }; /* manufacturer access defines */ @@ -44,7 +48,7 @@ enum { #define MANUFACTURER_ACCESS_SLEEP 0x0011 /* battery status value bits */ -#define BATTERY_CHARGING 0x40 +#define BATTERY_DISCHARGING 0x40 #define BATTERY_FULL_CHARGED 0x20 #define BATTERY_FULL_DISCHARGED 0x10 @@ -72,6 +76,10 @@ static const struct bq20z75_device_data { 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_FULL_CHARGE_CAPACITY] = + BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535), [REG_TIME_TO_EMPTY] = BQ20Z75_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, 65535), @@ -82,6 +90,12 @@ static const struct bq20z75_device_data { 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_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), }; @@ -99,6 +113,10 @@ static enum power_supply_property bq20z75_properties[] = { 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, }; struct bq20z75_info { @@ -106,6 +124,35 @@ struct bq20z75_info { struct power_supply power_supply; }; +static int bq20z75_read_word_data(struct i2c_client *client, u8 address) +{ + s32 ret; + + ret = i2c_smbus_read_word_data(client, address); + if (ret < 0) { + dev_err(&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) +{ + s32 ret; + + ret = i2c_smbus_write_word_data(client, address, le16_to_cpu(value)); + if (ret < 0) { + dev_err(&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) @@ -115,24 +162,17 @@ static int bq20z75_get_battery_presence_and_health( /* Write to ManufacturerAccess with * ManufacturerAccess command and then * read the status */ - ret = i2c_smbus_write_word_data(client, + ret = bq20z75_write_word_data(client, bq20z75_data[REG_MANUFACTURER_DATA].addr, MANUFACTURER_ACCESS_STATUS); - if (ret < 0) { - dev_err(&client->dev, - "%s: i2c write for battery presence failed\n", - __func__); - return -ENODEV; - } + if (ret < 0) + return ret; - ret = i2c_smbus_read_word_data(client, + + ret = bq20z75_read_word_data(client, bq20z75_data[REG_MANUFACTURER_DATA].addr); - if (ret < 0) { - dev_err(&client->dev, - "%s: i2c read for battery presence failed\n", - __func__); - return -EIO; - } + if (ret < 0) + return ret; if (ret < bq20z75_data[REG_MANUFACTURER_DATA].min_value || ret > bq20z75_data[REG_MANUFACTURER_DATA].max_value) { @@ -171,31 +211,28 @@ static int bq20z75_get_battery_property(struct i2c_client *client, { s32 ret; - ret = i2c_smbus_read_word_data(client, + ret = bq20z75_read_word_data(client, bq20z75_data[reg_offset].addr); - if (ret < 0) { - dev_err(&client->dev, - "%s: i2c read for %d failed\n", __func__, reg_offset); - return -EIO; - } + 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) { - if (ret & BATTERY_CHARGING) - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (ret & BATTERY_FULL_CHARGED) + 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 + else if (ret & BATTERY_DISCHARGING) val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else + val->intval = POWER_SUPPLY_STATUS_CHARGING; } - /* bq20z75 provides battery tempreture in 0.1°K - * so convert it to °C */ - else if (psp == POWER_SUPPLY_PROP_TEMP) - val->intval = ret - 2731; } else { if (psp == POWER_SUPPLY_PROP_STATUS) val->intval = POWER_SUPPLY_STATUS_UNKNOWN; @@ -206,21 +243,77 @@ static int bq20z75_get_battery_property(struct i2c_client *client, 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 600 +#define TEMP_KELVIN_TO_CELCIUS 2731 + switch (psp) { + case POWER_SUPPLY_PROP_ENERGY_NOW: + case POWER_SUPPLY_PROP_ENERGY_FULL: + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + 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: + val->intval *= BASE_UNIT_CONVERSION; + break; + + case POWER_SUPPLY_PROP_TEMP: + /* bq20z75 provides battery tempreture in 0.1°K + * so convert it to 0.1°C */ + val->intval -= TEMP_KELVIN_TO_CELCIUS; + val->intval *= 10; + break; + + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + val->intval *= TIME_UNIT_CONVERSION; + break; + + default: + dev_dbg(&client->dev, + "%s: no need for unit conversion %d\n", __func__, psp); + } +} + 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; - ret = i2c_smbus_read_byte_data(client, bq20z75_data[REG_CAPACITY].addr); - if (ret < 0) { - dev_err(&client->dev, - "%s: i2c read for %d failed\n", __func__, REG_CAPACITY); - return -EIO; - } + ret = bq20z75_read_word_data(client, bq20z75_data[reg_offset].addr); + if (ret < 0) + return ret; - /* bq20z75 spec says that this can be >100 % - * even if max value is 100 % */ - val->intval = min(ret, 100); + 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; + + 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; } @@ -247,8 +340,23 @@ static int bq20z75_get_property(struct power_supply *psy, 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_CAPACITY: - ret = bq20z75_get_battery_capacity(client, val); + for (count = 0; count < ARRAY_SIZE(bq20z75_data); count++) { + if (psp == bq20z75_data[count].psp) + break; + } + + ret = bq20z75_get_battery_capacity(client, count, psp, val); + if (ret) + return ret; + + break; + + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + ret = bq20z75_get_battery_serial_number(client, val); if (ret) return ret; break; @@ -260,7 +368,7 @@ static int bq20z75_get_property(struct power_supply *psy, 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_SERIAL_NUMBER: + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: for (count = 0; count < ARRAY_SIZE(bq20z75_data); count++) { if (psp == bq20z75_data[count].psp) break; @@ -269,6 +377,7 @@ static int bq20z75_get_property(struct power_supply *psy, ret = bq20z75_get_battery_property(client, count, psp, val); if (ret) return ret; + break; default: @@ -277,6 +386,9 @@ static int bq20z75_get_property(struct power_supply *psy, return -EINVAL; } + /* Convert units to match requirements for power supply class */ + bq20z75_unit_adjustment(client, psp, val); + dev_dbg(&client->dev, "%s: property = %d, value = %d\n", __func__, psp, val->intval); @@ -335,15 +447,11 @@ static int bq20z75_suspend(struct i2c_client *client, s32 ret; /* write to manufacturer access with sleep command */ - ret = i2c_smbus_write_word_data(client, + ret = bq20z75_write_word_data(client, bq20z75_data[REG_MANUFACTURER_DATA].addr, MANUFACTURER_ACCESS_SLEEP); - if (ret < 0) { - dev_err(&client->dev, - "%s: i2c write for %d failed\n", - __func__, MANUFACTURER_ACCESS_SLEEP); - return -EIO; - } + if (ret < 0) + return ret; return 0; } From 2e727f1787e311087950711451e6eec82c05d81c Mon Sep 17 00:00:00 2001 From: Grazvydas Ignotas Date: Mon, 27 Sep 2010 23:18:24 +0300 Subject: [PATCH 12/17] power_supply: Add driver for TWL4030/TPS65950 BCI charger TWL4030/TPS65950 is a multi-function device with integrated charger, which allows charging from AC or USB. This driver enables the charger and provides several monitoring functions. Tested on OMAP3 Pandora board. Signed-off-by: Grazvydas Ignotas Acked-by: Felipe Balbi Signed-off-by: Anton Vorontsov --- drivers/power/Kconfig | 6 + drivers/power/Makefile | 1 + drivers/power/twl4030_charger.c | 565 ++++++++++++++++++++++++++++++++ 3 files changed, 572 insertions(+) create mode 100644 drivers/power/twl4030_charger.c diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 0e98b20d4c22..60d83d983a36 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -179,4 +179,10 @@ config CHARGER_ISP1704 Say Y to enable support for USB Charger Detection with ISP1707/ISP1704 USB transceivers. +config CHARGER_TWL4030 + tristate "OMAP TWL4030 BCI charger driver" + depends on TWL4030_CORE + help + Say Y here to enable support for TWL4030 Battery Charge Interface. + endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 2799a8418186..695e54fdbcdb 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -39,3 +39,4 @@ obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o +obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c new file mode 100644 index 000000000000..ff1f42398a2e --- /dev/null +++ b/drivers/power/twl4030_charger.c @@ -0,0 +1,565 @@ +/* + * TWL4030/TPS65950 BCI (Battery Charger Interface) driver + * + * Copyright (C) 2010 Gražvydas Ignotas + * + * based on twl4030_bci_battery.c by TI + * Copyright (C) 2008 Texas Instruments, Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TWL4030_BCIMSTATEC 0x02 +#define TWL4030_BCIICHG 0x08 +#define TWL4030_BCIVAC 0x0a +#define TWL4030_BCIVBUS 0x0c +#define TWL4030_BCIMFSTS4 0x10 +#define TWL4030_BCICTL1 0x23 + +#define TWL4030_BCIAUTOWEN BIT(5) +#define TWL4030_CONFIG_DONE BIT(4) +#define TWL4030_BCIAUTOUSB BIT(1) +#define TWL4030_BCIAUTOAC BIT(0) +#define TWL4030_CGAIN BIT(5) +#define TWL4030_USBFASTMCHG BIT(2) +#define TWL4030_STS_VBUS BIT(7) +#define TWL4030_STS_USB_ID BIT(2) + +/* BCI interrupts */ +#define TWL4030_WOVF BIT(0) /* Watchdog overflow */ +#define TWL4030_TMOVF BIT(1) /* Timer overflow */ +#define TWL4030_ICHGHIGH BIT(2) /* Battery charge current high */ +#define TWL4030_ICHGLOW BIT(3) /* Battery cc. low / FSM state change */ +#define TWL4030_ICHGEOC BIT(4) /* Battery current end-of-charge */ +#define TWL4030_TBATOR2 BIT(5) /* Battery temperature out of range 2 */ +#define TWL4030_TBATOR1 BIT(6) /* Battery temperature out of range 1 */ +#define TWL4030_BATSTS BIT(7) /* Battery status */ + +#define TWL4030_VBATLVL BIT(0) /* VBAT level */ +#define TWL4030_VBATOV BIT(1) /* VBAT overvoltage */ +#define TWL4030_VBUSOV BIT(2) /* VBUS overvoltage */ +#define TWL4030_ACCHGOV BIT(3) /* Ac charger overvoltage */ + +#define TWL4030_MSTATEC_USB BIT(4) +#define TWL4030_MSTATEC_AC BIT(5) +#define TWL4030_MSTATEC_MASK 0x0f +#define TWL4030_MSTATEC_QUICK1 0x02 +#define TWL4030_MSTATEC_QUICK7 0x07 +#define TWL4030_MSTATEC_COMPLETE1 0x0b +#define TWL4030_MSTATEC_COMPLETE4 0x0e + +static bool allow_usb; +module_param(allow_usb, bool, 1); +MODULE_PARM_DESC(allow_usb, "Allow USB charge drawing default current"); + +struct twl4030_bci { + struct device *dev; + struct power_supply ac; + struct power_supply usb; + struct otg_transceiver *transceiver; + struct notifier_block otg_nb; + int irq_chg; + int irq_bci; +}; + +/* + * clear and set bits on an given register on a given module + */ +static int twl4030_clear_set(u8 mod_no, u8 clear, u8 set, u8 reg) +{ + u8 val = 0; + int ret; + + ret = twl_i2c_read_u8(mod_no, &val, reg); + if (ret) + return ret; + + val &= ~clear; + val |= set; + + return twl_i2c_write_u8(mod_no, val, reg); +} + +static int twl4030_bci_read(u8 reg, u8 *val) +{ + return twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, val, reg); +} + +static int twl4030_clear_set_boot_bci(u8 clear, u8 set) +{ + return twl4030_clear_set(TWL4030_MODULE_PM_MASTER, 0, + TWL4030_CONFIG_DONE | TWL4030_BCIAUTOWEN | set, + TWL4030_PM_MASTER_BOOT_BCI); +} + +static int twl4030bci_read_adc_val(u8 reg) +{ + int ret, temp; + u8 val; + + /* read MSB */ + ret = twl4030_bci_read(reg + 1, &val); + if (ret) + return ret; + + temp = (int)(val & 0x03) << 8; + + /* read LSB */ + ret = twl4030_bci_read(reg, &val); + if (ret) + return ret; + + return temp | val; +} + +/* + * Check if VBUS power is present + */ +static int twl4030_bci_have_vbus(struct twl4030_bci *bci) +{ + int ret; + u8 hwsts; + + ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &hwsts, + TWL4030_PM_MASTER_STS_HW_CONDITIONS); + if (ret < 0) + return 0; + + dev_dbg(bci->dev, "check_vbus: HW_CONDITIONS %02x\n", hwsts); + + /* in case we also have STS_USB_ID, VBUS is driven by TWL itself */ + if ((hwsts & TWL4030_STS_VBUS) && !(hwsts & TWL4030_STS_USB_ID)) + return 1; + + return 0; +} + +/* + * Enable/Disable USB Charge funtionality. + */ +static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) +{ + int ret; + + if (enable) { + /* Check for USB charger conneted */ + if (!twl4030_bci_have_vbus(bci)) + return -ENODEV; + + /* + * Until we can find out what current the device can provide, + * require a module param to enable USB charging. + */ + if (!allow_usb) { + dev_warn(bci->dev, "USB charging is disabled.\n"); + return -EACCES; + } + + /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */ + ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB); + if (ret < 0) + return ret; + + /* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */ + ret = twl4030_clear_set(TWL4030_MODULE_MAIN_CHARGE, 0, + TWL4030_USBFASTMCHG, TWL4030_BCIMFSTS4); + } else { + ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0); + } + + return ret; +} + +/* + * Enable/Disable AC Charge funtionality. + */ +static int twl4030_charger_enable_ac(bool enable) +{ + int ret; + + if (enable) + ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOAC); + else + ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOAC, 0); + + return ret; +} + +/* + * TWL4030 CHG_PRES (AC charger presence) events + */ +static irqreturn_t twl4030_charger_interrupt(int irq, void *arg) +{ + struct twl4030_bci *bci = arg; + + dev_dbg(bci->dev, "CHG_PRES irq\n"); + power_supply_changed(&bci->ac); + power_supply_changed(&bci->usb); + + return IRQ_HANDLED; +} + +/* + * TWL4030 BCI monitoring events + */ +static irqreturn_t twl4030_bci_interrupt(int irq, void *arg) +{ + struct twl4030_bci *bci = arg; + u8 irqs1, irqs2; + int ret; + + ret = twl_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &irqs1, + TWL4030_INTERRUPTS_BCIISR1A); + if (ret < 0) + return IRQ_HANDLED; + + ret = twl_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &irqs2, + TWL4030_INTERRUPTS_BCIISR2A); + if (ret < 0) + return IRQ_HANDLED; + + dev_dbg(bci->dev, "BCI irq %02x %02x\n", irqs2, irqs1); + + if (irqs1 & (TWL4030_ICHGLOW | TWL4030_ICHGEOC)) { + /* charger state change, inform the core */ + power_supply_changed(&bci->ac); + power_supply_changed(&bci->usb); + } + + /* various monitoring events, for now we just log them here */ + if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1)) + dev_warn(bci->dev, "battery temperature out of range\n"); + + if (irqs1 & TWL4030_BATSTS) + dev_crit(bci->dev, "battery disconnected\n"); + + if (irqs2 & TWL4030_VBATOV) + dev_crit(bci->dev, "VBAT overvoltage\n"); + + if (irqs2 & TWL4030_VBUSOV) + dev_crit(bci->dev, "VBUS overvoltage\n"); + + if (irqs2 & TWL4030_ACCHGOV) + dev_crit(bci->dev, "Ac charger overvoltage\n"); + + return IRQ_HANDLED; +} + +static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val, + void *priv) +{ + struct twl4030_bci *bci = container_of(nb, struct twl4030_bci, otg_nb); + + dev_dbg(bci->dev, "OTG notify %lu\n", val); + + switch (val) { + case USB_EVENT_VBUS: + case USB_EVENT_CHARGER: + twl4030_charger_enable_usb(bci, true); + break; + case USB_EVENT_NONE: + twl4030_charger_enable_usb(bci, false); + break; + } + + return NOTIFY_OK; +} + +/* + * TI provided formulas: + * CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 - 1) - 0.85 + * CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 - 1) - 1.7 + * Here we use integer approximation of: + * CGAIN == 0: val * 1.6618 - 0.85 + * CGAIN == 1: (val * 1.6618 - 0.85) * 2 + */ +static int twl4030_charger_get_current(void) +{ + int curr; + int ret; + u8 bcictl1; + + curr = twl4030bci_read_adc_val(TWL4030_BCIICHG); + if (curr < 0) + return curr; + + ret = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); + if (ret) + return ret; + + ret = (curr * 16618 - 850 * 10000) / 10; + if (bcictl1 & TWL4030_CGAIN) + ret *= 2; + + return ret; +} + +/* + * Returns the main charge FSM state + * Or < 0 on failure. + */ +static int twl4030bci_state(struct twl4030_bci *bci) +{ + int ret; + u8 state; + + ret = twl4030_bci_read(TWL4030_BCIMSTATEC, &state); + if (ret) { + pr_err("twl4030_bci: error reading BCIMSTATEC\n"); + return ret; + } + + dev_dbg(bci->dev, "state: %02x\n", state); + + return state; +} + +static int twl4030_bci_state_to_status(int state) +{ + state &= TWL4030_MSTATEC_MASK; + if (TWL4030_MSTATEC_QUICK1 <= state && state <= TWL4030_MSTATEC_QUICK7) + return POWER_SUPPLY_STATUS_CHARGING; + else if (TWL4030_MSTATEC_COMPLETE1 <= state && + state <= TWL4030_MSTATEC_COMPLETE4) + return POWER_SUPPLY_STATUS_FULL; + else + return POWER_SUPPLY_STATUS_NOT_CHARGING; +} + +static int twl4030_bci_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct twl4030_bci *bci = dev_get_drvdata(psy->dev->parent); + int is_charging; + int state; + int ret; + + state = twl4030bci_state(bci); + if (state < 0) + return state; + + if (psy->type == POWER_SUPPLY_TYPE_USB) + is_charging = state & TWL4030_MSTATEC_USB; + else + is_charging = state & TWL4030_MSTATEC_AC; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (is_charging) + val->intval = twl4030_bci_state_to_status(state); + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + /* charging must be active for meaningful result */ + if (!is_charging) + return -ENODATA; + if (psy->type == POWER_SUPPLY_TYPE_USB) { + ret = twl4030bci_read_adc_val(TWL4030_BCIVBUS); + if (ret < 0) + return ret; + /* BCIVBUS uses ADCIN8, 7/1023 V/step */ + val->intval = ret * 6843; + } else { + ret = twl4030bci_read_adc_val(TWL4030_BCIVAC); + if (ret < 0) + return ret; + /* BCIVAC uses ADCIN11, 10/1023 V/step */ + val->intval = ret * 9775; + } + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (!is_charging) + return -ENODATA; + /* current measurement is shared between AC and USB */ + ret = twl4030_charger_get_current(); + if (ret < 0) + return ret; + val->intval = ret; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = is_charging && + twl4030_bci_state_to_status(state) != + POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property twl4030_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static int __init twl4030_bci_probe(struct platform_device *pdev) +{ + struct twl4030_bci *bci; + int ret; + int reg; + + bci = kzalloc(sizeof(*bci), GFP_KERNEL); + if (bci == NULL) + return -ENOMEM; + + bci->dev = &pdev->dev; + bci->irq_chg = platform_get_irq(pdev, 0); + bci->irq_bci = platform_get_irq(pdev, 1); + + platform_set_drvdata(pdev, bci); + + bci->ac.name = "twl4030_ac"; + bci->ac.type = POWER_SUPPLY_TYPE_MAINS; + bci->ac.properties = twl4030_charger_props; + bci->ac.num_properties = ARRAY_SIZE(twl4030_charger_props); + bci->ac.get_property = twl4030_bci_get_property; + + ret = power_supply_register(&pdev->dev, &bci->ac); + if (ret) { + dev_err(&pdev->dev, "failed to register ac: %d\n", ret); + goto fail_register_ac; + } + + bci->usb.name = "twl4030_usb"; + bci->usb.type = POWER_SUPPLY_TYPE_USB; + bci->usb.properties = twl4030_charger_props; + bci->usb.num_properties = ARRAY_SIZE(twl4030_charger_props); + bci->usb.get_property = twl4030_bci_get_property; + + ret = power_supply_register(&pdev->dev, &bci->usb); + if (ret) { + dev_err(&pdev->dev, "failed to register usb: %d\n", ret); + goto fail_register_usb; + } + + ret = request_threaded_irq(bci->irq_chg, NULL, + twl4030_charger_interrupt, 0, pdev->name, bci); + if (ret < 0) { + dev_err(&pdev->dev, "could not request irq %d, status %d\n", + bci->irq_chg, ret); + goto fail_chg_irq; + } + + ret = request_threaded_irq(bci->irq_bci, NULL, + twl4030_bci_interrupt, 0, pdev->name, bci); + if (ret < 0) { + dev_err(&pdev->dev, "could not request irq %d, status %d\n", + bci->irq_bci, ret); + goto fail_bci_irq; + } + + bci->transceiver = otg_get_transceiver(); + if (bci->transceiver != NULL) { + bci->otg_nb.notifier_call = twl4030_bci_usb_ncb; + otg_register_notifier(bci->transceiver, &bci->otg_nb); + } + + /* Enable interrupts now. */ + reg = ~(TWL4030_ICHGLOW | TWL4030_ICHGEOC | TWL4030_TBATOR2 | + TWL4030_TBATOR1 | TWL4030_BATSTS); + ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg, + TWL4030_INTERRUPTS_BCIIMR1A); + if (ret < 0) { + dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret); + goto fail_unmask_interrupts; + } + + reg = ~(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV); + ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg, + TWL4030_INTERRUPTS_BCIIMR2A); + if (ret < 0) + dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret); + + twl4030_charger_enable_ac(true); + twl4030_charger_enable_usb(bci, true); + + return 0; + +fail_unmask_interrupts: + if (bci->transceiver != NULL) { + otg_unregister_notifier(bci->transceiver, &bci->otg_nb); + otg_put_transceiver(bci->transceiver); + } + free_irq(bci->irq_bci, bci); +fail_bci_irq: + free_irq(bci->irq_chg, bci); +fail_chg_irq: + power_supply_unregister(&bci->usb); +fail_register_usb: + power_supply_unregister(&bci->ac); +fail_register_ac: + platform_set_drvdata(pdev, NULL); + kfree(bci); + + return ret; +} + +static int __exit twl4030_bci_remove(struct platform_device *pdev) +{ + struct twl4030_bci *bci = platform_get_drvdata(pdev); + + twl4030_charger_enable_ac(false); + twl4030_charger_enable_usb(bci, false); + + /* mask interrupts */ + twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, + TWL4030_INTERRUPTS_BCIIMR1A); + twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, + TWL4030_INTERRUPTS_BCIIMR2A); + + if (bci->transceiver != NULL) { + otg_unregister_notifier(bci->transceiver, &bci->otg_nb); + otg_put_transceiver(bci->transceiver); + } + free_irq(bci->irq_bci, bci); + free_irq(bci->irq_chg, bci); + power_supply_unregister(&bci->usb); + power_supply_unregister(&bci->ac); + platform_set_drvdata(pdev, NULL); + kfree(bci); + + return 0; +} + +static struct platform_driver twl4030_bci_driver = { + .driver = { + .name = "twl4030_bci", + .owner = THIS_MODULE, + }, + .remove = __exit_p(twl4030_bci_remove), +}; + +static int __init twl4030_bci_init(void) +{ + return platform_driver_probe(&twl4030_bci_driver, twl4030_bci_probe); +} +module_init(twl4030_bci_init); + +static void __exit twl4030_bci_exit(void) +{ + platform_driver_unregister(&twl4030_bci_driver); +} +module_exit(twl4030_bci_exit); + +MODULE_AUTHOR("Gražydas Ignotas"); +MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:twl4030_bci"); From 353f867b5536e55e46801562987d605778cf262b Mon Sep 17 00:00:00 2001 From: Ryan Mallon Date: Fri, 1 Oct 2010 14:17:42 -0700 Subject: [PATCH 13/17] ds2782_battery: Fix units Correct the unit names in the ds2782 battery driver. Changes voltage_uA to voltage_uV and capacity_uA to capacity. Signed-off-by: Ryan Mallon Cc: Geert Uytterhoeven Cc: David Woodhouse Cc: Ben Hutchings Cc: Yulia Vilensky Signed-off-by: Andrew Morton Signed-off-by: Anton Vorontsov --- drivers/power/ds2782_battery.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/power/ds2782_battery.c b/drivers/power/ds2782_battery.c index 84d3c43cf2bc..6957e8af6449 100644 --- a/drivers/power/ds2782_battery.c +++ b/drivers/power/ds2782_battery.c @@ -44,8 +44,8 @@ struct ds278x_info; struct ds278x_battery_ops { int (*get_battery_current)(struct ds278x_info *info, int *current_uA); - int (*get_battery_voltage)(struct ds278x_info *info, int *voltage_uA); - int (*get_battery_capacity)(struct ds278x_info *info, int *capacity_uA); + int (*get_battery_voltage)(struct ds278x_info *info, int *voltage_uV); + int (*get_battery_capacity)(struct ds278x_info *info, int *capacity); }; #define to_ds278x_info(x) container_of(x, struct ds278x_info, battery) @@ -137,7 +137,7 @@ static int ds2782_get_current(struct ds278x_info *info, int *current_uA) return 0; } -static int ds2782_get_voltage(struct ds278x_info *info, int *voltage_uA) +static int ds2782_get_voltage(struct ds278x_info *info, int *voltage_uV) { s16 raw; int err; @@ -149,7 +149,7 @@ static int ds2782_get_voltage(struct ds278x_info *info, int *voltage_uA) err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw); if (err) return err; - *voltage_uA = (raw / 32) * 4800; + *voltage_uV = (raw / 32) * 4800; return 0; } @@ -177,7 +177,7 @@ static int ds2786_get_current(struct ds278x_info *info, int *current_uA) return 0; } -static int ds2786_get_voltage(struct ds278x_info *info, int *voltage_uA) +static int ds2786_get_voltage(struct ds278x_info *info, int *voltage_uV) { s16 raw; int err; @@ -189,7 +189,7 @@ static int ds2786_get_voltage(struct ds278x_info *info, int *voltage_uA) err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw); if (err) return err; - *voltage_uA = (raw / 8) * 1220; + *voltage_uV = (raw / 8) * 1220; return 0; } From 85efc8a18cedf70e55acd0c825e2d9d2f3b19999 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Mon, 4 Oct 2010 10:51:37 +0300 Subject: [PATCH 14/17] power_supply: Add types for USB chargers This adds power supply types for USB chargers defined in Battery Charging Specification 1.1. Signed-off-by: Heikki Krogerus Signed-off-by: Anton Vorontsov --- drivers/power/power_supply_sysfs.c | 3 ++- include/linux/power_supply.h | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index 9d30eeb8c810..88f5e43100c2 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -42,7 +42,8 @@ static ssize_t power_supply_show_property(struct device *dev, struct device_attribute *attr, char *buf) { static char *type_text[] = { - "Battery", "UPS", "Mains", "USB" + "Battery", "UPS", "Mains", "USB", + "USB_DCP", "USB_CDP", "USB_ACA" }; static char *status_text[] = { "Unknown", "Charging", "Discharging", "Not charging", "Full" diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 30083a896f36..d37fef67ece2 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -125,7 +125,10 @@ enum power_supply_type { POWER_SUPPLY_TYPE_BATTERY = 0, POWER_SUPPLY_TYPE_UPS, POWER_SUPPLY_TYPE_MAINS, - POWER_SUPPLY_TYPE_USB, + POWER_SUPPLY_TYPE_USB, /* Standard Downstream Port */ + POWER_SUPPLY_TYPE_USB_DCP, /* Dedicated Charging Port */ + POWER_SUPPLY_TYPE_USB_CDP, /* Charging Downstream Port */ + POWER_SUPPLY_TYPE_USB_ACA, /* Accessory Charger Adapters */ }; union power_supply_propval { From fe3f6d097a042cff54bc1dc06f21ef528affe8ca Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Mon, 4 Oct 2010 10:51:38 +0300 Subject: [PATCH 15/17] power_supply: Introduce maximum current property USB only gives the maximum current allowed to draw. Signed-off-by: Heikki Krogerus Signed-off-by: Anton Vorontsov --- drivers/power/power_supply_sysfs.c | 1 + include/linux/power_supply.h | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index 88f5e43100c2..cd1f90754a3a 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -139,6 +139,7 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(voltage_min_design), POWER_SUPPLY_ATTR(voltage_now), POWER_SUPPLY_ATTR(voltage_avg), + POWER_SUPPLY_ATTR(current_max), POWER_SUPPLY_ATTR(current_now), POWER_SUPPLY_ATTR(current_avg), POWER_SUPPLY_ATTR(power_now), diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index d37fef67ece2..7d7325685c42 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -89,6 +89,7 @@ enum power_supply_property { POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_MAX, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_AVG, POWER_SUPPLY_PROP_POWER_NOW, From 9292585c67ddd76285f2610c0cbf6b61fe27f5db Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Tue, 5 Oct 2010 10:05:34 +0800 Subject: [PATCH 16/17] bq27x00_battery: Add missing kfree(di->bus) in bq27x00_battery_remove() Signed-off-by: Axel Lin Signed-off-by: Anton Vorontsov --- drivers/power/bq27x00_battery.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c index 3ec9c6a8896b..eff0273d4030 100644 --- a/drivers/power/bq27x00_battery.c +++ b/drivers/power/bq27x00_battery.c @@ -418,6 +418,7 @@ static int bq27x00_battery_remove(struct i2c_client *client) power_supply_unregister(&di->bat); + kfree(di->bus); kfree(di->bat.name); mutex_lock(&battery_mutex); From 57894517139b283a3cf27e36b29b4b4d7c45b303 Mon Sep 17 00:00:00 2001 From: matt mooney Date: Wed, 6 Oct 2010 00:46:27 -0700 Subject: [PATCH 17/17] power_supply: Makefile cleanup Replace EXTRA_CFLAGS with ccflags-y; remove if-statements and replace with lists using the kbuild idiom. None of the dependencies are modified. Signed-off-by: matt mooney Signed-off-by: Anton Vorontsov --- drivers/power/Makefile | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 695e54fdbcdb..c75772eb157c 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -1,16 +1,8 @@ -power_supply-objs := power_supply_core.o +ccflags-$(CONFIG_POWER_SUPPLY_DEBUG) := -DDEBUG -ifeq ($(CONFIG_SYSFS),y) -power_supply-objs += power_supply_sysfs.o -endif - -ifeq ($(CONFIG_LEDS_TRIGGERS),y) -power_supply-objs += power_supply_leds.o -endif - -ifeq ($(CONFIG_POWER_SUPPLY_DEBUG),y) -EXTRA_CFLAGS += -DDEBUG -endif +power_supply-y := power_supply_core.o +power_supply-$(CONFIG_SYSFS) += power_supply_sysfs.o +power_supply-$(CONFIG_LEDS_TRIGGERS) += power_supply_leds.o obj-$(CONFIG_POWER_SUPPLY) += power_supply.o