| /* |
| * rcar_du_plane.c -- R-Car Display Unit Planes |
| * |
| * Copyright (C) 2013 Renesas Corporation |
| * |
| * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| */ |
| |
| #include <drm/drmP.h> |
| #include <drm/drm_crtc.h> |
| #include <drm/drm_crtc_helper.h> |
| #include <drm/drm_fb_cma_helper.h> |
| #include <drm/drm_gem_cma_helper.h> |
| |
| #include "rcar_du_drv.h" |
| #include "rcar_du_kms.h" |
| #include "rcar_du_plane.h" |
| #include "rcar_du_regs.h" |
| |
| #define RCAR_DU_COLORKEY_NONE (0 << 24) |
| #define RCAR_DU_COLORKEY_SOURCE (1 << 24) |
| #define RCAR_DU_COLORKEY_MASK (1 << 24) |
| |
| struct rcar_du_kms_plane { |
| struct drm_plane plane; |
| struct rcar_du_plane *hwplane; |
| }; |
| |
| static inline struct rcar_du_plane *to_rcar_plane(struct drm_plane *plane) |
| { |
| return container_of(plane, struct rcar_du_kms_plane, plane)->hwplane; |
| } |
| |
| static u32 rcar_du_plane_read(struct rcar_du_device *rcdu, |
| unsigned int index, u32 reg) |
| { |
| return rcar_du_read(rcdu, index * PLANE_OFF + reg); |
| } |
| |
| static void rcar_du_plane_write(struct rcar_du_device *rcdu, |
| unsigned int index, u32 reg, u32 data) |
| { |
| rcar_du_write(rcdu, index * PLANE_OFF + reg, data); |
| } |
| |
| int rcar_du_plane_reserve(struct rcar_du_plane *plane, |
| const struct rcar_du_format_info *format) |
| { |
| struct rcar_du_device *rcdu = plane->dev; |
| unsigned int i; |
| int ret = -EBUSY; |
| |
| mutex_lock(&rcdu->planes.lock); |
| |
| for (i = 0; i < ARRAY_SIZE(rcdu->planes.planes); ++i) { |
| if (!(rcdu->planes.free & (1 << i))) |
| continue; |
| |
| if (format->planes == 1 || |
| rcdu->planes.free & (1 << ((i + 1) % 8))) |
| break; |
| } |
| |
| if (i == ARRAY_SIZE(rcdu->planes.planes)) |
| goto done; |
| |
| rcdu->planes.free &= ~(1 << i); |
| if (format->planes == 2) |
| rcdu->planes.free &= ~(1 << ((i + 1) % 8)); |
| |
| plane->hwindex = i; |
| |
| ret = 0; |
| |
| done: |
| mutex_unlock(&rcdu->planes.lock); |
| return ret; |
| } |
| |
| void rcar_du_plane_release(struct rcar_du_plane *plane) |
| { |
| struct rcar_du_device *rcdu = plane->dev; |
| |
| if (plane->hwindex == -1) |
| return; |
| |
| mutex_lock(&rcdu->planes.lock); |
| rcdu->planes.free |= 1 << plane->hwindex; |
| if (plane->format->planes == 2) |
| rcdu->planes.free |= 1 << ((plane->hwindex + 1) % 8); |
| mutex_unlock(&rcdu->planes.lock); |
| |
| plane->hwindex = -1; |
| } |
| |
| void rcar_du_plane_update_base(struct rcar_du_plane *plane) |
| { |
| struct rcar_du_device *rcdu = plane->dev; |
| unsigned int index = plane->hwindex; |
| |
| /* The Y position is expressed in raster line units and must be doubled |
| * for 32bpp formats, according to the R8A7790 datasheet. No mention of |
| * doubling the Y position is found in the R8A7779 datasheet, but the |
| * rule seems to apply there as well. |
| * |
| * Similarly, for the second plane, NV12 and NV21 formats seem to |
| * require a halved Y position value. |
| */ |
| rcar_du_plane_write(rcdu, index, PnSPXR, plane->src_x); |
| rcar_du_plane_write(rcdu, index, PnSPYR, plane->src_y * |
| (plane->format->bpp == 32 ? 2 : 1)); |
| rcar_du_plane_write(rcdu, index, PnDSA0R, plane->dma[0]); |
| |
| if (plane->format->planes == 2) { |
| index = (index + 1) % 8; |
| |
| rcar_du_plane_write(rcdu, index, PnSPXR, plane->src_x); |
| rcar_du_plane_write(rcdu, index, PnSPYR, plane->src_y * |
| (plane->format->bpp == 16 ? 2 : 1) / 2); |
| rcar_du_plane_write(rcdu, index, PnDSA0R, plane->dma[1]); |
| } |
| } |
| |
| void rcar_du_plane_compute_base(struct rcar_du_plane *plane, |
| struct drm_framebuffer *fb) |
| { |
| struct drm_gem_cma_object *gem; |
| |
| gem = drm_fb_cma_get_gem_obj(fb, 0); |
| plane->dma[0] = gem->paddr + fb->offsets[0]; |
| |
| if (plane->format->planes == 2) { |
| gem = drm_fb_cma_get_gem_obj(fb, 1); |
| plane->dma[1] = gem->paddr + fb->offsets[1]; |
| } |
| } |
| |
| static void rcar_du_plane_setup_mode(struct rcar_du_plane *plane, |
| unsigned int index) |
| { |
| struct rcar_du_device *rcdu = plane->dev; |
| u32 colorkey; |
| u32 pnmr; |
| |
| /* The PnALPHAR register controls alpha-blending in 16bpp formats |
| * (ARGB1555 and XRGB1555). |
| * |
| * For ARGB, set the alpha value to 0, and enable alpha-blending when |
| * the A bit is 0. This maps A=0 to alpha=0 and A=1 to alpha=255. |
| * |
| * For XRGB, set the alpha value to the plane-wide alpha value and |
| * enable alpha-blending regardless of the X bit value. |
| */ |
| if (plane->format->fourcc != DRM_FORMAT_XRGB1555) |
| rcar_du_plane_write(rcdu, index, PnALPHAR, PnALPHAR_ABIT_0); |
| else |
| rcar_du_plane_write(rcdu, index, PnALPHAR, |
| PnALPHAR_ABIT_X | plane->alpha); |
| |
| pnmr = PnMR_BM_MD | plane->format->pnmr; |
| |
| /* Disable color keying when requested. YUV formats have the |
| * PnMR_SPIM_TP_OFF bit set in their pnmr field, disabling color keying |
| * automatically. |
| */ |
| if ((plane->colorkey & RCAR_DU_COLORKEY_MASK) == RCAR_DU_COLORKEY_NONE) |
| pnmr |= PnMR_SPIM_TP_OFF; |
| |
| /* For packed YUV formats we need to select the U/V order. */ |
| if (plane->format->fourcc == DRM_FORMAT_YUYV) |
| pnmr |= PnMR_YCDF_YUYV; |
| |
| rcar_du_plane_write(rcdu, index, PnMR, pnmr); |
| |
| switch (plane->format->fourcc) { |
| case DRM_FORMAT_RGB565: |
| colorkey = ((plane->colorkey & 0xf80000) >> 8) |
| | ((plane->colorkey & 0x00fc00) >> 5) |
| | ((plane->colorkey & 0x0000f8) >> 3); |
| rcar_du_plane_write(rcdu, index, PnTC2R, colorkey); |
| break; |
| |
| case DRM_FORMAT_ARGB1555: |
| case DRM_FORMAT_XRGB1555: |
| colorkey = ((plane->colorkey & 0xf80000) >> 9) |
| | ((plane->colorkey & 0x00f800) >> 6) |
| | ((plane->colorkey & 0x0000f8) >> 3); |
| rcar_du_plane_write(rcdu, index, PnTC2R, colorkey); |
| break; |
| |
| case DRM_FORMAT_XRGB8888: |
| case DRM_FORMAT_ARGB8888: |
| rcar_du_plane_write(rcdu, index, PnTC3R, |
| PnTC3R_CODE | (plane->colorkey & 0xffffff)); |
| break; |
| } |
| } |
| |
| static void __rcar_du_plane_setup(struct rcar_du_plane *plane, |
| unsigned int index) |
| { |
| struct rcar_du_device *rcdu = plane->dev; |
| u32 ddcr2 = PnDDCR2_CODE; |
| u32 ddcr4; |
| u32 mwr; |
| |
| /* Data format |
| * |
| * The data format is selected by the DDDF field in PnMR and the EDF |
| * field in DDCR4. |
| */ |
| ddcr4 = rcar_du_plane_read(rcdu, index, PnDDCR4); |
| ddcr4 &= ~PnDDCR4_EDF_MASK; |
| ddcr4 |= plane->format->edf | PnDDCR4_CODE; |
| |
| rcar_du_plane_setup_mode(plane, index); |
| |
| if (plane->format->planes == 2) { |
| if (plane->hwindex != index) { |
| if (plane->format->fourcc == DRM_FORMAT_NV12 || |
| plane->format->fourcc == DRM_FORMAT_NV21) |
| ddcr2 |= PnDDCR2_Y420; |
| |
| if (plane->format->fourcc == DRM_FORMAT_NV21) |
| ddcr2 |= PnDDCR2_NV21; |
| |
| ddcr2 |= PnDDCR2_DIVU; |
| } else { |
| ddcr2 |= PnDDCR2_DIVY; |
| } |
| } |
| |
| rcar_du_plane_write(rcdu, index, PnDDCR2, ddcr2); |
| rcar_du_plane_write(rcdu, index, PnDDCR4, ddcr4); |
| |
| /* Memory pitch (expressed in pixels) */ |
| if (plane->format->planes == 2) |
| mwr = plane->pitch; |
| else |
| mwr = plane->pitch * 8 / plane->format->bpp; |
| |
| rcar_du_plane_write(rcdu, index, PnMWR, mwr); |
| |
| /* Destination position and size */ |
| rcar_du_plane_write(rcdu, index, PnDSXR, plane->width); |
| rcar_du_plane_write(rcdu, index, PnDSYR, plane->height); |
| rcar_du_plane_write(rcdu, index, PnDPXR, plane->dst_x); |
| rcar_du_plane_write(rcdu, index, PnDPYR, plane->dst_y); |
| |
| /* Wrap-around and blinking, disabled */ |
| rcar_du_plane_write(rcdu, index, PnWASPR, 0); |
| rcar_du_plane_write(rcdu, index, PnWAMWR, 4095); |
| rcar_du_plane_write(rcdu, index, PnBTR, 0); |
| rcar_du_plane_write(rcdu, index, PnMLR, 0); |
| } |
| |
| void rcar_du_plane_setup(struct rcar_du_plane *plane) |
| { |
| __rcar_du_plane_setup(plane, plane->hwindex); |
| if (plane->format->planes == 2) |
| __rcar_du_plane_setup(plane, (plane->hwindex + 1) % 8); |
| |
| rcar_du_plane_update_base(plane); |
| } |
| |
| static int |
| rcar_du_plane_update(struct drm_plane *plane, struct drm_crtc *crtc, |
| struct drm_framebuffer *fb, int crtc_x, int crtc_y, |
| unsigned int crtc_w, unsigned int crtc_h, |
| uint32_t src_x, uint32_t src_y, |
| uint32_t src_w, uint32_t src_h) |
| { |
| struct rcar_du_plane *rplane = to_rcar_plane(plane); |
| struct rcar_du_device *rcdu = plane->dev->dev_private; |
| const struct rcar_du_format_info *format; |
| unsigned int nplanes; |
| int ret; |
| |
| format = rcar_du_format_info(fb->pixel_format); |
| if (format == NULL) { |
| dev_dbg(rcdu->dev, "%s: unsupported format %08x\n", __func__, |
| fb->pixel_format); |
| return -EINVAL; |
| } |
| |
| if (src_w >> 16 != crtc_w || src_h >> 16 != crtc_h) { |
| dev_dbg(rcdu->dev, "%s: scaling not supported\n", __func__); |
| return -EINVAL; |
| } |
| |
| nplanes = rplane->format ? rplane->format->planes : 0; |
| |
| /* Reallocate hardware planes if the number of required planes has |
| * changed. |
| */ |
| if (format->planes != nplanes) { |
| rcar_du_plane_release(rplane); |
| ret = rcar_du_plane_reserve(rplane, format); |
| if (ret < 0) |
| return ret; |
| } |
| |
| rplane->crtc = crtc; |
| rplane->format = format; |
| rplane->pitch = fb->pitches[0]; |
| |
| rplane->src_x = src_x >> 16; |
| rplane->src_y = src_y >> 16; |
| rplane->dst_x = crtc_x; |
| rplane->dst_y = crtc_y; |
| rplane->width = crtc_w; |
| rplane->height = crtc_h; |
| |
| rcar_du_plane_compute_base(rplane, fb); |
| rcar_du_plane_setup(rplane); |
| |
| mutex_lock(&rcdu->planes.lock); |
| rplane->enabled = true; |
| rcar_du_crtc_update_planes(rplane->crtc); |
| mutex_unlock(&rcdu->planes.lock); |
| |
| return 0; |
| } |
| |
| static int rcar_du_plane_disable(struct drm_plane *plane) |
| { |
| struct rcar_du_device *rcdu = plane->dev->dev_private; |
| struct rcar_du_plane *rplane = to_rcar_plane(plane); |
| |
| if (!rplane->enabled) |
| return 0; |
| |
| mutex_lock(&rcdu->planes.lock); |
| rplane->enabled = false; |
| rcar_du_crtc_update_planes(rplane->crtc); |
| mutex_unlock(&rcdu->planes.lock); |
| |
| rcar_du_plane_release(rplane); |
| |
| rplane->crtc = NULL; |
| rplane->format = NULL; |
| |
| return 0; |
| } |
| |
| /* Both the .set_property and the .update_plane operations are called with the |
| * mode_config lock held. There is this no need to explicitly protect access to |
| * the alpha and colorkey fields and the mode register. |
| */ |
| static void rcar_du_plane_set_alpha(struct rcar_du_plane *plane, u32 alpha) |
| { |
| if (plane->alpha == alpha) |
| return; |
| |
| plane->alpha = alpha; |
| if (!plane->enabled || plane->format->fourcc != DRM_FORMAT_XRGB1555) |
| return; |
| |
| rcar_du_plane_setup_mode(plane, plane->hwindex); |
| } |
| |
| static void rcar_du_plane_set_colorkey(struct rcar_du_plane *plane, |
| u32 colorkey) |
| { |
| if (plane->colorkey == colorkey) |
| return; |
| |
| plane->colorkey = colorkey; |
| if (!plane->enabled) |
| return; |
| |
| rcar_du_plane_setup_mode(plane, plane->hwindex); |
| } |
| |
| static void rcar_du_plane_set_zpos(struct rcar_du_plane *plane, |
| unsigned int zpos) |
| { |
| struct rcar_du_device *rcdu = plane->dev; |
| |
| mutex_lock(&rcdu->planes.lock); |
| if (plane->zpos == zpos) |
| goto done; |
| |
| plane->zpos = zpos; |
| if (!plane->enabled) |
| goto done; |
| |
| rcar_du_crtc_update_planes(plane->crtc); |
| |
| done: |
| mutex_unlock(&rcdu->planes.lock); |
| } |
| |
| static int rcar_du_plane_set_property(struct drm_plane *plane, |
| struct drm_property *property, |
| uint64_t value) |
| { |
| struct rcar_du_device *rcdu = plane->dev->dev_private; |
| struct rcar_du_plane *rplane = to_rcar_plane(plane); |
| |
| if (property == rcdu->planes.alpha) |
| rcar_du_plane_set_alpha(rplane, value); |
| else if (property == rcdu->planes.colorkey) |
| rcar_du_plane_set_colorkey(rplane, value); |
| else if (property == rcdu->planes.zpos) |
| rcar_du_plane_set_zpos(rplane, value); |
| else |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static const struct drm_plane_funcs rcar_du_plane_funcs = { |
| .update_plane = rcar_du_plane_update, |
| .disable_plane = rcar_du_plane_disable, |
| .set_property = rcar_du_plane_set_property, |
| .destroy = drm_plane_cleanup, |
| }; |
| |
| static const uint32_t formats[] = { |
| DRM_FORMAT_RGB565, |
| DRM_FORMAT_ARGB1555, |
| DRM_FORMAT_XRGB1555, |
| DRM_FORMAT_XRGB8888, |
| DRM_FORMAT_ARGB8888, |
| DRM_FORMAT_UYVY, |
| DRM_FORMAT_YUYV, |
| DRM_FORMAT_NV12, |
| DRM_FORMAT_NV21, |
| DRM_FORMAT_NV16, |
| }; |
| |
| int rcar_du_plane_init(struct rcar_du_device *rcdu) |
| { |
| unsigned int i; |
| |
| mutex_init(&rcdu->planes.lock); |
| rcdu->planes.free = 0xff; |
| |
| rcdu->planes.alpha = |
| drm_property_create_range(rcdu->ddev, 0, "alpha", 0, 255); |
| if (rcdu->planes.alpha == NULL) |
| return -ENOMEM; |
| |
| /* The color key is expressed as an RGB888 triplet stored in a 32-bit |
| * integer in XRGB8888 format. Bit 24 is used as a flag to disable (0) |
| * or enable source color keying (1). |
| */ |
| rcdu->planes.colorkey = |
| drm_property_create_range(rcdu->ddev, 0, "colorkey", |
| 0, 0x01ffffff); |
| if (rcdu->planes.colorkey == NULL) |
| return -ENOMEM; |
| |
| rcdu->planes.zpos = |
| drm_property_create_range(rcdu->ddev, 0, "zpos", 1, 7); |
| if (rcdu->planes.zpos == NULL) |
| return -ENOMEM; |
| |
| for (i = 0; i < ARRAY_SIZE(rcdu->planes.planes); ++i) { |
| struct rcar_du_plane *plane = &rcdu->planes.planes[i]; |
| |
| plane->dev = rcdu; |
| plane->hwindex = -1; |
| plane->alpha = 255; |
| plane->colorkey = RCAR_DU_COLORKEY_NONE; |
| plane->zpos = 0; |
| } |
| |
| return 0; |
| } |
| |
| int rcar_du_plane_register(struct rcar_du_device *rcdu) |
| { |
| unsigned int i; |
| int ret; |
| |
| for (i = 0; i < RCAR_DU_NUM_KMS_PLANES; ++i) { |
| struct rcar_du_kms_plane *plane; |
| |
| plane = devm_kzalloc(rcdu->dev, sizeof(*plane), GFP_KERNEL); |
| if (plane == NULL) |
| return -ENOMEM; |
| |
| plane->hwplane = &rcdu->planes.planes[i + 2]; |
| plane->hwplane->zpos = 1; |
| |
| ret = drm_plane_init(rcdu->ddev, &plane->plane, |
| (1 << rcdu->num_crtcs) - 1, |
| &rcar_du_plane_funcs, formats, |
| ARRAY_SIZE(formats), false); |
| if (ret < 0) |
| return ret; |
| |
| drm_object_attach_property(&plane->plane.base, |
| rcdu->planes.alpha, 255); |
| drm_object_attach_property(&plane->plane.base, |
| rcdu->planes.colorkey, |
| RCAR_DU_COLORKEY_NONE); |
| drm_object_attach_property(&plane->plane.base, |
| rcdu->planes.zpos, 1); |
| } |
| |
| return 0; |
| } |