power: bq2419x: add shutdown functionality
[linux-2.6.git] / drivers / power / bq2419x-charger.c
1 /*
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>
35 #include <linux/regulator/driver.h>
36 #include <linux/regulator/machine.h>
37
38 struct bq2419x_charger *bq_charger;
39 /* input current limit */
40 static const unsigned int iinlim[] = {
41         100, 150, 500, 900, 1200, 1500, 2000, 3000,
42 };
43
44 struct bq2419x_charger {
45         struct device           *dev;
46         struct bq2419x_chip     *chip;
47         struct power_supply     ac;
48         struct power_supply     usb;
49         int                     ac_online;
50         int                     usb_online;
51         int                     gpio_status;
52         int                     gpio_interrupt;
53         int                     usb_in_current_limit;
54         int                     ac_in_current_limit;
55         unsigned                use_mains:1;
56         unsigned                use_usb:1;
57         int status;
58         void (*update_status)(int, int);
59         struct regulator_dev    *rdev;
60         struct regulator_desc   reg_desc;
61         struct regulator_init_data      reg_init_data;
62         int                     shutdown_complete;
63         struct mutex            mutex;
64 };
65
66 static enum power_supply_property bq2419x_psy_props[] = {
67         POWER_SUPPLY_PROP_ONLINE,
68 };
69
70 static int current_to_reg(const unsigned int *tbl,
71                         size_t size, unsigned int val)
72 {
73         size_t i;
74
75         for (i = 0; i < size; i++)
76                 if (val < tbl[i])
77                         break;
78         return i > 0 ? i - 1 : -EINVAL;
79 }
80
81 static int bq2419x_charger_enable(struct bq2419x_charger *charger)
82 {
83         int ret;
84         if (bq_charger && bq_charger->shutdown_complete) {
85                 mutex_unlock(&bq_charger->mutex);
86                 return -ENODEV;
87         }
88         dev_info(charger->dev, "Charging enabled\n");
89         ret = regmap_update_bits(charger->chip->regmap, BQ2419X_PWR_ON_REG,
90                                 ENABLE_CHARGE_MASK, ENABLE_CHARGE);
91         if (ret < 0)
92                 dev_err(charger->dev, "register update failed, err %d\n", ret);
93         mutex_unlock(&bq_charger->mutex);
94         return ret;
95 }
96
97 static int bq2419x_charger_disable(struct bq2419x_charger *charger)
98 {
99         int ret;
100         if (bq_charger && bq_charger->shutdown_complete) {
101                 mutex_unlock(&bq_charger->mutex);
102                 return -ENODEV;
103         }
104
105         dev_info(charger->dev, "Charging disabled\n");
106         ret = regmap_update_bits(charger->chip->regmap, BQ2419X_PWR_ON_REG,
107                                 ENABLE_CHARGE_MASK, 0);
108         if (ret < 0)
109                 dev_err(charger->dev, "register update failed, err %d\n", ret);
110         mutex_unlock(&bq_charger->mutex);
111         return ret;
112 }
113
114 static int bq2419x_ac_get_property(struct power_supply *psy,
115         enum power_supply_property psp, union power_supply_propval *val)
116 {
117         struct bq2419x_charger *chip;
118
119         chip = container_of(psy, struct bq2419x_charger, ac);
120         if (psp == POWER_SUPPLY_PROP_ONLINE)
121                 val->intval = chip->ac_online;
122         else
123                 return -EINVAL;
124         return 0;
125 }
126
127 static int bq2419x_usb_get_property(struct power_supply *psy,
128                 enum power_supply_property psp,
129                 union power_supply_propval *val)
130 {
131         struct bq2419x_charger *chip;
132
133         chip = container_of(psy, struct bq2419x_charger, usb);
134         if (psp == POWER_SUPPLY_PROP_ONLINE)
135                 val->intval = chip->usb_online;
136         else
137                 return -EINVAL;
138         return 0;
139 }
140
141 static int bq2419x_init(struct bq2419x_charger *charger)
142 {
143         int val, ret = 0;
144
145         if (bq_charger && bq_charger->shutdown_complete) {
146                 mutex_unlock(&bq_charger->mutex);
147                 return -ENODEV;
148         }
149
150         if (charger->usb_online) {
151                 val = current_to_reg(iinlim, ARRAY_SIZE(iinlim),
152                                         charger->usb_in_current_limit);
153                 if (val < 0)
154                         val = 0;
155
156                 ret = regmap_write(charger->chip->regmap,
157                                 BQ2419X_INPUT_SRC_REG, val);
158                 if (ret < 0)
159                         dev_err(charger->dev, "error reading reg: 0x%x\n",
160                                 BQ2419X_INPUT_SRC_REG);
161         }
162
163         if (charger->ac_online) {
164                 val = current_to_reg(iinlim, ARRAY_SIZE(iinlim),
165                                         charger->ac_in_current_limit);
166                 if (val < 0)
167                         val = 0;
168
169                 ret = regmap_write(charger->chip->regmap,
170                                 BQ2419X_INPUT_SRC_REG, val);
171                 if (ret < 0)
172                         dev_err(charger->dev, "error reading reg: 0x%x\n",
173                                 BQ2419X_INPUT_SRC_REG);
174         }
175         mutex_unlock(&bq_charger->mutex);
176         return ret;
177 }
178
179 static int bq2419x_enable_charger(struct regulator_dev *rdev,
180                                         int min_uA, int max_uA)
181 {
182         int ret = 0;
183         int val;
184
185         msleep(200);
186         bq_charger->usb_online = 0;
187         bq_charger->ac_online = 0;
188         bq_charger->status = 0;
189
190         mutex_lock(&bq_charger->mutex);
191         if (bq_charger && bq_charger->shutdown_complete) {
192                 mutex_unlock(&bq_charger->mutex);
193                 return -ENODEV;
194         }
195
196         ret = regmap_read(bq_charger->chip->regmap, BQ2419X_SYS_STAT_REG, &val);
197         if (ret < 0)
198                 dev_err(bq_charger->dev, "error reading reg: 0x%x\n",
199                                 BQ2419X_SYS_STAT_REG);
200
201         if (max_uA == 0 && val != 0) {
202                 mutex_unlock(&bq_charger->mutex);
203                 return ret;
204         }
205
206         if ((val & BQ2419x_VBUS_STAT) == BQ2419x_VBUS_UNKNOWN) {
207                 bq_charger->status = 0;
208                 bq_charger->usb_online = 1;
209                 bq_charger->usb_in_current_limit = 500;
210                 ret = bq2419x_init(bq_charger);
211                 if (ret < 0)
212                         goto error;
213                 if (bq_charger->update_status)
214                         bq_charger->update_status
215                                 (bq_charger->status, 0);
216         } else if ((val & BQ2419x_VBUS_STAT) == BQ2419x_VBUS_USB) {
217                 bq_charger->status = 1;
218                 bq_charger->usb_online = 1;
219                 bq_charger->usb_in_current_limit = max_uA;
220                 ret = bq2419x_init(bq_charger);
221                 if (ret < 0)
222                         goto error;
223                 if (bq_charger->update_status)
224                         bq_charger->update_status
225                                 (bq_charger->status, 2);
226         } else if ((val & BQ2419x_VBUS_STAT) == BQ2419x_VBUS_AC) {
227                 bq_charger->status = 1;
228                 bq_charger->ac_online = 1;
229                 bq_charger->ac_in_current_limit = max_uA;
230                 ret = bq2419x_init(bq_charger);
231                 if (ret < 0)
232                         goto error;
233                 if (bq_charger->update_status)
234                         bq_charger->update_status
235                                 (bq_charger->status, 1);
236         }
237         if (ret == 0) {
238                 if (bq_charger->use_mains)
239                         power_supply_changed(&bq_charger->ac);
240                 if (bq_charger->use_usb)
241                         power_supply_changed(&bq_charger->usb);
242         }
243         mutex_unlock(&bq_charger->mutex);
244         return 0;
245 error:
246         dev_err(bq_charger->dev, "Charger enable failed, err = %d\n", ret);
247         mutex_unlock(&bq_charger->mutex);
248         return ret;
249 }
250
251 static struct regulator_ops bq2419x_tegra_regulator_ops = {
252         .set_current_limit = bq2419x_enable_charger,
253 };
254
255 static void bq2419x_charger_shutdown(struct platform_device *pdev)
256 {
257         struct bq2419x_charger *charger = platform_get_drvdata(pdev);
258
259         mutex_lock(&charger->mutex);
260         charger->shutdown_complete = 1;
261         mutex_unlock(&charger->mutex);
262 }
263
264 static int __devinit bq2419x_charger_probe(struct platform_device *pdev)
265 {
266         struct bq2419x_platform_data *chip_pdata;
267         struct bq2419x_charger_platform_data *bcharger_pdata = NULL;
268         struct bq2419x_charger *charger;
269         int ret;
270         u32 val;
271
272         chip_pdata = dev_get_platdata(pdev->dev.parent);
273         if (chip_pdata)
274                 bcharger_pdata = chip_pdata->bcharger_pdata;
275
276         if (!bcharger_pdata) {
277                 dev_err(&pdev->dev, "No Platform data");
278                 return -EIO;
279         }
280
281         charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
282         if (!charger) {
283                 dev_err(&pdev->dev, "Memory alloc failed\n");
284                 return -ENOMEM;
285         }
286
287         charger->chip = dev_get_drvdata(pdev->dev.parent);
288         charger->dev = &pdev->dev;
289         charger->use_usb = bcharger_pdata->use_usb;
290         charger->use_mains = bcharger_pdata->use_mains;
291         charger->gpio_status = bcharger_pdata->gpio_status;
292         charger->gpio_interrupt = bcharger_pdata->gpio_interrupt;
293         charger->update_status = bcharger_pdata->update_status;
294         charger->shutdown_complete = 0;
295         bq_charger = charger;
296         platform_set_drvdata(pdev, charger);
297         mutex_init(&bq_charger->mutex);
298
299         mutex_lock(&bq_charger->mutex);
300         ret = regmap_read(charger->chip->regmap, BQ2419X_REVISION_REG, &val);
301         if (ret < 0) {
302                 dev_err(charger->dev, "error reading reg: 0x%x, err = %d\n",
303                                         BQ2419X_REVISION_REG, ret);
304                 mutex_unlock(&bq_charger->mutex);
305                 return ret;
306         }
307         mutex_unlock(&bq_charger->mutex);
308
309         if ((val & BQ24190_IC_VER) == BQ24190_IC_VER)
310                 dev_info(charger->dev, "chip type BQ24190 detected\n");
311         else if ((val & BQ24192_IC_VER) == BQ24192_IC_VER)
312                 dev_info(charger->dev, "chip type BQ2419X/3 detected\n");
313         else if ((val & BQ24192i_IC_VER) == BQ24192i_IC_VER)
314                 dev_info(charger->dev, "chip type BQ2419Xi detected\n");
315
316         charger->gpio_status = bcharger_pdata->gpio_status;
317         charger->gpio_interrupt = bcharger_pdata->gpio_interrupt;
318
319         if (gpio_is_valid(charger->gpio_status)) {
320                 ret = gpio_request_one(charger->gpio_status,
321                                         GPIOF_IN, "bq2419x-status");
322                 if (ret < 0) {
323                         dev_err(charger->dev,
324                                 "status gpio request failed, err = %d\n", ret);
325                         return ret;
326                 }
327         }
328
329         charger->reg_desc.name  = "vbus_charger";
330         charger->reg_desc.ops   = &bq2419x_tegra_regulator_ops;
331         charger->reg_desc.type  = REGULATOR_CURRENT;
332         charger->reg_desc.id    = bcharger_pdata->regulator_id;
333         charger->reg_desc.type  = REGULATOR_CURRENT;
334         charger->reg_desc.owner = THIS_MODULE;
335
336         charger->reg_init_data.supply_regulator         = NULL;
337         charger->reg_init_data.num_consumer_supplies    =
338                                 bcharger_pdata->num_consumer_supplies;
339
340         charger->reg_init_data.regulator_init           = NULL;
341         charger->reg_init_data.consumer_supplies        =
342                                         bcharger_pdata->consumer_supplies;
343         charger->reg_init_data.driver_data              = charger;
344         charger->reg_init_data.constraints.name         = "vbus_charger";
345         charger->reg_init_data.constraints.min_uA       = 0;
346         charger->reg_init_data.constraints.max_uA       =
347                                 bcharger_pdata->max_charge_current_mA * 1000;
348
349         charger->reg_init_data.constraints.valid_modes_mask =
350                                         REGULATOR_MODE_NORMAL |
351                                         REGULATOR_MODE_STANDBY;
352
353         charger->reg_init_data.constraints.valid_ops_mask =
354                                         REGULATOR_CHANGE_MODE |
355                                         REGULATOR_CHANGE_STATUS |
356                                         REGULATOR_CHANGE_CURRENT;
357
358         charger->rdev = regulator_register(&charger->reg_desc, charger->dev,
359                                         &charger->reg_init_data, charger, NULL);
360
361         if (IS_ERR(charger->rdev)) {
362                 dev_err(&pdev->dev, "failed to register %s\n",
363                                 charger->reg_desc.name);
364                 ret = PTR_ERR(charger->rdev);
365                 goto free_gpio_status;
366         }
367
368         charger->ac_online = 0;
369         charger->usb_online = 0;
370         charger->status = 0;
371         if (charger->use_mains) {
372                 charger->ac.name                = "bq2419x-ac";
373                 charger->ac.type                = POWER_SUPPLY_TYPE_MAINS;
374                 charger->ac.get_property        = bq2419x_ac_get_property;
375                 charger->ac.properties          = bq2419x_psy_props;
376                 charger->ac.num_properties      = ARRAY_SIZE(bq2419x_psy_props);
377                 ret = power_supply_register(&pdev->dev, &charger->ac);
378                 if (ret) {
379                         dev_err(&pdev->dev, "failed: power supply register\n");
380                         return ret;
381                 }
382         }
383
384         if (charger->use_usb) {
385                 charger->usb.name               = "bq2419x-usb";
386                 charger->usb.type               = POWER_SUPPLY_TYPE_USB;
387                 charger->usb.get_property       = bq2419x_usb_get_property;
388                 charger->usb.properties         = bq2419x_psy_props;
389                 charger->usb.num_properties     = ARRAY_SIZE(bq2419x_psy_props);
390                 ret = power_supply_register(&pdev->dev, &charger->usb);
391                 if (ret) {
392                         dev_err(&pdev->dev, "failed: power supply register\n");
393                         goto psy_error;
394                 }
395         }
396
397         /* enable charging */
398         ret = bq2419x_charger_enable(charger);
399         if (ret < 0)
400                 goto psy_error1;
401
402         return 0;
403
404 psy_error1:
405         power_supply_unregister(&charger->usb);
406 psy_error:
407         power_supply_unregister(&charger->ac);
408 free_gpio_status:
409         if (gpio_is_valid(charger->gpio_status))
410                 gpio_free(charger->gpio_status);
411         mutex_destroy(&bq_charger->mutex);
412         return ret;
413 }
414
415 static int __devexit bq2419x_charger_remove(struct platform_device *pdev)
416 {
417         struct bq2419x_charger *charger = platform_get_drvdata(pdev);
418
419         if (charger->use_mains)
420                 power_supply_unregister(&charger->usb);
421         if (charger->use_usb)
422                 power_supply_unregister(&charger->ac);
423         if (gpio_is_valid(charger->gpio_status))
424                 gpio_free(charger->gpio_status);
425         mutex_destroy(&bq_charger->mutex);
426         return 0;
427 }
428
429 static struct platform_driver bq2419x_charger_driver = {
430         .driver = {
431                 .name = "bq2419x-battery-charger",
432                 .owner = THIS_MODULE,
433         },
434         .probe = bq2419x_charger_probe,
435         .shutdown = bq2419x_charger_shutdown,
436         .remove = __devexit_p(bq2419x_charger_probe),
437 };
438
439 static int __init bq2419x_charger_init(void)
440 {
441         return platform_driver_register(&bq2419x_charger_driver);
442 }
443 subsys_initcall(bq2419x_charger_init);
444
445 static void __exit bq2419x_charger_cleanup(void)
446 {
447         platform_driver_unregister(&bq2419x_charger_driver);
448 }
449 module_exit(bq2419x_charger_cleanup);
450
451 MODULE_DESCRIPTION("bq2419x battery charger driver");
452 MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
453 MODULE_LICENSE("GPL v2");