diff options
Diffstat (limited to 'drivers/power/bq2415x_charger.c')
| -rw-r--r-- | drivers/power/bq2415x_charger.c | 190 |
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"); |
