Files
linux/drivers/phy/phy-eyeq5-eth.c
Uwe Kleine-König (The Capable Hub) 995832b2ce Replace <linux/mod_devicetable.h> by more specific <linux/device-id/*.h> (c files)
Replace the #include of <linux/mod_devicetable.h> by the more specific
<linux/device-id/*.h> where applicable. For most cases the include
can be dropped completely, only a few drivers need one or two headers
added.

Acked-by: Danilo Krummrich <dakr@kernel.org>
Acked-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Acked-by: Bjorn Helgaas <bhelgaas@google.com>
Link: https://patch.msgid.link/1a3f2007c5c5dcf555c09a4035ce3ae8ef1b6c49.1782808461.git.u.kleine-koenig@baylibre.com
Signed-off-by: Uwe Kleine-König (The Capable Hub) <u.kleine-koenig@baylibre.com>
2026-07-03 07:38:17 +02:00

280 lines
6.6 KiB
C

// 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/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");