* [PATCH v1 01/10] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs
@ 2026-06-12 5:59 Oleksij Rempel
2026-06-12 5:59 ` [PATCH v1 02/10] serial: atmel: add lan9696 (Microchip LAN969X) support Oleksij Rempel
` (8 more replies)
0 siblings, 9 replies; 17+ messages in thread
From: Oleksij Rempel @ 2026-06-12 5:59 UTC (permalink / raw)
To: barebox; +Cc: Oleksij Rempel
Add the ARCH_MICROCHIP top-level symbol so subsequent peripheral
driver patches (atmel serial, atmel-sdhci, 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.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
arch/arm/Kconfig | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index cb6652001546..5fdec7159827 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -265,6 +265,20 @@ 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_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] 17+ messages in thread
* [PATCH v1 02/10] serial: atmel: add lan9696 (Microchip LAN969X) support
2026-06-12 5:59 [PATCH v1 01/10] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs Oleksij Rempel
@ 2026-06-12 5:59 ` Oleksij Rempel
2026-06-12 12:49 ` Marco Felsch
2026-06-12 5:59 ` [PATCH v1 03/10] clk: add Microchip LAN966X / LAN969X generic clock controller driver Oleksij Rempel
` (7 subsequent siblings)
8 siblings, 1 reply; 17+ messages in thread
From: Oleksij Rempel @ 2026-06-12 5:59 UTC (permalink / raw)
To: barebox; +Cc: 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.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
drivers/serial/Kconfig | 10 +++++++---
drivers/serial/atmel.c | 3 ++-
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 6ac1d94526c2..15cbdafcffcc 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
- default y
- bool "Atmel serial driver"
+ bool "Atmel USART support"
+ depends on ARCH_AT91 || ARCH_MICROCHIP
+ default y if ARCH_AT91
+ 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] 17+ messages in thread
* [PATCH v1 03/10] clk: add Microchip LAN966X / LAN969X generic clock controller driver
2026-06-12 5:59 [PATCH v1 01/10] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs Oleksij Rempel
2026-06-12 5:59 ` [PATCH v1 02/10] serial: atmel: add lan9696 (Microchip LAN969X) support Oleksij Rempel
@ 2026-06-12 5:59 ` Oleksij Rempel
2026-06-12 12:58 ` Marco Felsch
2026-06-15 7:46 ` Sascha Hauer
2026-06-12 5:59 ` [PATCH v1 04/10] clk: tolerate clocks registered without a name Oleksij Rempel
` (6 subsequent siblings)
8 siblings, 2 replies; 17+ messages in thread
From: Oleksij Rempel @ 2026-06-12 5:59 UTC (permalink / raw)
To: barebox; +Cc: 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_* - explicit kfree on probe failure
- 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`.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
drivers/clk/Kconfig | 9 ++
drivers/clk/Makefile | 1 +
drivers/clk/clk-lan966x.c | 325 ++++++++++++++++++++++++++++++++++++++
3 files changed, 335 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..a2f43b2f8dce
--- /dev/null
+++ b/drivers/clk/clk-lan966x.c
@@ -0,0 +1,325 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * 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 = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return ERR_PTR(-ENOMEM);
+
+ priv->reg = base + (i * 4);
+ priv->hw.init = &init;
+ ret = clk_hw_register(dev, &priv->hw);
+ if (ret) {
+ kfree(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 = kzalloc(sizeof(*clk_data), GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
+
+ clk_data->clks = kcalloc(data->num_total_clks,
+ sizeof(*clk_data->clks), GFP_KERNEL);
+ if (!clk_data->clks)
+ return -ENOMEM;
+ 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] 17+ messages in thread
* [PATCH v1 04/10] clk: tolerate clocks registered without a name
2026-06-12 5:59 [PATCH v1 01/10] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs Oleksij Rempel
2026-06-12 5:59 ` [PATCH v1 02/10] serial: atmel: add lan9696 (Microchip LAN969X) support Oleksij Rempel
2026-06-12 5:59 ` [PATCH v1 03/10] clk: add Microchip LAN966X / LAN969X generic clock controller driver Oleksij Rempel
@ 2026-06-12 5:59 ` Oleksij Rempel
2026-06-12 5:59 ` [PATCH v1 05/10] pinctrl: ocelot: port Microsemi/Microchip Ocelot pinctrl from Linux Oleksij Rempel
` (5 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Oleksij Rempel @ 2026-06-12 5:59 UTC (permalink / raw)
To: barebox; +Cc: 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>
---
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] 17+ messages in thread
* [PATCH v1 05/10] pinctrl: ocelot: port Microsemi/Microchip Ocelot pinctrl from Linux
2026-06-12 5:59 [PATCH v1 01/10] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs Oleksij Rempel
` (2 preceding siblings ...)
2026-06-12 5:59 ` [PATCH v1 04/10] clk: tolerate clocks registered without a name Oleksij Rempel
@ 2026-06-12 5:59 ` Oleksij Rempel
2026-06-12 5:59 ` [PATCH v1 06/10] mci: atmel-sdhci: add Microchip LAN9691 / LAN969X SDHCI support Oleksij Rempel
` (4 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Oleksij Rempel @ 2026-06-12 5:59 UTC (permalink / raw)
To: barebox; +Cc: 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
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
drivers/pinctrl/Kconfig | 10 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-ocelot.c | 574 +++++++++++++++++++++++++++++++
3 files changed, 585 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..9445c7bd85d7
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ocelot.c
@@ -0,0 +1,574 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * 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] 17+ messages in thread
* [PATCH v1 06/10] mci: atmel-sdhci: add Microchip LAN9691 / LAN969X SDHCI support
2026-06-12 5:59 [PATCH v1 01/10] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs Oleksij Rempel
` (3 preceding siblings ...)
2026-06-12 5:59 ` [PATCH v1 05/10] pinctrl: ocelot: port Microsemi/Microchip Ocelot pinctrl from Linux Oleksij Rempel
@ 2026-06-12 5:59 ` Oleksij Rempel
2026-06-15 6:17 ` Marco Felsch
2026-06-15 7:25 ` Sascha Hauer
2026-06-12 5:59 ` [PATCH v1 07/10] gpio: add Microchip SGPIO (serial GPIO) driver Oleksij Rempel
` (3 subsequent siblings)
8 siblings, 2 replies; 17+ messages in thread
From: Oleksij Rempel @ 2026-06-12 5:59 UTC (permalink / raw)
To: barebox; +Cc: Oleksij Rempel
Match the "microchip,lan9691-sdhci" compatible and allow building on
ARCH_MICROCHIP (and COMPILE_TEST). LAN969X uses a different GCK rate
(100 MHz) than the AT91 SAMA5D2 (240 MHz), so plumb the per-compatible
rate through device_get_match_data() and fall back to the SAMA5D2 rate
when no match data is supplied.
The LAN969X SDHCI binding doesn't expose the PMC base clock that
sama5d2/sam9x60 carry as "baseclk" (the GCK driver handles the upstream
source internally), so make that lookup optional.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
drivers/mci/Kconfig | 6 +++---
drivers/mci/atmel-sdhci.c | 22 ++++++++++++++++++----
2 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/drivers/mci/Kconfig b/drivers/mci/Kconfig
index b38f7a3bdf8b..8108c7b14848 100644
--- a/drivers/mci/Kconfig
+++ b/drivers/mci/Kconfig
@@ -197,12 +197,12 @@ config MCI_ATMEL
Atmel AT91.
config MCI_ATMEL_SDHCI
- bool "ATMEL SDHCI (sama5d2)"
+ bool "ATMEL SDHCI (sama5d2, lan9691)"
select MCI_SDHCI
- depends on ARCH_AT91
+ depends on ARCH_AT91 || ARCH_MICROCHIP || COMPILE_TEST
help
Enable this entry to add support to read and write SD cards on an
- Atmel sama5d2
+ Atmel sama5d2 or Microchip LAN969X.
config MCI_MMCI
bool "ARM PL180 MMCI"
diff --git a/drivers/mci/atmel-sdhci.c b/drivers/mci/atmel-sdhci.c
index 462cf21bc25f..458d022fa3fa 100644
--- a/drivers/mci/atmel-sdhci.c
+++ b/drivers/mci/atmel-sdhci.c
@@ -18,6 +18,7 @@
#define ATMEL_SDHC_MIN_FREQ 400000
#define ATMEL_SDHC_GCK_RATE 240000000
+#define LAN969X_GCK_RATE 100000000
struct at91_sdhci_priv {
struct at91_sdhci host;
@@ -55,7 +56,8 @@ static int at91_sdhci_mci_init(struct mci_host *mci, struct device *dev)
priv->mci.non_removable, priv->cal_always_on);
}
-static int at91_sdhci_conf_clks(struct at91_sdhci_priv *priv)
+static int at91_sdhci_conf_clks(struct at91_sdhci_priv *priv,
+ unsigned long gck_rate)
{
unsigned long real_gck_rate;
int ret;
@@ -66,7 +68,7 @@ static int at91_sdhci_conf_clks(struct at91_sdhci_priv *priv)
* base clock rate and the clock mult from capabilities.
*/
clk_enable(priv->hclock);
- ret = clk_set_rate(priv->gck, ATMEL_SDHC_GCK_RATE);
+ ret = clk_set_rate(priv->gck, gck_rate);
if (ret < 0) {
clk_disable(priv->hclock);
return ret;
@@ -109,6 +111,11 @@ static int at91_sdhci_probe(struct device *dev)
{
struct at91_sdhci_priv *priv;
struct resource *iores;
+ unsigned long gck_rate;
+
+ gck_rate = (uintptr_t)device_get_match_data(dev);
+ if (!gck_rate)
+ gck_rate = ATMEL_SDHC_GCK_RATE;
priv = xzalloc(sizeof(*priv));
dev->priv = priv;
@@ -119,7 +126,12 @@ static int at91_sdhci_probe(struct device *dev)
return PTR_ERR(iores);
}
- priv->mainck = clk_get(dev, "baseclk");
+ /*
+ * "baseclk" is the PMC base clock on sama5d2/sam9x60. The LAN969X
+ * SDHCI binding doesn't expose it (the GCK driver handles the upstream
+ * source internally), so treat it as optional.
+ */
+ priv->mainck = clk_get_optional(dev, "baseclk");
if (IS_ERR(priv->mainck)) {
dev_err(dev, "failed to get baseclk\n");
return PTR_ERR(priv->mainck);
@@ -147,7 +159,7 @@ static int at91_sdhci_probe(struct device *dev)
at91_sdhci_mmio_init(&priv->host, IOMEM(iores->start));
priv->host.sdhci.mci = &priv->mci;
- priv->gck_rate = at91_sdhci_conf_clks(priv);
+ priv->gck_rate = at91_sdhci_conf_clks(priv, gck_rate);
if (priv->gck_rate < 0)
return priv->gck_rate;
@@ -164,6 +176,8 @@ static int at91_sdhci_probe(struct device *dev)
static const struct of_device_id at91_sdhci_dt_match[] = {
{ .compatible = "atmel,sama5d2-sdhci" },
{ .compatible = "microchip,sam9x60-sdhci" },
+ { .compatible = "microchip,lan9691-sdhci",
+ .data = (void *)LAN969X_GCK_RATE },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, at91_sdhci_dt_match);
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v1 07/10] gpio: add Microchip SGPIO (serial GPIO) driver
2026-06-12 5:59 [PATCH v1 01/10] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs Oleksij Rempel
` (4 preceding siblings ...)
2026-06-12 5:59 ` [PATCH v1 06/10] mci: atmel-sdhci: add Microchip LAN9691 / LAN969X SDHCI support Oleksij Rempel
@ 2026-06-12 5:59 ` Oleksij Rempel
2026-06-15 7:32 ` Sascha Hauer
2026-06-12 5:59 ` [PATCH v1 08/10] reset: add Microchip sparx5 / LAN969X / LAN966X switch reset driver Oleksij Rempel
` (2 subsequent siblings)
8 siblings, 1 reply; 17+ messages in thread
From: Oleksij Rempel @ 2026-06-12 5:59 UTC (permalink / raw)
To: barebox; +Cc: 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.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
drivers/gpio/Kconfig | 8 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-microchip-sgpio.c | 464 ++++++++++++++++++++++++++++
3 files changed, 473 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..b071c569d85a
--- /dev/null
+++ b/drivers/gpio/gpio-microchip-sgpio.c
@@ -0,0 +1,464 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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;
+
+ if (of_device_is_compatible(dev->of_node, "microchip,sparx5-sgpio-bank"))
+ return microchip_sgpio_bank_probe(dev);
+ 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) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ 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");
+ ret = PTR_ERR(clk);
+ goto err;
+ }
+ 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);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores)) {
+ ret = PTR_ERR(iores);
+ goto err;
+ }
+ base = IOMEM(iores->start);
+
+ priv->regs = regmap_init_mmio(dev, base, &sgpio_regmap_config);
+ if (IS_ERR(priv->regs)) {
+ ret = PTR_ERR(priv->regs);
+ goto err;
+ }
+
+ ret = microchip_sgpio_get_ports(priv);
+ if (ret)
+ goto err;
+
+ /* 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);
+ goto err;
+ }
+
+ dev_dbg(dev, "registered: clk %u Hz, %u ports\n", priv->clock,
+ hweight32(priv->ports));
+ return 0;
+
+err:
+ free(priv);
+ return ret;
+}
+
+static const struct of_device_id microchip_sgpio_of_match[] = {
+ { .compatible = "microchip,sparx5-sgpio", .data = &properties_sparx5 },
+ { .compatible = "microchip,sparx5-sgpio-bank" },
+ { /* 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);
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v1 08/10] reset: add Microchip sparx5 / LAN969X / LAN966X switch reset driver
2026-06-12 5:59 [PATCH v1 01/10] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs Oleksij Rempel
` (5 preceding siblings ...)
2026-06-12 5:59 ` [PATCH v1 07/10] gpio: add Microchip SGPIO (serial GPIO) driver Oleksij Rempel
@ 2026-06-12 5:59 ` Oleksij Rempel
2026-06-12 5:59 ` [PATCH v1 09/10] spi: atmel-quadspi: add Microchip LAN966X / LAN969X support Oleksij Rempel
2026-06-12 5:59 ` [PATCH v1 10/10] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A board Oleksij Rempel
8 siblings, 0 replies; 17+ messages in thread
From: Oleksij Rempel @ 2026-06-12 5:59 UTC (permalink / raw)
To: barebox; +Cc: 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
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
drivers/reset/Kconfig | 10 ++
drivers/reset/Makefile | 1 +
drivers/reset/reset-microchip-sparx5.c | 170 +++++++++++++++++++++++++
3 files changed, 181 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..8944e3fc1510
--- /dev/null
+++ b/drivers/reset/reset-microchip-sparx5.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* 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] 17+ messages in thread
* [PATCH v1 09/10] spi: atmel-quadspi: add Microchip LAN966X / LAN969X support
2026-06-12 5:59 [PATCH v1 01/10] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs Oleksij Rempel
` (6 preceding siblings ...)
2026-06-12 5:59 ` [PATCH v1 08/10] reset: add Microchip sparx5 / LAN969X / LAN966X switch reset driver Oleksij Rempel
@ 2026-06-12 5:59 ` Oleksij Rempel
2026-06-12 5:59 ` [PATCH v1 10/10] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A board Oleksij Rempel
8 siblings, 0 replies; 17+ messages in thread
From: Oleksij Rempel @ 2026-06-12 5:59 UTC (permalink / raw)
To: barebox; +Cc: 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.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
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] 17+ messages in thread
* [PATCH v1 10/10] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A board
2026-06-12 5:59 [PATCH v1 01/10] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs Oleksij Rempel
` (7 preceding siblings ...)
2026-06-12 5:59 ` [PATCH v1 09/10] spi: atmel-quadspi: add Microchip LAN966X / LAN969X support Oleksij Rempel
@ 2026-06-12 5:59 ` Oleksij Rempel
8 siblings, 0 replies; 17+ messages in thread
From: Oleksij Rempel @ 2026-06-12 5:59 UTC (permalink / raw)
To: barebox; +Cc: Oleksij Rempel
Introduce the Microchip LAN9696 SoC (lan969x family) and the EV23X71A
evaluation board, booted as BL33 from TF-A.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
arch/arm/Kconfig | 11 +++
arch/arm/boards/Makefile | 1 +
.../microchip-lan9696-ev23x71a/Makefile | 4 ++
.../boards/microchip-lan9696-ev23x71a/board.c | 33 +++++++++
.../microchip-lan9696-ev23x71a/lowlevel.c | 34 ++++++++++
arch/arm/dts/Makefile | 1 +
arch/arm/dts/lan9691-bb.dtsi | 68 +++++++++++++++++++
arch/arm/dts/lan9696-ev23x71a.dts | 65 ++++++++++++++++++
images/Makefile | 1 +
images/Makefile.microchip | 9 +++
10 files changed, 227 insertions(+)
create mode 100644 arch/arm/boards/microchip-lan9696-ev23x71a/Makefile
create mode 100644 arch/arm/boards/microchip-lan9696-ev23x71a/board.c
create mode 100644 arch/arm/boards/microchip-lan9696-ev23x71a/lowlevel.c
create mode 100644 arch/arm/dts/lan9691-bb.dtsi
create mode 100644 arch/arm/dts/lan9696-ev23x71a.dts
create mode 100644 images/Makefile.microchip
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 5fdec7159827..26f0d040ca68 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -301,6 +301,17 @@ source "arch/arm/mach-tegra/Kconfig"
source "arch/arm/mach-zynq/Kconfig"
source "arch/arm/mach-zynqmp/Kconfig"
+if ARCH_MICROCHIP
+
+config MACH_MICROCHIP_LAN9696_EV23X71A
+ bool "Microchip LAN9696 EV23X71A Evaluation Board"
+ help
+ Support for Microchip LAN9696 EV23X71A Evaluation Board.
+ This is a 28-port Ethernet switch SoC with ARM64 CPU and
+ AT91/SAMA5 compatible peripherals.
+
+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 f08983f4d23f..4a15cb8e5909 100644
--- a/arch/arm/boards/Makefile
+++ b/arch/arm/boards/Makefile
@@ -116,6 +116,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_MICROCHIP_LAN9696_EV23X71A) += microchip-lan9696-ev23x71a/
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/microchip-lan9696-ev23x71a/Makefile b/arch/arm/boards/microchip-lan9696-ev23x71a/Makefile
new file mode 100644
index 000000000000..5678718188b9
--- /dev/null
+++ b/arch/arm/boards/microchip-lan9696-ev23x71a/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/microchip-lan9696-ev23x71a/board.c b/arch/arm/boards/microchip-lan9696-ev23x71a/board.c
new file mode 100644
index 000000000000..2707a425659d
--- /dev/null
+++ b/arch/arm/boards/microchip-lan9696-ev23x71a/board.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Oleksij Rempel <o.rempel@pengutronix.de>
+ */
+
+#include <bbu.h>
+#include <bootsource.h>
+#include <common.h>
+#include <deep-probe.h>
+#include <init.h>
+
+static int lan9696_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 lan9696_of_match[] = {
+ {
+ .compatible = "microchip,ev23x71a",
+ },
+ { /* sentinel */ },
+};
+
+static struct driver lan9696_board_driver = {
+ .name = "board-lan9696-ev23x71a",
+ .probe = lan9696_probe,
+ .of_compatible = lan9696_of_match,
+};
+coredevice_platform_driver(lan9696_board_driver);
+
+BAREBOX_DEEP_PROBE_ENABLE(lan9696_of_match);
diff --git a/arch/arm/boards/microchip-lan9696-ev23x71a/lowlevel.c b/arch/arm/boards/microchip-lan9696-ev23x71a/lowlevel.c
new file mode 100644
index 000000000000..c95bbd4cccc0
--- /dev/null
+++ b/arch/arm/boards/microchip-lan9696-ev23x71a/lowlevel.c
@@ -0,0 +1,34 @@
+// 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_lan9696_ev23x71a_start[];
+
+/*
+ * EV23x71a memory map.
+ *
+ * The board has 896 MiB of DDR mapped at 0x60000000. TF-A's BL31
+ * (EL3 runtime) lives in the top 2 MiB of that region, so the
+ * usable area for barebox + payload ends at LAN9696_DRAM_USABLE_END.
+ */
+#define LAN9696_DRAM_BASE UL(0x60000000)
+#define LAN9696_DRAM_SIZE (896 * SZ_1M)
+#define LAN9696_BL31_RESERVE SZ_2M
+#define LAN9696_DRAM_USABLE (LAN9696_DRAM_SIZE - LAN9696_BL31_RESERVE)
+#define LAN9696_DRAM_USABLE_END (LAN9696_DRAM_BASE + LAN9696_DRAM_USABLE)
+
+ENTRY_FUNCTION_WITHSTACK(start_lan9696_ev23x71a,
+ LAN9696_DRAM_USABLE_END, r0, r1, r2)
+{
+ arm_cpu_lowlevel_init();
+ relocate_to_current_adr();
+ setup_c();
+
+ barebox_arm_entry(LAN9696_DRAM_BASE, LAN9696_DRAM_USABLE,
+ runtime_address(__dtb_z_lan9696_ev23x71a_start));
+}
diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile
index 55149d9c3675..f993da7ebb65 100644
--- a/arch/arm/dts/Makefile
+++ b/arch/arm/dts/Makefile
@@ -256,6 +256,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_MICROCHIP_LAN9696_EV23X71A) += lan9696-ev23x71a.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..72e1b870f68c
--- /dev/null
+++ b/arch/arm/dts/lan9691-bb.dtsi
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: (GPL-2.0-or-later OR MIT)
+/*
+ * Barebox-only overlay for the LAN9691 SoC. Adds nodes that exist in the
+ * upstream Linux DTSI but are not yet present in the snapshot under
+ * barebox/dts/src/ (which is auto-synced from Linux and must not be patched
+ * locally).
+ *
+ * Nodes here are copied verbatim from
+ * linux/arch/arm64/boot/dts/microchip/lan9691.dtsi
+ * so that when the upstream snapshot catches up, this file can be deleted
+ * (or trimmed) without touching anything else.
+ */
+
+#include <arm64/microchip/clk-lan9691.h>
+
+&axi {
+ sdmmc0: mmc@e0830000 {
+ compatible = "microchip,lan9691-sdhci";
+ reg = <0xe0830000 0x00000300>;
+ interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clks GCK_ID_SDMMC0>, <&clks GCK_ID_SDMMC0>;
+ clock-names = "hclock", "multclk";
+ assigned-clocks = <&clks GCK_ID_SDMMC0>;
+ assigned-clock-rates = <100000000>;
+ status = "disabled";
+ };
+
+ sdmmc1: mmc@e0838000 {
+ compatible = "microchip,lan9691-sdhci";
+ reg = <0xe0838000 0x00000300>;
+ interrupts = <GIC_SPI 50 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clks GCK_ID_SDMMC1>, <&clks GCK_ID_SDMMC1>;
+ clock-names = "hclock", "multclk";
+ assigned-clocks = <&clks GCK_ID_SDMMC1>;
+ assigned-clock-rates = <45000000>;
+ status = "disabled";
+ };
+
+ 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/lan9696-ev23x71a.dts b/arch/arm/dts/lan9696-ev23x71a.dts
new file mode 100644
index 000000000000..e72abca6ed39
--- /dev/null
+++ b/arch/arm/dts/lan9696-ev23x71a.dts
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: (GPL-2.0-or-later OR MIT)
+/*
+ * Copyright (C) 2025 Microchip Technology Inc.
+ */
+
+/dts-v1/;
+
+/* Include upstream device tree from Linux */
+#include <arm64/microchip/lan9696-ev23x71a.dts>
+
+#include "lan9691-bb.dtsi"
+
+/* Ensure serial console is enabled */
+&usart0 {
+ status = "okay";
+};
+
+&sdmmc0 {
+ pinctrl-0 = <&emmc_sd_pins>;
+ pinctrl-names = "default";
+ max-frequency = <100000000>;
+ bus-width = <8>;
+ mmc-ddr-1_8v;
+ mmc-hs200-1_8v;
+ non-removable;
+ disable-wp;
+ status = "okay";
+};
+
+&{/leds/led-status} {
+ barebox,default-trigger = "heartbeat";
+};
+
+&{/leds/led-sfp1-green} {
+ barebox,default-trigger = "heartbeat";
+};
+
+&sgpio {
+ pinctrl-0 = <&sgpio_pins>;
+ pinctrl-names = "default";
+ microchip,sgpio-port-ranges = <0 1>, <6 9>;
+ status = "okay";
+
+ gpio@0 {
+ ngpios = <128>;
+ };
+ gpio@1 {
+ ngpios = <128>;
+ };
+};
+
+&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..161396c73542
--- /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
+#
+
+# Microchip LAN9696 EV23X71A Evaluation Board
+pblb-$(CONFIG_MACH_MICROCHIP_LAN9696_EV23X71A) += start_lan9696_ev23x71a
+FILE_barebox-microchip-lan9696-ev23x71a.img = start_lan9696_ev23x71a.pblb
+image-$(CONFIG_MACH_MICROCHIP_LAN9696_EV23X71A) += barebox-microchip-lan9696-ev23x71a.img
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v1 02/10] serial: atmel: add lan9696 (Microchip LAN969X) support
2026-06-12 5:59 ` [PATCH v1 02/10] serial: atmel: add lan9696 (Microchip LAN969X) support Oleksij Rempel
@ 2026-06-12 12:49 ` Marco Felsch
0 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2026-06-12 12:49 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: barebox
On 26-06-12, Oleksij Rempel wrote:
> 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.
>
> Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
> ---
> drivers/serial/Kconfig | 10 +++++++---
> drivers/serial/atmel.c | 3 ++-
> 2 files changed, 9 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
> index 6ac1d94526c2..15cbdafcffcc 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
> - default y
> - bool "Atmel serial driver"
> + bool "Atmel USART support"
> + depends on ARCH_AT91 || ARCH_MICROCHIP
> + default y if ARCH_AT91
Just keep 'default y'?
Regards,
Marco
> + 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
>
>
>
--
#gernperDu
#CallMeByMyFirstName
Pengutronix e.K. | |
Steuerwalder Str. 21 | https://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v1 03/10] clk: add Microchip LAN966X / LAN969X generic clock controller driver
2026-06-12 5:59 ` [PATCH v1 03/10] clk: add Microchip LAN966X / LAN969X generic clock controller driver Oleksij Rempel
@ 2026-06-12 12:58 ` Marco Felsch
2026-06-15 7:02 ` Sascha Hauer
2026-06-15 7:46 ` Sascha Hauer
1 sibling, 1 reply; 17+ messages in thread
From: Marco Felsch @ 2026-06-12 12:58 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: barebox
Hi Oleksij,
thanks for your patch, please see below.
On 26-06-12, Oleksij Rempel wrote:
> 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_* - explicit kfree on probe failure
> - 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`.
>
> Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
> ---
> drivers/clk/Kconfig | 9 ++
> drivers/clk/Makefile | 1 +
> drivers/clk/clk-lan966x.c | 325 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 335 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..a2f43b2f8dce
> --- /dev/null
> +++ b/drivers/clk/clk-lan966x.c
> @@ -0,0 +1,325 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * 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;
This seems odd, is this also part of the Linux kernel? Furthermore it's
only used during lan966x_gck_clk_register().
> +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 = kzalloc(sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return ERR_PTR(-ENOMEM);
> +
> + priv->reg = base + (i * 4);
> + priv->hw.init = &init;
> + ret = clk_hw_register(dev, &priv->hw);
> + if (ret) {
> + kfree(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 = kzalloc(sizeof(*clk_data), GFP_KERNEL);
> + if (!clk_data)
> + return -ENOMEM;
> +
> + clk_data->clks = kcalloc(data->num_total_clks,
> + sizeof(*clk_data->clks), GFP_KERNEL);
> + if (!clk_data->clks)
> + return -ENOMEM;
^
This leaks the clk_data memory.
> + clk_data->clk_num = data->num_total_clks;
> +
> + iores = dev_request_mem_resource(dev, 0);
> + if (IS_ERR(iores))
> + return PTR_ERR(iores);
Since here you leak both clk_data and clks. Please check the cleanup
path.
> + 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);
This leaks a proper cleanup for previous
allocated clk_hw's.
> + }
> + 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
>
>
>
--
#gernperDu
#CallMeByMyFirstName
Pengutronix e.K. | |
Steuerwalder Str. 21 | https://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v1 06/10] mci: atmel-sdhci: add Microchip LAN9691 / LAN969X SDHCI support
2026-06-12 5:59 ` [PATCH v1 06/10] mci: atmel-sdhci: add Microchip LAN9691 / LAN969X SDHCI support Oleksij Rempel
@ 2026-06-15 6:17 ` Marco Felsch
2026-06-15 7:25 ` Sascha Hauer
1 sibling, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2026-06-15 6:17 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: barebox
On 26-06-12, Oleksij Rempel wrote:
> Match the "microchip,lan9691-sdhci" compatible and allow building on
> ARCH_MICROCHIP (and COMPILE_TEST). LAN969X uses a different GCK rate
> (100 MHz) than the AT91 SAMA5D2 (240 MHz), so plumb the per-compatible
> rate through device_get_match_data() and fall back to the SAMA5D2 rate
> when no match data is supplied.
>
> The LAN969X SDHCI binding doesn't expose the PMC base clock that
> sama5d2/sam9x60 carry as "baseclk" (the GCK driver handles the upstream
> source internally), so make that lookup optional.
>
> Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
> ---
> drivers/mci/Kconfig | 6 +++---
> drivers/mci/atmel-sdhci.c | 22 ++++++++++++++++++----
> 2 files changed, 21 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/mci/Kconfig b/drivers/mci/Kconfig
> index b38f7a3bdf8b..8108c7b14848 100644
> --- a/drivers/mci/Kconfig
> +++ b/drivers/mci/Kconfig
> @@ -197,12 +197,12 @@ config MCI_ATMEL
> Atmel AT91.
>
> config MCI_ATMEL_SDHCI
> - bool "ATMEL SDHCI (sama5d2)"
> + bool "ATMEL SDHCI (sama5d2, lan9691)"
> select MCI_SDHCI
> - depends on ARCH_AT91
> + depends on ARCH_AT91 || ARCH_MICROCHIP || COMPILE_TEST
> help
> Enable this entry to add support to read and write SD cards on an
> - Atmel sama5d2
> + Atmel sama5d2 or Microchip LAN969X.
>
> config MCI_MMCI
> bool "ARM PL180 MMCI"
> diff --git a/drivers/mci/atmel-sdhci.c b/drivers/mci/atmel-sdhci.c
> index 462cf21bc25f..458d022fa3fa 100644
> --- a/drivers/mci/atmel-sdhci.c
> +++ b/drivers/mci/atmel-sdhci.c
> @@ -18,6 +18,7 @@
>
> #define ATMEL_SDHC_MIN_FREQ 400000
> #define ATMEL_SDHC_GCK_RATE 240000000
> +#define LAN969X_GCK_RATE 100000000
>
> struct at91_sdhci_priv {
> struct at91_sdhci host;
> @@ -55,7 +56,8 @@ static int at91_sdhci_mci_init(struct mci_host *mci, struct device *dev)
> priv->mci.non_removable, priv->cal_always_on);
> }
>
> -static int at91_sdhci_conf_clks(struct at91_sdhci_priv *priv)
> +static int at91_sdhci_conf_clks(struct at91_sdhci_priv *priv,
> + unsigned long gck_rate)
> {
> unsigned long real_gck_rate;
> int ret;
> @@ -66,7 +68,7 @@ static int at91_sdhci_conf_clks(struct at91_sdhci_priv *priv)
> * base clock rate and the clock mult from capabilities.
> */
> clk_enable(priv->hclock);
> - ret = clk_set_rate(priv->gck, ATMEL_SDHC_GCK_RATE);
> + ret = clk_set_rate(priv->gck, gck_rate);
> if (ret < 0) {
> clk_disable(priv->hclock);
> return ret;
> @@ -109,6 +111,11 @@ static int at91_sdhci_probe(struct device *dev)
> {
> struct at91_sdhci_priv *priv;
> struct resource *iores;
> + unsigned long gck_rate;
> +
> + gck_rate = (uintptr_t)device_get_match_data(dev);
> + if (!gck_rate)
> + gck_rate = ATMEL_SDHC_GCK_RATE;
Could be set as of_device_data as well to avoid this default assingment.
> priv = xzalloc(sizeof(*priv));
> dev->priv = priv;
> @@ -119,7 +126,12 @@ static int at91_sdhci_probe(struct device *dev)
> return PTR_ERR(iores);
> }
>
> - priv->mainck = clk_get(dev, "baseclk");
> + /*
> + * "baseclk" is the PMC base clock on sama5d2/sam9x60. The LAN969X
> + * SDHCI binding doesn't expose it (the GCK driver handles the upstream
> + * source internally), so treat it as optional.
> + */
> + priv->mainck = clk_get_optional(dev, "baseclk");
This commit may introduce bugs in case the clock is not found and you're
running on a SoC which requires it. This is no longer covered.
Please see Linux commit 3976656d67c1 ("mmc: sdhci-of-at91: rework clocks
management to support SAM9x60 device"). This commits introduces a proper
driver struct and the optional internal clock handling.
Regards,
Marco
> if (IS_ERR(priv->mainck)) {
> dev_err(dev, "failed to get baseclk\n");
> return PTR_ERR(priv->mainck);
> @@ -147,7 +159,7 @@ static int at91_sdhci_probe(struct device *dev)
> at91_sdhci_mmio_init(&priv->host, IOMEM(iores->start));
> priv->host.sdhci.mci = &priv->mci;
>
> - priv->gck_rate = at91_sdhci_conf_clks(priv);
> + priv->gck_rate = at91_sdhci_conf_clks(priv, gck_rate);
> if (priv->gck_rate < 0)
> return priv->gck_rate;
>
> @@ -164,6 +176,8 @@ static int at91_sdhci_probe(struct device *dev)
> static const struct of_device_id at91_sdhci_dt_match[] = {
> { .compatible = "atmel,sama5d2-sdhci" },
> { .compatible = "microchip,sam9x60-sdhci" },
> + { .compatible = "microchip,lan9691-sdhci",
> + .data = (void *)LAN969X_GCK_RATE },
> { /* sentinel */ }
> };
> MODULE_DEVICE_TABLE(of, at91_sdhci_dt_match);
> --
> 2.47.3
>
>
>
--
#gernperDu
#CallMeByMyFirstName
Pengutronix e.K. | |
Steuerwalder Str. 21 | https://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v1 03/10] clk: add Microchip LAN966X / LAN969X generic clock controller driver
2026-06-12 12:58 ` Marco Felsch
@ 2026-06-15 7:02 ` Sascha Hauer
0 siblings, 0 replies; 17+ messages in thread
From: Sascha Hauer @ 2026-06-15 7:02 UTC (permalink / raw)
To: Marco Felsch; +Cc: Oleksij Rempel, barebox
On 2026-06-12 14:58, Marco Felsch wrote:
> Hi Oleksij,
>
> thanks for your patch, please see below.
>
> On 26-06-12, Oleksij Rempel wrote:
> > 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_* - explicit kfree on probe failure
> > - 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`.
> >
> > Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
> > ---
> > drivers/clk/Kconfig | 9 ++
> > drivers/clk/Makefile | 1 +
> > drivers/clk/clk-lan966x.c | 325 ++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 335 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..a2f43b2f8dce
> > --- /dev/null
> > +++ b/drivers/clk/clk-lan966x.c
> > @@ -0,0 +1,325 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * 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;
>
> This seems odd, is this also part of the Linux kernel? Furthermore it's
> only used during lan966x_gck_clk_register().
>
> > +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 = kzalloc(sizeof(*priv), GFP_KERNEL);
> > + if (!priv)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + priv->reg = base + (i * 4);
> > + priv->hw.init = &init;
> > + ret = clk_hw_register(dev, &priv->hw);
> > + if (ret) {
> > + kfree(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 = kzalloc(sizeof(*clk_data), GFP_KERNEL);
> > + if (!clk_data)
> > + return -ENOMEM;
> > +
> > + clk_data->clks = kcalloc(data->num_total_clks,
> > + sizeof(*clk_data->clks), GFP_KERNEL);
> > + if (!clk_data->clks)
> > + return -ENOMEM;
> ^
> This leaks the clk_data memory.
Usually we ignore the memory leaks in the probe paths as there's no real
gain in freeing a few bytes from a functions error path that is executed
only once.
Sascha
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v1 06/10] mci: atmel-sdhci: add Microchip LAN9691 / LAN969X SDHCI support
2026-06-12 5:59 ` [PATCH v1 06/10] mci: atmel-sdhci: add Microchip LAN9691 / LAN969X SDHCI support Oleksij Rempel
2026-06-15 6:17 ` Marco Felsch
@ 2026-06-15 7:25 ` Sascha Hauer
1 sibling, 0 replies; 17+ messages in thread
From: Sascha Hauer @ 2026-06-15 7:25 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: barebox, Oleksij Rempel
On 2026-06-12 07:59, Oleksij Rempel wrote:
> Match the "microchip,lan9691-sdhci" compatible and allow building on
> ARCH_MICROCHIP (and COMPILE_TEST). LAN969X uses a different GCK rate
> (100 MHz) than the AT91 SAMA5D2 (240 MHz), so plumb the per-compatible
> rate through device_get_match_data() and fall back to the SAMA5D2 rate
> when no match data is supplied.
>
> The LAN969X SDHCI binding doesn't expose the PMC base clock that
> sama5d2/sam9x60 carry as "baseclk" (the GCK driver handles the upstream
> source internally), so make that lookup optional.
>
> Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
> ---
> drivers/mci/Kconfig | 6 +++---
> drivers/mci/atmel-sdhci.c | 22 ++++++++++++++++++----
> 2 files changed, 21 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/mci/Kconfig b/drivers/mci/Kconfig
> index b38f7a3bdf8b..8108c7b14848 100644
> --- a/drivers/mci/Kconfig
> +++ b/drivers/mci/Kconfig
> @@ -197,12 +197,12 @@ config MCI_ATMEL
> Atmel AT91.
>
> config MCI_ATMEL_SDHCI
> - bool "ATMEL SDHCI (sama5d2)"
> + bool "ATMEL SDHCI (sama5d2, lan9691)"
> select MCI_SDHCI
> - depends on ARCH_AT91
> + depends on ARCH_AT91 || ARCH_MICROCHIP || COMPILE_TEST
> help
> Enable this entry to add support to read and write SD cards on an
> - Atmel sama5d2
> + Atmel sama5d2 or Microchip LAN969X.
>
> config MCI_MMCI
> bool "ARM PL180 MMCI"
> diff --git a/drivers/mci/atmel-sdhci.c b/drivers/mci/atmel-sdhci.c
> index 462cf21bc25f..458d022fa3fa 100644
> --- a/drivers/mci/atmel-sdhci.c
> +++ b/drivers/mci/atmel-sdhci.c
> @@ -18,6 +18,7 @@
>
> #define ATMEL_SDHC_MIN_FREQ 400000
> #define ATMEL_SDHC_GCK_RATE 240000000
> +#define LAN969X_GCK_RATE 100000000
>
> struct at91_sdhci_priv {
> struct at91_sdhci host;
> @@ -55,7 +56,8 @@ static int at91_sdhci_mci_init(struct mci_host *mci, struct device *dev)
> priv->mci.non_removable, priv->cal_always_on);
> }
>
> -static int at91_sdhci_conf_clks(struct at91_sdhci_priv *priv)
> +static int at91_sdhci_conf_clks(struct at91_sdhci_priv *priv,
> + unsigned long gck_rate)
> {
> unsigned long real_gck_rate;
> int ret;
> @@ -66,7 +68,7 @@ static int at91_sdhci_conf_clks(struct at91_sdhci_priv *priv)
> * base clock rate and the clock mult from capabilities.
> */
> clk_enable(priv->hclock);
> - ret = clk_set_rate(priv->gck, ATMEL_SDHC_GCK_RATE);
> + ret = clk_set_rate(priv->gck, gck_rate);
Do we need this clk_set_rate() call at all?
dts/src/arm/microchip/sama5d2.dtsi sets this clock via
assigned-clock-rates, so we shouldn't have to repeat that here. Only
thing is that it's set to 480MHz in the dtsi and 240MHz in the driver.
Sascha
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v1 07/10] gpio: add Microchip SGPIO (serial GPIO) driver
2026-06-12 5:59 ` [PATCH v1 07/10] gpio: add Microchip SGPIO (serial GPIO) driver Oleksij Rempel
@ 2026-06-15 7:32 ` Sascha Hauer
0 siblings, 0 replies; 17+ messages in thread
From: Sascha Hauer @ 2026-06-15 7:32 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: barebox, Oleksij Rempel
On 2026-06-12 07:59, Oleksij Rempel wrote:
> 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.
>
> Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
> ---
> drivers/gpio/Kconfig | 8 +
> drivers/gpio/Makefile | 1 +
> drivers/gpio/gpio-microchip-sgpio.c | 464 ++++++++++++++++++++++++++++
> 3 files changed, 473 insertions(+)
> create mode 100644 drivers/gpio/gpio-microchip-sgpio.c
>
> +
> +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;
> +
> + if (of_device_is_compatible(dev->of_node, "microchip,sparx5-sgpio-bank"))
> + return microchip_sgpio_bank_probe(dev);
I think it would be cleaner just to register two drivers instead of
repurposing a single driver for something different.
Sascha
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v1 03/10] clk: add Microchip LAN966X / LAN969X generic clock controller driver
2026-06-12 5:59 ` [PATCH v1 03/10] clk: add Microchip LAN966X / LAN969X generic clock controller driver Oleksij Rempel
2026-06-12 12:58 ` Marco Felsch
@ 2026-06-15 7:46 ` Sascha Hauer
1 sibling, 0 replies; 17+ messages in thread
From: Sascha Hauer @ 2026-06-15 7:46 UTC (permalink / raw)
To: Oleksij Rempel; +Cc: barebox, Oleksij Rempel
On 2026-06-12 07:59, Oleksij Rempel wrote:
> 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_* - explicit kfree on probe failure
> - 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`.
>
> Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
> ---
> drivers/clk/Kconfig | 9 ++
> drivers/clk/Makefile | 1 +
> drivers/clk/clk-lan966x.c | 325 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 335 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..a2f43b2f8dce
> --- /dev/null
> +++ b/drivers/clk/clk-lan966x.c
> @@ -0,0 +1,325 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * 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.
We have a new // SPDX-Comment: Origin-URL: tag which you could use here
and other code you are adding from Linux.
Sascha
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-06-15 7:47 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-12 5:59 [PATCH v1 01/10] ARM: introduce ARCH_MICROCHIP for ARM64 Microchip SoCs Oleksij Rempel
2026-06-12 5:59 ` [PATCH v1 02/10] serial: atmel: add lan9696 (Microchip LAN969X) support Oleksij Rempel
2026-06-12 12:49 ` Marco Felsch
2026-06-12 5:59 ` [PATCH v1 03/10] clk: add Microchip LAN966X / LAN969X generic clock controller driver Oleksij Rempel
2026-06-12 12:58 ` Marco Felsch
2026-06-15 7:02 ` Sascha Hauer
2026-06-15 7:46 ` Sascha Hauer
2026-06-12 5:59 ` [PATCH v1 04/10] clk: tolerate clocks registered without a name Oleksij Rempel
2026-06-12 5:59 ` [PATCH v1 05/10] pinctrl: ocelot: port Microsemi/Microchip Ocelot pinctrl from Linux Oleksij Rempel
2026-06-12 5:59 ` [PATCH v1 06/10] mci: atmel-sdhci: add Microchip LAN9691 / LAN969X SDHCI support Oleksij Rempel
2026-06-15 6:17 ` Marco Felsch
2026-06-15 7:25 ` Sascha Hauer
2026-06-12 5:59 ` [PATCH v1 07/10] gpio: add Microchip SGPIO (serial GPIO) driver Oleksij Rempel
2026-06-15 7:32 ` Sascha Hauer
2026-06-12 5:59 ` [PATCH v1 08/10] reset: add Microchip sparx5 / LAN969X / LAN966X switch reset driver Oleksij Rempel
2026-06-12 5:59 ` [PATCH v1 09/10] spi: atmel-quadspi: add Microchip LAN966X / LAN969X support Oleksij Rempel
2026-06-12 5:59 ` [PATCH v1 10/10] ARM: add Microchip LAN9696 (LAN969X) SoC and EV23X71A 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