diff options
Diffstat (limited to 'drivers/power/smb136_charger_q1.c')
-rw-r--r-- | drivers/power/smb136_charger_q1.c | 531 |
1 files changed, 531 insertions, 0 deletions
diff --git a/drivers/power/smb136_charger_q1.c b/drivers/power/smb136_charger_q1.c new file mode 100644 index 0000000..da545e0 --- /dev/null +++ b/drivers/power/smb136_charger_q1.c @@ -0,0 +1,531 @@ +/* + * smb136_charger.c + * + * Copyright (C) 2011 Samsung Electronics + * Ikkeun Kim <iks.kim@samsung.com> + * + * 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/power/smb136_charger_q1.h> +#define DEBUG + +enum cable_type_t { + CABLE_TYPE_NONE = 0, + CABLE_TYPE_USB, + CABLE_TYPE_AC, + CABLE_TYPE_MISC, +}; + +static int smb136_i2c_read(struct i2c_client *client, u8 reg, u8 *data) +{ + int ret = 0; + + if (!client) + return -ENODEV; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + return -EIO; + + *data = ret & 0xff; + return 0; +} + +static int smb136_i2c_write(struct i2c_client *client, u8 reg, u8 data) +{ + if (!client) + return -ENODEV; + + return i2c_smbus_write_byte_data(client, reg, data); +} + +static void smb136_test_read(struct i2c_client *client) +{ + struct smb136_chip *chg = i2c_get_clientdata(client); + u8 data = 0; + u32 addr = 0; + + for (addr = 0; addr < 0x0c; addr++) { + smb136_i2c_read(chg->client, addr, &data); + dev_info(&client->dev, + "SMB136 addr : 0x%02x data : 0x%02x\n", addr, data); + } + + for (addr = 0x31; addr < 0x3D; addr++) { + smb136_i2c_read(chg->client, addr, &data); + dev_info(&client->dev, + "SMB136 addr : 0x%02x data : 0x%02x\n", addr, data); + } +} + +static int smb136_get_charging_status(struct i2c_client *client) +{ + struct smb136_chip *chg = i2c_get_clientdata(client); + int status = POWER_SUPPLY_STATUS_UNKNOWN; + u8 data1 = 0; + u8 data2 = 0; + + smb136_i2c_read(chg->client, 0x36, &data1); + smb136_i2c_read(chg->client, 0x39, &data2); + dev_info(&client->dev, "%s : 0x36h(0x%02x), 0x39h(0x%02x)\n", + __func__, data1, data2); + + if (data2 & 0x01) + status = POWER_SUPPLY_STATUS_CHARGING; + else { + if ((data1 & 0x08) == 0x08) { + /* if error bit check, + ignore the status of charger-ic */ + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else if ((data1 & 0xc0) == 0xc0) { + /* At least one charge cycle terminated, + Charge current < Termination Current */ + status = POWER_SUPPLY_STATUS_FULL; + } + } + + return (int)status; +} + +static int smb136_charging(struct i2c_client *client) +{ + struct smb136_chip *chg = i2c_get_clientdata(client); + u8 data = 0; + int gpio = 0; + + dev_info(&client->dev, "%s : enable(%d), cable(%d)\n", + __func__, chg->is_enable, chg->cable_type); + + if (chg->is_enable) { + switch (chg->cable_type) { + case CABLE_TYPE_AC: + /* 1. HC mode */ + data = 0x8c; + + smb136_i2c_write(chg->client, SMB_CommandA, data); + udelay(10); + + /* 2. Change USB5/1/HC Control from Pin to I2C */ + /* 2. EN pin control - active low */ + smb136_i2c_write(chg->client, SMB_PinControl, 0x8); + udelay(10); + + smb136_i2c_write(chg->client, SMB_CommandA, 0x8c); + udelay(10); + + /* 3. Set charge current to 950mA, + termination current to 150mA */ + data = 0x94; + + smb136_i2c_write(chg->client, SMB_ChargeCurrent, data); + udelay(10); + break; + case CABLE_TYPE_USB: + default: + /* 1. USBIN 500mA mode */ + data = 0x88; + + smb136_i2c_write(chg->client, SMB_CommandA, data); + udelay(10); + + /* 2. Change USB5/1/HC Control from Pin to I2C */ + /* 2. EN pin control - active low */ + smb136_i2c_write(chg->client, SMB_PinControl, 0x8); + udelay(10); + + smb136_i2c_write(chg->client, SMB_CommandA, 0x88); + udelay(10); + + /* 3. Set charge current to 500mA, + termination current to 150mA */ + data = 0x14; + + smb136_i2c_write(chg->client, SMB_ChargeCurrent, data); + udelay(10); + break; + } + + /* 3. Enable Automatic Input Current Limit to 1000mA + (threshold 4.25V) */ + data = 0x60; + smb136_i2c_write(chg->client, SMB_InputCurrentLimit, data); + udelay(10); + + /* 4. Automatic Recharge Disabed */ + data = 0x8c; + smb136_i2c_write(chg->client, SMB_ControlA, data); + udelay(10); + + /* 5. Safety timer Disabled */ + data = 0x28; + smb136_i2c_write(chg->client, SMB_ControlB, data); + udelay(10); + + /* 6. Disable USB D+/D- Detection */ + data = 0x28; + smb136_i2c_write(chg->client, SMB_OTGControl, data); + udelay(10); + + /* 7. Set Output Polarity for STAT */ + /* 7. Set float voltage to 4.2V */ + data = 0x4a; + smb136_i2c_write(chg->client, SMB_FloatVoltage, data); + udelay(10); + + /* 8. Re-load Enable */ + data = 0x4b; + smb136_i2c_write(chg->client, SMB_SafetyTimer, data); + udelay(10); + } else { + /* do nothing... */ + } + + /* CHG_EN pin control - active low */ + gpio = gpio_request(chg->pdata->gpio_chg_en, "CHG_EN"); + if (!gpio) { + gpio_direction_output(chg->pdata->gpio_chg_en, + !(chg->is_enable)); + dev_info(&client->dev, "gpio(CHG_EN)is %d\n", + gpio_get_value(chg->pdata->gpio_chg_en)); + gpio_free(chg->pdata->gpio_chg_en); + } else + dev_err(&client->dev, + "faile to request gpio(CHG_EN)\n"); + + /* smb136_test_read(client); */ + + return 0; +} + +static int smb136_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb136_chip *chip = container_of(psy, + struct smb136_chip, + charger); + u8 data; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = smb136_get_charging_status(chip->client); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = chip->cable_type; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = chip->is_enable; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + smb136_i2c_read(chip->client, SMB_ChargeCurrent, &data); + switch (data >> 5) { + case 0: + val->intval = 500; + break; + case 1: + val->intval = 650; + break; + case 2: + val->intval = 750; + break; + case 3: + val->intval = 850; + break; + case 4: + val->intval = 950; + break; + case 5: + val->intval = 1100; + break; + case 6: + val->intval = 1300; + break; + case 7: + val->intval = 1500; + break; + } + break; + default: + return -EINVAL; + } + + dev_info(&chip->client->dev, "%s: smb136_get_property (%d,%d)\n", + __func__, psp, val->intval); + + return 0; +} + +static int smb136_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb136_chip *chip = container_of(psy, + struct smb136_chip, + charger); + + dev_info(&chip->client->dev, "%s: smb136_set_property (%d,%d)\n", + __func__, psp, val->intval); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + chip->is_enable = (val->intval == POWER_SUPPLY_STATUS_CHARGING); + smb136_charging(chip->client); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + switch (val->intval) { + case CABLE_TYPE_USB: + case CABLE_TYPE_AC: + chip->cable_type = val->intval; + break; + default: + dev_err(&chip->client->dev, "cable type NOT supported!\n"); + chip->cable_type = CABLE_TYPE_NONE; + break; + } + break; + case POWER_SUPPLY_PROP_ONLINE: + chip->is_enable = (bool)val->intval; + smb136_charging(chip->client); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (val->intval <= 450) + chip->cable_type = CABLE_TYPE_USB; /* USB */ + else + chip->cable_type = CABLE_TYPE_AC; /* TA */ + break; + default: + return -EINVAL; + } + return 0; +} + +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1) +static bool is_ovp_status; +#endif + +static irqreturn_t smb136_irq_thread(int irq, void *data) +{ + struct smb136_chip *chip = data; +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1) + int ret = 0; + u8 data1 = 0; +#endif + + dev_info(&chip->client->dev, "%s\n", __func__); + + smb136_test_read(chip->client); +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1) + smb136_i2c_read(chip->client, 0x33, &data1); + + if (data1 & 0x02) { + if (is_ovp_status == false) { + is_ovp_status = true; + if (chip->pdata->ovp_cb) + ret = chip->pdata->ovp_cb(true); + dev_info(&chip->client->dev, "$s OVP!!\n"); + } + } else { + if (is_ovp_status == true) { + is_ovp_status = false; + if (chip->pdata->ovp_cb) + ret = chip->pdata->ovp_cb(false); + dev_info(&chip->client->dev, + "$s ovp status released!!\n"); + } + } +#endif + + return IRQ_HANDLED; +} + +static int smb136_irq_init(struct smb136_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + + if (client->irq) { + ret = request_threaded_irq(client->irq, NULL, +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1) + smb136_irq_thread, IRQ_TYPE_EDGE_BOTH, +#else + smb136_irq_thread, IRQ_TYPE_EDGE_FALLING, +#endif + "SMB136 charger", chip); + if (ret) { + dev_err(&client->dev, "failed to reqeust IRQ\n"); + return ret; + } + + ret = enable_irq_wake(client->irq); + if (ret < 0) + dev_err(&client->dev, + "failed to enable wakeup src %d\n", ret); + } + + return 0; +} + +static int smb136_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct smb136_chip *chip; + int ret = 0; + int gpio = 0; + u8 data; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + if (smb136_i2c_read(client, 0x36, &data) < 0) /* check HW */ + return -EIO; + + dev_info(&client->dev, + "%s : SMB136 Charger Driver Loading\n", __func__); + + chip = kzalloc(sizeof(struct smb136_chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, chip); + + if (!chip->pdata) { + dev_err(&client->dev, + "%s : No platform data supplied\n", __func__); + ret = -EINVAL; + goto err_pdata; + } + + if (chip->pdata->set_charger_name) + chip->pdata->set_charger_name(); + + chip->is_enable = false; + chip->cable_type = CABLE_TYPE_NONE; + + chip->charger.name = "smb136-charger"; + chip->charger.type = POWER_SUPPLY_TYPE_BATTERY; + chip->charger.get_property = smb136_get_property; + chip->charger.set_property = smb136_set_property; + chip->charger.properties = smb136_charger_props; + chip->charger.num_properties = ARRAY_SIZE(smb136_charger_props); + + ret = power_supply_register(&client->dev, &chip->charger); + if (ret) { + dev_err(&client->dev, + "failed: power supply register\n"); + kfree(chip); + return ret; + } + + /* CHG_EN pin control - active low */ + if (chip->pdata->gpio_chg_en) { + s3c_gpio_cfgpin(chip->pdata->gpio_chg_en, S3C_GPIO_OUTPUT); + s3c_gpio_setpull(chip->pdata->gpio_chg_en, S3C_GPIO_PULL_NONE); + + gpio = gpio_request(chip->pdata->gpio_chg_en, "CHG_EN"); + if (!gpio) { + gpio_direction_output(chip->pdata->gpio_chg_en, + GPIO_LEVEL_HIGH); + gpio_free(chip->pdata->gpio_chg_en); + } else + dev_err(&client->dev, + "faile to request gpio(CHG_EN)\n"); + } + + if (chip->pdata->gpio_otg_en) { + s3c_gpio_cfgpin(chip->pdata->gpio_otg_en, S3C_GPIO_OUTPUT); + s3c_gpio_setpull(chip->pdata->gpio_otg_en, S3C_GPIO_PULL_NONE); + + gpio = gpio_request(chip->pdata->gpio_otg_en, "OTG_EN"); + if (!gpio) { + gpio_direction_output(chip->pdata->gpio_otg_en, + GPIO_LEVEL_LOW); + gpio_free(chip->pdata->gpio_otg_en); + } else + dev_err(&client->dev, + "faile to request gpio(OTG_EN)\n"); + } + + if (chip->pdata->gpio_ta_nconnected) { + s3c_gpio_cfgpin(chip->pdata->gpio_ta_nconnected, + S3C_GPIO_INPUT); + s3c_gpio_setpull(chip->pdata->gpio_ta_nconnected, + S3C_GPIO_PULL_NONE); + } + + if (chip->pdata->gpio_chg_ing) { +#if 1 +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1) + s3c_gpio_cfgpin(chip->pdata->gpio_chg_ing, S3C_GPIO_SFN(0xf)); +#endif + client->irq = gpio_to_irq(chip->pdata->gpio_chg_ing); + ret = smb136_irq_init(chip); + if (ret) + goto err_pdata; +#else + s3c_gpio_cfgpin(chip->pdata->gpio_chg_ing, S3C_GPIO_INPUT); + s3c_gpio_setpull(chip->pdata->gpio_chg_ing, S3C_GPIO_PULL_NONE); +#endif + } + +#if defined(CONFIG_MACH_Q1_CHN) && defined(CONFIG_SMB136_CHARGER_Q1) + is_ovp_status = false; +#endif + + smb136_test_read(client); + + return 0; + +err_pdata: + kfree(chip); + return ret; +} + +static int __devexit smb136_remove(struct i2c_client *client) +{ + struct smb136_chip *chip = i2c_get_clientdata(client); + + kfree(chip); + return 0; +} + +static const struct i2c_device_id smb136_id[] = { + {"smb136-charger", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, smb136_id); + +static struct i2c_driver smb136_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "smb136-charger", + }, + .probe = smb136_probe, + .remove = __devexit_p(smb136_remove), + .command = NULL, + .id_table = smb136_id, +}; + +static int __init smb136_init(void) +{ + return i2c_add_driver(&smb136_i2c_driver); +} + +static void __exit smb136_exit(void) +{ + i2c_del_driver(&smb136_i2c_driver); +} + +module_init(smb136_init); +module_exit(smb136_exit); + +MODULE_AUTHOR("Ikkeun Kim <iks.kim@samsung.com>"); +MODULE_DESCRIPTION("smb136 charger driver"); +MODULE_LICENSE("GPL"); |