power: rk818-charger: add TS2 voltage detect when update input current
authorchenjh <chenjh@rock-chips.com>
Wed, 17 May 2017 09:27:56 +0000 (17:27 +0800)
committerHuang, Tao <huangtao@rock-chips.com>
Fri, 19 May 2017 07:31:03 +0000 (15:31 +0800)
rk818's input charge voltage limit function doesn't works well. If software
set input current over than charger's max support value, rk818 may cause
charger over current protect which means disconnecting.
To solve this problem, we need to detect vbus voltage by TS2 pin, if vbus
is upper than 4.4v, we can safely adjust input current step by step from
low to high until meeting the target input current value.

Change-Id: I01d63974f251ad8ef0037158b66f4b85d3928baf
Signed-off-by: chenjh <chenjh@rock-chips.com>
drivers/power/rk818_charger.c

index c38d4828f4b5f6784381e0ae2bfe0e728b74ceae..00146303d27ea3aed701796d5e7c480e5f457faa 100644 (file)
@@ -57,6 +57,8 @@ module_param_named(dbg_level, dbg_enable, int, 0644);
 
 /* RK818_USB_CTRL_REG */
 #define INPUT_CUR450MA         (0x00)
+#define INPUT_CUR80MA          (0x01)
+#define INPUT_CUR850MA         (0x02)
 #define INPUT_CUR1500MA                (0x05)
 #define INPUT_CUR_MSK          (0x0f)
 /* RK818_CHRG_CTRL_REG3 */
@@ -77,9 +79,17 @@ module_param_named(dbg_level, dbg_enable, int, 0644);
 #define PLUG_IN_STS            BIT(6)
 /* RK818_TS_CTRL_REG */
 #define GG_EN                  BIT(7)
+#define TS2_FUN_ADC            BIT(5)
+/* RK818_ADC_CTRL_REG */
+#define ADC_TS2_EN             BIT(4)
 
 #define DRIVER_VERSION         "1.0"
 
+#define DEFAULT_TS2_THRESHOLD_VOL      4350
+#define DEFAULT_TS2_VALID_VOL          1000
+#define DEFAULT_TS2_VOL_MULTI          0
+#define DEFAULT_TS2_CHECK_CNT          5
+
 static const u16 chrg_vol_sel_array[] = {
        4050, 4100, 4150, 4200, 4250, 4300, 4350
 };
@@ -115,6 +125,7 @@ struct charger_platform_data {
        int sample_res;
        int otg5v_suspend_enable;
        bool extcon;
+       int ts2_vol_multi;
 };
 
 struct rk818_charger {
@@ -129,12 +140,14 @@ struct rk818_charger {
        struct workqueue_struct *usb_charger_wq;
        struct workqueue_struct *dc_charger_wq;
        struct workqueue_struct *finish_sig_wq;
+       struct workqueue_struct *ts2_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 delayed_work ts2_vol_work;
        struct notifier_block bc_nb;
        struct notifier_block cable_cg_nb;
        struct notifier_block cable_host_nb;
@@ -226,6 +239,22 @@ static int rk818_cg_get_avg_current(struct rk818_charger *cg)
        return cur;
 }
 
+static int rk818_cg_get_ts2_voltage(struct rk818_charger *cg)
+{
+       u32 val = 0;
+       int voltage;
+
+       val |= rk818_reg_read(cg, RK818_TS2_ADC_REGL) << 0;
+       val |= rk818_reg_read(cg, RK818_TS2_ADC_REGH) << 8;
+
+       /* refer voltage 2.2V, 12bit adc accuracy */
+       voltage = val * 2200 * cg->pdata->ts2_vol_multi / 4095;
+
+       DBG("********* ts2 adc=%d, vol=%d\n", val, voltage);
+
+       return voltage;
+}
+
 static u64 get_boot_sec(void)
 {
        struct timespec ts;
@@ -477,17 +506,32 @@ static void rk818_cg_set_chrg_param(struct rk818_charger *cg,
                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
+               if (charger == USB_TYPE_AC_CHARGER) {
+                       if (cg->pdata->ts2_vol_multi) {
+                               rk818_cg_set_input_current(cg, INPUT_CUR450MA);
+                               queue_delayed_work(cg->ts2_wq,
+                                                  &cg->ts2_vol_work,
+                                                  msecs_to_jiffies(0));
+                       } else {
+                               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);
+               if (cg->pdata->ts2_vol_multi) {
+                       rk818_cg_set_input_current(cg, INPUT_CUR450MA);
+                       queue_delayed_work(cg->ts2_wq,
+                                          &cg->ts2_vol_work,
+                                          msecs_to_jiffies(0));
+               } else {
+                       rk818_cg_set_input_current(cg, cg->chrg_input);
+               }
                power_supply_changed(cg->usb_psy);
                power_supply_changed(cg->ac_psy);
                break;
@@ -672,6 +716,63 @@ static void rk818_cg_init_config(struct rk818_charger *cg)
        rk818_reg_write(cg, RK818_CHRG_CTRL_REG1, chrg_ctrl1);
 }
 
+static void rk818_ts2_vol_work(struct work_struct *work)
+{
+       struct rk818_charger *cg;
+       int ts2_vol, input_current, invalid_cnt = 0, confirm_cnt = 0;
+
+       cg = container_of(work, struct rk818_charger, ts2_vol_work.work);
+
+       input_current = INPUT_CUR80MA;
+       while (input_current < cg->chrg_input) {
+               msleep(100);
+               ts2_vol = rk818_cg_get_ts2_voltage(cg);
+
+               /* filter invalid voltage */
+               if (ts2_vol <= DEFAULT_TS2_VALID_VOL) {
+                       invalid_cnt++;
+                       DBG("%s: invalid ts2 voltage: %d\n, cnt=%d",
+                           __func__, ts2_vol, invalid_cnt);
+                       if (invalid_cnt < DEFAULT_TS2_CHECK_CNT)
+                               continue;
+
+                       /* if fail, set max input current as default */
+                       input_current = cg->chrg_input;
+                       rk818_cg_set_input_current(cg, input_current);
+                       break;
+               }
+
+               /* update input current */
+               if (ts2_vol >= DEFAULT_TS2_THRESHOLD_VOL) {
+                       /* update input current */
+                       input_current++;
+                       rk818_cg_set_input_current(cg, input_current);
+                       DBG("********* input=%d\n",
+                           chrg_cur_input_array[input_current & 0x0f]);
+               } else {
+                       /* confirm lower threshold voltage */
+                       confirm_cnt++;
+                       if (confirm_cnt < DEFAULT_TS2_CHECK_CNT) {
+                               DBG("%s: confirm ts2 voltage: %d\n, cnt=%d",
+                                   __func__, ts2_vol, confirm_cnt);
+                               continue;
+                       }
+
+                       /* trigger threshold, so roll back 1 step */
+                       input_current--;
+                       if (input_current == INPUT_CUR80MA ||
+                           input_current < 0)
+                               input_current = INPUT_CUR450MA;
+                       rk818_cg_set_input_current(cg, input_current);
+                       break;
+               }
+       }
+
+       if (input_current != cg->chrg_input)
+               CG_INFO("adjust input current: %dma\n",
+                       chrg_cur_input_array[input_current & 0x0f]);
+}
+
 static int rk818_cg_charger_evt_notifier(struct notifier_block *nb,
                                         unsigned long event, void *ptr)
 {
@@ -1111,6 +1212,32 @@ static void rk818_cg_init_finish_sig(struct rk818_charger *cg)
        INIT_DELAYED_WORK(&cg->finish_sig_work, rk818_cg_finish_sig_work);
 }
 
+static void rk818_cg_init_ts2_detect(struct rk818_charger *cg)
+{
+       u8 buf;
+
+       cg->ts2_wq = alloc_ordered_workqueue("%s",
+                               WQ_MEM_RECLAIM | WQ_FREEZABLE,
+                               "rk818-ts2-wq");
+       INIT_DELAYED_WORK(&cg->ts2_vol_work, rk818_ts2_vol_work);
+
+       if (!cg->pdata->ts2_vol_multi)
+               return;
+
+       /* TS2 adc mode */
+       buf = rk818_reg_read(cg, RK818_TS_CTRL_REG);
+       buf |= TS2_FUN_ADC;
+       rk818_reg_write(cg, RK818_TS_CTRL_REG, buf);
+
+       /* TS2 adc enable */
+       buf = rk818_reg_read(cg, RK818_ADC_CTRL_REG);
+       buf |= ADC_TS2_EN;
+       rk818_reg_write(cg, RK818_ADC_CTRL_REG, buf);
+
+       CG_INFO("enable ts2 voltage detect, multi=%d\n",
+               cg->pdata->ts2_vol_multi);
+}
+
 static void rk818_cg_init_charger_state(struct rk818_charger *cg)
 {
        rk818_cg_init_config(cg);
@@ -1183,6 +1310,9 @@ static int rk818_cg_parse_dt(struct rk818_charger *cg)
                dev_err(dev, "otg5v_suspend_enable missing!\n");
        }
 
+       ret = of_property_read_u32(np, "ts2_vol_multi",
+                                  &pdata->ts2_vol_multi);
+
        if (!is_battery_exist(cg))
                pdata->virtual_power = 1;
 
@@ -1211,11 +1341,12 @@ static int rk818_cg_parse_dt(struct rk818_charger *cg)
            "chrg_voltage:%d\n"
            "sample_res:%d\n"
            "extcon:%d\n"
+           "ts2_vol_multi:%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);
+           pdata->ts2_vol_multi, pdata->virtual_power, pdata->power_dc2otg);
 
        return 0;
 }
@@ -1254,6 +1385,8 @@ static int rk818_charger_probe(struct platform_device *pdev)
                return ret;
        }
 
+       rk818_cg_init_ts2_detect(cg);
+
        ret = rk818_cg_init_dc(cg);
        if (ret) {
                dev_err(cg->dev, "init dc failed!\n");
@@ -1293,6 +1426,8 @@ static void rk818_charger_shutdown(struct platform_device *pdev)
        cancel_delayed_work_sync(&cg->dc_work);
        cancel_delayed_work_sync(&cg->finish_sig_work);
        cancel_delayed_work_sync(&cg->irq_work);
+       cancel_delayed_work_sync(&cg->ts2_vol_work);
+       destroy_workqueue(cg->ts2_wq);
        destroy_workqueue(cg->usb_charger_wq);
        destroy_workqueue(cg->dc_charger_wq);
        destroy_workqueue(cg->finish_sig_wq);