summaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
authorJonghwa Lee <jonghwa3.lee@samsung.com>2013-04-02 17:08:58 +0900
committerChanho Park <chanho61.park@samsung.com>2014-11-18 11:42:58 +0900
commitff8bf393c78bbeadd92fab12584818623889c3c7 (patch)
tree721e89a1ab74fe0db160621373746d7ee5234cf2 /drivers/power
parent3c2953e5f11fb689b1a93119775fc33e4e8f7f1b (diff)
downloadlinux-3.10-ff8bf393c78bbeadd92fab12584818623889c3c7.tar.gz
linux-3.10-ff8bf393c78bbeadd92fab12584818623889c3c7.tar.bz2
linux-3.10-ff8bf393c78bbeadd92fab12584818623889c3c7.zip
power: max77693: Add max77693 charger dirver.
Signed-off-by: Jonghwa Lee <jonghwa3.lee@samsung.com>
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/Makefile2
-rw-r--r--drivers/power/max77693_charger.c689
2 files changed, 691 insertions, 0 deletions
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 653bf6ceff3..a917c98181e 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -49,6 +49,8 @@ obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
+obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o
+obj-y += max77693_charger.o
obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o
obj-$(CONFIG_POWER_AVS) += avs/
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
diff --git a/drivers/power/max77693_charger.c b/drivers/power/max77693_charger.c
new file mode 100644
index 00000000000..340aec036bf
--- /dev/null
+++ b/drivers/power/max77693_charger.c
@@ -0,0 +1,689 @@
+/*
+ * max77693_charger.c
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * SangYoung Son <hello.son@samsung.com>
+ *
+ * Copyright (C) 2012 Samsung Electronics
+ * MyungJoo Ham <myungjoo.ham@samsung.com>
+ *
+ * Simplified hello.son's driver removing unnecessary features for
+ * charger-manager; supporting power-supply-class is the main role.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * "Input current" in the original driver is controlled by regulator
+ * "CHARGER".
+ * "Charge current" in the original driver is controlled by regulator
+ * "CHARGER_CC".
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/mfd/max77693.h>
+#include <linux/mfd/max77693-private.h>
+#include <linux/power/charger-manager.h>
+#include <linux/extcon.h>
+#include <plat/gpio-cfg.h>
+
+/* MAX77693 Registers(defined @max77693-private.h) */
+
+/* MAX77693_CHG_REG_CHG_INT */
+#define MAX77693_BYP_I (1 << 0)
+#define MAX77693_THM_I (1 << 2)
+#define MAX77693_BAT_I (1 << 3)
+#define MAX77693_CHG_I (1 << 4)
+#define MAX77693_CHGIN_I (1 << 6)
+
+/* MAX77693_CHG_REG_CHG_INT_MASK */
+#define MAX77693_BYP_IM (1 << 0)
+#define MAX77693_THM_IM (1 << 2)
+#define MAX77693_BAT_IM (1 << 3)
+#define MAX77693_CHG_IM (1 << 4)
+#define MAX77693_CHGIN_IM (1 << 6)
+
+/* MAX77693_CHG_REG_CHG_INT_OK */
+#define MAX77693_BYP_OK 0x01
+#define MAX77693_BYP_OK_SHIFT 0
+#define MAX77693_THM_OK 0x04
+#define MAX77693_THM_OK_SHIFT 2
+#define MAX77693_BAT_OK 0x08
+#define MAX77693_BAT_OK_SHIFT 3
+#define MAX77693_CHG_OK 0x10
+#define MAX77693_CHG_OK_SHIFT 4
+#define MAX77693_CHGIN_OK 0x40
+#define MAX77693_CHGIN_OK_SHIFT 6
+#define MAX77693_DETBAT 0x80
+#define MAX77693_DETBAT_SHIFT 7
+
+/* MAX77693_CHG_REG_CHG_DTLS_00 */
+#define MAX77693_THM_DTLS 0x07
+#define MAX77693_THM_DTLS_SHIFT 0
+#define MAX77693_CHGIN_DTLS 0x60
+#define MAX77693_CHGIN_DTLS_SHIFT 5
+
+/* MAX77693_CHG_REG_CHG_DTLS_01 */
+#define MAX77693_CHG_DTLS 0x0F
+#define MAX77693_CHG_DTLS_SHIFT 0
+#define MAX77693_BAT_DTLS 0x70
+#define MAX77693_BAT_DTLS_SHIFT 4
+
+/* MAX77693_CHG_REG_CHG_DTLS_02 */
+#define MAX77693_BYP_DTLS 0x0F
+#define MAX77693_BYP_DTLS_SHIFT 0
+#define MAX77693_BYP_DTLS0 0x1
+#define MAX77693_BYP_DTLS1 0x2
+#define MAX77693_BYP_DTLS2 0x4
+#define MAX77693_BYP_DTLS3 0x8
+
+/* MAX77693_CHG_REG_CHG_CNFG_00 */
+#define MAX77693_MODE_DEFAULT 0x04
+#define MAX77693_MODE_CHGR 0x01
+#define MAX77693_MODE_OTG 0x02
+#define MAX77693_MODE_BUCK 0x04
+
+/* MAX77693_CHG_REG_CHG_CNFG_02 */
+#define MAX77693_CHG_CC 0x3F
+
+/* MAX77693_CHG_REG_CHG_CNFG_04 */
+#define MAX77693_CHG_MINVSYS_MASK 0xE0
+#define MAX77693_CHG_MINVSYS_SHIFT 5
+#define MAX77693_CHG_MINVSYS_3_6V 0x06
+#define MAX77693_CHG_CV_PRM_MASK 0x1F
+#define MAX77693_CHG_CV_PRM_SHIFT 0
+#define MAX77693_CHG_CV_PRM_4_20V 0x16
+#define MAX77693_CHG_CV_PRM_4_35V 0x1D
+#define MAX77693_CHG_CV_PRM_4_40V 0x1F
+
+/* MAX77693_CHG_REG_CHG_CNFG_06 */
+#define MAX77693_CHG_CHGPROT 0x0C
+#define MAX77693_CHG_CHGPROT_SHIFT 2
+#define MAX77693_CHG_CHGPROT_UNLOCK 0x03
+
+/* MAX77693_CHG_REG_CHG_CNFG_09 */
+#define MAX77693_CHG_CHGIN_LIM 0x7F
+
+/* MAX77693_MUIC_REG_CDETCTRL1 */
+#define MAX77693_CHGTYPMAN 0x02
+#define MAX77693_CHGTYPMAN_SHIFT 1
+
+/* MAX77693_MUIC_REG_STATUS2 */
+#define MAX77693_VBVOLT 0x40
+#define MAX77693_VBVOLT_SHIFT 6
+#define MAX77693_DXOVP 0x20
+#define MAX77693_DXOVP_SHIFT 5
+#define MAX77693_CHGDETRUN 0x08
+#define MAX77693_CHGDETRUN_SHIFT 3
+#define MAX77693_CHGTYPE 0x07
+#define MAX77693_CHGTYPE_SHIFT 0
+
+/* irq */
+#define IRQ_DEBOUNCE_TIME 20 /* msec */
+
+/* charger unlock */
+#define CHG_UNLOCK_RETRY 10
+#define CHG_UNLOCK_DELAY 100
+
+/* power stabe guarantee */
+#define STABLE_POWER_DELAY 500
+
+/* charger type detection */
+#define DET_ERR_RETRY 5
+#define DET_ERR_DELAY 200
+
+/* soft charging */
+#define SOFT_CHG_START_CURR 100 /* mA */
+#define SOFT_CHG_START_DUR 100 /* ms */
+#define SOFT_CHG_CURR_STEP 100 /* mA */
+#define SOFT_CHG_STEP_DUR 20 /* ms */
+
+/* soft regulation */
+#define SW_REG_CURR_STEP_MA 100
+#define SW_REG_START_DELAY 500
+#define SW_REG_STEP_DELAY 100
+
+struct max77693_charger_data {
+ struct max77693_dev *max77693;
+
+ struct power_supply charger;
+
+ /* mutex */
+ struct mutex irq_lock;
+ struct mutex ops_lock;
+
+ unsigned int charging_state;
+ unsigned int charging_type;
+ unsigned int battery_state;
+ unsigned int battery_present;
+ unsigned int cable_type;
+ unsigned int charging_current;
+ unsigned int vbus_state;
+
+ int irq_bypass;
+ int irq_therm;
+ int irq_battery;
+ int irq_charge;
+ int irq_chargin;
+
+ /* software regulation */
+ bool soft_reg_state;
+ int soft_reg_current;
+ bool soft_reg_ing;
+
+ /* unsufficient power */
+ bool reg_loop_deted;
+
+ struct max77693_charger_platform_data *charger_pdata;
+
+ int irq;
+ u8 irq_reg;
+ int irq_cnt;
+};
+
+static int max77693_get_battery_present(struct max77693_charger_data *chg_data)
+{
+ struct regmap *rmap = chg_data->max77693->regmap;
+ u8 reg_data;
+
+ max77693_read_reg(rmap, MAX77693_CHG_REG_CHG_INT_OK, &reg_data);
+ reg_data = ((reg_data & MAX77693_DETBAT) >> MAX77693_DETBAT_SHIFT);
+
+ return !reg_data;
+}
+
+static int max77693_get_vbus_state(struct max77693_charger_data *chg_data)
+{
+ struct regmap *rmap = chg_data->max77693->regmap;
+ int state;
+ u8 reg_data;
+
+ max77693_read_reg(rmap, MAX77693_CHG_REG_CHG_DETAILS_00, &reg_data);
+ reg_data = ((reg_data & MAX77693_CHGIN_DTLS) >>
+ MAX77693_CHGIN_DTLS_SHIFT);
+
+ switch (reg_data) {
+ case 0x00:
+ /* V chgin < UVLO */
+ state = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+ case 0x01:
+ /* V chgin < V batt + minimum threshold */
+ state = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+ case 0x02:
+ /* V chgin > OVLO */
+ state = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ break;
+ case 0x03:
+ state = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ default:
+ state = POWER_SUPPLY_HEALTH_UNKNOWN;
+ break;
+ }
+
+ chg_data->vbus_state = state;
+ return state;
+}
+
+static int max77693_get_charger_type(struct max77693_charger_data *chg_data)
+{
+ struct regmap *rmap = chg_data->max77693->regmap;
+ int state;
+ u8 reg_data;
+
+ max77693_read_reg(rmap, MAX77693_CHG_REG_CHG_DETAILS_01, &reg_data);
+ reg_data = ((reg_data & MAX77693_CHG_DTLS) >> MAX77693_CHG_DTLS_SHIFT);
+
+ switch (reg_data) {
+ case 0x0:
+ state = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case 0x1:
+ case 0x2:
+ case 0x3:
+ state = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case 0x4 ... 0x8:
+ case 0xA:
+ case 0xB:
+ state = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ default:
+ state = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ break;
+ }
+
+ chg_data->charging_type = state;
+ return state;
+}
+
+static int max77693_get_charger_state(struct max77693_charger_data *chg_data)
+{
+ struct regmap *rmap = chg_data->max77693->regmap;
+ int state;
+ u8 reg_data;
+
+ max77693_read_reg(rmap, MAX77693_CHG_REG_CHG_DETAILS_01, &reg_data);
+ reg_data = ((reg_data & MAX77693_CHG_DTLS) >> MAX77693_CHG_DTLS_SHIFT);
+ switch (reg_data) {
+ case 0x0:
+ case 0x1:
+ case 0x2:
+ /*
+ * Note that whether to consider 0x3 as CHARGING or FULL
+ * is arguable.
+ * According to TN's standard 0x3 (TOP-OFF) should be
+ * "FULL".
+ * According to the strict semantics of "FULL", this is
+ * "CHARGING".
+ */
+ case 0x3:
+ state = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case 0x4:
+ state = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case 0x5:
+ case 0x6:
+ case 0x7:
+ state = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case 0x8:
+ case 0xA:
+ case 0xB:
+ state = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ default:
+ state = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+
+ chg_data->charging_state = state;
+ return state;
+}
+
+static int max77693_get_online(struct max77693_charger_data *chg_data)
+{
+ struct regmap *rmap = chg_data->max77693->regmap;
+ u8 reg_data;
+
+ max77693_read_reg(rmap, MAX77693_CHG_REG_CHG_INT_OK, &reg_data);
+ return !!(reg_data & MAX77693_CHGIN_I);
+}
+
+static int max77693_get_battery_health(struct max77693_charger_data *chg_data)
+{
+ struct regmap *rmap = chg_data->max77693->regmap;
+ int state;
+ bool low_bat = false;
+ u8 reg_data;
+
+ max77693_read_reg(rmap, MAX77693_CHG_REG_CHG_DETAILS_01, &reg_data);
+ reg_data = ((reg_data & MAX77693_BAT_DTLS) >> MAX77693_BAT_DTLS_SHIFT);
+ switch (reg_data) {
+ case 0x00: /* NO BATT */
+ state = POWER_SUPPLY_HEALTH_DEAD;
+ break;
+ case 0x01: /* V Batt < Prequalification */
+ low_bat = true;
+ state = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case 0x02: /* Takes too much time to charge. Damaged battery? */
+ state = POWER_SUPPLY_HEALTH_DEAD;
+ break;
+ case 0x03: /* V Batt > Good > Prequal */
+ state = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case 0x04: /* Good > V Batt > Prequal. Not good enough */
+ low_bat = true;
+ state = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case 0x05: /* V Batt > Overvoltage */
+ state = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ break;
+ case 0x06: /* I Batt > Overcurrent */
+ state = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ default:
+ state = POWER_SUPPLY_HEALTH_UNKNOWN;
+ break;
+ }
+
+ if (state == POWER_SUPPLY_HEALTH_GOOD)
+ state = max77693_get_vbus_state(chg_data);
+
+ /* Battery is healthy and fully-charged, but has low voltage? */
+ if (state == POWER_SUPPLY_HEALTH_GOOD && low_bat &&
+ max77693_get_charger_state(chg_data) == POWER_SUPPLY_STATUS_FULL)
+ state = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+
+ chg_data->battery_state = state;
+ return state;
+}
+
+static bool max77693_charger_unlock(struct max77693_charger_data *chg_data)
+{
+ struct regmap *rmap = chg_data->max77693->regmap;
+ u8 reg_data;
+ u8 chgprot;
+ int retry_cnt = 0;
+ bool need_init = false;
+ pr_debug("%s\n", __func__);
+
+ max77693_read_reg(rmap, MAX77693_CHG_REG_CHG_CNFG_06, &reg_data);
+ chgprot = ((reg_data & MAX77693_CHG_CHGPROT) >>
+ MAX77693_CHG_CHGPROT_SHIFT);
+
+ if (chgprot == MAX77693_CHG_CHGPROT_UNLOCK) {
+ pr_debug("%s: unlocked state, return\n", __func__);
+ need_init = false;
+ goto unlock_finish;
+ }
+
+ do {
+ max77693_write_reg(rmap, MAX77693_CHG_REG_CHG_CNFG_06,
+ (MAX77693_CHG_CHGPROT_UNLOCK <<
+ MAX77693_CHG_CHGPROT_SHIFT));
+
+ max77693_read_reg(rmap, MAX77693_CHG_REG_CHG_CNFG_06, &reg_data);
+ chgprot = ((reg_data & MAX77693_CHG_CHGPROT) >>
+ MAX77693_CHG_CHGPROT_SHIFT);
+
+ if (chgprot != MAX77693_CHG_CHGPROT_UNLOCK) {
+ pr_err("%s: unlock err, chgprot(0x%x), retry(%d)\n",
+ __func__, chgprot, retry_cnt);
+ msleep(CHG_UNLOCK_DELAY);
+ } else {
+ pr_info("%s: unlock success, chgprot(0x%x)\n",
+ __func__, chgprot);
+ need_init = true;
+ break;
+ }
+ } while ((chgprot != MAX77693_CHG_CHGPROT_UNLOCK) &&
+ (++retry_cnt < CHG_UNLOCK_RETRY));
+
+unlock_finish:
+ return need_init;
+}
+
+static void max77693_charger_reg_init(struct max77693_charger_data *chg_data)
+{
+ struct regmap *rmap = chg_data->max77693->regmap;
+ u8 reg_data;
+
+ /*
+ * fast charge timer 10hrs
+ * restart threshold disable
+ * pre-qual charge enable(default)
+ */
+ reg_data = (0x04 << 0) | (0x03 << 4);
+ max77693_write_reg(rmap, MAX77693_CHG_REG_CHG_CNFG_01, reg_data);
+
+ /*
+ * charge current 466mA(default)
+ * otg current limit 900mA
+ */
+ reg_data = (1 << 7);
+ max77693_write_reg(rmap, MAX77693_CHG_REG_CHG_CNFG_02, reg_data);
+
+ /*
+ * top off current 100mA
+ * top off timer 0min
+ */
+ reg_data = (0x00 << 0); /* 100mA */
+
+ reg_data |= (0x00 << 3);
+ max77693_write_reg(rmap, MAX77693_CHG_REG_CHG_CNFG_03, reg_data);
+
+ /*
+ * cv voltage 4.2V or 4.35V
+ * MINVSYS 3.6V(default)
+ */
+ reg_data &= (~MAX77693_CHG_MINVSYS_MASK);
+ reg_data |= (MAX77693_CHG_MINVSYS_3_6V << MAX77693_CHG_MINVSYS_SHIFT);
+ reg_data &= (~MAX77693_CHG_CV_PRM_MASK);
+#if defined(CONFIG_MACH_M0)
+ if ((system_rev != 3) && (system_rev >= 1))
+ reg_data |= (MAX77693_CHG_CV_PRM_4_35V << 0);
+ else
+ reg_data |= (MAX77693_CHG_CV_PRM_4_20V << 0);
+#else /* C1, C2, M3, T0, ... */
+ reg_data |= (MAX77693_CHG_CV_PRM_4_35V << 0);
+#endif
+
+ /*
+ * For GC1 Model, MINVSYS is 3.4V.
+ * For GC1 Model PRMV( Primary Charge Regn. Voltage) = 4.2V.
+ * Actual expected regulated voltage needs to be 4.2V but due to
+ * internal resistance and circuit deviation we might have to set the
+ * benchmark a bit higher sometimes. (4.225V now)
+ */
+#if defined(CONFIG_MACH_GC1)
+ reg_data &= (~MAX77693_CHG_CV_PRM_MASK);
+ reg_data |= (0x17 << MAX77693_CHG_CV_PRM_SHIFT);
+ reg_data &= (~MAX77693_CHG_MINVSYS_MASK);
+ reg_data |= (0x4 << MAX77693_CHG_MINVSYS_SHIFT);
+#endif
+ max77693_write_reg(rmap, MAX77693_CHG_REG_CHG_CNFG_04, reg_data);
+
+ /* VBYPSET 5V */
+ reg_data = 0x50;
+ max77693_write_reg(rmap, MAX77693_CHG_REG_CHG_CNFG_11, reg_data);
+}
+
+/* Support property from charger */
+static enum power_supply_property max77693_charger_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static const char *model_name = "MAX77693";
+static const char *manufacturer = "Maxim Semiconductor";
+static int max77693_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max77693_charger_data *chg_data = container_of(psy,
+ struct max77693_charger_data,
+ charger);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = max77693_get_charger_state(chg_data);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = max77693_get_charger_type(chg_data);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = max77693_get_battery_health(chg_data);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = max77693_get_battery_present(chg_data);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = max77693_get_online(chg_data);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = model_name;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = manufacturer;
+ break;
+ default:
+ return -EINVAL;
+ }
+ /*
+ * TODO: support chaging type...
+ val->intval = max77693_get_cable_type(chg_data);
+ */
+
+ return ret;
+}
+
+static void max77693_charger_initialize(struct max77693_charger_data *chg_data)
+{
+ struct max77693_charger_platform_data *charger_pdata =
+ chg_data->charger_pdata;
+ struct regmap *rmap = chg_data->max77693->regmap;
+ int i;
+
+ for (i = 0; i < charger_pdata->num_init_data; i++)
+ max77693_write_reg(rmap, charger_pdata->init_data[i].addr,
+ charger_pdata->init_data[i].data);
+}
+
+static int max77693_charger_probe(struct platform_device *pdev)
+{
+ struct max77693_charger_data *chg_data;
+ struct max77693_dev *max77693 = dev_get_drvdata(pdev->dev.parent);
+ struct max77693_platform_data *pdata = dev_get_platdata(max77693->dev);
+ int ret;
+
+ pr_info("%s: charger init start\n", __func__);
+
+ chg_data = devm_kzalloc(&pdev->dev, sizeof(struct max77693_charger_data), GFP_KERNEL);
+ if (!chg_data)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, chg_data);
+ chg_data->max77693 = max77693;
+
+ mutex_init(&chg_data->irq_lock);
+ mutex_init(&chg_data->ops_lock);
+
+ /* unlock charger setting protect */
+ max77693_charger_unlock(chg_data);
+
+ chg_data->charger_pdata = pdata->charger_data;
+ if (pdata->charger_data && pdata->charger_data->init_data)
+ max77693_charger_initialize(chg_data);
+ else
+ max77693_charger_reg_init(chg_data);
+
+ chg_data->charger.name = "max77693-charger",
+ chg_data->charger.type = POWER_SUPPLY_TYPE_BATTERY,
+ chg_data->charger.properties = max77693_charger_props,
+ chg_data->charger.num_properties = ARRAY_SIZE(max77693_charger_props),
+ chg_data->charger.get_property = max77693_charger_get_property,
+
+ ret = power_supply_register(&pdev->dev, &chg_data->charger);
+ if (ret) {
+ pr_err("%s: failed: power supply register\n", __func__);
+ goto err_kfree;
+ }
+
+ return 0;
+
+err_kfree:
+ mutex_destroy(&chg_data->ops_lock);
+ mutex_destroy(&chg_data->irq_lock);
+ return ret;
+}
+
+static int max77693_charger_remove(struct platform_device *pdev)
+{
+ struct max77693_charger_data *chg_data = platform_get_drvdata(pdev);
+
+ mutex_destroy(&chg_data->ops_lock);
+ mutex_destroy(&chg_data->irq_lock);
+
+ power_supply_unregister(&chg_data->charger);
+
+ return 0;
+}
+
+/*
+ * WORKAROUND: (test and remove w/ later MAX77693 chips)
+ * TODO: read chip revision and bypass this code if revision > ?
+ * Several interrupts occur while charging through TA.
+ * Suspended state cannot be maintained by the interrupts.
+ */
+static u8 saved_int_mask;
+static int max77693_charger_suspend(struct device *dev)
+{
+ struct max77693_dev *max77693 = dev_get_drvdata(dev->parent);
+ u8 int_mask;
+
+ /* Save the masking value */
+ max77693_read_reg(max77693->regmap,
+ MAX77693_CHG_REG_CHG_INT_MASK,
+ &saved_int_mask);
+
+ /* Mask all the interrupts related to charger */
+ int_mask = 0xff;
+ max77693_write_reg(max77693->regmap,
+ MAX77693_CHG_REG_CHG_INT_MASK,
+ int_mask);
+ return 0;
+}
+
+static int max77693_charger_resume(struct device *dev)
+{
+ struct max77693_dev *max77693 = dev_get_drvdata(dev->parent);
+
+ /* Restore the saved masking value */
+ max77693_write_reg(max77693->regmap,
+ MAX77693_CHG_REG_CHG_INT_MASK,
+ saved_int_mask);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(max77693_charger_pm_ops, max77693_charger_suspend,
+ max77693_charger_resume);
+
+#ifdef CONFIG_OF
+static struct of_device_id max77693_charger_of_match[] __initconst = {
+ { .compatible = "samsung,max77693-charger", },
+ { },
+};
+#endif
+
+static struct platform_driver max77693_charger_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "max77693-charger",
+ .pm = &max77693_charger_pm_ops,
+ .of_match_table = of_match_ptr(max77693_charger_of_match),
+ },
+ .probe = max77693_charger_probe,
+ .remove = max77693_charger_remove,
+};
+
+static int __init max77693_charger_init(void)
+{
+ return platform_driver_register(&max77693_charger_driver);
+}
+
+static void __exit max77693_charger_exit(void)
+{
+ platform_driver_unregister(&max77693_charger_driver);
+}
+
+module_init(max77693_charger_init);
+module_exit(max77693_charger_exit);
+
+MODULE_AUTHOR("SangYoung Son <hello.son@samsung.com>");
+MODULE_DESCRIPTION("max77693 Charger driver");
+MODULE_LICENSE("GPL");