From: Jongmyeong Ko <jongmyeong...@samsung.com>

Signed-off-by: Jongmyeong Ko <jongmyeong...@samsung.com>
Signed-off-by: Bálint Czobor <czoborbal...@gmail.com>
---
 arch/arm/mach-msm/Kconfig             |    5 +
 drivers/power/Kconfig                 |    6 +
 drivers/power/Makefile                |    1 +
 drivers/power/smb328a-charger.c       |  968 +++++++++++++++++++++++++++++++++
 include/linux/power/smb328a-charger.h |   19 +
 5 files changed, 999 insertions(+)
 create mode 100644 drivers/power/smb328a-charger.c
 create mode 100644 include/linux/power/smb328a-charger.h

diff --git a/arch/arm/mach-msm/Kconfig b/arch/arm/mach-msm/Kconfig
index 9625cf3..4837994 100644
--- a/arch/arm/mach-msm/Kconfig
+++ b/arch/arm/mach-msm/Kconfig
@@ -83,6 +83,11 @@ config ARCH_QSD8X50
 
 endchoice
 
+config HW_REV_USING_SMB328
+       hex "Select H/W Revision using smb328"
+       depends on CHARGER_SMB328A
+       default "0x00"
+
 config MSM_HAS_DEBUG_UART_HS
        bool
 
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index ba69751..084eb69 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -360,6 +360,12 @@ config CHARGER_BQ24735
        help
          Say Y to enable support for the TI BQ24735 battery charger.
 
+config CHARGER_SMB328A
+       bool "SMB328A charger driver"
+       default n
+       help
+         Say Y here to enable support for charger with SMB328A chip.
+
 config CHARGER_SMB347
        tristate "Summit Microelectronics SMB347 Battery Charger"
        depends on I2C
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index ee54a3e..a07d9ea 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o
 obj-$(CONFIG_CHARGER_BQ24190)  += bq24190_charger.o
 obj-$(CONFIG_CHARGER_BQ24735)  += bq24735-charger.o
 obj-$(CONFIG_POWER_AVS)                += avs/
+obj-$(CONFIG_CHARGER_SMB328A)  += smb328a-charger.o
 obj-$(CONFIG_CHARGER_SMB347)   += smb347-charger.o
 obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
 obj-$(CONFIG_POWER_RESET)      += reset/
diff --git a/drivers/power/smb328a-charger.c b/drivers/power/smb328a-charger.c
new file mode 100644
index 0000000..01958bf
--- /dev/null
+++ b/drivers/power/smb328a-charger.c
@@ -0,0 +1,968 @@
+/*
+ *  SMB328A-charger.c
+ *  SMB328A charger interface driver
+ *
+ *  Copyright (C) 2011 Samsung Electronics
+ *
+ *  <jongmyeong...@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/power_supply.h>
+#include <linux/regulator/machine.h>
+#include <linux/power/smb328a-charger.h>
+#include <linux/platform_data/fsa9480.h>
+
+/* Register define */
+#define SMB328A_INPUT_AND_CHARGE_CURRENTS      0x00
+#define        SMB328A_CURRENT_TERMINATION                     0x01
+#define SMB328A_FLOAT_VOLTAGE                          0x02
+#define SMB328A_FUNCTION_CONTROL_A1                    0x03
+#define SMB328A_FUNCTION_CONTROL_A2                    0x04
+#define SMB328A_FUNCTION_CONTROL_B                     0x05
+#define SMB328A_OTG_PWR_AND_LDO_CONTROL                0x06
+#define SMB328A_VARIOUS_CONTROL_FUNCTION_A     0x07
+#define SMB328A_CELL_TEMPERATURE_MONITOR       0x08
+#define SMB328A_INTERRUPT_SIGNAL_SELECTION     0x09
+#define SMB328A_I2C_BUS_SLAVE_ADDRESS          0x0A
+
+#define SMB328A_CLEAR_IRQ                                      0x30
+#define SMB328A_COMMAND                                                0x31
+#define SMB328A_INTERRUPT_STATUS_A                     0x32
+#define SMB328A_BATTERY_CHARGING_STATUS_A      0x33
+#define SMB328A_INTERRUPT_STATUS_B                     0x34
+#define SMB328A_BATTERY_CHARGING_STATUS_B      0x35
+#define SMB328A_BATTERY_CHARGING_STATUS_C      0x36
+#define SMB328A_INTERRUPT_STATUS_C                     0x37
+#define SMB328A_BATTERY_CHARGING_STATUS_D      0x38
+#define SMB328A_AUTOMATIC_INPUT_CURRENT_LIMMIT_STATUS  0x39
+
+enum {
+       BAT_NOT_DETECTED,
+       BAT_DETECTED
+};
+
+enum {
+       CHG_MODE_NONE,
+       CHG_MODE_AC,
+       CHG_MODE_USB,
+       CHG_MODE_MISC
+};
+
+struct smb328a_chip {
+       struct i2c_client               *client;
+       struct delayed_work             work;
+       struct power_supply             psy_bat;
+       struct smb328a_platform_data    *pdata;
+
+       int chg_mode;
+       unsigned int batt_vcell;
+};
+
+static enum power_supply_property smb328a_battery_props[] = {
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_ONLINE,
+};
+
+/* Check batt init */
+extern struct work_struct *p_batt_init;
+extern int board_hw_revision;
+
+
+static int smb328a_write_reg(struct i2c_client *client, int reg, u8 value)
+{
+       int ret;
+
+       ret = i2c_smbus_write_byte_data(client, reg, value);
+
+       if (ret < 0)
+               pr_err("%s: err %d\n", __func__, ret);
+
+       return ret;
+}
+
+static int smb328a_read_reg(struct i2c_client *client, int reg)
+{
+       int ret;
+
+       ret = i2c_smbus_read_byte_data(client, reg);
+
+       if (ret < 0)
+               pr_err("%s: err %d\n", __func__, ret);
+
+       return ret;
+}
+
+static void smb328a_allow_volatile_writes(struct i2c_client *client)
+{
+       int val;
+       u8 data;
+
+       val = smb328a_read_reg(client, SMB328A_COMMAND);
+       if ((val >= 0) && !(val&0x80)) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_COMMAND, data);
+               data |= (0x1 << 7);
+               if (smb328a_write_reg(client, SMB328A_COMMAND, data) < 0)
+                       pr_err("%s : error!\n", __func__);
+               val = smb328a_read_reg(client, SMB328A_COMMAND);
+               if (val >= 0) {
+                       data = (u8)data;
+                       pr_info("%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_COMMAND, data);
+               }
+       }
+}
+
+static void smb328a_charger_function_conrol(struct i2c_client *client)
+{
+       struct smb328a_chip *chip = i2c_get_clientdata(client);
+       int val;
+       u8 data, set_data;
+
+       smb328a_allow_volatile_writes(client);
+
+       /* Clear IRQ register*/
+       set_data = 0xAA;
+
+       if (smb328a_write_reg(client, SMB328A_CLEAR_IRQ, set_data) < 0)
+                       pr_err("%s : write error!\n", __func__);
+       else
+               printk("%s : Clear IRQ register.\n", __func__);
+
+
+
+       val = smb328a_read_reg(client, SMB328A_INPUT_AND_CHARGE_CURRENTS);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_INPUT_AND_CHARGE_CURRENTS, data);
+               set_data = 0x75;
+               if (data != set_data) { /* this can be changed with top-off 
setting */
+                       data = set_data;
+                       if (smb328a_write_reg(client, 
SMB328A_INPUT_AND_CHARGE_CURRENTS, data) < 0)
+                               pr_err("%s : error!\n", __func__);
+                       val = smb328a_read_reg(client, 
SMB328A_INPUT_AND_CHARGE_CURRENTS);
+                       if (val >= 0) {
+                               data = (u8)val;
+                               dev_info(&client->dev, "%s : => reg (0x%x) = 
0x%x\n", __func__, SMB328A_INPUT_AND_CHARGE_CURRENTS, data);
+                       }
+               }
+       }
+
+       val = smb328a_read_reg(client, SMB328A_CURRENT_TERMINATION);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_CURRENT_TERMINATION, data);
+               set_data = 0x34;
+               if (data != set_data) { /* AICL enable */
+                       data = set_data;
+                       if (smb328a_write_reg(client, 
SMB328A_CURRENT_TERMINATION, data) < 0)
+                               pr_err("%s : error!\n", __func__);
+                       val = smb328a_read_reg(client, 
SMB328A_CURRENT_TERMINATION);
+                       if (val >= 0) {
+                               data = (u8)val;
+                               dev_info(&client->dev, "%s : => reg (0x%x) = 
0x%x\n", __func__, SMB328A_CURRENT_TERMINATION, data);
+                       }
+               }
+       }
+
+
+       val = smb328a_read_reg(client, SMB328A_FLOAT_VOLTAGE);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_FLOAT_VOLTAGE, data);
+        if (data != 0xCC) {
+            data = 0xCC; /* 4.22V float voltage */ /* hw requirements 
'ej...@samsung.com'*/
+                       if (smb328a_write_reg(client, SMB328A_FLOAT_VOLTAGE, 
data) < 0)
+                               pr_err("%s : error!\n", __func__);
+                       val = smb328a_read_reg(client, SMB328A_FLOAT_VOLTAGE);
+                       if (val >= 0) {
+                               data = (u8)val;
+                               dev_info(&client->dev, "%s : => reg (0x%x) = 
0x%x\n", __func__, SMB328A_FLOAT_VOLTAGE, data);
+                       }
+               }
+       }
+
+       val = smb328a_read_reg(client, SMB328A_FUNCTION_CONTROL_A1);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_FUNCTION_CONTROL_A1, data);
+               if (data != 0xDA) {
+                       data = 0xDA;
+                       if (smb328a_write_reg(client, 
SMB328A_FUNCTION_CONTROL_A1, data) < 0)
+                               pr_err("%s : error!\n", __func__);
+                       val = smb328a_read_reg(client, 
SMB328A_FUNCTION_CONTROL_A1);
+                       if (val >= 0) {
+                               data = (u8)val;
+                               dev_info(&client->dev, "%s : => reg (0x%x) = 
0x%x\n", __func__, SMB328A_FUNCTION_CONTROL_A1, data);
+                       }
+               }
+       }
+
+       val = smb328a_read_reg(client, SMB328A_FUNCTION_CONTROL_A2);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_FUNCTION_CONTROL_A2, data);
+               if (data != 0x4F) {
+                       data = 0x4F;
+                       if (smb328a_write_reg(client, 
SMB328A_FUNCTION_CONTROL_A2, data) < 0)
+                               pr_err("%s : error!\n", __func__);
+                       val = smb328a_read_reg(client, 
SMB328A_FUNCTION_CONTROL_A2);
+                       if (val >= 0) {
+                               data = (u8)val;
+                               dev_info(&client->dev, "%s : => reg (0x%x) = 
0x%x\n", __func__, SMB328A_FUNCTION_CONTROL_A2, data);
+                       }
+               }
+       }
+
+       val = smb328a_read_reg(client, SMB328A_FUNCTION_CONTROL_B);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_FUNCTION_CONTROL_B, data);
+               if (data != 0x00) {
+                       data = 0x00;
+                       if (smb328a_write_reg(client, 
SMB328A_FUNCTION_CONTROL_B, data) < 0)
+                               pr_err("%s : error!\n", __func__);
+                       val = smb328a_read_reg(client, 
SMB328A_FUNCTION_CONTROL_B);
+                       if (val >= 0) {
+                               data = (u8)val;
+                               dev_info(&client->dev, "%s : => reg (0x%x) = 
0x%x\n", __func__, SMB328A_FUNCTION_CONTROL_B, data);
+                       }
+               }
+       }
+
+       val = smb328a_read_reg(client, SMB328A_OTG_PWR_AND_LDO_CONTROL);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_OTG_PWR_AND_LDO_CONTROL, data);
+#if defined (CONFIG_TARGET_LOCALE_USA)
+               set_data = 0x4d;
+#else
+               set_data = 0xC5;
+#endif
+               if (data != set_data) {
+                       data = set_data;
+                       if (smb328a_write_reg(client, 
SMB328A_OTG_PWR_AND_LDO_CONTROL, data) < 0)
+                               pr_err("%s : error!\n", __func__);
+                       val = smb328a_read_reg(client, 
SMB328A_OTG_PWR_AND_LDO_CONTROL);
+                       if (val >= 0) {
+                               data = (u8)val;
+                               dev_info(&client->dev, "%s : => reg (0x%x) = 
0x%x\n", __func__, SMB328A_OTG_PWR_AND_LDO_CONTROL, data);
+                       }
+               }
+       }
+
+       val = smb328a_read_reg(client, SMB328A_VARIOUS_CONTROL_FUNCTION_A);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_VARIOUS_CONTROL_FUNCTION_A, data);
+               if (data != 0xF6) { /* this can be changed with top-off setting 
*/
+                       data = 0xF6;
+                       if (smb328a_write_reg(client, 
SMB328A_VARIOUS_CONTROL_FUNCTION_A, data) < 0)
+                               pr_err("%s : error!\n", __func__);
+                       val = smb328a_read_reg(client, 
SMB328A_VARIOUS_CONTROL_FUNCTION_A);
+                       if (val >= 0) {
+                               data = (u8)val;
+                               dev_info(&client->dev, "%s : => reg (0x%x) = 
0x%x\n", __func__, SMB328A_VARIOUS_CONTROL_FUNCTION_A, data);
+                       }
+               }
+       }
+
+       val = smb328a_read_reg(client, SMB328A_CELL_TEMPERATURE_MONITOR);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_CELL_TEMPERATURE_MONITOR, data);
+               if (data != 0x00) {
+                       data = 0x00;
+                       if (smb328a_write_reg(client, 
SMB328A_CELL_TEMPERATURE_MONITOR, data) < 0)
+                               pr_err("%s : error!\n", __func__);
+                       val = smb328a_read_reg(client, 
SMB328A_CELL_TEMPERATURE_MONITOR);
+                       if (val >= 0) {
+                               data = (u8)val;
+                               dev_info(&client->dev, "%s : => reg (0x%x) = 
0x%x\n", __func__, SMB328A_CELL_TEMPERATURE_MONITOR, data);
+                       }
+               }
+       }
+
+       val = smb328a_read_reg(client, SMB328A_INTERRUPT_SIGNAL_SELECTION);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_INTERRUPT_SIGNAL_SELECTION, data);
+               if (data != 0x00) {
+                       data = 0x20;
+                       if (smb328a_write_reg(client, 
SMB328A_INTERRUPT_SIGNAL_SELECTION, data) < 0)
+                               pr_err("%s : error!\n", __func__);
+                       val = smb328a_read_reg(client, 
SMB328A_INTERRUPT_SIGNAL_SELECTION);
+                       if (val >= 0) {
+                               data = (u8)val;
+                               dev_info(&client->dev, "%s : => reg (0x%x) = 
0x%x\n", __func__, SMB328A_INTERRUPT_SIGNAL_SELECTION, data);
+                       }
+               }
+       }
+}
+
+static int smb328a_check_charging_status(struct i2c_client *client)
+{
+       int val;
+       u8 data = 0;
+       int ret = -1;
+
+       val = smb328a_read_reg(client, SMB328A_BATTERY_CHARGING_STATUS_C);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_BATTERY_CHARGING_STATUS_C, data);
+
+               ret = (data&(0x3<<1))>>1;
+               dev_info(&client->dev, "%s : status = 0x%x\n", __func__, data);
+       }
+
+       return ret;
+}
+
+static bool smb328a_check_is_charging(struct i2c_client *client)
+{
+       int val;
+       u8 data = 0;
+       bool ret = false;
+
+       val = smb328a_read_reg(client, SMB328A_BATTERY_CHARGING_STATUS_C);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_BATTERY_CHARGING_STATUS_C, data);
+
+               if (data&0x1)
+                       ret = true; /* charger enabled */
+       }
+
+       return ret;
+}
+
+static bool smb328a_check_bat_full(struct i2c_client *client)
+{
+       int val;
+       u8 data = 0;
+       bool ret = false;
+
+       val = smb328a_read_reg(client, SMB328A_BATTERY_CHARGING_STATUS_C);
+       if (val >= 0) {
+               data = (u8)val;
+
+               if (data&(0x1<<6))
+                       ret = true; /* full */
+       }
+
+       return ret;
+}
+
+/* vf check */
+static bool smb328a_check_bat_missing(struct i2c_client *client)
+{
+       int val;
+       u8 data = 0;
+       bool ret = false;
+
+       val = smb328a_read_reg(client, SMB328A_BATTERY_CHARGING_STATUS_B);
+
+       if (val >= 0) {
+               data = (u8)val;
+               printk("%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_BATTERY_CHARGING_STATUS_B, data);
+
+               if (data&0x1) {
+                       pr_info("%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_BATTERY_CHARGING_STATUS_B, data);
+                       ret = true; /* missing battery */
+               }
+       }
+
+       return ret;
+}
+
+static bool smb328a_read_chg_status(struct i2c_client *client, unsigned int 
*status)
+{
+       int status_A=0,status_B=0,status_C=0, int_status_C=0;
+       bool ret = false;
+
+       status_A = smb328a_read_reg(client, SMB328A_BATTERY_CHARGING_STATUS_A);
+       status_B = smb328a_read_reg(client, SMB328A_BATTERY_CHARGING_STATUS_B);
+       status_C = smb328a_read_reg(client, SMB328A_BATTERY_CHARGING_STATUS_C);
+       int_status_C = smb328a_read_reg(client, SMB328A_INTERRUPT_STATUS_C);
+
+       if( status_A < 0 || status_B < 0 || status_C < 0 || int_status_C < 0)
+               return false;
+       else
+               *status = int_status_C << 24 | status_A << 16 | status_B << 8 | 
status_C ;
+
+       return true;
+}
+
+/* whether valid dcin or not */
+static bool smb328a_check_vdcin(struct i2c_client *client)
+{
+       int val;
+       u8 data = 0;
+       bool ret = false;
+
+       val = smb328a_read_reg(client, SMB328A_BATTERY_CHARGING_STATUS_A);
+       if (val >= 0) {
+               data = (u8)val;
+
+               if (data&(0x1<<1))
+                       ret = true;
+       }
+
+       return ret;
+}
+
+static bool smb328a_check_bmd_disabled(struct i2c_client *client)
+{
+       int val;
+       u8 data = 0;
+       bool ret = false;
+
+       val = smb328a_read_reg(client, SMB328A_FUNCTION_CONTROL_B);
+       if (val >= 0) {
+               data = (u8)val;
+
+               if (data&(0x1<<7)) {
+                       ret = true;
+                       pr_info("%s : return ture : reg(0x%x)=0x%x (0x%x)\n", 
__func__,
+                               SMB328A_FUNCTION_CONTROL_B, data, 
data&(0x1<<7));
+               }
+       }
+
+#if !defined (CONFIG_TARGET_LOCALE_USA)
+       val = smb328a_read_reg(client, SMB328A_OTG_PWR_AND_LDO_CONTROL);
+       if (val >= 0) {
+               data = (u8)val;
+
+               if ((data&(0x1<<7))==0) {
+                       ret = true;
+                       pr_info("%s : return ture : reg(0x%x)=0x%x (0x%x)\n", 
__func__,
+                               SMB328A_OTG_PWR_AND_LDO_CONTROL, data, 
data&(0x1<<7));
+               }
+       }
+#endif
+
+       return ret;
+}
+
+static int smb328a_chg_get_property(struct power_supply *psy,
+                               enum power_supply_property psp,
+                               union power_supply_propval *val)
+{
+       struct smb328a_chip *chip = container_of(psy,
+                               struct smb328a_chip, psy_bat);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               if (smb328a_check_vdcin(chip->client))
+                       val->intval = POWER_SUPPLY_STATUS_CHARGING;
+               else
+                       val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+               break;
+       case POWER_SUPPLY_PROP_PRESENT:
+               if (smb328a_check_bat_missing(chip->client))
+                       val->intval = BAT_NOT_DETECTED;
+               else
+                       val->intval = BAT_DETECTED;
+               break;
+       case POWER_SUPPLY_PROP_ONLINE:
+               /* check VF check available */
+               if (smb328a_check_bmd_disabled(chip->client))
+                       val->intval = 1;
+               else
+                       val->intval = 0;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_FULL:
+               if (smb328a_check_bat_full(chip->client))
+                       val->intval = 1;
+               else
+                       val->intval = 0;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_TYPE:
+               switch (smb328a_check_charging_status(chip->client)) {
+                       case 0:
+                               val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+                               break;
+                       case 1:
+                               val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+                               break;
+                       case 2:
+                               val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+                               break;
+                       case 3:
+                               val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+                               break;
+                       default:
+                               pr_err("%s : get charge type error!\n", 
__func__);
+                               return -EINVAL;
+               }
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_NOW:
+               if (smb328a_check_is_charging(chip->client))
+                       val->intval = 1;
+               else
+                       val->intval = 0;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int smb328a_set_top_off(struct i2c_client *client, int top_off)
+{
+       int val, set_val = 0;
+       u8 data;
+
+       printk("%s : \n", __func__);
+
+       smb328a_allow_volatile_writes(client);
+
+       set_val = top_off/25;
+       set_val -= 1;
+
+       if (set_val < 0 || set_val > 7) {
+               pr_err("%s: invalid topoff set value(%d)\n", __func__, set_val);
+               return -EINVAL;
+       }
+
+       val = smb328a_read_reg(client, SMB328A_INPUT_AND_CHARGE_CURRENTS);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_INPUT_AND_CHARGE_CURRENTS, data);
+               data |= (set_val << 0);
+               if (smb328a_write_reg(client, 
SMB328A_INPUT_AND_CHARGE_CURRENTS, data) < 0) {
+                       pr_err("%s : error!\n", __func__);
+                       return -1;
+               }
+               data = smb328a_read_reg(client, 
SMB328A_INPUT_AND_CHARGE_CURRENTS);
+               dev_info(&client->dev, "%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_INPUT_AND_CHARGE_CURRENTS, data);
+       }
+
+       val = smb328a_read_reg(client, SMB328A_VARIOUS_CONTROL_FUNCTION_A);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_VARIOUS_CONTROL_FUNCTION_A, data);
+               data |= (set_val << 5);
+               if (smb328a_write_reg(client, 
SMB328A_VARIOUS_CONTROL_FUNCTION_A, data) < 0) {
+                       pr_err("%s : error!\n", __func__);
+                       return -1;
+               }
+               data = smb328a_read_reg(client, 
SMB328A_VARIOUS_CONTROL_FUNCTION_A);
+               dev_info(&client->dev, "%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_VARIOUS_CONTROL_FUNCTION_A, data);
+       }
+
+       return 0;
+}
+
+static int smb328a_set_charging_current(struct i2c_client *client, int 
chg_current)
+{
+       struct smb328a_chip *chip = i2c_get_clientdata(client);
+
+       dev_info(&client->dev, "%s : \n", __func__);
+
+       if (chg_current < 200 || chg_current > 950)
+               return -EINVAL;
+
+       if (chg_current == 600) {
+               chip->chg_mode = CHG_MODE_AC;
+       } else if (chg_current == 450) {
+               chip->chg_mode = CHG_MODE_USB;
+       } else {
+               pr_err("%s : error! invalid setting current\n", __func__);
+               chip->chg_mode = CHG_MODE_NONE;
+               return -1;
+       }
+
+       return 0;
+}
+
+static int smb328a_enable_charging(struct i2c_client *client)
+{
+       int val;
+       u8 data;
+       struct smb328a_chip *chip = i2c_get_clientdata(client);
+
+       dev_info(&client->dev, "%s : \n", __func__);
+
+       val = smb328a_read_reg(client, SMB328A_COMMAND);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_COMMAND, data);
+               if (chip->chg_mode == CHG_MODE_AC)
+                       data = 0x8C;
+               else if (chip->chg_mode == CHG_MODE_USB)
+                       data = 0x88;
+
+               if (smb328a_write_reg(client, SMB328A_COMMAND, data) < 0) {
+                       pr_err("%s : error!\n", __func__);
+                       return -1;
+               }
+               data = smb328a_read_reg(client, SMB328A_COMMAND);
+               pr_info("%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_COMMAND, data);
+       }
+
+       return 0;
+}
+
+static int smb328a_disable_charging(struct i2c_client *client)
+{
+       int val;
+       u8 data;
+       struct smb328a_chip *chip = i2c_get_clientdata(client);
+
+       dev_info(&client->dev, "%s : \n", __func__);
+
+       smb328a_allow_volatile_writes(client);
+
+       /* Write register for charging termination */
+
+       data = 0x75;
+
+       if (smb328a_write_reg(client, SMB328A_INPUT_AND_CHARGE_CURRENTS, data) 
< 0)
+                       pr_err("%s : error!\n", __func__);
+       val = smb328a_read_reg(client, SMB328A_INPUT_AND_CHARGE_CURRENTS);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_INPUT_AND_CHARGE_CURRENTS, data);
+       }
+
+       data = 0x34;
+
+       if (smb328a_write_reg(client, SMB328A_CURRENT_TERMINATION, data) < 0)
+                       pr_err("%s : error!\n", __func__);
+       val = smb328a_read_reg(client, SMB328A_CURRENT_TERMINATION);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_CURRENT_TERMINATION, data);
+       }
+
+       data = 0xCC; /* 4.22V float voltage */ /* hw requirements 
'ej...@samsung.com'*/
+
+       if (smb328a_write_reg(client, SMB328A_FLOAT_VOLTAGE, data) < 0)
+                       pr_err("%s : error!\n", __func__);
+       val = smb328a_read_reg(client, SMB328A_FLOAT_VOLTAGE);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_FLOAT_VOLTAGE, data);
+       }
+
+       data = 0xDA;
+
+       if (smb328a_write_reg(client, SMB328A_FUNCTION_CONTROL_A1, data) < 0)
+                       pr_err("%s : error!\n", __func__);
+       val = smb328a_read_reg(client, SMB328A_FUNCTION_CONTROL_A1);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_FUNCTION_CONTROL_A1, data);
+       }
+
+       data = 0x4D;
+
+       if (smb328a_write_reg(client, SMB328A_FUNCTION_CONTROL_A2, data) < 0)
+                       pr_err("%s : error!\n", __func__);
+       val = smb328a_read_reg(client, SMB328A_FUNCTION_CONTROL_A2);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_FUNCTION_CONTROL_A2, data);
+       }
+
+       data = 0x00;
+
+       if (smb328a_write_reg(client, SMB328A_FUNCTION_CONTROL_B, data) < 0)
+                       pr_err("%s : error!\n", __func__);
+       val = smb328a_read_reg(client, SMB328A_FUNCTION_CONTROL_B);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_FUNCTION_CONTROL_B, data);
+       }
+
+#if defined (CONFIG_TARGET_LOCALE_USA)
+       data = 0x4d;
+#else
+       data = 0xC5;
+#endif
+
+       if (smb328a_write_reg(client, SMB328A_OTG_PWR_AND_LDO_CONTROL, data) < 
0)
+                       pr_err("%s : error!\n", __func__);
+       val = smb328a_read_reg(client, SMB328A_OTG_PWR_AND_LDO_CONTROL);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_OTG_PWR_AND_LDO_CONTROL, data);
+       }
+
+       data = 0xF6;
+
+       if (smb328a_write_reg(client, SMB328A_VARIOUS_CONTROL_FUNCTION_A, data) 
< 0)
+                       pr_err("%s : error!\n", __func__);
+       val = smb328a_read_reg(client, SMB328A_VARIOUS_CONTROL_FUNCTION_A);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_VARIOUS_CONTROL_FUNCTION_A, data);
+       }
+
+       data = 0x00;
+
+       if (smb328a_write_reg(client, SMB328A_CELL_TEMPERATURE_MONITOR, data) < 
0)
+                       pr_err("%s : error!\n", __func__);
+       val = smb328a_read_reg(client, SMB328A_CELL_TEMPERATURE_MONITOR);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_CELL_TEMPERATURE_MONITOR, data);
+       }
+
+       data = 0x20;
+
+       if (smb328a_write_reg(client, SMB328A_INTERRUPT_SIGNAL_SELECTION, data) 
< 0)
+                       pr_err("%s : error!\n", __func__);
+       val = smb328a_read_reg(client, SMB328A_INTERRUPT_SIGNAL_SELECTION);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_INTERRUPT_SIGNAL_SELECTION, data);
+       }
+
+       val = smb328a_read_reg(client, SMB328A_COMMAND);
+       if (val >= 0) {
+               data = (u8)val;
+               dev_info(&client->dev, "%s : reg (0x%x) = 0x%x\n", __func__, 
SMB328A_COMMAND, data);
+               data = 0x98;
+               if (smb328a_write_reg(client, SMB328A_COMMAND, data) < 0) {
+                       pr_err("%s : error!\n", __func__);
+                       return -1;
+               }
+               data = smb328a_read_reg(client, SMB328A_COMMAND);
+               pr_info("%s : => reg (0x%x) = 0x%x\n", __func__, 
SMB328A_COMMAND, data);
+       }
+
+       return 0;
+}
+
+static int smb328a_chg_set_property(struct power_supply *psy,
+                           enum power_supply_property psp,
+                           const union power_supply_propval *val)
+{
+       struct smb328a_chip *chip = container_of(psy,
+                               struct smb328a_chip, psy_bat);
+       int ret;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_CURRENT_NOW: /* step1) Set charging current */
+               smb328a_charger_function_conrol(chip->client);
+               ret = smb328a_set_charging_current(chip->client, val->intval);
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_FULL: /* step2) Set top-off current */
+               if (val->intval < 25 || val->intval > 200) {
+                       pr_err("%s: invalid topoff current(%d)\n",
+                                       __func__, val->intval);
+                       return -EINVAL;
+               }
+               ret = smb328a_set_top_off(chip->client, val->intval);
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW: /* step3) Notify Vcell Now */
+               chip->batt_vcell = val->intval;
+               pr_info("%s : vcell(%d)\n", __func__, chip->batt_vcell);
+               ret = 0;
+               break;
+       case POWER_SUPPLY_PROP_STATUS: /* step4) Enable/Disable charging */
+               if (val->intval == POWER_SUPPLY_STATUS_CHARGING) {
+                       ret = smb328a_enable_charging(chip->client);
+               } else
+
+                       ret = smb328a_disable_charging(chip->client);
+               break;
+       default:
+               return -EINVAL;
+       }
+       return ret;
+}
+
+static ssize_t sec_smb328a_show_property(struct device *dev,
+                                   struct device_attribute *attr, char *buf);
+
+#define SEC_SMB328A_ATTR(_name)                        \
+{                                              \
+       .attr = { .name = #_name,               \
+                 .mode = S_IRUGO | S_IWUSR |S_IWGRP },                 \
+       .show = sec_smb328a_show_property,              \
+       .store = NULL,                  \
+}
+
+static struct device_attribute sec_smb328a_attrs[] = {
+       SEC_SMB328A_ATTR(smb_read_36h),
+};
+
+enum {
+       SMB_READ_36H = 0,
+};
+
+static ssize_t sec_smb328a_show_property(struct device *dev,
+                                   struct device_attribute *attr, char *buf)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct smb328a_chip *chip = container_of(psy,
+                                                 struct smb328a_chip,
+                                                 psy_bat);
+
+       int i = 0;
+       const ptrdiff_t off = attr - sec_smb328a_attrs;
+       int val;
+       u8 data = 0;
+
+       switch (off) {
+       case SMB_READ_36H:
+               val = smb328a_read_reg(chip->client, 
SMB328A_BATTERY_CHARGING_STATUS_C);
+               if (val >= 0) {
+                       data = (u8)val;
+                       i += scnprintf(buf + i, PAGE_SIZE - i, "0x%x (bit6 : 
%d)\n",
+                                       data, (data&0x40)>>6);
+               } else {
+                       i = -EINVAL;
+               }
+               break;
+       default:
+               i = -EINVAL;
+       }
+
+       return i;
+}
+
+static int smb328a_create_attrs(struct device *dev)
+{
+       int i, rc;
+
+       for (i = 0; i < ARRAY_SIZE(sec_smb328a_attrs); i++) {
+               rc = device_create_file(dev, &sec_smb328a_attrs[i]);
+               if (rc)
+                       goto smb328a_attrs_failed;
+       }
+       goto succeed;
+
+smb328a_attrs_failed:
+       while (i--)
+               device_remove_file(dev, &sec_smb328a_attrs[i]);
+succeed:
+       return rc;
+}
+
+static int smb328a_probe(struct i2c_client *client,
+                       const struct i2c_device_id *id)
+{
+       struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+       struct smb328a_chip *chip;
+       int ret;
+
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+               return -EIO;
+
+       pr_info("%s: SMB328A driver Loading! \n", __func__);
+
+       chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+       if (!chip)
+               return -ENOMEM;
+
+       chip->client = client;
+       chip->pdata = client->dev.platform_data;
+
+       i2c_set_clientdata(client, chip);
+
+       chip->psy_bat.name = "smb328a-charger",
+       chip->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY,
+       chip->psy_bat.properties = smb328a_battery_props,
+       chip->psy_bat.num_properties = ARRAY_SIZE(smb328a_battery_props),
+       chip->psy_bat.get_property = smb328a_chg_get_property,
+       chip->psy_bat.set_property = smb328a_chg_set_property,
+       ret = power_supply_register(&client->dev, &chip->psy_bat);
+       if (ret) {
+               pr_err("Failed to register power supply psy_bat\n");
+               goto err_kfree;
+       }
+
+       chip->chg_mode = CHG_MODE_NONE;
+
+       /* create smb328a attributes */
+       smb328a_create_attrs(chip->psy_bat.dev);
+
+#ifdef CONFIG_HW_REV_USING_SMB328
+       if(board_hw_revision >= CONFIG_HW_REV_USING_SMB328)
+       {
+               /* Enable batt init */
+               if(p_batt_init != NULL)
+               {
+                       if (work_pending(p_batt_init))
+                       {
+                               cancel_delayed_work(p_batt_init);
+                               schedule_work(p_batt_init);
+                       }
+               }
+       }
+#endif
+       return 0;
+
+err_kfree:
+       kfree(chip);
+       return ret;
+}
+
+static int smb328a_remove(struct i2c_client *client)
+{
+       struct smb328a_chip *chip = i2c_get_clientdata(client);
+
+       power_supply_unregister(&chip->psy_bat);
+       kfree(chip);
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int smb328a_suspend(struct i2c_client *client,
+               pm_message_t state)
+{
+       struct smb328a_chip *chip = i2c_get_clientdata(client);
+
+       return 0;
+}
+
+static int smb328a_resume(struct i2c_client *client)
+{
+       struct smb328a_chip *chip = i2c_get_clientdata(client);
+
+       return 0;
+}
+#else
+#define smb328a_suspend NULL
+#define smb328a_resume NULL
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id smb328a_id[] = {
+       { "smb328a", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, smb328a_id);
+
+static struct i2c_driver smb328a_i2c_driver = {
+       .driver = {
+               .name   = "smb328a",
+       },
+       .probe          = smb328a_probe,
+       .remove         = smb328a_remove,
+       .suspend        = smb328a_suspend,
+       .resume         = smb328a_resume,
+       .id_table       = smb328a_id,
+};
+
+module_i2c_driver(smb328a_i2c_driver);
+
+MODULE_DESCRIPTION("SMB328A charger control driver");
+MODULE_AUTHOR("<jongmyeong...@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/power/smb328a-charger.h 
b/include/linux/power/smb328a-charger.h
new file mode 100644
index 0000000..4dce7e0
--- /dev/null
+++ b/include/linux/power/smb328a-charger.h
@@ -0,0 +1,19 @@
+/*
+ *  Copyright (C) 2011 Samsung Electronics
+ *  jongmyeong ko <jongmyeong...@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __SMB328A_CHARGER_H_
+#define __SMB328A_CHARGER_H_
+
+struct smb328a_platform_data {
+       int             (*set_charger)(int);
+       int             (*topoff_cb) (void);
+       void    (*hw_init)(void);
+};
+
+#endif
-- 
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to