GSCALERs in Exynos5433 have local path to DECON and DECON_TV. They can be used as extra planes with support for non-RGB formats and scaling. To enable it GSCALER must expose exynos_plane with associated plane callbacks and bind it to DECONs CRTCs. Moreover device locking should be added to prevent simultanous device usage by IPP and DRM. Signed-off-by: Andrzej Hajda <a.hajda@xxxxxxxxxxx> --- drivers/gpu/drm/exynos/exynos_drm_gsc.c | 179 ++++++++++++++++++++---- drivers/gpu/drm/exynos/regs-gsc.h | 6 + 2 files changed, 156 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/exynos/exynos_drm_gsc.c b/drivers/gpu/drm/exynos/exynos_drm_gsc.c index 8d5aa70c8ab8..28a738a68a82 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gsc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_gsc.c @@ -16,14 +16,16 @@ #include <linux/platform_device.h> #include <linux/clk.h> #include <linux/pm_runtime.h> -#include <linux/mfd/syscon.h> #include <linux/of_device.h> #include <linux/regmap.h> +#include <drm/drm_atomic.h> #include <drm/drmP.h> #include <drm/exynos_drm.h> #include "regs-gsc.h" +#include "exynos_drm_crtc.h" #include "exynos_drm_drv.h" +#include "exynos_drm_fb.h" #include "exynos_drm_ipp.h" /* @@ -98,11 +100,13 @@ struct gsc_scaler { */ struct gsc_context { struct exynos_drm_ipp ipp; + struct exynos_drm_plane plane; struct drm_device *drm_dev; struct device *dev; struct exynos_drm_ipp_task *task; struct exynos_drm_ipp_formats *formats; unsigned int num_formats; + struct spinlock lock; struct resource *regs_res; void __iomem *regs; @@ -113,8 +117,12 @@ struct gsc_context { int id; int irq; bool rotation; + bool enabled; + bool use_local_path; }; +#define to_gsc(ptr) container_of(ptr, struct gsc_context, ptr) + /** * struct gsc_driverdata - per device type driver data for init time. * @@ -127,6 +135,7 @@ struct gsc_driverdata { int num_limits; const char *clk_names[GSC_MAX_CLOCKS]; int num_clocks; + bool use_local_path; }; /* 8-tap Filter Coefficient */ @@ -377,6 +386,32 @@ static const int v_coef_4t[GSC_COEF_RATIO][GSC_COEF_ATTR][GSC_COEF_V_4T] = { } }; +static int gsc_dev_lock(struct gsc_context *ctx, void *task) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&ctx->lock, flags); + if (ctx->task) + ret = -EBUSY; + else + ctx->task = task; + spin_unlock_irqrestore(&ctx->lock, flags); + return ret; +} + +static void *gsc_dev_unlock(struct gsc_context *ctx) +{ + unsigned long flags; + void *task; + + spin_lock_irqsave(&ctx->lock, flags); + task = ctx->task; + ctx->task = NULL; + spin_unlock_irqrestore(&ctx->lock, flags); + return task; +} + static int gsc_sw_reset(struct gsc_context *ctx) { u32 cfg; @@ -1021,6 +1056,7 @@ static int gsc_get_dst_buf_index(struct gsc_context *ctx) static irqreturn_t gsc_irq_handler(int irq, void *dev_id) { struct gsc_context *ctx = dev_id; + struct exynos_drm_ipp_task *task; u32 status; int err = 0; @@ -1049,10 +1085,8 @@ static irqreturn_t gsc_irq_handler(int irq, void *dev_id) err = -EINVAL; } - if (ctx->task) { - struct exynos_drm_ipp_task *task = ctx->task; - - ctx->task = NULL; + task = gsc_dev_unlock(ctx); + if (!IS_ERR_OR_NULL(task)) { pm_runtime_mark_last_busy(ctx->dev); pm_runtime_put_autosuspend(ctx->dev); exynos_drm_ipp_task_done(task, err); @@ -1117,13 +1151,15 @@ static int gsc_commit(struct exynos_drm_ipp *ipp, struct gsc_context *ctx = container_of(ipp, struct gsc_context, ipp); int ret; - pm_runtime_get_sync(ctx->dev); - ctx->task = task; + ret = gsc_dev_lock(ctx, task); + if (ret) + return ret; + pm_runtime_get_sync(ctx->dev); ret = gsc_reset(ctx); if (ret) { pm_runtime_put_autosuspend(ctx->dev); - ctx->task = NULL; + gsc_dev_unlock(ctx); return ret; } @@ -1147,10 +1183,8 @@ static void gsc_abort(struct exynos_drm_ipp *ipp, container_of(ipp, struct gsc_context, ipp); gsc_reset(ctx); - if (ctx->task) { - struct exynos_drm_ipp_task *task = ctx->task; - - ctx->task = NULL; + task = gsc_dev_unlock(ctx); + if (!IS_ERR_OR_NULL(task)) { pm_runtime_mark_last_busy(ctx->dev); pm_runtime_put_autosuspend(ctx->dev); exynos_drm_ipp_task_done(task, -EIO); @@ -1162,11 +1196,97 @@ static struct exynos_drm_ipp_funcs ipp_funcs = { .abort = gsc_abort, }; +static const unsigned int gsc_formats[] = { + DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888, DRM_FORMAT_BGRX8888, + DRM_FORMAT_RGB565, + DRM_FORMAT_NV12, DRM_FORMAT_NV16, DRM_FORMAT_NV21, DRM_FORMAT_NV61, + DRM_FORMAT_UYVY, DRM_FORMAT_VYUY, DRM_FORMAT_YUYV, DRM_FORMAT_YVYU, + DRM_FORMAT_YUV420, DRM_FORMAT_YVU420, DRM_FORMAT_YUV422, +}; + +static const unsigned int gsc_tiled_formats[] = { + DRM_FORMAT_NV12, DRM_FORMAT_NV21, +}; + +static void gsc_update_plane(struct exynos_drm_plane *plane) +{ + struct gsc_context *ctx = to_gsc(plane); + struct exynos_drm_plane_state *state = + to_exynos_plane_state(plane->base.state); + struct drm_framebuffer *fb = plane->base.state->fb; + u32 src_w = fb->pitches[0] / fb->format->cpp[0], src_h = state->src.h; + u32 src_x = state->src.x, src_y = state->src.y; + u32 cropped_w = state->src.w, cropped_h = state->src.h; + u32 scaled_w = state->crtc.w, scaled_h = state->crtc.h; + int i; + + if (!ctx->enabled) { + while (gsc_dev_lock(ctx, ERR_PTR(-EBUSY)) < 0) + usleep_range(1000, 2000); + pm_runtime_get_sync(ctx->dev); + gsc_sw_reset(ctx); + gsc_src_set_buf_seq(ctx, 0, true); + gsc_write(GSC_SMART_IF_EN | GSC_HIERARCHICAL_MODE, GSC_SMART_IF_CON); + ctx->enabled = true; + } + gsc_write(GSC_POINT(src_x, src_y), GSC_SRCIMG_OFFSET); + gsc_write(GSC_POINT(src_w, src_h), GSC_SRCIMG_SIZE); + gsc_write(GSC_POINT(cropped_w, cropped_h), GSC_CROPPED_SIZE); + gsc_write(GSC_POINT(scaled_w, scaled_h), GSC_SCALED_SIZE); + gsc_write(GSC_OUT_PATH_LOCAL | GSC_OUT_RGB_HD_WIDE, GSC_OUT_CON); + gsc_write(GSC_POINT(1, 1), GSC_PRE_SCALE_RATIO); + gsc_write(BIT(16) * cropped_w / scaled_w, GSC_MAIN_H_RATIO); + gsc_write(BIT(16) * cropped_h / scaled_h, GSC_MAIN_V_RATIO); + gsc_write(exynos_drm_fb_dma_addr(fb, 0), GSC_IN_BASE_ADDR_Y(0)); + if (fb->format->num_planes > 1) + gsc_write(exynos_drm_fb_dma_addr(fb, 1), GSC_IN_BASE_ADDR_CB(0)); + if (fb->format->num_planes > 2) + gsc_write(exynos_drm_fb_dma_addr(fb, 2), GSC_IN_BASE_ADDR_CR(0)); + gsc_src_set_fmt(ctx, fb->format->format, fb->modifier); + gsc_write(scaled_w * scaled_h, GSC_SMART_IF_PIXEL_NUM); + gsc_write(GSC_ENABLE_SFR_UPDATE | GSC_ENABLE_ON, GSC_ENABLE); +} + +static void gsc_disable_plane(struct exynos_drm_plane *plane) +{ + struct gsc_context *ctx = to_gsc(plane); + u32 val; + + val = gsc_read(GSC_ENABLE); + val |= GSC_ENABLE_SFR_UPDATE; + val &= ~GSC_ENABLE_ON; + gsc_write(val, GSC_ENABLE); + pm_runtime_mark_last_busy(ctx->dev); + pm_runtime_put_autosuspend(ctx->dev); + ctx->enabled = false; + gsc_dev_unlock(ctx); +} + +const struct exynos_drm_plane_ops gsc_plane_ops = { + .update_plane = gsc_update_plane, + .disable_plane = gsc_disable_plane +}; + +static void gsc_set_possible_crtcs(struct gsc_context *ctx) +{ + struct exynos_drm_crtc *crtc; + u32 mask = 0; + + crtc = exynos_drm_crtc_get_by_type(ctx->drm_dev, EXYNOS_DISPLAY_TYPE_HDMI); + if (crtc) + mask |= drm_crtc_mask(&crtc->base); + crtc = exynos_drm_crtc_get_by_type(ctx->drm_dev, EXYNOS_DISPLAY_TYPE_LCD); + if (crtc) + mask |= drm_crtc_mask(&crtc->base); + ctx->plane.base.possible_crtcs = mask; +} + static int gsc_bind(struct device *dev, struct device *master, void *data) { struct gsc_context *ctx = dev_get_drvdata(dev); struct drm_device *drm_dev = data; struct exynos_drm_ipp *ipp = &ctx->ipp; + int ret; ctx->drm_dev = drm_dev; exynos_drm_register_dma(drm_dev, dev); @@ -1176,9 +1296,19 @@ static int gsc_bind(struct device *dev, struct device *master, void *data) DRM_EXYNOS_IPP_CAP_SCALE | DRM_EXYNOS_IPP_CAP_CONVERT, ctx->formats, ctx->num_formats, "gsc"); - dev_info(dev, "The exynos gscaler has been probed successfully\n"); - - return 0; + if (!ctx->use_local_path) + return 0; + + ctx->plane.index = ctx->id; + ctx->plane.capabilities = EXYNOS_DRM_PLANE_CAP_GSCALER | + EXYNOS_DRM_PLANE_CAP_ZPOS | + EXYNOS_DRM_PLANE_CAP_SCALE | + EXYNOS_DRM_PLANE_CAP_WIN_BLEND; + ctx->plane.ops = &gsc_plane_ops; + ret = exynos_plane_init(drm_dev, &ctx->plane, gsc_formats, + ARRAY_SIZE(gsc_formats), 0); + gsc_set_possible_crtcs(ctx); + return ret; } static void gsc_unbind(struct device *dev, struct device *master, @@ -1197,18 +1327,6 @@ static const struct component_ops gsc_component_ops = { .unbind = gsc_unbind, }; -static const unsigned int gsc_formats[] = { - DRM_FORMAT_ARGB8888, - DRM_FORMAT_XRGB8888, DRM_FORMAT_RGB565, DRM_FORMAT_BGRX8888, - DRM_FORMAT_NV12, DRM_FORMAT_NV16, DRM_FORMAT_NV21, DRM_FORMAT_NV61, - DRM_FORMAT_UYVY, DRM_FORMAT_VYUY, DRM_FORMAT_YUYV, DRM_FORMAT_YVYU, - DRM_FORMAT_YUV420, DRM_FORMAT_YVU420, DRM_FORMAT_YUV422, -}; - -static const unsigned int gsc_tiled_formats[] = { - DRM_FORMAT_NV12, DRM_FORMAT_NV21, -}; - static int gsc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1226,6 +1344,8 @@ static int gsc_probe(struct platform_device *pdev) ctx->dev = dev; ctx->num_clocks = driver_data->num_clocks; ctx->clk_names = driver_data->clk_names; + ctx->use_local_path = driver_data->use_local_path; + spin_lock_init(&ctx->lock); /* construct formats/limits array */ num_formats = ARRAY_SIZE(gsc_formats) + ARRAY_SIZE(gsc_tiled_formats); @@ -1395,10 +1515,11 @@ static struct gsc_driverdata gsc_exynos5420_drvdata = { }; static struct gsc_driverdata gsc_exynos5433_drvdata = { - .clk_names = {"pclk", "aclk", "aclk_xiu", "aclk_gsclbend"}, - .num_clocks = 4, + .clk_names = {"pclk", "aclk", "aclk_xiu", "aclk_gsclbend", "gsd"}, + .num_clocks = 5, .limits = gsc_5433_limits, .num_limits = ARRAY_SIZE(gsc_5433_limits), + .use_local_path = true, }; static const struct of_device_id exynos_drm_gsc_of_match[] = { diff --git a/drivers/gpu/drm/exynos/regs-gsc.h b/drivers/gpu/drm/exynos/regs-gsc.h index 16b39734115c..7c23a79085e2 100644 --- a/drivers/gpu/drm/exynos/regs-gsc.h +++ b/drivers/gpu/drm/exynos/regs-gsc.h @@ -91,6 +91,7 @@ #define GSC_IN_PATH_LOCAL (1 << 0) #define GSC_IN_PATH_MEMORY (0 << 0) +#define GSC_POINT(x, y) ((x) + ((y) << 16)) /* G-Scaler source image size */ #define GSC_SRCIMG_SIZE 0x14 #define GSC_SRCIMG_HEIGHT_MASK (0x1fff << 16) @@ -264,6 +265,11 @@ #define GSC_VPOSITION 0xA7C #define GSC_VPOS_F(x) ((x) << 0) +#define GSC_SMART_IF_PIXEL_NUM 0xAF0 + +#define GSC_SMART_IF_CON 0xAF4 +#define GSC_SMART_IF_EN (1 << 0) +#define GSC_HIERARCHICAL_MODE (0x3 << 1) /* G-Scaler clock initial count */ #define GSC_CLK_INIT_COUNT 0xC00 -- 2.17.1