bcm27xx: patches: cherry-pick for RP1 kmods

Cherry-pick patches to support building RP1 modules.

Signed-off-by: John Audia <therealgraysky@proton.me>
Link: https://github.com/openwrt/openwrt/pull/17233
Signed-off-by: Robert Marko <robimarko@gmail.com>
(cherry picked from commit 613dd79d5e)
This commit is contained in:
John Audia 2024-12-12 15:33:15 -05:00 committed by Álvaro Fernández Rojas
parent 3ca3ee6012
commit f4c5d0e77e
32 changed files with 7488 additions and 0 deletions

View File

@ -0,0 +1,252 @@
From 0d58d8cfb6f989f290d983552fcaa116e582e84a Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Thu, 31 Oct 2024 17:33:38 +0000
Subject: [PATCH] mailbox: Add RP1 mailbox support
The Raspberry Pi RP1 includes 2 M3 cores running firmware. This driver
adds a mailbox communication channel to them via a doorbell and some
shared memory.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
drivers/mailbox/Kconfig | 9 ++
drivers/mailbox/Makefile | 2 +
drivers/mailbox/rp1-mailbox.c | 208 ++++++++++++++++++++++++++++++++++
3 files changed, 219 insertions(+)
create mode 100644 drivers/mailbox/rp1-mailbox.c
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -295,4 +295,13 @@ config QCOM_IPCC
acts as an interrupt controller for receiving interrupts from clients.
Say Y here if you want to build this driver.
+config MBOX_RP1
+ tristate "RP1 Mailbox"
+ depends on MFD_RP1
+ help
+ An implementation of a mailbox interface to the Raspberry Pi RP1 I/O
+ interface. Although written as a mailbox driver, the hardware only
+ provides an array of 32 doorbells.
+ Say Y here if you want to use the RP1 Mailbox.
+
endif
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -62,3 +62,5 @@ obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox
obj-$(CONFIG_QCOM_IPCC) += qcom-ipcc.o
obj-$(CONFIG_APPLE_MAILBOX) += apple-mailbox.o
+
+obj-$(CONFIG_MBOX_RP1) += rp1-mailbox.o
--- /dev/null
+++ b/drivers/mailbox/rp1-mailbox.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 Raspberry Pi Ltd.
+ *
+ * Parts of this driver are based on:
+ * - bcm2835-mailbox.c
+ * Copyright (C) 2010,2015 Broadcom
+ * Copyright (C) 2013-2014 Lubomir Rintel
+ * Copyright (C) 2013 Craig McGeachie
+ */
+
+#include <linux/compat.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/mailbox_controller.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+
+/*
+ * RP1's PROC_EVENTS register can generate interrupts on the M3 cores (when
+ * enabled). The 32-bit register is treated as 32 events, all of which share a
+ * common interrupt. HOST_EVENTS is the same in the reverse direction.
+ */
+#define SYSCFG_PROC_EVENTS 0x00000008
+#define SYSCFG_HOST_EVENTS 0x0000000c
+#define SYSCFG_HOST_EVENT_IRQ_EN 0x00000010
+#define SYSCFG_HOST_EVENT_IRQ 0x00000014
+
+#define HW_SET_BITS 0x00002000
+#define HW_CLR_BITS 0x00003000
+
+#define MAX_CHANS 4 /* 32 is the hardware limit */
+
+struct rp1_mbox {
+ void __iomem *regs;
+ unsigned int irq;
+ struct mbox_controller controller;
+};
+
+static struct rp1_mbox *rp1_chan_mbox(struct mbox_chan *chan)
+{
+ return container_of(chan->mbox, struct rp1_mbox, controller);
+}
+
+static unsigned int rp1_chan_event(struct mbox_chan *chan)
+{
+ return (unsigned int)(uintptr_t)chan->con_priv;
+}
+
+static irqreturn_t rp1_mbox_irq(int irq, void *dev_id)
+{
+ struct rp1_mbox *mbox = dev_id;
+ struct mbox_chan *chan;
+ unsigned int doorbell;
+ unsigned int evs;
+
+ evs = readl(mbox->regs + SYSCFG_HOST_EVENT_IRQ);
+ writel(evs, mbox->regs + SYSCFG_HOST_EVENTS + HW_CLR_BITS);
+
+ while (evs) {
+ doorbell = __ffs(evs);
+ chan = &mbox->controller.chans[doorbell];
+ mbox_chan_received_data(chan, NULL);
+ evs &= ~(1 << doorbell);
+ }
+ return IRQ_HANDLED;
+}
+
+static int rp1_send_data(struct mbox_chan *chan, void *data)
+{
+ struct rp1_mbox *mbox = rp1_chan_mbox(chan);
+ unsigned int event = rp1_chan_event(chan);
+
+ writel(event, mbox->regs + SYSCFG_PROC_EVENTS + HW_SET_BITS);
+
+ return 0;
+}
+
+static int rp1_startup(struct mbox_chan *chan)
+{
+ struct rp1_mbox *mbox = rp1_chan_mbox(chan);
+ unsigned int event = rp1_chan_event(chan);
+
+ writel(event, mbox->regs + SYSCFG_HOST_EVENT_IRQ_EN + HW_SET_BITS);
+
+ return 0;
+}
+
+static void rp1_shutdown(struct mbox_chan *chan)
+{
+ struct rp1_mbox *mbox = rp1_chan_mbox(chan);
+ unsigned int event = rp1_chan_event(chan);
+
+ writel(event, mbox->regs + SYSCFG_HOST_EVENT_IRQ_EN + HW_CLR_BITS);
+}
+
+static bool rp1_last_tx_done(struct mbox_chan *chan)
+{
+ struct rp1_mbox *mbox = rp1_chan_mbox(chan);
+ unsigned int event = rp1_chan_event(chan);
+ unsigned int evs;
+
+ evs = readl(mbox->regs + SYSCFG_HOST_EVENT_IRQ);
+
+ return !(evs & event);
+}
+
+static const struct mbox_chan_ops rp1_mbox_chan_ops = {
+ .send_data = rp1_send_data,
+ .startup = rp1_startup,
+ .shutdown = rp1_shutdown,
+ .last_tx_done = rp1_last_tx_done
+};
+
+static struct mbox_chan *rp1_mbox_xlate(struct mbox_controller *mbox,
+ const struct of_phandle_args *spec)
+{
+ struct mbox_chan *chan;
+ unsigned int doorbell;
+
+ if (spec->args_count != 1)
+ return ERR_PTR(-EINVAL);
+
+ doorbell = spec->args[0];
+ if (doorbell >= MAX_CHANS)
+ return ERR_PTR(-EINVAL);
+
+ chan = &mbox->chans[doorbell];
+ if (chan->con_priv)
+ return ERR_PTR(-EBUSY);
+
+ chan->con_priv = (void *)(uintptr_t)(1 << doorbell);
+
+ return chan;
+}
+
+static int rp1_mbox_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mbox_chan *chans;
+ struct rp1_mbox *mbox;
+ int ret = 0;
+
+ mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
+ if (mbox == NULL)
+ return -ENOMEM;
+
+ ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
+ rp1_mbox_irq, 0, dev_name(dev), mbox);
+ if (ret) {
+ dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n",
+ ret);
+ return -ENODEV;
+ }
+
+ mbox->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mbox->regs)) {
+ ret = PTR_ERR(mbox->regs);
+ return ret;
+ }
+
+ chans = devm_kcalloc(dev, MAX_CHANS, sizeof(*chans), GFP_KERNEL);
+ if (!chans)
+ return -ENOMEM;
+
+ mbox->controller.txdone_poll = true;
+ mbox->controller.txpoll_period = 5;
+ mbox->controller.ops = &rp1_mbox_chan_ops;
+ mbox->controller.of_xlate = &rp1_mbox_xlate;
+ mbox->controller.dev = dev;
+ mbox->controller.num_chans = MAX_CHANS;
+ mbox->controller.chans = chans;
+
+ ret = devm_mbox_controller_register(dev, &mbox->controller);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, mbox);
+
+ return 0;
+}
+
+static const struct of_device_id rp1_mbox_of_match[] = {
+ { .compatible = "raspberrypi,rp1-mbox", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rp1_mbox_of_match);
+
+static struct platform_driver rp1_mbox_driver = {
+ .driver = {
+ .name = "rp1-mbox",
+ .of_match_table = rp1_mbox_of_match,
+ },
+ .probe = rp1_mbox_probe,
+};
+
+module_platform_driver(rp1_mbox_driver);
+
+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
+MODULE_DESCRIPTION("RP1 mailbox IPC driver");
+MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,421 @@
From 67daeadcaa7cee1f4b9df7aa108d199e73f35451 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Thu, 31 Oct 2024 17:36:54 +0000
Subject: [PATCH] firmware: Add an RP1 firmware interface over mbox
The RP1 firmware runs a simple communications channel over some shared
memory and a mailbox. This driver provides access to that channel.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
drivers/firmware/Kconfig | 9 +
drivers/firmware/Makefile | 1 +
drivers/firmware/rp1.c | 316 +++++++++++++++++++++++++++++++++++
include/linux/rp1-firmware.h | 53 ++++++
4 files changed, 379 insertions(+)
create mode 100644 drivers/firmware/rp1.c
create mode 100644 include/linux/rp1-firmware.h
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -153,6 +153,15 @@ config RASPBERRYPI_FIRMWARE
This option enables support for communicating with the firmware on the
Raspberry Pi.
+config FIRMWARE_RP1
+ tristate "RP1 Firmware Driver"
+ depends on MBOX_RP1
+ help
+ The Raspberry Pi RP1 processor presents a firmware
+ interface using shared memory and a mailbox. To enable
+ the driver that communicates with it, say Y. Otherwise,
+ say N.
+
config FW_CFG_SYSFS
tristate "QEMU fw_cfg device support in sysfs"
depends on SYSFS && (ARM || ARM64 || PARISC || PPC_PMAC || SPARC || X86)
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o
obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o
obj-$(CONFIG_MTK_ADSP_IPC) += mtk-adsp-ipc.o
obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o
+obj-$(CONFIG_FIRMWARE_RP1) += rp1.o
obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o
obj-$(CONFIG_QCOM_SCM) += qcom-scm.o
qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
--- /dev/null
+++ b/drivers/firmware/rp1.c
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023-24 Raspberry Pi Ltd.
+ *
+ * Parts of this driver are based on:
+ * - raspberrypi.c, by Eric Anholt <eric@anholt.net>
+ * Copyright (C) 2015 Broadcom
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/kref.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/rp1-firmware.h>
+
+#define RP1_MAILBOX_FIRMWARE 0
+
+enum rp1_firmware_ops {
+ MBOX_SUCCESS = 0x0000,
+ GET_FIRMWARE_VERSION = 0x0001, // na -> 160-bit version
+ GET_FEATURE = 0x0002, // FOURCC -> op base (0 == unsupported), op count
+
+ COMMON_COUNT
+};
+
+struct rp1_firmware {
+ struct mbox_client cl;
+ struct mbox_chan *chan; /* The doorbell channel */
+ uint32_t __iomem *buf; /* The shared buffer */
+ u32 buf_size; /* The size of the shared buffer */
+ struct completion c;
+
+ struct kref consumers;
+};
+
+struct rp1_get_feature_resp {
+ uint32_t op_base;
+ uint32_t op_count;
+};
+
+static DEFINE_MUTEX(transaction_lock);
+
+static const struct of_device_id rp1_firmware_of_match[] = {
+ { .compatible = "raspberrypi,rp1-firmware", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rp1_firmware_of_match);
+
+static void response_callback(struct mbox_client *cl, void *msg)
+{
+ struct rp1_firmware *fw = container_of(cl, struct rp1_firmware, cl);
+
+ complete(&fw->c);
+}
+
+/*
+ * Sends a request to the RP1 firmware and synchronously waits for the reply.
+ * Returns zero or a positive count of response bytes on success, negative on
+ * error.
+ */
+
+int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
+ const void *data, unsigned int data_len,
+ void *resp, unsigned int resp_space)
+{
+ int ret;
+ u32 rc;
+
+ if (data_len + 4 > fw->buf_size)
+ return -EINVAL;
+
+ mutex_lock(&transaction_lock);
+
+ memcpy_toio(&fw->buf[1], data, data_len);
+ writel((op << 16) | data_len, fw->buf);
+
+ reinit_completion(&fw->c);
+ ret = mbox_send_message(fw->chan, NULL);
+ if (ret >= 0) {
+ if (wait_for_completion_timeout(&fw->c, HZ))
+ ret = 0;
+ else
+ ret = -ETIMEDOUT;
+ } else {
+ dev_err(fw->cl.dev, "mbox_send_message returned %d\n", ret);
+ }
+
+ if (ret == 0) {
+ rc = readl(fw->buf);
+ if (rc & 0x80000000) {
+ ret = (int32_t)rc;
+ } else {
+ ret = min(rc, resp_space);
+ memcpy_fromio(resp, &fw->buf[1], ret);
+ }
+ }
+
+ mutex_unlock(&transaction_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(rp1_firmware_message);
+
+static void rp1_firmware_delete(struct kref *kref)
+{
+ struct rp1_firmware *fw = container_of(kref, struct rp1_firmware, consumers);
+
+ mbox_free_channel(fw->chan);
+ kfree(fw);
+}
+
+void rp1_firmware_put(struct rp1_firmware *fw)
+{
+ kref_put(&fw->consumers, rp1_firmware_delete);
+}
+EXPORT_SYMBOL_GPL(rp1_firmware_put);
+
+int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
+ uint32_t *op_base, uint32_t *op_count)
+{
+ struct rp1_get_feature_resp resp;
+ int ret;
+
+ memset(&resp, 0, sizeof(resp));
+ ret = rp1_firmware_message(fw, GET_FEATURE,
+ &fourcc, sizeof(fourcc),
+ &resp, sizeof(resp));
+ *op_base = resp.op_base;
+ *op_count = resp.op_count;
+ if (ret < 0)
+ return ret;
+ if (ret < sizeof(resp) || !resp.op_base)
+ return -EOPNOTSUPP;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rp1_firmware_get_feature);
+
+static void devm_rp1_firmware_put(void *data)
+{
+ struct rp1_firmware *fw = data;
+
+ rp1_firmware_put(fw);
+}
+
+/**
+ * rp1_firmware_get - Get pointer to rp1_firmware structure.
+ *
+ * The reference to rp1_firmware has to be released with rp1_firmware_put().
+ *
+ * Returns an error pointer on failure.
+ */
+struct rp1_firmware *rp1_firmware_get(struct device_node *client)
+{
+ const char *match = rp1_firmware_of_match[0].compatible;
+ struct platform_device *pdev;
+ struct device_node *fwnode;
+ struct rp1_firmware *fw;
+
+ if (client) {
+ fwnode = of_parse_phandle(client, "firmware", 0);
+ if (!fwnode)
+ fwnode = of_get_parent(client);
+ if (fwnode && !of_device_is_compatible(fwnode, match)) {
+ of_node_put(fwnode);
+ fwnode = NULL;
+ }
+ }
+
+ if (!fwnode)
+ fwnode = of_find_matching_node(NULL, rp1_firmware_of_match);
+
+ if (!fwnode)
+ return ERR_PTR(-ENOENT);
+
+ pdev = of_find_device_by_node(fwnode);
+ of_node_put(fwnode);
+
+ if (!pdev)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ fw = platform_get_drvdata(pdev);
+ if (!fw)
+ goto err_defer;
+
+ if (!kref_get_unless_zero(&fw->consumers))
+ goto err_defer;
+
+ put_device(&pdev->dev);
+
+ return fw;
+
+err_defer:
+ put_device(&pdev->dev);
+ return ERR_PTR(-EPROBE_DEFER);
+}
+EXPORT_SYMBOL_GPL(rp1_firmware_get);
+
+/**
+ * devm_rp1_firmware_get - Get pointer to rp1_firmware structure.
+ * @firmware_node: Pointer to the firmware Device Tree node.
+ *
+ * Returns NULL is the firmware device is not ready.
+ */
+struct rp1_firmware *devm_rp1_firmware_get(struct device *dev, struct device_node *client)
+{
+ struct rp1_firmware *fw;
+ int ret;
+
+ fw = rp1_firmware_get(client);
+ if (IS_ERR(fw))
+ return fw;
+
+ ret = devm_add_action_or_reset(dev, devm_rp1_firmware_put, fw);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return fw;
+}
+EXPORT_SYMBOL_GPL(devm_rp1_firmware_get);
+
+static int rp1_firmware_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *shmem;
+ struct rp1_firmware *fw;
+ struct resource res;
+ uint32_t version[5];
+ int ret;
+
+ shmem = of_parse_phandle(dev->of_node, "shmem", 0);
+ if (!of_device_is_compatible(shmem, "raspberrypi,rp1-shmem")) {
+ of_node_put(shmem);
+ return -ENXIO;
+ }
+
+ ret = of_address_to_resource(shmem, 0, &res);
+ of_node_put(shmem);
+ if (ret) {
+ dev_err(dev, "failed to get shared memory (%pOF) - %d\n", shmem, ret);
+ return ret;
+ }
+
+ /*
+ * Memory will be freed by rp1_firmware_delete() once all users have
+ * released their firmware handles. Don't use devm_kzalloc() here.
+ */
+ fw = kzalloc(sizeof(*fw), GFP_KERNEL);
+ if (!fw)
+ return -ENOMEM;
+
+ fw->buf_size = resource_size(&res);
+ fw->buf = devm_ioremap(dev, res.start, fw->buf_size);
+ if (!fw->buf) {
+ dev_err(dev, "failed to ioremap shared memory\n");
+ kfree(fw);
+ return -EADDRNOTAVAIL;
+ }
+
+ fw->cl.dev = dev;
+ fw->cl.rx_callback = response_callback;
+ fw->cl.tx_block = false;
+
+ fw->chan = mbox_request_channel(&fw->cl, RP1_MAILBOX_FIRMWARE);
+ if (IS_ERR(fw->chan)) {
+ int ret = PTR_ERR(fw->chan);
+
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get mbox channel: %d\n", ret);
+ kfree(fw);
+ return ret;
+ }
+
+ init_completion(&fw->c);
+ kref_init(&fw->consumers);
+
+ platform_set_drvdata(pdev, fw);
+
+ ret = rp1_firmware_message(fw, GET_FIRMWARE_VERSION,
+ NULL, 0, &version, sizeof(version));
+ if (ret == sizeof(version)) {
+ dev_info(dev, "RP1 Firmware version %08x%08x%08x%08x%08x\n",
+ version[0], version[1], version[2], version[3], version[4]);
+ ret = 0;
+ } else if (ret >= 0) {
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static int rp1_firmware_remove(struct platform_device *pdev)
+{
+ struct rp1_firmware *fw = platform_get_drvdata(pdev);
+
+ rp1_firmware_put(fw);
+
+ return 0;
+}
+
+static struct platform_driver rp1_firmware_driver = {
+ .driver = {
+ .name = "rp1-firmware",
+ .of_match_table = rp1_firmware_of_match,
+ },
+ .probe = rp1_firmware_probe,
+ .remove = rp1_firmware_remove,
+};
+
+module_platform_driver(rp1_firmware_driver);
+
+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
+MODULE_DESCRIPTION("RP1 firmware driver");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/include/linux/rp1-firmware.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023 2023-2024 Raspberry Pi Ltd.
+ */
+
+#ifndef __SOC_RP1_FIRMWARE_H__
+#define __SOC_RP1_FIRMWARE_H__
+
+#include <linux/types.h>
+#include <linux/of_device.h>
+
+#define RP1_FOURCC(s) ((uint32_t)((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0)))
+
+struct rp1_firmware;
+
+#if IS_ENABLED(CONFIG_FIRMWARE_RP1)
+int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
+ const void *data, unsigned int data_len,
+ void *resp, unsigned int resp_space);
+void rp1_firmware_put(struct rp1_firmware *fw);
+struct rp1_firmware *rp1_firmware_get(struct device_node *fwnode);
+struct rp1_firmware *devm_rp1_firmware_get(struct device *dev, struct device_node *fwnode);
+int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
+ uint32_t *op_base, uint32_t *op_count);
+#else
+static inline int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
+ const void *data, unsigned int data_len,
+ void *resp, unsigned int resp_space)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void rp1_firmware_put(struct rp1_firmware *fw) { }
+
+static inline struct rp1_firmware *rp1_firmware_get(struct device_node *fwnode)
+{
+ return NULL;
+}
+
+static inline struct rp1_firmware *devm_rp1_firmware_get(struct device *dev,
+ struct device_node *fwnode)
+{
+ return NULL;
+}
+
+static inline int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
+ uint32_t *op_base, uint32_t *op_count)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
+#endif /* __SOC_RP1_FIRMWARE_H__ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
From 1b5acd42281ad102b79f4e1794f0a0cccdafda05 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Sat, 16 Nov 2024 16:53:31 +0000
Subject: [PATCH] fixup! misc: Add RP1 PIO driver
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
include/uapi/misc/rp1_pio_if.h | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
--- a/include/uapi/misc/rp1_pio_if.h
+++ b/include/uapi/misc/rp1_pio_if.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0 */
+/* SPDX-License-Identifier: GPL-2.0 + WITH Linux-syscall-note */
/*
* Copyright (c) 2023-24 Raspberry Pi Ltd.
* All rights reserved.
@@ -169,10 +169,6 @@ struct rp1_access_hw_args {
#define PIO_IOC_SM_CONFIG_XFER _IOW(PIO_IOC_MAGIC, 0, struct rp1_pio_sm_config_xfer_args)
#define PIO_IOC_SM_XFER_DATA _IOW(PIO_IOC_MAGIC, 1, struct rp1_pio_sm_xfer_data_args)
-#ifdef CONFIG_COMPAT
-//XXX #define PIO_IOC_SM_XFER_DATA32 _IOW(PIO_IOC_MAGIC, 2, struct pio_sm_xfer_data_args)
-#endif
-
#define PIO_IOC_READ_HW _IOW(PIO_IOC_MAGIC, 8, struct rp1_access_hw_args)
#define PIO_IOC_WRITE_HW _IOW(PIO_IOC_MAGIC, 9, struct rp1_access_hw_args)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,299 @@
From 4d20aadc3188ecfb62b309a9924ee9696a94fc33 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Fri, 8 Nov 2024 09:37:58 +0000
Subject: [PATCH] pwm: Add pwm-pio-rp1 driver
Use the PIO hardware on RP1 to implement a PWM interface.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
drivers/pwm/Kconfig | 11 ++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-pio-rp1.c | 251 ++++++++++++++++++++++++++++++++++++++
3 files changed, 263 insertions(+)
create mode 100644 drivers/pwm/pwm-pio-rp1.c
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -454,6 +454,17 @@ config PWM_PCA9685
To compile this driver as a module, choose M here: the module
will be called pwm-pca9685.
+config PWM_PIO_RP1
+ tristate "RP1 PIO PWM support"
+ depends on FIRMWARE_RP1 || COMPILE_TEST
+ help
+ This is a PWM framework driver for Raspberry Pi 5, using the PIO
+ hardware of RP1 to provide PWM functionality. Supports up to 4
+ instances on GPIOs in bank 0.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-pio-rp1.
+
config PWM_PXA
tristate "PXA PWM support"
depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_PWM_MXS) += pwm-mxs.o
obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o
obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o
obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o
+obj-$(CONFIG_PWM_PIO_RP1) += pwm-pio-rp1.o
obj-$(CONFIG_PWM_PXA) += pwm-pxa.o
obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o
obj-$(CONFIG_PWM_RP1) += pwm-rp1.o
--- /dev/null
+++ b/drivers/pwm/pwm-pio-rp1.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Raspberry Pi PIO PWM.
+ *
+ * Copyright (C) 2024 Raspberry Pi Ltd.
+ *
+ * Author: Phil Elwell (phil@raspberrypi.com)
+ *
+ * Based on the pwm-rp1 driver by:
+ * Naushir Patuck <naush@raspberrypi.com>
+ * and on the pwm-gpio driver by:
+ * Vincent Whitchurch <vincent.whitchurch@axis.com>
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/pio_rp1.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+struct pwm_pio_rp1 {
+ struct pwm_chip chip;
+ struct device *dev;
+ struct gpio_desc *gpiod;
+ struct mutex mutex;
+ PIO pio;
+ uint sm;
+ uint offset;
+ uint gpio;
+ uint32_t period; /* In SM cycles */
+ uint32_t duty_cycle; /* In SM cycles */
+ enum pwm_polarity polarity;
+ bool enabled;
+};
+
+/* Generated from pwm.pio by pioasm */
+#define pwm_wrap_target 0
+#define pwm_wrap 6
+#define pwm_loop_ticks 3
+
+static const uint16_t pwm_program_instructions[] = {
+ // .wrap_target
+ 0x9080, // 0: pull noblock side 0
+ 0xa027, // 1: mov x, osr
+ 0xa046, // 2: mov y, isr
+ 0x00a5, // 3: jmp x != y, 5
+ 0x1806, // 4: jmp 6 side 1
+ 0xa042, // 5: nop
+ 0x0083, // 6: jmp y--, 3
+ // .wrap
+};
+
+static const struct pio_program pwm_program = {
+ .instructions = pwm_program_instructions,
+ .length = 7,
+ .origin = -1,
+};
+
+static unsigned int pwm_pio_resolution __read_mostly;
+
+static inline pio_sm_config pwm_program_get_default_config(uint offset)
+{
+ pio_sm_config c = pio_get_default_sm_config();
+
+ sm_config_set_wrap(&c, offset + pwm_wrap_target, offset + pwm_wrap);
+ sm_config_set_sideset(&c, 2, true, false);
+ return c;
+}
+
+static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin)
+{
+ pio_gpio_init(pio, pin);
+
+ pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
+ pio_sm_config c = pwm_program_get_default_config(offset);
+
+ sm_config_set_sideset_pins(&c, pin);
+ pio_sm_init(pio, sm, offset, &c);
+}
+
+/* Write `period` to the input shift register - must be disabled */
+static void pio_pwm_set_period(PIO pio, uint sm, uint32_t period)
+{
+ pio_sm_put_blocking(pio, sm, period);
+ pio_sm_exec(pio, sm, pio_encode_pull(false, false));
+ pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
+}
+
+/* Write `level` to TX FIFO. State machine will copy this into X. */
+static void pio_pwm_set_level(PIO pio, uint sm, uint32_t level)
+{
+ pio_sm_put_blocking(pio, sm, level);
+}
+
+static int pwm_pio_rp1_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct pwm_pio_rp1 *ppwm = container_of(chip, struct pwm_pio_rp1, chip);
+ uint32_t new_duty_cycle;
+ uint32_t new_period;
+
+ if (state->duty_cycle && state->duty_cycle < pwm_pio_resolution)
+ return -EINVAL;
+
+ if (state->duty_cycle != state->period &&
+ (state->period - state->duty_cycle < pwm_pio_resolution))
+ return -EINVAL;
+
+ new_period = state->period / pwm_pio_resolution;
+ new_duty_cycle = state->duty_cycle / pwm_pio_resolution;
+
+ mutex_lock(&ppwm->mutex);
+
+ if ((ppwm->enabled && !state->enabled) || new_period != ppwm->period) {
+ pio_sm_set_enabled(ppwm->pio, ppwm->sm, false);
+ ppwm->enabled = false;
+ }
+
+ if (new_period != ppwm->period) {
+ pio_pwm_set_period(ppwm->pio, ppwm->sm, new_period);
+ ppwm->period = new_period;
+ }
+
+ if (state->enabled && new_duty_cycle != ppwm->duty_cycle) {
+ pio_pwm_set_level(ppwm->pio, ppwm->sm, new_duty_cycle);
+ ppwm->duty_cycle = new_duty_cycle;
+ }
+
+ if (state->polarity != ppwm->polarity) {
+ pio_gpio_set_outover(ppwm->pio, ppwm->gpio,
+ (state->polarity == PWM_POLARITY_INVERSED) ?
+ GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
+ ppwm->polarity = state->polarity;
+ }
+
+ if (!ppwm->enabled && state->enabled) {
+ pio_sm_set_enabled(ppwm->pio, ppwm->sm, true);
+ ppwm->enabled = true;
+ }
+
+ mutex_unlock(&ppwm->mutex);
+
+ return 0;
+}
+
+static const struct pwm_ops pwm_pio_rp1_ops = {
+ .apply = pwm_pio_rp1_apply,
+};
+
+static int pwm_pio_rp1_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct of_phandle_args of_args = { 0 };
+ struct device *dev = &pdev->dev;
+ struct pwm_pio_rp1 *ppwm;
+ struct pwm_chip *chip;
+ bool is_rp1;
+
+ ppwm = devm_kzalloc(dev, sizeof(*ppwm), GFP_KERNEL);
+ if (IS_ERR(ppwm))
+ return PTR_ERR(ppwm);
+
+ chip = &ppwm->chip;
+
+ mutex_init(&ppwm->mutex);
+
+ ppwm->gpiod = devm_gpiod_get(dev, NULL, GPIOD_ASIS);
+ /* Need to check that this is an RP1 GPIO in the first bank, and retrieve the offset */
+ /* Unfortunately I think this has to be done by parsing the gpios property */
+ if (IS_ERR(ppwm->gpiod))
+ return dev_err_probe(dev, PTR_ERR(ppwm->gpiod),
+ "could not get a gpio\n");
+
+ /* This really shouldn't fail, given that we have a gpiod */
+ if (of_parse_phandle_with_args(np, "gpios", "#gpio-cells", 0, &of_args))
+ return dev_err_probe(dev, -EINVAL,
+ "can't find gpio declaration\n");
+
+ is_rp1 = of_device_is_compatible(of_args.np, "raspberrypi,rp1-gpio");
+ of_node_put(of_args.np);
+ if (!is_rp1 || of_args.args_count != 2)
+ return dev_err_probe(dev, -EINVAL,
+ "not an RP1 gpio\n");
+
+ ppwm->gpio = of_args.args[0];
+
+ ppwm->pio = pio_open();
+ if (IS_ERR(ppwm->pio))
+ return dev_err_probe(dev, PTR_ERR(ppwm->pio),
+ "%pfw: could not open PIO\n",
+ dev_fwnode(dev));
+
+ ppwm->sm = pio_claim_unused_sm(ppwm->pio, false);
+ if ((int)ppwm->sm < 0) {
+ pio_close(ppwm->pio);
+ return dev_err_probe(dev, -EBUSY,
+ "%pfw: no free PIO SM\n",
+ dev_fwnode(dev));
+ }
+
+ ppwm->offset = pio_add_program(ppwm->pio, &pwm_program);
+ if (ppwm->offset == PIO_ORIGIN_ANY) {
+ pio_close(ppwm->pio);
+ return dev_err_probe(dev, -EBUSY,
+ "%pfw: not enough PIO program space\n",
+ dev_fwnode(dev));
+ }
+
+ pwm_program_init(ppwm->pio, ppwm->sm, ppwm->offset, ppwm->gpio);
+
+ pwm_pio_resolution = (1000u * 1000 * 1000 * pwm_loop_ticks) / clock_get_hz(clk_sys);
+
+ chip->dev = dev;
+ chip->ops = &pwm_pio_rp1_ops;
+ chip->atomic = true;
+ chip->npwm = 1;
+
+ platform_set_drvdata(pdev, ppwm);
+
+ return devm_pwmchip_add(dev, chip);
+}
+
+static void pwm_pio_rp1_remove(struct platform_device *pdev)
+{
+ struct pwm_pio_rp1 *ppwm = platform_get_drvdata(pdev);
+
+ pio_close(ppwm->pio);
+}
+
+static const struct of_device_id pwm_pio_rp1_dt_ids[] = {
+ { .compatible = "raspberrypi,pwm-pio-rp1" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pwm_pio_rp1_dt_ids);
+
+static struct platform_driver pwm_pio_rp1_driver = {
+ .driver = {
+ .name = "pwm-pio-rp1",
+ .of_match_table = pwm_pio_rp1_dt_ids,
+ },
+ .probe = pwm_pio_rp1_probe,
+ .remove_new = pwm_pio_rp1_remove,
+};
+module_platform_driver(pwm_pio_rp1_driver);
+
+MODULE_DESCRIPTION("PWM PIO RP1 driver");
+MODULE_AUTHOR("Phil Elwell");
+MODULE_LICENSE("GPL");

View File

@ -0,0 +1,25 @@
From 99a0201bb0abc946dc431214b638b2cc6b01dda5 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Mon, 25 Nov 2024 16:19:55 +0000
Subject: [PATCH] misc/rp1-pio: Fix copy/paste error in pio_rp1.h
As per the subject, there was a copy/paste error that caused
pio_sm_unclaim from a driver to result in a call to
pio_sm_claim. Fix it.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
include/linux/pio_rp1.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/include/linux/pio_rp1.h
+++ b/include/linux/pio_rp1.h
@@ -318,7 +318,7 @@ static inline int pio_sm_unclaim(struct
if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
return -EINVAL;
- return rp1_pio_sm_claim(client, &args);
+ return rp1_pio_sm_unclaim(client, &args);
}
static inline int pio_claim_unused_sm(struct rp1_pio_client *client, bool required)

View File

@ -0,0 +1,42 @@
From df8a2f6dc114b2c5c7685a069f717f2b06186b74 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Wed, 20 Nov 2024 16:23:06 +0000
Subject: [PATCH] rp1-pio: Add missing 'static inline's
Avoid some duplicate symbol errors by adding some missing
'static inline' decorations.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
include/linux/pio_rp1.h | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
--- a/include/linux/pio_rp1.h
+++ b/include/linux/pio_rp1.h
@@ -245,7 +245,7 @@ static inline bool pio_can_add_program_a
return !rp1_pio_can_add_program(client, &args);
}
-uint pio_add_program(struct rp1_pio_client *client, const pio_program_t *program)
+static inline uint pio_add_program(struct rp1_pio_client *client, const pio_program_t *program)
{
struct rp1_pio_add_program_args args;
int offset;
@@ -365,7 +365,7 @@ static inline int pio_sm_set_config(stru
return rp1_pio_sm_set_config(client, &args);
}
-int pio_sm_exec(struct rp1_pio_client *client, uint sm, uint instr)
+static inline int pio_sm_exec(struct rp1_pio_client *client, uint sm, uint instr)
{
struct rp1_pio_sm_exec_args args = { .sm = sm, .instr = instr, .blocking = false };
@@ -375,7 +375,7 @@ int pio_sm_exec(struct rp1_pio_client *c
return rp1_pio_sm_exec(client, &args);
}
-int pio_sm_exec_wait_blocking(struct rp1_pio_client *client, uint sm, uint instr)
+static inline int pio_sm_exec_wait_blocking(struct rp1_pio_client *client, uint sm, uint instr)
{
struct rp1_pio_sm_exec_args args = { .sm = sm, .instr = instr, .blocking = true };

View File

@ -0,0 +1,39 @@
From d1f0c94e974a5f26d210b1d13a6ef9543bee4984 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Thu, 21 Nov 2024 11:11:48 +0000
Subject: [PATCH] misc: rp1-pio: Back-port some 6.11 build fixes
Porting rp1-pio to rpi-6.11.y uncovered a few missing #includes and a
difference of const-ness. Although not needed here, back-porting the
resulting changes makes the driver more "correct" and may prevent a
future merge conflict.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
drivers/misc/rp1-pio.c | 3 +++
include/linux/pio_rp1.h | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
--- a/drivers/misc/rp1-pio.c
+++ b/drivers/misc/rp1-pio.c
@@ -22,6 +22,9 @@
#include <linux/init.h>
#include <linux/ioctl.h>
#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pio_rp1.h>
+#include <linux/platform_device.h>
#include <linux/rp1-firmware.h>
#include <linux/semaphore.h>
#include <linux/slab.h>
--- a/include/linux/pio_rp1.h
+++ b/include/linux/pio_rp1.h
@@ -176,7 +176,7 @@ typedef rp1_pio_sm_config pio_sm_config;
typedef struct rp1_pio_client *PIO;
void pio_set_error(struct rp1_pio_client *client, int err);
-int pio_get_error(struct rp1_pio_client *client);
+int pio_get_error(const struct rp1_pio_client *client);
void pio_clear_error(struct rp1_pio_client *client);
int rp1_pio_can_add_program(struct rp1_pio_client *client, void *param);

View File

@ -0,0 +1,215 @@
From dd2394360860d15146c96635796a75b05bb32b61 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Tue, 19 Nov 2024 09:25:34 +0000
Subject: [PATCH] misc: rp1-pio: Add FIFO-related methods
Add support for querying the FIFO status and clearing the TX FIFO.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
drivers/misc/rp1-fw-pio.h | 3 ++
drivers/misc/rp1-pio.c | 24 +++++++++
include/linux/pio_rp1.h | 89 ++++++++++++++++++++++++++++++++++
include/uapi/misc/rp1_pio_if.h | 13 ++++-
4 files changed, 128 insertions(+), 1 deletion(-)
--- a/drivers/misc/rp1-fw-pio.h
+++ b/drivers/misc/rp1-fw-pio.h
@@ -47,6 +47,9 @@ enum rp1_pio_ops {
READ_HW, // src address, len -> data bytes
WRITE_HW, // dst address, data
+ PIO_SM_FIFO_STATE, // u16 sm, u8 tx -> u16 level, u8 empty, u8 full
+ PIO_SM_DRAIN_TX, // u16 sm
+
PIO_COUNT
};
--- a/drivers/misc/rp1-pio.c
+++ b/drivers/misc/rp1-pio.c
@@ -479,6 +479,28 @@ int rp1_pio_sm_set_dmactrl(struct rp1_pi
}
EXPORT_SYMBOL_GPL(rp1_pio_sm_set_dmactrl);
+int rp1_pio_sm_fifo_state(struct rp1_pio_client *client, void *param)
+{
+ struct rp1_pio_sm_fifo_state_args *args = param;
+ const int level_offset = offsetof(struct rp1_pio_sm_fifo_state_args, level);
+ int ret;
+
+ ret = rp1_pio_message_resp(client->pio, PIO_SM_FIFO_STATE, args, sizeof(*args),
+ &args->level, NULL, sizeof(*args) - level_offset);
+ if (ret >= 0)
+ return level_offset + ret;
+ return ret;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_fifo_state);
+
+int rp1_pio_sm_drain_tx(struct rp1_pio_client *client, void *param)
+{
+ struct rp1_pio_sm_clear_fifos_args *args = param;
+
+ return rp1_pio_message(client->pio, PIO_SM_DRAIN_TX, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_drain_tx);
+
int rp1_pio_gpio_init(struct rp1_pio_client *client, void *param)
{
struct rp1_gpio_init_args *args = param;
@@ -851,6 +873,8 @@ struct handler_info {
HANDLER(SM_PUT, sm_put),
HANDLER(SM_GET, sm_get),
HANDLER(SM_SET_DMACTRL, sm_set_dmactrl),
+ HANDLER(SM_FIFO_STATE, sm_fifo_state),
+ HANDLER(SM_DRAIN_TX, sm_drain_tx),
HANDLER(GPIO_INIT, gpio_init),
HANDLER(GPIO_SET_FUNCTION, gpio_set_function),
--- a/include/linux/pio_rp1.h
+++ b/include/linux/pio_rp1.h
@@ -200,6 +200,8 @@ int rp1_pio_sm_enable_sync(struct rp1_pi
int rp1_pio_sm_put(struct rp1_pio_client *client, void *param);
int rp1_pio_sm_get(struct rp1_pio_client *client, void *param);
int rp1_pio_sm_set_dmactrl(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_fifo_state(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_drain_tx(struct rp1_pio_client *client, void *param);
int rp1_pio_gpio_init(struct rp1_pio_client *client, void *param);
int rp1_pio_gpio_set_function(struct rp1_pio_client *client, void *param);
int rp1_pio_gpio_set_pulls(struct rp1_pio_client *client, void *param);
@@ -551,6 +553,15 @@ static inline int pio_sm_set_dmactrl(str
return rp1_pio_sm_set_dmactrl(client, &args);
};
+static inline int pio_sm_drain_tx_fifo(struct rp1_pio_client *client, uint sm)
+{
+ struct rp1_pio_sm_clear_fifos_args args = { .sm = sm };
+
+ if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+ return -EINVAL;
+ return rp1_pio_sm_drain_tx(client, &args);
+};
+
static inline int pio_sm_put(struct rp1_pio_client *client, uint sm, uint32_t data)
{
struct rp1_pio_sm_put_args args = { .sm = (uint16_t)sm, .blocking = false, .data = data };
@@ -587,6 +598,84 @@ static inline uint32_t pio_sm_get_blocki
return args.data;
}
+static inline int pio_sm_is_rx_fifo_empty(struct rp1_pio_client *client, uint sm)
+{
+ struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = false };
+ int ret;
+
+ if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+ return -EINVAL;
+ ret = rp1_pio_sm_fifo_state(client, &args);
+ if (ret == sizeof(args))
+ ret = args.empty;
+ return ret;
+};
+
+static inline int pio_sm_is_rx_fifo_full(struct rp1_pio_client *client, uint sm)
+{
+ struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = false };
+ int ret;
+
+ if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+ return -EINVAL;
+ ret = rp1_pio_sm_fifo_state(client, &args);
+ if (ret == sizeof(args))
+ ret = args.full;
+ return ret;
+};
+
+static inline int pio_sm_rx_fifo_level(struct rp1_pio_client *client, uint sm)
+{
+ struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = false };
+ int ret;
+
+ if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+ return -EINVAL;
+ ret = rp1_pio_sm_fifo_state(client, &args);
+ if (ret == sizeof(args))
+ ret = args.level;
+ return ret;
+};
+
+static inline int pio_sm_is_tx_fifo_empty(struct rp1_pio_client *client, uint sm)
+{
+ struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = true };
+ int ret;
+
+ if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+ return -EINVAL;
+ ret = rp1_pio_sm_fifo_state(client, &args);
+ if (ret == sizeof(args))
+ ret = args.empty;
+ return ret;
+};
+
+static inline int pio_sm_is_tx_fifo_full(struct rp1_pio_client *client, uint sm)
+{
+ struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = true };
+ int ret;
+
+ if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+ return -EINVAL;
+ ret = rp1_pio_sm_fifo_state(client, &args);
+ if (ret == sizeof(args))
+ ret = args.full;
+ return ret;
+};
+
+static inline int pio_sm_tx_fifo_level(struct rp1_pio_client *client, uint sm)
+{
+ struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = true };
+ int ret;
+
+ if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+ return -EINVAL;
+ ret = rp1_pio_sm_fifo_state(client, &args);
+ if (ret == sizeof(args))
+ ret = args.level;
+ return ret;
+};
+
static inline void sm_config_set_out_pins(pio_sm_config *c, uint out_base, uint out_count)
{
if (bad_params_if(NULL, out_base >= RP1_PIO_GPIO_COUNT ||
--- a/include/uapi/misc/rp1_pio_if.h
+++ b/include/uapi/misc/rp1_pio_if.h
@@ -114,7 +114,7 @@ struct rp1_pio_sm_get_args {
uint16_t sm;
uint8_t blocking;
uint8_t rsvd;
- uint32_t data; /* IN/OUT */
+ uint32_t data; /* OUT */
};
struct rp1_pio_sm_set_dmactrl_args {
@@ -124,6 +124,15 @@ struct rp1_pio_sm_set_dmactrl_args {
uint32_t ctrl;
};
+struct rp1_pio_sm_fifo_state_args {
+ uint16_t sm;
+ uint8_t tx;
+ uint8_t rsvd;
+ uint16_t level; /* OUT */
+ uint8_t empty; /* OUT */
+ uint8_t full; /* OUT */
+};
+
struct rp1_gpio_init_args {
uint16_t gpio;
};
@@ -195,6 +204,8 @@ struct rp1_access_hw_args {
#define PIO_IOC_SM_PUT _IOW(PIO_IOC_MAGIC, 41, struct rp1_pio_sm_put_args)
#define PIO_IOC_SM_GET _IOWR(PIO_IOC_MAGIC, 42, struct rp1_pio_sm_get_args)
#define PIO_IOC_SM_SET_DMACTRL _IOW(PIO_IOC_MAGIC, 43, struct rp1_pio_sm_set_dmactrl_args)
+#define PIO_IOC_SM_FIFO_STATE _IOW(PIO_IOC_MAGIC, 44, struct rp1_pio_sm_fifo_state_args)
+#define PIO_IOC_SM_DRAIN_TX _IOW(PIO_IOC_MAGIC, 45, struct rp1_pio_sm_clear_fifos_args)
#define PIO_IOC_GPIO_INIT _IOW(PIO_IOC_MAGIC, 50, struct rp1_gpio_init_args)
#define PIO_IOC_GPIO_SET_FUNCTION _IOW(PIO_IOC_MAGIC, 51, struct rp1_gpio_set_function_args)

View File

@ -0,0 +1,25 @@
From 3687701e8d252864f440f91f1aedf8ffd58d6ee6 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Mon, 25 Nov 2024 21:51:13 +0000
Subject: [PATCH] misc: rp1-pio: Fix parameter checks wihout client
Passing bad parameters to an API call without a pio pointer will cause
a NULL pointer exception when the persistent error is set. Guard
against that.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
include/linux/pio_rp1.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/include/linux/pio_rp1.h
+++ b/include/linux/pio_rp1.h
@@ -20,7 +20,7 @@
#endif
#define bad_params_if(client, test) \
- ({ bool f = (test); if (f) pio_set_error(client, -EINVAL); \
+ ({ bool f = (test); if (f && client) pio_set_error(client, -EINVAL); \
if (f && PARAM_WARNINGS_ENABLED) WARN_ON((test)); \
f; })

View File

@ -0,0 +1,91 @@
From b4472d09b1ffdafd8132803ffbec62596e559fd8 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Mon, 18 Nov 2024 09:10:52 +0000
Subject: [PATCH 1394/1482] misc: rp1-pio: Add compat_ioctl method
Provide a compat_ioctl method, to support running a 64-bit kernel with
a 32-bit userland.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
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, &param);
+ }
+
+ case PIO_IOC_READ_HW_COMPAT:
+ case PIO_IOC_WRITE_HW_COMPAT:
+ {
+ struct rp1_access_hw_args_compat compat_param;
+ struct rp1_access_hw_args param;
+
+ if (copy_from_user(&compat_param, compat_ptr(ioctl_param), sizeof(compat_param)))
+ return -EFAULT;
+ param.addr = compat_param.addr;
+ param.len = compat_param.len;
+ param.data = compat_ptr(compat_param.data);
+ if (ioctl_num == PIO_IOC_READ_HW_COMPAT)
+ return rp1_pio_read_hw(client, &param);
+ else
+ return rp1_pio_write_hw(client, &param);
+ }
+ default:
+ return rp1_pio_ioctl(filp, ioctl_num, ioctl_param);
+ }
+}
+#else
+#define rp1_pio_compat_ioctl NULL
+#endif
+
const struct file_operations rp1_pio_fops = {
.owner = THIS_MODULE,
.open = rp1_pio_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)

View File

@ -0,0 +1,43 @@
From f9f0024bd9bf04a58b64bae356be4c04022d23bc Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Mon, 11 Nov 2024 16:40:07 +0000
Subject: [PATCH 1423/1482] net: macb: Add support for Raspberry Pi RP1
ethernet controller
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 <dave.stevenson@raspberrypi.com>
---
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},

View File

@ -0,0 +1,27 @@
From 33c225f622d596034a9261316666089a92aa6834 Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Mon, 25 Nov 2024 12:30:06 +0000
Subject: [PATCH 1424/1482] rp1: clk: Only set PLL_SEC_RST in
rp1_pll_divider_off
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 <dave.stevenson@raspberrypi.com>
---
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);
}

View File

@ -0,0 +1,124 @@
From eb836a6a299322a8e2b9627cccd23c7a76d068ba Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Fri, 8 Nov 2024 17:36:13 +0000
Subject: [PATCH 1425/1482] rp1: clk: Rationalise the use of the
CLK_IS_CRITICAL flag
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 <dave.stevenson@raspberrypi.com>
---
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;

View File

@ -0,0 +1,50 @@
From 0b4af929b7125abd3a262577b380c7c81ee9b1c5 Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Mon, 11 Nov 2024 15:18:14 +0000
Subject: [PATCH 1426/1482] dt: arm64: Fixup RP1 ethernet DT configuration
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 <dave.stevenson@raspberrypi.com>
---
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 = <RP1_INT_ETH IRQ_TYPE_LEVEL_HIGH>;
- 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>;

View File

@ -0,0 +1,44 @@
From d4e41ed9954fa86c4774f98d393aa401c81a68e7 Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Wed, 13 Nov 2024 13:10:27 +0000
Subject: [PATCH 1427/1482] clk: rp1: Add RP1_CLK_DMA.
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 <dave.stevenson@raspberrypi.com>
---
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",

View File

@ -0,0 +1,59 @@
From 9049e4df2c54b5e620f855f66db3a18c9f2e181f Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Fri, 8 Nov 2024 17:37:08 +0000
Subject: [PATCH 1428/1482] rp1: clk: Remove CLK_IGNORE_UNUSED flags
There should be no issue in disabling the RP1 clocks as long as
the kernel knows about all consumers.
Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
---
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);

View File

@ -0,0 +1,45 @@
From 542d0f7f2e9f90fc0f02f8cb141f7c3fbf46081b Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Mon, 11 Nov 2024 17:11:18 +0000
Subject: [PATCH 1429/1482] dt: rp1: Use clk_sys for ethernet hclk and pclk
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 <dave.stevenson@raspberrypi.com>
---
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 = <RP1_INT_ETH IRQ_TYPE_LEVEL_HIGH>;
- 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";

View File

@ -0,0 +1,24 @@
From efecbda4014b490e042c7fd090942b32316f9345 Mon Sep 17 00:00:00 2001
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Date: Wed, 13 Nov 2024 13:11:33 +0000
Subject: [PATCH 1430/1482] dt: rp1: Link RP1 DMA to the associated clock
This makes the kernel representation of the clock structure
match reality.
Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
---
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 = <RP1_INT_DMA IRQ_TYPE_LEVEL_HIGH>;
- 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>;

View File

@ -0,0 +1,762 @@
From 7735dd0736322cff23aff95490bae1d69937a9bf Mon Sep 17 00:00:00 2001
From: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
Date: Tue, 10 Dec 2024 13:23:09 +0000
Subject: [PATCH 1456/1482] drm: rp1: rp1-dpi: Add interlaced modes and PIO
program to fix VSYNC
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 <nick.hollinghurst@raspberrypi.com>
---
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 <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/of.h>
+#include <linux/pio_rp1.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <drm/drm_print.h>
+
+#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 ; <modify for -DE fixup>
+ 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 ; <modify for -DE fixup>
+ 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;
+ }
+}

View File

@ -0,0 +1,22 @@
From f85f3509692f966ec32e4db499f7e64dc6b6b952 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Thu, 12 Dec 2024 10:09:13 +0000
Subject: [PATCH 1457/1482] pwm: Improve PWM_PIO_RP1 dependencies
PWM_PIO_RP1 should select RP1_PIO, as it is useless without it.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
drivers/pwm/Kconfig | 1 +
1 file changed, 1 insertion(+)
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -457,6 +457,7 @@ config PWM_PCA9685
config PWM_PIO_RP1
tristate "RP1 PIO PWM support"
depends on FIRMWARE_RP1 || COMPILE_TEST
+ select RP1_PIO
help
This is a PWM framework driver for Raspberry Pi 5, using the PIO
hardware of RP1 to provide PWM functionality. Supports up to 4

View File

@ -0,0 +1,20 @@
From 73fb1e979a210094935f4af4c3d6e700fba30c5f Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Thu, 12 Dec 2024 10:28:54 +0000
Subject: [PATCH 1458/1482] Revert "pwm: Improve PWM_PIO_RP1 dependencies"
This reverts commit f85f3509692f966ec32e4db499f7e64dc6b6b952.
---
drivers/pwm/Kconfig | 1 -
1 file changed, 1 deletion(-)
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -457,7 +457,6 @@ config PWM_PCA9685
config PWM_PIO_RP1
tristate "RP1 PIO PWM support"
depends on FIRMWARE_RP1 || COMPILE_TEST
- select RP1_PIO
help
This is a PWM framework driver for Raspberry Pi 5, using the PIO
hardware of RP1 to provide PWM functionality. Supports up to 4

View File

@ -0,0 +1,99 @@
From 80533a952218696c0ef1b346bab50dc401e6b74c Mon Sep 17 00:00:00 2001
From: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
Date: Thu, 12 Dec 2024 11:58:12 +0000
Subject: [PATCH 1463/1482] drm: rp1: rp1-dpi: Fix optional dependency on
RP1_PIO
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 <nick.hollinghurst@raspberrypi.com>
---
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 <linux/kernel.h>
#include <linux/errno.h>
#include <linux/of.h>
-#include <linux/pio_rp1.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <drm/drm_print.h>
#include "rp1_dpi.h"
+#if IS_REACHABLE(CONFIG_RP1_PIO)
+
+#include <linux/pio_rp1.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).
@@ -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

View File

@ -0,0 +1,127 @@
From 4b0ca96738bb937529655a0062d60775f47b0f5e Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Mon, 16 Dec 2024 23:01:41 +0000
Subject: [PATCH 1468/1482] misc: rp1-pio: Support larger data transfers
Add a separate IOCTL for larger transfer with a 32-bit data_bytes
field.
See: https://github.com/raspberrypi/utils/issues/107
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
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, &param);
}
+ case PIO_IOC_SM_XFER_DATA32_COMPAT:
+ {
+ struct rp1_pio_sm_xfer_data32_args_compat compat_param;
+ struct rp1_pio_sm_xfer_data32_args param;
+
+ if (copy_from_user(&compat_param, compat_ptr(ioctl_param), sizeof(compat_param)))
+ return -EFAULT;
+ param.sm = compat_param.sm;
+ param.dir = compat_param.dir;
+ param.data_bytes = compat_param.data_bytes;
+ param.data = compat_ptr(compat_param.data);
+ return rp1_pio_sm_xfer_data32(client, &param);
+ }
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)

View File

@ -0,0 +1,24 @@
From cd26850713088942ca4f9a248a8bed1f0504a58f Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Thu, 19 Dec 2024 15:11:40 +0000
Subject: [PATCH 1471/1482] fixup! misc: Add RP1 PIO driver
Change the Kconfig dependencies so that RP1_PIO depends on FIRMWARE_RP1,
rather than selecting it.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
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

View File

@ -0,0 +1,81 @@
From 468b525d45a726e4ba704b33c4eba53de47ac684 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Thu, 5 Dec 2024 16:03:39 +0000
Subject: [PATCH 1473/1482] misc: rp1-pio: More logical probe sequence
Sort the probe function initialisation into a more logical order.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
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);

View File

@ -0,0 +1,92 @@
From 5c07ba20630a629399eaa6583457aca93ff74606 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Mon, 9 Dec 2024 09:58:29 +0000
Subject: [PATCH 1474/1482] misc: rp1-pio: Convert floats to 24.8 fixed point
Floating point arithmetic is not supported in the kernel, so use fixed
point instead.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
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);
}

View File

@ -0,0 +1,42 @@
From 75203c6641cfe47dfb817b095430021b0981ff47 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Tue, 10 Dec 2024 12:06:14 +0000
Subject: [PATCH 1475/1482] misc: rp1-pio: Minor cosmetic tweaks
No functional change.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
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;

View File

@ -0,0 +1,508 @@
From fddd3e9318dbf01fb763b6880021abc558fce8e6 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Thu, 12 Dec 2024 17:09:27 +0000
Subject: [PATCH 1476/1482] misc: rp1-pio: Add in-kernel DMA support
Add kernel-facing implementations of pio_sm_config_xfer and
pio_xm_xfer_data.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
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, &param);
+ return rp1_pio_sm_xfer_data_user(client, &param);
}
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, &param);
+ return rp1_pio_sm_xfer_data32_user(client, &param);
}
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)
{

View File

@ -0,0 +1,568 @@
From d6d83ad3d9a3a594909a1ad1c82b735ab711cd12 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Tue, 3 Dec 2024 16:09:30 +0000
Subject: [PATCH 1477/1482] misc: Add ws2812-pio-rp1 driver
ws2812-pio-rp1 is a PIO-based driver for WS2812 LEDS. It creates a
character device in /dev, the default name of which is /dev/leds<n>,
where <n> 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 <phil@raspberrypi.com>
---
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 <gordon@raspberrypi.com>
+ */
+
+#include <linux/cdev.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fcntl.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/pio_rp1.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/gpio/consumer.h>
+
+#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");

View File

@ -0,0 +1,107 @@
From 4a8f2b39157825fefc505fe4b94f3a9ce101e170 Mon Sep 17 00:00:00 2001
From: Phil Elwell <phil@raspberrypi.com>
Date: Thu, 12 Dec 2024 23:23:39 +0000
Subject: [PATCH 1478/1482] overlays: Add ws2812-pio overlay
Add an overlay to enable a WS2812 LED driver on a given GPIO.
Signed-off-by: Phil Elwell <phil@raspberrypi.com>
---
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,<param>=<val>
+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?";
+ };
+};