mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Paolo Pisati <p.pisati@gmail.com>
To: Geert Uytterhoeven <geert@linux-m68k.org>,
	Damien Le Moal <dlemoal@kernel.org>,
	Niklas Cassel <cassel@kernel.org>
Cc: linux-m68k@lists.linux-m68k.org, linux-ide@vger.kernel.org,
	linux-kernel@vger.kernel.org, Lee Jones <lee@kernel.org>,
	mfd@lists.linux.dev
Subject: [PATCH v1 4/7] zorro: warp mfd: add the dual-port RAM command channel and expose the board identity
Date: Wed, 11 Nov 2026 17:37:31 +0100	[thread overview]
Message-ID: <20261111163735.227286-5-p.pisati@gmail.com> (raw)
In-Reply-To: <20261111163735.227286-1-p.pisati@gmail.com>

The ARM co-processor is reached through a dual-port RAM window and an
interrupt control register: the 68k writes a command frame, rings the
ARM, and the ARM acknowledges and then answers in the same buffer,
raising a flag for each on the shared IRQ_AMIGA_PORTS line.

The protocol header describes only what the driver uses: the control
bits, GetDiag and its reply frame, laid out as the 68k sees it, with
static_assert()s pinning the offsets - the firmware decides them, not
the compiler.

Use it for the first attributes: boardname, hwver and fmwver, read from
a fresh diagnostic frame on every access.

Signed-off-by: Paolo Pisati <p.pisati@gmail.com>
---
 .../ABI/testing/sysfs-driver-warp-mfd         |  16 ++
 MAINTAINERS                                   |   1 +
 drivers/zorro/warp-mfd.c                      | 259 +++++++++++++++++-
 drivers/zorro/warp-mfd.h                      | 120 +++++++-
 4 files changed, 392 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-warp-mfd

diff --git a/Documentation/ABI/testing/sysfs-driver-warp-mfd b/Documentation/ABI/testing/sysfs-driver-warp-mfd
new file mode 100644
index 000000000000..8996d1961b96
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-warp-mfd
@@ -0,0 +1,16 @@
+What:		/sys/bus/zorro/devices/zorro<N>/boardname
+Date:		November 2026
+KernelVersion:	7.5
+Contact:	Paolo Pisati <p.pisati@gmail.com>
+Description:
+		(RO) Name of the CS-Lab Warp board, as reported by its ARM
+		co-processor.
+
+What:		/sys/bus/zorro/devices/zorro<N>/fmwver
+What:		/sys/bus/zorro/devices/zorro<N>/hwver
+Date:		November 2026
+KernelVersion:	7.5
+Contact:	Paolo Pisati <p.pisati@gmail.com>
+Description:
+		(RO) Firmware and hardware version, as four hexadecimal digits:
+		an fmwver of 2027 is firmware release 20.27.
diff --git a/MAINTAINERS b/MAINTAINERS
index cfabc4609c9d..7b5e01300418 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7177,6 +7177,7 @@ CS-LAB WARP MULTIFUNCTION DRIVER
 M:	Paolo Pisati <p.pisati@gmail.com>
 L:	linux-m68k@lists.linux-m68k.org
 S:	Maintained
+F:	Documentation/ABI/testing/sysfs-driver-warp-mfd
 F:	drivers/zorro/warp-mfd.c
 F:	drivers/zorro/warp-mfd.h
 
diff --git a/drivers/zorro/warp-mfd.c b/drivers/zorro/warp-mfd.c
index 94af7c9d0ea5..61866f678b61 100644
--- a/drivers/zorro/warp-mfd.c
+++ b/drivers/zorro/warp-mfd.c
@@ -9,22 +9,50 @@
  * and the IDE interface and RTG display controller, which the 68k drives
  * directly.
  *
- * This driver claims the Zorro device and hands the IDE window to pata_cswarp
- * as an mfd cell.
+ * This driver claims the Zorro device, talks to the co-processor over the
+ * dual-port RAM to expose the board's configuration and telemetry in sysfs,
+ * and hands the IDE window to pata_cswarp as an mfd cell.
  */
 
 #include <linux/array_size.h>
+#include <linux/completion.h>
 #include <linux/device.h>
 #include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
 #include <linux/ioport.h>
+#include <linux/jiffies.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
 
 #include <linux/zorro.h>
+#include <asm/amigaints.h>
 
 #include <linux/mfd/core.h>
 
 #include "warp-mfd.h"
 
+/**
+ * struct warp_mfd - per-board state
+ * @dev:    the zorro device we hang off
+ * @dpram:  mapped dual-port RAM, shared with the ARM co-processor
+ * @reg_cr: mapped dual-port interrupt control register
+ * @recv:   completed when the ARM acknowledges a message
+ * @reply:  completed when the ARM has a reply waiting in @dpram
+ * @lock:   serialises use of @dpram, which is one shared buffer
+ */
+struct warp_mfd {
+	struct device		*dev;
+	void __iomem		*dpram;
+	void __iomem		*reg_cr;
+	struct completion	recv;
+	struct completion	reply;
+	struct mutex		lock;	/* serialises @dpram */
+};
+
 static const struct mfd_cell warp_mfd_cells[] = {
 	{
 		.name		= "warp-pata",
@@ -36,13 +64,235 @@ static const struct mfd_cell warp_mfd_cells[] = {
 	}
 };
 
+static irqreturn_t warp_irq(int irq, void *data)
+{
+	struct warp_mfd *warp = data;
+	u32 cr = z_readl(warp->reg_cr);
+	irqreturn_t ret = IRQ_NONE;
+
+	/*
+	 * More than one of these can be latched by the time we run - the ARM
+	 * acknowledges and answers a GetDiag in quick succession - so test
+	 * them all. Returning after the first would leave the others set and
+	 * their waiter asleep until it times out.
+	 */
+	if (cr & DPREG_CR_MR_ARM) {
+		z_writel(DPREG_CR_CLR | DPREG_CR_MR_ARM, warp->reg_cr);
+		/* the ARM has taken the message out of the dual-port RAM */
+		complete(&warp->recv);
+		ret = IRQ_HANDLED;
+	}
+
+	if (cr & DPREG_CR_MP_68K) {
+		z_writel(DPREG_CR_CLR | DPREG_CR_MP_68K, warp->reg_cr);
+		/* the ARM has left a reply in the dual-port RAM */
+		complete(&warp->reply);
+		ret = IRQ_HANDLED;
+	}
+
+	if (cr & DPREG_CR_IF_ETHRX) {
+		z_writel(DPREG_CR_CLR | DPREG_CR_IF_ETHRX, warp->reg_cr);
+		/*
+		 * Acknowledged only. There is no network driver behind this
+		 * yet; the ARM stops re-raising it once the flag is cleared.
+		 */
+		ret = IRQ_HANDLED;
+	}
+
+	return ret;
+}
+
+/*
+ * The AmigaOS library waits on SIGBREAKF_CTRL_C alongside the ARM's signal,
+ * so a user can always break out of a stalled exchange. We have no such
+ * escape from inside a sysfs store, so bound the wait instead - and stay
+ * interruptible, which is the part of that behaviour worth keeping.
+ */
+#define WARP_MSG_TIMEOUT	msecs_to_jiffies(2000)
+
+/* send a message to the ARM coprocessor, optionally waiting for a reply */
+static int warp_send_msg(struct warp_mfd *warp, bool wait_reply)
+{
+	struct completion *done = wait_reply ? &warp->reply : &warp->recv;
+	long ret;
+
+	/* a previous exchange may have timed out and completed late */
+	reinit_completion(done);
+
+	/* enable 68k & ARM interrupts */
+	z_writel(DPREG_CR_SET | DPREG_CR_IE_68K | DPREG_CR_IE_ARM, warp->reg_cr);
+
+	/* send irq to ARM */
+	z_writel(DPREG_CR_SET | DPREG_CR_MP_ARM, warp->reg_cr);
+
+	/* wait for a reply, or just wait for a receiver ack */
+	ret = wait_for_completion_interruptible_timeout(done,
+							WARP_MSG_TIMEOUT);
+
+	/* disable ARM & 68k interrupts */
+	z_writel(DPREG_CR_CLR | DPREG_CR_IE_68K | DPREG_CR_IE_ARM, warp->reg_cr);
+
+	if (ret < 0)
+		return ret;
+	if (!ret) {
+		dev_err(warp->dev, "no answer from the ARM co-processor\n");
+		return -ETIMEDOUT;
+	}
+	return 0;
+}
+
+/*
+ * Submit one command frame and, when @rpl is given, collect the answer.
+ */
+static int warp_exec_cmd(struct warp_mfd *warp, const union warp_cmd *cmd,
+			 union warp_reply *rpl)
+{
+	int ret;
+
+	ret = mutex_lock_interruptible(&warp->lock);
+	if (ret)
+		return ret;
+
+	z_memcpy_toio(warp->dpram, cmd, sizeof(*cmd));
+	ret = warp_send_msg(warp, rpl);
+	if (!ret && rpl)
+		z_memcpy_fromio(rpl, warp->dpram, sizeof(*rpl));
+
+	mutex_unlock(&warp->lock);
+	return ret;
+}
+
+static int warp_get_status(struct warp_mfd *warp, union warp_reply *rpl)
+{
+	union warp_cmd *cmd;
+	int ret;
+
+	cmd = kzalloc_obj(*cmd);
+	if (!cmd)
+		return -ENOMEM;
+	cmd->hdr.cmd = WARP_CMD_GET_DIAG;
+	ret = warp_exec_cmd(warp, cmd, rpl);
+	kfree(cmd);
+	if (ret)
+		return ret;
+
+	/*
+	 * The dual-port RAM still holds the last frame written to it. If the
+	 * board answered something else - or did not answer and we picked up
+	 * a stale completion - the bytes look like a perfectly good reading.
+	 * The vendor library checks this on every command; so do we.
+	 */
+	if (rpl->hdr.rpl != WARP_RPL_DIAG) {
+		dev_err(warp->dev, "unexpected reply %u, wanted a diag frame\n",
+			rpl->hdr.rpl);
+		return -EIO;
+	}
+	return 0;
+}
+
+#define warp_config_uint_attr(name, field, format_string)		\
+static ssize_t name##_show(struct device *dev,				\
+			   struct device_attribute *attr, char *buf)	\
+{									\
+	int ret;							\
+	struct warp_mfd *warp = dev_get_drvdata(dev);			\
+	union warp_reply *rpl;						\
+									\
+	rpl = kmalloc_obj(*rpl);					\
+	if (!rpl)							\
+		return -ENOMEM;						\
+	ret = warp_get_status(warp, rpl);				\
+	if (ret)							\
+		goto out;						\
+	ret = sysfs_emit(buf, format_string "\n", rpl->diag.field);	\
+out:									\
+	kfree(rpl);							\
+	return ret;							\
+}									\
+static DEVICE_ATTR_RO(name)
+
+#define warp_config_string_attr(name, field)				\
+static ssize_t name##_show(struct device *dev,				\
+			   struct device_attribute *attr, char *buf)	\
+{									\
+	int ret;							\
+	struct warp_mfd *warp = dev_get_drvdata(dev);			\
+	union warp_reply *rpl;						\
+									\
+	rpl = kmalloc_obj(*rpl);					\
+	if (!rpl)							\
+		return -ENOMEM;						\
+	ret = warp_get_status(warp, rpl);				\
+	if (ret)							\
+		goto out;						\
+	ret = sysfs_emit(buf, "%.*s\n", (int)sizeof(rpl->diag.field),	\
+			 rpl->diag.field);				\
+out:									\
+	kfree(rpl);							\
+	return ret;							\
+}									\
+static DEVICE_ATTR_RO(name)
+
+warp_config_string_attr(boardname, board_name);
+warp_config_uint_attr(fmwver, fmw_ver, "%04X");
+warp_config_uint_attr(hwver, hw_ver, "%04X");
+
+static struct attribute *warp_attrs[] = {
+	/* RO attr */
+	&dev_attr_boardname.attr,
+	&dev_attr_fmwver.attr,
+	&dev_attr_hwver.attr,
+	NULL
+};
+
+ATTRIBUTE_GROUPS(warp);
+
 static int warp_mfd_probe(struct zorro_dev *z,
 			  const struct zorro_device_id *ent)
 {
+	unsigned long base = z->resource.start;
+	struct warp_mfd *warp;
 	int error;
 
 	dev_info(&z->dev, "Warp at %pR\n", &z->resource);
 
+	warp = devm_kzalloc(&z->dev, sizeof(*warp), GFP_KERNEL);
+	if (!warp)
+		return -ENOMEM;
+	warp->dev = &z->dev;
+	mutex_init(&warp->lock);
+	init_completion(&warp->recv);
+	init_completion(&warp->reply);
+	zorro_set_drvdata(z, warp);
+
+	if (!devm_request_mem_region(&z->dev, base + WARP_OFFSET_DPREG_CR,
+				     sizeof(u32), "warp-mfd")) {
+		dev_err(&z->dev, "cannot reserve the interrupt control register\n");
+		return -EBUSY;
+	}
+
+	if (!devm_request_mem_region(&z->dev, base + WARP_OFFSET_DPRAM,
+				     WARP_DPRAM_SIZE, "warp-mfd")) {
+		dev_err(&z->dev, "cannot reserve the dual-port RAM window\n");
+		return -EBUSY;
+	}
+
+	warp->dpram = devm_ioremap(&z->dev, base + WARP_OFFSET_DPRAM,
+				   WARP_DPRAM_SIZE);
+	warp->reg_cr = devm_ioremap(&z->dev, base + WARP_OFFSET_DPREG_CR,
+				    sizeof(u32));
+	if (!warp->dpram || !warp->reg_cr) {
+		dev_err(&z->dev, "cannot map the dual-port RAM window\n");
+		return -ENOMEM;
+	}
+
+	error = devm_request_irq(&z->dev, IRQ_AMIGA_PORTS, warp_irq,
+				 IRQF_SHARED, "warp-mfd", warp);
+	if (error) {
+		dev_err(&z->dev, "cannot request irq %u\n", IRQ_AMIGA_PORTS);
+		return error;
+	}
+
 	error = mfd_add_devices(&z->dev, z->dev.id, warp_mfd_cells,
 				ARRAY_SIZE(warp_mfd_cells), &z->resource,
 				0, NULL);
@@ -68,7 +318,10 @@ static struct zorro_driver warp_mfd_driver = {
 	.name		= "warp-mfd",
 	.id_table	= warp_mfd_ids,
 	.probe		= warp_mfd_probe,
-	.remove		= warp_mfd_remove
+	.remove		= warp_mfd_remove,
+	.driver		= {
+		.dev_groups	= warp_groups,
+	},
 };
 
 static int __init warp_mfd_init(void)
diff --git a/drivers/zorro/warp-mfd.h b/drivers/zorro/warp-mfd.h
index 3f44f2b57c76..366848545ea4 100644
--- a/drivers/zorro/warp-mfd.h
+++ b/drivers/zorro/warp-mfd.h
@@ -1,13 +1,34 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 /*
- * CS-Lab Warp - board window layout
+ * CS-Lab Warp - 68k <-> ARM dual-port RAM protocol
  *
  * Copyright 2026 Paolo Pisati <p.pisati@gmail.com>
+ *
+ * The Warp boards pair the 68k with an ARM co-processor. The two talk
+ * through a dual-port RAM window and a shared interrupt control register,
+ * both of which sit at fixed offsets inside the board's Zorro space.
+ *
+ * This describes only the small part of that interface this driver uses:
+ * the control register bits, the handful of commands behind the sysfs
+ * attributes, and the diagnostic reply. The board firmware implements many
+ * more commands - see the vendor sources for the full protocol.
+ *
+ * Multi-byte fields are laid out as the 68k sees them, and the wire format
+ * assumes 2-byte alignment for 32-bit members. That is what m68k gcc does
+ * by default, so no packing attribute is needed here - but it does mean the
+ * kernel must not be built with -malign-int, which would push every 32-bit
+ * member to a 4-byte boundary and desynchronise the layout from the board.
+ * The static_assert()s at the end of this file catch that.
  */
 
 #ifndef _WARP_MFD_H
 #define _WARP_MFD_H
 
+#include <linux/bits.h>
+#include <linux/build_bug.h>
+#include <linux/stddef.h>
+#include <linux/types.h>
+
 /* Offsets from the start of the board's Zorro window. */
 #define WARP_OFFSET_DPREG_CR	0x1000	/* dual-port interrupt control reg */
 #define WARP_OFFSET_DPRAM	0x2000	/* dual-port RAM */
@@ -16,4 +37,101 @@
 #define WARP_DPRAM_SIZE		0x2000
 #define WARP_ATA_SIZE		0x1800
 
+/*
+ * Interrupt control register. Writes are read-modify-write requests rather
+ * than plain stores: OR in DPREG_CR_SET to set the named bits, or write them
+ * with DPREG_CR_CLR to clear them. Reads return the raw bit state.
+ */
+#define DPREG_CR_SET		0x80000000	/* set the bits in this word */
+#define DPREG_CR_CLR		0x00000000	/* clear the bits in this word */
+#define DPREG_CR_IE_ARM		BIT(0)		/* ARM irq enable */
+#define DPREG_CR_IE_68K		BIT(1)		/* 68k irq enable */
+#define DPREG_CR_MP_ARM		BIT(2)		/* message pending (ARM) */
+#define DPREG_CR_MP_68K		BIT(3)		/* message pending (68k) */
+#define DPREG_CR_MR_ARM		BIT(4)		/* message received (ARM) */
+#define DPREG_CR_MR_68K		BIT(5)		/* message received (68k) */
+#define DPREG_CR_IE_ETHRX	BIT(6)		/* ethernet rx irq enable */
+#define DPREG_CR_IF_ETHRX	BIT(9)		/* ethernet rx irq flag */
+
+#define WARP_BOARDNAME_LEN	32
+#define WARP_WIFI_SSID_LEN	128
+#define WARP_WIFI_PASS_LEN	128
+
+/* Command opcodes. Only the ones this driver issues are listed. */
+enum warp_cmd_id {
+	WARP_CMD_GET_DIAG	= 6,
+};
+
+/* Reply opcodes, as reported in struct warp_rpl_hdr::rpl. */
+enum warp_rpl_id {
+	WARP_RPL_NOP		= 0,
+	WARP_RPL_DIAG		= 1,
+};
+
+struct warp_cmd_hdr {
+	u32	cmd;			/* enum warp_cmd_id */
+};
+
+union warp_cmd {
+	struct warp_cmd_hdr		hdr;
+};
+
+struct warp_rpl_hdr {
+	u32	rpl;
+};
+
+/*
+ * Reply to WARP_CMD_GET_DIAG. Fields this driver does not expose are kept
+ * so the ones after them land at the right offset.
+ *
+ * turbo_level and tz_corr_secs are reported by the firmware but no longer
+ * maintained by it; treat their contents as stale.
+ */
+struct warp_diag {
+	struct warp_rpl_hdr	hdr;
+	u32			vcc5v;			/* binary32 */
+	u32			vbatt;			/* binary32 */
+	u32			t60_ntc;		/* binary32 */
+	u32			t60_internal;		/* binary32 */
+	u32			t_arm;			/* binary32 */
+	u32			hw_ver;
+	u32			fmw_ver;
+	u32			turbo_level;		/* obsolete, stale */
+	u32			fan_percent;
+	char			board_name[WARP_BOARDNAME_LEN] __nonstring;
+	u32			kickstart_nr;
+	u32			usb_dev_status[2];
+	char			wifi_ssid[WARP_WIFI_SSID_LEN] __nonstring;
+	char			wifi_pass[WARP_WIFI_PASS_LEN] __nonstring;
+	u32			native_ide_enabled;
+	s32			temp_reg_cpu;
+	s32			temp_reg_min_pwm;
+	u8			ide_ior_as;
+	u8			ide_ior_ng;
+	u8			ide_iow_as;
+	u8			ide_iow_ng;
+	u8			ide_ack_as;
+	s32			tz_corr_secs;		/* obsolete */
+	u8			board_type;
+	u32			wifi_state;
+	u32			cpu_freq_mhz;		/* binary32 */
+	u32			cpu_clk_phase_shift_deg; /* binary32 */
+};
+
+union warp_reply {
+	struct warp_rpl_hdr	hdr;
+	struct warp_diag	diag;
+};
+
+/*
+ * The board decides these offsets, not the compiler. If a toolchain change
+ * moves them, every field past the first mismatch decodes as garbage, so
+ * fail the build instead.
+ */
+static_assert(offsetof(struct warp_diag, board_name) == 40);
+static_assert(offsetof(struct warp_diag, wifi_ssid) == 84);
+static_assert(offsetof(struct warp_diag, wifi_pass) == 212);
+static_assert(offsetof(struct warp_diag, wifi_state) == 364);
+static_assert(sizeof(struct warp_diag) == 376);
+
 #endif /* _WARP_MFD_H */
-- 
2.43.0


  parent reply	other threads:[~2026-11-11 16:37 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-11 16:37 [PATCH v1 0/7] zorro: add a CS-Lab Warp 1260 MFD Paolo Pisati
2026-11-11 16:37 ` [PATCH v1 1/7] zorro: warp mfd: add the CS-Lab Warp multifunction parent Paolo Pisati
2026-11-11 16:37 ` [PATCH v1 2/7] ata: pata_cswarp: bind to the Warp mfd cell instead of the Zorro device Paolo Pisati
2026-11-11 16:37 ` [PATCH v1 3/7] m68k: defconfig: enable WARP_MFD Paolo Pisati
2026-11-11 16:37 ` Paolo Pisati [this message]
2026-11-11 16:37 ` [PATCH v1 5/7] zorro: warp mfd: expose the voltage and temperature sensors Paolo Pisati
2026-11-11 16:37 ` [PATCH v1 6/7] zorro: warp mfd: expose the fan duty cycle Paolo Pisati
2026-11-11 16:37 ` [PATCH v1 7/7] zorro: warp mfd: expose the Kickstart slot selection Paolo Pisati

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261111163735.227286-5-p.pisati@gmail.com \
    --to=p.pisati@gmail.com \
    --cc=cassel@kernel.org \
    --cc=dlemoal@kernel.org \
    --cc=geert@linux-m68k.org \
    --cc=lee@kernel.org \
    --cc=linux-ide@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-m68k@lists.linux-m68k.org \
    --cc=mfd@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®