mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 02:17:36 -04:00
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>
280 lines
6.6 KiB
C
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");
|