CONFIG_PTP_1588_CLOCK_OPTIONAL=y
CONFIG_PWM=y
CONFIG_PWM_BCM2835=y
+CONFIG_PWM_GPIO=y
CONFIG_PWM_SYSFS=y
CONFIG_RANDSTRUCT_NONE=y
CONFIG_RASPBERRYPI_FIRMWARE=y
# CONFIG_SERIAL_DEV_CTRL_TTYPORT is not set
CONFIG_SERIAL_MCTRL_GPIO=y
CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_RPI_FW=y
CONFIG_SG_POOL=y
CONFIG_SMSC_PHY=y
CONFIG_SOFTIRQ_ON_OWN_STACK=y
CONFIG_PTP_1588_CLOCK_OPTIONAL=y
CONFIG_PWM=y
CONFIG_PWM_BCM2835=y
+CONFIG_PWM_GPIO=y
CONFIG_PWM_SYSFS=y
CONFIG_RANDSTRUCT_NONE=y
CONFIG_RAS=y
# CONFIG_SERIAL_DEV_CTRL_TTYPORT is not set
CONFIG_SERIAL_MCTRL_GPIO=y
CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_RPI_FW=y
CONFIG_SG_POOL=y
CONFIG_SMP=y
CONFIG_SMP_ON_UP=y
CONFIG_PTP_1588_CLOCK_OPTIONAL=y
CONFIG_PWM=y
CONFIG_PWM_BCM2835=y
+CONFIG_PWM_GPIO=y
CONFIG_PWM_SYSFS=y
CONFIG_QUEUED_RWLOCKS=y
CONFIG_QUEUED_SPINLOCKS=y
# CONFIG_SERIAL_DEV_CTRL_TTYPORT is not set
CONFIG_SERIAL_MCTRL_GPIO=y
CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_RPI_FW=y
CONFIG_SG_POOL=y
CONFIG_SMP=y
CONFIG_SMSC_PHY=y
CONFIG_PTP_1588_CLOCK_OPTIONAL=y
CONFIG_PWM=y
CONFIG_PWM_BCM2835=y
+CONFIG_PWM_GPIO=y
CONFIG_PWM_SYSFS=y
CONFIG_QUEUED_RWLOCKS=y
CONFIG_QUEUED_SPINLOCKS=y
# CONFIG_SERIAL_DEV_CTRL_TTYPORT is not set
CONFIG_SERIAL_MCTRL_GPIO=y
CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_RPI_FW=y
CONFIG_SG_POOL=y
CONFIG_SMP=y
CONFIG_SOCK_RX_QUEUE_MAPPING=y
CONFIG_PWM=y
CONFIG_PWM_BCM2835=y
CONFIG_PWM_BRCMSTB=y
+CONFIG_PWM_GPIO=y
CONFIG_PWM_RP1=y
CONFIG_PWM_SYSFS=y
CONFIG_QUEUED_RWLOCKS=y
CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y
CONFIG_SPARSE_IRQ=y
CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU=y
+CONFIG_SRAM=y
# CONFIG_STRIP_ASM_SYMS is not set
CONFIG_SUSPEND=y
CONFIG_SUSPEND_FREEZER=y
# CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2 is not set
# CONFIG_RP1_PIO is not set
# CONFIG_SENSORS_RP1_ADC is not set
+# CONFIG_SERIAL_RPI_FW is not set
+# CONFIG_SND_PIMIDI is not set
# CONFIG_SPI_RP2040_GPIO_BRIDGE is not set
# CONFIG_VIDEO_AD5398 is not set
# CONFIG_VIDEO_ARDUCAM_64MP is not set
+++ /dev/null
-From fc5ed9d9bf0411523220bab60304da6d23257a64 Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Thu, 1 Nov 2018 17:31:37 +0000
-Subject: [PATCH 0297/1085] staging: vchiq_arm: Add 36-bit address support
-
-Conditional on a new compatible string, change the pagelist encoding
-such that the top 24 bits are the pfn, leaving 8 bits for run length
-(-1), giving a 36-bit address range.
-
-Manage the split between addresses for the VPU and addresses for the
-40-bit DMA controller with a dedicated DMA device pointer that on non-
-BCM2711 platforms is the same as the main VCHIQ device. This allows
-the VCHIQ node to stay in the usual place in the DT.
-
-Signed-off-by: Phil Elwell <phil@raspberrypi.com>
----
- .../interface/vchiq_arm/vchiq_arm.c | 125 +++++++++++++-----
- 1 file changed, 90 insertions(+), 35 deletions(-)
-
---- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
-+++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
-@@ -73,6 +73,7 @@ static struct platform_device *bcm2835_i
-
- struct vchiq_drvdata {
- const unsigned int cache_line_size;
-+ const bool use_36bit_addrs;
- struct rpi_firmware *fw;
- };
-
-@@ -118,6 +119,11 @@ struct vchiq_arm_state {
- int first_connect;
- };
-
-+static struct vchiq_drvdata bcm2711_drvdata = {
-+ .cache_line_size = 64,
-+ .use_36bit_addrs = true,
-+};
-+
- struct vchiq_pagelist_info {
- struct pagelist *pagelist;
- size_t pagelist_buffer_size;
-@@ -142,10 +148,12 @@ static void __iomem *g_regs;
- * of 32.
- */
- static unsigned int g_cache_line_size = 32;
-+static unsigned int g_use_36bit_addrs = 0;
- static unsigned int g_fragments_size;
- static char *g_fragments_base;
- static char *g_free_fragments;
- static struct semaphore g_free_fragments_sema;
-+static struct device *g_dma_dev;
-
- static DEFINE_SEMAPHORE(g_free_fragments_mutex, 1);
-
-@@ -175,7 +183,7 @@ static void
- cleanup_pagelistinfo(struct vchiq_instance *instance, struct vchiq_pagelist_info *pagelistinfo)
- {
- if (pagelistinfo->scatterlist_mapped) {
-- dma_unmap_sg(instance->state->dev, pagelistinfo->scatterlist,
-+ dma_unmap_sg(g_dma_dev, pagelistinfo->scatterlist,
- pagelistinfo->num_pages, pagelistinfo->dma_dir);
- }
-
-@@ -335,7 +343,7 @@ create_pagelist(struct vchiq_instance *i
- count -= len;
- }
-
-- dma_buffers = dma_map_sg(instance->state->dev,
-+ dma_buffers = dma_map_sg(g_dma_dev,
- scatterlist,
- num_pages,
- pagelistinfo->dma_dir);
-@@ -349,22 +357,61 @@ create_pagelist(struct vchiq_instance *i
-
- /* Combine adjacent blocks for performance */
- k = 0;
-- for_each_sg(scatterlist, sg, dma_buffers, i) {
-- u32 len = sg_dma_len(sg);
-- u32 addr = sg_dma_address(sg);
--
-- /* Note: addrs is the address + page_count - 1
-- * The firmware expects blocks after the first to be page-
-- * aligned and a multiple of the page size
-- */
-- WARN_ON(len == 0);
-- WARN_ON(i && (i != (dma_buffers - 1)) && (len & ~PAGE_MASK));
-- WARN_ON(i && (addr & ~PAGE_MASK));
-- if (is_adjacent_block(addrs, addr, k))
-- addrs[k - 1] += ((len + PAGE_SIZE - 1) >> PAGE_SHIFT);
-- else
-- addrs[k++] = (addr & PAGE_MASK) |
-- (((len + PAGE_SIZE - 1) >> PAGE_SHIFT) - 1);
-+ if (g_use_36bit_addrs) {
-+ for_each_sg(scatterlist, sg, dma_buffers, i) {
-+ u32 len = sg_dma_len(sg);
-+ u64 addr = sg_dma_address(sg);
-+ u32 page_id = (u32)((addr >> 4) & ~0xff);
-+ u32 sg_pages = (len + PAGE_SIZE - 1) >> PAGE_SHIFT;
-+
-+ /* Note: addrs is the address + page_count - 1
-+ * The firmware expects blocks after the first to be page-
-+ * aligned and a multiple of the page size
-+ */
-+ WARN_ON(len == 0);
-+ WARN_ON(i &&
-+ (i != (dma_buffers - 1)) && (len & ~PAGE_MASK));
-+ WARN_ON(i && (addr & ~PAGE_MASK));
-+ WARN_ON(upper_32_bits(addr) > 0xf);
-+
-+ if (k > 0 &&
-+ ((addrs[k - 1] & ~0xff) +
-+ (((addrs[k - 1] & 0xff) + 1) << 8)
-+ == page_id)) {
-+ u32 inc_pages = min(sg_pages,
-+ 0xff - (addrs[k - 1] & 0xff));
-+ addrs[k - 1] += inc_pages;
-+ page_id += inc_pages << 8;
-+ sg_pages -= inc_pages;
-+ }
-+ while (sg_pages) {
-+ u32 inc_pages = min(sg_pages, 0x100u);
-+ addrs[k++] = page_id | (inc_pages - 1);
-+ page_id += inc_pages << 8;
-+ sg_pages -= inc_pages;
-+ }
-+ }
-+ } else {
-+ for_each_sg(scatterlist, sg, dma_buffers, i) {
-+ u32 len = sg_dma_len(sg);
-+ u32 addr = sg_dma_address(sg);
-+ u32 new_pages = (len + PAGE_SIZE - 1) >> PAGE_SHIFT;
-+
-+ /* Note: addrs is the address + page_count - 1
-+ * The firmware expects blocks after the first to be page-
-+ * aligned and a multiple of the page size
-+ */
-+ WARN_ON(len == 0);
-+ WARN_ON(i && (i != (dma_buffers - 1)) && (len & ~PAGE_MASK));
-+ WARN_ON(i && (addr & ~PAGE_MASK));
-+ if (k > 0 &&
-+ ((addrs[k - 1] & PAGE_MASK) +
-+ (((addrs[k - 1] & ~PAGE_MASK) + 1) << PAGE_SHIFT))
-+ == (addr & PAGE_MASK))
-+ addrs[k - 1] += new_pages;
-+ else
-+ addrs[k++] = (addr & PAGE_MASK) | (new_pages - 1);
-+ }
- }
-
- /* Partial cache lines (fragments) require special measures */
-@@ -408,7 +455,7 @@ free_pagelist(struct vchiq_instance *ins
- * NOTE: dma_unmap_sg must be called before the
- * cpu can touch any of the data/pages.
- */
-- dma_unmap_sg(instance->state->dev, pagelistinfo->scatterlist,
-+ dma_unmap_sg(g_dma_dev, pagelistinfo->scatterlist,
- pagelistinfo->num_pages, pagelistinfo->dma_dir);
- pagelistinfo->scatterlist_mapped = 0;
-
-@@ -463,6 +510,7 @@ free_pagelist(struct vchiq_instance *ins
- static int vchiq_platform_init(struct platform_device *pdev, struct vchiq_state *state)
- {
- struct device *dev = &pdev->dev;
-+ struct device *dma_dev = NULL;
- struct vchiq_drvdata *drvdata = platform_get_drvdata(pdev);
- struct rpi_firmware *fw = drvdata->fw;
- struct vchiq_slot_zero *vchiq_slot_zero;
-@@ -484,6 +532,24 @@ static int vchiq_platform_init(struct pl
- g_cache_line_size = drvdata->cache_line_size;
- g_fragments_size = 2 * g_cache_line_size;
-
-+ if (drvdata->use_36bit_addrs) {
-+ struct device_node *dma_node =
-+ of_find_compatible_node(NULL, NULL, "brcm,bcm2711-dma");
-+
-+ if (dma_node) {
-+ struct platform_device *pdev;
-+
-+ pdev = of_find_device_by_node(dma_node);
-+ if (pdev)
-+ dma_dev = &pdev->dev;
-+ of_node_put(dma_node);
-+ g_use_36bit_addrs = true;
-+ } else {
-+ dev_err(dev, "40-bit DMA controller not found\n");
-+ return -EINVAL;
-+ }
-+ }
-+
- /* Allocate space for the channels in coherent memory */
- slot_mem_size = PAGE_ALIGN(TOTAL_SLOTS * VCHIQ_SLOT_SIZE);
- frag_mem_size = PAGE_ALIGN(g_fragments_size * MAX_FRAGMENTS);
-@@ -496,13 +562,14 @@ static int vchiq_platform_init(struct pl
- }
-
- WARN_ON(((unsigned long)slot_mem & (PAGE_SIZE - 1)) != 0);
-+ channelbase = slot_phys;
-
- vchiq_slot_zero = vchiq_init_slots(slot_mem, slot_mem_size);
- if (!vchiq_slot_zero)
- return -ENOMEM;
-
- vchiq_slot_zero->platform_data[VCHIQ_PLATFORM_FRAGMENTS_OFFSET_IDX] =
-- (int)slot_phys + slot_mem_size;
-+ channelbase + slot_mem_size;
- vchiq_slot_zero->platform_data[VCHIQ_PLATFORM_FRAGMENTS_COUNT_IDX] =
- MAX_FRAGMENTS;
-
-@@ -536,7 +603,6 @@ static int vchiq_platform_init(struct pl
- }
-
- /* Send the base address of the slots to VideoCore */
-- channelbase = slot_phys;
- err = rpi_firmware_property(fw, RPI_FIRMWARE_VCHIQ_INIT,
- &channelbase, sizeof(channelbase));
- if (err) {
-@@ -550,6 +616,8 @@ static int vchiq_platform_init(struct pl
- return -ENXIO;
- }
-
-+ g_dma_dev = dma_dev ?: dev;
-+
- vchiq_log_info(vchiq_arm_log_level, "vchiq_init - done (slots %pK, phys %pad)",
- vchiq_slot_zero, &slot_phys);
-
-@@ -1755,6 +1823,7 @@ void vchiq_platform_conn_state_changed(s
- static const struct of_device_id vchiq_of_match[] = {
- { .compatible = "brcm,bcm2835-vchiq", .data = &bcm2835_drvdata },
- { .compatible = "brcm,bcm2836-vchiq", .data = &bcm2836_drvdata },
-+ { .compatible = "brcm,bcm2711-vchiq", .data = &bcm2711_drvdata },
- {},
- };
- MODULE_DEVICE_TABLE(of, vchiq_of_match);
-@@ -1787,22 +1856,8 @@ vchiq_register_child(struct platform_dev
-
- child->dev.of_node = np;
-
-- /*
-- * We want the dma-ranges etc to be copied from a device with the
-- * correct dma-ranges for the VPU.
-- * VCHIQ on Pi4 is now under scb which doesn't get those dma-ranges.
-- * Take the "dma" node as going to be suitable as it sees the world
-- * through the same eyes as the VPU.
-- */
-- np = of_find_node_by_path("dma");
-- if (!np)
-- np = pdev->dev.of_node;
--
- of_dma_configure(&child->dev, np, true);
-
-- if (np != pdev->dev.of_node)
-- of_node_put(np);
--
- return child;
- }
-
+++ /dev/null
-From d4712f611e6d60dd9cf09df581f5df6fad6a2207 Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Tue, 21 Jul 2020 17:34:09 +0100
-Subject: [PATCH 0298/1085] staging: vchiq_arm: children inherit DMA config
-
-Although it is no longer necessary for vchiq's children to have a
-different DMA configuration to the parent, they do still need to
-explicitly to have their DMA configuration set - to be that of the
-parent.
-
-Signed-off-by: Phil Elwell <phil@raspberrypi.com>
----
- .../vc04_services/interface/vchiq_arm/vchiq_arm.c | 10 ++++++++++
- 1 file changed, 10 insertions(+)
-
---- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
-+++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
-@@ -1856,8 +1856,18 @@ vchiq_register_child(struct platform_dev
-
- child->dev.of_node = np;
-
-+ /*
-+ * We want the dma-ranges etc to be copied from the parent VCHIQ device
-+ * to be passed on to the children without a node of their own.
-+ */
-+ if (!np)
-+ np = pdev->dev.of_node;
-+
- of_dma_configure(&child->dev, np, true);
-
-+ if (np != pdev->dev.of_node)
-+ of_node_put(np);
-+
- return child;
- }
-
+++ /dev/null
-From 9f328c347fc9a5495b8383aa2bae1d3bc242a2ab Mon Sep 17 00:00:00 2001
-From: detule <ogjoneski@gmail.com>
-Date: Tue, 2 Oct 2018 04:10:08 -0400
-Subject: [PATCH 0299/1085] staging: vchiq_arm: Usa a DMA pool for small bulks
-
-During a bulk transfer we request a DMA allocation to hold the
-scatter-gather list. Most of the time, this allocation is small
-(<< PAGE_SIZE), however it can be requested at a high enough frequency
-to cause fragmentation and/or stress the CMA allocator (think time
-spent in compaction here, or during allocations elsewhere).
-
-Implement a pool to serve up small DMA allocations, falling back
-to a coherent allocation if the request is greater than
-VCHIQ_DMA_POOL_SIZE.
-
-Signed-off-by: Oliver Gjoneski <ogjoneski@gmail.com>
----
- .../interface/vchiq_arm/vchiq_arm.c | 33 ++++++++++++++++---
- 1 file changed, 29 insertions(+), 4 deletions(-)
-
---- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
-+++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
-@@ -22,6 +22,7 @@
- #include <linux/platform_device.h>
- #include <linux/compat.h>
- #include <linux/dma-mapping.h>
-+#include <linux/dmapool.h>
- #include <linux/rcupdate.h>
- #include <linux/delay.h>
- #include <linux/slab.h>
-@@ -51,6 +52,8 @@
-
- #define ARM_DS_ACTIVE BIT(2)
-
-+#define VCHIQ_DMA_POOL_SIZE PAGE_SIZE
-+
- /* Override the default prefix, which would be vchiq_arm (from the filename) */
- #undef MODULE_PARAM_PREFIX
- #define MODULE_PARAM_PREFIX DEVICE_NAME "."
-@@ -128,6 +131,7 @@ struct vchiq_pagelist_info {
- struct pagelist *pagelist;
- size_t pagelist_buffer_size;
- dma_addr_t dma_addr;
-+ bool is_from_pool;
- enum dma_data_direction dma_dir;
- unsigned int num_pages;
- unsigned int pages_need_release;
-@@ -148,6 +152,7 @@ static void __iomem *g_regs;
- * of 32.
- */
- static unsigned int g_cache_line_size = 32;
-+static struct dma_pool *g_dma_pool;
- static unsigned int g_use_36bit_addrs = 0;
- static unsigned int g_fragments_size;
- static char *g_fragments_base;
-@@ -190,8 +195,13 @@ cleanup_pagelistinfo(struct vchiq_instan
- if (pagelistinfo->pages_need_release)
- unpin_user_pages(pagelistinfo->pages, pagelistinfo->num_pages);
-
-- dma_free_coherent(instance->state->dev, pagelistinfo->pagelist_buffer_size,
-- pagelistinfo->pagelist, pagelistinfo->dma_addr);
-+ if (pagelistinfo->is_from_pool) {
-+ dma_pool_free(g_dma_pool, pagelistinfo->pagelist,
-+ pagelistinfo->dma_addr);
-+ } else {
-+ dma_free_coherent(instance->state->dev, pagelistinfo->pagelist_buffer_size,
-+ pagelistinfo->pagelist, pagelistinfo->dma_addr);
-+ }
- }
-
- static inline bool
-@@ -226,6 +236,7 @@ create_pagelist(struct vchiq_instance *i
- u32 *addrs;
- unsigned int num_pages, offset, i, k;
- int actual_pages;
-+ bool is_from_pool;
- size_t pagelist_size;
- struct scatterlist *scatterlist, *sg;
- int dma_buffers;
-@@ -255,8 +266,14 @@ create_pagelist(struct vchiq_instance *i
- /* Allocate enough storage to hold the page pointers and the page
- * list
- */
-- pagelist = dma_alloc_coherent(instance->state->dev, pagelist_size, &dma_addr,
-- GFP_KERNEL);
-+ if (pagelist_size > VCHIQ_DMA_POOL_SIZE) {
-+ pagelist = dma_alloc_coherent(instance->state->dev, pagelist_size, &dma_addr,
-+ GFP_KERNEL);
-+ is_from_pool = false;
-+ } else {
-+ pagelist = dma_pool_alloc(g_dma_pool, GFP_KERNEL, &dma_addr);
-+ is_from_pool = true;
-+ }
-
- vchiq_log_trace(vchiq_arm_log_level, "%s - %pK", __func__, pagelist);
-
-@@ -277,6 +294,7 @@ create_pagelist(struct vchiq_instance *i
- pagelistinfo->pagelist = pagelist;
- pagelistinfo->pagelist_buffer_size = pagelist_size;
- pagelistinfo->dma_addr = dma_addr;
-+ pagelistinfo->is_from_pool = is_from_pool;
- pagelistinfo->dma_dir = (type == PAGELIST_WRITE) ?
- DMA_TO_DEVICE : DMA_FROM_DEVICE;
- pagelistinfo->num_pages = num_pages;
-@@ -617,6 +635,13 @@ static int vchiq_platform_init(struct pl
- }
-
- g_dma_dev = dma_dev ?: dev;
-+ g_dma_pool = dmam_pool_create("vchiq_scatter_pool", dev,
-+ VCHIQ_DMA_POOL_SIZE, g_cache_line_size,
-+ 0);
-+ if (!g_dma_pool) {
-+ dev_err(dev, "failed to create dma pool");
-+ return -ENOMEM;
-+ }
-
- vchiq_log_info(vchiq_arm_log_level, "vchiq_init - done (slots %pK, phys %pad)",
- vchiq_slot_zero, &slot_phys);
+++ /dev/null
-From 79f24f7454a416fed9106c75ea9b3be480465dda Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Fri, 29 Apr 2022 09:19:10 +0100
-Subject: [PATCH 0365/1085] staging: vchiq_arm: Add log_level module params
-
-Add module parameters to control the logging levels for the various
-vchiq logging categories.
-
-Signed-off-by: Phil Elwell <phil@raspberrypi.com>
----
- .../staging/vc04_services/interface/vchiq_arm/vchiq_arm.c | 5 +++++
- 1 file changed, 5 insertions(+)
-
---- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
-+++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
-@@ -64,6 +64,11 @@
- /* Run time control of log level, based on KERN_XXX level. */
- int vchiq_arm_log_level = VCHIQ_LOG_DEFAULT;
- int vchiq_susp_log_level = VCHIQ_LOG_ERROR;
-+module_param_named(arm_log_level, vchiq_arm_log_level, int, 0644);
-+module_param_named(susp_log_level, vchiq_susp_log_level, int, 0644);
-+module_param_named(core_log_level, vchiq_core_log_level, int, 0644);
-+module_param_named(core_msg_log_level, vchiq_core_msg_log_level, int, 0644);
-+module_param_named(sync_log_level, vchiq_sync_log_level, int, 0644);
-
- DEFINE_SPINLOCK(msg_queue_spinlock);
- struct vchiq_state g_state;
+++ /dev/null
-From 24cb07b0c0724a22e474d12e7c2d5b834bf3b076 Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Tue, 26 Mar 2024 15:57:46 +0000
-Subject: [PATCH 0998/1085] i2c: designware: Add support for bus clear feature
-
-Newer versions of the DesignWare I2C block support the detection of
-stuck signals, and a mechanism to recover from them. Add the required
-software support to the driver.
-
-This change was prompted by the observation that reading a single byte
-from register 0 of a VEML7700 seems to cause it to issue an ACK too
-early, and the controller to complain about losing arbitration. There
-is a suspicion that this may be a more widespread problem, but at least
-this patch prevents the bus from locking up.
-
-See: https://github.com/raspberrypi/linux/issues/6057
-
-Signed-off-by: Phil Elwell <phil@raspberrypi.com>
----
- drivers/i2c/busses/i2c-designware-common.c | 12 ++++++++++++
- drivers/i2c/busses/i2c-designware-core.h | 8 ++++++++
- drivers/i2c/busses/i2c-designware-master.c | 19 ++++++++++++++++++-
- 3 files changed, 38 insertions(+), 1 deletion(-)
-
---- a/drivers/i2c/busses/i2c-designware-common.c
-+++ b/drivers/i2c/busses/i2c-designware-common.c
-@@ -57,6 +57,8 @@ static char *abort_sources[] = {
- "slave lost the bus while transmitting data to a remote master",
- [ABRT_SLAVE_RD_INTX] =
- "incorrect slave-transmitter mode configuration",
-+ [ABRT_SLAVE_SDA_STUCK_AT_LOW] =
-+ "SDA stuck at low",
- };
-
- static int dw_reg_read(void *context, unsigned int reg, unsigned int *val)
-@@ -609,8 +611,16 @@ int i2c_dw_wait_bus_not_busy(struct dw_i
- int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev)
- {
- unsigned long abort_source = dev->abort_source;
-+ unsigned int reg;
- int i;
-
-+ if (abort_source & DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW) {
-+ regmap_write(dev->map, DW_IC_ENABLE,
-+ DW_IC_ENABLE_ENABLE | DW_IC_ENABLE_BUS_RECOVERY);
-+ regmap_read_poll_timeout(dev->map, DW_IC_ENABLE, reg,
-+ !(reg & DW_IC_ENABLE_BUS_RECOVERY),
-+ 1100, 200000);
-+ }
- if (abort_source & DW_IC_TX_ABRT_NOACK) {
- for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources))
- dev_dbg(dev->dev,
-@@ -625,6 +635,8 @@ int i2c_dw_handle_tx_abort(struct dw_i2c
- return -EAGAIN;
- else if (abort_source & DW_IC_TX_ABRT_GCALL_READ)
- return -EINVAL; /* wrong msgs[] data */
-+ else if (abort_source & DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW)
-+ return -EREMOTEIO;
- else
- return -EIO;
- }
---- a/drivers/i2c/busses/i2c-designware-core.h
-+++ b/drivers/i2c/busses/i2c-designware-core.h
-@@ -79,9 +79,12 @@
- #define DW_IC_TX_ABRT_SOURCE 0x80
- #define DW_IC_ENABLE_STATUS 0x9c
- #define DW_IC_CLR_RESTART_DET 0xa8
-+#define DW_IC_SCL_STUCK_AT_LOW_TIMEOUT 0xac
-+#define DW_IC_SDA_STUCK_AT_LOW_TIMEOUT 0xb0
- #define DW_IC_COMP_PARAM_1 0xf4
- #define DW_IC_COMP_VERSION 0xf8
- #define DW_IC_SDA_HOLD_MIN_VERS 0x3131312A /* "111*" == v1.11* */
-+#define DW_IC_BUS_CLEAR_MIN_VERS 0x3230302A /* "200*" == v2.00* */
- #define DW_IC_COMP_TYPE 0xfc
- #define DW_IC_COMP_TYPE_VALUE 0x44570140 /* "DW" + 0x0140 */
-
-@@ -111,6 +114,7 @@
-
- #define DW_IC_ENABLE_ENABLE BIT(0)
- #define DW_IC_ENABLE_ABORT BIT(1)
-+#define DW_IC_ENABLE_BUS_RECOVERY BIT(3)
-
- #define DW_IC_STATUS_ACTIVITY BIT(0)
- #define DW_IC_STATUS_TFE BIT(2)
-@@ -118,6 +122,7 @@
- #define DW_IC_STATUS_MASTER_ACTIVITY BIT(5)
- #define DW_IC_STATUS_SLAVE_ACTIVITY BIT(6)
- #define DW_IC_STATUS_MASTER_HOLD_TX_FIFO_EMPTY BIT(7)
-+#define DW_IC_STATUS_SDA_STUCK_NOT_RECOVERED BIT(11)
-
- #define DW_IC_SDA_HOLD_RX_SHIFT 16
- #define DW_IC_SDA_HOLD_RX_MASK GENMASK(23, 16)
-@@ -165,6 +170,7 @@
- #define ABRT_SLAVE_FLUSH_TXFIFO 13
- #define ABRT_SLAVE_ARBLOST 14
- #define ABRT_SLAVE_RD_INTX 15
-+#define ABRT_SLAVE_SDA_STUCK_AT_LOW 17
-
- #define DW_IC_TX_ABRT_7B_ADDR_NOACK BIT(ABRT_7B_ADDR_NOACK)
- #define DW_IC_TX_ABRT_10ADDR1_NOACK BIT(ABRT_10ADDR1_NOACK)
-@@ -180,6 +186,7 @@
- #define DW_IC_RX_ABRT_SLAVE_RD_INTX BIT(ABRT_SLAVE_RD_INTX)
- #define DW_IC_RX_ABRT_SLAVE_ARBLOST BIT(ABRT_SLAVE_ARBLOST)
- #define DW_IC_RX_ABRT_SLAVE_FLUSH_TXFIFO BIT(ABRT_SLAVE_FLUSH_TXFIFO)
-+#define DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW BIT(ABRT_SLAVE_SDA_STUCK_AT_LOW)
-
- #define DW_IC_TX_ABRT_NOACK (DW_IC_TX_ABRT_7B_ADDR_NOACK | \
- DW_IC_TX_ABRT_10ADDR1_NOACK | \
---- a/drivers/i2c/busses/i2c-designware-master.c
-+++ b/drivers/i2c/busses/i2c-designware-master.c
-@@ -212,6 +212,7 @@ static int i2c_dw_set_timings_master(str
- */
- static int i2c_dw_init_master(struct dw_i2c_dev *dev)
- {
-+ unsigned int timeout = 0;
- int ret;
-
- ret = i2c_dw_acquire_lock(dev);
-@@ -235,6 +236,17 @@ static int i2c_dw_init_master(struct dw_
- regmap_write(dev->map, DW_IC_HS_SCL_LCNT, dev->hs_lcnt);
- }
-
-+ if (dev->master_cfg & DW_IC_CON_BUS_CLEAR_CTRL) {
-+ /* Set a sensible timeout if not already configured */
-+ regmap_read(dev->map, DW_IC_SDA_STUCK_AT_LOW_TIMEOUT, &timeout);
-+ if (timeout == ~0) {
-+ /* Use 10ms as a timeout, which is 1000 cycles at 100kHz */
-+ timeout = i2c_dw_clk_rate(dev) * 10; /* clock rate is in kHz */
-+ regmap_write(dev->map, DW_IC_SDA_STUCK_AT_LOW_TIMEOUT, timeout);
-+ regmap_write(dev->map, DW_IC_SCL_STUCK_AT_LOW_TIMEOUT, timeout);
-+ }
-+ }
-+
- /* Write SDA hold time if supported */
- if (dev->sda_hold_time)
- regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time);
-@@ -1071,6 +1083,7 @@ int i2c_dw_probe_master(struct dw_i2c_de
- struct i2c_adapter *adap = &dev->adapter;
- unsigned long irq_flags;
- unsigned int ic_con;
-+ unsigned int id_ver;
- int ret;
-
- init_completion(&dev->cmd_complete);
-@@ -1106,7 +1119,11 @@ int i2c_dw_probe_master(struct dw_i2c_de
- if (ret)
- return ret;
-
-- if (ic_con & DW_IC_CON_BUS_CLEAR_CTRL)
-+ ret = regmap_read(dev->map, DW_IC_COMP_VERSION, &id_ver);
-+ if (ret)
-+ return ret;
-+
-+ if (ic_con & DW_IC_CON_BUS_CLEAR_CTRL || id_ver >= DW_IC_BUS_CLEAR_MIN_VERS)
- dev->master_cfg |= DW_IC_CON_BUS_CLEAR_CTRL;
-
- ret = dev->init(dev);
+++ /dev/null
-From cc63d552b9aab92fb581dfb08267d5af697f477b Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Wed, 18 Sep 2024 16:45:24 +0100
-Subject: [PATCH 1267/1350] dts: rp1: Disable DMA usage for UART0
-
-Some recent DMA changes have led to data loss in UART0 on Pi 5. It also
-seems that even prior to these changes there was a problem with aborted
-transfers.
-
-As this is the only RP1 UART configured for DMA, it is better to remove
-the DMA usage until it is shown to be reliable.
-
-Link: https://github.com/raspberrypi/linux/issues/6365
-
-Signed-off-by: Phil Elwell <phil@raspberrypi.com>
----
- arch/arm64/boot/dts/broadcom/rp1.dtsi | 6 +++---
- 1 file changed, 3 insertions(+), 3 deletions(-)
-
---- a/arch/arm64/boot/dts/broadcom/rp1.dtsi
-+++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi
-@@ -55,9 +55,9 @@
- interrupts = <RP1_INT_UART0 IRQ_TYPE_LEVEL_HIGH>;
- clocks = <&rp1_clocks RP1_CLK_UART &rp1_clocks RP1_PLL_SYS_PRI_PH>;
- clock-names = "uartclk", "apb_pclk";
-- dmas = <&rp1_dma RP1_DMA_UART0_TX>,
-- <&rp1_dma RP1_DMA_UART0_RX>;
-- dma-names = "tx", "rx";
-+ // dmas = <&rp1_dma RP1_DMA_UART0_TX>,
-+ // <&rp1_dma RP1_DMA_UART0_RX>;
-+ // dma-names = "tx", "rx";
- pinctrl-names = "default";
- arm,primecell-periphid = <0x00541011>;
- uart-has-rtscts;
--- /dev/null
+From 25e6acfe00f589a5989ebd2c8d21a130fb3bf106 Mon Sep 17 00:00:00 2001
+From: Naushir Patuck <naush@raspberrypi.com>
+Date: Fri, 18 Oct 2024 09:18:10 +0100
+Subject: [PATCH] drivers: media: bcm2835_isp: Cache LS table dmabuf
+
+Clients such as libcamera do not change the LS table dmabuf on every
+frame. In such cases instead of mapping/remapping the same dmabuf on
+every frame to send to the firmware, cache the dmabuf once and only
+update and remap if the dmabuf has been changed by the userland client.
+
+Signed-off-by: Naushir Patuck <naush@raspberrypi.com>
+---
+ .../bcm2835-isp/bcm2835-v4l2-isp.c | 77 +++++++++++--------
+ 1 file changed, 46 insertions(+), 31 deletions(-)
+
+--- a/drivers/staging/vc04_services/bcm2835-isp/bcm2835-v4l2-isp.c
++++ b/drivers/staging/vc04_services/bcm2835-isp/bcm2835-v4l2-isp.c
+@@ -139,6 +139,8 @@ struct bcm2835_isp_dev {
+ /* Image pipeline controls. */
+ int r_gain;
+ int b_gain;
++ struct dma_buf *last_ls_dmabuf;
++ struct mmal_parameter_lens_shading_v2 ls;
+ };
+
+ struct bcm2835_isp_buffer {
+@@ -657,18 +659,18 @@ static void bcm2835_isp_node_stop_stream
+ atomic_dec(&dev->num_streaming);
+ /* If all ports disabled, then disable the component */
+ if (atomic_read(&dev->num_streaming) == 0) {
+- struct bcm2835_isp_lens_shading ls;
+ /*
+ * The ISP component on the firmware has a reference to the
+ * dmabuf handle for the lens shading table. Pass a null handle
+ * to remove that reference now.
+ */
+- memset(&ls, 0, sizeof(ls));
++ memset(&dev->ls, 0, sizeof(dev->ls));
+ /* Must set a valid grid size for the FW */
+- ls.grid_cell_size = 16;
++ dev->ls.grid_cell_size = 16;
+ set_isp_param(&dev->node[0],
+ MMAL_PARAMETER_LENS_SHADING_OVERRIDE,
+- &ls, sizeof(ls));
++ &dev->ls, sizeof(dev->ls));
++ dev->last_ls_dmabuf = NULL;
+
+ ret = vchiq_mmal_component_disable(dev->mmal_instance,
+ dev->component);
+@@ -719,6 +721,36 @@ static inline unsigned int get_sizeimage
+ return (bpl * height * fmt->size_multiplier_x2) >> 1;
+ }
+
++static int map_ls_table(struct bcm2835_isp_dev *dev, struct dma_buf *dmabuf,
++ const struct bcm2835_isp_lens_shading *v4l2_ls)
++{
++ void *vcsm_handle;
++ int ret;
++
++ if (IS_ERR_OR_NULL(dmabuf))
++ return -EINVAL;
++
++ /*
++ * struct bcm2835_isp_lens_shading and struct
++ * mmal_parameter_lens_shading_v2 match so that we can do a
++ * simple memcpy here.
++ * Only the dmabuf to the actual table needs any manipulation.
++ */
++ memcpy(&dev->ls, v4l2_ls, sizeof(dev->ls));
++ ret = vc_sm_cma_import_dmabuf(dmabuf, &vcsm_handle);
++ if (ret) {
++ dma_buf_put(dmabuf);
++ return ret;
++ }
++
++ dev->ls.mem_handle_table = vc_sm_cma_int_handle(vcsm_handle);
++ dev->last_ls_dmabuf = dmabuf;
++
++ vc_sm_cma_free(vcsm_handle);
++
++ return 0;
++}
++
+ static int bcm2835_isp_s_ctrl(struct v4l2_ctrl *ctrl)
+ {
+ struct bcm2835_isp_dev *dev =
+@@ -754,44 +786,27 @@ static int bcm2835_isp_s_ctrl(struct v4l
+ case V4L2_CID_USER_BCM2835_ISP_LENS_SHADING:
+ {
+ struct bcm2835_isp_lens_shading *v4l2_ls;
+- struct mmal_parameter_lens_shading_v2 ls;
+- struct dma_buf *dmabuf;
+- void *vcsm_handle;
+
+ v4l2_ls = (struct bcm2835_isp_lens_shading *)ctrl->p_new.p_u8;
+- /*
+- * struct bcm2835_isp_lens_shading and struct
+- * mmal_parameter_lens_shading_v2 match so that we can do a
+- * simple memcpy here.
+- * Only the dmabuf to the actual table needs any manipulation.
+- */
+- memcpy(&ls, v4l2_ls, sizeof(ls));
++ struct dma_buf *dmabuf = dma_buf_get(v4l2_ls->dmabuf);
+
+- dmabuf = dma_buf_get(v4l2_ls->dmabuf);
+- if (IS_ERR_OR_NULL(dmabuf))
+- return -EINVAL;
+-
+- ret = vc_sm_cma_import_dmabuf(dmabuf, &vcsm_handle);
+- if (ret) {
+- dma_buf_put(dmabuf);
+- return -EINVAL;
+- }
++ if (dmabuf != dev->last_ls_dmabuf)
++ ret = map_ls_table(dev, dmabuf, v4l2_ls);
+
+- ls.mem_handle_table = vc_sm_cma_int_handle(vcsm_handle);
+- if (ls.mem_handle_table)
+- /* The VPU will take a reference on the vcsm handle,
++ if (!ret && dev->ls.mem_handle_table)
++ /*
++ * The VPU will take a reference on the vcsm handle,
+ * which in turn will retain a reference on the dmabuf.
+ * This code can therefore safely release all
+ * references to the buffer.
+ */
+- ret = set_isp_param(node,
+- MMAL_PARAMETER_LENS_SHADING_OVERRIDE,
+- &ls,
+- sizeof(ls));
++ ret =
++ set_isp_param(node,
++ MMAL_PARAMETER_LENS_SHADING_OVERRIDE,
++ &dev->ls, sizeof(dev->ls));
+ else
+ ret = -EINVAL;
+
+- vc_sm_cma_free(vcsm_handle);
+ dma_buf_put(dmabuf);
+ break;
+ }
+++ /dev/null
-From 0d58d8cfb6f989f290d983552fcaa116e582e84a Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Thu, 31 Oct 2024 17:33:38 +0000
-Subject: [PATCH] mailbox: Add RP1 mailbox support
-
-The Raspberry Pi RP1 includes 2 M3 cores running firmware. This driver
-adds a mailbox communication channel to them via a doorbell and some
-shared memory.
-
-Signed-off-by: Phil Elwell <phil@raspberrypi.com>
----
- drivers/mailbox/Kconfig | 9 ++
- drivers/mailbox/Makefile | 2 +
- drivers/mailbox/rp1-mailbox.c | 208 ++++++++++++++++++++++++++++++++++
- 3 files changed, 219 insertions(+)
- create mode 100644 drivers/mailbox/rp1-mailbox.c
-
---- a/drivers/mailbox/Kconfig
-+++ b/drivers/mailbox/Kconfig
-@@ -295,4 +295,13 @@ config QCOM_IPCC
- acts as an interrupt controller for receiving interrupts from clients.
- Say Y here if you want to build this driver.
-
-+config MBOX_RP1
-+ tristate "RP1 Mailbox"
-+ depends on MFD_RP1
-+ help
-+ An implementation of a mailbox interface to the Raspberry Pi RP1 I/O
-+ interface. Although written as a mailbox driver, the hardware only
-+ provides an array of 32 doorbells.
-+ Say Y here if you want to use the RP1 Mailbox.
-+
- endif
---- a/drivers/mailbox/Makefile
-+++ b/drivers/mailbox/Makefile
-@@ -62,3 +62,5 @@ obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox
- obj-$(CONFIG_QCOM_IPCC) += qcom-ipcc.o
-
- obj-$(CONFIG_APPLE_MAILBOX) += apple-mailbox.o
-+
-+obj-$(CONFIG_MBOX_RP1) += rp1-mailbox.o
---- /dev/null
-+++ b/drivers/mailbox/rp1-mailbox.c
-@@ -0,0 +1,208 @@
-+// SPDX-License-Identifier: GPL-2.0
-+/*
-+ * Copyright (C) 2023 Raspberry Pi Ltd.
-+ *
-+ * Parts of this driver are based on:
-+ * - bcm2835-mailbox.c
-+ * Copyright (C) 2010,2015 Broadcom
-+ * Copyright (C) 2013-2014 Lubomir Rintel
-+ * Copyright (C) 2013 Craig McGeachie
-+ */
-+
-+#include <linux/compat.h>
-+#include <linux/device.h>
-+#include <linux/dma-mapping.h>
-+#include <linux/err.h>
-+#include <linux/interrupt.h>
-+#include <linux/irq.h>
-+#include <linux/kernel.h>
-+#include <linux/mailbox_controller.h>
-+#include <linux/miscdevice.h>
-+#include <linux/module.h>
-+#include <linux/of_address.h>
-+#include <linux/of_irq.h>
-+#include <linux/platform_device.h>
-+
-+/*
-+ * RP1's PROC_EVENTS register can generate interrupts on the M3 cores (when
-+ * enabled). The 32-bit register is treated as 32 events, all of which share a
-+ * common interrupt. HOST_EVENTS is the same in the reverse direction.
-+ */
-+#define SYSCFG_PROC_EVENTS 0x00000008
-+#define SYSCFG_HOST_EVENTS 0x0000000c
-+#define SYSCFG_HOST_EVENT_IRQ_EN 0x00000010
-+#define SYSCFG_HOST_EVENT_IRQ 0x00000014
-+
-+#define HW_SET_BITS 0x00002000
-+#define HW_CLR_BITS 0x00003000
-+
-+#define MAX_CHANS 4 /* 32 is the hardware limit */
-+
-+struct rp1_mbox {
-+ void __iomem *regs;
-+ unsigned int irq;
-+ struct mbox_controller controller;
-+};
-+
-+static struct rp1_mbox *rp1_chan_mbox(struct mbox_chan *chan)
-+{
-+ return container_of(chan->mbox, struct rp1_mbox, controller);
-+}
-+
-+static unsigned int rp1_chan_event(struct mbox_chan *chan)
-+{
-+ return (unsigned int)(uintptr_t)chan->con_priv;
-+}
-+
-+static irqreturn_t rp1_mbox_irq(int irq, void *dev_id)
-+{
-+ struct rp1_mbox *mbox = dev_id;
-+ struct mbox_chan *chan;
-+ unsigned int doorbell;
-+ unsigned int evs;
-+
-+ evs = readl(mbox->regs + SYSCFG_HOST_EVENT_IRQ);
-+ writel(evs, mbox->regs + SYSCFG_HOST_EVENTS + HW_CLR_BITS);
-+
-+ while (evs) {
-+ doorbell = __ffs(evs);
-+ chan = &mbox->controller.chans[doorbell];
-+ mbox_chan_received_data(chan, NULL);
-+ evs &= ~(1 << doorbell);
-+ }
-+ return IRQ_HANDLED;
-+}
-+
-+static int rp1_send_data(struct mbox_chan *chan, void *data)
-+{
-+ struct rp1_mbox *mbox = rp1_chan_mbox(chan);
-+ unsigned int event = rp1_chan_event(chan);
-+
-+ writel(event, mbox->regs + SYSCFG_PROC_EVENTS + HW_SET_BITS);
-+
-+ return 0;
-+}
-+
-+static int rp1_startup(struct mbox_chan *chan)
-+{
-+ struct rp1_mbox *mbox = rp1_chan_mbox(chan);
-+ unsigned int event = rp1_chan_event(chan);
-+
-+ writel(event, mbox->regs + SYSCFG_HOST_EVENT_IRQ_EN + HW_SET_BITS);
-+
-+ return 0;
-+}
-+
-+static void rp1_shutdown(struct mbox_chan *chan)
-+{
-+ struct rp1_mbox *mbox = rp1_chan_mbox(chan);
-+ unsigned int event = rp1_chan_event(chan);
-+
-+ writel(event, mbox->regs + SYSCFG_HOST_EVENT_IRQ_EN + HW_CLR_BITS);
-+}
-+
-+static bool rp1_last_tx_done(struct mbox_chan *chan)
-+{
-+ struct rp1_mbox *mbox = rp1_chan_mbox(chan);
-+ unsigned int event = rp1_chan_event(chan);
-+ unsigned int evs;
-+
-+ evs = readl(mbox->regs + SYSCFG_HOST_EVENT_IRQ);
-+
-+ return !(evs & event);
-+}
-+
-+static const struct mbox_chan_ops rp1_mbox_chan_ops = {
-+ .send_data = rp1_send_data,
-+ .startup = rp1_startup,
-+ .shutdown = rp1_shutdown,
-+ .last_tx_done = rp1_last_tx_done
-+};
-+
-+static struct mbox_chan *rp1_mbox_xlate(struct mbox_controller *mbox,
-+ const struct of_phandle_args *spec)
-+{
-+ struct mbox_chan *chan;
-+ unsigned int doorbell;
-+
-+ if (spec->args_count != 1)
-+ return ERR_PTR(-EINVAL);
-+
-+ doorbell = spec->args[0];
-+ if (doorbell >= MAX_CHANS)
-+ return ERR_PTR(-EINVAL);
-+
-+ chan = &mbox->chans[doorbell];
-+ if (chan->con_priv)
-+ return ERR_PTR(-EBUSY);
-+
-+ chan->con_priv = (void *)(uintptr_t)(1 << doorbell);
-+
-+ return chan;
-+}
-+
-+static int rp1_mbox_probe(struct platform_device *pdev)
-+{
-+ struct device *dev = &pdev->dev;
-+ struct mbox_chan *chans;
-+ struct rp1_mbox *mbox;
-+ int ret = 0;
-+
-+ mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
-+ if (mbox == NULL)
-+ return -ENOMEM;
-+
-+ ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
-+ rp1_mbox_irq, 0, dev_name(dev), mbox);
-+ if (ret) {
-+ dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n",
-+ ret);
-+ return -ENODEV;
-+ }
-+
-+ mbox->regs = devm_platform_ioremap_resource(pdev, 0);
-+ if (IS_ERR(mbox->regs)) {
-+ ret = PTR_ERR(mbox->regs);
-+ return ret;
-+ }
-+
-+ chans = devm_kcalloc(dev, MAX_CHANS, sizeof(*chans), GFP_KERNEL);
-+ if (!chans)
-+ return -ENOMEM;
-+
-+ mbox->controller.txdone_poll = true;
-+ mbox->controller.txpoll_period = 5;
-+ mbox->controller.ops = &rp1_mbox_chan_ops;
-+ mbox->controller.of_xlate = &rp1_mbox_xlate;
-+ mbox->controller.dev = dev;
-+ mbox->controller.num_chans = MAX_CHANS;
-+ mbox->controller.chans = chans;
-+
-+ ret = devm_mbox_controller_register(dev, &mbox->controller);
-+ if (ret)
-+ return ret;
-+
-+ platform_set_drvdata(pdev, mbox);
-+
-+ return 0;
-+}
-+
-+static const struct of_device_id rp1_mbox_of_match[] = {
-+ { .compatible = "raspberrypi,rp1-mbox", },
-+ {},
-+};
-+MODULE_DEVICE_TABLE(of, rp1_mbox_of_match);
-+
-+static struct platform_driver rp1_mbox_driver = {
-+ .driver = {
-+ .name = "rp1-mbox",
-+ .of_match_table = rp1_mbox_of_match,
-+ },
-+ .probe = rp1_mbox_probe,
-+};
-+
-+module_platform_driver(rp1_mbox_driver);
-+
-+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
-+MODULE_DESCRIPTION("RP1 mailbox IPC driver");
-+MODULE_LICENSE("GPL v2");
+++ /dev/null
-From 67daeadcaa7cee1f4b9df7aa108d199e73f35451 Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Thu, 31 Oct 2024 17:36:54 +0000
-Subject: [PATCH] firmware: Add an RP1 firmware interface over mbox
-
-The RP1 firmware runs a simple communications channel over some shared
-memory and a mailbox. This driver provides access to that channel.
-
-Signed-off-by: Phil Elwell <phil@raspberrypi.com>
----
- drivers/firmware/Kconfig | 9 +
- drivers/firmware/Makefile | 1 +
- drivers/firmware/rp1.c | 316 +++++++++++++++++++++++++++++++++++
- include/linux/rp1-firmware.h | 53 ++++++
- 4 files changed, 379 insertions(+)
- create mode 100644 drivers/firmware/rp1.c
- create mode 100644 include/linux/rp1-firmware.h
-
---- a/drivers/firmware/Kconfig
-+++ b/drivers/firmware/Kconfig
-@@ -153,6 +153,15 @@ config RASPBERRYPI_FIRMWARE
- This option enables support for communicating with the firmware on the
- Raspberry Pi.
-
-+config FIRMWARE_RP1
-+ tristate "RP1 Firmware Driver"
-+ depends on MBOX_RP1
-+ help
-+ The Raspberry Pi RP1 processor presents a firmware
-+ interface using shared memory and a mailbox. To enable
-+ the driver that communicates with it, say Y. Otherwise,
-+ say N.
-+
- config FW_CFG_SYSFS
- tristate "QEMU fw_cfg device support in sysfs"
- depends on SYSFS && (ARM || ARM64 || PARISC || PPC_PMAC || SPARC || X86)
---- a/drivers/firmware/Makefile
-+++ b/drivers/firmware/Makefile
-@@ -17,6 +17,7 @@ obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o
- obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o
- obj-$(CONFIG_MTK_ADSP_IPC) += mtk-adsp-ipc.o
- obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o
-+obj-$(CONFIG_FIRMWARE_RP1) += rp1.o
- obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o
- obj-$(CONFIG_QCOM_SCM) += qcom-scm.o
- qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
---- /dev/null
-+++ b/drivers/firmware/rp1.c
-@@ -0,0 +1,316 @@
-+// SPDX-License-Identifier: GPL-2.0
-+/*
-+ * Copyright (C) 2023-24 Raspberry Pi Ltd.
-+ *
-+ * Parts of this driver are based on:
-+ * - raspberrypi.c, by Eric Anholt <eric@anholt.net>
-+ * Copyright (C) 2015 Broadcom
-+ */
-+
-+#include <linux/dma-mapping.h>
-+#include <linux/kref.h>
-+#include <linux/mailbox_client.h>
-+#include <linux/module.h>
-+#include <linux/of_address.h>
-+#include <linux/of_platform.h>
-+#include <linux/platform_device.h>
-+#include <linux/rp1-firmware.h>
-+
-+#define RP1_MAILBOX_FIRMWARE 0
-+
-+enum rp1_firmware_ops {
-+ MBOX_SUCCESS = 0x0000,
-+ GET_FIRMWARE_VERSION = 0x0001, // na -> 160-bit version
-+ GET_FEATURE = 0x0002, // FOURCC -> op base (0 == unsupported), op count
-+
-+ COMMON_COUNT
-+};
-+
-+struct rp1_firmware {
-+ struct mbox_client cl;
-+ struct mbox_chan *chan; /* The doorbell channel */
-+ uint32_t __iomem *buf; /* The shared buffer */
-+ u32 buf_size; /* The size of the shared buffer */
-+ struct completion c;
-+
-+ struct kref consumers;
-+};
-+
-+struct rp1_get_feature_resp {
-+ uint32_t op_base;
-+ uint32_t op_count;
-+};
-+
-+static DEFINE_MUTEX(transaction_lock);
-+
-+static const struct of_device_id rp1_firmware_of_match[] = {
-+ { .compatible = "raspberrypi,rp1-firmware", },
-+ {},
-+};
-+MODULE_DEVICE_TABLE(of, rp1_firmware_of_match);
-+
-+static void response_callback(struct mbox_client *cl, void *msg)
-+{
-+ struct rp1_firmware *fw = container_of(cl, struct rp1_firmware, cl);
-+
-+ complete(&fw->c);
-+}
-+
-+/*
-+ * Sends a request to the RP1 firmware and synchronously waits for the reply.
-+ * Returns zero or a positive count of response bytes on success, negative on
-+ * error.
-+ */
-+
-+int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
-+ const void *data, unsigned int data_len,
-+ void *resp, unsigned int resp_space)
-+{
-+ int ret;
-+ u32 rc;
-+
-+ if (data_len + 4 > fw->buf_size)
-+ return -EINVAL;
-+
-+ mutex_lock(&transaction_lock);
-+
-+ memcpy_toio(&fw->buf[1], data, data_len);
-+ writel((op << 16) | data_len, fw->buf);
-+
-+ reinit_completion(&fw->c);
-+ ret = mbox_send_message(fw->chan, NULL);
-+ if (ret >= 0) {
-+ if (wait_for_completion_timeout(&fw->c, HZ))
-+ ret = 0;
-+ else
-+ ret = -ETIMEDOUT;
-+ } else {
-+ dev_err(fw->cl.dev, "mbox_send_message returned %d\n", ret);
-+ }
-+
-+ if (ret == 0) {
-+ rc = readl(fw->buf);
-+ if (rc & 0x80000000) {
-+ ret = (int32_t)rc;
-+ } else {
-+ ret = min(rc, resp_space);
-+ memcpy_fromio(resp, &fw->buf[1], ret);
-+ }
-+ }
-+
-+ mutex_unlock(&transaction_lock);
-+
-+ return ret;
-+}
-+EXPORT_SYMBOL_GPL(rp1_firmware_message);
-+
-+static void rp1_firmware_delete(struct kref *kref)
-+{
-+ struct rp1_firmware *fw = container_of(kref, struct rp1_firmware, consumers);
-+
-+ mbox_free_channel(fw->chan);
-+ kfree(fw);
-+}
-+
-+void rp1_firmware_put(struct rp1_firmware *fw)
-+{
-+ kref_put(&fw->consumers, rp1_firmware_delete);
-+}
-+EXPORT_SYMBOL_GPL(rp1_firmware_put);
-+
-+int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
-+ uint32_t *op_base, uint32_t *op_count)
-+{
-+ struct rp1_get_feature_resp resp;
-+ int ret;
-+
-+ memset(&resp, 0, sizeof(resp));
-+ ret = rp1_firmware_message(fw, GET_FEATURE,
-+ &fourcc, sizeof(fourcc),
-+ &resp, sizeof(resp));
-+ *op_base = resp.op_base;
-+ *op_count = resp.op_count;
-+ if (ret < 0)
-+ return ret;
-+ if (ret < sizeof(resp) || !resp.op_base)
-+ return -EOPNOTSUPP;
-+ return 0;
-+}
-+EXPORT_SYMBOL_GPL(rp1_firmware_get_feature);
-+
-+static void devm_rp1_firmware_put(void *data)
-+{
-+ struct rp1_firmware *fw = data;
-+
-+ rp1_firmware_put(fw);
-+}
-+
-+/**
-+ * rp1_firmware_get - Get pointer to rp1_firmware structure.
-+ *
-+ * The reference to rp1_firmware has to be released with rp1_firmware_put().
-+ *
-+ * Returns an error pointer on failure.
-+ */
-+struct rp1_firmware *rp1_firmware_get(struct device_node *client)
-+{
-+ const char *match = rp1_firmware_of_match[0].compatible;
-+ struct platform_device *pdev;
-+ struct device_node *fwnode;
-+ struct rp1_firmware *fw;
-+
-+ if (client) {
-+ fwnode = of_parse_phandle(client, "firmware", 0);
-+ if (!fwnode)
-+ fwnode = of_get_parent(client);
-+ if (fwnode && !of_device_is_compatible(fwnode, match)) {
-+ of_node_put(fwnode);
-+ fwnode = NULL;
-+ }
-+ }
-+
-+ if (!fwnode)
-+ fwnode = of_find_matching_node(NULL, rp1_firmware_of_match);
-+
-+ if (!fwnode)
-+ return ERR_PTR(-ENOENT);
-+
-+ pdev = of_find_device_by_node(fwnode);
-+ of_node_put(fwnode);
-+
-+ if (!pdev)
-+ return ERR_PTR(-EPROBE_DEFER);
-+
-+ fw = platform_get_drvdata(pdev);
-+ if (!fw)
-+ goto err_defer;
-+
-+ if (!kref_get_unless_zero(&fw->consumers))
-+ goto err_defer;
-+
-+ put_device(&pdev->dev);
-+
-+ return fw;
-+
-+err_defer:
-+ put_device(&pdev->dev);
-+ return ERR_PTR(-EPROBE_DEFER);
-+}
-+EXPORT_SYMBOL_GPL(rp1_firmware_get);
-+
-+/**
-+ * devm_rp1_firmware_get - Get pointer to rp1_firmware structure.
-+ * @firmware_node: Pointer to the firmware Device Tree node.
-+ *
-+ * Returns NULL is the firmware device is not ready.
-+ */
-+struct rp1_firmware *devm_rp1_firmware_get(struct device *dev, struct device_node *client)
-+{
-+ struct rp1_firmware *fw;
-+ int ret;
-+
-+ fw = rp1_firmware_get(client);
-+ if (IS_ERR(fw))
-+ return fw;
-+
-+ ret = devm_add_action_or_reset(dev, devm_rp1_firmware_put, fw);
-+ if (ret)
-+ return ERR_PTR(ret);
-+
-+ return fw;
-+}
-+EXPORT_SYMBOL_GPL(devm_rp1_firmware_get);
-+
-+static int rp1_firmware_probe(struct platform_device *pdev)
-+{
-+ struct device *dev = &pdev->dev;
-+ struct device_node *shmem;
-+ struct rp1_firmware *fw;
-+ struct resource res;
-+ uint32_t version[5];
-+ int ret;
-+
-+ shmem = of_parse_phandle(dev->of_node, "shmem", 0);
-+ if (!of_device_is_compatible(shmem, "raspberrypi,rp1-shmem")) {
-+ of_node_put(shmem);
-+ return -ENXIO;
-+ }
-+
-+ ret = of_address_to_resource(shmem, 0, &res);
-+ of_node_put(shmem);
-+ if (ret) {
-+ dev_err(dev, "failed to get shared memory (%pOF) - %d\n", shmem, ret);
-+ return ret;
-+ }
-+
-+ /*
-+ * Memory will be freed by rp1_firmware_delete() once all users have
-+ * released their firmware handles. Don't use devm_kzalloc() here.
-+ */
-+ fw = kzalloc(sizeof(*fw), GFP_KERNEL);
-+ if (!fw)
-+ return -ENOMEM;
-+
-+ fw->buf_size = resource_size(&res);
-+ fw->buf = devm_ioremap(dev, res.start, fw->buf_size);
-+ if (!fw->buf) {
-+ dev_err(dev, "failed to ioremap shared memory\n");
-+ kfree(fw);
-+ return -EADDRNOTAVAIL;
-+ }
-+
-+ fw->cl.dev = dev;
-+ fw->cl.rx_callback = response_callback;
-+ fw->cl.tx_block = false;
-+
-+ fw->chan = mbox_request_channel(&fw->cl, RP1_MAILBOX_FIRMWARE);
-+ if (IS_ERR(fw->chan)) {
-+ int ret = PTR_ERR(fw->chan);
-+
-+ if (ret != -EPROBE_DEFER)
-+ dev_err(dev, "Failed to get mbox channel: %d\n", ret);
-+ kfree(fw);
-+ return ret;
-+ }
-+
-+ init_completion(&fw->c);
-+ kref_init(&fw->consumers);
-+
-+ platform_set_drvdata(pdev, fw);
-+
-+ ret = rp1_firmware_message(fw, GET_FIRMWARE_VERSION,
-+ NULL, 0, &version, sizeof(version));
-+ if (ret == sizeof(version)) {
-+ dev_info(dev, "RP1 Firmware version %08x%08x%08x%08x%08x\n",
-+ version[0], version[1], version[2], version[3], version[4]);
-+ ret = 0;
-+ } else if (ret >= 0) {
-+ ret = -EIO;
-+ }
-+
-+ return ret;
-+}
-+
-+static int rp1_firmware_remove(struct platform_device *pdev)
-+{
-+ struct rp1_firmware *fw = platform_get_drvdata(pdev);
-+
-+ rp1_firmware_put(fw);
-+
-+ return 0;
-+}
-+
-+static struct platform_driver rp1_firmware_driver = {
-+ .driver = {
-+ .name = "rp1-firmware",
-+ .of_match_table = rp1_firmware_of_match,
-+ },
-+ .probe = rp1_firmware_probe,
-+ .remove = rp1_firmware_remove,
-+};
-+
-+module_platform_driver(rp1_firmware_driver);
-+
-+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
-+MODULE_DESCRIPTION("RP1 firmware driver");
-+MODULE_LICENSE("GPL v2");
---- /dev/null
-+++ b/include/linux/rp1-firmware.h
-@@ -0,0 +1,53 @@
-+/* SPDX-License-Identifier: GPL-2.0 */
-+/*
-+ * Copyright (C) 2023 2023-2024 Raspberry Pi Ltd.
-+ */
-+
-+#ifndef __SOC_RP1_FIRMWARE_H__
-+#define __SOC_RP1_FIRMWARE_H__
-+
-+#include <linux/types.h>
-+#include <linux/of_device.h>
-+
-+#define RP1_FOURCC(s) ((uint32_t)((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0)))
-+
-+struct rp1_firmware;
-+
-+#if IS_ENABLED(CONFIG_FIRMWARE_RP1)
-+int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
-+ const void *data, unsigned int data_len,
-+ void *resp, unsigned int resp_space);
-+void rp1_firmware_put(struct rp1_firmware *fw);
-+struct rp1_firmware *rp1_firmware_get(struct device_node *fwnode);
-+struct rp1_firmware *devm_rp1_firmware_get(struct device *dev, struct device_node *fwnode);
-+int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
-+ uint32_t *op_base, uint32_t *op_count);
-+#else
-+static inline int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
-+ const void *data, unsigned int data_len,
-+ void *resp, unsigned int resp_space)
-+{
-+ return -EOPNOTSUPP;
-+}
-+
-+static inline void rp1_firmware_put(struct rp1_firmware *fw) { }
-+
-+static inline struct rp1_firmware *rp1_firmware_get(struct device_node *fwnode)
-+{
-+ return NULL;
-+}
-+
-+static inline struct rp1_firmware *devm_rp1_firmware_get(struct device *dev,
-+ struct device_node *fwnode)
-+{
-+ return NULL;
-+}
-+
-+static inline int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
-+ uint32_t *op_base, uint32_t *op_count)
-+{
-+ return -EOPNOTSUPP;
-+}
-+#endif
-+
-+#endif /* __SOC_RP1_FIRMWARE_H__ */
+++ /dev/null
-From 55fd5c9018e1520d45f08cf08630a493ec7dedea 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>
----
- drivers/misc/Kconfig | 8 +
- drivers/misc/Makefile | 1 +
- drivers/misc/rp1-fw-pio.h | 53 ++
- drivers/misc/rp1-pio.c | 1064 ++++++++++++++++++++++++++++++++
- include/uapi/misc/rp1_pio_if.h | 212 +++++++
- 5 files changed, 1338 insertions(+)
- create mode 100644 drivers/misc/rp1-fw-pio.h
- create mode 100644 drivers/misc/rp1-pio.c
- 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"
-+ select FIRMWARE_RP1
-+ 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
-@@ -18,6 +18,7 @@ obj-$(CONFIG_TIFM_7XX1) += tifm_7
- obj-$(CONFIG_PHANTOM) += phantom.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,53 @@
-+/* 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_COUNT
-+};
-+
-+#endif
---- /dev/null
-+++ b/drivers/misc/rp1-pio.c
-@@ -0,0 +1,1064 @@
-+// 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/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_buf_info {
-+ void *buf;
-+ dma_addr_t phys;
-+ 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;
-+};
-+
-+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;
-+}
-+
-+static 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));
-+}
-+
-+static 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;
-+}
-+
-+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);
-+}
-+
-+static 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;
-+}
-+
-+static 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;
-+}
-+
-+static 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;
-+}
-+
-+static 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;
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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;
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static 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));
-+}
-+
-+static void rp1_pio_sm_dma_callback(void *param)
-+{
-+ struct dma_info *dma = param;
-+
-+ up(&dma->buf_sem);
-+}
-+
-+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].phys);
-+ }
-+
-+ dma_release_channel(dma->chan);
-+}
-+
-+static int rp1_pio_sm_config_xfer(struct rp1_pio_client *client, void *param)
-+{
-+ struct rp1_pio_sm_config_xfer_args *args = param;
-+ 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 = {};
-+ phys_addr_t fifo_addr;
-+ struct dma_info *dma;
-+ uint32_t dma_mask;
-+ char chan_name[4];
-+ uint buf_size;
-+ int ret = 0;
-+
-+ if (args->sm >= RP1_PIO_SMS_COUNT || args->dir >= RP1_PIO_DIR_COUNT ||
-+ !args->buf_size || (args->buf_size & 3) ||
-+ !args->buf_count || args->buf_count > DMA_BOUNCE_BUFFER_COUNT)
-+ return -EINVAL;
-+
-+ dma_mask = 1 << (args->sm * 2 + args->dir);
-+
-+ dma = &pio->dma_configs[args->sm][args->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 = args->buf_size;
-+ /* Round up the allocations */
-+ buf_size = ROUND_UP(args->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] = (args->dir == RP1_PIO_DIR_TO_SM) ? 't' : 'r';
-+ chan_name[1] = 'x';
-+ chan_name[2] = '0' + args->sm;
-+ chan_name[3] = '\0';
-+
-+ dma->chan = dma_request_chan(dev, chan_name);
-+ if (IS_ERR(dma->chan))
-+ return PTR_ERR(dma->chan);
-+
-+ /* Alloc and map bounce buffers */
-+ for (dma->buf_count = 0; dma->buf_count < args->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->phys, GFP_KERNEL);
-+ if (!dbi->buf) {
-+ ret = -ENOMEM;
-+ goto err_dma_free;
-+ }
-+ sg_init_table(&dbi->sgl, 1);
-+ sg_dma_address(&dbi->sgl) = dbi->phys;
-+ }
-+
-+ fifo_addr = pio->phys_addr;
-+ fifo_addr += args->sm * (RP1_PIO_FIFO_TX1 - RP1_PIO_FIFO_TX0);
-+ fifo_addr += (args->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 = (args->dir == RP1_PIO_DIR_TO_SM) ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
-+
-+ ret = dmaengine_slave_config(dma->chan, &config);
-+ if (ret)
-+ goto err_dma_free;
-+
-+ set_dmactrl_args.sm = args->sm;
-+ set_dmactrl_args.is_tx = (args->dir == RP1_PIO_DIR_TO_SM);
-+ set_dmactrl_args.ctrl = RP1_PIO_DMACTRL_DEFAULT;
-+ if (args->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);
-+
-+ 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_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 failedzn");
-+ 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");
-+ 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");
-+ 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_async_issue_pending(dma->chan);
-+
-+ dma->head_idx++;
-+ bytes -= copy_bytes;
-+ }
-+
-+ return ret;
-+}
-+
-+static int rp1_pio_sm_xfer_data(struct rp1_pio_client *client, void *param)
-+{
-+ struct rp1_pio_sm_xfer_data_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);
-+}
-+
-+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),
-+ HANDLER(SM_XFER_DATA, sm_xfer_data),
-+
-+ 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(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),
-+};
-+
-+static int rp1_pio_open(struct inode *inode, struct file *filp)
-+{
-+ struct rp1_pio_device *pio = g_pio;
-+ struct rp1_pio_client *client;
-+
-+ client = kzalloc(sizeof(*client), GFP_KERNEL);
-+
-+ client->pio = pio;
-+ filp->private_data = client;
-+
-+ return 0;
-+}
-+
-+static int rp1_pio_release(struct inode *inode, struct file *filp)
-+{
-+ struct rp1_pio_client *client = filp->private_data;
-+ 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);
-+
-+ 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;
-+}
-+
-+const struct file_operations rp1_pio_fops = {
-+ .owner = THIS_MODULE,
-+ .open = rp1_pio_open,
-+ .release = rp1_pio_release,
-+ .unlocked_ioctl = rp1_pio_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;
-+ }
-+
-+ fw = devm_rp1_firmware_get(dev, dev->of_node);
-+ if (IS_ERR(fw))
-+ 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;
-+ }
-+
-+ 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_unregister;
-+ }
-+
-+ 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_cdev_del;
-+ }
-+ pdev->id = of_alias_get_id(pdev->dev.of_node, "pio");
-+ if (pdev->id < 0) {
-+ dev_err(dev, "alias is missing\n");
-+ return -EINVAL;
-+ 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_class_destroy;
-+ }
-+
-+ g_pio = pio;
-+
-+ dev_info(dev, "Created instance as %s\n", dev_name);
-+ return 0;
-+
-+out_class_destroy:
-+ class_destroy(pio->dev_class);
-+
-+out_cdev_del:
-+ cdev_del(&pio->cdev);
-+
-+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;
-+}
-+
-+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_new = 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/uapi/misc/rp1_pio_if.h
-@@ -0,0 +1,212 @@
-+/* SPDX-License-Identifier: GPL-2.0 */
-+/*
-+ * 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; /* IN/OUT */
-+};
-+
-+struct rp1_pio_sm_set_dmactrl_args {
-+ uint16_t sm;
-+ uint8_t is_tx;
-+ uint8_t rsvd;
-+ uint32_t ctrl;
-+};
-+
-+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_xfer_data_args {
-+ uint16_t sm;
-+ uint16_t dir;
-+ uint16_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)
-+
-+#ifdef CONFIG_COMPAT
-+//XXX #define PIO_IOC_SM_XFER_DATA32 _IOW(PIO_IOC_MAGIC, 2, struct pio_sm_xfer_data_args)
-+#endif
-+
-+#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_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
--- /dev/null
+From 3ab72fc21ea8576e59f6aad10bd6b1a0eae6e5eb Mon Sep 17 00:00:00 2001
+From: Vincent Whitchurch <vincent.whitchurch@axis.com>
+Date: Tue, 4 Jun 2024 23:00:41 +0200
+Subject: [PATCH] pwm: Add GPIO PWM driver
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+commit 7f61257cd6e1ad4769b4b819668cab00f68f2556 upstream.
+
+Add a software PWM which toggles a GPIO from a high-resolution timer.
+
+This will naturally not be as accurate or as efficient as a hardware
+PWM, but it is useful in some cases. I have for example used it for
+evaluating LED brightness handling (via leds-pwm) on a board where the
+LED was just hooked up to a GPIO, and for a simple verification of the
+timer frequency on another platform.
+
+Since high-resolution timers are used, sleeping GPIO chips are not
+supported and are rejected in the probe function.
+
+Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
+Co-developed-by: Stefan Wahren <wahrenst@gmx.net>
+Signed-off-by: Stefan Wahren <wahrenst@gmx.net>
+Co-developed-by: Linus Walleij <linus.walleij@linaro.org>
+Reviewed-by: Andy Shevchenko <andy@kernel.org>
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+Reviewed-by: Dhruva Gole <d-gole@ti.com>
+Link: https://lore.kernel.org/r/20240604-pwm-gpio-v7-2-6b67cf60db92@linaro.org
+Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>
+Signed-off-by: Tim Gover <tim.gover@raspberrypi.com>
+
+pwm: Backport pwm-gpio.c to rpi-6.6.y
+---
+ .../driver-api/gpio/drivers-on-gpio.rst | 7 +-
+ drivers/pwm/Kconfig | 11 +
+ drivers/pwm/Makefile | 1 +
+ drivers/pwm/pwm-gpio.c | 240 ++++++++++++++++++
+ 4 files changed, 258 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/pwm/pwm-gpio.c
+
+--- a/Documentation/driver-api/gpio/drivers-on-gpio.rst
++++ b/Documentation/driver-api/gpio/drivers-on-gpio.rst
+@@ -27,7 +27,12 @@ hardware descriptions such as device tre
+ to the lines for a more permanent solution of this type.
+
+ - gpio-beeper: drivers/input/misc/gpio-beeper.c is used to provide a beep from
+- an external speaker connected to a GPIO line.
++ an external speaker connected to a GPIO line. (If the beep is controlled by
++ off/on, for an actual PWM waveform, see pwm-gpio below.)
++
++- pwm-gpio: drivers/pwm/pwm-gpio.c is used to toggle a GPIO with a high
++ resolution timer producing a PWM waveform on the GPIO line, as well as
++ Linux high resolution timers can do.
+
+ - extcon-gpio: drivers/extcon/extcon-gpio.c is used when you need to read an
+ external connector status, such as a headset line for an audio driver or an
+--- a/drivers/pwm/Kconfig
++++ b/drivers/pwm/Kconfig
+@@ -217,6 +217,17 @@ config PWM_FSL_FTM
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-fsl-ftm.
+
++config PWM_GPIO
++ tristate "GPIO PWM support"
++ depends on GPIOLIB
++ depends on HIGH_RES_TIMERS
++ help
++ Generic PWM framework driver for software PWM toggling a GPIO pin
++ from kernel high-resolution timers.
++
++ To compile this driver as a module, choose M here: the module
++ will be called pwm-gpio.
++
+ config PWM_HIBVT
+ tristate "HiSilicon BVT PWM support"
+ depends on ARCH_HISI || COMPILE_TEST
+--- a/drivers/pwm/Makefile
++++ b/drivers/pwm/Makefile
+@@ -18,6 +18,7 @@ obj-$(CONFIG_PWM_CROS_EC) += pwm-cros-ec
+ obj-$(CONFIG_PWM_DWC) += pwm-dwc.o
+ obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
+ obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o
++obj-$(CONFIG_PWM_GPIO) += pwm-gpio.o
+ obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o
+ obj-$(CONFIG_PWM_IMG) += pwm-img.o
+ obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o
+--- /dev/null
++++ b/drivers/pwm/pwm-gpio.c
+@@ -0,0 +1,240 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Generic software PWM for modulating GPIOs
++ *
++ * Copyright (C) 2020 Axis Communications AB
++ * Copyright (C) 2020 Nicola Di Lieto
++ * Copyright (C) 2024 Stefan Wahren
++ * Copyright (C) 2024 Linus Walleij
++ */
++
++#include <linux/cleanup.h>
++#include <linux/container_of.h>
++#include <linux/device.h>
++#include <linux/err.h>
++#include <linux/gpio/consumer.h>
++#include <linux/hrtimer.h>
++#include <linux/math.h>
++#include <linux/module.h>
++#include <linux/mod_devicetable.h>
++#include <linux/platform_device.h>
++#include <linux/property.h>
++#include <linux/pwm.h>
++#include <linux/spinlock.h>
++#include <linux/time.h>
++#include <linux/types.h>
++
++struct pwm_gpio {
++ struct hrtimer gpio_timer;
++ struct gpio_desc *gpio;
++ struct pwm_state state;
++ struct pwm_state next_state;
++
++ /* Protect internal state between pwm_ops and hrtimer */
++ spinlock_t lock;
++
++ bool changing;
++ bool running;
++ bool level;
++ struct pwm_chip chip;
++};
++
++static void pwm_gpio_round(struct pwm_state *dest, const struct pwm_state *src)
++{
++ u64 dividend;
++ u32 remainder;
++
++ *dest = *src;
++
++ /* Round down to hrtimer resolution */
++ dividend = dest->period;
++ remainder = do_div(dividend, hrtimer_resolution);
++ dest->period -= remainder;
++
++ dividend = dest->duty_cycle;
++ remainder = do_div(dividend, hrtimer_resolution);
++ dest->duty_cycle -= remainder;
++}
++
++static u64 pwm_gpio_toggle(struct pwm_gpio *gpwm, bool level)
++{
++ const struct pwm_state *state = &gpwm->state;
++ bool invert = state->polarity == PWM_POLARITY_INVERSED;
++
++ gpwm->level = level;
++ gpiod_set_value(gpwm->gpio, gpwm->level ^ invert);
++
++ if (!state->duty_cycle || state->duty_cycle == state->period) {
++ gpwm->running = false;
++ return 0;
++ }
++
++ gpwm->running = true;
++ return level ? state->duty_cycle : state->period - state->duty_cycle;
++}
++
++static enum hrtimer_restart pwm_gpio_timer(struct hrtimer *gpio_timer)
++{
++ struct pwm_gpio *gpwm = container_of(gpio_timer, struct pwm_gpio,
++ gpio_timer);
++ u64 next_toggle;
++ bool new_level;
++
++ guard(spinlock_irqsave)(&gpwm->lock);
++
++ /* Apply new state at end of current period */
++ if (!gpwm->level && gpwm->changing) {
++ gpwm->changing = false;
++ gpwm->state = gpwm->next_state;
++ new_level = !!gpwm->state.duty_cycle;
++ } else {
++ new_level = !gpwm->level;
++ }
++
++ next_toggle = pwm_gpio_toggle(gpwm, new_level);
++ if (next_toggle)
++ hrtimer_forward(gpio_timer, hrtimer_get_expires(gpio_timer),
++ ns_to_ktime(next_toggle));
++
++ return next_toggle ? HRTIMER_RESTART : HRTIMER_NORESTART;
++}
++
++static int pwm_gpio_apply(struct pwm_chip *chip, struct pwm_device *pwm,
++ const struct pwm_state *state)
++{
++ struct pwm_gpio *gpwm = container_of(chip, struct pwm_gpio, chip);
++ bool invert = state->polarity == PWM_POLARITY_INVERSED;
++
++ if (state->duty_cycle && state->duty_cycle < hrtimer_resolution)
++ return -EINVAL;
++
++ if (state->duty_cycle != state->period &&
++ (state->period - state->duty_cycle < hrtimer_resolution))
++ return -EINVAL;
++
++ if (!state->enabled) {
++ hrtimer_cancel(&gpwm->gpio_timer);
++ } else if (!gpwm->running) {
++ int ret;
++
++ /*
++ * This just enables the output, but pwm_gpio_toggle()
++ * really starts the duty cycle.
++ */
++ ret = gpiod_direction_output(gpwm->gpio, invert);
++ if (ret)
++ return ret;
++ }
++
++ guard(spinlock_irqsave)(&gpwm->lock);
++
++ if (!state->enabled) {
++ pwm_gpio_round(&gpwm->state, state);
++ gpwm->running = false;
++ gpwm->changing = false;
++
++ gpiod_set_value(gpwm->gpio, invert);
++ } else if (gpwm->running) {
++ pwm_gpio_round(&gpwm->next_state, state);
++ gpwm->changing = true;
++ } else {
++ unsigned long next_toggle;
++
++ pwm_gpio_round(&gpwm->state, state);
++ gpwm->changing = false;
++
++ next_toggle = pwm_gpio_toggle(gpwm, !!state->duty_cycle);
++ if (next_toggle)
++ hrtimer_start(&gpwm->gpio_timer, next_toggle,
++ HRTIMER_MODE_REL);
++ }
++
++ return 0;
++}
++
++static int pwm_gpio_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
++ struct pwm_state *state)
++{
++ struct pwm_gpio *gpwm = container_of(chip, struct pwm_gpio, chip);
++
++ guard(spinlock_irqsave)(&gpwm->lock);
++
++ if (gpwm->changing)
++ *state = gpwm->next_state;
++ else
++ *state = gpwm->state;
++
++ return 0;
++}
++
++static const struct pwm_ops pwm_gpio_ops = {
++ .apply = pwm_gpio_apply,
++ .get_state = pwm_gpio_get_state,
++};
++
++static void pwm_gpio_disable_hrtimer(void *data)
++{
++ struct pwm_gpio *gpwm = data;
++
++ hrtimer_cancel(&gpwm->gpio_timer);
++}
++
++static int pwm_gpio_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ struct pwm_chip *chip;
++ struct pwm_gpio *gpwm;
++ int ret;
++
++ gpwm = devm_kzalloc(&pdev->dev, sizeof(*gpwm), GFP_KERNEL);
++ if (IS_ERR(gpwm))
++ return PTR_ERR(gpwm);
++
++ chip = &gpwm->chip;
++
++ spin_lock_init(&gpwm->lock);
++
++ gpwm->gpio = devm_gpiod_get(dev, NULL, GPIOD_ASIS);
++ if (IS_ERR(gpwm->gpio))
++ return dev_err_probe(dev, PTR_ERR(gpwm->gpio),
++ "%pfw: could not get gpio\n",
++ dev_fwnode(dev));
++
++ if (gpiod_cansleep(gpwm->gpio))
++ return dev_err_probe(dev, -EINVAL,
++ "%pfw: sleeping GPIO not supported\n",
++ dev_fwnode(dev));
++
++ chip->dev = dev;
++ chip->ops = &pwm_gpio_ops;
++ chip->atomic = true;
++ chip->npwm = 1;
++
++ hrtimer_init(&gpwm->gpio_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
++ ret = devm_add_action_or_reset(dev, pwm_gpio_disable_hrtimer, gpwm);
++ if (ret)
++ return ret;
++
++ gpwm->gpio_timer.function = pwm_gpio_timer;
++
++ return devm_pwmchip_add(dev, chip);
++}
++
++static const struct of_device_id pwm_gpio_dt_ids[] = {
++ { .compatible = "pwm-gpio" },
++ { /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, pwm_gpio_dt_ids);
++
++static struct platform_driver pwm_gpio_driver = {
++ .driver = {
++ .name = "pwm-gpio",
++ .of_match_table = pwm_gpio_dt_ids,
++ },
++ .probe = pwm_gpio_probe,
++};
++module_platform_driver(pwm_gpio_driver);
++
++MODULE_DESCRIPTION("PWM GPIO driver");
++MODULE_AUTHOR("Vincent Whitchurch");
++MODULE_LICENSE("GPL");
+++ /dev/null
-From 1b5acd42281ad102b79f4e1794f0a0cccdafda05 Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Sat, 16 Nov 2024 16:53:31 +0000
-Subject: [PATCH] fixup! misc: Add RP1 PIO driver
-
-Signed-off-by: Phil Elwell <phil@raspberrypi.com>
----
- include/uapi/misc/rp1_pio_if.h | 6 +-----
- 1 file changed, 1 insertion(+), 5 deletions(-)
-
---- a/include/uapi/misc/rp1_pio_if.h
-+++ b/include/uapi/misc/rp1_pio_if.h
-@@ -1,4 +1,4 @@
--/* SPDX-License-Identifier: GPL-2.0 */
-+/* SPDX-License-Identifier: GPL-2.0 + WITH Linux-syscall-note */
- /*
- * Copyright (c) 2023-24 Raspberry Pi Ltd.
- * All rights reserved.
-@@ -169,10 +169,6 @@ struct rp1_access_hw_args {
- #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)
-
--#ifdef CONFIG_COMPAT
--//XXX #define PIO_IOC_SM_XFER_DATA32 _IOW(PIO_IOC_MAGIC, 2, struct pio_sm_xfer_data_args)
--#endif
--
- #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)
-
--- /dev/null
+From ff0fe12ab875d587348b6f2b9e73ae928049ebee Mon Sep 17 00:00:00 2001
+From: Tim Gover <tim.gover@raspberrypi.com>
+Date: Thu, 31 Oct 2024 16:12:54 +0000
+Subject: [PATCH] dtoverlay: Add a dtoverlay for pwm-gpio
+
+Signed-off-by: Tim Gover <tim.gover@raspberrypi.com>
+---
+ arch/arm/boot/dts/overlays/Makefile | 1 +
+ arch/arm/boot/dts/overlays/README | 6 +++
+ .../boot/dts/overlays/pwm-gpio-overlay.dts | 38 +++++++++++++++++++
+ 3 files changed, 45 insertions(+)
+ create mode 100644 arch/arm/boot/dts/overlays/pwm-gpio-overlay.dts
+
+--- a/arch/arm/boot/dts/overlays/Makefile
++++ b/arch/arm/boot/dts/overlays/Makefile
+@@ -217,6 +217,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
+ proto-codec.dtbo \
+ pwm.dtbo \
+ pwm-2chan.dtbo \
++ pwm-gpio.dtbo \
+ pwm-ir-tx.dtbo \
+ pwm1.dtbo \
+ qca7000.dtbo \
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -3903,6 +3903,12 @@ Params: pin Output p
+ clock PWM clock frequency (informational)
+
+
++Name: pwm-gpio
++Info: Configures the software PWM GPIO driver
++Load: dtoverlay=pwm-gpio,<param>=<val>
++Params: gpio Output pin (default 4)
++
++
+ Name: pwm-ir-tx
+ Info: Use GPIO pin as pwm-assisted infrared transmitter output.
+ This is an alternative to "gpio-ir-tx". pwm-ir-tx makes use
+--- /dev/null
++++ b/arch/arm/boot/dts/overlays/pwm-gpio-overlay.dts
+@@ -0,0 +1,38 @@
++// Device tree overlay for software GPIO PWM.
++/dts-v1/;
++/plugin/;
++
++/ {
++ compatible = "brcm,bcm2835";
++
++ fragment@0 {
++ target = <&gpio>;
++ __overlay__ {
++ pwm_gpio_pins: pwm_gpio_pins@4 {
++ brcm,pins = <4>; /* gpio 4 */
++ brcm,function = <1>; /* output */
++ brcm,pull = <0>; /* pull-none */
++ };
++ };
++ };
++
++ fragment@1 {
++ target-path = "/";
++ __overlay__ {
++ pwm_gpio: pwm_gpio@4 {
++ compatible = "pwm-gpio";
++ pinctrl-names = "default";
++ pinctrl-0 = <&pwm_gpio_pins>;
++ gpios = <&gpio 4 0>;
++ };
++ };
++ };
++
++ __overrides__ {
++ gpio = <&pwm_gpio>,"gpios:4",
++ <&pwm_gpio_pins>,"brcm,pins:0",
++ /* modify reg values to allow multiple instantiation */
++ <&pwm_gpio>,"reg:0",
++ <&pwm_gpio_pins>,"reg:0";
++ };
++};
+++ /dev/null
-From 2819a61eb000c207589c97eef9d69a237c6cfdf3 Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Fri, 8 Nov 2024 09:31:38 +0000
-Subject: [PATCH] misc: rp1-pio: Add an in-kernel API
-
-The header file linux/pio_rp1.h adds a pico-sdk-like interface to the
-RP1 PIO subsystem for other drivers.
-
-Signed-off-by: Phil Elwell <phil@raspberrypi.com>
----
- drivers/misc/rp1-pio.c | 169 ++++--
- include/linux/pio_instructions.h | 481 +++++++++++++++++
- include/linux/pio_rp1.h | 873 +++++++++++++++++++++++++++++++
- 3 files changed, 1474 insertions(+), 49 deletions(-)
- create mode 100644 include/linux/pio_instructions.h
- create mode 100644 include/linux/pio_rp1.h
-
---- a/drivers/misc/rp1-pio.c
-+++ b/drivers/misc/rp1-pio.c
-@@ -1,15 +1,17 @@
- // 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.
-+/*
-+ * 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>
-@@ -97,6 +99,7 @@ struct rp1_pio_client {
- uint32_t claimed_sms;
- uint32_t claimed_instrs;
- uint32_t claimed_dmas;
-+ int error;
- };
-
- static struct rp1_pio_device *g_pio;
-@@ -195,7 +198,7 @@ static int rp1_pio_find_program(struct r
- return -1;
- }
-
--static int rp1_pio_can_add_program(struct rp1_pio_client *client, void *param)
-+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;
-@@ -217,8 +220,9 @@ static int rp1_pio_can_add_program(struc
- 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);
-
--static int rp1_pio_add_program(struct rp1_pio_client *client, void *param)
-+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;
-@@ -254,6 +258,7 @@ static int rp1_pio_add_program(struct rp
- 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)
- {
-@@ -277,7 +282,7 @@ static void rp1_pio_remove_instrs(struct
- mutex_unlock(&pio->instr_mutex);
- }
-
--static int rp1_pio_remove_program(struct rp1_pio_client *client, void *param)
-+int rp1_pio_remove_program(struct rp1_pio_client *client, void *param)
- {
- struct rp1_pio_remove_program_args *args = param;
- uint32_t used_mask;
-@@ -296,8 +301,9 @@ static int rp1_pio_remove_program(struct
- }
- return ret;
- }
-+EXPORT_SYMBOL_GPL(rp1_pio_remove_program);
-
--static int rp1_pio_clear_instr_mem(struct rp1_pio_client *client, void *param)
-+int rp1_pio_clear_instr_mem(struct rp1_pio_client *client, void *param)
- {
- struct rp1_pio_device *pio = client->pio;
-
-@@ -309,8 +315,9 @@ static int rp1_pio_clear_instr_mem(struc
- mutex_unlock(&pio->instr_mutex);
- return 0;
- }
-+EXPORT_SYMBOL_GPL(rp1_pio_clear_instr_mem);
-
--static int rp1_pio_sm_claim(struct rp1_pio_client *client, void *param)
-+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;
-@@ -328,8 +335,9 @@ static int rp1_pio_sm_claim(struct rp1_p
- mutex_unlock(&pio->instr_mutex);
- return ret;
- }
-+EXPORT_SYMBOL_GPL(rp1_pio_sm_claim);
-
--static int rp1_pio_sm_unclaim(struct rp1_pio_client *client, void *param)
-+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;
-@@ -341,99 +349,113 @@ static int rp1_pio_sm_unclaim(struct rp1
- mutex_unlock(&pio->instr_mutex);
- return 0;
- }
-+EXPORT_SYMBOL_GPL(rp1_pio_sm_unclaim);
-
--static int rp1_pio_sm_is_claimed(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_sm_init(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_sm_set_config(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_sm_exec(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_sm_clear_fifos(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_sm_set_clkdiv(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_sm_set_pins(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_sm_set_pindirs(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_sm_set_enabled(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_sm_restart(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_sm_clkdiv_restart(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_sm_enable_sync(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_sm_put(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_sm_get(struct rp1_pio_client *client, void *param)
-+int rp1_pio_sm_get(struct rp1_pio_client *client, void *param)
- {
- struct rp1_pio_sm_get_args *args = param;
- int ret;
-@@ -444,69 +466,79 @@ static int rp1_pio_sm_get(struct rp1_pio
- return offsetof(struct rp1_pio_sm_get_args, data) + ret;
- return ret;
- }
-+EXPORT_SYMBOL_GPL(rp1_pio_sm_get);
-
--static int rp1_pio_sm_set_dmactrl(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_gpio_init(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_gpio_set_function(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_gpio_set_pulls(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_gpio_set_outover(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_gpio_set_inover(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_gpio_set_oeover(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_gpio_set_input_enabled(struct rp1_pio_client *client, void *param)
-+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);
-
--static int rp1_pio_gpio_set_drive_strength(struct rp1_pio_client *client, void *param)
-+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)
- {
-@@ -633,7 +665,7 @@ static int rp1_pio_sm_tx_user(struct rp1
- struct device *dev = &pdev->dev;
- int ret = 0;
-
-- // Clean the slate - we're running synchronously
-+ /* Clean the slate - we're running synchronously */
- dma->head_idx = 0;
- dma->tail_idx = 0;
-
-@@ -686,7 +718,7 @@ static int rp1_pio_sm_tx_user(struct rp1
- bytes -= copy_bytes;
- }
-
-- // Block for completion
-+ /* 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");
-@@ -830,22 +862,22 @@ struct handler_info {
- HANDLER(WRITE_HW, write_hw),
- };
-
--static int rp1_pio_open(struct inode *inode, struct file *filp)
-+struct rp1_pio_client *pio_open(void)
- {
-- struct rp1_pio_device *pio = g_pio;
- struct rp1_pio_client *client;
-
- client = kzalloc(sizeof(*client), GFP_KERNEL);
-+ if (!client)
-+ return ERR_PTR(-ENOMEM);
-
-- client->pio = pio;
-- filp->private_data = client;
-+ client->pio = g_pio;
-
-- return 0;
-+ return client;
- }
-+EXPORT_SYMBOL_GPL(pio_open);
-
--static int rp1_pio_release(struct inode *inode, struct file *filp)
-+void pio_close(struct rp1_pio_client *client)
- {
-- struct rp1_pio_client *client = filp->private_data;
- struct rp1_pio_device *pio = client->pio;
- uint claimed_dmas = client->claimed_dmas;
- int i;
-@@ -885,6 +917,45 @@ static int rp1_pio_release(struct inode
- /* Reinitialise the SM? */
-
- kfree(client);
-+}
-+EXPORT_SYMBOL_GPL(pio_close);
-+
-+void pio_set_error(struct rp1_pio_client *client, int err)
-+{
-+ client->error = err;
-+}
-+EXPORT_SYMBOL_GPL(pio_set_error);
-+
-+int pio_get_error(const struct rp1_pio_client *client)
-+{
-+ return client->error;
-+}
-+EXPORT_SYMBOL_GPL(pio_get_error);
-+
-+void pio_clear_error(struct rp1_pio_client *client)
-+{
-+ client->error = 0;
-+}
-+EXPORT_SYMBOL_GPL(pio_clear_error);
-+
-+static int rp1_pio_open(struct inode *inode, struct file *filp)
-+{
-+ struct rp1_pio_client *client;
-+
-+ client = pio_open();
-+ if (IS_ERR(client))
-+ return PTR_ERR(client);
-+
-+ filp->private_data = client;
-+
-+ return 0;
-+}
-+
-+static int rp1_pio_release(struct inode *inode, struct file *filp)
-+{
-+ struct rp1_pio_client *client = filp->private_data;
-+
-+ pio_close(client);
-
- return 0;
- }
---- /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,873 @@
-+/* 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) 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
-+};
-+
-+typedef rp1_pio_sm_config pio_sm_config;
-+
-+typedef struct rp1_pio_client *PIO;
-+
-+void pio_set_error(struct rp1_pio_client *client, int err);
-+int pio_get_error(struct rp1_pio_client *client);
-+void pio_clear_error(struct rp1_pio_client *client);
-+
-+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_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);
-+
-+int pio_init(void);
-+PIO pio_open(void);
-+void pio_close(PIO pio);
-+
-+int pio_sm_config_xfer(PIO pio, uint sm, uint dir, uint buf_size, uint buf_count);
-+int pio_sm_xfer_data(PIO pio, uint sm, uint dir, uint data_bytes, void *data);
-+
-+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);
-+}
-+
-+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_claim(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);
-+}
-+
-+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);
-+}
-+
-+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_float(float div, uint16_t *div_int,
-+ uint8_t *div_frac)
-+{
-+ if (bad_params_if(NULL, div < 1 || div > 65536))
-+ return false;
-+ *div_int = (uint16_t)div;
-+ if (*div_int == 0)
-+ *div_frac = 0;
-+ else
-+ *div_frac = (uint8_t)((div - (float)*div_int) * (1u << 8u));
-+ 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, float div)
-+{
-+ struct rp1_pio_sm_set_clkdiv_args args = { .sm = sm };
-+
-+ if (!pio_calculate_clkdiv_from_float(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_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 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, float div)
-+{
-+ uint16_t div_int;
-+ uint8_t div_frac;
-+
-+ pio_calculate_clkdiv_from_float(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
+From 624eb357e1a16385b3d6171e9194e4c5f8d4fd5f Mon Sep 17 00:00:00 2001
+From: Dom Cobley <popcornmix@gmail.com>
+Date: Wed, 23 Oct 2024 19:09:18 +0100
+Subject: [PATCH] dts: 2712: Drop some numa options from bootargs
+
+iommu_dma_numa_policy=interleave is not valid in the current tree
+It generates an unknown setting will be passed to usespace warning
+
+system_heap.max_order=0 is wanted when numa is enabled, but may not
+be when it is disabled.
+
+Add it on firmware side when we know if numa=fake=<n> is used.
+
+Signed-off-by: Dom Cobley <popcornmix@gmail.com>
+---
+ arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi
++++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi
+@@ -99,7 +99,7 @@
+
+ / {
+ chosen: chosen {
+- bootargs = "reboot=w coherent_pool=1M 8250.nr_uarts=1 pci=pcie_bus_safe cgroup_disable=memory numa_policy=interleave iommu_dma_numa_policy=interleave system_heap.max_order=0";
++ bootargs = "reboot=w coherent_pool=1M 8250.nr_uarts=1 pci=pcie_bus_safe cgroup_disable=memory numa_policy=interleave";
+ stdout-path = "serial10:115200n8";
+ };
+
+++ /dev/null
-From 4d20aadc3188ecfb62b309a9924ee9696a94fc33 Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Fri, 8 Nov 2024 09:37:58 +0000
-Subject: [PATCH] pwm: Add pwm-pio-rp1 driver
-
-Use the PIO hardware on RP1 to implement a PWM interface.
-
-Signed-off-by: Phil Elwell <phil@raspberrypi.com>
----
- drivers/pwm/Kconfig | 11 ++
- drivers/pwm/Makefile | 1 +
- drivers/pwm/pwm-pio-rp1.c | 251 ++++++++++++++++++++++++++++++++++++++
- 3 files changed, 263 insertions(+)
- create mode 100644 drivers/pwm/pwm-pio-rp1.c
-
---- a/drivers/pwm/Kconfig
-+++ b/drivers/pwm/Kconfig
-@@ -454,6 +454,17 @@ config PWM_PCA9685
- To compile this driver as a module, choose M here: the module
- will be called pwm-pca9685.
-
-+config PWM_PIO_RP1
-+ tristate "RP1 PIO PWM support"
-+ depends on FIRMWARE_RP1 || COMPILE_TEST
-+ help
-+ This is a PWM framework driver for Raspberry Pi 5, using the PIO
-+ hardware of RP1 to provide PWM functionality. Supports up to 4
-+ instances on GPIOs in bank 0.
-+
-+ To compile this driver as a module, choose M here: the module
-+ will be called pwm-pio-rp1.
-+
- config PWM_PXA
- tristate "PXA PWM support"
- depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST
---- a/drivers/pwm/Makefile
-+++ b/drivers/pwm/Makefile
-@@ -41,6 +41,7 @@ obj-$(CONFIG_PWM_MXS) += pwm-mxs.o
- obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o
- obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o
- obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o
-+obj-$(CONFIG_PWM_PIO_RP1) += pwm-pio-rp1.o
- obj-$(CONFIG_PWM_PXA) += pwm-pxa.o
- obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o
- obj-$(CONFIG_PWM_RP1) += pwm-rp1.o
---- /dev/null
-+++ b/drivers/pwm/pwm-pio-rp1.c
-@@ -0,0 +1,251 @@
-+// SPDX-License-Identifier: GPL-2.0-only
-+/*
-+ * Raspberry Pi PIO PWM.
-+ *
-+ * Copyright (C) 2024 Raspberry Pi Ltd.
-+ *
-+ * Author: Phil Elwell (phil@raspberrypi.com)
-+ *
-+ * Based on the pwm-rp1 driver by:
-+ * Naushir Patuck <naush@raspberrypi.com>
-+ * and on the pwm-gpio driver by:
-+ * Vincent Whitchurch <vincent.whitchurch@axis.com>
-+ */
-+
-+#include <linux/err.h>
-+#include <linux/gpio/consumer.h>
-+#include <linux/module.h>
-+#include <linux/mutex.h>
-+#include <linux/of.h>
-+#include <linux/pio_rp1.h>
-+#include <linux/platform_device.h>
-+#include <linux/pwm.h>
-+
-+struct pwm_pio_rp1 {
-+ struct pwm_chip chip;
-+ struct device *dev;
-+ struct gpio_desc *gpiod;
-+ struct mutex mutex;
-+ PIO pio;
-+ uint sm;
-+ uint offset;
-+ uint gpio;
-+ uint32_t period; /* In SM cycles */
-+ uint32_t duty_cycle; /* In SM cycles */
-+ enum pwm_polarity polarity;
-+ bool enabled;
-+};
-+
-+/* Generated from pwm.pio by pioasm */
-+#define pwm_wrap_target 0
-+#define pwm_wrap 6
-+#define pwm_loop_ticks 3
-+
-+static const uint16_t pwm_program_instructions[] = {
-+ // .wrap_target
-+ 0x9080, // 0: pull noblock side 0
-+ 0xa027, // 1: mov x, osr
-+ 0xa046, // 2: mov y, isr
-+ 0x00a5, // 3: jmp x != y, 5
-+ 0x1806, // 4: jmp 6 side 1
-+ 0xa042, // 5: nop
-+ 0x0083, // 6: jmp y--, 3
-+ // .wrap
-+};
-+
-+static const struct pio_program pwm_program = {
-+ .instructions = pwm_program_instructions,
-+ .length = 7,
-+ .origin = -1,
-+};
-+
-+static unsigned int pwm_pio_resolution __read_mostly;
-+
-+static inline pio_sm_config pwm_program_get_default_config(uint offset)
-+{
-+ pio_sm_config c = pio_get_default_sm_config();
-+
-+ sm_config_set_wrap(&c, offset + pwm_wrap_target, offset + pwm_wrap);
-+ sm_config_set_sideset(&c, 2, true, false);
-+ return c;
-+}
-+
-+static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin)
-+{
-+ pio_gpio_init(pio, pin);
-+
-+ pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
-+ pio_sm_config c = pwm_program_get_default_config(offset);
-+
-+ sm_config_set_sideset_pins(&c, pin);
-+ pio_sm_init(pio, sm, offset, &c);
-+}
-+
-+/* Write `period` to the input shift register - must be disabled */
-+static void pio_pwm_set_period(PIO pio, uint sm, uint32_t period)
-+{
-+ pio_sm_put_blocking(pio, sm, period);
-+ pio_sm_exec(pio, sm, pio_encode_pull(false, false));
-+ pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
-+}
-+
-+/* Write `level` to TX FIFO. State machine will copy this into X. */
-+static void pio_pwm_set_level(PIO pio, uint sm, uint32_t level)
-+{
-+ pio_sm_put_blocking(pio, sm, level);
-+}
-+
-+static int pwm_pio_rp1_apply(struct pwm_chip *chip, struct pwm_device *pwm,
-+ const struct pwm_state *state)
-+{
-+ struct pwm_pio_rp1 *ppwm = container_of(chip, struct pwm_pio_rp1, chip);
-+ uint32_t new_duty_cycle;
-+ uint32_t new_period;
-+
-+ if (state->duty_cycle && state->duty_cycle < pwm_pio_resolution)
-+ return -EINVAL;
-+
-+ if (state->duty_cycle != state->period &&
-+ (state->period - state->duty_cycle < pwm_pio_resolution))
-+ return -EINVAL;
-+
-+ new_period = state->period / pwm_pio_resolution;
-+ new_duty_cycle = state->duty_cycle / pwm_pio_resolution;
-+
-+ mutex_lock(&ppwm->mutex);
-+
-+ if ((ppwm->enabled && !state->enabled) || new_period != ppwm->period) {
-+ pio_sm_set_enabled(ppwm->pio, ppwm->sm, false);
-+ ppwm->enabled = false;
-+ }
-+
-+ if (new_period != ppwm->period) {
-+ pio_pwm_set_period(ppwm->pio, ppwm->sm, new_period);
-+ ppwm->period = new_period;
-+ }
-+
-+ if (state->enabled && new_duty_cycle != ppwm->duty_cycle) {
-+ pio_pwm_set_level(ppwm->pio, ppwm->sm, new_duty_cycle);
-+ ppwm->duty_cycle = new_duty_cycle;
-+ }
-+
-+ if (state->polarity != ppwm->polarity) {
-+ pio_gpio_set_outover(ppwm->pio, ppwm->gpio,
-+ (state->polarity == PWM_POLARITY_INVERSED) ?
-+ GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
-+ ppwm->polarity = state->polarity;
-+ }
-+
-+ if (!ppwm->enabled && state->enabled) {
-+ pio_sm_set_enabled(ppwm->pio, ppwm->sm, true);
-+ ppwm->enabled = true;
-+ }
-+
-+ mutex_unlock(&ppwm->mutex);
-+
-+ return 0;
-+}
-+
-+static const struct pwm_ops pwm_pio_rp1_ops = {
-+ .apply = pwm_pio_rp1_apply,
-+};
-+
-+static int pwm_pio_rp1_probe(struct platform_device *pdev)
-+{
-+ struct device_node *np = pdev->dev.of_node;
-+ struct of_phandle_args of_args = { 0 };
-+ struct device *dev = &pdev->dev;
-+ struct pwm_pio_rp1 *ppwm;
-+ struct pwm_chip *chip;
-+ bool is_rp1;
-+
-+ ppwm = devm_kzalloc(dev, sizeof(*ppwm), GFP_KERNEL);
-+ if (IS_ERR(ppwm))
-+ return PTR_ERR(ppwm);
-+
-+ chip = &ppwm->chip;
-+
-+ mutex_init(&ppwm->mutex);
-+
-+ ppwm->gpiod = devm_gpiod_get(dev, NULL, GPIOD_ASIS);
-+ /* Need to check that this is an RP1 GPIO in the first bank, and retrieve the offset */
-+ /* Unfortunately I think this has to be done by parsing the gpios property */
-+ if (IS_ERR(ppwm->gpiod))
-+ return dev_err_probe(dev, PTR_ERR(ppwm->gpiod),
-+ "could not get a gpio\n");
-+
-+ /* This really shouldn't fail, given that we have a gpiod */
-+ if (of_parse_phandle_with_args(np, "gpios", "#gpio-cells", 0, &of_args))
-+ return dev_err_probe(dev, -EINVAL,
-+ "can't find gpio declaration\n");
-+
-+ is_rp1 = of_device_is_compatible(of_args.np, "raspberrypi,rp1-gpio");
-+ of_node_put(of_args.np);
-+ if (!is_rp1 || of_args.args_count != 2)
-+ return dev_err_probe(dev, -EINVAL,
-+ "not an RP1 gpio\n");
-+
-+ ppwm->gpio = of_args.args[0];
-+
-+ ppwm->pio = pio_open();
-+ if (IS_ERR(ppwm->pio))
-+ return dev_err_probe(dev, PTR_ERR(ppwm->pio),
-+ "%pfw: could not open PIO\n",
-+ dev_fwnode(dev));
-+
-+ ppwm->sm = pio_claim_unused_sm(ppwm->pio, false);
-+ if ((int)ppwm->sm < 0) {
-+ pio_close(ppwm->pio);
-+ return dev_err_probe(dev, -EBUSY,
-+ "%pfw: no free PIO SM\n",
-+ dev_fwnode(dev));
-+ }
-+
-+ ppwm->offset = pio_add_program(ppwm->pio, &pwm_program);
-+ if (ppwm->offset == PIO_ORIGIN_ANY) {
-+ pio_close(ppwm->pio);
-+ return dev_err_probe(dev, -EBUSY,
-+ "%pfw: not enough PIO program space\n",
-+ dev_fwnode(dev));
-+ }
-+
-+ pwm_program_init(ppwm->pio, ppwm->sm, ppwm->offset, ppwm->gpio);
-+
-+ pwm_pio_resolution = (1000u * 1000 * 1000 * pwm_loop_ticks) / clock_get_hz(clk_sys);
-+
-+ chip->dev = dev;
-+ chip->ops = &pwm_pio_rp1_ops;
-+ chip->atomic = true;
-+ chip->npwm = 1;
-+
-+ platform_set_drvdata(pdev, ppwm);
-+
-+ return devm_pwmchip_add(dev, chip);
-+}
-+
-+static void pwm_pio_rp1_remove(struct platform_device *pdev)
-+{
-+ struct pwm_pio_rp1 *ppwm = platform_get_drvdata(pdev);
-+
-+ pio_close(ppwm->pio);
-+}
-+
-+static const struct of_device_id pwm_pio_rp1_dt_ids[] = {
-+ { .compatible = "raspberrypi,pwm-pio-rp1" },
-+ { /* sentinel */ }
-+};
-+MODULE_DEVICE_TABLE(of, pwm_pio_rp1_dt_ids);
-+
-+static struct platform_driver pwm_pio_rp1_driver = {
-+ .driver = {
-+ .name = "pwm-pio-rp1",
-+ .of_match_table = pwm_pio_rp1_dt_ids,
-+ },
-+ .probe = pwm_pio_rp1_probe,
-+ .remove_new = pwm_pio_rp1_remove,
-+};
-+module_platform_driver(pwm_pio_rp1_driver);
-+
-+MODULE_DESCRIPTION("PWM PIO RP1 driver");
-+MODULE_AUTHOR("Phil Elwell");
-+MODULE_LICENSE("GPL");
+++ /dev/null
-From 99a0201bb0abc946dc431214b638b2cc6b01dda5 Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Mon, 25 Nov 2024 16:19:55 +0000
-Subject: [PATCH] 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>
----
- include/linux/pio_rp1.h | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
---- a/include/linux/pio_rp1.h
-+++ b/include/linux/pio_rp1.h
-@@ -318,7 +318,7 @@ static inline int pio_sm_unclaim(struct
- if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
- return -EINVAL;
-
-- return rp1_pio_sm_claim(client, &args);
-+ return rp1_pio_sm_unclaim(client, &args);
- }
-
- static inline int pio_claim_unused_sm(struct rp1_pio_client *client, bool required)
--- /dev/null
+From 74f3ca5e39586ea26201fe6eaf1b9e6793b101b7 Mon Sep 17 00:00:00 2001
+From: Jonathan Bell <jonathan@raspberrypi.com>
+Date: Tue, 29 Oct 2024 13:33:21 +0000
+Subject: [PATCH] mmc: quirks: add more broken Kingston Canvas Go! SD card date
+ ranges
+
+A user has reported that a card of this model from late 2021 doesn't
+work, so extend the date range and make it match on all card sizes.
+
+Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
+---
+ drivers/mmc/core/quirks.h | 18 +++++++++++++++---
+ 1 file changed, 15 insertions(+), 3 deletions(-)
+
+--- a/drivers/mmc/core/quirks.h
++++ b/drivers/mmc/core/quirks.h
+@@ -18,10 +18,22 @@
+ static const struct mmc_fixup __maybe_unused mmc_sd_fixups[] = {
+ /*
+ * Kingston Canvas Go! Plus microSD cards never finish SD cache flush.
+- * This has so far only been observed on cards from 11/2019, while new
+- * cards from 2023/05 do not exhibit this behavior.
++ * This has been observed on cards from 2019/11 and 2021/11, while new
++ * cards from 2023/05 and 2024/08 do not exhibit this behavior.
+ */
+- _FIXUP_EXT("SD64G", CID_MANFID_KINGSTON_SD, 0x5449, 2019, 11,
++ _FIXUP_EXT(CID_NAME_ANY, CID_MANFID_KINGSTON_SD, 0x5449, 2019, CID_MONTH_ANY,
++ 0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd,
++ MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY),
++
++ _FIXUP_EXT(CID_NAME_ANY, CID_MANFID_KINGSTON_SD, 0x5449, 2020, CID_MONTH_ANY,
++ 0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd,
++ MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY),
++
++ _FIXUP_EXT(CID_NAME_ANY, CID_MANFID_KINGSTON_SD, 0x5449, 2021, CID_MONTH_ANY,
++ 0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd,
++ MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY),
++
++ _FIXUP_EXT(CID_NAME_ANY, CID_MANFID_KINGSTON_SD, 0x5449, 2022, CID_MONTH_ANY,
+ 0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd,
+ MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY),
+
+++ /dev/null
-From df8a2f6dc114b2c5c7685a069f717f2b06186b74 Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Wed, 20 Nov 2024 16:23:06 +0000
-Subject: [PATCH] rp1-pio: Add missing 'static inline's
-
-Avoid some duplicate symbol errors by adding some missing
-'static inline' decorations.
-
-Signed-off-by: Phil Elwell <phil@raspberrypi.com>
----
- include/linux/pio_rp1.h | 6 +++---
- 1 file changed, 3 insertions(+), 3 deletions(-)
-
---- a/include/linux/pio_rp1.h
-+++ b/include/linux/pio_rp1.h
-@@ -245,7 +245,7 @@ static inline bool pio_can_add_program_a
- return !rp1_pio_can_add_program(client, &args);
- }
-
--uint pio_add_program(struct rp1_pio_client *client, const pio_program_t *program)
-+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;
-@@ -365,7 +365,7 @@ static inline int pio_sm_set_config(stru
- return rp1_pio_sm_set_config(client, &args);
- }
-
--int pio_sm_exec(struct rp1_pio_client *client, uint sm, uint instr)
-+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 };
-
-@@ -375,7 +375,7 @@ int pio_sm_exec(struct rp1_pio_client *c
- return rp1_pio_sm_exec(client, &args);
- }
-
--int pio_sm_exec_wait_blocking(struct rp1_pio_client *client, uint sm, uint instr)
-+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 };
-
--- /dev/null
+From 6c0f34fb0f83741f7f03f6bfd3fcbc89cb2c7cde Mon Sep 17 00:00:00 2001
+From: Jonathan Bell <jonathan@raspberrypi.com>
+Date: Wed, 6 Nov 2024 10:26:55 +0000
+Subject: [PATCH] dt-bindings: usb: snps,dwc3: add FS/HS periodic NAK polling
+ quirk
+
+Add two quirk properties that control whether or not the controller
+issues many more handshakes to FS/HS Async endpoints in a single
+(micro)frame. Enabling these can significantly increase throughput for
+endpoints that frequently respond with NAKs.
+
+Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
+---
+ Documentation/devicetree/bindings/usb/snps,dwc3.yaml | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+--- a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml
++++ b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml
+@@ -231,6 +231,16 @@ properties:
+ description: When set, disable u2mac linestate check during HS transmit
+ type: boolean
+
++ snps,enhanced-nak-fs-quirk:
++ description:
++ When set, the controller schedules many more handshakes to Async FS
++ endpoints, improving throughput when they frequently respond with NAKs.
++
++ snps,enhanced-nak-hs-quirk:
++ description:
++ When set, the controller schedules many more handshakes to Async HS
++ endpoints, improving throughput when they frequently respond with NAKs.
++
+ snps,parkmode-disable-ss-quirk:
+ description:
+ When set, disable park mode for all Superspeed bus instances.
+++ /dev/null
-From d1f0c94e974a5f26d210b1d13a6ef9543bee4984 Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Thu, 21 Nov 2024 11:11:48 +0000
-Subject: [PATCH] misc: rp1-pio: Back-port some 6.11 build fixes
-
-Porting rp1-pio to rpi-6.11.y uncovered a few missing #includes and a
-difference of const-ness. Although not needed here, back-porting the
-resulting changes makes the driver more "correct" and may prevent a
-future merge conflict.
-
-Signed-off-by: Phil Elwell <phil@raspberrypi.com>
----
- drivers/misc/rp1-pio.c | 3 +++
- include/linux/pio_rp1.h | 2 +-
- 2 files changed, 4 insertions(+), 1 deletion(-)
-
---- a/drivers/misc/rp1-pio.c
-+++ b/drivers/misc/rp1-pio.c
-@@ -22,6 +22,9 @@
- #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>
---- a/include/linux/pio_rp1.h
-+++ b/include/linux/pio_rp1.h
-@@ -176,7 +176,7 @@ typedef rp1_pio_sm_config pio_sm_config;
- typedef struct rp1_pio_client *PIO;
-
- void pio_set_error(struct rp1_pio_client *client, int err);
--int pio_get_error(struct rp1_pio_client *client);
-+int pio_get_error(const struct rp1_pio_client *client);
- void pio_clear_error(struct rp1_pio_client *client);
-
- int rp1_pio_can_add_program(struct rp1_pio_client *client, void *param);
+++ /dev/null
-From dd2394360860d15146c96635796a75b05bb32b61 Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Tue, 19 Nov 2024 09:25:34 +0000
-Subject: [PATCH] misc: rp1-pio: Add FIFO-related methods
-
-Add support for querying the FIFO status and clearing the TX FIFO.
-
-Signed-off-by: Phil Elwell <phil@raspberrypi.com>
----
- drivers/misc/rp1-fw-pio.h | 3 ++
- drivers/misc/rp1-pio.c | 24 +++++++++
- include/linux/pio_rp1.h | 89 ++++++++++++++++++++++++++++++++++
- include/uapi/misc/rp1_pio_if.h | 13 ++++-
- 4 files changed, 128 insertions(+), 1 deletion(-)
-
---- a/drivers/misc/rp1-fw-pio.h
-+++ b/drivers/misc/rp1-fw-pio.h
-@@ -47,6 +47,9 @@ enum rp1_pio_ops {
- 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
- };
-
---- a/drivers/misc/rp1-pio.c
-+++ b/drivers/misc/rp1-pio.c
-@@ -479,6 +479,28 @@ int rp1_pio_sm_set_dmactrl(struct rp1_pi
- }
- 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;
-@@ -851,6 +873,8 @@ struct handler_info {
- 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),
---- a/include/linux/pio_rp1.h
-+++ b/include/linux/pio_rp1.h
-@@ -200,6 +200,8 @@ int rp1_pio_sm_enable_sync(struct rp1_pi
- 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);
-@@ -551,6 +553,15 @@ static inline int pio_sm_set_dmactrl(str
- 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 };
-@@ -587,6 +598,84 @@ static inline uint32_t pio_sm_get_blocki
- 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 ||
---- a/include/uapi/misc/rp1_pio_if.h
-+++ b/include/uapi/misc/rp1_pio_if.h
-@@ -114,7 +114,7 @@ struct rp1_pio_sm_get_args {
- uint16_t sm;
- uint8_t blocking;
- uint8_t rsvd;
-- uint32_t data; /* IN/OUT */
-+ uint32_t data; /* OUT */
- };
-
- struct rp1_pio_sm_set_dmactrl_args {
-@@ -124,6 +124,15 @@ struct rp1_pio_sm_set_dmactrl_args {
- 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;
- };
-@@ -195,6 +204,8 @@ struct rp1_access_hw_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)
--- /dev/null
+From bb53ca75f9e3631e753f397ccab704a8f975658b Mon Sep 17 00:00:00 2001
+From: Jonathan Bell <jonathan@raspberrypi.com>
+Date: Wed, 6 Nov 2024 10:45:24 +0000
+Subject: [PATCH] usb: dwc3: core: add support for setting NAK enhancement bits
+ for FS/HS
+
+If a device frequently NAKs, it can exhaust the scheduled handshakes in
+a frame. It will then not get polled by the controller until the next
+frame interval. This is most noticeable on FS devices as the controller
+schedules a small set of transactions only once per full-speed frame.
+
+Setting the ENH_PER_NAK_FS/LS bits in the GUCTL1 register increases the
+number of transactions that can be scheduled to Async (Control/Bulk)
+endpoints in the respective frame time. In the FS case, this only
+applies to FS devices directly connected to root ports.
+
+Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
+---
+ drivers/usb/dwc3/core.c | 10 ++++++++++
+ drivers/usb/dwc3/core.h | 6 ++++++
+ 2 files changed, 16 insertions(+)
+
+--- a/drivers/usb/dwc3/core.c
++++ b/drivers/usb/dwc3/core.c
+@@ -1366,6 +1366,12 @@ static int dwc3_core_init(struct dwc3 *d
+ if (dwc->dis_tx_ipgap_linecheck_quirk)
+ reg |= DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS;
+
++ if (dwc->enh_nak_fs_quirk)
++ reg |= DWC3_GUCTL1_NAK_PER_ENH_FS;
++
++ if (dwc->enh_nak_hs_quirk)
++ reg |= DWC3_GUCTL1_NAK_PER_ENH_HS;
++
+ if (dwc->parkmode_disable_ss_quirk)
+ reg |= DWC3_GUCTL1_PARKMODE_DISABLE_SS;
+
+@@ -1669,6 +1675,10 @@ static void dwc3_get_properties(struct d
+ "snps,resume-hs-terminations");
+ dwc->ulpi_ext_vbus_drv = device_property_read_bool(dev,
+ "snps,ulpi-ext-vbus-drv");
++ dwc->enh_nak_fs_quirk = device_property_read_bool(dev,
++ "snps,enhanced-nak-fs-quirk");
++ dwc->enh_nak_hs_quirk = device_property_read_bool(dev,
++ "snps,enhanced-nak-hs-quirk");
+ dwc->parkmode_disable_ss_quirk = device_property_read_bool(dev,
+ "snps,parkmode-disable-ss-quirk");
+ dwc->parkmode_disable_hs_quirk = device_property_read_bool(dev,
+--- a/drivers/usb/dwc3/core.h
++++ b/drivers/usb/dwc3/core.h
+@@ -269,6 +269,8 @@
+ #define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28)
+ #define DWC3_GUCTL1_DEV_FORCE_20_CLK_FOR_30_CLK BIT(26)
+ #define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24)
++#define DWC3_GUCTL1_NAK_PER_ENH_FS BIT(19)
++#define DWC3_GUCTL1_NAK_PER_ENH_HS BIT(18)
+ #define DWC3_GUCTL1_PARKMODE_DISABLE_SS BIT(17)
+ #define DWC3_GUCTL1_PARKMODE_DISABLE_HS BIT(16)
+ #define DWC3_GUCTL1_PARKMODE_DISABLE_FSLS BIT(15)
+@@ -1118,6 +1120,8 @@ struct dwc3_scratchpad_array {
+ * generation after resume from suspend.
+ * @ulpi_ext_vbus_drv: Set to confiure the upli chip to drives CPEN pin
+ * VBUS with an external supply.
++ * @enh_nak_fs_quirk: Set to schedule more handshakes to Async FS endpoints.
++ * @enh_nak_hs_quirk: Set to schedule more handshakes to Async HS endpoints.
+ * @parkmode_disable_ss_quirk: If set, disable park mode feature for all
+ * Superspeed instances.
+ * @parkmode_disable_hs_quirk: If set, disable park mode feature for all
+@@ -1348,6 +1352,8 @@ struct dwc3 {
+ unsigned dis_tx_ipgap_linecheck_quirk:1;
+ unsigned resume_hs_terminations:1;
+ unsigned ulpi_ext_vbus_drv:1;
++ unsigned enh_nak_fs_quirk:1;
++ unsigned enh_nak_hs_quirk:1;
+ unsigned parkmode_disable_ss_quirk:1;
+ unsigned parkmode_disable_hs_quirk:1;
+ unsigned parkmode_disable_fsls_quirk:1;
--- /dev/null
+From 803757627b48bdad9530b50053321fdea6dfcab4 Mon Sep 17 00:00:00 2001
+From: Jonathan Bell <jonathan@raspberrypi.com>
+Date: Wed, 6 Nov 2024 10:54:58 +0000
+Subject: [PATCH] DTS: rp1: set enhanced FS NAK quirk for usb3 controllers
+
+There seem to be only benefits, and no downsides.
+
+Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
+---
+ arch/arm64/boot/dts/broadcom/rp1.dtsi | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/arch/arm64/boot/dts/broadcom/rp1.dtsi
++++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi
+@@ -1077,6 +1077,7 @@
+ usb3-lpm-capable;
+ snps,axi-pipe-limit = /bits/ 8 <8>;
+ snps,dis_rxdet_inp3_quirk;
++ snps,enhanced-nak-fs-quirk;
+ snps,parkmode-disable-ss-quirk;
+ snps,parkmode-disable-hs-quirk;
+ snps,parkmode-disable-fsls-quirk;
+@@ -1093,6 +1094,7 @@
+ usb3-lpm-capable;
+ snps,axi-pipe-limit = /bits/ 8 <8>;
+ snps,dis_rxdet_inp3_quirk;
++ snps,enhanced-nak-fs-quirk;
+ snps,parkmode-disable-ss-quirk;
+ snps,parkmode-disable-hs-quirk;
+ snps,parkmode-disable-fsls-quirk;
+++ /dev/null
-From 3687701e8d252864f440f91f1aedf8ffd58d6ee6 Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Mon, 25 Nov 2024 21:51:13 +0000
-Subject: [PATCH] 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>
----
- include/linux/pio_rp1.h | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
---- a/include/linux/pio_rp1.h
-+++ b/include/linux/pio_rp1.h
-@@ -20,7 +20,7 @@
- #endif
-
- #define bad_params_if(client, test) \
-- ({ bool f = (test); if (f) pio_set_error(client, -EINVAL); \
-+ ({ bool f = (test); if (f && client) pio_set_error(client, -EINVAL); \
- if (f && PARAM_WARNINGS_ENABLED) WARN_ON((test)); \
- f; })
-
--- /dev/null
+From e9e852af347ae3ccee4e7abb01f9ef91387980f9 Mon Sep 17 00:00:00 2001
+From: Jonathan Bell <jonathan@raspberrypi.com>
+Date: Wed, 6 Nov 2024 11:07:55 +0000
+Subject: [PATCH] drivers: usb: xhci: prevent a theoretical race on
+ non-coherent platforms
+
+For platforms that have xHCI controllers attached over PCIe, and
+non-coherent routes to main memory, a theoretical race exists between
+posting new TRBs to a ring, and writing to the doorbell register.
+
+In a contended system, write traffic from the CPU may be stalled before
+the memory controller, whereas the CPU to Endpoint route is separate
+and not likely to be contended. Similarly, the DMA route from the
+endpoint to main memory may be separate and uncontended.
+
+Therefore the xHCI can receive a doorbell write and find a stale view
+of a transfer ring. In cases where only a single TRB is ping-ponged at
+a time, this can cause the endpoint to not get polled at all.
+
+Adding a readl() before the write forces a round-trip transaction
+across PCIe, definitively serialising the CPU along the PCI
+producer-consumer ordering rules.
+
+Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
+---
+ drivers/usb/host/xhci-ring.c | 13 +++++++++++++
+ 1 file changed, 13 insertions(+)
+
+--- a/drivers/usb/host/xhci-ring.c
++++ b/drivers/usb/host/xhci-ring.c
+@@ -505,6 +505,19 @@ void xhci_ring_ep_doorbell(struct xhci_h
+
+ trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id));
+
++ /*
++ * For non-coherent systems with PCIe DMA (such as Pi 4, Pi 5) there
++ * is a theoretical race between the TRB write and barrier, which
++ * is reported complete as soon as the write leaves the CPU domain,
++ * the doorbell write, which may be reported as complete by the RC
++ * at some arbitrary point, and the visibility of new TRBs in system
++ * RAM by the endpoint DMA engine.
++ *
++ * This read before the write positively serialises the CPU state
++ * by incurring a round-trip across the link.
++ */
++ readl(db_addr);
++
+ writel(DB_VALUE(ep_index, stream_id), db_addr);
+ /* flush the write */
+ readl(db_addr);
--- /dev/null
+From ce65ed02cb6707ae5c9f3a304f5b0124f4eed559 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Mon, 4 Nov 2024 14:10:53 +0000
+Subject: [PATCH] iio: humidity: dht11: Allow non-zero decimals
+
+The DHT11 datasheet is pretty cryptic, but it does suggest that after
+each integer value (humidity and temperature) there are "decimal"
+values. Validate these as integers in the range 0-9 and treat them as
+tenths of a unit.
+
+Link: https://github.com/raspberrypi/linux/issues/6220
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/iio/humidity/dht11.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/iio/humidity/dht11.c
++++ b/drivers/iio/humidity/dht11.c
+@@ -152,9 +152,9 @@ static int dht11_decode(struct dht11 *dh
+ dht11->temperature = (((temp_int & 0x7f) << 8) + temp_dec) *
+ ((temp_int & 0x80) ? -100 : 100);
+ dht11->humidity = ((hum_int << 8) + hum_dec) * 100;
+- } else if (temp_dec == 0 && hum_dec == 0) { /* DHT11 */
+- dht11->temperature = temp_int * 1000;
+- dht11->humidity = hum_int * 1000;
++ } else if (temp_dec < 10 && hum_dec < 10) { /* DHT11 */
++ dht11->temperature = temp_int * 1000 + temp_dec * 100;
++ dht11->humidity = hum_int * 1000 + hum_dec * 100;
+ } else {
+ dev_err(dht11->dev,
+ "Don't know how to decode data: %d %d %d %d\n",
--- /dev/null
+From c3393ac1098d1f191e37eed73bf366ebc88ac4ee Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 11 Sep 2024 14:49:05 +0100
+Subject: [PATCH] drm/vc4: Correct condition for ignoring a plane to src rect
+ =0, not <1.0
+
+The logic for dropping a plane less than zero didn't account for the
+possibility that a plane could be being upscaled with a src_rect with
+width/height < 1 pixel, but not 0 subpixels.
+
+Check for not 0 subpixels, not < 1, in both vc4 and vc6 paths.
+
+Fixes: dac616899f87 ("drm/vc4: Drop planes that have 0 destination size")
+Fixes: f73b18eb0d48 ("drm/vc4: Drop planes that are completely off-screen")
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/gpu/drm/vc4/vc4_plane.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+--- a/drivers/gpu/drm/vc4/vc4_plane.c
++++ b/drivers/gpu/drm/vc4/vc4_plane.c
+@@ -1160,7 +1160,8 @@ static int vc4_plane_mode_set(struct drm
+ width = vc4_state->src_w[0] >> 16;
+ height = vc4_state->src_h[0] >> 16;
+
+- if (!width || !height || !vc4_state->crtc_w || !vc4_state->crtc_h) {
++ if (!vc4_state->src_w[0] || !vc4_state->src_h[0] ||
++ !vc4_state->crtc_w || !vc4_state->crtc_h) {
+ /* 0 source size probably means the plane is offscreen */
+ vc4_state->dlist_initialized = 1;
+ return 0;
+@@ -1698,7 +1699,8 @@ static int vc6_plane_mode_set(struct drm
+ width = vc4_state->src_w[0] >> 16;
+ height = vc4_state->src_h[0] >> 16;
+
+- if (!width || !height || !vc4_state->crtc_w || !vc4_state->crtc_h) {
++ if (!vc4_state->src_w[0] || !vc4_state->src_h[0] ||
++ !vc4_state->crtc_w || !vc4_state->crtc_h) {
+ /* 0 source size probably means the plane is offscreen.
+ * 0 destination size is a redundant plane.
+ */
--- /dev/null
+From ca621585c573cae54dc1235d90822e8bcef2f73d Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 11 Sep 2024 15:23:33 +0100
+Subject: [PATCH] drm/vc4: Use the TPZ scaling filter for 1x1 source images
+
+The documentation says that the TPZ filter can not upscale,
+and requesting a scaling factor > 1:1 will output the original
+image in the top left, and repeat the right/bottom most pixels
+thereafter.
+That fits perfectly with upscaling a 1x1 image which is done
+a fair amount by some compositors to give solid colour, and it
+saves a large amount of LBM (TPZ is based on src size, whilst
+PPF is based on dest size).
+
+Select TPZ filter for images with source rectangle <=1.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/gpu/drm/vc4/vc4_plane.c | 21 +++++++++++++++------
+ 1 file changed, 15 insertions(+), 6 deletions(-)
+
+--- a/drivers/gpu/drm/vc4/vc4_plane.c
++++ b/drivers/gpu/drm/vc4/vc4_plane.c
+@@ -265,7 +265,11 @@ static enum vc4_scaling_mode vc4_get_sca
+ {
+ if (dst == src >> 16)
+ return VC4_SCALING_NONE;
+- if (3 * dst >= 2 * (src >> 16))
++
++ if (src <= (1 << 16))
++ /* Source rectangle <= 1 pixel can use TPZ for resize/upscale */
++ return VC4_SCALING_TPZ;
++ else if (3 * dst >= 2 * (src >> 16))
+ return VC4_SCALING_PPF;
+ else
+ return VC4_SCALING_TPZ;
+@@ -560,12 +564,17 @@ static void vc4_write_tpz(struct vc4_pla
+
+ WARN_ON_ONCE(vc4->gen > VC4_GEN_6);
+
+- scale = src / dst;
++ if ((dst << 16) < src) {
++ scale = src / dst;
+
+- /* The specs note that while the reciprocal would be defined
+- * as (1<<32)/scale, ~0 is close enough.
+- */
+- recip = ~0 / scale;
++ /* The specs note that while the reciprocal would be defined
++ * as (1<<32)/scale, ~0 is close enough.
++ */
++ recip = ~0 / scale;
++ } else {
++ scale = (1 << 16) + 1;
++ recip = (1 << 16) - 1;
++ }
+
+ vc4_dlist_write(vc4_state,
+ /*
--- /dev/null
+From 68b0ff3549148e614e1733d773cee8e689c763c6 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Tue, 20 Aug 2024 16:25:10 +0100
+Subject: [PATCH] drm: Set non-desktop property to true for writeback and
+ virtual connectors
+
+The non-desktop property "Indicates the output should be ignored for
+purposes of displaying a standard desktop environment or console."
+
+That sounds like it should be true for all writeback and virtual
+connectors as you shouldn't render a desktop to them, so set it
+by default.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/gpu/drm/drm_connector.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/gpu/drm/drm_connector.c
++++ b/drivers/gpu/drm/drm_connector.c
+@@ -361,7 +361,8 @@ static int __drm_connector_init(struct d
+
+ drm_object_attach_property(&connector->base,
+ config->non_desktop_property,
+- 0);
++ (connector_type != DRM_MODE_CONNECTOR_VIRTUAL &&
++ connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ? 0 : 1;
+ drm_object_attach_property(&connector->base,
+ config->tile_property,
+ 0);
--- /dev/null
+From 8181e682d6f4ef209845ec24f0a1eb37764d6731 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Fri, 21 Oct 2022 14:26:12 +0100
+Subject: [PATCH] drm: Increase plane_mask to 64bit.
+
+The limit of 32 planes per DRM device is dictated by the use
+of planes_mask returning a u32.
+
+Change to a u64 such that 64 planes can be supported by a device.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/gpu/drm/drm_atomic.c | 2 +-
+ drivers/gpu/drm/drm_framebuffer.c | 2 +-
+ drivers/gpu/drm/drm_mode_config.c | 2 +-
+ drivers/gpu/drm/drm_plane.c | 2 +-
+ drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c | 2 +-
+ include/drm/drm_crtc.h | 2 +-
+ include/drm/drm_plane.h | 4 ++--
+ 7 files changed, 8 insertions(+), 8 deletions(-)
+
+--- a/drivers/gpu/drm/drm_atomic.c
++++ b/drivers/gpu/drm/drm_atomic.c
+@@ -451,7 +451,7 @@ static void drm_atomic_crtc_print_state(
+ drm_printf(p, "\tactive_changed=%d\n", state->active_changed);
+ drm_printf(p, "\tconnectors_changed=%d\n", state->connectors_changed);
+ drm_printf(p, "\tcolor_mgmt_changed=%d\n", state->color_mgmt_changed);
+- drm_printf(p, "\tplane_mask=%x\n", state->plane_mask);
++ drm_printf(p, "\tplane_mask=%llx\n", state->plane_mask);
+ drm_printf(p, "\tconnector_mask=%x\n", state->connector_mask);
+ drm_printf(p, "\tencoder_mask=%x\n", state->encoder_mask);
+ drm_printf(p, "\tmode: " DRM_MODE_FMT "\n", DRM_MODE_ARG(&state->mode));
+--- a/drivers/gpu/drm/drm_framebuffer.c
++++ b/drivers/gpu/drm/drm_framebuffer.c
+@@ -959,7 +959,7 @@ static int atomic_remove_fb(struct drm_f
+ struct drm_connector *conn __maybe_unused;
+ struct drm_connector_state *conn_state;
+ int i, ret;
+- unsigned plane_mask;
++ u64 plane_mask;
+ bool disable_crtcs = false;
+
+ retry_disable:
+--- a/drivers/gpu/drm/drm_mode_config.c
++++ b/drivers/gpu/drm/drm_mode_config.c
+@@ -636,7 +636,7 @@ void drm_mode_config_validate(struct drm
+ struct drm_encoder *encoder;
+ struct drm_crtc *crtc;
+ struct drm_plane *plane;
+- u32 primary_with_crtc = 0, cursor_with_crtc = 0;
++ u64 primary_with_crtc = 0, cursor_with_crtc = 0;
+ unsigned int num_primary = 0;
+
+ if (!drm_core_check_feature(dev, DRIVER_MODESET))
+--- a/drivers/gpu/drm/drm_plane.c
++++ b/drivers/gpu/drm/drm_plane.c
+@@ -249,7 +249,7 @@ static int __drm_universal_plane_init(st
+ int ret;
+
+ /* plane index is used with 32bit bitmasks */
+- if (WARN_ON(config->num_total_plane >= 32))
++ if (WARN_ON(config->num_total_plane >= 64))
+ return -EINVAL;
+
+ /*
+--- a/drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c
++++ b/drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c
+@@ -230,7 +230,7 @@ static int ipu_crtc_atomic_check(struct
+ {
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
+ crtc);
+- u32 primary_plane_mask = drm_plane_mask(crtc->primary);
++ u64 primary_plane_mask = drm_plane_mask(crtc->primary);
+
+ if (crtc_state->active && (primary_plane_mask & crtc_state->plane_mask) == 0)
+ return -EINVAL;
+--- a/include/drm/drm_crtc.h
++++ b/include/drm/drm_crtc.h
+@@ -192,7 +192,7 @@ struct drm_crtc_state {
+ * @plane_mask: Bitmask of drm_plane_mask(plane) of planes attached to
+ * this CRTC.
+ */
+- u32 plane_mask;
++ u64 plane_mask;
+
+ /**
+ * @connector_mask: Bitmask of drm_connector_mask(connector) of
+--- a/include/drm/drm_plane.h
++++ b/include/drm/drm_plane.h
+@@ -915,9 +915,9 @@ static inline unsigned int drm_plane_ind
+ * drm_plane_mask - find the mask of a registered plane
+ * @plane: plane to find mask for
+ */
+-static inline u32 drm_plane_mask(const struct drm_plane *plane)
++static inline u64 drm_plane_mask(const struct drm_plane *plane)
+ {
+- return 1 << drm_plane_index(plane);
++ return 1ULL << drm_plane_index(plane);
+ }
+
+ struct drm_plane * drm_plane_from_index(struct drm_device *dev, int idx);
--- /dev/null
+From 5dc4cef7d7fcda4ea59b9e456a835fa54336af6b Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Fri, 21 Oct 2022 14:27:45 +0100
+Subject: [PATCH] drm/vc4: Increase number of overlay planes from 16 to 48
+
+The HVS can accept an arbitrary number of planes, provided
+that the overall pixel read load is within limits, and
+the display list can fit into the dlist memory.
+
+Now that DRM will support 64 planes per device, increase
+the number of overlay planes from 16 to 48 so that the
+dlist complexity can be increased (eg 4x4 video wall on
+each of 3 displays).
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/gpu/drm/drm_connector.c | 2 +-
+ drivers/gpu/drm/vc4/vc4_plane.c | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/gpu/drm/drm_connector.c
++++ b/drivers/gpu/drm/drm_connector.c
+@@ -362,7 +362,7 @@ static int __drm_connector_init(struct d
+ drm_object_attach_property(&connector->base,
+ config->non_desktop_property,
+ (connector_type != DRM_MODE_CONNECTOR_VIRTUAL &&
+- connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ? 0 : 1;
++ connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ? 0 : 1);
+ drm_object_attach_property(&connector->base,
+ config->tile_property,
+ 0);
+--- a/drivers/gpu/drm/vc4/vc4_plane.c
++++ b/drivers/gpu/drm/vc4/vc4_plane.c
+@@ -2517,7 +2517,7 @@ struct drm_plane *vc4_plane_init(struct
+ return plane;
+ }
+
+-#define VC4_NUM_OVERLAY_PLANES 16
++#define VC4_NUM_OVERLAY_PLANES 48
+
+ int vc4_plane_create_additional_planes(struct drm_device *drm)
+ {
--- /dev/null
+From dd340cb082a020fbd42b794493ffd063dd8e15b4 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Tue, 15 Aug 2023 15:44:34 +0100
+Subject: [PATCH] drm/vc4: Assign 32 overlay planes to writeback only
+
+Instead of having 48 generic overlay planes, assign 32 to the
+writeback connector so that there is no ambiguity in wlroots
+when trying to find a plane for composition using the writeback
+connector vs display.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/gpu/drm/vc4/vc4_plane.c | 34 +++++++++++++++++++++++++++++++--
+ 1 file changed, 32 insertions(+), 2 deletions(-)
+
+--- a/drivers/gpu/drm/vc4/vc4_plane.c
++++ b/drivers/gpu/drm/vc4/vc4_plane.c
+@@ -2517,13 +2517,28 @@ struct drm_plane *vc4_plane_init(struct
+ return plane;
+ }
+
+-#define VC4_NUM_OVERLAY_PLANES 48
++#define VC4_NUM_OVERLAY_PLANES 16
++#define VC4_NUM_TXP_OVERLAY_PLANES 32
+
+ int vc4_plane_create_additional_planes(struct drm_device *drm)
+ {
+ struct drm_plane *cursor_plane;
+ struct drm_crtc *crtc;
+ unsigned int i;
++ struct drm_crtc *txp_crtc;
++ uint32_t non_txp_crtc_mask;
++
++ drm_for_each_crtc(crtc, drm) {
++ struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
++
++ if (vc4_crtc->feeds_txp) {
++ txp_crtc = crtc;
++ break;
++ }
++ }
++
++ non_txp_crtc_mask = GENMASK(drm->mode_config.num_crtc - 1, 0) -
++ drm_crtc_mask(txp_crtc);
+
+ /* Set up some arbitrary number of planes. We're not limited
+ * by a set number of physical registers, just the space in
+@@ -2537,7 +2552,22 @@ int vc4_plane_create_additional_planes(s
+ for (i = 0; i < VC4_NUM_OVERLAY_PLANES; i++) {
+ struct drm_plane *plane =
+ vc4_plane_init(drm, DRM_PLANE_TYPE_OVERLAY,
+- GENMASK(drm->mode_config.num_crtc - 1, 0));
++ non_txp_crtc_mask);
++
++ if (IS_ERR(plane))
++ continue;
++
++ /* Create zpos property. Max of all the overlays + 1 primary +
++ * 1 cursor plane on a crtc.
++ */
++ drm_plane_create_zpos_property(plane, i + 1, 1,
++ VC4_NUM_OVERLAY_PLANES + 1);
++ }
++
++ for (i = 0; i < VC4_NUM_TXP_OVERLAY_PLANES; i++) {
++ struct drm_plane *plane =
++ vc4_plane_init(drm, DRM_PLANE_TYPE_OVERLAY,
++ drm_crtc_mask(txp_crtc));
+
+ if (IS_ERR(plane))
+ continue;
--- /dev/null
+From b3b3d12cf0734318a0fed0b33e13d714188369db Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Tue, 22 Oct 2024 17:17:31 +0100
+Subject: [PATCH] drm: Add a DRM_MODE_TRANSPOSE option to the DRM rotation
+ property
+
+Some hardware will implement transpose as a rotation operation,
+which when combined with X and Y reflect can result in a rotation,
+but is a discrete operation in its own right.
+
+Add an option for transpose only.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/gpu/drm/drm_blend.c | 3 +++
+ include/uapi/drm/drm_mode.h | 1 +
+ 2 files changed, 4 insertions(+)
+
+--- a/drivers/gpu/drm/drm_blend.c
++++ b/drivers/gpu/drm/drm_blend.c
+@@ -263,6 +263,8 @@ EXPORT_SYMBOL(drm_plane_create_alpha_pro
+ * "reflect-x"
+ * DRM_MODE_REFLECT_Y:
+ * "reflect-y"
++ * DRM_MODE_TRANSPOSE:
++ * "transpose"
+ *
+ * Rotation is the specified amount in degrees in counter clockwise direction,
+ * the X and Y axis are within the source rectangle, i.e. the X/Y axis before
+@@ -280,6 +282,7 @@ int drm_plane_create_rotation_property(s
+ { __builtin_ffs(DRM_MODE_ROTATE_270) - 1, "rotate-270" },
+ { __builtin_ffs(DRM_MODE_REFLECT_X) - 1, "reflect-x" },
+ { __builtin_ffs(DRM_MODE_REFLECT_Y) - 1, "reflect-y" },
++ { __builtin_ffs(DRM_MODE_TRANSPOSE) - 1, "transpose" },
+ };
+ struct drm_property *prop;
+
+--- a/include/uapi/drm/drm_mode.h
++++ b/include/uapi/drm/drm_mode.h
+@@ -203,6 +203,7 @@ extern "C" {
+ */
+ #define DRM_MODE_REFLECT_X (1<<4)
+ #define DRM_MODE_REFLECT_Y (1<<5)
++#define DRM_MODE_TRANSPOSE (1<<6)
+
+ /*
+ * DRM_MODE_REFLECT_MASK
--- /dev/null
+From 8fec3ff870499256f2c18fe7983f6ed3fea4faaf Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Tue, 22 Oct 2024 17:22:40 +0100
+Subject: [PATCH] drm: Add a rotation parameter to connectors.
+
+Some connectors, particularly writeback, can implement flip
+or transpose operations as writing back to memory.
+
+Add a connector rotation property to control this.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/gpu/drm/drm_atomic_uapi.c | 4 +++
+ drivers/gpu/drm/drm_blend.c | 50 ++++++++++++++++++++++++-------
+ include/drm/drm_blend.h | 5 ++++
+ include/drm/drm_connector.h | 11 +++++++
+ 4 files changed, 60 insertions(+), 10 deletions(-)
+
+--- a/drivers/gpu/drm/drm_atomic_uapi.c
++++ b/drivers/gpu/drm/drm_atomic_uapi.c
+@@ -811,6 +811,8 @@ static int drm_atomic_connector_set_prop
+ state->max_requested_bpc = val;
+ } else if (property == connector->privacy_screen_sw_state_property) {
+ state->privacy_screen_sw_state = val;
++ } else if (property == connector->rotation_property) {
++ state->rotation = val;
+ } else if (connector->funcs->atomic_set_property) {
+ return connector->funcs->atomic_set_property(connector,
+ state, property, val);
+@@ -900,6 +902,8 @@ drm_atomic_connector_get_property(struct
+ *val = state->max_requested_bpc;
+ } else if (property == connector->privacy_screen_sw_state_property) {
+ *val = state->privacy_screen_sw_state;
++ } else if (property == connector->rotation_property) {
++ *val = state->rotation;
+ } else if (connector->funcs->atomic_get_property) {
+ return connector->funcs->atomic_get_property(connector,
+ state, property, val);
+--- a/drivers/gpu/drm/drm_blend.c
++++ b/drivers/gpu/drm/drm_blend.c
+@@ -235,6 +235,16 @@ int drm_plane_create_alpha_property(stru
+ }
+ EXPORT_SYMBOL(drm_plane_create_alpha_property);
+
++static const struct drm_prop_enum_list drm_rotate_props[] = {
++ { __builtin_ffs(DRM_MODE_ROTATE_0) - 1, "rotate-0" },
++ { __builtin_ffs(DRM_MODE_ROTATE_90) - 1, "rotate-90" },
++ { __builtin_ffs(DRM_MODE_ROTATE_180) - 1, "rotate-180" },
++ { __builtin_ffs(DRM_MODE_ROTATE_270) - 1, "rotate-270" },
++ { __builtin_ffs(DRM_MODE_REFLECT_X) - 1, "reflect-x" },
++ { __builtin_ffs(DRM_MODE_REFLECT_Y) - 1, "reflect-y" },
++ { __builtin_ffs(DRM_MODE_TRANSPOSE) - 1, "transpose" },
++};
++
+ /**
+ * drm_plane_create_rotation_property - create a new rotation property
+ * @plane: drm plane
+@@ -275,15 +285,6 @@ int drm_plane_create_rotation_property(s
+ unsigned int rotation,
+ unsigned int supported_rotations)
+ {
+- static const struct drm_prop_enum_list props[] = {
+- { __builtin_ffs(DRM_MODE_ROTATE_0) - 1, "rotate-0" },
+- { __builtin_ffs(DRM_MODE_ROTATE_90) - 1, "rotate-90" },
+- { __builtin_ffs(DRM_MODE_ROTATE_180) - 1, "rotate-180" },
+- { __builtin_ffs(DRM_MODE_ROTATE_270) - 1, "rotate-270" },
+- { __builtin_ffs(DRM_MODE_REFLECT_X) - 1, "reflect-x" },
+- { __builtin_ffs(DRM_MODE_REFLECT_Y) - 1, "reflect-y" },
+- { __builtin_ffs(DRM_MODE_TRANSPOSE) - 1, "transpose" },
+- };
+ struct drm_property *prop;
+
+ WARN_ON((supported_rotations & DRM_MODE_ROTATE_MASK) == 0);
+@@ -291,7 +292,8 @@ int drm_plane_create_rotation_property(s
+ WARN_ON(rotation & ~supported_rotations);
+
+ prop = drm_property_create_bitmask(plane->dev, 0, "rotation",
+- props, ARRAY_SIZE(props),
++ drm_rotate_props,
++ ARRAY_SIZE(drm_rotate_props),
+ supported_rotations);
+ if (!prop)
+ return -ENOMEM;
+@@ -307,6 +309,34 @@ int drm_plane_create_rotation_property(s
+ }
+ EXPORT_SYMBOL(drm_plane_create_rotation_property);
+
++int drm_connector_create_rotation_property(struct drm_connector *conn,
++ unsigned int rotation,
++ unsigned int supported_rotations)
++{
++ struct drm_property *prop;
++
++ WARN_ON((supported_rotations & DRM_MODE_ROTATE_MASK) == 0);
++ WARN_ON(!is_power_of_2(rotation & DRM_MODE_ROTATE_MASK));
++ WARN_ON(rotation & ~supported_rotations);
++
++ prop = drm_property_create_bitmask(conn->dev, 0, "rotation",
++ drm_rotate_props,
++ ARRAY_SIZE(drm_rotate_props),
++ supported_rotations);
++ if (!prop)
++ return -ENOMEM;
++
++ drm_object_attach_property(&conn->base, prop, rotation);
++
++ if (conn->state)
++ conn->state->rotation = rotation;
++
++ conn->rotation_property = prop;
++
++ return 0;
++}
++EXPORT_SYMBOL(drm_connector_create_rotation_property);
++
+ /**
+ * drm_rotation_simplify() - Try to simplify the rotation
+ * @rotation: Rotation to be simplified
+--- a/include/drm/drm_blend.h
++++ b/include/drm/drm_blend.h
+@@ -34,6 +34,7 @@
+ struct drm_device;
+ struct drm_atomic_state;
+ struct drm_plane;
++struct drm_connector;
+
+ static inline bool drm_rotation_90_or_270(unsigned int rotation)
+ {
+@@ -58,4 +59,8 @@ int drm_atomic_normalize_zpos(struct drm
+ struct drm_atomic_state *state);
+ int drm_plane_create_blend_mode_property(struct drm_plane *plane,
+ unsigned int supported_modes);
++
++int drm_connector_create_rotation_property(struct drm_connector *conn,
++ unsigned int rotation,
++ unsigned int supported_rotations);
+ #endif
+--- a/include/drm/drm_connector.h
++++ b/include/drm/drm_connector.h
+@@ -1029,6 +1029,11 @@ struct drm_connector_state {
+ * DRM blob property for HDR output metadata
+ */
+ struct drm_property_blob *hdr_output_metadata;
++
++ /**
++ * @rotation: Connector property to rotate the maximum output image.
++ */
++ u32 rotation;
+ };
+
+ /**
+@@ -1696,6 +1701,12 @@ struct drm_connector {
+ */
+ struct drm_property *privacy_screen_hw_state_property;
+
++ /**
++ * @rotation_property: Optional DRM property controlling rotation of the
++ * output.
++ */
++ struct drm_property *rotation_property;
++
+ #define DRM_CONNECTOR_POLL_HPD (1 << 0)
+ #define DRM_CONNECTOR_POLL_CONNECT (1 << 1)
+ #define DRM_CONNECTOR_POLL_DISCONNECT (1 << 2)
--- /dev/null
+From 8346446098032c62d1de891a97c7f62264b18f81 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 14 Aug 2024 16:41:07 +0100
+Subject: [PATCH] drm/vc4: txp: Add a rotation property to the writeback
+ connector
+
+The txp block can implement transpose as it writes out the image
+data, so expose that through the new connector rotation property.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/gpu/drm/vc4/vc4_txp.c | 21 +++++++++++++++++----
+ 1 file changed, 17 insertions(+), 4 deletions(-)
+
+--- a/drivers/gpu/drm/vc4/vc4_txp.c
++++ b/drivers/gpu/drm/vc4/vc4_txp.c
+@@ -15,6 +15,7 @@
+
+ #include <drm/drm_atomic.h>
+ #include <drm/drm_atomic_helper.h>
++#include <drm/drm_blend.h>
+ #include <drm/drm_drv.h>
+ #include <drm/drm_edid.h>
+ #include <drm/drm_fb_dma_helper.h>
+@@ -259,10 +260,15 @@ static int vc4_txp_connector_atomic_chec
+ crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
+
+ fb = conn_state->writeback_job->fb;
+- if (fb->width != crtc_state->mode.hdisplay ||
+- fb->height != crtc_state->mode.vdisplay) {
+- DRM_DEBUG_KMS("Invalid framebuffer size %ux%u\n",
+- fb->width, fb->height);
++ if ((conn_state->rotation == DRM_MODE_ROTATE_0 &&
++ fb->width != crtc_state->mode.hdisplay &&
++ fb->height != crtc_state->mode.vdisplay) ||
++ (conn_state->rotation == (DRM_MODE_ROTATE_0 | DRM_MODE_TRANSPOSE) &&
++ fb->width != crtc_state->mode.vdisplay &&
++ fb->height != crtc_state->mode.hdisplay)) {
++ DRM_DEBUG_KMS("Invalid framebuffer size %ux%u vs mode %ux%u\n",
++ fb->width, fb->height,
++ crtc_state->mode.hdisplay, crtc_state->mode.vdisplay);
+ return -EINVAL;
+ }
+
+@@ -330,6 +336,9 @@ static void vc4_txp_connector_atomic_com
+ */
+ ctrl |= TXP_ALPHA_INVERT;
+
++ if (conn_state->rotation & DRM_MODE_TRANSPOSE)
++ ctrl |= TXP_TRANSPOSE;
++
+ if (!drm_dev_enter(drm, &idx))
+ return;
+
+@@ -608,6 +617,10 @@ static int vc4_txp_bind(struct device *d
+ if (ret)
+ return ret;
+
++ drm_connector_create_rotation_property(&txp->connector.base, DRM_MODE_ROTATE_0,
++ DRM_MODE_ROTATE_0 |
++ DRM_MODE_TRANSPOSE);
++
+ ret = devm_request_irq(dev, irq, vc4_txp_interrupt, 0,
+ dev_name(dev), txp);
+ if (ret)
--- /dev/null
+From a2fa911d90495762047c05dec4241308ae61ca36 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Thu, 19 Sep 2024 18:05:00 +0100
+Subject: [PATCH] dmaengine: dw-axi-dmac: Allow client-chosen width
+
+For devices where transfer lengths are not known upfront, there is a
+danger when the destination is wider than the source that partial words
+can be lost at the end of a transfer. Ideally the controller would be
+able to flush the residue, but it can't - it's not even possible to tell
+that there is any.
+
+Instead, allow the client driver to avoid the problem by setting a
+smaller width.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
++++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
+@@ -724,6 +724,18 @@ static int dw_axi_dma_set_hw_desc(struct
+ case DMA_DEV_TO_MEM:
+ reg_burst_msize = axi_dma_encode_msize(chan->config.src_maxburst);
+ reg_width = __ffs(chan->config.src_addr_width);
++ /*
++ * For devices where transfer lengths are not known upfront,
++ * there is a danger when the destination is wider than the
++ * source that partial words can be lost at the end of a transfer.
++ * Ideally the controller would be able to flush the residue, but
++ * it can't - it's not even possible to tell that there is any.
++ * Instead, allow the client driver to avoid the problem by setting
++ * a smaller width.
++ */
++ if (chan->config.dst_addr_width &&
++ (chan->config.dst_addr_width < mem_width))
++ mem_width = chan->config.dst_addr_width;
+ device_addr = phys_to_dma(chan->chip->dev, chan->config.src_addr);
+ ctllo = reg_width << CH_CTL_L_SRC_WIDTH_POS |
+ mem_width << CH_CTL_L_DST_WIDTH_POS |
--- /dev/null
+From 5cf7209c294a58029984880d4858e2d3c7e46a3c Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Thu, 19 Sep 2024 18:12:12 +0100
+Subject: [PATCH] spi: dw: Let the DMAC set the transfer widths
+
+SPI transfers are of defined length, unlike some UART traffic, so it is
+safe to let the DMA controller choose a suitable memory width.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/spi/spi-dw-dma.c | 2 --
+ 1 file changed, 2 deletions(-)
+
+--- a/drivers/spi/spi-dw-dma.c
++++ b/drivers/spi/spi-dw-dma.c
+@@ -330,7 +330,6 @@ static int dw_spi_dma_config_tx(struct d
+ txconf.direction = DMA_MEM_TO_DEV;
+ txconf.dst_addr = dws->dma_addr;
+ txconf.dst_maxburst = dws->txburst;
+- txconf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ txconf.dst_addr_width = dw_spi_dma_convert_width(dws->n_bytes);
+ txconf.device_fc = false;
+
+@@ -431,7 +430,6 @@ static int dw_spi_dma_config_rx(struct d
+ rxconf.direction = DMA_DEV_TO_MEM;
+ rxconf.src_addr = dws->dma_addr;
+ rxconf.src_maxburst = dws->rxburst;
+- rxconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ rxconf.src_addr_width = dw_spi_dma_convert_width(dws->n_bytes);
+ rxconf.device_fc = false;
+
--- /dev/null
+From 8894298105f4cb41dfa41e0b0d3c40c3f7b92c44 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Thu, 19 Sep 2024 18:22:24 +0100
+Subject: [PATCH] serial: pl011: Request a memory width of 1 byte
+
+In order to avoid losing residue bytes when a receive is terminated
+early, set the destination width to single bytes.
+
+Link: https://github.com/raspberrypi/linux/issues/6365
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/tty/serial/amba-pl011.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/tty/serial/amba-pl011.c
++++ b/drivers/tty/serial/amba-pl011.c
+@@ -468,6 +468,7 @@ static void pl011_dma_probe(struct uart_
+ .src_addr = uap->port.mapbase +
+ pl011_reg_to_offset(uap, REG_DR),
+ .src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
++ .dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
+ .direction = DMA_DEV_TO_MEM,
+ .src_maxburst = uap->fifosize >> 2,
+ .device_fc = false,
--- /dev/null
+From 66aef6ce3557edd9d58d794e4a800c5be49ca0e7 Mon Sep 17 00:00:00 2001
+From: Jonathan Bell <jonathan@raspberrypi.com>
+Date: Mon, 11 Nov 2024 10:30:38 +0000
+Subject: [PATCH] drivers: usb: xhci: set HID bit in streaming endpoint
+ contexts
+
+The xHC may commence Host Initiated Data Moves for streaming endpoints -
+see USB3.2 spec s8.12.1.4.2.4. However, this behaviour is typically
+counterproductive as the submission of UAS URBs in {Status, Data,
+Command} order and 1 outstanding IO per stream ID means the device never
+enters Move Data after a HIMD for Status or Data stages with the same
+stream ID. For OUT transfers this is especially inefficient as the host
+will start transmitting multiple bulk packets as a burst, all of which
+get NAKed by the device - wasting bandwidth.
+
+Also, some buggy UAS adapters don't properly handle the EP flow control
+state this creates - e.g. RTL9210.
+
+Set Host Initiated Data Move Disable to always defer stream selection to
+the device. xHC implementations may treat this field as "don't care,
+forced to 1" anyway - xHCI 1.2 s4.12.1.
+
+Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
+---
+ drivers/usb/host/xhci-mem.c | 8 ++++++++
+ drivers/usb/host/xhci.h | 2 ++
+ 2 files changed, 10 insertions(+)
+
+--- a/drivers/usb/host/xhci-mem.c
++++ b/drivers/usb/host/xhci-mem.c
+@@ -716,6 +716,14 @@ void xhci_setup_streams_ep_input_ctx(str
+ ep_ctx->ep_info &= cpu_to_le32(~EP_MAXPSTREAMS_MASK);
+ ep_ctx->ep_info |= cpu_to_le32(EP_MAXPSTREAMS(max_primary_streams)
+ | EP_HAS_LSA);
++
++ /*
++ * Set Host Initiated Data Move Disable to always defer stream
++ * selection to the device. xHC implementations may treat this
++ * field as "don't care, forced to 1" anyway - xHCI 1.2 s4.12.1.
++ */
++ ep_ctx->ep_info2 |= EP_HID;
++
+ ep_ctx->deq = cpu_to_le64(stream_info->ctx_array_dma);
+ }
+
+--- a/drivers/usb/host/xhci.h
++++ b/drivers/usb/host/xhci.h
+@@ -492,6 +492,8 @@ struct xhci_ep_ctx {
+ #define CTX_TO_EP_MAXPSTREAMS(p) (((p) & EP_MAXPSTREAMS_MASK) >> 10)
+ /* Endpoint is set up with a Linear Stream Array (vs. Secondary Stream Array) */
+ #define EP_HAS_LSA (1 << 15)
++/* Host initiated data move disable in info2 */
++#define EP_HID (1 << 7)
+ /* hosts with LEC=1 use bits 31:24 as ESIT high bits. */
+ #define CTX_TO_MAX_ESIT_PAYLOAD_HI(p) (((p) >> 24) & 0xff)
+
--- /dev/null
+From 35e50ee3d66e014d869f0d7a3468bef964d26d32 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Thu, 14 Nov 2024 13:14:02 +0000
+Subject: [PATCH] media: i2c: imx477: Add options for slightly modifying the
+ link freq
+
+The default link frequency of 450MHz has been noted to interfere
+with GPS if they are in close proximty.
+Add the option for 453 and 456MHz to move the signal slightly out
+of the band. (447MHz can not be offered as corruption is then observed
+on the 133x992 10bit mode).
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+
+fixup imx477 gps
+---
+ drivers/media/i2c/imx477.c | 86 +++++++++++++++++++++++++++++---------
+ 1 file changed, 67 insertions(+), 19 deletions(-)
+
+--- a/drivers/media/i2c/imx477.c
++++ b/drivers/media/i2c/imx477.c
+@@ -164,8 +164,48 @@ struct imx477_mode {
+ struct imx477_reg_list reg_list;
+ };
+
+-static const s64 imx477_link_freq_menu[] = {
+- IMX477_DEFAULT_LINK_FREQ,
++/* Link frequency setup */
++enum {
++ IMX477_LINK_FREQ_450MHZ,
++ IMX477_LINK_FREQ_453MHZ,
++ IMX477_LINK_FREQ_456MHZ,
++};
++
++static const s64 link_freqs[] = {
++ [IMX477_LINK_FREQ_450MHZ] = 450000000,
++ [IMX477_LINK_FREQ_453MHZ] = 453000000,
++ [IMX477_LINK_FREQ_456MHZ] = 456000000,
++};
++
++/* 450MHz is the nominal "default" link frequency */
++static const struct imx477_reg link_450Mhz_regs[] = {
++ {0x030E, 0x00},
++ {0x030F, 0x96},
++};
++
++static const struct imx477_reg link_453Mhz_regs[] = {
++ {0x030E, 0x00},
++ {0x030F, 0x97},
++};
++
++static const struct imx477_reg link_456Mhz_regs[] = {
++ {0x030E, 0x00},
++ {0x030F, 0x98},
++};
++
++static const struct imx477_reg_list link_freq_regs[] = {
++ [IMX477_LINK_FREQ_450MHZ] = {
++ .regs = link_450Mhz_regs,
++ .num_of_regs = ARRAY_SIZE(link_450Mhz_regs)
++ },
++ [IMX477_LINK_FREQ_453MHZ] = {
++ .regs = link_453Mhz_regs,
++ .num_of_regs = ARRAY_SIZE(link_453Mhz_regs)
++ },
++ [IMX477_LINK_FREQ_456MHZ] = {
++ .regs = link_456Mhz_regs,
++ .num_of_regs = ARRAY_SIZE(link_456Mhz_regs)
++ },
+ };
+
+ static const struct imx477_reg mode_common_regs[] = {
+@@ -558,8 +598,6 @@ static const struct imx477_reg mode_4056
+ {0x0309, 0x0c},
+ {0x030b, 0x02},
+ {0x030d, 0x02},
+- {0x030e, 0x00},
+- {0x030f, 0x96},
+ {0x0310, 0x01},
+ {0x0820, 0x07},
+ {0x0821, 0x08},
+@@ -659,8 +697,6 @@ static const struct imx477_reg mode_2028
+ {0x0309, 0x0c},
+ {0x030b, 0x02},
+ {0x030d, 0x02},
+- {0x030e, 0x00},
+- {0x030f, 0x96},
+ {0x0310, 0x01},
+ {0x0820, 0x07},
+ {0x0821, 0x08},
+@@ -760,8 +796,6 @@ static const struct imx477_reg mode_2028
+ {0x0309, 0x0c},
+ {0x030b, 0x02},
+ {0x030d, 0x02},
+- {0x030e, 0x00},
+- {0x030f, 0x96},
+ {0x0310, 0x01},
+ {0x0820, 0x07},
+ {0x0821, 0x08},
+@@ -890,8 +924,6 @@ static const struct imx477_reg mode_1332
+ {0x0309, 0x0a},
+ {0x030b, 0x02},
+ {0x030d, 0x02},
+- {0x030e, 0x00},
+- {0x030f, 0x96},
+ {0x0310, 0x01},
+ {0x0820, 0x07},
+ {0x0821, 0x08},
+@@ -1121,6 +1153,8 @@ struct imx477 {
+ struct v4l2_ctrl *vblank;
+ struct v4l2_ctrl *hblank;
+
++ unsigned int link_freq_idx;
++
+ /* Current mode */
+ const struct imx477_mode *mode;
+
+@@ -1712,7 +1746,7 @@ static int imx477_get_selection(struct v
+ static int imx477_start_streaming(struct imx477 *imx477)
+ {
+ struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd);
+- const struct imx477_reg_list *reg_list;
++ const struct imx477_reg_list *reg_list, *freq_regs;
+ const struct imx477_reg_list *extra_regs;
+ int ret, tm;
+
+@@ -1725,6 +1759,13 @@ static int imx477_start_streaming(struct
+ extra_regs->num_of_regs);
+ }
+
++ if (!ret) {
++ /* Update the link frequency registers */
++ freq_regs = &link_freq_regs[imx477->link_freq_idx];
++ ret = imx477_write_regs(imx477, freq_regs->regs,
++ freq_regs->num_of_regs);
++ }
++
+ if (ret) {
+ dev_err(&client->dev, "%s failed to set common settings\n",
+ __func__);
+@@ -2010,9 +2051,8 @@ static int imx477_init_controls(struct i
+ /* LINK_FREQ is also read only */
+ imx477->link_freq =
+ v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx477_ctrl_ops,
+- V4L2_CID_LINK_FREQ,
+- ARRAY_SIZE(imx477_link_freq_menu) - 1, 0,
+- imx477_link_freq_menu);
++ V4L2_CID_LINK_FREQ, 1, 0,
++ &link_freqs[imx477->link_freq_idx]);
+ if (imx477->link_freq)
+ imx477->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+@@ -2110,13 +2150,14 @@ static void imx477_free_controls(struct
+ mutex_destroy(&imx477->mutex);
+ }
+
+-static int imx477_check_hwcfg(struct device *dev)
++static int imx477_check_hwcfg(struct device *dev, struct imx477 *imx477)
+ {
+ struct fwnode_handle *endpoint;
+ struct v4l2_fwnode_endpoint ep_cfg = {
+ .bus_type = V4L2_MBUS_CSI2_DPHY
+ };
+ int ret = -EINVAL;
++ int i;
+
+ endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
+ if (!endpoint) {
+@@ -2141,11 +2182,18 @@ static int imx477_check_hwcfg(struct dev
+ goto error_out;
+ }
+
+- if (ep_cfg.nr_of_link_frequencies != 1 ||
+- ep_cfg.link_frequencies[0] != IMX477_DEFAULT_LINK_FREQ) {
++ for (i = 0; i < ARRAY_SIZE(link_freqs); i++) {
++ if (link_freqs[i] == ep_cfg.link_frequencies[0]) {
++ imx477->link_freq_idx = i;
++ break;
++ }
++ }
++
++ if (i == ARRAY_SIZE(link_freqs)) {
+ dev_err(dev, "Link frequency not supported: %lld\n",
+ ep_cfg.link_frequencies[0]);
+- goto error_out;
++ ret = -EINVAL;
++ goto error_out;
+ }
+
+ ret = 0;
+@@ -2206,7 +2254,7 @@ static int imx477_probe(struct i2c_clien
+ (const struct imx477_compatible_data *)match->data;
+
+ /* Check the hardware configuration in device tree */
+- if (imx477_check_hwcfg(dev))
++ if (imx477_check_hwcfg(dev, imx477))
+ return -EINVAL;
+
+ /* Default the trigger mode from OF to -1, which means invalid */
--- /dev/null
+From 7e253a062d5a14de13ccfb410570975099c238be Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Thu, 14 Nov 2024 13:15:24 +0000
+Subject: [PATCH] dtoverlays: Add link-frequency override to imx477/378 overlay
+
+Copy of the imx708 change.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ arch/arm/boot/dts/overlays/README | 4 ++++
+ arch/arm/boot/dts/overlays/imx477_378-overlay.dtsi | 1 +
+ 2 files changed, 5 insertions(+)
+
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -2780,6 +2780,8 @@ Params: rotation Mounting
+ camera clamping I/Os such as XVS to 0V.
+ sync-source Configure as vsync source
+ sync-sink Configure as vsync sink
++ link-frequency Allowable link frequency values to use in Hz:
++ 450000000 (default), 453000000, 456000000.
+
+
+ Name: imx462
+@@ -2822,6 +2824,8 @@ Params: rotation Mounting
+ camera clamping I/Os such as XVS to 0V.
+ sync-source Configure as vsync source
+ sync-sink Configure as vsync sink
++ link-frequency Allowable link frequency values to use in Hz:
++ 450000000 (default), 453000000, 456000000.
+
+
+ Name: imx500
+--- a/arch/arm/boot/dts/overlays/imx477_378-overlay.dtsi
++++ b/arch/arm/boot/dts/overlays/imx477_378-overlay.dtsi
+@@ -80,6 +80,7 @@
+ <&cam_node>, "clocks:0=",<&cam0_clk>,
+ <&cam_node>, "VANA-supply:0=",<&cam0_reg>;
+ always-on = <0>, "+99";
++ link-frequency = <&cam_endpoint>,"link-frequencies#0";
+ };
+ };
+
--- /dev/null
+From 59a8855b51c1d8acf37d3c80f34782d71f474617 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Wed, 13 Nov 2024 10:37:22 +0000
+Subject: [PATCH] dmaengine: dw-axi-dmac: Only start idle channels
+
+Attempting to start a non-idle channel causes an error message to be
+logged, and is inefficient. Test for emptiness of the desc_issued list
+before doing so.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
++++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
+@@ -536,9 +536,11 @@ static void dma_chan_issue_pending(struc
+ {
+ struct axi_dma_chan *chan = dchan_to_axi_dma_chan(dchan);
+ unsigned long flags;
++ bool was_empty;
+
+ spin_lock_irqsave(&chan->vc.lock, flags);
+- if (vchan_issue_pending(&chan->vc))
++ was_empty = list_empty(&chan->vc.desc_issued);
++ if (vchan_issue_pending(&chan->vc) && was_empty)
+ axi_chan_start_first_queued(chan);
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
+ }
--- /dev/null
+From 0d58d8cfb6f989f290d983552fcaa116e582e84a Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Thu, 31 Oct 2024 17:33:38 +0000
+Subject: [PATCH] mailbox: Add RP1 mailbox support
+
+The Raspberry Pi RP1 includes 2 M3 cores running firmware. This driver
+adds a mailbox communication channel to them via a doorbell and some
+shared memory.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/mailbox/Kconfig | 9 ++
+ drivers/mailbox/Makefile | 2 +
+ drivers/mailbox/rp1-mailbox.c | 208 ++++++++++++++++++++++++++++++++++
+ 3 files changed, 219 insertions(+)
+ create mode 100644 drivers/mailbox/rp1-mailbox.c
+
+--- a/drivers/mailbox/Kconfig
++++ b/drivers/mailbox/Kconfig
+@@ -295,4 +295,13 @@ config QCOM_IPCC
+ acts as an interrupt controller for receiving interrupts from clients.
+ Say Y here if you want to build this driver.
+
++config MBOX_RP1
++ tristate "RP1 Mailbox"
++ depends on MFD_RP1
++ help
++ An implementation of a mailbox interface to the Raspberry Pi RP1 I/O
++ interface. Although written as a mailbox driver, the hardware only
++ provides an array of 32 doorbells.
++ Say Y here if you want to use the RP1 Mailbox.
++
+ endif
+--- a/drivers/mailbox/Makefile
++++ b/drivers/mailbox/Makefile
+@@ -62,3 +62,5 @@ obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox
+ obj-$(CONFIG_QCOM_IPCC) += qcom-ipcc.o
+
+ obj-$(CONFIG_APPLE_MAILBOX) += apple-mailbox.o
++
++obj-$(CONFIG_MBOX_RP1) += rp1-mailbox.o
+--- /dev/null
++++ b/drivers/mailbox/rp1-mailbox.c
+@@ -0,0 +1,208 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (C) 2023 Raspberry Pi Ltd.
++ *
++ * Parts of this driver are based on:
++ * - bcm2835-mailbox.c
++ * Copyright (C) 2010,2015 Broadcom
++ * Copyright (C) 2013-2014 Lubomir Rintel
++ * Copyright (C) 2013 Craig McGeachie
++ */
++
++#include <linux/compat.h>
++#include <linux/device.h>
++#include <linux/dma-mapping.h>
++#include <linux/err.h>
++#include <linux/interrupt.h>
++#include <linux/irq.h>
++#include <linux/kernel.h>
++#include <linux/mailbox_controller.h>
++#include <linux/miscdevice.h>
++#include <linux/module.h>
++#include <linux/of_address.h>
++#include <linux/of_irq.h>
++#include <linux/platform_device.h>
++
++/*
++ * RP1's PROC_EVENTS register can generate interrupts on the M3 cores (when
++ * enabled). The 32-bit register is treated as 32 events, all of which share a
++ * common interrupt. HOST_EVENTS is the same in the reverse direction.
++ */
++#define SYSCFG_PROC_EVENTS 0x00000008
++#define SYSCFG_HOST_EVENTS 0x0000000c
++#define SYSCFG_HOST_EVENT_IRQ_EN 0x00000010
++#define SYSCFG_HOST_EVENT_IRQ 0x00000014
++
++#define HW_SET_BITS 0x00002000
++#define HW_CLR_BITS 0x00003000
++
++#define MAX_CHANS 4 /* 32 is the hardware limit */
++
++struct rp1_mbox {
++ void __iomem *regs;
++ unsigned int irq;
++ struct mbox_controller controller;
++};
++
++static struct rp1_mbox *rp1_chan_mbox(struct mbox_chan *chan)
++{
++ return container_of(chan->mbox, struct rp1_mbox, controller);
++}
++
++static unsigned int rp1_chan_event(struct mbox_chan *chan)
++{
++ return (unsigned int)(uintptr_t)chan->con_priv;
++}
++
++static irqreturn_t rp1_mbox_irq(int irq, void *dev_id)
++{
++ struct rp1_mbox *mbox = dev_id;
++ struct mbox_chan *chan;
++ unsigned int doorbell;
++ unsigned int evs;
++
++ evs = readl(mbox->regs + SYSCFG_HOST_EVENT_IRQ);
++ writel(evs, mbox->regs + SYSCFG_HOST_EVENTS + HW_CLR_BITS);
++
++ while (evs) {
++ doorbell = __ffs(evs);
++ chan = &mbox->controller.chans[doorbell];
++ mbox_chan_received_data(chan, NULL);
++ evs &= ~(1 << doorbell);
++ }
++ return IRQ_HANDLED;
++}
++
++static int rp1_send_data(struct mbox_chan *chan, void *data)
++{
++ struct rp1_mbox *mbox = rp1_chan_mbox(chan);
++ unsigned int event = rp1_chan_event(chan);
++
++ writel(event, mbox->regs + SYSCFG_PROC_EVENTS + HW_SET_BITS);
++
++ return 0;
++}
++
++static int rp1_startup(struct mbox_chan *chan)
++{
++ struct rp1_mbox *mbox = rp1_chan_mbox(chan);
++ unsigned int event = rp1_chan_event(chan);
++
++ writel(event, mbox->regs + SYSCFG_HOST_EVENT_IRQ_EN + HW_SET_BITS);
++
++ return 0;
++}
++
++static void rp1_shutdown(struct mbox_chan *chan)
++{
++ struct rp1_mbox *mbox = rp1_chan_mbox(chan);
++ unsigned int event = rp1_chan_event(chan);
++
++ writel(event, mbox->regs + SYSCFG_HOST_EVENT_IRQ_EN + HW_CLR_BITS);
++}
++
++static bool rp1_last_tx_done(struct mbox_chan *chan)
++{
++ struct rp1_mbox *mbox = rp1_chan_mbox(chan);
++ unsigned int event = rp1_chan_event(chan);
++ unsigned int evs;
++
++ evs = readl(mbox->regs + SYSCFG_HOST_EVENT_IRQ);
++
++ return !(evs & event);
++}
++
++static const struct mbox_chan_ops rp1_mbox_chan_ops = {
++ .send_data = rp1_send_data,
++ .startup = rp1_startup,
++ .shutdown = rp1_shutdown,
++ .last_tx_done = rp1_last_tx_done
++};
++
++static struct mbox_chan *rp1_mbox_xlate(struct mbox_controller *mbox,
++ const struct of_phandle_args *spec)
++{
++ struct mbox_chan *chan;
++ unsigned int doorbell;
++
++ if (spec->args_count != 1)
++ return ERR_PTR(-EINVAL);
++
++ doorbell = spec->args[0];
++ if (doorbell >= MAX_CHANS)
++ return ERR_PTR(-EINVAL);
++
++ chan = &mbox->chans[doorbell];
++ if (chan->con_priv)
++ return ERR_PTR(-EBUSY);
++
++ chan->con_priv = (void *)(uintptr_t)(1 << doorbell);
++
++ return chan;
++}
++
++static int rp1_mbox_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ struct mbox_chan *chans;
++ struct rp1_mbox *mbox;
++ int ret = 0;
++
++ mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
++ if (mbox == NULL)
++ return -ENOMEM;
++
++ ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
++ rp1_mbox_irq, 0, dev_name(dev), mbox);
++ if (ret) {
++ dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n",
++ ret);
++ return -ENODEV;
++ }
++
++ mbox->regs = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(mbox->regs)) {
++ ret = PTR_ERR(mbox->regs);
++ return ret;
++ }
++
++ chans = devm_kcalloc(dev, MAX_CHANS, sizeof(*chans), GFP_KERNEL);
++ if (!chans)
++ return -ENOMEM;
++
++ mbox->controller.txdone_poll = true;
++ mbox->controller.txpoll_period = 5;
++ mbox->controller.ops = &rp1_mbox_chan_ops;
++ mbox->controller.of_xlate = &rp1_mbox_xlate;
++ mbox->controller.dev = dev;
++ mbox->controller.num_chans = MAX_CHANS;
++ mbox->controller.chans = chans;
++
++ ret = devm_mbox_controller_register(dev, &mbox->controller);
++ if (ret)
++ return ret;
++
++ platform_set_drvdata(pdev, mbox);
++
++ return 0;
++}
++
++static const struct of_device_id rp1_mbox_of_match[] = {
++ { .compatible = "raspberrypi,rp1-mbox", },
++ {},
++};
++MODULE_DEVICE_TABLE(of, rp1_mbox_of_match);
++
++static struct platform_driver rp1_mbox_driver = {
++ .driver = {
++ .name = "rp1-mbox",
++ .of_match_table = rp1_mbox_of_match,
++ },
++ .probe = rp1_mbox_probe,
++};
++
++module_platform_driver(rp1_mbox_driver);
++
++MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
++MODULE_DESCRIPTION("RP1 mailbox IPC driver");
++MODULE_LICENSE("GPL v2");
--- /dev/null
+From 67daeadcaa7cee1f4b9df7aa108d199e73f35451 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Thu, 31 Oct 2024 17:36:54 +0000
+Subject: [PATCH] firmware: Add an RP1 firmware interface over mbox
+
+The RP1 firmware runs a simple communications channel over some shared
+memory and a mailbox. This driver provides access to that channel.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/firmware/Kconfig | 9 +
+ drivers/firmware/Makefile | 1 +
+ drivers/firmware/rp1.c | 316 +++++++++++++++++++++++++++++++++++
+ include/linux/rp1-firmware.h | 53 ++++++
+ 4 files changed, 379 insertions(+)
+ create mode 100644 drivers/firmware/rp1.c
+ create mode 100644 include/linux/rp1-firmware.h
+
+--- a/drivers/firmware/Kconfig
++++ b/drivers/firmware/Kconfig
+@@ -153,6 +153,15 @@ config RASPBERRYPI_FIRMWARE
+ This option enables support for communicating with the firmware on the
+ Raspberry Pi.
+
++config FIRMWARE_RP1
++ tristate "RP1 Firmware Driver"
++ depends on MBOX_RP1
++ help
++ The Raspberry Pi RP1 processor presents a firmware
++ interface using shared memory and a mailbox. To enable
++ the driver that communicates with it, say Y. Otherwise,
++ say N.
++
+ config FW_CFG_SYSFS
+ tristate "QEMU fw_cfg device support in sysfs"
+ depends on SYSFS && (ARM || ARM64 || PARISC || PPC_PMAC || SPARC || X86)
+--- a/drivers/firmware/Makefile
++++ b/drivers/firmware/Makefile
+@@ -17,6 +17,7 @@ obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o
+ obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o
+ obj-$(CONFIG_MTK_ADSP_IPC) += mtk-adsp-ipc.o
+ obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o
++obj-$(CONFIG_FIRMWARE_RP1) += rp1.o
+ obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o
+ obj-$(CONFIG_QCOM_SCM) += qcom-scm.o
+ qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
+--- /dev/null
++++ b/drivers/firmware/rp1.c
+@@ -0,0 +1,316 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (C) 2023-24 Raspberry Pi Ltd.
++ *
++ * Parts of this driver are based on:
++ * - raspberrypi.c, by Eric Anholt <eric@anholt.net>
++ * Copyright (C) 2015 Broadcom
++ */
++
++#include <linux/dma-mapping.h>
++#include <linux/kref.h>
++#include <linux/mailbox_client.h>
++#include <linux/module.h>
++#include <linux/of_address.h>
++#include <linux/of_platform.h>
++#include <linux/platform_device.h>
++#include <linux/rp1-firmware.h>
++
++#define RP1_MAILBOX_FIRMWARE 0
++
++enum rp1_firmware_ops {
++ MBOX_SUCCESS = 0x0000,
++ GET_FIRMWARE_VERSION = 0x0001, // na -> 160-bit version
++ GET_FEATURE = 0x0002, // FOURCC -> op base (0 == unsupported), op count
++
++ COMMON_COUNT
++};
++
++struct rp1_firmware {
++ struct mbox_client cl;
++ struct mbox_chan *chan; /* The doorbell channel */
++ uint32_t __iomem *buf; /* The shared buffer */
++ u32 buf_size; /* The size of the shared buffer */
++ struct completion c;
++
++ struct kref consumers;
++};
++
++struct rp1_get_feature_resp {
++ uint32_t op_base;
++ uint32_t op_count;
++};
++
++static DEFINE_MUTEX(transaction_lock);
++
++static const struct of_device_id rp1_firmware_of_match[] = {
++ { .compatible = "raspberrypi,rp1-firmware", },
++ {},
++};
++MODULE_DEVICE_TABLE(of, rp1_firmware_of_match);
++
++static void response_callback(struct mbox_client *cl, void *msg)
++{
++ struct rp1_firmware *fw = container_of(cl, struct rp1_firmware, cl);
++
++ complete(&fw->c);
++}
++
++/*
++ * Sends a request to the RP1 firmware and synchronously waits for the reply.
++ * Returns zero or a positive count of response bytes on success, negative on
++ * error.
++ */
++
++int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
++ const void *data, unsigned int data_len,
++ void *resp, unsigned int resp_space)
++{
++ int ret;
++ u32 rc;
++
++ if (data_len + 4 > fw->buf_size)
++ return -EINVAL;
++
++ mutex_lock(&transaction_lock);
++
++ memcpy_toio(&fw->buf[1], data, data_len);
++ writel((op << 16) | data_len, fw->buf);
++
++ reinit_completion(&fw->c);
++ ret = mbox_send_message(fw->chan, NULL);
++ if (ret >= 0) {
++ if (wait_for_completion_timeout(&fw->c, HZ))
++ ret = 0;
++ else
++ ret = -ETIMEDOUT;
++ } else {
++ dev_err(fw->cl.dev, "mbox_send_message returned %d\n", ret);
++ }
++
++ if (ret == 0) {
++ rc = readl(fw->buf);
++ if (rc & 0x80000000) {
++ ret = (int32_t)rc;
++ } else {
++ ret = min(rc, resp_space);
++ memcpy_fromio(resp, &fw->buf[1], ret);
++ }
++ }
++
++ mutex_unlock(&transaction_lock);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(rp1_firmware_message);
++
++static void rp1_firmware_delete(struct kref *kref)
++{
++ struct rp1_firmware *fw = container_of(kref, struct rp1_firmware, consumers);
++
++ mbox_free_channel(fw->chan);
++ kfree(fw);
++}
++
++void rp1_firmware_put(struct rp1_firmware *fw)
++{
++ kref_put(&fw->consumers, rp1_firmware_delete);
++}
++EXPORT_SYMBOL_GPL(rp1_firmware_put);
++
++int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
++ uint32_t *op_base, uint32_t *op_count)
++{
++ struct rp1_get_feature_resp resp;
++ int ret;
++
++ memset(&resp, 0, sizeof(resp));
++ ret = rp1_firmware_message(fw, GET_FEATURE,
++ &fourcc, sizeof(fourcc),
++ &resp, sizeof(resp));
++ *op_base = resp.op_base;
++ *op_count = resp.op_count;
++ if (ret < 0)
++ return ret;
++ if (ret < sizeof(resp) || !resp.op_base)
++ return -EOPNOTSUPP;
++ return 0;
++}
++EXPORT_SYMBOL_GPL(rp1_firmware_get_feature);
++
++static void devm_rp1_firmware_put(void *data)
++{
++ struct rp1_firmware *fw = data;
++
++ rp1_firmware_put(fw);
++}
++
++/**
++ * rp1_firmware_get - Get pointer to rp1_firmware structure.
++ *
++ * The reference to rp1_firmware has to be released with rp1_firmware_put().
++ *
++ * Returns an error pointer on failure.
++ */
++struct rp1_firmware *rp1_firmware_get(struct device_node *client)
++{
++ const char *match = rp1_firmware_of_match[0].compatible;
++ struct platform_device *pdev;
++ struct device_node *fwnode;
++ struct rp1_firmware *fw;
++
++ if (client) {
++ fwnode = of_parse_phandle(client, "firmware", 0);
++ if (!fwnode)
++ fwnode = of_get_parent(client);
++ if (fwnode && !of_device_is_compatible(fwnode, match)) {
++ of_node_put(fwnode);
++ fwnode = NULL;
++ }
++ }
++
++ if (!fwnode)
++ fwnode = of_find_matching_node(NULL, rp1_firmware_of_match);
++
++ if (!fwnode)
++ return ERR_PTR(-ENOENT);
++
++ pdev = of_find_device_by_node(fwnode);
++ of_node_put(fwnode);
++
++ if (!pdev)
++ return ERR_PTR(-EPROBE_DEFER);
++
++ fw = platform_get_drvdata(pdev);
++ if (!fw)
++ goto err_defer;
++
++ if (!kref_get_unless_zero(&fw->consumers))
++ goto err_defer;
++
++ put_device(&pdev->dev);
++
++ return fw;
++
++err_defer:
++ put_device(&pdev->dev);
++ return ERR_PTR(-EPROBE_DEFER);
++}
++EXPORT_SYMBOL_GPL(rp1_firmware_get);
++
++/**
++ * devm_rp1_firmware_get - Get pointer to rp1_firmware structure.
++ * @firmware_node: Pointer to the firmware Device Tree node.
++ *
++ * Returns NULL is the firmware device is not ready.
++ */
++struct rp1_firmware *devm_rp1_firmware_get(struct device *dev, struct device_node *client)
++{
++ struct rp1_firmware *fw;
++ int ret;
++
++ fw = rp1_firmware_get(client);
++ if (IS_ERR(fw))
++ return fw;
++
++ ret = devm_add_action_or_reset(dev, devm_rp1_firmware_put, fw);
++ if (ret)
++ return ERR_PTR(ret);
++
++ return fw;
++}
++EXPORT_SYMBOL_GPL(devm_rp1_firmware_get);
++
++static int rp1_firmware_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ struct device_node *shmem;
++ struct rp1_firmware *fw;
++ struct resource res;
++ uint32_t version[5];
++ int ret;
++
++ shmem = of_parse_phandle(dev->of_node, "shmem", 0);
++ if (!of_device_is_compatible(shmem, "raspberrypi,rp1-shmem")) {
++ of_node_put(shmem);
++ return -ENXIO;
++ }
++
++ ret = of_address_to_resource(shmem, 0, &res);
++ of_node_put(shmem);
++ if (ret) {
++ dev_err(dev, "failed to get shared memory (%pOF) - %d\n", shmem, ret);
++ return ret;
++ }
++
++ /*
++ * Memory will be freed by rp1_firmware_delete() once all users have
++ * released their firmware handles. Don't use devm_kzalloc() here.
++ */
++ fw = kzalloc(sizeof(*fw), GFP_KERNEL);
++ if (!fw)
++ return -ENOMEM;
++
++ fw->buf_size = resource_size(&res);
++ fw->buf = devm_ioremap(dev, res.start, fw->buf_size);
++ if (!fw->buf) {
++ dev_err(dev, "failed to ioremap shared memory\n");
++ kfree(fw);
++ return -EADDRNOTAVAIL;
++ }
++
++ fw->cl.dev = dev;
++ fw->cl.rx_callback = response_callback;
++ fw->cl.tx_block = false;
++
++ fw->chan = mbox_request_channel(&fw->cl, RP1_MAILBOX_FIRMWARE);
++ if (IS_ERR(fw->chan)) {
++ int ret = PTR_ERR(fw->chan);
++
++ if (ret != -EPROBE_DEFER)
++ dev_err(dev, "Failed to get mbox channel: %d\n", ret);
++ kfree(fw);
++ return ret;
++ }
++
++ init_completion(&fw->c);
++ kref_init(&fw->consumers);
++
++ platform_set_drvdata(pdev, fw);
++
++ ret = rp1_firmware_message(fw, GET_FIRMWARE_VERSION,
++ NULL, 0, &version, sizeof(version));
++ if (ret == sizeof(version)) {
++ dev_info(dev, "RP1 Firmware version %08x%08x%08x%08x%08x\n",
++ version[0], version[1], version[2], version[3], version[4]);
++ ret = 0;
++ } else if (ret >= 0) {
++ ret = -EIO;
++ }
++
++ return ret;
++}
++
++static int rp1_firmware_remove(struct platform_device *pdev)
++{
++ struct rp1_firmware *fw = platform_get_drvdata(pdev);
++
++ rp1_firmware_put(fw);
++
++ return 0;
++}
++
++static struct platform_driver rp1_firmware_driver = {
++ .driver = {
++ .name = "rp1-firmware",
++ .of_match_table = rp1_firmware_of_match,
++ },
++ .probe = rp1_firmware_probe,
++ .remove = rp1_firmware_remove,
++};
++
++module_platform_driver(rp1_firmware_driver);
++
++MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
++MODULE_DESCRIPTION("RP1 firmware driver");
++MODULE_LICENSE("GPL v2");
+--- /dev/null
++++ b/include/linux/rp1-firmware.h
+@@ -0,0 +1,53 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++/*
++ * Copyright (C) 2023 2023-2024 Raspberry Pi Ltd.
++ */
++
++#ifndef __SOC_RP1_FIRMWARE_H__
++#define __SOC_RP1_FIRMWARE_H__
++
++#include <linux/types.h>
++#include <linux/of_device.h>
++
++#define RP1_FOURCC(s) ((uint32_t)((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0)))
++
++struct rp1_firmware;
++
++#if IS_ENABLED(CONFIG_FIRMWARE_RP1)
++int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
++ const void *data, unsigned int data_len,
++ void *resp, unsigned int resp_space);
++void rp1_firmware_put(struct rp1_firmware *fw);
++struct rp1_firmware *rp1_firmware_get(struct device_node *fwnode);
++struct rp1_firmware *devm_rp1_firmware_get(struct device *dev, struct device_node *fwnode);
++int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
++ uint32_t *op_base, uint32_t *op_count);
++#else
++static inline int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
++ const void *data, unsigned int data_len,
++ void *resp, unsigned int resp_space)
++{
++ return -EOPNOTSUPP;
++}
++
++static inline void rp1_firmware_put(struct rp1_firmware *fw) { }
++
++static inline struct rp1_firmware *rp1_firmware_get(struct device_node *fwnode)
++{
++ return NULL;
++}
++
++static inline struct rp1_firmware *devm_rp1_firmware_get(struct device *dev,
++ struct device_node *fwnode)
++{
++ return NULL;
++}
++
++static inline int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
++ uint32_t *op_base, uint32_t *op_count)
++{
++ return -EOPNOTSUPP;
++}
++#endif
++
++#endif /* __SOC_RP1_FIRMWARE_H__ */
--- /dev/null
+From 55fd5c9018e1520d45f08cf08630a493ec7dedea 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>
+---
+ drivers/misc/Kconfig | 8 +
+ drivers/misc/Makefile | 1 +
+ drivers/misc/rp1-fw-pio.h | 53 ++
+ drivers/misc/rp1-pio.c | 1064 ++++++++++++++++++++++++++++++++
+ include/uapi/misc/rp1_pio_if.h | 212 +++++++
+ 5 files changed, 1338 insertions(+)
+ create mode 100644 drivers/misc/rp1-fw-pio.h
+ create mode 100644 drivers/misc/rp1-pio.c
+ 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"
++ select FIRMWARE_RP1
++ 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
+@@ -18,6 +18,7 @@ obj-$(CONFIG_TIFM_7XX1) += tifm_7
+ obj-$(CONFIG_PHANTOM) += phantom.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,53 @@
++/* 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_COUNT
++};
++
++#endif
+--- /dev/null
++++ b/drivers/misc/rp1-pio.c
+@@ -0,0 +1,1064 @@
++// 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/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_buf_info {
++ void *buf;
++ dma_addr_t phys;
++ 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;
++};
++
++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;
++}
++
++static 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));
++}
++
++static 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;
++}
++
++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);
++}
++
++static 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;
++}
++
++static 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;
++}
++
++static 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;
++}
++
++static 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;
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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;
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static 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));
++}
++
++static void rp1_pio_sm_dma_callback(void *param)
++{
++ struct dma_info *dma = param;
++
++ up(&dma->buf_sem);
++}
++
++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].phys);
++ }
++
++ dma_release_channel(dma->chan);
++}
++
++static int rp1_pio_sm_config_xfer(struct rp1_pio_client *client, void *param)
++{
++ struct rp1_pio_sm_config_xfer_args *args = param;
++ 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 = {};
++ phys_addr_t fifo_addr;
++ struct dma_info *dma;
++ uint32_t dma_mask;
++ char chan_name[4];
++ uint buf_size;
++ int ret = 0;
++
++ if (args->sm >= RP1_PIO_SMS_COUNT || args->dir >= RP1_PIO_DIR_COUNT ||
++ !args->buf_size || (args->buf_size & 3) ||
++ !args->buf_count || args->buf_count > DMA_BOUNCE_BUFFER_COUNT)
++ return -EINVAL;
++
++ dma_mask = 1 << (args->sm * 2 + args->dir);
++
++ dma = &pio->dma_configs[args->sm][args->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 = args->buf_size;
++ /* Round up the allocations */
++ buf_size = ROUND_UP(args->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] = (args->dir == RP1_PIO_DIR_TO_SM) ? 't' : 'r';
++ chan_name[1] = 'x';
++ chan_name[2] = '0' + args->sm;
++ chan_name[3] = '\0';
++
++ dma->chan = dma_request_chan(dev, chan_name);
++ if (IS_ERR(dma->chan))
++ return PTR_ERR(dma->chan);
++
++ /* Alloc and map bounce buffers */
++ for (dma->buf_count = 0; dma->buf_count < args->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->phys, GFP_KERNEL);
++ if (!dbi->buf) {
++ ret = -ENOMEM;
++ goto err_dma_free;
++ }
++ sg_init_table(&dbi->sgl, 1);
++ sg_dma_address(&dbi->sgl) = dbi->phys;
++ }
++
++ fifo_addr = pio->phys_addr;
++ fifo_addr += args->sm * (RP1_PIO_FIFO_TX1 - RP1_PIO_FIFO_TX0);
++ fifo_addr += (args->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 = (args->dir == RP1_PIO_DIR_TO_SM) ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
++
++ ret = dmaengine_slave_config(dma->chan, &config);
++ if (ret)
++ goto err_dma_free;
++
++ set_dmactrl_args.sm = args->sm;
++ set_dmactrl_args.is_tx = (args->dir == RP1_PIO_DIR_TO_SM);
++ set_dmactrl_args.ctrl = RP1_PIO_DMACTRL_DEFAULT;
++ if (args->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);
++
++ 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_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 failedzn");
++ 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");
++ 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");
++ 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_async_issue_pending(dma->chan);
++
++ dma->head_idx++;
++ bytes -= copy_bytes;
++ }
++
++ return ret;
++}
++
++static int rp1_pio_sm_xfer_data(struct rp1_pio_client *client, void *param)
++{
++ struct rp1_pio_sm_xfer_data_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);
++}
++
++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),
++ HANDLER(SM_XFER_DATA, sm_xfer_data),
++
++ 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(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),
++};
++
++static int rp1_pio_open(struct inode *inode, struct file *filp)
++{
++ struct rp1_pio_device *pio = g_pio;
++ struct rp1_pio_client *client;
++
++ client = kzalloc(sizeof(*client), GFP_KERNEL);
++
++ client->pio = pio;
++ filp->private_data = client;
++
++ return 0;
++}
++
++static int rp1_pio_release(struct inode *inode, struct file *filp)
++{
++ struct rp1_pio_client *client = filp->private_data;
++ 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);
++
++ 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;
++}
++
++const struct file_operations rp1_pio_fops = {
++ .owner = THIS_MODULE,
++ .open = rp1_pio_open,
++ .release = rp1_pio_release,
++ .unlocked_ioctl = rp1_pio_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;
++ }
++
++ fw = devm_rp1_firmware_get(dev, dev->of_node);
++ if (IS_ERR(fw))
++ 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;
++ }
++
++ 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_unregister;
++ }
++
++ 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_cdev_del;
++ }
++ pdev->id = of_alias_get_id(pdev->dev.of_node, "pio");
++ if (pdev->id < 0) {
++ dev_err(dev, "alias is missing\n");
++ return -EINVAL;
++ 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_class_destroy;
++ }
++
++ g_pio = pio;
++
++ dev_info(dev, "Created instance as %s\n", dev_name);
++ return 0;
++
++out_class_destroy:
++ class_destroy(pio->dev_class);
++
++out_cdev_del:
++ cdev_del(&pio->cdev);
++
++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;
++}
++
++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_new = 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/uapi/misc/rp1_pio_if.h
+@@ -0,0 +1,212 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++/*
++ * 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; /* IN/OUT */
++};
++
++struct rp1_pio_sm_set_dmactrl_args {
++ uint16_t sm;
++ uint8_t is_tx;
++ uint8_t rsvd;
++ uint32_t ctrl;
++};
++
++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_xfer_data_args {
++ uint16_t sm;
++ uint16_t dir;
++ uint16_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)
++
++#ifdef CONFIG_COMPAT
++//XXX #define PIO_IOC_SM_XFER_DATA32 _IOW(PIO_IOC_MAGIC, 2, struct pio_sm_xfer_data_args)
++#endif
++
++#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_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
--- /dev/null
+From 0b76dec8dfba8c1a4793dff0c86bf73d088a812e Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Fri, 1 Nov 2024 09:12:01 +0000
+Subject: [PATCH] dts: bcm2712-rpi: Add RP1 firmware and mailboxes
+
+Declare the communications channel to RP1.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ .../boot/dts/broadcom/bcm2712-rpi-5-b.dts | 4 +--
+ .../boot/dts/broadcom/bcm2712-rpi-cm5.dtsi | 4 +--
+ arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi | 4 +++
+ arch/arm64/boot/dts/broadcom/rp1.dtsi | 27 +++++++++++++++++++
+ 4 files changed, 35 insertions(+), 4 deletions(-)
+
+--- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts
++++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts
+@@ -195,7 +195,7 @@ i2c_rp1boot: &_i2c3 { };
+ // This is the RP1 peripheral space
+ ranges = <0xc0 0x40000000
+ 0x02000000 0x00 0x00000000
+- 0x00 0x00400000>;
++ 0x00 0x00410000>;
+
+ dma-ranges =
+ // inbound RP1 1x_xxxxxxxx -> PCIe 1x_xxxxxxxx
+@@ -207,7 +207,7 @@ i2c_rp1boot: &_i2c3 { };
+ // This allows the RP1 DMA controller to address RP1 hardware
+ <0xc0 0x40000000
+ 0x02000000 0x0 0x00000000
+- 0x0 0x00400000>,
++ 0x0 0x00410000>,
+
+ // inbound RP1 0x_xxxxxxxx -> PCIe 1x_xxxxxxxx
+ <0x00 0x00000000
+--- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi
++++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi
+@@ -176,7 +176,7 @@ i2c_rp1boot: &_i2c3 { };
+ // This is the RP1 peripheral space
+ ranges = <0xc0 0x40000000
+ 0x02000000 0x00 0x00000000
+- 0x00 0x00400000>;
++ 0x00 0x00410000>;
+
+ dma-ranges =
+ // inbound RP1 1x_xxxxxxxx -> PCIe 1x_xxxxxxxx
+@@ -188,7 +188,7 @@ i2c_rp1boot: &_i2c3 { };
+ // This allows the RP1 DMA controller to address RP1 hardware
+ <0xc0 0x40000000
+ 0x02000000 0x0 0x00000000
+- 0x0 0x00400000>,
++ 0x0 0x00410000>,
+
+ // inbound RP1 0x_xxxxxxxx -> PCIe 1x_xxxxxxxx
+ <0x00 0x00000000
+--- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi
++++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi
+@@ -294,6 +294,10 @@ pciex4: &pcie2 { };
+ status = "okay";
+ };
+
++&rp1_mbox {
++ status = "okay";
++};
++
+ /* Add some gpiomem nodes to make the devices accessible to userspace.
+ * /dev/gpiomem<n> should expose the registers for the interface with DT alias
+ * gpio<n>.
+--- a/arch/arm64/boot/dts/broadcom/rp1.dtsi
++++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi
+@@ -13,6 +13,14 @@
+
+ // ranges and dma-ranges must be provided by the includer
+
++ rp1_mbox: mailbox@8000 {
++ compatible = "raspberrypi,rp1-mbox";
++ status = "disabled";
++ reg = <0xc0 0x40008000 0x0 0x4000>; // SYSCFG
++ interrupts = <RP1_INT_SYSCFG IRQ_TYPE_LEVEL_HIGH>;
++ #mbox-cells = <1>;
++ };
++
+ rp1_clocks: clocks@18000 {
+ compatible = "raspberrypi,rp1-clocks";
+ #clock-cells = <1>;
+@@ -1183,6 +1191,19 @@
+ assigned-clocks = <&rp1_clocks RP1_CLK_DPI>;
+ assigned-clock-parents = <&rp1_clocks RP1_PLL_VIDEO>;
+ };
++
++ sram: sram@400000 {
++ compatible = "mmio-sram";
++ reg = <0xc0 0x40400000 0x0 0x10000>;
++ #address-cells = <1>;
++ #size-cells = <1>;
++ ranges = <0 0xc0 0x40400000 0x10000>;
++
++ rp1_fw_shmem: shmem@ff00 {
++ compatible = "raspberrypi,rp1-shmem";
++ reg = <0xff00 0x100>; // firmware mailbox buffer
++ };
++ };
+ };
+ };
+
+@@ -1281,6 +1302,12 @@
+ };
+
+ / {
++ rp1_firmware: rp1_firmware {
++ compatible = "raspberrypi,rp1-firmware", "simple-mfd";
++ mboxes = <&rp1_mbox 0>;
++ shmem = <&rp1_fw_shmem>;
++ };
++
+ rp1_vdd_3v3: rp1_vdd_3v3 {
+ compatible = "regulator-fixed";
+ regulator-name = "vdd-3v3";
--- /dev/null
+From 3e3c1b9922b22d362a4a9133361597ac80b974bb Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Fri, 1 Nov 2024 09:13:53 +0000
+Subject: [PATCH] dts: bcm2712-rpi: Add the RP1 PIO device
+
+Declare the device that proxies RP1's PIO hardware.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi | 5 +++++
+ arch/arm64/boot/dts/broadcom/rp1.dtsi | 12 ++++++++++++
+ 2 files changed, 17 insertions(+)
+
+--- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi
++++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi
+@@ -97,6 +97,10 @@
+ };
+ };
+
++pio: &rp1_pio {
++ status = "okay";
++};
++
+ / {
+ chosen: chosen {
+ bootargs = "reboot=w coherent_pool=1M 8250.nr_uarts=1 pci=pcie_bus_safe cgroup_disable=memory numa_policy=interleave";
+@@ -129,6 +133,7 @@
+ i2c12 = &i2c_rp1boot;
+ mailbox = &mailbox;
+ mmc0 = &sdio1;
++ pio0 = &pio;
+ serial0 = &uart0;
+ serial1 = &uart1;
+ serial10 = &uart10;
+--- a/arch/arm64/boot/dts/broadcom/rp1.dtsi
++++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi
+@@ -1028,6 +1028,18 @@
+ status = "disabled";
+ };
+
++ rp1_pio: pio@178000 {
++ reg = <0xc0 0x40178000 0x0 0x20>;
++ compatible = "raspberrypi,rp1-pio";
++ firmware = <&rp1_firmware>;
++ dmas = <&rp1_dma RP1_DMA_PIO_CH0_TX>, <&rp1_dma RP1_DMA_PIO_CH0_RX>,
++ <&rp1_dma RP1_DMA_PIO_CH1_TX>, <&rp1_dma RP1_DMA_PIO_CH1_RX>,
++ <&rp1_dma RP1_DMA_PIO_CH2_TX>, <&rp1_dma RP1_DMA_PIO_CH2_RX>,
++ <&rp1_dma RP1_DMA_PIO_CH3_TX>, <&rp1_dma RP1_DMA_PIO_CH3_RX>;
++ dma-names = "tx0", "rx0", "tx1", "rx1", "tx2", "rx2", "tx3", "rx3";
++ status = "disabled";
++ };
++
+ rp1_mmc0: mmc@180000 {
+ reg = <0xc0 0x40180000 0x0 0x100>;
+ compatible = "raspberrypi,rp1-dwcmshc";
--- /dev/null
+From 2819a61eb000c207589c97eef9d69a237c6cfdf3 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Fri, 8 Nov 2024 09:31:38 +0000
+Subject: [PATCH] misc: rp1-pio: Add an in-kernel API
+
+The header file linux/pio_rp1.h adds a pico-sdk-like interface to the
+RP1 PIO subsystem for other drivers.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/misc/rp1-pio.c | 169 ++++--
+ include/linux/pio_instructions.h | 481 +++++++++++++++++
+ include/linux/pio_rp1.h | 873 +++++++++++++++++++++++++++++++
+ 3 files changed, 1474 insertions(+), 49 deletions(-)
+ create mode 100644 include/linux/pio_instructions.h
+ create mode 100644 include/linux/pio_rp1.h
+
+--- a/drivers/misc/rp1-pio.c
++++ b/drivers/misc/rp1-pio.c
+@@ -1,15 +1,17 @@
+ // 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.
++/*
++ * 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>
+@@ -97,6 +99,7 @@ struct rp1_pio_client {
+ uint32_t claimed_sms;
+ uint32_t claimed_instrs;
+ uint32_t claimed_dmas;
++ int error;
+ };
+
+ static struct rp1_pio_device *g_pio;
+@@ -195,7 +198,7 @@ static int rp1_pio_find_program(struct r
+ return -1;
+ }
+
+-static int rp1_pio_can_add_program(struct rp1_pio_client *client, void *param)
++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;
+@@ -217,8 +220,9 @@ static int rp1_pio_can_add_program(struc
+ 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);
+
+-static int rp1_pio_add_program(struct rp1_pio_client *client, void *param)
++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;
+@@ -254,6 +258,7 @@ static int rp1_pio_add_program(struct rp
+ 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)
+ {
+@@ -277,7 +282,7 @@ static void rp1_pio_remove_instrs(struct
+ mutex_unlock(&pio->instr_mutex);
+ }
+
+-static int rp1_pio_remove_program(struct rp1_pio_client *client, void *param)
++int rp1_pio_remove_program(struct rp1_pio_client *client, void *param)
+ {
+ struct rp1_pio_remove_program_args *args = param;
+ uint32_t used_mask;
+@@ -296,8 +301,9 @@ static int rp1_pio_remove_program(struct
+ }
+ return ret;
+ }
++EXPORT_SYMBOL_GPL(rp1_pio_remove_program);
+
+-static int rp1_pio_clear_instr_mem(struct rp1_pio_client *client, void *param)
++int rp1_pio_clear_instr_mem(struct rp1_pio_client *client, void *param)
+ {
+ struct rp1_pio_device *pio = client->pio;
+
+@@ -309,8 +315,9 @@ static int rp1_pio_clear_instr_mem(struc
+ mutex_unlock(&pio->instr_mutex);
+ return 0;
+ }
++EXPORT_SYMBOL_GPL(rp1_pio_clear_instr_mem);
+
+-static int rp1_pio_sm_claim(struct rp1_pio_client *client, void *param)
++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;
+@@ -328,8 +335,9 @@ static int rp1_pio_sm_claim(struct rp1_p
+ mutex_unlock(&pio->instr_mutex);
+ return ret;
+ }
++EXPORT_SYMBOL_GPL(rp1_pio_sm_claim);
+
+-static int rp1_pio_sm_unclaim(struct rp1_pio_client *client, void *param)
++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;
+@@ -341,99 +349,113 @@ static int rp1_pio_sm_unclaim(struct rp1
+ mutex_unlock(&pio->instr_mutex);
+ return 0;
+ }
++EXPORT_SYMBOL_GPL(rp1_pio_sm_unclaim);
+
+-static int rp1_pio_sm_is_claimed(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_sm_init(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_sm_set_config(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_sm_exec(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_sm_clear_fifos(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_sm_set_clkdiv(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_sm_set_pins(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_sm_set_pindirs(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_sm_set_enabled(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_sm_restart(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_sm_clkdiv_restart(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_sm_enable_sync(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_sm_put(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_sm_get(struct rp1_pio_client *client, void *param)
++int rp1_pio_sm_get(struct rp1_pio_client *client, void *param)
+ {
+ struct rp1_pio_sm_get_args *args = param;
+ int ret;
+@@ -444,69 +466,79 @@ static int rp1_pio_sm_get(struct rp1_pio
+ return offsetof(struct rp1_pio_sm_get_args, data) + ret;
+ return ret;
+ }
++EXPORT_SYMBOL_GPL(rp1_pio_sm_get);
+
+-static int rp1_pio_sm_set_dmactrl(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_gpio_init(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_gpio_set_function(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_gpio_set_pulls(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_gpio_set_outover(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_gpio_set_inover(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_gpio_set_oeover(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_gpio_set_input_enabled(struct rp1_pio_client *client, void *param)
++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);
+
+-static int rp1_pio_gpio_set_drive_strength(struct rp1_pio_client *client, void *param)
++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)
+ {
+@@ -633,7 +665,7 @@ static int rp1_pio_sm_tx_user(struct rp1
+ struct device *dev = &pdev->dev;
+ int ret = 0;
+
+- // Clean the slate - we're running synchronously
++ /* Clean the slate - we're running synchronously */
+ dma->head_idx = 0;
+ dma->tail_idx = 0;
+
+@@ -686,7 +718,7 @@ static int rp1_pio_sm_tx_user(struct rp1
+ bytes -= copy_bytes;
+ }
+
+- // Block for completion
++ /* 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");
+@@ -830,22 +862,22 @@ struct handler_info {
+ HANDLER(WRITE_HW, write_hw),
+ };
+
+-static int rp1_pio_open(struct inode *inode, struct file *filp)
++struct rp1_pio_client *pio_open(void)
+ {
+- struct rp1_pio_device *pio = g_pio;
+ struct rp1_pio_client *client;
+
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
++ if (!client)
++ return ERR_PTR(-ENOMEM);
+
+- client->pio = pio;
+- filp->private_data = client;
++ client->pio = g_pio;
+
+- return 0;
++ return client;
+ }
++EXPORT_SYMBOL_GPL(pio_open);
+
+-static int rp1_pio_release(struct inode *inode, struct file *filp)
++void pio_close(struct rp1_pio_client *client)
+ {
+- struct rp1_pio_client *client = filp->private_data;
+ struct rp1_pio_device *pio = client->pio;
+ uint claimed_dmas = client->claimed_dmas;
+ int i;
+@@ -885,6 +917,45 @@ static int rp1_pio_release(struct inode
+ /* Reinitialise the SM? */
+
+ kfree(client);
++}
++EXPORT_SYMBOL_GPL(pio_close);
++
++void pio_set_error(struct rp1_pio_client *client, int err)
++{
++ client->error = err;
++}
++EXPORT_SYMBOL_GPL(pio_set_error);
++
++int pio_get_error(const struct rp1_pio_client *client)
++{
++ return client->error;
++}
++EXPORT_SYMBOL_GPL(pio_get_error);
++
++void pio_clear_error(struct rp1_pio_client *client)
++{
++ client->error = 0;
++}
++EXPORT_SYMBOL_GPL(pio_clear_error);
++
++static int rp1_pio_open(struct inode *inode, struct file *filp)
++{
++ struct rp1_pio_client *client;
++
++ client = pio_open();
++ if (IS_ERR(client))
++ return PTR_ERR(client);
++
++ filp->private_data = client;
++
++ return 0;
++}
++
++static int rp1_pio_release(struct inode *inode, struct file *filp)
++{
++ struct rp1_pio_client *client = filp->private_data;
++
++ pio_close(client);
+
+ return 0;
+ }
+--- /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,873 @@
++/* 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) 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
++};
++
++typedef rp1_pio_sm_config pio_sm_config;
++
++typedef struct rp1_pio_client *PIO;
++
++void pio_set_error(struct rp1_pio_client *client, int err);
++int pio_get_error(struct rp1_pio_client *client);
++void pio_clear_error(struct rp1_pio_client *client);
++
++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_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);
++
++int pio_init(void);
++PIO pio_open(void);
++void pio_close(PIO pio);
++
++int pio_sm_config_xfer(PIO pio, uint sm, uint dir, uint buf_size, uint buf_count);
++int pio_sm_xfer_data(PIO pio, uint sm, uint dir, uint data_bytes, void *data);
++
++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);
++}
++
++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_claim(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);
++}
++
++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);
++}
++
++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_float(float div, uint16_t *div_int,
++ uint8_t *div_frac)
++{
++ if (bad_params_if(NULL, div < 1 || div > 65536))
++ return false;
++ *div_int = (uint16_t)div;
++ if (*div_int == 0)
++ *div_frac = 0;
++ else
++ *div_frac = (uint8_t)((div - (float)*div_int) * (1u << 8u));
++ 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, float div)
++{
++ struct rp1_pio_sm_set_clkdiv_args args = { .sm = sm };
++
++ if (!pio_calculate_clkdiv_from_float(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_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 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, float div)
++{
++ uint16_t div_int;
++ uint8_t div_frac;
++
++ pio_calculate_clkdiv_from_float(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
+From 4d20aadc3188ecfb62b309a9924ee9696a94fc33 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Fri, 8 Nov 2024 09:37:58 +0000
+Subject: [PATCH] pwm: Add pwm-pio-rp1 driver
+
+Use the PIO hardware on RP1 to implement a PWM interface.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/pwm/Kconfig | 11 ++
+ drivers/pwm/Makefile | 1 +
+ drivers/pwm/pwm-pio-rp1.c | 251 ++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 263 insertions(+)
+ create mode 100644 drivers/pwm/pwm-pio-rp1.c
+
+--- a/drivers/pwm/Kconfig
++++ b/drivers/pwm/Kconfig
+@@ -465,6 +465,17 @@ config PWM_PCA9685
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-pca9685.
+
++config PWM_PIO_RP1
++ tristate "RP1 PIO PWM support"
++ depends on FIRMWARE_RP1 || COMPILE_TEST
++ help
++ This is a PWM framework driver for Raspberry Pi 5, using the PIO
++ hardware of RP1 to provide PWM functionality. Supports up to 4
++ instances on GPIOs in bank 0.
++
++ To compile this driver as a module, choose M here: the module
++ will be called pwm-pio-rp1.
++
+ config PWM_PXA
+ tristate "PXA PWM support"
+ depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST
+--- a/drivers/pwm/Makefile
++++ b/drivers/pwm/Makefile
+@@ -42,6 +42,7 @@ obj-$(CONFIG_PWM_MXS) += pwm-mxs.o
+ obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o
+ obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o
+ obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o
++obj-$(CONFIG_PWM_PIO_RP1) += pwm-pio-rp1.o
+ obj-$(CONFIG_PWM_PXA) += pwm-pxa.o
+ obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o
+ obj-$(CONFIG_PWM_RP1) += pwm-rp1.o
+--- /dev/null
++++ b/drivers/pwm/pwm-pio-rp1.c
+@@ -0,0 +1,251 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Raspberry Pi PIO PWM.
++ *
++ * Copyright (C) 2024 Raspberry Pi Ltd.
++ *
++ * Author: Phil Elwell (phil@raspberrypi.com)
++ *
++ * Based on the pwm-rp1 driver by:
++ * Naushir Patuck <naush@raspberrypi.com>
++ * and on the pwm-gpio driver by:
++ * Vincent Whitchurch <vincent.whitchurch@axis.com>
++ */
++
++#include <linux/err.h>
++#include <linux/gpio/consumer.h>
++#include <linux/module.h>
++#include <linux/mutex.h>
++#include <linux/of.h>
++#include <linux/pio_rp1.h>
++#include <linux/platform_device.h>
++#include <linux/pwm.h>
++
++struct pwm_pio_rp1 {
++ struct pwm_chip chip;
++ struct device *dev;
++ struct gpio_desc *gpiod;
++ struct mutex mutex;
++ PIO pio;
++ uint sm;
++ uint offset;
++ uint gpio;
++ uint32_t period; /* In SM cycles */
++ uint32_t duty_cycle; /* In SM cycles */
++ enum pwm_polarity polarity;
++ bool enabled;
++};
++
++/* Generated from pwm.pio by pioasm */
++#define pwm_wrap_target 0
++#define pwm_wrap 6
++#define pwm_loop_ticks 3
++
++static const uint16_t pwm_program_instructions[] = {
++ // .wrap_target
++ 0x9080, // 0: pull noblock side 0
++ 0xa027, // 1: mov x, osr
++ 0xa046, // 2: mov y, isr
++ 0x00a5, // 3: jmp x != y, 5
++ 0x1806, // 4: jmp 6 side 1
++ 0xa042, // 5: nop
++ 0x0083, // 6: jmp y--, 3
++ // .wrap
++};
++
++static const struct pio_program pwm_program = {
++ .instructions = pwm_program_instructions,
++ .length = 7,
++ .origin = -1,
++};
++
++static unsigned int pwm_pio_resolution __read_mostly;
++
++static inline pio_sm_config pwm_program_get_default_config(uint offset)
++{
++ pio_sm_config c = pio_get_default_sm_config();
++
++ sm_config_set_wrap(&c, offset + pwm_wrap_target, offset + pwm_wrap);
++ sm_config_set_sideset(&c, 2, true, false);
++ return c;
++}
++
++static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin)
++{
++ pio_gpio_init(pio, pin);
++
++ pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
++ pio_sm_config c = pwm_program_get_default_config(offset);
++
++ sm_config_set_sideset_pins(&c, pin);
++ pio_sm_init(pio, sm, offset, &c);
++}
++
++/* Write `period` to the input shift register - must be disabled */
++static void pio_pwm_set_period(PIO pio, uint sm, uint32_t period)
++{
++ pio_sm_put_blocking(pio, sm, period);
++ pio_sm_exec(pio, sm, pio_encode_pull(false, false));
++ pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
++}
++
++/* Write `level` to TX FIFO. State machine will copy this into X. */
++static void pio_pwm_set_level(PIO pio, uint sm, uint32_t level)
++{
++ pio_sm_put_blocking(pio, sm, level);
++}
++
++static int pwm_pio_rp1_apply(struct pwm_chip *chip, struct pwm_device *pwm,
++ const struct pwm_state *state)
++{
++ struct pwm_pio_rp1 *ppwm = container_of(chip, struct pwm_pio_rp1, chip);
++ uint32_t new_duty_cycle;
++ uint32_t new_period;
++
++ if (state->duty_cycle && state->duty_cycle < pwm_pio_resolution)
++ return -EINVAL;
++
++ if (state->duty_cycle != state->period &&
++ (state->period - state->duty_cycle < pwm_pio_resolution))
++ return -EINVAL;
++
++ new_period = state->period / pwm_pio_resolution;
++ new_duty_cycle = state->duty_cycle / pwm_pio_resolution;
++
++ mutex_lock(&ppwm->mutex);
++
++ if ((ppwm->enabled && !state->enabled) || new_period != ppwm->period) {
++ pio_sm_set_enabled(ppwm->pio, ppwm->sm, false);
++ ppwm->enabled = false;
++ }
++
++ if (new_period != ppwm->period) {
++ pio_pwm_set_period(ppwm->pio, ppwm->sm, new_period);
++ ppwm->period = new_period;
++ }
++
++ if (state->enabled && new_duty_cycle != ppwm->duty_cycle) {
++ pio_pwm_set_level(ppwm->pio, ppwm->sm, new_duty_cycle);
++ ppwm->duty_cycle = new_duty_cycle;
++ }
++
++ if (state->polarity != ppwm->polarity) {
++ pio_gpio_set_outover(ppwm->pio, ppwm->gpio,
++ (state->polarity == PWM_POLARITY_INVERSED) ?
++ GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
++ ppwm->polarity = state->polarity;
++ }
++
++ if (!ppwm->enabled && state->enabled) {
++ pio_sm_set_enabled(ppwm->pio, ppwm->sm, true);
++ ppwm->enabled = true;
++ }
++
++ mutex_unlock(&ppwm->mutex);
++
++ return 0;
++}
++
++static const struct pwm_ops pwm_pio_rp1_ops = {
++ .apply = pwm_pio_rp1_apply,
++};
++
++static int pwm_pio_rp1_probe(struct platform_device *pdev)
++{
++ struct device_node *np = pdev->dev.of_node;
++ struct of_phandle_args of_args = { 0 };
++ struct device *dev = &pdev->dev;
++ struct pwm_pio_rp1 *ppwm;
++ struct pwm_chip *chip;
++ bool is_rp1;
++
++ ppwm = devm_kzalloc(dev, sizeof(*ppwm), GFP_KERNEL);
++ if (IS_ERR(ppwm))
++ return PTR_ERR(ppwm);
++
++ chip = &ppwm->chip;
++
++ mutex_init(&ppwm->mutex);
++
++ ppwm->gpiod = devm_gpiod_get(dev, NULL, GPIOD_ASIS);
++ /* Need to check that this is an RP1 GPIO in the first bank, and retrieve the offset */
++ /* Unfortunately I think this has to be done by parsing the gpios property */
++ if (IS_ERR(ppwm->gpiod))
++ return dev_err_probe(dev, PTR_ERR(ppwm->gpiod),
++ "could not get a gpio\n");
++
++ /* This really shouldn't fail, given that we have a gpiod */
++ if (of_parse_phandle_with_args(np, "gpios", "#gpio-cells", 0, &of_args))
++ return dev_err_probe(dev, -EINVAL,
++ "can't find gpio declaration\n");
++
++ is_rp1 = of_device_is_compatible(of_args.np, "raspberrypi,rp1-gpio");
++ of_node_put(of_args.np);
++ if (!is_rp1 || of_args.args_count != 2)
++ return dev_err_probe(dev, -EINVAL,
++ "not an RP1 gpio\n");
++
++ ppwm->gpio = of_args.args[0];
++
++ ppwm->pio = pio_open();
++ if (IS_ERR(ppwm->pio))
++ return dev_err_probe(dev, PTR_ERR(ppwm->pio),
++ "%pfw: could not open PIO\n",
++ dev_fwnode(dev));
++
++ ppwm->sm = pio_claim_unused_sm(ppwm->pio, false);
++ if ((int)ppwm->sm < 0) {
++ pio_close(ppwm->pio);
++ return dev_err_probe(dev, -EBUSY,
++ "%pfw: no free PIO SM\n",
++ dev_fwnode(dev));
++ }
++
++ ppwm->offset = pio_add_program(ppwm->pio, &pwm_program);
++ if (ppwm->offset == PIO_ORIGIN_ANY) {
++ pio_close(ppwm->pio);
++ return dev_err_probe(dev, -EBUSY,
++ "%pfw: not enough PIO program space\n",
++ dev_fwnode(dev));
++ }
++
++ pwm_program_init(ppwm->pio, ppwm->sm, ppwm->offset, ppwm->gpio);
++
++ pwm_pio_resolution = (1000u * 1000 * 1000 * pwm_loop_ticks) / clock_get_hz(clk_sys);
++
++ chip->dev = dev;
++ chip->ops = &pwm_pio_rp1_ops;
++ chip->atomic = true;
++ chip->npwm = 1;
++
++ platform_set_drvdata(pdev, ppwm);
++
++ return devm_pwmchip_add(dev, chip);
++}
++
++static void pwm_pio_rp1_remove(struct platform_device *pdev)
++{
++ struct pwm_pio_rp1 *ppwm = platform_get_drvdata(pdev);
++
++ pio_close(ppwm->pio);
++}
++
++static const struct of_device_id pwm_pio_rp1_dt_ids[] = {
++ { .compatible = "raspberrypi,pwm-pio-rp1" },
++ { /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, pwm_pio_rp1_dt_ids);
++
++static struct platform_driver pwm_pio_rp1_driver = {
++ .driver = {
++ .name = "pwm-pio-rp1",
++ .of_match_table = pwm_pio_rp1_dt_ids,
++ },
++ .probe = pwm_pio_rp1_probe,
++ .remove_new = pwm_pio_rp1_remove,
++};
++module_platform_driver(pwm_pio_rp1_driver);
++
++MODULE_DESCRIPTION("PWM PIO RP1 driver");
++MODULE_AUTHOR("Phil Elwell");
++MODULE_LICENSE("GPL");
--- /dev/null
+From ba7e2e3d03a432acbc338c6c03e46dcd97cfa1b3 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Thu, 7 Nov 2024 11:41:33 +0000
+Subject: [PATCH] overlays: Add pwm-pio overlay
+
+Add an overlay to enable a single-channel PIO-assisted PWM interface on any
+header pin.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ arch/arm/boot/dts/overlays/Makefile | 1 +
+ arch/arm/boot/dts/overlays/README | 8 ++++
+ arch/arm/boot/dts/overlays/overlay_map.dts | 4 ++
+ .../arm/boot/dts/overlays/pwm-pio-overlay.dts | 39 +++++++++++++++++++
+ 4 files changed, 52 insertions(+)
+ create mode 100644 arch/arm/boot/dts/overlays/pwm-pio-overlay.dts
+
+--- a/arch/arm/boot/dts/overlays/Makefile
++++ b/arch/arm/boot/dts/overlays/Makefile
+@@ -219,6 +219,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
+ pwm-2chan.dtbo \
+ pwm-gpio.dtbo \
+ pwm-ir-tx.dtbo \
++ pwm-pio.dtbo \
+ pwm1.dtbo \
+ qca7000.dtbo \
+ qca7000-uart0.dtbo \
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -3926,6 +3926,14 @@ Params: gpio_pin Output G
+ func Pin function (default 2 = Alt5)
+
+
++Name: pwm-pio
++Info: Configures a GPIO pin as PIO-assisted PWM output. Unlike hardware PWM,
++ this can be used on any RP1 GPIO in bank 0 (0-27). Up to 4 are
++ supported, assuming nothing else is using PIO. Pi 5 only.
++Load: dtoverlay=pwm-pio,<param>=<val>
++Params: gpio Output GPIO (0-27, default 4)
++
++
+ Name: pwm1
+ Info: Configures one or two PWM channel on PWM1 (BCM2711 only)
+ N.B.:
+--- a/arch/arm/boot/dts/overlays/overlay_map.dts
++++ b/arch/arm/boot/dts/overlays/overlay_map.dts
+@@ -240,6 +240,10 @@
+ bcm2712;
+ };
+
++ pwm-pio {
++ bcm2712;
++ };
++
+ pwm1 {
+ bcm2711;
+ };
+--- /dev/null
++++ b/arch/arm/boot/dts/overlays/pwm-pio-overlay.dts
+@@ -0,0 +1,39 @@
++// SPDX-License-Identifier: GPL-2.0
++// Device tree overlay for RP1 PIO PWM.
++/dts-v1/;
++/plugin/;
++
++/ {
++ compatible = "brcm,bcm2712";
++
++ fragment@0 {
++ target = <&gpio>;
++ __overlay__ {
++ pwm_pio_pins: pwm_pio_pins@4 {
++ brcm,pins = <4>; /* gpio 4 */
++ function = "pio";
++ bias-disable;
++ };
++ };
++ };
++
++ fragment@1 {
++ target-path = "/";
++ __overlay__ {
++ pwm_pio: pwm_pio@4 {
++ compatible = "raspberrypi,pwm-pio-rp1";
++ pinctrl-names = "default";
++ pinctrl-0 = <&pwm_pio_pins>;
++ gpios = <&gpio 4 0>;
++ };
++ };
++ };
++
++ __overrides__ {
++ gpio = <&pwm_pio>,"gpios:4",
++ <&pwm_pio_pins>,"brcm,pins:0",
++ /* modify reg values to allow multiple instantiation */
++ <&pwm_pio>,"reg:0",
++ <&pwm_pio_pins>,"reg:0";
++ };
++};
--- /dev/null
+From 1b5acd42281ad102b79f4e1794f0a0cccdafda05 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Sat, 16 Nov 2024 16:53:31 +0000
+Subject: [PATCH] fixup! misc: Add RP1 PIO driver
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ include/uapi/misc/rp1_pio_if.h | 6 +-----
+ 1 file changed, 1 insertion(+), 5 deletions(-)
+
+--- a/include/uapi/misc/rp1_pio_if.h
++++ b/include/uapi/misc/rp1_pio_if.h
+@@ -1,4 +1,4 @@
+-/* SPDX-License-Identifier: GPL-2.0 */
++/* SPDX-License-Identifier: GPL-2.0 + WITH Linux-syscall-note */
+ /*
+ * Copyright (c) 2023-24 Raspberry Pi Ltd.
+ * All rights reserved.
+@@ -169,10 +169,6 @@ struct rp1_access_hw_args {
+ #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)
+
+-#ifdef CONFIG_COMPAT
+-//XXX #define PIO_IOC_SM_XFER_DATA32 _IOW(PIO_IOC_MAGIC, 2, struct pio_sm_xfer_data_args)
+-#endif
+-
+ #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)
+
From b4472d09b1ffdafd8132803ffbec62596e559fd8 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Mon, 18 Nov 2024 09:10:52 +0000
-Subject: [PATCH 1394/1482] misc: rp1-pio: Add compat_ioctl method
+Subject: [PATCH] misc: rp1-pio: Add compat_ioctl method
Provide a compat_ioctl method, to support running a 64-bit kernel with
a 32-bit userland.
--- a/drivers/misc/rp1-pio.c
+++ b/drivers/misc/rp1-pio.c
-@@ -1023,11 +1023,75 @@ static long rp1_pio_ioctl(struct file *f
+@@ -996,11 +996,75 @@ static long rp1_pio_ioctl(struct file *f
return ret;
}
--- /dev/null
+From 0e4968617aad7d0f88e0a630499202eaae407a19 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Tue, 26 Mar 2024 15:57:46 +0000
+Subject: [PATCH] i2c: designware: Add support for bus clear feature
+
+Newer versions of the DesignWare I2C block support the detection of
+stuck signals, and a mechanism to recover from them. Add the required
+software support to the driver.
+
+This change was prompted by the observation that reading a single byte
+from register 0 of a VEML7700 seems to cause it to issue an ACK too
+early, and the controller to complain about losing arbitration. There
+is a suspicion that this may be a more widespread problem, but at least
+this patch prevents the bus from locking up.
+
+See: https://github.com/raspberrypi/linux/issues/6057
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/i2c/busses/i2c-designware-common.c | 12 ++++++++++++
+ drivers/i2c/busses/i2c-designware-core.h | 8 ++++++++
+ drivers/i2c/busses/i2c-designware-master.c | 19 ++++++++++++++++++-
+ 3 files changed, 38 insertions(+), 1 deletion(-)
+
+--- a/drivers/i2c/busses/i2c-designware-common.c
++++ b/drivers/i2c/busses/i2c-designware-common.c
+@@ -57,6 +57,8 @@ static char *abort_sources[] = {
+ "slave lost the bus while transmitting data to a remote master",
+ [ABRT_SLAVE_RD_INTX] =
+ "incorrect slave-transmitter mode configuration",
++ [ABRT_SLAVE_SDA_STUCK_AT_LOW] =
++ "SDA stuck at low",
+ };
+
+ static int dw_reg_read(void *context, unsigned int reg, unsigned int *val)
+@@ -609,8 +611,16 @@ int i2c_dw_wait_bus_not_busy(struct dw_i
+ int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev)
+ {
+ unsigned long abort_source = dev->abort_source;
++ unsigned int reg;
+ int i;
+
++ if (abort_source & DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW) {
++ regmap_write(dev->map, DW_IC_ENABLE,
++ DW_IC_ENABLE_ENABLE | DW_IC_ENABLE_BUS_RECOVERY);
++ regmap_read_poll_timeout(dev->map, DW_IC_ENABLE, reg,
++ !(reg & DW_IC_ENABLE_BUS_RECOVERY),
++ 1100, 200000);
++ }
+ if (abort_source & DW_IC_TX_ABRT_NOACK) {
+ for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources))
+ dev_dbg(dev->dev,
+@@ -625,6 +635,8 @@ int i2c_dw_handle_tx_abort(struct dw_i2c
+ return -EAGAIN;
+ else if (abort_source & DW_IC_TX_ABRT_GCALL_READ)
+ return -EINVAL; /* wrong msgs[] data */
++ else if (abort_source & DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW)
++ return -EREMOTEIO;
+ else
+ return -EIO;
+ }
+--- a/drivers/i2c/busses/i2c-designware-core.h
++++ b/drivers/i2c/busses/i2c-designware-core.h
+@@ -79,9 +79,12 @@
+ #define DW_IC_TX_ABRT_SOURCE 0x80
+ #define DW_IC_ENABLE_STATUS 0x9c
+ #define DW_IC_CLR_RESTART_DET 0xa8
++#define DW_IC_SCL_STUCK_AT_LOW_TIMEOUT 0xac
++#define DW_IC_SDA_STUCK_AT_LOW_TIMEOUT 0xb0
+ #define DW_IC_COMP_PARAM_1 0xf4
+ #define DW_IC_COMP_VERSION 0xf8
+ #define DW_IC_SDA_HOLD_MIN_VERS 0x3131312A /* "111*" == v1.11* */
++#define DW_IC_BUS_CLEAR_MIN_VERS 0x3230302A /* "200*" == v2.00* */
+ #define DW_IC_COMP_TYPE 0xfc
+ #define DW_IC_COMP_TYPE_VALUE 0x44570140 /* "DW" + 0x0140 */
+
+@@ -111,6 +114,7 @@
+
+ #define DW_IC_ENABLE_ENABLE BIT(0)
+ #define DW_IC_ENABLE_ABORT BIT(1)
++#define DW_IC_ENABLE_BUS_RECOVERY BIT(3)
+
+ #define DW_IC_STATUS_ACTIVITY BIT(0)
+ #define DW_IC_STATUS_TFE BIT(2)
+@@ -118,6 +122,7 @@
+ #define DW_IC_STATUS_MASTER_ACTIVITY BIT(5)
+ #define DW_IC_STATUS_SLAVE_ACTIVITY BIT(6)
+ #define DW_IC_STATUS_MASTER_HOLD_TX_FIFO_EMPTY BIT(7)
++#define DW_IC_STATUS_SDA_STUCK_NOT_RECOVERED BIT(11)
+
+ #define DW_IC_SDA_HOLD_RX_SHIFT 16
+ #define DW_IC_SDA_HOLD_RX_MASK GENMASK(23, 16)
+@@ -165,6 +170,7 @@
+ #define ABRT_SLAVE_FLUSH_TXFIFO 13
+ #define ABRT_SLAVE_ARBLOST 14
+ #define ABRT_SLAVE_RD_INTX 15
++#define ABRT_SLAVE_SDA_STUCK_AT_LOW 17
+
+ #define DW_IC_TX_ABRT_7B_ADDR_NOACK BIT(ABRT_7B_ADDR_NOACK)
+ #define DW_IC_TX_ABRT_10ADDR1_NOACK BIT(ABRT_10ADDR1_NOACK)
+@@ -180,6 +186,7 @@
+ #define DW_IC_RX_ABRT_SLAVE_RD_INTX BIT(ABRT_SLAVE_RD_INTX)
+ #define DW_IC_RX_ABRT_SLAVE_ARBLOST BIT(ABRT_SLAVE_ARBLOST)
+ #define DW_IC_RX_ABRT_SLAVE_FLUSH_TXFIFO BIT(ABRT_SLAVE_FLUSH_TXFIFO)
++#define DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW BIT(ABRT_SLAVE_SDA_STUCK_AT_LOW)
+
+ #define DW_IC_TX_ABRT_NOACK (DW_IC_TX_ABRT_7B_ADDR_NOACK | \
+ DW_IC_TX_ABRT_10ADDR1_NOACK | \
+--- a/drivers/i2c/busses/i2c-designware-master.c
++++ b/drivers/i2c/busses/i2c-designware-master.c
+@@ -215,6 +215,7 @@ static int i2c_dw_set_timings_master(str
+ */
+ static int i2c_dw_init_master(struct dw_i2c_dev *dev)
+ {
++ unsigned int timeout = 0;
+ int ret;
+
+ ret = i2c_dw_acquire_lock(dev);
+@@ -238,6 +239,17 @@ static int i2c_dw_init_master(struct dw_
+ regmap_write(dev->map, DW_IC_HS_SCL_LCNT, dev->hs_lcnt);
+ }
+
++ if (dev->master_cfg & DW_IC_CON_BUS_CLEAR_CTRL) {
++ /* Set a sensible timeout if not already configured */
++ regmap_read(dev->map, DW_IC_SDA_STUCK_AT_LOW_TIMEOUT, &timeout);
++ if (timeout == ~0) {
++ /* Use 10ms as a timeout, which is 1000 cycles at 100kHz */
++ timeout = i2c_dw_clk_rate(dev) * 10; /* clock rate is in kHz */
++ regmap_write(dev->map, DW_IC_SDA_STUCK_AT_LOW_TIMEOUT, timeout);
++ regmap_write(dev->map, DW_IC_SCL_STUCK_AT_LOW_TIMEOUT, timeout);
++ }
++ }
++
+ /* Write SDA hold time if supported */
+ if (dev->sda_hold_time)
+ regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time);
+@@ -1074,6 +1086,7 @@ int i2c_dw_probe_master(struct dw_i2c_de
+ struct i2c_adapter *adap = &dev->adapter;
+ unsigned long irq_flags;
+ unsigned int ic_con;
++ unsigned int id_ver;
+ int ret;
+
+ init_completion(&dev->cmd_complete);
+@@ -1109,7 +1122,11 @@ int i2c_dw_probe_master(struct dw_i2c_de
+ if (ret)
+ return ret;
+
+- if (ic_con & DW_IC_CON_BUS_CLEAR_CTRL)
++ ret = regmap_read(dev->map, DW_IC_COMP_VERSION, &id_ver);
++ if (ret)
++ return ret;
++
++ if (ic_con & DW_IC_CON_BUS_CLEAR_CTRL || id_ver >= DW_IC_BUS_CLEAR_MIN_VERS)
+ dev->master_cfg |= DW_IC_CON_BUS_CLEAR_CTRL;
+
+ ret = dev->init(dev);
--- /dev/null
+From 32511f035b086bca254d8adab234cef3541492b4 Mon Sep 17 00:00:00 2001
+From: Naushir Patuck <naush@raspberrypi.com>
+Date: Thu, 17 Oct 2024 11:37:29 +0100
+Subject: [PATCH] drivers: media: pci: Update Hailo accelerator device driver
+ to v4.19
+
+Sourced from https://github.com/hailo-ai/hailort-drivers/
+
+Signed-off-by: Naushir Patuck <naush@raspberrypi.com>
+---
+ drivers/media/pci/hailo/Makefile | 4 +-
+ drivers/media/pci/hailo/common/fw_operation.c | 50 ++-
+ drivers/media/pci/hailo/common/fw_operation.h | 8 +-
+ .../media/pci/hailo/common/fw_validation.c | 10 +-
+ .../media/pci/hailo/common/fw_validation.h | 7 +-
+ .../pci/hailo/common/hailo_ioctl_common.h | 28 +-
+ .../media/pci/hailo/common/hailo_resource.c | 23 +-
+ .../media/pci/hailo/common/hailo_resource.h | 2 +-
+ drivers/media/pci/hailo/common/pcie_common.c | 380 +++++++++---------
+ drivers/media/pci/hailo/common/pcie_common.h | 38 +-
+ drivers/media/pci/hailo/common/soc_structs.h | 79 ++++
+ drivers/media/pci/hailo/common/utils.h | 23 +-
+ drivers/media/pci/hailo/common/vdma_common.c | 93 +++--
+ drivers/media/pci/hailo/common/vdma_common.h | 22 +-
+ drivers/media/pci/hailo/src/fops.c | 284 ++-----------
+ drivers/media/pci/hailo/src/fops.h | 5 +-
+ drivers/media/pci/hailo/src/nnc.c | 299 ++++++++++++++
+ drivers/media/pci/hailo/src/nnc.h | 22 +
+ drivers/media/pci/hailo/src/pci_soc_ioctl.c | 155 -------
+ drivers/media/pci/hailo/src/pcie.c | 166 +++-----
+ drivers/media/pci/hailo/src/pcie.h | 26 +-
+ drivers/media/pci/hailo/src/soc.c | 244 +++++++++++
+ .../pci/hailo/src/{pci_soc_ioctl.h => soc.h} | 13 +-
+ drivers/media/pci/hailo/src/sysfs.c | 2 +-
+ drivers/media/pci/hailo/src/sysfs.h | 2 +-
+ drivers/media/pci/hailo/src/utils.c | 26 --
+ drivers/media/pci/hailo/utils/compact.h | 2 +-
+ drivers/media/pci/hailo/utils/fw_common.h | 2 +-
+ .../pci/hailo/utils/integrated_nnc_utils.c | 10 +-
+ .../pci/hailo/utils/integrated_nnc_utils.h | 2 +-
+ drivers/media/pci/hailo/utils/logs.c | 2 +-
+ drivers/media/pci/hailo/utils/logs.h | 2 +-
+ drivers/media/pci/hailo/vdma/ioctl.c | 18 +-
+ drivers/media/pci/hailo/vdma/ioctl.h | 6 +-
+ drivers/media/pci/hailo/vdma/memory.c | 12 +-
+ drivers/media/pci/hailo/vdma/memory.h | 2 +-
+ drivers/media/pci/hailo/vdma/vdma.c | 39 +-
+ drivers/media/pci/hailo/vdma/vdma.h | 5 +-
+ 38 files changed, 1224 insertions(+), 889 deletions(-)
+ create mode 100644 drivers/media/pci/hailo/common/soc_structs.h
+ create mode 100644 drivers/media/pci/hailo/src/nnc.c
+ create mode 100644 drivers/media/pci/hailo/src/nnc.h
+ delete mode 100755 drivers/media/pci/hailo/src/pci_soc_ioctl.c
+ create mode 100644 drivers/media/pci/hailo/src/soc.c
+ rename drivers/media/pci/hailo/src/{pci_soc_ioctl.h => soc.h} (53%)
+ mode change 100755 => 100644
+ delete mode 100644 drivers/media/pci/hailo/src/utils.c
+
+--- a/drivers/media/pci/hailo/Makefile
++++ b/drivers/media/pci/hailo/Makefile
+@@ -8,9 +8,9 @@ obj-$(CONFIG_MEDIA_PCI_HAILO) := hailo_p
+
+ hailo_pci-objs += src/pcie.o
+ hailo_pci-objs += src/fops.o
+-hailo_pci-objs += src/utils.o
+ hailo_pci-objs += src/sysfs.o
+-hailo_pci-objs += src/pci_soc_ioctl.o
++hailo_pci-objs += src/nnc.o
++hailo_pci-objs += src/soc.o
+
+ hailo_pci-objs += $(COMMON_SRC_DIRECTORY)/fw_validation.o
+ hailo_pci-objs += $(COMMON_SRC_DIRECTORY)/fw_operation.o
+--- a/drivers/media/pci/hailo/common/fw_operation.c
++++ b/drivers/media/pci/hailo/common/fw_operation.c
+@@ -1,7 +1,7 @@
+ // SPDX-License-Identifier: MIT
+ /**
+- * Copyright (c) 2022 Hailo Technologies Ltd. All rights reserved.
+-**/
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
++ **/
+
+ #include "fw_operation.h"
+
+@@ -15,7 +15,10 @@ typedef struct {
+ u32 chip_offset;
+ } FW_DEBUG_BUFFER_HEADER_t;
+
+-#define DEBUG_BUFFER_DATA_SIZE (DEBUG_BUFFER_TOTAL_SIZE - sizeof(FW_DEBUG_BUFFER_HEADER_t))
++#define DEBUG_BUFFER_DATA_SIZE (DEBUG_BUFFER_TOTAL_SIZE - sizeof(FW_DEBUG_BUFFER_HEADER_t))
++#define PCIE_D2H_NOTIFICATION_SRAM_OFFSET (0x640 + 0x640)
++#define PCIE_APP_CPU_DEBUG_OFFSET (8*1024)
++#define PCIE_CORE_CPU_DEBUG_OFFSET (PCIE_APP_CPU_DEBUG_OFFSET + DEBUG_BUFFER_TOTAL_SIZE)
+
+ int hailo_read_firmware_notification(struct hailo_resource *resource, struct hailo_d2h_notification *notification)
+ {
+@@ -35,6 +38,21 @@ int hailo_read_firmware_notification(str
+ return 0;
+ }
+
++int hailo_pcie_read_firmware_notification(struct hailo_resource *resource,
++ struct hailo_d2h_notification *notification)
++{
++ struct hailo_resource notification_resource;
++
++ if (PCIE_D2H_NOTIFICATION_SRAM_OFFSET > resource->size) {
++ return -EINVAL;
++ }
++
++ notification_resource.address = resource->address + PCIE_D2H_NOTIFICATION_SRAM_OFFSET,
++ notification_resource.size = sizeof(struct hailo_d2h_notification);
++
++ return hailo_read_firmware_notification(¬ification_resource, notification);
++}
++
+ static inline size_t calculate_log_ready_to_read(FW_DEBUG_BUFFER_HEADER_t *header)
+ {
+ size_t ready_to_read = 0;
+@@ -100,4 +118,30 @@ long hailo_read_firmware_log(struct hail
+
+ params->read_bytes = ready_to_read;
+ return 0;
++}
++
++long hailo_pcie_read_firmware_log(struct hailo_resource *resource, struct hailo_read_log_params *params)
++{
++ long err = 0;
++ struct hailo_resource log_resource = {resource->address, DEBUG_BUFFER_TOTAL_SIZE};
++
++ if (HAILO_CPU_ID_CPU0 == params->cpu_id) {
++ log_resource.address += PCIE_APP_CPU_DEBUG_OFFSET;
++ } else if (HAILO_CPU_ID_CPU1 == params->cpu_id) {
++ log_resource.address += PCIE_CORE_CPU_DEBUG_OFFSET;
++ } else {
++ return -EINVAL;
++ }
++
++ if (0 == params->buffer_size) {
++ params->read_bytes = 0;
++ return 0;
++ }
++
++ err = hailo_read_firmware_log(&log_resource, params);
++ if (0 != err) {
++ return err;
++ }
++
++ return 0;
+ }
+\ No newline at end of file
+--- a/drivers/media/pci/hailo/common/fw_operation.h
++++ b/drivers/media/pci/hailo/common/fw_operation.h
+@@ -1,7 +1,7 @@
+ // SPDX-License-Identifier: MIT
+ /**
+- * Copyright (c) 2022 Hailo Technologies Ltd. All rights reserved.
+-**/
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
++ **/
+
+ #ifndef _HAILO_COMMON_FIRMWARE_OPERATION_H_
+ #define _HAILO_COMMON_FIRMWARE_OPERATION_H_
+@@ -16,8 +16,12 @@ extern "C" {
+
+ int hailo_read_firmware_notification(struct hailo_resource *resource, struct hailo_d2h_notification *notification);
+
++int hailo_pcie_read_firmware_notification(struct hailo_resource *resource, struct hailo_d2h_notification *notification);
++
+ long hailo_read_firmware_log(struct hailo_resource *fw_logger_resource, struct hailo_read_log_params *params);
+
++long hailo_pcie_read_firmware_log(struct hailo_resource *resource, struct hailo_read_log_params *params);
++
+ #ifdef __cplusplus
+ }
+ #endif
+--- a/drivers/media/pci/hailo/common/fw_validation.c
++++ b/drivers/media/pci/hailo/common/fw_validation.c
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: MIT
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #include "fw_validation.h"
+@@ -85,15 +85,15 @@ exit:
+ }
+
+ int FW_VALIDATION__validate_cert_header(uintptr_t firmware_base_address,
+- size_t firmware_size, u32 *outer_consumed_firmware_offset, secure_boot_certificate_t **out_firmware_cert)
++ size_t firmware_size, u32 *outer_consumed_firmware_offset, secure_boot_certificate_header_t **out_firmware_cert)
+ {
+
+- secure_boot_certificate_t *firmware_cert = NULL;
++ secure_boot_certificate_header_t *firmware_cert = NULL;
+ int err = -EINVAL;
+ u32 consumed_firmware_offset = *outer_consumed_firmware_offset;
+
+- firmware_cert = (secure_boot_certificate_t *) (firmware_base_address + consumed_firmware_offset);
+- CONSUME_FIRMWARE(sizeof(secure_boot_certificate_t), -EINVAL);
++ firmware_cert = (secure_boot_certificate_header_t *) (firmware_base_address + consumed_firmware_offset);
++ CONSUME_FIRMWARE(sizeof(secure_boot_certificate_header_t), -EINVAL);
+
+ if ((MAXIMUM_FIRMWARE_CERT_KEY_SIZE < firmware_cert->key_size) ||
+ (MAXIMUM_FIRMWARE_CERT_CONTENT_SIZE < firmware_cert->content_size)) {
+--- a/drivers/media/pci/hailo/common/fw_validation.h
++++ b/drivers/media/pci/hailo/common/fw_validation.h
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: MIT
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #ifndef PCIE_COMMON_FIRMWARE_HEADER_UTILS_H_
+@@ -44,8 +44,7 @@ typedef struct {
+ typedef struct {
+ u32 key_size;
+ u32 content_size;
+- u8 certificates_data[0];
+-} secure_boot_certificate_t;
++} secure_boot_certificate_header_t;
+
+ #ifdef _MSC_VER
+ #pragma warning(pop)
+@@ -60,6 +59,6 @@ int FW_VALIDATION__validate_fw_header(ui
+ firmware_header_t **out_firmware_header, enum hailo_board_type board_type);
+
+ int FW_VALIDATION__validate_cert_header(uintptr_t firmware_base_address,
+- size_t firmware_size, u32 *outer_consumed_firmware_offset, secure_boot_certificate_t **out_firmware_cert);
++ size_t firmware_size, u32 *outer_consumed_firmware_offset, secure_boot_certificate_header_t **out_firmware_cert);
+
+ #endif
+\ No newline at end of file
+--- a/drivers/media/pci/hailo/common/hailo_ioctl_common.h
++++ b/drivers/media/pci/hailo/common/hailo_ioctl_common.h
+@@ -1,13 +1,13 @@
+ // SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) AND MIT
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #ifndef _HAILO_IOCTL_COMMON_H_
+ #define _HAILO_IOCTL_COMMON_H_
+
+ #define HAILO_DRV_VER_MAJOR 4
+-#define HAILO_DRV_VER_MINOR 18
++#define HAILO_DRV_VER_MINOR 19
+ #define HAILO_DRV_VER_REVISION 0
+
+ #define _STRINGIFY_EXPANDED( x ) #x
+@@ -17,10 +17,11 @@
+
+ // This value is not easily changeable.
+ // For example: the channel interrupts ioctls assume we have up to 32 channels
+-#define MAX_VDMA_CHANNELS_PER_ENGINE (32)
+-#define MAX_VDMA_ENGINES (3)
+-#define SIZE_OF_VDMA_DESCRIPTOR (16)
+-#define VDMA_DEST_CHANNELS_START (16)
++#define MAX_VDMA_CHANNELS_PER_ENGINE (32)
++#define VDMA_CHANNELS_PER_ENGINE_PER_DIRECTION (16)
++#define MAX_VDMA_ENGINES (3)
++#define SIZE_OF_VDMA_DESCRIPTOR (16)
++#define VDMA_DEST_CHANNELS_START (16)
+
+ #define HAILO_VDMA_MAX_ONGOING_TRANSFERS (128)
+ #define HAILO_VDMA_MAX_ONGOING_TRANSFERS_MASK (HAILO_VDMA_MAX_ONGOING_TRANSFERS - 1)
+@@ -37,8 +38,8 @@
+ #define FW_ACCESS_APP_CPU_CONTROL_MASK (1 << FW_ACCESS_CONTROL_INTERRUPT_SHIFT)
+ #define FW_ACCESS_DRIVER_SHUTDOWN_SHIFT (2)
+ #define FW_ACCESS_DRIVER_SHUTDOWN_MASK (1 << FW_ACCESS_DRIVER_SHUTDOWN_SHIFT)
+-#define FW_ACCESS_SOC_CONNECT_SHIFT (3)
+-#define FW_ACCESS_SOC_CONNECT_MASK (1 << FW_ACCESS_SOC_CONNECT_SHIFT)
++#define FW_ACCESS_SOC_CONTROL_SHIFT (3)
++#define FW_ACCESS_SOC_CONTROL_MASK (1 << FW_ACCESS_SOC_CONTROL_SHIFT)
+
+ #define INVALID_VDMA_CHANNEL (0xff)
+
+@@ -245,6 +246,12 @@ struct hailo_desc_list_release_params {
+ uintptr_t desc_handle; // in
+ };
+
++struct hailo_write_action_list_params {
++ uint8_t *data; // in
++ size_t size; // in
++ uint64_t dma_address; // out
++};
++
+ /* structure used in ioctl HAILO_DESC_LIST_BIND_VDMA_BUFFER */
+ struct hailo_desc_list_program_params {
+ size_t buffer_handle; // in
+@@ -508,6 +515,7 @@ struct hailo_vdma_launch_transfer_params
+
+ /* structure used in ioctl HAILO_SOC_CONNECT */
+ struct hailo_soc_connect_params {
++ uint16_t port_number; // in
+ uint8_t input_channel_index; // out
+ uint8_t output_channel_index; // out
+ uintptr_t input_desc_handle; // in
+@@ -522,6 +530,7 @@ struct hailo_soc_close_params {
+
+ /* structure used in ioctl HAILO_PCI_EP_ACCEPT */
+ struct hailo_pci_ep_accept_params {
++ uint16_t port_number; // in
+ uint8_t input_channel_index; // out
+ uint8_t output_channel_index; // out
+ uintptr_t input_desc_handle; // in
+@@ -562,6 +571,7 @@ struct tCompatibleHailoIoctlData
+ struct hailo_soc_close_params SocCloseParams;
+ struct hailo_pci_ep_accept_params AcceptParams;
+ struct hailo_pci_ep_close_params PciEpCloseParams;
++ struct hailo_write_action_list_params WriteActionListParams;
+ } Buffer;
+ };
+ #endif // _MSC_VER
+@@ -632,6 +642,7 @@ enum hailo_nnc_ioctl_code {
+ HAILO_DISABLE_NOTIFICATION_CODE,
+ HAILO_READ_LOG_CODE,
+ HAILO_RESET_NN_CORE_CODE,
++ HAILO_WRITE_ACTION_LIST_CODE,
+
+ // Must be last
+ HAILO_NNC_IOCTL_MAX_NR
+@@ -642,6 +653,7 @@ enum hailo_nnc_ioctl_code {
+ #define HAILO_DISABLE_NOTIFICATION _IO_(HAILO_NNC_IOCTL_MAGIC, HAILO_DISABLE_NOTIFICATION_CODE)
+ #define HAILO_READ_LOG _IOWR_(HAILO_NNC_IOCTL_MAGIC, HAILO_READ_LOG_CODE, struct hailo_read_log_params)
+ #define HAILO_RESET_NN_CORE _IO_(HAILO_NNC_IOCTL_MAGIC, HAILO_RESET_NN_CORE_CODE)
++#define HAILO_WRITE_ACTION_LIST _IOW_(HAILO_NNC_IOCTL_MAGIC, HAILO_WRITE_ACTION_LIST_CODE, struct hailo_write_action_list_params)
+
+ enum hailo_soc_ioctl_code {
+ HAILO_SOC_IOCTL_CONNECT_CODE,
+--- a/drivers/media/pci/hailo/common/hailo_resource.c
++++ b/drivers/media/pci/hailo/common/hailo_resource.c
+@@ -1,24 +1,31 @@
+ // SPDX-License-Identifier: MIT
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #include "hailo_resource.h"
+
++#include "utils.h"
++
+ #include <linux/io.h>
+ #include <linux/errno.h>
+ #include <linux/types.h>
+ #include <linux/kernel.h>
+
++#define ALIGN_TO_32_BIT(addr) ((addr) & (~((uintptr_t)0x3)))
+
+ u8 hailo_resource_read8(struct hailo_resource *resource, size_t offset)
+ {
+- return ioread8((u8*)resource->address + offset);
++ u32 val = ioread32((u8*)ALIGN_TO_32_BIT(resource->address + offset));
++ u64 offset_in_bits = BITS_IN_BYTE * ((resource->address + offset) - ALIGN_TO_32_BIT(resource->address + offset));
++ return (u8)READ_BITS_AT_OFFSET(BYTE_SIZE * BITS_IN_BYTE, offset_in_bits, val);
+ }
+
+ u16 hailo_resource_read16(struct hailo_resource *resource, size_t offset)
+ {
+- return ioread16((u8*)resource->address + offset);
++ u32 val = ioread32((u8*)ALIGN_TO_32_BIT(resource->address + offset));
++ u64 offset_in_bits = BITS_IN_BYTE * ((resource->address + offset) - ALIGN_TO_32_BIT(resource->address + offset));
++ return (u16)READ_BITS_AT_OFFSET(WORD_SIZE * BITS_IN_BYTE, offset_in_bits, val);
+ }
+
+ u32 hailo_resource_read32(struct hailo_resource *resource, size_t offset)
+@@ -28,12 +35,18 @@ u32 hailo_resource_read32(struct hailo_r
+
+ void hailo_resource_write8(struct hailo_resource *resource, size_t offset, u8 value)
+ {
+- iowrite8(value, (u8*)resource->address + offset);
++ u32 initial_val = ioread32((u8*)ALIGN_TO_32_BIT(resource->address + offset));
++ u64 offset_in_bits = BITS_IN_BYTE * ((resource->address + offset) - ALIGN_TO_32_BIT(resource->address + offset));
++ iowrite32(WRITE_BITS_AT_OFFSET(BYTE_SIZE * BITS_IN_BYTE, offset_in_bits, initial_val, value),
++ (u8*)ALIGN_TO_32_BIT(resource->address + offset));
+ }
+
+ void hailo_resource_write16(struct hailo_resource *resource, size_t offset, u16 value)
+ {
+- iowrite16(value, (u8*)resource->address + offset);
++ u32 initial_val = ioread32((u8*)ALIGN_TO_32_BIT(resource->address + offset));
++ u64 offset_in_bits = BITS_IN_BYTE * ((resource->address + offset) - ALIGN_TO_32_BIT(resource->address + offset));
++ iowrite32(WRITE_BITS_AT_OFFSET(WORD_SIZE * BITS_IN_BYTE, offset_in_bits, initial_val, value),
++ (u8*)ALIGN_TO_32_BIT(resource->address + offset));
+ }
+
+ void hailo_resource_write32(struct hailo_resource *resource, size_t offset, u32 value)
+--- a/drivers/media/pci/hailo/common/hailo_resource.h
++++ b/drivers/media/pci/hailo/common/hailo_resource.h
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: MIT
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #ifndef _HAILO_COMMON_HAILO_RESOURCE_H_
+--- a/drivers/media/pci/hailo/common/pcie_common.c
++++ b/drivers/media/pci/hailo/common/pcie_common.c
+@@ -1,10 +1,11 @@
+ // SPDX-License-Identifier: MIT
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #include "pcie_common.h"
+ #include "fw_operation.h"
++#include "soc_structs.h"
+
+ #include <linux/errno.h>
+ #include <linux/bug.h>
+@@ -35,10 +36,6 @@
+ #define FIRMWARE_LOAD_WAIT_MAX_RETRIES (100)
+ #define FIRMWARE_LOAD_SLEEP_MS (50)
+
+-#define PCIE_APP_CPU_DEBUG_OFFSET (8*1024)
+-#define PCIE_CORE_CPU_DEBUG_OFFSET (PCIE_APP_CPU_DEBUG_OFFSET + DEBUG_BUFFER_TOTAL_SIZE)
+-
+-#define PCIE_D2H_NOTIFICATION_SRAM_OFFSET (0x640 + 0x640)
+ #define PCIE_REQUEST_SIZE_OFFSET (0x640)
+
+ #define PCIE_CONFIG_VENDOR_OFFSET (0x0098)
+@@ -59,7 +56,6 @@ struct hailo_fw_addresses {
+ u32 app_fw_code_ram_base;
+ u32 boot_key_cert;
+ u32 boot_cont_cert;
+- u32 boot_fw_trigger;
+ u32 core_code_ram_base;
+ u32 core_fw_header;
+ u32 atr0_trsl_addr1;
+@@ -69,13 +65,11 @@ struct hailo_fw_addresses {
+
+ struct loading_stage {
+ const struct hailo_file_batch *batch;
++ u32 trigger_address;
+ };
+
+ struct hailo_board_compatibility {
+ struct hailo_fw_addresses fw_addresses;
+- const char *fw_filename;
+- const struct hailo_config_constants board_cfg;
+- const struct hailo_config_constants fw_cfg;
+ const struct loading_stage stages[MAX_LOADING_STAGES];
+ };
+
+@@ -85,28 +79,32 @@ static const struct hailo_file_batch hai
+ .address = 0xA0000,
+ .max_size = 0x8004,
+ .is_mandatory = true,
+- .has_header = false
++ .has_header = false,
++ .has_core = false
+ },
+ {
+ .filename = "hailo/hailo10h/u-boot.dtb.signed",
+ .address = 0xA8004,
+ .max_size = 0x20000,
+ .is_mandatory = true,
+- .has_header = false
++ .has_header = false,
++ .has_core = false
+ },
+ {
+ .filename = "hailo/hailo10h/scu_fw.bin",
+ .address = 0x20000,
+ .max_size = 0x40000,
+ .is_mandatory = true,
+- .has_header = true
++ .has_header = true,
++ .has_core = false
+ },
+ {
+ .filename = NULL,
+ .address = 0x00,
+ .max_size = 0x00,
+ .is_mandatory = false,
+- .has_header = false
++ .has_header = false,
++ .has_core = false
+ }
+ };
+
+@@ -116,36 +114,140 @@ static const struct hailo_file_batch hai
+ .address = 0x85000000,
+ .max_size = 0x1000000,
+ .is_mandatory = true,
+- .has_header = false
++ .has_header = false,
++ .has_core = false
+ },
+ {
+ .filename = "hailo/hailo10h/u-boot-tfa.itb",
+ .address = 0x86000000,
+ .max_size = 0x1000000,
+ .is_mandatory = true,
+- .has_header = false
++ .has_header = false,
++ .has_core = false
+ },
+ {
+ .filename = "hailo/hailo10h/fitImage",
+ .address = 0x87000000,
+ .max_size = 0x1000000,
+ .is_mandatory = true,
+- .has_header = false
++ .has_header = false,
++ .has_core = false
+ },
+ {
+ .filename = "hailo/hailo10h/core-image-minimal-hailo10-m2.ext4.gz",
+ .address = 0x88000000,
+ .max_size = 0x20000000, // Max size 512MB
+ .is_mandatory = true,
+- .has_header = false
++ .has_header = false,
++ .has_core = false
+ },
+ };
+
++// If loading linux from EMMC - only need few files from second batch (u-boot-spl.bin and u-boot-tfa.itb)
++static const struct hailo_file_batch hailo10h_files_stg2_linux_in_emmc[] = {
++ {
++ .filename = "hailo/hailo10h/u-boot-spl.bin",
++ .address = 0x85000000,
++ .max_size = 0x1000000,
++ .is_mandatory = true,
++ .has_header = false,
++ .has_core = false
++ },
++ {
++ .filename = "hailo/hailo10h/u-boot-tfa.itb",
++ .address = 0x86000000,
++ .max_size = 0x1000000,
++ .is_mandatory = true,
++ .has_header = false,
++ .has_core = false
++ },
++ {
++ .filename = NULL,
++ .address = 0x00,
++ .max_size = 0x00,
++ .is_mandatory = false,
++ .has_header = false,
++ .has_core = false
++ },
++};
++
++static const struct hailo_file_batch hailo8_files_stg1[] = {
++ {
++ .filename = "hailo/hailo8_fw.4.19.0.bin",
++ .address = 0x20000,
++ .max_size = 0x50000,
++ .is_mandatory = true,
++ .has_header = true,
++ .has_core = true
++ },
++ {
++ .filename = "hailo/hailo8_board_cfg.bin",
++ .address = 0x60001000,
++ .max_size = PCIE_HAILO8_BOARD_CFG_MAX_SIZE,
++ .is_mandatory = false,
++ .has_header = false,
++ .has_core = false
++ },
++ {
++ .filename = "hailo/hailo8_fw_cfg.bin",
++ .address = 0x60001500,
++ .max_size = PCIE_HAILO8_FW_CFG_MAX_SIZE,
++ .is_mandatory = false,
++ .has_header = false,
++ .has_core = false
++ },
++ {
++ .filename = NULL,
++ .address = 0x00,
++ .max_size = 0x00,
++ .is_mandatory = false,
++ .has_header = false,
++ .has_core = false
++ }
++};
++
++static const struct hailo_file_batch hailo10h_legacy_files_stg1[] = {
++ {
++ .filename = "hailo/hailo15_fw.bin",
++ .address = 0x20000,
++ .max_size = 0x100000,
++ .is_mandatory = true,
++ .has_header = true,
++ .has_core = true
++ },
++ {
++ .filename = NULL,
++ .address = 0x00,
++ .max_size = 0x00,
++ .is_mandatory = false,
++ .has_header = false,
++ .has_core = false
++ }
++};
++
++static const struct hailo_file_batch pluto_files_stg1[] = {
++ {
++ .filename = "hailo/pluto_fw.bin",
++ .address = 0x20000,
++ .max_size = 0x100000,
++ .is_mandatory = true,
++ .has_header = true,
++ .has_core = true
++ },
++ {
++ .filename = NULL,
++ .address = 0x00,
++ .max_size = 0x00,
++ .is_mandatory = false,
++ .has_header = false,
++ .has_core = false
++ }
++};
++
+ static const struct hailo_board_compatibility compat[HAILO_BOARD_TYPE_COUNT] = {
+ [HAILO_BOARD_TYPE_HAILO8] = {
+ .fw_addresses = {
+ .boot_fw_header = 0xE0030,
+- .boot_fw_trigger = 0xE0980,
+ .boot_key_cert = 0xE0048,
+ .boot_cont_cert = 0xE0390,
+ .app_fw_code_ram_base = 0x60000,
+@@ -155,22 +257,16 @@ static const struct hailo_board_compatib
+ .raise_ready_offset = 0x1684,
+ .boot_status = 0xe0000,
+ },
+- .fw_filename = "hailo/hailo8_fw.bin",
+- .board_cfg = {
+- .filename = "hailo/hailo8_board_cfg.bin",
+- .address = 0x60001000,
+- .max_size = PCIE_HAILO8_BOARD_CFG_MAX_SIZE,
+- },
+- .fw_cfg = {
+- .filename = "hailo/hailo8_fw_cfg.bin",
+- .address = 0x60001500,
+- .max_size = PCIE_HAILO8_FW_CFG_MAX_SIZE,
++ .stages = {
++ {
++ .batch = hailo8_files_stg1,
++ .trigger_address = 0xE0980
++ },
+ },
+ },
+ [HAILO_BOARD_TYPE_HAILO10H_LEGACY] = {
+ .fw_addresses = {
+ .boot_fw_header = 0x88000,
+- .boot_fw_trigger = 0x88c98,
+ .boot_key_cert = 0x88018,
+ .boot_cont_cert = 0x886a8,
+ .app_fw_code_ram_base = 0x20000,
+@@ -180,22 +276,16 @@ static const struct hailo_board_compatib
+ .raise_ready_offset = 0x1754,
+ .boot_status = 0x80000,
+ },
+- .fw_filename = "hailo/hailo15_fw.bin",
+- .board_cfg = {
+- .filename = NULL,
+- .address = 0,
+- .max_size = 0,
+- },
+- .fw_cfg = {
+- .filename = NULL,
+- .address = 0,
+- .max_size = 0,
++ .stages = {
++ {
++ .batch = hailo10h_legacy_files_stg1,
++ .trigger_address = 0x88c98
++ },
+ },
+ },
+ [HAILO_BOARD_TYPE_HAILO10H] = {
+ .fw_addresses = {
+ .boot_fw_header = 0x88000,
+- .boot_fw_trigger = 0x88c98,
+ .boot_key_cert = 0x88018,
+ .boot_cont_cert = 0x886a8,
+ .app_fw_code_ram_base = 0x20000,
+@@ -205,23 +295,18 @@ static const struct hailo_board_compatib
+ .raise_ready_offset = 0x1754,
+ .boot_status = 0x80000,
+ },
+- .fw_filename = NULL,
+- .board_cfg = {
+- .filename = NULL,
+- .address = 0,
+- .max_size = 0,
+- },
+- .fw_cfg = {
+- .filename = NULL,
+- .address = 0,
+- .max_size = 0,
+- },
+ .stages = {
+ {
+ .batch = hailo10h_files_stg1,
++ .trigger_address = 0x88c98
+ },
+ {
+ .batch = hailo10h_files_stg2,
++ .trigger_address = 0x84000000
++ },
++ {
++ .batch = hailo10h_files_stg2_linux_in_emmc,
++ .trigger_address = 0x84000000
+ },
+ },
+ },
+@@ -230,7 +315,6 @@ static const struct hailo_board_compatib
+ [HAILO_BOARD_TYPE_PLUTO] = {
+ .fw_addresses = {
+ .boot_fw_header = 0x88000,
+- .boot_fw_trigger = 0x88c98,
+ .boot_key_cert = 0x88018,
+ .boot_cont_cert = 0x886a8,
+ .app_fw_code_ram_base = 0x20000,
+@@ -241,16 +325,11 @@ static const struct hailo_board_compatib
+ .raise_ready_offset = 0x174c,
+ .boot_status = 0x80000,
+ },
+- .fw_filename = "hailo/pluto_fw.bin",
+- .board_cfg = {
+- .filename = NULL,
+- .address = 0,
+- .max_size = 0,
+- },
+- .fw_cfg = {
+- .filename = NULL,
+- .address = 0,
+- .max_size = 0,
++ .stages = {
++ {
++ .batch = pluto_files_stg1,
++ .trigger_address = 0x88c98
++ },
+ },
+ }
+ };
+@@ -340,21 +419,6 @@ void hailo_pcie_write_firmware_driver_sh
+ hailo_resource_write32(&resources->fw_access, fw_addresses->raise_ready_offset, fw_access_value);
+ }
+
+-int hailo_pcie_read_firmware_notification(struct hailo_pcie_resources *resources,
+- struct hailo_d2h_notification *notification)
+-{
+- struct hailo_resource notification_resource;
+-
+- if (PCIE_D2H_NOTIFICATION_SRAM_OFFSET > resources->fw_access.size) {
+- return -EINVAL;
+- }
+-
+- notification_resource.address = resources->fw_access.address + PCIE_D2H_NOTIFICATION_SRAM_OFFSET,
+- notification_resource.size = sizeof(struct hailo_d2h_notification);
+-
+- return hailo_read_firmware_notification(¬ification_resource, notification);
+-}
+-
+ int hailo_pcie_configure_atr_table(struct hailo_resource *bridge_config, u64 trsl_addr, u32 atr_index)
+ {
+ size_t offset = 0;
+@@ -388,7 +452,7 @@ static void write_memory_chunk(struct ha
+ u32 ATR_INDEX = 0;
+ BUG_ON(dest_offset + len > (u32)resources->fw_access.size);
+
+- (void)hailo_pcie_configure_atr_table(&resources->config, (u64)dest, ATR_INDEX);
++ (void)hailo_pcie_configure_atr_table(&resources->config, dest, ATR_INDEX);
+ (void)hailo_resource_write_buffer(&resources->fw_access, dest_offset, len, src);
+ }
+
+@@ -398,13 +462,13 @@ static void read_memory_chunk(
+ u32 ATR_INDEX = 0;
+ BUG_ON(src_offset + len > (u32)resources->fw_access.size);
+
+- (void)hailo_pcie_configure_atr_table(&resources->config, (u64)src, ATR_INDEX);
++ (void)hailo_pcie_configure_atr_table(&resources->config, src, ATR_INDEX);
+ (void)hailo_resource_read_buffer(&resources->fw_access, src_offset, len, dest);
+ }
+
+ // Note: this function modify the device ATR table (that is also used by the firmware for control and vdma).
+ // Use with caution, and restore the original atr if needed.
+-void write_memory(struct hailo_pcie_resources *resources, hailo_ptr_t dest, const void *src, u32 len)
++static void write_memory(struct hailo_pcie_resources *resources, hailo_ptr_t dest, const void *src, u32 len)
+ {
+ struct hailo_atr_config previous_atr = {0};
+ hailo_ptr_t base_address = (dest & ~ATR_TABLE_SIZE_MASK);
+@@ -417,8 +481,8 @@ void write_memory(struct hailo_pcie_reso
+
+ if (base_address != dest) {
+ // Data is not aligned, write the first chunk
+- chunk_len = min(base_address + ATR_TABLE_SIZE - dest, len);
+- write_memory_chunk(resources, base_address, dest - base_address, src, chunk_len);
++ chunk_len = min((u32)(base_address + ATR_TABLE_SIZE - dest), len);
++ write_memory_chunk(resources, base_address, (u32)(dest - base_address), src, chunk_len);
+ offset += chunk_len;
+ }
+
+@@ -447,8 +511,8 @@ static void read_memory(struct hailo_pci
+
+ if (base_address != src) {
+ // Data is not aligned, write the first chunk
+- chunk_len = min(base_address + ATR_TABLE_SIZE - src, len);
+- read_memory_chunk(resources, base_address, src - base_address, dest, chunk_len);
++ chunk_len = min((u32)(base_address + ATR_TABLE_SIZE - src), len);
++ read_memory_chunk(resources, base_address, (u32)(src - base_address), dest, chunk_len);
+ offset += chunk_len;
+ }
+
+@@ -463,12 +527,12 @@ static void read_memory(struct hailo_pci
+ }
+
+ static void hailo_write_app_firmware(struct hailo_pcie_resources *resources, firmware_header_t *fw_header,
+- secure_boot_certificate_t *fw_cert)
++ secure_boot_certificate_header_t *fw_cert)
+ {
+ const struct hailo_fw_addresses *fw_addresses = &(compat[resources->board_type].fw_addresses);
+- void *fw_code = (void*)((u8*)fw_header + sizeof(firmware_header_t));
+- void *key_data = &fw_cert->certificates_data[0];
+- void *content_data = &fw_cert->certificates_data[fw_cert->key_size];
++ u8 *fw_code = ((u8*)fw_header + sizeof(firmware_header_t));
++ u8 *key_data = ((u8*)fw_cert + sizeof(secure_boot_certificate_header_t));
++ u8 *content_data = key_data + fw_cert->key_size;
+
+ write_memory(resources, fw_addresses->boot_fw_header, fw_header, sizeof(firmware_header_t));
+
+@@ -487,13 +551,11 @@ static void hailo_write_core_firmware(st
+ write_memory(resources, fw_addresses->core_fw_header, fw_header, sizeof(firmware_header_t));
+ }
+
+-void hailo_trigger_firmware_boot(struct hailo_pcie_resources *resources)
++void hailo_trigger_firmware_boot(struct hailo_pcie_resources *resources, u32 address)
+ {
+- const struct hailo_fw_addresses *fw_addresses = &(compat[resources->board_type].fw_addresses);
+ u32 pcie_finished = 1;
+
+- write_memory(resources, fw_addresses->boot_fw_trigger,
+- (void*)&pcie_finished, sizeof(pcie_finished));
++ write_memory(resources, address, (void*)&pcie_finished, sizeof(pcie_finished));
+ }
+
+ u32 hailo_get_boot_status(struct hailo_pcie_resources *resources)
+@@ -501,8 +563,7 @@ u32 hailo_get_boot_status(struct hailo_p
+ u32 boot_status = 0;
+ const struct hailo_fw_addresses *fw_addresses = &(compat[resources->board_type].fw_addresses);
+
+- read_memory(resources, fw_addresses->boot_status,
+- &boot_status, sizeof(boot_status));
++ read_memory(resources, fw_addresses->boot_status, &boot_status, sizeof(boot_status));
+
+ return boot_status;
+ }
+@@ -517,11 +578,11 @@ u32 hailo_get_boot_status(struct hailo_p
+ */
+ static int FW_VALIDATION__validate_fw_headers(uintptr_t firmware_base_address, size_t firmware_size,
+ firmware_header_t **out_app_firmware_header, firmware_header_t **out_core_firmware_header,
+- secure_boot_certificate_t **out_firmware_cert, enum hailo_board_type board_type)
++ secure_boot_certificate_header_t **out_firmware_cert, enum hailo_board_type board_type)
+ {
+ firmware_header_t *app_firmware_header = NULL;
+ firmware_header_t *core_firmware_header = NULL;
+- secure_boot_certificate_t *firmware_cert = NULL;
++ secure_boot_certificate_header_t *firmware_cert = NULL;
+ int err = -EINVAL;
+ u32 consumed_firmware_offset = 0;
+
+@@ -571,25 +632,25 @@ exit:
+ return err;
+ }
+
+-static int write_single_file(struct hailo_pcie_resources *resources, const struct hailo_file_batch *files_batch, struct device *dev)
++static int write_single_file(struct hailo_pcie_resources *resources, const struct hailo_file_batch *file_info, struct device *dev)
+ {
+ const struct firmware *firmware = NULL;
+ firmware_header_t *app_firmware_header = NULL;
+- secure_boot_certificate_t *firmware_cert = NULL;
++ secure_boot_certificate_header_t *firmware_cert = NULL;
+ firmware_header_t *core_firmware_header = NULL;
+ int err = 0;
+
+- err = request_firmware_direct(&firmware, files_batch->filename, dev);
++ err = request_firmware_direct(&firmware, file_info->filename, dev);
+ if (err < 0) {
+ return err;
+ }
+
+- if (firmware->size > files_batch->max_size) {
++ if (firmware->size > file_info->max_size) {
+ release_firmware(firmware);
+ return -EFBIG;
+ }
+
+- if (files_batch->has_header) {
++ if (file_info->has_header) {
+ err = FW_VALIDATION__validate_fw_headers((uintptr_t)firmware->data, firmware->size,
+ &app_firmware_header, &core_firmware_header, &firmware_cert, resources->board_type);
+ if (err < 0) {
+@@ -598,8 +659,11 @@ static int write_single_file(struct hail
+ }
+
+ hailo_write_app_firmware(resources, app_firmware_header, firmware_cert);
++ if (file_info->has_core) {
++ hailo_write_core_firmware(resources, core_firmware_header);
++ }
+ } else {
+- write_memory(resources, files_batch->address, (void*)firmware->data, firmware->size);
++ write_memory(resources, file_info->address, (void*)firmware->data, firmware->size);
+ }
+
+ release_firmware(firmware);
+@@ -632,31 +696,13 @@ int hailo_pcie_write_firmware_batch(stru
+ dev_notice(dev, "File %s written successfully\n", files_batch[file_index].filename);
+ }
+
+- return 0;
+-}
+-
+-int hailo_pcie_write_firmware(struct hailo_pcie_resources *resources, const void *fw_data, size_t fw_size)
+-{
+- firmware_header_t *app_firmware_header = NULL;
+- secure_boot_certificate_t *firmware_cert = NULL;
+- firmware_header_t *core_firmware_header = NULL;
+-
+- int err = FW_VALIDATION__validate_fw_headers((uintptr_t)fw_data, fw_size,
+- &app_firmware_header, &core_firmware_header, &firmware_cert, resources->board_type);
+- if (err < 0) {
+- return err;
+- }
+-
+- hailo_write_app_firmware(resources, app_firmware_header, firmware_cert);
+- hailo_write_core_firmware(resources, core_firmware_header);
+-
+- hailo_trigger_firmware_boot(resources);
++ hailo_trigger_firmware_boot(resources, compat[resources->board_type].stages[stage].trigger_address);
+
+ return 0;
+ }
+
+ // TODO: HRT-14147 - remove this function
+-bool hailo_pcie_is_device_ready_for_boot(struct hailo_pcie_resources *resources)
++static bool hailo_pcie_is_device_ready_for_boot(struct hailo_pcie_resources *resources)
+ {
+ return hailo_get_boot_status(resources) == BOOT_STATUS_UNINITIALIZED;
+ }
+@@ -691,32 +737,6 @@ bool hailo_pcie_wait_for_firmware(struct
+ return false;
+ }
+
+-int hailo_pcie_write_config_common(struct hailo_pcie_resources *resources, const void* config_data,
+- const size_t config_size, const struct hailo_config_constants *config_consts)
+-{
+- if (config_size > config_consts->max_size) {
+- return -EINVAL;
+- }
+-
+- write_memory(resources, config_consts->address, config_data, (u32)config_size);
+- return 0;
+-}
+-
+-const struct hailo_config_constants* hailo_pcie_get_board_config_constants(const enum hailo_board_type board_type) {
+- BUG_ON(board_type >= HAILO_BOARD_TYPE_COUNT || board_type < 0);
+- return &compat[board_type].board_cfg;
+-}
+-
+-const struct hailo_config_constants* hailo_pcie_get_user_config_constants(const enum hailo_board_type board_type) {
+- BUG_ON(board_type >= HAILO_BOARD_TYPE_COUNT || board_type < 0);
+- return &compat[board_type].fw_cfg;
+-}
+-
+-const char* hailo_pcie_get_fw_filename(const enum hailo_board_type board_type) {
+- BUG_ON(board_type >= HAILO_BOARD_TYPE_COUNT || board_type < 0);
+- return compat[board_type].fw_filename;
+-}
+-
+ void hailo_pcie_update_channel_interrupts_mask(struct hailo_pcie_resources* resources, u32 channels_bitmap)
+ {
+ size_t i = 0;
+@@ -745,7 +765,7 @@ void hailo_pcie_enable_interrupts(struct
+ hailo_resource_write32(&resources->config, BCS_SOURCE_INTERRUPT_PER_CHANNEL, 0xFFFFFFFF);
+
+ mask |= (BCS_ISTATUS_HOST_FW_IRQ_CONTROL_MASK | BCS_ISTATUS_HOST_FW_IRQ_NOTIFICATION |
+- BCS_ISTATUS_HOST_DRIVER_DOWN | BCS_ISTATUS_SOC_CONNECT_ACCEPTED);
++ BCS_ISTATUS_HOST_DRIVER_DOWN | BCS_ISTATUS_SOC_CONNECT_ACCEPTED | BCS_ISTATUS_SOC_CLOSED_IRQ);
+ hailo_resource_write32(&resources->config, BSC_IMASK_HOST, mask);
+ }
+
+@@ -754,45 +774,15 @@ void hailo_pcie_disable_interrupts(struc
+ hailo_resource_write32(&resources->config, BSC_IMASK_HOST, 0);
+ }
+
+-long hailo_pcie_read_firmware_log(struct hailo_pcie_resources *resources, struct hailo_read_log_params *params)
+-{
+- long err = 0;
+- struct hailo_resource log_resource = {resources->fw_access.address, DEBUG_BUFFER_TOTAL_SIZE};
+-
+- if (HAILO_CPU_ID_CPU0 == params->cpu_id) {
+- log_resource.address += PCIE_APP_CPU_DEBUG_OFFSET;
+- } else if (HAILO_CPU_ID_CPU1 == params->cpu_id) {
+- log_resource.address += PCIE_CORE_CPU_DEBUG_OFFSET;
+- } else {
+- return -EINVAL;
+- }
+-
+- if (0 == params->buffer_size) {
+- params->read_bytes = 0;
+- return 0;
+- }
+-
+- err = hailo_read_firmware_log(&log_resource, params);
+- if (0 != err) {
+- return err;
+- }
+-
+- return 0;
+-}
+-
+ static int direct_memory_transfer(struct hailo_pcie_resources *resources,
+ struct hailo_memory_transfer_params *params)
+ {
+- if (params->address > U32_MAX) {
+- return -EFAULT;
+- }
+-
+ switch (params->transfer_direction) {
+ case TRANSFER_READ:
+- read_memory(resources, (u32)params->address, params->buffer, (u32)params->count);
++ read_memory(resources, params->address, params->buffer, (u32)params->count);
+ break;
+ case TRANSFER_WRITE:
+- write_memory(resources, (u32)params->address, params->buffer, (u32)params->count);
++ write_memory(resources, params->address, params->buffer, (u32)params->count);
+ break;
+ default:
+ return -EINVAL;
+@@ -845,16 +835,18 @@ int hailo_set_device_type(struct hailo_p
+ return 0;
+ }
+
+-// On PCIe, just return the address
+-static u64 encode_dma_address(dma_addr_t dma_address, u8 channel_id)
++// On PCIe, just return the start address
++u64 hailo_pcie_encode_desc_dma_address_range(dma_addr_t dma_address_start, dma_addr_t dma_address_end, u32 step, u8 channel_id)
+ {
+ (void)channel_id;
+- return (u64)dma_address;
++ (void)dma_address_end;
++ (void)step;
++ return (u64)dma_address_start;
+ }
+
+ struct hailo_vdma_hw hailo_pcie_vdma_hw = {
+ .hw_ops = {
+- .encode_desc_dma_address = encode_dma_address
++ .encode_desc_dma_address_range = hailo_pcie_encode_desc_dma_address_range,
+ },
+ .ddr_data_id = HAILO_PCIE_HOST_DMA_DATA_ID,
+ .device_interrupts_bitmask = HAILO_PCIE_DMA_DEVICE_INTERRUPTS_BITMASK,
+@@ -862,11 +854,19 @@ struct hailo_vdma_hw hailo_pcie_vdma_hw
+ .src_channels_bitmask = HAILO_PCIE_DMA_SRC_CHANNELS_BITMASK,
+ };
+
+-void hailo_soc_write_soc_connect(struct hailo_pcie_resources *resources)
++void hailo_pcie_soc_write_request(struct hailo_pcie_resources *resources,
++ const struct hailo_pcie_soc_request *request)
+ {
+ const struct hailo_fw_addresses *fw_addresses = &(compat[resources->board_type].fw_addresses);
+- const u32 soc_connect_value = FW_ACCESS_SOC_CONNECT_MASK;
++ BUILD_BUG_ON_MSG((sizeof(*request) % sizeof(u32)) != 0, "Request must be a multiple of 4 bytes");
+
+- // Write shutdown flag to FW
+- hailo_resource_write32(&resources->fw_access, fw_addresses->raise_ready_offset, soc_connect_value);
+-}
+\ No newline at end of file
++ hailo_resource_write_buffer(&resources->fw_access, 0, sizeof(*request), (void*)request);
++ hailo_resource_write32(&resources->fw_access, fw_addresses->raise_ready_offset, FW_ACCESS_SOC_CONTROL_MASK);
++}
++
++void hailo_pcie_soc_read_response(struct hailo_pcie_resources *resources,
++ struct hailo_pcie_soc_response *response)
++{
++ BUILD_BUG_ON_MSG((sizeof(*response) % sizeof(u32)) != 0, "Request must be a multiple of 4 bytes");
++ hailo_resource_read_buffer(&resources->fw_access, 0, sizeof(*response), response);
++}
+--- a/drivers/media/pci/hailo/common/pcie_common.h
++++ b/drivers/media/pci/hailo/common/pcie_common.h
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: MIT
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #ifndef _HAILO_COMMON_PCIE_COMMON_H_
+@@ -12,6 +12,7 @@
+ #include "fw_operation.h"
+ #include "utils.h"
+ #include "vdma_common.h"
++#include "soc_structs.h"
+
+ #include <linux/types.h>
+ #include <linux/firmware.h>
+@@ -21,6 +22,7 @@
+ #define BCS_ISTATUS_HOST_FW_IRQ_NOTIFICATION (0x02000000)
+ #define BCS_ISTATUS_HOST_DRIVER_DOWN (0x08000000)
+ #define BCS_ISTATUS_SOC_CONNECT_ACCEPTED (0x10000000)
++#define BCS_ISTATUS_SOC_CLOSED_IRQ (0x20000000)
+ #define BCS_ISTATUS_HOST_VDMA_SRC_IRQ_MASK (0x000000FF)
+ #define BCS_ISTATUS_HOST_VDMA_DEST_IRQ_MASK (0x0000FF00)
+
+@@ -42,7 +44,7 @@
+ #define PCI_DEVICE_ID_HAILO_HAILO15 0x45C4
+ #define PCI_DEVICE_ID_HAILO_PLUTO 0x43a2
+
+-typedef u32 hailo_ptr_t;
++typedef u64 hailo_ptr_t;
+
+ struct hailo_pcie_resources {
+ struct hailo_resource config; // BAR0
+@@ -63,7 +65,8 @@ struct hailo_atr_config {
+ enum loading_stages {
+ FIRST_STAGE = 0,
+ SECOND_STAGE = 1,
+- MAX_LOADING_STAGES = 2
++ SECOND_STAGE_LINUX_IN_EMMC = 2,
++ MAX_LOADING_STAGES = 3
+ };
+
+ enum hailo_pcie_interrupt_masks {
+@@ -71,6 +74,7 @@ enum hailo_pcie_interrupt_masks {
+ FW_NOTIFICATION = BCS_ISTATUS_HOST_FW_IRQ_NOTIFICATION,
+ DRIVER_DOWN = BCS_ISTATUS_HOST_DRIVER_DOWN,
+ SOC_CONNECT_ACCEPTED = BCS_ISTATUS_SOC_CONNECT_ACCEPTED,
++ SOC_CLOSED_IRQ = BCS_ISTATUS_SOC_CLOSED_IRQ,
+ VDMA_SRC_IRQ_MASK = BCS_ISTATUS_HOST_VDMA_SRC_IRQ_MASK,
+ VDMA_DEST_IRQ_MASK = BCS_ISTATUS_HOST_VDMA_DEST_IRQ_MASK
+ };
+@@ -80,18 +84,13 @@ struct hailo_pcie_interrupt_source {
+ u32 vdma_channels_bitmap;
+ };
+
+-struct hailo_config_constants {
+- const char *filename;
+- u32 address;
+- size_t max_size;
+-};
+-
+ struct hailo_file_batch {
+ const char *filename;
+ u32 address;
+ size_t max_size;
+ bool is_mandatory;
+ bool has_header;
++ bool has_core;
+ };
+
+ // TODO: HRT-6144 - Align Windows/Linux to QNX
+@@ -130,27 +129,15 @@ void hailo_pcie_disable_interrupts(struc
+ int hailo_pcie_write_firmware_control(struct hailo_pcie_resources *resources, const struct hailo_fw_control *command);
+ int hailo_pcie_read_firmware_control(struct hailo_pcie_resources *resources, struct hailo_fw_control *command);
+
+-int hailo_pcie_write_firmware(struct hailo_pcie_resources *resources, const void *fw_data, size_t fw_size);
+ int hailo_pcie_write_firmware_batch(struct device *dev, struct hailo_pcie_resources *resources, u32 stage);
+ bool hailo_pcie_is_firmware_loaded(struct hailo_pcie_resources *resources);
+ bool hailo_pcie_wait_for_firmware(struct hailo_pcie_resources *resources);
+
+-int hailo_pcie_read_firmware_notification(struct hailo_pcie_resources *resources,
+- struct hailo_d2h_notification *notification);
+-
+-int hailo_pcie_write_config_common(struct hailo_pcie_resources *resources, const void* config_data,
+- const size_t config_size, const struct hailo_config_constants *config_consts);
+-const struct hailo_config_constants* hailo_pcie_get_board_config_constants(const enum hailo_board_type board_type);
+-const struct hailo_config_constants* hailo_pcie_get_user_config_constants(const enum hailo_board_type board_type);
+-const char* hailo_pcie_get_fw_filename(const enum hailo_board_type board_type);
+-
+-long hailo_pcie_read_firmware_log(struct hailo_pcie_resources *resources, struct hailo_read_log_params *params);
+ int hailo_pcie_memory_transfer(struct hailo_pcie_resources *resources, struct hailo_memory_transfer_params *params);
+
+ bool hailo_pcie_is_device_connected(struct hailo_pcie_resources *resources);
+ void hailo_pcie_write_firmware_driver_shutdown(struct hailo_pcie_resources *resources);
+-void write_memory(struct hailo_pcie_resources *resources, hailo_ptr_t dest, const void *src, u32 len);
+-void hailo_trigger_firmware_boot(struct hailo_pcie_resources *resources);
++void hailo_trigger_firmware_boot(struct hailo_pcie_resources *resources, u32 address);
+
+ int hailo_set_device_type(struct hailo_pcie_resources *resources);
+
+@@ -159,7 +146,12 @@ u32 hailo_get_boot_status(struct hailo_p
+ int hailo_pcie_configure_atr_table(struct hailo_resource *bridge_config, u64 trsl_addr, u32 atr_index);
+ void hailo_pcie_read_atr_table(struct hailo_resource *bridge_config, struct hailo_atr_config *atr, u32 atr_index);
+
+-void hailo_soc_write_soc_connect(struct hailo_pcie_resources *resources);
++u64 hailo_pcie_encode_desc_dma_address_range(dma_addr_t dma_address_start, dma_addr_t dma_address_end, u32 step, u8 channel_id);
++
++void hailo_pcie_soc_write_request(struct hailo_pcie_resources *resources,
++ const struct hailo_pcie_soc_request *request);
++void hailo_pcie_soc_read_response(struct hailo_pcie_resources *resources,
++ struct hailo_pcie_soc_response *response);
+
+ #ifdef __cplusplus
+ }
+--- /dev/null
++++ b/drivers/media/pci/hailo/common/soc_structs.h
+@@ -0,0 +1,79 @@
++// SPDX-License-Identifier: MIT
++/**
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
++ **/
++/**
++ * Contains definitions for pcie soc to pcie ep communication
++ */
++
++#ifndef __HAILO_COMMON_SOC_STRUCTS__
++#define __HAILO_COMMON_SOC_STRUCTS__
++
++#include <linux/types.h>
++
++#pragma pack(push, 1)
++
++struct hailo_pcie_soc_connect_request {
++ u16 port;
++};
++
++struct hailo_pcie_soc_connect_response {
++ u8 input_channel_index;
++ u8 output_channel_index;
++};
++
++
++struct hailo_pcie_soc_close_request {
++ u32 channels_bitmap;
++};
++
++struct hailo_pcie_soc_close_response {
++ u8 reserved;
++};
++
++enum hailo_pcie_soc_control_code {
++ // Start from big initial value to ensure the right code was used (using 0
++ // as initiale may cause confusion if the code was not set correctly).
++ HAILO_PCIE_SOC_CONTROL_CODE_CONNECT = 0x100,
++ HAILO_PCIE_SOC_CONTROL_CODE_CLOSE,
++ HAILO_PCIE_SOC_CONTROL_CODE_INVALID,
++};
++
++#define HAILO_PCIE_SOC_MAX_REQUEST_SIZE_BYTES (16)
++#define HAILO_PCIE_SOC_MAX_RESPONSE_SIZE_BYTES (16)
++
++// IRQ to signal the PCIe that the EP was closed/released
++#define PCI_EP_SOC_CLOSED_IRQ (0x00000020)
++#define PCI_EP_SOC_CONNECT_RESPONSE (0x00000010)
++
++struct hailo_pcie_soc_request {
++ u32 control_code;
++ union {
++ struct hailo_pcie_soc_connect_request connect;
++ struct hailo_pcie_soc_close_request close;
++ u8 pad[HAILO_PCIE_SOC_MAX_REQUEST_SIZE_BYTES];
++ };
++};
++
++struct hailo_pcie_soc_response {
++ u32 control_code;
++ s32 status;
++ union {
++ struct hailo_pcie_soc_connect_response connect;
++ struct hailo_pcie_soc_close_response close;
++ u8 pad[HAILO_PCIE_SOC_MAX_RESPONSE_SIZE_BYTES];
++ };
++};
++
++#pragma pack(pop)
++
++// Compile time validate function. Don't need to call it.
++static inline void __validate_soc_struct_sizes(void)
++{
++ BUILD_BUG_ON_MSG(sizeof(struct hailo_pcie_soc_request) !=
++ sizeof(u32) + HAILO_PCIE_SOC_MAX_REQUEST_SIZE_BYTES, "Invalid request size");
++ BUILD_BUG_ON_MSG(sizeof(struct hailo_pcie_soc_response) !=
++ sizeof(u32) + sizeof(s32) + HAILO_PCIE_SOC_MAX_RESPONSE_SIZE_BYTES, "Invalid response size");
++}
++
++#endif /* __HAILO_COMMON_SOC_STRUCTS__ */
+\ No newline at end of file
+--- a/drivers/media/pci/hailo/common/utils.h
++++ b/drivers/media/pci/hailo/common/utils.h
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: MIT
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #ifndef _HAILO_DRIVER_UTILS_H_
+@@ -8,6 +8,11 @@
+
+ #include <linux/bitops.h>
+
++#define DWORD_SIZE (4)
++#define WORD_SIZE (2)
++#define BYTE_SIZE (1)
++#define BITS_IN_BYTE (8)
++
+ #define hailo_clear_bit(bit, pval) { *(pval) &= ~(1 << bit); }
+ #define hailo_test_bit(pos,var_addr) ((*var_addr) & (1<<(pos)))
+
+@@ -50,6 +55,22 @@ static inline uint8_t ceil_log2(uint32_t
+ return result;
+ }
+
++// Gets the nearest power of 2 >= value, for any value <= MAX_POWER_OF_2_VALUE. Otherwise POWER_OF_2_ERROR is returned.
++#define MAX_POWER_OF_2_VALUE (0x80000000)
++#define POWER_OF_2_ERROR ((uint32_t)-1)
++static inline uint32_t get_nearest_powerof_2(uint32_t value)
++{
++ uint32_t power_of_2 = 1;
++ if (value > MAX_POWER_OF_2_VALUE) {
++ return POWER_OF_2_ERROR;
++ }
++
++ while (value > power_of_2) {
++ power_of_2 <<= 1;
++ }
++ return power_of_2;
++}
++
+ #ifndef DIV_ROUND_UP
+ #define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
+ #endif
+--- a/drivers/media/pci/hailo/common/vdma_common.c
++++ b/drivers/media/pci/hailo/common/vdma_common.c
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: MIT
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #include "vdma_common.h"
+@@ -62,11 +62,6 @@
+ #define VDMA_CHANNEL_NUM_PROCESSED_MASK ((1 << VDMA_CHANNEL_NUM_PROCESSED_WIDTH) - 1)
+ #define VDMA_CHANNEL_NUM_ONGOING_MASK VDMA_CHANNEL_NUM_PROCESSED_MASK
+
+-#define DWORD_SIZE (4)
+-#define WORD_SIZE (2)
+-#define BYTE_SIZE (1)
+-#define BITS_IN_BYTE (8)
+-
+ #define TIMESTAMPS_CIRC_SPACE(timestamp_list) \
+ CIRC_SPACE((timestamp_list).head, (timestamp_list).tail, CHANNEL_IRQ_TIMESTAMPS_SIZE)
+ #define TIMESTAMPS_CIRC_CNT(timestamp_list) \
+@@ -150,7 +145,7 @@ static bool validate_last_desc_status(st
+ return true;
+ }
+
+-void hailo_vdma_program_descriptor(struct hailo_vdma_descriptor *descriptor, u64 dma_address, size_t page_size,
++static void hailo_vdma_program_descriptor(struct hailo_vdma_descriptor *descriptor, u64 dma_address, size_t page_size,
+ u8 data_id)
+ {
+ descriptor->PageSize_DescControl = (u32)((page_size << DESCRIPTOR_PAGE_SIZE_SHIFT) +
+@@ -174,33 +169,45 @@ static int program_descriptors_in_chunk(
+ u32 max_desc_index,
+ u8 channel_id)
+ {
+- const u32 desc_per_chunk = DIV_ROUND_UP(chunk_size, desc_list->desc_page_size);
++ const u16 page_size = desc_list->desc_page_size;
++ const u8 ddr_data_id = vdma_hw->ddr_data_id;
++ const u32 descs_to_program = DIV_ROUND_UP(chunk_size, page_size);
++ const u32 starting_desc_index = desc_index;
++ const u32 residue_size = chunk_size % page_size;
+ struct hailo_vdma_descriptor *dma_desc = NULL;
+- u16 size_to_program = 0;
+- u32 index = 0;
+ u64 encoded_addr = 0;
+
+- for (index = 0; index < desc_per_chunk; index++) {
+- if (desc_index > max_desc_index) {
+- return -ERANGE;
+- }
+-
+- encoded_addr = vdma_hw->hw_ops.encode_desc_dma_address(chunk_addr, channel_id);
+- if (INVALID_VDMA_ADDRESS == encoded_addr) {
+- return -EFAULT;
+- }
++ if (descs_to_program == 0) {
++ // Nothing to program
++ return 0;
++ }
+
+- dma_desc = &desc_list->desc_list[desc_index % desc_list->desc_count];
+- size_to_program = chunk_size > desc_list->desc_page_size ?
+- desc_list->desc_page_size : (u16)chunk_size;
+- hailo_vdma_program_descriptor(dma_desc, encoded_addr, size_to_program, vdma_hw->ddr_data_id);
++ // We iterate through descriptors [desc_index, desc_index + descs_to_program)
++ if (desc_index + descs_to_program > max_desc_index + 1) {
++ return -ERANGE;
++ }
+
+- chunk_addr += size_to_program;
+- chunk_size -= size_to_program;
+- desc_index++;
++ encoded_addr = vdma_hw->hw_ops.encode_desc_dma_address_range(chunk_addr, chunk_addr + chunk_size, page_size, channel_id);
++ if (INVALID_VDMA_ADDRESS == encoded_addr) {
++ return -EFAULT;
+ }
+
+- return (int)desc_per_chunk;
++ // Program all descriptors except the last one
++ for (desc_index = starting_desc_index; desc_index < starting_desc_index + descs_to_program - 1; desc_index++) {
++ // 'desc_index & desc_list_len_mask' is used instead of modulo; see hailo_vdma_descriptors_list documentation.
++ hailo_vdma_program_descriptor(
++ &desc_list->desc_list[desc_index & desc_list->desc_count_mask],
++ encoded_addr, page_size, ddr_data_id);
++ encoded_addr += page_size;
++ }
++
++ // Handle the last descriptor outside of the loop
++ // 'desc_index & desc_list_len_mask' is used instead of modulo; see hailo_vdma_descriptors_list documentation.
++ dma_desc = &desc_list->desc_list[desc_index & desc_list->desc_count_mask];
++ hailo_vdma_program_descriptor(dma_desc, encoded_addr,
++ (residue_size == 0) ? page_size : (u16)residue_size, ddr_data_id);
++
++ return (int)descs_to_program;
+ }
+
+ static unsigned long get_interrupts_bitmask(struct hailo_vdma_hw *vdma_hw,
+@@ -236,11 +243,11 @@ static int bind_and_program_descriptors_
+ {
+ const u8 channel_id = get_channel_id(channel_index);
+ int desc_programmed = 0;
++ int descs_programmed_in_chunk = 0;
+ u32 max_desc_index = 0;
+ u32 chunk_size = 0;
+ struct scatterlist *sg_entry = NULL;
+ unsigned int i = 0;
+- int ret = 0;
+ size_t buffer_current_offset = 0;
+ dma_addr_t chunk_start_addr = 0;
+ u32 program_size = buffer->size;
+@@ -272,14 +279,14 @@ static int bind_and_program_descriptors_
+ (u32)(sg_dma_len(sg_entry));
+ chunk_size = min((u32)program_size, chunk_size);
+
+- ret = program_descriptors_in_chunk(vdma_hw, chunk_start_addr, chunk_size, desc_list,
++ descs_programmed_in_chunk = program_descriptors_in_chunk(vdma_hw, chunk_start_addr, chunk_size, desc_list,
+ starting_desc, max_desc_index, channel_id);
+- if (ret < 0) {
+- return ret;
++ if (descs_programmed_in_chunk < 0) {
++ return descs_programmed_in_chunk;
+ }
+
+- desc_programmed += ret;
+- starting_desc = starting_desc + ret;
++ desc_programmed += descs_programmed_in_chunk;
++ starting_desc = starting_desc + descs_programmed_in_chunk;
+ program_size -= chunk_size;
+ buffer_current_offset += sg_dma_len(sg_entry);
+ }
+@@ -583,21 +590,23 @@ void hailo_vdma_engine_disable_channels(
+ engine->enabled_channels &= ~bitmap;
+
+ for_each_vdma_channel(engine, channel, channel_index) {
+- channel_state_init(&channel->state);
++ if (hailo_test_bit(channel_index, &bitmap)) {
++ channel_state_init(&channel->state);
+
+- while (ONGOING_TRANSFERS_CIRC_CNT(channel->ongoing_transfers) > 0) {
+- struct hailo_ongoing_transfer transfer;
+- ongoing_transfer_pop(channel, &transfer);
+-
+- if (channel->last_desc_list == NULL) {
+- pr_err("Channel %d has ongoing transfers but no desc list\n", channel->index);
+- continue;
++ while (ONGOING_TRANSFERS_CIRC_CNT(channel->ongoing_transfers) > 0) {
++ struct hailo_ongoing_transfer transfer;
++ ongoing_transfer_pop(channel, &transfer);
++
++ if (channel->last_desc_list == NULL) {
++ pr_err("Channel %d has ongoing transfers but no desc list\n", channel->index);
++ continue;
++ }
++
++ clear_dirty_descs(channel, &transfer);
+ }
+
+- clear_dirty_descs(channel, &transfer);
++ channel->last_desc_list = NULL;
+ }
+-
+- channel->last_desc_list = NULL;
+ }
+ }
+
+--- a/drivers/media/pci/hailo/common/vdma_common.h
++++ b/drivers/media/pci/hailo/common/vdma_common.h
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: MIT
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #ifndef _HAILO_COMMON_VDMA_COMMON_H_
+@@ -30,7 +30,13 @@ struct hailo_vdma_descriptor {
+
+ struct hailo_vdma_descriptors_list {
+ struct hailo_vdma_descriptor *desc_list;
+- u32 desc_count; // Must be power of 2 if is_circular is set.
++ // Must be power of 2 if is_circular is set.
++ u32 desc_count;
++ // The nearest power of 2 to desc_count (including desc_count), minus 1.
++ // * If the list is circular, then 'index & desc_count_mask' can be used instead of modulo.
++ // * Otherwise, we can't wrap around the list anyway. However, for any index < desc_count, 'index & desc_count_mask'
++ // will return the same value.
++ u32 desc_count_mask;
+ u16 desc_page_size;
+ bool is_circular;
+ };
+@@ -113,9 +119,10 @@ struct hailo_vdma_engine {
+ };
+
+ struct hailo_vdma_hw_ops {
+- // Accepts some dma_addr_t mapped to the device and encodes it using
+- // hw specific encode. returns INVALID_VDMA_ADDRESS on failure.
+- u64 (*encode_desc_dma_address)(dma_addr_t dma_address, u8 channel_id);
++ // Accepts start, end and step of an address range (of type dma_addr_t).
++ // Returns the encoded base address or INVALID_VDMA_ADDRESS if the range/step is invalid.
++ // All addresses in the range of [returned_addr, returned_addr + step, returned_addr + 2*step, ..., dma_address_end) are valid.
++ u64 (*encode_desc_dma_address_range)(dma_addr_t dma_address_start, dma_addr_t dma_address_end, u32 step, u8 channel_id);
+ };
+
+ struct hailo_vdma_hw {
+@@ -136,12 +143,9 @@ struct hailo_vdma_hw {
+ for (index = 0, element = &array[index]; index < size; index++, element = &array[index])
+
+ #define for_each_vdma_channel(engine, channel, channel_index) \
+- _for_each_element_array(engine->channels, MAX_VDMA_CHANNELS_PER_ENGINE, \
++ _for_each_element_array((engine)->channels, MAX_VDMA_CHANNELS_PER_ENGINE, \
+ channel, channel_index)
+
+-void hailo_vdma_program_descriptor(struct hailo_vdma_descriptor *descriptor, u64 dma_address, size_t page_size,
+- u8 data_id);
+-
+ /**
+ * Program the given descriptors list to map the given buffer.
+ *
+--- a/drivers/media/pci/hailo/src/fops.c
++++ b/drivers/media/pci/hailo/src/fops.c
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #include <linux/version.h>
+@@ -19,14 +19,14 @@
+ #include <linux/sched/signal.h>
+ #endif
+
+-#include "utils.h"
+ #include "fops.h"
+ #include "vdma_common.h"
+ #include "utils/logs.h"
+ #include "vdma/memory.h"
+ #include "vdma/ioctl.h"
+ #include "utils/compact.h"
+-#include "pci_soc_ioctl.h"
++#include "nnc.h"
++#include "soc.h"
+
+
+ #if LINUX_VERSION_CODE >= KERNEL_VERSION( 4, 13, 0 )
+@@ -48,13 +48,6 @@
+ // On pcie driver there is only one dma engine
+ #define DEFAULT_VDMA_ENGINE_INDEX (0)
+
+-#if !defined(HAILO_EMULATOR)
+-#define DEFAULT_SHUTDOWN_TIMEOUT_MS (5)
+-#else /* !defined(HAILO_EMULATOR) */
+-#define DEFAULT_SHUTDOWN_TIMEOUT_MS (1000)
+-#endif /* !defined(HAILO_EMULATOR) */
+-
+-static long hailo_add_notification_wait(struct hailo_pcie_board *board, struct file *filp);
+
+ static struct hailo_file_context *create_file_context(struct hailo_pcie_board *board, struct file *filp)
+ {
+@@ -124,7 +117,7 @@ int hailo_pcie_fops_open(struct inode *i
+
+ previous_power_state = pBoard->pDev->current_state;
+ if (PCI_D0 != previous_power_state) {
+- hailo_info(pBoard, "Waking up board");
++ hailo_info(pBoard, "Waking up board change state from %d to PCI_D0\n", previous_power_state);
+ err = pci_set_power_state(pBoard->pDev, PCI_D0);
+ if (err < 0) {
+ hailo_err(pBoard, "Failed waking up board %d", err);
+@@ -148,7 +141,11 @@ int hailo_pcie_fops_open(struct inode *i
+ interrupts_enabled_by_filp = true;
+ }
+
+- err = hailo_add_notification_wait(pBoard, filp);
++ if (pBoard->pcie_resources.accelerator_type == HAILO_ACCELERATOR_TYPE_NNC) {
++ err = hailo_nnc_file_context_init(pBoard, context);
++ } else {
++ err = hailo_soc_file_context_init(pBoard, context);
++ }
+ if (err < 0) {
+ goto l_release_irq;
+ }
+@@ -166,6 +163,7 @@ l_release_irq:
+
+ l_revert_power_state:
+ if (pBoard->pDev->current_state != previous_power_state) {
++ hailo_info(pBoard, "Power changing state from %d to %d\n", previous_power_state, pBoard->pDev->current_state);
+ if (pci_set_power_state(pBoard->pDev, previous_power_state) < 0) {
+ hailo_err(pBoard, "Failed setting power state back to %d\n", (int)previous_power_state);
+ }
+@@ -180,34 +178,6 @@ l_exit:
+ return err;
+ }
+
+-int hailo_pcie_driver_down(struct hailo_pcie_board *board)
+-{
+- long completion_result = 0;
+- int err = 0;
+-
+- reinit_completion(&board->driver_down.reset_completed);
+-
+- hailo_pcie_write_firmware_driver_shutdown(&board->pcie_resources);
+-
+- // Wait for response
+- completion_result =
+- wait_for_completion_timeout(&board->driver_down.reset_completed, msecs_to_jiffies(DEFAULT_SHUTDOWN_TIMEOUT_MS));
+- if (completion_result <= 0) {
+- if (0 == completion_result) {
+- hailo_err(board, "hailo_pcie_driver_down, timeout waiting for shutdown response (timeout_ms=%d)\n", DEFAULT_SHUTDOWN_TIMEOUT_MS);
+- err = -ETIMEDOUT;
+- } else {
+- hailo_info(board, "hailo_pcie_driver_down, wait for completion failed with err=%ld (process was interrupted or killed)\n",
+- completion_result);
+- err = completion_result;
+- }
+- goto l_exit;
+- }
+-
+-l_exit:
+- return err;
+-}
+-
+ int hailo_pcie_fops_release(struct inode *inode, struct file *filp)
+ {
+ struct hailo_pcie_board *board = (struct hailo_pcie_board *)filp->private_data;
+@@ -234,12 +204,10 @@ int hailo_pcie_fops_release(struct inode
+ hailo_err(board, "Invalid file context\n");
+ }
+
+- hailo_pcie_clear_notification_wait_list(board, filp);
+-
+- if (filp == board->vdma.used_by_filp) {
+- if (hailo_pcie_driver_down(board)) {
+- hailo_err(board, "Failed sending FW shutdown event");
+- }
++ if (board->pcie_resources.accelerator_type == HAILO_ACCELERATOR_TYPE_NNC) {
++ hailo_nnc_file_context_finalize(board, context);
++ } else {
++ hailo_soc_file_context_finalize(board, context);
+ }
+
+ hailo_vdma_file_context_finalize(&context->vdma_context, &board->vdma, filp);
+@@ -250,6 +218,7 @@ int hailo_pcie_fops_release(struct inode
+ hailo_disable_interrupts(board);
+
+ if (power_mode_enabled()) {
++ hailo_info(board, "Power change state to PCI_D3hot\n");
+ if (board->pDev && pci_set_power_state(board->pDev, PCI_D3hot) < 0) {
+ hailo_err(board, "Failed setting power state to D3hot");
+ }
+@@ -301,44 +270,23 @@ static long hailo_memory_transfer_ioctl(
+ return err;
+ }
+
+-static long hailo_read_log_ioctl(struct hailo_pcie_board *pBoard, unsigned long arg)
+-{
+- long err = 0;
+- struct hailo_read_log_params params;
+-
+- if (copy_from_user(¶ms, (void __user*)arg, sizeof(params))) {
+- hailo_err(pBoard, "HAILO_READ_LOG, copy_from_user fail\n");
+- return -ENOMEM;
+- }
+-
+- if (0 > (err = hailo_pcie_read_firmware_log(&pBoard->pcie_resources, ¶ms))) {
+- hailo_err(pBoard, "HAILO_READ_LOG, reading from log failed with error: %ld \n", err);
+- return err;
+- }
+-
+- if (copy_to_user((void*)arg, ¶ms, sizeof(params))) {
+- return -ENOMEM;
+- }
+-
+- return 0;
+-}
+-
+ static void firmware_notification_irq_handler(struct hailo_pcie_board *board)
+ {
+ struct hailo_notification_wait *notif_wait_cursor = NULL;
+ int err = 0;
+ unsigned long irq_saved_flags = 0;
+
+- spin_lock_irqsave(&board->notification_read_spinlock, irq_saved_flags);
+- err = hailo_pcie_read_firmware_notification(&board->pcie_resources, &board->notification_cache);
+- spin_unlock_irqrestore(&board->notification_read_spinlock, irq_saved_flags);
++ spin_lock_irqsave(&board->nnc.notification_read_spinlock, irq_saved_flags);
++ err = hailo_pcie_read_firmware_notification(&board->pcie_resources.fw_access, &board->nnc.notification_cache);
++ spin_unlock_irqrestore(&board->nnc.notification_read_spinlock, irq_saved_flags);
+
+ if (err < 0) {
+ hailo_err(board, "Failed reading firmware notification");
+ }
+ else {
++ // TODO: HRT-14502 move interrupt handling to nnc
+ rcu_read_lock();
+- list_for_each_entry_rcu(notif_wait_cursor, &board->notification_wait_list, notification_wait_list)
++ list_for_each_entry_rcu(notif_wait_cursor, &board->nnc.notification_wait_list, notification_wait_list)
+ {
+ complete(¬if_wait_cursor->notification_completion);
+ }
+@@ -374,7 +322,7 @@ irqreturn_t hailo_irqhandler(int irq, vo
+
+ // wake fw_control if needed
+ if (irq_source.interrupt_bitmask & FW_CONTROL) {
+- complete(&board->fw_control.completion);
++ complete(&board->nnc.fw_control.completion);
+ }
+
+ // wake driver_down if needed
+@@ -392,7 +340,14 @@ irqreturn_t hailo_irqhandler(int irq, vo
+ }
+
+ if (irq_source.interrupt_bitmask & SOC_CONNECT_ACCEPTED) {
+- complete_all(&board->soc_connect_accepted);
++ complete_all(&board->soc.control_resp_ready);
++ }
++
++ if (irq_source.interrupt_bitmask & SOC_CLOSED_IRQ) {
++ hailo_info(board, "hailo_irqhandler - SOC_CLOSED_IRQ\n");
++ // always use bitmap=0xFFFFFFFF - it is ok to wake all interrupts since each handler will check if the stream was aborted or not.
++ hailo_vdma_wakeup_interrupts(&board->vdma, &board->vdma.vdma_engines[DEFAULT_VDMA_ENGINE_INDEX],
++ 0xFFFFFFFF);
+ }
+
+ if (0 != irq_source.vdma_channels_bitmap) {
+@@ -404,170 +359,11 @@ irqreturn_t hailo_irqhandler(int irq, vo
+ return return_value;
+ }
+
+-static long hailo_get_notification_wait_thread(struct hailo_pcie_board *pBoard, struct file *filp,
+- struct hailo_notification_wait **current_waiting_thread)
+-{
+- struct hailo_notification_wait *cursor = NULL;
+- // note: safe to access without rcu because the notification_wait_list is closed only on file release
+- list_for_each_entry(cursor, &pBoard->notification_wait_list, notification_wait_list)
+- {
+- if ((current->tgid == cursor->tgid) && (filp == cursor->filp)) {
+- *current_waiting_thread = cursor;
+- return 0;
+- }
+- }
+-
+- return -EFAULT;
+-}
+-
+-static long hailo_add_notification_wait(struct hailo_pcie_board *board, struct file *filp)
+-{
+- struct hailo_notification_wait *new_notification_wait = NULL;
+- if (!(new_notification_wait = kmalloc(sizeof(*new_notification_wait), GFP_KERNEL))) {
+- hailo_err(board, "Failed to allocate notification wait structure.\n");
+- return -ENOMEM;
+- }
+- new_notification_wait->tgid = current->tgid;
+- new_notification_wait->filp = filp;
+- new_notification_wait->is_disabled = false;
+- init_completion(&new_notification_wait->notification_completion);
+- list_add_rcu(&new_notification_wait->notification_wait_list, &board->notification_wait_list);
+- return 0;
+-}
+-
+-static long hailo_read_notification_ioctl(struct hailo_pcie_board *pBoard, unsigned long arg, struct file *filp,
+- bool* should_up_board_mutex)
+-{
+- long err = 0;
+- struct hailo_notification_wait *current_waiting_thread = NULL;
+- struct hailo_d2h_notification *notification = &pBoard->notification_to_user;
+- unsigned long irq_saved_flags;
+-
+- err = hailo_get_notification_wait_thread(pBoard, filp, ¤t_waiting_thread);
+- if (0 != err) {
+- goto l_exit;
+- }
+- up(&pBoard->mutex);
+-
+- if (0 > (err = wait_for_completion_interruptible(¤t_waiting_thread->notification_completion))) {
+- hailo_info(pBoard,
+- "HAILO_READ_NOTIFICATION - wait_for_completion_interruptible error. err=%ld. tgid=%d (process was interrupted or killed)\n",
+- err, current_waiting_thread->tgid);
+- *should_up_board_mutex = false;
+- goto l_exit;
+- }
+-
+- if (down_interruptible(&pBoard->mutex)) {
+- hailo_info(pBoard, "HAILO_READ_NOTIFICATION - down_interruptible error (process was interrupted or killed)\n");
+- *should_up_board_mutex = false;
+- err = -ERESTARTSYS;
+- goto l_exit;
+- }
+-
+- // Check if was disabled
+- if (current_waiting_thread->is_disabled) {
+- hailo_info(pBoard, "HAILO_READ_NOTIFICATION, can't find notification wait for tgid=%d\n", current->tgid);
+- err = -EINVAL;
+- goto l_exit;
+- }
+-
+- reinit_completion(¤t_waiting_thread->notification_completion);
+-
+- spin_lock_irqsave(&pBoard->notification_read_spinlock, irq_saved_flags);
+- notification->buffer_len = pBoard->notification_cache.buffer_len;
+- memcpy(notification->buffer, pBoard->notification_cache.buffer, notification->buffer_len);
+- spin_unlock_irqrestore(&pBoard->notification_read_spinlock, irq_saved_flags);
+-
+- if (copy_to_user((void __user*)arg, notification, sizeof(*notification))) {
+- hailo_err(pBoard, "HAILO_READ_NOTIFICATION copy_to_user fail\n");
+- err = -ENOMEM;
+- goto l_exit;
+- }
+-
+-l_exit:
+- return err;
+-}
+-
+-static long hailo_disable_notification(struct hailo_pcie_board *pBoard, struct file *filp)
+-{
+- struct hailo_notification_wait *cursor = NULL;
+-
+- hailo_info(pBoard, "HAILO_DISABLE_NOTIFICATION: disable notification");
+- rcu_read_lock();
+- list_for_each_entry_rcu(cursor, &pBoard->notification_wait_list, notification_wait_list) {
+- if ((current->tgid == cursor->tgid) && (filp == cursor->filp)) {
+- cursor->is_disabled = true;
+- complete(&cursor->notification_completion);
+- break;
+- }
+- }
+- rcu_read_unlock();
+-
+- return 0;
+-}
+-
+-static int hailo_fw_control(struct hailo_pcie_board *pBoard, unsigned long arg, bool* should_up_board_mutex)
+-{
+- struct hailo_fw_control *command = &pBoard->fw_control.command;
+- long completion_result = 0;
+- int err = 0;
+-
+- up(&pBoard->mutex);
+- *should_up_board_mutex = false;
+-
+- if (down_interruptible(&pBoard->fw_control.mutex)) {
+- hailo_info(pBoard, "hailo_fw_control down_interruptible fail tgid:%d (process was interrupted or killed)\n", current->tgid);
+- return -ERESTARTSYS;
+- }
+-
+- if (copy_from_user(command, (void __user*)arg, sizeof(*command))) {
+- hailo_err(pBoard, "hailo_fw_control, copy_from_user fail\n");
+- err = -ENOMEM;
+- goto l_exit;
+- }
+-
+- reinit_completion(&pBoard->fw_control.completion);
+-
+- err = hailo_pcie_write_firmware_control(&pBoard->pcie_resources, command);
+- if (err < 0) {
+- hailo_err(pBoard, "Failed writing fw control to pcie\n");
+- goto l_exit;
+- }
+-
+- // Wait for response
+- completion_result = wait_for_completion_interruptible_timeout(&pBoard->fw_control.completion, msecs_to_jiffies(command->timeout_ms));
+- if (completion_result <= 0) {
+- if (0 == completion_result) {
+- hailo_err(pBoard, "hailo_fw_control, timeout waiting for control (timeout_ms=%d)\n", command->timeout_ms);
+- err = -ETIMEDOUT;
+- } else {
+- hailo_info(pBoard, "hailo_fw_control, wait for completion failed with err=%ld (process was interrupted or killed)\n", completion_result);
+- err = -EINTR;
+- }
+- goto l_exit;
+- }
+-
+- err = hailo_pcie_read_firmware_control(&pBoard->pcie_resources, command);
+- if (err < 0) {
+- hailo_err(pBoard, "Failed reading fw control from pcie\n");
+- goto l_exit;
+- }
+-
+- if (copy_to_user((void __user*)arg, command, sizeof(*command))) {
+- hailo_err(pBoard, "hailo_fw_control, copy_to_user fail\n");
+- err = -ENOMEM;
+- goto l_exit;
+- }
+-
+-l_exit:
+- up(&pBoard->fw_control.mutex);
+- return err;
+-}
+-
+ static long hailo_query_device_properties(struct hailo_pcie_board *board, unsigned long arg)
+ {
+ struct hailo_device_properties props = {
+ .desc_max_page_size = board->desc_max_page_size,
++ .board_type = board->pcie_resources.board_type,
+ .allocation_mode = board->allocation_mode,
+ .dma_type = HAILO_DMA_TYPE_PCIE,
+ .dma_engines_count = board->vdma.vdma_engines_count,
+@@ -618,24 +414,6 @@ static long hailo_general_ioctl(struct h
+ }
+ }
+
+-static long hailo_nnc_ioctl(struct hailo_pcie_board *board, unsigned int cmd, unsigned long arg,
+- struct file *filp, bool *should_up_board_mutex)
+-{
+- switch (cmd) {
+- case HAILO_FW_CONTROL:
+- return hailo_fw_control(board, arg, should_up_board_mutex);
+- case HAILO_READ_NOTIFICATION:
+- return hailo_read_notification_ioctl(board, arg, filp, should_up_board_mutex);
+- case HAILO_DISABLE_NOTIFICATION:
+- return hailo_disable_notification(board, filp);
+- case HAILO_READ_LOG:
+- return hailo_read_log_ioctl(board, arg);
+- default:
+- hailo_err(board, "Invalid nnc ioctl code 0x%x (nr: %d)\n", cmd, _IOC_NR(cmd));
+- return -ENOTTY;
+- }
+-}
+-
+ long hailo_pcie_fops_unlockedioctl(struct file* filp, unsigned int cmd, unsigned long arg)
+ {
+ long err = 0;
+@@ -694,7 +472,7 @@ long hailo_pcie_fops_unlockedioctl(struc
+ hailo_err(board, "Ioctl %d is not supported on this accelerator type\n", _IOC_TYPE(cmd));
+ err = -EINVAL;
+ } else {
+- err = hailo_soc_ioctl(board, &context->vdma_context, &board->vdma, cmd, arg);
++ err = hailo_soc_ioctl(board, context, &board->vdma, cmd, arg);
+ }
+ break;
+ case HAILO_NNC_IOCTL_MAGIC:
+--- a/drivers/media/pci/hailo/src/fops.h
++++ b/drivers/media/pci/hailo/src/fops.h
+@@ -1,16 +1,17 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #ifndef _HAILO_PCI_FOPS_H_
+ #define _HAILO_PCI_FOPS_H_
+
++#include "pcie.h"
++
+ int hailo_pcie_fops_open(struct inode* inode, struct file* filp);
+ int hailo_pcie_fops_release(struct inode* inode, struct file* filp);
+ long hailo_pcie_fops_unlockedioctl(struct file* filp, unsigned int cmd, unsigned long arg);
+ int hailo_pcie_fops_mmap(struct file* filp, struct vm_area_struct *vma);
+-int hailo_pcie_driver_down(struct hailo_pcie_board *board);
+ void hailo_pcie_ep_init(struct hailo_pcie_board *board);
+
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)
+--- /dev/null
++++ b/drivers/media/pci/hailo/src/nnc.c
+@@ -0,0 +1,299 @@
++// SPDX-License-Identifier: GPL-2.0
++/**
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
++ **/
++/**
++ * A Hailo PCIe NNC device is a device contains a NNC (neural network core) and some basic FW.
++ * The device supports sending controls, receiving notification and reading the FW log.
++ */
++
++#include "nnc.h"
++#include "hailo_ioctl_common.h"
++
++#include "utils/logs.h"
++#include "utils/compact.h"
++
++#include <linux/uaccess.h>
++
++#if !defined(HAILO_EMULATOR)
++#define DEFAULT_SHUTDOWN_TIMEOUT_MS (5)
++#else /* !defined(HAILO_EMULATOR) */
++#define DEFAULT_SHUTDOWN_TIMEOUT_MS (1000)
++#endif /* !defined(HAILO_EMULATOR) */
++
++void hailo_nnc_init(struct hailo_pcie_nnc *nnc)
++{
++ sema_init(&nnc->fw_control.mutex, 1);
++ spin_lock_init(&nnc->notification_read_spinlock);
++ init_completion(&nnc->fw_control.completion);
++ INIT_LIST_HEAD(&nnc->notification_wait_list);
++ memset(&nnc->notification_cache, 0, sizeof(nnc->notification_cache));
++}
++
++void hailo_nnc_finalize(struct hailo_pcie_nnc *nnc)
++{
++ struct hailo_notification_wait *cursor = NULL;
++
++ // Lock rcu_read_lock and send notification_completion to wake anyone waiting on the notification_wait_list when removed
++ rcu_read_lock();
++ list_for_each_entry_rcu(cursor, &nnc->notification_wait_list, notification_wait_list) {
++ cursor->is_disabled = true;
++ complete(&cursor->notification_completion);
++ }
++ rcu_read_unlock();
++}
++
++static int hailo_fw_control(struct hailo_pcie_board *board, unsigned long arg, bool* should_up_board_mutex)
++{
++ struct hailo_fw_control *command = &board->nnc.fw_control.command;
++ long completion_result = 0;
++ int err = 0;
++
++ up(&board->mutex);
++ *should_up_board_mutex = false;
++
++ if (down_interruptible(&board->nnc.fw_control.mutex)) {
++ hailo_info(board, "hailo_fw_control down_interruptible fail tgid:%d (process was interrupted or killed)\n", current->tgid);
++ return -ERESTARTSYS;
++ }
++
++ if (copy_from_user(command, (void __user*)arg, sizeof(*command))) {
++ hailo_err(board, "hailo_fw_control, copy_from_user fail\n");
++ err = -ENOMEM;
++ goto l_exit;
++ }
++
++ reinit_completion(&board->nnc.fw_control.completion);
++
++ err = hailo_pcie_write_firmware_control(&board->pcie_resources, command);
++ if (err < 0) {
++ hailo_err(board, "Failed writing fw control to pcie\n");
++ goto l_exit;
++ }
++
++ // Wait for response
++ completion_result = wait_for_completion_interruptible_timeout(&board->nnc.fw_control.completion, msecs_to_jiffies(command->timeout_ms));
++ if (completion_result <= 0) {
++ if (0 == completion_result) {
++ hailo_err(board, "hailo_fw_control, timeout waiting for control (timeout_ms=%d)\n", command->timeout_ms);
++ err = -ETIMEDOUT;
++ } else {
++ hailo_info(board, "hailo_fw_control, wait for completion failed with err=%ld (process was interrupted or killed)\n", completion_result);
++ err = -EINTR;
++ }
++ goto l_exit;
++ }
++
++ err = hailo_pcie_read_firmware_control(&board->pcie_resources, command);
++ if (err < 0) {
++ hailo_err(board, "Failed reading fw control from pcie\n");
++ goto l_exit;
++ }
++
++ if (copy_to_user((void __user*)arg, command, sizeof(*command))) {
++ hailo_err(board, "hailo_fw_control, copy_to_user fail\n");
++ err = -ENOMEM;
++ goto l_exit;
++ }
++
++l_exit:
++ up(&board->nnc.fw_control.mutex);
++ return err;
++}
++
++static long hailo_get_notification_wait_thread(struct hailo_pcie_board *board, struct file *filp,
++ struct hailo_notification_wait **current_waiting_thread)
++{
++ struct hailo_notification_wait *cursor = NULL;
++ // note: safe to access without rcu because the notification_wait_list is closed only on file release
++ list_for_each_entry(cursor, &board->nnc.notification_wait_list, notification_wait_list)
++ {
++ if ((current->tgid == cursor->tgid) && (filp == cursor->filp)) {
++ *current_waiting_thread = cursor;
++ return 0;
++ }
++ }
++
++ return -EFAULT;
++}
++
++static long hailo_read_notification_ioctl(struct hailo_pcie_board *board, unsigned long arg, struct file *filp,
++ bool* should_up_board_mutex)
++{
++ long err = 0;
++ struct hailo_notification_wait *current_waiting_thread = NULL;
++ struct hailo_d2h_notification *notification = &board->nnc.notification_to_user;
++ unsigned long irq_saved_flags;
++
++ err = hailo_get_notification_wait_thread(board, filp, ¤t_waiting_thread);
++ if (0 != err) {
++ goto l_exit;
++ }
++ up(&board->mutex);
++
++ if (0 > (err = wait_for_completion_interruptible(¤t_waiting_thread->notification_completion))) {
++ hailo_info(board,
++ "HAILO_READ_NOTIFICATION - wait_for_completion_interruptible error. err=%ld. tgid=%d (process was interrupted or killed)\n",
++ err, current_waiting_thread->tgid);
++ *should_up_board_mutex = false;
++ goto l_exit;
++ }
++
++ if (down_interruptible(&board->mutex)) {
++ hailo_info(board, "HAILO_READ_NOTIFICATION - down_interruptible error (process was interrupted or killed)\n");
++ *should_up_board_mutex = false;
++ err = -ERESTARTSYS;
++ goto l_exit;
++ }
++
++ // Check if was disabled
++ if (current_waiting_thread->is_disabled) {
++ hailo_info(board, "HAILO_READ_NOTIFICATION, can't find notification wait for tgid=%d\n", current->tgid);
++ err = -EINVAL;
++ goto l_exit;
++ }
++
++ reinit_completion(¤t_waiting_thread->notification_completion);
++
++ spin_lock_irqsave(&board->nnc.notification_read_spinlock, irq_saved_flags);
++ notification->buffer_len = board->nnc.notification_cache.buffer_len;
++ memcpy(notification->buffer, board->nnc.notification_cache.buffer, notification->buffer_len);
++ spin_unlock_irqrestore(&board->nnc.notification_read_spinlock, irq_saved_flags);
++
++ if (copy_to_user((void __user*)arg, notification, sizeof(*notification))) {
++ hailo_err(board, "HAILO_READ_NOTIFICATION copy_to_user fail\n");
++ err = -ENOMEM;
++ goto l_exit;
++ }
++
++l_exit:
++ return err;
++}
++
++static long hailo_disable_notification(struct hailo_pcie_board *board, struct file *filp)
++{
++ struct hailo_notification_wait *cursor = NULL;
++
++ hailo_info(board, "HAILO_DISABLE_NOTIFICATION: disable notification");
++ rcu_read_lock();
++ list_for_each_entry_rcu(cursor, &board->nnc.notification_wait_list, notification_wait_list) {
++ if ((current->tgid == cursor->tgid) && (filp == cursor->filp)) {
++ cursor->is_disabled = true;
++ complete(&cursor->notification_completion);
++ break;
++ }
++ }
++ rcu_read_unlock();
++
++ return 0;
++}
++
++static long hailo_read_log_ioctl(struct hailo_pcie_board *board, unsigned long arg)
++{
++ long err = 0;
++ struct hailo_read_log_params params;
++
++ if (copy_from_user(¶ms, (void __user*)arg, sizeof(params))) {
++ hailo_err(board, "HAILO_READ_LOG, copy_from_user fail\n");
++ return -ENOMEM;
++ }
++
++ if (0 > (err = hailo_pcie_read_firmware_log(&board->pcie_resources.fw_access, ¶ms))) {
++ hailo_err(board, "HAILO_READ_LOG, reading from log failed with error: %ld \n", err);
++ return err;
++ }
++
++ if (copy_to_user((void*)arg, ¶ms, sizeof(params))) {
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++
++long hailo_nnc_ioctl(struct hailo_pcie_board *board, unsigned int cmd, unsigned long arg,
++ struct file *filp, bool *should_up_board_mutex)
++{
++ switch (cmd) {
++ case HAILO_FW_CONTROL:
++ return hailo_fw_control(board, arg, should_up_board_mutex);
++ case HAILO_READ_NOTIFICATION:
++ return hailo_read_notification_ioctl(board, arg, filp, should_up_board_mutex);
++ case HAILO_DISABLE_NOTIFICATION:
++ return hailo_disable_notification(board, filp);
++ case HAILO_READ_LOG:
++ return hailo_read_log_ioctl(board, arg);
++ default:
++ hailo_err(board, "Invalid nnc ioctl code 0x%x (nr: %d)\n", cmd, _IOC_NR(cmd));
++ return -ENOTTY;
++ }
++}
++
++
++static int add_notification_wait(struct hailo_pcie_board *board, struct file *filp)
++{
++ struct hailo_notification_wait *wait = kmalloc(sizeof(*wait), GFP_KERNEL);
++ if (!wait) {
++ hailo_err(board, "Failed to allocate notification wait structure.\n");
++ return -ENOMEM;
++ }
++ wait->tgid = current->tgid;
++ wait->filp = filp;
++ wait->is_disabled = false;
++ init_completion(&wait->notification_completion);
++ list_add_rcu(&wait->notification_wait_list, &board->nnc.notification_wait_list);
++ return 0;
++}
++
++int hailo_nnc_file_context_init(struct hailo_pcie_board *board, struct hailo_file_context *context)
++{
++ return add_notification_wait(board, context->filp);
++}
++
++static void clear_notification_wait_list(struct hailo_pcie_board *board, struct file *filp)
++{
++ struct hailo_notification_wait *cur = NULL, *next = NULL;
++ list_for_each_entry_safe(cur, next, &board->nnc.notification_wait_list, notification_wait_list) {
++ if (cur->filp == filp) {
++ list_del_rcu(&cur->notification_wait_list);
++ synchronize_rcu();
++ kfree(cur);
++ }
++ }
++}
++
++int hailo_nnc_driver_down(struct hailo_pcie_board *board)
++{
++ long completion_result = 0;
++ int err = 0;
++
++ reinit_completion(&board->driver_down.reset_completed);
++
++ hailo_pcie_write_firmware_driver_shutdown(&board->pcie_resources);
++
++ // Wait for response
++ completion_result =
++ wait_for_completion_timeout(&board->driver_down.reset_completed, msecs_to_jiffies(DEFAULT_SHUTDOWN_TIMEOUT_MS));
++ if (completion_result <= 0) {
++ if (0 == completion_result) {
++ hailo_err(board, "hailo_nnc_driver_down, timeout waiting for shutdown response (timeout_ms=%d)\n", DEFAULT_SHUTDOWN_TIMEOUT_MS);
++ err = -ETIMEDOUT;
++ } else {
++ hailo_info(board, "hailo_nnc_driver_down, wait for completion failed with err=%ld (process was interrupted or killed)\n",
++ completion_result);
++ err = completion_result;
++ }
++ goto l_exit;
++ }
++
++l_exit:
++ return err;
++}
++
++void hailo_nnc_file_context_finalize(struct hailo_pcie_board *board, struct hailo_file_context *context)
++{
++ clear_notification_wait_list(board, context->filp);
++
++ if (context->filp == board->vdma.used_by_filp) {
++ hailo_nnc_driver_down(board);
++ }
++}
+\ No newline at end of file
+--- /dev/null
++++ b/drivers/media/pci/hailo/src/nnc.h
+@@ -0,0 +1,22 @@
++// SPDX-License-Identifier: GPL-2.0
++/**
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
++ **/
++
++#ifndef _HAILO_PCI_NNC_H_
++#define _HAILO_PCI_NNC_H_
++
++#include "pcie.h"
++
++void hailo_nnc_init(struct hailo_pcie_nnc *nnc);
++void hailo_nnc_finalize(struct hailo_pcie_nnc *nnc);
++
++long hailo_nnc_ioctl(struct hailo_pcie_board *board, unsigned int cmd, unsigned long arg,
++ struct file *filp, bool *should_up_board_mutex);
++
++int hailo_nnc_file_context_init(struct hailo_pcie_board *board, struct hailo_file_context *context);
++void hailo_nnc_file_context_finalize(struct hailo_pcie_board *board, struct hailo_file_context *context);
++
++int hailo_nnc_driver_down(struct hailo_pcie_board *board);
++
++#endif /* _HAILO_PCI_NNC_H_ */
+\ No newline at end of file
+--- a/drivers/media/pci/hailo/src/pci_soc_ioctl.c
++++ /dev/null
+@@ -1,155 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-/**
+- * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+- **/
+-#include "pci_soc_ioctl.h"
+-
+-#include "utils.h"
+-#include "vdma_common.h"
+-#include "utils/logs.h"
+-#include "vdma/memory.h"
+-
+-#define PCI_SOC_VDMA_ENGINE_INDEX (0)
+-#define PCI_SOC_WAIT_FOR_CONNECT_TIMEOUT_MS (10000)
+-
+-long hailo_soc_ioctl(struct hailo_pcie_board *board, struct hailo_vdma_file_context *context,
+- struct hailo_vdma_controller *controller, unsigned int cmd, unsigned long arg)
+-{
+- switch (cmd) {
+- case HAILO_SOC_CONNECT:
+- return hailo_soc_connect_ioctl(board, context, controller, arg);
+- case HAILO_SOC_CLOSE:
+- return hailo_soc_close_ioctl(board, controller, arg);
+- default:
+- hailo_err(board, "Invalid pcie EP ioctl code 0x%x (nr: %d)\n", cmd, _IOC_NR(cmd));
+- return -ENOTTY;
+- }
+-}
+-
+-long hailo_soc_connect_ioctl(struct hailo_pcie_board *board, struct hailo_vdma_file_context *context,
+- struct hailo_vdma_controller *controller, unsigned long arg)
+-{
+- struct hailo_soc_connect_params params;
+- struct hailo_vdma_channel *input_channel = NULL;
+- struct hailo_vdma_channel *output_channel = NULL;
+- struct hailo_vdma_engine *vdma_engine = NULL;
+- struct hailo_descriptors_list_buffer *input_descriptors_buffer = NULL;
+- struct hailo_descriptors_list_buffer *output_descriptors_buffer = NULL;
+- uint8_t depth = 0;
+- int err = 0;
+- long completion_result = 0;
+-
+- if (copy_from_user(¶ms, (void *)arg, sizeof(params))) {
+- hailo_err(board, "copy_from_user fail\n");
+- return -ENOMEM;
+- }
+-
+- // TODO: have pci_ep choose the channel indexes the soc will use - for now use 0 and 16
+- params.input_channel_index = 0;
+- params.output_channel_index = 16;
+-
+- reinit_completion(&board->soc_connect_accepted);
+- hailo_soc_write_soc_connect(&board->pcie_resources);
+-
+- // Wait for completion
+- completion_result = wait_for_completion_interruptible_timeout(&board->soc_connect_accepted,
+- msecs_to_jiffies(PCI_SOC_WAIT_FOR_CONNECT_TIMEOUT_MS));
+- if (0 > completion_result) {
+- if (0 == completion_result) {
+- hailo_err(board, "Timeout waiting for connect to be accepted (timeout_ms=%d)\n", PCI_SOC_WAIT_FOR_CONNECT_TIMEOUT_MS);
+- return -ETIMEDOUT;
+- } else {
+- hailo_info(board, "soc connect failed with err=%ld (process was interrupted or killed)\n",
+- completion_result);
+- return -EINTR;
+- }
+- }
+-
+- vdma_engine = &controller->vdma_engines[PCI_SOC_VDMA_ENGINE_INDEX];
+- input_channel = &vdma_engine->channels[params.input_channel_index];
+- output_channel = &vdma_engine->channels[params.output_channel_index];
+-
+- input_descriptors_buffer = hailo_vdma_find_descriptors_buffer(context, params.input_desc_handle);
+- output_descriptors_buffer = hailo_vdma_find_descriptors_buffer(context, params.output_desc_handle);
+- if (NULL == input_descriptors_buffer || NULL == output_descriptors_buffer) {
+- hailo_dev_err(&board->pDev->dev, "input / output descriptors buffer not found \n");
+- return -EINVAL;
+- }
+-
+- // Make sure channels that we are accepting are not already enabled
+- if (0 != (vdma_engine->enabled_channels & params.input_channel_index) ||
+- 0 != (vdma_engine->enabled_channels & params.output_channel_index)) {
+- hailo_dev_err(&board->pDev->dev, "Trying to accept already enabled channels\n");
+- return -EINVAL;
+- }
+-
+- if (!is_powerof2((size_t)input_descriptors_buffer->desc_list.desc_count) ||
+- !is_powerof2((size_t)output_descriptors_buffer->desc_list.desc_count)) {
+- hailo_dev_err(&board->pDev->dev, "Invalid desc list size\n");
+- return -EINVAL;
+- }
+-
+- // configure and start input channel
+- depth = ceil_log2(input_descriptors_buffer->desc_list.desc_count);
+- // DMA Direction is only to get channel index - so
+- err = hailo_vdma_start_channel(input_channel->host_regs, input_descriptors_buffer->dma_address, depth,
+- board->vdma.hw->ddr_data_id);
+- if (err < 0) {
+- hailo_dev_err(&board->pDev->dev, "Error starting vdma input channel index %u\n", params.input_channel_index);
+- return -EINVAL;
+- }
+-
+- // configure and start output channel
+- depth = ceil_log2(output_descriptors_buffer->desc_list.desc_count);
+- // DMA Direction is only to get channel index - so
+- err = hailo_vdma_start_channel(output_channel->host_regs, output_descriptors_buffer->dma_address, depth,
+- board->vdma.hw->ddr_data_id);
+- if (err < 0) {
+- hailo_dev_err(&board->pDev->dev, "Error starting vdma output channel index %u\n", params.output_channel_index);
+- // Close input channel
+- hailo_vdma_stop_channel(input_channel->host_regs);
+- return -EINVAL;
+- }
+-
+- if (copy_to_user((void *)arg, ¶ms, sizeof(params))) {
+- hailo_dev_err(&board->pDev->dev, "copy_to_user fail\n");
+- return -ENOMEM;
+- }
+-
+- return 0;
+-}
+-
+-long hailo_soc_close_ioctl(struct hailo_pcie_board *board, struct hailo_vdma_controller *controller, unsigned long arg)
+-{
+- struct hailo_soc_close_params params;
+- struct hailo_vdma_channel *input_channel = NULL;
+- struct hailo_vdma_channel *output_channel = NULL;
+- struct hailo_vdma_engine *vdma_engine = NULL;
+-
+- if (copy_from_user(¶ms, (void *)arg, sizeof(params))) {
+- hailo_dev_err(&board->pDev->dev, "copy_from_user fail\n");
+- return -ENOMEM;
+- }
+-
+- vdma_engine = &controller->vdma_engines[PCI_SOC_VDMA_ENGINE_INDEX];
+-
+- if (!hailo_check_channel_index(params.input_channel_index, controller->hw->src_channels_bitmask, true)) {
+- hailo_dev_err(&board->pDev->dev, "Invalid input channel index %u\n", params.input_channel_index);
+- return -EINVAL;
+- }
+-
+- if (!hailo_check_channel_index(params.output_channel_index, controller->hw->src_channels_bitmask, false)) {
+- hailo_dev_err(&board->pDev->dev, "Invalid output channel index %u\n", params.output_channel_index);
+- return -EINVAL;
+- }
+-
+- input_channel = &vdma_engine->channels[params.input_channel_index];
+- output_channel = &vdma_engine->channels[params.output_channel_index];
+-
+- // Close channels
+- hailo_vdma_stop_channel(input_channel->host_regs);
+- hailo_vdma_stop_channel(output_channel->host_regs);
+-
+- hailo_pcie_write_firmware_driver_shutdown(&board->pcie_resources);
+- return 0;
+-}
+\ No newline at end of file
+--- a/drivers/media/pci/hailo/src/pcie.c
++++ b/drivers/media/pci/hailo/src/pcie.c
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #include <linux/version.h>
+@@ -22,6 +22,8 @@
+
+ #include "hailo_ioctl_common.h"
+ #include "pcie.h"
++#include "nnc.h"
++#include "soc.h"
+ #include "fops.h"
+ #include "sysfs.h"
+ #include "utils/logs.h"
+@@ -40,11 +42,12 @@ enum hailo_allocate_driver_buffer_driver
+ HAILO_FORCE_BUFFER_FROM_DRIVER = 2,
+ };
+
+-//Debug flag
++// Debug flag
+ static int force_desc_page_size = 0;
+ static bool g_is_power_mode_enabled = true;
+ static int force_allocation_from_driver = HAILO_NO_FORCE_BUFFER;
+ static bool force_hailo15_legacy_mode = false;
++static bool force_boot_linux_from_eemc = false;
+
+ #define DEVICE_NODE_NAME "hailo"
+ static int char_major = 0;
+@@ -206,7 +209,7 @@ static int hailo_pcie_disable_aspm(struc
+ /* Double-check ASPM control. If not disabled by the above, the
+ * BIOS is preventing that from happening (or CONFIG_PCIEASPM is
+ * not enabled); override by writing PCI config space directly.
+- */
++ */
+ err = pcie_capability_read_word(pdev, PCI_EXP_LNKCTL, &pdev_aspmc);
+ if (err < 0) {
+ hailo_err(board, "Couldn't read LNKCTL capability\n");
+@@ -288,101 +291,59 @@ static void hailo_pcie_remove_board(stru
+ up(&g_hailo_add_board_mutex);
+ }
+
+-static int hailo_write_config(struct hailo_pcie_resources *resources, struct device *dev,
+- const struct hailo_config_constants *config_consts)
+-{
+- const struct firmware *config = NULL;
+- int err = 0;
+-
+- if (NULL == config_consts->filename) {
+- // Config not supported for platform
+- return 0;
+- }
+-
+- err = request_firmware_direct(&config, config_consts->filename, dev);
+- if (err < 0) {
+- hailo_dev_info(dev, "Config %s not found\n", config_consts->filename);
+- return 0;
+- }
+-
+- hailo_dev_notice(dev, "Writing config %s\n", config_consts->filename);
+-
+- err = hailo_pcie_write_config_common(resources, config->data, config->size, config_consts);
+- if (err < 0) {
+- if (-EINVAL == err) {
+- hailo_dev_warn(dev, "Config size %zu is bigger than max %zu\n", config->size, config_consts->max_size);
+- }
+- release_firmware(config);
+- return err;
+- }
+-
+- release_firmware(config);
+- return 0;
+-}
+-
+ static bool wait_for_firmware_completion(struct completion *fw_load_completion)
+ {
+ return (0 != wait_for_completion_timeout(fw_load_completion, msecs_to_jiffies(FIRMWARE_WAIT_TIMEOUT_MS)));
+ }
+
+-static int hailo_load_firmware(struct hailo_pcie_resources *resources,
++static int hailo_load_soc_firmware(struct hailo_pcie_resources *resources,
+ struct device *dev, struct completion *fw_load_completion)
+ {
+- const struct firmware *firmware = NULL;
+- int err = 0;
+ u32 boot_status = 0;
++ int err = 0;
++ u32 second_stage = force_boot_linux_from_eemc ? SECOND_STAGE_LINUX_IN_EMMC : SECOND_STAGE;
+
+ if (hailo_pcie_is_firmware_loaded(resources)) {
+- hailo_dev_warn(dev, "Firmware was already loaded\n");
++ hailo_dev_warn(dev, "Firmware batch was already loaded\n");
+ return 0;
+ }
+
+- reinit_completion(fw_load_completion);
+-
+- err = hailo_write_config(resources, dev, hailo_pcie_get_board_config_constants(resources->board_type));
+- if (err < 0) {
+- hailo_dev_err(dev, "Failed writing board config");
+- return err;
+- }
++ init_completion(fw_load_completion);
+
+- err = hailo_write_config(resources, dev, hailo_pcie_get_user_config_constants(resources->board_type));
++ err = hailo_pcie_write_firmware_batch(dev, resources, FIRST_STAGE);
+ if (err < 0) {
+- hailo_dev_err(dev, "Failed writing fw config");
++ hailo_dev_err(dev, "Failed writing firmware files. err %d\n", err);
+ return err;
+ }
+
+- // read firmware file
+- err = request_firmware_direct(&firmware, hailo_pcie_get_fw_filename(resources->board_type), dev);
+- if (err < 0) {
+- hailo_dev_warn(dev, "Firmware file not found (/lib/firmware/%s), please upload the firmware manually \n",
+- hailo_pcie_get_fw_filename(resources->board_type));
+- return 0;
++ if (!wait_for_firmware_completion(fw_load_completion)) {
++ boot_status = hailo_get_boot_status(resources);
++ hailo_dev_err(dev, "Timeout waiting for firmware file, boot status %u\n", boot_status);
++ return -ETIMEDOUT;
+ }
++ reinit_completion(fw_load_completion);
+
+- err = hailo_pcie_write_firmware(resources, firmware->data, firmware->size);
++ err = hailo_pcie_write_firmware_batch(dev, resources, second_stage);
+ if (err < 0) {
+- hailo_dev_err(dev, "Failed writing firmware. err %d\n", err);
+- release_firmware(firmware);
++ hailo_dev_err(dev, "Failed writing firmware files. err %d\n", err);
+ return err;
+ }
+
+- release_firmware(firmware);
+-
+ if (!wait_for_firmware_completion(fw_load_completion)) {
+ boot_status = hailo_get_boot_status(resources);
+ hailo_dev_err(dev, "Timeout waiting for firmware file, boot status %u\n", boot_status);
+ return -ETIMEDOUT;
+ }
+
+- hailo_dev_notice(dev, "Firmware was loaded successfully\n");
++ hailo_dev_notice(dev, "Firmware Batch loaded successfully\n");
++
+ return 0;
+ }
+
+-static int hailo_load_firmware_batch(struct hailo_pcie_resources *resources,
++static int hailo_load_nnc_firmware(struct hailo_pcie_resources *resources,
+ struct device *dev, struct completion *fw_load_completion)
+ {
+ u32 boot_status = 0;
+- u32 pcie_finished = 1;
+ int err = 0;
+
+ if (hailo_pcie_is_firmware_loaded(resources)) {
+@@ -398,31 +359,13 @@ static int hailo_load_firmware_batch(str
+ return err;
+ }
+
+- hailo_trigger_firmware_boot(resources);
+-
+ if (!wait_for_firmware_completion(fw_load_completion)) {
+ boot_status = hailo_get_boot_status(resources);
+ hailo_dev_err(dev, "Timeout waiting for firmware file, boot status %u\n", boot_status);
+ return -ETIMEDOUT;
+ }
+- reinit_completion(fw_load_completion);
+
+- err = hailo_pcie_write_firmware_batch(dev, resources, SECOND_STAGE);
+- if (err < 0) {
+- hailo_dev_err(dev, "Failed writing firmware files. err %d\n", err);
+- return err;
+- }
+-
+- // TODO: HRT-13838 - Remove, move address to compat, make write_memory static
+- write_memory(resources, 0x84000000, (void*)&pcie_finished, sizeof(pcie_finished));
+-
+- if (!wait_for_firmware_completion(fw_load_completion)) {
+- boot_status = hailo_get_boot_status(resources);
+- hailo_dev_err(dev, "Timeout waiting for firmware file, boot status %u\n", boot_status);
+- return -ETIMEDOUT;
+- }
+-
+- hailo_dev_notice(dev, "Firmware Batch loaded successfully\n");
++ hailo_dev_notice(dev, "Firmware loaded successfully\n");
+
+ return 0;
+ }
+@@ -439,15 +382,13 @@ static int hailo_activate_board(struct h
+ return err;
+ }
+
+- switch (board->pcie_resources.board_type) {
+- case HAILO_BOARD_TYPE_HAILO10H:
+- err = hailo_load_firmware_batch(&board->pcie_resources, &board->pDev->dev,
++ switch (board->pcie_resources.accelerator_type) {
++ case HAILO_ACCELERATOR_TYPE_SOC:
++ err = hailo_load_soc_firmware(&board->pcie_resources, &board->pDev->dev,
+ &board->fw_loaded_completion);
+ break;
+- case HAILO_BOARD_TYPE_HAILO10H_LEGACY:
+- case HAILO_BOARD_TYPE_PLUTO:
+- case HAILO_BOARD_TYPE_HAILO8:
+- err = hailo_load_firmware(&board->pcie_resources, &board->pDev->dev,
++ case HAILO_ACCELERATOR_TYPE_NNC:
++ err = hailo_load_nnc_firmware(&board->pcie_resources, &board->pDev->dev,
+ &board->fw_loaded_completion);
+ break;
+ default:
+@@ -464,6 +405,7 @@ static int hailo_activate_board(struct h
+
+ if (power_mode_enabled()) {
+ // Setting the device to low power state, until the user opens the device
++ hailo_info(board, "Power change state to PCI_D3hot\n");
+ err = pci_set_power_state(board->pDev, PCI_D3hot);
+ if (err < 0) {
+ hailo_err(board, "Set power state failed %d\n", err);
+@@ -755,21 +697,17 @@ static int hailo_pcie_probe(struct pci_d
+
+ pBoard->interrupts_enabled = false;
+ init_completion(&pBoard->fw_loaded_completion);
+- init_completion(&pBoard->soc_connect_accepted);
+
+ sema_init(&pBoard->mutex, 1);
+ atomic_set(&pBoard->ref_count, 0);
+ INIT_LIST_HEAD(&pBoard->open_files_list);
+
+- sema_init(&pBoard->fw_control.mutex, 1);
+- spin_lock_init(&pBoard->notification_read_spinlock);
+- init_completion(&pBoard->fw_control.completion);
++ // Init both soc and nnc, since the interrupts are shared.
++ hailo_nnc_init(&pBoard->nnc);
++ hailo_soc_init(&pBoard->soc);
+
+ init_completion(&pBoard->driver_down.reset_completed);
+
+- INIT_LIST_HEAD(&pBoard->notification_wait_list);
+-
+- memset(&pBoard->notification_cache, 0, sizeof(pBoard->notification_cache));
+ memset(&pBoard->memory_transfer_params, 0, sizeof(pBoard->memory_transfer_params));
+
+ err = hailo_pcie_vdma_controller_init(&pBoard->vdma, &pBoard->pDev->dev,
+@@ -832,7 +770,6 @@ probe_exit:
+ static void hailo_pcie_remove(struct pci_dev* pDev)
+ {
+ struct hailo_pcie_board* pBoard = (struct hailo_pcie_board*) pci_get_drvdata(pDev);
+- struct hailo_notification_wait *cursor = NULL;
+
+ pci_notice(pDev, "Remove: Releasing board\n");
+
+@@ -864,13 +801,7 @@ static void hailo_pcie_remove(struct pci
+
+ pci_set_drvdata(pDev, NULL);
+
+- // Lock rcu_read_lock and send notification_completion to wake anyone waiting on the notification_wait_list when removed
+- rcu_read_lock();
+- list_for_each_entry_rcu(cursor, &pBoard->notification_wait_list, notification_wait_list) {
+- cursor->is_disabled = true;
+- complete(&cursor->notification_completion);
+- }
+- rcu_read_unlock();
++ hailo_nnc_finalize(&pBoard->nnc);
+
+ up(&pBoard->mutex);
+
+@@ -889,6 +820,15 @@ static void hailo_pcie_remove(struct pci
+
+ }
+
++inline int driver_down(struct hailo_pcie_board *board)
++{
++ if (board->pcie_resources.accelerator_type == HAILO_ACCELERATOR_TYPE_NNC) {
++ return hailo_nnc_driver_down(board);
++ } else {
++ return hailo_soc_driver_down(board);
++ }
++}
++
+ #ifdef CONFIG_PM_SLEEP
+ static int hailo_pcie_suspend(struct device *dev)
+ {
+@@ -899,17 +839,16 @@ static int hailo_pcie_suspend(struct dev
+ // lock board to wait for any pending operations
+ down(&board->mutex);
+
+- // Disable all interrupts. All interrupts from Hailo chip would be masked.
+- hailo_disable_interrupts(board);
+-
+- // Close all vDMA channels
+ if (board->vdma.used_by_filp != NULL) {
+- err = hailo_pcie_driver_down(board);
++ err = driver_down(board);
+ if (err < 0) {
+ dev_notice(dev, "Error while trying to call FW to close vdma channels\n");
+ }
+ }
+
++ // Disable all interrupts. All interrupts from Hailo chip would be masked.
++ hailo_disable_interrupts(board);
++
+ // Un validate all activae file contexts so every new action would return error to the user.
+ list_for_each_entry(cur, &board->open_files_list, open_files_list) {
+ cur->is_valid = false;
+@@ -919,8 +858,8 @@ static int hailo_pcie_suspend(struct dev
+ up(&board->mutex);
+
+ dev_notice(dev, "PM's suspend\n");
+- // Continue system suspend
+- return err;
++ // Success Oriented - Continue system suspend even in case of error (otherwise system will not suspend correctly)
++ return 0;
+ }
+
+ static int hailo_pcie_resume(struct device *dev)
+@@ -930,10 +869,10 @@ static int hailo_pcie_resume(struct devi
+
+ if ((err = hailo_activate_board(board)) < 0) {
+ dev_err(dev, "Failed activating board %d\n", err);
+- return err;
+ }
+
+ dev_notice(dev, "PM's resume\n");
++ // Success Oriented - Continue system resume even in case of error (otherwise system will not suspend correctly)
+ return 0;
+ }
+ #endif /* CONFIG_PM_SLEEP */
+@@ -954,7 +893,7 @@ static void hailo_pci_reset_prepare(stru
+ down(&board->mutex);
+ if (board->vdma.used_by_filp != NULL) {
+ // Try to close all vDMA channels before reset
+- err = hailo_pcie_driver_down(board);
++ err = driver_down(board);
+ if (err < 0) {
+ pci_err(pdev, "Error while trying to call FW to close vdma channels (errno %d)\n", err);
+ }
+@@ -1088,6 +1027,9 @@ MODULE_PARM_DESC(force_desc_page_size, "
+ module_param(force_hailo15_legacy_mode, bool, S_IRUGO);
+ MODULE_PARM_DESC(force_hailo15_legacy_mode, "Forces work with Hailo15 in legacy mode(relevant for emulators)");
+
++module_param(force_boot_linux_from_eemc, bool, S_IRUGO);
++MODULE_PARM_DESC(force_boot_linux_from_eemc, "Boot the linux image from eemc (Requires special Image)");
++
+ MODULE_AUTHOR("Hailo Technologies Ltd.");
+ MODULE_DESCRIPTION("Hailo PCIe driver");
+ MODULE_LICENSE("GPL v2");
+--- a/drivers/media/pci/hailo/src/pcie.h
++++ b/drivers/media/pci/hailo/src/pcie.h
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #ifndef _HAILO_PCI_PCIE_H_
+@@ -41,6 +41,19 @@ struct hailo_fw_boot {
+ };
+
+
++struct hailo_pcie_nnc {
++ struct hailo_fw_control_info fw_control;
++
++ spinlock_t notification_read_spinlock;
++ struct list_head notification_wait_list;
++ struct hailo_d2h_notification notification_cache;
++ struct hailo_d2h_notification notification_to_user;
++};
++
++struct hailo_pcie_soc {
++ struct completion control_resp_ready;
++};
++
+ // Context for each open file handle
+ // TODO: store board and use as actual context
+ struct hailo_file_context {
+@@ -48,6 +61,7 @@ struct hailo_file_context {
+ struct file *filp;
+ struct hailo_vdma_file_context vdma_context;
+ bool is_valid;
++ u32 soc_used_channels_bitmap;
+ };
+
+ struct hailo_pcie_board {
+@@ -57,21 +71,17 @@ struct hailo_pcie_board {
+ atomic_t ref_count;
+ struct list_head open_files_list;
+ struct hailo_pcie_resources pcie_resources;
+- struct hailo_fw_control_info fw_control;
++ struct hailo_pcie_nnc nnc;
++ struct hailo_pcie_soc soc;
+ struct hailo_pcie_driver_down_info driver_down;
+ struct semaphore mutex;
+ struct hailo_vdma_controller vdma;
+- spinlock_t notification_read_spinlock;
+- struct list_head notification_wait_list;
+- struct hailo_d2h_notification notification_cache;
+- struct hailo_d2h_notification notification_to_user;
++
+ struct hailo_memory_transfer_params memory_transfer_params;
+ u32 desc_max_page_size;
+ enum hailo_allocation_mode allocation_mode;
+ struct completion fw_loaded_completion;
+ bool interrupts_enabled;
+- // Only needed in accelerator type soc
+- struct completion soc_connect_accepted;
+ };
+
+ bool power_mode_enabled(void);
+--- /dev/null
++++ b/drivers/media/pci/hailo/src/soc.c
+@@ -0,0 +1,244 @@
++// SPDX-License-Identifier: GPL-2.0
++/**
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
++ **/
++/**
++ * A Hailo PCIe NNC device is a device contains a full SoC over PCIe. The SoC contains NNC (neural network core) and
++ * some application processor (pci_ep).
++ */
++
++#include "soc.h"
++
++#include "vdma_common.h"
++#include "utils/logs.h"
++#include "vdma/memory.h"
++
++#include <linux/uaccess.h>
++
++#define PCI_SOC_VDMA_ENGINE_INDEX (0)
++#define PCI_SOC_CONTROL_CONNECT_TIMEOUT_MS (1000)
++#define PCI_SOC_INPUT_CHANNEL_BITMASK (0x000000FF)
++
++void hailo_soc_init(struct hailo_pcie_soc *soc)
++{
++ init_completion(&soc->control_resp_ready);
++}
++
++long hailo_soc_ioctl(struct hailo_pcie_board *board, struct hailo_file_context *context,
++ struct hailo_vdma_controller *controller, unsigned int cmd, unsigned long arg)
++{
++ switch (cmd) {
++ case HAILO_SOC_CONNECT:
++ return hailo_soc_connect_ioctl(board, context, controller, arg);
++ case HAILO_SOC_CLOSE:
++ return hailo_soc_close_ioctl(board, controller, context, arg);
++ default:
++ hailo_err(board, "Invalid pcie EP ioctl code 0x%x (nr: %d)\n", cmd, _IOC_NR(cmd));
++ return -ENOTTY;
++ }
++}
++
++static int soc_control(struct hailo_pcie_board *board,
++ const struct hailo_pcie_soc_request *request,
++ struct hailo_pcie_soc_response *response)
++{
++ int ret = 0;
++ reinit_completion(&board->soc.control_resp_ready);
++
++ hailo_pcie_soc_write_request(&board->pcie_resources, request);
++
++ ret = wait_for_completion_interruptible_timeout(&board->soc.control_resp_ready,
++ msecs_to_jiffies(PCI_SOC_CONTROL_CONNECT_TIMEOUT_MS));
++ if (ret <= 0) {
++ if (0 == ret) {
++ hailo_err(board, "Timeout waiting for soc control (timeout_ms=%d)\n", PCI_SOC_CONTROL_CONNECT_TIMEOUT_MS);
++ return -ETIMEDOUT;
++ } else {
++ hailo_info(board, "soc control failed with err=%d (process was interrupted or killed)\n",
++ ret);
++ return ret;
++ }
++ }
++
++ hailo_pcie_soc_read_response(&board->pcie_resources, response);
++
++ if (response->status < 0) {
++ hailo_err(board, "soc control failed with status=%d\n", response->status);
++ return response->status;
++ }
++
++ if (response->control_code != request->control_code) {
++ hailo_err(board, "Invalid response control code %d (expected %d)\n",
++ response->control_code, request->control_code);
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++long hailo_soc_connect_ioctl(struct hailo_pcie_board *board, struct hailo_file_context *context,
++ struct hailo_vdma_controller *controller, unsigned long arg)
++{
++ struct hailo_pcie_soc_request request = {0};
++ struct hailo_pcie_soc_response response = {0};
++ struct hailo_soc_connect_params params;
++ struct hailo_vdma_channel *input_channel = NULL;
++ struct hailo_vdma_channel *output_channel = NULL;
++ struct hailo_vdma_engine *vdma_engine = &controller->vdma_engines[PCI_SOC_VDMA_ENGINE_INDEX];
++ struct hailo_descriptors_list_buffer *input_descriptors_buffer = NULL;
++ struct hailo_descriptors_list_buffer *output_descriptors_buffer = NULL;
++ uint8_t depth = 0;
++ int err = 0;
++
++ if (copy_from_user(¶ms, (void *)arg, sizeof(params))) {
++ hailo_err(board, "copy_from_user fail\n");
++ return -ENOMEM;
++ }
++
++ request = (struct hailo_pcie_soc_request) {
++ .control_code = HAILO_PCIE_SOC_CONTROL_CODE_CONNECT,
++ .connect = {
++ .port = params.port_number
++ }
++ };
++ err = soc_control(board, &request, &response);
++ if (err < 0) {
++ return err;
++ }
++
++ params.input_channel_index = response.connect.input_channel_index;
++ params.output_channel_index = response.connect.output_channel_index;
++
++ if (!hailo_check_channel_index(params.input_channel_index, controller->hw->src_channels_bitmask, true)) {
++ hailo_dev_err(&board->pDev->dev, "Invalid input channel index %u\n", params.input_channel_index);
++ return -EINVAL;
++ }
++
++ if (!hailo_check_channel_index(params.output_channel_index, controller->hw->src_channels_bitmask, false)) {
++ hailo_dev_err(&board->pDev->dev, "Invalid output channel index %u\n", params.output_channel_index);
++ return -EINVAL;
++ }
++
++ input_channel = &vdma_engine->channels[params.input_channel_index];
++ output_channel = &vdma_engine->channels[params.output_channel_index];
++
++ input_descriptors_buffer = hailo_vdma_find_descriptors_buffer(&context->vdma_context, params.input_desc_handle);
++ output_descriptors_buffer = hailo_vdma_find_descriptors_buffer(&context->vdma_context, params.output_desc_handle);
++ if (NULL == input_descriptors_buffer || NULL == output_descriptors_buffer) {
++ hailo_dev_err(&board->pDev->dev, "input / output descriptors buffer not found \n");
++ return -EINVAL;
++ }
++
++ if (!is_powerof2((size_t)input_descriptors_buffer->desc_list.desc_count) ||
++ !is_powerof2((size_t)output_descriptors_buffer->desc_list.desc_count)) {
++ hailo_dev_err(&board->pDev->dev, "Invalid desc list size\n");
++ return -EINVAL;
++ }
++
++ // configure and start input channel
++ depth = ceil_log2(input_descriptors_buffer->desc_list.desc_count);
++ // DMA Direction is only to get channel index - so
++ err = hailo_vdma_start_channel(input_channel->host_regs, input_descriptors_buffer->dma_address, depth,
++ board->vdma.hw->ddr_data_id);
++ if (err < 0) {
++ hailo_dev_err(&board->pDev->dev, "Error starting vdma input channel index %u\n", params.input_channel_index);
++ return -EINVAL;
++ }
++
++ // Store the input channels state in bitmap (open)
++ hailo_set_bit(params.input_channel_index, &context->soc_used_channels_bitmap);
++
++ // configure and start output channel
++ depth = ceil_log2(output_descriptors_buffer->desc_list.desc_count);
++ // DMA Direction is only to get channel index - so
++ err = hailo_vdma_start_channel(output_channel->host_regs, output_descriptors_buffer->dma_address, depth,
++ board->vdma.hw->ddr_data_id);
++ if (err < 0) {
++ hailo_dev_err(&board->pDev->dev, "Error starting vdma output channel index %u\n", params.output_channel_index);
++ // Close input channel
++ hailo_vdma_stop_channel(input_channel->host_regs);
++ return -EINVAL;
++ }
++
++ // Store the output channels state in bitmap (open)
++ hailo_set_bit(params.output_channel_index, &context->soc_used_channels_bitmap);
++
++ if (copy_to_user((void *)arg, ¶ms, sizeof(params))) {
++ hailo_dev_err(&board->pDev->dev, "copy_to_user fail\n");
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++
++static int close_channels(struct hailo_pcie_board *board, u32 channels_bitmap)
++{
++ struct hailo_pcie_soc_request request = {0};
++ struct hailo_pcie_soc_response response = {0};
++ struct hailo_vdma_engine *engine = &board->vdma.vdma_engines[PCI_SOC_VDMA_ENGINE_INDEX];
++ struct hailo_vdma_channel *channel = NULL;
++ u8 channel_index = 0;
++
++ hailo_info(board, "Closing channels bitmap 0x%x\n", channels_bitmap);
++ for_each_vdma_channel(engine, channel, channel_index) {
++ if (hailo_test_bit(channel_index, &channels_bitmap)) {
++ hailo_vdma_stop_channel(channel->host_regs);
++ }
++ }
++
++ request = (struct hailo_pcie_soc_request) {
++ .control_code = HAILO_PCIE_SOC_CONTROL_CODE_CLOSE,
++ .close = {
++ .channels_bitmap = channels_bitmap
++ }
++ };
++ return soc_control(board, &request, &response);
++}
++
++long hailo_soc_close_ioctl(struct hailo_pcie_board *board, struct hailo_vdma_controller *controller,
++ struct hailo_file_context *context, unsigned long arg)
++{
++ struct hailo_soc_close_params params;
++ u32 channels_bitmap = 0;
++ int err = 0;
++
++ if (copy_from_user(¶ms, (void *)arg, sizeof(params))) {
++ hailo_dev_err(&board->pDev->dev, "copy_from_user fail\n");
++ return -ENOMEM;
++ }
++
++ // TOOD: check channels are connected
++
++ channels_bitmap = (1 << params.input_channel_index) | (1 << params.output_channel_index);
++
++ err = close_channels(board, channels_bitmap);
++ if (0 != err) {
++ hailo_dev_err(&board->pDev->dev, "Error closing channels\n");
++ return err;
++ }
++
++ // Store the channel state in bitmap (closed)
++ hailo_clear_bit(params.input_channel_index, &context->soc_used_channels_bitmap);
++ hailo_clear_bit(params.output_channel_index, &context->soc_used_channels_bitmap);
++
++ return err;
++}
++
++int hailo_soc_file_context_init(struct hailo_pcie_board *board, struct hailo_file_context *context)
++{
++ // Nothing to init yet
++ return 0;
++}
++
++void hailo_soc_file_context_finalize(struct hailo_pcie_board *board, struct hailo_file_context *context)
++{
++ // close only channels connected by this (by bitmap)
++ if (context->soc_used_channels_bitmap != 0) {
++ close_channels(board, context->soc_used_channels_bitmap);
++ }
++}
++
++int hailo_soc_driver_down(struct hailo_pcie_board *board)
++{
++ return close_channels(board, 0xFFFFFFFF);
++}
+\ No newline at end of file
+--- a/drivers/media/pci/hailo/src/pci_soc_ioctl.h
++++ /dev/null
+@@ -1,19 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-/**
+- * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+- **/
+-
+-#ifndef _HAILO_PCI_SOC_IOCTL_H_
+-#define _HAILO_PCI_SOC_IOCTL_H_
+-
+-#include "vdma/ioctl.h"
+-#include "pcie.h"
+-
+-
+-long hailo_soc_ioctl(struct hailo_pcie_board *board, struct hailo_vdma_file_context *context,
+- struct hailo_vdma_controller *controller, unsigned int cmd, unsigned long arg);
+-long hailo_soc_connect_ioctl(struct hailo_pcie_board *board, struct hailo_vdma_file_context *context,
+- struct hailo_vdma_controller *controller, unsigned long arg);
+-long hailo_soc_close_ioctl(struct hailo_pcie_board *board, struct hailo_vdma_controller *controller, unsigned long arg);
+-
+-#endif // _HAILO_PCI_SOC_IOCTL_H_
+\ No newline at end of file
+--- /dev/null
++++ b/drivers/media/pci/hailo/src/soc.h
+@@ -0,0 +1,26 @@
++// SPDX-License-Identifier: GPL-2.0
++/**
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
++ **/
++
++#ifndef _HAILO_PCI_SOC_IOCTL_H_
++#define _HAILO_PCI_SOC_IOCTL_H_
++
++#include "vdma/ioctl.h"
++#include "pcie.h"
++
++
++void hailo_soc_init(struct hailo_pcie_soc *soc);
++
++long hailo_soc_ioctl(struct hailo_pcie_board *board, struct hailo_file_context *context,
++ struct hailo_vdma_controller *controller, unsigned int cmd, unsigned long arg);
++long hailo_soc_connect_ioctl(struct hailo_pcie_board *board, struct hailo_file_context *context,
++ struct hailo_vdma_controller *controller, unsigned long arg);
++long hailo_soc_close_ioctl(struct hailo_pcie_board *board, struct hailo_vdma_controller *controller, struct hailo_file_context *context, unsigned long arg);
++
++int hailo_soc_file_context_init(struct hailo_pcie_board *board, struct hailo_file_context *context);
++void hailo_soc_file_context_finalize(struct hailo_pcie_board *board, struct hailo_file_context *context);
++
++int hailo_soc_driver_down(struct hailo_pcie_board *board);
++
++#endif // _HAILO_PCI_SOC_IOCTL_H_
+\ No newline at end of file
+--- a/drivers/media/pci/hailo/src/sysfs.c
++++ b/drivers/media/pci/hailo/src/sysfs.c
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #include "sysfs.h"
+--- a/drivers/media/pci/hailo/src/sysfs.h
++++ b/drivers/media/pci/hailo/src/sysfs.h
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #ifndef _HAILO_PCI_SYSFS_H_
+--- a/drivers/media/pci/hailo/src/utils.c
++++ /dev/null
+@@ -1,26 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-/**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
+- **/
+-
+-#include <linux/version.h>
+-#include <linux/init.h>
+-#include <linux/module.h>
+-#include <linux/pci.h>
+-
+-#include "pcie.h"
+-#include "utils.h"
+-#include "utils/logs.h"
+-
+-
+-void hailo_pcie_clear_notification_wait_list(struct hailo_pcie_board *pBoard, struct file *filp)
+-{
+- struct hailo_notification_wait *cur = NULL, *next = NULL;
+- list_for_each_entry_safe(cur, next, &pBoard->notification_wait_list, notification_wait_list) {
+- if (cur->filp == filp) {
+- list_del_rcu(&cur->notification_wait_list);
+- synchronize_rcu();
+- kfree(cur);
+- }
+- }
+-}
+--- a/drivers/media/pci/hailo/utils/compact.h
++++ b/drivers/media/pci/hailo/utils/compact.h
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #ifndef _HAILO_PCI_COMPACT_H_
+--- a/drivers/media/pci/hailo/utils/fw_common.h
++++ b/drivers/media/pci/hailo/utils/fw_common.h
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #ifndef _HAILO_LINUX_COMMON_H_
+--- a/drivers/media/pci/hailo/utils/integrated_nnc_utils.c
++++ b/drivers/media/pci/hailo/utils/integrated_nnc_utils.c
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #include "integrated_nnc_utils.h"
+@@ -43,11 +43,19 @@ int hailo_ioremap_shmem(struct platform_
+ void __iomem * remap_ptr;
+
+ shmem = of_parse_phandle(pdev->dev.of_node, "shmem", index);
++ if (!shmem) {
++ hailo_dev_err(&pdev->dev, "Failed to find shmem node index: %d in device tree\n", index);
++ return -ENODEV;
++ }
++
+ ret = of_address_to_resource(shmem, 0, &res);
+ if (ret) {
+ hailo_dev_err(&pdev->dev, "hailo_ioremap_shmem, failed to get memory (index: %d)\n", index);
++ of_node_put(shmem);
+ return ret;
+ }
++
++ // Decrement the refcount of the node
+ of_node_put(shmem);
+
+ remap_ptr = devm_ioremap(&pdev->dev, res.start, resource_size(&res));
+--- a/drivers/media/pci/hailo/utils/integrated_nnc_utils.h
++++ b/drivers/media/pci/hailo/utils/integrated_nnc_utils.h
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #ifndef _INTEGRATED_NNC_UTILS_H_
+--- a/drivers/media/pci/hailo/utils/logs.c
++++ b/drivers/media/pci/hailo/utils/logs.c
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #include "logs.h"
+--- a/drivers/media/pci/hailo/utils/logs.h
++++ b/drivers/media/pci/hailo/utils/logs.h
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #ifndef _COMMON_LOGS_H_
+--- a/drivers/media/pci/hailo/vdma/ioctl.c
++++ b/drivers/media/pci/hailo/vdma/ioctl.c
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #include "ioctl.h"
+@@ -12,7 +12,7 @@
+ #include <linux/uaccess.h>
+
+
+-long hailo_vdma_enable_channels_ioctl(struct hailo_vdma_controller *controller, unsigned long arg)
++long hailo_vdma_enable_channels_ioctl(struct hailo_vdma_controller *controller, unsigned long arg, struct hailo_vdma_file_context *context)
+ {
+ struct hailo_vdma_enable_channels_params input;
+ struct hailo_vdma_engine *engine = NULL;
+@@ -40,12 +40,15 @@ long hailo_vdma_enable_channels_ioctl(st
+ hailo_vdma_update_interrupts_mask(controller, engine_index);
+ hailo_dev_info(controller->dev, "Enabled interrupts for engine %u, channels bitmap 0x%x\n",
+ engine_index, channels_bitmap);
++
++ // Update the context with the enabled channels bitmap
++ context->enabled_channels_bitmap[engine_index] |= channels_bitmap;
+ }
+
+ return 0;
+ }
+
+-long hailo_vdma_disable_channels_ioctl(struct hailo_vdma_controller *controller, unsigned long arg)
++long hailo_vdma_disable_channels_ioctl(struct hailo_vdma_controller *controller, unsigned long arg, struct hailo_vdma_file_context *context)
+ {
+ struct hailo_vdma_disable_channels_params input;
+ struct hailo_vdma_engine *engine = NULL;
+@@ -77,6 +80,9 @@ long hailo_vdma_disable_channels_ioctl(s
+
+ hailo_dev_info(controller->dev, "Disabled channels for engine %u, bitmap 0x%x\n",
+ engine_index, channels_bitmap);
++
++ // Update the context with the disabled channels bitmap
++ context->enabled_channels_bitmap[engine_index] &= ~channels_bitmap;
+ }
+
+ // Wake up threads waiting
+@@ -204,7 +210,7 @@ long hailo_vdma_buffer_map_ioctl(struct
+ return -EFAULT;
+ }
+
+- hailo_dev_info(controller->dev, "address %lx tgid %d size: %zu\n",
++ hailo_dev_dbg(controller->dev, "address %lx tgid %d size: %zu\n",
+ buf_info.user_address, current->tgid, buf_info.size);
+
+ direction = get_dma_direction(buf_info.data_direction);
+@@ -231,7 +237,7 @@ long hailo_vdma_buffer_map_ioctl(struct
+ }
+
+ list_add(&mapped_buffer->mapped_user_buffer_list, &context->mapped_user_buffer_list);
+- hailo_dev_info(controller->dev, "buffer %lx (handle %zu) is mapped\n",
++ hailo_dev_dbg(controller->dev, "buffer %lx (handle %zu) is mapped\n",
+ buf_info.user_address, buf_info.mapped_handle);
+ return 0;
+ }
+@@ -247,7 +253,7 @@ long hailo_vdma_buffer_unmap_ioctl(struc
+ return -EFAULT;
+ }
+
+- hailo_dev_info(controller->dev, "unmap user buffer handle %zu\n", buffer_unmap_params.mapped_handle);
++ hailo_dev_dbg(controller->dev, "unmap user buffer handle %zu\n", buffer_unmap_params.mapped_handle);
+
+ mapped_buffer = hailo_vdma_find_mapped_user_buffer(context, buffer_unmap_params.mapped_handle);
+ if (mapped_buffer == NULL) {
+--- a/drivers/media/pci/hailo/vdma/ioctl.h
++++ b/drivers/media/pci/hailo/vdma/ioctl.h
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #ifndef _HAILO_VDMA_IOCTL_H_
+@@ -8,8 +8,8 @@
+
+ #include "vdma/vdma.h"
+
+-long hailo_vdma_enable_channels_ioctl(struct hailo_vdma_controller *controller, unsigned long arg);
+-long hailo_vdma_disable_channels_ioctl(struct hailo_vdma_controller *controller, unsigned long arg);
++long hailo_vdma_enable_channels_ioctl(struct hailo_vdma_controller *controller, unsigned long arg, struct hailo_vdma_file_context *context);
++long hailo_vdma_disable_channels_ioctl(struct hailo_vdma_controller *controller, unsigned long arg, struct hailo_vdma_file_context *context);
+ long hailo_vdma_interrupts_wait_ioctl(struct hailo_vdma_controller *controller, unsigned long arg,
+ struct semaphore *mutex, bool *should_up_board_mutex);
+
+--- a/drivers/media/pci/hailo/vdma/memory.c
++++ b/drivers/media/pci/hailo/vdma/memory.c
+@@ -1,11 +1,12 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #define pr_fmt(fmt) "hailo: " fmt
+
+ #include "memory.h"
++#include "utils.h"
+ #include "utils/compact.h"
+
+ #include <linux/slab.h>
+@@ -316,6 +317,11 @@ int hailo_desc_list_create(struct device
+ size_t buffer_size = 0;
+ const u64 align = VDMA_DESCRIPTOR_LIST_ALIGN; //First addr must be aligned on 64 KB (from the VDMA registers documentation)
+
++ if (MAX_POWER_OF_2_VALUE < descriptors_count) {
++ dev_err(dev, "Invalid descriptors count %u\n", descriptors_count);
++ return -EINVAL;
++ }
++
+ buffer_size = descriptors_count * sizeof(struct hailo_vdma_descriptor);
+ buffer_size = ALIGN(buffer_size, align);
+
+@@ -323,7 +329,7 @@ int hailo_desc_list_create(struct device
+ &descriptors->dma_address, GFP_KERNEL | __GFP_ZERO);
+ if (descriptors->kernel_address == NULL) {
+ dev_err(dev, "Failed to allocate descriptors list, desc_count 0x%x, buffer_size 0x%zx, This failure means there is not a sufficient amount of CMA memory "
+- "(contiguous physical memory), This usually is caused by lack of general system memory. Please check you have sufficent memory.\n",
++ "(contiguous physical memory), This usually is caused by lack of general system memory. Please check you have sufficient memory.\n",
+ descriptors_count, buffer_size);
+ return -ENOMEM;
+ }
+@@ -333,6 +339,8 @@ int hailo_desc_list_create(struct device
+
+ descriptors->desc_list.desc_list = descriptors->kernel_address;
+ descriptors->desc_list.desc_count = descriptors_count;
++ // No need to check the return value of get_nearest_powerof_2 because we already checked the input
++ descriptors->desc_list.desc_count_mask = is_circular ? (descriptors_count - 1) : (get_nearest_powerof_2(descriptors_count) - 1);
+ descriptors->desc_list.desc_page_size = desc_page_size;
+ descriptors->desc_list.is_circular = is_circular;
+
+--- a/drivers/media/pci/hailo/vdma/memory.h
++++ b/drivers/media/pci/hailo/vdma/memory.h
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+ /**
+ * vDMA memory utility (including allocation and mappings)
+--- a/drivers/media/pci/hailo/vdma/vdma.c
++++ b/drivers/media/pci/hailo/vdma/vdma.c
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+
+ #define pr_fmt(fmt) "hailo: " fmt
+@@ -105,6 +105,9 @@ void hailo_vdma_file_context_init(struct
+ INIT_LIST_HEAD(&context->descriptors_buffer_list);
+ INIT_LIST_HEAD(&context->vdma_low_memory_buffer_list);
+ INIT_LIST_HEAD(&context->continuous_buffer_list);
++
++ BUILD_BUG_ON_MSG(MAX_VDMA_CHANNELS_PER_ENGINE > sizeof(context->enabled_channels_bitmap[0]) * BITS_IN_BYTE,
++ "Unexpected amount of VDMA channels per engine");
+ }
+
+ void hailo_vdma_update_interrupts_mask(struct hailo_vdma_controller *controller,
+@@ -119,21 +122,22 @@ void hailo_vdma_file_context_finalize(st
+ {
+ size_t engine_index = 0;
+ struct hailo_vdma_engine *engine = NULL;
+- const u32 channels_bitmap = 0xFFFFFFFF; // disable all channel interrupts
+ unsigned long irq_saved_flags = 0;
+ // In case of FLR, the vdma registers will be NULL
+ const bool is_device_up = (NULL != controller->dev);
+
+- if (filp == controller->used_by_filp) {
+- for_each_vdma_engine(controller, engine, engine_index) {
+- hailo_vdma_engine_disable_channels(engine, channels_bitmap);
++ for_each_vdma_engine(controller, engine, engine_index) {
++ if (context->enabled_channels_bitmap[engine_index]) {
++ hailo_dev_info(controller->dev, "Disabling channels for engine %zu, channels bitmap 0x%x\n", engine_index,
++ context->enabled_channels_bitmap[engine_index]);
++ hailo_vdma_engine_disable_channels(engine, context->enabled_channels_bitmap[engine_index]);
+
+ if (is_device_up) {
+ hailo_vdma_update_interrupts_mask(controller, engine_index);
+ }
+
+ spin_lock_irqsave(&controller->interrupts_lock, irq_saved_flags);
+- hailo_vdma_engine_clear_channel_interrupts(engine, channels_bitmap);
++ hailo_vdma_engine_clear_channel_interrupts(engine, context->enabled_channels_bitmap[engine_index]);
+ spin_unlock_irqrestore(&controller->interrupts_lock, irq_saved_flags);
+ }
+ }
+@@ -148,10 +152,21 @@ void hailo_vdma_file_context_finalize(st
+ }
+ }
+
++void hailo_vdma_wakeup_interrupts(struct hailo_vdma_controller *controller, struct hailo_vdma_engine *engine,
++ u32 channels_bitmap)
++{
++ unsigned long irq_saved_flags = 0;
++
++ spin_lock_irqsave(&controller->interrupts_lock, irq_saved_flags);
++ hailo_vdma_engine_set_channel_interrupts(engine, channels_bitmap);
++ spin_unlock_irqrestore(&controller->interrupts_lock, irq_saved_flags);
++
++ wake_up_interruptible_all(&controller->interrupts_wq);
++}
++
+ void hailo_vdma_irq_handler(struct hailo_vdma_controller *controller,
+ size_t engine_index, u32 channels_bitmap)
+ {
+- unsigned long irq_saved_flags = 0;
+ struct hailo_vdma_engine *engine = NULL;
+
+ BUG_ON(engine_index >= controller->vdma_engines_count);
+@@ -159,11 +174,7 @@ void hailo_vdma_irq_handler(struct hailo
+
+ hailo_vdma_engine_push_timestamps(engine, channels_bitmap);
+
+- spin_lock_irqsave(&controller->interrupts_lock, irq_saved_flags);
+- hailo_vdma_engine_set_channel_interrupts(engine, channels_bitmap);
+- spin_unlock_irqrestore(&controller->interrupts_lock, irq_saved_flags);
+-
+- wake_up_interruptible_all(&controller->interrupts_wq);
++ hailo_vdma_wakeup_interrupts(controller, engine, channels_bitmap);
+ }
+
+ long hailo_vdma_ioctl(struct hailo_vdma_file_context *context, struct hailo_vdma_controller *controller,
+@@ -171,9 +182,9 @@ long hailo_vdma_ioctl(struct hailo_vdma_
+ {
+ switch (cmd) {
+ case HAILO_VDMA_ENABLE_CHANNELS:
+- return hailo_vdma_enable_channels_ioctl(controller, arg);
++ return hailo_vdma_enable_channels_ioctl(controller, arg, context);
+ case HAILO_VDMA_DISABLE_CHANNELS:
+- return hailo_vdma_disable_channels_ioctl(controller, arg);
++ return hailo_vdma_disable_channels_ioctl(controller, arg, context);
+ case HAILO_VDMA_INTERRUPTS_WAIT:
+ return hailo_vdma_interrupts_wait_ioctl(controller, arg, mutex, should_up_board_mutex);
+ case HAILO_VDMA_INTERRUPTS_READ_TIMESTAMPS:
+--- a/drivers/media/pci/hailo/vdma/vdma.h
++++ b/drivers/media/pci/hailo/vdma/vdma.h
+@@ -1,6 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /**
+- * Copyright (c) 2019-2022 Hailo Technologies Ltd. All rights reserved.
++ * Copyright (c) 2019-2024 Hailo Technologies Ltd. All rights reserved.
+ **/
+ /**
+ * Hailo vdma engine definitions
+@@ -130,6 +130,7 @@ struct hailo_vdma_file_context {
+ struct list_head descriptors_buffer_list;
+ struct list_head vdma_low_memory_buffer_list;
+ struct list_head continuous_buffer_list;
++ u32 enabled_channels_bitmap[MAX_VDMA_ENGINES];
+ };
+
+
+@@ -145,6 +146,8 @@ void hailo_vdma_file_context_init(struct
+ void hailo_vdma_file_context_finalize(struct hailo_vdma_file_context *context,
+ struct hailo_vdma_controller *controller, struct file *filp);
+
++void hailo_vdma_wakeup_interrupts(struct hailo_vdma_controller *controller, struct hailo_vdma_engine *engine,
++ u32 channels_bitmap);
+ void hailo_vdma_irq_handler(struct hailo_vdma_controller *controller, size_t engine_index,
+ u32 channels_bitmap);
+
--- /dev/null
+From dbf12796d1368286672529d7b03f81066a8c36f3 Mon Sep 17 00:00:00 2001
+From: Iker Pedrosa <ikerpedrosam@gmail.com>
+Date: Mon, 18 Nov 2024 10:55:33 +0100
+Subject: [PATCH] dtoverlays: enable SPI CS active-high
+
+The documentation isn't very clear explaining how to enable SPI CS
+active-high and it takes a long time to understand it. Adding a specific
+overlay as a simple example on how to invert this signal can help
+understand the solution.
+
+Link: https://forums.raspberrypi.com/viewtopic.php?t=378222
+Signed-off-by: Iker Pedrosa <ikerpedrosam@gmail.com>
+---
+ arch/arm/boot/dts/overlays/Makefile | 1 +
+ arch/arm/boot/dts/overlays/README | 8 +++
+ .../overlays/spi0-1cs-inverted-overlay.dts | 59 +++++++++++++++++++
+ 3 files changed, 68 insertions(+)
+ create mode 100644 arch/arm/boot/dts/overlays/spi0-1cs-inverted-overlay.dts
+
+--- a/arch/arm/boot/dts/overlays/Makefile
++++ b/arch/arm/boot/dts/overlays/Makefile
+@@ -259,6 +259,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
+ spi-rtc.dtbo \
+ spi0-0cs.dtbo \
+ spi0-1cs.dtbo \
++ spi0-1cs-inverted.dtbo \
+ spi0-2cs.dtbo \
+ spi1-1cs.dtbo \
+ spi1-2cs.dtbo \
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -4438,6 +4438,14 @@ Params: cs0_pin GPIO pin
+ it for other uses.
+
+
++Name: spi0-1cs-inverted
++Info: Only use one CS pin for SPI0 and set to active-high
++Load: dtoverlay=spi0-1cs-inverted,<param>=<val>
++Params: cs0_pin GPIO pin for CS0 (default 8)
++ no_miso Don't claim and use the MISO pin (9), freeing
++ it for other uses.
++
++
+ Name: spi0-2cs
+ Info: Change the CS pins for SPI0
+ Load: dtoverlay=spi0-2cs,<param>=<val>
+--- /dev/null
++++ b/arch/arm/boot/dts/overlays/spi0-1cs-inverted-overlay.dts
+@@ -0,0 +1,59 @@
++/dts-v1/;
++/plugin/;
++
++/*
++ * There are some devices that need an inverted Chip Select (CS) to select the
++ * device signal, as an example the AZDelivery 12864 display. That means that
++ * the CS polarity is active-high. To invert the CS signal the DT needs to set
++ * the cs-gpio to GPIO_ACTIVE_HIGH (0) in the controller and set the
++ * spi-cs-high in the peripheral property. On top of that, since this is a
++ * display the DT also needs to specify the write-only property.
++*/
++
++#include <dt-bindings/gpio/gpio.h>
++
++/ {
++ compatible = "brcm,bcm2835";
++
++ fragment@0 {
++ target = <&spi0_cs_pins>;
++ frag0: __overlay__ {
++ brcm,pins = <8>;
++ };
++ };
++
++ fragment@1 {
++ target = <&spi0>;
++ frag1: __overlay__ {
++ cs-gpios = <&gpio 8 GPIO_ACTIVE_HIGH>;
++ status = "okay";
++ };
++ };
++
++ fragment@2 {
++ target = <&spidev1>;
++ __overlay__ {
++ status = "disabled";
++ };
++ };
++
++ fragment@3 {
++ target = <&spi0_pins>;
++ __dormant__ {
++ brcm,pins = <10 11>;
++ };
++ };
++
++ fragment@4 {
++ target = <&spidev0>;
++ __overlay__ {
++ spi-cs-high;
++ };
++ };
++
++ __overrides__ {
++ cs0_pin = <&frag0>,"brcm,pins:0",
++ <&frag1>,"cs-gpios:4";
++ no_miso = <0>,"=3";
++ };
++};
--- /dev/null
+From 57b528e557890f25e010b6bc7356b5a716c79db2 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Tue, 12 Nov 2024 17:58:52 +0000
+Subject: [PATCH] drm/vc4: hvs: Defer updating the enable_bg_fill until vblank
+
+The register to enable/disable background fill was being set
+from atomic flush, however that will be applied immediately and
+can be a while before the vblank. If it was required for the
+current frame but not for the next one, that can result in
+corruption for part of the current frame.
+
+Store the state in vc4_hvs, and update it on vblank.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/gpu/drm/vc4/vc4_drv.h | 2 ++
+ drivers/gpu/drm/vc4/vc4_hvs.c | 18 ++++++++++--------
+ 2 files changed, 12 insertions(+), 8 deletions(-)
+
+--- a/drivers/gpu/drm/vc4/vc4_drv.h
++++ b/drivers/gpu/drm/vc4/vc4_drv.h
+@@ -339,6 +339,8 @@ struct vc4_hvs {
+ unsigned int enabled: 1;
+ } eof_irq[HVS_NUM_CHANNELS];
+
++ bool bg_fill[HVS_NUM_CHANNELS];
++
+ unsigned long max_core_rate;
+
+ /* Memory manager for CRTCs to allocate space in the display
+--- a/drivers/gpu/drm/vc4/vc4_hvs.c
++++ b/drivers/gpu/drm/vc4/vc4_hvs.c
+@@ -1470,14 +1470,7 @@ void vc4_hvs_atomic_flush(struct drm_crt
+ /* This sets a black background color fill, as is the case
+ * with other DRM drivers.
+ */
+- if (enable_bg_fill)
+- HVS_WRITE(SCALER6_DISPX_CTRL1(channel),
+- HVS_READ(SCALER6_DISPX_CTRL1(channel)) |
+- SCALER6(DISPX_CTRL1_BGENB));
+- else
+- HVS_WRITE(SCALER6_DISPX_CTRL1(channel),
+- HVS_READ(SCALER6_DISPX_CTRL1(channel)) &
+- ~SCALER6(DISPX_CTRL1_BGENB));
++ hvs->bg_fill[channel] = enable_bg_fill;
+ } else {
+ /* we can actually run with a lower core clock when background
+ * fill is enabled on VC4_GEN_5 so leave it enabled always.
+@@ -1662,6 +1655,15 @@ static irqreturn_t vc6_hvs_eof_irq_handl
+ if (hvs->eof_irq[i].desc != irq)
+ continue;
+
++ if (hvs->bg_fill[i])
++ HVS_WRITE(SCALER6_DISPX_CTRL1(i),
++ HVS_READ(SCALER6_DISPX_CTRL1(i)) |
++ SCALER6(DISPX_CTRL1_BGENB));
++ else
++ HVS_WRITE(SCALER6_DISPX_CTRL1(i),
++ HVS_READ(SCALER6_DISPX_CTRL1(i)) &
++ ~SCALER6(DISPX_CTRL1_BGENB));
++
+ vc4_hvs_schedule_dlist_sweep(hvs, i);
+ return IRQ_HANDLED;
+ }
--- /dev/null
+From dd2394360860d15146c96635796a75b05bb32b61 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Tue, 19 Nov 2024 09:25:34 +0000
+Subject: [PATCH] misc: rp1-pio: Add FIFO-related methods
+
+Add support for querying the FIFO status and clearing the TX FIFO.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/misc/rp1-fw-pio.h | 3 ++
+ drivers/misc/rp1-pio.c | 24 +++++++++
+ include/linux/pio_rp1.h | 89 ++++++++++++++++++++++++++++++++++
+ include/uapi/misc/rp1_pio_if.h | 13 ++++-
+ 4 files changed, 128 insertions(+), 1 deletion(-)
+
+--- a/drivers/misc/rp1-fw-pio.h
++++ b/drivers/misc/rp1-fw-pio.h
+@@ -47,6 +47,9 @@ enum rp1_pio_ops {
+ 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
+ };
+
+--- a/drivers/misc/rp1-pio.c
++++ b/drivers/misc/rp1-pio.c
+@@ -476,6 +476,28 @@ int rp1_pio_sm_set_dmactrl(struct rp1_pi
+ }
+ 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;
+@@ -848,6 +870,8 @@ struct handler_info {
+ 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),
+--- a/include/linux/pio_rp1.h
++++ b/include/linux/pio_rp1.h
+@@ -200,6 +200,8 @@ int rp1_pio_sm_enable_sync(struct rp1_pi
+ 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);
+@@ -551,6 +553,15 @@ static inline int pio_sm_set_dmactrl(str
+ 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 };
+@@ -587,6 +598,84 @@ static inline uint32_t pio_sm_get_blocki
+ 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 ||
+--- a/include/uapi/misc/rp1_pio_if.h
++++ b/include/uapi/misc/rp1_pio_if.h
+@@ -114,7 +114,7 @@ struct rp1_pio_sm_get_args {
+ uint16_t sm;
+ uint8_t blocking;
+ uint8_t rsvd;
+- uint32_t data; /* IN/OUT */
++ uint32_t data; /* OUT */
+ };
+
+ struct rp1_pio_sm_set_dmactrl_args {
+@@ -124,6 +124,15 @@ struct rp1_pio_sm_set_dmactrl_args {
+ 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;
+ };
+@@ -195,6 +204,8 @@ struct rp1_access_hw_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)
--- /dev/null
+From fa6ad4bcad4e8db18493a4af640b4b5c95434e70 Mon Sep 17 00:00:00 2001
+From: Just a nerd <157698061+foonerd@users.noreply.github.com>
+Date: Wed, 20 Nov 2024 14:08:48 +0000
+Subject: [PATCH] overlays: Enable Raspberry Touch 2 rotation with overlay
+
+See: https://github.com/raspberrypi/linux/pull/6480
+Signed-off-by: foonerd <foonerd@github.com>
+---
+ arch/arm/boot/dts/overlays/README | 1 +
+ arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-7inch-overlay.dts | 1 +
+ 2 files changed, 2 insertions(+)
+
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -5249,6 +5249,7 @@ Params: sizex Touchscr
+ invy Touchscreen inverted y axis
+ swapxy Touchscreen swapped x y axis
+ disable_touch Disables the touch screen overlay driver
++ rotation Display rotation {0,90,180,270} (default 0)
+ dsi0 Use DSI0 and i2c_csi_dsi0 (rather than
+ the default DSI1 and i2c_csi_dsi).
+
+--- a/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-7inch-overlay.dts
++++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-7inch-overlay.dts
+@@ -118,5 +118,6 @@
+ invy = <0>, "+11";
+ swapxy = <>911>,"touchscreen-swapped-x-y?";
+ disable_touch = <>911>, "status=disabled";
++ rotation = <&dsi_panel>, "rotation:0";
+ };
+ };
--- /dev/null
+From df8a2f6dc114b2c5c7685a069f717f2b06186b74 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Wed, 20 Nov 2024 16:23:06 +0000
+Subject: [PATCH] rp1-pio: Add missing 'static inline's
+
+Avoid some duplicate symbol errors by adding some missing
+'static inline' decorations.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ include/linux/pio_rp1.h | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/include/linux/pio_rp1.h
++++ b/include/linux/pio_rp1.h
+@@ -247,7 +247,7 @@ static inline bool pio_can_add_program_a
+ return !rp1_pio_can_add_program(client, &args);
+ }
+
+-uint pio_add_program(struct rp1_pio_client *client, const pio_program_t *program)
++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;
+@@ -367,7 +367,7 @@ static inline int pio_sm_set_config(stru
+ return rp1_pio_sm_set_config(client, &args);
+ }
+
+-int pio_sm_exec(struct rp1_pio_client *client, uint sm, uint instr)
++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 };
+
+@@ -377,7 +377,7 @@ int pio_sm_exec(struct rp1_pio_client *c
+ return rp1_pio_sm_exec(client, &args);
+ }
+
+-int pio_sm_exec_wait_blocking(struct rp1_pio_client *client, uint sm, uint instr)
++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 };
+
--- /dev/null
+From d1f0c94e974a5f26d210b1d13a6ef9543bee4984 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Thu, 21 Nov 2024 11:11:48 +0000
+Subject: [PATCH] misc: rp1-pio: Back-port some 6.11 build fixes
+
+Porting rp1-pio to rpi-6.11.y uncovered a few missing #includes and a
+difference of const-ness. Although not needed here, back-porting the
+resulting changes makes the driver more "correct" and may prevent a
+future merge conflict.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/misc/rp1-pio.c | 3 +++
+ include/linux/pio_rp1.h | 2 +-
+ 2 files changed, 4 insertions(+), 1 deletion(-)
+
+--- a/drivers/misc/rp1-pio.c
++++ b/drivers/misc/rp1-pio.c
+@@ -22,6 +22,9 @@
+ #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>
+--- a/include/linux/pio_rp1.h
++++ b/include/linux/pio_rp1.h
+@@ -176,7 +176,7 @@ typedef rp1_pio_sm_config pio_sm_config;
+ typedef struct rp1_pio_client *PIO;
+
+ void pio_set_error(struct rp1_pio_client *client, int err);
+-int pio_get_error(struct rp1_pio_client *client);
++int pio_get_error(const struct rp1_pio_client *client);
+ void pio_clear_error(struct rp1_pio_client *client);
+
+ int rp1_pio_can_add_program(struct rp1_pio_client *client, void *param);
--- /dev/null
+From 8a6f640708627ac8ebf79f88793038933f169198 Mon Sep 17 00:00:00 2001
+From: Giedrius <giedrius@blokas.io>
+Date: Thu, 21 Nov 2024 08:04:02 +0000
+Subject: [PATCH] Adding Pimidi kernel module.
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io>
+---
+ sound/drivers/Kconfig | 10 +
+ sound/drivers/Makefile | 2 +
+ sound/drivers/pimidi.c | 1113 ++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 1125 insertions(+)
+ create mode 100644 sound/drivers/pimidi.c
+
+--- a/sound/drivers/Kconfig
++++ b/sound/drivers/Kconfig
+@@ -263,4 +263,14 @@ config SND_AC97_POWER_SAVE_DEFAULT
+
+ See SND_AC97_POWER_SAVE for more details.
+
++config SND_PIMIDI
++ tristate "Pimidi driver"
++ depends on SND_SEQUENCER && CRC8
++ select SND_RAWMIDI
++ help
++ Say Y here to include support for Blokas Pimidi.
++
++ To compile this driver as a module, choose M here: the module
++ will be called snd-pimidi.
++
+ endif # SND_DRIVERS
+--- a/sound/drivers/Makefile
++++ b/sound/drivers/Makefile
+@@ -9,6 +9,7 @@ snd-aloop-objs := aloop.o
+ snd-mtpav-objs := mtpav.o
+ snd-mts64-objs := mts64.o
+ snd-pcmtest-objs := pcmtest.o
++snd-pimidi-objs := pimidi.o
+ snd-portman2x4-objs := portman2x4.o
+ snd-serial-u16550-objs := serial-u16550.o
+ snd-serial-generic-objs := serial-generic.o
+@@ -23,6 +24,7 @@ obj-$(CONFIG_SND_SERIAL_U16550) += snd-s
+ obj-$(CONFIG_SND_SERIAL_GENERIC) += snd-serial-generic.o
+ obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o
+ obj-$(CONFIG_SND_MTS64) += snd-mts64.o
++obj-$(CONFIG_SND_PIMIDI) += snd-pimidi.o
+ obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o
+
+ obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/
+--- /dev/null
++++ b/sound/drivers/pimidi.c
+@@ -0,0 +1,1113 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Pimidi Linux kernel module.
++ * Copyright (C) 2017-2024 Vilniaus Blokas UAB, https://blokas.io/
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; version 2 of the
++ * License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/completion.h>
++#include <linux/module.h>
++#include <linux/i2c.h>
++#include <linux/irq.h>
++#include <linux/irqdesc.h>
++#include <linux/bitops.h>
++#include <linux/of_irq.h>
++#include <linux/kfifo.h>
++#include <linux/list.h>
++#include <linux/workqueue.h>
++#include <linux/gpio.h>
++#include <linux/gpio/consumer.h>
++#include <linux/interrupt.h>
++#include <linux/mutex.h>
++#include <linux/refcount.h>
++#include <linux/crc8.h>
++#include <linux/delay.h>
++
++#include <sound/core.h>
++#include <sound/initval.h>
++#include <sound/rawmidi.h>
++#include <sound/asequencer.h>
++#include <sound/info.h>
++
++#define PIMIDI_LOG_IMPL(instance, log_func, msg, ...) log_func("pimidi(%s)[%c]: " msg "\n", \
++ __func__, (instance) ? (instance)->d + '0' : 'G', ## __VA_ARGS__)
++
++#ifdef PIMIDI_DEBUG
++# define printd(instance, ...) PIMIDI_LOG_IMPL(instance, pr_alert, __VA_ARGS__)
++# define printd_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_alert_ratelimited, __VA_ARGS__)
++# define printd_g(...) printd((struct pimidi_instance *)NULL, __VA_ARGS__)
++#else
++# define printd(instance, ...) do {} while (0)
++# define printd_rl(instance, ...) do {} while (0)
++# define printd_g(...) do {} while (0)
++#endif
++
++#define printe(instance, ...) PIMIDI_LOG_IMPL(instance, pr_err, __VA_ARGS__)
++#define printe_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_err_ratelimited, __VA_ARGS__)
++#define printi(instance, ...) PIMIDI_LOG_IMPL(instance, pr_info, __VA_ARGS__)
++#define printw(instance, ...) PIMIDI_LOG_IMPL(instance, pr_warn, __VA_ARGS__)
++#define printw_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_warn_ratelimited, __VA_ARGS__)
++
++#define printe_g(...) printe((struct pimidi_instance *)NULL, __VA_ARGS__)
++#define printi_g(...) printi((struct pimidi_instance *)NULL, __VA_ARGS__)
++
++DECLARE_CRC8_TABLE(pimidi_crc8_table);
++enum { PIMIDI_CRC8_POLYNOMIAL = 0x83 };
++enum { PIMIDI_MAX_DEVICES = 4 };
++enum { PIMIDI_MAX_PACKET_SIZE = 17 };
++enum { PIMIDI_PORTS = 2 };
++
++struct pimidi_shared {
++ // lock protects the shared reset_gpio and devices list.
++ struct mutex lock;
++ struct gpio_desc *reset_gpio;
++ struct workqueue_struct *work_queue;
++ struct list_head devices;
++};
++
++static struct pimidi_shared pimidi_global = {
++ .devices = LIST_HEAD_INIT(pimidi_global.devices),
++};
++
++struct pimidi_version_t {
++ u8 hwrev;
++ u8 major;
++ u8 minor;
++ u8 build;
++};
++
++enum { PIMIDI_IN_FIFO_SIZE = 4096 };
++
++struct pimidi_midi_port {
++ // in_lock protects the input substream.
++ struct mutex in_lock;
++ // out_lock protects the output substream.
++ struct mutex out_lock;
++ DECLARE_KFIFO(in_fifo, uint8_t, PIMIDI_IN_FIFO_SIZE);
++ unsigned int last_output_at;
++ unsigned int output_buffer_used_in_millibytes;
++ struct work_struct in_handler;
++ struct delayed_work out_handler;
++ unsigned long enabled_streams;
++ unsigned int tx_cnt;
++ unsigned int rx_cnt;
++};
++
++struct pimidi_instance {
++ struct list_head list;
++ struct i2c_client *i2c_client;
++ struct pimidi_version_t version;
++ char serial[11];
++ char d;
++ struct gpio_desc *data_ready_gpio;
++
++ struct work_struct drdy_handler;
++
++ // comm_lock serializes I2C communication.
++ struct mutex comm_lock;
++ char *rx_buf;
++ size_t rx_len;
++ int rx_status;
++ struct completion *rx_completion;
++
++ struct snd_rawmidi *rawmidi;
++ struct pimidi_midi_port midi_port[PIMIDI_PORTS];
++ bool stopping;
++};
++
++static struct snd_rawmidi_substream *pimidi_find_substream(struct snd_rawmidi *rawmidi,
++ int stream,
++ int number
++ )
++{
++ struct snd_rawmidi_substream *substream;
++
++ list_for_each_entry(substream, &rawmidi->streams[stream].substreams, list) {
++ if (substream->number == number)
++ return substream;
++ }
++ return NULL;
++}
++
++static void pimidi_midi_in_handler(struct pimidi_instance *instance, int port)
++{
++ int i, n, err;
++
++ printd(instance, "(%d)", port);
++
++ struct pimidi_midi_port *midi_port = &instance->midi_port[port];
++
++ if (!test_bit(SNDRV_RAWMIDI_STREAM_INPUT, &midi_port->enabled_streams)) {
++ printd(instance, "Input not enabled for %d", port);
++ return;
++ }
++
++ u8 data[512];
++
++ n = kfifo_out_peek(&midi_port->in_fifo, data, sizeof(data));
++ printd(instance, "Peeked %d MIDI bytes", n);
++
++ mutex_lock(&midi_port->in_lock);
++ struct snd_rawmidi_substream *substream =
++ pimidi_find_substream(instance->rawmidi,
++ SNDRV_RAWMIDI_STREAM_INPUT,
++ port);
++
++ err = snd_rawmidi_receive(substream, data, n);
++ if (err > 0)
++ midi_port->rx_cnt += err;
++ mutex_unlock(&midi_port->in_lock);
++
++ for (i = 0; i < err; ++i)
++ kfifo_skip(&midi_port->in_fifo);
++
++ if (n != err)
++ printw_rl(instance,
++ "Not all MIDI data consumed for port %d: %d / %d", port, err, n);
++
++ if (!kfifo_is_empty(&midi_port->in_fifo) && !instance->stopping)
++ queue_work(pimidi_global.work_queue, &midi_port->in_handler);
++
++ printd(instance, "Done");
++}
++
++static void pimidi_midi_in_handler_0(struct work_struct *work)
++{
++ pimidi_midi_in_handler(container_of(work, struct pimidi_instance, midi_port[0].in_handler),
++ 0);
++}
++
++static void pimidi_midi_in_handler_1(struct work_struct *work)
++{
++ pimidi_midi_in_handler(container_of(work, struct pimidi_instance, midi_port[1].in_handler),
++ 1);
++}
++
++static void pimidi_midi_out_handler(struct pimidi_instance *instance, int port)
++{
++ printd(instance, "(%d)", port);
++ if (!test_bit(SNDRV_RAWMIDI_STREAM_OUTPUT, &instance->midi_port[port].enabled_streams)) {
++ printd(instance, "Output not enabled for %d", port);
++ return;
++ }
++
++ struct pimidi_midi_port *midi_port = &instance->midi_port[port];
++
++ struct snd_rawmidi_substream *substream =
++ pimidi_find_substream(instance->rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, port);
++
++ mutex_lock(&midi_port->out_lock);
++
++ enum { MIDI_MILLI_BYTES_PER_JIFFY = 3125000 / HZ };
++ enum { MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES =
++ (512 - PIMIDI_MAX_PACKET_SIZE - 1) * 1000 };
++
++ unsigned int now = jiffies;
++ unsigned int millibytes_became_available =
++ (MIDI_MILLI_BYTES_PER_JIFFY) * (now - midi_port->last_output_at);
++
++ midi_port->output_buffer_used_in_millibytes =
++ midi_port->output_buffer_used_in_millibytes <=
++ millibytes_became_available ? 0 : midi_port->output_buffer_used_in_millibytes -
++ millibytes_became_available;
++
++ unsigned int output_buffer_available =
++ (MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES
++ - midi_port->output_buffer_used_in_millibytes)
++ / 1000;
++
++ u8 buffer[PIMIDI_MAX_PACKET_SIZE];
++ int n, batch, err;
++
++ for (batch = 0; batch < 3; ++batch) {
++ if (output_buffer_available == 0)
++ printd(instance, "Buffer full");
++
++ printd(instance, "Buffer available: %u (%u +%u, %u -> %u, dt %u) (%u) @ %u",
++ output_buffer_available, midi_port->output_buffer_used_in_millibytes,
++ millibytes_became_available, midi_port->last_output_at, now,
++ now - midi_port->last_output_at, midi_port->tx_cnt, HZ);
++ midi_port->last_output_at = now;
++
++ n = output_buffer_available
++ ? snd_rawmidi_transmit_peek(substream, buffer + 1,
++ min(output_buffer_available,
++ sizeof(buffer) - 2))
++ : 0;
++ if (n > 0) {
++ printd(instance, "Peeked: %d", n);
++ snd_rawmidi_transmit_ack(substream, n);
++
++ buffer[0] = (port << 4) | n;
++ buffer[n + 1] = ~crc8(pimidi_crc8_table, buffer, n + 1, CRC8_INIT_VALUE);
++
++#ifdef PIMIDI_DEBUG
++ pr_debug("%s[%d]: Sending %d bytes:", __func__, instance->d, n + 2);
++ int i;
++
++ for (i = 0; i < n + 2; ++i)
++ pr_cont(" %02x", buffer[i]);
++
++ pr_cont("\n");
++#endif
++ mutex_lock(&instance->comm_lock);
++ err = i2c_master_send(instance->i2c_client, buffer, n + 2);
++ mutex_unlock(&instance->comm_lock);
++
++ if (err < 0) {
++ printe(instance,
++ "Error occurred when sending MIDI data over I2C! (%d)",
++ err);
++ goto cleanup;
++ }
++
++ midi_port->tx_cnt += n;
++ midi_port->output_buffer_used_in_millibytes += n * 1000;
++ output_buffer_available -= n;
++ } else if (n < 0) {
++ err = n;
++ printe(instance, "snd_rawmidi_transmit_peek returned error %d!", err);
++ goto cleanup;
++ } else {
++ break;
++ }
++ }
++
++ printd(instance, "Checking if empty %p", substream);
++ if (!snd_rawmidi_transmit_empty(substream) && !instance->stopping) {
++ unsigned int delay = 1;
++
++ if (output_buffer_available == 0)
++ delay = 125000 / MIDI_MILLI_BYTES_PER_JIFFY;
++ printd(instance, "Queue more work after %u jiffies", delay);
++ mod_delayed_work(pimidi_global.work_queue, &midi_port->out_handler, delay);
++ }
++
++cleanup:
++ mutex_unlock(&midi_port->out_lock);
++ printd(instance, "Done");
++}
++
++static void pimidi_midi_out_handler_0(struct work_struct *work)
++{
++ pimidi_midi_out_handler(container_of(work, struct pimidi_instance,
++ midi_port[0].out_handler.work), 0);
++}
++
++static void pimidi_midi_out_handler_1(struct work_struct *work)
++{
++ pimidi_midi_out_handler(container_of(work, struct pimidi_instance,
++ midi_port[1].out_handler.work), 1);
++}
++
++static void pimidi_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
++{
++ struct pimidi_instance *instance = substream->rmidi->private_data;
++
++ printd(instance, "(%d, %d, %d)", substream->stream, substream->number, up);
++
++ if (up == 0) {
++ clear_bit(substream->stream,
++ &instance->midi_port[substream->number].enabled_streams);
++ } else {
++ set_bit(substream->stream,
++ &instance->midi_port[substream->number].enabled_streams);
++ if (!delayed_work_pending(&instance->midi_port[substream->number].out_handler)) {
++ printd(instance, "Queueing work");
++ queue_delayed_work(pimidi_global.work_queue,
++ &instance->midi_port[substream->number].out_handler, 0);
++ }
++ }
++}
++
++static void pimidi_midi_output_drain(struct snd_rawmidi_substream *substream)
++{
++ struct pimidi_instance *instance = substream->rmidi->private_data;
++
++ printd(instance, "(%d, %d)", substream->stream, substream->number);
++
++ printd(instance, "Begin draining!");
++
++ queue_delayed_work(pimidi_global.work_queue,
++ &instance->midi_port[substream->number].out_handler, 0);
++
++ unsigned long deadline = jiffies + 5 * HZ;
++
++ do {
++ printd(instance, "Before flush");
++ while (delayed_work_pending(&instance->midi_port[substream->number].out_handler))
++ flush_delayed_work(&instance->midi_port[substream->number].out_handler);
++ printd(instance, "Flushed");
++ } while (!snd_rawmidi_transmit_empty(substream) && time_before(jiffies, deadline));
++
++ printd(instance, "Done!");
++}
++
++static int pimidi_midi_output_close(struct snd_rawmidi_substream *substream)
++{
++ struct pimidi_instance *instance = substream->rmidi->private_data;
++ struct pimidi_midi_port *midi_port = &instance->midi_port[substream->number];
++
++ mutex_lock(&midi_port->out_lock);
++ clear_bit(substream->stream, &midi_port->enabled_streams);
++ mutex_unlock(&midi_port->out_lock);
++ return 0;
++}
++
++static int pimidi_midi_input_close(struct snd_rawmidi_substream *substream)
++{
++ struct pimidi_instance *instance = substream->rmidi->private_data;
++ struct pimidi_midi_port *midi_port = &instance->midi_port[substream->number];
++
++ mutex_lock(&midi_port->in_lock);
++ clear_bit(substream->stream, &midi_port->enabled_streams);
++ mutex_unlock(&midi_port->in_lock);
++ return 0;
++}
++
++static void pimidi_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
++{
++ struct pimidi_instance *instance = substream->rmidi->private_data;
++
++ printd(instance, "(%d, %d, %d)", substream->stream, substream->number, up);
++
++ if (up == 0) {
++ clear_bit(substream->stream,
++ &instance->midi_port[substream->number].enabled_streams);
++ cancel_work_sync(&instance->midi_port[substream->number].in_handler);
++ } else {
++ set_bit(substream->stream,
++ &instance->midi_port[substream->number].enabled_streams);
++ if (!instance->stopping)
++ queue_work(pimidi_global.work_queue,
++ &instance->midi_port[substream->number].in_handler);
++ }
++}
++
++static void pimidi_get_port_info(struct snd_rawmidi *rmidi, int number,
++ struct snd_seq_port_info *seq_port_info)
++{
++ printd_g("%p, %d, %p", rmidi, number, seq_port_info);
++ seq_port_info->type =
++ SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
++ SNDRV_SEQ_PORT_TYPE_HARDWARE |
++ SNDRV_SEQ_PORT_TYPE_PORT;
++ strscpy(seq_port_info->name, number == 0 ? "a" : "b",
++ sizeof(seq_port_info->name));
++ seq_port_info->midi_voices = 0;
++}
++
++static const struct snd_rawmidi_global_ops pimidi_midi_ops = {
++ .get_port_info = pimidi_get_port_info,
++};
++
++static int pimidi_midi_open(struct snd_rawmidi_substream *substream)
++{
++ printd_g("(%p) stream=%d number=%d", substream, substream->stream, substream->number);
++ return 0;
++}
++
++static const struct snd_rawmidi_ops pimidi_midi_output_ops = {
++ .open = pimidi_midi_open,
++ .close = pimidi_midi_output_close,
++ .trigger = pimidi_midi_output_trigger,
++ .drain = pimidi_midi_output_drain,
++};
++
++static const struct snd_rawmidi_ops pimidi_midi_input_ops = {
++ .open = pimidi_midi_open,
++ .close = pimidi_midi_input_close,
++ .trigger = pimidi_midi_input_trigger,
++};
++
++static int pimidi_register(struct pimidi_instance *instance)
++{
++ int err = 0;
++
++ mutex_lock(&pimidi_global.lock);
++ printd(instance, "Registering...");
++ if (!pimidi_global.reset_gpio) {
++ printd_g("Getting reset pin.");
++ pimidi_global.reset_gpio = gpiod_get(&instance->i2c_client->dev, "reset",
++ GPIOD_OUT_LOW);
++ if (IS_ERR(pimidi_global.reset_gpio)) {
++ err = PTR_ERR(pimidi_global.reset_gpio);
++ printe_g("gpiod_get failed: %d", err);
++ pimidi_global.reset_gpio = NULL;
++ mutex_unlock(&pimidi_global.lock);
++ return err;
++ }
++ }
++ list_add_tail(&instance->list, &pimidi_global.devices);
++ mutex_unlock(&pimidi_global.lock);
++ return err;
++}
++
++static void pimidi_unregister(struct pimidi_instance *instance)
++{
++ mutex_lock(&pimidi_global.lock);
++ printd(instance, "Unregistering...");
++ list_del(&instance->list);
++ if (list_empty(&pimidi_global.devices)) {
++ printd_g("Releasing reset pin");
++ gpiod_put(pimidi_global.reset_gpio);
++ pimidi_global.reset_gpio = NULL;
++ }
++ mutex_unlock(&pimidi_global.lock);
++}
++
++static void pimidi_perform_reset(void)
++{
++ mutex_lock(&pimidi_global.lock);
++
++ printd_g("Performing reset.");
++
++ struct list_head *p;
++
++ list_for_each(p, &pimidi_global.devices) {
++ struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list);
++
++ printd(instance, "Pausing...");
++ instance->stopping = true;
++ disable_irq(instance->i2c_client->irq);
++ cancel_work(&instance->drdy_handler);
++
++ int i;
++
++ for (i = 0; i < PIMIDI_PORTS; ++i) {
++ cancel_work(&instance->midi_port[i].in_handler);
++ cancel_delayed_work(&instance->midi_port[i].out_handler);
++ }
++
++ drain_workqueue(pimidi_global.work_queue);
++ }
++
++ printd_g("Reset = low");
++ gpiod_set_value(pimidi_global.reset_gpio, 1);
++
++ list_for_each(p, &pimidi_global.devices) {
++ struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list);
++
++ if (gpiod_is_active_low(instance->data_ready_gpio))
++ gpiod_toggle_active_low(instance->data_ready_gpio);
++ gpiod_direction_output(instance->data_ready_gpio, 1);
++ printd(instance, "DRDY high");
++ }
++
++ usleep_range(1000, 5000);
++ printd_g("Reset = high");
++ gpiod_set_value(pimidi_global.reset_gpio, 0);
++ msleep(30);
++
++ int i;
++
++ for (i = 0; i < PIMIDI_MAX_DEVICES; ++i) {
++ usleep_range(1000, 3000);
++ list_for_each(p, &pimidi_global.devices) {
++ struct pimidi_instance *instance = list_entry(p, struct pimidi_instance,
++ list);
++
++ if (instance->d < i)
++ continue;
++ printd(instance, "DRDY -> %d", !gpiod_get_value(instance->data_ready_gpio));
++ gpiod_set_value(instance->data_ready_gpio,
++ !gpiod_get_value(instance->data_ready_gpio));
++ }
++ }
++ usleep_range(16000, 20000);
++
++ list_for_each(p, &pimidi_global.devices) {
++ struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list);
++
++ if (!gpiod_is_active_low(instance->data_ready_gpio))
++ gpiod_toggle_active_low(instance->data_ready_gpio);
++
++ printd(instance, "DRDY input");
++ gpiod_direction_input(instance->data_ready_gpio);
++
++ printd(instance, "Resume...");
++ instance->stopping = false;
++ enable_irq(instance->i2c_client->irq);
++ }
++
++ printd_g("Reset done.");
++ usleep_range(16000, 20000);
++
++ mutex_unlock(&pimidi_global.lock);
++}
++
++static int pimidi_read_version(struct pimidi_version_t *version, struct pimidi_instance *instance)
++{
++ memset(version, 0, sizeof(*version));
++
++ const char cmd[4] = { 0xb2, 0x01, 0x01, 0x95 };
++
++ char result[9];
++
++ memset(result, 0, sizeof(result));
++
++ DECLARE_COMPLETION_ONSTACK(done);
++
++ mutex_lock(&instance->comm_lock);
++ int err = i2c_master_send(instance->i2c_client, cmd, sizeof(cmd));
++
++ if (err < 0) {
++ mutex_unlock(&instance->comm_lock);
++ return err;
++ }
++ instance->rx_buf = result;
++ instance->rx_len = sizeof(result);
++ instance->rx_completion = &done;
++ mutex_unlock(&instance->comm_lock);
++
++ printd(instance, "Waiting for drdy");
++ wait_for_completion_io_timeout(&done, msecs_to_jiffies(1000u));
++ printd(instance, "Done waiting");
++
++ if (!completion_done(&done)) {
++ mutex_lock(&instance->comm_lock);
++ instance->rx_buf = NULL;
++ instance->rx_len = 0;
++ instance->rx_status = -ETIMEDOUT;
++ instance->rx_completion = NULL;
++ mutex_unlock(&instance->comm_lock);
++ return -ETIMEDOUT;
++ }
++
++ if (CRC8_GOOD_VALUE(pimidi_crc8_table) != crc8(pimidi_crc8_table, result, sizeof(result),
++ CRC8_INIT_VALUE))
++ return -EIO;
++
++ const char expected[4] = { 0xb7, 0x81, 0x01, 0x00 };
++
++ if (memcmp(result, expected, sizeof(expected)) != 0)
++ return -EPROTO;
++
++ u32 v = ntohl(*(uint32_t *)(result + 4));
++
++ version->hwrev = v >> 24;
++ version->major = (v & 0x00ff0000) >> 16;
++ version->minor = (v & 0x0000ff00) >> 8;
++ version->build = v & 0x000000ff;
++
++ return 0;
++}
++
++static int pimidi_read_serial(char serial[11], struct pimidi_instance *instance)
++{
++ memset(serial, 0, sizeof(char[11]));
++
++ const char cmd[4] = { 0xb2, 0x03, 0x04, 0x97 };
++
++ char result[PIMIDI_MAX_PACKET_SIZE];
++
++ memset(result, 0, sizeof(result));
++
++ DECLARE_COMPLETION_ONSTACK(done);
++
++ mutex_lock(&instance->comm_lock);
++ int err = i2c_master_send(instance->i2c_client, cmd, sizeof(cmd));
++
++ if (err < 0) {
++ mutex_unlock(&instance->comm_lock);
++ return err;
++ }
++ instance->rx_buf = result;
++ instance->rx_len = sizeof(result);
++ instance->rx_completion = &done;
++ mutex_unlock(&instance->comm_lock);
++
++ printd(instance, "Waiting for drdy");
++ wait_for_completion_io_timeout(&done, msecs_to_jiffies(1000u));
++ printd(instance, "Done waiting");
++
++ if (!completion_done(&done)) {
++ mutex_lock(&instance->comm_lock);
++ instance->rx_buf = NULL;
++ instance->rx_len = 0;
++ instance->rx_status = -ETIMEDOUT;
++ instance->rx_completion = NULL;
++ mutex_unlock(&instance->comm_lock);
++ printe(instance, "Timed out");
++ return -ETIMEDOUT;
++ }
++
++ if (CRC8_GOOD_VALUE(pimidi_crc8_table) != crc8(pimidi_crc8_table, result,
++ (result[0] & 0x0f) + 2, CRC8_INIT_VALUE))
++ return -EIO;
++
++ const char expected[4] = { 0xbd, 0x83, 0x04, 0x0a };
++
++ if (memcmp(result, expected, sizeof(expected)) != 0) {
++ printe(instance, "Unexpected response: %02x %02x %02x %02x", result[0], result[1],
++ result[2], result[3]);
++ return -EPROTO;
++ }
++
++ memcpy(serial, result + 4, 10);
++
++ if (strspn(serial, "\xff") == 10)
++ strscpy(serial, "(unset)", 8);
++
++ return 0;
++}
++
++static void pimidi_handle_midi_data(struct pimidi_instance *instance, int port, const uint8_t *data,
++ unsigned int n)
++{
++ printd(instance, "Handling MIDI data for port %d (%u bytes)", port, n);
++ if (n == 0)
++ return;
++
++ struct pimidi_midi_port *midi_port = &instance->midi_port[port];
++
++ kfifo_in(&midi_port->in_fifo, data, n);
++
++ if (!instance->stopping)
++ queue_work(pimidi_global.work_queue, &midi_port->in_handler);
++
++ printd(instance, "Done");
++}
++
++static void pimidi_drdy_continue(struct pimidi_instance *instance)
++{
++ if (instance->stopping) {
++ printd(instance, "Refusing to queue work / enable IRQ due to stopping.");
++ return;
++ }
++
++ if (gpiod_get_value(instance->data_ready_gpio)) {
++ printd_rl(instance, "Queue work due to DRDY line still low");
++ queue_work(pimidi_global.work_queue, &instance->drdy_handler);
++ } else {
++ printd_rl(instance, "Enabling irq for more data");
++ enable_irq(gpiod_to_irq(instance->data_ready_gpio));
++ }
++}
++
++static void pimidi_drdy_handler(struct work_struct *work)
++{
++ struct pimidi_instance *instance = container_of(work, struct pimidi_instance, drdy_handler);
++
++ printd(instance, "(%p)", work);
++
++ mutex_lock(&instance->comm_lock);
++ if (!instance->rx_completion) {
++ u8 data[PIMIDI_MAX_PACKET_SIZE];
++ int n = i2c_master_recv(instance->i2c_client, data, 3);
++
++ if (n < 0) {
++ printe(instance, "Error reading from device: %d", n);
++ mutex_unlock(&instance->comm_lock);
++ pimidi_drdy_continue(instance);
++ return;
++ }
++
++ if (data[0] == 0xfe) {
++ printe_rl(instance, "Invalid packet 0x%02x 0x%02x 0x%02x", data[0], data[1],
++ data[2]);
++ mutex_unlock(&instance->comm_lock);
++ pimidi_drdy_continue(instance);
++ return;
++ }
++
++ int len = (data[0] & 0x0f) + 2;
++
++ if (len > n) {
++ printd(instance, "Need %d more bytes", len - n);
++ int err = i2c_master_recv(instance->i2c_client, data + n, len - n);
++
++ if (err < 0) {
++ printe(instance, "Error reading remainder from device: %d", err);
++ mutex_unlock(&instance->comm_lock);
++ pimidi_drdy_continue(instance);
++ return;
++#ifdef PIMIDI_DEBUG
++ } else {
++ pr_debug("Recv_2:");
++ int i;
++
++ for (i = n; i < len; ++i)
++ pr_cont(" %02x", data[i]);
++ pr_cont("\n");
++#endif
++ }
++ }
++
++ if (CRC8_GOOD_VALUE(pimidi_crc8_table) == crc8(pimidi_crc8_table, data, len,
++ CRC8_INIT_VALUE)) {
++ switch (data[0] & 0xf0) {
++ case 0x00:
++ pimidi_handle_midi_data(instance, 0, data + 1, len - 2);
++ break;
++ case 0x10:
++ pimidi_handle_midi_data(instance, 1, data + 1, len - 2);
++ break;
++ default:
++ printd(instance, "Unhandled command %02x", data[0]);
++ break;
++ }
++ } else {
++ printe(instance, "I2C rx corruption detected.");
++ pr_info("Packet [%d]:", len);
++ int i;
++
++ for (i = 0; i < len; ++i)
++ pr_cont(" %02x", data[i]);
++ pr_cont("\n");
++ }
++
++ mutex_unlock(&instance->comm_lock);
++ } else {
++ printd(instance, "Completing drdy");
++ instance->rx_status = i2c_master_recv(instance->i2c_client, instance->rx_buf, 3);
++ printd(instance, "Recv_1 %02x %02x %02x", instance->rx_buf[0], instance->rx_buf[1],
++ instance->rx_buf[2]);
++ if (instance->rx_len > 3 && instance->rx_status == 3) {
++ instance->rx_status = i2c_master_recv(instance->i2c_client,
++ instance->rx_buf + 3,
++ instance->rx_len - 3);
++ if (instance->rx_status >= 0)
++ instance->rx_status += 3;
++#ifdef PIMIDI_DEBUG
++ pr_debug("Recv_2:");
++ int i;
++
++ for (i = 3; i < instance->rx_len; ++i)
++ pr_cont(" %02x", instance->rx_buf[i]);
++ pr_cont("\n");
++#endif
++ }
++ struct completion *done = instance->rx_completion;
++
++ instance->rx_buf = NULL;
++ instance->rx_len = 0;
++ instance->rx_completion = NULL;
++ complete_all(done);
++ mutex_unlock(&instance->comm_lock);
++ }
++
++ pimidi_drdy_continue(instance);
++}
++
++static irqreturn_t pimidi_drdy_interrupt_handler(int irq, void *dev_id)
++{
++ struct pimidi_instance *instance = (struct pimidi_instance *)dev_id;
++
++ if (instance->stopping) {
++ printd(instance, "DRDY interrupt, but stopping, ignoring...");
++ return IRQ_HANDLED;
++ }
++
++ printd(instance, "DRDY interrupt, masking");
++ disable_irq_nosync(irq);
++
++ printd(instance, "Queue work due to DRDY interrupt");
++ queue_work(pimidi_global.work_queue, &instance->drdy_handler);
++
++ return IRQ_HANDLED;
++}
++
++static void pimidi_proc_stat_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
++{
++ const unsigned int *d = entry->private_data;
++
++ snd_iprintf(buffer, "%u\n", *d);
++}
++
++static void pimidi_proc_serial_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
++{
++ struct pimidi_instance *instance = entry->private_data;
++
++ snd_iprintf(buffer, "%s\n", instance->serial);
++}
++
++static void pimidi_proc_version_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
++{
++ struct pimidi_instance *instance = entry->private_data;
++
++ snd_iprintf(buffer, "%u.%u.%u\n", instance->version.major, instance->version.minor,
++ instance->version.build);
++}
++
++static void pimidi_proc_hwrev_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
++{
++ struct pimidi_instance *instance = entry->private_data;
++
++ snd_iprintf(buffer, "%u\n", instance->version.hwrev);
++}
++
++static int pimidi_i2c_probe(struct i2c_client *client)
++{
++ struct snd_card *card = NULL;
++ int err, d, i;
++
++ d = client->addr - 0x20;
++
++ if (d < 0 || d >= 8) {
++ printe_g("Unexpected device address: %d", client->addr);
++ err = -EINVAL;
++ goto finalize;
++ }
++
++ err = snd_card_new(&client->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE,
++ sizeof(struct pimidi_instance), &card);
++
++ if (err) {
++ printe_g("snd_card_new failed: %d", err);
++ return err;
++ }
++
++ struct pimidi_instance *instance = (struct pimidi_instance *)card->private_data;
++
++ instance->i2c_client = client;
++ instance->d = d;
++
++ struct snd_rawmidi *rawmidi;
++
++ err = snd_rawmidi_new(card, card->shortname, 0, 2, 2, &rawmidi);
++ if (err < 0) {
++ printe(instance, "snd_rawmidi_new failed: %d", err);
++ goto finalize;
++ }
++
++ instance->rawmidi = rawmidi;
++ strscpy(rawmidi->name, "pimidi", sizeof(rawmidi->name));
++
++ rawmidi->info_flags =
++ SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX;
++ rawmidi->private_data = instance;
++ rawmidi->ops = &pimidi_midi_ops;
++
++ snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &pimidi_midi_output_ops);
++ snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, &pimidi_midi_input_ops);
++
++ instance->data_ready_gpio = devm_gpiod_get(&client->dev, "data-ready", GPIOD_OUT_HIGH);
++ if (IS_ERR(instance->data_ready_gpio)) {
++ err = PTR_ERR(instance->data_ready_gpio);
++ printe(instance, "devm_gpiod_get failed: %d", err);
++ goto finalize;
++ }
++
++ err = pimidi_register(instance);
++ if (err < 0) {
++ printe(instance, "pimidi_register failed: %d", err);
++ goto finalize;
++ }
++
++ pimidi_perform_reset();
++
++ INIT_WORK(&instance->drdy_handler, pimidi_drdy_handler);
++ mutex_init(&instance->comm_lock);
++
++ err = devm_request_irq(&client->dev, client->irq, pimidi_drdy_interrupt_handler,
++ IRQF_SHARED | IRQF_TRIGGER_LOW, "data_ready_int", instance);
++
++ if (err != 0) {
++ printe(instance, "data_available IRQ request failed! %d", err);
++ goto finalize;
++ }
++
++ err = pimidi_read_version(&instance->version, instance);
++ if (err < 0) {
++ printe(instance, "pimidi_read_version failed: %d", err);
++ goto finalize;
++ }
++
++ err = pimidi_read_serial(instance->serial, instance);
++ if (err < 0) {
++ printe(instance, "pimidi_read_serial failed: %d", err);
++ goto finalize;
++ } else if (instance->serial[0] != 'P' || instance->serial[1] != 'M' ||
++ strlen(instance->serial) != 10) {
++ printe(instance, "Unexpected serial number: %s", instance->serial);
++ err = -EIO;
++ goto finalize;
++ }
++
++ printi(instance, "pimidi%d hw:%d version %u.%u.%u-%u, serial %s",
++ d,
++ card->number,
++ instance->version.major,
++ instance->version.minor,
++ instance->version.build,
++ instance->version.hwrev,
++ instance->serial
++ );
++
++ strscpy(card->driver, "snd-pimidi", sizeof(card->driver));
++ snprintf(card->shortname, sizeof(card->shortname), "pimidi%d", d);
++ snprintf(card->longname, sizeof(card->longname), "pimidi%d %s", d, instance->serial);
++ snprintf(card->id, sizeof(card->id), "pimidi%d", d);
++
++ snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 0)->name,
++ 10u, "pimidi%d-a", d);
++ snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, 0)->name,
++ 10u, "pimidi%d-a", d);
++ snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 1)->name,
++ 10u, "pimidi%d-b", d);
++ snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, 1)->name,
++ 10u, "pimidi%d-b", d);
++
++ err = snd_card_ro_proc_new(card, "a-tx", &instance->midi_port[0].tx_cnt,
++ pimidi_proc_stat_show);
++ err = snd_card_ro_proc_new(card, "a-rx", &instance->midi_port[0].rx_cnt,
++ pimidi_proc_stat_show);
++ err = snd_card_ro_proc_new(card, "b-tx", &instance->midi_port[1].tx_cnt,
++ pimidi_proc_stat_show);
++ err = snd_card_ro_proc_new(card, "b-rx", &instance->midi_port[1].rx_cnt,
++ pimidi_proc_stat_show);
++ err = snd_card_ro_proc_new(card, "serial", instance, pimidi_proc_serial_show);
++ err = snd_card_ro_proc_new(card, "version", instance, pimidi_proc_version_show);
++ err = snd_card_ro_proc_new(card, "hwrev", instance, pimidi_proc_hwrev_show);
++ if (err < 0) {
++ printe(instance, "snd_card_ro_proc_new failed: %d", err);
++ goto finalize;
++ }
++
++ err = snd_card_register(card);
++ if (err < 0) {
++ printe(instance, "snd_card_register failed: %d", err);
++ goto finalize;
++ }
++
++finalize:
++ if (err) {
++ instance->stopping = true;
++ cancel_work_sync(&instance->drdy_handler);
++ mutex_destroy(&instance->comm_lock);
++ pimidi_unregister(instance);
++ snd_card_free(card);
++ return err;
++ }
++
++ for (i = 0; i < PIMIDI_PORTS; ++i) {
++ struct pimidi_midi_port *port = &instance->midi_port[i];
++
++ mutex_init(&port->in_lock);
++ mutex_init(&port->out_lock);
++ INIT_WORK(&port->in_handler,
++ i == 0 ? pimidi_midi_in_handler_0 : pimidi_midi_in_handler_1);
++ INIT_DELAYED_WORK(&port->out_handler,
++ i == 0 ? pimidi_midi_out_handler_0 : pimidi_midi_out_handler_1);
++ INIT_KFIFO(port->in_fifo);
++ port->last_output_at = jiffies;
++ }
++
++ i2c_set_clientdata(client, card);
++ return 0;
++}
++
++static void pimidi_i2c_remove(struct i2c_client *client)
++{
++ printd_g("(%p)", client);
++
++ int i;
++ struct snd_card *card = i2c_get_clientdata(client);
++
++ if (card) {
++ printi_g("Unloading hw:%d %s", card->number, card->longname);
++ struct pimidi_instance *instance = (struct pimidi_instance *)card->private_data;
++
++ instance->stopping = true;
++ i2c_set_clientdata(client, NULL);
++ devm_free_irq(&client->dev, client->irq, instance);
++ cancel_work_sync(&instance->drdy_handler);
++
++ for (i = 0; i < PIMIDI_PORTS; ++i) {
++ cancel_work_sync(&instance->midi_port[i].in_handler);
++ cancel_delayed_work_sync(&instance->midi_port[i].out_handler);
++ mutex_destroy(&instance->midi_port[i].out_lock);
++ mutex_destroy(&instance->midi_port[i].in_lock);
++ kfifo_free(&instance->midi_port[i].in_fifo);
++ }
++
++ mutex_destroy(&instance->comm_lock);
++ pimidi_unregister(instance);
++ snd_card_free(card);
++ }
++}
++
++static const struct i2c_device_id pimidi_i2c_ids[] = {
++ { "pimidi", 0 },
++ {}
++};
++MODULE_DEVICE_TABLE(i2c, pimidi_i2c_ids);
++
++static const struct of_device_id pimidi_i2c_dt_ids[] = {
++ { .compatible = "blokaslabs,pimidi", },
++ {}
++};
++MODULE_DEVICE_TABLE(of, pimidi_i2c_dt_ids);
++
++static struct i2c_driver pimidi_i2c_driver = {
++ .driver = {
++ .name = "pimidi",
++ .owner = THIS_MODULE,
++ .of_match_table = of_match_ptr(pimidi_i2c_dt_ids),
++ },
++ .probe = pimidi_i2c_probe,
++ .remove = pimidi_i2c_remove,
++ .id_table = pimidi_i2c_ids,
++};
++
++int pimidi_module_init(void)
++{
++ int err = 0;
++
++ mutex_init(&pimidi_global.lock);
++
++ INIT_LIST_HEAD(&pimidi_global.devices);
++
++ pimidi_global.work_queue = create_singlethread_workqueue("pimidi");
++ if (!pimidi_global.work_queue) {
++ err = -ENOMEM;
++ goto cleanup;
++ }
++
++ err = i2c_add_driver(&pimidi_i2c_driver);
++ if (err < 0)
++ goto cleanup;
++
++ crc8_populate_msb(pimidi_crc8_table, PIMIDI_CRC8_POLYNOMIAL);
++
++ return 0;
++
++cleanup:
++ mutex_destroy(&pimidi_global.lock);
++ return err;
++}
++
++void pimidi_module_exit(void)
++{
++ i2c_del_driver(&pimidi_i2c_driver);
++ mutex_lock(&pimidi_global.lock);
++ if (pimidi_global.reset_gpio) {
++ gpiod_put(pimidi_global.reset_gpio);
++ pimidi_global.reset_gpio = NULL;
++ }
++ mutex_unlock(&pimidi_global.lock);
++
++ destroy_workqueue(pimidi_global.work_queue);
++ pimidi_global.work_queue = NULL;
++
++ mutex_destroy(&pimidi_global.lock);
++}
++
++module_init(pimidi_module_init);
++module_exit(pimidi_module_exit);
++
++MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius <giedrius@blokas.io>");
++MODULE_DESCRIPTION("MIDI driver for Blokas Pimidi, https://blokas.io/");
++MODULE_LICENSE("GPL");
++
++/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+From 75ab92b077602734458f0a77e19a3599be29b93b Mon Sep 17 00:00:00 2001
+From: Giedrius <giedrius@blokas.io>
+Date: Thu, 21 Nov 2024 08:05:49 +0000
+Subject: [PATCH] Adding pimidi-overlay.dts
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io>
+---
+ arch/arm/boot/dts/overlays/Makefile | 1 +
+ arch/arm/boot/dts/overlays/README | 8 +++
+ arch/arm/boot/dts/overlays/pimidi-overlay.dts | 54 +++++++++++++++++++
+ 3 files changed, 63 insertions(+)
+ create mode 100644 arch/arm/boot/dts/overlays/pimidi-overlay.dts
+
+--- a/arch/arm/boot/dts/overlays/Makefile
++++ b/arch/arm/boot/dts/overlays/Makefile
+@@ -203,6 +203,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
+ pifi-dac-zero.dtbo \
+ pifi-mini-210.dtbo \
+ piglow.dtbo \
++ pimidi.dtbo \
+ pineboards-hat-ai.dtbo \
+ pineboards-hatdrive-poe-plus.dtbo \
+ piscreen.dtbo \
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -3701,6 +3701,14 @@ Load: dtoverlay=piglow
+ Params: <None>
+
+
++Name: pimidi
++Info: Configures the Blokas Labs Pimidi card
++Load: dtoverlay=pimidi,<param>=<val>
++Params: sel The position used for the sel rotary switch.
++ Each unit in the stack must be set on a unique
++ position. If param is omitted, sel=0 is assumed.
++
++
+ Name: pineboards-hat-ai
+ Info: Pineboards Hat Ai! overlay for the Google Coral Edge TPU
+ Load: dtoverlay=pineboards-hat-ai
+--- /dev/null
++++ b/arch/arm/boot/dts/overlays/pimidi-overlay.dts
+@@ -0,0 +1,54 @@
++/*
++ * Pimidi Linux kernel module.
++ * Copyright (C) 2017-2024 Vilniaus Blokas UAB, https://blokas.io/
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; version 2 of the
++ * License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++/dts-v1/;
++/plugin/;
++
++#include <dt-bindings/gpio/gpio.h>
++#include <dt-bindings/interrupt-controller/irq.h>
++
++/ {
++ compatible = "brcm,bcm2835";
++
++ fragment@0 {
++ target = <&i2c_arm>;
++ __overlay__ {
++ status = "okay";
++ clock-frequency=<1000000>;
++
++ pimidi_ctrl: pimidi_ctrl@20 {
++ compatible = "blokaslabs,pimidi";
++
++ reg = <0x20>;
++ status = "okay";
++
++ interrupt-parent = <&gpio>;
++ interrupts = <23 IRQ_TYPE_LEVEL_LOW>;
++ interrupt-names = "data_ready";
++ interrupt-controller;
++ #interrupt-cells = <2>;
++
++ data-ready-gpios = <&gpio 23 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
++ reset-gpios = <&gpio 22 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
++ };
++ };
++ };
++
++ __overrides__ {
++ sel = <&pimidi_ctrl>,"reg:0{0=0x20,1=0x21,2=0x22,3=0x23}",
++ <&pimidi_ctrl>,"data-ready-gpios:4{0=23,1=5,2=6,3=27}",
++ <&pimidi_ctrl>,"interrupts:0{0=23,1=5,2=6,3=27}";
++ };
++};
--- /dev/null
+From a1e4b72997dc3ef423b6f510bfead470475750d4 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Thu, 1 Nov 2018 17:31:37 +0000
+Subject: [PATCH] staging: vchiq_arm: Add 36-bit address support
+
+Conditional on a new compatible string, change the pagelist encoding
+such that the top 24 bits are the pfn, leaving 8 bits for run length
+(-1), giving a 36-bit address range.
+
+Manage the split between addresses for the VPU and addresses for the
+40-bit DMA controller with a dedicated DMA device pointer that on non-
+BCM2711 platforms is the same as the main VCHIQ device. This allows
+the VCHIQ node to stay in the usual place in the DT.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ .../interface/vchiq_arm/vchiq_arm.c | 125 +++++++++++++-----
+ 1 file changed, 90 insertions(+), 35 deletions(-)
+
+--- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
++++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
+@@ -73,6 +73,7 @@ static struct platform_device *bcm2835_i
+
+ struct vchiq_drvdata {
+ const unsigned int cache_line_size;
++ const bool use_36bit_addrs;
+ struct rpi_firmware *fw;
+ };
+
+@@ -118,6 +119,11 @@ struct vchiq_arm_state {
+ int first_connect;
+ };
+
++static struct vchiq_drvdata bcm2711_drvdata = {
++ .cache_line_size = 64,
++ .use_36bit_addrs = true,
++};
++
+ struct vchiq_pagelist_info {
+ struct pagelist *pagelist;
+ size_t pagelist_buffer_size;
+@@ -142,10 +148,12 @@ static void __iomem *g_regs;
+ * of 32.
+ */
+ static unsigned int g_cache_line_size = 32;
++static unsigned int g_use_36bit_addrs = 0;
+ static unsigned int g_fragments_size;
+ static char *g_fragments_base;
+ static char *g_free_fragments;
+ static struct semaphore g_free_fragments_sema;
++static struct device *g_dma_dev;
+
+ static DEFINE_SEMAPHORE(g_free_fragments_mutex, 1);
+
+@@ -175,7 +183,7 @@ static void
+ cleanup_pagelistinfo(struct vchiq_instance *instance, struct vchiq_pagelist_info *pagelistinfo)
+ {
+ if (pagelistinfo->scatterlist_mapped) {
+- dma_unmap_sg(instance->state->dev, pagelistinfo->scatterlist,
++ dma_unmap_sg(g_dma_dev, pagelistinfo->scatterlist,
+ pagelistinfo->num_pages, pagelistinfo->dma_dir);
+ }
+
+@@ -335,7 +343,7 @@ create_pagelist(struct vchiq_instance *i
+ count -= len;
+ }
+
+- dma_buffers = dma_map_sg(instance->state->dev,
++ dma_buffers = dma_map_sg(g_dma_dev,
+ scatterlist,
+ num_pages,
+ pagelistinfo->dma_dir);
+@@ -349,22 +357,61 @@ create_pagelist(struct vchiq_instance *i
+
+ /* Combine adjacent blocks for performance */
+ k = 0;
+- for_each_sg(scatterlist, sg, dma_buffers, i) {
+- u32 len = sg_dma_len(sg);
+- u32 addr = sg_dma_address(sg);
+-
+- /* Note: addrs is the address + page_count - 1
+- * The firmware expects blocks after the first to be page-
+- * aligned and a multiple of the page size
+- */
+- WARN_ON(len == 0);
+- WARN_ON(i && (i != (dma_buffers - 1)) && (len & ~PAGE_MASK));
+- WARN_ON(i && (addr & ~PAGE_MASK));
+- if (is_adjacent_block(addrs, addr, k))
+- addrs[k - 1] += ((len + PAGE_SIZE - 1) >> PAGE_SHIFT);
+- else
+- addrs[k++] = (addr & PAGE_MASK) |
+- (((len + PAGE_SIZE - 1) >> PAGE_SHIFT) - 1);
++ if (g_use_36bit_addrs) {
++ for_each_sg(scatterlist, sg, dma_buffers, i) {
++ u32 len = sg_dma_len(sg);
++ u64 addr = sg_dma_address(sg);
++ u32 page_id = (u32)((addr >> 4) & ~0xff);
++ u32 sg_pages = (len + PAGE_SIZE - 1) >> PAGE_SHIFT;
++
++ /* Note: addrs is the address + page_count - 1
++ * The firmware expects blocks after the first to be page-
++ * aligned and a multiple of the page size
++ */
++ WARN_ON(len == 0);
++ WARN_ON(i &&
++ (i != (dma_buffers - 1)) && (len & ~PAGE_MASK));
++ WARN_ON(i && (addr & ~PAGE_MASK));
++ WARN_ON(upper_32_bits(addr) > 0xf);
++
++ if (k > 0 &&
++ ((addrs[k - 1] & ~0xff) +
++ (((addrs[k - 1] & 0xff) + 1) << 8)
++ == page_id)) {
++ u32 inc_pages = min(sg_pages,
++ 0xff - (addrs[k - 1] & 0xff));
++ addrs[k - 1] += inc_pages;
++ page_id += inc_pages << 8;
++ sg_pages -= inc_pages;
++ }
++ while (sg_pages) {
++ u32 inc_pages = min(sg_pages, 0x100u);
++ addrs[k++] = page_id | (inc_pages - 1);
++ page_id += inc_pages << 8;
++ sg_pages -= inc_pages;
++ }
++ }
++ } else {
++ for_each_sg(scatterlist, sg, dma_buffers, i) {
++ u32 len = sg_dma_len(sg);
++ u32 addr = sg_dma_address(sg);
++ u32 new_pages = (len + PAGE_SIZE - 1) >> PAGE_SHIFT;
++
++ /* Note: addrs is the address + page_count - 1
++ * The firmware expects blocks after the first to be page-
++ * aligned and a multiple of the page size
++ */
++ WARN_ON(len == 0);
++ WARN_ON(i && (i != (dma_buffers - 1)) && (len & ~PAGE_MASK));
++ WARN_ON(i && (addr & ~PAGE_MASK));
++ if (k > 0 &&
++ ((addrs[k - 1] & PAGE_MASK) +
++ (((addrs[k - 1] & ~PAGE_MASK) + 1) << PAGE_SHIFT))
++ == (addr & PAGE_MASK))
++ addrs[k - 1] += new_pages;
++ else
++ addrs[k++] = (addr & PAGE_MASK) | (new_pages - 1);
++ }
+ }
+
+ /* Partial cache lines (fragments) require special measures */
+@@ -408,7 +455,7 @@ free_pagelist(struct vchiq_instance *ins
+ * NOTE: dma_unmap_sg must be called before the
+ * cpu can touch any of the data/pages.
+ */
+- dma_unmap_sg(instance->state->dev, pagelistinfo->scatterlist,
++ dma_unmap_sg(g_dma_dev, pagelistinfo->scatterlist,
+ pagelistinfo->num_pages, pagelistinfo->dma_dir);
+ pagelistinfo->scatterlist_mapped = 0;
+
+@@ -463,6 +510,7 @@ free_pagelist(struct vchiq_instance *ins
+ static int vchiq_platform_init(struct platform_device *pdev, struct vchiq_state *state)
+ {
+ struct device *dev = &pdev->dev;
++ struct device *dma_dev = NULL;
+ struct vchiq_drvdata *drvdata = platform_get_drvdata(pdev);
+ struct rpi_firmware *fw = drvdata->fw;
+ struct vchiq_slot_zero *vchiq_slot_zero;
+@@ -484,6 +532,24 @@ static int vchiq_platform_init(struct pl
+ g_cache_line_size = drvdata->cache_line_size;
+ g_fragments_size = 2 * g_cache_line_size;
+
++ if (drvdata->use_36bit_addrs) {
++ struct device_node *dma_node =
++ of_find_compatible_node(NULL, NULL, "brcm,bcm2711-dma");
++
++ if (dma_node) {
++ struct platform_device *pdev;
++
++ pdev = of_find_device_by_node(dma_node);
++ if (pdev)
++ dma_dev = &pdev->dev;
++ of_node_put(dma_node);
++ g_use_36bit_addrs = true;
++ } else {
++ dev_err(dev, "40-bit DMA controller not found\n");
++ return -EINVAL;
++ }
++ }
++
+ /* Allocate space for the channels in coherent memory */
+ slot_mem_size = PAGE_ALIGN(TOTAL_SLOTS * VCHIQ_SLOT_SIZE);
+ frag_mem_size = PAGE_ALIGN(g_fragments_size * MAX_FRAGMENTS);
+@@ -496,13 +562,14 @@ static int vchiq_platform_init(struct pl
+ }
+
+ WARN_ON(((unsigned long)slot_mem & (PAGE_SIZE - 1)) != 0);
++ channelbase = slot_phys;
+
+ vchiq_slot_zero = vchiq_init_slots(slot_mem, slot_mem_size);
+ if (!vchiq_slot_zero)
+ return -ENOMEM;
+
+ vchiq_slot_zero->platform_data[VCHIQ_PLATFORM_FRAGMENTS_OFFSET_IDX] =
+- (int)slot_phys + slot_mem_size;
++ channelbase + slot_mem_size;
+ vchiq_slot_zero->platform_data[VCHIQ_PLATFORM_FRAGMENTS_COUNT_IDX] =
+ MAX_FRAGMENTS;
+
+@@ -536,7 +603,6 @@ static int vchiq_platform_init(struct pl
+ }
+
+ /* Send the base address of the slots to VideoCore */
+- channelbase = slot_phys;
+ err = rpi_firmware_property(fw, RPI_FIRMWARE_VCHIQ_INIT,
+ &channelbase, sizeof(channelbase));
+ if (err) {
+@@ -550,6 +616,8 @@ static int vchiq_platform_init(struct pl
+ return -ENXIO;
+ }
+
++ g_dma_dev = dma_dev ?: dev;
++
+ vchiq_log_info(vchiq_arm_log_level, "vchiq_init - done (slots %pK, phys %pad)",
+ vchiq_slot_zero, &slot_phys);
+
+@@ -1755,6 +1823,7 @@ void vchiq_platform_conn_state_changed(s
+ static const struct of_device_id vchiq_of_match[] = {
+ { .compatible = "brcm,bcm2835-vchiq", .data = &bcm2835_drvdata },
+ { .compatible = "brcm,bcm2836-vchiq", .data = &bcm2836_drvdata },
++ { .compatible = "brcm,bcm2711-vchiq", .data = &bcm2711_drvdata },
+ {},
+ };
+ MODULE_DEVICE_TABLE(of, vchiq_of_match);
+@@ -1787,22 +1856,8 @@ vchiq_register_child(struct platform_dev
+
+ child->dev.of_node = np;
+
+- /*
+- * We want the dma-ranges etc to be copied from a device with the
+- * correct dma-ranges for the VPU.
+- * VCHIQ on Pi4 is now under scb which doesn't get those dma-ranges.
+- * Take the "dma" node as going to be suitable as it sees the world
+- * through the same eyes as the VPU.
+- */
+- np = of_find_node_by_path("dma");
+- if (!np)
+- np = pdev->dev.of_node;
+-
+ of_dma_configure(&child->dev, np, true);
+
+- if (np != pdev->dev.of_node)
+- of_node_put(np);
+-
+ return child;
+ }
+
--- /dev/null
+From 1129091b2d95273d930acf2926a569b90512a248 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Tue, 21 Jul 2020 17:34:09 +0100
+Subject: [PATCH] staging: vchiq_arm: children inherit DMA config
+
+Although it is no longer necessary for vchiq's children to have a
+different DMA configuration to the parent, they do still need to
+explicitly to have their DMA configuration set - to be that of the
+parent.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ .../vc04_services/interface/vchiq_arm/vchiq_arm.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+--- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
++++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
+@@ -1856,8 +1856,18 @@ vchiq_register_child(struct platform_dev
+
+ child->dev.of_node = np;
+
++ /*
++ * We want the dma-ranges etc to be copied from the parent VCHIQ device
++ * to be passed on to the children without a node of their own.
++ */
++ if (!np)
++ np = pdev->dev.of_node;
++
+ of_dma_configure(&child->dev, np, true);
+
++ if (np != pdev->dev.of_node)
++ of_node_put(np);
++
+ return child;
+ }
+
--- /dev/null
+From 2d26a598ceceaea8a6837146c741eb742bbd4baa Mon Sep 17 00:00:00 2001
+From: detule <ogjoneski@gmail.com>
+Date: Tue, 2 Oct 2018 04:10:08 -0400
+Subject: [PATCH] staging: vchiq_arm: Usa a DMA pool for small bulks
+
+During a bulk transfer we request a DMA allocation to hold the
+scatter-gather list. Most of the time, this allocation is small
+(<< PAGE_SIZE), however it can be requested at a high enough frequency
+to cause fragmentation and/or stress the CMA allocator (think time
+spent in compaction here, or during allocations elsewhere).
+
+Implement a pool to serve up small DMA allocations, falling back
+to a coherent allocation if the request is greater than
+VCHIQ_DMA_POOL_SIZE.
+
+Signed-off-by: Oliver Gjoneski <ogjoneski@gmail.com>
+---
+ .../interface/vchiq_arm/vchiq_arm.c | 33 ++++++++++++++++---
+ 1 file changed, 29 insertions(+), 4 deletions(-)
+
+--- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
++++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
+@@ -22,6 +22,7 @@
+ #include <linux/platform_device.h>
+ #include <linux/compat.h>
+ #include <linux/dma-mapping.h>
++#include <linux/dmapool.h>
+ #include <linux/rcupdate.h>
+ #include <linux/delay.h>
+ #include <linux/slab.h>
+@@ -51,6 +52,8 @@
+
+ #define ARM_DS_ACTIVE BIT(2)
+
++#define VCHIQ_DMA_POOL_SIZE PAGE_SIZE
++
+ /* Override the default prefix, which would be vchiq_arm (from the filename) */
+ #undef MODULE_PARAM_PREFIX
+ #define MODULE_PARAM_PREFIX DEVICE_NAME "."
+@@ -128,6 +131,7 @@ struct vchiq_pagelist_info {
+ struct pagelist *pagelist;
+ size_t pagelist_buffer_size;
+ dma_addr_t dma_addr;
++ bool is_from_pool;
+ enum dma_data_direction dma_dir;
+ unsigned int num_pages;
+ unsigned int pages_need_release;
+@@ -148,6 +152,7 @@ static void __iomem *g_regs;
+ * of 32.
+ */
+ static unsigned int g_cache_line_size = 32;
++static struct dma_pool *g_dma_pool;
+ static unsigned int g_use_36bit_addrs = 0;
+ static unsigned int g_fragments_size;
+ static char *g_fragments_base;
+@@ -190,8 +195,13 @@ cleanup_pagelistinfo(struct vchiq_instan
+ if (pagelistinfo->pages_need_release)
+ unpin_user_pages(pagelistinfo->pages, pagelistinfo->num_pages);
+
+- dma_free_coherent(instance->state->dev, pagelistinfo->pagelist_buffer_size,
+- pagelistinfo->pagelist, pagelistinfo->dma_addr);
++ if (pagelistinfo->is_from_pool) {
++ dma_pool_free(g_dma_pool, pagelistinfo->pagelist,
++ pagelistinfo->dma_addr);
++ } else {
++ dma_free_coherent(instance->state->dev, pagelistinfo->pagelist_buffer_size,
++ pagelistinfo->pagelist, pagelistinfo->dma_addr);
++ }
+ }
+
+ static inline bool
+@@ -226,6 +236,7 @@ create_pagelist(struct vchiq_instance *i
+ u32 *addrs;
+ unsigned int num_pages, offset, i, k;
+ int actual_pages;
++ bool is_from_pool;
+ size_t pagelist_size;
+ struct scatterlist *scatterlist, *sg;
+ int dma_buffers;
+@@ -255,8 +266,14 @@ create_pagelist(struct vchiq_instance *i
+ /* Allocate enough storage to hold the page pointers and the page
+ * list
+ */
+- pagelist = dma_alloc_coherent(instance->state->dev, pagelist_size, &dma_addr,
+- GFP_KERNEL);
++ if (pagelist_size > VCHIQ_DMA_POOL_SIZE) {
++ pagelist = dma_alloc_coherent(instance->state->dev, pagelist_size, &dma_addr,
++ GFP_KERNEL);
++ is_from_pool = false;
++ } else {
++ pagelist = dma_pool_alloc(g_dma_pool, GFP_KERNEL, &dma_addr);
++ is_from_pool = true;
++ }
+
+ vchiq_log_trace(vchiq_arm_log_level, "%s - %pK", __func__, pagelist);
+
+@@ -277,6 +294,7 @@ create_pagelist(struct vchiq_instance *i
+ pagelistinfo->pagelist = pagelist;
+ pagelistinfo->pagelist_buffer_size = pagelist_size;
+ pagelistinfo->dma_addr = dma_addr;
++ pagelistinfo->is_from_pool = is_from_pool;
+ pagelistinfo->dma_dir = (type == PAGELIST_WRITE) ?
+ DMA_TO_DEVICE : DMA_FROM_DEVICE;
+ pagelistinfo->num_pages = num_pages;
+@@ -617,6 +635,13 @@ static int vchiq_platform_init(struct pl
+ }
+
+ g_dma_dev = dma_dev ?: dev;
++ g_dma_pool = dmam_pool_create("vchiq_scatter_pool", dev,
++ VCHIQ_DMA_POOL_SIZE, g_cache_line_size,
++ 0);
++ if (!g_dma_pool) {
++ dev_err(dev, "failed to create dma pool");
++ return -ENOMEM;
++ }
+
+ vchiq_log_info(vchiq_arm_log_level, "vchiq_init - done (slots %pK, phys %pad)",
+ vchiq_slot_zero, &slot_phys);
--- /dev/null
+From 5b29221e96d1ba60a78d5c804a20fa35a6d0517a Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Fri, 29 Apr 2022 09:19:10 +0100
+Subject: [PATCH] staging: vchiq_arm: Add log_level module params
+
+Add module parameters to control the logging levels for the various
+vchiq logging categories.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ .../staging/vc04_services/interface/vchiq_arm/vchiq_arm.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+--- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
++++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
+@@ -64,6 +64,11 @@
+ /* Run time control of log level, based on KERN_XXX level. */
+ int vchiq_arm_log_level = VCHIQ_LOG_DEFAULT;
+ int vchiq_susp_log_level = VCHIQ_LOG_ERROR;
++module_param_named(arm_log_level, vchiq_arm_log_level, int, 0644);
++module_param_named(susp_log_level, vchiq_susp_log_level, int, 0644);
++module_param_named(core_log_level, vchiq_core_log_level, int, 0644);
++module_param_named(core_msg_log_level, vchiq_core_msg_log_level, int, 0644);
++module_param_named(sync_log_level, vchiq_sync_log_level, int, 0644);
+
+ DEFINE_SPINLOCK(msg_queue_spinlock);
+ struct vchiq_state g_state;
--- /dev/null
+From 8691544f688bd3ae9b6db0845a75ce230fc9e90f Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Thu, 21 Nov 2024 15:54:58 +0000
+Subject: [PATCH] media: i2c: imx477: Fix link frequency menu
+
+"media: i2c: imx477: Add options for slightly modifying the link freq"
+created a link frequency menu with 2 items in instead of one.
+Correct this.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx477.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/media/i2c/imx477.c
++++ b/drivers/media/i2c/imx477.c
+@@ -2051,7 +2051,7 @@ static int imx477_init_controls(struct i
+ /* LINK_FREQ is also read only */
+ imx477->link_freq =
+ v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx477_ctrl_ops,
+- V4L2_CID_LINK_FREQ, 1, 0,
++ V4L2_CID_LINK_FREQ, 0, 0,
+ &link_freqs[imx477->link_freq_idx]);
+ if (imx477->link_freq)
+ imx477->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
--- /dev/null
+From 99a0201bb0abc946dc431214b638b2cc6b01dda5 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Mon, 25 Nov 2024 16:19:55 +0000
+Subject: [PATCH] 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>
+---
+ include/linux/pio_rp1.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/include/linux/pio_rp1.h
++++ b/include/linux/pio_rp1.h
+@@ -320,7 +320,7 @@ static inline int pio_sm_unclaim(struct
+ if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+ return -EINVAL;
+
+- return rp1_pio_sm_claim(client, &args);
++ return rp1_pio_sm_unclaim(client, &args);
+ }
+
+ static inline int pio_claim_unused_sm(struct rp1_pio_client *client, bool required)
--- /dev/null
+From 3687701e8d252864f440f91f1aedf8ffd58d6ee6 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Mon, 25 Nov 2024 21:51:13 +0000
+Subject: [PATCH] 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>
+---
+ include/linux/pio_rp1.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/include/linux/pio_rp1.h
++++ b/include/linux/pio_rp1.h
+@@ -20,7 +20,7 @@
+ #endif
+
+ #define bad_params_if(client, test) \
+- ({ bool f = (test); if (f) pio_set_error(client, -EINVAL); \
++ ({ bool f = (test); if (f && client) pio_set_error(client, -EINVAL); \
+ if (f && PARAM_WARNINGS_ENABLED) WARN_ON((test)); \
+ f; })
+
--- /dev/null
+From 008c93b47b9b965368eb5bbfbef60b816931e0ab Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 20 Nov 2024 13:58:08 +0000
+Subject: [PATCH] drm: vc4: dsi: Handle the different command FIFO widths
+
+DSI0 and DSI1 have different widths for the command FIFO (24bit
+vs 32bit), but the driver was assuming the 32bit width of DSI1
+in all cases.
+DSI0 also wants the data packed as 24bit big endian, so the
+formatting code needs updating.
+
+Handle the difference via the variant structure.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/gpu/drm/vc4/vc4_dsi.c | 64 ++++++++++++++++++++++++-----------
+ 1 file changed, 44 insertions(+), 20 deletions(-)
+
+--- a/drivers/gpu/drm/vc4/vc4_dsi.c
++++ b/drivers/gpu/drm/vc4/vc4_dsi.c
+@@ -44,7 +44,6 @@
+
+ #define DSI_CMD_FIFO_DEPTH 16
+ #define DSI_PIX_FIFO_DEPTH 256
+-#define DSI_PIX_FIFO_WIDTH 4
+
+ #define DSI0_CTRL 0x00
+
+@@ -170,11 +169,15 @@
+ #define DSI1_DISP1_CTRL 0x2c
+ /* Format of the data written to TXPKT_PIX_FIFO. */
+ # define DSI_DISP1_PFORMAT_MASK VC4_MASK(2, 1)
+-# define DSI_DISP1_PFORMAT_SHIFT 1
+-# define DSI_DISP1_PFORMAT_16BIT 0
+-# define DSI_DISP1_PFORMAT_24BIT 1
+-# define DSI_DISP1_PFORMAT_32BIT_LE 2
+-# define DSI_DISP1_PFORMAT_32BIT_BE 3
++# define DSI1_DISP1_PFORMAT_SHIFT 1
++# define DSI0_DISP1_PFORMAT_16BIT 0
++# define DSI0_DISP1_PFORMAT_16BIT_ADJ 1
++# define DSI0_DISP1_PFORMAT_24BIT 2
++# define DSI0_DISP1_PFORMAT_32BIT_LE 3 /* NB Invalid, but required for macros to work */
++# define DSI1_DISP1_PFORMAT_16BIT 0
++# define DSI1_DISP1_PFORMAT_24BIT 1
++# define DSI1_DISP1_PFORMAT_32BIT_LE 2
++# define DSI1_DISP1_PFORMAT_32BIT_BE 3
+
+ /* DISP1 is always command mode. */
+ # define DSI_DISP1_ENABLE BIT(0)
+@@ -553,6 +556,7 @@ struct vc4_dsi_variant {
+ unsigned int port;
+
+ bool broken_axi_workaround;
++ unsigned int cmd_fifo_width;
+
+ const char *debugfs_name;
+ const struct debugfs_reg32 *regs;
+@@ -1151,10 +1155,16 @@ static void vc4_dsi_bridge_pre_enable(st
+ /* Set up DISP1 for transferring long command payloads through
+ * the pixfifo.
+ */
+- DSI_PORT_WRITE(DISP1_CTRL,
+- VC4_SET_FIELD(DSI_DISP1_PFORMAT_32BIT_LE,
+- DSI_DISP1_PFORMAT) |
+- DSI_DISP1_ENABLE);
++ if (dsi->variant->cmd_fifo_width == 4)
++ DSI_PORT_WRITE(DISP1_CTRL,
++ VC4_SET_FIELD(DSI_PORT_BIT(DISP1_PFORMAT_32BIT_LE),
++ DSI_DISP1_PFORMAT) |
++ DSI_DISP1_ENABLE);
++ else
++ DSI_PORT_WRITE(DISP1_CTRL,
++ VC4_SET_FIELD(DSI_PORT_BIT(DISP1_PFORMAT_24BIT),
++ DSI_DISP1_PFORMAT) |
++ DSI_DISP1_ENABLE);
+
+ /* Bring AFE out of reset. */
+ DSI_PORT_WRITE(PHY_AFEC0,
+@@ -1235,9 +1245,9 @@ static ssize_t vc4_dsi_transfer(struct v
+ pix_fifo_len = 0;
+ } else {
+ cmd_fifo_len = (packet.payload_length %
+- DSI_PIX_FIFO_WIDTH);
++ dsi->variant->cmd_fifo_width);
+ pix_fifo_len = ((packet.payload_length - cmd_fifo_len) /
+- DSI_PIX_FIFO_WIDTH);
++ dsi->variant->cmd_fifo_width);
+ }
+
+ WARN_ON_ONCE(pix_fifo_len >= DSI_PIX_FIFO_DEPTH);
+@@ -1255,14 +1265,25 @@ static ssize_t vc4_dsi_transfer(struct v
+
+ for (i = 0; i < cmd_fifo_len; i++)
+ DSI_PORT_WRITE(TXPKT_CMD_FIFO, packet.payload[i]);
+- for (i = 0; i < pix_fifo_len; i++) {
+- const u8 *pix = packet.payload + cmd_fifo_len + i * 4;
++ if (dsi->variant->cmd_fifo_width == 4) {
++ for (i = 0; i < pix_fifo_len; i++) {
++ const u8 *pix = packet.payload + cmd_fifo_len + i * 4;
++
++ DSI_PORT_WRITE(TXPKT_PIX_FIFO,
++ pix[0] |
++ pix[1] << 8 |
++ pix[2] << 16 |
++ pix[3] << 24);
++ }
++ } else {
++ for (i = 0; i < pix_fifo_len; i++) {
++ const u8 *pix = packet.payload + cmd_fifo_len + i * 3;
+
+- DSI_PORT_WRITE(TXPKT_PIX_FIFO,
+- pix[0] |
+- pix[1] << 8 |
+- pix[2] << 16 |
+- pix[3] << 24);
++ DSI_PORT_WRITE(TXPKT_PIX_FIFO,
++ pix[2] |
++ pix[1] << 8 |
++ pix[0] << 16);
++ }
+ }
+
+ if (msg->flags & MIPI_DSI_MSG_USE_LPM)
+@@ -1516,6 +1537,7 @@ static const struct drm_encoder_funcs vc
+
+ static const struct vc4_dsi_variant bcm2711_dsi1_variant = {
+ .port = 1,
++ .cmd_fifo_width = 4,
+ .debugfs_name = "dsi1_regs",
+ .regs = dsi1_regs,
+ .nregs = ARRAY_SIZE(dsi1_regs),
+@@ -1523,6 +1545,7 @@ static const struct vc4_dsi_variant bcm2
+
+ static const struct vc4_dsi_variant bcm2835_dsi0_variant = {
+ .port = 0,
++ .cmd_fifo_width = 3,
+ .debugfs_name = "dsi0_regs",
+ .regs = dsi0_regs,
+ .nregs = ARRAY_SIZE(dsi0_regs),
+@@ -1530,6 +1553,7 @@ static const struct vc4_dsi_variant bcm2
+
+ static const struct vc4_dsi_variant bcm2835_dsi1_variant = {
+ .port = 1,
++ .cmd_fifo_width = 4,
+ .broken_axi_workaround = true,
+ .debugfs_name = "dsi1_regs",
+ .regs = dsi1_regs,
--- /dev/null
+From eafaa6015fc0ed676f6115905e7c4145d23f5b7d Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Tue, 26 Nov 2024 15:53:24 +0000
+Subject: [PATCH] dts: bcm2712-rpi: For CM5IO, i2c_csi_dsi needs to be
+ CAM/DISP1
+
+Noted setting up a display on CM5IO. Add
+"dtoverlay=vc4-kms-dsi-ili7881-7inch" fails as it tries to
+find the regulator/backlight/touch on i2c_csi_dsi, which pointed
+at i2c_csi_dsi0 by default.
+
+Adding the dsi0 override updated to point at dsi0, and pointed
+the i2c at i2c_csi_dsi0, which all works.
+
+The default with i2c_csi_dsi needs to be consistent in using
+dsi1/csi1 and the corresponding i2c interface (i2c_csi_dsi1).
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5io.dtsi | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5io.dtsi
++++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5io.dtsi
+@@ -11,4 +11,4 @@ i2c_csi_dsi0: &i2c6 { // Note: This is f
+ symlink = "i2c-6";
+ };
+
+-i2c_csi_dsi: &i2c_csi_dsi0 { }; // The connector that needs no jumper to enable
++i2c_csi_dsi: &i2c_csi_dsi1 { }; // The connector that needs no jumper to enable
--- /dev/null
+From d128c123754e9dd03ad72c16851a1652331d6da1 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Wed, 27 Nov 2024 10:24:47 +0000
+Subject: [PATCH] dts: bcm2712-rpi-cm5: Remove inaccessible USB_OC_N
+
+Although VBUS_EN on GPIO42 appears on the CM5's 100-way headers,
+USB_OC_N on GPIO43 does not. Remove the signal name to avoid further
+confusion and disappointment.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi
++++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi
+@@ -718,7 +718,7 @@ spi10_cs_pins: &spi10_cs_gpio1 {};
+ "-", // GPIO40
+ "-", // GPIO41
+ "USB_VBUS_EN", // GPIO42
+- "USB_OC_N", // GPIO43
++ "-", // GPIO43
+ "RP1_STAT_LED", // GPIO44
+ "FAN_PWM", // GPIO45
+ "-", // GPIO46
--- /dev/null
+From 77389e715039b1feac9c6261727600892cc12fdb Mon Sep 17 00:00:00 2001
+From: Michael Heimpold <michael.heimpold@chargebyte.com>
+Date: Fri, 29 Nov 2024 14:10:04 +0100
+Subject: [PATCH] overlays: qca7000: replace URL with textual hint
+
+The deep link into the website is not that stable, so let's
+replace it with a textual description where to find the
+product information.
+
+Signed-off-by: Michael Heimpold <michael.heimpold@chargebyte.com>
+---
+ arch/arm/boot/dts/overlays/qca7000-overlay.dts | 2 +-
+ arch/arm/boot/dts/overlays/qca7000-uart0-overlay.dts | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+--- a/arch/arm/boot/dts/overlays/qca7000-overlay.dts
++++ b/arch/arm/boot/dts/overlays/qca7000-overlay.dts
+@@ -1,5 +1,5 @@
+ // Overlay for the Qualcomm Atheros QCA7000 on PLC Stamp micro EVK
+-// Visit: https://chargebyte.com/products/evaluation-tools/plc-stamp-micro-2-evaluation-board for details
++// Visit: https://chargebyte.com -> Controllers & Modules -> Evaluation Tools -> PLC Stamp Micro 2 Evaluation Board for details
+
+ /dts-v1/;
+ /plugin/;
+--- a/arch/arm/boot/dts/overlays/qca7000-uart0-overlay.dts
++++ b/arch/arm/boot/dts/overlays/qca7000-uart0-overlay.dts
+@@ -1,5 +1,5 @@
+ // Overlay for the Qualcomm Atheros QCA7000 on PLC Stamp micro EVK
+-// Visit: https://in-tech-smartcharging.com/products/evaluation-tools/plc-stamp-micro-2-evaluation-board for details
++// Visit: https://chargebyte.com -> Controllers & Modules -> Evaluation Tools -> PLC Stamp Micro 2 Evaluation Board for details
+
+ /dts-v1/;
+ /plugin/;
--- /dev/null
+From 178f1c2747c3920723242f26ba290785d45bffae Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Mon, 11 Nov 2024 16:38:01 +0000
+Subject: [PATCH] dt-bindings: net: cdns,macb: Add compatible for Raspberry Pi
+ RP1
+
+The Raspberry Pi RP1 chip has the Cadence GEM ethernet
+controller, so add a compatible string for it.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ Documentation/devicetree/bindings/net/cdns,macb.yaml | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/Documentation/devicetree/bindings/net/cdns,macb.yaml
++++ b/Documentation/devicetree/bindings/net/cdns,macb.yaml
+@@ -54,6 +54,7 @@ properties:
+ - cdns,np4-macb # NP4 SoC devices
+ - microchip,sama7g5-emac # Microchip SAMA7G5 ethernet interface
+ - microchip,sama7g5-gem # Microchip SAMA7G5 gigabit ethernet interface
++ - raspberrypi,rp1-gem # Raspberry Pi RP1 gigabit ethernet interface
+ - sifive,fu540-c000-gem # SiFive FU540-C000 SoC
+ - cdns,emac # Generic
+ - cdns,gem # Generic
From f9f0024bd9bf04a58b64bae356be4c04022d23bc Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Mon, 11 Nov 2024 16:40:07 +0000
-Subject: [PATCH 1423/1482] net: macb: Add support for Raspberry Pi RP1
- ethernet controller
+Subject: [PATCH] net: macb: Add support for Raspberry Pi RP1 ethernet
+ controller
The RP1 chip has the Cadence GEM block, but wants the tx_clock
to always run at 125MHz, in the same way as sama7g5.
From 33c225f622d596034a9261316666089a92aa6834 Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Mon, 25 Nov 2024 12:30:06 +0000
-Subject: [PATCH 1424/1482] rp1: clk: Only set PLL_SEC_RST in
- rp1_pll_divider_off
+Subject: [PATCH] rp1: clk: Only set PLL_SEC_RST in rp1_pll_divider_off
Rather than clearing all the bits in rp1_pll_divider_off
and setting PLL_SEC_RST, retain the status of all the other
From eb836a6a299322a8e2b9627cccd23c7a76d068ba Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Fri, 8 Nov 2024 17:36:13 +0000
-Subject: [PATCH 1425/1482] rp1: clk: Rationalise the use of the
- CLK_IS_CRITICAL flag
+Subject: [PATCH] rp1: clk: Rationalise the use of the CLK_IS_CRITICAL flag
The clock setup had been copied from clk-bcm2835 which had to cope
with the firmware having configured clocks, so there were flags
From 0b4af929b7125abd3a262577b380c7c81ee9b1c5 Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Mon, 11 Nov 2024 15:18:14 +0000
-Subject: [PATCH 1426/1482] dt: arm64: Fixup RP1 ethernet DT configuration
+Subject: [PATCH] dt: arm64: Fixup RP1 ethernet DT configuration
Configure RP1's ethernet block to do the correct thing.
clk_eth is intended to be fixed at 125MHz, so use a new compatible,
--- a/arch/arm64/boot/dts/broadcom/rp1.dtsi
+++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi
-@@ -24,6 +24,7 @@
+@@ -32,6 +32,7 @@
// RP1_PLL_VIDEO_CORE and dividers are now managed by VEC,DPI drivers
<&rp1_clocks RP1_PLL_SYS>,
<&rp1_clocks RP1_PLL_SYS_SEC>,
<&rp1_clocks RP1_PLL_AUDIO>,
<&rp1_clocks RP1_PLL_AUDIO_SEC>,
<&rp1_clocks RP1_CLK_SYS>,
-@@ -38,6 +39,7 @@
+@@ -46,6 +47,7 @@
<1536000000>, // RP1_PLL_AUDIO_CORE
<200000000>, // RP1_PLL_SYS
<125000000>, // RP1_PLL_SYS_SEC
<61440000>, // RP1_PLL_AUDIO
<192000000>, // RP1_PLL_AUDIO_SEC
<200000000>, // RP1_CLK_SYS
-@@ -968,12 +970,14 @@
+@@ -976,12 +978,14 @@
rp1_eth: ethernet@100000 {
reg = <0xc0 0x40100000 0x0 0x4000>;
From d4e41ed9954fa86c4774f98d393aa401c81a68e7 Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Wed, 13 Nov 2024 13:10:27 +0000
-Subject: [PATCH 1427/1482] clk: rp1: Add RP1_CLK_DMA.
+Subject: [PATCH] clk: rp1: Add RP1_CLK_DMA.
The DMA block has a clock, but wasn't defined in the driver. This
resulted in the parent being disabled as unused, and then DMA
From 9049e4df2c54b5e620f855f66db3a18c9f2e181f Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Fri, 8 Nov 2024 17:37:08 +0000
-Subject: [PATCH 1428/1482] rp1: clk: Remove CLK_IGNORE_UNUSED flags
+Subject: [PATCH] rp1: clk: Remove CLK_IGNORE_UNUSED flags
There should be no issue in disabling the RP1 clocks as long as
the kernel knows about all consumers.
From 542d0f7f2e9f90fc0f02f8cb141f7c3fbf46081b Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Mon, 11 Nov 2024 17:11:18 +0000
-Subject: [PATCH 1429/1482] dt: rp1: Use clk_sys for ethernet hclk and pclk
+Subject: [PATCH] dt: rp1: Use clk_sys for ethernet hclk and pclk
hclk and pclk of the MAC are connected to clk_sys, so define
them as being connected accordingly, rather than having fake
--- a/arch/arm64/boot/dts/broadcom/rp1.dtsi
+++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi
-@@ -974,7 +974,8 @@
+@@ -982,7 +982,8 @@
#address-cells = <1>;
#size-cells = <0>;
interrupts = <RP1_INT_ETH IRQ_TYPE_LEVEL_HIGH>;
&rp1_clocks RP1_CLK_ETH_TSU
&rp1_clocks RP1_CLK_ETH>;
clock-names = "pclk", "hclk", "tsu_clk", "tx_clk";
-@@ -1195,18 +1196,6 @@
+@@ -1230,18 +1231,6 @@
clock-output-names = "xosc";
clock-frequency = <50000000>;
};
From efecbda4014b490e042c7fd090942b32316f9345 Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Wed, 13 Nov 2024 13:11:33 +0000
-Subject: [PATCH 1430/1482] dt: rp1: Link RP1 DMA to the associated clock
+Subject: [PATCH] dt: rp1: Link RP1 DMA to the associated clock
This makes the kernel representation of the clock structure
match reality.
--- a/arch/arm64/boot/dts/broadcom/rp1.dtsi
+++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi
-@@ -1061,7 +1061,7 @@
+@@ -1081,7 +1081,7 @@
reg = <0xc0 0x40188000 0x0 0x1000>;
compatible = "snps,axi-dma-1.01a";
interrupts = <RP1_INT_DMA IRQ_TYPE_LEVEL_HIGH>;
--- /dev/null
+From eb035f3ad7da1324d310ef83b42398f47d5bafe7 Mon Sep 17 00:00:00 2001
+From: Tim Gover <tim.gover@raspberrypi.com>
+Date: Fri, 1 Nov 2024 19:42:17 +0000
+Subject: [PATCH] raspberrypi-firmware: Add the RPI firmware UART APIs
+
+Add VideoCore mailbox definitions for the new RPi firmware UART.
+
+Signed-off-by: Tim Gover <tim.gover@raspberrypi.com>
+---
+ include/soc/bcm2835/raspberrypi-firmware.h | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/include/soc/bcm2835/raspberrypi-firmware.h
++++ b/include/soc/bcm2835/raspberrypi-firmware.h
+@@ -98,6 +98,8 @@ enum rpi_firmware_property_tag {
+ RPI_FIRMWARE_GET_REBOOT_FLAGS = 0x00030064,
+ RPI_FIRMWARE_SET_REBOOT_FLAGS = 0x00038064,
+ RPI_FIRMWARE_NOTIFY_DISPLAY_DONE = 0x00030066,
++ RPI_FIRMWARE_GET_SW_UART = 0x0003008a,
++ RPI_FIRMWARE_SET_SW_UART = 0x0003808a,
+
+ /* Dispmanx TAGS */
+ RPI_FIRMWARE_FRAMEBUFFER_ALLOCATE = 0x00040001,
--- /dev/null
+From b8a0e563fd181205565a0edaaebc82b1abf0c5be Mon Sep 17 00:00:00 2001
+From: Tim Gover <tim.gover@raspberrypi.com>
+Date: Fri, 1 Nov 2024 19:43:21 +0000
+Subject: [PATCH] serial: core: Add the Raspberry Pi firmware UART id
+
+Assign a new serial core number for the RPi firmware UART.
+
+Signed-off-by: Tim Gover <tim.gover@raspberrypi.com>
+---
+ include/uapi/linux/serial_core.h | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/include/uapi/linux/serial_core.h
++++ b/include/uapi/linux/serial_core.h
+@@ -245,4 +245,7 @@
+ /* Sunplus UART */
+ #define PORT_SUNPLUS 123
+
++/* RPi firmware UART */
++#define PORT_RPI_FW 124
++
+ #endif /* _UAPILINUX_SERIAL_CORE_H */
--- /dev/null
+From 2548d954d78bca44c5cf430f8ea6de7c771312d7 Mon Sep 17 00:00:00 2001
+From: Tim Gover <tim.gover@raspberrypi.com>
+Date: Wed, 28 Aug 2024 09:46:50 +0100
+Subject: [PATCH] serial: tty: Add a driver for the RPi firmware UART
+
+On Raspberry Pi 4 and earlier models the firmware provides
+a low speed (up to 115200 baud) bit-bashed UART on arbitrary
+GPIOs using the second VPU core.
+
+The firmware driver is designed to support 19200 baud. Higher
+rates up to 115200 seem to work but there may be more jitter.
+
+This can be useful for debug or managing additional low
+speed peripherals if the hardware PL011 and 8250 hardware
+UARTs are already used for console / bluetooth.
+
+The firmware driver requires a fixed core clock frequency
+and also requires the VPU PWM audio driver to be disabled
+(dtparam=audio=off)
+
+Runtime configuration is handled via the vc-mailbox APIs
+with the FIFO buffers being allocated in uncached VPU
+addressable memory. The FIFO pointers are stored in spare
+VideoCore multi-core sync registers in order to reduce the number
+of uncached SDRAM accesses thereby reducing jitter.
+
+Signed-off-by: Tim Gover <tim.gover@raspberrypi.com>
+---
+ drivers/tty/serial/Kconfig | 11 +
+ drivers/tty/serial/Makefile | 1 +
+ drivers/tty/serial/rpi-fw-uart.c | 563 +++++++++++++++++++++++++++++++
+ 3 files changed, 575 insertions(+)
+ create mode 100644 drivers/tty/serial/rpi-fw-uart.c
+
+--- a/drivers/tty/serial/Kconfig
++++ b/drivers/tty/serial/Kconfig
+@@ -1578,6 +1578,17 @@ config SERIAL_NUVOTON_MA35D1_CONSOLE
+ but you can alter that using a kernel command line option such as
+ "console=ttyNVTx".
+
++config SERIAL_RPI_FW
++ tristate "Raspberry Pi Firmware software UART support"
++ depends on ARM_AMBA || COMPILE_TEST
++ select SERIAL_CORE
++ help
++ This selects the Raspberry Pi firmware UART. This is a bit-bashed
++ implementation running on the Raspbery Pi VPU core.
++ This is not supported on Raspberry Pi 5 or newer platforms.
++
++ If unsure, say N.
++
+ endmenu
+
+ config SERIAL_MCTRL_GPIO
+--- a/drivers/tty/serial/Makefile
++++ b/drivers/tty/serial/Makefile
+@@ -88,6 +88,7 @@ obj-$(CONFIG_SERIAL_MILBEAUT_USIO) += mi
+ obj-$(CONFIG_SERIAL_SIFIVE) += sifive.o
+ obj-$(CONFIG_SERIAL_LITEUART) += liteuart.o
+ obj-$(CONFIG_SERIAL_SUNPLUS) += sunplus-uart.o
++obj-$(CONFIG_SERIAL_RPI_FW) += rpi-fw-uart.o
+
+ # GPIOLIB helpers for modem control lines
+ obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o
+--- /dev/null
++++ b/drivers/tty/serial/rpi-fw-uart.c
+@@ -0,0 +1,563 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (c) 2024, Raspberry Pi Ltd. All rights reserved.
++ */
++
++#include <linux/console.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/gpio/consumer.h>
++#include <linux/platform_device.h>
++#include <linux/serial.h>
++#include <linux/serial_core.h>
++#include <linux/slab.h>
++#include <linux/tty.h>
++#include <linux/tty_flip.h>
++#include <soc/bcm2835/raspberrypi-firmware.h>
++#include <linux/dma-mapping.h>
++
++#define RPI_FW_UART_RX_FIFO_RD 0xb0
++#define RPI_FW_UART_RX_FIFO_WR 0xb4
++#define RPI_FW_UART_TX_FIFO_RD 0xb8
++#define RPI_FW_UART_TX_FIFO_WR 0xbc
++
++#define RPI_FW_UART_FIFO_SIZE 32
++#define RPI_FW_UART_FIFO_SIZE_MASK (RPI_FW_UART_FIFO_SIZE - 1)
++
++#define RPI_FW_UART_MIN_VERSION 3
++
++struct rpi_fw_uart_params {
++ u32 start;
++ u32 baud;
++ u32 data_bits;
++ u32 stop_bits;
++ u32 gpio_rx;
++ u32 gpio_tx;
++ u32 flags;
++ u32 fifosize;
++ u32 rx_buffer;
++ u32 tx_buffer;
++ u32 version;
++ u32 fifo_reg_base;
++};
++
++struct rpi_fw_uart {
++ struct uart_driver driver;
++ struct uart_port port;
++ struct rpi_firmware *firmware;
++ struct gpio_desc *rx_gpiod;
++ struct gpio_desc *tx_gpiod;
++ unsigned int rx_gpio;
++ unsigned int tx_gpio;
++ unsigned int baud;
++ unsigned int data_bits;
++ unsigned int stop_bits;
++ unsigned char __iomem *base;
++ size_t dma_buffer_size;
++
++ struct hrtimer trigger_start_rx;
++ ktime_t rx_poll_delay;
++ void *rx_buffer;
++ dma_addr_t rx_buffer_dma_addr;
++ int rx_stop;
++
++ void *tx_buffer;
++ dma_addr_t tx_buffer_dma_addr;
++};
++
++static unsigned int rpi_fw_uart_tx_is_full(struct uart_port *port)
++{
++ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
++ u32 rd, wr;
++
++ rd = readl(rfu->base + RPI_FW_UART_TX_FIFO_RD);
++ wr = readl(rfu->base + RPI_FW_UART_TX_FIFO_WR);
++ return ((wr + 1) & RPI_FW_UART_FIFO_SIZE_MASK) == rd;
++}
++
++static unsigned int rpi_fw_uart_tx_is_empty(struct uart_port *port)
++{
++ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
++ u32 rd, wr;
++
++ if (!rfu->tx_buffer)
++ return 1;
++
++ rd = readl(rfu->base + RPI_FW_UART_TX_FIFO_RD);
++ wr = readl(rfu->base + RPI_FW_UART_TX_FIFO_WR);
++
++ return rd == wr;
++}
++
++unsigned int rpi_fw_uart_rx_is_empty(struct uart_port *port)
++{
++ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
++ u32 rd, wr;
++
++ if (!rfu->rx_buffer)
++ return 1;
++
++ rd = readl(rfu->base + RPI_FW_UART_RX_FIFO_RD);
++ wr = readl(rfu->base + RPI_FW_UART_RX_FIFO_WR);
++
++ return rd == wr;
++}
++
++static unsigned int rpi_fw_uart_tx_empty(struct uart_port *port)
++{
++ return rpi_fw_uart_tx_is_empty(port) ? TIOCSER_TEMT : 0;
++}
++
++static void rpi_fw_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
++{
++ /*
++ * No hardware flow control, firmware automatically configures
++ * TX to output high and RX to input low.
++ */
++ dev_dbg(port->dev, "%s mctrl %u\n", __func__, mctrl);
++}
++
++static unsigned int rpi_fw_uart_get_mctrl(struct uart_port *port)
++{
++ /* No hardware flow control */
++ return TIOCM_CTS;
++}
++
++static void rpi_fw_uart_stop(struct uart_port *port)
++{
++ struct rpi_fw_uart_params msg = {.start = 0};
++ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
++
++ hrtimer_cancel(&rfu->trigger_start_rx);
++
++ if (rpi_firmware_property(rfu->firmware,
++ RPI_FIRMWARE_SET_SW_UART,
++ &msg, sizeof(msg)))
++ dev_warn(port->dev,
++ "Failed to shutdown rpi-fw uart. Firmware not configured?");
++}
++
++static void rpi_fw_uart_stop_tx(struct uart_port *port)
++{
++ /* No supported by the current firmware APIs. */
++}
++
++static void rpi_fw_uart_stop_rx(struct uart_port *port)
++{
++ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
++
++ rfu->rx_stop = 1;
++}
++
++static unsigned int rpi_fw_write(struct uart_port *port, const char *s,
++ unsigned int count)
++{
++ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
++ u8 *out = rfu->tx_buffer;
++ unsigned int consumed = 0;
++
++ while (consumed < count && !rpi_fw_uart_tx_is_full(port)) {
++ u32 wp = readl(rfu->base + RPI_FW_UART_TX_FIFO_WR)
++ & RPI_FW_UART_FIFO_SIZE_MASK;
++ out[wp] = s[consumed++];
++ wp = (wp + 1) & RPI_FW_UART_FIFO_SIZE_MASK;
++ writel(wp, rfu->base + RPI_FW_UART_TX_FIFO_WR);
++ }
++ return consumed;
++}
++
++/* Called with port.lock taken */
++static void rpi_fw_uart_start_tx(struct uart_port *port)
++{
++ struct circ_buf *xmit;
++
++ xmit = &port->state->xmit;
++ for (;;) {
++ unsigned int consumed;
++ unsigned long count = CIRC_CNT_TO_END(xmit->head, xmit->tail,
++ UART_XMIT_SIZE);
++ if (!count)
++ break;
++
++ consumed = rpi_fw_write(port, &xmit->buf[xmit->tail], count);
++ uart_xmit_advance(port, consumed);
++ }
++ uart_write_wakeup(port);
++}
++
++/* Called with port.lock taken */
++static void rpi_fw_uart_start_rx(struct uart_port *port)
++{
++ struct tty_port *tty_port = &port->state->port;
++ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
++ int count = 0;
++
++ /*
++ * RX is polled, read up to a full buffer of data before trying again
++ * so that this can be interrupted if the firmware is filling the
++ * buffer too fast
++ */
++ while (!rpi_fw_uart_rx_is_empty(port) && count < port->fifosize) {
++ const u8 *in = rfu->rx_buffer;
++ u32 rp = readl(rfu->base + RPI_FW_UART_RX_FIFO_RD)
++ & RPI_FW_UART_FIFO_SIZE_MASK;
++
++ tty_insert_flip_char(tty_port, in[rp], TTY_NORMAL);
++ rp = (rp + 1) & RPI_FW_UART_FIFO_SIZE_MASK;
++ writel(rp, rfu->base + RPI_FW_UART_RX_FIFO_RD);
++ count++;
++ }
++ if (count)
++ tty_flip_buffer_push(tty_port);
++}
++
++static enum hrtimer_restart rpi_fw_uart_trigger_rx(struct hrtimer *t)
++{
++ unsigned long flags;
++ struct rpi_fw_uart *rfu = container_of(t, struct rpi_fw_uart,
++ trigger_start_rx);
++
++ spin_lock_irqsave(&rfu->port.lock, flags);
++ if (rfu->rx_stop) {
++ spin_unlock_irqrestore(&rfu->port.lock, flags);
++ return HRTIMER_NORESTART;
++ }
++
++ rpi_fw_uart_start_rx(&rfu->port);
++ spin_unlock_irqrestore(&rfu->port.lock, flags);
++ hrtimer_forward_now(t, rfu->rx_poll_delay);
++ return HRTIMER_RESTART;
++}
++
++static void rpi_fw_uart_break_ctl(struct uart_port *port, int ctl)
++{
++ dev_dbg(port->dev, "%s ctl %d\n", __func__, ctl);
++}
++
++static int rpi_fw_uart_configure(struct uart_port *port)
++{
++ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
++ struct rpi_fw_uart_params msg;
++ unsigned long flags;
++ int rc;
++
++ rpi_fw_uart_stop(port);
++
++ memset(&msg, 0, sizeof(msg));
++ msg.start = 1;
++ msg.gpio_rx = rfu->rx_gpio;
++ msg.gpio_tx = rfu->tx_gpio;
++ msg.data_bits = rfu->data_bits;
++ msg.stop_bits = rfu->stop_bits;
++ msg.baud = rfu->baud;
++ msg.fifosize = RPI_FW_UART_FIFO_SIZE;
++ msg.rx_buffer = (u32) rfu->rx_buffer_dma_addr;
++ msg.tx_buffer = (u32) rfu->tx_buffer_dma_addr;
++
++ rfu->rx_poll_delay = ms_to_ktime(50);
++
++ /*
++ * Reconfigures the firmware UART with the new settings. On the first
++ * call retrieve the addresses of the FIFO buffers. The buffers are
++ * allocated at startup and are not de-allocated.
++ * NB rpi_firmware_property can block
++ */
++ rc = rpi_firmware_property(rfu->firmware,
++ RPI_FIRMWARE_SET_SW_UART,
++ &msg, sizeof(msg));
++ if (rc)
++ goto fail;
++
++ rc = rpi_firmware_property(rfu->firmware,
++ RPI_FIRMWARE_GET_SW_UART,
++ &msg, sizeof(msg));
++ if (rc)
++ goto fail;
++
++ dev_dbg(port->dev, "version %08x, reg addr %x\n", msg.version,
++ msg.fifo_reg_base);
++
++ dev_info(port->dev, "started %d baud %u data %u stop %u rx %u tx %u flags %u fifosize %u\n",
++ msg.start, msg.baud, msg.data_bits, msg.stop_bits,
++ msg.gpio_rx, msg.gpio_tx, msg.flags, msg.fifosize);
++
++ if (msg.fifosize != port->fifosize) {
++ dev_err(port->dev, "Expected fifo size %u actual %u",
++ port->fifosize, msg.fifosize);
++ rc = -EINVAL;
++ goto fail;
++ }
++
++ if (!msg.start) {
++ dev_err(port->dev, "Firmware service not running\n");
++ rc = -EINVAL;
++ }
++
++ spin_lock_irqsave(&rfu->port.lock, flags);
++ rfu->rx_stop = 0;
++ hrtimer_start(&rfu->trigger_start_rx,
++ rfu->rx_poll_delay, HRTIMER_MODE_REL);
++ spin_unlock_irqrestore(&rfu->port.lock, flags);
++ return 0;
++fail:
++ dev_err(port->dev, "Failed to configure rpi-fw uart. Firmware not configured?");
++ return rc;
++}
++
++static void rpi_fw_uart_free_buffers(struct uart_port *port)
++{
++ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
++
++ if (rfu->rx_buffer)
++ dma_free_coherent(port->dev, rfu->dma_buffer_size,
++ rfu->rx_buffer, GFP_ATOMIC);
++
++ if (rfu->tx_buffer)
++ dma_free_coherent(port->dev, rfu->dma_buffer_size,
++ rfu->tx_buffer, GFP_ATOMIC);
++
++ rfu->rx_buffer = NULL;
++ rfu->tx_buffer = NULL;
++ rfu->rx_buffer_dma_addr = 0;
++ rfu->tx_buffer_dma_addr = 0;
++}
++
++static int rpi_fw_uart_alloc_buffers(struct uart_port *port)
++{
++ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
++
++ if (rfu->tx_buffer)
++ return 0;
++
++ rfu->dma_buffer_size = PAGE_ALIGN(RPI_FW_UART_FIFO_SIZE);
++
++ rfu->rx_buffer = dma_alloc_coherent(port->dev, rfu->dma_buffer_size,
++ &rfu->rx_buffer_dma_addr, GFP_ATOMIC);
++
++ if (!rfu->rx_buffer)
++ goto alloc_fail;
++
++ rfu->tx_buffer = dma_alloc_coherent(port->dev, rfu->dma_buffer_size,
++ &rfu->tx_buffer_dma_addr, GFP_ATOMIC);
++
++ if (!rfu->tx_buffer)
++ goto alloc_fail;
++
++ dev_dbg(port->dev, "alloc-buffers %p %x %p %x\n",
++ rfu->rx_buffer, (u32) rfu->rx_buffer_dma_addr,
++ rfu->tx_buffer, (u32) rfu->tx_buffer_dma_addr);
++ return 0;
++
++alloc_fail:
++ dev_err(port->dev, "%s uart buffer allocation failed\n", __func__);
++ rpi_fw_uart_free_buffers(port);
++ return -ENOMEM;
++}
++
++static int rpi_fw_uart_startup(struct uart_port *port)
++{
++ int rc;
++
++ rc = rpi_fw_uart_alloc_buffers(port);
++ if (rc)
++ dev_err(port->dev, "Failed to start\n");
++ return rc;
++}
++
++static void rpi_fw_uart_shutdown(struct uart_port *port)
++{
++ rpi_fw_uart_stop(port);
++ rpi_fw_uart_free_buffers(port);
++}
++
++static void rpi_fw_uart_set_termios(struct uart_port *port,
++ struct ktermios *new,
++ const struct ktermios *old)
++{
++ struct rpi_fw_uart *rfu =
++ container_of(port, struct rpi_fw_uart, port);
++ rfu->baud = uart_get_baud_rate(port, new, old, 50, 115200);
++ rfu->stop_bits = (new->c_cflag & CSTOPB) ? 2 : 1;
++
++ rpi_fw_uart_configure(port);
++}
++
++static const struct uart_ops rpi_fw_uart_ops = {
++ .tx_empty = rpi_fw_uart_tx_empty,
++ .set_mctrl = rpi_fw_uart_set_mctrl,
++ .get_mctrl = rpi_fw_uart_get_mctrl,
++ .stop_rx = rpi_fw_uart_stop_rx,
++ .stop_tx = rpi_fw_uart_stop_tx,
++ .start_tx = rpi_fw_uart_start_tx,
++ .break_ctl = rpi_fw_uart_break_ctl,
++ .startup = rpi_fw_uart_startup,
++ .shutdown = rpi_fw_uart_shutdown,
++ .set_termios = rpi_fw_uart_set_termios,
++};
++
++static int rpi_fw_uart_get_gpio_offset(struct device *dev, const char *name)
++{
++ struct of_phandle_args of_args = { 0 };
++ bool is_bcm28xx;
++
++ /* This really shouldn't fail, given that we have a gpiod */
++ if (of_parse_phandle_with_args(dev->of_node, name, "#gpio-cells", 0, &of_args))
++ return dev_err_probe(dev, -EINVAL, "can't find gpio declaration\n");
++
++ is_bcm28xx = of_device_is_compatible(of_args.np, "brcm,bcm2835-gpio") ||
++ of_device_is_compatible(of_args.np, "brcm,bcm2711-gpio");
++ of_node_put(of_args.np);
++ if (!is_bcm28xx || of_args.args_count != 2)
++ return dev_err_probe(dev, -EINVAL, "not a BCM28xx gpio\n");
++
++ return of_args.args[0];
++}
++
++static int rpi_fw_uart_probe(struct platform_device *pdev)
++{
++ struct device_node *firmware_node;
++ struct device *dev = &pdev->dev;
++ struct rpi_firmware *firmware;
++ struct uart_port *port;
++ struct rpi_fw_uart *rfu;
++ struct rpi_fw_uart_params msg;
++ int version_major;
++ int err;
++
++ dev_dbg(dev, "%s of_node %p\n", __func__, dev->of_node);
++
++ /*
++ * We can be probed either through the an old-fashioned
++ * platform device registration or through a DT node that is a
++ * child of the firmware node. Handle both cases.
++ */
++ if (dev->of_node)
++ firmware_node = of_parse_phandle(dev->of_node, "firmware", 0);
++ else
++ firmware_node = of_find_compatible_node(NULL, NULL,
++ "raspberrypi,bcm2835-firmware");
++ if (!firmware_node) {
++ dev_err(dev, "Missing firmware node\n");
++ return -ENOENT;
++ }
++
++ firmware = devm_rpi_firmware_get(dev, firmware_node);
++ of_node_put(firmware_node);
++ if (!firmware)
++ return -EPROBE_DEFER;
++
++ rfu = devm_kzalloc(dev, sizeof(*rfu), GFP_KERNEL);
++ if (!rfu)
++ return -ENOMEM;
++
++ rfu->firmware = firmware;
++
++ err = rpi_firmware_property(rfu->firmware, RPI_FIRMWARE_GET_SW_UART,
++ &msg, sizeof(msg));
++ if (err) {
++ dev_err(dev, "VC firmware does not support rpi-fw-uart\n");
++ return err;
++ }
++
++ version_major = msg.version >> 16;
++ if (msg.version < RPI_FW_UART_MIN_VERSION) {
++ dev_err(dev, "rpi-fw-uart fw version %d is too old min version %d\n",
++ version_major, RPI_FW_UART_MIN_VERSION);
++ return -EINVAL;
++ }
++
++ rfu->rx_gpiod = devm_gpiod_get(dev, "rx", GPIOD_IN);
++ if (IS_ERR(rfu->rx_gpiod))
++ return PTR_ERR(rfu->rx_gpiod);
++
++ rfu->tx_gpiod = devm_gpiod_get(dev, "tx", GPIOD_OUT_HIGH);
++ if (IS_ERR(rfu->tx_gpiod))
++ return PTR_ERR(rfu->tx_gpiod);
++
++ rfu->rx_gpio = rpi_fw_uart_get_gpio_offset(dev, "rx-gpios");
++ if (rfu->rx_gpio < 0)
++ return rfu->rx_gpio;
++ rfu->tx_gpio = rpi_fw_uart_get_gpio_offset(dev, "tx-gpios");
++ if (rfu->tx_gpio < 0)
++ return rfu->tx_gpio;
++
++ rfu->base = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(rfu->base))
++ return PTR_ERR(rfu->base);
++
++ /* setup the driver */
++ rfu->driver.owner = THIS_MODULE;
++ rfu->driver.driver_name = "ttyRFU";
++ rfu->driver.dev_name = "ttyRFU";
++ rfu->driver.nr = 1;
++ rfu->data_bits = 8;
++
++ /* RX is polled */
++ hrtimer_init(&rfu->trigger_start_rx, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
++ rfu->trigger_start_rx.function = rpi_fw_uart_trigger_rx;
++
++ err = uart_register_driver(&rfu->driver);
++ if (err) {
++ dev_err(dev, "failed to register UART driver: %d\n",
++ err);
++ return err;
++ }
++
++ /* setup the port */
++ port = &rfu->port;
++ spin_lock_init(&port->lock);
++ port->dev = &pdev->dev;
++ port->type = PORT_RPI_FW;
++ port->ops = &rpi_fw_uart_ops;
++ port->fifosize = RPI_FW_UART_FIFO_SIZE;
++ port->iotype = UPIO_MEM;
++ port->flags = UPF_BOOT_AUTOCONF;
++ port->private_data = rfu;
++
++ err = uart_add_one_port(&rfu->driver, port);
++ if (err) {
++ dev_err(dev, "failed to add UART port: %d\n", err);
++ goto unregister_uart;
++ }
++ platform_set_drvdata(pdev, rfu);
++
++ dev_info(dev, "version %d.%d gpios tx %u rx %u\n",
++ msg.version >> 16, msg.version & 0xffff,
++ rfu->tx_gpio, rfu->rx_gpio);
++ return 0;
++
++unregister_uart:
++ uart_unregister_driver(&rfu->driver);
++
++ return err;
++}
++
++static int rpi_fw_uart_remove(struct platform_device *pdev)
++{
++ struct rpi_fw_uart *rfu = platform_get_drvdata(pdev);
++
++ uart_remove_one_port(&rfu->driver, &rfu->port);
++ uart_unregister_driver(&rfu->driver);
++
++ return 0;
++}
++
++static const struct of_device_id rpi_fw_match[] = {
++ { .compatible = "raspberrypi,firmware-uart" },
++ { }
++};
++MODULE_DEVICE_TABLE(of, rpi_fw_match);
++
++static struct platform_driver rpi_fw_driver = {
++ .driver = {
++ .name = "rpi_fw-uart",
++ .of_match_table = rpi_fw_match,
++ },
++ .probe = rpi_fw_uart_probe,
++ .remove = rpi_fw_uart_remove,
++};
++module_platform_driver(rpi_fw_driver);
++
++MODULE_AUTHOR("Tim Gover <tim.gover@rasberrypi.com>");
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("Raspberry Pi Firmware Software UART driver");
--- /dev/null
+From b6b126861062020fb50859c5af71d8846ce43d7c Mon Sep 17 00:00:00 2001
+From: Tim Gover <tim.gover@raspberrypi.com>
+Date: Mon, 4 Nov 2024 13:44:10 +0000
+Subject: [PATCH] dtoverlay: Add an overlay for the Raspberry Pi firmware UART
+
+Add a device-tree overlay to configure the GPIOs for the
+Raspberry Pi firmware UART.
+
+Example config.txt
+dtoverlay=rpi-fw-uart,txd0_pin=20,rxd0_pin=21
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+Signed-off-by: Tim Gover <tim.gover@raspberrypi.com>
+---
+ arch/arm/boot/dts/overlays/Makefile | 1 +
+ arch/arm/boot/dts/overlays/README | 12 ++++++
+ .../boot/dts/overlays/rpi-fw-uart-overlay.dts | 41 +++++++++++++++++++
+ 3 files changed, 54 insertions(+)
+ create mode 100644 arch/arm/boot/dts/overlays/rpi-fw-uart-overlay.dts
+
+--- a/arch/arm/boot/dts/overlays/Makefile
++++ b/arch/arm/boot/dts/overlays/Makefile
+@@ -233,6 +233,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
+ rpi-dacpro.dtbo \
+ rpi-digiampplus.dtbo \
+ rpi-ft5406.dtbo \
++ rpi-fw-uart.dtbo \
+ rpi-poe.dtbo \
+ rpi-poe-plus.dtbo \
+ rpi-sense.dtbo \
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -4141,6 +4141,18 @@ Params: touchscreen-size-x Touchscr
+ touchscreen-swapped-x-y Swap X and Y cordinates (default 0);
+
+
++Name: rpi-fw-uart
++Info: Configures the firmware software UART driver.
++ This driver requires exclusive usage of the second VPU core. The
++ following config.txt entries should be set when this driver is used.
++ dtparam=audio=off
++ isp_use_vpu0=1
++Load: dtoverlay=rpi-fw-uart,<param>[=<val>]
++Params: txd0_pin GPIO pin for TXD0 (any free - default 20)
++
++ rxd0_pin GPIO pin for RXD0 (any free - default 21)
++
++
+ Name: rpi-poe
+ Info: Raspberry Pi PoE HAT fan
+ Load: dtoverlay=rpi-poe,<param>[=<val>]
+--- /dev/null
++++ b/arch/arm/boot/dts/overlays/rpi-fw-uart-overlay.dts
+@@ -0,0 +1,41 @@
++// SPDX-License-Identifier: GPL-2.0
++// Overlay for the Raspberry Pi Firmware UART driver
++/dts-v1/;
++/plugin/;
++
++/{
++ compatible = "brcm,bcm2835";
++
++ fragment@0 {
++ target = <&gpio>;
++ __overlay__ {
++ rpi_fw_uart_pins: rpi_fw_uart_pins@4 {
++ brcm,pins = <20 21>;
++ brcm,function = <1 0>; /* output input */
++ brcm,pull = <0 2>; /* none pull-up */
++ };
++ };
++ };
++
++ fragment@1 {
++ target = <&soc>;
++ __overlay__ {
++ rpi_fw_uart: rpi_fw_uart@7e000000 {
++ compatible = "raspberrypi,firmware-uart";
++ reg = <0x7e000000 0x100>; /* VideoCore MS sync regs */
++ firmware = <&firmware>;
++ pinctrl-names = "default";
++ pinctrl-0 = <&rpi_fw_uart_pins>;
++ tx-gpios = <&gpio 20 0>;
++ rx-gpios = <&gpio 21 0>;
++ };
++ };
++ };
++
++ __overrides__ {
++ txd0_pin = <&rpi_fw_uart>,"tx-gpios:4",
++ <&rpi_fw_uart_pins>, "brcm,pins:0";
++ rxd0_pin = <&rpi_fw_uart>,"rx-gpios:4",
++ <&rpi_fw_uart_pins>, "brcm,pins:4";
++ };
++};
--- /dev/null
+From 1993b453dc4a62378e90d91e9e0006a6c085f38a Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Wed, 18 Sep 2024 10:23:41 +0100
+Subject: [PATCH] ARM: dts: Remove duplicate tags
+
+A dts file should have exactly one /dts-v1/ tag, and overlays should
+also have one /plugin/ tag. Through careless inclusion of other files,
+some Device Trees and overlays end up with duplicated tags - this
+commit removes them.
+
+The change is largely cosmetic, unless using an old version of dtc.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ arch/arm/boot/dts/broadcom/bcm2711-rpi-400.dts | 1 -
+ arch/arm/boot/dts/overlays/i2c-sensor-common.dtsi | 3 ---
+ arch/arm/boot/dts/overlays/imx290_327-overlay.dtsi | 2 --
+ arch/arm/boot/dts/overlays/pisound-pi5-overlay.dts | 3 ---
+ arch/arm/boot/dts/overlays/vc4-fkms-v3d-overlay.dts | 3 ---
+ arch/arm/boot/dts/overlays/vc4-fkms-v3d-pi4-overlay.dts | 3 ---
+ arch/arm/boot/dts/overlays/vc4-kms-v3d-overlay.dts | 3 ---
+ arch/arm/boot/dts/overlays/vc4-kms-v3d-pi4-overlay.dts | 3 ---
+ arch/arm/boot/dts/overlays/w1-gpio-pi5-overlay.dts | 3 ---
+ arch/arm/boot/dts/overlays/w1-gpio-pullup-pi5-overlay.dts | 3 ---
+ arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l.dtsi | 1 -
+ 11 files changed, 28 deletions(-)
+
+--- a/arch/arm/boot/dts/broadcom/bcm2711-rpi-400.dts
++++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-400.dts
+@@ -1,5 +1,4 @@
+ // SPDX-License-Identifier: GPL-2.0
+-/dts-v1/;
+ #include "bcm2711-rpi-4-b.dts"
+
+ / {
+--- a/arch/arm/boot/dts/overlays/i2c-sensor-common.dtsi
++++ b/arch/arm/boot/dts/overlays/i2c-sensor-common.dtsi
+@@ -1,7 +1,4 @@
+ // Definitions for I2C based sensors using the Industrial IO or HWMON interface.
+-/dts-v1/;
+-/plugin/;
+-
+ #include <dt-bindings/gpio/gpio.h>
+
+ / {
+--- a/arch/arm/boot/dts/overlays/imx290_327-overlay.dtsi
++++ b/arch/arm/boot/dts/overlays/imx290_327-overlay.dtsi
+@@ -1,8 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0-only
+ // Partial definitions for IMX290 or IMX327 camera module on VC I2C bus
+ // The compatible string should be set in an overlay that then includes this one
+-/dts-v1/;
+-/plugin/;
+
+ #include <dt-bindings/gpio/gpio.h>
+
+--- a/arch/arm/boot/dts/overlays/pisound-pi5-overlay.dts
++++ b/arch/arm/boot/dts/overlays/pisound-pi5-overlay.dts
+@@ -17,9 +17,6 @@
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+-/dts-v1/;
+-/plugin/;
+-
+ #include "pisound-overlay.dts"
+
+ &pisound_spi {
+--- a/arch/arm/boot/dts/overlays/vc4-fkms-v3d-overlay.dts
++++ b/arch/arm/boot/dts/overlays/vc4-fkms-v3d-overlay.dts
+@@ -2,9 +2,6 @@
+ * vc4-fkms-v3d-overlay.dts
+ */
+
+-/dts-v1/;
+-/plugin/;
+-
+ #include "cma-overlay.dts"
+
+ / {
+--- a/arch/arm/boot/dts/overlays/vc4-fkms-v3d-pi4-overlay.dts
++++ b/arch/arm/boot/dts/overlays/vc4-fkms-v3d-pi4-overlay.dts
+@@ -2,9 +2,6 @@
+ * vc4-fkms-v3d-overlay.dts
+ */
+
+-/dts-v1/;
+-/plugin/;
+-
+ #include "cma-overlay.dts"
+
+ &frag0 {
+--- a/arch/arm/boot/dts/overlays/vc4-kms-v3d-overlay.dts
++++ b/arch/arm/boot/dts/overlays/vc4-kms-v3d-overlay.dts
+@@ -2,9 +2,6 @@
+ * vc4-kms-v3d-overlay.dts
+ */
+
+-/dts-v1/;
+-/plugin/;
+-
+ #include <dt-bindings/clock/bcm2835.h>
+
+ #include "cma-overlay.dts"
+--- a/arch/arm/boot/dts/overlays/vc4-kms-v3d-pi4-overlay.dts
++++ b/arch/arm/boot/dts/overlays/vc4-kms-v3d-pi4-overlay.dts
+@@ -2,9 +2,6 @@
+ * vc4-kms-v3d-pi4-overlay.dts
+ */
+
+-/dts-v1/;
+-/plugin/;
+-
+ #include <dt-bindings/clock/bcm2835.h>
+
+ #include "cma-overlay.dts"
+--- a/arch/arm/boot/dts/overlays/w1-gpio-pi5-overlay.dts
++++ b/arch/arm/boot/dts/overlays/w1-gpio-pi5-overlay.dts
+@@ -1,6 +1,3 @@
+-/dts-v1/;
+-/plugin/;
+-
+ #include "w1-gpio-overlay.dts"
+
+ / {
+--- a/arch/arm/boot/dts/overlays/w1-gpio-pullup-pi5-overlay.dts
++++ b/arch/arm/boot/dts/overlays/w1-gpio-pullup-pi5-overlay.dts
+@@ -1,6 +1,3 @@
+-/dts-v1/;
+-/plugin/;
+-
+ #include "w1-gpio-pullup-overlay.dts"
+
+ / {
+--- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l.dtsi
++++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l.dtsi
+@@ -1,5 +1,4 @@
+ // SPDX-License-Identifier: GPL-2.0
+-/dts-v1/;
+
+ #include "bcm2712-rpi-cm5.dtsi"
+
--- /dev/null
+From e33702e5e5fe9fef6ec967961e2e5e1c2285ba36 Mon Sep 17 00:00:00 2001
+From: gtrainavicius <gtrainavicius@users.noreply.github.com>
+Date: Wed, 4 Dec 2024 11:18:14 +0200
+Subject: [PATCH] =?UTF-8?q?Allow=20setting=20I=C2=B2C=20clock=20frequency?=
+ =?UTF-8?q?=20via=20i2c=5Farm=5Fbaudrate=20dtparam=20when=20using=20pimidi?=
+ =?UTF-8?q?=20overlay.?=
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This change removes the forced 1MHz clock frequency, so it can be overridden using `i2c_arm_baudrate`.
+---
+ arch/arm/boot/dts/overlays/pimidi-overlay.dts | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/arch/arm/boot/dts/overlays/pimidi-overlay.dts
++++ b/arch/arm/boot/dts/overlays/pimidi-overlay.dts
+@@ -26,7 +26,6 @@
+ target = <&i2c_arm>;
+ __overlay__ {
+ status = "okay";
+- clock-frequency=<1000000>;
+
+ pimidi_ctrl: pimidi_ctrl@20 {
+ compatible = "blokaslabs,pimidi";
--- /dev/null
+From fda47c026dee7acd975ee2c0f7a440d4038cfaa3 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Tue, 3 Dec 2024 15:57:01 +0000
+Subject: [PATCH] nvme-pci: Disable Host Memory Buffer usage
+
+Some NVME drives seem to request significant amounts of DMA coherent
+memory - enough to exhaust our standard 64MB CMA allocation.
+
+Try disabling the feature to see what effect it has - drives should
+continue to function without it.
+
+Link: https://github.com/raspberrypi/linux/issues/6504
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/nvme/host/pci.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/nvme/host/pci.c
++++ b/drivers/nvme/host/pci.c
+@@ -1932,6 +1932,7 @@ static void nvme_free_host_mem(struct nv
+ dev->nr_host_mem_descs = 0;
+ }
+
++#if 0
+ static int __nvme_alloc_host_mem(struct nvme_dev *dev, u64 preferred,
+ u32 chunk_size)
+ {
+@@ -2000,9 +2001,11 @@ out:
+ dev->host_mem_descs = NULL;
+ return -ENOMEM;
+ }
++#endif
+
+ static int nvme_alloc_host_mem(struct nvme_dev *dev, u64 min, u64 preferred)
+ {
++#if 0
+ u64 min_chunk = min_t(u64, preferred, PAGE_SIZE * MAX_ORDER_NR_PAGES);
+ u64 hmminds = max_t(u32, dev->ctrl.hmminds * 4096, PAGE_SIZE * 2);
+ u64 chunk_size;
+@@ -2015,6 +2018,7 @@ static int nvme_alloc_host_mem(struct nv
+ nvme_free_host_mem(dev);
+ }
+ }
++#endif
+
+ return -ENOMEM;
+ }
--- /dev/null
+From 0313a0961b685973f7833017479a277e3a4c05a4 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Wed, 4 Dec 2024 14:40:59 +0000
+Subject: [PATCH] fixup! serial: tty: Add a driver for the RPi firmware UART
+
+Make SERIAL_RPI_FW depend on RASPBERRYPI_FIRMWARE.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/tty/serial/Kconfig | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/tty/serial/Kconfig
++++ b/drivers/tty/serial/Kconfig
+@@ -1580,7 +1580,7 @@ config SERIAL_NUVOTON_MA35D1_CONSOLE
+
+ config SERIAL_RPI_FW
+ tristate "Raspberry Pi Firmware software UART support"
+- depends on ARM_AMBA || COMPILE_TEST
++ depends on RASPBERRYPI_FIRMWARE || COMPILE_TEST
+ select SERIAL_CORE
+ help
+ This selects the Raspberry Pi firmware UART. This is a bit-bashed
--- /dev/null
+From 0a5be0fe6ba3a981508421131def7eab55d6d75c Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Thu, 5 Dec 2024 12:08:23 +0000
+Subject: [PATCH] serial: rpi-fw-uart: Demote debug log messages
+
+A dev_info call in rpi_fw_uart_configure causes kernel log output every
+time one opens the UART. Demote it to dev_dbg.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/tty/serial/rpi-fw-uart.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/tty/serial/rpi-fw-uart.c
++++ b/drivers/tty/serial/rpi-fw-uart.c
+@@ -277,9 +277,9 @@ static int rpi_fw_uart_configure(struct
+ dev_dbg(port->dev, "version %08x, reg addr %x\n", msg.version,
+ msg.fifo_reg_base);
+
+- dev_info(port->dev, "started %d baud %u data %u stop %u rx %u tx %u flags %u fifosize %u\n",
+- msg.start, msg.baud, msg.data_bits, msg.stop_bits,
+- msg.gpio_rx, msg.gpio_tx, msg.flags, msg.fifosize);
++ dev_dbg(port->dev, "started %d baud %u data %u stop %u rx %u tx %u flags %u fifosize %u\n",
++ msg.start, msg.baud, msg.data_bits, msg.stop_bits,
++ msg.gpio_rx, msg.gpio_tx, msg.flags, msg.fifosize);
+
+ if (msg.fifosize != port->fifosize) {
+ dev_err(port->dev, "Expected fifo size %u actual %u",
--- /dev/null
+From 02dee262a9c7295ea514e9db7b9aa4b239922cb3 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Mon, 2 Dec 2024 15:41:21 +0000
+Subject: [PATCH] dtoverlays: Add Arducam override for ov9281
+
+The Arducam module is slow starting up, so add an override
+to slow the regulator down.
+https://forums.raspberrypi.com/viewtopic.php?t=380236
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ arch/arm/boot/dts/overlays/README | 2 ++
+ arch/arm/boot/dts/overlays/ov9281-overlay.dts | 13 ++++++++++++-
+ 2 files changed, 14 insertions(+), 1 deletion(-)
+
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -3538,6 +3538,8 @@ Params: rotation Mounting
+ configuring the sensor (default on)
+ cam0 Adopt the default configuration for CAM0 on a
+ Compute Module (CSI0, i2c_vc, and cam0_reg).
++ arducam Slow down the regulator for slow Arducam
++ modules.
+
+
+ Name: papirus
+--- a/arch/arm/boot/dts/overlays/ov9281-overlay.dts
++++ b/arch/arm/boot/dts/overlays/ov9281-overlay.dts
+@@ -57,6 +57,14 @@
+ };
+ };
+
++ reg_frag: fragment@5 {
++ target = <&cam1_reg>;
++ __dormant__ {
++ startup-delay-us = <20000>;
++ off-on-delay-us = <30000>;
++ };
++ };
++
+ __overrides__ {
+ rotation = <&cam_node>,"rotation:0";
+ orientation = <&cam_node>,"orientation:0";
+@@ -65,7 +73,10 @@
+ <&csi_frag>, "target:0=",<&csi0>,
+ <&clk_frag>, "target:0=",<&cam0_clk>,
+ <&cam_node>, "clocks:0=",<&cam0_clk>,
+- <&cam_node>, "avdd-supply:0=",<&cam0_reg>;
++ <&cam_node>, "avdd-supply:0=",<&cam0_reg>,
++ <®_frag>, "target:0=",<&cam0_reg>;
++ arducam = <0>, "+5";
++
+ };
+ };
+
--- /dev/null
+From 97638920f1a40e2e0cab363d1e03837ff50c5478 Mon Sep 17 00:00:00 2001
+From: eng33 <eng33@waveshare.com>
+Date: Thu, 5 Dec 2024 17:19:23 +0800
+Subject: [PATCH] drivers:input:touchscreen: Add support for no irq to ili210x
+ driver
+
+Signed-off-by: eng33 <eng33@waveshare.com>
+---
+ drivers/input/touchscreen/ili210x.c | 63 ++++++++++++++++++++++++-----
+ 1 file changed, 52 insertions(+), 11 deletions(-)
+
+--- a/drivers/input/touchscreen/ili210x.c
++++ b/drivers/input/touchscreen/ili210x.c
+@@ -67,6 +67,8 @@ struct ili210x {
+ u8 version_proto[2];
+ u8 ic_mode[2];
+ bool stop;
++ struct timer_list poll_timer;
++ struct work_struct poll_work;
+ };
+
+ static int ili210x_read_reg(struct i2c_client *client,
+@@ -360,6 +362,34 @@ static irqreturn_t ili210x_irq(int irq,
+ return IRQ_HANDLED;
+ }
+
++static void ili210x_poll_work(struct work_struct *work)
++{
++ struct ili210x *priv = container_of(work, struct ili210x, poll_work);
++ struct i2c_client *client = priv->client;
++ const struct ili2xxx_chip *chip = priv->chip;
++ u8 touchdata[ILI210X_DATA_SIZE] = { 0 };
++ bool touch;
++ int error;
++
++ error = chip->get_touch_data(client, touchdata);
++ if (error) {
++ dev_err(&client->dev, "Unable to get touch data: %d\n", error);
++ return;
++ }
++
++ touch = ili210x_report_events(priv, touchdata);
++}
++
++static void ili210x_poll_timer_callback(struct timer_list *t)
++{
++ struct ili210x *priv = from_timer(priv, t, poll_timer);
++
++ schedule_work(&priv->poll_work);
++
++ if (!priv->stop)
++ mod_timer(&priv->poll_timer, jiffies + msecs_to_jiffies(ILI2XXX_POLL_PERIOD));
++}
++
+ static int ili251x_firmware_update_resolution(struct device *dev)
+ {
+ struct i2c_client *client = to_i2c_client(dev);
+@@ -945,11 +975,6 @@ static int ili210x_i2c_probe(struct i2c_
+ return -ENODEV;
+ }
+
+- if (client->irq <= 0) {
+- dev_err(dev, "No IRQ!\n");
+- return -EINVAL;
+- }
+-
+ reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(reset_gpio))
+ return PTR_ERR(reset_gpio);
+@@ -1001,12 +1026,17 @@ static int ili210x_i2c_probe(struct i2c_
+ return error;
+ }
+
+- error = devm_request_threaded_irq(dev, client->irq, NULL, ili210x_irq,
+- IRQF_ONESHOT, client->name, priv);
+- if (error) {
+- dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n",
+- error);
+- return error;
++ if (client->irq) {
++ error = devm_request_threaded_irq(dev, client->irq, NULL, ili210x_irq,
++ IRQF_ONESHOT, client->name, priv);
++ if (error) {
++ dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n", error);
++ return error;
++ }
++ } else {
++ timer_setup(&priv->poll_timer, ili210x_poll_timer_callback, 0);
++ mod_timer(&priv->poll_timer, jiffies + msecs_to_jiffies(ILI2XXX_POLL_PERIOD));
++ INIT_WORK(&priv->poll_work, ili210x_poll_work);
+ }
+
+ error = devm_add_action_or_reset(dev, ili210x_stop, priv);
+@@ -1029,6 +1059,16 @@ static int ili210x_i2c_probe(struct i2c_
+ return 0;
+ }
+
++static void ili210x_i2c_remove(struct i2c_client *client)
++{
++ struct ili210x *tsdata = i2c_get_clientdata(client);
++
++ if (!client->irq) {
++ del_timer(&tsdata->poll_timer);
++ cancel_work_sync(&tsdata->poll_work);
++ }
++}
++
+ static const struct i2c_device_id ili210x_i2c_id[] = {
+ { "ili210x", (long)&ili210x_chip },
+ { "ili2117", (long)&ili211x_chip },
+@@ -1054,6 +1094,7 @@ static struct i2c_driver ili210x_ts_driv
+ },
+ .id_table = ili210x_i2c_id,
+ .probe = ili210x_i2c_probe,
++ .remove = ili210x_i2c_remove,
+ };
+
+ module_i2c_driver(ili210x_ts_driver);
--- /dev/null
+From 4a89fda8f73df89e009a6188ef07ab97b1d03c7f Mon Sep 17 00:00:00 2001
+From: eng33 <eng33@waveshare.com>
+Date: Thu, 5 Dec 2024 17:20:22 +0800
+Subject: [PATCH] drivers:gpu:drm:panel: Added waveshare 13.3inch panel(support
+ 2/4lane)
+
+Signed-off-by: eng33 <eng33@waveshare.com>
+---
+ drivers/gpu/drm/panel/panel-waveshare-dsi.c | 155 +++++++++++++++++---
+ 1 file changed, 138 insertions(+), 17 deletions(-)
+
+--- a/drivers/gpu/drm/panel/panel-waveshare-dsi.c
++++ b/drivers/gpu/drm/panel/panel-waveshare-dsi.c
+@@ -32,6 +32,12 @@ struct ws_panel {
+ enum drm_panel_orientation orientation;
+ };
+
++struct ws_panel_data {
++ const struct drm_display_mode *mode;
++ int lanes;
++ unsigned long mode_flags;
++};
++
+ /* 2.8inch 480x640
+ * https://www.waveshare.com/product/raspberry-pi/displays/2.8inch-dsi-lcd.htm
+ */
+@@ -47,6 +53,12 @@ static const struct drm_display_mode ws_
+ .vtotal = 640 + 150 + 50 + 150,
+ };
+
++static const struct ws_panel_data ws_panel_2_8_data = {
++ .mode = &ws_panel_2_8_mode,
++ .lanes = 2,
++ .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
++};
++
+ /* 3.4inch 800x800 Round
+ * https://www.waveshare.com/product/displays/lcd-oled/3.4inch-dsi-lcd-c.htm
+ */
+@@ -62,6 +74,12 @@ static const struct drm_display_mode ws_
+ .vtotal = 800 + 8 + 4 + 16,
+ };
+
++static const struct ws_panel_data ws_panel_3_4_data = {
++ .mode = &ws_panel_3_4_mode,
++ .lanes = 2,
++ .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
++};
++
+ /* 4.0inch 480x800
+ * https://www.waveshare.com/product/raspberry-pi/displays/4inch-dsi-lcd.htm
+ */
+@@ -77,6 +95,12 @@ static const struct drm_display_mode ws_
+ .vtotal = 800 + 20 + 100 + 20,
+ };
+
++static const struct ws_panel_data ws_panel_4_0_data = {
++ .mode = &ws_panel_4_0_mode,
++ .lanes = 2,
++ .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
++};
++
+ /* 7.0inch C 1024x600
+ * https://www.waveshare.com/product/raspberry-pi/displays/lcd-oled/7inch-dsi-lcd-c-with-case-a.htm
+ */
+@@ -92,6 +116,12 @@ static const struct drm_display_mode ws_
+ .vtotal = 600 + 10 + 10 + 10,
+ };
+
++static const struct ws_panel_data ws_panel_7_0_c_data = {
++ .mode = &ws_panel_7_0_c_mode,
++ .lanes = 2,
++ .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
++};
++
+ /* 7.9inch 400x1280
+ * https://www.waveshare.com/product/raspberry-pi/displays/7.9inch-dsi-lcd.htm
+ */
+@@ -107,6 +137,12 @@ static const struct drm_display_mode ws_
+ .vtotal = 1280 + 20 + 10 + 20,
+ };
+
++static const struct ws_panel_data ws_panel_7_9_data = {
++ .mode = &ws_panel_7_9_mode,
++ .lanes = 2,
++ .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
++};
++
+ /* 8.0inch or 10.1inch 1280x800
+ * https://www.waveshare.com/product/raspberry-pi/displays/8inch-dsi-lcd-c.htm
+ * https://www.waveshare.com/product/raspberry-pi/displays/10.1inch-dsi-lcd-c.htm
+@@ -123,6 +159,12 @@ static const struct drm_display_mode ws_
+ .vtotal = 800 + 40 + 48 + 40,
+ };
+
++static const struct ws_panel_data ws_panel_10_1_data = {
++ .mode = &ws_panel_10_1_mode,
++ .lanes = 2,
++ .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
++};
++
+ /* 11.9inch 320x1480
+ * https://www.waveshare.com/product/raspberry-pi/displays/11.9inch-dsi-lcd.htm
+ */
+@@ -138,6 +180,12 @@ static const struct drm_display_mode ws_
+ .vtotal = 1480 + 60 + 60 + 60,
+ };
+
++static const struct ws_panel_data ws_panel_11_9_data = {
++ .mode = &ws_panel_11_9_mode,
++ .lanes = 2,
++ .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
++};
++
+ static const struct drm_display_mode ws_panel_4_mode = {
+ .clock = 50000,
+ .hdisplay = 720,
+@@ -150,6 +198,12 @@ static const struct drm_display_mode ws_
+ .vtotal = 720 + 8 + 4 + 16,
+ };
+
++static const struct ws_panel_data ws_panel_4_data = {
++ .mode = &ws_panel_4_mode,
++ .lanes = 2,
++ .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
++};
++
+ /* 5.0inch 720x1280
+ * https://www.waveshare.com/5inch-dsi-lcd-d.htm
+ */
+@@ -165,6 +219,12 @@ static const struct drm_display_mode ws_
+ .vtotal = 1280 + 20 + 20 + 20,
+ };
+
++static const struct ws_panel_data ws_panel_5_0_data = {
++ .mode = &ws_panel_5_0_mode,
++ .lanes = 2,
++ .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
++};
++
+ /* 6.25inch 720x1560
+ * https://www.waveshare.com/6.25inch-dsi-lcd.htm
+ */
+@@ -180,6 +240,12 @@ static const struct drm_display_mode ws_
+ .vtotal = 1560 + 20 + 20 + 20,
+ };
+
++static const struct ws_panel_data ws_panel_6_25_data = {
++ .mode = &ws_panel_6_25_mode,
++ .lanes = 2,
++ .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
++};
++
+ /* 8.8inch 480x1920
+ * https://www.waveshare.com/8.8inch-dsi-lcd.htm
+ */
+@@ -195,6 +261,48 @@ static const struct drm_display_mode ws_
+ .vtotal = 1920 + 20 + 20 + 20,
+ };
+
++static const struct ws_panel_data ws_panel_8_8_data = {
++ .mode = &ws_panel_8_8_mode,
++ .lanes = 2,
++ .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
++};
++
++static const struct drm_display_mode ws_panel_13_3_4lane_mode = {
++ .clock = 148500,
++ .hdisplay = 1920,
++ .hsync_start = 1920 + 88,
++ .hsync_end = 1920 + 88 + 44,
++ .htotal = 1920 + 88 + 44 + 148,
++ .vdisplay = 1080,
++ .vsync_start = 1080 + 4,
++ .vsync_end = 1080 + 4 + 5,
++ .vtotal = 1080 + 4 + 5 + 36,
++};
++
++static const struct ws_panel_data ws_panel_13_3_4lane_data = {
++ .mode = &ws_panel_13_3_4lane_mode,
++ .lanes = 4,
++ .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM,
++};
++
++static const struct drm_display_mode ws_panel_13_3_2lane_mode = {
++ .clock = 83333,
++ .hdisplay = 1920,
++ .hsync_start = 1920 + 88,
++ .hsync_end = 1920 + 88 + 44,
++ .htotal = 1920 + 88 + 44 + 148,
++ .vdisplay = 1080,
++ .vsync_start = 1080 + 4,
++ .vsync_end = 1080 + 4 + 5,
++ .vtotal = 1080 + 4 + 5 + 36,
++};
++
++static const struct ws_panel_data ws_panel_13_3_2lane_data = {
++ .mode = &ws_panel_13_3_2lane_mode,
++ .lanes = 2,
++ .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM,
++};
++
+ static struct ws_panel *panel_to_ts(struct drm_panel *panel)
+ {
+ return container_of(panel, struct ws_panel, base);
+@@ -232,7 +340,10 @@ static int ws_panel_enable(struct drm_pa
+ {
+ struct ws_panel *ts = panel_to_ts(panel);
+
+- ws_panel_i2c_write(ts, 0xad, 0x01);
++ if (ts->mode == &ws_panel_13_3_2lane_mode)
++ ws_panel_i2c_write(ts, 0xad, 0x02);
++ else
++ ws_panel_i2c_write(ts, 0xad, 0x01);
+
+ return 0;
+ }
+@@ -328,13 +439,18 @@ static int ws_panel_probe(struct i2c_cli
+ .channel = 0,
+ .node = NULL,
+ };
++ const struct ws_panel_data *_ws_panel_data;
+ int ret;
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+- ts->mode = of_device_get_match_data(dev);
++ _ws_panel_data = of_device_get_match_data(dev);
++ if (!_ws_panel_data)
++ return -EINVAL;
++
++ ts->mode = _ws_panel_data->mode;
+ if (!ts->mode)
+ return -EINVAL;
+
+@@ -396,10 +512,9 @@ static int ws_panel_probe(struct i2c_cli
+ */
+ drm_panel_add(&ts->base);
+
+- ts->dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+- MIPI_DSI_CLOCK_NON_CONTINUOUS;
++ ts->dsi->mode_flags = _ws_panel_data->mode_flags;
+ ts->dsi->format = MIPI_DSI_FMT_RGB888;
+- ts->dsi->lanes = 2;
++ ts->dsi->lanes = _ws_panel_data->lanes;
+
+ ret = devm_mipi_dsi_attach(dev, ts->dsi);
+
+@@ -432,40 +547,46 @@ static void ws_panel_shutdown(struct i2c
+ static const struct of_device_id ws_panel_of_ids[] = {
+ {
+ .compatible = "waveshare,2.8inch-panel",
+- .data = &ws_panel_2_8_mode,
++ .data = &ws_panel_2_8_data,
+ }, {
+ .compatible = "waveshare,3.4inch-panel",
+- .data = &ws_panel_3_4_mode,
++ .data = &ws_panel_3_4_data,
+ }, {
+ .compatible = "waveshare,4.0inch-panel",
+- .data = &ws_panel_4_0_mode,
++ .data = &ws_panel_4_0_data,
+ }, {
+ .compatible = "waveshare,7.0inch-c-panel",
+- .data = &ws_panel_7_0_c_mode,
++ .data = &ws_panel_7_0_c_data,
+ }, {
+ .compatible = "waveshare,7.9inch-panel",
+- .data = &ws_panel_7_9_mode,
++ .data = &ws_panel_7_9_data,
+ }, {
+ .compatible = "waveshare,8.0inch-panel",
+- .data = &ws_panel_10_1_mode,
++ .data = &ws_panel_10_1_data,
+ }, {
+ .compatible = "waveshare,10.1inch-panel",
+- .data = &ws_panel_10_1_mode,
++ .data = &ws_panel_10_1_data,
+ }, {
+ .compatible = "waveshare,11.9inch-panel",
+- .data = &ws_panel_11_9_mode,
++ .data = &ws_panel_11_9_data,
+ }, {
+ .compatible = "waveshare,4inch-panel",
+- .data = &ws_panel_4_mode,
++ .data = &ws_panel_4_data,
+ }, {
+ .compatible = "waveshare,5.0inch-panel",
+- .data = &ws_panel_5_0_mode,
++ .data = &ws_panel_5_0_data,
+ }, {
+ .compatible = "waveshare,6.25inch-panel",
+- .data = &ws_panel_6_25_mode,
++ .data = &ws_panel_6_25_data,
+ }, {
+ .compatible = "waveshare,8.8inch-panel",
+- .data = &ws_panel_8_8_mode,
++ .data = &ws_panel_8_8_data,
++ }, {
++ .compatible = "waveshare,13.3inch-4lane-panel",
++ .data = &ws_panel_13_3_4lane_data,
++ }, {
++ .compatible = "waveshare,13.3inch-2lane-panel",
++ .data = &ws_panel_13_3_2lane_data,
+ }, {
+ /* sentinel */
+ }
--- /dev/null
+From e442e5c1ab6bff5b5460b4fc949beb72aaf77970 Mon Sep 17 00:00:00 2001
+From: eng33 <eng33@waveshare.com>
+Date: Thu, 5 Dec 2024 18:11:26 +0800
+Subject: [PATCH] arch:arm:boot:dts:overlays: Added waveshare 13.3inch panel
+ support
+
+Signed-off-by: eng33 <eng33@waveshare.com>
+---
+ arch/arm/boot/dts/overlays/README | 2 ++
+ .../dts/overlays/vc4-kms-dsi-waveshare-panel-overlay.dts | 7 +++++++
+ 2 files changed, 9 insertions(+)
+
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -5338,6 +5338,8 @@ Params: 2_8_inch 2.8" 480
+ 8_0_inch 8.0" 1280x800
+ 10_1_inch 10.1" 1280x800
+ 11_9_inch 11.9" 320x1480
++ 13_3_inch_4lane 13.3" 1920x1080 4lane
++ 13_3_inch_2lane 13.3" 1920x1080 2lane
+ i2c1 Use i2c-1 with jumper wires from GPIOs 2&3
+ disable_touch Disable the touch controller
+ rotation Set the panel orientation property
+--- a/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-overlay.dts
++++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-overlay.dts
+@@ -51,6 +51,11 @@
+ reg = <0x14>;
+ compatible = "goodix,gt911";
+ };
++
++ touch2: ilitek@41 {
++ compatible = "ilitek,ili251x";
++ reg = <0x41>;
++ };
+ };
+ };
+
+@@ -120,6 +125,8 @@
+ <&touch>, "touchscreen-inverted-x?",
+ <&touch>, "touchscreen-inverted-y?";
+ 8_8_inch = <&panel>, "compatible=waveshare,8.8inch-panel";
++ 13_3_inch_4lane = <&panel>, "compatible=waveshare,13.3inch-4lane-panel";
++ 13_3_inch_2lane = <&panel>, "compatible=waveshare,13.3inch-2lane-panel";
+ i2c1 = <&i2c_frag>, "target:0=",<&i2c1>,
+ <0>, "-3-4+5";
+ disable_touch = <&touch>, "status=disabled";
--- /dev/null
+From 166dfc4399643681f2e4277bf7b7407e926861e5 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Mon, 9 Dec 2024 14:58:16 +0000
+Subject: [PATCH] fixup! cgroup: Use kernel command line to disable memory
+ cgroup
+
+cgroup features are distinct from cgroup subsystems - handle them
+correctly.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ kernel/cgroup/cgroup.c | 10 +++++++++-
+ 1 file changed, 9 insertions(+), 1 deletion(-)
+
+--- a/kernel/cgroup/cgroup.c
++++ b/kernel/cgroup/cgroup.c
+@@ -6769,11 +6769,19 @@ static int __init cgroup_enable(char *st
+ strcmp(token, ss->legacy_name))
+ continue;
+
+- cgroup_feature_disable_mask &= ~(1 << i);
+ static_branch_enable(cgroup_subsys_enabled_key[i]);
+ pr_info("Enabling %s control group subsystem\n",
+ ss->name);
+ }
++
++ for (i = 0; i < OPT_FEATURE_COUNT; i++) {
++ if (strcmp(token, cgroup_opt_feature_names[i]))
++ continue;
++ cgroup_feature_disable_mask &= ~(1 << i);
++ pr_info("Enabling %s control group feature\n",
++ cgroup_opt_feature_names[i]);
++ break;
++ }
+ }
+ return 1;
+ }
--- /dev/null
+From e23afbf2c7aae9264322eee8e5c72ca1887606df Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Mon, 9 Dec 2024 10:43:18 +0000
+Subject: [PATCH] media: i2c: ov9282: Correct the exposure offset
+
+The datasheet lists that "Maximum exposure time is frame
+length -25 row periods, where frame length is set by
+registers {0x380E, 0x380F}".
+However this driver had OV9282_EXPOSURE_OFFSET set to 12
+which allowed that restriction to be violated, and would
+result in very under-exposed images.
+
+Correct the offset.
+
+Fixes: 14ea315bbeb7 ("media: i2c: Add ov9282 camera sensor driver")
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/ov9282.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/media/i2c/ov9282.c
++++ b/drivers/media/i2c/ov9282.c
+@@ -40,7 +40,7 @@
+ /* Exposure control */
+ #define OV9282_REG_EXPOSURE 0x3500
+ #define OV9282_EXPOSURE_MIN 1
+-#define OV9282_EXPOSURE_OFFSET 12
++#define OV9282_EXPOSURE_OFFSET 25
+ #define OV9282_EXPOSURE_STEP 1
+ #define OV9282_EXPOSURE_DEFAULT 0x0282
+
--- /dev/null
+From 448a2db3990534810b45d3e4202df96ab2dc5815 Mon Sep 17 00:00:00 2001
+From: Dom Cobley <popcornmix@gmail.com>
+Date: Tue, 10 Dec 2024 15:28:28 +0000
+Subject: [PATCH] Revert "drm/vc4: hvs: Don't write gamma luts on 2711"
+
+This reverts commit 40c77e93cfdda320f47fc1a00a76ce466d20e976.
+---
+ drivers/gpu/drm/vc4/vc4_hvs.c | 3 ---
+ 1 file changed, 3 deletions(-)
+
+--- a/drivers/gpu/drm/vc4/vc4_hvs.c
++++ b/drivers/gpu/drm/vc4/vc4_hvs.c
+@@ -521,9 +521,6 @@ static void vc4_hvs_lut_load(struct vc4_
+ if (!drm_dev_enter(drm, &idx))
+ return;
+
+- if (hvs->vc4->gen == VC4_GEN_5)
+- return;
+-
+ /* The LUT memory is laid out with each HVS channel in order,
+ * each of which takes 256 writes for R, 256 for G, then 256
+ * for B.
--- /dev/null
+From 746662562995125ef7fb2c294300b0bd061b1251 Mon Sep 17 00:00:00 2001
+From: Dom Cobley <popcornmix@gmail.com>
+Date: Tue, 10 Dec 2024 16:39:31 +0000
+Subject: [PATCH] Revert "PCI: Warn if no host bridge NUMA node info"
+
+This warning doesn't mean anyting on our platform and
+the warning causes confusion.
+
+See: https://forums.raspberrypi.com/viewtopic.php?p=2276125#p2276125
+
+This reverts commit ad5086108b9f0361929aa9a79cf959ab5681d249.
+
+Signed-off-by: Dom Cobley <popcornmix@gmail.com>
+---
+ drivers/pci/probe.c | 3 ---
+ 1 file changed, 3 deletions(-)
+
+--- a/drivers/pci/probe.c
++++ b/drivers/pci/probe.c
+@@ -968,9 +968,6 @@ static int pci_register_host_bridge(stru
+ else
+ pr_info("PCI host bridge to bus %s\n", name);
+
+- if (nr_node_ids > 1 && pcibus_to_node(bus) == NUMA_NO_NODE)
+- dev_warn(&bus->dev, "Unknown NUMA node; performance will be reduced\n");
+-
+ /* Coalesce contiguous windows */
+ resource_list_for_each_entry_safe(window, n, &resources) {
+ if (list_is_last(&window->node, &resources))
--- /dev/null
+From 7d294fbff4863e53a64685335b30aed9604cae49 Mon Sep 17 00:00:00 2001
+From: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
+Date: Tue, 19 Nov 2024 16:11:32 +0000
+Subject: [PATCH] drm: bridge: panel: Connector to allow interlaced modes
+
+When initialized from panel_bridge_attach(), connector should
+allow interlaced modes rather than invariably rejecting them,
+so that other components can validate them.
+
+Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
+---
+ drivers/gpu/drm/bridge/panel.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/gpu/drm/bridge/panel.c
++++ b/drivers/gpu/drm/bridge/panel.c
+@@ -82,6 +82,8 @@ static int panel_bridge_attach(struct dr
+ return ret;
+ }
+
++ connector->interlace_allowed = true;
++
+ drm_panel_bridge_set_orientation(connector, bridge);
+
+ drm_connector_attach_encoder(&panel_bridge->connector,
--- /dev/null
+From 2b0acbe8fd008e09a904b7a3c796a2dc79bf10ea Mon Sep 17 00:00:00 2001
+From: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
+Date: Tue, 19 Nov 2024 16:17:40 +0000
+Subject: [PATCH] dts: overlays: vc4-kms-dpi-generic-overlay: Add "interlaced"
+ property
+
+Almost no DPI hardware supports it, but it's useful for RP1 video out.
+
+Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
+---
+ arch/arm/boot/dts/overlays/README | 1 +
+ arch/arm/boot/dts/overlays/vc4-kms-dpi-generic-overlay.dts | 1 +
+ 2 files changed, 2 insertions(+)
+
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -5099,6 +5099,7 @@ Params: clock-frequency Display
+ vsync-invert Vertical sync active low
+ de-invert Data Enable active low
+ pixclk-invert Negative edge pixel clock
++ interlaced Use an interlaced mode (where supported)
+ width-mm Define the screen width in mm
+ height-mm Define the screen height in mm
+ rgb565 Change to RGB565 output on GPIOs 0-19
+--- a/arch/arm/boot/dts/overlays/vc4-kms-dpi-generic-overlay.dts
++++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi-generic-overlay.dts
+@@ -59,6 +59,7 @@
+ vsync-invert = <&timing>, "vsync-active:0=0";
+ de-invert = <&timing>, "de-active:0=0";
+ pixclk-invert = <&timing>, "pixelclk-active:0=0";
++ interlaced = <&timing>, "interlaced?";
+
+ width-mm = <&panel_generic>, "width-mm:0";
+ height-mm = <&panel_generic>, "height-mm:0";
From 7735dd0736322cff23aff95490bae1d69937a9bf Mon Sep 17 00:00:00 2001
From: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
Date: Tue, 10 Dec 2024 13:23:09 +0000
-Subject: [PATCH 1456/1482] drm: rp1: rp1-dpi: Add interlaced modes and PIO
- program to fix VSYNC
+Subject: [PATCH] drm: rp1: rp1-dpi: Add interlaced modes and PIO program to
+ fix VSYNC
Implement interlaced modes by wobbling the base pointer and VFP width
for every field. This results in correct pixels but incorrect VSYNC.
+++ /dev/null
-From f85f3509692f966ec32e4db499f7e64dc6b6b952 Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Thu, 12 Dec 2024 10:09:13 +0000
-Subject: [PATCH 1457/1482] pwm: Improve PWM_PIO_RP1 dependencies
-
-PWM_PIO_RP1 should select RP1_PIO, as it is useless without it.
-
-Signed-off-by: Phil Elwell <phil@raspberrypi.com>
----
- drivers/pwm/Kconfig | 1 +
- 1 file changed, 1 insertion(+)
-
---- a/drivers/pwm/Kconfig
-+++ b/drivers/pwm/Kconfig
-@@ -457,6 +457,7 @@ config PWM_PCA9685
- config PWM_PIO_RP1
- tristate "RP1 PIO PWM support"
- depends on FIRMWARE_RP1 || COMPILE_TEST
-+ select RP1_PIO
- help
- This is a PWM framework driver for Raspberry Pi 5, using the PIO
- hardware of RP1 to provide PWM functionality. Supports up to 4
+++ /dev/null
-From 73fb1e979a210094935f4af4c3d6e700fba30c5f Mon Sep 17 00:00:00 2001
-From: Phil Elwell <phil@raspberrypi.com>
-Date: Thu, 12 Dec 2024 10:28:54 +0000
-Subject: [PATCH 1458/1482] Revert "pwm: Improve PWM_PIO_RP1 dependencies"
-
-This reverts commit f85f3509692f966ec32e4db499f7e64dc6b6b952.
----
- drivers/pwm/Kconfig | 1 -
- 1 file changed, 1 deletion(-)
-
---- a/drivers/pwm/Kconfig
-+++ b/drivers/pwm/Kconfig
-@@ -457,7 +457,6 @@ config PWM_PCA9685
- config PWM_PIO_RP1
- tristate "RP1 PIO PWM support"
- depends on FIRMWARE_RP1 || COMPILE_TEST
-- select RP1_PIO
- help
- This is a PWM framework driver for Raspberry Pi 5, using the PIO
- hardware of RP1 to provide PWM functionality. Supports up to 4
--- /dev/null
+From ac0cd73932aa1e371ffaf0b974855ed3cd22937f Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Wed, 11 Dec 2024 13:47:30 +0000
+Subject: [PATCH] ASoC: allo-piano-dac-plus: Fix volume limit locking
+
+Calling snd_soc_limit_volume from within a kcontrol put handler seems
+to cause a deadlock as it attempts to claim a write lock that is already
+held. Call snd_soc_limit_volume from the main initialisation code
+instead, to avoid the recursive locking.
+
+See: https://github.com/raspberrypi/linux/issues/6527
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ sound/soc/bcm/allo-piano-dac-plus.c | 32 +++++++++++++----------------
+ 1 file changed, 14 insertions(+), 18 deletions(-)
+
+--- a/sound/soc/bcm/allo-piano-dac-plus.c
++++ b/sound/soc/bcm/allo-piano-dac-plus.c
+@@ -452,14 +452,6 @@ static int pcm512x_set_reg_sub(struct sn
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+- if (digital_gain_0db_limit) {
+- ret = snd_soc_limit_volume(card, "Subwoofer Playback Volume",
+- 207);
+- if (ret < 0)
+- dev_warn(card->dev, "Failed to set volume limit: %d\n",
+- ret);
+- }
+-
+ // When in Dual Mono, Sub vol control should not set anything.
+ if (glb_ptr->dual_mode != 1) { //Not in Dual Mono mode
+
+@@ -562,14 +554,6 @@ static int pcm512x_set_reg_master(struct
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+- if (digital_gain_0db_limit) {
+- ret = snd_soc_limit_volume(card, "Master Playback Volume",
+- 207);
+- if (ret < 0)
+- dev_warn(card->dev, "Failed to set volume limit: %d\n",
+- ret);
+- }
+-
+ if (glb_ptr->dual_mode == 1) { //in Dual Mono Mode
+
+ ret = snd_soc_component_write(asoc_rtd_to_codec(rtd, 0)->component,
+@@ -750,6 +734,18 @@ static int snd_allo_piano_dac_init(struc
+ if (digital_gain_0db_limit) {
+ int ret;
+
++ ret = snd_soc_limit_volume(card, "Master Playback Volume",
++ 207);
++ if (ret < 0)
++ dev_warn(card->dev, "Failed to set master volume limit: %d\n",
++ ret);
++
++ ret = snd_soc_limit_volume(card, "Subwoofer Playback Volume",
++ 207);
++ if (ret < 0)
++ dev_warn(card->dev, "Failed to set subwoofer volume limit: %d\n",
++ ret);
++
+ //Set volume limit on both dacs
+ for (i = 0; i < ARRAY_SIZE(codec_ctl_pfx); i++) {
+ char cname[256];
+@@ -757,8 +753,8 @@ static int snd_allo_piano_dac_init(struc
+ sprintf(cname, "%s %s", codec_ctl_pfx[i], codec_ctl_name[0]);
+ ret = snd_soc_limit_volume(card, cname, 207);
+ if (ret < 0)
+- dev_warn(card->dev, "Failed to set volume limit: %d\n",
+- ret);
++ dev_warn(card->dev, "Failed to set %s volume limit: %d\n",
++ cname, ret);
+ }
+ }
+
--- /dev/null
+From af4ab4fb77dfc697c8ae068b18f27de1ee5d609f Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 11 Dec 2024 16:30:43 +0000
+Subject: [PATCH] drm: vc4: txp: Do not allow 24bpp formats when transposing
+
+The hardware doesn't support transposing to 24bpp (RGB888/BGR888)
+formats. There's no way to advertise this through DRM, so block
+it from atomic_check instead.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/gpu/drm/vc4/vc4_txp.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+--- a/drivers/gpu/drm/vc4/vc4_txp.c
++++ b/drivers/gpu/drm/vc4/vc4_txp.c
+@@ -272,6 +272,13 @@ static int vc4_txp_connector_atomic_chec
+ return -EINVAL;
+ }
+
++ if (conn_state->rotation & DRM_MODE_TRANSPOSE &&
++ (fb->format->format == DRM_FORMAT_RGB888 ||
++ fb->format->format == DRM_FORMAT_BGR888)) {
++ DRM_DEBUG_KMS("24bpp formats not supported when transposing\n");
++ return -EINVAL;
++ }
++
+ for (i = 0; i < ARRAY_SIZE(drm_fmts); i++) {
+ if (fb->format->format == drm_fmts[i])
+ break;
--- /dev/null
+From 0b216b3988e5b7035cd5ed8a9910eacbb3420ce0 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Thu, 12 Dec 2024 11:59:52 +0000
+Subject: [PATCH] drm: Validate connector rotation has one bit set in the
+ rotation property
+
+Copy the same validation logic as from the plane rotation property.
+
+Fixes: 8fec3ff87049 ("drm: Add a rotation parameter to connectors.")
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/gpu/drm/drm_atomic_uapi.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/drivers/gpu/drm/drm_atomic_uapi.c
++++ b/drivers/gpu/drm/drm_atomic_uapi.c
+@@ -812,6 +812,12 @@ static int drm_atomic_connector_set_prop
+ } else if (property == connector->privacy_screen_sw_state_property) {
+ state->privacy_screen_sw_state = val;
+ } else if (property == connector->rotation_property) {
++ if (!is_power_of_2(val & DRM_MODE_ROTATE_MASK)) {
++ drm_dbg_atomic(connector->dev,
++ "[CONNECTOR:%d:%s] bad rotation bitmask: 0x%llx\n",
++ connector->base.id, connector->name, val);
++ return -EINVAL;
++ }
+ state->rotation = val;
+ } else if (connector->funcs->atomic_set_property) {
+ return connector->funcs->atomic_set_property(connector,
--- /dev/null
+From 61494a7aa2ea887fa1cd1399a8db1317c87f661b Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Thu, 12 Dec 2024 13:05:41 +0000
+Subject: [PATCH] ASoC: allo-piano-dac-plus: Suppress -517 errors
+
+Use dev_err_probe to simplify the code and suppress EPROBE_DEFER errors.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ sound/soc/bcm/allo-piano-dac-plus.c | 37 ++++++++---------------------
+ 1 file changed, 10 insertions(+), 27 deletions(-)
+
+--- a/sound/soc/bcm/allo-piano-dac-plus.c
++++ b/sound/soc/bcm/allo-piano-dac-plus.c
+@@ -974,48 +974,31 @@ static int snd_allo_piano_dac_probe(stru
+
+ allo_piano_2_1_codecs[0].of_node =
+ of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+- if (!allo_piano_2_1_codecs[0].of_node) {
+- dev_err(&pdev->dev,
+- "Property 'audio-codec' missing or invalid\n");
+- return -EINVAL;
+- }
+-
+ allo_piano_2_1_codecs[1].of_node =
+ of_parse_phandle(pdev->dev.of_node, "audio-codec", 1);
+- if (!allo_piano_2_1_codecs[1].of_node) {
+- dev_err(&pdev->dev,
++ if (!allo_piano_2_1_codecs[0].of_node || !allo_piano_2_1_codecs[1].of_node)
++ return dev_err_probe(&pdev->dev, -EINVAL,
+ "Property 'audio-codec' missing or invalid\n");
+- return -EINVAL;
+- }
+
+ mute_gpio[0] = devm_gpiod_get_optional(&pdev->dev, "mute1",
+ GPIOD_OUT_LOW);
+- if (IS_ERR(mute_gpio[0])) {
+- ret = PTR_ERR(mute_gpio[0]);
+- dev_err(&pdev->dev,
+- "failed to get mute1 gpio6: %d\n", ret);
+- return ret;
+- }
++ if (IS_ERR(mute_gpio[0]))
++ return dev_err_probe(&pdev->dev, PTR_ERR(mute_gpio[0]),
++ "failed to get mute1 gpio\n");
+
+ mute_gpio[1] = devm_gpiod_get_optional(&pdev->dev, "mute2",
+ GPIOD_OUT_LOW);
+- if (IS_ERR(mute_gpio[1])) {
+- ret = PTR_ERR(mute_gpio[1]);
+- dev_err(&pdev->dev,
+- "failed to get mute2 gpio25: %d\n", ret);
+- return ret;
+- }
++ if (IS_ERR(mute_gpio[1]))
++ return dev_err_probe(&pdev->dev, PTR_ERR(mute_gpio[1]),
++ "failed to get mute2 gpio\n");
+
+ if (mute_gpio[0] && mute_gpio[1])
+ snd_allo_piano_dac.set_bias_level =
+ snd_allo_piano_set_bias_level;
+
+ ret = snd_soc_register_card(&snd_allo_piano_dac);
+- if (ret < 0) {
+- dev_err(&pdev->dev,
+- "snd_soc_register_card() failed: %d\n", ret);
+- return ret;
+- }
++ if (ret < 0)
++ return dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
+
+ if ((mute_gpio[0]) && (mute_gpio[1]))
+ snd_allo_piano_gpio_mute(&snd_allo_piano_dac);
From 80533a952218696c0ef1b346bab50dc401e6b74c Mon Sep 17 00:00:00 2001
From: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
Date: Thu, 12 Dec 2024 11:58:12 +0000
-Subject: [PATCH 1463/1482] drm: rp1: rp1-dpi: Fix optional dependency on
- RP1_PIO
+Subject: [PATCH] drm: rp1: rp1-dpi: Fix optional dependency on RP1_PIO
Add optional dependency to Kconfig, and conditionally compile
PIO-dependent code. Add a mode validation function to reject
--- /dev/null
+From 694247173f2e136196d7cb3a392c84cda65674d2 Mon Sep 17 00:00:00 2001
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Date: Mon, 7 Oct 2024 12:27:15 -0400
+Subject: [PATCH] serial: sc16is7xx: announce support for SER_RS485_RTS_ON_SEND
+
+commit 068d35a7be65fa3bca4bba21c269bfe0b39158a6 upstream.
+
+When specifying flag SER_RS485_RTS_ON_SEND in RS485 configuration,
+we get the following warning after commit 4afeced55baa ("serial: core:
+fix sanitizing check for RTS settings"):
+
+ invalid RTS setting, using RTS_AFTER_SEND instead
+
+This results in SER_RS485_RTS_AFTER_SEND being set and the
+driver always write to the register field SC16IS7XX_EFCR_RTS_INVERT_BIT,
+which breaks some hardware using these chips.
+
+The hardware supports both RTS_ON_SEND and RTS_AFTER_SEND modes, so fix
+this by announcing support for RTS_ON_SEND.
+
+Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Suggested-by: Konstantin Pugin <ria.freelander@gmail.com>
+Link: https://lore.kernel.org/lkml/20240422133219.2710061-2-ria.freelander@gmail.com
+Reviewed-by: Andy Shevchenko <andy@kernel.org>
+Tested-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Link: https://lore.kernel.org/r/20241007162716.3122912-1-hugo@hugovil.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/sc16is7xx.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/tty/serial/sc16is7xx.c
++++ b/drivers/tty/serial/sc16is7xx.c
+@@ -1457,7 +1457,7 @@ static int sc16is7xx_setup_mctrl_ports(s
+ }
+
+ static const struct serial_rs485 sc16is7xx_rs485_supported = {
+- .flags = SER_RS485_ENABLED | SER_RS485_RTS_AFTER_SEND,
++ .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND,
+ .delay_rts_before_send = 1,
+ .delay_rts_after_send = 1, /* Not supported but keep returning -EINVAL */
+ };
--- /dev/null
+From b75fd2a9385e1358fa82218184e73513f9a5e57f Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Mon, 16 Dec 2024 15:11:08 +0000
+Subject: [PATCH] dtoverlays: Add override for target-path on I2C overlays
+
+To allow for attaching any of the standard overlays to a
+bitbashed i2c-gpio bus, allow specifying the target path for
+the overlay.
+
+Suggested by:
+https://forums.raspberrypi.com/viewtopic.php?t=381059
+
+Example:
+dtoverlay=i2c-gpio,i2c_gpio_sda=10,i2c_gpio_scl=11
+dtoverlay=mcp23017,i2c-path=/i2c@0
+dtoverlay=i2c-gpio,i2c_gpio_sda=12,i2c_gpio_scl=13,bus=3
+dtoverlay=mcp23017,i2c-path=/i2c@3
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ arch/arm/boot/dts/overlays/README | 59 +++++++++++++++++++
+ .../arm/boot/dts/overlays/ads1115-overlay.dts | 2 +
+ .../boot/dts/overlays/edt-ft5406-overlay.dts | 3 +
+ arch/arm/boot/dts/overlays/goodix-overlay.dts | 4 +-
+ .../dts/overlays/hd44780-i2c-lcd-overlay.dts | 4 +-
+ .../arm/boot/dts/overlays/i2c-fan-overlay.dts | 2 +
+ .../arm/boot/dts/overlays/i2c-mux-overlay.dts | 2 +
+ .../dts/overlays/i2c-pwm-pca9685a-overlay.dts | 2 +
+ .../arm/boot/dts/overlays/i2c-rtc-overlay.dts | 2 +
+ .../boot/dts/overlays/i2c-sensor-overlay.dts | 2 +
+ .../boot/dts/overlays/ilitek251x-overlay.dts | 4 +-
+ .../boot/dts/overlays/mcp23017-overlay.dts | 2 +
+ .../arm/boot/dts/overlays/pca953x-overlay.dts | 30 +++++++++-
+ .../arm/boot/dts/overlays/pcf857x-overlay.dts | 30 +++++++++-
+ .../dts/overlays/sc16is750-i2c-overlay.dts | 30 +++++++++-
+ .../dts/overlays/sc16is752-i2c-overlay.dts | 30 +++++++++-
+ 16 files changed, 201 insertions(+), 7 deletions(-)
+
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -555,6 +555,7 @@ Params: addr I2C bus
+ overlay - BCM2711 only)
+ i2c6 Choose the I2C6 bus (configure with the i2c6
+ overlay - BCM2711 only)
++ i2c-path Override I2C path to allow for i2c-gpio buses
+
+ Channel parameters can be set for each enabled channel.
+ A maximum of 4 channels can be enabled (letters a thru d).
+@@ -1238,6 +1239,7 @@ Params: sizex Touchscr
+ addr Sets the address for the touch controller. Note
+ that the device must be configured to use the
+ specified address.
++ i2c-path Override I2C path to allow for i2c-gpio buses
+
+
+ Name: enc28j60
+@@ -1439,6 +1441,7 @@ Info: Enables I2C connected Goodix gt9
+ Load: dtoverlay=goodix,<param>=<val>
+ Params: interrupt GPIO used for interrupt (default 4)
+ reset GPIO used for reset (default 17)
++ i2c-path Override I2C path to allow for i2c-gpio buses
+
+
+ Name: googlevoicehat-soundcard
+@@ -1730,6 +1733,7 @@ Params: addr I2C addr
+ display_height Height of the display in characters (default 2)
+
+ display_width Width of the display in characters (default 16)
++ i2c-path Override I2C path to allow for i2c-gpio buses
+
+
+ Name: hd44780-lcd
+@@ -2095,6 +2099,8 @@ Params: addr Sets the
+ i2c6 Choose the I2C6 bus (configure with the i2c6
+ overlay - BCM2711 only)
+
++ i2c-path Override I2C path to allow for i2c-gpio buses
++
+ minpwm PWM setting for the fan when the SoC is below
+ mintemp (range 0-255. default 0)
+ maxpwm PWM setting for the fan when the SoC is above
+@@ -2165,6 +2171,8 @@ Params: pca9542 Select t
+ i2c6 Choose the I2C6 bus (configure with the i2c6
+ overlay - BCM2711 only)
+
++ i2c-path Override I2C path to allow for i2c-gpio buses
++
+ disconnect_on_idle Force the mux to disconnect all child buses
+ after every transaction.
+
+@@ -2186,6 +2194,7 @@ Params: addr I2C addr
+ overlay - BCM2711 only)
+ i2c6 Choose the I2C6 bus (configure with the i2c6
+ overlay - BCM2711 only)
++ i2c-path Override I2C path to allow for i2c-gpio buses
+
+
+ Name: i2c-rtc
+@@ -2255,6 +2264,8 @@ Params: abx80x Select o
+ i2c6 Choose the I2C6 bus (configure with the i2c6
+ overlay - BCM2711 only)
+
++ i2c-path Override I2C path to allow for i2c-gpio buses
++
+ addr Sets the address for the RTC. Note that the
+ device must be configured to use the specified
+ address.
+@@ -2519,6 +2530,8 @@ Params: addr Set the
+ i2c6 Choose the I2C6 bus (configure with the i2c6
+ overlay - BCM2711 only)
+
++ i2c-path Override I2C path to allow for i2c-gpio buses
++
+
+ Name: i2c0
+ Info: Change i2c0 pin usage. Not all pin combinations are usable on all
+@@ -2661,6 +2674,7 @@ Params: interrupt GPIO use
+ touchscreen (in pixels)
+ sizey Touchscreen size y, vertical resolution of
+ touchscreen (in pixels)
++ i2c-path Override I2C path to allow for i2c-gpio buses
+
+
+ Name: imx219
+@@ -3138,6 +3152,7 @@ Params: gpiopin Gpio pin
+ overlay - BCM2711 only)
+ i2c6 Choose the I2C6 bus (configure with the i2c6
+ overlay - BCM2711 only)
++ i2c-path Override I2C path to allow for i2c-gpio buses
+
+
+ Name: mcp23s17
+@@ -3587,6 +3602,17 @@ Params: addr I2C addr
+ cat9554 Select the Onnn CAT9554 (8 bit)
+ pca9654 Select the Onnn PCA9654 (8 bit)
+ xra1202 Select the Exar XRA1202 (8 bit)
++ i2c0 Choose the I2C0 bus on GPIOs 0&1
++ i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c3 Choose the I2C3 bus (configure with the i2c3
++ overlay - BCM2711 only)
++ i2c4 Choose the I2C3 bus (configure with the i2c3
++ overlay - BCM2711 only)
++ i2c5 Choose the I2C5 bus (configure with the i2c4
++ overlay - BCM2711 only)
++ i2c6 Choose the I2C6 bus (configure with the i2c6
++ overlay - BCM2711 only)
++ i2c-path Override I2C path to allow for i2c-gpio buses
+
+
+ Name: pcf857x
+@@ -3598,6 +3624,17 @@ Params: addr I2C addr
+ pcf8574a Select the NXP PCF8574A (8 bit)
+ pcf8575 Select the NXP PCF8575 (16 bit)
+ pca8574 Select the NXP PCA8574 (8 bit)
++ i2c0 Choose the I2C0 bus on GPIOs 0&1
++ i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c3 Choose the I2C3 bus (configure with the i2c3
++ overlay - BCM2711 only)
++ i2c4 Choose the I2C3 bus (configure with the i2c3
++ overlay - BCM2711 only)
++ i2c5 Choose the I2C5 bus (configure with the i2c4
++ overlay - BCM2711 only)
++ i2c6 Choose the I2C6 bus (configure with the i2c6
++ overlay - BCM2711 only)
++ i2c-path Override I2C path to allow for i2c-gpio buses
+
+
+ Name: pcie-32bit-dma
+@@ -4257,6 +4294,17 @@ Load: dtoverlay=sc16is750-i2c,<param>=
+ Params: int_pin GPIO used for IRQ (default 24)
+ addr Address (default 0x48)
+ xtal On-board crystal frequency (default 14745600)
++ i2c0 Choose the I2C0 bus on GPIOs 0&1
++ i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c3 Choose the I2C3 bus (configure with the i2c3
++ overlay - BCM2711 only)
++ i2c4 Choose the I2C4 bus (configure with the i2c4
++ overlay - BCM2711 only)
++ i2c5 Choose the I2C5 bus (configure with the i2c5
++ overlay - BCM2711 only)
++ i2c6 Choose the I2C6 bus (configure with the i2c6
++ overlay - BCM2711 only)
++ i2c-path Override I2C path to allow for i2c-gpio buses
+
+
+ Name: sc16is750-spi0
+@@ -4275,6 +4323,17 @@ Load: dtoverlay=sc16is752-i2c,<param>=
+ Params: int_pin GPIO used for IRQ (default 24)
+ addr Address (default 0x48)
+ xtal On-board crystal frequency (default 14745600)
++ i2c0 Choose the I2C0 bus on GPIOs 0&1
++ i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c3 Choose the I2C3 bus (configure with the i2c3
++ overlay - BCM2711 only)
++ i2c4 Choose the I2C4 bus (configure with the i2c4
++ overlay - BCM2711 only)
++ i2c5 Choose the I2C5 bus (configure with the i2c5
++ overlay - BCM2711 only)
++ i2c6 Choose the I2C6 bus (configure with the i2c6
++ overlay - BCM2711 only)
++ i2c-path Override I2C path to allow for i2c-gpio buses
+
+
+ Name: sc16is752-spi0
+--- a/arch/arm/boot/dts/overlays/ads1115-overlay.dts
++++ b/arch/arm/boot/dts/overlays/ads1115-overlay.dts
+@@ -131,5 +131,7 @@
+ <&frag100>, "target-path=i2c5";
+ i2c6 = <&frag100>, "target?=0",
+ <&frag100>, "target-path=i2c6";
++ i2c-path = <&frag100>, "target?=0",
++ <&frag100>, "target-path";
+ };
+ };
+--- a/arch/arm/boot/dts/overlays/edt-ft5406-overlay.dts
++++ b/arch/arm/boot/dts/overlays/edt-ft5406-overlay.dts
+@@ -41,6 +41,9 @@
+ i2c6 = <&ts_i2c_frag>, "target?=0",
+ <&ts_i2c_frag>, "target-path=i2c6",
+ <0>,"-0-1";
++ i2c-path = <&ts_i2c_frag>, "target?=0",
++ <&ts_i2c_frag>, "target-path",
++ <0>,"-0-1";
+ addr = <&ft5406>,"reg:0";
+ };
+ };
+--- a/arch/arm/boot/dts/overlays/goodix-overlay.dts
++++ b/arch/arm/boot/dts/overlays/goodix-overlay.dts
+@@ -16,7 +16,7 @@
+ };
+ };
+
+- fragment@1 {
++ i2c_frag: fragment@1 {
+ target = <&i2c1>;
+ __overlay__ {
+ #address-cells = <1>;
+@@ -42,5 +42,7 @@
+ <>9271>,"irq-gpios:4";
+ reset = <&goodix_pins>,"brcm,pins:4",
+ <>9271>,"reset-gpios:4";
++ i2c-path = <&i2c_frag>, "target?=0",
++ <&i2c_frag>, "target-path";
+ };
+ };
+--- a/arch/arm/boot/dts/overlays/hd44780-i2c-lcd-overlay.dts
++++ b/arch/arm/boot/dts/overlays/hd44780-i2c-lcd-overlay.dts
+@@ -4,7 +4,7 @@
+ / {
+ compatible = "brcm,bcm2835";
+
+- fragment@0 {
++ i2c_frag: fragment@0 {
+ target = <&i2c_arm>;
+ __overlay__ {
+ status = "okay";
+@@ -52,6 +52,8 @@
+ display_height = <&lcd_screen>,"display-height-chars:0";
+ display_width = <&lcd_screen>,"display-width-chars:0";
+ addr = <&pcf857x>,"reg:0";
++ i2c-path = <&i2c_frag>, "target?=0",
++ <&i2c_frag>, "target-path";
+ };
+
+ };
+--- a/arch/arm/boot/dts/overlays/i2c-fan-overlay.dts
++++ b/arch/arm/boot/dts/overlays/i2c-fan-overlay.dts
+@@ -93,6 +93,8 @@
+ <&frag100>, "target-path=i2c5";
+ i2c6 = <&frag100>, "target?=0",
+ <&frag100>, "target-path=i2c6";
++ i2c-path = <&frag100>, "target?=0",
++ <&frag100>, "target-path";
+ addr = <&emc2301>,"reg:0";
+ minpwm = <&emc2301>,"emc2305,pwm-min.0";
+ maxpwm = <&emc2301>,"emc2305,pwm-max.0";
+--- a/arch/arm/boot/dts/overlays/i2c-mux-overlay.dts
++++ b/arch/arm/boot/dts/overlays/i2c-mux-overlay.dts
+@@ -175,6 +175,8 @@
+ <&frag100>, "target-path=i2c5";
+ i2c6 = <&frag100>, "target?=0",
+ <&frag100>, "target-path=i2c6";
++ i2c-path = <&frag100>, "target?=0",
++ <&frag100>, "target-path";
+ disconnect_on_idle =
+ <&pca9542>,"idle-state:0=", <MUX_IDLE_DISCONNECT>,
+ <&pca9545>,"idle-state:0=", <MUX_IDLE_DISCONNECT>,
+--- a/arch/arm/boot/dts/overlays/i2c-pwm-pca9685a-overlay.dts
++++ b/arch/arm/boot/dts/overlays/i2c-pwm-pca9685a-overlay.dts
+@@ -57,5 +57,7 @@
+ <&frag100>, "target-path=i2c5";
+ i2c6 = <&frag100>, "target?=0",
+ <&frag100>, "target-path=i2c6";
++ i2c-path = <&frag100>, "target?=0",
++ <&frag100>, "target-path";
+ };
+ };
+--- a/arch/arm/boot/dts/overlays/i2c-rtc-overlay.dts
++++ b/arch/arm/boot/dts/overlays/i2c-rtc-overlay.dts
+@@ -38,5 +38,7 @@
+ <&frag100>, "target-path=i2c5";
+ i2c6 = <&frag100>, "target?=0",
+ <&frag100>, "target-path=i2c6";
++ i2c-path = <&frag100>, "target?=0",
++ <&frag100>, "target-path";
+ };
+ };
+--- a/arch/arm/boot/dts/overlays/i2c-sensor-overlay.dts
++++ b/arch/arm/boot/dts/overlays/i2c-sensor-overlay.dts
+@@ -38,5 +38,7 @@
+ <&frag100>, "target-path=i2c5";
+ i2c6 = <&frag100>, "target?=0",
+ <&frag100>, "target-path=i2c6";
++ i2c-path = <&frag100>, "target?=0",
++ <&frag100>, "target-path";
+ };
+ };
+--- a/arch/arm/boot/dts/overlays/ilitek251x-overlay.dts
++++ b/arch/arm/boot/dts/overlays/ilitek251x-overlay.dts
+@@ -16,7 +16,7 @@
+ };
+ };
+
+- fragment@1 {
++ frag1: fragment@1 {
+ target = <&i2c1>;
+ __overlay__ {
+ #address-cells = <1>;
+@@ -41,5 +41,7 @@
+ <&ili251x>,"interrupts:0";
+ sizex = <&ili251x>,"touchscreen-size-x:0";
+ sizey = <&ili251x>,"touchscreen-size-y:0";
++ i2c-path = <&frag1>, "target?=0",
++ <&frag1>, "target-path";
+ };
+ };
+--- a/arch/arm/boot/dts/overlays/mcp23017-overlay.dts
++++ b/arch/arm/boot/dts/overlays/mcp23017-overlay.dts
+@@ -98,6 +98,8 @@
+ <&frag100>, "target-path=i2c5";
+ i2c6 = <&frag100>, "target?=0",
+ <&frag100>, "target-path=i2c6";
++ i2c-path = <&frag100>, "target?=0",
++ <&frag100>, "target-path";
+ };
+ };
+
+--- a/arch/arm/boot/dts/overlays/pca953x-overlay.dts
++++ b/arch/arm/boot/dts/overlays/pca953x-overlay.dts
+@@ -5,7 +5,7 @@
+ /{
+ compatible = "brcm,bcm2835";
+
+- fragment@0 {
++ frag0: fragment@0 {
+ target = <&i2c_arm>;
+ __overlay__ {
+ #address-cells = <1>;
+@@ -204,6 +204,20 @@
+ };
+ };
+
++ fragment@100 {
++ target = <&i2c0if>;
++ __dormant__ {
++ status = "okay";
++ };
++ };
++
++ fragment@101 {
++ target = <&i2c0mux>;
++ __dormant__ {
++ status = "okay";
++ };
++ };
++
+ __overrides__ {
+ addr = <&pca>,"reg:0";
+ pca6416 = <0>, "+1";
+@@ -236,5 +250,19 @@
+ cat9554 = <0>, "+28";
+ pca9654 = <0>, "+29";
+ xra1202 = <0>, "+30";
++ i2c0 = <&frag0>, "target:0=",<&i2c0>,
++ <0>,"+100+101";
++ i2c_csi_dsi = <&frag0>, "target:0=",<&i2c_csi_dsi>,
++ <0>,"+100+101";
++ i2c3 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c3";
++ i2c4 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c4";
++ i2c5 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c5";
++ i2c6 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c6";
++ i2c-path = <&frag0>, "target?=0",
++ <&frag0>, "target-path";
+ };
+ };
+--- a/arch/arm/boot/dts/overlays/pcf857x-overlay.dts
++++ b/arch/arm/boot/dts/overlays/pcf857x-overlay.dts
+@@ -6,7 +6,7 @@
+ / {
+ compatible = "brcm,bcm2835";
+
+- fragment@0 {
++ frag0: fragment@0 {
+ target = <&i2c_arm>;
+ __overlay__ {
+ #address-cells = <1>;
+@@ -22,11 +22,39 @@
+ };
+ };
+
++ fragment@100 {
++ target = <&i2c0if>;
++ __dormant__ {
++ status = "okay";
++ };
++ };
++
++ fragment@101 {
++ target = <&i2c0mux>;
++ __dormant__ {
++ status = "okay";
++ };
++ };
++
+ __overrides__ {
+ pcf8574 = <&pcf857x>,"compatible=nxp,pcf8574", <&pcf857x>,"reg:0=0x20";
+ pcf8574a = <&pcf857x>,"compatible=nxp,pcf8574a", <&pcf857x>,"reg:0=0x38";
+ pcf8575 = <&pcf857x>,"compatible=nxp,pcf8575", <&pcf857x>,"reg:0=0x20";
+ pca8574 = <&pcf857x>,"compatible=nxp,pca8574", <&pcf857x>,"reg:0=0x20";
+ addr = <&pcf857x>,"reg:0";
++ i2c0 = <&frag0>, "target:0=",<&i2c0>,
++ <0>,"+100+101";
++ i2c_csi_dsi = <&frag0>, "target:0=",<&i2c_csi_dsi>,
++ <0>,"+100+101";
++ i2c3 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c3";
++ i2c4 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c4";
++ i2c5 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c5";
++ i2c6 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c6";
++ i2c-path = <&frag0>, "target?=0",
++ <&frag0>, "target-path";
+ };
+ };
+--- a/arch/arm/boot/dts/overlays/sc16is750-i2c-overlay.dts
++++ b/arch/arm/boot/dts/overlays/sc16is750-i2c-overlay.dts
+@@ -4,7 +4,7 @@
+ / {
+ compatible = "brcm,bcm2835";
+
+- fragment@0 {
++ frag0: fragment@0 {
+ target = <&i2c_arm>;
+ __overlay__ {
+ #address-cells = <1>;
+@@ -48,10 +48,38 @@
+ };
+ };
+
++ fragment@100 {
++ target = <&i2c0if>;
++ __dormant__ {
++ status = "okay";
++ };
++ };
++
++ fragment@101 {
++ target = <&i2c0mux>;
++ __dormant__ {
++ status = "okay";
++ };
++ };
++
+ __overrides__ {
+ int_pin = <&sc16is750>,"interrupts:0", <&int_pins>,"brcm,pins:0",
+ <&int_pins>,"reg:0";
+ addr = <&sc16is750>,"reg:0", <&sc16is750_clk>,"name";
+ xtal = <&sc16is750_clk>,"clock-frequency:0";
++ i2c0 = <&frag0>, "target:0=",<&i2c0>,
++ <0>,"+100+101";
++ i2c_csi_dsi = <&frag0>, "target:0=",<&i2c_csi_dsi>,
++ <0>,"+100+101";
++ i2c3 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c3";
++ i2c4 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c4";
++ i2c5 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c5";
++ i2c6 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c6";
++ i2c-path = <&frag0>, "target?=0",
++ <&frag0>, "target-path";
+ };
+ };
+--- a/arch/arm/boot/dts/overlays/sc16is752-i2c-overlay.dts
++++ b/arch/arm/boot/dts/overlays/sc16is752-i2c-overlay.dts
+@@ -4,7 +4,7 @@
+ / {
+ compatible = "brcm,bcm2835";
+
+- fragment@0 {
++ frag0: fragment@0 {
+ target = <&i2c_arm>;
+ __overlay__ {
+ #address-cells = <1>;
+@@ -48,10 +48,38 @@
+ };
+ };
+
++ fragment@100 {
++ target = <&i2c0if>;
++ __dormant__ {
++ status = "okay";
++ };
++ };
++
++ fragment@101 {
++ target = <&i2c0mux>;
++ __dormant__ {
++ status = "okay";
++ };
++ };
++
+ __overrides__ {
+ int_pin = <&sc16is752>,"interrupts:0", <&int_pins>,"brcm,pins:0",
+ <&int_pins>,"reg:0";
+ addr = <&sc16is752>,"reg:0",<&sc16is752_clk>,"name";
+ xtal = <&sc16is752_clk>,"clock-frequency:0";
++ i2c0 = <&frag0>, "target:0=",<&i2c0>,
++ <0>,"+100+101";
++ i2c_csi_dsi = <&frag0>, "target:0=",<&i2c_csi_dsi>,
++ <0>,"+100+101";
++ i2c3 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c3";
++ i2c4 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c4";
++ i2c5 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c5";
++ i2c6 = <&frag0>, "target?=0",
++ <&frag0>, "target-path=i2c6";
++ i2c-path = <&frag0>, "target?=0",
++ <&frag0>, "target-path";
+ };
+ };
From 4b0ca96738bb937529655a0062d60775f47b0f5e Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Mon, 16 Dec 2024 23:01:41 +0000
-Subject: [PATCH 1468/1482] misc: rp1-pio: Support larger data transfers
+Subject: [PATCH] misc: rp1-pio: Support larger data transfers
Add a separate IOCTL for larger transfer with a 32-bit data_bytes
field.
--- /dev/null
+From a4a4d7f9183bae11d81616346038e9efaba2fce1 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Mon, 16 Dec 2024 19:15:52 +0000
+Subject: [PATCH] dtoverlays: Use continuous clock mode for ov9281
+
+This increases the maximum frame rate from 247 to 260fps in
+10-bit mode.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ arch/arm/boot/dts/overlays/ov9281-overlay.dts | 1 -
+ arch/arm/boot/dts/overlays/ov9281.dtsi | 1 -
+ 2 files changed, 2 deletions(-)
+
+--- a/arch/arm/boot/dts/overlays/ov9281-overlay.dts
++++ b/arch/arm/boot/dts/overlays/ov9281-overlay.dts
+@@ -29,7 +29,6 @@
+ csi_ep: endpoint {
+ remote-endpoint = <&cam_endpoint>;
+ data-lanes = <1 2>;
+- clock-noncontinuous;
+ };
+ };
+ };
+--- a/arch/arm/boot/dts/overlays/ov9281.dtsi
++++ b/arch/arm/boot/dts/overlays/ov9281.dtsi
+@@ -19,7 +19,6 @@ cam_node: ov9281@60 {
+ cam_endpoint: endpoint {
+ clock-lanes = <0>;
+ data-lanes = <1 2>;
+- clock-noncontinuous;
+ link-frequencies =
+ /bits/ 64 <400000000>;
+ };
--- /dev/null
+From 62085522016ee2dadbe8668a6a97919770020817 Mon Sep 17 00:00:00 2001
+From: Renjaya Raga Zenta <ragazenta@gmail.com>
+Date: Wed, 18 Dec 2024 16:44:32 +0700
+Subject: [PATCH] overlays: goodix: Allow override i2c address
+
+Some Goodix devices e.g. gt911 use address 0x5d instead of 0x14.
+So, make the address overridable.
+
+Signed-off-by: Renjaya Raga Zenta <ragazenta@gmail.com>
+---
+ arch/arm/boot/dts/overlays/README | 3 ++-
+ arch/arm/boot/dts/overlays/goodix-overlay.dts | 1 +
+ 2 files changed, 3 insertions(+), 1 deletion(-)
+
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -1439,7 +1439,8 @@ Name: goodix
+ Info: Enables I2C connected Goodix gt9271 multiple touch controller using
+ GPIOs 4 and 17 (pins 7 and 11 on GPIO header) for interrupt and reset.
+ Load: dtoverlay=goodix,<param>=<val>
+-Params: interrupt GPIO used for interrupt (default 4)
++Params: addr I2C address (default 0x14)
++ interrupt GPIO used for interrupt (default 4)
+ reset GPIO used for reset (default 17)
+ i2c-path Override I2C path to allow for i2c-gpio buses
+
+--- a/arch/arm/boot/dts/overlays/goodix-overlay.dts
++++ b/arch/arm/boot/dts/overlays/goodix-overlay.dts
+@@ -37,6 +37,7 @@
+ };
+
+ __overrides__ {
++ addr = <>9271>,"reg:0";
+ interrupt = <&goodix_pins>,"brcm,pins:0",
+ <>9271>,"interrupts:0",
+ <>9271>,"irq-gpios:4";
From cd26850713088942ca4f9a248a8bed1f0504a58f Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Thu, 19 Dec 2024 15:11:40 +0000
-Subject: [PATCH 1471/1482] fixup! misc: Add RP1 PIO driver
+Subject: [PATCH] fixup! misc: Add RP1 PIO driver
Change the Kconfig dependencies so that RP1_PIO depends on FIRMWARE_RP1,
rather than selecting it.
From 468b525d45a726e4ba704b33c4eba53de47ac684 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Thu, 5 Dec 2024 16:03:39 +0000
-Subject: [PATCH 1473/1482] misc: rp1-pio: More logical probe sequence
+Subject: [PATCH] misc: rp1-pio: More logical probe sequence
Sort the probe function initialisation into a more logical order.
From 5c07ba20630a629399eaa6583457aca93ff74606 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Mon, 9 Dec 2024 09:58:29 +0000
-Subject: [PATCH 1474/1482] misc: rp1-pio: Convert floats to 24.8 fixed point
+Subject: [PATCH] misc: rp1-pio: Convert floats to 24.8 fixed point
Floating point arithmetic is not supported in the kernel, so use fixed
point instead.
From 75203c6641cfe47dfb817b095430021b0981ff47 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Tue, 10 Dec 2024 12:06:14 +0000
-Subject: [PATCH 1475/1482] misc: rp1-pio: Minor cosmetic tweaks
+Subject: [PATCH] misc: rp1-pio: Minor cosmetic tweaks
No functional change.
From fddd3e9318dbf01fb763b6880021abc558fce8e6 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Thu, 12 Dec 2024 17:09:27 +0000
-Subject: [PATCH 1476/1482] misc: rp1-pio: Add in-kernel DMA support
+Subject: [PATCH] misc: rp1-pio: Add in-kernel DMA support
Add kernel-facing implementations of pio_sm_config_xfer and
pio_xm_xfer_data.
From d6d83ad3d9a3a594909a1ad1c82b735ab711cd12 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Tue, 3 Dec 2024 16:09:30 +0000
-Subject: [PATCH 1477/1482] misc: Add ws2812-pio-rp1 driver
+Subject: [PATCH] misc: Add ws2812-pio-rp1 driver
ws2812-pio-rp1 is a PIO-based driver for WS2812 LEDS. It creates a
character device in /dev, the default name of which is /dev/leds<n>,
From 4a8f2b39157825fefc505fe4b94f3a9ce101e170 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Thu, 12 Dec 2024 23:23:39 +0000
-Subject: [PATCH 1478/1482] overlays: Add ws2812-pio overlay
+Subject: [PATCH] overlays: Add ws2812-pio overlay
Add an overlay to enable a WS2812 LED driver on a given GPIO.
--- a/arch/arm/boot/dts/overlays/Makefile
+++ b/arch/arm/boot/dts/overlays/Makefile
-@@ -337,7 +337,8 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
+@@ -342,7 +342,8 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
waveshare-can-fd-hat-mode-a.dtbo \
waveshare-can-fd-hat-mode-b.dtbo \
wittypi.dtbo \
targets += $(dtbo-y)
--- a/arch/arm/boot/dts/overlays/README
+++ b/arch/arm/boot/dts/overlays/README
-@@ -5487,6 +5487,28 @@ Params: alsaname Changes
+@@ -5599,6 +5599,28 @@ Params: alsaname Changes
compatible Changes the codec compatibility
--- /dev/null
+From 489570796a5789f849683fc3fb034c55cb13e4c6 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Thu, 19 Dec 2024 17:13:17 +0000
+Subject: [PATCH] overlays: Add and document i2c_csi_dsi0 parameters
+
+Add "i2c_csi_dsi0" parameters to overlays that already have an
+"i2c_csi_dsi" parameter.
+
+The I2C bus and GPIO mapping of i2c_csi_dsi and i2c_csi_dsi0 varies
+between platforms. Document the associations against the dtparams
+"i2c_csi_dsi" and "i2c_csi_dsi0" - run "dtparam -h i2c_csi_dsi"
+and "dtparam -h i2c_csi_dsi0" to read it.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ arch/arm/boot/dts/overlays/README | 103 ++++++++++++++++--
+ .../arm/boot/dts/overlays/ads1115-overlay.dts | 2 +
+ .../arm/boot/dts/overlays/i2c-fan-overlay.dts | 2 +
+ .../arm/boot/dts/overlays/i2c-mux-overlay.dts | 2 +
+ .../dts/overlays/i2c-pwm-pca9685a-overlay.dts | 2 +
+ .../arm/boot/dts/overlays/i2c-rtc-overlay.dts | 2 +
+ .../boot/dts/overlays/i2c-sensor-overlay.dts | 2 +
+ .../boot/dts/overlays/mcp23017-overlay.dts | 2 +
+ .../arm/boot/dts/overlays/pca953x-overlay.dts | 2 +
+ .../arm/boot/dts/overlays/pcf857x-overlay.dts | 2 +
+ .../dts/overlays/sc16is750-i2c-overlay.dts | 2 +
+ .../dts/overlays/sc16is752-i2c-overlay.dts | 2 +
+ 12 files changed, 113 insertions(+), 12 deletions(-)
+
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -301,10 +301,31 @@ Params:
+ i2c_baudrate An alias for i2c_arm_baudrate
+
+ i2c_csi_dsi Set to "on" to enable the i2c_csi_dsi interface
++ The I2C bus and GPIOs are platform specific:
++ B rev 1:
++ i2c-1 on 2 & 3
++ B rev 2, B+, CM, Zero, Zero W, 2B, CM2, CM3,
++ CM4S:
++ i2c-0 on 28 & 29
++ 3B, 3B+, Zero 2W, 4B, 400, CM4:
++ i2c-0 on 44 & 45
++ 5, 500:
++ i2c-11/i2c-4 on 40 & 41
++ CM5 on CM5IO:
++ i2c-0 on 0 & 1
++ CM5 on CM4IO:
++ i2c-10/i2c-6 on 38 & 39
+
+ i2c_csi_dsi0 Set to "on" to enable the i2c_csi_dsi0 interface
++ The I2C bus and GPIOs are platform specific:
++ B rev 1 & 2, B+, CM, Zero, Zero W, 2B, CM2,
++ CM3, CM4S, 3B, 3B+, Zero 2W, 4B, 400, CM4,
++ CM5 on CM4IO:
++ i2c-0 on 0 & 1
++ 5, 500, CM5 on CM5IO:
++ i2c-10/i2c-6 on 38 & 39
+
+- i2c_csi_dsi1 Set to "on" to enable the i2c_csi_dsi1 interface
++ i2c_csi_dsi1 A Pi 5 family-specific alias for i2c_csi_dsi.
+
+ i2c_vc Set to "on" to enable the i2c interface
+ usually reserved for the VideoCore processor
+@@ -546,7 +567,12 @@ Params: addr I2C bus
+ Amplifier for this channel. (Default 1 sets the
+ full scale of the channel to 4.096 Volts)
+ i2c0 Choose the I2C0 bus on GPIOs 0&1
+- i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c_csi_dsi Choose the I2C bus connected to the main
++ camera/display connector.
++ See "dtparam -h i2c_csi_dsi" for details.
++ i2c_csi_dsi0 Choose the I2C bus connected to the second
++ camera/display connector, if present.
++ See "dtparam -h i2c_csi_dsi0" for details.
+ i2c3 Choose the I2C3 bus (configure with the i2c3
+ overlay - BCM2711 only)
+ i2c4 Choose the I2C4 bus (configure with the i2c4
+@@ -2086,7 +2112,13 @@ Params: addr Sets the
+
+ i2c0 Choose the I2C0 bus on GPIOs 0&1
+
+- i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c_csi_dsi Choose the I2C bus connected to the main
++ camera/display connector.
++ See "dtparam -h i2c_csi_dsi" for details.
++
++ i2c_csi_dsi0 Choose the I2C bus connected to the second
++ camera/display connector, if present.
++ See "dtparam -h i2c_csi_dsi0" for details.
+
+ i2c3 Choose the I2C3 bus (configure with the i2c3
+ overlay - BCM2711 only)
+@@ -2158,7 +2190,13 @@ Params: pca9542 Select t
+
+ i2c0 Choose the I2C0 bus on GPIOs 0&1
+
+- i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c_csi_dsi Choose the I2C bus connected to the main
++ camera/display connector.
++ See "dtparam -h i2c_csi_dsi" for details.
++
++ i2c_csi_dsi0 Choose the I2C bus connected to the second
++ camera/display connector, if present.
++ See "dtparam -h i2c_csi_dsi0" for details.
+
+ i2c3 Choose the I2C3 bus (configure with the i2c3
+ overlay - BCM2711 only)
+@@ -2186,7 +2224,12 @@ Info: Adds support for an NXP PCA9685A
+ Load: dtoverlay=i2c-pwm-pca9685a,<param>=<val>
+ Params: addr I2C address of PCA9685A (default 0x40)
+ i2c0 Choose the I2C0 bus on GPIOs 0&1
+- i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c_csi_dsi Choose the I2C bus connected to the main
++ camera/display connector.
++ See "dtparam -h i2c_csi_dsi" for details.
++ i2c_csi_dsi0 Choose the I2C bus connected to the second
++ camera/display connector, if present.
++ See "dtparam -h i2c_csi_dsi0" for details.
+ i2c3 Choose the I2C3 bus (configure with the i2c3
+ overlay - BCM2711 only)
+ i2c4 Choose the I2C3 bus (configure with the i2c3
+@@ -2251,7 +2294,13 @@ Params: abx80x Select o
+
+ i2c0 Choose the I2C0 bus on GPIOs 0&1
+
+- i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c_csi_dsi Choose the I2C bus connected to the main
++ camera/display connector.
++ See "dtparam -h i2c_csi_dsi" for details.
++
++ i2c_csi_dsi0 Choose the I2C bus connected to the second
++ camera/display connector, if present.
++ See "dtparam -h i2c_csi_dsi0" for details.
+
+ i2c3 Choose the I2C3 bus (configure with the i2c3
+ overlay - BCM2711 only)
+@@ -2517,7 +2566,12 @@ Params: addr Set the
+
+ i2c0 Choose the I2C0 bus on GPIOs 0&1
+
+- i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c_csi_dsi Choose the I2C bus connected to the main
++ camera/display connector.
++ See "dtparam -h i2c_csi_dsi" for details.
++ i2c_csi_dsi0 Choose the I2C bus connected to the second
++ camera/display connector, if present.
++ See "dtparam -h i2c_csi_dsi0" for details.
+
+ i2c3 Choose the I2C3 bus (configure with the i2c3
+ overlay - BCM2711 only)
+@@ -3144,7 +3198,12 @@ Params: gpiopin Gpio pin
+ mcp23008 Configure an MCP23008 instead.
+ noints Disable the interrupt GPIO line.
+ i2c0 Choose the I2C0 bus on GPIOs 0&1
+- i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c_csi_dsi Choose the I2C bus connected to the main
++ camera/display connector.
++ See "dtparam -h i2c_csi_dsi" for details.
++ i2c_csi_dsi0 Choose the I2C bus connected to the second
++ camera/display connector, if present.
++ See "dtparam -h i2c_csi_dsi0" for details.
+ i2c3 Choose the I2C3 bus (configure with the i2c3
+ overlay - BCM2711 only)
+ i2c4 Choose the I2C4 bus (configure with the i2c4
+@@ -3604,7 +3663,12 @@ Params: addr I2C addr
+ pca9654 Select the Onnn PCA9654 (8 bit)
+ xra1202 Select the Exar XRA1202 (8 bit)
+ i2c0 Choose the I2C0 bus on GPIOs 0&1
+- i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c_csi_dsi Choose the I2C bus connected to the main
++ camera/display connector.
++ See "dtparam -h i2c_csi_dsi" for details.
++ i2c_csi_dsi0 Choose the I2C bus connected to the second
++ camera/display connector, if present.
++ See "dtparam -h i2c_csi_dsi0" for details.
+ i2c3 Choose the I2C3 bus (configure with the i2c3
+ overlay - BCM2711 only)
+ i2c4 Choose the I2C3 bus (configure with the i2c3
+@@ -3626,7 +3690,12 @@ Params: addr I2C addr
+ pcf8575 Select the NXP PCF8575 (16 bit)
+ pca8574 Select the NXP PCA8574 (8 bit)
+ i2c0 Choose the I2C0 bus on GPIOs 0&1
+- i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c_csi_dsi Choose the I2C bus connected to the main
++ camera/display connector.
++ See "dtparam -h i2c_csi_dsi" for details.
++ i2c_csi_dsi0 Choose the I2C bus connected to the second
++ camera/display connector, if present.
++ See "dtparam -h i2c_csi_dsi0" for details.
+ i2c3 Choose the I2C3 bus (configure with the i2c3
+ overlay - BCM2711 only)
+ i2c4 Choose the I2C3 bus (configure with the i2c3
+@@ -4296,7 +4365,12 @@ Params: int_pin GPIO use
+ addr Address (default 0x48)
+ xtal On-board crystal frequency (default 14745600)
+ i2c0 Choose the I2C0 bus on GPIOs 0&1
+- i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c_csi_dsi Choose the I2C bus connected to the main
++ camera/display connector.
++ See "dtparam -h i2c_csi_dsi" for details.
++ i2c_csi_dsi0 Choose the I2C bus connected to the second
++ camera/display connector, if present.
++ See "dtparam -h i2c_csi_dsi0" for details.
+ i2c3 Choose the I2C3 bus (configure with the i2c3
+ overlay - BCM2711 only)
+ i2c4 Choose the I2C4 bus (configure with the i2c4
+@@ -4325,7 +4399,12 @@ Params: int_pin GPIO use
+ addr Address (default 0x48)
+ xtal On-board crystal frequency (default 14745600)
+ i2c0 Choose the I2C0 bus on GPIOs 0&1
+- i2c_csi_dsi Choose the I2C0 bus on GPIOs 44&45
++ i2c_csi_dsi Choose the I2C bus connected to the main
++ camera/display connector.
++ See "dtparam -h i2c_csi_dsi" for details.
++ i2c_csi_dsi0 Choose the I2C bus connected to the second
++ camera/display connector, if present.
++ See "dtparam -h i2c_csi_dsi0" for details.
+ i2c3 Choose the I2C3 bus (configure with the i2c3
+ overlay - BCM2711 only)
+ i2c4 Choose the I2C4 bus (configure with the i2c4
+--- a/arch/arm/boot/dts/overlays/ads1115-overlay.dts
++++ b/arch/arm/boot/dts/overlays/ads1115-overlay.dts
+@@ -123,6 +123,8 @@
+ i2c0 = <&frag100>, "target:0=",<&i2c0>;
+ i2c_csi_dsi = <&frag100>, "target:0=",<&i2c_csi_dsi>,
+ <0>,"+101+102";
++ i2c_csi_dsi0 = <&frag100>, "target:0=",<&i2c_csi_dsi0>,
++ <0>,"+101+102";
+ i2c3 = <&frag100>, "target?=0",
+ <&frag100>, "target-path=i2c3";
+ i2c4 = <&frag100>, "target?=0",
+--- a/arch/arm/boot/dts/overlays/i2c-fan-overlay.dts
++++ b/arch/arm/boot/dts/overlays/i2c-fan-overlay.dts
+@@ -85,6 +85,8 @@
+ i2c0 = <&frag100>,"target:0=",<&i2c0>;
+ i2c_csi_dsi = <&frag100>,"target:0=",<&i2c_csi_dsi>,
+ <0>,"+101+102";
++ i2c_csi_dsi0 = <&frag100>, "target:0=",<&i2c_csi_dsi0>,
++ <0>,"+101+102";
+ i2c3 = <&frag100>, "target?=0",
+ <&frag100>, "target-path=i2c3";
+ i2c4 = <&frag100>, "target?=0",
+--- a/arch/arm/boot/dts/overlays/i2c-mux-overlay.dts
++++ b/arch/arm/boot/dts/overlays/i2c-mux-overlay.dts
+@@ -167,6 +167,8 @@
+ <0>,"+101+102";
+ i2c_csi_dsi = <&frag100>, "target:0=",<&i2c_csi_dsi>,
+ <0>,"+101+102";
++ i2c_csi_dsi0 = <&frag100>, "target:0=",<&i2c_csi_dsi0>,
++ <0>,"+101+102";
+ i2c3 = <&frag100>, "target?=0",
+ <&frag100>, "target-path=i2c3";
+ i2c4 = <&frag100>, "target?=0",
+--- a/arch/arm/boot/dts/overlays/i2c-pwm-pca9685a-overlay.dts
++++ b/arch/arm/boot/dts/overlays/i2c-pwm-pca9685a-overlay.dts
+@@ -49,6 +49,8 @@
+ <0>,"+101+102";
+ i2c_csi_dsi = <&frag100>, "target:0=",<&i2c_csi_dsi>,
+ <0>,"+101+102";
++ i2c_csi_dsi0 = <&frag100>, "target:0=",<&i2c_csi_dsi0>,
++ <0>,"+101+102";
+ i2c3 = <&frag100>, "target?=0",
+ <&frag100>, "target-path=i2c3";
+ i2c4 = <&frag100>, "target?=0",
+--- a/arch/arm/boot/dts/overlays/i2c-rtc-overlay.dts
++++ b/arch/arm/boot/dts/overlays/i2c-rtc-overlay.dts
+@@ -30,6 +30,8 @@
+ i2c0 = <&frag100>, "target:0=",<&i2c0>;
+ i2c_csi_dsi = <&frag100>, "target:0=",<&i2c_csi_dsi>,
+ <0>,"+101+102";
++ i2c_csi_dsi0 = <&frag100>, "target:0=",<&i2c_csi_dsi0>,
++ <0>,"+101+102";
+ i2c3 = <&frag100>, "target?=0",
+ <&frag100>, "target-path=i2c3";
+ i2c4 = <&frag100>, "target?=0",
+--- a/arch/arm/boot/dts/overlays/i2c-sensor-overlay.dts
++++ b/arch/arm/boot/dts/overlays/i2c-sensor-overlay.dts
+@@ -30,6 +30,8 @@
+ i2c0 = <&frag100>, "target:0=",<&i2c0>;
+ i2c_csi_dsi = <&frag100>, "target:0=",<&i2c_csi_dsi>,
+ <0>,"+101+102";
++ i2c_csi_dsi0 = <&frag100>, "target:0=",<&i2c_csi_dsi0>,
++ <0>,"+101+102";
+ i2c3 = <&frag100>, "target?=0",
+ <&frag100>, "target-path=i2c3";
+ i2c4 = <&frag100>, "target?=0",
+--- a/arch/arm/boot/dts/overlays/mcp23017-overlay.dts
++++ b/arch/arm/boot/dts/overlays/mcp23017-overlay.dts
+@@ -90,6 +90,8 @@
+ i2c0 = <&frag100>, "target:0=",<&i2c0>;
+ i2c_csi_dsi = <&frag100>, "target:0=",<&i2c_csi_dsi>,
+ <0>,"+101+102";
++ i2c_csi_dsi0 = <&frag100>, "target:0=",<&i2c_csi_dsi0>,
++ <0>,"+101+102";
+ i2c3 = <&frag100>, "target?=0",
+ <&frag100>, "target-path=i2c3";
+ i2c4 = <&frag100>, "target?=0",
+--- a/arch/arm/boot/dts/overlays/pca953x-overlay.dts
++++ b/arch/arm/boot/dts/overlays/pca953x-overlay.dts
+@@ -254,6 +254,8 @@
+ <0>,"+100+101";
+ i2c_csi_dsi = <&frag0>, "target:0=",<&i2c_csi_dsi>,
+ <0>,"+100+101";
++ i2c_csi_dsi0 = <&frag0>, "target:0=",<&i2c_csi_dsi0>,
++ <0>,"+100+101";
+ i2c3 = <&frag0>, "target?=0",
+ <&frag0>, "target-path=i2c3";
+ i2c4 = <&frag0>, "target?=0",
+--- a/arch/arm/boot/dts/overlays/pcf857x-overlay.dts
++++ b/arch/arm/boot/dts/overlays/pcf857x-overlay.dts
+@@ -46,6 +46,8 @@
+ <0>,"+100+101";
+ i2c_csi_dsi = <&frag0>, "target:0=",<&i2c_csi_dsi>,
+ <0>,"+100+101";
++ i2c_csi_dsi0 = <&frag0>, "target:0=",<&i2c_csi_dsi0>,
++ <0>,"+100+101";
+ i2c3 = <&frag0>, "target?=0",
+ <&frag0>, "target-path=i2c3";
+ i2c4 = <&frag0>, "target?=0",
+--- a/arch/arm/boot/dts/overlays/sc16is750-i2c-overlay.dts
++++ b/arch/arm/boot/dts/overlays/sc16is750-i2c-overlay.dts
+@@ -71,6 +71,8 @@
+ <0>,"+100+101";
+ i2c_csi_dsi = <&frag0>, "target:0=",<&i2c_csi_dsi>,
+ <0>,"+100+101";
++ i2c_csi_dsi0 = <&frag0>, "target:0=",<&i2c_csi_dsi0>,
++ <0>,"+100+101";
+ i2c3 = <&frag0>, "target?=0",
+ <&frag0>, "target-path=i2c3";
+ i2c4 = <&frag0>, "target?=0",
+--- a/arch/arm/boot/dts/overlays/sc16is752-i2c-overlay.dts
++++ b/arch/arm/boot/dts/overlays/sc16is752-i2c-overlay.dts
+@@ -71,6 +71,8 @@
+ <0>,"+100+101";
+ i2c_csi_dsi = <&frag0>, "target:0=",<&i2c_csi_dsi>,
+ <0>,"+100+101";
++ i2c_csi_dsi0 = <&frag0>, "target:0=",<&i2c_csi_dsi0>,
++ <0>,"+100+101";
+ i2c3 = <&frag0>, "target?=0",
+ <&frag0>, "target-path=i2c3";
+ i2c4 = <&frag0>, "target?=0",
--- /dev/null
+From 147ddfdaf626fe5484596235bba8bdc6dcfde501 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Fri, 20 Dec 2024 15:08:52 +0000
+Subject: [PATCH] dts: Add noanthogs parameter to CM4 and CM5
+
+By default, the antenna selection on CM4 and CM5 is fixed at boot time,
+with the dtparams ant1, ant2 and noant selecting which should be
+enabled. Add a new dtparam - noanthogs - which leaves the GPIOs free
+to be controlled at runtime by the OS.
+
+N.B. Using this parameter without suitable OS support will leave both
+antennae disabled, resulting in attenuated WiFi and Bluetooth signals.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4.dts | 2 ++
+ arch/arm/boot/dts/overlays/README | 6 ++++++
+ arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi | 2 ++
+ 3 files changed, 10 insertions(+)
+
+--- a/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4.dts
++++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4.dts
+@@ -493,6 +493,8 @@ i2c_csi_dsi0: &i2c0 {
+ <&ant1>, "output-low?=on",
+ <&ant2>, "output-high?=off",
+ <&ant2>, "output-low?=on";
++ noanthogs = <&ant1>,"status=disabled",
++ <&ant2>, "status=disabled";
+
+ pcie_tperst_clk_ms = <&pcie0>,"brcm,tperst-clk-ms:0";
+ };
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -153,6 +153,12 @@ Params:
+
+ noant Disable both antennas. CM4/5 only.
+
++ noanthogs Disable the GPIO hogs on the antenna controls
++ so they can be controlled at runtime. Note that
++ using this parameter without suitable OS
++ support will result in attenuated WiFi and
++ Bluetooth signals. CM4/5 only.
++
+ audio Set to "on" to enable the onboard ALSA audio
+ interface (default "off")
+
+--- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi
++++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi
+@@ -750,5 +750,7 @@ spi10_cs_pins: &spi10_cs_gpio1 {};
+ <&ant1>, "output-low?=on",
+ <&ant2>, "output-high?=off",
+ <&ant2>, "output-low?=on";
++ noanthogs = <&ant1>,"status=disabled",
++ <&ant2>, "status=disabled";
+ };
+ };