From: Frank Li <Frank.li@oss.nxp.com>
To: Nas Chung <nas.chung@chipsnmedia.com>
Cc: mchehab@kernel.org, hverkuil@xs4all.nl, robh@kernel.org,
krzk+dt@kernel.org, conor+dt@kernel.org, shawnguo@kernel.org,
s.hauer@pengutronix.de, linux-media@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-imx@nxp.com, linux-arm-kernel@lists.infradead.org,
jackson.lee@chipsnmedia.com, lafley.kim@chipsnmedia.com,
marek.vasut@mailbox.org, Ming Qian <ming.qian@oss.nxp.com>
Subject: Re: [PATCH v7 8/9] media: chips-media: wave6: Add Wave6 control driver
Date: Thu, 10 Sep 2026 17:04:14 -0400 [thread overview]
Message-ID: <aqMbTg0oDTrjs7Hl@lizhi-Precision-Tower-5810> (raw)
In-Reply-To: <9a39dbcd48c8326394992cc048a635fb2472537e.1788496816.git.nas.chung@chipsnmedia.com>
On Fri, Sep 04, 2026 at 03:46:34PM +0900, Nas Chung wrote:
> 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>
> ---
Look likes this driver is independent on other one, as parent devices, which
register children devices only.
You can move this patch just after binding.
Frank
> 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 | 826 ++++++++++++++++++
> .../platform/chips-media/wave6/wave6-vpu.h | 143 +++
> 6 files changed, 1005 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..40948063b058
> --- /dev/null
> +++ b/drivers/media/platform/chips-media/wave6/wave6-vpu.c
> @@ -0,0 +1,826 @@
> +// 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/property.h>
> +#include <linux/of_irq.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);
> + 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 (dma_mapping_error(vpu->dev, 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 int 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 -EINVAL;
> + }
> +
> + ret = wave6_vpu_check_result(core);
> + if (ret) {
> + dev_err(vpu->dev, "init work buf failed, reason 0x%x\n", ret);
> + return -EIO;
> + }
> +
> + vpu->work_buffers_avail = vpu->work_buffers_alloc;
> +
> + return 0;
> +}
> +
> +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:
> + ret = wave6_vpu_init_work_buf(vpu, core);
> + if (ret)
> + return ret;
> +
> + 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 (!wave6_is_product_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;
> +
> + 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 = 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);
> + if (dma_mapping_error(&pdev->dev, vpu->sram_buf.dma_addr)) {
> + gen_pool_free(vpu->sram_pool,
> + (unsigned long)vpu->sram_buf.vaddr,
> + vpu->sram_buf.size);
> + memset(&vpu->sram_buf, 0, sizeof(vpu->sram_buf));
> + }
> + }
> + }
> +
> + vpu->thermal.dev = wave6_vpu_get_performance_dev(vpu);
> + if (vpu->thermal.dev) {
> + ret = wave6_vpu_cooling_init(vpu->dev, &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;
> +
> + request_module("platform:%s", WAVE6_VPU_CORE_PLATFORM_DRIVER_NAME);
> +
> + 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:
> + wave6_vpu_release(vpu);
> + pm_runtime_disable(&pdev->dev);
> + 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);
> +
> + wave6_vpu_release(vpu);
> + pm_runtime_disable(vpu->dev);
> + wave6_vpu_detach_pm_domains(vpu);
> +}
> +
> +static int 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 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 = {
> + RUNTIME_PM_OPS(wave6_vpu_runtime_suspend,
> + wave6_vpu_runtime_resume, NULL)
> + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> + pm_runtime_force_resume)
> +};
> +
> +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 = pm_ptr(&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_AUTHOR("CHIPS&MEDIA INC");
> +MODULE_FIRMWARE("cnm/wave633c_imx9_codec_fw.bin");
> +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
>
next prev parent reply other threads:[~2026-09-10 21:04 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-04 6:46 [PATCH v7 0/9] Add support for Wave6 video codec driver Nas Chung
2026-09-04 6:46 ` [PATCH v7 1/9] media: v4l2-common: Fix P010 format info Nas Chung
2026-09-04 6:46 ` [PATCH v7 2/9] dt-bindings: media: nxp: Add Wave6 video codec device Nas Chung
2026-09-08 9:50 ` Krzysztof Kozlowski
2026-09-09 16:33 ` Frank Li
2026-09-04 6:46 ` [PATCH v7 5/9] media: chips-media: wave6: Add Wave6 core driver Nas Chung
2026-09-10 20:48 ` Frank Li
2026-09-11 8:35 ` Nas Chung
2026-09-04 6:46 ` [PATCH v7 6/9] media: chips-media: wave6: Improve debugging capabilities Nas Chung
2026-09-09 20:40 ` Frank Li
2026-09-10 4:38 ` Nas Chung
2026-09-10 16:16 ` Frank Li
2026-09-11 3:53 ` Nas Chung
2026-09-04 6:46 ` [PATCH v7 7/9] media: chips-media: wave6: Add Wave6 thermal cooling device Nas Chung
2026-09-04 6:46 ` [PATCH v7 8/9] media: chips-media: wave6: Add Wave6 control driver Nas Chung
2026-09-10 21:04 ` Frank Li [this message]
2026-09-11 7:07 ` Nas Chung
2026-09-04 6:46 ` [PATCH v7 9/9] arm64: dts: freescale: imx95: Add video codec node Nas Chung
[not found] ` <39115ce2f3d9cc18350cddc650b7cb40317e5ef3.1788496816.git.nas.chung@chipsnmedia.com>
2026-09-09 19:49 ` [PATCH v7 3/9] media: chips-media: wave6: Add Wave6 VPU interface Frank Li
2026-09-10 8:14 ` Nas Chung
2026-09-10 20:00 ` [PATCH v7 0/9] Add support for Wave6 video codec driver Frank Li
2026-09-11 6:58 ` Nas Chung
[not found] ` <235b2de7057182d619a361dffc247f1c1f438a3c.1788496816.git.nas.chung@chipsnmedia.com>
2026-09-10 20:37 ` [PATCH v7 4/9] media: chips-media: wave6: Add v4l2 m2m driver support Frank Li
2026-09-11 7:12 ` Nas Chung
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aqMbTg0oDTrjs7Hl@lizhi-Precision-Tower-5810 \
--to=frank.li@oss.nxp.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=hverkuil@xs4all.nl \
--cc=jackson.lee@chipsnmedia.com \
--cc=krzk+dt@kernel.org \
--cc=lafley.kim@chipsnmedia.com \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-imx@nxp.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=marek.vasut@mailbox.org \
--cc=mchehab@kernel.org \
--cc=ming.qian@oss.nxp.com \
--cc=nas.chung@chipsnmedia.com \
--cc=robh@kernel.org \
--cc=s.hauer@pengutronix.de \
--cc=shawnguo@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox