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