linux/drivers/phy/allwinner/phy-sun50i-usb3.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Allwinner sun50i(H6) USB 3.0 phy driver
   4 *
   5 * Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io>
   6 *
   7 * Based on phy-sun9i-usb.c, which is:
   8 *
   9 * Copyright (C) 2014-2015 Chen-Yu Tsai <wens@csie.org>
  10 *
  11 * Based on code from Allwinner BSP, which is:
  12 *
  13 * Copyright (c) 2010-2015 Allwinner Technology Co., Ltd.
  14 */
  15
  16#include <linux/clk.h>
  17#include <linux/err.h>
  18#include <linux/io.h>
  19#include <linux/module.h>
  20#include <linux/phy/phy.h>
  21#include <linux/platform_device.h>
  22#include <linux/reset.h>
  23
  24/* Interface Status and Control Registers */
  25#define SUNXI_ISCR                      0x00
  26#define SUNXI_PIPE_CLOCK_CONTROL        0x14
  27#define SUNXI_PHY_TUNE_LOW              0x18
  28#define SUNXI_PHY_TUNE_HIGH             0x1c
  29#define SUNXI_PHY_EXTERNAL_CONTROL      0x20
  30
  31/* USB2.0 Interface Status and Control Register */
  32#define SUNXI_ISCR_FORCE_VBUS           (3 << 12)
  33
  34/* PIPE Clock Control Register */
  35#define SUNXI_PCC_PIPE_CLK_OPEN         (1 << 6)
  36
  37/* PHY External Control Register */
  38#define SUNXI_PEC_EXTERN_VBUS           (3 << 1)
  39#define SUNXI_PEC_SSC_EN                (1 << 24)
  40#define SUNXI_PEC_REF_SSP_EN            (1 << 26)
  41
  42/* PHY Tune High Register */
  43#define SUNXI_TX_DEEMPH_3P5DB(n)        ((n) << 19)
  44#define SUNXI_TX_DEEMPH_3P5DB_MASK      GENMASK(24, 19)
  45#define SUNXI_TX_DEEMPH_6DB(n)          ((n) << 13)
  46#define SUNXI_TX_DEEMPH_6GB_MASK        GENMASK(18, 13)
  47#define SUNXI_TX_SWING_FULL(n)          ((n) << 6)
  48#define SUNXI_TX_SWING_FULL_MASK        GENMASK(12, 6)
  49#define SUNXI_LOS_BIAS(n)               ((n) << 3)
  50#define SUNXI_LOS_BIAS_MASK             GENMASK(5, 3)
  51#define SUNXI_TXVBOOSTLVL(n)            ((n) << 0)
  52#define SUNXI_TXVBOOSTLVL_MASK          GENMASK(2, 0)
  53
  54struct sun50i_usb3_phy {
  55        struct phy *phy;
  56        void __iomem *regs;
  57        struct reset_control *reset;
  58        struct clk *clk;
  59};
  60
  61static void sun50i_usb3_phy_open(struct sun50i_usb3_phy *phy)
  62{
  63        u32 val;
  64
  65        val = readl(phy->regs + SUNXI_PHY_EXTERNAL_CONTROL);
  66        val |= SUNXI_PEC_EXTERN_VBUS;
  67        val |= SUNXI_PEC_SSC_EN | SUNXI_PEC_REF_SSP_EN;
  68        writel(val, phy->regs + SUNXI_PHY_EXTERNAL_CONTROL);
  69
  70        val = readl(phy->regs + SUNXI_PIPE_CLOCK_CONTROL);
  71        val |= SUNXI_PCC_PIPE_CLK_OPEN;
  72        writel(val, phy->regs + SUNXI_PIPE_CLOCK_CONTROL);
  73
  74        val = readl(phy->regs + SUNXI_ISCR);
  75        val |= SUNXI_ISCR_FORCE_VBUS;
  76        writel(val, phy->regs + SUNXI_ISCR);
  77
  78        /*
  79         * All the magic numbers written to the PHY_TUNE_{LOW_HIGH}
  80         * registers are directly taken from the BSP USB3 driver from
  81         * Allwiner.
  82         */
  83        writel(0x0047fc87, phy->regs + SUNXI_PHY_TUNE_LOW);
  84
  85        val = readl(phy->regs + SUNXI_PHY_TUNE_HIGH);
  86        val &= ~(SUNXI_TXVBOOSTLVL_MASK | SUNXI_LOS_BIAS_MASK |
  87                 SUNXI_TX_SWING_FULL_MASK | SUNXI_TX_DEEMPH_6GB_MASK |
  88                 SUNXI_TX_DEEMPH_3P5DB_MASK);
  89        val |= SUNXI_TXVBOOSTLVL(0x7);
  90        val |= SUNXI_LOS_BIAS(0x7);
  91        val |= SUNXI_TX_SWING_FULL(0x55);
  92        val |= SUNXI_TX_DEEMPH_6DB(0x20);
  93        val |= SUNXI_TX_DEEMPH_3P5DB(0x15);
  94        writel(val, phy->regs + SUNXI_PHY_TUNE_HIGH);
  95}
  96
  97static int sun50i_usb3_phy_init(struct phy *_phy)
  98{
  99        struct sun50i_usb3_phy *phy = phy_get_drvdata(_phy);
 100        int ret;
 101
 102        ret = clk_prepare_enable(phy->clk);
 103        if (ret)
 104                return ret;
 105
 106        ret = reset_control_deassert(phy->reset);
 107        if (ret) {
 108                clk_disable_unprepare(phy->clk);
 109                return ret;
 110        }
 111
 112        sun50i_usb3_phy_open(phy);
 113        return 0;
 114}
 115
 116static int sun50i_usb3_phy_exit(struct phy *_phy)
 117{
 118        struct sun50i_usb3_phy *phy = phy_get_drvdata(_phy);
 119
 120        reset_control_assert(phy->reset);
 121        clk_disable_unprepare(phy->clk);
 122
 123        return 0;
 124}
 125
 126static const struct phy_ops sun50i_usb3_phy_ops = {
 127        .init           = sun50i_usb3_phy_init,
 128        .exit           = sun50i_usb3_phy_exit,
 129        .owner          = THIS_MODULE,
 130};
 131
 132static int sun50i_usb3_phy_probe(struct platform_device *pdev)
 133{
 134        struct sun50i_usb3_phy *phy;
 135        struct device *dev = &pdev->dev;
 136        struct phy_provider *phy_provider;
 137
 138        phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
 139        if (!phy)
 140                return -ENOMEM;
 141
 142        phy->clk = devm_clk_get(dev, NULL);
 143        if (IS_ERR(phy->clk)) {
 144                if (PTR_ERR(phy->clk) != -EPROBE_DEFER)
 145                        dev_err(dev, "failed to get phy clock\n");
 146                return PTR_ERR(phy->clk);
 147        }
 148
 149        phy->reset = devm_reset_control_get(dev, NULL);
 150        if (IS_ERR(phy->reset)) {
 151                dev_err(dev, "failed to get reset control\n");
 152                return PTR_ERR(phy->reset);
 153        }
 154
 155        phy->regs = devm_platform_ioremap_resource(pdev, 0);
 156        if (IS_ERR(phy->regs))
 157                return PTR_ERR(phy->regs);
 158
 159        phy->phy = devm_phy_create(dev, NULL, &sun50i_usb3_phy_ops);
 160        if (IS_ERR(phy->phy)) {
 161                dev_err(dev, "failed to create PHY\n");
 162                return PTR_ERR(phy->phy);
 163        }
 164
 165        phy_set_drvdata(phy->phy, phy);
 166        phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
 167
 168        return PTR_ERR_OR_ZERO(phy_provider);
 169}
 170
 171static const struct of_device_id sun50i_usb3_phy_of_match[] = {
 172        { .compatible = "allwinner,sun50i-h6-usb3-phy" },
 173        { },
 174};
 175MODULE_DEVICE_TABLE(of, sun50i_usb3_phy_of_match);
 176
 177static struct platform_driver sun50i_usb3_phy_driver = {
 178        .probe  = sun50i_usb3_phy_probe,
 179        .driver = {
 180                .of_match_table = sun50i_usb3_phy_of_match,
 181                .name  = "sun50i-usb3-phy",
 182        }
 183};
 184module_platform_driver(sun50i_usb3_phy_driver);
 185
 186MODULE_DESCRIPTION("Allwinner H6 USB 3.0 phy driver");
 187MODULE_AUTHOR("Icenowy Zheng <icenowy@aosc.io>");
 188MODULE_LICENSE("GPL");
 189