alistair23-linux/drivers/gpu/drm/exynos/exynos_drm_fimc.c
Thomas Gleixner 2874c5fd28 treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 152
Based on 1 normalized pattern(s):

  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

extracted by the scancode license scanner the SPDX license identifier

  GPL-2.0-or-later

has been chosen to replace the boilerplate/reference in 3029 file(s).

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Allison Randal <allison@lohutok.net>
Cc: linux-spdx@vger.kernel.org
Link: https://lkml.kernel.org/r/20190527070032.746973796@linutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-05-30 11:26:32 -07:00

1422 lines
36 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2012 Samsung Electronics Co.Ltd
* Authors:
* Eunchul Kim <chulspro.kim@samsung.com>
* Jinyoung Jeon <jy0.jeon@samsung.com>
* Sangmin Lee <lsmin.lee@samsung.com>
*/
#include <linux/kernel.h>
#include <linux/component.h>
#include <linux/platform_device.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/of.h>
#include <linux/spinlock.h>
#include <drm/drmP.h>
#include <drm/exynos_drm.h>
#include "regs-fimc.h"
#include "exynos_drm_drv.h"
#include "exynos_drm_ipp.h"
/*
* FIMC stands for Fully Interactive Mobile Camera and
* supports image scaler/rotator and input/output DMA operations.
* input DMA reads image data from the memory.
* output DMA writes image data to memory.
* FIMC supports image rotation and image effect functions.
*/
#define FIMC_MAX_DEVS 4
#define FIMC_MAX_SRC 2
#define FIMC_MAX_DST 32
#define FIMC_SHFACTOR 10
#define FIMC_BUF_STOP 1
#define FIMC_BUF_START 2
#define FIMC_WIDTH_ITU_709 1280
#define FIMC_AUTOSUSPEND_DELAY 2000
static unsigned int fimc_mask = 0xc;
module_param_named(fimc_devs, fimc_mask, uint, 0644);
MODULE_PARM_DESC(fimc_devs, "Alias mask for assigning FIMC devices to Exynos DRM");
#define get_fimc_context(dev) platform_get_drvdata(to_platform_device(dev))
enum {
FIMC_CLK_LCLK,
FIMC_CLK_GATE,
FIMC_CLK_WB_A,
FIMC_CLK_WB_B,
FIMC_CLKS_MAX
};
static const char * const fimc_clock_names[] = {
[FIMC_CLK_LCLK] = "sclk_fimc",
[FIMC_CLK_GATE] = "fimc",
[FIMC_CLK_WB_A] = "pxl_async0",
[FIMC_CLK_WB_B] = "pxl_async1",
};
/*
* A structure of scaler.
*
* @range: narrow, wide.
* @bypass: unused scaler path.
* @up_h: horizontal scale up.
* @up_v: vertical scale up.
* @hratio: horizontal ratio.
* @vratio: vertical ratio.
*/
struct fimc_scaler {
bool range;
bool bypass;
bool up_h;
bool up_v;
u32 hratio;
u32 vratio;
};
/*
* A structure of fimc context.
*
* @regs_res: register resources.
* @regs: memory mapped io registers.
* @lock: locking of operations.
* @clocks: fimc clocks.
* @sc: scaler infomations.
* @pol: porarity of writeback.
* @id: fimc id.
* @irq: irq number.
*/
struct fimc_context {
struct exynos_drm_ipp ipp;
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 resource *regs_res;
void __iomem *regs;
spinlock_t lock;
struct clk *clocks[FIMC_CLKS_MAX];
struct fimc_scaler sc;
int id;
int irq;
};
static u32 fimc_read(struct fimc_context *ctx, u32 reg)
{
return readl(ctx->regs + reg);
}
static void fimc_write(struct fimc_context *ctx, u32 val, u32 reg)
{
writel(val, ctx->regs + reg);
}
static void fimc_set_bits(struct fimc_context *ctx, u32 reg, u32 bits)
{
void __iomem *r = ctx->regs + reg;
writel(readl(r) | bits, r);
}
static void fimc_clear_bits(struct fimc_context *ctx, u32 reg, u32 bits)
{
void __iomem *r = ctx->regs + reg;
writel(readl(r) & ~bits, r);
}
static void fimc_sw_reset(struct fimc_context *ctx)
{
u32 cfg;
/* stop dma operation */
cfg = fimc_read(ctx, EXYNOS_CISTATUS);
if (EXYNOS_CISTATUS_GET_ENVID_STATUS(cfg))
fimc_clear_bits(ctx, EXYNOS_MSCTRL, EXYNOS_MSCTRL_ENVID);
fimc_set_bits(ctx, EXYNOS_CISRCFMT, EXYNOS_CISRCFMT_ITU601_8BIT);
/* disable image capture */
fimc_clear_bits(ctx, EXYNOS_CIIMGCPT,
EXYNOS_CIIMGCPT_IMGCPTEN_SC | EXYNOS_CIIMGCPT_IMGCPTEN);
/* s/w reset */
fimc_set_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_SWRST);
/* s/w reset complete */
fimc_clear_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_SWRST);
/* reset sequence */
fimc_write(ctx, 0x0, EXYNOS_CIFCNTSEQ);
}
static void fimc_set_type_ctrl(struct fimc_context *ctx)
{
u32 cfg;
cfg = fimc_read(ctx, EXYNOS_CIGCTRL);
cfg &= ~(EXYNOS_CIGCTRL_TESTPATTERN_MASK |
EXYNOS_CIGCTRL_SELCAM_ITU_MASK |
EXYNOS_CIGCTRL_SELCAM_MIPI_MASK |
EXYNOS_CIGCTRL_SELCAM_FIMC_MASK |
EXYNOS_CIGCTRL_SELWB_CAMIF_MASK |
EXYNOS_CIGCTRL_SELWRITEBACK_MASK);
cfg |= (EXYNOS_CIGCTRL_SELCAM_ITU_A |
EXYNOS_CIGCTRL_SELWRITEBACK_A |
EXYNOS_CIGCTRL_SELCAM_MIPI_A |
EXYNOS_CIGCTRL_SELCAM_FIMC_ITU);
fimc_write(ctx, cfg, EXYNOS_CIGCTRL);
}
static void fimc_handle_jpeg(struct fimc_context *ctx, bool enable)
{
u32 cfg;
DRM_DEV_DEBUG_KMS(ctx->dev, "enable[%d]\n", enable);
cfg = fimc_read(ctx, EXYNOS_CIGCTRL);
if (enable)
cfg |= EXYNOS_CIGCTRL_CAM_JPEG;
else
cfg &= ~EXYNOS_CIGCTRL_CAM_JPEG;
fimc_write(ctx, cfg, EXYNOS_CIGCTRL);
}
static void fimc_mask_irq(struct fimc_context *ctx, bool enable)
{
u32 cfg;
DRM_DEV_DEBUG_KMS(ctx->dev, "enable[%d]\n", enable);
cfg = fimc_read(ctx, EXYNOS_CIGCTRL);
if (enable) {
cfg &= ~EXYNOS_CIGCTRL_IRQ_OVFEN;
cfg |= EXYNOS_CIGCTRL_IRQ_ENABLE | EXYNOS_CIGCTRL_IRQ_LEVEL;
} else
cfg &= ~EXYNOS_CIGCTRL_IRQ_ENABLE;
fimc_write(ctx, cfg, EXYNOS_CIGCTRL);
}
static void fimc_clear_irq(struct fimc_context *ctx)
{
fimc_set_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_IRQ_CLR);
}
static bool fimc_check_ovf(struct fimc_context *ctx)
{
u32 status, flag;
status = fimc_read(ctx, EXYNOS_CISTATUS);
flag = EXYNOS_CISTATUS_OVFIY | EXYNOS_CISTATUS_OVFICB |
EXYNOS_CISTATUS_OVFICR;
DRM_DEV_DEBUG_KMS(ctx->dev, "flag[0x%x]\n", flag);
if (status & flag) {
fimc_set_bits(ctx, EXYNOS_CIWDOFST,
EXYNOS_CIWDOFST_CLROVFIY | EXYNOS_CIWDOFST_CLROVFICB |
EXYNOS_CIWDOFST_CLROVFICR);
DRM_DEV_ERROR(ctx->dev,
"occurred overflow at %d, status 0x%x.\n",
ctx->id, status);
return true;
}
return false;
}
static bool fimc_check_frame_end(struct fimc_context *ctx)
{
u32 cfg;
cfg = fimc_read(ctx, EXYNOS_CISTATUS);
DRM_DEV_DEBUG_KMS(ctx->dev, "cfg[0x%x]\n", cfg);
if (!(cfg & EXYNOS_CISTATUS_FRAMEEND))
return false;
cfg &= ~(EXYNOS_CISTATUS_FRAMEEND);
fimc_write(ctx, cfg, EXYNOS_CISTATUS);
return true;
}
static int fimc_get_buf_id(struct fimc_context *ctx)
{
u32 cfg;
int frame_cnt, buf_id;
cfg = fimc_read(ctx, EXYNOS_CISTATUS2);
frame_cnt = EXYNOS_CISTATUS2_GET_FRAMECOUNT_BEFORE(cfg);
if (frame_cnt == 0)
frame_cnt = EXYNOS_CISTATUS2_GET_FRAMECOUNT_PRESENT(cfg);
DRM_DEV_DEBUG_KMS(ctx->dev, "present[%d]before[%d]\n",
EXYNOS_CISTATUS2_GET_FRAMECOUNT_PRESENT(cfg),
EXYNOS_CISTATUS2_GET_FRAMECOUNT_BEFORE(cfg));
if (frame_cnt == 0) {
DRM_DEV_ERROR(ctx->dev, "failed to get frame count.\n");
return -EIO;
}
buf_id = frame_cnt - 1;
DRM_DEV_DEBUG_KMS(ctx->dev, "buf_id[%d]\n", buf_id);
return buf_id;
}
static void fimc_handle_lastend(struct fimc_context *ctx, bool enable)
{
u32 cfg;
DRM_DEV_DEBUG_KMS(ctx->dev, "enable[%d]\n", enable);
cfg = fimc_read(ctx, EXYNOS_CIOCTRL);
if (enable)
cfg |= EXYNOS_CIOCTRL_LASTENDEN;
else
cfg &= ~EXYNOS_CIOCTRL_LASTENDEN;
fimc_write(ctx, cfg, EXYNOS_CIOCTRL);
}
static void fimc_src_set_fmt_order(struct fimc_context *ctx, u32 fmt)
{
u32 cfg;
DRM_DEV_DEBUG_KMS(ctx->dev, "fmt[0x%x]\n", fmt);
/* RGB */
cfg = fimc_read(ctx, EXYNOS_CISCCTRL);
cfg &= ~EXYNOS_CISCCTRL_INRGB_FMT_RGB_MASK;
switch (fmt) {
case DRM_FORMAT_RGB565:
cfg |= EXYNOS_CISCCTRL_INRGB_FMT_RGB565;
fimc_write(ctx, cfg, EXYNOS_CISCCTRL);
return;
case DRM_FORMAT_RGB888:
case DRM_FORMAT_XRGB8888:
cfg |= EXYNOS_CISCCTRL_INRGB_FMT_RGB888;
fimc_write(ctx, cfg, EXYNOS_CISCCTRL);
return;
default:
/* bypass */
break;
}
/* YUV */
cfg = fimc_read(ctx, EXYNOS_MSCTRL);
cfg &= ~(EXYNOS_MSCTRL_ORDER2P_SHIFT_MASK |
EXYNOS_MSCTRL_C_INT_IN_2PLANE |
EXYNOS_MSCTRL_ORDER422_YCBYCR);
switch (fmt) {
case DRM_FORMAT_YUYV:
cfg |= EXYNOS_MSCTRL_ORDER422_YCBYCR;
break;
case DRM_FORMAT_YVYU:
cfg |= EXYNOS_MSCTRL_ORDER422_YCRYCB;
break;
case DRM_FORMAT_UYVY:
cfg |= EXYNOS_MSCTRL_ORDER422_CBYCRY;
break;
case DRM_FORMAT_VYUY:
case DRM_FORMAT_YUV444:
cfg |= EXYNOS_MSCTRL_ORDER422_CRYCBY;
break;
case DRM_FORMAT_NV21:
case DRM_FORMAT_NV61:
cfg |= (EXYNOS_MSCTRL_ORDER2P_LSB_CRCB |
EXYNOS_MSCTRL_C_INT_IN_2PLANE);
break;
case DRM_FORMAT_YUV422:
case DRM_FORMAT_YUV420:
case DRM_FORMAT_YVU420:
cfg |= EXYNOS_MSCTRL_C_INT_IN_3PLANE;
break;
case DRM_FORMAT_NV12:
case DRM_FORMAT_NV16:
cfg |= (EXYNOS_MSCTRL_ORDER2P_LSB_CBCR |
EXYNOS_MSCTRL_C_INT_IN_2PLANE);
break;
}
fimc_write(ctx, cfg, EXYNOS_MSCTRL);
}
static void fimc_src_set_fmt(struct fimc_context *ctx, u32 fmt, bool tiled)
{
u32 cfg;
DRM_DEV_DEBUG_KMS(ctx->dev, "fmt[0x%x]\n", fmt);
cfg = fimc_read(ctx, EXYNOS_MSCTRL);
cfg &= ~EXYNOS_MSCTRL_INFORMAT_RGB;
switch (fmt) {
case DRM_FORMAT_RGB565:
case DRM_FORMAT_RGB888:
case DRM_FORMAT_XRGB8888:
cfg |= EXYNOS_MSCTRL_INFORMAT_RGB;
break;
case DRM_FORMAT_YUV444:
cfg |= EXYNOS_MSCTRL_INFORMAT_YCBCR420;
break;
case DRM_FORMAT_YUYV:
case DRM_FORMAT_YVYU:
case DRM_FORMAT_UYVY:
case DRM_FORMAT_VYUY:
cfg |= EXYNOS_MSCTRL_INFORMAT_YCBCR422_1PLANE;
break;
case DRM_FORMAT_NV16:
case DRM_FORMAT_NV61:
case DRM_FORMAT_YUV422:
cfg |= EXYNOS_MSCTRL_INFORMAT_YCBCR422;
break;
case DRM_FORMAT_YUV420:
case DRM_FORMAT_YVU420:
case DRM_FORMAT_NV12:
case DRM_FORMAT_NV21:
cfg |= EXYNOS_MSCTRL_INFORMAT_YCBCR420;
break;
}
fimc_write(ctx, cfg, EXYNOS_MSCTRL);
cfg = fimc_read(ctx, EXYNOS_CIDMAPARAM);
cfg &= ~EXYNOS_CIDMAPARAM_R_MODE_MASK;
if (tiled)
cfg |= EXYNOS_CIDMAPARAM_R_MODE_64X32;
else
cfg |= EXYNOS_CIDMAPARAM_R_MODE_LINEAR;
fimc_write(ctx, cfg, EXYNOS_CIDMAPARAM);
fimc_src_set_fmt_order(ctx, fmt);
}
static void fimc_src_set_transf(struct fimc_context *ctx, unsigned int rotation)
{
unsigned int degree = rotation & DRM_MODE_ROTATE_MASK;
u32 cfg1, cfg2;
DRM_DEV_DEBUG_KMS(ctx->dev, "rotation[%x]\n", rotation);
cfg1 = fimc_read(ctx, EXYNOS_MSCTRL);
cfg1 &= ~(EXYNOS_MSCTRL_FLIP_X_MIRROR |
EXYNOS_MSCTRL_FLIP_Y_MIRROR);
cfg2 = fimc_read(ctx, EXYNOS_CITRGFMT);
cfg2 &= ~EXYNOS_CITRGFMT_INROT90_CLOCKWISE;
switch (degree) {
case DRM_MODE_ROTATE_0:
if (rotation & DRM_MODE_REFLECT_X)
cfg1 |= EXYNOS_MSCTRL_FLIP_X_MIRROR;
if (rotation & DRM_MODE_REFLECT_Y)
cfg1 |= EXYNOS_MSCTRL_FLIP_Y_MIRROR;
break;
case DRM_MODE_ROTATE_90:
cfg2 |= EXYNOS_CITRGFMT_INROT90_CLOCKWISE;
if (rotation & DRM_MODE_REFLECT_X)
cfg1 |= EXYNOS_MSCTRL_FLIP_X_MIRROR;
if (rotation & DRM_MODE_REFLECT_Y)
cfg1 |= EXYNOS_MSCTRL_FLIP_Y_MIRROR;
break;
case DRM_MODE_ROTATE_180:
cfg1 |= (EXYNOS_MSCTRL_FLIP_X_MIRROR |
EXYNOS_MSCTRL_FLIP_Y_MIRROR);
if (rotation & DRM_MODE_REFLECT_X)
cfg1 &= ~EXYNOS_MSCTRL_FLIP_X_MIRROR;
if (rotation & DRM_MODE_REFLECT_Y)
cfg1 &= ~EXYNOS_MSCTRL_FLIP_Y_MIRROR;
break;
case DRM_MODE_ROTATE_270:
cfg1 |= (EXYNOS_MSCTRL_FLIP_X_MIRROR |
EXYNOS_MSCTRL_FLIP_Y_MIRROR);
cfg2 |= EXYNOS_CITRGFMT_INROT90_CLOCKWISE;
if (rotation & DRM_MODE_REFLECT_X)
cfg1 &= ~EXYNOS_MSCTRL_FLIP_X_MIRROR;
if (rotation & DRM_MODE_REFLECT_Y)
cfg1 &= ~EXYNOS_MSCTRL_FLIP_Y_MIRROR;
break;
}
fimc_write(ctx, cfg1, EXYNOS_MSCTRL);
fimc_write(ctx, cfg2, EXYNOS_CITRGFMT);
}
static void fimc_set_window(struct fimc_context *ctx,
struct exynos_drm_ipp_buffer *buf)
{
unsigned int real_width = buf->buf.pitch[0] / buf->format->cpp[0];
u32 cfg, h1, h2, v1, v2;
/* cropped image */
h1 = buf->rect.x;
h2 = real_width - buf->rect.w - buf->rect.x;
v1 = buf->rect.y;
v2 = buf->buf.height - buf->rect.h - buf->rect.y;
DRM_DEV_DEBUG_KMS(ctx->dev, "x[%d]y[%d]w[%d]h[%d]hsize[%d]vsize[%d]\n",
buf->rect.x, buf->rect.y, buf->rect.w, buf->rect.h,
real_width, buf->buf.height);
DRM_DEV_DEBUG_KMS(ctx->dev, "h1[%d]h2[%d]v1[%d]v2[%d]\n", h1, h2, v1,
v2);
/*
* set window offset 1, 2 size
* check figure 43-21 in user manual
*/
cfg = fimc_read(ctx, EXYNOS_CIWDOFST);
cfg &= ~(EXYNOS_CIWDOFST_WINHOROFST_MASK |
EXYNOS_CIWDOFST_WINVEROFST_MASK);
cfg |= (EXYNOS_CIWDOFST_WINHOROFST(h1) |
EXYNOS_CIWDOFST_WINVEROFST(v1));
cfg |= EXYNOS_CIWDOFST_WINOFSEN;
fimc_write(ctx, cfg, EXYNOS_CIWDOFST);
cfg = (EXYNOS_CIWDOFST2_WINHOROFST2(h2) |
EXYNOS_CIWDOFST2_WINVEROFST2(v2));
fimc_write(ctx, cfg, EXYNOS_CIWDOFST2);
}
static void fimc_src_set_size(struct fimc_context *ctx,
struct exynos_drm_ipp_buffer *buf)
{
unsigned int real_width = buf->buf.pitch[0] / buf->format->cpp[0];
u32 cfg;
DRM_DEV_DEBUG_KMS(ctx->dev, "hsize[%d]vsize[%d]\n", real_width,
buf->buf.height);
/* original size */
cfg = (EXYNOS_ORGISIZE_HORIZONTAL(real_width) |
EXYNOS_ORGISIZE_VERTICAL(buf->buf.height));
fimc_write(ctx, cfg, EXYNOS_ORGISIZE);
DRM_DEV_DEBUG_KMS(ctx->dev, "x[%d]y[%d]w[%d]h[%d]\n", buf->rect.x,
buf->rect.y, buf->rect.w, buf->rect.h);
/* set input DMA image size */
cfg = fimc_read(ctx, EXYNOS_CIREAL_ISIZE);
cfg &= ~(EXYNOS_CIREAL_ISIZE_HEIGHT_MASK |
EXYNOS_CIREAL_ISIZE_WIDTH_MASK);
cfg |= (EXYNOS_CIREAL_ISIZE_WIDTH(buf->rect.w) |
EXYNOS_CIREAL_ISIZE_HEIGHT(buf->rect.h));
fimc_write(ctx, cfg, EXYNOS_CIREAL_ISIZE);
/*
* set input FIFO image size
* for now, we support only ITU601 8 bit mode
*/
cfg = (EXYNOS_CISRCFMT_ITU601_8BIT |
EXYNOS_CISRCFMT_SOURCEHSIZE(real_width) |
EXYNOS_CISRCFMT_SOURCEVSIZE(buf->buf.height));
fimc_write(ctx, cfg, EXYNOS_CISRCFMT);
/* offset Y(RGB), Cb, Cr */
cfg = (EXYNOS_CIIYOFF_HORIZONTAL(buf->rect.x) |
EXYNOS_CIIYOFF_VERTICAL(buf->rect.y));
fimc_write(ctx, cfg, EXYNOS_CIIYOFF);
cfg = (EXYNOS_CIICBOFF_HORIZONTAL(buf->rect.x) |
EXYNOS_CIICBOFF_VERTICAL(buf->rect.y));
fimc_write(ctx, cfg, EXYNOS_CIICBOFF);
cfg = (EXYNOS_CIICROFF_HORIZONTAL(buf->rect.x) |
EXYNOS_CIICROFF_VERTICAL(buf->rect.y));
fimc_write(ctx, cfg, EXYNOS_CIICROFF);
fimc_set_window(ctx, buf);
}
static void fimc_src_set_addr(struct fimc_context *ctx,
struct exynos_drm_ipp_buffer *buf)
{
fimc_write(ctx, buf->dma_addr[0], EXYNOS_CIIYSA(0));
fimc_write(ctx, buf->dma_addr[1], EXYNOS_CIICBSA(0));
fimc_write(ctx, buf->dma_addr[2], EXYNOS_CIICRSA(0));
}
static void fimc_dst_set_fmt_order(struct fimc_context *ctx, u32 fmt)
{
u32 cfg;
DRM_DEV_DEBUG_KMS(ctx->dev, "fmt[0x%x]\n", fmt);
/* RGB */
cfg = fimc_read(ctx, EXYNOS_CISCCTRL);
cfg &= ~EXYNOS_CISCCTRL_OUTRGB_FMT_RGB_MASK;
switch (fmt) {
case DRM_FORMAT_RGB565:
cfg |= EXYNOS_CISCCTRL_OUTRGB_FMT_RGB565;
fimc_write(ctx, cfg, EXYNOS_CISCCTRL);
return;
case DRM_FORMAT_RGB888:
cfg |= EXYNOS_CISCCTRL_OUTRGB_FMT_RGB888;
fimc_write(ctx, cfg, EXYNOS_CISCCTRL);
return;
case DRM_FORMAT_XRGB8888:
cfg |= (EXYNOS_CISCCTRL_OUTRGB_FMT_RGB888 |
EXYNOS_CISCCTRL_EXTRGB_EXTENSION);
fimc_write(ctx, cfg, EXYNOS_CISCCTRL);
break;
default:
/* bypass */
break;
}
/* YUV */
cfg = fimc_read(ctx, EXYNOS_CIOCTRL);
cfg &= ~(EXYNOS_CIOCTRL_ORDER2P_MASK |
EXYNOS_CIOCTRL_ORDER422_MASK |
EXYNOS_CIOCTRL_YCBCR_PLANE_MASK);
switch (fmt) {
case DRM_FORMAT_XRGB8888:
cfg |= EXYNOS_CIOCTRL_ALPHA_OUT;
break;
case DRM_FORMAT_YUYV:
cfg |= EXYNOS_CIOCTRL_ORDER422_YCBYCR;
break;
case DRM_FORMAT_YVYU:
cfg |= EXYNOS_CIOCTRL_ORDER422_YCRYCB;
break;
case DRM_FORMAT_UYVY:
cfg |= EXYNOS_CIOCTRL_ORDER422_CBYCRY;
break;
case DRM_FORMAT_VYUY:
cfg |= EXYNOS_CIOCTRL_ORDER422_CRYCBY;
break;
case DRM_FORMAT_NV21:
case DRM_FORMAT_NV61:
cfg |= EXYNOS_CIOCTRL_ORDER2P_LSB_CRCB;
cfg |= EXYNOS_CIOCTRL_YCBCR_2PLANE;
break;
case DRM_FORMAT_YUV422:
case DRM_FORMAT_YUV420:
case DRM_FORMAT_YVU420:
cfg |= EXYNOS_CIOCTRL_YCBCR_3PLANE;
break;
case DRM_FORMAT_NV12:
case DRM_FORMAT_NV16:
cfg |= EXYNOS_CIOCTRL_ORDER2P_LSB_CBCR;
cfg |= EXYNOS_CIOCTRL_YCBCR_2PLANE;
break;
}
fimc_write(ctx, cfg, EXYNOS_CIOCTRL);
}
static void fimc_dst_set_fmt(struct fimc_context *ctx, u32 fmt, bool tiled)
{
u32 cfg;
DRM_DEV_DEBUG_KMS(ctx->dev, "fmt[0x%x]\n", fmt);
cfg = fimc_read(ctx, EXYNOS_CIEXTEN);
if (fmt == DRM_FORMAT_AYUV) {
cfg |= EXYNOS_CIEXTEN_YUV444_OUT;
fimc_write(ctx, cfg, EXYNOS_CIEXTEN);
} else {
cfg &= ~EXYNOS_CIEXTEN_YUV444_OUT;
fimc_write(ctx, cfg, EXYNOS_CIEXTEN);
cfg = fimc_read(ctx, EXYNOS_CITRGFMT);
cfg &= ~EXYNOS_CITRGFMT_OUTFORMAT_MASK;
switch (fmt) {
case DRM_FORMAT_RGB565:
case DRM_FORMAT_RGB888:
case DRM_FORMAT_XRGB8888:
cfg |= EXYNOS_CITRGFMT_OUTFORMAT_RGB;
break;
case DRM_FORMAT_YUYV:
case DRM_FORMAT_YVYU:
case DRM_FORMAT_UYVY:
case DRM_FORMAT_VYUY:
cfg |= EXYNOS_CITRGFMT_OUTFORMAT_YCBCR422_1PLANE;
break;
case DRM_FORMAT_NV16:
case DRM_FORMAT_NV61:
case DRM_FORMAT_YUV422:
cfg |= EXYNOS_CITRGFMT_OUTFORMAT_YCBCR422;
break;
case DRM_FORMAT_YUV420:
case DRM_FORMAT_YVU420:
case DRM_FORMAT_NV12:
case DRM_FORMAT_NV21:
cfg |= EXYNOS_CITRGFMT_OUTFORMAT_YCBCR420;
break;
}
fimc_write(ctx, cfg, EXYNOS_CITRGFMT);
}
cfg = fimc_read(ctx, EXYNOS_CIDMAPARAM);
cfg &= ~EXYNOS_CIDMAPARAM_W_MODE_MASK;
if (tiled)
cfg |= EXYNOS_CIDMAPARAM_W_MODE_64X32;
else
cfg |= EXYNOS_CIDMAPARAM_W_MODE_LINEAR;
fimc_write(ctx, cfg, EXYNOS_CIDMAPARAM);
fimc_dst_set_fmt_order(ctx, fmt);
}
static void fimc_dst_set_transf(struct fimc_context *ctx, unsigned int rotation)
{
unsigned int degree = rotation & DRM_MODE_ROTATE_MASK;
u32 cfg;
DRM_DEV_DEBUG_KMS(ctx->dev, "rotation[0x%x]\n", rotation);
cfg = fimc_read(ctx, EXYNOS_CITRGFMT);
cfg &= ~EXYNOS_CITRGFMT_FLIP_MASK;
cfg &= ~EXYNOS_CITRGFMT_OUTROT90_CLOCKWISE;
switch (degree) {
case DRM_MODE_ROTATE_0:
if (rotation & DRM_MODE_REFLECT_X)
cfg |= EXYNOS_CITRGFMT_FLIP_X_MIRROR;
if (rotation & DRM_MODE_REFLECT_Y)
cfg |= EXYNOS_CITRGFMT_FLIP_Y_MIRROR;
break;
case DRM_MODE_ROTATE_90:
cfg |= EXYNOS_CITRGFMT_OUTROT90_CLOCKWISE;
if (rotation & DRM_MODE_REFLECT_X)
cfg |= EXYNOS_CITRGFMT_FLIP_X_MIRROR;
if (rotation & DRM_MODE_REFLECT_Y)
cfg |= EXYNOS_CITRGFMT_FLIP_Y_MIRROR;
break;
case DRM_MODE_ROTATE_180:
cfg |= (EXYNOS_CITRGFMT_FLIP_X_MIRROR |
EXYNOS_CITRGFMT_FLIP_Y_MIRROR);
if (rotation & DRM_MODE_REFLECT_X)
cfg &= ~EXYNOS_CITRGFMT_FLIP_X_MIRROR;
if (rotation & DRM_MODE_REFLECT_Y)
cfg &= ~EXYNOS_CITRGFMT_FLIP_Y_MIRROR;
break;
case DRM_MODE_ROTATE_270:
cfg |= (EXYNOS_CITRGFMT_OUTROT90_CLOCKWISE |
EXYNOS_CITRGFMT_FLIP_X_MIRROR |
EXYNOS_CITRGFMT_FLIP_Y_MIRROR);
if (rotation & DRM_MODE_REFLECT_X)
cfg &= ~EXYNOS_CITRGFMT_FLIP_X_MIRROR;
if (rotation & DRM_MODE_REFLECT_Y)
cfg &= ~EXYNOS_CITRGFMT_FLIP_Y_MIRROR;
break;
}
fimc_write(ctx, cfg, EXYNOS_CITRGFMT);
}
static int fimc_set_prescaler(struct fimc_context *ctx, struct fimc_scaler *sc,
struct drm_exynos_ipp_task_rect *src,
struct drm_exynos_ipp_task_rect *dst)
{
u32 cfg, cfg_ext, shfactor;
u32 pre_dst_width, pre_dst_height;
u32 hfactor, vfactor;
int ret = 0;
u32 src_w, src_h, dst_w, dst_h;
cfg_ext = fimc_read(ctx, EXYNOS_CITRGFMT);
if (cfg_ext & EXYNOS_CITRGFMT_INROT90_CLOCKWISE) {
src_w = src->h;
src_h = src->w;
} else {
src_w = src->w;
src_h = src->h;
}
if (cfg_ext & EXYNOS_CITRGFMT_OUTROT90_CLOCKWISE) {
dst_w = dst->h;
dst_h = dst->w;
} else {
dst_w = dst->w;
dst_h = dst->h;
}
/* fimc_ippdrv_check_property assures that dividers are not null */
hfactor = fls(src_w / dst_w / 2);
if (hfactor > FIMC_SHFACTOR / 2) {
dev_err(ctx->dev, "failed to get ratio horizontal.\n");
return -EINVAL;
}
vfactor = fls(src_h / dst_h / 2);
if (vfactor > FIMC_SHFACTOR / 2) {
dev_err(ctx->dev, "failed to get ratio vertical.\n");
return -EINVAL;
}
pre_dst_width = src_w >> hfactor;
pre_dst_height = src_h >> vfactor;
DRM_DEV_DEBUG_KMS(ctx->dev, "pre_dst_width[%d]pre_dst_height[%d]\n",
pre_dst_width, pre_dst_height);
DRM_DEV_DEBUG_KMS(ctx->dev, "hfactor[%d]vfactor[%d]\n", hfactor,
vfactor);
sc->hratio = (src_w << 14) / (dst_w << hfactor);
sc->vratio = (src_h << 14) / (dst_h << vfactor);
sc->up_h = (dst_w >= src_w) ? true : false;
sc->up_v = (dst_h >= src_h) ? true : false;
DRM_DEV_DEBUG_KMS(ctx->dev, "hratio[%d]vratio[%d]up_h[%d]up_v[%d]\n",
sc->hratio, sc->vratio, sc->up_h, sc->up_v);
shfactor = FIMC_SHFACTOR - (hfactor + vfactor);
DRM_DEV_DEBUG_KMS(ctx->dev, "shfactor[%d]\n", shfactor);
cfg = (EXYNOS_CISCPRERATIO_SHFACTOR(shfactor) |
EXYNOS_CISCPRERATIO_PREHORRATIO(1 << hfactor) |
EXYNOS_CISCPRERATIO_PREVERRATIO(1 << vfactor));
fimc_write(ctx, cfg, EXYNOS_CISCPRERATIO);
cfg = (EXYNOS_CISCPREDST_PREDSTWIDTH(pre_dst_width) |
EXYNOS_CISCPREDST_PREDSTHEIGHT(pre_dst_height));
fimc_write(ctx, cfg, EXYNOS_CISCPREDST);
return ret;
}
static void fimc_set_scaler(struct fimc_context *ctx, struct fimc_scaler *sc)
{
u32 cfg, cfg_ext;
DRM_DEV_DEBUG_KMS(ctx->dev, "range[%d]bypass[%d]up_h[%d]up_v[%d]\n",
sc->range, sc->bypass, sc->up_h, sc->up_v);
DRM_DEV_DEBUG_KMS(ctx->dev, "hratio[%d]vratio[%d]\n",
sc->hratio, sc->vratio);
cfg = fimc_read(ctx, EXYNOS_CISCCTRL);
cfg &= ~(EXYNOS_CISCCTRL_SCALERBYPASS |
EXYNOS_CISCCTRL_SCALEUP_H | EXYNOS_CISCCTRL_SCALEUP_V |
EXYNOS_CISCCTRL_MAIN_V_RATIO_MASK |
EXYNOS_CISCCTRL_MAIN_H_RATIO_MASK |
EXYNOS_CISCCTRL_CSCR2Y_WIDE |
EXYNOS_CISCCTRL_CSCY2R_WIDE);
if (sc->range)
cfg |= (EXYNOS_CISCCTRL_CSCR2Y_WIDE |
EXYNOS_CISCCTRL_CSCY2R_WIDE);
if (sc->bypass)
cfg |= EXYNOS_CISCCTRL_SCALERBYPASS;
if (sc->up_h)
cfg |= EXYNOS_CISCCTRL_SCALEUP_H;
if (sc->up_v)
cfg |= EXYNOS_CISCCTRL_SCALEUP_V;
cfg |= (EXYNOS_CISCCTRL_MAINHORRATIO((sc->hratio >> 6)) |
EXYNOS_CISCCTRL_MAINVERRATIO((sc->vratio >> 6)));
fimc_write(ctx, cfg, EXYNOS_CISCCTRL);
cfg_ext = fimc_read(ctx, EXYNOS_CIEXTEN);
cfg_ext &= ~EXYNOS_CIEXTEN_MAINHORRATIO_EXT_MASK;
cfg_ext &= ~EXYNOS_CIEXTEN_MAINVERRATIO_EXT_MASK;
cfg_ext |= (EXYNOS_CIEXTEN_MAINHORRATIO_EXT(sc->hratio) |
EXYNOS_CIEXTEN_MAINVERRATIO_EXT(sc->vratio));
fimc_write(ctx, cfg_ext, EXYNOS_CIEXTEN);
}
static void fimc_dst_set_size(struct fimc_context *ctx,
struct exynos_drm_ipp_buffer *buf)
{
unsigned int real_width = buf->buf.pitch[0] / buf->format->cpp[0];
u32 cfg, cfg_ext;
DRM_DEV_DEBUG_KMS(ctx->dev, "hsize[%d]vsize[%d]\n", real_width,
buf->buf.height);
/* original size */
cfg = (EXYNOS_ORGOSIZE_HORIZONTAL(real_width) |
EXYNOS_ORGOSIZE_VERTICAL(buf->buf.height));
fimc_write(ctx, cfg, EXYNOS_ORGOSIZE);
DRM_DEV_DEBUG_KMS(ctx->dev, "x[%d]y[%d]w[%d]h[%d]\n", buf->rect.x,
buf->rect.y,
buf->rect.w, buf->rect.h);
/* CSC ITU */
cfg = fimc_read(ctx, EXYNOS_CIGCTRL);
cfg &= ~EXYNOS_CIGCTRL_CSC_MASK;
if (buf->buf.width >= FIMC_WIDTH_ITU_709)
cfg |= EXYNOS_CIGCTRL_CSC_ITU709;
else
cfg |= EXYNOS_CIGCTRL_CSC_ITU601;
fimc_write(ctx, cfg, EXYNOS_CIGCTRL);
cfg_ext = fimc_read(ctx, EXYNOS_CITRGFMT);
/* target image size */
cfg = fimc_read(ctx, EXYNOS_CITRGFMT);
cfg &= ~(EXYNOS_CITRGFMT_TARGETH_MASK |
EXYNOS_CITRGFMT_TARGETV_MASK);
if (cfg_ext & EXYNOS_CITRGFMT_OUTROT90_CLOCKWISE)
cfg |= (EXYNOS_CITRGFMT_TARGETHSIZE(buf->rect.h) |
EXYNOS_CITRGFMT_TARGETVSIZE(buf->rect.w));
else
cfg |= (EXYNOS_CITRGFMT_TARGETHSIZE(buf->rect.w) |
EXYNOS_CITRGFMT_TARGETVSIZE(buf->rect.h));
fimc_write(ctx, cfg, EXYNOS_CITRGFMT);
/* target area */
cfg = EXYNOS_CITAREA_TARGET_AREA(buf->rect.w * buf->rect.h);
fimc_write(ctx, cfg, EXYNOS_CITAREA);
/* offset Y(RGB), Cb, Cr */
cfg = (EXYNOS_CIOYOFF_HORIZONTAL(buf->rect.x) |
EXYNOS_CIOYOFF_VERTICAL(buf->rect.y));
fimc_write(ctx, cfg, EXYNOS_CIOYOFF);
cfg = (EXYNOS_CIOCBOFF_HORIZONTAL(buf->rect.x) |
EXYNOS_CIOCBOFF_VERTICAL(buf->rect.y));
fimc_write(ctx, cfg, EXYNOS_CIOCBOFF);
cfg = (EXYNOS_CIOCROFF_HORIZONTAL(buf->rect.x) |
EXYNOS_CIOCROFF_VERTICAL(buf->rect.y));
fimc_write(ctx, cfg, EXYNOS_CIOCROFF);
}
static void fimc_dst_set_buf_seq(struct fimc_context *ctx, u32 buf_id,
bool enqueue)
{
unsigned long flags;
u32 buf_num;
u32 cfg;
DRM_DEV_DEBUG_KMS(ctx->dev, "buf_id[%d]enqueu[%d]\n", buf_id, enqueue);
spin_lock_irqsave(&ctx->lock, flags);
cfg = fimc_read(ctx, EXYNOS_CIFCNTSEQ);
if (enqueue)
cfg |= (1 << buf_id);
else
cfg &= ~(1 << buf_id);
fimc_write(ctx, cfg, EXYNOS_CIFCNTSEQ);
buf_num = hweight32(cfg);
if (enqueue && buf_num >= FIMC_BUF_START)
fimc_mask_irq(ctx, true);
else if (!enqueue && buf_num <= FIMC_BUF_STOP)
fimc_mask_irq(ctx, false);
spin_unlock_irqrestore(&ctx->lock, flags);
}
static void fimc_dst_set_addr(struct fimc_context *ctx,
struct exynos_drm_ipp_buffer *buf)
{
fimc_write(ctx, buf->dma_addr[0], EXYNOS_CIOYSA(0));
fimc_write(ctx, buf->dma_addr[1], EXYNOS_CIOCBSA(0));
fimc_write(ctx, buf->dma_addr[2], EXYNOS_CIOCRSA(0));
fimc_dst_set_buf_seq(ctx, 0, true);
}
static void fimc_stop(struct fimc_context *ctx);
static irqreturn_t fimc_irq_handler(int irq, void *dev_id)
{
struct fimc_context *ctx = dev_id;
int buf_id;
DRM_DEV_DEBUG_KMS(ctx->dev, "fimc id[%d]\n", ctx->id);
fimc_clear_irq(ctx);
if (fimc_check_ovf(ctx))
return IRQ_NONE;
if (!fimc_check_frame_end(ctx))
return IRQ_NONE;
buf_id = fimc_get_buf_id(ctx);
if (buf_id < 0)
return IRQ_HANDLED;
DRM_DEV_DEBUG_KMS(ctx->dev, "buf_id[%d]\n", buf_id);
if (ctx->task) {
struct exynos_drm_ipp_task *task = ctx->task;
ctx->task = NULL;
pm_runtime_mark_last_busy(ctx->dev);
pm_runtime_put_autosuspend(ctx->dev);
exynos_drm_ipp_task_done(task, 0);
}
fimc_dst_set_buf_seq(ctx, buf_id, false);
fimc_stop(ctx);
return IRQ_HANDLED;
}
static void fimc_clear_addr(struct fimc_context *ctx)
{
int i;
for (i = 0; i < FIMC_MAX_SRC; i++) {
fimc_write(ctx, 0, EXYNOS_CIIYSA(i));
fimc_write(ctx, 0, EXYNOS_CIICBSA(i));
fimc_write(ctx, 0, EXYNOS_CIICRSA(i));
}
for (i = 0; i < FIMC_MAX_DST; i++) {
fimc_write(ctx, 0, EXYNOS_CIOYSA(i));
fimc_write(ctx, 0, EXYNOS_CIOCBSA(i));
fimc_write(ctx, 0, EXYNOS_CIOCRSA(i));
}
}
static void fimc_reset(struct fimc_context *ctx)
{
/* reset h/w block */
fimc_sw_reset(ctx);
/* reset scaler capability */
memset(&ctx->sc, 0x0, sizeof(ctx->sc));
fimc_clear_addr(ctx);
}
static void fimc_start(struct fimc_context *ctx)
{
u32 cfg0, cfg1;
fimc_mask_irq(ctx, true);
/* If set true, we can save jpeg about screen */
fimc_handle_jpeg(ctx, false);
fimc_set_scaler(ctx, &ctx->sc);
fimc_set_type_ctrl(ctx);
fimc_handle_lastend(ctx, false);
/* setup dma */
cfg0 = fimc_read(ctx, EXYNOS_MSCTRL);
cfg0 &= ~EXYNOS_MSCTRL_INPUT_MASK;
cfg0 |= EXYNOS_MSCTRL_INPUT_MEMORY;
fimc_write(ctx, cfg0, EXYNOS_MSCTRL);
/* Reset status */
fimc_write(ctx, 0x0, EXYNOS_CISTATUS);
cfg0 = fimc_read(ctx, EXYNOS_CIIMGCPT);
cfg0 &= ~EXYNOS_CIIMGCPT_IMGCPTEN_SC;
cfg0 |= EXYNOS_CIIMGCPT_IMGCPTEN_SC;
/* Scaler */
cfg1 = fimc_read(ctx, EXYNOS_CISCCTRL);
cfg1 &= ~EXYNOS_CISCCTRL_SCAN_MASK;
cfg1 |= (EXYNOS_CISCCTRL_PROGRESSIVE |
EXYNOS_CISCCTRL_SCALERSTART);
fimc_write(ctx, cfg1, EXYNOS_CISCCTRL);
/* Enable image capture*/
cfg0 |= EXYNOS_CIIMGCPT_IMGCPTEN;
fimc_write(ctx, cfg0, EXYNOS_CIIMGCPT);
/* Disable frame end irq */
fimc_clear_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_IRQ_END_DISABLE);
fimc_clear_bits(ctx, EXYNOS_CIOCTRL, EXYNOS_CIOCTRL_WEAVE_MASK);
fimc_set_bits(ctx, EXYNOS_MSCTRL, EXYNOS_MSCTRL_ENVID);
}
static void fimc_stop(struct fimc_context *ctx)
{
u32 cfg;
/* Source clear */
cfg = fimc_read(ctx, EXYNOS_MSCTRL);
cfg &= ~EXYNOS_MSCTRL_INPUT_MASK;
cfg &= ~EXYNOS_MSCTRL_ENVID;
fimc_write(ctx, cfg, EXYNOS_MSCTRL);
fimc_mask_irq(ctx, false);
/* reset sequence */
fimc_write(ctx, 0x0, EXYNOS_CIFCNTSEQ);
/* Scaler disable */
fimc_clear_bits(ctx, EXYNOS_CISCCTRL, EXYNOS_CISCCTRL_SCALERSTART);
/* Disable image capture */
fimc_clear_bits(ctx, EXYNOS_CIIMGCPT,
EXYNOS_CIIMGCPT_IMGCPTEN_SC | EXYNOS_CIIMGCPT_IMGCPTEN);
/* Enable frame end irq */
fimc_set_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_IRQ_END_DISABLE);
}
static int fimc_commit(struct exynos_drm_ipp *ipp,
struct exynos_drm_ipp_task *task)
{
struct fimc_context *ctx =
container_of(ipp, struct fimc_context, ipp);
pm_runtime_get_sync(ctx->dev);
ctx->task = task;
fimc_src_set_fmt(ctx, task->src.buf.fourcc, task->src.buf.modifier);
fimc_src_set_size(ctx, &task->src);
fimc_src_set_transf(ctx, DRM_MODE_ROTATE_0);
fimc_src_set_addr(ctx, &task->src);
fimc_dst_set_fmt(ctx, task->dst.buf.fourcc, task->dst.buf.modifier);
fimc_dst_set_transf(ctx, task->transform.rotation);
fimc_dst_set_size(ctx, &task->dst);
fimc_dst_set_addr(ctx, &task->dst);
fimc_set_prescaler(ctx, &ctx->sc, &task->src.rect, &task->dst.rect);
fimc_start(ctx);
return 0;
}
static void fimc_abort(struct exynos_drm_ipp *ipp,
struct exynos_drm_ipp_task *task)
{
struct fimc_context *ctx =
container_of(ipp, struct fimc_context, ipp);
fimc_reset(ctx);
if (ctx->task) {
struct exynos_drm_ipp_task *task = ctx->task;
ctx->task = NULL;
pm_runtime_mark_last_busy(ctx->dev);
pm_runtime_put_autosuspend(ctx->dev);
exynos_drm_ipp_task_done(task, -EIO);
}
}
static struct exynos_drm_ipp_funcs ipp_funcs = {
.commit = fimc_commit,
.abort = fimc_abort,
};
static int fimc_bind(struct device *dev, struct device *master, void *data)
{
struct fimc_context *ctx = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
struct exynos_drm_ipp *ipp = &ctx->ipp;
ctx->drm_dev = drm_dev;
ipp->drm_dev = drm_dev;
exynos_drm_register_dma(drm_dev, dev);
exynos_drm_ipp_register(dev, ipp, &ipp_funcs,
DRM_EXYNOS_IPP_CAP_CROP | DRM_EXYNOS_IPP_CAP_ROTATE |
DRM_EXYNOS_IPP_CAP_SCALE | DRM_EXYNOS_IPP_CAP_CONVERT,
ctx->formats, ctx->num_formats, "fimc");
dev_info(dev, "The exynos fimc has been probed successfully\n");
return 0;
}
static void fimc_unbind(struct device *dev, struct device *master,
void *data)
{
struct fimc_context *ctx = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
struct exynos_drm_ipp *ipp = &ctx->ipp;
exynos_drm_ipp_unregister(dev, ipp);
exynos_drm_unregister_dma(drm_dev, dev);
}
static const struct component_ops fimc_component_ops = {
.bind = fimc_bind,
.unbind = fimc_unbind,
};
static void fimc_put_clocks(struct fimc_context *ctx)
{
int i;
for (i = 0; i < FIMC_CLKS_MAX; i++) {
if (IS_ERR(ctx->clocks[i]))
continue;
clk_put(ctx->clocks[i]);
ctx->clocks[i] = ERR_PTR(-EINVAL);
}
}
static int fimc_setup_clocks(struct fimc_context *ctx)
{
struct device *fimc_dev = ctx->dev;
struct device *dev;
int ret, i;
for (i = 0; i < FIMC_CLKS_MAX; i++)
ctx->clocks[i] = ERR_PTR(-EINVAL);
for (i = 0; i < FIMC_CLKS_MAX; i++) {
if (i == FIMC_CLK_WB_A || i == FIMC_CLK_WB_B)
dev = fimc_dev->parent;
else
dev = fimc_dev;
ctx->clocks[i] = clk_get(dev, fimc_clock_names[i]);
if (IS_ERR(ctx->clocks[i])) {
ret = PTR_ERR(ctx->clocks[i]);
dev_err(fimc_dev, "failed to get clock: %s\n",
fimc_clock_names[i]);
goto e_clk_free;
}
}
ret = clk_prepare_enable(ctx->clocks[FIMC_CLK_LCLK]);
if (!ret)
return ret;
e_clk_free:
fimc_put_clocks(ctx);
return ret;
}
int exynos_drm_check_fimc_device(struct device *dev)
{
int id = of_alias_get_id(dev->of_node, "fimc");
if (id >= 0 && (BIT(id) & fimc_mask))
return 0;
return -ENODEV;
}
static const unsigned int fimc_formats[] = {
DRM_FORMAT_XRGB8888, 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,
DRM_FORMAT_YUV444,
};
static const unsigned int fimc_tiled_formats[] = {
DRM_FORMAT_NV12, DRM_FORMAT_NV21,
};
static const struct drm_exynos_ipp_limit fimc_4210_limits_v1[] = {
{ IPP_SIZE_LIMIT(BUFFER, .h = { 16, 8192, 8 }, .v = { 16, 8192, 2 }) },
{ IPP_SIZE_LIMIT(AREA, .h = { 16, 4224, 2 }, .v = { 16, 0, 2 }) },
{ IPP_SIZE_LIMIT(ROTATED, .h = { 128, 1920 }, .v = { 128, 0 }) },
{ IPP_SCALE_LIMIT(.h = { (1 << 16) / 64, (1 << 16) * 64 },
.v = { (1 << 16) / 64, (1 << 16) * 64 }) },
};
static const struct drm_exynos_ipp_limit fimc_4210_limits_v2[] = {
{ IPP_SIZE_LIMIT(BUFFER, .h = { 16, 8192, 8 }, .v = { 16, 8192, 2 }) },
{ IPP_SIZE_LIMIT(AREA, .h = { 16, 1920, 2 }, .v = { 16, 0, 2 }) },
{ IPP_SIZE_LIMIT(ROTATED, .h = { 128, 1366 }, .v = { 128, 0 }) },
{ IPP_SCALE_LIMIT(.h = { (1 << 16) / 64, (1 << 16) * 64 },
.v = { (1 << 16) / 64, (1 << 16) * 64 }) },
};
static const struct drm_exynos_ipp_limit fimc_4210_limits_tiled_v1[] = {
{ IPP_SIZE_LIMIT(BUFFER, .h = { 128, 1920, 128 }, .v = { 32, 1920, 32 }) },
{ IPP_SIZE_LIMIT(AREA, .h = { 128, 1920, 2 }, .v = { 128, 0, 2 }) },
{ IPP_SCALE_LIMIT(.h = { (1 << 16) / 64, (1 << 16) * 64 },
.v = { (1 << 16) / 64, (1 << 16) * 64 }) },
};
static const struct drm_exynos_ipp_limit fimc_4210_limits_tiled_v2[] = {
{ IPP_SIZE_LIMIT(BUFFER, .h = { 128, 1920, 128 }, .v = { 32, 1920, 32 }) },
{ IPP_SIZE_LIMIT(AREA, .h = { 128, 1366, 2 }, .v = { 128, 0, 2 }) },
{ IPP_SCALE_LIMIT(.h = { (1 << 16) / 64, (1 << 16) * 64 },
.v = { (1 << 16) / 64, (1 << 16) * 64 }) },
};
static int fimc_probe(struct platform_device *pdev)
{
const struct drm_exynos_ipp_limit *limits;
struct exynos_drm_ipp_formats *formats;
struct device *dev = &pdev->dev;
struct fimc_context *ctx;
struct resource *res;
int ret;
int i, j, num_limits, num_formats;
if (exynos_drm_check_fimc_device(dev) != 0)
return -ENODEV;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->dev = dev;
ctx->id = of_alias_get_id(dev->of_node, "fimc");
/* construct formats/limits array */
num_formats = ARRAY_SIZE(fimc_formats) + ARRAY_SIZE(fimc_tiled_formats);
formats = devm_kcalloc(dev, num_formats, sizeof(*formats),
GFP_KERNEL);
if (!formats)
return -ENOMEM;
/* linear formats */
if (ctx->id < 3) {
limits = fimc_4210_limits_v1;
num_limits = ARRAY_SIZE(fimc_4210_limits_v1);
} else {
limits = fimc_4210_limits_v2;
num_limits = ARRAY_SIZE(fimc_4210_limits_v2);
}
for (i = 0; i < ARRAY_SIZE(fimc_formats); i++) {
formats[i].fourcc = fimc_formats[i];
formats[i].type = DRM_EXYNOS_IPP_FORMAT_SOURCE |
DRM_EXYNOS_IPP_FORMAT_DESTINATION;
formats[i].limits = limits;
formats[i].num_limits = num_limits;
}
/* tiled formats */
if (ctx->id < 3) {
limits = fimc_4210_limits_tiled_v1;
num_limits = ARRAY_SIZE(fimc_4210_limits_tiled_v1);
} else {
limits = fimc_4210_limits_tiled_v2;
num_limits = ARRAY_SIZE(fimc_4210_limits_tiled_v2);
}
for (j = i, i = 0; i < ARRAY_SIZE(fimc_tiled_formats); j++, i++) {
formats[j].fourcc = fimc_tiled_formats[i];
formats[j].modifier = DRM_FORMAT_MOD_SAMSUNG_64_32_TILE;
formats[j].type = DRM_EXYNOS_IPP_FORMAT_SOURCE |
DRM_EXYNOS_IPP_FORMAT_DESTINATION;
formats[j].limits = limits;
formats[j].num_limits = num_limits;
}
ctx->formats = formats;
ctx->num_formats = num_formats;
/* resource memory */
ctx->regs_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ctx->regs = devm_ioremap_resource(dev, ctx->regs_res);
if (IS_ERR(ctx->regs))
return PTR_ERR(ctx->regs);
/* resource irq */
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(dev, "failed to request irq resource.\n");
return -ENOENT;
}
ret = devm_request_irq(dev, res->start, fimc_irq_handler,
0, dev_name(dev), ctx);
if (ret < 0) {
dev_err(dev, "failed to request irq.\n");
return ret;
}
ret = fimc_setup_clocks(ctx);
if (ret < 0)
return ret;
spin_lock_init(&ctx->lock);
platform_set_drvdata(pdev, ctx);
pm_runtime_use_autosuspend(dev);
pm_runtime_set_autosuspend_delay(dev, FIMC_AUTOSUSPEND_DELAY);
pm_runtime_enable(dev);
ret = component_add(dev, &fimc_component_ops);
if (ret)
goto err_pm_dis;
dev_info(dev, "drm fimc registered successfully.\n");
return 0;
err_pm_dis:
pm_runtime_dont_use_autosuspend(dev);
pm_runtime_disable(dev);
fimc_put_clocks(ctx);
return ret;
}
static int fimc_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct fimc_context *ctx = get_fimc_context(dev);
component_del(dev, &fimc_component_ops);
pm_runtime_dont_use_autosuspend(dev);
pm_runtime_disable(dev);
fimc_put_clocks(ctx);
return 0;
}
#ifdef CONFIG_PM
static int fimc_runtime_suspend(struct device *dev)
{
struct fimc_context *ctx = get_fimc_context(dev);
DRM_DEV_DEBUG_KMS(dev, "id[%d]\n", ctx->id);
clk_disable_unprepare(ctx->clocks[FIMC_CLK_GATE]);
return 0;
}
static int fimc_runtime_resume(struct device *dev)
{
struct fimc_context *ctx = get_fimc_context(dev);
DRM_DEV_DEBUG_KMS(dev, "id[%d]\n", ctx->id);
return clk_prepare_enable(ctx->clocks[FIMC_CLK_GATE]);
}
#endif
static const struct dev_pm_ops fimc_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(fimc_runtime_suspend, fimc_runtime_resume, NULL)
};
static const struct of_device_id fimc_of_match[] = {
{ .compatible = "samsung,exynos4210-fimc" },
{ .compatible = "samsung,exynos4212-fimc" },
{ },
};
MODULE_DEVICE_TABLE(of, fimc_of_match);
struct platform_driver fimc_driver = {
.probe = fimc_probe,
.remove = fimc_remove,
.driver = {
.of_match_table = fimc_of_match,
.name = "exynos-drm-fimc",
.owner = THIS_MODULE,
.pm = &fimc_pm_ops,
},
};