blob: d08d1a2ee9b2fa6466e64dde388d48d57fafdc49 [file] [log] [blame]
Laxman Dewanganf27ed352012-11-29 19:33:02 -05001/*
2 * bq2419x-charger.c - Battery charger driver
3 *
4 * Copyright (c) 2012, NVIDIA Corporation.
5 *
6 * Author: Laxman Dewangan <ldewangan@nvidia.com>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation version 2.
11 *
12 * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
13 * whether express or implied; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20 * 02111-1307, USA
21 */
22#include <linux/delay.h>
23#include <linux/err.h>
24#include <linux/i2c.h>
25#include <linux/init.h>
26#include <linux/interrupt.h>
27#include <linux/gpio.h>
28#include <linux/kernel.h>
29#include <linux/module.h>
30#include <linux/mfd/bq2419x.h>
31#include <linux/regmap.h>
32#include <linux/platform_device.h>
33#include <linux/power_supply.h>
34#include <linux/slab.h>
Syed Rafiuddinee69db32013-01-09 15:40:55 +053035#include <linux/regulator/driver.h>
36#include <linux/regulator/machine.h>
Laxman Dewanganf27ed352012-11-29 19:33:02 -050037
38/* input current limit */
39static const unsigned int iinlim[] = {
40 100, 150, 500, 900, 1200, 1500, 2000, 3000,
41};
42
43struct bq2419x_charger {
44 struct device *dev;
45 struct bq2419x_chip *chip;
46 struct power_supply ac;
47 struct power_supply usb;
48 int ac_online;
49 int usb_online;
50 int gpio_status;
51 int gpio_interrupt;
52 int usb_in_current_limit;
53 int ac_in_current_limit;
54 unsigned use_mains:1;
55 unsigned use_usb:1;
Syed Rafiuddin5ea55fb2012-12-12 20:58:29 +053056 int status;
57 void (*update_status)(int, int);
Syed Rafiuddinee69db32013-01-09 15:40:55 +053058 struct regulator_dev *rdev;
59 struct regulator_desc reg_desc;
60 struct regulator_init_data reg_init_data;
Kunal Agrawal24680952013-01-29 14:44:19 +053061 int shutdown_complete;
62 struct mutex mutex;
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +053063 struct mutex chrg_mutex;
Laxman Dewanganf27ed352012-11-29 19:33:02 -050064};
65
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +053066struct bq2419x_charger *bq_charger;
67
Laxman Dewanganf27ed352012-11-29 19:33:02 -050068static enum power_supply_property bq2419x_psy_props[] = {
69 POWER_SUPPLY_PROP_ONLINE,
70};
Laxman Dewanganf27ed352012-11-29 19:33:02 -050071static int current_to_reg(const unsigned int *tbl,
72 size_t size, unsigned int val)
73{
74 size_t i;
75
76 for (i = 0; i < size; i++)
77 if (val < tbl[i])
78 break;
79 return i > 0 ? i - 1 : -EINVAL;
80}
81
82static int bq2419x_charger_enable(struct bq2419x_charger *charger)
83{
84 int ret;
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +053085 mutex_lock(&charger->mutex);
86 if (charger && charger->shutdown_complete) {
87 mutex_unlock(&charger->mutex);
Kunal Agrawal24680952013-01-29 14:44:19 +053088 return -ENODEV;
89 }
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +053090 mutex_unlock(&charger->mutex);
91
Laxman Dewanganf27ed352012-11-29 19:33:02 -050092 dev_info(charger->dev, "Charging enabled\n");
93 ret = regmap_update_bits(charger->chip->regmap, BQ2419X_PWR_ON_REG,
94 ENABLE_CHARGE_MASK, ENABLE_CHARGE);
95 if (ret < 0)
96 dev_err(charger->dev, "register update failed, err %d\n", ret);
97 return ret;
98}
99
100static int bq2419x_charger_disable(struct bq2419x_charger *charger)
101{
102 int ret;
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +0530103
104 mutex_lock(&charger->mutex);
105 if (charger && charger->shutdown_complete) {
106 mutex_unlock(&charger->mutex);
Kunal Agrawal24680952013-01-29 14:44:19 +0530107 return -ENODEV;
108 }
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +0530109 mutex_unlock(&charger->mutex);
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500110
111 dev_info(charger->dev, "Charging disabled\n");
112 ret = regmap_update_bits(charger->chip->regmap, BQ2419X_PWR_ON_REG,
113 ENABLE_CHARGE_MASK, 0);
114 if (ret < 0)
115 dev_err(charger->dev, "register update failed, err %d\n", ret);
116 return ret;
117}
118
119static int bq2419x_ac_get_property(struct power_supply *psy,
120 enum power_supply_property psp, union power_supply_propval *val)
121{
122 struct bq2419x_charger *chip;
123
124 chip = container_of(psy, struct bq2419x_charger, ac);
125 if (psp == POWER_SUPPLY_PROP_ONLINE)
126 val->intval = chip->ac_online;
127 else
128 return -EINVAL;
129 return 0;
130}
131
132static int bq2419x_usb_get_property(struct power_supply *psy,
133 enum power_supply_property psp,
134 union power_supply_propval *val)
135{
136 struct bq2419x_charger *chip;
137
138 chip = container_of(psy, struct bq2419x_charger, usb);
139 if (psp == POWER_SUPPLY_PROP_ONLINE)
140 val->intval = chip->usb_online;
141 else
142 return -EINVAL;
143 return 0;
144}
145
146static int bq2419x_init(struct bq2419x_charger *charger)
147{
148 int val, ret = 0;
149
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +0530150 mutex_lock(&charger->mutex);
151 if (charger && charger->shutdown_complete) {
152 mutex_unlock(&charger->mutex);
Kunal Agrawal24680952013-01-29 14:44:19 +0530153 return -ENODEV;
154 }
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +0530155 mutex_unlock(&charger->mutex);
Kunal Agrawal24680952013-01-29 14:44:19 +0530156
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500157 if (charger->usb_online) {
158 val = current_to_reg(iinlim, ARRAY_SIZE(iinlim),
159 charger->usb_in_current_limit);
160 if (val < 0)
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530161 val = 0;
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500162
163 ret = regmap_write(charger->chip->regmap,
164 BQ2419X_INPUT_SRC_REG, val);
165 if (ret < 0)
166 dev_err(charger->dev, "error reading reg: 0x%x\n",
167 BQ2419X_INPUT_SRC_REG);
168 }
169
170 if (charger->ac_online) {
171 val = current_to_reg(iinlim, ARRAY_SIZE(iinlim),
172 charger->ac_in_current_limit);
173 if (val < 0)
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530174 val = 0;
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500175
176 ret = regmap_write(charger->chip->regmap,
177 BQ2419X_INPUT_SRC_REG, val);
178 if (ret < 0)
179 dev_err(charger->dev, "error reading reg: 0x%x\n",
180 BQ2419X_INPUT_SRC_REG);
181 }
182 return ret;
183}
184
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530185static int bq2419x_enable_charger(struct regulator_dev *rdev,
186 int min_uA, int max_uA)
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500187{
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530188 int ret = 0;
189 int val;
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500190
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530191 msleep(200);
192 bq_charger->usb_online = 0;
193 bq_charger->ac_online = 0;
194 bq_charger->status = 0;
195
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +0530196 mutex_lock(&bq_charger->chrg_mutex);
Kunal Agrawal24680952013-01-29 14:44:19 +0530197 if (bq_charger && bq_charger->shutdown_complete) {
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +0530198 mutex_unlock(&bq_charger->chrg_mutex);
Kunal Agrawal24680952013-01-29 14:44:19 +0530199 return -ENODEV;
200 }
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +0530201 mutex_unlock(&bq_charger->chrg_mutex);
Kunal Agrawal24680952013-01-29 14:44:19 +0530202
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530203 ret = regmap_read(bq_charger->chip->regmap, BQ2419X_SYS_STAT_REG, &val);
204 if (ret < 0)
205 dev_err(bq_charger->dev, "error reading reg: 0x%x\n",
206 BQ2419X_SYS_STAT_REG);
207
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +0530208 if (max_uA == 0 && val != 0)
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530209 return ret;
210
Syed Rafiuddin208dc892013-01-24 14:57:46 +0530211 if ((val & BQ2419x_VBUS_STAT) == BQ2419x_VBUS_UNKNOWN) {
212 bq_charger->status = 0;
213 bq_charger->usb_online = 1;
214 bq_charger->usb_in_current_limit = 500;
215 ret = bq2419x_init(bq_charger);
216 if (ret < 0)
217 goto error;
218 if (bq_charger->update_status)
219 bq_charger->update_status
220 (bq_charger->status, 0);
221 } else if ((val & BQ2419x_VBUS_STAT) == BQ2419x_VBUS_USB) {
222 bq_charger->status = 1;
223 bq_charger->usb_online = 1;
224 bq_charger->usb_in_current_limit = max_uA;
225 ret = bq2419x_init(bq_charger);
226 if (ret < 0)
227 goto error;
228 if (bq_charger->update_status)
229 bq_charger->update_status
230 (bq_charger->status, 2);
231 } else if ((val & BQ2419x_VBUS_STAT) == BQ2419x_VBUS_AC) {
232 bq_charger->status = 1;
233 bq_charger->ac_online = 1;
234 bq_charger->ac_in_current_limit = max_uA;
235 ret = bq2419x_init(bq_charger);
236 if (ret < 0)
237 goto error;
238 if (bq_charger->update_status)
239 bq_charger->update_status
240 (bq_charger->status, 1);
241 }
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530242 if (ret == 0) {
243 if (bq_charger->use_mains)
244 power_supply_changed(&bq_charger->ac);
245 if (bq_charger->use_usb)
246 power_supply_changed(&bq_charger->usb);
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500247 }
Syed Rafiuddin208dc892013-01-24 14:57:46 +0530248 return 0;
249error:
250 dev_err(bq_charger->dev, "Charger enable failed, err = %d\n", ret);
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530251 return ret;
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500252}
253
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530254static struct regulator_ops bq2419x_tegra_regulator_ops = {
255 .set_current_limit = bq2419x_enable_charger,
256};
257
Kunal Agrawal24680952013-01-29 14:44:19 +0530258static void bq2419x_charger_shutdown(struct platform_device *pdev)
259{
260 struct bq2419x_charger *charger = platform_get_drvdata(pdev);
261
262 mutex_lock(&charger->mutex);
263 charger->shutdown_complete = 1;
264 mutex_unlock(&charger->mutex);
265}
266
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500267static int __devinit bq2419x_charger_probe(struct platform_device *pdev)
268{
269 struct bq2419x_platform_data *chip_pdata;
270 struct bq2419x_charger_platform_data *bcharger_pdata = NULL;
271 struct bq2419x_charger *charger;
272 int ret;
273 u32 val;
274
275 chip_pdata = dev_get_platdata(pdev->dev.parent);
276 if (chip_pdata)
277 bcharger_pdata = chip_pdata->bcharger_pdata;
278
279 if (!bcharger_pdata) {
280 dev_err(&pdev->dev, "No Platform data");
281 return -EIO;
282 }
283
284 charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
285 if (!charger) {
286 dev_err(&pdev->dev, "Memory alloc failed\n");
287 return -ENOMEM;
288 }
289
290 charger->chip = dev_get_drvdata(pdev->dev.parent);
291 charger->dev = &pdev->dev;
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500292 charger->use_usb = bcharger_pdata->use_usb;
293 charger->use_mains = bcharger_pdata->use_mains;
294 charger->gpio_status = bcharger_pdata->gpio_status;
295 charger->gpio_interrupt = bcharger_pdata->gpio_interrupt;
Syed Rafiuddin5ea55fb2012-12-12 20:58:29 +0530296 charger->update_status = bcharger_pdata->update_status;
Kunal Agrawal24680952013-01-29 14:44:19 +0530297 charger->shutdown_complete = 0;
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530298 bq_charger = charger;
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500299 platform_set_drvdata(pdev, charger);
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +0530300 mutex_init(&charger->mutex);
301 mutex_init(&charger->chrg_mutex);
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500302
303 ret = regmap_read(charger->chip->regmap, BQ2419X_REVISION_REG, &val);
304 if (ret < 0) {
305 dev_err(charger->dev, "error reading reg: 0x%x, err = %d\n",
306 BQ2419X_REVISION_REG, ret);
307 return ret;
308 }
309
310 if ((val & BQ24190_IC_VER) == BQ24190_IC_VER)
311 dev_info(charger->dev, "chip type BQ24190 detected\n");
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530312 else if ((val & BQ24192_IC_VER) == BQ24192_IC_VER)
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500313 dev_info(charger->dev, "chip type BQ2419X/3 detected\n");
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530314 else if ((val & BQ24192i_IC_VER) == BQ24192i_IC_VER)
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500315 dev_info(charger->dev, "chip type BQ2419Xi detected\n");
316
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500317 charger->gpio_status = bcharger_pdata->gpio_status;
318 charger->gpio_interrupt = bcharger_pdata->gpio_interrupt;
319
320 if (gpio_is_valid(charger->gpio_status)) {
321 ret = gpio_request_one(charger->gpio_status,
322 GPIOF_IN, "bq2419x-status");
323 if (ret < 0) {
324 dev_err(charger->dev,
325 "status gpio request failed, err = %d\n", ret);
326 return ret;
327 }
328 }
329
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530330 charger->reg_desc.name = "vbus_charger";
331 charger->reg_desc.ops = &bq2419x_tegra_regulator_ops;
332 charger->reg_desc.type = REGULATOR_CURRENT;
333 charger->reg_desc.id = bcharger_pdata->regulator_id;
334 charger->reg_desc.type = REGULATOR_CURRENT;
335 charger->reg_desc.owner = THIS_MODULE;
336
337 charger->reg_init_data.supply_regulator = NULL;
338 charger->reg_init_data.num_consumer_supplies =
339 bcharger_pdata->num_consumer_supplies;
340
341 charger->reg_init_data.regulator_init = NULL;
342 charger->reg_init_data.consumer_supplies =
343 bcharger_pdata->consumer_supplies;
344 charger->reg_init_data.driver_data = charger;
345 charger->reg_init_data.constraints.name = "vbus_charger";
346 charger->reg_init_data.constraints.min_uA = 0;
347 charger->reg_init_data.constraints.max_uA =
348 bcharger_pdata->max_charge_current_mA * 1000;
349
350 charger->reg_init_data.constraints.valid_modes_mask =
351 REGULATOR_MODE_NORMAL |
352 REGULATOR_MODE_STANDBY;
353
354 charger->reg_init_data.constraints.valid_ops_mask =
355 REGULATOR_CHANGE_MODE |
356 REGULATOR_CHANGE_STATUS |
357 REGULATOR_CHANGE_CURRENT;
358
359 charger->rdev = regulator_register(&charger->reg_desc, charger->dev,
360 &charger->reg_init_data, charger, NULL);
361
362 if (IS_ERR(charger->rdev)) {
363 dev_err(&pdev->dev, "failed to register %s\n",
364 charger->reg_desc.name);
365 ret = PTR_ERR(charger->rdev);
366 goto free_gpio_status;
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500367 }
368
369 charger->ac_online = 0;
370 charger->usb_online = 0;
Syed Rafiuddin5ea55fb2012-12-12 20:58:29 +0530371 charger->status = 0;
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500372 if (charger->use_mains) {
373 charger->ac.name = "bq2419x-ac";
374 charger->ac.type = POWER_SUPPLY_TYPE_MAINS;
375 charger->ac.get_property = bq2419x_ac_get_property;
376 charger->ac.properties = bq2419x_psy_props;
377 charger->ac.num_properties = ARRAY_SIZE(bq2419x_psy_props);
378 ret = power_supply_register(&pdev->dev, &charger->ac);
379 if (ret) {
380 dev_err(&pdev->dev, "failed: power supply register\n");
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530381 return ret;
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500382 }
383 }
384
385 if (charger->use_usb) {
386 charger->usb.name = "bq2419x-usb";
387 charger->usb.type = POWER_SUPPLY_TYPE_USB;
388 charger->usb.get_property = bq2419x_usb_get_property;
389 charger->usb.properties = bq2419x_psy_props;
390 charger->usb.num_properties = ARRAY_SIZE(bq2419x_psy_props);
391 ret = power_supply_register(&pdev->dev, &charger->usb);
392 if (ret) {
393 dev_err(&pdev->dev, "failed: power supply register\n");
394 goto psy_error;
395 }
396 }
397
Syed Rafiuddin208dc892013-01-24 14:57:46 +0530398 /* enable charging */
399 ret = bq2419x_charger_enable(charger);
400 if (ret < 0)
401 goto psy_error1;
402
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500403 return 0;
Syed Rafiuddin208dc892013-01-24 14:57:46 +0530404
405psy_error1:
406 power_supply_unregister(&charger->usb);
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500407psy_error:
408 power_supply_unregister(&charger->ac);
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500409free_gpio_status:
410 if (gpio_is_valid(charger->gpio_status))
411 gpio_free(charger->gpio_status);
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +0530412 mutex_destroy(&charger->mutex);
413 mutex_destroy(&charger->chrg_mutex);
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500414 return ret;
415}
416
417static int __devexit bq2419x_charger_remove(struct platform_device *pdev)
418{
419 struct bq2419x_charger *charger = platform_get_drvdata(pdev);
420
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500421 if (charger->use_mains)
422 power_supply_unregister(&charger->usb);
423 if (charger->use_usb)
424 power_supply_unregister(&charger->ac);
425 if (gpio_is_valid(charger->gpio_status))
426 gpio_free(charger->gpio_status);
Rohith Seelaboyina2fd2e112013-02-02 18:10:54 +0530427 mutex_destroy(&charger->mutex);
428 mutex_destroy(&charger->chrg_mutex);
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500429 return 0;
430}
431
432static struct platform_driver bq2419x_charger_driver = {
433 .driver = {
434 .name = "bq2419x-battery-charger",
435 .owner = THIS_MODULE,
436 },
437 .probe = bq2419x_charger_probe,
Kunal Agrawal24680952013-01-29 14:44:19 +0530438 .shutdown = bq2419x_charger_shutdown,
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500439 .remove = __devexit_p(bq2419x_charger_probe),
440};
441
Syed Rafiuddinee69db32013-01-09 15:40:55 +0530442static int __init bq2419x_charger_init(void)
443{
444 return platform_driver_register(&bq2419x_charger_driver);
445}
446subsys_initcall(bq2419x_charger_init);
447
448static void __exit bq2419x_charger_cleanup(void)
449{
450 platform_driver_unregister(&bq2419x_charger_driver);
451}
452module_exit(bq2419x_charger_cleanup);
Laxman Dewanganf27ed352012-11-29 19:33:02 -0500453
454MODULE_DESCRIPTION("bq2419x battery charger driver");
455MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
456MODULE_LICENSE("GPL v2");