blob: da5a0bdcdb8dc454af84df80cf7080a43705d34c [file] [log] [blame]
Duy Truong790f06d2013-02-13 16:38:12 -08001/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002 *
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 */
David Keitel88e1b572012-01-11 11:57:14 -080013#define pr_fmt(fmt) "%s: " fmt, __func__
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070014
15#include <linux/i2c.h>
16#include <linux/gpio.h>
17#include <linux/errno.h>
18#include <linux/delay.h>
19#include <linux/module.h>
20#include <linux/debugfs.h>
21#include <linux/workqueue.h>
22#include <linux/interrupt.h>
23#include <linux/msm-charger.h>
David Keitel88e1b572012-01-11 11:57:14 -080024#include <linux/power_supply.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070025#include <linux/slab.h>
26#include <linux/i2c/isl9519.h>
27#include <linux/msm_adc.h>
Amir Samuelov43cb1e92011-10-23 15:14:12 +020028#include <linux/spinlock.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070029
30#define CHG_CURRENT_REG 0x14
31#define MAX_SYS_VOLTAGE_REG 0x15
32#define CONTROL_REG 0x3D
33#define MIN_SYS_VOLTAGE_REG 0x3E
34#define INPUT_CURRENT_REG 0x3F
35#define MANUFACTURER_ID_REG 0xFE
36#define DEVICE_ID_REG 0xFF
37
38#define TRCKL_CHG_STATUS_BIT 0x80
39
Amir Samuelov43cb1e92011-10-23 15:14:12 +020040#define ISL9519_CHG_PERIOD_SEC 150
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070041
42struct isl9519q_struct {
43 struct i2c_client *client;
44 struct delayed_work charge_work;
45 int present;
46 int batt_present;
47 bool charging;
48 int chgcurrent;
49 int term_current;
50 int input_current;
51 int max_system_voltage;
52 int min_system_voltage;
53 int valid_n_gpio;
54 struct dentry *dent;
55 struct msm_hardware_charger adapter_hw_chg;
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -070056 int suspended;
57 int charge_at_resume;
David Keitel88e1b572012-01-11 11:57:14 -080058 struct power_supply dc_psy;
Amir Samuelov43cb1e92011-10-23 15:14:12 +020059 spinlock_t lock;
60 bool notify_by_pmic;
61 bool trickle;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070062};
63
David Keitel81d5c8c92011-10-31 16:42:35 -070064static struct isl9519q_struct *the_isl_chg;
65
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070066static int isl9519q_read_reg(struct i2c_client *client, int reg,
67 u16 *val)
68{
69 int ret;
70 struct isl9519q_struct *isl_chg;
71
72 isl_chg = i2c_get_clientdata(client);
73 ret = i2c_smbus_read_word_data(isl_chg->client, reg);
74
75 if (ret < 0) {
76 dev_err(&isl_chg->client->dev,
77 "i2c read fail: can't read from %02x: %d\n", reg, ret);
78 return -EAGAIN;
David Keitel88e1b572012-01-11 11:57:14 -080079 } else {
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070080 *val = ret;
David Keitel88e1b572012-01-11 11:57:14 -080081 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070082
David Keitel88e1b572012-01-11 11:57:14 -080083 pr_debug("reg=0x%x.val=0x%x.\n", reg, *val);
Amir Samuelov43cb1e92011-10-23 15:14:12 +020084
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070085 return 0;
86}
87
88static int isl9519q_write_reg(struct i2c_client *client, int reg,
89 u16 val)
90{
91 int ret;
92 struct isl9519q_struct *isl_chg;
93
David Keitel88e1b572012-01-11 11:57:14 -080094 pr_debug("reg=0x%x.val=0x%x.\n", reg, val);
Amir Samuelov43cb1e92011-10-23 15:14:12 +020095
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070096 isl_chg = i2c_get_clientdata(client);
97 ret = i2c_smbus_write_word_data(isl_chg->client, reg, val);
98
99 if (ret < 0) {
100 dev_err(&isl_chg->client->dev,
101 "i2c write fail: can't write %02x to %02x: %d\n",
102 val, reg, ret);
103 return -EAGAIN;
104 }
105 return 0;
106}
107
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200108/**
109 * Read charge-current via ADC.
110 *
111 * The ISL CCMON (charge-current-monitor) pin is connected to
112 * the PMIC MPP#X pin.
113 * This not required when notify_by_pmic is used where the PMIC
114 * uses BMS to notify the ISL on charging-done / charge-resume.
115 */
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700116static int isl_read_adc(int channel, int *mv_reading)
117{
118 int ret;
119 void *h;
120 struct adc_chan_result adc_chan_result;
121 struct completion conv_complete_evt;
122
David Keitel88e1b572012-01-11 11:57:14 -0800123 pr_debug("called for %d\n", channel);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700124 ret = adc_channel_open(channel, &h);
125 if (ret) {
David Keitel88e1b572012-01-11 11:57:14 -0800126 pr_err("couldnt open channel %d ret=%d\n", channel, ret);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700127 goto out;
128 }
129 init_completion(&conv_complete_evt);
130 ret = adc_channel_request_conv(h, &conv_complete_evt);
131 if (ret) {
David Keitel88e1b572012-01-11 11:57:14 -0800132 pr_err("couldnt request conv channel %d ret=%d\n",
133 channel, ret);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700134 goto out;
135 }
136 ret = wait_for_completion_interruptible(&conv_complete_evt);
137 if (ret) {
David Keitel88e1b572012-01-11 11:57:14 -0800138 pr_err("wait interrupted channel %d ret=%d\n", channel, ret);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700139 goto out;
140 }
141 ret = adc_channel_read_result(h, &adc_chan_result);
142 if (ret) {
David Keitel88e1b572012-01-11 11:57:14 -0800143 pr_err("couldnt read result channel %d ret=%d\n",
144 channel, ret);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700145 goto out;
146 }
147 ret = adc_channel_close(h);
148 if (ret)
David Keitel88e1b572012-01-11 11:57:14 -0800149 pr_err("couldnt close channel %d ret=%d\n", channel, ret);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700150 if (mv_reading)
151 *mv_reading = (int)adc_chan_result.measurement;
152
David Keitel88e1b572012-01-11 11:57:14 -0800153 pr_debug("done for %d\n", channel);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700154 return adc_chan_result.physical;
155out:
156 *mv_reading = 0;
David Keitel88e1b572012-01-11 11:57:14 -0800157 pr_debug("done with error for %d\n", channel);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700158
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200159 return -EINVAL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700160}
161
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200162static bool is_trickle_charging(struct isl9519q_struct *isl_chg)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700163{
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200164 u16 ctrl = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700165 int ret;
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200166
167 ret = isl9519q_read_reg(isl_chg->client, CONTROL_REG, &ctrl);
168
169 if (!ret) {
David Keitel88e1b572012-01-11 11:57:14 -0800170 pr_debug("control_reg=0x%x.\n", ctrl);
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200171 } else {
172 dev_err(&isl_chg->client->dev,
173 "%s couldnt read cntrl reg\n", __func__);
174 }
175
176 if (ctrl & TRCKL_CHG_STATUS_BIT)
177 return true;
178
179 return false;
180}
181
182static void isl_adapter_check_ichg(struct isl9519q_struct *isl_chg)
183{
184 int ichg; /* isl charger current */
185 int mv_reading = 0;
186
187 ichg = isl_read_adc(CHANNEL_ADC_BATT_AMON, &mv_reading);
188
189 dev_dbg(&isl_chg->client->dev, "%s mv_reading=%d\n",
190 __func__, mv_reading);
191 dev_dbg(&isl_chg->client->dev, "%s isl_charger_current=%d\n",
192 __func__, ichg);
193
194 if (ichg >= 0 && ichg <= isl_chg->term_current)
195 msm_charger_notify_event(&isl_chg->adapter_hw_chg,
196 CHG_DONE_EVENT);
197
198 isl_chg->trickle = is_trickle_charging(isl_chg);
199 if (isl_chg->trickle)
200 msm_charger_notify_event(&isl_chg->adapter_hw_chg,
201 CHG_BATT_BEGIN_FAST_CHARGING);
202}
203
204/**
205 * isl9519q_worker
206 *
207 * Periodic task required to kick the ISL HW watchdog to keep
208 * charging.
209 *
210 * @isl9519_work: work context.
211 */
212static void isl9519q_worker(struct work_struct *isl9519_work)
213{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700214 struct isl9519q_struct *isl_chg;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700215
216 isl_chg = container_of(isl9519_work, struct isl9519q_struct,
217 charge_work.work);
218
219 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
220
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200221 if (!isl_chg->charging) {
David Keitel88e1b572012-01-11 11:57:14 -0800222 pr_debug("stop charging.\n");
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200223 isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, 0);
224 return; /* Stop periodic worker */
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700225 }
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200226
227 /* Kick the dog by writting to CHG_CURRENT_REG */
228 isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG,
229 isl_chg->chgcurrent);
230
231 if (isl_chg->notify_by_pmic)
232 isl_chg->trickle = is_trickle_charging(isl_chg);
233 else
234 isl_adapter_check_ichg(isl_chg);
235
236 schedule_delayed_work(&isl_chg->charge_work,
237 (ISL9519_CHG_PERIOD_SEC * HZ));
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700238}
239
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200240static int isl9519q_start_charging(struct isl9519q_struct *isl_chg,
241 int chg_voltage, int chg_current)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700242{
David Keitel88e1b572012-01-11 11:57:14 -0800243 pr_debug("\n");
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200244
245 if (isl_chg->charging) {
David Keitel88e1b572012-01-11 11:57:14 -0800246 pr_warn("already charging.\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700247 return 0;
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200248 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700249
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700250 if (isl_chg->suspended) {
David Keitel88e1b572012-01-11 11:57:14 -0800251 pr_warn("suspended - can't start charging.\n");
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700252 isl_chg->charge_at_resume = 1;
253 return 0;
254 }
255
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200256 dev_dbg(&isl_chg->client->dev,
257 "%s starting timed work.period=%d seconds.\n",
258 __func__, (int) ISL9519_CHG_PERIOD_SEC);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700259
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200260 /*
261 * The ISL will start charging from the worker context.
262 * This API might be called from interrupt context.
263 */
264 schedule_delayed_work(&isl_chg->charge_work, 1);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700265
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700266 isl_chg->charging = true;
267
David Keitel88e1b572012-01-11 11:57:14 -0800268 return 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700269}
270
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200271static int isl9519q_stop_charging(struct isl9519q_struct *isl_chg)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700272{
David Keitel88e1b572012-01-11 11:57:14 -0800273 pr_debug("\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700274
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200275 if (!(isl_chg->charging)) {
David Keitel88e1b572012-01-11 11:57:14 -0800276 pr_warn("already not charging.\n");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700277 return 0;
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200278 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700279
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700280 if (isl_chg->suspended) {
281 isl_chg->charge_at_resume = 0;
282 return 0;
283 }
284
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700285 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
286
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700287 isl_chg->charging = false;
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200288 isl_chg->trickle = false;
289 /*
290 * The ISL will stop charging from the worker context.
291 * This API might be called from interrupt context.
292 */
293 schedule_delayed_work(&isl_chg->charge_work, 1);
294
295 return 0;
296}
297
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200298static int isl_adapter_start_charging(struct msm_hardware_charger *hw_chg,
299 int chg_voltage, int chg_current)
300{
301 int rc;
302 struct isl9519q_struct *isl_chg;
303
304 isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg);
305 rc = isl9519q_start_charging(isl_chg, chg_voltage, chg_current);
306
307 return rc;
308}
309
310static int isl_adapter_stop_charging(struct msm_hardware_charger *hw_chg)
311{
312 int rc;
313 struct isl9519q_struct *isl_chg;
314
315 isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg);
316 rc = isl9519q_stop_charging(isl_chg);
317
318 return rc;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700319}
320
321static int isl9519q_charging_switched(struct msm_hardware_charger *hw_chg)
322{
323 struct isl9519q_struct *isl_chg;
324
325 isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg);
326 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
327 return 0;
328}
329
330static irqreturn_t isl_valid_handler(int irq, void *dev_id)
331{
332 int val;
333 struct isl9519q_struct *isl_chg;
334 struct i2c_client *client = dev_id;
335
336 isl_chg = i2c_get_clientdata(client);
337 val = gpio_get_value_cansleep(isl_chg->valid_n_gpio);
338 if (val < 0) {
339 dev_err(&isl_chg->client->dev,
340 "%s gpio_get_value failed for %d ret=%d\n", __func__,
341 isl_chg->valid_n_gpio, val);
342 goto err;
343 }
344 dev_dbg(&isl_chg->client->dev, "%s val=%d\n", __func__, val);
345
346 if (val) {
347 if (isl_chg->present == 1) {
348 msm_charger_notify_event(&isl_chg->adapter_hw_chg,
349 CHG_REMOVED_EVENT);
350 isl_chg->present = 0;
351 }
352 } else {
353 if (isl_chg->present == 0) {
354 msm_charger_notify_event(&isl_chg->adapter_hw_chg,
355 CHG_INSERTED_EVENT);
356 isl_chg->present = 1;
357 }
358 }
359err:
360 return IRQ_HANDLED;
361}
362
David Keitel88e1b572012-01-11 11:57:14 -0800363static enum power_supply_property pm_power_props[] = {
364 POWER_SUPPLY_PROP_ONLINE,
365 POWER_SUPPLY_PROP_CURRENT_MAX,
366 POWER_SUPPLY_PROP_CHARGE_TYPE,
367};
368
369static char *pm_power_supplied_to[] = {
370 "battery",
371};
372
373static int get_prop_charge_type(struct isl9519q_struct *isl_chg)
374{
375 if (!isl_chg->present)
376 return POWER_SUPPLY_CHARGE_TYPE_NONE;
377
378 if (isl_chg->trickle)
379 return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
380
381 if (isl_chg->charging)
382 return POWER_SUPPLY_CHARGE_TYPE_FAST;
383
384 return POWER_SUPPLY_CHARGE_TYPE_NONE;
385}
386static int pm_power_get_property(struct power_supply *psy,
387 enum power_supply_property psp,
388 union power_supply_propval *val)
389{
390 struct isl9519q_struct *isl_chg = container_of(psy,
391 struct isl9519q_struct,
392 dc_psy);
393
394 switch (psp) {
395 case POWER_SUPPLY_PROP_CURRENT_MAX:
396 val->intval = isl_chg->chgcurrent;
397 break;
398 case POWER_SUPPLY_PROP_ONLINE:
399 val->intval = (int)isl_chg->present;
400 break;
401 case POWER_SUPPLY_PROP_CHARGE_TYPE:
402 val->intval = get_prop_charge_type(isl_chg);
403 break;
404 default:
405 return -EINVAL;
406 }
407 return 0;
408}
409
410static int pm_power_set_property(struct power_supply *psy,
411 enum power_supply_property psp,
412 const union power_supply_propval *val)
413{
414 struct isl9519q_struct *isl_chg = container_of(psy,
415 struct isl9519q_struct,
416 dc_psy);
417 unsigned long flags;
418 int rc;
419
420 switch (psp) {
421 case POWER_SUPPLY_PROP_ONLINE:
422 if (val->intval) {
423 isl_chg->present = val->intval;
424 } else {
425 isl_chg->present = 0;
426 if (isl_chg->charging)
427 goto stop_charging;
428 }
429 break;
430 case POWER_SUPPLY_PROP_CURRENT_MAX:
431 if (val->intval) {
432 if (isl_chg->chgcurrent != val->intval)
433 return -EINVAL;
434 }
435 break;
436 case POWER_SUPPLY_PROP_CHARGE_TYPE:
437 if (val->intval && isl_chg->present) {
438 if (val->intval == POWER_SUPPLY_CHARGE_TYPE_FAST)
439 goto start_charging;
440 if (val->intval == POWER_SUPPLY_CHARGE_TYPE_NONE)
441 goto stop_charging;
442 } else {
443 return -EINVAL;
444 }
445 break;
446 default:
447 return -EINVAL;
448 }
449 power_supply_changed(&isl_chg->dc_psy);
450 return 0;
451
452start_charging:
453 spin_lock_irqsave(&isl_chg->lock, flags);
454 rc = isl9519q_start_charging(isl_chg, 0, isl_chg->chgcurrent);
455 if (rc)
456 pr_err("Failed to start charging rc=%d\n", rc);
457 spin_unlock_irqrestore(&isl_chg->lock, flags);
458 power_supply_changed(&isl_chg->dc_psy);
459 return rc;
460
461stop_charging:
462 spin_lock_irqsave(&isl_chg->lock, flags);
463 rc = isl9519q_stop_charging(isl_chg);
464 if (rc)
465 pr_err("Failed to start charging rc=%d\n", rc);
466 spin_unlock_irqrestore(&isl_chg->lock, flags);
467 power_supply_changed(&isl_chg->dc_psy);
468 return rc;
469}
470
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700471#define MAX_VOLTAGE_REG_MASK 0x3FF0
472#define MIN_VOLTAGE_REG_MASK 0x3F00
473#define DEFAULT_MAX_VOLTAGE_REG_VALUE 0x1070
474#define DEFAULT_MIN_VOLTAGE_REG_VALUE 0x0D00
475
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200476static int __devinit isl9519q_init_adapter(struct isl9519q_struct *isl_chg)
477{
478 int ret;
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200479 struct i2c_client *client = isl_chg->client;
David Keitel88e1b572012-01-11 11:57:14 -0800480 struct isl_platform_data *pdata = client->dev.platform_data;
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200481
482 isl_chg->adapter_hw_chg.type = CHG_TYPE_AC;
483 isl_chg->adapter_hw_chg.rating = 2;
484 isl_chg->adapter_hw_chg.name = "isl-adapter";
485 isl_chg->adapter_hw_chg.start_charging = isl_adapter_start_charging;
486 isl_chg->adapter_hw_chg.stop_charging = isl_adapter_stop_charging;
487 isl_chg->adapter_hw_chg.charging_switched = isl9519q_charging_switched;
488
489 ret = gpio_request(pdata->valid_n_gpio, "isl_charger_valid");
490 if (ret) {
491 dev_err(&client->dev, "%s gpio_request failed "
492 "for %d ret=%d\n",
493 __func__, pdata->valid_n_gpio, ret);
494 goto out;
495 }
496
497 ret = msm_charger_register(&isl_chg->adapter_hw_chg);
498 if (ret) {
499 dev_err(&client->dev,
500 "%s msm_charger_register failed for ret =%d\n",
501 __func__, ret);
502 goto free_gpio;
503 }
504
505 ret = request_threaded_irq(client->irq, NULL,
506 isl_valid_handler,
507 IRQF_TRIGGER_FALLING |
508 IRQF_TRIGGER_RISING,
509 "isl_charger_valid", client);
510 if (ret) {
511 dev_err(&client->dev,
512 "%s request_threaded_irq failed "
513 "for %d ret =%d\n",
514 __func__, client->irq, ret);
515 goto unregister;
516 }
517 irq_set_irq_wake(client->irq, 1);
518
519 ret = gpio_get_value_cansleep(isl_chg->valid_n_gpio);
520 if (ret < 0) {
521 dev_err(&client->dev,
522 "%s gpio_get_value failed for %d ret=%d\n",
523 __func__, pdata->valid_n_gpio, ret);
524 /* assume absent */
525 ret = 1;
526 }
527 if (!ret) {
528 msm_charger_notify_event(&isl_chg->adapter_hw_chg,
529 CHG_INSERTED_EVENT);
530 isl_chg->present = 1;
531 }
532
533 return 0;
534
535unregister:
536 msm_charger_unregister(&isl_chg->adapter_hw_chg);
537free_gpio:
538 gpio_free(pdata->valid_n_gpio);
539out:
540 return ret;
541
542}
543
544static int __devinit isl9519q_init_ext_chg(struct isl9519q_struct *isl_chg)
545{
546 int ret;
547
David Keitel88e1b572012-01-11 11:57:14 -0800548 isl_chg->dc_psy.name = "dc";
549 isl_chg->dc_psy.type = POWER_SUPPLY_TYPE_MAINS;
550 isl_chg->dc_psy.supplied_to = pm_power_supplied_to;
551 isl_chg->dc_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to);
552 isl_chg->dc_psy.properties = pm_power_props;
553 isl_chg->dc_psy.num_properties = ARRAY_SIZE(pm_power_props);
554 isl_chg->dc_psy.get_property = pm_power_get_property;
555 isl_chg->dc_psy.set_property = pm_power_set_property;
556
557 ret = power_supply_register(&isl_chg->client->dev, &isl_chg->dc_psy);
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200558 if (ret) {
David Keitel88e1b572012-01-11 11:57:14 -0800559 pr_err("failed to register dc charger.ret=%d.\n", ret);
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200560 return ret;
561 }
562
563 return 0;
564}
David Keitel81d5c8c92011-10-31 16:42:35 -0700565static int set_reg(void *data, u64 val)
566{
567 int addr = (int)data;
568 int ret;
569 u16 temp;
570
571 temp = (u16) val;
572 ret = isl9519q_write_reg(the_isl_chg->client, addr, temp);
573
574 if (ret) {
575 pr_err("isl9519q_write_reg to %x value =%d errored = %d\n",
576 addr, temp, ret);
577 return -EAGAIN;
578 }
579 return 0;
580}
581static int get_reg(void *data, u64 *val)
582{
583 int addr = (int)data;
584 int ret;
585 u16 temp;
586
587 ret = isl9519q_read_reg(the_isl_chg->client, addr, &temp);
588 if (ret) {
589 pr_err("isl9519q_read_reg to %x value =%d errored = %d\n",
590 addr, temp, ret);
591 return -EAGAIN;
592 }
593
594 *val = temp;
595 return 0;
596}
597
598DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n");
599
600static void create_debugfs_entries(struct isl9519q_struct *isl_chg)
601{
602 isl_chg->dent = debugfs_create_dir("isl9519q", NULL);
603
604 if (IS_ERR(isl_chg->dent)) {
605 pr_err("isl9519q driver couldn't create debugfs dir\n");
606 return;
607 }
608
609 debugfs_create_file("CHG_CURRENT_REG", 0644, isl_chg->dent,
610 (void *) CHG_CURRENT_REG, &reg_fops);
611 debugfs_create_file("MAX_SYS_VOLTAGE_REG", 0644, isl_chg->dent,
612 (void *) MAX_SYS_VOLTAGE_REG, &reg_fops);
613 debugfs_create_file("CONTROL_REG", 0644, isl_chg->dent,
614 (void *) CONTROL_REG, &reg_fops);
615 debugfs_create_file("MIN_SYS_VOLTAGE_REG", 0644, isl_chg->dent,
616 (void *) MIN_SYS_VOLTAGE_REG, &reg_fops);
617 debugfs_create_file("INPUT_CURRENT_REG", 0644, isl_chg->dent,
618 (void *) INPUT_CURRENT_REG, &reg_fops);
619 debugfs_create_file("MANUFACTURER_ID_REG", 0644, isl_chg->dent,
620 (void *) MANUFACTURER_ID_REG, &reg_fops);
621 debugfs_create_file("DEVICE_ID_REG", 0644, isl_chg->dent,
622 (void *) DEVICE_ID_REG, &reg_fops);
623}
624
625static void remove_debugfs_entries(struct isl9519q_struct *isl_chg)
626{
627 if (isl_chg->dent)
628 debugfs_remove_recursive(isl_chg->dent);
629}
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200630
David Keitel88e1b572012-01-11 11:57:14 -0800631static int __devinit isl9519q_hwinit(struct isl9519q_struct *isl_chg)
632{
633 int ret;
634
635 ret = isl9519q_write_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG,
636 isl_chg->max_system_voltage);
637 if (ret) {
638 pr_err("Failed to set MAX_SYS_VOLTAGE rc=%d\n", ret);
639 return ret;
640 }
641
642 ret = isl9519q_write_reg(isl_chg->client, MIN_SYS_VOLTAGE_REG,
643 isl_chg->min_system_voltage);
644 if (ret) {
645 pr_err("Failed to set MIN_SYS_VOLTAGE rc=%d\n", ret);
646 return ret;
647 }
648
649 if (isl_chg->input_current) {
650 ret = isl9519q_write_reg(isl_chg->client,
651 INPUT_CURRENT_REG,
652 isl_chg->input_current);
653 if (ret) {
654 pr_err("Failed to set INPUT_CURRENT rc=%d\n", ret);
655 return ret;
656 }
657 }
658 return 0;
659}
660
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700661static int __devinit isl9519q_probe(struct i2c_client *client,
662 const struct i2c_device_id *id)
663{
664 struct isl_platform_data *pdata;
665 struct isl9519q_struct *isl_chg;
666 int ret;
667
668 ret = 0;
669 pdata = client->dev.platform_data;
670
David Keitel88e1b572012-01-11 11:57:14 -0800671 pr_debug("\n");
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200672
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700673 if (pdata == NULL) {
674 dev_err(&client->dev, "%s no platform data\n", __func__);
675 ret = -EINVAL;
676 goto out;
677 }
678
679 if (!i2c_check_functionality(client->adapter,
680 I2C_FUNC_SMBUS_WORD_DATA)) {
681 ret = -EIO;
682 goto out;
683 }
684
685 isl_chg = kzalloc(sizeof(*isl_chg), GFP_KERNEL);
686 if (!isl_chg) {
687 ret = -ENOMEM;
688 goto out;
689 }
690
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200691 spin_lock_init(&isl_chg->lock);
692
693 INIT_DELAYED_WORK(&isl_chg->charge_work, isl9519q_worker);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700694 isl_chg->client = client;
695 isl_chg->chgcurrent = pdata->chgcurrent;
696 isl_chg->term_current = pdata->term_current;
697 isl_chg->input_current = pdata->input_current;
698 isl_chg->max_system_voltage = pdata->max_system_voltage;
699 isl_chg->min_system_voltage = pdata->min_system_voltage;
700 isl_chg->valid_n_gpio = pdata->valid_n_gpio;
701
702 /* h/w ignores lower 7 bits of charging current and input current */
703 isl_chg->chgcurrent &= ~0x7F;
704 isl_chg->input_current &= ~0x7F;
705
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200706 /**
707 * ISL is Notified by PMIC to start/stop charging, rather than
708 * handling interrupt from ISL for End-Of-Chargring, and
709 * monitoring the charge-current periodically. The valid_n_gpio
710 * is also not used, dc-present is detected by PMIC.
711 */
712 isl_chg->notify_by_pmic = (client->irq == 0);
713 i2c_set_clientdata(client, isl_chg);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700714
715 if (pdata->chg_detection_config) {
716 ret = pdata->chg_detection_config();
717 if (ret) {
718 dev_err(&client->dev, "%s valid config failed ret=%d\n",
719 __func__, ret);
720 goto free_isl_chg;
721 }
722 }
723
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700724 isl_chg->max_system_voltage &= MAX_VOLTAGE_REG_MASK;
725 isl_chg->min_system_voltage &= MIN_VOLTAGE_REG_MASK;
726 if (isl_chg->max_system_voltage == 0)
727 isl_chg->max_system_voltage = DEFAULT_MAX_VOLTAGE_REG_VALUE;
728 if (isl_chg->min_system_voltage == 0)
729 isl_chg->min_system_voltage = DEFAULT_MIN_VOLTAGE_REG_VALUE;
730
David Keitel88e1b572012-01-11 11:57:14 -0800731 ret = isl9519q_hwinit(isl_chg);
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200732 if (ret)
733 goto free_isl_chg;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700734
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200735 if (isl_chg->notify_by_pmic)
736 ret = isl9519q_init_ext_chg(isl_chg);
737 else
738 ret = isl9519q_init_adapter(isl_chg);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700739
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200740 if (ret)
741 goto free_isl_chg;
742
David Keitel81d5c8c92011-10-31 16:42:35 -0700743 the_isl_chg = isl_chg;
744 create_debugfs_entries(isl_chg);
745
David Keitel88e1b572012-01-11 11:57:14 -0800746 pr_info("OK.\n");
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200747
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700748 return 0;
749
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700750free_isl_chg:
751 kfree(isl_chg);
752out:
753 return ret;
754}
755
756static int __devexit isl9519q_remove(struct i2c_client *client)
757{
758 struct isl_platform_data *pdata;
759 struct isl9519q_struct *isl_chg = i2c_get_clientdata(client);
760
761 pdata = client->dev.platform_data;
762 gpio_free(pdata->valid_n_gpio);
763 free_irq(client->irq, client);
764 cancel_delayed_work_sync(&isl_chg->charge_work);
David Keitel88e1b572012-01-11 11:57:14 -0800765 if (isl_chg->notify_by_pmic) {
766 power_supply_unregister(&isl_chg->dc_psy);
767 } else {
768 msm_charger_notify_event(&isl_chg->adapter_hw_chg,
769 CHG_REMOVED_EVENT);
770 msm_charger_unregister(&isl_chg->adapter_hw_chg);
771 }
David Keitel81d5c8c92011-10-31 16:42:35 -0700772 remove_debugfs_entries(isl_chg);
773 the_isl_chg = NULL;
774 kfree(isl_chg);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700775 return 0;
776}
777
778static const struct i2c_device_id isl9519q_id[] = {
779 {"isl9519q", 0},
780 {},
781};
782
783#ifdef CONFIG_PM
784static int isl9519q_suspend(struct device *dev)
785{
786 struct isl9519q_struct *isl_chg = dev_get_drvdata(dev);
787
788 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
789 /*
790 * do not suspend while we are charging
791 * because we need to periodically update the register
792 * for charging to proceed
793 */
794 if (isl_chg->charging)
795 return -EBUSY;
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700796
797 isl_chg->suspended = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700798 return 0;
799}
800
801static int isl9519q_resume(struct device *dev)
802{
803 struct isl9519q_struct *isl_chg = dev_get_drvdata(dev);
804
805 dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700806 isl_chg->suspended = 0;
807 if (isl_chg->charge_at_resume) {
808 isl_chg->charge_at_resume = 0;
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200809 isl9519q_start_charging(isl_chg, 0, 0);
Abhijeet Dharmapurikar8cd05eb2011-08-29 12:08:16 -0700810 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700811 return 0;
812}
813
814static const struct dev_pm_ops isl9519q_pm_ops = {
815 .suspend = isl9519q_suspend,
816 .resume = isl9519q_resume,
817};
818#endif
819
820static struct i2c_driver isl9519q_driver = {
821 .driver = {
822 .name = "isl9519q",
823 .owner = THIS_MODULE,
824#ifdef CONFIG_PM
825 .pm = &isl9519q_pm_ops,
826#endif
827 },
828 .probe = isl9519q_probe,
829 .remove = __devexit_p(isl9519q_remove),
830 .id_table = isl9519q_id,
831};
832
833static int __init isl9519q_init(void)
834{
835 return i2c_add_driver(&isl9519q_driver);
836}
837
Amir Samuelov43cb1e92011-10-23 15:14:12 +0200838late_initcall_sync(isl9519q_init);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700839
840static void __exit isl9519q_exit(void)
841{
842 return i2c_del_driver(&isl9519q_driver);
843}
844
845module_exit(isl9519q_exit);
846
847MODULE_AUTHOR("Abhijeet Dharmapurikar <adharmap@codeaurora.org>");
848MODULE_DESCRIPTION("Driver for ISL9519Q Charger chip");
849MODULE_LICENSE("GPL v2");