* [PATCH v2 0/9] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A board
@ 2026-08-13 8:14 Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 1/9] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs Oleksij Rempel
` (8 more replies)
0 siblings, 9 replies; 10+ messages in thread
From: Oleksij Rempel @ 2026-08-13 8:14 UTC (permalink / raw)
To: barebox; +Cc: robert.marko, Oleksij Rempel
This series adds barebox support for the Microchip LAN9696 (LAN969X
family) SoC and the EV23X71A evaluation board, a 28-port Ethernet switch
platform with an ARM64 Cortex-A53 and AT91/SAMA5 compatible peripherals.
barebox runs as BL33 in a TF-A boot chain.
Link to v1: https://lore.barebox.org/barebox/20260612055930.635833-1-o.rempel@pengutronix.de/
Each patch carries its own changelog.
Ahmad Fatoum (1):
clk: tolerate clocks registered without a name
Oleksij Rempel (8):
ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs
serial: atmel: add lan9696 (Microchip LAN969X) support
clk: add Microchip LAN966X / LAN969X generic clock controller driver
pinctrl: ocelot: port Microsemi/Microchip Ocelot pinctrl from Linux
gpio: add Microchip SGPIO (serial GPIO) driver
reset: add Microchip sparx5 / LAN969X / LAN966X switch reset driver
spi: atmel-quadspi: add Microchip LAN966X / LAN969X support
ARM: add Novarq Tactical 1000 board
arch/arm/Kconfig | 36 ++
arch/arm/boards/Makefile | 1 +
arch/arm/boards/novarq-tactical-1000/Makefile | 4 +
arch/arm/boards/novarq-tactical-1000/board.c | 45 ++
.../boards/novarq-tactical-1000/lowlevel.c | 52 ++
arch/arm/configs/multi_v8_defconfig | 4 +
arch/arm/dts/Makefile | 1 +
arch/arm/dts/lan9691-bb.dtsi | 48 ++
arch/arm/dts/novarq-tactical-1000.dts | 51 ++
drivers/clk/Kconfig | 9 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-lan966x.c | 319 ++++++++++
drivers/clk/clk.c | 3 +
drivers/gpio/Kconfig | 8 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-microchip-sgpio.c | 463 ++++++++++++++
drivers/pinctrl/Kconfig | 10 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-ocelot.c | 575 ++++++++++++++++++
drivers/reset/Kconfig | 10 +
drivers/reset/Makefile | 1 +
drivers/reset/reset-microchip-sparx5.c | 171 ++++++
drivers/serial/Kconfig | 8 +-
drivers/serial/atmel.c | 3 +-
drivers/spi/Kconfig | 2 +-
drivers/spi/atmel-quadspi.c | 105 ++++
images/Makefile | 1 +
images/Makefile.microchip | 9 +
28 files changed, 1938 insertions(+), 4 deletions(-)
create mode 100644 arch/arm/boards/novarq-tactical-1000/Makefile
create mode 100644 arch/arm/boards/novarq-tactical-1000/board.c
create mode 100644 arch/arm/boards/novarq-tactical-1000/lowlevel.c
create mode 100644 arch/arm/dts/lan9691-bb.dtsi
create mode 100644 arch/arm/dts/novarq-tactical-1000.dts
create mode 100644 drivers/clk/clk-lan966x.c
create mode 100644 drivers/gpio/gpio-microchip-sgpio.c
create mode 100644 drivers/pinctrl/pinctrl-ocelot.c
create mode 100644 drivers/reset/reset-microchip-sparx5.c
create mode 100644 images/Makefile.microchip
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 1/9] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs
2026-08-13 8:14 [PATCH v2 0/9] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A board Oleksij Rempel
@ 2026-08-13 8:14 ` Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 2/9] serial: atmel: add lan9696 (Microchip LAN969X) support Oleksij Rempel
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2026-08-13 8:14 UTC (permalink / raw)
To: barebox; +Cc: robert.marko, Oleksij Rempel
Add the ARCH_MICROCHIP top-level symbol so subsequent peripheral
driver patches (atmel serial, atmel-quadspi, ...) can gain
ARCH_MICROCHIP as a build dependency.
This SoC architecture covers Microchip's ARM64 switch SoCs, starting
with the LAN969X family. ARM32 Microchip/Atmel SoCs (AT91, SAMA5)
continue to use ARCH_AT91.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
Link to v1: https://lore.barebox.org/barebox/20260612055930.635833-1-o.rempel@pengutronix.de/
Changes v2:
- also select COMMON_CLK: the SoC clock controller driver added later
in this series depends on it
---
arch/arm/Kconfig | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index fe4fd3f47178..aed6084bef07 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -272,6 +272,21 @@ config ARCH_ZYNQMP
select GPIOLIB
select HAS_MACB
+config ARCH_MICROCHIP
+ bool "Microchip ARM64 SoC support"
+ depends on ARCH_MULTIARCH
+ depends on 64BIT
+ select OFDEVICE
+ select COMMON_CLK
+ select COMMON_CLK_OF_PROVIDER
+ select PINCTRL
+ select GPIOLIB
+ help
+ This enables support for Microchip ARM64-based SoCs,
+ including the LAN969X Ethernet switch family.
+
+ For ARM32 Microchip/Atmel SoCs (AT91, SAMA5), use ARCH_AT91.
+
source "arch/arm/cpu/Kconfig"
source "arch/arm/mach-at91/Kconfig"
source "arch/arm/mach-bcm283x/Kconfig"
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 2/9] serial: atmel: add lan9696 (Microchip LAN969X) support
2026-08-13 8:14 [PATCH v2 0/9] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A board Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 1/9] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs Oleksij Rempel
@ 2026-08-13 8:14 ` Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 3/9] clk: add Microchip LAN966X / LAN969X generic clock controller driver Oleksij Rempel
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2026-08-13 8:14 UTC (permalink / raw)
To: barebox; +Cc: robert.marko, Oleksij Rempel
Allow building the Atmel USART driver on Microchip LAN969X (ARCH_MICROCHIP)
in addition to ARCH_AT91, and match the new "microchip,lan9691-usart"
compatible alongside the existing AT91 variants.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
Link to v1: https://lore.barebox.org/barebox/20260612055930.635833-2-o.rempel@pengutronix.de/
Changes v2:
- Kconfig: keep plain "default y" instead of "default y if ARCH_AT91"
(Marco Felsch)
---
drivers/serial/Kconfig | 8 ++++++--
drivers/serial/atmel.c | 3 ++-
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 6ac1d94526c2..e2e7cea55e70 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -90,9 +90,13 @@ config DRIVER_SERIAL_NS16550_OMAP_TTYS
used.
config DRIVER_SERIAL_ATMEL
- depends on ARCH_AT91
+ bool "Atmel USART support"
+ depends on ARCH_AT91 || ARCH_MICROCHIP
default y
- bool "Atmel serial driver"
+ help
+ This enables the driver for the on-chip UARTs of Atmel/Microchip
+ AT91, SAMA5, and LAN969X SoC families. The driver supports device
+ tree configuration and clock framework integration.
config DRIVER_SERIAL_NS16550_PCI
depends on DRIVER_SERIAL_NS16550
diff --git a/drivers/serial/atmel.c b/drivers/serial/atmel.c
index 2dcb458fbd30..13747ef8201b 100644
--- a/drivers/serial/atmel.c
+++ b/drivers/serial/atmel.c
@@ -442,8 +442,9 @@ static int atmel_serial_probe(struct device *dev)
}
static const struct of_device_id __maybe_unused atmel_serial_dt_ids[] = {
- { .compatible = "atmel,at91rm9200-usart" },
+ { .compatible = "microchip,lan9691-usart" },
{ .compatible = "atmel,at91sam9260-usart" },
+ { .compatible = "atmel,at91rm9200-usart" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, atmel_serial_dt_ids);
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 3/9] clk: add Microchip LAN966X / LAN969X generic clock controller driver
2026-08-13 8:14 [PATCH v2 0/9] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A board Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 1/9] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 2/9] serial: atmel: add lan9696 (Microchip LAN969X) support Oleksij Rempel
@ 2026-08-13 8:14 ` Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 4/9] clk: tolerate clocks registered without a name Oleksij Rempel
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2026-08-13 8:14 UTC (permalink / raw)
To: barebox; +Cc: robert.marko, Oleksij Rempel
Port the Microchip LAN966X Generic Clock Controller (GCK) driver from
the Linux kernel. GCK generates and supplies clocks to peripherals on
the LAN966X and LAN969X switch SoCs (UART, SDHCI, QSPI, SGPIO, ...).
Ported from Linux drivers/clk/clk-lan966x.c at tag v7.1-rc7
Barebox-specific deltas:
- probe() takes `struct device *` instead of `platform_device`
- no devm_* - allocations use xzalloc(), which cannot fail
- clk_parent_data carries both `.fw_name` (for Linux source-compat) and
`.name` (used by barebox at register time, since barebox's clk
framework does not resolve `fw_name` via clock-names).
- Linux's `.determine_rate` is implemented here as `.round_rate`.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
Link to v1: https://lore.barebox.org/barebox/20260612055930.635833-3-o.rempel@pengutronix.de/
Changes v2:
- add SPDX-Comment: Origin-URL: tag pointing at the Linux source file
and the exact commit it was taken from (Sascha Hauer)
- use xzalloc() instead of kzalloc()/kcalloc(): xzalloc() cannot return
NULL, so the -ENOMEM error paths Marco flagged as leaking are gone
entirely. Sascha noted probe-path leaks are not worth handling, this
way there is nothing to handle. (Marco Felsch, Sascha Hauer)
- the file-scope "static void __iomem *base" Marco asked about is kept:
it is verbatim from Linux (drivers/clk/clk-lan966x.c), and there is
only ever one GCK instance per SoC
---
drivers/clk/Kconfig | 9 ++
drivers/clk/Makefile | 1 +
drivers/clk/clk-lan966x.c | 319 ++++++++++++++++++++++++++++++++++++++
3 files changed, 329 insertions(+)
create mode 100644 drivers/clk/clk-lan966x.c
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index d2a61329e125..fe7056f8971b 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -84,4 +84,13 @@ config COMMON_CLK_GPIO
source "drivers/clk/sifive/Kconfig"
+config COMMON_CLK_LAN966X
+ bool "Generic Clock Controller driver for LAN966X SoC"
+ depends on OFDEVICE
+ depends on COMMON_CLK
+ help
+ Microchip LAN966X and LAN969X SoC Generic Clock Controller (GCK).
+ GCK generates and supplies clocks to various peripherals within the
+ SoC.
+
endif
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 4fda2c1e0dd3..d7e06de04230 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -33,3 +33,4 @@ obj-$(CONFIG_COMMON_CLK_SCMI) += clk-scmi.o
obj-$(CONFIG_COMMON_CLK_GPIO) += clk-gpio.o
obj-$(CONFIG_TI_SCI_CLK) += ti-sci-clk.o
obj-$(CONFIG_ARCH_K3) += k3/
+obj-$(CONFIG_COMMON_CLK_LAN966X) += clk-lan966x.o
diff --git a/drivers/clk/clk-lan966x.c b/drivers/clk/clk-lan966x.c
new file mode 100644
index 000000000000..15e0105ce7b4
--- /dev/null
+++ b/drivers/clk/clk-lan966x.c
@@ -0,0 +1,319 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-Comment: Origin-URL: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/clk/clk-lan966x.c?id=b7e5ac83cb16f7ffd11dc23736f84276602100ed
+/*
+ * Microchip LAN966x SoC Clock driver.
+ *
+ * Copyright (C) 2021 Microchip Technology, Inc. and its subsidiaries
+ *
+ * Author: Kavyasree Kotagiri <kavyasree.kotagiri@microchip.com>
+ *
+ * Ported from Linux drivers/clk/clk-lan966x.c. Structure and identifiers
+ * are kept aligned with Linux so future fixes can be backported with
+ * minimal context churn.
+ */
+
+#include <common.h>
+#include <init.h>
+#include <driver.h>
+#include <io.h>
+#include <of.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+
+#define GCK_ENA BIT(0)
+#define GCK_SRC_SEL GENMASK(9, 8)
+#define GCK_PRESCALER GENMASK(23, 16)
+
+#define DIV_MAX 255
+
+static const char * const lan966x_clk_names[] = {
+ "qspi0", "qspi1", "qspi2", "sdmmc0",
+ "pi", "mcan0", "mcan1", "flexcom0",
+ "flexcom1", "flexcom2", "flexcom3",
+ "flexcom4", "timer1", "usb_refclk",
+};
+
+static const char * const lan969x_clk_names[] = {
+ "qspi0", "qspi2", "sdmmc0", "sdmmc1",
+ "mcan0", "mcan1", "flexcom0",
+ "flexcom1", "flexcom2", "flexcom3",
+ "timer1", "usb_refclk",
+};
+
+struct lan966x_gck {
+ struct clk_hw hw;
+ void __iomem *reg;
+};
+#define to_lan966x_gck(hw) container_of(hw, struct lan966x_gck, hw)
+
+static const struct clk_parent_data lan966x_gck_pdata[] = {
+ { .fw_name = "cpu", .name = "cpu-clk", },
+ { .fw_name = "ddr", .name = "ddr-clk", },
+ { .fw_name = "sys", .name = "fx100-clk", },
+};
+
+static struct clk_init_data init = {
+ .parent_data = lan966x_gck_pdata,
+ .num_parents = ARRAY_SIZE(lan966x_gck_pdata),
+};
+
+struct clk_gate_soc_desc {
+ const char *name;
+ int bit_idx;
+};
+
+static const struct clk_gate_soc_desc lan966x_clk_gate_desc[] = {
+ { "uhphs", 11 },
+ { "udphs", 10 },
+ { "mcramc", 9 },
+ { "hmatrix", 8 },
+ { }
+};
+
+static const struct clk_gate_soc_desc lan969x_clk_gate_desc[] = {
+ { "usb_drd", 10 },
+ { "mcramc", 9 },
+ { "hmatrix", 8 },
+ { }
+};
+
+struct lan966x_match_data {
+ char *name;
+ const char * const *clk_name;
+ const struct clk_gate_soc_desc *clk_gate_desc;
+ u8 num_generic_clks;
+ u8 num_total_clks;
+};
+
+static struct lan966x_match_data lan966x_desc = {
+ .name = "lan966x",
+ .clk_name = lan966x_clk_names,
+ .clk_gate_desc = lan966x_clk_gate_desc,
+ .num_total_clks = 18,
+ .num_generic_clks = 14,
+};
+
+static struct lan966x_match_data lan969x_desc = {
+ .name = "lan969x",
+ .clk_name = lan969x_clk_names,
+ .clk_gate_desc = lan969x_clk_gate_desc,
+ .num_total_clks = 15,
+ .num_generic_clks = 12,
+};
+
+static DEFINE_SPINLOCK(clk_gate_lock);
+static void __iomem *base;
+
+static int lan966x_gck_enable(struct clk_hw *hw)
+{
+ struct lan966x_gck *gck = to_lan966x_gck(hw);
+ u32 val = readl(gck->reg);
+
+ val |= GCK_ENA;
+ writel(val, gck->reg);
+
+ return 0;
+}
+
+static void lan966x_gck_disable(struct clk_hw *hw)
+{
+ struct lan966x_gck *gck = to_lan966x_gck(hw);
+ u32 val = readl(gck->reg);
+
+ val &= ~GCK_ENA;
+ writel(val, gck->reg);
+}
+
+static int lan966x_gck_set_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct lan966x_gck *gck = to_lan966x_gck(hw);
+ u32 div, val = readl(gck->reg);
+
+ if (rate == 0 || parent_rate == 0)
+ return -EINVAL;
+
+ /* Set Prescalar */
+ div = parent_rate / rate;
+ val &= ~GCK_PRESCALER;
+ val |= FIELD_PREP(GCK_PRESCALER, (div - 1));
+ writel(val, gck->reg);
+
+ return 0;
+}
+
+static unsigned long lan966x_gck_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct lan966x_gck *gck = to_lan966x_gck(hw);
+ u32 div, val = readl(gck->reg);
+
+ div = FIELD_GET(GCK_PRESCALER, val);
+
+ return parent_rate / (div + 1);
+}
+
+/*
+ * Linux uses .determine_rate, which barebox does not have. round_rate is
+ * called against the already-selected parent, so we just clamp the divider.
+ * Source selection happens via .set_parent / .get_parent.
+ */
+static long lan966x_gck_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ unsigned long div;
+
+ if (!rate || !*parent_rate)
+ return 0;
+
+ div = DIV_ROUND_CLOSEST(*parent_rate, rate);
+ if (div > DIV_MAX + 1)
+ div = DIV_MAX + 1;
+ if (div < 1)
+ div = 1;
+
+ return *parent_rate / div;
+}
+
+static int lan966x_gck_get_parent(struct clk_hw *hw)
+{
+ struct lan966x_gck *gck = to_lan966x_gck(hw);
+ u32 val = readl(gck->reg);
+
+ return FIELD_GET(GCK_SRC_SEL, val);
+}
+
+static int lan966x_gck_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct lan966x_gck *gck = to_lan966x_gck(hw);
+ u32 val = readl(gck->reg);
+
+ val &= ~GCK_SRC_SEL;
+ val |= FIELD_PREP(GCK_SRC_SEL, index);
+ writel(val, gck->reg);
+
+ return 0;
+}
+
+static const struct clk_ops lan966x_gck_ops = {
+ .enable = lan966x_gck_enable,
+ .disable = lan966x_gck_disable,
+ .set_rate = lan966x_gck_set_rate,
+ .recalc_rate = lan966x_gck_recalc_rate,
+ .round_rate = lan966x_gck_round_rate,
+ .set_parent = lan966x_gck_set_parent,
+ .get_parent = lan966x_gck_get_parent,
+};
+
+static struct clk_hw *lan966x_gck_clk_register(struct device *dev, int i)
+{
+ struct lan966x_gck *priv;
+ int ret;
+
+ priv = xzalloc(sizeof(*priv));
+
+ priv->reg = base + (i * 4);
+ priv->hw.init = &init;
+ ret = clk_hw_register(dev, &priv->hw);
+ if (ret) {
+ free(priv);
+ return ERR_PTR(ret);
+ }
+
+ return &priv->hw;
+};
+
+static int lan966x_gate_clk_register(struct device *dev,
+ const struct lan966x_match_data *data,
+ struct clk_onecell_data *clk_data,
+ void __iomem *gate_base)
+{
+ struct clk_hw *hw;
+ int i;
+
+ for (i = data->num_generic_clks; i < data->num_total_clks; ++i) {
+ int idx = i - data->num_generic_clks;
+ const struct clk_gate_soc_desc *desc;
+
+ desc = &data->clk_gate_desc[idx];
+
+ hw = clk_hw_register_gate(dev, desc->name,
+ data->name, 0, gate_base,
+ desc->bit_idx,
+ 0, &clk_gate_lock);
+ if (IS_ERR(hw)) {
+ dev_err(dev, "failed to register %s clock\n",
+ desc->name);
+ return PTR_ERR(hw);
+ }
+ clk_data->clks[i] = clk_hw_to_clk(hw);
+ }
+
+ return 0;
+}
+
+static int lan966x_clk_probe(struct device *dev)
+{
+ const struct lan966x_match_data *data;
+ struct clk_onecell_data *clk_data;
+ struct resource *iores;
+ int i, ret;
+
+ data = device_get_match_data(dev);
+ if (!data)
+ return -EINVAL;
+
+ clk_data = xzalloc(sizeof(*clk_data));
+ clk_data->clks = xzalloc(data->num_total_clks *
+ sizeof(*clk_data->clks));
+ clk_data->clk_num = data->num_total_clks;
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores))
+ return PTR_ERR(iores);
+ base = IOMEM(iores->start);
+
+ init.ops = &lan966x_gck_ops;
+
+ for (i = 0; i < data->num_generic_clks; i++) {
+ struct clk_hw *hw;
+
+ init.name = data->clk_name[i];
+ hw = lan966x_gck_clk_register(dev, i);
+ if (IS_ERR(hw)) {
+ dev_err(dev, "failed to register %s clock\n",
+ init.name);
+ return PTR_ERR(hw);
+ }
+ clk_data->clks[i] = clk_hw_to_clk(hw);
+ }
+
+ iores = dev_request_mem_resource(dev, 1);
+ if (!IS_ERR(iores)) {
+ void __iomem *gate_base = IOMEM(iores->start);
+
+ ret = lan966x_gate_clk_register(dev, data, clk_data, gate_base);
+ if (ret)
+ return ret;
+ }
+
+ return of_clk_add_provider(dev->of_node, of_clk_src_onecell_get,
+ clk_data);
+}
+
+static const struct of_device_id lan966x_clk_dt_ids[] = {
+ { .compatible = "microchip,lan966x-gck", .data = &lan966x_desc },
+ { .compatible = "microchip,lan9691-gck", .data = &lan969x_desc },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lan966x_clk_dt_ids);
+
+static struct driver lan966x_clk_driver = {
+ .name = "lan966x-clk",
+ .probe = lan966x_clk_probe,
+ .of_compatible = DRV_OF_COMPAT(lan966x_clk_dt_ids),
+};
+postcore_platform_driver(lan966x_clk_driver);
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 4/9] clk: tolerate clocks registered without a name
2026-08-13 8:14 [PATCH v2 0/9] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A board Oleksij Rempel
` (2 preceding siblings ...)
2026-08-13 8:14 ` [PATCH v2 3/9] clk: add Microchip LAN966X / LAN969X generic clock controller driver Oleksij Rempel
@ 2026-08-13 8:14 ` Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 5/9] pinctrl: ocelot: port Microsemi/Microchip Ocelot pinctrl from Linux Oleksij Rempel
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2026-08-13 8:14 UTC (permalink / raw)
To: barebox; +Cc: robert.marko, Ahmad Fatoum, Oleksij Rempel
From: Ahmad Fatoum <a.fatoum@barebox.org>
bclk_register() crashes when ->name is NULL because __bclk_register()
strdup()s it unconditionally. Reject such registrations early with
-EINVAL instead of taking down the system, so a buggy driver only
fails its own clock instead of the whole boot.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
Link to v1: https://lore.barebox.org/barebox/20260612055930.635833-4-o.rempel@pengutronix.de/
Changes v2:
- no changes
---
drivers/clk/clk.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index 5e385380cee2..99f995fb0224 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -494,6 +494,9 @@ int bclk_register(struct clk *clk)
clk->parents = xzalloc(sizeof(struct clk *) * clk->num_parents);
+ if (!clk->name)
+ return -EINVAL;
+
ret = __bclk_register(clk);
if (ret)
free(clk->parents);
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 5/9] pinctrl: ocelot: port Microsemi/Microchip Ocelot pinctrl from Linux
2026-08-13 8:14 [PATCH v2 0/9] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A board Oleksij Rempel
` (3 preceding siblings ...)
2026-08-13 8:14 ` [PATCH v2 4/9] clk: tolerate clocks registered without a name Oleksij Rempel
@ 2026-08-13 8:14 ` Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 6/9] gpio: add Microchip SGPIO (serial GPIO) driver Oleksij Rempel
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2026-08-13 8:14 UTC (permalink / raw)
To: barebox; +Cc: robert.marko, Oleksij Rempel
Port drivers/pinctrl/pinctrl-ocelot.c from the Linux kernel as a
pinmux + GPIO controller for the Microsemi/Microchip Ocelot SoC
family. The drop is reduced to the LAN969X (lan9691) subset that
barebox needs to bring the board up; the other SoC variants are
dropped to keep the file reviewable. Identifier and structure layout
are kept compatible with Linux so future feature backports stay
mechanical.
Ported from Linux drivers/pinctrl/pinctrl-ocelot.c at tag v7.1-rc7
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
Link to v1: https://lore.barebox.org/barebox/20260612055930.635833-5-o.rempel@pengutronix.de/
Changes v2:
- add SPDX-Comment: Origin-URL: tag pointing at the Linux source file
and the exact commit it was taken from (Sascha Hauer)
---
drivers/pinctrl/Kconfig | 10 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-ocelot.c | 575 +++++++++++++++++++++++++++++++
3 files changed, 586 insertions(+)
create mode 100644 drivers/pinctrl/pinctrl-ocelot.c
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index 1d237db106ba..8cb6e2efebdf 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -111,6 +111,16 @@ config PINCTRL_STM32
help
Pinmux and GPIO controller found on STM32 family
+config PINCTRL_OCELOT
+ bool "Pinctrl driver for the Microsemi Ocelot family of SoCs"
+ depends on OFDEVICE
+ select GPIOLIB
+ help
+ Pinmux + GPIO controller driver for the Microsemi/Microchip Ocelot
+ family. Currently implements the LAN969X (lan9691) subset matched
+ with the same identifiers as Linux's drivers/pinctrl/pinctrl-ocelot.c
+ so future Linux patches backport with minimal churn.
+
source "drivers/pinctrl/sunxi/Kconfig"
endif
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 3bc718d35539..6dd11c0d64b0 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_PINCTRL_TEGRA30) += pinctrl-tegra30.o
obj-$(CONFIG_PINCTRL_TEGRA_XUSB) += pinctrl-tegra-xusb.o
obj-$(CONFIG_PINCTRL_VF610) += pinctrl-vf610.o
obj-$(CONFIG_PINCTRL_STM32) += pinctrl-stm32.o
+obj-$(CONFIG_PINCTRL_OCELOT) += pinctrl-ocelot.o
obj-$(CONFIG_ARCH_MVEBU) += mvebu/
obj-$(CONFIG_ARCH_SUNXI) += sunxi/
diff --git a/drivers/pinctrl/pinctrl-ocelot.c b/drivers/pinctrl/pinctrl-ocelot.c
new file mode 100644
index 000000000000..6b74411938de
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ocelot.c
@@ -0,0 +1,575 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+// SPDX-Comment: Origin-URL: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/pinctrl/pinctrl-ocelot.c?id=b7e5ac83cb16f7ffd11dc23736f84276602100ed
+/*
+ * Microchip ocelot family pinmux driver — LAN969X subset.
+ *
+ * Copyright (c) 2017 Microsemi Corporation
+ * Copyright (c) 2025 Microchip Technology Inc.
+ *
+ * Ported from Linux drivers/pinctrl/pinctrl-ocelot.c. Identifiers, struct
+ * layouts, and pin/function tables are kept aligned with Linux so future
+ * fixes can be backported with minimal context churn.
+ */
+
+#include <common.h>
+#include <init.h>
+#include <driver.h>
+#include <gpio.h>
+#include <io.h>
+#include <of.h>
+#include <pinctrl.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/regmap.h>
+
+#define OCELOT_FUNC_PER_PIN 4
+
+/* GPIO standard registers - byte offsets, identical to Linux. REG/REG_ALT
+ * scale these by info->stride to step over the 32-bit-bank groups.
+ */
+#define OCELOT_GPIO_OUT_SET 0x0
+#define OCELOT_GPIO_OUT_CLR 0x4
+#define OCELOT_GPIO_OUT 0x8
+#define OCELOT_GPIO_IN 0xc
+#define OCELOT_GPIO_OE 0x10
+#define OCELOT_GPIO_INTR 0x14
+#define OCELOT_GPIO_INTR_ENA 0x18
+#define OCELOT_GPIO_INTR_IDENT 0x1c
+#define OCELOT_GPIO_ALT0 0x20
+#define OCELOT_GPIO_ALT1 0x24
+#define OCELOT_GPIO_SD_MAP 0x28
+
+/*
+ * Function enum — LAN969X subset of Linux's full enum, kept in Linux's
+ * ordering. Other SoCs' FUNC_* entries are intentionally omitted; add
+ * them when porting more SoCs.
+ */
+enum {
+ FUNC_CAN0_a,
+ FUNC_CAN0_b,
+ FUNC_CAN1,
+ FUNC_CLKMON,
+ FUNC_NONE,
+ FUNC_FAN,
+ FUNC_FC,
+ FUNC_FC_SHRD,
+ FUNC_FUSA,
+ FUNC_GPIO,
+ FUNC_IRQ0,
+ FUNC_IRQ1,
+ FUNC_IRQ3,
+ FUNC_IRQ4,
+ FUNC_MIIM,
+ FUNC_MIIM_Sa,
+ FUNC_MIIM_IRQ,
+ FUNC_PCIE_PERST,
+ FUNC_PTPSYNC_0,
+ FUNC_PTPSYNC_1,
+ FUNC_PTPSYNC_2,
+ FUNC_PTPSYNC_3,
+ FUNC_PTPSYNC_4,
+ FUNC_PTPSYNC_5,
+ FUNC_PTPSYNC_6,
+ FUNC_PTPSYNC_7,
+ FUNC_QSPI1,
+ FUNC_R,
+ FUNC_SD,
+ FUNC_SFP_SD,
+ FUNC_SGPIO_a,
+ FUNC_SYNCE,
+ FUNC_TWI,
+ FUNC_USB_POWER,
+ FUNC_USB2PHY_RST,
+ FUNC_USB_OVER_DETECT,
+ FUNC_USB_ULPI,
+ FUNC_EMMC_SD,
+ FUNC_MAX
+};
+
+static const char *const ocelot_function_names[] = {
+ [FUNC_CAN0_a] = "can0_a",
+ [FUNC_CAN0_b] = "can0_b",
+ [FUNC_CAN1] = "can1",
+ [FUNC_CLKMON] = "clkmon",
+ [FUNC_NONE] = "none",
+ [FUNC_FAN] = "fan",
+ [FUNC_FC] = "fc",
+ [FUNC_FC_SHRD] = "fc_shrd",
+ [FUNC_FUSA] = "fusa",
+ [FUNC_GPIO] = "gpio",
+ [FUNC_IRQ0] = "irq0",
+ [FUNC_IRQ1] = "irq1",
+ [FUNC_IRQ3] = "irq3",
+ [FUNC_IRQ4] = "irq4",
+ [FUNC_MIIM] = "miim",
+ [FUNC_MIIM_Sa] = "miim_slave_a",
+ [FUNC_MIIM_IRQ] = "miim_irq",
+ [FUNC_PCIE_PERST] = "pcie_perst",
+ [FUNC_PTPSYNC_0] = "ptpsync_0",
+ [FUNC_PTPSYNC_1] = "ptpsync_1",
+ [FUNC_PTPSYNC_2] = "ptpsync_2",
+ [FUNC_PTPSYNC_3] = "ptpsync_3",
+ [FUNC_PTPSYNC_4] = "ptpsync_4",
+ [FUNC_PTPSYNC_5] = "ptpsync_5",
+ [FUNC_PTPSYNC_6] = "ptpsync_6",
+ [FUNC_PTPSYNC_7] = "ptpsync_7",
+ [FUNC_QSPI1] = "qspi1",
+ [FUNC_R] = "reserved",
+ [FUNC_SD] = "sd",
+ [FUNC_SFP_SD] = "sfp_sd",
+ [FUNC_SGPIO_a] = "sgpio_a",
+ [FUNC_SYNCE] = "synce",
+ [FUNC_TWI] = "twi",
+ [FUNC_USB_POWER] = "usb_power",
+ [FUNC_USB2PHY_RST] = "usb2phy_rst",
+ [FUNC_USB_OVER_DETECT] = "usb_over_detect",
+ [FUNC_USB_ULPI] = "usb_ulpi",
+ [FUNC_EMMC_SD] = "emmc_sd",
+};
+
+struct ocelot_pin_caps {
+ unsigned int pin;
+ unsigned char functions[OCELOT_FUNC_PER_PIN];
+ unsigned char a_functions[OCELOT_FUNC_PER_PIN]; /* Additional functions */
+};
+
+struct ocelot_pin_desc {
+ unsigned int number;
+ const char *name;
+ struct ocelot_pin_caps *drv_data;
+};
+
+#define LAN969X_P(p, f0, f1, f2, f3, f4, f5, f6, f7) \
+static struct ocelot_pin_caps lan969x_pin_##p = { \
+ .pin = p, \
+ .functions = { \
+ FUNC_##f0, FUNC_##f1, FUNC_##f2, \
+ FUNC_##f3 \
+ }, \
+ .a_functions = { \
+ FUNC_##f4, FUNC_##f5, FUNC_##f6, \
+ FUNC_##f7 \
+ }, \
+}
+
+/* Pinmuxing table taken from data sheet */
+/* Pin FUNC0 FUNC1 FUNC2 FUNC3 FUNC4 FUNC5 FUNC6 FUNC7 */
+LAN969X_P(0, GPIO, IRQ0, FC_SHRD, PCIE_PERST, NONE, NONE, NONE, R);
+LAN969X_P(1, GPIO, IRQ1, FC_SHRD, USB_POWER, NONE, NONE, NONE, R);
+LAN969X_P(2, GPIO, FC, NONE, NONE, NONE, NONE, NONE, R);
+LAN969X_P(3, GPIO, FC, NONE, NONE, NONE, NONE, NONE, R);
+LAN969X_P(4, GPIO, FC, NONE, NONE, NONE, NONE, NONE, R);
+LAN969X_P(5, GPIO, SGPIO_a, NONE, CLKMON, NONE, NONE, NONE, R);
+LAN969X_P(6, GPIO, SGPIO_a, NONE, CLKMON, NONE, NONE, NONE, R);
+LAN969X_P(7, GPIO, SGPIO_a, NONE, CLKMON, NONE, NONE, NONE, R);
+LAN969X_P(8, GPIO, SGPIO_a, NONE, CLKMON, NONE, NONE, NONE, R);
+LAN969X_P(9, GPIO, MIIM, MIIM_Sa, CLKMON, NONE, NONE, NONE, R);
+LAN969X_P(10, GPIO, MIIM, MIIM_Sa, CLKMON, NONE, NONE, NONE, R);
+LAN969X_P(11, GPIO, MIIM_IRQ, MIIM_Sa, CLKMON, NONE, NONE, NONE, R);
+LAN969X_P(12, GPIO, IRQ3, FC_SHRD, USB2PHY_RST, NONE, NONE, NONE, R);
+LAN969X_P(13, GPIO, IRQ4, FC_SHRD, USB_OVER_DETECT, NONE, NONE, NONE, R);
+LAN969X_P(14, GPIO, EMMC_SD, QSPI1, FC, NONE, NONE, NONE, R);
+LAN969X_P(15, GPIO, EMMC_SD, QSPI1, FC, NONE, NONE, NONE, R);
+LAN969X_P(16, GPIO, EMMC_SD, QSPI1, FC, NONE, NONE, NONE, R);
+LAN969X_P(17, GPIO, EMMC_SD, QSPI1, PTPSYNC_0, USB_POWER, NONE, NONE, R);
+LAN969X_P(18, GPIO, EMMC_SD, QSPI1, PTPSYNC_1, USB2PHY_RST, NONE, NONE, R);
+LAN969X_P(19, GPIO, EMMC_SD, QSPI1, PTPSYNC_2, USB_OVER_DETECT, NONE, NONE, R);
+LAN969X_P(20, GPIO, EMMC_SD, NONE, FC_SHRD, NONE, NONE, NONE, R);
+LAN969X_P(21, GPIO, EMMC_SD, NONE, FC_SHRD, NONE, NONE, NONE, R);
+LAN969X_P(22, GPIO, EMMC_SD, NONE, FC_SHRD, NONE, NONE, NONE, R);
+LAN969X_P(23, GPIO, EMMC_SD, NONE, FC_SHRD, NONE, NONE, NONE, R);
+LAN969X_P(24, GPIO, EMMC_SD, NONE, NONE, NONE, NONE, NONE, R);
+LAN969X_P(25, GPIO, FAN, FUSA, CAN0_a, QSPI1, NONE, NONE, R);
+LAN969X_P(26, GPIO, FAN, FUSA, CAN0_a, QSPI1, NONE, NONE, R);
+LAN969X_P(27, GPIO, SYNCE, FC, MIIM, QSPI1, NONE, NONE, R);
+LAN969X_P(28, GPIO, SYNCE, FC, MIIM, QSPI1, NONE, NONE, R);
+LAN969X_P(29, GPIO, SYNCE, FC, MIIM_IRQ, QSPI1, NONE, NONE, R);
+LAN969X_P(30, GPIO, PTPSYNC_0, USB_ULPI, FC_SHRD, QSPI1, NONE, NONE, R);
+LAN969X_P(31, GPIO, PTPSYNC_1, USB_ULPI, FC_SHRD, NONE, NONE, NONE, R);
+LAN969X_P(32, GPIO, PTPSYNC_2, USB_ULPI, FC_SHRD, NONE, NONE, NONE, R);
+LAN969X_P(33, GPIO, SD, USB_ULPI, FC_SHRD, NONE, NONE, NONE, R);
+LAN969X_P(34, GPIO, SD, USB_ULPI, CAN1, FC_SHRD, NONE, NONE, R);
+LAN969X_P(35, GPIO, SD, USB_ULPI, CAN1, FC_SHRD, NONE, NONE, R);
+LAN969X_P(36, GPIO, SD, USB_ULPI, PCIE_PERST, FC_SHRD, NONE, NONE, R);
+LAN969X_P(37, GPIO, SD, USB_ULPI, CAN0_b, NONE, NONE, NONE, R);
+LAN969X_P(38, GPIO, SD, USB_ULPI, CAN0_b, NONE, NONE, NONE, R);
+LAN969X_P(39, GPIO, SD, USB_ULPI, MIIM, NONE, NONE, NONE, R);
+LAN969X_P(40, GPIO, SD, USB_ULPI, MIIM, NONE, NONE, NONE, R);
+LAN969X_P(41, GPIO, SD, USB_ULPI, MIIM_IRQ, NONE, NONE, NONE, R);
+LAN969X_P(42, GPIO, PTPSYNC_3, CAN1, NONE, NONE, NONE, NONE, R);
+LAN969X_P(43, GPIO, PTPSYNC_4, CAN1, NONE, NONE, NONE, NONE, R);
+LAN969X_P(44, GPIO, PTPSYNC_5, SFP_SD, NONE, NONE, NONE, NONE, R);
+LAN969X_P(45, GPIO, PTPSYNC_6, SFP_SD, NONE, NONE, NONE, NONE, R);
+LAN969X_P(46, GPIO, PTPSYNC_7, SFP_SD, NONE, NONE, NONE, NONE, R);
+LAN969X_P(47, GPIO, NONE, SFP_SD, NONE, NONE, NONE, NONE, R);
+LAN969X_P(48, GPIO, NONE, SFP_SD, NONE, NONE, NONE, NONE, R);
+LAN969X_P(49, GPIO, NONE, SFP_SD, NONE, NONE, NONE, NONE, R);
+LAN969X_P(50, GPIO, NONE, SFP_SD, NONE, NONE, NONE, NONE, R);
+LAN969X_P(51, GPIO, NONE, SFP_SD, NONE, NONE, NONE, NONE, R);
+LAN969X_P(52, GPIO, FAN, SFP_SD, NONE, NONE, NONE, NONE, R);
+LAN969X_P(53, GPIO, FAN, SFP_SD, NONE, NONE, NONE, NONE, R);
+LAN969X_P(54, GPIO, SYNCE, FC, NONE, NONE, NONE, NONE, R);
+LAN969X_P(55, GPIO, SYNCE, FC, NONE, NONE, NONE, NONE, R);
+LAN969X_P(56, GPIO, SYNCE, FC, NONE, NONE, NONE, NONE, R);
+LAN969X_P(57, GPIO, SFP_SD, FC_SHRD, TWI, PTPSYNC_3, NONE, NONE, R);
+LAN969X_P(58, GPIO, SFP_SD, FC_SHRD, TWI, PTPSYNC_4, NONE, NONE, R);
+LAN969X_P(59, GPIO, SFP_SD, FC_SHRD, TWI, PTPSYNC_5, NONE, NONE, R);
+LAN969X_P(60, GPIO, SFP_SD, FC_SHRD, TWI, PTPSYNC_6, NONE, NONE, R);
+LAN969X_P(61, GPIO, MIIM, FC_SHRD, TWI, NONE, NONE, NONE, R);
+LAN969X_P(62, GPIO, MIIM, FC_SHRD, TWI, NONE, NONE, NONE, R);
+LAN969X_P(63, GPIO, MIIM_IRQ, FC_SHRD, TWI, NONE, NONE, NONE, R);
+LAN969X_P(64, GPIO, FC, FC_SHRD, TWI, NONE, NONE, NONE, R);
+LAN969X_P(65, GPIO, FC, FC_SHRD, TWI, NONE, NONE, NONE, R);
+LAN969X_P(66, GPIO, FC, FC_SHRD, TWI, NONE, NONE, NONE, R);
+
+#define LAN969X_PIN(n) { \
+ .number = n, \
+ .name = "GPIO_"#n, \
+ .drv_data = &lan969x_pin_##n \
+}
+
+static const struct ocelot_pin_desc lan969x_pins[] = {
+ LAN969X_PIN(0), LAN969X_PIN(1), LAN969X_PIN(2), LAN969X_PIN(3),
+ LAN969X_PIN(4), LAN969X_PIN(5), LAN969X_PIN(6), LAN969X_PIN(7),
+ LAN969X_PIN(8), LAN969X_PIN(9), LAN969X_PIN(10), LAN969X_PIN(11),
+ LAN969X_PIN(12), LAN969X_PIN(13), LAN969X_PIN(14), LAN969X_PIN(15),
+ LAN969X_PIN(16), LAN969X_PIN(17), LAN969X_PIN(18), LAN969X_PIN(19),
+ LAN969X_PIN(20), LAN969X_PIN(21), LAN969X_PIN(22), LAN969X_PIN(23),
+ LAN969X_PIN(24), LAN969X_PIN(25), LAN969X_PIN(26), LAN969X_PIN(27),
+ LAN969X_PIN(28), LAN969X_PIN(29), LAN969X_PIN(30), LAN969X_PIN(31),
+ LAN969X_PIN(32), LAN969X_PIN(33), LAN969X_PIN(34), LAN969X_PIN(35),
+ LAN969X_PIN(36), LAN969X_PIN(37), LAN969X_PIN(38), LAN969X_PIN(39),
+ LAN969X_PIN(40), LAN969X_PIN(41), LAN969X_PIN(42), LAN969X_PIN(43),
+ LAN969X_PIN(44), LAN969X_PIN(45), LAN969X_PIN(46), LAN969X_PIN(47),
+ LAN969X_PIN(48), LAN969X_PIN(49), LAN969X_PIN(50), LAN969X_PIN(51),
+ LAN969X_PIN(52), LAN969X_PIN(53), LAN969X_PIN(54), LAN969X_PIN(55),
+ LAN969X_PIN(56), LAN969X_PIN(57), LAN969X_PIN(58), LAN969X_PIN(59),
+ LAN969X_PIN(60), LAN969X_PIN(61), LAN969X_PIN(62), LAN969X_PIN(63),
+ LAN969X_PIN(64), LAN969X_PIN(65), LAN969X_PIN(66),
+};
+
+struct ocelot_pinctrl_desc {
+ const char *name;
+ const struct ocelot_pin_desc *pins;
+ unsigned int npins;
+};
+
+struct ocelot_pinctrl {
+ struct device *dev;
+ struct pinctrl_device pctl;
+ struct gpio_chip gpio_chip;
+ struct regmap *map;
+ struct regmap *pincfg;
+ const struct ocelot_pinctrl_desc *desc;
+ u8 stride;
+ u8 altm_stride;
+};
+
+#define to_ocelot_pctl(pdev) container_of(pdev, struct ocelot_pinctrl, pctl)
+#define to_ocelot_gpio(gc) container_of(gc, struct ocelot_pinctrl, gpio_chip)
+
+static const struct ocelot_pinctrl_desc lan969x_desc = {
+ .name = "lan969x-pinctrl",
+ .pins = lan969x_pins,
+ .npins = ARRAY_SIZE(lan969x_pins),
+};
+
+/*
+ * Register stride: each ALT0/ALT1/ALT2 group of pin-mux bits spans
+ * (info->stride) 32-bit words to cover all pins; the LAN969X 78-pin layout
+ * also uses altm_stride == stride.
+ */
+#define REG(r, info, p) ((r) * (info)->stride + (4 * ((p) / 32)))
+#define REG_ALT(msb, info, p) \
+ (OCELOT_GPIO_ALT0 * (info)->stride + 4 * ((msb) + ((info)->altm_stride * ((p) / 32))))
+
+static int ocelot_pin_function_idx(struct ocelot_pinctrl *info,
+ unsigned int pin, unsigned int function)
+{
+ struct ocelot_pin_caps *p = info->desc->pins[pin].drv_data;
+ int i;
+
+ for (i = 0; i < OCELOT_FUNC_PER_PIN; i++) {
+ if (function == p->functions[i])
+ return i;
+ if (function == p->a_functions[i])
+ return i + OCELOT_FUNC_PER_PIN;
+ }
+
+ return -1;
+}
+
+/* 3-bit pinmux encoding across ALT0/ALT1/ALT2, used by LAN966X and LAN969X. */
+static int lan969x_pinmux_set_mux(struct ocelot_pinctrl *info,
+ unsigned int group, unsigned int selector)
+{
+ struct ocelot_pin_caps *pin = info->desc->pins[group].drv_data;
+ unsigned int p = pin->pin % 32;
+ int f;
+
+ f = ocelot_pin_function_idx(info, group, selector);
+ if (f < 0)
+ return -EINVAL;
+
+ /*
+ * f is encoded on three bits.
+ * bit 0 of f goes in BIT(pin) of ALT[0], bit 1 of f goes in BIT(pin) of
+ * ALT[1], bit 2 of f goes in BIT(pin) of ALT[2]
+ * Note: ALT0/ALT1/ALT2 are organized specially for 78 gpio targets
+ */
+ regmap_update_bits(info->map, REG_ALT(0, info, pin->pin),
+ BIT(p), f << p);
+ regmap_update_bits(info->map, REG_ALT(1, info, pin->pin),
+ BIT(p), (f >> 1) << p);
+ regmap_update_bits(info->map, REG_ALT(2, info, pin->pin),
+ BIT(p), (f >> 2) << p);
+
+ return 0;
+}
+
+/* ----- GPIO ops ----- */
+
+static int ocelot_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct ocelot_pinctrl *info = to_ocelot_gpio(chip);
+ unsigned int val;
+
+ regmap_read(info->map, REG(OCELOT_GPIO_IN, info, offset), &val);
+
+ return !!(val & BIT(offset % 32));
+}
+
+static int ocelot_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct ocelot_pinctrl *info = to_ocelot_gpio(chip);
+
+ if (value)
+ regmap_write(info->map, REG(OCELOT_GPIO_OUT_SET, info, offset),
+ BIT(offset % 32));
+ else
+ regmap_write(info->map, REG(OCELOT_GPIO_OUT_CLR, info, offset),
+ BIT(offset % 32));
+
+ return 0;
+}
+
+static int ocelot_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct ocelot_pinctrl *info = to_ocelot_gpio(chip);
+ unsigned int val;
+
+ regmap_read(info->map, REG(OCELOT_GPIO_OE, info, offset), &val);
+
+ /* GPIOF_DIR_OUT == 0, GPIOF_DIR_IN == 1 */
+ return (val & BIT(offset % 32)) ? 0 : 1;
+}
+
+static int ocelot_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct ocelot_pinctrl *info = to_ocelot_gpio(chip);
+ unsigned int pin = BIT(offset % 32);
+
+ regmap_update_bits(info->map, REG(OCELOT_GPIO_OE, info, offset),
+ pin, 0);
+
+ return 0;
+}
+
+static int ocelot_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ struct ocelot_pinctrl *info = to_ocelot_gpio(chip);
+ unsigned int pin = BIT(offset % 32);
+
+ if (value)
+ regmap_write(info->map, REG(OCELOT_GPIO_OUT_SET, info, offset),
+ pin);
+ else
+ regmap_write(info->map, REG(OCELOT_GPIO_OUT_CLR, info, offset),
+ pin);
+
+ regmap_update_bits(info->map, REG(OCELOT_GPIO_OE, info, offset),
+ pin, pin);
+
+ return 0;
+}
+
+static struct gpio_ops ocelot_gpio_ops = {
+ .direction_input = ocelot_gpio_direction_input,
+ .direction_output = ocelot_gpio_direction_output,
+ .get_direction = ocelot_gpio_get_direction,
+ .get = ocelot_gpio_get,
+ .set = ocelot_gpio_set,
+};
+
+/* ----- pinctrl ops ----- */
+
+static int ocelot_lookup_function(const char *name)
+{
+ int i;
+
+ for (i = 0; i < FUNC_MAX; i++) {
+ if (ocelot_function_names[i] &&
+ strcmp(name, ocelot_function_names[i]) == 0)
+ return i;
+ }
+ return -1;
+}
+
+static int ocelot_lookup_pin(struct ocelot_pinctrl *info, const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; i < info->desc->npins; i++)
+ if (strcmp(info->desc->pins[i].name, name) == 0)
+ return i;
+ return -1;
+}
+
+static int ocelot_pinctrl_set_state(struct pinctrl_device *pdev,
+ struct device_node *np)
+{
+ struct ocelot_pinctrl *info = to_ocelot_pctl(pdev);
+ const char *func_name;
+ const char **pin_names;
+ int func, ret, n, i;
+
+ ret = of_property_read_string(np, "function", &func_name);
+ if (ret < 0)
+ return 0;
+
+ func = ocelot_lookup_function(func_name);
+ if (func < 0) {
+ dev_err(pdev->dev, "unknown function %s\n", func_name);
+ return -EINVAL;
+ }
+
+ n = of_property_count_strings(np, "pins");
+ if (n <= 0)
+ return 0;
+
+ pin_names = xzalloc(n * sizeof(*pin_names));
+ ret = of_property_read_string_array(np, "pins", pin_names, n);
+ if (ret < 0)
+ goto out;
+
+ for (i = 0; i < n; i++) {
+ int idx = ocelot_lookup_pin(info, pin_names[i]);
+
+ if (idx < 0) {
+ dev_err(pdev->dev, "unknown pin %s\n", pin_names[i]);
+ ret = -EINVAL;
+ goto out;
+ }
+ ret = lan969x_pinmux_set_mux(info, idx, func);
+ if (ret < 0) {
+ dev_err(pdev->dev,
+ "failed to set pin %s to function %s\n",
+ pin_names[i], func_name);
+ goto out;
+ }
+ }
+ ret = 0;
+out:
+ free(pin_names);
+ return ret;
+}
+
+static struct pinctrl_ops ocelot_pinctrl_ops = {
+ .set_state = ocelot_pinctrl_set_state,
+};
+
+/* ----- probe ----- */
+
+static const struct regmap_config ocelot_pinctrl_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static int ocelot_pinctrl_probe(struct device *dev)
+{
+ struct ocelot_pinctrl *info;
+ const struct ocelot_pinctrl_desc *desc;
+ struct resource *iores;
+ void __iomem *base;
+ int ret;
+
+ desc = device_get_match_data(dev);
+ if (!desc)
+ return -EINVAL;
+
+ info = xzalloc(sizeof(*info));
+ info->dev = dev;
+ info->desc = desc;
+ info->stride = 1 + (desc->npins - 1) / 32;
+ info->altm_stride = info->stride;
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores)) {
+ ret = PTR_ERR(iores);
+ goto err_free;
+ }
+ base = IOMEM(iores->start);
+
+ info->map = regmap_init_mmio(dev, base, &ocelot_pinctrl_regmap_config);
+ if (IS_ERR(info->map)) {
+ ret = PTR_ERR(info->map);
+ goto err_free;
+ }
+
+ /* PINCFG resource (optional). */
+ iores = dev_request_mem_resource(dev, 1);
+ if (!IS_ERR(iores)) {
+ void __iomem *pincfg_base = IOMEM(iores->start);
+
+ info->pincfg = regmap_init_mmio(dev, pincfg_base,
+ &ocelot_pinctrl_regmap_config);
+ if (IS_ERR(info->pincfg))
+ info->pincfg = NULL;
+ }
+
+ info->pctl.dev = dev;
+ info->pctl.ops = &ocelot_pinctrl_ops;
+ info->pctl.base = 0;
+ info->pctl.npins = desc->npins;
+ ret = pinctrl_register(&info->pctl);
+ if (ret) {
+ dev_err(dev, "failed to register pinctrl: %d\n", ret);
+ goto err_free;
+ }
+
+ info->gpio_chip.dev = dev;
+ info->gpio_chip.base = -1;
+ info->gpio_chip.ngpio = desc->npins;
+ info->gpio_chip.ops = &ocelot_gpio_ops;
+ ret = gpiochip_add(&info->gpio_chip);
+ if (ret) {
+ dev_err(dev, "failed to register gpio chip: %d\n", ret);
+ pinctrl_unregister(&info->pctl);
+ goto err_free;
+ }
+
+ dev_info(dev, "%s registered: %u pins\n", desc->name, desc->npins);
+ return 0;
+
+err_free:
+ free(info);
+ return ret;
+}
+
+static const struct of_device_id ocelot_pinctrl_of_match[] = {
+ { .compatible = "microchip,lan9691-pinctrl", .data = &lan969x_desc },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ocelot_pinctrl_of_match);
+
+static struct driver ocelot_pinctrl_driver = {
+ .name = "ocelot-pinctrl",
+ .probe = ocelot_pinctrl_probe,
+ .of_compatible = DRV_OF_COMPAT(ocelot_pinctrl_of_match),
+};
+coredevice_platform_driver(ocelot_pinctrl_driver);
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 6/9] gpio: add Microchip SGPIO (serial GPIO) driver
2026-08-13 8:14 [PATCH v2 0/9] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A board Oleksij Rempel
` (4 preceding siblings ...)
2026-08-13 8:14 ` [PATCH v2 5/9] pinctrl: ocelot: port Microsemi/Microchip Ocelot pinctrl from Linux Oleksij Rempel
@ 2026-08-13 8:14 ` Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 7/9] reset: add Microchip sparx5 / LAN969X / LAN966X switch reset driver Oleksij Rempel
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2026-08-13 8:14 UTC (permalink / raw)
To: barebox; +Cc: robert.marko, Oleksij Rempel
Add a driver for the Microchip Serial GPIO (SGPIO) controller used on
the sparx5 / LAN969X switch SoCs to drive LED chains and per-port
signals (SFP TX-disable, presence detect, etc.).
Ported from Linux drivers/pinctrl/pinctrl-microchip-sgpio.c at tag
v7.1-rc7.
barebox doesn't carry the full Linux pinctrl subsystem, so the driver
is placed under drivers/gpio/ and exposes only the GPIO/output subset;
the pin-claim and IRQ paths from the Linux source are dropped.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
Link to v1: https://lore.barebox.org/barebox/20260612055930.635833-7-o.rempel@pengutronix.de/
Changes v2:
- register two separate drivers instead of dispatching on the
"microchip,sparx5-sgpio-bank" compatible inside a single probe:
microchip-sgpio for the controller, microchip-sgpio-bank for the
banks it populates (Sascha Hauer)
- add SPDX-Comment: Origin-URL: tag pointing at the Linux source file
and the exact commit it was taken from (Sascha Hauer)
- drop the probe error-unwind entirely. dev->priv was assigned before
of_platform_populate(), so freeing priv on its error path left
dev->priv dangling; of_platform_populate() breaks on the first failing
child and leaves earlier bank devices registered, which then read
dev->parent->priv from their own probe. Not unwinding is both correct
and the barebox convention for probe paths.
---
drivers/gpio/Kconfig | 8 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-microchip-sgpio.c | 463 ++++++++++++++++++++++++++++
3 files changed, 472 insertions(+)
create mode 100644 drivers/gpio/gpio-microchip-sgpio.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 988d08cf143c..7acd658f4fff 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -251,6 +251,14 @@ config GPIO_INTEL
help
Say Y here to build support for the Intel GPIO driver.
+config GPIO_MICROCHIP_SGPIO
+ bool "Microchip SGPIO (serial GPIO) driver"
+ depends on OFDEVICE
+ help
+ Say yes here to support the Microchip serial GPIO controller
+ used on the sparx5 / LAN969X switch SoCs to drive LED chains and
+ per-port signals (SFP TX-disable, presence, etc.).
+
endmenu
endif
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 8560e1f05925..fd1c5ac07f43 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -32,3 +32,4 @@ obj-$(CONFIG_GPIO_STARFIVE) += gpio-starfive-vic.o
obj-$(CONFIG_GPIO_ZYNQ) += gpio-zynq.o
obj-$(CONFIG_GPIO_LATCH) += gpio-latch.o
obj-$(CONFIG_GPIO_INTEL) += gpio-intel.o
+obj-$(CONFIG_GPIO_MICROCHIP_SGPIO) += gpio-microchip-sgpio.o
diff --git a/drivers/gpio/gpio-microchip-sgpio.c b/drivers/gpio/gpio-microchip-sgpio.c
new file mode 100644
index 000000000000..4b45e8718369
--- /dev/null
+++ b/drivers/gpio/gpio-microchip-sgpio.c
@@ -0,0 +1,463 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-Comment: Origin-URL: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/pinctrl/pinctrl-microchip-sgpio.c?id=b7e5ac83cb16f7ffd11dc23736f84276602100ed
+/*
+ * Microchip SGPIO driver — LAN969X / sparx5 subset.
+ *
+ * Copyright (c) 2020 Microchip Technology Inc.
+ *
+ * Ported from Linux drivers/pinctrl/pinctrl-microchip-sgpio.c. Identifiers
+ * and register-layout tables are kept aligned with Linux so future fixes
+ * can be backported with minimal context churn.
+ */
+
+#include <common.h>
+#include <init.h>
+#include <driver.h>
+#include <gpio.h>
+#include <io.h>
+#include <of.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#define SGPIO_BITS_PER_WORD 32
+#define SGPIO_MAX_BITS 4
+
+enum {
+ REG_INPUT_DATA,
+ REG_PORT_CONFIG,
+ REG_PORT_ENABLE,
+ REG_SIO_CONFIG,
+ REG_SIO_CLOCK,
+ REG_INT_POLARITY,
+ REG_INT_TRIGGER,
+ REG_INT_ACK,
+ REG_INT_ENABLE,
+ REG_INT_IDENT,
+ MAXREG
+};
+
+enum {
+ SGPIO_ARCH_LUTON,
+ SGPIO_ARCH_OCELOT,
+ SGPIO_ARCH_SPARX5,
+};
+
+struct sgpio_properties {
+ int arch;
+ int flags;
+ u8 regoff[MAXREG];
+};
+
+#define SGPIO_SPARX5_AUTO_REPEAT BIT(6)
+#define SGPIO_SPARX5_PORT_WIDTH GENMASK(4, 3)
+#define SGPIO_SPARX5_CLK_FREQ GENMASK(19, 8)
+#define SGPIO_SPARX5_BIT_SOURCE GENMASK(23, 12)
+
+static const struct sgpio_properties properties_sparx5 = {
+ .arch = SGPIO_ARCH_SPARX5,
+ .regoff = { 0x00, 0x06, 0x26, 0x04, 0x05, 0x2a, 0x32, 0x3a, 0x3e, 0x42 },
+};
+
+struct sgpio_priv;
+
+struct sgpio_bank {
+ struct sgpio_priv *priv;
+ bool is_input;
+ struct gpio_chip gpio;
+};
+
+struct sgpio_priv {
+ struct device *dev;
+ struct sgpio_bank in;
+ struct sgpio_bank out;
+ u32 bitcount;
+ u32 ports;
+ u32 clock;
+ struct regmap *regs;
+ const struct sgpio_properties *properties;
+};
+
+struct sgpio_port_addr {
+ u8 port;
+ u8 bit;
+};
+
+#define to_sgpio_bank(gc) container_of(gc, struct sgpio_bank, gpio)
+
+static inline void sgpio_pin_to_addr(struct sgpio_priv *priv, int pin,
+ struct sgpio_port_addr *addr)
+{
+ addr->port = pin / priv->bitcount;
+ addr->bit = pin % priv->bitcount;
+}
+
+static inline int sgpio_addr_to_pin(struct sgpio_priv *priv, int port, int bit)
+{
+ return bit + port * priv->bitcount;
+}
+
+static inline u32 sgpio_get_addr(struct sgpio_priv *priv, u32 rno, u32 off)
+{
+ return (priv->properties->regoff[rno] + off) * 4;
+}
+
+static u32 sgpio_readl(struct sgpio_priv *priv, u32 rno, u32 off)
+{
+ u32 val = 0;
+ int ret;
+
+ ret = regmap_read(priv->regs, sgpio_get_addr(priv, rno, off), &val);
+ WARN_ONCE(ret, "error reading sgpio reg %d\n", ret);
+
+ return val;
+}
+
+static void sgpio_writel(struct sgpio_priv *priv, u32 val, u32 rno, u32 off)
+{
+ int ret;
+
+ ret = regmap_write(priv->regs, sgpio_get_addr(priv, rno, off), val);
+ WARN_ONCE(ret, "error writing sgpio reg %d\n", ret);
+}
+
+static inline void sgpio_clrsetbits(struct sgpio_priv *priv,
+ u32 rno, u32 off, u32 clear, u32 set)
+{
+ int ret;
+
+ ret = regmap_update_bits(priv->regs, sgpio_get_addr(priv, rno, off),
+ clear | set, set);
+ WARN_ONCE(ret, "error updating sgpio reg %d\n", ret);
+}
+
+static inline void sgpio_configure_bitstream(struct sgpio_priv *priv)
+{
+ int width = priv->bitcount - 1;
+ u32 clr, set;
+
+ /* Only SPARX5 architecture is implemented here. */
+ clr = SGPIO_SPARX5_PORT_WIDTH;
+ set = SGPIO_SPARX5_AUTO_REPEAT |
+ FIELD_PREP(SGPIO_SPARX5_PORT_WIDTH, width);
+
+ sgpio_clrsetbits(priv, REG_SIO_CONFIG, 0, clr, set);
+}
+
+static inline void sgpio_configure_clock(struct sgpio_priv *priv, u32 clkfrq)
+{
+ u32 clr = SGPIO_SPARX5_CLK_FREQ;
+ u32 set = FIELD_PREP(SGPIO_SPARX5_CLK_FREQ, clkfrq);
+
+ sgpio_clrsetbits(priv, REG_SIO_CLOCK, 0, clr, set);
+}
+
+/* ---- output / input value helpers ---- */
+
+static int sgpio_output_set(struct sgpio_priv *priv,
+ struct sgpio_port_addr *addr, int value)
+{
+ unsigned int bit = 3 * addr->bit;
+ u32 clr = FIELD_PREP(SGPIO_SPARX5_BIT_SOURCE, BIT(bit));
+ u32 set = (value & 1) ? clr : 0;
+
+ sgpio_clrsetbits(priv, REG_PORT_CONFIG, addr->port, clr, set);
+ return 0;
+}
+
+static int sgpio_output_get(struct sgpio_priv *priv,
+ struct sgpio_port_addr *addr)
+{
+ u32 portval = sgpio_readl(priv, REG_PORT_CONFIG, addr->port);
+ u32 bit_source = FIELD_GET(SGPIO_SPARX5_BIT_SOURCE, portval);
+
+ return !!(bit_source & BIT(3 * addr->bit));
+}
+
+static int sgpio_input_get(struct sgpio_priv *priv,
+ struct sgpio_port_addr *addr)
+{
+ return !!(sgpio_readl(priv, REG_INPUT_DATA, addr->bit) & BIT(addr->port));
+}
+
+/* ---- gpio_chip ops ---- */
+
+static int microchip_sgpio_get_direction(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct sgpio_bank *bank = to_sgpio_bank(gc);
+
+ /* in bank = input, out bank = output. Fixed direction per bank. */
+ return bank->is_input ? 1 : 0;
+}
+
+static int microchip_sgpio_direction_input(struct gpio_chip *gc,
+ unsigned int gpio)
+{
+ struct sgpio_bank *bank = to_sgpio_bank(gc);
+
+ return bank->is_input ? 0 : -EINVAL;
+}
+
+static int microchip_sgpio_direction_output(struct gpio_chip *gc,
+ unsigned int gpio, int value)
+{
+ struct sgpio_bank *bank = to_sgpio_bank(gc);
+ struct sgpio_port_addr addr;
+
+ if (bank->is_input)
+ return -EINVAL;
+
+ sgpio_pin_to_addr(bank->priv, gpio, &addr);
+ return sgpio_output_set(bank->priv, &addr, value);
+}
+
+static int microchip_sgpio_get_value(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct sgpio_bank *bank = to_sgpio_bank(gc);
+ struct sgpio_port_addr addr;
+
+ sgpio_pin_to_addr(bank->priv, gpio, &addr);
+ return bank->is_input ? sgpio_input_get(bank->priv, &addr)
+ : sgpio_output_get(bank->priv, &addr);
+}
+
+static int microchip_sgpio_set_value(struct gpio_chip *gc,
+ unsigned int gpio, int value)
+{
+ struct sgpio_bank *bank = to_sgpio_bank(gc);
+ struct sgpio_port_addr addr;
+
+ if (bank->is_input)
+ return -EINVAL;
+
+ sgpio_pin_to_addr(bank->priv, gpio, &addr);
+ return sgpio_output_set(bank->priv, &addr, value);
+}
+
+static int microchip_sgpio_of_xlate(struct gpio_chip *gc,
+ const struct of_phandle_args *gpiospec,
+ u32 *flags)
+{
+ struct sgpio_bank *bank = to_sgpio_bank(gc);
+ struct sgpio_priv *priv = bank->priv;
+ int pin;
+
+ /*
+ * SGPIO pin is identified by *two* numbers: a port between 0..31
+ * and a bit index 0..3. Cells: <port bit flags>.
+ */
+ if (gpiospec->args[0] > SGPIO_BITS_PER_WORD ||
+ gpiospec->args[1] > priv->bitcount)
+ return -EINVAL;
+
+ pin = sgpio_addr_to_pin(priv, gpiospec->args[0], gpiospec->args[1]);
+ if (pin > gc->ngpio)
+ return -EINVAL;
+
+ if (flags)
+ *flags = gpiospec->args[2];
+
+ /*
+ * Convert chip-local pin to global GPIO number, like
+ * of_gpio_simple_xlate().
+ */
+ return gc->base + pin;
+}
+
+static struct gpio_ops microchip_sgpio_gpio_ops = {
+ .direction_input = microchip_sgpio_direction_input,
+ .direction_output = microchip_sgpio_direction_output,
+ .get_direction = microchip_sgpio_get_direction,
+ .get = microchip_sgpio_get_value,
+ .set = microchip_sgpio_set_value,
+ .of_xlate = microchip_sgpio_of_xlate,
+};
+
+/* ---- probe-time setup ---- */
+
+static int microchip_sgpio_get_ports(struct sgpio_priv *priv)
+{
+ const char *prop = "microchip,sgpio-port-ranges";
+ struct device *dev = priv->dev;
+ u32 range_params[64];
+ int i, nranges, ret;
+
+ nranges = of_property_count_elems_of_size(dev->of_node, prop, sizeof(u32));
+ if (nranges < 2 || nranges % 2 || nranges > ARRAY_SIZE(range_params)) {
+ dev_err(dev, "%s port range: '%s' property\n",
+ nranges < 0 ? "Missing" : "Invalid", prop);
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_array(dev->of_node, prop, range_params, nranges);
+ if (ret) {
+ dev_err(dev, "failed to parse '%s' property: %d\n", prop, ret);
+ return ret;
+ }
+ for (i = 0; i < nranges; i += 2) {
+ int start = range_params[i];
+ int end = range_params[i + 1];
+
+ if (start > end || end >= SGPIO_BITS_PER_WORD) {
+ dev_err(dev, "Ill-formed port-range [%d:%d]\n", start, end);
+ continue;
+ }
+ priv->ports |= GENMASK(end, start);
+ }
+
+ return 0;
+}
+
+/*
+ * Child bank probe - runs once per `microchip,sparx5-sgpio-bank` node
+ * spawned by the parent via of_platform_populate.
+ */
+static int microchip_sgpio_bank_probe(struct device *dev)
+{
+ struct sgpio_priv *priv;
+ struct sgpio_bank *bank;
+ struct gpio_chip *gc;
+ u32 ngpios, reg;
+ int ret;
+
+ if (!dev->parent || !dev->parent->priv) {
+ dev_err(dev, "no parent SGPIO controller\n");
+ return -ENODEV;
+ }
+ priv = dev->parent->priv;
+
+ ret = of_property_read_u32(dev->of_node, "reg", ®);
+ if (ret) {
+ dev_err(dev, "missing 'reg' on bank node\n");
+ return ret;
+ }
+ bank = reg ? &priv->out : &priv->in;
+ bank->priv = priv;
+ bank->is_input = (reg == 0);
+
+ if (of_property_read_u32(dev->of_node, "ngpios", &ngpios))
+ ngpios = SGPIO_BITS_PER_WORD * priv->bitcount;
+
+ gc = &bank->gpio;
+ gc->dev = dev;
+ gc->base = -1;
+ gc->ngpio = ngpios;
+ gc->ops = µchip_sgpio_gpio_ops;
+ gc->of_gpio_n_cells = 3;
+
+ ret = gpiochip_add(gc);
+ if (ret)
+ dev_err(dev, "failed to register sgpio bank (reg=%u): %d\n",
+ reg, ret);
+ else
+ dev_dbg(dev, "sgpio bank (%s) registered, %d gpios\n",
+ bank->is_input ? "in" : "out", ngpios);
+ return ret;
+}
+
+static int microchip_sgpio_probe(struct device *dev)
+{
+ struct sgpio_priv *priv;
+ struct reset_control *reset;
+ struct clk *clk;
+ struct resource *iores;
+ void __iomem *base;
+ int ret, port;
+ u32 div_clock, val;
+
+ static const struct regmap_config sgpio_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ };
+
+ priv = xzalloc(sizeof(*priv));
+ priv->dev = dev;
+ priv->properties = device_get_match_data(dev);
+ if (!priv->properties)
+ return -EINVAL;
+
+ reset = reset_control_get_optional(dev, "switch");
+ if (!IS_ERR(reset))
+ reset_control_reset(reset);
+
+ clk = clk_get(dev, NULL);
+ if (IS_ERR(clk)) {
+ dev_err(dev, "failed to get clock\n");
+ return PTR_ERR(clk);
+ }
+ div_clock = clk_get_rate(clk);
+
+ if (of_property_read_u32(dev->of_node, "bus-frequency", &priv->clock))
+ priv->clock = 12500000;
+ if (priv->clock == 0 || priv->clock > (div_clock / 2)) {
+ dev_err(dev, "Invalid frequency %d\n", priv->clock);
+ return -EINVAL;
+ }
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores))
+ return PTR_ERR(iores);
+ base = IOMEM(iores->start);
+
+ priv->regs = regmap_init_mmio(dev, base, &sgpio_regmap_config);
+ if (IS_ERR(priv->regs))
+ return PTR_ERR(priv->regs);
+
+ ret = microchip_sgpio_get_ports(priv);
+ if (ret)
+ return ret;
+
+ /* Bit count is fixed for sparx5/lan969x (4 bits per port). */
+ priv->bitcount = SGPIO_MAX_BITS;
+
+ sgpio_configure_bitstream(priv);
+
+ val = max(2U, div_clock / priv->clock);
+ sgpio_configure_clock(priv, val);
+
+ for (port = 0; port < SGPIO_BITS_PER_WORD; port++)
+ sgpio_writel(priv, 0, REG_PORT_CONFIG, port);
+ sgpio_writel(priv, priv->ports, REG_PORT_ENABLE, 0);
+
+ dev->priv = priv;
+
+ ret = of_platform_populate(dev->of_node, NULL, dev);
+ if (ret) {
+ dev_err(dev, "failed to populate bank children: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(dev, "registered: clk %u Hz, %u ports\n", priv->clock,
+ hweight32(priv->ports));
+ return 0;
+}
+
+static const struct of_device_id microchip_sgpio_of_match[] = {
+ { .compatible = "microchip,sparx5-sgpio", .data = &properties_sparx5 },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, microchip_sgpio_of_match);
+
+static struct driver microchip_sgpio_driver = {
+ .name = "microchip-sgpio",
+ .probe = microchip_sgpio_probe,
+ .of_compatible = DRV_OF_COMPAT(microchip_sgpio_of_match),
+};
+coredevice_platform_driver(microchip_sgpio_driver);
+
+static const struct of_device_id microchip_sgpio_bank_of_match[] = {
+ { .compatible = "microchip,sparx5-sgpio-bank" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, microchip_sgpio_bank_of_match);
+
+static struct driver microchip_sgpio_bank_driver = {
+ .name = "microchip-sgpio-bank",
+ .probe = microchip_sgpio_bank_probe,
+ .of_compatible = DRV_OF_COMPAT(microchip_sgpio_bank_of_match),
+};
+coredevice_platform_driver(microchip_sgpio_bank_driver);
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 7/9] reset: add Microchip sparx5 / LAN969X / LAN966X switch reset driver
2026-08-13 8:14 [PATCH v2 0/9] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A board Oleksij Rempel
` (5 preceding siblings ...)
2026-08-13 8:14 ` [PATCH v2 6/9] gpio: add Microchip SGPIO (serial GPIO) driver Oleksij Rempel
@ 2026-08-13 8:14 ` Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 8/9] spi: atmel-quadspi: add Microchip LAN966X / LAN969X support Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 9/9] ARM: add Novarq Tactical 1000 board Oleksij Rempel
8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2026-08-13 8:14 UTC (permalink / raw)
To: barebox; +Cc: robert.marko, Oleksij Rempel
Add the reset controller for the Microchip sparx5 / LAN969X / LAN966X
switch SoCs. The reset is asserted once at probe time so downstream
peripherals (SGPIO, MDIO, switch core) come up against a clean IP
state instead of inheriting whatever the previous boot stage left.
Ported from Linux drivers/reset/reset-microchip-sparx5.c at tag
v7.1-rc7
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
Link to v1: https://lore.barebox.org/barebox/20260612055930.635833-8-o.rempel@pengutronix.de/
Changes v2:
- add SPDX-Comment: Origin-URL: tag pointing at the Linux source file
and the exact commit it was taken from (Sascha Hauer)
---
drivers/reset/Kconfig | 10 ++
drivers/reset/Makefile | 1 +
drivers/reset/reset-microchip-sparx5.c | 171 +++++++++++++++++++++++++
3 files changed, 182 insertions(+)
create mode 100644 drivers/reset/reset-microchip-sparx5.c
diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index c3779f087490..1819da73b27f 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -63,4 +63,14 @@ config RESET_SOCFPGA32
help
This enables the reset controller driver for 32-bit SoCFPGA platforms.
+config RESET_MICROCHIP_SPARX5
+ bool "Microchip Sparx5 / LAN969X / LAN966X switch reset driver"
+ depends on OFDEVICE
+ select MFD_SYSCON
+ help
+ This enables the switch reset controller driver for the Microchip
+ Sparx5 / LAN969X / LAN966X switch SoCs. The reset is asserted once
+ at probe time so downstream peripherals (SGPIO, MDIO, switch core)
+ see a clean IP state.
+
endif
diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
index a446cb20d0b7..b5540252bc9a 100644
--- a/drivers/reset/Makefile
+++ b/drivers/reset/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_RESET_SOCFPGA32) += reset-socfpga.o
obj-$(CONFIG_RESET_IMX7) += reset-imx7.o
obj-$(CONFIG_RESET_STARFIVE) += reset-starfive-vic.o
obj-$(CONFIG_RESET_SCMI) += reset-scmi.o
+obj-$(CONFIG_RESET_MICROCHIP_SPARX5) += reset-microchip-sparx5.o
diff --git a/drivers/reset/reset-microchip-sparx5.c b/drivers/reset/reset-microchip-sparx5.c
new file mode 100644
index 000000000000..dfe0dddb33d9
--- /dev/null
+++ b/drivers/reset/reset-microchip-sparx5.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0+
+// SPDX-Comment: Origin-URL: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/reset/reset-microchip-sparx5.c?id=b7e5ac83cb16f7ffd11dc23736f84276602100ed
+/* Microchip Sparx5 / LAN969X / LAN966X Switch Reset driver
+ *
+ * Copyright (c) 2020 Microchip Technology Inc. and its subsidiaries.
+ *
+ * Ported from Linux drivers/reset/reset-microchip-sparx5.c. Identifiers,
+ * struct layouts and per-SoC props are kept aligned with Linux so future
+ * fixes can be backported with minimal context churn.
+ */
+#include <common.h>
+#include <init.h>
+#include <driver.h>
+#include <io.h>
+#include <of.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <linux/regmap.h>
+#include <linux/reset-controller.h>
+#include <mfd/syscon.h>
+
+struct reset_props {
+ u32 protect_reg;
+ u32 protect_bit;
+ u32 reset_reg;
+ u32 reset_bit;
+};
+
+struct mchp_reset_context {
+ struct regmap *cpu_ctrl;
+ struct regmap *gcb_ctrl;
+ struct reset_controller_dev rcdev;
+ const struct reset_props *props;
+};
+
+static const struct regmap_config sparx5_reset_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static int sparx5_switch_reset(struct mchp_reset_context *ctx)
+{
+ u32 val;
+
+ /* Make sure the core is PROTECTED from reset */
+ regmap_update_bits(ctx->cpu_ctrl, ctx->props->protect_reg,
+ ctx->props->protect_bit, ctx->props->protect_bit);
+
+ /* Start soft reset */
+ regmap_write(ctx->gcb_ctrl, ctx->props->reset_reg,
+ ctx->props->reset_bit);
+
+ /* Wait for soft reset done */
+ return regmap_read_poll_timeout(ctx->gcb_ctrl, ctx->props->reset_reg,
+ val,
+ (val & ctx->props->reset_bit) == 0,
+ 100);
+}
+
+static int sparx5_reset_noop(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ return 0;
+}
+
+static const struct reset_control_ops sparx5_reset_ops = {
+ .reset = sparx5_reset_noop,
+};
+
+static int mchp_sparx5_reset_probe(struct device *dev)
+{
+ struct device_node *dn = dev->of_node;
+ struct mchp_reset_context *ctx;
+ struct device_node *syscon_np;
+ struct resource *iores;
+ void __iomem *base;
+ int ret;
+
+ ctx = xzalloc(sizeof(*ctx));
+
+ syscon_np = of_parse_phandle(dn, "cpu-syscon", 0);
+ if (!syscon_np) {
+ dev_err(dev, "missing 'cpu-syscon' phandle\n");
+ ret = -ENODEV;
+ goto err;
+ }
+ ctx->cpu_ctrl = syscon_node_to_regmap(syscon_np);
+ if (IS_ERR(ctx->cpu_ctrl)) {
+ dev_err(dev, "failed to map cpu-syscon: %pe\n", ctx->cpu_ctrl);
+ ret = PTR_ERR(ctx->cpu_ctrl);
+ goto err;
+ }
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores)) {
+ ret = PTR_ERR(iores);
+ goto err;
+ }
+ base = IOMEM(iores->start);
+ ctx->gcb_ctrl = regmap_init_mmio(dev, base, &sparx5_reset_regmap_config);
+ if (IS_ERR(ctx->gcb_ctrl)) {
+ ret = PTR_ERR(ctx->gcb_ctrl);
+ goto err;
+ }
+
+ ctx->props = device_get_match_data(dev);
+ if (!ctx->props) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ctx->rcdev.nr_resets = 1;
+ ctx->rcdev.ops = &sparx5_reset_ops;
+ ctx->rcdev.of_node = dn;
+
+ /*
+ * Issue the reset very early; reset_control_reset() callers see a
+ * no-op afterwards.
+ */
+ ret = sparx5_switch_reset(ctx);
+ if (ret) {
+ dev_err(dev, "switch reset timed out: %d\n", ret);
+ goto err;
+ }
+
+ ret = reset_controller_register(&ctx->rcdev);
+ if (ret)
+ goto err;
+
+ dev_info(dev, "switch reset complete\n");
+ return 0;
+
+err:
+ free(ctx);
+ return ret;
+}
+
+static const struct reset_props reset_props_sparx5 = {
+ .protect_reg = 0x84,
+ .protect_bit = BIT(10),
+ .reset_reg = 0x0,
+ .reset_bit = BIT(1),
+};
+
+static const struct reset_props reset_props_lan966x = {
+ .protect_reg = 0x88,
+ .protect_bit = BIT(5),
+ .reset_reg = 0x0,
+ .reset_bit = BIT(1),
+};
+
+static const struct of_device_id mchp_sparx5_reset_of_match[] = {
+ { .compatible = "microchip,sparx5-switch-reset",
+ .data = &reset_props_sparx5 },
+ { .compatible = "microchip,lan966x-switch-reset",
+ .data = &reset_props_lan966x },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mchp_sparx5_reset_of_match);
+
+static struct driver mchp_sparx5_reset_driver = {
+ .name = "sparx5-switch-reset",
+ .probe = mchp_sparx5_reset_probe,
+ .of_compatible = DRV_OF_COMPAT(mchp_sparx5_reset_of_match),
+};
+/*
+ * Same rationale as Linux's postcore_initcall(): the switch IP must be
+ * reset before downstream consumers (SGPIO, MDIO, switch fabric) probe.
+ */
+coredevice_platform_driver(mchp_sparx5_reset_driver);
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 8/9] spi: atmel-quadspi: add Microchip LAN966X / LAN969X support
2026-08-13 8:14 [PATCH v2 0/9] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A board Oleksij Rempel
` (6 preceding siblings ...)
2026-08-13 8:14 ` [PATCH v2 7/9] reset: add Microchip sparx5 / LAN969X / LAN966X switch reset driver Oleksij Rempel
@ 2026-08-13 8:14 ` Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 9/9] ARM: add Novarq Tactical 1000 board Oleksij Rempel
8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2026-08-13 8:14 UTC (permalink / raw)
To: barebox; +Cc: robert.marko, Oleksij Rempel
Match the LAN966X QSPI variant and run a slightly different init
sequence (LAN966x max speed 100 MHz, dedicated DLYBS/DLYCS values,
write-protection unlock key). Allow building on ARCH_MICROCHIP and
COMPILE_TEST in addition to ARCH_AT91.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
Link to v1: https://lore.barebox.org/barebox/20260612055930.635833-9-o.rempel@pengutronix.de/
Changes v2:
- no changes
---
drivers/spi/Kconfig | 2 +-
drivers/spi/atmel-quadspi.c | 105 ++++++++++++++++++++++++++++++++++++
2 files changed, 106 insertions(+), 1 deletion(-)
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 1af2b107ecec..57ac02c82091 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -24,7 +24,7 @@ config DRIVER_SPI_ATMEL
config SPI_ATMEL_QUADSPI
tristate "Atmel Quad SPI Controller"
- depends on ARCH_AT91
+ depends on ARCH_AT91 || ARCH_MICROCHIP || COMPILE_TEST
depends on SPI_MEM
help
This enables support for the Quad SPI controller in master mode.
diff --git a/drivers/spi/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c
index b6b5f9ad9e55..d1018aadf20e 100644
--- a/drivers/spi/atmel-quadspi.c
+++ b/drivers/spi/atmel-quadspi.c
@@ -52,6 +52,7 @@
#define QSPI_VERSION 0x00FC /* Version Register */
#define SAMA7G5_QSPI0_MAX_SPEED_HZ 200000000
+#define LAN966x_QSPI0_MAX_SPEED_HZ 100000000
#define SAMA7G5_QSPI1_SDR_MAX_SPEED_HZ 133000000
/* Bitfields in QSPI_CR (Control Register) */
@@ -210,6 +211,11 @@
#define QSPI_WPMR_WPKEY_MASK GENMASK(31, 8)
#define QSPI_WPMR_WPKEY(wpkey) (((wpkey) << 8) & QSPI_WPMR_WPKEY_MASK)
+/* Used by the LAN966X-style init sequence */
+#define QSPI_DLYBS 0x2
+#define QSPI_DLYCS 0x7
+#define QSPI_WPKEY 0x515350
+
/* Bitfields in QSPI_WPSR (Write Protection Status Register) */
#define QSPI_WPSR_WPVS BIT(0)
#define QSPI_WPSR_WPVSRC_MASK GENMASK(15, 8)
@@ -250,6 +256,7 @@ struct atmel_qspi_caps {
bool has_gclk;
bool has_ricr;
bool octal;
+ bool has_lan966x;
};
struct atmel_qspi_ops;
@@ -883,6 +890,91 @@ static int atmel_qspi_sama7g5_init(struct atmel_qspi *aq)
return ret;
}
+static int lan966x_qspi_init(struct atmel_qspi *aq)
+{
+ u32 val;
+ int ret;
+
+ atmel_qspi_write(QSPI_CR_DLLOFF, aq, QSPI_CR);
+
+ ret = readl_poll_timeout(aq->regs + QSPI_SR2, val,
+ !(val & QSPI_SR2_DLOCK),
+ ATMEL_QSPI_TIMEOUT);
+ if (ret) {
+ dev_err(aq->ctlr.dev, "QSPI_SR2_DLOCK not cleared\n");
+ return ret;
+ }
+
+ ret = clk_set_rate(aq->gclk, aq->target_max_speed_hz);
+ if (ret) {
+ dev_err(aq->ctlr.dev, "Failed to set generic clock rate.\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(aq->gclk);
+ if (ret) {
+ dev_err(aq->ctlr.dev, "Failed to enable generic clock.\n");
+ return ret;
+ }
+
+ /* DLL on + stop calibration; wait for DLOCK */
+ atmel_qspi_write(QSPI_CR_DLLON | QSPI_CR_STPCAL, aq, QSPI_CR);
+ ret = readl_poll_timeout(aq->regs + QSPI_SR2, val,
+ (val & QSPI_SR2_DLOCK),
+ ATMEL_QSPI_TIMEOUT);
+ if (ret) {
+ dev_err(aq->ctlr.dev, "QSPI_SR2_DLOCK not set\n");
+ return ret;
+ }
+
+ /* Disable then soft-reset, synchronizing each step */
+ atmel_qspi_write(QSPI_CR_QSPIDIS, aq, QSPI_CR);
+ ret = atmel_qspi_reg_sync(aq);
+ if (ret)
+ return ret;
+
+ atmel_qspi_write(QSPI_CR_SWRST, aq, QSPI_CR);
+ ret = atmel_qspi_reg_sync(aq);
+ if (ret)
+ return ret;
+
+ /* Disable write protection */
+ atmel_qspi_write(QSPI_WPMR_WPKEY(QSPI_WPKEY), aq, QSPI_WPMR);
+
+ /* LAN966X variant lacks pad-calibration; just DLL-on + STPCAL again. */
+ atmel_qspi_write(QSPI_CR_DLLON | QSPI_CR_STPCAL, aq, QSPI_CR);
+ ret = readl_poll_timeout(aq->regs + QSPI_SR2, val,
+ (val & QSPI_SR2_DLOCK),
+ ATMEL_QSPI_TIMEOUT);
+ if (ret) {
+ dev_err(aq->ctlr.dev, "QSPI_SR2_DLOCK not set (post-WPMR)\n");
+ return ret;
+ }
+
+ /* Serial Memory Mode, with chip-select delay */
+ atmel_qspi_write(QSPI_MR_SMM | QSPI_MR_DLYCS(QSPI_DLYCS), aq, QSPI_MR);
+ aq->mr = QSPI_MR_SMM;
+
+ /* Apply DLYBS in SCR */
+ atmel_qspi_write(QSPI_SCR_DLYBS(QSPI_DLYBS), aq, QSPI_SCR);
+
+ ret = atmel_qspi_update_config(aq);
+ if (ret)
+ return ret;
+
+ /* Enable the controller and wait for it to come up */
+ atmel_qspi_write(QSPI_CR_QSPIEN, aq, QSPI_CR);
+ ret = readl_poll_timeout(aq->regs + QSPI_SR2, val,
+ val & QSPI_SR2_QSPIENS,
+ ATMEL_QSPI_TIMEOUT);
+ if (ret) {
+ dev_err(aq->ctlr.dev, "QSPI_SR2_QSPIENS not set\n");
+ return ret;
+ }
+
+ return 0;
+}
+
static int atmel_qspi_sama7g5_setup(struct spi_device *spi)
{
struct atmel_qspi *aq = spi_controller_get_devdata(spi->controller);
@@ -890,6 +982,9 @@ static int atmel_qspi_sama7g5_setup(struct spi_device *spi)
/* The controller can communicate with a single peripheral device (target). */
aq->target_max_speed_hz = spi->max_speed_hz;
+ if (aq->caps->has_lan966x)
+ return lan966x_qspi_init(aq);
+
return atmel_qspi_sama7g5_init(aq);
}
@@ -1112,6 +1207,12 @@ static __maybe_unused const struct atmel_qspi_caps atmel_sama7g5_qspi_caps = {
.has_gclk = true,
};
+static __maybe_unused const struct atmel_qspi_caps atmel_lan966x_qspi_caps = {
+ .max_speed_hz = LAN966x_QSPI0_MAX_SPEED_HZ,
+ .has_gclk = true,
+ .has_lan966x = true,
+};
+
static __maybe_unused const struct of_device_id atmel_qspi_dt_ids[] = {
{
.compatible = "atmel,sama5d2-qspi",
@@ -1129,6 +1230,10 @@ static __maybe_unused const struct of_device_id atmel_qspi_dt_ids[] = {
.compatible = "microchip,sama7g5-qspi",
.data = &atmel_sama7g5_qspi_caps,
},
+ {
+ .compatible = "microchip,lan966x-qspi",
+ .data = &atmel_lan966x_qspi_caps,
+ },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, atmel_qspi_dt_ids);
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 9/9] ARM: add Novarq Tactical 1000 board
2026-08-13 8:14 [PATCH v2 0/9] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A board Oleksij Rempel
` (7 preceding siblings ...)
2026-08-13 8:14 ` [PATCH v2 8/9] spi: atmel-quadspi: add Microchip LAN966X / LAN969X support Oleksij Rempel
@ 2026-08-13 8:14 ` Oleksij Rempel
8 siblings, 0 replies; 10+ messages in thread
From: Oleksij Rempel @ 2026-08-13 8:14 UTC (permalink / raw)
To: barebox; +Cc: robert.marko, Oleksij Rempel
Add board support for the Novarq Tactical 1000, a switch built around
the Microchip LAN9696: 24 1G ports, 4 10G SFP+ uplinks, booted as BL33
from TF-A.
This covers the pre-production revision, which is laid out as a close
copy of the Microchip EV23X71A reference design. It is therefore
described by including the upstream EV23X71A device tree and correcting
the identity on top of it, rather than by a board device tree of its
own. The compatible is novarq,tactical-1000-alpha with
microchip,ev23x71a kept as a fallback: the board is not made by
Microchip and is not an EV23X71A, but it is electrically that design.
The shipping Tactical 1000 differs - fans, an RTC and more - and has a
device tree of its own. That one is not upstream yet; supporting the
board means a separate board entry including it, once it lands in Linux
and reaches the dts/ snapshot.
The qspi controllers the board boots from are not in the dts/ snapshot
and are not upstream yet, so they are carried in
arch/arm/dts/lan9691-bb.dtsi until a sync brings them in.
Two things are not settled and are marked as such in the code: the
hardware used for bring-up is pre-production, and the 896 MiB of DDR is
u-boot's compile-time fallback rather than a figure read from this
board.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
Link to v1: https://lore.barebox.org/barebox/20260612055930.635833-10-o.rempel@pengutronix.de/
Changes v2:
- drop the &usart0 override: the included upstream board dts already
sets status = "okay" with the pinctrl, so it changed nothing
- drop the &sgpio block: it was character for character the same as the
node in the included upstream board dts, and would have drifted apart
on the next dts/ sync
- lan9691-bb.dtsi: explain what the file is for and when it can be
dropped, and fix the snapshot path in the comment
- board.c: drop the unused bootsource.h include
- Kconfig: extend the help text, checkpatch asked for a fuller paragraph
- lan9691-bb.dtsi: include dt-bindings/interrupt-controller/arm-gic.h
for GIC_SPI and IRQ_TYPE_LEVEL_HIGH instead of relying on the upstream
dts having pulled it in first
- lan9696-ev23x71a.dts: this file is new barebox code, so carry my own
copyright rather than the one from the upstream dts it includes
- drop the heartbeat trigger on led-sfp1-green: that is the front panel
link LED for SFP port 0, and blinking it reads as link activity
- drop the sdmmc nodes and the &sdmmc0 override. eMMC support is not
verified on this board yet, so it is held back together with the
atmel-sdhci patch rather than shipped untested
- enable the board in multi_v8_defconfig instead of adding a SoC
specific defconfig, so the series is actually built by an in-tree
configuration
- have MACH_MICROCHIP_LAN9696_EV23X71A select the clk, pinctrl, sgpio
and reset drivers the board cannot come up without. They are selected
from the board symbol rather than ARCH_MICROCHIP because ARCH_MICROCHIP
is introduced before any of those drivers exist, and selecting unknown
symbols would break bisect
- lowlevel.c: say where the 896 MiB comes from. It is u-boot's
compile-time fallback LAN969X_DDR_SIZE_DEF, not a measured figure,
and u-boot asks TF-A first. Querying TF-A is still to be done
- lan9691-bb.dtsi: add the copyright line it was missing
- name the board what it is. v1 and the first v2 called this
MACH_MICROCHIP_LAN9696_EV23X71A and bound to "microchip,ev23x71a",
but the board is not manufactured by Microchip. It is a
pre-production Novarq Tactical 1000 built on that reference design.
Renamed the symbol, board directory, dts, entry point and image, and
gave the device tree its own model and compatible, keeping
microchip,ev23x71a as a fallback
---
arch/arm/Kconfig | 21 ++++++++
arch/arm/boards/Makefile | 1 +
arch/arm/boards/novarq-tactical-1000/Makefile | 4 ++
arch/arm/boards/novarq-tactical-1000/board.c | 45 ++++++++++++++++
.../boards/novarq-tactical-1000/lowlevel.c | 52 +++++++++++++++++++
arch/arm/configs/multi_v8_defconfig | 4 ++
arch/arm/dts/Makefile | 1 +
arch/arm/dts/lan9691-bb.dtsi | 48 +++++++++++++++++
arch/arm/dts/novarq-tactical-1000.dts | 51 ++++++++++++++++++
images/Makefile | 1 +
images/Makefile.microchip | 9 ++++
11 files changed, 237 insertions(+)
create mode 100644 arch/arm/boards/novarq-tactical-1000/Makefile
create mode 100644 arch/arm/boards/novarq-tactical-1000/board.c
create mode 100644 arch/arm/boards/novarq-tactical-1000/lowlevel.c
create mode 100644 arch/arm/dts/lan9691-bb.dtsi
create mode 100644 arch/arm/dts/novarq-tactical-1000.dts
create mode 100644 images/Makefile.microchip
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index aed6084bef07..ef26ceda8b21 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -309,6 +309,27 @@ source "arch/arm/mach-tegra/Kconfig"
source "arch/arm/mach-zynq/Kconfig"
source "arch/arm/mach-zynqmp/Kconfig"
+if ARCH_MICROCHIP
+
+config MACH_NOVARQ_TACTICAL_1000
+ bool "Novarq Tactical 1000"
+ select COMMON_CLK_LAN966X
+ select PINCTRL_OCELOT
+ select GPIO_MICROCHIP_SGPIO
+ select RESET_CONTROLLER
+ select RESET_MICROCHIP_SPARX5
+ help
+ Support for the Novarq Tactical 1000, a switch built around the
+ Microchip LAN9696: 24 1G ports plus 4 10G SFP+ uplinks, an ARM64
+ CPU and AT91/SAMA5 compatible peripherals. barebox runs as the
+ BL33 payload started by TF-A and can update itself into the FIP
+ image on the QSPI NOR.
+
+ This covers the pre-production revision, which is laid out as a
+ close copy of the Microchip EV23X71A reference design.
+
+endif # ARCH_MICROCHIP
+
config BOARD_ARM_VIRT
bool
select BOARD_GENERIC_DT
diff --git a/arch/arm/boards/Makefile b/arch/arm/boards/Makefile
index fbcf14748ad7..14652fba0206 100644
--- a/arch/arm/boards/Makefile
+++ b/arch/arm/boards/Makefile
@@ -117,6 +117,7 @@ obj-$(CONFIG_MACH_SAMA5D27_GIANTBOARD) += sama5d27-giantboard/
obj-$(CONFIG_MACH_SAMA5D27_SOM1) += sama5d27-som1/
obj-$(CONFIG_MACH_SAMA5D3_XPLAINED) += sama5d3_xplained/
obj-$(CONFIG_MACH_MICROCHIP_KSZ9477_EVB) += microchip-ksz9477-evb/
+obj-$(CONFIG_MACH_NOVARQ_TACTICAL_1000) += novarq-tactical-1000/
obj-$(CONFIG_MACH_MICROCHIP_SAMA5D3_EDS) += microchip-sama5d3-eds/
obj-$(CONFIG_MACH_SAMA5D4_XPLAINED) += sama5d4_xplained/
obj-$(CONFIG_MACH_SAMA5D4_WIFX) += sama5d4_wifx/
diff --git a/arch/arm/boards/novarq-tactical-1000/Makefile b/arch/arm/boards/novarq-tactical-1000/Makefile
new file mode 100644
index 000000000000..5678718188b9
--- /dev/null
+++ b/arch/arm/boards/novarq-tactical-1000/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+lwl-y += lowlevel.o
+obj-y += board.o
diff --git a/arch/arm/boards/novarq-tactical-1000/board.c b/arch/arm/boards/novarq-tactical-1000/board.c
new file mode 100644
index 000000000000..f339ee5d271e
--- /dev/null
+++ b/arch/arm/boards/novarq-tactical-1000/board.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Oleksij Rempel <o.rempel@pengutronix.de>
+ *
+ * Novarq Tactical 1000, a LAN9696 based 24x1G + 4x10G switch.
+ *
+ * This supports the pre-production revision, which is built as a close
+ * copy of the Microchip EV23X71A reference design - hence the
+ * novarq,tactical-1000-alpha compatible and a device tree that includes
+ * the upstream EV23X71A one. The board is not manufactured by Microchip
+ * and is not an EV23X71A, so it does not claim to be one, but it is
+ * electrically that design.
+ *
+ * The shipping Tactical 1000 differs (fans, RTC, ...) and has a device
+ * tree of its own. Supporting that board means adding a board entry
+ * which includes it, not extending this one.
+ */
+
+#include <bbu.h>
+#include <common.h>
+#include <deep-probe.h>
+#include <init.h>
+
+static int tactical1000_probe(struct device *dev)
+{
+ bbu_register_std_file_update("nor", BBU_HANDLER_FLAG_DEFAULT,
+ "/dev/m25p0", filetype_fip);
+ return 0;
+}
+
+static const struct of_device_id tactical1000_of_match[] = {
+ {
+ .compatible = "novarq,tactical-1000-alpha",
+ },
+ { /* sentinel */ },
+};
+
+static struct driver tactical1000_board_driver = {
+ .name = "board-novarq-tactical-1000",
+ .probe = tactical1000_probe,
+ .of_compatible = tactical1000_of_match,
+};
+coredevice_platform_driver(tactical1000_board_driver);
+
+BAREBOX_DEEP_PROBE_ENABLE(tactical1000_of_match);
diff --git a/arch/arm/boards/novarq-tactical-1000/lowlevel.c b/arch/arm/boards/novarq-tactical-1000/lowlevel.c
new file mode 100644
index 000000000000..0ac4b40dbc99
--- /dev/null
+++ b/arch/arm/boards/novarq-tactical-1000/lowlevel.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Oleksij Rempel <o.rempel@pengutronix.de>
+ */
+
+#include <common.h>
+#include <linux/sizes.h>
+#include <asm/barebox-arm.h>
+
+extern char __dtb_z_novarq_tactical_1000_start[];
+
+/*
+ * Novarq Tactical 1000 memory map.
+ *
+ * DDR is mapped at 0x60000000. The 896 MiB used here is deliberately
+ * conservative and is NOT a measured figure for this board: it is
+ * u-boot's compile-time default LAN969X_DDR_SIZE_DEF from
+ * include/configs/lan969x.h, whose comment attributes the missing
+ * eighth of the gigabyte to ECC. LAN969X uses in-line ECC, which does
+ * consume roughly an eighth of the array, so the ratio is plausible,
+ * but nothing here has been checked against the fitted parts.
+ *
+ * u-boot itself only uses that constant as a fallback: dram_init()
+ * asks TF-A via tfa_get_dram_size() first. Under-reporting is safe -
+ * barebox simply uses less of the array - so this is fine for
+ * bring-up, but querying TF-A for the real size is the correct fix and
+ * is still to be done.
+ *
+ * The top 2 MiB is kept out of the region handed to barebox. There is
+ * no derivation behind that number: it is what worked when starting
+ * barebox as a second stage after u-boot, and without it the handover
+ * did not come up.
+ *
+ * FIXME: find out whether this is still needed, and whether 2 MiB is
+ * the right amount, on a system running TF-A plus barebox only.
+ */
+#define TACTICAL1000_DRAM_BASE UL(0x60000000)
+#define TACTICAL1000_DRAM_SIZE (896 * SZ_1M)
+#define TACTICAL1000_UBOOT_RESERVE SZ_2M
+#define TACTICAL1000_DRAM_USABLE (TACTICAL1000_DRAM_SIZE - TACTICAL1000_UBOOT_RESERVE)
+#define TACTICAL1000_DRAM_USABLE_END (TACTICAL1000_DRAM_BASE + TACTICAL1000_DRAM_USABLE)
+
+ENTRY_FUNCTION_WITHSTACK(start_novarq_tactical_1000,
+ TACTICAL1000_DRAM_USABLE_END, r0, r1, r2)
+{
+ arm_cpu_lowlevel_init();
+ relocate_to_current_adr();
+ setup_c();
+
+ barebox_arm_entry(TACTICAL1000_DRAM_BASE, TACTICAL1000_DRAM_USABLE,
+ runtime_address(__dtb_z_novarq_tactical_1000_start));
+}
diff --git a/arch/arm/configs/multi_v8_defconfig b/arch/arm/configs/multi_v8_defconfig
index fad10669e114..7b1ddea96e8a 100644
--- a/arch/arm/configs/multi_v8_defconfig
+++ b/arch/arm/configs/multi_v8_defconfig
@@ -2,9 +2,11 @@ CONFIG_ARCH_ARM64_VIRT=y
CONFIG_ARCH_BCM283X=y
CONFIG_ARCH_IMX=y
CONFIG_ARCH_K3=y
+CONFIG_ARCH_MICROCHIP=y
CONFIG_ARCH_SUNXI=y
CONFIG_ARCH_ROCKCHIP=y
CONFIG_ARCH_ZYNQMP=y
+CONFIG_MACH_NOVARQ_TACTICAL_1000=y
CONFIG_MACH_RPI3=y
CONFIG_MACH_RPI_CM3=y
CONFIG_MACH_RPI4=y
@@ -197,6 +199,8 @@ CONFIG_NET_USB_SMSC95XX=y
CONFIG_NET_USB_RTL8152=y
CONFIG_DRIVER_SPI_FSL_QUADSPI=y
CONFIG_DRIVER_SPI_IMX=y
+CONFIG_SPI_ATMEL_QUADSPI=y
+CONFIG_SPI_MEM=y
CONFIG_SPI_NXP_FLEXSPI=y
CONFIG_SPI_ROCKCHIP=y
CONFIG_I2C_GPIO=y
diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile
index b82f6c96fca2..75bc41a08b28 100644
--- a/arch/arm/dts/Makefile
+++ b/arch/arm/dts/Makefile
@@ -257,6 +257,7 @@ lwl-$(CONFIG_MACH_LS1046ARDB) += fsl-ls1046a-rdb.dtb.o
lwl-$(CONFIG_MACH_TQMLS1046A) += fsl-ls1046a-tqmls1046a-mbls10xxa.dtb.o
lwl-$(CONFIG_MACH_TQMLS1046A) += fsl-tqmls1046a-arkona-at300.dtb.o
lwl-$(CONFIG_MACH_LS1021AIOT) += fsl-ls1021a-iot.dtb.o
+lwl-$(CONFIG_MACH_NOVARQ_TACTICAL_1000) += novarq-tactical-1000.dtb.o
lwl-$(CONFIG_MACH_ZEDBOARD) += zynq-zed.dtb.o
lwl-$(CONFIG_MACH_MNT_REFORM) += imx8mq-mnt-reform2.dtb.o
lwl-$(CONFIG_MACH_VARISCITE_DT8MCUSTOMBOARD_IMX8MP) += imx8mp-var-dart-dt8mcustomboard.dtb.o
diff --git a/arch/arm/dts/lan9691-bb.dtsi b/arch/arm/dts/lan9691-bb.dtsi
new file mode 100644
index 000000000000..5dbe671e9f67
--- /dev/null
+++ b/arch/arm/dts/lan9691-bb.dtsi
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: (GPL-2.0-or-later OR MIT)
+/*
+ * Copyright (C) 2026 Oleksij Rempel <o.rempel@pengutronix.de>
+ *
+ * Barebox-only overlay for the LAN9691 SoC. dts/src/arm64/microchip/
+ * lan9691.dtsi is a wholesale snapshot of the Linux device trees and
+ * must not be patched locally, so SoC nodes barebox needs but that
+ * snapshot does not carry are added here instead - currently the qspi
+ * controllers.
+ *
+ * Once a dts/ sync brings a node in, drop it from here. When nothing is
+ * left, delete the file; nothing else needs touching.
+ */
+
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+#include <arm64/microchip/clk-lan9691.h>
+
+&axi {
+ qspi0: spi@e0804000 {
+ compatible = "microchip,lan9691-qspi", "microchip,lan966x-qspi";
+ reg = <0xe0804000 0x00000100>,
+ <0x20000000 0x08000000>;
+ reg-names = "qspi_base", "qspi_mmap";
+ interrupts = <GIC_SPI 43 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&fabric_clk>, <&clks GCK_ID_QSPI0>;
+ clock-names = "pclk", "gclk";
+ assigned-clocks = <&clks GCK_ID_QSPI0>;
+ assigned-clock-rates = <100000000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ qspi2: spi@e0834000 {
+ compatible = "microchip,lan9691-qspi", "microchip,lan966x-qspi";
+ reg = <0xe0834000 0x00000100>,
+ <0x30000000 0x04000000>;
+ reg-names = "qspi_base", "qspi_mmap";
+ interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&fabric_clk>, <&clks GCK_ID_QSPI2>;
+ clock-names = "pclk", "gclk";
+ assigned-clocks = <&clks GCK_ID_QSPI2>;
+ assigned-clock-rates = <100000000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+};
diff --git a/arch/arm/dts/novarq-tactical-1000.dts b/arch/arm/dts/novarq-tactical-1000.dts
new file mode 100644
index 000000000000..188ca60d4e80
--- /dev/null
+++ b/arch/arm/dts/novarq-tactical-1000.dts
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: (GPL-2.0-or-later OR MIT)
+/*
+ * Copyright (C) 2026 Oleksij Rempel <o.rempel@pengutronix.de>
+ */
+
+/dts-v1/;
+
+/*
+ * This is the pre-production Tactical 1000, which is laid out as a close
+ * copy of the Microchip EV23X71A reference design. So rather than carry a
+ * board device tree of our own, include the upstream EV23X71A one and
+ * correct the identity on top of it. That describes the hardware
+ * accurately and keeps the delta reviewable.
+ *
+ * The shipping Tactical 1000 is a different board - it gains fans, an
+ * RTC and more - and has its own device tree, lan9696-tactical-1000.dts.
+ * Production hardware should get its own board entry including that
+ * file instead; this one stays for the pre-production boards.
+ */
+#include <arm64/microchip/lan9696-ev23x71a.dts>
+
+#include "lan9691-bb.dtsi"
+
+/ {
+ model = "Novarq Tactical 1000 (pre-production, EV23X71A based)";
+ /*
+ * microchip,ev23x71a is kept as a fallback on purpose: this board
+ * really is that design, so anything keyed on the eval board applies.
+ */
+ compatible = "novarq,tactical-1000-alpha", "microchip,ev23x71a",
+ "microchip,lan9696", "microchip,lan9691";
+};
+
+&{/leds/led-status} {
+ barebox,default-trigger = "heartbeat";
+};
+
+&qspi0 {
+ status = "okay";
+
+ flash@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>;
+ spi-max-frequency = <50000000>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ spi-tx-bus-width = <1>;
+ spi-rx-bus-width = <4>;
+ m25p,fast-read;
+ };
+};
diff --git a/images/Makefile b/images/Makefile
index 91425dd3a068..644b7ee0bf93 100644
--- a/images/Makefile
+++ b/images/Makefile
@@ -193,6 +193,7 @@ include $(srctree)/images/Makefile.versatile
include $(srctree)/images/Makefile.vexpress
include $(srctree)/images/Makefile.xburst
include $(srctree)/images/Makefile.at91
+include $(srctree)/images/Makefile.microchip
include $(srctree)/images/Makefile.zynq
include $(srctree)/images/Makefile.zynqmp
include $(srctree)/images/Makefile.layerscape
diff --git a/images/Makefile.microchip b/images/Makefile.microchip
new file mode 100644
index 000000000000..2368febe843d
--- /dev/null
+++ b/images/Makefile.microchip
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# barebox image generation Makefile for Microchip ARM64 SoCs
+#
+
+# Novarq Tactical 1000 (Microchip LAN9696)
+pblb-$(CONFIG_MACH_NOVARQ_TACTICAL_1000) += start_novarq_tactical_1000
+FILE_barebox-novarq-tactical-1000.img = start_novarq_tactical_1000.pblb
+image-$(CONFIG_MACH_NOVARQ_TACTICAL_1000) += barebox-novarq-tactical-1000.img
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-08-13 9:12 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-13 8:14 [PATCH v2 0/9] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A board Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 1/9] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 2/9] serial: atmel: add lan9696 (Microchip LAN969X) support Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 3/9] clk: add Microchip LAN966X / LAN969X generic clock controller driver Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 4/9] clk: tolerate clocks registered without a name Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 5/9] pinctrl: ocelot: port Microsemi/Microchip Ocelot pinctrl from Linux Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 6/9] gpio: add Microchip SGPIO (serial GPIO) driver Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 7/9] reset: add Microchip sparx5 / LAN969X / LAN966X switch reset driver Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 8/9] spi: atmel-quadspi: add Microchip LAN966X / LAN969X support Oleksij Rempel
2026-08-13 8:14 ` [PATCH v2 9/9] ARM: add Novarq Tactical 1000 board Oleksij Rempel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox