Message ID | 20220408162814.227120-1-nate.d@variscite.com |
---|---|
State | Accepted |
Commit | d79f1a85697e3af24a97728f6e4f16635bdc8290 |
Delegated to: | Ramon Fried |
Headers | show |
Series | phy: adin: add driver for Analog Devices ADIN1300 PHY | expand |
On Fri, Apr 8, 2022 at 8:20 PM Nate Drude <nate.d@variscite.com> wrote: > > The current implementation configures RGMII using device tree phy-mode > property and then calls genphy_config > > adin_config_rgmii_mode is derived from: > https://github.com/varigit/linux-imx/blob/lf-5.10.y_var04/drivers/net/phy/adin.c#L218-L262 > > Signed-off-by: Nate Drude <nate.d@variscite.com> > --- > doc/device-tree-bindings/net/phy/adin.txt | 26 +++ > drivers/net/phy/Kconfig | 5 + > drivers/net/phy/Makefile | 1 + > drivers/net/phy/adin.c | 228 ++++++++++++++++++++++ > drivers/net/phy/phy.c | 3 + > include/phy.h | 1 + > 6 files changed, 264 insertions(+) > create mode 100644 doc/device-tree-bindings/net/phy/adin.txt > create mode 100644 drivers/net/phy/adin.c > > diff --git a/doc/device-tree-bindings/net/phy/adin.txt b/doc/device-tree-bindings/net/phy/adin.txt > new file mode 100644 > index 0000000000..d4bab57e2d > --- /dev/null > +++ b/doc/device-tree-bindings/net/phy/adin.txt > @@ -0,0 +1,26 @@ > +* Analog Devices ADIN PHY Device Tree binding > + > +Required properties: > +- reg: PHY address > + > +Optional properties: > +- adi,rx-internal-delay-ps: RGMII RX Clock Delay used only when PHY operates > + in RGMII mode with internal delay (phy-mode is 'rgmii-id' or > + 'rgmii-rxid') in pico-seconds. > +- adi,tx-internal-delay-ps: RGMII TX Clock Delay used only when PHY operates > + in RGMII mode with internal delay (phy-mode is 'rgmii-id' or > + 'rgmii-txid') in pico-seconds. > +- adi,phy-mode-override: Override phy-mode property for adin. This is useful > + when a single device tree supports an adin PHY (e.g. ADIN1300) > + or another PHY (e.g. AR8033) at the same address, but they require > + different phy-modes. > + > +Example: > + > + ethernet-phy@0 { > + reg = <0>; > + > + adi,rx-internal-delay-ps = <1800>; > + adi,tx-internal-delay-ps = <2200>; > + adi,phy-mode-override = "rgmii-id"; > + }; > diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig > index 014a4de223..2f90ab0d2c 100644 > --- a/drivers/net/phy/Kconfig > +++ b/drivers/net/phy/Kconfig > @@ -68,6 +68,11 @@ endif # MV88E61XX_SWITCH > config PHYLIB_10G > bool "Generic 10G PHY support" > > +config PHY_ADIN > + bool "Analog Devices Industrial Ethernet PHYs" > + help > + Add support for configuring RGMII on Analog Devices ADIN PHYs. > + > menuconfig PHY_AQUANTIA > bool "Aquantia Ethernet PHYs support" > select PHY_GIGE > diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile > index b28440bc4e..a4dd1052e1 100644 > --- a/drivers/net/phy/Makefile > +++ b/drivers/net/phy/Makefile > @@ -10,6 +10,7 @@ obj-$(CONFIG_MV88E6352_SWITCH) += mv88e6352.o > > obj-$(CONFIG_PHYLIB) += phy.o > obj-$(CONFIG_PHYLIB_10G) += generic_10g.o > +obj-$(CONFIG_PHY_ADIN) += adin.o > obj-$(CONFIG_PHY_AQUANTIA) += aquantia.o > obj-$(CONFIG_PHY_ATHEROS) += atheros.o > obj-$(CONFIG_PHY_BROADCOM) += broadcom.o > diff --git a/drivers/net/phy/adin.c b/drivers/net/phy/adin.c > new file mode 100644 > index 0000000000..cff841ab3d > --- /dev/null > +++ b/drivers/net/phy/adin.c > @@ -0,0 +1,228 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/** > + * Driver for Analog Devices Industrial Ethernet PHYs > + * > + * Copyright 2019 Analog Devices Inc. > + * Copyright 2022 Variscite Ltd. > + */ > +#include <common.h> > +#include <phy.h> > +#include <linux/bitops.h> > +#include <linux/bitfield.h> > + > +#define PHY_ID_ADIN1300 0x0283bc30 > +#define ADIN1300_EXT_REG_PTR 0x10 > +#define ADIN1300_EXT_REG_DATA 0x11 > +#define ADIN1300_GE_RGMII_CFG 0xff23 > +#define ADIN1300_GE_RGMII_RX_MSK GENMASK(8, 6) > +#define ADIN1300_GE_RGMII_RX_SEL(x) \ > + FIELD_PREP(ADIN1300_GE_RGMII_RX_MSK, x) > +#define ADIN1300_GE_RGMII_GTX_MSK GENMASK(5, 3) > +#define ADIN1300_GE_RGMII_GTX_SEL(x) \ > + FIELD_PREP(ADIN1300_GE_RGMII_GTX_MSK, x) > +#define ADIN1300_GE_RGMII_RXID_EN BIT(2) > +#define ADIN1300_GE_RGMII_TXID_EN BIT(1) > +#define ADIN1300_GE_RGMII_EN BIT(0) > + > +/* RGMII internal delay settings for rx and tx for ADIN1300 */ > +#define ADIN1300_RGMII_1_60_NS 0x0001 > +#define ADIN1300_RGMII_1_80_NS 0x0002 > +#define ADIN1300_RGMII_2_00_NS 0x0000 > +#define ADIN1300_RGMII_2_20_NS 0x0006 > +#define ADIN1300_RGMII_2_40_NS 0x0007 > + > +/** > + * struct adin_cfg_reg_map - map a config value to aregister value > + * @cfg value in device configuration > + * @reg value in the register > + */ > +struct adin_cfg_reg_map { > + int cfg; > + int reg; > +}; > + > +static const struct adin_cfg_reg_map adin_rgmii_delays[] = { > + { 1600, ADIN1300_RGMII_1_60_NS }, > + { 1800, ADIN1300_RGMII_1_80_NS }, > + { 2000, ADIN1300_RGMII_2_00_NS }, > + { 2200, ADIN1300_RGMII_2_20_NS }, > + { 2400, ADIN1300_RGMII_2_40_NS }, > + { }, > +}; > + > +static int adin_lookup_reg_value(const struct adin_cfg_reg_map *tbl, int cfg) > +{ > + size_t i; > + > + for (i = 0; tbl[i].cfg; i++) { > + if (tbl[i].cfg == cfg) > + return tbl[i].reg; > + } > + > + return -EINVAL; > +} > + > +static u32 adin_get_reg_value(struct phy_device *phydev, > + const char *prop_name, > + const struct adin_cfg_reg_map *tbl, > + u32 dflt) > +{ > + u32 val; > + int rc; > + > + ofnode node = phy_get_ofnode(phydev); > + if (!ofnode_valid(node)) { > + printf("%s: failed to get node\n", __func__); > + return -EINVAL; > + } > + > + if (ofnode_read_u32(node, prop_name, &val)) { > + printf("%s: failed to find %s, using default %d\n", > + __func__, prop_name, dflt); > + return dflt; > + } > + > + debug("%s: %s = '%d'\n", __func__, prop_name, val); > + > + rc = adin_lookup_reg_value(tbl, val); > + if (rc < 0) { > + printf("%s: Unsupported value %u for %s using default (%u)\n", > + __func__, val, prop_name, dflt); > + return dflt; > + } > + > + return rc; > +} > + > +/** > + * adin_get_phy_mode_override - Get phy-mode override for adin PHY > + * > + * The function gets phy-mode string from property 'adi,phy-mode-override' > + * and return its index in phy_interface_strings table, or -1 in error case. > + */ > +int adin_get_phy_mode_override(struct phy_device *phydev) > +{ > + ofnode node = phy_get_ofnode(phydev); > + const char *phy_mode_override; > + const char *prop_phy_mode_override = "adi,phy-mode-override"; > + int override_interface; > + > + phy_mode_override = ofnode_read_string(node, prop_phy_mode_override); > + if (!phy_mode_override) > + return -ENODEV; > + > + debug("%s: %s = '%s'\n", > + __func__, prop_phy_mode_override, phy_mode_override); > + > + override_interface = phy_get_interface_by_name(phy_mode_override); > + > + if (override_interface < 0) > + printf("%s: %s = '%s' is not valid\n", > + __func__, prop_phy_mode_override, phy_mode_override); > + > + return override_interface; > +} > + > +static u16 adin_ext_read(struct phy_device *phydev, const u32 regnum) > +{ > + u16 val; > + > + phy_write(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_PTR, regnum); > + val = phy_read(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_DATA); > + > + debug("%s: adin@0x%x 0x%x=0x%x\n", __func__, phydev->addr, regnum, val); > + > + return val; > +} > + > +static int adin_ext_write(struct phy_device *phydev, const u32 regnum, const u16 val) > +{ > + debug("%s: adin@0x%x 0x%x=0x%x\n", __func__, phydev->addr, regnum, val); > + > + phy_write(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_PTR, regnum); > + > + return phy_write(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_DATA, val); > +} > + > +static int adin_config_rgmii_mode(struct phy_device *phydev) > +{ > + u16 reg_val; > + u32 val; > + int phy_mode_override = adin_get_phy_mode_override(phydev); > + > + if (phy_mode_override >= 0) { > + phydev->interface = (phy_interface_t) phy_mode_override; > + } > + > + reg_val = adin_ext_read(phydev, ADIN1300_GE_RGMII_CFG); > + > + if (!phy_interface_is_rgmii(phydev)) { > + /* Disable RGMII */ > + reg_val &= ~ADIN1300_GE_RGMII_EN; > + return adin_ext_write(phydev, ADIN1300_GE_RGMII_CFG, reg_val); > + } > + > + /* Enable RGMII */ > + reg_val |= ADIN1300_GE_RGMII_EN; > + > + /* Enable / Disable RGMII RX Delay */ > + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || > + phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) { > + reg_val |= ADIN1300_GE_RGMII_RXID_EN; > + > + val = adin_get_reg_value(phydev, "adi,rx-internal-delay-ps", > + adin_rgmii_delays, > + ADIN1300_RGMII_2_00_NS); > + reg_val &= ~ADIN1300_GE_RGMII_RX_MSK; > + reg_val |= ADIN1300_GE_RGMII_RX_SEL(val); > + } else { > + reg_val &= ~ADIN1300_GE_RGMII_RXID_EN; > + } > + > + /* Enable / Disable RGMII RX Delay */ > + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || > + phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) { > + reg_val |= ADIN1300_GE_RGMII_TXID_EN; > + > + val = adin_get_reg_value(phydev, "adi,tx-internal-delay-ps", > + adin_rgmii_delays, > + ADIN1300_RGMII_2_00_NS); > + reg_val &= ~ADIN1300_GE_RGMII_GTX_MSK; > + reg_val |= ADIN1300_GE_RGMII_GTX_SEL(val); > + } else { > + reg_val &= ~ADIN1300_GE_RGMII_TXID_EN; > + } > + > + return adin_ext_write(phydev, ADIN1300_GE_RGMII_CFG, reg_val); > +} > + > +static int adin1300_config(struct phy_device *phydev) > +{ > + int ret; > + > + printf("ADIN1300 PHY detected at addr %d\n", phydev->addr); > + > + ret = adin_config_rgmii_mode(phydev); > + > + if (ret < 0) > + return ret; > + > + return genphy_config(phydev); > +} > + > +static struct phy_driver ADIN1300_driver = { > + .name = "ADIN1300", > + .uid = PHY_ID_ADIN1300, > + .mask = 0xffffffff, > + .features = PHY_GBIT_FEATURES, > + .config = adin1300_config, > + .startup = genphy_startup, > + .shutdown = genphy_shutdown, > +}; > + > +int phy_adin_init(void) > +{ > + phy_register(&ADIN1300_driver); > + > + return 0; > +} > diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c > index d4731860f7..1c54b72cfb 100644 > --- a/drivers/net/phy/phy.c > +++ b/drivers/net/phy/phy.c > @@ -490,6 +490,9 @@ int phy_init(void) > #ifdef CONFIG_MV88E61XX_SWITCH > phy_mv88e61xx_init(); > #endif > +#ifdef CONFIG_PHY_ADIN > + phy_adin_init(); > +#endif > #ifdef CONFIG_PHY_AQUANTIA > phy_aquantia_init(); > #endif > diff --git a/include/phy.h b/include/phy.h > index 5e3da4b01b..bcc7eae7d5 100644 > --- a/include/phy.h > +++ b/include/phy.h > @@ -540,6 +540,7 @@ int gen10g_discover_mmds(struct phy_device *phydev); > > int phy_b53_init(void); > int phy_mv88e61xx_init(void); > +int phy_adin_init(void); > int phy_aquantia_init(void); > int phy_atheros_init(void); > int phy_broadcom_init(void); > -- > 2.32.0 > Reviewed-by: Ramon Fried <rfried.dev@gmail.com>
On Sun, Apr 10, 2022 at 8:13 AM Ramon Fried <rfried.dev@gmail.com> wrote: > > On Fri, Apr 8, 2022 at 8:20 PM Nate Drude <nate.d@variscite.com> wrote: > > > > The current implementation configures RGMII using device tree phy-mode > > property and then calls genphy_config > > > > adin_config_rgmii_mode is derived from: > > https://github.com/varigit/linux-imx/blob/lf-5.10.y_var04/drivers/net/phy/adin.c#L218-L262 > > > > Signed-off-by: Nate Drude <nate.d@variscite.com> > > --- > > doc/device-tree-bindings/net/phy/adin.txt | 26 +++ > > drivers/net/phy/Kconfig | 5 + > > drivers/net/phy/Makefile | 1 + > > drivers/net/phy/adin.c | 228 ++++++++++++++++++++++ > > drivers/net/phy/phy.c | 3 + > > include/phy.h | 1 + > > 6 files changed, 264 insertions(+) > > create mode 100644 doc/device-tree-bindings/net/phy/adin.txt > > create mode 100644 drivers/net/phy/adin.c > > > > diff --git a/doc/device-tree-bindings/net/phy/adin.txt b/doc/device-tree-bindings/net/phy/adin.txt > > new file mode 100644 > > index 0000000000..d4bab57e2d > > --- /dev/null > > +++ b/doc/device-tree-bindings/net/phy/adin.txt > > @@ -0,0 +1,26 @@ > > +* Analog Devices ADIN PHY Device Tree binding > > + > > +Required properties: > > +- reg: PHY address > > + > > +Optional properties: > > +- adi,rx-internal-delay-ps: RGMII RX Clock Delay used only when PHY operates > > + in RGMII mode with internal delay (phy-mode is 'rgmii-id' or > > + 'rgmii-rxid') in pico-seconds. > > +- adi,tx-internal-delay-ps: RGMII TX Clock Delay used only when PHY operates > > + in RGMII mode with internal delay (phy-mode is 'rgmii-id' or > > + 'rgmii-txid') in pico-seconds. > > +- adi,phy-mode-override: Override phy-mode property for adin. This is useful > > + when a single device tree supports an adin PHY (e.g. ADIN1300) > > + or another PHY (e.g. AR8033) at the same address, but they require > > + different phy-modes. > > + > > +Example: > > + > > + ethernet-phy@0 { > > + reg = <0>; > > + > > + adi,rx-internal-delay-ps = <1800>; > > + adi,tx-internal-delay-ps = <2200>; > > + adi,phy-mode-override = "rgmii-id"; > > + }; > > diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig > > index 014a4de223..2f90ab0d2c 100644 > > --- a/drivers/net/phy/Kconfig > > +++ b/drivers/net/phy/Kconfig > > @@ -68,6 +68,11 @@ endif # MV88E61XX_SWITCH > > config PHYLIB_10G > > bool "Generic 10G PHY support" > > > > +config PHY_ADIN > > + bool "Analog Devices Industrial Ethernet PHYs" > > + help > > + Add support for configuring RGMII on Analog Devices ADIN PHYs. > > + > > menuconfig PHY_AQUANTIA > > bool "Aquantia Ethernet PHYs support" > > select PHY_GIGE > > diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile > > index b28440bc4e..a4dd1052e1 100644 > > --- a/drivers/net/phy/Makefile > > +++ b/drivers/net/phy/Makefile > > @@ -10,6 +10,7 @@ obj-$(CONFIG_MV88E6352_SWITCH) += mv88e6352.o > > > > obj-$(CONFIG_PHYLIB) += phy.o > > obj-$(CONFIG_PHYLIB_10G) += generic_10g.o > > +obj-$(CONFIG_PHY_ADIN) += adin.o > > obj-$(CONFIG_PHY_AQUANTIA) += aquantia.o > > obj-$(CONFIG_PHY_ATHEROS) += atheros.o > > obj-$(CONFIG_PHY_BROADCOM) += broadcom.o > > diff --git a/drivers/net/phy/adin.c b/drivers/net/phy/adin.c > > new file mode 100644 > > index 0000000000..cff841ab3d > > --- /dev/null > > +++ b/drivers/net/phy/adin.c > > @@ -0,0 +1,228 @@ > > +// SPDX-License-Identifier: GPL-2.0+ > > +/** > > + * Driver for Analog Devices Industrial Ethernet PHYs > > + * > > + * Copyright 2019 Analog Devices Inc. > > + * Copyright 2022 Variscite Ltd. > > + */ > > +#include <common.h> > > +#include <phy.h> > > +#include <linux/bitops.h> > > +#include <linux/bitfield.h> > > + > > +#define PHY_ID_ADIN1300 0x0283bc30 > > +#define ADIN1300_EXT_REG_PTR 0x10 > > +#define ADIN1300_EXT_REG_DATA 0x11 > > +#define ADIN1300_GE_RGMII_CFG 0xff23 > > +#define ADIN1300_GE_RGMII_RX_MSK GENMASK(8, 6) > > +#define ADIN1300_GE_RGMII_RX_SEL(x) \ > > + FIELD_PREP(ADIN1300_GE_RGMII_RX_MSK, x) > > +#define ADIN1300_GE_RGMII_GTX_MSK GENMASK(5, 3) > > +#define ADIN1300_GE_RGMII_GTX_SEL(x) \ > > + FIELD_PREP(ADIN1300_GE_RGMII_GTX_MSK, x) > > +#define ADIN1300_GE_RGMII_RXID_EN BIT(2) > > +#define ADIN1300_GE_RGMII_TXID_EN BIT(1) > > +#define ADIN1300_GE_RGMII_EN BIT(0) > > + > > +/* RGMII internal delay settings for rx and tx for ADIN1300 */ > > +#define ADIN1300_RGMII_1_60_NS 0x0001 > > +#define ADIN1300_RGMII_1_80_NS 0x0002 > > +#define ADIN1300_RGMII_2_00_NS 0x0000 > > +#define ADIN1300_RGMII_2_20_NS 0x0006 > > +#define ADIN1300_RGMII_2_40_NS 0x0007 > > + > > +/** > > + * struct adin_cfg_reg_map - map a config value to aregister value > > + * @cfg value in device configuration > > + * @reg value in the register > > + */ > > +struct adin_cfg_reg_map { > > + int cfg; > > + int reg; > > +}; > > + > > +static const struct adin_cfg_reg_map adin_rgmii_delays[] = { > > + { 1600, ADIN1300_RGMII_1_60_NS }, > > + { 1800, ADIN1300_RGMII_1_80_NS }, > > + { 2000, ADIN1300_RGMII_2_00_NS }, > > + { 2200, ADIN1300_RGMII_2_20_NS }, > > + { 2400, ADIN1300_RGMII_2_40_NS }, > > + { }, > > +}; > > + > > +static int adin_lookup_reg_value(const struct adin_cfg_reg_map *tbl, int cfg) > > +{ > > + size_t i; > > + > > + for (i = 0; tbl[i].cfg; i++) { > > + if (tbl[i].cfg == cfg) > > + return tbl[i].reg; > > + } > > + > > + return -EINVAL; > > +} > > + > > +static u32 adin_get_reg_value(struct phy_device *phydev, > > + const char *prop_name, > > + const struct adin_cfg_reg_map *tbl, > > + u32 dflt) > > +{ > > + u32 val; > > + int rc; > > + > > + ofnode node = phy_get_ofnode(phydev); > > + if (!ofnode_valid(node)) { > > + printf("%s: failed to get node\n", __func__); > > + return -EINVAL; > > + } > > + > > + if (ofnode_read_u32(node, prop_name, &val)) { > > + printf("%s: failed to find %s, using default %d\n", > > + __func__, prop_name, dflt); > > + return dflt; > > + } > > + > > + debug("%s: %s = '%d'\n", __func__, prop_name, val); > > + > > + rc = adin_lookup_reg_value(tbl, val); > > + if (rc < 0) { > > + printf("%s: Unsupported value %u for %s using default (%u)\n", > > + __func__, val, prop_name, dflt); > > + return dflt; > > + } > > + > > + return rc; > > +} > > + > > +/** > > + * adin_get_phy_mode_override - Get phy-mode override for adin PHY > > + * > > + * The function gets phy-mode string from property 'adi,phy-mode-override' > > + * and return its index in phy_interface_strings table, or -1 in error case. > > + */ > > +int adin_get_phy_mode_override(struct phy_device *phydev) > > +{ > > + ofnode node = phy_get_ofnode(phydev); > > + const char *phy_mode_override; > > + const char *prop_phy_mode_override = "adi,phy-mode-override"; > > + int override_interface; > > + > > + phy_mode_override = ofnode_read_string(node, prop_phy_mode_override); > > + if (!phy_mode_override) > > + return -ENODEV; > > + > > + debug("%s: %s = '%s'\n", > > + __func__, prop_phy_mode_override, phy_mode_override); > > + > > + override_interface = phy_get_interface_by_name(phy_mode_override); > > + > > + if (override_interface < 0) > > + printf("%s: %s = '%s' is not valid\n", > > + __func__, prop_phy_mode_override, phy_mode_override); > > + > > + return override_interface; > > +} > > + > > +static u16 adin_ext_read(struct phy_device *phydev, const u32 regnum) > > +{ > > + u16 val; > > + > > + phy_write(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_PTR, regnum); > > + val = phy_read(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_DATA); > > + > > + debug("%s: adin@0x%x 0x%x=0x%x\n", __func__, phydev->addr, regnum, val); > > + > > + return val; > > +} > > + > > +static int adin_ext_write(struct phy_device *phydev, const u32 regnum, const u16 val) > > +{ > > + debug("%s: adin@0x%x 0x%x=0x%x\n", __func__, phydev->addr, regnum, val); > > + > > + phy_write(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_PTR, regnum); > > + > > + return phy_write(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_DATA, val); > > +} > > + > > +static int adin_config_rgmii_mode(struct phy_device *phydev) > > +{ > > + u16 reg_val; > > + u32 val; > > + int phy_mode_override = adin_get_phy_mode_override(phydev); > > + > > + if (phy_mode_override >= 0) { > > + phydev->interface = (phy_interface_t) phy_mode_override; > > + } > > + > > + reg_val = adin_ext_read(phydev, ADIN1300_GE_RGMII_CFG); > > + > > + if (!phy_interface_is_rgmii(phydev)) { > > + /* Disable RGMII */ > > + reg_val &= ~ADIN1300_GE_RGMII_EN; > > + return adin_ext_write(phydev, ADIN1300_GE_RGMII_CFG, reg_val); > > + } > > + > > + /* Enable RGMII */ > > + reg_val |= ADIN1300_GE_RGMII_EN; > > + > > + /* Enable / Disable RGMII RX Delay */ > > + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || > > + phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) { > > + reg_val |= ADIN1300_GE_RGMII_RXID_EN; > > + > > + val = adin_get_reg_value(phydev, "adi,rx-internal-delay-ps", > > + adin_rgmii_delays, > > + ADIN1300_RGMII_2_00_NS); > > + reg_val &= ~ADIN1300_GE_RGMII_RX_MSK; > > + reg_val |= ADIN1300_GE_RGMII_RX_SEL(val); > > + } else { > > + reg_val &= ~ADIN1300_GE_RGMII_RXID_EN; > > + } > > + > > + /* Enable / Disable RGMII RX Delay */ > > + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || > > + phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) { > > + reg_val |= ADIN1300_GE_RGMII_TXID_EN; > > + > > + val = adin_get_reg_value(phydev, "adi,tx-internal-delay-ps", > > + adin_rgmii_delays, > > + ADIN1300_RGMII_2_00_NS); > > + reg_val &= ~ADIN1300_GE_RGMII_GTX_MSK; > > + reg_val |= ADIN1300_GE_RGMII_GTX_SEL(val); > > + } else { > > + reg_val &= ~ADIN1300_GE_RGMII_TXID_EN; > > + } > > + > > + return adin_ext_write(phydev, ADIN1300_GE_RGMII_CFG, reg_val); > > +} > > + > > +static int adin1300_config(struct phy_device *phydev) > > +{ > > + int ret; > > + > > + printf("ADIN1300 PHY detected at addr %d\n", phydev->addr); > > + > > + ret = adin_config_rgmii_mode(phydev); > > + > > + if (ret < 0) > > + return ret; > > + > > + return genphy_config(phydev); > > +} > > + > > +static struct phy_driver ADIN1300_driver = { > > + .name = "ADIN1300", > > + .uid = PHY_ID_ADIN1300, > > + .mask = 0xffffffff, > > + .features = PHY_GBIT_FEATURES, > > + .config = adin1300_config, > > + .startup = genphy_startup, > > + .shutdown = genphy_shutdown, > > +}; > > + > > +int phy_adin_init(void) > > +{ > > + phy_register(&ADIN1300_driver); > > + > > + return 0; > > +} > > diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c > > index d4731860f7..1c54b72cfb 100644 > > --- a/drivers/net/phy/phy.c > > +++ b/drivers/net/phy/phy.c > > @@ -490,6 +490,9 @@ int phy_init(void) > > #ifdef CONFIG_MV88E61XX_SWITCH > > phy_mv88e61xx_init(); > > #endif > > +#ifdef CONFIG_PHY_ADIN > > + phy_adin_init(); > > +#endif > > #ifdef CONFIG_PHY_AQUANTIA > > phy_aquantia_init(); > > #endif > > diff --git a/include/phy.h b/include/phy.h > > index 5e3da4b01b..bcc7eae7d5 100644 > > --- a/include/phy.h > > +++ b/include/phy.h > > @@ -540,6 +540,7 @@ int gen10g_discover_mmds(struct phy_device *phydev); > > > > int phy_b53_init(void); > > int phy_mv88e61xx_init(void); > > +int phy_adin_init(void); > > int phy_aquantia_init(void); > > int phy_atheros_init(void); > > int phy_broadcom_init(void); > > -- > > 2.32.0 > > > Reviewed-by: Ramon Fried <rfried.dev@gmail.com> Applied to u-boot-net/next Thanks, Ramon
diff --git a/doc/device-tree-bindings/net/phy/adin.txt b/doc/device-tree-bindings/net/phy/adin.txt new file mode 100644 index 0000000000..d4bab57e2d --- /dev/null +++ b/doc/device-tree-bindings/net/phy/adin.txt @@ -0,0 +1,26 @@ +* Analog Devices ADIN PHY Device Tree binding + +Required properties: +- reg: PHY address + +Optional properties: +- adi,rx-internal-delay-ps: RGMII RX Clock Delay used only when PHY operates + in RGMII mode with internal delay (phy-mode is 'rgmii-id' or + 'rgmii-rxid') in pico-seconds. +- adi,tx-internal-delay-ps: RGMII TX Clock Delay used only when PHY operates + in RGMII mode with internal delay (phy-mode is 'rgmii-id' or + 'rgmii-txid') in pico-seconds. +- adi,phy-mode-override: Override phy-mode property for adin. This is useful + when a single device tree supports an adin PHY (e.g. ADIN1300) + or another PHY (e.g. AR8033) at the same address, but they require + different phy-modes. + +Example: + + ethernet-phy@0 { + reg = <0>; + + adi,rx-internal-delay-ps = <1800>; + adi,tx-internal-delay-ps = <2200>; + adi,phy-mode-override = "rgmii-id"; + }; diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 014a4de223..2f90ab0d2c 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -68,6 +68,11 @@ endif # MV88E61XX_SWITCH config PHYLIB_10G bool "Generic 10G PHY support" +config PHY_ADIN + bool "Analog Devices Industrial Ethernet PHYs" + help + Add support for configuring RGMII on Analog Devices ADIN PHYs. + menuconfig PHY_AQUANTIA bool "Aquantia Ethernet PHYs support" select PHY_GIGE diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index b28440bc4e..a4dd1052e1 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_MV88E6352_SWITCH) += mv88e6352.o obj-$(CONFIG_PHYLIB) += phy.o obj-$(CONFIG_PHYLIB_10G) += generic_10g.o +obj-$(CONFIG_PHY_ADIN) += adin.o obj-$(CONFIG_PHY_AQUANTIA) += aquantia.o obj-$(CONFIG_PHY_ATHEROS) += atheros.o obj-$(CONFIG_PHY_BROADCOM) += broadcom.o diff --git a/drivers/net/phy/adin.c b/drivers/net/phy/adin.c new file mode 100644 index 0000000000..cff841ab3d --- /dev/null +++ b/drivers/net/phy/adin.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0+ +/** + * Driver for Analog Devices Industrial Ethernet PHYs + * + * Copyright 2019 Analog Devices Inc. + * Copyright 2022 Variscite Ltd. + */ +#include <common.h> +#include <phy.h> +#include <linux/bitops.h> +#include <linux/bitfield.h> + +#define PHY_ID_ADIN1300 0x0283bc30 +#define ADIN1300_EXT_REG_PTR 0x10 +#define ADIN1300_EXT_REG_DATA 0x11 +#define ADIN1300_GE_RGMII_CFG 0xff23 +#define ADIN1300_GE_RGMII_RX_MSK GENMASK(8, 6) +#define ADIN1300_GE_RGMII_RX_SEL(x) \ + FIELD_PREP(ADIN1300_GE_RGMII_RX_MSK, x) +#define ADIN1300_GE_RGMII_GTX_MSK GENMASK(5, 3) +#define ADIN1300_GE_RGMII_GTX_SEL(x) \ + FIELD_PREP(ADIN1300_GE_RGMII_GTX_MSK, x) +#define ADIN1300_GE_RGMII_RXID_EN BIT(2) +#define ADIN1300_GE_RGMII_TXID_EN BIT(1) +#define ADIN1300_GE_RGMII_EN BIT(0) + +/* RGMII internal delay settings for rx and tx for ADIN1300 */ +#define ADIN1300_RGMII_1_60_NS 0x0001 +#define ADIN1300_RGMII_1_80_NS 0x0002 +#define ADIN1300_RGMII_2_00_NS 0x0000 +#define ADIN1300_RGMII_2_20_NS 0x0006 +#define ADIN1300_RGMII_2_40_NS 0x0007 + +/** + * struct adin_cfg_reg_map - map a config value to aregister value + * @cfg value in device configuration + * @reg value in the register + */ +struct adin_cfg_reg_map { + int cfg; + int reg; +}; + +static const struct adin_cfg_reg_map adin_rgmii_delays[] = { + { 1600, ADIN1300_RGMII_1_60_NS }, + { 1800, ADIN1300_RGMII_1_80_NS }, + { 2000, ADIN1300_RGMII_2_00_NS }, + { 2200, ADIN1300_RGMII_2_20_NS }, + { 2400, ADIN1300_RGMII_2_40_NS }, + { }, +}; + +static int adin_lookup_reg_value(const struct adin_cfg_reg_map *tbl, int cfg) +{ + size_t i; + + for (i = 0; tbl[i].cfg; i++) { + if (tbl[i].cfg == cfg) + return tbl[i].reg; + } + + return -EINVAL; +} + +static u32 adin_get_reg_value(struct phy_device *phydev, + const char *prop_name, + const struct adin_cfg_reg_map *tbl, + u32 dflt) +{ + u32 val; + int rc; + + ofnode node = phy_get_ofnode(phydev); + if (!ofnode_valid(node)) { + printf("%s: failed to get node\n", __func__); + return -EINVAL; + } + + if (ofnode_read_u32(node, prop_name, &val)) { + printf("%s: failed to find %s, using default %d\n", + __func__, prop_name, dflt); + return dflt; + } + + debug("%s: %s = '%d'\n", __func__, prop_name, val); + + rc = adin_lookup_reg_value(tbl, val); + if (rc < 0) { + printf("%s: Unsupported value %u for %s using default (%u)\n", + __func__, val, prop_name, dflt); + return dflt; + } + + return rc; +} + +/** + * adin_get_phy_mode_override - Get phy-mode override for adin PHY + * + * The function gets phy-mode string from property 'adi,phy-mode-override' + * and return its index in phy_interface_strings table, or -1 in error case. + */ +int adin_get_phy_mode_override(struct phy_device *phydev) +{ + ofnode node = phy_get_ofnode(phydev); + const char *phy_mode_override; + const char *prop_phy_mode_override = "adi,phy-mode-override"; + int override_interface; + + phy_mode_override = ofnode_read_string(node, prop_phy_mode_override); + if (!phy_mode_override) + return -ENODEV; + + debug("%s: %s = '%s'\n", + __func__, prop_phy_mode_override, phy_mode_override); + + override_interface = phy_get_interface_by_name(phy_mode_override); + + if (override_interface < 0) + printf("%s: %s = '%s' is not valid\n", + __func__, prop_phy_mode_override, phy_mode_override); + + return override_interface; +} + +static u16 adin_ext_read(struct phy_device *phydev, const u32 regnum) +{ + u16 val; + + phy_write(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_PTR, regnum); + val = phy_read(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_DATA); + + debug("%s: adin@0x%x 0x%x=0x%x\n", __func__, phydev->addr, regnum, val); + + return val; +} + +static int adin_ext_write(struct phy_device *phydev, const u32 regnum, const u16 val) +{ + debug("%s: adin@0x%x 0x%x=0x%x\n", __func__, phydev->addr, regnum, val); + + phy_write(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_PTR, regnum); + + return phy_write(phydev, MDIO_DEVAD_NONE, ADIN1300_EXT_REG_DATA, val); +} + +static int adin_config_rgmii_mode(struct phy_device *phydev) +{ + u16 reg_val; + u32 val; + int phy_mode_override = adin_get_phy_mode_override(phydev); + + if (phy_mode_override >= 0) { + phydev->interface = (phy_interface_t) phy_mode_override; + } + + reg_val = adin_ext_read(phydev, ADIN1300_GE_RGMII_CFG); + + if (!phy_interface_is_rgmii(phydev)) { + /* Disable RGMII */ + reg_val &= ~ADIN1300_GE_RGMII_EN; + return adin_ext_write(phydev, ADIN1300_GE_RGMII_CFG, reg_val); + } + + /* Enable RGMII */ + reg_val |= ADIN1300_GE_RGMII_EN; + + /* Enable / Disable RGMII RX Delay */ + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || + phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) { + reg_val |= ADIN1300_GE_RGMII_RXID_EN; + + val = adin_get_reg_value(phydev, "adi,rx-internal-delay-ps", + adin_rgmii_delays, + ADIN1300_RGMII_2_00_NS); + reg_val &= ~ADIN1300_GE_RGMII_RX_MSK; + reg_val |= ADIN1300_GE_RGMII_RX_SEL(val); + } else { + reg_val &= ~ADIN1300_GE_RGMII_RXID_EN; + } + + /* Enable / Disable RGMII RX Delay */ + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || + phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) { + reg_val |= ADIN1300_GE_RGMII_TXID_EN; + + val = adin_get_reg_value(phydev, "adi,tx-internal-delay-ps", + adin_rgmii_delays, + ADIN1300_RGMII_2_00_NS); + reg_val &= ~ADIN1300_GE_RGMII_GTX_MSK; + reg_val |= ADIN1300_GE_RGMII_GTX_SEL(val); + } else { + reg_val &= ~ADIN1300_GE_RGMII_TXID_EN; + } + + return adin_ext_write(phydev, ADIN1300_GE_RGMII_CFG, reg_val); +} + +static int adin1300_config(struct phy_device *phydev) +{ + int ret; + + printf("ADIN1300 PHY detected at addr %d\n", phydev->addr); + + ret = adin_config_rgmii_mode(phydev); + + if (ret < 0) + return ret; + + return genphy_config(phydev); +} + +static struct phy_driver ADIN1300_driver = { + .name = "ADIN1300", + .uid = PHY_ID_ADIN1300, + .mask = 0xffffffff, + .features = PHY_GBIT_FEATURES, + .config = adin1300_config, + .startup = genphy_startup, + .shutdown = genphy_shutdown, +}; + +int phy_adin_init(void) +{ + phy_register(&ADIN1300_driver); + + return 0; +} diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index d4731860f7..1c54b72cfb 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -490,6 +490,9 @@ int phy_init(void) #ifdef CONFIG_MV88E61XX_SWITCH phy_mv88e61xx_init(); #endif +#ifdef CONFIG_PHY_ADIN + phy_adin_init(); +#endif #ifdef CONFIG_PHY_AQUANTIA phy_aquantia_init(); #endif diff --git a/include/phy.h b/include/phy.h index 5e3da4b01b..bcc7eae7d5 100644 --- a/include/phy.h +++ b/include/phy.h @@ -540,6 +540,7 @@ int gen10g_discover_mmds(struct phy_device *phydev); int phy_b53_init(void); int phy_mv88e61xx_init(void); +int phy_adin_init(void); int phy_aquantia_init(void); int phy_atheros_init(void); int phy_broadcom_init(void);
The current implementation configures RGMII using device tree phy-mode property and then calls genphy_config adin_config_rgmii_mode is derived from: https://github.com/varigit/linux-imx/blob/lf-5.10.y_var04/drivers/net/phy/adin.c#L218-L262 Signed-off-by: Nate Drude <nate.d@variscite.com> --- doc/device-tree-bindings/net/phy/adin.txt | 26 +++ drivers/net/phy/Kconfig | 5 + drivers/net/phy/Makefile | 1 + drivers/net/phy/adin.c | 228 ++++++++++++++++++++++ drivers/net/phy/phy.c | 3 + include/phy.h | 1 + 6 files changed, 264 insertions(+) create mode 100644 doc/device-tree-bindings/net/phy/adin.txt create mode 100644 drivers/net/phy/adin.c