mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 02:17:36 -04:00
phy: Add driver for EyeQ5 Ethernet PHY wrapper
EyeQ5 embeds a system-controller called OLB. It features many unrelated registers, and some of those are registers used to configure the integration of the RGMII/SGMII Cadence PHY used by MACB/GEM instances. Wrap in a neat generic PHY provider, exposing two PHYs with standard phy_init() / phy_set_mode() / phy_power_on() operations. Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com> Signed-off-by: Théo Lebrun <theo.lebrun@bootlin.com> Reviewed-by: Vladimir Oltean <olteanv@gmail.com> Link: https://patch.msgid.link/20260309-macb-phy-v9-1-5afd87d9db43@bootlin.com Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
@@ -17958,6 +17958,7 @@ F: arch/mips/boot/dts/mobileye/
|
||||
F: arch/mips/configs/eyeq*_defconfig
|
||||
F: arch/mips/mobileye/board-epm5.its.S
|
||||
F: drivers/clk/clk-eyeq.c
|
||||
F: drivers/phy/phy-eyeq5-eth.c
|
||||
F: drivers/pinctrl/pinctrl-eyeq5.c
|
||||
F: drivers/reset/reset-eyeq.c
|
||||
F: include/dt-bindings/clock/mobileye,eyeq5-clk.h
|
||||
|
||||
@@ -66,6 +66,19 @@ config PHY_CAN_TRANSCEIVER
|
||||
functional modes using gpios and sets the attribute max link
|
||||
rate, for CAN drivers.
|
||||
|
||||
config PHY_EYEQ5_ETH
|
||||
tristate "Ethernet PHY Driver on EyeQ5"
|
||||
depends on OF
|
||||
depends on MACH_EYEQ5 || COMPILE_TEST
|
||||
select AUXILIARY_BUS
|
||||
select GENERIC_PHY
|
||||
default MACH_EYEQ5
|
||||
help
|
||||
Enable this to support the Ethernet PHY integrated on EyeQ5.
|
||||
It supports both RGMII and SGMII. Registers are located in a
|
||||
shared register region called OLB. If M is selected, the
|
||||
module will be called phy-eyeq5-eth.
|
||||
|
||||
config PHY_GOOGLE_USB
|
||||
tristate "Google Tensor SoC USB PHY driver"
|
||||
select GENERIC_PHY
|
||||
|
||||
@@ -9,6 +9,7 @@ obj-$(CONFIG_GENERIC_PHY) += phy-core.o
|
||||
obj-$(CONFIG_GENERIC_PHY_MIPI_DPHY) += phy-core-mipi-dphy.o
|
||||
obj-$(CONFIG_PHY_AIROHA_PCIE) += phy-airoha-pcie.o
|
||||
obj-$(CONFIG_PHY_CAN_TRANSCEIVER) += phy-can-transceiver.o
|
||||
obj-$(CONFIG_PHY_EYEQ5_ETH) += phy-eyeq5-eth.o
|
||||
obj-$(CONFIG_PHY_GOOGLE_USB) += phy-google-usb.o
|
||||
obj-$(CONFIG_USB_LGM_PHY) += phy-lgm-usb.o
|
||||
obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o
|
||||
|
||||
280
drivers/phy/phy-eyeq5-eth.c
Normal file
280
drivers/phy/phy-eyeq5-eth.c
Normal file
@@ -0,0 +1,280 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/gfp_types.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define EQ5_PHY_COUNT 2
|
||||
|
||||
#define EQ5_PHY0_GP 0x128
|
||||
#define EQ5_PHY1_GP 0x12c
|
||||
#define EQ5_PHY0_SGMII 0x134
|
||||
#define EQ5_PHY1_SGMII 0x138
|
||||
|
||||
#define EQ5_GP_TX_SWRST_DIS BIT(0) // Tx SW reset
|
||||
#define EQ5_GP_TX_M_CLKE BIT(1) // Tx M clock enable
|
||||
#define EQ5_GP_SYS_SWRST_DIS BIT(2) // Sys SW reset
|
||||
#define EQ5_GP_SYS_M_CLKE BIT(3) // Sys clock enable
|
||||
#define EQ5_GP_SGMII_MODE BIT(4) // SGMII mode
|
||||
#define EQ5_GP_RGMII_DRV GENMASK(8, 5) // RGMII drive strength
|
||||
|
||||
#define EQ5_SGMII_PWR_EN BIT(0)
|
||||
#define EQ5_SGMII_RST_DIS BIT(1)
|
||||
#define EQ5_SGMII_PLL_EN BIT(2)
|
||||
#define EQ5_SGMII_SIG_DET_SW BIT(3)
|
||||
#define EQ5_SGMII_PWR_STATE BIT(4)
|
||||
#define EQ5_SGMII_PLL_ACK BIT(18)
|
||||
#define EQ5_SGMII_PWR_STATE_ACK GENMASK(24, 20)
|
||||
|
||||
/*
|
||||
* Instead of storing a phy_interface_t, we store this enum.
|
||||
*
|
||||
* We do not deal with RGMII timings in this generic PHY driver,
|
||||
* it is all handled inside the net PHY.
|
||||
*/
|
||||
enum eq5_phy_submode {
|
||||
EQ5_PHY_SUBMODE_SGMII,
|
||||
EQ5_PHY_SUBMODE_RGMII,
|
||||
};
|
||||
|
||||
struct eq5_phy_inst {
|
||||
struct device *dev;
|
||||
struct phy *phy;
|
||||
void __iomem *gp, *sgmii;
|
||||
enum eq5_phy_submode submode;
|
||||
bool sgmii_support;
|
||||
};
|
||||
|
||||
struct eq5_phy_private {
|
||||
struct eq5_phy_inst phys[EQ5_PHY_COUNT];
|
||||
};
|
||||
|
||||
static int eq5_phy_exit(struct phy *phy)
|
||||
{
|
||||
struct eq5_phy_inst *inst = phy_get_drvdata(phy);
|
||||
|
||||
writel(0, inst->gp);
|
||||
writel(0, inst->sgmii);
|
||||
udelay(5); /* settling time */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eq5_phy_init(struct phy *phy)
|
||||
{
|
||||
struct eq5_phy_inst *inst = phy_get_drvdata(phy);
|
||||
u32 reg;
|
||||
|
||||
/*
|
||||
* Hardware stops listening to our instructions once it is started.
|
||||
* It must be reset to reconfigure it.
|
||||
*/
|
||||
eq5_phy_exit(phy);
|
||||
|
||||
reg = EQ5_GP_TX_SWRST_DIS | EQ5_GP_TX_M_CLKE |
|
||||
EQ5_GP_SYS_SWRST_DIS | EQ5_GP_SYS_M_CLKE |
|
||||
FIELD_PREP(EQ5_GP_RGMII_DRV, 0x9);
|
||||
writel(reg, inst->gp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eq5_phy_power_on(struct phy *phy)
|
||||
{
|
||||
struct eq5_phy_inst *inst = phy_get_drvdata(phy);
|
||||
u32 reg;
|
||||
|
||||
if (inst->submode == EQ5_PHY_SUBMODE_SGMII) {
|
||||
writel(readl(inst->gp) | EQ5_GP_SGMII_MODE, inst->gp);
|
||||
|
||||
reg = EQ5_SGMII_PWR_EN | EQ5_SGMII_RST_DIS | EQ5_SGMII_PLL_EN;
|
||||
writel(reg, inst->sgmii);
|
||||
|
||||
if (readl_poll_timeout(inst->sgmii, reg,
|
||||
reg & EQ5_SGMII_PLL_ACK, 1, 100)) {
|
||||
dev_err(inst->dev, "PLL timeout\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
reg = readl(inst->sgmii);
|
||||
reg |= EQ5_SGMII_PWR_STATE | EQ5_SGMII_SIG_DET_SW;
|
||||
writel(reg, inst->sgmii);
|
||||
} else {
|
||||
writel(readl(inst->gp) & ~EQ5_GP_SGMII_MODE, inst->gp);
|
||||
writel(0, inst->sgmii);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eq5_phy_power_off(struct phy *phy)
|
||||
{
|
||||
struct eq5_phy_inst *inst = phy_get_drvdata(phy);
|
||||
|
||||
writel(readl(inst->gp) & ~EQ5_GP_SGMII_MODE, inst->gp);
|
||||
writel(0, inst->sgmii);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eq5_phy_validate(struct phy *phy, enum phy_mode mode, int submode,
|
||||
union phy_configure_opts *opts)
|
||||
{
|
||||
struct eq5_phy_inst *inst = phy_get_drvdata(phy);
|
||||
|
||||
if (mode != PHY_MODE_ETHERNET)
|
||||
return -EINVAL;
|
||||
|
||||
if (phy_interface_mode_is_rgmii(submode))
|
||||
return 0;
|
||||
|
||||
if (inst->sgmii_support && submode == PHY_INTERFACE_MODE_SGMII)
|
||||
return 0;
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int eq5_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
|
||||
{
|
||||
struct eq5_phy_inst *inst = phy_get_drvdata(phy);
|
||||
enum eq5_phy_submode target_submode;
|
||||
int ret;
|
||||
|
||||
ret = eq5_phy_validate(phy, mode, submode, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (submode == PHY_INTERFACE_MODE_SGMII)
|
||||
target_submode = EQ5_PHY_SUBMODE_SGMII;
|
||||
else
|
||||
target_submode = EQ5_PHY_SUBMODE_RGMII;
|
||||
|
||||
if (target_submode == inst->submode)
|
||||
return 0;
|
||||
|
||||
inst->submode = target_submode;
|
||||
|
||||
if (phy->power_count) {
|
||||
eq5_phy_init(phy);
|
||||
return eq5_phy_power_on(phy);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct phy_ops eq5_phy_ops = {
|
||||
.init = eq5_phy_init,
|
||||
.exit = eq5_phy_exit,
|
||||
.power_on = eq5_phy_power_on,
|
||||
.power_off = eq5_phy_power_off,
|
||||
.set_mode = eq5_phy_set_mode,
|
||||
.validate = eq5_phy_validate,
|
||||
};
|
||||
|
||||
static struct phy *eq5_phy_xlate(struct device *dev,
|
||||
const struct of_phandle_args *args)
|
||||
{
|
||||
struct eq5_phy_private *priv = dev_get_drvdata(dev);
|
||||
|
||||
if (args->args_count != 1 || args->args[0] >= EQ5_PHY_COUNT)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return priv->phys[args->args[0]].phy;
|
||||
}
|
||||
|
||||
static int eq5_phy_probe_phy(struct device *dev, struct eq5_phy_private *priv,
|
||||
unsigned int index, void __iomem *base,
|
||||
unsigned int gp, unsigned int sgmii,
|
||||
bool sgmii_support)
|
||||
{
|
||||
struct eq5_phy_inst *inst = &priv->phys[index];
|
||||
struct phy *phy;
|
||||
|
||||
phy = devm_phy_create(dev, dev->of_node, &eq5_phy_ops);
|
||||
if (IS_ERR(phy))
|
||||
return dev_err_probe(dev, PTR_ERR(phy),
|
||||
"failed to create PHY %u\n", index);
|
||||
|
||||
inst->dev = dev;
|
||||
inst->phy = phy;
|
||||
inst->gp = base + gp;
|
||||
inst->sgmii = base + sgmii;
|
||||
inst->sgmii_support = sgmii_support;
|
||||
phy_set_drvdata(phy, inst);
|
||||
|
||||
/*
|
||||
* Init inst->submode based on probe hardware state, allowing
|
||||
* consumers to power us on without first setting the mode.
|
||||
*/
|
||||
if (sgmii_support && (readl(inst->gp) & EQ5_GP_SGMII_MODE))
|
||||
inst->submode = EQ5_PHY_SUBMODE_SGMII;
|
||||
else
|
||||
inst->submode = EQ5_PHY_SUBMODE_RGMII;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eq5_phy_probe(struct auxiliary_device *adev,
|
||||
const struct auxiliary_device_id *id)
|
||||
{
|
||||
struct device *dev = &adev->dev;
|
||||
struct phy_provider *provider;
|
||||
struct eq5_phy_private *priv;
|
||||
void __iomem *base;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(dev, priv);
|
||||
|
||||
base = (void __iomem *)dev_get_platdata(dev);
|
||||
|
||||
ret = eq5_phy_probe_phy(dev, priv, 0, base, EQ5_PHY0_GP,
|
||||
EQ5_PHY0_SGMII, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = eq5_phy_probe_phy(dev, priv, 1, base, EQ5_PHY1_GP,
|
||||
EQ5_PHY1_SGMII, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
provider = devm_of_phy_provider_register(dev, eq5_phy_xlate);
|
||||
if (IS_ERR(provider))
|
||||
return dev_err_probe(dev, PTR_ERR(provider),
|
||||
"registering provider failed\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct auxiliary_device_id eq5_phy_id_table[] = {
|
||||
{ .name = "clk_eyeq.phy" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(auxiliary, eq5_phy_id_table);
|
||||
|
||||
static struct auxiliary_driver eq5_phy_driver = {
|
||||
.probe = eq5_phy_probe,
|
||||
.id_table = eq5_phy_id_table,
|
||||
};
|
||||
module_auxiliary_driver(eq5_phy_driver);
|
||||
|
||||
MODULE_DESCRIPTION("EyeQ5 Ethernet PHY driver");
|
||||
MODULE_AUTHOR("Théo Lebrun <theo.lebrun@bootlin.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
Reference in New Issue
Block a user