| /* |
| * 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. |
| */ |
| |
| #include <linux/battery/sec_charger.h> |
| |
| static enum power_supply_property sec_charger_props[] = { |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_CHARGE_TYPE, |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_ONLINE, |
| POWER_SUPPLY_PROP_CURRENT_NOW, |
| }; |
| |
| 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_CHARGE_TYPE: |
| 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_STATUS: |
| charger->status = val->intval; |
| break; |
| 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; |
| int full_check_type; |
| |
| dev_info(&charger->client->dev, |
| "%s: Charger Interrupt\n", __func__); |
| |
| psy_do_property("battery", get, |
| POWER_SUPPLY_PROP_CHARGE_NOW, val); |
| if (val.intval == SEC_BATTERY_CHARGING_1ST) |
| full_check_type = charger->pdata->full_check_type; |
| else |
| full_check_type = charger->pdata->full_check_type_2nd; |
| |
| if (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; |
| |
| 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_UNKNOWN; |
| 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) { |
| INIT_DELAYED_WORK_DEFERRABLE( |
| &charger->isr_work, sec_chg_isr_work); |
| |
| 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__); |
| goto err_supply_unreg; |
| } |
| |
| 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); |
| } |
| |
| ret = sec_chg_create_attrs(charger->psy_chg.dev); |
| if (ret) { |
| dev_err(&client->dev, |
| "%s : Failed to create_attrs\n", __func__); |
| goto err_req_irq; |
| } |
| |
| dev_dbg(&client->dev, |
| "%s: SEC Charger Driver Loaded\n", __func__); |
| return 0; |
| |
| err_req_irq: |
| if (charger->pdata->chg_irq) |
| free_irq(charger->pdata->chg_irq, charger); |
| err_supply_unreg: |
| power_supply_unregister(&charger->psy_chg); |
| 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"); |