power_supply: Add driver for LTC4088 Charger
LTC4088 is high efficiency USB powerpath controller
and Li-Ion/Polymer battery charger. This driver
enables the charger, allows to set current limits,
and detection.
Change-Id: I844431210cfd6e71d02fa7ffb730ee56d8417273
Signed-off-by: Jay Chokshi <jchokshi@codeaurora.org>
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 7c8dfea..3dfa68e 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -338,6 +338,13 @@
help
Say Y here to enable support for pm8921 chip bms subdevice
+config LTC4088_CHARGER
+ tristate "LTC4088 Charger driver"
+ depends on GPIOLIB
+ help
+ Say Y here to enable support for ltc4088 chip charger. It controls the
+ operations through GPIO pins.
+
config PM8921_BMS
select PM8XXX_CCADC
tristate "PM8921 Battery Monitoring System driver"
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index e168590..03db839 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -48,3 +48,4 @@
obj-$(CONFIG_PM8XXX_CCADC) += pm8xxx-ccadc.o
obj-$(CONFIG_PM8921_BMS) += pm8921-bms.o
obj-$(CONFIG_PM8921_CHARGER) += pm8921-charger.o
+obj-$(CONFIG_LTC4088_CHARGER) += ltc4088-charger.o
diff --git a/drivers/power/ltc4088-charger.c b/drivers/power/ltc4088-charger.c
new file mode 100644
index 0000000..dbc75cd
--- /dev/null
+++ b/drivers/power/ltc4088-charger.c
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/power/ltc4088-charger.h>
+
+#define MAX_CURRENT_UA(n) (n)
+#define MAX_CURRENT_MA(n) (n * MAX_CURRENT_UA(1000))
+
+/**
+ * ltc4088_max_current - A typical current values supported by the charger
+ * @LTC4088_MAX_CURRENT_100mA: 100mA current
+ * @LTC4088_MAX_CURRENT_500mA: 500mA current
+ * @LTC4088_MAX_CURRENT_1A: 1A current
+ */
+enum ltc4088_max_current {
+ LTC4088_MAX_CURRENT_100mA = 100,
+ LTC4088_MAX_CURRENT_500mA = 500,
+ LTC4088_MAX_CURRENT_1A = 1000,
+};
+
+/**
+ * struct ltc4088_chg_chip - Device information
+ * @dev: Device pointer to access the parent
+ * @lock: Enable mutual exclusion
+ * @usb_psy: USB device information
+ * @gpio_mode_select_d0: GPIO #pin for D0 charger line
+ * @gpio_mode_select_d1: GPIO #pin for D1 charger line
+ * @gpio_mode_select_d2: GPIO #pin for D2 charger line
+ * @max_current: Maximum current that is supplied at this time
+ */
+struct ltc4088_chg_chip {
+ struct device *dev;
+ struct mutex lock;
+ struct power_supply usb_psy;
+ unsigned int gpio_mode_select_d0;
+ unsigned int gpio_mode_select_d1;
+ unsigned int gpio_mode_select_d2;
+ unsigned int max_current;
+};
+
+static enum power_supply_property pm_power_props[] = {
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static char *pm_power_supplied_to[] = {
+ "battery",
+};
+
+static int ltc4088_set_charging(struct ltc4088_chg_chip *chip, bool enable)
+{
+ mutex_lock(&chip->lock);
+
+ if (enable) {
+ gpio_set_value_cansleep(chip->gpio_mode_select_d2, 0);
+ } else {
+ /* When disabling charger, set the max current to 0 also */
+ chip->max_current = 0;
+ gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d2, 1);
+ }
+
+ mutex_unlock(&chip->lock);
+
+ return 0;
+}
+
+static void ltc4088_set_max_current(struct ltc4088_chg_chip *chip, int value)
+{
+ mutex_lock(&chip->lock);
+
+ /* If current is less than 100mA, we can not support that granularity */
+ if (value < MAX_CURRENT_MA(LTC4088_MAX_CURRENT_100mA)) {
+ chip->max_current = 0;
+ gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1);
+ } else if (value < MAX_CURRENT_MA(LTC4088_MAX_CURRENT_500mA)) {
+ chip->max_current = MAX_CURRENT_MA(LTC4088_MAX_CURRENT_100mA);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d0, 0);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d1, 0);
+ } else if (value < MAX_CURRENT_MA(LTC4088_MAX_CURRENT_1A)) {
+ chip->max_current = MAX_CURRENT_MA(LTC4088_MAX_CURRENT_500mA);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d0, 0);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1);
+ } else {
+ chip->max_current = MAX_CURRENT_MA(LTC4088_MAX_CURRENT_1A);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d1, 0);
+ }
+
+ mutex_unlock(&chip->lock);
+}
+
+static void ltc4088_set_charging_off(struct ltc4088_chg_chip *chip)
+{
+ gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1);
+}
+
+static int ltc4088_set_initial_state(struct ltc4088_chg_chip *chip)
+{
+ int rc;
+
+ rc = gpio_request(chip->gpio_mode_select_d0, "ltc4088_D0");
+ if (rc) {
+ pr_err("gpio request failed for GPIO %d\n",
+ chip->gpio_mode_select_d0);
+ return rc;
+ }
+
+ rc = gpio_request(chip->gpio_mode_select_d1, "ltc4088_D1");
+ if (rc) {
+ pr_err("gpio request failed for GPIO %d\n",
+ chip->gpio_mode_select_d1);
+ goto gpio_err_d0;
+ }
+
+ rc = gpio_request(chip->gpio_mode_select_d2, "ltc4088_D2");
+ if (rc) {
+ pr_err("gpio request failed for GPIO %d\n",
+ chip->gpio_mode_select_d2);
+ goto gpio_err_d1;
+ }
+
+ rc = gpio_direction_output(chip->gpio_mode_select_d0, 0);
+ if (rc) {
+ pr_err("failed to set direction for GPIO %d\n",
+ chip->gpio_mode_select_d0);
+ goto gpio_err_d2;
+ }
+
+ rc = gpio_direction_output(chip->gpio_mode_select_d1, 0);
+ if (rc) {
+ pr_err("failed to set direction for GPIO %d\n",
+ chip->gpio_mode_select_d1);
+ goto gpio_err_d2;
+ }
+
+ rc = gpio_direction_output(chip->gpio_mode_select_d2, 1);
+ if (rc) {
+ pr_err("failed to set direction for GPIO %d\n",
+ chip->gpio_mode_select_d2);
+ goto gpio_err_d2;
+ }
+
+ return 0;
+
+gpio_err_d2:
+ gpio_free(chip->gpio_mode_select_d2);
+gpio_err_d1:
+ gpio_free(chip->gpio_mode_select_d1);
+gpio_err_d0:
+ gpio_free(chip->gpio_mode_select_d0);
+ return rc;
+}
+
+static int pm_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ltc4088_chg_chip *chip;
+
+ if (psy->type == POWER_SUPPLY_TYPE_USB) {
+ chip = container_of(psy, struct ltc4088_chg_chip,
+ usb_psy);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (chip->max_current)
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = chip->max_current;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static int pm_power_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct ltc4088_chg_chip *chip;
+
+ if (psy->type == POWER_SUPPLY_TYPE_USB) {
+ chip = container_of(psy, struct ltc4088_chg_chip,
+ usb_psy);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ltc4088_set_charging(chip, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ ltc4088_set_max_current(chip, val->intval);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static int __devinit ltc4088_charger_probe(struct platform_device *pdev)
+{
+ int rc;
+ struct ltc4088_chg_chip *chip;
+ const struct ltc4088_charger_platform_data *pdata
+ = pdev->dev.platform_data;
+
+ if (!pdata) {
+ pr_err("missing platform data\n");
+ return -EINVAL;
+ }
+
+ chip = kzalloc(sizeof(struct ltc4088_chg_chip),
+ GFP_KERNEL);
+ if (!chip) {
+ pr_err("Cannot allocate pm_chg_chip\n");
+ return -ENOMEM;
+ }
+
+ chip->dev = &pdev->dev;
+
+ if (pdata->gpio_mode_select_d0 < 0 ||
+ pdata->gpio_mode_select_d1 < 0 ||
+ pdata->gpio_mode_select_d2 < 0) {
+ pr_err("Invalid platform data supplied\n");
+ rc = -EINVAL;
+ goto free_chip;
+ }
+
+ mutex_init(&chip->lock);
+
+ chip->gpio_mode_select_d0 = pdata->gpio_mode_select_d0;
+ chip->gpio_mode_select_d1 = pdata->gpio_mode_select_d1;
+ chip->gpio_mode_select_d2 = pdata->gpio_mode_select_d2;
+
+ chip->usb_psy.name = "usb",
+ chip->usb_psy.type = POWER_SUPPLY_TYPE_USB,
+ chip->usb_psy.supplied_to = pm_power_supplied_to,
+ chip->usb_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to),
+ chip->usb_psy.properties = pm_power_props,
+ chip->usb_psy.num_properties = ARRAY_SIZE(pm_power_props),
+ chip->usb_psy.get_property = pm_power_get_property,
+ chip->usb_psy.set_property = pm_power_set_property,
+
+ rc = power_supply_register(chip->dev, &chip->usb_psy);
+ if (rc < 0) {
+ pr_err("power_supply_register usb failed rc = %d\n", rc);
+ goto free_chip;
+ }
+
+ platform_set_drvdata(pdev, chip);
+
+ rc = ltc4088_set_initial_state(chip);
+ if (rc < 0) {
+ pr_err("setting initial state failed rc = %d\n", rc);
+ goto unregister_usb;
+ }
+
+ return 0;
+
+unregister_usb:
+ platform_set_drvdata(pdev, NULL);
+ power_supply_unregister(&chip->usb_psy);
+free_chip:
+ kfree(chip);
+
+ return rc;
+}
+
+static int __devexit ltc4088_charger_remove(struct platform_device *pdev)
+{
+ struct ltc4088_chg_chip *chip = platform_get_drvdata(pdev);
+
+ ltc4088_set_charging_off(chip);
+
+ gpio_free(chip->gpio_mode_select_d2);
+ gpio_free(chip->gpio_mode_select_d1);
+ gpio_free(chip->gpio_mode_select_d0);
+
+ power_supply_unregister(&chip->usb_psy);
+
+ platform_set_drvdata(pdev, NULL);
+ mutex_destroy(&chip->lock);
+ kfree(chip);
+
+ return 0;
+}
+
+static struct platform_driver ltc4088_charger_driver = {
+ .probe = ltc4088_charger_probe,
+ .remove = __devexit_p(ltc4088_charger_remove),
+ .driver = {
+ .name = LTC4088_CHARGER_DEV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init ltc4088_charger_init(void)
+{
+ return platform_driver_register(<c4088_charger_driver);
+}
+
+static void __exit ltc4088_charger_exit(void)
+{
+ platform_driver_unregister(<c4088_charger_driver);
+}
+
+subsys_initcall(ltc4088_charger_init);
+module_exit(ltc4088_charger_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("LTC4088 charger/battery driver");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:" LTC4088_CHARGER_DEV_NAME);
diff --git a/include/linux/power/ltc4088-charger.h b/include/linux/power/ltc4088-charger.h
new file mode 100644
index 0000000..7a0bacf
--- /dev/null
+++ b/include/linux/power/ltc4088-charger.h
@@ -0,0 +1,30 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#ifndef LTC4088_CHARGER_H_
+#define LTC4088_CHARGER_H_
+
+#define LTC4088_CHARGER_DEV_NAME "ltc4088-charger"
+
+/**
+ * struct ltc4088_charger_platform_data - platform data for LTC4088 charger
+ * @gpio_mode_select_d0: GPIO #pin for D0 charger line
+ * @gpio_mode_select_d1: GPIO #pin for D1 charger line
+ * @gpio_mode_select_d2: GPIO #pin for D2 charger line
+ */
+struct ltc4088_charger_platform_data {
+ unsigned int gpio_mode_select_d0;
+ unsigned int gpio_mode_select_d1;
+ unsigned int gpio_mode_select_d2;
+};
+
+#endif /* LTC4088_CHARGER_H_ */