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
next prev parent reply other threads:[~2026-11-11 16:37 UTC|newest]
Thread overview: 13+ 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 17:03 ` sashiko-bot
2026-11-11 18:04 ` Lee Jones
2026-11-12 15:21 ` 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 17:12 ` sashiko-bot
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 17:23 ` [PATCH v1 4/7] zorro: warp mfd: add the dual-port RAM command channel and expose the board identity sashiko-bot
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.