blob: e4729f20c5497994b99c155c1ab66af53b881fe3 [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>
Amir Samuelov43cb1e92011-10-23 15:14:12 +020023#include <linux/mfd/pm8xxx/pm8921-charger.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070024#include <linux/slab.h>
25#include <linux/i2c/isl9519.h>
26#include <linux/msm_adc.h>
Amir Samuelov43cb1e92011-10-23 15:14:12 +020027#include <linux/spinlock.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070028
29#define CHG_CURRENT_REG 0x14
30#define MAX_SYS_VOLTAGE_REG 0x15
31#define CONTROL_REG 0x3D
32#define MIN_SYS_VOLTAGE_REG 0x3E
33#define INPUT_CURRENT_REG 0x3F
34#define MANUFACTURER_ID_REG 0xFE
35#define DEVICE_ID_REG 0xFF
36
37#define TRCKL_CHG_STATUS_BIT 0x80
38
Amir Samuelov43cb1e92011-10-23 15:14:12 +020039#define ISL9519_CHG_PERIOD_SEC 150
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070040
41struct isl9519q_struct {
42 struct i2c_client *client;
43 struct delayed_work charge_work;
44 int present;
45 int batt_present;
46 bool charging;
47 int chgcurrent;
48 int term_current;
49 int input_current;
50 int max_system_voltage;
51 int min_system_voltage;
52 int valid_n_gpio;
53 struct dentry *dent;
54 struct msm_hardware_charger adapter_hw_chg;
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -070055 int suspended;
56 int charge_at_resume;
Amir Samuelov43cb1e92011-10-23 15:14:12 +020057 struct ext_chg_pm8921 ext_chg;
58 spinlock_t lock;
59 bool notify_by_pmic;
60 bool trickle;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070061};
62
David Keitel81d5c8c92011-10-31 16:42:35 -070063static struct isl9519q_struct *the_isl_chg;
64
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070065static int isl9519q_read_reg(struct i2c_client *client, int reg,
66 u16 *val)
67{
68 int ret;
69 struct isl9519q_struct *isl_chg;
70
71 isl_chg = i2c_get_clientdata(client);
72 ret = i2c_smbus_read_word_data(isl_chg->client, reg);
73
74 if (ret < 0) {
75 dev_err(&isl_chg->client->dev,
76 "i2c read fail: can't read from %02x: %d\n", reg, ret);
77 return -EAGAIN;
78 } else
79 *val = ret;
80
Amir Samuelov43cb1e92011-10-23 15:14:12 +020081 pr_debug("%s.reg=0x%x.val=0x%x.\n", __func__, reg, *val);
82
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070083 return 0;
84}
85
86static int isl9519q_write_reg(struct i2c_client *client, int reg,
87 u16 val)
88{
89 int ret;
90 struct isl9519q_struct *isl_chg;
91
Amir Samuelov43cb1e92011-10-23 15:14:12 +020092 pr_debug("%s.reg=0x%x.val=0x%x.\n", __func__, reg, val);
93
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070094 isl_chg = i2c_get_clientdata(client);
95 ret = i2c_smbus_write_word_data(isl_chg->client, reg, val);
96
97 if (ret < 0) {
98 dev_err(&isl_chg->client->dev,
99 "i2c write fail: can't write %02x to %02x: %d\n",
100 val, reg, ret);
101 return -EAGAIN;
102 }
103 return 0;
104}
105
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200106/**
107 * Read charge-current via ADC.
108 *
109 * The ISL CCMON (charge-current-monitor) pin is connected to
110 * the PMIC MPP#X pin.
111 * This not required when notify_by_pmic is used where the PMIC
112 * uses BMS to notify the ISL on charging-done / charge-resume.
113 */
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700114static int isl_read_adc(int channel, int *mv_reading)
115{
116 int ret;
117 void *h;
118 struct adc_chan_result adc_chan_result;
119 struct completion conv_complete_evt;
120
121 pr_debug("%s: called for %d\n", __func__, channel);
122 ret = adc_channel_open(channel, &h);
123 if (ret) {
124 pr_err("%s: couldnt open channel %d ret=%d\n",
125 __func__, channel, ret);
126 goto out;
127 }
128 init_completion(&conv_complete_evt);
129 ret = adc_channel_request_conv(h, &conv_complete_evt);
130 if (ret) {
131 pr_err("%s: couldnt request conv channel %d ret=%d\n",
132 __func__, channel, ret);
133 goto out;
134 }
135 ret = wait_for_completion_interruptible(&conv_complete_evt);
136 if (ret) {
137 pr_err("%s: wait interrupted channel %d ret=%d\n",
138 __func__, channel, ret);
139 goto out;
140 }
141 ret = adc_channel_read_result(h, &adc_chan_result);
142 if (ret) {
143 pr_err("%s: couldnt read result channel %d ret=%d\n",
144 __func__, channel, ret);
145 goto out;
146 }
147 ret = adc_channel_close(h);
148 if (ret)
149 pr_err("%s: couldnt close channel %d ret=%d\n",
150 __func__, channel, ret);
151 if (mv_reading)
152 *mv_reading = (int)adc_chan_result.measurement;
153
154 pr_debug("%s: done for %d\n", __func__, channel);
155 return adc_chan_result.physical;
156out:
157 *mv_reading = 0;
158 pr_debug("%s: done with error for %d\n", __func__, channel);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700159
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200160 return -EINVAL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700161}
162
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200163static bool is_trickle_charging(struct isl9519q_struct *isl_chg)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700164{
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200165 u16 ctrl = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700166 int ret;
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200167
168 ret = isl9519q_read_reg(isl_chg->client, CONTROL_REG, &ctrl);
169
170 if (!ret) {
171 pr_debug("%s.control_reg=0x%x.\n", __func__, ctrl);
172 } else {
173 dev_err(&isl_chg->client->dev,
174 "%s couldnt read cntrl reg\n", __func__);
175 }
176
177 if (ctrl & TRCKL_CHG_STATUS_BIT)
178 return true;
179
180 return false;
181}
182
183static void isl_adapter_check_ichg(struct isl9519q_struct *isl_chg)
184{
185 int ichg; /* isl charger current */
186 int mv_reading = 0;
187
188 ichg = isl_read_adc(CHANNEL_ADC_BATT_AMON, &mv_reading);
189
190 dev_dbg(&isl_chg->client->dev, "%s mv_reading=%d\n",
191 __func__, mv_reading);
192 dev_dbg(&isl_chg->client->dev, "%s isl_charger_current=%d\n",
193 __func__, ichg);
194
195 if (ichg >= 0 && ichg <= isl_chg->term_current)
196 msm_charger_notify_event(&isl_chg->adapter_hw_chg,
197 CHG_DONE_EVENT);
198
199 isl_chg->trickle = is_trickle_charging(isl_chg);
200 if (isl_chg->trickle)
201 msm_charger_notify_event(&isl_chg->adapter_hw_chg,
202 CHG_BATT_BEGIN_FAST_CHARGING);
203}
204
205/**
206 * isl9519q_worker
207 *
208 * Periodic task required to kick the ISL HW watchdog to keep
209 * charging.
210 *
211 * @isl9519_work: work context.
212 */
213static void isl9519q_worker(struct work_struct *isl9519_work)
214{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700215 struct isl9519q_struct *isl_chg;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700216
217 isl_chg = container_of(isl9519_work, struct isl9519q_struct,
218 charge_work.work);
219
220 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
221
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200222 if (!isl_chg->charging) {
223 pr_info("%s.stop charging.\n", __func__);
224 isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, 0);
225 return; /* Stop periodic worker */
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700226 }
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200227
228 /* Kick the dog by writting to CHG_CURRENT_REG */
229 isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG,
230 isl_chg->chgcurrent);
231
232 if (isl_chg->notify_by_pmic)
233 isl_chg->trickle = is_trickle_charging(isl_chg);
234 else
235 isl_adapter_check_ichg(isl_chg);
236
237 schedule_delayed_work(&isl_chg->charge_work,
238 (ISL9519_CHG_PERIOD_SEC * HZ));
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700239}
240
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200241static int isl9519q_start_charging(struct isl9519q_struct *isl_chg,
242 int chg_voltage, int chg_current)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700243{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700244 int ret = 0;
245
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200246 pr_info("%s.\n", __func__);
247
248 if (isl_chg->charging) {
249 pr_warn("%s.already charging.\n", __func__);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700250 return 0;
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200251 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700252
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700253 if (isl_chg->suspended) {
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200254 pr_warn("%s.suspended - can't start charging.\n", __func__);
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700255 isl_chg->charge_at_resume = 1;
256 return 0;
257 }
258
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200259 dev_dbg(&isl_chg->client->dev,
260 "%s starting timed work.period=%d seconds.\n",
261 __func__, (int) ISL9519_CHG_PERIOD_SEC);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700262
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200263 /*
264 * The ISL will start charging from the worker context.
265 * This API might be called from interrupt context.
266 */
267 schedule_delayed_work(&isl_chg->charge_work, 1);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700268
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700269 isl_chg->charging = true;
270
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700271 return ret;
272}
273
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200274static int isl9519q_stop_charging(struct isl9519q_struct *isl_chg)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700275{
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200276 pr_info("%s.\n", __func__);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700277
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200278 if (!(isl_chg->charging)) {
279 pr_warn("%s.already not charging.\n", __func__);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700280 return 0;
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200281 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700282
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700283 if (isl_chg->suspended) {
284 isl_chg->charge_at_resume = 0;
285 return 0;
286 }
287
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700288 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
289
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700290 isl_chg->charging = false;
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200291 isl_chg->trickle = false;
292 /*
293 * The ISL will stop charging from the worker context.
294 * This API might be called from interrupt context.
295 */
296 schedule_delayed_work(&isl_chg->charge_work, 1);
297
298 return 0;
299}
300
301static int isl_ext_start_charging(void *ctx)
302{
303 int rc;
304 struct isl9519q_struct *isl_chg = ctx;
305 unsigned long flags;
306
307 spin_lock_irqsave(&isl_chg->lock, flags);
308 rc = isl9519q_start_charging(isl_chg, 0, isl_chg->chgcurrent);
309 spin_unlock_irqrestore(&isl_chg->lock, flags);
310
311 return rc;
312}
313
314static int isl_ext_stop_charging(void *ctx)
315{
316 int rc;
317 struct isl9519q_struct *isl_chg = ctx;
318 unsigned long flags;
319
320 spin_lock_irqsave(&isl_chg->lock, flags);
321 rc = isl9519q_stop_charging(isl_chg);
322 spin_unlock_irqrestore(&isl_chg->lock, flags);
323
324 return rc;
325}
326
327static bool isl_ext_is_trickle(void *ctx)
328{
329 struct isl9519q_struct *isl_chg = ctx;
330
331 return isl_chg->trickle;
332}
333
334static int isl_adapter_start_charging(struct msm_hardware_charger *hw_chg,
335 int chg_voltage, int chg_current)
336{
337 int rc;
338 struct isl9519q_struct *isl_chg;
339
340 isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg);
341 rc = isl9519q_start_charging(isl_chg, chg_voltage, chg_current);
342
343 return rc;
344}
345
346static int isl_adapter_stop_charging(struct msm_hardware_charger *hw_chg)
347{
348 int rc;
349 struct isl9519q_struct *isl_chg;
350
351 isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg);
352 rc = isl9519q_stop_charging(isl_chg);
353
354 return rc;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700355}
356
357static int isl9519q_charging_switched(struct msm_hardware_charger *hw_chg)
358{
359 struct isl9519q_struct *isl_chg;
360
361 isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg);
362 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
363 return 0;
364}
365
366static irqreturn_t isl_valid_handler(int irq, void *dev_id)
367{
368 int val;
369 struct isl9519q_struct *isl_chg;
370 struct i2c_client *client = dev_id;
371
372 isl_chg = i2c_get_clientdata(client);
373 val = gpio_get_value_cansleep(isl_chg->valid_n_gpio);
374 if (val < 0) {
375 dev_err(&isl_chg->client->dev,
376 "%s gpio_get_value failed for %d ret=%d\n", __func__,
377 isl_chg->valid_n_gpio, val);
378 goto err;
379 }
380 dev_dbg(&isl_chg->client->dev, "%s val=%d\n", __func__, val);
381
382 if (val) {
383 if (isl_chg->present == 1) {
384 msm_charger_notify_event(&isl_chg->adapter_hw_chg,
385 CHG_REMOVED_EVENT);
386 isl_chg->present = 0;
387 }
388 } else {
389 if (isl_chg->present == 0) {
390 msm_charger_notify_event(&isl_chg->adapter_hw_chg,
391 CHG_INSERTED_EVENT);
392 isl_chg->present = 1;
393 }
394 }
395err:
396 return IRQ_HANDLED;
397}
398
399#define MAX_VOLTAGE_REG_MASK 0x3FF0
400#define MIN_VOLTAGE_REG_MASK 0x3F00
401#define DEFAULT_MAX_VOLTAGE_REG_VALUE 0x1070
402#define DEFAULT_MIN_VOLTAGE_REG_VALUE 0x0D00
403
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200404static int __devinit isl9519q_init_adapter(struct isl9519q_struct *isl_chg)
405{
406 int ret;
407 struct isl_platform_data *pdata = isl_chg->client->dev.platform_data;
408 struct i2c_client *client = isl_chg->client;
409
410 isl_chg->adapter_hw_chg.type = CHG_TYPE_AC;
411 isl_chg->adapter_hw_chg.rating = 2;
412 isl_chg->adapter_hw_chg.name = "isl-adapter";
413 isl_chg->adapter_hw_chg.start_charging = isl_adapter_start_charging;
414 isl_chg->adapter_hw_chg.stop_charging = isl_adapter_stop_charging;
415 isl_chg->adapter_hw_chg.charging_switched = isl9519q_charging_switched;
416
417 ret = gpio_request(pdata->valid_n_gpio, "isl_charger_valid");
418 if (ret) {
419 dev_err(&client->dev, "%s gpio_request failed "
420 "for %d ret=%d\n",
421 __func__, pdata->valid_n_gpio, ret);
422 goto out;
423 }
424
425 ret = msm_charger_register(&isl_chg->adapter_hw_chg);
426 if (ret) {
427 dev_err(&client->dev,
428 "%s msm_charger_register failed for ret =%d\n",
429 __func__, ret);
430 goto free_gpio;
431 }
432
433 ret = request_threaded_irq(client->irq, NULL,
434 isl_valid_handler,
435 IRQF_TRIGGER_FALLING |
436 IRQF_TRIGGER_RISING,
437 "isl_charger_valid", client);
438 if (ret) {
439 dev_err(&client->dev,
440 "%s request_threaded_irq failed "
441 "for %d ret =%d\n",
442 __func__, client->irq, ret);
443 goto unregister;
444 }
445 irq_set_irq_wake(client->irq, 1);
446
447 ret = gpio_get_value_cansleep(isl_chg->valid_n_gpio);
448 if (ret < 0) {
449 dev_err(&client->dev,
450 "%s gpio_get_value failed for %d ret=%d\n",
451 __func__, pdata->valid_n_gpio, ret);
452 /* assume absent */
453 ret = 1;
454 }
455 if (!ret) {
456 msm_charger_notify_event(&isl_chg->adapter_hw_chg,
457 CHG_INSERTED_EVENT);
458 isl_chg->present = 1;
459 }
460
461 return 0;
462
463unregister:
464 msm_charger_unregister(&isl_chg->adapter_hw_chg);
465free_gpio:
466 gpio_free(pdata->valid_n_gpio);
467out:
468 return ret;
469
470}
471
472static int __devinit isl9519q_init_ext_chg(struct isl9519q_struct *isl_chg)
473{
474 int ret;
475
476 isl_chg->ext_chg.name = "isl9519q";
477 isl_chg->ext_chg.ctx = isl_chg;
478 isl_chg->ext_chg.start_charging = isl_ext_start_charging;
479 isl_chg->ext_chg.stop_charging = isl_ext_stop_charging;
480 isl_chg->ext_chg.is_trickle = isl_ext_is_trickle;
481 ret = register_external_dc_charger(&isl_chg->ext_chg);
482 if (ret) {
483 pr_err("%s.failed to register external dc charger.ret=%d.\n",
484 __func__, ret);
485 return ret;
486 }
487
488 return 0;
489}
David Keitel81d5c8c92011-10-31 16:42:35 -0700490static int set_reg(void *data, u64 val)
491{
492 int addr = (int)data;
493 int ret;
494 u16 temp;
495
496 temp = (u16) val;
497 ret = isl9519q_write_reg(the_isl_chg->client, addr, temp);
498
499 if (ret) {
500 pr_err("isl9519q_write_reg to %x value =%d errored = %d\n",
501 addr, temp, ret);
502 return -EAGAIN;
503 }
504 return 0;
505}
506static int get_reg(void *data, u64 *val)
507{
508 int addr = (int)data;
509 int ret;
510 u16 temp;
511
512 ret = isl9519q_read_reg(the_isl_chg->client, addr, &temp);
513 if (ret) {
514 pr_err("isl9519q_read_reg to %x value =%d errored = %d\n",
515 addr, temp, ret);
516 return -EAGAIN;
517 }
518
519 *val = temp;
520 return 0;
521}
522
523DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n");
524
525static void create_debugfs_entries(struct isl9519q_struct *isl_chg)
526{
527 isl_chg->dent = debugfs_create_dir("isl9519q", NULL);
528
529 if (IS_ERR(isl_chg->dent)) {
530 pr_err("isl9519q driver couldn't create debugfs dir\n");
531 return;
532 }
533
534 debugfs_create_file("CHG_CURRENT_REG", 0644, isl_chg->dent,
535 (void *) CHG_CURRENT_REG, &reg_fops);
536 debugfs_create_file("MAX_SYS_VOLTAGE_REG", 0644, isl_chg->dent,
537 (void *) MAX_SYS_VOLTAGE_REG, &reg_fops);
538 debugfs_create_file("CONTROL_REG", 0644, isl_chg->dent,
539 (void *) CONTROL_REG, &reg_fops);
540 debugfs_create_file("MIN_SYS_VOLTAGE_REG", 0644, isl_chg->dent,
541 (void *) MIN_SYS_VOLTAGE_REG, &reg_fops);
542 debugfs_create_file("INPUT_CURRENT_REG", 0644, isl_chg->dent,
543 (void *) INPUT_CURRENT_REG, &reg_fops);
544 debugfs_create_file("MANUFACTURER_ID_REG", 0644, isl_chg->dent,
545 (void *) MANUFACTURER_ID_REG, &reg_fops);
546 debugfs_create_file("DEVICE_ID_REG", 0644, isl_chg->dent,
547 (void *) DEVICE_ID_REG, &reg_fops);
548}
549
550static void remove_debugfs_entries(struct isl9519q_struct *isl_chg)
551{
552 if (isl_chg->dent)
553 debugfs_remove_recursive(isl_chg->dent);
554}
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200555
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700556static int __devinit isl9519q_probe(struct i2c_client *client,
557 const struct i2c_device_id *id)
558{
559 struct isl_platform_data *pdata;
560 struct isl9519q_struct *isl_chg;
561 int ret;
562
563 ret = 0;
564 pdata = client->dev.platform_data;
565
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200566 pr_info("%s.\n", __func__);
567
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700568 if (pdata == NULL) {
569 dev_err(&client->dev, "%s no platform data\n", __func__);
570 ret = -EINVAL;
571 goto out;
572 }
573
574 if (!i2c_check_functionality(client->adapter,
575 I2C_FUNC_SMBUS_WORD_DATA)) {
576 ret = -EIO;
577 goto out;
578 }
579
580 isl_chg = kzalloc(sizeof(*isl_chg), GFP_KERNEL);
581 if (!isl_chg) {
582 ret = -ENOMEM;
583 goto out;
584 }
585
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200586 spin_lock_init(&isl_chg->lock);
587
588 INIT_DELAYED_WORK(&isl_chg->charge_work, isl9519q_worker);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700589 isl_chg->client = client;
590 isl_chg->chgcurrent = pdata->chgcurrent;
591 isl_chg->term_current = pdata->term_current;
592 isl_chg->input_current = pdata->input_current;
593 isl_chg->max_system_voltage = pdata->max_system_voltage;
594 isl_chg->min_system_voltage = pdata->min_system_voltage;
595 isl_chg->valid_n_gpio = pdata->valid_n_gpio;
596
597 /* h/w ignores lower 7 bits of charging current and input current */
598 isl_chg->chgcurrent &= ~0x7F;
599 isl_chg->input_current &= ~0x7F;
600
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200601 /**
602 * ISL is Notified by PMIC to start/stop charging, rather than
603 * handling interrupt from ISL for End-Of-Chargring, and
604 * monitoring the charge-current periodically. The valid_n_gpio
605 * is also not used, dc-present is detected by PMIC.
606 */
607 isl_chg->notify_by_pmic = (client->irq == 0);
608 i2c_set_clientdata(client, isl_chg);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700609
610 if (pdata->chg_detection_config) {
611 ret = pdata->chg_detection_config();
612 if (ret) {
613 dev_err(&client->dev, "%s valid config failed ret=%d\n",
614 __func__, ret);
615 goto free_isl_chg;
616 }
617 }
618
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700619 isl_chg->max_system_voltage &= MAX_VOLTAGE_REG_MASK;
620 isl_chg->min_system_voltage &= MIN_VOLTAGE_REG_MASK;
621 if (isl_chg->max_system_voltage == 0)
622 isl_chg->max_system_voltage = DEFAULT_MAX_VOLTAGE_REG_VALUE;
623 if (isl_chg->min_system_voltage == 0)
624 isl_chg->min_system_voltage = DEFAULT_MIN_VOLTAGE_REG_VALUE;
625
626 ret = isl9519q_write_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG,
627 isl_chg->max_system_voltage);
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200628 if (ret)
629 goto free_isl_chg;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700630
631 ret = isl9519q_write_reg(isl_chg->client, MIN_SYS_VOLTAGE_REG,
632 isl_chg->min_system_voltage);
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200633 if (ret)
634 goto free_isl_chg;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700635
636 if (isl_chg->input_current) {
637 ret = isl9519q_write_reg(isl_chg->client,
638 INPUT_CURRENT_REG,
639 isl_chg->input_current);
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200640 if (ret)
641 goto free_isl_chg;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700642 }
643
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200644 if (isl_chg->notify_by_pmic)
645 ret = isl9519q_init_ext_chg(isl_chg);
646 else
647 ret = isl9519q_init_adapter(isl_chg);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700648
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200649 if (ret)
650 goto free_isl_chg;
651
David Keitel81d5c8c92011-10-31 16:42:35 -0700652 the_isl_chg = isl_chg;
653 create_debugfs_entries(isl_chg);
654
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200655 pr_info("%s OK.\n", __func__);
656
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700657 return 0;
658
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700659free_isl_chg:
660 kfree(isl_chg);
661out:
662 return ret;
663}
664
665static int __devexit isl9519q_remove(struct i2c_client *client)
666{
667 struct isl_platform_data *pdata;
668 struct isl9519q_struct *isl_chg = i2c_get_clientdata(client);
669
670 pdata = client->dev.platform_data;
671 gpio_free(pdata->valid_n_gpio);
672 free_irq(client->irq, client);
673 cancel_delayed_work_sync(&isl_chg->charge_work);
674 msm_charger_notify_event(&isl_chg->adapter_hw_chg, CHG_REMOVED_EVENT);
675 msm_charger_unregister(&isl_chg->adapter_hw_chg);
David Keitel81d5c8c92011-10-31 16:42:35 -0700676 remove_debugfs_entries(isl_chg);
677 the_isl_chg = NULL;
678 kfree(isl_chg);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700679 return 0;
680}
681
682static const struct i2c_device_id isl9519q_id[] = {
683 {"isl9519q", 0},
684 {},
685};
686
687#ifdef CONFIG_PM
688static int isl9519q_suspend(struct device *dev)
689{
690 struct isl9519q_struct *isl_chg = dev_get_drvdata(dev);
691
692 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
693 /*
694 * do not suspend while we are charging
695 * because we need to periodically update the register
696 * for charging to proceed
697 */
698 if (isl_chg->charging)
699 return -EBUSY;
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700700
701 isl_chg->suspended = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700702 return 0;
703}
704
705static int isl9519q_resume(struct device *dev)
706{
707 struct isl9519q_struct *isl_chg = dev_get_drvdata(dev);
708
709 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700710 isl_chg->suspended = 0;
711 if (isl_chg->charge_at_resume) {
712 isl_chg->charge_at_resume = 0;
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200713 isl9519q_start_charging(isl_chg, 0, 0);
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700714 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700715 return 0;
716}
717
718static const struct dev_pm_ops isl9519q_pm_ops = {
719 .suspend = isl9519q_suspend,
720 .resume = isl9519q_resume,
721};
722#endif
723
724static struct i2c_driver isl9519q_driver = {
725 .driver = {
726 .name = "isl9519q",
727 .owner = THIS_MODULE,
728#ifdef CONFIG_PM
729 .pm = &isl9519q_pm_ops,
730#endif
731 },
732 .probe = isl9519q_probe,
733 .remove = __devexit_p(isl9519q_remove),
734 .id_table = isl9519q_id,
735};
736
737static int __init isl9519q_init(void)
738{
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200739 pr_info("%s. isl9519q SW rev 1.01\n", __func__);
740
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700741 return i2c_add_driver(&isl9519q_driver);
742}
743
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200744late_initcall_sync(isl9519q_init);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700745
746static void __exit isl9519q_exit(void)
747{
748 return i2c_del_driver(&isl9519q_driver);
749}
750
751module_exit(isl9519q_exit);
752
753MODULE_AUTHOR("Abhijeet Dharmapurikar <adharmap@codeaurora.org>");
754MODULE_DESCRIPTION("Driver for ISL9519Q Charger chip");
755MODULE_LICENSE("GPL v2");