Messages in this thread | | | From | Philip Rakity <> | Date | Sun, 30 Oct 2011 02:52:58 -0700 | Subject | [PATCH 2/3] power: support intersil isl9519q charger driver -- RESEND |
| |
On Aug 29, 2011, at 10:04 AM, Philip Rakity wrote:
> > This code is based on the original work of Abhijeet > Dharmapurikar <adharmap@codeaurora.org>. > > The basic driver has been extended to support the > supplied_to and num_supplicants selectors. These > fields allow the core power infrastructure to signal > to the driver listed in the supplied_to field that > a power change has taken place when that driver > implements the external_power_changed call back. > > The isl9519q has been extended to implement this > callback allowing a driver that detects ac on/off > to signal the change to the isl9519q. > > In the 9519q the supplied_to field is used to tell > the 9519q what battery device it is supplying power to. > It queries the battery using the properties > CHARGE_TO_USE and VOLTAGE_TO_USE to set the correct > voltage and charge. > > In our implementation we have set up the max8925 to > signal ac on/off to the intersil 9519q. The 9519q > will then use the supplied_to field to query the > battery for power information. > > Signed-off-by: Philip Rakity <prakity@marvell.com> > --- > drivers/power/Kconfig | 7 + > drivers/power/Makefile | 1 + > drivers/power/isl9519q.c | 493 ++++++++++++++++++++++++++++++++ > include/linux/i2c/isl9519.h | 52 ++++ > include/linux/power/isl9519q_charger.h | 13 + > 5 files changed, 566 insertions(+), 0 deletions(-) > create mode 100644 drivers/power/isl9519q.c > create mode 100644 include/linux/i2c/isl9519.h > create mode 100644 include/linux/power/isl9519q_charger.h > > diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig > index 57de051..c6fa490 100644 > --- a/drivers/power/Kconfig > +++ b/drivers/power/Kconfig > @@ -249,4 +249,11 @@ config CHARGER_MAX8998 > Say Y to enable support for the battery charger control sysfs and > platform data of MAX8998/LP3974 PMICs. > > +config CHARGER_ISL9519 > + tristate "Intersil ISL9519q battery charger driver" > + depends on I2C > + default n > + help > + Say Y here to enable support for ISL9519q Battery Charger.ß > + > endif # POWER_SUPPLY > diff --git a/drivers/power/Makefile b/drivers/power/Makefile > index b4af13d..ab86e66 100644 > --- a/drivers/power/Makefile > +++ b/drivers/power/Makefile > @@ -38,3 +38,4 @@ obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o > obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o > obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o > obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o > +obj-$(CONFIG_CHARGER_ISL9519) += isl9519q.o > diff --git a/drivers/power/isl9519q.c b/drivers/power/isl9519q.c > new file mode 100644 > index 0000000..685a5aa > --- /dev/null > +++ b/drivers/power/isl9519q.c > @@ -0,0 +1,493 @@ > +/* Copyright (c) 2010 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. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > + * 02110-1301, USA. > + * > + */ > + > +#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/slab.h> > +#include <linux/i2c/isl9519.h> > +#include <linux/power_supply.h> > +#include <linux/power/isl9519q_charger.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 CONTROL_REG_AC_OK (1<<6) > +#define ISL9519_CHG_PERIOD ((HZ) * 150) > + > +struct charger_struct { > + struct i2c_client *client; > + struct power_supply psy; > + struct delayed_work charge_work; > + > + int present; > + int batt_present; > + bool charging; > + int chgcurrent; > + int chgvoltage; > + int term_current; > + int max_system_voltage; > + int min_system_voltage; > + bool online; > +}; > + > +static int isl9519q_read_reg(struct i2c_client *client, int reg, u16 *val) > +{ > + int ret; > + > + ret = i2c_smbus_read_word_data(client, reg); > + > + if (ret < 0) { > + dev_err(&client->dev, > + "i2c read fail: can't read from %02x: %d\n", reg, ret); > + return -EAGAIN; > + } else > + *val = ret; > + > + return 0; > +} > + > +static int isl9519q_write_reg(struct i2c_client *client, int reg, > + u16 val) > +{ > + int ret; > + struct charger_struct *isl_chg; > + > + 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; > +} > + > + > +static int isl9519q_start_charging(struct charger_struct *isl_chg) > +{ > + int ret = 0; > + u16 old_voltage; > + u16 old_current; > + > + dev_dbg(&isl_chg->client->dev, "%s\n", __func__); > + > + ret = isl9519q_read_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG, > + &old_voltage); > + > + ret = isl9519q_read_reg(isl_chg->client, CHG_CURRENT_REG, > + &old_current); > + > + ret = isl9519q_write_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG, > + isl_chg->chgvoltage); > + > + if (ret) { > + dev_err(&isl_chg->client->dev, > + "%s coulnt write to MAX_SYS_VOLTAGE_REG\n", __func__); > + goto out; > + } > + ret = isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, > + isl_chg->chgcurrent); > + if (ret) { > + dev_err(&isl_chg->client->dev, > + "%s coulnt write to CHG_CURRENT_REG\n", __func__); > + goto out; > + } > + > + ret = isl9519q_read_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG, > + &old_voltage); > + > + ret = isl9519q_read_reg(isl_chg->client, CHG_CURRENT_REG, > + &old_current); > + > + isl_chg->charging = true; > + > +out: > + return ret; > +} > + > +static int isl9519q_stop_charging(struct charger_struct *isl_chg) > +{ > + int ret = 0; > + > + if (!(isl_chg->charging)) > + /* we arent charging */ > + return 0; > + > + dev_dbg(&isl_chg->client->dev, "%s\n", __func__); > + > + ret = isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, 0); > + if (ret) { > + dev_err(&isl_chg->client->dev, > + "%s coulnt write to current_reg\n", __func__); > + goto out; > + } > +out: > + return ret; > +} > + > +static void isl9519q_charge(struct work_struct *isl9519_work) > +{ > + struct charger_struct *isl_chg; > + struct power_supply *psy; > + union power_supply_propval ret = {0,}; > + int voltage = 0; > + int charge = 0; > + int i; > + > + isl_chg = container_of(isl9519_work, struct charger_struct, > + charge_work.work); > + > + dev_dbg(&isl_chg->client->dev, "%s\n", __func__); > + > + if (isl_chg->charging) { > + for (i = 0; i < isl_chg->psy.num_supplicants; i++) { > + psy = power_supply_get_by_name > + (isl_chg->psy.supplied_to[i]); > + if (psy != NULL) { > + if (psy->get_property(psy, > + POWER_SUPPLY_PROP_CHARGE_TO_USE, &ret) > + == 0) > + charge = ret.intval; > + > + if (psy->get_property(psy, > + POWER_SUPPLY_PROP_VOLTAGE_TO_USE, &ret) > + == 0) > + voltage = ret.intval; > + > + if (isl_chg->charging) { > + isl_chg->chgvoltage > + = (voltage / 1000); /* mV */ > + isl_chg->chgcurrent > + = (charge / 1000); /* mA */ > + isl9519q_start_charging(isl_chg); > + } > + } > + } > + schedule_delayed_work(&isl_chg->charge_work, > + ISL9519_CHG_PERIOD); > + } else > + isl9519q_stop_charging(isl_chg); > + > +} > + > +static void isl9519q_external_power_changed(struct power_supply *pst) > +{ > + struct charger_struct *isl_chg = > + container_of(pst, struct charger_struct, psy); > + u16 temp; > + > + if (isl9519q_read_reg(isl_chg->client, CONTROL_REG, &temp) < 0) { > + isl_chg->online = 0; > + isl_chg->charging = 0; > + } else { > + isl_chg->online = 1; > + if (temp & CONTROL_REG_AC_OK) > + isl_chg->charging = 1; > + else > + isl_chg->charging = 0; > + } > + if (isl_chg->charging) > + schedule_delayed_work(&isl_chg->charge_work, > + ISL9519_CHG_PERIOD); > + else > + cancel_delayed_work(&isl_chg->charge_work); > + > + power_supply_changed(pst); > +} > + > + > +static int isl9519q_charger_get_property(struct power_supply *psy, > + enum power_supply_property psp, > + union power_supply_propval *val) > +{ > + struct charger_struct *isl_chg = > + container_of(psy, struct charger_struct, psy); > + u16 temp; > + int ret; > + > + switch (psp) { > + case POWER_SUPPLY_PROP_PRESENT: > + ret = isl9519q_read_reg(isl_chg->client, DEVICE_ID_REG, > + &temp); > + > + isl_chg->present = ret ? 0 : 1; > + val->intval = isl_chg->present; > + break; > + case POWER_SUPPLY_PROP_ONLINE: > + val->intval = isl_chg->online; > + break; > + case POWER_SUPPLY_PROP_VOLTAGE_MAX: > + ret = isl9519q_read_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG, > + &temp); > + if (ret) { > + dev_err(&isl_chg->client->dev, > + "%s could not read to MAX_SYS_VOLTAGE_REG" > + " ret=%d\n", > + __func__, ret); > + return -EINVAL; > + } > + isl_chg->max_system_voltage = temp; > + val->intval = temp * 1000; /* convert to uV */ > + break; > + case POWER_SUPPLY_PROP_VOLTAGE_MIN: > + ret = isl9519q_read_reg(isl_chg->client, MIN_SYS_VOLTAGE_REG, > + &temp); > + if (ret) { > + dev_err(&isl_chg->client->dev, > + "%s couldnt read to MIN_SYS_VOLTAGE_REG" > + " ret=%d\n", > + __func__, ret); > + return -EINVAL; > + } > + temp &= 0x2F00; > + isl_chg->min_system_voltage = temp; > + val->intval = temp * 1000; /* convert to uV */ > + break; > + case POWER_SUPPLY_PROP_CHARGE_FULL: > + val->intval = !isl_chg->charging; > + break; > + case POWER_SUPPLY_PROP_MANUFACTURER: > + val->strval = "Intersil"; > + break; > + default: > + return -EINVAL; > + } > + return 0; > +} > + > +static enum power_supply_property power_props[] = { > + POWER_SUPPLY_PROP_PRESENT, > + POWER_SUPPLY_PROP_ONLINE, > + POWER_SUPPLY_PROP_VOLTAGE_MAX, > + POWER_SUPPLY_PROP_VOLTAGE_MIN, > + POWER_SUPPLY_PROP_CHARGE_FULL, > + POWER_SUPPLY_PROP_MANUFACTURER, > +}; > + > +#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 isl9519q_init_chip(struct i2c_client *client, > + struct charger_struct *isl_chg) > +{ > + int ret; > + u16 temp; > + > + 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_write_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG, > + isl_chg->max_system_voltage); > + if (ret) { > + dev_err(&client->dev, > + "%s couldnt write to MAX_SYS_VOLTAGE_REG ret=%d\n", > + __func__, ret); > + goto exit; > + } > + > + ret = isl9519q_write_reg(isl_chg->client, MIN_SYS_VOLTAGE_REG, > + isl_chg->min_system_voltage); > + if (ret) { > + dev_err(&client->dev, > + "%s couldnt write to MIN_SYS_VOLTAGE_REG ret=%d\n", > + __func__, ret); > + goto exit; > + } > + > + ret = isl9519q_read_reg(isl_chg->client, CONTROL_REG, > + &temp); > + if (temp & CONTROL_REG_AC_OK) > + isl_chg->charging = 1; > + else > + isl_chg->charging = 0; > +exit: > + return ret; > +} > + > +static int __devinit isl9519q_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct isl9519q_platform_data *pdata; > + struct charger_struct *isl_chg; > + int ret; > + > + ret = 0; > + pdata = client->dev.platform_data; > + > + 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; > + } > + > + INIT_DELAYED_WORK(&isl_chg->charge_work, isl9519q_charge); > + isl_chg->client = client; > + isl_chg->chgcurrent = pdata->chgcurrent; > + isl_chg->term_current = pdata->term_current; > + isl_chg->max_system_voltage = pdata->max_system_voltage; > + isl_chg->min_system_voltage = pdata->min_system_voltage; > + > + /* h/w ignores lower 7 bits of charging current */ > + isl_chg->chgcurrent &= ~0x7F; > + > + isl_chg->psy.name = "isl-adapter"; > + isl_chg->psy.type = POWER_SUPPLY_TYPE_MAINS; > + isl_chg->psy.properties = power_props; > + isl_chg->psy.num_properties = ARRAY_SIZE(power_props); > + isl_chg->psy.get_property = isl9519q_charger_get_property; > + isl_chg->psy.supplied_to = pdata->supplied_to; > + isl_chg->psy.num_supplicants = pdata->num_supplicants; > + isl_chg->psy.external_power_changed = isl9519q_external_power_changed; > + > + i2c_set_clientdata(client, isl_chg); > + ret = isl9519q_init_chip(client, isl_chg); > + if (ret) > + goto free_isl_chg; > + ret = power_supply_register(&client->dev, &isl_chg->psy); > + if (ret) > + goto free_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; > + } > + } > + schedule_delayed_work(&isl_chg->charge_work, > + ISL9519_CHG_PERIOD); > + return 0; > + > +free_isl_chg: > + kfree(isl_chg); > +out: > + return ret; > +} > + > +static int __devexit isl9519q_remove(struct i2c_client *client) > +{ > + struct isl9519q_platform_data *pdata; > + struct charger_struct *isl_chg = i2c_get_clientdata(client); > + kfree(isl_chg); > + pdata = client->dev.platform_data; > + return 0; > +} > + > +static const struct i2c_device_id isl9519q_id[] = { > + {"isl9519q", 0}, > + {}, > +}; > + > +#ifdef CONFIG_PM > +static int isl9519q_suspend(struct i2c_client *client, > + pm_message_t state) > +{ > + struct charger_struct *isl_chg = i2c_get_clientdata(client); > + > + dev_dbg(&isl_chg->client->dev, "%s\n", __func__); > + return 0; > +} > + > +static int isl9519q_resume(struct i2c_client *client) > +{ > + struct charger_struct *isl_chg = i2c_get_clientdata(client); > + u16 temp; > + int ret; > + > + dev_dbg(&isl_chg->client->dev, "%s\n", __func__); > + > + ret = isl9519q_read_reg(isl_chg->client, CONTROL_REG, > + &temp); > + if (!ret && (temp & CONTROL_REG_AC_OK)) { > + isl_chg->charging = 1; > + schedule_delayed_work(&isl_chg->charge_work, > + ISL9519_CHG_PERIOD); > + } else { > + isl_chg->charging = 0; > + isl9519q_stop_charging(isl_chg); > + cancel_delayed_work(&isl_chg->charge_work); > + } > + return 0; > +} > +#endif > + > +static struct i2c_driver isl9519q_driver = { > + .driver = { > + .name = "isl9519q", > + .owner = THIS_MODULE, > + }, > + .probe = isl9519q_probe, > +#ifdef CONFIG_PM > + .suspend = isl9519q_suspend, > + .resume = isl9519q_resume, > +#endif > + .remove = __devexit_p(isl9519q_remove), > + .id_table = isl9519q_id, > +}; > + > +static int __init isl9519q_init(void) > +{ > + return i2c_add_driver(&isl9519q_driver); > +} > + > +module_init(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"); > diff --git a/include/linux/i2c/isl9519.h b/include/linux/i2c/isl9519.h > new file mode 100644 > index 0000000..6814ef0 > --- /dev/null > +++ b/include/linux/i2c/isl9519.h > @@ -0,0 +1,52 @@ > +/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. > + * > + * Redistribution and use in source and binary forms, with or without > + * modification, are permitted provided that the following conditions are > + * met: > + * * Redistributions of source code must retain the above copyright > + * notice, this list of conditions and the following disclaimer. > + * * Redistributions in binary form must reproduce the above > + * copyright notice, this list of conditions and the following > + * disclaimer in the documentation and/or other materials provided > + * with the distribution. > + * * Neither the name of Code Aurora Forum, Inc. nor the names of its > + * contributors may be used to endorse or promote products derived > + * from this software without specific prior written permission. > + * > + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED > + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF > + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT > + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS > + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR > + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF > + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR > + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, > + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE > + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN > + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. > + * > + */ > +#ifndef __ISL9519_H__ > +#define __ISL9519_H__ > + > +/** > + * struct isl_platform_data > + * @chgcurrent max current the islchip can draw > + * @valid_irq interrupt for insertion/removal notification > + * @valid_n_gpio gpio to debounce insertion/removal > + * @valid_config machine specific func to configure gpio line > + * @max_system_voltage the max voltage isl should charge battery to > + * @min_system_voltage the min voltage isl should trkl charge the > + * battery > + * @term_current the battery current when isl charging should stop > + */ > +struct isl_platform_data { > + int chgcurrent; > + int valid_n_gpio; > + int (*chg_detection_config) (void); > + int max_system_voltage; > + int min_system_voltage; > + int term_current; > +}; > + > +#endif > diff --git a/include/linux/power/isl9519q_charger.h b/include/linux/power/isl9519q_charger.h > new file mode 100644 > index 0000000..dccf1f2 > --- /dev/null > +++ b/include/linux/power/isl9519q_charger.h > @@ -0,0 +1,13 @@ > +#ifndef __ISL9519Q_CHARGER_H_ > +#define __ISL9519Q_CHARGER_H_ > + > +struct isl9519q_platform_data { > + char **supplied_to; > + int num_supplicants; > + u16 chgcurrent; > + u16 term_current; > + u16 max_system_voltage; > + u16 min_system_voltage; > + int (*chg_detection_config) (void); > +}; > +#endif > -- > 1.7.6 > >
-- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
| |