// SPDX-License-Identifier: GPL-2.0+
/*
* Allwinner sun50i ( H6 ) USB 3 . 0 phy driver
*
* Copyright ( C ) 2017 Icenowy Zheng < icenowy @ aosc . io >
*
* Based on phy - sun9i - usb . c , which is :
*
* Copyright ( C ) 2014 - 2015 Chen - Yu Tsai < wens @ csie . org >
*
* Based on code from Allwinner BSP , which is :
*
* Copyright ( c ) 2010 - 2015 Allwinner Technology Co . , Ltd .
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
/* Interface Status and Control Registers */
#define SUNXI_ISCR 0 x00
#define SUNXI_PIPE_CLOCK_CONTROL 0 x14
#define SUNXI_PHY_TUNE_LOW 0 x18
#define SUNXI_PHY_TUNE_HIGH 0 x1c
#define SUNXI_PHY_EXTERNAL_CONTROL 0 x20
/* USB2.0 Interface Status and Control Register */
#define SUNXI_ISCR_FORCE_VBUS (3 << 12 )
/* PIPE Clock Control Register */
#define SUNXI_PCC_PIPE_CLK_OPEN (1 << 6 )
/* PHY External Control Register */
#define SUNXI_PEC_EXTERN_VBUS (3 << 1 )
#define SUNXI_PEC_SSC_EN (1 << 24 )
#define SUNXI_PEC_REF_SSP_EN (1 << 26 )
/* PHY Tune High Register */
#define SUNXI_TX_DEEMPH_3P5DB(n) ((n) << 19 )
#define SUNXI_TX_DEEMPH_3P5DB_MASK GENMASK(24 , 19 )
#define SUNXI_TX_DEEMPH_6DB(n) ((n) << 13 )
#define SUNXI_TX_DEEMPH_6GB_MASK GENMASK(18 , 13 )
#define SUNXI_TX_SWING_FULL(n) ((n) << 6 )
#define SUNXI_TX_SWING_FULL_MASK GENMASK(12 , 6 )
#define SUNXI_LOS_BIAS(n) ((n) << 3 )
#define SUNXI_LOS_BIAS_MASK GENMASK(5 , 3 )
#define SUNXI_TXVBOOSTLVL(n) ((n) << 0 )
#define SUNXI_TXVBOOSTLVL_MASK GENMASK(2 , 0 )
struct sun50i_usb3_phy {
struct phy *phy;
void __iomem *regs;
struct reset_control *reset;
struct clk *clk;
};
static void sun50i_usb3_phy_open(struct sun50i_usb3_phy *phy)
{
u32 val;
val = readl(phy->regs + SUNXI_PHY_EXTERNAL_CONTROL);
val |= SUNXI_PEC_EXTERN_VBUS;
val |= SUNXI_PEC_SSC_EN | SUNXI_PEC_REF_SSP_EN;
writel(val, phy->regs + SUNXI_PHY_EXTERNAL_CONTROL);
val = readl(phy->regs + SUNXI_PIPE_CLOCK_CONTROL);
val |= SUNXI_PCC_PIPE_CLK_OPEN;
writel(val, phy->regs + SUNXI_PIPE_CLOCK_CONTROL);
val = readl(phy->regs + SUNXI_ISCR);
val |= SUNXI_ISCR_FORCE_VBUS;
writel(val, phy->regs + SUNXI_ISCR);
/*
* All the magic numbers written to the PHY_TUNE_ { LOW_HIGH }
* registers are directly taken from the BSP USB3 driver from
* Allwiner .
*/
writel(0 x0047fc87, phy->regs + SUNXI_PHY_TUNE_LOW);
val = readl(phy->regs + SUNXI_PHY_TUNE_HIGH);
val &= ~(SUNXI_TXVBOOSTLVL_MASK | SUNXI_LOS_BIAS_MASK |
SUNXI_TX_SWING_FULL_MASK | SUNXI_TX_DEEMPH_6GB_MASK |
SUNXI_TX_DEEMPH_3P5DB_MASK);
val |= SUNXI_TXVBOOSTLVL(0 x7);
val |= SUNXI_LOS_BIAS(0 x7);
val |= SUNXI_TX_SWING_FULL(0 x55);
val |= SUNXI_TX_DEEMPH_6DB(0 x20);
val |= SUNXI_TX_DEEMPH_3P5DB(0 x15);
writel(val, phy->regs + SUNXI_PHY_TUNE_HIGH);
}
static int sun50i_usb3_phy_init(struct phy *_phy)
{
struct sun50i_usb3_phy *phy = phy_get_drvdata(_phy);
int ret;
ret = clk_prepare_enable(phy->clk);
if (ret)
return ret;
ret = reset_control_deassert(phy->reset);
if (ret) {
clk_disable_unprepare(phy->clk);
return ret;
}
sun50i_usb3_phy_open(phy);
return 0 ;
}
static int sun50i_usb3_phy_exit(struct phy *_phy)
{
struct sun50i_usb3_phy *phy = phy_get_drvdata(_phy);
reset_control_assert(phy->reset);
clk_disable_unprepare(phy->clk);
return 0 ;
}
static const struct phy_ops sun50i_usb3_phy_ops = {
.init = sun50i_usb3_phy_init,
.exit = sun50i_usb3_phy_exit,
.owner = THIS_MODULE,
};
static int sun50i_usb3_phy_probe(struct platform_device *pdev)
{
struct sun50i_usb3_phy *phy;
struct device *dev = &pdev->dev;
struct phy_provider *phy_provider;
phy = devm_kzalloc(dev, sizeof (*phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
phy->clk = devm_clk_get(dev, NULL);
if (IS_ERR(phy->clk)) {
if (PTR_ERR(phy->clk) != -EPROBE_DEFER)
dev_err(dev, "failed to get phy clock\n" );
return PTR_ERR(phy->clk);
}
phy->reset = devm_reset_control_get(dev, NULL);
if (IS_ERR(phy->reset)) {
dev_err(dev, "failed to get reset control\n" );
return PTR_ERR(phy->reset);
}
phy->regs = devm_platform_ioremap_resource(pdev, 0 );
if (IS_ERR(phy->regs))
return PTR_ERR(phy->regs);
phy->phy = devm_phy_create(dev, NULL, &sun50i_usb3_phy_ops);
if (IS_ERR(phy->phy)) {
dev_err(dev, "failed to create PHY\n" );
return PTR_ERR(phy->phy);
}
phy_set_drvdata(phy->phy, phy);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static const struct of_device_id sun50i_usb3_phy_of_match[] = {
{ .compatible = "allwinner,sun50i-h6-usb3-phy" },
{ },
};
MODULE_DEVICE_TABLE(of, sun50i_usb3_phy_of_match);
static struct platform_driver sun50i_usb3_phy_driver = {
.probe = sun50i_usb3_phy_probe,
.driver = {
.of_match_table = sun50i_usb3_phy_of_match,
.name = "sun50i-usb3-phy" ,
}
};
module_platform_driver(sun50i_usb3_phy_driver);
MODULE_DESCRIPTION("Allwinner H6 USB 3.0 phy driver" );
MODULE_AUTHOR("Icenowy Zheng <icenowy@aosc.io>" );
MODULE_LICENSE("GPL" );
Messung V0.5 in Prozent C=91 H=100 G=95
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland