root/drivers/phy/phy-eyeq5-eth.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");