mirror of
https://github.com/torvalds/linux.git
synced 2024-12-29 06:12:08 +00:00
1a396789f6
The Atmel HLCDC (HLCD Controller) IP available on some Atmel SoCs (i.e. at91sam9n12, at91sam9x5 family or sama5d3 family) provides a display controller device. This display controller supports at least one primary plane and might provide several overlays and an hardware cursor depending on the IP version. At the moment, this driver only implements an RGB connector to interface with LCD panels, but support for other kind of external devices might be added later. Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com> Reviewed-by: Rob Clark <robdclark@gmail.com> Tested-by: Anthony Harivel <anthony.harivel@emtrion.de> Tested-by: Ludovic Desroches <ludovic.desroches@atmel.com> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>
857 lines
21 KiB
C
857 lines
21 KiB
C
/*
|
|
* Copyright (C) 2014 Free Electrons
|
|
* Copyright (C) 2014 Atmel
|
|
*
|
|
* Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published by
|
|
* the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "atmel_hlcdc_dc.h"
|
|
|
|
#define SUBPIXEL_MASK 0xffff
|
|
|
|
static uint32_t rgb_formats[] = {
|
|
DRM_FORMAT_XRGB4444,
|
|
DRM_FORMAT_ARGB4444,
|
|
DRM_FORMAT_RGBA4444,
|
|
DRM_FORMAT_ARGB1555,
|
|
DRM_FORMAT_RGB565,
|
|
DRM_FORMAT_RGB888,
|
|
DRM_FORMAT_XRGB8888,
|
|
DRM_FORMAT_ARGB8888,
|
|
DRM_FORMAT_RGBA8888,
|
|
};
|
|
|
|
struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats = {
|
|
.formats = rgb_formats,
|
|
.nformats = ARRAY_SIZE(rgb_formats),
|
|
};
|
|
|
|
static uint32_t rgb_and_yuv_formats[] = {
|
|
DRM_FORMAT_XRGB4444,
|
|
DRM_FORMAT_ARGB4444,
|
|
DRM_FORMAT_RGBA4444,
|
|
DRM_FORMAT_ARGB1555,
|
|
DRM_FORMAT_RGB565,
|
|
DRM_FORMAT_RGB888,
|
|
DRM_FORMAT_XRGB8888,
|
|
DRM_FORMAT_ARGB8888,
|
|
DRM_FORMAT_RGBA8888,
|
|
DRM_FORMAT_AYUV,
|
|
DRM_FORMAT_YUYV,
|
|
DRM_FORMAT_UYVY,
|
|
DRM_FORMAT_YVYU,
|
|
DRM_FORMAT_VYUY,
|
|
DRM_FORMAT_NV21,
|
|
DRM_FORMAT_NV61,
|
|
DRM_FORMAT_YUV422,
|
|
DRM_FORMAT_YUV420,
|
|
};
|
|
|
|
struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_and_yuv_formats = {
|
|
.formats = rgb_and_yuv_formats,
|
|
.nformats = ARRAY_SIZE(rgb_and_yuv_formats),
|
|
};
|
|
|
|
static int atmel_hlcdc_format_to_plane_mode(u32 format, u32 *mode)
|
|
{
|
|
switch (format) {
|
|
case DRM_FORMAT_XRGB4444:
|
|
*mode = ATMEL_HLCDC_XRGB4444_MODE;
|
|
break;
|
|
case DRM_FORMAT_ARGB4444:
|
|
*mode = ATMEL_HLCDC_ARGB4444_MODE;
|
|
break;
|
|
case DRM_FORMAT_RGBA4444:
|
|
*mode = ATMEL_HLCDC_RGBA4444_MODE;
|
|
break;
|
|
case DRM_FORMAT_RGB565:
|
|
*mode = ATMEL_HLCDC_RGB565_MODE;
|
|
break;
|
|
case DRM_FORMAT_RGB888:
|
|
*mode = ATMEL_HLCDC_RGB888_MODE;
|
|
break;
|
|
case DRM_FORMAT_ARGB1555:
|
|
*mode = ATMEL_HLCDC_ARGB1555_MODE;
|
|
break;
|
|
case DRM_FORMAT_XRGB8888:
|
|
*mode = ATMEL_HLCDC_XRGB8888_MODE;
|
|
break;
|
|
case DRM_FORMAT_ARGB8888:
|
|
*mode = ATMEL_HLCDC_ARGB8888_MODE;
|
|
break;
|
|
case DRM_FORMAT_RGBA8888:
|
|
*mode = ATMEL_HLCDC_RGBA8888_MODE;
|
|
break;
|
|
case DRM_FORMAT_AYUV:
|
|
*mode = ATMEL_HLCDC_AYUV_MODE;
|
|
break;
|
|
case DRM_FORMAT_YUYV:
|
|
*mode = ATMEL_HLCDC_YUYV_MODE;
|
|
break;
|
|
case DRM_FORMAT_UYVY:
|
|
*mode = ATMEL_HLCDC_UYVY_MODE;
|
|
break;
|
|
case DRM_FORMAT_YVYU:
|
|
*mode = ATMEL_HLCDC_YVYU_MODE;
|
|
break;
|
|
case DRM_FORMAT_VYUY:
|
|
*mode = ATMEL_HLCDC_VYUY_MODE;
|
|
break;
|
|
case DRM_FORMAT_NV21:
|
|
*mode = ATMEL_HLCDC_NV21_MODE;
|
|
break;
|
|
case DRM_FORMAT_NV61:
|
|
*mode = ATMEL_HLCDC_NV61_MODE;
|
|
break;
|
|
case DRM_FORMAT_YUV420:
|
|
*mode = ATMEL_HLCDC_YUV420_MODE;
|
|
break;
|
|
case DRM_FORMAT_YUV422:
|
|
*mode = ATMEL_HLCDC_YUV422_MODE;
|
|
break;
|
|
default:
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool atmel_hlcdc_format_embedds_alpha(u32 format)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < sizeof(format); i++) {
|
|
char tmp = (format >> (8 * i)) & 0xff;
|
|
|
|
if (tmp == 'A')
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static u32 heo_downscaling_xcoef[] = {
|
|
0x11343311,
|
|
0x000000f7,
|
|
0x1635300c,
|
|
0x000000f9,
|
|
0x1b362c08,
|
|
0x000000fb,
|
|
0x1f372804,
|
|
0x000000fe,
|
|
0x24382400,
|
|
0x00000000,
|
|
0x28371ffe,
|
|
0x00000004,
|
|
0x2c361bfb,
|
|
0x00000008,
|
|
0x303516f9,
|
|
0x0000000c,
|
|
};
|
|
|
|
static u32 heo_downscaling_ycoef[] = {
|
|
0x00123737,
|
|
0x00173732,
|
|
0x001b382d,
|
|
0x001f3928,
|
|
0x00243824,
|
|
0x0028391f,
|
|
0x002d381b,
|
|
0x00323717,
|
|
};
|
|
|
|
static u32 heo_upscaling_xcoef[] = {
|
|
0xf74949f7,
|
|
0x00000000,
|
|
0xf55f33fb,
|
|
0x000000fe,
|
|
0xf5701efe,
|
|
0x000000ff,
|
|
0xf87c0dff,
|
|
0x00000000,
|
|
0x00800000,
|
|
0x00000000,
|
|
0x0d7cf800,
|
|
0x000000ff,
|
|
0x1e70f5ff,
|
|
0x000000fe,
|
|
0x335ff5fe,
|
|
0x000000fb,
|
|
};
|
|
|
|
static u32 heo_upscaling_ycoef[] = {
|
|
0x00004040,
|
|
0x00075920,
|
|
0x00056f0c,
|
|
0x00027b03,
|
|
0x00008000,
|
|
0x00037b02,
|
|
0x000c6f05,
|
|
0x00205907,
|
|
};
|
|
|
|
static void
|
|
atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane,
|
|
struct atmel_hlcdc_plane_update_req *req)
|
|
{
|
|
const struct atmel_hlcdc_layer_cfg_layout *layout =
|
|
&plane->layer.desc->layout;
|
|
|
|
if (layout->size)
|
|
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
|
layout->size,
|
|
0xffffffff,
|
|
(req->crtc_w - 1) |
|
|
((req->crtc_h - 1) << 16));
|
|
|
|
if (layout->memsize)
|
|
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
|
layout->memsize,
|
|
0xffffffff,
|
|
(req->src_w - 1) |
|
|
((req->src_h - 1) << 16));
|
|
|
|
if (layout->pos)
|
|
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
|
layout->pos,
|
|
0xffffffff,
|
|
req->crtc_x |
|
|
(req->crtc_y << 16));
|
|
|
|
/* TODO: rework the rescaling part */
|
|
if (req->crtc_w != req->src_w || req->crtc_h != req->src_h) {
|
|
u32 factor_reg = 0;
|
|
|
|
if (req->crtc_w != req->src_w) {
|
|
int i;
|
|
u32 factor;
|
|
u32 *coeff_tab = heo_upscaling_xcoef;
|
|
u32 max_memsize;
|
|
|
|
if (req->crtc_w < req->src_w)
|
|
coeff_tab = heo_downscaling_xcoef;
|
|
for (i = 0; i < ARRAY_SIZE(heo_upscaling_xcoef); i++)
|
|
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
|
17 + i,
|
|
0xffffffff,
|
|
coeff_tab[i]);
|
|
factor = ((8 * 256 * req->src_w) - (256 * 4)) /
|
|
req->crtc_w;
|
|
factor++;
|
|
max_memsize = ((factor * req->crtc_w) + (256 * 4)) /
|
|
2048;
|
|
if (max_memsize > req->src_w)
|
|
factor--;
|
|
factor_reg |= factor | 0x80000000;
|
|
}
|
|
|
|
if (req->crtc_h != req->src_h) {
|
|
int i;
|
|
u32 factor;
|
|
u32 *coeff_tab = heo_upscaling_ycoef;
|
|
u32 max_memsize;
|
|
|
|
if (req->crtc_w < req->src_w)
|
|
coeff_tab = heo_downscaling_ycoef;
|
|
for (i = 0; i < ARRAY_SIZE(heo_upscaling_ycoef); i++)
|
|
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
|
33 + i,
|
|
0xffffffff,
|
|
coeff_tab[i]);
|
|
factor = ((8 * 256 * req->src_w) - (256 * 4)) /
|
|
req->crtc_w;
|
|
factor++;
|
|
max_memsize = ((factor * req->crtc_w) + (256 * 4)) /
|
|
2048;
|
|
if (max_memsize > req->src_w)
|
|
factor--;
|
|
factor_reg |= (factor << 16) | 0x80000000;
|
|
}
|
|
|
|
atmel_hlcdc_layer_update_cfg(&plane->layer, 13, 0xffffffff,
|
|
factor_reg);
|
|
}
|
|
}
|
|
|
|
static void
|
|
atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane,
|
|
struct atmel_hlcdc_plane_update_req *req)
|
|
{
|
|
const struct atmel_hlcdc_layer_cfg_layout *layout =
|
|
&plane->layer.desc->layout;
|
|
unsigned int cfg = ATMEL_HLCDC_LAYER_DMA;
|
|
|
|
if (plane->base.type != DRM_PLANE_TYPE_PRIMARY) {
|
|
cfg |= ATMEL_HLCDC_LAYER_OVR | ATMEL_HLCDC_LAYER_ITER2BL |
|
|
ATMEL_HLCDC_LAYER_ITER;
|
|
|
|
if (atmel_hlcdc_format_embedds_alpha(req->fb->pixel_format))
|
|
cfg |= ATMEL_HLCDC_LAYER_LAEN;
|
|
else
|
|
cfg |= ATMEL_HLCDC_LAYER_GAEN;
|
|
}
|
|
|
|
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
|
ATMEL_HLCDC_LAYER_DMA_CFG_ID,
|
|
ATMEL_HLCDC_LAYER_DMA_BLEN_MASK,
|
|
ATMEL_HLCDC_LAYER_DMA_BLEN_INCR16);
|
|
|
|
atmel_hlcdc_layer_update_cfg(&plane->layer, layout->general_config,
|
|
ATMEL_HLCDC_LAYER_ITER2BL |
|
|
ATMEL_HLCDC_LAYER_ITER |
|
|
ATMEL_HLCDC_LAYER_GAEN |
|
|
ATMEL_HLCDC_LAYER_LAEN |
|
|
ATMEL_HLCDC_LAYER_OVR |
|
|
ATMEL_HLCDC_LAYER_DMA, cfg);
|
|
}
|
|
|
|
static void atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane,
|
|
struct atmel_hlcdc_plane_update_req *req)
|
|
{
|
|
u32 cfg;
|
|
int ret;
|
|
|
|
ret = atmel_hlcdc_format_to_plane_mode(req->fb->pixel_format, &cfg);
|
|
if (ret)
|
|
return;
|
|
|
|
if ((req->fb->pixel_format == DRM_FORMAT_YUV422 ||
|
|
req->fb->pixel_format == DRM_FORMAT_NV61) &&
|
|
(plane->rotation & (BIT(DRM_ROTATE_90) | BIT(DRM_ROTATE_270))))
|
|
cfg |= ATMEL_HLCDC_YUV422ROT;
|
|
|
|
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
|
ATMEL_HLCDC_LAYER_FORMAT_CFG_ID,
|
|
0xffffffff,
|
|
cfg);
|
|
|
|
/*
|
|
* Rotation optimization is not working on RGB888 (rotation is still
|
|
* working but without any optimization).
|
|
*/
|
|
if (req->fb->pixel_format == DRM_FORMAT_RGB888)
|
|
cfg = ATMEL_HLCDC_LAYER_DMA_ROTDIS;
|
|
else
|
|
cfg = 0;
|
|
|
|
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
|
ATMEL_HLCDC_LAYER_DMA_CFG_ID,
|
|
ATMEL_HLCDC_LAYER_DMA_ROTDIS, cfg);
|
|
}
|
|
|
|
static void atmel_hlcdc_plane_update_buffers(struct atmel_hlcdc_plane *plane,
|
|
struct atmel_hlcdc_plane_update_req *req)
|
|
{
|
|
struct atmel_hlcdc_layer *layer = &plane->layer;
|
|
const struct atmel_hlcdc_layer_cfg_layout *layout =
|
|
&layer->desc->layout;
|
|
int i;
|
|
|
|
atmel_hlcdc_layer_update_set_fb(&plane->layer, req->fb, req->offsets);
|
|
|
|
for (i = 0; i < req->nplanes; i++) {
|
|
if (layout->xstride[i]) {
|
|
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
|
layout->xstride[i],
|
|
0xffffffff,
|
|
req->xstride[i]);
|
|
}
|
|
|
|
if (layout->pstride[i]) {
|
|
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
|
layout->pstride[i],
|
|
0xffffffff,
|
|
req->pstride[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int atmel_hlcdc_plane_check_update_req(struct drm_plane *p,
|
|
struct atmel_hlcdc_plane_update_req *req,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
|
|
const struct atmel_hlcdc_layer_cfg_layout *layout =
|
|
&plane->layer.desc->layout;
|
|
|
|
if (!layout->size &&
|
|
(mode->hdisplay != req->crtc_w ||
|
|
mode->vdisplay != req->crtc_h))
|
|
return -EINVAL;
|
|
|
|
if (plane->layer.desc->max_height &&
|
|
req->crtc_h > plane->layer.desc->max_height)
|
|
return -EINVAL;
|
|
|
|
if (plane->layer.desc->max_width &&
|
|
req->crtc_w > plane->layer.desc->max_width)
|
|
return -EINVAL;
|
|
|
|
if ((req->crtc_h != req->src_h || req->crtc_w != req->src_w) &&
|
|
(!layout->memsize ||
|
|
atmel_hlcdc_format_embedds_alpha(req->fb->pixel_format)))
|
|
return -EINVAL;
|
|
|
|
if (req->crtc_x < 0 || req->crtc_y < 0)
|
|
return -EINVAL;
|
|
|
|
if (req->crtc_w + req->crtc_x > mode->hdisplay ||
|
|
req->crtc_h + req->crtc_y > mode->vdisplay)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int atmel_hlcdc_plane_prepare_update_req(struct drm_plane *p,
|
|
struct atmel_hlcdc_plane_update_req *req,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
|
|
unsigned int patched_crtc_w;
|
|
unsigned int patched_crtc_h;
|
|
unsigned int patched_src_w;
|
|
unsigned int patched_src_h;
|
|
unsigned int tmp;
|
|
int x_offset = 0;
|
|
int y_offset = 0;
|
|
int hsub = 1;
|
|
int vsub = 1;
|
|
int i;
|
|
|
|
if ((req->src_x | req->src_y | req->src_w | req->src_h) &
|
|
SUBPIXEL_MASK)
|
|
return -EINVAL;
|
|
|
|
req->src_x >>= 16;
|
|
req->src_y >>= 16;
|
|
req->src_w >>= 16;
|
|
req->src_h >>= 16;
|
|
|
|
req->nplanes = drm_format_num_planes(req->fb->pixel_format);
|
|
if (req->nplanes > ATMEL_HLCDC_MAX_PLANES)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* Swap width and size in case of 90 or 270 degrees rotation
|
|
*/
|
|
if (plane->rotation & (BIT(DRM_ROTATE_90) | BIT(DRM_ROTATE_270))) {
|
|
tmp = req->crtc_w;
|
|
req->crtc_w = req->crtc_h;
|
|
req->crtc_h = tmp;
|
|
tmp = req->src_w;
|
|
req->src_w = req->src_h;
|
|
req->src_h = tmp;
|
|
}
|
|
|
|
if (req->crtc_x + req->crtc_w > mode->hdisplay)
|
|
patched_crtc_w = mode->hdisplay - req->crtc_x;
|
|
else
|
|
patched_crtc_w = req->crtc_w;
|
|
|
|
if (req->crtc_x < 0) {
|
|
patched_crtc_w += req->crtc_x;
|
|
x_offset = -req->crtc_x;
|
|
req->crtc_x = 0;
|
|
}
|
|
|
|
if (req->crtc_y + req->crtc_h > mode->vdisplay)
|
|
patched_crtc_h = mode->vdisplay - req->crtc_y;
|
|
else
|
|
patched_crtc_h = req->crtc_h;
|
|
|
|
if (req->crtc_y < 0) {
|
|
patched_crtc_h += req->crtc_y;
|
|
y_offset = -req->crtc_y;
|
|
req->crtc_y = 0;
|
|
}
|
|
|
|
patched_src_w = DIV_ROUND_CLOSEST(patched_crtc_w * req->src_w,
|
|
req->crtc_w);
|
|
patched_src_h = DIV_ROUND_CLOSEST(patched_crtc_h * req->src_h,
|
|
req->crtc_h);
|
|
|
|
hsub = drm_format_horz_chroma_subsampling(req->fb->pixel_format);
|
|
vsub = drm_format_vert_chroma_subsampling(req->fb->pixel_format);
|
|
|
|
for (i = 0; i < req->nplanes; i++) {
|
|
unsigned int offset = 0;
|
|
int xdiv = i ? hsub : 1;
|
|
int ydiv = i ? vsub : 1;
|
|
|
|
req->bpp[i] = drm_format_plane_cpp(req->fb->pixel_format, i);
|
|
if (!req->bpp[i])
|
|
return -EINVAL;
|
|
|
|
switch (plane->rotation & 0xf) {
|
|
case BIT(DRM_ROTATE_90):
|
|
offset = ((y_offset + req->src_y + patched_src_w - 1) /
|
|
ydiv) * req->fb->pitches[i];
|
|
offset += ((x_offset + req->src_x) / xdiv) *
|
|
req->bpp[i];
|
|
req->xstride[i] = ((patched_src_w - 1) / ydiv) *
|
|
req->fb->pitches[i];
|
|
req->pstride[i] = -req->fb->pitches[i] - req->bpp[i];
|
|
break;
|
|
case BIT(DRM_ROTATE_180):
|
|
offset = ((y_offset + req->src_y + patched_src_h - 1) /
|
|
ydiv) * req->fb->pitches[i];
|
|
offset += ((x_offset + req->src_x + patched_src_w - 1) /
|
|
xdiv) * req->bpp[i];
|
|
req->xstride[i] = ((((patched_src_w - 1) / xdiv) - 1) *
|
|
req->bpp[i]) - req->fb->pitches[i];
|
|
req->pstride[i] = -2 * req->bpp[i];
|
|
break;
|
|
case BIT(DRM_ROTATE_270):
|
|
offset = ((y_offset + req->src_y) / ydiv) *
|
|
req->fb->pitches[i];
|
|
offset += ((x_offset + req->src_x + patched_src_h - 1) /
|
|
xdiv) * req->bpp[i];
|
|
req->xstride[i] = -(((patched_src_w - 1) / ydiv) *
|
|
req->fb->pitches[i]) -
|
|
(2 * req->bpp[i]);
|
|
req->pstride[i] = req->fb->pitches[i] - req->bpp[i];
|
|
break;
|
|
case BIT(DRM_ROTATE_0):
|
|
default:
|
|
offset = ((y_offset + req->src_y) / ydiv) *
|
|
req->fb->pitches[i];
|
|
offset += ((x_offset + req->src_x) / xdiv) *
|
|
req->bpp[i];
|
|
req->xstride[i] = req->fb->pitches[i] -
|
|
((patched_src_w / xdiv) *
|
|
req->bpp[i]);
|
|
req->pstride[i] = 0;
|
|
break;
|
|
}
|
|
|
|
req->offsets[i] = offset + req->fb->offsets[i];
|
|
}
|
|
|
|
req->src_w = patched_src_w;
|
|
req->src_h = patched_src_h;
|
|
req->crtc_w = patched_crtc_w;
|
|
req->crtc_h = patched_crtc_h;
|
|
|
|
return atmel_hlcdc_plane_check_update_req(p, req, mode);
|
|
}
|
|
|
|
int atmel_hlcdc_plane_apply_update_req(struct drm_plane *p,
|
|
struct atmel_hlcdc_plane_update_req *req)
|
|
{
|
|
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
|
|
int ret;
|
|
|
|
ret = atmel_hlcdc_layer_update_start(&plane->layer);
|
|
if (ret)
|
|
return ret;
|
|
|
|
atmel_hlcdc_plane_update_pos_and_size(plane, req);
|
|
atmel_hlcdc_plane_update_general_settings(plane, req);
|
|
atmel_hlcdc_plane_update_format(plane, req);
|
|
atmel_hlcdc_plane_update_buffers(plane, req);
|
|
|
|
atmel_hlcdc_layer_update_commit(&plane->layer);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int atmel_hlcdc_plane_update_with_mode(struct drm_plane *p,
|
|
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,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
|
|
struct atmel_hlcdc_plane_update_req req;
|
|
int ret = 0;
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.crtc_x = crtc_x;
|
|
req.crtc_y = crtc_y;
|
|
req.crtc_w = crtc_w;
|
|
req.crtc_h = crtc_h;
|
|
req.src_x = src_x;
|
|
req.src_y = src_y;
|
|
req.src_w = src_w;
|
|
req.src_h = src_h;
|
|
req.fb = fb;
|
|
|
|
ret = atmel_hlcdc_plane_prepare_update_req(&plane->base, &req, mode);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!req.crtc_h || !req.crtc_w)
|
|
return atmel_hlcdc_layer_disable(&plane->layer);
|
|
|
|
return atmel_hlcdc_plane_apply_update_req(&plane->base, &req);
|
|
}
|
|
|
|
static int atmel_hlcdc_plane_update(struct drm_plane *p,
|
|
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)
|
|
{
|
|
return atmel_hlcdc_plane_update_with_mode(p, crtc, fb, crtc_x, crtc_y,
|
|
crtc_w, crtc_h, src_x, src_y,
|
|
src_w, src_h, &crtc->hwmode);
|
|
}
|
|
|
|
static int atmel_hlcdc_plane_disable(struct drm_plane *p)
|
|
{
|
|
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
|
|
|
|
return atmel_hlcdc_layer_disable(&plane->layer);
|
|
}
|
|
|
|
static void atmel_hlcdc_plane_destroy(struct drm_plane *p)
|
|
{
|
|
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
|
|
|
|
if (plane->base.fb)
|
|
drm_framebuffer_unreference(plane->base.fb);
|
|
|
|
atmel_hlcdc_layer_cleanup(p->dev, &plane->layer);
|
|
|
|
drm_plane_cleanup(p);
|
|
devm_kfree(p->dev->dev, plane);
|
|
}
|
|
|
|
static int atmel_hlcdc_plane_set_alpha(struct atmel_hlcdc_plane *plane,
|
|
u8 alpha)
|
|
{
|
|
atmel_hlcdc_layer_update_start(&plane->layer);
|
|
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
|
plane->layer.desc->layout.general_config,
|
|
ATMEL_HLCDC_LAYER_GA_MASK,
|
|
alpha << ATMEL_HLCDC_LAYER_GA_SHIFT);
|
|
atmel_hlcdc_layer_update_commit(&plane->layer);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int atmel_hlcdc_plane_set_rotation(struct atmel_hlcdc_plane *plane,
|
|
unsigned int rotation)
|
|
{
|
|
plane->rotation = rotation;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int atmel_hlcdc_plane_set_property(struct drm_plane *p,
|
|
struct drm_property *property,
|
|
uint64_t value)
|
|
{
|
|
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
|
|
struct atmel_hlcdc_plane_properties *props = plane->properties;
|
|
|
|
if (property == props->alpha)
|
|
atmel_hlcdc_plane_set_alpha(plane, value);
|
|
else if (property == props->rotation)
|
|
atmel_hlcdc_plane_set_rotation(plane, value);
|
|
else
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void atmel_hlcdc_plane_init_properties(struct atmel_hlcdc_plane *plane,
|
|
const struct atmel_hlcdc_layer_desc *desc,
|
|
struct atmel_hlcdc_plane_properties *props)
|
|
{
|
|
struct regmap *regmap = plane->layer.hlcdc->regmap;
|
|
|
|
if (desc->type == ATMEL_HLCDC_OVERLAY_LAYER ||
|
|
desc->type == ATMEL_HLCDC_CURSOR_LAYER) {
|
|
drm_object_attach_property(&plane->base.base,
|
|
props->alpha, 255);
|
|
|
|
/* Set default alpha value */
|
|
regmap_update_bits(regmap,
|
|
desc->regs_offset +
|
|
ATMEL_HLCDC_LAYER_GENERAL_CFG(&plane->layer),
|
|
ATMEL_HLCDC_LAYER_GA_MASK,
|
|
ATMEL_HLCDC_LAYER_GA_MASK);
|
|
}
|
|
|
|
if (desc->layout.xstride && desc->layout.pstride)
|
|
drm_object_attach_property(&plane->base.base,
|
|
props->rotation,
|
|
BIT(DRM_ROTATE_0));
|
|
|
|
if (desc->layout.csc) {
|
|
/*
|
|
* TODO: decare a "yuv-to-rgb-conv-factors" property to let
|
|
* userspace modify these factors (using a BLOB property ?).
|
|
*/
|
|
regmap_write(regmap,
|
|
desc->regs_offset +
|
|
ATMEL_HLCDC_LAYER_CSC_CFG(&plane->layer, 0),
|
|
0x4c900091);
|
|
regmap_write(regmap,
|
|
desc->regs_offset +
|
|
ATMEL_HLCDC_LAYER_CSC_CFG(&plane->layer, 1),
|
|
0x7a5f5090);
|
|
regmap_write(regmap,
|
|
desc->regs_offset +
|
|
ATMEL_HLCDC_LAYER_CSC_CFG(&plane->layer, 2),
|
|
0x40040890);
|
|
}
|
|
}
|
|
|
|
static struct drm_plane_funcs layer_plane_funcs = {
|
|
.update_plane = atmel_hlcdc_plane_update,
|
|
.disable_plane = atmel_hlcdc_plane_disable,
|
|
.set_property = atmel_hlcdc_plane_set_property,
|
|
.destroy = atmel_hlcdc_plane_destroy,
|
|
};
|
|
|
|
static struct atmel_hlcdc_plane *
|
|
atmel_hlcdc_plane_create(struct drm_device *dev,
|
|
const struct atmel_hlcdc_layer_desc *desc,
|
|
struct atmel_hlcdc_plane_properties *props)
|
|
{
|
|
struct atmel_hlcdc_plane *plane;
|
|
enum drm_plane_type type;
|
|
int ret;
|
|
|
|
plane = devm_kzalloc(dev->dev, sizeof(*plane), GFP_KERNEL);
|
|
if (!plane)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
ret = atmel_hlcdc_layer_init(dev, &plane->layer, desc);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
if (desc->type == ATMEL_HLCDC_BASE_LAYER)
|
|
type = DRM_PLANE_TYPE_PRIMARY;
|
|
else if (desc->type == ATMEL_HLCDC_CURSOR_LAYER)
|
|
type = DRM_PLANE_TYPE_CURSOR;
|
|
else
|
|
type = DRM_PLANE_TYPE_OVERLAY;
|
|
|
|
ret = drm_universal_plane_init(dev, &plane->base, 0,
|
|
&layer_plane_funcs,
|
|
desc->formats->formats,
|
|
desc->formats->nformats, type);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
/* Set default property values*/
|
|
atmel_hlcdc_plane_init_properties(plane, desc, props);
|
|
|
|
return plane;
|
|
}
|
|
|
|
static struct atmel_hlcdc_plane_properties *
|
|
atmel_hlcdc_plane_create_properties(struct drm_device *dev)
|
|
{
|
|
struct atmel_hlcdc_plane_properties *props;
|
|
|
|
props = devm_kzalloc(dev->dev, sizeof(*props), GFP_KERNEL);
|
|
if (!props)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
props->alpha = drm_property_create_range(dev, 0, "alpha", 0, 255);
|
|
if (!props->alpha)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
props->rotation = drm_mode_create_rotation_property(dev,
|
|
BIT(DRM_ROTATE_0) |
|
|
BIT(DRM_ROTATE_90) |
|
|
BIT(DRM_ROTATE_180) |
|
|
BIT(DRM_ROTATE_270));
|
|
if (!props->rotation)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
return props;
|
|
}
|
|
|
|
struct atmel_hlcdc_planes *
|
|
atmel_hlcdc_create_planes(struct drm_device *dev)
|
|
{
|
|
struct atmel_hlcdc_dc *dc = dev->dev_private;
|
|
struct atmel_hlcdc_plane_properties *props;
|
|
struct atmel_hlcdc_planes *planes;
|
|
const struct atmel_hlcdc_layer_desc *descs = dc->desc->layers;
|
|
int nlayers = dc->desc->nlayers;
|
|
int i;
|
|
|
|
planes = devm_kzalloc(dev->dev, sizeof(*planes), GFP_KERNEL);
|
|
if (!planes)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
for (i = 0; i < nlayers; i++) {
|
|
if (descs[i].type == ATMEL_HLCDC_OVERLAY_LAYER)
|
|
planes->noverlays++;
|
|
}
|
|
|
|
if (planes->noverlays) {
|
|
planes->overlays = devm_kzalloc(dev->dev,
|
|
planes->noverlays *
|
|
sizeof(*planes->overlays),
|
|
GFP_KERNEL);
|
|
if (!planes->overlays)
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
props = atmel_hlcdc_plane_create_properties(dev);
|
|
if (IS_ERR(props))
|
|
return ERR_CAST(props);
|
|
|
|
planes->noverlays = 0;
|
|
for (i = 0; i < nlayers; i++) {
|
|
struct atmel_hlcdc_plane *plane;
|
|
|
|
if (descs[i].type == ATMEL_HLCDC_PP_LAYER)
|
|
continue;
|
|
|
|
plane = atmel_hlcdc_plane_create(dev, &descs[i], props);
|
|
if (IS_ERR(plane))
|
|
return ERR_CAST(plane);
|
|
|
|
plane->properties = props;
|
|
|
|
switch (descs[i].type) {
|
|
case ATMEL_HLCDC_BASE_LAYER:
|
|
if (planes->primary)
|
|
return ERR_PTR(-EINVAL);
|
|
planes->primary = plane;
|
|
break;
|
|
|
|
case ATMEL_HLCDC_OVERLAY_LAYER:
|
|
planes->overlays[planes->noverlays++] = plane;
|
|
break;
|
|
|
|
case ATMEL_HLCDC_CURSOR_LAYER:
|
|
if (planes->cursor)
|
|
return ERR_PTR(-EINVAL);
|
|
planes->cursor = plane;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return planes;
|
|
}
|