diff --git a/patches/uboot/0001-net-mv88e6xxx-Add-reset-gpios-support.patch b/patches/uboot/0001-net-mv88e6xxx-Add-reset-gpios-support.patch new file mode 100644 index 00000000..2cd90250 --- /dev/null +++ b/patches/uboot/0001-net-mv88e6xxx-Add-reset-gpios-support.patch @@ -0,0 +1,156 @@ +From 4348261902b60f93c217f07069a2b5613a933c17 Mon Sep 17 00:00:00 2001 +From: Tobias Waldekranz +Date: Tue, 17 Oct 2023 13:15:58 +0200 +Subject: [PATCH 1/5] net: mv88e6xxx: Add reset-gpios support + +If the switch's RESETn signal is under the CPUs control, release it +before starting the probe. + +Signed-off-by: Tobias Waldekranz +--- + drivers/net/mv88e6xxx.c | 68 ++++++++++++++++++++++++++++++++++++----- + 1 file changed, 61 insertions(+), 7 deletions(-) + +diff --git a/drivers/net/mv88e6xxx.c b/drivers/net/mv88e6xxx.c +index 64e860e324..1bf7cfd6a6 100644 +--- a/drivers/net/mv88e6xxx.c ++++ b/drivers/net/mv88e6xxx.c +@@ -33,6 +33,8 @@ + #include + #include + ++#include ++ + /* Device addresses */ + #define DEVADDR_PHY(p) (p) + #define DEVADDR_SERDES 0x0F +@@ -146,6 +148,8 @@ struct mv88e6xxx_priv { + int port_reg_base; /* Base of the switch port registers */ + u8 global1; /* Offset of Switch Global 1 registers */ + u8 global2; /* Offset of Switch Global 2 registers */ ++ ++ struct gpio_desc reset; + }; + + /* Wait for the current SMI indirect command to complete */ +@@ -675,6 +679,37 @@ static int mv88e6xxx_probe_mdio(struct udevice *dev) + return ret; + } + ++static int mv88e6xxx_probe_reset(struct udevice *dev) ++{ ++ struct mv88e6xxx_priv *priv = dev_get_priv(dev); ++ int err; ++ ++ if (!CONFIG_IS_ENABLED(DM_GPIO)) ++ return 0; ++ ++ err = gpio_request_by_name(dev, "reset-gpios", 0, ++ &priv->reset, GPIOD_IS_OUT); ++ if (err && (err != -ENOENT)) ++ return err; ++ ++ if (dm_gpio_is_valid(&priv->reset)) { ++ dm_gpio_set_value(&priv->reset, 0); ++ mdelay(10); ++ } ++ ++ return 0; ++} ++ ++static void mv88e6xxx_remove_reset(struct udevice *dev) ++{ ++ struct mv88e6xxx_priv *priv = dev_get_priv(dev); ++ ++ if (CONFIG_IS_ENABLED(DM_GPIO) && dm_gpio_is_valid(&priv->reset)) { ++ dm_gpio_set_value(&priv->reset, 1); ++ dm_gpio_free(dev, &priv->reset); ++ } ++} ++ + static int mv88e6xxx_probe(struct udevice *dev) + { + struct dsa_pdata *dsa_pdata = dev_get_uclass_plat(dev); +@@ -684,17 +719,22 @@ static int mv88e6xxx_probe(struct udevice *dev) + if (ofnode_valid(dev_ofnode(dev)) && + !ofnode_is_enabled(dev_ofnode(dev))) { + dev_dbg(dev, "switch disabled\n"); +- return -ENODEV; ++ ret = -ENODEV; ++ goto err; + } + ++ ret = mv88e6xxx_probe_reset(dev); ++ if (ret) ++ goto err; ++ + /* probe internal mdio bus */ + ret = mv88e6xxx_probe_mdio(dev); + if (ret) +- return ret; ++ goto err_remove_reset; + + ret = mv88e6xxx_priv_reg_offs_pre_init(dev); + if (ret) +- return ret; ++ goto err_remove_reset; + + dev_dbg(dev, "ID=0x%x PORT_BASE=0x%02x GLOBAL1=0x%02x GLOBAL2=0x%02x\n", + priv->id, priv->port_reg_base, priv->global1, priv->global2); +@@ -716,28 +756,40 @@ static int mv88e6xxx_probe(struct udevice *dev) + priv->port_count = 7; + break; + default: +- return -ENODEV; ++ ret = -ENODEV; ++ goto err_remove_reset; + } + + ret = mv88e6xxx_switch_reset(dev); + if (ret < 0) +- return ret; ++ goto err_remove_reset; + + if (mv88e6xxx_6352_family(dev)) { + val = mv88e6xxx_get_cmode(dev, dsa_pdata->cpu_port); + if (val < 0) +- return val; ++ goto err_remove_reset; + /* initialize serdes */ + if (val == PORT_REG_STATUS_CMODE_100BASE_X || + val == PORT_REG_STATUS_CMODE_1000BASE_X || + val == PORT_REG_STATUS_CMODE_SGMII) { + ret = mv88e6xxx_serdes_init(dev); + if (ret < 0) +- return ret; ++ goto err_remove_reset; + } + } + + return 0; ++ ++err_remove_reset: ++ mv88e6xxx_remove_reset(dev); ++err: ++ return ret; ++} ++ ++static int mv88e6xxx_remove(struct udevice *dev) ++{ ++ mv88e6xxx_remove_reset(dev); ++ return 0; + } + + static const struct udevice_id mv88e6xxx_ids[] = { +@@ -750,6 +802,8 @@ U_BOOT_DRIVER(mv88e6xxx) = { + .id = UCLASS_DSA, + .of_match = mv88e6xxx_ids, + .probe = mv88e6xxx_probe, ++ .remove = mv88e6xxx_remove, + .ops = &mv88e6xxx_dsa_ops, + .priv_auto = sizeof(struct mv88e6xxx_priv), ++ .flags = DM_FLAG_OS_PREPARE, + }; +-- +2.34.1 + diff --git a/patches/uboot/0002-net-mv88e6xxx-Support-clause-45-addressing-on-intern.patch b/patches/uboot/0002-net-mv88e6xxx-Support-clause-45-addressing-on-intern.patch new file mode 100644 index 00000000..d6c3d692 --- /dev/null +++ b/patches/uboot/0002-net-mv88e6xxx-Support-clause-45-addressing-on-intern.patch @@ -0,0 +1,181 @@ +From d87aea2f0d0acfa0d54adb20a980256c0ba95023 Mon Sep 17 00:00:00 2001 +From: Tobias Waldekranz +Date: Tue, 17 Oct 2023 13:20:19 +0200 +Subject: [PATCH 2/5] net: mv88e6xxx: Support clause 45 addressing on internal + MDIO bus + +This is needed to access SERDES registers on newer chips, like the +6393X. + +Signed-off-by: Tobias Waldekranz +--- + drivers/net/mv88e6xxx.c | 113 +++++++++++++++++++++++++++++++++++++--- + 1 file changed, 106 insertions(+), 7 deletions(-) + +diff --git a/drivers/net/mv88e6xxx.c b/drivers/net/mv88e6xxx.c +index 1bf7cfd6a6..eddac332e0 100644 +--- a/drivers/net/mv88e6xxx.c ++++ b/drivers/net/mv88e6xxx.c +@@ -114,6 +114,9 @@ + #define SMI_CMD_CLAUSE_22 BIT(12) + #define SMI_CMD_CLAUSE_22_OP_READ (2 << 10) + #define SMI_CMD_CLAUSE_22_OP_WRITE (1 << 10) ++#define SMI_CMD_CLAUSE_45_OP_WADDR (0 << 10) ++#define SMI_CMD_CLAUSE_45_OP_WDATA (1 << 10) ++#define SMI_CMD_CLAUSE_45_OP_RDATA (3 << 10) + #define SMI_CMD_ADDR_SHIFT 5 + #define SMI_CMD_ADDR_MASK 0x1f + #define SMI_CMD_REG_SHIFT 0 +@@ -126,6 +129,18 @@ + (SMI_BUSY | SMI_CMD_CLAUSE_22 | SMI_CMD_CLAUSE_22_OP_WRITE) | \ + (((addr) & SMI_CMD_ADDR_MASK) << SMI_CMD_ADDR_SHIFT) | \ + (((reg) & SMI_CMD_REG_MASK) << SMI_CMD_REG_SHIFT) ++#define XSMI_CMD_WADDR(port, dev) \ ++ (SMI_BUSY | SMI_CMD_CLAUSE_45_OP_WADDR) | \ ++ (((port) & SMI_CMD_ADDR_MASK) << SMI_CMD_ADDR_SHIFT) | \ ++ (((dev) & SMI_CMD_REG_MASK) << SMI_CMD_REG_SHIFT) ++#define XSMI_CMD_WDATA(port, dev) \ ++ (SMI_BUSY | SMI_CMD_CLAUSE_45_OP_WDATA) | \ ++ (((port) & SMI_CMD_ADDR_MASK) << SMI_CMD_ADDR_SHIFT) | \ ++ (((dev) & SMI_CMD_REG_MASK) << SMI_CMD_REG_SHIFT) ++#define XSMI_CMD_RDATA(port, dev) \ ++ (SMI_BUSY | SMI_CMD_CLAUSE_45_OP_RDATA) | \ ++ (((port) & SMI_CMD_ADDR_MASK) << SMI_CMD_ADDR_SHIFT) | \ ++ (((dev) & SMI_CMD_REG_MASK) << SMI_CMD_REG_SHIFT) + + /* ID register values for different switch models */ + #define PORT_SWITCH_ID_6020 0x0200 +@@ -273,7 +288,41 @@ static int mv88e6xxx_phy_wait(struct udevice *dev) + return -ETIMEDOUT; + } + +-static int mv88e6xxx_phy_read_indirect(struct udevice *dev, int phyad, int devad, int reg) ++static int mv88e6xxx_phy_read_indirect_c45(struct udevice *dev, int phyad, int devad, int reg) ++{ ++ struct mv88e6xxx_priv *priv = dev_get_priv(dev); ++ int res; ++ ++ res = mv88e6xxx_reg_write(dev, priv->global2, ++ GLOBAL2_REG_PHY_DATA, reg); ++ if (res < 0) ++ return res; ++ ++ res = mv88e6xxx_reg_write(dev, priv->global2, ++ GLOBAL2_REG_PHY_CMD, ++ XSMI_CMD_WADDR(phyad, devad)); ++ if (res < 0) ++ return res; ++ ++ res = mv88e6xxx_phy_wait(dev); ++ if (res < 0) ++ return res; ++ ++ res = mv88e6xxx_reg_write(dev, priv->global2, ++ GLOBAL2_REG_PHY_CMD, ++ XSMI_CMD_RDATA(phyad, devad)); ++ if (res < 0) ++ return res; ++ ++ res = mv88e6xxx_phy_wait(dev); ++ if (res < 0) ++ return res; ++ ++ return mv88e6xxx_reg_read(dev, priv->global2, ++ GLOBAL2_REG_PHY_DATA); ++} ++ ++static int mv88e6xxx_phy_read_indirect_c22(struct udevice *dev, int phyad, int reg) + { + struct mv88e6xxx_priv *priv = dev_get_priv(dev); + int res; +@@ -293,8 +342,16 @@ static int mv88e6xxx_phy_read_indirect(struct udevice *dev, int phyad, int devad + GLOBAL2_REG_PHY_DATA); + } + +-static int mv88e6xxx_phy_write_indirect(struct udevice *dev, int phyad, +- int devad, int reg, u16 data) ++static int mv88e6xxx_phy_read_indirect(struct udevice *dev, int phyad, int devad, int reg) ++{ ++ if (devad == MDIO_DEVAD_NONE) ++ return mv88e6xxx_phy_read_indirect_c22(dev, phyad, reg); ++ ++ return mv88e6xxx_phy_read_indirect_c45(dev, phyad, devad, reg); ++} ++ ++static int mv88e6xxx_phy_write_indirect_c22(struct udevice *dev, int phyad, ++ int reg, u16 data) + { + struct mv88e6xxx_priv *priv = dev_get_priv(dev); + int res; +@@ -315,6 +372,50 @@ static int mv88e6xxx_phy_write_indirect(struct udevice *dev, int phyad, + return mv88e6xxx_phy_wait(dev); + } + ++static int mv88e6xxx_phy_write_indirect_c45(struct udevice *dev, int phyad, ++ int devad, int reg, u16 data) ++{ ++ struct mv88e6xxx_priv *priv = dev_get_priv(dev); ++ int res; ++ ++ res = mv88e6xxx_reg_write(dev, priv->global2, ++ GLOBAL2_REG_PHY_DATA, reg); ++ if (res < 0) ++ return res; ++ ++ res = mv88e6xxx_reg_write(dev, priv->global2, ++ GLOBAL2_REG_PHY_CMD, ++ XSMI_CMD_WADDR(phyad, devad)); ++ if (res < 0) ++ return res; ++ ++ res = mv88e6xxx_phy_wait(dev); ++ if (res < 0) ++ return res; ++ ++ res = mv88e6xxx_reg_write(dev, priv->global2, ++ GLOBAL2_REG_PHY_DATA, data); ++ if (res < 0) ++ return res; ++ ++ res = mv88e6xxx_reg_write(dev, priv->global2, ++ GLOBAL2_REG_PHY_CMD, ++ XSMI_CMD_WDATA(phyad, devad)); ++ if (res < 0) ++ return res; ++ ++ return mv88e6xxx_phy_wait(dev); ++} ++ ++static int mv88e6xxx_phy_write_indirect(struct udevice *dev, int phyad, ++ int devad, int reg, u16 data) ++{ ++ if (devad == MDIO_DEVAD_NONE) ++ return mv88e6xxx_phy_write_indirect_c22(dev, phyad, reg, data); ++ ++ return mv88e6xxx_phy_write_indirect_c45(dev, phyad, devad, reg, data); ++} ++ + /* Wrapper function to make calls to phy_read_indirect simpler */ + static int mv88e6xxx_phy_read(struct udevice *dev, int phy, int reg) + { +@@ -493,15 +594,13 @@ static int mv88e6xxx_priv_reg_offs_pre_init(struct udevice *dev) + + static int mv88e6xxx_mdio_read(struct udevice *dev, int addr, int devad, int reg) + { +- return mv88e6xxx_phy_read_indirect(dev->parent, DEVADDR_PHY(addr), +- MDIO_DEVAD_NONE, reg); ++ return mv88e6xxx_phy_read_indirect(dev->parent, DEVADDR_PHY(addr), devad, reg); + } + + static int mv88e6xxx_mdio_write(struct udevice *dev, int addr, int devad, + int reg, u16 val) + { +- return mv88e6xxx_phy_write_indirect(dev->parent, DEVADDR_PHY(addr), +- MDIO_DEVAD_NONE, reg, val); ++ return mv88e6xxx_phy_write_indirect(dev->parent, DEVADDR_PHY(addr), devad, reg, val); + } + + static const struct mdio_ops mv88e6xxx_mdio_ops = { +-- +2.34.1 + diff --git a/patches/uboot/0003-net-mv88e6xxx-Add-support-for-6393X.patch b/patches/uboot/0003-net-mv88e6xxx-Add-support-for-6393X.patch new file mode 100644 index 00000000..fd589454 --- /dev/null +++ b/patches/uboot/0003-net-mv88e6xxx-Add-support-for-6393X.patch @@ -0,0 +1,216 @@ +From c801299f06f0e5c95bd597fe0ce4ad6c3df9a444 Mon Sep 17 00:00:00 2001 +From: Tobias Waldekranz +Date: Tue, 17 Oct 2023 13:22:00 +0200 +Subject: [PATCH 3/5] net: mv88e6xxx: Add support for 6393X + +Only built-in copper PHYs are supported as access ports. Support +10GBASE-R SERDES connection to CPU. + +Signed-off-by: Tobias Waldekranz +--- + drivers/net/mv88e6xxx.c | 134 ++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 130 insertions(+), 4 deletions(-) + +diff --git a/drivers/net/mv88e6xxx.c b/drivers/net/mv88e6xxx.c +index eddac332e0..4a4a3ae0b1 100644 +--- a/drivers/net/mv88e6xxx.c ++++ b/drivers/net/mv88e6xxx.c +@@ -74,6 +74,7 @@ + #define PORT_REG_STATUS_SPEED_1000 2 + + #define PORT_REG_STATUS_CMODE_MASK 0xF ++#define PORT_REG_STATUS_CMODE_10GBASE_R 0xd + #define PORT_REG_STATUS_CMODE_SGMII 0xa + #define PORT_REG_STATUS_CMODE_1000BASE_X 0x9 + #define PORT_REG_STATUS_CMODE_100BASE_X 0x8 +@@ -155,6 +156,7 @@ + #define PORT_SWITCH_ID_6250 0x2500 + #define PORT_SWITCH_ID_6320 0x1150 + #define PORT_SWITCH_ID_6352 0x3520 ++#define PORT_SWITCH_ID_6393 0x3930 + + struct mv88e6xxx_priv { + int smi_addr; +@@ -475,6 +477,17 @@ static bool mv88e6xxx_6352_family(struct udevice *dev) + return false; + } + ++static bool mv88e6xxx_6393_family(struct udevice *dev) ++{ ++ struct mv88e6xxx_priv *priv = dev_get_priv(dev); ++ ++ switch (priv->id) { ++ case PORT_SWITCH_ID_6393: ++ return true; ++ } ++ return false; ++} ++ + static int mv88e6xxx_get_cmode(struct udevice *dev, u8 port) + { + int res; +@@ -529,7 +542,7 @@ static int mv88e6xxx_switch_reset(struct udevice *dev) + return 0; + } + +-static int mv88e6xxx_serdes_init(struct udevice *dev) ++static int mv88e6352_serdes_init(struct udevice *dev) + { + int val; + +@@ -549,6 +562,107 @@ static int mv88e6xxx_serdes_init(struct udevice *dev) + return 0; + } + ++static int mv88e6393_serdes_erratum_4_6(struct udevice *dev, int port) ++{ ++ int val; ++ ++ /* mv88e6393x family errata 4.6: ++ * Cannot clear PwrDn bit on SERDES if device is configured CPU_MGD ++ * mode or P0_mode is configured for [x]MII. ++ * Workaround: Set SERDES register 4.F002 bit 5=0 and bit 15=1. ++ * ++ * It seems that after this workaround the SERDES is automatically ++ * powered up (the bit is cleared), so power it down. ++ */ ++ val = mv88e6xxx_phy_read_indirect(dev, port, MDIO_MMD_PHYXS, 0xf002); ++ if (val < 0) ++ return val; ++ ++ val &= ~BIT(5); ++ val |= MDIO_CTRL1_RESET; ++ return mv88e6xxx_phy_write_indirect(dev, port, MDIO_MMD_PHYXS, 0xf002, val); ++} ++ ++static int mv88e6393_serdes_erratum_5_2(struct udevice *dev, int port, int cmode) ++{ ++ static const struct { ++ u16 dev, reg, val, mask; ++ } fixes[] = { ++ { MDIO_MMD_VEND1, 0x8093, 0xcb5a, 0xffff }, ++ { MDIO_MMD_VEND1, 0x8171, 0x7088, 0xffff }, ++ { MDIO_MMD_VEND1, 0x80c9, 0x311a, 0xffff }, ++ { MDIO_MMD_VEND1, 0x80a2, 0x8000, 0xff7f }, ++ { MDIO_MMD_VEND1, 0x80a9, 0x0000, 0xfff0 }, ++ { MDIO_MMD_VEND1, 0x80a3, 0x0000, 0xf8ff }, ++ { MDIO_MMD_PHYXS, 0xf002, 0x8000, 0x8000 } ++ }; ++ int val, i; ++ ++ /* mv88e6393x family errata 5.2: ++ * For optimal signal integrity the following sequence should be applied ++ * to SERDES operating in 10G mode. These registers only apply to 10G ++ * operation and have no effect on other speeds. ++ */ ++ if (cmode != PORT_REG_STATUS_CMODE_10GBASE_R) ++ return 0; ++ ++ for (i = 0; i < ARRAY_SIZE(fixes); ++i) { ++ val = mv88e6xxx_phy_read_indirect(dev, port, fixes[i].dev, fixes[i].reg); ++ if (val < 0) ++ return val; ++ ++ val &= ~fixes[i].mask; ++ val |= fixes[i].val; ++ ++ val = mv88e6xxx_phy_write_indirect(dev, port, fixes[i].dev, fixes[i].reg, val); ++ if (val < 0) ++ return val; ++ } ++ ++ return 0; ++} ++ ++static int mv88e6393_serdes_init(struct udevice *dev, int port, int cmode) ++{ ++ u16 base; ++ int val; ++ ++ val = mv88e6393_serdes_erratum_4_6(dev, port); ++ if (val < 0) ++ return val; ++ ++ val = mv88e6393_serdes_erratum_5_2(dev, port, cmode); ++ if (val < 0) ++ return val; ++ ++ switch (cmode) { ++ case PORT_REG_STATUS_CMODE_10GBASE_R: ++ base = 0x1000; ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ val = mv88e6xxx_phy_read_indirect(dev, port, MDIO_MMD_PHYXS, base); ++ if (val < 0) ++ return val; ++ ++ val &= ~MDIO_CTRL1_LPOWER; ++ val |= MDIO_CTRL1_RESET; ++ ++ return mv88e6xxx_phy_write_indirect(dev, port, MDIO_MMD_PHYXS, base, val); ++} ++ ++static int mv88e6xxx_serdes_init(struct udevice *dev, int cpu_port, int cmode) ++{ ++ if (mv88e6xxx_6352_family(dev)) ++ return mv88e6352_serdes_init(dev); ++ else if (mv88e6xxx_6393_family(dev)) ++ return mv88e6393_serdes_init(dev, cpu_port, cmode); ++ ++ return -EOPNOTSUPP; ++} ++ + /* + * This function is used to pre-configure the required register + * offsets, so that the indirect register access to the PHY registers +@@ -569,6 +683,15 @@ static int mv88e6xxx_priv_reg_offs_pre_init(struct udevice *dev) + */ + priv->port_reg_base = 0x10; + priv->id = mv88e6xxx_get_switch_id(dev); ++ if (priv->id == 0xfff0) { ++ /* ++ * Look for a newer device in which ports start at ++ * offset 0 (6390/6393 and compatible switches). ++ */ ++ priv->port_reg_base = 0; ++ priv->id = mv88e6xxx_get_switch_id(dev); ++ } ++ + if (priv->id != 0xfff0) { + priv->global1 = 0x1B; + priv->global2 = 0x1C; +@@ -844,6 +967,7 @@ static int mv88e6xxx_probe(struct udevice *dev) + case PORT_SWITCH_ID_6176: + case PORT_SWITCH_ID_6240: + case PORT_SWITCH_ID_6352: ++ case PORT_SWITCH_ID_6393: + priv->port_count = 11; + break; + case PORT_SWITCH_ID_6020: +@@ -863,15 +987,17 @@ static int mv88e6xxx_probe(struct udevice *dev) + if (ret < 0) + goto err_remove_reset; + +- if (mv88e6xxx_6352_family(dev)) { ++ if (mv88e6xxx_6352_family(dev) || ++ mv88e6xxx_6393_family(dev)) { + val = mv88e6xxx_get_cmode(dev, dsa_pdata->cpu_port); + if (val < 0) + goto err_remove_reset; + /* initialize serdes */ + if (val == PORT_REG_STATUS_CMODE_100BASE_X || + val == PORT_REG_STATUS_CMODE_1000BASE_X || +- val == PORT_REG_STATUS_CMODE_SGMII) { +- ret = mv88e6xxx_serdes_init(dev); ++ val == PORT_REG_STATUS_CMODE_SGMII || ++ val == PORT_REG_STATUS_CMODE_10GBASE_R) { ++ ret = mv88e6xxx_serdes_init(dev, dsa_pdata->cpu_port, val); + if (ret < 0) + goto err_remove_reset; + } +-- +2.34.1 + diff --git a/patches/uboot/0004-i2c-pcf8575-Properly-address-chip.patch b/patches/uboot/0004-i2c-pcf8575-Properly-address-chip.patch new file mode 100644 index 00000000..af84a1c7 --- /dev/null +++ b/patches/uboot/0004-i2c-pcf8575-Properly-address-chip.patch @@ -0,0 +1,54 @@ +From 5013dadfd3ba92eba930f8ea0f88c0f34b8c55a5 Mon Sep 17 00:00:00 2001 +From: Tobias Waldekranz +Date: Fri, 20 Oct 2023 13:19:41 +0200 +Subject: [PATCH 4/5] i2c: pcf8575: Properly address chip + +These devices hold a simple shift register that is accessed without +any "register" setup. + +Signed-off-by: Tobias Waldekranz +--- + drivers/gpio/pcf8575_gpio.c | 16 ++++++++++++++-- + 1 file changed, 14 insertions(+), 2 deletions(-) + +diff --git a/drivers/gpio/pcf8575_gpio.c b/drivers/gpio/pcf8575_gpio.c +index d5930d941f..bc0275b901 100644 +--- a/drivers/gpio/pcf8575_gpio.c ++++ b/drivers/gpio/pcf8575_gpio.c +@@ -49,9 +49,15 @@ static int pcf8575_i2c_write(struct udevice *dev, unsigned int word) + { + struct dm_i2c_chip *chip = dev_get_parent_plat(dev); + u8 buf[2] = { word & 0xff, word >> 8, }; ++ struct i2c_msg wmsg = { ++ .addr = chip->chip_addr, ++ .flags = 0, ++ .buf = buf, ++ .len = dev_get_driver_data(dev), ++ }; + int ret; + +- ret = dm_i2c_write(dev, 0, buf, dev_get_driver_data(dev)); ++ ret = dm_i2c_xfer(dev, &wmsg, 1); + if (ret) + printf("%s i2c write failed to addr %x\n", __func__, + chip->chip_addr); +@@ -63,9 +69,15 @@ static int pcf8575_i2c_read(struct udevice *dev) + { + struct dm_i2c_chip *chip = dev_get_parent_plat(dev); + u8 buf[2] = {0x00, 0x00}; ++ struct i2c_msg rmsg = { ++ .addr = chip->chip_addr, ++ .flags = I2C_M_RD, ++ .buf = buf, ++ .len = dev_get_driver_data(dev), ++ }; + int ret; + +- ret = dm_i2c_read(dev, 0, buf, dev_get_driver_data(dev)); ++ ret = dm_i2c_xfer(dev, &rmsg, 1); + if (ret) { + printf("%s i2c read failed from addr %x\n", __func__, + chip->chip_addr); +-- +2.34.1 + diff --git a/patches/uboot/0005-arm64-mvebu-a8k-Add-eFuse-support.patch b/patches/uboot/0005-arm64-mvebu-a8k-Add-eFuse-support.patch new file mode 100644 index 00000000..295d4a15 --- /dev/null +++ b/patches/uboot/0005-arm64-mvebu-a8k-Add-eFuse-support.patch @@ -0,0 +1,576 @@ +From c0495a607652aef38a4d4480ebdb6dd45e3dd9b9 Mon Sep 17 00:00:00 2001 +From: Tobias Waldekranz +Date: Tue, 24 Oct 2023 22:23:44 +0200 +Subject: [PATCH 5/5] arm64: mvebu: a8k: Add eFuse support + +Add support for reading and burning LD and HD fuses. Use a specialized +commandline implementation over the standard "fuse" command because: + +- We want to reference fuses by the die and module they belong to, + rather than a plain integer bank ID. + +- Our lines are wider than 32 bits, meaning that we would have to use + similar tactics as arch/arm/mach-mvebu/efuse.c in order to support + fuse programming, which is cumbersome. + +Signed-off-by: Tobias Waldekranz +--- + arch/arm/mach-mvebu/Kconfig | 7 + + arch/arm/mach-mvebu/armada8k/Makefile | 2 + + arch/arm/mach-mvebu/armada8k/efuse.c | 516 ++++++++++++++++++++++++++ + 3 files changed, 525 insertions(+) + create mode 100644 arch/arm/mach-mvebu/armada8k/efuse.c + +diff --git a/arch/arm/mach-mvebu/Kconfig b/arch/arm/mach-mvebu/Kconfig +index ac484c73f6..45584b4282 100644 +--- a/arch/arm/mach-mvebu/Kconfig ++++ b/arch/arm/mach-mvebu/Kconfig +@@ -421,6 +421,13 @@ config MVEBU_EFUSE_VHV_GPIO_ACTIVE_LOW + bool "VHV_Enable GPIO is Active Low" + depends on MVEBU_EFUSE_VHV_GPIO != "" + ++config MVEBU_8K_EFUSE ++ bool "Enable eFuse support" ++ depends on ARMADA_8K ++ select HEX_DUMP ++ help ++ Enable support for reading and writing eFuses on mvebu SoCs. ++ + config SECURED_MODE_IMAGE + bool "Build image for trusted boot" + default false +diff --git a/arch/arm/mach-mvebu/armada8k/Makefile b/arch/arm/mach-mvebu/armada8k/Makefile +index 0a4756717a..c1be059b85 100644 +--- a/arch/arm/mach-mvebu/armada8k/Makefile ++++ b/arch/arm/mach-mvebu/armada8k/Makefile +@@ -3,3 +3,5 @@ + # Copyright (C) 2016 Stefan Roese + + obj-y = cpu.o cache_llc.o dram.o ++ ++obj-$(CONFIG_MVEBU_8K_EFUSE) += efuse.o +diff --git a/arch/arm/mach-mvebu/armada8k/efuse.c b/arch/arm/mach-mvebu/armada8k/efuse.c +new file mode 100644 +index 0000000000..5438d4466d +--- /dev/null ++++ b/arch/arm/mach-mvebu/armada8k/efuse.c +@@ -0,0 +1,516 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2015-2016 Reinhard Pfau ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++#include ++#include ++ ++enum mvebu_efuse_mod { ++ MVEBU_EFUSE_MOD_LD0, ++ MVEBU_EFUSE_MOD_LD1, ++ MVEBU_EFUSE_MOD_HD, ++}; ++ ++struct mvebu_efuse_ld { ++ u32 array[256 / 32]; ++#define MVEBU_EFUSE_LD_PARITY_BIT BIT(31) ++ u32 burn; ++}; ++ ++struct mvebu_efuse_hd_line { ++ u32 array[2]; ++#define MVEBU_EFUSE_HD_ECC_MASK 0xfe000000 ++ u32 lock; ++ u32 pad; ++}; ++ ++struct mvebu_efuse_hd { ++#define MVEBU_EFUSE_HD_LINES 64 ++ struct mvebu_efuse_hd_line line[MVEBU_EFUSE_HD_LINES]; ++}; ++ ++struct mvebu_efuse_die { ++ union { ++ struct { ++ u32 resvd0; ++ u32 resvd1; ++ u32 ctrl; ++ }; ++ ++ u8 pad_ctrl[0xf00]; ++ }; ++ ++ union { ++ struct mvebu_efuse_ld ld; ++ u8 pad_ld[0x100]; ++ }; ++ ++ union { ++ struct mvebu_efuse_hd hd; ++ }; ++}; ++ ++static const struct mvebu_efuse_die __iomem *mvebu_efuse_dies[] = { ++ (void __iomem *)0xf06f8000, ++ (void __iomem *)0xf2400000, ++}; ++ ++static void mvebu_efuse_program_set(const struct mvebu_efuse_die __iomem *die, ++ bool enable) ++{ ++ if (enable) ++ setbits_le32(&die->ctrl, BIT(31)); ++ else ++ clrbits_le32(&die->ctrl, BIT(31)); ++} ++ ++static void mvebu_efuse_secure_set(const struct mvebu_efuse_die __iomem *die, ++ bool enable) ++{ ++ if (enable) ++ setbits_le32(&die->ctrl, BIT(7)); ++ else ++ clrbits_le32(&die->ctrl, BIT(7)); ++} ++ ++static void mvebu_efuse_ld_select(const struct mvebu_efuse_die __iomem *die, ++ bool ld1) ++{ ++ if (ld1) ++ setbits_le32(&die->ctrl, BIT(6)); ++ else ++ clrbits_le32(&die->ctrl, BIT(6)); ++} ++ ++static void mvebu_efuse_ld_read(const struct mvebu_efuse_die __iomem *die, ++ bool ld1, struct mvebu_efuse_ld *ld) ++{ ++ size_t i; ++ ++ mvebu_efuse_ld_select(die, ld1); ++ ++ for (i = 0; i < ARRAY_SIZE(ld->array); i++) { ++ ld->array[i] = readl(&die->ld.array[i]); ++ if (i & 1) ++ ld->array[i] &= ~MVEBU_EFUSE_LD_PARITY_BIT; ++ } ++} ++ ++static int mvebu_efuse_ld_write(const struct mvebu_efuse_die __iomem *die, ++ bool ld1, bool secure, const struct mvebu_efuse_ld *ld) ++{ ++ size_t i; ++ ++ for (i = 0; i < ARRAY_SIZE(ld->array); i++) ++ if ((i & 1) && (ld->array[i] & MVEBU_EFUSE_LD_PARITY_BIT)) ++ return -EINVAL; ++ ++ mvebu_efuse_ld_select(die, ld1); ++ mvebu_efuse_program_set(die, true); ++ mvebu_efuse_secure_set(die, secure); ++ ++ for (i = 0; i < ARRAY_SIZE(ld->array); i++) ++ writel(ld->array[i], &die->ld.array[i]); ++ ++ writel(0, &die->ld.burn); ++ mdelay(1); ++ ++ mvebu_efuse_program_set(die, false); ++ return 0; ++} ++ ++static int mvebu_efuse_hd_read_line(const struct mvebu_efuse_die __iomem *die, ++ u8 line, struct mvebu_efuse_hd_line *hdl) ++{ ++ if (line >= ARRAY_SIZE(die->hd.line)) ++ return -ERANGE; ++ ++ hdl->array[0] = readl(&die->hd.line[line].array[0]); ++ hdl->array[1] = readl(&die->hd.line[line].array[1]); ++ hdl->array[1] &= ~MVEBU_EFUSE_HD_ECC_MASK; ++ hdl->lock = readl(&die->hd.line[line].lock); ++ return 0; ++} ++ ++static int mvebu_efuse_hd_write_line(const struct mvebu_efuse_die __iomem *die, ++ u8 line, const struct mvebu_efuse_hd_line *hdl) ++{ ++ if (line >= ARRAY_SIZE(die->hd.line)) ++ return -ERANGE; ++ ++ if (hdl->array[1] & MVEBU_EFUSE_HD_ECC_MASK) ++ return -EINVAL; ++ ++ mvebu_efuse_program_set(die, true); ++ ++ writel(hdl->array[0], &die->hd.line[line].array[0]); ++ writel(hdl->array[1], &die->hd.line[line].array[1]); ++ writel(0, &die->hd.line[line].lock); ++ mdelay(1); ++ ++ mvebu_efuse_program_set(die, false); ++ return 0; ++} ++ ++ ++/* ++ * Commandline interface ++ */ ++ ++static int mvefuse_die(const char *diestr, ++ const struct mvebu_efuse_die __iomem **diep) ++{ ++ if (!strcmp(diestr, "ap")) { ++ *diep = mvebu_efuse_dies[0]; ++ } else if (!strcmp(diestr, "cp0")) { ++ *diep = mvebu_efuse_dies[1]; ++ } else { ++ printf("\"%s\" is not a valid die\n", diestr); ++ return CMD_RET_USAGE; ++ } ++ ++ return 0; ++} ++ ++static int mvefuse_mod(const char *fuse, enum mvebu_efuse_mod *modp) ++{ ++ if (!strcmp(fuse, "ld0")) { ++ *modp = MVEBU_EFUSE_MOD_LD0; ++ } else if (!strcmp(fuse, "ld1")) { ++ *modp = MVEBU_EFUSE_MOD_LD1; ++ } else if (!strncmp(fuse, "hd", 2)) { ++ *modp = MVEBU_EFUSE_MOD_HD; ++ } else { ++ printf("\"%s\" is not a valid fuse\n", fuse); ++ return CMD_RET_USAGE; ++ } ++ ++ return 0; ++} ++ ++#define MVEFUSE_LINE_ALL 0xff ++ ++static int mvefuse_line(const char *fuse, u8 *linep) ++{ ++ unsigned long line; ++ ++ if (!strcmp(fuse, "hd")) { ++ *linep = MVEFUSE_LINE_ALL; ++ return 0; ++ } ++ ++ line = simple_strtoul(&fuse[2], NULL, 0); ++ if (line >= MVEBU_EFUSE_HD_LINES) { ++ printf("\"%s\" is not valid HD fuse\n", fuse); ++ return CMD_RET_USAGE; ++ } ++ ++ *linep = line; ++ return 0; ++} ++ ++static int mvefuse_parse(char * const *argv, ++ const struct mvebu_efuse_die __iomem **diep, ++ enum mvebu_efuse_mod *modp, ++ u8 *linep) ++{ ++ int err; ++ ++ err = mvefuse_die(argv[0], diep); ++ if (err) ++ return err; ++ ++ err = mvefuse_mod(argv[1], modp); ++ if (err) ++ return err; ++ ++ if (*modp != MVEBU_EFUSE_MOD_HD) ++ return 0; ++ ++ return mvefuse_line(argv[1], linep); ++} ++ ++static int do_mvefuse_md_ld(const struct mvebu_efuse_die __iomem *die, ++ bool ld1) ++{ ++ struct mvebu_efuse_ld ld; ++ ++ mvebu_efuse_ld_read(die, ld1, &ld); ++ ++ print_hex_dump("", DUMP_PREFIX_OFFSET, 16, 4, ++ ld.array, sizeof(ld.array), false); ++ return 0; ++} ++ ++static int do_mvefuse_md_hd_line(const struct mvebu_efuse_die __iomem *die, ++ u8 line) ++{ ++ struct mvebu_efuse_hd_line hdl; ++ int err; ++ ++ err = mvebu_efuse_hd_read_line(die, line, &hdl); ++ if (err) { ++ printf("Unable to read HD line %u: %d\n", line, err); ++ return CMD_RET_FAILURE; ++ } ++ ++ printf("%08x %08x %s\n", hdl.array[0], hdl.array[1], ++ hdl.lock ? "[LOCKED]" : ""); ++ return 0; ++} ++ ++static int do_mvefuse_md_hd_all(const struct mvebu_efuse_die __iomem *die) ++{ ++ u8 line; ++ int err; ++ ++ for (line = 0; line < MVEBU_EFUSE_HD_LINES; line++) { ++ printf("Line %02u: ", line); ++ err = do_mvefuse_md_hd_line(die, line); ++ if (err) ++ return err; ++ } ++ ++ return 0; ++} ++ ++static int do_mvefuse_md(struct cmd_tbl *cmdtp, int flag, ++ int argc, char *const *argv) ++{ ++ const struct mvebu_efuse_die __iomem *die; ++ enum mvebu_efuse_mod mod; ++ u8 line; ++ int err; ++ ++ if (argc < 3) ++ return CMD_RET_USAGE; ++ ++ err = mvefuse_parse(&argv[1], &die, &mod, &line); ++ if (err) ++ return err; ++ ++ switch (mod) { ++ case MVEBU_EFUSE_MOD_LD0: ++ return do_mvefuse_md_ld(die, false); ++ case MVEBU_EFUSE_MOD_LD1: ++ return do_mvefuse_md_ld(die, true); ++ case MVEBU_EFUSE_MOD_HD: ++ if (line == MVEFUSE_LINE_ALL) ++ return do_mvefuse_md_hd_all(die); ++ else ++ return do_mvefuse_md_hd_line(die, line); ++ } ++ ++ return CMD_RET_FAILURE; ++} ++ ++static int do_mvefuse_mw_ld(const struct mvebu_efuse_die __iomem *die, ++ bool ld1, int argc, char *const *argv, ++ bool interactive) ++{ ++ struct mvebu_efuse_ld ld; ++ bool secure = false; ++ size_t i; ++ int err; ++ ++ mvebu_efuse_ld_read(die, ld1, &ld); ++ ++ switch (argc) { ++ case 9: ++ if (!strcmp(argv[8], "lock")) ++ secure = true; ++ else ++ return CMD_RET_USAGE; ++ ++ /* fallthrough */ ++ case 8: ++ for (i = 0; i < ARRAY_SIZE(ld.array); i++) ++ ld.array[i] |= hextoul(argv[i], NULL); ++ ++ break; ++ ++ default: ++ return CMD_RET_USAGE; ++ } ++ ++ if (interactive) { ++ printf("The following values will be written%s into %s:\n", ++ secure ? ", and PERMANENTLY LOCKED," : "", ++ ld1 ? "LD1" : "LD0"); ++ ++ print_hex_dump("", DUMP_PREFIX_OFFSET, 16, 4, ++ ld.array, sizeof(ld.array), false); ++ ++ puts("WARNING: This operation is irreversible\n" ++ "Are you sure you want to continue? (y/N)\n"); ++ ++ if (!confirm_yesno()) { ++ puts("Programming aborted\n"); ++ return CMD_RET_FAILURE; ++ } ++ } ++ ++ err = mvebu_efuse_ld_write(die, ld1, secure, &ld); ++ if (err) { ++ printf("Failed to program %s: %d\n", ld1 ? "LD1" : "LD0", err); ++ return CMD_RET_FAILURE; ++ } ++ ++ return 0; ++} ++ ++static int do_mvefuse_mw_hd_line(const struct mvebu_efuse_die __iomem *die, ++ u8 line, int argc, char *const *argv, ++ bool interactive) ++{ ++ struct mvebu_efuse_hd_line hdl; ++ int err; ++ ++ mvebu_efuse_hd_read_line(die, line, &hdl); ++ if (hdl.lock) { ++ printf("Unable to program LOCKED line HD%u\n", line); ++ return CMD_RET_FAILURE; ++ } ++ ++ switch (argc) { ++ case 3: ++ /* In contrast to the LD fuse array, HD fuse lines are ++ * always locked when programmed, make sure the user ++ * is aware of this by requiring the "lock" keyword to ++ * be specified. */ ++ if (strcmp(argv[2], "lock")) ++ return CMD_RET_USAGE; ++ ++ hdl.array[0] |= hextoul(argv[0], NULL); ++ hdl.array[1] |= hextoul(argv[1], NULL); ++ break; ++ ++ default: ++ return CMD_RET_USAGE; ++ } ++ ++ if (interactive) { ++ printf("The following values will be PERMANENTLY written into HD%u:\n", ++ line); ++ ++ printf("%08x %08x\n", hdl.array[0], hdl.array[1]); ++ ++ puts("WARNING: This operation is irreversible\n" ++ "Are you sure you want to continue? (y/N)\n"); ++ ++ if (!confirm_yesno()) { ++ puts("Programming aborted\n"); ++ return CMD_RET_FAILURE; ++ } ++ } ++ ++ err = mvebu_efuse_hd_write_line(die, line, &hdl); ++ if (err) { ++ printf("Failed to program HD%u: %d\n", line, err); ++ return CMD_RET_FAILURE; ++ } ++ ++ return 0; ++} ++ ++static int do_mvefuse_mw(struct cmd_tbl *cmdtp, int flag, ++ int argc, char *const *argv) ++{ ++ const struct mvebu_efuse_die __iomem *die; ++ enum mvebu_efuse_mod mod; ++ bool interactive = true; ++ u8 line; ++ int err; ++ ++ /* Pop off "md" */ ++ argv++, argc--; ++ ++ if (argc && (!strcmp("-y", argv[0]))) { ++ interactive = false; ++ argv++, argc--; ++ } ++ ++ if (argc < 2) ++ return CMD_RET_USAGE; ++ ++ err = mvefuse_parse(argv, &die, &mod, &line); ++ if (err) ++ return err; ++ ++ /* Pop off and */ ++ argv++, argc--; ++ argv++, argc--; ++ ++ switch (mod) { ++ case MVEBU_EFUSE_MOD_LD0: ++ return do_mvefuse_mw_ld(die, false, argc, argv, interactive); ++ case MVEBU_EFUSE_MOD_LD1: ++ return do_mvefuse_mw_ld(die, true, argc, argv, interactive); ++ case MVEBU_EFUSE_MOD_HD: ++ if (line == MVEFUSE_LINE_ALL) ++ return CMD_RET_USAGE; ++ else ++ return do_mvefuse_mw_hd_line(die, line, argc, argv, interactive); ++ } ++ ++ return CMD_RET_FAILURE; ++} ++ ++static int do_mvefuse_unlocked(struct cmd_tbl *cmdtp, int flag, ++ int argc, char *const argv[]) ++{ ++ const struct mvebu_efuse_die __iomem *die; ++ struct mvebu_efuse_hd_line hdl; ++ enum mvebu_efuse_mod mod; ++ u8 line; ++ int err; ++ ++ if (argc != 3) ++ return CMD_RET_USAGE; ++ ++ err = mvefuse_parse(&argv[1], &die, &mod, &line); ++ if (err) ++ return err; ++ ++ if (mod != MVEBU_EFUSE_MOD_HD) { ++ printf("Only HD fuse lines have individual lock bits\n"); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (line == MVEFUSE_LINE_ALL) { ++ printf("Only an individual fuse line can be tested\n"); ++ return CMD_RET_FAILURE; ++ } ++ ++ err = mvebu_efuse_hd_read_line(die, line, &hdl); ++ if (err) { ++ printf("Unable to read HD%u: %d\n", line, err); ++ return CMD_RET_FAILURE; ++ } ++ ++ return !!hdl.lock; ++} ++ ++U_BOOT_CMD_WITH_SUBCMDS( ++ mvefuse, "Marvell eFuse Access", ++ "md - Dump the contents of from \n" ++ "mvefuse mw [-y] [lock] - Program into\n" ++ " in , optionally permanently locking it\n" ++ "mvefuse unlocked hd<0-63> - Test if an HD fuse line on is \n" ++ " unlocked\n" ++ "\n" ++ ": ap|cp0\n" ++ ": ld0|ld1|hd<0-63>\n", ++ ++ U_BOOT_SUBCMD_MKENT(md, 3, 1, do_mvefuse_md), ++ U_BOOT_SUBCMD_MKENT(mw, 12, 1, do_mvefuse_mw), ++ U_BOOT_SUBCMD_MKENT(unlocked, 3, 1, do_mvefuse_unlocked)); +-- +2.34.1 + diff --git a/patches/uboot/crb-phy-mode.patch b/patches/uboot/crb-phy-mode.patch deleted file mode 100644 index 2b166b5a..00000000 --- a/patches/uboot/crb-phy-mode.patch +++ /dev/null @@ -1,13 +0,0 @@ -Index: uboot-2023.07.02/arch/arm/dts/cn9130-crb.dtsi -=================================================================== ---- uboot-2023.07.02.orig/arch/arm/dts/cn9130-crb.dtsi -+++ uboot-2023.07.02/arch/arm/dts/cn9130-crb.dtsi -@@ -239,7 +239,7 @@ - - &cp0_eth0 { - status = "okay"; -- phy-mode = "sfi"; -+ phy-mode = "10gbase-r"; - }; - - &cp0_eth1 {