[PATCH] power: Add driver for SMB328A Battery Charger

From: BÃlint Czobor
Date: Sun Mar 16 2014 - 09:55:00 EST


From: Jongmyeong Ko <jongmyeong.ko@xxxxxxxxxxx>

Signed-off-by: Jongmyeong Ko <jongmyeong.ko@xxxxxxxxxxx>
Signed-off-by: BÃlint Czobor <czoborbalint@xxxxxxxxx>
---
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.ko@xxxxxxxxxxx>
+ *
+ * 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 'ejuni@xxxxxxxxxxx'*/
+ 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 'ejuni@xxxxxxxxxxx'*/
+
+ 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.ko@xxxxxxxxxxx>");
+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.ko@xxxxxxxxxxx>
+ *
+ * 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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/