aboutsummaryrefslogtreecommitdiff
path: root/drivers/power/bq2415x_charger.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/bq2415x_charger.c')
-rw-r--r--drivers/power/bq2415x_charger.c190
1 files changed, 130 insertions, 60 deletions
diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c
index ee842b37f46..79a37f6d330 100644
--- a/drivers/power/bq2415x_charger.c
+++ b/drivers/power/bq2415x_charger.c
@@ -1,7 +1,7 @@
/*
* bq2415x charger driver
*
- * Copyright (C) 2011-2012 Pali Rohár <pali.rohar@gmail.com>
+ * Copyright (C) 2011-2013 Pali Rohár <pali.rohar@gmail.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
@@ -28,7 +28,6 @@
* http://www.ti.com/product/bq24155
*/
-#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/param.h>
@@ -171,6 +170,8 @@ struct bq2415x_device {
struct bq2415x_platform_data init_data;
struct power_supply charger;
struct delayed_work work;
+ struct power_supply *notify_psy;
+ struct notifier_block nb;
enum bq2415x_mode reported_mode;/* mode reported by hook function */
enum bq2415x_mode mode; /* current configured mode */
enum bq2415x_chip chip;
@@ -606,9 +607,13 @@ static int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq,
{
int val = (mV/10 - 350) / 2;
+ /*
+ * According to datasheet, maximum battery regulation voltage is
+ * 4440mV which is b101111 = 47.
+ */
if (val < 0)
val = 0;
- else if (val > 94) /* FIXME: Max is 94 or 122 ? Set max value ? */
+ else if (val > 47)
return -EINVAL;
return bq2415x_i2c_write_mask(bq, BQ2415X_REG_VOLTAGE, val,
@@ -734,12 +739,10 @@ static int bq2415x_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode)
int charger = 0;
int boost = 0;
- if (mode == BQ2415X_MODE_HOST_CHARGER ||
- mode == BQ2415X_MODE_DEDICATED_CHARGER)
- charger = 1;
-
if (mode == BQ2415X_MODE_BOOST)
boost = 1;
+ else if (mode != BQ2415X_MODE_OFF)
+ charger = 1;
if (!charger)
ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE);
@@ -751,6 +754,10 @@ static int bq2415x_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode)
return ret;
switch (mode) {
+ case BQ2415X_MODE_OFF:
+ dev_dbg(bq->dev, "changing mode to: Offline\n");
+ ret = bq2415x_set_current_limit(bq, 100);
+ break;
case BQ2415X_MODE_NONE:
dev_dbg(bq->dev, "changing mode to: N/A\n");
ret = bq2415x_set_current_limit(bq, 100);
@@ -790,24 +797,53 @@ static int bq2415x_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode)
}
-/* hook function called by other driver which set reported mode */
-static void bq2415x_hook_function(enum bq2415x_mode mode, void *data)
+static int bq2415x_notifier_call(struct notifier_block *nb,
+ unsigned long val, void *v)
{
- struct bq2415x_device *bq = data;
+ struct bq2415x_device *bq =
+ container_of(nb, struct bq2415x_device, nb);
+ struct power_supply *psy = v;
+ enum bq2415x_mode mode;
+ union power_supply_propval prop;
+ int ret;
+ int mA;
- if (!bq)
- return;
+ if (val != PSY_EVENT_PROP_CHANGED)
+ return NOTIFY_OK;
+
+ if (psy != bq->notify_psy)
+ return NOTIFY_OK;
+
+ dev_dbg(bq->dev, "notifier call was called\n");
+
+ ret = psy->get_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
+ if (ret != 0)
+ return NOTIFY_OK;
+
+ mA = prop.intval;
+
+ if (mA == 0)
+ mode = BQ2415X_MODE_OFF;
+ else if (mA < 500)
+ mode = BQ2415X_MODE_NONE;
+ else if (mA < 1800)
+ mode = BQ2415X_MODE_HOST_CHARGER;
+ else
+ mode = BQ2415X_MODE_DEDICATED_CHARGER;
+
+ if (bq->reported_mode == mode)
+ return NOTIFY_OK;
- dev_dbg(bq->dev, "hook function was called\n");
bq->reported_mode = mode;
/* if automode is not enabled do not tell about reported_mode */
if (bq->automode < 1)
- return;
+ return NOTIFY_OK;
sysfs_notify(&bq->charger.dev->kobj, NULL, "reported_mode");
bq2415x_set_mode(bq, bq->reported_mode);
+ return NOTIFY_OK;
}
/**** timer functions ****/
@@ -843,7 +879,7 @@ static void bq2415x_timer_error(struct bq2415x_device *bq, const char *msg)
dev_err(bq->dev, "%s\n", msg);
if (bq->automode > 0)
bq->automode = 0;
- bq2415x_set_mode(bq, BQ2415X_MODE_NONE);
+ bq2415x_set_mode(bq, BQ2415X_MODE_OFF);
bq2415x_set_autotimer(bq, 0);
}
@@ -1136,6 +1172,10 @@ static ssize_t bq2415x_sysfs_set_mode(struct device *dev,
return -ENOSYS;
bq->automode = 1;
mode = bq->reported_mode;
+ } else if (strncmp(buf, "off", 3) == 0) {
+ if (bq->automode > 0)
+ bq->automode = 0;
+ mode = BQ2415X_MODE_OFF;
} else if (strncmp(buf, "none", 4) == 0) {
if (bq->automode > 0)
bq->automode = 0;
@@ -1183,6 +1223,9 @@ static ssize_t bq2415x_sysfs_show_mode(struct device *dev,
ret += sprintf(buf+ret, "auto (");
switch (bq->mode) {
+ case BQ2415X_MODE_OFF:
+ ret += sprintf(buf+ret, "off");
+ break;
case BQ2415X_MODE_NONE:
ret += sprintf(buf+ret, "none");
break;
@@ -1217,6 +1260,8 @@ static ssize_t bq2415x_sysfs_show_reported_mode(struct device *dev,
return -EINVAL;
switch (bq->reported_mode) {
+ case BQ2415X_MODE_OFF:
+ return sprintf(buf, "off\n");
case BQ2415X_MODE_NONE:
return sprintf(buf, "none\n");
case BQ2415X_MODE_HOST_CHARGER:
@@ -1498,23 +1543,20 @@ static int bq2415x_probe(struct i2c_client *client,
int num;
char *name;
struct bq2415x_device *bq;
+ struct device_node *np = client->dev.of_node;
+ struct bq2415x_platform_data *pdata = client->dev.platform_data;
- if (!client->dev.platform_data) {
- dev_err(&client->dev, "platform data not set\n");
+ if (!np && !pdata) {
+ dev_err(&client->dev, "platform data missing\n");
return -ENODEV;
}
/* Get new ID for the new device */
- ret = idr_pre_get(&bq2415x_id, GFP_KERNEL);
- if (ret == 0)
- return -ENOMEM;
-
mutex_lock(&bq2415x_id_mutex);
- ret = idr_get_new(&bq2415x_id, client, &num);
+ num = idr_alloc(&bq2415x_id, client, 0, 0, GFP_KERNEL);
mutex_unlock(&bq2415x_id_mutex);
-
- if (ret < 0)
- return ret;
+ if (num < 0)
+ return num;
name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
if (!name) {
@@ -1523,57 +1565,98 @@ static int bq2415x_probe(struct i2c_client *client,
goto error_1;
}
- bq = kzalloc(sizeof(*bq), GFP_KERNEL);
+ bq = devm_kzalloc(&client->dev, sizeof(*bq), GFP_KERNEL);
if (!bq) {
dev_err(&client->dev, "failed to allocate device data\n");
ret = -ENOMEM;
goto error_2;
}
+ if (np) {
+ bq->notify_psy = power_supply_get_by_phandle(np, "ti,usb-charger-detection");
+
+ if (!bq->notify_psy)
+ return -EPROBE_DEFER;
+ }
+ else if (pdata->notify_device)
+ bq->notify_psy = power_supply_get_by_name(pdata->notify_device);
+ else
+ bq->notify_psy = NULL;
+
i2c_set_clientdata(client, bq);
bq->id = num;
bq->dev = &client->dev;
bq->chip = id->driver_data;
bq->name = name;
- bq->mode = BQ2415X_MODE_NONE;
- bq->reported_mode = BQ2415X_MODE_NONE;
+ bq->mode = BQ2415X_MODE_OFF;
+ bq->reported_mode = BQ2415X_MODE_OFF;
bq->autotimer = 0;
bq->automode = 0;
- memcpy(&bq->init_data, client->dev.platform_data,
- sizeof(bq->init_data));
+ if (np) {
+ ret = of_property_read_u32(np, "ti,current-limit",
+ &bq->init_data.current_limit);
+ if (ret)
+ return ret;
+ ret = of_property_read_u32(np, "ti,weak-battery-voltage",
+ &bq->init_data.weak_battery_voltage);
+ if (ret)
+ return ret;
+ ret = of_property_read_u32(np, "ti,battery-regulation-voltage",
+ &bq->init_data.battery_regulation_voltage);
+ if (ret)
+ return ret;
+ ret = of_property_read_u32(np, "ti,charge-current",
+ &bq->init_data.charge_current);
+ if (ret)
+ return ret;
+ ret = of_property_read_u32(np, "ti,termination-current",
+ &bq->init_data.termination_current);
+ if (ret)
+ return ret;
+ ret = of_property_read_u32(np, "ti,resistor-sense",
+ &bq->init_data.resistor_sense);
+ if (ret)
+ return ret;
+ } else {
+ memcpy(&bq->init_data, pdata, sizeof(bq->init_data));
+ }
bq2415x_reset_chip(bq);
ret = bq2415x_power_supply_init(bq);
if (ret) {
dev_err(bq->dev, "failed to register power supply: %d\n", ret);
- goto error_3;
+ goto error_2;
}
ret = bq2415x_sysfs_init(bq);
if (ret) {
dev_err(bq->dev, "failed to create sysfs entries: %d\n", ret);
- goto error_4;
+ goto error_3;
}
ret = bq2415x_set_defaults(bq);
if (ret) {
dev_err(bq->dev, "failed to set default values: %d\n", ret);
- goto error_5;
+ goto error_4;
}
- if (bq->init_data.set_mode_hook) {
- if (bq->init_data.set_mode_hook(
- bq2415x_hook_function, bq)) {
- bq->automode = 1;
- bq2415x_set_mode(bq, bq->reported_mode);
- dev_info(bq->dev, "automode enabled\n");
- } else {
- bq->automode = -1;
- dev_info(bq->dev, "automode failed\n");
+ if (bq->notify_psy) {
+ bq->nb.notifier_call = bq2415x_notifier_call;
+ ret = power_supply_reg_notifier(&bq->nb);
+ if (ret) {
+ dev_err(bq->dev, "failed to reg notifier: %d\n", ret);
+ goto error_5;
}
+
+ /* Query for initial reported_mode and set it */
+ bq2415x_notifier_call(&bq->nb, PSY_EVENT_PROP_CHANGED, bq->notify_psy);
+ bq2415x_set_mode(bq, bq->reported_mode);
+
+ bq->automode = 1;
+ dev_info(bq->dev, "automode enabled\n");
} else {
bq->automode = -1;
dev_info(bq->dev, "automode not supported\n");
@@ -1586,11 +1669,10 @@ static int bq2415x_probe(struct i2c_client *client,
return 0;
error_5:
- bq2415x_sysfs_exit(bq);
error_4:
- bq2415x_power_supply_exit(bq);
+ bq2415x_sysfs_exit(bq);
error_3:
- kfree(bq);
+ bq2415x_power_supply_exit(bq);
error_2:
kfree(name);
error_1:
@@ -1607,8 +1689,8 @@ static int bq2415x_remove(struct i2c_client *client)
{
struct bq2415x_device *bq = i2c_get_clientdata(client);
- if (bq->init_data.set_mode_hook)
- bq->init_data.set_mode_hook(NULL, NULL);
+ if (bq->notify_psy)
+ power_supply_unreg_notifier(&bq->nb);
bq2415x_sysfs_exit(bq);
bq2415x_power_supply_exit(bq);
@@ -1622,7 +1704,6 @@ static int bq2415x_remove(struct i2c_client *client)
dev_info(bq->dev, "driver unregistered\n");
kfree(bq->name);
- kfree(bq);
return 0;
}
@@ -1652,18 +1733,7 @@ static struct i2c_driver bq2415x_driver = {
.remove = bq2415x_remove,
.id_table = bq2415x_i2c_id_table,
};
-
-static int __init bq2415x_init(void)
-{
- return i2c_add_driver(&bq2415x_driver);
-}
-module_init(bq2415x_init);
-
-static void __exit bq2415x_exit(void)
-{
- i2c_del_driver(&bq2415x_driver);
-}
-module_exit(bq2415x_exit);
+module_i2c_driver(bq2415x_driver);
MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
MODULE_DESCRIPTION("bq2415x charger driver");