From 0b7a604d73f332fe97c67a92889692e795b943e2 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Thu, 31 Oct 2024 18:26:00 +0000
Subject: [PATCH] misc: Add RP1 PIO driver

Provide remote access to the PIO hardware in RP1. There is a single
instance, with 4 state machines.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc: rp1-pio: Support larger data transfers

Add a separate IOCTL for larger transfer with a 32-bit data_bytes
field.

See: https://github.com/raspberrypi/utils/issues/107

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc: rp1-pio: More logical probe sequence

Sort the probe function initialisation into a more logical order.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc: rp1-pio: Minor cosmetic tweaks

No functional change.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc: rp1-pio: Add in-kernel DMA support

Add kernel-facing implementations of pio_sm_config_xfer and
pio_xm_xfer_data.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc: rp1-pio: Handle probe errors

Ensure that rp1_pio_open fails if the device failed to probe.

Link: https://github.com/raspberrypi/linux/issues/6593

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc: rp1-pio: SM_CONFIG_XFER32 = larger DMA bufs

Add an ioctl type - SM_CONFIG_XFER32 - that takes uints for the buf_size
and buf_count values.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc/rp1-pio: Fix copy/paste error in pio_rp1.h

As per the subject, there was a copy/paste error that caused
pio_sm_unclaim from a driver to result in a call to
pio_sm_claim. Fix it.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc: rp1-pio: Fix parameter checks wihout client

Passing bad parameters to an API call without a pio pointer will cause
a NULL pointer exception when the persistent error is set. Guard
against that.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc: rp1-pio: Convert floats to 24.8 fixed point

Floating point arithmetic is not supported in the kernel, so use fixed
point instead.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc: rp1-pio: Error out on incompatible firmware

If the RP1 firmware has reported an error then return that from the PIO
probe function, otherwise defer the probing.

Link: https://github.com/raspberrypi/linux/issues/6642

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc: rp1-pio: Demote fw probe error to warning

Support for the RP1 firmware mailbox API is rolling out to Pi 5 EEPROM
images. For most users, the fact that the PIO is not available is no
cause for alarm. Change the message to a warning, so that it does not
appear with "quiet" in cmdline.txt.

Link: https://github.com/raspberrypi/linux/issues/6642

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc: rp1-pio: Don't just reuse the same DMA buf

A missing pointer increment meant that not only was the same buffer
being reused again and again, there was also no protection against
using it simultaneously for multiple transfers. Fix that basic bug, and
also move a similar increment to before the transfer is started, which
feels less racy.

See: https://github.com/raspberrypi/linux/issues/6919

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc: rp1-pio: Fix a config_xfer error path

If the DMA channel allocation fails, the relevant dma_configs entry
should be marked as no longer claimed, otherwise rp1_pio_sm_dma_free
will be called with an error number as a DMA channel pointer.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc: rp1-pio: Request a DMA burst size of 8

Improve DMA performance by increasing the burst size to 8.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

misc: rp1-pio: Get burst size from DMA capabilities

Although the PIO throughput benefits from larger burst sizes, only the
first two DMA channels support a burst size of 8 - the others are capped
at 4. To avoid misconfiguring the PIO hardware, retrieve the actual
max_burst value from the DMA channel's capabilities.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
 drivers/misc/Kconfig             |    8 +
 drivers/misc/Makefile            |    1 +
 drivers/misc/rp1-fw-pio.h        |   56 ++
 drivers/misc/rp1-pio.c           | 1400 ++++++++++++++++++++++++++++++
 include/linux/pio_instructions.h |  481 ++++++++++
 include/linux/pio_rp1.h          | 1019 ++++++++++++++++++++++
 include/uapi/misc/rp1_pio_if.h   |  235 +++++
 7 files changed, 3200 insertions(+)
 create mode 100644 drivers/misc/rp1-fw-pio.h
 create mode 100644 drivers/misc/rp1-pio.c
 create mode 100644 include/linux/pio_instructions.h
 create mode 100644 include/linux/pio_rp1.h
 create mode 100644 include/uapi/misc/rp1_pio_if.h

--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -17,6 +17,14 @@ config BCM2835_SMI
 		Driver for enabling and using Broadcom's Secondary/Slow Memory Interface.
 		Appears as /dev/bcm2835_smi. For ioctl interface see drivers/misc/bcm2835_smi.h
 
+config RP1_PIO
+	tristate "Raspberry Pi RP1 PIO driver"
+	depends on FIRMWARE_RP1 || COMPILE_TEST
+	default n
+	help
+		Driver providing control of the Raspberry Pi PIO block, as found in
+		RP1.
+
 config AD525X_DPOT
 	tristate "Analog Devices Digital Potentiometers"
 	depends on (I2C || SPI) && SYSFS
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_PHANTOM)		+= phantom.o
 obj-$(CONFIG_RPMB)		+= rpmb-core.o
 obj-$(CONFIG_QCOM_COINCELL)	+= qcom-coincell.o
 obj-$(CONFIG_QCOM_FASTRPC)	+= fastrpc.o
+obj-$(CONFIG_RP1_PIO)		+= rp1-pio.o
 obj-$(CONFIG_SENSORS_BH1770)	+= bh1770glc.o
 obj-$(CONFIG_SENSORS_APDS990X)	+= apds990x.o
 obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o
--- /dev/null
+++ b/drivers/misc/rp1-fw-pio.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ *  Copyright (C) 2023 2023-2024 Raspberry Pi Ltd.
+ */
+
+#ifndef __SOC_RP1_FIRMWARE_OPS_H__
+#define __SOC_RP1_FIRMWARE_OPS_H__
+
+#include <linux/rp1-firmware.h>
+
+#define FOURCC_PIO RP1_FOURCC("PIO ")
+
+enum rp1_pio_ops {
+	PIO_CAN_ADD_PROGRAM,	// u16 num_instrs, u16 origin -> origin
+	PIO_ADD_PROGRAM,	// u16 num_instrs, u16 origin, u16 prog[] -> rc
+	PIO_REMOVE_PROGRAM,	// u16 num_instrs, u16 origin
+	PIO_CLEAR_INSTR_MEM,	// -
+
+	PIO_SM_CLAIM,		// u16 mask -> sm
+	PIO_SM_UNCLAIM,		// u16 mask
+	PIO_SM_IS_CLAIMED,	// u16 mask -> claimed
+
+	PIO_SM_INIT,		// u16 sm, u16 initial_pc, u32 sm_config[4]
+	PIO_SM_SET_CONFIG,	// u16 sm, u16 rsvd, u32 sm_config[4]
+	PIO_SM_EXEC,		// u16 sm, u16 instr, u8 blocking, u8 rsvd
+	PIO_SM_CLEAR_FIFOS,	// u16 sm
+	PIO_SM_SET_CLKDIV,	// u16 sm, u16 div_int, u8 div_frac, u8 rsvd
+	PIO_SM_SET_PINS,	// u16 sm, u16 rsvd, u32 values, u32 mask
+	PIO_SM_SET_PINDIRS,	// u16 sm, u16 rsvd, u32 dirs, u32 mask
+	PIO_SM_SET_ENABLED,	// u16 mask, u8 enable, u8 rsvd
+	PIO_SM_RESTART,		// u16 mask
+	PIO_SM_CLKDIV_RESTART,	// u16 mask
+	PIO_SM_ENABLE_SYNC,	// u16 mask
+	PIO_SM_PUT,		// u16 sm, u8 blocking, u8 rsvd, u32 data
+	PIO_SM_GET,		// u16 sm, u8 blocking, u8 rsvd -> u32 data
+	PIO_SM_SET_DMACTRL,	// u16 sm, u16 is_tx, u32 ctrl
+
+	GPIO_INIT,		// u16 gpio
+	GPIO_SET_FUNCTION,	// u16 gpio, u16 fn
+	GPIO_SET_PULLS,		// u16 gpio, u8 up, u8 down
+	GPIO_SET_OUTOVER,	// u16 gpio, u16 value
+	GPIO_SET_INOVER,	// u16 gpio, u16 value
+	GPIO_SET_OEOVER,	// u16 gpio, u16 value
+	GPIO_SET_INPUT_ENABLED,	// u16 gpio, u16 value
+	GPIO_SET_DRIVE_STRENGTH,	// u16 gpio, u16 value
+
+	READ_HW,		// src address, len -> data bytes
+	WRITE_HW,		// dst address, data
+
+	PIO_SM_FIFO_STATE,	// u16 sm, u8 tx -> u16 level, u8 empty, u8 full
+	PIO_SM_DRAIN_TX,	// u16 sm
+
+	PIO_COUNT
+};
+
+#endif
--- /dev/null
+++ b/drivers/misc/rp1-pio.c
@@ -0,0 +1,1400 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PIO driver for RP1
+ *
+ * Copyright (C) 2023-2024 Raspberry Pi Ltd.
+ *
+ * Parts of this driver are based on:
+ *  - vcio.c, by Noralf Trønnes
+ *    Copyright (C) 2010 Broadcom
+ *    Copyright (C) 2015 Noralf Trønnes
+ *    Copyright (C) 2021 Raspberry Pi (Trading) Ltd.
+ *  - bcm2835_smi.c & bcm2835_smi_dev.c by Luke Wren
+ *    Copyright (c) 2015 Raspberry Pi (Trading) Ltd.
+ */
+
+#include <linux/cdev.h>
+#include <linux/compat.h>
+#include <linux/device.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/ioctl.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pio_rp1.h>
+#include <linux/platform_device.h>
+#include <linux/rp1-firmware.h>
+#include <linux/semaphore.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <uapi/misc/rp1_pio_if.h>
+
+#include "rp1-fw-pio.h"
+
+#define DRIVER_NAME		"rp1-pio"
+
+#define RP1_PIO_SMS_COUNT	4
+#define RP1_PIO_INSTR_COUNT	32
+
+#define MAX_ARG_SIZE		256
+
+#define RP1_PIO_FIFO_TX0	0x00
+#define RP1_PIO_FIFO_TX1	0x04
+#define RP1_PIO_FIFO_TX2	0x08
+#define RP1_PIO_FIFO_TX3	0x0c
+#define RP1_PIO_FIFO_RX0	0x10
+#define RP1_PIO_FIFO_RX1	0x14
+#define RP1_PIO_FIFO_RX2	0x18
+#define RP1_PIO_FIFO_RX3	0x1c
+
+#define RP1_PIO_DMACTRL_DEFAULT	0x80000104
+
+#define HANDLER(_n, _f) \
+	[_IOC_NR(PIO_IOC_ ## _n)] = { #_n, rp1_pio_ ## _f, _IOC_SIZE(PIO_IOC_ ## _n) }
+
+
+#define ROUND_UP(x, y) (((x) + (y) - 1) - (((x) + (y) - 1) % (y)))
+
+#define DMA_BOUNCE_BUFFER_SIZE 0x1000
+#define DMA_BOUNCE_BUFFER_COUNT 4
+
+struct dma_xfer_state {
+	struct dma_info *dma;
+	void (*callback)(void *param);
+	void *callback_param;
+};
+
+struct dma_buf_info {
+	void *buf;
+	dma_addr_t dma_addr;
+	struct scatterlist sgl;
+};
+
+struct dma_info {
+	struct semaphore buf_sem;
+	struct dma_chan *chan;
+	size_t buf_size;
+	size_t buf_count;
+	unsigned int head_idx;
+	unsigned int tail_idx;
+	struct dma_buf_info bufs[DMA_BOUNCE_BUFFER_COUNT];
+};
+
+struct rp1_pio_device {
+	struct platform_device *pdev;
+	struct rp1_firmware *fw;
+	uint16_t fw_pio_base;
+	uint16_t fw_pio_count;
+	dev_t dev_num;
+	struct class *dev_class;
+	struct cdev cdev;
+	phys_addr_t phys_addr;
+	uint32_t claimed_sms;
+	uint32_t claimed_dmas;
+	spinlock_t lock;
+	struct mutex instr_mutex;
+	struct dma_info dma_configs[RP1_PIO_SMS_COUNT][RP1_PIO_DIR_COUNT];
+	uint32_t used_instrs;
+	uint8_t instr_refcounts[RP1_PIO_INSTR_COUNT];
+	uint16_t instrs[RP1_PIO_INSTR_COUNT];
+	uint client_count;
+};
+
+struct rp1_pio_client {
+	struct rp1_pio_device *pio;
+	uint32_t claimed_sms;
+	uint32_t claimed_instrs;
+	uint32_t claimed_dmas;
+	int error;
+};
+
+static struct rp1_pio_device *g_pio;
+
+static int rp1_pio_message(struct rp1_pio_device *pio,
+			   uint16_t op, const void *data, unsigned int data_len)
+{
+	uint32_t rc;
+	int ret;
+
+	if (op >= pio->fw_pio_count)
+		return -EOPNOTSUPP;
+	ret = rp1_firmware_message(pio->fw, pio->fw_pio_base + op,
+				   data, data_len,
+				   &rc, sizeof(rc));
+	if (ret == 4)
+		ret = rc;
+	return ret;
+}
+
+static int rp1_pio_message_resp(struct rp1_pio_device *pio,
+				uint16_t op, const void *data, unsigned int data_len,
+				void *resp, void __user *userbuf, unsigned int resp_len)
+{
+	uint32_t resp_buf[1 + 32];
+	int ret;
+
+	if (op >= pio->fw_pio_count)
+		return -EOPNOTSUPP;
+	if (resp_len + 4 >= sizeof(resp_buf))
+		return -EINVAL;
+	if (!resp && !userbuf)
+		return -EINVAL;
+	ret = rp1_firmware_message(pio->fw, pio->fw_pio_base + op,
+				   data, data_len,
+				   resp_buf, resp_len + 4);
+	if (ret >= 4 && !resp_buf[0]) {
+		ret -= 4;
+		if (resp)
+			memcpy(resp, &resp_buf[1], ret);
+		else if (copy_to_user(userbuf, &resp_buf[1], ret))
+			ret = -EFAULT;
+	} else if (ret >= 0) {
+		ret = -EIO;
+	}
+	return ret;
+}
+
+static int rp1_pio_read_hw(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_device *pio = client->pio;
+	struct rp1_access_hw_args *args = param;
+
+	return rp1_pio_message_resp(pio, READ_HW,
+				    args, 8, NULL, args->data, args->len);
+}
+
+static int rp1_pio_write_hw(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_device *pio = client->pio;
+	struct rp1_access_hw_args *args = param;
+	uint32_t write_buf[32 + 1];
+	int len;
+
+	len = min(args->len, sizeof(write_buf) - 4);
+	write_buf[0] = args->addr;
+	if (copy_from_user(&write_buf[1], args->data, len))
+		return -EFAULT;
+	return rp1_firmware_message(pio->fw, pio->fw_pio_base + WRITE_HW,
+				    write_buf, 4 + len, NULL, 0);
+}
+
+static int rp1_pio_find_program(struct rp1_pio_device *pio,
+				struct rp1_pio_add_program_args *prog)
+{
+	uint start, end, prog_size;
+	uint32_t used_mask;
+	uint i;
+
+	start = (prog->origin != RP1_PIO_ORIGIN_ANY) ? prog->origin : 0;
+	end = (prog->origin != RP1_PIO_ORIGIN_ANY) ? prog->origin :
+			(RP1_PIO_INSTRUCTION_COUNT - prog->num_instrs);
+	prog_size = sizeof(prog->instrs[0]) * prog->num_instrs;
+	used_mask = (uint32_t)(~0) >> (32 - prog->num_instrs);
+
+	/* Find the best match */
+	for (i = start; i <= end; i++) {
+		uint32_t mask = used_mask << i;
+
+		if ((pio->used_instrs & mask) != mask)
+			continue;
+		if (!memcmp(pio->instrs + i, prog->instrs, prog_size))
+			return i;
+	}
+
+	return -1;
+}
+
+int rp1_pio_can_add_program(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_add_program_args *args = param;
+	struct rp1_pio_device *pio = client->pio;
+	int offset;
+
+	if (args->num_instrs > RP1_PIO_INSTR_COUNT ||
+		((args->origin != RP1_PIO_ORIGIN_ANY) &&
+		 (args->origin >= RP1_PIO_INSTR_COUNT ||
+		  ((args->origin + args->num_instrs) > RP1_PIO_INSTR_COUNT))))
+		return -EINVAL;
+
+	mutex_lock(&pio->instr_mutex);
+	offset = rp1_pio_find_program(pio, args);
+	mutex_unlock(&pio->instr_mutex);
+	if (offset >= 0)
+		return offset;
+
+	/* Don't send the instructions, just the header */
+	return rp1_pio_message(pio, PIO_CAN_ADD_PROGRAM, args,
+			       offsetof(struct rp1_pio_add_program_args, instrs));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_can_add_program);
+
+int rp1_pio_add_program(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_add_program_args *args = param;
+	struct rp1_pio_device *pio = client->pio;
+	int offset;
+	uint i;
+
+	if (args->num_instrs > RP1_PIO_INSTR_COUNT ||
+		((args->origin != RP1_PIO_ORIGIN_ANY) &&
+		 (args->origin >= RP1_PIO_INSTR_COUNT ||
+		  ((args->origin + args->num_instrs) > RP1_PIO_INSTR_COUNT))))
+		return -EINVAL;
+
+	mutex_lock(&pio->instr_mutex);
+	offset = rp1_pio_find_program(pio, args);
+	if (offset < 0)
+		offset = rp1_pio_message(client->pio, PIO_ADD_PROGRAM, args, sizeof(*args));
+
+	if (offset >= 0) {
+		uint32_t used_mask;
+		uint prog_size;
+
+		used_mask = ((uint32_t)(~0) >> (-args->num_instrs & 0x1f)) << offset;
+		prog_size = sizeof(args->instrs[0]) * args->num_instrs;
+
+		if ((pio->used_instrs & used_mask) != used_mask) {
+			pio->used_instrs |= used_mask;
+			memcpy(pio->instrs + offset, args->instrs, prog_size);
+		}
+		client->claimed_instrs |= used_mask;
+		for (i = 0; i < args->num_instrs; i++)
+			pio->instr_refcounts[offset + i]++;
+	}
+	mutex_unlock(&pio->instr_mutex);
+	return offset;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_add_program);
+
+static void rp1_pio_remove_instrs(struct rp1_pio_device *pio, uint32_t mask)
+{
+	struct rp1_pio_remove_program_args args;
+	uint i;
+
+	mutex_lock(&pio->instr_mutex);
+	args.num_instrs = 0;
+	for (i = 0; ; i++, mask >>= 1) {
+		if ((mask & 1) && pio->instr_refcounts[i] && !--pio->instr_refcounts[i]) {
+			pio->used_instrs &= ~(1 << i);
+			args.num_instrs++;
+		} else if (args.num_instrs) {
+			args.origin = i - args.num_instrs;
+			rp1_pio_message(pio, PIO_REMOVE_PROGRAM, &args, sizeof(args));
+			args.num_instrs = 0;
+		}
+		if (!mask)
+			break;
+	}
+	mutex_unlock(&pio->instr_mutex);
+}
+
+int rp1_pio_remove_program(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_remove_program_args *args = param;
+	uint32_t used_mask;
+	int ret = -ENOENT;
+
+	if (args->num_instrs > RP1_PIO_INSTR_COUNT ||
+		args->origin >= RP1_PIO_INSTR_COUNT ||
+		(args->origin + args->num_instrs) > RP1_PIO_INSTR_COUNT)
+		return -EINVAL;
+
+	used_mask = ((uint32_t)(~0) >> (32 - args->num_instrs)) << args->origin;
+	if ((client->claimed_instrs & used_mask) == used_mask) {
+		client->claimed_instrs &= ~used_mask;
+		rp1_pio_remove_instrs(client->pio, used_mask);
+		ret = 0;
+	}
+	return ret;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_remove_program);
+
+int rp1_pio_clear_instr_mem(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_device *pio = client->pio;
+
+	mutex_lock(&pio->instr_mutex);
+	(void)rp1_pio_message(client->pio, PIO_CLEAR_INSTR_MEM, NULL, 0);
+	memset(pio->instr_refcounts, 0, sizeof(pio->instr_refcounts));
+	pio->used_instrs = 0;
+	client->claimed_instrs = 0;
+	mutex_unlock(&pio->instr_mutex);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_clear_instr_mem);
+
+int rp1_pio_sm_claim(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_claim_args *args = param;
+	struct rp1_pio_device *pio = client->pio;
+	int ret;
+
+	mutex_lock(&pio->instr_mutex);
+	ret = rp1_pio_message(client->pio, PIO_SM_CLAIM, args, sizeof(*args));
+	if (ret >= 0) {
+		if (args->mask)
+			client->claimed_sms |= args->mask;
+		else
+			client->claimed_sms |= (1 << ret);
+		pio->claimed_sms |= client->claimed_sms;
+	}
+	mutex_unlock(&pio->instr_mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_claim);
+
+int rp1_pio_sm_unclaim(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_claim_args *args = param;
+	struct rp1_pio_device *pio = client->pio;
+
+	mutex_lock(&pio->instr_mutex);
+	(void)rp1_pio_message(client->pio, PIO_SM_UNCLAIM, args, sizeof(*args));
+	client->claimed_sms &= ~args->mask;
+	pio->claimed_sms &= ~args->mask;
+	mutex_unlock(&pio->instr_mutex);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_unclaim);
+
+int rp1_pio_sm_is_claimed(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_claim_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_IS_CLAIMED, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_is_claimed);
+
+int rp1_pio_sm_init(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_init_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_INIT, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_init);
+
+int rp1_pio_sm_set_config(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_set_config_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_SET_CONFIG, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_set_config);
+
+int rp1_pio_sm_exec(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_exec_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_EXEC, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_exec);
+
+int rp1_pio_sm_clear_fifos(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_clear_fifos_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_CLEAR_FIFOS, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_clear_fifos);
+
+int rp1_pio_sm_set_clkdiv(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_set_clkdiv_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_SET_CLKDIV, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_set_clkdiv);
+
+int rp1_pio_sm_set_pins(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_set_pins_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_SET_PINS, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_set_pins);
+
+int rp1_pio_sm_set_pindirs(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_set_pindirs_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_SET_PINDIRS, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_set_pindirs);
+
+int rp1_pio_sm_set_enabled(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_set_enabled_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_SET_ENABLED, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_set_enabled);
+
+int rp1_pio_sm_restart(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_restart_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_RESTART, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_restart);
+
+int rp1_pio_sm_clkdiv_restart(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_restart_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_CLKDIV_RESTART, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_clkdiv_restart);
+
+int rp1_pio_sm_enable_sync(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_enable_sync_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_ENABLE_SYNC, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_enable_sync);
+
+int rp1_pio_sm_put(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_put_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_PUT, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_put);
+
+int rp1_pio_sm_get(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_get_args *args = param;
+	int ret;
+
+	ret = rp1_pio_message_resp(client->pio, PIO_SM_GET, args, sizeof(*args),
+				   &args->data, NULL, sizeof(args->data));
+	if (ret >= 0)
+		return offsetof(struct rp1_pio_sm_get_args, data) + ret;
+	return ret;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_get);
+
+int rp1_pio_sm_set_dmactrl(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_set_dmactrl_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_SET_DMACTRL, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_set_dmactrl);
+
+int rp1_pio_sm_fifo_state(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_fifo_state_args *args = param;
+	const int level_offset = offsetof(struct rp1_pio_sm_fifo_state_args, level);
+	int ret;
+
+	ret = rp1_pio_message_resp(client->pio, PIO_SM_FIFO_STATE, args, sizeof(*args),
+				   &args->level, NULL, sizeof(*args) - level_offset);
+	if (ret >= 0)
+		return level_offset + ret;
+	return ret;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_fifo_state);
+
+int rp1_pio_sm_drain_tx(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_clear_fifos_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_DRAIN_TX, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_drain_tx);
+
+int rp1_pio_gpio_init(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_init_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_INIT, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_init);
+
+int rp1_pio_gpio_set_function(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_set_function_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_SET_FUNCTION, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_function);
+
+int rp1_pio_gpio_set_pulls(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_set_pulls_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_SET_PULLS, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_pulls);
+
+int rp1_pio_gpio_set_outover(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_set_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_SET_OUTOVER, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_outover);
+
+int rp1_pio_gpio_set_inover(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_set_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_SET_INOVER, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_inover);
+
+int rp1_pio_gpio_set_oeover(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_set_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_SET_OEOVER, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_oeover);
+
+int rp1_pio_gpio_set_input_enabled(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_set_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_SET_INPUT_ENABLED, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_input_enabled);
+
+int rp1_pio_gpio_set_drive_strength(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_set_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_SET_DRIVE_STRENGTH, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_drive_strength);
+
+static void rp1_pio_sm_dma_callback(void *param)
+{
+	struct dma_info *dma = param;
+
+	up(&dma->buf_sem);
+}
+
+static void rp1_pio_sm_kernel_dma_callback(void *param)
+{
+	struct dma_xfer_state *dxs = param;
+
+	dxs->dma->tail_idx++;
+	up(&dxs->dma->buf_sem);
+
+	dxs->callback(dxs->callback_param);
+
+	kfree(dxs);
+}
+
+static void rp1_pio_sm_dma_free(struct device *dev, struct dma_info *dma)
+{
+	dmaengine_terminate_all(dma->chan);
+	while (dma->buf_count > 0) {
+		dma->buf_count--;
+		dma_free_coherent(dev, ROUND_UP(dma->buf_size, PAGE_SIZE),
+				  dma->bufs[dma->buf_count].buf,
+				  dma->bufs[dma->buf_count].dma_addr);
+	}
+
+	dma_release_channel(dma->chan);
+}
+
+static int rp1_pio_sm_config_xfer_internal(struct rp1_pio_client *client, uint sm, uint dir,
+					   uint buf_size, uint buf_count)
+{
+	struct rp1_pio_sm_set_dmactrl_args set_dmactrl_args;
+	struct rp1_pio_device *pio = client->pio;
+	struct platform_device *pdev = pio->pdev;
+	struct device *dev = &pdev->dev;
+	struct dma_slave_config config = {};
+	struct dma_slave_caps dma_caps;
+	phys_addr_t fifo_addr;
+	struct dma_info *dma;
+	uint32_t dma_mask;
+	char chan_name[4];
+	int ret = 0;
+
+	if (sm >= RP1_PIO_SMS_COUNT || dir >= RP1_PIO_DIR_COUNT)
+		return -EINVAL;
+	if ((buf_count || buf_size) &&
+	    (!buf_size || (buf_size & 3) ||
+	     !buf_count || buf_count > DMA_BOUNCE_BUFFER_COUNT))
+		return -EINVAL;
+
+	dma_mask = 1 << (sm * 2 + dir);
+
+	dma = &pio->dma_configs[sm][dir];
+
+	spin_lock(&pio->lock);
+	if (pio->claimed_dmas & dma_mask)
+		rp1_pio_sm_dma_free(dev, dma);
+	pio->claimed_dmas |= dma_mask;
+	client->claimed_dmas |= dma_mask;
+	spin_unlock(&pio->lock);
+
+	dma->buf_size = buf_size;
+	/* Round up the allocations */
+	buf_size = ROUND_UP(buf_size, PAGE_SIZE);
+	sema_init(&dma->buf_sem, 0);
+
+	/* Allocate and configure a DMA channel */
+	/* Careful - each SM FIFO has its own DREQ value */
+	chan_name[0] = (dir == RP1_PIO_DIR_TO_SM) ? 't' : 'r';
+	chan_name[1] = 'x';
+	chan_name[2] = '0' + sm;
+	chan_name[3] = '\0';
+
+	dma->chan = dma_request_chan(dev, chan_name);
+	if (IS_ERR(dma->chan)) {
+		ret = PTR_ERR(dma->chan);
+		goto err_unclaim;
+	}
+
+	/* Alloc and map bounce buffers */
+	for (dma->buf_count = 0; dma->buf_count < buf_count; dma->buf_count++) {
+		struct dma_buf_info *dbi = &dma->bufs[dma->buf_count];
+
+		dbi->buf = dma_alloc_coherent(dma->chan->device->dev, buf_size,
+					      &dbi->dma_addr, GFP_KERNEL);
+		if (!dbi->buf) {
+			ret = -ENOMEM;
+			goto err_dma_free;
+		}
+		sg_init_table(&dbi->sgl, 1);
+		sg_dma_address(&dbi->sgl) = dbi->dma_addr;
+	}
+
+	fifo_addr = pio->phys_addr;
+	fifo_addr += sm * (RP1_PIO_FIFO_TX1 - RP1_PIO_FIFO_TX0);
+	fifo_addr += (dir == RP1_PIO_DIR_TO_SM) ? RP1_PIO_FIFO_TX0 : RP1_PIO_FIFO_RX0;
+
+	config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+	config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+	config.src_addr = fifo_addr;
+	config.dst_addr = fifo_addr;
+	config.direction = (dir == RP1_PIO_DIR_TO_SM) ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
+	dma_caps.max_burst = 4;
+	dma_get_slave_caps(dma->chan, &dma_caps);
+	if (dir == RP1_PIO_DIR_TO_SM)
+		config.dst_maxburst = dma_caps.max_burst;
+	else
+		config.src_maxburst = dma_caps.max_burst;
+
+	ret = dmaengine_slave_config(dma->chan, &config);
+	if (ret)
+		goto err_dma_free;
+
+	set_dmactrl_args.sm = sm;
+	set_dmactrl_args.is_tx = (dir == RP1_PIO_DIR_TO_SM);
+	set_dmactrl_args.ctrl = RP1_PIO_DMACTRL_DEFAULT;
+	if (dir == RP1_PIO_DIR_FROM_SM)
+		set_dmactrl_args.ctrl = (RP1_PIO_DMACTRL_DEFAULT & ~0x1f) | 1;
+
+	ret = rp1_pio_sm_set_dmactrl(client, &set_dmactrl_args);
+	if (ret)
+		goto err_dma_free;
+
+	return 0;
+
+err_dma_free:
+	rp1_pio_sm_dma_free(dev, dma);
+
+err_unclaim:
+	spin_lock(&pio->lock);
+	client->claimed_dmas &= ~dma_mask;
+	pio->claimed_dmas &= ~dma_mask;
+	spin_unlock(&pio->lock);
+
+	return ret;
+}
+
+static int rp1_pio_sm_config_xfer_user(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_config_xfer_args *args = param;
+
+	return rp1_pio_sm_config_xfer_internal(client, args->sm, args->dir,
+					       args->buf_size, args->buf_count);
+}
+
+static int rp1_pio_sm_config_xfer32_user(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_config_xfer32_args *args = param;
+
+	return rp1_pio_sm_config_xfer_internal(client, args->sm, args->dir,
+					       args->buf_size, args->buf_count);
+}
+
+static int rp1_pio_sm_tx_user(struct rp1_pio_device *pio, struct dma_info *dma,
+				  const void __user *userbuf, size_t bytes)
+{
+	struct platform_device *pdev = pio->pdev;
+	struct dma_async_tx_descriptor *desc;
+	struct device *dev = &pdev->dev;
+	int ret = 0;
+
+	/* Clean the slate - we're running synchronously */
+	dma->head_idx = 0;
+	dma->tail_idx = 0;
+
+	while (bytes > 0) {
+		size_t copy_bytes = min(bytes, dma->buf_size);
+		struct dma_buf_info *dbi;
+
+		/* grab the next free buffer, waiting if they're all full */
+		if (dma->head_idx - dma->tail_idx == dma->buf_count) {
+			if (down_timeout(&dma->buf_sem,
+				msecs_to_jiffies(1000))) {
+				dev_err(dev, "DMA bounce timed out\n");
+				break;
+			}
+			dma->tail_idx++;
+		}
+
+		dbi = &dma->bufs[dma->head_idx % dma->buf_count];
+
+		sg_dma_len(&dbi->sgl) = copy_bytes;
+
+		ret = copy_from_user(dbi->buf, userbuf, copy_bytes);
+		if (ret < 0)
+			break;
+
+		userbuf += copy_bytes;
+
+		desc = dmaengine_prep_slave_sg(dma->chan, &dbi->sgl, 1,
+					       DMA_MEM_TO_DEV,
+					       DMA_PREP_INTERRUPT | DMA_CTRL_ACK |
+					       DMA_PREP_FENCE);
+		if (!desc) {
+			dev_err(dev, "DMA preparation failed\n");
+			ret = -EIO;
+			break;
+		}
+
+		desc->callback = rp1_pio_sm_dma_callback;
+		desc->callback_param = dma;
+
+		/* Submit the buffer - the callback will kick the semaphore */
+		ret = dmaengine_submit(desc);
+		if (ret < 0)
+			break;
+		ret = 0;
+
+		dma_async_issue_pending(dma->chan);
+
+		dma->head_idx++;
+		bytes -= copy_bytes;
+	}
+
+	/* Block for completion */
+	while (dma->tail_idx != dma->head_idx) {
+		if (down_timeout(&dma->buf_sem, msecs_to_jiffies(1000))) {
+			dev_err(dev, "DMA wait timed out\n");
+			ret = -ETIMEDOUT;
+			break;
+		}
+		dma->tail_idx++;
+	}
+
+	return ret;
+}
+
+static int rp1_pio_sm_rx_user(struct rp1_pio_device *pio, struct dma_info *dma,
+				  void __user *userbuf, size_t bytes)
+{
+	struct platform_device *pdev = pio->pdev;
+	struct dma_async_tx_descriptor *desc;
+	struct device *dev = &pdev->dev;
+	int ret = 0;
+
+	/* Clean the slate - we're running synchronously */
+	dma->head_idx = 0;
+	dma->tail_idx = 0;
+
+	while (bytes || dma->tail_idx != dma->head_idx) {
+		size_t copy_bytes = min(bytes, dma->buf_size);
+		struct dma_buf_info *dbi;
+
+		/*
+		 * wait for the next RX to complete if all the buffers are
+		 * outstanding or we're finishing up.
+		 */
+		if (!bytes || dma->head_idx - dma->tail_idx == dma->buf_count) {
+			if (down_timeout(&dma->buf_sem,
+				msecs_to_jiffies(1000))) {
+				dev_err(dev, "DMA wait timed out\n");
+				ret = -ETIMEDOUT;
+				break;
+			}
+
+			dbi = &dma->bufs[dma->tail_idx++ % dma->buf_count];
+			ret = copy_to_user(userbuf, dbi->buf, sg_dma_len(&dbi->sgl));
+			if (ret < 0)
+				break;
+			userbuf += sg_dma_len(&dbi->sgl);
+
+			if (!bytes)
+				continue;
+		}
+
+		dbi = &dma->bufs[dma->head_idx % dma->buf_count];
+		sg_dma_len(&dbi->sgl) = copy_bytes;
+		desc = dmaengine_prep_slave_sg(dma->chan, &dbi->sgl, 1,
+					       DMA_DEV_TO_MEM,
+					       DMA_PREP_INTERRUPT | DMA_CTRL_ACK |
+					       DMA_PREP_FENCE);
+		if (!desc) {
+			dev_err(dev, "DMA preparation failed\n");
+			ret = -EIO;
+			break;
+		}
+
+		desc->callback = rp1_pio_sm_dma_callback;
+		desc->callback_param = dma;
+
+		/* Submit the buffer - the callback will kick the semaphore */
+		ret = dmaengine_submit(desc);
+		if (ret < 0)
+			break;
+
+		dma->head_idx++;
+		dma_async_issue_pending(dma->chan);
+
+		bytes -= copy_bytes;
+	}
+
+	return ret;
+}
+
+static int rp1_pio_sm_xfer_data32_user(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_xfer_data32_args *args = param;
+	struct rp1_pio_device *pio = client->pio;
+	struct dma_info *dma;
+
+	if (args->sm >= RP1_PIO_SMS_COUNT || args->dir >= RP1_PIO_DIR_COUNT ||
+	    !args->data_bytes || !args->data)
+		return -EINVAL;
+
+	dma = &pio->dma_configs[args->sm][args->dir];
+
+	if (args->dir == RP1_PIO_DIR_TO_SM)
+		return rp1_pio_sm_tx_user(pio, dma, args->data, args->data_bytes);
+	else
+		return rp1_pio_sm_rx_user(pio, dma, args->data, args->data_bytes);
+}
+
+static int rp1_pio_sm_xfer_data_user(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_xfer_data_args *args = param;
+	struct rp1_pio_sm_xfer_data32_args args32;
+
+	args32.sm = args->sm;
+	args32.dir = args->dir;
+	args32.data_bytes = args->data_bytes;
+	args32.data = args->data;
+
+	return rp1_pio_sm_xfer_data32_user(client, &args32);
+}
+
+int rp1_pio_sm_config_xfer(struct rp1_pio_client *client, uint sm, uint dir,
+			      uint buf_size, uint buf_count)
+{
+	return rp1_pio_sm_config_xfer_internal(client, sm, dir, buf_size, buf_count);
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_config_xfer);
+
+int rp1_pio_sm_xfer_data(struct rp1_pio_client *client, uint sm, uint dir,
+				    uint data_bytes, void *data, dma_addr_t dma_addr,
+				    void (*callback)(void *param), void *param)
+{
+	struct rp1_pio_device *pio = client->pio;
+	struct platform_device *pdev = pio->pdev;
+	struct dma_async_tx_descriptor *desc;
+	struct dma_xfer_state *dxs = NULL;
+	struct device *dev = &pdev->dev;
+	struct dma_buf_info *dbi = NULL;
+	struct scatterlist sg;
+	struct dma_info *dma;
+	int ret = 0;
+
+	if (sm >= RP1_PIO_SMS_COUNT || dir >= RP1_PIO_DIR_COUNT)
+		return -EINVAL;
+
+	dma = &pio->dma_configs[sm][dir];
+
+	if (!dma_addr) {
+		dxs = kmalloc(sizeof(*dxs), GFP_KERNEL);
+		dxs->dma = dma;
+		dxs->callback = callback;
+		dxs->callback_param = param;
+		callback = rp1_pio_sm_kernel_dma_callback;
+		param = dxs;
+
+		if (!dma->buf_count || data_bytes > dma->buf_size)
+			return -EINVAL;
+
+		/* Grab a dma buffer */
+		if (dma->head_idx - dma->tail_idx == dma->buf_count) {
+			if (down_timeout(&dma->buf_sem, msecs_to_jiffies(1000))) {
+				dev_err(dev, "DMA wait timed out\n");
+				return -ETIMEDOUT;
+			}
+		}
+
+		dbi = &dma->bufs[dma->head_idx % dma->buf_count];
+		dma_addr = dbi->dma_addr;
+
+		if (dir == PIO_DIR_TO_SM)
+			memcpy(dbi->buf, data, data_bytes);
+	}
+
+	sg_init_table(&sg, 1);
+	sg_dma_address(&sg) = dma_addr;
+	sg_dma_len(&sg) = data_bytes;
+
+	desc = dmaengine_prep_slave_sg(dma->chan, &sg, 1,
+					   (dir == PIO_DIR_TO_SM) ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM,
+					   DMA_PREP_INTERRUPT | DMA_CTRL_ACK |
+					   DMA_PREP_FENCE);
+	if (!desc) {
+		dev_err(dev, "DMA preparation failed\n");
+		return -EIO;
+	}
+
+	desc->callback = callback;
+	desc->callback_param = param;
+
+	ret = dmaengine_submit(desc);
+	if (ret < 0) {
+		dev_err(dev, "dmaengine_submit failed (%d)\n", ret);
+		return ret;
+	}
+
+	dma->head_idx++;
+	dma_async_issue_pending(dma->chan);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_xfer_data);
+
+struct handler_info {
+	const char *name;
+	int (*func)(struct rp1_pio_client *client, void *param);
+	int argsize;
+} ioctl_handlers[] = {
+	HANDLER(SM_CONFIG_XFER, sm_config_xfer_user),
+	HANDLER(SM_XFER_DATA, sm_xfer_data_user),
+	HANDLER(SM_XFER_DATA32, sm_xfer_data32_user),
+	HANDLER(SM_CONFIG_XFER32, sm_config_xfer32_user),
+
+	HANDLER(CAN_ADD_PROGRAM, can_add_program),
+	HANDLER(ADD_PROGRAM, add_program),
+	HANDLER(REMOVE_PROGRAM, remove_program),
+	HANDLER(CLEAR_INSTR_MEM, clear_instr_mem),
+
+	HANDLER(SM_CLAIM, sm_claim),
+	HANDLER(SM_UNCLAIM, sm_unclaim),
+	HANDLER(SM_IS_CLAIMED, sm_is_claimed),
+
+	HANDLER(SM_INIT, sm_init),
+	HANDLER(SM_SET_CONFIG, sm_set_config),
+	HANDLER(SM_EXEC, sm_exec),
+	HANDLER(SM_CLEAR_FIFOS, sm_clear_fifos),
+	HANDLER(SM_SET_CLKDIV, sm_set_clkdiv),
+	HANDLER(SM_SET_PINS, sm_set_pins),
+	HANDLER(SM_SET_PINDIRS, sm_set_pindirs),
+	HANDLER(SM_SET_ENABLED, sm_set_enabled),
+	HANDLER(SM_RESTART, sm_restart),
+	HANDLER(SM_CLKDIV_RESTART, sm_clkdiv_restart),
+	HANDLER(SM_ENABLE_SYNC, sm_enable_sync),
+	HANDLER(SM_PUT, sm_put),
+	HANDLER(SM_GET, sm_get),
+	HANDLER(SM_SET_DMACTRL, sm_set_dmactrl),
+	HANDLER(SM_FIFO_STATE, sm_fifo_state),
+	HANDLER(SM_DRAIN_TX, sm_drain_tx),
+
+	HANDLER(GPIO_INIT, gpio_init),
+	HANDLER(GPIO_SET_FUNCTION, gpio_set_function),
+	HANDLER(GPIO_SET_PULLS, gpio_set_pulls),
+	HANDLER(GPIO_SET_OUTOVER, gpio_set_outover),
+	HANDLER(GPIO_SET_INOVER, gpio_set_inover),
+	HANDLER(GPIO_SET_OEOVER, gpio_set_oeover),
+	HANDLER(GPIO_SET_INPUT_ENABLED, gpio_set_input_enabled),
+	HANDLER(GPIO_SET_DRIVE_STRENGTH, gpio_set_drive_strength),
+
+	HANDLER(READ_HW, read_hw),
+	HANDLER(WRITE_HW, write_hw),
+};
+
+struct rp1_pio_client *rp1_pio_open(void)
+{
+	struct rp1_pio_client *client;
+
+	if (!g_pio)
+		return ERR_PTR(-ENOENT);
+
+	client = kzalloc(sizeof(*client), GFP_KERNEL);
+	if (!client)
+		return ERR_PTR(-ENOMEM);
+
+	client->pio = g_pio;
+
+	return client;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_open);
+
+void rp1_pio_close(struct rp1_pio_client *client)
+{
+	struct rp1_pio_device *pio = client->pio;
+	uint claimed_dmas = client->claimed_dmas;
+	int i;
+
+	/* Free any allocated resources */
+
+	for (i = 0; claimed_dmas; i++) {
+		uint mask = (1 << i);
+
+		if (claimed_dmas & mask) {
+			struct dma_info *dma = &pio->dma_configs[i >> 1][i & 1];
+
+			claimed_dmas &= ~mask;
+			rp1_pio_sm_dma_free(&pio->pdev->dev, dma);
+		}
+	}
+
+	spin_lock(&pio->lock);
+	pio->claimed_dmas &= ~client->claimed_dmas;
+	spin_unlock(&pio->lock);
+
+	if (client->claimed_sms) {
+		struct rp1_pio_sm_set_enabled_args se_args = {
+			.mask = client->claimed_sms, .enable = 0
+		};
+		struct rp1_pio_sm_claim_args uc_args = {
+			.mask = client->claimed_sms
+		};
+
+		rp1_pio_sm_set_enabled(client, &se_args);
+		rp1_pio_sm_unclaim(client, &uc_args);
+	}
+
+	if (client->claimed_instrs)
+		rp1_pio_remove_instrs(pio, client->claimed_instrs);
+
+	/* Reinitialise the SM? */
+
+	kfree(client);
+}
+EXPORT_SYMBOL_GPL(rp1_pio_close);
+
+void rp1_pio_set_error(struct rp1_pio_client *client, int err)
+{
+	client->error = err;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_set_error);
+
+int rp1_pio_get_error(const struct rp1_pio_client *client)
+{
+	return client->error;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_get_error);
+
+void rp1_pio_clear_error(struct rp1_pio_client *client)
+{
+	client->error = 0;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_clear_error);
+
+static int rp1_pio_file_open(struct inode *inode, struct file *filp)
+{
+	struct rp1_pio_client *client;
+
+	client = rp1_pio_open();
+	if (IS_ERR(client))
+		return PTR_ERR(client);
+
+	filp->private_data = client;
+
+	return 0;
+}
+
+static int rp1_pio_file_release(struct inode *inode, struct file *filp)
+{
+	struct rp1_pio_client *client = filp->private_data;
+
+	rp1_pio_close(client);
+
+	return 0;
+}
+
+static long rp1_pio_ioctl(struct file *filp, unsigned int ioctl_num,
+			  unsigned long ioctl_param)
+{
+	struct rp1_pio_client *client = filp->private_data;
+	struct device *dev = &client->pio->pdev->dev;
+	void __user *argp = (void __user *)ioctl_param;
+	int nr = _IOC_NR(ioctl_num);
+	int sz = _IOC_SIZE(ioctl_num);
+	struct handler_info *hdlr = &ioctl_handlers[nr];
+	uint32_t argbuf[MAX_ARG_SIZE/sizeof(uint32_t)];
+	int ret;
+
+	if (nr >= ARRAY_SIZE(ioctl_handlers) || !hdlr->func) {
+		dev_err(dev, "unknown ioctl: %x\n", ioctl_num);
+		return -EOPNOTSUPP;
+	}
+
+	if (sz != hdlr->argsize) {
+		dev_err(dev, "wrong %s argsize (expected %d, got %d)\n",
+			hdlr->name, hdlr->argsize, sz);
+		return -EINVAL;
+	}
+
+	if (copy_from_user(argbuf, argp, sz))
+		return -EFAULT;
+
+	ret = (hdlr->func)(client, argbuf);
+	dev_dbg(dev, "%s: %s -> %d\n", __func__, hdlr->name, ret);
+	if (ret > 0) {
+		if (copy_to_user(argp, argbuf, ret))
+			ret = -EFAULT;
+	}
+
+	return ret;
+}
+
+#ifdef CONFIG_COMPAT
+
+struct rp1_pio_sm_xfer_data_args_compat {
+	uint16_t sm;
+	uint16_t dir;
+	uint16_t data_bytes;
+	compat_uptr_t data;
+};
+
+struct rp1_pio_sm_xfer_data32_args_compat {
+	uint16_t sm;
+	uint16_t dir;
+	uint32_t data_bytes;
+	compat_uptr_t data;
+};
+
+struct rp1_access_hw_args_compat {
+	uint32_t addr;
+	uint32_t len;
+	compat_uptr_t data;
+};
+
+#define PIO_IOC_SM_XFER_DATA_COMPAT \
+	_IOW(PIO_IOC_MAGIC, 1, struct rp1_pio_sm_xfer_data_args_compat)
+#define PIO_IOC_SM_XFER_DATA32_COMPAT \
+	_IOW(PIO_IOC_MAGIC, 2, struct rp1_pio_sm_xfer_data32_args_compat)
+#define PIO_IOC_READ_HW_COMPAT _IOW(PIO_IOC_MAGIC, 8, struct rp1_access_hw_args_compat)
+#define PIO_IOC_WRITE_HW_COMPAT _IOW(PIO_IOC_MAGIC, 9, struct rp1_access_hw_args_compat)
+
+static long rp1_pio_compat_ioctl(struct file *filp, unsigned int ioctl_num,
+				 unsigned long ioctl_param)
+{
+	struct rp1_pio_client *client = filp->private_data;
+
+	switch (ioctl_num) {
+	case PIO_IOC_SM_XFER_DATA_COMPAT:
+	{
+		struct rp1_pio_sm_xfer_data_args_compat compat_param;
+		struct rp1_pio_sm_xfer_data_args param;
+
+		if (copy_from_user(&compat_param, compat_ptr(ioctl_param), sizeof(compat_param)))
+			return -EFAULT;
+		param.sm = compat_param.sm;
+		param.dir = compat_param.dir;
+		param.data_bytes = compat_param.data_bytes;
+		param.data = compat_ptr(compat_param.data);
+		return rp1_pio_sm_xfer_data_user(client, &param);
+	}
+	case PIO_IOC_SM_XFER_DATA32_COMPAT:
+	{
+		struct rp1_pio_sm_xfer_data32_args_compat compat_param;
+		struct rp1_pio_sm_xfer_data32_args param;
+
+		if (copy_from_user(&compat_param, compat_ptr(ioctl_param), sizeof(compat_param)))
+			return -EFAULT;
+		param.sm = compat_param.sm;
+		param.dir = compat_param.dir;
+		param.data_bytes = compat_param.data_bytes;
+		param.data = compat_ptr(compat_param.data);
+		return rp1_pio_sm_xfer_data32_user(client, &param);
+	}
+
+	case PIO_IOC_READ_HW_COMPAT:
+	case PIO_IOC_WRITE_HW_COMPAT:
+	{
+		struct rp1_access_hw_args_compat compat_param;
+		struct rp1_access_hw_args param;
+
+		if (copy_from_user(&compat_param, compat_ptr(ioctl_param), sizeof(compat_param)))
+			return -EFAULT;
+		param.addr = compat_param.addr;
+		param.len = compat_param.len;
+		param.data = compat_ptr(compat_param.data);
+		if (ioctl_num == PIO_IOC_READ_HW_COMPAT)
+			return rp1_pio_read_hw(client, &param);
+		else
+			return rp1_pio_write_hw(client, &param);
+	}
+	default:
+		return rp1_pio_ioctl(filp, ioctl_num, ioctl_param);
+	}
+}
+#else
+#define rp1_pio_compat_ioctl NULL
+#endif
+
+const struct file_operations rp1_pio_fops = {
+	.owner =	THIS_MODULE,
+	.open =		rp1_pio_file_open,
+	.release =	rp1_pio_file_release,
+	.unlocked_ioctl = rp1_pio_ioctl,
+	.compat_ioctl = rp1_pio_compat_ioctl,
+};
+
+static int rp1_pio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *ioresource;
+	struct rp1_pio_device *pio;
+	struct rp1_firmware *fw;
+	uint32_t op_count = 0;
+	uint32_t op_base = 0;
+	struct device *cdev;
+	char dev_name[16];
+	void *p;
+	int ret;
+	int i;
+
+	/* Run-time check for a build-time misconfiguration */
+	for (i = 0; i < ARRAY_SIZE(ioctl_handlers); i++) {
+		struct handler_info *hdlr = &ioctl_handlers[i];
+
+		if (WARN_ON(hdlr->argsize > MAX_ARG_SIZE))
+			return -EINVAL;
+	}
+
+	pdev->id = of_alias_get_id(pdev->dev.of_node, "pio");
+	if (pdev->id < 0)
+		return dev_err_probe(dev, pdev->id, "alias is missing\n");
+
+	fw = devm_rp1_firmware_get(dev, dev->of_node);
+	if (!fw)
+		return dev_err_probe(dev, -EPROBE_DEFER, "failed to find RP1 firmware driver\n");
+	if (IS_ERR(fw)) {
+		dev_warn(dev, "failed to contact RP1 firmware\n");
+		return PTR_ERR(fw);
+	}
+	ret = rp1_firmware_get_feature(fw, FOURCC_PIO, &op_base, &op_count);
+	if (ret < 0)
+		return ret;
+
+	pio = devm_kzalloc(&pdev->dev, sizeof(*pio), GFP_KERNEL);
+	if (!pio)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, pio);
+	pio->fw_pio_base = op_base;
+	pio->fw_pio_count = op_count;
+	pio->pdev = pdev;
+	pio->fw = fw;
+	spin_lock_init(&pio->lock);
+	mutex_init(&pio->instr_mutex);
+
+	p = devm_platform_get_and_ioremap_resource(pdev, 0, &ioresource);
+	if (IS_ERR(p))
+		return PTR_ERR(p);
+
+	pio->phys_addr = ioresource->start;
+
+	ret = alloc_chrdev_region(&pio->dev_num, 0, 1, DRIVER_NAME);
+	if (ret < 0) {
+		dev_err(dev, "alloc_chrdev_region failed (rc=%d)\n", ret);
+		goto out_err;
+	}
+
+	pio->dev_class = class_create(DRIVER_NAME);
+	if (IS_ERR(pio->dev_class)) {
+		ret = PTR_ERR(pio->dev_class);
+		dev_err(dev, "class_create failed (err %d)\n", ret);
+		goto out_unregister;
+	}
+
+	cdev_init(&pio->cdev, &rp1_pio_fops);
+	ret = cdev_add(&pio->cdev, pio->dev_num, 1);
+	if (ret) {
+		dev_err(dev, "cdev_add failed (err %d)\n", ret);
+		goto out_class_destroy;
+	}
+
+	sprintf(dev_name, "pio%d", pdev->id);
+	cdev = device_create(pio->dev_class, NULL, pio->dev_num, NULL, dev_name);
+	if (IS_ERR(cdev)) {
+		ret = PTR_ERR(cdev);
+		dev_err(dev, "%s: device_create failed (err %d)\n", __func__, ret);
+		goto out_cdev_del;
+	}
+
+	g_pio = pio;
+
+	dev_info(dev, "Created instance as %s\n", dev_name);
+	return 0;
+
+out_cdev_del:
+	cdev_del(&pio->cdev);
+
+out_class_destroy:
+	class_destroy(pio->dev_class);
+
+out_unregister:
+	unregister_chrdev_region(pio->dev_num, 1);
+
+out_err:
+	return ret;
+}
+
+static void rp1_pio_remove(struct platform_device *pdev)
+{
+	struct rp1_pio_device *pio = platform_get_drvdata(pdev);
+
+	/* There should be no clients */
+
+	if (g_pio == pio)
+		g_pio = NULL;
+
+	device_destroy(pio->dev_class, pio->dev_num);
+	cdev_del(&pio->cdev);
+	class_destroy(pio->dev_class);
+	unregister_chrdev_region(pio->dev_num, 1);
+}
+
+static const struct of_device_id rp1_pio_ids[] = {
+	{ .compatible = "raspberrypi,rp1-pio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, rp1_pio_ids);
+
+static struct platform_driver rp1_pio_driver = {
+	.driver	= {
+		.name		= "rp1-pio",
+		.of_match_table	= of_match_ptr(rp1_pio_ids),
+	},
+	.probe		= rp1_pio_probe,
+	.remove	= rp1_pio_remove,
+	.shutdown	= rp1_pio_remove,
+};
+
+module_platform_driver(rp1_pio_driver);
+
+MODULE_DESCRIPTION("PIO controller driver for Raspberry Pi RP1");
+MODULE_AUTHOR("Phil Elwell");
+MODULE_LICENSE("GPL");
--- /dev/null
+++ b/include/linux/pio_instructions.h
@@ -0,0 +1,481 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
+ */
+
+#ifndef _HARDWARE_PIO_INSTRUCTIONS_H
+#define _HARDWARE_PIO_INSTRUCTIONS_H
+
+/** \brief PIO instruction encoding
+ *  \defgroup pio_instructions pio_instructions
+ *  \ingroup hardware_pio
+ *
+ * Functions for generating PIO instruction encodings programmatically. In debug builds
+ *`PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS` can be set to 1 to enable validation of encoding function
+ * parameters.
+ *
+ * For fuller descriptions of the instructions in question see the "RP2040 Datasheet"
+ */
+
+// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS, Enable/disable assertions in the PIO instructions, type=bool, default=0, group=pio_instructions
+#ifndef PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS
+#define PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS 0
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum pio_instr_bits {
+    pio_instr_bits_jmp = 0x0000,
+    pio_instr_bits_wait = 0x2000,
+    pio_instr_bits_in = 0x4000,
+    pio_instr_bits_out = 0x6000,
+    pio_instr_bits_push = 0x8000,
+    pio_instr_bits_pull = 0x8080,
+    pio_instr_bits_mov = 0xa000,
+    pio_instr_bits_irq = 0xc000,
+    pio_instr_bits_set = 0xe000,
+};
+
+#ifndef NDEBUG
+#define _PIO_INVALID_IN_SRC    0x08u
+#define _PIO_INVALID_OUT_DEST 0x10u
+#define _PIO_INVALID_SET_DEST 0x20u
+#define _PIO_INVALID_MOV_SRC  0x40u
+#define _PIO_INVALID_MOV_DEST 0x80u
+#else
+#define _PIO_INVALID_IN_SRC    0u
+#define _PIO_INVALID_OUT_DEST 0u
+#define _PIO_INVALID_SET_DEST 0u
+#define _PIO_INVALID_MOV_SRC  0u
+#define _PIO_INVALID_MOV_DEST 0u
+#endif
+
+/*! \brief Enumeration of values to pass for source/destination args for instruction encoding functions
+ *  \ingroup pio_instructions
+ *
+ * \note Not all values are suitable for all functions. Validity is only checked in debug mode when
+ * `PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS` is 1
+ */
+enum pio_src_dest {
+    pio_pins = 0u,
+    pio_x = 1u,
+    pio_y = 2u,
+    pio_null = 3u | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_DEST,
+    pio_pindirs = 4u | _PIO_INVALID_IN_SRC | _PIO_INVALID_MOV_SRC | _PIO_INVALID_MOV_DEST,
+    pio_exec_mov = 4u | _PIO_INVALID_IN_SRC | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC,
+    pio_status = 5u | _PIO_INVALID_IN_SRC | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_DEST,
+    pio_pc = 5u | _PIO_INVALID_IN_SRC | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC,
+    pio_isr = 6u | _PIO_INVALID_SET_DEST,
+    pio_osr = 7u | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST,
+    pio_exec_out = 7u | _PIO_INVALID_IN_SRC | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC | _PIO_INVALID_MOV_DEST,
+};
+
+static inline uint _pio_major_instr_bits(uint instr) {
+    return instr & 0xe000u;
+}
+
+static inline uint _pio_encode_instr_and_args(enum pio_instr_bits instr_bits, uint arg1, uint arg2) {
+    valid_params_if(PIO_INSTRUCTIONS, arg1 <= 0x7);
+#if PARAM_ASSERTIONS_ENABLED(PIO_INSTRUCTIONS)
+    uint32_t major = _pio_major_instr_bits(instr_bits);
+    if (major == pio_instr_bits_in || major == pio_instr_bits_out) {
+        assert(arg2 && arg2 <= 32);
+    } else {
+        assert(arg2 <= 31);
+    }
+#endif
+    return instr_bits | (arg1 << 5u) | (arg2 & 0x1fu);
+}
+
+static inline uint _pio_encode_instr_and_src_dest(enum pio_instr_bits instr_bits, enum pio_src_dest dest, uint value) {
+    return _pio_encode_instr_and_args(instr_bits, dest & 7u, value);
+}
+
+/*! \brief Encode just the delay slot bits of an instruction
+ *  \ingroup pio_instructions
+ *
+ * \note This function does not return a valid instruction encoding; instead it returns an encoding of the delay
+ * slot suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when
+ * combining the results of this function with the results of \ref pio_encode_sideset and \ref pio_encode_sideset_opt
+ * as they share the same bits within the instruction encoding.
+ *
+ * \param cycles the number of cycles 0-31 (or less if side set is being used)
+ * \return the delay slot bits to be ORed with an instruction encoding
+ */
+static inline uint pio_encode_delay(uint cycles) {
+    // note that the maximum cycles will be smaller if sideset_bit_count > 0
+    valid_params_if(PIO_INSTRUCTIONS, cycles <= 0x1f);
+    return cycles << 8u;
+}
+
+/*! \brief Encode just the side set bits of an instruction (in non optional side set mode)
+ *  \ingroup pio_instructions
+ *
+ * \note This function does not return a valid instruction encoding; instead it returns an encoding of the side set bits
+ * suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when
+ * combining the results of this function with the results of \ref pio_encode_delay as they share the same bits
+ * within the instruction encoding.
+ *
+ * \param sideset_bit_count number of side set bits as would be specified via `.sideset` in pioasm
+ * \param value the value to sideset on the pins
+ * \return the side set bits to be ORed with an instruction encoding
+ */
+static inline uint pio_encode_sideset(uint sideset_bit_count, uint value) {
+    valid_params_if(PIO_INSTRUCTIONS, sideset_bit_count >= 1 && sideset_bit_count <= 5);
+    valid_params_if(PIO_INSTRUCTIONS, value <= ((1u << sideset_bit_count) - 1));
+    return value << (13u - sideset_bit_count);
+}
+
+/*! \brief Encode just the side set bits of an instruction (in optional -`opt` side set mode)
+ *  \ingroup pio_instructions
+ *
+ * \note This function does not return a valid instruction encoding; instead it returns an encoding of the side set bits
+ * suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when
+ * combining the results of this function with the results of \ref pio_encode_delay as they share the same bits
+ * within the instruction encoding.
+ *
+ * \param sideset_bit_count number of side set bits as would be specified via `.sideset <n> opt` in pioasm
+ * \param value the value to sideset on the pins
+ * \return the side set bits to be ORed with an instruction encoding
+ */
+static inline uint pio_encode_sideset_opt(uint sideset_bit_count, uint value) {
+    valid_params_if(PIO_INSTRUCTIONS, sideset_bit_count >= 1 && sideset_bit_count <= 4);
+    valid_params_if(PIO_INSTRUCTIONS, value <= ((1u << sideset_bit_count) - 1));
+    return 0x1000u | value << (12u - sideset_bit_count);
+}
+
+/*! \brief Encode an unconditional JMP instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 0, addr);
+}
+
+/*! \brief Encode a conditional JMP if scratch X zero instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP !X <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp_not_x(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 1, addr);
+}
+
+/*! \brief Encode a conditional JMP if scratch X non-zero (and post-decrement X) instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP X-- <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp_x_dec(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 2, addr);
+}
+
+/*! \brief Encode a conditional JMP if scratch Y zero instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP !Y <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp_not_y(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 3, addr);
+}
+
+/*! \brief Encode a conditional JMP if scratch Y non-zero (and post-decrement Y) instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP Y-- <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp_y_dec(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 4, addr);
+}
+
+/*! \brief Encode a conditional JMP if scratch X not equal scratch Y instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP X!=Y <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp_x_ne_y(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 5, addr);
+}
+
+/*! \brief Encode a conditional JMP if input pin high instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP PIN <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp_pin(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 6, addr);
+}
+
+/*! \brief Encode a conditional JMP if output shift register not empty instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP !OSRE <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp_not_osre(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 7, addr);
+}
+
+static inline uint _pio_encode_irq(bool relative, uint irq) {
+    valid_params_if(PIO_INSTRUCTIONS, irq <= 7);
+    return (relative ? 0x10u : 0x0u) | irq;
+}
+
+/*! \brief Encode a WAIT for GPIO pin instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `WAIT <polarity> GPIO <gpio>`
+ *
+ * \param polarity true for `WAIT 1`, false for `WAIT 0`
+ * \param gpio The real GPIO number 0-31
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_wait_gpio(bool polarity, uint gpio) {
+    return _pio_encode_instr_and_args(pio_instr_bits_wait, 0u | (polarity ? 4u : 0u), gpio);
+}
+
+/*! \brief Encode a WAIT for pin instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `WAIT <polarity> PIN <pin>`
+ *
+ * \param polarity true for `WAIT 1`, false for `WAIT 0`
+ * \param pin The pin number 0-31 relative to the executing SM's input pin mapping
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_wait_pin(bool polarity, uint pin) {
+    return _pio_encode_instr_and_args(pio_instr_bits_wait, 1u | (polarity ? 4u : 0u), pin);
+}
+
+/*! \brief Encode a WAIT for IRQ instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `WAIT <polarity> IRQ <irq> <relative>`
+ *
+ * \param polarity true for `WAIT 1`, false for `WAIT 0`
+ * \param relative true for a `WAIT IRQ <irq> REL`, false for regular `WAIT IRQ <irq>`
+ * \param irq the irq number 0-7
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_wait_irq(bool polarity, bool relative, uint irq) {
+    valid_params_if(PIO_INSTRUCTIONS, irq <= 7);
+    return _pio_encode_instr_and_args(pio_instr_bits_wait, 2u | (polarity ? 4u : 0u), _pio_encode_irq(relative, irq));
+}
+
+/*! \brief Encode an IN instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `IN <src>, <count>`
+ *
+ * \param src The source to take data from
+ * \param count The number of bits 1-32
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_in(enum pio_src_dest src, uint count) {
+    valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_IN_SRC));
+    return _pio_encode_instr_and_src_dest(pio_instr_bits_in, src, count);
+}
+
+/*! \brief Encode an OUT instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `OUT <src>, <count>`
+ *
+ * \param dest The destination to write data to
+ * \param count The number of bits 1-32
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_out(enum pio_src_dest dest, uint count) {
+    valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_OUT_DEST));
+    return _pio_encode_instr_and_src_dest(pio_instr_bits_out, dest, count);
+}
+
+/*! \brief Encode a PUSH instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `PUSH <if_full>, <block>`
+ *
+ * \param if_full true for `PUSH IF_FULL ...`, false for `PUSH ...`
+ * \param block true for `PUSH ... BLOCK`, false for `PUSH ...`
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_push(bool if_full, bool block) {
+    return _pio_encode_instr_and_args(pio_instr_bits_push, (if_full ? 2u : 0u) | (block ? 1u : 0u), 0);
+}
+
+/*! \brief Encode a PULL instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `PULL <if_empty>, <block>`
+ *
+ * \param if_empty true for `PULL IF_EMPTY ...`, false for `PULL ...`
+ * \param block true for `PULL ... BLOCK`, false for `PULL ...`
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_pull(bool if_empty, bool block) {
+    return _pio_encode_instr_and_args(pio_instr_bits_pull, (if_empty ? 2u : 0u) | (block ? 1u : 0u), 0);
+}
+
+/*! \brief Encode a MOV instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `MOV <dest>, <src>`
+ *
+ * \param dest The destination to write data to
+ * \param src The source to take data from
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_mov(enum pio_src_dest dest, enum pio_src_dest src) {
+    valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST));
+    valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC));
+    return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, src & 7u);
+}
+
+/*! \brief Encode a MOV instruction with bit invert
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `MOV <dest>, ~<src>`
+ *
+ * \param dest The destination to write inverted data to
+ * \param src The source to take data from
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_mov_not(enum pio_src_dest dest, enum pio_src_dest src) {
+    valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST));
+    valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC));
+    return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, (1u << 3u) | (src & 7u));
+}
+
+/*! \brief Encode a MOV instruction with bit reverse
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `MOV <dest>, ::<src>`
+ *
+ * \param dest The destination to write bit reversed data to
+ * \param src The source to take data from
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_mov_reverse(enum pio_src_dest dest, enum pio_src_dest src) {
+    valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST));
+    valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC));
+    return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, (2u << 3u) | (src & 7u));
+}
+
+/*! \brief Encode a IRQ SET instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `IRQ SET <irq> <relative>`
+ *
+ * \param relative true for a `IRQ SET <irq> REL`, false for regular `IRQ SET <irq>`
+ * \param irq the irq number 0-7
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_irq_set(bool relative, uint irq) {
+    return _pio_encode_instr_and_args(pio_instr_bits_irq, 0, _pio_encode_irq(relative, irq));
+}
+
+/*! \brief Encode a IRQ WAIT instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `IRQ WAIT <irq> <relative>`
+ *
+ * \param relative true for a `IRQ WAIT <irq> REL`, false for regular `IRQ WAIT <irq>`
+ * \param irq the irq number 0-7
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_irq_wait(bool relative, uint irq) {
+    return _pio_encode_instr_and_args(pio_instr_bits_irq, 1, _pio_encode_irq(relative, irq));
+}
+
+/*! \brief Encode a IRQ CLEAR instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `IRQ CLEAR <irq> <relative>`
+ *
+ * \param relative true for a `IRQ CLEAR <irq> REL`, false for regular `IRQ CLEAR <irq>`
+ * \param irq the irq number 0-7
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_irq_clear(bool relative, uint irq) {
+    return _pio_encode_instr_and_args(pio_instr_bits_irq, 2, _pio_encode_irq(relative, irq));
+}
+
+/*! \brief Encode a SET instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `SET <dest>, <value>`
+ *
+ * \param dest The destination to apply the value to
+ * \param value The value 0-31
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_set(enum pio_src_dest dest, uint value) {
+    valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_SET_DEST));
+    return _pio_encode_instr_and_src_dest(pio_instr_bits_set, dest, value);
+}
+
+/*! \brief Encode a NOP instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `NOP` which is itself encoded as `MOV y, y`
+ *
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_nop(void) {
+    return pio_encode_mov(pio_y, pio_y);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
--- /dev/null
+++ b/include/linux/pio_rp1.h
@@ -0,0 +1,1019 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2024 Raspberry Pi Ltd.
+ * All rights reserved.
+ */
+
+#ifndef _PIO_RP1_H
+#define _PIO_RP1_H
+
+#include <uapi/misc/rp1_pio_if.h>
+
+#define PARAM_WARNINGS_ENABLED 1
+
+#ifdef DEBUG
+#define PARAM_WARNINGS_ENABLED 1
+#endif
+
+#ifndef PARAM_WARNINGS_ENABLED
+#define PARAM_WARNINGS_ENABLED 0
+#endif
+
+#define bad_params_if(client, test) \
+	({ bool f = (test); if (f && client) pio_set_error(client, -EINVAL); \
+		if (f && PARAM_WARNINGS_ENABLED) WARN_ON((test)); \
+		f; })
+
+#ifndef PARAM_ASSERTIONS_ENABLE_ALL
+#define PARAM_ASSERTIONS_ENABLE_ALL 0
+#endif
+
+#ifndef PARAM_ASSERTIONS_DISABLE_ALL
+#define PARAM_ASSERTIONS_DISABLE_ALL 0
+#endif
+
+#define PARAM_ASSERTIONS_ENABLED(x) \
+	((PARAM_ASSERTIONS_ENABLED_ ## x || PARAM_ASSERTIONS_ENABLE_ALL) && \
+	 !PARAM_ASSERTIONS_DISABLE_ALL)
+#define valid_params_if(x, test) ({if (PARAM_ASSERTIONS_ENABLED(x)) WARN_ON(test); })
+
+#include <linux/pio_instructions.h>
+
+#define NUM_PIO_STATE_MACHINES		4
+#define PIO_INSTRUCTION_COUNT		32
+#define PIO_ORIGIN_ANY			((uint)(~0))
+#define GPIOS_MASK			((1 << RP1_PIO_GPIO_COUNT) - 1)
+
+#define PICO_NO_HARDWARE		0
+
+#define pio0				pio_open_helper(0)
+
+#define PROC_PIO_SM0_PINCTRL_OUT_BASE_BITS	0x0000001f
+#define PROC_PIO_SM0_PINCTRL_OUT_BASE_LSB	0
+#define PROC_PIO_SM0_PINCTRL_OUT_COUNT_BITS	0x03f00000
+#define PROC_PIO_SM0_PINCTRL_OUT_COUNT_LSB	20
+#define PROC_PIO_SM0_PINCTRL_SET_BASE_BITS	0x000003e0
+#define PROC_PIO_SM0_PINCTRL_SET_BASE_LSB	5
+#define PROC_PIO_SM0_PINCTRL_SET_COUNT_BITS	0x1c000000
+#define PROC_PIO_SM0_PINCTRL_SET_COUNT_LSB	26
+#define PROC_PIO_SM0_PINCTRL_IN_BASE_BITS	0x000f8000
+#define PROC_PIO_SM0_PINCTRL_IN_BASE_LSB	15
+#define PROC_PIO_SM0_PINCTRL_SIDESET_BASE_BITS	0x00007c00
+#define PROC_PIO_SM0_PINCTRL_SIDESET_BASE_LSB	10
+#define PROC_PIO_SM0_PINCTRL_SIDESET_COUNT_BITS	0xe0000000
+#define PROC_PIO_SM0_PINCTRL_SIDESET_COUNT_LSB	29
+#define PROC_PIO_SM0_EXECCTRL_SIDE_EN_BITS	0x40000000
+#define PROC_PIO_SM0_EXECCTRL_SIDE_EN_LSB	30
+#define PROC_PIO_SM0_EXECCTRL_SIDE_PINDIR_BITS	0x20000000
+#define PROC_PIO_SM0_EXECCTRL_SIDE_PINDIR_LSB	29
+#define PROC_PIO_SM0_CLKDIV_INT_LSB		16
+#define PROC_PIO_SM0_CLKDIV_FRAC_LSB		8
+#define PROC_PIO_SM0_EXECCTRL_WRAP_TOP_BITS	0x0001f000
+#define PROC_PIO_SM0_EXECCTRL_WRAP_TOP_LSB	12
+#define PROC_PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS	0x00000f80
+#define PROC_PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB	7
+#define PROC_PIO_SM0_EXECCTRL_JMP_PIN_BITS	0x1f000000
+#define PROC_PIO_SM0_EXECCTRL_JMP_PIN_LSB	24
+#define PROC_PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_BITS	0x00040000
+#define PROC_PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_LSB	18
+#define PROC_PIO_SM0_SHIFTCTRL_AUTOPULL_BITS	0x00020000
+#define PROC_PIO_SM0_SHIFTCTRL_AUTOPULL_LSB	17
+#define PROC_PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS	0x00010000
+#define PROC_PIO_SM0_SHIFTCTRL_AUTOPUSH_LSB	16
+#define PROC_PIO_SM0_SHIFTCTRL_PUSH_THRESH_BITS	0x01f00000
+#define PROC_PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB	20
+#define PROC_PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_BITS	0x00080000
+#define PROC_PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_LSB	19
+#define PROC_PIO_SM0_SHIFTCTRL_PULL_THRESH_BITS	0x3e000000
+#define PROC_PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB	25
+#define PROC_PIO_SM0_SHIFTCTRL_FJOIN_TX_BITS	0x40000000
+#define PROC_PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB	30
+#define PROC_PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS	0x80000000
+#define PROC_PIO_SM0_SHIFTCTRL_FJOIN_RX_LSB	31
+#define PROC_PIO_SM0_EXECCTRL_OUT_STICKY_BITS	0x00020000
+#define PROC_PIO_SM0_EXECCTRL_OUT_STICKY_LSB	17
+#define PROC_PIO_SM0_EXECCTRL_INLINE_OUT_EN_BITS	0x00040000
+#define PROC_PIO_SM0_EXECCTRL_INLINE_OUT_EN_LSB	18
+#define PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS	0x00f80000
+#define PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_LSB	19
+#define PROC_PIO_SM0_EXECCTRL_STATUS_SEL_BITS	0x00000020
+#define PROC_PIO_SM0_EXECCTRL_STATUS_SEL_LSB	5
+#define PROC_PIO_SM0_EXECCTRL_STATUS_N_BITS	0x0000001f
+#define PROC_PIO_SM0_EXECCTRL_STATUS_N_LSB	0
+
+enum pio_fifo_join {
+	PIO_FIFO_JOIN_NONE = 0,
+	PIO_FIFO_JOIN_TX = 1,
+	PIO_FIFO_JOIN_RX = 2,
+};
+
+enum pio_mov_status_type {
+	STATUS_TX_LESSTHAN = 0,
+	STATUS_RX_LESSTHAN = 1
+};
+
+enum pio_xfer_dir {
+	PIO_DIR_TO_SM,
+	PIO_DIR_FROM_SM,
+	PIO_DIR_COUNT
+};
+
+enum clock_index {
+	clk_sys = 5
+};
+
+typedef struct pio_program {
+	const uint16_t *instructions;
+	uint8_t length;
+	int8_t origin; // required instruction memory origin or -1
+} pio_program_t;
+
+enum gpio_function {
+	GPIO_FUNC_FSEL0 = 0,
+	GPIO_FUNC_FSEL1 = 1,
+	GPIO_FUNC_FSEL2 = 2,
+	GPIO_FUNC_FSEL3 = 3,
+	GPIO_FUNC_FSEL4 = 4,
+	GPIO_FUNC_FSEL5 = 5,
+	GPIO_FUNC_FSEL6 = 6,
+	GPIO_FUNC_FSEL7 = 7,
+	GPIO_FUNC_FSEL8 = 8,
+	GPIO_FUNC_NULL = 0x1f,
+
+	// Name a few
+	GPIO_FUNC_SYS_RIO = 5,
+	GPIO_FUNC_PROC_RIO = 6,
+	GPIO_FUNC_PIO = 7,
+};
+
+enum gpio_irq_level {
+	GPIO_IRQ_LEVEL_LOW = 0x1u,
+	GPIO_IRQ_LEVEL_HIGH = 0x2u,
+	GPIO_IRQ_EDGE_FALL = 0x4u,
+	GPIO_IRQ_EDGE_RISE = 0x8u,
+};
+
+enum gpio_override {
+	GPIO_OVERRIDE_NORMAL = 0,
+	GPIO_OVERRIDE_INVERT = 1,
+	GPIO_OVERRIDE_LOW = 2,
+	GPIO_OVERRIDE_HIGH = 3,
+};
+enum gpio_slew_rate {
+	GPIO_SLEW_RATE_SLOW = 0,
+	GPIO_SLEW_RATE_FAST = 1
+};
+
+enum gpio_drive_strength {
+	GPIO_DRIVE_STRENGTH_2MA = 0,
+	GPIO_DRIVE_STRENGTH_4MA = 1,
+	GPIO_DRIVE_STRENGTH_8MA = 2,
+	GPIO_DRIVE_STRENGTH_12MA = 3
+};
+
+struct fp24_8 {
+	uint32_t val;
+};
+
+typedef rp1_pio_sm_config pio_sm_config;
+
+typedef struct rp1_pio_client *PIO;
+
+int rp1_pio_init(void);
+PIO rp1_pio_open(void);
+void rp1_pio_close(struct rp1_pio_client *client);
+void rp1_pio_set_error(struct rp1_pio_client *client, int err);
+int rp1_pio_get_error(const struct rp1_pio_client *client);
+void rp1_pio_clear_error(struct rp1_pio_client *client);
+int rp1_pio_sm_config_xfer(struct rp1_pio_client *client, uint sm, uint dir,
+			   uint buf_size, uint buf_count);
+int rp1_pio_sm_xfer_data(struct rp1_pio_client *client, uint sm, uint dir,
+			 uint data_bytes, void *data, dma_addr_t dma_addr,
+			 void (*callback)(void *param), void *param);
+
+int rp1_pio_can_add_program(struct rp1_pio_client *client, void *param);
+int rp1_pio_add_program(struct rp1_pio_client *client, void *param);
+int rp1_pio_remove_program(struct rp1_pio_client *client, void *param);
+int rp1_pio_clear_instr_mem(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_claim(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_unclaim(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_is_claimed(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_init(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_set_config(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_exec(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_clear_fifos(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_set_clkdiv(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_set_pins(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_set_pindirs(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_set_enabled(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_restart(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_clkdiv_restart(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_enable_sync(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_put(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_get(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_set_dmactrl(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_fifo_state(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_drain_tx(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_init(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_set_function(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_set_pulls(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_set_outover(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_set_inover(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_set_oeover(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_set_input_enabled(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_set_drive_strength(struct rp1_pio_client *client, void *param);
+
+static inline int pio_init(void)
+{
+	return rp1_pio_init();
+}
+
+static inline struct rp1_pio_client *pio_open(void)
+{
+	return rp1_pio_open();
+}
+
+static inline void pio_close(struct rp1_pio_client *client)
+{
+	rp1_pio_close(client);
+}
+
+static inline void pio_set_error(struct rp1_pio_client *client, int err)
+{
+	rp1_pio_set_error(client, err);
+}
+
+static inline int pio_get_error(const struct rp1_pio_client *client)
+{
+	return rp1_pio_get_error(client);
+}
+
+static inline void pio_clear_error(struct rp1_pio_client *client)
+{
+	rp1_pio_clear_error(client);
+}
+
+static inline int pio_sm_config_xfer(struct rp1_pio_client *client, uint sm, uint dir,
+				     uint buf_size, uint buf_count)
+{
+	return rp1_pio_sm_config_xfer(client, sm, dir, buf_size, buf_count);
+}
+
+static inline int pio_sm_xfer_data(struct rp1_pio_client *client, uint sm, uint dir,
+				   uint data_bytes, void *data, dma_addr_t dma_addr,
+				   void (*callback)(void *param), void *param)
+{
+	return rp1_pio_sm_xfer_data(client, sm, dir, data_bytes, data, dma_addr, callback, param);
+}
+
+static inline struct fp24_8 make_fp24_8(uint mul, uint div)
+{
+	struct fp24_8 res = { .val = ((unsigned long long)mul << 8) / div };
+
+	return res;
+}
+
+static inline bool pio_can_add_program(struct rp1_pio_client *client,
+				       const pio_program_t *program)
+{
+	struct rp1_pio_add_program_args args;
+
+	if (bad_params_if(client, program->length > PIO_INSTRUCTION_COUNT))
+		return false;
+	args.origin = (program->origin == -1) ? PIO_ORIGIN_ANY : program->origin;
+	args.num_instrs = program->length;
+
+	memcpy(args.instrs, program->instructions, args.num_instrs * sizeof(args.instrs[0]));
+	return rp1_pio_can_add_program(client, &args);
+}
+
+static inline bool pio_can_add_program_at_offset(struct rp1_pio_client *client,
+						 const pio_program_t *program, uint offset)
+{
+	struct rp1_pio_add_program_args args;
+
+	if (bad_params_if(client, program->length > PIO_INSTRUCTION_COUNT ||
+			  offset >= PIO_INSTRUCTION_COUNT))
+		return false;
+	args.origin = offset;
+	args.num_instrs = program->length;
+
+	memcpy(args.instrs, program->instructions, args.num_instrs * sizeof(args.instrs[0]));
+	return !rp1_pio_can_add_program(client, &args);
+}
+
+static inline uint pio_add_program(struct rp1_pio_client *client, const pio_program_t *program)
+{
+	struct rp1_pio_add_program_args args;
+	int offset;
+
+	if (bad_params_if(client, program->length > PIO_INSTRUCTION_COUNT))
+		return PIO_ORIGIN_ANY;
+	args.origin = (program->origin == -1) ? PIO_ORIGIN_ANY : program->origin;
+	args.num_instrs = program->length;
+
+	memcpy(args.instrs, program->instructions, args.num_instrs * sizeof(args.instrs[0]));
+	offset = rp1_pio_add_program(client, &args);
+	return (offset >= 0) ? offset : PIO_ORIGIN_ANY;
+}
+
+static inline int pio_add_program_at_offset(struct rp1_pio_client *client,
+					    const pio_program_t *program, uint offset)
+{
+	struct rp1_pio_add_program_args args;
+
+	if (bad_params_if(client, program->length > PIO_INSTRUCTION_COUNT ||
+				  offset >= PIO_INSTRUCTION_COUNT))
+		return -EINVAL;
+	args.origin = offset;
+	args.num_instrs = program->length;
+
+	memcpy(args.instrs, program->instructions, args.num_instrs * sizeof(args.instrs[0]));
+	return rp1_pio_add_program(client, &args);
+}
+
+static inline int pio_remove_program(struct rp1_pio_client *client, const pio_program_t *program,
+				     uint loaded_offset)
+{
+	struct rp1_pio_remove_program_args args;
+
+	args.origin = loaded_offset;
+	args.num_instrs = program->length;
+
+	return rp1_pio_remove_program(client, &args);
+}
+
+static inline int pio_clear_instruction_memory(struct rp1_pio_client *client)
+{
+	return rp1_pio_clear_instr_mem(client, NULL);
+}
+
+static inline int pio_sm_claim(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_claim_args args = { .mask = 1 << sm };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+
+	return rp1_pio_sm_claim(client, &args);
+}
+
+static inline int pio_claim_sm_mask(struct rp1_pio_client *client, uint mask)
+{
+	struct rp1_pio_sm_claim_args args = { .mask = mask };
+
+	if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES)))
+		return -EINVAL;
+
+	return rp1_pio_sm_claim(client, &args);
+}
+
+static inline int pio_sm_unclaim(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_claim_args args = { .mask = 1 << sm };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+
+	return rp1_pio_sm_unclaim(client, &args);
+}
+
+static inline int pio_claim_unused_sm(struct rp1_pio_client *client, bool required)
+{
+	struct rp1_pio_sm_claim_args args = { .mask = 0 };
+	int sm;
+
+	sm = rp1_pio_sm_claim(client, &args);
+	if (sm < 0 && required)
+		WARN_ON("No PIO state machines are available");
+	return sm;
+}
+
+static inline bool pio_sm_is_claimed(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_claim_args args = { .mask = (1 << sm) };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return true;
+	return rp1_pio_sm_is_claimed(client, &args);
+}
+
+static inline int pio_sm_init(struct rp1_pio_client *client, uint sm, uint initial_pc,
+			      const pio_sm_config *config)
+{
+	struct rp1_pio_sm_init_args args = { .sm = sm, .initial_pc = initial_pc,
+					     .config = *config };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES ||
+				  initial_pc >= PIO_INSTRUCTION_COUNT))
+		return -EINVAL;
+
+	return rp1_pio_sm_init(client, &args);
+}
+
+static inline int pio_sm_set_config(struct rp1_pio_client *client, uint sm,
+				    const pio_sm_config *config)
+{
+	struct rp1_pio_sm_init_args args = { .sm = sm, .config = *config };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+
+	return rp1_pio_sm_set_config(client, &args);
+}
+
+static inline int pio_sm_exec(struct rp1_pio_client *client, uint sm, uint instr)
+{
+	struct rp1_pio_sm_exec_args args = { .sm = sm, .instr = instr, .blocking = false };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES || instr > (uint16_t)~0))
+		return -EINVAL;
+
+	return rp1_pio_sm_exec(client, &args);
+}
+
+static inline int pio_sm_exec_wait_blocking(struct rp1_pio_client *client, uint sm, uint instr)
+{
+	struct rp1_pio_sm_exec_args args = { .sm = sm, .instr = instr, .blocking = true };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES || instr > (uint16_t)~0))
+		return -EINVAL;
+
+	return rp1_pio_sm_exec(client, &args);
+}
+
+static inline int pio_sm_clear_fifos(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_clear_fifos_args args = { .sm = sm };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_clear_fifos(client, &args);
+}
+
+static inline bool pio_calculate_clkdiv_from_fp24_8(struct fp24_8 div, uint16_t *div_int,
+						   uint8_t *div_frac)
+{
+	uint inum = (div.val >> 8);
+
+	if (bad_params_if(NULL, inum < 1 || inum > 65536))
+		return false;
+	*div_int = (uint16_t)inum;
+	if (*div_int == 0)
+		*div_frac = 0;
+	else
+		*div_frac = div.val & 0xff;
+	return true;
+}
+
+static inline int pio_sm_set_clkdiv_int_frac(struct rp1_pio_client *client, uint sm,
+					     uint16_t div_int, uint8_t div_frac)
+{
+	struct rp1_pio_sm_set_clkdiv_args args = { .sm = sm, .div_int = div_int,
+						   .div_frac = div_frac };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES ||
+			  (div_int == 0 && div_frac != 0)))
+		return -EINVAL;
+	return rp1_pio_sm_set_clkdiv(client, &args);
+}
+
+static inline int pio_sm_set_clkdiv(struct rp1_pio_client *client, uint sm, struct fp24_8 div)
+{
+	struct rp1_pio_sm_set_clkdiv_args args = { .sm = sm };
+
+	if (!pio_calculate_clkdiv_from_fp24_8(div, &args.div_int, &args.div_frac))
+		return -EINVAL;
+	return rp1_pio_sm_set_clkdiv(client, &args);
+}
+
+static inline int pio_sm_set_pins(struct rp1_pio_client *client, uint sm, uint32_t pin_values)
+{
+	struct rp1_pio_sm_set_pins_args args = { .sm = sm, .values = pin_values,
+						 .mask = GPIOS_MASK };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_set_pins(client, &args);
+}
+
+static inline int pio_sm_set_pins_with_mask(struct rp1_pio_client *client, uint sm,
+					    uint32_t pin_values, uint32_t pin_mask)
+{
+	struct rp1_pio_sm_set_pins_args args = { .sm = sm, .values = pin_values,
+						 .mask = pin_mask };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_set_pins(client, &args);
+}
+
+static inline int pio_sm_set_pindirs_with_mask(struct rp1_pio_client *client, uint sm,
+					       uint32_t pin_dirs, uint32_t pin_mask)
+{
+	struct rp1_pio_sm_set_pindirs_args args = { .sm = sm, .dirs = pin_dirs,
+						    .mask = pin_mask };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES ||
+			      (pin_dirs & GPIOS_MASK) != pin_dirs ||
+			      (pin_mask & pin_mask) != pin_mask))
+		return -EINVAL;
+	return rp1_pio_sm_set_pindirs(client, &args);
+}
+
+static inline int pio_sm_set_consecutive_pindirs(struct rp1_pio_client *client, uint sm,
+						 uint pin_base, uint pin_count, bool is_out)
+{
+	uint32_t mask = ((1 << pin_count) - 1) << pin_base;
+	struct rp1_pio_sm_set_pindirs_args args = { .sm = sm, .dirs = is_out ? mask : 0,
+						    .mask = mask };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES ||
+			      pin_base >= RP1_PIO_GPIO_COUNT ||
+			      pin_count > RP1_PIO_GPIO_COUNT ||
+			      (pin_base + pin_count) > RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_sm_set_pindirs(client, &args);
+}
+
+static inline int pio_sm_set_enabled(struct rp1_pio_client *client, uint sm, bool enabled)
+{
+	struct rp1_pio_sm_set_enabled_args args = { .mask = (1 << sm), .enable = enabled };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_set_enabled(client, &args);
+}
+
+static inline int pio_set_sm_mask_enabled(struct rp1_pio_client *client, uint32_t mask,
+					  bool enabled)
+{
+	struct rp1_pio_sm_set_enabled_args args = { .mask = mask, .enable = enabled };
+
+	if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES)))
+		return -EINVAL;
+	return rp1_pio_sm_set_enabled(client, &args);
+}
+
+static inline int pio_sm_restart(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_restart_args args = { .mask = (1 << sm) };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_restart(client, &args);
+}
+
+static inline int pio_restart_sm_mask(struct rp1_pio_client *client, uint32_t mask)
+{
+	struct rp1_pio_sm_restart_args args = { .mask = (uint16_t)mask };
+
+	if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES)))
+		return -EINVAL;
+	return rp1_pio_sm_restart(client, &args);
+}
+
+static inline int pio_sm_clkdiv_restart(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_restart_args args = { .mask = (1 << sm) };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_clkdiv_restart(client, &args);
+}
+
+static inline int pio_clkdiv_restart_sm_mask(struct rp1_pio_client *client, uint32_t mask)
+{
+	struct rp1_pio_sm_restart_args args = { .mask = (uint16_t)mask };
+
+	if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES)))
+		return -EINVAL;
+	return rp1_pio_sm_clkdiv_restart(client, &args);
+}
+
+static inline int pio_enable_sm_in_sync_mask(struct rp1_pio_client *client, uint32_t mask)
+{
+	struct rp1_pio_sm_enable_sync_args args = { .mask = (uint16_t)mask };
+
+	if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES)))
+		return -EINVAL;
+	return rp1_pio_sm_enable_sync(client, &args);
+}
+
+static inline int pio_sm_set_dmactrl(struct rp1_pio_client *client, uint sm, bool is_tx,
+				     uint32_t ctrl)
+{
+	struct rp1_pio_sm_set_dmactrl_args args = { .sm = sm, .is_tx = is_tx, .ctrl = ctrl };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_set_dmactrl(client, &args);
+};
+
+static inline int pio_sm_drain_tx_fifo(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_clear_fifos_args args = { .sm = sm };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_drain_tx(client, &args);
+};
+
+static inline int pio_sm_put(struct rp1_pio_client *client, uint sm, uint32_t data)
+{
+	struct rp1_pio_sm_put_args args = { .sm = (uint16_t)sm, .blocking = false, .data = data };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_put(client, &args);
+}
+
+static inline int pio_sm_put_blocking(struct rp1_pio_client *client, uint sm, uint32_t data)
+{
+	struct rp1_pio_sm_put_args args = { .sm = (uint16_t)sm, .blocking = true, .data = data };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_put(client, &args);
+}
+
+static inline uint32_t pio_sm_get(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_get_args args = { .sm = (uint16_t)sm, .blocking = false };
+
+	if (!bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		rp1_pio_sm_get(client, &args);
+	return args.data;
+}
+
+static inline uint32_t pio_sm_get_blocking(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_get_args args = { .sm = (uint16_t)sm, .blocking = true };
+
+	if (!bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		rp1_pio_sm_get(client, &args);
+	return args.data;
+}
+
+static inline int pio_sm_is_rx_fifo_empty(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = false };
+	int ret;
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	ret = rp1_pio_sm_fifo_state(client, &args);
+	if (ret == sizeof(args))
+		ret = args.empty;
+	return ret;
+};
+
+static inline int pio_sm_is_rx_fifo_full(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = false };
+	int ret;
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	ret = rp1_pio_sm_fifo_state(client, &args);
+	if (ret == sizeof(args))
+		ret = args.full;
+	return ret;
+};
+
+static inline int pio_sm_rx_fifo_level(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = false };
+	int ret;
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	ret = rp1_pio_sm_fifo_state(client, &args);
+	if (ret == sizeof(args))
+		ret = args.level;
+	return ret;
+};
+
+static inline int pio_sm_is_tx_fifo_empty(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = true };
+	int ret;
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	ret = rp1_pio_sm_fifo_state(client, &args);
+	if (ret == sizeof(args))
+		ret = args.empty;
+	return ret;
+};
+
+static inline int pio_sm_is_tx_fifo_full(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = true };
+	int ret;
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	ret = rp1_pio_sm_fifo_state(client, &args);
+	if (ret == sizeof(args))
+		ret = args.full;
+	return ret;
+};
+
+static inline int pio_sm_tx_fifo_level(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = true };
+	int ret;
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	ret = rp1_pio_sm_fifo_state(client, &args);
+	if (ret == sizeof(args))
+		ret = args.level;
+	return ret;
+};
+
+static inline void sm_config_set_out_pins(pio_sm_config *c, uint out_base, uint out_count)
+{
+	if (bad_params_if(NULL, out_base >= RP1_PIO_GPIO_COUNT ||
+				    out_count > RP1_PIO_GPIO_COUNT))
+		return;
+
+	c->pinctrl = (c->pinctrl & ~(PROC_PIO_SM0_PINCTRL_OUT_BASE_BITS |
+				     PROC_PIO_SM0_PINCTRL_OUT_COUNT_BITS)) |
+			(out_base << PROC_PIO_SM0_PINCTRL_OUT_BASE_LSB) |
+			(out_count << PROC_PIO_SM0_PINCTRL_OUT_COUNT_LSB);
+}
+
+static inline void sm_config_set_set_pins(pio_sm_config *c, uint set_base, uint set_count)
+{
+	if (bad_params_if(NULL, set_base >= RP1_PIO_GPIO_COUNT ||
+				    set_count > 5))
+		return;
+
+	c->pinctrl = (c->pinctrl & ~(PROC_PIO_SM0_PINCTRL_SET_BASE_BITS |
+				     PROC_PIO_SM0_PINCTRL_SET_COUNT_BITS)) |
+			(set_base << PROC_PIO_SM0_PINCTRL_SET_BASE_LSB) |
+			(set_count << PROC_PIO_SM0_PINCTRL_SET_COUNT_LSB);
+}
+
+
+static inline void sm_config_set_in_pins(pio_sm_config *c, uint in_base)
+{
+	if (bad_params_if(NULL, in_base >= RP1_PIO_GPIO_COUNT))
+		return;
+
+	c->pinctrl = (c->pinctrl & ~PROC_PIO_SM0_PINCTRL_IN_BASE_BITS) |
+			(in_base << PROC_PIO_SM0_PINCTRL_IN_BASE_LSB);
+}
+
+static inline void sm_config_set_sideset_pins(pio_sm_config *c, uint sideset_base)
+{
+	if (bad_params_if(NULL, sideset_base >= RP1_PIO_GPIO_COUNT))
+		return;
+
+	c->pinctrl = (c->pinctrl & ~PROC_PIO_SM0_PINCTRL_SIDESET_BASE_BITS) |
+			(sideset_base << PROC_PIO_SM0_PINCTRL_SIDESET_BASE_LSB);
+}
+
+static inline void sm_config_set_sideset(pio_sm_config *c, uint bit_count, bool optional,
+					 bool pindirs)
+{
+	if (bad_params_if(NULL, bit_count > 5 ||
+				    (optional && (bit_count == 0))))
+		return;
+	c->pinctrl = (c->pinctrl & ~PROC_PIO_SM0_PINCTRL_SIDESET_COUNT_BITS) |
+			(bit_count << PROC_PIO_SM0_PINCTRL_SIDESET_COUNT_LSB);
+
+	c->execctrl = (c->execctrl & ~(PROC_PIO_SM0_EXECCTRL_SIDE_EN_BITS |
+				       PROC_PIO_SM0_EXECCTRL_SIDE_PINDIR_BITS)) |
+			(optional << PROC_PIO_SM0_EXECCTRL_SIDE_EN_LSB) |
+			(pindirs << PROC_PIO_SM0_EXECCTRL_SIDE_PINDIR_LSB);
+}
+
+static inline void sm_config_set_clkdiv_int_frac(pio_sm_config *c, uint16_t div_int,
+						 uint8_t div_frac)
+{
+	if (bad_params_if(NULL, div_int == 0 && div_frac != 0))
+		return;
+
+	c->clkdiv =
+		(((uint)div_frac) << PROC_PIO_SM0_CLKDIV_FRAC_LSB) |
+		(((uint)div_int) << PROC_PIO_SM0_CLKDIV_INT_LSB);
+}
+
+static inline void sm_config_set_clkdiv(pio_sm_config *c, struct fp24_8 div)
+{
+	uint16_t div_int;
+	uint8_t div_frac;
+
+	pio_calculate_clkdiv_from_fp24_8(div, &div_int, &div_frac);
+	sm_config_set_clkdiv_int_frac(c, div_int, div_frac);
+}
+
+static inline void sm_config_set_wrap(pio_sm_config *c, uint wrap_target, uint wrap)
+{
+	if (bad_params_if(NULL, wrap >= PIO_INSTRUCTION_COUNT ||
+				    wrap_target >= PIO_INSTRUCTION_COUNT))
+		return;
+
+	c->execctrl = (c->execctrl & ~(PROC_PIO_SM0_EXECCTRL_WRAP_TOP_BITS |
+				       PROC_PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS)) |
+			(wrap_target << PROC_PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB) |
+			(wrap << PROC_PIO_SM0_EXECCTRL_WRAP_TOP_LSB);
+}
+
+static inline void sm_config_set_jmp_pin(pio_sm_config *c, uint pin)
+{
+	if (bad_params_if(NULL, pin >= RP1_PIO_GPIO_COUNT))
+		return;
+
+	c->execctrl = (c->execctrl & ~PROC_PIO_SM0_EXECCTRL_JMP_PIN_BITS) |
+		(pin << PROC_PIO_SM0_EXECCTRL_JMP_PIN_LSB);
+}
+
+static inline void sm_config_set_in_shift(pio_sm_config *c, bool shift_right, bool autopush,
+					  uint push_threshold)
+{
+	if (bad_params_if(NULL, push_threshold > 32))
+		return;
+
+	c->shiftctrl = (c->shiftctrl &
+		~(PROC_PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_BITS |
+		PROC_PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS |
+		PROC_PIO_SM0_SHIFTCTRL_PUSH_THRESH_BITS)) |
+		(shift_right << PROC_PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_LSB) |
+		(autopush << PROC_PIO_SM0_SHIFTCTRL_AUTOPUSH_LSB) |
+		((push_threshold & 0x1fu) << PROC_PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB);
+}
+
+static inline void sm_config_set_out_shift(pio_sm_config *c, bool shift_right, bool autopull,
+					   uint pull_threshold)
+{
+	if (bad_params_if(NULL, pull_threshold > 32))
+		return;
+
+	c->shiftctrl = (c->shiftctrl &
+		~(PROC_PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_BITS |
+		PROC_PIO_SM0_SHIFTCTRL_AUTOPULL_BITS |
+		PROC_PIO_SM0_SHIFTCTRL_PULL_THRESH_BITS)) |
+		(shift_right << PROC_PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_LSB) |
+		(autopull << PROC_PIO_SM0_SHIFTCTRL_AUTOPULL_LSB) |
+		((pull_threshold & 0x1fu) << PROC_PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB);
+}
+
+static inline void sm_config_set_fifo_join(pio_sm_config *c, enum pio_fifo_join join)
+{
+	if (bad_params_if(NULL, join != PIO_FIFO_JOIN_NONE &&
+				    join != PIO_FIFO_JOIN_TX &&
+				    join != PIO_FIFO_JOIN_RX))
+		return;
+
+	c->shiftctrl = (c->shiftctrl & (uint)~(PROC_PIO_SM0_SHIFTCTRL_FJOIN_TX_BITS |
+					       PROC_PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS)) |
+		(((uint)join) << PROC_PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB);
+}
+
+static inline void sm_config_set_out_special(pio_sm_config *c, bool sticky, bool has_enable_pin,
+					     uint enable_pin_index)
+{
+	c->execctrl = (c->execctrl &
+		(uint)~(PROC_PIO_SM0_EXECCTRL_OUT_STICKY_BITS |
+			PROC_PIO_SM0_EXECCTRL_INLINE_OUT_EN_BITS |
+		PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS)) |
+		(sticky << PROC_PIO_SM0_EXECCTRL_OUT_STICKY_LSB) |
+		(has_enable_pin << PROC_PIO_SM0_EXECCTRL_INLINE_OUT_EN_LSB) |
+		((enable_pin_index << PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_LSB) &
+		 PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS);
+}
+
+static inline void sm_config_set_mov_status(pio_sm_config *c, enum pio_mov_status_type status_sel,
+					    uint status_n)
+{
+	if (bad_params_if(NULL, status_sel != STATUS_TX_LESSTHAN &&
+				status_sel != STATUS_RX_LESSTHAN))
+		return;
+
+	c->execctrl = (c->execctrl
+		& ~(PROC_PIO_SM0_EXECCTRL_STATUS_SEL_BITS | PROC_PIO_SM0_EXECCTRL_STATUS_N_BITS))
+		| ((((uint)status_sel) << PROC_PIO_SM0_EXECCTRL_STATUS_SEL_LSB) &
+		   PROC_PIO_SM0_EXECCTRL_STATUS_SEL_BITS)
+		| ((status_n << PROC_PIO_SM0_EXECCTRL_STATUS_N_LSB) &
+		   PROC_PIO_SM0_EXECCTRL_STATUS_N_BITS);
+}
+
+static inline pio_sm_config pio_get_default_sm_config(void)
+{
+	pio_sm_config c = { 0 };
+
+	sm_config_set_clkdiv_int_frac(&c, 1, 0);
+	sm_config_set_wrap(&c, 0, 31);
+	sm_config_set_in_shift(&c, true, false, 32);
+	sm_config_set_out_shift(&c, true, false, 32);
+	return c;
+}
+
+static inline uint32_t clock_get_hz(enum clock_index clk_index)
+{
+	const uint32_t MHZ = 1000000;
+
+	if (bad_params_if(NULL, clk_index != clk_sys))
+		return 0;
+	return 200 * MHZ;
+}
+
+static inline int pio_gpio_set_function(struct rp1_pio_client *client, uint gpio,
+					enum gpio_function fn)
+{
+	struct rp1_gpio_set_function_args args = { .gpio = gpio, .fn = fn };
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_gpio_set_function(client, &args);
+}
+
+static inline int pio_gpio_init(struct rp1_pio_client *client, uint gpio)
+{
+	struct rp1_gpio_init_args args = { .gpio = gpio };
+	int ret;
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	ret = rp1_pio_gpio_init(client, &args);
+	if (ret)
+		return ret;
+	return pio_gpio_set_function(client, gpio, RP1_GPIO_FUNC_PIO);
+}
+
+static inline int pio_gpio_set_pulls(struct rp1_pio_client *client, uint gpio, bool up, bool down)
+{
+	struct rp1_gpio_set_pulls_args args = { .gpio = gpio, .up = up, .down = down };
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_gpio_set_pulls(client, &args);
+}
+
+static inline int pio_gpio_set_outover(struct rp1_pio_client *client, uint gpio, uint value)
+{
+	struct rp1_gpio_set_args args = { .gpio = gpio, .value = value };
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_gpio_set_outover(client, &args);
+}
+
+static inline int pio_gpio_set_inover(struct rp1_pio_client *client, uint gpio, uint value)
+{
+	struct rp1_gpio_set_args args = { .gpio = gpio, .value = value };
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_gpio_set_inover(client, &args);
+}
+
+static inline int pio_gpio_set_oeover(struct rp1_pio_client *client, uint gpio, uint value)
+{
+	struct rp1_gpio_set_args args = { .gpio = gpio, .value = value };
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_gpio_set_oeover(client, &args);
+}
+
+static inline int pio_gpio_set_input_enabled(struct rp1_pio_client *client, uint gpio,
+					     bool enabled)
+{
+	struct rp1_gpio_set_args args = { .gpio = gpio, .value = enabled };
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_gpio_set_input_enabled(client, &args);
+}
+
+static inline int pio_gpio_set_drive_strength(struct rp1_pio_client *client, uint gpio,
+					      enum gpio_drive_strength drive)
+{
+	struct rp1_gpio_set_args args = { .gpio = gpio, .value = drive };
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_gpio_set_drive_strength(client, &args);
+}
+
+static inline int pio_gpio_pull_up(struct rp1_pio_client *client, uint gpio)
+{
+	return pio_gpio_set_pulls(client, gpio, true, false);
+}
+
+static inline int pio_gpio_pull_down(struct rp1_pio_client *client, uint gpio)
+{
+	return pio_gpio_set_pulls(client, gpio, false, true);
+}
+
+static inline int pio_gpio_disable_pulls(struct rp1_pio_client *client, uint gpio)
+{
+	return pio_gpio_set_pulls(client, gpio, false, false);
+}
+
+#endif
--- /dev/null
+++ b/include/uapi/misc/rp1_pio_if.h
@@ -0,0 +1,235 @@
+/* SPDX-License-Identifier: GPL-2.0 + WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2023-24 Raspberry Pi Ltd.
+ * All rights reserved.
+ */
+#ifndef _PIO_RP1_IF_H
+#define _PIO_RP1_IF_H
+
+#include <linux/ioctl.h>
+
+#define RP1_PIO_INSTRUCTION_COUNT   32
+#define RP1_PIO_SM_COUNT            4
+#define RP1_PIO_GPIO_COUNT          28
+#define RP1_GPIO_FUNC_PIO           7
+
+#define RP1_PIO_ORIGIN_ANY          ((uint16_t)(~0))
+
+#define RP1_PIO_DIR_TO_SM           0
+#define RP1_PIO_DIR_FROM_SM         1
+#define RP1_PIO_DIR_COUNT           2
+
+typedef struct {
+	uint32_t clkdiv;
+	uint32_t execctrl;
+	uint32_t shiftctrl;
+	uint32_t pinctrl;
+} rp1_pio_sm_config;
+
+struct rp1_pio_add_program_args {
+	uint16_t num_instrs;
+	uint16_t origin;
+	uint16_t instrs[RP1_PIO_INSTRUCTION_COUNT];
+};
+
+struct rp1_pio_remove_program_args {
+	uint16_t num_instrs;
+	uint16_t origin;
+};
+
+struct rp1_pio_sm_claim_args {
+	uint16_t mask;
+};
+
+struct rp1_pio_sm_init_args {
+	uint16_t sm;
+	uint16_t initial_pc;
+	rp1_pio_sm_config config;
+};
+
+struct rp1_pio_sm_set_config_args {
+	uint16_t sm;
+	uint16_t rsvd;
+	rp1_pio_sm_config config;
+};
+
+struct rp1_pio_sm_exec_args {
+	uint16_t sm;
+	uint16_t instr;
+	uint8_t blocking;
+	uint8_t rsvd;
+};
+
+struct rp1_pio_sm_clear_fifos_args {
+	uint16_t sm;
+};
+
+struct rp1_pio_sm_set_clkdiv_args {
+	uint16_t sm;
+	uint16_t div_int;
+	uint8_t div_frac;
+	uint8_t rsvd;
+};
+
+struct rp1_pio_sm_set_pins_args {
+	uint16_t sm;
+	uint16_t rsvd;
+	uint32_t values;
+	uint32_t mask;
+};
+
+struct rp1_pio_sm_set_pindirs_args {
+	uint16_t sm;
+	uint16_t rsvd;
+	uint32_t dirs;
+	uint32_t mask;
+};
+
+struct rp1_pio_sm_set_enabled_args {
+	uint16_t mask;
+	uint8_t enable;
+	uint8_t rsvd;
+};
+
+struct rp1_pio_sm_restart_args {
+	uint16_t mask;
+};
+
+struct rp1_pio_sm_clkdiv_restart_args {
+	uint16_t mask;
+};
+
+struct rp1_pio_sm_enable_sync_args {
+	uint16_t mask;
+};
+
+struct rp1_pio_sm_put_args {
+	uint16_t sm;
+	uint8_t blocking;
+	uint8_t rsvd;
+	uint32_t data;
+};
+
+struct rp1_pio_sm_get_args {
+	uint16_t sm;
+	uint8_t blocking;
+	uint8_t rsvd;
+	uint32_t data; /* OUT */
+};
+
+struct rp1_pio_sm_set_dmactrl_args {
+	uint16_t sm;
+	uint8_t is_tx;
+	uint8_t rsvd;
+	uint32_t ctrl;
+};
+
+struct rp1_pio_sm_fifo_state_args {
+	uint16_t sm;
+	uint8_t tx;
+	uint8_t rsvd;
+	uint16_t level; /* OUT */
+	uint8_t empty; /* OUT */
+	uint8_t full; /* OUT */
+};
+
+struct rp1_gpio_init_args {
+	uint16_t gpio;
+};
+
+struct rp1_gpio_set_function_args {
+	uint16_t gpio;
+	uint16_t fn;
+};
+
+struct rp1_gpio_set_pulls_args {
+	uint16_t gpio;
+	uint8_t up;
+	uint8_t down;
+};
+
+struct rp1_gpio_set_args {
+	uint16_t gpio;
+	uint16_t value;
+};
+
+struct rp1_pio_sm_config_xfer_args {
+	uint16_t sm;
+	uint16_t dir;
+	uint16_t buf_size;
+	uint16_t buf_count;
+};
+
+struct rp1_pio_sm_config_xfer32_args {
+	uint16_t sm;
+	uint16_t dir;
+	uint32_t buf_size;
+	uint32_t buf_count;
+};
+
+struct rp1_pio_sm_xfer_data_args {
+	uint16_t sm;
+	uint16_t dir;
+	uint16_t data_bytes;
+	void *data;
+};
+
+struct rp1_pio_sm_xfer_data32_args {
+	uint16_t sm;
+	uint16_t dir;
+	uint32_t data_bytes;
+	void *data;
+};
+
+struct rp1_access_hw_args {
+	uint32_t addr;
+	uint32_t len;
+	void *data;
+};
+
+#define PIO_IOC_MAGIC 102
+
+#define PIO_IOC_SM_CONFIG_XFER _IOW(PIO_IOC_MAGIC, 0, struct rp1_pio_sm_config_xfer_args)
+#define PIO_IOC_SM_XFER_DATA _IOW(PIO_IOC_MAGIC, 1, struct rp1_pio_sm_xfer_data_args)
+#define PIO_IOC_SM_XFER_DATA32 _IOW(PIO_IOC_MAGIC, 2, struct rp1_pio_sm_xfer_data32_args)
+#define PIO_IOC_SM_CONFIG_XFER32 _IOW(PIO_IOC_MAGIC, 3, struct rp1_pio_sm_config_xfer32_args)
+
+#define PIO_IOC_READ_HW _IOW(PIO_IOC_MAGIC, 8, struct rp1_access_hw_args)
+#define PIO_IOC_WRITE_HW _IOW(PIO_IOC_MAGIC, 9, struct rp1_access_hw_args)
+
+#define PIO_IOC_CAN_ADD_PROGRAM _IOW(PIO_IOC_MAGIC, 10, struct rp1_pio_add_program_args)
+#define PIO_IOC_ADD_PROGRAM _IOW(PIO_IOC_MAGIC, 11, struct rp1_pio_add_program_args)
+#define PIO_IOC_REMOVE_PROGRAM _IOW(PIO_IOC_MAGIC, 12, struct rp1_pio_remove_program_args)
+#define PIO_IOC_CLEAR_INSTR_MEM _IO(PIO_IOC_MAGIC, 13)
+
+#define PIO_IOC_SM_CLAIM _IOW(PIO_IOC_MAGIC, 20, struct rp1_pio_sm_claim_args)
+#define PIO_IOC_SM_UNCLAIM _IOW(PIO_IOC_MAGIC, 21, struct rp1_pio_sm_claim_args)
+#define PIO_IOC_SM_IS_CLAIMED _IOW(PIO_IOC_MAGIC, 22, struct rp1_pio_sm_claim_args)
+
+#define PIO_IOC_SM_INIT _IOW(PIO_IOC_MAGIC, 30, struct rp1_pio_sm_init_args)
+#define PIO_IOC_SM_SET_CONFIG _IOW(PIO_IOC_MAGIC, 31, struct rp1_pio_sm_set_config_args)
+#define PIO_IOC_SM_EXEC _IOW(PIO_IOC_MAGIC, 32, struct rp1_pio_sm_exec_args)
+#define PIO_IOC_SM_CLEAR_FIFOS _IOW(PIO_IOC_MAGIC, 33, struct rp1_pio_sm_clear_fifos_args)
+#define PIO_IOC_SM_SET_CLKDIV _IOW(PIO_IOC_MAGIC, 34, struct rp1_pio_sm_set_clkdiv_args)
+#define PIO_IOC_SM_SET_PINS _IOW(PIO_IOC_MAGIC, 35, struct rp1_pio_sm_set_pins_args)
+#define PIO_IOC_SM_SET_PINDIRS _IOW(PIO_IOC_MAGIC, 36, struct rp1_pio_sm_set_pindirs_args)
+#define PIO_IOC_SM_SET_ENABLED _IOW(PIO_IOC_MAGIC, 37, struct rp1_pio_sm_set_enabled_args)
+#define PIO_IOC_SM_RESTART _IOW(PIO_IOC_MAGIC, 38, struct rp1_pio_sm_restart_args)
+#define PIO_IOC_SM_CLKDIV_RESTART _IOW(PIO_IOC_MAGIC, 39, struct rp1_pio_sm_restart_args)
+#define PIO_IOC_SM_ENABLE_SYNC _IOW(PIO_IOC_MAGIC, 40, struct rp1_pio_sm_enable_sync_args)
+#define PIO_IOC_SM_PUT _IOW(PIO_IOC_MAGIC, 41, struct rp1_pio_sm_put_args)
+#define PIO_IOC_SM_GET _IOWR(PIO_IOC_MAGIC, 42, struct rp1_pio_sm_get_args)
+#define PIO_IOC_SM_SET_DMACTRL _IOW(PIO_IOC_MAGIC, 43, struct rp1_pio_sm_set_dmactrl_args)
+#define PIO_IOC_SM_FIFO_STATE _IOW(PIO_IOC_MAGIC, 44, struct rp1_pio_sm_fifo_state_args)
+#define PIO_IOC_SM_DRAIN_TX _IOW(PIO_IOC_MAGIC, 45, struct rp1_pio_sm_clear_fifos_args)
+
+#define PIO_IOC_GPIO_INIT _IOW(PIO_IOC_MAGIC, 50, struct rp1_gpio_init_args)
+#define PIO_IOC_GPIO_SET_FUNCTION _IOW(PIO_IOC_MAGIC, 51, struct rp1_gpio_set_function_args)
+#define PIO_IOC_GPIO_SET_PULLS _IOW(PIO_IOC_MAGIC, 52, struct rp1_gpio_set_pulls_args)
+#define PIO_IOC_GPIO_SET_OUTOVER _IOW(PIO_IOC_MAGIC, 53, struct rp1_gpio_set_args)
+#define PIO_IOC_GPIO_SET_INOVER _IOW(PIO_IOC_MAGIC, 54, struct rp1_gpio_set_args)
+#define PIO_IOC_GPIO_SET_OEOVER _IOW(PIO_IOC_MAGIC, 55, struct rp1_gpio_set_args)
+#define PIO_IOC_GPIO_SET_INPUT_ENABLED _IOW(PIO_IOC_MAGIC, 56, struct rp1_gpio_set_args)
+#define PIO_IOC_GPIO_SET_DRIVE_STRENGTH _IOW(PIO_IOC_MAGIC, 57, struct rp1_gpio_set_args)
+
+#endif
