blob: a45d2867381c108d093079dd341f3c973aff6031 [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 */
13
14#include <linux/i2c.h>
15#include <linux/gpio.h>
16#include <linux/errno.h>
17#include <linux/delay.h>
18#include <linux/module.h>
19#include <linux/debugfs.h>
20#include <linux/workqueue.h>
21#include <linux/interrupt.h>
22#include <linux/msm-charger.h>
23#include <linux/slab.h>
24#include <linux/i2c/isl9519.h>
25#include <linux/msm_adc.h>
26
27#define CHG_CURRENT_REG 0x14
28#define MAX_SYS_VOLTAGE_REG 0x15
29#define CONTROL_REG 0x3D
30#define MIN_SYS_VOLTAGE_REG 0x3E
31#define INPUT_CURRENT_REG 0x3F
32#define MANUFACTURER_ID_REG 0xFE
33#define DEVICE_ID_REG 0xFF
34
35#define TRCKL_CHG_STATUS_BIT 0x80
36
37#define ISL9519_CHG_PERIOD ((HZ) * 150)
38
39struct isl9519q_struct {
40 struct i2c_client *client;
41 struct delayed_work charge_work;
42 int present;
43 int batt_present;
44 bool charging;
45 int chgcurrent;
46 int term_current;
47 int input_current;
48 int max_system_voltage;
49 int min_system_voltage;
50 int valid_n_gpio;
51 struct dentry *dent;
52 struct msm_hardware_charger adapter_hw_chg;
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -070053 int suspended;
54 int charge_at_resume;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070055};
56
57static int isl9519q_read_reg(struct i2c_client *client, int reg,
58 u16 *val)
59{
60 int ret;
61 struct isl9519q_struct *isl_chg;
62
63 isl_chg = i2c_get_clientdata(client);
64 ret = i2c_smbus_read_word_data(isl_chg->client, reg);
65
66 if (ret < 0) {
67 dev_err(&isl_chg->client->dev,
68 "i2c read fail: can't read from %02x: %d\n", reg, ret);
69 return -EAGAIN;
70 } else
71 *val = ret;
72
73 return 0;
74}
75
76static int isl9519q_write_reg(struct i2c_client *client, int reg,
77 u16 val)
78{
79 int ret;
80 struct isl9519q_struct *isl_chg;
81
82 isl_chg = i2c_get_clientdata(client);
83 ret = i2c_smbus_write_word_data(isl_chg->client, reg, val);
84
85 if (ret < 0) {
86 dev_err(&isl_chg->client->dev,
87 "i2c write fail: can't write %02x to %02x: %d\n",
88 val, reg, ret);
89 return -EAGAIN;
90 }
91 return 0;
92}
93
94static int isl_read_adc(int channel, int *mv_reading)
95{
96 int ret;
97 void *h;
98 struct adc_chan_result adc_chan_result;
99 struct completion conv_complete_evt;
100
101 pr_debug("%s: called for %d\n", __func__, channel);
102 ret = adc_channel_open(channel, &h);
103 if (ret) {
104 pr_err("%s: couldnt open channel %d ret=%d\n",
105 __func__, channel, ret);
106 goto out;
107 }
108 init_completion(&conv_complete_evt);
109 ret = adc_channel_request_conv(h, &conv_complete_evt);
110 if (ret) {
111 pr_err("%s: couldnt request conv channel %d ret=%d\n",
112 __func__, channel, ret);
113 goto out;
114 }
115 ret = wait_for_completion_interruptible(&conv_complete_evt);
116 if (ret) {
117 pr_err("%s: wait interrupted channel %d ret=%d\n",
118 __func__, channel, ret);
119 goto out;
120 }
121 ret = adc_channel_read_result(h, &adc_chan_result);
122 if (ret) {
123 pr_err("%s: couldnt read result channel %d ret=%d\n",
124 __func__, channel, ret);
125 goto out;
126 }
127 ret = adc_channel_close(h);
128 if (ret)
129 pr_err("%s: couldnt close channel %d ret=%d\n",
130 __func__, channel, ret);
131 if (mv_reading)
132 *mv_reading = (int)adc_chan_result.measurement;
133
134 pr_debug("%s: done for %d\n", __func__, channel);
135 return adc_chan_result.physical;
136out:
137 *mv_reading = 0;
138 pr_debug("%s: done with error for %d\n", __func__, channel);
139 return -EINVAL;
140
141}
142
143static void isl9519q_charge(struct work_struct *isl9519_work)
144{
145 u16 temp;
146 int ret;
147 struct isl9519q_struct *isl_chg;
148 int isl_charger_current;
149 int mv_reading;
150
151 isl_chg = container_of(isl9519_work, struct isl9519q_struct,
152 charge_work.work);
153
154 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
155
156 if (isl_chg->charging) {
157 isl_charger_current = isl_read_adc(CHANNEL_ADC_BATT_AMON,
158 &mv_reading);
159 dev_dbg(&isl_chg->client->dev, "%s mv_reading=%d\n",
160 __func__, mv_reading);
161 dev_dbg(&isl_chg->client->dev, "%s isl_charger_current=%d\n",
162 __func__, isl_charger_current);
163 if (isl_charger_current >= 0
164 && isl_charger_current <= isl_chg->term_current) {
165 msm_charger_notify_event(
166 &isl_chg->adapter_hw_chg,
167 CHG_DONE_EVENT);
168 }
169 isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG,
170 isl_chg->chgcurrent);
171 ret = isl9519q_read_reg(isl_chg->client, CONTROL_REG, &temp);
172 if (!ret) {
173 if (!(temp & TRCKL_CHG_STATUS_BIT))
174 msm_charger_notify_event(
175 &isl_chg->adapter_hw_chg,
176 CHG_BATT_BEGIN_FAST_CHARGING);
177 } else {
178 dev_err(&isl_chg->client->dev,
179 "%s couldnt read cntrl reg\n", __func__);
180 }
181 schedule_delayed_work(&isl_chg->charge_work,
182 ISL9519_CHG_PERIOD);
183 }
184}
185
186static int isl9519q_start_charging(struct msm_hardware_charger *hw_chg,
187 int chg_voltage, int chg_current)
188{
189 struct isl9519q_struct *isl_chg;
190 int ret = 0;
191
192 isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg);
193 if (isl_chg->charging)
194 /* we are already charging */
195 return 0;
196
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700197 if (isl_chg->suspended) {
198 isl_chg->charge_at_resume = 1;
199 return 0;
200 }
201
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700202 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
203
204 ret = isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG,
205 isl_chg->chgcurrent);
206 if (ret) {
207 dev_err(&isl_chg->client->dev,
208 "%s coulnt write to current_reg\n", __func__);
209 goto out;
210 }
211
212 dev_dbg(&isl_chg->client->dev, "%s starting timed work\n",
213 __func__);
214 schedule_delayed_work(&isl_chg->charge_work,
215 ISL9519_CHG_PERIOD);
216 isl_chg->charging = true;
217
218out:
219 return ret;
220}
221
222static int isl9519q_stop_charging(struct msm_hardware_charger *hw_chg)
223{
224 struct isl9519q_struct *isl_chg;
225 int ret = 0;
226
227 isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg);
228 if (!(isl_chg->charging))
229 /* we arent charging */
230 return 0;
231
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700232 if (isl_chg->suspended) {
233 isl_chg->charge_at_resume = 0;
234 return 0;
235 }
236
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700237 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
238
239 ret = isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, 0);
240 if (ret) {
241 dev_err(&isl_chg->client->dev,
242 "%s coulnt write to current_reg\n", __func__);
243 goto out;
244 }
245
246 isl_chg->charging = false;
247 cancel_delayed_work(&isl_chg->charge_work);
248out:
249 return ret;
250}
251
252static int isl9519q_charging_switched(struct msm_hardware_charger *hw_chg)
253{
254 struct isl9519q_struct *isl_chg;
255
256 isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg);
257 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
258 return 0;
259}
260
261static irqreturn_t isl_valid_handler(int irq, void *dev_id)
262{
263 int val;
264 struct isl9519q_struct *isl_chg;
265 struct i2c_client *client = dev_id;
266
267 isl_chg = i2c_get_clientdata(client);
268 val = gpio_get_value_cansleep(isl_chg->valid_n_gpio);
269 if (val < 0) {
270 dev_err(&isl_chg->client->dev,
271 "%s gpio_get_value failed for %d ret=%d\n", __func__,
272 isl_chg->valid_n_gpio, val);
273 goto err;
274 }
275 dev_dbg(&isl_chg->client->dev, "%s val=%d\n", __func__, val);
276
277 if (val) {
278 if (isl_chg->present == 1) {
279 msm_charger_notify_event(&isl_chg->adapter_hw_chg,
280 CHG_REMOVED_EVENT);
281 isl_chg->present = 0;
282 }
283 } else {
284 if (isl_chg->present == 0) {
285 msm_charger_notify_event(&isl_chg->adapter_hw_chg,
286 CHG_INSERTED_EVENT);
287 isl_chg->present = 1;
288 }
289 }
290err:
291 return IRQ_HANDLED;
292}
293
294#define MAX_VOLTAGE_REG_MASK 0x3FF0
295#define MIN_VOLTAGE_REG_MASK 0x3F00
296#define DEFAULT_MAX_VOLTAGE_REG_VALUE 0x1070
297#define DEFAULT_MIN_VOLTAGE_REG_VALUE 0x0D00
298
299static int __devinit isl9519q_probe(struct i2c_client *client,
300 const struct i2c_device_id *id)
301{
302 struct isl_platform_data *pdata;
303 struct isl9519q_struct *isl_chg;
304 int ret;
305
306 ret = 0;
307 pdata = client->dev.platform_data;
308
309 if (pdata == NULL) {
310 dev_err(&client->dev, "%s no platform data\n", __func__);
311 ret = -EINVAL;
312 goto out;
313 }
314
315 if (!i2c_check_functionality(client->adapter,
316 I2C_FUNC_SMBUS_WORD_DATA)) {
317 ret = -EIO;
318 goto out;
319 }
320
321 isl_chg = kzalloc(sizeof(*isl_chg), GFP_KERNEL);
322 if (!isl_chg) {
323 ret = -ENOMEM;
324 goto out;
325 }
326
327 INIT_DELAYED_WORK(&isl_chg->charge_work, isl9519q_charge);
328 isl_chg->client = client;
329 isl_chg->chgcurrent = pdata->chgcurrent;
330 isl_chg->term_current = pdata->term_current;
331 isl_chg->input_current = pdata->input_current;
332 isl_chg->max_system_voltage = pdata->max_system_voltage;
333 isl_chg->min_system_voltage = pdata->min_system_voltage;
334 isl_chg->valid_n_gpio = pdata->valid_n_gpio;
335
336 /* h/w ignores lower 7 bits of charging current and input current */
337 isl_chg->chgcurrent &= ~0x7F;
338 isl_chg->input_current &= ~0x7F;
339
340 isl_chg->adapter_hw_chg.type = CHG_TYPE_AC;
341 isl_chg->adapter_hw_chg.rating = 2;
342 isl_chg->adapter_hw_chg.name = "isl-adapter";
343 isl_chg->adapter_hw_chg.start_charging = isl9519q_start_charging;
344 isl_chg->adapter_hw_chg.stop_charging = isl9519q_stop_charging;
345 isl_chg->adapter_hw_chg.charging_switched = isl9519q_charging_switched;
346
347 if (pdata->chg_detection_config) {
348 ret = pdata->chg_detection_config();
349 if (ret) {
350 dev_err(&client->dev, "%s valid config failed ret=%d\n",
351 __func__, ret);
352 goto free_isl_chg;
353 }
354 }
355
356 ret = gpio_request(pdata->valid_n_gpio, "isl_charger_valid");
357 if (ret) {
358 dev_err(&client->dev, "%s gpio_request failed for %d ret=%d\n",
359 __func__, pdata->valid_n_gpio, ret);
360 goto free_isl_chg;
361 }
362
363 i2c_set_clientdata(client, isl_chg);
364
365 ret = msm_charger_register(&isl_chg->adapter_hw_chg);
366 if (ret) {
367 dev_err(&client->dev,
368 "%s msm_charger_register failed for ret =%d\n",
369 __func__, ret);
370 goto free_gpio;
371 }
372
373 ret = request_threaded_irq(client->irq, NULL,
374 isl_valid_handler,
375 IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
376 "isl_charger_valid", client);
377 if (ret) {
378 dev_err(&client->dev,
379 "%s request_threaded_irq failed for %d ret =%d\n",
380 __func__, client->irq, ret);
381 goto unregister;
382 }
383 irq_set_irq_wake(client->irq, 1);
384
385 isl_chg->max_system_voltage &= MAX_VOLTAGE_REG_MASK;
386 isl_chg->min_system_voltage &= MIN_VOLTAGE_REG_MASK;
387 if (isl_chg->max_system_voltage == 0)
388 isl_chg->max_system_voltage = DEFAULT_MAX_VOLTAGE_REG_VALUE;
389 if (isl_chg->min_system_voltage == 0)
390 isl_chg->min_system_voltage = DEFAULT_MIN_VOLTAGE_REG_VALUE;
391
392 ret = isl9519q_write_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG,
393 isl_chg->max_system_voltage);
394 if (ret) {
395 dev_err(&client->dev,
396 "%s couldnt write to MAX_SYS_VOLTAGE_REG ret=%d\n",
397 __func__, ret);
398 goto free_irq;
399 }
400
401 ret = isl9519q_write_reg(isl_chg->client, MIN_SYS_VOLTAGE_REG,
402 isl_chg->min_system_voltage);
403 if (ret) {
404 dev_err(&client->dev,
405 "%s couldnt write to MIN_SYS_VOLTAGE_REG ret=%d\n",
406 __func__, ret);
407 goto free_irq;
408 }
409
410 if (isl_chg->input_current) {
411 ret = isl9519q_write_reg(isl_chg->client,
412 INPUT_CURRENT_REG,
413 isl_chg->input_current);
414 if (ret) {
415 dev_err(&client->dev,
416 "%s couldnt write INPUT_CURRENT_REG ret=%d\n",
417 __func__, ret);
418 goto free_irq;
419 }
420 }
421
422 ret = gpio_get_value_cansleep(isl_chg->valid_n_gpio);
423 if (ret < 0) {
424 dev_err(&client->dev,
425 "%s gpio_get_value failed for %d ret=%d\n", __func__,
426 pdata->valid_n_gpio, ret);
427 /* assume absent */
428 ret = 1;
429 }
430 if (!ret) {
431 msm_charger_notify_event(&isl_chg->adapter_hw_chg,
432 CHG_INSERTED_EVENT);
433 isl_chg->present = 1;
434 }
435
436 pr_debug("%s OK chg_present=%d\n", __func__, isl_chg->present);
437 return 0;
438
439free_irq:
440 free_irq(client->irq, NULL);
441unregister:
Abhijeet Dharmapurikar9ed4b192011-07-11 12:37:59 -0700442 msm_charger_unregister(&isl_chg->adapter_hw_chg);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700443free_gpio:
444 gpio_free(pdata->valid_n_gpio);
445free_isl_chg:
446 kfree(isl_chg);
447out:
448 return ret;
449}
450
451static int __devexit isl9519q_remove(struct i2c_client *client)
452{
453 struct isl_platform_data *pdata;
454 struct isl9519q_struct *isl_chg = i2c_get_clientdata(client);
455
456 pdata = client->dev.platform_data;
457 gpio_free(pdata->valid_n_gpio);
458 free_irq(client->irq, client);
459 cancel_delayed_work_sync(&isl_chg->charge_work);
460 msm_charger_notify_event(&isl_chg->adapter_hw_chg, CHG_REMOVED_EVENT);
461 msm_charger_unregister(&isl_chg->adapter_hw_chg);
462 return 0;
463}
464
465static const struct i2c_device_id isl9519q_id[] = {
466 {"isl9519q", 0},
467 {},
468};
469
470#ifdef CONFIG_PM
471static int isl9519q_suspend(struct device *dev)
472{
473 struct isl9519q_struct *isl_chg = dev_get_drvdata(dev);
474
475 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
476 /*
477 * do not suspend while we are charging
478 * because we need to periodically update the register
479 * for charging to proceed
480 */
481 if (isl_chg->charging)
482 return -EBUSY;
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700483
484 isl_chg->suspended = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700485 return 0;
486}
487
488static int isl9519q_resume(struct device *dev)
489{
490 struct isl9519q_struct *isl_chg = dev_get_drvdata(dev);
491
492 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700493 isl_chg->suspended = 0;
494 if (isl_chg->charge_at_resume) {
495 isl_chg->charge_at_resume = 0;
496 isl9519q_start_charging(&isl_chg->adapter_hw_chg, 0, 0);
497 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700498 return 0;
499}
500
501static const struct dev_pm_ops isl9519q_pm_ops = {
502 .suspend = isl9519q_suspend,
503 .resume = isl9519q_resume,
504};
505#endif
506
507static struct i2c_driver isl9519q_driver = {
508 .driver = {
509 .name = "isl9519q",
510 .owner = THIS_MODULE,
511#ifdef CONFIG_PM
512 .pm = &isl9519q_pm_ops,
513#endif
514 },
515 .probe = isl9519q_probe,
516 .remove = __devexit_p(isl9519q_remove),
517 .id_table = isl9519q_id,
518};
519
520static int __init isl9519q_init(void)
521{
522 return i2c_add_driver(&isl9519q_driver);
523}
524
525module_init(isl9519q_init);
526
527static void __exit isl9519q_exit(void)
528{
529 return i2c_del_driver(&isl9519q_driver);
530}
531
532module_exit(isl9519q_exit);
533
534MODULE_AUTHOR("Abhijeet Dharmapurikar <adharmap@codeaurora.org>");
535MODULE_DESCRIPTION("Driver for ISL9519Q Charger chip");
536MODULE_LICENSE("GPL v2");