| /* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| #define pr_fmt(fmt) "%s: " fmt, __func__ |
| |
| #include <linux/i2c.h> |
| #include <linux/gpio.h> |
| #include <linux/errno.h> |
| #include <linux/delay.h> |
| #include <linux/module.h> |
| #include <linux/debugfs.h> |
| #include <linux/workqueue.h> |
| #include <linux/interrupt.h> |
| #include <linux/msm-charger.h> |
| #include <linux/power_supply.h> |
| #include <linux/slab.h> |
| #include <linux/i2c/isl9519.h> |
| #include <linux/msm_adc.h> |
| #include <linux/spinlock.h> |
| |
| #define CHG_CURRENT_REG 0x14 |
| #define MAX_SYS_VOLTAGE_REG 0x15 |
| #define CONTROL_REG 0x3D |
| #define MIN_SYS_VOLTAGE_REG 0x3E |
| #define INPUT_CURRENT_REG 0x3F |
| #define MANUFACTURER_ID_REG 0xFE |
| #define DEVICE_ID_REG 0xFF |
| |
| #define TRCKL_CHG_STATUS_BIT 0x80 |
| |
| #define ISL9519_CHG_PERIOD_SEC 150 |
| |
| struct isl9519q_struct { |
| struct i2c_client *client; |
| struct delayed_work charge_work; |
| int present; |
| int batt_present; |
| bool charging; |
| int chgcurrent; |
| int term_current; |
| int input_current; |
| int max_system_voltage; |
| int min_system_voltage; |
| int valid_n_gpio; |
| struct dentry *dent; |
| struct msm_hardware_charger adapter_hw_chg; |
| int suspended; |
| int charge_at_resume; |
| struct power_supply dc_psy; |
| spinlock_t lock; |
| bool notify_by_pmic; |
| bool trickle; |
| }; |
| |
| static struct isl9519q_struct *the_isl_chg; |
| |
| static int isl9519q_read_reg(struct i2c_client *client, int reg, |
| u16 *val) |
| { |
| int ret; |
| struct isl9519q_struct *isl_chg; |
| |
| isl_chg = i2c_get_clientdata(client); |
| ret = i2c_smbus_read_word_data(isl_chg->client, reg); |
| |
| if (ret < 0) { |
| dev_err(&isl_chg->client->dev, |
| "i2c read fail: can't read from %02x: %d\n", reg, ret); |
| return -EAGAIN; |
| } else { |
| *val = ret; |
| } |
| |
| pr_debug("reg=0x%x.val=0x%x.\n", reg, *val); |
| |
| return 0; |
| } |
| |
| static int isl9519q_write_reg(struct i2c_client *client, int reg, |
| u16 val) |
| { |
| int ret; |
| struct isl9519q_struct *isl_chg; |
| |
| pr_debug("reg=0x%x.val=0x%x.\n", reg, val); |
| |
| isl_chg = i2c_get_clientdata(client); |
| ret = i2c_smbus_write_word_data(isl_chg->client, reg, val); |
| |
| if (ret < 0) { |
| dev_err(&isl_chg->client->dev, |
| "i2c write fail: can't write %02x to %02x: %d\n", |
| val, reg, ret); |
| return -EAGAIN; |
| } |
| return 0; |
| } |
| |
| /** |
| * Read charge-current via ADC. |
| * |
| * The ISL CCMON (charge-current-monitor) pin is connected to |
| * the PMIC MPP#X pin. |
| * This not required when notify_by_pmic is used where the PMIC |
| * uses BMS to notify the ISL on charging-done / charge-resume. |
| */ |
| static int isl_read_adc(int channel, int *mv_reading) |
| { |
| int ret; |
| void *h; |
| struct adc_chan_result adc_chan_result; |
| struct completion conv_complete_evt; |
| |
| pr_debug("called for %d\n", channel); |
| ret = adc_channel_open(channel, &h); |
| if (ret) { |
| pr_err("couldnt open channel %d ret=%d\n", channel, ret); |
| goto out; |
| } |
| init_completion(&conv_complete_evt); |
| ret = adc_channel_request_conv(h, &conv_complete_evt); |
| if (ret) { |
| pr_err("couldnt request conv channel %d ret=%d\n", |
| channel, ret); |
| goto out; |
| } |
| ret = wait_for_completion_interruptible(&conv_complete_evt); |
| if (ret) { |
| pr_err("wait interrupted channel %d ret=%d\n", channel, ret); |
| goto out; |
| } |
| ret = adc_channel_read_result(h, &adc_chan_result); |
| if (ret) { |
| pr_err("couldnt read result channel %d ret=%d\n", |
| channel, ret); |
| goto out; |
| } |
| ret = adc_channel_close(h); |
| if (ret) |
| pr_err("couldnt close channel %d ret=%d\n", channel, ret); |
| if (mv_reading) |
| *mv_reading = (int)adc_chan_result.measurement; |
| |
| pr_debug("done for %d\n", channel); |
| return adc_chan_result.physical; |
| out: |
| *mv_reading = 0; |
| pr_debug("done with error for %d\n", channel); |
| |
| return -EINVAL; |
| } |
| |
| static bool is_trickle_charging(struct isl9519q_struct *isl_chg) |
| { |
| u16 ctrl = 0; |
| int ret; |
| |
| ret = isl9519q_read_reg(isl_chg->client, CONTROL_REG, &ctrl); |
| |
| if (!ret) { |
| pr_debug("control_reg=0x%x.\n", ctrl); |
| } else { |
| dev_err(&isl_chg->client->dev, |
| "%s couldnt read cntrl reg\n", __func__); |
| } |
| |
| if (ctrl & TRCKL_CHG_STATUS_BIT) |
| return true; |
| |
| return false; |
| } |
| |
| static void isl_adapter_check_ichg(struct isl9519q_struct *isl_chg) |
| { |
| int ichg; /* isl charger current */ |
| int mv_reading = 0; |
| |
| ichg = isl_read_adc(CHANNEL_ADC_BATT_AMON, &mv_reading); |
| |
| dev_dbg(&isl_chg->client->dev, "%s mv_reading=%d\n", |
| __func__, mv_reading); |
| dev_dbg(&isl_chg->client->dev, "%s isl_charger_current=%d\n", |
| __func__, ichg); |
| |
| if (ichg >= 0 && ichg <= isl_chg->term_current) |
| msm_charger_notify_event(&isl_chg->adapter_hw_chg, |
| CHG_DONE_EVENT); |
| |
| isl_chg->trickle = is_trickle_charging(isl_chg); |
| if (isl_chg->trickle) |
| msm_charger_notify_event(&isl_chg->adapter_hw_chg, |
| CHG_BATT_BEGIN_FAST_CHARGING); |
| } |
| |
| /** |
| * isl9519q_worker |
| * |
| * Periodic task required to kick the ISL HW watchdog to keep |
| * charging. |
| * |
| * @isl9519_work: work context. |
| */ |
| static void isl9519q_worker(struct work_struct *isl9519_work) |
| { |
| struct isl9519q_struct *isl_chg; |
| |
| isl_chg = container_of(isl9519_work, struct isl9519q_struct, |
| charge_work.work); |
| |
| dev_dbg(&isl_chg->client->dev, "%s\n", __func__); |
| |
| if (!isl_chg->charging) { |
| pr_debug("stop charging.\n"); |
| isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, 0); |
| return; /* Stop periodic worker */ |
| } |
| |
| /* Kick the dog by writting to CHG_CURRENT_REG */ |
| isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, |
| isl_chg->chgcurrent); |
| |
| if (isl_chg->notify_by_pmic) |
| isl_chg->trickle = is_trickle_charging(isl_chg); |
| else |
| isl_adapter_check_ichg(isl_chg); |
| |
| schedule_delayed_work(&isl_chg->charge_work, |
| (ISL9519_CHG_PERIOD_SEC * HZ)); |
| } |
| |
| static int isl9519q_start_charging(struct isl9519q_struct *isl_chg, |
| int chg_voltage, int chg_current) |
| { |
| pr_debug("\n"); |
| |
| if (isl_chg->charging) { |
| pr_warn("already charging.\n"); |
| return 0; |
| } |
| |
| if (isl_chg->suspended) { |
| pr_warn("suspended - can't start charging.\n"); |
| isl_chg->charge_at_resume = 1; |
| return 0; |
| } |
| |
| dev_dbg(&isl_chg->client->dev, |
| "%s starting timed work.period=%d seconds.\n", |
| __func__, (int) ISL9519_CHG_PERIOD_SEC); |
| |
| /* |
| * The ISL will start charging from the worker context. |
| * This API might be called from interrupt context. |
| */ |
| schedule_delayed_work(&isl_chg->charge_work, 1); |
| |
| isl_chg->charging = true; |
| |
| return 0; |
| } |
| |
| static int isl9519q_stop_charging(struct isl9519q_struct *isl_chg) |
| { |
| pr_debug("\n"); |
| |
| if (!(isl_chg->charging)) { |
| pr_warn("already not charging.\n"); |
| return 0; |
| } |
| |
| if (isl_chg->suspended) { |
| isl_chg->charge_at_resume = 0; |
| return 0; |
| } |
| |
| dev_dbg(&isl_chg->client->dev, "%s\n", __func__); |
| |
| isl_chg->charging = false; |
| isl_chg->trickle = false; |
| /* |
| * The ISL will stop charging from the worker context. |
| * This API might be called from interrupt context. |
| */ |
| schedule_delayed_work(&isl_chg->charge_work, 1); |
| |
| return 0; |
| } |
| |
| static int isl_adapter_start_charging(struct msm_hardware_charger *hw_chg, |
| int chg_voltage, int chg_current) |
| { |
| int rc; |
| struct isl9519q_struct *isl_chg; |
| |
| isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg); |
| rc = isl9519q_start_charging(isl_chg, chg_voltage, chg_current); |
| |
| return rc; |
| } |
| |
| static int isl_adapter_stop_charging(struct msm_hardware_charger *hw_chg) |
| { |
| int rc; |
| struct isl9519q_struct *isl_chg; |
| |
| isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg); |
| rc = isl9519q_stop_charging(isl_chg); |
| |
| return rc; |
| } |
| |
| static int isl9519q_charging_switched(struct msm_hardware_charger *hw_chg) |
| { |
| struct isl9519q_struct *isl_chg; |
| |
| isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg); |
| dev_dbg(&isl_chg->client->dev, "%s\n", __func__); |
| return 0; |
| } |
| |
| static irqreturn_t isl_valid_handler(int irq, void *dev_id) |
| { |
| int val; |
| struct isl9519q_struct *isl_chg; |
| struct i2c_client *client = dev_id; |
| |
| isl_chg = i2c_get_clientdata(client); |
| val = gpio_get_value_cansleep(isl_chg->valid_n_gpio); |
| if (val < 0) { |
| dev_err(&isl_chg->client->dev, |
| "%s gpio_get_value failed for %d ret=%d\n", __func__, |
| isl_chg->valid_n_gpio, val); |
| goto err; |
| } |
| dev_dbg(&isl_chg->client->dev, "%s val=%d\n", __func__, val); |
| |
| if (val) { |
| if (isl_chg->present == 1) { |
| msm_charger_notify_event(&isl_chg->adapter_hw_chg, |
| CHG_REMOVED_EVENT); |
| isl_chg->present = 0; |
| } |
| } else { |
| if (isl_chg->present == 0) { |
| msm_charger_notify_event(&isl_chg->adapter_hw_chg, |
| CHG_INSERTED_EVENT); |
| isl_chg->present = 1; |
| } |
| } |
| err: |
| return IRQ_HANDLED; |
| } |
| |
| static enum power_supply_property pm_power_props[] = { |
| POWER_SUPPLY_PROP_ONLINE, |
| POWER_SUPPLY_PROP_CURRENT_MAX, |
| POWER_SUPPLY_PROP_CHARGE_TYPE, |
| }; |
| |
| static char *pm_power_supplied_to[] = { |
| "battery", |
| }; |
| |
| static int get_prop_charge_type(struct isl9519q_struct *isl_chg) |
| { |
| if (!isl_chg->present) |
| return POWER_SUPPLY_CHARGE_TYPE_NONE; |
| |
| if (isl_chg->trickle) |
| return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; |
| |
| if (isl_chg->charging) |
| return POWER_SUPPLY_CHARGE_TYPE_FAST; |
| |
| return POWER_SUPPLY_CHARGE_TYPE_NONE; |
| } |
| static int pm_power_get_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| struct isl9519q_struct *isl_chg = container_of(psy, |
| struct isl9519q_struct, |
| dc_psy); |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_CURRENT_MAX: |
| val->intval = isl_chg->chgcurrent; |
| break; |
| case POWER_SUPPLY_PROP_ONLINE: |
| val->intval = (int)isl_chg->present; |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_TYPE: |
| val->intval = get_prop_charge_type(isl_chg); |
| break; |
| default: |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int pm_power_set_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| struct isl9519q_struct *isl_chg = container_of(psy, |
| struct isl9519q_struct, |
| dc_psy); |
| unsigned long flags; |
| int rc; |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_ONLINE: |
| if (val->intval) { |
| isl_chg->present = val->intval; |
| } else { |
| isl_chg->present = 0; |
| if (isl_chg->charging) |
| goto stop_charging; |
| } |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_MAX: |
| if (val->intval) { |
| if (isl_chg->chgcurrent != val->intval) |
| return -EINVAL; |
| } |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_TYPE: |
| if (val->intval && isl_chg->present) { |
| if (val->intval == POWER_SUPPLY_CHARGE_TYPE_FAST) |
| goto start_charging; |
| if (val->intval == POWER_SUPPLY_CHARGE_TYPE_NONE) |
| goto stop_charging; |
| } else { |
| return -EINVAL; |
| } |
| break; |
| default: |
| return -EINVAL; |
| } |
| power_supply_changed(&isl_chg->dc_psy); |
| return 0; |
| |
| start_charging: |
| spin_lock_irqsave(&isl_chg->lock, flags); |
| rc = isl9519q_start_charging(isl_chg, 0, isl_chg->chgcurrent); |
| if (rc) |
| pr_err("Failed to start charging rc=%d\n", rc); |
| spin_unlock_irqrestore(&isl_chg->lock, flags); |
| power_supply_changed(&isl_chg->dc_psy); |
| return rc; |
| |
| stop_charging: |
| spin_lock_irqsave(&isl_chg->lock, flags); |
| rc = isl9519q_stop_charging(isl_chg); |
| if (rc) |
| pr_err("Failed to start charging rc=%d\n", rc); |
| spin_unlock_irqrestore(&isl_chg->lock, flags); |
| power_supply_changed(&isl_chg->dc_psy); |
| return rc; |
| } |
| |
| #define MAX_VOLTAGE_REG_MASK 0x3FF0 |
| #define MIN_VOLTAGE_REG_MASK 0x3F00 |
| #define DEFAULT_MAX_VOLTAGE_REG_VALUE 0x1070 |
| #define DEFAULT_MIN_VOLTAGE_REG_VALUE 0x0D00 |
| |
| static int __devinit isl9519q_init_adapter(struct isl9519q_struct *isl_chg) |
| { |
| int ret; |
| struct i2c_client *client = isl_chg->client; |
| struct isl_platform_data *pdata = client->dev.platform_data; |
| |
| isl_chg->adapter_hw_chg.type = CHG_TYPE_AC; |
| isl_chg->adapter_hw_chg.rating = 2; |
| isl_chg->adapter_hw_chg.name = "isl-adapter"; |
| isl_chg->adapter_hw_chg.start_charging = isl_adapter_start_charging; |
| isl_chg->adapter_hw_chg.stop_charging = isl_adapter_stop_charging; |
| isl_chg->adapter_hw_chg.charging_switched = isl9519q_charging_switched; |
| |
| ret = gpio_request(pdata->valid_n_gpio, "isl_charger_valid"); |
| if (ret) { |
| dev_err(&client->dev, "%s gpio_request failed " |
| "for %d ret=%d\n", |
| __func__, pdata->valid_n_gpio, ret); |
| goto out; |
| } |
| |
| ret = msm_charger_register(&isl_chg->adapter_hw_chg); |
| if (ret) { |
| dev_err(&client->dev, |
| "%s msm_charger_register failed for ret =%d\n", |
| __func__, ret); |
| goto free_gpio; |
| } |
| |
| ret = request_threaded_irq(client->irq, NULL, |
| isl_valid_handler, |
| IRQF_TRIGGER_FALLING | |
| IRQF_TRIGGER_RISING, |
| "isl_charger_valid", client); |
| if (ret) { |
| dev_err(&client->dev, |
| "%s request_threaded_irq failed " |
| "for %d ret =%d\n", |
| __func__, client->irq, ret); |
| goto unregister; |
| } |
| irq_set_irq_wake(client->irq, 1); |
| |
| ret = gpio_get_value_cansleep(isl_chg->valid_n_gpio); |
| if (ret < 0) { |
| dev_err(&client->dev, |
| "%s gpio_get_value failed for %d ret=%d\n", |
| __func__, pdata->valid_n_gpio, ret); |
| /* assume absent */ |
| ret = 1; |
| } |
| if (!ret) { |
| msm_charger_notify_event(&isl_chg->adapter_hw_chg, |
| CHG_INSERTED_EVENT); |
| isl_chg->present = 1; |
| } |
| |
| return 0; |
| |
| unregister: |
| msm_charger_unregister(&isl_chg->adapter_hw_chg); |
| free_gpio: |
| gpio_free(pdata->valid_n_gpio); |
| out: |
| return ret; |
| |
| } |
| |
| static int __devinit isl9519q_init_ext_chg(struct isl9519q_struct *isl_chg) |
| { |
| int ret; |
| |
| isl_chg->dc_psy.name = "dc"; |
| isl_chg->dc_psy.type = POWER_SUPPLY_TYPE_MAINS; |
| isl_chg->dc_psy.supplied_to = pm_power_supplied_to; |
| isl_chg->dc_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to); |
| isl_chg->dc_psy.properties = pm_power_props; |
| isl_chg->dc_psy.num_properties = ARRAY_SIZE(pm_power_props); |
| isl_chg->dc_psy.get_property = pm_power_get_property; |
| isl_chg->dc_psy.set_property = pm_power_set_property; |
| |
| ret = power_supply_register(&isl_chg->client->dev, &isl_chg->dc_psy); |
| if (ret) { |
| pr_err("failed to register dc charger.ret=%d.\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| static int set_reg(void *data, u64 val) |
| { |
| int addr = (int)data; |
| int ret; |
| u16 temp; |
| |
| temp = (u16) val; |
| ret = isl9519q_write_reg(the_isl_chg->client, addr, temp); |
| |
| if (ret) { |
| pr_err("isl9519q_write_reg to %x value =%d errored = %d\n", |
| addr, temp, ret); |
| return -EAGAIN; |
| } |
| return 0; |
| } |
| static int get_reg(void *data, u64 *val) |
| { |
| int addr = (int)data; |
| int ret; |
| u16 temp; |
| |
| ret = isl9519q_read_reg(the_isl_chg->client, addr, &temp); |
| if (ret) { |
| pr_err("isl9519q_read_reg to %x value =%d errored = %d\n", |
| addr, temp, ret); |
| return -EAGAIN; |
| } |
| |
| *val = temp; |
| return 0; |
| } |
| |
| DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n"); |
| |
| static void create_debugfs_entries(struct isl9519q_struct *isl_chg) |
| { |
| isl_chg->dent = debugfs_create_dir("isl9519q", NULL); |
| |
| if (IS_ERR(isl_chg->dent)) { |
| pr_err("isl9519q driver couldn't create debugfs dir\n"); |
| return; |
| } |
| |
| debugfs_create_file("CHG_CURRENT_REG", 0644, isl_chg->dent, |
| (void *) CHG_CURRENT_REG, ®_fops); |
| debugfs_create_file("MAX_SYS_VOLTAGE_REG", 0644, isl_chg->dent, |
| (void *) MAX_SYS_VOLTAGE_REG, ®_fops); |
| debugfs_create_file("CONTROL_REG", 0644, isl_chg->dent, |
| (void *) CONTROL_REG, ®_fops); |
| debugfs_create_file("MIN_SYS_VOLTAGE_REG", 0644, isl_chg->dent, |
| (void *) MIN_SYS_VOLTAGE_REG, ®_fops); |
| debugfs_create_file("INPUT_CURRENT_REG", 0644, isl_chg->dent, |
| (void *) INPUT_CURRENT_REG, ®_fops); |
| debugfs_create_file("MANUFACTURER_ID_REG", 0644, isl_chg->dent, |
| (void *) MANUFACTURER_ID_REG, ®_fops); |
| debugfs_create_file("DEVICE_ID_REG", 0644, isl_chg->dent, |
| (void *) DEVICE_ID_REG, ®_fops); |
| } |
| |
| static void remove_debugfs_entries(struct isl9519q_struct *isl_chg) |
| { |
| if (isl_chg->dent) |
| debugfs_remove_recursive(isl_chg->dent); |
| } |
| |
| static int __devinit isl9519q_hwinit(struct isl9519q_struct *isl_chg) |
| { |
| int ret; |
| |
| ret = isl9519q_write_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG, |
| isl_chg->max_system_voltage); |
| if (ret) { |
| pr_err("Failed to set MAX_SYS_VOLTAGE rc=%d\n", ret); |
| return ret; |
| } |
| |
| ret = isl9519q_write_reg(isl_chg->client, MIN_SYS_VOLTAGE_REG, |
| isl_chg->min_system_voltage); |
| if (ret) { |
| pr_err("Failed to set MIN_SYS_VOLTAGE rc=%d\n", ret); |
| return ret; |
| } |
| |
| if (isl_chg->input_current) { |
| ret = isl9519q_write_reg(isl_chg->client, |
| INPUT_CURRENT_REG, |
| isl_chg->input_current); |
| if (ret) { |
| pr_err("Failed to set INPUT_CURRENT rc=%d\n", ret); |
| return ret; |
| } |
| } |
| return 0; |
| } |
| |
| static int __devinit isl9519q_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct isl_platform_data *pdata; |
| struct isl9519q_struct *isl_chg; |
| int ret; |
| |
| ret = 0; |
| pdata = client->dev.platform_data; |
| |
| pr_debug("\n"); |
| |
| if (pdata == NULL) { |
| dev_err(&client->dev, "%s no platform data\n", __func__); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| if (!i2c_check_functionality(client->adapter, |
| I2C_FUNC_SMBUS_WORD_DATA)) { |
| ret = -EIO; |
| goto out; |
| } |
| |
| isl_chg = kzalloc(sizeof(*isl_chg), GFP_KERNEL); |
| if (!isl_chg) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| spin_lock_init(&isl_chg->lock); |
| |
| INIT_DELAYED_WORK(&isl_chg->charge_work, isl9519q_worker); |
| isl_chg->client = client; |
| isl_chg->chgcurrent = pdata->chgcurrent; |
| isl_chg->term_current = pdata->term_current; |
| isl_chg->input_current = pdata->input_current; |
| isl_chg->max_system_voltage = pdata->max_system_voltage; |
| isl_chg->min_system_voltage = pdata->min_system_voltage; |
| isl_chg->valid_n_gpio = pdata->valid_n_gpio; |
| |
| /* h/w ignores lower 7 bits of charging current and input current */ |
| isl_chg->chgcurrent &= ~0x7F; |
| isl_chg->input_current &= ~0x7F; |
| |
| /** |
| * ISL is Notified by PMIC to start/stop charging, rather than |
| * handling interrupt from ISL for End-Of-Chargring, and |
| * monitoring the charge-current periodically. The valid_n_gpio |
| * is also not used, dc-present is detected by PMIC. |
| */ |
| isl_chg->notify_by_pmic = (client->irq == 0); |
| i2c_set_clientdata(client, isl_chg); |
| |
| if (pdata->chg_detection_config) { |
| ret = pdata->chg_detection_config(); |
| if (ret) { |
| dev_err(&client->dev, "%s valid config failed ret=%d\n", |
| __func__, ret); |
| goto free_isl_chg; |
| } |
| } |
| |
| isl_chg->max_system_voltage &= MAX_VOLTAGE_REG_MASK; |
| isl_chg->min_system_voltage &= MIN_VOLTAGE_REG_MASK; |
| if (isl_chg->max_system_voltage == 0) |
| isl_chg->max_system_voltage = DEFAULT_MAX_VOLTAGE_REG_VALUE; |
| if (isl_chg->min_system_voltage == 0) |
| isl_chg->min_system_voltage = DEFAULT_MIN_VOLTAGE_REG_VALUE; |
| |
| ret = isl9519q_hwinit(isl_chg); |
| if (ret) |
| goto free_isl_chg; |
| |
| if (isl_chg->notify_by_pmic) |
| ret = isl9519q_init_ext_chg(isl_chg); |
| else |
| ret = isl9519q_init_adapter(isl_chg); |
| |
| if (ret) |
| goto free_isl_chg; |
| |
| the_isl_chg = isl_chg; |
| create_debugfs_entries(isl_chg); |
| |
| pr_info("OK.\n"); |
| |
| return 0; |
| |
| free_isl_chg: |
| kfree(isl_chg); |
| out: |
| return ret; |
| } |
| |
| static int __devexit isl9519q_remove(struct i2c_client *client) |
| { |
| struct isl_platform_data *pdata; |
| struct isl9519q_struct *isl_chg = i2c_get_clientdata(client); |
| |
| pdata = client->dev.platform_data; |
| gpio_free(pdata->valid_n_gpio); |
| free_irq(client->irq, client); |
| cancel_delayed_work_sync(&isl_chg->charge_work); |
| if (isl_chg->notify_by_pmic) { |
| power_supply_unregister(&isl_chg->dc_psy); |
| } else { |
| msm_charger_notify_event(&isl_chg->adapter_hw_chg, |
| CHG_REMOVED_EVENT); |
| msm_charger_unregister(&isl_chg->adapter_hw_chg); |
| } |
| remove_debugfs_entries(isl_chg); |
| the_isl_chg = NULL; |
| kfree(isl_chg); |
| return 0; |
| } |
| |
| static const struct i2c_device_id isl9519q_id[] = { |
| {"isl9519q", 0}, |
| {}, |
| }; |
| |
| #ifdef CONFIG_PM |
| static int isl9519q_suspend(struct device *dev) |
| { |
| struct isl9519q_struct *isl_chg = dev_get_drvdata(dev); |
| |
| dev_dbg(&isl_chg->client->dev, "%s\n", __func__); |
| /* |
| * do not suspend while we are charging |
| * because we need to periodically update the register |
| * for charging to proceed |
| */ |
| if (isl_chg->charging) |
| return -EBUSY; |
| |
| isl_chg->suspended = 1; |
| return 0; |
| } |
| |
| static int isl9519q_resume(struct device *dev) |
| { |
| struct isl9519q_struct *isl_chg = dev_get_drvdata(dev); |
| |
| dev_dbg(&isl_chg->client->dev, "%s\n", __func__); |
| isl_chg->suspended = 0; |
| if (isl_chg->charge_at_resume) { |
| isl_chg->charge_at_resume = 0; |
| isl9519q_start_charging(isl_chg, 0, 0); |
| } |
| return 0; |
| } |
| |
| static const struct dev_pm_ops isl9519q_pm_ops = { |
| .suspend = isl9519q_suspend, |
| .resume = isl9519q_resume, |
| }; |
| #endif |
| |
| static struct i2c_driver isl9519q_driver = { |
| .driver = { |
| .name = "isl9519q", |
| .owner = THIS_MODULE, |
| #ifdef CONFIG_PM |
| .pm = &isl9519q_pm_ops, |
| #endif |
| }, |
| .probe = isl9519q_probe, |
| .remove = __devexit_p(isl9519q_remove), |
| .id_table = isl9519q_id, |
| }; |
| |
| static int __init isl9519q_init(void) |
| { |
| return i2c_add_driver(&isl9519q_driver); |
| } |
| |
| late_initcall_sync(isl9519q_init); |
| |
| static void __exit isl9519q_exit(void) |
| { |
| return i2c_del_driver(&isl9519q_driver); |
| } |
| |
| module_exit(isl9519q_exit); |
| |
| MODULE_AUTHOR("Abhijeet Dharmapurikar <adharmap@codeaurora.org>"); |
| MODULE_DESCRIPTION("Driver for ISL9519Q Charger chip"); |
| MODULE_LICENSE("GPL v2"); |