diff options
| author | Tom Rini <[email protected]> | 2025-12-02 13:44:01 -0600 |
|---|---|---|
| committer | Tom Rini <[email protected]> | 2025-12-02 13:44:01 -0600 |
| commit | 94bda4068cb4a05f5975e6976a1d79fa1a9ca51b (patch) | |
| tree | 1aa1125f749e0d696163b3451f940441eb0cbc50 | |
| parent | 74dac40d4e1fa8013e72519c50cf046b6cbd1990 (diff) | |
| parent | c10e1c2eded4783b8f4650da879ebae2beffcce6 (diff) | |
Merge tag 'net-next-20251201' of https://source.denx.de/u-boot/custodians/u-boot-net into next
Pull request net-next-20251201
net:
- phy: Add the Airoha EN8811H PHY driver
- airoha: bind MDIO controller on Ethernet load
- phy: Disallow PHY_MSCC and PHY_VITESSE under COMPILE_TEST
- phy: aquantia: refactor firmware upload helpers
- phy: aquantia: use generic firmware loader
net-legacy:
- tftp: Remove tftp_init_load_addr error path
| -rw-r--r-- | drivers/net/Kconfig | 1 | ||||
| -rw-r--r-- | drivers/net/airoha_eth.c | 32 | ||||
| -rw-r--r-- | drivers/net/phy/Kconfig | 27 | ||||
| -rw-r--r-- | drivers/net/phy/Makefile | 1 | ||||
| -rw-r--r-- | drivers/net/phy/airoha/Kconfig | 11 | ||||
| -rw-r--r-- | drivers/net/phy/airoha/Makefile | 3 | ||||
| -rw-r--r-- | drivers/net/phy/airoha/air_en8811.c | 805 | ||||
| -rw-r--r-- | drivers/net/phy/aquantia.c | 101 | ||||
| -rw-r--r-- | net/tftp.c | 18 |
9 files changed, 918 insertions, 81 deletions
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 544e302d600..f382a7752d5 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -126,6 +126,7 @@ config AIROHA_ETH depends on ARCH_AIROHA select PHYLIB select DM_RESET + select MDIO_MT7531 help This Driver support Airoha Ethernet QDMA Driver Say Y to enable support for the Airoha Ethernet QDMA. diff --git a/drivers/net/airoha_eth.c b/drivers/net/airoha_eth.c index 3234d875887..046b677d78e 100644 --- a/drivers/net/airoha_eth.c +++ b/drivers/net/airoha_eth.c @@ -10,6 +10,7 @@ #include <dm.h> #include <dm/devres.h> +#include <dm/lists.h> #include <mapmem.h> #include <net.h> #include <regmap.h> @@ -982,6 +983,36 @@ static int arht_eth_write_hwaddr(struct udevice *dev) return 0; } +static int airoha_eth_bind(struct udevice *dev) +{ + ofnode switch_node, mdio_node; + struct udevice *mdio_dev; + int ret = 0; + + if (!CONFIG_IS_ENABLED(MDIO_MT7531)) + return 0; + + switch_node = ofnode_by_compatible(ofnode_null(), + "airoha,en7581-switch"); + if (!ofnode_valid(switch_node)) { + debug("Warning: missing switch node\n"); + return 0; + } + + mdio_node = ofnode_find_subnode(switch_node, "mdio"); + if (!ofnode_valid(mdio_node)) { + debug("Warning: missing mdio node\n"); + return 0; + } + + ret = device_bind_driver_to_node(dev, "mt7531-mdio", "mdio", + mdio_node, &mdio_dev); + if (ret) + debug("Warning: failed to bind mdio controller\n"); + + return 0; +} + static const struct airoha_eth_soc_data en7523_data = { .xsi_rsts_names = en7523_xsi_rsts_names, .num_xsi_rsts = ARRAY_SIZE(en7523_xsi_rsts_names), @@ -1018,6 +1049,7 @@ U_BOOT_DRIVER(airoha_eth) = { .id = UCLASS_ETH, .of_match = airoha_eth_ids, .probe = airoha_eth_probe, + .bind = airoha_eth_bind, .ops = &airoha_eth_ops, .priv_auto = sizeof(struct airoha_eth), .plat_auto = sizeof(struct eth_pdata), diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 018be98705a..d0aab0b8fe8 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -91,23 +91,22 @@ menuconfig PHY_AQUANTIA config PHY_AQUANTIA_UPLOAD_FW bool "Aquantia firmware loading support" depends on PHY_AQUANTIA + select FW_LOADER help - Aquantia PHYs use firmware which can be either loaded automatically - from storage directly attached to the phy or loaded by the boot loader - via MDIO commands. The firmware is loaded from a file, specified by - the PHY_AQUANTIA_FW_PART and PHY_AQUANTIA_FW_NAME options. + Aquantia PHYs use firmware which can be either loaded automatically + from storage directly attached to the phy or loaded by the boot loader + via MDIO commands. -config PHY_AQUANTIA_FW_PART - string "Aquantia firmware partition" - depends on PHY_AQUANTIA_UPLOAD_FW - help - Partition containing the firmware file. + This option enables loading the firmware using the generic + firmware loader framework. -config PHY_AQUANTIA_FW_NAME - string "Aquantia firmware filename" +config PHY_AQUANTIA_FW_MAX_SIZE + hex "Max firmware size" depends on PHY_AQUANTIA_UPLOAD_FW + default 0x80000 help - Firmware filename. + The maximum size of the Aquantia PHY firmware. This is used to + allocate a buffer to load the firmware into. config PHY_ATHEROS bool "Atheros Ethernet PHYs support" @@ -188,6 +187,8 @@ config PHY_MARVELL_10G source "drivers/net/phy/mediatek/Kconfig" +source "drivers/net/phy/airoha/Kconfig" + config PHY_MESON_GXL bool "Amlogic Meson GXL Internal PHY support" @@ -236,6 +237,7 @@ config PHY_MOTORCOMM Currently supports the YT8511 and YT8531 Gigabit Ethernet PHYs. config PHY_MSCC + depends on !COMPILE_TEST bool "Microsemi Corp Ethernet PHYs support" config PHY_NATSEMI @@ -340,6 +342,7 @@ config PHY_TI_GENERIC config PHY_VITESSE bool "Vitesse Ethernet PHYs support" + depends on !COMPILE_TEST config PHY_XILINX bool "Xilinx Ethernet PHYs support" diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index a339b8ac29d..83520de7f1f 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_PHY_LXT) += lxt.o obj-$(CONFIG_PHY_MARVELL) += marvell.o obj-$(CONFIG_PHY_MARVELL_10G) += marvell10g.o obj-y += mediatek/ +obj-y += airoha/ obj-$(CONFIG_PHY_MICREL_KSZ8XXX) += micrel_ksz8xxx.o obj-$(CONFIG_PHY_MICREL_KSZ90X1) += micrel_ksz90x1.o obj-$(CONFIG_PHY_MESON_GXL) += meson-gxl.o diff --git a/drivers/net/phy/airoha/Kconfig b/drivers/net/phy/airoha/Kconfig new file mode 100644 index 00000000000..999564e4848 --- /dev/null +++ b/drivers/net/phy/airoha/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig PHY_AIROHA + bool "Airoha Ethernet PHYs support" + +config PHY_AIROHA_EN8811 + bool "Airoha Ethernet EN8811H support" + depends on PHY_AIROHA + select FW_LOADER + help + AIROHA EN8811H supported. + diff --git a/drivers/net/phy/airoha/Makefile b/drivers/net/phy/airoha/Makefile new file mode 100644 index 00000000000..84d23b19ab0 --- /dev/null +++ b/drivers/net/phy/airoha/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_PHY_AIROHA_EN8811) += air_en8811.o diff --git a/drivers/net/phy/airoha/air_en8811.c b/drivers/net/phy/airoha/air_en8811.c new file mode 100644 index 00000000000..1a628ede82b --- /dev/null +++ b/drivers/net/phy/airoha/air_en8811.c @@ -0,0 +1,805 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the Airoha EN8811H 2.5 Gigabit PHY. + * + * Limitations of the EN8811H: + * - Only full duplex supported + * - Forced speed (AN off) is not supported by hardware (100Mbps) + * + * Source originated from linux air_en8811h.c + * + * Copyright (C) 2025 Airoha Technology Corp. + */ + +#include <phy.h> +#include <errno.h> +#include <log.h> +#include <env.h> +#include <malloc.h> +#include <fw_loader.h> +#include <asm/unaligned.h> +#include <linux/iopoll.h> +#include <linux/bitops.h> +#include <linux/compat.h> +#include <dm/device_compat.h> +#include <u-boot/crc.h> + +#define EN8811H_PHY_ID 0x03a2a411 + +#define AIR_FW_ADDR_DM 0x00000000 +#define AIR_FW_ADDR_DSP 0x00100000 + +#define EN8811H_MD32_DM_SIZE 0x4000 +#define EN8811H_MD32_DSP_SIZE 0x20000 + +#define EN8811H_FW_CTRL_1 0x0f0018 +#define EN8811H_FW_CTRL_1_START 0x0 +#define EN8811H_FW_CTRL_1_FINISH 0x1 +#define EN8811H_FW_CTRL_2 0x800000 +#define EN8811H_FW_CTRL_2_LOADING BIT(11) + +/* MII Registers */ +#define AIR_AUX_CTRL_STATUS 0x1d +#define AIR_AUX_CTRL_STATUS_SPEED_MASK GENMASK(4, 2) +#define AIR_AUX_CTRL_STATUS_SPEED_100 0x4 +#define AIR_AUX_CTRL_STATUS_SPEED_1000 0x8 +#define AIR_AUX_CTRL_STATUS_SPEED_2500 0xc + +#define AIR_EXT_PAGE_ACCESS 0x1f +#define AIR_PHY_PAGE_STANDARD 0x0000 +#define AIR_PHY_PAGE_EXTENDED_4 0x0004 + +/* MII Registers Page 4 */ +#define AIR_BPBUS_MODE 0x10 +#define AIR_BPBUS_MODE_ADDR_FIXED 0x0000 +#define AIR_BPBUS_MODE_ADDR_INCR BIT(15) +#define AIR_BPBUS_WR_ADDR_HIGH 0x11 +#define AIR_BPBUS_WR_ADDR_LOW 0x12 +#define AIR_BPBUS_WR_DATA_HIGH 0x13 +#define AIR_BPBUS_WR_DATA_LOW 0x14 +#define AIR_BPBUS_RD_ADDR_HIGH 0x15 +#define AIR_BPBUS_RD_ADDR_LOW 0x16 +#define AIR_BPBUS_RD_DATA_HIGH 0x17 +#define AIR_BPBUS_RD_DATA_LOW 0x18 + +/* Registers on MDIO_MMD_VEND1 */ +#define EN8811H_PHY_FW_STATUS 0x8009 +#define EN8811H_PHY_READY 0x02 + +/* Registers on MDIO_MMD_VEND2 */ +#define AIR_PHY_LED_BCR 0x021 +#define AIR_PHY_LED_BCR_MODE_MASK GENMASK(1, 0) +#define AIR_PHY_LED_BCR_TIME_TEST BIT(2) +#define AIR_PHY_LED_BCR_CLK_EN BIT(3) +#define AIR_PHY_LED_BCR_EXT_CTRL BIT(15) + +#define AIR_PHY_LED_DUR_ON 0x022 + +#define AIR_PHY_LED_DUR_BLINK 0x023 + +#define AIR_PHY_LED_ON(i) (0x024 + ((i) * 2)) +#define AIR_PHY_LED_ON_MASK (GENMASK(6, 0) | BIT(8)) +#define AIR_PHY_LED_ON_LINK1000 BIT(0) +#define AIR_PHY_LED_ON_LINK100 BIT(1) +#define AIR_PHY_LED_ON_LINK10 BIT(2) +#define AIR_PHY_LED_ON_LINKDOWN BIT(3) +#define AIR_PHY_LED_ON_FDX BIT(4) /* Full duplex */ +#define AIR_PHY_LED_ON_HDX BIT(5) /* Half duplex */ +#define AIR_PHY_LED_ON_FORCE_ON BIT(6) +#define AIR_PHY_LED_ON_LINK2500 BIT(8) +#define AIR_PHY_LED_ON_POLARITY BIT(14) +#define AIR_PHY_LED_ON_ENABLE BIT(15) + +#define AIR_PHY_LED_BLINK(i) (0x025 + ((i) * 2)) +#define AIR_PHY_LED_BLINK_1000TX BIT(0) +#define AIR_PHY_LED_BLINK_1000RX BIT(1) +#define AIR_PHY_LED_BLINK_100TX BIT(2) +#define AIR_PHY_LED_BLINK_100RX BIT(3) +#define AIR_PHY_LED_BLINK_10TX BIT(4) +#define AIR_PHY_LED_BLINK_10RX BIT(5) +#define AIR_PHY_LED_BLINK_COLLISION BIT(6) +#define AIR_PHY_LED_BLINK_RX_CRC_ERR BIT(7) +#define AIR_PHY_LED_BLINK_RX_IDLE_ERR BIT(8) +#define AIR_PHY_LED_BLINK_FORCE_BLINK BIT(9) +#define AIR_PHY_LED_BLINK_2500TX BIT(10) +#define AIR_PHY_LED_BLINK_2500RX BIT(11) + +#define EN8811H_FW_VERSION 0x3b3c + +#define EN8811H_POLARITY 0xca0f8 +#define EN8811H_POLARITY_TX_NORMAL BIT(0) +#define EN8811H_POLARITY_RX_REVERSE BIT(1) + +#define EN8811H_CLK_CGM 0xcf958 +#define EN8811H_CLK_CGM_CKO BIT(26) +#define EN8811H_HWTRAP1 0xcf914 +#define EN8811H_HWTRAP1_CKO BIT(12) + +#define clear_bit(bit, bitmap) __clear_bit(bit, bitmap) + +/* Led definitions */ +#define EN8811H_LED_COUNT 3 + +struct led { + unsigned long rules; + unsigned long state; +}; + +enum { + AIR_PHY_LED_STATE_FORCE_ON, + AIR_PHY_LED_STATE_FORCE_BLINK, +}; + +enum { + AIR_PHY_LED_DUR_BLINK_32MS, + AIR_PHY_LED_DUR_BLINK_64MS, + AIR_PHY_LED_DUR_BLINK_128MS, + AIR_PHY_LED_DUR_BLINK_256MS, + AIR_PHY_LED_DUR_BLINK_512MS, + AIR_PHY_LED_DUR_BLINK_1024MS, +}; + +enum { + AIR_LED_DISABLE, + AIR_LED_ENABLE, +}; + +enum { + AIR_ACTIVE_LOW, + AIR_ACTIVE_HIGH, +}; + +enum { + AIR_LED_MODE_DISABLE, + AIR_LED_MODE_USER_DEFINE, +}; + +/* Trigger specific enum */ +enum air_led_trigger_netdev_modes { + AIR_TRIGGER_NETDEV_LINK = 0, + AIR_TRIGGER_NETDEV_LINK_10, + AIR_TRIGGER_NETDEV_LINK_100, + AIR_TRIGGER_NETDEV_LINK_1000, + AIR_TRIGGER_NETDEV_LINK_2500, + AIR_TRIGGER_NETDEV_LINK_5000, + AIR_TRIGGER_NETDEV_LINK_10000, + AIR_TRIGGER_NETDEV_HALF_DUPLEX, + AIR_TRIGGER_NETDEV_FULL_DUPLEX, + AIR_TRIGGER_NETDEV_TX, + AIR_TRIGGER_NETDEV_RX, + AIR_TRIGGER_NETDEV_TX_ERR, + AIR_TRIGGER_NETDEV_RX_ERR, + + /* Keep last */ + __AIR_TRIGGER_NETDEV_MAX, +}; + +/* Default LED setup: + * GPIO5 <-> LED0 On: Link detected, blink Rx/Tx + * GPIO4 <-> LED1 On: Link detected at 2500 and 1000 Mbps + * GPIO3 <-> LED2 On: Link detected at 2500 and 100 Mbps + */ +#define AIR_DEFAULT_TRIGGER_LED0 (BIT(AIR_TRIGGER_NETDEV_LINK) | \ + BIT(AIR_TRIGGER_NETDEV_RX) | \ + BIT(AIR_TRIGGER_NETDEV_TX)) +#define AIR_DEFAULT_TRIGGER_LED1 (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | \ + BIT(AIR_TRIGGER_NETDEV_LINK_1000)) +#define AIR_DEFAULT_TRIGGER_LED2 (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | \ + BIT(AIR_TRIGGER_NETDEV_LINK_100)) + +#define AIR_PHY_LED_DUR_UNIT 781 +#define AIR_PHY_LED_DUR (AIR_PHY_LED_DUR_UNIT << AIR_PHY_LED_DUR_BLINK_64MS) + +struct en8811h_priv { + int firmware_version; + bool mcu_needs_restart; + struct led led[EN8811H_LED_COUNT]; +}; + +static int air_buckpbus_reg_write(struct phy_device *phydev, + u32 pbus_address, u32 pbus_data) +{ + int ret, saved_page; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + if (saved_page < 0) + return saved_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, + AIR_BPBUS_MODE_ADDR_FIXED); + if (ret < 0) + goto restore_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + goto restore_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + goto restore_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH, + upper_16_bits(pbus_data)); + if (ret < 0) + goto restore_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW, + lower_16_bits(pbus_data)); + if (ret < 0) + goto restore_page; + +restore_page: + if (ret < 0) + dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__, + pbus_address, ret); + + return phy_restore_page(phydev, saved_page, ret); +} + +static int air_buckpbus_reg_read(struct phy_device *phydev, + u32 pbus_address, u32 *pbus_data) +{ + int pbus_data_low, pbus_data_high; + int ret = 0, saved_page; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + if (saved_page < 0) + return saved_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, + AIR_BPBUS_MODE_ADDR_FIXED); + if (ret < 0) + goto restore_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + goto restore_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + goto restore_page; + + pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH); + if (pbus_data_high < 0) { + ret = pbus_data_high; + goto restore_page; + } + + pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW); + if (pbus_data_low < 0) { + ret = pbus_data_low; + goto restore_page; + } + + *pbus_data = pbus_data_low | (pbus_data_high << 16); + +restore_page: + if (ret < 0) + dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__, + pbus_address, ret); + + return phy_restore_page(phydev, saved_page, ret); +} + +static int air_buckpbus_reg_modify(struct phy_device *phydev, + u32 pbus_address, u32 mask, u32 set) +{ + int pbus_data_low, pbus_data_high; + u32 pbus_data_old, pbus_data_new; + int ret = 0, saved_page; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + if (saved_page < 0) + return saved_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, + AIR_BPBUS_MODE_ADDR_FIXED); + if (ret < 0) + goto restore_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + goto restore_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + goto restore_page; + + pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH); + if (pbus_data_high < 0) { + ret = pbus_data_high; + goto restore_page; + } + + pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW); + if (pbus_data_low < 0) { + ret = pbus_data_low; + goto restore_page; + } + + pbus_data_old = pbus_data_low | (pbus_data_high << 16); + pbus_data_new = (pbus_data_old & ~mask) | set; + if (pbus_data_new == pbus_data_old) + goto restore_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + goto restore_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + goto restore_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH, + upper_16_bits(pbus_data_new)); + if (ret < 0) + goto restore_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW, + lower_16_bits(pbus_data_new)); + if (ret < 0) + goto restore_page; + +restore_page: + if (ret < 0) + dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__, + pbus_address, ret); + + return phy_restore_page(phydev, saved_page, ret); +} + +static int air_write_buf(struct phy_device *phydev, unsigned long address, + unsigned long array_size, const unsigned char *buffer) +{ + unsigned int offset; + int ret, saved_page; + u16 val; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + if (saved_page < 0) + return saved_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, + AIR_BPBUS_MODE_ADDR_INCR); + if (ret < 0) + goto restore_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH, + upper_16_bits(address)); + if (ret < 0) + goto restore_page; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW, + lower_16_bits(address)); + if (ret < 0) + goto restore_page; + + for (offset = 0; offset < array_size; offset += 4) { + val = get_unaligned_le16(&buffer[offset + 2]); + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH, val); + if (ret < 0) + goto restore_page; + + val = get_unaligned_le16(&buffer[offset]); + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW, val); + if (ret < 0) + goto restore_page; + } + +restore_page: + if (ret < 0) + dev_err(phydev->dev, "%s 0x%08lx failed: %d\n", __func__, + address, ret); + + return phy_restore_page(phydev, saved_page, ret); +} + +static int en8811h_wait_mcu_ready(struct phy_device *phydev) +{ + int ret, reg_value; + + /* Because of mdio-lock, may have to wait for multiple loads */ + ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, + EN8811H_PHY_FW_STATUS, reg_value, + reg_value == EN8811H_PHY_READY, + 20000, 7500000, true); + if (ret) { + dev_err(phydev->dev, "MCU not ready: 0x%x\n", reg_value); + return ret; + } + + return ret; +} + +int en8811h_read_fw(void **fw, size_t *fwsize) +{ + void *buffer; + int ret; + + buffer = malloc(EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE); + if (!buffer) + return -ENOMEM; + + ret = request_firmware_into_buf_via_script(buffer, + EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE, + "en8811h_load_firmware", fwsize); + if (ret) { + free(buffer); + return ret; + } + + *fw = buffer; + + debug("Found Airoha Firmware.\n"); + + return 0; +} + +static int en8811h_load_firmware(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; + size_t fw_size; + void *buffer; + int ret; + + ret = en8811h_read_fw(&buffer, &fw_size); + if (ret < 0) { + dev_err(phydev->dev, "Failed to get firmware data\n"); + return -EINVAL; + } + + if (fw_size != EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE) { + dev_err(phydev->dev, + "MD32 firmware size mismatch (0x%zx != 0x%x)\n", + fw_size, EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE); + ret = -EINVAL; + goto en8811h_load_firmware_out; + } + + ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_START); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2, + EN8811H_FW_CTRL_2_LOADING, + EN8811H_FW_CTRL_2_LOADING); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = air_write_buf(phydev, AIR_FW_ADDR_DM, EN8811H_MD32_DM_SIZE, + (unsigned char *)buffer); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = air_write_buf(phydev, AIR_FW_ADDR_DSP, EN8811H_MD32_DSP_SIZE, + (unsigned char *)buffer + EN8811H_MD32_DM_SIZE); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2, + EN8811H_FW_CTRL_2_LOADING, 0); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_FINISH); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = en8811h_wait_mcu_ready(phydev); + + air_buckpbus_reg_read(phydev, EN8811H_FW_VERSION, + &priv->firmware_version); + dev_info(phydev->dev, "MD32 firmware version: %08x\n", + priv->firmware_version); + +en8811h_load_firmware_out: + free(buffer); + if (ret < 0) + dev_err(phydev->dev, "Firmware loading failed: %d\n", ret); + + return ret; +} + +static int en8811h_restart_mcu(struct phy_device *phydev) +{ + int ret; + + ret = phy_write_mmd(phydev, 0x1e, 0x8009, 0x0); + if (ret < 0) + return ret; + + ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_START); + if (ret < 0) + return ret; + + return air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_FINISH); +} + +static int air_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + struct en8811h_priv *priv = phydev->priv; + u16 on = 0, blink = 0; + int ret; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + priv->led[index].rules = rules; + + if (rules & BIT(AIR_TRIGGER_NETDEV_FULL_DUPLEX)) + on |= AIR_PHY_LED_ON_FDX; + + if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_10) | BIT(AIR_TRIGGER_NETDEV_LINK))) + on |= AIR_PHY_LED_ON_LINK10; + + if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_100) | BIT(AIR_TRIGGER_NETDEV_LINK))) + on |= AIR_PHY_LED_ON_LINK100; + + if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_1000) | BIT(AIR_TRIGGER_NETDEV_LINK))) + on |= AIR_PHY_LED_ON_LINK1000; + + if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | BIT(AIR_TRIGGER_NETDEV_LINK))) + on |= AIR_PHY_LED_ON_LINK2500; + + if (rules & BIT(AIR_TRIGGER_NETDEV_RX)) { + blink |= AIR_PHY_LED_BLINK_10RX | + AIR_PHY_LED_BLINK_100RX | + AIR_PHY_LED_BLINK_1000RX | + AIR_PHY_LED_BLINK_2500RX; + } + + if (rules & BIT(AIR_TRIGGER_NETDEV_TX)) { + blink |= AIR_PHY_LED_BLINK_10TX | + AIR_PHY_LED_BLINK_100TX | + AIR_PHY_LED_BLINK_1000TX | + AIR_PHY_LED_BLINK_2500TX; + } + + if (blink || on) { + /* switch hw-control on, so led-on and led-blink are off */ + clear_bit(AIR_PHY_LED_STATE_FORCE_ON, + &priv->led[index].state); + clear_bit(AIR_PHY_LED_STATE_FORCE_BLINK, + &priv->led[index].state); + } else { + priv->led[index].rules = 0; + } + + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_ON(index), + AIR_PHY_LED_ON_MASK, on); + + if (ret < 0) + return ret; + + return phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BLINK(index), + blink); +}; + +static int air_led_init(struct phy_device *phydev, u8 index, u8 state, u8 pol) +{ + int val = 0; + int err; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + if (state == AIR_LED_ENABLE) + val |= AIR_PHY_LED_ON_ENABLE; + else + val &= ~AIR_PHY_LED_ON_ENABLE; + + if (pol == AIR_ACTIVE_HIGH) + val |= AIR_PHY_LED_ON_POLARITY; + else + val &= ~AIR_PHY_LED_ON_POLARITY; + + err = phy_write_mmd(phydev, 0x1f, AIR_PHY_LED_ON(index), val); + if (err < 0) + return err; + + return 0; +} + +static int air_leds_init(struct phy_device *phydev, int num, u16 dur, int mode) +{ + struct en8811h_priv *priv = phydev->priv; + int ret, i; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_BLINK, + dur); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_ON, + dur >> 1); + if (ret < 0) + return ret; + + switch (mode) { + case AIR_LED_MODE_DISABLE: + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR, + AIR_PHY_LED_BCR_EXT_CTRL | + AIR_PHY_LED_BCR_MODE_MASK, 0); + break; + case AIR_LED_MODE_USER_DEFINE: + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR, + AIR_PHY_LED_BCR_EXT_CTRL | + AIR_PHY_LED_BCR_CLK_EN, + AIR_PHY_LED_BCR_EXT_CTRL | + AIR_PHY_LED_BCR_CLK_EN); + if (ret < 0) + return ret; + break; + default: + dev_err(phydev->dev, "LED mode %d is not supported\n", mode); + return -EINVAL; + } + + for (i = 0; i < num; ++i) { + ret = air_led_init(phydev, i, AIR_LED_ENABLE, AIR_ACTIVE_HIGH); + if (ret < 0) { + dev_err(phydev->dev, "LED%d init failed: %d\n", i, ret); + return ret; + } + air_led_hw_control_set(phydev, i, priv->led[i].rules); + } + + return 0; +} + +static int en8811h_config(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; + ofnode node = phy_get_ofnode(phydev); + u32 pbus_value = 0; + int ret = 0; + + /* If restart happened in .probe(), no need to restart now */ + if (priv->mcu_needs_restart) { + ret = en8811h_restart_mcu(phydev); + if (ret < 0) + return ret; + } else { + ret = en8811h_load_firmware(phydev); + if (ret) { + dev_err(phydev->dev, "Load firmware fail.\n"); + return ret; + } + /* Next calls to .config() mcu needs to restart */ + priv->mcu_needs_restart = true; + } + + ret = phy_write_mmd(phydev, 0x1e, 0x800c, 0x0); + if (ret < 0) + return ret; + ret = phy_write_mmd(phydev, 0x1e, 0x800d, 0x0); + if (ret < 0) + return ret; + ret = phy_write_mmd(phydev, 0x1e, 0x800e, 0x1101); + if (ret < 0) + return ret; + ret = phy_write_mmd(phydev, 0x1e, 0x800f, 0x0002); + if (ret < 0) + return ret; + + /* Serdes polarity */ + pbus_value = 0; + if (ofnode_read_bool(node, "airoha,pnswap-rx")) + pbus_value |= EN8811H_POLARITY_RX_REVERSE; + else + pbus_value &= ~EN8811H_POLARITY_RX_REVERSE; + if (ofnode_read_bool(node, "airoha,pnswap-tx")) + pbus_value &= ~EN8811H_POLARITY_TX_NORMAL; + else + pbus_value |= EN8811H_POLARITY_TX_NORMAL; + ret = air_buckpbus_reg_modify(phydev, EN8811H_POLARITY, + EN8811H_POLARITY_RX_REVERSE | + EN8811H_POLARITY_TX_NORMAL, pbus_value); + if (ret < 0) + return ret; + + ret = air_leds_init(phydev, EN8811H_LED_COUNT, AIR_PHY_LED_DUR, + AIR_LED_MODE_USER_DEFINE); + if (ret < 0) { + dev_err(phydev->dev, "Failed to disable leds: %d\n", ret); + return ret; + } + + return 0; +} + +static int en8811h_parse_status(struct phy_device *phydev) +{ + int ret = 0, reg_value; + + phydev->duplex = DUPLEX_FULL; + + reg_value = phy_read(phydev, MDIO_DEVAD_NONE, AIR_AUX_CTRL_STATUS); + if (reg_value < 0) + return reg_value; + + switch (reg_value & AIR_AUX_CTRL_STATUS_SPEED_MASK) { + case AIR_AUX_CTRL_STATUS_SPEED_2500: + phydev->speed = SPEED_2500; + break; + case AIR_AUX_CTRL_STATUS_SPEED_1000: + phydev->speed = SPEED_1000; + break; + case AIR_AUX_CTRL_STATUS_SPEED_100: + phydev->speed = SPEED_100; + break; + default: + dev_err(phydev->dev, "Auto-neg error, defaulting to 2500M/FD\n"); + phydev->speed = SPEED_2500; + break; + } + + return ret; +} + +static int en8811h_startup(struct phy_device *phydev) +{ + int ret = 0; + + ret = genphy_update_link(phydev); + if (ret) + return ret; + + return en8811h_parse_status(phydev); +} + +static int en8811h_probe(struct phy_device *phydev) +{ + struct en8811h_priv *priv; + + priv = malloc(sizeof(*priv)); + if (!priv) + return -ENOMEM; + memset(priv, 0, sizeof(*priv)); + + priv->led[0].rules = AIR_DEFAULT_TRIGGER_LED0; + priv->led[1].rules = AIR_DEFAULT_TRIGGER_LED1; + priv->led[2].rules = AIR_DEFAULT_TRIGGER_LED2; + + /* mcu has just restarted after firmware load */ + priv->mcu_needs_restart = false; + + phydev->priv = priv; + + return 0; +} + +static int en8811h_read_page(struct phy_device *phydev) +{ + return phy_read(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS); +} + +static int en8811h_write_page(struct phy_device *phydev, int page) +{ + return phy_write(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS, page); +} + +U_BOOT_PHY_DRIVER(en8811h) = { + .name = "Airoha EN8811H", + .uid = EN8811H_PHY_ID, + .mask = 0x0ffffff0, + .config = &en8811h_config, + .probe = &en8811h_probe, + .read_page = &en8811h_read_page, + .write_page = &en8811h_write_page, + .startup = &en8811h_startup, + .shutdown = &genphy_shutdown, +}; diff --git a/drivers/net/phy/aquantia.c b/drivers/net/phy/aquantia.c index 903fcd667f6..e0deef58f52 100644 --- a/drivers/net/phy/aquantia.c +++ b/drivers/net/phy/aquantia.c @@ -16,7 +16,9 @@ #include <u-boot/crc.h> #include <malloc.h> #include <asm/byteorder.h> -#include <fs.h> +#if (IS_ENABLED(CONFIG_PHY_AQUANTIA_UPLOAD_FW)) +#include <fw_loader.h> +#endif #define AQUNTIA_10G_CTL 0x20 #define AQUNTIA_VENDOR_P1 0xc400 @@ -127,52 +129,29 @@ struct fw_header { #pragma pack() -#if defined(CONFIG_PHY_AQUANTIA_UPLOAD_FW) -static int aquantia_read_fw(u8 **fw_addr, size_t *fw_length) +#if (IS_ENABLED(CONFIG_PHY_AQUANTIA_UPLOAD_FW)) +int __weak aquantia_read_fw(struct phy_device *phydev, + u8 **fw_addr, size_t *fw_length) { - loff_t length, read; int ret; - void *addr = NULL; - - *fw_addr = NULL; - *fw_length = 0; - debug("Loading Aquantia microcode from %s %s\n", - CONFIG_PHY_AQUANTIA_FW_PART, CONFIG_PHY_AQUANTIA_FW_NAME); - ret = fs_set_blk_dev("mmc", CONFIG_PHY_AQUANTIA_FW_PART, FS_TYPE_ANY); - if (ret < 0) - goto cleanup; + u8 *microcode; - ret = fs_size(CONFIG_PHY_AQUANTIA_FW_NAME, &length); - if (ret < 0) - goto cleanup; - - addr = malloc(length); - if (!addr) { - ret = -ENOMEM; - goto cleanup; + microcode = malloc(CONFIG_PHY_AQUANTIA_FW_MAX_SIZE); + if (!microcode) { + printf("Failed to allocate memory for firmware\n"); + return -ENOMEM; } - ret = fs_set_blk_dev("mmc", CONFIG_PHY_AQUANTIA_FW_PART, FS_TYPE_ANY); - if (ret < 0) - goto cleanup; - - ret = fs_read(CONFIG_PHY_AQUANTIA_FW_NAME, (ulong)addr, 0, length, - &read); - if (ret < 0) - goto cleanup; - - *fw_addr = addr; - *fw_length = length; - debug("Found Aquantia microcode.\n"); - -cleanup: - if (ret < 0) { - printf("loading firmware file %s %s failed with error %d\n", - CONFIG_PHY_AQUANTIA_FW_PART, - CONFIG_PHY_AQUANTIA_FW_NAME, ret); - free(addr); + ret = request_firmware_into_buf_via_script( + microcode, CONFIG_PHY_AQUANTIA_FW_MAX_SIZE, + "aqr_phy_load_firmware", fw_length); + if (ret) { + free(microcode); + return ret; } - return ret; + + *fw_addr = microcode; + return 1; } /* load data into the phy's memory */ @@ -218,27 +197,26 @@ static u32 unpack_u24(const u8 *data) return (data[2] << 16) + (data[1] << 8) + data[0]; } -static int aquantia_upload_firmware(struct phy_device *phydev) +static int aquantia_do_upload_firmware(struct phy_device *phydev, + const u8 *addr, size_t fw_length) { int ret; - u8 *addr = NULL; - size_t fw_length = 0; u16 calculated_crc, read_crc; char version[VERSION_STRING_SIZE]; u32 primary_offset, iram_offset, iram_size, dram_offset, dram_size; const struct fw_header *header; - ret = aquantia_read_fw(&addr, &fw_length); - if (ret != 0) - return ret; + if (!addr || !fw_length) { + printf("%s: Invalid firmware data\n", phydev->dev->name); + return -EINVAL; + } - read_crc = (addr[fw_length - 2] << 8) | addr[fw_length - 1]; + read_crc = (addr[fw_length - 2] << 8) | addr[fw_length - 1]; calculated_crc = crc16_ccitt(0, addr, fw_length - 2); if (read_crc != calculated_crc) { printf("%s bad firmware crc: file 0x%04x calculated 0x%04x\n", phydev->dev->name, read_crc, calculated_crc); - ret = -EINVAL; - goto done; + return -EINVAL; } /* Find the DRAM and IRAM sections within the firmware file. */ @@ -268,14 +246,14 @@ static int aquantia_upload_firmware(struct phy_device *phydev) ret = aquantia_load_memory(phydev, DRAM_BASE_ADDR, &addr[dram_offset], dram_size); if (ret != 0) - goto done; + return ret; debug("loading iram 0x%08x from offset=%d size=%d\n", IRAM_BASE_ADDR, iram_offset, iram_size); ret = aquantia_load_memory(phydev, IRAM_BASE_ADDR, &addr[iram_offset], iram_size); if (ret != 0) - goto done; + return ret; /* make sure soft reset and low power mode are clear */ phy_write(phydev, MDIO_MMD_VEND1, GLOBAL_STANDARD_CONTROL, 0); @@ -289,8 +267,23 @@ static int aquantia_upload_firmware(struct phy_device *phydev) phy_write(phydev, MDIO_MMD_VEND1, UP_CONTROL, UP_RUN_STALL_OVERRIDE); printf("%s firmware loading done.\n", phydev->dev->name); -done: - free(addr); + return 0; +} + +static int aquantia_upload_firmware(struct phy_device *phydev) +{ + int ret, fwrc; + u8 *addr = NULL; + size_t fw_length; + + fwrc = aquantia_read_fw(phydev, &addr, &fw_length); + if (fwrc < 0) + return fwrc; + + ret = aquantia_do_upload_firmware(phydev, addr, fw_length); + if (fwrc > 0) + free(addr); + return ret; } #else diff --git a/net/tftp.c b/net/tftp.c index 1760877107f..3b0f4cd2006 100644 --- a/net/tftp.c +++ b/net/tftp.c @@ -714,10 +714,9 @@ static void tftp_timeout_handler(void) } } -static int tftp_init_load_addr(void) +static void tftp_init_load_addr(void) { tftp_load_addr = image_load_addr; - return 0; } static int saved_tftp_block_size_option; @@ -901,13 +900,7 @@ void tftp_start(enum proto_t protocol) } else #endif { - if (tftp_init_load_addr()) { - eth_halt(); - net_set_state(NETLOOP_FAIL); - puts("\nTFTP error: "); - puts("trying to overwrite reserved memory...\n"); - return; - } + tftp_init_load_addr(); printf("Load address: 0x%lx\n", tftp_load_addr); puts("Loading: *\b"); tftp_state = STATE_SEND_RRQ; @@ -953,12 +946,7 @@ void tftp_start_server(void) { tftp_filename[0] = 0; - if (tftp_init_load_addr()) { - eth_halt(); - net_set_state(NETLOOP_FAIL); - puts("\nTFTP error: trying to overwrite reserved memory...\n"); - return; - } + tftp_init_load_addr(); printf("Using %s device\n", eth_get_name()); printf("Listening for TFTP transfer on %pI4\n", &net_ip); printf("Load address: 0x%lx\n", tftp_load_addr); |
