* [PATCH v6 21/28] media: rockchip: rga: share the interrupt when an external iommu is used
From: Sven Püschel @ 2026-05-15 15:32 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, m.tretter,
p.zabel, Sven Püschel, Michael Olbrich, Nicolas Dufresne
In-Reply-To: <20260515-spu-rga3-v6-0-e547152eb9c9@pengutronix.de>
From: Michael Olbrich <m.olbrich@pengutronix.de>
The RGA3 and the corresponding iommu share the interrupt. So in that
case, request a shared interrupt so that the iommu driver can request
it as well.
Signed-off-by: Michael Olbrich <m.olbrich@pengutronix.de>
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index e3c99c3f7c5be..cda3cecb1ce8c 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -767,7 +767,8 @@ static int rga_probe(struct platform_device *pdev)
goto err_put_clk;
}
- ret = devm_request_irq(rga->dev, irq, rga_isr, 0,
+ ret = devm_request_irq(rga->dev, irq, rga_isr,
+ rga_has_internal_iommu(rga) ? 0 : IRQF_SHARED,
dev_name(rga->dev), rga);
if (ret < 0) {
dev_err(rga->dev, "failed to request irq\n");
--
2.54.0
^ permalink raw reply related
* [PATCH v6 14/28] media: rockchip: rga: move cmdbuf to rga_ctx
From: Sven Püschel @ 2026-05-15 15:32 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, m.tretter,
p.zabel, Sven Püschel, Nicolas Dufresne
In-Reply-To: <20260515-spu-rga3-v6-0-e547152eb9c9@pengutronix.de>
Move the command buffer to the rga_ctx struct in preparation to reuse
an already prepared command buffer. This allows to split the command
buffer setup in a further commit to setup a template for the command
buffer at streamon and only update the buffer addresses in device_run
and trigger the command stream. No sync point is added, as one command
buffer should only be used for one conversion at a time.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga-hw.c | 28 +++++++---------
drivers/media/platform/rockchip/rga/rga.c | 48 ++++++++++++++++------------
drivers/media/platform/rockchip/rga/rga.h | 5 +--
3 files changed, 41 insertions(+), 40 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c
index 17f7a67c0b4bb..9881c14f908d5 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.c
+++ b/drivers/media/platform/rockchip/rga/rga-hw.c
@@ -122,8 +122,7 @@ static struct rga_addr_offset *rga_lookup_draw_pos(struct
static void rga_cmd_set_src_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
{
- struct rockchip_rga *rga = ctx->rga;
- u32 *dest = rga->cmdbuf_virt;
+ u32 *dest = ctx->cmdbuf_virt;
unsigned int reg;
reg = RGA_MMU_SRC_BASE - RGA_MODE_BASE_REG;
@@ -135,8 +134,7 @@ static void rga_cmd_set_src_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
static void rga_cmd_set_src1_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
{
- struct rockchip_rga *rga = ctx->rga;
- u32 *dest = rga->cmdbuf_virt;
+ u32 *dest = ctx->cmdbuf_virt;
unsigned int reg;
reg = RGA_MMU_SRC1_BASE - RGA_MODE_BASE_REG;
@@ -148,8 +146,7 @@ static void rga_cmd_set_src1_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
static void rga_cmd_set_dst_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
{
- struct rockchip_rga *rga = ctx->rga;
- u32 *dest = rga->cmdbuf_virt;
+ u32 *dest = ctx->cmdbuf_virt;
unsigned int reg;
reg = RGA_MMU_DST_BASE - RGA_MODE_BASE_REG;
@@ -162,7 +159,7 @@ static void rga_cmd_set_dst_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
static void rga_cmd_set_trans_info(struct rga_ctx *ctx)
{
struct rockchip_rga *rga = ctx->rga;
- u32 *dest = rga->cmdbuf_virt;
+ u32 *dest = ctx->cmdbuf_virt;
unsigned int scale_dst_w, scale_dst_h;
unsigned int src_h, src_w, dst_h, dst_w;
union rga_src_info src_info;
@@ -322,8 +319,7 @@ static void rga_cmd_set_src_info(struct rga_ctx *ctx,
struct rga_addr_offset *offset)
{
struct rga_corners_addr_offset src_offsets;
- struct rockchip_rga *rga = ctx->rga;
- u32 *dest = rga->cmdbuf_virt;
+ u32 *dest = ctx->cmdbuf_virt;
unsigned int src_h, src_w, src_x, src_y;
src_h = ctx->in.crop.height;
@@ -350,8 +346,7 @@ static void rga_cmd_set_dst_info(struct rga_ctx *ctx,
{
struct rga_addr_offset *dst_offset;
struct rga_corners_addr_offset offsets;
- struct rockchip_rga *rga = ctx->rga;
- u32 *dest = rga->cmdbuf_virt;
+ u32 *dest = ctx->cmdbuf_virt;
unsigned int dst_h, dst_w, dst_x, dst_y;
unsigned int mir_mode = 0;
unsigned int rot_mode = 0;
@@ -397,8 +392,7 @@ static void rga_cmd_set_dst_info(struct rga_ctx *ctx,
static void rga_cmd_set_mode(struct rga_ctx *ctx)
{
- struct rockchip_rga *rga = ctx->rga;
- u32 *dest = rga->cmdbuf_virt;
+ u32 *dest = ctx->cmdbuf_virt;
union rga_mode_ctrl mode;
union rga_alpha_ctrl0 alpha_ctrl0;
union rga_alpha_ctrl1 alpha_ctrl1;
@@ -423,7 +417,7 @@ static void rga_cmd_set(struct rga_ctx *ctx,
{
struct rockchip_rga *rga = ctx->rga;
- memset(rga->cmdbuf_virt, 0, RGA_CMDBUF_SIZE);
+ memset(ctx->cmdbuf_virt, 0, RGA_CMDBUF_SIZE);
rga_cmd_set_src_addr(ctx, src->dma_desc_pa);
/*
@@ -439,11 +433,11 @@ static void rga_cmd_set(struct rga_ctx *ctx,
rga_cmd_set_dst_info(ctx, &dst->offset);
rga_cmd_set_trans_info(ctx);
- rga_write(rga, RGA_CMD_BASE, rga->cmdbuf_phy);
+ rga_write(rga, RGA_CMD_BASE, ctx->cmdbuf_phy);
/* sync CMD buf for RGA */
- dma_sync_single_for_device(rga->dev, rga->cmdbuf_phy,
- PAGE_SIZE, DMA_BIDIRECTIONAL);
+ dma_sync_single_for_device(rga->dev, ctx->cmdbuf_phy,
+ PAGE_SIZE, DMA_BIDIRECTIONAL);
}
static void rga_hw_start(struct rockchip_rga *rga,
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index 77b8c7ab74274..bf6bbcbfc869b 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -219,6 +219,16 @@ static int rga_open(struct file *file)
ctx = kzalloc_obj(*ctx);
if (!ctx)
return -ENOMEM;
+
+ /* Create CMD buffer */
+ ctx->cmdbuf_virt = dma_alloc_attrs(rga->dev, rga->hw->cmdbuf_size,
+ &ctx->cmdbuf_phy, GFP_KERNEL,
+ DMA_ATTR_WRITE_COMBINE);
+ if (!ctx->cmdbuf_virt) {
+ ret = -ENOMEM;
+ goto rel_ctx;
+ }
+
ctx->rga = rga;
/* Set default formats */
ctx->in = def_frame;
@@ -230,15 +240,13 @@ static int rga_open(struct file *file)
ctx->out.fmt->fourcc, def_width, def_height);
if (mutex_lock_interruptible(&rga->mutex)) {
- kfree(ctx);
- return -ERESTARTSYS;
+ ret = -ERESTARTSYS;
+ goto rel_cmdbuf;
}
ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(rga->m2m_dev, ctx, &queue_init);
if (IS_ERR(ctx->fh.m2m_ctx)) {
ret = PTR_ERR(ctx->fh.m2m_ctx);
- mutex_unlock(&rga->mutex);
- kfree(ctx);
- return ret;
+ goto unlock_mutex;
}
v4l2_fh_init(&ctx->fh, video_devdata(file));
v4l2_fh_add(&ctx->fh, file);
@@ -252,6 +260,15 @@ static int rga_open(struct file *file)
mutex_unlock(&rga->mutex);
return 0;
+
+unlock_mutex:
+ mutex_unlock(&rga->mutex);
+rel_cmdbuf:
+ dma_free_attrs(rga->dev, rga->hw->cmdbuf_size, ctx->cmdbuf_virt,
+ ctx->cmdbuf_phy, DMA_ATTR_WRITE_COMBINE);
+rel_ctx:
+ kfree(ctx);
+ return ret;
}
static int rga_release(struct file *file)
@@ -266,6 +283,10 @@ static int rga_release(struct file *file)
v4l2_ctrl_handler_free(&ctx->ctrl_handler);
v4l2_fh_del(&ctx->fh, file);
v4l2_fh_exit(&ctx->fh);
+
+ dma_free_attrs(rga->dev, rga->hw->cmdbuf_size, ctx->cmdbuf_virt,
+ ctx->cmdbuf_phy, DMA_ATTR_WRITE_COMBINE);
+
kfree(ctx);
mutex_unlock(&rga->mutex);
@@ -720,19 +741,10 @@ static int rga_probe(struct platform_device *pdev)
pm_runtime_put(rga->dev);
- /* Create CMD buffer */
- rga->cmdbuf_virt = dma_alloc_attrs(rga->dev, rga->hw->cmdbuf_size,
- &rga->cmdbuf_phy, GFP_KERNEL,
- DMA_ATTR_WRITE_COMBINE);
- if (!rga->cmdbuf_virt) {
- ret = -ENOMEM;
- goto rel_m2m;
- }
-
ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1);
if (ret) {
v4l2_err(&rga->v4l2_dev, "Failed to register video device\n");
- goto free_dma;
+ goto rel_m2m;
}
v4l2_info(&rga->v4l2_dev, "Registered %s as /dev/%s\n",
@@ -740,9 +752,6 @@ static int rga_probe(struct platform_device *pdev)
return 0;
-free_dma:
- dma_free_attrs(rga->dev, rga->hw->cmdbuf_size, rga->cmdbuf_virt,
- rga->cmdbuf_phy, DMA_ATTR_WRITE_COMBINE);
rel_m2m:
v4l2_m2m_release(rga->m2m_dev);
rel_vdev:
@@ -759,9 +768,6 @@ static void rga_remove(struct platform_device *pdev)
{
struct rockchip_rga *rga = platform_get_drvdata(pdev);
- dma_free_attrs(rga->dev, rga->hw->cmdbuf_size, rga->cmdbuf_virt,
- rga->cmdbuf_phy, DMA_ATTR_WRITE_COMBINE);
-
v4l2_info(&rga->v4l2_dev, "Removing\n");
v4l2_m2m_release(rga->m2m_dev);
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index 27b3c9b4f220c..04aeb7b429523 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -53,6 +53,9 @@ struct rga_ctx {
struct rga_frame out;
struct v4l2_ctrl_handler ctrl_handler;
+ void *cmdbuf_virt;
+ dma_addr_t cmdbuf_phy;
+
int osequence;
int csequence;
@@ -89,8 +92,6 @@ struct rockchip_rga {
spinlock_t ctrl_lock;
struct rga_ctx *curr;
- dma_addr_t cmdbuf_phy;
- void *cmdbuf_virt;
const struct rga_hw *hw;
};
--
2.54.0
^ permalink raw reply related
* [PATCH v6 17/28] media: rockchip: rga: check scaling factor
From: Sven Püschel @ 2026-05-15 15:32 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, m.tretter,
p.zabel, Sven Püschel
In-Reply-To: <20260515-spu-rga3-v6-0-e547152eb9c9@pengutronix.de>
Check the scaling factor to avoid potential problems. This is relevant
for the upcoming RGA3 support, as it can hang when the scaling factor
is exceeded.
The check is done at streamon when the other side is already streaming
to avoid incorrectly failing if the application configures the other
side after calling streamon. As try_fmt shouldn't be state aware,
it cannot be used to limit the format based on the scaling factor.
Therefore the check is done just before the actual streaming would be
started.
As the driver allows changing the rotation and selection while
streaming, add additional checks to ensure these changes
don't exceed the scaling factor.
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
Changes in v6:
- Dropped scaling adjustment in s_fmt, as this didn't match the try_fmt
result (which shouldn't have it to avoid making it stateful)
- Moved scaling check to the prepare_streaming callback instead of
overwriting the ioctl directly
- Consider rotation when checking the scaling
- Check scaling factor when adjusting rotation and selection while
streaming
---
drivers/media/platform/rockchip/rga/rga-buf.c | 28 ++++++++++++
drivers/media/platform/rockchip/rga/rga-hw.c | 1 +
drivers/media/platform/rockchip/rga/rga-hw.h | 1 +
drivers/media/platform/rockchip/rga/rga.c | 63 +++++++++++++++++++++++++--
drivers/media/platform/rockchip/rga/rga.h | 4 ++
5 files changed, 94 insertions(+), 3 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga-buf.c b/drivers/media/platform/rockchip/rga/rga-buf.c
index ffc6162b2e681..dcaba66f5c1fc 100644
--- a/drivers/media/platform/rockchip/rga/rga-buf.c
+++ b/drivers/media/platform/rockchip/rga/rga-buf.c
@@ -197,6 +197,33 @@ static void rga_buf_return_buffers(struct vb2_queue *q,
}
}
+static int rga_buf_prepare_streaming(struct vb2_queue *q)
+{
+ struct rga_ctx *ctx = vb2_get_drv_priv(q);
+ const struct rga_hw *hw = ctx->rga->hw;
+ int ret;
+
+ /* It's safe to check the streaming state of the other queue,
+ * as the streamon ioctl's can't race due to the lock set in
+ * the queue_init function.
+ */
+ if ((V4L2_TYPE_IS_OUTPUT(q->type) &&
+ vb2_is_streaming(v4l2_m2m_get_dst_vq(ctx->fh.m2m_ctx))) ||
+ (V4L2_TYPE_IS_CAPTURE(q->type) &&
+ vb2_is_streaming(v4l2_m2m_get_src_vq(ctx->fh.m2m_ctx)))) {
+ /*
+ * As the other side is already streaming,
+ * check that the max scaling factor isn't exceeded.
+ */
+ ret = rga_check_scaling(hw, &ctx->in.crop, &ctx->out.crop,
+ ctx->rotate);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
static int rga_buf_start_streaming(struct vb2_queue *q, unsigned int count)
{
struct rga_ctx *ctx = vb2_get_drv_priv(q);
@@ -232,6 +259,7 @@ const struct vb2_ops rga_qops = {
.buf_prepare = rga_buf_prepare,
.buf_queue = rga_buf_queue,
.buf_cleanup = rga_buf_cleanup,
+ .prepare_streaming = rga_buf_prepare_streaming,
.start_streaming = rga_buf_start_streaming,
.stop_streaming = rga_buf_stop_streaming,
};
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c
index 567d39e58d33f..f2900812ba76f 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.c
+++ b/drivers/media/platform/rockchip/rga/rga-hw.c
@@ -584,6 +584,7 @@ const struct rga_hw rga2_hw = {
.max_width = MAX_WIDTH,
.min_height = MIN_HEIGHT,
.max_height = MAX_HEIGHT,
+ .max_scaling_factor = MAX_SCALING_FACTOR,
.stride_alignment = 4,
.setup_cmdbuf = rga_hw_setup_cmdbuf,
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.h b/drivers/media/platform/rockchip/rga/rga-hw.h
index c2e34be751939..805ec23e5e3f4 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.h
+++ b/drivers/media/platform/rockchip/rga/rga-hw.h
@@ -14,6 +14,7 @@
#define MIN_WIDTH 34
#define MIN_HEIGHT 34
+#define MAX_SCALING_FACTOR 16
#define RGA_TIMEOUT 500
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index 394b14b9469df..22954bbae55fc 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -127,7 +127,9 @@ static int rga_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct rga_ctx *ctx = container_of(ctrl->handler, struct rga_ctx,
ctrl_handler);
+ const struct rga_hw *hw = ctx->rga->hw;
unsigned long flags;
+ int ret = 0;
spin_lock_irqsave(&ctx->rga->ctrl_lock, flags);
switch (ctrl->id) {
@@ -138,6 +140,13 @@ static int rga_s_ctrl(struct v4l2_ctrl *ctrl)
ctx->vflip = ctrl->val;
break;
case V4L2_CID_ROTATE:
+ if (vb2_is_streaming(v4l2_m2m_get_dst_vq(ctx->fh.m2m_ctx)) &&
+ vb2_is_streaming(v4l2_m2m_get_src_vq(ctx->fh.m2m_ctx))) {
+ ret = rga_check_scaling(hw, &ctx->in.crop,
+ &ctx->out.crop, ctrl->val);
+ if (ret < 0)
+ goto s_ctrl_done;
+ }
ctx->rotate = ctrl->val;
break;
case V4L2_CID_BG_COLOR:
@@ -145,8 +154,10 @@ static int rga_s_ctrl(struct v4l2_ctrl *ctrl)
break;
}
ctx->cmdbuf_dirty = true;
+
+s_ctrl_done:
spin_unlock_irqrestore(&ctx->rga->ctrl_lock, flags);
- return 0;
+ return ret;
}
static const struct v4l2_ctrl_ops rga_ctrl_ops = {
@@ -182,6 +193,38 @@ static int rga_setup_ctrls(struct rga_ctx *ctx)
return 0;
}
+static bool check_scaling_factor(const struct rga_hw *hw, u32 src_size,
+ u32 dst_size)
+{
+ if (src_size < dst_size)
+ return src_size * hw->max_scaling_factor >= dst_size;
+ else
+ return dst_size * hw->max_scaling_factor >= src_size;
+}
+
+int rga_check_scaling(const struct rga_hw *hw, const struct v4l2_rect *crop_in,
+ const struct v4l2_rect *crop_out, u32 rotate)
+{
+ u32 scaled_width;
+ u32 scaled_height;
+
+ if (rotate == 90 || rotate == 270) {
+ scaled_width = crop_out->height;
+ scaled_height = crop_out->width;
+ } else {
+ scaled_width = crop_out->width;
+ scaled_height = crop_out->height;
+ }
+
+ if (!check_scaling_factor(hw, crop_in->width, scaled_width))
+ return -EINVAL;
+
+ if (!check_scaling_factor(hw, crop_in->height, scaled_height))
+ return -EINVAL;
+
+ return 0;
+}
+
static struct rga_fmt *rga_fmt_find(struct rockchip_rga *rga, u32 pixelformat)
{
unsigned int i;
@@ -525,7 +568,6 @@ static int vidioc_s_selection(struct file *file, void *priv,
struct rga_ctx *ctx = file_to_rga_ctx(file);
struct rockchip_rga *rga = ctx->rga;
struct rga_frame *f;
- int ret = 0;
f = rga_get_frame(ctx, s->type);
if (IS_ERR(f))
@@ -569,10 +611,25 @@ static int vidioc_s_selection(struct file *file, void *priv,
return -EINVAL;
}
+ if (vb2_is_streaming(v4l2_m2m_get_dst_vq(ctx->fh.m2m_ctx)) &&
+ vb2_is_streaming(v4l2_m2m_get_src_vq(ctx->fh.m2m_ctx))) {
+ int ret = 0;
+
+ if (V4L2_TYPE_IS_OUTPUT(s->type))
+ ret = rga_check_scaling(rga->hw, &s->r, &ctx->out.crop,
+ ctx->rotate);
+ else
+ ret = rga_check_scaling(rga->hw, &ctx->in.crop, &s->r,
+ ctx->rotate);
+
+ if (ret < 0)
+ return ret;
+ }
+
f->crop = s->r;
ctx->cmdbuf_dirty = true;
- return ret;
+ return 0;
}
static const struct v4l2_ioctl_ops rga_ioctl_ops = {
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index 5360f092fecf0..df525c6aea8b6 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -123,6 +123,9 @@ static inline struct rga_vb_buffer *vb_to_rga(struct vb2_v4l2_buffer *vb)
struct rga_frame *rga_get_frame(struct rga_ctx *ctx, enum v4l2_buf_type type);
+int rga_check_scaling(const struct rga_hw *hw, const struct v4l2_rect *crop_in,
+ const struct v4l2_rect *crop_out, u32 rotate);
+
/* RGA Buffers Manage */
extern const struct vb2_ops rga_qops;
@@ -151,6 +154,7 @@ struct rga_hw {
size_t cmdbuf_size;
u32 min_width, min_height;
u32 max_width, max_height;
+ u8 max_scaling_factor;
u8 stride_alignment;
void (*setup_cmdbuf)(struct rga_ctx *ctx);
--
2.54.0
^ permalink raw reply related
* [PATCH v6 13/28] media: rockchip: rga: calculate x_div/y_div using v4l2_format_info
From: Sven Püschel @ 2026-05-15 15:32 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, m.tretter,
p.zabel, Sven Püschel, Nicolas Dufresne
In-Reply-To: <20260515-spu-rga3-v6-0-e547152eb9c9@pengutronix.de>
Calculate the x_div and y_div variables with the information from
v4l2_format_info instead of storing these in the rga_fmt struct.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga-hw.c | 45 +++++++---------------------
drivers/media/platform/rockchip/rga/rga.h | 2 --
2 files changed, 11 insertions(+), 36 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c
index 40498796507e0..17f7a67c0b4bb 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.c
+++ b/drivers/media/platform/rockchip/rga/rga-hw.c
@@ -42,6 +42,7 @@ rga_get_addr_offset(struct rga_frame *frm, struct rga_addr_offset *offset,
{
struct rga_corners_addr_offset offsets;
struct rga_addr_offset *lt, *lb, *rt, *rb;
+ const struct v4l2_format_info *format_info;
unsigned int x_div = 0,
y_div = 0, uv_stride = 0, pixel_width = 0;
@@ -50,8 +51,16 @@ rga_get_addr_offset(struct rga_frame *frm, struct rga_addr_offset *offset,
rt = &offsets.right_top;
rb = &offsets.right_bottom;
- x_div = frm->fmt->x_div;
- y_div = frm->fmt->y_div;
+ format_info = v4l2_format_info(frm->pix.pixelformat);
+ /* x_div is only used for the u/v planes.
+ * When the format doesn't have these, use 1 to avoid a division by zero.
+ */
+ if (format_info->bpp[1])
+ x_div = format_info->hdiv * format_info->bpp_div[1] /
+ format_info->bpp[1];
+ else
+ x_div = 1;
+ y_div = format_info->vdiv;
uv_stride = frm->stride / x_div;
pixel_width = frm->stride / frm->pix.width;
@@ -476,128 +485,96 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_ALPHA_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR8888,
.depth = 32,
- .y_div = 1,
- .x_div = 1,
},
{
.fourcc = V4L2_PIX_FMT_ABGR32,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR8888,
.depth = 32,
- .y_div = 1,
- .x_div = 1,
},
{
.fourcc = V4L2_PIX_FMT_XBGR32,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_XBGR8888,
.depth = 32,
- .y_div = 1,
- .x_div = 1,
},
{
.fourcc = V4L2_PIX_FMT_RGB24,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_RGB888,
.depth = 24,
- .y_div = 1,
- .x_div = 1,
},
{
.fourcc = V4L2_PIX_FMT_BGR24,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_RGB888,
.depth = 24,
- .y_div = 1,
- .x_div = 1,
},
{
.fourcc = V4L2_PIX_FMT_ARGB444,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR4444,
.depth = 16,
- .y_div = 1,
- .x_div = 1,
},
{
.fourcc = V4L2_PIX_FMT_ARGB555,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR1555,
.depth = 16,
- .y_div = 1,
- .x_div = 1,
},
{
.fourcc = V4L2_PIX_FMT_RGB565,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_BGR565,
.depth = 16,
- .y_div = 1,
- .x_div = 1,
},
{
.fourcc = V4L2_PIX_FMT_NV21,
.color_swap = RGA_COLOR_UV_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420SP,
.depth = 12,
- .y_div = 2,
- .x_div = 1,
},
{
.fourcc = V4L2_PIX_FMT_NV61,
.color_swap = RGA_COLOR_UV_SWAP,
.hw_format = RGA_COLOR_FMT_YUV422SP,
.depth = 16,
- .y_div = 1,
- .x_div = 1,
},
{
.fourcc = V4L2_PIX_FMT_NV12,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420SP,
.depth = 12,
- .y_div = 2,
- .x_div = 1,
},
{
.fourcc = V4L2_PIX_FMT_NV12M,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420SP,
.depth = 12,
- .y_div = 2,
- .x_div = 1,
},
{
.fourcc = V4L2_PIX_FMT_NV16,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV422SP,
.depth = 16,
- .y_div = 1,
- .x_div = 1,
},
{
.fourcc = V4L2_PIX_FMT_YUV420,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420P,
.depth = 12,
- .y_div = 2,
- .x_div = 2,
},
{
.fourcc = V4L2_PIX_FMT_YUV422P,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV422P,
.depth = 16,
- .y_div = 1,
- .x_div = 2,
},
{
.fourcc = V4L2_PIX_FMT_YVU420,
.color_swap = RGA_COLOR_UV_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420P,
.depth = 12,
- .y_div = 2,
- .x_div = 2,
},
};
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index 640e510285341..27b3c9b4f220c 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -20,8 +20,6 @@
struct rga_fmt {
u32 fourcc;
int depth;
- u8 y_div;
- u8 x_div;
u8 color_swap;
u8 hw_format;
};
--
2.54.0
^ permalink raw reply related
* [PATCH v6 20/28] media: rockchip: rga: support external iommus
From: Sven Püschel @ 2026-05-15 15:32 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, m.tretter,
p.zabel, Sven Püschel, Nicolas Dufresne
In-Reply-To: <20260515-spu-rga3-v6-0-e547152eb9c9@pengutronix.de>
In preparation for the RGA3 add support for external iommus. This is a
transition step to just disable the RGA2 specific mmu table setup code.
Currently a simple rga_hw struct field is used to set the internal iommu.
But to handle the case of more sophisticated detection mechanisms
(e.g. check for an iommu property in the device tree), it is abstracted
by an inline function.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
Changes in v6:
- Also select VIDEOBUF2_DMA_CONTIG in Kconfig, Flagged by sashiko:
https://sashiko.dev/#/patchset/20260325-spu-rga3-v4-0-e90ec1c61354%40pengutronix.de?part=10
---
drivers/media/platform/rockchip/rga/Kconfig | 1 +
drivers/media/platform/rockchip/rga/rga-buf.c | 31 ++++++++++++++++++---------
drivers/media/platform/rockchip/rga/rga-hw.c | 1 +
drivers/media/platform/rockchip/rga/rga.c | 11 ++++++++--
drivers/media/platform/rockchip/rga/rga.h | 6 ++++++
5 files changed, 38 insertions(+), 12 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/Kconfig b/drivers/media/platform/rockchip/rga/Kconfig
index 727a0f6ea4664..846e555829f38 100644
--- a/drivers/media/platform/rockchip/rga/Kconfig
+++ b/drivers/media/platform/rockchip/rga/Kconfig
@@ -3,6 +3,7 @@ config VIDEO_ROCKCHIP_RGA
depends on V4L_MEM2MEM_DRIVERS
depends on VIDEO_DEV
depends on ARCH_ROCKCHIP || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
select VIDEOBUF2_DMA_SG
select V4L2_MEM2MEM_DEV
help
diff --git a/drivers/media/platform/rockchip/rga/rga-buf.c b/drivers/media/platform/rockchip/rga/rga-buf.c
index ab9554c1c4cd9..cd6904d5fe5a6 100644
--- a/drivers/media/platform/rockchip/rga/rga-buf.c
+++ b/drivers/media/platform/rockchip/rga/rga-buf.c
@@ -12,6 +12,7 @@
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mem2mem.h>
#include <media/videobuf2-dma-sg.h>
+#include <media/videobuf2-dma-contig.h>
#include <media/videobuf2-v4l2.h>
#include "rga.h"
@@ -82,6 +83,9 @@ static int rga_buf_init(struct vb2_buffer *vb)
if (IS_ERR(f))
return PTR_ERR(f);
+ if (!rga_has_internal_iommu(rga))
+ return 0;
+
n_desc = DIV_ROUND_UP(f->size, PAGE_SIZE);
rbuf->n_desc = n_desc;
@@ -136,17 +140,21 @@ static int rga_buf_prepare(struct vb2_buffer *vb)
for (i = 0; i < vb->num_planes; i++) {
vb2_set_plane_payload(vb, i, f->pix.plane_fmt[i].sizeimage);
- /* Create local MMU table for RGA */
- n_desc = fill_descriptors(&rbuf->dma_desc[curr_desc],
- rbuf->n_desc - curr_desc,
- vb2_dma_sg_plane_desc(vb, i));
- if (n_desc < 0) {
- v4l2_err(&ctx->rga->v4l2_dev,
- "Failed to map video buffer to RGA\n");
- return n_desc;
+ if (rga_has_internal_iommu(ctx->rga)) {
+ /* Create local MMU table for RGA */
+ n_desc = fill_descriptors(&rbuf->dma_desc[curr_desc],
+ rbuf->n_desc - curr_desc,
+ vb2_dma_sg_plane_desc(vb, i));
+ if (n_desc < 0) {
+ v4l2_err(&ctx->rga->v4l2_dev,
+ "Failed to map video buffer to RGA\n");
+ return n_desc;
+ }
+ dma_addrs[i] = curr_desc << PAGE_SHIFT;
+ curr_desc += n_desc;
+ } else {
+ dma_addrs[i] = vb2_dma_contig_plane_dma_addr(vb, i);
}
- dma_addrs[i] = curr_desc << PAGE_SHIFT;
- curr_desc += n_desc;
}
/* Fill the remaining planes */
@@ -176,6 +184,9 @@ static void rga_buf_cleanup(struct vb2_buffer *vb)
struct rga_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
struct rockchip_rga *rga = ctx->rga;
+ if (!rga_has_internal_iommu(rga))
+ return;
+
dma_free_coherent(rga->dev, rbuf->n_desc * sizeof(*rbuf->dma_desc),
rbuf->dma_desc, rbuf->dma_desc_pa);
}
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c
index 99cf57d5ba89d..73584706a47e2 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.c
+++ b/drivers/media/platform/rockchip/rga/rga-hw.c
@@ -577,6 +577,7 @@ static struct rga_fmt formats[] = {
const struct rga_hw rga2_hw = {
.card_type = "rga2",
+ .has_internal_iommu = true,
.formats = formats,
.num_formats = ARRAY_SIZE(formats),
.cmdbuf_size = RGA_CMDBUF_SIZE,
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index 91775b43ff617..e3c99c3f7c5be 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -23,6 +23,7 @@
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mem2mem.h>
#include <media/videobuf2-dma-sg.h>
+#include <media/videobuf2-dma-contig.h>
#include <media/videobuf2-v4l2.h>
#include "rga.h"
@@ -98,7 +99,10 @@ queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq)
src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
src_vq->drv_priv = ctx;
src_vq->ops = &rga_qops;
- src_vq->mem_ops = &vb2_dma_sg_memops;
+ if (rga_has_internal_iommu(ctx->rga))
+ src_vq->mem_ops = &vb2_dma_sg_memops;
+ else
+ src_vq->mem_ops = &vb2_dma_contig_memops;
src_vq->gfp_flags = __GFP_DMA32;
src_vq->buf_struct_size = sizeof(struct rga_vb_buffer);
src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
@@ -113,7 +117,10 @@ queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq)
dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
dst_vq->drv_priv = ctx;
dst_vq->ops = &rga_qops;
- dst_vq->mem_ops = &vb2_dma_sg_memops;
+ if (rga_has_internal_iommu(ctx->rga))
+ dst_vq->mem_ops = &vb2_dma_sg_memops;
+ else
+ dst_vq->mem_ops = &vb2_dma_contig_memops;
dst_vq->gfp_flags = __GFP_DMA32;
dst_vq->buf_struct_size = sizeof(struct rga_vb_buffer);
dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index bf21a57555a59..b180df5c48370 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -150,6 +150,7 @@ static inline void rga_mod(struct rockchip_rga *rga, u32 reg, u32 val, u32 mask)
struct rga_hw {
const char *card_type;
+ bool has_internal_iommu;
struct rga_fmt *formats;
u32 num_formats;
size_t cmdbuf_size;
@@ -165,6 +166,11 @@ struct rga_hw {
void (*get_version)(struct rockchip_rga *rga);
};
+static inline bool rga_has_internal_iommu(const struct rockchip_rga *rga)
+{
+ return rga->hw->has_internal_iommu;
+}
+
extern const struct rga_hw rga2_hw;
#endif
--
2.54.0
^ permalink raw reply related
* [PATCH v6 19/28] media: rockchip: rga: change offset to dma_addresses
From: Sven Püschel @ 2026-05-15 15:32 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, m.tretter,
p.zabel, Sven Püschel, Nicolas Dufresne
In-Reply-To: <20260515-spu-rga3-v6-0-e547152eb9c9@pengutronix.de>
Change the offset to dma_addresses, as the current naming is misleading.
The offset naming comes from the fact that it references the offset in
the mapped iommu address space. But from the hardware point of view this
is an address, as also pointed out by the register naming
(e.g. RGA_DST_Y_RGB_BASE_ADDR). Therefore also change the type to
dma_addr_t, as with an external iommu driver this would also be the
correct type.
This change is a preparation for the RGA3 support, which uses an external
iommu and therefore just gets an dma_addr_t for each buffer. The field
renaming allows to reuse the existing fields of rga_vb_buffer to store
these values.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
Changes in v6:
- Also changed dma_addrs variable type to dma_addr_t. Flagged by Sashiko
https://sashiko.dev/#/patchset/20260428-spu-rga3-v5-0-eb7f5d019d86%40pengutronix.de?part=20
---
drivers/media/platform/rockchip/rga/rga-buf.c | 12 +--
drivers/media/platform/rockchip/rga/rga-hw.c | 105 +++++++++++++-------------
drivers/media/platform/rockchip/rga/rga.h | 12 +--
3 files changed, 64 insertions(+), 65 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga-buf.c b/drivers/media/platform/rockchip/rga/rga-buf.c
index dcaba66f5c1fc..ab9554c1c4cd9 100644
--- a/drivers/media/platform/rockchip/rga/rga-buf.c
+++ b/drivers/media/platform/rockchip/rga/rga-buf.c
@@ -121,7 +121,7 @@ static int rga_buf_prepare(struct vb2_buffer *vb)
size_t curr_desc = 0;
int i;
const struct v4l2_format_info *info;
- unsigned int offsets[VIDEO_MAX_PLANES];
+ dma_addr_t dma_addrs[VIDEO_MAX_PLANES];
if (IS_ERR(f))
return PTR_ERR(f);
@@ -145,18 +145,18 @@ static int rga_buf_prepare(struct vb2_buffer *vb)
"Failed to map video buffer to RGA\n");
return n_desc;
}
- offsets[i] = curr_desc << PAGE_SHIFT;
+ dma_addrs[i] = curr_desc << PAGE_SHIFT;
curr_desc += n_desc;
}
/* Fill the remaining planes */
info = v4l2_format_info(f->fmt->fourcc);
for (i = info->mem_planes; i < info->comp_planes; i++)
- offsets[i] = get_plane_offset(f, info, i);
+ dma_addrs[i] = dma_addrs[0] + get_plane_offset(f, info, i);
- rbuf->offset.y_off = offsets[0];
- rbuf->offset.u_off = offsets[1];
- rbuf->offset.v_off = offsets[2];
+ rbuf->dma_addrs.y_addr = dma_addrs[0];
+ rbuf->dma_addrs.u_addr = dma_addrs[1];
+ rbuf->dma_addrs.v_addr = dma_addrs[2];
return 0;
}
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c
index 43fd023b7571c..99cf57d5ba89d 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.c
+++ b/drivers/media/platform/rockchip/rga/rga-hw.c
@@ -16,11 +16,11 @@ enum e_rga_start_pos {
RB = 3,
};
-struct rga_corners_addr_offset {
- struct rga_addr_offset left_top;
- struct rga_addr_offset right_top;
- struct rga_addr_offset left_bottom;
- struct rga_addr_offset right_bottom;
+struct rga_corners_addrs {
+ struct rga_addrs left_top;
+ struct rga_addrs right_top;
+ struct rga_addrs left_bottom;
+ struct rga_addrs right_bottom;
};
static unsigned int rga_get_scaling(unsigned int src, unsigned int dst)
@@ -36,20 +36,20 @@ static unsigned int rga_get_scaling(unsigned int src, unsigned int dst)
return (src > dst) ? ((dst << 16) / src) : ((src << 16) / dst);
}
-static struct rga_corners_addr_offset
-rga_get_addr_offset(struct rga_frame *frm, struct rga_addr_offset *offset,
- unsigned int x, unsigned int y, unsigned int w, unsigned int h)
+static struct rga_corners_addrs
+rga_get_corner_addrs(struct rga_frame *frm, struct rga_addrs *addrs,
+ unsigned int x, unsigned int y, unsigned int w, unsigned int h)
{
- struct rga_corners_addr_offset offsets;
- struct rga_addr_offset *lt, *lb, *rt, *rb;
+ struct rga_corners_addrs corner_addrs;
+ struct rga_addrs *lt, *lb, *rt, *rb;
const struct v4l2_format_info *format_info;
unsigned int x_div = 0,
y_div = 0, uv_stride = 0, pixel_width = 0;
- lt = &offsets.left_top;
- lb = &offsets.left_bottom;
- rt = &offsets.right_top;
- rb = &offsets.right_bottom;
+ lt = &corner_addrs.left_top;
+ lb = &corner_addrs.left_bottom;
+ rt = &corner_addrs.right_top;
+ rb = &corner_addrs.right_bottom;
format_info = v4l2_format_info(frm->pix.pixelformat);
/* x_div is only used for the u/v planes.
@@ -64,29 +64,28 @@ rga_get_addr_offset(struct rga_frame *frm, struct rga_addr_offset *offset,
uv_stride = frm->stride / x_div;
pixel_width = frm->stride / frm->pix.width;
- lt->y_off = offset->y_off + y * frm->stride + x * pixel_width;
- lt->u_off = offset->u_off + (y / y_div) * uv_stride + x / x_div;
- lt->v_off = offset->v_off + (y / y_div) * uv_stride + x / x_div;
+ lt->y_addr = addrs->y_addr + y * frm->stride + x * pixel_width;
+ lt->u_addr = addrs->u_addr + (y / y_div) * uv_stride + x / x_div;
+ lt->v_addr = addrs->v_addr + (y / y_div) * uv_stride + x / x_div;
- lb->y_off = lt->y_off + (h - 1) * frm->stride;
- lb->u_off = lt->u_off + (h / y_div - 1) * uv_stride;
- lb->v_off = lt->v_off + (h / y_div - 1) * uv_stride;
+ lb->y_addr = lt->y_addr + (h - 1) * frm->stride;
+ lb->u_addr = lt->u_addr + (h / y_div - 1) * uv_stride;
+ lb->v_addr = lt->v_addr + (h / y_div - 1) * uv_stride;
- rt->y_off = lt->y_off + (w - 1) * pixel_width;
- rt->u_off = lt->u_off + w / x_div - 1;
- rt->v_off = lt->v_off + w / x_div - 1;
+ rt->y_addr = lt->y_addr + (w - 1) * pixel_width;
+ rt->u_addr = lt->u_addr + w / x_div - 1;
+ rt->v_addr = lt->v_addr + w / x_div - 1;
- rb->y_off = lb->y_off + (w - 1) * pixel_width;
- rb->u_off = lb->u_off + w / x_div - 1;
- rb->v_off = lb->v_off + w / x_div - 1;
+ rb->y_addr = lb->y_addr + (w - 1) * pixel_width;
+ rb->u_addr = lb->u_addr + w / x_div - 1;
+ rb->v_addr = lb->v_addr + w / x_div - 1;
- return offsets;
+ return corner_addrs;
}
-static struct rga_addr_offset *rga_lookup_draw_pos(struct
- rga_corners_addr_offset
- * offsets, u32 rotate_mode,
- u32 mirr_mode)
+static struct rga_addrs *rga_lookup_draw_pos(struct rga_corners_addrs *corner_addrs,
+ u32 rotate_mode,
+ u32 mirr_mode)
{
static enum e_rga_start_pos rot_mir_point_matrix[4][4] = {
{
@@ -103,18 +102,18 @@ static struct rga_addr_offset *rga_lookup_draw_pos(struct
},
};
- if (!offsets)
+ if (!corner_addrs)
return NULL;
switch (rot_mir_point_matrix[rotate_mode][mirr_mode]) {
case LT:
- return &offsets->left_top;
+ return &corner_addrs->left_top;
case LB:
- return &offsets->left_bottom;
+ return &corner_addrs->left_bottom;
case RT:
- return &offsets->right_top;
+ return &corner_addrs->right_top;
case RB:
- return &offsets->right_bottom;
+ return &corner_addrs->right_bottom;
}
return NULL;
@@ -316,9 +315,9 @@ static void rga_cmd_set_trans_info(struct rga_ctx *ctx)
}
static void rga_cmd_set_src_info(struct rga_ctx *ctx,
- struct rga_addr_offset *offset)
+ struct rga_addrs *addrs)
{
- struct rga_corners_addr_offset src_offsets;
+ struct rga_corners_addrs src_corner_addrs;
u32 *dest = ctx->cmdbuf_virt;
unsigned int src_h, src_w, src_x, src_y;
@@ -330,22 +329,22 @@ static void rga_cmd_set_src_info(struct rga_ctx *ctx,
/*
* Calculate the source framebuffer base address with offset pixel.
*/
- src_offsets = rga_get_addr_offset(&ctx->in, offset,
- src_x, src_y, src_w, src_h);
+ src_corner_addrs = rga_get_corner_addrs(&ctx->in, addrs,
+ src_x, src_y, src_w, src_h);
dest[(RGA_SRC_Y_RGB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
- src_offsets.left_top.y_off;
+ src_corner_addrs.left_top.y_addr;
dest[(RGA_SRC_CB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
- src_offsets.left_top.u_off;
+ src_corner_addrs.left_top.u_addr;
dest[(RGA_SRC_CR_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
- src_offsets.left_top.v_off;
+ src_corner_addrs.left_top.v_addr;
}
static void rga_cmd_set_dst_info(struct rga_ctx *ctx,
- struct rga_addr_offset *offset)
+ struct rga_addrs *addrs)
{
- struct rga_addr_offset *dst_offset;
- struct rga_corners_addr_offset offsets;
+ struct rga_addrs *dst_addrs;
+ struct rga_corners_addrs corner_addrs;
u32 *dest = ctx->cmdbuf_virt;
unsigned int dst_h, dst_w, dst_x, dst_y;
unsigned int mir_mode = 0;
@@ -379,15 +378,15 @@ static void rga_cmd_set_dst_info(struct rga_ctx *ctx,
/*
* Configure the dest framebuffer base address with pixel offset.
*/
- offsets = rga_get_addr_offset(&ctx->out, offset, dst_x, dst_y, dst_w, dst_h);
- dst_offset = rga_lookup_draw_pos(&offsets, rot_mode, mir_mode);
+ corner_addrs = rga_get_corner_addrs(&ctx->out, addrs, dst_x, dst_y, dst_w, dst_h);
+ dst_addrs = rga_lookup_draw_pos(&corner_addrs, rot_mode, mir_mode);
dest[(RGA_DST_Y_RGB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
- dst_offset->y_off;
+ dst_addrs->y_addr;
dest[(RGA_DST_CB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
- dst_offset->u_off;
+ dst_addrs->u_addr;
dest[(RGA_DST_CR_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
- dst_offset->v_off;
+ dst_addrs->v_addr;
}
static void rga_cmd_set_mode(struct rga_ctx *ctx)
@@ -426,8 +425,8 @@ static void rga_cmd_set(struct rga_ctx *ctx,
rga_cmd_set_dst_addr(ctx, dst->dma_desc_pa);
- rga_cmd_set_src_info(ctx, &src->offset);
- rga_cmd_set_dst_info(ctx, &dst->offset);
+ rga_cmd_set_src_info(ctx, &src->dma_addrs);
+ rga_cmd_set_dst_info(ctx, &dst->dma_addrs);
rga_write(rga, RGA_CMD_BASE, ctx->cmdbuf_phy);
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index cee2e75ea89f1..bf21a57555a59 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -97,10 +97,10 @@ struct rockchip_rga {
const struct rga_hw *hw;
};
-struct rga_addr_offset {
- unsigned int y_off;
- unsigned int u_off;
- unsigned int v_off;
+struct rga_addrs {
+ dma_addr_t y_addr;
+ dma_addr_t u_addr;
+ dma_addr_t v_addr;
};
struct rga_vb_buffer {
@@ -112,8 +112,8 @@ struct rga_vb_buffer {
dma_addr_t dma_desc_pa;
size_t n_desc;
- /* Plane offsets of this buffer into the mapping */
- struct rga_addr_offset offset;
+ /* Plane DMA addresses after the MMU mapping of the buffer */
+ struct rga_addrs dma_addrs;
};
static inline struct rga_vb_buffer *vb_to_rga(struct vb2_v4l2_buffer *vb)
--
2.54.0
^ permalink raw reply related
* [PATCH v6 10/28] media: rockchip: rga: announce and sync colorimetry
From: Sven Püschel @ 2026-05-15 15:32 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, m.tretter,
p.zabel, Sven Püschel, Nicolas Dufresne
In-Reply-To: <20260515-spu-rga3-v6-0-e547152eb9c9@pengutronix.de>
Announce the capability to adjust the quantization and ycbcr_enc on the
capture side and check if the SET_CSC flag is set when the colorimetry
is changed. Furthermore copy the colorimetry from the output to the
capture side to fix the currently failing v4l2-compliance tests, which
expect exactly this behavior.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga.c | 37 +++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index ca8d8a53dc251..8c34f73d69764 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -437,6 +437,15 @@ static int vidioc_enum_fmt(struct file *file, void *priv, struct v4l2_fmtdesc *f
fmt = &formats[f->index];
f->pixelformat = fmt->fourcc;
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+ f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return 0;
+
+ /* allow changing the quantization and xfer func for YUV formats */
+ if (v4l2_is_format_yuv(v4l2_format_info(f->pixelformat)))
+ f->flags |= V4L2_FMT_FLAG_CSC_QUANTIZATION |
+ V4L2_FMT_FLAG_CSC_YCBCR_ENC;
+
return 0;
}
@@ -459,8 +468,25 @@ static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f)
{
struct v4l2_pix_format_mplane *pix_fmt = &f->fmt.pix_mp;
+ struct rga_ctx *ctx = file_to_rga_ctx(file);
struct rga_fmt *fmt;
+ if (V4L2_TYPE_IS_CAPTURE(f->type)) {
+ const struct rga_frame *frm;
+
+ frm = rga_get_frame(ctx, f->type);
+ if (IS_ERR(frm))
+ return PTR_ERR(frm);
+
+ if (!(pix_fmt->flags & V4L2_PIX_FMT_FLAG_SET_CSC)) {
+ pix_fmt->quantization = frm->pix.quantization;
+ pix_fmt->ycbcr_enc = frm->pix.ycbcr_enc;
+ }
+ /* disallow values not announced in vidioc_enum_fmt */
+ pix_fmt->colorspace = frm->pix.colorspace;
+ pix_fmt->xfer_func = frm->pix.xfer_func;
+ }
+
fmt = rga_fmt_find(pix_fmt->pixelformat);
if (!fmt)
fmt = &formats[0];
@@ -506,6 +532,17 @@ static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
frm->fmt = rga_fmt_find(pix_fmt->pixelformat);
frm->stride = pix_fmt->plane_fmt[0].bytesperline;
+ /*
+ * Copy colorimetry from output to capture as required by the
+ * v4l2-compliance tests
+ */
+ if (V4L2_TYPE_IS_OUTPUT(f->type)) {
+ ctx->out.pix.colorspace = pix_fmt->colorspace;
+ ctx->out.pix.ycbcr_enc = pix_fmt->ycbcr_enc;
+ ctx->out.pix.quantization = pix_fmt->quantization;
+ ctx->out.pix.xfer_func = pix_fmt->xfer_func;
+ }
+
/* Reset crop settings */
frm->crop.left = 0;
frm->crop.top = 0;
--
2.54.0
^ permalink raw reply related
* [PATCH v6 00/28] media: platform: rga: Add RGA3 support
From: Sven Püschel @ 2026-05-15 15:32 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, m.tretter,
p.zabel, Sven Püschel, Nicolas Dufresne, Michael Olbrich
This series adds support for the Raster Graphic Acceleration 3 (RGA3)
peripheral, which is included in the RK3588 SoC. The RK3588
contains one RGA2-Enhanced core (which is already implemented by the
rockchip rga driver) and two independent RGA3 cores. They feature
a similar functionality of scaling, cropping and rotating of up to two input
images into one output image. Key differences of the RGA3 are:
- supports 10bit YUV output formats
- supports 8x8 tiles and FBCD as inputs and outputs
- supports BT2020 color space conversion
- max output resolution of (8192-64)x(8192-64)
- MMU can map up to 32G DDR RAM
- fully planar formats (3 planes) are not supported
- max scale up/down factor of 8 (RGA2 allows up to 16)
This patch set adds support for one RGA3 core in the existing
rga m2m driver. The feature set of the PR is limited to scaling,
format and color space conversions between common 8bit RGB/YUV formats.
This already allows a practical usage of the RGA3.
During testing it has been noted that the scaling of the hardware is
slightly incorrect. A test conversion of 128x128 RGBA to 256x256 RGBA
causes a slight shift to the bottom right. The shift is suddle, as it seems
that the image is shifted by about 2px down and right and then cropped to
it's final size (probably caused by the source sampling).
The same behavior has been observed when using the vendor driver
with the librga library.
Furthermore comparing the RGA3 conversion with the GStreamer
videoconvertscale element, the chroma-site is different. A quick testing
didn't reveal a chroma-site that creates the same image with the
GStreamer Element. Also when converting from YUV to RGB the RGB values
differ by 1 or 2. This doesn't seem to be a colorspace conversion issue
but rather a slightly different precision on the calculation.
This was tested on a Radxa Rock 5T. Around 80 fps were measured when
scaling and converting from RGBA 480x360 to NV12 3840x2160 in a single
gstreamer pipeline. Format conversions were tested with a single
gstreamer pipeline converting a fixed input to a given input format.
Afterwards it's piped through the RGA3 and the result is converted back
to rgba and compared against a given hash value (generated after
comparing the output manually to the input).
The patchset also fixes the failing v4l2-compliance tests due to the
missing colorimetry propagation from output to capture:
v4l2-compliance 1.32.0, 64 bits, 64-bit time_t
...
Card type : rga2
...
Total for rockchip-rga device /dev/video0: 48, Succeeded: 48, Failed: 0, Warnings: 0
v4l2-compliance 1.32.0, 64 bits, 64-bit time_t
...
Card type : rga3
...
Total for rockchip-rga device /dev/video1: 48, Succeeded: 48, Failed: 0, Warnings: 0
To distinguish the RGA2 core from the RGA3 cores the Card type is set
accordingly. Scheduling operations between both RGA3 cores to double
the possible frame rate might be a future improvement. Until then
additional RGA3 cores are disabled to only provide one video device to
the user space. This prevents a potential ABI breakage when multi core
support is implemented.
The DTS change at the end is just as a preview, as this series targets
media/next. After it's merged the DTS change will be sent as a new
patch not targeting media.
Patch 1 updates the dtb bindings doc to support the RGA3
Patch 2-5 extend v4l2 common functionality
Patch 6-10 are general cleanups
Patch 11-26 prepare the rga driver for the RGA3
Patch 27 adds RGA3 support to the rga driver
Patch 28 dtsi additions for the RGA3
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
Changes in v6:
- avoid build failure by selecting VIDEOBUF2_DMA_CONTIG
- sorted nodes in dtsi addition
- reworked cmdbuf preparation/reuse to a dirty flag instead of streamon
- fix alignment multiplication edge cases in v4l2_format_plane_stride
- fix unintended parameter change when switching to v4l2_fill_pixfmt_mp_aligned call
- adjust dma_addrs variable type to match the changed struct type change
- dropped scaling limit from s_fmt
- scaling limit considers rotation
- scaling limit is checked when updating selection/rotation while streaming
- don't prevent setting a selection for the RGA3
- fixed flipped inline if branches in rga3_adjust_and_map_format
- Link to v5: https://patch.msgid.link/20260428-spu-rga3-v5-0-eb7f5d019d86@pengutronix.de
Changes in v5:
- Fixed cmdbuf allocation size being only a quarter
- Fixed streamon cmdbuf preparation to not set
rotation/flipping which prevents changing it during streaming
- Link to v4: https://patch.msgid.link/20260325-spu-rga3-v4-0-e90ec1c61354@pengutronix.de
Changes in v4:
- Add Nicolas to Cc for potential reviews and Sebastian for the nice
RK3588 mainline status table
- Improved single memory plane y stride alignment adjustments
- Adjusted scaling inaccuracy description
- Dropped required iommu property from the binding yaml
- Fixed binding yaml indentation
- Link to v3: https://lore.kernel.org/r/20260127-spu-rga3-v3-0-77b273067beb@pengutronix.de
Changes in v3:
- Add iommus property to the dtb bindings documentation
- Drop interrupt name from the dtsi
- Added v4l2_format_info for missing 2 byte RGB formats
- Fixed incorrect dt node reference in the binding patch commit message
- Removed now unused depth member of rga_frame
- Replaced RGA3 semi planar bool with v4l2_format_info check
- Calculated x_div/y_div variables instead of storing them
- Limited width/height to even values for YUV formats
- Support all 4 CSC modes: BT601L, BT601F, BT709L, BT2020L
- Note slightly incorrect scaling by the hardware
- Fix stride alignment to bytes
- Use early returns in rga-buf init/cleanup
- Fix incorrect devm_clk_bulk_get with devm_clk_bulk_get_all
- Don't enforce max scaling factor in try_fmt (only in s_fmt)
- Merge single register editing RGA3 functions into the other functions
- Link to v2: https://lore.kernel.org/r/20251203-spu-rga3-v2-0-989a67947f71@pengutronix.de
Changes in v2:
- Removed overclocking (assigning higher clock speeds in the dts)
- Disable the second RGA3 core
- Improved RGA3 feature documentation and code comments
- Don't write the whole command buffer in each frame
- Don't announce CIDs for the RGA3 and error out on s_selection
- Check the max scaling factor of 16 (RGA2) and 8 (RGA3)
- Move stride alignment and alpha checking to v4l2 common
- Register the interrupt as shared for an external IOMMU
- Add IOMMU patch as dependency to fix sporadic hangups
- Link to v1: https://lore.kernel.org/r/20251007-spu-rga3-v1-0-36ad85570402@pengutronix.de
To: Jacob Chen <jacob-chen@iotwrt.com>
To: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
To: Mauro Carvalho Chehab <mchehab@kernel.org>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Heiko Stuebner <heiko@sntech.de>
To: Hans Verkuil <hverkuil@kernel.org>
Cc: linux-media@vger.kernel.org
Cc: linux-rockchip@lists.infradead.org
Cc: devicetree@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
---
Michael Olbrich (1):
media: rockchip: rga: share the interrupt when an external iommu is used
Sven Püschel (27):
media: dt-bindings: media: rockchip-rga: add rockchip,rk3588-rga3
media: v4l2-common: sort RGB formats in v4l2_format_info
media: v4l2-common: add missing 1 and 2 byte RGB formats to v4l2_format_info
media: v4l2-common: add has_alpha to v4l2_format_info
media: v4l2-common: add v4l2_fill_pixfmt_mp_aligned helper
media: rockchip: rga: fix too small buffer size
media: rockchip: rga: use clk_bulk api
media: rockchip: rga: use stride for offset calculation
media: rockchip: rga: remove redundant rga_frame variables
media: rockchip: rga: announce and sync colorimetry
media: rockchip: rga: move hw specific parts to a dedicated struct
media: rockchip: rga: avoid odd frame sizes for YUV formats
media: rockchip: rga: calculate x_div/y_div using v4l2_format_info
media: rockchip: rga: move cmdbuf to rga_ctx
media: rockchip: rga: align stride to 4 bytes
media: rockchip: rga: reuse cmdbuf contents
media: rockchip: rga: check scaling factor
media: rockchip: rga: use card type to specify rga type
media: rockchip: rga: change offset to dma_addresses
media: rockchip: rga: support external iommus
media: rockchip: rga: remove size from rga_frame
media: rockchip: rga: remove stride from rga_frame
media: rockchip: rga: move rga_fmt to rga-hw.h
media: rockchip: rga: add feature flags
media: rockchip: rga: disable multi-core support
media: rockchip: rga: add rga3 support
arm64: dts: rockchip: add rga3 dt nodes
.../devicetree/bindings/media/rockchip-rga.yaml | 10 +-
arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 44 ++
drivers/media/platform/rockchip/rga/Kconfig | 1 +
drivers/media/platform/rockchip/rga/Makefile | 2 +-
drivers/media/platform/rockchip/rga/rga-buf.c | 89 +++-
drivers/media/platform/rockchip/rga/rga-hw.c | 357 +++++++++----
drivers/media/platform/rockchip/rga/rga-hw.h | 16 +-
drivers/media/platform/rockchip/rga/rga.c | 576 ++++++++++-----------
drivers/media/platform/rockchip/rga/rga.h | 88 ++--
drivers/media/platform/rockchip/rga/rga3-hw.c | 507 ++++++++++++++++++
drivers/media/platform/rockchip/rga/rga3-hw.h | 192 +++++++
drivers/media/v4l2-core/v4l2-common.c | 128 +++--
include/media/v4l2-common.h | 6 +
13 files changed, 1513 insertions(+), 503 deletions(-)
---
base-commit: bc1ba628e37c93cf2abeb2c79716f49087f8a024
change-id: 20251001-spu-rga3-8a00e018b120
prerequisite-change-id: 20251126-spu-iommudtefix-cd0c5244c74a:v2
prerequisite-patch-id: 10c6c977c0f71400931941b42da73adcaf63e810
Best regards,
--
Sven Püschel <s.pueschel@pengutronix.de>
^ permalink raw reply
* [PATCH v6 07/28] media: rockchip: rga: use clk_bulk api
From: Sven Püschel @ 2026-05-15 15:32 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, m.tretter,
p.zabel, Sven Püschel, Nicolas Dufresne
In-Reply-To: <20260515-spu-rga3-v6-0-e547152eb9c9@pengutronix.de>
Use the clk_bulk API to avoid code duplication for each of the three
clocks.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga.c | 65 ++++---------------------------
drivers/media/platform/rockchip/rga/rga.h | 6 +--
2 files changed, 11 insertions(+), 60 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index fea63b94c5f3d..4e710a050cb7c 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -698,48 +698,10 @@ static const struct video_device rga_videodev = {
.device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING,
};
-static int rga_enable_clocks(struct rockchip_rga *rga)
-{
- int ret;
-
- ret = clk_prepare_enable(rga->sclk);
- if (ret) {
- dev_err(rga->dev, "Cannot enable rga sclk: %d\n", ret);
- return ret;
- }
-
- ret = clk_prepare_enable(rga->aclk);
- if (ret) {
- dev_err(rga->dev, "Cannot enable rga aclk: %d\n", ret);
- goto err_disable_sclk;
- }
-
- ret = clk_prepare_enable(rga->hclk);
- if (ret) {
- dev_err(rga->dev, "Cannot enable rga hclk: %d\n", ret);
- goto err_disable_aclk;
- }
-
- return 0;
-
-err_disable_aclk:
- clk_disable_unprepare(rga->aclk);
-err_disable_sclk:
- clk_disable_unprepare(rga->sclk);
-
- return ret;
-}
-
-static void rga_disable_clocks(struct rockchip_rga *rga)
-{
- clk_disable_unprepare(rga->sclk);
- clk_disable_unprepare(rga->hclk);
- clk_disable_unprepare(rga->aclk);
-}
-
static int rga_parse_dt(struct rockchip_rga *rga)
{
struct reset_control *core_rst, *axi_rst, *ahb_rst;
+ int ret;
core_rst = devm_reset_control_get(rga->dev, "core");
if (IS_ERR(core_rst)) {
@@ -771,23 +733,12 @@ static int rga_parse_dt(struct rockchip_rga *rga)
udelay(1);
reset_control_deassert(ahb_rst);
- rga->sclk = devm_clk_get(rga->dev, "sclk");
- if (IS_ERR(rga->sclk)) {
- dev_err(rga->dev, "failed to get sclk clock\n");
- return PTR_ERR(rga->sclk);
- }
-
- rga->aclk = devm_clk_get(rga->dev, "aclk");
- if (IS_ERR(rga->aclk)) {
- dev_err(rga->dev, "failed to get aclk clock\n");
- return PTR_ERR(rga->aclk);
- }
-
- rga->hclk = devm_clk_get(rga->dev, "hclk");
- if (IS_ERR(rga->hclk)) {
- dev_err(rga->dev, "failed to get hclk clock\n");
- return PTR_ERR(rga->hclk);
+ ret = devm_clk_bulk_get_all(rga->dev, &rga->clks);
+ if (ret < 0) {
+ dev_err(rga->dev, "failed to get clocks\n");
+ return ret;
}
+ rga->num_clks = ret;
return 0;
}
@@ -935,7 +886,7 @@ static int __maybe_unused rga_runtime_suspend(struct device *dev)
{
struct rockchip_rga *rga = dev_get_drvdata(dev);
- rga_disable_clocks(rga);
+ clk_bulk_disable_unprepare(rga->num_clks, rga->clks);
return 0;
}
@@ -944,7 +895,7 @@ static int __maybe_unused rga_runtime_resume(struct device *dev)
{
struct rockchip_rga *rga = dev_get_drvdata(dev);
- return rga_enable_clocks(rga);
+ return clk_bulk_prepare_enable(rga->num_clks, rga->clks);
}
static const struct dev_pm_ops rga_pm = {
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index 72a28b120fabf..2db10acecb405 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -6,6 +6,7 @@
#ifndef __RGA_H__
#define __RGA_H__
+#include <linux/clk.h>
#include <linux/platform_device.h>
#include <media/videobuf2-v4l2.h>
#include <media/v4l2-ctrls.h>
@@ -81,9 +82,8 @@ struct rockchip_rga {
struct device *dev;
struct regmap *grf;
void __iomem *regs;
- struct clk *sclk;
- struct clk *aclk;
- struct clk *hclk;
+ struct clk_bulk_data *clks;
+ int num_clks;
struct rockchip_rga_version version;
/* vfd lock */
--
2.54.0
^ permalink raw reply related
* [PATCH v6 09/28] media: rockchip: rga: remove redundant rga_frame variables
From: Sven Püschel @ 2026-05-15 15:32 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, m.tretter,
p.zabel, Sven Püschel, Nicolas Dufresne
In-Reply-To: <20260515-spu-rga3-v6-0-e547152eb9c9@pengutronix.de>
Remove the redundant rga_frame variables width, height and color space.
The value of these variables is already contained in the pix member
of rga_frame. The code also keeps these values in sync. Therefore drop
them in favor of the existing pix member.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga-buf.c | 6 ++---
drivers/media/platform/rockchip/rga/rga-hw.c | 6 ++---
drivers/media/platform/rockchip/rga/rga.c | 32 ++++++++++-----------------
drivers/media/platform/rockchip/rga/rga.h | 5 -----
4 files changed, 18 insertions(+), 31 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga-buf.c b/drivers/media/platform/rockchip/rga/rga-buf.c
index 65fc0d5b4aa10..ffc6162b2e681 100644
--- a/drivers/media/platform/rockchip/rga/rga-buf.c
+++ b/drivers/media/platform/rockchip/rga/rga-buf.c
@@ -103,10 +103,10 @@ static int get_plane_offset(struct rga_frame *f,
if (plane == 0)
return 0;
if (plane == 1)
- return stride * f->height;
+ return stride * f->pix.height;
if (plane == 2)
- return stride * f->height +
- (stride * f->height / info->hdiv / info->vdiv);
+ return stride * f->pix.height +
+ (stride * f->pix.height / info->hdiv / info->vdiv);
return -EINVAL;
}
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c
index d1618bb247501..ec6c17504ca15 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.c
+++ b/drivers/media/platform/rockchip/rga/rga-hw.c
@@ -53,7 +53,7 @@ rga_get_addr_offset(struct rga_frame *frm, struct rga_addr_offset *offset,
x_div = frm->fmt->x_div;
y_div = frm->fmt->y_div;
uv_stride = frm->stride / x_div;
- pixel_width = frm->stride / frm->width;
+ pixel_width = frm->stride / frm->pix.width;
lt->y_off = offset->y_off + y * frm->stride + x * pixel_width;
lt->u_off = offset->u_off + (y / y_div) * uv_stride + x / x_div;
@@ -191,7 +191,7 @@ static void rga_cmd_set_trans_info(struct rga_ctx *ctx)
if (RGA_COLOR_FMT_IS_YUV(ctx->in.fmt->hw_format) &&
RGA_COLOR_FMT_IS_RGB(ctx->out.fmt->hw_format)) {
- switch (ctx->in.colorspace) {
+ switch (ctx->in.pix.colorspace) {
case V4L2_COLORSPACE_REC709:
src_info.data.csc_mode = RGA_SRC_CSC_MODE_BT709_R0;
break;
@@ -203,7 +203,7 @@ static void rga_cmd_set_trans_info(struct rga_ctx *ctx)
if (RGA_COLOR_FMT_IS_RGB(ctx->in.fmt->hw_format) &&
RGA_COLOR_FMT_IS_YUV(ctx->out.fmt->hw_format)) {
- switch (ctx->out.colorspace) {
+ switch (ctx->out.pix.colorspace) {
case V4L2_COLORSPACE_REC709:
dst_info.data.csc_mode = RGA_SRC_CSC_MODE_BT709_R0;
break;
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index c07207edffdb6..ca8d8a53dc251 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -329,9 +329,6 @@ static struct rga_fmt *rga_fmt_find(u32 pixelformat)
}
static struct rga_frame def_frame = {
- .width = DEFAULT_WIDTH,
- .height = DEFAULT_HEIGHT,
- .colorspace = V4L2_COLORSPACE_DEFAULT,
.crop.left = 0,
.crop.top = 0,
.crop.width = DEFAULT_WIDTH,
@@ -363,9 +360,9 @@ static int rga_open(struct file *file)
ctx->out = def_frame;
v4l2_fill_pixfmt_mp(&ctx->in.pix,
- ctx->in.fmt->fourcc, ctx->out.width, ctx->out.height);
+ ctx->in.fmt->fourcc, DEFAULT_WIDTH, DEFAULT_HEIGHT);
v4l2_fill_pixfmt_mp(&ctx->out.pix,
- ctx->out.fmt->fourcc, ctx->out.width, ctx->out.height);
+ ctx->out.fmt->fourcc, DEFAULT_WIDTH, DEFAULT_HEIGHT);
if (mutex_lock_interruptible(&rga->mutex)) {
kfree(ctx);
@@ -453,10 +450,8 @@ static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
if (IS_ERR(frm))
return PTR_ERR(frm);
- v4l2_fill_pixfmt_mp(pix_fmt, frm->fmt->fourcc, frm->width, frm->height);
-
+ *pix_fmt = frm->pix;
pix_fmt->field = V4L2_FIELD_NONE;
- pix_fmt->colorspace = frm->colorspace;
return 0;
}
@@ -505,27 +500,24 @@ static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
frm = rga_get_frame(ctx, f->type);
if (IS_ERR(frm))
return PTR_ERR(frm);
- frm->width = pix_fmt->width;
- frm->height = pix_fmt->height;
frm->size = 0;
for (i = 0; i < pix_fmt->num_planes; i++)
frm->size += pix_fmt->plane_fmt[i].sizeimage;
frm->fmt = rga_fmt_find(pix_fmt->pixelformat);
frm->stride = pix_fmt->plane_fmt[0].bytesperline;
- frm->colorspace = pix_fmt->colorspace;
/* Reset crop settings */
frm->crop.left = 0;
frm->crop.top = 0;
- frm->crop.width = frm->width;
- frm->crop.height = frm->height;
+ frm->crop.width = pix_fmt->width;
+ frm->crop.height = pix_fmt->height;
frm->pix = *pix_fmt;
v4l2_dbg(debug, 1, &rga->v4l2_dev,
"[%s] fmt - %p4cc %dx%d (stride %d, sizeimage %d)\n",
V4L2_TYPE_IS_OUTPUT(f->type) ? "OUTPUT" : "CAPTURE",
- &frm->fmt->fourcc, frm->width, frm->height,
+ &frm->fmt->fourcc, pix_fmt->width, pix_fmt->height,
frm->stride, frm->size);
for (i = 0; i < pix_fmt->num_planes; i++) {
@@ -579,8 +571,8 @@ static int vidioc_g_selection(struct file *file, void *priv,
} else {
s->r.left = 0;
s->r.top = 0;
- s->r.width = f->width;
- s->r.height = f->height;
+ s->r.width = f->pix.width;
+ s->r.height = f->pix.height;
}
return 0;
@@ -629,8 +621,8 @@ static int vidioc_s_selection(struct file *file, void *priv,
return -EINVAL;
}
- if (s->r.left + s->r.width > f->width ||
- s->r.top + s->r.height > f->height ||
+ if (s->r.left + s->r.width > f->pix.width ||
+ s->r.top + s->r.height > f->pix.height ||
s->r.width < MIN_WIDTH || s->r.height < MIN_HEIGHT) {
v4l2_dbg(debug, 1, &rga->v4l2_dev, "unsupported crop value.\n");
return -EINVAL;
@@ -821,8 +813,8 @@ static int rga_probe(struct platform_device *pdev)
goto rel_m2m;
}
- def_frame.stride = (def_frame.width * def_frame.fmt->depth) >> 3;
- def_frame.size = def_frame.stride * def_frame.height;
+ def_frame.stride = (DEFAULT_WIDTH * def_frame.fmt->depth) >> 3;
+ def_frame.size = def_frame.stride * DEFAULT_HEIGHT;
ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1);
if (ret) {
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index 477cf5b62bbb2..c4a3905a48f0d 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -24,11 +24,6 @@ struct rga_fmt {
};
struct rga_frame {
- /* Original dimensions */
- u32 width;
- u32 height;
- u32 colorspace;
-
/* Crop */
struct v4l2_rect crop;
--
2.54.0
^ permalink raw reply related
* [PATCH v6 08/28] media: rockchip: rga: use stride for offset calculation
From: Sven Püschel @ 2026-05-15 15:32 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, m.tretter,
p.zabel, Sven Püschel, Nicolas Dufresne
In-Reply-To: <20260515-spu-rga3-v6-0-e547152eb9c9@pengutronix.de>
Use the stride instead of the width for the offset calculation. This
ensures that the bytesperline value doesn't need to match the width
value of the image.
Furthermore this patch removes the dependency on the uv_factor property
and instead reuses the v4l2_format_info to determine the correct
division factor.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
drivers/media/platform/rockchip/rga/rga-buf.c | 14 +++++++++-----
drivers/media/platform/rockchip/rga/rga.c | 16 ----------------
drivers/media/platform/rockchip/rga/rga.h | 1 -
3 files changed, 9 insertions(+), 22 deletions(-)
diff --git a/drivers/media/platform/rockchip/rga/rga-buf.c b/drivers/media/platform/rockchip/rga/rga-buf.c
index bb575873f2b24..65fc0d5b4aa10 100644
--- a/drivers/media/platform/rockchip/rga/rga-buf.c
+++ b/drivers/media/platform/rockchip/rga/rga-buf.c
@@ -14,7 +14,6 @@
#include <media/videobuf2-dma-sg.h>
#include <media/videobuf2-v4l2.h>
-#include "rga-hw.h"
#include "rga.h"
static ssize_t fill_descriptors(struct rga_dma_desc *desc, size_t max_desc,
@@ -95,14 +94,19 @@ static int rga_buf_init(struct vb2_buffer *vb)
return 0;
}
-static int get_plane_offset(struct rga_frame *f, int plane)
+static int get_plane_offset(struct rga_frame *f,
+ const struct v4l2_format_info *info,
+ int plane)
{
+ u32 stride = f->pix.plane_fmt[0].bytesperline;
+
if (plane == 0)
return 0;
if (plane == 1)
- return f->width * f->height;
+ return stride * f->height;
if (plane == 2)
- return f->width * f->height + (f->width * f->height / f->fmt->uv_factor);
+ return stride * f->height +
+ (stride * f->height / info->hdiv / info->vdiv);
return -EINVAL;
}
@@ -148,7 +152,7 @@ static int rga_buf_prepare(struct vb2_buffer *vb)
/* Fill the remaining planes */
info = v4l2_format_info(f->fmt->fourcc);
for (i = info->mem_planes; i < info->comp_planes; i++)
- offsets[i] = get_plane_offset(f, i);
+ offsets[i] = get_plane_offset(f, info, i);
rbuf->offset.y_off = offsets[0];
rbuf->offset.u_off = offsets[1];
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index 4e710a050cb7c..c07207edffdb6 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -190,7 +190,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_ALPHA_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR8888,
.depth = 32,
- .uv_factor = 1,
.y_div = 1,
.x_div = 1,
},
@@ -199,7 +198,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR8888,
.depth = 32,
- .uv_factor = 1,
.y_div = 1,
.x_div = 1,
},
@@ -208,7 +206,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_XBGR8888,
.depth = 32,
- .uv_factor = 1,
.y_div = 1,
.x_div = 1,
},
@@ -217,7 +214,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_RGB888,
.depth = 24,
- .uv_factor = 1,
.y_div = 1,
.x_div = 1,
},
@@ -226,7 +222,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_RGB888,
.depth = 24,
- .uv_factor = 1,
.y_div = 1,
.x_div = 1,
},
@@ -235,7 +230,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR4444,
.depth = 16,
- .uv_factor = 1,
.y_div = 1,
.x_div = 1,
},
@@ -244,7 +238,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR1555,
.depth = 16,
- .uv_factor = 1,
.y_div = 1,
.x_div = 1,
},
@@ -253,7 +246,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_BGR565,
.depth = 16,
- .uv_factor = 1,
.y_div = 1,
.x_div = 1,
},
@@ -262,7 +254,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_UV_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420SP,
.depth = 12,
- .uv_factor = 4,
.y_div = 2,
.x_div = 1,
},
@@ -271,7 +262,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_UV_SWAP,
.hw_format = RGA_COLOR_FMT_YUV422SP,
.depth = 16,
- .uv_factor = 2,
.y_div = 1,
.x_div = 1,
},
@@ -280,7 +270,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420SP,
.depth = 12,
- .uv_factor = 4,
.y_div = 2,
.x_div = 1,
},
@@ -289,7 +278,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420SP,
.depth = 12,
- .uv_factor = 4,
.y_div = 2,
.x_div = 1,
},
@@ -298,7 +286,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV422SP,
.depth = 16,
- .uv_factor = 2,
.y_div = 1,
.x_div = 1,
},
@@ -307,7 +294,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420P,
.depth = 12,
- .uv_factor = 4,
.y_div = 2,
.x_div = 2,
},
@@ -316,7 +302,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV422P,
.depth = 16,
- .uv_factor = 2,
.y_div = 1,
.x_div = 2,
},
@@ -325,7 +310,6 @@ static struct rga_fmt formats[] = {
.color_swap = RGA_COLOR_UV_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420P,
.depth = 12,
- .uv_factor = 4,
.y_div = 2,
.x_div = 2,
},
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index 2db10acecb405..477cf5b62bbb2 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -17,7 +17,6 @@
struct rga_fmt {
u32 fourcc;
int depth;
- u8 uv_factor;
u8 y_div;
u8 x_div;
u8 color_swap;
--
2.54.0
^ permalink raw reply related
* [PATCH v6 05/28] media: v4l2-common: add v4l2_fill_pixfmt_mp_aligned helper
From: Sven Püschel @ 2026-05-15 15:32 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, m.tretter,
p.zabel, Sven Püschel, Nicolas Dufresne
In-Reply-To: <20260515-spu-rga3-v6-0-e547152eb9c9@pengutronix.de>
Add a v4l2_fill_pixfmt_mp_aligned helper which allows the user to
specify a custom stride alignment in bytes. This is necessary for
hardware like the Rockchip RGA3, which requires the stride value to be
aligned to a 16 bytes boundary.
The code makes some assumptions about the v4l2 format to simplify the
calculation. They currently hold for all known v4l2 formats.
v4l2_format_plane_stride uses an unsigned int as argument type to avoid
the later multiplication from overflowing the u8 value. All other places
use u8, as no practical use cases for a larger alignment are known at
the moment.
Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
Changes in v6:
- Fixed alignment multiplication of 0 for NV24 - flagged by Sashiko:
https://sashiko.dev/#/patchset/20260428-spu-rga3-v5-0-eb7f5d019d86%40pengutronix.de?part=5
- Changed v4l2_format_plane_stride alignment parameter type to
avoid overflow for 64/128 byte alignment by multiplication.
Flagged by Sashiko URL above.
---
drivers/media/v4l2-core/v4l2-common.c | 51 ++++++++++++++++++++++++++---------
include/media/v4l2-common.h | 4 +++
2 files changed, 43 insertions(+), 12 deletions(-)
diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c
index 3cc8b04e1ea63..a03e48812bcf5 100644
--- a/drivers/media/v4l2-core/v4l2-common.c
+++ b/drivers/media/v4l2-core/v4l2-common.c
@@ -432,14 +432,28 @@ static inline unsigned int v4l2_format_block_height(const struct v4l2_format_inf
}
static inline unsigned int v4l2_format_plane_stride(const struct v4l2_format_info *info, int plane,
- unsigned int width)
+ unsigned int width, unsigned int byte_alignment)
{
unsigned int hdiv = plane ? info->hdiv : 1;
unsigned int aligned_width =
ALIGN(width, v4l2_format_block_width(info, plane));
- return DIV_ROUND_UP(aligned_width, hdiv) *
- info->bpp[plane] / info->bpp_div[plane];
+ /*
+ * Formats with a single memory plane derive the stride of the
+ * other planes from the y stride. Due to chroma subsampling this
+ * may cause the other plane strides to break the byte_alignment.
+ * Increase the alignment if necessary to prevent this.
+ *
+ * It assumes the following format properties:
+ * - bpp_div[0] == bpp_div[1]
+ * - The multiplication factor doesn't differ between the non y planes
+ */
+ if (info->mem_planes == 1 && info->comp_planes > 1 && plane == 0)
+ byte_alignment *= DIV_ROUND_UP(info->hdiv * info->bpp[0], info->bpp[1]);
+
+ return ALIGN(DIV_ROUND_UP(aligned_width, hdiv) * info->bpp[plane] /
+ info->bpp_div[plane],
+ byte_alignment);
}
static inline unsigned int v4l2_format_plane_height(const struct v4l2_format_info *info, int plane,
@@ -453,9 +467,10 @@ static inline unsigned int v4l2_format_plane_height(const struct v4l2_format_inf
}
static inline unsigned int v4l2_format_plane_size(const struct v4l2_format_info *info, int plane,
- unsigned int width, unsigned int height)
+ unsigned int width, unsigned int height,
+ u8 stride_alignment)
{
- return v4l2_format_plane_stride(info, plane, width) *
+ return v4l2_format_plane_stride(info, plane, width, stride_alignment) *
v4l2_format_plane_height(info, plane, height);
}
@@ -476,8 +491,9 @@ void v4l2_apply_frmsize_constraints(u32 *width, u32 *height,
}
EXPORT_SYMBOL_GPL(v4l2_apply_frmsize_constraints);
-int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt,
- u32 pixelformat, u32 width, u32 height)
+int v4l2_fill_pixfmt_mp_aligned(struct v4l2_pix_format_mplane *pixfmt,
+ u32 pixelformat, u32 width, u32 height,
+ u8 stride_alignment)
{
const struct v4l2_format_info *info;
struct v4l2_plane_pix_format *plane;
@@ -494,23 +510,34 @@ int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt,
if (info->mem_planes == 1) {
plane = &pixfmt->plane_fmt[0];
- plane->bytesperline = v4l2_format_plane_stride(info, 0, width);
+ plane->bytesperline = v4l2_format_plane_stride(info, 0, width,
+ stride_alignment);
plane->sizeimage = 0;
for (i = 0; i < info->comp_planes; i++)
plane->sizeimage +=
- v4l2_format_plane_size(info, i, width, height);
+ v4l2_format_plane_size(info, i, width, height,
+ stride_alignment);
} else {
for (i = 0; i < info->comp_planes; i++) {
plane = &pixfmt->plane_fmt[i];
plane->bytesperline =
- v4l2_format_plane_stride(info, i, width);
+ v4l2_format_plane_stride(info, i, width,
+ stride_alignment);
plane->sizeimage = plane->bytesperline *
v4l2_format_plane_height(info, i, height);
}
}
return 0;
}
+EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt_mp_aligned);
+
+int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt,
+ u32 pixelformat, u32 width, u32 height)
+{
+ return v4l2_fill_pixfmt_mp_aligned(pixfmt, pixelformat,
+ width, height, 1);
+}
EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt_mp);
int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat,
@@ -530,12 +557,12 @@ int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat,
pixfmt->width = width;
pixfmt->height = height;
pixfmt->pixelformat = pixelformat;
- pixfmt->bytesperline = v4l2_format_plane_stride(info, 0, width);
+ pixfmt->bytesperline = v4l2_format_plane_stride(info, 0, width, 1);
pixfmt->sizeimage = 0;
for (i = 0; i < info->comp_planes; i++)
pixfmt->sizeimage +=
- v4l2_format_plane_size(info, i, width, height);
+ v4l2_format_plane_size(info, i, width, height, 1);
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt);
diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h
index 401d8506c24b5..edd416178c333 100644
--- a/include/media/v4l2-common.h
+++ b/include/media/v4l2-common.h
@@ -558,6 +558,10 @@ int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat,
u32 width, u32 height);
int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt, u32 pixelformat,
u32 width, u32 height);
+/* @stride_alignment is a power of 2 value in bytes */
+int v4l2_fill_pixfmt_mp_aligned(struct v4l2_pix_format_mplane *pixfmt,
+ u32 pixelformat, u32 width, u32 height,
+ u8 stride_alignment);
/**
* v4l2_get_link_freq - Get link rate from transmitter
--
2.54.0
^ permalink raw reply related
* [PATCH v6 01/28] media: dt-bindings: media: rockchip-rga: add rockchip,rk3588-rga3
From: Sven Püschel @ 2026-05-15 15:32 UTC (permalink / raw)
To: Jacob Chen, Ezequiel Garcia, Mauro Carvalho Chehab,
Heiko Stuebner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans Verkuil
Cc: linux-media, linux-rockchip, linux-arm-kernel, linux-kernel,
devicetree, kernel, nicolas, sebastian.reichel, m.tretter,
p.zabel, Sven Püschel
In-Reply-To: <20260515-spu-rga3-v6-0-e547152eb9c9@pengutronix.de>
Add a new compatible for the RGA3 (Raster Graphic Acceleration 3)
peripheral found on the RK3588 SoC. Also specify an iommu property,
as the RGA3 contains the generic rockchip iommu. While other versions
also have an iommu, it's usually specific to them.
The RK3588 contains one RGA2-Enhance core (also contained on the RK3399)
and two RGA3 cores. Both feature a similar functionality of scaling,
cropping and rotating of up to two input images into one output image.
Key differences of the RGA3 are:
- supports 10bit YUV output formats
- supports 8x8 tiles and FBCD as inputs and outputs
- supports BT2020 color space conversion
- max output resolution of (8192-64)x(8192-64)
- MMU can map up to 32G DDR RAM
- fully planar formats (3 planes) are not supported
- max scale up/down factor of 8 (RGA2 allows up to 16)
Acked-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Sven Püschel <s.pueschel@pengutronix.de>
---
Changed in v3/v4:
- Dropped Acked-by: Krzysztof Kozlowski due to the added iommus property
and description adjustments.
---
Documentation/devicetree/bindings/media/rockchip-rga.yaml | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/media/rockchip-rga.yaml b/Documentation/devicetree/bindings/media/rockchip-rga.yaml
index ac17cda65191b..7bd92f7336664 100644
--- a/Documentation/devicetree/bindings/media/rockchip-rga.yaml
+++ b/Documentation/devicetree/bindings/media/rockchip-rga.yaml
@@ -9,7 +9,11 @@ title: Rockchip 2D raster graphic acceleration controller (RGA)
description:
RGA is a standalone 2D raster graphic acceleration unit. It accelerates 2D
graphics operations, such as point/line drawing, image scaling, rotation,
- BitBLT, alpha blending and image blur/sharpness.
+ BitBLT, alpha blending and image blur/sharpness. There exist many versions
+ of this unit that differ in the supported inputs/output formats,
+ the attached IOMMU and the supported operations on the input. As some SoCs
+ include multiple RGA units with different versions, a more specific
+ compatible name to differentiate the concrete unit is used for them.
maintainers:
- Jacob Chen <jacob-chen@iotwrt.com>
@@ -20,6 +24,7 @@ properties:
oneOf:
- const: rockchip,rk3288-rga
- const: rockchip,rk3399-rga
+ - const: rockchip,rk3588-rga3
- items:
- enum:
- rockchip,rk3228-rga
@@ -45,6 +50,9 @@ properties:
power-domains:
maxItems: 1
+ iommus:
+ maxItems: 1
+
resets:
maxItems: 3
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] dt-bindings: arm-smmu: qcom: Add compatible for IPQ9650 SoC
From: Kathiravan Thirumoorthy @ 2026-05-15 15:28 UTC (permalink / raw)
To: Krzysztof Kozlowski, Will Deacon, Robin Murphy, Joerg Roedel,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-kernel, iommu, devicetree, linux-kernel
In-Reply-To: <39588541-b6f8-4697-9787-4b42df21156b@kernel.org>
On 5/15/2026 8:46 PM, Krzysztof Kozlowski wrote:
> On 15/05/2026 15:30, Kathiravan Thirumoorthy wrote:
>> On 5/12/2026 12:17 PM, Kathiravan Thirumoorthy wrote:
>>> Qualcomm IPQ9650 SoC include APPS SMMU that implements arm,mmu-500.
>>> Document the compatible for the same.
>> Kindly ignore this patch. I will repost along with DTSI changes once it
>> is validated again!
> Preemptively, I dropped all your patches from Patchwork based on earlier
> comments. Now I have proof that I should be doing that for future as
> well. :/
I understand your frustration. Apologies for that. Please note that, I
have validated all the changes before posting.
In this case, when I was cross checking the HW doc for some other
details and I noticed that SMMU interrupts are changed and HW folks
haven't notified us. Hence I wanted to re-validate the change and post
it. I don't want to change the binding (if any such case arises), hence
requested to drop this patch. Hope this helps.
>
> Best regards,
> Krzysztof
^ permalink raw reply
* [PATCH] media: bcm2835-unicam: fix dummy buffer double free when register fails
From: Dawei Feng @ 2026-05-15 15:25 UTC (permalink / raw)
To: kernel-list
Cc: mchehab, linux-media, florian.fainelli, bcm-kernel-feedback-list,
rjui, sbranden, linux-rpi-kernel, linux-arm-kernel, linux-kernel,
jianhao.xu, Dawei Feng, stable, Zilin Guan
unicam_register_node() allocates node->dummy_buf_cpu_addr before
registering the video device. If video_register_device() fails, the
error path frees the dummy buffer but leaves the pointer unchanged.
unicam_async_complete() then unwinds the partial setup through
unicam_unregister_nodes(), which sees the stale non-NULL pointer and
frees the same buffer again.
Set dummy_buf_cpu_addr to NULL after dma_free_coherent() in both the
local error path and unicam_unregister_nodes() so later cleanup skips
already released buffers.
The bug was first flagged by an experimental analysis tool we are
developing for kernel memory-management bugs while analyzing
v6.13-rc1. The tool is still under development and is not yet publicly
available. Manual inspection confirms that the bug is still
present in v7.1-rc3.
Runtime validation was not attempted because reproducing this failure
path requires fault injection in a hardware-specific driver.
Fixes: 392cd78d495f ("media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface")
Cc: stable@vger.kernel.org
Signed-off-by: Zilin Guan <zilin@seu.edu.cn>
Signed-off-by: Dawei Feng <dawei.feng@seu.edu.cn>
---
drivers/media/platform/broadcom/bcm2835-unicam.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/media/platform/broadcom/bcm2835-unicam.c b/drivers/media/platform/broadcom/bcm2835-unicam.c
index 8d28ba0b59a3..94c80601c50d 100644
--- a/drivers/media/platform/broadcom/bcm2835-unicam.c
+++ b/drivers/media/platform/broadcom/bcm2835-unicam.c
@@ -2360,6 +2360,7 @@ static int unicam_register_node(struct unicam_device *unicam,
dma_free_coherent(unicam->dev, node->dummy_buf.size,
node->dummy_buf_cpu_addr,
node->dummy_buf.dma_addr);
+ node->dummy_buf_cpu_addr = NULL;
err_entity_cleanup:
media_entity_cleanup(&vdev->entity);
err_unicam_put:
@@ -2379,10 +2380,12 @@ static void unicam_unregister_nodes(struct unicam_device *unicam)
node->registered = false;
}
- if (node->dummy_buf_cpu_addr)
+ if (node->dummy_buf_cpu_addr) {
dma_free_coherent(unicam->dev, node->dummy_buf.size,
node->dummy_buf_cpu_addr,
node->dummy_buf.dma_addr);
+ node->dummy_buf_cpu_addr = NULL;
+ }
}
}
--
2.34.1
^ permalink raw reply related
* Re: [PATCH] dt-bindings: arm-smmu: qcom: Add compatible for IPQ9650 SoC
From: Krzysztof Kozlowski @ 2026-05-15 15:16 UTC (permalink / raw)
To: Kathiravan Thirumoorthy, Will Deacon, Robin Murphy, Joerg Roedel,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-kernel, iommu, devicetree, linux-kernel
In-Reply-To: <b8f02a31-b175-4dc2-9b2a-5765774b4d88@oss.qualcomm.com>
On 15/05/2026 15:30, Kathiravan Thirumoorthy wrote:
>
> On 5/12/2026 12:17 PM, Kathiravan Thirumoorthy wrote:
>> Qualcomm IPQ9650 SoC include APPS SMMU that implements arm,mmu-500.
>> Document the compatible for the same.
>
> Kindly ignore this patch. I will repost along with DTSI changes once it
> is validated again!
Preemptively, I dropped all your patches from Patchwork based on earlier
comments. Now I have proof that I should be doing that for future as
well. :/
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 4/5] x86/pci: Use official API to iterate over PCI buses
From: Dave Hansen @ 2026-05-15 15:13 UTC (permalink / raw)
To: Gerd Bayer, Richard Henderson, Matt Turner, Magnus Lindholm,
Russell King, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Bjorn Helgaas,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin
Cc: Yinghai Lu, linux-alpha, linux-kernel, linux-arm-kernel,
linuxppc-dev, linux-pci
In-Reply-To: <20260515-priv_root_buses-v1-4-f8e393c57390@linux.ibm.com>
On 5/15/26 07:22, Gerd Bayer wrote:
> static int __init pcibios_assign_resources(void)
> {
> - struct pci_bus *bus;
> + struct pci_bus *bus = NULL;
>
> if (!(pci_probe & PCI_ASSIGN_ROMS))
> - list_for_each_entry(bus, &pci_root_buses, node)
> + while ((bus = pci_find_next_bus(bus)) != NULL)
> pcibios_allocate_rom_resources(bus);
What's with the 'bus = NULL'? I thought there was some crazy macro magic
going on or something, but pci_find_next_bus() looks like a normal
function that's just taking a pointer and not _modifying_ the pointer value.
Also, wouldn't this be a more readable way of writing what you have?
while (bus = pci_find_next_bus(bus))
For that matter isn't the kernel idiom for these things:
for_each_pci_bus(bus) {
// do bus stuff
}
I'm kinda surprised there isn't one of those already.
^ permalink raw reply
* [PATCH 3/4] ASoC: meson: aiu: introduce I2S output formatter
From: Valerio Setti @ 2026-05-15 15:10 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
Valerio Setti
In-Reply-To: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com>
Introduce aiu-formatter-i2s, a gx_formatter implementation for the AIU I2S
playback path. This is going to replace data formatting tasks that are
currently being implemented in aiu-encoder-i2s.
This should ideally follow the same design pattern used on the AXG
platform (see axg-tdmout), but the problem here is that all playback
features (including data formatting) so far are implemented in the AIU
component. Getting the full AXG design would mean introducing incompatible
device-tree changes. Therefore aiu-formatter-i2s is kept very simple and
it only implements the bare minimum functionalities to provide I2S
playback formatting. It's not a standalone component though because this
is still implemented by AIU.
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
sound/soc/meson/Makefile | 1 +
sound/soc/meson/aiu-formatter-i2s.c | 106 ++++++++++++++++++++++++++++++++++++
2 files changed, 107 insertions(+)
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index 146ec81526ba091a174a113ce3d8412ddbbfd9dd..f9ec0ebb01f048728b8f85fd8e58fb90df990470 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -5,6 +5,7 @@ snd-soc-meson-aiu-y += aiu-acodec-ctrl.o
snd-soc-meson-aiu-y += aiu-codec-ctrl.o
snd-soc-meson-aiu-y += aiu-encoder-i2s.o
snd-soc-meson-aiu-y += gx-formatter.o
+snd-soc-meson-aiu-y += aiu-formatter-i2s.o
snd-soc-meson-aiu-y += aiu-encoder-spdif.o
snd-soc-meson-aiu-y += aiu-fifo.o
snd-soc-meson-aiu-y += aiu-fifo-i2s.o
diff --git a/sound/soc/meson/aiu-formatter-i2s.c b/sound/soc/meson/aiu-formatter-i2s.c
new file mode 100644
index 0000000000000000000000000000000000000000..c7eff04521de3c282f7f79864143e073ff1b2f27
--- /dev/null
+++ b/sound/soc/meson/aiu-formatter-i2s.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2026 BayLibre, SAS.
+// Author: Valerio Setti <vsetti@baylibre.com>
+
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "aiu.h"
+#include "gx-formatter.h"
+
+#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0)
+#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5)
+#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9)
+#define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11)
+#define AIU_RST_SOFT_I2S_FAST BIT(0)
+
+#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2)
+
+static struct snd_soc_dai *
+aiu_formatter_i2s_get_be(struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dapm_path *p;
+ struct snd_soc_dai *be;
+
+ snd_soc_dapm_widget_for_each_sink_path(w, p) {
+ if (!p->connect)
+ continue;
+
+ if (p->sink->id == snd_soc_dapm_dai_in)
+ return (struct snd_soc_dai *)p->sink->priv;
+
+ be = aiu_formatter_i2s_get_be(p->sink);
+ if (be)
+ return be;
+ }
+
+ return NULL;
+}
+
+static struct gx_stream *
+aiu_formatter_i2s_get_stream(struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dai *be = aiu_formatter_i2s_get_be(w);
+
+ if (!be)
+ return NULL;
+
+ return snd_soc_dai_dma_data_get_playback(be);
+}
+
+static int aiu_formatter_i2s_prepare(struct regmap *map,
+ const struct gx_formatter_hw *quirks,
+ struct gx_stream *ts)
+{
+ /* Always operate in split (classic interleaved) mode */
+ unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT;
+ unsigned int tmp;
+
+ /* Reset required to update the pipeline */
+ regmap_write(map, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST);
+ regmap_read(map, AIU_I2S_SYNC, &tmp);
+
+ switch (ts->physical_width) {
+ case 16: /* Nothing to do */
+ break;
+
+ case 32:
+ desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT |
+ AIU_I2S_SOURCE_DESC_MODE_32BIT);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ switch (ts->channels) {
+ case 2: /* Nothing to do */
+ break;
+ case 8:
+ desc |= AIU_I2S_SOURCE_DESC_MODE_8CH;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(map, AIU_I2S_SOURCE_DESC,
+ AIU_I2S_SOURCE_DESC_MODE_8CH |
+ AIU_I2S_SOURCE_DESC_MODE_24BIT |
+ AIU_I2S_SOURCE_DESC_MODE_32BIT |
+ AIU_I2S_SOURCE_DESC_MODE_SPLIT,
+ desc);
+
+ /* Send data MSB first */
+ regmap_update_bits(map, AIU_I2S_DAC_CFG,
+ AIU_I2S_DAC_CFG_MSB_FIRST,
+ AIU_I2S_DAC_CFG_MSB_FIRST);
+
+ return 0;
+}
+
+const struct gx_formatter_ops aiu_formatter_i2s_ops = {
+ .get_stream = aiu_formatter_i2s_get_stream,
+ .prepare = aiu_formatter_i2s_prepare,
+};
--
2.39.5
^ permalink raw reply related
* [PATCH 1/4] ASoC: meson: gx: add gx-formatter and gx-interface
From: Valerio Setti @ 2026-05-15 15:10 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
Valerio Setti
In-Reply-To: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com>
These files are the basic block which allow to shape I2S in GX devices
the same as the AXG ones: the DAI backend only controls the interface
(i.e. clocks and pins) whereas a formatter takes care of properly
formatting the data.
gx-formatter and gx-interface are strongly inspired to axg-tdm-formatter
and axg-tdm, respectively. The long term plan is to join the two platforms
to use the same formatter solution.
There is only a minor addition here compared to what has been done for
AXG and it's "gx_formatter_create()" which is required in order to let
already existing AIU code to make use of this formatter without making any
devicetree change.
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
sound/soc/meson/Makefile | 1 +
sound/soc/meson/gx-formatter.c | 277 +++++++++++++++++++++++++++++++++++++++++
sound/soc/meson/gx-formatter.h | 47 +++++++
sound/soc/meson/gx-interface.h | 45 +++++++
4 files changed, 370 insertions(+)
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile
index 24078e4396b02d545d8ba4bcb1632979001354e3..146ec81526ba091a174a113ce3d8412ddbbfd9dd 100644
--- a/sound/soc/meson/Makefile
+++ b/sound/soc/meson/Makefile
@@ -4,6 +4,7 @@ snd-soc-meson-aiu-y := aiu.o
snd-soc-meson-aiu-y += aiu-acodec-ctrl.o
snd-soc-meson-aiu-y += aiu-codec-ctrl.o
snd-soc-meson-aiu-y += aiu-encoder-i2s.o
+snd-soc-meson-aiu-y += gx-formatter.o
snd-soc-meson-aiu-y += aiu-encoder-spdif.o
snd-soc-meson-aiu-y += aiu-fifo.o
snd-soc-meson-aiu-y += aiu-fifo-i2s.o
diff --git a/sound/soc/meson/gx-formatter.c b/sound/soc/meson/gx-formatter.c
new file mode 100644
index 0000000000000000000000000000000000000000..db93546ed9217f711fcdbeddbd815ce21f27bab5
--- /dev/null
+++ b/sound/soc/meson/gx-formatter.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+//
+// Copyright (c) 2026 BayLibre, SAS.
+// Author: Valerio Setti <vsetti@baylibre.com>
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+
+#include "gx-formatter.h"
+
+struct gx_formatter {
+ struct list_head list;
+ struct gx_stream *stream;
+ const struct gx_formatter_driver *drv;
+ bool enabled;
+ struct regmap *map;
+};
+
+static int gx_formatter_enable(struct gx_formatter *formatter)
+{
+ int ret;
+
+ /* Do nothing if the formatter is already enabled */
+ if (formatter->enabled)
+ return 0;
+
+ /* Setup the stream parameter in the formatter */
+ if (formatter->drv->ops->prepare) {
+ ret = formatter->drv->ops->prepare(formatter->map,
+ formatter->drv->quirks,
+ formatter->stream);
+ if (ret)
+ return ret;
+ }
+
+ /* Finally, actually enable the formatter */
+ if (formatter->drv->ops->enable)
+ formatter->drv->ops->enable(formatter->map);
+
+ formatter->enabled = true;
+
+ return 0;
+}
+
+static void gx_formatter_disable(struct gx_formatter *formatter)
+{
+ /* Do nothing if the formatter is already disabled */
+ if (!formatter->enabled)
+ return;
+
+ if (formatter->drv->ops->disable)
+ formatter->drv->ops->disable(formatter->map);
+
+ formatter->enabled = false;
+}
+
+static int gx_formatter_attach(struct gx_formatter *formatter)
+{
+ struct gx_stream *ts = formatter->stream;
+ int ret = 0;
+
+ mutex_lock(&ts->lock);
+
+ /* Catch up if the stream is already running when we attach */
+ if (ts->ready) {
+ ret = gx_formatter_enable(formatter);
+ if (ret) {
+ pr_err("failed to enable formatter\n");
+ goto out;
+ }
+ }
+
+ list_add_tail(&formatter->list, &ts->formatter_list);
+out:
+ mutex_unlock(&ts->lock);
+ return ret;
+}
+
+static void gx_formatter_detach(struct gx_formatter *formatter)
+{
+ struct gx_stream *ts = formatter->stream;
+
+ mutex_lock(&ts->lock);
+ list_del(&formatter->list);
+ mutex_unlock(&ts->lock);
+
+ gx_formatter_disable(formatter);
+}
+
+static int gx_formatter_power_up(struct gx_formatter *formatter,
+ struct snd_soc_dapm_widget *w)
+{
+ struct gx_stream *ts = formatter->drv->ops->get_stream(w);
+ int ret;
+
+ /*
+ * If we don't get a stream at this stage, it would mean that the
+ * widget is powering up but is not attached to any backend DAI.
+ * It should not happen, ever !
+ */
+ if (WARN_ON(!ts))
+ return -ENODEV;
+
+ formatter->stream = ts;
+ ret = gx_formatter_attach(formatter);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void gx_formatter_power_down(struct gx_formatter *formatter)
+{
+ gx_formatter_detach(formatter);
+ formatter->stream = NULL;
+}
+
+int gx_formatter_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *control,
+ int event)
+{
+ struct snd_soc_component *c;
+ struct gx_formatter *formatter;
+ int ret = 0;
+
+ c = snd_soc_dapm_to_component(w->dapm);
+
+ if (w->priv)
+ formatter = w->priv;
+ else
+ formatter = snd_soc_component_get_drvdata(c);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ ret = gx_formatter_power_up(formatter, w);
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ gx_formatter_power_down(formatter);
+ break;
+
+ default:
+ dev_err(c->dev, "Unexpected event %d\n", event);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gx_formatter_event);
+
+int gx_formatter_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct gx_formatter_driver *drv;
+ struct gx_formatter *formatter;
+ void __iomem *regs;
+
+ drv = of_device_get_match_data(dev);
+ if (!drv) {
+ dev_err(dev, "failed to match device\n");
+ return -ENODEV;
+ }
+
+ formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL);
+ if (!formatter)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, formatter);
+ formatter->drv = drv;
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ formatter->map = devm_regmap_init_mmio(dev, regs, drv->regmap_cfg);
+ if (IS_ERR(formatter->map)) {
+ dev_err(dev, "failed to init regmap: %ld\n",
+ PTR_ERR(formatter->map));
+ return PTR_ERR(formatter->map);
+ }
+
+ return devm_snd_soc_register_component(dev, drv->component_drv,
+ NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gx_formatter_probe);
+
+int gx_formatter_create(struct device *dev,
+ struct snd_soc_dapm_widget *w,
+ const struct gx_formatter_driver *drv,
+ struct regmap *regmap)
+{
+ struct gx_formatter *formatter;
+
+ formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL);
+ if (!formatter)
+ return -ENOMEM;
+
+ formatter->drv = drv;
+ formatter->map = regmap;
+
+ w->priv = formatter;
+
+ return 0;
+}
+
+int gx_stream_start(struct gx_stream *ts)
+{
+ struct gx_formatter *formatter;
+ int ret = 0;
+
+ mutex_lock(&ts->lock);
+
+ /* Start all the formatters attached to the stream */
+ list_for_each_entry(formatter, &ts->formatter_list, list) {
+ ret = gx_formatter_enable(formatter);
+ if (ret) {
+ pr_err("failed to start tdm stream\n");
+ goto out;
+ }
+ }
+
+ ts->ready = true;
+
+out:
+ mutex_unlock(&ts->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gx_stream_start);
+
+void gx_stream_stop(struct gx_stream *ts)
+{
+ struct gx_formatter *formatter;
+
+ mutex_lock(&ts->lock);
+ ts->ready = false;
+
+ /* Stop all the formatters attached to the stream */
+ list_for_each_entry(formatter, &ts->formatter_list, list) {
+ gx_formatter_disable(formatter);
+ }
+
+ mutex_unlock(&ts->lock);
+}
+EXPORT_SYMBOL_GPL(gx_stream_stop);
+
+struct gx_stream *gx_stream_alloc(struct gx_iface *iface)
+{
+ struct gx_stream *ts;
+
+ ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+ if (ts) {
+ INIT_LIST_HEAD(&ts->formatter_list);
+ mutex_init(&ts->lock);
+ ts->iface = iface;
+ }
+
+ return ts;
+}
+EXPORT_SYMBOL_GPL(gx_stream_alloc);
+
+void gx_stream_free(struct gx_stream *ts)
+{
+ /*
+ * If the list is not empty, it would mean that one of the formatter
+ * widget is still powered and attached to the interface while we
+ * are removing the TDM DAI. It should not be possible
+ */
+ WARN_ON(!list_empty(&ts->formatter_list));
+ mutex_destroy(&ts->lock);
+ kfree(ts);
+}
+EXPORT_SYMBOL_GPL(gx_stream_free);
+
+MODULE_DESCRIPTION("Amlogic GX formatter driver");
+MODULE_AUTHOR("Valerio Setti <vsetti@baylibre.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/meson/gx-formatter.h b/sound/soc/meson/gx-formatter.h
new file mode 100644
index 0000000000000000000000000000000000000000..05670c3dfb9f43ac3ee959f1d3d11bacee020c43
--- /dev/null
+++ b/sound/soc/meson/gx-formatter.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2026 Baylibre SAS.
+ * Author: Valerio Setti <vsetti@baylibre.com>
+ */
+
+#ifndef _MESON_GX_FORMATTER_H
+#define _MESON_GX_FORMATTER_H
+
+#include "gx-interface.h"
+
+struct platform_device;
+struct regmap;
+struct snd_soc_dapm_widget;
+struct snd_kcontrol;
+
+struct gx_formatter_hw {
+ unsigned int skew_offset;
+};
+
+struct gx_formatter_ops {
+ struct gx_stream *(*get_stream)(struct snd_soc_dapm_widget *w);
+ void (*enable)(struct regmap *map);
+ void (*disable)(struct regmap *map);
+ int (*prepare)(struct regmap *map,
+ const struct gx_formatter_hw *quirks,
+ struct gx_stream *ts);
+};
+
+struct gx_formatter_driver {
+ const struct snd_soc_component_driver *component_drv;
+ const struct regmap_config *regmap_cfg;
+ const struct gx_formatter_ops *ops;
+ const struct gx_formatter_hw *quirks;
+};
+
+int gx_formatter_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *control,
+ int event);
+int gx_formatter_probe(struct platform_device *pdev);
+
+int gx_formatter_create(struct device *dev,
+ struct snd_soc_dapm_widget *w,
+ const struct gx_formatter_driver *drv,
+ struct regmap *regmap);
+
+#endif /* _MESON_GX_FORMATTER_H */
diff --git a/sound/soc/meson/gx-interface.h b/sound/soc/meson/gx-interface.h
new file mode 100644
index 0000000000000000000000000000000000000000..d9ab894589fa039f7fdd76765390630a2c6d8fbf
--- /dev/null
+++ b/sound/soc/meson/gx-interface.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2026 Baylibre SAS.
+ * Author: Valerio Setti <vsetti@baylibre.com>
+ */
+
+#ifndef _MESON_GX_INTERFACE_H
+#define _MESON_GX_INTERFACE_H
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+struct gx_iface {
+ struct clk *mclk;
+ unsigned long mclk_rate;
+
+ /* format is common to all the DAIs of the iface */
+ unsigned int fmt;
+
+ /* For component wide symmetry */
+ int rate;
+};
+
+struct gx_stream {
+ struct gx_iface *iface;
+ struct list_head formatter_list;
+ struct mutex lock;
+ unsigned int channels;
+ unsigned int width;
+ unsigned int physical_width;
+ bool ready;
+
+ /* For continuous clock tracking */
+ bool clk_enabled;
+};
+
+struct gx_stream *gx_stream_alloc(struct gx_iface *iface);
+void gx_stream_free(struct gx_stream *ts);
+int gx_stream_start(struct gx_stream *ts);
+void gx_stream_stop(struct gx_stream *ts);
+
+#endif /* _MESON_GX_INTERFACE_H */
--
2.39.5
^ permalink raw reply related
* [PATCH 2/4] ASoC: meson: aiu-encoder-i2s: use gx_iface and gx_stream structures
From: Valerio Setti @ 2026-05-15 15:10 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
Valerio Setti
In-Reply-To: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com>
Start using gx_iface and gx_stream to store interface and stream info,
respectively. probe()/remove() functions are added to allocate/free the
gx_stream structures for each PCM stream.
Clock-wise instead of bulk enabling all the clocks on startup and disabling
them on shutdown, only the peripheral's internal ones are enabled/disabled
in those functions, whereas MCLK and I2S clock divider are handled in
hw_params/hw_free.
Interface wide rate symmetry is also enforced here. This is useful when the
interface is used for playback and capture at the same time.
Finally a trigger() callback is also added to start/stop the associated
I2S data formatter.
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
sound/soc/meson/aiu-encoder-i2s.c | 200 ++++++++++++++++++++++++++++++++------
sound/soc/meson/aiu.h | 3 +
2 files changed, 174 insertions(+), 29 deletions(-)
diff --git a/sound/soc/meson/aiu-encoder-i2s.c b/sound/soc/meson/aiu-encoder-i2s.c
index 3b4061508c18047fe8d6f3f98061720f8ce238f2..39accd396affb8beb49fa7cca394244730b24574 100644
--- a/sound/soc/meson/aiu-encoder-i2s.c
+++ b/sound/soc/meson/aiu-encoder-i2s.c
@@ -10,6 +10,8 @@
#include <sound/soc-dai.h>
#include "aiu.h"
+#include "gx-formatter.h"
+#include "gx-interface.h"
#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0)
#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5)
@@ -79,7 +81,7 @@ static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component,
}
static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component,
- struct snd_pcm_hw_params *params,
+ struct gx_stream *ts,
unsigned int bs)
{
switch (bs) {
@@ -109,7 +111,7 @@ static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component,
}
static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component,
- struct snd_pcm_hw_params *params,
+ struct gx_stream *ts,
unsigned int bs)
{
/*
@@ -119,7 +121,7 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component,
* increased by 50% to get the correct output rate.
* No idea why !
*/
- if (params_width(params) == 16 && params_channels(params) == 8) {
+ if (ts->width == 16 && ts->channels == 8) {
if (bs % 2) {
dev_err(component->dev,
"Cannot increase i2s divider by 50%%\n");
@@ -142,24 +144,18 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component,
}
static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component,
- struct snd_pcm_hw_params *params)
+ struct gx_stream *ts)
{
struct aiu *aiu = snd_soc_component_get_drvdata(component);
- unsigned int srate = params_rate(params);
unsigned int fs, bs;
int ret;
/* Get the oversampling factor */
- fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate);
+ fs = DIV_ROUND_CLOSEST(ts->iface->mclk_rate, ts->iface->rate);
if (fs % 64)
return -EINVAL;
- /* Send data MSB first */
- snd_soc_component_update_bits(component, AIU_I2S_DAC_CFG,
- AIU_I2S_DAC_CFG_MSB_FIRST,
- AIU_I2S_DAC_CFG_MSB_FIRST);
-
/* Set bclk to lrlck ratio */
snd_soc_component_update_bits(component, AIU_CODEC_DAC_LRCLK_CTRL,
AIU_CODEC_DAC_LRCLK_CTRL_DIV,
@@ -169,9 +165,9 @@ static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component,
bs = fs / 64;
if (aiu->platform->has_clk_ctrl_more_i2s_div)
- ret = aiu_encoder_i2s_set_more_div(component, params, bs);
+ ret = aiu_encoder_i2s_set_more_div(component, ts, bs);
else
- ret = aiu_encoder_i2s_set_legacy_div(component, params, bs);
+ ret = aiu_encoder_i2s_set_legacy_div(component, ts, bs);
if (ret)
return ret;
@@ -188,25 +184,55 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
+ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
+ struct gx_iface *iface = ts->iface;
struct snd_soc_component *component = dai->component;
int ret;
- /* Disable the clock while changing the settings */
- aiu_encoder_i2s_divider_enable(component, false);
+ /* Enforce interface wide rate symmetry. */
+ if (iface->rate && (iface->rate != params_rate(params))) {
+ dev_err(dai->dev, "can't set iface rate (%d != %d)\n",
+ iface->rate, params_rate(params));
+ return -EINVAL;
+ }
+
+ iface->rate = params_rate(params);
+ ts->physical_width = params_physical_width(params);
+ ts->width = params_width(params);
+ ts->channels = params_channels(params);
ret = aiu_encoder_i2s_setup_desc(component, params);
if (ret) {
- dev_err(dai->dev, "setting i2s desc failed\n");
+ dev_err(dai->dev, "setting i2s desc failed: %d\n", ret);
return ret;
}
- ret = aiu_encoder_i2s_set_clocks(component, params);
+ ret = aiu_encoder_i2s_set_clocks(component, ts);
if (ret) {
- dev_err(dai->dev, "setting i2s clocks failed\n");
+ dev_err(dai->dev, "setting i2s clocks failed: %d\n", ret);
return ret;
}
- aiu_encoder_i2s_divider_enable(component, true);
+ return 0;
+}
+
+static int aiu_encoder_i2s_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
+ struct snd_soc_component *component = dai->component;
+ int ret;
+
+ if (ts->clk_enabled)
+ return 0;
+
+ ret = clk_prepare_enable(ts->iface->mclk);
+ if (ret)
+ return ret;
+
+ ts->clk_enabled = true;
+
+ aiu_encoder_i2s_divider_enable(component, 1);
return 0;
}
@@ -214,9 +240,20 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream,
static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
+ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
struct snd_soc_component *component = dai->component;
- aiu_encoder_i2s_divider_enable(component, false);
+ /*
+ * Disable the i2s clock divider only if this is the last substream
+ * being closed.
+ */
+ if (snd_soc_dai_active(dai) <= 1)
+ aiu_encoder_i2s_divider_enable(component, 0);
+
+ if (ts->clk_enabled) {
+ clk_disable_unprepare(ts->iface->mclk);
+ ts->clk_enabled = false;
+ }
return 0;
}
@@ -224,6 +261,8 @@ static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream,
static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct snd_soc_component *component = dai->component;
+ struct aiu *aiu = snd_soc_component_get_drvdata(component);
+ struct gx_iface *iface = &aiu->i2s.iface;
unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK;
unsigned int val = 0;
unsigned int skew;
@@ -255,9 +294,12 @@ static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
skew = 0;
break;
default:
+ dev_err(dai->dev, "unsupported dai format\n");
return -EINVAL;
}
+ iface->fmt = fmt;
+
val |= FIELD_PREP(AIU_CLK_CTRL_LRCLK_SKEW, skew);
snd_soc_component_update_bits(component, AIU_CLK_CTRL,
AIU_CLK_CTRL_LRCLK_INVERT |
@@ -281,10 +323,14 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,
return 0;
ret = clk_set_rate(aiu->i2s.clks[MCLK].clk, freq);
- if (ret)
- dev_err(dai->dev, "Failed to set sysclk to %uHz", freq);
+ if (ret) {
+ dev_err(dai->dev, "Failed to set sysclk to %uHz: %d", freq, ret);
+ return ret;
+ }
- return ret;
+ aiu->i2s.iface.mclk_rate = freq;
+
+ return 0;
}
static const unsigned int hw_channels[] = {2, 8};
@@ -305,30 +351,126 @@ static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream,
SNDRV_PCM_HW_PARAM_CHANNELS,
&hw_channel_constraints);
if (ret) {
- dev_err(dai->dev, "adding channels constraints failed\n");
+ dev_err(dai->dev, "adding channels constraints failed: %d\n", ret);
return ret;
}
- ret = clk_bulk_prepare_enable(aiu->i2s.clk_num, aiu->i2s.clks);
- if (ret)
- dev_err(dai->dev, "failed to enable i2s clocks\n");
+ /*
+ * Enable only clocks which are required for the interface internal
+ * logic. MCLK is enabled/disabled from the formatter and the I2S
+ * divider is enabled/disabled in "hw_params"/"hw_free", respectively.
+ */
+ ret = clk_prepare_enable(aiu->i2s.clks[PCLK].clk);
+ if (ret) {
+ dev_err(dai->dev, "failed to enable PCLK: %d\n", ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(aiu->i2s.clks[MIXER].clk);
+ if (ret) {
+ dev_err(dai->dev, "failed to enable MIXER: %d\n", ret);
+ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk);
+ return ret;
+ }
+ ret = clk_prepare_enable(aiu->i2s.clks[AOCLK].clk);
+ if (ret) {
+ dev_err(dai->dev, "failed to enable AOCLK: %d\n", ret);
+ clk_disable_unprepare(aiu->i2s.clks[MIXER].clk);
+ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk);
+ return ret;
+ }
- return ret;
+ return 0;
}
static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
+ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
+ struct gx_iface *iface = ts->iface;
+
+ if (!snd_soc_dai_active(dai))
+ iface->rate = 0;
+
+ clk_disable_unprepare(aiu->i2s.clks[AOCLK].clk);
+ clk_disable_unprepare(aiu->i2s.clks[MIXER].clk);
+ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk);
+}
- clk_bulk_disable_unprepare(aiu->i2s.clk_num, aiu->i2s.clks);
+static int aiu_encoder_i2s_trigger(struct snd_pcm_substream *substream,
+ int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream);
+ int ret;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = gx_stream_start(ts);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_STOP:
+ gx_stream_stop(ts);
+ ret = 0;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int aiu_encoder_i2s_remove_dai(struct snd_soc_dai *dai)
+{
+ int stream;
+
+ for_each_pcm_streams(stream) {
+ struct gx_stream *ts = snd_soc_dai_dma_data_get(dai, stream);
+
+ if (ts)
+ gx_stream_free(ts);
+ }
+
+ return 0;
+}
+
+static int aiu_encoder_i2s_probe_dai(struct snd_soc_dai *dai)
+{
+ struct aiu *aiu = snd_soc_dai_get_drvdata(dai);
+ struct gx_iface *iface = &aiu->i2s.iface;
+ int stream;
+
+ for_each_pcm_streams(stream) {
+ struct gx_stream *ts;
+
+ if (!snd_soc_dai_get_widget(dai, stream))
+ continue;
+
+ ts = gx_stream_alloc(iface);
+ if (!ts) {
+ aiu_encoder_i2s_remove_dai(dai);
+ return -ENOMEM;
+ }
+ snd_soc_dai_dma_data_set(dai, stream, ts);
+ }
+
+ iface->mclk = aiu->i2s.clks[MCLK].clk;
+
+ return 0;
}
const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops = {
+ .probe = aiu_encoder_i2s_probe_dai,
+ .remove = aiu_encoder_i2s_remove_dai,
.hw_params = aiu_encoder_i2s_hw_params,
+ .prepare = aiu_encoder_i2s_prepare,
.hw_free = aiu_encoder_i2s_hw_free,
.set_fmt = aiu_encoder_i2s_set_fmt,
.set_sysclk = aiu_encoder_i2s_set_sysclk,
.startup = aiu_encoder_i2s_startup,
.shutdown = aiu_encoder_i2s_shutdown,
+ .trigger = aiu_encoder_i2s_trigger,
};
diff --git a/sound/soc/meson/aiu.h b/sound/soc/meson/aiu.h
index 0f94c8bf608181112d78402532b832eb50c2d409..68310de0bdf7a97d8de2ff306c159248ee9b0ede 100644
--- a/sound/soc/meson/aiu.h
+++ b/sound/soc/meson/aiu.h
@@ -7,6 +7,8 @@
#ifndef _MESON_AIU_H
#define _MESON_AIU_H
+#include "gx-formatter.h"
+
struct clk;
struct clk_bulk_data;
struct device;
@@ -25,6 +27,7 @@ struct aiu_interface {
struct clk_bulk_data *clks;
unsigned int clk_num;
int irq;
+ struct gx_iface iface;
};
struct aiu_platform_data {
--
2.39.5
^ permalink raw reply related
* [PATCH 4/4] ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data
From: Valerio Setti @ 2026-05-15 15:10 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
Valerio Setti
In-Reply-To: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com>
Create a new DAPM widget for "I2S formatter" and place it on the path
between FIFO and output DAI interface. Remove I2S output formatting code
from aiu-encoder-i2s since it's now implemented from aiu-formatter-i2s.
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
sound/soc/meson/aiu-encoder-i2s.c | 56 ---------------------------------------
sound/soc/meson/aiu.c | 30 ++++++++++++++++++---
sound/soc/meson/aiu.h | 1 +
3 files changed, 27 insertions(+), 60 deletions(-)
diff --git a/sound/soc/meson/aiu-encoder-i2s.c b/sound/soc/meson/aiu-encoder-i2s.c
index 39accd396affb8beb49fa7cca394244730b24574..9f935a93aeab7a27f880fbde2d29041a4802e3a3 100644
--- a/sound/soc/meson/aiu-encoder-i2s.c
+++ b/sound/soc/meson/aiu-encoder-i2s.c
@@ -13,13 +13,6 @@
#include "gx-formatter.h"
#include "gx-interface.h"
-#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0)
-#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5)
-#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9)
-#define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11)
-#define AIU_RST_SOFT_I2S_FAST BIT(0)
-
-#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2)
#define AIU_CLK_CTRL_I2S_DIV_EN BIT(0)
#define AIU_CLK_CTRL_I2S_DIV GENMASK(3, 2)
#define AIU_CLK_CTRL_AOCLK_INVERT BIT(6)
@@ -37,49 +30,6 @@ static void aiu_encoder_i2s_divider_enable(struct snd_soc_component *component,
enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0);
}
-static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component,
- struct snd_pcm_hw_params *params)
-{
- /* Always operate in split (classic interleaved) mode */
- unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT;
-
- /* Reset required to update the pipeline */
- snd_soc_component_write(component, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST);
- snd_soc_component_read(component, AIU_I2S_SYNC);
-
- switch (params_physical_width(params)) {
- case 16: /* Nothing to do */
- break;
-
- case 32:
- desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT |
- AIU_I2S_SOURCE_DESC_MODE_32BIT);
- break;
-
- default:
- return -EINVAL;
- }
-
- switch (params_channels(params)) {
- case 2: /* Nothing to do */
- break;
- case 8:
- desc |= AIU_I2S_SOURCE_DESC_MODE_8CH;
- break;
- default:
- return -EINVAL;
- }
-
- snd_soc_component_update_bits(component, AIU_I2S_SOURCE_DESC,
- AIU_I2S_SOURCE_DESC_MODE_8CH |
- AIU_I2S_SOURCE_DESC_MODE_24BIT |
- AIU_I2S_SOURCE_DESC_MODE_32BIT |
- AIU_I2S_SOURCE_DESC_MODE_SPLIT,
- desc);
-
- return 0;
-}
-
static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component,
struct gx_stream *ts,
unsigned int bs)
@@ -201,12 +151,6 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream,
ts->width = params_width(params);
ts->channels = params_channels(params);
- ret = aiu_encoder_i2s_setup_desc(component, params);
- if (ret) {
- dev_err(dai->dev, "setting i2s desc failed: %d\n", ret);
- return ret;
- }
-
ret = aiu_encoder_i2s_set_clocks(component, ts);
if (ret) {
dev_err(dai->dev, "setting i2s clocks failed: %d\n", ret);
diff --git a/sound/soc/meson/aiu.c b/sound/soc/meson/aiu.c
index f2890111c1d2cfa2213bf01849957a796744b9ae..b09c2058eacaf2998d0d3cd6682910f94ec89912 100644
--- a/sound/soc/meson/aiu.c
+++ b/sound/soc/meson/aiu.c
@@ -29,13 +29,22 @@ static SOC_ENUM_SINGLE_DECL(aiu_spdif_encode_sel_enum, AIU_I2S_MISC,
static const struct snd_kcontrol_new aiu_spdif_encode_mux =
SOC_DAPM_ENUM("SPDIF Buffer Src", aiu_spdif_encode_sel_enum);
-static const struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = {
- SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0,
- &aiu_spdif_encode_mux),
+#define AIU_WIDGET_SPDIF_SRC_SEL 0
+#define AIU_WIDGET_I2S_FORMATTER 1
+
+static struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = {
+ [AIU_WIDGET_SPDIF_SRC_SEL] =
+ SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0,
+ &aiu_spdif_encode_mux),
+ [AIU_WIDGET_I2S_FORMATTER] =
+ SND_SOC_DAPM_PGA_E("I2S Formatter", SND_SOC_NOPM, 0, 0, NULL, 0,
+ gx_formatter_event,
+ (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
};
static const struct snd_soc_dapm_route aiu_cpu_dapm_routes[] = {
- { "I2S Encoder Playback", NULL, "I2S FIFO Playback" },
+ { "I2S Formatter", NULL, "I2S FIFO Playback" },
+ { "I2S Encoder Playback", NULL, "I2S Formatter" },
{ "SPDIF SRC SEL", "SPDIF", "SPDIF FIFO Playback" },
{ "SPDIF SRC SEL", "I2S", "I2S FIFO Playback" },
{ "SPDIF Encoder Playback", NULL, "SPDIF SRC SEL" },
@@ -172,6 +181,11 @@ static const struct regmap_config aiu_regmap_cfg = {
.max_register = 0x2ac,
};
+const struct gx_formatter_driver aiu_formatter_i2s_drv = {
+ .regmap_cfg = &aiu_regmap_cfg,
+ .ops = &aiu_formatter_i2s_ops,
+};
+
static int aiu_clk_bulk_get(struct device *dev,
const char * const *ids,
unsigned int num,
@@ -291,6 +305,14 @@ static int aiu_probe(struct platform_device *pdev)
return ret;
}
+ /* Allocate the aiu-formatter into its widget */
+ ret = gx_formatter_create(dev, &aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER],
+ &aiu_formatter_i2s_drv, map);
+ if (ret) {
+ dev_err(dev, "Failed to allocate aiu formatter\n");
+ goto err;
+ }
+
/* Register the hdmi codec control component */
ret = aiu_hdmi_ctrl_register_component(dev);
if (ret) {
diff --git a/sound/soc/meson/aiu.h b/sound/soc/meson/aiu.h
index 68310de0bdf7a97d8de2ff306c159248ee9b0ede..7d0b98c1f351b3c526ca06c43a4c04ee5f4b6dfa 100644
--- a/sound/soc/meson/aiu.h
+++ b/sound/soc/meson/aiu.h
@@ -61,6 +61,7 @@ extern const struct snd_soc_dai_ops aiu_fifo_i2s_dai_ops;
extern const struct snd_soc_dai_ops aiu_fifo_spdif_dai_ops;
extern const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops;
extern const struct snd_soc_dai_ops aiu_encoder_spdif_dai_ops;
+extern const struct gx_formatter_ops aiu_formatter_i2s_ops;
#define AIU_IEC958_BPF 0x000
#define AIU_958_MISC 0x010
--
2.39.5
^ permalink raw reply related
* [PATCH 0/4] ASoC: meson: aiu: align I2S design to the AXG one
From: Valerio Setti @ 2026-05-15 15:10 UTC (permalink / raw)
To: Jerome Brunet, Liam Girdwood, Mark Brown, Jaroslav Kysela,
Takashi Iwai, Neil Armstrong, Kevin Hilman, Martin Blumenstingl
Cc: linux-kernel, linux-sound, linux-arm-kernel, linux-amlogic,
Valerio Setti
This is the first follow-up patch series based on RFC [1]. The goal here
is simply to reshape Amlogic GX's AIU implementation for I2S to follow
the same design as in AXG's TDM. Keeping the same design allows for
unifying the two platform implementations in the future.
The first commit introduces gx-formatter as the basic block which takes
care of properly formatting audio data. Formatters are DAPM widgets
(c.f. axg-tdm-formatter in AXG) which are dynamically attached/detached
to the streams when the latters starts/stop, respectively.
aiu-formatter-i2s is introduced as formatter implementation for the i2s
output.
By the end aiu-encoder-i2s will only need to handle interface clocks and
enforce interface wide rate symmetry (c.f axg-tdm-interface on the AXG
platform). Right now rate symmetry is not relevant because only i2s output
is supported, but it will become useful when following patch series will
introduce the i2s input part.
This series was tested on an OdroidC2 board (Amlogic S905 SOC) with an
NXP SGTL5000 codec connected to its I2S input port.
Changes from RFC:
- Use devm_ variant of snd_soc_register_component in gx_formatte_probe.
- Return on failures in aiu_encoder_i2s_set_sysclk.
- MCLK and i2s_divider clocks enablement moved from hw_params() to
prepare(). Stream's "clk_enabled" is used as simple refcounting to
prevent multiple enable/disable.
- Improved the mechanism to ensure rate symmetry on the interface.
Previous implementation had issues with alsaloop when both capture and
playback streams are opened before any hw_params is set on any of them.
Thanks to Mark Brown and Jerome Brunet for the review of the RFC.
[1]: https://lore.kernel.org/linux-sound/20260411-audin-rfc-v2-0-4c8a6ec5fcab@baylibre.com/
Signed-off-by: Valerio Setti <vsetti@baylibre.com>
---
Valerio Setti (4):
ASoC: meson: gx: add gx-formatter and gx-interface
ASoC: meson: aiu-encoder-i2s: use gx_iface and gx_stream structures
ASoC: meson: aiu: introduce I2S output formatter
ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data
sound/soc/meson/Makefile | 2 +
sound/soc/meson/aiu-encoder-i2s.c | 250 +++++++++++++++++++++-----------
sound/soc/meson/aiu-formatter-i2s.c | 106 ++++++++++++++
sound/soc/meson/aiu.c | 30 +++-
sound/soc/meson/aiu.h | 4 +
sound/soc/meson/gx-formatter.c | 277 ++++++++++++++++++++++++++++++++++++
sound/soc/meson/gx-formatter.h | 47 ++++++
sound/soc/meson/gx-interface.h | 45 ++++++
8 files changed, 675 insertions(+), 86 deletions(-)
---
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
change-id: 20260515-reshape-aiu-as-axg-1dac9037cad3
Best regards,
--
Valerio Setti <vsetti@baylibre.com>
^ permalink raw reply
* Re: [PATCH v2 6/8] PCI: aardvark: Add 100 ms delay after link training
From: Hans Zhang @ 2026-05-15 15:04 UTC (permalink / raw)
To: Pali Rohár
Cc: bhelgaas, lpieralisi, kwilczynski, mani, vigneshr, jingoohan1,
thomas.petazzoni, ryder.lee, jianjun.wang, claudiu.beznea.uj,
mpillai, robh, s-vadapalli, linux-omap, linux-arm-kernel,
linux-mediatek, linux-renesas-soc, linux-pci, linux-kernel
In-Reply-To: <20260513185442.mw3md5te7dhojyd7@pali>
On 5/14/26 02:54, Pali Rohár wrote:
> On Wednesday 13 May 2026 15:34:46 Hans Zhang wrote:
>> On 5/13/26 15:20, Pali Rohár wrote:
>>> On Wednesday 13 May 2026 15:00:04 Hans Zhang wrote:
>>>>
>>>>
>>>> On 5/13/26 05:25, Pali Rohár wrote:
>>>>> On Wednesday 06 May 2026 23:23:44 Hans Zhang wrote:
>>>>>> The Aardvark PCIe controller driver waits for the link to come up but
>>>>>> does not implement the mandatory 100 ms delay after link training
>>>>>> completes for speeds greater than 5.0 GT/s (PCIe r6.0 sec 6.6.1).
>>>>>>
>>>>>> The driver already maintains a 'link_gen' field that holds the negotiated
>>>>>> link speed. Use it together with pcie_wait_after_link_train() to insert
>>>>>> the required delay immediately after confirming that the link is up.
>>>>>>
>>>>>> Signed-off-by: Hans Zhang <18255117159@163.com>
>>>>>> ---
>>>>>> drivers/pci/controller/pci-aardvark.c | 4 +++-
>>>>>> 1 file changed, 3 insertions(+), 1 deletion(-)
>>>>>>
>>>>>> diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
>>>>>> index e34bea1ff0ac..526351c21c49 100644
>>>>>> --- a/drivers/pci/controller/pci-aardvark.c
>>>>>> +++ b/drivers/pci/controller/pci-aardvark.c
>>>>>> @@ -350,8 +350,10 @@ static int advk_pcie_wait_for_link(struct advk_pcie *pcie)
>>>>>> /* check if the link is up or not */
>>>>>> for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) {
>>>>>> - if (advk_pcie_link_up(pcie))
>>>>>> + if (advk_pcie_link_up(pcie)) {
>>>>>> + pcie_wait_after_link_train(pcie->link_gen);
>>>>>> return 0;
>>>>>> + }
>>>>>> usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX);
>>>>>> }
>>>>>> --
>>>>>> 2.34.1
>>>>>>
>>>>>
>>>>> Are you sure that this is correct to do? Have you checked the A3720
>>>>> Functional Specification which describes how to bring PCIe link up?
>>>>>
>>>>> A3720 PCIe controller is buggy and needs more timing hacks to make it
>>>>> behave. Playing with random sleeps can break its internal logic.
>>>>> I'm not sure if it could be safe without proper testing.
>>>>>
>>>>> And IIRC A3720 PCIe controller is just PCIe2.0 with 5 GT/s.
>>>>
>>>>
>>>> Hi Pali,
>>>>
>>>> 1. This driver does not support A3720.
>>>>
>>>> static const struct of_device_id advk_pcie_of_match_table[] = {
>>>> { .compatible = "marvell,armada-3700-pcie", },
>>>> {},
>>>> };
>>>> MODULE_DEVICE_TABLE(of, advk_pcie_of_match_table);
>>>>
>>>> If you need support for A3720, please submit the corresponding patch so that
>>>> Bjorn and Mani can review it.
>>>
>>> 3700 (or 37xx) is family and covers both a3710 and a3720. In most cases is the
>>> a3720 dominant and hence identifiers 3700 and 3720 are begin mixed.
>>>
>>>>
>>>> 2. If A3720 only supports GEN2, you can configure "max-link-speed" to be 2
>>>> in the DT. This will not affect the functionality of this patch.
>>>
>>> Whole A37xx supports only GEN2. And in DT files for 37xx should be
>>> already there max-link-speed.
>>>
>>> Seems that in advk_pcie_of_match_table there is no GEN3 device
>>> specified.
>>>
>>
>> Hi Pali,
>>
>> However, I saw many GEN3 assignments and conditions in the code.
>>
>> ret = of_pci_get_max_link_speed(dev->of_node);
>> if (ret <= 0 || ret > 3)
>> pcie->link_gen = 3;
>> else
>> pcie->link_gen = ret;
>>
>>
>> static void advk_pcie_train_link(struct advk_pcie *pcie)
>> {
>> struct device *dev = &pcie->pdev->dev;
>> u32 reg;
>> int ret;
>>
>> /*
>> * Setup PCIe rev / gen compliance based on device tree property
>> * 'max-link-speed' which also forces maximal link speed.
>> */
>> reg = advk_readl(pcie, PCIE_CORE_CTRL0_REG);
>> reg &= ~PCIE_GEN_SEL_MSK;
>> if (pcie->link_gen == 3)
>> reg |= SPEED_GEN_3;
>> else if (pcie->link_gen == 2)
>> reg |= SPEED_GEN_2;
>> else
>> reg |= SPEED_GEN_1;
>> advk_writel(pcie, reg, PCIE_CORE_CTRL0_REG);
>>
>> /*
>> * Set maximal link speed value also into PCIe Link Control 2 register.
>> * Armada 3700 Functional Specification says that default value is based
>> * on SPEED_GEN but tests showed that default value is always 8.0 GT/s.
>> */
>> reg = advk_readl(pcie, PCIE_CORE_PCIEXP_CAP + PCI_EXP_LNKCTL2);
>> reg &= ~PCI_EXP_LNKCTL2_TLS;
>> if (pcie->link_gen == 3)
>> reg |= PCI_EXP_LNKCTL2_TLS_8_0GT;
>> else if (pcie->link_gen == 2)
>> reg |= PCI_EXP_LNKCTL2_TLS_5_0GT;
>> else
>> reg |= PCI_EXP_LNKCTL2_TLS_2_5GT;
>> advk_writel(pcie, reg, PCIE_CORE_PCIEXP_CAP + PCI_EXP_LNKCTL2);
>>
>> ....
>>
>>
>> If you are certain about the relevant information. Is it understandable that
>> we need to delete the code related to GEN3?
>
> Ok. So some explanation. pci-aardvark.c is implementing driver for PCIe
> controller with codename aardvark. I have no idea from what this
> codename comes and what is represents. What we know that the driver was
> written for A37xx SoC platform according to A37xx functional specification.
> As it is common in SoC world, vendors just buy some IP and integrate it
> into SoC. In this case Marvell bought this PCIe controller IP and
> integrated it into the A37xx. In past I tried to investigate what it
> could be and IIRC my assumption was that it was PCIe IP from Denali.
> Denali was acquired by Cadence, and when I compared Cadence PCIe
> controller registers and PCIe controller registers in A37xx functional
> specification there were large overlap. For me it looked like new
> Cadence PCIe controller is an evolution (or new version) of what is in
> A37xx. So this was some confirmation of my theory. Linux kernel has
> separate driver for PCIe controller from Cadence and for refactoring
> there were ideas to merge these two drivers... But there were more
> important things, fix issues related to A37xx PCIe, lot of changes
> which address these issues were sent to the list but they were not
> taken. I do not think that it makes sense to do refactoring or doing any
> other changes before addressing any existing issues with these
> drivers (like PCIe card is not working correctly).
>
> There are reported more HW erratas for this PCIe controller which needs
> to be addressed in the software (meaning in Linux kernel) to make PCIe
> card working properly. And there are more design HW decision which needs
> does not conform to the PCIe specification and those deviations needs to
> be "fixed" or "adjusted" in software (meaning in pci-aardvark.c driver)
> to make PCI/PCIe compatible drivers to work correctly.
>
> Now about GEN3. From register allocation it looks like that PCIe IP
> supports GEN3. A37xx does not support it (or at least officially). This
> does not mean that there cannot be some SoC with this "aardvark" PCIe IP
> that is GEN3 capable. Just we see that such SoC is not supported by Linux.
> Also as the comment in above code says, by default the speed is reported
> as 8.0 GT/s, so changing it to 5.0 GT/s or 2.5 GT/s is needed as so code
> some parts of GEN3 code in the driver is needed.
>
> Does it makes sense to remove it? Does it makes sense to spend time on
> such thing which does not address any existing issue? For me not.
> Because it does not fix any _real_ issue with existing PCIe cards. And
> for refactoring it is better to merge drivers as explained above and
> IIRC cadence driver has HW on which is GEN3 used.
>
> Now about your change. If you are sure that pcie_wait_after_link_train()
> function is noop for pcie->link_gen == 2 || pcie->link_gen == 1 then go
> ahead, I have no objects. I have not looked deeply at the change. I just
> spotted some change which is touching timing critical code path which
> was problematic in the past and broke many wifi cards. So I'm really
> careful to prevent breaking Linux support again.
>
Hi Pali,
This condition, pcie->link_gen == 2 || pcie->link_gen == 1, will have no
effect.
Best regards,
Hans
> As maintainers decided to not take any new changes from me for this
> driver, I have no motivation to prepare any new changes. I will rather
> spend my free time on something which will make sense and not be wasting
> of my free time.
>
^ permalink raw reply
* Re: [PATCH 01/19] btrfs: require at least 4 devices for RAID 6
From: David Sterba @ 2026-05-15 14:51 UTC (permalink / raw)
To: H. Peter Anvin
Cc: kreijack, Goffredo Baroncelli, Christoph Hellwig, Andrew Morton,
Catalin Marinas, Will Deacon, Ard Biesheuvel, Huacai Chen,
WANG Xuerui, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Heiko Carstens,
Vasily Gorbik, Alexander Gordeev, Christian Borntraeger,
Sven Schnelle, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, Herbert Xu, Dan Williams, Chris Mason,
David Sterba, Arnd Bergmann, Song Liu, Yu Kuai, Li Nan,
linux-kernel, linux-arm-kernel, loongarch, linuxppc-dev,
linux-riscv, linux-s390, linux-crypto, linux-btrfs, linux-arch,
linux-raid
In-Reply-To: <0507CCEF-0548-442F-8703-1D006B5E068B@zytor.com>
On Thu, May 14, 2026 at 12:57:53PM -0700, H. Peter Anvin wrote:
> On May 14, 2026 12:51:59 PM PDT, Goffredo Baroncelli <kreijack@libero.it> wrote:
> >On 13/05/2026 07.47, Christoph Hellwig wrote:
> >> On Tue, May 12, 2026 at 01:42:31PM +0200, David Sterba wrote:
> >
> >>
> >>> The degenerate modes of
> >>> raid0, 5, or 6 are explicit as a possible middle step when converting
> >>> profiles. We can use a fallback implementation for this case if the
> >>> accelerated implementations cannot do it.
> >>
> >> This is not about a degenerated mode. For a degenerated RAID 6, parity
> >> generation uses the RAID 5 XOR routines as the second parity will be
> >> missing. This is about generating two parities for a single data disk,
> >> which must be explicitly selected.
> >>
> >
> >I think that the David concern is : "what happens for an already
> >existing btrfs raid6 3 disks filesystem when the user upgrade the kernel ?"
> >(I am thinking when a new BG needs to be allocated)...
>
> That's what I'm saying – it should invoke the RAID-1 code under the cover (as with 3 disks, D = P = Q.)
Thanks, it was not clear to me what you meant. For the two edge cases
the code should do simple memcpy for both calculations of parity and
recovery.
^ permalink raw reply
* Re: [PATCH RFC 05/12] usb: hub: Power on connected M.2 E-key connectors
From: Alan Stern @ 2026-05-15 14:39 UTC (permalink / raw)
To: Chen-Yu Tsai
Cc: Bartosz Golaszewski, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, linux-pm, linux-usb, devicetree,
linux-mediatek, linux-arm-kernel, linux-kernel,
Manivannan Sadhasivam
In-Reply-To: <20260515090149.3169406-6-wenst@chromium.org>
On Fri, May 15, 2026 at 05:01:41PM +0800, Chen-Yu Tsai wrote:
> diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
> index 90ea597d42ae..4165f71e212b 100644
> --- a/drivers/usb/core/hub.c
> +++ b/drivers/usb/core/hub.c
> @@ -31,7 +31,9 @@
> #include <linux/minmax.h>
> #include <linux/mutex.h>
> #include <linux/random.h>
> +#include <linux/of_graph.h>
> #include <linux/pm_qos.h>
> +#include <linux/pwrseq/consumer.h>
> #include <linux/kobject.h>
>
> #include <linux/bitfield.h>
> @@ -888,13 +890,25 @@ int usb_hub_set_port_power(struct usb_device *hdev, struct usb_hub *hub,
> {
> int ret;
>
> + if (set)
> + ret = pwrseq_power_on(hub->ports[port1 - 1]->pwrseq);
> + else
> + ret = pwrseq_power_off(hub->ports[port1 - 1]->pwrseq);
> + if (ret)
> + return ret;
> +
> if (set)
> ret = set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
> else
> ret = usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
>
> - if (ret)
> + if (ret) {
> + if (set)
> + pwrseq_power_off(hub->ports[port1 - 1]->pwrseq);
> + else
> + pwrseq_power_on(hub->ports[port1 - 1]->pwrseq);
> return ret;
> + }
>
> if (set)
> set_bit(port1, hub->power_bits);
> @@ -1867,6 +1881,7 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
> struct usb_host_interface *desc;
> struct usb_device *hdev;
> struct usb_hub *hub;
> + int ret;
>
> desc = intf->cur_altsetting;
> hdev = interface_to_usbdev(intf);
This change is totally useless. Didn't you get a warning from the
compiler when you built it?
> diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
> index 9ebc5ef54a32..6039e5f5dcd7 100644
> --- a/drivers/usb/core/hub.h
> +++ b/drivers/usb/core/hub.h
> @@ -85,6 +85,7 @@ struct usb_hub {
> * @port_owner: port's owner
> * @peer: related usb2 and usb3 ports (share the same connector)
> * @connector: USB Type-C connector
> + * @pwrseq: power sequencing descriptor for the port
> * @req: default pm qos request for hubs without port power control
> * @connect_type: port's connect type
> * @state: device state of the usb device attached to the port
> @@ -104,6 +105,7 @@ struct usb_port {
> struct usb_dev_state *port_owner;
> struct usb_port *peer;
> struct typec_connector *connector;
> + struct pwrseq_desc *pwrseq;
> struct dev_pm_qos_request *req;
> enum usb_port_connect_type connect_type;
> enum usb_device_state state;
The fact that hub.h uses struct pwrseq_desc indicates that it ought to
#include <linux/pwrseq/consumer.h>, instead of making the .c files do
so themselves. Then you wouldn't have to add the #include lines to
hub.c and port.c.
> diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
> index b1364f0c384c..2d09037fee93 100644
> --- a/drivers/usb/core/port.c
> +++ b/drivers/usb/core/port.c
> @@ -7,11 +7,14 @@
> * Author: Lan Tianyu <tianyu.lan@intel.com>
> */
>
> +#include <linux/cleanup.h>
Why is this needed?
> #include <linux/kstrtox.h>
> #include <linux/slab.h>
> #include <linux/string_choices.h>
> #include <linux/sysfs.h>
> +#include <linux/of_graph.h>
> #include <linux/pm_qos.h>
> +#include <linux/pwrseq/consumer.h>
> #include <linux/component.h>
> #include <linux/usb/of.h>
>
Alan Stern
^ permalink raw reply
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