#include <linux/kernel.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 <drm/drmP.h>
#include <drm/exynos_drm.h>
#include "regs-fimc.h"
#include "exynos_drm_drv.h"
#include "exynos_drm_ipp.h"
#include "exynos_drm_fimc.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.
*
* M2M operation : supports crop/scale/rotation/csc so on.
* Memory ----> FIMC H/W ----> Memory.
* Writeback operation : supports cloned screen with FIMD.
* FIMD ----> FIMC H/W ----> Memory.
* Output operation : supports direct display using local path.
* Memory ----> FIMC H/W ----> FIMD.
*/
/*
* TODO
* 1. check suspend/resume api if needed.
* 2. need to check use case platform_device_id.
* 3. check src/dst size with, height.
* 4. added check_prepare api for right register.
* 5. need to add supported list in prop_list.
* 6. check prescaler/scaler optimization.
*/
#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_REG_SZ 32
#define FIMC_WIDTH_ITU_709 1280
#define FIMC_REFRESH_MAX 60
#define FIMC_REFRESH_MIN 12
#define FIMC_CROP_MAX 8192
#define FIMC_CROP_MIN 32
#define FIMC_SCALE_MAX 4224
#define FIMC_SCALE_MIN 32
#define get_fimc_context(dev) platform_get_drvdata(to_platform_device(dev))
#define get_ctx_from_ippdrv(ippdrv) container_of(ippdrv,\
struct fimc_context, ippdrv);
#define fimc_read(offset) readl(ctx->regs + (offset))
#define fimc_write(cfg, offset) writel(cfg, ctx->regs + (offset))
enum fimc_wb {
FIMC_WB_NONE,
FIMC_WB_A,
FIMC_WB_B,
};
enum {
FIMC_CLK_LCLK,
FIMC_CLK_GATE,
FIMC_CLK_WB_A,
FIMC_CLK_WB_B,
FIMC_CLK_MUX,
FIMC_CLK_PARENT,
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",
[FIMC_CLK_MUX] = "mux",
[FIMC_CLK_PARENT] = "parent",
};
#define FIMC_DEFAULT_LCLK_FREQUENCY 133000000UL
/*
* 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 scaler capability.
*
* find user manual table 43-1.
* @in_hori: scaler input horizontal size.
* @bypass: scaler bypass mode.
* @dst_h_wo_rot: target horizontal size without output rotation.
* @dst_h_rot: target horizontal size with output rotation.
* @rl_w_wo_rot: real width without input rotation.
* @rl_h_rot: real height without output rotation.
*/
struct fimc_capability {
/* scaler */
u32 in_hori;
u32 bypass;
/* output rotator */
u32 dst_h_wo_rot;
u32 dst_h_rot;
/* input rotator */
u32 rl_w_wo_rot;
u32 rl_h_rot;
};
/*
* A structure of fimc context.
*
* @ippdrv: prepare initialization using ippdrv.
* @regs_res: register resources.
* @regs: memory mapped io registers.
* @lock: locking of operations.
* @clocks: fimc clocks.
* @clk_frequency: LCLK clock frequency.
* @sysreg: handle to SYSREG block regmap.
* @sc: scaler infomations.
* @pol: porarity of writeback.
* @id: fimc id.
* @irq: irq number.
* @suspended: qos operations.
*/
struct fimc_context {
struct exynos_drm_ippdrv ippdrv;
struct resource *regs_res;
void __iomem *regs;
struct mutex lock;
struct clk *clocks[FIMC_CLKS_MAX];
u32 clk_frequency;
struct regmap *sysreg;
struct fimc_scaler sc;
struct exynos_drm_ipp_pol pol;
int id;
int irq;
bool suspended;
};
static void fimc_sw_reset(struct fimc_context *ctx)
{
u32 cfg;
/* stop dma operation */
cfg = fimc_read(EXYNOS_CISTATUS);
if (EXYNOS_CISTATUS_GET_ENVID_STATUS(cfg)) {
cfg = fimc_read(EXYNOS_MSCTRL);
cfg &= ~EXYNOS_MSCTRL_ENVID;
fimc_write(cfg, EXYNOS_MSCTRL);
}
cfg = fimc_read(EXYNOS_CISRCFMT);
cfg |= EXYNOS_CISRCFMT_ITU601_8BIT;
fimc_write(cfg, EXYNOS_CISRCFMT);
/* disable image capture */
cfg = fimc_read(EXYNOS_CIIMGCPT);
cfg &= ~(EXYNOS_CIIMGCPT_IMGCPTEN_SC | EXYNOS_CIIMGCPT_IMGCPTEN);
fimc_write(cfg, EXYNOS_CIIMGCPT);
/* s/w reset */
cfg = fimc_read(EXYNOS_CIGCTRL);
cfg |= (EXYNOS_CIGCTRL_SWRST);
fimc_write(cfg, EXYNOS_CIGCTRL);
/* s/w reset complete */
cfg = fimc_read(EXYNOS_CIGCTRL);
cfg &= ~EXYNOS_CIGCTRL_SWRST;
fimc_write(cfg, EXYNOS_CIGCTRL);
/* reset sequence */
fimc_write(0x0, EXYNOS_CIFCNTSEQ);
}
static int fimc_set_camblk_fimd0_wb(struct fimc_context *ctx)
{
return regmap_update_bits(ctx->sysreg, SYSREG_CAMERA_BLK,
SYSREG_FIMD0WB_DEST_MASK,
ctx->id << SYSREG_FIMD0WB_DEST_SHIFT);
}
static void fimc_set_type_ctrl(struct fimc_context *ctx, enum fimc_wb wb)
{
u32 cfg;
DRM_DEBUG_KMS("wb[%d]\n", wb);
cfg = fimc_read(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);
switch (wb) {
case FIMC_WB_A:
cfg |= (EXYNOS_CIGCTRL_SELWRITEBACK_A |
EXYNOS_CIGCTRL_SELWB_CAMIF_WRITEBACK);
break;
case FIMC_WB_B:
cfg |= (EXYNOS_CIGCTRL_SELWRITEBACK_B |
EXYNOS_CIGCTRL_SELWB_CAMIF_WRITEBACK);
break;
case FIMC_WB_NONE:
default:
cfg |= (EXYNOS_CIGCTRL_SELCAM_ITU_A |
EXYNOS_CIGCTRL_SELWRITEBACK_A |
EXYNOS_CIGCTRL_SELCAM_MIPI_A |
EXYNOS_CIGCTRL_SELCAM_FIMC_ITU);
break;
}
fimc_write(cfg, EXYNOS_CIGCTRL);
}
static void fimc_set_polarity(struct fimc_context *ctx,
struct exynos_drm_ipp_pol *pol)
{
u32 cfg;
DRM_DEBUG_KMS("inv_pclk[%d]inv_vsync[%d]\n",
pol->inv_pclk, pol->inv_vsync);
DRM_DEBUG_KMS("inv_href[%d]inv_hsync[%d]\n",
pol->inv_href, pol->inv_hsync);
cfg = fimc_read(EXYNOS_CIGCTRL);
cfg &= ~(EXYNOS_CIGCTRL_INVPOLPCLK | EXYNOS_CIGCTRL_INVPOLVSYNC |
EXYNOS_CIGCTRL_INVPOLHREF | EXYNOS_CIGCTRL_INVPOLHSYNC);
if (pol->inv_pclk)
cfg |= EXYNOS_CIGCTRL_INVPOLPCLK;
if (pol->inv_vsync)
cfg |= EXYNOS_CIGCTRL_INVPOLVSYNC;
if (pol->inv_href)
cfg |= EXYNOS_CIGCTRL_INVPOLHREF;
if (pol->inv_hsync)
cfg |= EXYNOS_CIGCTRL_INVPOLHSYNC;
fimc_write(cfg, EXYNOS_CIGCTRL);
}
static void fimc_handle_jpeg(struct fimc_context *ctx, bool enable)
{
u32 cfg;
DRM_DEBUG_KMS("enable[%d]\n", enable);
cfg = fimc_read(EXYNOS_CIGCTRL);
if (enable)
cfg |= EXYNOS_CIGCTRL_CAM_JPEG;
else
cfg &= ~EXYNOS_CIGCTRL_CAM_JPEG;
fimc_write(cfg, EXYNOS_CIGCTRL);
}
static void fimc_handle_irq(struct fimc_context *ctx, bool enable,
bool overflow, bool level)
{
u32 cfg;
DRM_DEBUG_KMS("enable[%d]overflow[%d]level[%d]\n",
enable, overflow, level);
cfg = fimc_read(EXYNOS_CIGCTRL);
if (enable) {
cfg &= ~(EXYNOS_CIGCTRL_IRQ_OVFEN | EXYNOS_CIGCTRL_IRQ_LEVEL);
cfg |= EXYNOS_CIGCTRL_IRQ_ENABLE;
if (overflow)
cfg |= EXYNOS_CIGCTRL_IRQ_OVFEN;
if (level)
cfg |= EXYNOS_CIGCTRL_IRQ_LEVEL;
} else
cfg &= ~(EXYNOS_CIGCTRL_IRQ_OVFEN | EXYNOS_CIGCTRL_IRQ_ENABLE);
fimc_write(cfg, EXYNOS_CIGCTRL);
}
static void fimc_clear_irq(struct fimc_context *ctx)
{
u32 cfg;
cfg = fimc_read(EXYNOS_CIGCTRL);
cfg |= EXYNOS_CIGCTRL_IRQ_CLR;
fimc_write(cfg, EXYNOS_CIGCTRL);
}
static bool fimc_check_ovf(struct fimc_context *ctx)
{
struct exynos_drm_ippdrv *ippdrv = &ctx->ippdrv;
u32 cfg, status, flag;
status = fimc_read(EXYNOS_CISTATUS);
flag = EXYNOS_CISTATUS_OVFIY | EXYNOS_CISTATUS_OVFICB |
EXYNOS_CISTATUS_OVFICR;
DRM_DEBUG_KMS("flag[0x%x]\n", flag);
if (status & flag) {
cfg = fimc_read(EXYNOS_CIWDOFST);
cfg |= (EXYNOS_CIWDOFST_CLROVFIY | EXYNOS_CIWDOFST_CLROVFICB |
EXYNOS_CIWDOFST_CLROVFICR);