* [RFC PATCH v5 5/9] media: chips-media: wave6: Add Wave6 core driver
From: Nas Chung @ 2026-04-15 9:25 UTC (permalink / raw)
To: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer
Cc: linux-media, devicetree, linux-kernel, linux-imx,
linux-arm-kernel, marek.vasut, ming.qian, Nas Chung
In-Reply-To: <20260415092529.577-1-nas.chung@chipsnmedia.com>
Add the core driver for the Chips&Media Wave6 video codec IP.
The hardware contains one control register region and four interface
register regions for a shared video processing engine. This driver
handles the interface register regions, each with its own MMIO range and
interrupt, while relying on the control driver for firmware loading and
shared resource management.
It configures the V4L2 mem2mem devices and communicates with the Wave6
hardware to perform video processing tasks.
Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
Tested-by: Ming Qian <ming.qian@oss.nxp.com>
Tested-by: Marek Vasut <marek.vasut@mailbox.org>
---
.../chips-media/wave6/wave6-vpu-core.c | 397 ++++++++++++++++++
.../chips-media/wave6/wave6-vpu-core.h | 123 ++++++
2 files changed, 520 insertions(+)
create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-core.c
create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-core.h
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-core.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-core.c
new file mode 100644
index 000000000000..d666a6bb22e8
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-core.c
@@ -0,0 +1,397 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Wave6 series multi-standard codec IP - wave6 core driver
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/pm_runtime.h>
+#include <linux/debugfs.h>
+#include <linux/iopoll.h>
+#include "wave6-vpu-core.h"
+#include "wave6-regdefine.h"
+#include "wave6-vpuconfig.h"
+#include "wave6-hw.h"
+#include "wave6-vpu-dbg.h"
+
+#define CREATE_TRACE_POINTS
+#include "wave6-trace.h"
+
+#define WAVE6_VPU_DEBUGFS_DIR "wave6"
+
+static irqreturn_t wave6_vpu_core_irq(int irq, void *dev_id)
+{
+ struct vpu_core_device *core = dev_id;
+ struct vpu_irq irq_info;
+
+ if (!vpu_read_reg(core, W6_VPU_VPU_INT_STS))
+ return IRQ_NONE;
+
+ irq_info.status = vpu_read_reg(core, W6_VPU_VINT_REASON);
+ irq_info.inst_idc = vpu_read_reg(core, W6_RET_INT_INSTANCE_INFO);
+
+ vpu_write_reg(core, W6_RET_INT_INSTANCE_INFO, INT_INSTANCE_INFO_CLEAR);
+ vpu_write_reg(core, W6_VPU_VINT_REASON_CLEAR, irq_info.status);
+ vpu_write_reg(core, W6_VPU_VINT_CLEAR, VINT_CLEAR);
+
+ trace_wave6_vpu_irq(core, irq_info.status, irq_info.inst_idc);
+
+ if (irq_info.status & BIT(W6_INT_BIT_REQ_WORK_BUF)) {
+ if (core->vpu)
+ core->vpu->req_work_buffer(core->vpu, core);
+
+ return IRQ_HANDLED;
+ }
+
+ kfifo_in(&core->irq_fifo, &irq_info, sizeof(struct vpu_irq));
+
+ return IRQ_WAKE_THREAD;
+}
+
+static struct vpu_instance *wave6_vpu_core_get_instance(struct vpu_core_device *core,
+ u32 inst_idc)
+{
+ struct vpu_instance *inst;
+
+ guard(spinlock)(&core->inst_lock);
+
+ list_for_each_entry(inst, &core->instances, list) {
+ if (BIT(inst->id) & inst_idc)
+ return inst;
+ }
+
+ return NULL;
+}
+
+static irqreturn_t wave6_vpu_core_irq_thread(int irq, void *dev_id)
+{
+ struct vpu_core_device *core = dev_id;
+ struct vpu_instance *inst;
+ struct vpu_irq irq_info;
+
+ while (kfifo_len(&core->irq_fifo)) {
+ bool error = false;
+
+ if (!kfifo_out(&core->irq_fifo, &irq_info, sizeof(struct vpu_irq)))
+ break;
+
+ inst = wave6_vpu_core_get_instance(core, irq_info.inst_idc);
+ if (!inst)
+ break;
+
+ if ((irq_info.status & BIT(W6_INT_BIT_INIT_SEQ)) ||
+ (irq_info.status & BIT(W6_INT_BIT_ENC_SET_PARAM))) {
+ complete(&inst->irq_done);
+ continue;
+ }
+
+ if (irq_info.status & BIT(W6_INT_BIT_BSBUF_ERROR))
+ error = true;
+
+ if (inst->ops && inst->ops->finish_process)
+ inst->ops->finish_process(inst, error);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void wave6_vpu_core_check_state(struct vpu_core_device *core)
+{
+ u32 val;
+ int ret;
+
+ guard(mutex)(&core->hw_lock);
+
+ ret = read_poll_timeout(vpu_read_reg, val, val != 0,
+ W6_VPU_POLL_DELAY_US, W6_VPU_POLL_TIMEOUT,
+ false, core, W6_VPU_VCPU_CUR_PC);
+ if (ret)
+ return;
+
+ wave6_vpu_enable_interrupt(core);
+ ret = wave6_vpu_get_version(core);
+ if (ret) {
+ dev_err(core->dev, "wave6_vpu_get_version fail\n");
+ return;
+ }
+
+ dev_dbg(core->dev, "product 0x%x, fw_ver %d.%d.%d(r%d), hw_ver 0x%x\n",
+ core->attr.product_code,
+ FW_VERSION_MAJOR(core->attr.fw_version),
+ FW_VERSION_MINOR(core->attr.fw_version),
+ FW_VERSION_REL(core->attr.fw_version),
+ core->attr.fw_revision,
+ core->attr.hw_version);
+
+ if (core->attr.fw_version < core->res->compatible_fw_version)
+ dev_err(core->dev, "fw version is too low (< v%d.%d.%d)\n",
+ FW_VERSION_MAJOR(core->res->compatible_fw_version),
+ FW_VERSION_MINOR(core->res->compatible_fw_version),
+ FW_VERSION_REL(core->res->compatible_fw_version));
+}
+
+void wave6_vpu_core_activate(struct vpu_core_device *core)
+{
+ core->active = true;
+}
+
+static void wave6_vpu_core_wait_activated(struct vpu_core_device *core)
+{
+ if (core->active)
+ wave6_vpu_core_check_state(core);
+}
+
+static int wave6_vpu_core_probe(struct platform_device *pdev)
+{
+ struct vpu_core_device *core;
+ const struct wave6_vpu_core_resource *res;
+ int ret;
+ int irq;
+
+ res = dev_get_platdata(&pdev->dev);
+ if (!res) {
+ dev_err(&pdev->dev, "There is no platform data\n");
+ return -ENODEV;
+ }
+
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to set DMA mask: %d\n", ret);
+ return ret;
+ }
+
+ core = devm_kzalloc(&pdev->dev, sizeof(*core), GFP_KERNEL);
+ if (!core)
+ return -ENOMEM;
+
+ ret = devm_mutex_init(&pdev->dev, &core->dev_lock);
+ if (ret)
+ return ret;
+
+ ret = devm_mutex_init(&pdev->dev, &core->hw_lock);
+ if (ret)
+ return ret;
+
+ spin_lock_init(&core->inst_lock);
+ INIT_LIST_HEAD(&core->instances);
+ dev_set_drvdata(&pdev->dev, core);
+ core->dev = &pdev->dev;
+ core->res = res;
+
+ if (pdev->dev.parent->driver && pdev->dev.parent->driver->name &&
+ !strcmp(pdev->dev.parent->driver->name, WAVE6_VPU_PLATFORM_DRIVER_NAME))
+ core->vpu = dev_get_drvdata(pdev->dev.parent);
+
+ core->reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(core->reg_base))
+ return PTR_ERR(core->reg_base);
+
+ ret = devm_clk_bulk_get_all(&pdev->dev, &core->clks);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "failed to get clocks\n");
+
+ core->num_clks = ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq,
+ wave6_vpu_core_irq,
+ wave6_vpu_core_irq_thread,
+ 0, "vpu_irq", core);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request IRQ: %d\n", ret);
+ return ret;
+ }
+
+ ret = v4l2_device_register(&pdev->dev, &core->v4l2_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register v4l2_dev: %d\n", ret);
+ return ret;
+ }
+
+ ret = wave6_vpu_init_m2m_dev(core);
+ if (ret)
+ goto err_v4l2_unregister;
+
+ ret = kfifo_alloc(&core->irq_fifo,
+ MAX_NUM_INSTANCE * sizeof(struct vpu_irq),
+ GFP_KERNEL);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to allocate fifo\n");
+ goto err_m2m_dev_release;
+ }
+
+ core->temp_vbuf.size = ALIGN(W6_TEMPBUF_SIZE, 4096);
+ ret = wave6_vdi_alloc_dma(core->dev, &core->temp_vbuf);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to allocate temp_vbuf: %d\n", ret);
+ goto err_kfifo_free;
+ }
+
+ core->debugfs = debugfs_lookup(WAVE6_VPU_DEBUGFS_DIR, NULL);
+ if (IS_ERR_OR_NULL(core->debugfs))
+ core->debugfs = debugfs_create_dir(WAVE6_VPU_DEBUGFS_DIR, NULL);
+
+ pm_runtime_enable(&pdev->dev);
+
+ if (core->res->codec_types & WAVE6_IS_DEC) {
+ ret = wave6_vpu_dec_register_device(core);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to register video_dev_dec: %d\n", ret);
+ goto err_temp_vbuf_free;
+ }
+ }
+ if (core->res->codec_types & WAVE6_IS_ENC) {
+ ret = wave6_vpu_enc_register_device(core);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to register video_dev_enc: %d\n", ret);
+ goto err_dec_unreg;
+ }
+ }
+
+ dev_dbg(&pdev->dev, "Added wave6 driver with caps %s %s\n",
+ core->res->codec_types & WAVE6_IS_ENC ? "'ENCODE'" : "",
+ core->res->codec_types & WAVE6_IS_DEC ? "'DECODE'" : "");
+
+ return 0;
+
+err_dec_unreg:
+ if (core->res->codec_types & WAVE6_IS_DEC)
+ wave6_vpu_dec_unregister_device(core);
+err_temp_vbuf_free:
+ wave6_vdi_free_dma(&core->temp_vbuf);
+err_kfifo_free:
+ kfifo_free(&core->irq_fifo);
+err_m2m_dev_release:
+ wave6_vpu_release_m2m_dev(core);
+err_v4l2_unregister:
+ v4l2_device_unregister(&core->v4l2_dev);
+
+ return ret;
+}
+
+static void wave6_vpu_core_remove(struct platform_device *pdev)
+{
+ struct vpu_core_device *core = dev_get_drvdata(&pdev->dev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ wave6_vpu_enc_unregister_device(core);
+ wave6_vpu_dec_unregister_device(core);
+ wave6_vdi_free_dma(&core->temp_vbuf);
+ kfifo_free(&core->irq_fifo);
+ wave6_vpu_release_m2m_dev(core);
+ v4l2_device_unregister(&core->v4l2_dev);
+}
+
+static int __maybe_unused wave6_vpu_core_runtime_suspend(struct device *dev)
+{
+ struct vpu_core_device *core = dev_get_drvdata(dev);
+
+ if (WARN_ON(!core))
+ return -ENODEV;
+
+ /*
+ * Only call parent VPU put_vpu if the core has a parent and is active.
+ * - core->vpu: prevent access in core without parent VPU.
+ * - core->active: execute sleep only after m2m streaming is started.
+ */
+ if (core->vpu && core->active)
+ core->vpu->put_vpu(core->vpu, core);
+
+ if (core->num_clks)
+ clk_bulk_disable_unprepare(core->num_clks, core->clks);
+
+ return 0;
+}
+
+static int __maybe_unused wave6_vpu_core_runtime_resume(struct device *dev)
+{
+ struct vpu_core_device *core = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (WARN_ON(!core))
+ return -ENODEV;
+
+ if (core->num_clks) {
+ ret = clk_bulk_prepare_enable(core->num_clks, core->clks);
+ if (ret) {
+ dev_err(dev, "failed to enable clocks: %d\n", ret);
+ return ret;
+ }
+ }
+
+ /*
+ * Only call parent VPU get_vpu if the core has a parent and is active.
+ * - core->vpu: prevent access in core without parent VPU.
+ * - core->active: execute boot only after m2m streaming is started.
+ */
+ if (core->vpu && core->active)
+ ret = core->vpu->get_vpu(core->vpu, core);
+
+ if (!ret)
+ wave6_vpu_core_wait_activated(core);
+ else if (core->num_clks)
+ clk_bulk_disable_unprepare(core->num_clks, core->clks);
+
+ return ret;
+}
+
+static int __maybe_unused wave6_vpu_core_suspend(struct device *dev)
+{
+ struct vpu_core_device *core = dev_get_drvdata(dev);
+ int ret;
+
+ v4l2_m2m_suspend(core->m2m_dev);
+
+ ret = pm_runtime_force_suspend(dev);
+ if (ret)
+ v4l2_m2m_resume(core->m2m_dev);
+
+ return ret;
+}
+
+static int __maybe_unused wave6_vpu_core_resume(struct device *dev)
+{
+ struct vpu_core_device *core = dev_get_drvdata(dev);
+ int ret;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret)
+ return ret;
+
+ v4l2_m2m_resume(core->m2m_dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops wave6_vpu_core_pm_ops = {
+ SET_RUNTIME_PM_OPS(wave6_vpu_core_runtime_suspend,
+ wave6_vpu_core_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(wave6_vpu_core_suspend,
+ wave6_vpu_core_resume)
+};
+
+static struct platform_driver wave6_vpu_core_driver = {
+ .driver = {
+ .name = WAVE6_VPU_CORE_PLATFORM_DRIVER_NAME,
+ .pm = &wave6_vpu_core_pm_ops,
+ },
+ .probe = wave6_vpu_core_probe,
+ .remove = wave6_vpu_core_remove,
+};
+
+module_platform_driver(wave6_vpu_core_driver);
+MODULE_ALIAS("platform:" WAVE6_VPU_CORE_PLATFORM_DRIVER_NAME);
+MODULE_DESCRIPTION("chips&media Wave6 VPU CORE V4L2 driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-core.h b/drivers/media/platform/chips-media/wave6/wave6-vpu-core.h
new file mode 100644
index 000000000000..728ea5a0578d
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-core.h
@@ -0,0 +1,123 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - wave6 core driver
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#ifndef __WAVE6_VPU_CORE_H__
+#define __WAVE6_VPU_CORE_H__
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+#include "wave6-vpuconfig.h"
+#include "wave6-vpuapi.h"
+
+#define vpu_write_reg(CORE, ADDR, DATA) wave6_vpu_writel(CORE, ADDR, DATA)
+#define vpu_read_reg(CORE, ADDR) wave6_vpu_readl(CORE, ADDR)
+
+struct vpu_buffer {
+ struct v4l2_m2m_buffer v4l2_m2m_buf;
+ bool consumed;
+ bool used;
+ bool error;
+ bool force_key_frame;
+ bool force_frame_qp;
+ u32 force_i_frame_qp;
+ u32 force_p_frame_qp;
+ u32 force_b_frame_qp;
+ ktime_t ts_input;
+ ktime_t ts_start;
+ ktime_t ts_finish;
+ ktime_t ts_output;
+ u64 hw_time;
+ u32 average_qp;
+};
+
+enum vpu_fmt_type {
+ VPU_FMT_TYPE_CODEC = 0,
+ VPU_FMT_TYPE_RAW = 1
+};
+
+#define VPU_FMT_FLAG_CBCR_INTERLEAVED BIT(0)
+#define VPU_FMT_FLAG_CRCB_ORDER BIT(1)
+#define VPU_FMT_FLAG_10BIT BIT(2)
+#define VPU_FMT_FLAG_RGB BIT(3)
+
+struct vpu_format {
+ unsigned int v4l2_pix_fmt;
+ unsigned int max_width;
+ unsigned int min_width;
+ unsigned int max_height;
+ unsigned int min_height;
+ unsigned int num_planes;
+ enum frame_buffer_format fb_fmt;
+ enum endian_mode endian;
+ enum csc_format_order csc_fmt_order;
+ unsigned int flags;
+};
+
+static inline struct vpu_instance *wave6_fh_to_vpu_inst(struct v4l2_fh *vfh)
+{
+ return container_of(vfh, struct vpu_instance, v4l2_fh);
+}
+
+static inline struct vpu_instance *wave6_file_to_vpu_inst(struct file *filp)
+{
+ return wave6_fh_to_vpu_inst(file_to_v4l2_fh(filp));
+}
+
+static inline struct vpu_instance *wave6_ctrl_to_vpu_inst(struct v4l2_ctrl *vctrl)
+{
+ return container_of(vctrl->handler, struct vpu_instance, v4l2_ctrl_hdl);
+}
+
+static inline struct vpu_buffer *wave6_to_vpu_buf(struct vb2_v4l2_buffer *vbuf)
+{
+ return container_of(vbuf, struct vpu_buffer, v4l2_m2m_buf.vb);
+}
+
+static inline bool wave6_vpu_both_queues_are_streaming(struct vpu_instance *inst)
+{
+ struct vb2_queue *vq_cap = v4l2_m2m_get_dst_vq(inst->v4l2_fh.m2m_ctx);
+ struct vb2_queue *vq_out = v4l2_m2m_get_src_vq(inst->v4l2_fh.m2m_ctx);
+
+ return vb2_is_streaming(vq_cap) && vb2_is_streaming(vq_out);
+}
+
+u32 wave6_vpu_get_consumed_fb_num(struct vpu_instance *inst);
+void wave6_vpu_core_activate(struct vpu_core_device *core);
+void wave6_update_pix_fmt(struct v4l2_pix_format_mplane *pix_mp,
+ unsigned int width,
+ unsigned int height);
+struct vb2_v4l2_buffer *wave6_get_dst_buf_by_addr(struct vpu_instance *inst,
+ dma_addr_t addr);
+dma_addr_t wave6_get_dma_addr(struct vb2_v4l2_buffer *buf,
+ unsigned int plane_no);
+enum codec_std wave6_to_codec_std(enum vpu_instance_type type, unsigned int v4l2_pix_fmt);
+const char *wave6_vpu_instance_state_name(enum vpu_instance_state state);
+void wave6_vpu_set_instance_state(struct vpu_instance *inst,
+ enum vpu_instance_state state);
+u64 wave6_vpu_cycle_to_ns(struct vpu_core_device *core, u64 cycle);
+int wave6_vpu_wait_interrupt(struct vpu_instance *inst, unsigned int timeout);
+int wave6_vpu_dec_register_device(struct vpu_core_device *core);
+void wave6_vpu_dec_unregister_device(struct vpu_core_device *core);
+int wave6_vpu_enc_register_device(struct vpu_core_device *core);
+void wave6_vpu_enc_unregister_device(struct vpu_core_device *core);
+void wave6_vpu_finish_job(struct vpu_instance *inst);
+void wave6_vpu_record_performance_timestamps(struct vpu_instance *inst);
+void wave6_vpu_handle_performance(struct vpu_instance *inst,
+ struct vpu_buffer *vpu_buf);
+void wave6_vpu_reset_performance(struct vpu_instance *inst);
+int wave6_vpu_init_m2m_dev(struct vpu_core_device *core);
+void wave6_vpu_release_m2m_dev(struct vpu_core_device *core);
+int wave6_vpu_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub);
+void wave6_vpu_return_buffers(struct vpu_instance *inst,
+ unsigned int type, enum vb2_buffer_state state);
+
+#endif /* __WAVE6_VPU_CORE_H__ */
--
2.31.1
^ permalink raw reply related
* [RFC PATCH v5 6/9] media: chips-media: wave6: Improve debugging capabilities
From: Nas Chung @ 2026-04-15 9:25 UTC (permalink / raw)
To: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer
Cc: linux-media, devicetree, linux-kernel, linux-imx,
linux-arm-kernel, marek.vasut, ming.qian, Nas Chung
In-Reply-To: <20260415092529.577-1-nas.chung@chipsnmedia.com>
Add debugfs entries and trace events to provide detailed
debugging information.
These enhancements help diagnose issues and improve debugging
capabilities for the Wave6 core driver.
Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
Tested-by: Ming Qian <ming.qian@oss.nxp.com>
Tested-by: Marek Vasut <marek.vasut@mailbox.org>
---
.../platform/chips-media/wave6/wave6-trace.h | 289 ++++++++++++++++++
.../chips-media/wave6/wave6-vpu-dbg.c | 225 ++++++++++++++
.../chips-media/wave6/wave6-vpu-dbg.h | 14 +
3 files changed, 528 insertions(+)
create mode 100644 drivers/media/platform/chips-media/wave6/wave6-trace.h
create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.c
create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.h
diff --git a/drivers/media/platform/chips-media/wave6/wave6-trace.h b/drivers/media/platform/chips-media/wave6/wave6-trace.h
new file mode 100644
index 000000000000..2c80923e2f29
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-trace.h
@@ -0,0 +1,289 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - wave6 driver tracer
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM wave6
+
+#if !defined(__WAVE6_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ)
+#define __WAVE6_TRACE_H__
+
+#include <linux/tracepoint.h>
+#include <media/videobuf2-v4l2.h>
+
+DECLARE_EVENT_CLASS(wave6_vpu_register_access,
+ TP_PROTO(struct device *dev, u32 addr, u32 value),
+ TP_ARGS(dev, addr, value),
+ TP_STRUCT__entry(__string(name, dev_name(dev))
+ __field(u32, addr)
+ __field(u32, value)),
+ TP_fast_assign(__assign_str(name);
+ __entry->addr = addr;
+ __entry->value = value;),
+ TP_printk("%s:0x%03x 0x%08x",
+ __get_str(name), __entry->addr, __entry->value));
+
+DEFINE_EVENT(wave6_vpu_register_access, wave6_vpu_writel,
+ TP_PROTO(struct device *dev, u32 addr, u32 value),
+ TP_ARGS(dev, addr, value));
+DEFINE_EVENT(wave6_vpu_register_access, wave6_vpu_readl,
+ TP_PROTO(struct device *dev, u32 addr, u32 value),
+ TP_ARGS(dev, addr, value));
+
+TRACE_EVENT(wave6_vpu_send_command,
+ TP_PROTO(struct vpu_core_device *core, u32 id, u32 std, u32 cmd),
+ TP_ARGS(core, id, std, cmd),
+ TP_STRUCT__entry(__string(name, dev_name(core->dev))
+ __field(u32, id)
+ __field(u32, std)
+ __field(u32, cmd)),
+ TP_fast_assign(__assign_str(name);
+ __entry->id = id;
+ __entry->std = std;
+ __entry->cmd = cmd;),
+ TP_printk("%s: inst id %d, std 0x%x, cmd 0x%x",
+ __get_str(name), __entry->id,
+ __entry->std, __entry->cmd));
+
+TRACE_EVENT(wave6_vpu_irq,
+ TP_PROTO(struct vpu_core_device *core, u32 irq, u32 idc),
+ TP_ARGS(core, irq, idc),
+ TP_STRUCT__entry(__string(name, dev_name(core->dev))
+ __field(u32, irq)
+ __field(u32, idc)),
+ TP_fast_assign(__assign_str(name);
+ __entry->irq = irq;
+ __entry->idc = idc;),
+ TP_printk("%s: irq 0x%x, idc 0x%x",
+ __get_str(name), __entry->irq, __entry->idc));
+
+TRACE_EVENT(wave6_vpu_set_state,
+ TP_PROTO(struct vpu_instance *inst, u32 state),
+ TP_ARGS(inst, state),
+ TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+ __field(u32, id)
+ __string(cur_state, wave6_vpu_instance_state_name(inst->state))
+ __string(nxt_state, wave6_vpu_instance_state_name(state))),
+ TP_fast_assign(__assign_str(name);
+ __entry->id = inst->id;
+ __assign_str(cur_state);
+ __assign_str(nxt_state);),
+ TP_printk("%s: inst[%d] set state %s -> %s",
+ __get_str(name), __entry->id,
+ __get_str(cur_state), __get_str(nxt_state)));
+
+DECLARE_EVENT_CLASS(wave6_vpu_inst_internal,
+ TP_PROTO(struct vpu_instance *inst, bool is_out),
+ TP_ARGS(inst, is_out),
+ TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+ __field(u32, id)
+ __string(type, is_out ? "output" : "capture")
+ __field(u32, pixelformat)
+ __field(u32, width)
+ __field(u32, height)
+ __field(u32, buf_cnt_src)
+ __field(u32, buf_cnt_dst)
+ __field(u32, processed_cnt)
+ __field(u32, error_cnt)),
+ TP_fast_assign(__assign_str(name);
+ __entry->id = inst->id;
+ __assign_str(type);
+ __entry->pixelformat = is_out ? inst->src_fmt.pixelformat :
+ inst->dst_fmt.pixelformat;
+ __entry->width = is_out ? inst->src_fmt.width :
+ inst->dst_fmt.width;
+ __entry->height = is_out ? inst->src_fmt.height :
+ inst->dst_fmt.height;
+ __entry->buf_cnt_src = inst->queued_src_buf_num;
+ __entry->buf_cnt_dst = inst->queued_dst_buf_num;
+ __entry->processed_cnt = inst->processed_buf_num;
+ __entry->error_cnt = inst->error_buf_num;),
+ TP_printk("%s: inst[%d] %s %c%c%c%c %dx%d, input %d, %d, process %d, error %d",
+ __get_str(name), __entry->id, __get_str(type),
+ __entry->pixelformat,
+ __entry->pixelformat >> 8,
+ __entry->pixelformat >> 16,
+ __entry->pixelformat >> 24,
+ __entry->width, __entry->height,
+ __entry->buf_cnt_src, __entry->buf_cnt_dst,
+ __entry->processed_cnt, __entry->error_cnt));
+
+DEFINE_EVENT(wave6_vpu_inst_internal, wave6_vpu_start_streaming,
+ TP_PROTO(struct vpu_instance *inst, bool is_out),
+ TP_ARGS(inst, is_out));
+
+DEFINE_EVENT(wave6_vpu_inst_internal, wave6_vpu_stop_streaming,
+ TP_PROTO(struct vpu_instance *inst, bool is_out),
+ TP_ARGS(inst, is_out));
+
+TRACE_EVENT(wave6_vpu_dec_pic,
+ TP_PROTO(struct vpu_instance *inst, u32 srcidx, u32 size),
+ TP_ARGS(inst, srcidx, size),
+ TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+ __field(u32, id)
+ __field(u32, srcidx)
+ __field(u32, start)
+ __field(u32, size)),
+ TP_fast_assign(__assign_str(name);
+ __entry->id = inst->id;
+ __entry->srcidx = srcidx;
+ __entry->start = inst->codec_info->dec_info.stream_rd_ptr;
+ __entry->size = size;),
+ TP_printk("%s: inst[%d] src[%2d] %8x, %d",
+ __get_str(name), __entry->id,
+ __entry->srcidx, __entry->start, __entry->size));
+
+TRACE_EVENT(wave6_vpu_source_change,
+ TP_PROTO(struct vpu_instance *inst, struct dec_seq_info *info),
+ TP_ARGS(inst, info),
+ TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+ __field(u32, id)
+ __field(u32, width)
+ __field(u32, height)
+ __field(u32, profile)
+ __field(u32, level)
+ __field(u32, tier)
+ __field(u32, min_fb_cnt)
+ __field(u32, disp_delay)
+ __field(u32, quantization)
+ __field(u32, colorspace)
+ __field(u32, xfer_func)
+ __field(u32, ycbcr_enc)),
+ TP_fast_assign(__assign_str(name);
+ __entry->id = inst->id;
+ __entry->width = info->pic_width,
+ __entry->height = info->pic_height,
+ __entry->profile = info->profile,
+ __entry->level = info->level;
+ __entry->tier = info->tier;
+ __entry->min_fb_cnt = info->min_frame_buffer_count;
+ __entry->disp_delay = info->frame_buf_delay;
+ __entry->quantization = inst->quantization;
+ __entry->colorspace = inst->colorspace;
+ __entry->xfer_func = inst->xfer_func;
+ __entry->ycbcr_enc = inst->ycbcr_enc;),
+ TP_printk("%s: inst[%d] %dx%d profile %d %d %d min_fb %d delay %d color %d %d %d %d",
+ __get_str(name), __entry->id,
+ __entry->width, __entry->height,
+ __entry->profile, __entry->level, __entry->tier,
+ __entry->min_fb_cnt, __entry->disp_delay,
+ __entry->quantization, __entry->colorspace,
+ __entry->xfer_func, __entry->ycbcr_enc));
+
+TRACE_EVENT(wave6_vpu_dec_done,
+ TP_PROTO(struct vpu_instance *inst, struct dec_output_info *info),
+ TP_ARGS(inst, info),
+ TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+ __field(u32, id)
+ __field(u32, dec_flag)
+ __field(u32, dec_poc)
+ __field(u32, disp_flag)
+ __field(u32, disp_cnt)
+ __field(u32, rel_cnt)
+ __field(u32, src_ch)
+ __field(u32, eos)
+ __field(u32, error)
+ __field(u32, warn)),
+ TP_fast_assign(__assign_str(name);
+ __entry->id = inst->id;
+ __entry->dec_flag = info->frame_decoded;
+ __entry->dec_poc = info->decoded_poc;
+ __entry->disp_flag = info->frame_display;
+ __entry->disp_cnt = info->disp_frame_num;
+ __entry->rel_cnt = info->release_disp_frame_num;
+ __entry->src_ch = info->notification_flags & DEC_NOTI_FLAG_SEQ_CHANGE;
+ __entry->eos = info->stream_end;
+ __entry->error = info->error_reason;
+ __entry->warn = info->warn_info;),
+ TP_printk("%s: inst[%d] dec %d %d disp %d(%d) rel %d src_ch %d eos %d error 0x%x 0x%x",
+ __get_str(name), __entry->id,
+ __entry->dec_flag, __entry->dec_poc,
+ __entry->disp_flag, __entry->disp_cnt,
+ __entry->rel_cnt,
+ __entry->src_ch, __entry->eos,
+ __entry->error, __entry->warn));
+
+TRACE_EVENT(wave6_vpu_enc_pic,
+ TP_PROTO(struct vpu_instance *inst, struct enc_param *param),
+ TP_ARGS(inst, param),
+ TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+ __field(u32, id)
+ __field(u32, srcidx)
+ __field(u32, buf_y)
+ __field(u32, buf_cb)
+ __field(u32, buf_cr)
+ __field(u32, stride)
+ __field(u32, buf_strm)
+ __field(u32, size_strm)
+ __field(u32, force_type_enable)
+ __field(u32, force_type)
+ __field(u32, end_flag)),
+ TP_fast_assign(__assign_str(name);
+ __entry->id = inst->id;
+ __entry->srcidx = param->src_idx;
+ __entry->buf_y = param->source_frame->buf_y;
+ __entry->buf_cb = param->source_frame->buf_cb;
+ __entry->buf_cr = param->source_frame->buf_cr;
+ __entry->stride = param->source_frame->stride;
+ __entry->buf_strm = param->pic_stream_buffer_addr;
+ __entry->size_strm = param->pic_stream_buffer_size;
+ __entry->force_type_enable = param->force_pic;
+ __entry->force_type = param->force_pic_type;
+ __entry->end_flag = param->src_end;),
+ TP_printk("%s: inst[%d] src[%2d] %8x %8x %8x(%d) dst %8x(%d) force type %d(%d) end %d",
+ __get_str(name), __entry->id, __entry->srcidx,
+ __entry->buf_y, __entry->buf_cb, __entry->buf_cr,
+ __entry->stride, __entry->buf_strm, __entry->size_strm,
+ __entry->force_type_enable, __entry->force_type,
+ __entry->end_flag));
+
+TRACE_EVENT(wave6_vpu_enc_done,
+ TP_PROTO(struct vpu_instance *inst, struct enc_output_info *info),
+ TP_ARGS(inst, info),
+ TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+ __field(u32, id)
+ __field(u32, srcidx)
+ __field(u32, frmidx)
+ __field(u32, size)
+ __field(u32, type)
+ __field(u32, avg_qp)),
+ TP_fast_assign(__assign_str(name);
+ __entry->id = inst->id;
+ __entry->srcidx = info->enc_src_idx;
+ __entry->frmidx = info->recon_frame_index;
+ __entry->size = info->bitstream_size;
+ __entry->type = info->pic_type;
+ __entry->avg_qp = info->avg_ctu_qp;),
+ TP_printk("%s: inst[%d] src %d, frame %d, size %d, type %d, qp %d, eos %d",
+ __get_str(name), __entry->id,
+ __entry->srcidx, __entry->frmidx,
+ __entry->size, __entry->type, __entry->avg_qp,
+ __entry->frmidx == RECON_IDX_FLAG_ENC_END));
+
+TRACE_EVENT(wave6_vpu_s_ctrl,
+ TP_PROTO(struct vpu_instance *inst, struct v4l2_ctrl *ctrl),
+ TP_ARGS(inst, ctrl),
+ TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+ __field(u32, id)
+ __string(ctrl_name, ctrl->name)
+ __field(u32, val)),
+ TP_fast_assign(__assign_str(name);
+ __entry->id = inst->id;
+ __assign_str(ctrl_name);
+ __entry->val = ctrl->val;),
+ TP_printk("%s: inst[%d] %s = %d",
+ __get_str(name), __entry->id,
+ __get_str(ctrl_name), __entry->val));
+
+#endif /* __WAVE6_TRACE_H__ */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE wave6-trace
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.c
new file mode 100644
index 000000000000..7f04060f0aea
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Wave6 series multi-standard codec IP - debug interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#include <linux/types.h>
+#include <linux/debugfs.h>
+#include "wave6-vpu-core.h"
+#include "wave6-vpu-dbg.h"
+
+static int wave6_vpu_dbg_instance(struct seq_file *s, void *data)
+{
+ struct vpu_instance *inst = s->private;
+ struct vpu_performance_info *perf = &inst->performance;
+ struct vb2_queue *vq;
+ char str[128];
+ int num;
+ s64 tmp;
+ s64 fps;
+
+ if (!inst->v4l2_fh.m2m_ctx)
+ return 0;
+
+ num = scnprintf(str, sizeof(str), "[%s]\n",
+ inst->type == VPU_INST_TYPE_DEC ? "Decoder" : "Encoder");
+ if (seq_write(s, str, num))
+ return 0;
+
+ num = scnprintf(str, sizeof(str), "%s : product 0x%x, fw_ver %d.%d.%d(r%d), hw_ver 0x%x\n",
+ dev_name(inst->dev->dev),
+ inst->dev->attr.product_code,
+ FW_VERSION_MAJOR(inst->dev->attr.fw_version),
+ FW_VERSION_MINOR(inst->dev->attr.fw_version),
+ FW_VERSION_REL(inst->dev->attr.fw_version),
+ inst->dev->attr.fw_revision,
+ inst->dev->attr.hw_version);
+ if (seq_write(s, str, num))
+ return 0;
+
+ num = scnprintf(str, sizeof(str), "state = %s\n",
+ wave6_vpu_instance_state_name(inst->state));
+ if (seq_write(s, str, num))
+ return 0;
+
+ vq = v4l2_m2m_get_src_vq(inst->v4l2_fh.m2m_ctx);
+ num = scnprintf(str, sizeof(str),
+ "output (%2d, %2d): fmt = %c%c%c%c %d x %d, %d;\n",
+ vb2_is_streaming(vq),
+ vb2_get_num_buffers(vq),
+ inst->src_fmt.pixelformat,
+ inst->src_fmt.pixelformat >> 8,
+ inst->src_fmt.pixelformat >> 16,
+ inst->src_fmt.pixelformat >> 24,
+ inst->src_fmt.width,
+ inst->src_fmt.height,
+ vq->last_buffer_dequeued);
+ if (seq_write(s, str, num))
+ return 0;
+
+ vq = v4l2_m2m_get_dst_vq(inst->v4l2_fh.m2m_ctx);
+ num = scnprintf(str, sizeof(str),
+ "capture(%2d, %2d): fmt = %c%c%c%c %d x %d, %d;\n",
+ vb2_is_streaming(vq),
+ vb2_get_num_buffers(vq),
+ inst->dst_fmt.pixelformat,
+ inst->dst_fmt.pixelformat >> 8,
+ inst->dst_fmt.pixelformat >> 16,
+ inst->dst_fmt.pixelformat >> 24,
+ inst->dst_fmt.width,
+ inst->dst_fmt.height,
+ vq->last_buffer_dequeued);
+ if (seq_write(s, str, num))
+ return 0;
+
+ num = scnprintf(str, sizeof(str), "crop: (%d, %d) %d x %d\n",
+ inst->crop.left,
+ inst->crop.top,
+ inst->crop.width,
+ inst->crop.height);
+ if (seq_write(s, str, num))
+ return 0;
+
+ if (inst->scaler_info.enable) {
+ num = scnprintf(str, sizeof(str), "scale: %d x %d\n",
+ inst->scaler_info.width, inst->scaler_info.height);
+ if (seq_write(s, str, num))
+ return 0;
+ }
+
+ num = scnprintf(str, sizeof(str),
+ "queued src %d, dst %d, process %d, sequence %d, error %d, drain %d:%d\n",
+ inst->queued_src_buf_num,
+ inst->queued_dst_buf_num,
+ inst->processed_buf_num,
+ inst->sequence,
+ inst->error_buf_num,
+ inst->v4l2_fh.m2m_ctx->out_q_ctx.buffered,
+ inst->eos);
+ if (seq_write(s, str, num))
+ return 0;
+
+ num = scnprintf(str, sizeof(str), "fps");
+ if (seq_write(s, str, num))
+ return 0;
+ tmp = MSEC_PER_SEC * inst->processed_buf_num;
+ if (perf->ts_last > perf->ts_first + NSEC_PER_MSEC) {
+ fps = DIV_ROUND_CLOSEST(tmp, (perf->ts_last - perf->ts_first) / NSEC_PER_MSEC);
+ num = scnprintf(str, sizeof(str), " actual: %lld;", fps);
+ if (seq_write(s, str, num))
+ return 0;
+ }
+ if (perf->total_sw_time) {
+ fps = DIV_ROUND_CLOSEST(tmp, perf->total_sw_time / NSEC_PER_MSEC);
+ num = scnprintf(str, sizeof(str), " sw: %lld;", fps);
+ if (seq_write(s, str, num))
+ return 0;
+ }
+ if (perf->total_hw_time) {
+ fps = DIV_ROUND_CLOSEST(tmp, perf->total_hw_time / NSEC_PER_MSEC);
+ num = scnprintf(str, sizeof(str), " hw: %lld", fps);
+ if (seq_write(s, str, num))
+ return 0;
+ }
+ num = scnprintf(str, sizeof(str), "\n");
+ if (seq_write(s, str, num))
+ return 0;
+
+ num = scnprintf(str, sizeof(str),
+ "latency(ms) first: %llu.%06llu, max %llu.%06llu, setup %llu.%06llu\n",
+ perf->latency_first / NSEC_PER_MSEC,
+ perf->latency_first % NSEC_PER_MSEC,
+ perf->latency_max / NSEC_PER_MSEC,
+ perf->latency_max % NSEC_PER_MSEC,
+ (perf->ts_first - perf->ts_start) / NSEC_PER_MSEC,
+ (perf->ts_first - perf->ts_start) % NSEC_PER_MSEC);
+ if (seq_write(s, str, num))
+ return 0;
+
+ num = scnprintf(str, sizeof(str),
+ "process frame time(ms) min: %llu.%06llu, max %llu.%06llu\n",
+ perf->min_process_time / NSEC_PER_MSEC,
+ perf->min_process_time % NSEC_PER_MSEC,
+ perf->max_process_time / NSEC_PER_MSEC,
+ perf->max_process_time % NSEC_PER_MSEC);
+ if (seq_write(s, str, num))
+ return 0;
+
+ if (inst->type == VPU_INST_TYPE_DEC) {
+ num = scnprintf(str, sizeof(str), "%s order\n",
+ inst->disp_mode == DISP_MODE_DISP_ORDER ? "display" : "decode");
+ if (seq_write(s, str, num))
+ return 0;
+ } else {
+ struct enc_info *p_enc_info = &inst->codec_info->enc_info;
+ struct enc_codec_param *param = &p_enc_info->open_param.codec_param;
+
+ num = scnprintf(str, sizeof(str), "profile %d, level %d, tier %d\n",
+ param->profile, param->level, param->tier);
+ if (seq_write(s, str, num))
+ return 0;
+
+ num = scnprintf(str, sizeof(str), "frame_rate %d, idr_period %d, intra_period %d\n",
+ param->frame_rate, param->idr_period, param->intra_period);
+ if (seq_write(s, str, num))
+ return 0;
+
+ num = scnprintf(str, sizeof(str), "rc %d, mode %d, bitrate %d\n",
+ param->en_rate_control,
+ param->rc_mode,
+ param->bitrate);
+ if (seq_write(s, str, num))
+ return 0;
+
+ num = scnprintf(str, sizeof(str),
+ "qp %d, i_qp [%d, %d], p_qp [%d, %d], b_qp [%d, %d]\n",
+ param->qp,
+ param->min_qp_i, param->max_qp_i,
+ param->min_qp_p, param->max_qp_p,
+ param->min_qp_b, param->max_qp_b);
+ if (seq_write(s, str, num))
+ return 0;
+ }
+
+ return 0;
+}
+
+static int wave6_vpu_dbg_open(struct inode *inode, struct file *filp)
+{
+ return single_open(filp, wave6_vpu_dbg_instance, inode->i_private);
+}
+
+static const struct file_operations wave6_vpu_dbg_fops = {
+ .owner = THIS_MODULE,
+ .open = wave6_vpu_dbg_open,
+ .release = single_release,
+ .read = seq_read,
+};
+
+int wave6_vpu_create_dbgfs_file(struct vpu_instance *inst)
+{
+ char name[64];
+
+ if (WARN_ON(!inst || !inst->dev || IS_ERR_OR_NULL(inst->dev->debugfs)))
+ return -EINVAL;
+
+ scnprintf(name, sizeof(name), "instance.%d", inst->id);
+ inst->debugfs = debugfs_create_file((const char *)name,
+ VERIFY_OCTAL_PERMISSIONS(0444),
+ inst->dev->debugfs,
+ inst,
+ &wave6_vpu_dbg_fops);
+
+ return 0;
+}
+
+void wave6_vpu_remove_dbgfs_file(struct vpu_instance *inst)
+{
+ if (WARN_ON(!inst || !inst->debugfs))
+ return;
+
+ debugfs_remove(inst->debugfs);
+ inst->debugfs = NULL;
+}
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.h b/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.h
new file mode 100644
index 000000000000..6453eb2de76f
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - debug interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#ifndef __WAVE6_VPU_DBG_H__
+#define __WAVE6_VPU_DBG_H__
+
+int wave6_vpu_create_dbgfs_file(struct vpu_instance *inst);
+void wave6_vpu_remove_dbgfs_file(struct vpu_instance *inst);
+
+#endif /* __WAVE6_VPU_DBG_H__ */
--
2.31.1
^ permalink raw reply related
* [RFC PATCH v5 7/9] media: chips-media: wave6: Add Wave6 thermal cooling device
From: Nas Chung @ 2026-04-15 9:25 UTC (permalink / raw)
To: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer
Cc: linux-media, devicetree, linux-kernel, linux-imx,
linux-arm-kernel, marek.vasut, ming.qian, Nas Chung
In-Reply-To: <20260415092529.577-1-nas.chung@chipsnmedia.com>
Add a thermal cooling device for the Wave6 VPU.
The device operates within the Linux thermal framework,
adjusting the VPU performance state based on thermal conditions.
Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
Tested-by: Ming Qian <ming.qian@oss.nxp.com>
Tested-by: Marek Vasut <marek.vasut@mailbox.org>
---
.../chips-media/wave6/wave6-vpu-thermal.c | 141 ++++++++++++++++++
.../chips-media/wave6/wave6-vpu-thermal.h | 26 ++++
2 files changed, 167 insertions(+)
create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.c
create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.h
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.c
new file mode 100644
index 000000000000..df8161fd4998
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Wave6 series multi-standard codec IP - wave6 thermal cooling interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ *
+ */
+
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/units.h>
+#include <linux/slab.h>
+#include "wave6-vpu-thermal.h"
+
+static int wave6_vpu_thermal_cooling_update(struct vpu_thermal_cooling *thermal,
+ int state)
+{
+ unsigned long new_clock_rate;
+ int ret;
+
+ if (state > thermal->thermal_max || !thermal->cooling)
+ return 0;
+
+ new_clock_rate = DIV_ROUND_UP(thermal->freq_table[state], HZ_PER_KHZ);
+ dev_dbg(thermal->dev, "receive cooling state: %d, new clock rate %ld\n",
+ state, new_clock_rate);
+
+ ret = dev_pm_genpd_set_performance_state(thermal->dev, new_clock_rate);
+ if (ret && !((ret == -ENODEV) || (ret == -EOPNOTSUPP))) {
+ dev_err(thermal->dev, "failed to set perf to %lu, ret = %d\n",
+ new_clock_rate, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int wave6_vpu_cooling_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct vpu_thermal_cooling *thermal = cdev->devdata;
+
+ *state = thermal->thermal_max;
+
+ return 0;
+}
+
+static int wave6_vpu_cooling_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct vpu_thermal_cooling *thermal = cdev->devdata;
+
+ *state = thermal->thermal_event;
+
+ return 0;
+}
+
+static int wave6_vpu_cooling_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct vpu_thermal_cooling *thermal = cdev->devdata;
+
+ thermal->thermal_event = state;
+ wave6_vpu_thermal_cooling_update(thermal, state);
+
+ return 0;
+}
+
+static struct thermal_cooling_device_ops wave6_cooling_ops = {
+ .get_max_state = wave6_vpu_cooling_get_max_state,
+ .get_cur_state = wave6_vpu_cooling_get_cur_state,
+ .set_cur_state = wave6_vpu_cooling_set_cur_state,
+};
+
+int wave6_vpu_cooling_init(struct vpu_thermal_cooling *thermal)
+{
+ int i;
+ int num_opps;
+ unsigned long freq;
+
+ if (WARN_ON(!thermal || !thermal->dev))
+ return -EINVAL;
+
+ num_opps = dev_pm_opp_get_opp_count(thermal->dev);
+ if (num_opps < 0) {
+ dev_err(thermal->dev, "fail to get pm opp count, ret = %d\n", num_opps);
+ return num_opps;
+ }
+ if (num_opps == 0) {
+ dev_err(thermal->dev, "no OPP entries found\n");
+ return -ENODEV;
+ }
+
+ thermal->freq_table = kcalloc(num_opps, sizeof(*thermal->freq_table), GFP_KERNEL);
+ if (!thermal->freq_table)
+ goto error;
+
+ for (i = 0, freq = ULONG_MAX; i < num_opps; i++, freq--) {
+ struct dev_pm_opp *opp;
+
+ opp = dev_pm_opp_find_freq_floor(thermal->dev, &freq);
+ if (IS_ERR(opp))
+ break;
+
+ dev_pm_opp_put(opp);
+
+ dev_dbg(thermal->dev, "[%d] = %lu\n", i, freq);
+ if (freq < 100 * HZ_PER_MHZ)
+ break;
+
+ thermal->freq_table[i] = freq;
+ thermal->thermal_max = i;
+ }
+
+ if (!thermal->thermal_max)
+ goto error;
+
+ thermal->thermal_event = 0;
+ thermal->cooling = thermal_of_cooling_device_register(thermal->dev->of_node,
+ dev_name(thermal->dev),
+ thermal,
+ &wave6_cooling_ops);
+ if (IS_ERR(thermal->cooling)) {
+ dev_err(thermal->dev, "register cooling device failed\n");
+ goto error;
+ }
+
+ return 0;
+
+error:
+ wave6_vpu_cooling_remove(thermal);
+
+ return -EINVAL;
+}
+
+void wave6_vpu_cooling_remove(struct vpu_thermal_cooling *thermal)
+{
+ thermal_cooling_device_unregister(thermal->cooling);
+ kfree(thermal->freq_table);
+ thermal->freq_table = NULL;
+}
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.h b/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.h
new file mode 100644
index 000000000000..16e86e99540a
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - wave6 thermal cooling interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ *
+ */
+
+#ifndef __WAVE6_VPU_THERMAL_H__
+#define __WAVE6_VPU_THERMAL_H__
+
+#include <linux/thermal.h>
+
+struct vpu_thermal_cooling {
+ struct device *dev;
+ struct device_node *of_node;
+ int thermal_event;
+ int thermal_max;
+ struct thermal_cooling_device *cooling;
+ unsigned long *freq_table;
+};
+
+int wave6_vpu_cooling_init(struct vpu_thermal_cooling *thermal);
+void wave6_vpu_cooling_remove(struct vpu_thermal_cooling *thermal);
+
+#endif /* __WAVE6_VPU_THERMAL_H__ */
--
2.31.1
^ permalink raw reply related
* [RFC PATCH v5 8/9] media: chips-media: wave6: Add Wave6 control driver
From: Nas Chung @ 2026-04-15 9:25 UTC (permalink / raw)
To: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer
Cc: linux-media, devicetree, linux-kernel, linux-imx,
linux-arm-kernel, marek.vasut, ming.qian, Nas Chung
In-Reply-To: <20260415092529.577-1-nas.chung@chipsnmedia.com>
Add the control driver for the Chips&Media Wave6 video codec IP.
The hardware contains one control register region and four interface
register regions for a shared video processing engine.
This driver handles the control region and manages shared resources such
as firmware loading, firmware memory allocation, and coordination required
by the interface register regions.
It also instantiates and coordinates with the `wave6-core` child devices
for firmware and power state management.
Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
Tested-by: Ming Qian <ming.qian@oss.nxp.com>
Tested-by: Marek Vasut <marek.vasut@mailbox.org>
---
drivers/media/platform/chips-media/Kconfig | 1 +
drivers/media/platform/chips-media/Makefile | 1 +
.../media/platform/chips-media/wave6/Kconfig | 17 +
.../media/platform/chips-media/wave6/Makefile | 17 +
.../platform/chips-media/wave6/wave6-vpu.c | 816 ++++++++++++++++++
.../platform/chips-media/wave6/wave6-vpu.h | 143 +++
6 files changed, 995 insertions(+)
create mode 100644 drivers/media/platform/chips-media/wave6/Kconfig
create mode 100644 drivers/media/platform/chips-media/wave6/Makefile
create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu.c
create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu.h
diff --git a/drivers/media/platform/chips-media/Kconfig b/drivers/media/platform/chips-media/Kconfig
index ad350eb6b1fc..8ef7fc8029a4 100644
--- a/drivers/media/platform/chips-media/Kconfig
+++ b/drivers/media/platform/chips-media/Kconfig
@@ -4,3 +4,4 @@ comment "Chips&Media media platform drivers"
source "drivers/media/platform/chips-media/coda/Kconfig"
source "drivers/media/platform/chips-media/wave5/Kconfig"
+source "drivers/media/platform/chips-media/wave6/Kconfig"
diff --git a/drivers/media/platform/chips-media/Makefile b/drivers/media/platform/chips-media/Makefile
index 6b5d99de8b54..b9a07a91c9d6 100644
--- a/drivers/media/platform/chips-media/Makefile
+++ b/drivers/media/platform/chips-media/Makefile
@@ -2,3 +2,4 @@
obj-y += coda/
obj-y += wave5/
+obj-y += wave6/
diff --git a/drivers/media/platform/chips-media/wave6/Kconfig b/drivers/media/platform/chips-media/wave6/Kconfig
new file mode 100644
index 000000000000..63d79c56c7fc
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config VIDEO_WAVE6_VPU
+ tristate "Chips&Media Wave6 Codec Driver"
+ depends on V4L_MEM2MEM_DRIVERS
+ depends on VIDEO_DEV && OF
+ depends on ARCH_MXC || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
+ select V4L2_MEM2MEM_DEV
+ select GENERIC_ALLOCATOR
+ help
+ Chips&Media Wave6 stateful codec driver.
+ The wave6 driver manages shared resources such as firmware memory.
+ The wave6-core driver provides encoding and decoding capabilities
+ for H.264, HEVC, and other video formats.
+ To compile this driver as modules, choose M here: the
+ modules will be called wave6 and wave6-core.
diff --git a/drivers/media/platform/chips-media/wave6/Makefile b/drivers/media/platform/chips-media/wave6/Makefile
new file mode 100644
index 000000000000..06f8ac9bef14
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/Makefile
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# tell define_trace.h where to find the trace header
+CFLAGS_wave6-vpu-core.o := -I$(src)
+
+wave6-objs += wave6-vpu.o \
+ wave6-vpu-thermal.o
+obj-$(CONFIG_VIDEO_WAVE6_VPU) += wave6.o
+
+wave6-core-objs += wave6-vpu-core.o \
+ wave6-vpu-v4l2.o \
+ wave6-vpu-dbg.o \
+ wave6-vpuapi.o \
+ wave6-vpu-dec.o \
+ wave6-vpu-enc.o \
+ wave6-hw.o
+obj-$(CONFIG_VIDEO_WAVE6_VPU) += wave6-core.o
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu.c b/drivers/media/platform/chips-media/wave6/wave6-vpu.c
new file mode 100644
index 000000000000..ca8dded9bf86
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu.c
@@ -0,0 +1,816 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Wave6 series multi-standard codec IP - wave6 driver
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_domain.h>
+#include <linux/dma-mapping.h>
+#include <linux/iopoll.h>
+#include <linux/genalloc.h>
+
+#include "wave6-vpuconfig.h"
+#include "wave6-regdefine.h"
+#include "wave6-vpu.h"
+
+static const struct wave6_vpu_resource wave633c_data = {
+ .fw_name = "cnm/wave633c_imx9_codec_fw.bin",
+ /* For HEVC, AVC, 4096x4096, 8bit */
+ .sram_size = 0x14800,
+};
+
+static const struct wave6_vpu_core_resource wave633c_core_data = {
+ .codec_types = WAVE633_CODEC_TYPE,
+ .compatible_fw_version = WAVE633_COMPATIBLE_FW_VERSION,
+};
+
+static const char *wave6_vpu_state_name(enum wave6_vpu_state state)
+{
+ switch (state) {
+ case WAVE6_VPU_STATE_OFF:
+ return "off";
+ case WAVE6_VPU_STATE_PREPARE:
+ return "prepare";
+ case WAVE6_VPU_STATE_ON:
+ return "on";
+ case WAVE6_VPU_STATE_SLEEP:
+ return "sleep";
+ default:
+ return "unknown";
+ }
+}
+
+static bool wave6_vpu_valid_transition(struct wave6_vpu_device *vpu,
+ enum wave6_vpu_state next)
+{
+ switch (vpu->state) {
+ case WAVE6_VPU_STATE_OFF:
+ /* to PREPARE: first boot attempt */
+ /* to ON: already booted before, skipping boot */
+ if (next == WAVE6_VPU_STATE_PREPARE ||
+ next == WAVE6_VPU_STATE_ON)
+ return true;
+ break;
+ case WAVE6_VPU_STATE_PREPARE:
+ /* to OFF: boot failed */
+ /* to ON: boot successful */
+ if (next == WAVE6_VPU_STATE_OFF ||
+ next == WAVE6_VPU_STATE_ON)
+ return true;
+ break;
+ case WAVE6_VPU_STATE_ON:
+ /* to OFF: sleep failed */
+ /* to SLEEP: sleep successful */
+ if (next == WAVE6_VPU_STATE_OFF ||
+ next == WAVE6_VPU_STATE_SLEEP)
+ return true;
+ break;
+ case WAVE6_VPU_STATE_SLEEP:
+ /* to OFF: resume failed */
+ /* to ON: resume successful */
+ if (next == WAVE6_VPU_STATE_OFF ||
+ next == WAVE6_VPU_STATE_ON)
+ return true;
+ break;
+ }
+
+ dev_err(vpu->dev, "invalid transition: %s -> %s\n",
+ wave6_vpu_state_name(vpu->state), wave6_vpu_state_name(next));
+
+ return false;
+}
+
+static void wave6_vpu_set_state(struct wave6_vpu_device *vpu,
+ enum wave6_vpu_state state)
+{
+ if (!wave6_vpu_valid_transition(vpu, state))
+ return;
+
+ dev_dbg(vpu->dev, "set state: %s -> %s\n",
+ wave6_vpu_state_name(vpu->state), wave6_vpu_state_name(state));
+
+ vpu->state = state;
+}
+
+static int wave6_vpu_wait_busy(struct vpu_core_device *core)
+{
+ u32 val;
+
+ return read_poll_timeout(wave6_vdi_readl, val, !val,
+ W6_VPU_POLL_DELAY_US, W6_VPU_POLL_TIMEOUT,
+ false, core->reg_base, W6_VPU_BUSY_STATUS);
+}
+
+static int wave6_vpu_check_result(struct vpu_core_device *core)
+{
+ if (wave6_vdi_readl(core->reg_base, W6_RET_SUCCESS))
+ return 0;
+
+ return wave6_vdi_readl(core->reg_base, W6_RET_FAIL_REASON);
+}
+
+static u32 wave6_vpu_get_code_buf_size(struct wave6_vpu_device *vpu)
+{
+ return min_t(u32, vpu->code_buf.size, W6_MAX_CODE_BUF_SIZE);
+}
+
+static void wave6_vpu_remap_code_buf(struct wave6_vpu_device *vpu)
+{
+ dma_addr_t code_base = vpu->code_buf.dma_addr;
+ u32 i, reg_val;
+
+ for (i = 0; i < wave6_vpu_get_code_buf_size(vpu) / W6_MAX_REMAP_PAGE_SIZE; i++) {
+ reg_val = REMAP_CTRL_ON |
+ REMAP_CTRL_INDEX(i) |
+ REMAP_CTRL_PAGE_SIZE_ON |
+ REMAP_CTRL_PAGE_SIZE(W6_MAX_REMAP_PAGE_SIZE);
+ wave6_vdi_writel(vpu->reg_base, W6_VPU_REMAP_CTRL_GB, reg_val);
+ wave6_vdi_writel(vpu->reg_base, W6_VPU_REMAP_VADDR_GB,
+ i * W6_MAX_REMAP_PAGE_SIZE);
+ wave6_vdi_writel(vpu->reg_base, W6_VPU_REMAP_PADDR_GB,
+ code_base + i * W6_MAX_REMAP_PAGE_SIZE);
+ }
+}
+
+static void wave6_vpu_init_code_buf(struct wave6_vpu_device *vpu)
+{
+ if (vpu->code_buf.size < W6_CODE_BUF_SIZE) {
+ dev_warn(vpu->dev,
+ "code buf size (%zu) is too small\n", vpu->code_buf.size);
+ vpu->code_buf.phys_addr = 0;
+ vpu->code_buf.size = 0;
+ memset(&vpu->code_buf, 0, sizeof(vpu->code_buf));
+ return;
+ }
+
+ vpu->code_buf.vaddr = devm_memremap(vpu->dev,
+ vpu->code_buf.phys_addr,
+ vpu->code_buf.size,
+ MEMREMAP_WC);
+ if (!vpu->code_buf.vaddr) {
+ memset(&vpu->code_buf, 0, sizeof(vpu->code_buf));
+ return;
+ }
+
+ vpu->code_buf.dma_addr = dma_map_resource(vpu->dev,
+ vpu->code_buf.phys_addr,
+ vpu->code_buf.size,
+ DMA_BIDIRECTIONAL,
+ 0);
+ if (!vpu->code_buf.dma_addr) {
+ memset(&vpu->code_buf, 0, sizeof(vpu->code_buf));
+ return;
+ }
+}
+
+static void wave6_vpu_allocate_work_buffers(struct wave6_vpu_device *vpu)
+{
+ struct vpu_buf *buf;
+ int i;
+
+ for (i = 0; i < MAX_NUM_INSTANCE; i++) {
+ buf = &vpu->work_buffers[i];
+ buf->size = W637DEC_WORKBUF_SIZE_FOR_CQ;
+
+ if (wave6_vdi_alloc_dma(vpu->dev, buf)) {
+ dev_warn(vpu->dev, "Failed to allocate work_buffers\n");
+ return;
+ }
+
+ vpu->work_buffers_alloc++;
+ }
+}
+
+static void wave6_vpu_free_work_buffers(struct wave6_vpu_device *vpu)
+{
+ int i;
+
+ for (i = 0; i < vpu->work_buffers_alloc; i++)
+ wave6_vdi_free_dma(&vpu->work_buffers[i]);
+
+ vpu->work_buffers_alloc = 0;
+ vpu->work_buffers_avail = 0;
+}
+
+static void wave6_vpu_init_work_buf(struct wave6_vpu_device *vpu,
+ struct vpu_core_device *core)
+{
+ int ret;
+
+ lockdep_assert_held(&vpu->lock);
+
+ wave6_vdi_writel(core->reg_base, W6_VPU_BUSY_STATUS, BUSY_STATUS_SET);
+ wave6_vdi_writel(core->reg_base, W6_COMMAND, W6_CMD_INIT_WORK_BUF);
+ wave6_vdi_writel(core->reg_base, W6_VPU_HOST_INT_REQ, HOST_INT_REQ_ON);
+
+ ret = wave6_vpu_wait_busy(core);
+ if (ret) {
+ dev_err(vpu->dev, "init work buf failed\n");
+ return;
+ }
+
+ ret = wave6_vpu_check_result(core);
+ if (ret) {
+ dev_err(vpu->dev, "init work buf failed, reason 0x%x\n", ret);
+ return;
+ }
+
+ vpu->work_buffers_avail = vpu->work_buffers_alloc;
+}
+
+static int wave6_vpu_init_vpu(struct wave6_vpu_device *vpu,
+ struct vpu_core_device *core)
+{
+ int ret;
+
+ lockdep_assert_held(&vpu->lock);
+
+ /* try init directly as firmware is running */
+ if (wave6_vdi_readl(core->reg_base, W6_VPU_VCPU_CUR_PC))
+ goto init_done;
+
+ wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_PREPARE);
+
+ wave6_vpu_remap_code_buf(vpu);
+
+ wave6_vdi_writel(core->reg_base, W6_VPU_BUSY_STATUS, BUSY_STATUS_SET);
+ wave6_vdi_writel(core->reg_base, W6_CMD_INIT_VPU_SEC_AXI_BASE_CORE0,
+ vpu->sram_buf.dma_addr);
+ wave6_vdi_writel(core->reg_base, W6_CMD_INIT_VPU_SEC_AXI_SIZE_CORE0,
+ vpu->sram_buf.size);
+ wave6_vdi_writel(vpu->reg_base, W6_COMMAND_GB, W6_CMD_INIT_VPU);
+ wave6_vdi_writel(vpu->reg_base, W6_VPU_REMAP_CORE_START_GB,
+ REMAP_CORE_START_ON);
+
+ ret = wave6_vpu_wait_busy(core);
+ if (ret) {
+ dev_err(vpu->dev, "init vpu timeout\n");
+ wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_OFF);
+ return -EINVAL;
+ }
+
+ ret = wave6_vpu_check_result(core);
+ if (ret) {
+ dev_err(vpu->dev, "init vpu fail, reason 0x%x\n", ret);
+ wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_OFF);
+ return -EIO;
+ }
+
+init_done:
+ wave6_vpu_init_work_buf(vpu, core);
+ wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_ON);
+
+ return 0;
+}
+
+static int wave6_vpu_sleep(struct wave6_vpu_device *vpu,
+ struct vpu_core_device *core)
+{
+ int ret;
+
+ lockdep_assert_held(&vpu->lock);
+
+ if (!wave6_vdi_readl(core->reg_base, W6_VPU_VCPU_CUR_PC)) {
+ wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_OFF);
+ return 0;
+ }
+
+ wave6_vdi_writel(core->reg_base, W6_VPU_BUSY_STATUS, BUSY_STATUS_SET);
+ wave6_vdi_writel(core->reg_base, W6_COMMAND, W6_CMD_SLEEP_VPU);
+ wave6_vdi_writel(core->reg_base, W6_VPU_HOST_INT_REQ, HOST_INT_REQ_ON);
+
+ ret = wave6_vpu_wait_busy(core);
+ if (ret) {
+ dev_err(vpu->dev, "sleep vpu timeout\n");
+ wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_OFF);
+ return -EINVAL;
+ }
+
+ ret = wave6_vpu_check_result(core);
+ if (ret) {
+ dev_err(vpu->dev, "sleep vpu fail, reason 0x%x\n", ret);
+ wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_OFF);
+ return -EIO;
+ }
+
+ wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_SLEEP);
+
+ return 0;
+}
+
+static int wave6_vpu_wakeup(struct wave6_vpu_device *vpu,
+ struct vpu_core_device *core)
+{
+ int ret;
+
+ lockdep_assert_held(&vpu->lock);
+
+ /* try wakeup directly as firmware is running */
+ if (wave6_vdi_readl(core->reg_base, W6_VPU_VCPU_CUR_PC))
+ goto wakeup_done;
+
+ wave6_vpu_remap_code_buf(vpu);
+
+ wave6_vdi_writel(core->reg_base, W6_VPU_BUSY_STATUS, BUSY_STATUS_SET);
+ wave6_vdi_writel(core->reg_base, W6_CMD_INIT_VPU_SEC_AXI_BASE_CORE0,
+ vpu->sram_buf.dma_addr);
+ wave6_vdi_writel(core->reg_base, W6_CMD_INIT_VPU_SEC_AXI_SIZE_CORE0,
+ vpu->sram_buf.size);
+ wave6_vdi_writel(vpu->reg_base, W6_COMMAND_GB, W6_CMD_WAKEUP_VPU);
+ wave6_vdi_writel(vpu->reg_base, W6_VPU_REMAP_CORE_START_GB,
+ REMAP_CORE_START_ON);
+
+ ret = wave6_vpu_wait_busy(core);
+ if (ret) {
+ dev_err(vpu->dev, "wakeup vpu timeout\n");
+ wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_OFF);
+ return -EINVAL;
+ }
+
+ ret = wave6_vpu_check_result(core);
+ if (ret) {
+ dev_err(vpu->dev, "wakeup vpu fail, reason 0x%x\n", ret);
+ wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_OFF);
+ return -EIO;
+ }
+
+wakeup_done:
+ wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_ON);
+
+ return 0;
+}
+
+static int wave6_vpu_try_boot(struct wave6_vpu_device *vpu,
+ struct vpu_core_device *core)
+{
+ u32 product_code;
+ int ret;
+
+ lockdep_assert_held(&vpu->lock);
+
+ if (vpu->state != WAVE6_VPU_STATE_OFF && vpu->state != WAVE6_VPU_STATE_SLEEP)
+ return 0;
+
+ product_code = wave6_vdi_readl(core->reg_base, W6_VPU_RET_PRODUCT_CODE);
+ if (!PRODUCT_CODE_W_SERIES(product_code)) {
+ dev_err(vpu->dev, "unknown product : %08x\n", product_code);
+ return -EINVAL;
+ }
+
+ if (vpu->state == WAVE6_VPU_STATE_SLEEP) {
+ ret = wave6_vpu_wakeup(vpu, core);
+ return ret;
+ }
+
+ ret = wave6_vpu_init_vpu(vpu, core);
+
+ return ret;
+}
+
+static int wave6_vpu_get(struct wave6_vpu_device *vpu,
+ struct vpu_core_device *core)
+{
+ int ret;
+
+ if (WARN_ON(!vpu || !core))
+ return -EINVAL;
+
+ guard(mutex)(&vpu->lock);
+
+ if (!vpu->fw_available)
+ return -EINVAL;
+
+ /* Only the first core executes boot; others return */
+ if (atomic_inc_return(&vpu->core_count) > 1)
+ return 0;
+
+ ret = pm_runtime_resume_and_get(vpu->dev);
+ if (ret)
+ goto error_pm;
+
+ ret = wave6_vpu_try_boot(vpu, core);
+ if (ret)
+ goto error_boot;
+
+ return 0;
+
+error_boot:
+ pm_runtime_put_sync(vpu->dev);
+error_pm:
+ atomic_dec(&vpu->core_count);
+
+ return ret;
+}
+
+static void wave6_vpu_put(struct wave6_vpu_device *vpu,
+ struct vpu_core_device *core)
+{
+ if (WARN_ON(!vpu || !core))
+ return;
+
+ guard(mutex)(&vpu->lock);
+
+ if (!vpu->fw_available)
+ return;
+
+ /* Only the last core executes sleep; others return */
+ if (atomic_dec_return(&vpu->core_count) > 0)
+ return;
+
+ wave6_vpu_sleep(vpu, core);
+
+ if (!pm_runtime_suspended(vpu->dev))
+ pm_runtime_put_sync(vpu->dev);
+}
+
+static void wave6_vpu_require_work_buffer(struct wave6_vpu_device *vpu,
+ struct vpu_core_device *core)
+{
+ struct vpu_buf *vb;
+ u32 size;
+
+ if (WARN_ON(!vpu || !core))
+ return;
+
+ size = wave6_vdi_readl(core->reg_base, W6_CMD_SET_WORK_BUF_SIZE);
+ if (!size)
+ return;
+
+ if (WARN_ON(size > W637DEC_WORKBUF_SIZE_FOR_CQ))
+ goto exit;
+
+ if (WARN_ON(vpu->work_buffers_avail <= 0))
+ goto exit;
+
+ vpu->work_buffers_avail--;
+ vb = &vpu->work_buffers[vpu->work_buffers_avail];
+
+ wave6_vdi_writel(core->reg_base, W6_CMD_SET_WORK_BUF_ADDR, vb->daddr);
+
+exit:
+ wave6_vdi_writel(core->reg_base, W6_CMD_SET_WORK_BUF_SIZE, SET_WORK_BUF_SIZE_ACK);
+}
+
+static int wave6_vpu_create_cores(struct wave6_vpu_device *vpu)
+{
+ struct device_node *child;
+ int num_cores = 0;
+
+ for_each_available_child_of_node(vpu->dev->of_node, child) {
+ struct platform_device_info info = {};
+ struct platform_device *pdev;
+ struct resource res[2];
+ int irq;
+
+ if (num_cores >= W6_VPU_MAX_NUM_CORE) {
+ of_node_put(child);
+ break;
+ }
+
+ if (of_address_to_resource(child, 0, &res[0])) {
+ dev_warn(vpu->dev, "%pOF: missing reg property\n", child);
+ continue;
+ }
+
+ irq = of_irq_get(child, 0);
+ if (irq < 0) {
+ dev_warn(vpu->dev, "%pOF: missing interrupts property\n", child);
+ continue;
+ }
+ res[1] = DEFINE_RES_IRQ(irq);
+
+ info.fwnode = of_fwnode_handle(child);
+ info.parent = vpu->dev;
+ info.name = WAVE6_VPU_CORE_PLATFORM_DRIVER_NAME;
+ info.id = num_cores;
+ info.dma_mask = DMA_BIT_MASK(32);
+ info.res = res;
+ info.num_res = ARRAY_SIZE(res);
+ info.data = &wave633c_core_data;
+ info.size_data = sizeof(wave633c_core_data);
+
+ pdev = platform_device_register_full(&info);
+ if (IS_ERR(pdev)) {
+ dev_err(vpu->dev, "Failed to register core %d: %ld\n",
+ num_cores, PTR_ERR(pdev));
+ continue;
+ }
+
+ vpu->core_pdevs[num_cores] = pdev;
+ num_cores++;
+ }
+
+ return num_cores;
+}
+
+static void wave6_vpu_destroy_cores(struct wave6_vpu_device *vpu)
+{
+ int i;
+
+ for (i = 0; i < W6_VPU_MAX_NUM_CORE; i++) {
+ struct platform_device *pdev = vpu->core_pdevs[i];
+
+ if (!pdev)
+ continue;
+
+ platform_device_unregister(pdev);
+ vpu->core_pdevs[i] = NULL;
+ }
+}
+
+static void wave6_vpu_release(struct wave6_vpu_device *vpu)
+{
+ guard(mutex)(&vpu->lock);
+
+ vpu->fw_available = false;
+ wave6_vpu_destroy_cores(vpu);
+ wave6_vpu_free_work_buffers(vpu);
+ if (vpu->sram_pool && vpu->sram_buf.vaddr) {
+ dma_unmap_resource(vpu->dev,
+ vpu->sram_buf.dma_addr,
+ vpu->sram_buf.size,
+ DMA_BIDIRECTIONAL,
+ 0);
+ gen_pool_free(vpu->sram_pool,
+ (unsigned long)vpu->sram_buf.vaddr,
+ vpu->sram_buf.size);
+ }
+ if (vpu->code_buf.dma_addr)
+ dma_unmap_resource(vpu->dev,
+ vpu->code_buf.dma_addr,
+ vpu->code_buf.size,
+ DMA_BIDIRECTIONAL,
+ 0);
+}
+
+static void wave6_vpu_load_firmware(const struct firmware *fw, void *context)
+{
+ struct wave6_vpu_device *vpu = context;
+
+ guard(mutex)(&vpu->lock);
+
+ if (!fw || !fw->data) {
+ dev_err(vpu->dev, "No firmware.\n");
+ return;
+ }
+
+ if (!vpu->fw_available)
+ goto exit;
+
+ if (fw->size + W6_EXTRA_CODE_BUF_SIZE > wave6_vpu_get_code_buf_size(vpu)) {
+ dev_err(vpu->dev, "firmware size (%zd > %zd) is too big\n",
+ fw->size, vpu->code_buf.size);
+ vpu->fw_available = false;
+ goto exit;
+ }
+
+ memcpy(vpu->code_buf.vaddr, fw->data, fw->size);
+
+ vpu->get_vpu = wave6_vpu_get;
+ vpu->put_vpu = wave6_vpu_put;
+ vpu->req_work_buffer = wave6_vpu_require_work_buffer;
+ request_module("platform:%s", WAVE6_VPU_CORE_PLATFORM_DRIVER_NAME);
+ if (!wave6_vpu_create_cores(vpu)) {
+ dev_err(vpu->dev, "Failed to create VPU cores\n");
+ vpu->fw_available = false;
+ }
+
+exit:
+ release_firmware(fw);
+}
+
+static void wave6_vpu_detach_pm_domains(struct wave6_vpu_device *vpu)
+{
+ if (!vpu->num_pm_domains)
+ return;
+
+ for (int i = 0; i < vpu->num_pm_domains; i++) {
+ struct device *pd_dev = vpu->pd_list->pd_devs[i];
+
+ if (!IS_ERR_OR_NULL(pd_dev) && !pm_runtime_suspended(pd_dev))
+ pm_runtime_force_suspend(pd_dev);
+ }
+
+ dev_pm_domain_detach_list(vpu->pd_list);
+ vpu->pd_list = NULL;
+ vpu->num_pm_domains = 0;
+}
+
+static int wave6_vpu_attach_pm_domains(struct wave6_vpu_device *vpu)
+{
+ int ret;
+
+ vpu->num_pm_domains = of_count_phandle_with_args(vpu->dev->of_node,
+ "power-domains",
+ "#power-domain-cells");
+ if (vpu->num_pm_domains < 0) {
+ dev_err(vpu->dev, "No power domains defined for vpu node\n");
+ return vpu->num_pm_domains;
+ }
+
+ if (vpu->num_pm_domains == 1) {
+ /* genpd_dev_pm_attach() attach automatically if count is 1 */
+ vpu->num_pm_domains = 0;
+ return 0;
+ }
+
+ ret = dev_pm_domain_attach_list(vpu->dev, NULL, &vpu->pd_list);
+ if (ret < 0) {
+ dev_err(vpu->dev, "Can't attach pm domains, ret = %d\n", ret);
+ return ret;
+ }
+
+ vpu->num_pm_domains = ret;
+
+ return 0;
+}
+
+static struct device *wave6_vpu_get_performance_dev(struct wave6_vpu_device *vpu)
+{
+ int index;
+
+ if (!vpu->num_pm_domains)
+ return vpu->dev;
+
+ index = of_property_match_string(vpu->dev->of_node,
+ "power-domain-names", "perf");
+ if (index < 0 || index >= vpu->num_pm_domains)
+ return NULL;
+
+ return vpu->pd_list->pd_devs[index];
+}
+
+static int wave6_vpu_probe(struct platform_device *pdev)
+{
+ struct device_node *np;
+ struct wave6_vpu_device *vpu;
+ const struct wave6_vpu_resource *res;
+ int ret;
+
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to set DMA mask: %d\n", ret);
+ return ret;
+ }
+
+ res = of_device_get_match_data(&pdev->dev);
+ if (!res)
+ return -ENODEV;
+
+ vpu = devm_kzalloc(&pdev->dev, sizeof(*vpu), GFP_KERNEL);
+ if (!vpu)
+ return -ENOMEM;
+
+ ret = devm_mutex_init(&pdev->dev, &vpu->lock);
+ if (ret)
+ return ret;
+
+ atomic_set(&vpu->core_count, 0);
+ dev_set_drvdata(&pdev->dev, vpu);
+ vpu->dev = &pdev->dev;
+ vpu->res = res;
+ vpu->reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(vpu->reg_base))
+ return PTR_ERR(vpu->reg_base);
+
+ ret = devm_clk_bulk_get_all(&pdev->dev, &vpu->clks);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "failed to get clocks\n");
+
+ vpu->num_clks = ret;
+
+ ret = wave6_vpu_attach_pm_domains(vpu);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to attach pm domains\n");
+
+ np = of_parse_phandle(pdev->dev.of_node, "memory-region", 0);
+ if (np) {
+ struct resource mem;
+
+ ret = of_address_to_resource(np, 0, &mem);
+ of_node_put(np);
+ if (!ret) {
+ vpu->code_buf.phys_addr = mem.start;
+ vpu->code_buf.size = resource_size(&mem);
+ wave6_vpu_init_code_buf(vpu);
+ } else {
+ dev_warn(&pdev->dev, "memory-region is not available.\n");
+ }
+ }
+
+ vpu->sram_pool = of_gen_pool_get(pdev->dev.of_node, "sram", 0);
+ if (vpu->sram_pool) {
+ vpu->sram_buf.size = vpu->res->sram_size;
+ vpu->sram_buf.vaddr = gen_pool_dma_alloc(vpu->sram_pool,
+ vpu->sram_buf.size,
+ &vpu->sram_buf.phys_addr);
+ if (!vpu->sram_buf.vaddr)
+ vpu->sram_buf.size = 0;
+ else
+ vpu->sram_buf.dma_addr = dma_map_resource(&pdev->dev,
+ vpu->sram_buf.phys_addr,
+ vpu->sram_buf.size,
+ DMA_BIDIRECTIONAL,
+ 0);
+ }
+
+ vpu->thermal.dev = wave6_vpu_get_performance_dev(vpu);
+ vpu->thermal.of_node = vpu->dev->of_node;
+ if (vpu->thermal.dev) {
+ ret = wave6_vpu_cooling_init(&vpu->thermal);
+ if (ret)
+ dev_err(&pdev->dev, "failed to initialize thermal cooling, %d\n", ret);
+ }
+
+ wave6_vpu_allocate_work_buffers(vpu);
+
+ pm_runtime_enable(&pdev->dev);
+ vpu->fw_available = true;
+
+ ret = firmware_request_nowait_nowarn(THIS_MODULE,
+ vpu->res->fw_name,
+ &pdev->dev,
+ GFP_KERNEL,
+ vpu,
+ wave6_vpu_load_firmware);
+ if (ret) {
+ dev_err(&pdev->dev, "request firmware fail, ret = %d\n", ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ pm_runtime_disable(&pdev->dev);
+ if (vpu->thermal.dev)
+ wave6_vpu_cooling_remove(&vpu->thermal);
+ wave6_vpu_release(vpu);
+ wave6_vpu_detach_pm_domains(vpu);
+
+ return ret;
+}
+
+static void wave6_vpu_remove(struct platform_device *pdev)
+{
+ struct wave6_vpu_device *vpu = dev_get_drvdata(&pdev->dev);
+
+ pm_runtime_disable(vpu->dev);
+ if (vpu->thermal.dev)
+ wave6_vpu_cooling_remove(&vpu->thermal);
+ wave6_vpu_release(vpu);
+ wave6_vpu_detach_pm_domains(vpu);
+}
+
+static int __maybe_unused wave6_vpu_runtime_suspend(struct device *dev)
+{
+ struct wave6_vpu_device *vpu = dev_get_drvdata(dev);
+
+ clk_bulk_disable_unprepare(vpu->num_clks, vpu->clks);
+
+ return 0;
+}
+
+static int __maybe_unused wave6_vpu_runtime_resume(struct device *dev)
+{
+ struct wave6_vpu_device *vpu = dev_get_drvdata(dev);
+
+ return clk_bulk_prepare_enable(vpu->num_clks, vpu->clks);
+}
+
+static const struct dev_pm_ops wave6_vpu_pm_ops = {
+ SET_RUNTIME_PM_OPS(wave6_vpu_runtime_suspend,
+ wave6_vpu_runtime_resume, NULL)
+};
+
+static const struct of_device_id wave6_vpu_ids[] = {
+ { .compatible = "nxp,imx95-vpu", .data = &wave633c_data },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, wave6_vpu_ids);
+
+static struct platform_driver wave6_vpu_driver = {
+ .driver = {
+ .name = WAVE6_VPU_PLATFORM_DRIVER_NAME,
+ .of_match_table = wave6_vpu_ids,
+ .pm = &wave6_vpu_pm_ops,
+ },
+ .probe = wave6_vpu_probe,
+ .remove = wave6_vpu_remove,
+};
+
+module_platform_driver(wave6_vpu_driver);
+MODULE_DESCRIPTION("chips&media Wave6 VPU driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu.h b/drivers/media/platform/chips-media/wave6/wave6-vpu.h
new file mode 100644
index 000000000000..ec3c9299526b
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu.h
@@ -0,0 +1,143 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - wave6 driver
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#ifndef __WAVE6_VPU_H__
+#define __WAVE6_VPU_H__
+
+#include <linux/device.h>
+#include "wave6-vpu-thermal.h"
+#include "wave6-vdi.h"
+#include "wave6-vpuapi.h"
+
+#define WAVE6_VPU_PLATFORM_DRIVER_NAME "wave6-vpu"
+#define WAVE6_VPU_CORE_PLATFORM_DRIVER_NAME "wave6-vpu-core"
+
+struct wave6_vpu_device;
+struct vpu_core_device;
+
+/**
+ * enum wave6_vpu_state - VPU states
+ * @WAVE6_VPU_STATE_OFF: VPU is powered off
+ * @WAVE6_VPU_STATE_PREPARE: VPU is booting
+ * @WAVE6_VPU_STATE_ON: VPU is running
+ * @WAVE6_VPU_STATE_SLEEP: VPU is in a sleep mode
+ */
+enum wave6_vpu_state {
+ WAVE6_VPU_STATE_OFF,
+ WAVE6_VPU_STATE_PREPARE,
+ WAVE6_VPU_STATE_ON,
+ WAVE6_VPU_STATE_SLEEP
+};
+
+/**
+ * struct wave6_vpu_dma_buf - VPU buffer from reserved memory or gen_pool
+ * @size: Buffer size
+ * @dma_addr: Mapped address for device access
+ * @vaddr: Kernel virtual address
+ * @phys_addr: Physical address of the reserved memory region or gen_pool
+ *
+ * Represents a buffer allocated from pre-reserved device memory regions or
+ * SRAM via gen_pool_dma_alloc(). Used for code and SRAM buffers only.
+ * Managed by the VPU device.
+ */
+struct wave6_vpu_dma_buf {
+ size_t size;
+ dma_addr_t dma_addr;
+ void *vaddr;
+ phys_addr_t phys_addr;
+};
+
+/**
+ * struct wave6_vpu_resource - VPU device compatible data
+ * @fw_name: Firmware name for the device
+ * @sram_size: Required SRAM size
+ */
+struct wave6_vpu_resource {
+ const char *fw_name;
+ u32 sram_size;
+};
+
+#define WAVE6_IS_ENC BIT(0)
+#define WAVE6_IS_DEC BIT(1)
+
+#define WAVE633_CODEC_TYPE (WAVE6_IS_ENC | WAVE6_IS_DEC)
+#define WAVE633_COMPATIBLE_FW_VERSION 0x2010000
+
+/**
+ * struct wave6_vpu_core_resource - VPU CORE device compatible data
+ * @codec_types: Bitmask of supported codec types
+ * @compatible_fw_version: Firmware version compatible with driver
+ */
+struct wave6_vpu_core_resource {
+ int codec_types;
+ u32 compatible_fw_version;
+};
+
+/**
+ * struct wave6_vpu_device - VPU driver structure
+ * @get_vpu: Function pointer, boot or wake the device
+ * @put_vpu: Function pointer, power off or suspend the device
+ * @req_work_buffer: Function pointer, request allocation of a work buffer
+ * @dev: Platform device pointer
+ * @reg_base: Base address of MMIO registers
+ * @clks: Array of clock handles
+ * @num_clks: Number of entries in @clks
+ * @state: Device state
+ * @lock: Mutex protecting device data, register access
+ * @fw_available: Firmware availability flag
+ * @res: Device compatible data
+ * @sram_pool: Genalloc pool for SRAM allocations
+ * @sram_buf: Optional SRAM buffer
+ * @code_buf: Firmware code buffer
+ * @work_buffers: Array of work buffers
+ * @work_buffers_alloc: Number of allocated work buffers
+ * @work_buffers_avail: Number of available work buffers
+ * @thermal: Thermal cooling device
+ * @core_count: Number of available VPU core devices
+ *
+ * @get_vpu, @put_vpu, @req_work_buffer are called by VPU core devices.
+ *
+ * Buffers such as @sram_buf, @code_buf, and @work_buffers are managed
+ * by the VPU device and accessed exclusively by the firmware.
+ */
+struct wave6_vpu_device {
+ int (*get_vpu)(struct wave6_vpu_device *vpu,
+ struct vpu_core_device *core);
+ void (*put_vpu)(struct wave6_vpu_device *vpu,
+ struct vpu_core_device *core);
+ void (*req_work_buffer)(struct wave6_vpu_device *vpu,
+ struct vpu_core_device *core);
+ struct device *dev;
+ void __iomem *reg_base;
+ struct clk_bulk_data *clks;
+ int num_clks;
+ enum wave6_vpu_state state;
+ struct mutex lock; /* Protects device data, register access */
+
+ /* Prevents boot or sleep sequence if firmware is unavailable. */
+ bool fw_available;
+
+ const struct wave6_vpu_resource *res;
+ struct gen_pool *sram_pool;
+ struct wave6_vpu_dma_buf sram_buf;
+ struct wave6_vpu_dma_buf code_buf;
+
+ /* Allocates per-instance, used for storing instance-specific data. */
+ struct vpu_buf work_buffers[MAX_NUM_INSTANCE];
+ u32 work_buffers_alloc;
+ u32 work_buffers_avail;
+
+ struct vpu_thermal_cooling thermal;
+ atomic_t core_count;
+
+ int num_pm_domains;
+ struct dev_pm_domain_list *pd_list;
+
+ struct platform_device *core_pdevs[W6_VPU_MAX_NUM_CORE];
+};
+
+#endif /* __WAVE6_VPU_H__ */
--
2.31.1
^ permalink raw reply related
* [PATCH] pwm: atmel-tcb: Fix sleeping function called from invalid context
From: Sangyun Kim @ 2026-04-15 9:34 UTC (permalink / raw)
To: ukleinek
Cc: nicolas.ferre, alexandre.belloni, claudiu.beznea, linux-pwm,
linux-arm-kernel, linux-kernel
atmel_tcb_pwm_apply() holds tcbpwmc->lock as a spinlock via
guard(spinlock)() and then calls atmel_tcb_pwm_config(), which calls
clk_get_rate() twice. clk_get_rate() acquires clk_prepare_lock (a
mutex), so this is a sleep-in-atomic-context violation.
On CONFIG_DEBUG_ATOMIC_SLEEP kernels every pwm_apply_state() that
enables or reconfigures the PWM triggers a "BUG: sleeping function
called from invalid context" warning.
All callers of tcbpwmc->lock (the .request and .apply callbacks) run in
process context and only need mutual exclusion against each other, so
use a mutex instead of a spinlock and allow the sleeping calls inside
atmel_tcb_pwm_config().
Fixes: 37f7707077f5 ("pwm: atmel-tcb: Fix race condition and convert to guards")
Signed-off-by: Sangyun Kim <sangyun.kim@snu.ac.kr>
---
drivers/pwm/pwm-atmel-tcb.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c
index f9ff78ba122d..6405e82d9f10 100644
--- a/drivers/pwm/pwm-atmel-tcb.c
+++ b/drivers/pwm/pwm-atmel-tcb.c
@@ -17,6 +17,7 @@
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
+#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/of.h>
@@ -47,7 +48,7 @@ struct atmel_tcb_channel {
};
struct atmel_tcb_pwm_chip {
- spinlock_t lock;
+ struct mutex lock;
u8 channel;
u8 width;
struct regmap *regmap;
@@ -81,7 +82,7 @@ static int atmel_tcb_pwm_request(struct pwm_chip *chip,
tcbpwm->period = 0;
tcbpwm->div = 0;
- guard(spinlock)(&tcbpwmc->lock);
+ guard(mutex)(&tcbpwmc->lock);
regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), &cmr);
/*
@@ -335,7 +336,7 @@ static int atmel_tcb_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_cycle, period;
int ret;
- guard(spinlock)(&tcbpwmc->lock);
+ guard(mutex)(&tcbpwmc->lock);
if (!state->enabled) {
atmel_tcb_pwm_disable(chip, pwm, state->polarity);
@@ -438,7 +439,7 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev)
if (err)
goto err_gclk;
- spin_lock_init(&tcbpwmc->lock);
+ mutex_init(&tcbpwmc->lock);
err = pwmchip_add(chip);
if (err < 0)
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v2] arm64: dts: airoha: en7581: Enable spi nand controller for EN7581 EVB
From: Christian Marangi (Ansuel) @ 2026-04-15 9:47 UTC (permalink / raw)
To: Lorenzo Bianconi
Cc: Matthias Brugger, AngeloGioacchino Del Regno, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, linux-arm-kernel,
linux-mediatek, devicetree
In-Reply-To: <abBPufvrG8I8UP69@lore-desk>
Il giorno mar 10 mar 2026 alle ore 18:07 Lorenzo Bianconi
<lorenzo@kernel.org> ha scritto:
>
> > Enable spi controller used for snand memory device for EN7581 evaluation
> > board.
> >
> > Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
> > Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
>
> Hi all,
>
> it seems this patch has been reviewed by AngeloGioacchino, but it has never
> been applied to linux-mediatek tree (or at least I can't find it). It is marked
> as 'New, archived' in patchwork [0]. Am I missing something?
>
> Regards,
> Lorenzo
>
> [0] https://patchwork.kernel.org/project/linux-mediatek/patch/20250225-en7581-snfi-probe-fix-v2-1-92e35add701b@kernel.org/
>
Hi,
friendly ping here. There are lots of patch with review tag and ACK
also for 7583.
Any chance someone can ping maintainers that take care of picking these patch?
Or someone that can reply on how to handle this? Maybe we need to sync with
them? Lorenzo (and also me) are fully maintaining the Airoha ARM target also on
U-Boot. Also on OpenWrt this target is starting to get traction and is
getting used
there, so Airoha is not considered an abandoned target anymore.
^ permalink raw reply
* [BUG] stm32mp135-dk: DT uses PA13 used for LED and button at the same time
From: Ahmad Fatoum @ 2026-04-15 9:55 UTC (permalink / raw)
To: Fabien Dessenne, Patrice Chotard
Cc: Alexandre Torgue, Maxime Coquelin, linux-stm32,
linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org,
kernel@pengutronix.de
Hello,
I just noticed that barebox built against the v7.0-rc1 device trees
reports following error:
ERROR: gpiolib: _gpio_request: gpio-13 (led-red) status -16
This is caused by the combination of following two commits:
57012d79fefd ("ARM: dts: stm32: add UserPA13 button on stm32mp135f-dk")
31f0d9a486a8 ("ARM: dts: stm32: Add red LED for stm32mp135f-dk board")
Both reference the same &gpioa 13. Linux didn't seem to mind, but
barebox fails the LED driver probe, because the GPIO had already been
requested.
Assuming it is correct that they share the same GPIO physically,
does the current DT description make sense though for actual use?
Blinking the LED would register a button press, so it feels that they
should rather be mutually exclusive?
Thanks,
Ahmad
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply
* Re: [PATCH] KVM: arm64: pkvm: Adopt MARKER() to define host hypercall ranges
From: Fuad Tabba @ 2026-04-15 9:56 UTC (permalink / raw)
To: Marc Zyngier
Cc: kvmarm, linux-arm-kernel, Will Deacon, Vincent Donnefort,
Joey Gouly, Suzuki K Poulose, Oliver Upton, Zenghui Yu
In-Reply-To: <20260414160528.2218858-1-maz@kernel.org>
Hi Marc,
On Tue, 14 Apr 2026 at 17:05, Marc Zyngier <maz@kernel.org> wrote:
>
> The EL2 code defines ranges of host hypercalls that are either
> enabled at boot-time only, used by [nh]VHE KVM, or reserved to pKVM.
>
> The way these ranges are delineated is error prone, as the enum symbols
> defining the limits are expressed in terms of actual function symbols.
> This means that should a new function be added, special care must be
> taken to also update the limit symbol.
>
> Improve this by reusing the mechanism introduced for the vcpu_sysreg
> enum, which uses a MARKER() macro and some extra trickery to make
> the limit symbol standalone. Crucially, the limit symbol has the
> same value as the *following* symbol.
>
> The handle_host_hcall() function is then updated to make use of
> the new limit definitions and get rid of the brittle default
> upper limit. This allows for some more strict checks at build
> time, and the removal of an comparison at run time.
This is pretty neat. There is still the issue of a hole, i.e., adding
an enum in the middle but forgetting to add a function, but that is
caught in handle_host_hcall(). I can't think of an easy way to catch
that though (xarray that initializes both?)
>
> Signed-off-by: Marc Zyngier <maz@kernel.org>
Tested-by: Fuad Tabba <tabba@google.com>
Reviewed-by: Fuad Tabba <tabba@google.com>
Cheers,
/fuad
> ---
> arch/arm64/include/asm/kvm_asm.h | 12 ++++++++++--
> arch/arm64/include/asm/kvm_host.h | 3 ---
> arch/arm64/kvm/hyp/nvhe/hyp-main.c | 10 +++++-----
> 3 files changed, 15 insertions(+), 10 deletions(-)
>
> diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
> index 37414440cee7f..fa033be6141ad 100644
> --- a/arch/arm64/include/asm/kvm_asm.h
> +++ b/arch/arm64/include/asm/kvm_asm.h
> @@ -50,6 +50,9 @@
>
> #include <linux/mm.h>
>
> +#define MARKER(m) \
> + m, __after_##m = m - 1
> +
> enum __kvm_host_smccc_func {
> /* Hypercalls that are unavailable once pKVM has finalised. */
> /* __KVM_HOST_SMCCC_FUNC___kvm_hyp_init */
> @@ -59,8 +62,10 @@ enum __kvm_host_smccc_func {
> __KVM_HOST_SMCCC_FUNC___kvm_enable_ssbs,
> __KVM_HOST_SMCCC_FUNC___vgic_v3_init_lrs,
> __KVM_HOST_SMCCC_FUNC___vgic_v3_get_gic_config,
> +
> + MARKER(__KVM_HOST_SMCCC_FUNC_MIN_PKVM),
> +
> __KVM_HOST_SMCCC_FUNC___pkvm_prot_finalize,
> - __KVM_HOST_SMCCC_FUNC_MIN_PKVM = __KVM_HOST_SMCCC_FUNC___pkvm_prot_finalize,
>
> /* Hypercalls that are always available and common to [nh]VHE/pKVM. */
> __KVM_HOST_SMCCC_FUNC___kvm_adjust_pc,
> @@ -76,7 +81,8 @@ enum __kvm_host_smccc_func {
> __KVM_HOST_SMCCC_FUNC___vgic_v3_restore_vmcr_aprs,
> __KVM_HOST_SMCCC_FUNC___vgic_v5_save_apr,
> __KVM_HOST_SMCCC_FUNC___vgic_v5_restore_vmcr_apr,
> - __KVM_HOST_SMCCC_FUNC_MAX_NO_PKVM = __KVM_HOST_SMCCC_FUNC___vgic_v5_restore_vmcr_apr,
> +
> + MARKER(__KVM_HOST_SMCCC_FUNC_PKVM_ONLY),
>
> /* Hypercalls that are available only when pKVM has finalised. */
> __KVM_HOST_SMCCC_FUNC___pkvm_host_share_hyp,
> @@ -108,6 +114,8 @@ enum __kvm_host_smccc_func {
> __KVM_HOST_SMCCC_FUNC___tracing_reset,
> __KVM_HOST_SMCCC_FUNC___tracing_enable_event,
> __KVM_HOST_SMCCC_FUNC___tracing_write_event,
> +
> + MARKER(__KVM_HOST_SMCCC_FUNC_MAX)
> };
>
> #define DECLARE_KVM_VHE_SYM(sym) extern char sym[]
> diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
> index 851f6171751c0..44211e86f5ebd 100644
> --- a/arch/arm64/include/asm/kvm_host.h
> +++ b/arch/arm64/include/asm/kvm_host.h
> @@ -450,9 +450,6 @@ struct kvm_vcpu_fault_info {
> r = __VNCR_START__ + ((VNCR_ ## r) / 8), \
> __after_##r = __MAX__(__before_##r - 1, r)
>
> -#define MARKER(m) \
> - m, __after_##m = m - 1
> -
> enum vcpu_sysreg {
> __INVALID_SYSREG__, /* 0 is reserved as an invalid value */
> MPIDR_EL1, /* MultiProcessor Affinity Register */
> diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> index 73f2e0221e703..9e44c05cf780e 100644
> --- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> +++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> @@ -748,9 +748,11 @@ static const hcall_t host_hcall[] = {
> static void handle_host_hcall(struct kvm_cpu_context *host_ctxt)
> {
> DECLARE_REG(unsigned long, id, host_ctxt, 0);
> - unsigned long hcall_min = 0, hcall_max = -1;
> + unsigned long hcall_min = 0, hcall_max = __KVM_HOST_SMCCC_FUNC_MAX;
> hcall_t hfn;
>
> + BUILD_BUG_ON(ARRAY_SIZE(host_hcall) != __KVM_HOST_SMCCC_FUNC_MAX);
> +
> /*
> * If pKVM has been initialised then reject any calls to the
> * early "privileged" hypercalls. Note that we cannot reject
> @@ -763,16 +765,14 @@ static void handle_host_hcall(struct kvm_cpu_context *host_ctxt)
> if (static_branch_unlikely(&kvm_protected_mode_initialized)) {
> hcall_min = __KVM_HOST_SMCCC_FUNC_MIN_PKVM;
> } else {
> - hcall_max = __KVM_HOST_SMCCC_FUNC_MAX_NO_PKVM;
> + hcall_max = __KVM_HOST_SMCCC_FUNC_PKVM_ONLY;
> }
>
> id &= ~ARM_SMCCC_CALL_HINTS;
> id -= KVM_HOST_SMCCC_ID(0);
>
> - if (unlikely(id < hcall_min || id > hcall_max ||
> - id >= ARRAY_SIZE(host_hcall))) {
> + if (unlikely(id < hcall_min || id >= hcall_max))
> goto inval;
> - }
>
> hfn = host_hcall[id];
> if (unlikely(!hfn))
> --
> 2.47.3
>
^ permalink raw reply
* Re: [PATCH v12 2/2] arm: dts: aspeed: ventura: add Meta Ventura BMC
From: P.K. Lee @ 2026-04-15 10:05 UTC (permalink / raw)
To: Andrew Lunn
Cc: robh+dt, krzysztof.kozlowski+dt, conor+dt, joel, andrew,
devicetree, linux-arm-kernel, linux-aspeed, linux-kernel,
Jason-Hsu, p.k.lee
In-Reply-To: <e7a1588d-b4d4-4aa7-ba94-da3e2591d49c@lunn.ch>
> > > > > If there are no devices on the bus, why enable it?
> > > >
> > > > We intentionally enable it so user-space tools can access the switch
> > > > registers. I have added a comment in v13 to clarify this.
> > >
> > > Why would user space want to access the switch registers for an
> > > unmanaged switch? It sounds like you are using Marvells SDK in
> > > userspace to manage the switch, rather than using DSA.
> > >
> >
> > We do have a custom user-space daemon that configures the switch
> > registers for our specific use case. Should I remove the &mdio0 node
> > if it is only enabled and has no other configuration in the upstream
> > device tree?
>
> Please just be truthful that you have a user space driver, so need the
> bus enabled.
>
> I also guess you have some other kernel code that allows you to
> actually use the bus from user space? The typical ethernet IOCTL
> handler does not work for you, since you don't have an ethernet device
> using this bus. Such code is unlikely to be accepted into mainline. We
> don't like user space drivers when there is a perfectly good kernel
> driver for this switch.
Since the kernel driver for mv88e6xxx in kernel 6.6 used by this
project does not support LED control, and this feature is only
available starting from kernel 6.13, I had to initialize the LEDs of
the 88E6393X from user space.
In this case, should I remove the &mdio0 node?
P.K. Lee
^ permalink raw reply
* [PATCH] gpio: aspeed: fix AST2700 debounce selector bit definitions
From: Billy Tsai @ 2026-04-15 10:24 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski, Joel Stanley, Andrew Jeffery
Cc: linux-gpio, linux-arm-kernel, linux-aspeed, linux-kernel,
Billy Tsai
The AST2700 datasheet defines reg_debounce_sel1 as the low bit and
reg_debounce_sel2 as the high bit. The current driver uses the AST2600
mapping instead, where sel1 is the high bit and sel2 is the low bit.
As a result, the debounce selector bits are programmed in reverse on
AST2700. Swap the G7 sel1/sel2 bit definitions so the driver matches the
hardware definition.
Fixes: b2e861bd1eaf ("gpio: aspeed: Support G7 Aspeed gpio controller")
Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
drivers/gpio/gpio-aspeed.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/gpio/gpio-aspeed.c b/drivers/gpio/gpio-aspeed.c
index 9115e56a1626..98b5bfbc04a3 100644
--- a/drivers/gpio/gpio-aspeed.c
+++ b/drivers/gpio/gpio-aspeed.c
@@ -42,8 +42,8 @@
#define GPIO_G7_CTRL_IRQ_TYPE1 BIT(4)
#define GPIO_G7_CTRL_IRQ_TYPE2 BIT(5)
#define GPIO_G7_CTRL_RST_TOLERANCE BIT(6)
-#define GPIO_G7_CTRL_DEBOUNCE_SEL1 BIT(7)
-#define GPIO_G7_CTRL_DEBOUNCE_SEL2 BIT(8)
+#define GPIO_G7_CTRL_DEBOUNCE_SEL2 BIT(7)
+#define GPIO_G7_CTRL_DEBOUNCE_SEL1 BIT(8)
#define GPIO_G7_CTRL_INPUT_MASK BIT(9)
#define GPIO_G7_CTRL_IRQ_STS BIT(12)
#define GPIO_G7_CTRL_IN_DATA BIT(13)
---
base-commit: 6de23f81a5e08be8fbf5e8d7e9febc72a5b5f27f
change-id: 20260415-gpio-fix-1745583ecf06
Best regards,
--
Billy Tsai <billy_tsai@aspeedtech.com>
^ permalink raw reply related
* Re: [RFC V1 05/16] arm64/mm: Convert READ_ONCE() as pmdp_get() while accessing PMD
From: David Hildenbrand (Arm) @ 2026-04-15 10:31 UTC (permalink / raw)
To: Anshuman Khandual, linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ryan Roberts, Mark Rutland,
Lorenzo Stoakes, Andrew Morton, Mike Rapoport, Linu Cherian,
linux-kernel, linux-mm, kasan-dev
In-Reply-To: <8a3849cc-e6d3-4932-865f-476a137f891a@arm.com>
On 4/10/26 06:48, Anshuman Khandual wrote:
>
>
> On 08/04/26 5:41 PM, David Hildenbrand (Arm) wrote:
>> On 2/24/26 06:11, Anshuman Khandual wrote:
>>> Convert all READ_ONCE() based PMD accesses as pmdp_get() instead which will
>>> support both D64 and D128 translation regime going forward.
>>
>> You should mention the move from pmdp_test_and_clear_young(), and why it
>> is performed.
>
> Sure will do that. Actually there as a build problem while accessing
> pmdp_get() from pmdp_test_and_clear_young() while being inside the
> header which necessitated this move.
Could that be resolved while keeping the files in the header?
--
Cheers,
David
^ permalink raw reply
* [PATCH v3] arm64: dts: airoha: en7581: Enable spi nand controller for EN7581 EVB
From: Lorenzo Bianconi @ 2026-04-15 10:31 UTC (permalink / raw)
To: Matthias Brugger, AngeloGioacchino Del Regno, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: Arnd Bergmann, linux-arm-kernel, linux-mediatek, devicetree,
Lorenzo Bianconi
Enable spi controller used for snand memory device for EN7581 evaluation
board.
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
Changes in v3:
- Rebase and resend
- Link to v2: https://lore.kernel.org/linux-mediatek/20250225-en7581-snfi-probe-fix-v2-1-92e35add701b@kernel.org/
Changes in v2:
- Clarify the commit title
- Link to v1: https://lore.kernel.org/r/20250225-en7581-snfi-probe-fix-v1-1-77e4769574e4@kernel.org
---
arch/arm64/boot/dts/airoha/en7581-evb.dts | 4 ++++
arch/arm64/boot/dts/airoha/en7581.dtsi | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/airoha/en7581-evb.dts b/arch/arm64/boot/dts/airoha/en7581-evb.dts
index 886e2e4b5f64..3dc68dbf1be7 100644
--- a/arch/arm64/boot/dts/airoha/en7581-evb.dts
+++ b/arch/arm64/boot/dts/airoha/en7581-evb.dts
@@ -25,6 +25,10 @@ memory@80000000 {
};
};
+&snfi {
+ status = "okay";
+};
+
&spi_nand {
partitions {
compatible = "fixed-partitions";
diff --git a/arch/arm64/boot/dts/airoha/en7581.dtsi b/arch/arm64/boot/dts/airoha/en7581.dtsi
index ff6908a76e8e..0964cd783013 100644
--- a/arch/arm64/boot/dts/airoha/en7581.dtsi
+++ b/arch/arm64/boot/dts/airoha/en7581.dtsi
@@ -150,7 +150,7 @@ gic: interrupt-controller@9000000 {
interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_LOW>;
};
- spi@1fa10000 {
+ snfi: spi@1fa10000 {
compatible = "airoha,en7581-snand";
reg = <0x0 0x1fa10000 0x0 0x140>,
<0x0 0x1fa11000 0x0 0x160>;
---
base-commit: e6490a169f6d5f5bdea7a2e8a673890d43afadc0
change-id: 20260415-airoha-7581-spi-enable-034c0ef32db6
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply related
* Re: [RFC V1 06/16] arm64/mm: Convert READ_ONCE() as pudp_get() while accessing PUD
From: David Hildenbrand (Arm) @ 2026-04-15 10:32 UTC (permalink / raw)
To: Anshuman Khandual, linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ryan Roberts, Mark Rutland,
Lorenzo Stoakes, Andrew Morton, Mike Rapoport, Linu Cherian,
linux-kernel, linux-mm, kasan-dev
In-Reply-To: <6d91c17f-2027-482a-b0b2-98ad258f1687@arm.com>
On 4/10/26 06:50, Anshuman Khandual wrote:
>
>
> On 08/04/26 5:45 PM, David Hildenbrand (Arm) wrote:
>> On 2/24/26 06:11, Anshuman Khandual wrote:
>>> Convert all READ_ONCE() based PUD accesses as pudp_get() instead which will
>>> support both D64 and D128 translation regime going forward.
>>>
>>> Cc: Catalin Marinas <catalin.marinas@arm.com>
>>> Cc: Will Deacon <will@kernel.org>
>>> Cc: Ryan Roberts <ryan.roberts@arm.com>
>>> Cc: Mark Rutland <mark.rutland@arm.com>
>>> Cc: linux-arm-kernel@lists.infradead.org
>>> Cc: linux-kernel@vger.kernel.org
>>> Cc: kasan-dev@googlegroups.com
>>> Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
>>> ---
>>
>> I was wondering for a second whether it would be better to structure
>> this as "convert READ_ONCE to use pxxxp_get() in fault.c" instead,
>> essentially, to touch each file only once.
>
> But will not that create too many patches ?
Good for your patch count? :)
--
Cheers,
David
^ permalink raw reply
* Re: [RFC V1 07/16] arm64/mm: Convert READ_ONCE() as p4dp_get() while accessing P4D
From: David Hildenbrand (Arm) @ 2026-04-15 10:35 UTC (permalink / raw)
To: Anshuman Khandual, linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ryan Roberts, Mark Rutland,
Lorenzo Stoakes, Andrew Morton, Mike Rapoport, Linu Cherian,
linux-kernel, linux-mm, kasan-dev
In-Reply-To: <c490455f-084a-4f53-86b0-89c15669efb3@arm.com>
>>>
>>> @@ -2258,4 +2258,21 @@ int pmdp_test_and_clear_young(struct vm_area_struct *vma,
>>> }
>>> #endif /* CONFIG_TRANSPARENT_HUGEPAGE || CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG */
>>>
>>> +#if CONFIG_PGTABLE_LEVELS > 3
>>> +phys_addr_t pud_offset_phys(p4d_t *p4dp, unsigned long addr)
>>> +{
>>> + p4d_t p4d = p4dp_get(p4dp);
>>> +
>>> + BUG_ON(!pgtable_l4_enabled());
>>
>> Heh, while at it, convert that to a VM_WARN_ON_ONCE() or anything else
>> that is not a BUG.
>>
>> I strongly assume CONFIG_DEBUG_VM checks are sufficient.
>
> There are multiple similar BUG_ON() instances
>
> arch/arm64/include/asm/pgtable.h: BUG_ON(!pgtable_l4_enabled());
> arch/arm64/include/asm/pgtable.h: BUG_ON(!pgtable_l5_enabled());
>
> arch/arm64/mm/mmu.c: BUG_ON(pmd_val(old_pmd) != 0 &&
> arch/arm64/mm/mmu.c: BUG_ON(pud_val(old_pud) != 0 &&
> arch/arm64/mm/mmu.c: BUG_ON(p4d_val(old_p4d) != 0 &&
>
> Shall we convert all of them as VM_WARN_ON_ONCE() in a separate patch
> as a pre-requisite ?
In MM at least, we fix them up when touching the code, and only
sometimes convert entire files.
As documented in Documentation/process/coding-style.rst, there might be
some exceptions where BUG_ON makes sense (severe data corruption
possible), and some cases would want to use WARN_ON_ONCE() + recovery code.
All of these cases here sound like VM_WARN_ON_ONCE() is more than
sufficient though, so feel free to convert them all in one shot.
--
Cheers,
David
^ permalink raw reply
* Re: [RFC V1 08/16] arm64/mm: Convert READ_ONCE() as pgdp_get() while accessing PGD
From: David Hildenbrand (Arm) @ 2026-04-15 10:37 UTC (permalink / raw)
To: Anshuman Khandual, linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ryan Roberts, Mark Rutland,
Lorenzo Stoakes, Andrew Morton, Mike Rapoport, Linu Cherian,
linux-kernel, linux-mm, kasan-dev
In-Reply-To: <37a7e502-ac1d-4573-a877-23b1bfd07238@arm.com>
On 4/10/26 07:30, Anshuman Khandual wrote:
> On 08/04/26 5:49 PM, David Hildenbrand (Arm) wrote:
>> On 2/24/26 06:11, Anshuman Khandual wrote:
>>> Convert all READ_ONCE() based PGD accesses as pgdp_get() instead which will
>>> support both D64 and D128 translation regime going forward.
>>
>> Please mention here why you move p4d_offset_phys/p4d_offset. (same
>> applies to other patches)
>
> Will do that.
>
>>
>> Do we get additional function calls that might degrade some page table
>> walkers?
>
> Sorry did not get it. Are you asking if D128 adds new page table
> function paths thus increasing memory access latency etc ?
Each p4d_offset() etc. now does a function call.
So during ordinary generic page table walking code (see
folio_walk_start() as one example), you'd now perform a bunch more
expensive function calls:
pgdp = pgd_offset(vma->vm_mm, addr);
...
p4dp = p4d_offset(pgdp, addr);
...
pudp = pud_offset(p4dp, addr);
...
That's not desirable. So I wonder if there is a way to just keep these
things inline.
Might have to move the definitions of pgdp_get() etc.
--
Cheers,
David
^ permalink raw reply
* Re: [RFC V1 11/16] arm64/mm: Route all pgtable atomics to central helpers
From: David Hildenbrand (Arm) @ 2026-04-15 10:38 UTC (permalink / raw)
To: Anshuman Khandual, linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ryan Roberts, Mark Rutland,
Lorenzo Stoakes, Andrew Morton, Mike Rapoport, Linu Cherian,
linux-kernel, linux-mm
In-Reply-To: <e5558ba2-dad6-4090-87d0-2fdec426e815@arm.com>
On 4/10/26 06:02, Anshuman Khandual wrote:
> On 08/04/26 5:58 PM, David Hildenbrand (Arm) wrote:
>> On 2/24/26 06:11, Anshuman Khandual wrote:
>>> Route all cmpxchg() operations performed on various page table entries to a
>>> new ptdesc_cmpxchg_relaxed() helper. Similarly route all xchg() operations
>>> performed on page table entries to a new ptdesc_xchg_relaxed() helper.
>>>
>>> Currently these helpers just forward to the same APIs that were previously
>>> called direct, but in future we will change the routing for D128 which is
>>> too long to use the standard APIs.
>>>
>>> Cc: Catalin Marinas <catalin.marinas@arm.com>
>>> Cc: Will Deacon <will@kernel.org>
>>> Cc: Ryan Roberts <ryan.roberts@arm.com>
>>> Cc: Mark Rutland <mark.rutland@arm.com>
>>> Cc: linux-arm-kernel@lists.infradead.org
>>> Cc: linux-kernel@vger.kernel.org
>>> Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
>>> ---
>>> arch/arm64/include/asm/pgtable.h | 23 +++++++++++++++++------
>>> arch/arm64/mm/fault.c | 2 +-
>>> 2 files changed, 18 insertions(+), 7 deletions(-)
>>>
>>> diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
>>> index 42124d2f323d..cf69ce68f951 100644
>>> --- a/arch/arm64/include/asm/pgtable.h
>>> +++ b/arch/arm64/include/asm/pgtable.h
>>> @@ -87,6 +87,17 @@ static inline void arch_leave_lazy_mmu_mode(void)
>>> #define ptdesc_get(x) READ_ONCE(x)
>>> #define ptdesc_set(x, val) WRITE_ONCE(x, val)
>>>
>>> +static inline ptdesc_t ptdesc_cmpxchg_relaxed(ptdesc_t *ptep, ptdesc_t old,
>>> + ptdesc_t new)
>>> +{
>>> + return cmpxchg_relaxed(ptep, old, new);
>>> +}
>>> +
>>> +static inline ptdesc_t ptdesc_xchg_relaxed(ptdesc_t *ptep, ptdesc_t new)
>>> +{
>>> + return xchg_relaxed(ptep, new);
>>> +}
>>> +
>>
>> We really want the rename of ptdesc_t before this change.
>>
>
> Planning to rename ptdesc_t as ptent_t in a pre-requisite
> patch early in the series.
Good. As discussed, something that contains "pxx" might be clearer than
something that includes "pte".
--
Cheers,
David
^ permalink raw reply
* Re: [RFC V1 14/16] arm64/mm: Enable fixmap with 5 level page table
From: David Hildenbrand (Arm) @ 2026-04-15 10:39 UTC (permalink / raw)
To: Anshuman Khandual, linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ryan Roberts, Mark Rutland,
Lorenzo Stoakes, Andrew Morton, Mike Rapoport, Linu Cherian,
linux-kernel, linux-mm
In-Reply-To: <ac73cc87-8663-4d45-8c0f-4a01ffc0fc29@arm.com>
On 4/10/26 05:22, Anshuman Khandual wrote:
>
>
> On 08/04/26 5:59 PM, David Hildenbrand (Arm) wrote:
>> On 2/24/26 06:11, Anshuman Khandual wrote:
>>> Enable fixmap with 5 level page table when required. This creates table
>>> entries at the PGD level. Add a fallback stub for pgd_page_paddr() when
>>> (PGTBALE_LEVELS <= 4) which helps in intercepting any unintended usage.
>>
>> Can you add the "why" ?
>
> Following reworded commit message should work ?
>
> ------------------------------------------------------------------
> arm64/mm: Enable fixmap with 5 level page table
>
> FEAT_D128 halves PTRS_PER_PXX thus shrinking the VA range coverage
> for each page table level. Hence in order to preserve all existing
> VA range configurations, some geometry now need to become 5-level.
>
> Since fixmap is used to build and manipulate page tables early on
> during boot the mapping must also gain that additional level which
> was not required earlier.
>
> Enable fixmap with 5 level page table when required. This creates table
> entries at the PGD level. Add a fallback stub for pgd_page_paddr() when
> (PGTBALE_LEVELS <= 4) which helps in intercepting any unintended usage.
> -------------------------------------------------------------------
Perfect, thanks!
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH RFC] ACPI: processor: idle: Do not propagate acpi_processor_ffh_lpi_probe() -ENODEV
From: Sudeep Holla @ 2026-04-15 10:45 UTC (permalink / raw)
To: Breno Leitao
Cc: lihuisong (C), Rafael J. Wysocki, Sudeep Holla, Len Brown,
lpieralisi, catalin.marinas, will, Rafael J. Wysocki, linux-acpi,
linux-kernel, pjaroszynski, guohanjun, linux-arm-kernel, rmikey,
kernel-team
In-Reply-To: <ad5qxncafOiTeLNg@gmail.com>
On Tue, Apr 14, 2026 at 09:31:33AM -0700, Breno Leitao wrote:
> Hello Sudeep,
>
> On Tue, Apr 14, 2026 at 03:10:03PM +0100, Sudeep Holla wrote:
> > On Tue, Apr 14, 2026 at 06:14:19AM -0700, Breno Leitao wrote:
> > > Hello Sudeep,
> > >
> > > On Tue, Apr 14, 2026 at 01:25:53PM +0100, Sudeep Holla wrote:
> > > > So while I understand that the kernel did not report an error previously, that
> > > > does not mean the _LPI table is merely moot on this platform when it contains
> > > > only a WFI state.
> > >
> > > Can you clarify whether datacenter ARM systems are expected to expose
> > > deeper idle states beyond WFI in their _LPI tables?
> > >
> >
> > Of course any system that prefers to save power when its idling must have
> > these _LPI deeper idle states.
> >
> > > Backing up, I'm observing 72 pr_err() messages during boot on these
> > > hosts and trying to determine whether this indicates a firmware issue or
> > > if the kernel needs adjustment.
> >
> > I consider this a firmware issue, but not a fatal one. What matters more is
> > the behavior after those errors are reported.
>
> I understand. While I'm not a hardware or firmware vendor myself, I can
> see how they might consider power management features _optional_ for certain
> server configurations.
>
> > If you force success, either through your change or through the PSCI approach
> > suggested by lihuisong, then in practice you are only enabling a cpuidle
> > driver with a single usable state: WFI. That is not inherently wrong, but it
> > also does not provide much benefit.
>
> Given that this isn't a critical error, would it make sense to downgrade
> the pr_err() to pr_debug()? is it a reasonable compromise. I just want
> to avoid these pr_err() all accross the board, affecting kernel health
> metrics in large fleets.
>
> My proposal:
>
> commit c98007f9e10fe229672d29c3844c96705cecaed5
> Author: Breno Leitao <leitao@debian.org>
> Date: Tue Apr 14 05:28:28 2026 -0700
>
> ACPI: processor: idle: Downgrade FFH LPI probe failure message to pr_debug()
>
> The "Invalid FFH LPI data" message is printed at pr_err() level for every
> CPU when acpi_processor_ffh_lpi_probe() fails. On platforms where the FFH
> probe legitimately returns an error (e.g., no deep idle states beyond
> WFI), this floods the kernel log with per-CPU error messages that are not
> actionable.
>
> Downgrade to pr_debug() since this is a diagnostic message, not a
> critical error.
>
> Signed-off-by: Breno Leitao <leitao@debian.org>
>
> diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c
> index ee5facccbe10c..ab93a2c10a9ad 100644
> --- a/drivers/acpi/processor_idle.c
> +++ b/drivers/acpi/processor_idle.c
> @@ -1259,7 +1259,7 @@ static int acpi_processor_get_power_info(struct acpi_processor *pr)
> if (pr->flags.has_lpi) {
> ret = acpi_processor_ffh_lpi_probe(pr->id);
> if (ret)
> - pr_err("CPU%u: Invalid FFH LPI data\n", pr->id);
> + pr_debug("CPU%u: Invalid FFH LPI data\n", pr->id);
I am afraid if this might mask the real _LPI table entry errors.
Need to think, I am still of the opinion that we need warn on that system
as _LPI with just WFI is simply useless.
--
Regards,
Sudeep
^ permalink raw reply
* Re: [PATCH RFC] arm64/scs: Fix potential sign extension issue of advance_loc4
From: Catalin Marinas @ 2026-04-15 10:48 UTC (permalink / raw)
To: Wentao Guan; +Cc: linux-arm-kernel, will, hello, linux-kernel
In-Reply-To: <20260413095459.2470584-1-guanwentao@uniontech.com>
On Mon, Apr 13, 2026 at 05:54:59PM +0800, Wentao Guan wrote:
> The expression (*opcode++ << 24) and exp * code_alignment_factor
> may overflow signed int and becomes negative.
>
> Fix this by casting each byte to u64 before shifting. Also fix
> the misaligned break statement while we are here.
>
> Example of the result can be seen here:
> Link: https://godbolt.org/z/zhY8d3595
>
> It maybe not a real problem, but could be a issue in future.
>
> Fixes: d499e9627d70 ("arm64/scs: Fix handling of advance_loc4")
> Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
> ---
> arch/arm64/kernel/pi/patch-scs.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/arch/arm64/kernel/pi/patch-scs.c b/arch/arm64/kernel/pi/patch-scs.c
> index dac568e4a54f2..3944ad899021c 100644
> --- a/arch/arm64/kernel/pi/patch-scs.c
> +++ b/arch/arm64/kernel/pi/patch-scs.c
> @@ -196,9 +196,9 @@ static int scs_handle_fde_frame(const struct eh_frame *frame,
> loc += *opcode++ * code_alignment_factor;
> loc += (*opcode++ << 8) * code_alignment_factor;
> loc += (*opcode++ << 16) * code_alignment_factor;
> - loc += (*opcode++ << 24) * code_alignment_factor;
> + loc += ((u64)*opcode++ << 24) * code_alignment_factor;
> size -= 4;
> - break;
> + break;
The fix makes sense. I'll queue it at -rc1.
Thanks.
--
Catalin
^ permalink raw reply
* [PATCH v2 0/3] gpio: Add EIO GPIO support
From: Shubhrajyoti Datta @ 2026-04-15 10:56 UTC (permalink / raw)
To: linux-kernel
Cc: git, shubhrajyoti.datta, Shubhrajyoti Datta, Srinivas Neeli,
Michal Simek, Linus Walleij, Bartosz Golaszewski, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, linux-gpio, devicetree,
linux-arm-kernel
Add the EIO GPIO support.
Add the dt description and the compatible to the driver.
Changes in v2:
- Add new patch to sort the compatible strings alphabetically
- Add description of EIO block in the dt-bindings patch
Shubhrajyoti Datta (3):
dt-bindings: gpio: zynq: Sort compatible strings alphabetically
dt-bindings: gpio: Add EIO GPIO compatible to gpio-zynq
gpio: zynq: Add eio gpio support
.../devicetree/bindings/gpio/gpio-zynq.yaml | 18 +++++++++++++++---
drivers/gpio/gpio-zynq.c | 12 ++++++++++++
2 files changed, 27 insertions(+), 3 deletions(-)
--
2.34.1
^ permalink raw reply
* [PATCH v2 2/3] dt-bindings: gpio: Add EIO GPIO compatible to gpio-zynq
From: Shubhrajyoti Datta @ 2026-04-15 10:56 UTC (permalink / raw)
To: linux-kernel
Cc: git, shubhrajyoti.datta, Shubhrajyoti Datta, Srinivas Neeli,
Michal Simek, Linus Walleij, Bartosz Golaszewski, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, linux-gpio, devicetree,
linux-arm-kernel
In-Reply-To: <20260415105628.957689-1-shubhrajyoti.datta@amd.com>
EIO (Extended IO) is a GPIO block found on xa2ve3288 silicon..
Signed-off-by: Shubhrajyoti Datta <shubhrajyoti.datta@amd.com>
---
Changes in v2:
- Add description of EIO block in the dt-bindings patch
.../devicetree/bindings/gpio/gpio-zynq.yaml | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml b/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml
index 30a7f836c341..1ca067217509 100644
--- a/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml
+++ b/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml
@@ -12,6 +12,7 @@ maintainers:
properties:
compatible:
enum:
+ - xlnx,eio-gpio-1.0
- xlnx,pmc-gpio-1.0
- xlnx,versal-gpio-1.0
- xlnx,zynq-gpio-1.0
@@ -30,7 +31,7 @@ properties:
gpio-line-names:
description: strings describing the names of each gpio line
- minItems: 58
+ minItems: 52
maxItems: 174
interrupt-controller: true
@@ -89,6 +90,17 @@ allOf:
minItems: 116
maxItems: 116
+ - if:
+ properties:
+ compatible:
+ enum:
+ - xlnx,eio-gpio-1.0
+ then:
+ properties:
+ gpio-line-names:
+ minItems: 52
+ maxItems: 52
+
required:
- compatible
- reg
--
2.34.1
^ permalink raw reply related
* [PATCH v2 1/3] dt-bindings: gpio: zynq: Sort compatible strings alphabetically
From: Shubhrajyoti Datta @ 2026-04-15 10:56 UTC (permalink / raw)
To: linux-kernel
Cc: git, shubhrajyoti.datta, Shubhrajyoti Datta, Srinivas Neeli,
Michal Simek, Linus Walleij, Bartosz Golaszewski, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, linux-gpio, devicetree,
linux-arm-kernel
In-Reply-To: <20260415105628.957689-1-shubhrajyoti.datta@amd.com>
Sort the compatible string alphabetically.
Signed-off-by: Shubhrajyoti Datta <shubhrajyoti.datta@amd.com>
---
Changes in v2:
- Add new patch to sort the compatible strings alphabetically
Documentation/devicetree/bindings/gpio/gpio-zynq.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml b/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml
index 5e2496379a3c..30a7f836c341 100644
--- a/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml
+++ b/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml
@@ -12,10 +12,10 @@ maintainers:
properties:
compatible:
enum:
+ - xlnx,pmc-gpio-1.0
+ - xlnx,versal-gpio-1.0
- xlnx,zynq-gpio-1.0
- xlnx,zynqmp-gpio-1.0
- - xlnx,versal-gpio-1.0
- - xlnx,pmc-gpio-1.0
reg:
maxItems: 1
--
2.34.1
^ permalink raw reply related
* [PATCH v2 3/3] gpio: zynq: Add eio gpio support
From: Shubhrajyoti Datta @ 2026-04-15 10:56 UTC (permalink / raw)
To: linux-kernel
Cc: git, shubhrajyoti.datta, Shubhrajyoti Datta, Srinivas Neeli,
Michal Simek, Linus Walleij, Bartosz Golaszewski, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, linux-gpio, devicetree,
linux-arm-kernel
In-Reply-To: <20260415105628.957689-1-shubhrajyoti.datta@amd.com>
Add support for the EIO GPIO controller found on
xa2ve3288 silicon.
The EIO GPIO block provides access to multiplexed I/O pins exposed
through the EIO interface. Only bank 0 and bank 1 are connected to
external MIO pins, with 26 GPIOs per bank (52 GPIOs total). This
change extends the Zynq GPIO driver to support the EIO GPIO
variant.
Signed-off-by: Shubhrajyoti Datta <shubhrajyoti.datta@amd.com>
---
(no changes since v1)
drivers/gpio/gpio-zynq.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/drivers/gpio/gpio-zynq.c b/drivers/gpio/gpio-zynq.c
index 571e366624d2..8118ae3412c2 100644
--- a/drivers/gpio/gpio-zynq.c
+++ b/drivers/gpio/gpio-zynq.c
@@ -25,6 +25,7 @@
#define VERSAL_GPIO_MAX_BANK 4
#define PMC_GPIO_MAX_BANK 5
#define VERSAL_UNUSED_BANKS 2
+#define EIO_GPIO_MAX_BANK 2
#define ZYNQ_GPIO_BANK0_NGPIO 32
#define ZYNQ_GPIO_BANK1_NGPIO 22
@@ -818,6 +819,16 @@ static const struct dev_pm_ops zynq_gpio_dev_pm_ops = {
RUNTIME_PM_OPS(zynq_gpio_runtime_suspend, zynq_gpio_runtime_resume, NULL)
};
+static const struct zynq_platform_data eio_gpio_def = {
+ .label = "eio_gpio",
+ .ngpio = 52,
+ .max_bank = EIO_GPIO_MAX_BANK,
+ .bank_min[0] = 0,
+ .bank_max[0] = 25, /* 0 to 25 are connected to MIOs (26 pins) */
+ .bank_min[1] = 26,
+ .bank_max[1] = 51, /* Bank 1 are connected to MIOs (26 pins) */
+};
+
static const struct zynq_platform_data versal_gpio_def = {
.label = "versal_gpio",
.quirks = GPIO_QUIRK_VERSAL,
@@ -882,6 +893,7 @@ static const struct of_device_id zynq_gpio_of_match[] = {
{ .compatible = "xlnx,zynqmp-gpio-1.0", .data = &zynqmp_gpio_def },
{ .compatible = "xlnx,versal-gpio-1.0", .data = &versal_gpio_def },
{ .compatible = "xlnx,pmc-gpio-1.0", .data = &pmc_gpio_def },
+ { .compatible = "xlnx,eio-gpio-1.0", .data = &eio_gpio_def },
{ /* end of table */ }
};
MODULE_DEVICE_TABLE(of, zynq_gpio_of_match);
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v13 00/48] arm64: Support for Arm CCA in KVM
From: Steven Price @ 2026-04-15 11:01 UTC (permalink / raw)
To: Alper Gun
Cc: kvm, kvmarm, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Aneesh Kumar K . V, Emi Kisanuki,
Vishal Annapurve
In-Reply-To: <CABpDEukEO4Y_fg8fv5Nr1_Pw_qOK=2UmioXk=WyPEzoEprPcGA@mail.gmail.com>
On 14/04/2026 22:40, Alper Gun wrote:
> On Wed, Mar 18, 2026 at 8:54 AM Steven Price <steven.price@arm.com> wrote:
>>
>> This series adds support for running protected VMs using KVM under the
>> Arm Confidential Compute Architecture (CCA).
>>
>> New major version number! This now targets RMM v2.0-bet0[1]. And unlike
>> for Linux this represents a significant change.
>>
>> RMM v2.0 brings with it the ability to configure the RMM to have the
>> same page size as the host (so no more RMM_PAGE_SIZE and dealing with
>> granules being different from host pages). It also introduces range
>> based APIs for many operations which should be more efficient and
>> simplifies the code in places.
>>
>> The handling of the GIC has changed, so the system registers are used to
>> pass the GIC state rather than memory. This means fewer changes to the
>> KVM code as it looks much like a normal VM in this respect.
>>
>> And of course the new uAPI introduced in the previous v12 posting is
>> retained so that also remains simplified compared to earlier postings.
>>
>> The RMM support for v2.0 is still early and so this series includes a
>> few hacks to ease the integration. Of note are that there are some RMM
>> v1.0 SMCs added to paper over areas where the RMM implementation isn't
>> quite ready for v2.0, and "SROs" (see below) are deferred to the final
>> patch in the series.
>>
>> The PMU in RMM v2.0 requires more handling on the RMM-side (and
>> therefore simplifies the implementation on Linux), but this isn't quite
>> ready yet. The Linux side is implemented (but untested).
>>
>> PSCI still requires the VMM to provide the "target" REC for operations
>> that affect another vCPU. This is likely to change in a future version
>> of the specification. There's also a desire to force PSCI to be handled
>> in the VMM for realm guests - this isn't implemented yet as I'm waiting
>> for the dust to settle on the RMM interface first.
>>
>> Stateful RMI Operations
>> -----------------------
>>
>> The RMM v2.0 spec brings a new concept of Stateful RMI Operations (SROs)
>> which allow the RMM to complete an operation over several SMC calls and
>> requesting/returning memory to the host. This has the benefit of
>> allowing interrupts to be handled in the middle of an operation (by
>> returning to the host to handle the interrupt without completing the
>> operation) and enables the RMM to dynamically allocate memory for
>> internal tracking purposes. One example of this is RMI_REC_CREATE no
>> longer needs "auxiliary granules" provided upfront but can request the
>> memory needed during the RMI_REC_CREATE operation.
>>
>> There are a fairly large number of operations that are defined as SROs
>> in the specification, but current both Linux and RMM only have support
>> for RMI_REC_CREATE and RMI_REC_DESTROY. There a number of TODOs/FIXMEs
>> in the code where support is missing.
>>
>> Given the early stage support for this, the SRO handling is all confined
>> to the final patch. This patch can be dropped to return to a pre-SRO
>> state (albeit a mixture of RMM v1.0 and v2.0 APIs) for testing purposes.
>>
>> A future posting will reorder the series to move the generic SRO support
>> to an early patch and will implement the proper support for this in all
>> RMI SMCs.
>>
>> One aspect of SROs which is not yet well captured is that in some
>> circumstances the Linux kernel will need to call an SRO call in a
>> context where memory allocation is restricted (e.g. because a spinlock
>> is held). In this case the intention is that the SRO will be cancelled,
>> the spinlock dropped so the memory allocation can be completed, and then
>> the SRO restarted (obviously after rechecking the state that the
>> spinlock was protecting). For this reason the code stores the memory
>> allocations within a struct rmi_sro_state object - see the final patch
>> for more details.
>>
>> This series is based on v7.0-rc1. It is also available as a git
>> repository:
>>
>> https://gitlab.arm.com/linux-arm/linux-cca cca-host/v13
>>
>>
>
> Hi Steven,
>
> I have a question regarding host kexec and kdump scenarios, and
> whether there is any plan to make them work in this initial series.
>
> Intel TDX and AMD SEV-SNP both have a firmware shutdown command that
> is invoked during the kexec or panic code paths to safely bypass
> hardware memory protections and boot into the new kernel. As far as
> I know, there is no similar global teardown command available for
> the RMM.
Correct, the RMM specification as it stands doesn't provide a mechanism
for the host to do this. The host would have to identify all the realm
guests in the system: specifically the address of the RDs (Realm
Descriptors) and RECs (Realm Execution Contexts). It needs this to tear
down the guests and be able to undelegate the memory.
It's an interesting point and I'll raise the idea of a "firmware
shutdown command" to make this more possible.
> What is the roadmap for supporting both general kexec and
> more specifically kdump (panic) scenarios with CCA?
I don't have a roadmap I'm afraid for these. kexec in theory would be
possible with KVM gracefully terminating all realms. For kdump/panic
that sort of graceful shutdown isn't really appropriate (or likely to
succeed).
There is also some RMM configuration which cannot be repeated (see
RMI_RMM_CONFIG_SET) - which implies that the kexec kernel must be
similar to the first kernel (i.e. same page size).
Thanks,
Steve
^ permalink raw reply
* [PATCH 2/3] arm64: dts: amlogic: t7: Add UART controllers nodes
From: Ronald Claveau @ 2026-04-15 11:16 UTC (permalink / raw)
To: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-kernel, linux-amlogic, devicetree, linux-kernel,
Ronald Claveau
In-Reply-To: <20260415-add-bluetooth-t7-vim4-v1-0-0ba0746cc1d6@aliel.fr>
Add device tree nodes for UART B through F (serial@7a000 to
serial@82000), completing the UART controller description for the T7
SoC. Each node includes the peripheral clock.
While at it, move the uart_a node to its correct position in the
bus address order (0x78000) to comply with the DT requirement that
nodes be sorted by their reg address. Complete the
uart_a node with its peripheral clock (CLKID_SYS_UART_A) and the
associated clock-names, matching the vendor default clock assignment,
consistent with the other UART nodes.
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 61 +++++++++++++++++++++++++----
1 file changed, 54 insertions(+), 7 deletions(-)
diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
index 531931cc1437c..56b015cfbd6d1 100644
--- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
+++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
@@ -577,13 +577,6 @@ gpio_intc: interrupt-controller@4080 {
<10 11 12 13 14 15 16 17 18 19 20 21>;
};
- uart_a: serial@78000 {
- compatible = "amlogic,t7-uart", "amlogic,meson-s4-uart";
- reg = <0x0 0x78000 0x0 0x18>;
- interrupts = <GIC_SPI 168 IRQ_TYPE_EDGE_RISING>;
- status = "disabled";
- };
-
gp0: clock-controller@8080 {
compatible = "amlogic,t7-gp0-pll";
reg = <0x0 0x8080 0x0 0x20>;
@@ -713,6 +706,60 @@ pwm_ao_cd: pwm@60000 {
status = "disabled";
};
+ uart_a: serial@78000 {
+ compatible = "amlogic,t7-uart", "amlogic,meson-s4-uart";
+ reg = <0x0 0x78000 0x0 0x18>;
+ interrupts = <GIC_SPI 168 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&xtal>, <&clkc_periphs CLKID_SYS_UART_A>, <&xtal>;
+ clock-names = "xtal", "pclk", "baud";
+ status = "disabled";
+ };
+
+ uart_b: serial@7a000 {
+ compatible = "amlogic,t7-uart", "amlogic,meson-s4-uart";
+ reg = <0x0 0x7a000 0x0 0x18>;
+ interrupts = <GIC_SPI 169 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&xtal>, <&clkc_periphs CLKID_SYS_UART_B>, <&xtal>;
+ clock-names = "xtal", "pclk", "baud";
+ status = "disabled";
+ };
+
+ uart_c: serial@7c000 {
+ compatible = "amlogic,t7-uart", "amlogic,meson-s4-uart";
+ reg = <0x0 0x7c000 0x0 0x18>;
+ interrupts = <GIC_SPI 170 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&xtal>, <&clkc_periphs CLKID_SYS_UART_C>, <&xtal>;
+ clock-names = "xtal", "pclk", "baud";
+ status = "disabled";
+ };
+
+ uart_d: serial@7e000 {
+ compatible = "amlogic,t7-uart", "amlogic,meson-s4-uart";
+ reg = <0x0 0x7e000 0x0 0x18>;
+ interrupts = <GIC_SPI 171 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&xtal>, <&clkc_periphs CLKID_SYS_UART_D>, <&xtal>;
+ clock-names = "xtal", "pclk", "baud";
+ status = "disabled";
+ };
+
+ uart_e: serial@80000 {
+ compatible = "amlogic,t7-uart", "amlogic,meson-s4-uart";
+ reg = <0x0 0x80000 0x0 0x18>;
+ interrupts = <GIC_SPI 172 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&xtal>, <&clkc_periphs CLKID_SYS_UART_E>, <&xtal>;
+ clock-names = "xtal", "pclk", "baud";
+ status = "disabled";
+ };
+
+ uart_f: serial@82000 {
+ compatible = "amlogic,t7-uart", "amlogic,meson-s4-uart";
+ reg = <0x0 0x82000 0x0 0x18>;
+ interrupts = <GIC_SPI 173 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&xtal>, <&clkc_periphs CLKID_SYS_UART_F>, <&xtal>;
+ clock-names = "xtal", "pclk", "baud";
+ status = "disabled";
+ };
+
sd_emmc_a: mmc@88000 {
compatible = "amlogic,t7-mmc", "amlogic,meson-axg-mmc";
reg = <0x0 0x88000 0x0 0x800>;
--
2.49.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox