// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../inc/mt6360_pmu.h" #include "../inc/mt6360_pmu_chg.h" #include #include #include #include #include #ifdef CONFIG_TCPC_CLASS #include #endif #define MT6360_PMU_CHG_DRV_VERSION "1.0.8_MTK" #define PHY_MODE_BC11_SET 1 #define PHY_MODE_BC11_CLR 2 void __attribute__ ((weak)) Charger_Detect_Init(void) { } void __attribute__ ((weak)) Charger_Detect_Release(void) { } struct tag_bootmode { u32 size; u32 tag; u32 bootmode; u32 boottype; }; enum mt6360_adc_channel { MT6360_ADC_VBUSDIV5, MT6360_ADC_VSYS, MT6360_ADC_VBAT, MT6360_ADC_IBUS, MT6360_ADC_IBAT, MT6360_ADC_NODUMP, MT6360_ADC_TEMP_JC = MT6360_ADC_NODUMP, MT6360_ADC_USBID, MT6360_ADC_TS, MT6360_ADC_MAX, }; static const char * const mt6360_adc_chan_list[] = { "VBUSDIV5", "VSYS", "VBAT", "IBUS", "IBAT", "TEMP_JC", "USBID", "TS", }; struct mt6360_pmu_chg_info { struct device *dev; struct mt6360_pmu_info *mpi; struct iio_channel *channels[MT6360_ADC_MAX]; struct charger_device *chg_dev; int hidden_mode_cnt; struct mutex hidden_mode_lock; struct mutex pe_lock; struct mutex aicr_lock; struct mutex tchg_lock; struct mutex ichg_lock; int tchg; u32 zcv; u32 ichg; u32 ichg_dis_chg; u32 bootmode; /* Charger type detection */ struct mutex chgdet_lock; bool attach; bool pwr_rdy; bool bc12_en; int psy_usb_type; #ifdef CONFIG_TCPC_CLASS bool tcpc_attach; /*dts:charger*/ //for bc1.2 power supply struct power_supply *chg_psy; /*type_c_port0*/ struct tcpc_device *tcpc; struct notifier_block pd_nb; /*chg_det*/ wait_queue_head_t attach_wq; atomic_t chrdet_start; struct task_struct *attach_task; struct mutex attach_lock; bool typec_attach; bool ignore_usb; bool bypass_chgdet; #else struct work_struct chgdet_work; #endif /* CONFIG_TCPC_CLASS */ /*power supply*/ struct power_supply_desc psy_desc; struct power_supply *psy; struct completion aicc_done; struct completion pumpx_done; atomic_t pe_complete; /* mivr */ atomic_t mivr_cnt; wait_queue_head_t mivr_wq; struct task_struct *mivr_task; /* unfinish pe pattern */ struct workqueue_struct *pe_wq; struct work_struct pe_work; u8 ctd_dischg_status; struct regulator_dev *otg_rdev; //otg_vbus union power_supply_propval *old_propval; }; /* for recive bat oc notify */ struct mt6360_pmu_chg_info *g_mpci; static const u32 mt6360_otg_oc_threshold[] = { 500000, 700000, 1100000, 1300000, 1800000, 2100000, 2400000, 3000000, }; /* uA */ enum mt6360_iinlmtsel { MT6360_IINLMTSEL_AICR_3250 = 0, MT6360_IINLMTSEL_CHG_TYPE, MT6360_IINLMTSEL_AICR, MT6360_IINLMTSEL_LOWER_LEVEL, }; enum mt6360_charging_status { MT6360_CHG_STATUS_READY = 0, MT6360_CHG_STATUS_PROGRESS, MT6360_CHG_STATUS_DONE, MT6360_CHG_STATUS_FAULT, MT6360_CHG_STATUS_MAX, }; enum mt6360_usbsw_state { MT6360_USBSW_CHG = 0, MT6360_USBSW_USB, }; enum mt6360_pmu_chg_type { MT6360_CHG_TYPE_NOVBUS = 0, MT6360_CHG_TYPE_UNDER_GOING, MT6360_CHG_TYPE_SDP, MT6360_CHG_TYPE_SDPNSTD, MT6360_CHG_TYPE_DCP, MT6360_CHG_TYPE_CDP, MT6360_CHG_TYPE_MAX, }; static enum power_supply_usb_type mt6360_pmu_chg_usb_types[] = { POWER_SUPPLY_USB_TYPE_UNKNOWN, POWER_SUPPLY_USB_TYPE_SDP, POWER_SUPPLY_USB_TYPE_DCP, POWER_SUPPLY_USB_TYPE_CDP, POWER_SUPPLY_USB_TYPE_C, POWER_SUPPLY_USB_TYPE_PD, POWER_SUPPLY_USB_TYPE_PD_DRP, POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID }; static const char *mt6360_chg_status_name[MT6360_CHG_STATUS_MAX] = { "ready", "progress", "done", "fault", }; static const struct mt6360_chg_platform_data def_platform_data = { .ichg = 2000000, /* uA */ .aicr = 500000, /* uA */ .mivr = 4400000, /* uV */ .cv = 4350000, /* uA */ .ieoc = 250000, /* uA */ .safety_timer = 12, /* hour */ #ifdef CONFIG_MTK_BIF_SUPPORT .ircmp_resistor = 0, /* uohm */ .ircmp_vclamp = 0, /* uV */ #else .ircmp_resistor = 25000, /* uohm */ .ircmp_vclamp = 32000, /* uV */ #endif .en_te = true, .en_wdt = true, .aicc_once = true, .post_aicc = true, .batoc_notify = false, .bc12_sel = 0, .chg_name = "primary_chg", }; /* ================== */ /* Internal Functions */ /* ================== */ static inline u32 mt6360_trans_sel(u32 target, u32 min_val, u32 step, u32 max_sel) { u32 data = 0; if (target >= min_val) data = (target - min_val) / step; if (data > max_sel) data = max_sel; return data; } static u32 mt6360_trans_ichg_sel(u32 uA) { return mt6360_trans_sel(uA, 100000, 100000, 0x31); } static u32 mt6360_trans_aicr_sel(u32 uA) { return mt6360_trans_sel(uA, 100000, 50000, 0x3F); } static u32 mt6360_trans_mivr_sel(u32 uV) { return mt6360_trans_sel(uV, 3900000, 100000, 0x5F); } static u32 mt6360_trans_cv_sel(u32 uV) { return mt6360_trans_sel(uV, 3900000, 10000, 0x51); } static u32 mt6360_trans_ieoc_sel(u32 uA) { return mt6360_trans_sel(uA, 100000, 50000, 0x0F); } static u32 mt6360_trans_safety_timer_sel(u32 hr) { u32 mt6360_st_tbl[] = {4, 6, 8, 10, 12, 14, 16, 20}; u32 cur_val, next_val; int i; if (hr < mt6360_st_tbl[0]) return 0; for (i = 0; i < ARRAY_SIZE(mt6360_st_tbl) - 1; i++) { cur_val = mt6360_st_tbl[i]; next_val = mt6360_st_tbl[i+1]; if (hr >= cur_val && hr < next_val) return i; } return ARRAY_SIZE(mt6360_st_tbl) - 1; } static u32 mt6360_trans_ircmp_r_sel(u32 uohm) { return mt6360_trans_sel(uohm, 0, 25000, 0x07); } static u32 mt6360_trans_ircmp_vclamp_sel(u32 uV) { return mt6360_trans_sel(uV, 0, 32000, 0x07); } static const u32 mt6360_usbid_rup[] = { 500000, 75000, 5000, 1000 }; static inline u32 mt6360_trans_usbid_rup(u32 rup) { int i; int maxidx = ARRAY_SIZE(mt6360_usbid_rup) - 1; if (rup >= mt6360_usbid_rup[0]) return 0; if (rup <= mt6360_usbid_rup[maxidx]) return maxidx; for (i = 0; i < maxidx; i++) { if (rup == mt6360_usbid_rup[i]) return i; if (rup < mt6360_usbid_rup[i] && rup > mt6360_usbid_rup[i + 1]) { if ((mt6360_usbid_rup[i] - rup) <= (rup - mt6360_usbid_rup[i + 1])) return i; else return i + 1; } } return maxidx; } static const u32 mt6360_usbid_src_ton[] = { 400, 1000, 4000, 10000, 40000, 100000, 400000, }; static inline u32 mt6360_trans_usbid_src_ton(u32 src_ton) { int i; int maxidx = ARRAY_SIZE(mt6360_usbid_src_ton) - 1; /* There is actually an option, always on, after 400000 */ if (src_ton == 0) return maxidx + 1; if (src_ton < mt6360_usbid_src_ton[0]) return 0; if (src_ton > mt6360_usbid_src_ton[maxidx]) return maxidx; for (i = 0; i < maxidx; i++) { if (src_ton == mt6360_usbid_src_ton[i]) return i; if (src_ton > mt6360_usbid_src_ton[i] && src_ton < mt6360_usbid_src_ton[i + 1]) { if ((src_ton - mt6360_usbid_src_ton[i]) <= (mt6360_usbid_src_ton[i + 1] - src_ton)) return i; else return i + 1; } } return maxidx; } static inline int mt6360_get_ieoc(struct mt6360_pmu_chg_info *mpci, u32 *uA) { int ret = 0; ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_CTRL9); if (ret < 0) return ret; ret = (ret & MT6360_MASK_IEOC) >> MT6360_SHFT_IEOC; *uA = 100000 + (ret * 50000); return ret; } static inline int mt6360_get_charging_status( struct mt6360_pmu_chg_info *mpci, enum mt6360_charging_status *chg_stat) { int ret = 0; ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_STAT); if (ret < 0) return ret; *chg_stat = (ret & MT6360_MASK_CHG_STAT) >> MT6360_SHFT_CHG_STAT; return 0; } static inline int mt6360_is_charger_enabled(struct mt6360_pmu_chg_info *mpci, bool *en) { int ret = 0; ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_CTRL2); if (ret < 0) return ret; *en = (ret & MT6360_MASK_CHG_EN) ? true : false; return 0; } static inline int mt6360_select_input_current_limit( struct mt6360_pmu_chg_info *mpci, enum mt6360_iinlmtsel sel) { dev_dbg(mpci->dev, "%s: select input current limit = %d\n", __func__, sel); return mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL2, MT6360_MASK_IINLMTSEL, sel << MT6360_SHFT_IINLMTSEL); } static int mt6360_enable_wdt(struct mt6360_pmu_chg_info *mpci, bool en) { struct mt6360_chg_platform_data *pdata = dev_get_platdata(mpci->dev); dev_dbg(mpci->dev, "%s enable wdt, en = %d\n", __func__, en); if (!pdata->en_wdt) return 0; return mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL13, MT6360_MASK_CHG_WDT_EN, en ? 0xff : 0); } static int mt6360_enable_otg_wdt(struct mt6360_pmu_chg_info *mpci, bool en) { struct mt6360_chg_platform_data *pdata = dev_get_platdata(mpci->dev); dev_dbg(mpci->dev, "%s enable otg wdt, en = %d\n", __func__, en); if (!pdata->en_otg_wdt) return 0; return mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL13, MT6360_MASK_CHG_WDT_EN, en ? 0xff : 0); } static inline int mt6360_get_chrdet_ext_stat(struct mt6360_pmu_chg_info *mpci, bool *pwr_rdy) { int ret = 0; ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHRDET_STAT); if (ret < 0) return ret; *pwr_rdy = !!(ret & BIT(4)); return 0; } static inline bool mt6360_is_meta_mode(struct mt6360_pmu_chg_info *mpci); static void mt6360_power_supply_changed(struct mt6360_pmu_chg_info *mpci); static int DPDM_Switch_TO_CHG_upstream(struct mt6360_pmu_chg_info *mpci, bool switch_to_chg) { struct phy *phy; int mode = 0; int ret; mode = switch_to_chg ? PHY_MODE_BC11_SET : PHY_MODE_BC11_CLR; phy = phy_get(mpci->dev, "usb2-phy"); if (IS_ERR_OR_NULL(phy)) { dev_info(mpci->dev, "phy_get fail\n"); return -EINVAL; } ret = phy_set_mode_ext(phy, PHY_MODE_USB_DEVICE, mode); if (ret) dev_info(mpci->dev, "phy_set_mode_ext fail\n"); phy_put(phy); return 0; } static int mt6360_set_usbsw_state(struct mt6360_pmu_chg_info *mpci, int state) { dev_info(mpci->dev, "%s: state = %d\n", __func__, state); /* Switch D+D- to AP/MT6360 */ if (state == MT6360_USBSW_CHG) DPDM_Switch_TO_CHG_upstream(mpci, true); else DPDM_Switch_TO_CHG_upstream(mpci, false); return 0; } #ifndef CONFIG_MT6360_DCDTOUT_SUPPORT static int __maybe_unused mt6360_enable_dcd_tout( struct mt6360_pmu_chg_info *mpci, bool en) { dev_info(mpci->dev, "%s en = %d\n", __func__, en); return (en ? mt6360_pmu_reg_set_bits : mt6360_pmu_reg_clr_bits) (mpci->mpi, MT6360_PMU_DEVICE_TYPE, MT6360_MASK_DCDTOUTEN); } static int __maybe_unused mt6360_is_dcd_tout_enable( struct mt6360_pmu_chg_info *mpci, bool *en) { int ret; ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_DEVICE_TYPE); if (ret < 0) { *en = false; return ret; } *en = (ret & MT6360_MASK_DCDTOUTEN ? true : false); return 0; } #endif bool is_usb_rdy(struct device *dev) { struct device_node *node; bool ready = false; node = of_parse_phandle(dev->of_node, "usb", 0); if (node) { ready = of_property_read_bool(node, "gadget-ready"); dev_info(dev, "gadget-ready=%d\n", ready); } else dev_info(dev, "usb node missing or invalid\n"); return ready; } static int __mt6360_enable_usbchgen(struct mt6360_pmu_chg_info *mpci, bool en) { int i, ret = 0; const int max_wait_cnt = 200; #ifndef CONFIG_TCPC_CLASS bool pwr_rdy = false; #endif /* !CONFIG_TCPC_CLASS */ enum mt6360_usbsw_state usbsw = en ? MT6360_USBSW_CHG : MT6360_USBSW_USB; #ifndef CONFIG_MT6360_DCDTOUT_SUPPORT bool dcd_en = false; #endif /* CONFIG_MT6360_DCDTOUT_SUPPORT */ dev_info(mpci->dev, "%s: en = %d\n", __func__, en); if (en) { #ifndef CONFIG_MT6360_DCDTOUT_SUPPORT ret = mt6360_is_dcd_tout_enable(mpci, &dcd_en); if (!dcd_en) msleep(180); #endif /* CONFIG_MT6360_DCDTOUT_SUPPORT */ /* Workaround for CDP port */ for (i = 0; i < max_wait_cnt; i++) { if (is_usb_rdy(mpci->dev)) { dev_info(mpci->dev, "%s: USB ready\n", __func__); break; } dev_info(mpci->dev, "%s: CDP block\n", __func__); #ifndef CONFIG_TCPC_CLASS /* Check vbus */ ret = mt6360_get_chrdet_ext_stat(mpci, &pwr_rdy); if (ret < 0) { dev_err(mpci->dev, "%s: fail, ret = %d\n", __func__, ret); return ret; } dev_info(mpci->dev, "%s: pwr_rdy = %d\n", __func__, pwr_rdy); if (!pwr_rdy) { dev_info(mpci->dev, "%s: plug out\n", __func__); return ret; } #else if (!(mpci->tcpc_attach)) { dev_info(mpci->dev, "%s: plug out\n", __func__); return 0; } #endif /* !CONFIG_TCPC_CLASS */ msleep(100); } if (i == max_wait_cnt) dev_err(mpci->dev, "%s: CDP timeout\n", __func__); else dev_info(mpci->dev, "%s: CDP free\n", __func__); } mt6360_set_usbsw_state(mpci, usbsw); ret = mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_DEVICE_TYPE, MT6360_MASK_USBCHGEN, en ? 0xff : 0); if (ret >= 0) mpci->bc12_en = en; return ret; } static int mt6360_enable_usbchgen(struct mt6360_pmu_chg_info *mpci, bool en) { int ret = 0; mutex_lock(&mpci->chgdet_lock); ret = __mt6360_enable_usbchgen(mpci, en); mutex_unlock(&mpci->chgdet_lock); return ret; } #ifdef CONFIG_MT6360_PMU_CHARGER_TYPE_DETECT static int mt6360_chgdet_pre_process(struct mt6360_pmu_chg_info *mpci) { bool attach = false; #ifdef CONFIG_TCPC_CLASS attach = mpci->tcpc_attach; #else attach = mpci->pwr_rdy; #endif /* CONFIG_TCPC_CLASS */ if (attach && mt6360_is_meta_mode(mpci)) { /* Skip charger type detection to speed up meta boot.*/ dev_notice(mpci->dev, "%s: force Standard USB Host in meta\n", __func__); mpci->attach = attach; mpci->psy_desc.type = POWER_SUPPLY_TYPE_USB; mpci->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP; mt6360_power_supply_changed(mpci); return 0; } return __mt6360_enable_usbchgen(mpci, attach); } static int mt6360_chgdet_post_process(struct mt6360_pmu_chg_info *mpci) { int ret = 0; bool attach = false, inform_psy = true; u8 usb_status = MT6360_CHG_TYPE_NOVBUS; #ifdef CONFIG_TCPC_CLASS attach = mpci->tcpc_attach; #else attach = mpci->pwr_rdy; #endif /* CONFIG_TCPC_CLASS */ if (mpci->attach == attach) { dev_info(mpci->dev, "%s: attach(%d) is the same\n", __func__, attach); inform_psy = !attach; goto out; } mpci->attach = attach; dev_info(mpci->dev, "%s: attach = %d\n", __func__, attach); /* Plug out during BC12 */ if (!attach) { dev_info(mpci->dev, "%s: Charger Type: UNKONWN\n", __func__); mpci->psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN; mpci->psy_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; goto out; } /* Plug in */ ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_USB_STATUS1); if (ret < 0) goto out; usb_status = (ret & MT6360_MASK_USB_STATUS) >> MT6360_SHFT_USB_STATUS; switch (usb_status) { case MT6360_CHG_TYPE_UNDER_GOING: dev_info(mpci->dev, "%s: under going...\n", __func__); return ret; case MT6360_CHG_TYPE_SDP: dev_info(mpci->dev, "%s: Charger Type: STANDARD_HOST\n", __func__); mpci->psy_desc.type = POWER_SUPPLY_TYPE_USB; mpci->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP; break; case MT6360_CHG_TYPE_SDPNSTD: dev_info(mpci->dev, "%s: Charger Type: NONSTANDARD_CHARGER\n", __func__); mpci->psy_desc.type = POWER_SUPPLY_TYPE_USB; mpci->psy_usb_type = POWER_SUPPLY_USB_TYPE_DCP; break; case MT6360_CHG_TYPE_CDP: dev_info(mpci->dev, "%s: Charger Type: CHARGING_HOST\n", __func__); mpci->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP; mpci->psy_usb_type = POWER_SUPPLY_USB_TYPE_CDP; break; case MT6360_CHG_TYPE_DCP: dev_info(mpci->dev, "%s: Charger Type: STANDARD_CHARGER\n", __func__); mpci->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP; mpci->psy_usb_type = POWER_SUPPLY_USB_TYPE_DCP; break; } out: if (!attach) { ret = __mt6360_enable_usbchgen(mpci, false); if (ret < 0) dev_notice(mpci->dev, "%s: disable chgdet fail\n", __func__); } else if (mpci->psy_desc.type != POWER_SUPPLY_TYPE_USB_DCP) mt6360_set_usbsw_state(mpci, MT6360_USBSW_USB); if (!inform_psy) return ret; mt6360_power_supply_changed(mpci); return ret; } #endif /* CONFIG_MT6360_PMU_CHARGER_TYPE_DETECT */ static const u32 mt6360_vinovp_list[] = { 5500000, 6500000, 10500000, 14500000, }; static int mt6360_select_vinovp(struct mt6360_pmu_chg_info *mpci, u32 uV) { int i; if (uV < mt6360_vinovp_list[0]) return -EINVAL; for (i = 1; i < ARRAY_SIZE(mt6360_vinovp_list); i++) { if (uV < mt6360_vinovp_list[i]) break; } i--; return mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL19, MT6360_MASK_CHG_VIN_OVP_VTHSEL, i << MT6360_SHFT_CHG_VIN_OVP_VTHSEL); } static inline int mt6360_read_zcv(struct mt6360_pmu_chg_info *mpci) { int ret = 0; u8 zcv_data[2] = {0}; dev_dbg(mpci->dev, "%s\n", __func__); /* Read ZCV data */ ret = mt6360_pmu_reg_block_read(mpci->mpi, MT6360_PMU_ADC_BAT_DATA_H, 2, zcv_data); if (ret < 0) { dev_err(mpci->dev, "%s: read zcv data fail\n", __func__); return ret; } mpci->zcv = 1250 * (zcv_data[0] * 256 + zcv_data[1]); dev_info(mpci->dev, "%s: zcv = (0x%02X, 0x%02X, %dmV)\n", __func__, zcv_data[0], zcv_data[1], mpci->zcv/1000); /* Disable ZCV */ ret = mt6360_pmu_reg_clr_bits(mpci->mpi, MT6360_PMU_ADC_CONFIG, MT6360_MASK_ZCV_EN); if (ret < 0) dev_err(mpci->dev, "%s: disable zcv fail\n", __func__); return ret; } /* ================== */ /* External Functions */ /* ================== */ static int __mt6360_set_ichg(struct mt6360_pmu_chg_info *mpci, u32 uA) { int ret = 0; u32 data = 0; mt_dbg(mpci->dev, "%s\n", __func__); data = mt6360_trans_ichg_sel(uA); ret = mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL7, MT6360_MASK_ICHG, data << MT6360_SHFT_ICHG); if (ret < 0) dev_notice(mpci->dev, "%s: fail\n", __func__); else mpci->ichg = uA; return ret; } static int mt6360_set_ichg(struct charger_device *chg_dev, u32 uA) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; mutex_lock(&mpci->ichg_lock); ret = __mt6360_set_ichg(mpci, uA); mutex_unlock(&mpci->ichg_lock); return ret; } static int mt6360_get_ichg(struct charger_device *chg_dev, u32 *uA) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_CTRL7); if (ret < 0) return ret; ret = (ret & MT6360_MASK_ICHG) >> MT6360_SHFT_ICHG; *uA = 100000 + (ret * 100000); return 0; } static int mt6360_enable_hidden_mode(struct charger_device *chg_dev, bool en) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); static const u8 pascode[] = { 0x69, 0x96, 0x63, 0x72, }; int ret = 0; mutex_lock(&mpci->hidden_mode_lock); if (en) { if (mpci->hidden_mode_cnt == 0) { ret = mt6360_pmu_reg_block_write(mpci->mpi, MT6360_PMU_TM_PAS_CODE1, 4, pascode); if (ret < 0) goto err; } mpci->hidden_mode_cnt++; } else { if (mpci->hidden_mode_cnt == 1) ret = mt6360_pmu_reg_write(mpci->mpi, MT6360_PMU_TM_PAS_CODE1, 0x00); mpci->hidden_mode_cnt--; if (ret < 0) goto err; } mt_dbg(mpci->dev, "%s: en = %d\n", __func__, en); goto out; err: dev_err(mpci->dev, "%s failed, en = %d\n", __func__, en); out: mutex_unlock(&mpci->hidden_mode_lock); return ret; } static int mt6360_enable(struct charger_device *chg_dev, bool en) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; u32 ichg_ramp_t = 0; mt_dbg(mpci->dev, "%s: en = %d\n", __func__, en); /* Workaround for vsys overshoot */ mutex_lock(&mpci->ichg_lock); if (mpci->ichg < 500000) { dev_info(mpci->dev, "%s: ichg < 500mA, bypass vsys wkard\n", __func__); goto out; } if (!en) { mpci->ichg_dis_chg = mpci->ichg; ichg_ramp_t = (mpci->ichg - 500000) / 50000 * 2; /* Set ichg to 500mA */ ret = mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL7, MT6360_MASK_ICHG, 0x04 << MT6360_SHFT_ICHG); if (ret < 0) { dev_notice(mpci->dev, "%s: set ichg fail\n", __func__); goto vsys_wkard_fail; } mdelay(ichg_ramp_t); } else { if (mpci->ichg == mpci->ichg_dis_chg) { ret = __mt6360_set_ichg(mpci, mpci->ichg); if (ret < 0) { dev_notice(mpci->dev, "%s: set ichg fail\n", __func__); goto out; } } } out: ret = mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL2, MT6360_MASK_CHG_EN, en ? 0xff : 0); if (ret < 0) dev_notice(mpci->dev, "%s: fail, en = %d\n", __func__, en); vsys_wkard_fail: mutex_unlock(&mpci->ichg_lock); mt6360_power_supply_changed(mpci); return ret; } static int mt6360_get_min_ichg(struct charger_device *chg_dev, u32 *uA) { *uA = 300000; return 0; } static int mt6360_get_cv(struct charger_device *chg_dev, u32 *uV) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_CTRL4); if (ret < 0) return ret; ret = (ret & MT6360_MASK_VOREG) >> MT6360_SHFT_VOREG; *uV = 3900000 + (ret * 10000); return 0; } static int mt6360_set_cv(struct charger_device *chg_dev, u32 uV) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); u8 data = 0; u32 reg_data = 0; int ret = 0; dev_dbg(mpci->dev, "%s: cv = %d\n", __func__, uV); ret = mt6360_get_cv(chg_dev, ®_data); if (ret < 0) return ret; if (uV == reg_data) /* same */ return ret; ret = mt6360_enable_hidden_mode(mpci->chg_dev, true); if (ret < 0) return ret; reg_data = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_HIDDEN_CTRL22); if (reg_data < 0) goto out; /* set reg0x45[6:5]=11 */ ret = mt6360_pmu_reg_set_bits(mpci->mpi, MT6360_PMU_CHG_HIDDEN_CTRL22, MT6360_MASK_BATOVP_LVL); if (ret < 0) goto out; data = mt6360_trans_cv_sel(uV); ret = mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL4, MT6360_MASK_VOREG, data << MT6360_SHFT_VOREG); if (ret < 0) goto out; mdelay(5); ret = mt6360_pmu_reg_write(mpci->mpi, MT6360_PMU_CHG_HIDDEN_CTRL22, reg_data); out: mt6360_enable_hidden_mode(mpci->chg_dev, false); return ret; } static int mt6360_toggle_aicc(struct mt6360_pmu_chg_info *mpci) { int ret = 0; u8 data = 0; mutex_lock(&mpci->mpi->io_lock); /* read aicc */ ret = i2c_smbus_read_i2c_block_data(mpci->mpi->i2c, MT6360_PMU_CHG_CTRL14, 1, &data); if (ret < 0) { dev_err(mpci->dev, "%s: read aicc fail\n", __func__); goto out; } /* aicc off */ data &= ~MT6360_MASK_RG_EN_AICC; ret = i2c_smbus_read_i2c_block_data(mpci->mpi->i2c, MT6360_PMU_CHG_CTRL14, 1, &data); if (ret < 0) { dev_err(mpci->dev, "%s: aicc off fail\n", __func__); goto out; } /* aicc on */ data |= MT6360_MASK_RG_EN_AICC; ret = i2c_smbus_read_i2c_block_data(mpci->mpi->i2c, MT6360_PMU_CHG_CTRL14, 1, &data); if (ret < 0) dev_err(mpci->dev, "%s: aicc on fail\n", __func__); out: mutex_unlock(&mpci->mpi->io_lock); return ret; } static int mt6360_set_aicr(struct charger_device *chg_dev, u32 uA) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); struct mt6360_chg_platform_data *pdata = dev_get_platdata(mpci->dev); int ret = 0; u8 data = 0; dev_dbg(mpci->dev, "%s\n", __func__); /* Toggle aicc for auto aicc mode */ if (!pdata->aicc_once) { ret = mt6360_toggle_aicc(mpci); if (ret < 0) { dev_err(mpci->dev, "%s: toggle aicc fail\n", __func__); return ret; } } /* Disable sys drop improvement for download mode */ ret = mt6360_pmu_reg_clr_bits(mpci->mpi, MT6360_PMU_CHG_CTRL20, MT6360_MASK_EN_SDI); if (ret < 0) { dev_err(mpci->dev, "%s: disable en_sdi fail\n", __func__); return ret; } data = mt6360_trans_aicr_sel(uA); return mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL3, MT6360_MASK_AICR, data << MT6360_SHFT_AICR); } static int mt6360_get_aicr(struct charger_device *chg_dev, u32 *uA) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_CTRL3); if (ret < 0) return ret; ret = (ret & MT6360_MASK_AICR) >> MT6360_SHFT_AICR; *uA = 100000 + (ret * 50000); return 0; } static int mt6360_get_min_aicr(struct charger_device *chg_dev, u32 *uA) { *uA = 100000; return 0; } static int mt6360_set_ieoc(struct charger_device *chg_dev, u32 uA) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); u8 data = 0; dev_dbg(mpci->dev, "%s: ieoc = %d\n", __func__, uA); data = mt6360_trans_ieoc_sel(uA); return mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL9, MT6360_MASK_IEOC, data << MT6360_SHFT_IEOC); } static int mt6360_set_mivr(struct charger_device *chg_dev, u32 uV) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); u32 aicc_vth = 0, data = 0; u8 aicc_vth_sel = 0; int ret = 0; mt_dbg(mpci->dev, "%s: mivr = %d\n", __func__, uV); if (uV < 3900000 || uV > 13400000) { dev_err(mpci->dev, "%s: unsuitable mivr val(%d)\n", __func__, uV); return -EINVAL; } /* Check if there's a suitable AICC_VTH */ aicc_vth = uV + 200000; aicc_vth_sel = (aicc_vth - 3900000) / 100000; if (aicc_vth_sel > MT6360_AICC_VTH_MAXVAL) { dev_err(mpci->dev, "%s: can't match, aicc_vth_sel = %d\n", __func__, aicc_vth_sel); return -EINVAL; } /* Set AICC_VTH threshold */ ret = mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL16, MT6360_MASK_AICC_VTH, aicc_vth_sel << MT6360_SHFT_AICC_VTH); if (ret < 0) return ret; /* Set MIVR */ data = mt6360_trans_mivr_sel(uV); return mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL6, MT6360_MASK_MIVR, data << MT6360_SHFT_MIVR); } static inline int mt6360_get_mivr(struct charger_device *chg_dev, u32 *uV) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_CTRL6); if (ret < 0) return ret; ret = (ret & MT6360_MASK_MIVR) >> MT6360_SHFT_MIVR; *uV = 3900000 + (ret * 100000); return 0; } static int mt6360_get_mivr_state(struct charger_device *chg_dev, bool *in_loop) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_STAT1); if (ret < 0) return ret; *in_loop = (ret & MT6360_MASK_MIVR_EVT) >> MT6360_SHFT_MIVR_EVT; return 0; } static int mt6360_enable_te(struct charger_device *chg_dev, bool en) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); struct mt6360_chg_platform_data *pdata = dev_get_platdata(mpci->dev); dev_info(mpci->dev, "%s: en = %d\n", __func__, en); if (!pdata->en_te) return 0; return mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL2, MT6360_MASK_TE_EN, en ? 0xff : 0); } static int mt6360_enable_pump_express(struct mt6360_pmu_chg_info *mpci, bool pe20) { long timeout, pe_timeout = pe20 ? 1400 : 2800; int ret = 0; dev_info(mpci->dev, "%s\n", __func__); ret = mt6360_set_aicr(mpci->chg_dev, 800000); if (ret < 0) return ret; ret = mt6360_set_ichg(mpci->chg_dev, 2000000); if (ret < 0) return ret; ret = mt6360_enable(mpci->chg_dev, true); if (ret < 0) return ret; ret = mt6360_pmu_reg_clr_bits(mpci->mpi, MT6360_PMU_CHG_CTRL17, MT6360_MASK_EN_PUMPX); if (ret < 0) return ret; ret = mt6360_pmu_reg_set_bits(mpci->mpi, MT6360_PMU_CHG_CTRL17, MT6360_MASK_EN_PUMPX); if (ret < 0) return ret; reinit_completion(&mpci->pumpx_done); atomic_set(&mpci->pe_complete, 1); timeout = wait_for_completion_interruptible_timeout( &mpci->pumpx_done, msecs_to_jiffies(pe_timeout)); if (timeout == 0) ret = -ETIMEDOUT; else if (timeout < 0) ret = -EINTR; else ret = 0; if (ret < 0) dev_err(mpci->dev, "%s: wait pumpx timeout, ret = %d\n", __func__, ret); return ret; } static int mt6360_set_pep_current_pattern(struct charger_device *chg_dev, bool is_inc) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; dev_dbg(mpci->dev, "%s: pe1.0 pump up = %d\n", __func__, is_inc); mutex_lock(&mpci->pe_lock); /* Set to PE1.0 */ ret = mt6360_pmu_reg_clr_bits(mpci->mpi, MT6360_PMU_CHG_CTRL17, MT6360_MASK_PUMPX_20_10); if (ret < 0) { dev_err(mpci->dev, "%s: enable pumpx 10 fail\n", __func__); goto out; } /* Set Pump Up/Down */ ret = mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL17, MT6360_MASK_PUMPX_UP_DN, is_inc ? 0xff : 0); if (ret < 0) { dev_err(mpci->dev, "%s: set pumpx up/down fail\n", __func__); goto out; } ret = mt6360_enable_pump_express(mpci, false); out: mutex_unlock(&mpci->pe_lock); return ret; } static int mt6360_set_pep20_efficiency_table(struct charger_device *chg_dev) { return 0; } static int mt6360_set_pep20_current_pattern(struct charger_device *chg_dev, u32 uV) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; u8 data = 0; dev_dbg(mpci->dev, "%s: pep2.0 = %d\n", __func__, uV); mutex_lock(&mpci->pe_lock); if (uV >= 5500000) data = (uV - 5500000) / 500000; if (data > MT6360_PUMPX_20_MAXVAL) data = MT6360_PUMPX_20_MAXVAL; /* Set to PE2.0 */ ret = mt6360_pmu_reg_set_bits(mpci->mpi, MT6360_PMU_CHG_CTRL17, MT6360_MASK_PUMPX_20_10); if (ret < 0) { dev_err(mpci->dev, "%s: enable pumpx 20 fail\n", __func__); goto out; } /* Set Voltage */ ret = mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL17, MT6360_MASK_PUMPX_DEC, data << MT6360_SHFT_PUMPX_DEC); if (ret < 0) { dev_err(mpci->dev, "%s: set pumpx voltage fail\n", __func__); goto out; } ret = mt6360_enable_pump_express(mpci, true); out: mutex_unlock(&mpci->pe_lock); return ret; } static int mt6360_reset_ta(struct charger_device *chg_dev) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; dev_dbg(mpci->dev, "%s\n", __func__); ret = mt6360_set_mivr(chg_dev, 4600000); if (ret < 0) return ret; ret = mt6360_select_input_current_limit(mpci, MT6360_IINLMTSEL_AICR); if (ret < 0) return ret; ret = mt6360_set_aicr(chg_dev, 100000); if (ret < 0) return ret; msleep(250); return mt6360_set_aicr(chg_dev, 500000); } static int mt6360_enable_cable_drop_comp(struct charger_device *chg_dev, bool en) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; dev_info(mpci->dev, "%s: en = %d\n", __func__, en); if (en) return ret; /* Set to PE2.0 */ mutex_lock(&mpci->pe_lock); ret = mt6360_pmu_reg_set_bits(mpci->mpi, MT6360_PMU_CHG_CTRL17, MT6360_MASK_PUMPX_20_10); if (ret < 0) { dev_err(mpci->dev, "%s: enable pumpx 20 fail\n", __func__); goto out; } /* Disable cable drop compensation */ ret = mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL17, MT6360_MASK_PUMPX_DEC, 0x1F << MT6360_SHFT_PUMPX_DEC); if (ret < 0) { dev_err(mpci->dev, "%s: set pumpx voltage fail\n", __func__); goto out; } ret = mt6360_enable_pump_express(mpci, true); out: mutex_unlock(&mpci->pe_lock); return ret; } static inline int mt6360_get_aicc(struct mt6360_pmu_chg_info *mpci, u32 *aicc_val) { u8 aicc_sel = 0; int ret = 0; ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_AICC_RESULT); if (ret < 0) { dev_err(mpci->dev, "%s: read aicc result fail\n", __func__); return ret; } aicc_sel = (ret & MT6360_MASK_RG_AICC_RESULT) >> MT6360_SHFT_RG_AICC_RESULT; *aicc_val = (aicc_sel * 50000) + 100000; return 0; } static inline int mt6360_post_aicc_measure(struct charger_device *chg_dev, u32 start, u32 stop, u32 step, u32 *measure) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int cur, ret; mt_dbg(mpci->dev, "%s: post_aicc = (%d, %d, %d)\n", __func__, start, stop, step); for (cur = start; cur < (stop + step); cur += step) { /* set_aicr to cur */ ret = mt6360_set_aicr(chg_dev, cur + step); if (ret < 0) return ret; usleep_range(150, 200); ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_STAT1); if (ret < 0) return ret; /* read mivr stat */ if (ret & MT6360_MASK_MIVR_EVT) break; } *measure = cur; return 0; } static int mt6360_run_aicc(struct charger_device *chg_dev, u32 *uA) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); struct mt6360_chg_platform_data *pdata = dev_get_platdata(mpci->dev); int ret = 0; u32 aicc_val = 0, aicr_val; long timeout; bool mivr_stat = false; mt_dbg(mpci->dev, "%s: aicc_once = %d\n", __func__, pdata->aicc_once); /* check MIVR stat is act */ ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_STAT1); if (ret < 0) { dev_err(mpci->dev, "%s: read mivr stat fail\n", __func__); return ret; } mivr_stat = (ret & MT6360_MASK_MIVR_EVT) ? true : false; if (!mivr_stat) { dev_err(mpci->dev, "%s: mivr stat not act\n", __func__); return ret; } /* Auto run aicc */ if (!pdata->aicc_once) { if (!try_wait_for_completion(&mpci->aicc_done)) { dev_info(mpci->dev, "%s: aicc is not act\n", __func__); return 0; } /* get aicc result */ ret = mt6360_get_aicc(mpci, &aicc_val); if (ret < 0) { dev_err(mpci->dev, "%s: get aicc fail\n", __func__); return ret; } *uA = aicc_val; reinit_completion(&mpci->aicc_done); return ret; } /* Use aicc once method */ /* Run AICC measure */ mutex_lock(&mpci->pe_lock); ret = mt6360_pmu_reg_set_bits(mpci->mpi, MT6360_PMU_CHG_CTRL14, MT6360_MASK_RG_EN_AICC); if (ret < 0) goto out; /* Clear AICC measurement IRQ */ reinit_completion(&mpci->aicc_done); timeout = wait_for_completion_interruptible_timeout( &mpci->aicc_done, msecs_to_jiffies(3000)); if (timeout == 0) ret = -ETIMEDOUT; else if (timeout < 0) ret = -EINTR; else ret = 0; if (ret < 0) { dev_err(mpci->dev, "%s: wait AICC time out, ret = %d\n", __func__, ret); goto out; } /* get aicc_result */ ret = mt6360_get_aicc(mpci, &aicc_val); if (ret < 0) { dev_err(mpci->dev, "%s: get aicc result fail\n", __func__); goto out; } if (!pdata->post_aicc) goto skip_post_aicc; dev_info(mpci->dev, "%s: aicc pre val = %d\n", __func__, aicc_val); ret = mt6360_get_aicr(chg_dev, &aicr_val); if (ret < 0) { dev_err(mpci->dev, "%s: get aicr fail\n", __func__); goto out; } ret = mt6360_set_aicr(chg_dev, aicc_val); if (ret < 0) { dev_err(mpci->dev, "%s: set aicr fail\n", __func__); goto out; } ret = mt6360_pmu_reg_clr_bits(mpci->mpi, MT6360_PMU_CHG_CTRL14, MT6360_MASK_RG_EN_AICC); if (ret < 0) goto out; /* always start/end aicc_val/aicc_val+200mA */ ret = mt6360_post_aicc_measure(chg_dev, aicc_val, aicc_val + 200000, 50000, &aicc_val); if (ret < 0) goto out; ret = mt6360_set_aicr(chg_dev, aicr_val); if (ret < 0) { dev_err(mpci->dev, "%s: set aicr fail\n", __func__); goto out; } dev_info(mpci->dev, "%s: aicc post val = %d\n", __func__, aicc_val); skip_post_aicc: *uA = aicc_val; out: /* Clear EN_AICC */ ret = mt6360_pmu_reg_clr_bits(mpci->mpi, MT6360_PMU_CHG_CTRL14, MT6360_MASK_RG_EN_AICC); mutex_unlock(&mpci->pe_lock); return ret; } static int mt6360_enable_power_path(struct charger_device *chg_dev, bool en) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); dev_dbg(mpci->dev, "%s: en = %d\n", __func__, en); return mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL1, MT6360_MASK_FORCE_SLEEP, en ? 0 : 0xff); } static int mt6360_is_power_path_enabled(struct charger_device *chg_dev, bool *en) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_CTRL1); if (ret < 0) return ret; *en = (ret & MT6360_MASK_FORCE_SLEEP) ? false : true; return 0; } static int mt6360_enable_safety_timer(struct charger_device *chg_dev, bool en) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); dev_dbg(mpci->dev, "%s: en = %d\n", __func__, en); return mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL12, MT6360_MASK_TMR_EN, en ? 0xff : 0); } static int mt6360_is_safety_timer_enabled( struct charger_device *chg_dev, bool *en) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_CTRL12); if (ret < 0) return ret; *en = (ret & MT6360_MASK_TMR_EN) ? true : false; return 0; } #ifdef DELECHECK // for hz static int mt6360_enable_hz(struct charger_device *chg_dev, bool en) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; dev_info(mpci->dev, "%s: en = %d\n", __func__, en); ret = (en ? mt6360_pmu_reg_set_bits : mt6360_pmu_reg_clr_bits) (mpci->mpi, MT6360_PMU_CHG_CTRL1, MT6360_MASK_HZ_EN); return ret; } #endif static const u32 otg_oc_table[] = { 500000, 700000, 1100000, 1300000, 1800000, 2100000, 2400000, 3000000 }; static int mt6360_set_otg_current_limit(struct charger_device *chg_dev, u32 uA) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int i; /* Set higher OC threshold protect */ for (i = 0; i < ARRAY_SIZE(otg_oc_table); i++) { if (uA <= otg_oc_table[i]) break; } if (i == ARRAY_SIZE(otg_oc_table)) i = MT6360_OTG_OC_MAXVAL; dev_dbg(mpci->dev, "%s: select oc threshold = %d\n", __func__, otg_oc_table[i]); return mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL10, MT6360_MASK_OTG_OC, i << MT6360_SHFT_OTG_OC); } static int mt6360_enable_otg(struct charger_device *chg_dev, bool en) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; dev_dbg(mpci->dev, "%s: en = %d\n", __func__, en); ret = mt6360_enable_otg_wdt(mpci, en ? true : false); if (ret < 0) { dev_err(mpci->dev, "%s: set wdt fail, en = %d\n", __func__, en); return ret; } return mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL1, MT6360_MASK_OPA_MODE, en ? 0xff : 0); } static int mt6360_enable_discharge(struct charger_device *chg_dev, bool en) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int i, ret = 0; const int dischg_retry_cnt = 3; bool is_dischg; dev_dbg(mpci->dev, "%s: en = %d\n", __func__, en); ret = mt6360_enable_hidden_mode(mpci->chg_dev, true); if (ret < 0) return ret; /* Set bit2 of reg[0x31] to 1/0 to enable/disable discharging */ ret = mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_HIDDEN_CTRL2, MT6360_MASK_DISCHG, en ? 0xff : 0); if (ret < 0) { dev_err(mpci->dev, "%s: fail, en = %d\n", __func__, en); goto out; } if (!en) { for (i = 0; i < dischg_retry_cnt; i++) { ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_HIDDEN_CTRL2); is_dischg = (ret & MT6360_MASK_DISCHG) ? true : false; if (!is_dischg) break; ret = mt6360_pmu_reg_clr_bits(mpci->mpi, MT6360_PMU_CHG_HIDDEN_CTRL2, MT6360_MASK_DISCHG); if (ret < 0) { dev_err(mpci->dev, "%s: disable dischg failed\n", __func__); goto out; } } if (i == dischg_retry_cnt) { dev_err(mpci->dev, "%s: dischg failed\n", __func__); ret = -EINVAL; } } out: mt6360_enable_hidden_mode(mpci->chg_dev, false); return ret; } static int mt6360_enable_chg_type_det(struct charger_device *chg_dev, bool en) { int ret = 0; #if defined(CONFIG_MT6360_PMU_CHARGER_TYPE_DETECT) && defined(CONFIG_TCPC_CLASS) struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); struct mt6360_chg_platform_data *pdata = dev_get_platdata(mpci->dev); dev_info(mpci->dev, "%s: en = %d\n", __func__, en); if (pdata->bc12_sel != 0) return ret; mutex_lock(&mpci->chgdet_lock); if (mpci->tcpc_attach == en) { dev_info(mpci->dev, "%s attach(%d) is the same\n", __func__, mpci->tcpc_attach); goto out; } mpci->tcpc_attach = en; ret = (en ? mt6360_chgdet_pre_process : mt6360_chgdet_post_process)(mpci); out: mutex_unlock(&mpci->chgdet_lock); #endif /* CONFIG_MT6360_PMU_CHARGER_TYPE_DETECT && CONFIG_TCPC_CLASS */ return ret; } static int mt6360_get_adc(struct charger_device *chg_dev, enum adc_channel chan, int *min, int *max) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); enum mt6360_adc_channel channel; int ret = 0; switch (chan) { case ADC_CHANNEL_VBUS: channel = MT6360_ADC_VBUSDIV5; break; case ADC_CHANNEL_VSYS: channel = MT6360_ADC_VSYS; break; case ADC_CHANNEL_VBAT: channel = MT6360_ADC_VBAT; break; case ADC_CHANNEL_IBUS: channel = MT6360_ADC_IBUS; break; case ADC_CHANNEL_IBAT: channel = MT6360_ADC_IBAT; break; case ADC_CHANNEL_TEMP_JC: channel = MT6360_ADC_TEMP_JC; break; case ADC_CHANNEL_USBID: channel = MT6360_ADC_USBID; break; case ADC_CHANNEL_TS: channel = MT6360_ADC_TS; break; default: return -ENOTSUPP; } ret = iio_read_channel_processed(mpci->channels[channel], min); if (ret < 0) { dev_info(mpci->dev, "%s: fail(%d)\n", __func__, ret); return ret; } *max = *min; return 0; } static int mt6360_get_vbus(struct charger_device *chg_dev, u32 *vbus) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); mt_dbg(mpci->dev, "%s\n", __func__); return mt6360_get_adc(chg_dev, ADC_CHANNEL_VBUS, vbus, vbus); } static int mt6360_get_ibus(struct charger_device *chg_dev, u32 *ibus) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); mt_dbg(mpci->dev, "%s\n", __func__); return mt6360_get_adc(chg_dev, ADC_CHANNEL_IBUS, ibus, ibus); } static int mt6360_get_ibat(struct charger_device *chg_dev, u32 *ibat) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); mt_dbg(mpci->dev, "%s\n", __func__); return mt6360_get_adc(chg_dev, ADC_CHANNEL_IBAT, ibat, ibat); } static int mt6360_get_tchg(struct charger_device *chg_dev, int *tchg_min, int *tchg_max) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int temp_jc = 0, ret = 0, retry_cnt = 3; mt_dbg(mpci->dev, "%s\n", __func__); /* temp abnormal Workaround */ do { ret = mt6360_get_adc(chg_dev, ADC_CHANNEL_TEMP_JC, &temp_jc, &temp_jc); if (ret < 0) { dev_err(mpci->dev, "%s: failed, ret = %d\n", __func__, ret); return ret; } } while (temp_jc >= 120 && (retry_cnt--) > 0); mutex_lock(&mpci->tchg_lock); if (temp_jc >= 120) temp_jc = mpci->tchg; else mpci->tchg = temp_jc; mutex_unlock(&mpci->tchg_lock); *tchg_min = *tchg_max = temp_jc; dev_info(mpci->dev, "%s: tchg = %d\n", __func__, temp_jc); return 0; } static int mt6360_kick_wdt(struct charger_device *chg_dev) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); dev_dbg(mpci->dev, "%s\n", __func__); return mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_CTRL1); } static int mt6360_safety_check(struct charger_device *chg_dev, u32 polling_ieoc) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret, ibat = 0; static int eoc_cnt; mt_dbg(mpci->dev, "%s\n", __func__); ret = iio_read_channel_processed(mpci->channels[MT6360_ADC_IBAT], &ibat); if (ret < 0) dev_err(mpci->dev, "%s: failed, ret = %d\n", __func__, ret); if (ibat <= polling_ieoc) eoc_cnt++; else eoc_cnt = 0; /* If ibat is less than polling_ieoc for 3 times, trigger EOC event */ if (eoc_cnt == 3) { dev_info(mpci->dev, "%s: polling_ieoc = %d, ibat = %d\n", __func__, polling_ieoc, ibat); charger_dev_notify(mpci->chg_dev, CHARGER_DEV_NOTIFY_EOC); eoc_cnt = 0; } return ret; } static int mt6360_reset_eoc_state(struct charger_device *chg_dev) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; dev_dbg(mpci->dev, "%s\n", __func__); ret = mt6360_enable_hidden_mode(mpci->chg_dev, true); if (ret < 0) return ret; ret = mt6360_pmu_reg_set_bits(mpci->mpi, MT6360_PMU_CHG_HIDDEN_CTRL1, MT6360_MASK_EOC_RST); if (ret < 0) { dev_err(mpci->dev, "%s: set failed, ret = %d\n", __func__, ret); goto out; } udelay(100); ret = mt6360_pmu_reg_clr_bits(mpci->mpi, MT6360_PMU_CHG_HIDDEN_CTRL1, MT6360_MASK_EOC_RST); if (ret < 0) { dev_err(mpci->dev, "%s: clear failed, ret = %d\n", __func__, ret); goto out; } out: mt6360_enable_hidden_mode(mpci->chg_dev, false); return ret; } static int mt6360_is_charging_done(struct charger_device *chg_dev, bool *done) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); enum mt6360_charging_status chg_stat; int ret = 0; mt_dbg(mpci->dev, "%s\n", __func__); ret = mt6360_get_charging_status(mpci, &chg_stat); if (ret < 0) return ret; *done = (chg_stat == MT6360_CHG_STATUS_DONE) ? true : false; return 0; } static int mt6360_get_zcv(struct charger_device *chg_dev, u32 *uV) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); dev_info(mpci->dev, "%s: zcv = %dmV\n", __func__, mpci->zcv / 1000); *uV = mpci->zcv; return 0; } static int mt6360_dump_registers(struct charger_device *chg_dev) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int i, ret = 0; int adc_vals[MT6360_ADC_MAX]; u32 ichg = 0, aicr = 0, mivr = 0, cv = 0, ieoc = 0; enum mt6360_charging_status chg_stat = MT6360_CHG_STATUS_READY; bool chg_en = false; u8 chg_stat1 = 0, chg_ctrl[2] = {0}; dev_dbg(mpci->dev, "%s\n", __func__); ret = mt6360_get_ichg(chg_dev, &ichg); ret |= mt6360_get_aicr(chg_dev, &aicr); ret |= mt6360_get_mivr(chg_dev, &mivr); ret |= mt6360_get_cv(chg_dev, &cv); ret |= mt6360_get_ieoc(mpci, &ieoc); ret |= mt6360_get_charging_status(mpci, &chg_stat); ret |= mt6360_is_charger_enabled(mpci, &chg_en); if (ret < 0) { dev_notice(mpci->dev, "%s: parse chg setting fail\n", __func__); return ret; } for (i = 0; i < MT6360_ADC_MAX; i++) { /* Skip unnecessary channel */ if (i >= MT6360_ADC_NODUMP) break; ret = iio_read_channel_processed(mpci->channels[i], &adc_vals[i]); if (ret < 0) { dev_err(mpci->dev, "%s: read [%s] adc fail(%d)\n", __func__, mt6360_adc_chan_list[i], ret); return ret; } } ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_STAT1); if (ret < 0) return ret; chg_stat1 = ret; ret = mt6360_pmu_reg_block_read(mpci->mpi, MT6360_PMU_CHG_CTRL1, 2, chg_ctrl); if (ret < 0) return ret; dev_info(mpci->dev, "%s: ICHG = %dmA, AICR = %dmA, MIVR = %dmV, IEOC = %dmA, CV = %dmV\n", __func__, ichg / 1000, aicr / 1000, mivr / 1000, ieoc / 1000, cv / 1000); dev_info(mpci->dev, "%s: VBUS = %dmV, IBUS = %dmA, VSYS = %dmV, VBAT = %dmV, IBAT = %dmA\n", __func__, adc_vals[MT6360_ADC_VBUSDIV5] / 1000, adc_vals[MT6360_ADC_IBUS] / 1000, adc_vals[MT6360_ADC_VSYS] / 1000, adc_vals[MT6360_ADC_VBAT] / 1000, adc_vals[MT6360_ADC_IBAT] / 1000); dev_info(mpci->dev, "%s: CHG_EN = %d, CHG_STATUS = %s, CHG_STAT1 = 0x%02X\n", __func__, chg_en, mt6360_chg_status_name[chg_stat], chg_stat1); dev_info(mpci->dev, "%s: CHG_CTRL1 = 0x%02X, CHG_CTRL2 = 0x%02X\n", __func__, chg_ctrl[0], chg_ctrl[1]); return 0; } static int mt6360_do_event(struct charger_device *chg_dev, u32 event, u32 args) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); mt_dbg(mpci->dev, "%s\n", __func__); if (!mpci->psy) { dev_notice(mpci->dev, "%s: cannot get psy\n", __func__); return -ENODEV; } switch (event) { case EVENT_FULL: case EVENT_RECHARGE: case EVENT_DISCHARGE: mt6360_power_supply_changed(mpci); break; default: break; } return 0; } static int mt6360_plug_in(struct charger_device *chg_dev) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; dev_dbg(mpci->dev, "%s\n", __func__); ret = mt6360_enable_wdt(mpci, true); if (ret < 0) { dev_err(mpci->dev, "%s: en wdt failed\n", __func__); return ret; } /* Replace CHG_EN by TE for avoid CV level too low trigger ieoc */ /* TODO: First select cv, then chg_en, no need ? */ ret = mt6360_enable_te(chg_dev, true); if (ret < 0) dev_err(mpci->dev, "%s: en te failed\n", __func__); return ret; } static int mt6360_plug_out(struct charger_device *chg_dev) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); int ret = 0; dev_dbg(mpci->dev, "%s\n", __func__); ret = mt6360_enable_wdt(mpci, false); if (ret < 0) { dev_err(mpci->dev, "%s: disable wdt failed\n", __func__); return ret; } ret = mt6360_enable_te(chg_dev, false); if (ret < 0) dev_err(mpci->dev, "%s: disable te failed\n", __func__); return ret; } static int mt6360_enable_usbid(struct charger_device *chg_dev, bool en) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); return (en ? mt6360_pmu_reg_set_bits : mt6360_pmu_reg_clr_bits) (mpci->mpi, MT6360_PMU_USBID_CTRL1, MT6360_MASK_USBID_EN); } static int mt6360_set_usbid_rup(struct charger_device *chg_dev, u32 rup) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); u32 data = mt6360_trans_usbid_rup(rup); return mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_USBID_CTRL1, MT6360_MASK_ID_RPULSEL, data << MT6360_SHFT_ID_RPULSEL); } static int mt6360_set_usbid_src_ton(struct charger_device *chg_dev, u32 src_ton) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); u32 data = mt6360_trans_usbid_src_ton(src_ton); return mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_USBID_CTRL1, MT6360_MASK_ISTDET, data << MT6360_SHFT_ISTDET); } static int mt6360_enable_usbid_floating(struct charger_device *chg_dev, bool en) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); return (en ? mt6360_pmu_reg_set_bits : mt6360_pmu_reg_clr_bits) (mpci->mpi, MT6360_PMU_USBID_CTRL2, MT6360_MASK_USBID_FLOAT); } static int mt6360_enable_force_typec_otp(struct charger_device *chg_dev, bool en) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); return (en ? mt6360_pmu_reg_set_bits : mt6360_pmu_reg_clr_bits) (mpci->mpi, MT6360_PMU_TYPEC_OTP_CTRL, MT6360_MASK_TYPEC_OTP_SWEN); } static int mt6360_get_ctd_dischg_status(struct charger_device *chg_dev, u8 *status) { struct mt6360_pmu_chg_info *mpci = charger_get_data(chg_dev); *status = mpci->ctd_dischg_status; return 0; } static const struct charger_ops mt6360_chg_ops = { /* cable plug in/out */ .plug_in = mt6360_plug_in, .plug_out = mt6360_plug_out, /* enable */ .enable = mt6360_enable, /* charging current */ .set_charging_current = mt6360_set_ichg, .get_charging_current = mt6360_get_ichg, .get_min_charging_current = mt6360_get_min_ichg, /* charging voltage */ .set_constant_voltage = mt6360_set_cv, .get_constant_voltage = mt6360_get_cv, /* charging input current */ .set_input_current = mt6360_set_aicr, .get_input_current = mt6360_get_aicr, .get_min_input_current = mt6360_get_min_aicr, /* set termination current */ .set_eoc_current = mt6360_set_ieoc, /* charging mivr */ .set_mivr = mt6360_set_mivr, .get_mivr = mt6360_get_mivr, .get_mivr_state = mt6360_get_mivr_state, /* charing termination */ .enable_termination = mt6360_enable_te, /* PE+/PE+20 */ .send_ta_current_pattern = mt6360_set_pep_current_pattern, .set_pe20_efficiency_table = mt6360_set_pep20_efficiency_table, .send_ta20_current_pattern = mt6360_set_pep20_current_pattern, .reset_ta = mt6360_reset_ta, .enable_cable_drop_comp = mt6360_enable_cable_drop_comp, .run_aicl = mt6360_run_aicc, /* Power path */ .enable_powerpath = mt6360_enable_power_path, .is_powerpath_enabled = mt6360_is_power_path_enabled, /* safety timer */ .enable_safety_timer = mt6360_enable_safety_timer, .is_safety_timer_enabled = mt6360_is_safety_timer_enabled, /* OTG */ .enable_otg = mt6360_enable_otg, .set_boost_current_limit = mt6360_set_otg_current_limit, .enable_discharge = mt6360_enable_discharge, /* Charger type detection */ .enable_chg_type_det = mt6360_enable_chg_type_det, /* ADC */ .get_adc = mt6360_get_adc, .get_vbus_adc = mt6360_get_vbus, .get_ibus_adc = mt6360_get_ibus, .get_ibat_adc = mt6360_get_ibat, .get_tchg_adc = mt6360_get_tchg, /* kick wdt */ .kick_wdt = mt6360_kick_wdt, /* misc */ .safety_check = mt6360_safety_check, .reset_eoc_state = mt6360_reset_eoc_state, .is_charging_done = mt6360_is_charging_done, .get_zcv = mt6360_get_zcv, .dump_registers = mt6360_dump_registers, /* event */ .event = mt6360_do_event, /* TypeC */ .enable_usbid = mt6360_enable_usbid, .set_usbid_rup = mt6360_set_usbid_rup, .set_usbid_src_ton = mt6360_set_usbid_src_ton, .enable_usbid_floating = mt6360_enable_usbid_floating, .enable_force_typec_otp = mt6360_enable_force_typec_otp, .get_ctd_dischg_status = mt6360_get_ctd_dischg_status, .enable_hidden_mode = mt6360_enable_hidden_mode, //.enable_hz = mt6360_enable_hz, }; static const struct charger_properties mt6360_chg_props = { .alias_name = "mt6360_chg", }; static irqreturn_t mt6360_pmu_chg_treg_evt_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; int ret = 0; dev_err(mpci->dev, "%s\n", __func__); ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_STAT1); if (ret < 0) return ret; if ((ret & MT6360_MASK_CHG_TREG) >> MT6360_SHFT_CHG_TREG) dev_err(mpci->dev, "%s: thermal regulation loop is active\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chg_aicr_evt_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_warn(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static void mt6360_pmu_chg_irq_enable(const char *name, int en); static irqreturn_t mt6360_pmu_chg_mivr_evt_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; mt_dbg(mpci->dev, "%s\n", __func__); mt6360_pmu_chg_irq_enable("chg_mivr_evt", 0); atomic_inc(&mpci->mivr_cnt); wake_up(&mpci->mivr_wq); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_pwr_rdy_evt_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; bool pwr_rdy = false; int ret = 0; ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_STAT1); if (ret < 0) return ret; pwr_rdy = (ret & MT6360_MASK_PWR_RDY_EVT); dev_info(mpci->dev, "%s: pwr_rdy = %d\n", __func__, pwr_rdy); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chg_batsysuv_evt_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_warn(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chg_vsysuv_evt_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_warn(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chg_vsysov_evt_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_warn(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chg_vbatov_evt_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_warn(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chg_vbusov_evt_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; struct chgdev_notify *noti = &(mpci->chg_dev->noti); bool vbusov_stat = false; int ret = 0; dev_warn(mpci->dev, "%s\n", __func__); ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_STAT2); if (ret < 0) goto out; vbusov_stat = !!(ret & BIT(7)); noti->vbusov_stat = vbusov_stat; dev_info(mpci->dev, "%s: stat = %d\n", __func__, vbusov_stat); out: return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_wd_pmu_det_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_info(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_wd_pmu_done_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_info(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chg_tmri_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; int ret = 0; dev_warn(mpci->dev, "%s\n", __func__); ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_STAT4); if (ret < 0) return IRQ_HANDLED; dev_info(mpci->dev, "%s: chg_stat4 = 0x%02x\n", __func__, ret); if (!(ret & MT6360_MASK_CHG_TMRI)) return IRQ_HANDLED; charger_dev_notify(mpci->chg_dev, CHARGER_DEV_NOTIFY_SAFETY_TIMEOUT); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chg_adpbadi_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_warn(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chg_rvpi_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_warn(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_otpi_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_warn(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chg_aiccmeasl_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_dbg(mpci->dev, "%s\n", __func__); complete(&mpci->aicc_done); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chgdet_donei_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_dbg(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_wdtmri_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; int ret; dev_warn(mpci->dev, "%s\n", __func__); /* Any I2C R/W can kick watchdog timer */ ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_CTRL1); if (ret < 0) dev_err(mpci->dev, "%s: kick wdt failed\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_ssfinishi_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_dbg(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chg_rechgi_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_dbg(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chg_termi_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_dbg(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chg_ieoci_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; bool ieoc_stat = false; int ret = 0; dev_dbg(mpci->dev, "%s\n", __func__); ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_STAT5); if (ret < 0) goto out; ieoc_stat = !!(ret & BIT(7)); if (!ieoc_stat) goto out; out: return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_pumpx_donei_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_info(mpci->dev, "%s\n", __func__); atomic_set(&mpci->pe_complete, 0); complete(&mpci->pumpx_done); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_bst_batuvi_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_warn(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_bst_vbusovi_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_warn(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_bst_olpi_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_warn(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_attachi_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_dbg(mpci->dev, "%s\n", __func__); #ifdef CONFIG_MT6360_PMU_CHARGER_TYPE_DETECT mutex_lock(&mpci->chgdet_lock); if (!mpci->bc12_en) { dev_err(mpci->dev, "%s: bc12 disabled, ignore irq\n", __func__); goto out; } mt6360_chgdet_post_process(mpci); out: mutex_unlock(&mpci->chgdet_lock); #endif /* CONFIG_MT6360_PMU_CHARGER_TYPE_DETECT */ return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_detachi_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_dbg(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_hvdcp_det_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_dbg(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chgdeti_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_dbg(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_dcdti_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; dev_dbg(mpci->dev, "%s\n", __func__); return IRQ_HANDLED; } static irqreturn_t mt6360_pmu_chrdet_ext_evt_handler(int irq, void *data) { struct mt6360_pmu_chg_info *mpci = data; int ret = 0; bool pwr_rdy = false; ret = mt6360_get_chrdet_ext_stat(mpci, &pwr_rdy); dev_info(mpci->dev, "%s: pwr_rdy = %d\n", __func__, pwr_rdy); if (ret < 0) goto out; if (mpci->pwr_rdy == pwr_rdy) goto out; mpci->pwr_rdy = pwr_rdy; #ifdef CONFIG_MT6360_PMU_CHARGER_TYPE_DETECT #ifndef CONFIG_TCPC_CLASS mutex_lock(&mpci->chgdet_lock); (pwr_rdy ? mt6360_chgdet_pre_process : mt6360_chgdet_post_process)(mpci); mutex_unlock(&mpci->chgdet_lock); #endif /* !CONFIG_TCPC_CLASS */ #endif /* CONFIG_MT6360_PMU_CHARGER_TYPE_DETECT */ if (atomic_read(&mpci->pe_complete) && pwr_rdy == true && mpci->mpi->chip_rev <= 0x02) { dev_info(mpci->dev, "%s: re-trigger pe20 pattern\n", __func__); queue_work(mpci->pe_wq, &mpci->pe_work); } out: return IRQ_HANDLED; } static struct mt6360_pmu_irq_desc mt6360_pmu_chg_irq_desc[] = { MT6360_PMU_IRQDESC(chg_treg_evt), MT6360_PMU_IRQDESC(chg_aicr_evt), MT6360_PMU_IRQDESC(chg_mivr_evt), MT6360_PMU_IRQDESC(pwr_rdy_evt), MT6360_PMU_IRQDESC(chg_batsysuv_evt), MT6360_PMU_IRQDESC(chg_vsysuv_evt), MT6360_PMU_IRQDESC(chg_vsysov_evt), MT6360_PMU_IRQDESC(chg_vbatov_evt), MT6360_PMU_IRQDESC(chg_vbusov_evt), MT6360_PMU_IRQDESC(wd_pmu_det), MT6360_PMU_IRQDESC(wd_pmu_done), MT6360_PMU_IRQDESC(chg_tmri), MT6360_PMU_IRQDESC(chg_adpbadi), MT6360_PMU_IRQDESC(chg_rvpi), MT6360_PMU_IRQDESC(otpi), MT6360_PMU_IRQDESC(chg_aiccmeasl), MT6360_PMU_IRQDESC(chgdet_donei), MT6360_PMU_IRQDESC(wdtmri), MT6360_PMU_IRQDESC(ssfinishi), MT6360_PMU_IRQDESC(chg_rechgi), MT6360_PMU_IRQDESC(chg_termi), MT6360_PMU_IRQDESC(chg_ieoci), MT6360_PMU_IRQDESC(pumpx_donei), MT6360_PMU_IRQDESC(bst_batuvi), MT6360_PMU_IRQDESC(bst_vbusovi), MT6360_PMU_IRQDESC(bst_olpi), MT6360_PMU_IRQDESC(attachi), MT6360_PMU_IRQDESC(detachi), MT6360_PMU_IRQDESC(hvdcp_det), MT6360_PMU_IRQDESC(chgdeti), MT6360_PMU_IRQDESC(dcdti), MT6360_PMU_IRQDESC(chrdet_ext_evt), }; static void mt6360_pmu_chg_irq_enable(const char *name, int en) { struct mt6360_pmu_irq_desc *irq_desc; int i = 0; if (unlikely(!name)) return; for (i = 0; i < ARRAY_SIZE(mt6360_pmu_chg_irq_desc); i++) { irq_desc = mt6360_pmu_chg_irq_desc + i; if (unlikely(!irq_desc->name)) continue; if (!strcmp(irq_desc->name, name)) { if (en) enable_irq(irq_desc->irq); else disable_irq_nosync(irq_desc->irq); break; } } } static void mt6360_pmu_chg_irq_register(struct platform_device *pdev) { struct mt6360_pmu_irq_desc *irq_desc; int i, ret; for (i = 0; i < ARRAY_SIZE(mt6360_pmu_chg_irq_desc); i++) { irq_desc = mt6360_pmu_chg_irq_desc + i; if (unlikely(!irq_desc->name)) continue; ret = platform_get_irq_byname(pdev, irq_desc->name); if (ret < 0) continue; irq_desc->irq = ret; ret = devm_request_threaded_irq(&pdev->dev, irq_desc->irq, NULL, irq_desc->irq_handler, IRQF_TRIGGER_FALLING, irq_desc->name, platform_get_drvdata(pdev)); if (ret < 0) dev_err(&pdev->dev, "request %s irq fail\n", irq_desc->name); } } static int mt6360_toggle_cfo(struct mt6360_pmu_chg_info *mpci) { int ret = 0; u8 data = 0; mutex_lock(&mpci->mpi->io_lock); /* check if strobe mode */ ret = i2c_smbus_read_i2c_block_data(mpci->mpi->i2c, MT6360_PMU_FLED_EN, 1, &data); if (ret < 0) { dev_err(mpci->dev, "%s: read cfo fail\n", __func__); goto out; } if (data & MT6360_MASK_STROBE_EN) { dev_err(mpci->dev, "%s: fled in strobe mode\n", __func__); goto out; } /* read data */ ret = i2c_smbus_read_i2c_block_data(mpci->mpi->i2c, MT6360_PMU_CHG_CTRL2, 1, &data); if (ret < 0) { dev_err(mpci->dev, "%s: read cfo fail\n", __func__); goto out; } /* cfo off */ data &= ~MT6360_MASK_CFO_EN; ret = i2c_smbus_write_i2c_block_data(mpci->mpi->i2c, MT6360_PMU_CHG_CTRL2, 1, &data); if (ret < 0) { dev_err(mpci->dev, "%s: clear cfo fail\n", __func__); goto out; } /* cfo on */ data |= MT6360_MASK_CFO_EN; ret = i2c_smbus_write_i2c_block_data(mpci->mpi->i2c, MT6360_PMU_CHG_CTRL2, 1, &data); if (ret < 0) dev_err(mpci->dev, "%s: set cfo fail\n", __func__); out: mutex_unlock(&mpci->mpi->io_lock); return ret; } static int mt6360_chg_mivr_task_threadfn(void *data) { struct mt6360_pmu_chg_info *mpci = data; u32 ibus; int ret; dev_info(mpci->dev, "%s ++\n", __func__); while (!kthread_should_stop()) { wait_event(mpci->mivr_wq, atomic_read(&mpci->mivr_cnt) > 0 || kthread_should_stop()); mt_dbg(mpci->dev, "%s: enter mivr thread\n", __func__); if (kthread_should_stop()) break; pm_stay_awake(mpci->dev); /* check real mivr stat or not */ ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_STAT1); if (ret < 0) goto loop_cont; if (!(ret & MT6360_MASK_MIVR_EVT)) { mt_dbg(mpci->dev, "%s: mivr stat not act\n", __func__); goto loop_cont; } /* read ibus adc */ ret = mt6360_get_ibus(mpci->chg_dev, &ibus); if (ret < 0) { dev_err(mpci->dev, "%s: get ibus adc fail\n", __func__); goto loop_cont; } /* if ibus adc value < 100mA), toggle cfo */ if (ibus < 100000) { dev_dbg(mpci->dev, "%s: enter toggle cfo\n", __func__); ret = mt6360_toggle_cfo(mpci); if (ret < 0) dev_err(mpci->dev, "%s: toggle cfo fail\n", __func__); } loop_cont: pm_relax(mpci->dev); atomic_set(&mpci->mivr_cnt, 0); mt6360_pmu_chg_irq_enable("chg_mivr_evt", 1); msleep(200); } dev_info(mpci->dev, "%s --\n", __func__); return 0; } static void mt6360_trigger_pep_work_handler(struct work_struct *work) { struct mt6360_pmu_chg_info *mpci = (struct mt6360_pmu_chg_info *)container_of(work, struct mt6360_pmu_chg_info, pe_work); int ret = 0; ret = mt6360_set_pep20_current_pattern(mpci->chg_dev, 5000000); if (ret < 0) dev_err(mpci->dev, "%s: trigger pe20 pattern fail\n", __func__); } #if defined(CONFIG_MT6360_PMU_CHARGER_TYPE_DETECT)\ && !defined(CONFIG_TCPC_CLASS) static void mt6360_chgdet_work_handler(struct work_struct *work) { int ret = 0; bool pwr_rdy = false; struct mt6360_pmu_chg_info *mpci = (struct mt6360_pmu_chg_info *)container_of(work, struct mt6360_pmu_chg_info, chgdet_work); mutex_lock(&mpci->chgdet_lock); /* Check PWR_RDY_STAT */ ret = mt6360_get_chrdet_ext_stat(mpci, &pwr_rdy); if (ret < 0) goto out; /* power not good */ if (!pwr_rdy) goto out; /* power good */ mpci->pwr_rdy = pwr_rdy; /* Turn on USB charger detection */ ret = __mt6360_enable_usbchgen(mpci, true); if (ret < 0) dev_err(mpci->dev, "%s: en bc12 fail\n", __func__); out: mutex_unlock(&mpci->chgdet_lock); } #endif /* CONFIG_MT6360_PMU_CHARGER_TYPE_DETECT && !CONFIG_TCPC_CLASS */ static const struct mt6360_pdata_prop mt6360_pdata_props[] = { MT6360_PDATA_VALPROP(ichg, struct mt6360_chg_platform_data, MT6360_PMU_CHG_CTRL7, 2, 0xFC, mt6360_trans_ichg_sel, 0), MT6360_PDATA_VALPROP(aicr, struct mt6360_chg_platform_data, MT6360_PMU_CHG_CTRL3, 2, 0xFC, mt6360_trans_aicr_sel, 0), MT6360_PDATA_VALPROP(mivr, struct mt6360_chg_platform_data, MT6360_PMU_CHG_CTRL6, 1, 0xFE, mt6360_trans_mivr_sel, 0), MT6360_PDATA_VALPROP(cv, struct mt6360_chg_platform_data, MT6360_PMU_CHG_CTRL4, 1, 0xFE, mt6360_trans_cv_sel, 0), MT6360_PDATA_VALPROP(ieoc, struct mt6360_chg_platform_data, MT6360_PMU_CHG_CTRL9, 4, 0xF0, mt6360_trans_ieoc_sel, 0), MT6360_PDATA_VALPROP(safety_timer, struct mt6360_chg_platform_data, MT6360_PMU_CHG_CTRL12, 5, 0xE0, mt6360_trans_safety_timer_sel, 0), MT6360_PDATA_VALPROP(ircmp_resistor, struct mt6360_chg_platform_data, MT6360_PMU_CHG_CTRL18, 3, 0x38, mt6360_trans_ircmp_r_sel, 0), MT6360_PDATA_VALPROP(ircmp_vclamp, struct mt6360_chg_platform_data, MT6360_PMU_CHG_CTRL18, 0, 0x07, mt6360_trans_ircmp_vclamp_sel, 0), MT6360_PDATA_VALPROP(aicc_once, struct mt6360_chg_platform_data, MT6360_PMU_CHG_CTRL14, 0, 0x04, NULL, 0), }; static int mt6360_chg_apply_pdata(struct mt6360_pmu_chg_info *mpci, struct mt6360_chg_platform_data *pdata) { int ret; dev_dbg(mpci->dev, "%s ++\n", __func__); ret = mt6360_pdata_apply_helper(mpci->mpi, pdata, mt6360_pdata_props, ARRAY_SIZE(mt6360_pdata_props)); if (ret < 0) return ret; dev_dbg(mpci->dev, "%s ++\n", __func__); return 0; } static const struct mt6360_val_prop mt6360_val_props[] = { MT6360_DT_VALPROP(ichg, struct mt6360_chg_platform_data), MT6360_DT_VALPROP(aicr, struct mt6360_chg_platform_data), MT6360_DT_VALPROP(mivr, struct mt6360_chg_platform_data), MT6360_DT_VALPROP(cv, struct mt6360_chg_platform_data), MT6360_DT_VALPROP(ieoc, struct mt6360_chg_platform_data), MT6360_DT_VALPROP(safety_timer, struct mt6360_chg_platform_data), MT6360_DT_VALPROP(ircmp_resistor, struct mt6360_chg_platform_data), MT6360_DT_VALPROP(ircmp_vclamp, struct mt6360_chg_platform_data), MT6360_DT_VALPROP(en_te, struct mt6360_chg_platform_data), MT6360_DT_VALPROP(en_wdt, struct mt6360_chg_platform_data), MT6360_DT_VALPROP(en_otg_wdt, struct mt6360_chg_platform_data), MT6360_DT_VALPROP(aicc_once, struct mt6360_chg_platform_data), MT6360_DT_VALPROP(post_aicc, struct mt6360_chg_platform_data), MT6360_DT_VALPROP(batoc_notify, struct mt6360_chg_platform_data), MT6360_DT_VALPROP(bc12_sel, struct mt6360_chg_platform_data), }; static int mt6360_chg_parse_dt_data(struct device *dev, struct mt6360_chg_platform_data *pdata) { struct device_node *np = dev->of_node; dev_dbg(dev, "%s ++\n", __func__); memcpy(pdata, &def_platform_data, sizeof(*pdata)); mt6360_dt_parser_helper(np, (void *)pdata, mt6360_val_props, ARRAY_SIZE(mt6360_val_props)); dev_dbg(dev, "%s --\n", __func__); return 0; } static int mt6360_enable_ilim(struct mt6360_pmu_chg_info *mpci, bool en) { return (en ? mt6360_pmu_reg_set_bits : mt6360_pmu_reg_clr_bits) (mpci->mpi, MT6360_PMU_CHG_CTRL3, MT6360_MASK_ILIM_EN); } static inline bool mt6360_is_meta_mode(struct mt6360_pmu_chg_info *mpci) { return (mpci->bootmode == META_BOOT || mpci->bootmode == ADVMETA_BOOT); } static void mt6360_get_bootmode(struct mt6360_pmu_chg_info *mpci) { struct device_node *boot_node = NULL; const struct tag_bootmode *tag = NULL; boot_node = of_parse_phandle(mpci->dev->of_node, "bootmode", 0); if (!boot_node) { dev_notice(mpci->dev, "%s: get bootmode phandle fail\n", __func__); return; } tag = of_get_property(boot_node, "atag,boot", NULL); if (!tag) { dev_notice(mpci->dev, "%s: get property atag,boot fail\n", __func__); return; } dev_info(mpci->dev, "%s: size:%d tag:0x%X mode:%d type:%d\n", __func__, tag->size, tag->tag, tag->bootmode, tag->boottype); mpci->bootmode = tag->bootmode; } static int mt6360_chg_init_setting(struct mt6360_pmu_chg_info *mpci) { struct mt6360_chg_platform_data *pdata = dev_get_platdata(mpci->dev); int ret = 0; dev_info(mpci->dev, "%s\n", __func__); mt6360_get_bootmode(mpci); ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHRDET_STAT); if (ret >= 0) mpci->ctd_dischg_status = ret & 0xE3; ret = mt6360_pmu_reg_clr_bits(mpci->mpi, MT6360_PMU_CTD_CTRL, 0x40); if (ret < 0) dev_err(mpci->dev, "%s: disable ctd ctrl fail\n", __func__); ret = mt6360_select_input_current_limit(mpci, MT6360_IINLMTSEL_AICR); if (ret < 0) { dev_err(mpci->dev, "%s: select iinlmtsel by aicr fail\n", __func__); return ret; } usleep_range(5000, 6000); ret = mt6360_enable_ilim(mpci, false); if (ret < 0) { dev_err(mpci->dev, "%s: disable ilim fail\n", __func__); return ret; } if (mt6360_is_meta_mode(mpci)) { ret = mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_CHG_CTRL3, MT6360_MASK_AICR, 0x02 << MT6360_SHFT_AICR); dev_info(mpci->dev, "%s: set aicr to 200mA in meta mode\n", __func__); } /* disable wdt reduce 1mA power consumption */ ret = mt6360_enable_wdt(mpci, false); if (ret < 0) { dev_err(mpci->dev, "%s: disable wdt fail\n", __func__); return ret; } /* Disable USB charger type detect, no matter use it or not */ ret = mt6360_enable_usbchgen(mpci, false); if (ret < 0) { dev_err(mpci->dev, "%s: disable chg type detect fail\n", __func__); return ret; } /* unlock ovp limit for pump express, can be replaced by option */ ret = mt6360_select_vinovp(mpci, 14500000); if (ret < 0) { dev_err(mpci->dev, "%s: unlimit vin for pump express\n", __func__); return ret; } /* Disable TE, set TE when plug in/out */ ret = mt6360_pmu_reg_clr_bits(mpci->mpi, MT6360_PMU_CHG_CTRL2, MT6360_MASK_TE_EN); if (ret < 0) { dev_err(mpci->dev, "%s: disable te fail\n", __func__); return ret; } /* Read ZCV */ ret = mt6360_read_zcv(mpci); if (ret < 0) { dev_err(mpci->dev, "%s: read zcv fail\n", __func__); return ret; } /* enable AICC_EN if aicc_once = 0 */ if (!pdata->aicc_once) { ret = mt6360_pmu_reg_set_bits(mpci->mpi, MT6360_PMU_CHG_CTRL14, MT6360_MASK_RG_EN_AICC); if (ret < 0) { dev_err(mpci->dev, "%s: enable en_aicc fail\n", __func__); return ret; } } #ifndef CONFIG_MT6360_DCDTOUT_SUPPORT /* Disable DCD */ ret = mt6360_enable_dcd_tout(mpci, false); if (ret < 0) dev_notice(mpci->dev, "%s disable dcd fail\n", __func__); #endif /* Check BATSYSUV occurred last time boot-on */ ret = mt6360_pmu_reg_read(mpci->mpi, MT6360_PMU_CHG_STAT); if (ret < 0) { dev_err(mpci->dev, "%s: read BATSYSUV fail\n", __func__); return ret; } if (!(ret & MT6360_MASK_CHG_BATSYSUV)) { dev_warn(mpci->dev, "%s: BATSYSUV occurred\n", __func__); ret = mt6360_pmu_reg_set_bits(mpci->mpi, MT6360_PMU_CHG_STAT, MT6360_MASK_CHG_BATSYSUV); if (ret < 0) dev_err(mpci->dev, "%s: clear BATSYSUV fail\n", __func__); } /* USBID ID_TD = 32T */ ret = mt6360_pmu_reg_update_bits(mpci->mpi, MT6360_PMU_USBID_CTRL2, MT6360_MASK_IDTD | MT6360_MASK_USBID_FLOAT, 0x62); /* Disable TypeC OTP for check EVB version by TS pin */ ret = mt6360_pmu_reg_clr_bits(mpci->mpi, MT6360_PMU_TYPEC_OTP_CTRL, MT6360_MASK_TYPEC_OTP_EN); return ret; } static int mt6360_set_shipping_mode(struct mt6360_pmu_chg_info *mpci) { struct mt6360_pmu_info *mpi = mpci->mpi; int ret; u8 data = 0; dev_info(mpci->dev, "%s\n", __func__); mutex_lock(&mpi->io_lock); /* disable shipping mode rst */ ret = i2c_smbus_read_i2c_block_data(mpi->i2c, MT6360_PMU_CORE_CTRL2, 1, &data); if (ret < 0) goto out; data |= MT6360_MASK_SHIP_RST_DIS; dev_info(mpci->dev, "%s: reg[0x06] = 0x%02x\n", __func__, data); ret = i2c_smbus_write_i2c_block_data(mpi->i2c, MT6360_PMU_CORE_CTRL2, 1, &data); if (ret < 0) { dev_err(mpci->dev, "%s: fail to disable shipping mode rst\n", __func__); goto out; } data = 0x80; /* enter shipping mode and disable cfo_en/chg_en */ ret = i2c_smbus_write_i2c_block_data(mpi->i2c, MT6360_PMU_CHG_CTRL2, 1, &data); if (ret < 0) dev_err(mpci->dev, "%s: fail to enter shipping mode\n", __func__); out: mutex_unlock(&mpi->io_lock); return ret; } static ssize_t shipping_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mt6360_pmu_chg_info *mpci = dev_get_drvdata(dev); int32_t tmp = 0; int ret = 0; if (kstrtoint(buf, 10, &tmp) < 0) { dev_notice(dev, "parsing number fail\n"); return -EINVAL; } if (tmp != 5526789) return -EINVAL; ret = mt6360_set_shipping_mode(mpci); if (ret < 0) return ret; return count; } static const DEVICE_ATTR_WO(shipping_mode); #ifdef FIXME /* TODO: wait mtk_charger_intf.h */ void mt6360_recv_batoc_callback(BATTERY_OC_LEVEL tag) { int ret, cnt = 0; if (tag != BATTERY_OC_LEVEL_1) return; while (!pmic_get_register_value(PMIC_RG_INT_STATUS_FG_CUR_H)) { if (cnt >= 1) { ret = mt6360_set_shipping_mode(g_mpci); if (ret < 0) dev_err(g_mpci->dev, "%s: set shipping mode fail\n", __func__); else dev_info(g_mpci->dev, "%s: set shipping mode done\n", __func__); } mdelay(8); cnt++; } dev_info(g_mpci->dev, "%s exit, cnt = %d, FG_CUR_H = %d\n", __func__, cnt, pmic_get_register_value(PMIC_RG_INT_STATUS_FG_CUR_H)); } #endif /* ======================= */ /* MT6360 Power Supply Ops */ /* ======================= */ static int mt6360_pmu_chg_get_online(struct mt6360_pmu_chg_info *mpci, union power_supply_propval *val) { bool online = false; #ifdef CONFIG_TCPC_CLASS mutex_lock(&mpci->attach_lock); online = mpci->typec_attach; mutex_unlock(&mpci->attach_lock); #else int ret = 0; ret = mt6360_get_chrdet_ext_stat(mpci, &online); if (ret < 0) { dev_notice(mpci->dev, "%s: read chrdet_ext_stat fail\n", __func__); return ret; } #endif dev_info(mpci->dev, "%s: online = %d\n", __func__, online); val->intval = online; return 0; } static int mt6360_pmu_chg_set_online(struct mt6360_pmu_chg_info *mpci, const union power_supply_propval *val) { return mt6360_enable_chg_type_det(mpci->chg_dev, val->intval); } static int mt6360_pmu_chg_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct mt6360_pmu_chg_info *mpci = power_supply_get_drvdata(psy); enum mt6360_charging_status chg_stat = MT6360_CHG_STATUS_READY; int ret = 0; val->intval = 0; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: ret = mt6360_pmu_chg_get_online(mpci, val); break; case POWER_SUPPLY_PROP_AUTHENTIC: val->intval = mpci->ignore_usb; break; case POWER_SUPPLY_PROP_TYPE: val->intval = mpci->psy_desc.type; break; case POWER_SUPPLY_PROP_USB_TYPE: val->intval = mpci->psy_usb_type; break; case POWER_SUPPLY_PROP_STATUS: ret = mt6360_pmu_chg_get_online(mpci, val); if (!val->intval) { val->intval = POWER_SUPPLY_STATUS_DISCHARGING; break; } ret = mt6360_get_charging_status(mpci, &chg_stat); switch (chg_stat) { case MT6360_CHG_STATUS_READY: case MT6360_CHG_STATUS_FAULT: val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; case MT6360_CHG_STATUS_PROGRESS: val->intval = POWER_SUPPLY_STATUS_CHARGING; break; case MT6360_CHG_STATUS_DONE: val->intval = POWER_SUPPLY_STATUS_FULL; break; default: val->intval = POWER_SUPPLY_STATUS_UNKNOWN; break; } break; case POWER_SUPPLY_PROP_CURRENT_MAX: if (mpci->psy_desc.type == POWER_SUPPLY_TYPE_USB) val->intval = 500000; break; case POWER_SUPPLY_PROP_VOLTAGE_MAX: if (mpci->psy_desc.type == POWER_SUPPLY_TYPE_USB) val->intval = 5000000; break; default: ret = -ENODATA; break; } return ret; } static int mt6360_pmu_chg_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct mt6360_pmu_chg_info *mpci = power_supply_get_drvdata(psy); int ret = 0; dev_dbg(mpci->dev, "%s: prop = %d\n", __func__, psp); switch (psp) { case POWER_SUPPLY_PROP_ONLINE: ret = mt6360_pmu_chg_set_online(mpci, val); break; default: ret = -EINVAL; break; } return ret; } static int mt6360_pmu_chg_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { switch (psp) { case POWER_SUPPLY_PROP_ONLINE: return 1; default: return 0; } } static enum power_supply_property mt6360_pmu_chg_properties[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_AUTHENTIC, POWER_SUPPLY_PROP_TYPE, POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CURRENT_MAX, POWER_SUPPLY_PROP_VOLTAGE_MAX, }; static const struct power_supply_desc mt6360_pmu_chg_desc = { .type = POWER_SUPPLY_TYPE_USB, .properties = mt6360_pmu_chg_properties, .num_properties = ARRAY_SIZE(mt6360_pmu_chg_properties), .get_property = mt6360_pmu_chg_get_property, .set_property = mt6360_pmu_chg_set_property, .property_is_writeable = mt6360_pmu_chg_property_is_writeable, .usb_types = mt6360_pmu_chg_usb_types, .num_usb_types = ARRAY_SIZE(mt6360_pmu_chg_usb_types), }; static char *mt6360_pmu_chg_supplied_to[] = { "battery", "mtk-master-charger" }; static void mt6360_power_supply_changed(struct mt6360_pmu_chg_info *mpci) { int ret = 0, i = 0; union power_supply_propval propval[mt6360_pmu_chg_desc.num_properties]; memset(propval, 0, sizeof(propval)); for (i = 0; i < ARRAY_SIZE(propval); i++) { ret = mt6360_pmu_chg_get_property(mpci->psy, mt6360_pmu_chg_properties[i], &propval[i]); if (ret < 0) dev_notice(mpci->dev, "%s: get prop fail(%d), i = %d\n", __func__, ret, i); } if (memcmp(mpci->old_propval, propval, sizeof(propval))) { memcpy(mpci->old_propval, propval, sizeof(propval)); power_supply_changed(mpci->psy); } } /*otg_vbus*/ static int mt6360_boost_enable(struct regulator_dev *rdev) { struct mt6360_pmu_chg_info *mpci = rdev_get_drvdata(rdev); return mt6360_enable_otg(mpci->chg_dev, true); } static int mt6360_boost_disable(struct regulator_dev *rdev) { struct mt6360_pmu_chg_info *mpci = rdev_get_drvdata(rdev); return mt6360_enable_otg(mpci->chg_dev, false); } static int mt6360_boost_is_enabled(struct regulator_dev *rdev) { struct mt6360_pmu_chg_info *mpci = rdev_get_drvdata(rdev); const struct regulator_desc *desc = rdev->desc; int ret = 0; ret = mt6360_pmu_reg_read(mpci->mpi, desc->enable_reg); if (ret < 0) return ret; return ret & desc->enable_mask ? true : false; } static int mt6360_boost_set_voltage_sel(struct regulator_dev *rdev, unsigned int sel) { /*sel = min-min_uv/vsetp*/ struct mt6360_pmu_chg_info *mpci = rdev_get_drvdata(rdev); const struct regulator_desc *desc = rdev->desc; int shift = ffs(desc->vsel_mask) - 1; u8 data = sel + 0x11;//0x11 = 4.85V return mt6360_pmu_reg_update_bits(mpci->mpi, desc->enable_reg, desc->enable_mask, data<desc; int shift = ffs(desc->vsel_mask) - 1, ret; ret = mt6360_pmu_reg_read(mpci->mpi, desc->vsel_reg); if (ret < 0) return ret; return (((ret & desc->vsel_mask) >> shift)-0x11); } static int mt6360_boost_set_current_limit(struct regulator_dev *rdev, int min_uA, int max_uA) { struct mt6360_pmu_chg_info *mpci = rdev_get_drvdata(rdev); const struct regulator_desc *desc = rdev->desc; int i, shift = ffs(desc->csel_mask) - 1; for (i = 0; i < ARRAY_SIZE(mt6360_otg_oc_threshold); i++) { if (min_uA <= mt6360_otg_oc_threshold[i]) break; } if (i == ARRAY_SIZE(mt6360_otg_oc_threshold) || mt6360_otg_oc_threshold[i] > max_uA) { dev_notice(mpci->dev, "%s: out of current range\n", __func__); return -EINVAL; } dev_info(mpci->dev, "%s: select otg_oc = %d\n", __func__, mt6360_otg_oc_threshold[i]); return mt6360_pmu_reg_update_bits(mpci->mpi, desc->csel_reg, desc->csel_mask, i << shift); } static int mt6360_boost_get_current_limit(struct regulator_dev *rdev) { struct mt6360_pmu_chg_info *mpci = rdev_get_drvdata(rdev); const struct regulator_desc *desc = rdev->desc; int shift = ffs(desc->csel_mask) - 1, ret; ret = mt6360_pmu_reg_read(mpci->mpi, desc->csel_reg); if (ret < 0) return ret; ret = (ret & desc->csel_mask) >> shift; if (ret >= ARRAY_SIZE(mt6360_otg_oc_threshold)) return -EINVAL; return mt6360_otg_oc_threshold[ret]; } static const struct regulator_ops mt6360_chg_otg_ops = { .list_voltage = regulator_list_voltage_linear, .enable = mt6360_boost_enable, .disable = mt6360_boost_disable, .is_enabled = mt6360_boost_is_enabled, .set_voltage_sel = mt6360_boost_set_voltage_sel, .get_voltage_sel = mt6360_boost_get_voltage_sel, .set_current_limit = mt6360_boost_set_current_limit, .get_current_limit = mt6360_boost_get_current_limit, }; static const struct regulator_desc mt6360_otg_rdesc = { .of_match = "usb-otg-vbus", .name = "usb-otg-vbus", .ops = &mt6360_chg_otg_ops, .owner = THIS_MODULE, .type = REGULATOR_VOLTAGE, .min_uV = 4850000, .uV_step = 25000, /* 25mV per step */ .n_voltages = 40, /* 4850mV to 5825mV */ .vsel_reg = MT6360_PMU_CHG_CTRL5, .vsel_mask = 0xFC, .enable_reg = MT6360_PMU_CHG_CTRL1, .enable_mask = MT6360_MASK_OPA_MODE, .csel_reg = MT6360_PMU_CHG_CTRL10, .csel_mask = MT6360_MASK_OTG_OC, }; //====pd_notifier_start=== #ifdef CONFIG_TCPC_CLASS static int mt6360_get_charger_type(struct mt6360_pmu_chg_info *mpci, bool attach) { static struct power_supply *chg_psy; int ret = 0; union power_supply_propval val = {.intval = 0}; struct mt6360_chg_platform_data *pdata = dev_get_platdata(mpci->dev); if (!chg_psy) { if (pdata->bc12_sel == 1) chg_psy = power_supply_get_by_name("mtk_charger_type"); else if (pdata->bc12_sel == 2) chg_psy = power_supply_get_by_name("ext_charger_type"); } if (!chg_psy) { pr_notice("%s Couldn't get chg_psy\n", __func__); mpci->psy_desc.type = attach ? POWER_SUPPLY_TYPE_USB : POWER_SUPPLY_TYPE_UNKNOWN; mpci->psy_usb_type = attach ? POWER_SUPPLY_USB_TYPE_DCP : POWER_SUPPLY_USB_TYPE_UNKNOWN; goto out; } if (attach) { val.intval = true; ret = power_supply_set_property(chg_psy, POWER_SUPPLY_PROP_ONLINE, &val); ret = power_supply_get_property(chg_psy, POWER_SUPPLY_PROP_TYPE, &val); mpci->psy_desc.type = val.intval; pr_notice("%s type:%d\n", __func__, val.intval); ret = power_supply_get_property(chg_psy, POWER_SUPPLY_PROP_USB_TYPE, &val); mpci->psy_usb_type = val.intval; pr_notice("%s usb_type:%d\n", __func__, val.intval); } else { mpci->psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN; mpci->psy_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; } out: mt6360_power_supply_changed(mpci); return mpci->psy_usb_type; } static int typec_attach_thread(void *data) { struct mt6360_pmu_chg_info *mpci = data; struct mt6360_chg_platform_data *pdata = dev_get_platdata(mpci->dev); int ret = 0; bool typec_attach = false, ignore_usb = false; union power_supply_propval val = {.intval = 0}; pr_info("%s: ++\n", __func__); while (!kthread_should_stop()) { wait_event(mpci->attach_wq, atomic_read(&mpci->chrdet_start) > 0 || kthread_should_stop()); if (kthread_should_stop()) break; mutex_lock(&mpci->attach_lock); typec_attach = mpci->typec_attach; ignore_usb = mpci->ignore_usb; atomic_set(&mpci->chrdet_start, 0); mutex_unlock(&mpci->attach_lock); pr_notice("%s bc12_sel:%d typec_attach:%d ignore_usb:%d\n", __func__, pdata->bc12_sel, typec_attach, ignore_usb); if (typec_attach && ignore_usb) { mpci->bypass_chgdet = true; goto bypass_chgdet; } else if (!typec_attach && mpci->bypass_chgdet) { mpci->bypass_chgdet = false; goto bypass_chgdet; } val.intval = typec_attach; if (pdata->bc12_sel == 0) { ret = power_supply_set_property(mpci->chg_psy, POWER_SUPPLY_PROP_ONLINE, &val); if (ret < 0) dev_info(mpci->dev, "%s: set online fail(%d)\n", __func__, ret); } else mt6360_get_charger_type(mpci, typec_attach); continue; bypass_chgdet: mutex_lock(&mpci->chgdet_lock); if (typec_attach) { mpci->psy_desc.type = POWER_SUPPLY_TYPE_USB; if (tcpm_inquire_typec_attach_state(mpci->tcpc) == TYPEC_ATTACHED_AUDIO) mpci->psy_usb_type = POWER_SUPPLY_USB_TYPE_DCP; else mpci->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP; } else { mpci->psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN; mpci->psy_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; } mutex_unlock(&mpci->chgdet_lock); mt6360_power_supply_changed(mpci); } return ret; } static void handle_typec_attach(struct mt6360_pmu_chg_info *mpci, bool attach, bool ignore) { mutex_lock(&mpci->attach_lock); if (mpci->typec_attach == attach) { mutex_unlock(&mpci->attach_lock); return; } mpci->typec_attach = attach; mpci->ignore_usb = ignore; atomic_inc(&mpci->chrdet_start); mutex_unlock(&mpci->attach_lock); wake_up(&mpci->attach_wq); } static int pd_tcp_notifier_call(struct notifier_block *nb, unsigned long event, void *data) { struct tcp_notify *noti = data; struct mt6360_pmu_chg_info *mpci = container_of(nb, struct mt6360_pmu_chg_info, pd_nb); uint8_t old_state = TYPEC_UNATTACHED, new_state = TYPEC_UNATTACHED; switch (event) { case TCP_NOTIFY_SINK_VBUS: if (tcpm_inquire_typec_attach_state(mpci->tcpc) == TYPEC_ATTACHED_AUDIO) handle_typec_attach(mpci, !!noti->vbus_state.mv, true); break; case TCP_NOTIFY_TYPEC_STATE: old_state = noti->typec_state.old_state; new_state = noti->typec_state.new_state; if (old_state == TYPEC_UNATTACHED && (new_state == TYPEC_ATTACHED_SNK || new_state == TYPEC_ATTACHED_NORP_SRC || new_state == TYPEC_ATTACHED_CUSTOM_SRC || new_state == TYPEC_ATTACHED_DBGACC_SNK)) { dev_info(mpci->dev, "%s Charger plug in, polarity = %d\n", __func__, noti->typec_state.polarity); handle_typec_attach(mpci, true, false); } else if ((old_state == TYPEC_ATTACHED_SNK || old_state == TYPEC_ATTACHED_NORP_SRC || old_state == TYPEC_ATTACHED_CUSTOM_SRC || old_state == TYPEC_ATTACHED_DBGACC_SNK || old_state == TYPEC_ATTACHED_AUDIO) && new_state == TYPEC_UNATTACHED) { dev_info(mpci->dev, "%s Charger plug out\n", __func__); handle_typec_attach(mpci, false, false); } else if (old_state == TYPEC_ATTACHED_SRC && new_state == TYPEC_ATTACHED_SNK) { dev_info(mpci->dev, "%s Source_to_Sink\n", __func__); handle_typec_attach(mpci, true, true); } else if (old_state == TYPEC_ATTACHED_SNK && new_state == TYPEC_ATTACHED_SRC) { dev_info(mpci->dev, "%s Sink_to_Source\n", __func__); handle_typec_attach(mpci, false, true); } break; default: break; }; return NOTIFY_OK; } #endif //====pd_notifier_end===== static int mt6360_pmu_chg_probe(struct platform_device *pdev) { struct mt6360_chg_platform_data *pdata = dev_get_platdata(&pdev->dev); struct mt6360_pmu_chg_info *mpci; struct iio_channel *channel; struct power_supply_config charger_cfg = {}; struct regulator_config config = { }; bool use_dt = pdev->dev.of_node; int i, ret = 0; dev_info(&pdev->dev, "%s\n", __func__); if (use_dt) { pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; ret = mt6360_chg_parse_dt_data(&pdev->dev, pdata); if (ret < 0) { dev_err(&pdev->dev, "parse dt fail\n"); return ret; } pdev->dev.platform_data = pdata; } if (!pdata) { dev_err(&pdev->dev, "no platform data specified\n"); return -EINVAL; } mpci = devm_kzalloc(&pdev->dev, sizeof(*mpci), GFP_KERNEL); if (!mpci) return -ENOMEM; mpci->dev = &pdev->dev; mpci->mpi = dev_get_drvdata(pdev->dev.parent); mpci->hidden_mode_cnt = 0; mutex_init(&mpci->hidden_mode_lock); mutex_init(&mpci->pe_lock); mutex_init(&mpci->aicr_lock); mutex_init(&mpci->chgdet_lock); mutex_init(&mpci->tchg_lock); mutex_init(&mpci->ichg_lock); mpci->tchg = 0; mpci->ichg = 2000000; mpci->ichg_dis_chg = 2000000; mpci->attach = false; g_mpci = mpci; #if defined(CONFIG_MT6360_PMU_CHARGER_TYPE_DETECT)\ && !defined(CONFIG_TCPC_CLASS) INIT_WORK(&mpci->chgdet_work, mt6360_chgdet_work_handler); #endif /* CONFIG_MT6360_PMU_CHARGER_TYPE_DETECT && !CONFIG_TCPC_CLASS */ init_completion(&mpci->aicc_done); init_completion(&mpci->pumpx_done); atomic_set(&mpci->pe_complete, 0); atomic_set(&mpci->mivr_cnt, 0); init_waitqueue_head(&mpci->mivr_wq); #ifdef CONFIG_TCPC_CLASS init_waitqueue_head(&mpci->attach_wq); atomic_set(&mpci->chrdet_start, 0); mutex_init(&mpci->attach_lock); #endif mpci->old_propval = devm_kcalloc(mpci->dev, mt6360_pmu_chg_desc.num_properties, sizeof(*mpci->old_propval), GFP_KERNEL); if (!mpci->old_propval) return -ENOMEM; platform_set_drvdata(pdev, mpci); /* apply platform data */ ret = mt6360_chg_apply_pdata(mpci, pdata); if (ret < 0) { dev_err(&pdev->dev, "apply pdata fail\n"); goto err_mutex_init; } /* Initial Setting */ ret = mt6360_chg_init_setting(mpci); if (ret < 0) { dev_err(&pdev->dev, "%s: init setting fail\n", __func__); goto err_mutex_init; } /* Get ADC iio channels */ for (i = 0; i < MT6360_ADC_MAX; i++) { channel = devm_iio_channel_get(&pdev->dev, mt6360_adc_chan_list[i]); if (IS_ERR(channel)) { ret = PTR_ERR(channel); goto err_mutex_init; } mpci->channels[i] = channel; } /* charger class register */ mpci->chg_dev = charger_device_register(pdata->chg_name, mpci->dev, mpci, &mt6360_chg_ops, &mt6360_chg_props); if (IS_ERR(mpci->chg_dev)) { dev_err(mpci->dev, "charger device register fail\n"); ret = PTR_ERR(mpci->chg_dev); goto err_mutex_init; } /* irq register */ mt6360_pmu_chg_irq_register(pdev); device_init_wakeup(&pdev->dev, true); /* mivr task */ mpci->mivr_task = kthread_run(mt6360_chg_mivr_task_threadfn, mpci, "mivr_thread.%s", dev_name(mpci->dev)); ret = PTR_ERR_OR_ZERO(mpci->mivr_task); if (ret < 0) { dev_err(mpci->dev, "create mivr handling thread fail\n"); goto err_register_chg_dev; } ret = device_create_file(mpci->dev, &dev_attr_shipping_mode); if (ret < 0) { dev_notice(&pdev->dev, "create shipping attr fail\n"); goto err_create_mivr_thread_run; } /* for trigger unfinish pe pattern */ mpci->pe_wq = create_singlethread_workqueue("pe_pattern"); if (!mpci->pe_wq) { dev_err(mpci->dev, "%s: create pe_pattern work queue fail\n", __func__); goto err_shipping_mode_attr; } INIT_WORK(&mpci->pe_work, mt6360_trigger_pep_work_handler); /* register fg bat oc notify */ #ifdef FIXME //without mtk_charger_intf BATTERY_OC_LEVEL if (pdata->batoc_notify) register_battery_oc_notify(&mt6360_recv_batoc_callback, BATTERY_OC_PRIO_CHARGER); #endif /*new framework*/ /* otg regulator */ config.dev = &pdev->dev; config.driver_data = mpci; mpci->otg_rdev = devm_regulator_register(&pdev->dev, &mt6360_otg_rdesc, &config); if (IS_ERR(mpci->otg_rdev)) { ret = PTR_ERR(mpci->otg_rdev); goto err_register_otg; } /* power supply register */ memcpy(&mpci->psy_desc, &mt6360_pmu_chg_desc, sizeof(mpci->psy_desc)); mpci->psy_desc.name = dev_name(&pdev->dev); charger_cfg.drv_data = mpci; charger_cfg.of_node = pdev->dev.of_node; charger_cfg.supplied_to = mt6360_pmu_chg_supplied_to; charger_cfg.num_supplicants = ARRAY_SIZE(mt6360_pmu_chg_supplied_to); mpci->psy = devm_power_supply_register(&pdev->dev, &mpci->psy_desc, &charger_cfg); if (IS_ERR(mpci->psy)) { dev_notice(&pdev->dev, "Fail to register power supply dev\n"); ret = PTR_ERR(mpci->psy); goto err_register_psy; } #ifdef CONFIG_TCPC_CLASS /*get bc1.2 power supply:chg_psy*/ mpci->chg_psy = devm_power_supply_get_by_phandle(&pdev->dev, "charger"); if (IS_ERR(mpci->chg_psy)) { dev_notice(&pdev->dev, "Failed to get charger psy\n"); ret = PTR_ERR(mpci->chg_psy); goto err_psy_get_phandle; } mpci->attach_task = kthread_run(typec_attach_thread, mpci, "attach_thread"); if (IS_ERR(mpci->attach_task)) { ret = PTR_ERR(mpci->attach_task); goto err_attach_task; } mpci->tcpc = tcpc_dev_get_by_name("type_c_port0"); if (!mpci->tcpc) { pr_notice("%s get tcpc device type_c_port0 fail\n", __func__); ret = -ENODEV; goto err_get_tcpcdev; } mpci->pd_nb.notifier_call = pd_tcp_notifier_call; ret = register_tcp_dev_notifier(mpci->tcpc, &mpci->pd_nb, TCP_NOTIFY_TYPE_ALL); if (ret < 0) { pr_notice("%s: register tcpc notifer fail\n", __func__); ret = -EINVAL; goto err_register_tcp_notifier; } #endif /* Schedule work for microB's BC1.2 */ #if defined(CONFIG_MT6360_PMU_CHARGER_TYPE_DETECT)\ && !defined(CONFIG_TCPC_CLASS) schedule_work(&mpci->chgdet_work); #endif /* CONFIG_MT6360_PMU_CHARGER_TYPE_DETECT && !CONFIG_TCPC_CLASS */ dev_info(&pdev->dev, "%s: successfully probed\n", __func__); return 0; #ifdef CONFIG_TCPC_CLASS err_register_tcp_notifier: err_get_tcpcdev: if (mpci->attach_task) kthread_stop(mpci->attach_task); err_attach_task: err_psy_get_phandle: #endif err_register_psy: err_register_otg: destroy_workqueue(mpci->pe_wq); err_shipping_mode_attr: device_remove_file(mpci->dev, &dev_attr_shipping_mode); err_create_mivr_thread_run: if (mpci->mivr_task) kthread_stop(mpci->mivr_task); err_register_chg_dev: charger_device_unregister(mpci->chg_dev); err_mutex_init: mutex_destroy(&mpci->tchg_lock); mutex_destroy(&mpci->chgdet_lock); mutex_destroy(&mpci->aicr_lock); mutex_destroy(&mpci->pe_lock); mutex_destroy(&mpci->hidden_mode_lock); mutex_destroy(&mpci->attach_lock); return -EPROBE_DEFER; } static int mt6360_pmu_chg_remove(struct platform_device *pdev) { struct mt6360_pmu_chg_info *mpci = platform_get_drvdata(pdev); dev_dbg(mpci->dev, "%s\n", __func__); flush_workqueue(mpci->pe_wq); destroy_workqueue(mpci->pe_wq); if (mpci->mivr_task) { atomic_inc(&mpci->mivr_cnt); wake_up(&mpci->mivr_wq); kthread_stop(mpci->mivr_task); } if (mpci->attach_task) kthread_stop(mpci->attach_task); device_remove_file(mpci->dev, &dev_attr_shipping_mode); charger_device_unregister(mpci->chg_dev); mutex_destroy(&mpci->tchg_lock); mutex_destroy(&mpci->chgdet_lock); mutex_destroy(&mpci->aicr_lock); mutex_destroy(&mpci->pe_lock); mutex_destroy(&mpci->hidden_mode_lock); mutex_destroy(&mpci->attach_lock); return 0; } static int __maybe_unused mt6360_pmu_chg_suspend(struct device *dev) { return 0; } static int __maybe_unused mt6360_pmu_chg_resume(struct device *dev) { return 0; } static SIMPLE_DEV_PM_OPS(mt6360_pmu_chg_pm_ops, mt6360_pmu_chg_suspend, mt6360_pmu_chg_resume); static const struct of_device_id __maybe_unused mt6360_pmu_chg_of_id[] = { { .compatible = "mediatek,mt6360_pmu_chg", }, {}, }; MODULE_DEVICE_TABLE(of, mt6360_pmu_chg_of_id); static const struct platform_device_id mt6360_pmu_chg_id[] = { { "mt6360_pmu_chg", 0 }, {}, }; MODULE_DEVICE_TABLE(platform, mt6360_pmu_chg_id); static struct platform_driver mt6360_pmu_chg_driver = { .driver = { .name = "mt6360_pmu_chg", .owner = THIS_MODULE, .pm = &mt6360_pmu_chg_pm_ops, .of_match_table = of_match_ptr(mt6360_pmu_chg_of_id), }, .probe = mt6360_pmu_chg_probe, .remove = mt6360_pmu_chg_remove, .id_table = mt6360_pmu_chg_id, }; static int __init mt6360_pmu_chg_init(void) { return platform_driver_register(&mt6360_pmu_chg_driver); } device_initcall_sync(mt6360_pmu_chg_init); static void __exit mt6360_pmu_chg_exit(void) { platform_driver_unregister(&mt6360_pmu_chg_driver); } module_exit(mt6360_pmu_chg_exit); MODULE_AUTHOR("CY_Huang "); MODULE_DESCRIPTION("MT6360 PMU CHG Driver"); MODULE_LICENSE("GPL"); MODULE_VERSION(MT6360_PMU_CHG_DRV_VERSION); /* * Version Note * 1.0.8_MTK * (1) Fix no charging icon when power role swap to sink on kernel-4.19 * (2) Fix mt6360_pmu_chg_ieoci_handler() * * 1.0.7_MTK * (1) Fix Unbalanced enable for MIVR IRQ * (2) Sleep 200ms before do another iteration in mt6360_chg_mivr_task_threadfn * * 1.0.6_MTK * (1) Fix the usages of charger power supply * * 1.0.5_MTK * (1) Prevent charger type infromed repeatedly * * 1.0.4_MTK * (1) Mask mivr irq until mivr task has run an iteration * * 1.0.3_MTK * (1) fix zcv adc from 5mV to 1.25mV per step * (2) add BC12 initial setting dcd timeout disable when unuse dcd * * 1.0.2_MTK * (1) remove eoc, rechg, te irq for evb with phone load * (2) report power supply online with chg type detect done * (3) remove unused irq event and status * (4) add chg termination irq notifier when safety timer timeout * * 1.0.1_MTK * (1) fix dtsi parse attribute about en_te, en_wdt, aicc_once * (2) add charger class get vbus adc interface * (3) add initial setting about disable en_sdi, and check batsysuv. * * 1.0.0_MTK * (1) Initial Release */