power: rk818: support rk818 charger driver
authorJianhong Chen <chenjh@rock-chips.com>
Tue, 5 Jul 2016 07:20:19 +0000 (15:20 +0800)
committerHuang, Tao <huangtao@rock-chips.com>
Wed, 27 Jul 2016 08:44:13 +0000 (16:44 +0800)
Change-Id: Ica9a517723d10ea75baddd3f16e3ee0aa07dfb8b
Signed-off-by: Jianhong Chen <chenjh@rock-chips.com>
drivers/mfd/rk808.c
drivers/power/Kconfig
drivers/power/Makefile
drivers/power/rk818_charger.c [new file with mode: 0644]

index 86dd427b0fe358e66df0b4783ae3b609cbb5f808..6e0540f5b70dc4c4cc1bcf97bdc0147f91dc8aff 100644 (file)
@@ -224,6 +224,7 @@ static const struct mfd_cell rk818s[] = {
        { .name = "rk808-clkout", },
        { .name = "rk818-regulator", },
        { .name = "rk818-battery", .of_compatible = "rk818-battery", },
        { .name = "rk808-clkout", },
        { .name = "rk818-regulator", },
        { .name = "rk818-battery", .of_compatible = "rk818-battery", },
+       { .name = "rk818-charger", },
        {
                .name = "rk808-rtc",
                .num_resources = ARRAY_SIZE(rtc_resources),
        {
                .name = "rk808-rtc",
                .num_resources = ARRAY_SIZE(rtc_resources),
index 1f6ec03cd378e723bb08a4a7e2a30fd09ab9bc99..18788dd3fde2c2ee47faff8528e45bea2a211832 100644 (file)
@@ -501,6 +501,14 @@ config BATTERY_RK818
          If you say yes here you will get support for the battery of RK818 PMIC.
          This driver can give support for Rk818 Battery Charge Interface.
 
          If you say yes here you will get support for the battery of RK818 PMIC.
          This driver can give support for Rk818 Battery Charge Interface.
 
+config CHARGER_RK818
+       bool "RK818 Charger driver"
+       depends on MFD_RK808
+       default n
+       help
+         If you say yes here you will get support for the charger of RK818 PMIC.
+         This driver can give support for Rk818 Charger Interface.
+
 config CHARGER_RT9455
        tristate "Richtek RT9455 battery charger driver"
        depends on I2C
 config CHARGER_RT9455
        tristate "Richtek RT9455 battery charger driver"
        depends on I2C
index b038e25333300408c7234f0016a07f3f128fdd1f..c2ad15e3de094dff5736ef115519584947e376c7 100644 (file)
@@ -39,6 +39,7 @@ obj-$(CONFIG_BATTERY_MAX17040)        += max17040_battery.o
 obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o
 obj-$(CONFIG_BATTERY_Z2)       += z2_battery.o
 obj-$(CONFIG_BATTERY_RK818)    += rk818_battery.o
 obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o
 obj-$(CONFIG_BATTERY_Z2)       += z2_battery.o
 obj-$(CONFIG_BATTERY_RK818)    += rk818_battery.o
+obj-$(CONFIG_CHARGER_RK818)    += rk818_charger.o
 obj-$(CONFIG_BATTERY_RT5033)   += rt5033_battery.o
 obj-$(CONFIG_CHARGER_RT9455)   += rt9455_charger.o
 obj-$(CONFIG_BATTERY_S3C_ADC)  += s3c_adc_battery.o
 obj-$(CONFIG_BATTERY_RT5033)   += rt5033_battery.o
 obj-$(CONFIG_CHARGER_RT9455)   += rt9455_charger.o
 obj-$(CONFIG_BATTERY_S3C_ADC)  += s3c_adc_battery.o
diff --git a/drivers/power/rk818_charger.c b/drivers/power/rk818_charger.c
new file mode 100644 (file)
index 0000000..593abec
--- /dev/null
@@ -0,0 +1,1351 @@
+/*
+ * rk818 charger driver
+ *
+ * Copyright (C) 2016 Rockchip Electronics Co., Ltd
+ * chenjh <chenjh@rock-chips.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/extcon.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/mfd/rk808.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/power/rk_usbbc.h>
+#include <linux/regmap.h>
+#include <linux/rtc.h>
+#include <linux/timer.h>
+#include <linux/workqueue.h>
+
+static int dbg_enable = 0;
+module_param_named(dbg_level, dbg_enable, int, 0644);
+
+#define DBG(args...) \
+       do { \
+               if (dbg_enable) { \
+                       pr_info(args); \
+               } \
+       } while (0)
+
+#define CG_INFO(fmt, args...) pr_info("rk818-charger: "fmt, ##args)
+
+#define DEFAULT_CHRG_CURRENT   1400
+#define DEFAULT_INPUT_CURRENT  2000
+#define DEFAULT_CHRG_VOLTAGE   4200
+#define SAMPLE_RES_10MR                10
+#define SAMPLE_RES_20MR                20
+#define SAMPLE_RES_DIV1                1
+#define SAMPLE_RES_DIV2                2
+
+/* RK818_USB_CTRL_REG */
+#define INPUT_CUR450MA         (0x00)
+#define INPUT_CUR1500MA                (0x05)
+#define INPUT_CUR_MSK          (0x0f)
+/* RK818_CHRG_CTRL_REG3 */
+#define CHRG_FINISH_MODE_MSK   BIT(5)
+#define CHRG_FINISH_ANA_SIGNAL (0)
+#define CHRG_FINISH_DIG_SIGNAL BIT(5)
+/* RK818_SUP_STS_REG */
+#define BAT_EXS                        BIT(7)
+#define USB_VLIMIT_EN          BIT(3)
+#define USB_CLIMIT_EN          BIT(2)
+/* RK818_CHRG_CTRL_REG1 */
+#define CHRG_EN                        BIT(7)
+/* RK818_INT_STS_MSK_REG2 */
+#define CHRG_CVTLMT_INT_MSK    BIT(6)
+#define PLUG_OUT_MSK           BIT(1)
+#define PLUG_IN_MSK            BIT(0)
+/* RK818_VB_MON_REG */
+#define PLUG_IN_STS            BIT(6)
+/* RK818_TS_CTRL_REG */
+#define GG_EN                  BIT(7)
+
+#define DRIVER_VERSION         "1.0"
+
+extern void rk_send_wakeup_key(void);
+
+static const u16 chrg_vol_sel_array[] = {
+       4050, 4100, 4150, 4200, 4250, 4300, 4350
+};
+
+static const u16 chrg_cur_sel_array[] = {
+       1000, 1200, 1400, 1600, 1800, 2000, 2250, 2400, 2600, 2800, 3000
+};
+
+static const u16 chrg_cur_input_array[] = {
+       450, 800, 850, 1000, 1250, 1500, 1750, 2000, 2250, 2500, 2750, 3000
+};
+
+enum charger_t {
+       USB_TYPE_UNKNOWN_CHARGER,
+       USB_TYPE_NONE_CHARGER,
+       USB_TYPE_USB_CHARGER,
+       USB_TYPE_AC_CHARGER,
+       USB_TYPE_CDP_CHARGER,
+       DC_TYPE_DC_CHARGER,
+       DC_TYPE_NONE_CHARGER,
+};
+
+struct charger_platform_data {
+       u32 max_input_current;
+       u32 max_chrg_current;
+       u32 max_chrg_voltage;
+       u32 pwroff_vol;
+       u32 power_dc2otg;
+       u32 dc_det_level;
+       int dc_det_pin;
+       bool support_dc_det;
+       int virtual_power;
+       int sample_res;
+       bool extcon;
+};
+
+struct rk818_charger {
+       struct platform_device *pdev;
+       struct device *dev;
+       struct rk808 *rk818;
+       struct regmap *regmap;
+       struct power_supply *ac_psy;
+       struct power_supply *usb_psy;
+       struct extcon_dev *cable_edev;
+       struct charger_platform_data *pdata;
+       struct workqueue_struct *usb_charger_wq;
+       struct workqueue_struct *dc_charger_wq;
+       struct workqueue_struct *finish_sig_wq;
+       struct delayed_work dc_work;
+       struct delayed_work usb_work;
+       struct delayed_work host_work;
+       struct delayed_work discnt_work;
+       struct delayed_work finish_sig_work;
+       struct delayed_work irq_work;
+       struct notifier_block bc_nb;
+       struct notifier_block cable_cg_nb;
+       struct notifier_block cable_host_nb;
+       struct notifier_block cable_discnt_nb;
+       unsigned int bc_event;
+       enum charger_t usb_charger;
+       enum charger_t dc_charger;
+       u8 ac_in;
+       u8 usb_in;
+       u8 otg_in;
+       u8 dc_in;
+       u8 prop_status;
+       u8 chrg_voltage;
+       u8 chrg_input;
+       u8 chrg_current;
+       u8 res_div;
+       u8 int_msk_reg2;
+       u8 plug_in_irq;
+       u8 plug_out_irq;
+};
+
+static int rk818_reg_read(struct rk818_charger *cg, u8 reg)
+{
+       int ret, val;
+
+       ret = regmap_read(cg->regmap, reg, &val);
+       if (ret)
+               dev_err(cg->dev, "i2c read reg: 0x%2x failed\n", reg);
+
+       return val;
+}
+
+static int rk818_reg_write(struct rk818_charger *cg, u8 reg, u8 buf)
+{
+       int ret;
+
+       ret = regmap_write(cg->regmap, reg, buf);
+       if (ret)
+               dev_err(cg->dev, "i2c write reg: 0x%2x failed\n", reg);
+
+       return ret;
+}
+
+static int rk818_reg_set_bits(struct rk818_charger *cg, u8 reg, u8 mask, u8 buf)
+{
+       int ret;
+
+       ret = regmap_update_bits(cg->regmap, reg, mask, buf);
+       if (ret)
+               dev_err(cg->dev, "i2c set reg: 0x%2x failed\n", reg);
+
+       return ret;
+}
+
+static int rk818_reg_clear_bits(struct rk818_charger *cg, u8 reg, u8 mask)
+{
+       int ret;
+
+       ret = regmap_update_bits(cg->regmap, reg, mask, 0);
+       if (ret)
+               dev_err(cg->dev, "i2c clr reg: 0x%02x failed\n", reg);
+
+       return ret;
+}
+
+static int rk818_cg_online(struct rk818_charger *cg)
+{
+       return (cg->ac_in | cg->usb_in | cg->dc_in);
+}
+
+static int rk818_cg_get_dsoc(struct rk818_charger *cg)
+{
+       return rk818_reg_read(cg, RK818_SOC_REG);
+}
+
+static int rk818_cg_get_avg_current(struct rk818_charger *cg)
+{
+       int cur, val = 0;
+
+       val |= rk818_reg_read(cg, RK818_BAT_CUR_AVG_REGL) << 0;
+       val |= rk818_reg_read(cg, RK818_BAT_CUR_AVG_REGH) << 8;
+
+       if (val & 0x800)
+               val -= 4096;
+       cur = val * cg->res_div * 1506 / 1000;
+
+       return cur;
+}
+
+static u64 get_boot_sec(void)
+{
+       struct timespec ts;
+
+       get_monotonic_boottime(&ts);
+
+       return ts.tv_sec;
+}
+
+static int rk818_cg_lowpwr_check(struct rk818_charger *cg)
+{
+       u8 buf;
+       static u64 time;
+       int current_avg, dsoc, fake_offline = 0;
+
+       buf = rk818_reg_read(cg, RK818_TS_CTRL_REG);
+       if (!(buf & GG_EN))
+               return fake_offline;
+
+       dsoc = rk818_cg_get_dsoc(cg);
+       current_avg = rk818_cg_get_avg_current(cg);
+       if ((current_avg < 0) && (dsoc == 0)) {
+               if (!time)
+                       time = get_boot_sec();
+               if ((get_boot_sec() - time) >= 30) {
+                       fake_offline = 1;
+                       CG_INFO("low power....soc=%d, current=%d\n",
+                               dsoc, current_avg);
+               }
+       } else {
+               time = 0;
+               fake_offline = 0;
+       }
+
+       DBG("<%s>. t=%lld, dsoc=%d, current=%d, fake_offline=%d\n",
+           __func__, get_boot_sec() - time, dsoc, current_avg, fake_offline);
+
+       return fake_offline;
+}
+
+static enum power_supply_property rk818_ac_props[] = {
+       POWER_SUPPLY_PROP_ONLINE,
+       POWER_SUPPLY_PROP_STATUS,
+};
+
+static enum power_supply_property rk818_usb_props[] = {
+       POWER_SUPPLY_PROP_ONLINE,
+       POWER_SUPPLY_PROP_STATUS,
+};
+
+static int rk818_cg_ac_get_property(struct power_supply *psy,
+                                   enum power_supply_property psp,
+                                   union power_supply_propval *val)
+{
+       struct rk818_charger *cg = power_supply_get_drvdata(psy);
+       int fake_offline = 0, ret = 0;
+
+       if (rk818_cg_online(cg))
+               fake_offline = rk818_cg_lowpwr_check(cg);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               if (cg->pdata->virtual_power)
+                       val->intval = 1;
+               else if (fake_offline)
+                       val->intval = 0;
+               else
+                       val->intval = (cg->ac_in | cg->dc_in);
+
+               DBG("report online: %d\n", val->intval);
+               break;
+       case POWER_SUPPLY_PROP_STATUS:
+               if (cg->pdata->virtual_power)
+                       val->intval = POWER_SUPPLY_STATUS_CHARGING;
+               else if (fake_offline)
+                       val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+               else
+                       val->intval = cg->prop_status;
+
+               DBG("report prop: %d\n", val->intval);
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
+static int rk818_cg_usb_get_property(struct power_supply *psy,
+                                    enum power_supply_property psp,
+                                    union power_supply_propval *val)
+{
+       struct rk818_charger *cg = power_supply_get_drvdata(psy);
+       int fake_offline, ret = 0;
+
+       if (rk818_cg_online(cg))
+               fake_offline = rk818_cg_lowpwr_check(cg);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               if (cg->pdata->virtual_power)
+                       val->intval = 1;
+               else if (fake_offline)
+                       val->intval = 0;
+               else
+                       val->intval = cg->usb_in;
+
+               DBG("report online: %d\n", val->intval);
+               break;
+       case POWER_SUPPLY_PROP_STATUS:
+               if (cg->pdata->virtual_power)
+                       val->intval = POWER_SUPPLY_STATUS_CHARGING;
+               else if (fake_offline)
+                       val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+               else
+                       val->intval = cg->prop_status;
+
+               DBG("report prop: %d\n", val->intval);
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
+static const struct power_supply_desc rk818_ac_desc = {
+       .name           = "ac",
+       .type           = POWER_SUPPLY_TYPE_MAINS,
+       .properties     = rk818_ac_props,
+       .num_properties = ARRAY_SIZE(rk818_ac_props),
+       .get_property   = rk818_cg_ac_get_property,
+};
+
+static const struct power_supply_desc rk818_usb_desc = {
+       .name           = "usb",
+       .type           = POWER_SUPPLY_TYPE_USB,
+       .properties     = rk818_usb_props,
+       .num_properties = ARRAY_SIZE(rk818_usb_props),
+       .get_property   = rk818_cg_usb_get_property,
+};
+
+static int rk818_cg_init_power_supply(struct rk818_charger *cg)
+{
+       struct power_supply_config psy_cfg = { .drv_data = cg, };
+
+       cg->usb_psy = devm_power_supply_register(cg->dev, &rk818_usb_desc,
+                                                &psy_cfg);
+       if (IS_ERR(cg->usb_psy)) {
+               dev_err(cg->dev, "register usb power supply fail\n");
+               return PTR_ERR(cg->usb_psy);
+       }
+
+       cg->ac_psy = devm_power_supply_register(cg->dev, &rk818_ac_desc,
+                                               &psy_cfg);
+       if (IS_ERR(cg->ac_psy)) {
+               dev_err(cg->dev, "register ac power supply fail\n");
+               return PTR_ERR(cg->ac_psy);
+       }
+
+       return 0;
+}
+
+static void rk818_cg_pr_info(struct rk818_charger *cg)
+{
+       u8 usb_ctrl, chrg_ctrl1;
+
+       usb_ctrl = rk818_reg_read(cg, RK818_USB_CTRL_REG);
+       chrg_ctrl1 = rk818_reg_read(cg, RK818_CHRG_CTRL_REG1);
+       CG_INFO("ac=%d usb=%d dc=%d otg=%d v=%d chrg=%d input=%d virt=%d\n",
+               cg->ac_in, cg->usb_in, cg->dc_in, cg->otg_in,
+               chrg_vol_sel_array[(chrg_ctrl1 & 0x70) >> 4],
+               chrg_cur_sel_array[chrg_ctrl1 & 0x0f] * cg->res_div,
+               chrg_cur_input_array[usb_ctrl & 0x0f],
+               cg->pdata->virtual_power);
+}
+
+static bool is_battery_exist(struct rk818_charger *cg)
+{
+       return (rk818_reg_read(cg, RK818_SUP_STS_REG) & BAT_EXS) ? true : false;
+}
+
+static void rk818_cg_set_input_current(struct rk818_charger *cg,
+                                      int input_current)
+{
+       u8 usb_ctrl;
+
+       if (cg->pdata->virtual_power) {
+               CG_INFO("warning: virtual power mode...\n");
+               input_current = cg->chrg_input;
+       }
+
+       usb_ctrl = rk818_reg_read(cg, RK818_USB_CTRL_REG);
+       usb_ctrl &= ~INPUT_CUR_MSK;
+       usb_ctrl |= (input_current);
+       rk818_reg_write(cg, RK818_USB_CTRL_REG, usb_ctrl);
+}
+
+static void rk818_cg_set_finish_sig(struct rk818_charger *cg, int mode)
+{
+       u8 buf;
+
+       buf = rk818_reg_read(cg, RK818_CHRG_CTRL_REG3);
+       buf &= ~CHRG_FINISH_MODE_MSK;
+       buf |= mode;
+       rk818_reg_write(cg, RK818_CHRG_CTRL_REG3, buf);
+}
+
+static void rk818_cg_finish_sig_work(struct work_struct *work)
+{
+       struct rk818_charger *cg;
+
+       cg = container_of(work, struct rk818_charger, finish_sig_work.work);
+       if (rk818_cg_online(cg))
+               rk818_cg_set_finish_sig(cg, CHRG_FINISH_DIG_SIGNAL);
+       else
+               rk818_cg_set_finish_sig(cg, CHRG_FINISH_ANA_SIGNAL);
+}
+
+static void rk818_cg_set_chrg_param(struct rk818_charger *cg,
+                                   enum charger_t charger)
+{
+       u8 buf;
+
+       switch (charger) {
+       case USB_TYPE_NONE_CHARGER:
+               cg->usb_in = 0;
+               cg->ac_in = 0;
+               if (cg->dc_in == 0) {
+                       cg->prop_status = POWER_SUPPLY_STATUS_DISCHARGING;
+                       rk818_cg_set_input_current(cg, INPUT_CUR450MA);
+               }
+               power_supply_changed(cg->usb_psy);
+               power_supply_changed(cg->ac_psy);
+               break;
+       case USB_TYPE_USB_CHARGER:
+               cg->usb_in = 1;
+               cg->ac_in = 0;
+               cg->prop_status = POWER_SUPPLY_STATUS_CHARGING;
+               if (cg->dc_in == 0)
+                       rk818_cg_set_input_current(cg, INPUT_CUR450MA);
+               power_supply_changed(cg->usb_psy);
+               power_supply_changed(cg->ac_psy);
+               break;
+       case USB_TYPE_AC_CHARGER:
+       case USB_TYPE_CDP_CHARGER:
+               cg->ac_in = 1;
+               cg->usb_in = 0;
+               cg->prop_status = POWER_SUPPLY_STATUS_CHARGING;
+               if (charger == USB_TYPE_AC_CHARGER)
+                       rk818_cg_set_input_current(cg, cg->chrg_input);
+               else
+                       rk818_cg_set_input_current(cg, INPUT_CUR1500MA);
+               power_supply_changed(cg->usb_psy);
+               power_supply_changed(cg->ac_psy);
+               break;
+       case DC_TYPE_DC_CHARGER:
+               cg->dc_in = 1;
+               cg->prop_status = POWER_SUPPLY_STATUS_CHARGING;
+               rk818_cg_set_input_current(cg, cg->chrg_input);
+               power_supply_changed(cg->usb_psy);
+               power_supply_changed(cg->ac_psy);
+               break;
+       case DC_TYPE_NONE_CHARGER:
+               cg->dc_in = 0;
+               buf = rk818_reg_read(cg, RK818_VB_MON_REG);
+               if ((buf & PLUG_IN_STS) == 0) {
+                       cg->ac_in = 0;
+                       cg->usb_in = 0;
+                       cg->prop_status = POWER_SUPPLY_STATUS_DISCHARGING;
+                       rk818_cg_set_input_current(cg, INPUT_CUR450MA);
+               } else if (cg->usb_in) {
+                       rk818_cg_set_input_current(cg, INPUT_CUR450MA);
+                       cg->prop_status = POWER_SUPPLY_STATUS_CHARGING;
+               }
+               power_supply_changed(cg->usb_psy);
+               power_supply_changed(cg->ac_psy);
+               break;
+       default:
+               cg->prop_status = POWER_SUPPLY_STATUS_DISCHARGING;
+               break;
+       }
+
+       if (rk818_cg_online(cg) && rk818_cg_get_dsoc(cg) == 100)
+               cg->prop_status = POWER_SUPPLY_STATUS_FULL;
+
+       if (cg->finish_sig_wq)
+               queue_delayed_work(cg->finish_sig_wq, &cg->finish_sig_work,
+                                  msecs_to_jiffies(1000));
+}
+
+static void rk818_cg_set_otg_state(struct rk818_charger *cg, int state)
+{
+       switch (state) {
+       case USB_OTG_POWER_ON:
+               rk818_reg_set_bits(cg, RK818_INT_STS_MSK_REG2,
+                                  PLUG_IN_MSK, PLUG_IN_MSK);
+               rk818_reg_set_bits(cg, RK818_INT_STS_MSK_REG2,
+                                  PLUG_OUT_MSK, PLUG_OUT_MSK);
+               rk818_reg_set_bits(cg, RK818_DCDC_EN_REG,
+                                  OTG_EN_MASK, OTG_EN_MASK);
+               break;
+       case USB_OTG_POWER_OFF:
+               rk818_reg_clear_bits(cg, RK818_INT_STS_MSK_REG2, PLUG_IN_MSK);
+               rk818_reg_clear_bits(cg, RK818_INT_STS_MSK_REG2, PLUG_OUT_MSK);
+               rk818_reg_clear_bits(cg, RK818_DCDC_EN_REG, OTG_EN_MASK);
+               break;
+       default:
+               dev_err(cg->dev, "error otg type\n");
+               break;
+       }
+}
+
+static enum charger_t rk818_cg_get_dc_state(struct rk818_charger *cg)
+{
+       int level;
+
+       if (!gpio_is_valid(cg->pdata->dc_det_pin))
+               return DC_TYPE_NONE_CHARGER;
+
+       level = gpio_get_value(cg->pdata->dc_det_pin);
+
+       return (level == cg->pdata->dc_det_level) ?
+               DC_TYPE_DC_CHARGER : DC_TYPE_NONE_CHARGER;
+}
+
+static void rk818_cg_dc_det_worker(struct work_struct *work)
+{
+       enum charger_t charger;
+       struct rk818_charger *cg = container_of(work,
+                       struct rk818_charger, dc_work.work);
+
+       charger = rk818_cg_get_dc_state(cg);
+       if (charger == DC_TYPE_DC_CHARGER) {
+               CG_INFO("detect dc charger in..\n");
+               rk818_cg_set_chrg_param(cg, DC_TYPE_DC_CHARGER);
+               /* check otg supply */
+               if (cg->otg_in && cg->pdata->power_dc2otg) {
+                       CG_INFO("otg power from dc adapter\n");
+                       rk818_cg_set_otg_state(cg, USB_OTG_POWER_OFF);
+               }
+       } else {
+               CG_INFO("detect dc charger out..\n");
+               rk818_cg_set_chrg_param(cg, DC_TYPE_NONE_CHARGER);
+               /* check otg supply, power on anyway */
+               if (cg->otg_in) {
+                       CG_INFO("disable charge, enable otg\n");
+                       rk818_cg_set_otg_state(cg, USB_OTG_POWER_ON);
+               }
+       }
+
+       rk_send_wakeup_key();
+       rk818_cg_pr_info(cg);
+}
+
+static u8 rk818_cg_decode_chrg_vol(struct rk818_charger *cg)
+{
+       u8 val, index;
+       u32 chrg_vol;
+
+       chrg_vol = cg->pdata->max_chrg_voltage;
+       for (index = 0; index < ARRAY_SIZE(chrg_vol_sel_array); index++) {
+               if (chrg_vol < chrg_vol_sel_array[index])
+                       break;
+               val = index << 4;
+       }
+
+       DBG("<%s>. vol=0x%x\n", __func__, val);
+       return val;
+}
+
+static u8 rk818_cg_decode_input_current(struct rk818_charger *cg)
+{
+       u8 val, index;
+       u32 input_current;
+
+       input_current = cg->pdata->max_input_current;
+       for (index = 0; index < ARRAY_SIZE(chrg_cur_input_array); index++) {
+               if (input_current < chrg_cur_input_array[index])
+                       break;
+               val = index <<  0;
+       }
+
+       DBG("<%s>. input=0x%x\n", __func__, val);
+       return val;
+}
+
+static u8 rk818_cg_decode_chrg_current(struct rk818_charger *cg)
+{
+       u8 val, index;
+       u32 chrg_current;
+
+       chrg_current = cg->pdata->max_chrg_current;
+       if (cg->pdata->sample_res == SAMPLE_RES_10MR) {
+               if (chrg_current > 2000)
+                       chrg_current /= cg->res_div;
+               else
+                       chrg_current = 1000;
+       }
+
+       for (index = 0; index < ARRAY_SIZE(chrg_cur_sel_array); index++) {
+               if (chrg_current < chrg_cur_sel_array[index])
+                       break;
+               val = index << 0;
+       }
+
+       DBG("<%s>. sel=0x%x\n", __func__, val);
+       return val;
+}
+
+static void rk818_cg_init_config(struct rk818_charger *cg)
+{
+       u8 usb_ctrl, sup_sts, chrg_ctrl1;
+
+       cg->chrg_voltage = rk818_cg_decode_chrg_vol(cg);
+       cg->chrg_current = rk818_cg_decode_chrg_current(cg);
+       cg->chrg_input = rk818_cg_decode_input_current(cg);
+
+       sup_sts = rk818_reg_read(cg, RK818_SUP_STS_REG);
+       usb_ctrl = rk818_reg_read(cg, RK818_USB_CTRL_REG);
+
+       /* set charge current and voltage */
+       usb_ctrl &= ~INPUT_CUR_MSK;
+       usb_ctrl |= cg->chrg_input;
+       chrg_ctrl1 = (CHRG_EN | cg->chrg_voltage | cg->chrg_current);
+
+       /* disable voltage limit and enable input current limit */
+       sup_sts &= ~USB_VLIMIT_EN;
+       sup_sts |= USB_CLIMIT_EN;
+
+       rk818_reg_write(cg, RK818_SUP_STS_REG, sup_sts);
+       rk818_reg_write(cg, RK818_USB_CTRL_REG, usb_ctrl);
+       rk818_reg_write(cg, RK818_CHRG_CTRL_REG1, chrg_ctrl1);
+}
+
+static int rk818_cg_charger_evt_notifier(struct notifier_block *nb,
+                                        unsigned long event, void *ptr)
+{
+       struct rk818_charger *cg =
+               container_of(nb, struct rk818_charger, cable_cg_nb);
+
+       queue_delayed_work(cg->usb_charger_wq, &cg->usb_work,
+                          msecs_to_jiffies(10));
+
+       return NOTIFY_DONE;
+}
+
+static int rk818_cg_discnt_evt_notfier(struct notifier_block *nb,
+                                      unsigned long event, void *ptr)
+{
+       struct rk818_charger *cg =
+               container_of(nb, struct rk818_charger, cable_discnt_nb);
+
+       queue_delayed_work(cg->usb_charger_wq, &cg->discnt_work,
+                          msecs_to_jiffies(10));
+
+       return NOTIFY_DONE;
+}
+
+static int rk818_cg_host_evt_notifier(struct notifier_block *nb,
+                                     unsigned long event, void *ptr)
+{
+       struct rk818_charger *cg =
+               container_of(nb, struct rk818_charger, cable_host_nb);
+
+       queue_delayed_work(cg->usb_charger_wq, &cg->host_work,
+                          msecs_to_jiffies(10));
+
+       return NOTIFY_DONE;
+}
+
+static int rk818_cg_bc_evt_notifier(struct notifier_block *nb,
+                                   unsigned long event, void *ptr)
+{
+       struct rk818_charger *cg =
+               container_of(nb, struct rk818_charger, bc_nb);
+
+       cg->bc_event = event;
+       queue_delayed_work(cg->usb_charger_wq, &cg->usb_work,
+                          msecs_to_jiffies(10));
+
+       return NOTIFY_DONE;
+}
+
+static void rk818_cg_bc_evt_worker(struct work_struct *work)
+{
+       struct rk818_charger *cg = container_of(work,
+                                       struct rk818_charger, usb_work.work);
+       const char *event_name[] = {"DISCNT", "USB", "AC", "CDP1.5A",
+                                   "UNKNOWN", "OTG ON", "OTG OFF"};
+
+       switch (cg->bc_event) {
+       case USB_BC_TYPE_DISCNT:
+               rk818_cg_set_chrg_param(cg, USB_TYPE_NONE_CHARGER);
+               break;
+       case USB_BC_TYPE_SDP:
+               rk818_cg_set_chrg_param(cg, USB_TYPE_USB_CHARGER);
+               break;
+       case USB_BC_TYPE_DCP:
+               rk818_cg_set_chrg_param(cg, USB_TYPE_AC_CHARGER);
+               break;
+       case USB_BC_TYPE_CDP:
+               rk818_cg_set_chrg_param(cg, USB_TYPE_CDP_CHARGER);
+               break;
+       case USB_OTG_POWER_ON:
+               cg->otg_in = 1;
+               if (cg->pdata->power_dc2otg && cg->dc_in) {
+                       CG_INFO("otg power from dc adapter\n");
+               } else {
+                       rk818_cg_set_otg_state(cg, USB_OTG_POWER_ON);
+                       CG_INFO("disable charge, enable otg\n");
+               }
+               break;
+       case USB_OTG_POWER_OFF:
+               cg->otg_in = 0;
+               rk818_cg_set_otg_state(cg, USB_OTG_POWER_OFF);
+               CG_INFO("enable charge, disable otg\n");
+               break;
+       default:
+               break;
+       }
+
+       CG_INFO("receive bc notifier event: %s..\n", event_name[cg->bc_event]);
+
+       rk818_cg_pr_info(cg);
+}
+
+static void rk818_cg_irq_delay_work(struct work_struct *work)
+{
+       struct rk818_charger *cg = container_of(work,
+                       struct rk818_charger, irq_work.work);
+
+       if (cg->plug_in_irq) {
+               CG_INFO("pmic: plug in\n");
+               cg->plug_in_irq = 0;
+               rk_send_wakeup_key();
+       } else if (cg->plug_out_irq) {
+               CG_INFO("pmic: plug out\n");
+               cg->plug_out_irq = 0;
+               rk818_cg_set_chrg_param(cg, USB_TYPE_NONE_CHARGER);
+               rk818_cg_set_chrg_param(cg, DC_TYPE_NONE_CHARGER);
+               rk_send_wakeup_key();
+               rk818_cg_pr_info(cg);
+       } else {
+               CG_INFO("pmic: unknown irq\n");
+       }
+}
+
+static irqreturn_t rk818_plug_in_isr(int irq, void *cg)
+{
+       struct rk818_charger *icg;
+
+       icg = (struct rk818_charger *)cg;
+       icg->plug_in_irq = 1;
+       queue_delayed_work(icg->usb_charger_wq, &icg->irq_work,
+                          msecs_to_jiffies(10));
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t rk818_plug_out_isr(int irq, void *cg)
+{
+       struct rk818_charger *icg;
+
+       icg = (struct rk818_charger *)cg;
+       icg->plug_out_irq = 1;
+       queue_delayed_work(icg->usb_charger_wq, &icg->irq_work,
+                          msecs_to_jiffies(10));
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t rk818_dc_det_isr(int irq, void *charger)
+{
+       struct rk818_charger *cg = (struct rk818_charger *)charger;
+
+       if (gpio_get_value(cg->pdata->dc_det_pin))
+               irq_set_irq_type(irq, IRQF_TRIGGER_LOW);
+       else
+               irq_set_irq_type(irq, IRQF_TRIGGER_HIGH);
+
+       queue_delayed_work(cg->dc_charger_wq, &cg->dc_work,
+                          msecs_to_jiffies(10));
+
+       return IRQ_HANDLED;
+}
+
+static int rk818_cg_init_irqs(struct rk818_charger *cg)
+{
+       struct rk808 *rk818 = cg->rk818;
+       struct platform_device *pdev = cg->pdev;
+       int ret, plug_in_irq, plug_out_irq;
+
+       plug_in_irq = regmap_irq_get_virq(rk818->irq_data, RK818_IRQ_PLUG_IN);
+       if (plug_in_irq < 0) {
+               dev_err(cg->dev, "plug_in_irq request failed!\n");
+               return plug_in_irq;
+       }
+
+       plug_out_irq = regmap_irq_get_virq(rk818->irq_data, RK818_IRQ_PLUG_OUT);
+       if (plug_out_irq < 0) {
+               dev_err(cg->dev, "plug_out_irq request failed!\n");
+               return plug_out_irq;
+       }
+
+       ret = devm_request_threaded_irq(cg->dev, plug_in_irq, NULL,
+                                       rk818_plug_in_isr,
+                                       IRQF_TRIGGER_RISING,
+                                       "rk818_plug_in", cg);
+       if (ret) {
+               dev_err(&pdev->dev, "plug_in_irq request failed!\n");
+               return ret;
+       }
+
+       ret = devm_request_threaded_irq(cg->dev, plug_out_irq, NULL,
+                                       rk818_plug_out_isr,
+                                       IRQF_TRIGGER_FALLING,
+                                       "rk818_plug_out", cg);
+       if (ret) {
+               dev_err(&pdev->dev, "plug_out_irq request failed!\n");
+               return ret;
+       }
+
+       INIT_DELAYED_WORK(&cg->irq_work, rk818_cg_irq_delay_work);
+
+       return 0;
+}
+
+static int rk818_cg_init_dc(struct rk818_charger *cg)
+{
+       int ret, level;
+       unsigned long irq_flags;
+       unsigned int dc_det_irq;
+
+       cg->dc_charger_wq = alloc_ordered_workqueue("%s",
+                               WQ_MEM_RECLAIM | WQ_FREEZABLE,
+                               "rk818-dc-wq");
+       INIT_DELAYED_WORK(&cg->dc_work, rk818_cg_dc_det_worker);
+       cg->dc_charger = DC_TYPE_NONE_CHARGER;
+
+       if (!cg->pdata->support_dc_det)
+               return 0;
+
+       ret = devm_gpio_request(cg->dev, cg->pdata->dc_det_pin, "rk818_dc_det");
+       if (ret < 0) {
+               dev_err(cg->dev, "failed to request gpio %d\n",
+                       cg->pdata->dc_det_pin);
+               return ret;
+       }
+
+       ret = gpio_direction_input(cg->pdata->dc_det_pin);
+       if (ret) {
+               dev_err(cg->dev, "failed to set gpio input\n");
+               return ret;
+       }
+
+       level = gpio_get_value(cg->pdata->dc_det_pin);
+       if (level == cg->pdata->dc_det_level)
+               cg->dc_charger = DC_TYPE_DC_CHARGER;
+       else
+               cg->dc_charger = DC_TYPE_NONE_CHARGER;
+
+       if (level)
+               irq_flags = IRQF_TRIGGER_LOW;
+       else
+               irq_flags = IRQF_TRIGGER_HIGH;
+
+       dc_det_irq = gpio_to_irq(cg->pdata->dc_det_pin);
+       ret = devm_request_irq(cg->dev, dc_det_irq, rk818_dc_det_isr,
+                              irq_flags, "rk818_dc_det", cg);
+       if (ret != 0) {
+               dev_err(cg->dev, "rk818_dc_det_irq request failed!\n");
+               return ret;
+       }
+
+       enable_irq_wake(dc_det_irq);
+
+       return 0;
+}
+
+static void rk818_cg_discnt_evt_worker(struct work_struct *work)
+{
+       struct rk818_charger *cg = container_of(work,
+                       struct rk818_charger, discnt_work.work);
+
+       if (extcon_get_cable_state_(cg->cable_edev, EXTCON_USB) == 0) {
+               CG_INFO("receive type-c notifier event: DISCNT...\n");
+               rk818_cg_set_chrg_param(cg, USB_TYPE_NONE_CHARGER);
+               rk818_cg_pr_info(cg);
+       }
+}
+
+static void rk818_cg_host_evt_worker(struct work_struct *work)
+{
+       struct rk818_charger *cg = container_of(work,
+                       struct rk818_charger, host_work.work);
+       struct extcon_dev *edev = cg->cable_edev;
+
+       /* Determine cable/charger type */
+       if (extcon_get_cable_state_(edev, EXTCON_USB_HOST) > 0) {
+               CG_INFO("receive type-c notifier event: OTG ON...\n");
+               cg->otg_in = 1;
+               if (cg->dc_in && cg->pdata->power_dc2otg) {
+                       CG_INFO("otg power from dc adapter\n");
+               } else {
+                       rk818_cg_set_otg_state(cg, USB_OTG_POWER_ON);
+                       CG_INFO("disable charge, enable otg\n");
+               }
+       } else if (extcon_get_cable_state_(edev, EXTCON_USB_HOST) == 0) {
+               CG_INFO("receive type-c notifier event: OTG OFF...\n");
+               cg->otg_in = 0;
+               rk818_cg_set_otg_state(cg, USB_OTG_POWER_OFF);
+               CG_INFO("enble charge, disable otg\n");
+       }
+
+       rk818_cg_pr_info(cg);
+}
+
+static void rk818_cg_charger_evt_worker(struct work_struct *work)
+{
+       struct rk818_charger *cg = container_of(work,
+                               struct rk818_charger, usb_work.work);
+       struct extcon_dev *edev = cg->cable_edev;
+       enum charger_t charger = USB_TYPE_UNKNOWN_CHARGER;
+       const char *event[] = {"UN", "NONE", "USB", "AC", "CDP1.5A"};
+
+       /* Determine cable/charger type */
+       if (extcon_get_cable_state_(edev, EXTCON_CHG_USB_SDP) > 0)
+               charger = USB_TYPE_USB_CHARGER;
+       else if (extcon_get_cable_state_(edev, EXTCON_CHG_USB_DCP) > 0)
+               charger = USB_TYPE_AC_CHARGER;
+       else if (extcon_get_cable_state_(edev, EXTCON_CHG_USB_CDP) > 0)
+               charger = USB_TYPE_CDP_CHARGER;
+
+       if (charger != USB_TYPE_UNKNOWN_CHARGER) {
+               CG_INFO("receive type-c notifier event: %s...\n",
+                       event[charger]);
+               rk818_cg_set_chrg_param(cg, charger);
+               rk818_cg_pr_info(cg);
+       }
+}
+
+static long rk818_cg_init_usb(struct rk818_charger *cg)
+{
+       enum charger_t charger;
+       enum bc_port_type bc_type;
+       struct extcon_dev *edev;
+       struct device *dev = cg->dev;
+       int ret;
+
+       cg->usb_charger_wq = alloc_ordered_workqueue("%s",
+                               WQ_MEM_RECLAIM | WQ_FREEZABLE,
+                               "rk818-usb-wq");
+       cg->usb_charger = USB_TYPE_NONE_CHARGER;
+
+       /* type-C */
+       if (cg->pdata->extcon) {
+               edev = extcon_get_edev_by_phandle(dev->parent, 0);
+               if (IS_ERR(edev)) {
+                       if (PTR_ERR(edev) != -EPROBE_DEFER)
+                               dev_err(dev, "Invalid or missing extcon\n");
+                       return PTR_ERR(edev);
+               }
+
+               /* Register chargers  */
+               INIT_DELAYED_WORK(&cg->usb_work, rk818_cg_charger_evt_worker);
+               cg->cable_cg_nb.notifier_call = rk818_cg_charger_evt_notifier;
+               ret = extcon_register_notifier(edev, EXTCON_CHG_USB_SDP,
+                                              &cg->cable_cg_nb);
+               if (ret < 0) {
+                       dev_err(dev, "failed to register notifier for SDP\n");
+                       return ret;
+               }
+
+               ret = extcon_register_notifier(edev, EXTCON_CHG_USB_DCP,
+                                              &cg->cable_cg_nb);
+               if (ret < 0) {
+                       dev_err(dev, "failed to register notifier for DCP\n");
+                       extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP,
+                                                  &cg->cable_cg_nb);
+                       return ret;
+               }
+
+               ret = extcon_register_notifier(edev, EXTCON_CHG_USB_CDP,
+                                              &cg->cable_cg_nb);
+               if (ret < 0) {
+                       dev_err(dev, "failed to register notifier for CDP\n");
+                       extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP,
+                                                  &cg->cable_cg_nb);
+                       extcon_unregister_notifier(edev, EXTCON_CHG_USB_DCP,
+                                                  &cg->cable_cg_nb);
+                       return ret;
+               }
+
+               /* Register host */
+               INIT_DELAYED_WORK(&cg->host_work, rk818_cg_host_evt_worker);
+               cg->cable_host_nb.notifier_call = rk818_cg_host_evt_notifier;
+               ret = extcon_register_notifier(edev, EXTCON_USB_HOST,
+                                              &cg->cable_host_nb);
+               if (ret < 0) {
+                       dev_err(dev, "failed to register notifier for HOST\n");
+                       extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP,
+                                                  &cg->cable_cg_nb);
+                       extcon_unregister_notifier(edev, EXTCON_CHG_USB_DCP,
+                                                  &cg->cable_cg_nb);
+                       extcon_unregister_notifier(edev, EXTCON_CHG_USB_CDP,
+                                                  &cg->cable_cg_nb);
+
+                       return ret;
+               }
+
+               /* Register discnt usb */
+               INIT_DELAYED_WORK(&cg->discnt_work, rk818_cg_discnt_evt_worker);
+               cg->cable_discnt_nb.notifier_call = rk818_cg_discnt_evt_notfier;
+               ret = extcon_register_notifier(edev, EXTCON_USB,
+                                              &cg->cable_discnt_nb);
+               if (ret < 0) {
+                       dev_err(dev, "failed to register notifier for HOST\n");
+                       extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP,
+                                                  &cg->cable_cg_nb);
+                       extcon_unregister_notifier(edev, EXTCON_CHG_USB_DCP,
+                                                  &cg->cable_cg_nb);
+                       extcon_unregister_notifier(edev, EXTCON_CHG_USB_CDP,
+                                                  &cg->cable_cg_nb);
+                       extcon_unregister_notifier(edev, EXTCON_USB_HOST,
+                                                  &cg->cable_host_nb);
+                       return ret;
+               }
+
+               cg->cable_edev = edev;
+
+               CG_INFO("register typec extcon evt notifier\n");
+       } else {
+               INIT_DELAYED_WORK(&cg->usb_work, rk818_cg_bc_evt_worker);
+               cg->bc_nb.notifier_call = rk818_cg_bc_evt_notifier;
+               ret = rk_bc_detect_notifier_register(&cg->bc_nb, &bc_type);
+               if (ret) {
+                       dev_err(dev, "failed to register notifier for bc\n");
+                       return -EINVAL;
+               }
+
+               switch (bc_type) {
+               case USB_BC_TYPE_DISCNT:
+                       charger = USB_TYPE_NONE_CHARGER;
+                       break;
+               case USB_BC_TYPE_SDP:
+               case USB_BC_TYPE_CDP:
+                       charger = USB_TYPE_USB_CHARGER;
+                       break;
+               case USB_BC_TYPE_DCP:
+                       charger = USB_TYPE_AC_CHARGER;
+                       break;
+               default:
+                       charger = USB_TYPE_NONE_CHARGER;
+                       break;
+               }
+
+               cg->usb_charger = charger;
+               CG_INFO("register bc evt notifier\n");
+       }
+
+       return 0;
+}
+
+static void rk818_cg_init_finish_sig(struct rk818_charger *cg)
+{
+       if (rk818_cg_online(cg))
+               rk818_cg_set_finish_sig(cg, CHRG_FINISH_DIG_SIGNAL);
+       else
+               rk818_cg_set_finish_sig(cg, CHRG_FINISH_ANA_SIGNAL);
+
+       cg->finish_sig_wq = alloc_ordered_workqueue("%s",
+                               WQ_MEM_RECLAIM | WQ_FREEZABLE,
+                               "rk818-finish-sig-wq");
+       INIT_DELAYED_WORK(&cg->finish_sig_work, rk818_cg_finish_sig_work);
+}
+
+static void rk818_cg_init_charger_state(struct rk818_charger *cg)
+{
+       rk818_cg_init_config(cg);
+       rk818_cg_init_finish_sig(cg);
+       rk818_cg_set_chrg_param(cg, cg->dc_charger);
+       rk818_cg_set_chrg_param(cg, cg->usb_charger);
+       CG_INFO("ac=%d, usb=%d, dc=%d, otg=%d\n",
+               cg->ac_in, cg->usb_in, cg->dc_in, cg->otg_in);
+}
+
+#ifdef CONFIG_OF
+static int rk818_cg_parse_dt(struct rk818_charger *cg)
+{
+       struct device_node *np;
+       struct charger_platform_data *pdata;
+       enum of_gpio_flags flags;
+       struct device *dev = cg->dev;
+       int ret;
+
+       np = of_find_node_by_name(cg->pdev->dev.of_node, "battery");
+       if (!np) {
+               dev_err(dev, "battery node not found!\n");
+               return -ENODEV;
+       }
+
+       pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+       if (!pdata)
+               return -ENOMEM;
+
+       cg->pdata = pdata;
+       pdata->max_chrg_current = DEFAULT_CHRG_CURRENT;
+       pdata->max_input_current = DEFAULT_INPUT_CURRENT;
+       pdata->max_chrg_voltage = DEFAULT_CHRG_VOLTAGE;
+
+       pdata->extcon = device_property_read_bool(dev->parent, "extcon");
+
+       ret = of_property_read_u32(np, "max_chrg_current",
+                                  &pdata->max_chrg_current);
+       if (ret < 0)
+               dev_err(dev, "max_chrg_current missing!\n");
+
+       ret = of_property_read_u32(np, "max_input_current",
+                                  &pdata->max_input_current);
+       if (ret < 0)
+               dev_err(dev, "max_input_current missing!\n");
+
+       ret = of_property_read_u32(np, "max_chrg_voltage",
+                                  &pdata->max_chrg_voltage);
+       if (ret < 0)
+               dev_err(dev, "max_chrg_voltage missing!\n");
+
+       ret = of_property_read_u32(np, "virtual_power", &pdata->virtual_power);
+       if (ret < 0)
+               dev_err(dev, "virtual_power missing!\n");
+
+       ret = of_property_read_u32(np, "power_dc2otg", &pdata->power_dc2otg);
+       if (ret < 0)
+               dev_err(dev, "power_dc2otg missing!\n");
+
+       ret = of_property_read_u32(np, "sample_res", &pdata->sample_res);
+       if (ret < 0) {
+               pdata->sample_res = SAMPLE_RES_20MR;
+               dev_err(dev, "sample_res missing!\n");
+       }
+
+       if (!is_battery_exist(cg))
+               pdata->virtual_power = 1;
+
+       cg->res_div = (cg->pdata->sample_res == SAMPLE_RES_20MR) ?
+                      SAMPLE_RES_DIV1 : SAMPLE_RES_DIV2;
+
+       if (!of_find_property(np, "dc_det_gpio", &ret)) {
+               pdata->support_dc_det = false;
+               CG_INFO("not support dc\n");
+       } else {
+               pdata->support_dc_det = true;
+               pdata->dc_det_pin = of_get_named_gpio_flags(np, "dc_det_gpio",
+                                                           0, &flags);
+               if (gpio_is_valid(pdata->dc_det_pin)) {
+                       CG_INFO("support dc\n");
+                       pdata->dc_det_level = (flags & OF_GPIO_ACTIVE_LOW) ?
+                                              0 : 1;
+               } else {
+                       dev_err(dev, "invalid dc det gpio!\n");
+                       return -EINVAL;
+               }
+       }
+
+       DBG("input_current:%d\n"
+           "chrg_current:%d\n"
+           "chrg_voltage:%d\n"
+           "sample_res:%d\n"
+           "extcon:%d\n"
+           "virtual_power:%d\n"
+           "power_dc2otg:%d\n",
+           pdata->max_input_current, pdata->max_chrg_current,
+           pdata->max_chrg_voltage, pdata->sample_res, pdata->extcon,
+           pdata->virtual_power, pdata->power_dc2otg);
+
+       return 0;
+}
+#else
+static int rk818_cg_parse_dt(struct rk818_charger *cg)
+{
+       return -ENODEV;
+}
+#endif
+
+static int rk818_charger_probe(struct platform_device *pdev)
+{
+       struct rk808 *rk818 = dev_get_drvdata(pdev->dev.parent);
+       struct rk818_charger *cg;
+       int ret;
+
+       cg = devm_kzalloc(&pdev->dev, sizeof(*cg), GFP_KERNEL);
+       if (!cg)
+               return -ENOMEM;
+
+       cg->rk818 = rk818;
+       cg->pdev = pdev;
+       cg->dev = &pdev->dev;
+       cg->regmap = rk818->regmap;
+       platform_set_drvdata(pdev, cg);
+
+       ret = rk818_cg_parse_dt(cg);
+       if (ret < 0) {
+               dev_err(cg->dev, "parse dt failed!\n");
+               return ret;
+       }
+
+       ret = rk818_cg_init_irqs(cg);
+       if (ret != 0) {
+               dev_err(cg->dev, "init irqs failed!\n");
+               return ret;
+       }
+
+       ret = rk818_cg_init_dc(cg);
+       if (ret) {
+               dev_err(cg->dev, "init dc failed!\n");
+               return ret;
+       }
+
+       ret = rk818_cg_init_usb(cg);
+       if (ret) {
+               dev_err(cg->dev, "init usb failed!\n");
+               return ret;
+       }
+
+       ret = rk818_cg_init_power_supply(cg);
+       if (ret) {
+               dev_err(cg->dev, "init power supply fail!\n");
+               return ret;
+       }
+
+       rk818_cg_init_charger_state(cg);
+
+       CG_INFO("driver version: %s\n", DRIVER_VERSION);
+
+       return ret;
+}
+
+static int rk818_charger_suspend(struct platform_device *pdev,
+                                pm_message_t state)
+{
+       struct rk818_charger *cg = platform_get_drvdata(pdev);
+
+       if (cg->otg_in && cg->dc_in) {
+               cg->int_msk_reg2 = rk818_reg_read(cg, RK818_INT_STS_MSK_REG2);
+               rk818_reg_set_bits(cg, RK818_INT_STS_MSK_REG2,
+                                  CHRG_CVTLMT_INT_MSK, CHRG_CVTLMT_INT_MSK);
+       }
+
+       return 0;
+}
+
+static int rk818_charger_resume(struct platform_device *pdev)
+{
+       struct rk818_charger *cg = platform_get_drvdata(pdev);
+
+       if (cg->otg_in && cg->dc_in)
+               rk818_reg_write(cg, RK818_INT_STS_MSK_REG2, cg->int_msk_reg2);
+
+       return 0;
+}
+
+static void rk818_charger_shutdown(struct platform_device *pdev)
+{
+       struct rk818_charger *cg = platform_get_drvdata(pdev);
+
+       cancel_delayed_work_sync(&cg->host_work);
+       cancel_delayed_work_sync(&cg->usb_work);
+       cancel_delayed_work_sync(&cg->discnt_work);
+       cancel_delayed_work_sync(&cg->dc_work);
+       cancel_delayed_work_sync(&cg->finish_sig_work);
+       cancel_delayed_work_sync(&cg->irq_work);
+       destroy_workqueue(cg->usb_charger_wq);
+       destroy_workqueue(cg->dc_charger_wq);
+       destroy_workqueue(cg->finish_sig_wq);
+
+       if (cg->pdata->extcon) {
+               extcon_unregister_notifier(cg->cable_edev, EXTCON_CHG_USB_SDP,
+                                          &cg->cable_cg_nb);
+               extcon_unregister_notifier(cg->cable_edev, EXTCON_CHG_USB_DCP,
+                                          &cg->cable_cg_nb);
+               extcon_unregister_notifier(cg->cable_edev, EXTCON_CHG_USB_CDP,
+                                          &cg->cable_cg_nb);
+               extcon_unregister_notifier(cg->cable_edev, EXTCON_USB_HOST,
+                                          &cg->cable_host_nb);
+               extcon_unregister_notifier(cg->cable_edev, EXTCON_USB,
+                                          &cg->cable_discnt_nb);
+       } else {
+               rk_bc_detect_notifier_unregister(&cg->bc_nb);
+       }
+
+       rk818_cg_set_otg_state(cg, USB_OTG_POWER_OFF);
+       rk818_cg_set_finish_sig(cg, CHRG_FINISH_ANA_SIGNAL);
+
+       CG_INFO("shutdown: ac=%d usb=%d dc=%d otg=%d\n",
+               cg->ac_in, cg->usb_in, cg->dc_in, cg->otg_in);
+}
+
+static struct platform_driver rk818_charger_driver = {
+       .probe = rk818_charger_probe,
+       .suspend = rk818_charger_suspend,
+       .resume = rk818_charger_resume,
+       .shutdown = rk818_charger_shutdown,
+       .driver = {
+               .name   = "rk818-charger",
+       },
+};
+
+static int __init charger_init(void)
+{
+       return platform_driver_register(&rk818_charger_driver);
+}
+module_init(charger_init);
+
+static void __exit charger_exit(void)
+{
+       platform_driver_unregister(&rk818_charger_driver);
+}
+module_exit(charger_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:rk818-charger");
+MODULE_AUTHOR("chenjh<chenjh@rock-chips.com>");