From 39ca70a706fb3ccadfaf63acc0bab4276c9be21f Mon Sep 17 00:00:00 2001 From: Hugo Osvaldo Barrera Date: Fri, 22 May 2026 13:34:00 +0200 Subject: [PATCH 1/3] mxc-epdc: implement software-based rotation Signed-off-by: Hugo Osvaldo Barrera --- drivers/gpu/drm/mxc-epdc/epdc_update.c | 80 +++++++++++++++++++------ drivers/gpu/drm/mxc-epdc/mxc_epdc.h | 1 + drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c | 32 +++++++++- 3 files changed, 91 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/mxc-epdc/epdc_update.c b/drivers/gpu/drm/mxc-epdc/epdc_update.c index af5a73d33683e2..a6a04ff13b3aa2 100644 --- a/drivers/gpu/drm/mxc-epdc/epdc_update.c +++ b/drivers/gpu/drm/mxc-epdc/epdc_update.c @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include "mxc_epdc.h" #include "epdc_hw.h" @@ -335,17 +337,40 @@ static int epdc_submit_merge(struct update_desc_list *upd_desc_list, return MERGE_OK; } -static void epdc_from_rgb_clear_lower_nibble(struct drm_rect *clip, void *vaddr, int pitch, u8 *dst, int dst_pitch) +/* Transform coordinates based on rotation */ +static void transform_coords(unsigned int x, unsigned int y, + u32 width, u32 height, unsigned int rotation, + unsigned int *out_x, unsigned int *out_y) { - unsigned int x, y; + switch (rotation) { + case DRM_MODE_ROTATE_90: + *out_x = height - 1 - y; + *out_y = x; + break; + case DRM_MODE_ROTATE_180: + *out_x = width - 1 - x; + *out_y = height - 1 - y; + break; + case DRM_MODE_ROTATE_270: + *out_x = y; + *out_y = width - 1 - x; + break; + default: + *out_x = x; + *out_y = y; + break; + } +} - dst += clip->y1 * dst_pitch; +static void epdc_from_rgb_clear_lower_nibble(struct drm_rect *clip, void *vaddr, int pitch, u8 *dst, int dst_pitch, + u32 fb_width, u32 fb_height, unsigned int rotation) +{ + unsigned int x, y; - for (y = clip->y1; y < clip->y2; y++, dst += dst_pitch) { - u32 *src; - src = vaddr + (y * pitch); - src += clip->x1; + for (y = clip->y1; y < clip->y2; y++) { + u32 *src = vaddr + (y * pitch) + clip->x1; for (x = clip->x1; x < clip->x2; x++) { + unsigned int out_x, out_y; u8 r = (*src & 0x00ff0000) >> 16; u8 g = (*src & 0x0000ff00) >> 8; u8 b = *src & 0x000000ff; @@ -355,34 +380,35 @@ static void epdc_from_rgb_clear_lower_nibble(struct drm_rect *clip, void *vaddr, /* * done in Tolino 3.0.x kernels via PXP_LUT_AA - * needed for 5 bit waveforms + * needed for 5 bit waveforms */ - dst[x] = gray & 0xF0; + transform_coords(x, y, fb_width, fb_height, rotation, &out_x, &out_y); + dst[out_y * dst_pitch + out_x] = gray & 0xF0; src++; } } } /* found by experimentation, reduced number of levels of gray */ -static void epdc_from_rgb_shift(struct drm_rect *clip, void *vaddr, int pitch, u8 *dst, int dst_pitch) +static void epdc_from_rgb_shift(struct drm_rect *clip, void *vaddr, int pitch, u8 *dst, int dst_pitch, + u32 fb_width, u32 fb_height, unsigned int rotation) { unsigned int x, y; - dst += clip->y1 * dst_pitch; - - for (y = clip->y1; y < clip->y2; y++, dst += dst_pitch) { - u32 *src; - src = vaddr + (y * pitch); - src += clip->x1; + for (y = clip->y1; y < clip->y2; y++) { + u32 *src = vaddr + (y * pitch) + clip->x1; for (x = clip->x1; x < clip->x2; x++) { + unsigned int out_x, out_y; u8 r = (*src & 0x00ff0000) >> 16; u8 g = (*src & 0x0000ff00) >> 8; u8 b = *src & 0x000000ff; /* ITU BT.601: Y = 0.299 R + 0.587 G + 0.114 B */ u8 gray = (3 * r + 6 * g + b) / 10; - dst[x] = (gray >> 2) | 0xC0; + + transform_coords(x, y, fb_width, fb_height, rotation, &out_x, &out_y); + dst[out_y * dst_pitch + out_x] = (gray >> 2) | 0xC0; src++; } } @@ -848,14 +874,29 @@ int mxc_epdc_send_single_update(struct drm_rect *clip, int pitch, void *vaddr, { struct update_desc_list *upd_desc; + unsigned int rotation = priv->rotation; + bool rotate_90_270 = drm_rotation_90_or_270(rotation); + u32 fb_width, fb_height; + + /* Framebuffer dimensions (before rotation) */ + if (rotate_90_270) { + fb_width = priv->epdc_mem_height; + fb_height = priv->epdc_mem_width; + } else { + fb_width = priv->epdc_mem_width; + fb_height = priv->epdc_mem_height; + } + if ((priv->rev < 30) || (priv->buf_pix_fmt == EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N)) epdc_from_rgb_clear_lower_nibble(clip, vaddr, pitch, (u8 *)priv->epdc_mem_virt, - priv->epdc_mem_width); + priv->epdc_mem_width, + fb_width, fb_height, rotation); else epdc_from_rgb_shift(clip, vaddr, pitch, (u8 *)priv->epdc_mem_virt, - priv->epdc_mem_width); + priv->epdc_mem_width, + fb_width, fb_height, rotation); /* Has EPDC HW been initialized? */ if (!priv->hw_ready) { @@ -885,6 +926,7 @@ int mxc_epdc_send_single_update(struct drm_rect *clip, int pitch, void *vaddr, } /* Initialize per-update marker list */ upd_desc->upd_data.update_region = *clip; + drm_rect_rotate(&upd_desc->upd_data.update_region, fb_width, fb_height, rotation); upd_desc->upd_data.waveform_mode = WAVEFORM_MODE_AUTO; upd_desc->upd_data.temp = TEMP_USE_AMBIENT; upd_desc->upd_data.update_mode = UPDATE_MODE_PARTIAL; diff --git a/drivers/gpu/drm/mxc-epdc/mxc_epdc.h b/drivers/gpu/drm/mxc-epdc/mxc_epdc.h index 6166537a4ac3f0..62ac18c3700767 100644 --- a/drivers/gpu/drm/mxc-epdc/mxc_epdc.h +++ b/drivers/gpu/drm/mxc-epdc/mxc_epdc.h @@ -73,6 +73,7 @@ struct mxc_epdc { struct drm_connector connector; struct display_timing timing; struct imx_epdc_fb_mode imx_mode; + unsigned int rotation; void __iomem *iobase; struct completion powerdown_compl; struct clk *epdc_clk_axi; diff --git a/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c b/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c index a6125cfa58aa74..f26fd74cebf968 100644 --- a/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c +++ b/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "mxc_epdc.h" #include "epdc_hw.h" #include "epdc_update.h" @@ -189,10 +190,26 @@ static void mxc_epdc_pipe_enable(struct drm_simple_display_pipe *pipe, { struct mxc_epdc *priv = drm_pipe_to_mxc_epdc(pipe); struct drm_display_mode *m = &pipe->crtc.state->adjusted_mode; + unsigned int rotation = plane_state->rotation; + bool rotate_90_270 = drm_rotation_90_or_270(rotation); + u32 width, height; + + dev_info(priv->drm.dev, "Mode: %d x %d, rotation: %u\n", + m->hdisplay, m->vdisplay, rotation); + + priv->rotation = rotation; + + /* Swap dimensions for 90/270 degree rotation */ + if (rotate_90_270) { + width = m->vdisplay; + height = m->hdisplay; + } else { + width = m->hdisplay; + height = m->vdisplay; + } - dev_info(priv->drm.dev, "Mode: %d x %d\n", m->hdisplay, m->vdisplay); - priv->epdc_mem_width = m->hdisplay; - priv->epdc_mem_height = m->vdisplay; + priv->epdc_mem_width = width; + priv->epdc_mem_height = height; priv->epdc_mem_virt = dma_alloc_wc(priv->drm.dev, m->hdisplay * m->vdisplay, &priv->epdc_mem_phys, GFP_DMA | GFP_KERNEL); @@ -354,6 +371,15 @@ static int mxc_epdc_probe(struct platform_device *pdev) ARRAY_SIZE(mxc_epdc_formats), NULL, &priv->connector); + + /* Add rotation property to support display rotation */ + drm_plane_create_rotation_property(&priv->pipe.plane, + DRM_MODE_ROTATE_0, + DRM_MODE_ROTATE_0 | + DRM_MODE_ROTATE_90 | + DRM_MODE_ROTATE_180 | + DRM_MODE_ROTATE_270); + drm_plane_enable_fb_damage_clips(&priv->pipe.plane); drm_mode_config_reset(&priv->drm); From 65e11b84984d70893f22d5e367e9dbc40f91c920 Mon Sep 17 00:00:00 2001 From: Hugo Osvaldo Barrera Date: Fri, 22 May 2026 14:41:29 +0200 Subject: [PATCH 2/3] mxc-epdc: use PxP for rendering My main goal was to use the Pixel Pipe for rotation, but it soon became obvious that is was the same effort to use it for all rendering, so we can have hardware-based rendering entirely, rather than CPU rendering. Used an internal API between the PxP and the EPDC, so that both rendering and rotation are done in hardware. Using the V4L2 API should be feasible too, but I have no idea how to do so and it would likely be *a lot* more code. I trust this doesn't violate any conventions: I found least one other instance of a similar approach. It's unclear to me if we need the software fallback or not. The main question is: is there any device which has this EPDC but doesn't have the same PxP? Signed-off-by: Hugo Osvaldo Barrera --- drivers/gpu/drm/mxc-epdc/epdc_update.c | 36 +++- drivers/gpu/drm/mxc-epdc/epdc_update.h | 6 +- drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c | 5 +- drivers/media/platform/nxp/imx-pxp.c | 217 ++++++++++++++++++++++++ include/media/pxp.h | 97 +++++++++++ 5 files changed, 356 insertions(+), 5 deletions(-) create mode 100644 include/media/pxp.h diff --git a/drivers/gpu/drm/mxc-epdc/epdc_update.c b/drivers/gpu/drm/mxc-epdc/epdc_update.c index a6a04ff13b3aa2..7fffb5beea5327 100644 --- a/drivers/gpu/drm/mxc-epdc/epdc_update.c +++ b/drivers/gpu/drm/mxc-epdc/epdc_update.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "mxc_epdc.h" #include "epdc_hw.h" @@ -869,7 +870,7 @@ void mxc_epdc_draw_mode0(struct mxc_epdc *priv) } -int mxc_epdc_send_single_update(struct drm_rect *clip, int pitch, void *vaddr, +int mxc_epdc_send_single_update(struct drm_rect *clip, int pitch, dma_addr_t src_addr, void *vaddr, struct mxc_epdc *priv) { struct update_desc_list *upd_desc; @@ -877,6 +878,10 @@ int mxc_epdc_send_single_update(struct drm_rect *clip, int pitch, void *vaddr, unsigned int rotation = priv->rotation; bool rotate_90_270 = drm_rotation_90_or_270(rotation); u32 fb_width, fb_height; + struct pxp_epdc_config pxp_cfg; + enum pxp_grayscale_mode pxp_mode; + struct device *pxp_dev; + int ret; /* Framebuffer dimensions (before rotation) */ if (rotate_90_270) { @@ -887,6 +892,34 @@ int mxc_epdc_send_single_update(struct drm_rect *clip, int pitch, void *vaddr, fb_height = priv->epdc_mem_height; } + /* Try to use PxP for hardware processing */ + pxp_dev = pxp_get_device(); + if (pxp_dev) { + /* Determine grayscale mode for EPDC */ + if ((priv->rev < 30) || (priv->buf_pix_fmt == EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N)) + pxp_mode = PXP_GRAYSCALE_Y4_UPPER; + else + pxp_mode = PXP_GRAYSCALE_Y8; + + /* Configure PxP for this update */ + pxp_cfg.src_addr = src_addr; + pxp_cfg.dst_addr = priv->epdc_mem_phys; + pxp_cfg.src_width = fb_width; + pxp_cfg.src_height = fb_height; + pxp_cfg.src_stride = pitch; + pxp_cfg.dst_stride = priv->epdc_mem_width; + pxp_cfg.rotation = rotation; + pxp_cfg.clip = clip; + + ret = pxp_epdc_process(pxp_dev, &pxp_cfg, pxp_mode); + if (ret == 0) + goto pxp_done; /* PxP succeeded, skip software copy */ + /* PxP failed, fall back to software */ + dev_dbg(priv->drm.dev, "PxP processing failed, using software fallback\n"); + } + + /* Software fallback for pixel processing */ + /* I'm not really certain if this is still needed on some devices */ if ((priv->rev < 30) || (priv->buf_pix_fmt == EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N)) epdc_from_rgb_clear_lower_nibble(clip, vaddr, pitch, (u8 *)priv->epdc_mem_virt, @@ -897,6 +930,7 @@ int mxc_epdc_send_single_update(struct drm_rect *clip, int pitch, void *vaddr, (u8 *)priv->epdc_mem_virt, priv->epdc_mem_width, fb_width, fb_height, rotation); +pxp_done: /* Has EPDC HW been initialized? */ if (!priv->hw_ready) { diff --git a/drivers/gpu/drm/mxc-epdc/epdc_update.h b/drivers/gpu/drm/mxc-epdc/epdc_update.h index bf6bb5af5dc4ef..241f38f85ed230 100644 --- a/drivers/gpu/drm/mxc-epdc/epdc_update.h +++ b/drivers/gpu/drm/mxc-epdc/epdc_update.h @@ -1,8 +1,10 @@ /* SPDX-License-Identifier: GPL-2.0+ */ /* Copyright (C) 2020 Andreas Kemnade */ +#include + int mxc_epdc_send_single_update(struct drm_rect *clip, int pitch, - void *vaddr, - struct mxc_epdc *priv); + dma_addr_t src_addr, void *vaddr, + struct mxc_epdc *priv); void mxc_epdc_draw_mode0(struct mxc_epdc *priv); int mxc_epdc_init_update(struct mxc_epdc *priv); void mxc_epdc_flush_updates(struct mxc_epdc *priv); diff --git a/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c b/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c index f26fd74cebf968..082382d431f7b9 100644 --- a/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c +++ b/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -231,7 +232,7 @@ static void mxc_epdc_pipe_enable(struct drm_simple_display_pipe *pipe, gem = drm_fb_dma_get_gem_obj(plane_state->fb, 0); mxc_epdc_send_single_update(&clip, plane_state->fb->pitches[0], - gem->vaddr, priv); + gem->dma_addr, gem->vaddr, priv); } } @@ -283,7 +284,7 @@ static void mxc_epdc_pipe_update(struct drm_simple_display_pipe *pipe, clip.x1, clip.y1, clip.x2, clip.y2); mxc_epdc_send_single_update(&clip, old_state->fb->pitches[0], - gem->vaddr, priv); + gem->dma_addr, gem->vaddr, priv); } return; diff --git a/drivers/media/platform/nxp/imx-pxp.c b/drivers/media/platform/nxp/imx-pxp.c index 740fcd8fb95287..57534808eac326 100644 --- a/drivers/media/platform/nxp/imx-pxp.c +++ b/drivers/media/platform/nxp/imx-pxp.c @@ -31,6 +31,9 @@ #include #include #include +#include +#include +#include #include "imx-pxp.h" @@ -38,6 +41,11 @@ static unsigned int debug; module_param(debug, uint, 0644); MODULE_PARM_DESC(debug, "activates debug info"); +/* PXP device pointer for EPDC internal API */ +static struct pxp_dev *g_pxp_dev; +static DEFINE_MUTEX(pxp_epdc_mutex); +static struct completion pxp_epdc_completion; + #define MIN_W 8 #define MIN_H 8 #define MAX_W 4096 @@ -1878,6 +1886,9 @@ static int pxp_probe(struct platform_device *pdev) } #endif + g_pxp_dev = dev; + init_completion(&pxp_epdc_completion); + return 0; #ifdef CONFIG_MEDIA_CONTROLLER @@ -1900,6 +1911,10 @@ static void pxp_remove(struct platform_device *pdev) { struct pxp_dev *dev = platform_get_drvdata(pdev); + mutex_lock(&pxp_epdc_mutex); + g_pxp_dev = NULL; + mutex_unlock(&pxp_epdc_mutex); + pxp_write(dev, HW_PXP_CTRL_SET, BM_PXP_CTRL_CLKGATE); pxp_write(dev, HW_PXP_CTRL_SET, BM_PXP_CTRL_SFTRST); @@ -1945,3 +1960,205 @@ module_platform_driver(pxp_driver); MODULE_DESCRIPTION("i.MX PXP mem2mem scaler/CSC/rotator"); MODULE_AUTHOR("Philipp Zabel "); MODULE_LICENSE("GPL"); + +/* EPDC internal API - synchronous PXP processing */ + +static void pxp_epdc_irq_handler(struct pxp_dev *dev) +{ + complete(&pxp_epdc_completion); +} + +/** + * pxp_get_device() - Get the PXP device for EPDC + * + * Returns a pointer to the PXP device for use with pxp_epdc_process(). + */ +struct device *pxp_get_device(void) +{ + if (!g_pxp_dev) + return NULL; + return g_pxp_dev->v4l2_dev.dev; +} +EXPORT_SYMBOL_GPL(pxp_get_device); + +/** + * pxp_epdc_process() - Process a framebuffer region for EPDC + * + * Configures the PXP hardware to read RGB32 pixels from source buffer, + * convert to grayscale using ITU BT.601, apply rotation, and write + * to destination buffer. + */ +int pxp_epdc_process(struct device *dev, const struct pxp_epdc_config *cfg, + enum pxp_grayscale_mode mode) +{ + struct pxp_dev *pxp = g_pxp_dev; + u32 ctrl, out_ctrl, out_buf, out_pitch, out_lrc, out_ps_ulc, out_ps_lrc; + u32 ps_ctrl, ps_buf, ps_ubuf, ps_vbuf, ps_pitch, ps_scale, ps_offset; + u32 as_ulc, as_lrc; + u32 rotation_val; + u32 src_x, src_y, src_w, src_h; + u32 dst_x, dst_y, dst_w, dst_h; + u32 fb_width, fb_height; + int ret; + long timeout; + + if (!pxp) + return -ENODEV; + + mutex_lock(&pxp_epdc_mutex); + + reinit_completion(&pxp_epdc_completion); + + /* Get clip region dimensions */ + src_x = cfg->clip->x1; + src_y = cfg->clip->y1; + src_w = cfg->clip->x2 - cfg->clip->x1; + src_h = cfg->clip->y2 - cfg->clip->y1; + + /* Align dimensions to 8-pixel blocks (PxP requirement for rotation) */ + src_w = ALIGN(src_w, 8); + src_h = ALIGN(src_h, 8); + + /* Full framebuffer dimensions */ + fb_width = cfg->src_width; + fb_height = cfg->src_height; + + /* Convert rotation degrees to PXP rotation value and calculate + * destination position in EPDC memory */ + switch (cfg->rotation) { + case 0: + rotation_val = BV_PXP_CTRL_ROTATE0__ROT_0; + dst_x = src_x; + dst_y = src_y; + dst_w = src_w; + dst_h = src_h; + break; + case 90: + rotation_val = BV_PXP_CTRL_ROTATE0__ROT_90; + dst_x = fb_height - src_y - src_h; + dst_y = src_x; + dst_w = src_h; + dst_h = src_w; + break; + case 180: + rotation_val = BV_PXP_CTRL_ROTATE0__ROT_180; + dst_x = fb_width - src_x - src_w; + dst_y = fb_height - src_y - src_h; + dst_w = src_w; + dst_h = src_h; + break; + case 270: + rotation_val = BV_PXP_CTRL_ROTATE0__ROT_270; + dst_x = src_y; + dst_y = fb_width - src_x - src_w; + dst_w = src_h; + dst_h = src_w; + break; + default: + mutex_unlock(&pxp_epdc_mutex); + return -EINVAL; + } + + /* Reset PXP */ + pxp_write(pxp, HW_PXP_CTRL_SET, BM_PXP_CTRL_SFTRST); + usleep_range(10, 20); + pxp_write(pxp, HW_PXP_CTRL_CLR, BM_PXP_CTRL_SFTRST); + usleep_range(10, 20); + pxp_write(pxp, HW_PXP_CTRL_CLR, BM_PXP_CTRL_CLKGATE); + usleep_range(10, 20); + + /* Control register with rotation */ + ctrl = BF_PXP_CTRL_ROTATE0(rotation_val); + pxp_write(pxp, HW_PXP_CTRL, ctrl); + + /* Output configuration - grayscale format */ + out_ctrl = BV_PXP_OUT_CTRL_FORMAT__Y8; + if (mode == PXP_GRAYSCALE_Y4_UPPER) + out_ctrl = BV_PXP_OUT_CTRL_FORMAT__Y4; + pxp_write(pxp, HW_PXP_OUT_CTRL, out_ctrl); + + /* Output buffer address */ + out_buf = cfg->dst_addr; + pxp_write(pxp, HW_PXP_OUT_BUF, out_buf); + + /* Output pitch and total buffer dimensions */ + out_pitch = BF_PXP_OUT_PITCH_PITCH(cfg->dst_stride); + pxp_write(pxp, HW_PXP_OUT_PITCH, out_pitch); + out_lrc = BF_PXP_OUT_LRC_X(cfg->dst_stride - 1) | + BF_PXP_OUT_LRC_Y((rotation_val == BV_PXP_CTRL_ROTATE0__ROT_90 || + rotation_val == BV_PXP_CTRL_ROTATE0__ROT_270) ? + fb_width - 1 : fb_height - 1); + pxp_write(pxp, HW_PXP_OUT_LRC, out_lrc); + + /* Output PS bounds - where processed region is placed in output */ + out_ps_ulc = BF_PXP_OUT_PS_ULC_X(dst_x) | BF_PXP_OUT_PS_ULC_Y(dst_y); + out_ps_lrc = BF_PXP_OUT_PS_LRC_X(dst_x + dst_w - 1) | + BF_PXP_OUT_PS_LRC_Y(dst_y + dst_h - 1); + pxp_write(pxp, HW_PXP_OUT_PS_ULC, out_ps_ulc); + pxp_write(pxp, HW_PXP_OUT_PS_LRC, out_ps_lrc); + + /* No alpha surface */ + as_ulc = BF_PXP_OUT_AS_ULC_X(1) | BF_PXP_OUT_AS_ULC_Y(1); + as_lrc = BF_PXP_OUT_AS_LRC_X(0) | BF_PXP_OUT_AS_LRC_Y(0); + pxp_write(pxp, HW_PXP_OUT_AS_ULC, as_ulc); + pxp_write(pxp, HW_PXP_OUT_AS_LRC, as_lrc); + + /* Process surface configuration - RGB888 input */ + ps_ctrl = BV_PXP_PS_CTRL_FORMAT__RGB888; + /* Source buffer address with offset for clip region */ + ps_buf = cfg->src_addr + src_y * cfg->src_stride + src_x * 4; + ps_ubuf = 0; + ps_vbuf = 0x8080; /* For grayscale conversion */ + ps_pitch = BF_PXP_PS_PITCH_PITCH(cfg->src_stride); + pxp_write(pxp, HW_PXP_PS_CTRL, ps_ctrl); + pxp_write(pxp, HW_PXP_PS_BUF, ps_buf); + pxp_write(pxp, HW_PXP_PS_UBUF, ps_ubuf); + pxp_write(pxp, HW_PXP_PS_VBUF, ps_vbuf); + pxp_write(pxp, HW_PXP_PS_PITCH, ps_pitch); + + /* No scaling - 1:1, but offset to start from clip origin */ + ps_scale = BF_PXP_PS_SCALE_XSCALE(0x1000) | BF_PXP_PS_SCALE_YSCALE(0x1000); + ps_offset = BF_PXP_PS_OFFSET_XOFFSET(0) | BF_PXP_PS_OFFSET_YOFFSET(0); + pxp_write(pxp, HW_PXP_PS_SCALE, ps_scale); + pxp_write(pxp, HW_PXP_PS_OFFSET, ps_offset); + + /* CSC for RGB to Y conversion - ITU BT.601 coefficients */ + pxp_write(pxp, HW_PXP_CSC1_COEF0, + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x0123) | /* 0.299 * 2^9 */ + BF_PXP_CSC1_COEF0_Y_OFFSET(0)); + pxp_write(pxp, HW_PXP_CSC1_COEF1, + BF_PXP_CSC1_COEF1_C1(0x0198) | /* 0.587 * 2^9 */ + BF_PXP_CSC1_COEF1_C4(0x0040)); /* 0.114 * 2^9 */ + pxp_write(pxp, HW_PXP_CSC1_COEF2, + BF_PXP_CSC1_COEF2_C2(0x0000) | + BF_PXP_CSC1_COEF2_C3(0x0000)); + + /* Enable IRQ */ + pxp_write(pxp, HW_PXP_IRQ_MASK, 0xffff); + + /* Enable PXP and start */ + pxp_write(pxp, HW_PXP_CTRL_SET, + BM_PXP_CTRL_IRQ_ENABLE | + BM_PXP_CTRL_ENABLE | + BM_PXP_CTRL_ENABLE_CSC2 | + BM_PXP_CTRL_ENABLE_ROTATE0 | + BM_PXP_CTRL_ENABLE_PS_AS_OUT); + + /* Wait for completion */ + timeout = wait_for_completion_timeout(&pxp_epdc_completion, + msecs_to_jiffies(1000)); + if (timeout == 0) { + dev_err(pxp->v4l2_dev.dev, "PXP EPDC processing timeout\n"); + ret = -ETIMEDOUT; + goto out; + } + + ret = 0; +out: + /* Disable PXP */ + pxp_write(pxp, HW_PXP_CTRL_CLR, BM_PXP_CTRL_ENABLE); + mutex_unlock(&pxp_epdc_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(pxp_epdc_process); diff --git a/include/media/pxp.h b/include/media/pxp.h new file mode 100644 index 00000000000000..1d48dc4ba176bc --- /dev/null +++ b/include/media/pxp.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * i.MX PXP API for internal kernel consumers + * + * Copyright (c) 2026 Hugo Osvaldo Barrera + */ +#ifndef __MEDIA_PXP_H__ +#define __MEDIA_PXP_H__ + +#include + +struct device; +struct drm_rect; + +/** + * struct pxp_epdc_config - PXP configuration for EPDC processing + * @src_addr: DMA address of source buffer (RGB32 format) + * @dst_addr: DMA address of destination buffer (grayscale) + * @src_width: source buffer width in pixels + * @src_height: source buffer height in pixels + * @src_stride: source buffer stride in bytes + * @dst_stride: destination buffer stride in bytes + * @rotation: rotation angle (0, 90, 180, 270 degrees) + * @clip: rectangle defining the region to process + * @grayscale_mode: grayscale output mode + */ +struct pxp_epdc_config { + dma_addr_t src_addr; + dma_addr_t dst_addr; + u32 src_width; + u32 src_height; + u32 src_stride; + u32 dst_stride; + unsigned int rotation; + const struct drm_rect *clip; +}; + +/** + * enum pxp_grayscale_mode - PXP grayscale output format + * @PXP_GRAYSCALE_Y8: 8-bit grayscale (full range) + * @PXP_GRAYSCALE_Y4_UPPER: 4-bit grayscale in upper nibble (for EPDC P4N) + * @PXP_GRAYSCALE_Y5_SHIFTED: 5-bit grayscale shifted (for EPDC P5N) + */ +enum pxp_grayscale_mode { + PXP_GRAYSCALE_Y8, + PXP_GRAYSCALE_Y4_UPPER, + PXP_GRAYSCALE_Y5_SHIFTED, +}; + +/** + * pxp_epdc_process() - Process a framebuffer region for EPDC + * @dev: PXP device pointer (obtained via pxp_get_device()) + * @cfg: configuration describing the processing operation + * @mode: grayscale output format mode + * + * This function configures the PXP hardware to: + * - Read RGB32 pixels from source buffer + * - Convert to grayscale using ITU BT.601 + * - Apply rotation transformation + * - Write to destination buffer in specified grayscale format + * + * The operation is synchronous and blocks until completion. + * + * Return: 0 on success, negative error code on failure + */ +#if IS_ENABLED(CONFIG_VIDEO_IMX_PXP) + +int pxp_epdc_process(struct device *dev, const struct pxp_epdc_config *cfg, + enum pxp_grayscale_mode mode); + +/** + * pxp_get_device() - Get the PXP device for EPDC + * + * Returns a pointer to the PXP device for use with pxp_epdc_process(). + * The device is obtained from the platform driver. + * + * Return: pointer to PXP device, or NULL if not available + */ +struct device *pxp_get_device(void); + +#else + +static inline int pxp_epdc_process(struct device *dev, + const struct pxp_epdc_config *cfg, + enum pxp_grayscale_mode mode) +{ + return -ENODEV; +} + +static inline struct device *pxp_get_device(void) +{ + return NULL; +} + +#endif /* CONFIG_VIDEO_IMX_PXP */ + +#endif /* __MEDIA_PXP_H__ */ From 975639c114550bb2b55daf4c003031661f04b4f9 Mon Sep 17 00:00:00 2001 From: Hugo Osvaldo Barrera Date: Sat, 23 May 2026 00:26:22 +0200 Subject: [PATCH 3/3] drm_fb_helper: delegate rotation to DRM Signed-off-by: Hugo Osvaldo Barrera --- drivers/gpu/drm/drm_client_modeset.c | 8 ++++++++ drivers/gpu/drm/drm_fb_helper.c | 3 +++ include/drm/drm_client.h | 8 ++++++++ 3 files changed, 19 insertions(+) diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c index bb49b8361271a3..cf3aad3cba2143 100644 --- a/drivers/gpu/drm/drm_client_modeset.c +++ b/drivers/gpu/drm/drm_client_modeset.c @@ -1082,6 +1082,14 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool plane_state->rotation = rotation; } + /* Apply fbdev rotation override if set */ + if (client->fbdev_rotation) { + struct drm_plane_state *plane_state; + + plane_state = drm_atomic_get_new_plane_state(state, primary); + plane_state->rotation = client->fbdev_rotation; + } + ret = __drm_atomic_helper_set_config(mode_set, state); if (ret != 0) goto out_state; diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 05803169bed571..5fc1075dc8f977 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -1176,6 +1176,9 @@ int drm_fb_helper_set_par(struct fb_info *info) */ force = var->activate & FB_ACTIVATE_KD_TEXT; + /* Translate fbdev rotation to DRM rotation property */ + fb_helper->client.fbdev_rotation = 1 << var->rotate; + __drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper, force); return 0; diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h index c972a8a3385b3b..41b0d6e5fbcc22 100644 --- a/include/drm/drm_client.h +++ b/include/drm/drm_client.h @@ -159,6 +159,14 @@ struct drm_client_dev { * before. It is usually not tried again. */ bool hotplug_failed; + + /** + * @fbdev_rotation: + * + * Rotation value set by fbdev emulation, to be applied to plane state. + * Value is in DRM rotation format (DRM_MODE_ROTATE_*). + */ + unsigned int fbdev_rotation; }; int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,