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: 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®