6db4831e98
Android 14
530 lines
14 KiB
C
530 lines
14 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/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"
|
|
|
|
|
|
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;
|
|
};
|
|
|
|
//void notify_adapter_event(enum adapter_type type, enum adapter_event evt,
|
|
// void *val);
|
|
|
|
|
|
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;
|
|
|
|
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);
|
|
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);
|
|
} 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;
|
|
|
|
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] == 0 &&
|
|
j >= 0 &&
|
|
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;
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
static int adapter_parse_dt(struct mtk_pd_adapter_info *info,
|
|
struct device *dev)
|
|
{
|
|
struct device_node *np = dev->of_node;
|
|
|
|
pr_notice("%s\n", __func__);
|
|
|
|
if (!np) {
|
|
pr_notice("%s: no device node\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_property_read_string(np, "adapter_name",
|
|
&info->adapter_dev_name) < 0)
|
|
pr_notice("%s: no adapter name\n", __func__);
|
|
|
|
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);
|
|
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");
|
|
|