7559e7572c
The DT of_device.h and of_platform.h date back to the separate of_platform_bus_type before it as merged into the regular platform bus. As part of that merge prepping Arm DT support 13 years ago, they "temporarily" include each other. They also include platform_device.h and of.h. As a result, there's a pretty much random mix of those include files used throughout the tree. In order to detangle these headers and replace the implicit includes with struct declarations, users need to explicitly include the correct includes. Signed-off-by: Rob Herring <robh@kernel.org> Acked-by: Marc Kleine-Budde <mkl@pengutronix.de> # for drivers/phy/phy-can-transceiver.c Acked-by: Heiko Stuebner <heiko@sntech.de> Acked-by: Sergio Paracuellos <sergio.paracuellos@gmail.com> Link: https://lore.kernel.org/r/20230714174841.4061919-1-robh@kernel.org Signed-off-by: Vinod Koul <vkoul@kernel.org>
448 lines
11 KiB
C
448 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Amlogic G12A USB3 + PCIE Combo PHY driver
|
|
*
|
|
* Copyright (C) 2017 Amlogic, Inc. All rights reserved
|
|
* Copyright (C) 2019 BayLibre, SAS
|
|
* Author: Neil Armstrong <narmstrong@baylibre.com>
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/platform_device.h>
|
|
#include <dt-bindings/phy/phy.h>
|
|
|
|
#define PHY_R0 0x00
|
|
#define PHY_R0_PCIE_POWER_STATE GENMASK(4, 0)
|
|
#define PHY_R0_PCIE_USB3_SWITCH GENMASK(6, 5)
|
|
|
|
#define PHY_R1 0x04
|
|
#define PHY_R1_PHY_TX1_TERM_OFFSET GENMASK(4, 0)
|
|
#define PHY_R1_PHY_TX0_TERM_OFFSET GENMASK(9, 5)
|
|
#define PHY_R1_PHY_RX1_EQ GENMASK(12, 10)
|
|
#define PHY_R1_PHY_RX0_EQ GENMASK(15, 13)
|
|
#define PHY_R1_PHY_LOS_LEVEL GENMASK(20, 16)
|
|
#define PHY_R1_PHY_LOS_BIAS GENMASK(23, 21)
|
|
#define PHY_R1_PHY_REF_CLKDIV2 BIT(24)
|
|
#define PHY_R1_PHY_MPLL_MULTIPLIER GENMASK(31, 25)
|
|
|
|
#define PHY_R2 0x08
|
|
#define PHY_R2_PCS_TX_DEEMPH_GEN2_6DB GENMASK(5, 0)
|
|
#define PHY_R2_PCS_TX_DEEMPH_GEN2_3P5DB GENMASK(11, 6)
|
|
#define PHY_R2_PCS_TX_DEEMPH_GEN1 GENMASK(17, 12)
|
|
#define PHY_R2_PHY_TX_VBOOST_LVL GENMASK(20, 18)
|
|
|
|
#define PHY_R4 0x10
|
|
#define PHY_R4_PHY_CR_WRITE BIT(0)
|
|
#define PHY_R4_PHY_CR_READ BIT(1)
|
|
#define PHY_R4_PHY_CR_DATA_IN GENMASK(17, 2)
|
|
#define PHY_R4_PHY_CR_CAP_DATA BIT(18)
|
|
#define PHY_R4_PHY_CR_CAP_ADDR BIT(19)
|
|
|
|
#define PHY_R5 0x14
|
|
#define PHY_R5_PHY_CR_DATA_OUT GENMASK(15, 0)
|
|
#define PHY_R5_PHY_CR_ACK BIT(16)
|
|
#define PHY_R5_PHY_BS_OUT BIT(17)
|
|
|
|
#define PCIE_RESET_DELAY 500
|
|
|
|
struct phy_g12a_usb3_pcie_priv {
|
|
struct regmap *regmap;
|
|
struct regmap *regmap_cr;
|
|
struct clk *clk_ref;
|
|
struct reset_control *reset;
|
|
struct phy *phy;
|
|
unsigned int mode;
|
|
};
|
|
|
|
static const struct regmap_config phy_g12a_usb3_pcie_regmap_conf = {
|
|
.reg_bits = 8,
|
|
.val_bits = 32,
|
|
.reg_stride = 4,
|
|
.max_register = PHY_R5,
|
|
};
|
|
|
|
static int phy_g12a_usb3_pcie_cr_bus_addr(struct phy_g12a_usb3_pcie_priv *priv,
|
|
unsigned int addr)
|
|
{
|
|
unsigned int val, reg;
|
|
int ret;
|
|
|
|
reg = FIELD_PREP(PHY_R4_PHY_CR_DATA_IN, addr);
|
|
|
|
regmap_write(priv->regmap, PHY_R4, reg);
|
|
regmap_write(priv->regmap, PHY_R4, reg);
|
|
|
|
regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_CAP_ADDR);
|
|
|
|
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
|
|
(val & PHY_R5_PHY_CR_ACK),
|
|
5, 1000);
|
|
if (ret)
|
|
return ret;
|
|
|
|
regmap_write(priv->regmap, PHY_R4, reg);
|
|
|
|
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
|
|
!(val & PHY_R5_PHY_CR_ACK),
|
|
5, 1000);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int phy_g12a_usb3_pcie_cr_bus_read(void *context, unsigned int addr,
|
|
unsigned int *data)
|
|
{
|
|
struct phy_g12a_usb3_pcie_priv *priv = context;
|
|
unsigned int val;
|
|
int ret;
|
|
|
|
ret = phy_g12a_usb3_pcie_cr_bus_addr(priv, addr);
|
|
if (ret)
|
|
return ret;
|
|
|
|
regmap_write(priv->regmap, PHY_R4, 0);
|
|
regmap_write(priv->regmap, PHY_R4, PHY_R4_PHY_CR_READ);
|
|
|
|
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
|
|
(val & PHY_R5_PHY_CR_ACK),
|
|
5, 1000);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*data = FIELD_GET(PHY_R5_PHY_CR_DATA_OUT, val);
|
|
|
|
regmap_write(priv->regmap, PHY_R4, 0);
|
|
|
|
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
|
|
!(val & PHY_R5_PHY_CR_ACK),
|
|
5, 1000);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int phy_g12a_usb3_pcie_cr_bus_write(void *context, unsigned int addr,
|
|
unsigned int data)
|
|
{
|
|
struct phy_g12a_usb3_pcie_priv *priv = context;
|
|
unsigned int val, reg;
|
|
int ret;
|
|
|
|
ret = phy_g12a_usb3_pcie_cr_bus_addr(priv, addr);
|
|
if (ret)
|
|
return ret;
|
|
|
|
reg = FIELD_PREP(PHY_R4_PHY_CR_DATA_IN, data);
|
|
|
|
regmap_write(priv->regmap, PHY_R4, reg);
|
|
regmap_write(priv->regmap, PHY_R4, reg);
|
|
|
|
regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_CAP_DATA);
|
|
|
|
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
|
|
(val & PHY_R5_PHY_CR_ACK),
|
|
5, 1000);
|
|
if (ret)
|
|
return ret;
|
|
|
|
regmap_write(priv->regmap, PHY_R4, reg);
|
|
|
|
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
|
|
(val & PHY_R5_PHY_CR_ACK) == 0,
|
|
5, 1000);
|
|
if (ret)
|
|
return ret;
|
|
|
|
regmap_write(priv->regmap, PHY_R4, reg);
|
|
|
|
regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_WRITE);
|
|
|
|
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
|
|
(val & PHY_R5_PHY_CR_ACK),
|
|
5, 1000);
|
|
if (ret)
|
|
return ret;
|
|
|
|
regmap_write(priv->regmap, PHY_R4, reg);
|
|
|
|
ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
|
|
(val & PHY_R5_PHY_CR_ACK) == 0,
|
|
5, 1000);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct regmap_config phy_g12a_usb3_pcie_cr_regmap_conf = {
|
|
.reg_bits = 16,
|
|
.val_bits = 16,
|
|
.reg_read = phy_g12a_usb3_pcie_cr_bus_read,
|
|
.reg_write = phy_g12a_usb3_pcie_cr_bus_write,
|
|
.max_register = 0xffff,
|
|
.disable_locking = true,
|
|
};
|
|
|
|
static int phy_g12a_usb3_init(struct phy *phy)
|
|
{
|
|
struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
|
|
int data, ret;
|
|
|
|
ret = reset_control_reset(priv->reset);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Switch PHY to USB3 */
|
|
/* TODO figure out how to handle when PCIe was set in the bootloader */
|
|
regmap_update_bits(priv->regmap, PHY_R0,
|
|
PHY_R0_PCIE_USB3_SWITCH,
|
|
PHY_R0_PCIE_USB3_SWITCH);
|
|
|
|
/*
|
|
* WORKAROUND: There is SSPHY suspend bug due to
|
|
* which USB enumerates
|
|
* in HS mode instead of SS mode. Workaround it by asserting
|
|
* LANE0.TX_ALT_BLOCK.EN_ALT_BUS to enable TX to use alt bus
|
|
* mode
|
|
*/
|
|
ret = regmap_update_bits(priv->regmap_cr, 0x102d, BIT(7), BIT(7));
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = regmap_update_bits(priv->regmap_cr, 0x1010, 0xff0, 20);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Fix RX Equalization setting as follows
|
|
* LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0
|
|
* LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1
|
|
* LANE0.RX_OVRD_IN_HI.RX_EQ set to 3
|
|
* LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1
|
|
*/
|
|
ret = regmap_read(priv->regmap_cr, 0x1006, &data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
data &= ~BIT(6);
|
|
data |= BIT(7);
|
|
data &= ~(0x7 << 8);
|
|
data |= (0x3 << 8);
|
|
data |= (1 << 11);
|
|
ret = regmap_write(priv->regmap_cr, 0x1006, data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Set EQ and TX launch amplitudes as follows
|
|
* LANE0.TX_OVRD_DRV_LO.PREEMPH set to 22
|
|
* LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 127
|
|
* LANE0.TX_OVRD_DRV_LO.EN set to 1.
|
|
*/
|
|
ret = regmap_read(priv->regmap_cr, 0x1002, &data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
data &= ~0x3f80;
|
|
data |= (0x16 << 7);
|
|
data &= ~0x7f;
|
|
data |= (0x7f | BIT(14));
|
|
ret = regmap_write(priv->regmap_cr, 0x1002, data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* MPLL_LOOP_CTL.PROP_CNTRL = 8 */
|
|
ret = regmap_update_bits(priv->regmap_cr, 0x30, 0xf << 4, 8 << 4);
|
|
if (ret)
|
|
return ret;
|
|
|
|
regmap_update_bits(priv->regmap, PHY_R2,
|
|
PHY_R2_PHY_TX_VBOOST_LVL,
|
|
FIELD_PREP(PHY_R2_PHY_TX_VBOOST_LVL, 0x4));
|
|
|
|
regmap_update_bits(priv->regmap, PHY_R1,
|
|
PHY_R1_PHY_LOS_BIAS | PHY_R1_PHY_LOS_LEVEL,
|
|
FIELD_PREP(PHY_R1_PHY_LOS_BIAS, 4) |
|
|
FIELD_PREP(PHY_R1_PHY_LOS_LEVEL, 9));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int phy_g12a_usb3_pcie_power_on(struct phy *phy)
|
|
{
|
|
struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
|
|
|
|
if (priv->mode == PHY_TYPE_USB3)
|
|
return 0;
|
|
|
|
regmap_update_bits(priv->regmap, PHY_R0,
|
|
PHY_R0_PCIE_POWER_STATE,
|
|
FIELD_PREP(PHY_R0_PCIE_POWER_STATE, 0x1c));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int phy_g12a_usb3_pcie_power_off(struct phy *phy)
|
|
{
|
|
struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
|
|
|
|
if (priv->mode == PHY_TYPE_USB3)
|
|
return 0;
|
|
|
|
regmap_update_bits(priv->regmap, PHY_R0,
|
|
PHY_R0_PCIE_POWER_STATE,
|
|
FIELD_PREP(PHY_R0_PCIE_POWER_STATE, 0x1d));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int phy_g12a_usb3_pcie_reset(struct phy *phy)
|
|
{
|
|
struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
|
|
int ret;
|
|
|
|
if (priv->mode == PHY_TYPE_USB3)
|
|
return 0;
|
|
|
|
ret = reset_control_assert(priv->reset);
|
|
if (ret)
|
|
return ret;
|
|
|
|
udelay(PCIE_RESET_DELAY);
|
|
|
|
ret = reset_control_deassert(priv->reset);
|
|
if (ret)
|
|
return ret;
|
|
|
|
udelay(PCIE_RESET_DELAY);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int phy_g12a_usb3_pcie_init(struct phy *phy)
|
|
{
|
|
struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
|
|
|
|
if (priv->mode == PHY_TYPE_USB3)
|
|
return phy_g12a_usb3_init(phy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int phy_g12a_usb3_pcie_exit(struct phy *phy)
|
|
{
|
|
struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
|
|
|
|
if (priv->mode == PHY_TYPE_USB3)
|
|
return reset_control_reset(priv->reset);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct phy *phy_g12a_usb3_pcie_xlate(struct device *dev,
|
|
struct of_phandle_args *args)
|
|
{
|
|
struct phy_g12a_usb3_pcie_priv *priv = dev_get_drvdata(dev);
|
|
unsigned int mode;
|
|
|
|
if (args->args_count < 1) {
|
|
dev_err(dev, "invalid number of arguments\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
mode = args->args[0];
|
|
|
|
if (mode != PHY_TYPE_USB3 && mode != PHY_TYPE_PCIE) {
|
|
dev_err(dev, "invalid phy mode select argument\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
priv->mode = mode;
|
|
|
|
return priv->phy;
|
|
}
|
|
|
|
static const struct phy_ops phy_g12a_usb3_pcie_ops = {
|
|
.init = phy_g12a_usb3_pcie_init,
|
|
.exit = phy_g12a_usb3_pcie_exit,
|
|
.power_on = phy_g12a_usb3_pcie_power_on,
|
|
.power_off = phy_g12a_usb3_pcie_power_off,
|
|
.reset = phy_g12a_usb3_pcie_reset,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static int phy_g12a_usb3_pcie_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct phy_g12a_usb3_pcie_priv *priv;
|
|
struct phy_provider *phy_provider;
|
|
void __iomem *base;
|
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(base))
|
|
return PTR_ERR(base);
|
|
|
|
priv->regmap = devm_regmap_init_mmio(dev, base,
|
|
&phy_g12a_usb3_pcie_regmap_conf);
|
|
if (IS_ERR(priv->regmap))
|
|
return PTR_ERR(priv->regmap);
|
|
|
|
priv->regmap_cr = devm_regmap_init(dev, NULL, priv,
|
|
&phy_g12a_usb3_pcie_cr_regmap_conf);
|
|
if (IS_ERR(priv->regmap_cr))
|
|
return PTR_ERR(priv->regmap_cr);
|
|
|
|
priv->clk_ref = devm_clk_get_enabled(dev, "ref_clk");
|
|
if (IS_ERR(priv->clk_ref))
|
|
return PTR_ERR(priv->clk_ref);
|
|
|
|
priv->reset = devm_reset_control_array_get_exclusive(dev);
|
|
if (IS_ERR(priv->reset))
|
|
return PTR_ERR(priv->reset);
|
|
|
|
priv->phy = devm_phy_create(dev, np, &phy_g12a_usb3_pcie_ops);
|
|
if (IS_ERR(priv->phy))
|
|
return dev_err_probe(dev, PTR_ERR(priv->phy), "failed to create PHY\n");
|
|
|
|
phy_set_drvdata(priv->phy, priv);
|
|
dev_set_drvdata(dev, priv);
|
|
|
|
phy_provider = devm_of_phy_provider_register(dev,
|
|
phy_g12a_usb3_pcie_xlate);
|
|
return PTR_ERR_OR_ZERO(phy_provider);
|
|
}
|
|
|
|
static const struct of_device_id phy_g12a_usb3_pcie_of_match[] = {
|
|
{ .compatible = "amlogic,g12a-usb3-pcie-phy", },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, phy_g12a_usb3_pcie_of_match);
|
|
|
|
static struct platform_driver phy_g12a_usb3_pcie_driver = {
|
|
.probe = phy_g12a_usb3_pcie_probe,
|
|
.driver = {
|
|
.name = "phy-g12a-usb3-pcie",
|
|
.of_match_table = phy_g12a_usb3_pcie_of_match,
|
|
},
|
|
};
|
|
module_platform_driver(phy_g12a_usb3_pcie_driver);
|
|
|
|
MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
|
|
MODULE_DESCRIPTION("Amlogic G12A USB3 + PCIE Combo PHY driver");
|
|
MODULE_LICENSE("GPL v2");
|