mail archive of the barebox mailing list
 help / color / mirror / Atom feed
* [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers
@ 2026-08-02 13:16 Luca Lauro via B4 Relay
  2026-08-02 13:16 ` [PATCH v3 01/15] ARM: mvebu: add Netgear RN102 support Luca Lauro via B4 Relay
                   ` (14 more replies)
  0 siblings, 15 replies; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

This series adds full barebox support for the Netgear RN102 and RN104
NAS platforms based on the Marvell Armada 370 SoC. Both devices share
the same hardware architecture, differing mainly in the number of drive
bays and board‑level integration.

The series introduces:
 - lowlevel initialization code for RN102 and RN104
 - board support files and environment integration
 - barebox‑specific device tree overlays for both platforms
 - image generation support and defconfig updates
 - fan controller subsystem and G76x driver
 - fan control command and Kconfig/Makefile integration
 - Marvell‑specific EHCI host controller driver
 - USB/EHCI fixes
 - ATA/AHCI improvements

The goal is to provide complete bootloader support for these NAS
devices, enabling reliable initialization of storage, USB, fan control,
and board‑specific peripherals. The patches have been tested on RN102.

Changes in v3:
 - cleaned up whitespace and indentation issues
 - squashed fan command integration into a single commit
 - removed noisy or unrelated changes
 - improved commit messages and structure

Link to v2:
https://patch.msgid.link/20260728-rn102-rn104-series-v2-0-ae31f55b7bc8@gmail.com

Signed-off-by: Luca Lauro <famlauro93l@gmail.com>

---
Luca Lauro (15):
      ARM: mvebu: add Netgear RN102 support
      ARM: mvebu: enable RN102 in mvebu_defconfig
      ARM: mvebu: improve Netgear RN104 support
      ARM: mvebu: rename PUTC_LL to MVEBU_PUTC_LL
      drivers: fan: add fan subsystem, core API and G76x fan controller driver
      commands: add fan control command
      usb: ehci: add Marvell EHCI host controller driver
      usb: ehci: initialize periodic_queue_dma
      ata: ahci: add PCI AHCI and Marvell 9170 controller support
      ata: ahci: fix zero-length DMA handling
      ata: ahci: add helper for ATA commands without data
      ata: ahci: improve AHCI port bring-up sequence
      ata: ahci: add FLUSH EXT and STANDBY IMMEDIATE support during shutdown
      ata: ahci: add shutdown helpers for AHCI controllers
      ata: ahci: cleanup legacy code and remove unused paths

 arch/arm/boards/Makefile                   |    1 +
 arch/arm/boards/netgear-rn102/Makefile     |    4 +
 arch/arm/boards/netgear-rn102/board.c      |  243 ++++++
 arch/arm/boards/netgear-rn102/lowlevel.c   |   58 ++
 arch/arm/boards/netgear-rn104/Makefile     |    1 +
 arch/arm/boards/netgear-rn104/board.c      |  182 +++++
 arch/arm/boards/netgear-rn104/lowlevel.c   |   37 +-
 arch/arm/configs/mvebu_defconfig           |    1 +
 arch/arm/dts/Makefile                      |    1 +
 arch/arm/dts/armada-370-rn102-bb.dts       |   82 ++
 arch/arm/dts/armada-370-rn104-bb.dts       |   73 +-
 arch/arm/mach-mvebu/Kconfig                |    4 +
 commands/Kconfig                           |    7 +
 commands/Makefile                          |    2 +
 commands/fan.c                             |  286 +++++++
 drivers/Kconfig                            |    1 +
 drivers/Makefile                           |    1 +
 drivers/ata/ahci.c                         |  429 ++++++----
 drivers/ata/ahci.h                         |    1 +
 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     | 1185 ++++++++++++++++++++++++++++
 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 +
 drivers/usb/host/Kconfig                   |    7 +
 drivers/usb/host/Makefile                  |   13 +-
 drivers/usb/host/ehci-hcd.c                |    7 +-
 drivers/usb/host/ehci-marvell.c            |  228 ++++++
 images/Makefile.mvebu                      |   13 +-
 include/fan/fan.h                          |   94 +++
 include/fan/i2c_fan_controllers/g76x.h     |   37 +
 include/mach/mvebu/debug_ll.h              |    2 +-
 37 files changed, 3064 insertions(+), 184 deletions(-)
---
base-commit: f9cc366e5b67680473e4ab7837eeb27bf1b5a567
change-id: 20260723-rn102-rn104-series-d668eb09d0ff

Best regards,
--  
Luca Lauro <famlauro93l@gmail.com>





^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v3 01/15] ARM: mvebu: add Netgear RN102 support
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  2026-08-03 12:53   ` Sascha Hauer
  2026-08-02 13:16 ` [PATCH v3 02/15] ARM: mvebu: enable RN102 in mvebu_defconfig Luca Lauro via B4 Relay
                   ` (13 subsequent siblings)
  14 siblings, 1 reply; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

This adds full support for the Netgear ReadyNAS 102, including:

 - board driver matching on "netgear,rn102"
 - lowlevel initialization
 - barebox overlay with environment, state backend and
	NAND partition layout
 - Kconfig entry
 - image support

Signed-off-by: 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 +++++++++++++++++++++++++++++++
 arch/arm/boards/netgear-rn102/lowlevel.c |  58 ++++++++
 arch/arm/dts/Makefile                    |   1 +
 arch/arm/dts/armada-370-rn102-bb.dts     |  82 +++++++++++
 arch/arm/mach-mvebu/Kconfig              |   4 +
 images/Makefile.mvebu                    |  10 ++
 8 files changed, 403 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..dba3d11518
--- /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 <of.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().
+ * 
+ * NOTE: Armada 370-XP EHCI controller shows instability when the periodic
+ * schedule is enabled.
+ */
+static void setup_usb0(void)
+{
+	writel(0x2, 0xf1051404);	/* enable force suspend */
+	u32 pwr = readl(0xf1051400);
+	pwr &= ~BIT(2);
+	writel(pwr, 0xf1051400);	/* force SUSPENDM=0 */
+}
+
+/*
+ * RN102 board driver
+ *
+ * Preferred pattern: board-specific code as a platform driver
+ * matching on the root DT node ("netgear,rn102").
+ */
+static int rn102_probe(struct device *dev)
+{
+	/* CFU configuration */
+	writel(0xC6, 0xf1020228);
+
+	setup_usb0();
+	setup_bays();
+	init_disks();
+
+	/* Barebox Update handlers */
+	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;
+}
+
+static const struct of_device_id rn102_of_match[] = {
+	{ .compatible = "netgear,rn102" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, rn102_of_match);
+
+static struct driver rn102_driver = {
+	.name = "rn102",
+	.probe = rn102_probe,
+	.of_match_table = rn102_of_match,
+};
+
+device_platform_driver(rn102_driver);
diff --git a/arch/arm/boards/netgear-rn102/lowlevel.c b/arch/arm/boards/netgear-rn102/lowlevel.c
new file mode 100644
index 0000000000..c1680cee82
--- /dev/null
+++ b/arch/arm/boards/netgear-rn102/lowlevel.c
@@ -0,0 +1,58 @@
+// 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);
+}
+
+/*
+ * NOTE:
+ * armada_370_xp_barebox_entry() cannot be used here because the
+ * upstream SDRAM size detection for Armada 370-XP misinterprets
+ * the DDR_SIZE_CSn registers on this board and reports an incorrect
+ * memory size (256MB instead of 512MB on RN102).
+ *
+ * Until the generic detection code is fixed, we compute the SDRAM
+ * size manually using the DDR_SIZE_CSn values.
+ */
+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);
+}
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..47064cb11a
--- /dev/null
+++ b/arch/arm/dts/armada-370-rn102-bb.dts
@@ -0,0 +1,82 @@
+/*
+ * Barebox specific DT overlay for Netgear ReadyNAS 102
+ */
+
+#include "arm/marvell/armada-370-netgear-rn102.dts"
+
+/ {
+	chosen {
+		stdout-path = &uart0;
+	};
+
+	aliases {
+		state = &state_nand;
+	};
+
+	/* 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>;
+		};
+	};
+};
+
+&nand_controller {
+	compatible = "marvell,armada370-nand", "marvell,pxa3xx-nand";
+	status = "okay";
+
+	nand-rb = <0>;
+	marvell,nand-keep-config;
+	nand-on-flash-bbt;
+
+	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>;
+		};
+
+		partition@400000 {
+			label = "environment";
+			reg = <0x400000 0x80000>;
+		};
+
+		backend_state_nand: partition@480000 {
+			label = "state";
+			reg = <0x480000 0x80000>;
+		};
+
+		partition@500000 {
+			label = "kernel";
+			reg = <0x500000 0x1400000>;
+		};
+
+		partition@1900000 {
+			label = "minirootfs";
+			reg = <0x1900000 0x6000000>;
+		};
+	};
+};
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
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] 22+ messages in thread

* [PATCH v3 02/15] ARM: mvebu: enable RN102 in mvebu_defconfig
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
  2026-08-02 13:16 ` [PATCH v3 01/15] ARM: mvebu: add Netgear RN102 support Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  2026-08-02 13:16 ` [PATCH v3 03/15] ARM: mvebu: improve Netgear RN104 support Luca Lauro via B4 Relay
                   ` (12 subsequent siblings)
  14 siblings, 0 replies; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

Signed-off-by: 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] 22+ messages in thread

* [PATCH v3 03/15] ARM: mvebu: improve Netgear RN104 support
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
  2026-08-02 13:16 ` [PATCH v3 01/15] ARM: mvebu: add Netgear RN102 support Luca Lauro via B4 Relay
  2026-08-02 13:16 ` [PATCH v3 02/15] ARM: mvebu: enable RN102 in mvebu_defconfig Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  2026-08-02 13:16 ` [PATCH v3 04/15] ARM: mvebu: rename PUTC_LL to MVEBU_PUTC_LL Luca Lauro via B4 Relay
                   ` (11 subsequent siblings)
  14 siblings, 0 replies; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

This updates full support for the Netgear ReadyNAS 104, including:

 - board driver matching on "netgear,rn104"
 - bay management placeholder
 - lowlevel initialization
 - barebox overlay with environment, state backend and
 	NAND partition layout
 - image support

Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
---
 arch/arm/boards/netgear-rn104/Makefile   |   1 +
 arch/arm/boards/netgear-rn104/board.c    | 182 +++++++++++++++++++++++++++++++
 arch/arm/boards/netgear-rn104/lowlevel.c |  37 ++++++-
 arch/arm/dts/armada-370-rn104-bb.dts     |  73 ++++++++++++-
 images/Makefile.mvebu                    |   3 +-
 5 files changed, 286 insertions(+), 10 deletions(-)

diff --git a/arch/arm/boards/netgear-rn104/Makefile b/arch/arm/boards/netgear-rn104/Makefile
index 458f520900..da63d2625f 100644
--- a/arch/arm/boards/netgear-rn104/Makefile
+++ b/arch/arm/boards/netgear-rn104/Makefile
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
+obj-y += board.o
 lwl-y += lowlevel.o
diff --git a/arch/arm/boards/netgear-rn104/board.c b/arch/arm/boards/netgear-rn104/board.c
new file mode 100644
index 0000000000..7a3ed1f16b
--- /dev/null
+++ b/arch/arm/boards/netgear-rn104/board.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <common.h>
+#include <init.h>
+#include <gpio.h>
+#include <driver.h>
+#include <of.h>
+#include <linux/device.h>
+#include <linux/mbus.h>
+#include <mach/mvebu/armada-370-xp-regs.h>
+#include <bbu.h>
+
+/*
+ * RN104 bay management is PCA9554-based and currently unimplemented.
+ * Placeholder logic is kept for future extension.
+ */
+
+#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 2
+
+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");
+}
+
+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().
+ *
+ * NOTE: Armada 370-XP EHCI controller shows instability when the periodic
+ * schedule is enabled.
+ */
+static void setup_usb0(void)
+{
+	writel(0x2, 0xf1051404);	/* enable force suspend */
+	u32 pwr = readl(0xf1051400);
+	pwr &= ~BIT(2);
+	writel(pwr, 0xf1051400);	/* force SUSPENDM=0 */
+}
+
+/*
+ * RN104 board driver
+ *
+ * Preferred pattern: board-specific code as a platform driver
+ * matching on the root DT node ("netgear,rn104").
+ */
+static int rn104_probe(struct device *dev)
+{
+	/* CFU configuration */
+	writel(0xC6, 0xf1020228);
+
+	setup_usb0();
+	setup_bays();
+	init_disks();
+
+	/* Barebox Update handlers */
+	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;
+}
+
+static const struct of_device_id rn104_of_match[] = {
+	{ .compatible = "netgear,rn104" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, rn104_of_match);
+
+static struct driver rn104_driver = {
+	.name = "rn104",
+	.probe = rn104_probe,
+	.of_match_table = rn104_of_match,
+};
+
+coredevice_platform_driver(rn104_board_driver);
diff --git a/arch/arm/boards/netgear-rn104/lowlevel.c b/arch/arm/boards/netgear-rn104/lowlevel.c
index e693d13993..2d2c097bfc 100644
--- a/arch/arm/boards/netgear-rn104/lowlevel.c
+++ b/arch/arm/boards/netgear-rn104/lowlevel.c
@@ -1,16 +1,39 @@
 // 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[];
 
+#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_rn104, r0, r1, r2)
 {
 	void *fdt;
@@ -20,5 +43,7 @@ ENTRY_FUNCTION_MVEBU(start_netgear_rn104, r0, r1, r2)
 	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);
 }
diff --git a/arch/arm/dts/armada-370-rn104-bb.dts b/arch/arm/dts/armada-370-rn104-bb.dts
index b786ef350a..a2bb47ba8c 100644
--- a/arch/arm/dts/armada-370-rn104-bb.dts
+++ b/arch/arm/dts/armada-370-rn104-bb.dts
@@ -5,9 +5,78 @@
 #include "arm/marvell/armada-370-netgear-rn104.dts"
 
 / {
-	barebox,disable-deep-probe;
-
 	chosen {
 		stdout-path = &uart0;
 	};
+
+	aliases {
+		state = &state_nand;
+	};
+
+	/* 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>;
+		};
+	};
+};
+
+&nand_controller {
+	compatible = "marvell,armada370-nand", "marvell,pxa3xx-nand";
+	status = "okay";
+
+	nand-rb = <0>;
+	marvell,nand-keep-config;
+	nand-on-flash-bbt;
+
+	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>;
+		};
+
+		partition@400000 {
+			label = "environment";
+			reg = <0x400000 0x80000>;
+		};
+
+		backend_state_nand: partition@480000 {
+			label = "state";
+			reg = <0x480000 0x80000>;
+		};
+
+		partition@500000 {
+			label = "kernel";
+			reg = <0x500000 0x1400000>;
+		};
+
+		partition@1900000 {
+			label = "minirootfs";
+			reg = <0x1900000 0x6000000>;
+		};
+	};
 };
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] 22+ messages in thread

* [PATCH v3 04/15] ARM: mvebu: rename PUTC_LL to MVEBU_PUTC_LL
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
                   ` (2 preceding siblings ...)
  2026-08-02 13:16 ` [PATCH v3 03/15] ARM: mvebu: improve Netgear RN104 support Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  2026-08-02 13:16 ` [PATCH v3 05/15] drivers: fan: add fan subsystem, core API and G76x fan controller driver Luca Lauro via B4 Relay
                   ` (10 subsequent siblings)
  14 siblings, 0 replies; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

The name PUTC_LL is used by multiple architectures in barebox for their
early low-level UART output helpers. Since these functions differ between
SoCs and are defined in architecture-specific debug_ll headers, using the
same global name can lead to collisions or accidental overrides when
including multiple debug_ll headers.

To avoid such namespace conflicts and to make the origin of the function
explicit, rename the mvebu-specific PUTC_LL to MVEBU_PUTC_LL.

Signed-off-by: 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] 22+ messages in thread

* [PATCH v3 05/15] drivers: fan: add fan subsystem, core API and G76x fan controller driver
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
                   ` (3 preceding siblings ...)
  2026-08-02 13:16 ` [PATCH v3 04/15] ARM: mvebu: rename PUTC_LL to MVEBU_PUTC_LL Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  2026-08-02 13:16 ` [PATCH v3 06/15] commands: add fan control command Luca Lauro via B4 Relay
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

Introduce a new fan subsystem with a shared API for multiple fan controller backends.
The subsystem provides a common interface for configuring and reading common fan
parameters like speed, level percentage, tachometer values,
and exposing fan devices to upper layers.

Add the initial controller implementation for the G76x family,
including Kconfig/Makefile entries, register layout, and support for
basic PWM and tachometer functionality.
---
 drivers/Kconfig                            |    1 +
 drivers/Makefile                           |    1 +
 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     | 1185 ++++++++++++++++++++++++++++
 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 +
 14 files changed, 1566 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/
diff --git a/drivers/fan/Kconfig b/drivers/fan/Kconfig
new file mode 100644
index 0000000000..0ff71d7e7d
--- /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..4ec675d01a
--- /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..da34dcb9da
--- /dev/null
+++ b/drivers/fan/i2c_fan_controllers/g76x.c
@@ -0,0 +1,1185 @@
+/*
+ * 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..44836c38ac
--- /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
+	(placeholder, not impemented) 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..ae5c23b71d
--- /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] 22+ messages in thread

* [PATCH v3 06/15] commands: add fan control command
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
                   ` (4 preceding siblings ...)
  2026-08-02 13:16 ` [PATCH v3 05/15] drivers: fan: add fan subsystem, core API and G76x fan controller driver Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  2026-08-02 13:16 ` [PATCH v3 07/15] usb: ehci: add Marvell EHCI host controller driver Luca Lauro via B4 Relay
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
---
 commands/Kconfig  |   7 ++
 commands/Makefile |   2 +
 commands/fan.c    | 286 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 295 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
diff --git a/commands/fan.c b/commands/fan.c
new file mode 100644
index 0000000000..613b4ac101
--- /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] 22+ messages in thread

* [PATCH v3 07/15] usb: ehci: add Marvell EHCI host controller driver
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
                   ` (5 preceding siblings ...)
  2026-08-02 13:16 ` [PATCH v3 06/15] commands: add fan control command Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  2026-08-02 13:16 ` [PATCH v3 08/15] usb: ehci: initialize periodic_queue_dma Luca Lauro via B4 Relay
                   ` (7 subsequent siblings)
  14 siblings, 0 replies; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
---
 drivers/usb/host/Kconfig        |   7 ++
 drivers/usb/host/Makefile       |  13 +--
 drivers/usb/host/ehci-marvell.c | 228 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 242 insertions(+), 6 deletions(-)

diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 58f276cdb4..8e46eca8a3 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..5442d44e60 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..97123558d1
--- /dev/null
+++ b/drivers/usb/host/ehci-marvell.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Marvell Orion / Kirkwood / Armada EHCI host controller for barebox
+ *
+ * Ported from the modern U-Boot ehci-marvell.c implementation and
+ * adapted to the barebox ehci_register() model.
+ */
+
+/*#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 (taken from 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	/* do not exceed the source address space */
+
+/*
+ * Private state for this Marvell EHCI instance.
+ * The driver only needs the pointer to struct ehci_host.
+ */
+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);
+}
+
+/*
+ * Program the USB → DRAM decoding windows using MBUS information.
+ * For AC5, replicate the special 0x0..USB_DRAM_SIZE mapping.
+ */
+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")) {
+		/* map DRAM as seen by USB at address 0x0 (as done 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 {
+		/* generic mvebu case: one window per each DRAM chip-select */
+
+		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);
+	}
+}
+
+/*
+ * Initialization hook called by ehci-hcd.c before starting the host.
+ * Only the bridge address decoding is performed here.
+ */
+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);
+
+	/*
+	* The SBUSCFG programming (BAWR/BARD/AHBBRST) in U-Boot is only
+	* required for SoCs without hlock (e.g. Armada3700).
+	* It is currently omitted; if needed in the future, a dedicated
+	* post_init or EHCI core hook can be added.
+	*/
+
+	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;
+
+	/*
+	* On Marvell controllers the EHCI block starts at offset 0x100
+	* from the USB base address.
+	*/
+	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] 22+ messages in thread

* [PATCH v3 08/15] usb: ehci: initialize periodic_queue_dma
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
                   ` (6 preceding siblings ...)
  2026-08-02 13:16 ` [PATCH v3 07/15] usb: ehci: add Marvell EHCI host controller driver Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  2026-08-03 21:27   ` Sascha Hauer
  2026-08-02 13:16 ` [PATCH v3 09/15] ata: ahci: add PCI AHCI and Marvell 9170 controller support Luca Lauro via B4 Relay
                   ` (6 subsequent siblings)
  14 siblings, 1 reply; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

Store the DMA address of the periodic queue head in
ehci->periodic_queue_dma. Without this initialization the periodic
schedule may reference an invalid address.

Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
---
 drivers/usb/host/ehci-hcd.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 51b9e52a4f..a4832bb3ff 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -897,6 +897,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
@@ -1442,11 +1444,14 @@ static int ehci_probe(struct device *dev)
 	if (ret)
 		return ret;
 
-	ret = clk_bulk_get_all_enabled(dev, &clks);
+	ret = clk_bulk_get_all(dev, &clks);
 	if (ret < 0)
 		return ret;
 
 	num_clocks = ret;
+	ret = clk_bulk_enable(num_clocks, clks);
+	if (ret)
+		return ret;
 
 	iores = dev_request_mem_resource(dev, 0);
 	if (IS_ERR(iores))

-- 
2.47.3





^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v3 09/15] ata: ahci: add PCI AHCI and Marvell 9170 controller support
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
                   ` (7 preceding siblings ...)
  2026-08-02 13:16 ` [PATCH v3 08/15] usb: ehci: initialize periodic_queue_dma Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  2026-08-03 21:33   ` Sascha Hauer
  2026-08-02 13:16 ` [PATCH v3 10/15] ata: ahci: fix zero-length DMA handling Luca Lauro via B4 Relay
                   ` (5 subsequent siblings)
  14 siblings, 1 reply; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

Barebox previously supported only memory-mapped AHCI controllers.
Some devices expose their AHCI controller through PCI BARs, such as the
Marvell 9170 controller found in Netgear RN102/RN104 NAS devices,
requiring a dedicated probing path and MMIO mapping.
This patch extends the barebox AHCI driver with full support for
PCI-based AHCI controllers, adding:

- A PCI probe path that enables AHCI initialization via
pci_enable_device(), pci_iomap() and ahci_add_host().

- A small device list to keep track of PCI AHCI instances.

Changes are based on the upstream Linux driver, with some adaptations.

Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
---
 drivers/ata/ahci.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/ata/ahci.h |  1 +
 2 files changed, 55 insertions(+)

diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
index 819dc37b3e..0ac1a9dead 100644
--- a/drivers/ata/ahci.c
+++ b/drivers/ata/ahci.c
@@ -19,6 +19,7 @@
 #include <disks.h>
 #include <ata_drive.h>
 #include <linux/sizes.h>
+#include <linux/pci.h>
 #include <clock.h>
 
 #include "ahci.h"
@@ -47,6 +48,51 @@
 #define ahci_debug(ahci, fmt, arg...) \
 	dev_dbg(ahci->dev, fmt, ##arg)
 
+#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;
@@ -635,6 +681,7 @@ int ahci_add_host(struct ahci_device *ahci)
 	tmp = ahci_ioread(ahci, HOST_CTL);
 
 	ahci->dev->detect = ahci_detect;
+	list_add(&ahci->list, &ahci_devices);
 
 	return 0;
 }
@@ -674,6 +721,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] 22+ messages in thread

* [PATCH v3 10/15] ata: ahci: fix zero-length DMA handling
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
                   ` (8 preceding siblings ...)
  2026-08-02 13:16 ` [PATCH v3 09/15] ata: ahci: add PCI AHCI and Marvell 9170 controller support Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  2026-08-02 13:16 ` [PATCH v3 11/15] ata: ahci: add helper for ATA commands without data Luca Lauro via B4 Relay
                   ` (4 subsequent siblings)
  14 siblings, 0 replies; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

ata: ahci: fix zero-length DMA handling

Commands without a data buffer must not trigger DMA setup. The previous
code unconditionally programmed PRDT entries and attempted DMA mapping
even when buf_len was zero, leading to invalid PRD tables and spurious
DMA operations.

This patch ensures that DMA mapping, PRDT setup and unmapping are only
performed when buf_len > 0.

Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
---
 drivers/ata/ahci.c | 33 ++++++++++++++++++---------------
 1 file changed, 18 insertions(+), 15 deletions(-)

diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
index 0ac1a9dead..a080de236e 100644
--- a/drivers/ata/ahci.c
+++ b/drivers/ata/ahci.c
@@ -198,30 +198,32 @@ 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 sg_count = 0;
 	int ret;
-	void *buf;
-	dma_addr_t buf_dma;
-	enum dma_data_direction dma_dir;
+	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 (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);
 
-	sg_count = ahci_fill_sg(ahci_port, buf_dma, buf_len);
 	opts = (fis_len >> 2) | (sg_count << 16);
-	if (wbuf)
+	if (wbuf && buf_len > 0)
 		opts |= CMD_LIST_OPTS_WRITE;
 	ahci_fill_cmd_slot(ahci_port, opts);
 
@@ -230,7 +232,8 @@ static int ahci_io(struct ahci_port *ahci_port, u8 *fis, int fis_len, void *rbuf
 	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;
 }

-- 
2.47.3





^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v3 11/15] ata: ahci: add helper for ATA commands without data
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
                   ` (9 preceding siblings ...)
  2026-08-02 13:16 ` [PATCH v3 10/15] ata: ahci: fix zero-length DMA handling Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  2026-08-02 13:16 ` [PATCH v3 12/15] ata: ahci: improve AHCI port bring-up sequence Luca Lauro via B4 Relay
                   ` (3 subsequent siblings)
  14 siblings, 0 replies; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

Introduce ahci_ata_nodata(), a small wrapper that prepares a FIS and
submits an AHCI command without PRDT entries or DMA setup. This avoids
duplicating zero-length handling logic across callers and simplifies
ATA command submission paths.

Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
---
 drivers/ata/ahci.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
index a080de236e..09d37720c2 100644
--- a/drivers/ata/ahci.c
+++ b/drivers/ata/ahci.c
@@ -238,6 +238,21 @@ static int ahci_io(struct ahci_port *ahci_port, u8 *fis, int fis_len, void *rbuf
 	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);
+}
+
 /*
  * SCSI INQUIRY command operation.
  */

-- 
2.47.3





^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v3 12/15] ata: ahci: improve AHCI port bring-up sequence
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
                   ` (10 preceding siblings ...)
  2026-08-02 13:16 ` [PATCH v3 11/15] ata: ahci: add helper for ATA commands without data Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  2026-08-02 13:16 ` [PATCH v3 13/15] ata: ahci: add FLUSH EXT and STANDBY IMMEDIATE support during shutdown Luca Lauro via B4 Relay
                   ` (2 subsequent siblings)
  14 siblings, 0 replies; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

The existing code only attempted a partial port disable and relied on
spin-up and link-up paths that are not reliable on some platforms like
Marvell 9170-based systems; which require a stricter and more complete
port initialization sequence than the generic AHCI specification.

This patch improves the port bring-up sequence by:
- fully disabling the port and waiting for FR/CR to clear
- clearing PORT_SCR_ERR before initialization
- issuing a COMRESET and waiting for PHY readiness
- programming both 32-bit and 64-bit base address registers
- enabling FIS receive and port start in the correct order
- waiting for device readiness via TFDATA
- removing obsolete spin-up, ICC and link-up logic

Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
---
 drivers/ata/ahci.c | 144 +++++++++++++++++------------------------------------
 1 file changed, 47 insertions(+), 97 deletions(-)

diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
index 09d37720c2..2504f4d19a 100644
--- a/drivers/ata/ahci.c
+++ b/drivers/ata/ahci.c
@@ -342,20 +342,37 @@ static int ahci_init_port(struct ahci_port *ahci_port)
 	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);
+	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");
+
+	/* Clear errors */
+	val = ahci_port_read(ahci_port, PORT_SCR_ERR);
+	if (val)
+		ahci_port_write(ahci_port, PORT_SCR_ERR, val);
+
+	/* 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);
+
+	/* 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;
 	}
 
+	/* Allocate DMA memory */
 	mem = dma_alloc_coherent(DMA_DEVICE_BROKEN,
 				 AHCI_PORT_PRIV_DMA_SZ, &mem_dma);
 	if (!mem) {
@@ -369,9 +386,6 @@ static int ahci_init_port(struct ahci_port *ahci_port)
 	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
 	 */
@@ -384,104 +398,40 @@ static int ahci_init_port(struct ahci_port *ahci_port)
 	 */
 	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));
+	/* 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));
+		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));
+		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.
-	 */
+	/* Enable FIS receive engine */
 	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);
+	/* Enable port start */
 	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");
-
+	/* 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)) == 0) ||
-			((ahci_port_read(ahci_port, PORT_SCR_STAT) &
-			 PORT_SCR_STAT_DET) == 1));
+			!(ahci_port_read(ahci_port, PORT_TFDATA) &
+			(ATA_STATUS_BUSY | ATA_STATUS_DRQ)));
 	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, "device not ready\n");
+		return -ENODEV;
 	}
 
-	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;
+	return 0;
 }
 
 static int ahci_port_start(struct ata_port *ata_port)

-- 
2.47.3





^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v3 13/15] ata: ahci: add FLUSH EXT and STANDBY IMMEDIATE support during shutdown
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
                   ` (11 preceding siblings ...)
  2026-08-02 13:16 ` [PATCH v3 12/15] ata: ahci: improve AHCI port bring-up sequence Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  2026-08-03 21:52   ` Sascha Hauer
  2026-08-02 13:16 ` [PATCH v3 14/15] ata: ahci: add shutdown helpers for AHCI controllers Luca Lauro via B4 Relay
  2026-08-02 13:16 ` [PATCH v3 15/15] ata: ahci: cleanup legacy code and remove unused paths Luca Lauro via B4 Relay
  14 siblings, 1 reply; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

Some AHCI controllers require ATA FLUSH EXT and STANDBY IMMEDIATE to be
issued before poweroff to ensure data integrity and proper device
shutdown. This patch introduces ahci_port_shutdown(), which sends these
commands using ahci_ata_nodata(), and integrates it into a new AHCI
poweroff handler.

Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
---
 drivers/ata/ahci.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)

diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
index 2504f4d19a..a9a3f43f21 100644
--- a/drivers/ata/ahci.c
+++ b/drivers/ata/ahci.c
@@ -48,6 +48,9 @@
 #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
@@ -680,6 +683,63 @@ static int ahci_probe(struct device *dev)
 	return ret;
 }
 
+/* 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;
+
+		/* FLUSH + STANDBY on all active 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;
+
+			ahci_port_shutdown(port);
+		}
+
+		/* (optional) shutsown controller after commands
+		* ahci_shutdown_host(ahci);
+		*/
+	}
+}
+
+static struct poweroff_handler ahci_po_handler = {
+	.poweroff = ahci_poweroff,
+	.priority = 200,   /* higher than 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",

-- 
2.47.3





^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v3 14/15] ata: ahci: add shutdown helpers for AHCI controllers
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
                   ` (12 preceding siblings ...)
  2026-08-02 13:16 ` [PATCH v3 13/15] ata: ahci: add FLUSH EXT and STANDBY IMMEDIATE support during shutdown Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  2026-08-03 21:55   ` Sascha Hauer
  2026-08-02 13:16 ` [PATCH v3 15/15] ata: ahci: cleanup legacy code and remove unused paths Luca Lauro via B4 Relay
  14 siblings, 1 reply; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

Introduce helper functions to stop the DMA engine and FIS receive engine
before shutdown. These helpers mirror the libata shutdown sequence and
ensure that no AHCI activity is in progress when the system powers off.

Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
---
 drivers/ata/ahci.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)

diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
index a9a3f43f21..e9e73069c1 100644
--- a/drivers/ata/ahci.c
+++ b/drivers/ata/ahci.c
@@ -21,6 +21,7 @@
 #include <linux/sizes.h>
 #include <linux/pci.h>
 #include <clock.h>
+#include <poweroff.h>
 
 #include "ahci.h"
 
@@ -683,6 +684,65 @@ static int ahci_probe(struct device *dev)
 	return ret;
 }
 
+/* -------------------------------- */
+/*		AHCI shutdown helpers		*/
+/* -------------------------------- */
+
+/* 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)
 {

-- 
2.47.3





^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v3 15/15] ata: ahci: cleanup legacy code and remove unused paths
  2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
                   ` (13 preceding siblings ...)
  2026-08-02 13:16 ` [PATCH v3 14/15] ata: ahci: add shutdown helpers for AHCI controllers Luca Lauro via B4 Relay
@ 2026-08-02 13:16 ` Luca Lauro via B4 Relay
  14 siblings, 0 replies; 22+ messages in thread
From: Luca Lauro via B4 Relay @ 2026-08-02 13:16 UTC (permalink / raw)
  To: Sascha Hauer, open list:BAREBOX; +Cc: Luca Lauro

From: Luca Lauro <famlauro93l@gmail.com>

Remove obsolete and unused AHCI initialization code inherited from older
drivers, including disabled flush helpers, legacy HOST_CAP/HOST_PORTS_IMPL
programming, outdated reset handling, and debug paths. These
changes do not alter functional behavior, as all required initialization
and shutdown logic is now implemented in dedicated commits.

Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
---
 drivers/ata/ahci.c | 63 ++++++++----------------------------------------------
 1 file changed, 9 insertions(+), 54 deletions(-)

diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
index e9e73069c1..f97e2573b7 100644
--- a/drivers/ata/ahci.c
+++ b/drivers/ata/ahci.c
@@ -465,43 +465,6 @@ static struct ata_port_operations ahci_ops = {
 	.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;
@@ -588,7 +551,7 @@ static int ahci_detect(struct device *dev)
 
 int ahci_add_host(struct ahci_device *ahci)
 {
-	u32 tmp, cap_save;
+	u32 tmp;
 	int n_ports, i, ret;
 
 	ahci->host_flags = ATA_FLAG_SATA
@@ -601,35 +564,27 @@ int ahci_add_host(struct ahci_device *ahci)
 
 	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);
+	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);
+		ahci_debug(ahci, "controller reset failed (HOST_CTL=0x%x)\n",
+				ahci_ioread(ahci, HOST_CTL));
 		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);
+	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);
+			ahci->cap, ahci->port_map, ahci->n_ports);
 
 	n_ports = max_t(int, ahci->n_ports, fls(ahci->port_map));
 
@@ -648,9 +603,9 @@ int ahci_add_host(struct ahci_device *ahci)
 		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);
-	tmp = ahci_ioread(ahci, HOST_CTL);
 
 	ahci->dev->detect = ahci_detect;
 	list_add(&ahci->list, &ahci_devices);

-- 
2.47.3





^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 01/15] ARM: mvebu: add Netgear RN102 support
  2026-08-02 13:16 ` [PATCH v3 01/15] ARM: mvebu: add Netgear RN102 support Luca Lauro via B4 Relay
@ 2026-08-03 12:53   ` Sascha Hauer
  2026-08-03 13:43     ` Marco Felsch
  0 siblings, 1 reply; 22+ messages in thread
From: Sascha Hauer @ 2026-08-03 12:53 UTC (permalink / raw)
  To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro

On 2026-08-02 15:16, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
> 
> This adds full support for the Netgear ReadyNAS 102, including:
> 
>  - board driver matching on "netgear,rn102"
>  - lowlevel initialization
>  - barebox overlay with environment, state backend and
> 	NAND partition layout
>  - Kconfig entry
>  - image support
> 
> Signed-off-by: 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 +++++++++++++++++++++++++++++++
>  arch/arm/boards/netgear-rn102/lowlevel.c |  58 ++++++++
>  arch/arm/dts/Makefile                    |   1 +
>  arch/arm/dts/armada-370-rn102-bb.dts     |  82 +++++++++++
>  arch/arm/mach-mvebu/Kconfig              |   4 +
>  images/Makefile.mvebu                    |  10 ++
>  8 files changed, 403 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..dba3d11518
> --- /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 <of.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.

barebox supports deep probe which means the devices are made available
once needed or requested. Provided your dts carries the
barebox,deep-probe property you can do this:

of_device_ensure_probed_by_alias("gpio0");
of_device_ensure_probed_by_alias("gpio1");
of_device_ensure_probed_by_alias("gpio2");

>From that on the GPIO driver should be probed and you can use it.

It shouldn't be necessary to duplicate the GPIO driver here.

> +/*
> + * RN102 board driver
> + *
> + * Preferred pattern: board-specific code as a platform driver
> + * matching on the root DT node ("netgear,rn102").
> + */
> +static int rn102_probe(struct device *dev)
> +{
> +	/* CFU configuration */
> +	writel(0xC6, 0xf1020228);
> +
> +	setup_usb0();
> +	setup_bays();
> +	init_disks();
> +
> +	/* Barebox Update handlers */
> +	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);

That is a creative use of barebox update handlers. They are designed for
updating barebox itself, not arbitrary other components.

I must think about this.

> +
> +	return 0;
> +}
> +
> +static const struct of_device_id rn102_of_match[] = {
> +	{ .compatible = "netgear,rn102" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, rn102_of_match);
> +
> +static struct driver rn102_driver = {
> +	.name = "rn102",
> +	.probe = rn102_probe,
> +	.of_match_table = rn102_of_match,
> +};
> +
> +device_platform_driver(rn102_driver);
> diff --git a/arch/arm/boards/netgear-rn102/lowlevel.c b/arch/arm/boards/netgear-rn102/lowlevel.c
> new file mode 100644
> index 0000000000..c1680cee82
> --- /dev/null
> +++ b/arch/arm/boards/netgear-rn102/lowlevel.c
> @@ -0,0 +1,58 @@
> +// 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);
> +}
> +
> +/*
> + * NOTE:
> + * armada_370_xp_barebox_entry() cannot be used here because the
> + * upstream SDRAM size detection for Armada 370-XP misinterprets
> + * the DDR_SIZE_CSn registers on this board and reports an incorrect
> + * memory size (256MB instead of 512MB on RN102).
> + *
> + * Until the generic detection code is fixed, we compute the SDRAM
> + * size manually using the DDR_SIZE_CSn values.
> + */
> +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);
> +}
> 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..47064cb11a
> --- /dev/null
> +++ b/arch/arm/dts/armada-370-rn102-bb.dts
> @@ -0,0 +1,82 @@
> +/*
> + * Barebox specific DT overlay for Netgear ReadyNAS 102
> + */
> +
> +#include "arm/marvell/armada-370-netgear-rn102.dts"
> +
> +/ {
> +	chosen {
> +		stdout-path = &uart0;
> +	};
> +
> +	aliases {
> +		state = &state_nand;
> +	};
> +
> +	/* 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>;
> +		};
> +	};
> +};
> +
> +&nand_controller {
> +	compatible = "marvell,armada370-nand", "marvell,pxa3xx-nand";
> +	status = "okay";
> +
> +	nand-rb = <0>;
> +	marvell,nand-keep-config;
> +	nand-on-flash-bbt;
> +
> +	nand-ecc-strength = <4>;
> +	nand-ecc-step-size = <512>;

Please drop all the properties that are already in the upstram dts.

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] 22+ messages in thread

* Re: [PATCH v3 01/15] ARM: mvebu: add Netgear RN102 support
  2026-08-03 12:53   ` Sascha Hauer
@ 2026-08-03 13:43     ` Marco Felsch
  0 siblings, 0 replies; 22+ messages in thread
From: Marco Felsch @ 2026-08-03 13:43 UTC (permalink / raw)
  To: Sascha Hauer; +Cc: Luca Lauro via B4 Relay, open list:BAREBOX, Luca Lauro

On 26-08-03, Sascha Hauer wrote:
> On 2026-08-02 15:16, Luca Lauro via B4 Relay wrote:
> > From: Luca Lauro <famlauro93l@gmail.com>
> > 
> > This adds full support for the Netgear ReadyNAS 102, including:
> > 
> >  - board driver matching on "netgear,rn102"
> >  - lowlevel initialization
> >  - barebox overlay with environment, state backend and
> > 	NAND partition layout
> >  - Kconfig entry
> >  - image support
> > 
> > Signed-off-by: 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 +++++++++++++++++++++++++++++++
> >  arch/arm/boards/netgear-rn102/lowlevel.c |  58 ++++++++
> >  arch/arm/dts/Makefile                    |   1 +
> >  arch/arm/dts/armada-370-rn102-bb.dts     |  82 +++++++++++
> >  arch/arm/mach-mvebu/Kconfig              |   4 +
> >  images/Makefile.mvebu                    |  10 ++
> >  8 files changed, 403 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..dba3d11518
> > --- /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 <of.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.
> 
> barebox supports deep probe which means the devices are made available
> once needed or requested. Provided your dts carries the
> barebox,deep-probe property you can do this:
> 
> of_device_ensure_probed_by_alias("gpio0");
> of_device_ensure_probed_by_alias("gpio1");
> of_device_ensure_probed_by_alias("gpio2");
> 
> From that on the GPIO driver should be probed and you can use it.
> 
> It shouldn't be necessary to duplicate the GPIO driver here.
> 
> > +/*
> > + * RN102 board driver
> > + *
> > + * Preferred pattern: board-specific code as a platform driver
> > + * matching on the root DT node ("netgear,rn102").
> > + */
> > +static int rn102_probe(struct device *dev)
> > +{
> > +	/* CFU configuration */
> > +	writel(0xC6, 0xf1020228);
> > +
> > +	setup_usb0();
> > +	setup_bays();
> > +	init_disks();
> > +
> > +	/* Barebox Update handlers */
> > +	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);
> 
> That is a creative use of barebox update handlers. They are designed for
> updating barebox itself, not arbitrary other components.
> 
> I must think about this.

Hint:
You can expose the kernel and minirootfs via fastboot partitions to get
a nice user interface. Barebox can talk the fastboot protocol over USB
or NET.

Regards,
  Marco



^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 08/15] usb: ehci: initialize periodic_queue_dma
  2026-08-02 13:16 ` [PATCH v3 08/15] usb: ehci: initialize periodic_queue_dma Luca Lauro via B4 Relay
@ 2026-08-03 21:27   ` Sascha Hauer
  0 siblings, 0 replies; 22+ messages in thread
From: Sascha Hauer @ 2026-08-03 21:27 UTC (permalink / raw)
  To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro

On 2026-08-02 15:16, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
> 
> Store the DMA address of the periodic queue head in
> ehci->periodic_queue_dma. Without this initialization the periodic
> schedule may reference an invalid address.
> 
> Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
> ---
>  drivers/usb/host/ehci-hcd.c | 7 ++++++-
>  1 file changed, 6 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
> index 51b9e52a4f..a4832bb3ff 100644
> --- a/drivers/usb/host/ehci-hcd.c
> +++ b/drivers/usb/host/ehci-hcd.c
> @@ -897,6 +897,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
> @@ -1442,11 +1444,14 @@ static int ehci_probe(struct device *dev)
>  	if (ret)
>  		return ret;
>  
> -	ret = clk_bulk_get_all_enabled(dev, &clks);
> +	ret = clk_bulk_get_all(dev, &clks);
>  	if (ret < 0)
>  		return ret;
>  
>  	num_clocks = ret;
> +	ret = clk_bulk_enable(num_clocks, clks);
> +	if (ret)
> +		return ret;

This looks unrelated to this patch. Does this change make any
difference?

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] 22+ messages in thread

* Re: [PATCH v3 09/15] ata: ahci: add PCI AHCI and Marvell 9170 controller support
  2026-08-02 13:16 ` [PATCH v3 09/15] ata: ahci: add PCI AHCI and Marvell 9170 controller support Luca Lauro via B4 Relay
@ 2026-08-03 21:33   ` Sascha Hauer
  0 siblings, 0 replies; 22+ messages in thread
From: Sascha Hauer @ 2026-08-03 21:33 UTC (permalink / raw)
  To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro

On 2026-08-02 15:16, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
> 
> Barebox previously supported only memory-mapped AHCI controllers.
> Some devices expose their AHCI controller through PCI BARs, such as the
> Marvell 9170 controller found in Netgear RN102/RN104 NAS devices,
> requiring a dedicated probing path and MMIO mapping.
> This patch extends the barebox AHCI driver with full support for
> PCI-based AHCI controllers, adding:
> 
> - A PCI probe path that enables AHCI initialization via
> pci_enable_device(), pci_iomap() and ahci_add_host().
> 
> - A small device list to keep track of PCI AHCI instances.
> 
> Changes are based on the upstream Linux driver, with some adaptations.
> 
> Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
> ---
>  drivers/ata/ahci.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  drivers/ata/ahci.h |  1 +
>  2 files changed, 55 insertions(+)
> 
> diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
> index 819dc37b3e..0ac1a9dead 100644
> --- a/drivers/ata/ahci.c
> +++ b/drivers/ata/ahci.c
> @@ -19,6 +19,7 @@
>  #include <disks.h>
>  #include <ata_drive.h>
>  #include <linux/sizes.h>
> +#include <linux/pci.h>
>  #include <clock.h>
>  
>  #include "ahci.h"
> @@ -47,6 +48,51 @@
>  #define ahci_debug(ahci, fmt, arg...) \
>  	dev_dbg(ahci->dev, fmt, ##arg)
>  
> +#ifndef PCI_VENDOR_ID_MARVELL_EXT
> +#define PCI_VENDOR_ID_MARVELL_EXT 0x1b4b
> +#endif

This is defined in include/linux/pci_ids.h already. Please use it.

> +
> +static LIST_HEAD(ahci_devices);

This list is only added to, but not used here. If it's used in a later
patch please move this there.

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] 22+ messages in thread

* Re: [PATCH v3 13/15] ata: ahci: add FLUSH EXT and STANDBY IMMEDIATE support during shutdown
  2026-08-02 13:16 ` [PATCH v3 13/15] ata: ahci: add FLUSH EXT and STANDBY IMMEDIATE support during shutdown Luca Lauro via B4 Relay
@ 2026-08-03 21:52   ` Sascha Hauer
  0 siblings, 0 replies; 22+ messages in thread
From: Sascha Hauer @ 2026-08-03 21:52 UTC (permalink / raw)
  To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro

On 2026-08-02 15:16, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
> 
> Some AHCI controllers require ATA FLUSH EXT and STANDBY IMMEDIATE to be
> issued before poweroff to ensure data integrity and proper device
> shutdown. This patch introduces ahci_port_shutdown(), which sends these
> commands using ahci_ata_nodata(), and integrates it into a new AHCI
> poweroff handler.
> 
> Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
> ---
>  drivers/ata/ahci.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 60 insertions(+)
> 
> diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
> index 2504f4d19a..a9a3f43f21 100644
> --- a/drivers/ata/ahci.c
> +++ b/drivers/ata/ahci.c
> @@ -48,6 +48,9 @@
>  #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
> @@ -680,6 +683,63 @@ static int ahci_probe(struct device *dev)
>  	return ret;
>  }
>  
> +/* 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;
> +
> +		/* FLUSH + STANDBY on all active 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;
> +
> +			ahci_port_shutdown(port);
> +		}
> +
> +		/* (optional) shutsown controller after commands
> +		* ahci_shutdown_host(ahci);
> +		*/
> +	}
> +}
> +
> +static struct poweroff_handler ahci_po_handler = {
> +	.poweroff = ahci_poweroff,
> +	.priority = 200,   /* higher than gpio-poweroff */
> +};
> +
> +static int ahci_register_poweroff(void)
> +{
> +	poweroff_handler_register(&ahci_po_handler);

A poweroff handler is the thing that actually powers off the system,
i.e. the gpio-poweroff handler toggles the GPIO that cuts the power to
the system.

You are looking for something to hook into poweroff_machine() and we
currently don't have anything for this.

We might want to call shutdown_barebox() before calling
poweroff_machine(). That way you could hook into the struct
driver->remove function. That would mean though that the ahci port
shutdown would also be called before starting Linux. If you do not want
that we could introduce some flag indicating the shutdown reason
(poweroff vs. start Linux)

Sascha

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 14/15] ata: ahci: add shutdown helpers for AHCI controllers
  2026-08-02 13:16 ` [PATCH v3 14/15] ata: ahci: add shutdown helpers for AHCI controllers Luca Lauro via B4 Relay
@ 2026-08-03 21:55   ` Sascha Hauer
  0 siblings, 0 replies; 22+ messages in thread
From: Sascha Hauer @ 2026-08-03 21:55 UTC (permalink / raw)
  To: Luca Lauro via B4 Relay; +Cc: open list:BAREBOX, Luca Lauro

On 2026-08-02 15:16, Luca Lauro via B4 Relay wrote:
> From: Luca Lauro <famlauro93l@gmail.com>
> 
> Introduce helper functions to stop the DMA engine and FIS receive engine
> before shutdown. These helpers mirror the libata shutdown sequence and
> ensure that no AHCI activity is in progress when the system powers off.
> 
> Signed-off-by: Luca Lauro <famlauro93l@gmail.com>
> ---
>  drivers/ata/ahci.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 60 insertions(+)
> 
> diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
> index a9a3f43f21..e9e73069c1 100644
> --- a/drivers/ata/ahci.c
> +++ b/drivers/ata/ahci.c
> @@ -21,6 +21,7 @@
>  #include <linux/sizes.h>
>  #include <linux/pci.h>
>  #include <clock.h>
> +#include <poweroff.h>
>  
>  #include "ahci.h"
>  
> @@ -683,6 +684,65 @@ static int ahci_probe(struct device *dev)
>  	return ret;
>  }
>  
> +/* -------------------------------- */
> +/*		AHCI shutdown helpers		*/
> +/* -------------------------------- */
> +
> +/* 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);
> +	}
> +}

This only adds unused code. If this is used in another patch, please
move it there.

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] 22+ messages in thread

end of thread, other threads:[~2026-08-03 21:57 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-02 13:16 [PATCH v3 00/15] ARM: mvebu: add Netgear RN102/RN104 support and related drivers Luca Lauro via B4 Relay
2026-08-02 13:16 ` [PATCH v3 01/15] ARM: mvebu: add Netgear RN102 support Luca Lauro via B4 Relay
2026-08-03 12:53   ` Sascha Hauer
2026-08-03 13:43     ` Marco Felsch
2026-08-02 13:16 ` [PATCH v3 02/15] ARM: mvebu: enable RN102 in mvebu_defconfig Luca Lauro via B4 Relay
2026-08-02 13:16 ` [PATCH v3 03/15] ARM: mvebu: improve Netgear RN104 support Luca Lauro via B4 Relay
2026-08-02 13:16 ` [PATCH v3 04/15] ARM: mvebu: rename PUTC_LL to MVEBU_PUTC_LL Luca Lauro via B4 Relay
2026-08-02 13:16 ` [PATCH v3 05/15] drivers: fan: add fan subsystem, core API and G76x fan controller driver Luca Lauro via B4 Relay
2026-08-02 13:16 ` [PATCH v3 06/15] commands: add fan control command Luca Lauro via B4 Relay
2026-08-02 13:16 ` [PATCH v3 07/15] usb: ehci: add Marvell EHCI host controller driver Luca Lauro via B4 Relay
2026-08-02 13:16 ` [PATCH v3 08/15] usb: ehci: initialize periodic_queue_dma Luca Lauro via B4 Relay
2026-08-03 21:27   ` Sascha Hauer
2026-08-02 13:16 ` [PATCH v3 09/15] ata: ahci: add PCI AHCI and Marvell 9170 controller support Luca Lauro via B4 Relay
2026-08-03 21:33   ` Sascha Hauer
2026-08-02 13:16 ` [PATCH v3 10/15] ata: ahci: fix zero-length DMA handling Luca Lauro via B4 Relay
2026-08-02 13:16 ` [PATCH v3 11/15] ata: ahci: add helper for ATA commands without data Luca Lauro via B4 Relay
2026-08-02 13:16 ` [PATCH v3 12/15] ata: ahci: improve AHCI port bring-up sequence Luca Lauro via B4 Relay
2026-08-02 13:16 ` [PATCH v3 13/15] ata: ahci: add FLUSH EXT and STANDBY IMMEDIATE support during shutdown Luca Lauro via B4 Relay
2026-08-03 21:52   ` Sascha Hauer
2026-08-02 13:16 ` [PATCH v3 14/15] ata: ahci: add shutdown helpers for AHCI controllers Luca Lauro via B4 Relay
2026-08-03 21:55   ` Sascha Hauer
2026-08-02 13:16 ` [PATCH v3 15/15] ata: ahci: cleanup legacy code and remove unused paths Luca Lauro via B4 Relay

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox