From: Nate Drude Date: Fri, 8 Apr 2022 16:28:14 +0000 (-0500) Subject: phy: adin: add driver for Analog Devices ADIN1300 PHY X-Git-Url: http://git.dujemihanovic.xyz/?a=commitdiff_plain;h=d79f1a85697e3af24a97728f6e4f16635bdc8290;p=u-boot.git phy: adin: add driver for Analog Devices ADIN1300 PHY 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 Reviewed-by: Ramon Fried --- 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 +#include +#include +#include + +#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 bc83846606..e0b37a9542 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 30af8bfa0e..37b2a0281e 100644 --- a/include/phy.h +++ b/include/phy.h @@ -526,6 +526,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);