diff options
Diffstat (limited to 'drivers/battery/smb358_charger.c')
| -rwxr-xr-x | drivers/battery/smb358_charger.c | 2022 |
1 files changed, 2022 insertions, 0 deletions
diff --git a/drivers/battery/smb358_charger.c b/drivers/battery/smb358_charger.c new file mode 100755 index 00000000..a6b0117b --- /dev/null +++ b/drivers/battery/smb358_charger.c @@ -0,0 +1,2022 @@ +/* + * smb358_charger.c + * Samsung SMB358 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> + +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/of_gpio.h> + +#ifndef SLOW_CHARGING_CURRENT_STANDARD + +#if defined(CONFIG_MACH_HEAT_AIO) +#define SLOW_CHARGING_CURRENT_STANDARD 400 +#else +#define SLOW_CHARGING_CURRENT_STANDARD 1000 +#endif + +#endif + +static int smb358_i2c_write(struct i2c_client *client, + int reg, u8 *buf) +{ + int ret; + ret = i2c_smbus_write_i2c_block_data(client, reg, 1, buf); + if (ret < 0) + dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret); + return ret; +} + +static int smb358_i2c_read(struct i2c_client *client, + int reg, u8 *buf) +{ + int ret; + ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf); + if (ret < 0) + dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret); + return ret; +} + +/*static void smb358_i2c_write_array(struct i2c_client *client, + u8 *buf, int size) +{ + int i; + for (i = 0; i < size; i += 3) + smb358_i2c_write(client, (u8) (*(buf + i)), (buf + i) + 1); +}*/ + +static int smb358_update_reg(struct i2c_client *client, int reg, u8 data) +{ + int ret; + u8 r_data = 0; + u8 w_data = 0; + u8 o_data = data; + + ret = smb358_i2c_read(client, reg, &r_data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - read(%d)\n", __func__, ret); + goto error; + } + + w_data = r_data | data; + ret = smb358_i2c_write(client, reg, &w_data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - write(%d)\n", __func__, ret); + goto error; + } + + ret = smb358_i2c_read(client, reg, &data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - read(%d)\n", __func__, ret); + goto error; + } + + dev_dbg(&client->dev, + "%s: reg(0x%02x) 0x%02x : 0x%02x -> 0x%02x -> 0x%02x\n", + __func__, reg, o_data, r_data, w_data, data); + +error: + return ret; +} + +static int smb358_clear_reg(struct i2c_client *client, int reg, u8 data) +{ + int ret; + u8 r_data = 0; + u8 w_data = 0; + u8 o_data = data; + + ret = smb358_i2c_read(client, reg, &r_data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - read(%d)\n", __func__, ret); + goto error; + } + + w_data = r_data & (~data); + ret = smb358_i2c_write(client, reg, &w_data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - write(%d)\n", __func__, ret); + goto error; + } + + ret = smb358_i2c_read(client, reg, &data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - read(%d)\n", __func__, ret); + goto error; + } + + dev_dbg(&client->dev, + "%s: reg(0x%02x)- 0x%02x : 0x%02x -> 0x%02x -> 0x%02x\n", + __func__, reg, o_data, r_data, w_data, data); + +error: + return ret; +} + +static int smb358_volatile_writes(struct i2c_client *client, u8 value) +{ + int ret = 0; + + if (value == SMB358_ENABLE_WRITE) { + ret = smb358_update_reg(client, SMB358_COMMAND_A, 0x80); + if (ret < 0) { + dev_err(&client->dev, "%s: error(%d)\n", __func__, ret); + goto error; + } + dev_dbg(&client->dev, "%s: ENABLED\n", __func__); + } else { + ret = smb358_clear_reg(client, SMB358_COMMAND_A, 0x80); + if (ret < 0) { + dev_err(&client->dev, "%s: error(%d)\n", __func__, ret); + goto error; + } + dev_dbg(&client->dev, "%s: DISABLED\n", __func__); + } + +error: + return ret; +} + +static void smb358_set_command(struct i2c_client *client, + int reg, u8 datum) +{ + int val; + u8 after_data; + + if (smb358_i2c_write(client, reg, &datum) < 0) + dev_err(&client->dev, + "%s : error!\n", __func__); + + val = smb358_i2c_read(client, reg, &after_data); + if (val >= 0) + dev_info(&client->dev, + "%s : reg(0x%02x) 0x%02x => 0x%02x\n", + __func__, reg, datum, after_data); + else + dev_err(&client->dev, "%s : error!\n", __func__); +} + +#if 0 +static void smb358_test_read(struct i2c_client *client) +{ + u8 data = 0; + u32 addr = 0; + for (addr = 0; addr <= 0x0f; addr++) { + smb358_i2c_read(client, addr, &data); + dev_dbg(&client->dev, + "%s : smb358 addr : 0x%02x data : 0x%02x\n", + __func__, addr, data); + } + for (addr = 0x30; addr <= 0x3f; addr++) { + smb358_i2c_read(client, addr, &data); + dev_dbg(&client->dev, + "%s : smb358 addr : 0x%02x data : 0x%02x\n", + __func__, addr, data); + } +} +#endif + +static void smb358_read_regs(struct i2c_client *client, char *str) +{ + u8 data = 0; + u32 addr = 0; + + for (addr = 0; addr <= 0x0f; addr++) { + smb358_i2c_read(client, addr, &data); + sprintf(str+strlen(str), "0x%x, ", data); + } + + /* "#" considered as new line in application */ + sprintf(str+strlen(str), "#"); + + for (addr = 0x30; addr <= 0x3f; addr++) { + smb358_i2c_read(client, addr, &data); + sprintf(str+strlen(str), "0x%x, ", data); + } +} + +static int smb358_get_aicl_current(u8 aicl_current) +{ + int data; + + if (aicl_current <= 0x10) + data = 300; + else if (aicl_current <= 0x11) + data = 500; + else if (aicl_current <= 0x12) + data = 700; + else if (aicl_current <= 0x13) + data = 1000; + else if (aicl_current <= 0x14) + data = 1200; + else if (aicl_current <= 0x15) + data = 1300; + else if (aicl_current <= 0x16) + data = 1800; + else + data = 2000; + + return data; +} + +static int smb358_get_charging_status(struct i2c_client *client) +{ + int status = POWER_SUPPLY_STATUS_UNKNOWN; + u8 data_a = 0; + u8 data_b = 0; + u8 data_c = 0; + u8 data_d = 0; + u8 data_e = 0; + u8 therm_control_a = 0; + u8 other_control_a = 0; + + + /*smb358_test_read(client);*/ + + smb358_i2c_read(client, SMB358_STATUS_A, &data_a); + dev_dbg(&client->dev, + "%s : charger status A(0x%02x)\n", __func__, data_a); + smb358_i2c_read(client, SMB358_STATUS_B, &data_b); + dev_dbg(&client->dev, + "%s : charger status B(0x%02x)\n", __func__, data_b); + smb358_i2c_read(client, SMB358_STATUS_C, &data_c); + dev_dbg(&client->dev, + "%s : charger status C(0x%02x)\n", __func__, data_c); + smb358_i2c_read(client, SMB358_STATUS_D, &data_d); + dev_dbg(&client->dev, + "%s : charger status D(0x%02x)\n", __func__, data_d); + smb358_i2c_read(client, SMB358_STATUS_E, &data_e); + dev_dbg(&client->dev, + "%s : charger status E(0x%02x)\n", __func__, data_e); + smb358_i2c_read(client, SMB358_THERM_CONTROL_A, &therm_control_a); + dev_dbg(&client->dev, + "%s : THERM_CONTROL_A(0x%02x)\n", __func__, therm_control_a); + smb358_i2c_read(client, SMB358_OTHER_CONTROL_A, &other_control_a); + dev_dbg(&client->dev, + "%s : OTHER_CONTROL_A(0x%02x)\n", __func__, other_control_a); + /* At least one charge cycle terminated, + * Charge current < Termination Current + */ + if (data_c & 0x20) { + /* top-off by full charging */ + status = POWER_SUPPLY_STATUS_FULL; + goto charging_status_end; + } + + /* Is enabled ? */ + if (data_c & 0x01) { + /* check for 0x06 : no charging (0b00) */ + /* not charging */ + if (!(data_c & 0x06)) { + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + goto charging_status_end; + } else { + status = POWER_SUPPLY_STATUS_CHARGING; + goto charging_status_end; + } + } else + status = POWER_SUPPLY_STATUS_DISCHARGING; + +charging_status_end: + return (int)status; +} + +static int smb358_get_charging_health(struct i2c_client *client) +{ + int health = POWER_SUPPLY_HEALTH_GOOD; + u8 data_a = 0; + u8 data_b = 0; + u8 data_c = 0; + u8 data_d = 0; + u8 data_e = 0; + + smb358_i2c_read(client, SMB358_STATUS_A, &data_a); + dev_dbg(&client->dev, + "%s : charger status A(0x%02x)\n", __func__, data_a); + smb358_i2c_read(client, SMB358_STATUS_B, &data_b); + dev_dbg(&client->dev, + "%s : charger status B(0x%02x)\n", __func__, data_b); + smb358_i2c_read(client, SMB358_STATUS_C, &data_c); + dev_dbg(&client->dev, + "%s : charger status C(0x%02x)\n", __func__, data_c); + smb358_i2c_read(client, SMB358_STATUS_D, &data_d); + dev_dbg(&client->dev, + "%s : charger status D(0x%02x)\n", __func__, data_d); + smb358_i2c_read(client, SMB358_STATUS_E, &data_e); + dev_dbg(&client->dev, + "%s : charger status E(0x%02x)\n", __func__, data_e); + smb358_i2c_read(client, SMB358_INTERRUPT_STATUS_E, &data_e); + dev_dbg(&client->dev, + "%s : charger interrupt status E(0x%02x)\n", __func__, data_e); + + if (data_e & 0x01) + health = POWER_SUPPLY_HEALTH_UNDERVOLTAGE; + else if (data_e & 0x04) + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + + return (int)health; +} + +/*static void smb358_allow_volatile_writes(struct i2c_client *client) +{ + int val, reg; + u8 data; + reg = SMB358_COMMAND_A; + val = smb358_i2c_read(client, reg, &data); + if ((val >= 0) && !(data & 0x80)) { + dev_dbg(&client->dev, + "%s : reg(0x%02x): 0x%02x", __func__, reg, data); + data |= (0x1 << 7); + if (smb358_i2c_write(client, reg, &data) < 0) + dev_err(&client->dev, "%s : error!\n", __func__); + val = smb358_i2c_read(client, reg, &data); + if (val >= 0) { + data = (u8) data; + dev_dbg(&client->dev, " => 0x%02x\n", data); + } + } +}*/ + +static u8 smb358_get_float_voltage_data(int float_voltage) +{ + u8 data; + + if (float_voltage < 3500) + data = 0; + else if(float_voltage <= 4340) + data = (float_voltage - 3500) / 20; + else if(float_voltage == 4350) + data = 0x2B; /* (4340 -3500)/20 + 1 */ + else if(float_voltage <= 4500) + data = (float_voltage - 3500) / 20 + 1; + else + data = 51; + + return data; +} + +static u8 smb358_get_input_current_limit_data( + struct sec_charger_info *charger, int input_current) +{ + u8 data; + + if (input_current <= 300) + data = 0x00; + else if (input_current <= 500) + data = 0x01; + else if (input_current <= 700) + data = 0x02; + else if (input_current <= 1000) + data = 0x03; + else if (input_current <= 1200) + data = 0x04; + else if (input_current <= 1500) + data = 0x05; + else if (input_current <= 1800) + data = 0x06; + else + data = 0x07; /* set input current to 2000mA */ + + return (data << 4); +} + +static u8 smb358_get_term_current_limit_data( + int termination_current) +{ + u8 data; + + if (termination_current <= 30) + data = 0x00; + else if (termination_current <= 40) + data = 0x01; + else if (termination_current <= 60) + data = 0x02; + else if (termination_current <= 80) + data = 0x03; + else if (termination_current <= 100) + data = 0x04; + else if (termination_current <= 125) + data = 0x05; + else if (termination_current <= 150) + data = 0x06; + else + data = 0x07; /* set termination current limit to 200mA */ + + return data; +} + +static u8 smb358_get_fast_charging_current_data( + int fast_charging_current) +{ + u8 data; + + if (fast_charging_current <= 200) + data = 0x00; + else if (fast_charging_current <= 450) + data = 0x01; + else if (fast_charging_current <= 600) + data = 0x02; + else if (fast_charging_current <= 900) + data = 0x03; + else if (fast_charging_current <= 1300) + data = 0x04; + else if (fast_charging_current <= 1500) + data = 0x05; + else if (fast_charging_current <= 1800) + data = 0x06; + else + data = 0x07; /* set fast charging current to 2000mA */ + + return data << 5; +} + +static void smb358_enter_suspend(struct i2c_client *client) +{ + u8 data = 0; + + pr_info("%s: ENTER SUSPEND\n", __func__); + smb358_update_reg(client, SMB358_COMMAND_A, 0x80); + smb358_set_command(client, SMB358_PIN_ENABLE_CONTROL, 0x18); + data = (data | 0x4); + smb358_set_command(client, SMB358_COMMAND_A, data); +} + +#if 0 +#if (defined(CONFIG_MACH_MILLET3G_EUR) || defined(CONFIG_MACH_MATISSE3G_OPEN) || defined(CONFIG_MACH_BERLUTI3G_EUR)) +static void smb358_aicl_calibrate(struct i2c_client *client) +{ + + struct sec_charger_info *charger = i2c_get_clientdata(client); + int ret = 0; + u8 data = 0; + u8 count = 0; + u8 current_initial = 0; + u8 current_final = 0; + + ret = smb358_i2c_read(client, SMB358_STATUS_E, &data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - read(%d)\n", __func__, ret); + } + + /* check If AICL complete */ + if(data & 0x10){ + current_initial = (data & 0x0F); + + if((current_initial >= 0x04) && (current_initial <= 0x05)){ + /* set 1000mA */ + ret = smb358_i2c_read(client, SMB358_INPUT_CURRENTLIMIT, &data); + data = data & 0x0F; + data = data | 0x30; + smb358_set_command(client,SMB358_INPUT_CURRENTLIMIT, data); + }else{ + /* set 1800mA */ + ret = smb358_i2c_read(client, SMB358_INPUT_CURRENTLIMIT, &data); + data = data & 0x0F; + data = data | 0x60; + smb358_set_command(client,SMB358_INPUT_CURRENTLIMIT, data); + } + + smb358_i2c_read(client, SMB358_INPUT_CURRENTLIMIT, &data); + current_final = (data & 0x0F); + + dev_err(&charger->client->dev, + "%s: AICL calibration success! input current (%dmA) -> (%dmA) ! \n", + __func__,current_initial,current_final); + return; + + }else{ + /* Incase of AICL not complete check three times */ + for(count=0 ;count < 3;count++) + { + msleep(300); + ret = smb358_i2c_read(client, SMB358_STATUS_E, &data); + if (ret < 0) { + dev_err(&client->dev, "%s: error - read(%d)\n", __func__, ret); + } + + if(data & 0x10){ + current_initial = (data & 0x0F); + + if((current_initial >= 0x04) && (current_initial <= 0x05)){ + /* set 1000mA */ + ret = smb358_i2c_read(client, SMB358_INPUT_CURRENTLIMIT, &data); + data = data & 0x0F; + data = data | 0x30; + smb358_set_command(client,SMB358_INPUT_CURRENTLIMIT, data); + }else{ + /* set 1800mA */ + ret = smb358_i2c_read(client, SMB358_INPUT_CURRENTLIMIT, &data); + data = data & 0x0F; + data = data | 0x60; + smb358_set_command(client,SMB358_INPUT_CURRENTLIMIT, data); + } + + smb358_i2c_read(client, SMB358_INPUT_CURRENTLIMIT, &data); + current_final = (data & 0x0F); + + dev_err(&charger->client->dev, + "%s: AICL calibration success! input current (%dmA) -> (%dmA) ! \n", + __func__,current_initial,current_final); + return; + }else{ + dev_err(&charger->client->dev, + "%s: AICL not complete \n",__func__); + } + } + } + + dev_err(&charger->client->dev, + "%s: AICL calibration Failed! current (%dmA) ! \n",__func__,current_initial); + return; + +} +#endif +#endif + +static void smb358_check_slow_charging(struct work_struct *work) +{ + struct sec_charger_info *charger = + container_of(work, struct sec_charger_info, slow_work.work); + + u8 i, aicl_data; + int aicl_current = 0; + union power_supply_propval val; + + if (charger->pdata->chg_functions_setting & + SEC_CHARGER_NO_GRADUAL_CHARGING_CURRENT) { + pr_err("%s: aicl is disabled\n", __func__); + return; + } + if (charger->pdata->charging_current + [charger->cable_type].input_current_limit <= SLOW_CHARGING_CURRENT_STANDARD) { + charger->is_slow_charging = true; + } else { + for(i = 0; i < 20; i++) { + if (charger->cable_type == + POWER_SUPPLY_TYPE_BATTERY) { + pr_info("%s: cable is removed\n", __func__); + return; + } + msleep(200); + smb358_i2c_read(charger->client, SMB358_STATUS_E, &aicl_data); + if (aicl_data & 0x10) { /* check AICL complete */ + break; + } + if (i == 10) { + pr_info("%s: aicl not complete, retry\n", __func__); + /* disable AICL */ + smb358_set_command(charger->client, + SMB358_VARIOUS_FUNCTIONS, 0x81); + /* enable AICL */ + smb358_set_command(charger->client, + SMB358_VARIOUS_FUNCTIONS, 0x95); + } + } + + aicl_data &= 0xF; /* get only AICL result field */ + switch (aicl_data) { + case 0: /* AICL result 300mA */ + aicl_current = 300; + break; + case 1: /* AICL result 500mA */ + aicl_current = 500; + break; + case 2: /* AICL result 700mA */ + aicl_current = 700; + break; + case 3: /* AICL result 1000mA */ + aicl_current = 1000; + break; + case 4: /* AICL result 1200mA */ + aicl_current = 1200; + break; + case 5: /* AICL result 1300mA */ + aicl_current = 1300; + break; + case 6: /* AICL result 1800mA */ + aicl_current = 1800; + break; + case 7: /* AICL result 2000mA */ + aicl_current = 2000; + break; + default: /* etc */ + aicl_current = 2000; + break; + } + if (aicl_current <= SLOW_CHARGING_CURRENT_STANDARD) + charger->is_slow_charging = true; + else + charger->is_slow_charging = false; + } + + pr_info("%s: Slow(%d), aicl_current(%d), input_current(%d)\n", + __func__, charger->is_slow_charging, aicl_current, charger->pdata->charging_current + [charger->cable_type].input_current_limit); + + psy_do_property("battery", set, + POWER_SUPPLY_PROP_CHARGE_TYPE, val); + +} +static void smb358_charger_function_control( + struct i2c_client *client) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + union power_supply_propval val, input_value; + int status; + u8 data; + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_STATUS, input_value); + status = input_value.intval; + + charger->charging_current_max = + charger->pdata->charging_current[ + charger->cable_type].input_current_limit; + + charger->charging_current = + charger->pdata->charging_current[ + charger->cable_type].fast_charging_current; + + if (charger->charging_current < 0) { + dev_dbg(&client->dev, + "%s : OTG is activated. Ignore command!\n", __func__); + return; + } + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_HEALTH, input_value); + if (input_value.intval == + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { + pr_info("[SMB358] Unspec_failure, charger suspend\n"); + smb358_enter_suspend(client); + } + else if (charger->cable_type == + POWER_SUPPLY_TYPE_BATTERY) { + /* Charger Disabled */ + smb358_set_command(client, SMB358_COMMAND_A, 0xc0); + if ((status == POWER_SUPPLY_STATUS_FULL) ||\ + (input_value.intval == POWER_SUPPLY_HEALTH_OVERHEAT) ||\ + (input_value.intval == POWER_SUPPLY_HEALTH_COLD)) + return; + pr_info("[SMB358] Set the registers to the default configuration\n"); + /* Set the registers to the default configuration */ + smb358_set_command(client, SMB358_CHARGE_CURRENT, 0xFE); + smb358_set_command(client, SMB358_INPUT_CURRENTLIMIT, 0x74); + smb358_set_command(client, SMB358_VARIOUS_FUNCTIONS, 0xD7); + data = 0x00; + data |= smb358_get_float_voltage_data(charger->pdata->chg_float_voltage); + smb358_set_command(client, SMB358_FLOAT_VOLTAGE, data); + /* Disable Automatic Recharge */ + smb358_set_command(client, SMB358_CHARGE_CONTROL, 0x84); + smb358_set_command(client, SMB358_PIN_ENABLE_CONTROL, 0x09); + smb358_set_command(client, SMB358_THERM_CONTROL_A, 0xF0); + smb358_set_command(client, SMB358_SYSOK_USB30_SELECTION, 0x08); + smb358_set_command(client, SMB358_OTHER_CONTROL_A, 0x01); + smb358_set_command(client, SMB358_OTG_TLIM_THERM_CONTROL, 0xF6); + smb358_set_command(client, SMB358_LIMIT_CELL_TEMPERATURE_MONITOR, 0xA5); + smb358_set_command(client, SMB358_STATUS_INTERRUPT, 0x00); + smb358_set_command(client, SMB358_COMMAND_B, 0x00); + if (charger->pdata->chg_irq) { + smb358_set_command(client, SMB358_STAT_TIMERS_CONTROL, 0x1F); + smb358_set_command(client, SMB358_FAULT_INTERRUPT, 0x0C); + } else { + smb358_set_command(client, SMB358_STAT_TIMERS_CONTROL, 0x0F); + smb358_set_command(client, SMB358_FAULT_INTERRUPT, 0x00); + } + + } else { + int full_check_type; + 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; + + smb358_i2c_read(client, SMB358_COMMAND_A, &data); + + if ((data & 0x10) && charger->pdata->vbus_ctrl_gpio) { + int level; + /* disable otg & charging */ + smb358_clear_reg(client, SMB358_COMMAND_A, 0x12); + + /* turn off vbus */ + gpio_set_value(charger->pdata->vbus_ctrl_gpio, 1); + msleep(30); + + level = gpio_get_value_cansleep(charger->pdata->vbus_ctrl_gpio); + pr_info("[SMB358] vbus ctrl gpio %d level %d\n", charger->pdata->vbus_ctrl_gpio, level); + + /* turn on vbus */ + gpio_set_value(charger->pdata->vbus_ctrl_gpio, 0); + } + /* [STEP - 1] ================================================ + * Volatile write permission(bit 7) - allow(1) + * Charging Enable(bit 1) - Disabled(0, default) + * STAT Output(bit 0) - Enabled(0) + */ + if (data & 0x02) { + u8 status_c; + u8 status_e; + smb358_i2c_read(client, SMB358_STATUS_C, &status_c); + smb358_i2c_read(client, SMB358_STATUS_E, &status_e); + pr_info("[SMB358] status_c: 0x%x, status_e: 0x%x\n", status_c, status_e); + + /* no charge or aicl not complete*/ + if (((status_c & 0x06) == 0) || (status_e & 0x10) == 0) + smb358_set_command(client, + SMB358_COMMAND_A, 0xC0); + } + + /* [STEP - 2] ================================================ + * USB 5/1(9/1.5) Mode(bit 1) - USB1/USB1.5(0), USB5/USB9(1) + * USB/HC Mode(bit 0) - USB5/1 or USB9/1.5 Mode(0) + * High-Current Mode(1) + */ + switch (charger->cable_type) { + case POWER_SUPPLY_TYPE_UNKNOWN: + case POWER_SUPPLY_TYPE_MAINS: + case POWER_SUPPLY_TYPE_USB_CDP: + case POWER_SUPPLY_TYPE_MISC: + case POWER_SUPPLY_TYPE_WIRELESS: + case POWER_SUPPLY_TYPE_CARDOCK: + case POWER_SUPPLY_TYPE_UARTOFF: + case POWER_SUPPLY_TYPE_LAN_HUB: + case POWER_SUPPLY_TYPE_MHL_900: + case POWER_SUPPLY_TYPE_MHL_1500: + case POWER_SUPPLY_TYPE_SMART_NOTG: + /* High-current mode */ + data = 0x03; + break; + case POWER_SUPPLY_TYPE_UPS: + case POWER_SUPPLY_TYPE_USB: + case POWER_SUPPLY_TYPE_USB_DCP: + case POWER_SUPPLY_TYPE_USB_ACA: + case POWER_SUPPLY_TYPE_MHL_500: + case POWER_SUPPLY_TYPE_MHL_USB: + case POWER_SUPPLY_TYPE_SMART_OTG: + case POWER_SUPPLY_TYPE_POWER_SHARING: + /* USB5 */ + data = 0x02; + break; + default: + /* USB1 */ + data = 0x00; + break; + } + smb358_set_command(client, + SMB358_COMMAND_B, data); + + + /* [STEP 3] Charge Current(0x00) =============================== + * Set pre-charge current(bit 4:3) - 450mA(11) + * Set fast charge current(bit 7:5) + * Set termination current(bit 2:0) + */ + if (charger->siop_level < 100) { + charger->charging_current = + charger->charging_current * charger->siop_level / 100; + } + dev_info(&client->dev, + "%s : fast charging current (%dmA)\n", + __func__, charger->charging_current); + + data = 0x18; + data |= smb358_get_fast_charging_current_data( + charger->charging_current); + switch (full_check_type) { + case SEC_BATTERY_FULLCHARGED_CHGGPIO: + case SEC_BATTERY_FULLCHARGED_CHGINT: + case SEC_BATTERY_FULLCHARGED_CHGPSY: + if (val.intval == SEC_BATTERY_CHARGING_1ST) { + dev_info(&client->dev, + "%s : termination current (%dmA)\n", + __func__, charger->pdata->charging_current[ + charger->cable_type].full_check_current_1st); + data |= smb358_get_term_current_limit_data( + charger->pdata->charging_current[ + charger->cable_type].full_check_current_1st); + } else { + dev_info(&client->dev, + "%s : termination current (%dmA)\n", + __func__, charger->pdata->charging_current[ + charger->cable_type].full_check_current_2nd); + data |= smb358_get_term_current_limit_data( + charger->pdata->charging_current[ + charger->cable_type].full_check_current_2nd); + } + break; + } + smb358_set_command(client, + SMB358_CHARGE_CURRENT, data); + + /* [STEP - 4] ================================================= + * Enable(EN) Pin Control(bit 6) - i2c(0), Pin(1) + * Pin control(bit 5) - active high(0), active low(1) + * USB5/1/HC input State(bit3) - Dual-state input(1) + * USB Input Pre-bias(bit 0) - Enable(1) + */ + data = 0x09; + if (charger->pdata->chg_gpio_en) + data |= 0x40; + if (charger->pdata->chg_polarity_en) + data |= 0x20; + smb358_set_command(client, + SMB358_PIN_ENABLE_CONTROL, data); + + /* [STEP - 5] =============================================== */ + dev_info(&client->dev, "%s : input current (%dmA)\n", + __func__, charger->pdata->charging_current + [charger->cable_type].input_current_limit); + /* Input current limit */ + data = 0x00; + data |= smb358_get_input_current_limit_data( + charger, + charger->pdata->charging_current + [charger->cable_type].input_current_limit); + smb358_set_command(client, + SMB358_INPUT_CURRENTLIMIT, data); + + /* [STEP - 6] ================================================= + * Input to System FET(bit 7) - Controlled by Register(1) + * Max System voltage(bit 5) - Vflt + 0.1v(0) + * AICL(bit 4) - Enabled(1) + * VCHG Function(bit 0) - Enabled(1) + */ + if (charger->pdata->chg_functions_setting & + SEC_CHARGER_NO_GRADUAL_CHARGING_CURRENT) + /* disable AICL */ + smb358_set_command(client, + SMB358_VARIOUS_FUNCTIONS, 0x81); + else { + /* disable AICL */ + smb358_set_command(client, + SMB358_VARIOUS_FUNCTIONS, 0x81); + /* enable AICL */ + smb358_set_command(client, + SMB358_VARIOUS_FUNCTIONS, 0x95); + } + + /* [STEP - 7] ================================================= + * Pre-charged to Fast-charge Voltage Threshold(Bit 7:6) - 2.3V + * Float Voltage(bit 5:0) + */ + dev_dbg(&client->dev, "%s : float voltage (%dmV)\n", + __func__, charger->pdata->chg_float_voltage); + data = 0x00; + data |= smb358_get_float_voltage_data( + charger->pdata->chg_float_voltage); + smb358_set_command(client, + SMB358_FLOAT_VOLTAGE, data); + + /* [STEP - 8] ================================================= + * Charge control + * Automatic Recharge disable(bit 7), + * Current Termination disable(bit 6), + * BMD disable(bit 5:4), + * INOK Output Configuration : Push-pull(bit 3) + * AICL glitch filter duration : 20msec(bit 0) + * APSD disable + */ + data = 0xC0; + + switch (full_check_type) { + case SEC_BATTERY_FULLCHARGED_CHGGPIO: + case SEC_BATTERY_FULLCHARGED_CHGINT: + case SEC_BATTERY_FULLCHARGED_CHGPSY: + /* Enable Current Termination */ + data &= 0xBF; + break; + } + smb358_set_command(client, + SMB358_CHARGE_CONTROL, data); + + /* [STEP - 9] ================================================= + * STAT active low(bit 7), + * Complete charge Timeout(bit 3:2) - Disabled(11) + * Pre-charge Timeout(bit 1:0) - Disable(11) + */ + smb358_set_command(client, + SMB358_STAT_TIMERS_CONTROL, 0x1F); + +#if defined(CONFIG_MACH_MATISSELTE_VZW) + /* [STEP - 10] ================================================= + * Mininum System Voltage(bit 6) - 3.15v(0) + * Therm monitor(bit 4) - Disabled(1) + * Soft Cold/Hot Temp Limit Behavior(bit 3:2, bit 1:0) - + * Charger Current + Float voltage Compensation(11) + */ + smb358_set_command(client, + SMB358_THERM_CONTROL_A, 0xB0); + + /* [STEP - 11] ================================================ + * OTG/ID Pin Control(bit 7:6) - RID Disabled, OTG I2c(00) + * Minimum System Voltage(bit 4) - 3.15V(0) + * Low-Battery/SYSOK Voltage threshold(bit 3:0) - 2.5V(0001) + * if this bit is disabled, + * input current for system will be disabled + */ + smb358_set_command(client, + SMB358_OTHER_CONTROL_A, 0x11); +#elif defined(CONFIG_MACH_CHAGALL_USC) + /* [STEP - 10] ================================================= + * Mininum System Voltage(bit 6) - 3.45v(0) + * Therm monitor(bit 4) - Disabled(1) + * Soft Cold/Hot Temp Limit Behavior(bit 3:2, bit 1:0) - + * Charger Current + Float voltage Compensation(11) + */ + smb358_set_command(client, + SMB358_THERM_CONTROL_A, 0xB0); + + /* [STEP - 11] ================================================ + * OTG/ID Pin Control(bit 7:6) - RID Disabled, OTG I2c(00) + * Minimum System Voltage(bit 4) - 3.45V(0) + * Low-Battery/SYSOK Voltage threshold(bit 3:0) - 2.5V(0001) + * if this bit is disabled, + * input current for system will be disabled + */ + smb358_set_command(client, + SMB358_OTHER_CONTROL_A, 0x01); +#else +{ + u8 therm_control_a, other_control_a; + /* [STEP - 10] ================================================= + * Mininum System Voltage(bit 6) + * 3.15V(0), 3.45V(0), 3.60V(1), 3.75V(1) + * Therm monitor(bit 4) - Disabled(1) + * Soft Cold/Hot Temp Limit Behavior(bit 3:2, bit 1:0) - + * Charger Current + Float voltage Compensation(11) + */ + smb358_i2c_read(client, SMB358_THERM_CONTROL_A, &therm_control_a); + pr_info("%s : THERM_CONTROL_A(0x%02x)\n", __func__, therm_control_a); + smb358_i2c_read(client, SMB358_OTHER_CONTROL_A, &other_control_a); + pr_info("%s : OTHER_CONTROL_A(0x%02x)\n", __func__, other_control_a); + + pr_info("%s: chg_min_system_voltage(%d)\n", __func__, charger->pdata->chg_min_system_voltage); + + if (charger->pdata->chg_min_system_voltage <= 3150) { + therm_control_a &= ~(0x1 << 6); + other_control_a |= 0x1 << 4; + } else if (charger->pdata->chg_min_system_voltage <= 3450) { + therm_control_a &= ~(0x1 << 6); + other_control_a &= ~(0x1 << 4); + } else if (charger->pdata->chg_min_system_voltage <= 3600) { + therm_control_a |= 0x1 << 6; + other_control_a &= ~(0x1 << 4); + } else { + therm_control_a |= 0x1 << 6; + other_control_a |= 0x1 << 4; + } + + pr_info("%s: therm_control_a(0x%02x)\n", __func__, therm_control_a); + pr_info("%s: other_control_a(0x%02x)\n", __func__, other_control_a); + + smb358_set_command(client, + SMB358_THERM_CONTROL_A, therm_control_a); + + /* [STEP - 11] ================================================ + * OTG/ID Pin Control(bit 7:6) - RID Disabled, OTG I2c(00) + * Minimum System Voltage(bit 4) + * 3.15V(1), 3.45V(0), 3.60V(0), 3.75(1) + * Low-Battery/SYSOK Voltage threshold(bit 3:0) - 2.5V(0001) + * if this bit is disabled, + * input current for system will be disabled + */ + smb358_set_command(client, + SMB358_OTHER_CONTROL_A, other_control_a); +} +#endif + + /* [STEP - 12] ================================================ + * Charge Current Compensation(bit 7:6) - 200mA(00) + * Digital Thermal Regulation Threshold(bit 5:4) - 130c + * OTG current Limit at USBIN(Bit 3:2) - 900mA(11) + * OTG Battery UVLO Threshold(Bit 1:0) - 3.3V(11) + */ + smb358_set_command(client, + SMB358_OTG_TLIM_THERM_CONTROL, 0x3F); + + /* [STEP - 13] ================================================ + * Hard/Soft Limit Cell temp monitor + */ + smb358_set_command(client, + SMB358_LIMIT_CELL_TEMPERATURE_MONITOR, 0x01); + + /* [STEP - 14] ================================================ + * FAULT interrupt - Disabled for non chg_irq, OVP enabled for chg_irq + */ + if (charger->pdata->chg_irq) { + smb358_set_command(client, + SMB358_FAULT_INTERRUPT, 0x0C); + } else { + smb358_set_command(client, + SMB358_FAULT_INTERRUPT, 0x00); + } + + /* [STEP - 15] ================================================ + * STATUS interrupt - Clear + */ + smb358_set_command(client, + SMB358_STATUS_INTERRUPT, 0x00); + + /* [STEP - 16] ================================================ + * Volatile write permission(bit 7) - allowed(1) + * Charging Enable(bit 1) - Enabled(1) + * STAT Output(bit 0) - Enabled(0) + */ + smb358_set_command(client, + SMB358_COMMAND_A, 0xC2); + + schedule_delayed_work(&charger->slow_work, 0); + } +#if 0 +#if (defined(CONFIG_MACH_MILLET3G_EUR) || defined(CONFIG_MACH_MATISSE3G_OPEN) || defined(CONFIG_MACH_BERLUTI3G_EUR)) + /* Allow time for AICL to complete */ + msleep(1000); + smb358_aicl_calibrate(client); +#endif +#endif +} + +static void smb358_charger_otg_control( + struct i2c_client *client) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + + if (charger->cable_type == + POWER_SUPPLY_TYPE_BATTERY) { + dev_info(&client->dev, "%s : turn off OTG\n", __func__); + + /* disable otg */ + smb358_clear_reg(client, SMB358_COMMAND_A, 0x10); + } else { + /* Change "OTG output current limit" to 250mA */ + smb358_clear_reg(client, SMB358_OTG_TLIM_THERM_CONTROL, 0x0C); + + /* OTG Enabled*/ + smb358_update_reg(client, SMB358_COMMAND_A, 0x10); + + dev_info(&client->dev, "%s : turn on OTG\n", __func__); + smb358_set_command(client, SMB358_COMMAND_B, 0x00); + + /* Change "OTG output current limit" to 500mA */ + smb358_update_reg(client, SMB358_OTG_TLIM_THERM_CONTROL, 0x84); + } + +} + +static void smb358_set_charging_current( + struct i2c_client *client, int charging_current) +{ + u8 data; + + smb358_clear_reg(client, SMB358_COMMAND_A, 0x02); + + if (!charging_current) + return; + + smb358_i2c_read(client, SMB358_CHARGE_CURRENT, &data); + data &= 0x1f; + data |= smb358_get_fast_charging_current_data(charging_current); + smb358_set_command(client, SMB358_CHARGE_CURRENT, data); + + smb358_update_reg(client, SMB358_COMMAND_A, 0x02); +} + +static void smb358_set_charging_input_current_limit( + struct i2c_client *client, int input_current_limit) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + u8 data; + + /* Input current limit */ + data = 0; + data = smb358_get_input_current_limit_data( + charger, input_current_limit); + smb358_set_command(client, SMB358_INPUT_CURRENTLIMIT, data); +} + +void smb358_charger_shutdown(struct i2c_client *client) +{ + pr_info("%s: smb358 Charging Disabled\n", __func__); + + smb358_set_command(client, SMB358_THERM_CONTROL_A, 0xF0); + smb358_set_command(client, SMB358_COMMAND_A, 0x80); + smb358_volatile_writes(client, SMB358_DISABLE_WRITE); +} + +static int smb358_debugfs_show(struct seq_file *s, void *data) +{ + struct sec_charger_info *charger = s->private; + u8 reg; + u8 reg_data; + + seq_printf(s, "SMB CHARGER IC :\n"); + seq_printf(s, "==================\n"); + for (reg = 0x00; reg <= 0x0E; reg++) { + smb358_i2c_read(charger->client, reg, ®_data); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); + } + + for (reg = 0x30; reg <= 0x3F; reg++) { + smb358_i2c_read(charger->client, reg, ®_data); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); + } + + seq_printf(s, "\n"); + return 0; +} + +static int smb358_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, smb358_debugfs_show, inode->i_private); +} + +static const struct file_operations smb358_debugfs_fops = { + .open = smb358_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +bool smb358_hal_chg_init(struct i2c_client *client) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + + dev_info(&client->dev, + "%s: SMB358 Charger init(Start)!!\n", __func__); + + smb358_volatile_writes(client, SMB358_ENABLE_WRITE); + + /*smb358_test_read(client);*/ + (void) debugfs_create_file("smb358_regs", + S_IRUGO, NULL, (void *)charger, &smb358_debugfs_fops); + + return true; +} + +bool smb358_hal_chg_suspend(struct i2c_client *client) +{ + return true; +} + +bool smb358_hal_chg_resume(struct i2c_client *client) +{ + return true; +} + +bool smb358_hal_chg_get_property(struct i2c_client *client, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + u8 data; + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = smb358_get_charging_status(client); + break; + + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (charger->is_charging) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + if (charger->is_slow_charging) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_SLOW; + pr_info("%s: slow-charging mode\n", __func__); + } + } + else + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + + case POWER_SUPPLY_PROP_HEALTH: + val->intval = smb358_get_charging_health(client); + break; + /* calculated input current limit value */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CURRENT_AVG: /* charging current */ + if (charger->charging_current) { + smb358_i2c_read(client, SMB358_STATUS_B, &data); + if (data & 0x20) + switch (data & 0x07) { + case 0: + val->intval = 100; + break; + case 1: + val->intval = 200; + break; + case 2: + val->intval = 450; + break; + case 3: + val->intval = 600; + break; + case 4: + val->intval = 900; + break; + case 5: + val->intval = 1300; + break; + case 6: + val->intval = 1500; + break; + case 7: + val->intval = 1800; + break; + } + else + switch ((data & 0x18) >> 3) { + case 0: + val->intval = 100; + break; + case 1: + val->intval = 150; + break; + case 2: + val->intval = 200; + break; + case 3: + val->intval = 250; + break; + } + } else + val->intval = 0; + dev_dbg(&client->dev, + "%s : set-current(%dmA), current now(%dmA)\n", + __func__, charger->charging_current, val->intval); + break; + default: + return false; + } + return true; +} + +bool smb358_hal_chg_set_property(struct i2c_client *client, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + + switch (psp) { + /* val->intval : type */ + case POWER_SUPPLY_PROP_ONLINE: + if (val->intval == POWER_SUPPLY_TYPE_POWER_SHARING) { + union power_supply_propval ps_status; + psy_do_property("ps", get, + POWER_SUPPLY_PROP_STATUS, ps_status); + if (ps_status.intval) { + charger->cable_type = POWER_SUPPLY_TYPE_OTG; + pr_info("%s: ps enable\n", __func__); + } else { + charger->cable_type = POWER_SUPPLY_TYPE_BATTERY; + pr_info("%s: ps disable\n", __func__); + } + } + if (charger->cable_type == POWER_SUPPLY_TYPE_OTG) { + smb358_charger_otg_control(client); + } else if (charger->cable_type == POWER_SUPPLY_TYPE_BATTERY) { + smb358_charger_function_control(client); + smb358_charger_otg_control(client); + } else { + smb358_charger_function_control(client); + } + /* smb358_test_read(client); */ + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: /* input current limit set */ + /* calculated input current limit value */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + smb358_set_charging_input_current_limit(client, val->intval); + break; + /* val->intval : charging current */ + case POWER_SUPPLY_PROP_CURRENT_AVG: + smb358_set_charging_current(client, val->intval); + break; + default: + return false; + } + return true; +} + +ssize_t smb358_hal_chg_show_attrs(struct device *dev, + const ptrdiff_t offset, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_charger_info *chg = + container_of(psy, struct sec_charger_info, psy_chg); + int i = 0; + char *str = NULL; + + switch (offset) { + case CHG_DATA: + i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", + chg->reg_data); + break; + case CHG_REGS: + str = kzalloc(sizeof(char)*1024, GFP_KERNEL); + if (!str) + return -ENOMEM; + + smb358_read_regs(chg->client, str); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + str); + + kfree(str); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t smb358_hal_chg_store_attrs(struct device *dev, + const ptrdiff_t offset, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_charger_info *chg = + container_of(psy, struct sec_charger_info, psy_chg); + int ret = 0; + int x = 0; + u8 data = 0; + + switch (offset) { + case CHG_REG: + if (sscanf(buf, "%x\n", &x) == 1) { + chg->reg_addr = x; + smb358_i2c_read(chg->client, + chg->reg_addr, &data); + chg->reg_data = data; + dev_dbg(dev, "%s: (read) addr = 0x%x, data = 0x%x\n", + __func__, chg->reg_addr, chg->reg_data); + ret = count; + } + break; + case CHG_DATA: + if (sscanf(buf, "%x\n", &x) == 1) { + data = (u8)x; + dev_dbg(dev, "%s: (write) addr = 0x%x, data = 0x%x\n", + __func__, chg->reg_addr, data); + smb358_i2c_write(chg->client, + chg->reg_addr, &data); + ret = count; + } + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static struct device_attribute smb358_charger_attrs[] = { + SMB358_CHARGER_ATTR(reg), + SMB358_CHARGER_ATTR(data), + SMB358_CHARGER_ATTR(regs), +}; + +static enum power_supply_property smb358_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, +}; + +static int smb358_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); + u8 data = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: /* input current limit set */ + smb358_i2c_read(charger->client, SMB358_STATUS_E, &data); + if (data & 0x10) { + int aicl_result = smb358_get_aicl_current(data); + dev_info(&charger->client->dev, + "%s : AICL completed (%dmA)\n", __func__, aicl_result); + charger->charging_current_max = aicl_result; + } else { + dev_info(&charger->client->dev, + "%s : AICL is not completed \n", __func__); + charger->charging_current_max = 300; + } + val->intval = charger->charging_current_max; + break; + + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_HEALTH: + case POWER_SUPPLY_PROP_CURRENT_AVG: /* charging current */ + /* calculated input current limit value */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (!smb358_hal_chg_get_property(charger->client, psp, val)) + return -EINVAL; + break; + default: + return -EINVAL; + } + return 0; +} + +static int smb358_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); + union power_supply_propval input_value; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + charger->status = val->intval; + break; + + /* val->intval : type */ + case POWER_SUPPLY_PROP_ONLINE: + charger->cable_type = val->intval; + if (val->intval == POWER_SUPPLY_TYPE_BATTERY || \ + val->intval == POWER_SUPPLY_TYPE_OTG || \ + val->intval == POWER_SUPPLY_TYPE_POWER_SHARING) { + charger->is_charging = false; + charger->is_slow_charging = false; + } + else + charger->is_charging = true; + + if (!smb358_hal_chg_set_property(charger->client, psp, val)) + return -EINVAL; + break; + + /* val->intval : input current limit set */ + case POWER_SUPPLY_PROP_CURRENT_MAX: + charger->charging_current_max = val->intval; + /* to control charging current, + * use input current limit and set charging current as much as possible + * so we only control input current limit to control charge current + */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (!smb358_hal_chg_set_property(charger->client, psp, val)) + return -EINVAL; + break; + + /* val->intval : charging current */ + case POWER_SUPPLY_PROP_CURRENT_AVG: + charger->charging_current = val->intval; + + if (!smb358_hal_chg_set_property(charger->client, psp, val)) + return -EINVAL; + break; + + /* val->intval : SIOP level (%) + * SIOP charging current setting + */ + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + charger->siop_level = val->intval; + if (charger->is_charging) { + /* change val as charging current by SIOP level + * do NOT change initial charging current setting + */ + input_value.intval = + charger->pdata->charging_current[ + charger->cable_type].fast_charging_current * val->intval / 100; + + /* charging current should be over than USB charging current */ + if (charger->pdata->chg_functions_setting & + SEC_CHARGER_MINIMUM_SIOP_CHARGING_CURRENT) { + if (input_value.intval > 0 && + input_value.intval < + charger->pdata->charging_current[ + POWER_SUPPLY_TYPE_USB].fast_charging_current) + input_value.intval = + charger->pdata->charging_current[ + POWER_SUPPLY_TYPE_USB].fast_charging_current; + } + + /* set charging current as new value */ + if (!smb358_hal_chg_set_property(charger->client, + POWER_SUPPLY_PROP_CURRENT_AVG, &input_value)) + return -EINVAL; + } + break; + + default: + return -EINVAL; + } + return 0; +} + +static void smb358_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 (!smb358_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 (!smb358_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__); + /* Do not set POWER_SUPPLY_PROP_HEALTH + * excute monitor work again. + * ovp/uvlo is checked by polling + */ + psy_do_property("battery", set, + POWER_SUPPLY_PROP_CHARGE_TYPE, 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__); + /* Do not set POWER_SUPPLY_PROP_HEALTH + * excute monitor work again. + * ovp/uvlo is checked by polling + */ + psy_do_property("battery", set, + POWER_SUPPLY_PROP_CHARGE_TYPE, val); + break; + + case POWER_SUPPLY_HEALTH_UNKNOWN: + default: + dev_err(&charger->client->dev, + "%s: Invalid Charger Health\n", __func__); + break; + } + } + + if (charger->pdata->cable_check_type & SEC_BATTERY_CABLE_CHECK_CHGINT) { + if (!smb358_hal_chg_get_property(charger->client, + POWER_SUPPLY_PROP_ONLINE, &val)) + return; + + /* use SEC_BATTERY_CABLE_SOURCE_EXTERNAL for cable_source_type + * charger would call battery driver to set ONLINE property + * check battery driver loaded or not + */ + if (get_power_supply_by_name("battery")) { + psy_do_property("battery", set, + POWER_SUPPLY_PROP_ONLINE, val); + } else { + if (charger->pdata->check_cable_result_callback) + charger->pdata->check_cable_result_callback(val.intval); + } + } +} + +static irqreturn_t smb358_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 smb358_chg_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(smb358_charger_attrs); i++) { + rc = device_create_file(dev, &smb358_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, &smb358_charger_attrs[i]); +create_attrs_succeed: + return rc; +} + +ssize_t smb358_chg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const ptrdiff_t offset = attr - smb358_charger_attrs; + int i = 0; + + switch (offset) { + case CHG_REG: + case CHG_DATA: + case CHG_REGS: + i = smb358_hal_chg_show_attrs(dev, offset, buf); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t smb358_chg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + const ptrdiff_t offset = attr - smb358_charger_attrs; + int ret = 0; + + switch (offset) { + case CHG_REG: + case CHG_DATA: + ret = smb358_hal_chg_store_attrs(dev, offset, buf, count); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +#ifdef CONFIG_OF +static int smb358_charger_read_u32_index_dt(const struct device_node *np, + const char *propname, + u32 index, u32 *out_value) +{ + struct property *prop = of_find_property(np, propname, NULL); + u32 len = (index + 1) * sizeof(*out_value); + + if (!prop) + return (-EINVAL); + if (!prop->value) + return (-ENODATA); + if (len > prop->length) + return (-EOVERFLOW); + + *out_value = be32_to_cpup(((__be32 *)prop->value) + index); + + return 0; +} +extern unsigned int system_rev; +static int smb358_charger_parse_dt(struct device *dev, + struct sec_charger_info *charger) +{ + struct device_node *np = dev->of_node; + sec_battery_platform_data_t *pdata = charger->pdata; + int ret = 0; + int i, len; + const u32 *p; + + if (np == NULL) { + pr_err("%s np NULL\n", __func__); + return -1; + } else { +#if defined(CONFIG_MACH_VIENNAVZW) || defined(CONFIG_MACH_VIENNAATT) + if (system_rev >= 0xC) + pdata->vbus_ctrl_gpio = 28; + else + pdata->vbus_ctrl_gpio = 0; + pr_info("%s reading vbus_ctrl_gpio = %d\n", + __func__, pdata->vbus_ctrl_gpio); +#else + ret = of_get_named_gpio(np, "charger,vbus_ctrl_gpio", 0); + if (ret > 0) { + pdata->vbus_ctrl_gpio = ret; + pr_info("%s reading vbus_ctrl_gpio = %d\n", __func__, ret); + } else { + pdata->vbus_ctrl_gpio = 0; + pr_info("%s vbus_ctrl_gpio read fail\n", __func__); + } +#endif + + np = of_find_node_by_name(NULL, "sec-battery"); + if (!np) { + pr_err("%s np NULL\n", __func__); + } + ret = of_property_read_u32(np, "battery,chg_float_voltage", + &pdata->chg_float_voltage); + if (ret < 0) + pr_err("%s: chg_float_voltage read failed (%d)\n", __func__, ret); + + ret = of_property_read_u32(np, "battery,chg_min_system_voltage", + &pdata->chg_min_system_voltage); + if (ret < 0) { + pr_err("%s: chg_min_system_voltage read failed (%d)\n", __func__, ret); + pdata->chg_min_system_voltage = 3750; + pr_err("%s: chg_min_system_voltage is used as default setting (%d)\n", + __func__, pdata->chg_min_system_voltage); + } + + ret = of_property_read_u32(np, "battery,ovp_uvlo_check_type", + &pdata->ovp_uvlo_check_type); + if (ret < 0) + pr_err("%s: ovp_uvlo_check_type read failed (%d)\n", __func__, ret); + + ret = of_get_named_gpio(np, "battery,chg_int", 0); + if (ret > 0) { + pdata->chg_irq = gpio_to_irq(ret); + pr_info("%s reading chg_int_gpio = %d\n", __func__, ret); + } else { + pr_info("%s reading chg_int_gpio is empty\n", __func__); + } + + ret = of_property_read_u32(np, "battery,chg_irq_attr", + (unsigned int *)&pdata->chg_irq_attr); + if (ret) + pr_info("%s: chg_irq_attr is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_check_type", + &pdata->full_check_type); + if (ret < 0) + pr_err("%s: full_check_type read failed (%d)\n", __func__, ret); + + p = of_get_property(np, "battery,input_current_limit", &len); + len = len / sizeof(u32); + pdata->charging_current = kzalloc(sizeof(sec_charging_current_t) * len, + GFP_KERNEL); + + for(i = 0; i < len; i++) { + ret = smb358_charger_read_u32_index_dt(np, + "battery,input_current_limit", i, + &pdata->charging_current[i].input_current_limit); + ret = smb358_charger_read_u32_index_dt(np, + "battery,fast_charging_current", i, + &pdata->charging_current[i].fast_charging_current); + ret = smb358_charger_read_u32_index_dt(np, + "battery,full_check_current_1st", i, + &pdata->charging_current[i].full_check_current_1st); + ret = smb358_charger_read_u32_index_dt(np, + "battery,full_check_current_2nd", i, + &pdata->charging_current[i].full_check_current_2nd); + } + } + return ret; +} +#else +static int smb358_charger_parse_dt(struct max77803_charger_data *charger) +{ + return 0; +} +#endif + +static int smb358_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_info(&client->dev, + "%s: SMB358 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; + if (client->dev.of_node) { + void * pdata = kzalloc(sizeof(sec_battery_platform_data_t), GFP_KERNEL); + if (!pdata) + goto err_free1; + charger->pdata = pdata; + if (smb358_charger_parse_dt(&client->dev, charger)) + dev_err(&client->dev, + "%s : Failed to get charger dt\n", __func__); + } else + charger->pdata = client->dev.platform_data; + + i2c_set_clientdata(client, charger); + + charger->siop_level = 100; + charger->psy_chg.name = "sec-charger"; + charger->psy_chg.type = POWER_SUPPLY_TYPE_UNKNOWN; + charger->psy_chg.get_property = smb358_chg_get_property; + charger->psy_chg.set_property = smb358_chg_set_property; + charger->psy_chg.properties = smb358_charger_props; + charger->psy_chg.num_properties = ARRAY_SIZE(smb358_charger_props); + charger->is_slow_charging = false; + + if (charger->pdata->chg_gpio_init) { + if (!charger->pdata->chg_gpio_init()) { + dev_err(&client->dev, + "%s: Failed to Initialize GPIO\n", __func__); + goto err_free; + } + } + + if (!smb358_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; + } + + INIT_DELAYED_WORK(&charger->slow_work, + smb358_check_slow_charging); + + if (charger->pdata->chg_irq) { + INIT_DELAYED_WORK( + &charger->isr_work, smb358_chg_isr_work); + + ret = request_threaded_irq(charger->pdata->chg_irq, + NULL, smb358_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 = smb358_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_info(&client->dev, + "%s: SMB358 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->pdata); +err_free1: + kfree(charger); + + return ret; +} + +static int smb358_charger_remove( + struct i2c_client *client) +{ + return 0; +} + +static int smb358_charger_suspend(struct i2c_client *client, + pm_message_t state) +{ + if (!smb358_hal_chg_suspend(client)) + dev_err(&client->dev, + "%s: Failed to Suspend Charger\n", __func__); + + return 0; +} + +static int smb358_charger_resume(struct i2c_client *client) +{ + dev_info(&client->dev,"%s: start\n", __func__); + + if (!smb358_hal_chg_resume(client)) + dev_err(&client->dev, + "%s: Failed to Resume Charger\n", __func__); + + return 0; +} + +static const struct i2c_device_id smb358_charger_id[] = { + {"smb358", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, smb358_charger_id); +static struct of_device_id smb358_i2c_match_table[] = { + { .compatible = "smb358,i2c", }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, smb358_i2c_match_table); + +static struct i2c_driver smb358_charger_driver = { + .driver = { + .name = "smb358", + .owner = THIS_MODULE, + .of_match_table = smb358_i2c_match_table, + }, + .probe = smb358_charger_probe, + .remove = smb358_charger_remove, + .suspend = smb358_charger_suspend, + .resume = smb358_charger_resume, + .shutdown = smb358_charger_shutdown, + .id_table = smb358_charger_id, +}; + +static int __init smb358_charger_init(void) +{ + return i2c_add_driver(&smb358_charger_driver); +} + +static void __exit smb358_charger_exit(void) +{ + i2c_del_driver(&smb358_charger_driver); +} + +module_init(smb358_charger_init); +module_exit(smb358_charger_exit); + +MODULE_DESCRIPTION("Samsung SMB358 Charger Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); + |
