/* * 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 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");