* [PATCH 01/19] ARM: mvebu: add board support for Netgear RN102
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-27 13:27 ` Sascha Hauer
2026-07-23 13:57 ` [PATCH 02/19] ARM: mvebu: add lowlevel " Luca Lauro via B4 Relay
` (17 subsequent siblings)
18 siblings, 1 reply; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
arch/arm/boards/Makefile | 1 +
arch/arm/boards/netgear-rn102/Makefile | 4 +
arch/arm/boards/netgear-rn102/board.c | 243 +++++++++++++++++++++++++++++++++
rebuild-series.sh | 39 ++++++
4 files changed, 287 insertions(+)
diff --git a/arch/arm/boards/Makefile b/arch/arm/boards/Makefile
index dd2f2c324e..aca1b45a81 100644
--- a/arch/arm/boards/Makefile
+++ b/arch/arm/boards/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_MACH_MARVELL_ARMADA_XP_DB) += marvell-armada-xp-db/
obj-$(CONFIG_MACH_MX23EVK) += freescale-mx23-evk/
obj-$(CONFIG_MACH_MX28EVK) += freescale-mx28-evk/
obj-$(CONFIG_MACH_MYIRTECH_X335X) += myirtech-x335x/
+obj-$(CONFIG_MACH_NETGEAR_RN102) += netgear-rn102/
obj-$(CONFIG_MACH_NETGEAR_RN104) += netgear-rn104/
obj-$(CONFIG_MACH_NETGEAR_RN2120) += netgear-rn2120/
obj-$(CONFIG_MACH_NVIDIA_BEAVER) += nvidia-beaver/
diff --git a/arch/arm/boards/netgear-rn102/Makefile b/arch/arm/boards/netgear-rn102/Makefile
new file mode 100644
index 0000000000..da63d2625f
--- /dev/null
+++ b/arch/arm/boards/netgear-rn102/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-y += board.o
+lwl-y += lowlevel.o
diff --git a/arch/arm/boards/netgear-rn102/board.c b/arch/arm/boards/netgear-rn102/board.c
new file mode 100644
index 0000000000..c6673e9356
--- /dev/null
+++ b/arch/arm/boards/netgear-rn102/board.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <common.h>
+#include <init.h>
+#include <gpio.h>
+#include <driver.h>
+#include <linux/device.h>
+#include <linux/mbus.h>
+#include <mach/mvebu/armada-370-xp-regs.h>
+#include <bbu.h>
+
+/*
+ * Early GPIO0 MMIO
+ *
+ * GPIO driver arrives too late for the disks to be
+ * ready in time for AHCI driver probe.
+ * So we use GPIO0 direct access for:
+ * - reading disk presence monitoring pins
+ * - enable powerup for bays that detect disk presence
+ * - display bay status with dedicated LEDs
+ */
+#define GPIO0_BASE (ARMADA_370_XP_INT_REGS_BASE + 0x18100)
+#define GPIO_OUT 0x00
+#define GPIO_OUT_EN 0x04
+#define GPIO_BLINK_EN 0x08
+#define GPIO_IN 0x10
+#define GPIO_BLINK_CNT_SEL 0x20
+#define GPIO_BLINK_CNT_A_ON 0xc0
+#define GPIO_BLINK_CNT_A_OFF 0xc4
+
+static inline void gpio0_set_output(int pin)
+{
+ u32 v = readl(GPIO0_BASE + GPIO_OUT_EN);
+
+ v &= ~(1 << pin);
+ writel(v, GPIO0_BASE + GPIO_OUT_EN);
+}
+
+static inline void gpio0_set_input(int pin)
+{
+ u32 v = readl(GPIO0_BASE + GPIO_OUT_EN);
+
+ v |= (1 << pin);
+ writel(v, GPIO0_BASE + GPIO_OUT_EN);
+}
+
+static inline void gpio0_write(int pin, int val)
+{
+ u32 v = readl(GPIO0_BASE + GPIO_OUT);
+
+ if (val)
+ v |= (1 << pin);
+ else
+ v &= ~(1 << pin);
+
+ writel(v, GPIO0_BASE + GPIO_OUT);
+}
+
+static inline int gpio0_read(int pin)
+{
+ return !!(readl(GPIO0_BASE + GPIO_IN) & (1 << pin));
+}
+
+static void gpio0_blink(int pin, int on_ms, int off_ms)
+{
+ u32 v;
+
+ gpio0_set_output(pin);
+
+ /* set blink counter A on and off time in core clok cycles */
+ writel(10*on_ms, GPIO0_BASE + GPIO_BLINK_CNT_A_ON);
+ writel(10*off_ms, GPIO0_BASE + GPIO_BLINK_CNT_A_OFF);
+
+ /* use blink counter A for selected pin */
+ v = readl(GPIO0_BASE + GPIO_BLINK_CNT_SEL);
+ v &= ~(1 << pin);
+ writel(v, GPIO0_BASE + GPIO_BLINK_CNT_SEL);
+
+ /* enable blink for selected pin */
+ v = readl(GPIO0_BASE + GPIO_BLINK_EN);
+ v |= (1 << pin);
+ writel(v, GPIO0_BASE + GPIO_BLINK_EN);
+}
+
+static void gpio0_blink_disable(int pin)
+{
+ u32 v;
+
+ v = readl(GPIO0_BASE + GPIO_BLINK_EN);
+ v &= ~(1 << pin);
+ writel(v, GPIO0_BASE + GPIO_BLINK_EN);
+}
+
+/* HDD bays description */
+
+enum disk_state {
+ DISK_ABSENT = 0,
+ DISK_PRESENT,
+ DISK_READY,
+};
+
+struct rn102_disk_bay {
+ int gpio_detect; /* input, active-low */
+ int gpio_power; /* output */
+ int gpio_led; /* output, active-low */
+ enum disk_state state;
+};
+
+#define RN102_NUM_DISK_BAYS 2
+
+static struct rn102_disk_bay rn102_bays[RN102_NUM_DISK_BAYS] = {
+ { 12, 13, 15, DISK_ABSENT }, /* Bay 1, default to disk absent */
+ { 10, 11, 14, DISK_ABSENT }, /* Bay 2, default to disk absent */
+};
+
+static void setup_bays(void) {
+ pr_info("Early disk power-on...\n");
+
+ for (int i = 0; i < RN102_NUM_DISK_BAYS; i++) {
+ int present;
+
+ gpio0_set_input(rn102_bays[i].gpio_detect);
+ present = (gpio0_read(rn102_bays[i].gpio_detect) == 0);
+
+ gpio0_set_output(rn102_bays[i].gpio_power);
+
+ if (present) {
+ pr_info("Bay %d: disk detected, powering on\n", i + 1);
+ gpio0_blink(rn102_bays[i].gpio_led, 500, 500);
+ gpio0_write(rn102_bays[i].gpio_power, 1);
+ rn102_bays[i].state = DISK_PRESENT;
+ } else {
+ pr_info("Bay %d empty\n", i + 1);
+ gpio0_write(rn102_bays[i].gpio_led, 0);
+ gpio0_write(rn102_bays[i].gpio_power, 0);
+ rn102_bays[i].state = DISK_ABSENT;
+ }
+ }
+}
+
+/*
+ * After waiting for present disks spinup,
+ * we ask explicitly for corresponding ATA devices probe.
+ */
+static void init_disks(void) {
+ pr_info("Waiting for disks spinup...\n");
+ mdelay(8000);
+
+ pr_info("Detecting ATA devices:\n");
+ for (int i = 0; i < RN102_NUM_DISK_BAYS; i++) {
+ char name[8];
+ struct device *dev;
+
+ if (rn102_bays[i].state != DISK_PRESENT) {
+ gpio0_write(rn102_bays[i].gpio_power, 0);
+ gpio0_blink_disable(rn102_bays[i].gpio_led);
+ gpio0_write(rn102_bays[i].gpio_led, 0);
+ continue;
+ }
+
+ snprintf(name, sizeof(name), "ata%d", i);
+
+ dev = get_device_by_name(name);
+ if (!dev) {
+ pr_warn("%s not found\n", name);
+ gpio0_write(rn102_bays[i].gpio_power, 0);
+ gpio0_blink(rn102_bays[i].gpio_led, 1000, 1000);
+ continue;
+ }
+
+ pr_info("Connecting %s to disk %d\n", name, i + 1);
+ device_detect(dev);
+ gpio0_blink_disable(rn102_bays[i].gpio_led);
+ gpio0_write(rn102_bays[i].gpio_led, 1);
+ rn102_bays[i].state = DISK_READY;
+ }
+}
+
+/*
+ * USB0 → DRAM MBUS windows
+ *
+ * frontal USB 2.0 port of RN102 is connected to the SoC usb0.
+ * Here we enable MBUS windows toward all the DRAM using
+ * informations already gathered by mvebu_mbus_dram_info().
+ */
+// #define USB0_BRIDGE_BASE (ARMADA_370_XP_USB_BASE + 0x300)
+// #define USB_WIN_CTRL(n) (USB0_BRIDGE_BASE + 0x20 + (n) * 0x10)
+// #define USB_WIN_BASE(n) (USB0_BRIDGE_BASE + 0x24 + (n) * 0x10)
+
+static void setup_usb0(void) {
+ // /* Window0: 512MB @ 0x00000000 */
+ // writel(0x00000000, USB_WIN_BASE(0));
+ // writel(0x1FFF0E01, USB_WIN_CTRL(0));
+
+ // /* Window1: 512MB @ 0xB0000000 (placeholder) */
+ // writel(0xB0000000, USB_WIN_BASE(1));
+ // writel(0x1FFE841, USB_WIN_CTRL(1));
+
+ writel(0x2, 0xf1051404); /* enable force suspend */
+ u32 pwr = readl(0xf1051400);
+ pwr &= ~BIT(2);
+ writel(pwr, 0xf1051400); /* force SUSPENDM=0 */
+}
+
+
+/* Early init: setup specific hardware without blocking initialization. */
+static int rn102_early_poweron(void)
+{
+ writel(0xC6, 0xf1020228); /* CFU configuration */
+ setup_usb0();
+ setup_bays();
+
+ return 0;
+}
+postcore_initcall(rn102_early_poweron);
+
+static int rn102_init(void)
+{
+ init_disks();
+
+ return 0;
+}
+device_initcall(rn102_init);
+
+
+/* BareBox Update handlers */
+static int rn102_register_bbu(void)
+{
+ bbu_register_std_file_update("bootloader", 0,
+ "/dev/nand0.bootloader",
+ filetype_kwbimage_v1);
+
+ bbu_register_std_file_update("kernel", 0,
+ "/dev/nand0.kernel",
+ filetype_arm_zimage);
+
+ bbu_register_std_file_update("minirootfs", 0,
+ "/dev/nand0.minirootfs",
+ filetype_gzip);
+
+ return 0;
+}
+late_initcall(rn102_register_bbu);
diff --git a/rebuild-series.sh b/rebuild-series.sh
new file mode 100755
index 0000000000..aad99bc296
--- /dev/null
+++ b/rebuild-series.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+set -e
+
+# Torna al punto base
+git checkout v2026.01.0
+git branch -D rn102-rn104-series 2>/dev/null || true
+git checkout -b rn102-rn104-series
+
+# Applica ogni patch e crea commit
+apply_patch() {
+ PATCH="$1"
+ MSG="$2"
+ echo "Applying $PATCH..."
+ git apply "$PATCH"
+ git add -A
+ git commit -m "$MSG"
+}
+
+apply_patch 0001-*.patch "ARM: mvebu: add board support for Netgear RN102"
+apply_patch 0002-*.patch "ARM: mvebu: add lowlevel support for Netgear RN102"
+apply_patch 0003-*.patch "ARM: dts: add barebox device tree for Netgear RN102"
+apply_patch 0004-*.patch "ARM: dts: update RN102 Linux DTS"
+apply_patch 0005-*.patch "ARM: mvebu: add Kconfig entry for Netgear RN102"
+apply_patch 0006-*.patch "ARM: mvebu: add RN102 image support"
+apply_patch 0007-*.patch "ARM: mvebu: enable RN102 in mvebu_defconfig"
+apply_patch 0008-*.patch "ARM: mvebu: rn104: placeholder bay management"
+apply_patch 0009-*.patch "ARM: mvebu: add lowlevel support for Netgear RN104"
+apply_patch 0010-*.patch "ARM: dts: update RN104 Linux DTS"
+apply_patch 0011-*.patch "ARM: mvebu: adapt RN104 image support"
+apply_patch 0012-*.patch "ARM: mvebu: rename PUTC_LL to MVEBU_PUTC_LL"
+apply_patch 0013-*.patch "drivers: fan: add fan framework and API"
+apply_patch 0014-*.patch "commands: add fan control command"
+apply_patch 0015-*.patch "commands: integrate fan command into Kconfig and Makefile"
+apply_patch 0016-*.patch "usb: ehci: add Marvell EHCI host controller driver"
+apply_patch 0017-*.patch "usb: ehci: minor fixes for Marvell compatibility"
+apply_patch 0018-*.patch "ata: ahci: fixes and updates for Marvell SATA controller"
+apply_patch 0019-*.patch "drivers: integrate USB/AHCI changes"
+
+echo "Serie ricostruita con successo!"
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* Re: [PATCH 01/19] ARM: mvebu: add board support for Netgear RN102
2026-07-23 13:57 ` [PATCH 01/19] ARM: mvebu: add board support for Netgear RN102 Luca Lauro via B4 Relay
@ 2026-07-27 13:27 ` Sascha Hauer
0 siblings, 0 replies; 30+ messages in thread
From: Sascha Hauer @ 2026-07-27 13:27 UTC (permalink / raw)
To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro
Hi Luca,
Thanks for the series. Support for more consumer devices is greatly
appreciated.
On 2026-07-23 15:57, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
Signed-off-by is missing, see https://barebox.org/doc/latest/devel/contributing.html#license-and-dco
>
> ---
> arch/arm/boards/Makefile | 1 +
> arch/arm/boards/netgear-rn102/Makefile | 4 +
> arch/arm/boards/netgear-rn102/board.c | 243 +++++++++++++++++++++++++++++++++
> rebuild-series.sh | 39 ++++++
> 4 files changed, 287 insertions(+)
>
> diff --git a/arch/arm/boards/Makefile b/arch/arm/boards/Makefile
> index dd2f2c324e..aca1b45a81 100644
> --- a/arch/arm/boards/Makefile
> +++ b/arch/arm/boards/Makefile
> @@ -64,6 +64,7 @@ obj-$(CONFIG_MACH_MARVELL_ARMADA_XP_DB) += marvell-armada-xp-db/
> obj-$(CONFIG_MACH_MX23EVK) += freescale-mx23-evk/
> obj-$(CONFIG_MACH_MX28EVK) += freescale-mx28-evk/
> obj-$(CONFIG_MACH_MYIRTECH_X335X) += myirtech-x335x/
> +obj-$(CONFIG_MACH_NETGEAR_RN102) += netgear-rn102/
> obj-$(CONFIG_MACH_NETGEAR_RN104) += netgear-rn104/
> obj-$(CONFIG_MACH_NETGEAR_RN2120) += netgear-rn2120/
> obj-$(CONFIG_MACH_NVIDIA_BEAVER) += nvidia-beaver/
> diff --git a/arch/arm/boards/netgear-rn102/Makefile b/arch/arm/boards/netgear-rn102/Makefile
> new file mode 100644
> index 0000000000..da63d2625f
> --- /dev/null
> +++ b/arch/arm/boards/netgear-rn102/Makefile
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +obj-y += board.o
> +lwl-y += lowlevel.o
> diff --git a/arch/arm/boards/netgear-rn102/board.c b/arch/arm/boards/netgear-rn102/board.c
> new file mode 100644
> index 0000000000..c6673e9356
> --- /dev/null
> +++ b/arch/arm/boards/netgear-rn102/board.c
> @@ -0,0 +1,243 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <common.h>
> +#include <init.h>
> +#include <gpio.h>
> +#include <driver.h>
> +#include <linux/device.h>
> +#include <linux/mbus.h>
> +#include <mach/mvebu/armada-370-xp-regs.h>
> +#include <bbu.h>
> +
> +/*
> + * Early GPIO0 MMIO
> + *
> + * GPIO driver arrives too late for the disks to be
> + * ready in time for AHCI driver probe.
> + * So we use GPIO0 direct access for:
> + * - reading disk presence monitoring pins
> + * - enable powerup for bays that detect disk presence
> + * - display bay status with dedicated LEDs
> + */
> +#define GPIO0_BASE (ARMADA_370_XP_INT_REGS_BASE + 0x18100)
> +#define GPIO_OUT 0x00
> +#define GPIO_OUT_EN 0x04
> +#define GPIO_BLINK_EN 0x08
> +#define GPIO_IN 0x10
> +#define GPIO_BLINK_CNT_SEL 0x20
> +#define GPIO_BLINK_CNT_A_ON 0xc0
> +#define GPIO_BLINK_CNT_A_OFF 0xc4
> +
> +static inline void gpio0_set_output(int pin)
> +{
> + u32 v = readl(GPIO0_BASE + GPIO_OUT_EN);
Please use Kernel coding style for barebox. Means, use tabs instead of
spaces and likely more stuff ./scripts/checkpatch.pl mourns about.
These functions seem to duplicate GPIO support. We already have a GPIO
driver, so these could likely be dropped in favour for regular
gpio_set_value and friends.
> +
> + v &= ~(1 << pin);
> + writel(v, GPIO0_BASE + GPIO_OUT_EN);
> +}
> +
> +static inline void gpio0_set_input(int pin)
> +{
> + u32 v = readl(GPIO0_BASE + GPIO_OUT_EN);
> +
> + v |= (1 << pin);
> + writel(v, GPIO0_BASE + GPIO_OUT_EN);
> +}
> +
> +static inline void gpio0_write(int pin, int val)
> +{
> + u32 v = readl(GPIO0_BASE + GPIO_OUT);
> +
> + if (val)
> + v |= (1 << pin);
> + else
> + v &= ~(1 << pin);
> +
> + writel(v, GPIO0_BASE + GPIO_OUT);
> +}
> +
> +static inline int gpio0_read(int pin)
> +{
> + return !!(readl(GPIO0_BASE + GPIO_IN) & (1 << pin));
> +}
> +
> +static void gpio0_blink(int pin, int on_ms, int off_ms)
> +{
> + u32 v;
> +
> + gpio0_set_output(pin);
> +
> + /* set blink counter A on and off time in core clok cycles */
> + writel(10*on_ms, GPIO0_BASE + GPIO_BLINK_CNT_A_ON);
> + writel(10*off_ms, GPIO0_BASE + GPIO_BLINK_CNT_A_OFF);
> +
> + /* use blink counter A for selected pin */
> + v = readl(GPIO0_BASE + GPIO_BLINK_CNT_SEL);
> + v &= ~(1 << pin);
> + writel(v, GPIO0_BASE + GPIO_BLINK_CNT_SEL);
> +
> + /* enable blink for selected pin */
> + v = readl(GPIO0_BASE + GPIO_BLINK_EN);
> + v |= (1 << pin);
> + writel(v, GPIO0_BASE + GPIO_BLINK_EN);
> +}
> +
> +static void gpio0_blink_disable(int pin)
> +{
> + u32 v;
> +
> + v = readl(GPIO0_BASE + GPIO_BLINK_EN);
> + v &= ~(1 << pin);
> + writel(v, GPIO0_BASE + GPIO_BLINK_EN);
> +}
> +
> +/* HDD bays description */
> +
> +enum disk_state {
> + DISK_ABSENT = 0,
> + DISK_PRESENT,
> + DISK_READY,
> +};
> +
> +struct rn102_disk_bay {
> + int gpio_detect; /* input, active-low */
> + int gpio_power; /* output */
> + int gpio_led; /* output, active-low */
> + enum disk_state state;
> +};
> +
> +#define RN102_NUM_DISK_BAYS 2
> +
> +static struct rn102_disk_bay rn102_bays[RN102_NUM_DISK_BAYS] = {
> + { 12, 13, 15, DISK_ABSENT }, /* Bay 1, default to disk absent */
> + { 10, 11, 14, DISK_ABSENT }, /* Bay 2, default to disk absent */
> +};
> +
> +static void setup_bays(void) {
> + pr_info("Early disk power-on...\n");
> +
> + for (int i = 0; i < RN102_NUM_DISK_BAYS; i++) {
> + int present;
> +
> + gpio0_set_input(rn102_bays[i].gpio_detect);
> + present = (gpio0_read(rn102_bays[i].gpio_detect) == 0);
> +
> + gpio0_set_output(rn102_bays[i].gpio_power);
> +
> + if (present) {
> + pr_info("Bay %d: disk detected, powering on\n", i + 1);
> + gpio0_blink(rn102_bays[i].gpio_led, 500, 500);
> + gpio0_write(rn102_bays[i].gpio_power, 1);
> + rn102_bays[i].state = DISK_PRESENT;
> + } else {
> + pr_info("Bay %d empty\n", i + 1);
> + gpio0_write(rn102_bays[i].gpio_led, 0);
> + gpio0_write(rn102_bays[i].gpio_power, 0);
> + rn102_bays[i].state = DISK_ABSENT;
> + }
> + }
> +}
> +
> +/*
> + * After waiting for present disks spinup,
> + * we ask explicitly for corresponding ATA devices probe.
> + */
> +static void init_disks(void) {
> + pr_info("Waiting for disks spinup...\n");
> + mdelay(8000);
> +
> + pr_info("Detecting ATA devices:\n");
> + for (int i = 0; i < RN102_NUM_DISK_BAYS; i++) {
> + char name[8];
> + struct device *dev;
> +
> + if (rn102_bays[i].state != DISK_PRESENT) {
> + gpio0_write(rn102_bays[i].gpio_power, 0);
> + gpio0_blink_disable(rn102_bays[i].gpio_led);
> + gpio0_write(rn102_bays[i].gpio_led, 0);
> + continue;
> + }
> +
> + snprintf(name, sizeof(name), "ata%d", i);
> +
> + dev = get_device_by_name(name);
> + if (!dev) {
> + pr_warn("%s not found\n", name);
> + gpio0_write(rn102_bays[i].gpio_power, 0);
> + gpio0_blink(rn102_bays[i].gpio_led, 1000, 1000);
> + continue;
> + }
> +
> + pr_info("Connecting %s to disk %d\n", name, i + 1);
> + device_detect(dev);
> + gpio0_blink_disable(rn102_bays[i].gpio_led);
> + gpio0_write(rn102_bays[i].gpio_led, 1);
> + rn102_bays[i].state = DISK_READY;
> + }
> +}
> +
> +/*
> + * USB0 → DRAM MBUS windows
> + *
> + * frontal USB 2.0 port of RN102 is connected to the SoC usb0.
> + * Here we enable MBUS windows toward all the DRAM using
> + * informations already gathered by mvebu_mbus_dram_info().
> + */
> +// #define USB0_BRIDGE_BASE (ARMADA_370_XP_USB_BASE + 0x300)
> +// #define USB_WIN_CTRL(n) (USB0_BRIDGE_BASE + 0x20 + (n) * 0x10)
> +// #define USB_WIN_BASE(n) (USB0_BRIDGE_BASE + 0x24 + (n) * 0x10)
> +
> +static void setup_usb0(void) {
> + // /* Window0: 512MB @ 0x00000000 */
> + // writel(0x00000000, USB_WIN_BASE(0));
> + // writel(0x1FFF0E01, USB_WIN_CTRL(0));
> +
> + // /* Window1: 512MB @ 0xB0000000 (placeholder) */
> + // writel(0xB0000000, USB_WIN_BASE(1));
> + // writel(0x1FFE841, USB_WIN_CTRL(1));
> +
> + writel(0x2, 0xf1051404); /* enable force suspend */
> + u32 pwr = readl(0xf1051400);
> + pwr &= ~BIT(2);
> + writel(pwr, 0xf1051400); /* force SUSPENDM=0 */
> +}
> +
> +
> +/* Early init: setup specific hardware without blocking initialization. */
> +static int rn102_early_poweron(void)
> +{
> + writel(0xC6, 0xf1020228); /* CFU configuration */
> + setup_usb0();
> + setup_bays();
> +
> + return 0;
> +}
> +postcore_initcall(rn102_early_poweron);
A single barebox image can be compiled for multiple boards/SoCs, so
such an initcall will be executed on other boards once this board is
compiled in. You either have to protect them with some
if (!of_machine_is_compatible("foo"))
return 0;
Preferred way is to write the board specific code as a platform driver
which of-matches against the root node of the device tree, see other
boards following this pattern.
> +
> +static int rn102_init(void)
> +{
> + init_disks();
> +
> + return 0;
> +}
> +device_initcall(rn102_init);
> +
> +
> +/* BareBox Update handlers */
> +static int rn102_register_bbu(void)
> +{
> + bbu_register_std_file_update("bootloader", 0,
> + "/dev/nand0.bootloader",
> + filetype_kwbimage_v1);
> +
> + bbu_register_std_file_update("kernel", 0,
> + "/dev/nand0.kernel",
> + filetype_arm_zimage);
> +
> + bbu_register_std_file_update("minirootfs", 0,
> + "/dev/nand0.minirootfs",
> + filetype_gzip);
> +
> + return 0;
> +}
> +late_initcall(rn102_register_bbu);
> diff --git a/rebuild-series.sh b/rebuild-series.sh
This shouldn't be part of the series.
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] 30+ messages in thread
* [PATCH 02/19] ARM: mvebu: add lowlevel support for Netgear RN102
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
2026-07-23 13:57 ` [PATCH 01/19] ARM: mvebu: add board support for Netgear RN102 Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-27 13:30 ` Sascha Hauer
2026-07-23 13:57 ` [PATCH 03/19] ARM: dts: add barebox device tree " Luca Lauro via B4 Relay
` (16 subsequent siblings)
18 siblings, 1 reply; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
arch/arm/boards/netgear-rn102/lowlevel.c | 49 ++++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/arch/arm/boards/netgear-rn102/lowlevel.c b/arch/arm/boards/netgear-rn102/lowlevel.c
new file mode 100644
index 0000000000..11130eabbc
--- /dev/null
+++ b/arch/arm/boards/netgear-rn102/lowlevel.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <common.h>
+#include <asm/barebox-arm.h>
+#include <mach/mvebu/lowlevel.h>
+#include <mach/mvebu/barebox-arm-head.h>
+#include <mach/mvebu/armada-370-xp-regs.h>
+
+extern char __dtb_armada_370_rn102_bb_start[];
+
+#define INTERNAL_REG_BASE_ADDR 0x20080
+
+static __always_inline void mvebu_remap_registers(void)
+{
+ void __iomem *base = mvebu_get_initial_int_reg_base();
+
+ writel(MVEBU_REMAP_INT_REG_BASE, base + INTERNAL_REG_BASE_ADDR);
+}
+
+static unsigned long armada_370_xp_memory_find(void)
+{
+ unsigned long mem_size = 0;
+
+ for (int cs = 0; cs < 4; cs++) {
+ u32 ctrl = readl(ARMADA_370_XP_SDRAM_BASE + DDR_SIZE_CSn(cs));
+
+ /* Skip non-enabled CS */
+ if ((ctrl & DDR_SIZE_ENABLED) != DDR_SIZE_ENABLED)
+ continue;
+
+ mem_size += (ctrl | ~DDR_SIZE_MASK) + 1;
+ }
+
+ return mem_size;
+}
+
+ENTRY_FUNCTION_MVEBU(start_netgear_rn102, r0, r1, r2)
+{
+ void *fdt;
+
+ arm_cpu_lowlevel_init();
+
+ fdt = __dtb_armada_370_rn102_bb_start +
+ get_runtime_offset();
+
+ mvebu_remap_registers();
+ barebox_arm_entry(0, armada_370_xp_memory_find(), fdt);
+ // armada_370_xp_barebox_entry(fdt);
+}
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* Re: [PATCH 02/19] ARM: mvebu: add lowlevel support for Netgear RN102
2026-07-23 13:57 ` [PATCH 02/19] ARM: mvebu: add lowlevel " Luca Lauro via B4 Relay
@ 2026-07-27 13:30 ` Sascha Hauer
0 siblings, 0 replies; 30+ messages in thread
From: Sascha Hauer @ 2026-07-27 13:30 UTC (permalink / raw)
To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro
On 2026-07-23 15:57, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
>
> ---
> arch/arm/boards/netgear-rn102/lowlevel.c | 49 ++++++++++++++++++++++++++++++++
> 1 file changed, 49 insertions(+)
>
> diff --git a/arch/arm/boards/netgear-rn102/lowlevel.c b/arch/arm/boards/netgear-rn102/lowlevel.c
> new file mode 100644
> index 0000000000..11130eabbc
> --- /dev/null
> +++ b/arch/arm/boards/netgear-rn102/lowlevel.c
> @@ -0,0 +1,49 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <common.h>
> +#include <asm/barebox-arm.h>
> +#include <mach/mvebu/lowlevel.h>
> +#include <mach/mvebu/barebox-arm-head.h>
> +#include <mach/mvebu/armada-370-xp-regs.h>
> +
> +extern char __dtb_armada_370_rn102_bb_start[];
> +
> +#define INTERNAL_REG_BASE_ADDR 0x20080
> +
> +static __always_inline void mvebu_remap_registers(void)
> +{
> + void __iomem *base = mvebu_get_initial_int_reg_base();
> +
> + writel(MVEBU_REMAP_INT_REG_BASE, base + INTERNAL_REG_BASE_ADDR);
> +}
> +
> +static unsigned long armada_370_xp_memory_find(void)
> +{
> + unsigned long mem_size = 0;
> +
> + for (int cs = 0; cs < 4; cs++) {
> + u32 ctrl = readl(ARMADA_370_XP_SDRAM_BASE + DDR_SIZE_CSn(cs));
> +
> + /* Skip non-enabled CS */
> + if ((ctrl & DDR_SIZE_ENABLED) != DDR_SIZE_ENABLED)
> + continue;
> +
> + mem_size += (ctrl | ~DDR_SIZE_MASK) + 1;
> + }
> +
> + return mem_size;
> +}
> +
> +ENTRY_FUNCTION_MVEBU(start_netgear_rn102, r0, r1, r2)
> +{
> + void *fdt;
> +
> + arm_cpu_lowlevel_init();
> +
> + fdt = __dtb_armada_370_rn102_bb_start +
> + get_runtime_offset();
> +
> + mvebu_remap_registers();
> + barebox_arm_entry(0, armada_370_xp_memory_find(), fdt);
> + // armada_370_xp_barebox_entry(fdt);
Why can't you use armada_370_xp_barebox_entry()? I assume that wrongly
detects the amount of memory. If that's the case the preferred way would
be to fix the SDRAM detection code. If that's not feasible, please leave
an explanation in the code why it doesn't work and drop the commented
out call.
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] 30+ messages in thread
* [PATCH 03/19] ARM: dts: add barebox device tree for Netgear RN102
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
2026-07-23 13:57 ` [PATCH 01/19] ARM: mvebu: add board support for Netgear RN102 Luca Lauro via B4 Relay
2026-07-23 13:57 ` [PATCH 02/19] ARM: mvebu: add lowlevel " Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-27 13:32 ` Sascha Hauer
2026-07-23 13:57 ` [PATCH 04/19] ARM: dts: update RN102 Linux DTS Luca Lauro via B4 Relay
` (15 subsequent siblings)
18 siblings, 1 reply; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
arch/arm/dts/Makefile | 1 +
arch/arm/dts/armada-370-rn102-bb.dts | 13 +++++++++++++
2 files changed, 14 insertions(+)
diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile
index a84e09e388..5d1b21b615 100644
--- a/arch/arm/dts/Makefile
+++ b/arch/arm/dts/Makefile
@@ -51,6 +51,7 @@ lwl-$(CONFIG_MACH_MARVELL_ARMADA_XP_GP) += armada-xp-gp-bb.dtb.o
lwl-$(CONFIG_MACH_MARVELL_ARMADA_XP_DB) += armada-xp-db-bb.dtb.o
lwl-$(CONFIG_MACH_MX28EVK) += imx28-evk.dtb.o
lwl-$(CONFIG_MACH_MYIRTECH_X335X) += am335x-myirtech-myd.dtb.o am335x-myirtech-myd-mlo.dtb.o
+lwl-$(CONFIG_MACH_NETGEAR_RN102) += armada-370-rn102-bb.dtb.o
lwl-$(CONFIG_MACH_NETGEAR_RN104) += armada-370-rn104-bb.dtb.o
lwl-$(CONFIG_MACH_NETGEAR_RN2120) += armada-xp-rn2120-bb.dtb.o
lwl-$(CONFIG_MACH_NITROGEN6) += imx6q-nitrogen6x.dtb.o imx6dl-nitrogen6x.dtb.o imx6qp-nitrogen6_max.dtb.o
diff --git a/arch/arm/dts/armada-370-rn102-bb.dts b/arch/arm/dts/armada-370-rn102-bb.dts
new file mode 100644
index 0000000000..0ade2cdd4e
--- /dev/null
+++ b/arch/arm/dts/armada-370-rn102-bb.dts
@@ -0,0 +1,13 @@
+/*
+ * Barebox specific DT overlay for Netgear ReadyNAS 102
+ */
+
+#include "arm/marvell/armada-370-netgear-rn102.dts"
+
+/ {
+ barebox,disable-deep-probe;
+
+ chosen {
+ stdout-path = &uart0;
+ };
+};
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* Re: [PATCH 03/19] ARM: dts: add barebox device tree for Netgear RN102
2026-07-23 13:57 ` [PATCH 03/19] ARM: dts: add barebox device tree " Luca Lauro via B4 Relay
@ 2026-07-27 13:32 ` Sascha Hauer
0 siblings, 0 replies; 30+ messages in thread
From: Sascha Hauer @ 2026-07-27 13:32 UTC (permalink / raw)
To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro
On 2026-07-23 15:57, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
>
> ---
> arch/arm/dts/Makefile | 1 +
> arch/arm/dts/armada-370-rn102-bb.dts | 13 +++++++++++++
> 2 files changed, 14 insertions(+)
>
> diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile
> index a84e09e388..5d1b21b615 100644
> --- a/arch/arm/dts/Makefile
> +++ b/arch/arm/dts/Makefile
> @@ -51,6 +51,7 @@ lwl-$(CONFIG_MACH_MARVELL_ARMADA_XP_GP) += armada-xp-gp-bb.dtb.o
> lwl-$(CONFIG_MACH_MARVELL_ARMADA_XP_DB) += armada-xp-db-bb.dtb.o
> lwl-$(CONFIG_MACH_MX28EVK) += imx28-evk.dtb.o
> lwl-$(CONFIG_MACH_MYIRTECH_X335X) += am335x-myirtech-myd.dtb.o am335x-myirtech-myd-mlo.dtb.o
> +lwl-$(CONFIG_MACH_NETGEAR_RN102) += armada-370-rn102-bb.dtb.o
> lwl-$(CONFIG_MACH_NETGEAR_RN104) += armada-370-rn104-bb.dtb.o
> lwl-$(CONFIG_MACH_NETGEAR_RN2120) += armada-xp-rn2120-bb.dtb.o
> lwl-$(CONFIG_MACH_NITROGEN6) += imx6q-nitrogen6x.dtb.o imx6dl-nitrogen6x.dtb.o imx6qp-nitrogen6_max.dtb.o
> diff --git a/arch/arm/dts/armada-370-rn102-bb.dts b/arch/arm/dts/armada-370-rn102-bb.dts
> new file mode 100644
> index 0000000000..0ade2cdd4e
> --- /dev/null
> +++ b/arch/arm/dts/armada-370-rn102-bb.dts
> @@ -0,0 +1,13 @@
> +/*
> + * Barebox specific DT overlay for Netgear ReadyNAS 102
> + */
> +
> +#include "arm/marvell/armada-370-netgear-rn102.dts"
> +
> +/ {
> + barebox,disable-deep-probe;
Why? Would be worth fixing this issue. Might be because you use
different initcall levels in your board code.
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] 30+ messages in thread
* [PATCH 04/19] ARM: dts: update RN102 Linux DTS
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (2 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 03/19] ARM: dts: add barebox device tree " Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-27 13:40 ` Sascha Hauer
2026-07-27 13:41 ` Sascha Hauer
2026-07-23 13:57 ` [PATCH 05/19] ARM: mvebu: add Kconfig entry for Netgear RN102 Luca Lauro via B4 Relay
` (14 subsequent siblings)
18 siblings, 2 replies; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
dts/src/arm/marvell/armada-370-netgear-rn102.dts | 114 ++++++++++++++---------
1 file changed, 71 insertions(+), 43 deletions(-)
diff --git a/dts/src/arm/marvell/armada-370-netgear-rn102.dts b/dts/src/arm/marvell/armada-370-netgear-rn102.dts
index 079b37cf14..f1bad2a233 100644
--- a/dts/src/arm/marvell/armada-370-netgear-rn102.dts
+++ b/dts/src/arm/marvell/armada-370-netgear-rn102.dts
@@ -15,6 +15,10 @@ / {
model = "NETGEAR ReadyNAS 102";
compatible = "netgear,readynas-102", "marvell,armada370", "marvell,armada-370-xp";
+ aliases {
+ state = &state_nand;
+ };
+
chosen {
stdout-path = "serial0:115200n8";
};
@@ -54,7 +58,7 @@ ethernet@74000 {
phy-mode = "rgmii-id";
};
- usb@50000 {
+ usb@50000 {
status = "okay";
};
@@ -153,10 +157,35 @@ backup-button {
gpio-poweroff {
compatible = "gpio-poweroff";
- pinctrl-0 = <&poweroff>;
+ pinctrl-0 = <&poweroff_pin>;
pinctrl-names = "default";
gpios = <&gpio0 8 GPIO_ACTIVE_LOW>;
};
+
+ /* configure nand partition to store barebox environment */
+ environment {
+ compatible = "barebox,environment";
+ device-path = &nand_controller, "partname:environment";
+ };
+
+ /* configure nand partition to store barebox-state backend */
+ state_nand: nand_state_memory {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ compatible = "barebox,state";
+ magic = <0xab67421f>;
+ backend-type = "raw";
+ backend = <&backend_state_nand>;
+ backend-storage-type = "circular";
+ backend-stridesize = <32>;
+
+
+ variable@0 {
+ reg = <0x0 0x1>;
+ type ="uint8";
+ default = <0x1>;
+ };
+ };
};
&pciec {
@@ -219,58 +248,57 @@ reset_button_pin: reset-button-pin {
marvell,function = "gpio";
};
- poweroff: poweroff {
+ poweroff_pin: poweroff-pin {
marvell,pins = "mpp8";
marvell,function = "gpio";
};
};
&nand_controller {
+ compatible = "marvell,armada370-nand", "marvell,pxa3xx-nand";
status = "okay";
- nand@0 {
- reg = <0>;
- label = "pxa3xx_nand-0";
- nand-rb = <0>;
- marvell,nand-keep-config;
- nand-on-flash-bbt;
-
- /* Use Hardware BCH ECC */
- nand-ecc-strength = <4>;
- nand-ecc-step-size = <512>;
-
- partitions {
- compatible = "fixed-partitions";
- #address-cells = <1>;
- #size-cells = <1>;
-
- partition@0 {
- label = "u-boot";
- reg = <0x0000000 0x180000>; /* 1.5MB */
- read-only;
- };
+ label = <&nand_controller>;
+ nand-rb = <0>;
+ marvell,nand-keep-config;
+ nand-on-flash-bbt;
+
+ /* Use Hardware BCH ECC */
+ nand-ecc-strength = <4>;
+ nand-ecc-step-size = <512>;
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "bootloader";
+ reg = <0x0 0x400000>; /* 4MB */
+ //read-only;
+ };
- partition@180000 {
- label = "u-boot-env";
- reg = <0x180000 0x20000>; /* 128KB */
- read-only;
- };
+ partition@0x400000 {
+ label = "environment";
+ reg = <0x400000 0x80000>; /* 0.5MB */
+ //read-only;
+ };
- partition@200000 {
- label = "uImage";
- reg = <0x0200000 0x600000>; /* 6MB */
- };
+ backend_state_nand: partition@0x480000 {
+ label = "state";
+ reg = <0x480000 0x80000>; /* 0.5MB */
+ };
- partition@800000 {
- label = "minirootfs";
- reg = <0x0800000 0x400000>; /* 4MB */
- };
+ partition@0x500000 {
+ label = "kernel";
+ reg = <0x500000 0x1400000>; /* 20MB */
+ };
- /* Last MB is for the BBT, i.e. not writable */
- partition@c00000 {
- label = "ubifs";
- reg = <0x0c00000 0x7400000>; /* 116MB */
- };
+ partition@0x1900000 {
+ label = "minirootfs";
+ reg = <0x1900000 0x6000000>; /* 100MB */
};
+
+ /* Last MB is for the BBT, i.e. not writable */
};
-};
+};
\ No newline at end of file
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* Re: [PATCH 04/19] ARM: dts: update RN102 Linux DTS
2026-07-23 13:57 ` [PATCH 04/19] ARM: dts: update RN102 Linux DTS Luca Lauro via B4 Relay
@ 2026-07-27 13:40 ` Sascha Hauer
2026-07-27 13:41 ` Sascha Hauer
1 sibling, 0 replies; 30+ messages in thread
From: Sascha Hauer @ 2026-07-27 13:40 UTC (permalink / raw)
To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro
On 2026-07-23 15:57, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
>
> ---
> dts/src/arm/marvell/armada-370-netgear-rn102.dts | 114 ++++++++++++++---------
> 1 file changed, 71 insertions(+), 43 deletions(-)
>
> diff --git a/dts/src/arm/marvell/armada-370-netgear-rn102.dts b/dts/src/arm/marvell/armada-370-netgear-rn102.dts
The files in dts/ are directly taken from the Linux upstream device tree
files. Changes to them are overwritten with the next sync.
You have to put a dts file into arch/arm/dts/ with your changes and
include the upstream version instead.
Sascha
> index 079b37cf14..f1bad2a233 100644
> --- a/dts/src/arm/marvell/armada-370-netgear-rn102.dts
> +++ b/dts/src/arm/marvell/armada-370-netgear-rn102.dts
> @@ -15,6 +15,10 @@ / {
> model = "NETGEAR ReadyNAS 102";
> compatible = "netgear,readynas-102", "marvell,armada370", "marvell,armada-370-xp";
>
> + aliases {
> + state = &state_nand;
> + };
> +
> chosen {
> stdout-path = "serial0:115200n8";
> };
> @@ -54,7 +58,7 @@ ethernet@74000 {
> phy-mode = "rgmii-id";
> };
>
> - usb@50000 {
> + usb@50000 {
> status = "okay";
> };
>
> @@ -153,10 +157,35 @@ backup-button {
>
> gpio-poweroff {
> compatible = "gpio-poweroff";
> - pinctrl-0 = <&poweroff>;
> + pinctrl-0 = <&poweroff_pin>;
> pinctrl-names = "default";
> gpios = <&gpio0 8 GPIO_ACTIVE_LOW>;
> };
> +
> + /* configure nand partition to store barebox environment */
> + environment {
> + compatible = "barebox,environment";
> + device-path = &nand_controller, "partname:environment";
> + };
> +
> + /* configure nand partition to store barebox-state backend */
> + state_nand: nand_state_memory {
> + #address-cells = <1>;
> + #size-cells = <1>;
> + compatible = "barebox,state";
> + magic = <0xab67421f>;
> + backend-type = "raw";
> + backend = <&backend_state_nand>;
> + backend-storage-type = "circular";
> + backend-stridesize = <32>;
> +
> +
> + variable@0 {
> + reg = <0x0 0x1>;
> + type ="uint8";
> + default = <0x1>;
> + };
> + };
> };
>
> &pciec {
> @@ -219,58 +248,57 @@ reset_button_pin: reset-button-pin {
> marvell,function = "gpio";
> };
>
> - poweroff: poweroff {
> + poweroff_pin: poweroff-pin {
> marvell,pins = "mpp8";
> marvell,function = "gpio";
> };
> };
>
> &nand_controller {
> + compatible = "marvell,armada370-nand", "marvell,pxa3xx-nand";
> status = "okay";
>
> - nand@0 {
> - reg = <0>;
> - label = "pxa3xx_nand-0";
> - nand-rb = <0>;
> - marvell,nand-keep-config;
> - nand-on-flash-bbt;
> -
> - /* Use Hardware BCH ECC */
> - nand-ecc-strength = <4>;
> - nand-ecc-step-size = <512>;
> -
> - partitions {
> - compatible = "fixed-partitions";
> - #address-cells = <1>;
> - #size-cells = <1>;
> -
> - partition@0 {
> - label = "u-boot";
> - reg = <0x0000000 0x180000>; /* 1.5MB */
> - read-only;
> - };
> + label = <&nand_controller>;
> + nand-rb = <0>;
> + marvell,nand-keep-config;
> + nand-on-flash-bbt;
> +
> + /* Use Hardware BCH ECC */
> + nand-ecc-strength = <4>;
> + nand-ecc-step-size = <512>;
> +
> + partitions {
> + compatible = "fixed-partitions";
> + #address-cells = <1>;
> + #size-cells = <1>;
> +
> + partition@0 {
> + label = "bootloader";
> + reg = <0x0 0x400000>; /* 4MB */
> + //read-only;
> + };
>
> - partition@180000 {
> - label = "u-boot-env";
> - reg = <0x180000 0x20000>; /* 128KB */
> - read-only;
> - };
> + partition@0x400000 {
> + label = "environment";
> + reg = <0x400000 0x80000>; /* 0.5MB */
> + //read-only;
> + };
>
> - partition@200000 {
> - label = "uImage";
> - reg = <0x0200000 0x600000>; /* 6MB */
> - };
> + backend_state_nand: partition@0x480000 {
> + label = "state";
> + reg = <0x480000 0x80000>; /* 0.5MB */
> + };
>
> - partition@800000 {
> - label = "minirootfs";
> - reg = <0x0800000 0x400000>; /* 4MB */
> - };
> + partition@0x500000 {
> + label = "kernel";
> + reg = <0x500000 0x1400000>; /* 20MB */
> + };
>
> - /* Last MB is for the BBT, i.e. not writable */
> - partition@c00000 {
> - label = "ubifs";
> - reg = <0x0c00000 0x7400000>; /* 116MB */
> - };
> + partition@0x1900000 {
> + label = "minirootfs";
> + reg = <0x1900000 0x6000000>; /* 100MB */
> };
> +
> + /* Last MB is for the BBT, i.e. not writable */
> };
> -};
> +};
> \ No newline at end of file
>
> --
> 2.47.3
>
>
>
--
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] 30+ messages in thread* Re: [PATCH 04/19] ARM: dts: update RN102 Linux DTS
2026-07-23 13:57 ` [PATCH 04/19] ARM: dts: update RN102 Linux DTS Luca Lauro via B4 Relay
2026-07-27 13:40 ` Sascha Hauer
@ 2026-07-27 13:41 ` Sascha Hauer
1 sibling, 0 replies; 30+ messages in thread
From: Sascha Hauer @ 2026-07-27 13:41 UTC (permalink / raw)
To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro
BTW you can fold patches 1-4 into a single patch so that the full board
support comes as a single patch, same with RN104 support.
Sascha
On 2026-07-23 15:57, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
>
> ---
> dts/src/arm/marvell/armada-370-netgear-rn102.dts | 114 ++++++++++++++---------
> 1 file changed, 71 insertions(+), 43 deletions(-)
>
> diff --git a/dts/src/arm/marvell/armada-370-netgear-rn102.dts b/dts/src/arm/marvell/armada-370-netgear-rn102.dts
> index 079b37cf14..f1bad2a233 100644
> --- a/dts/src/arm/marvell/armada-370-netgear-rn102.dts
> +++ b/dts/src/arm/marvell/armada-370-netgear-rn102.dts
> @@ -15,6 +15,10 @@ / {
> model = "NETGEAR ReadyNAS 102";
> compatible = "netgear,readynas-102", "marvell,armada370", "marvell,armada-370-xp";
>
> + aliases {
> + state = &state_nand;
> + };
> +
> chosen {
> stdout-path = "serial0:115200n8";
> };
> @@ -54,7 +58,7 @@ ethernet@74000 {
> phy-mode = "rgmii-id";
> };
>
> - usb@50000 {
> + usb@50000 {
> status = "okay";
> };
>
> @@ -153,10 +157,35 @@ backup-button {
>
> gpio-poweroff {
> compatible = "gpio-poweroff";
> - pinctrl-0 = <&poweroff>;
> + pinctrl-0 = <&poweroff_pin>;
> pinctrl-names = "default";
> gpios = <&gpio0 8 GPIO_ACTIVE_LOW>;
> };
> +
> + /* configure nand partition to store barebox environment */
> + environment {
> + compatible = "barebox,environment";
> + device-path = &nand_controller, "partname:environment";
> + };
> +
> + /* configure nand partition to store barebox-state backend */
> + state_nand: nand_state_memory {
> + #address-cells = <1>;
> + #size-cells = <1>;
> + compatible = "barebox,state";
> + magic = <0xab67421f>;
> + backend-type = "raw";
> + backend = <&backend_state_nand>;
> + backend-storage-type = "circular";
> + backend-stridesize = <32>;
> +
> +
> + variable@0 {
> + reg = <0x0 0x1>;
> + type ="uint8";
> + default = <0x1>;
> + };
> + };
> };
>
> &pciec {
> @@ -219,58 +248,57 @@ reset_button_pin: reset-button-pin {
> marvell,function = "gpio";
> };
>
> - poweroff: poweroff {
> + poweroff_pin: poweroff-pin {
> marvell,pins = "mpp8";
> marvell,function = "gpio";
> };
> };
>
> &nand_controller {
> + compatible = "marvell,armada370-nand", "marvell,pxa3xx-nand";
> status = "okay";
>
> - nand@0 {
> - reg = <0>;
> - label = "pxa3xx_nand-0";
> - nand-rb = <0>;
> - marvell,nand-keep-config;
> - nand-on-flash-bbt;
> -
> - /* Use Hardware BCH ECC */
> - nand-ecc-strength = <4>;
> - nand-ecc-step-size = <512>;
> -
> - partitions {
> - compatible = "fixed-partitions";
> - #address-cells = <1>;
> - #size-cells = <1>;
> -
> - partition@0 {
> - label = "u-boot";
> - reg = <0x0000000 0x180000>; /* 1.5MB */
> - read-only;
> - };
> + label = <&nand_controller>;
> + nand-rb = <0>;
> + marvell,nand-keep-config;
> + nand-on-flash-bbt;
> +
> + /* Use Hardware BCH ECC */
> + nand-ecc-strength = <4>;
> + nand-ecc-step-size = <512>;
> +
> + partitions {
> + compatible = "fixed-partitions";
> + #address-cells = <1>;
> + #size-cells = <1>;
> +
> + partition@0 {
> + label = "bootloader";
> + reg = <0x0 0x400000>; /* 4MB */
> + //read-only;
> + };
>
> - partition@180000 {
> - label = "u-boot-env";
> - reg = <0x180000 0x20000>; /* 128KB */
> - read-only;
> - };
> + partition@0x400000 {
> + label = "environment";
> + reg = <0x400000 0x80000>; /* 0.5MB */
> + //read-only;
> + };
>
> - partition@200000 {
> - label = "uImage";
> - reg = <0x0200000 0x600000>; /* 6MB */
> - };
> + backend_state_nand: partition@0x480000 {
> + label = "state";
> + reg = <0x480000 0x80000>; /* 0.5MB */
> + };
>
> - partition@800000 {
> - label = "minirootfs";
> - reg = <0x0800000 0x400000>; /* 4MB */
> - };
> + partition@0x500000 {
> + label = "kernel";
> + reg = <0x500000 0x1400000>; /* 20MB */
> + };
>
> - /* Last MB is for the BBT, i.e. not writable */
> - partition@c00000 {
> - label = "ubifs";
> - reg = <0x0c00000 0x7400000>; /* 116MB */
> - };
> + partition@0x1900000 {
> + label = "minirootfs";
> + reg = <0x1900000 0x6000000>; /* 100MB */
> };
> +
> + /* Last MB is for the BBT, i.e. not writable */
> };
> -};
> +};
> \ No newline at end of file
>
> --
> 2.47.3
>
>
>
--
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] 30+ messages in thread
* [PATCH 05/19] ARM: mvebu: add Kconfig entry for Netgear RN102
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (3 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 04/19] ARM: dts: update RN102 Linux DTS Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-23 13:57 ` [PATCH 06/19] ARM: mvebu: add RN102 image support Luca Lauro via B4 Relay
` (13 subsequent siblings)
18 siblings, 0 replies; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
arch/arm/mach-mvebu/Kconfig | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/arm/mach-mvebu/Kconfig b/arch/arm/mach-mvebu/Kconfig
index ed1302af65..046d539656 100644
--- a/arch/arm/mach-mvebu/Kconfig
+++ b/arch/arm/mach-mvebu/Kconfig
@@ -37,6 +37,10 @@ config MACH_GLOBALSCALE_MIRABOX
bool "Globalscale Mirabox"
select ARCH_ARMADA_370
+config MACH_NETGEAR_RN102
+ bool "Netgear ReadyNAS 102"
+ select ARCH_ARMADA_370
+
config MACH_NETGEAR_RN104
bool "Netgear ReadyNAS 104"
select ARCH_ARMADA_370
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* [PATCH 06/19] ARM: mvebu: add RN102 image support
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (4 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 05/19] ARM: mvebu: add Kconfig entry for Netgear RN102 Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-23 13:57 ` [PATCH 07/19] ARM: mvebu: enable RN102 in mvebu_defconfig Luca Lauro via B4 Relay
` (12 subsequent siblings)
18 siblings, 0 replies; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
images/Makefile.mvebu | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/images/Makefile.mvebu b/images/Makefile.mvebu
index 7c918cb9f1..1741f0e9a5 100644
--- a/images/Makefile.mvebu
+++ b/images/Makefile.mvebu
@@ -27,6 +27,16 @@ image-$(CONFIG_MACH_GLOBALSCALE_MIRABOX) += barebox-globalscale-mirabox.img
FILE_barebox-globalscale-mirabox-2nd.img = start_globalscale_mirabox.pblb
image-$(CONFIG_MACH_GLOBALSCALE_MIRABOX) += barebox-globalscale-mirabox-2nd.img
+BOOTSRC_start_netgear_rn102.pblb.mvebu1img = nand
+BINHDR_start_netgear_rn102.pblb.mvebu1img = $(board)/netgear-rn102/binary.0
+FLAGS_start_netgear_rn102.pblb.mvebu1img = -B 0x20000:1 -d 0x0 -e 0x0
+FILE_barebox-netgear-rn102.img = start_netgear_rn102.pblb.mvebu1img
+pblb-$(CONFIG_MACH_NETGEAR_RN102) += start_netgear_rn102
+image-$(CONFIG_MACH_NETGEAR_RN102) += barebox-netgear-rn102.img
+
+FILE_barebox-netgear-rn102-2nd.img = start_netgear_rn102.pblb
+image-$(CONFIG_MACH_NETGEAR_RN102) += barebox-netgear-rn102-2nd.img
+
FLAGS_start_netgear_rn104.pblb.mvebu1img = -d 0x600000 -e 0x6e0000
BOOTSRC_start_netgear_rn104.pblb.mvebu1img = nand
BINHDR_start_netgear_rn104.pblb.mvebu1img = $(board)/netgear-rn104/binary.0
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* [PATCH 07/19] ARM: mvebu: enable RN102 in mvebu_defconfig
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (5 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 06/19] ARM: mvebu: add RN102 image support Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-23 13:57 ` [PATCH 08/19] ARM: mvebu: rn104: placeholder bay management Luca Lauro via B4 Relay
` (11 subsequent siblings)
18 siblings, 0 replies; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
arch/arm/configs/mvebu_defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm/configs/mvebu_defconfig b/arch/arm/configs/mvebu_defconfig
index 91dd23e1c8..a13b153f9f 100644
--- a/arch/arm/configs/mvebu_defconfig
+++ b/arch/arm/configs/mvebu_defconfig
@@ -1,5 +1,6 @@
CONFIG_ARCH_MVEBU=y
CONFIG_MACH_GLOBALSCALE_MIRABOX=y
+CONFIG_MACH_NETGEAR_RN102=y
CONFIG_MACH_NETGEAR_RN104=y
CONFIG_MACH_LENOVO_IX4_300D=y
CONFIG_MACH_MARVELL_ARMADA_XP_DB=y
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* [PATCH 08/19] ARM: mvebu: rn104: placeholder bay management
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (6 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 07/19] ARM: mvebu: enable RN102 in mvebu_defconfig Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-23 13:57 ` [PATCH 09/19] ARM: mvebu: add lowlevel support for Netgear RN104 Luca Lauro via B4 Relay
` (10 subsequent siblings)
18 siblings, 0 replies; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
arch/arm/boards/netgear-rn104/board.c | 193 ++++++++++++++++++++++++++++++++++
1 file changed, 193 insertions(+)
diff --git a/arch/arm/boards/netgear-rn104/board.c b/arch/arm/boards/netgear-rn104/board.c
new file mode 100644
index 0000000000..29c112ec9e
--- /dev/null
+++ b/arch/arm/boards/netgear-rn104/board.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <common.h>
+#include <init.h>
+#include <gpio.h>
+#include <driver.h>
+#include <linux/device.h>
+#include <linux/mbus.h>
+#include <mach/mvebu/armada-370-xp-regs.h>
+#include <bbu.h>
+
+/*
+ * Early GPIO0 MMIO
+ *
+ * GPIO driver arrives too late for the disks to be
+ * ready in time for AHCI driver probe.
+ * So we use GPIO0 direct access for:
+ * - reading disk presence monitoring pins
+ * - enable powerup for bays that detect disk presence
+ * - display bay status with dedicated LEDs
+ */
+#define GPIO0_BASE (ARMADA_370_XP_INT_REGS_BASE + 0x18100)
+#define GPIO_OUT 0x00
+#define GPIO_OUT_EN 0x04
+#define GPIO_BLINK_EN 0x08
+#define GPIO_IN 0x10
+#define GPIO_BLINK_CNT_SEL 0x20
+#define GPIO_BLINK_CNT_A_ON 0xc0
+#define GPIO_BLINK_CNT_A_OFF 0xc4
+
+static inline void gpio0_set_output(int pin)
+{
+ u32 v = readl(GPIO0_BASE + GPIO_OUT_EN);
+
+ v &= ~(1 << pin);
+ writel(v, GPIO0_BASE + GPIO_OUT_EN);
+}
+
+static inline void gpio0_set_input(int pin)
+{
+ u32 v = readl(GPIO0_BASE + GPIO_OUT_EN);
+
+ v |= (1 << pin);
+ writel(v, GPIO0_BASE + GPIO_OUT_EN);
+}
+
+static inline void gpio0_write(int pin, int val)
+{
+ u32 v = readl(GPIO0_BASE + GPIO_OUT);
+
+ if (val)
+ v |= (1 << pin);
+ else
+ v &= ~(1 << pin);
+
+ writel(v, GPIO0_BASE + GPIO_OUT);
+}
+
+static inline int gpio0_read(int pin)
+{
+ return !!(readl(GPIO0_BASE + GPIO_IN) & (1 << pin));
+}
+
+static void gpio0_blink(int pin, int on_ms, int off_ms)
+{
+ u32 v;
+
+ gpio0_set_output(pin);
+
+ /* set blink counter A on and off time in core clok cycles */
+ writel(10*on_ms, GPIO0_BASE + GPIO_BLINK_CNT_A_ON);
+ writel(10*off_ms, GPIO0_BASE + GPIO_BLINK_CNT_A_OFF);
+
+ /* use blink counter A for selected pin */
+ v = readl(GPIO0_BASE + GPIO_BLINK_CNT_SEL);
+ v &= ~(1 << pin);
+ writel(v, GPIO0_BASE + GPIO_BLINK_CNT_SEL);
+
+ /* enable blink for selected pin */
+ v = readl(GPIO0_BASE + GPIO_BLINK_EN);
+ v |= (1 << pin);
+ writel(v, GPIO0_BASE + GPIO_BLINK_EN);
+}
+
+static void gpio0_blink_disable(int pin)
+{
+ u32 v;
+
+ v = readl(GPIO0_BASE + GPIO_BLINK_EN);
+ v &= ~(1 << pin);
+ writel(v, GPIO0_BASE + GPIO_BLINK_EN);
+}
+
+/* HDD bays description */
+
+enum disk_state {
+ DISK_ABSENT = 0,
+ DISK_PRESENT,
+ DISK_READY,
+};
+
+struct rn104_disk_bay {
+ int gpio_detect; /* input, active-low */
+ int gpio_power; /* output */
+ int gpio_led; /* output, active-low */
+ enum disk_state state;
+};
+
+#define rn104_NUM_DISK_BAYS 1
+
+static struct rn104_disk_bay rn104_bays[rn104_NUM_DISK_BAYS] = {
+ { -1, -1, -1, DISK_ABSENT }, /* placeholder */
+};
+
+static void setup_bays(void)
+{
+ pr_info("RN104: bay management not implemented (PCA9554-based)\n");
+}
+
+/*
+ * After waiting for present disks spinup,
+ * we ask explicitly for corresponding ATA devices probe.
+ */
+static void init_disks(void)
+{
+ pr_info("RN104: disk spin-up logic not implemented\n");
+}
+
+/*
+ * USB0 → DRAM MBUS windows
+ *
+ * frontal USB 2.0 port of rn104 is connected to the SoC usb0.
+ * Here we enable MBUS windows toward all the DRAM using
+ * informations already gathered by mvebu_mbus_dram_info().
+ */
+// #define USB0_BRIDGE_BASE (ARMADA_370_XP_USB_BASE + 0x300)
+// #define USB_WIN_CTRL(n) (USB0_BRIDGE_BASE + 0x20 + (n) * 0x10)
+// #define USB_WIN_BASE(n) (USB0_BRIDGE_BASE + 0x24 + (n) * 0x10)
+
+static void setup_usb0(void) {
+ // /* Window0: 512MB @ 0x00000000 */
+ // writel(0x00000000, USB_WIN_BASE(0));
+ // writel(0x1FFF0E01, USB_WIN_CTRL(0));
+
+ // /* Window1: 512MB @ 0xB0000000 (placeholder) */
+ // writel(0xB0000000, USB_WIN_BASE(1));
+ // writel(0x1FFE841, USB_WIN_CTRL(1));
+
+ writel(0x2, 0xf1051404); /* enable force suspend */
+ u32 pwr = readl(0xf1051400);
+ pwr &= ~BIT(2);
+ writel(pwr, 0xf1051400); /* force SUSPENDM=0 */
+}
+
+
+/* Early init: setup specific hardware without blocking initialization. */
+static int rn104_early_poweron(void)
+{
+ writel(0xC6, 0xf1020228); /* CFU configuration */
+ setup_usb0();
+ setup_bays();
+
+ return 0;
+}
+postcore_initcall(rn104_early_poweron);
+
+static int rn104_init(void)
+{
+ init_disks();
+
+ return 0;
+}
+device_initcall(rn104_init);
+
+
+/* BareBox Update handlers */
+static int rn104_register_bbu(void)
+{
+ bbu_register_std_file_update("bootloader", 0,
+ "/dev/nand0.bootloader",
+ filetype_kwbimage_v1);
+
+ bbu_register_std_file_update("kernel", 0,
+ "/dev/nand0.kernel",
+ filetype_arm_zimage);
+
+ bbu_register_std_file_update("minirootfs", 0,
+ "/dev/nand0.minirootfs",
+ filetype_gzip);
+
+ return 0;
+}
+late_initcall(rn104_register_bbu);
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* [PATCH 09/19] ARM: mvebu: add lowlevel support for Netgear RN104
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (7 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 08/19] ARM: mvebu: rn104: placeholder bay management Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-23 13:57 ` [PATCH 10/19] ARM: dts: update RN104 Linux DTS Luca Lauro via B4 Relay
` (9 subsequent siblings)
18 siblings, 0 replies; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
arch/arm/boards/netgear-rn104/lowlevel.c | 43 +++++++++++++++++++++++++-------
1 file changed, 34 insertions(+), 9 deletions(-)
diff --git a/arch/arm/boards/netgear-rn104/lowlevel.c b/arch/arm/boards/netgear-rn104/lowlevel.c
index e693d13993..4fb5b6432b 100644
--- a/arch/arm/boards/netgear-rn104/lowlevel.c
+++ b/arch/arm/boards/netgear-rn104/lowlevel.c
@@ -1,24 +1,49 @@
// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Copyright (C) 2014 Uwe Kleine-Koenig <uwe@kleine-koenig.org>
- */
-
#include <common.h>
#include <asm/barebox-arm.h>
-#include <mach/mvebu/barebox-arm-head.h>
#include <mach/mvebu/lowlevel.h>
+#include <mach/mvebu/barebox-arm-head.h>
+#include <mach/mvebu/armada-370-xp-regs.h>
extern char __dtb_armada_370_rn104_bb_start[];
-ENTRY_FUNCTION_MVEBU(start_netgear_rn104, r0, r1, r2)
+#define INTERNAL_REG_BASE_ADDR 0x20080
+
+static __always_inline void mvebu_remap_registers(void)
+{
+ void __iomem *base = mvebu_get_initial_int_reg_base();
+
+ writel(MVEBU_REMAP_INT_REG_BASE, base + INTERNAL_REG_BASE_ADDR);
+}
+
+static unsigned long armada_370_xp_memory_find(void)
{
- void *fdt;
+ unsigned long mem_size = 0;
+
+ for (int cs = 0; cs < 4; cs++) {
+ u32 ctrl = readl(ARMADA_370_XP_SDRAM_BASE + DDR_SIZE_CSn(cs));
+
+ /* Skip non-enabled CS */
+ if ((ctrl & DDR_SIZE_ENABLED) != DDR_SIZE_ENABLED)
+ continue;
+
+ mem_size += (ctrl | ~DDR_SIZE_MASK) + 1;
+ }
- arm_cpu_lowlevel_init();
+ return mem_size;
+}
+
+ENTRY_FUNCTION_MVEBU(start_netgear_rn104, r0, r1, r2)
+{
+ void *fdt;
+ arm_cpu_lowlevel_init();
+
fdt = __dtb_armada_370_rn104_bb_start +
get_runtime_offset();
- armada_370_xp_barebox_entry(fdt);
+ mvebu_remap_registers();
+ barebox_arm_entry(0, armada_370_xp_memory_find(), fdt);
+ // armada_370_xp_barebox_entry(fdt);
}
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* [PATCH 10/19] ARM: dts: update RN104 Linux DTS
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (8 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 09/19] ARM: mvebu: add lowlevel support for Netgear RN104 Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-23 13:57 ` [PATCH 11/19] ARM: mvebu: adapt RN104 image support Luca Lauro via B4 Relay
` (8 subsequent siblings)
18 siblings, 0 replies; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
dts/src/arm/marvell/armada-370-netgear-rn104.dts | 173 ++++++++++++++---------
1 file changed, 104 insertions(+), 69 deletions(-)
diff --git a/dts/src/arm/marvell/armada-370-netgear-rn104.dts b/dts/src/arm/marvell/armada-370-netgear-rn104.dts
index d752ac1d7b..d7cc0549f4 100644
--- a/dts/src/arm/marvell/armada-370-netgear-rn104.dts
+++ b/dts/src/arm/marvell/armada-370-netgear-rn104.dts
@@ -15,6 +15,10 @@ / {
model = "NETGEAR ReadyNAS 104";
compatible = "netgear,readynas-104", "marvell,armada370", "marvell,armada-370-xp";
+ aliases {
+ state = &state_nand;
+ };
+
chosen {
stdout-path = "serial0:115200n8";
};
@@ -40,6 +44,12 @@ serial@12000 {
status = "okay";
};
+ /* eSATA interface */
+ sata@a0000 {
+ nr-ports = <1>;
+ status = "okay";
+ };
+
ethernet@70000 {
pinctrl-0 = <&ge0_rgmii_pins>;
pinctrl-names = "default";
@@ -103,15 +113,10 @@ g762_clk: g762-oscillator {
gpio-leds {
compatible = "gpio-leds";
- pinctrl-0 = <&backup_led_pin &power_led_pin>;
+ pinctrl-0 = <&power_led_pin
+ &backup_led_pin>;
pinctrl-names = "default";
- blue-backup-led {
- label = "rn104:blue:backup";
- gpios = <&gpio1 31 GPIO_ACTIVE_HIGH>;
- default-state = "off";
- };
-
blue-power-led {
label = "rn104:blue:pwr";
gpios = <&gpio2 0 GPIO_ACTIVE_LOW>;
@@ -141,6 +146,12 @@ blue-sata4-led {
gpios = <&pca9554 3 GPIO_ACTIVE_LOW>;
default-state = "off";
};
+
+ blue-backup-led {
+ label = "rn104:blue:backup";
+ gpios = <&gpio1 31 GPIO_ACTIVE_HIGH>;
+ default-state = "off";
+ };
};
auxdisplay {
@@ -159,17 +170,11 @@ auxdisplay {
gpio-keys {
compatible = "gpio-keys";
- pinctrl-0 = <&backup_button_pin
- &power_button_pin
- &reset_button_pin>;
+ pinctrl-0 = <&power_button_pin
+ &reset_button_pin
+ &backup_button_pin>;
pinctrl-names = "default";
- backup-button {
- label = "Backup Button";
- linux,code = <KEY_COPY>;
- gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
- };
-
power-button {
label = "Power Button";
linux,code = <KEY_POWER>;
@@ -181,6 +186,12 @@ reset-button {
linux,code = <KEY_RESTART>;
gpios = <&gpio2 1 GPIO_ACTIVE_LOW>;
};
+
+ backup-button {
+ label = "Backup Button";
+ linux,code = <KEY_COPY>;
+ gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
+ };
};
gpio-poweroff {
@@ -189,18 +200,43 @@ gpio-poweroff {
pinctrl-names = "default";
gpios = <&gpio1 28 GPIO_ACTIVE_LOW>;
};
+
+ /* configure nand partition to store barebox environment */
+ environment {
+ compatible = "barebox,environment";
+ device-path = &nand_controller, "partname:environment";
+ };
+
+ /* configure nand partition to store barebox-state backend */
+ state_nand: nand_state_memory {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ compatible = "barebox,state";
+ magic = <0xab67421f>;
+ backend-type = "raw";
+ backend = <&backend_state_nand>;
+ backend-storage-type = "circular";
+ backend-stridesize = <32>;
+
+
+ variable@0 {
+ reg = <0x0 0x1>;
+ type ="uint8";
+ default = <0x1>;
+ };
+ };
};
&pciec {
status = "okay";
- /* Connected to FL1009 USB 3.0 controller */
+ /* Connected to Marvell 88SE9170 SATA controller */
pcie@1,0 {
/* Port 0, Lane 0 */
status = "okay";
};
- /* Connected to Marvell 88SE9215 SATA controller */
+ /* Connected to FL1009 USB 3.0 controller */
pcie@2,0 {
/* Port 1, Lane 0 */
status = "okay";
@@ -220,8 +256,13 @@ phy1: ethernet-phy@1 { /* Marvell 88E1318 */
};
&pinctrl {
- poweroff: poweroff {
- marvell,pins = "mpp60";
+ power_led_pin: power-led-pin {
+ marvell,pins = "mpp64";
+ marvell,function = "gpio";
+ };
+
+ backup_led_pin: backup-led-pin {
+ marvell,pins = "mpp63";
marvell,function = "gpio";
};
@@ -235,68 +276,62 @@ power_button_pin: power-button-pin {
marvell,function = "gpio";
};
- backup_led_pin: backup-led-pin {
- marvell,pins = "mpp63";
- marvell,function = "gpio";
- };
-
- power_led_pin: power-led-pin {
- marvell,pins = "mpp64";
+ reset_button_pin: reset-button-pin {
+ marvell,pins = "mpp65";
marvell,function = "gpio";
};
- reset_button_pin: reset-button-pin {
- marvell,pins = "mpp65";
+ poweroff_pin: poweroff-pin {
+ marvell,pins = "mpp60";
marvell,function = "gpio";
};
};
&nand_controller {
+ compatible = "marvell,armada370-nand", "marvell,pxa3xx-nand";
status = "okay";
- nand@0 {
- reg = <0>;
- label = "pxa3xx_nand-0";
- nand-rb = <0>;
- marvell,nand-keep-config;
- nand-on-flash-bbt;
-
- /* Use Hardware BCH ECC */
- nand-ecc-strength = <4>;
- nand-ecc-step-size = <512>;
-
- partitions {
- compatible = "fixed-partitions";
- #address-cells = <1>;
- #size-cells = <1>;
-
- partition@0 {
- label = "u-boot";
- reg = <0x0000000 0x180000>; /* 1.5MB */
- read-only;
- };
+ label = <&nand_controller>;
+ nand-rb = <0>;
+ marvell,nand-keep-config;
+ nand-on-flash-bbt;
- partition@180000 {
- label = "u-boot-env";
- reg = <0x180000 0x20000>; /* 128KB */
- read-only;
- };
+ /* Use Hardware BCH ECC */
+ nand-ecc-strength = <4>;
+ nand-ecc-step-size = <512>;
- partition@200000 {
- label = "uImage";
- reg = <0x0200000 0x600000>; /* 6MB */
- };
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
- partition@800000 {
- label = "minirootfs";
- reg = <0x0800000 0x400000>; /* 4MB */
- };
+ partition@0 {
+ label = "bootloader";
+ reg = <0x0 0x400000>; /* 4MB */
+ //read-only;
+ };
- /* Last MB is for the BBT, i.e. not writable */
- partition@c00000 {
- label = "ubifs";
- reg = <0x0c00000 0x7400000>; /* 116MB */
- };
+ partition@0x400000 {
+ label = "environment";
+ reg = <0x400000 0x80000>; /* 0.5MB */
+ //read-only;
};
+
+ backend_state_nand: partition@0x480000 {
+ label = "state";
+ reg = <0x480000 0x80000>; /* 0.5MB */
+ };
+
+ partition@0x500000 {
+ label = "kernel";
+ reg = <0x500000 0x1400000>; /* 20MB */
+ };
+
+ partition@0x1900000 {
+ label = "minirootfs";
+ reg = <0x1900000 0x6000000>; /* 100MB */
+ };
+
+ /* Last MB is for the BBT, i.e. not writable */
};
-};
+};
\ No newline at end of file
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* [PATCH 11/19] ARM: mvebu: adapt RN104 image support
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (9 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 10/19] ARM: dts: update RN104 Linux DTS Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-23 13:57 ` [PATCH 12/19] ARM: mvebu: rename PUTC_LL to MVEBU_PUTC_LL Luca Lauro via B4 Relay
` (7 subsequent siblings)
18 siblings, 0 replies; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
images/Makefile.mvebu | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/images/Makefile.mvebu b/images/Makefile.mvebu
index 1741f0e9a5..106a302cfa 100644
--- a/images/Makefile.mvebu
+++ b/images/Makefile.mvebu
@@ -37,10 +37,9 @@ image-$(CONFIG_MACH_NETGEAR_RN102) += barebox-netgear-rn102.img
FILE_barebox-netgear-rn102-2nd.img = start_netgear_rn102.pblb
image-$(CONFIG_MACH_NETGEAR_RN102) += barebox-netgear-rn102-2nd.img
-FLAGS_start_netgear_rn104.pblb.mvebu1img = -d 0x600000 -e 0x6e0000
BOOTSRC_start_netgear_rn104.pblb.mvebu1img = nand
BINHDR_start_netgear_rn104.pblb.mvebu1img = $(board)/netgear-rn104/binary.0
-FLAGS_start_netgear_rn104.pblb.mvebu1img = -B 0x20000:1
+FLAGS_start_netgear_rn104.pblb.mvebu1img = -B 0x20000:1 -d 0x0 -e 0x0
FILE_barebox-netgear-rn104.img = start_netgear_rn104.pblb.mvebu1img
pblb-$(CONFIG_MACH_NETGEAR_RN104) += start_netgear_rn104
image-$(CONFIG_MACH_NETGEAR_RN104) += barebox-netgear-rn104.img
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* [PATCH 12/19] ARM: mvebu: rename PUTC_LL to MVEBU_PUTC_LL
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (10 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 11/19] ARM: mvebu: adapt RN104 image support Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-27 13:46 ` Sascha Hauer
2026-07-23 13:57 ` [PATCH 13/19] drivers: fan: add fan framework and API Luca Lauro via B4 Relay
` (6 subsequent siblings)
18 siblings, 1 reply; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
include/mach/mvebu/debug_ll.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/include/mach/mvebu/debug_ll.h b/include/mach/mvebu/debug_ll.h
index cf08776d4f..cd81b27e39 100644
--- a/include/mach/mvebu/debug_ll.h
+++ b/include/mach/mvebu/debug_ll.h
@@ -15,7 +15,7 @@
#define EARLY_UART UARTn_BASE(CONFIG_MVEBU_CONSOLE_UART)
-static inline void PUTC_LL(char c)
+static inline void MVEBU_PUTC_LL(char c)
{
/* Wait until there is space in the FIFO */
while (!(readl(EARLY_UART + UART_LSR) & LSR_THRE))
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* Re: [PATCH 12/19] ARM: mvebu: rename PUTC_LL to MVEBU_PUTC_LL
2026-07-23 13:57 ` [PATCH 12/19] ARM: mvebu: rename PUTC_LL to MVEBU_PUTC_LL Luca Lauro via B4 Relay
@ 2026-07-27 13:46 ` Sascha Hauer
0 siblings, 0 replies; 30+ messages in thread
From: Sascha Hauer @ 2026-07-27 13:46 UTC (permalink / raw)
To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro
On 2026-07-23 15:57, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
>
> ---
> include/mach/mvebu/debug_ll.h | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/include/mach/mvebu/debug_ll.h b/include/mach/mvebu/debug_ll.h
> index cf08776d4f..cd81b27e39 100644
> --- a/include/mach/mvebu/debug_ll.h
> +++ b/include/mach/mvebu/debug_ll.h
> @@ -15,7 +15,7 @@
>
> #define EARLY_UART UARTn_BASE(CONFIG_MVEBU_CONSOLE_UART)
>
> -static inline void PUTC_LL(char c)
> +static inline void MVEBU_PUTC_LL(char c)
Why is this necessary? Please add an explanation to the commit message.
Sascha
> {
> /* Wait until there is space in the FIFO */
> while (!(readl(EARLY_UART + UART_LSR) & LSR_THRE))
>
> --
> 2.47.3
>
>
>
--
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] 30+ messages in thread
* [PATCH 13/19] drivers: fan: add fan framework and API
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (11 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 12/19] ARM: mvebu: rename PUTC_LL to MVEBU_PUTC_LL Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-23 13:57 ` [PATCH 14/19] commands: add fan control command Luca Lauro via B4 Relay
` (5 subsequent siblings)
18 siblings, 0 replies; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
drivers/fan/Kconfig | 35 +
drivers/fan/Makefile | 8 +
drivers/fan/fan.c | 180 +++++
drivers/fan/i2c_fan_controllers/Kconfig | 6 +
drivers/fan/i2c_fan_controllers/Makefile | 5 +
drivers/fan/i2c_fan_controllers/g76x.c | 1184 ++++++++++++++++++++++++++++
drivers/fan/other_fan_controllers/Kconfig | 7 +
drivers/fan/other_fan_controllers/Makefile | 3 +
drivers/fan/spi_fan_controllers/Kconfig | 1 +
drivers/fan/spi_fan_controllers/Makefile | 3 +
include/fan/fan.h | 94 +++
include/fan/i2c_fan_controllers/g76x.h | 37 +
12 files changed, 1563 insertions(+)
diff --git a/drivers/fan/Kconfig b/drivers/fan/Kconfig
new file mode 100644
index 0000000000..7b1e795f01
--- /dev/null
+++ b/drivers/fan/Kconfig
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menuconfig FAN
+ bool "Fan controller drivers"
+ help
+ Add support for various fan controllers.
+
+if FAN
+
+menuconfig I2C_FAN
+ bool "i2c fan controllers support"
+ depends on I2C
+ help
+ add support for fan controllers based on i2c interface.
+ if I2C_FAN
+ source "drivers/fan/i2c_fan_controllers/Kconfig"
+ endif
+
+menuconfig SPI_FAN
+ bool "spi fan controllers support"
+ depends on SPI
+ help
+ add support fan controllers based on spi interface.
+ if SPI_FAN
+ source "drivers/fan/spi_fan_controllers/Kconfig"
+ endif
+
+menuconfig OTHER_FAN
+ bool "other fan controllers support"
+ help
+ support other fan control methods.
+ if OTHER_FAN
+ source "drivers/fan/other_fan_controllers/Kconfig"
+ endif
+
+endif
diff --git a/drivers/fan/Makefile b/drivers/fan/Makefile
new file mode 100644
index 0000000000..9ff140c0da
--- /dev/null
+++ b/drivers/fan/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+# fan controller interface types
+
+obj-y += fan.o
+obj-$(CONFIG_I2C_FAN) += i2c_fan_controllers/
+obj-$(CONFIG_SPI_FAN) += spi_fan_controllers/
+obj-$(CONFIG_OTHER_FAN) += other_fan_controllers/
diff --git a/drivers/fan/fan.c b/drivers/fan/fan.c
new file mode 100644
index 0000000000..672cda0a71
--- /dev/null
+++ b/drivers/fan/fan.c
@@ -0,0 +1,180 @@
+#include <fan/fan.h>
+
+/* Generic fan driver fan device management */
+
+LIST_HEAD(fan_device_list);
+
+static void fan_set_dev(struct fan_device *fan, struct device *dev)
+{
+ fan->dev = dev;
+}
+/*
+static struct device *fan_get_dev(struct fan_device *fan)
+{
+ return fan->dev;
+}
+*/
+static void fan_set_ops(struct fan_device *fan, struct fan_ops *ops)
+{
+ fan->ops = ops;
+}
+/*
+static const struct fan_ops *fan_get_ops(struct fan_device *fan)
+{
+ return fan->ops;
+}
+*/
+
+/* Generic filesystem fan device management */
+
+static int fan_idx = 0;
+
+static void cdev_set_fan(struct fan_device *fan)
+{
+ struct cdev *cdev = fan->cdev;
+
+ cdev->priv = fan;
+ cdev->dev = fan->dev;
+ cdev->ops = NULL;
+ cdev->size = 0;
+ cdev->flags = 0;
+}
+/*
+static struct fan_device *cdev_get_fan(struct cdev *cdev)
+{
+ return cdev->priv;
+}
+*/
+static int cdev_set_name(struct cdev *cdev)
+{
+ char name[32];
+
+ if (snprintf(name, sizeof(name), "fan%d", fan_idx++) >= sizeof(name))
+ return -EINVAL;
+
+ cdev->name = xstrdup(name);
+ if (!cdev->name)
+ return -ENOMEM;
+
+ return 0;
+}
+/*
+static char *cdev_get_name(struct cdev *cdev)
+{
+ return cdev->name;
+}
+*/
+struct fan_device *fan_device_find(const char *devmatch)
+{
+ struct fan_device *fan = NULL;
+ char devpath[32];
+
+ if (!devmatch || strlen(devmatch) > sizeof(devpath) - sizeof("/dev/"))
+ return ERR_PTR(-EINVAL);
+
+ list_for_each_entry(fan, &fan_device_list, list) {
+ /* search by driver-specific fan controller device */
+ if (fan->dev && fan->dev->name) {
+ snprintf(devpath, sizeof(devpath),
+ "/dev/%s", fan->dev->name);
+
+ /* by path ("/dev/.../ctrlr name") */
+ if (!strcmp(devpath, devmatch))
+ return fan;
+
+ /* or by name ("ctrlr name") */
+ if (!strcmp(fan->dev->name, devmatch))
+ return fan;
+ }
+ /* search by filesystem generic fan device */
+ if (fan->cdev && fan->cdev->name) {
+ snprintf(devpath, sizeof(devpath),
+ "/dev/%s", fan->cdev->name);
+ /* by path ("/dev/fanX") or by name ("fanX") */
+ if (!strcmp(devpath, devmatch) ||
+ !strcmp(fan->cdev->name, devmatch))
+ return fan;
+ }
+ }
+
+ return ERR_PTR(-ENODEV);
+}
+
+
+/* specific fan controller driver device management */
+
+int fan_device_register(struct device *dev, struct fan_ops *ops)
+{
+ struct fan_device *fan;
+ int ret;
+
+ if (!dev || !ops)
+ return -EINVAL;
+
+ /* allocate and initialize generic fan device structure */
+ fan = xzalloc(sizeof(*fan));
+ if (!fan)
+ return -ENOMEM;
+
+ fan->cdev = xzalloc(sizeof(*fan->cdev));
+ if (!fan->cdev) {
+ free(fan);
+ return -ENOMEM;
+ }
+
+ fan_set_dev(fan, dev);
+ fan_set_ops(fan, ops);
+ dev->type_data = fan;
+
+ list_add(&fan->list, &fan_device_list);
+
+ cdev_set_fan(fan);
+
+ ret = cdev_set_name(fan->cdev);
+ if (ret)
+ goto err;
+
+ ret = devfs_create(fan->cdev);
+ if (ret)
+ goto err;
+
+ // expose fan parameters as global variables
+ // globalvar_add_simple_int(".clk_freq", (int *)fan->ops->get_clk_freq, "%u");
+ // globalvar_add_simple_bool(".fail_state", (int *)fan->ops->get_failure_state);
+ // globalvar_add_simple_bool(".ooc_state", (int *)fan->ops->get_failure_state);
+
+ return 0;
+
+err:
+ if (fan->cdev->name)
+ free(fan->cdev->name);
+ free(fan->cdev);
+ free(fan);
+ dev->type_data = NULL;
+ return ret;
+}
+
+void fan_device_unregister(struct device *dev)
+{
+ /* find the generic fan device associated to the driver-specific fan controller device */
+ struct fan_device *fan;
+
+ if (!dev)
+ return;
+
+ fan = dev->type_data;
+ if (!fan)
+ return;
+
+ list_del(&fan->list);
+
+ if (fan->cdev) {
+ devfs_remove(fan->cdev);
+ if (fan->cdev->name)
+ free(fan->cdev->name);
+ free(fan->cdev);
+ }
+
+ free(fan);
+ dev->type_data = NULL;
+}
diff --git a/drivers/fan/i2c_fan_controllers/Kconfig b/drivers/fan/i2c_fan_controllers/Kconfig
new file mode 100644
index 0000000000..e258783848
--- /dev/null
+++ b/drivers/fan/i2c_fan_controllers/Kconfig
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config G76x
+ bool "GMT G76x driver"
+ help
+ Support Global Mixed-mode Technology Inc. G762 and G763 fan speed PWM controller chips
diff --git a/drivers/fan/i2c_fan_controllers/Makefile b/drivers/fan/i2c_fan_controllers/Makefile
new file mode 100644
index 0000000000..ca3c49e8a2
--- /dev/null
+++ b/drivers/fan/i2c_fan_controllers/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+# fan controller interface types
+
+obj-$(CONFIG_G76x) += g76x.o
\ No newline at end of file
diff --git a/drivers/fan/i2c_fan_controllers/g76x.c b/drivers/fan/i2c_fan_controllers/g76x.c
new file mode 100644
index 0000000000..c5c67766af
--- /dev/null
+++ b/drivers/fan/i2c_fan_controllers/g76x.c
@@ -0,0 +1,1184 @@
+/*
+ * g76x - Driver for the Global Mixed-mode Technology Inc. fan speed
+ * PWM controller chips from G76x family, i.e. g76x and G763
+ *
+ * based on g760a code written by Herbert Valerio Riedel <hvr@gnu.org>
+ * Copyright (C) 2007 Herbert Valerio Riedel <hvr@gnu.org>
+ *
+ * Author: Olivier Mouchet <olivier.mouchet@gmail.com>
+ * Copyright (c) 2009 LaCie
+ *
+ * Copyright (C) 2013, Arnaud EBALARD <arno@natisbad.org>
+ *
+ * g76x: minimal datasheet available at:
+ * http://www.gmt.com.tw/product/datasheet/EDS-762_3.pdf
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation.
+ */
+
+#include <fan/fan.h>
+#include <stdbool.h>
+#include <i2c/i2c.h>
+#include <init.h>
+#include <of.h>
+#include <of_device.h>
+#include <linux/minmax.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/limits.h>
+
+/*
+ * driver-specific layer
+ */
+
+static const struct platform_device_id g76x_id[] = {
+ { "g762", 0 },
+ { "g763", 0 },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(i2c, g76x_id);
+
+enum g76x_regs {
+ G76x_REG_SET_CNT = 0x00,
+ G76x_REG_ACT_CNT = 0x01,
+ G76x_REG_FAN_STA = 0x02,
+ G76x_REG_SET_OUT = 0x03,
+ G76x_REG_FAN_CMD1 = 0x04,
+ G76x_REG_FAN_CMD2 = 0x05,
+};
+
+/* Config register bits */
+#define G76x_REG_FAN_CMD1_DET_FAN_FAIL 0x80 /* enable fan_fail signal */
+#define G76x_REG_FAN_CMD1_DET_FAN_OOC 0x40 /* enable fan_out_of_control */
+#define G76x_REG_FAN_CMD1_OUT_MODE 0x20 /* out mode: PWM or DC */
+#define G76x_REG_FAN_CMD1_CTRL_MODE 0x10 /* fan mode: closed/open-loop */
+#define G76x_REG_FAN_CMD1_CLK_DIV_ID1 0x08 /* clock divisor value */
+#define G76x_REG_FAN_CMD1_CLK_DIV_ID0 0x04
+#define G76x_REG_FAN_CMD1_PWM_POLARITY 0x02 /* PWM polarity */
+#define G76x_REG_FAN_CMD1_PULSE_PER_REV 0x01 /* pulse per fan revolution */
+
+#define G76x_REG_FAN_CMD2_GEAR_MODE_1 0x08 /* fan gear mode */
+#define G76x_REG_FAN_CMD2_GEAR_MODE_0 0x04
+#define G76x_REG_FAN_CMD2_FAN_STARTV_1 0x02 /* fan startup voltage */
+#define G76x_REG_FAN_CMD2_FAN_STARTV_0 0x01
+
+#define G76x_REG_FAN_STA_FAIL 0x02 /* fan fail */
+#define G76x_REG_FAN_STA_OOC 0x01 /* fan out of control */
+
+/* Config register values */
+#define G76x_OUT_MODE_PWM 1
+#define G76x_OUT_MODE_ADC 0
+
+#define G76x_FAN_MODE_CLOSED_LOOP 1
+#define G76x_FAN_MODE_OPEN_LOOP 0
+
+#define G76x_PWM_POLARITY_NEGATIVE 1
+#define G76x_PWM_POLARITY_POSITIVE 0
+
+/* Register data is read (and cached) at most once per second. */
+#define G76x_UPDATE_INTERVAL_NS 1000000
+
+/*
+ * Extract fan startup voltage value (0, 1, 2 or 3) from given
+ * FAN_CMD2 register value.
+ */
+#define G76x_STARTV_FROM_REG(reg) \
+ ((reg) & (G76x_REG_FAN_CMD2_FAN_STARTV_0 | \
+ G76x_REG_FAN_CMD2_FAN_STARTV_1))
+
+/*
+ * Extract fan gear mode multiplier value (0, 2 or 4) from given
+ * FAN_CMD2 register value.
+ */
+#define G76x_GEARMULT_FROM_REG(reg) \
+ (1 << (((reg) & (G76x_REG_FAN_CMD2_GEAR_MODE_0 | \
+ G76x_REG_FAN_CMD2_GEAR_MODE_1)) >> 2))
+
+/*
+ * Extract pulse count per fan revolution value (2 or 4) from given
+ * FAN_CMD1 register value.
+ */
+#define G76x_PULSE_FROM_REG(reg) \
+ ((((reg) & G76x_REG_FAN_CMD1_PULSE_PER_REV) + 1) << 1)
+
+/*
+ * Extract fan clock divisor (1, 2, 4 or 8) from given FAN_CMD1
+ * register value.
+ */
+#define G76x_CLKDIV_FROM_REG(reg) \
+ (1 << (((reg) & (G76x_REG_FAN_CMD1_CLK_DIV_ID0 | \
+ G76x_REG_FAN_CMD1_CLK_DIV_ID1)) >> 2))
+
+struct g76x_data {
+ struct i2c_client *client;
+ struct clk *clk;
+
+ /* update mutex */
+ struct mutex update_lock;
+
+ /* board specific parameters. */
+ uint32_t clk_freq;
+
+ /* g76x register cache */
+ bool valid;
+ uint64_t last_updated; /* in nanoseconds */
+
+ uint8_t set_cnt; /* controls fan rotation speed in closed-loop mode */
+ uint8_t act_cnt; /* provides access to current fan RPM value */
+ uint8_t fan_sta; /* bit 0: set when actual fan speed is more than
+ * 25% outside requested fan speed
+ * bit 1: set when no transition occurs on fan
+ * pin for 0.7s
+ */
+ uint8_t set_out; /* controls fan rotation speed in open-loop mode */
+ uint8_t fan_cmd1; /* 0: FG_PLS_ID0 FG pulses count per revolution
+ * 0: 2 counts per revolution
+ * 1: 4 counts per revolution
+ * 1: PWM_POLARITY 1: negative_duty
+ * 0: positive_duty
+ * 2,3: [FG_CLOCK_ID0, FG_CLK_ID1]
+ * 00: Divide fan clock by 1
+ * 01: Divide fan clock by 2
+ * 10: Divide fan clock by 4
+ * 11: Divide fan clock by 8
+ * 4: FAN_MODE 1:closed-loop, 0:open-loop
+ * 5: OUT_MODE 1:PWM, 0:DC
+ * 6: DET_FAN_OOC enable "fan ooc" status
+ * 7: DET_FAN_FAIL enable "fan fail" status
+ */
+ uint8_t fan_cmd2; /* 0,1: FAN_STARTV 0,1,2,3 -> 0,32,64,96 dac_code
+ * 2,3: FG_GEAR_MODE
+ * 00: multiplier = 1
+ * 01: multiplier = 2
+ * 10: multiplier = 4
+ * 4: Mask ALERT# (g763 only)
+ */
+};
+
+/*
+ * Following structure can be used to set g762 driver-specific platform data
+ * during board init. Note that passing a sparse structure is possible but
+ * will result in non-specified attributes to be set to default value, hence
+ * overloading those installed during boot (e.g. by u-boot).
+ */
+struct g76x_platform_data {
+ unsigned int fan_startv;
+ unsigned int fan_gear_mode;
+ unsigned int pwm_polarity;
+ unsigned int clk_freq;
+};
+
+/* driver-specific helpers */
+
+/*
+ * Convert count value from fan controller register (FAN_SET_CNT) into fan
+ * speed RPM value. Note that the datasheet documents a basic formula;
+ * influence of additional parameters (fan clock divisor, fan gear mode)
+ * have been infered from examples in the datasheet and tests.
+ */
+static inline unsigned int g76x_rpm_from_cnt(uint8_t cnt, uint32_t clk_freq, uint16_t p,
+ uint8_t clk_div, uint8_t gear_mult)
+{
+ return (clk_freq * 30 * gear_mult) / ((cnt ? cnt : 1) * p * clk_div);
+}
+
+/*
+ * Convert fan RPM value into pulse count value for fan controller
+ * register (FAN_SET_CNT).
+ */
+static inline unsigned char g76x_cnt_from_rpm(unsigned long rpm, uint32_t clk_freq, uint16_t p,
+ uint8_t clk_div, uint8_t gear_mult)
+{
+ unsigned long f1 = clk_freq * 30 * gear_mult;
+ unsigned long f2 = p * clk_div;
+
+ rpm = clamp_val(rpm, f1 / (255 * f2), ULONG_MAX / f2);
+ return DIV_ROUND_CLOSEST(f1, rpm * f2);
+}
+
+/*
+ * Convert count value for fan controller register (FAN_SET_CNT)
+ * into percentage of full speed (0 to 100).
+ */
+static inline unsigned int g76x_level_from_cnt(uint8_t cnt) {
+ uint8_t level = 0;
+ cnt = clamp_val(cnt, 0, 255);
+ level = (cnt * 100) / 255;
+ return level;
+}
+
+/*
+ * Convert percentage of full speed into count value for
+ * fan controller register (FAN_SET_CNT).
+ */
+static inline unsigned int g76x_cnt_from_level(unsigned char level) {
+ unsigned char cnt = 0;
+ level = clamp_val(level, 0, 100);
+ cnt = (level * 255) / 100;
+ return cnt;
+}
+
+/* helper to manage g76x errors */
+static struct g76x_data *g76x_error_handle(struct g76x_data *data, int err_ret)
+{
+ if (data) {
+ mutex_unlock(&data->update_lock);
+ dev_err(&data->client->dev, "g76x error: %d\n", err_ret);
+ }
+
+ return ERR_PTR(err_ret);
+}
+
+/* helper to grab and cache data, at most one time per second */
+static struct g76x_data *g76x_update_client(struct device *dev)
+{
+ struct g76x_data *data = dev->priv;
+ if (!data) return ERR_PTR(-ENODEV);
+ struct i2c_client *client = data->client;
+ int ret = 0;
+
+ mutex_lock(&data->update_lock);
+
+ if (data->valid && !is_timeout(data->last_updated, G76x_UPDATE_INTERVAL_NS)) {
+ mutex_unlock(&data->update_lock);
+ return data;
+ }
+
+ ret = i2c_smbus_read_byte_data(client, G76x_REG_SET_CNT);
+ if (ret < 0) goto err_update;
+ data->set_cnt = ret;
+
+ ret = i2c_smbus_read_byte_data(client, G76x_REG_ACT_CNT);
+ if (ret < 0) goto err_update;
+ data->act_cnt = ret;
+
+ ret = i2c_smbus_read_byte_data(client, G76x_REG_FAN_STA);
+ if (ret < 0) goto err_update;
+ data->fan_sta = ret;
+
+ ret = i2c_smbus_read_byte_data(client, G76x_REG_SET_OUT);
+ if (ret < 0) goto err_update;
+ data->set_out = ret;
+
+ ret = i2c_smbus_read_byte_data(client, G76x_REG_FAN_CMD1);
+ if (ret < 0) goto err_update;
+ data->fan_cmd1 = ret;
+
+ ret = i2c_smbus_read_byte_data(client, G76x_REG_FAN_CMD2);
+ if (ret < 0) goto err_update;
+ data->fan_cmd2 = ret;
+
+ data->last_updated = get_time_ns();
+ data->valid = true;
+
+ mutex_unlock(&data->update_lock);
+ return data;
+
+err_update:
+ data->valid = false;
+ return g76x_error_handle(data, ret);
+}
+
+
+/* driver-specific configuration functions */
+
+/* Get and Set fan startup voltage. Accepted values are either 0, 1, 2 or 3. */
+static int g76x_get_fan_startv(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ return sprintf(buf, "%d\n", G76x_STARTV_FROM_REG(data->fan_cmd2));
+}
+
+static int g76x_set_fan_startv(struct device *dev, unsigned long val)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+ int ret;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ mutex_lock(&data->update_lock);
+ switch (val) {
+ case 0:
+ data->fan_cmd2 &= ~G76x_REG_FAN_CMD2_FAN_STARTV_0;
+ data->fan_cmd2 &= ~G76x_REG_FAN_CMD2_FAN_STARTV_1;
+ break;
+ case 1:
+ data->fan_cmd2 |= G76x_REG_FAN_CMD2_FAN_STARTV_0;
+ data->fan_cmd2 &= ~G76x_REG_FAN_CMD2_FAN_STARTV_1;
+ break;
+ case 2:
+ data->fan_cmd2 &= ~G76x_REG_FAN_CMD2_FAN_STARTV_0;
+ data->fan_cmd2 |= G76x_REG_FAN_CMD2_FAN_STARTV_1;
+ break;
+ case 3:
+ data->fan_cmd2 |= G76x_REG_FAN_CMD2_FAN_STARTV_0;
+ data->fan_cmd2 |= G76x_REG_FAN_CMD2_FAN_STARTV_1;
+ break;
+ default:
+ mutex_unlock(&data->update_lock);
+ return -EINVAL;
+ }
+ ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD2,
+ data->fan_cmd2);
+ data->valid = false;
+ mutex_unlock(&data->update_lock);
+
+ return ret;
+}
+
+/* Get and Set fan gear multiplier. Accepts either 0, 1 or 2. */
+static int g76x_get_gear_multiplier(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ return sprintf(buf, "%d\n", G76x_GEARMULT_FROM_REG(data->fan_cmd2));
+}
+
+static int g76x_set_gear_multiplier(struct device *dev, unsigned long val)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+ int ret;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ mutex_lock(&data->update_lock);
+ switch (val) {
+ case 0:
+ data->fan_cmd2 &= ~G76x_REG_FAN_CMD2_GEAR_MODE_0;
+ data->fan_cmd2 &= ~G76x_REG_FAN_CMD2_GEAR_MODE_1;
+ break;
+ case 1:
+ data->fan_cmd2 |= G76x_REG_FAN_CMD2_GEAR_MODE_0;
+ data->fan_cmd2 &= ~G76x_REG_FAN_CMD2_GEAR_MODE_1;
+ break;
+ case 2:
+ data->fan_cmd2 &= ~G76x_REG_FAN_CMD2_GEAR_MODE_0;
+ data->fan_cmd2 |= G76x_REG_FAN_CMD2_GEAR_MODE_1;
+ break;
+ default:
+ mutex_unlock(&data->update_lock);
+ return -EINVAL;
+ }
+ ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD2,
+ data->fan_cmd2);
+ data->valid = false;
+ mutex_unlock(&data->update_lock);
+
+ return ret;
+}
+
+/* Get and Set number of fan pulses per revolution. Accepts either 2 or 4. */
+static int g76x_get_fan_ppr(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ return sprintf(buf, "%d\n", G76x_PULSE_FROM_REG(data->fan_cmd1));
+}
+
+static int g76x_set_fan_ppr(struct device *dev, unsigned long val)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+ int ret;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ mutex_lock(&data->update_lock);
+ switch (val) {
+ case 2:
+ data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_PULSE_PER_REV;
+ break;
+ case 4:
+ data->fan_cmd1 |= G76x_REG_FAN_CMD1_PULSE_PER_REV;
+ break;
+ default:
+ mutex_unlock(&data->update_lock);
+ return -EINVAL;
+ }
+ ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD1,
+ data->fan_cmd1);
+ data->valid = false;
+ mutex_unlock(&data->update_lock);
+
+ return ret;
+}
+
+/* Get and Set PWM polarity. Accepts either 0 (positive duty) or 1 (negative duty) */
+static int g76x_get_pwm_polarity(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ return sprintf(buf, "%d\n", !!(data->fan_cmd1 & G76x_REG_FAN_CMD1_PWM_POLARITY));
+}
+
+static int g76x_set_pwm_polarity(struct device *dev, unsigned long val)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+ int ret;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ mutex_lock(&data->update_lock);
+ switch (val) {
+ case G76x_PWM_POLARITY_POSITIVE:
+ data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_PWM_POLARITY;
+ break;
+ case G76x_PWM_POLARITY_NEGATIVE:
+ data->fan_cmd1 |= G76x_REG_FAN_CMD1_PWM_POLARITY;
+ break;
+ default:
+ mutex_unlock(&data->update_lock);
+ return -EINVAL;
+ }
+ ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD1,
+ data->fan_cmd1);
+ data->valid = false;
+ mutex_unlock(&data->update_lock);
+
+ return ret;
+}
+
+/*
+ * Get and Set input clock frequency received on CLK pin of the chip. Accepted values
+ * are between 0 and 0xffffff. If zero is given, then default frequency
+ * (32768Hz) is used. Note that clock frequency is a characteristic of the
+ * system but an internal parameter, i.e. value is not passed to the device.
+ */
+static int g76x_get_clk_freq(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ return sprintf(buf, "%d\n", !!(data->clk_freq));
+}
+
+static int g76x_set_clk_freq(struct device *dev, unsigned long val)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct g76x_data *data = i2c_get_clientdata(client);
+
+ if (!data)
+ return 0;
+
+ if (val == 0)
+ val = 32768;
+
+ else if (val > 100000000)
+ return -EINVAL;
+
+ data->clk_freq = val;
+
+ return 0;
+}
+
+/* Get and Set fan clock divisor. Accepts either 1, 2, 4 or 8. */
+static int g76x_get_clk_div(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ return sprintf(buf, "%d\n", G76x_CLKDIV_FROM_REG(data->fan_cmd1));
+}
+
+static int g76x_set_clk_div(struct device *dev, unsigned long val)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+ int ret;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ mutex_lock(&data->update_lock);
+ switch (val) {
+ case 1:
+ data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_CLK_DIV_ID0;
+ data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_CLK_DIV_ID1;
+ break;
+ case 2:
+ data->fan_cmd1 |= G76x_REG_FAN_CMD1_CLK_DIV_ID0;
+ data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_CLK_DIV_ID1;
+ break;
+ case 4:
+ data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_CLK_DIV_ID0;
+ data->fan_cmd1 |= G76x_REG_FAN_CMD1_CLK_DIV_ID1;
+ break;
+ case 8:
+ data->fan_cmd1 |= G76x_REG_FAN_CMD1_CLK_DIV_ID0;
+ data->fan_cmd1 |= G76x_REG_FAN_CMD1_CLK_DIV_ID1;
+ break;
+ default:
+ mutex_unlock(&data->update_lock);
+ return -EINVAL;
+ }
+ ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD1,
+ data->fan_cmd1);
+ data->valid = false;
+ mutex_unlock(&data->update_lock);
+
+ return ret;
+}
+
+/* Get and Set fan control mode. Accepts either 0 (open-loop) or 1 (closed-loop). */
+static int g76x_get_control_mode(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ return sprintf(buf, "%d\n", !!(data->fan_cmd1 & G76x_REG_FAN_CMD1_CTRL_MODE));
+}
+
+static int g76x_set_control_mode(struct device *dev, unsigned long val)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+ int ret;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ mutex_lock(&data->update_lock);
+ switch (val) {
+ case G76x_FAN_MODE_CLOSED_LOOP:
+ data->fan_cmd1 |= G76x_REG_FAN_CMD1_CTRL_MODE;
+ break;
+ case G76x_FAN_MODE_OPEN_LOOP:
+ data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_CTRL_MODE;
+ /*
+ * NOTE: if SET_CNT register value is 255, fan is disarmed,
+ * no matter the value of SET_OUT (to be specific, this seems
+ * to happen only in PWM mode). To workaround this case,
+ * we give SET_CNT value of 0 if it is 255 when switching to open-loop.
+ * This means that fan will be armed every time we switch to open-loop.
+ */
+ if (data->set_cnt == 0xff)
+ i2c_smbus_write_byte_data(data->client, G76x_REG_SET_CNT, 0);
+ break;
+ default:
+ mutex_unlock(&data->update_lock);
+ return -EINVAL;
+ }
+
+ ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD1,
+ data->fan_cmd1);
+ data->valid = false;
+ mutex_unlock(&data->update_lock);
+
+ return ret;
+}
+
+/* Get and Set output mode. Accepts either 0 (ADC) or 1 (PWM) */
+static int g76x_get_output_mode(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ return sprintf(buf, "%d\n", !!(data->fan_cmd1 & G76x_REG_FAN_CMD1_OUT_MODE));
+}
+
+static int g76x_set_output_mode(struct device *dev, unsigned long val)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+ int ret;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ mutex_lock(&data->update_lock);
+ switch (val) {
+ case G76x_OUT_MODE_PWM:
+ data->fan_cmd1 |= G76x_REG_FAN_CMD1_OUT_MODE;
+ break;
+ case G76x_OUT_MODE_ADC:
+ data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_OUT_MODE;
+ break;
+ default:
+ mutex_unlock(&data->update_lock);
+ return -EINVAL;
+ }
+ ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD1,
+ data->fan_cmd1);
+ data->valid = false;
+ mutex_unlock(&data->update_lock);
+
+ return ret;
+}
+
+/* Get and Set fan out of control detection. Accepts either 0 (disabled) or 1 (enabled) */
+static int g76x_get_ooc_detection(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ return sprintf(buf, "%d\n", !!(data->fan_cmd1 & G76x_REG_FAN_CMD1_DET_FAN_OOC));
+}
+
+static int g76x_set_ooc_detection(struct device *dev, unsigned long val)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+ int ret;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ mutex_lock(&data->update_lock);
+ switch (val) {
+ case 0:
+ data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_DET_FAN_OOC;
+ break;
+ case 1:
+ data->fan_cmd1 |= G76x_REG_FAN_CMD1_DET_FAN_OOC;
+ break;
+ default:
+ mutex_unlock(&data->update_lock);
+ return -EINVAL;
+ }
+ ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD1,
+ data->fan_cmd1);
+ data->valid = false;
+ mutex_unlock(&data->update_lock);
+
+ return ret;
+}
+
+/* Get and Set fan failure detection. Accepts either 0 (disabled) or 1 (enabled) */
+static int g76x_get_failure_detection(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ return sprintf(buf, "%d\n", !!(data->fan_cmd1 & G76x_REG_FAN_CMD1_DET_FAN_FAIL));
+}
+
+static int g76x_set_failure_detection(struct device *dev, unsigned long val)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+ int ret;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ mutex_lock(&data->update_lock);
+ switch (val) {
+ case 0:
+ data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_DET_FAN_FAIL;
+ break;
+ case 1:
+ data->fan_cmd1 |= G76x_REG_FAN_CMD1_DET_FAN_FAIL;
+ break;
+ default:
+ mutex_unlock(&data->update_lock);
+ return -EINVAL;
+ }
+ ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD1,
+ data->fan_cmd1);
+ data->valid = false;
+ mutex_unlock(&data->update_lock);
+
+ return ret;
+}
+
+/*
+ * Import hardware characteristics from .dts file and push
+ * those to the chip.
+ */
+#ifdef CONFIG_OF
+static const struct of_device_id g76x_dt_match[] = {
+ { .compatible = "gmt,g762" },
+ { .compatible = "gmt,g763" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, g76x_dt_match);
+/*
+ * Grab clock (a required property), enable it, get (fixed) clock frequency
+ * and store it. Note: upon success, clock has been prepared and enabled; it
+ * must later be unprepared and disabled (e.g. during module unloading) by a
+ * call to g76x_of_clock_disable(). Note that a reference to clock is kept
+ * in our private data structure to be used in this function.
+ */
+static int g76x_of_clock_enable(struct i2c_client *client)
+{
+ struct g76x_data *data;
+ unsigned long clk_freq;
+ struct clk *clk;
+ int ret;
+
+ if (!client->dev.of_node)
+ return 0;
+
+ clk = of_clk_get(client->dev.of_node, 0);
+ if (IS_ERR(clk)) {
+ dev_err(&client->dev, "failed to get clock\n");
+ return PTR_ERR(clk);
+ }
+
+ ret = clk_prepare_enable(clk);
+ if (ret) {
+ dev_err(&client->dev, "failed to enable clock\n");
+ clk_put(clk);
+ return ret;
+ }
+
+ clk_freq = clk_get_rate(clk);
+ ret = g76x_set_clk_freq(&client->dev, clk_freq);
+ if (ret) {
+ dev_err(&client->dev, "invalid clock freq %lu\n", clk_freq);
+ clk_disable_unprepare(clk);
+ clk_put(clk);
+ return ret;
+ }
+
+ data = i2c_get_clientdata(client);
+ data->clk = clk;
+
+ return 0;
+}
+
+static void g76x_of_clock_disable(struct i2c_client *client)
+{
+ struct g76x_data *data = i2c_get_clientdata(client);
+
+ if (!data->clk)
+ return;
+
+ clk_disable_unprepare(data->clk);
+ clk_put(data->clk);
+}
+
+static int g76x_of_prop_import_one(struct i2c_client *client,
+ const char *pname,
+ int (*psetter)(struct device *dev,
+ unsigned long val))
+{
+ int ret;
+ uint32_t pval;
+
+ if (of_property_read_u32(client->dev.of_node, pname, &pval))
+ return 0;
+
+ dev_dbg(&client->dev, "found %s (%d)\n", pname, pval);
+ ret = (*psetter)(&client->dev, pval);
+ if (ret)
+ dev_err(&client->dev, "unable to set %s (%d)\n", pname, pval);
+
+ return ret;
+}
+
+static int g76x_of_prop_import(struct i2c_client *client)
+{
+ int ret;
+
+ if (!client->dev.of_node)
+ return 0;
+
+ ret = g76x_of_prop_import_one(client, "fan_gear_mode",
+ g76x_set_gear_multiplier);
+ if (ret)
+ return ret;
+
+ ret = g76x_of_prop_import_one(client, "pwm_polarity",
+ g76x_set_pwm_polarity);
+ if (ret)
+ return ret;
+
+ return g76x_of_prop_import_one(client, "fan_startv",
+ g76x_set_fan_startv);
+}
+
+#else
+static int g76x_of_prop_import(struct i2c_client *client)
+{
+ return 0;
+}
+
+static int g76x_of_clock_enable(struct i2c_client *client)
+{
+ return 0;
+}
+
+static void g76x_of_clock_disable(struct i2c_client *client) { }
+#endif
+
+/*
+ * Import platform data from chip
+ */
+static int g76x_pdata_prop_import(struct i2c_client *client)
+{
+ struct g76x_platform_data *pdata = client->dev.platform_data;
+ int ret;
+
+ if (!pdata)
+ return 0;
+
+ ret = g76x_set_gear_multiplier(&client->dev, pdata->fan_gear_mode);
+ if (ret)
+ return ret;
+
+ ret = g76x_set_pwm_polarity(&client->dev, pdata->pwm_polarity);
+ if (ret)
+ return ret;
+
+ ret = g76x_set_fan_startv(&client->dev, pdata->fan_startv);
+ if (ret)
+ return ret;
+
+ return g76x_set_clk_freq(&client->dev, pdata->clk_freq);
+}
+
+
+/* driver-specific control functions */
+
+/* Get fan failure flag state. 0 (no fault) or 1 (fan failure) */
+static int g76x_get_failure_state(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ return sprintf(buf, "%d\n", !!(data->fan_sta & G76x_REG_FAN_STA_FAIL));
+}
+
+/* Get fan out of control flag state. 0 (fan ooc) or 1 (fan under control) */
+static int g76x_get_ooc_state(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ return sprintf(buf, "%d\n", !!(data->fan_sta & G76x_REG_FAN_STA_OOC));
+}
+
+/*
+ * Get and Set fan speed value. Can be called both in closed and open-loop mode
+ * but effect will only be seen after closed-loop mode is configured.
+ *
+ * Accepts values between
+ * 0 (stops the fan) and 255 (full speed) for open-loop
+ * 0 (stops the fan) and 254 (full speed) for closed-loop
+ * 255 in closed-loop mode will disarm the fan
+ */
+static int g76x_get_fan_speed(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+ int ret;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ switch (data->fan_cmd1 & G76x_REG_FAN_CMD1_CTRL_MODE) {
+ case G76x_FAN_MODE_CLOSED_LOOP:
+ ret = sprintf(buf, "%d\n", data->act_cnt);
+ break;
+ default:
+ ret = sprintf(buf, "%d\n", data->set_out);
+ break;
+ }
+
+ return ret;
+}
+
+static int g76x_set_fan_speed(struct device *dev, unsigned long val)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+ int ret;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ val = clamp_val(val, 0, 255);
+
+ mutex_lock(&data->update_lock);
+ switch (data->fan_cmd1 & G76x_REG_FAN_CMD1_CTRL_MODE) {
+ case G76x_FAN_MODE_CLOSED_LOOP:
+ data->set_cnt = val;
+ ret = i2c_smbus_write_byte_data(data->client, G76x_REG_SET_CNT, val);
+ break;
+ default:
+ data->set_out = val;
+ ret = i2c_smbus_write_byte_data(data->client, G76x_REG_SET_OUT, val);
+ break;
+ }
+ data->valid = false;
+ mutex_unlock(&data->update_lock);
+
+ return ret;
+}
+
+/*
+ * Get/set the fan speed as a RPM value;
+ * in closed-loop mode the chip will regulate the fan speed using
+ * pulses from fan tachometer.
+ *
+ * in open-loop mode fan speed feedback is not available so it will
+ * estimate rpm from open-loop input speed.
+ *
+ * Also note that due to rounding errors it is possible that you don't read
+ * back exactly the value you have set.
+ */
+ static int g76x_get_fan_rpm(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+ unsigned int rpm;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ mutex_lock(&data->update_lock);
+ switch (data->fan_cmd1 & G76x_REG_FAN_CMD1_CTRL_MODE) {
+ case G76x_FAN_MODE_CLOSED_LOOP:
+ if (data->set_cnt == 0xff) /* fan disarmed */
+ rpm = 0;
+ else
+ rpm = g76x_rpm_from_cnt(data->act_cnt, data->clk_freq,
+ G76x_PULSE_FROM_REG(data->fan_cmd1),
+ G76x_CLKDIV_FROM_REG(data->fan_cmd1),
+ G76x_GEARMULT_FROM_REG(data->fan_cmd2));
+ break;
+ default:
+ /* estimate rpm from input speed value as a percentage of common max RPM */
+ rpm = (data->set_out * 3000) / 255;
+ break;
+ }
+ mutex_unlock(&data->update_lock);
+
+ return sprintf(buf, "%u\n", rpm);
+}
+
+static int g76x_set_fan_rpm(struct device *dev, unsigned long rpm)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+ unsigned int speed;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ speed = g76x_cnt_from_rpm(rpm, data->clk_freq,
+ G76x_PULSE_FROM_REG(data->fan_cmd1),
+ G76x_CLKDIV_FROM_REG(data->fan_cmd1),
+ G76x_GEARMULT_FROM_REG(data->fan_cmd2));
+
+ return g76x_set_fan_speed(dev, speed);
+}
+
+/* Get/set the fan speed as a percentage of max speed */
+static int g76x_get_fan_level(struct device *dev, char *buf)
+{
+ struct g76x_data *data = g76x_update_client(dev);
+ unsigned int level;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ mutex_lock(&data->update_lock);
+
+ switch (data->fan_cmd1 & G76x_REG_FAN_CMD1_CTRL_MODE) {
+ case G76x_FAN_MODE_CLOSED_LOOP:
+ if (data->set_cnt == 0xff) /* fan disarmed */
+ level = 0;
+ else
+ level = g76x_level_from_cnt(data->act_cnt);
+ break;
+ default:
+ /* estimate level from input speed value */
+ level = g76x_level_from_cnt(data->set_out);
+ break;
+ }
+ mutex_unlock(&data->update_lock);
+
+ return sprintf(buf, "%d\n", level);
+}
+
+static int g76x_set_fan_level(struct device *dev, unsigned long level)
+{
+ level = clamp_val(level, 0, 100);
+
+ return g76x_set_fan_speed(dev, g76x_cnt_from_level(level));
+}
+
+/* Setup parameters to a default (and safe) known state and arm the fan */
+static int g76x_fan_init(struct device *dev)
+{
+ /*
+ * g76x PowerOn/Reset state:
+ * set_cnt = 0xff (fan disarmed)
+ * act_cnt = 0xff
+ * fan_sta = 0x01 (no fault, no ooc)
+ * set_out = 0x0 (open-loop speed set to 0)
+ * fan_cmd1 = 0x10 (2 ppr, positive duty, clk div 1,
+ * closed-loop, ADC mode, ooc detection disabled,
+ * failure detection disabled)
+ * fan_cmd2 = 0x01 (fan startv 1, gear mode x1, alert# unmasked)
+ */
+ int ret;
+ /* default clock frequency */
+ ret = g76x_set_clk_freq(dev, 32768);
+ if (ret < 0) goto err_init;
+
+ /* enable ooc detection */
+ ret = g76x_set_ooc_detection(dev, 1);
+ if (ret < 0) goto err_init;
+
+ /* enable failure detection */
+ ret = g76x_set_failure_detection(dev, 1);
+ if (ret < 0) goto err_init;
+
+ /* set speed to 0 and arm the fan */
+ ret = g76x_set_fan_speed(dev, 0);
+ if (ret < 0) goto err_init;
+
+ return 0;
+
+err_init:
+ return -EIO;
+}
+
+
+/*
+ * Generic fan driver layer
+ */
+
+static struct fan_ops g76x_fan_ops = {
+ .get_fan_startv = g76x_get_fan_startv,
+ .set_fan_startv = g76x_set_fan_startv,
+ .get_gear_multiplier = g76x_get_gear_multiplier,
+ .set_gear_multiplier = g76x_set_gear_multiplier,
+ .get_fan_ppr = g76x_get_fan_ppr,
+ .set_fan_ppr = g76x_set_fan_ppr,
+ .get_pwm_polarity = g76x_get_pwm_polarity,
+ .set_pwm_polarity = g76x_set_pwm_polarity,
+ .get_clk_freq = g76x_get_clk_freq,
+ .set_clk_freq = g76x_set_clk_freq,
+ .get_clk_div = g76x_get_clk_div,
+ .set_clk_div = g76x_set_clk_div,
+ .get_control_mode = g76x_get_control_mode,
+ .set_control_mode = g76x_set_control_mode,
+ .get_output_mode = g76x_get_output_mode,
+ .set_output_mode = g76x_set_output_mode,
+ .get_ooc_detection = g76x_get_ooc_detection,
+ .set_ooc_detection = g76x_set_ooc_detection,
+ .get_failure_detection = g76x_get_failure_detection,
+ .set_failure_detection = g76x_set_failure_detection,
+ .get_failure_state = g76x_get_failure_state,
+ .get_ooc_state = g76x_get_ooc_state,
+ .get_fan_speed = g76x_get_fan_speed,
+ .set_fan_speed = g76x_set_fan_speed,
+ .get_fan_rpm = g76x_get_fan_rpm,
+ .set_fan_rpm = g76x_set_fan_rpm,
+ .get_fan_level = g76x_get_fan_level,
+ .set_fan_level = g76x_set_fan_level,
+ .fan_init = g76x_fan_init,
+};
+
+static int g76x_fan_probe(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct g76x_data *data;
+ int ret;
+
+ /* make sure that chip is present by reading a register */
+ ret = i2c_smbus_read_byte_data(client, G76x_REG_FAN_CMD1);
+ if (ret < 0) {
+ ret = -ENODEV;
+ goto err_ret;
+ }
+
+ /* allocate and initialize driver data structure */
+ data = xzalloc(sizeof(*data));
+ if (!data) {
+ ret = -ENOMEM;
+ goto err_ret;
+ }
+
+ /* pass specific driver data to i2c device */
+ dev->priv = data;
+ i2c_set_clientdata(client, data);
+ data->client = client;
+ mutex_init(&data->update_lock);
+
+ /* Get configuration via DT ... */
+ ret = g76x_of_clock_enable(client);
+ if (ret)
+ goto err_alloc;
+
+ ret = g76x_of_prop_import(client);
+ if (ret)
+ goto err_clk;
+
+ /* ... or platform_data */
+ ret = g76x_pdata_prop_import(client);
+ if (ret)
+ goto err_clk;
+
+ ret = fan_device_register(&client->dev, &g76x_fan_ops);
+ if (ret)
+ goto err_clk;
+
+ return 0;
+
+err_clk:
+ g76x_of_clock_disable(client);
+err_alloc:
+ free(data);
+err_ret:
+ return ret;
+}
+
+static void g76x_fan_remove(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct g76x_data *data = i2c_get_clientdata(client);
+
+ fan_device_unregister(&client->dev);
+ g76x_of_clock_disable(client);
+ free(data);
+}
+
+static struct driver g76x_driver = {
+ .name = "g76x",
+ .probe = g76x_fan_probe,
+ .remove = g76x_fan_remove,
+ .id_table = g76x_id,
+ .of_match_table = of_match_ptr(g76x_dt_match),
+};
+
+device_i2c_driver(g76x_driver);
diff --git a/drivers/fan/other_fan_controllers/Kconfig b/drivers/fan/other_fan_controllers/Kconfig
new file mode 100644
index 0000000000..8e750fdae5
--- /dev/null
+++ b/drivers/fan/other_fan_controllers/Kconfig
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config GPIO_PWM_FAN
+ bool "Generic GPIO PWM controlled fan driver"
+ depends on PWM
+ help
+ Support fan speed control trough GPIO PWM signal
diff --git a/drivers/fan/other_fan_controllers/Makefile b/drivers/fan/other_fan_controllers/Makefile
new file mode 100644
index 0000000000..43ea3ab5b3
--- /dev/null
+++ b/drivers/fan/other_fan_controllers/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+# fan controller interface types
diff --git a/drivers/fan/spi_fan_controllers/Kconfig b/drivers/fan/spi_fan_controllers/Kconfig
new file mode 100644
index 0000000000..a4e40e534e
--- /dev/null
+++ b/drivers/fan/spi_fan_controllers/Kconfig
@@ -0,0 +1 @@
+# SPDX-License-Identifier: GPL-2.0-only
diff --git a/drivers/fan/spi_fan_controllers/Makefile b/drivers/fan/spi_fan_controllers/Makefile
new file mode 100644
index 0000000000..43ea3ab5b3
--- /dev/null
+++ b/drivers/fan/spi_fan_controllers/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+# fan controller interface types
diff --git a/include/fan/fan.h b/include/fan/fan.h
new file mode 100644
index 0000000000..3289c6d214
--- /dev/null
+++ b/include/fan/fan.h
@@ -0,0 +1,94 @@
+#ifndef __FAN_H__
+#define __FAN_H__
+
+#include <common.h>
+#include <device.h>
+#include <driver.h>
+#include <linux/list.h>
+#include <linux/types.h>
+#include <fs.h>
+#include <malloc.h>
+#include <xfuncs.h>
+#include <globalvar.h>
+#include <errno.h>
+
+
+/* Generic fan driver fan device management */
+
+struct fan_device { /*!< generic fan driver device */
+ struct list_head list; /*!< link in generic fan driver devices list */
+ struct device *dev; /*!< specific fan controller driver device */
+ const struct fan_ops *ops; /*!< specific fan controller driver operations */
+ struct cdev *cdev; /*!< generic filesystem fan device */
+};
+
+struct fan_ops {
+ int (*get_fan_startv)(struct device *dev, char *buf);
+ int (*set_fan_startv)(struct device *dev, unsigned long val);
+
+ int (*get_gear_multiplier)(struct device *dev, char *buf);
+ int (*set_gear_multiplier)(struct device *dev, unsigned long val);
+
+ int (*get_fan_ppr)(struct device *dev, char *buf);
+ int (*set_fan_ppr)(struct device *dev, unsigned long val);
+
+ int (*get_pwm_polarity)(struct device *dev, char *buf);
+ int (*set_pwm_polarity)(struct device *dev, unsigned long val);
+
+ int (*get_clk_freq)(struct device *dev, char *buf);
+ int (*set_clk_freq)(struct device *dev, unsigned long val);
+
+ int (*get_clk_div)(struct device *dev, char *buf);
+ int (*set_clk_div)(struct device *dev, unsigned long val);
+
+ int (*get_control_mode)(struct device *dev, char *buf);
+ int (*set_control_mode)(struct device *dev, unsigned long val);
+
+ int (*get_output_mode)(struct device *dev, char *buf);
+ int (*set_output_mode)(struct device *dev, unsigned long val);
+
+ int (*get_ooc_detection)(struct device *dev, char *buf);
+ int (*set_ooc_detection)(struct device *dev, unsigned long val);
+
+ int (*get_failure_detection)(struct device *dev, char *buf);
+ int (*set_failure_detection)(struct device *dev, unsigned long val);
+
+ int (*get_failure_state)(struct device *dev, char *buf);
+ int (*get_ooc_state)(struct device *dev, char *buf);
+
+ int (*get_fan_speed)(struct device *dev, char *buf);
+ int (*set_fan_speed)(struct device *dev, unsigned long val);
+
+ int (*get_fan_rpm)(struct device *dev, char *buf);
+ int (*set_fan_rpm)(struct device *dev, unsigned long val);
+
+ int (*get_fan_level)(struct device *dev, char *buf);
+ int (*set_fan_level)(struct device *dev, unsigned long val);
+
+ int (*fan_init)(struct device *dev);
+};
+/*
+static void fan_set_dev(struct fan_device *fan, struct device *dev);
+static struct device *fan_get_dev(struct fan_device *fan);
+
+static void fan_set_ops(struct fan_device *fan, struct fan_ops *ops);
+static const struct fan_ops *fan_get_ops(struct fan_device *fan);
+*/
+
+/* Generic filesystem fan device management */
+/*
+static void cdev_set_fan(struct fan_device *fan);
+static struct fan_device *cdev_get_fan(struct cdev *cdev);
+
+static void cdev_set_name(struct cdev *cdev);
+static char *cdev_get_name(struct cdev *cdev);
+*/
+struct fan_device *fan_device_find(const char *devmatch);
+
+
+/* specific fan controller driver device management */
+
+int fan_device_register(struct device *dev, struct fan_ops *ops);
+void fan_device_unregister(struct device *dev);
+
+#endif /* __FAN_H__ */
\ No newline at end of file
diff --git a/include/fan/i2c_fan_controllers/g76x.h b/include/fan/i2c_fan_controllers/g76x.h
new file mode 100644
index 0000000000..e9075a1b5c
--- /dev/null
+++ b/include/fan/i2c_fan_controllers/g76x.h
@@ -0,0 +1,37 @@
+/*
+ * Platform data structure for g762 fan controller driver
+ *
+ * Copyright (C) 2013, Arnaud EBALARD <arno@natisbad.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __LINUX_PLATFORM_DATA_G762_H__
+#define __LINUX_PLATFORM_DATA_G762_H__
+
+/*
+ * Following structure can be used to set g762 driver platform specific data
+ * during board init. Note that passing a sparse structure is possible but
+ * will result in non-specified attributes to be set to default value, hence
+ * overloading those installed during boot (e.g. by u-boot).
+ */
+
+struct g76x_platform_data {
+ unsigned int fan_startv;
+ unsigned int fan_gear_mode;
+ unsigned int pwm_polarity;
+ unsigned int clk_freq;
+};
+
+#endif /* __LINUX_PLATFORM_DATA_G762_H__ */
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* [PATCH 14/19] commands: add fan control command
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (12 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 13/19] drivers: fan: add fan framework and API Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-23 13:57 ` [PATCH 15/19] commands: integrate fan command into Kconfig and Makefile Luca Lauro via B4 Relay
` (4 subsequent siblings)
18 siblings, 0 replies; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
commands/fan.c | 286 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 286 insertions(+)
diff --git a/commands/fan.c b/commands/fan.c
new file mode 100644
index 0000000000..1581751d51
--- /dev/null
+++ b/commands/fan.c
@@ -0,0 +1,286 @@
+#include <common.h>
+#include <command.h>
+#include <complete.h>
+#include <errno.h>
+#include <fan/fan.h>
+
+static int do_fan(int argc, char *argv[])
+{
+ struct fan_device *fan;
+ char buf[32];
+ unsigned long val;
+ int ret = 0;
+
+
+ if (argc < 3) {
+ return COMMAND_ERROR_USAGE;
+ }
+
+ fan = fan_device_find(argv[2]);
+ if (IS_ERR(fan)) {
+ printf("Device not found: %s\n", argv[2]);
+ return PTR_ERR(fan);
+ }
+ if (!fan->ops) {
+ printf("Fan driver ops not initialized\n");
+ return -ENODEV;
+ }
+
+ if (!strcmp(argv[1], "get")) {
+ if (argc == 4) {
+ if (!strcmp(argv[3], "startv") && fan->ops->get_fan_startv) {
+ ret = fan->ops->get_fan_startv(fan->dev, buf);
+ printf("Fan startup voltage mode: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "gearmult") && fan->ops->get_gear_multiplier) {
+ ret = fan->ops->get_gear_multiplier(fan->dev, buf);
+ printf("Gear multiplier: %s\n", ret >= 0 ? buf : "N/A");
+}
+ else if (!strcmp(argv[3], "outmode") && fan->ops->get_output_mode) {
+ ret = fan->ops->get_output_mode(fan->dev, buf);
+ printf("Output mode: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "ppr") && fan->ops->get_fan_ppr) {
+ ret = fan->ops->get_fan_ppr(fan->dev, buf);
+ printf("Pulses per revolution: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "pwmpol") && fan->ops->get_pwm_polarity) {
+ ret = fan->ops->get_pwm_polarity(fan->dev, buf);
+ printf("PWM polarity: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "clkfreq") && fan->ops->get_clk_freq) {
+ ret = fan->ops->get_clk_freq(fan->dev, buf);
+ printf("Clock frequency: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "clkdiv") && fan->ops->get_clk_div) {
+ ret = fan->ops->get_clk_div(fan->dev, buf);
+ printf("Clock divider: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "ctrlmode") && fan->ops->get_control_mode) {
+ ret = fan->ops->get_control_mode(fan->dev, buf);
+ printf("Control mode: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "outmode") && fan->ops->get_output_mode) {
+ ret = fan->ops->get_output_mode(fan->dev, buf);
+ printf("Output mode: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "oocdet") && fan->ops->get_ooc_detection) {
+ ret = fan->ops->get_ooc_detection(fan->dev, buf);
+ printf("Out of control detection: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "faultdet") && fan->ops->get_failure_detection) {
+ ret = fan->ops->get_failure_detection(fan->dev, buf);
+ printf("Failure detection: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "oocstate") && fan->ops->get_ooc_state) {
+ ret = fan->ops->get_ooc_state(fan->dev, buf);
+ printf("Fan out of control state: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "faultstate") && fan->ops->get_failure_state) {
+ ret = fan->ops->get_failure_state(fan->dev, buf);
+ printf("Fan failure state: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "speed") && fan->ops->get_fan_speed) {
+ ret = fan->ops->get_fan_speed(fan->dev, buf);
+ printf("Fan speed: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "rpm") && fan->ops->get_fan_rpm) {
+ ret = fan->ops->get_fan_rpm(fan->dev, buf);
+ printf("Fan rpm: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "level") && fan->ops->get_fan_level) {
+ ret = fan->ops->get_fan_level(fan->dev, buf);
+ printf("Fan level: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "all")) {
+ if (fan->ops->get_fan_startv) {
+ ret = fan->ops->get_fan_startv(fan->dev, buf);
+ printf("Fan startup voltage mode: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ if (fan->ops->get_gear_multiplier) {
+ ret = fan->ops->get_gear_multiplier(fan->dev, buf);
+ printf("Gear multiplier: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ if (fan->ops->get_fan_ppr) {
+ ret = fan->ops->get_fan_ppr(fan->dev, buf);
+ printf("Pulses per revolution: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ if (fan->ops->get_pwm_polarity) {
+ ret = fan->ops->get_pwm_polarity(fan->dev, buf);
+ printf("PWM polarity: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ if (fan->ops->get_clk_freq) {
+ ret = fan->ops->get_clk_freq(fan->dev, buf);
+ printf("Clock frequency: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ if (fan->ops->get_clk_div) {
+ ret = fan->ops->get_clk_div(fan->dev, buf);
+ printf("Clock divider: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ if (fan->ops->get_control_mode) {
+ ret = fan->ops->get_control_mode(fan->dev, buf);
+ printf("Control mode: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ if (fan->ops->get_output_mode) {
+ ret = fan->ops->get_output_mode(fan->dev, buf);
+ printf("Output mode: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ if (fan->ops->get_ooc_detection) {
+ ret = fan->ops->get_ooc_detection(fan->dev, buf);
+ printf("Out of control detection: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ if (fan->ops->get_failure_detection) {
+ ret = fan->ops->get_failure_detection(fan->dev, buf);
+ printf("Failure detection: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ if (fan->ops->get_failure_state) {
+ ret = fan->ops->get_failure_state(fan->dev, buf);
+ printf("Fan failure state: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ if (fan->ops->get_ooc_state) {
+ ret = fan->ops->get_ooc_state(fan->dev, buf);
+ printf("Fan out of control state: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ if (fan->ops->get_fan_speed) {
+ ret = fan->ops->get_fan_speed(fan->dev, buf);
+ printf("Fan speed: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ if (fan->ops->get_fan_rpm) {
+ ret = fan->ops->get_fan_rpm(fan->dev, buf);
+ printf("Fan rpm: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ if (fan->ops->get_fan_level) {
+ ret = fan->ops->get_fan_level(fan->dev, buf);
+ printf("Fan level: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ }
+ else {
+ printf("Invalid parameter: %s\n", argv[3]);
+ return COMMAND_ERROR_USAGE;
+ }
+ }
+ else {
+ printf("Invalid number of arguments for %s cmd\n", argv[1]);
+ return COMMAND_ERROR_USAGE;
+ }
+ }
+ else if (!strcmp(argv[1], "set")) {
+ if (argc == 5) {
+ val = simple_strtoul(argv[4], NULL, 10);
+ snprintf(buf, sizeof(buf), "%lu", val);
+
+ if (!strcmp(argv[3], "startv") && fan->ops->set_fan_startv) {
+ ret = fan->ops->set_fan_startv(fan->dev, val);
+ printf("Fan startup voltage set to: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "gearmult") && fan->ops->set_gear_multiplier) {
+ ret = fan->ops->set_gear_multiplier(fan->dev, val);
+ printf("Gear multiplier set to: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "ppr") && fan->ops->set_fan_ppr) {
+ ret = fan->ops->set_fan_ppr(fan->dev, val);
+ printf("Pulses per revolution set to: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "pwmpol") && fan->ops->set_pwm_polarity) {
+ ret = fan->ops->set_pwm_polarity(fan->dev, val);
+ printf("PWM polarity set to: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "clkfreq") && fan->ops->set_clk_freq) {
+ ret = fan->ops->set_clk_freq(fan->dev, val);
+ printf("Clock frequency set to: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "clkdiv") && fan->ops->set_clk_div) {
+ ret = fan->ops->set_clk_div(fan->dev, val);
+ printf("Clock divider set to: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "ctrlmode") && fan->ops->set_control_mode) {
+ ret = fan->ops->set_control_mode(fan->dev, val);
+ printf("Control mode set to: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "outmode") && fan->ops->set_output_mode) {
+ ret = fan->ops->set_output_mode(fan->dev, val);
+ printf("Output mode set to: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "ooc") && fan->ops->set_ooc_detection) {
+ ret = fan->ops->set_ooc_detection(fan->dev, val);
+ printf("Out of control detection set to: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "fault") && fan->ops->set_failure_detection) {
+ ret = fan->ops->set_failure_detection(fan->dev, val);
+ printf("Failure detection set to: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "speed") && fan->ops->set_fan_speed) {
+ ret = fan->ops->set_fan_speed(fan->dev, val);
+ printf("Fan speed set to: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "rpm") && fan->ops->set_fan_rpm) {
+ ret = fan->ops->set_fan_rpm(fan->dev, val);
+ printf("Fan rpm set to: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else if (!strcmp(argv[3], "level") && fan->ops->set_fan_level) {
+ ret = fan->ops->set_fan_level(fan->dev, val);
+ printf("Fan level set to: %s\n", ret >= 0 ? buf : "N/A");
+ }
+ else {
+ printf("Invalid parameter: %s\n", argv[3]);
+ return COMMAND_ERROR_USAGE;
+ }
+ }
+ else {
+ printf("Invalid number of arguments for %s cmd\n", argv[1]);
+ return COMMAND_ERROR_USAGE;
+ }
+ }
+ else if (!strcmp(argv[1], "init")) {
+ if (argc == 3) {
+ if (fan->ops->fan_init) {
+ ret = fan->ops->fan_init(fan->dev);
+ printf("Fan initialization: %s\n", ret >= 0 ? "successful" : "N/A");
+ }
+ }
+ else {
+ printf("Invalid number of arguments for %s cmd\n", argv[1]);
+ return COMMAND_ERROR_USAGE;
+ }
+ }
+ else {
+ printf("Invalid cmd: %s\n", argv[1]);
+ return COMMAND_ERROR_USAGE;
+ }
+
+ return ret;
+}
+
+BAREBOX_CMD_HELP_START(fan)
+BAREBOX_CMD_HELP_TEXT("cmd options:\n")
+BAREBOX_CMD_HELP_OPT("get", "get fan parameter")
+BAREBOX_CMD_HELP_OPT("set", "set fan parameter")
+BAREBOX_CMD_HELP_OPT("init", "initialize fan")
+
+BAREBOX_CMD_HELP_TEXT("\nparameter options:\n")
+BAREBOX_CMD_HELP_OPT("outmode", "fan controller ouptut mode [0 (adc)|1 (pwm)]")
+BAREBOX_CMD_HELP_OPT("clkdiv", "fan controller prescaler value [1|2|4|8]")
+BAREBOX_CMD_HELP_OPT("clkfreq", "input clock frequency (Hz)")
+BAREBOX_CMD_HELP_OPT("ppr", "fan tachometer pulses per revolution [2|4]")
+BAREBOX_CMD_HELP_OPT("ctrlmode", "fan control mode [0 (open-loop)|1 (closed-loop)]")
+BAREBOX_CMD_HELP_OPT("speed", "fan speed register value [0:255]")
+BAREBOX_CMD_HELP_OPT("rpm", "fan rpm [0:(max fan rpm)]")
+BAREBOX_CMD_HELP_OPT("level", "fan speed percentage [0:100]")
+BAREBOX_CMD_HELP_OPT("startv", "fan startup voltage [0|1|2|3]")
+BAREBOX_CMD_HELP_OPT("gearmult", "gear multiplier [0|1|2]")
+BAREBOX_CMD_HELP_OPT("pwmpol", "PWM polarity [0 (positive)|1 (negative)]")
+BAREBOX_CMD_HELP_OPT("faultdet", "enable/disable failure detection [0|1]")
+BAREBOX_CMD_HELP_OPT("oocdet", "enable/disable out-of-control detection [0|1]")
+BAREBOX_CMD_HELP_OPT("fault", "fan failure flag [0 (ok)|1 (fault)]")
+BAREBOX_CMD_HELP_OPT("ooc", "fan out of control flag [1 (ok):0 (ooc)]")
+BAREBOX_CMD_HELP_OPT("all", "print all fan parameters")
+BAREBOX_CMD_HELP_TEXT("\n")
+BAREBOX_CMD_HELP_END
+
+BAREBOX_CMD_START(fan)
+ .cmd = do_fan,
+ BAREBOX_CMD_DESC("fan management")
+ BAREBOX_CMD_OPTS("<cmd> <devpath> <parameter> [value]")
+ BAREBOX_CMD_GROUP(CMD_GRP_HWMANIP)
+ BAREBOX_CMD_HELP(cmd_fan_help)
+BAREBOX_CMD_END
\ No newline at end of file
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* [PATCH 15/19] commands: integrate fan command into Kconfig and Makefile
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (13 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 14/19] commands: add fan control command Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-23 13:57 ` [PATCH 16/19] usb: ehci: add Marvell EHCI host controller driver Luca Lauro via B4 Relay
` (3 subsequent siblings)
18 siblings, 0 replies; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
commands/Kconfig | 7 +++++++
commands/Makefile | 2 ++
2 files changed, 9 insertions(+)
diff --git a/commands/Kconfig b/commands/Kconfig
index 297c89b4b5..d661f0eaab 100644
--- a/commands/Kconfig
+++ b/commands/Kconfig
@@ -2337,6 +2337,13 @@ config CMD_RKSECURE
The rksecure command allows to retrieve information about and enable
secure boot for Rockchip rk3588 SoCs.
+config CMD_FAN
+ bool
+ depends on FAN
+ prompt "fan command"
+ help
+ Control and monitor target fans.
+
# end Hardware manipulation commands
endmenu
diff --git a/commands/Makefile b/commands/Makefile
index 2563efb12f..227c40b3f5 100644
--- a/commands/Makefile
+++ b/commands/Makefile
@@ -173,4 +173,6 @@ obj-$(CONFIG_CMD_DMSETUP) += dmsetup.o
obj-$(CONFIG_CMD_VERITYSETUP) += veritysetup.o
obj-$(CONFIG_CMD_SCONFIG) += sconfig.o
obj-$(CONFIG_CMD_RKSECURE) += rksecure.o
+obj-$(CONFIG_CMD_FAN) += fan.o
+
UBSAN_SANITIZE_ubsan.o := y
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* [PATCH 16/19] usb: ehci: add Marvell EHCI host controller driver
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (14 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 15/19] commands: integrate fan command into Kconfig and Makefile Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-27 14:09 ` Sascha Hauer
2026-07-23 13:57 ` [PATCH 17/19] usb: ehci: minor fixes for Marvell compatibility Luca Lauro via B4 Relay
` (2 subsequent siblings)
18 siblings, 1 reply; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
drivers/usb/host/Kconfig | 7 ++
drivers/usb/host/Makefile | 13 +--
drivers/usb/host/ehci-marvell.c | 229 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 243 insertions(+), 6 deletions(-)
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 58f276cdb4..21bf876d69 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -22,6 +22,13 @@ config USB_EHCI_ZYNQ
help
Enable support for Zynq on-chip EHCI USB controller
+config USB_EHCI_MARVELL
+ bool "Marvell USB EHCI driver"
+ depends on ARCH_MVEBU
+ depends on USB_EHCI
+ help
+ Enable support for Marvell USB EHCI controller
+
config USB_OHCI
bool "OHCI driver"
depends on !MMU && HAS_DMA
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index cbddfbe923..932581d691 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -1,8 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-only
-obj-$(CONFIG_USB_EHCI) += ehci-hcd.o
-obj-$(CONFIG_USB_EHCI_OMAP) += ehci-omap.o
+obj-$(CONFIG_USB_EHCI) += ehci-hcd.o
+obj-$(CONFIG_USB_EHCI_OMAP) += ehci-omap.o
obj-$(CONFIG_USB_EHCI_ATMEL) += ehci-atmel.o
-obj-$(CONFIG_USB_EHCI_ZYNQ) += ehci-zynq.o
-obj-$(CONFIG_USB_OHCI) += ohci-hcd.o
-obj-$(CONFIG_USB_OHCI_AT91) += ohci-at91.o
-obj-$(CONFIG_USB_XHCI) += xhci.o xhci-mem.o xhci-ring.o
+obj-$(CONFIG_USB_EHCI_ZYNQ) += ehci-zynq.o
+obj-$(CONFIG_USB_EHCI_MARVELL) += ehci-marvell.o
+obj-$(CONFIG_USB_OHCI) += ohci-hcd.o
+obj-$(CONFIG_USB_OHCI_AT91) += ohci-at91.o
+obj-$(CONFIG_USB_XHCI) += xhci.o xhci-mem.o xhci-ring.o
diff --git a/drivers/usb/host/ehci-marvell.c b/drivers/usb/host/ehci-marvell.c
new file mode 100644
index 0000000000..8accfd697d
--- /dev/null
+++ b/drivers/usb/host/ehci-marvell.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Marvell Orion / Kirkwood / Armada EHCI host for barebox
+ *
+ * Porting basato su ehci-marvell.c di U-Boot moderno,
+ * adattato al modello ehci_register() di barebox.
+ */
+/*#define DEBUG*/
+
+#include <common.h>
+#include <driver.h>
+#include <init.h>
+#include <io.h>
+#include <of.h>
+#include <linux/usb/usb.h>
+#include <linux/usb/ehci.h>
+#include <linux/mbus.h>
+
+#include "ehci.h"
+
+#define USB_WINDOW_CTRL(i) (0x320 + ((i) << 4))
+#define USB_WINDOW_BASE(i) (0x324 + ((i) << 4))
+#define USB_TARGET_DRAM 0x0
+
+#define USB2_SBUSCFG_OFF 0x90
+
+#define USB_SBUSCFG_BAWR_OFF 0x6
+#define USB_SBUSCFG_BARD_OFF 0x3
+#define USB_SBUSCFG_AHBBRST_OFF 0x0
+
+#define USB_SBUSCFG_BAWR_ALIGN_64B 0x4
+#define USB_SBUSCFG_BARD_ALIGN_64B 0x4
+#define USB_SBUSCFG_AHBBRST_INCR16 0x7
+
+/* AC5 special window mapping (copiato da U-Boot) */
+#define USB_TO_DRAM_TARGET_ID 0x2
+#define USB_TO_DRAM_ATTR_ID 0x0
+#define USB_DRAM_BASE 0x00000000
+#define USB_DRAM_SIZE 0xfff /* non oltrepassare lo spazio sorgente */
+
+/*
+ * Stato privato per questa istanza EHCI Marvell.
+ * Non serve conoscere struct ehci_host: basta il puntatore.
+ */
+struct marvell_ehci {
+ struct ehci_host *ehci;
+ void __iomem *base;
+ struct device *dev;
+ u32 dram_bus_base;
+};
+
+static void marvell_ehci_sbuscfg_fixup(void __iomem *base)
+{
+ /*
+ * SBUSCFG: Control for the AMBA system bus interface:
+ * BAWR = BARD = 4 : Align rd/wr bursts packets larger than 64 bytes
+ * AHBBRST = 7 : Align AHB burst for packets larger than 64 bytes
+ */
+ writel((USB_SBUSCFG_BAWR_ALIGN_64B << USB_SBUSCFG_BAWR_OFF) |
+ (USB_SBUSCFG_BARD_ALIGN_64B << USB_SBUSCFG_BARD_OFF) |
+ (USB_SBUSCFG_AHBBRST_INCR16 << USB_SBUSCFG_AHBBRST_OFF),
+ base + USB2_SBUSCFG_OFF);
+}
+
+/*
+ * Programma le decoding windows USB -> DRAM usando le info MBUS.
+ * Per AC5 replica lo speciale mapping 0x0..USB_DRAM_SIZE.
+ */
+static void marvell_usb_brg_adrdec_setup(struct marvell_ehci *priv)
+{
+ const struct mbus_dram_target_info *dram;
+ void __iomem *base = priv->base;
+ int i;
+
+ dev_info(priv->dev, "USB adrdec setup: ENTER\n");
+
+ dram = mvebu_mbus_dram_info();
+ if (!dram) {
+ dev_err(priv->dev, "no MBUS DRAM info\n");
+ return;
+ }
+
+ for (i = 0; i < 4; i++) {
+ writel(0, base + USB_WINDOW_CTRL(i));
+ writel(0, base + USB_WINDOW_BASE(i));
+ }
+
+ if (priv->dev->of_node &&
+ of_device_is_compatible(priv->dev->of_node, "marvell,ac5-ehci")) {
+ /* mappa DRAM vista da USB a 0x0 (come in U-Boot) */
+ writel((USB_DRAM_SIZE << 16) |
+ (USB_TO_DRAM_ATTR_ID << 8) |
+ (USB_TO_DRAM_TARGET_ID << 4) | 1,
+ base + USB_WINDOW_CTRL(0));
+ writel(USB_DRAM_BASE, base + USB_WINDOW_BASE(0));
+
+ dev_dbg(priv->dev,
+ "AC5 decoding windows: ctrl=0x%08x base=0x%08x\n",
+ readl(base + USB_WINDOW_CTRL(0)),
+ readl(base + USB_WINDOW_BASE(0)));
+ } else {
+ /* caso generico mvebu: una window per ciascun CS DRAM */
+
+ for (i = 0; i < dram->num_cs; i++) {
+ const struct mbus_dram_window *cs = dram->cs + i;
+
+ /* Write size, attributes and target id to control register */
+ writel(((cs->size - 1) & 0xffff0000) |
+ (cs->mbus_attr << 8) |
+ (dram->mbus_dram_target_id << 4) | 1,
+ base + USB_WINDOW_CTRL(i));
+
+ /* Write base address to base register */
+ writel(cs->base, base + USB_WINDOW_BASE(i));
+ }
+ }
+
+ for (i = 0; i < 4; i++) {
+ u32 ctrl = readl(base + USB_WINDOW_CTRL(i));
+ u32 winb = readl(base + USB_WINDOW_BASE(i));
+ dev_info(priv->dev, "WIN%d: CTRL=0x%08x BASE=0x%08x\n", i, ctrl, winb);
+ }
+}
+
+/*
+ * Hook di init chiamato da ehci-hcd.c prima di avviare l'host.
+ * Qui facciamo solo la parte “bridge address decoding”.
+ */
+static int marvell_ehci_init(void *drvdata)
+{
+ struct marvell_ehci *priv = drvdata;
+
+ marvell_usb_brg_adrdec_setup(priv);
+
+ writel((USB_SBUSCFG_BAWR_ALIGN_64B << USB_SBUSCFG_BAWR_OFF) |
+ (USB_SBUSCFG_BARD_ALIGN_64B << USB_SBUSCFG_BARD_OFF) |
+ (USB_SBUSCFG_AHBBRST_INCR16 << USB_SBUSCFG_AHBBRST_OFF),
+ priv->base + 0x100 + 0x90);
+ mdelay(50);
+
+ /*
+ * La programmazione di SBUSCFG (BAWR/BARD/AHBBRST) in U-Boot
+ * serve solo per SoC senza hlock (Armada3700, ecc.).
+ * Per ora la lasciamo fuori: se in futuro servirà, possiamo
+ * aggiungere un post_init o un hook dedicato nel core EHCI.
+ */
+
+ dev_dbg(priv->dev, "marvell_ehci_init done\n");
+
+ return 0;
+}
+
+static int marvell_ehci_probe(struct device *dev)
+{
+ struct marvell_ehci *priv;
+ struct resource *iores;
+ struct ehci_data data;
+ void __iomem *base;
+ struct ehci_host *ehci;
+
+ priv = xzalloc(sizeof(*priv));
+ priv->dev = dev;
+ dev->priv = priv;
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores))
+ return PTR_ERR(iores);
+
+ base = IOMEM(iores->start);
+ priv->base = base;
+
+ memset(&data, 0, sizeof(data));
+
+ /*
+ * Sui controller Marvell il blocco EHCI inizia a offset 0x100
+ * rispetto alla base USB.
+ */
+ data.hccr = base + 0x100;
+
+ u32 capbase = readl(data.hccr); /* HC_CAPBASE */
+ u8 caplength = capbase & 0xff; /* CAPLENGTH */
+
+ data.hcor = (void __iomem *)((u8 *)data.hccr + caplength);
+
+ dev_info(dev, "EHCI probe: base=%p hccr=%p hcor=%p\n",
+ base, data.hccr, data.hcor);
+
+ dev_info(dev, "EHCI CAPBASE=0x%08x CAPLENGTH=0x%02x\n",
+ capbase, caplength);
+
+ data.init = marvell_ehci_init;
+ data.drvdata = priv;
+
+ if (of_device_is_compatible(dev->of_node, "marvell,armada-3700-ehci"))
+ marvell_ehci_sbuscfg_fixup(base);
+
+ dev_dbg(dev, "marvell_ehci_probe: done\n");
+
+ ehci = ehci_register(dev, &data);
+ if (IS_ERR(ehci))
+ return PTR_ERR(ehci);
+
+ priv->ehci = ehci;
+
+ return 0;
+}
+
+static void marvell_ehci_remove(struct device *dev)
+{
+ struct marvell_ehci *priv = dev->priv;
+
+ if (priv && priv->ehci)
+ ehci_unregister(priv->ehci);
+}
+
+static const struct of_device_id marvell_ehci_dt_ids[] = {
+ { .compatible = "marvell,orion-ehci" },
+ { .compatible = "marvell,armada-3700-ehci" },
+ { .compatible = "marvell,ac5-ehci" },
+ { /* sentinel */ }
+};
+
+static struct driver marvell_ehci_driver = {
+ .name = "marvell-ehci",
+ .probe = marvell_ehci_probe,
+ .remove = marvell_ehci_remove,
+ .of_compatible = DRV_OF_COMPAT(marvell_ehci_dt_ids),
+};
+device_platform_driver(marvell_ehci_driver);
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* Re: [PATCH 16/19] usb: ehci: add Marvell EHCI host controller driver
2026-07-23 13:57 ` [PATCH 16/19] usb: ehci: add Marvell EHCI host controller driver Luca Lauro via B4 Relay
@ 2026-07-27 14:09 ` Sascha Hauer
0 siblings, 0 replies; 30+ messages in thread
From: Sascha Hauer @ 2026-07-27 14:09 UTC (permalink / raw)
To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro
On 2026-07-23 15:57, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
>
> ---
> drivers/usb/host/Kconfig | 7 ++
> drivers/usb/host/Makefile | 13 +--
> drivers/usb/host/ehci-marvell.c | 229 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 243 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
> index 58f276cdb4..21bf876d69 100644
> --- a/drivers/usb/host/Kconfig
> +++ b/drivers/usb/host/Kconfig
> @@ -22,6 +22,13 @@ config USB_EHCI_ZYNQ
> help
> Enable support for Zynq on-chip EHCI USB controller
>
> +config USB_EHCI_MARVELL
> + bool "Marvell USB EHCI driver"
> + depends on ARCH_MVEBU
> + depends on USB_EHCI
> + help
> + Enable support for Marvell USB EHCI controller
> +
> config USB_OHCI
> bool "OHCI driver"
> depends on !MMU && HAS_DMA
> diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
> index cbddfbe923..932581d691 100644
> --- a/drivers/usb/host/Makefile
> +++ b/drivers/usb/host/Makefile
> @@ -1,8 +1,9 @@
> # SPDX-License-Identifier: GPL-2.0-only
> -obj-$(CONFIG_USB_EHCI) += ehci-hcd.o
> -obj-$(CONFIG_USB_EHCI_OMAP) += ehci-omap.o
> +obj-$(CONFIG_USB_EHCI) += ehci-hcd.o
> +obj-$(CONFIG_USB_EHCI_OMAP) += ehci-omap.o
> obj-$(CONFIG_USB_EHCI_ATMEL) += ehci-atmel.o
> -obj-$(CONFIG_USB_EHCI_ZYNQ) += ehci-zynq.o
> -obj-$(CONFIG_USB_OHCI) += ohci-hcd.o
> -obj-$(CONFIG_USB_OHCI_AT91) += ohci-at91.o
> -obj-$(CONFIG_USB_XHCI) += xhci.o xhci-mem.o xhci-ring.o
> +obj-$(CONFIG_USB_EHCI_ZYNQ) += ehci-zynq.o
> +obj-$(CONFIG_USB_EHCI_MARVELL) += ehci-marvell.o
> +obj-$(CONFIG_USB_OHCI) += ohci-hcd.o
> +obj-$(CONFIG_USB_OHCI_AT91) += ohci-at91.o
> +obj-$(CONFIG_USB_XHCI) += xhci.o xhci-mem.o xhci-ring.o
> diff --git a/drivers/usb/host/ehci-marvell.c b/drivers/usb/host/ehci-marvell.c
> new file mode 100644
> index 0000000000..8accfd697d
> --- /dev/null
> +++ b/drivers/usb/host/ehci-marvell.c
> @@ -0,0 +1,229 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Marvell Orion / Kirkwood / Armada EHCI host for barebox
> + *
> + * Porting basato su ehci-marvell.c di U-Boot moderno,
> + * adattato al modello ehci_register() di barebox.
> + */
Per favore usa l'inglese nel codice ;)
> +static int marvell_ehci_probe(struct device *dev)
> +{
> + struct marvell_ehci *priv;
> + struct resource *iores;
> + struct ehci_data data;
> + void __iomem *base;
> + struct ehci_host *ehci;
> +
> + priv = xzalloc(sizeof(*priv));
> + priv->dev = dev;
> + dev->priv = priv;
> +
> + iores = dev_request_mem_resource(dev, 0);
> + if (IS_ERR(iores))
> + return PTR_ERR(iores);
> +
> + base = IOMEM(iores->start);
> + priv->base = base;
> +
> + memset(&data, 0, sizeof(data));
Easier done with
struct ehci_data data = {};
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] 30+ messages in thread
* [PATCH 17/19] usb: ehci: minor fixes for Marvell compatibility
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (15 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 16/19] usb: ehci: add Marvell EHCI host controller driver Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-27 14:12 ` Sascha Hauer
2026-07-23 13:57 ` [PATCH 18/19] ata: ahci: fixes and updates for Marvell SATA controller Luca Lauro via B4 Relay
2026-07-23 13:57 ` [PATCH 19/19] drivers: integrate USB/AHCI changes Luca Lauro via B4 Relay
18 siblings, 1 reply; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
drivers/usb/host/ehci-hcd.c | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 51b9e52a4f..b72436b312 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -857,8 +857,6 @@ static int ehci_init(struct usb_host *host)
struct QH *periodic;
int i;
- ehci_halt(ehci);
-
/* EHCI spec section 4.1 */
if (ehci_reset(ehci) != 0)
return -1;
@@ -870,16 +868,16 @@ static int ehci_init(struct usb_host *host)
}
ehci->qh_list[0].qh_link = cpu_to_hc32(ehci_qh_dma(ehci, &ehci->qh_list[1]) |
- QH_LINK_TYPE_QH);
+ QH_LINK_TYPE_QH);
ehci->qh_list[0].qh_endpt1 = cpu_to_hc32(QH_ENDPT1_H(1) |
- QH_ENDPT1_EPS(USB_SPEED_HIGH));
+ QH_ENDPT1_EPS(USB_SPEED_HIGH));
ehci->qh_list[0].qh_curtd = cpu_to_hc32(QT_NEXT_TERMINATE);
ehci->qh_list[0].qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
ehci->qh_list[0].qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
ehci->qh_list[0].qt_token = cpu_to_hc32(QT_TOKEN_STATUS_HALTED);
ehci->qh_list[1].qh_link = cpu_to_hc32(ehci_qh_dma(ehci,
- &ehci->qh_list[0]) |
+ &ehci->qh_list[0]) |
QH_LINK_TYPE_QH);
ehci->qh_list[1].qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
@@ -897,6 +895,8 @@ static int ehci_init(struct usb_host *host)
periodic->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
periodic->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
+ ehci->periodic_queue_dma = ehci_qh_dma(ehci, periodic);
+
/*
* Step 2: Setup frame-list: Every microframe, USB tries the same list.
* In particular, device specifications on polling frequency
@@ -906,23 +906,23 @@ static int ehci_init(struct usb_host *host)
* Split Transactions will be spread across microframes using
* S-mask and C-mask.
*/
- if (ehci->periodic_list == NULL)
+ if (ehci->periodic_list == NULL) {
+ ehci->periodic_list = dma_alloc_coherent(DMA_DEVICE_BROKEN, 1024 * 4,
+ &ehci->periodic_list_dma);
/*
* FIXME: this memory chunk have to be 4k aligned AND
* reside in coherent memory. Current implementation of
* dma_alloc_coherent() allocates PAGE_SIZE aligned memory chunks.
* PAGE_SIZE less then 4k will break this code.
*/
- ehci->periodic_list = dma_alloc_coherent(DMA_DEVICE_BROKEN, 1024 * 4,
- &ehci->periodic_list_dma);
for (i = 0; i < 1024; i++) {
- ehci->periodic_list[i] = cpu_to_hc32((unsigned long)ehci->periodic_queue_dma
- | QH_LINK_TYPE_QH);
+ ehci->periodic_list[i] = cpu_to_hc32(ehci->periodic_queue_dma
+ | QH_LINK_TYPE_QH);
}
/* Set periodic list base address */
ehci_writel(&ehci->hcor->or_periodiclistbase,
- (uint32_t)ehci->periodic_list_dma);
+ ehci->periodic_list_dma);
reg = ehci_readl(&ehci->hccr->cr_hcsparams);
descriptor.hub.bNbrPorts = HCS_N_PORTS(reg);
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* Re: [PATCH 17/19] usb: ehci: minor fixes for Marvell compatibility
2026-07-23 13:57 ` [PATCH 17/19] usb: ehci: minor fixes for Marvell compatibility Luca Lauro via B4 Relay
@ 2026-07-27 14:12 ` Sascha Hauer
0 siblings, 0 replies; 30+ messages in thread
From: Sascha Hauer @ 2026-07-27 14:12 UTC (permalink / raw)
To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro
On 2026-07-23 15:57, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
>
> ---
> drivers/usb/host/ehci-hcd.c | 22 +++++++++++-----------
> 1 file changed, 11 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
> index 51b9e52a4f..b72436b312 100644
> --- a/drivers/usb/host/ehci-hcd.c
> +++ b/drivers/usb/host/ehci-hcd.c
> @@ -857,8 +857,6 @@ static int ehci_init(struct usb_host *host)
> struct QH *periodic;
> int i;
>
> - ehci_halt(ehci);
> -
> /* EHCI spec section 4.1 */
> if (ehci_reset(ehci) != 0)
> return -1;
> @@ -870,16 +868,16 @@ static int ehci_init(struct usb_host *host)
> }
>
> ehci->qh_list[0].qh_link = cpu_to_hc32(ehci_qh_dma(ehci, &ehci->qh_list[1]) |
> - QH_LINK_TYPE_QH);
> + QH_LINK_TYPE_QH);
Please drop the whitespace-only changes in this patch.
>From what is left: It deserves a better description why the changes are
necessary. We can always read *what* is being done from the patch, but
the most important thing for future readers is *why* it has been done.
Sascha
> ehci->qh_list[0].qh_endpt1 = cpu_to_hc32(QH_ENDPT1_H(1) |
> - QH_ENDPT1_EPS(USB_SPEED_HIGH));
> + QH_ENDPT1_EPS(USB_SPEED_HIGH));
> ehci->qh_list[0].qh_curtd = cpu_to_hc32(QT_NEXT_TERMINATE);
> ehci->qh_list[0].qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
> ehci->qh_list[0].qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
> ehci->qh_list[0].qt_token = cpu_to_hc32(QT_TOKEN_STATUS_HALTED);
>
> ehci->qh_list[1].qh_link = cpu_to_hc32(ehci_qh_dma(ehci,
> - &ehci->qh_list[0]) |
> + &ehci->qh_list[0]) |
> QH_LINK_TYPE_QH);
> ehci->qh_list[1].qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
>
> @@ -897,6 +895,8 @@ static int ehci_init(struct usb_host *host)
> periodic->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
> periodic->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
>
> + ehci->periodic_queue_dma = ehci_qh_dma(ehci, periodic);
> +
> /*
> * Step 2: Setup frame-list: Every microframe, USB tries the same list.
> * In particular, device specifications on polling frequency
> @@ -906,23 +906,23 @@ static int ehci_init(struct usb_host *host)
> * Split Transactions will be spread across microframes using
> * S-mask and C-mask.
> */
> - if (ehci->periodic_list == NULL)
> + if (ehci->periodic_list == NULL) {
> + ehci->periodic_list = dma_alloc_coherent(DMA_DEVICE_BROKEN, 1024 * 4,
> + &ehci->periodic_list_dma);
> /*
> * FIXME: this memory chunk have to be 4k aligned AND
> * reside in coherent memory. Current implementation of
> * dma_alloc_coherent() allocates PAGE_SIZE aligned memory chunks.
> * PAGE_SIZE less then 4k will break this code.
> */
> - ehci->periodic_list = dma_alloc_coherent(DMA_DEVICE_BROKEN, 1024 * 4,
> - &ehci->periodic_list_dma);
> for (i = 0; i < 1024; i++) {
> - ehci->periodic_list[i] = cpu_to_hc32((unsigned long)ehci->periodic_queue_dma
> - | QH_LINK_TYPE_QH);
> + ehci->periodic_list[i] = cpu_to_hc32(ehci->periodic_queue_dma
> + | QH_LINK_TYPE_QH);
> }
>
> /* Set periodic list base address */
> ehci_writel(&ehci->hcor->or_periodiclistbase,
> - (uint32_t)ehci->periodic_list_dma);
> + ehci->periodic_list_dma);
>
> reg = ehci_readl(&ehci->hccr->cr_hcsparams);
> descriptor.hub.bNbrPorts = HCS_N_PORTS(reg);
>
> --
> 2.47.3
>
>
>
--
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] 30+ messages in thread
* [PATCH 18/19] ata: ahci: fixes and updates for Marvell SATA controller
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (16 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 17/19] usb: ehci: minor fixes for Marvell compatibility Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-27 14:15 ` Sascha Hauer
2026-07-23 13:57 ` [PATCH 19/19] drivers: integrate USB/AHCI changes Luca Lauro via B4 Relay
18 siblings, 1 reply; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
drivers/ata/ahci.c | 656 ++++++++++++++++++++++++++++++-----------------------
drivers/ata/ahci.h | 1 +
2 files changed, 374 insertions(+), 283 deletions(-)
diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
index 819dc37b3e..6c32393959 100644
--- a/drivers/ata/ahci.c
+++ b/drivers/ata/ahci.c
@@ -19,7 +19,9 @@
#include <disks.h>
#include <ata_drive.h>
#include <linux/sizes.h>
+#include <linux/pci.h>
#include <clock.h>
+#include <poweroff.h>
#include "ahci.h"
@@ -47,6 +49,54 @@
#define ahci_debug(ahci, fmt, arg...) \
dev_dbg(ahci->dev, fmt, ##arg)
+#define ATA_CMD_FLUSH_EXT 0xEA
+#define ATA_CMD_STANDBYNOW1 0xE0
+
+#ifndef PCI_VENDOR_ID_MARVELL_EXT
+#define PCI_VENDOR_ID_MARVELL_EXT 0x1b4b
+#endif
+
+static LIST_HEAD(ahci_devices);
+
+static const struct pci_device_id ahci_pci_tbl[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_MARVELL_EXT, 0x9170) },
+ { 0, }
+};
+
+static int ahci_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct ahci_device *ahci;
+ void __iomem *mmio;
+ int ret;
+
+ dev_info(&pdev->dev, "ahci: PCI probe %04x:%04x rev %02x\n",
+ pdev->vendor, pdev->device, pdev->revision);
+
+ ret = pci_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ pci_set_master(pdev);
+
+ mmio = pci_iomap(pdev, 5);
+ if (!mmio)
+ return -ENODEV;
+
+ ahci = xzalloc(sizeof(*ahci));
+ ahci->dev = &pdev->dev;
+ ahci->mmio_base = mmio;
+ pdev->dev.priv = ahci;
+
+ ret = ahci_add_host(ahci);
+ if (ret) {
+ free(ahci);
+ pdev->dev.priv = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+
struct ahci_cmd_hdr {
u32 opts;
u32 status;
@@ -148,45 +198,63 @@ static int ahci_fill_sg(struct ahci_port *ahci_port, dma_addr_t buf_dma, int buf
return sg_count;
}
-static int ahci_io(struct ahci_port *ahci_port, u8 *fis, int fis_len, void *rbuf,
- const void *wbuf, int buf_len)
+static int ahci_io(struct ahci_port *ahci_port, u8 *fis, int fis_len,
+ void *rbuf, const void *wbuf, int buf_len)
{
- u32 opts;
- int sg_count;
- int ret;
- void *buf;
- dma_addr_t buf_dma;
- enum dma_data_direction dma_dir;
+ u32 opts;
+ int sg_count = 0;
+ int ret;
+ void *buf = NULL;
+ dma_addr_t buf_dma = 0;
+ enum dma_data_direction dma_dir = DMA_NONE;
- if (!ahci_link_ok(ahci_port, 1))
- return -EIO;
+ if (!ahci_link_ok(ahci_port, 1))
+ return -EIO;
- if (wbuf) {
- buf = (void *)wbuf;
- dma_dir = DMA_TO_DEVICE;
- } else {
- buf = rbuf;
- dma_dir = DMA_FROM_DEVICE;
- }
+ if (buf_len > 0) {
+ if (wbuf) {
+ buf = (void *)wbuf;
+ dma_dir = DMA_TO_DEVICE;
+ } else {
+ buf = rbuf;
+ dma_dir = DMA_FROM_DEVICE;
+ }
- buf_dma = dma_map_single(ahci_port->ahci->dev, buf, buf_len, dma_dir);
+ buf_dma = dma_map_single(ahci_port->ahci->dev, buf, buf_len, dma_dir);
+ sg_count = ahci_fill_sg(ahci_port, buf_dma, buf_len);
+ }
- memcpy(ahci_port->cmd_tbl, fis, fis_len);
+ memcpy(ahci_port->cmd_tbl, fis, fis_len);
- sg_count = ahci_fill_sg(ahci_port, buf_dma, buf_len);
- opts = (fis_len >> 2) | (sg_count << 16);
- if (wbuf)
- opts |= CMD_LIST_OPTS_WRITE;
- ahci_fill_cmd_slot(ahci_port, opts);
+ opts = (fis_len >> 2) | (sg_count << 16);
+ if (wbuf && buf_len > 0)
+ opts |= CMD_LIST_OPTS_WRITE;
+ ahci_fill_cmd_slot(ahci_port, opts);
- ahci_port_write_f(ahci_port, PORT_CMD_ISSUE, 1);
+ ahci_port_write_f(ahci_port, PORT_CMD_ISSUE, 1);
- ret = wait_on_timeout(WAIT_DATAIO,
- (ahci_port_read(ahci_port, PORT_CMD_ISSUE) & 0x1) == 0);
+ ret = wait_on_timeout(WAIT_DATAIO,
+ (ahci_port_read(ahci_port, PORT_CMD_ISSUE) & 0x1) == 0);
- dma_unmap_single(ahci_port->ahci->dev, buf_dma, buf_len, dma_dir);
+ if (buf_len > 0)
+ dma_unmap_single(ahci_port->ahci->dev, buf_dma, buf_len, dma_dir);
- return ret;
+ return ret;
+}
+
+static int ahci_ata_nodata(struct ahci_port *ahci, u8 command, u8 feature)
+{
+ u8 fis[20] = {
+ 0x27, /* Host to device FIS */
+ 1 << 7, /* Command FIS */
+ command, /* Command */
+ feature, /* Features */
+ };
+
+ if (!ahci_link_ok(ahci, 0))
+ return -ENODEV;
+
+ return ahci_io(ahci, fis, sizeof(fis), NULL, NULL, 0);
}
/*
@@ -272,152 +340,88 @@ static int ahci_write(struct ata_port *ata, const void *buf, sector_t block,
static int ahci_init_port(struct ahci_port *ahci_port)
{
- u32 val, cmd;
- void *mem;
- dma_addr_t mem_dma;
- int ret;
-
- /* make sure port is not active */
- val = ahci_port_read(ahci_port, PORT_CMD);
- if (val & (PORT_CMD_LIST_ON | PORT_CMD_FIS_ON | PORT_CMD_FIS_RX | PORT_CMD_START)) {
- ahci_port_debug(ahci_port, "Port is active. Deactivating.\n");
- val &= ~(PORT_CMD_LIST_ON | PORT_CMD_FIS_ON |
- PORT_CMD_FIS_RX | PORT_CMD_START);
- ahci_port_write(ahci_port, PORT_CMD, val);
-
- /*
- * spec says 500 msecs for each bit, so
- * this is slightly incorrect.
- */
- mdelay(500);
- }
-
- mem = dma_alloc_coherent(DMA_DEVICE_BROKEN,
- AHCI_PORT_PRIV_DMA_SZ, &mem_dma);
- if (!mem) {
- return -ENOMEM;
- }
-
- /*
- * First item in chunk of DMA memory: 32-slot command list,
- * 32 bytes each in size
- */
- ahci_port->cmd_slot = mem;
- ahci_port->cmd_slot_dma = mem_dma;
-
- ahci_port_debug(ahci_port, "cmd_slot = 0x%p (0x%pad)\n",
- ahci_port->cmd_slot, &ahci_port->cmd_slot_dma);
-
- /*
- * Second item: Received-FIS area
- */
- ahci_port->rx_fis = mem + AHCI_CMD_LIST_SZ;
- ahci_port->rx_fis_dma = mem_dma + AHCI_CMD_LIST_SZ;
-
- /*
- * Third item: data area for storing a single command
- * and its scatter-gather table
- */
- ahci_port->cmd_tbl = mem + AHCI_CMD_LIST_SZ + AHCI_RX_FIS_SZ;
- ahci_port->cmd_tbl_dma = mem_dma + AHCI_CMD_LIST_SZ + AHCI_RX_FIS_SZ;
-
- ahci_port_debug(ahci_port, "cmd_tbl = 0x%p (0x%pad)\n",
- ahci_port->cmd_tbl, &ahci_port->cmd_tbl_dma);
-
- ahci_port->cmd_tbl_sg = ahci_port->cmd_tbl + AHCI_CMD_TBL_HDR_SZ;
-
- ahci_port_write_f(ahci_port, PORT_LST_ADDR, lower_32_bits(ahci_port->cmd_slot_dma));
- if (ahci_port->ahci->cap & HOST_CAP_64)
- ahci_port_write_f(ahci_port, PORT_LST_ADDR_HI, upper_32_bits(ahci_port->cmd_slot_dma));
- ahci_port_write_f(ahci_port, PORT_FIS_ADDR, lower_32_bits(ahci_port->rx_fis_dma));
- if (ahci_port->ahci->cap & HOST_CAP_64)
- ahci_port_write_f(ahci_port, PORT_FIS_ADDR_HI, upper_32_bits(ahci_port->rx_fis_dma));
-
- /*
- * Add the spinup command to whatever mode bits may
- * already be on in the command register.
- */
- cmd = ahci_port_read(ahci_port, PORT_CMD);
- cmd |= PORT_CMD_FIS_RX;
- cmd |= PORT_CMD_SPIN_UP;
- cmd |= PORT_CMD_ICC_ACTIVE;
- ahci_port_write_f(ahci_port, PORT_CMD, cmd);
-
- mdelay(10);
-
- cmd = ahci_port_read(ahci_port, PORT_CMD);
- cmd |= PORT_CMD_START;
- ahci_port_write_f(ahci_port, PORT_CMD, cmd);
-
- /*
- * Bring up SATA link.
- * SATA link bringup time is usually less than 1 ms; only very
- * rarely has it taken between 1-2 ms. Never seen it above 2 ms.
- */
- ret = wait_on_timeout(WAIT_LINKUP,
- (ahci_port_read(ahci_port, PORT_SCR_STAT) & PORT_SCR_STAT_DET) == 0x3);
- if (ret) {
- ahci_port_info(ahci_port, "SATA link timeout\n");
- ret = -ETIMEDOUT;
- goto err_init;
- }
-
- ahci_port_info(ahci_port, "SATA link ok\n");
-
- /* Clear error status */
- val = ahci_port_read(ahci_port, PORT_SCR_ERR);
- if (val)
- ahci_port_write(ahci_port, PORT_SCR_ERR, val);
-
- ahci_port_info(ahci_port, "Spinning up device...\n");
-
- ret = wait_on_timeout(WAIT_SPINUP,
- ((ahci_port_read(ahci_port, PORT_TFDATA) &
- (ATA_STATUS_BUSY | ATA_STATUS_DRQ)) == 0) ||
- ((ahci_port_read(ahci_port, PORT_SCR_STAT) &
- PORT_SCR_STAT_DET) == 1));
- if (ret) {
- ahci_port_info(ahci_port, "timeout.\n");
- ret = -ENODEV;
- goto err_init;
- }
-
- if ((ahci_port_read(ahci_port, PORT_SCR_STAT) & PORT_SCR_STAT_DET) == 1) {
- ahci_port_info(ahci_port, "down.\n");
- ret = -ENODEV;
- goto err_init;
- }
-
- ahci_port_info(ahci_port, "ok.\n");
-
- val = ahci_port_read(ahci_port, PORT_SCR_ERR);
-
- ahci_port_write(ahci_port, PORT_SCR_ERR, val);
-
- /* ack any pending irq events for this port */
- val = ahci_port_read(ahci_port, PORT_IRQ_STAT);
- if (val)
- ahci_port_write(ahci_port, PORT_IRQ_STAT, val);
-
- ahci_iowrite(ahci_port->ahci, HOST_IRQ_STAT, 1 << ahci_port->num);
-
- /* set irq mask (enables interrupts) */
- ahci_port_write(ahci_port, PORT_IRQ_MASK, DEF_PORT_IRQ);
-
- /* register linkup ports */
- val = ahci_port_read(ahci_port, PORT_SCR_STAT);
-
- ahci_port_debug(ahci_port, "status: 0x%08x\n", val);
-
- if ((val & PORT_SCR_STAT_DET) == 0x3)
- return 0;
-
- ret = -ENODEV;
-
-err_init:
- dma_free_coherent(DMA_DEVICE_BROKEN,
- mem, mem_dma, AHCI_PORT_PRIV_DMA_SZ);
- return ret;
+ u32 cmd, val;
+ void *mem;
+ dma_addr_t mem_dma;
+ int ret;
+
+ /* 1. Disable port (clear ST, FRE) */
+ cmd = ahci_port_read(ahci_port, PORT_CMD);
+ cmd &= ~(PORT_CMD_START | PORT_CMD_FIS_RX | PORT_CMD_FIS_ON |
+ PORT_CMD_LIST_ON | PORT_CMD_SPIN_UP);
+ ahci_port_write_f(ahci_port, PORT_CMD, cmd);
+
+ /* Wait for FR=0 and CR=0 */
+ ret = wait_on_timeout(SECOND,
+ !(ahci_port_read(ahci_port, PORT_CMD) &
+ (PORT_CMD_FIS_ON | PORT_CMD_LIST_ON)));
+ if (ret)
+ dev_warn(ahci_port->ahci->dev, "timeout waiting for port disable\n");
+
+ /* 2. Clear errors */
+ val = ahci_port_read(ahci_port, PORT_SCR_ERR);
+ if (val)
+ ahci_port_write(ahci_port, PORT_SCR_ERR, val);
+
+ /* 3. COMRESET: write DET=1 then DET=0 */
+ ahci_port_write(ahci_port, PORT_SCR_CTL, 1);
+ udelay(1000);
+ ahci_port_write(ahci_port, PORT_SCR_CTL, 0);
+
+ /* 4. Wait for PHY ready */
+ ret = wait_on_timeout(SECOND,
+ (ahci_port_read(ahci_port, PORT_SCR_STAT) & PORT_SCR_STAT_DET) == 0x3);
+ if (ret) {
+ ahci_port_info(ahci_port, "PHY not ready after COMRESET\n");
+ return -ETIMEDOUT;
+ }
+
+ /* 5. Allocate DMA memory (unchanged) */
+ mem = dma_alloc_coherent(DMA_DEVICE_BROKEN,
+ AHCI_PORT_PRIV_DMA_SZ, &mem_dma);
+ if (!mem)
+ return -ENOMEM;
+
+ ahci_port->cmd_slot = mem;
+ ahci_port->cmd_slot_dma = mem_dma;
+ ahci_port->rx_fis = mem + AHCI_CMD_LIST_SZ;
+ ahci_port->rx_fis_dma = mem_dma + AHCI_CMD_LIST_SZ;
+ ahci_port->cmd_tbl = mem + AHCI_CMD_LIST_SZ + AHCI_RX_FIS_SZ;
+ ahci_port->cmd_tbl_dma = mem_dma + AHCI_CMD_LIST_SZ + AHCI_RX_FIS_SZ;
+ ahci_port->cmd_tbl_sg = ahci_port->cmd_tbl + AHCI_CMD_TBL_HDR_SZ;
+
+ /* 6. Program command list + FIS base addresses */
+ ahci_port_write_f(ahci_port, PORT_LST_ADDR,
+ lower_32_bits(ahci_port->cmd_slot_dma));
+ if (ahci_port->ahci->cap & HOST_CAP_64)
+ ahci_port_write_f(ahci_port, PORT_LST_ADDR_HI,
+ upper_32_bits(ahci_port->cmd_slot_dma));
+
+ ahci_port_write_f(ahci_port, PORT_FIS_ADDR,
+ lower_32_bits(ahci_port->rx_fis_dma));
+ if (ahci_port->ahci->cap & HOST_CAP_64)
+ ahci_port_write_f(ahci_port, PORT_FIS_ADDR_HI,
+ upper_32_bits(ahci_port->rx_fis_dma));
+
+ /* 7. Enable FIS receive engine */
+ cmd = ahci_port_read(ahci_port, PORT_CMD);
+ cmd |= PORT_CMD_FIS_RX;
+ ahci_port_write_f(ahci_port, PORT_CMD, cmd);
+
+ /* 8. Enable port start */
+ cmd |= PORT_CMD_START;
+ ahci_port_write_f(ahci_port, PORT_CMD, cmd);
+
+ /* 9. Wait for device ready (TFDATA not BUSY) */
+ ret = wait_on_timeout(WAIT_SPINUP,
+ !(ahci_port_read(ahci_port, PORT_TFDATA) &
+ (ATA_STATUS_BUSY | ATA_STATUS_DRQ)));
+ if (ret) {
+ ahci_port_info(ahci_port, "device not ready\n");
+ return -ENODEV;
+ }
+
+ return 0;
}
static int ahci_port_start(struct ata_port *ata_port)
@@ -441,49 +445,12 @@ static int ahci_port_start(struct ata_port *ata_port)
}
static struct ata_port_operations ahci_ops = {
- .init = ahci_port_start,
- .read_id = ahci_read_id,
- .read = ahci_read,
- .write = ahci_write,
+ .init = ahci_port_start,
+ .read_id = ahci_read_id,
+ .read = ahci_read,
+ .write = ahci_write,
};
-#if 0
-/*
- * In the general case of generic rotating media it makes sense to have a
- * flush capability. It probably even makes sense in the case of SSDs because
- * one cannot always know for sure what kind of internal cache/flush mechanism
- * is embodied therein. At first it was planned to invoke this after the last
- * write to disk and before rebooting. In practice, knowing, a priori, which
- * is the last write is difficult. Because writing to the disk in u-boot is
- * very rare, this flush command will be invoked after every block write.
- */
-static int ata_io_flush(u8 port)
-{
- u8 fis[20];
- struct ahci_ioports *pp = &(probe_ent->port[port]);
- volatile u8 *port_mmio = (volatile u8 *)pp->port_mmio;
- u32 cmd_fis_len = 5; /* five dwords */
-
- /* Preset the FIS */
- memset(fis, 0, 20);
- fis[0] = 0x27; /* Host to device FIS. */
- fis[1] = 1 << 7; /* Command FIS. */
- fis[2] = ATA_CMD_FLUSH_EXT;
-
- memcpy((unsigned char *)pp->cmd_tbl, fis, 20);
- ahci_fill_cmd_slot(pp, cmd_fis_len);
- mywritel_with_flush(1, port_mmio + PORT_CMD_ISSUE);
-
- if (waiting_for_cmd_completed(port_mmio + PORT_CMD_ISSUE,
- WAIT_MS_FLUSH, 0x1)) {
- debug("scsi_ahci: flush command timeout on port %d.\n", port);
- return -EIO;
- }
-
- return 0;
-}
-#endif
-
void ahci_print_info(struct ahci_device *ahci)
{
u32 vers, cap, cap2, impl, speed;
@@ -570,73 +537,71 @@ static int ahci_detect(struct device *dev)
int ahci_add_host(struct ahci_device *ahci)
{
- u32 tmp, cap_save;
- int n_ports, i, ret;
-
- ahci->host_flags = ATA_FLAG_SATA
- | ATA_FLAG_NO_LEGACY
- | ATA_FLAG_MMIO
- | ATA_FLAG_PIO_DMA
- | ATA_FLAG_NO_ATAPI;
- ahci->pio_mask = 0x1f;
- ahci->udma_mask = 0x7f; /* FIXME: assume to support UDMA6 */
-
- ahci_debug(ahci, "ahci_host_init: start\n");
-
- cap_save = ahci_ioread(ahci, HOST_CAP);
- cap_save &= (HOST_CAP_SMPS | HOST_CAP_SPM);
- cap_save |= HOST_CAP_SSS; /* Staggered Spin-up. Not needed. */
-
- /* global controller reset */
- tmp = ahci_ioread(ahci, HOST_CTL);
- if ((tmp & HOST_RESET) == 0)
- ahci_iowrite_f(ahci, HOST_CTL, tmp | HOST_RESET);
-
- /*
- * reset must complete within 1 second, or
- * the hardware should be considered fried.
- */
- ret = wait_on_timeout(SECOND, (ahci_ioread(ahci, HOST_CTL) & HOST_RESET) == 0);
- if (ret) {
- ahci_debug(ahci, "controller reset failed (0x%x)\n", tmp);
- return -ENODEV;
- }
-
- ahci_iowrite_f(ahci, HOST_CTL, HOST_AHCI_EN);
- ahci_iowrite(ahci, HOST_CAP, cap_save);
- ahci_iowrite_f(ahci, HOST_PORTS_IMPL, 0xf);
-
- ahci->cap = ahci_ioread(ahci, HOST_CAP);
- ahci->port_map = ahci_ioread(ahci, HOST_PORTS_IMPL);
- ahci->n_ports = (ahci->cap & HOST_CAP_NP) + 1;
-
- ahci_debug(ahci, "cap 0x%x port_map 0x%x n_ports %d\n",
- ahci->cap, ahci->port_map, ahci->n_ports);
-
- n_ports = max_t(int, ahci->n_ports, fls(ahci->port_map));
-
- for (i = 0; i < n_ports; i++) {
- struct ahci_port *ahci_port = &ahci->ports[i];
-
- if (!(ahci->port_map & (1 << i)))
- continue;
-
- ahci_port->num = i;
- ahci_port->ahci = ahci;
- ahci_port->ata.dev = ahci->dev;
- ahci_port->port_mmio = ahci_port_base(ahci->mmio_base, i);
- ahci_port->ata.ops = &ahci_ops;
- ahci_port->ata.ahci = true;
- ata_port_register(&ahci_port->ata);
- }
-
- tmp = ahci_ioread(ahci, HOST_CTL);
- ahci_iowrite(ahci, HOST_CTL, tmp | HOST_IRQ_EN);
- tmp = ahci_ioread(ahci, HOST_CTL);
-
- ahci->dev->detect = ahci_detect;
-
- return 0;
+ u32 tmp;
+ int n_ports, i, ret;
+
+ ahci->host_flags = ATA_FLAG_SATA
+ | ATA_FLAG_NO_LEGACY
+ | ATA_FLAG_MMIO
+ | ATA_FLAG_PIO_DMA
+ | ATA_FLAG_NO_ATAPI;
+ ahci->pio_mask = 0x1f;
+ ahci->udma_mask = 0x7f; /* FIXME: assume to support UDMA6 */
+
+ ahci_debug(ahci, "ahci_host_init: start\n");
+
+ /*
+ * Global controller reset: forziamo sempre il bit HOST_RESET,
+ * come fa il kernel Linux, e aspettiamo che torni a 0.
+ */
+ tmp = ahci_ioread(ahci, HOST_CTL);
+ ahci_iowrite_f(ahci, HOST_CTL, tmp | HOST_RESET);
+
+ ret = wait_on_timeout(SECOND,
+ (ahci_ioread(ahci, HOST_CTL) & HOST_RESET) == 0);
+ if (ret) {
+ ahci_debug(ahci, "controller reset failed (HOST_CTL=0x%x)\n",
+ ahci_ioread(ahci, HOST_CTL));
+ return -ENODEV;
+ }
+
+
+ tmp = ahci_ioread(ahci, HOST_CTL);
+ tmp |= HOST_AHCI_EN;
+ ahci_iowrite_f(ahci, HOST_CTL, tmp);
+
+ ahci->cap = ahci_ioread(ahci, HOST_CAP);
+ ahci->port_map = ahci_ioread(ahci, HOST_PORTS_IMPL);
+ ahci->n_ports = (ahci->cap & HOST_CAP_NP) + 1;
+
+ ahci_debug(ahci, "cap 0x%x port_map 0x%x n_ports %d\n",
+ ahci->cap, ahci->port_map, ahci->n_ports);
+
+ n_ports = max_t(int, ahci->n_ports, fls(ahci->port_map));
+
+ for (i = 0; i < n_ports; i++) {
+ struct ahci_port *ahci_port = &ahci->ports[i];
+
+ if (!(ahci->port_map & (1 << i)))
+ continue;
+
+ ahci_port->num = i;
+ ahci_port->ahci = ahci;
+ ahci_port->ata.dev = ahci->dev;
+ ahci_port->port_mmio = ahci_port_base(ahci->mmio_base, i);
+ ahci_port->ata.ops = &ahci_ops;
+ ahci_port->ata.ahci = true;
+ ata_port_register(&ahci_port->ata);
+ }
+
+ /* enable HBA level interrupts */
+ tmp = ahci_ioread(ahci, HOST_CTL);
+ ahci_iowrite(ahci, HOST_CTL, tmp | HOST_IRQ_EN);
+
+ ahci->dev->detect = ahci_detect;
+ list_add(&ahci->list, &ahci_devices);
+
+ return 0;
}
static int ahci_probe(struct device *dev)
@@ -665,6 +630,124 @@ static int ahci_probe(struct device *dev)
return ret;
}
+/* -------------------------------------------------------------------------- */
+/* AHCI shutdown helpers (kernel-like) */
+/* -------------------------------------------------------------------------- */
+
+/* Stop DMA engine (clear START, wait LIST_ON=0) */
+static int ahci_stop_engine(struct ahci_port *port)
+{
+ u32 cmd;
+
+ cmd = ahci_port_read(port, PORT_CMD);
+
+ /* Already stopped? */
+ if (!(cmd & (PORT_CMD_START | PORT_CMD_LIST_ON)))
+ return 0;
+
+ /* Clear START */
+ cmd &= ~PORT_CMD_START;
+ ahci_port_write_f(port, PORT_CMD, cmd);
+
+ /* Wait for LIST_ON to clear */
+ return wait_on_timeout(500 * MSECOND,
+ !(ahci_port_read(port, PORT_CMD) & PORT_CMD_LIST_ON));
+}
+
+/* Stop FIS receive engine (clear FIS_RX, wait FIS_ON=0) */
+static int ahci_stop_fis_rx(struct ahci_port *port)
+{
+ u32 cmd;
+
+ cmd = ahci_port_read(port, PORT_CMD);
+ cmd &= ~PORT_CMD_FIS_RX;
+ ahci_port_write_f(port, PORT_CMD, cmd);
+
+ /* Wait for FIS_ON to clear */
+ return wait_on_timeout(1000 * MSECOND,
+ !(ahci_port_read(port, PORT_CMD) & PORT_CMD_FIS_ON));
+}
+
+/* Stop all ports (libata_pci_shutdown_one equivalent) */
+static void __maybe_unused ahci_shutdown_host(struct ahci_device *ahci)
+{
+ int i, n_ports;
+
+ n_ports = max_t(int, ahci->n_ports, fls(ahci->port_map));
+
+ for (i = 0; i < n_ports; i++) {
+ struct ahci_port *port = &ahci->ports[i];
+
+ if (!(ahci->port_map & (1 << i)))
+ continue;
+
+ /* Stop DMA engine */
+ ahci_stop_engine(port);
+
+ /* Stop FIS receive engine */
+ ahci_stop_fis_rx(port);
+ }
+}
+
+/* Issue FLUSH EXT + STANDBY IMMEDIATE */
+static void ahci_port_shutdown(struct ahci_port *port)
+{
+ if (!port->cmd_tbl || !port->cmd_slot)
+ return;
+
+ if (!ahci_link_ok(port, 0))
+ return;
+
+ if (ahci_ata_nodata(port, ATA_CMD_FLUSH_EXT, 0))
+ ahci_port_info(port, "FLUSH EXT failed\n");
+
+ if (ahci_ata_nodata(port, ATA_CMD_STANDBYNOW1, 0))
+ ahci_port_info(port, "STANDBY IMMEDIATE failed\n");
+}
+
+/* Full poweroff sequence */
+static void ahci_poweroff(struct poweroff_handler *handler, unsigned long flags)
+{
+ struct ahci_device *ahci;
+ int i, n_ports;
+
+ list_for_each_entry(ahci, &ahci_devices, list) {
+
+ if (!ahci->mmio_base)
+ continue;
+
+ /* 1. FLUSH + STANDBY su tutte le porte attive */
+ n_ports = max_t(int, ahci->n_ports, fls(ahci->port_map));
+
+ for (i = 0; i < n_ports; i++) {
+ struct ahci_port *port = &ahci->ports[i];
+
+ if (!(ahci->port_map & (1 << i)))
+ continue;
+
+ ahci_port_shutdown(port);
+ }
+
+ /* 2. (opzionale) spegnere il controller dopo i comandi
+ * Se vuoi tenerlo, fallo SOLO dopo i comandi:
+ *
+ * ahci_shutdown_host(ahci);
+ */
+ }
+}
+
+static struct poweroff_handler ahci_po_handler = {
+ .poweroff = ahci_poweroff,
+ .priority = 200, /* più alto di gpio-poweroff */
+};
+
+static int ahci_register_poweroff(void)
+{
+ poweroff_handler_register(&ahci_po_handler);
+ return 0;
+}
+postcore_initcall(ahci_register_poweroff);
+
static __maybe_unused struct of_device_id ahci_dt_ids[] = {
{
.compatible = "calxeda,hb-ahci",
@@ -674,6 +757,13 @@ static __maybe_unused struct of_device_id ahci_dt_ids[] = {
};
MODULE_DEVICE_TABLE(of, ahci_dt_ids);
+static struct pci_driver ahci_pci_driver = {
+ .name = "ahci-pci",
+ .id_table = ahci_pci_tbl,
+ .probe = ahci_pci_probe,
+};
+device_pci_driver(ahci_pci_driver);
+
static struct driver ahci_driver = {
.name = "ahci",
.probe = ahci_probe,
diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h
index 196bde73c2..d2a19f4648 100644
--- a/drivers/ata/ahci.h
+++ b/drivers/ata/ahci.h
@@ -184,6 +184,7 @@ struct ahci_port {
};
struct ahci_device {
+ struct list_head list;
struct device *dev;
struct ahci_port ports[AHCI_MAX_PORTS];
u32 n_ports;
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* Re: [PATCH 18/19] ata: ahci: fixes and updates for Marvell SATA controller
2026-07-23 13:57 ` [PATCH 18/19] ata: ahci: fixes and updates for Marvell SATA controller Luca Lauro via B4 Relay
@ 2026-07-27 14:15 ` Sascha Hauer
0 siblings, 0 replies; 30+ messages in thread
From: Sascha Hauer @ 2026-07-27 14:15 UTC (permalink / raw)
To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro
This patch basically rewrites the whole file. It seems most changes are
whitespace only. Please rework in a way that makes the changes visible.
After that we'll likely need a discussion why we need it and how we
integrate it without breaking others.
Sascha
On 2026-07-23 15:57, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
>
> ---
> drivers/ata/ahci.c | 656 ++++++++++++++++++++++++++++++-----------------------
> drivers/ata/ahci.h | 1 +
> 2 files changed, 374 insertions(+), 283 deletions(-)
>
> diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
> index 819dc37b3e..6c32393959 100644
> --- a/drivers/ata/ahci.c
> +++ b/drivers/ata/ahci.c
> @@ -19,7 +19,9 @@
> #include <disks.h>
> #include <ata_drive.h>
> #include <linux/sizes.h>
> +#include <linux/pci.h>
> #include <clock.h>
> +#include <poweroff.h>
>
> #include "ahci.h"
>
> @@ -47,6 +49,54 @@
> #define ahci_debug(ahci, fmt, arg...) \
> dev_dbg(ahci->dev, fmt, ##arg)
>
> +#define ATA_CMD_FLUSH_EXT 0xEA
> +#define ATA_CMD_STANDBYNOW1 0xE0
> +
> +#ifndef PCI_VENDOR_ID_MARVELL_EXT
> +#define PCI_VENDOR_ID_MARVELL_EXT 0x1b4b
> +#endif
> +
> +static LIST_HEAD(ahci_devices);
> +
> +static const struct pci_device_id ahci_pci_tbl[] = {
> + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL_EXT, 0x9170) },
> + { 0, }
> +};
> +
> +static int ahci_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
> +{
> + struct ahci_device *ahci;
> + void __iomem *mmio;
> + int ret;
> +
> + dev_info(&pdev->dev, "ahci: PCI probe %04x:%04x rev %02x\n",
> + pdev->vendor, pdev->device, pdev->revision);
> +
> + ret = pci_enable_device(pdev);
> + if (ret)
> + return ret;
> +
> + pci_set_master(pdev);
> +
> + mmio = pci_iomap(pdev, 5);
> + if (!mmio)
> + return -ENODEV;
> +
> + ahci = xzalloc(sizeof(*ahci));
> + ahci->dev = &pdev->dev;
> + ahci->mmio_base = mmio;
> + pdev->dev.priv = ahci;
> +
> + ret = ahci_add_host(ahci);
> + if (ret) {
> + free(ahci);
> + pdev->dev.priv = NULL;
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> struct ahci_cmd_hdr {
> u32 opts;
> u32 status;
> @@ -148,45 +198,63 @@ static int ahci_fill_sg(struct ahci_port *ahci_port, dma_addr_t buf_dma, int buf
> return sg_count;
> }
>
> -static int ahci_io(struct ahci_port *ahci_port, u8 *fis, int fis_len, void *rbuf,
> - const void *wbuf, int buf_len)
> +static int ahci_io(struct ahci_port *ahci_port, u8 *fis, int fis_len,
> + void *rbuf, const void *wbuf, int buf_len)
> {
> - u32 opts;
> - int sg_count;
> - int ret;
> - void *buf;
> - dma_addr_t buf_dma;
> - enum dma_data_direction dma_dir;
> + u32 opts;
> + int sg_count = 0;
> + int ret;
> + void *buf = NULL;
> + dma_addr_t buf_dma = 0;
> + enum dma_data_direction dma_dir = DMA_NONE;
>
> - if (!ahci_link_ok(ahci_port, 1))
> - return -EIO;
> + if (!ahci_link_ok(ahci_port, 1))
> + return -EIO;
>
> - if (wbuf) {
> - buf = (void *)wbuf;
> - dma_dir = DMA_TO_DEVICE;
> - } else {
> - buf = rbuf;
> - dma_dir = DMA_FROM_DEVICE;
> - }
> + if (buf_len > 0) {
> + if (wbuf) {
> + buf = (void *)wbuf;
> + dma_dir = DMA_TO_DEVICE;
> + } else {
> + buf = rbuf;
> + dma_dir = DMA_FROM_DEVICE;
> + }
>
> - buf_dma = dma_map_single(ahci_port->ahci->dev, buf, buf_len, dma_dir);
> + buf_dma = dma_map_single(ahci_port->ahci->dev, buf, buf_len, dma_dir);
> + sg_count = ahci_fill_sg(ahci_port, buf_dma, buf_len);
> + }
>
> - memcpy(ahci_port->cmd_tbl, fis, fis_len);
> + memcpy(ahci_port->cmd_tbl, fis, fis_len);
>
> - sg_count = ahci_fill_sg(ahci_port, buf_dma, buf_len);
> - opts = (fis_len >> 2) | (sg_count << 16);
> - if (wbuf)
> - opts |= CMD_LIST_OPTS_WRITE;
> - ahci_fill_cmd_slot(ahci_port, opts);
> + opts = (fis_len >> 2) | (sg_count << 16);
> + if (wbuf && buf_len > 0)
> + opts |= CMD_LIST_OPTS_WRITE;
> + ahci_fill_cmd_slot(ahci_port, opts);
>
> - ahci_port_write_f(ahci_port, PORT_CMD_ISSUE, 1);
> + ahci_port_write_f(ahci_port, PORT_CMD_ISSUE, 1);
>
> - ret = wait_on_timeout(WAIT_DATAIO,
> - (ahci_port_read(ahci_port, PORT_CMD_ISSUE) & 0x1) == 0);
> + ret = wait_on_timeout(WAIT_DATAIO,
> + (ahci_port_read(ahci_port, PORT_CMD_ISSUE) & 0x1) == 0);
>
> - dma_unmap_single(ahci_port->ahci->dev, buf_dma, buf_len, dma_dir);
> + if (buf_len > 0)
> + dma_unmap_single(ahci_port->ahci->dev, buf_dma, buf_len, dma_dir);
>
> - return ret;
> + return ret;
> +}
> +
> +static int ahci_ata_nodata(struct ahci_port *ahci, u8 command, u8 feature)
> +{
> + u8 fis[20] = {
> + 0x27, /* Host to device FIS */
> + 1 << 7, /* Command FIS */
> + command, /* Command */
> + feature, /* Features */
> + };
> +
> + if (!ahci_link_ok(ahci, 0))
> + return -ENODEV;
> +
> + return ahci_io(ahci, fis, sizeof(fis), NULL, NULL, 0);
> }
>
> /*
> @@ -272,152 +340,88 @@ static int ahci_write(struct ata_port *ata, const void *buf, sector_t block,
>
> static int ahci_init_port(struct ahci_port *ahci_port)
> {
> - u32 val, cmd;
> - void *mem;
> - dma_addr_t mem_dma;
> - int ret;
> -
> - /* make sure port is not active */
> - val = ahci_port_read(ahci_port, PORT_CMD);
> - if (val & (PORT_CMD_LIST_ON | PORT_CMD_FIS_ON | PORT_CMD_FIS_RX | PORT_CMD_START)) {
> - ahci_port_debug(ahci_port, "Port is active. Deactivating.\n");
> - val &= ~(PORT_CMD_LIST_ON | PORT_CMD_FIS_ON |
> - PORT_CMD_FIS_RX | PORT_CMD_START);
> - ahci_port_write(ahci_port, PORT_CMD, val);
> -
> - /*
> - * spec says 500 msecs for each bit, so
> - * this is slightly incorrect.
> - */
> - mdelay(500);
> - }
> -
> - mem = dma_alloc_coherent(DMA_DEVICE_BROKEN,
> - AHCI_PORT_PRIV_DMA_SZ, &mem_dma);
> - if (!mem) {
> - return -ENOMEM;
> - }
> -
> - /*
> - * First item in chunk of DMA memory: 32-slot command list,
> - * 32 bytes each in size
> - */
> - ahci_port->cmd_slot = mem;
> - ahci_port->cmd_slot_dma = mem_dma;
> -
> - ahci_port_debug(ahci_port, "cmd_slot = 0x%p (0x%pad)\n",
> - ahci_port->cmd_slot, &ahci_port->cmd_slot_dma);
> -
> - /*
> - * Second item: Received-FIS area
> - */
> - ahci_port->rx_fis = mem + AHCI_CMD_LIST_SZ;
> - ahci_port->rx_fis_dma = mem_dma + AHCI_CMD_LIST_SZ;
> -
> - /*
> - * Third item: data area for storing a single command
> - * and its scatter-gather table
> - */
> - ahci_port->cmd_tbl = mem + AHCI_CMD_LIST_SZ + AHCI_RX_FIS_SZ;
> - ahci_port->cmd_tbl_dma = mem_dma + AHCI_CMD_LIST_SZ + AHCI_RX_FIS_SZ;
> -
> - ahci_port_debug(ahci_port, "cmd_tbl = 0x%p (0x%pad)\n",
> - ahci_port->cmd_tbl, &ahci_port->cmd_tbl_dma);
> -
> - ahci_port->cmd_tbl_sg = ahci_port->cmd_tbl + AHCI_CMD_TBL_HDR_SZ;
> -
> - ahci_port_write_f(ahci_port, PORT_LST_ADDR, lower_32_bits(ahci_port->cmd_slot_dma));
> - if (ahci_port->ahci->cap & HOST_CAP_64)
> - ahci_port_write_f(ahci_port, PORT_LST_ADDR_HI, upper_32_bits(ahci_port->cmd_slot_dma));
> - ahci_port_write_f(ahci_port, PORT_FIS_ADDR, lower_32_bits(ahci_port->rx_fis_dma));
> - if (ahci_port->ahci->cap & HOST_CAP_64)
> - ahci_port_write_f(ahci_port, PORT_FIS_ADDR_HI, upper_32_bits(ahci_port->rx_fis_dma));
> -
> - /*
> - * Add the spinup command to whatever mode bits may
> - * already be on in the command register.
> - */
> - cmd = ahci_port_read(ahci_port, PORT_CMD);
> - cmd |= PORT_CMD_FIS_RX;
> - cmd |= PORT_CMD_SPIN_UP;
> - cmd |= PORT_CMD_ICC_ACTIVE;
> - ahci_port_write_f(ahci_port, PORT_CMD, cmd);
> -
> - mdelay(10);
> -
> - cmd = ahci_port_read(ahci_port, PORT_CMD);
> - cmd |= PORT_CMD_START;
> - ahci_port_write_f(ahci_port, PORT_CMD, cmd);
> -
> - /*
> - * Bring up SATA link.
> - * SATA link bringup time is usually less than 1 ms; only very
> - * rarely has it taken between 1-2 ms. Never seen it above 2 ms.
> - */
> - ret = wait_on_timeout(WAIT_LINKUP,
> - (ahci_port_read(ahci_port, PORT_SCR_STAT) & PORT_SCR_STAT_DET) == 0x3);
> - if (ret) {
> - ahci_port_info(ahci_port, "SATA link timeout\n");
> - ret = -ETIMEDOUT;
> - goto err_init;
> - }
> -
> - ahci_port_info(ahci_port, "SATA link ok\n");
> -
> - /* Clear error status */
> - val = ahci_port_read(ahci_port, PORT_SCR_ERR);
> - if (val)
> - ahci_port_write(ahci_port, PORT_SCR_ERR, val);
> -
> - ahci_port_info(ahci_port, "Spinning up device...\n");
> -
> - ret = wait_on_timeout(WAIT_SPINUP,
> - ((ahci_port_read(ahci_port, PORT_TFDATA) &
> - (ATA_STATUS_BUSY | ATA_STATUS_DRQ)) == 0) ||
> - ((ahci_port_read(ahci_port, PORT_SCR_STAT) &
> - PORT_SCR_STAT_DET) == 1));
> - if (ret) {
> - ahci_port_info(ahci_port, "timeout.\n");
> - ret = -ENODEV;
> - goto err_init;
> - }
> -
> - if ((ahci_port_read(ahci_port, PORT_SCR_STAT) & PORT_SCR_STAT_DET) == 1) {
> - ahci_port_info(ahci_port, "down.\n");
> - ret = -ENODEV;
> - goto err_init;
> - }
> -
> - ahci_port_info(ahci_port, "ok.\n");
> -
> - val = ahci_port_read(ahci_port, PORT_SCR_ERR);
> -
> - ahci_port_write(ahci_port, PORT_SCR_ERR, val);
> -
> - /* ack any pending irq events for this port */
> - val = ahci_port_read(ahci_port, PORT_IRQ_STAT);
> - if (val)
> - ahci_port_write(ahci_port, PORT_IRQ_STAT, val);
> -
> - ahci_iowrite(ahci_port->ahci, HOST_IRQ_STAT, 1 << ahci_port->num);
> -
> - /* set irq mask (enables interrupts) */
> - ahci_port_write(ahci_port, PORT_IRQ_MASK, DEF_PORT_IRQ);
> -
> - /* register linkup ports */
> - val = ahci_port_read(ahci_port, PORT_SCR_STAT);
> -
> - ahci_port_debug(ahci_port, "status: 0x%08x\n", val);
> -
> - if ((val & PORT_SCR_STAT_DET) == 0x3)
> - return 0;
> -
> - ret = -ENODEV;
> -
> -err_init:
> - dma_free_coherent(DMA_DEVICE_BROKEN,
> - mem, mem_dma, AHCI_PORT_PRIV_DMA_SZ);
> - return ret;
> + u32 cmd, val;
> + void *mem;
> + dma_addr_t mem_dma;
> + int ret;
> +
> + /* 1. Disable port (clear ST, FRE) */
> + cmd = ahci_port_read(ahci_port, PORT_CMD);
> + cmd &= ~(PORT_CMD_START | PORT_CMD_FIS_RX | PORT_CMD_FIS_ON |
> + PORT_CMD_LIST_ON | PORT_CMD_SPIN_UP);
> + ahci_port_write_f(ahci_port, PORT_CMD, cmd);
> +
> + /* Wait for FR=0 and CR=0 */
> + ret = wait_on_timeout(SECOND,
> + !(ahci_port_read(ahci_port, PORT_CMD) &
> + (PORT_CMD_FIS_ON | PORT_CMD_LIST_ON)));
> + if (ret)
> + dev_warn(ahci_port->ahci->dev, "timeout waiting for port disable\n");
> +
> + /* 2. Clear errors */
> + val = ahci_port_read(ahci_port, PORT_SCR_ERR);
> + if (val)
> + ahci_port_write(ahci_port, PORT_SCR_ERR, val);
> +
> + /* 3. COMRESET: write DET=1 then DET=0 */
> + ahci_port_write(ahci_port, PORT_SCR_CTL, 1);
> + udelay(1000);
> + ahci_port_write(ahci_port, PORT_SCR_CTL, 0);
> +
> + /* 4. Wait for PHY ready */
> + ret = wait_on_timeout(SECOND,
> + (ahci_port_read(ahci_port, PORT_SCR_STAT) & PORT_SCR_STAT_DET) == 0x3);
> + if (ret) {
> + ahci_port_info(ahci_port, "PHY not ready after COMRESET\n");
> + return -ETIMEDOUT;
> + }
> +
> + /* 5. Allocate DMA memory (unchanged) */
> + mem = dma_alloc_coherent(DMA_DEVICE_BROKEN,
> + AHCI_PORT_PRIV_DMA_SZ, &mem_dma);
> + if (!mem)
> + return -ENOMEM;
> +
> + ahci_port->cmd_slot = mem;
> + ahci_port->cmd_slot_dma = mem_dma;
> + ahci_port->rx_fis = mem + AHCI_CMD_LIST_SZ;
> + ahci_port->rx_fis_dma = mem_dma + AHCI_CMD_LIST_SZ;
> + ahci_port->cmd_tbl = mem + AHCI_CMD_LIST_SZ + AHCI_RX_FIS_SZ;
> + ahci_port->cmd_tbl_dma = mem_dma + AHCI_CMD_LIST_SZ + AHCI_RX_FIS_SZ;
> + ahci_port->cmd_tbl_sg = ahci_port->cmd_tbl + AHCI_CMD_TBL_HDR_SZ;
> +
> + /* 6. Program command list + FIS base addresses */
> + ahci_port_write_f(ahci_port, PORT_LST_ADDR,
> + lower_32_bits(ahci_port->cmd_slot_dma));
> + if (ahci_port->ahci->cap & HOST_CAP_64)
> + ahci_port_write_f(ahci_port, PORT_LST_ADDR_HI,
> + upper_32_bits(ahci_port->cmd_slot_dma));
> +
> + ahci_port_write_f(ahci_port, PORT_FIS_ADDR,
> + lower_32_bits(ahci_port->rx_fis_dma));
> + if (ahci_port->ahci->cap & HOST_CAP_64)
> + ahci_port_write_f(ahci_port, PORT_FIS_ADDR_HI,
> + upper_32_bits(ahci_port->rx_fis_dma));
> +
> + /* 7. Enable FIS receive engine */
> + cmd = ahci_port_read(ahci_port, PORT_CMD);
> + cmd |= PORT_CMD_FIS_RX;
> + ahci_port_write_f(ahci_port, PORT_CMD, cmd);
> +
> + /* 8. Enable port start */
> + cmd |= PORT_CMD_START;
> + ahci_port_write_f(ahci_port, PORT_CMD, cmd);
> +
> + /* 9. Wait for device ready (TFDATA not BUSY) */
> + ret = wait_on_timeout(WAIT_SPINUP,
> + !(ahci_port_read(ahci_port, PORT_TFDATA) &
> + (ATA_STATUS_BUSY | ATA_STATUS_DRQ)));
> + if (ret) {
> + ahci_port_info(ahci_port, "device not ready\n");
> + return -ENODEV;
> + }
> +
> + return 0;
> }
>
> static int ahci_port_start(struct ata_port *ata_port)
> @@ -441,49 +445,12 @@ static int ahci_port_start(struct ata_port *ata_port)
> }
>
> static struct ata_port_operations ahci_ops = {
> - .init = ahci_port_start,
> - .read_id = ahci_read_id,
> - .read = ahci_read,
> - .write = ahci_write,
> + .init = ahci_port_start,
> + .read_id = ahci_read_id,
> + .read = ahci_read,
> + .write = ahci_write,
> };
>
> -#if 0
> -/*
> - * In the general case of generic rotating media it makes sense to have a
> - * flush capability. It probably even makes sense in the case of SSDs because
> - * one cannot always know for sure what kind of internal cache/flush mechanism
> - * is embodied therein. At first it was planned to invoke this after the last
> - * write to disk and before rebooting. In practice, knowing, a priori, which
> - * is the last write is difficult. Because writing to the disk in u-boot is
> - * very rare, this flush command will be invoked after every block write.
> - */
> -static int ata_io_flush(u8 port)
> -{
> - u8 fis[20];
> - struct ahci_ioports *pp = &(probe_ent->port[port]);
> - volatile u8 *port_mmio = (volatile u8 *)pp->port_mmio;
> - u32 cmd_fis_len = 5; /* five dwords */
> -
> - /* Preset the FIS */
> - memset(fis, 0, 20);
> - fis[0] = 0x27; /* Host to device FIS. */
> - fis[1] = 1 << 7; /* Command FIS. */
> - fis[2] = ATA_CMD_FLUSH_EXT;
> -
> - memcpy((unsigned char *)pp->cmd_tbl, fis, 20);
> - ahci_fill_cmd_slot(pp, cmd_fis_len);
> - mywritel_with_flush(1, port_mmio + PORT_CMD_ISSUE);
> -
> - if (waiting_for_cmd_completed(port_mmio + PORT_CMD_ISSUE,
> - WAIT_MS_FLUSH, 0x1)) {
> - debug("scsi_ahci: flush command timeout on port %d.\n", port);
> - return -EIO;
> - }
> -
> - return 0;
> -}
> -#endif
> -
> void ahci_print_info(struct ahci_device *ahci)
> {
> u32 vers, cap, cap2, impl, speed;
> @@ -570,73 +537,71 @@ static int ahci_detect(struct device *dev)
>
> int ahci_add_host(struct ahci_device *ahci)
> {
> - u32 tmp, cap_save;
> - int n_ports, i, ret;
> -
> - ahci->host_flags = ATA_FLAG_SATA
> - | ATA_FLAG_NO_LEGACY
> - | ATA_FLAG_MMIO
> - | ATA_FLAG_PIO_DMA
> - | ATA_FLAG_NO_ATAPI;
> - ahci->pio_mask = 0x1f;
> - ahci->udma_mask = 0x7f; /* FIXME: assume to support UDMA6 */
> -
> - ahci_debug(ahci, "ahci_host_init: start\n");
> -
> - cap_save = ahci_ioread(ahci, HOST_CAP);
> - cap_save &= (HOST_CAP_SMPS | HOST_CAP_SPM);
> - cap_save |= HOST_CAP_SSS; /* Staggered Spin-up. Not needed. */
> -
> - /* global controller reset */
> - tmp = ahci_ioread(ahci, HOST_CTL);
> - if ((tmp & HOST_RESET) == 0)
> - ahci_iowrite_f(ahci, HOST_CTL, tmp | HOST_RESET);
> -
> - /*
> - * reset must complete within 1 second, or
> - * the hardware should be considered fried.
> - */
> - ret = wait_on_timeout(SECOND, (ahci_ioread(ahci, HOST_CTL) & HOST_RESET) == 0);
> - if (ret) {
> - ahci_debug(ahci, "controller reset failed (0x%x)\n", tmp);
> - return -ENODEV;
> - }
> -
> - ahci_iowrite_f(ahci, HOST_CTL, HOST_AHCI_EN);
> - ahci_iowrite(ahci, HOST_CAP, cap_save);
> - ahci_iowrite_f(ahci, HOST_PORTS_IMPL, 0xf);
> -
> - ahci->cap = ahci_ioread(ahci, HOST_CAP);
> - ahci->port_map = ahci_ioread(ahci, HOST_PORTS_IMPL);
> - ahci->n_ports = (ahci->cap & HOST_CAP_NP) + 1;
> -
> - ahci_debug(ahci, "cap 0x%x port_map 0x%x n_ports %d\n",
> - ahci->cap, ahci->port_map, ahci->n_ports);
> -
> - n_ports = max_t(int, ahci->n_ports, fls(ahci->port_map));
> -
> - for (i = 0; i < n_ports; i++) {
> - struct ahci_port *ahci_port = &ahci->ports[i];
> -
> - if (!(ahci->port_map & (1 << i)))
> - continue;
> -
> - ahci_port->num = i;
> - ahci_port->ahci = ahci;
> - ahci_port->ata.dev = ahci->dev;
> - ahci_port->port_mmio = ahci_port_base(ahci->mmio_base, i);
> - ahci_port->ata.ops = &ahci_ops;
> - ahci_port->ata.ahci = true;
> - ata_port_register(&ahci_port->ata);
> - }
> -
> - tmp = ahci_ioread(ahci, HOST_CTL);
> - ahci_iowrite(ahci, HOST_CTL, tmp | HOST_IRQ_EN);
> - tmp = ahci_ioread(ahci, HOST_CTL);
> -
> - ahci->dev->detect = ahci_detect;
> -
> - return 0;
> + u32 tmp;
> + int n_ports, i, ret;
> +
> + ahci->host_flags = ATA_FLAG_SATA
> + | ATA_FLAG_NO_LEGACY
> + | ATA_FLAG_MMIO
> + | ATA_FLAG_PIO_DMA
> + | ATA_FLAG_NO_ATAPI;
> + ahci->pio_mask = 0x1f;
> + ahci->udma_mask = 0x7f; /* FIXME: assume to support UDMA6 */
> +
> + ahci_debug(ahci, "ahci_host_init: start\n");
> +
> + /*
> + * Global controller reset: forziamo sempre il bit HOST_RESET,
> + * come fa il kernel Linux, e aspettiamo che torni a 0.
> + */
> + tmp = ahci_ioread(ahci, HOST_CTL);
> + ahci_iowrite_f(ahci, HOST_CTL, tmp | HOST_RESET);
> +
> + ret = wait_on_timeout(SECOND,
> + (ahci_ioread(ahci, HOST_CTL) & HOST_RESET) == 0);
> + if (ret) {
> + ahci_debug(ahci, "controller reset failed (HOST_CTL=0x%x)\n",
> + ahci_ioread(ahci, HOST_CTL));
> + return -ENODEV;
> + }
> +
> +
> + tmp = ahci_ioread(ahci, HOST_CTL);
> + tmp |= HOST_AHCI_EN;
> + ahci_iowrite_f(ahci, HOST_CTL, tmp);
> +
> + ahci->cap = ahci_ioread(ahci, HOST_CAP);
> + ahci->port_map = ahci_ioread(ahci, HOST_PORTS_IMPL);
> + ahci->n_ports = (ahci->cap & HOST_CAP_NP) + 1;
> +
> + ahci_debug(ahci, "cap 0x%x port_map 0x%x n_ports %d\n",
> + ahci->cap, ahci->port_map, ahci->n_ports);
> +
> + n_ports = max_t(int, ahci->n_ports, fls(ahci->port_map));
> +
> + for (i = 0; i < n_ports; i++) {
> + struct ahci_port *ahci_port = &ahci->ports[i];
> +
> + if (!(ahci->port_map & (1 << i)))
> + continue;
> +
> + ahci_port->num = i;
> + ahci_port->ahci = ahci;
> + ahci_port->ata.dev = ahci->dev;
> + ahci_port->port_mmio = ahci_port_base(ahci->mmio_base, i);
> + ahci_port->ata.ops = &ahci_ops;
> + ahci_port->ata.ahci = true;
> + ata_port_register(&ahci_port->ata);
> + }
> +
> + /* enable HBA level interrupts */
> + tmp = ahci_ioread(ahci, HOST_CTL);
> + ahci_iowrite(ahci, HOST_CTL, tmp | HOST_IRQ_EN);
> +
> + ahci->dev->detect = ahci_detect;
> + list_add(&ahci->list, &ahci_devices);
> +
> + return 0;
> }
>
> static int ahci_probe(struct device *dev)
> @@ -665,6 +630,124 @@ static int ahci_probe(struct device *dev)
> return ret;
> }
>
> +/* -------------------------------------------------------------------------- */
> +/* AHCI shutdown helpers (kernel-like) */
> +/* -------------------------------------------------------------------------- */
> +
> +/* Stop DMA engine (clear START, wait LIST_ON=0) */
> +static int ahci_stop_engine(struct ahci_port *port)
> +{
> + u32 cmd;
> +
> + cmd = ahci_port_read(port, PORT_CMD);
> +
> + /* Already stopped? */
> + if (!(cmd & (PORT_CMD_START | PORT_CMD_LIST_ON)))
> + return 0;
> +
> + /* Clear START */
> + cmd &= ~PORT_CMD_START;
> + ahci_port_write_f(port, PORT_CMD, cmd);
> +
> + /* Wait for LIST_ON to clear */
> + return wait_on_timeout(500 * MSECOND,
> + !(ahci_port_read(port, PORT_CMD) & PORT_CMD_LIST_ON));
> +}
> +
> +/* Stop FIS receive engine (clear FIS_RX, wait FIS_ON=0) */
> +static int ahci_stop_fis_rx(struct ahci_port *port)
> +{
> + u32 cmd;
> +
> + cmd = ahci_port_read(port, PORT_CMD);
> + cmd &= ~PORT_CMD_FIS_RX;
> + ahci_port_write_f(port, PORT_CMD, cmd);
> +
> + /* Wait for FIS_ON to clear */
> + return wait_on_timeout(1000 * MSECOND,
> + !(ahci_port_read(port, PORT_CMD) & PORT_CMD_FIS_ON));
> +}
> +
> +/* Stop all ports (libata_pci_shutdown_one equivalent) */
> +static void __maybe_unused ahci_shutdown_host(struct ahci_device *ahci)
> +{
> + int i, n_ports;
> +
> + n_ports = max_t(int, ahci->n_ports, fls(ahci->port_map));
> +
> + for (i = 0; i < n_ports; i++) {
> + struct ahci_port *port = &ahci->ports[i];
> +
> + if (!(ahci->port_map & (1 << i)))
> + continue;
> +
> + /* Stop DMA engine */
> + ahci_stop_engine(port);
> +
> + /* Stop FIS receive engine */
> + ahci_stop_fis_rx(port);
> + }
> +}
> +
> +/* Issue FLUSH EXT + STANDBY IMMEDIATE */
> +static void ahci_port_shutdown(struct ahci_port *port)
> +{
> + if (!port->cmd_tbl || !port->cmd_slot)
> + return;
> +
> + if (!ahci_link_ok(port, 0))
> + return;
> +
> + if (ahci_ata_nodata(port, ATA_CMD_FLUSH_EXT, 0))
> + ahci_port_info(port, "FLUSH EXT failed\n");
> +
> + if (ahci_ata_nodata(port, ATA_CMD_STANDBYNOW1, 0))
> + ahci_port_info(port, "STANDBY IMMEDIATE failed\n");
> +}
> +
> +/* Full poweroff sequence */
> +static void ahci_poweroff(struct poweroff_handler *handler, unsigned long flags)
> +{
> + struct ahci_device *ahci;
> + int i, n_ports;
> +
> + list_for_each_entry(ahci, &ahci_devices, list) {
> +
> + if (!ahci->mmio_base)
> + continue;
> +
> + /* 1. FLUSH + STANDBY su tutte le porte attive */
> + n_ports = max_t(int, ahci->n_ports, fls(ahci->port_map));
> +
> + for (i = 0; i < n_ports; i++) {
> + struct ahci_port *port = &ahci->ports[i];
> +
> + if (!(ahci->port_map & (1 << i)))
> + continue;
> +
> + ahci_port_shutdown(port);
> + }
> +
> + /* 2. (opzionale) spegnere il controller dopo i comandi
> + * Se vuoi tenerlo, fallo SOLO dopo i comandi:
> + *
> + * ahci_shutdown_host(ahci);
> + */
> + }
> +}
> +
> +static struct poweroff_handler ahci_po_handler = {
> + .poweroff = ahci_poweroff,
> + .priority = 200, /* più alto di gpio-poweroff */
> +};
> +
> +static int ahci_register_poweroff(void)
> +{
> + poweroff_handler_register(&ahci_po_handler);
> + return 0;
> +}
> +postcore_initcall(ahci_register_poweroff);
> +
> static __maybe_unused struct of_device_id ahci_dt_ids[] = {
> {
> .compatible = "calxeda,hb-ahci",
> @@ -674,6 +757,13 @@ static __maybe_unused struct of_device_id ahci_dt_ids[] = {
> };
> MODULE_DEVICE_TABLE(of, ahci_dt_ids);
>
> +static struct pci_driver ahci_pci_driver = {
> + .name = "ahci-pci",
> + .id_table = ahci_pci_tbl,
> + .probe = ahci_pci_probe,
> +};
> +device_pci_driver(ahci_pci_driver);
> +
> static struct driver ahci_driver = {
> .name = "ahci",
> .probe = ahci_probe,
> diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h
> index 196bde73c2..d2a19f4648 100644
> --- a/drivers/ata/ahci.h
> +++ b/drivers/ata/ahci.h
> @@ -184,6 +184,7 @@ struct ahci_port {
> };
>
> struct ahci_device {
> + struct list_head list;
> struct device *dev;
> struct ahci_port ports[AHCI_MAX_PORTS];
> u32 n_ports;
>
> --
> 2.47.3
>
>
>
--
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] 30+ messages in thread
* [PATCH 19/19] drivers: integrate USB/AHCI changes
2026-07-23 13:57 [PATCH 00/19] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
` (17 preceding siblings ...)
2026-07-23 13:57 ` [PATCH 18/19] ata: ahci: fixes and updates for Marvell SATA controller Luca Lauro via B4 Relay
@ 2026-07-23 13:57 ` Luca Lauro via B4 Relay
2026-07-27 14:16 ` Sascha Hauer
18 siblings, 1 reply; 30+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-07-23 13:57 UTC (permalink / raw)
To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro
From: Luca Lauro <famlauro93l@gmail.com>
---
drivers/Kconfig | 1 +
drivers/Makefile | 1 +
2 files changed, 2 insertions(+)
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 32910addcd..b1ba73887a 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -50,5 +50,6 @@ source "drivers/power/Kconfig"
source "drivers/virtio/Kconfig"
source "drivers/mailbox/Kconfig"
source "drivers/tee/Kconfig"
+source "drivers/fan/Kconfig"
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index db491dad83..4d6071fdc7 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -51,3 +51,4 @@ obj-$(CONFIG_SOUND) += sound/
obj-y += virtio/
obj-y += mailbox/
obj-y += tee/
+obj-$(CONFIG_FAN) += fan/
--
2.47.3
^ permalink raw reply [flat|nested] 30+ messages in thread* Re: [PATCH 19/19] drivers: integrate USB/AHCI changes
2026-07-23 13:57 ` [PATCH 19/19] drivers: integrate USB/AHCI changes Luca Lauro via B4 Relay
@ 2026-07-27 14:16 ` Sascha Hauer
0 siblings, 0 replies; 30+ messages in thread
From: Sascha Hauer @ 2026-07-27 14:16 UTC (permalink / raw)
To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro
On 2026-07-23 15:57, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
>
> ---
> drivers/Kconfig | 1 +
> drivers/Makefile | 1 +
> 2 files changed, 2 insertions(+)
>
> diff --git a/drivers/Kconfig b/drivers/Kconfig
> index 32910addcd..b1ba73887a 100644
> --- a/drivers/Kconfig
> +++ b/drivers/Kconfig
> @@ -50,5 +50,6 @@ source "drivers/power/Kconfig"
> source "drivers/virtio/Kconfig"
> source "drivers/mailbox/Kconfig"
> source "drivers/tee/Kconfig"
> +source "drivers/fan/Kconfig"
>
> endmenu
> diff --git a/drivers/Makefile b/drivers/Makefile
> index db491dad83..4d6071fdc7 100644
> --- a/drivers/Makefile
> +++ b/drivers/Makefile
> @@ -51,3 +51,4 @@ obj-$(CONFIG_SOUND) += sound/
> obj-y += virtio/
> obj-y += mailbox/
> obj-y += tee/
> +obj-$(CONFIG_FAN) += fan/
Naa, this patch has nothing USB or AHCI specific in it, but should be
squashed into the fan framework patches.
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] 30+ messages in thread