kernel-brax3-ubuntu-touch/drivers/power/supply/mtk_pd_adapter.c
erascape f319b992b1 kernel-5.15: Initial import brax3 UT kernel
* halium configs enabled

Signed-off-by: erascape <erascape@proton.me>
2025-09-23 15:17:10 +00:00

865 lines
23 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 MediaTek Inc.
* Author Wy Chuang<wy.chuang@mediatek.com>
*/
#include <linux/init.h> /* For init/exit macros */
#include <linux/module.h> /* For MODULE_ marcros */
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/power_supply.h>
#include <linux/pm_wakeup.h>
#include <linux/phy/phy.h>
#include <linux/time.h>
#include <linux/mutex.h>
#include <linux/kthread.h>
#include <linux/proc_fs.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/scatterlist.h>
#include <linux/suspend.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/reboot.h>
/* PD */
#include <tcpm.h>
#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, &noti->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 <wy.chuang@mediatek.com>");
MODULE_DESCRIPTION("MTK PD Adapter Driver");
MODULE_LICENSE("GPL");