From 613dd79d5eabe53f07a7b74cc062d91cfc550403 Mon Sep 17 00:00:00 2001 From: John Audia Date: Thu, 12 Dec 2024 15:33:15 -0500 Subject: [PATCH] bcm27xx: patches: cherry-pick for RP1 kmods Cherry-pick patches to support building RP1 modules. Signed-off-by: John Audia Link: https://github.com/openwrt/openwrt/pull/17233 Signed-off-by: Robert Marko --- ...1351-mailbox-Add-RP1-mailbox-support.patch | 252 +++ ...rmware-Add-an-RP1-firmware-interface.patch | 421 ++++ .../950-1353-misc-Add-RP1-PIO-driver.patch | 1385 +++++++++++++ ...0-1354-fixup-musc-add-RP1-PIO-driver.patch | 29 + ...0-1355-misc-rp1-pio-Add-an-in-kernel.patch | 1797 +++++++++++++++++ .../950-1356-pwm-Add-pwm-pio-rp1-driver.patch | 299 +++ ...io-Fix-copy-paste-error-in-pio_rp1.h.patch | 25 + .../950-1357-rpi-pio-add-missing.patch | 42 + ...950-1358-misc-rpi-pio-back-port-some.patch | 39 + .../950-1359-misc-rp1-pio-Add-FIFO.patch | 215 ++ ...o-Fix-parameter-checks-wihout-client.patch | 25 + ...misc-rp1-pio-Add-compat_ioctl-method.patch | 91 + ...port-for-Raspberry-Pi-RP1-ethernet-c.patch | 43 + ...t-PLL_SEC_RST-in-rp1_pll_divider_off.patch | 27 + ...ise-the-use-of-the-CLK_IS_CRITICAL-f.patch | 124 ++ ...-Fixup-RP1-ethernet-DT-configuration.patch | 50 + .../950-1427-clk-rp1-Add-RP1_CLK_DMA.patch | 44 + ...1-clk-Remove-CLK_IGNORE_UNUSED-flags.patch | 59 + ...e-clk_sys-for-ethernet-hclk-and-pclk.patch | 45 + ...Link-RP1-DMA-to-the-associated-clock.patch | 24 + ...Add-interlaced-modes-and-PIO-program.patch | 762 +++++++ ...pwm-Improve-PWM_PIO_RP1-dependencies.patch | 22 + ...pwm-Improve-PWM_PIO_RP1-dependencies.patch | 20 + ...i-Fix-optional-dependency-on-RP1_PIO.patch | 99 + ...p1-pio-Support-larger-data-transfers.patch | 127 ++ ...0-1471-fixup-misc-Add-RP1-PIO-driver.patch | 24 + ...-rp1-pio-More-logical-probe-sequence.patch | 81 + ...o-Convert-floats-to-24.8-fixed-point.patch | 92 + ...5-misc-rp1-pio-Minor-cosmetic-tweaks.patch | 42 + ...sc-rp1-pio-Add-in-kernel-DMA-support.patch | 508 +++++ ...-1477-misc-Add-ws2812-pio-rp1-driver.patch | 568 ++++++ ...1478-overlays-Add-ws2812-pio-overlay.patch | 107 + 32 files changed, 7488 insertions(+) create mode 100644 target/linux/bcm27xx/patches-6.6/950-1351-mailbox-Add-RP1-mailbox-support.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1352-firmware-Add-an-RP1-firmware-interface.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1353-misc-Add-RP1-PIO-driver.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1354-fixup-musc-add-RP1-PIO-driver.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1355-misc-rp1-pio-Add-an-in-kernel.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1356-pwm-Add-pwm-pio-rp1-driver.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1357-misc-rp1-pio-Fix-copy-paste-error-in-pio_rp1.h.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1357-rpi-pio-add-missing.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1358-misc-rpi-pio-back-port-some.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1359-misc-rp1-pio-Add-FIFO.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1360-misc-rp1-pio-Fix-parameter-checks-wihout-client.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1394-misc-rp1-pio-Add-compat_ioctl-method.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1423-net-macb-Add-support-for-Raspberry-Pi-RP1-ethernet-c.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1424-rp1-clk-Only-set-PLL_SEC_RST-in-rp1_pll_divider_off.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1425-rp1-clk-Rationalise-the-use-of-the-CLK_IS_CRITICAL-f.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1426-dt-arm64-Fixup-RP1-ethernet-DT-configuration.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1427-clk-rp1-Add-RP1_CLK_DMA.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1428-rp1-clk-Remove-CLK_IGNORE_UNUSED-flags.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1429-dt-rp1-Use-clk_sys-for-ethernet-hclk-and-pclk.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1430-dt-rp1-Link-RP1-DMA-to-the-associated-clock.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1456-drm-rp1-rp1-dpi-Add-interlaced-modes-and-PIO-program.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1457-pwm-Improve-PWM_PIO_RP1-dependencies.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1458-Revert-pwm-Improve-PWM_PIO_RP1-dependencies.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1463-drm-rp1-rp1-dpi-Fix-optional-dependency-on-RP1_PIO.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1468-misc-rp1-pio-Support-larger-data-transfers.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1471-fixup-misc-Add-RP1-PIO-driver.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1473-misc-rp1-pio-More-logical-probe-sequence.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1474-misc-rp1-pio-Convert-floats-to-24.8-fixed-point.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1475-misc-rp1-pio-Minor-cosmetic-tweaks.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1476-misc-rp1-pio-Add-in-kernel-DMA-support.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1477-misc-Add-ws2812-pio-rp1-driver.patch create mode 100644 target/linux/bcm27xx/patches-6.6/950-1478-overlays-Add-ws2812-pio-overlay.patch diff --git a/target/linux/bcm27xx/patches-6.6/950-1351-mailbox-Add-RP1-mailbox-support.patch b/target/linux/bcm27xx/patches-6.6/950-1351-mailbox-Add-RP1-mailbox-support.patch new file mode 100644 index 0000000000..23bdb6a8fb --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1351-mailbox-Add-RP1-mailbox-support.patch @@ -0,0 +1,252 @@ +From 0d58d8cfb6f989f290d983552fcaa116e582e84a Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ++ * 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 "); ++MODULE_DESCRIPTION("RP1 mailbox IPC driver"); ++MODULE_LICENSE("GPL v2"); diff --git a/target/linux/bcm27xx/patches-6.6/950-1352-firmware-Add-an-RP1-firmware-interface.patch b/target/linux/bcm27xx/patches-6.6/950-1352-firmware-Add-an-RP1-firmware-interface.patch new file mode 100644 index 0000000000..3a9eb03c94 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1352-firmware-Add-an-RP1-firmware-interface.patch @@ -0,0 +1,421 @@ +From 67daeadcaa7cee1f4b9df7aa108d199e73f35451 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 ++ * Copyright (C) 2015 Broadcom ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 "); ++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 ++#include ++ ++#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__ */ diff --git a/target/linux/bcm27xx/patches-6.6/950-1353-misc-Add-RP1-PIO-driver.patch b/target/linux/bcm27xx/patches-6.6/950-1353-misc-Add-RP1-PIO-driver.patch new file mode 100644 index 0000000000..521129b919 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1353-misc-Add-RP1-PIO-driver.patch @@ -0,0 +1,1385 @@ +From 55fd5c9018e1520d45f08cf08630a493ec7dedea Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 ++ ++#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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 ++ ++#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 diff --git a/target/linux/bcm27xx/patches-6.6/950-1354-fixup-musc-add-RP1-PIO-driver.patch b/target/linux/bcm27xx/patches-6.6/950-1354-fixup-musc-add-RP1-PIO-driver.patch new file mode 100644 index 0000000000..cd197f7a7b --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1354-fixup-musc-add-RP1-PIO-driver.patch @@ -0,0 +1,29 @@ +From 1b5acd42281ad102b79f4e1794f0a0cccdafda05 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +Date: Sat, 16 Nov 2024 16:53:31 +0000 +Subject: [PATCH] fixup! misc: Add RP1 PIO driver + +Signed-off-by: Phil Elwell +--- + 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) + diff --git a/target/linux/bcm27xx/patches-6.6/950-1355-misc-rp1-pio-Add-an-in-kernel.patch b/target/linux/bcm27xx/patches-6.6/950-1355-misc-rp1-pio-Add-an-in-kernel.patch new file mode 100644 index 0000000000..6accaf000e --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1355-misc-rp1-pio-Add-an-in-kernel.patch @@ -0,0 +1,1797 @@ +From 2819a61eb000c207589c97eef9d69a237c6cfdf3 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 + #include +@@ -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 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 ` ++ * ++ * \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 ` ++ * ++ * \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-- ` ++ * ++ * \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 ` ++ * ++ * \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-- ` ++ * ++ * \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 ` ++ * ++ * \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 ` ++ * ++ * \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 ` ++ * ++ * \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 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 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 IRQ ` ++ * ++ * \param polarity true for `WAIT 1`, false for `WAIT 0` ++ * \param relative true for a `WAIT IRQ REL`, false for regular `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_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 , ` ++ * ++ * \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 , ` ++ * ++ * \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 , ` ++ * ++ * \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 , ` ++ * ++ * \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 , ` ++ * ++ * \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 , ~` ++ * ++ * \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 , ::` ++ * ++ * \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 ` ++ * ++ * \param relative true for a `IRQ SET REL`, false for regular `IRQ SET ` ++ * \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 ` ++ * ++ * \param relative true for a `IRQ WAIT REL`, false for regular `IRQ WAIT ` ++ * \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 ` ++ * ++ * \param relative true for a `IRQ CLEAR REL`, false for regular `IRQ CLEAR ` ++ * \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 , ` ++ * ++ * \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 ++ ++#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 ++ ++#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 diff --git a/target/linux/bcm27xx/patches-6.6/950-1356-pwm-Add-pwm-pio-rp1-driver.patch b/target/linux/bcm27xx/patches-6.6/950-1356-pwm-Add-pwm-pio-rp1-driver.patch new file mode 100644 index 0000000000..3f787de9d9 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1356-pwm-Add-pwm-pio-rp1-driver.patch @@ -0,0 +1,299 @@ +From 4d20aadc3188ecfb62b309a9924ee9696a94fc33 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 ++ * and on the pwm-gpio driver by: ++ * Vincent Whitchurch ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++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"); diff --git a/target/linux/bcm27xx/patches-6.6/950-1357-misc-rp1-pio-Fix-copy-paste-error-in-pio_rp1.h.patch b/target/linux/bcm27xx/patches-6.6/950-1357-misc-rp1-pio-Fix-copy-paste-error-in-pio_rp1.h.patch new file mode 100644 index 0000000000..0746e3c32b --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1357-misc-rp1-pio-Fix-copy-paste-error-in-pio_rp1.h.patch @@ -0,0 +1,25 @@ +From 99a0201bb0abc946dc431214b638b2cc6b01dda5 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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) diff --git a/target/linux/bcm27xx/patches-6.6/950-1357-rpi-pio-add-missing.patch b/target/linux/bcm27xx/patches-6.6/950-1357-rpi-pio-add-missing.patch new file mode 100644 index 0000000000..c871fa681f --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1357-rpi-pio-add-missing.patch @@ -0,0 +1,42 @@ +From df8a2f6dc114b2c5c7685a069f717f2b06186b74 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 }; + diff --git a/target/linux/bcm27xx/patches-6.6/950-1358-misc-rpi-pio-back-port-some.patch b/target/linux/bcm27xx/patches-6.6/950-1358-misc-rpi-pio-back-port-some.patch new file mode 100644 index 0000000000..92bde521d2 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1358-misc-rpi-pio-back-port-some.patch @@ -0,0 +1,39 @@ +From d1f0c94e974a5f26d210b1d13a6ef9543bee4984 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 + #include + #include ++#include ++#include ++#include + #include + #include + #include +--- 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); diff --git a/target/linux/bcm27xx/patches-6.6/950-1359-misc-rp1-pio-Add-FIFO.patch b/target/linux/bcm27xx/patches-6.6/950-1359-misc-rp1-pio-Add-FIFO.patch new file mode 100644 index 0000000000..fff033f5a6 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1359-misc-rp1-pio-Add-FIFO.patch @@ -0,0 +1,215 @@ +From dd2394360860d15146c96635796a75b05bb32b61 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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) diff --git a/target/linux/bcm27xx/patches-6.6/950-1360-misc-rp1-pio-Fix-parameter-checks-wihout-client.patch b/target/linux/bcm27xx/patches-6.6/950-1360-misc-rp1-pio-Fix-parameter-checks-wihout-client.patch new file mode 100644 index 0000000000..e68bd175d8 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1360-misc-rp1-pio-Fix-parameter-checks-wihout-client.patch @@ -0,0 +1,25 @@ +From 3687701e8d252864f440f91f1aedf8ffd58d6ee6 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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; }) + diff --git a/target/linux/bcm27xx/patches-6.6/950-1394-misc-rp1-pio-Add-compat_ioctl-method.patch b/target/linux/bcm27xx/patches-6.6/950-1394-misc-rp1-pio-Add-compat_ioctl-method.patch new file mode 100644 index 0000000000..bffa2418a1 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1394-misc-rp1-pio-Add-compat_ioctl-method.patch @@ -0,0 +1,91 @@ +From b4472d09b1ffdafd8132803ffbec62596e559fd8 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +Date: Mon, 18 Nov 2024 09:10:52 +0000 +Subject: [PATCH 1394/1482] misc: rp1-pio: Add compat_ioctl method + +Provide a compat_ioctl method, to support running a 64-bit kernel with +a 32-bit userland. + +Signed-off-by: Phil Elwell +--- + drivers/misc/rp1-pio.c | 64 ++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 64 insertions(+) + +--- a/drivers/misc/rp1-pio.c ++++ b/drivers/misc/rp1-pio.c +@@ -1023,11 +1023,75 @@ static long rp1_pio_ioctl(struct file *f + return ret; + } + ++#ifdef CONFIG_COMPAT ++ ++struct rp1_pio_sm_xfer_data_args_compat { ++ uint16_t sm; ++ uint16_t dir; ++ uint16_t data_bytes; ++ compat_uptr_t data; ++}; ++ ++struct rp1_access_hw_args_compat { ++ uint32_t addr; ++ uint32_t len; ++ compat_uptr_t data; ++}; ++ ++#define PIO_IOC_SM_XFER_DATA_COMPAT _IOW(PIO_IOC_MAGIC, 1, struct rp1_pio_sm_xfer_data_args_compat) ++#define PIO_IOC_READ_HW_COMPAT _IOW(PIO_IOC_MAGIC, 8, struct rp1_access_hw_args_compat) ++#define PIO_IOC_WRITE_HW_COMPAT _IOW(PIO_IOC_MAGIC, 9, struct rp1_access_hw_args_compat) ++ ++static long rp1_pio_compat_ioctl(struct file *filp, unsigned int ioctl_num, ++ unsigned long ioctl_param) ++{ ++ struct rp1_pio_client *client = filp->private_data; ++ ++ switch (ioctl_num) { ++ case PIO_IOC_SM_XFER_DATA_COMPAT: ++ { ++ struct rp1_pio_sm_xfer_data_args_compat compat_param; ++ struct rp1_pio_sm_xfer_data_args param; ++ ++ if (copy_from_user(&compat_param, compat_ptr(ioctl_param), sizeof(compat_param))) ++ return -EFAULT; ++ param.sm = compat_param.sm; ++ param.dir = compat_param.dir; ++ param.data_bytes = compat_param.data_bytes; ++ param.data = compat_ptr(compat_param.data); ++ return rp1_pio_sm_xfer_data(client, ¶m); ++ } ++ ++ case PIO_IOC_READ_HW_COMPAT: ++ case PIO_IOC_WRITE_HW_COMPAT: ++ { ++ struct rp1_access_hw_args_compat compat_param; ++ struct rp1_access_hw_args param; ++ ++ if (copy_from_user(&compat_param, compat_ptr(ioctl_param), sizeof(compat_param))) ++ return -EFAULT; ++ param.addr = compat_param.addr; ++ param.len = compat_param.len; ++ param.data = compat_ptr(compat_param.data); ++ if (ioctl_num == PIO_IOC_READ_HW_COMPAT) ++ return rp1_pio_read_hw(client, ¶m); ++ else ++ return rp1_pio_write_hw(client, ¶m); ++ } ++ default: ++ return rp1_pio_ioctl(filp, ioctl_num, ioctl_param); ++ } ++} ++#else ++#define rp1_pio_compat_ioctl NULL ++#endif ++ + const struct file_operations rp1_pio_fops = { + .owner = THIS_MODULE, + .open = rp1_pio_open, + .release = rp1_pio_release, + .unlocked_ioctl = rp1_pio_ioctl, ++ .compat_ioctl = rp1_pio_compat_ioctl, + }; + + static int rp1_pio_probe(struct platform_device *pdev) diff --git a/target/linux/bcm27xx/patches-6.6/950-1423-net-macb-Add-support-for-Raspberry-Pi-RP1-ethernet-c.patch b/target/linux/bcm27xx/patches-6.6/950-1423-net-macb-Add-support-for-Raspberry-Pi-RP1-ethernet-c.patch new file mode 100644 index 0000000000..3941d69edf --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1423-net-macb-Add-support-for-Raspberry-Pi-RP1-ethernet-c.patch @@ -0,0 +1,43 @@ +From f9f0024bd9bf04a58b64bae356be4c04022d23bc Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +Date: Mon, 11 Nov 2024 16:40:07 +0000 +Subject: [PATCH 1423/1482] 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. +Add the relevant configuration. + +Signed-off-by: Dave Stevenson +--- + drivers/net/ethernet/cadence/macb_main.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +--- a/drivers/net/ethernet/cadence/macb_main.c ++++ b/drivers/net/ethernet/cadence/macb_main.c +@@ -5023,6 +5023,17 @@ static const struct macb_config versal_c + .usrio = &macb_default_usrio, + }; + ++static const struct macb_config raspberrypi_rp1_config = { ++ .caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_CLK_HW_CHG | ++ MACB_CAPS_JUMBO | ++ MACB_CAPS_GEM_HAS_PTP, ++ .dma_burst_length = 16, ++ .clk_init = macb_clk_init, ++ .init = macb_init, ++ .usrio = &macb_default_usrio, ++ .jumbo_max_len = 10240, ++}; ++ + static const struct of_device_id macb_dt_ids[] = { + { .compatible = "cdns,at91sam9260-macb", .data = &at91sam9260_config }, + { .compatible = "cdns,macb" }, +@@ -5043,6 +5054,7 @@ static const struct of_device_id macb_dt + { .compatible = "microchip,mpfs-macb", .data = &mpfs_config }, + { .compatible = "microchip,sama7g5-gem", .data = &sama7g5_gem_config }, + { .compatible = "microchip,sama7g5-emac", .data = &sama7g5_emac_config }, ++ { .compatible = "raspberrypi,rp1-gem", .data = &raspberrypi_rp1_config }, + { .compatible = "xlnx,zynqmp-gem", .data = &zynqmp_config}, + { .compatible = "xlnx,zynq-gem", .data = &zynq_config }, + { .compatible = "xlnx,versal-gem", .data = &versal_config}, diff --git a/target/linux/bcm27xx/patches-6.6/950-1424-rp1-clk-Only-set-PLL_SEC_RST-in-rp1_pll_divider_off.patch b/target/linux/bcm27xx/patches-6.6/950-1424-rp1-clk-Only-set-PLL_SEC_RST-in-rp1_pll_divider_off.patch new file mode 100644 index 0000000000..d3f216d31d --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1424-rp1-clk-Only-set-PLL_SEC_RST-in-rp1_pll_divider_off.patch @@ -0,0 +1,27 @@ +From 33c225f622d596034a9261316666089a92aa6834 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 + +Rather than clearing all the bits in rp1_pll_divider_off +and setting PLL_SEC_RST, retain the status of all the other +bits. + +Signed-off-by: Dave Stevenson +--- + drivers/clk/clk-rp1.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +--- a/drivers/clk/clk-rp1.c ++++ b/drivers/clk/clk-rp1.c +@@ -927,7 +927,8 @@ static void rp1_pll_divider_off(struct c + const struct rp1_pll_data *data = divider->data; + + spin_lock(&clockman->regs_lock); +- clockman_write(clockman, data->ctrl_reg, PLL_SEC_RST); ++ clockman_write(clockman, data->ctrl_reg, ++ clockman_read(clockman, data->ctrl_reg) | PLL_SEC_RST); + spin_unlock(&clockman->regs_lock); + } + diff --git a/target/linux/bcm27xx/patches-6.6/950-1425-rp1-clk-Rationalise-the-use-of-the-CLK_IS_CRITICAL-f.patch b/target/linux/bcm27xx/patches-6.6/950-1425-rp1-clk-Rationalise-the-use-of-the-CLK_IS_CRITICAL-f.patch new file mode 100644 index 0000000000..247ae455ac --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1425-rp1-clk-Rationalise-the-use-of-the-CLK_IS_CRITICAL-f.patch @@ -0,0 +1,124 @@ +From eb836a6a299322a8e2b9627cccd23c7a76d068ba Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +Date: Fri, 8 Nov 2024 17:36:13 +0000 +Subject: [PATCH 1425/1482] 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 +of CLK_IS_CRITICAL and CLK_IGNORE_UNUSED dotted around. + +That isn't the situation with RP1 where only the main PLLs, CLK_SYS, +and CLK_SLOW_SYS are critical, so update the configuration to match. + +Signed-off-by: Dave Stevenson +--- + drivers/clk/clk-rp1.c | 41 ++++++----------------------------------- + 1 file changed, 6 insertions(+), 35 deletions(-) + +--- a/drivers/clk/clk-rp1.c ++++ b/drivers/clk/clk-rp1.c +@@ -1504,8 +1504,6 @@ static const struct clk_ops rp1_varsrc_o + .round_rate = rp1_varsrc_round_rate, + }; + +-static bool rp1_clk_is_claimed(const char *name); +- + static struct clk_hw *rp1_register_pll_core(struct rp1_clockman *clockman, + const void *data) + { +@@ -1521,7 +1519,7 @@ static struct clk_hw *rp1_register_pll_c + init.num_parents = 1; + init.name = pll_core_data->name; + init.ops = &rp1_pll_core_ops; +- init.flags = pll_core_data->flags | CLK_IGNORE_UNUSED | CLK_IS_CRITICAL; ++ init.flags = pll_core_data->flags | CLK_IS_CRITICAL; + + pll_core = kzalloc(sizeof(*pll_core), GFP_KERNEL); + if (!pll_core) +@@ -1554,7 +1552,7 @@ static struct clk_hw *rp1_register_pll(s + init.num_parents = 1; + init.name = pll_data->name; + init.ops = &rp1_pll_ops; +- init.flags = pll_data->flags | CLK_IGNORE_UNUSED | CLK_IS_CRITICAL; ++ init.flags = pll_data->flags | CLK_IGNORE_UNUSED; + + pll = kzalloc(sizeof(*pll), GFP_KERNEL); + if (!pll) +@@ -1635,11 +1633,6 @@ static struct clk_hw *rp1_register_pll_d + divider->div.hw.init = &init; + divider->div.table = pll_sec_div_table; + +- if (!rp1_clk_is_claimed(divider_data->source_pll)) +- init.flags |= CLK_IS_CRITICAL; +- if (!rp1_clk_is_claimed(divider_data->name)) +- divider->div.flags |= CLK_IS_CRITICAL; +- + divider->clockman = clockman; + divider->data = divider_data; + +@@ -1861,6 +1854,8 @@ static const struct rp1_clk_desc clk_des + .max_freq = 200 * MHz, + .fc0_src = FC_NUM(0, 4), + .clk_src_mask = 0x3, ++ /* Always enabled in hardware */ ++ .flags = CLK_IS_CRITICAL, + ), + + [RP1_CLK_SLOW_SYS] = REGISTER_CLK( +@@ -1875,6 +1870,8 @@ static const struct rp1_clk_desc clk_des + .max_freq = 50 * MHz, + .fc0_src = FC_NUM(1, 4), + .clk_src_mask = 0x1, ++ /* Always enabled in hardware */ ++ .flags = CLK_IS_CRITICAL, + ), + + [RP1_CLK_UART] = REGISTER_CLK( +@@ -2394,24 +2391,6 @@ static const struct rp1_clk_desc clk_des + [RP1_CLK_MIPI1_DSI_BYTECLOCK] = REGISTER_VARSRC("clksrc_mipi1_dsi_byteclk"), + }; + +-static bool rp1_clk_claimed[ARRAY_SIZE(clk_desc_array)]; +- +-static bool rp1_clk_is_claimed(const char *name) +-{ +- unsigned int i; +- +- for (i = 0; i < ARRAY_SIZE(clk_desc_array); i++) { +- if (clk_desc_array[i].data) { +- const char *clk_name = *(const char **)(clk_desc_array[i].data); +- +- if (!strcmp(name, clk_name)) +- return rp1_clk_claimed[i]; +- } +- } +- +- return false; +-} +- + static int rp1_clk_probe(struct platform_device *pdev) + { + const struct rp1_clk_desc *desc; +@@ -2422,7 +2401,6 @@ static int rp1_clk_probe(struct platform + const size_t asize = ARRAY_SIZE(clk_desc_array); + u32 chip_id, platform; + unsigned int i; +- u32 clk_id; + int ret; + + clockman = devm_kzalloc(dev, struct_size(clockman, onecell.hws, asize), +@@ -2439,13 +2417,6 @@ static int rp1_clk_probe(struct platform + if (IS_ERR(clockman->regs)) + return PTR_ERR(clockman->regs); + +- memset(rp1_clk_claimed, 0, sizeof(rp1_clk_claimed)); +- for (i = 0; +- !of_property_read_u32_index(pdev->dev.of_node, "claim-clocks", +- i, &clk_id); +- i++) +- rp1_clk_claimed[clk_id] = true; +- + platform_set_drvdata(pdev, clockman); + + clockman->onecell.num = asize; diff --git a/target/linux/bcm27xx/patches-6.6/950-1426-dt-arm64-Fixup-RP1-ethernet-DT-configuration.patch b/target/linux/bcm27xx/patches-6.6/950-1426-dt-arm64-Fixup-RP1-ethernet-DT-configuration.patch new file mode 100644 index 0000000000..ad6569e99c --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1426-dt-arm64-Fixup-RP1-ethernet-DT-configuration.patch @@ -0,0 +1,50 @@ +From 0b4af929b7125abd3a262577b380c7c81ee9b1c5 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +Date: Mon, 11 Nov 2024 15:18:14 +0000 +Subject: [PATCH 1426/1482] 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, +and use assigned-clocks to configure the clock appropriately. + +Signed-off-by: Dave Stevenson +--- + arch/arm64/boot/dts/broadcom/rp1.dtsi | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +--- a/arch/arm64/boot/dts/broadcom/rp1.dtsi ++++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi +@@ -24,6 +24,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_CLK_ETH>, + <&rp1_clocks RP1_PLL_AUDIO>, + <&rp1_clocks RP1_PLL_AUDIO_SEC>, + <&rp1_clocks RP1_CLK_SYS>, +@@ -38,6 +39,7 @@ + <1536000000>, // RP1_PLL_AUDIO_CORE + <200000000>, // RP1_PLL_SYS + <125000000>, // RP1_PLL_SYS_SEC ++ <125000000>, // RP1_CLK_ETH + <61440000>, // RP1_PLL_AUDIO + <192000000>, // RP1_PLL_AUDIO_SEC + <200000000>, // RP1_CLK_SYS +@@ -968,12 +970,14 @@ + + rp1_eth: ethernet@100000 { + reg = <0xc0 0x40100000 0x0 0x4000>; +- compatible = "cdns,macb"; ++ compatible = "raspberrypi,rp1-gem", "cdns,macb"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = ; +- clocks = <&macb_pclk &macb_hclk &rp1_clocks RP1_CLK_ETH_TSU>; +- clock-names = "pclk", "hclk", "tsu_clk"; ++ clocks = <&macb_pclk &macb_hclk ++ &rp1_clocks RP1_CLK_ETH_TSU ++ &rp1_clocks RP1_CLK_ETH>; ++ clock-names = "pclk", "hclk", "tsu_clk", "tx_clk"; + phy-mode = "rgmii-id"; + cdns,aw2w-max-pipe = /bits/ 8 <8>; + cdns,ar2r-max-pipe = /bits/ 8 <8>; diff --git a/target/linux/bcm27xx/patches-6.6/950-1427-clk-rp1-Add-RP1_CLK_DMA.patch b/target/linux/bcm27xx/patches-6.6/950-1427-clk-rp1-Add-RP1_CLK_DMA.patch new file mode 100644 index 0000000000..9026cebaf9 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1427-clk-rp1-Add-RP1_CLK_DMA.patch @@ -0,0 +1,44 @@ +From d4e41ed9954fa86c4774f98d393aa401c81a68e7 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +Date: Wed, 13 Nov 2024 13:10:27 +0000 +Subject: [PATCH 1427/1482] 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 +stopped working. + +Signed-off-by: Dave Stevenson +--- + drivers/clk/clk-rp1.c | 21 +++++++++++++++++++++ + 1 file changed, 21 insertions(+) + +--- a/drivers/clk/clk-rp1.c ++++ b/drivers/clk/clk-rp1.c +@@ -1874,6 +1874,27 @@ static const struct rp1_clk_desc clk_des + .flags = CLK_IS_CRITICAL, + ), + ++ [RP1_CLK_DMA] = REGISTER_CLK( ++ .name = "clk_dma", ++ .parents = {"pll_sys_pri_ph", ++ "pll_video", ++ "xosc", ++ "clksrc_gp0", ++ "clksrc_gp1", ++ "clksrc_gp2", ++ "clksrc_gp3", ++ "clksrc_gp4", ++ "clksrc_gp5"}, ++ .num_std_parents = 0, ++ .num_aux_parents = 9, ++ .ctrl_reg = CLK_DMA_CTRL, ++ .div_int_reg = CLK_DMA_DIV_INT, ++ .sel_reg = CLK_DMA_SEL, ++ .div_int_max = DIV_INT_8BIT_MAX, ++ .max_freq = 100 * MHz, ++ .fc0_src = FC_NUM(2, 2), ++ ), ++ + [RP1_CLK_UART] = REGISTER_CLK( + .name = "clk_uart", + .parents = {"pll_sys_pri_ph", diff --git a/target/linux/bcm27xx/patches-6.6/950-1428-rp1-clk-Remove-CLK_IGNORE_UNUSED-flags.patch b/target/linux/bcm27xx/patches-6.6/950-1428-rp1-clk-Remove-CLK_IGNORE_UNUSED-flags.patch new file mode 100644 index 0000000000..8fa06045a5 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1428-rp1-clk-Remove-CLK_IGNORE_UNUSED-flags.patch @@ -0,0 +1,59 @@ +From 9049e4df2c54b5e620f855f66db3a18c9f2e181f Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +Date: Fri, 8 Nov 2024 17:37:08 +0000 +Subject: [PATCH 1428/1482] 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. + +Signed-off-by: Dave Stevenson +--- + drivers/clk/clk-rp1.c | 9 ++++----- + 1 file changed, 4 insertions(+), 5 deletions(-) + +--- a/drivers/clk/clk-rp1.c ++++ b/drivers/clk/clk-rp1.c +@@ -1552,7 +1552,7 @@ static struct clk_hw *rp1_register_pll(s + init.num_parents = 1; + init.name = pll_data->name; + init.ops = &rp1_pll_ops; +- init.flags = pll_data->flags | CLK_IGNORE_UNUSED; ++ init.flags = pll_data->flags; + + pll = kzalloc(sizeof(*pll), GFP_KERNEL); + if (!pll) +@@ -1586,7 +1586,7 @@ static struct clk_hw *rp1_register_pll_p + init.num_parents = 1; + init.name = ph_data->name; + init.ops = &rp1_pll_ph_ops; +- init.flags = ph_data->flags | CLK_IGNORE_UNUSED; ++ init.flags = ph_data->flags; + + ph = kzalloc(sizeof(*ph), GFP_KERNEL); + if (!ph) +@@ -1619,7 +1619,7 @@ static struct clk_hw *rp1_register_pll_d + init.num_parents = 1; + init.name = divider_data->name; + init.ops = &rp1_pll_divider_ops; +- init.flags = divider_data->flags | CLK_IGNORE_UNUSED; ++ init.flags = divider_data->flags; + + divider = devm_kzalloc(clockman->dev, sizeof(*divider), GFP_KERNEL); + if (!divider) +@@ -1662,7 +1662,7 @@ static struct clk_hw *rp1_register_clock + init.num_parents = + clock_data->num_std_parents + clock_data->num_aux_parents; + init.name = clock_data->name; +- init.flags = clock_data->flags | CLK_IGNORE_UNUSED; ++ init.flags = clock_data->flags; + init.ops = &rp1_clk_ops; + + clock = devm_kzalloc(clockman->dev, sizeof(*clock), GFP_KERNEL); +@@ -1692,7 +1692,6 @@ static struct clk_hw *rp1_register_varsr + init.parent_names = &ref_clock; + init.num_parents = 1; + init.name = name; +- init.flags = CLK_IGNORE_UNUSED; + init.ops = &rp1_varsrc_ops; + + clock = devm_kzalloc(clockman->dev, sizeof(*clock), GFP_KERNEL); diff --git a/target/linux/bcm27xx/patches-6.6/950-1429-dt-rp1-Use-clk_sys-for-ethernet-hclk-and-pclk.patch b/target/linux/bcm27xx/patches-6.6/950-1429-dt-rp1-Use-clk_sys-for-ethernet-hclk-and-pclk.patch new file mode 100644 index 0000000000..6d0eb13389 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1429-dt-rp1-Use-clk_sys-for-ethernet-hclk-and-pclk.patch @@ -0,0 +1,45 @@ +From 542d0f7f2e9f90fc0f02f8cb141f7c3fbf46081b Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +Date: Mon, 11 Nov 2024 17:11:18 +0000 +Subject: [PATCH 1429/1482] 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 +fixed clocks for them. + +Signed-off-by: Dave Stevenson +--- + arch/arm64/boot/dts/broadcom/rp1.dtsi | 15 ++------------- + 1 file changed, 2 insertions(+), 13 deletions(-) + +--- a/arch/arm64/boot/dts/broadcom/rp1.dtsi ++++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi +@@ -974,7 +974,8 @@ + #address-cells = <1>; + #size-cells = <0>; + interrupts = ; +- clocks = <&macb_pclk &macb_hclk ++ clocks = <&rp1_clocks RP1_CLK_SYS ++ &rp1_clocks RP1_CLK_SYS + &rp1_clocks RP1_CLK_ETH_TSU + &rp1_clocks RP1_CLK_ETH>; + clock-names = "pclk", "hclk", "tsu_clk", "tx_clk"; +@@ -1195,18 +1196,6 @@ + clock-output-names = "xosc"; + clock-frequency = <50000000>; + }; +- macb_pclk: macb_pclk { +- compatible = "fixed-clock"; +- #clock-cells = <0>; +- clock-output-names = "pclk"; +- clock-frequency = <200000000>; +- }; +- macb_hclk: macb_hclk { +- compatible = "fixed-clock"; +- #clock-cells = <0>; +- clock-output-names = "hclk"; +- clock-frequency = <200000000>; +- }; + sdio_src: sdio_src { + // 400 MHz on FPGA. PLL sys VCO on asic + compatible = "fixed-clock"; diff --git a/target/linux/bcm27xx/patches-6.6/950-1430-dt-rp1-Link-RP1-DMA-to-the-associated-clock.patch b/target/linux/bcm27xx/patches-6.6/950-1430-dt-rp1-Link-RP1-DMA-to-the-associated-clock.patch new file mode 100644 index 0000000000..36d4f8b723 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1430-dt-rp1-Link-RP1-DMA-to-the-associated-clock.patch @@ -0,0 +1,24 @@ +From efecbda4014b490e042c7fd090942b32316f9345 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +Date: Wed, 13 Nov 2024 13:11:33 +0000 +Subject: [PATCH 1430/1482] dt: rp1: Link RP1 DMA to the associated clock + +This makes the kernel representation of the clock structure +match reality. + +Signed-off-by: Dave Stevenson +--- + arch/arm64/boot/dts/broadcom/rp1.dtsi | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/arch/arm64/boot/dts/broadcom/rp1.dtsi ++++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi +@@ -1061,7 +1061,7 @@ + reg = <0xc0 0x40188000 0x0 0x1000>; + compatible = "snps,axi-dma-1.01a"; + interrupts = ; +- clocks = <&sdhci_core &rp1_clocks RP1_CLK_SYS>; ++ clocks = <&rp1_clocks RP1_CLK_DMA &rp1_clocks RP1_CLK_SYS>; + clock-names = "core-clk", "cfgr-clk"; + + #dma-cells = <1>; diff --git a/target/linux/bcm27xx/patches-6.6/950-1456-drm-rp1-rp1-dpi-Add-interlaced-modes-and-PIO-program.patch b/target/linux/bcm27xx/patches-6.6/950-1456-drm-rp1-rp1-dpi-Add-interlaced-modes-and-PIO-program.patch new file mode 100644 index 0000000000..bbaee270c5 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1456-drm-rp1-rp1-dpi-Add-interlaced-modes-and-PIO-program.patch @@ -0,0 +1,762 @@ +From 7735dd0736322cff23aff95490bae1d69937a9bf Mon Sep 17 00:00:00 2001 +From: Nick Hollinghurst +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 + +Implement interlaced modes by wobbling the base pointer and VFP width +for every field. This results in correct pixels but incorrect VSYNC. + +Now use PIO to generate a fixed-up VSYNC by sampling DE and HSYNC. +This requires DPI's DE output to be mapped to GPIO1, which we check. + +When DE is not exposed, the internal fixup is disabled. VSYNC/GPIO2 +becomes a modified signal, designed to help an external device or +PIO program synthesize CSYNC or VSYNC. + +Signed-off-by: Nick Hollinghurst +--- + drivers/gpu/drm/rp1/rp1-dpi/Makefile | 2 +- + drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c | 34 ++- + drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h | 18 ++ + drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c | 253 ++++++++++++++++------ + drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c | 225 +++++++++++++++++++ + 5 files changed, 461 insertions(+), 71 deletions(-) + create mode 100644 drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c + +--- a/drivers/gpu/drm/rp1/rp1-dpi/Makefile ++++ b/drivers/gpu/drm/rp1/rp1-dpi/Makefile +@@ -1,5 +1,5 @@ + # SPDX-License-Identifier: GPL-2.0-only + +-drm-rp1-dpi-y := rp1_dpi.o rp1_dpi_hw.o rp1_dpi_cfg.o ++drm-rp1-dpi-y := rp1_dpi.o rp1_dpi_hw.o rp1_dpi_cfg.o rp1_dpi_pio.o + + obj-$(CONFIG_DRM_RP1_DPI) += drm-rp1-dpi.o +--- a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c ++++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c +@@ -80,6 +80,7 @@ static void rp1dpi_pipe_update(struct dr + if (dpi->dpi_running && + fb->format->format != dpi->cur_fmt) { + rp1dpi_hw_stop(dpi); ++ rp1dpi_pio_stop(dpi); + dpi->dpi_running = false; + } + if (!dpi->dpi_running) { +@@ -88,6 +89,7 @@ static void rp1dpi_pipe_update(struct dr + dpi->bus_fmt, + dpi->de_inv, + &pipe->crtc.state->mode); ++ rp1dpi_pio_start(dpi, &pipe->crtc.state->mode); + dpi->dpi_running = true; + } + dpi->cur_fmt = fb->format->format; +@@ -187,6 +189,7 @@ static void rp1dpi_pipe_disable(struct d + drm_crtc_vblank_off(&pipe->crtc); + if (dpi->dpi_running) { + rp1dpi_hw_stop(dpi); ++ rp1dpi_pio_stop(dpi); + dpi->dpi_running = false; + } + clk_disable_unprepare(dpi->clocks[RP1DPI_CLK_DPI]); +@@ -236,6 +239,7 @@ static void rp1dpi_stopall(struct drm_de + if (dpi->dpi_running || rp1dpi_hw_busy(dpi)) { + rp1dpi_hw_stop(dpi); + clk_disable_unprepare(dpi->clocks[RP1DPI_CLK_DPI]); ++ rp1dpi_pio_stop(dpi); + dpi->dpi_running = false; + } + rp1dpi_vidout_poweroff(dpi); +@@ -273,7 +277,7 @@ static int rp1dpi_platform_probe(struct + struct rp1_dpi *dpi; + struct drm_bridge *bridge = NULL; + struct drm_panel *panel; +- int i, ret; ++ int i, j, ret; + + dev_info(dev, __func__); + ret = drm_of_find_panel_or_bridge(pdev->dev.of_node, 0, 0, +@@ -295,6 +299,7 @@ static int rp1dpi_platform_probe(struct + return ret; + } + dpi->pdev = pdev; ++ spin_lock_init(&dpi->hw_lock); + + dpi->bus_fmt = default_bus_fmt; + ret = of_property_read_u32(dev->of_node, "default_bus_fmt", &dpi->bus_fmt); +@@ -332,6 +337,33 @@ static int rp1dpi_platform_probe(struct + if (ret) + goto done_err; + ++ /* Check if PIO can snoop on or override DPI's GPIO1 */ ++ dpi->gpio1_used = false; ++ for (i = 0; !dpi->gpio1_used; i++) { ++ u32 p = 0; ++ const char *str = NULL; ++ struct device_node *np1 = of_parse_phandle(dev->of_node, "pinctrl-0", i); ++ ++ if (!np1) ++ break; ++ ++ if (!of_property_read_string(np1, "function", &str) && !strcmp(str, "dpi")) { ++ for (j = 0; !dpi->gpio1_used; j++) { ++ if (of_property_read_string_index(np1, "pins", j, &str)) ++ break; ++ if (!strcmp(str, "gpio1")) ++ dpi->gpio1_used = true; ++ } ++ for (j = 0; !dpi->gpio1_used; j++) { ++ if (of_property_read_u32_index(np1, "brcm,pins", j, &p)) ++ break; ++ if (p == 1) ++ dpi->gpio1_used = true; ++ } ++ } ++ of_node_put(np1); ++ } ++ + /* Now we have all our resources, finish driver initialization */ + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + init_completion(&dpi->finished); +--- a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h ++++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h +@@ -46,6 +46,18 @@ struct rp1_dpi { + bool de_inv, clk_inv; + bool dpi_running, pipe_enabled; + struct completion finished; ++ ++ /* Experimental stuff for interlace follows */ ++ struct rp1_pio_client *pio; ++ bool gpio1_used; ++ bool pio_stole_gpio2; ++ ++ spinlock_t hw_lock; /* the following are used in line-match ISR */ ++ dma_addr_t last_dma_addr; ++ u32 last_stride; ++ u32 shorter_front_porch; ++ bool interlaced; ++ bool lower_field_flag; + }; + + /* ---------------------------------------------------------------------- */ +@@ -67,3 +79,9 @@ void rp1dpi_hw_vblank_ctrl(struct rp1_dp + + void rp1dpi_vidout_setup(struct rp1_dpi *dpi, bool drive_negedge); + void rp1dpi_vidout_poweroff(struct rp1_dpi *dpi); ++ ++/* ---------------------------------------------------------------------- */ ++/* PIO control -- we need PIO to generate VSync (from DE) when interlaced */ ++ ++int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode); ++void rp1dpi_pio_stop(struct rp1_dpi *dpi); +--- a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c ++++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c +@@ -202,7 +202,7 @@ + // Status + #define DPI_DMA_STATUS 0x3c + +-#define BITS(field, val) (((val) << (field ## _SHIFT)) & (field ## _MASK)) ++#define BITS(field, val) FIELD_PREP((field ## _MASK), val) + + static unsigned int rp1dpi_hw_read(struct rp1_dpi *dpi, unsigned int reg) + { +@@ -231,69 +231,73 @@ struct rp1dpi_ipixfmt { + u32 rgbsz; /* Shifts used for scaling; also (BPP/8-1) */ + }; + +-#define IMASK_RGB(r, g, b) (BITS(DPI_DMA_IMASK_R, r) | \ +- BITS(DPI_DMA_IMASK_G, g) | \ +- BITS(DPI_DMA_IMASK_B, b)) +-#define OMASK_RGB(r, g, b) (BITS(DPI_DMA_OMASK_R, r) | \ +- BITS(DPI_DMA_OMASK_G, g) | \ +- BITS(DPI_DMA_OMASK_B, b)) +-#define ISHIFT_RGB(r, g, b) (BITS(DPI_DMA_SHIFT_IR, r) | \ +- BITS(DPI_DMA_SHIFT_IG, g) | \ +- BITS(DPI_DMA_SHIFT_IB, b)) +-#define OSHIFT_RGB(r, g, b) (BITS(DPI_DMA_SHIFT_OR, r) | \ +- BITS(DPI_DMA_SHIFT_OG, g) | \ +- BITS(DPI_DMA_SHIFT_OB, b)) ++#define IMASK_RGB(r, g, b) (FIELD_PREP_CONST(DPI_DMA_IMASK_R_MASK, r) | \ ++ FIELD_PREP_CONST(DPI_DMA_IMASK_G_MASK, g) | \ ++ FIELD_PREP_CONST(DPI_DMA_IMASK_B_MASK, b)) ++#define OMASK_RGB(r, g, b) (FIELD_PREP_CONST(DPI_DMA_OMASK_R_MASK, r) | \ ++ FIELD_PREP_CONST(DPI_DMA_OMASK_G_MASK, g) | \ ++ FIELD_PREP_CONST(DPI_DMA_OMASK_B_MASK, b)) ++#define ISHIFT_RGB(r, g, b) (FIELD_PREP_CONST(DPI_DMA_SHIFT_IR_MASK, r) | \ ++ FIELD_PREP_CONST(DPI_DMA_SHIFT_IG_MASK, g) | \ ++ FIELD_PREP_CONST(DPI_DMA_SHIFT_IB_MASK, b)) ++#define OSHIFT_RGB(r, g, b) (FIELD_PREP_CONST(DPI_DMA_SHIFT_OR_MASK, r) | \ ++ FIELD_PREP_CONST(DPI_DMA_SHIFT_OG_MASK, g) | \ ++ FIELD_PREP_CONST(DPI_DMA_SHIFT_OB_MASK, b)) + + static const struct rp1dpi_ipixfmt my_formats[] = { + { + .format = DRM_FORMAT_XRGB8888, + .mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = ISHIFT_RGB(23, 15, 7), +- .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 3), ++ .rgbsz = FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 3), + }, + { + .format = DRM_FORMAT_XBGR8888, + .mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = ISHIFT_RGB(7, 15, 23), +- .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 3), ++ .rgbsz = FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 3), + }, + { + .format = DRM_FORMAT_ARGB8888, + .mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = ISHIFT_RGB(23, 15, 7), +- .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 3), ++ .rgbsz = FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 3), + }, + { + .format = DRM_FORMAT_ABGR8888, + .mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = ISHIFT_RGB(7, 15, 23), +- .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 3), ++ .rgbsz = FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 3), + }, + { + .format = DRM_FORMAT_RGB888, + .mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = ISHIFT_RGB(23, 15, 7), +- .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 2), ++ .rgbsz = FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 2), + }, + { + .format = DRM_FORMAT_BGR888, + .mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc), + .shift = ISHIFT_RGB(7, 15, 23), +- .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 2), ++ .rgbsz = FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 2), + }, + { + .format = DRM_FORMAT_RGB565, + .mask = IMASK_RGB(0x3e0, 0x3f0, 0x3e0), + .shift = ISHIFT_RGB(15, 10, 4), +- .rgbsz = BITS(DPI_DMA_RGBSZ_R, 5) | BITS(DPI_DMA_RGBSZ_G, 6) | +- BITS(DPI_DMA_RGBSZ_B, 5) | BITS(DPI_DMA_RGBSZ_BPP, 1), ++ .rgbsz = (FIELD_PREP_CONST(DPI_DMA_RGBSZ_R_MASK, 5) | ++ FIELD_PREP_CONST(DPI_DMA_RGBSZ_G_MASK, 6) | ++ FIELD_PREP_CONST(DPI_DMA_RGBSZ_B_MASK, 5) | ++ FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 1)), + }, + { + .format = DRM_FORMAT_BGR565, + .mask = IMASK_RGB(0x3e0, 0x3f0, 0x3e0), + .shift = ISHIFT_RGB(4, 10, 15), +- .rgbsz = BITS(DPI_DMA_RGBSZ_R, 5) | BITS(DPI_DMA_RGBSZ_G, 6) | +- BITS(DPI_DMA_RGBSZ_B, 5) | BITS(DPI_DMA_RGBSZ_BPP, 1), ++ .rgbsz = (FIELD_PREP_CONST(DPI_DMA_RGBSZ_R_MASK, 5) | ++ FIELD_PREP_CONST(DPI_DMA_RGBSZ_G_MASK, 6) | ++ FIELD_PREP_CONST(DPI_DMA_RGBSZ_B_MASK, 5) | ++ FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 1)), + } + }; + +@@ -354,42 +358,26 @@ void rp1dpi_hw_setup(struct rp1_dpi *dpi + u32 in_format, u32 bus_format, bool de_inv, + struct drm_display_mode const *mode) + { +- u32 shift, imask, omask, rgbsz; ++ u32 shift, imask, omask, rgbsz, vctrl; + int i; + +- pr_info("%s: in_fmt=\'%c%c%c%c\' bus_fmt=0x%x mode=%dx%d total=%dx%d %dkHz %cH%cV%cD%cC", +- __func__, in_format, in_format >> 8, in_format >> 16, in_format >> 24, bus_format, +- mode->hdisplay, mode->vdisplay, +- mode->htotal, mode->vtotal, +- mode->clock, +- (mode->flags & DRM_MODE_FLAG_NHSYNC) ? '-' : '+', +- (mode->flags & DRM_MODE_FLAG_NVSYNC) ? '-' : '+', +- de_inv ? '-' : '+', +- dpi->clk_inv ? '-' : '+'); ++ drm_info(&dpi->drm, ++ "in_fmt=\'%c%c%c%c\' bus_fmt=0x%x mode=%dx%d total=%dx%d%s %dkHz %cH%cV%cD%cC", ++ in_format, in_format >> 8, in_format >> 16, in_format >> 24, bus_format, ++ mode->hdisplay, mode->vdisplay, ++ mode->htotal, mode->vtotal, ++ (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "i" : "", ++ mode->clock, ++ (mode->flags & DRM_MODE_FLAG_NHSYNC) ? '-' : '+', ++ (mode->flags & DRM_MODE_FLAG_NVSYNC) ? '-' : '+', ++ de_inv ? '-' : '+', ++ dpi->clk_inv ? '-' : '+'); + + /* + * Configure all DPI/DMA block registers, except base address. + * DMA will not actually start until a FB base address is specified + * using rp1dpi_hw_update(). + */ +- rp1dpi_hw_write(dpi, DPI_DMA_VISIBLE_AREA, +- BITS(DPI_DMA_VISIBLE_AREA_ROWSM1, mode->vdisplay - 1) | +- BITS(DPI_DMA_VISIBLE_AREA_COLSM1, mode->hdisplay - 1)); +- +- rp1dpi_hw_write(dpi, DPI_DMA_SYNC_WIDTH, +- BITS(DPI_DMA_SYNC_WIDTH_ROWSM1, mode->vsync_end - mode->vsync_start - 1) | +- BITS(DPI_DMA_SYNC_WIDTH_COLSM1, mode->hsync_end - mode->hsync_start - 1)); +- +- /* In these registers, "back porch" time includes sync width */ +- rp1dpi_hw_write(dpi, DPI_DMA_BACK_PORCH, +- BITS(DPI_DMA_BACK_PORCH_ROWSM1, mode->vtotal - mode->vsync_start - 1) | +- BITS(DPI_DMA_BACK_PORCH_COLSM1, mode->htotal - mode->hsync_start - 1)); +- +- rp1dpi_hw_write(dpi, DPI_DMA_FRONT_PORCH, +- BITS(DPI_DMA_FRONT_PORCH_ROWSM1, mode->vsync_start - mode->vdisplay - 1) | +- BITS(DPI_DMA_FRONT_PORCH_COLSM1, mode->hsync_start - mode->hdisplay - 1)); +- +- /* Input to output pixel format conversion */ + for (i = 0; i < ARRAY_SIZE(my_formats); ++i) { + if (my_formats[i].format == in_format) + break; +@@ -417,6 +405,89 @@ void rp1dpi_hw_setup(struct rp1_dpi *dpi + BITS(DPI_DMA_QOS_LLEV, 0x8) | + BITS(DPI_DMA_QOS_LQOS, 0x7)); + ++ if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) { ++ rp1dpi_hw_write(dpi, DPI_DMA_VISIBLE_AREA, ++ BITS(DPI_DMA_VISIBLE_AREA_ROWSM1, mode->vdisplay - 1) | ++ BITS(DPI_DMA_VISIBLE_AREA_COLSM1, mode->hdisplay - 1)); ++ ++ rp1dpi_hw_write(dpi, DPI_DMA_SYNC_WIDTH, ++ BITS(DPI_DMA_SYNC_WIDTH_ROWSM1, ++ mode->vsync_end - mode->vsync_start - 1) | ++ BITS(DPI_DMA_SYNC_WIDTH_COLSM1, ++ mode->hsync_end - mode->hsync_start - 1)); ++ ++ /* In these registers, "back porch" time includes sync width */ ++ rp1dpi_hw_write(dpi, DPI_DMA_BACK_PORCH, ++ BITS(DPI_DMA_BACK_PORCH_ROWSM1, ++ mode->vtotal - mode->vsync_start - 1) | ++ BITS(DPI_DMA_BACK_PORCH_COLSM1, ++ mode->htotal - mode->hsync_start - 1)); ++ ++ rp1dpi_hw_write(dpi, DPI_DMA_FRONT_PORCH, ++ BITS(DPI_DMA_FRONT_PORCH_ROWSM1, ++ mode->vsync_start - mode->vdisplay - 1) | ++ BITS(DPI_DMA_FRONT_PORCH_COLSM1, ++ mode->hsync_start - mode->hdisplay - 1)); ++ ++ vctrl = BITS(DPI_DMA_CONTROL_VSYNC_POL, !!(mode->flags & DRM_MODE_FLAG_NVSYNC)) | ++ BITS(DPI_DMA_CONTROL_VBP_EN, (mode->vtotal != mode->vsync_start)) | ++ BITS(DPI_DMA_CONTROL_VFP_EN, (mode->vsync_start != mode->vdisplay)) | ++ BITS(DPI_DMA_CONTROL_VSYNC_EN, (mode->vsync_end != mode->vsync_start)); ++ ++ dpi->interlaced = false; ++ } else { ++ /* ++ * Experimental interlace support ++ * ++ * RP1 DPI hardware wasn't designed to support interlace, but lets us change ++ * both the VFP line count and the next DMA address while running. That allows ++ * pixel data to be correctly timed for interlace, but VSYNC remains wrong. ++ * ++ * It is necessary to use external hardware (such as PIO) to regenerate VSYNC ++ * based on HSYNC, DE (which *must* both be mapped to GPIOs 1, 3 respectively). ++ * This driver includes a PIO program to do that, when DE is enabled. ++ * ++ * An alternative fixup is to synthesize CSYNC from HSYNC and modified-VSYNC. ++ * We don't implement that here, but to facilitate it, DPI's VSYNC is replaced ++ * by a "helper signal" that pulses low for 1 or 2 scan-lines, starting 2.0 or ++ * 2.5 scan-lines respectively before nominal VSYNC start. ++ */ ++ int vact = mode->vdisplay >> 1; /* visible lines per field. Can't do half-lines */ ++ int vtot0 = mode->vtotal >> 1; /* vtotal should always be odd when interlaced. */ ++ int vfp0 = (mode->vsync_start >= mode->vdisplay + 4) ? ++ ((mode->vsync_start - mode->vdisplay - 2) >> 1) : 1; ++ int vbp = max(0, vtot0 - vact - vfp0); ++ ++ rp1dpi_hw_write(dpi, DPI_DMA_VISIBLE_AREA, ++ BITS(DPI_DMA_VISIBLE_AREA_ROWSM1, vact - 1) | ++ BITS(DPI_DMA_VISIBLE_AREA_COLSM1, mode->hdisplay - 1)); ++ ++ rp1dpi_hw_write(dpi, DPI_DMA_SYNC_WIDTH, ++ BITS(DPI_DMA_SYNC_WIDTH_ROWSM1, vtot0 - 2) | ++ BITS(DPI_DMA_SYNC_WIDTH_COLSM1, ++ mode->hsync_end - mode->hsync_start - 1)); ++ ++ rp1dpi_hw_write(dpi, DPI_DMA_BACK_PORCH, ++ BITS(DPI_DMA_BACK_PORCH_ROWSM1, vbp - 1) | ++ BITS(DPI_DMA_BACK_PORCH_COLSM1, ++ mode->htotal - mode->hsync_start - 1)); ++ ++ dpi->shorter_front_porch = ++ BITS(DPI_DMA_FRONT_PORCH_ROWSM1, vfp0 - 1) | ++ BITS(DPI_DMA_FRONT_PORCH_COLSM1, ++ mode->hsync_start - mode->hdisplay - 1); ++ rp1dpi_hw_write(dpi, DPI_DMA_FRONT_PORCH, dpi->shorter_front_porch); ++ ++ vctrl = BITS(DPI_DMA_CONTROL_VSYNC_POL, 0) | ++ BITS(DPI_DMA_CONTROL_VBP_EN, (vbp > 0)) | ++ BITS(DPI_DMA_CONTROL_VFP_EN, 1) | ++ BITS(DPI_DMA_CONTROL_VSYNC_EN, 1); ++ ++ dpi->interlaced = true; ++ } ++ dpi->lower_field_flag = false; ++ dpi->last_dma_addr = 0; ++ + rp1dpi_hw_write(dpi, DPI_DMA_IRQ_FLAGS, -1); + rp1dpi_hw_vblank_ctrl(dpi, 1); + +@@ -425,49 +496,64 @@ void rp1dpi_hw_setup(struct rp1_dpi *dpi + pr_warn("%s: Unexpectedly busy at start!", __func__); + + rp1dpi_hw_write(dpi, DPI_DMA_CONTROL, ++ vctrl | + BITS(DPI_DMA_CONTROL_ARM, !i) | + BITS(DPI_DMA_CONTROL_AUTO_REPEAT, 1) | + BITS(DPI_DMA_CONTROL_HIGH_WATER, 448) | + BITS(DPI_DMA_CONTROL_DEN_POL, de_inv) | + BITS(DPI_DMA_CONTROL_HSYNC_POL, !!(mode->flags & DRM_MODE_FLAG_NHSYNC)) | +- BITS(DPI_DMA_CONTROL_VSYNC_POL, !!(mode->flags & DRM_MODE_FLAG_NVSYNC)) | +- BITS(DPI_DMA_CONTROL_COLORM, 0) | +- BITS(DPI_DMA_CONTROL_SHUTDN, 0) | + BITS(DPI_DMA_CONTROL_HBP_EN, (mode->htotal != mode->hsync_end)) | + BITS(DPI_DMA_CONTROL_HFP_EN, (mode->hsync_start != mode->hdisplay)) | +- BITS(DPI_DMA_CONTROL_VBP_EN, (mode->vtotal != mode->vsync_end)) | +- BITS(DPI_DMA_CONTROL_VFP_EN, (mode->vsync_start != mode->vdisplay)) | +- BITS(DPI_DMA_CONTROL_HSYNC_EN, (mode->hsync_end != mode->hsync_start)) | +- BITS(DPI_DMA_CONTROL_VSYNC_EN, (mode->vsync_end != mode->vsync_start))); ++ BITS(DPI_DMA_CONTROL_HSYNC_EN, (mode->hsync_end != mode->hsync_start))); + } + + void rp1dpi_hw_update(struct rp1_dpi *dpi, dma_addr_t addr, u32 offset, u32 stride) + { +- u64 a = addr + offset; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&dpi->hw_lock, flags); + + /* + * Update STRIDE, DMAH and DMAL only. When called after rp1dpi_hw_setup(), + * DMA starts immediately; if already running, the buffer will flip at +- * the next vertical sync event. ++ * the next vertical sync event. In interlaced mode, we need to adjust ++ * the address and stride to display only the current field, saving ++ * the original address (so it can be flipped for subsequent fields). + */ ++ addr += offset; ++ dpi->last_dma_addr = addr; ++ dpi->last_stride = stride; ++ if (dpi->interlaced) { ++ if (dpi->lower_field_flag) ++ addr += stride; ++ stride *= 2; ++ } + rp1dpi_hw_write(dpi, DPI_DMA_DMA_STRIDE, stride); +- rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_H, a >> 32); +- rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_L, a & 0xFFFFFFFFu); ++ rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_H, addr >> 32); ++ rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_L, addr & 0xFFFFFFFFu); ++ ++ spin_unlock_irqrestore(&dpi->hw_lock, flags); + } + + void rp1dpi_hw_stop(struct rp1_dpi *dpi) + { + u32 ctrl; ++ unsigned long flags; + + /* +- * Stop DMA by turning off the Auto-Repeat flag, and wait up to 100ms for +- * the current and any queued frame to end. "Force drain" flags are not used, +- * as they seem to prevent DMA from re-starting properly; it's safer to wait. ++ * Stop DMA by turning off Auto-Repeat (and disable S/W field-flip), ++ * then wait up to 100ms for the current and any queued frame to end. ++ * (There is a "force drain" flag, but it can leave DPI in a broken ++ * state which prevents it from restarting; it's safer to wait.) + */ ++ spin_lock_irqsave(&dpi->hw_lock, flags); ++ dpi->last_dma_addr = 0; + reinit_completion(&dpi->finished); + ctrl = rp1dpi_hw_read(dpi, DPI_DMA_CONTROL); + ctrl &= ~(DPI_DMA_CONTROL_ARM_MASK | DPI_DMA_CONTROL_AUTO_REPEAT_MASK); + rp1dpi_hw_write(dpi, DPI_DMA_CONTROL, ctrl); ++ spin_unlock_irqrestore(&dpi->hw_lock, flags); ++ + if (!wait_for_completion_timeout(&dpi->finished, HZ / 10)) + drm_err(&dpi->drm, "%s: timed out waiting for idle\n", __func__); + rp1dpi_hw_write(dpi, DPI_DMA_IRQ_EN, 0); +@@ -476,10 +562,11 @@ void rp1dpi_hw_stop(struct rp1_dpi *dpi) + void rp1dpi_hw_vblank_ctrl(struct rp1_dpi *dpi, int enable) + { + rp1dpi_hw_write(dpi, DPI_DMA_IRQ_EN, +- BITS(DPI_DMA_IRQ_EN_AFIFO_EMPTY, 1) | +- BITS(DPI_DMA_IRQ_EN_UNDERFLOW, 1) | +- BITS(DPI_DMA_IRQ_EN_DMA_READY, !!enable) | +- BITS(DPI_DMA_IRQ_EN_MATCH_LINE, 4095)); ++ BITS(DPI_DMA_IRQ_EN_AFIFO_EMPTY, 1) | ++ BITS(DPI_DMA_IRQ_EN_UNDERFLOW, 1) | ++ BITS(DPI_DMA_IRQ_EN_DMA_READY, !!enable) | ++ BITS(DPI_DMA_IRQ_EN_MATCH, dpi->interlaced) | ++ BITS(DPI_DMA_IRQ_EN_MATCH_LINE, 32)); + } + + irqreturn_t rp1dpi_hw_isr(int irq, void *dev) +@@ -498,7 +585,35 @@ irqreturn_t rp1dpi_hw_isr(int irq, void + drm_crtc_handle_vblank(&dpi->pipe.crtc); + if (u & DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_MASK) + complete(&dpi->finished); ++ ++ /* ++ * Added for interlace support: We use this mid-frame interrupt to ++ * wobble the VFP between fields, re-submitting the next-buffer address ++ * with an offset to display the opposite field. NB: rp1dpi_hw_update() ++ * may be called at any time, before or after, so locking is needed. ++ * H/W Auto-update is no longer needed (unless this IRQ is lost). ++ */ ++ if ((u & DPI_DMA_IRQ_FLAGS_MATCH_MASK) && dpi->interlaced) { ++ unsigned long flags; ++ dma_addr_t a; ++ ++ spin_lock_irqsave(&dpi->hw_lock, flags); ++ dpi->lower_field_flag = !dpi->lower_field_flag; ++ rp1dpi_hw_write(dpi, DPI_DMA_FRONT_PORCH, ++ dpi->shorter_front_porch + ++ BITS(DPI_DMA_FRONT_PORCH_ROWSM1, ++ dpi->lower_field_flag)); ++ a = dpi->last_dma_addr; ++ if (a) { ++ if (dpi->lower_field_flag) ++ a += dpi->last_stride; ++ rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_H, a >> 32); ++ rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_L, a & 0xFFFFFFFFu); ++ } ++ spin_unlock_irqrestore(&dpi->hw_lock, flags); ++ } + } + } ++ + return u ? IRQ_HANDLED : IRQ_NONE; + } +--- /dev/null ++++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c +@@ -0,0 +1,225 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * PIO code for Raspberry Pi RP1 DPI driver ++ * ++ * Copyright (c) 2024 Raspberry Pi Limited. ++ */ ++ ++/* ++ * Use PIO to fix up VSYNC for interlaced modes. ++ * ++ * For this to work we *require* DPI's pinctrl to enable DE on GPIO1. ++ * PIO can then snoop on HSYNC and DE pins to generate corrected VSYNC. ++ * ++ * Note that corrected VSYNC outputs will not be synchronous to DPICLK, ++ * will lag HSYNC by about 30ns and may suffer up to 5ns of jitter. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "rp1_dpi.h" ++ ++/* ++ * Start a PIO SM to generate an interrupt just after HSYNC onset, then another ++ * after a fixed delay (during which we assume HSYNC will have been deasserted). ++ */ ++ ++static int rp1dpi_pio_start_timer_both(struct rp1_dpi *dpi, u32 flags, u32 tc) ++{ ++ static const u16 instructions[2][5] = { ++ { 0xa022, 0x2083, 0xc001, 0x0043, 0xc001 }, /* posedge */ ++ { 0xa022, 0x2003, 0xc001, 0x0043, 0xc001 }, /* negedge */ ++ }; ++ const struct pio_program prog = { ++ .instructions = instructions[(flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0], ++ .length = ARRAY_SIZE(instructions[0]), ++ .origin = -1 ++ }; ++ int offset, sm; ++ ++ sm = pio_claim_unused_sm(dpi->pio, true); ++ if (sm < 0) ++ return -EBUSY; ++ ++ offset = pio_add_program(dpi->pio, &prog); ++ if (offset == PIO_ORIGIN_ANY) ++ return -EBUSY; ++ ++ pio_sm_config cfg = pio_get_default_sm_config(); ++ ++ pio_sm_set_enabled(dpi->pio, sm, false); ++ sm_config_set_wrap(&cfg, offset, offset + 4); ++ pio_sm_init(dpi->pio, sm, offset, &cfg); ++ ++ pio_sm_put(dpi->pio, sm, tc - 4); ++ pio_sm_exec(dpi->pio, sm, pio_encode_pull(false, false)); ++ pio_sm_exec(dpi->pio, sm, pio_encode_out(pio_y, 32)); ++ pio_sm_set_enabled(dpi->pio, sm, true); ++ ++ return 0; ++} ++ ++/* ++ * Snoop on DE, HSYNC to count half-lines in the vertical blanking interval ++ * to determine when the VSYNC pulse should start and finish. Then, at a ++ * suitable moment (which should be an odd number of half-lines since the ++ * last active line), sample DE again to detect field phase. ++ * ++ * This version assumes VFP length is within 2..129 half-lines for any field ++ * (one half-line delay is needed to sample DE; we always wait for the next ++ * half-line boundary to improve VSync start accuracy). ++ */ ++ ++static int rp1dpi_pio_vsync_ilace(struct rp1_dpi *dpi, ++ struct drm_display_mode const *mode) ++{ ++ static const int wrap_target = 14; ++ static const int wrap = 26; ++ u16 instructions[] = { /* This is mutable */ ++ 0xa0e6, // 0: mov osr, isr side 0 ; top: rewind parameters ++ 0x2081, // 1: wait 1 gpio, 1 side 0 ; main: while (!DE) wait; ++ 0x2783, // 2: wait 1 gpio, 3 side 0 [7] ; do { @HSync ++ 0xc041, // 3: irq clear 1 side 0 ; flush stale IRQs ++ 0x20c1, // 4: wait 1 irq, 1 side 0 ; @midline ++ 0x00c1, // 5: jmp pin, 1 side 0 ; } while (DE) ++ 0x0007, // 6: jmp 7 side 0 ; ++ 0x6027, // 7: out x, 7 side 0 ; x = VFPlen - 2 ++ 0x000a, // 8: jmp 10 side 0 ; while (x--) { ++ 0x20c1, // 9: wait 1 irq, 1 side 0 ; @halfline ++ 0x0049, // 10: jmp x--, 9 side 0 ; } ++ 0x6021, // 11: out x, 1 side 0 ; test for aligned case ++ 0x003a, // 12: jmp !x, 26 side 0 ; if (!x) goto precise; ++ 0x20c1, // 13: wait 1 irq, 1 side 0 ; @halfline ++ // .wrap_target ; vsjoin: ++ 0xb722, // 14: mov x, y side 1 [7] ; VSYNC=1; x = VSyncLen ++ 0xd041, // 15: irq clear 1 side 1 ; VSYNC=1; flush stale IRQs ++ 0x30c1, // 16: wait 1 irq, 1 side 1 ; VSYNC=1; do { @halfline ++ 0x1050, // 17: jmp x--, 16 side 1 ; VSYNC=1; } while (x--) ++ 0x6028, // 18: out x, 8 side 0 ; VSYNC=0; x = VBPLen ++ 0x0015, // 19: jmp 21 side 0 ; while (x--) { ++ 0x20c1, // 20: wait 1 irq, 1 side 0 ; @halfline ++ 0x0054, // 21: jmp x--, 20 side 0 ; } ++ 0x00c0, // 22: jmp pin, 0 side 0 ; if (DE) reset phase ++ 0x0018, // 23: jmp 24 side 0 ; ++ 0x00e1, // 24: jmp !osre, 1 side 0 ; if (!phase) goto main ++ 0x0000, // 25: jmp 0 side 0 ; goto top ++ 0x2083, // 26: wait 1 gpio, 3 side 0 ; precise: @HSync ++ // .wrap ; goto vsjoin ++ }; ++ struct pio_program prog = { ++ .instructions = instructions, ++ .length = ARRAY_SIZE(instructions), ++ .origin = -1 ++ }; ++ pio_sm_config cfg = pio_get_default_sm_config(); ++ unsigned int i, offset; ++ u32 tc, vfp, vbp; ++ u32 sysclk = clock_get_hz(clk_sys); ++ int sm = pio_claim_unused_sm(dpi->pio, true); ++ ++ if (sm < 0) ++ return -EBUSY; ++ ++ /* Compute mid-line time constant and start the timer SM */ ++ tc = (mode->htotal * (u64)sysclk) / (u64)(2000u * mode->clock); ++ if (rp1dpi_pio_start_timer_both(dpi, mode->flags, tc) < 0) { ++ pio_sm_unclaim(dpi->pio, sm); ++ return -EBUSY; ++ } ++ ++ /* Adapt program code according to DE and Sync polarity; configure program */ ++ pio_sm_set_enabled(dpi->pio, sm, false); ++ if (dpi->de_inv) { ++ instructions[1] ^= 0x0080; ++ instructions[5] = 0x00c7; ++ instructions[6] = 0x0001; ++ instructions[22] = 0x00d8; ++ instructions[23] = 0x0000; ++ } ++ for (i = 0; i < ARRAY_SIZE(instructions); i++) { ++ if (mode->flags & DRM_MODE_FLAG_NVSYNC) ++ instructions[i] ^= 0x1000; ++ if ((mode->flags & DRM_MODE_FLAG_NHSYNC) && (instructions[i] & 0xe07f) == 0x2003) ++ instructions[i] ^= 0x0080; ++ } ++ offset = pio_add_program(dpi->pio, &prog); ++ if (offset == PIO_ORIGIN_ANY) ++ return -EBUSY; ++ ++ /* Configure pins and SM */ ++ dpi->pio_stole_gpio2 = true; ++ sm_config_set_wrap(&cfg, offset + wrap_target, offset + wrap); ++ sm_config_set_sideset(&cfg, 1, false, false); ++ sm_config_set_sideset_pins(&cfg, 2); ++ pio_gpio_init(dpi->pio, 2); ++ sm_config_set_jmp_pin(&cfg, 1); /* "DE" is always GPIO1 */ ++ pio_sm_init(dpi->pio, sm, offset, &cfg); ++ pio_sm_set_consecutive_pindirs(dpi->pio, sm, 2, 1, true); ++ ++ /* Compute vertical times, remembering how we rounded vdisplay, vtotal */ ++ vfp = mode->vsync_start - (mode->vdisplay & ~1); ++ vbp = (mode->vtotal | 1) - mode->vsync_end; ++ if (vfp > 128) { ++ vbp += vfp - 128; ++ vfp = 128; ++ } else if (vfp < 3) { ++ vbp = (vbp > 3 - vfp) ? (vbp - 3 + vfp) : 0; ++ vfp = 3; ++ } ++ ++ pio_sm_put(dpi->pio, sm, ++ (vfp - 2) + ((vfp & 1) << 7) + (vbp << 8) + ++ ((vfp - 3) << 16) + (((~vfp) & 1) << 23) + ((vbp + 1) << 24)); ++ pio_sm_put(dpi->pio, sm, mode->vsync_end - mode->vsync_start - 1); ++ pio_sm_exec(dpi->pio, sm, pio_encode_pull(false, false)); ++ pio_sm_exec(dpi->pio, sm, pio_encode_out(pio_y, 32)); ++ pio_sm_exec(dpi->pio, sm, pio_encode_in(pio_y, 32)); ++ pio_sm_exec(dpi->pio, sm, pio_encode_pull(false, false)); ++ pio_sm_exec(dpi->pio, sm, pio_encode_out(pio_y, 32)); ++ pio_sm_set_enabled(dpi->pio, sm, true); ++ ++ return 0; ++} ++ ++int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode) ++{ ++ int r; ++ ++ if (!(mode->flags & DRM_MODE_FLAG_INTERLACE) || !dpi->gpio1_used) ++ return 0; ++ ++ if (dpi->pio) ++ pio_close(dpi->pio); ++ ++ dpi->pio = pio_open(); ++ if (IS_ERR(dpi->pio)) { ++ drm_err(&dpi->drm, "Could not open PIO\n"); ++ dpi->pio = NULL; ++ return -ENODEV; ++ } ++ ++ r = rp1dpi_pio_vsync_ilace(dpi, mode); ++ if (r) { ++ drm_err(&dpi->drm, "Failed to initialize PIO\n"); ++ rp1dpi_pio_stop(dpi); ++ } ++ ++ return r; ++} ++ ++void rp1dpi_pio_stop(struct rp1_dpi *dpi) ++{ ++ if (dpi->pio) { ++ if (dpi->pio_stole_gpio2) ++ pio_gpio_set_function(dpi->pio, 2, GPIO_FUNC_FSEL1); ++ pio_close(dpi->pio); ++ dpi->pio_stole_gpio2 = false; ++ dpi->pio = NULL; ++ } ++} diff --git a/target/linux/bcm27xx/patches-6.6/950-1457-pwm-Improve-PWM_PIO_RP1-dependencies.patch b/target/linux/bcm27xx/patches-6.6/950-1457-pwm-Improve-PWM_PIO_RP1-dependencies.patch new file mode 100644 index 0000000000..357cd05dc8 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1457-pwm-Improve-PWM_PIO_RP1-dependencies.patch @@ -0,0 +1,22 @@ +From f85f3509692f966ec32e4db499f7e64dc6b6b952 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 diff --git a/target/linux/bcm27xx/patches-6.6/950-1458-Revert-pwm-Improve-PWM_PIO_RP1-dependencies.patch b/target/linux/bcm27xx/patches-6.6/950-1458-Revert-pwm-Improve-PWM_PIO_RP1-dependencies.patch new file mode 100644 index 0000000000..eabf3887d1 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1458-Revert-pwm-Improve-PWM_PIO_RP1-dependencies.patch @@ -0,0 +1,20 @@ +From 73fb1e979a210094935f4af4c3d6e700fba30c5f Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 diff --git a/target/linux/bcm27xx/patches-6.6/950-1463-drm-rp1-rp1-dpi-Fix-optional-dependency-on-RP1_PIO.patch b/target/linux/bcm27xx/patches-6.6/950-1463-drm-rp1-rp1-dpi-Fix-optional-dependency-on-RP1_PIO.patch new file mode 100644 index 0000000000..52e87daa38 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1463-drm-rp1-rp1-dpi-Fix-optional-dependency-on-RP1_PIO.patch @@ -0,0 +1,99 @@ +From 80533a952218696c0ef1b346bab50dc401e6b74c Mon Sep 17 00:00:00 2001 +From: Nick Hollinghurst +Date: Thu, 12 Dec 2024 11:58:12 +0000 +Subject: [PATCH 1463/1482] 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 +interlaced modes when RP1_PIO is not present. + +Signed-off-by: Nick Hollinghurst +--- + drivers/gpu/drm/rp1/rp1-dpi/Kconfig | 7 ++++++- + drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c | 16 ++++++++++++++++ + drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c | 18 +++++++++++++++++- + 3 files changed, 39 insertions(+), 2 deletions(-) + +--- a/drivers/gpu/drm/rp1/rp1-dpi/Kconfig ++++ b/drivers/gpu/drm/rp1/rp1-dpi/Kconfig +@@ -7,5 +7,10 @@ config DRM_RP1_DPI + select DRM_VRAM_HELPER + select DRM_TTM + select DRM_TTM_HELPER ++ depends on RP1_PIO || !RP1_PIO + help +- Choose this option to enable Video Out on RP1 ++ Choose this option to enable DPI output on Raspberry Pi RP1 ++ ++ There is an optional dependency on RP1_PIO, as the PIO block ++ must be used to fix up interlaced sync. Interlaced DPI modes ++ will be unavailable when RP1_PIO is not selected. +--- a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c ++++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c +@@ -217,12 +217,28 @@ static void rp1dpi_pipe_disable_vblank(s + rp1dpi_hw_vblank_ctrl(dpi, 0); + } + ++static enum drm_mode_status rp1dpi_pipe_mode_valid(struct drm_simple_display_pipe *pipe, ++ const struct drm_display_mode *mode) ++{ ++#if !IS_REACHABLE(CONFIG_RP1_PIO) ++ if (mode->flags & DRM_MODE_FLAG_INTERLACE) ++ return MODE_NO_INTERLACE; ++#endif ++ if (mode->clock < 1000) /* 1 MHz */ ++ return MODE_CLOCK_LOW; ++ if (mode->clock > 200000) /* 200 MHz */ ++ return MODE_CLOCK_HIGH; ++ ++ return MODE_OK; ++} ++ + static const struct drm_simple_display_pipe_funcs rp1dpi_pipe_funcs = { + .enable = rp1dpi_pipe_enable, + .update = rp1dpi_pipe_update, + .disable = rp1dpi_pipe_disable, + .enable_vblank = rp1dpi_pipe_enable_vblank, + .disable_vblank = rp1dpi_pipe_disable_vblank, ++ .mode_valid = rp1dpi_pipe_mode_valid, + }; + + static const struct drm_mode_config_funcs rp1dpi_mode_funcs = { +--- a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c ++++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c +@@ -18,13 +18,16 @@ + #include + #include + #include +-#include + #include + #include + #include + + #include "rp1_dpi.h" + ++#if IS_REACHABLE(CONFIG_RP1_PIO) ++ ++#include ++ + /* + * Start a PIO SM to generate an interrupt just after HSYNC onset, then another + * after a fixed delay (during which we assume HSYNC will have been deasserted). +@@ -223,3 +226,16 @@ void rp1dpi_pio_stop(struct rp1_dpi *dpi + dpi->pio = NULL; + } + } ++ ++#else /* !IS_REACHABLE(CONFIG_RP1_PIO) */ ++ ++int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode) ++{ ++ return -ENODEV; ++} ++ ++void rp1dpi_pio_stop(struct rp1_dpi *dpi) ++{ ++} ++ ++#endif diff --git a/target/linux/bcm27xx/patches-6.6/950-1468-misc-rp1-pio-Support-larger-data-transfers.patch b/target/linux/bcm27xx/patches-6.6/950-1468-misc-rp1-pio-Support-larger-data-transfers.patch new file mode 100644 index 0000000000..828efdb79b --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1468-misc-rp1-pio-Support-larger-data-transfers.patch @@ -0,0 +1,127 @@ +From 4b0ca96738bb937529655a0062d60775f47b0f5e Mon Sep 17 00:00:00 2001 +From: Phil Elwell +Date: Mon, 16 Dec 2024 23:01:41 +0000 +Subject: [PATCH 1468/1482] misc: rp1-pio: Support larger data transfers + +Add a separate IOCTL for larger transfer with a 32-bit data_bytes +field. + +See: https://github.com/raspberrypi/utils/issues/107 + +Signed-off-by: Phil Elwell +--- + drivers/misc/rp1-pio.c | 43 +++++++++++++++++++++++++++++++--- + include/uapi/misc/rp1_pio_if.h | 8 +++++++ + 2 files changed, 48 insertions(+), 3 deletions(-) + +--- a/drivers/misc/rp1-pio.c ++++ b/drivers/misc/rp1-pio.c +@@ -824,9 +824,9 @@ static int rp1_pio_sm_rx_user(struct rp1 + return ret; + } + +-static int rp1_pio_sm_xfer_data(struct rp1_pio_client *client, void *param) ++static int rp1_pio_sm_xfer_data32(struct rp1_pio_client *client, void *param) + { +- struct rp1_pio_sm_xfer_data_args *args = param; ++ struct rp1_pio_sm_xfer_data32_args *args = param; + struct rp1_pio_device *pio = client->pio; + struct dma_info *dma; + +@@ -842,6 +842,19 @@ static int rp1_pio_sm_xfer_data(struct r + return rp1_pio_sm_rx_user(pio, dma, args->data, args->data_bytes); + } + ++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_sm_xfer_data32_args args32; ++ ++ args32.sm = args->sm; ++ args32.dir = args->dir; ++ args32.data_bytes = args->data_bytes; ++ args32.data = args->data; ++ ++ return rp1_pio_sm_xfer_data32(client, &args32); ++} ++ + struct handler_info { + const char *name; + int (*func)(struct rp1_pio_client *client, void *param); +@@ -849,6 +862,7 @@ struct handler_info { + } ioctl_handlers[] = { + HANDLER(SM_CONFIG_XFER, sm_config_xfer), + HANDLER(SM_XFER_DATA, sm_xfer_data), ++ HANDLER(SM_XFER_DATA32, sm_xfer_data32), + + HANDLER(CAN_ADD_PROGRAM, can_add_program), + HANDLER(ADD_PROGRAM, add_program), +@@ -1032,13 +1046,23 @@ struct rp1_pio_sm_xfer_data_args_compat + compat_uptr_t data; + }; + ++struct rp1_pio_sm_xfer_data32_args_compat { ++ uint16_t sm; ++ uint16_t dir; ++ uint32_t data_bytes; ++ compat_uptr_t data; ++}; ++ + struct rp1_access_hw_args_compat { + uint32_t addr; + uint32_t len; + compat_uptr_t data; + }; + +-#define PIO_IOC_SM_XFER_DATA_COMPAT _IOW(PIO_IOC_MAGIC, 1, struct rp1_pio_sm_xfer_data_args_compat) ++#define PIO_IOC_SM_XFER_DATA_COMPAT \ ++ _IOW(PIO_IOC_MAGIC, 1, struct rp1_pio_sm_xfer_data_args_compat) ++#define PIO_IOC_SM_XFER_DATA32_COMPAT \ ++ _IOW(PIO_IOC_MAGIC, 2, struct rp1_pio_sm_xfer_data32_args_compat) + #define PIO_IOC_READ_HW_COMPAT _IOW(PIO_IOC_MAGIC, 8, struct rp1_access_hw_args_compat) + #define PIO_IOC_WRITE_HW_COMPAT _IOW(PIO_IOC_MAGIC, 9, struct rp1_access_hw_args_compat) + +@@ -1061,6 +1085,19 @@ static long rp1_pio_compat_ioctl(struct + param.data = compat_ptr(compat_param.data); + return rp1_pio_sm_xfer_data(client, ¶m); + } ++ case PIO_IOC_SM_XFER_DATA32_COMPAT: ++ { ++ struct rp1_pio_sm_xfer_data32_args_compat compat_param; ++ struct rp1_pio_sm_xfer_data32_args param; ++ ++ if (copy_from_user(&compat_param, compat_ptr(ioctl_param), sizeof(compat_param))) ++ return -EFAULT; ++ param.sm = compat_param.sm; ++ param.dir = compat_param.dir; ++ param.data_bytes = compat_param.data_bytes; ++ param.data = compat_ptr(compat_param.data); ++ return rp1_pio_sm_xfer_data32(client, ¶m); ++ } + + case PIO_IOC_READ_HW_COMPAT: + case PIO_IOC_WRITE_HW_COMPAT: +--- a/include/uapi/misc/rp1_pio_if.h ++++ b/include/uapi/misc/rp1_pio_if.h +@@ -167,6 +167,13 @@ struct rp1_pio_sm_xfer_data_args { + void *data; + }; + ++struct rp1_pio_sm_xfer_data32_args { ++ uint16_t sm; ++ uint16_t dir; ++ uint32_t data_bytes; ++ void *data; ++}; ++ + struct rp1_access_hw_args { + uint32_t addr; + uint32_t len; +@@ -177,6 +184,7 @@ 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) ++#define PIO_IOC_SM_XFER_DATA32 _IOW(PIO_IOC_MAGIC, 2, struct rp1_pio_sm_xfer_data32_args) + + #define PIO_IOC_READ_HW _IOW(PIO_IOC_MAGIC, 8, struct rp1_access_hw_args) + #define PIO_IOC_WRITE_HW _IOW(PIO_IOC_MAGIC, 9, struct rp1_access_hw_args) diff --git a/target/linux/bcm27xx/patches-6.6/950-1471-fixup-misc-Add-RP1-PIO-driver.patch b/target/linux/bcm27xx/patches-6.6/950-1471-fixup-misc-Add-RP1-PIO-driver.patch new file mode 100644 index 0000000000..696c0d9186 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1471-fixup-misc-Add-RP1-PIO-driver.patch @@ -0,0 +1,24 @@ +From cd26850713088942ca4f9a248a8bed1f0504a58f Mon Sep 17 00:00:00 2001 +From: Phil Elwell +Date: Thu, 19 Dec 2024 15:11:40 +0000 +Subject: [PATCH 1471/1482] fixup! misc: Add RP1 PIO driver + +Change the Kconfig dependencies so that RP1_PIO depends on FIRMWARE_RP1, +rather than selecting it. + +Signed-off-by: Phil Elwell +--- + drivers/misc/Kconfig | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -19,7 +19,7 @@ config BCM2835_SMI + + config RP1_PIO + tristate "Raspberry Pi RP1 PIO driver" +- select FIRMWARE_RP1 ++ depends on FIRMWARE_RP1 || COMPILE_TEST + default n + help + Driver providing control of the Raspberry Pi PIO block, as found in diff --git a/target/linux/bcm27xx/patches-6.6/950-1473-misc-rp1-pio-More-logical-probe-sequence.patch b/target/linux/bcm27xx/patches-6.6/950-1473-misc-rp1-pio-More-logical-probe-sequence.patch new file mode 100644 index 0000000000..3204997b6e --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1473-misc-rp1-pio-More-logical-probe-sequence.patch @@ -0,0 +1,81 @@ +From 468b525d45a726e4ba704b33c4eba53de47ac684 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +Date: Thu, 5 Dec 2024 16:03:39 +0000 +Subject: [PATCH 1473/1482] misc: rp1-pio: More logical probe sequence + +Sort the probe function initialisation into a more logical order. + +Signed-off-by: Phil Elwell +--- + drivers/misc/rp1-pio.c | 31 +++++++++++++++---------------- + 1 file changed, 15 insertions(+), 16 deletions(-) + +--- a/drivers/misc/rp1-pio.c ++++ b/drivers/misc/rp1-pio.c +@@ -1153,6 +1153,10 @@ static int rp1_pio_probe(struct platform + return -EINVAL; + } + ++ pdev->id = of_alias_get_id(pdev->dev.of_node, "pio"); ++ if (pdev->id < 0) ++ return dev_err_probe(dev, pdev->id, "alias is missing\n"); ++ + fw = devm_rp1_firmware_get(dev, dev->of_node); + if (IS_ERR(fw)) + return PTR_ERR(fw); +@@ -1185,31 +1189,26 @@ static int rp1_pio_probe(struct platform + 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; ++ goto out_unregister; + } +- pdev->id = of_alias_get_id(pdev->dev.of_node, "pio"); +- if (pdev->id < 0) { +- dev_err(dev, "alias is missing\n"); +- return -EINVAL; ++ ++ cdev_init(&pio->cdev, &rp1_pio_fops); ++ ret = cdev_add(&pio->cdev, pio->dev_num, 1); ++ if (ret) { ++ dev_err(dev, "cdev_add failed (err %d)\n", ret); + goto out_class_destroy; + } ++ + sprintf(dev_name, "pio%d", pdev->id); + cdev = device_create(pio->dev_class, NULL, pio->dev_num, NULL, dev_name); + if (IS_ERR(cdev)) { + ret = PTR_ERR(cdev); + dev_err(dev, "%s: device_create failed (err %d)\n", __func__, ret); +- goto out_class_destroy; ++ goto out_cdev_del; + } + + g_pio = pio; +@@ -1217,12 +1216,12 @@ static int rp1_pio_probe(struct platform + 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_class_destroy: ++ class_destroy(pio->dev_class); ++ + out_unregister: + unregister_chrdev_region(pio->dev_num, 1); + diff --git a/target/linux/bcm27xx/patches-6.6/950-1474-misc-rp1-pio-Convert-floats-to-24.8-fixed-point.patch b/target/linux/bcm27xx/patches-6.6/950-1474-misc-rp1-pio-Convert-floats-to-24.8-fixed-point.patch new file mode 100644 index 0000000000..ba997136bd --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1474-misc-rp1-pio-Convert-floats-to-24.8-fixed-point.patch @@ -0,0 +1,92 @@ +From 5c07ba20630a629399eaa6583457aca93ff74606 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +Date: Mon, 9 Dec 2024 09:58:29 +0000 +Subject: [PATCH 1474/1482] misc: rp1-pio: Convert floats to 24.8 fixed point + +Floating point arithmetic is not supported in the kernel, so use fixed +point instead. + +Signed-off-by: Phil Elwell +--- + include/linux/pio_rp1.h | 29 +++++++++++++++++++++-------- + 1 file changed, 21 insertions(+), 8 deletions(-) + +--- a/include/linux/pio_rp1.h ++++ b/include/linux/pio_rp1.h +@@ -171,6 +171,10 @@ enum gpio_drive_strength { + GPIO_DRIVE_STRENGTH_12MA = 3 + }; + ++struct fp24_8 { ++ uint32_t val; ++}; ++ + typedef rp1_pio_sm_config pio_sm_config; + + typedef struct rp1_pio_client *PIO; +@@ -218,6 +222,13 @@ 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 struct fp24_8 make_fp24_8(uint mul, uint div) ++{ ++ struct fp24_8 res = { .val = ((unsigned long long)mul << 8) / div }; ++ ++ return res; ++} ++ + static inline bool pio_can_add_program(struct rp1_pio_client *client, + const pio_program_t *program) + { +@@ -396,16 +407,18 @@ static inline int pio_sm_clear_fifos(str + return rp1_pio_sm_clear_fifos(client, &args); + } + +-static inline bool pio_calculate_clkdiv_from_float(float div, uint16_t *div_int, ++static inline bool pio_calculate_clkdiv_from_fp24_8(struct fp24_8 div, uint16_t *div_int, + uint8_t *div_frac) + { +- if (bad_params_if(NULL, div < 1 || div > 65536)) ++ uint inum = (div.val >> 8); ++ ++ if (bad_params_if(NULL, inum < 1 || inum > 65536)) + return false; +- *div_int = (uint16_t)div; ++ *div_int = (uint16_t)inum; + if (*div_int == 0) + *div_frac = 0; + else +- *div_frac = (uint8_t)((div - (float)*div_int) * (1u << 8u)); ++ *div_frac = div.val & 0xff; + return true; + } + +@@ -421,11 +434,11 @@ static inline int pio_sm_set_clkdiv_int_ + return rp1_pio_sm_set_clkdiv(client, &args); + } + +-static inline int pio_sm_set_clkdiv(struct rp1_pio_client *client, uint sm, float div) ++static inline int pio_sm_set_clkdiv(struct rp1_pio_client *client, uint sm, struct fp24_8 div) + { + struct rp1_pio_sm_set_clkdiv_args args = { .sm = sm }; + +- if (!pio_calculate_clkdiv_from_float(div, &args.div_int, &args.div_frac)) ++ if (!pio_calculate_clkdiv_from_fp24_8(div, &args.div_int, &args.div_frac)) + return -EINVAL; + return rp1_pio_sm_set_clkdiv(client, &args); + } +@@ -745,12 +758,12 @@ static inline void sm_config_set_clkdiv_ + (((uint)div_int) << PROC_PIO_SM0_CLKDIV_INT_LSB); + } + +-static inline void sm_config_set_clkdiv(pio_sm_config *c, float div) ++static inline void sm_config_set_clkdiv(pio_sm_config *c, struct fp24_8 div) + { + uint16_t div_int; + uint8_t div_frac; + +- pio_calculate_clkdiv_from_float(div, &div_int, &div_frac); ++ pio_calculate_clkdiv_from_fp24_8(div, &div_int, &div_frac); + sm_config_set_clkdiv_int_frac(c, div_int, div_frac); + } + diff --git a/target/linux/bcm27xx/patches-6.6/950-1475-misc-rp1-pio-Minor-cosmetic-tweaks.patch b/target/linux/bcm27xx/patches-6.6/950-1475-misc-rp1-pio-Minor-cosmetic-tweaks.patch new file mode 100644 index 0000000000..73cbacb328 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1475-misc-rp1-pio-Minor-cosmetic-tweaks.patch @@ -0,0 +1,42 @@ +From 75203c6641cfe47dfb817b095430021b0981ff47 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +Date: Tue, 10 Dec 2024 12:06:14 +0000 +Subject: [PATCH 1475/1482] misc: rp1-pio: Minor cosmetic tweaks + +No functional change. + +Signed-off-by: Phil Elwell +--- + drivers/misc/rp1-pio.c | 7 +++---- + 1 file changed, 3 insertions(+), 4 deletions(-) + +--- a/drivers/misc/rp1-pio.c ++++ b/drivers/misc/rp1-pio.c +@@ -683,7 +683,7 @@ err_dma_free: + } + + static int rp1_pio_sm_tx_user(struct rp1_pio_device *pio, struct dma_info *dma, +- const void __user *userbuf, size_t bytes) ++ const void __user *userbuf, size_t bytes) + { + struct platform_device *pdev = pio->pdev; + struct dma_async_tx_descriptor *desc; +@@ -757,7 +757,7 @@ static int rp1_pio_sm_tx_user(struct rp1 + } + + static int rp1_pio_sm_rx_user(struct rp1_pio_device *pio, struct dma_info *dma, +- void __user *userbuf, size_t bytes) ++ void __user *userbuf, size_t bytes) + { + struct platform_device *pdev = pio->pdev; + struct dma_async_tx_descriptor *desc; +@@ -809,8 +809,7 @@ static int rp1_pio_sm_rx_user(struct rp1 + desc->callback = rp1_pio_sm_dma_callback; + desc->callback_param = dma; + +- // Submit the buffer - the callback will kick the semaphore +- ++ /* Submit the buffer - the callback will kick the semaphore */ + ret = dmaengine_submit(desc); + if (ret < 0) + break; diff --git a/target/linux/bcm27xx/patches-6.6/950-1476-misc-rp1-pio-Add-in-kernel-DMA-support.patch b/target/linux/bcm27xx/patches-6.6/950-1476-misc-rp1-pio-Add-in-kernel-DMA-support.patch new file mode 100644 index 0000000000..05c6de2b01 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1476-misc-rp1-pio-Add-in-kernel-DMA-support.patch @@ -0,0 +1,508 @@ +From fddd3e9318dbf01fb763b6880021abc558fce8e6 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +Date: Thu, 12 Dec 2024 17:09:27 +0000 +Subject: [PATCH 1476/1482] misc: rp1-pio: Add in-kernel DMA support + +Add kernel-facing implementations of pio_sm_config_xfer and +pio_xm_xfer_data. + +Signed-off-by: Phil Elwell +--- + drivers/misc/rp1-pio.c | 208 ++++++++++++++++++++++++++++++---------- + include/linux/pio_rp1.h | 60 ++++++++++-- + 2 files changed, 210 insertions(+), 58 deletions(-) + +--- a/drivers/misc/rp1-pio.c ++++ b/drivers/misc/rp1-pio.c +@@ -61,9 +61,15 @@ + #define DMA_BOUNCE_BUFFER_SIZE 0x1000 + #define DMA_BOUNCE_BUFFER_COUNT 4 + ++struct dma_xfer_state { ++ struct dma_info *dma; ++ void (*callback)(void *param); ++ void *callback_param; ++}; ++ + struct dma_buf_info { + void *buf; +- dma_addr_t phys; ++ dma_addr_t dma_addr; + struct scatterlist sgl; + }; + +@@ -572,21 +578,34 @@ static void rp1_pio_sm_dma_callback(void + up(&dma->buf_sem); + } + ++static void rp1_pio_sm_kernel_dma_callback(void *param) ++{ ++ struct dma_xfer_state *dxs = param; ++ ++ dxs->dma->tail_idx++; ++ up(&dxs->dma->buf_sem); ++ ++ dxs->callback(dxs->callback_param); ++ ++ kfree(dxs); ++} ++ + static void rp1_pio_sm_dma_free(struct device *dev, struct dma_info *dma) + { + dmaengine_terminate_all(dma->chan); + while (dma->buf_count > 0) { + dma->buf_count--; + dma_free_coherent(dev, ROUND_UP(dma->buf_size, PAGE_SIZE), +- dma->bufs[dma->buf_count].buf, dma->bufs[dma->buf_count].phys); ++ dma->bufs[dma->buf_count].buf, ++ dma->bufs[dma->buf_count].dma_addr); + } + + dma_release_channel(dma->chan); + } + +-static int rp1_pio_sm_config_xfer(struct rp1_pio_client *client, void *param) ++static int rp1_pio_sm_config_xfer_internal(struct rp1_pio_client *client, uint sm, uint dir, ++ uint buf_size, uint buf_count) + { +- struct rp1_pio_sm_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; +@@ -596,17 +615,18 @@ static int rp1_pio_sm_config_xfer(struct + 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) ++ if (sm >= RP1_PIO_SMS_COUNT || dir >= RP1_PIO_DIR_COUNT) ++ return -EINVAL; ++ if ((buf_count || buf_size) && ++ (!buf_size || (buf_size & 3) || ++ !buf_count || buf_count > DMA_BOUNCE_BUFFER_COUNT)) + return -EINVAL; + +- dma_mask = 1 << (args->sm * 2 + args->dir); ++ dma_mask = 1 << (sm * 2 + dir); + +- dma = &pio->dma_configs[args->sm][args->dir]; ++ dma = &pio->dma_configs[sm][dir]; + + spin_lock(&pio->lock); + if (pio->claimed_dmas & dma_mask) +@@ -615,16 +635,16 @@ static int rp1_pio_sm_config_xfer(struct + client->claimed_dmas |= dma_mask; + spin_unlock(&pio->lock); + +- dma->buf_size = args->buf_size; ++ dma->buf_size = buf_size; + /* Round up the allocations */ +- buf_size = ROUND_UP(args->buf_size, PAGE_SIZE); ++ buf_size = ROUND_UP(buf_size, PAGE_SIZE); + sema_init(&dma->buf_sem, 0); + + /* Allocate and configure a DMA channel */ + /* Careful - each SM FIFO has its own DREQ value */ +- chan_name[0] = (args->dir == RP1_PIO_DIR_TO_SM) ? 't' : 'r'; ++ chan_name[0] = (dir == RP1_PIO_DIR_TO_SM) ? 't' : 'r'; + chan_name[1] = 'x'; +- chan_name[2] = '0' + args->sm; ++ chan_name[2] = '0' + sm; + chan_name[3] = '\0'; + + dma->chan = dma_request_chan(dev, chan_name); +@@ -632,37 +652,37 @@ static int rp1_pio_sm_config_xfer(struct + return PTR_ERR(dma->chan); + + /* Alloc and map bounce buffers */ +- for (dma->buf_count = 0; dma->buf_count < args->buf_count; dma->buf_count++) { ++ for (dma->buf_count = 0; dma->buf_count < buf_count; dma->buf_count++) { + struct dma_buf_info *dbi = &dma->bufs[dma->buf_count]; + + dbi->buf = dma_alloc_coherent(dma->chan->device->dev, buf_size, +- &dbi->phys, GFP_KERNEL); ++ &dbi->dma_addr, GFP_KERNEL); + if (!dbi->buf) { + ret = -ENOMEM; + goto err_dma_free; + } + sg_init_table(&dbi->sgl, 1); +- sg_dma_address(&dbi->sgl) = dbi->phys; ++ sg_dma_address(&dbi->sgl) = dbi->dma_addr; + } + + 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; ++ fifo_addr += sm * (RP1_PIO_FIFO_TX1 - RP1_PIO_FIFO_TX0); ++ fifo_addr += (dir == RP1_PIO_DIR_TO_SM) ? RP1_PIO_FIFO_TX0 : RP1_PIO_FIFO_RX0; + + config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + config.src_addr = fifo_addr; + config.dst_addr = fifo_addr; +- config.direction = (args->dir == RP1_PIO_DIR_TO_SM) ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; ++ config.direction = (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.sm = sm; ++ set_dmactrl_args.is_tx = (dir == RP1_PIO_DIR_TO_SM); + set_dmactrl_args.ctrl = RP1_PIO_DMACTRL_DEFAULT; +- if (args->dir == RP1_PIO_DIR_FROM_SM) ++ if (dir == RP1_PIO_DIR_FROM_SM) + set_dmactrl_args.ctrl = (RP1_PIO_DMACTRL_DEFAULT & ~0x1f) | 1; + + ret = rp1_pio_sm_set_dmactrl(client, &set_dmactrl_args); +@@ -682,6 +702,14 @@ err_dma_free: + return ret; + } + ++static int rp1_pio_sm_config_xfer_user(struct rp1_pio_client *client, void *param) ++{ ++ struct rp1_pio_sm_config_xfer_args *args = param; ++ ++ return rp1_pio_sm_config_xfer_internal(client, args->sm, args->dir, ++ args->buf_size, args->buf_count); ++} ++ + static int rp1_pio_sm_tx_user(struct rp1_pio_device *pio, struct dma_info *dma, + const void __user *userbuf, size_t bytes) + { +@@ -723,7 +751,7 @@ static int rp1_pio_sm_tx_user(struct rp1 + DMA_PREP_INTERRUPT | DMA_CTRL_ACK | + DMA_PREP_FENCE); + if (!desc) { +- dev_err(dev, "DMA preparation failedzn"); ++ dev_err(dev, "DMA preparation failed\n"); + ret = -EIO; + break; + } +@@ -779,7 +807,7 @@ static int rp1_pio_sm_rx_user(struct rp1 + 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"); ++ dev_err(dev, "DMA wait timed out\n"); + ret = -ETIMEDOUT; + break; + } +@@ -801,7 +829,7 @@ static int rp1_pio_sm_rx_user(struct rp1 + DMA_PREP_INTERRUPT | DMA_CTRL_ACK | + DMA_PREP_FENCE); + if (!desc) { +- dev_err(dev, "DMA preparation failed"); ++ dev_err(dev, "DMA preparation failed\n"); + ret = -EIO; + break; + } +@@ -823,7 +851,7 @@ static int rp1_pio_sm_rx_user(struct rp1 + return ret; + } + +-static int rp1_pio_sm_xfer_data32(struct rp1_pio_client *client, void *param) ++static int rp1_pio_sm_xfer_data32_user(struct rp1_pio_client *client, void *param) + { + struct rp1_pio_sm_xfer_data32_args *args = param; + struct rp1_pio_device *pio = client->pio; +@@ -841,7 +869,7 @@ static int rp1_pio_sm_xfer_data32(struct + return rp1_pio_sm_rx_user(pio, dma, args->data, args->data_bytes); + } + +-static int rp1_pio_sm_xfer_data(struct rp1_pio_client *client, void *param) ++static int rp1_pio_sm_xfer_data_user(struct rp1_pio_client *client, void *param) + { + struct rp1_pio_sm_xfer_data_args *args = param; + struct rp1_pio_sm_xfer_data32_args args32; +@@ -851,17 +879,97 @@ static int rp1_pio_sm_xfer_data(struct r + args32.data_bytes = args->data_bytes; + args32.data = args->data; + +- return rp1_pio_sm_xfer_data32(client, &args32); ++ return rp1_pio_sm_xfer_data32_user(client, &args32); ++} ++ ++int rp1_pio_sm_config_xfer(struct rp1_pio_client *client, uint sm, uint dir, ++ uint buf_size, uint buf_count) ++{ ++ return rp1_pio_sm_config_xfer_internal(client, sm, dir, buf_size, buf_count); ++} ++EXPORT_SYMBOL_GPL(rp1_pio_sm_config_xfer); ++ ++int rp1_pio_sm_xfer_data(struct rp1_pio_client *client, uint sm, uint dir, ++ uint data_bytes, void *data, dma_addr_t dma_addr, ++ void (*callback)(void *param), void *param) ++{ ++ struct rp1_pio_device *pio = client->pio; ++ struct platform_device *pdev = pio->pdev; ++ struct dma_async_tx_descriptor *desc; ++ struct dma_xfer_state *dxs = NULL; ++ struct device *dev = &pdev->dev; ++ struct dma_buf_info *dbi = NULL; ++ struct scatterlist sg; ++ struct dma_info *dma; ++ int ret = 0; ++ ++ if (sm >= RP1_PIO_SMS_COUNT || dir >= RP1_PIO_DIR_COUNT) ++ return -EINVAL; ++ ++ dma = &pio->dma_configs[sm][dir]; ++ ++ if (!dma_addr) { ++ dxs = kmalloc(sizeof(*dxs), GFP_KERNEL); ++ dxs->dma = dma; ++ dxs->callback = callback; ++ dxs->callback_param = param; ++ callback = rp1_pio_sm_kernel_dma_callback; ++ param = dxs; ++ ++ if (!dma->buf_count || data_bytes > dma->buf_size) ++ return -EINVAL; ++ ++ /* Grab a dma buffer */ ++ if (dma->head_idx - dma->tail_idx == dma->buf_count) { ++ if (down_timeout(&dma->buf_sem, msecs_to_jiffies(1000))) { ++ dev_err(dev, "DMA wait timed out\n"); ++ return -ETIMEDOUT; ++ } ++ } ++ ++ dbi = &dma->bufs[dma->head_idx % dma->buf_count]; ++ dma_addr = dbi->dma_addr; ++ ++ if (dir == PIO_DIR_TO_SM) ++ memcpy(dbi->buf, data, data_bytes); ++ } ++ ++ sg_init_table(&sg, 1); ++ sg_dma_address(&sg) = dma_addr; ++ sg_dma_len(&sg) = data_bytes; ++ ++ desc = dmaengine_prep_slave_sg(dma->chan, &sg, 1, ++ (dir == PIO_DIR_TO_SM) ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, ++ DMA_PREP_INTERRUPT | DMA_CTRL_ACK | ++ DMA_PREP_FENCE); ++ if (!desc) { ++ dev_err(dev, "DMA preparation failed\n"); ++ return -EIO; ++ } ++ ++ desc->callback = callback; ++ desc->callback_param = param; ++ ++ ret = dmaengine_submit(desc); ++ if (ret < 0) { ++ dev_err(dev, "dmaengine_submit failed (%d)\n", ret); ++ return ret; ++ } ++ ++ dma_async_issue_pending(dma->chan); ++ ++ return 0; + } ++EXPORT_SYMBOL_GPL(rp1_pio_sm_xfer_data); + + struct handler_info { + const char *name; + int (*func)(struct rp1_pio_client *client, void *param); + int argsize; + } ioctl_handlers[] = { +- HANDLER(SM_CONFIG_XFER, sm_config_xfer), +- HANDLER(SM_XFER_DATA, sm_xfer_data), +- HANDLER(SM_XFER_DATA32, sm_xfer_data32), ++ HANDLER(SM_CONFIG_XFER, sm_config_xfer_user), ++ HANDLER(SM_XFER_DATA, sm_xfer_data_user), ++ HANDLER(SM_XFER_DATA32, sm_xfer_data32_user), + + HANDLER(CAN_ADD_PROGRAM, can_add_program), + HANDLER(ADD_PROGRAM, add_program), +@@ -902,7 +1010,7 @@ struct handler_info { + HANDLER(WRITE_HW, write_hw), + }; + +-struct rp1_pio_client *pio_open(void) ++struct rp1_pio_client *rp1_pio_open(void) + { + struct rp1_pio_client *client; + +@@ -914,9 +1022,9 @@ struct rp1_pio_client *pio_open(void) + + return client; + } +-EXPORT_SYMBOL_GPL(pio_open); ++EXPORT_SYMBOL_GPL(rp1_pio_open); + +-void pio_close(struct rp1_pio_client *client) ++void rp1_pio_close(struct rp1_pio_client *client) + { + struct rp1_pio_device *pio = client->pio; + uint claimed_dmas = client->claimed_dmas; +@@ -958,31 +1066,31 @@ void pio_close(struct rp1_pio_client *cl + + kfree(client); + } +-EXPORT_SYMBOL_GPL(pio_close); ++EXPORT_SYMBOL_GPL(rp1_pio_close); + +-void pio_set_error(struct rp1_pio_client *client, int err) ++void rp1_pio_set_error(struct rp1_pio_client *client, int err) + { + client->error = err; + } +-EXPORT_SYMBOL_GPL(pio_set_error); ++EXPORT_SYMBOL_GPL(rp1_pio_set_error); + +-int pio_get_error(const struct rp1_pio_client *client) ++int rp1_pio_get_error(const struct rp1_pio_client *client) + { + return client->error; + } +-EXPORT_SYMBOL_GPL(pio_get_error); ++EXPORT_SYMBOL_GPL(rp1_pio_get_error); + +-void pio_clear_error(struct rp1_pio_client *client) ++void rp1_pio_clear_error(struct rp1_pio_client *client) + { + client->error = 0; + } +-EXPORT_SYMBOL_GPL(pio_clear_error); ++EXPORT_SYMBOL_GPL(rp1_pio_clear_error); + +-static int rp1_pio_open(struct inode *inode, struct file *filp) ++static int rp1_pio_file_open(struct inode *inode, struct file *filp) + { + struct rp1_pio_client *client; + +- client = pio_open(); ++ client = rp1_pio_open(); + if (IS_ERR(client)) + return PTR_ERR(client); + +@@ -991,11 +1099,11 @@ static int rp1_pio_open(struct inode *in + return 0; + } + +-static int rp1_pio_release(struct inode *inode, struct file *filp) ++static int rp1_pio_file_release(struct inode *inode, struct file *filp) + { + struct rp1_pio_client *client = filp->private_data; + +- pio_close(client); ++ rp1_pio_close(client); + + return 0; + } +@@ -1082,7 +1190,7 @@ static long rp1_pio_compat_ioctl(struct + param.dir = compat_param.dir; + param.data_bytes = compat_param.data_bytes; + param.data = compat_ptr(compat_param.data); +- return rp1_pio_sm_xfer_data(client, ¶m); ++ return rp1_pio_sm_xfer_data_user(client, ¶m); + } + case PIO_IOC_SM_XFER_DATA32_COMPAT: + { +@@ -1095,7 +1203,7 @@ static long rp1_pio_compat_ioctl(struct + param.dir = compat_param.dir; + param.data_bytes = compat_param.data_bytes; + param.data = compat_ptr(compat_param.data); +- return rp1_pio_sm_xfer_data32(client, ¶m); ++ return rp1_pio_sm_xfer_data32_user(client, ¶m); + } + + case PIO_IOC_READ_HW_COMPAT: +@@ -1124,8 +1232,8 @@ static long rp1_pio_compat_ioctl(struct + + const struct file_operations rp1_pio_fops = { + .owner = THIS_MODULE, +- .open = rp1_pio_open, +- .release = rp1_pio_release, ++ .open = rp1_pio_file_open, ++ .release = rp1_pio_file_release, + .unlocked_ioctl = rp1_pio_ioctl, + .compat_ioctl = rp1_pio_compat_ioctl, + }; +--- a/include/linux/pio_rp1.h ++++ b/include/linux/pio_rp1.h +@@ -179,9 +179,17 @@ 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(const struct rp1_pio_client *client); +-void pio_clear_error(struct rp1_pio_client *client); ++int rp1_pio_init(void); ++PIO rp1_pio_open(void); ++void rp1_pio_close(struct rp1_pio_client *client); ++void rp1_pio_set_error(struct rp1_pio_client *client, int err); ++int rp1_pio_get_error(const struct rp1_pio_client *client); ++void rp1_pio_clear_error(struct rp1_pio_client *client); ++int rp1_pio_sm_config_xfer(struct rp1_pio_client *client, uint sm, uint dir, ++ uint buf_size, uint buf_count); ++int rp1_pio_sm_xfer_data(struct rp1_pio_client *client, uint sm, uint dir, ++ uint data_bytes, void *data, dma_addr_t dma_addr, ++ void (*callback)(void *param), void *param); + + int rp1_pio_can_add_program(struct rp1_pio_client *client, void *param); + int rp1_pio_add_program(struct rp1_pio_client *client, void *param); +@@ -215,12 +223,48 @@ int rp1_pio_gpio_set_oeover(struct rp1_p + 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); ++static inline int pio_init(void) ++{ ++ return rp1_pio_init(); ++} ++ ++static inline struct rp1_pio_client *pio_open(void) ++{ ++ return rp1_pio_open(); ++} ++ ++static inline void pio_close(struct rp1_pio_client *client) ++{ ++ rp1_pio_close(client); ++} ++ ++static inline void pio_set_error(struct rp1_pio_client *client, int err) ++{ ++ rp1_pio_set_error(client, err); ++} + +-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 int pio_get_error(const struct rp1_pio_client *client) ++{ ++ return rp1_pio_get_error(client); ++} ++ ++static inline void pio_clear_error(struct rp1_pio_client *client) ++{ ++ rp1_pio_clear_error(client); ++} ++ ++static inline int pio_sm_config_xfer(struct rp1_pio_client *client, uint sm, uint dir, ++ uint buf_size, uint buf_count) ++{ ++ return rp1_pio_sm_config_xfer(client, sm, dir, buf_size, buf_count); ++} ++ ++static inline int pio_sm_xfer_data(struct rp1_pio_client *client, uint sm, uint dir, ++ uint data_bytes, void *data, dma_addr_t dma_addr, ++ void (*callback)(void *param), void *param) ++{ ++ return rp1_pio_sm_xfer_data(client, sm, dir, data_bytes, data, dma_addr, callback, param); ++} + + static inline struct fp24_8 make_fp24_8(uint mul, uint div) + { diff --git a/target/linux/bcm27xx/patches-6.6/950-1477-misc-Add-ws2812-pio-rp1-driver.patch b/target/linux/bcm27xx/patches-6.6/950-1477-misc-Add-ws2812-pio-rp1-driver.patch new file mode 100644 index 0000000000..469a8a9b91 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1477-misc-Add-ws2812-pio-rp1-driver.patch @@ -0,0 +1,568 @@ +From d6d83ad3d9a3a594909a1ad1c82b735ab711cd12 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +Date: Tue, 3 Dec 2024 16:09:30 +0000 +Subject: [PATCH 1477/1482] 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, +where is the instance number. The number of LEDS should be set +in the DT overlay, as should whether it is RGB or RGBW, and the default +brightness. + +Write data to the /dev/* entry in a 4 bytes-per-pixel format in RGBW +order: + + RR GG BB WW RR GG BB WW ... + +The white values are ignored unless the rgbw flag is set for the device. + +To change the brightness, write a single byte to offset 0, 255 being +full brightness and 0 being off. + +Signed-off-by: Phil Elwell +--- + drivers/misc/Kconfig | 10 + + drivers/misc/Makefile | 1 + + drivers/misc/ws2812-pio-rp1.c | 507 ++++++++++++++++++++++++++++++++++ + 3 files changed, 518 insertions(+) + create mode 100644 drivers/misc/ws2812-pio-rp1.c + +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -25,6 +25,16 @@ config RP1_PIO + Driver providing control of the Raspberry Pi PIO block, as found in + RP1. + ++config WS2812_PIO_RP1 ++ tristate "Raspberry Pi PIO-base WS2812 driver" ++ depends on RP1_PIO || COMPILE_TEST ++ default n ++ help ++ Driver for the WS2812 (NeoPixel) LEDs using the RP1 PIO hardware. ++ The driver creates a character device to which rgbw pixels may be ++ written. Single-byte writes to offset 0 set the brightness at ++ runtime. ++ + config AD525X_DPOT + tristate "Analog Devices Digital Potentiometers" + depends on (I2C || SPI) && SYSFS +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -19,6 +19,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_WS2812_PIO_RP1) += ws2812-pio-rp1.o + obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o + obj-$(CONFIG_SENSORS_APDS990X) += apds990x.o + obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o +--- /dev/null ++++ b/drivers/misc/ws2812-pio-rp1.c +@@ -0,0 +1,507 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Raspberry Pi PIO-based WS2812 driver ++ * ++ * Copyright (C) 2014-2024 Raspberry Pi Ltd. ++ * ++ * Author: Phil Elwell (phil@raspberrypi.com) ++ * ++ * Based on the ws2812 driver by Gordon Hollingworth ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DRIVER_NAME "ws2812-pio-rp1" ++#define MAX_INSTANCES 4 ++ ++#define RESET_US 50 ++#define PIXEL_BYTES 4 ++ ++struct ws2812_pio_rp1_state { ++ struct device *dev; ++ struct gpio_desc *gpiod; ++ struct gpio_desc *power_gpiod; ++ uint gpio; ++ PIO pio; ++ uint sm; ++ uint offset; ++ ++ u8 *buffer; ++ u8 *pixbuf; ++ u32 pixbuf_size; ++ u32 write_end; ++ ++ u8 brightness; ++ u32 invert; ++ u32 num_leds; ++ u32 xfer_end_us; ++ bool is_rgbw; ++ struct delayed_work deferred_work; ++ ++ struct completion dma_completion; ++ struct cdev cdev; ++ dev_t dev_num; ++ const char *dev_name; ++}; ++ ++static DEFINE_MUTEX(ws2812_pio_mutex); ++static DEFINE_IDA(ws2812_pio_ida); ++static long ws2812_pio_ref_count; ++static struct class *ws2812_pio_class; ++static dev_t ws2812_pio_dev_num; ++/* ++ * WS2812B gamma correction ++ * GammaE=255*(res/255).^(1/.45) ++ * From: http://rgb-123.com/ws2812-color-output/ ++ */ ++ ++static const u8 ws2812_gamma[] = { ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, ++ 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, ++ 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, ++ 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, ++ 19, 19, 20, 21, 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 27, 28, ++ 29, 29, 30, 31, 31, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40, ++ 40, 41, 42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54, ++ 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, ++ 71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 88, 89, ++ 90, 91, 93, 94, 95, 96, 98, 99, 100, 102, 103, 104, 106, 107, 109, 110, ++ 111, 113, 114, 116, 117, 119, 120, 121, 123, 124, 126, 128, 129, 131, 132, 134, ++ 135, 137, 138, 140, 142, 143, 145, 146, 148, 150, 151, 153, 155, 157, 158, 160, ++ 162, 163, 165, 167, 169, 170, 172, 174, 176, 178, 179, 181, 183, 185, 187, 189, ++ 191, 193, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, ++ 222, 224, 227, 229, 231, 233, 235, 237, 239, 241, 244, 246, 248, 250, 252, 255 ++}; ++ ++// ------ // ++// ws2812 // ++// ------ // ++ ++#define ws2812_wrap_target 0 ++#define ws2812_wrap 3 ++ ++#define ws2812_T1 3 ++#define ws2812_T2 4 ++#define ws2812_T3 3 ++ ++static const uint16_t ws2812_program_instructions[] = { ++ // .wrap_target ++ 0x6221, // 0: out x, 1 side 0 [2] ++ 0x1223, // 1: jmp !x, 3 side 1 [2] ++ 0x1300, // 2: jmp 0 side 1 [3] ++ 0xa342, // 3: nop side 0 [3] ++ // .wrap ++}; ++ ++static const struct pio_program ws2812_program = { ++ .instructions = ws2812_program_instructions, ++ .length = 4, ++ .origin = -1, ++}; ++ ++static inline pio_sm_config ws2812_program_get_default_config(uint offset) ++{ ++ pio_sm_config c = pio_get_default_sm_config(); ++ ++ sm_config_set_wrap(&c, offset + ws2812_wrap_target, offset + ws2812_wrap); ++ sm_config_set_sideset(&c, 1, false, false); ++ return c; ++} ++ ++static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, uint freq, ++ bool rgbw) ++{ ++ int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3; ++ struct fp24_8 div; ++ pio_sm_config c; ++ ++ pio_gpio_init(pio, pin); ++ pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); ++ c = ws2812_program_get_default_config(offset); ++ sm_config_set_sideset_pins(&c, pin); ++ sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24); ++ sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); ++ div = make_fp24_8(clock_get_hz(clk_sys), freq * cycles_per_bit); ++ sm_config_set_clkdiv(&c, div); ++ pio_sm_init(pio, sm, offset, &c); ++ pio_sm_set_enabled(pio, sm, true); ++} ++ ++static uint8_t ws2812_apply_gamma(uint8_t brightness, uint8_t val) ++{ ++ int bright; ++ ++ if (!val) ++ return 0; ++ bright = (val * brightness) / 255; ++ return ws2812_gamma[bright]; ++} ++ ++static inline uint8_t *rgbw_u32(const struct ws2812_pio_rp1_state *state, ++ uint8_t r, uint8_t g, uint8_t b, uint8_t w, uint8_t *p) ++{ ++ p[0] = ws2812_apply_gamma(state->brightness, w); ++ p[1] = ws2812_apply_gamma(state->brightness, b); ++ p[2] = ws2812_apply_gamma(state->brightness, r); ++ p[3] = ws2812_apply_gamma(state->brightness, g); ++ return p + 4; ++} ++ ++static void ws2812_dma_complete(void *param) ++{ ++ struct ws2812_pio_rp1_state *state = param; ++ ++ complete(&state->dma_completion); ++} ++ ++static void ws2812_update_leds(struct ws2812_pio_rp1_state *state, uint length) ++{ ++ init_completion(&state->dma_completion); ++ if (!pio_sm_xfer_data(state->pio, state->sm, PIO_DIR_TO_SM, length, state->buffer, 0, ++ (void (*)(void *))ws2812_dma_complete, state)) { ++ wait_for_completion(&state->dma_completion); ++ usleep_range(RESET_US, RESET_US + 100); ++ } ++} ++ ++static void ws2812_clear_leds(struct ws2812_pio_rp1_state *state) ++{ ++ uint8_t *p_buffer; ++ uint length; ++ int i; ++ ++ p_buffer = state->buffer; ++ for (i = 0; i < state->num_leds; i++) ++ p_buffer = rgbw_u32(state, 0, 0, 0, 0, p_buffer); ++ ++ length = (void *)p_buffer - (void *)state->buffer; ++ ++ ws2812_update_leds(state, length); ++} ++ ++/* ++ * Function to write the RGB buffer to the WS2812 leds, the input buffer ++ * contains a sequence of up to num_leds RGB32 integers, these are then ++ * gamma-corrected before being sent to the PIO state machine. ++ */ ++ ++static ssize_t ws2812_pio_rp1_write(struct file *filp, const char __user *buf, size_t count, ++ loff_t *ppos) ++{ ++ struct ws2812_pio_rp1_state *state; ++ uint32_t pixbuf_size; ++ unsigned long delay; ++ loff_t pos = *ppos; ++ int err = 0; ++ ++ state = (struct ws2812_pio_rp1_state *)filp->private_data; ++ pixbuf_size = state->pixbuf_size; ++ ++ if (pos > pixbuf_size) ++ return -EFBIG; ++ ++ if (count > pixbuf_size) { ++ err = -EFBIG; ++ count = pixbuf_size; ++ } ++ ++ if (pos + count > pixbuf_size) { ++ if (!err) ++ err = -ENOSPC; ++ ++ count = pixbuf_size - pos; ++ } ++ ++ if (!pos && count == 1) { ++ if (copy_from_user(&state->brightness, buf, 1)) ++ return -EFAULT; ++ } else { ++ if (copy_from_user(state->pixbuf + pos, buf, count)) ++ return -EFAULT; ++ pos += count; ++ state->write_end = (u32)pos; ++ } ++ ++ *ppos = pos; ++ ++ delay = (state->write_end == pixbuf_size) ? 0 : HZ / 20; ++ schedule_delayed_work(&state->deferred_work, delay); ++ ++ return err ? err : count; ++} ++ ++static void ws2812_pio_rp1_deferred_work(struct work_struct *work) ++{ ++ struct ws2812_pio_rp1_state *state = ++ container_of(work, struct ws2812_pio_rp1_state, deferred_work.work); ++ uint8_t *p_buffer; ++ uint32_t *p_rgb; ++ int blank_bytes; ++ uint length; ++ int i; ++ ++ blank_bytes = state->pixbuf_size - state->write_end; ++ if (blank_bytes > 0) ++ memset(state->pixbuf + state->write_end, 0, blank_bytes); ++ ++ p_rgb = (uint32_t *)state->pixbuf; ++ p_buffer = state->buffer; ++ ++ for (i = 0; i < state->num_leds; i++) { ++ uint32_t rgbw_pix = *(p_rgb++); ++ ++ p_buffer = rgbw_u32(state, ++ (uint8_t)(rgbw_pix >> 0), ++ (uint8_t)(rgbw_pix >> 8), ++ (uint8_t)(rgbw_pix >> 16), ++ (uint8_t)(rgbw_pix >> 24), ++ p_buffer); ++ } ++ ++ length = (void *)p_buffer - (void *)state->buffer; ++ ++ ws2812_update_leds(state, length); ++} ++ ++static int ws2812_pio_rp1_open(struct inode *inode, struct file *file) ++{ ++ struct ws2812_pio_rp1_state *state; ++ ++ state = container_of(inode->i_cdev, struct ws2812_pio_rp1_state, cdev); ++ file->private_data = state; ++ ++ return 0; ++} ++ ++const struct file_operations ws2812_pio_rp1_fops = { ++ .owner = THIS_MODULE, ++ .write = ws2812_pio_rp1_write, ++ .open = ws2812_pio_rp1_open, ++}; ++ ++/* ++ * Probe function ++ */ ++static int ws2812_pio_rp1_probe(struct platform_device *pdev) ++{ ++ struct device_node *np = pdev->dev.of_node; ++ struct of_phandle_args of_args = { 0 }; ++ struct ws2812_pio_rp1_state *state; ++ struct device *dev = &pdev->dev; ++ struct device *char_dev; ++ const char *dev_name; ++ uint32_t brightness; ++ bool is_rp1; ++ int minor; ++ int ret; ++ ++ state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); ++ if (IS_ERR(state)) ++ return PTR_ERR(state); ++ ++ state->dev = dev; ++ ++ platform_set_drvdata(pdev, state); ++ ++ ret = of_property_read_u32(np, "rpi,num-leds", &state->num_leds); ++ if (ret) ++ return dev_err_probe(dev, ret, "Could not get num-leds\n"); ++ ++ brightness = 255; ++ of_property_read_u32(np, "rpi,brightness", &brightness); ++ state->brightness = min(brightness, 255); ++ ++ state->pixbuf_size = state->num_leds * PIXEL_BYTES; ++ ++ state->is_rgbw = of_property_read_bool(np, "rpi,rgbw"); ++ state->gpiod = devm_gpiod_get(dev, "leds", GPIOD_ASIS); ++ if (IS_ERR(state->gpiod)) ++ return dev_err_probe(dev, PTR_ERR(state->gpiod), ++ "Could not get a gpio\n"); ++ ++ /* This must be an RP1 GPIO in the first bank, and retrieve the offset. */ ++ /* Unfortunately I think this has to be done by parsing the gpios property */ ++ ++ /* This really shouldn't fail, given that we have a gpiod */ ++ if (of_parse_phandle_with_args(np, "leds-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"); ++ ++ state->gpio = of_args.args[0]; ++ ++ state->pixbuf = devm_kmalloc(dev, state->pixbuf_size, GFP_KERNEL); ++ if (state->pixbuf == NULL) ++ return -ENOMEM; ++ ++ state->buffer = devm_kmalloc(dev, state->num_leds * PIXEL_BYTES, GFP_KERNEL); ++ if (state->buffer == NULL) ++ return -ENOMEM; ++ ++ ret = of_property_read_string(np, "dev-name", &dev_name); ++ if (ret) { ++ pr_err("Failed to read 'dev-name' property\n"); ++ return ret; ++ } ++ ++ state->pio = pio_open(); ++ if (IS_ERR(state->pio)) ++ return dev_err_probe(dev, PTR_ERR(state->pio), ++ "Could not open PIO\n"); ++ ++ state->sm = pio_claim_unused_sm(state->pio, false); ++ if ((int)state->sm < 0) { ++ dev_err(dev, "No free PIO SM\n"); ++ ret = -EBUSY; ++ goto fail_pio; ++ } ++ ++ state->offset = pio_add_program(state->pio, &ws2812_program); ++ if (state->offset == PIO_ORIGIN_ANY) { ++ dev_err(dev, "Not enough PIO program space\n"); ++ ret = -EBUSY; ++ goto fail_pio; ++ } ++ ++ pio_sm_config_xfer(state->pio, state->sm, PIO_DIR_TO_SM, state->num_leds * sizeof(int), 1); ++ ++ pio_sm_clear_fifos(state->pio, state->sm); ++ pio_sm_set_clkdiv(state->pio, state->sm, make_fp24_8(1, 1)); ++ ws2812_program_init(state->pio, state->sm, state->offset, state->gpio, 800000, ++ state->is_rgbw); ++ ++ mutex_lock(&ws2812_pio_mutex); ++ ++ if (!ws2812_pio_ref_count) { ++ ret = alloc_chrdev_region(&ws2812_pio_dev_num, 0, MAX_INSTANCES, DRIVER_NAME); ++ if (ret < 0) { ++ dev_err(dev, "alloc_chrdev_region failed (rc=%d)\n", ret); ++ goto fail_mutex; ++ } ++ ++ ws2812_pio_class = class_create(DRIVER_NAME); ++ if (IS_ERR(ws2812_pio_class)) { ++ pr_err("Unable to create class " DRIVER_NAME "\n"); ++ ret = PTR_ERR(ws2812_pio_class); ++ goto fail_chrdev; ++ } ++ } ++ ++ ws2812_pio_ref_count++; ++ ++ minor = ida_alloc_range(&ws2812_pio_ida, 0, MAX_INSTANCES - 1, GFP_KERNEL); ++ if (minor < 0) { ++ pr_err("No free instances\n"); ++ ret = minor; ++ goto fail_class; ++ ++ } ++ ++ mutex_unlock(&ws2812_pio_mutex); ++ ++ state->dev_num = MKDEV(MAJOR(ws2812_pio_dev_num), minor); ++ state->dev_name = devm_kasprintf(dev, GFP_KERNEL, dev_name, minor); ++ ++ char_dev = device_create(ws2812_pio_class, NULL, state->dev_num, NULL, state->dev_name); ++ ++ if (IS_ERR(char_dev)) { ++ pr_err("Unable to create device %s\n", state->dev_name); ++ ret = PTR_ERR(char_dev); ++ goto fail_ida; ++ } ++ ++ state->cdev.owner = THIS_MODULE; ++ cdev_init(&state->cdev, &ws2812_pio_rp1_fops); ++ ++ ret = cdev_add(&state->cdev, state->dev_num, 1); ++ if (ret) { ++ pr_err("cdev_add failed\n"); ++ goto fail_device; ++ } ++ ++ INIT_DELAYED_WORK(&state->deferred_work, ws2812_pio_rp1_deferred_work); ++ ++ ws2812_clear_leds(state); ++ ++ dev_info(&pdev->dev, "Instantiated %d LEDs on GPIO %d as /dev/%s\n", ++ state->num_leds, state->gpio, state->dev_name); ++ ++ return 0; ++ ++fail_device: ++ device_destroy(ws2812_pio_class, state->dev_num); ++fail_ida: ++ mutex_lock(&ws2812_pio_mutex); ++ ida_free(&ws2812_pio_ida, minor); ++fail_class: ++ ws2812_pio_ref_count--; ++ if (ws2812_pio_ref_count) ++ goto fail_mutex; ++ class_destroy(ws2812_pio_class); ++fail_chrdev: ++ unregister_chrdev_region(ws2812_pio_dev_num, MAX_INSTANCES); ++fail_mutex: ++ mutex_unlock(&ws2812_pio_mutex); ++fail_pio: ++ pio_close(state->pio); ++ ++ return ret; ++} ++ ++static void ws2812_pio_rp1_remove(struct platform_device *pdev) ++{ ++ struct ws2812_pio_rp1_state *state = platform_get_drvdata(pdev); ++ ++ cancel_delayed_work(&state->deferred_work); ++ platform_set_drvdata(pdev, NULL); ++ ++ cdev_del(&state->cdev); ++ device_destroy(ws2812_pio_class, state->dev_num); ++ ++ mutex_lock(&ws2812_pio_mutex); ++ ida_free(&ws2812_pio_ida, MINOR(state->dev_num)); ++ ws2812_pio_ref_count--; ++ if (!ws2812_pio_ref_count) { ++ class_destroy(ws2812_pio_class); ++ unregister_chrdev_region(ws2812_pio_dev_num, MAX_INSTANCES); ++ } ++ mutex_unlock(&ws2812_pio_mutex); ++ ++ pio_close(state->pio); ++} ++ ++static const struct of_device_id ws2812_pio_rp1_match[] = { ++ { .compatible = "raspberrypi,ws2812-pio-rp1" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, ws2812_pio_rp1_match); ++ ++static struct platform_driver ws2812_pio_rp1_driver = { ++ .driver = { ++ .name = "ws2812-pio-rp1", ++ .of_match_table = ws2812_pio_rp1_match, ++ }, ++ .probe = ws2812_pio_rp1_probe, ++ .remove_new = ws2812_pio_rp1_remove, ++}; ++module_platform_driver(ws2812_pio_rp1_driver); ++ ++MODULE_DESCRIPTION("WS2812 PIO RP1 driver"); ++MODULE_AUTHOR("Phil Elwell"); ++MODULE_LICENSE("GPL"); diff --git a/target/linux/bcm27xx/patches-6.6/950-1478-overlays-Add-ws2812-pio-overlay.patch b/target/linux/bcm27xx/patches-6.6/950-1478-overlays-Add-ws2812-pio-overlay.patch new file mode 100644 index 0000000000..8e5051f385 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-1478-overlays-Add-ws2812-pio-overlay.patch @@ -0,0 +1,107 @@ +From 4a8f2b39157825fefc505fe4b94f3a9ce101e170 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +Date: Thu, 12 Dec 2024 23:23:39 +0000 +Subject: [PATCH 1478/1482] overlays: Add ws2812-pio overlay + +Add an overlay to enable a WS2812 LED driver on a given GPIO. + +Signed-off-by: Phil Elwell +--- + arch/arm/boot/dts/overlays/Makefile | 3 +- + arch/arm/boot/dts/overlays/README | 22 +++++++++ + .../boot/dts/overlays/ws2812-pio-overlay.dts | 46 +++++++++++++++++++ + 3 files changed, 70 insertions(+), 1 deletion(-) + create mode 100644 arch/arm/boot/dts/overlays/ws2812-pio-overlay.dts + +--- a/arch/arm/boot/dts/overlays/Makefile ++++ b/arch/arm/boot/dts/overlays/Makefile +@@ -337,7 +337,8 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \ + waveshare-can-fd-hat-mode-a.dtbo \ + waveshare-can-fd-hat-mode-b.dtbo \ + wittypi.dtbo \ +- wm8960-soundcard.dtbo ++ wm8960-soundcard.dtbo \ ++ ws2812-pio.dtbo + + targets += dtbs dtbs_install + targets += $(dtbo-y) +--- a/arch/arm/boot/dts/overlays/README ++++ b/arch/arm/boot/dts/overlays/README +@@ -5487,6 +5487,28 @@ Params: alsaname Changes + compatible Changes the codec compatibility + + ++Name: ws2812-pio ++Info: Configures a GPIO pin to drive a string of WS2812 LEDS using pio. It ++ can be enabled 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=ws2812-pio,= ++Params: brightness Set the initial brightness for the LEDs. The ++ brightness can be changed at runtime by writing ++ a single byte to offset 0 of the device. Note ++ that brightness is a multiplier for the pixel ++ values, and only white pixels can reach the ++ maximum visible brightness. (range 0-255, ++ default 255) ++ dev_name The name for the /dev/ device entry. Note that ++ if the name includes '%d' it will be replaced ++ by the instance number. (default 'leds%d') ++ gpio Output GPIO (0-27, default 4) ++ num_leds Number of LEDs (default 60) ++ rgbw 'rgbw=on' (or 'rgbw') indicates that each pixel ++ includes a white LED as well as the usual red, ++ green and blue. (default 'off') ++ ++ + Troubleshooting + =============== + +--- /dev/null ++++ b/arch/arm/boot/dts/overlays/ws2812-pio-overlay.dts +@@ -0,0 +1,46 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Device tree overlay for RP1 PIO WS2812 driver. ++/dts-v1/; ++/plugin/; ++ ++/ { ++ compatible = "brcm,bcm2712"; ++ ++ fragment@0 { ++ target = <&gpio>; ++ __overlay__ { ++ ws2812_pio_pins: ws2812_pio_pins@4 { ++ brcm,pins = <4>; /* gpio 4 */ ++ function = "pio"; ++ bias-disable; ++ }; ++ }; ++ }; ++ ++ fragment@1 { ++ target-path = "/"; ++ __overlay__ { ++ ws2812_pio: ws2812_pio@4 { ++ compatible = "raspberrypi,ws2812-pio-rp1"; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&ws2812_pio_pins>; ++ dev-name = "leds%d"; ++ leds-gpios = <&gpio 4 0>; ++ rpi,num-leds = <60>; ++ rpi,brightness = <255>; ++ }; ++ }; ++ }; ++ ++ __overrides__ { ++ brightness = <&ws2812_pio>, "rpi,brightness:0"; ++ dev_name = <&ws2812_pio>, "dev-name"; ++ gpio = <&ws2812_pio>,"leds-gpios:4", ++ <&ws2812_pio_pins>,"brcm,pins:0", ++ /* modify reg values to allow multiple instantiation */ ++ <&ws2812_pio>,"reg:0", ++ <&ws2812_pio_pins>,"reg:0"; ++ num_leds = <&ws2812_pio>, "rpi,num-leds:0"; ++ rgbw = <&ws2812_pio>, "rpi,rgbw?"; ++ }; ++}; -- 2.30.2