diff options
Diffstat (limited to 'drivers/battery/sec_charger.c')
-rw-r--r-- | drivers/battery/sec_charger.c | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/drivers/battery/sec_charger.c b/drivers/battery/sec_charger.c new file mode 100644 index 0000000..9947027 --- /dev/null +++ b/drivers/battery/sec_charger.c @@ -0,0 +1,395 @@ +/* + * sec_charger.c + * Samsung Mobile Charger Driver + * + * Copyright (C) 2012 Samsung Electronics + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define DEBUG + +#include <linux/battery/sec_charger.h> + +static int sec_chg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_charger_info *charger = + container_of(psy, struct sec_charger_info, psy_chg); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = charger->charging_current ? 1 : 0; + break; + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_HEALTH: + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (!sec_hal_chg_get_property(charger->client, psp, val)) + return -EINVAL; + break; + default: + return -EINVAL; + } + return 0; +} + +static int sec_chg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_charger_info *charger = + container_of(psy, struct sec_charger_info, psy_chg); + + switch (psp) { + /* val->intval : type */ + case POWER_SUPPLY_PROP_ONLINE: + charger->cable_type = val->intval; + if (val->intval == POWER_SUPPLY_TYPE_BATTERY) + charger->is_charging = false; + else + charger->is_charging = true; + + /* current setting */ + charger->charging_current = + charger->pdata->charging_current[ + val->intval].fast_charging_current; + + if (!sec_hal_chg_set_property(charger->client, psp, val)) + return -EINVAL; + break; + /* val->intval : charging current */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + charger->charging_current = val->intval; + + if (!sec_hal_chg_set_property(charger->client, psp, val)) + return -EINVAL; + break; + default: + return -EINVAL; + } + return 0; +} + +static void sec_chg_isr_work(struct work_struct *work) +{ + struct sec_charger_info *charger = + container_of(work, struct sec_charger_info, isr_work.work); + union power_supply_propval val; + + dev_info(&charger->client->dev, + "%s: Charger Interrupt\n", __func__); + + if (charger->pdata->full_check_type == + SEC_BATTERY_FULLCHARGED_CHGINT) { + if (!sec_hal_chg_get_property(charger->client, + POWER_SUPPLY_PROP_STATUS, &val)) + return; + + switch (val.intval) { + case POWER_SUPPLY_STATUS_DISCHARGING: + dev_err(&charger->client->dev, + "%s: Interrupted but Discharging\n", __func__); + break; + + case POWER_SUPPLY_STATUS_NOT_CHARGING: + dev_err(&charger->client->dev, + "%s: Interrupted but NOT Charging\n", __func__); + break; + + case POWER_SUPPLY_STATUS_FULL: + dev_info(&charger->client->dev, + "%s: Interrupted by Full\n", __func__); + psy_do_property("battery", set, + POWER_SUPPLY_PROP_STATUS, val); + break; + + case POWER_SUPPLY_STATUS_CHARGING: + dev_err(&charger->client->dev, + "%s: Interrupted but Charging\n", __func__); + break; + + case POWER_SUPPLY_STATUS_UNKNOWN: + default: + dev_err(&charger->client->dev, + "%s: Invalid Charger Status\n", __func__); + break; + } + } + + if (charger->pdata->ovp_uvlo_check_type == + SEC_BATTERY_OVP_UVLO_CHGINT) { + if (!sec_hal_chg_get_property(charger->client, + POWER_SUPPLY_PROP_HEALTH, &val)) + return; + + switch (val.intval) { + case POWER_SUPPLY_HEALTH_OVERHEAT: + case POWER_SUPPLY_HEALTH_COLD: + dev_err(&charger->client->dev, + "%s: Interrupted but Hot/Cold\n", __func__); + break; + + case POWER_SUPPLY_HEALTH_DEAD: + dev_err(&charger->client->dev, + "%s: Interrupted but Dead\n", __func__); + break; + + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + case POWER_SUPPLY_HEALTH_UNDERVOLTAGE: + dev_info(&charger->client->dev, + "%s: Interrupted by OVP/UVLO\n", __func__); + psy_do_property("battery", set, + POWER_SUPPLY_PROP_HEALTH, val); + break; + + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + dev_err(&charger->client->dev, + "%s: Interrupted but Unspec\n", __func__); + break; + + case POWER_SUPPLY_HEALTH_GOOD: + dev_err(&charger->client->dev, + "%s: Interrupted but Good\n", __func__); + break; + + case POWER_SUPPLY_HEALTH_UNKNOWN: + default: + dev_err(&charger->client->dev, + "%s: Invalid Charger Health\n", __func__); + break; + } + } +} + + +static irqreturn_t sec_chg_irq_thread(int irq, void *irq_data) +{ + struct sec_charger_info *charger = irq_data; + + if ((charger->pdata->full_check_type == + SEC_BATTERY_FULLCHARGED_CHGINT) || + (charger->pdata->ovp_uvlo_check_type == + SEC_BATTERY_OVP_UVLO_CHGINT)) + schedule_delayed_work(&charger->isr_work, 0); + + return IRQ_HANDLED; +} + +static int sec_chg_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(sec_charger_attrs); i++) { + rc = device_create_file(dev, &sec_charger_attrs[i]); + if (rc) + goto create_attrs_failed; + } + goto create_attrs_succeed; + +create_attrs_failed: + dev_err(dev, "%s: failed (%d)\n", __func__, rc); + while (i--) + device_remove_file(dev, &sec_charger_attrs[i]); +create_attrs_succeed: + return rc; +} + +ssize_t sec_chg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const ptrdiff_t offset = attr - sec_charger_attrs; + int i = 0; + + switch (offset) { + case CHG_REG: + case CHG_DATA: + case CHG_REGS: + i = sec_hal_chg_show_attrs(dev, offset, buf); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t sec_chg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + const ptrdiff_t offset = attr - sec_charger_attrs; + int ret = 0; + + switch (offset) { + case CHG_REG: + case CHG_DATA: + ret = sec_hal_chg_store_attrs(dev, offset, buf, count); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int __devinit sec_charger_probe( + struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = + to_i2c_adapter(client->dev.parent); + struct sec_charger_info *charger; + int ret = 0; + + dev_dbg(&client->dev, + "%s: SEC Charger Driver Loading\n", __func__); + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + charger->client = client; + charger->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, charger); + + charger->psy_chg.name = "sec-charger"; + charger->psy_chg.type = POWER_SUPPLY_TYPE_BATTERY; + charger->psy_chg.get_property = sec_chg_get_property; + charger->psy_chg.set_property = sec_chg_set_property; + charger->psy_chg.properties = sec_charger_props; + charger->psy_chg.num_properties = ARRAY_SIZE(sec_charger_props); + + if (!charger->pdata->chg_gpio_init()) { + dev_err(&client->dev, + "%s: Failed to Initialize GPIO\n", __func__); + goto err_free; + } + + if (!sec_hal_chg_init(charger->client)) { + dev_err(&client->dev, + "%s: Failed to Initialize Charger\n", __func__); + goto err_free; + } + + ret = power_supply_register(&client->dev, &charger->psy_chg); + if (ret) { + dev_err(&client->dev, + "%s: Failed to Register psy_chg\n", __func__); + goto err_free; + } + + if (charger->pdata->chg_irq) { + ret = request_threaded_irq(charger->pdata->chg_irq, + NULL, sec_chg_irq_thread, + charger->pdata->chg_irq_attr, + "charger-irq", charger); + if (ret) { + dev_err(&client->dev, + "%s: Failed to Reqeust IRQ\n", __func__); + return ret; + } + + if (charger->pdata->full_check_type == + SEC_BATTERY_FULLCHARGED_CHGINT) { + ret = enable_irq_wake(charger->pdata->chg_irq); + if (ret < 0) + dev_err(&client->dev, + "%s: Failed to Enable Wakeup Source(%d)\n", + __func__, ret); + } + + INIT_DELAYED_WORK_DEFERRABLE( + &charger->isr_work, sec_chg_isr_work); + } + + ret = sec_chg_create_attrs(charger->psy_chg.dev); + if (ret) { + dev_err(&client->dev, + "%s : Failed to create_attrs\n", __func__); + goto err_free; + } + + dev_dbg(&client->dev, + "%s: SEC Charger Driver Loaded\n", __func__); + return 0; + +err_free: + kfree(charger); + + return ret; +} + +static int __devexit sec_charger_remove( + struct i2c_client *client) +{ + return 0; +} + +static int sec_charger_suspend(struct i2c_client *client, + pm_message_t state) +{ + if (!sec_hal_chg_suspend(client)) + dev_err(&client->dev, + "%s: Failed to Suspend Charger\n", __func__); + + return 0; +} + +static int sec_charger_resume(struct i2c_client *client) +{ + if (!sec_hal_chg_resume(client)) + dev_err(&client->dev, + "%s: Failed to Resume Charger\n", __func__); + + return 0; +} + +static void sec_charger_shutdown(struct i2c_client *client) +{ +} + +static const struct i2c_device_id sec_charger_id[] = { + {"sec-charger", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, sec_charger_id); + +static struct i2c_driver sec_charger_driver = { + .driver = { + .name = "sec-charger", + }, + .probe = sec_charger_probe, + .remove = __devexit_p(sec_charger_remove), + .suspend = sec_charger_suspend, + .resume = sec_charger_resume, + .shutdown = sec_charger_shutdown, + .id_table = sec_charger_id, +}; + +static int __init sec_charger_init(void) +{ + return i2c_add_driver(&sec_charger_driver); +} + +static void __exit sec_charger_exit(void) +{ + i2c_del_driver(&sec_charger_driver); +} + +module_init(sec_charger_init); +module_exit(sec_charger_exit); + +MODULE_DESCRIPTION("Samsung Charger Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); |