6db4831e98
Android 14
487 lines
9.8 KiB
C
487 lines
9.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2020 MediaTek Inc.
|
|
*/
|
|
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/device.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/phy/phy.h>
|
|
|
|
#include <linux/phy/mediatek/mtk_usb_phy.h>
|
|
#include <phy-mtk.h>
|
|
|
|
#define DRIVER_NAME "mtk_phy"
|
|
|
|
u32 phy_debug_level = K_ERR | K_ALET;
|
|
|
|
module_param(phy_debug_level, int, 0644);
|
|
MODULE_PARM_DESC(phy_debug_level, "Debug Print Log for mtkphy");
|
|
|
|
int usb_mtkphy_switch_to_usb(struct phy *phy)
|
|
{
|
|
int ret = 0;
|
|
struct mtk_phy_instance *instance;
|
|
const struct mtk_phy_interface *phycfg;
|
|
|
|
if (!phy)
|
|
return -EINVAL;
|
|
|
|
instance = phy_get_drvdata(phy);
|
|
phycfg = instance->phycfg;
|
|
|
|
if (phycfg && phycfg->usb_phy_switch_to_usb)
|
|
phycfg->usb_phy_switch_to_usb(instance);
|
|
else
|
|
ret = -ENOTSUPP;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_mtkphy_switch_to_usb);
|
|
|
|
int usb_mtkphy_switch_to_uart(struct phy *phy)
|
|
{
|
|
int ret = 0;
|
|
struct mtk_phy_instance *instance;
|
|
const struct mtk_phy_interface *phycfg;
|
|
|
|
if (!phy)
|
|
return -EINVAL;
|
|
|
|
instance = phy_get_drvdata(phy);
|
|
phycfg = instance->phycfg;
|
|
|
|
if (phycfg && phycfg->usb_phy_switch_to_uart)
|
|
phycfg->usb_phy_switch_to_uart(instance);
|
|
else
|
|
ret = -ENOTSUPP;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_mtkphy_switch_to_uart);
|
|
|
|
int usb_mtkphy_check_in_uart_mode(struct phy *phy)
|
|
{
|
|
int ret = 0;
|
|
struct mtk_phy_instance *instance;
|
|
const struct mtk_phy_interface *phycfg;
|
|
|
|
if (!phy)
|
|
return -EINVAL;
|
|
|
|
instance = phy_get_drvdata(phy);
|
|
phycfg = instance->phycfg;
|
|
|
|
if (phycfg && phycfg->usb_phy_check_in_uart_mode)
|
|
ret = phycfg->usb_phy_check_in_uart_mode(instance);
|
|
else
|
|
ret = -ENOTSUPP;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_mtkphy_check_in_uart_mode);
|
|
|
|
int usb_mtkphy_dump_usb2uart_reg(struct phy *phy)
|
|
{
|
|
int ret = 0;
|
|
struct mtk_phy_instance *instance;
|
|
const struct mtk_phy_interface *phycfg;
|
|
|
|
if (!phy)
|
|
return -EINVAL;
|
|
|
|
instance = phy_get_drvdata(phy);
|
|
phycfg = instance->phycfg;
|
|
|
|
if (phycfg && phycfg->usb_phy_dump_usb2uart_reg)
|
|
phycfg->usb_phy_dump_usb2uart_reg(instance);
|
|
else
|
|
ret = -ENOTSUPP;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_mtkphy_dump_usb2uart_reg);
|
|
|
|
int usb_mtkphy_sib_enable_switch(struct phy *phy, bool enable)
|
|
{
|
|
int ret = 0;
|
|
struct mtk_phy_instance *instance;
|
|
const struct mtk_phy_interface *phycfg;
|
|
|
|
if (!phy)
|
|
return -EINVAL;
|
|
|
|
instance = phy_get_drvdata(phy);
|
|
phycfg = instance->phycfg;
|
|
|
|
if (phycfg && phycfg->usb_phy_sib_enable_switch)
|
|
phycfg->usb_phy_sib_enable_switch(instance, enable);
|
|
else
|
|
ret = -ENOTSUPP;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_mtkphy_sib_enable_switch);
|
|
|
|
int usb_mtkphy_sib_enable_switch_status(struct phy *phy)
|
|
{
|
|
int ret = 0;
|
|
struct mtk_phy_instance *instance;
|
|
const struct mtk_phy_interface *phycfg;
|
|
|
|
if (!phy)
|
|
return -EINVAL;
|
|
|
|
instance = phy_get_drvdata(phy);
|
|
phycfg = instance->phycfg;
|
|
|
|
if (phycfg && phycfg->usb_phy_sib_switch_status)
|
|
phycfg->usb_phy_sib_switch_status(instance);
|
|
else
|
|
ret = -ENOTSUPP;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_mtkphy_sib_enable_switch_status);
|
|
|
|
|
|
int usb_mtkphy_u3_loop_back_test(struct phy *phy)
|
|
{
|
|
int ret;
|
|
struct mtk_phy_instance *instance;
|
|
const struct mtk_phy_interface *phycfg;
|
|
|
|
if (!phy)
|
|
return -EINVAL;
|
|
|
|
instance = phy_get_drvdata(phy);
|
|
phycfg = instance->phycfg;
|
|
|
|
if (phycfg && phycfg->usb_phy_u3_loop_back_test)
|
|
ret = phycfg->usb_phy_u3_loop_back_test(instance);
|
|
else
|
|
ret = -ENOTSUPP;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_mtkphy_u3_loop_back_test);
|
|
|
|
int usb_mtkphy_slew_rate_calibration(struct phy *phy)
|
|
{
|
|
int ret = 0;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_mtkphy_slew_rate_calibration);
|
|
|
|
int usb_mtkphy_switch_to_bc11(struct phy *phy, bool on)
|
|
{
|
|
int ret = 0;
|
|
struct mtk_phy_instance *instance;
|
|
const struct mtk_phy_interface *phycfg;
|
|
|
|
if (!phy)
|
|
return -EINVAL;
|
|
|
|
instance = phy_get_drvdata(phy);
|
|
phycfg = instance->phycfg;
|
|
|
|
if (phycfg && phycfg->usb_phy_switch_to_bc11)
|
|
phycfg->usb_phy_switch_to_bc11(instance, on);
|
|
else
|
|
ret = -ENOTSUPP;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_mtkphy_switch_to_bc11);
|
|
|
|
int usb_mtkphy_dpdm_pulldown(struct phy *phy, bool enable)
|
|
{
|
|
int ret = 0;
|
|
struct mtk_phy_instance *instance;
|
|
const struct mtk_phy_interface *phycfg;
|
|
|
|
if (!phy)
|
|
return -EINVAL;
|
|
|
|
instance = phy_get_drvdata(phy);
|
|
phycfg = instance->phycfg;
|
|
|
|
if (phycfg && phycfg->usb_phy_dpdm_pulldown)
|
|
phycfg->usb_phy_dpdm_pulldown(instance, enable);
|
|
else
|
|
ret = -ENOTSUPP;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_mtkphy_dpdm_pulldown);
|
|
|
|
int usb_mtkphy_lpm_enable(struct phy *phy, bool on)
|
|
{
|
|
int ret = 0;
|
|
struct mtk_phy_instance *instance;
|
|
const struct mtk_phy_interface *phycfg;
|
|
|
|
if (!phy)
|
|
return -EINVAL;
|
|
|
|
instance = phy_get_drvdata(phy);
|
|
phycfg = instance->phycfg;
|
|
|
|
if (phycfg && phycfg->usb_phy_lpm_enable)
|
|
ret = phycfg->usb_phy_lpm_enable(instance, on);
|
|
else
|
|
ret = -ENOTSUPP;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_mtkphy_lpm_enable);
|
|
|
|
int usb_mtkphy_host_mode(struct phy *phy, bool on)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!phy)
|
|
ret = -EINVAL;
|
|
|
|
if (phy) {
|
|
struct mtk_phy_instance *instance = phy_get_drvdata(phy);
|
|
const struct mtk_phy_interface *phycfg;
|
|
|
|
phycfg = instance->phycfg;
|
|
|
|
if (phycfg && phycfg->usb_phy_host_mode)
|
|
ret = phycfg->usb_phy_host_mode(instance, on);
|
|
else
|
|
ret = -ENOTSUPP;
|
|
} else
|
|
ret = -EINVAL;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_mtkphy_host_mode);
|
|
|
|
int usb_mtkphy_io_read(struct phy *phy, u32 reg)
|
|
{
|
|
int ret = 0;
|
|
struct mtk_phy_instance *instance;
|
|
const struct mtk_phy_interface *phycfg;
|
|
|
|
if (!phy)
|
|
return -EINVAL;
|
|
|
|
instance = phy_get_drvdata(phy);
|
|
phycfg = instance->phycfg;
|
|
|
|
if (phycfg && phycfg->usb_phy_io_read)
|
|
ret = phycfg->usb_phy_io_read(instance, reg);
|
|
else
|
|
ret = -ENOTSUPP;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_mtkphy_io_read);
|
|
|
|
int usb_mtkphy_io_write(struct phy *phy, u32 val, u32 reg)
|
|
{
|
|
int ret = 0;
|
|
struct mtk_phy_instance *instance;
|
|
const struct mtk_phy_interface *phycfg;
|
|
|
|
if (!phy)
|
|
return -EINVAL;
|
|
|
|
instance = phy_get_drvdata(phy);
|
|
phycfg = instance->phycfg;
|
|
|
|
if (phycfg && phycfg->usb_phy_io_write)
|
|
phycfg->usb_phy_io_write(instance, val, reg);
|
|
else
|
|
ret = -ENOTSUPP;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(usb_mtkphy_io_write);
|
|
|
|
|
|
static int mtk_phy_init(struct phy *phy)
|
|
{
|
|
struct mtk_phy_instance *instance = phy_get_drvdata(phy);
|
|
|
|
instance->phycfg->usb_phy_init(instance);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int mtk_phy_power_on(struct phy *phy)
|
|
{
|
|
struct mtk_phy_instance *instance = phy_get_drvdata(phy);
|
|
|
|
instance->phycfg->usb_phy_recover(instance);
|
|
return 0;
|
|
}
|
|
|
|
static int mtk_phy_power_off(struct phy *phy)
|
|
{
|
|
struct mtk_phy_instance *instance = phy_get_drvdata(phy);
|
|
|
|
instance->phycfg->usb_phy_savecurrent(instance);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static struct phy_ops mtk_u3phy_ops = {
|
|
.init = mtk_phy_init,
|
|
.exit = NULL,
|
|
.power_on = mtk_phy_power_on,
|
|
.power_off = mtk_phy_power_off,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static struct phy *mtk_phy_xlate(struct device *dev,
|
|
struct of_phandle_args *args)
|
|
{
|
|
struct mtk_phy_drv *drv;
|
|
|
|
drv = dev_get_drvdata(dev);
|
|
if (!drv)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
if (WARN_ON(args->args[0] >= drv->phycfg->num_phys))
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
return drv->phys[args->args[0]]->phy;
|
|
}
|
|
|
|
static int mtk_usb_phy_probe(struct platform_device *pdev)
|
|
{
|
|
int retval, i;
|
|
struct device *dev = &pdev->dev;
|
|
const struct of_device_id *match;
|
|
const struct mtk_usbphy_config *mtkcfg;
|
|
struct device_node *np = pdev->dev.of_node;
|
|
struct mtk_phy_drv *mtkphy;
|
|
struct phy_provider *provider;
|
|
|
|
if (!pdev->dev.of_node) {
|
|
dev_info(dev, "This driver is required to be instantiated from device tree\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
match = of_match_node(mtk_phy_of_match, pdev->dev.of_node);
|
|
if (!match) {
|
|
dev_info(dev, "of_match_node() failed\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mtkcfg = match->data;
|
|
mtkphy = devm_kzalloc(dev, sizeof(*mtkphy), GFP_KERNEL);
|
|
if (!mtkphy)
|
|
return -ENOMEM;
|
|
|
|
mtkphy->nphys = mtkcfg->num_phys;
|
|
mtkphy->phys = devm_kcalloc(dev, mtkphy->nphys,
|
|
sizeof(*mtkphy->phys), GFP_KERNEL);
|
|
if (!mtkphy->phys) {
|
|
devm_kfree(dev, mtkphy);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
mtkphy->phycfg = mtkcfg;
|
|
mtkphy->dev = dev;
|
|
|
|
platform_set_drvdata(pdev, mtkphy);
|
|
|
|
/* init phy driver */
|
|
retval = mtkcfg->usb_drv_init(pdev, mtkphy);
|
|
if (retval)
|
|
return retval;
|
|
|
|
pm_runtime_enable(dev);
|
|
retval = pm_runtime_get_sync(dev);
|
|
if (retval < 0)
|
|
goto err1;
|
|
pm_runtime_forbid(dev);
|
|
|
|
for (i = 0; i < mtkphy->phycfg->num_phys; i++) {
|
|
struct mtk_phy_instance *instance = mtkphy->phys[i];
|
|
char *name = mtkphy->phycfg->phys[i].name;
|
|
struct phy *phy;
|
|
|
|
instance = devm_kzalloc(dev, sizeof(*instance), GFP_KERNEL);
|
|
if (!instance) {
|
|
retval = -ENOMEM;
|
|
goto err2;
|
|
}
|
|
|
|
instance->port_base = mtkphy->phy_base
|
|
+ mtkphy->phycfg->phys[i].reg_offset;
|
|
|
|
phy = devm_phy_create(dev, np, &mtk_u3phy_ops);
|
|
if (IS_ERR(phy)) {
|
|
dev_info(dev, "Failed to create usb2_phy \"%s\"\n",
|
|
name);
|
|
retval = PTR_ERR(phy);
|
|
goto err2;
|
|
}
|
|
instance->phy = phy;
|
|
instance->phycfg = &mtkphy->phycfg->phys[i];
|
|
instance->phy_drv = mtkphy;
|
|
instance->phycfg->usb_phy_inst_init(instance);
|
|
mtkphy->phys[i] = instance;
|
|
phy_set_drvdata(instance->phy, instance);
|
|
}
|
|
|
|
provider = devm_of_phy_provider_register(dev,
|
|
mtk_phy_xlate);
|
|
|
|
return PTR_ERR_OR_ZERO(provider);
|
|
|
|
err2:
|
|
pm_runtime_allow(dev);
|
|
err1:
|
|
pm_runtime_put_sync(dev);
|
|
pm_runtime_disable(dev);
|
|
mtkcfg->usb_drv_exit(pdev, mtkphy);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int mtk_usb_phy_remove(struct platform_device *pdev)
|
|
{
|
|
/* pm_runtime_put_sync(&pdev->dev);*/
|
|
/* pm_runtime_disable(&pdev->dev);*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static struct platform_driver mtk_usb_phy_driver = {
|
|
.probe = mtk_usb_phy_probe,
|
|
.remove = mtk_usb_phy_remove,
|
|
.driver = {
|
|
.name = DRIVER_NAME,
|
|
.of_match_table = mtk_phy_of_match,
|
|
},
|
|
};
|
|
|
|
|
|
static int __init usb_phy_mtk_init(void)
|
|
{
|
|
return platform_driver_register(&mtk_usb_phy_driver);
|
|
}
|
|
module_init(usb_phy_mtk_init);
|
|
|
|
static void __exit usb_phy_mtk_exit(void)
|
|
{
|
|
platform_driver_unregister(&mtk_usb_phy_driver);
|
|
}
|
|
module_exit(usb_phy_mtk_exit);
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("Mediatek USB transceiver driver");
|