// SPDX-License-Identifier: GPL-2.0
/*
* Allwinner sun8i deinterlacer with scaler driver
*
* Copyright (C) 2019 Jernej Skrabec <jernej.skrabec@siol.net>
*
* Based on vim2m driver.
*/
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mem2mem.h>
#include "sun8i-di.h"
#define FLAG_SIZE (DEINTERLACE_MAX_WIDTH * DEINTERLACE_MAX_HEIGHT / 4)
static u32 deinterlace_formats[] = {
V4L2_PIX_FMT_NV12,
V4L2_PIX_FMT_NV21,
};
static inline u32 deinterlace_read(struct deinterlace_dev *dev, u32 reg)
{
return readl(dev->base + reg);
}
static inline void deinterlace_write(struct deinterlace_dev *dev,
u32 reg, u32 value)
{
writel(value, dev->base + reg);
}
static inline void deinterlace_set_bits(struct deinterlace_dev *dev,
u32 reg, u32 bits)
{
writel(readl(dev->base + reg) | bits, dev->base + reg);
}
static inline void deinterlace_clr_set_bits(struct deinterlace_dev *dev,
u32 reg, u32 clr, u32 set)
{
u32 val = readl(dev->base + reg);
val &= ~clr;
val |= set;
writel(val, dev->base + reg);
}
static void deinterlace_device_run(void *priv)
{
struct deinterlace_ctx *ctx = priv;
struct deinterlace_dev *dev = ctx->dev;
u32 size, stride, width, height, val;
struct vb2_v4l2_buffer *src, *dst;
unsigned int hstep, vstep;
dma_addr_t addr;
int i;
src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
v4l2_m2m_buf_copy_metadata(src, dst, true);
deinterlace_write(dev, DEINTERLACE_MOD_ENABLE,
DEINTERLACE_MOD_ENABLE_EN);
if (ctx->field) {
deinterlace_write(dev, DEINTERLACE_TILE_FLAG0,
ctx->flag1_buf_dma);
deinterlace_write(dev, DEINTERLACE_TILE_FLAG1,
ctx->flag2_buf_dma);
} else {
deinterlace_write(dev, DEINTERLACE_TILE_FLAG0,
ctx->flag2_buf_dma);
deinterlace_write(dev, DEINTERLACE_TILE_FLAG1,
ctx->flag1_buf_dma);
}
deinterlace_write(dev, DEINTERLACE_FLAG_LINE_STRIDE, 0x200);
width = ctx->src_fmt.width;
height = ctx->src_fmt.height;
stride = ctx->src_fmt.bytesperline;
size = stride * height;
addr = vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 0);
deinterlace_write(dev, DEINTERLACE_BUF_ADDR0, addr);
deinterlace_write(dev, DEINTERLACE_BUF_ADDR1, addr + size);
deinterlace_write(dev, DEINTERLACE_BUF_ADDR2, 0);
deinterlace_write(dev, DEINTERLACE_LINE_STRIDE0, stride);
deinterlace_write(dev, DEINTERLACE_LINE_STRIDE1, stride);
deinterlace_write(dev, DEINTERLACE_CH0_IN_SIZE,
DEINTERLACE_SIZE(width, height));
deinterlace_write(dev, DEINTERLACE_CH1_IN_SIZE,
DEINTERLACE_SIZE(width / 2, height / 2));
val = DEINTERLACE_IN_FMT_FMT(DEINTERLACE_IN_FMT_YUV420) |
DEINTERLACE_IN_FMT_MOD(DEINTERLACE_MODE_UV_COMBINED);
switch (ctx->src_fmt.pixelformat) {
case V4L2_PIX_FMT_NV12:
val |= DEINTERLACE_IN_FMT_PS(DEINTERLACE_PS_UVUV);
break;
case V4L2_PIX_FMT_NV21:
val |= DEINTERLACE_IN_FMT_PS(DEINTERLACE_PS_VUVU);
break;
}
deinterlace_write(dev, DEINTERLACE_IN_FMT, val);
if (ctx->prev)
addr = vb2_dma_contig_plane_dma_addr(&ctx->prev->vb2_buf, 0);
deinterlace_write(dev, DEINTERLACE_PRELUMA, addr);
deinterlace_write(dev, DEINTERLACE_PRECHROMA, addr + size);
val = DEINTERLACE_OUT_FMT_FMT(DEINTERLACE_OUT_FMT_YUV420SP);
switch (ctx->src_fmt.pixelformat) {
case V4L2_PIX_FMT_NV12:
val |= DEINTERLACE_OUT_FMT_PS(DEINTERLACE_PS_UVUV);
break;
case V4L2_PIX_FMT_NV21:
val |= DEINTERLACE_OUT_FMT_PS(DEINTERLACE_PS_VUVU);
break;
}
deinterlace_write(dev, DEINTERLACE_OUT_FMT, val);
width = ctx->dst_fmt.width;
height = ctx->dst_fmt.height;
stride = ctx->dst_fmt.bytesperline;
size = stride * height;
deinterlace_write(dev, DEINTERLACE_CH0_OUT_SIZE,
DEINTERLACE_SIZE(width, height));
deinterlace_write(dev, DEINTERLACE_CH1_OUT_SIZE,
DEINTERLACE_SIZE(width / 2, height / 2));
deinterlace_write(dev, DEINTERLACE_WB_LINE_STRIDE0, stride);
deinterlace_write(dev, DEINTERLACE_WB_LINE_STRIDE1, stride);
addr = vb2_dma_contig_plane_dma_addr(&dst->vb2_buf, 0);
deinterlace_write(dev, DEINTERLACE_WB_ADDR0, addr);
deinterlace_write(dev, DEINTERLACE_WB_ADDR1, addr + size);
deinterlace_write(dev, DEINTERLACE_WB_ADDR2, 0);
hstep = (ctx->src_fmt.width << 16) / ctx->dst_fmt.width;
vstep = (ctx->src_fmt.height << 16) / ctx->dst_fmt.height;
deinterlace_write(dev<