// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. * Author Wy Chuang */ #include /* For init/exit macros */ #include /* For MODULE_ marcros */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* PD */ #include #include "adapter_class.h" #define PHY_MODE_DPDMPULLDOWN_SET 3 #define PHY_MODE_DPDMPULLDOWN_CLR 4 struct mtk_pd_adapter_info { struct tcpc_device *tcpc; struct notifier_block pd_nb; struct adapter_device *adapter_dev; struct task_struct *adapter_task; const char *adapter_dev_name; bool enable_kpoc_shdn; int pd_type; bool force_cv; u32 ita_min; u32 bootmode; u32 boottype; bool enable_pp; }; struct apdo_pps_range { u32 prog_mv; u32 min_mv; u32 max_mv; }; static struct apdo_pps_range __maybe_unused apdo_pps_tbl[] = { {5000, 3300, 5900}, /* 5VProg */ {9000, 3300, 11000}, /* 9VProg */ {15000, 3300, 16000}, /* 15VProg */ {20000, 3300, 21000}, /* 20VProg */ }; //void notify_adapter_event(enum adapter_type type, enum adapter_event evt, // void *val); static int pd_adapter_enable_power_path(bool en) { static struct power_supply *chg_psy; union power_supply_propval val; if (chg_psy == NULL) chg_psy = power_supply_get_by_name("mtk-master-charger"); if (chg_psy == NULL || IS_ERR(chg_psy)) { pr_notice("%s Couldn't get chg_psy\n", __func__); return -1; } val.intval = !en; return power_supply_set_property(chg_psy, POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val); } static int pd_adapter_high_voltage_enable(int enable) { union power_supply_propval prop; static struct power_supply *chg_psy; int ret; if (chg_psy == NULL) chg_psy = power_supply_get_by_name("mtk-master-charger"); if (chg_psy == NULL || IS_ERR(chg_psy)) { pr_notice("%s Couldn't get chg_psy\n", __func__); ret = -1; } else { prop.intval = enable; ret = power_supply_set_property(chg_psy, POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop); pr_notice("%s enable_hv:%d\n", __func__, prop.intval); power_supply_changed(chg_psy); } return ret; } static inline int to_mtk_adapter_ret(int tcpm_ret) { switch (tcpm_ret) { case TCP_DPM_RET_SUCCESS: return MTK_ADAPTER_OK; case TCP_DPM_RET_NOT_SUPPORT: return MTK_ADAPTER_NOT_SUPPORT; case TCP_DPM_RET_TIMEOUT: return MTK_ADAPTER_TIMEOUT; case TCP_DPM_RET_REJECT: return MTK_ADAPTER_REJECT; default: return MTK_ADAPTER_ERROR; } } static inline int check_typec_attached_snk(struct tcpc_device *tcpc) { if (tcpm_inquire_typec_attach_state(tcpc) != TYPEC_ATTACHED_SNK) return -EINVAL; return 0; } static int usb_dpdm_pulldown(struct adapter_device *adapter, bool dpdm_pulldown) { struct phy *phy; int mode = 0; int ret; mode = dpdm_pulldown ? PHY_MODE_DPDMPULLDOWN_SET : PHY_MODE_DPDMPULLDOWN_CLR; phy = phy_get(adapter->dev.parent, "usb2-phy"); if (IS_ERR_OR_NULL(phy)) { dev_info(&adapter->dev, "phy_get fail\n"); return -EINVAL; } ret = phy_set_mode_ext(phy, PHY_MODE_USB_DEVICE, mode); if (ret) dev_info(&adapter->dev, "phy_set_mode_ext fail\n"); phy_put(&adapter->dev, phy); return 0; } static int pd_tcp_notifier_call(struct notifier_block *pnb, unsigned long event, void *data) { struct tcp_notify *noti = data; struct mtk_pd_adapter_info *pinfo; struct adapter_device *adapter; int ret = 0, sink_mv, sink_ma; pinfo = container_of(pnb, struct mtk_pd_adapter_info, pd_nb); adapter = pinfo->adapter_dev; pr_notice("PD charger event:%d %d\n", (int)event, (int)noti->pd_state.connected); switch (event) { case TCP_NOTIFY_PD_STATE: switch (noti->pd_state.connected) { case PD_CONNECT_NONE: pinfo->pd_type = MTK_PD_CONNECT_NONE; ret = srcu_notifier_call_chain(&adapter->evt_nh, MTK_PD_CONNECT_NONE, NULL); // notify_adapter_event(MTK_PD_ADAPTER, // MTK_PD_CONNECT_NONE, NULL); break; case PD_CONNECT_HARD_RESET: pinfo->pd_type = MTK_PD_CONNECT_NONE; ret = srcu_notifier_call_chain(&adapter->evt_nh, MTK_PD_CONNECT_HARD_RESET, NULL); // notify_adapter_event(MTK_PD_ADAPTER, // MTK_PD_CONNECT_HARD_RESET, NULL); break; case PD_CONNECT_PE_READY_SNK: pinfo->pd_type = MTK_PD_CONNECT_PE_READY_SNK; ret = srcu_notifier_call_chain(&adapter->evt_nh, MTK_PD_CONNECT_PE_READY_SNK, NULL); // notify_adapter_event(MTK_PD_ADAPTER, // MTK_PD_CONNECT_PE_READY_SNK, NULL); break; case PD_CONNECT_PE_READY_SNK_PD30: pinfo->pd_type = MTK_PD_CONNECT_PE_READY_SNK_PD30; ret = srcu_notifier_call_chain(&adapter->evt_nh, MTK_PD_CONNECT_PE_READY_SNK_PD30, NULL); // notify_adapter_event(MTK_PD_ADAPTER, // MTK_PD_CONNECT_PE_READY_SNK_PD30, NULL); break; case PD_CONNECT_PE_READY_SNK_APDO: pinfo->pd_type = MTK_PD_CONNECT_PE_READY_SNK_APDO; ret = srcu_notifier_call_chain(&adapter->evt_nh, MTK_PD_CONNECT_PE_READY_SNK_APDO, NULL); // notify_adapter_event(MTK_PD_ADAPTER, // MTK_PD_CONNECT_PE_READY_SNK_APDO, NULL); break; case PD_CONNECT_TYPEC_ONLY_SNK_DFT: /* fall-through */ case PD_CONNECT_TYPEC_ONLY_SNK: pinfo->pd_type = MTK_PD_CONNECT_TYPEC_ONLY_SNK; ret = srcu_notifier_call_chain(&adapter->evt_nh, MTK_PD_CONNECT_TYPEC_ONLY_SNK, NULL); // notify_adapter_event(MTK_PD_ADAPTER, // MTK_PD_CONNECT_PE_READY_SNK_APDO, NULL); break; }; break; case TCP_NOTIFY_TYPEC_STATE: /* handle No-rp and dual-rp cable */ if (noti->typec_state.old_state == TYPEC_UNATTACHED && (noti->typec_state.new_state == TYPEC_ATTACHED_CUSTOM_SRC || noti->typec_state.new_state == TYPEC_ATTACHED_NORP_SRC)) { pinfo->pd_type = MTK_PD_CONNECT_TYPEC_ONLY_SNK; ret = srcu_notifier_call_chain(&adapter->evt_nh, MTK_PD_CONNECT_TYPEC_ONLY_SNK, NULL); } else if ((noti->typec_state.old_state == TYPEC_ATTACHED_CUSTOM_SRC || noti->typec_state.old_state == TYPEC_ATTACHED_NORP_SRC) && noti->typec_state.new_state == TYPEC_UNATTACHED) { pinfo->pd_type = MTK_PD_CONNECT_NONE; ret = srcu_notifier_call_chain(&adapter->evt_nh, MTK_PD_CONNECT_NONE, NULL); } break; case TCP_NOTIFY_WD_STATUS: ret = srcu_notifier_call_chain(&adapter->evt_nh, MTK_TYPEC_WD_STATUS, ¬i->wd_status.water_detected); if (noti->wd_status.water_detected) { usb_dpdm_pulldown(adapter, false); if (pinfo->bootmode == 8) pd_adapter_high_voltage_enable(0); } else { usb_dpdm_pulldown(adapter, true); if (pinfo->bootmode == 8) pd_adapter_high_voltage_enable(1); } break; case TCP_NOTIFY_SINK_VBUS: sink_mv = noti->vbus_state.mv; sink_ma = noti->vbus_state.ma; pr_info("%s: sink vbus %dmV %dmA type(0x%02x)\n", __func__, sink_mv, sink_ma, noti->vbus_state.type); if (!pinfo->enable_pp) { if (sink_mv && sink_ma) { pinfo->enable_pp = true; pd_adapter_enable_power_path(true); } } else { if (!sink_mv || !sink_ma) { pinfo->enable_pp = false; pd_adapter_enable_power_path(false); } } break; } return ret; } static int pd_get_property(struct adapter_device *dev, enum adapter_property sta) { struct mtk_pd_adapter_info *info; info = (struct mtk_pd_adapter_info *)adapter_dev_get_drvdata(dev); if (info == NULL || info->tcpc == NULL) return -1; switch (sta) { case TYPEC_RP_LEVEL: { return tcpm_inquire_typec_remote_rp_curr(info->tcpc); } break; case PD_TYPE: { return info->pd_type; } break; default: { } break; } return -1; } static int pd_set_cap(struct adapter_device *dev, enum adapter_cap_type type, int mV, int mA) { int ret = MTK_ADAPTER_OK; int tcpm_ret = TCPM_SUCCESS; struct mtk_pd_adapter_info *info; pr_notice("[%s] type:%d mV:%d mA:%d\n", __func__, type, mV, mA); info = (struct mtk_pd_adapter_info *)adapter_dev_get_drvdata(dev); if (info == NULL || info->tcpc == NULL) { pr_notice("[%s] info null\n", __func__); return -1; } if (type == MTK_PD_APDO_START) { tcpm_ret = tcpm_set_apdo_charging_policy(info->tcpc, DPM_CHARGING_POLICY_PPS, mV, mA, NULL); } else if (type == MTK_PD_APDO_END) { // tcpm_ret = tcpm_set_pd_charging_policy(info->tcpc, // DPM_CHARGING_POLICY_VSAFE5V, NULL); tcpm_ret = tcpm_reset_pd_charging_policy(info->tcpc, NULL); } else if (type == MTK_PD_APDO) { tcpm_ret = tcpm_dpm_pd_request(info->tcpc, mV, mA, NULL); } else if (type == MTK_PD) { tcpm_ret = tcpm_dpm_pd_request(info->tcpc, mV, mA, NULL); } pr_notice("[%s] type:%d mV:%d mA:%d ret:%d\n", __func__, type, mV, mA, tcpm_ret); if (tcpm_ret == TCP_DPM_RET_REJECT) return MTK_ADAPTER_REJECT; else if (tcpm_ret != 0) return MTK_ADAPTER_ERROR; return ret; } int pd_get_output(struct adapter_device *dev, int *mV, int *mA) { int ret = MTK_ADAPTER_OK; int tcpm_ret = TCPM_SUCCESS; struct pd_pps_status pps_status; struct mtk_pd_adapter_info *info; info = (struct mtk_pd_adapter_info *)adapter_dev_get_drvdata(dev); if (info == NULL || info->tcpc == NULL) return MTK_ADAPTER_NOT_SUPPORT; tcpm_ret = tcpm_dpm_pd_get_pps_status(info->tcpc, NULL, &pps_status); if (tcpm_ret == TCP_DPM_RET_NOT_SUPPORT) return MTK_ADAPTER_NOT_SUPPORT; else if (tcpm_ret != 0) return MTK_ADAPTER_ERROR; *mV = pps_status.output_mv; *mA = pps_status.output_ma; return ret; } int pd_get_status(struct adapter_device *dev, struct adapter_status *sta) { struct pd_status TAstatus = {0,}; int ret = MTK_ADAPTER_OK; int tcpm_ret = TCPM_SUCCESS; struct mtk_pd_adapter_info *info; info = (struct mtk_pd_adapter_info *)adapter_dev_get_drvdata(dev); if (info == NULL || info->tcpc == NULL) return MTK_ADAPTER_ERROR; tcpm_ret = tcpm_dpm_pd_get_status(info->tcpc, NULL, &TAstatus); sta->temperature = TAstatus.internal_temp; sta->ocp = TAstatus.event_flags & PD_STASUS_EVENT_OCP; sta->otp = TAstatus.event_flags & PD_STATUS_EVENT_OTP; sta->ovp = TAstatus.event_flags & PD_STATUS_EVENT_OVP; if (tcpm_ret == TCP_DPM_RET_NOT_SUPPORT) return MTK_ADAPTER_NOT_SUPPORT; else if (tcpm_ret == TCP_DPM_RET_TIMEOUT) return MTK_ADAPTER_TIMEOUT; else if (tcpm_ret == TCP_DPM_RET_SUCCESS) return MTK_ADAPTER_OK; else return MTK_ADAPTER_ERROR; return ret; } static int pd_get_cap(struct adapter_device *dev, enum adapter_cap_type type, struct adapter_power_cap *tacap) { struct tcpm_power_cap_val apdo_cap = {}; struct tcpm_remote_power_cap pd_cap = {}; struct pd_source_cap_ext cap_ext = {}; uint8_t cap_i = 0; int ret; unsigned int idx = 0; unsigned int i, j; struct mtk_pd_adapter_info *info; memset(&pd_cap, 0, sizeof(pd_cap)); info = (struct mtk_pd_adapter_info *)adapter_dev_get_drvdata(dev); if (info == NULL || info->tcpc == NULL) return MTK_ADAPTER_ERROR; if (type == MTK_PD_APDO) { while (1) { ret = tcpm_inquire_pd_source_apdo(info->tcpc, TCPM_POWER_CAP_APDO_TYPE_PPS, &cap_i, &apdo_cap); if (ret == TCPM_ERROR_NOT_FOUND) { break; } else if (ret != TCPM_SUCCESS) { pr_notice("[%s] tcpm_inquire_pd_source_apdo failed(%d)\n", __func__, ret); break; } ret = tcpm_dpm_pd_get_source_cap_ext(info->tcpc, NULL, &cap_ext); if (ret == TCPM_SUCCESS) tacap->pdp = cap_ext.source_pdp; else { tacap->pdp = 0; pr_notice("[%s] tcpm_dpm_pd_get_source_cap_ext failed(%d)\n", __func__, ret); } tacap->pwr_limit[idx] = apdo_cap.pwr_limit; /* If TA has PDP, we set pwr_limit as true */ if (tacap->pdp > 0 && !tacap->pwr_limit[idx]) tacap->pwr_limit[idx] = 1; tacap->ma[idx] = apdo_cap.ma; tacap->max_mv[idx] = apdo_cap.max_mv; tacap->min_mv[idx] = apdo_cap.min_mv; tacap->maxwatt[idx] = apdo_cap.max_mv * apdo_cap.ma; tacap->minwatt[idx] = apdo_cap.min_mv * apdo_cap.ma; tacap->type[idx] = MTK_PD_APDO; idx++; pr_notice("pps_boundary[%d], %d mv ~ %d mv, %d ma pl:%d\n", cap_i, apdo_cap.min_mv, apdo_cap.max_mv, apdo_cap.ma, apdo_cap.pwr_limit); if (idx >= ADAPTER_CAP_MAX_NR) { pr_notice("CAP NR > %d\n", ADAPTER_CAP_MAX_NR); break; } } tacap->nr = idx; for (i = 0; i < tacap->nr; i++) { pr_notice("pps_cap[%d:%d], %d mv ~ %d mv, %d ma pl:%d pdp:%d\n", i, (int)tacap->nr, tacap->min_mv[i], tacap->max_mv[i], tacap->ma[i], tacap->pwr_limit[i], tacap->pdp); } if (cap_i == 0) pr_notice("no APDO for pps\n"); } else if (type == MTK_PD) { pd_cap.nr = 0; pd_cap.selected_cap_idx = 0; tcpm_get_remote_power_cap(info->tcpc, &pd_cap); if (pd_cap.nr != 0) { tacap->selected_cap_idx = pd_cap.selected_cap_idx - 1; pr_notice("[%s] nr:%d idx:%d\n", __func__, pd_cap.nr, pd_cap.selected_cap_idx - 1); j = 0; pr_notice("adapter cap: nr:%d\n", pd_cap.nr); for (i = 0; i < pd_cap.nr; i++) { if (!pd_cap.type[i] && j < ADAPTER_CAP_MAX_NR) { tacap->type[j] = MTK_PD; tacap->ma[j] = pd_cap.ma[i]; tacap->max_mv[j] = pd_cap.max_mv[i]; tacap->min_mv[j] = pd_cap.min_mv[i]; tacap->maxwatt[j] = tacap->max_mv[j] * tacap->ma[i]; tacap->minwatt[j] = tacap->min_mv[j] * tacap->ma[i]; j++; } pr_notice("[%s]:%d mv:[%d,%d] mA:%d type:%d %d\n", __func__, i, pd_cap.min_mv[i], pd_cap.max_mv[i], pd_cap.ma[i], pd_cap.type[i], pd_cap.type[i]); } tacap->nr = j; pr_notice("pd cap: nr:%d\n", tacap->nr); for (i = 0; i < tacap->nr; i++) { pr_notice("[%s]:%d mv:[%d,%d] mA:%d max:%d min:%d type:%d %d\n", __func__, i, tacap->min_mv[i], tacap->max_mv[i], tacap->ma[i], tacap->maxwatt[i], tacap->minwatt[i], tacap->type[i], tacap->type[i]); } } } return MTK_ADAPTER_OK; } #define PPS_STATUS_VTA_NOTSUPP (-1) #define PPS_STATUS_ITA_NOTSUPP (-1) static int __maybe_unused pd_authentication(struct adapter_device *dev, struct adapter_auth_data *data) { int ret = 0, ret_check = 0, apdo_idx = -1, i; struct mtk_pd_adapter_info *info = adapter_dev_get_drvdata(dev); struct tcpm_power_cap_val apdo_cap; struct tcpm_power_cap_val selected_apdo_cap; struct pd_source_cap_ext src_cap_ext; struct adapter_status status; u8 cap_idx; u32 vta_meas, ita_meas, prog_mv; int apdo_pps_cnt = ARRAY_SIZE(apdo_pps_tbl); if (check_typec_attached_snk(info->tcpc) < 0) return MTK_ADAPTER_ERROR; if (info->pd_type != MTK_PD_CONNECT_PE_READY_SNK_APDO) { pr_info("%s pd type is not snk apdo\n", __func__); return MTK_ADAPTER_ERROR; } if (!tcpm_inquire_pd_pe_ready(info->tcpc)) { pr_info("%s PD PE not ready\n", __func__); return MTK_ADAPTER_ERROR; } /* select TA boundary */ cap_idx = 0; while (1) { ret_check = (int)tcpm_inquire_pd_source_apdo(info->tcpc, TCPM_POWER_CAP_APDO_TYPE_PPS, &cap_idx, &apdo_cap); if (ret_check != (int)TCPM_SUCCESS) { if (apdo_idx == -1) pr_info("%s inquire pd apdo fail(%d)\n", __func__, ret_check); break; } pr_info("%s cap_idx[%d], %d mv ~ %d mv, %d ma\n", __func__, cap_idx, apdo_cap.min_mv, apdo_cap.max_mv, apdo_cap.ma); /* * !(apdo_cap.min_mv <= data->vcap_min && * apdo_cap.max_mv >= data->vcap_max && * apdo_cap.ma >= data->icap_min) */ if (apdo_cap.min_mv > data->vcap_min || apdo_cap.max_mv < data->vcap_max || apdo_cap.ma < data->icap_min) continue; if (apdo_idx == -1 || apdo_cap.ma > selected_apdo_cap.ma) { memcpy(&selected_apdo_cap, &apdo_cap, sizeof(struct tcpm_power_cap_val)); apdo_idx = cap_idx; pr_info("%s select potential cap_idx[%d]\n", __func__, cap_idx); } } if (apdo_idx != -1) { data->vta_min = selected_apdo_cap.min_mv; data->vta_max = selected_apdo_cap.max_mv; data->ita_max = selected_apdo_cap.ma; data->ita_min = info->ita_min; data->pwr_lmt = selected_apdo_cap.pwr_limit; data->support_cc = true; data->support_meas_cap = true; data->support_status = true; data->vta_step = 20; data->ita_step = 50; data->ita_gap_per_vstep = 200; ret_check = tcpm_dpm_pd_get_source_cap_ext(info->tcpc, NULL, &src_cap_ext); if (ret_check != (int)TCP_DPM_RET_SUCCESS) { pr_info("%s inquire pdp fail(%d)\n", __func__, ret); if (data->pwr_lmt) { for (i = 0; i < apdo_pps_cnt; i++) { if (apdo_pps_tbl[i].max_mv < data->vta_max) continue; prog_mv = min_t(u32, apdo_pps_tbl[i].prog_mv, (u32)data->vta_max); data->pdp = prog_mv * data->ita_max / 1000000; } } } else { data->pdp = src_cap_ext.source_pdp; if (data->pdp > 0 && !data->pwr_lmt) data->pwr_lmt = true; } /* Check whether TA supports getting pps status */ ret = pd_set_cap(dev, MTK_PD_APDO_START, 5000, 3000); if (ret != (int)MTK_ADAPTER_OK) goto out; ret = pd_get_output(dev, &vta_meas, &ita_meas); if (ret != (int)MTK_ADAPTER_OK && ret != (int)MTK_ADAPTER_NOT_SUPPORT) goto out; if (ret == (int)MTK_ADAPTER_NOT_SUPPORT || vta_meas == PPS_STATUS_VTA_NOTSUPP || ita_meas == PPS_STATUS_ITA_NOTSUPP) { data->support_cc = false; data->support_meas_cap = false; ret = (int)MTK_ADAPTER_OK; } ret = pd_get_status(dev, &status); if (ret == (int)MTK_ADAPTER_NOT_SUPPORT) { data->support_status = false; ret = (int)MTK_ADAPTER_OK; } else if (ret != (int)MTK_ADAPTER_OK) goto out; if (info->force_cv) data->support_cc = false; pr_info("%s select cap_idx[%d], power limit[%d,%dW]\n", __func__, apdo_idx, data->pwr_lmt, data->pdp); } else { pr_info("%s cannot find apdo for pps algo\n", __func__); return (int)MTK_ADAPTER_ERROR; } out: if (ret != (int)MTK_ADAPTER_OK) pr_info("%s fail(%d)\n", __func__, ret); return ret; } static int __maybe_unused pd_is_cc(struct adapter_device *dev, bool *cc) { struct mtk_pd_adapter_info *info = adapter_dev_get_drvdata(dev); int ret; struct pd_pps_status pps_status; ret = tcpm_dpm_pd_get_pps_status(info->tcpc, NULL, &pps_status); if (ret == TCP_DPM_RET_SUCCESS) *cc = !!(pps_status.real_time_flags & PD_PPS_FLAGS_CFF); else pr_info("%s fail(%d)\n", __func__, ret); return to_mtk_adapter_ret(ret); } int pd_set_wdt(struct adapter_device *dev, u32 wdt) { return MTK_ADAPTER_OK; } int pd_enable_wdt(struct adapter_device *dev, bool en) { return MTK_ADAPTER_OK; } int pd_send_hardreset(struct adapter_device *dev) { int ret; struct mtk_pd_adapter_info *info = adapter_dev_get_drvdata(dev); if (check_typec_attached_snk(info->tcpc) < 0) return MTK_ADAPTER_ERROR; pr_info("++\n"); ret = tcpm_dpm_pd_hard_reset(info->tcpc, NULL); if (ret != TCP_DPM_RET_SUCCESS) pr_info("fail(%d)\n", ret); return to_mtk_adapter_ret(ret); } static struct adapter_ops adapter_ops = { .get_status = pd_get_status, .set_cap = pd_set_cap, .get_output = pd_get_output, .get_property = pd_get_property, .get_cap = pd_get_cap, .authentication = pd_authentication, .is_cc = pd_is_cc, .set_wdt = pd_set_wdt, .enable_wdt = pd_enable_wdt, .send_hardreset = pd_send_hardreset, }; static int adapter_parse_dt(struct mtk_pd_adapter_info *info, struct device *dev) { struct device_node *np = dev->of_node; struct device_node *boot_np = NULL; const struct { u32 size; u32 tag; u32 boot_mode; u32 boot_type; } *tag; if (of_property_read_string(np, "adapter_name", &info->adapter_dev_name) < 0) pr_notice("%s: no adapter name\n", __func__); info->force_cv = of_property_read_bool(np, "force_cv"); of_property_read_u32(np, "ita_min", &info->ita_min); pr_notice("%s\n", __func__); if (!np) { pr_notice("%s: no device node\n", __func__); return -EINVAL; } /* mediatek boot mode */ boot_np = of_parse_phandle(np, "boot_mode", 0); if (!boot_np) pr_info("%s: failed to get bootmode phandle\n", __func__); tag = of_get_property(boot_np, "atag,boot", NULL); if (!tag) { pr_info("%s: failed to get atag,boot\n", __func__); info->bootmode = 8; info->boottype = 2; pr_notice("%s: set bootmode=8, boottype=2\n", __func__); } else { info->bootmode = tag->boot_mode; info->boottype = tag->boot_type; pr_notice("%s: sz:0x%x tag:0x%x mode:0x%x type:0x%x\n", __func__, tag->size, tag->tag, tag->boot_mode, tag->boot_type); } return 0; } static int mtk_pd_adapter_probe(struct platform_device *pdev) { int ret = 0; struct mtk_pd_adapter_info *info = NULL; static bool is_deferred; pr_notice("%s\n", __func__); info = devm_kzalloc(&pdev->dev, sizeof(struct mtk_pd_adapter_info), GFP_KERNEL); if (!info) return -ENOMEM; adapter_parse_dt(info, &pdev->dev); info->adapter_dev = adapter_device_register(info->adapter_dev_name, &pdev->dev, info, &adapter_ops, NULL); if (IS_ERR_OR_NULL(info->adapter_dev)) { ret = PTR_ERR(info->adapter_dev); goto err_register_adapter_dev; } adapter_dev_set_drvdata(info->adapter_dev, info); info->tcpc = tcpc_dev_get_by_name("type_c_port0"); if (info->tcpc == NULL) { if (is_deferred == false) { pr_info("%s: tcpc device not ready, defer\n", __func__); is_deferred = true; ret = -EPROBE_DEFER; } else { pr_info("%s: failed to get tcpc device\n", __func__); ret = -EINVAL; } goto err_get_tcpc_dev; } info->pd_nb.notifier_call = pd_tcp_notifier_call; ret = register_tcp_dev_notifier(info->tcpc, &info->pd_nb, TCP_NOTIFY_TYPE_USB | TCP_NOTIFY_TYPE_MISC | TCP_NOTIFY_TYPE_VBUS); if (ret < 0) { pr_info("%s: register tcpc notifer fail\n", __func__); ret = -EINVAL; goto err_get_tcpc_dev; } return 0; err_get_tcpc_dev: adapter_device_unregister(info->adapter_dev); err_register_adapter_dev: devm_kfree(&pdev->dev, info); return ret; } static int mtk_pd_adapter_remove(struct platform_device *dev) { return 0; } static void mtk_pd_adapter_shutdown(struct platform_device *dev) { } static const struct of_device_id mtk_pd_adapter_of_match[] = { {.compatible = "mediatek,pd_adapter",}, {}, }; MODULE_DEVICE_TABLE(of, mtk_pd_adapter_of_match); static struct platform_driver mtk_pd_adapter_driver = { .probe = mtk_pd_adapter_probe, .remove = mtk_pd_adapter_remove, .shutdown = mtk_pd_adapter_shutdown, .driver = { .name = "pd_adapter", .of_match_table = mtk_pd_adapter_of_match, }, }; static int __init mtk_pd_adapter_init(void) { return platform_driver_register(&mtk_pd_adapter_driver); } module_init(mtk_pd_adapter_init); static void __exit mtk_pd_adapter_exit(void) { platform_driver_unregister(&mtk_pd_adapter_driver); } module_exit(mtk_pd_adapter_exit); MODULE_AUTHOR("wy.chuang "); MODULE_DESCRIPTION("MTK PD Adapter Driver"); MODULE_LICENSE("GPL");