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/drivers/gpu/drm/mxc-epdc/epdc_update.c b/drivers/gpu/drm/mxc-epdc/epdc_update.c index af5a73d33683e2..7fffb5beea5327 100644 --- a/drivers/gpu/drm/mxc-epdc/epdc_update.c +++ b/drivers/gpu/drm/mxc-epdc/epdc_update.c @@ -10,6 +10,9 @@ #include #include #include +#include +#include +#include #include #include "mxc_epdc.h" #include "epdc_hw.h" @@ -335,17 +338,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 +381,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++; } } @@ -843,19 +870,67 @@ 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; + 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) { + 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; + } + + /* 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, - 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); +pxp_done: /* Has EPDC HW been initialized? */ if (!priv->hw_ready) { @@ -885,6 +960,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/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.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..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 @@ -29,6 +30,7 @@ #include #include #include +#include #include "mxc_epdc.h" #include "epdc_hw.h" #include "epdc_update.h" @@ -189,10 +191,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); @@ -214,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); } } @@ -266,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; @@ -354,6 +372,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); 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/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, 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__ */