summaryrefslogtreecommitdiff
path: root/drivers/power/bq2471x-charger.c
diff options
context:
space:
mode:
authorAndy Park <andyp@nvidia.com>2013-08-26 14:41:51 -0700
committerDan Willemsen <dwillemsen@nvidia.com>2013-09-14 13:44:46 -0700
commitf06f4bb6ec63bb3c75b3f810bd11e1a00d4742f7 (patch)
treeb6c0e171f30cd34ca7af89c088f29738a64c2efb /drivers/power/bq2471x-charger.c
parent6a873613df62c2ba7a5dea2273af8e3df1e3cf83 (diff)
power: add bq2471x battery charger driver
Add BQ24715/BQ24717 battery charger driver. Bug 1344257 Change-Id: Ia5bf9d3af7f836d937634b00043adef1c6391b0b Signed-off-by: Andy Park <andyp@nvidia.com> Reviewed-on: http://git-master/r/268056 Reviewed-by: Automatic_Commit_Validation_User Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
Diffstat (limited to 'drivers/power/bq2471x-charger.c')
-rw-r--r--drivers/power/bq2471x-charger.c406
1 files changed, 406 insertions, 0 deletions
diff --git a/drivers/power/bq2471x-charger.c b/drivers/power/bq2471x-charger.c
new file mode 100644
index 000000000000..7260cace17d1
--- /dev/null
+++ b/drivers/power/bq2471x-charger.c
@@ -0,0 +1,406 @@
+/*
+ * bq2471x-charger.c -- BQ24715 Charger driver
+ *
+ * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Author: Andy Park <andyp@nvidia.com>
+ * Author: Syed Rafiuddin <srafiuddin@nvidia.com>
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
+ * whether express or implied; 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 <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/sched/rt.h>
+#include <linux/time.h>
+#include <linux/timer.h>
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/power/bq2471x-charger.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/slab.h>
+#include <linux/rtc.h>
+#include <linux/alarmtimer.h>
+
+struct bq2471x_chip {
+ struct device *dev;
+ struct power_supply ac;
+ struct regmap *regmap;
+ struct mutex mutex;
+ int irq;
+ int gpio;
+ int ac_online;
+ int dac_ichg;
+ int dac_v;
+ int dac_minsv;
+ int dac_iin;
+ int suspended;
+ int wdt_refresh_timeout;
+ struct kthread_worker bq_kworker;
+ struct task_struct *bq_kworker_task;
+ struct kthread_work bq_wdt_work;
+};
+
+/* Kthread scheduling parameters */
+struct sched_param bq2471x_param = {
+ .sched_priority = MAX_RT_PRIO - 1,
+};
+
+static const struct regmap_config bq2471x_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = BQ2471X_MAX_REGS,
+};
+
+/* Charge current limit */
+static const unsigned int dac_ichg[] = {
+ 64, 128, 256, 512, 1024, 2048, 4096,
+};
+
+/* Output charge regulation voltage */
+static const unsigned int dac_v[] = {
+ 16, 32, 64, 128, 256, 512, 1024, 2048,
+ 4096, 8192, 16384,
+};
+
+/* Minimum system votlage */
+static const unsigned int dac_minsv[] = {
+ 256, 512, 1024, 2048, 4096, 8192,
+};
+
+/* Setting input current */
+static const unsigned int dac_iin[] = {
+ 64, 128, 256, 512, 1024, 2048, 4096,
+};
+
+static int bq2471x_read(struct bq2471x_chip *bq2471x,
+ unsigned int reg, unsigned int *val)
+{
+ return regmap_read(bq2471x->regmap, reg, val);
+}
+
+static int bq2471x_write(struct bq2471x_chip *bq2471x,
+ unsigned int reg, unsigned int val)
+{
+ return regmap_write(bq2471x->regmap, reg, val);
+}
+
+static int bq2471x_update_bits(struct bq2471x_chip *bq2471x,
+ unsigned int reg, unsigned int mask, unsigned int val)
+{
+ return regmap_update_bits(bq2471x->regmap, reg, mask, val);
+}
+
+static uint16_t convert_endianness(uint16_t val)
+{
+ return (val >> 8) | (val << 8);
+}
+
+static enum power_supply_property bq2471x_psy_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static int bq2471x_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp, union power_supply_propval *val)
+{
+ struct bq2471x_chip *bq2471x;
+
+ bq2471x = container_of(psy, struct bq2471x_chip, ac);
+ if (psp == POWER_SUPPLY_PROP_ONLINE)
+ val->intval = bq2471x->ac_online;
+ else
+ return -EINVAL;
+ return 0;
+}
+
+static int bq2471x_check_manufacturer(struct bq2471x_chip *bq2471x)
+{
+ int ret;
+ uint16_t val;
+
+ ret = bq2471x_read(bq2471x, BQ2471X_MANUFACTURER_ID_REG, &val);
+ if (ret < 0) {
+ dev_err(bq2471x->dev,
+ "MANUFACTURER_ID_REG read failed: %d\n", ret);
+ return ret;
+ }
+
+ if ((val & BQ2471X_MANUFACTURER_ID) == BQ2471X_MANUFACTURER_ID)
+ dev_info(bq2471x->dev,
+ "correct manufacturer id detected: 0x%4x\n", val);
+ else {
+ dev_info(bq2471x->dev,
+ "wrong manufactuerer id detected: 0x%4x\n", val);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int bq2471x_show_chip_version(struct bq2471x_chip *bq2471x)
+{
+ int ret;
+ uint16_t val;
+
+ ret = bq2471x_read(bq2471x, BQ2471X_DEVICE_ID_REG, &val);
+ if (ret < 0) {
+ dev_err(bq2471x->dev, "DEVICE_ID_REG read failed: %d\n", ret);
+ return ret;
+ }
+
+ if ((val & BQ24715_DEVICE_ID) == BQ24715_DEVICE_ID)
+ dev_info(bq2471x->dev, "chip type BQ24715 detected\n");
+ else if ((val & BQ24717_DEVICE_ID) == BQ24717_DEVICE_ID)
+ dev_info(bq2471x->dev, "chip type BQ24717 detected\n");
+ else {
+ dev_info(bq2471x->dev, "unrecognized chip type: 0x%4x\n", val);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int bq2471x_hw_init(struct bq2471x_chip *bq2471x)
+{
+ int ret = 0;
+ uint16_t val;
+
+ /* Configure control */
+ ret = bq2471x_write(bq2471x, BQ2471X_CHARGE_OPTION,
+ BQ2471X_CHARGE_OPTION_POR);
+ if (ret < 0) {
+ dev_err(bq2471x->dev, "CHARGE_OPTION write failed %d\n", ret);
+ return ret;
+ }
+
+ /* Configure output charge regulation voltage */
+ ret = bq2471x_write(bq2471x, BQ2471X_MAX_CHARGE_VOLTAGE,
+ convert_endianness(bq2471x->dac_v));
+ if (ret < 0) {
+ dev_err(bq2471x->dev, "CHARGE_VOLTAGE write failed %d\n", ret);
+ return ret;
+ }
+
+ ret = bq2471x_write(bq2471x, BQ2471X_MIN_SYS_VOLTAGE,
+ convert_endianness(bq2471x->dac_minsv));
+ if (ret < 0) {
+ dev_err(bq2471x->dev, "MIN_SYS_VOLTAGE write failed %d\n", ret);
+ return ret;
+ }
+
+ /* Configure setting input current */
+ ret = bq2471x_write(bq2471x, BQ2471X_INPUT_CURRENT,
+ convert_endianness(bq2471x->dac_iin));
+ if (ret < 0) {
+ dev_err(bq2471x->dev, "INPUT_CURRENT write failed %d\n", ret);
+ return ret;
+ }
+
+ /* Configure charge current limit */
+ ret = bq2471x_write(bq2471x, BQ2471X_CHARGE_CURRENT,
+ convert_endianness(bq2471x->dac_ichg));
+ if (ret < 0) {
+ dev_err(bq2471x->dev, "CHARGE_CURRENT write failed %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static void bq2471x_work_thread(struct kthread_work *work)
+{
+ struct bq2471x_chip *bq2471x = container_of(work,
+ struct bq2471x_chip, bq_wdt_work);
+ int ret;
+
+ for (;;) {
+ ret = bq2471x_hw_init(bq2471x);
+ if (ret < 0) {
+ dev_err(bq2471x->dev, "Hardware init failed %d\n", ret);
+ return;
+ }
+ ret = bq2471x_update_bits(bq2471x, BQ2471X_CHARGE_OPTION,
+ BQ2471X_WATCHDOG_TIMER, 0x3);
+ if (ret < 0) {
+ dev_err(bq2471x->dev,
+ "CHARGE_OPTION write failed %d\n", ret);
+ return;
+ }
+ msleep(bq2471x->wdt_refresh_timeout * 1000);
+ }
+}
+
+static int bq2471x_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct bq2471x_chip *bq2471x;
+ struct bq2471x_platform_data *pdata;
+ int ret = 0;
+ uint16_t val;
+
+ pdata = client->dev.platform_data;
+ if (!pdata) {
+ dev_err(&client->dev, "No Platform data");
+ return -EINVAL;
+ }
+
+ ret = gpio_request(pdata->gpio, "bq2471x-charger");
+ if (ret) {
+ dev_err(&client->dev, "Failed to request gpio pin: %d\n", ret);
+ goto psy_error;
+ }
+
+ ret = gpio_direction_output(pdata->gpio, 1);
+ if (ret) {
+ dev_err(&client->dev,
+ "Failed to set gpio to output: %d\n", ret);
+ goto psy_error;
+ }
+
+ gpio_set_value(pdata->gpio, 1);
+
+ msleep(20);
+
+ bq2471x = devm_kzalloc(&client->dev, sizeof(*bq2471x), GFP_KERNEL);
+ if (!bq2471x) {
+ dev_err(&client->dev, "Memory allocation failed\n");
+ return -ENOMEM;
+ }
+ bq2471x->dev = &client->dev;
+
+ bq2471x->dac_ichg = pdata->dac_ichg;
+ bq2471x->dac_v = pdata->dac_v;
+ bq2471x->dac_minsv = pdata->dac_minsv;
+ bq2471x->dac_iin = pdata->dac_iin;
+ bq2471x->wdt_refresh_timeout = pdata->wdt_refresh_timeout;
+
+ i2c_set_clientdata(client, bq2471x);
+ bq2471x->irq = client->irq;
+ mutex_init(&bq2471x->mutex);
+
+ bq2471x->ac_online = 0;
+
+ bq2471x->regmap = devm_regmap_init_i2c(client, &bq2471x_regmap_config);
+ if (IS_ERR(bq2471x->regmap)) {
+ ret = PTR_ERR(bq2471x->regmap);
+ dev_err(&client->dev, "regmap init failed with err %d\n", ret);
+ return ret;
+ }
+
+ ret = bq2471x_check_manufacturer(bq2471x);
+ if (ret < 0) {
+ dev_err(bq2471x->dev, "manufacturer check failed %d\n", ret);
+ return ret;
+ }
+
+ ret = bq2471x_show_chip_version(bq2471x);
+ if (ret < 0) {
+ dev_err(bq2471x->dev, "version read failed %d\n", ret);
+ return ret;
+ }
+
+ bq2471x->ac.name = "bq2471x-ac";
+ bq2471x->ac.type = POWER_SUPPLY_TYPE_MAINS;
+ bq2471x->ac.get_property = bq2471x_ac_get_property;
+ bq2471x->ac.properties = bq2471x_psy_props;
+ bq2471x->ac.num_properties = ARRAY_SIZE(bq2471x_psy_props);
+
+ ret = power_supply_register(bq2471x->dev, &bq2471x->ac);
+ if (ret < 0) {
+ dev_err(bq2471x->dev,
+ "AC power supply register failed %d\n", ret);
+ return ret;
+ }
+
+ ret = bq2471x_hw_init(bq2471x);
+ if (ret < 0) {
+ dev_err(bq2471x->dev, "Hardware init failed %d\n", ret);
+ goto gpio_err;
+ }
+
+ init_kthread_worker(&bq2471x->bq_kworker);
+ bq2471x->bq_kworker_task = kthread_run(kthread_worker_fn,
+ &bq2471x->bq_kworker,
+ dev_name(bq2471x->dev));
+ if (IS_ERR(bq2471x->bq_kworker_task)) {
+ ret = PTR_ERR(bq2471x->bq_kworker_task);
+ dev_err(&client->dev, "Kworker task creation failed %d\n", ret);
+ goto gpio_err;
+ }
+
+ init_kthread_work(&bq2471x->bq_wdt_work, bq2471x_work_thread);
+ sched_setscheduler(bq2471x->bq_kworker_task,
+ SCHED_FIFO, &bq2471x_param);
+ queue_kthread_work(&bq2471x->bq_kworker, &bq2471x->bq_wdt_work);
+
+ dev_info(bq2471x->dev, "bq2471x charger registerd\n");
+
+ return ret;
+
+psy_error:
+ power_supply_unregister(&bq2471x->ac);
+gpio_err:
+ gpio_free(bq2471x->gpio);
+ return ret;
+}
+
+static int bq2471x_remove(struct i2c_client *client)
+{
+ struct bq2471x_chip *bq2471x = i2c_get_clientdata(client);
+ flush_kthread_worker(&bq2471x->bq_kworker);
+ kthread_stop(bq2471x->bq_kworker_task);
+ power_supply_unregister(&bq2471x->ac);
+ gpio_free(bq2471x->gpio);
+ return 0;
+}
+
+static const struct i2c_device_id bq2471x_id[] = {
+ {.name = "bq2471x",},
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, bq2471x_id);
+
+static struct i2c_driver bq2471x_i2c_driver = {
+ .driver = {
+ .name = "bq2471x",
+ .owner = THIS_MODULE,
+ },
+ .probe = bq2471x_probe,
+ .remove = bq2471x_remove,
+ .id_table = bq2471x_id,
+};
+
+static int __init bq2471x_module_init(void)
+{
+ return i2c_add_driver(&bq2471x_i2c_driver);
+}
+subsys_initcall(bq2471x_module_init);
+
+static void __exit bq2471x_cleanup(void)
+{
+ i2c_del_driver(&bq2471x_i2c_driver);
+}
+module_exit(bq2471x_cleanup);
+
+MODULE_DESCRIPTION("BQ24715/BQ24717 battery charger driver");
+MODULE_AUTHOR("Andy Park <andyp@nvidia.com>");
+MODULE_AUTHOR("Syed Rafiuddin <srafiuddin@nvidia.com");
+MODULE_LICENSE("GPL v2");
+