summaryrefslogtreecommitdiff
path: root/drivers/battery/smb358_charger.c
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/battery/smb358_charger.c
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'drivers/battery/smb358_charger.c')
-rwxr-xr-xdrivers/battery/smb358_charger.c2022
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, &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, &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");
+