Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-only
config IMX_IPUV3_CORE
tristate "IPUv3 core support"
depends on SOC_IMX5 || SOC_IMX6Q || COMPILE_TEST
depends on DRM || !DRM # if DRM=m, this can't be 'y'
select BITREVERSE
select GENERIC_ALLOCATOR if DRM
select GENERIC_IRQ_CHIP
help
Choose this if you have a i.MX5/6 system and want to use the Image
Processing Unit. This option only enables IPU base support.
+10
View File
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_IMX_IPUV3_CORE) += imx-ipu-v3.o
imx-ipu-v3-objs := ipu-common.o ipu-cpmem.o ipu-csi.o ipu-dc.o ipu-di.o \
ipu-dp.o ipu-dmfc.o ipu-ic.o ipu-ic-csc.o \
ipu-image-convert.o ipu-smfc.o ipu-vdi.o
ifdef CONFIG_DRM
imx-ipu-v3-objs += ipu-pre.o ipu-prg.o
endif
File diff suppressed because it is too large Load Diff
+953
View File
@@ -0,0 +1,953 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2012 Mentor Graphics Inc.
* Copyright 2005-2012 Freescale Semiconductor, Inc. All Rights Reserved.
*/
#include <linux/types.h>
#include <linux/bitrev.h>
#include <linux/io.h>
#include <linux/sizes.h>
#include <drm/drm_fourcc.h>
#include "ipu-prv.h"
struct ipu_cpmem_word {
u32 data[5];
u32 res[3];
};
struct ipu_ch_param {
struct ipu_cpmem_word word[2];
};
struct ipu_cpmem {
struct ipu_ch_param __iomem *base;
u32 module;
spinlock_t lock;
int use_count;
struct ipu_soc *ipu;
};
#define IPU_CPMEM_WORD(word, ofs, size) ((((word) * 160 + (ofs)) << 8) | (size))
#define IPU_FIELD_UBO IPU_CPMEM_WORD(0, 46, 22)
#define IPU_FIELD_VBO IPU_CPMEM_WORD(0, 68, 22)
#define IPU_FIELD_IOX IPU_CPMEM_WORD(0, 90, 4)
#define IPU_FIELD_RDRW IPU_CPMEM_WORD(0, 94, 1)
#define IPU_FIELD_SO IPU_CPMEM_WORD(0, 113, 1)
#define IPU_FIELD_SLY IPU_CPMEM_WORD(1, 102, 14)
#define IPU_FIELD_SLUV IPU_CPMEM_WORD(1, 128, 14)
#define IPU_FIELD_XV IPU_CPMEM_WORD(0, 0, 10)
#define IPU_FIELD_YV IPU_CPMEM_WORD(0, 10, 9)
#define IPU_FIELD_XB IPU_CPMEM_WORD(0, 19, 13)
#define IPU_FIELD_YB IPU_CPMEM_WORD(0, 32, 12)
#define IPU_FIELD_NSB_B IPU_CPMEM_WORD(0, 44, 1)
#define IPU_FIELD_CF IPU_CPMEM_WORD(0, 45, 1)
#define IPU_FIELD_SX IPU_CPMEM_WORD(0, 46, 12)
#define IPU_FIELD_SY IPU_CPMEM_WORD(0, 58, 11)
#define IPU_FIELD_NS IPU_CPMEM_WORD(0, 69, 10)
#define IPU_FIELD_SDX IPU_CPMEM_WORD(0, 79, 7)
#define IPU_FIELD_SM IPU_CPMEM_WORD(0, 86, 10)
#define IPU_FIELD_SCC IPU_CPMEM_WORD(0, 96, 1)
#define IPU_FIELD_SCE IPU_CPMEM_WORD(0, 97, 1)
#define IPU_FIELD_SDY IPU_CPMEM_WORD(0, 98, 7)
#define IPU_FIELD_SDRX IPU_CPMEM_WORD(0, 105, 1)
#define IPU_FIELD_SDRY IPU_CPMEM_WORD(0, 106, 1)
#define IPU_FIELD_BPP IPU_CPMEM_WORD(0, 107, 3)
#define IPU_FIELD_DEC_SEL IPU_CPMEM_WORD(0, 110, 2)
#define IPU_FIELD_DIM IPU_CPMEM_WORD(0, 112, 1)
#define IPU_FIELD_BNDM IPU_CPMEM_WORD(0, 114, 3)
#define IPU_FIELD_BM IPU_CPMEM_WORD(0, 117, 2)
#define IPU_FIELD_ROT IPU_CPMEM_WORD(0, 119, 1)
#define IPU_FIELD_ROT_HF_VF IPU_CPMEM_WORD(0, 119, 3)
#define IPU_FIELD_HF IPU_CPMEM_WORD(0, 120, 1)
#define IPU_FIELD_VF IPU_CPMEM_WORD(0, 121, 1)
#define IPU_FIELD_THE IPU_CPMEM_WORD(0, 122, 1)
#define IPU_FIELD_CAP IPU_CPMEM_WORD(0, 123, 1)
#define IPU_FIELD_CAE IPU_CPMEM_WORD(0, 124, 1)
#define IPU_FIELD_FW IPU_CPMEM_WORD(0, 125, 13)
#define IPU_FIELD_FH IPU_CPMEM_WORD(0, 138, 12)
#define IPU_FIELD_EBA0 IPU_CPMEM_WORD(1, 0, 29)
#define IPU_FIELD_EBA1 IPU_CPMEM_WORD(1, 29, 29)
#define IPU_FIELD_ILO IPU_CPMEM_WORD(1, 58, 20)
#define IPU_FIELD_NPB IPU_CPMEM_WORD(1, 78, 7)
#define IPU_FIELD_PFS IPU_CPMEM_WORD(1, 85, 4)
#define IPU_FIELD_ALU IPU_CPMEM_WORD(1, 89, 1)
#define IPU_FIELD_ALBM IPU_CPMEM_WORD(1, 90, 3)
#define IPU_FIELD_ID IPU_CPMEM_WORD(1, 93, 2)
#define IPU_FIELD_TH IPU_CPMEM_WORD(1, 95, 7)
#define IPU_FIELD_SL IPU_CPMEM_WORD(1, 102, 14)
#define IPU_FIELD_WID0 IPU_CPMEM_WORD(1, 116, 3)
#define IPU_FIELD_WID1 IPU_CPMEM_WORD(1, 119, 3)
#define IPU_FIELD_WID2 IPU_CPMEM_WORD(1, 122, 3)
#define IPU_FIELD_WID3 IPU_CPMEM_WORD(1, 125, 3)
#define IPU_FIELD_OFS0 IPU_CPMEM_WORD(1, 128, 5)
#define IPU_FIELD_OFS1 IPU_CPMEM_WORD(1, 133, 5)
#define IPU_FIELD_OFS2 IPU_CPMEM_WORD(1, 138, 5)
#define IPU_FIELD_OFS3 IPU_CPMEM_WORD(1, 143, 5)
#define IPU_FIELD_SXYS IPU_CPMEM_WORD(1, 148, 1)
#define IPU_FIELD_CRE IPU_CPMEM_WORD(1, 149, 1)
#define IPU_FIELD_DEC_SEL2 IPU_CPMEM_WORD(1, 150, 1)
static inline struct ipu_ch_param __iomem *
ipu_get_cpmem(struct ipuv3_channel *ch)
{
struct ipu_cpmem *cpmem = ch->ipu->cpmem_priv;
return cpmem->base + ch->num;
}
static void ipu_ch_param_write_field(struct ipuv3_channel *ch, u32 wbs, u32 v)
{
struct ipu_ch_param __iomem *base = ipu_get_cpmem(ch);
u32 bit = (wbs >> 8) % 160;
u32 size = wbs & 0xff;
u32 word = (wbs >> 8) / 160;
u32 i = bit / 32;
u32 ofs = bit % 32;
u32 mask = (1 << size) - 1;
u32 val;
pr_debug("%s %d %d %d\n", __func__, word, bit , size);
val = readl(&base->word[word].data[i]);
val &= ~(mask << ofs);
val |= v << ofs;
writel(val, &base->word[word].data[i]);
if ((bit + size - 1) / 32 > i) {
val = readl(&base->word[word].data[i + 1]);
val &= ~(mask >> (ofs ? (32 - ofs) : 0));
val |= v >> (ofs ? (32 - ofs) : 0);
writel(val, &base->word[word].data[i + 1]);
}
}
static u32 ipu_ch_param_read_field(struct ipuv3_channel *ch, u32 wbs)
{
struct ipu_ch_param __iomem *base = ipu_get_cpmem(ch);
u32 bit = (wbs >> 8) % 160;
u32 size = wbs & 0xff;
u32 word = (wbs >> 8) / 160;
u32 i = bit / 32;
u32 ofs = bit % 32;
u32 mask = (1 << size) - 1;
u32 val = 0;
pr_debug("%s %d %d %d\n", __func__, word, bit , size);
val = (readl(&base->word[word].data[i]) >> ofs) & mask;
if ((bit + size - 1) / 32 > i) {
u32 tmp;
tmp = readl(&base->word[word].data[i + 1]);
tmp &= mask >> (ofs ? (32 - ofs) : 0);
val |= tmp << (ofs ? (32 - ofs) : 0);
}
return val;
}
/*
* The V4L2 spec defines packed RGB formats in memory byte order, which from
* point of view of the IPU corresponds to little-endian words with the first
* component in the least significant bits.
* The DRM pixel formats and IPU internal representation are ordered the other
* way around, with the first named component ordered at the most significant
* bits. Further, V4L2 formats are not well defined:
* https://linuxtv.org/downloads/v4l-dvb-apis/packed-rgb.html
* We choose the interpretation which matches GStreamer behavior.
*/
static int v4l2_pix_fmt_to_drm_fourcc(u32 pixelformat)
{
switch (pixelformat) {
case V4L2_PIX_FMT_RGB565:
/*
* Here we choose the 'corrected' interpretation of RGBP, a
* little-endian 16-bit word with the red component at the most
* significant bits:
* g[2:0]b[4:0] r[4:0]g[5:3] <=> [16:0] R:G:B
*/
return DRM_FORMAT_RGB565;
case V4L2_PIX_FMT_BGR24:
/* B G R <=> [24:0] R:G:B */
return DRM_FORMAT_RGB888;
case V4L2_PIX_FMT_RGB24:
/* R G B <=> [24:0] B:G:R */
return DRM_FORMAT_BGR888;
case V4L2_PIX_FMT_BGR32:
/* B G R A <=> [32:0] A:B:G:R */
return DRM_FORMAT_XRGB8888;
case V4L2_PIX_FMT_RGB32:
/* R G B A <=> [32:0] A:B:G:R */
return DRM_FORMAT_XBGR8888;
case V4L2_PIX_FMT_ABGR32:
/* B G R A <=> [32:0] A:R:G:B */
return DRM_FORMAT_ARGB8888;
case V4L2_PIX_FMT_XBGR32:
/* B G R X <=> [32:0] X:R:G:B */
return DRM_FORMAT_XRGB8888;
case V4L2_PIX_FMT_BGRA32:
/* A B G R <=> [32:0] R:G:B:A */
return DRM_FORMAT_RGBA8888;
case V4L2_PIX_FMT_BGRX32:
/* X B G R <=> [32:0] R:G:B:X */
return DRM_FORMAT_RGBX8888;
case V4L2_PIX_FMT_RGBA32:
/* R G B A <=> [32:0] A:B:G:R */
return DRM_FORMAT_ABGR8888;
case V4L2_PIX_FMT_RGBX32:
/* R G B X <=> [32:0] X:B:G:R */
return DRM_FORMAT_XBGR8888;
case V4L2_PIX_FMT_ARGB32:
/* A R G B <=> [32:0] B:G:R:A */
return DRM_FORMAT_BGRA8888;
case V4L2_PIX_FMT_XRGB32:
/* X R G B <=> [32:0] B:G:R:X */
return DRM_FORMAT_BGRX8888;
case V4L2_PIX_FMT_UYVY:
return DRM_FORMAT_UYVY;
case V4L2_PIX_FMT_YUYV:
return DRM_FORMAT_YUYV;
case V4L2_PIX_FMT_YUV420:
return DRM_FORMAT_YUV420;
case V4L2_PIX_FMT_YUV422P:
return DRM_FORMAT_YUV422;
case V4L2_PIX_FMT_YVU420:
return DRM_FORMAT_YVU420;
case V4L2_PIX_FMT_NV12:
return DRM_FORMAT_NV12;
case V4L2_PIX_FMT_NV16:
return DRM_FORMAT_NV16;
}
return -EINVAL;
}
void ipu_cpmem_zero(struct ipuv3_channel *ch)
{
struct ipu_ch_param __iomem *p = ipu_get_cpmem(ch);
void __iomem *base = p;
int i;
for (i = 0; i < sizeof(*p) / sizeof(u32); i++)
writel(0, base + i * sizeof(u32));
}
EXPORT_SYMBOL_GPL(ipu_cpmem_zero);
void ipu_cpmem_set_resolution(struct ipuv3_channel *ch, int xres, int yres)
{
ipu_ch_param_write_field(ch, IPU_FIELD_FW, xres - 1);
ipu_ch_param_write_field(ch, IPU_FIELD_FH, yres - 1);
}
EXPORT_SYMBOL_GPL(ipu_cpmem_set_resolution);
void ipu_cpmem_skip_odd_chroma_rows(struct ipuv3_channel *ch)
{
ipu_ch_param_write_field(ch, IPU_FIELD_RDRW, 1);
}
EXPORT_SYMBOL_GPL(ipu_cpmem_skip_odd_chroma_rows);
void ipu_cpmem_set_stride(struct ipuv3_channel *ch, int stride)
{
ipu_ch_param_write_field(ch, IPU_FIELD_SLY, stride - 1);
}
EXPORT_SYMBOL_GPL(ipu_cpmem_set_stride);
void ipu_cpmem_set_high_priority(struct ipuv3_channel *ch)
{
struct ipu_soc *ipu = ch->ipu;
u32 val;
if (ipu->ipu_type == IPUV3EX)
ipu_ch_param_write_field(ch, IPU_FIELD_ID, 1);
val = ipu_idmac_read(ipu, IDMAC_CHA_PRI(ch->num));
val |= 1 << (ch->num % 32);
ipu_idmac_write(ipu, val, IDMAC_CHA_PRI(ch->num));
};
EXPORT_SYMBOL_GPL(ipu_cpmem_set_high_priority);
void ipu_cpmem_set_buffer(struct ipuv3_channel *ch, int bufnum, dma_addr_t buf)
{
WARN_ON_ONCE(buf & 0x7);
if (bufnum)
ipu_ch_param_write_field(ch, IPU_FIELD_EBA1, buf >> 3);
else
ipu_ch_param_write_field(ch, IPU_FIELD_EBA0, buf >> 3);
}
EXPORT_SYMBOL_GPL(ipu_cpmem_set_buffer);
void ipu_cpmem_set_uv_offset(struct ipuv3_channel *ch, u32 u_off, u32 v_off)
{
WARN_ON_ONCE((u_off & 0x7) || (v_off & 0x7));
ipu_ch_param_write_field(ch, IPU_FIELD_UBO, u_off / 8);
ipu_ch_param_write_field(ch, IPU_FIELD_VBO, v_off / 8);
}
EXPORT_SYMBOL_GPL(ipu_cpmem_set_uv_offset);
void ipu_cpmem_interlaced_scan(struct ipuv3_channel *ch, int stride,
u32 pixelformat)
{
u32 ilo, sly, sluv;
if (stride < 0) {
stride = -stride;
ilo = 0x100000 - (stride / 8);
} else {
ilo = stride / 8;
}
sly = (stride * 2) - 1;
switch (pixelformat) {
case V4L2_PIX_FMT_YUV420:
case V4L2_PIX_FMT_YVU420:
sluv = stride / 2 - 1;
break;
case V4L2_PIX_FMT_NV12:
sluv = stride - 1;
break;
case V4L2_PIX_FMT_YUV422P:
sluv = stride - 1;
break;
case V4L2_PIX_FMT_NV16:
sluv = stride * 2 - 1;
break;
default:
sluv = 0;
break;
}
ipu_ch_param_write_field(ch, IPU_FIELD_SO, 1);
ipu_ch_param_write_field(ch, IPU_FIELD_ILO, ilo);
ipu_ch_param_write_field(ch, IPU_FIELD_SLY, sly);
if (sluv)
ipu_ch_param_write_field(ch, IPU_FIELD_SLUV, sluv);
};
EXPORT_SYMBOL_GPL(ipu_cpmem_interlaced_scan);
void ipu_cpmem_set_axi_id(struct ipuv3_channel *ch, u32 id)
{
id &= 0x3;
ipu_ch_param_write_field(ch, IPU_FIELD_ID, id);
}
EXPORT_SYMBOL_GPL(ipu_cpmem_set_axi_id);
void ipu_cpmem_set_burstsize(struct ipuv3_channel *ch, int burstsize)
{
ipu_ch_param_write_field(ch, IPU_FIELD_NPB, burstsize - 1);
};
EXPORT_SYMBOL_GPL(ipu_cpmem_set_burstsize);
void ipu_cpmem_set_block_mode(struct ipuv3_channel *ch)
{
ipu_ch_param_write_field(ch, IPU_FIELD_BM, 1);
}
EXPORT_SYMBOL_GPL(ipu_cpmem_set_block_mode);
void ipu_cpmem_set_rotation(struct ipuv3_channel *ch,
enum ipu_rotate_mode rot)
{
u32 temp_rot = bitrev8(rot) >> 5;
ipu_ch_param_write_field(ch, IPU_FIELD_ROT_HF_VF, temp_rot);
}
EXPORT_SYMBOL_GPL(ipu_cpmem_set_rotation);
int ipu_cpmem_set_format_rgb(struct ipuv3_channel *ch,
const struct ipu_rgb *rgb)
{
int bpp = 0, npb = 0, ro, go, bo, to;
ro = rgb->bits_per_pixel - rgb->red.length - rgb->red.offset;
go = rgb->bits_per_pixel - rgb->green.length - rgb->green.offset;
bo = rgb->bits_per_pixel - rgb->blue.length - rgb->blue.offset;
to = rgb->bits_per_pixel - rgb->transp.length - rgb->transp.offset;
ipu_ch_param_write_field(ch, IPU_FIELD_WID0, rgb->red.length - 1);
ipu_ch_param_write_field(ch, IPU_FIELD_OFS0, ro);
ipu_ch_param_write_field(ch, IPU_FIELD_WID1, rgb->green.length - 1);
ipu_ch_param_write_field(ch, IPU_FIELD_OFS1, go);
ipu_ch_param_write_field(ch, IPU_FIELD_WID2, rgb->blue.length - 1);
ipu_ch_param_write_field(ch, IPU_FIELD_OFS2, bo);
if (rgb->transp.length) {
ipu_ch_param_write_field(ch, IPU_FIELD_WID3,
rgb->transp.length - 1);
ipu_ch_param_write_field(ch, IPU_FIELD_OFS3, to);
} else {
ipu_ch_param_write_field(ch, IPU_FIELD_WID3, 7);
ipu_ch_param_write_field(ch, IPU_FIELD_OFS3,
rgb->bits_per_pixel);
}
switch (rgb->bits_per_pixel) {
case 32:
bpp = 0;
npb = 15;
break;
case 24:
bpp = 1;
npb = 19;
break;
case 16:
bpp = 3;
npb = 31;
break;
case 8:
bpp = 5;
npb = 63;
break;
default:
return -EINVAL;
}
ipu_ch_param_write_field(ch, IPU_FIELD_BPP, bpp);
ipu_ch_param_write_field(ch, IPU_FIELD_NPB, npb);
ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 7); /* rgb mode */
return 0;
}
EXPORT_SYMBOL_GPL(ipu_cpmem_set_format_rgb);
int ipu_cpmem_set_format_passthrough(struct ipuv3_channel *ch, int width)
{
int bpp = 0, npb = 0;
switch (width) {
case 32:
bpp = 0;
npb = 15;
break;
case 24:
bpp = 1;
npb = 19;
break;
case 16:
bpp = 3;
npb = 31;
break;
case 8:
bpp = 5;
npb = 63;
break;
default:
return -EINVAL;
}
ipu_ch_param_write_field(ch, IPU_FIELD_BPP, bpp);
ipu_ch_param_write_field(ch, IPU_FIELD_NPB, npb);
ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 6); /* raw mode */
return 0;
}
EXPORT_SYMBOL_GPL(ipu_cpmem_set_format_passthrough);
void ipu_cpmem_set_yuv_planar_full(struct ipuv3_channel *ch,
unsigned int uv_stride,
unsigned int u_offset, unsigned int v_offset)
{
WARN_ON_ONCE((u_offset & 0x7) || (v_offset & 0x7));
ipu_ch_param_write_field(ch, IPU_FIELD_SLUV, uv_stride - 1);
ipu_ch_param_write_field(ch, IPU_FIELD_UBO, u_offset / 8);
ipu_ch_param_write_field(ch, IPU_FIELD_VBO, v_offset / 8);
}
EXPORT_SYMBOL_GPL(ipu_cpmem_set_yuv_planar_full);
static const struct ipu_rgb def_xrgb_32 = {
.red = { .offset = 16, .length = 8, },
.green = { .offset = 8, .length = 8, },
.blue = { .offset = 0, .length = 8, },
.transp = { .offset = 24, .length = 8, },
.bits_per_pixel = 32,
};
static const struct ipu_rgb def_xbgr_32 = {
.red = { .offset = 0, .length = 8, },
.green = { .offset = 8, .length = 8, },
.blue = { .offset = 16, .length = 8, },
.transp = { .offset = 24, .length = 8, },
.bits_per_pixel = 32,
};
static const struct ipu_rgb def_rgbx_32 = {
.red = { .offset = 24, .length = 8, },
.green = { .offset = 16, .length = 8, },
.blue = { .offset = 8, .length = 8, },
.transp = { .offset = 0, .length = 8, },
.bits_per_pixel = 32,
};
static const struct ipu_rgb def_bgrx_32 = {
.red = { .offset = 8, .length = 8, },
.green = { .offset = 16, .length = 8, },
.blue = { .offset = 24, .length = 8, },
.transp = { .offset = 0, .length = 8, },
.bits_per_pixel = 32,
};
static const struct ipu_rgb def_rgb_24 = {
.red = { .offset = 16, .length = 8, },
.green = { .offset = 8, .length = 8, },
.blue = { .offset = 0, .length = 8, },
.transp = { .offset = 0, .length = 0, },
.bits_per_pixel = 24,
};
static const struct ipu_rgb def_bgr_24 = {
.red = { .offset = 0, .length = 8, },
.green = { .offset = 8, .length = 8, },
.blue = { .offset = 16, .length = 8, },
.transp = { .offset = 0, .length = 0, },
.bits_per_pixel = 24,
};
static const struct ipu_rgb def_rgb_16 = {
.red = { .offset = 11, .length = 5, },
.green = { .offset = 5, .length = 6, },
.blue = { .offset = 0, .length = 5, },
.transp = { .offset = 0, .length = 0, },
.bits_per_pixel = 16,
};
static const struct ipu_rgb def_bgr_16 = {
.red = { .offset = 0, .length = 5, },
.green = { .offset = 5, .length = 6, },
.blue = { .offset = 11, .length = 5, },
.transp = { .offset = 0, .length = 0, },
.bits_per_pixel = 16,
};
static const struct ipu_rgb def_argb_16 = {
.red = { .offset = 10, .length = 5, },
.green = { .offset = 5, .length = 5, },
.blue = { .offset = 0, .length = 5, },
.transp = { .offset = 15, .length = 1, },
.bits_per_pixel = 16,
};
static const struct ipu_rgb def_argb_16_4444 = {
.red = { .offset = 8, .length = 4, },
.green = { .offset = 4, .length = 4, },
.blue = { .offset = 0, .length = 4, },
.transp = { .offset = 12, .length = 4, },
.bits_per_pixel = 16,
};
static const struct ipu_rgb def_abgr_16 = {
.red = { .offset = 0, .length = 5, },
.green = { .offset = 5, .length = 5, },
.blue = { .offset = 10, .length = 5, },
.transp = { .offset = 15, .length = 1, },
.bits_per_pixel = 16,
};
static const struct ipu_rgb def_rgba_16 = {
.red = { .offset = 11, .length = 5, },
.green = { .offset = 6, .length = 5, },
.blue = { .offset = 1, .length = 5, },
.transp = { .offset = 0, .length = 1, },
.bits_per_pixel = 16,
};
static const struct ipu_rgb def_bgra_16 = {
.red = { .offset = 1, .length = 5, },
.green = { .offset = 6, .length = 5, },
.blue = { .offset = 11, .length = 5, },
.transp = { .offset = 0, .length = 1, },
.bits_per_pixel = 16,
};
#define Y_OFFSET(pix, x, y) ((x) + pix->bytesperline * (y))
#define U_OFFSET(pix, x, y) ((pix->bytesperline * pix->height) + \
(pix->bytesperline * ((y) / 2) / 2) + (x) / 2)
#define V_OFFSET(pix, x, y) ((pix->bytesperline * pix->height) + \
(pix->bytesperline * pix->height / 4) + \
(pix->bytesperline * ((y) / 2) / 2) + (x) / 2)
#define U2_OFFSET(pix, x, y) ((pix->bytesperline * pix->height) + \
(pix->bytesperline * (y) / 2) + (x) / 2)
#define V2_OFFSET(pix, x, y) ((pix->bytesperline * pix->height) + \
(pix->bytesperline * pix->height / 2) + \
(pix->bytesperline * (y) / 2) + (x) / 2)
#define UV_OFFSET(pix, x, y) ((pix->bytesperline * pix->height) + \
(pix->bytesperline * ((y) / 2)) + (x))
#define UV2_OFFSET(pix, x, y) ((pix->bytesperline * pix->height) + \
(pix->bytesperline * y) + (x))
#define NUM_ALPHA_CHANNELS 7
/* See Table 37-12. Alpha channels mapping. */
static int ipu_channel_albm(int ch_num)
{
switch (ch_num) {
case IPUV3_CHANNEL_G_MEM_IC_PRP_VF: return 0;
case IPUV3_CHANNEL_G_MEM_IC_PP: return 1;
case IPUV3_CHANNEL_MEM_FG_SYNC: return 2;
case IPUV3_CHANNEL_MEM_FG_ASYNC: return 3;
case IPUV3_CHANNEL_MEM_BG_SYNC: return 4;
case IPUV3_CHANNEL_MEM_BG_ASYNC: return 5;
case IPUV3_CHANNEL_MEM_VDI_PLANE1_COMB: return 6;
default:
return -EINVAL;
}
}
static void ipu_cpmem_set_separate_alpha(struct ipuv3_channel *ch)
{
struct ipu_soc *ipu = ch->ipu;
int albm;
u32 val;
albm = ipu_channel_albm(ch->num);
if (albm < 0)
return;
ipu_ch_param_write_field(ch, IPU_FIELD_ALU, 1);
ipu_ch_param_write_field(ch, IPU_FIELD_ALBM, albm);
ipu_ch_param_write_field(ch, IPU_FIELD_CRE, 1);
val = ipu_idmac_read(ipu, IDMAC_SEP_ALPHA);
val |= BIT(ch->num);
ipu_idmac_write(ipu, val, IDMAC_SEP_ALPHA);
}
int ipu_cpmem_set_fmt(struct ipuv3_channel *ch, u32 drm_fourcc)
{
switch (drm_fourcc) {
case DRM_FORMAT_YUV420:
case DRM_FORMAT_YVU420:
/* pix format */
ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 2);
/* burst size */
ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31);
break;
case DRM_FORMAT_YUV422:
case DRM_FORMAT_YVU422:
/* pix format */
ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 1);
/* burst size */
ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31);
break;
case DRM_FORMAT_YUV444:
case DRM_FORMAT_YVU444:
/* pix format */
ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 0);
/* burst size */
ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31);
break;
case DRM_FORMAT_NV12:
/* pix format */
ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 4);
/* burst size */
ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31);
break;
case DRM_FORMAT_NV16:
/* pix format */
ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 3);
/* burst size */
ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31);
break;
case DRM_FORMAT_UYVY:
/* bits/pixel */
ipu_ch_param_write_field(ch, IPU_FIELD_BPP, 3);
/* pix format */
ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 0xA);
/* burst size */
ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31);
break;
case DRM_FORMAT_YUYV:
/* bits/pixel */
ipu_ch_param_write_field(ch, IPU_FIELD_BPP, 3);
/* pix format */
ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 0x8);
/* burst size */
ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31);
break;
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_XBGR8888:
ipu_cpmem_set_format_rgb(ch, &def_xbgr_32);
break;
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_XRGB8888:
ipu_cpmem_set_format_rgb(ch, &def_xrgb_32);
break;
case DRM_FORMAT_RGBA8888:
case DRM_FORMAT_RGBX8888:
case DRM_FORMAT_RGBX8888_A8:
ipu_cpmem_set_format_rgb(ch, &def_rgbx_32);
break;
case DRM_FORMAT_BGRA8888:
case DRM_FORMAT_BGRX8888:
case DRM_FORMAT_BGRX8888_A8:
ipu_cpmem_set_format_rgb(ch, &def_bgrx_32);
break;
case DRM_FORMAT_BGR888:
case DRM_FORMAT_BGR888_A8:
ipu_cpmem_set_format_rgb(ch, &def_bgr_24);
break;
case DRM_FORMAT_RGB888:
case DRM_FORMAT_RGB888_A8:
ipu_cpmem_set_format_rgb(ch, &def_rgb_24);
break;
case DRM_FORMAT_RGB565:
case DRM_FORMAT_RGB565_A8:
ipu_cpmem_set_format_rgb(ch, &def_rgb_16);
break;
case DRM_FORMAT_BGR565:
case DRM_FORMAT_BGR565_A8:
ipu_cpmem_set_format_rgb(ch, &def_bgr_16);
break;
case DRM_FORMAT_ARGB1555:
ipu_cpmem_set_format_rgb(ch, &def_argb_16);
break;
case DRM_FORMAT_ABGR1555:
ipu_cpmem_set_format_rgb(ch, &def_abgr_16);
break;
case DRM_FORMAT_RGBA5551:
ipu_cpmem_set_format_rgb(ch, &def_rgba_16);
break;
case DRM_FORMAT_BGRA5551:
ipu_cpmem_set_format_rgb(ch, &def_bgra_16);
break;
case DRM_FORMAT_ARGB4444:
ipu_cpmem_set_format_rgb(ch, &def_argb_16_4444);
break;
default:
return -EINVAL;
}
switch (drm_fourcc) {
case DRM_FORMAT_RGB565_A8:
case DRM_FORMAT_BGR565_A8:
case DRM_FORMAT_RGB888_A8:
case DRM_FORMAT_BGR888_A8:
case DRM_FORMAT_RGBX8888_A8:
case DRM_FORMAT_BGRX8888_A8:
ipu_ch_param_write_field(ch, IPU_FIELD_WID3, 7);
ipu_cpmem_set_separate_alpha(ch);
break;
default:
break;
}
return 0;
}
EXPORT_SYMBOL_GPL(ipu_cpmem_set_fmt);
int ipu_cpmem_set_image(struct ipuv3_channel *ch, struct ipu_image *image)
{
struct v4l2_pix_format *pix = &image->pix;
int offset, u_offset, v_offset;
int ret = 0;
pr_debug("%s: resolution: %dx%d stride: %d\n",
__func__, pix->width, pix->height,
pix->bytesperline);
ipu_cpmem_set_resolution(ch, image->rect.width, image->rect.height);
ipu_cpmem_set_stride(ch, pix->bytesperline);
ipu_cpmem_set_fmt(ch, v4l2_pix_fmt_to_drm_fourcc(pix->pixelformat));
switch (pix->pixelformat) {
case V4L2_PIX_FMT_YUV420:
offset = Y_OFFSET(pix, image->rect.left, image->rect.top);
u_offset = image->u_offset ?
image->u_offset : U_OFFSET(pix, image->rect.left,
image->rect.top) - offset;
v_offset = image->v_offset ?
image->v_offset : V_OFFSET(pix, image->rect.left,
image->rect.top) - offset;
ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline / 2,
u_offset, v_offset);
break;
case V4L2_PIX_FMT_YVU420:
offset = Y_OFFSET(pix, image->rect.left, image->rect.top);
u_offset = image->u_offset ?
image->u_offset : V_OFFSET(pix, image->rect.left,
image->rect.top) - offset;
v_offset = image->v_offset ?
image->v_offset : U_OFFSET(pix, image->rect.left,
image->rect.top) - offset;
ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline / 2,
u_offset, v_offset);
break;
case V4L2_PIX_FMT_YUV422P:
offset = Y_OFFSET(pix, image->rect.left, image->rect.top);
u_offset = image->u_offset ?
image->u_offset : U2_OFFSET(pix, image->rect.left,
image->rect.top) - offset;
v_offset = image->v_offset ?
image->v_offset : V2_OFFSET(pix, image->rect.left,
image->rect.top) - offset;
ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline / 2,
u_offset, v_offset);
break;
case V4L2_PIX_FMT_NV12:
offset = Y_OFFSET(pix, image->rect.left, image->rect.top);
u_offset = image->u_offset ?
image->u_offset : UV_OFFSET(pix, image->rect.left,
image->rect.top) - offset;
v_offset = image->v_offset ? image->v_offset : 0;
ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline,
u_offset, v_offset);
break;
case V4L2_PIX_FMT_NV16:
offset = Y_OFFSET(pix, image->rect.left, image->rect.top);
u_offset = image->u_offset ?
image->u_offset : UV2_OFFSET(pix, image->rect.left,
image->rect.top) - offset;
v_offset = image->v_offset ? image->v_offset : 0;
ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline,
u_offset, v_offset);
break;
case V4L2_PIX_FMT_UYVY:
case V4L2_PIX_FMT_YUYV:
case V4L2_PIX_FMT_RGB565:
offset = image->rect.left * 2 +
image->rect.top * pix->bytesperline;
break;
case V4L2_PIX_FMT_RGB32:
case V4L2_PIX_FMT_BGR32:
case V4L2_PIX_FMT_ABGR32:
case V4L2_PIX_FMT_XBGR32:
case V4L2_PIX_FMT_BGRA32:
case V4L2_PIX_FMT_BGRX32:
case V4L2_PIX_FMT_RGBA32:
case V4L2_PIX_FMT_RGBX32:
case V4L2_PIX_FMT_ARGB32:
case V4L2_PIX_FMT_XRGB32:
offset = image->rect.left * 4 +
image->rect.top * pix->bytesperline;
break;
case V4L2_PIX_FMT_RGB24:
case V4L2_PIX_FMT_BGR24:
offset = image->rect.left * 3 +
image->rect.top * pix->bytesperline;
break;
case V4L2_PIX_FMT_SBGGR8:
case V4L2_PIX_FMT_SGBRG8:
case V4L2_PIX_FMT_SGRBG8:
case V4L2_PIX_FMT_SRGGB8:
case V4L2_PIX_FMT_GREY:
offset = image->rect.left + image->rect.top * pix->bytesperline;
break;
case V4L2_PIX_FMT_SBGGR16:
case V4L2_PIX_FMT_SGBRG16:
case V4L2_PIX_FMT_SGRBG16:
case V4L2_PIX_FMT_SRGGB16:
case V4L2_PIX_FMT_Y16:
offset = image->rect.left * 2 +
image->rect.top * pix->bytesperline;
break;
default:
/* This should not happen */
WARN_ON(1);
offset = 0;
ret = -EINVAL;
}
ipu_cpmem_set_buffer(ch, 0, image->phys0 + offset);
ipu_cpmem_set_buffer(ch, 1, image->phys1 + offset);
return ret;
}
EXPORT_SYMBOL_GPL(ipu_cpmem_set_image);
void ipu_cpmem_dump(struct ipuv3_channel *ch)
{
struct ipu_ch_param __iomem *p = ipu_get_cpmem(ch);
struct ipu_soc *ipu = ch->ipu;
int chno = ch->num;
dev_dbg(ipu->dev, "ch %d word 0 - %08X %08X %08X %08X %08X\n", chno,
readl(&p->word[0].data[0]),
readl(&p->word[0].data[1]),
readl(&p->word[0].data[2]),
readl(&p->word[0].data[3]),
readl(&p->word[0].data[4]));
dev_dbg(ipu->dev, "ch %d word 1 - %08X %08X %08X %08X %08X\n", chno,
readl(&p->word[1].data[0]),
readl(&p->word[1].data[1]),
readl(&p->word[1].data[2]),
readl(&p->word[1].data[3]),
readl(&p->word[1].data[4]));
dev_dbg(ipu->dev, "PFS 0x%x, ",
ipu_ch_param_read_field(ch, IPU_FIELD_PFS));
dev_dbg(ipu->dev, "BPP 0x%x, ",
ipu_ch_param_read_field(ch, IPU_FIELD_BPP));
dev_dbg(ipu->dev, "NPB 0x%x\n",
ipu_ch_param_read_field(ch, IPU_FIELD_NPB));
dev_dbg(ipu->dev, "FW %d, ",
ipu_ch_param_read_field(ch, IPU_FIELD_FW));
dev_dbg(ipu->dev, "FH %d, ",
ipu_ch_param_read_field(ch, IPU_FIELD_FH));
dev_dbg(ipu->dev, "EBA0 0x%x\n",
ipu_ch_param_read_field(ch, IPU_FIELD_EBA0) << 3);
dev_dbg(ipu->dev, "EBA1 0x%x\n",
ipu_ch_param_read_field(ch, IPU_FIELD_EBA1) << 3);
dev_dbg(ipu->dev, "Stride %d\n",
ipu_ch_param_read_field(ch, IPU_FIELD_SL));
dev_dbg(ipu->dev, "scan_order %d\n",
ipu_ch_param_read_field(ch, IPU_FIELD_SO));
dev_dbg(ipu->dev, "uv_stride %d\n",
ipu_ch_param_read_field(ch, IPU_FIELD_SLUV));
dev_dbg(ipu->dev, "u_offset 0x%x\n",
ipu_ch_param_read_field(ch, IPU_FIELD_UBO) << 3);
dev_dbg(ipu->dev, "v_offset 0x%x\n",
ipu_ch_param_read_field(ch, IPU_FIELD_VBO) << 3);
dev_dbg(ipu->dev, "Width0 %d+1, ",
ipu_ch_param_read_field(ch, IPU_FIELD_WID0));
dev_dbg(ipu->dev, "Width1 %d+1, ",
ipu_ch_param_read_field(ch, IPU_FIELD_WID1));
dev_dbg(ipu->dev, "Width2 %d+1, ",
ipu_ch_param_read_field(ch, IPU_FIELD_WID2));
dev_dbg(ipu->dev, "Width3 %d+1, ",
ipu_ch_param_read_field(ch, IPU_FIELD_WID3));
dev_dbg(ipu->dev, "Offset0 %d, ",
ipu_ch_param_read_field(ch, IPU_FIELD_OFS0));
dev_dbg(ipu->dev, "Offset1 %d, ",
ipu_ch_param_read_field(ch, IPU_FIELD_OFS1));
dev_dbg(ipu->dev, "Offset2 %d, ",
ipu_ch_param_read_field(ch, IPU_FIELD_OFS2));
dev_dbg(ipu->dev, "Offset3 %d\n",
ipu_ch_param_read_field(ch, IPU_FIELD_OFS3));
}
EXPORT_SYMBOL_GPL(ipu_cpmem_dump);
int ipu_cpmem_init(struct ipu_soc *ipu, struct device *dev, unsigned long base)
{
struct ipu_cpmem *cpmem;
cpmem = devm_kzalloc(dev, sizeof(*cpmem), GFP_KERNEL);
if (!cpmem)
return -ENOMEM;
ipu->cpmem_priv = cpmem;
spin_lock_init(&cpmem->lock);
cpmem->base = devm_ioremap(dev, base, SZ_128K);
if (!cpmem->base)
return -ENOMEM;
dev_dbg(dev, "CPMEM base: 0x%08lx remapped to %p\n",
base, cpmem->base);
cpmem->ipu = ipu;
return 0;
}
void ipu_cpmem_exit(struct ipu_soc *ipu)
{
}
+734
View File
@@ -0,0 +1,734 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2012-2014 Mentor Graphics Inc.
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*/
#include <linux/export.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/videodev2.h>
#include <uapi/linux/v4l2-mediabus.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include "ipu-prv.h"
struct ipu_csi {
void __iomem *base;
int id;
u32 module;
struct clk *clk_ipu; /* IPU bus clock */
spinlock_t lock;
bool inuse;
struct ipu_soc *ipu;
};
/* CSI Register Offsets */
#define CSI_SENS_CONF 0x0000
#define CSI_SENS_FRM_SIZE 0x0004
#define CSI_ACT_FRM_SIZE 0x0008
#define CSI_OUT_FRM_CTRL 0x000c
#define CSI_TST_CTRL 0x0010
#define CSI_CCIR_CODE_1 0x0014
#define CSI_CCIR_CODE_2 0x0018
#define CSI_CCIR_CODE_3 0x001c
#define CSI_MIPI_DI 0x0020
#define CSI_SKIP 0x0024
#define CSI_CPD_CTRL 0x0028
#define CSI_CPD_RC(n) (0x002c + ((n)*4))
#define CSI_CPD_RS(n) (0x004c + ((n)*4))
#define CSI_CPD_GRC(n) (0x005c + ((n)*4))
#define CSI_CPD_GRS(n) (0x007c + ((n)*4))
#define CSI_CPD_GBC(n) (0x008c + ((n)*4))
#define CSI_CPD_GBS(n) (0x00Ac + ((n)*4))
#define CSI_CPD_BC(n) (0x00Bc + ((n)*4))
#define CSI_CPD_BS(n) (0x00Dc + ((n)*4))
#define CSI_CPD_OFFSET1 0x00ec
#define CSI_CPD_OFFSET2 0x00f0
/* CSI Register Fields */
#define CSI_SENS_CONF_DATA_FMT_SHIFT 8
#define CSI_SENS_CONF_DATA_FMT_MASK 0x00000700
#define CSI_SENS_CONF_DATA_FMT_RGB_YUV444 0L
#define CSI_SENS_CONF_DATA_FMT_YUV422_YUYV 1L
#define CSI_SENS_CONF_DATA_FMT_YUV422_UYVY 2L
#define CSI_SENS_CONF_DATA_FMT_BAYER 3L
#define CSI_SENS_CONF_DATA_FMT_RGB565 4L
#define CSI_SENS_CONF_DATA_FMT_RGB555 5L
#define CSI_SENS_CONF_DATA_FMT_RGB444 6L
#define CSI_SENS_CONF_DATA_FMT_JPEG 7L
#define CSI_SENS_CONF_VSYNC_POL_SHIFT 0
#define CSI_SENS_CONF_HSYNC_POL_SHIFT 1
#define CSI_SENS_CONF_DATA_POL_SHIFT 2
#define CSI_SENS_CONF_PIX_CLK_POL_SHIFT 3
#define CSI_SENS_CONF_SENS_PRTCL_MASK 0x00000070
#define CSI_SENS_CONF_SENS_PRTCL_SHIFT 4
#define CSI_SENS_CONF_PACK_TIGHT_SHIFT 7
#define CSI_SENS_CONF_DATA_WIDTH_SHIFT 11
#define CSI_SENS_CONF_EXT_VSYNC_SHIFT 15
#define CSI_SENS_CONF_DIVRATIO_SHIFT 16
#define CSI_SENS_CONF_DIVRATIO_MASK 0x00ff0000
#define CSI_SENS_CONF_DATA_DEST_SHIFT 24
#define CSI_SENS_CONF_DATA_DEST_MASK 0x07000000
#define CSI_SENS_CONF_JPEG8_EN_SHIFT 27
#define CSI_SENS_CONF_JPEG_EN_SHIFT 28
#define CSI_SENS_CONF_FORCE_EOF_SHIFT 29
#define CSI_SENS_CONF_DATA_EN_POL_SHIFT 31
#define CSI_DATA_DEST_IC 2
#define CSI_DATA_DEST_IDMAC 4
#define CSI_CCIR_ERR_DET_EN 0x01000000
#define CSI_HORI_DOWNSIZE_EN 0x80000000
#define CSI_VERT_DOWNSIZE_EN 0x40000000
#define CSI_TEST_GEN_MODE_EN 0x01000000
#define CSI_HSC_MASK 0x1fff0000
#define CSI_HSC_SHIFT 16
#define CSI_VSC_MASK 0x00000fff
#define CSI_VSC_SHIFT 0
#define CSI_TEST_GEN_R_MASK 0x000000ff
#define CSI_TEST_GEN_R_SHIFT 0
#define CSI_TEST_GEN_G_MASK 0x0000ff00
#define CSI_TEST_GEN_G_SHIFT 8
#define CSI_TEST_GEN_B_MASK 0x00ff0000
#define CSI_TEST_GEN_B_SHIFT 16
#define CSI_MAX_RATIO_SKIP_SMFC_MASK 0x00000007
#define CSI_MAX_RATIO_SKIP_SMFC_SHIFT 0
#define CSI_SKIP_SMFC_MASK 0x000000f8
#define CSI_SKIP_SMFC_SHIFT 3
#define CSI_ID_2_SKIP_MASK 0x00000300
#define CSI_ID_2_SKIP_SHIFT 8
#define CSI_COLOR_FIRST_ROW_MASK 0x00000002
#define CSI_COLOR_FIRST_COMP_MASK 0x00000001
/* MIPI CSI-2 data types */
#define MIPI_DT_YUV420 0x18 /* YYY.../UYVY.... */
#define MIPI_DT_YUV420_LEGACY 0x1a /* UYY.../VYY... */
#define MIPI_DT_YUV422 0x1e /* UYVY... */
#define MIPI_DT_RGB444 0x20
#define MIPI_DT_RGB555 0x21
#define MIPI_DT_RGB565 0x22
#define MIPI_DT_RGB666 0x23
#define MIPI_DT_RGB888 0x24
#define MIPI_DT_RAW6 0x28
#define MIPI_DT_RAW7 0x29
#define MIPI_DT_RAW8 0x2a
#define MIPI_DT_RAW10 0x2b
#define MIPI_DT_RAW12 0x2c
#define MIPI_DT_RAW14 0x2d
/*
* Bitfield of CSI bus signal polarities and modes.
*/
struct ipu_csi_bus_config {
unsigned data_width:4;
unsigned clk_mode:3;
unsigned ext_vsync:1;
unsigned vsync_pol:1;
unsigned hsync_pol:1;
unsigned pixclk_pol:1;
unsigned data_pol:1;
unsigned sens_clksrc:1;
unsigned pack_tight:1;
unsigned force_eof:1;
unsigned data_en_pol:1;
unsigned data_fmt;
unsigned mipi_dt;
};
/*
* Enumeration of CSI data bus widths.
*/
enum ipu_csi_data_width {
IPU_CSI_DATA_WIDTH_4 = 0,
IPU_CSI_DATA_WIDTH_8 = 1,
IPU_CSI_DATA_WIDTH_10 = 3,
IPU_CSI_DATA_WIDTH_12 = 5,
IPU_CSI_DATA_WIDTH_16 = 9,
};
/*
* Enumeration of CSI clock modes.
*/
enum ipu_csi_clk_mode {
IPU_CSI_CLK_MODE_GATED_CLK,
IPU_CSI_CLK_MODE_NONGATED_CLK,
IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE,
IPU_CSI_CLK_MODE_CCIR656_INTERLACED,
IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR,
IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR,
IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR,
IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR,
};
static inline u32 ipu_csi_read(struct ipu_csi *csi, unsigned offset)
{
return readl(csi->base + offset);
}
static inline void ipu_csi_write(struct ipu_csi *csi, u32 value,
unsigned offset)
{
writel(value, csi->base + offset);
}
/*
* Find the CSI data format and data width for the given V4L2 media
* bus pixel format code.
*/
static int mbus_code_to_bus_cfg(struct ipu_csi_bus_config *cfg, u32 mbus_code,
enum v4l2_mbus_type mbus_type)
{
switch (mbus_code) {
case MEDIA_BUS_FMT_BGR565_2X8_BE:
case MEDIA_BUS_FMT_BGR565_2X8_LE:
case MEDIA_BUS_FMT_RGB565_2X8_BE:
case MEDIA_BUS_FMT_RGB565_2X8_LE:
if (mbus_type == V4L2_MBUS_CSI2_DPHY)
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB565;
else
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER;
cfg->mipi_dt = MIPI_DT_RGB565;
cfg->data_width = IPU_CSI_DATA_WIDTH_8;
break;
case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE:
case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE:
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB444;
cfg->mipi_dt = MIPI_DT_RGB444;
cfg->data_width = IPU_CSI_DATA_WIDTH_8;
break;
case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE:
case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE:
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB555;
cfg->mipi_dt = MIPI_DT_RGB555;
cfg->data_width = IPU_CSI_DATA_WIDTH_8;
break;
case MEDIA_BUS_FMT_RGB888_1X24:
case MEDIA_BUS_FMT_BGR888_1X24:
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB_YUV444;
cfg->mipi_dt = MIPI_DT_RGB888;
cfg->data_width = IPU_CSI_DATA_WIDTH_8;
break;
case MEDIA_BUS_FMT_UYVY8_2X8:
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_UYVY;
cfg->mipi_dt = MIPI_DT_YUV422;
cfg->data_width = IPU_CSI_DATA_WIDTH_8;
break;
case MEDIA_BUS_FMT_YUYV8_2X8:
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_YUYV;
cfg->mipi_dt = MIPI_DT_YUV422;
cfg->data_width = IPU_CSI_DATA_WIDTH_8;
break;
case MEDIA_BUS_FMT_UYVY8_1X16:
if (mbus_type == V4L2_MBUS_BT656) {
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_UYVY;
cfg->data_width = IPU_CSI_DATA_WIDTH_8;
} else {
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER;
cfg->data_width = IPU_CSI_DATA_WIDTH_16;
}
cfg->mipi_dt = MIPI_DT_YUV422;
break;
case MEDIA_BUS_FMT_YUYV8_1X16:
if (mbus_type == V4L2_MBUS_BT656) {
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_YUYV;
cfg->data_width = IPU_CSI_DATA_WIDTH_8;
} else {
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER;
cfg->data_width = IPU_CSI_DATA_WIDTH_16;
}
cfg->mipi_dt = MIPI_DT_YUV422;
break;
case MEDIA_BUS_FMT_SBGGR8_1X8:
case MEDIA_BUS_FMT_SGBRG8_1X8:
case MEDIA_BUS_FMT_SGRBG8_1X8:
case MEDIA_BUS_FMT_SRGGB8_1X8:
case MEDIA_BUS_FMT_Y8_1X8:
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER;
cfg->mipi_dt = MIPI_DT_RAW8;
cfg->data_width = IPU_CSI_DATA_WIDTH_8;
break;
case MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8:
case MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8:
case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8:
case MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8:
case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE:
case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE:
case MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE:
case MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE:
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER;
cfg->mipi_dt = MIPI_DT_RAW10;
cfg->data_width = IPU_CSI_DATA_WIDTH_8;
break;
case MEDIA_BUS_FMT_SBGGR10_1X10:
case MEDIA_BUS_FMT_SGBRG10_1X10:
case MEDIA_BUS_FMT_SGRBG10_1X10:
case MEDIA_BUS_FMT_SRGGB10_1X10:
case MEDIA_BUS_FMT_Y10_1X10:
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER;
cfg->mipi_dt = MIPI_DT_RAW10;
cfg->data_width = IPU_CSI_DATA_WIDTH_10;
break;
case MEDIA_BUS_FMT_SBGGR12_1X12:
case MEDIA_BUS_FMT_SGBRG12_1X12:
case MEDIA_BUS_FMT_SGRBG12_1X12:
case MEDIA_BUS_FMT_SRGGB12_1X12:
case MEDIA_BUS_FMT_Y12_1X12:
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER;
cfg->mipi_dt = MIPI_DT_RAW12;
cfg->data_width = IPU_CSI_DATA_WIDTH_12;
break;
case MEDIA_BUS_FMT_JPEG_1X8:
/* TODO */
cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_JPEG;
cfg->mipi_dt = MIPI_DT_RAW8;
cfg->data_width = IPU_CSI_DATA_WIDTH_8;
break;
default:
return -EINVAL;
}
return 0;
}
/* translate alternate field mode based on given standard */
static inline enum v4l2_field
ipu_csi_translate_field(enum v4l2_field field, v4l2_std_id std)
{
return (field != V4L2_FIELD_ALTERNATE) ? field :
((std & V4L2_STD_525_60) ?
V4L2_FIELD_SEQ_BT : V4L2_FIELD_SEQ_TB);
}
/*
* Fill a CSI bus config struct from mbus_config and mbus_framefmt.
*/
static int fill_csi_bus_cfg(struct ipu_csi_bus_config *csicfg,
const struct v4l2_mbus_config *mbus_cfg,
const struct v4l2_mbus_framefmt *mbus_fmt)
{
int ret, is_bt1120;
memset(csicfg, 0, sizeof(*csicfg));
ret = mbus_code_to_bus_cfg(csicfg, mbus_fmt->code, mbus_cfg->type);
if (ret < 0)
return ret;
switch (mbus_cfg->type) {
case V4L2_MBUS_PARALLEL:
csicfg->ext_vsync = 1;
csicfg->vsync_pol = (mbus_cfg->bus.parallel.flags &
V4L2_MBUS_VSYNC_ACTIVE_LOW) ? 1 : 0;
csicfg->hsync_pol = (mbus_cfg->bus.parallel.flags &
V4L2_MBUS_HSYNC_ACTIVE_LOW) ? 1 : 0;
csicfg->pixclk_pol = (mbus_cfg->bus.parallel.flags &
V4L2_MBUS_PCLK_SAMPLE_FALLING) ? 1 : 0;
csicfg->clk_mode = IPU_CSI_CLK_MODE_GATED_CLK;
break;
case V4L2_MBUS_BT656:
csicfg->ext_vsync = 0;
/* UYVY10_1X20 etc. should be supported as well */
is_bt1120 = mbus_fmt->code == MEDIA_BUS_FMT_UYVY8_1X16 ||
mbus_fmt->code == MEDIA_BUS_FMT_YUYV8_1X16;
if (V4L2_FIELD_HAS_BOTH(mbus_fmt->field) ||
mbus_fmt->field == V4L2_FIELD_ALTERNATE)
csicfg->clk_mode = is_bt1120 ?
IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR :
IPU_CSI_CLK_MODE_CCIR656_INTERLACED;
else
csicfg->clk_mode = is_bt1120 ?
IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR :
IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE;
break;
case V4L2_MBUS_CSI2_DPHY:
/*
* MIPI CSI-2 requires non gated clock mode, all other
* parameters are not applicable for MIPI CSI-2 bus.
*/
csicfg->clk_mode = IPU_CSI_CLK_MODE_NONGATED_CLK;
break;
default:
/* will never get here, keep compiler quiet */
break;
}
return 0;
}
static int
ipu_csi_set_bt_interlaced_codes(struct ipu_csi *csi,
const struct v4l2_mbus_framefmt *infmt,
const struct v4l2_mbus_framefmt *outfmt,
v4l2_std_id std)
{
enum v4l2_field infield, outfield;
bool swap_fields;
/* get translated field type of input and output */
infield = ipu_csi_translate_field(infmt->field, std);
outfield = ipu_csi_translate_field(outfmt->field, std);
/*
* Write the H-V-F codes the CSI will match against the
* incoming data for start/end of active and blanking
* field intervals. If input and output field types are
* sequential but not the same (one is SEQ_BT and the other
* is SEQ_TB), swap the F-bit so that the CSI will capture
* field 1 lines before field 0 lines.
*/
swap_fields = (V4L2_FIELD_IS_SEQUENTIAL(infield) &&
V4L2_FIELD_IS_SEQUENTIAL(outfield) &&
infield != outfield);
if (!swap_fields) {
/*
* Field0BlankEnd = 110, Field0BlankStart = 010
* Field0ActiveEnd = 100, Field0ActiveStart = 000
* Field1BlankEnd = 111, Field1BlankStart = 011
* Field1ActiveEnd = 101, Field1ActiveStart = 001
*/
ipu_csi_write(csi, 0x40596 | CSI_CCIR_ERR_DET_EN,
CSI_CCIR_CODE_1);
ipu_csi_write(csi, 0xD07DF, CSI_CCIR_CODE_2);
} else {
dev_dbg(csi->ipu->dev, "capture field swap\n");
/* same as above but with F-bit inverted */
ipu_csi_write(csi, 0xD07DF | CSI_CCIR_ERR_DET_EN,
CSI_CCIR_CODE_1);
ipu_csi_write(csi, 0x40596, CSI_CCIR_CODE_2);
}
ipu_csi_write(csi, 0xFF0000, CSI_CCIR_CODE_3);
return 0;
}
int ipu_csi_init_interface(struct ipu_csi *csi,
const struct v4l2_mbus_config *mbus_cfg,
const struct v4l2_mbus_framefmt *infmt,
const struct v4l2_mbus_framefmt *outfmt)
{
struct ipu_csi_bus_config cfg;
unsigned long flags;
u32 width, height, data = 0;
v4l2_std_id std;
int ret;
ret = fill_csi_bus_cfg(&cfg, mbus_cfg, infmt);
if (ret < 0)
return ret;
/* set default sensor frame width and height */
width = infmt->width;
height = infmt->height;
if (infmt->field == V4L2_FIELD_ALTERNATE)
height *= 2;
/* Set the CSI_SENS_CONF register remaining fields */
data |= cfg.data_width << CSI_SENS_CONF_DATA_WIDTH_SHIFT |
cfg.data_fmt << CSI_SENS_CONF_DATA_FMT_SHIFT |
cfg.data_pol << CSI_SENS_CONF_DATA_POL_SHIFT |
cfg.vsync_pol << CSI_SENS_CONF_VSYNC_POL_SHIFT |
cfg.hsync_pol << CSI_SENS_CONF_HSYNC_POL_SHIFT |
cfg.pixclk_pol << CSI_SENS_CONF_PIX_CLK_POL_SHIFT |
cfg.ext_vsync << CSI_SENS_CONF_EXT_VSYNC_SHIFT |
cfg.clk_mode << CSI_SENS_CONF_SENS_PRTCL_SHIFT |
cfg.pack_tight << CSI_SENS_CONF_PACK_TIGHT_SHIFT |
cfg.force_eof << CSI_SENS_CONF_FORCE_EOF_SHIFT |
cfg.data_en_pol << CSI_SENS_CONF_DATA_EN_POL_SHIFT;
spin_lock_irqsave(&csi->lock, flags);
ipu_csi_write(csi, data, CSI_SENS_CONF);
/* Set CCIR registers */
switch (cfg.clk_mode) {
case IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE:
ipu_csi_write(csi, 0x40030, CSI_CCIR_CODE_1);
ipu_csi_write(csi, 0xFF0000, CSI_CCIR_CODE_3);
break;
case IPU_CSI_CLK_MODE_CCIR656_INTERLACED:
if (width == 720 && height == 480) {
std = V4L2_STD_NTSC;
height = 525;
} else if (width == 720 && height == 576) {
std = V4L2_STD_PAL;
height = 625;
} else {
dev_err(csi->ipu->dev,
"Unsupported interlaced video mode\n");
ret = -EINVAL;
goto out_unlock;
}
ret = ipu_csi_set_bt_interlaced_codes(csi, infmt, outfmt, std);
if (ret)
goto out_unlock;
break;
case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR:
case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR:
case IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR:
case IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR:
ipu_csi_write(csi, 0x40030 | CSI_CCIR_ERR_DET_EN,
CSI_CCIR_CODE_1);
ipu_csi_write(csi, 0xFF0000, CSI_CCIR_CODE_3);
break;
case IPU_CSI_CLK_MODE_GATED_CLK:
case IPU_CSI_CLK_MODE_NONGATED_CLK:
ipu_csi_write(csi, 0, CSI_CCIR_CODE_1);
break;
}
/* Setup sensor frame size */
ipu_csi_write(csi, (width - 1) | ((height - 1) << 16),
CSI_SENS_FRM_SIZE);
dev_dbg(csi->ipu->dev, "CSI_SENS_CONF = 0x%08X\n",
ipu_csi_read(csi, CSI_SENS_CONF));
dev_dbg(csi->ipu->dev, "CSI_ACT_FRM_SIZE = 0x%08X\n",
ipu_csi_read(csi, CSI_ACT_FRM_SIZE));
out_unlock:
spin_unlock_irqrestore(&csi->lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(ipu_csi_init_interface);
void ipu_csi_set_window(struct ipu_csi *csi, struct v4l2_rect *w)
{
unsigned long flags;
u32 reg;
spin_lock_irqsave(&csi->lock, flags);
ipu_csi_write(csi, (w->width - 1) | ((w->height - 1) << 16),
CSI_ACT_FRM_SIZE);
reg = ipu_csi_read(csi, CSI_OUT_FRM_CTRL);
reg &= ~(CSI_HSC_MASK | CSI_VSC_MASK);
reg |= ((w->top << CSI_VSC_SHIFT) | (w->left << CSI_HSC_SHIFT));
ipu_csi_write(csi, reg, CSI_OUT_FRM_CTRL);
spin_unlock_irqrestore(&csi->lock, flags);
}
EXPORT_SYMBOL_GPL(ipu_csi_set_window);
void ipu_csi_set_downsize(struct ipu_csi *csi, bool horiz, bool vert)
{
unsigned long flags;
u32 reg;
spin_lock_irqsave(&csi->lock, flags);
reg = ipu_csi_read(csi, CSI_OUT_FRM_CTRL);
reg &= ~(CSI_HORI_DOWNSIZE_EN | CSI_VERT_DOWNSIZE_EN);
reg |= (horiz ? CSI_HORI_DOWNSIZE_EN : 0) |
(vert ? CSI_VERT_DOWNSIZE_EN : 0);
ipu_csi_write(csi, reg, CSI_OUT_FRM_CTRL);
spin_unlock_irqrestore(&csi->lock, flags);
}
EXPORT_SYMBOL_GPL(ipu_csi_set_downsize);
int ipu_csi_set_mipi_datatype(struct ipu_csi *csi, u32 vc,
struct v4l2_mbus_framefmt *mbus_fmt)
{
struct ipu_csi_bus_config cfg;
unsigned long flags;
u32 temp;
int ret;
if (vc > 3)
return -EINVAL;
ret = mbus_code_to_bus_cfg(&cfg, mbus_fmt->code, V4L2_MBUS_CSI2_DPHY);
if (ret < 0)
return ret;
spin_lock_irqsave(&csi->lock, flags);
temp = ipu_csi_read(csi, CSI_MIPI_DI);
temp &= ~(0xff << (vc * 8));
temp |= (cfg.mipi_dt << (vc * 8));
ipu_csi_write(csi, temp, CSI_MIPI_DI);
spin_unlock_irqrestore(&csi->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_csi_set_mipi_datatype);
int ipu_csi_set_skip_smfc(struct ipu_csi *csi, u32 skip,
u32 max_ratio, u32 id)
{
unsigned long flags;
u32 temp;
if (max_ratio > 5 || id > 3)
return -EINVAL;
spin_lock_irqsave(&csi->lock, flags);
temp = ipu_csi_read(csi, CSI_SKIP);
temp &= ~(CSI_MAX_RATIO_SKIP_SMFC_MASK | CSI_ID_2_SKIP_MASK |
CSI_SKIP_SMFC_MASK);
temp |= (max_ratio << CSI_MAX_RATIO_SKIP_SMFC_SHIFT) |
(id << CSI_ID_2_SKIP_SHIFT) |
(skip << CSI_SKIP_SMFC_SHIFT);
ipu_csi_write(csi, temp, CSI_SKIP);
spin_unlock_irqrestore(&csi->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_csi_set_skip_smfc);
int ipu_csi_set_dest(struct ipu_csi *csi, enum ipu_csi_dest csi_dest)
{
unsigned long flags;
u32 csi_sens_conf, dest;
if (csi_dest == IPU_CSI_DEST_IDMAC)
dest = CSI_DATA_DEST_IDMAC;
else
dest = CSI_DATA_DEST_IC; /* IC or VDIC */
spin_lock_irqsave(&csi->lock, flags);
csi_sens_conf = ipu_csi_read(csi, CSI_SENS_CONF);
csi_sens_conf &= ~CSI_SENS_CONF_DATA_DEST_MASK;
csi_sens_conf |= (dest << CSI_SENS_CONF_DATA_DEST_SHIFT);
ipu_csi_write(csi, csi_sens_conf, CSI_SENS_CONF);
spin_unlock_irqrestore(&csi->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_csi_set_dest);
int ipu_csi_enable(struct ipu_csi *csi)
{
ipu_module_enable(csi->ipu, csi->module);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_csi_enable);
int ipu_csi_disable(struct ipu_csi *csi)
{
ipu_module_disable(csi->ipu, csi->module);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_csi_disable);
struct ipu_csi *ipu_csi_get(struct ipu_soc *ipu, int id)
{
unsigned long flags;
struct ipu_csi *csi, *ret;
if (id > 1)
return ERR_PTR(-EINVAL);
csi = ipu->csi_priv[id];
ret = csi;
spin_lock_irqsave(&csi->lock, flags);
if (csi->inuse) {
ret = ERR_PTR(-EBUSY);
goto unlock;
}
csi->inuse = true;
unlock:
spin_unlock_irqrestore(&csi->lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(ipu_csi_get);
void ipu_csi_put(struct ipu_csi *csi)
{
unsigned long flags;
spin_lock_irqsave(&csi->lock, flags);
csi->inuse = false;
spin_unlock_irqrestore(&csi->lock, flags);
}
EXPORT_SYMBOL_GPL(ipu_csi_put);
int ipu_csi_init(struct ipu_soc *ipu, struct device *dev, int id,
unsigned long base, u32 module, struct clk *clk_ipu)
{
struct ipu_csi *csi;
if (id > 1)
return -ENODEV;
csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
if (!csi)
return -ENOMEM;
ipu->csi_priv[id] = csi;
spin_lock_init(&csi->lock);
csi->module = module;
csi->id = id;
csi->clk_ipu = clk_ipu;
csi->base = devm_ioremap(dev, base, PAGE_SIZE);
if (!csi->base)
return -ENOMEM;
dev_dbg(dev, "CSI%d base: 0x%08lx remapped to %p\n",
id, base, csi->base);
csi->ipu = ipu;
return 0;
}
void ipu_csi_exit(struct ipu_soc *ipu, int id)
{
}
void ipu_csi_dump(struct ipu_csi *csi)
{
dev_dbg(csi->ipu->dev, "CSI_SENS_CONF: %08x\n",
ipu_csi_read(csi, CSI_SENS_CONF));
dev_dbg(csi->ipu->dev, "CSI_SENS_FRM_SIZE: %08x\n",
ipu_csi_read(csi, CSI_SENS_FRM_SIZE));
dev_dbg(csi->ipu->dev, "CSI_ACT_FRM_SIZE: %08x\n",
ipu_csi_read(csi, CSI_ACT_FRM_SIZE));
dev_dbg(csi->ipu->dev, "CSI_OUT_FRM_CTRL: %08x\n",
ipu_csi_read(csi, CSI_OUT_FRM_CTRL));
dev_dbg(csi->ipu->dev, "CSI_TST_CTRL: %08x\n",
ipu_csi_read(csi, CSI_TST_CTRL));
dev_dbg(csi->ipu->dev, "CSI_CCIR_CODE_1: %08x\n",
ipu_csi_read(csi, CSI_CCIR_CODE_1));
dev_dbg(csi->ipu->dev, "CSI_CCIR_CODE_2: %08x\n",
ipu_csi_read(csi, CSI_CCIR_CODE_2));
dev_dbg(csi->ipu->dev, "CSI_CCIR_CODE_3: %08x\n",
ipu_csi_read(csi, CSI_CCIR_CODE_3));
dev_dbg(csi->ipu->dev, "CSI_MIPI_DI: %08x\n",
ipu_csi_read(csi, CSI_MIPI_DI));
dev_dbg(csi->ipu->dev, "CSI_SKIP: %08x\n",
ipu_csi_read(csi, CSI_SKIP));
}
EXPORT_SYMBOL_GPL(ipu_csi_dump);
+426
View File
@@ -0,0 +1,426 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*/
#include <linux/export.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <video/imx-ipu-v3.h>
#include "ipu-prv.h"
#define DC_MAP_CONF_PTR(n) (0x108 + ((n) & ~0x1) * 2)
#define DC_MAP_CONF_VAL(n) (0x144 + ((n) & ~0x1) * 2)
#define DC_EVT_NF 0
#define DC_EVT_NL 1
#define DC_EVT_EOF 2
#define DC_EVT_NFIELD 3
#define DC_EVT_EOL 4
#define DC_EVT_EOFIELD 5
#define DC_EVT_NEW_ADDR 6
#define DC_EVT_NEW_CHAN 7
#define DC_EVT_NEW_DATA 8
#define DC_EVT_NEW_ADDR_W_0 0
#define DC_EVT_NEW_ADDR_W_1 1
#define DC_EVT_NEW_CHAN_W_0 2
#define DC_EVT_NEW_CHAN_W_1 3
#define DC_EVT_NEW_DATA_W_0 4
#define DC_EVT_NEW_DATA_W_1 5
#define DC_EVT_NEW_ADDR_R_0 6
#define DC_EVT_NEW_ADDR_R_1 7
#define DC_EVT_NEW_CHAN_R_0 8
#define DC_EVT_NEW_CHAN_R_1 9
#define DC_EVT_NEW_DATA_R_0 10
#define DC_EVT_NEW_DATA_R_1 11
#define DC_WR_CH_CONF 0x0
#define DC_WR_CH_ADDR 0x4
#define DC_RL_CH(evt) (8 + ((evt) & ~0x1) * 2)
#define DC_GEN 0xd4
#define DC_DISP_CONF1(disp) (0xd8 + (disp) * 4)
#define DC_DISP_CONF2(disp) (0xe8 + (disp) * 4)
#define DC_STAT 0x1c8
#define WROD(lf) (0x18 | ((lf) << 1))
#define WRG 0x01
#define WCLK 0xc9
#define SYNC_WAVE 0
#define NULL_WAVE (-1)
#define DC_GEN_SYNC_1_6_SYNC (2 << 1)
#define DC_GEN_SYNC_PRIORITY_1 (1 << 7)
#define DC_WR_CH_CONF_WORD_SIZE_8 (0 << 0)
#define DC_WR_CH_CONF_WORD_SIZE_16 (1 << 0)
#define DC_WR_CH_CONF_WORD_SIZE_24 (2 << 0)
#define DC_WR_CH_CONF_WORD_SIZE_32 (3 << 0)
#define DC_WR_CH_CONF_DISP_ID_PARALLEL(i) (((i) & 0x1) << 3)
#define DC_WR_CH_CONF_DISP_ID_SERIAL (2 << 3)
#define DC_WR_CH_CONF_DISP_ID_ASYNC (3 << 4)
#define DC_WR_CH_CONF_FIELD_MODE (1 << 9)
#define DC_WR_CH_CONF_PROG_TYPE_NORMAL (4 << 5)
#define DC_WR_CH_CONF_PROG_TYPE_MASK (7 << 5)
#define DC_WR_CH_CONF_PROG_DI_ID (1 << 2)
#define DC_WR_CH_CONF_PROG_DISP_ID(i) (((i) & 0x1) << 3)
#define IPU_DC_NUM_CHANNELS 10
struct ipu_dc_priv;
enum ipu_dc_map {
IPU_DC_MAP_RGB24,
IPU_DC_MAP_RGB565,
IPU_DC_MAP_GBR24, /* TVEv2 */
IPU_DC_MAP_BGR666,
IPU_DC_MAP_LVDS666,
IPU_DC_MAP_BGR24,
};
struct ipu_dc {
/* The display interface number assigned to this dc channel */
unsigned int di;
void __iomem *base;
struct ipu_dc_priv *priv;
int chno;
bool in_use;
};
struct ipu_dc_priv {
void __iomem *dc_reg;
void __iomem *dc_tmpl_reg;
struct ipu_soc *ipu;
struct device *dev;
struct ipu_dc channels[IPU_DC_NUM_CHANNELS];
struct mutex mutex;
struct completion comp;
int use_count;
};
static void dc_link_event(struct ipu_dc *dc, int event, int addr, int priority)
{
u32 reg;
reg = readl(dc->base + DC_RL_CH(event));
reg &= ~(0xffff << (16 * (event & 0x1)));
reg |= ((addr << 8) | priority) << (16 * (event & 0x1));
writel(reg, dc->base + DC_RL_CH(event));
}
static void dc_write_tmpl(struct ipu_dc *dc, int word, u32 opcode, u32 operand,
int map, int wave, int glue, int sync, int stop)
{
struct ipu_dc_priv *priv = dc->priv;
u32 reg1, reg2;
if (opcode == WCLK) {
reg1 = (operand << 20) & 0xfff00000;
reg2 = operand >> 12 | opcode << 1 | stop << 9;
} else if (opcode == WRG) {
reg1 = sync | glue << 4 | ++wave << 11 | ((operand << 15) & 0xffff8000);
reg2 = operand >> 17 | opcode << 7 | stop << 9;
} else {
reg1 = sync | glue << 4 | ++wave << 11 | ++map << 15 | ((operand << 20) & 0xfff00000);
reg2 = operand >> 12 | opcode << 4 | stop << 9;
}
writel(reg1, priv->dc_tmpl_reg + word * 8);
writel(reg2, priv->dc_tmpl_reg + word * 8 + 4);
}
static int ipu_bus_format_to_map(u32 fmt)
{
switch (fmt) {
default:
WARN_ON(1);
fallthrough;
case MEDIA_BUS_FMT_RGB888_1X24:
return IPU_DC_MAP_RGB24;
case MEDIA_BUS_FMT_RGB565_1X16:
return IPU_DC_MAP_RGB565;
case MEDIA_BUS_FMT_GBR888_1X24:
return IPU_DC_MAP_GBR24;
case MEDIA_BUS_FMT_RGB666_1X18:
return IPU_DC_MAP_BGR666;
case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
return IPU_DC_MAP_LVDS666;
case MEDIA_BUS_FMT_BGR888_1X24:
return IPU_DC_MAP_BGR24;
}
}
int ipu_dc_init_sync(struct ipu_dc *dc, struct ipu_di *di, bool interlaced,
u32 bus_format, u32 width)
{
struct ipu_dc_priv *priv = dc->priv;
int addr, sync;
u32 reg = 0;
int map;
dc->di = ipu_di_get_num(di);
if (!IS_ALIGNED(width, 8)) {
dev_warn(priv->dev,
"%s: hactive does not align to 8 byte\n", __func__);
}
map = ipu_bus_format_to_map(bus_format);
/*
* In interlaced mode we need more counters to create the asymmetric
* per-field VSYNC signals. The pixel active signal synchronising DC
* to DI moves to signal generator #6 (see ipu-di.c). In progressive
* mode counter #5 is used.
*/
sync = interlaced ? 6 : 5;
/* Reserve 5 microcode template words for each DI */
if (dc->di)
addr = 5;
else
addr = 0;
if (interlaced) {
dc_link_event(dc, DC_EVT_NL, addr, 3);
dc_link_event(dc, DC_EVT_EOL, addr, 2);
dc_link_event(dc, DC_EVT_NEW_DATA, addr, 1);
/* Init template microcode */
dc_write_tmpl(dc, addr, WROD(0), 0, map, SYNC_WAVE, 0, sync, 1);
} else {
dc_link_event(dc, DC_EVT_NL, addr + 2, 3);
dc_link_event(dc, DC_EVT_EOL, addr + 3, 2);
dc_link_event(dc, DC_EVT_NEW_DATA, addr + 1, 1);
/* Init template microcode */
dc_write_tmpl(dc, addr + 2, WROD(0), 0, map, SYNC_WAVE, 8, sync, 1);
dc_write_tmpl(dc, addr + 3, WROD(0), 0, map, SYNC_WAVE, 4, sync, 0);
dc_write_tmpl(dc, addr + 4, WRG, 0, map, NULL_WAVE, 0, 0, 1);
dc_write_tmpl(dc, addr + 1, WROD(0), 0, map, SYNC_WAVE, 0, sync, 1);
}
dc_link_event(dc, DC_EVT_NF, 0, 0);
dc_link_event(dc, DC_EVT_NFIELD, 0, 0);
dc_link_event(dc, DC_EVT_EOF, 0, 0);
dc_link_event(dc, DC_EVT_EOFIELD, 0, 0);
dc_link_event(dc, DC_EVT_NEW_CHAN, 0, 0);
dc_link_event(dc, DC_EVT_NEW_ADDR, 0, 0);
reg = readl(dc->base + DC_WR_CH_CONF);
if (interlaced)
reg |= DC_WR_CH_CONF_FIELD_MODE;
else
reg &= ~DC_WR_CH_CONF_FIELD_MODE;
writel(reg, dc->base + DC_WR_CH_CONF);
writel(0x0, dc->base + DC_WR_CH_ADDR);
writel(width, priv->dc_reg + DC_DISP_CONF2(dc->di));
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dc_init_sync);
void ipu_dc_enable(struct ipu_soc *ipu)
{
struct ipu_dc_priv *priv = ipu->dc_priv;
mutex_lock(&priv->mutex);
if (!priv->use_count)
ipu_module_enable(priv->ipu, IPU_CONF_DC_EN);
priv->use_count++;
mutex_unlock(&priv->mutex);
}
EXPORT_SYMBOL_GPL(ipu_dc_enable);
void ipu_dc_enable_channel(struct ipu_dc *dc)
{
u32 reg;
reg = readl(dc->base + DC_WR_CH_CONF);
reg |= DC_WR_CH_CONF_PROG_TYPE_NORMAL;
writel(reg, dc->base + DC_WR_CH_CONF);
}
EXPORT_SYMBOL_GPL(ipu_dc_enable_channel);
void ipu_dc_disable_channel(struct ipu_dc *dc)
{
u32 val;
val = readl(dc->base + DC_WR_CH_CONF);
val &= ~DC_WR_CH_CONF_PROG_TYPE_MASK;
writel(val, dc->base + DC_WR_CH_CONF);
}
EXPORT_SYMBOL_GPL(ipu_dc_disable_channel);
void ipu_dc_disable(struct ipu_soc *ipu)
{
struct ipu_dc_priv *priv = ipu->dc_priv;
mutex_lock(&priv->mutex);
priv->use_count--;
if (!priv->use_count)
ipu_module_disable(priv->ipu, IPU_CONF_DC_EN);
if (priv->use_count < 0)
priv->use_count = 0;
mutex_unlock(&priv->mutex);
}
EXPORT_SYMBOL_GPL(ipu_dc_disable);
static void ipu_dc_map_config(struct ipu_dc_priv *priv, enum ipu_dc_map map,
int byte_num, int offset, int mask)
{
int ptr = map * 3 + byte_num;
u32 reg;
reg = readl(priv->dc_reg + DC_MAP_CONF_VAL(ptr));
reg &= ~(0xffff << (16 * (ptr & 0x1)));
reg |= ((offset << 8) | mask) << (16 * (ptr & 0x1));
writel(reg, priv->dc_reg + DC_MAP_CONF_VAL(ptr));
reg = readl(priv->dc_reg + DC_MAP_CONF_PTR(map));
reg &= ~(0x1f << ((16 * (map & 0x1)) + (5 * byte_num)));
reg |= ptr << ((16 * (map & 0x1)) + (5 * byte_num));
writel(reg, priv->dc_reg + DC_MAP_CONF_PTR(map));
}
static void ipu_dc_map_clear(struct ipu_dc_priv *priv, int map)
{
u32 reg = readl(priv->dc_reg + DC_MAP_CONF_PTR(map));
writel(reg & ~(0xffff << (16 * (map & 0x1))),
priv->dc_reg + DC_MAP_CONF_PTR(map));
}
struct ipu_dc *ipu_dc_get(struct ipu_soc *ipu, int channel)
{
struct ipu_dc_priv *priv = ipu->dc_priv;
struct ipu_dc *dc;
if (channel >= IPU_DC_NUM_CHANNELS)
return ERR_PTR(-ENODEV);
dc = &priv->channels[channel];
mutex_lock(&priv->mutex);
if (dc->in_use) {
mutex_unlock(&priv->mutex);
return ERR_PTR(-EBUSY);
}
dc->in_use = true;
mutex_unlock(&priv->mutex);
return dc;
}
EXPORT_SYMBOL_GPL(ipu_dc_get);
void ipu_dc_put(struct ipu_dc *dc)
{
struct ipu_dc_priv *priv = dc->priv;
mutex_lock(&priv->mutex);
dc->in_use = false;
mutex_unlock(&priv->mutex);
}
EXPORT_SYMBOL_GPL(ipu_dc_put);
int ipu_dc_init(struct ipu_soc *ipu, struct device *dev,
unsigned long base, unsigned long template_base)
{
struct ipu_dc_priv *priv;
static const int channel_offsets[] = {
0, 0x1c, 0x38, 0x54, 0x58, 0x5c, 0x78, 0, 0x94, 0xb4
};
int i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
mutex_init(&priv->mutex);
priv->dev = dev;
priv->ipu = ipu;
priv->dc_reg = devm_ioremap(dev, base, PAGE_SIZE);
priv->dc_tmpl_reg = devm_ioremap(dev, template_base, PAGE_SIZE);
if (!priv->dc_reg || !priv->dc_tmpl_reg)
return -ENOMEM;
for (i = 0; i < IPU_DC_NUM_CHANNELS; i++) {
priv->channels[i].chno = i;
priv->channels[i].priv = priv;
priv->channels[i].base = priv->dc_reg + channel_offsets[i];
}
writel(DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(1) |
DC_WR_CH_CONF_PROG_DI_ID,
priv->channels[1].base + DC_WR_CH_CONF);
writel(DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(0),
priv->channels[5].base + DC_WR_CH_CONF);
writel(DC_GEN_SYNC_1_6_SYNC | DC_GEN_SYNC_PRIORITY_1,
priv->dc_reg + DC_GEN);
ipu->dc_priv = priv;
dev_dbg(dev, "DC base: 0x%08lx template base: 0x%08lx\n",
base, template_base);
/* rgb24 */
ipu_dc_map_clear(priv, IPU_DC_MAP_RGB24);
ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 0, 7, 0xff); /* blue */
ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 1, 15, 0xff); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 2, 23, 0xff); /* red */
/* rgb565 */
ipu_dc_map_clear(priv, IPU_DC_MAP_RGB565);
ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 0, 4, 0xf8); /* blue */
ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 1, 10, 0xfc); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 2, 15, 0xf8); /* red */
/* gbr24 */
ipu_dc_map_clear(priv, IPU_DC_MAP_GBR24);
ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 2, 15, 0xff); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 1, 7, 0xff); /* blue */
ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 0, 23, 0xff); /* red */
/* bgr666 */
ipu_dc_map_clear(priv, IPU_DC_MAP_BGR666);
ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 0, 5, 0xfc); /* blue */
ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 1, 11, 0xfc); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 2, 17, 0xfc); /* red */
/* lvds666 */
ipu_dc_map_clear(priv, IPU_DC_MAP_LVDS666);
ipu_dc_map_config(priv, IPU_DC_MAP_LVDS666, 0, 5, 0xfc); /* blue */
ipu_dc_map_config(priv, IPU_DC_MAP_LVDS666, 1, 13, 0xfc); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_LVDS666, 2, 21, 0xfc); /* red */
/* bgr24 */
ipu_dc_map_clear(priv, IPU_DC_MAP_BGR24);
ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 2, 7, 0xff); /* red */
ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 1, 15, 0xff); /* green */
ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 0, 23, 0xff); /* blue */
return 0;
}
void ipu_dc_exit(struct ipu_soc *ipu)
{
}
+749
View File
@@ -0,0 +1,749 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*/
#include <linux/export.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <video/imx-ipu-v3.h>
#include "ipu-prv.h"
struct ipu_di {
void __iomem *base;
int id;
u32 module;
struct clk *clk_di; /* display input clock */
struct clk *clk_ipu; /* IPU bus clock */
struct clk *clk_di_pixel; /* resulting pixel clock */
bool inuse;
struct ipu_soc *ipu;
};
static DEFINE_MUTEX(di_mutex);
struct di_sync_config {
int run_count;
int run_src;
int offset_count;
int offset_src;
int repeat_count;
int cnt_clr_src;
int cnt_polarity_gen_en;
int cnt_polarity_clr_src;
int cnt_polarity_trigger_src;
int cnt_up;
int cnt_down;
};
enum di_pins {
DI_PIN11 = 0,
DI_PIN12 = 1,
DI_PIN13 = 2,
DI_PIN14 = 3,
DI_PIN15 = 4,
DI_PIN16 = 5,
DI_PIN17 = 6,
DI_PIN_CS = 7,
DI_PIN_SER_CLK = 0,
DI_PIN_SER_RS = 1,
};
enum di_sync_wave {
DI_SYNC_NONE = 0,
DI_SYNC_CLK = 1,
DI_SYNC_INT_HSYNC = 2,
DI_SYNC_HSYNC = 3,
DI_SYNC_VSYNC = 4,
DI_SYNC_DE = 6,
DI_SYNC_CNT1 = 2, /* counter >= 2 only */
DI_SYNC_CNT4 = 5, /* counter >= 5 only */
DI_SYNC_CNT5 = 6, /* counter >= 6 only */
};
#define SYNC_WAVE 0
#define DI_GENERAL 0x0000
#define DI_BS_CLKGEN0 0x0004
#define DI_BS_CLKGEN1 0x0008
#define DI_SW_GEN0(gen) (0x000c + 4 * ((gen) - 1))
#define DI_SW_GEN1(gen) (0x0030 + 4 * ((gen) - 1))
#define DI_STP_REP(gen) (0x0148 + 4 * (((gen) - 1)/2))
#define DI_SYNC_AS_GEN 0x0054
#define DI_DW_GEN(gen) (0x0058 + 4 * (gen))
#define DI_DW_SET(gen, set) (0x0088 + 4 * ((gen) + 0xc * (set)))
#define DI_SER_CONF 0x015c
#define DI_SSC 0x0160
#define DI_POL 0x0164
#define DI_AW0 0x0168
#define DI_AW1 0x016c
#define DI_SCR_CONF 0x0170
#define DI_STAT 0x0174
#define DI_SW_GEN0_RUN_COUNT(x) ((x) << 19)
#define DI_SW_GEN0_RUN_SRC(x) ((x) << 16)
#define DI_SW_GEN0_OFFSET_COUNT(x) ((x) << 3)
#define DI_SW_GEN0_OFFSET_SRC(x) ((x) << 0)
#define DI_SW_GEN1_CNT_POL_GEN_EN(x) ((x) << 29)
#define DI_SW_GEN1_CNT_CLR_SRC(x) ((x) << 25)
#define DI_SW_GEN1_CNT_POL_TRIGGER_SRC(x) ((x) << 12)
#define DI_SW_GEN1_CNT_POL_CLR_SRC(x) ((x) << 9)
#define DI_SW_GEN1_CNT_DOWN(x) ((x) << 16)
#define DI_SW_GEN1_CNT_UP(x) (x)
#define DI_SW_GEN1_AUTO_RELOAD (0x10000000)
#define DI_DW_GEN_ACCESS_SIZE_OFFSET 24
#define DI_DW_GEN_COMPONENT_SIZE_OFFSET 16
#define DI_GEN_POLARITY_1 (1 << 0)
#define DI_GEN_POLARITY_2 (1 << 1)
#define DI_GEN_POLARITY_3 (1 << 2)
#define DI_GEN_POLARITY_4 (1 << 3)
#define DI_GEN_POLARITY_5 (1 << 4)
#define DI_GEN_POLARITY_6 (1 << 5)
#define DI_GEN_POLARITY_7 (1 << 6)
#define DI_GEN_POLARITY_8 (1 << 7)
#define DI_GEN_POLARITY_DISP_CLK (1 << 17)
#define DI_GEN_DI_CLK_EXT (1 << 20)
#define DI_GEN_DI_VSYNC_EXT (1 << 21)
#define DI_POL_DRDY_DATA_POLARITY (1 << 7)
#define DI_POL_DRDY_POLARITY_15 (1 << 4)
#define DI_VSYNC_SEL_OFFSET 13
static inline u32 ipu_di_read(struct ipu_di *di, unsigned offset)
{
return readl(di->base + offset);
}
static inline void ipu_di_write(struct ipu_di *di, u32 value, unsigned offset)
{
writel(value, di->base + offset);
}
static void ipu_di_data_wave_config(struct ipu_di *di,
int wave_gen,
int access_size, int component_size)
{
u32 reg;
reg = (access_size << DI_DW_GEN_ACCESS_SIZE_OFFSET) |
(component_size << DI_DW_GEN_COMPONENT_SIZE_OFFSET);
ipu_di_write(di, reg, DI_DW_GEN(wave_gen));
}
static void ipu_di_data_pin_config(struct ipu_di *di, int wave_gen, int di_pin,
int set, int up, int down)
{
u32 reg;
reg = ipu_di_read(di, DI_DW_GEN(wave_gen));
reg &= ~(0x3 << (di_pin * 2));
reg |= set << (di_pin * 2);
ipu_di_write(di, reg, DI_DW_GEN(wave_gen));
ipu_di_write(di, (down << 16) | up, DI_DW_SET(wave_gen, set));
}
static void ipu_di_sync_config(struct ipu_di *di, struct di_sync_config *config,
int start, int count)
{
u32 reg;
int i;
for (i = 0; i < count; i++) {
struct di_sync_config *c = &config[i];
int wave_gen = start + i + 1;
if ((c->run_count >= 0x1000) || (c->offset_count >= 0x1000) ||
(c->repeat_count >= 0x1000) ||
(c->cnt_up >= 0x400) ||
(c->cnt_down >= 0x400)) {
dev_err(di->ipu->dev, "DI%d counters out of range.\n",
di->id);
return;
}
reg = DI_SW_GEN0_RUN_COUNT(c->run_count) |
DI_SW_GEN0_RUN_SRC(c->run_src) |
DI_SW_GEN0_OFFSET_COUNT(c->offset_count) |
DI_SW_GEN0_OFFSET_SRC(c->offset_src);
ipu_di_write(di, reg, DI_SW_GEN0(wave_gen));
reg = DI_SW_GEN1_CNT_POL_GEN_EN(c->cnt_polarity_gen_en) |
DI_SW_GEN1_CNT_CLR_SRC(c->cnt_clr_src) |
DI_SW_GEN1_CNT_POL_TRIGGER_SRC(
c->cnt_polarity_trigger_src) |
DI_SW_GEN1_CNT_POL_CLR_SRC(c->cnt_polarity_clr_src) |
DI_SW_GEN1_CNT_DOWN(c->cnt_down) |
DI_SW_GEN1_CNT_UP(c->cnt_up);
/* Enable auto reload */
if (c->repeat_count == 0)
reg |= DI_SW_GEN1_AUTO_RELOAD;
ipu_di_write(di, reg, DI_SW_GEN1(wave_gen));
reg = ipu_di_read(di, DI_STP_REP(wave_gen));
reg &= ~(0xffff << (16 * ((wave_gen - 1) & 0x1)));
reg |= c->repeat_count << (16 * ((wave_gen - 1) & 0x1));
ipu_di_write(di, reg, DI_STP_REP(wave_gen));
}
}
static void ipu_di_sync_config_interlaced(struct ipu_di *di,
struct ipu_di_signal_cfg *sig)
{
u32 h_total = sig->mode.hactive + sig->mode.hsync_len +
sig->mode.hback_porch + sig->mode.hfront_porch;
u32 v_total = sig->mode.vactive + sig->mode.vsync_len +
sig->mode.vback_porch + sig->mode.vfront_porch;
struct di_sync_config cfg[] = {
{
/* 1: internal VSYNC for each frame */
.run_count = v_total * 2 - 1,
.run_src = 3, /* == counter 7 */
}, {
/* PIN2: HSYNC waveform */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_CLK,
.cnt_down = sig->mode.hsync_len * 2,
}, {
/* PIN3: VSYNC waveform */
.run_count = v_total - 1,
.run_src = 4, /* == counter 7 */
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = 4, /* == counter 7 */
.cnt_down = sig->mode.vsync_len * 2,
.cnt_clr_src = DI_SYNC_CNT1,
}, {
/* 4: Field */
.run_count = v_total / 2,
.run_src = DI_SYNC_HSYNC,
.offset_count = h_total / 2,
.offset_src = DI_SYNC_CLK,
.repeat_count = 2,
.cnt_clr_src = DI_SYNC_CNT1,
}, {
/* 5: Active lines */
.run_src = DI_SYNC_HSYNC,
.offset_count = (sig->mode.vsync_len +
sig->mode.vback_porch) / 2,
.offset_src = DI_SYNC_HSYNC,
.repeat_count = sig->mode.vactive / 2,
.cnt_clr_src = DI_SYNC_CNT4,
}, {
/* 6: Active pixel, referenced by DC */
.run_src = DI_SYNC_CLK,
.offset_count = sig->mode.hsync_len +
sig->mode.hback_porch,
.offset_src = DI_SYNC_CLK,
.repeat_count = sig->mode.hactive,
.cnt_clr_src = DI_SYNC_CNT5,
}, {
/* 7: Half line HSYNC */
.run_count = h_total / 2 - 1,
.run_src = DI_SYNC_CLK,
}
};
ipu_di_sync_config(di, cfg, 0, ARRAY_SIZE(cfg));
ipu_di_write(di, v_total / 2 - 1, DI_SCR_CONF);
}
static void ipu_di_sync_config_noninterlaced(struct ipu_di *di,
struct ipu_di_signal_cfg *sig, int div)
{
u32 h_total = sig->mode.hactive + sig->mode.hsync_len +
sig->mode.hback_porch + sig->mode.hfront_porch;
u32 v_total = sig->mode.vactive + sig->mode.vsync_len +
sig->mode.vback_porch + sig->mode.vfront_porch;
struct di_sync_config cfg[] = {
{
/* 1: INT_HSYNC */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
} , {
/* PIN2: HSYNC */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
.offset_count = div * sig->v_to_h_sync,
.offset_src = DI_SYNC_CLK,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_CLK,
.cnt_down = sig->mode.hsync_len * 2,
} , {
/* PIN3: VSYNC */
.run_count = v_total - 1,
.run_src = DI_SYNC_INT_HSYNC,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC,
.cnt_down = sig->mode.vsync_len * 2,
} , {
/* 4: Line Active */
.run_src = DI_SYNC_HSYNC,
.offset_count = sig->mode.vsync_len +
sig->mode.vback_porch,
.offset_src = DI_SYNC_HSYNC,
.repeat_count = sig->mode.vactive,
.cnt_clr_src = DI_SYNC_VSYNC,
} , {
/* 5: Pixel Active, referenced by DC */
.run_src = DI_SYNC_CLK,
.offset_count = sig->mode.hsync_len +
sig->mode.hback_porch,
.offset_src = DI_SYNC_CLK,
.repeat_count = sig->mode.hactive,
.cnt_clr_src = 5, /* Line Active */
} , {
/* unused */
} , {
/* unused */
},
};
/* can't use #7 and #8 for line active and pixel active counters */
struct di_sync_config cfg_vga[] = {
{
/* 1: INT_HSYNC */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
} , {
/* 2: VSYNC */
.run_count = v_total - 1,
.run_src = DI_SYNC_INT_HSYNC,
} , {
/* 3: Line Active */
.run_src = DI_SYNC_INT_HSYNC,
.offset_count = sig->mode.vsync_len +
sig->mode.vback_porch,
.offset_src = DI_SYNC_INT_HSYNC,
.repeat_count = sig->mode.vactive,
.cnt_clr_src = 3 /* VSYNC */,
} , {
/* PIN4: HSYNC for VGA via TVEv2 on TQ MBa53 */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
.offset_count = div * sig->v_to_h_sync + 18, /* magic value from Freescale TVE driver */
.offset_src = DI_SYNC_CLK,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_CLK,
.cnt_down = sig->mode.hsync_len * 2,
} , {
/* 5: Pixel Active signal to DC */
.run_src = DI_SYNC_CLK,
.offset_count = sig->mode.hsync_len +
sig->mode.hback_porch,
.offset_src = DI_SYNC_CLK,
.repeat_count = sig->mode.hactive,
.cnt_clr_src = 4, /* Line Active */
} , {
/* PIN6: VSYNC for VGA via TVEv2 on TQ MBa53 */
.run_count = v_total - 1,
.run_src = DI_SYNC_INT_HSYNC,
.offset_count = 1, /* magic value from Freescale TVE driver */
.offset_src = DI_SYNC_INT_HSYNC,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC,
.cnt_down = sig->mode.vsync_len * 2,
} , {
/* PIN4: HSYNC for VGA via TVEv2 on i.MX53-QSB */
.run_count = h_total - 1,
.run_src = DI_SYNC_CLK,
.offset_count = div * sig->v_to_h_sync + 18, /* magic value from Freescale TVE driver */
.offset_src = DI_SYNC_CLK,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_CLK,
.cnt_down = sig->mode.hsync_len * 2,
} , {
/* PIN6: VSYNC for VGA via TVEv2 on i.MX53-QSB */
.run_count = v_total - 1,
.run_src = DI_SYNC_INT_HSYNC,
.offset_count = 1, /* magic value from Freescale TVE driver */
.offset_src = DI_SYNC_INT_HSYNC,
.cnt_polarity_gen_en = 1,
.cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC,
.cnt_down = sig->mode.vsync_len * 2,
} , {
/* unused */
},
};
ipu_di_write(di, v_total - 1, DI_SCR_CONF);
if (sig->hsync_pin == 2 && sig->vsync_pin == 3)
ipu_di_sync_config(di, cfg, 0, ARRAY_SIZE(cfg));
else
ipu_di_sync_config(di, cfg_vga, 0, ARRAY_SIZE(cfg_vga));
}
static void ipu_di_config_clock(struct ipu_di *di,
const struct ipu_di_signal_cfg *sig)
{
struct clk *clk;
unsigned clkgen0;
uint32_t val;
if (sig->clkflags & IPU_DI_CLKMODE_EXT) {
/*
* CLKMODE_EXT means we must use the DI clock: this is
* needed for things like LVDS which needs to feed the
* DI and LDB with the same pixel clock.
*/
clk = di->clk_di;
if (sig->clkflags & IPU_DI_CLKMODE_SYNC) {
/*
* CLKMODE_SYNC means that we want the DI to be
* clocked at the same rate as the parent clock.
* This is needed (eg) for LDB which needs to be
* fed with the same pixel clock. We assume that
* the LDB clock has already been set correctly.
*/
clkgen0 = 1 << 4;
} else {
/*
* We can use the divider. We should really have
* a flag here indicating whether the bridge can
* cope with a fractional divider or not. For the
* time being, let's go for simplicitly and
* reliability.
*/
unsigned long in_rate;
unsigned div;
clk_set_rate(clk, sig->mode.pixelclock);
in_rate = clk_get_rate(clk);
div = DIV_ROUND_CLOSEST(in_rate, sig->mode.pixelclock);
div = clamp(div, 1U, 255U);
clkgen0 = div << 4;
}
} else {
/*
* For other interfaces, we can arbitarily select between
* the DI specific clock and the internal IPU clock. See
* DI_GENERAL bit 20. We select the IPU clock if it can
* give us a clock rate within 1% of the requested frequency,
* otherwise we use the DI clock.
*/
unsigned long rate, clkrate;
unsigned div, error;
clkrate = clk_get_rate(di->clk_ipu);
div = DIV_ROUND_CLOSEST(clkrate, sig->mode.pixelclock);
div = clamp(div, 1U, 255U);
rate = clkrate / div;
error = rate / (sig->mode.pixelclock / 1000);
dev_dbg(di->ipu->dev, " IPU clock can give %lu with divider %u, error %c%d.%d%%\n",
rate, div, error < 1000 ? '-' : '+',
abs(error - 1000) / 10, abs(error - 1000) % 10);
/* Allow a 1% error */
if (error < 1010 && error >= 990) {
clk = di->clk_ipu;
clkgen0 = div << 4;
} else {
unsigned long in_rate;
unsigned div;
clk = di->clk_di;
clk_set_rate(clk, sig->mode.pixelclock);
in_rate = clk_get_rate(clk);
div = DIV_ROUND_CLOSEST(in_rate, sig->mode.pixelclock);
div = clamp(div, 1U, 255U);
clkgen0 = div << 4;
}
}
di->clk_di_pixel = clk;
/* Set the divider */
ipu_di_write(di, clkgen0, DI_BS_CLKGEN0);
/*
* Set the high/low periods. Bits 24:16 give us the falling edge,
* and bits 8:0 give the rising edge. LSB is fraction, and is
* based on the divider above. We want a 50% duty cycle, so set
* the falling edge to be half the divider.
*/
ipu_di_write(di, (clkgen0 >> 4) << 16, DI_BS_CLKGEN1);
/* Finally select the input clock */
val = ipu_di_read(di, DI_GENERAL) & ~DI_GEN_DI_CLK_EXT;
if (clk == di->clk_di)
val |= DI_GEN_DI_CLK_EXT;
ipu_di_write(di, val, DI_GENERAL);
dev_dbg(di->ipu->dev, "Want %luHz IPU %luHz DI %luHz using %s, %luHz\n",
sig->mode.pixelclock,
clk_get_rate(di->clk_ipu),
clk_get_rate(di->clk_di),
clk == di->clk_di ? "DI" : "IPU",
clk_get_rate(di->clk_di_pixel) / (clkgen0 >> 4));
}
/*
* This function is called to adjust a video mode to IPU restrictions.
* It is meant to be called from drm crtc mode_fixup() methods.
*/
int ipu_di_adjust_videomode(struct ipu_di *di, struct videomode *mode)
{
u32 diff;
if (!IS_ALIGNED(mode->hactive, 8) &&
mode->hfront_porch < ALIGN(mode->hactive, 8) - mode->hactive) {
dev_err(di->ipu->dev, "hactive %d is not aligned to 8 and front porch is too small to compensate\n",
mode->hactive);
return -EINVAL;
}
if (mode->vfront_porch >= 2)
return 0;
diff = 2 - mode->vfront_porch;
if (mode->vback_porch >= diff) {
mode->vfront_porch = 2;
mode->vback_porch -= diff;
} else if (mode->vsync_len > diff) {
mode->vfront_porch = 2;
mode->vsync_len = mode->vsync_len - diff;
} else {
dev_warn(di->ipu->dev, "failed to adjust videomode\n");
return -EINVAL;
}
dev_dbg(di->ipu->dev, "videomode adapted for IPU restrictions\n");
return 0;
}
EXPORT_SYMBOL_GPL(ipu_di_adjust_videomode);
static u32 ipu_di_gen_polarity(int pin)
{
switch (pin) {
case 1:
return DI_GEN_POLARITY_1;
case 2:
return DI_GEN_POLARITY_2;
case 3:
return DI_GEN_POLARITY_3;
case 4:
return DI_GEN_POLARITY_4;
case 5:
return DI_GEN_POLARITY_5;
case 6:
return DI_GEN_POLARITY_6;
case 7:
return DI_GEN_POLARITY_7;
case 8:
return DI_GEN_POLARITY_8;
}
return 0;
}
int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
{
u32 reg;
u32 di_gen, vsync_cnt;
u32 div;
dev_dbg(di->ipu->dev, "disp %d: panel size = %d x %d\n",
di->id, sig->mode.hactive, sig->mode.vactive);
dev_dbg(di->ipu->dev, "Clocks: IPU %luHz DI %luHz Needed %luHz\n",
clk_get_rate(di->clk_ipu),
clk_get_rate(di->clk_di),
sig->mode.pixelclock);
mutex_lock(&di_mutex);
ipu_di_config_clock(di, sig);
div = ipu_di_read(di, DI_BS_CLKGEN0) & 0xfff;
div = div / 16; /* Now divider is integer portion */
/* Setup pixel clock timing */
/* Down time is half of period */
ipu_di_write(di, (div << 16), DI_BS_CLKGEN1);
ipu_di_data_wave_config(di, SYNC_WAVE, div - 1, div - 1);
ipu_di_data_pin_config(di, SYNC_WAVE, DI_PIN15, 3, 0, div * 2);
di_gen = ipu_di_read(di, DI_GENERAL) & DI_GEN_DI_CLK_EXT;
di_gen |= DI_GEN_DI_VSYNC_EXT;
if (sig->mode.flags & DISPLAY_FLAGS_INTERLACED) {
ipu_di_sync_config_interlaced(di, sig);
/* set y_sel = 1 */
di_gen |= 0x10000000;
vsync_cnt = 3;
} else {
ipu_di_sync_config_noninterlaced(di, sig, div);
vsync_cnt = 3;
if (di->id == 1)
/*
* TODO: change only for TVEv2, parallel display
* uses pin 2 / 3
*/
if (!(sig->hsync_pin == 2 && sig->vsync_pin == 3))
vsync_cnt = 6;
}
if (sig->mode.flags & DISPLAY_FLAGS_HSYNC_HIGH)
di_gen |= ipu_di_gen_polarity(sig->hsync_pin);
if (sig->mode.flags & DISPLAY_FLAGS_VSYNC_HIGH)
di_gen |= ipu_di_gen_polarity(sig->vsync_pin);
if (sig->clk_pol)
di_gen |= DI_GEN_POLARITY_DISP_CLK;
ipu_di_write(di, di_gen, DI_GENERAL);
ipu_di_write(di, (--vsync_cnt << DI_VSYNC_SEL_OFFSET) | 0x00000002,
DI_SYNC_AS_GEN);
reg = ipu_di_read(di, DI_POL);
reg &= ~(DI_POL_DRDY_DATA_POLARITY | DI_POL_DRDY_POLARITY_15);
if (sig->enable_pol)
reg |= DI_POL_DRDY_POLARITY_15;
if (sig->data_pol)
reg |= DI_POL_DRDY_DATA_POLARITY;
ipu_di_write(di, reg, DI_POL);
mutex_unlock(&di_mutex);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_di_init_sync_panel);
int ipu_di_enable(struct ipu_di *di)
{
int ret;
WARN_ON(IS_ERR(di->clk_di_pixel));
ret = clk_prepare_enable(di->clk_di_pixel);
if (ret)
return ret;
ipu_module_enable(di->ipu, di->module);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_di_enable);
int ipu_di_disable(struct ipu_di *di)
{
WARN_ON(IS_ERR(di->clk_di_pixel));
ipu_module_disable(di->ipu, di->module);
clk_disable_unprepare(di->clk_di_pixel);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_di_disable);
int ipu_di_get_num(struct ipu_di *di)
{
return di->id;
}
EXPORT_SYMBOL_GPL(ipu_di_get_num);
static DEFINE_MUTEX(ipu_di_lock);
struct ipu_di *ipu_di_get(struct ipu_soc *ipu, int disp)
{
struct ipu_di *di;
if (disp > 1)
return ERR_PTR(-EINVAL);
di = ipu->di_priv[disp];
mutex_lock(&ipu_di_lock);
if (di->inuse) {
di = ERR_PTR(-EBUSY);
goto out;
}
di->inuse = true;
out:
mutex_unlock(&ipu_di_lock);
return di;
}
EXPORT_SYMBOL_GPL(ipu_di_get);
void ipu_di_put(struct ipu_di *di)
{
mutex_lock(&ipu_di_lock);
di->inuse = false;
mutex_unlock(&ipu_di_lock);
}
EXPORT_SYMBOL_GPL(ipu_di_put);
int ipu_di_init(struct ipu_soc *ipu, struct device *dev, int id,
unsigned long base,
u32 module, struct clk *clk_ipu)
{
struct ipu_di *di;
if (id > 1)
return -ENODEV;
di = devm_kzalloc(dev, sizeof(*di), GFP_KERNEL);
if (!di)
return -ENOMEM;
ipu->di_priv[id] = di;
di->clk_di = devm_clk_get(dev, id ? "di1" : "di0");
if (IS_ERR(di->clk_di))
return PTR_ERR(di->clk_di);
di->module = module;
di->id = id;
di->clk_ipu = clk_ipu;
di->base = devm_ioremap(dev, base, PAGE_SIZE);
if (!di->base)
return -ENOMEM;
ipu_di_write(di, 0x10, DI_BS_CLKGEN0);
dev_dbg(dev, "DI%d base: 0x%08lx remapped to %p\n",
id, base, di->base);
di->inuse = false;
di->ipu = ipu;
return 0;
}
void ipu_di_exit(struct ipu_soc *ipu, int id)
{
}
+214
View File
@@ -0,0 +1,214 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*/
#include <linux/export.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <video/imx-ipu-v3.h>
#include "ipu-prv.h"
#define DMFC_RD_CHAN 0x0000
#define DMFC_WR_CHAN 0x0004
#define DMFC_WR_CHAN_DEF 0x0008
#define DMFC_DP_CHAN 0x000c
#define DMFC_DP_CHAN_DEF 0x0010
#define DMFC_GENERAL1 0x0014
#define DMFC_GENERAL2 0x0018
#define DMFC_IC_CTRL 0x001c
#define DMFC_WR_CHAN_ALT 0x0020
#define DMFC_WR_CHAN_DEF_ALT 0x0024
#define DMFC_DP_CHAN_ALT 0x0028
#define DMFC_DP_CHAN_DEF_ALT 0x002c
#define DMFC_GENERAL1_ALT 0x0030
#define DMFC_STAT 0x0034
#define DMFC_WR_CHAN_1_28 0
#define DMFC_WR_CHAN_2_41 8
#define DMFC_WR_CHAN_1C_42 16
#define DMFC_WR_CHAN_2C_43 24
#define DMFC_DP_CHAN_5B_23 0
#define DMFC_DP_CHAN_5F_27 8
#define DMFC_DP_CHAN_6B_24 16
#define DMFC_DP_CHAN_6F_29 24
struct dmfc_channel_data {
int ipu_channel;
unsigned long channel_reg;
unsigned long shift;
unsigned eot_shift;
unsigned max_fifo_lines;
};
static const struct dmfc_channel_data dmfcdata[] = {
{
.ipu_channel = IPUV3_CHANNEL_MEM_BG_SYNC,
.channel_reg = DMFC_DP_CHAN,
.shift = DMFC_DP_CHAN_5B_23,
.eot_shift = 20,
.max_fifo_lines = 3,
}, {
.ipu_channel = 24,
.channel_reg = DMFC_DP_CHAN,
.shift = DMFC_DP_CHAN_6B_24,
.eot_shift = 22,
.max_fifo_lines = 1,
}, {
.ipu_channel = IPUV3_CHANNEL_MEM_FG_SYNC,
.channel_reg = DMFC_DP_CHAN,
.shift = DMFC_DP_CHAN_5F_27,
.eot_shift = 21,
.max_fifo_lines = 2,
}, {
.ipu_channel = IPUV3_CHANNEL_MEM_DC_SYNC,
.channel_reg = DMFC_WR_CHAN,
.shift = DMFC_WR_CHAN_1_28,
.eot_shift = 16,
.max_fifo_lines = 2,
}, {
.ipu_channel = 29,
.channel_reg = DMFC_DP_CHAN,
.shift = DMFC_DP_CHAN_6F_29,
.eot_shift = 23,
.max_fifo_lines = 1,
},
};
#define DMFC_NUM_CHANNELS ARRAY_SIZE(dmfcdata)
struct ipu_dmfc_priv;
struct dmfc_channel {
unsigned slots;
struct ipu_soc *ipu;
struct ipu_dmfc_priv *priv;
const struct dmfc_channel_data *data;
};
struct ipu_dmfc_priv {
struct ipu_soc *ipu;
struct device *dev;
struct dmfc_channel channels[DMFC_NUM_CHANNELS];
struct mutex mutex;
void __iomem *base;
int use_count;
};
int ipu_dmfc_enable_channel(struct dmfc_channel *dmfc)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
mutex_lock(&priv->mutex);
if (!priv->use_count)
ipu_module_enable(priv->ipu, IPU_CONF_DMFC_EN);
priv->use_count++;
mutex_unlock(&priv->mutex);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dmfc_enable_channel);
void ipu_dmfc_disable_channel(struct dmfc_channel *dmfc)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
mutex_lock(&priv->mutex);
priv->use_count--;
if (!priv->use_count)
ipu_module_disable(priv->ipu, IPU_CONF_DMFC_EN);
if (priv->use_count < 0)
priv->use_count = 0;
mutex_unlock(&priv->mutex);
}
EXPORT_SYMBOL_GPL(ipu_dmfc_disable_channel);
void ipu_dmfc_config_wait4eot(struct dmfc_channel *dmfc, int width)
{
struct ipu_dmfc_priv *priv = dmfc->priv;
u32 dmfc_gen1;
mutex_lock(&priv->mutex);
dmfc_gen1 = readl(priv->base + DMFC_GENERAL1);
if ((dmfc->slots * 64 * 4) / width > dmfc->data->max_fifo_lines)
dmfc_gen1 |= 1 << dmfc->data->eot_shift;
else
dmfc_gen1 &= ~(1 << dmfc->data->eot_shift);
writel(dmfc_gen1, priv->base + DMFC_GENERAL1);
mutex_unlock(&priv->mutex);
}
EXPORT_SYMBOL_GPL(ipu_dmfc_config_wait4eot);
struct dmfc_channel *ipu_dmfc_get(struct ipu_soc *ipu, int ipu_channel)
{
struct ipu_dmfc_priv *priv = ipu->dmfc_priv;
int i;
for (i = 0; i < DMFC_NUM_CHANNELS; i++)
if (dmfcdata[i].ipu_channel == ipu_channel)
return &priv->channels[i];
return ERR_PTR(-ENODEV);
}
EXPORT_SYMBOL_GPL(ipu_dmfc_get);
void ipu_dmfc_put(struct dmfc_channel *dmfc)
{
}
EXPORT_SYMBOL_GPL(ipu_dmfc_put);
int ipu_dmfc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base,
struct clk *ipu_clk)
{
struct ipu_dmfc_priv *priv;
int i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->base = devm_ioremap(dev, base, PAGE_SIZE);
if (!priv->base)
return -ENOMEM;
priv->dev = dev;
priv->ipu = ipu;
mutex_init(&priv->mutex);
ipu->dmfc_priv = priv;
for (i = 0; i < DMFC_NUM_CHANNELS; i++) {
priv->channels[i].priv = priv;
priv->channels[i].ipu = ipu;
priv->channels[i].data = &dmfcdata[i];
if (dmfcdata[i].ipu_channel == IPUV3_CHANNEL_MEM_BG_SYNC ||
dmfcdata[i].ipu_channel == IPUV3_CHANNEL_MEM_FG_SYNC ||
dmfcdata[i].ipu_channel == IPUV3_CHANNEL_MEM_DC_SYNC)
priv->channels[i].slots = 2;
}
writel(0x00000050, priv->base + DMFC_WR_CHAN);
writel(0x00005654, priv->base + DMFC_DP_CHAN);
writel(0x202020f6, priv->base + DMFC_WR_CHAN_DEF);
writel(0x2020f6f6, priv->base + DMFC_DP_CHAN_DEF);
writel(0x00000003, priv->base + DMFC_GENERAL1);
return 0;
}
void ipu_dmfc_exit(struct ipu_soc *ipu)
{
}
+376
View File
@@ -0,0 +1,376 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*/
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/err.h>
#include <drm/drm_color_mgmt.h>
#include <video/imx-ipu-v3.h>
#include "ipu-prv.h"
#define DP_SYNC 0
#define DP_ASYNC0 0x60
#define DP_ASYNC1 0xBC
#define DP_COM_CONF 0x0
#define DP_GRAPH_WIND_CTRL 0x0004
#define DP_FG_POS 0x0008
#define DP_CSC_A_0 0x0044
#define DP_CSC_A_1 0x0048
#define DP_CSC_A_2 0x004C
#define DP_CSC_A_3 0x0050
#define DP_CSC_0 0x0054
#define DP_CSC_1 0x0058
#define DP_COM_CONF_FG_EN (1 << 0)
#define DP_COM_CONF_GWSEL (1 << 1)
#define DP_COM_CONF_GWAM (1 << 2)
#define DP_COM_CONF_GWCKE (1 << 3)
#define DP_COM_CONF_CSC_DEF_MASK (3 << 8)
#define DP_COM_CONF_CSC_DEF_OFFSET 8
#define DP_COM_CONF_CSC_DEF_FG (3 << 8)
#define DP_COM_CONF_CSC_DEF_BG (2 << 8)
#define DP_COM_CONF_CSC_DEF_BOTH (1 << 8)
#define IPUV3_NUM_FLOWS 3
struct ipu_dp_priv;
struct ipu_dp {
u32 flow;
bool in_use;
bool foreground;
enum ipu_color_space in_cs;
};
struct ipu_flow {
struct ipu_dp foreground;
struct ipu_dp background;
enum ipu_color_space out_cs;
void __iomem *base;
struct ipu_dp_priv *priv;
};
struct ipu_dp_priv {
struct ipu_soc *ipu;
struct device *dev;
void __iomem *base;
struct ipu_flow flow[IPUV3_NUM_FLOWS];
struct mutex mutex;
int use_count;
};
static u32 ipu_dp_flow_base[] = {DP_SYNC, DP_ASYNC0, DP_ASYNC1};
static inline struct ipu_flow *to_flow(struct ipu_dp *dp)
{
if (dp->foreground)
return container_of(dp, struct ipu_flow, foreground);
else
return container_of(dp, struct ipu_flow, background);
}
int ipu_dp_set_global_alpha(struct ipu_dp *dp, bool enable,
u8 alpha, bool bg_chan)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
u32 reg;
mutex_lock(&priv->mutex);
reg = readl(flow->base + DP_COM_CONF);
if (bg_chan)
reg &= ~DP_COM_CONF_GWSEL;
else
reg |= DP_COM_CONF_GWSEL;
writel(reg, flow->base + DP_COM_CONF);
if (enable) {
reg = readl(flow->base + DP_GRAPH_WIND_CTRL) & 0x00FFFFFFL;
writel(reg | ((u32) alpha << 24),
flow->base + DP_GRAPH_WIND_CTRL);
reg = readl(flow->base + DP_COM_CONF);
writel(reg | DP_COM_CONF_GWAM, flow->base + DP_COM_CONF);
} else {
reg = readl(flow->base + DP_COM_CONF);
writel(reg & ~DP_COM_CONF_GWAM, flow->base + DP_COM_CONF);
}
ipu_srm_dp_update(priv->ipu, true);
mutex_unlock(&priv->mutex);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dp_set_global_alpha);
int ipu_dp_set_window_pos(struct ipu_dp *dp, u16 x_pos, u16 y_pos)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
writel((x_pos << 16) | y_pos, flow->base + DP_FG_POS);
ipu_srm_dp_update(priv->ipu, true);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dp_set_window_pos);
static void ipu_dp_csc_init(struct ipu_flow *flow,
enum drm_color_encoding ycbcr_enc,
enum drm_color_range range,
enum ipu_color_space in,
enum ipu_color_space out,
u32 place)
{
u32 reg;
reg = readl(flow->base + DP_COM_CONF);
reg &= ~DP_COM_CONF_CSC_DEF_MASK;
if (in == out) {
writel(reg, flow->base + DP_COM_CONF);
return;
}
if (in == IPUV3_COLORSPACE_RGB && out == IPUV3_COLORSPACE_YUV) {
writel(0x099 | (0x12d << 16), flow->base + DP_CSC_A_0);
writel(0x03a | (0x3a9 << 16), flow->base + DP_CSC_A_1);
writel(0x356 | (0x100 << 16), flow->base + DP_CSC_A_2);
writel(0x100 | (0x329 << 16), flow->base + DP_CSC_A_3);
writel(0x3d6 | (0x0000 << 16) | (2 << 30),
flow->base + DP_CSC_0);
writel(0x200 | (2 << 14) | (0x200 << 16) | (2 << 30),
flow->base + DP_CSC_1);
} else if (ycbcr_enc == DRM_COLOR_YCBCR_BT709) {
/* Rec.709 limited range */
writel(0x095 | (0x000 << 16), flow->base + DP_CSC_A_0);
writel(0x0e5 | (0x095 << 16), flow->base + DP_CSC_A_1);
writel(0x3e5 | (0x3bc << 16), flow->base + DP_CSC_A_2);
writel(0x095 | (0x10e << 16), flow->base + DP_CSC_A_3);
writel(0x000 | (0x3e10 << 16) | (1 << 30),
flow->base + DP_CSC_0);
writel(0x09a | (1 << 14) | (0x3dbe << 16) | (1 << 30),
flow->base + DP_CSC_1);
} else {
/* BT.601 limited range */
writel(0x095 | (0x000 << 16), flow->base + DP_CSC_A_0);
writel(0x0cc | (0x095 << 16), flow->base + DP_CSC_A_1);
writel(0x3ce | (0x398 << 16), flow->base + DP_CSC_A_2);
writel(0x095 | (0x0ff << 16), flow->base + DP_CSC_A_3);
writel(0x000 | (0x3e42 << 16) | (1 << 30),
flow->base + DP_CSC_0);
writel(0x10a | (1 << 14) | (0x3dd6 << 16) | (1 << 30),
flow->base + DP_CSC_1);
}
reg |= place;
writel(reg, flow->base + DP_COM_CONF);
}
int ipu_dp_setup_channel(struct ipu_dp *dp,
enum drm_color_encoding ycbcr_enc,
enum drm_color_range range,
enum ipu_color_space in,
enum ipu_color_space out)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
mutex_lock(&priv->mutex);
dp->in_cs = in;
if (!dp->foreground)
flow->out_cs = out;
if (flow->foreground.in_cs == flow->background.in_cs) {
/*
* foreground and background are of same colorspace, put
* colorspace converter after combining unit.
*/
ipu_dp_csc_init(flow, ycbcr_enc, range,
flow->foreground.in_cs, flow->out_cs,
DP_COM_CONF_CSC_DEF_BOTH);
} else {
if (flow->foreground.in_cs == IPUV3_COLORSPACE_UNKNOWN ||
flow->foreground.in_cs == flow->out_cs)
/*
* foreground identical to output, apply color
* conversion on background
*/
ipu_dp_csc_init(flow, ycbcr_enc, range,
flow->background.in_cs,
flow->out_cs, DP_COM_CONF_CSC_DEF_BG);
else
ipu_dp_csc_init(flow, ycbcr_enc, range,
flow->foreground.in_cs,
flow->out_cs, DP_COM_CONF_CSC_DEF_FG);
}
ipu_srm_dp_update(priv->ipu, true);
mutex_unlock(&priv->mutex);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dp_setup_channel);
int ipu_dp_enable(struct ipu_soc *ipu)
{
struct ipu_dp_priv *priv = ipu->dp_priv;
mutex_lock(&priv->mutex);
if (!priv->use_count)
ipu_module_enable(priv->ipu, IPU_CONF_DP_EN);
priv->use_count++;
mutex_unlock(&priv->mutex);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dp_enable);
int ipu_dp_enable_channel(struct ipu_dp *dp)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
u32 reg;
if (!dp->foreground)
return 0;
mutex_lock(&priv->mutex);
reg = readl(flow->base + DP_COM_CONF);
reg |= DP_COM_CONF_FG_EN;
writel(reg, flow->base + DP_COM_CONF);
ipu_srm_dp_update(priv->ipu, true);
mutex_unlock(&priv->mutex);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_dp_enable_channel);
void ipu_dp_disable_channel(struct ipu_dp *dp, bool sync)
{
struct ipu_flow *flow = to_flow(dp);
struct ipu_dp_priv *priv = flow->priv;
u32 reg, csc;
dp->in_cs = IPUV3_COLORSPACE_UNKNOWN;
if (!dp->foreground)
return;
mutex_lock(&priv->mutex);
reg = readl(flow->base + DP_COM_CONF);
csc = reg & DP_COM_CONF_CSC_DEF_MASK;
reg &= ~DP_COM_CONF_CSC_DEF_MASK;
if (csc == DP_COM_CONF_CSC_DEF_BOTH || csc == DP_COM_CONF_CSC_DEF_BG)
reg |= DP_COM_CONF_CSC_DEF_BG;
reg &= ~DP_COM_CONF_FG_EN;
writel(reg, flow->base + DP_COM_CONF);
writel(0, flow->base + DP_FG_POS);
ipu_srm_dp_update(priv->ipu, sync);
mutex_unlock(&priv->mutex);
}
EXPORT_SYMBOL_GPL(ipu_dp_disable_channel);
void ipu_dp_disable(struct ipu_soc *ipu)
{
struct ipu_dp_priv *priv = ipu->dp_priv;
mutex_lock(&priv->mutex);
priv->use_count--;
if (!priv->use_count)
ipu_module_disable(priv->ipu, IPU_CONF_DP_EN);
if (priv->use_count < 0)
priv->use_count = 0;
mutex_unlock(&priv->mutex);
}
EXPORT_SYMBOL_GPL(ipu_dp_disable);
struct ipu_dp *ipu_dp_get(struct ipu_soc *ipu, unsigned int flow)
{
struct ipu_dp_priv *priv = ipu->dp_priv;
struct ipu_dp *dp;
if ((flow >> 1) >= IPUV3_NUM_FLOWS)
return ERR_PTR(-EINVAL);
if (flow & 1)
dp = &priv->flow[flow >> 1].foreground;
else
dp = &priv->flow[flow >> 1].background;
if (dp->in_use)
return ERR_PTR(-EBUSY);
dp->in_use = true;
return dp;
}
EXPORT_SYMBOL_GPL(ipu_dp_get);
void ipu_dp_put(struct ipu_dp *dp)
{
dp->in_use = false;
}
EXPORT_SYMBOL_GPL(ipu_dp_put);
int ipu_dp_init(struct ipu_soc *ipu, struct device *dev, unsigned long base)
{
struct ipu_dp_priv *priv;
int i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
priv->ipu = ipu;
ipu->dp_priv = priv;
priv->base = devm_ioremap(dev, base, PAGE_SIZE);
if (!priv->base)
return -ENOMEM;
mutex_init(&priv->mutex);
for (i = 0; i < IPUV3_NUM_FLOWS; i++) {
priv->flow[i].background.in_cs = IPUV3_COLORSPACE_UNKNOWN;
priv->flow[i].foreground.in_cs = IPUV3_COLORSPACE_UNKNOWN;
priv->flow[i].foreground.foreground = true;
priv->flow[i].base = priv->base + ipu_dp_flow_base[i];
priv->flow[i].priv = priv;
}
return 0;
}
void ipu_dp_exit(struct ipu_soc *ipu)
{
}
+409
View File
@@ -0,0 +1,409 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019 Mentor Graphics Inc.
*/
#include <linux/types.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/sizes.h>
#include "ipu-prv.h"
#define QUANT_MAP(q) \
((q) == V4L2_QUANTIZATION_FULL_RANGE || \
(q) == V4L2_QUANTIZATION_DEFAULT ? 0 : 1)
/* identity matrix */
static const struct ipu_ic_csc_params identity = {
.coeff = {
{ 128, 0, 0, },
{ 0, 128, 0, },
{ 0, 0, 128, },
},
.offset = { 0, 0, 0, },
.scale = 2,
};
/*
* RGB full-range to RGB limited-range
*
* R_lim = 0.8588 * R_full + 16
* G_lim = 0.8588 * G_full + 16
* B_lim = 0.8588 * B_full + 16
*/
static const struct ipu_ic_csc_params rgbf2rgbl = {
.coeff = {
{ 220, 0, 0, },
{ 0, 220, 0, },
{ 0, 0, 220, },
},
.offset = { 64, 64, 64, },
.scale = 1,
};
/*
* RGB limited-range to RGB full-range
*
* R_full = 1.1644 * (R_lim - 16)
* G_full = 1.1644 * (G_lim - 16)
* B_full = 1.1644 * (B_lim - 16)
*/
static const struct ipu_ic_csc_params rgbl2rgbf = {
.coeff = {
{ 149, 0, 0, },
{ 0, 149, 0, },
{ 0, 0, 149, },
},
.offset = { -37, -37, -37, },
.scale = 2,
};
/*
* YUV full-range to YUV limited-range
*
* Y_lim = 0.8588 * Y_full + 16
* Cb_lim = 0.8784 * (Cb_full - 128) + 128
* Cr_lim = 0.8784 * (Cr_full - 128) + 128
*/
static const struct ipu_ic_csc_params yuvf2yuvl = {
.coeff = {
{ 220, 0, 0, },
{ 0, 225, 0, },
{ 0, 0, 225, },
},
.offset = { 64, 62, 62, },
.scale = 1,
.sat = true,
};
/*
* YUV limited-range to YUV full-range
*
* Y_full = 1.1644 * (Y_lim - 16)
* Cb_full = 1.1384 * (Cb_lim - 128) + 128
* Cr_full = 1.1384 * (Cr_lim - 128) + 128
*/
static const struct ipu_ic_csc_params yuvl2yuvf = {
.coeff = {
{ 149, 0, 0, },
{ 0, 146, 0, },
{ 0, 0, 146, },
},
.offset = { -37, -35, -35, },
.scale = 2,
};
static const struct ipu_ic_csc_params *rgb2rgb[] = {
&identity,
&rgbf2rgbl,
&rgbl2rgbf,
&identity,
};
static const struct ipu_ic_csc_params *yuv2yuv[] = {
&identity,
&yuvf2yuvl,
&yuvl2yuvf,
&identity,
};
/*
* BT.601 RGB full-range to YUV full-range
*
* Y = .2990 * R + .5870 * G + .1140 * B
* U = -.1687 * R - .3313 * G + .5000 * B + 128
* V = .5000 * R - .4187 * G - .0813 * B + 128
*/
static const struct ipu_ic_csc_params rgbf2yuvf_601 = {
.coeff = {
{ 77, 150, 29, },
{ -43, -85, 128, },
{ 128, -107, -21, },
},
.offset = { 0, 512, 512, },
.scale = 1,
};
/* BT.601 RGB full-range to YUV limited-range */
static const struct ipu_ic_csc_params rgbf2yuvl_601 = {
.coeff = {
{ 66, 129, 25, },
{ -38, -74, 112, },
{ 112, -94, -18, },
},
.offset = { 64, 512, 512, },
.scale = 1,
.sat = true,
};
/* BT.601 RGB limited-range to YUV full-range */
static const struct ipu_ic_csc_params rgbl2yuvf_601 = {
.coeff = {
{ 89, 175, 34, },
{ -50, -99, 149, },
{ 149, -125, -24, },
},
.offset = { -75, 512, 512, },
.scale = 1,
};
/* BT.601 RGB limited-range to YUV limited-range */
static const struct ipu_ic_csc_params rgbl2yuvl_601 = {
.coeff = {
{ 77, 150, 29, },
{ -44, -87, 131, },
{ 131, -110, -21, },
},
.offset = { 0, 512, 512, },
.scale = 1,
.sat = true,
};
/*
* BT.601 YUV full-range to RGB full-range
*
* R = 1. * Y + 0 * (Cb - 128) + 1.4020 * (Cr - 128)
* G = 1. * Y - .3441 * (Cb - 128) - .7141 * (Cr - 128)
* B = 1. * Y + 1.7720 * (Cb - 128) + 0 * (Cr - 128)
*
* equivalently (factoring out the offsets):
*
* R = 1. * Y + 0 * Cb + 1.4020 * Cr - 179.456
* G = 1. * Y - .3441 * Cb - .7141 * Cr + 135.450
* B = 1. * Y + 1.7720 * Cb + 0 * Cr - 226.816
*/
static const struct ipu_ic_csc_params yuvf2rgbf_601 = {
.coeff = {
{ 128, 0, 179, },
{ 128, -44, -91, },
{ 128, 227, 0, },
},
.offset = { -359, 271, -454, },
.scale = 2,
};
/* BT.601 YUV full-range to RGB limited-range */
static const struct ipu_ic_csc_params yuvf2rgbl_601 = {
.coeff = {
{ 110, 0, 154, },
{ 110, -38, -78, },
{ 110, 195, 0, },
},
.offset = { -276, 265, -358, },
.scale = 2,
};
/* BT.601 YUV limited-range to RGB full-range */
static const struct ipu_ic_csc_params yuvl2rgbf_601 = {
.coeff = {
{ 75, 0, 102, },
{ 75, -25, -52, },
{ 75, 129, 0, },
},
.offset = { -223, 136, -277, },
.scale = 3,
};
/* BT.601 YUV limited-range to RGB limited-range */
static const struct ipu_ic_csc_params yuvl2rgbl_601 = {
.coeff = {
{ 128, 0, 175, },
{ 128, -43, -89, },
{ 128, 222, 0, },
},
.offset = { -351, 265, -443, },
.scale = 2,
};
static const struct ipu_ic_csc_params *rgb2yuv_601[] = {
&rgbf2yuvf_601,
&rgbf2yuvl_601,
&rgbl2yuvf_601,
&rgbl2yuvl_601,
};
static const struct ipu_ic_csc_params *yuv2rgb_601[] = {
&yuvf2rgbf_601,
&yuvf2rgbl_601,
&yuvl2rgbf_601,
&yuvl2rgbl_601,
};
/*
* REC.709 encoding from RGB full range to YUV full range:
*
* Y = .2126 * R + .7152 * G + .0722 * B
* U = -.1146 * R - .3854 * G + .5000 * B + 128
* V = .5000 * R - .4542 * G - .0458 * B + 128
*/
static const struct ipu_ic_csc_params rgbf2yuvf_709 = {
.coeff = {
{ 54, 183, 19 },
{ -29, -99, 128 },
{ 128, -116, -12 },
},
.offset = { 0, 512, 512 },
.scale = 1,
};
/* Rec.709 RGB full-range to YUV limited-range */
static const struct ipu_ic_csc_params rgbf2yuvl_709 = {
.coeff = {
{ 47, 157, 16, },
{ -26, -87, 112, },
{ 112, -102, -10, },
},
.offset = { 64, 512, 512, },
.scale = 1,
.sat = true,
};
/* Rec.709 RGB limited-range to YUV full-range */
static const struct ipu_ic_csc_params rgbl2yuvf_709 = {
.coeff = {
{ 63, 213, 22, },
{ -34, -115, 149, },
{ 149, -135, -14, },
},
.offset = { -75, 512, 512, },
.scale = 1,
};
/* Rec.709 RGB limited-range to YUV limited-range */
static const struct ipu_ic_csc_params rgbl2yuvl_709 = {
.coeff = {
{ 54, 183, 18, },
{ -30, -101, 131, },
{ 131, -119, -12, },
},
.offset = { 0, 512, 512, },
.scale = 1,
.sat = true,
};
/*
* Inverse REC.709 encoding from YUV full range to RGB full range:
*
* R = 1. * Y + 0 * (Cb - 128) + 1.5748 * (Cr - 128)
* G = 1. * Y - .1873 * (Cb - 128) - .4681 * (Cr - 128)
* B = 1. * Y + 1.8556 * (Cb - 128) + 0 * (Cr - 128)
*
* equivalently (factoring out the offsets):
*
* R = 1. * Y + 0 * Cb + 1.5748 * Cr - 201.574
* G = 1. * Y - .1873 * Cb - .4681 * Cr + 83.891
* B = 1. * Y + 1.8556 * Cb + 0 * Cr - 237.517
*/
static const struct ipu_ic_csc_params yuvf2rgbf_709 = {
.coeff = {
{ 128, 0, 202 },
{ 128, -24, -60 },
{ 128, 238, 0 },
},
.offset = { -403, 168, -475 },
.scale = 2,
};
/* Rec.709 YUV full-range to RGB limited-range */
static const struct ipu_ic_csc_params yuvf2rgbl_709 = {
.coeff = {
{ 110, 0, 173, },
{ 110, -21, -51, },
{ 110, 204, 0, },
},
.offset = { -314, 176, -376, },
.scale = 2,
};
/* Rec.709 YUV limited-range to RGB full-range */
static const struct ipu_ic_csc_params yuvl2rgbf_709 = {
.coeff = {
{ 75, 0, 115, },
{ 75, -14, -34, },
{ 75, 135, 0, },
},
.offset = { -248, 77, -289, },
.scale = 3,
};
/* Rec.709 YUV limited-range to RGB limited-range */
static const struct ipu_ic_csc_params yuvl2rgbl_709 = {
.coeff = {
{ 128, 0, 197, },
{ 128, -23, -59, },
{ 128, 232, 0, },
},
.offset = { -394, 164, -464, },
.scale = 2,
};
static const struct ipu_ic_csc_params *rgb2yuv_709[] = {
&rgbf2yuvf_709,
&rgbf2yuvl_709,
&rgbl2yuvf_709,
&rgbl2yuvl_709,
};
static const struct ipu_ic_csc_params *yuv2rgb_709[] = {
&yuvf2rgbf_709,
&yuvf2rgbl_709,
&yuvl2rgbf_709,
&yuvl2rgbl_709,
};
static int calc_csc_coeffs(struct ipu_ic_csc *csc)
{
const struct ipu_ic_csc_params **params_tbl;
int tbl_idx;
tbl_idx = (QUANT_MAP(csc->in_cs.quant) << 1) |
QUANT_MAP(csc->out_cs.quant);
if (csc->in_cs.cs == csc->out_cs.cs) {
csc->params = (csc->in_cs.cs == IPUV3_COLORSPACE_YUV) ?
*yuv2yuv[tbl_idx] : *rgb2rgb[tbl_idx];
return 0;
}
/* YUV <-> RGB encoding is required */
switch (csc->out_cs.enc) {
case V4L2_YCBCR_ENC_601:
params_tbl = (csc->in_cs.cs == IPUV3_COLORSPACE_YUV) ?
yuv2rgb_601 : rgb2yuv_601;
break;
case V4L2_YCBCR_ENC_709:
params_tbl = (csc->in_cs.cs == IPUV3_COLORSPACE_YUV) ?
yuv2rgb_709 : rgb2yuv_709;
break;
default:
return -ENOTSUPP;
}
csc->params = *params_tbl[tbl_idx];
return 0;
}
int __ipu_ic_calc_csc(struct ipu_ic_csc *csc)
{
return calc_csc_coeffs(csc);
}
EXPORT_SYMBOL_GPL(__ipu_ic_calc_csc);
int ipu_ic_calc_csc(struct ipu_ic_csc *csc,
enum v4l2_ycbcr_encoding in_enc,
enum v4l2_quantization in_quant,
enum ipu_color_space in_cs,
enum v4l2_ycbcr_encoding out_enc,
enum v4l2_quantization out_quant,
enum ipu_color_space out_cs)
{
ipu_ic_fill_colorspace(&csc->in_cs, in_enc, in_quant, in_cs);
ipu_ic_fill_colorspace(&csc->out_cs, out_enc, out_quant, out_cs);
return __ipu_ic_calc_csc(csc);
}
EXPORT_SYMBOL_GPL(ipu_ic_calc_csc);
+688
View File
@@ -0,0 +1,688 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2012-2014 Mentor Graphics Inc.
* Copyright 2005-2012 Freescale Semiconductor, Inc. All Rights Reserved.
*/
#include <linux/types.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/spinlock.h>
#include <linux/bitrev.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/sizes.h>
#include "ipu-prv.h"
/* IC Register Offsets */
#define IC_CONF 0x0000
#define IC_PRP_ENC_RSC 0x0004
#define IC_PRP_VF_RSC 0x0008
#define IC_PP_RSC 0x000C
#define IC_CMBP_1 0x0010
#define IC_CMBP_2 0x0014
#define IC_IDMAC_1 0x0018
#define IC_IDMAC_2 0x001C
#define IC_IDMAC_3 0x0020
#define IC_IDMAC_4 0x0024
/* IC Register Fields */
#define IC_CONF_PRPENC_EN (1 << 0)
#define IC_CONF_PRPENC_CSC1 (1 << 1)
#define IC_CONF_PRPENC_ROT_EN (1 << 2)
#define IC_CONF_PRPVF_EN (1 << 8)
#define IC_CONF_PRPVF_CSC1 (1 << 9)
#define IC_CONF_PRPVF_CSC2 (1 << 10)
#define IC_CONF_PRPVF_CMB (1 << 11)
#define IC_CONF_PRPVF_ROT_EN (1 << 12)
#define IC_CONF_PP_EN (1 << 16)
#define IC_CONF_PP_CSC1 (1 << 17)
#define IC_CONF_PP_CSC2 (1 << 18)
#define IC_CONF_PP_CMB (1 << 19)
#define IC_CONF_PP_ROT_EN (1 << 20)
#define IC_CONF_IC_GLB_LOC_A (1 << 28)
#define IC_CONF_KEY_COLOR_EN (1 << 29)
#define IC_CONF_RWS_EN (1 << 30)
#define IC_CONF_CSI_MEM_WR_EN (1 << 31)
#define IC_IDMAC_1_CB0_BURST_16 (1 << 0)
#define IC_IDMAC_1_CB1_BURST_16 (1 << 1)
#define IC_IDMAC_1_CB2_BURST_16 (1 << 2)
#define IC_IDMAC_1_CB3_BURST_16 (1 << 3)
#define IC_IDMAC_1_CB4_BURST_16 (1 << 4)
#define IC_IDMAC_1_CB5_BURST_16 (1 << 5)
#define IC_IDMAC_1_CB6_BURST_16 (1 << 6)
#define IC_IDMAC_1_CB7_BURST_16 (1 << 7)
#define IC_IDMAC_1_PRPENC_ROT_MASK (0x7 << 11)
#define IC_IDMAC_1_PRPENC_ROT_OFFSET 11
#define IC_IDMAC_1_PRPVF_ROT_MASK (0x7 << 14)
#define IC_IDMAC_1_PRPVF_ROT_OFFSET 14
#define IC_IDMAC_1_PP_ROT_MASK (0x7 << 17)
#define IC_IDMAC_1_PP_ROT_OFFSET 17
#define IC_IDMAC_1_PP_FLIP_RS (1 << 22)
#define IC_IDMAC_1_PRPVF_FLIP_RS (1 << 21)
#define IC_IDMAC_1_PRPENC_FLIP_RS (1 << 20)
#define IC_IDMAC_2_PRPENC_HEIGHT_MASK (0x3ff << 0)
#define IC_IDMAC_2_PRPENC_HEIGHT_OFFSET 0
#define IC_IDMAC_2_PRPVF_HEIGHT_MASK (0x3ff << 10)
#define IC_IDMAC_2_PRPVF_HEIGHT_OFFSET 10
#define IC_IDMAC_2_PP_HEIGHT_MASK (0x3ff << 20)
#define IC_IDMAC_2_PP_HEIGHT_OFFSET 20
#define IC_IDMAC_3_PRPENC_WIDTH_MASK (0x3ff << 0)
#define IC_IDMAC_3_PRPENC_WIDTH_OFFSET 0
#define IC_IDMAC_3_PRPVF_WIDTH_MASK (0x3ff << 10)
#define IC_IDMAC_3_PRPVF_WIDTH_OFFSET 10
#define IC_IDMAC_3_PP_WIDTH_MASK (0x3ff << 20)
#define IC_IDMAC_3_PP_WIDTH_OFFSET 20
struct ic_task_regoffs {
u32 rsc;
u32 tpmem_csc[2];
};
struct ic_task_bitfields {
u32 ic_conf_en;
u32 ic_conf_rot_en;
u32 ic_conf_cmb_en;
u32 ic_conf_csc1_en;
u32 ic_conf_csc2_en;
u32 ic_cmb_galpha_bit;
};
static const struct ic_task_regoffs ic_task_reg[IC_NUM_TASKS] = {
[IC_TASK_ENCODER] = {
.rsc = IC_PRP_ENC_RSC,
.tpmem_csc = {0x2008, 0},
},
[IC_TASK_VIEWFINDER] = {
.rsc = IC_PRP_VF_RSC,
.tpmem_csc = {0x4028, 0x4040},
},
[IC_TASK_POST_PROCESSOR] = {
.rsc = IC_PP_RSC,
.tpmem_csc = {0x6060, 0x6078},
},
};
static const struct ic_task_bitfields ic_task_bit[IC_NUM_TASKS] = {
[IC_TASK_ENCODER] = {
.ic_conf_en = IC_CONF_PRPENC_EN,
.ic_conf_rot_en = IC_CONF_PRPENC_ROT_EN,
.ic_conf_cmb_en = 0, /* NA */
.ic_conf_csc1_en = IC_CONF_PRPENC_CSC1,
.ic_conf_csc2_en = 0, /* NA */
.ic_cmb_galpha_bit = 0, /* NA */
},
[IC_TASK_VIEWFINDER] = {
.ic_conf_en = IC_CONF_PRPVF_EN,
.ic_conf_rot_en = IC_CONF_PRPVF_ROT_EN,
.ic_conf_cmb_en = IC_CONF_PRPVF_CMB,
.ic_conf_csc1_en = IC_CONF_PRPVF_CSC1,
.ic_conf_csc2_en = IC_CONF_PRPVF_CSC2,
.ic_cmb_galpha_bit = 0,
},
[IC_TASK_POST_PROCESSOR] = {
.ic_conf_en = IC_CONF_PP_EN,
.ic_conf_rot_en = IC_CONF_PP_ROT_EN,
.ic_conf_cmb_en = IC_CONF_PP_CMB,
.ic_conf_csc1_en = IC_CONF_PP_CSC1,
.ic_conf_csc2_en = IC_CONF_PP_CSC2,
.ic_cmb_galpha_bit = 8,
},
};
struct ipu_ic_priv;
struct ipu_ic {
enum ipu_ic_task task;
const struct ic_task_regoffs *reg;
const struct ic_task_bitfields *bit;
struct ipu_ic_colorspace in_cs;
struct ipu_ic_colorspace g_in_cs;
struct ipu_ic_colorspace out_cs;
bool graphics;
bool rotation;
bool in_use;
struct ipu_ic_priv *priv;
};
struct ipu_ic_priv {
void __iomem *base;
void __iomem *tpmem_base;
spinlock_t lock;
struct ipu_soc *ipu;
int use_count;
int irt_use_count;
struct ipu_ic task[IC_NUM_TASKS];
};
static inline u32 ipu_ic_read(struct ipu_ic *ic, unsigned offset)
{
return readl(ic->priv->base + offset);
}
static inline void ipu_ic_write(struct ipu_ic *ic, u32 value, unsigned offset)
{
writel(value, ic->priv->base + offset);
}
static int init_csc(struct ipu_ic *ic,
const struct ipu_ic_csc *csc,
int csc_index)
{
struct ipu_ic_priv *priv = ic->priv;
u32 __iomem *base;
const u16 (*c)[3];
const u16 *a;
u32 param;
base = (u32 __iomem *)
(priv->tpmem_base + ic->reg->tpmem_csc[csc_index]);
/* Cast to unsigned */
c = (const u16 (*)[3])csc->params.coeff;
a = (const u16 *)csc->params.offset;
param = ((a[0] & 0x1f) << 27) | ((c[0][0] & 0x1ff) << 18) |
((c[1][1] & 0x1ff) << 9) | (c[2][2] & 0x1ff);
writel(param, base++);
param = ((a[0] & 0x1fe0) >> 5) | (csc->params.scale << 8) |
(csc->params.sat << 10);
writel(param, base++);
param = ((a[1] & 0x1f) << 27) | ((c[0][1] & 0x1ff) << 18) |
((c[1][0] & 0x1ff) << 9) | (c[2][0] & 0x1ff);
writel(param, base++);
param = ((a[1] & 0x1fe0) >> 5);
writel(param, base++);
param = ((a[2] & 0x1f) << 27) | ((c[0][2] & 0x1ff) << 18) |
((c[1][2] & 0x1ff) << 9) | (c[2][1] & 0x1ff);
writel(param, base++);
param = ((a[2] & 0x1fe0) >> 5);
writel(param, base++);
return 0;
}
static int calc_resize_coeffs(struct ipu_ic *ic,
u32 in_size, u32 out_size,
u32 *resize_coeff,
u32 *downsize_coeff)
{
struct ipu_ic_priv *priv = ic->priv;
struct ipu_soc *ipu = priv->ipu;
u32 temp_size, temp_downsize;
/*
* Input size cannot be more than 4096, and output size cannot
* be more than 1024
*/
if (in_size > 4096) {
dev_err(ipu->dev, "Unsupported resize (in_size > 4096)\n");
return -EINVAL;
}
if (out_size > 1024) {
dev_err(ipu->dev, "Unsupported resize (out_size > 1024)\n");
return -EINVAL;
}
/* Cannot downsize more than 4:1 */
if ((out_size << 2) < in_size) {
dev_err(ipu->dev, "Unsupported downsize\n");
return -EINVAL;
}
/* Compute downsizing coefficient */
temp_downsize = 0;
temp_size = in_size;
while (((temp_size > 1024) || (temp_size >= out_size * 2)) &&
(temp_downsize < 2)) {
temp_size >>= 1;
temp_downsize++;
}
*downsize_coeff = temp_downsize;
/*
* compute resizing coefficient using the following equation:
* resize_coeff = M * (SI - 1) / (SO - 1)
* where M = 2^13, SI = input size, SO = output size
*/
*resize_coeff = (8192L * (temp_size - 1)) / (out_size - 1);
if (*resize_coeff >= 16384L) {
dev_err(ipu->dev, "Warning! Overflow on resize coeff.\n");
*resize_coeff = 0x3FFF;
}
return 0;
}
void ipu_ic_task_enable(struct ipu_ic *ic)
{
struct ipu_ic_priv *priv = ic->priv;
unsigned long flags;
u32 ic_conf;
spin_lock_irqsave(&priv->lock, flags);
ic_conf = ipu_ic_read(ic, IC_CONF);
ic_conf |= ic->bit->ic_conf_en;
if (ic->rotation)
ic_conf |= ic->bit->ic_conf_rot_en;
if (ic->in_cs.cs != ic->out_cs.cs)
ic_conf |= ic->bit->ic_conf_csc1_en;
if (ic->graphics) {
ic_conf |= ic->bit->ic_conf_cmb_en;
ic_conf |= ic->bit->ic_conf_csc1_en;
if (ic->g_in_cs.cs != ic->out_cs.cs)
ic_conf |= ic->bit->ic_conf_csc2_en;
}
ipu_ic_write(ic, ic_conf, IC_CONF);
spin_unlock_irqrestore(&priv->lock, flags);
}
EXPORT_SYMBOL_GPL(ipu_ic_task_enable);
void ipu_ic_task_disable(struct ipu_ic *ic)
{
struct ipu_ic_priv *priv = ic->priv;
unsigned long flags;
u32 ic_conf;
spin_lock_irqsave(&priv->lock, flags);
ic_conf = ipu_ic_read(ic, IC_CONF);
ic_conf &= ~(ic->bit->ic_conf_en |
ic->bit->ic_conf_csc1_en |
ic->bit->ic_conf_rot_en);
if (ic->bit->ic_conf_csc2_en)
ic_conf &= ~ic->bit->ic_conf_csc2_en;
if (ic->bit->ic_conf_cmb_en)
ic_conf &= ~ic->bit->ic_conf_cmb_en;
ipu_ic_write(ic, ic_conf, IC_CONF);
spin_unlock_irqrestore(&priv->lock, flags);
}
EXPORT_SYMBOL_GPL(ipu_ic_task_disable);
int ipu_ic_task_init_rsc(struct ipu_ic *ic,
const struct ipu_ic_csc *csc,
int in_width, int in_height,
int out_width, int out_height,
u32 rsc)
{
struct ipu_ic_priv *priv = ic->priv;
u32 downsize_coeff, resize_coeff;
unsigned long flags;
int ret = 0;
if (!rsc) {
/* Setup vertical resizing */
ret = calc_resize_coeffs(ic, in_height, out_height,
&resize_coeff, &downsize_coeff);
if (ret)
return ret;
rsc = (downsize_coeff << 30) | (resize_coeff << 16);
/* Setup horizontal resizing */
ret = calc_resize_coeffs(ic, in_width, out_width,
&resize_coeff, &downsize_coeff);
if (ret)
return ret;
rsc |= (downsize_coeff << 14) | resize_coeff;
}
spin_lock_irqsave(&priv->lock, flags);
ipu_ic_write(ic, rsc, ic->reg->rsc);
/* Setup color space conversion */
ic->in_cs = csc->in_cs;
ic->out_cs = csc->out_cs;
ret = init_csc(ic, csc, 0);
spin_unlock_irqrestore(&priv->lock, flags);
return ret;
}
int ipu_ic_task_init(struct ipu_ic *ic,
const struct ipu_ic_csc *csc,
int in_width, int in_height,
int out_width, int out_height)
{
return ipu_ic_task_init_rsc(ic, csc,
in_width, in_height,
out_width, out_height, 0);
}
EXPORT_SYMBOL_GPL(ipu_ic_task_init);
int ipu_ic_task_idma_init(struct ipu_ic *ic, struct ipuv3_channel *channel,
u32 width, u32 height, int burst_size,
enum ipu_rotate_mode rot)
{
struct ipu_ic_priv *priv = ic->priv;
struct ipu_soc *ipu = priv->ipu;
u32 ic_idmac_1, ic_idmac_2, ic_idmac_3;
u32 temp_rot = bitrev8(rot) >> 5;
bool need_hor_flip = false;
unsigned long flags;
int ret = 0;
if ((burst_size != 8) && (burst_size != 16)) {
dev_err(ipu->dev, "Illegal burst length for IC\n");
return -EINVAL;
}
width--;
height--;
if (temp_rot & 0x2) /* Need horizontal flip */
need_hor_flip = true;
spin_lock_irqsave(&priv->lock, flags);
ic_idmac_1 = ipu_ic_read(ic, IC_IDMAC_1);
ic_idmac_2 = ipu_ic_read(ic, IC_IDMAC_2);
ic_idmac_3 = ipu_ic_read(ic, IC_IDMAC_3);
switch (channel->num) {
case IPUV3_CHANNEL_IC_PP_MEM:
if (burst_size == 16)
ic_idmac_1 |= IC_IDMAC_1_CB2_BURST_16;
else
ic_idmac_1 &= ~IC_IDMAC_1_CB2_BURST_16;
if (need_hor_flip)
ic_idmac_1 |= IC_IDMAC_1_PP_FLIP_RS;
else
ic_idmac_1 &= ~IC_IDMAC_1_PP_FLIP_RS;
ic_idmac_2 &= ~IC_IDMAC_2_PP_HEIGHT_MASK;
ic_idmac_2 |= height << IC_IDMAC_2_PP_HEIGHT_OFFSET;
ic_idmac_3 &= ~IC_IDMAC_3_PP_WIDTH_MASK;
ic_idmac_3 |= width << IC_IDMAC_3_PP_WIDTH_OFFSET;
break;
case IPUV3_CHANNEL_MEM_IC_PP:
if (burst_size == 16)
ic_idmac_1 |= IC_IDMAC_1_CB5_BURST_16;
else
ic_idmac_1 &= ~IC_IDMAC_1_CB5_BURST_16;
break;
case IPUV3_CHANNEL_MEM_ROT_PP:
ic_idmac_1 &= ~IC_IDMAC_1_PP_ROT_MASK;
ic_idmac_1 |= temp_rot << IC_IDMAC_1_PP_ROT_OFFSET;
break;
case IPUV3_CHANNEL_MEM_IC_PRP_VF:
if (burst_size == 16)
ic_idmac_1 |= IC_IDMAC_1_CB6_BURST_16;
else
ic_idmac_1 &= ~IC_IDMAC_1_CB6_BURST_16;
break;
case IPUV3_CHANNEL_IC_PRP_ENC_MEM:
if (burst_size == 16)
ic_idmac_1 |= IC_IDMAC_1_CB0_BURST_16;
else
ic_idmac_1 &= ~IC_IDMAC_1_CB0_BURST_16;
if (need_hor_flip)
ic_idmac_1 |= IC_IDMAC_1_PRPENC_FLIP_RS;
else
ic_idmac_1 &= ~IC_IDMAC_1_PRPENC_FLIP_RS;
ic_idmac_2 &= ~IC_IDMAC_2_PRPENC_HEIGHT_MASK;
ic_idmac_2 |= height << IC_IDMAC_2_PRPENC_HEIGHT_OFFSET;
ic_idmac_3 &= ~IC_IDMAC_3_PRPENC_WIDTH_MASK;
ic_idmac_3 |= width << IC_IDMAC_3_PRPENC_WIDTH_OFFSET;
break;
case IPUV3_CHANNEL_MEM_ROT_ENC:
ic_idmac_1 &= ~IC_IDMAC_1_PRPENC_ROT_MASK;
ic_idmac_1 |= temp_rot << IC_IDMAC_1_PRPENC_ROT_OFFSET;
break;
case IPUV3_CHANNEL_IC_PRP_VF_MEM:
if (burst_size == 16)
ic_idmac_1 |= IC_IDMAC_1_CB1_BURST_16;
else
ic_idmac_1 &= ~IC_IDMAC_1_CB1_BURST_16;
if (need_hor_flip)
ic_idmac_1 |= IC_IDMAC_1_PRPVF_FLIP_RS;
else
ic_idmac_1 &= ~IC_IDMAC_1_PRPVF_FLIP_RS;
ic_idmac_2 &= ~IC_IDMAC_2_PRPVF_HEIGHT_MASK;
ic_idmac_2 |= height << IC_IDMAC_2_PRPVF_HEIGHT_OFFSET;
ic_idmac_3 &= ~IC_IDMAC_3_PRPVF_WIDTH_MASK;
ic_idmac_3 |= width << IC_IDMAC_3_PRPVF_WIDTH_OFFSET;
break;
case IPUV3_CHANNEL_MEM_ROT_VF:
ic_idmac_1 &= ~IC_IDMAC_1_PRPVF_ROT_MASK;
ic_idmac_1 |= temp_rot << IC_IDMAC_1_PRPVF_ROT_OFFSET;
break;
case IPUV3_CHANNEL_G_MEM_IC_PRP_VF:
if (burst_size == 16)
ic_idmac_1 |= IC_IDMAC_1_CB3_BURST_16;
else
ic_idmac_1 &= ~IC_IDMAC_1_CB3_BURST_16;
break;
case IPUV3_CHANNEL_G_MEM_IC_PP:
if (burst_size == 16)
ic_idmac_1 |= IC_IDMAC_1_CB4_BURST_16;
else
ic_idmac_1 &= ~IC_IDMAC_1_CB4_BURST_16;
break;
case IPUV3_CHANNEL_VDI_MEM_IC_VF:
if (burst_size == 16)
ic_idmac_1 |= IC_IDMAC_1_CB7_BURST_16;
else
ic_idmac_1 &= ~IC_IDMAC_1_CB7_BURST_16;
break;
default:
goto unlock;
}
ipu_ic_write(ic, ic_idmac_1, IC_IDMAC_1);
ipu_ic_write(ic, ic_idmac_2, IC_IDMAC_2);
ipu_ic_write(ic, ic_idmac_3, IC_IDMAC_3);
if (ipu_rot_mode_is_irt(rot))
ic->rotation = true;
unlock:
spin_unlock_irqrestore(&priv->lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(ipu_ic_task_idma_init);
static void ipu_irt_enable(struct ipu_ic *ic)
{
struct ipu_ic_priv *priv = ic->priv;
if (!priv->irt_use_count)
ipu_module_enable(priv->ipu, IPU_CONF_ROT_EN);
priv->irt_use_count++;
}
static void ipu_irt_disable(struct ipu_ic *ic)
{
struct ipu_ic_priv *priv = ic->priv;
if (priv->irt_use_count) {
if (!--priv->irt_use_count)
ipu_module_disable(priv->ipu, IPU_CONF_ROT_EN);
}
}
int ipu_ic_enable(struct ipu_ic *ic)
{
struct ipu_ic_priv *priv = ic->priv;
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
if (!priv->use_count)
ipu_module_enable(priv->ipu, IPU_CONF_IC_EN);
priv->use_count++;
if (ic->rotation)
ipu_irt_enable(ic);
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_ic_enable);
int ipu_ic_disable(struct ipu_ic *ic)
{
struct ipu_ic_priv *priv = ic->priv;
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
priv->use_count--;
if (!priv->use_count)
ipu_module_disable(priv->ipu, IPU_CONF_IC_EN);
if (priv->use_count < 0)
priv->use_count = 0;
if (ic->rotation)
ipu_irt_disable(ic);
ic->rotation = ic->graphics = false;
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_ic_disable);
struct ipu_ic *ipu_ic_get(struct ipu_soc *ipu, enum ipu_ic_task task)
{
struct ipu_ic_priv *priv = ipu->ic_priv;
unsigned long flags;
struct ipu_ic *ic, *ret;
if (task >= IC_NUM_TASKS)
return ERR_PTR(-EINVAL);
ic = &priv->task[task];
spin_lock_irqsave(&priv->lock, flags);
if (ic->in_use) {
ret = ERR_PTR(-EBUSY);
goto unlock;
}
ic->in_use = true;
ret = ic;
unlock:
spin_unlock_irqrestore(&priv->lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(ipu_ic_get);
void ipu_ic_put(struct ipu_ic *ic)
{
struct ipu_ic_priv *priv = ic->priv;
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
ic->in_use = false;
spin_unlock_irqrestore(&priv->lock, flags);
}
EXPORT_SYMBOL_GPL(ipu_ic_put);
int ipu_ic_init(struct ipu_soc *ipu, struct device *dev,
unsigned long base, unsigned long tpmem_base)
{
struct ipu_ic_priv *priv;
int i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
ipu->ic_priv = priv;
spin_lock_init(&priv->lock);
priv->base = devm_ioremap(dev, base, PAGE_SIZE);
if (!priv->base)
return -ENOMEM;
priv->tpmem_base = devm_ioremap(dev, tpmem_base, SZ_64K);
if (!priv->tpmem_base)
return -ENOMEM;
dev_dbg(dev, "IC base: 0x%08lx remapped to %p\n", base, priv->base);
priv->ipu = ipu;
for (i = 0; i < IC_NUM_TASKS; i++) {
priv->task[i].task = i;
priv->task[i].priv = priv;
priv->task[i].reg = &ic_task_reg[i];
priv->task[i].bit = &ic_task_bit[i];
}
return 0;
}
void ipu_ic_exit(struct ipu_soc *ipu)
{
}
void ipu_ic_dump(struct ipu_ic *ic)
{
struct ipu_ic_priv *priv = ic->priv;
struct ipu_soc *ipu = priv->ipu;
dev_dbg(ipu->dev, "IC_CONF = \t0x%08X\n",
ipu_ic_read(ic, IC_CONF));
dev_dbg(ipu->dev, "IC_PRP_ENC_RSC = \t0x%08X\n",
ipu_ic_read(ic, IC_PRP_ENC_RSC));
dev_dbg(ipu->dev, "IC_PRP_VF_RSC = \t0x%08X\n",
ipu_ic_read(ic, IC_PRP_VF_RSC));
dev_dbg(ipu->dev, "IC_PP_RSC = \t0x%08X\n",
ipu_ic_read(ic, IC_PP_RSC));
dev_dbg(ipu->dev, "IC_CMBP_1 = \t0x%08X\n",
ipu_ic_read(ic, IC_CMBP_1));
dev_dbg(ipu->dev, "IC_CMBP_2 = \t0x%08X\n",
ipu_ic_read(ic, IC_CMBP_2));
dev_dbg(ipu->dev, "IC_IDMAC_1 = \t0x%08X\n",
ipu_ic_read(ic, IC_IDMAC_1));
dev_dbg(ipu->dev, "IC_IDMAC_2 = \t0x%08X\n",
ipu_ic_read(ic, IC_IDMAC_2));
dev_dbg(ipu->dev, "IC_IDMAC_3 = \t0x%08X\n",
ipu_ic_read(ic, IC_IDMAC_3));
dev_dbg(ipu->dev, "IC_IDMAC_4 = \t0x%08X\n",
ipu_ic_read(ic, IC_IDMAC_4));
}
EXPORT_SYMBOL_GPL(ipu_ic_dump);
File diff suppressed because it is too large Load Diff
+382
View File
@@ -0,0 +1,382 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017 Lucas Stach, Pengutronix
*/
#include <drm/drm_fourcc.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/genalloc.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <video/imx-ipu-v3.h>
#include "ipu-prv.h"
#define IPU_PRE_MAX_WIDTH 2048
#define IPU_PRE_NUM_SCANLINES 8
#define IPU_PRE_CTRL 0x000
#define IPU_PRE_CTRL_SET 0x004
#define IPU_PRE_CTRL_ENABLE (1 << 0)
#define IPU_PRE_CTRL_BLOCK_EN (1 << 1)
#define IPU_PRE_CTRL_BLOCK_16 (1 << 2)
#define IPU_PRE_CTRL_SDW_UPDATE (1 << 4)
#define IPU_PRE_CTRL_VFLIP (1 << 5)
#define IPU_PRE_CTRL_SO (1 << 6)
#define IPU_PRE_CTRL_INTERLACED_FIELD (1 << 7)
#define IPU_PRE_CTRL_HANDSHAKE_EN (1 << 8)
#define IPU_PRE_CTRL_HANDSHAKE_LINE_NUM(v) ((v & 0x3) << 9)
#define IPU_PRE_CTRL_HANDSHAKE_ABORT_SKIP_EN (1 << 11)
#define IPU_PRE_CTRL_EN_REPEAT (1 << 28)
#define IPU_PRE_CTRL_TPR_REST_SEL (1 << 29)
#define IPU_PRE_CTRL_CLKGATE (1 << 30)
#define IPU_PRE_CTRL_SFTRST (1 << 31)
#define IPU_PRE_CUR_BUF 0x030
#define IPU_PRE_NEXT_BUF 0x040
#define IPU_PRE_TPR_CTRL 0x070
#define IPU_PRE_TPR_CTRL_TILE_FORMAT(v) ((v & 0xff) << 0)
#define IPU_PRE_TPR_CTRL_TILE_FORMAT_MASK 0xff
#define IPU_PRE_TPR_CTRL_TILE_FORMAT_16_BIT (1 << 0)
#define IPU_PRE_TPR_CTRL_TILE_FORMAT_SPLIT_BUF (1 << 4)
#define IPU_PRE_TPR_CTRL_TILE_FORMAT_SINGLE_BUF (1 << 5)
#define IPU_PRE_TPR_CTRL_TILE_FORMAT_SUPER_TILED (1 << 6)
#define IPU_PRE_PREFETCH_ENG_CTRL 0x080
#define IPU_PRE_PREF_ENG_CTRL_PREFETCH_EN (1 << 0)
#define IPU_PRE_PREF_ENG_CTRL_RD_NUM_BYTES(v) ((v & 0x7) << 1)
#define IPU_PRE_PREF_ENG_CTRL_INPUT_ACTIVE_BPP(v) ((v & 0x3) << 4)
#define IPU_PRE_PREF_ENG_CTRL_INPUT_PIXEL_FORMAT(v) ((v & 0x7) << 8)
#define IPU_PRE_PREF_ENG_CTRL_SHIFT_BYPASS (1 << 11)
#define IPU_PRE_PREF_ENG_CTRL_FIELD_INVERSE (1 << 12)
#define IPU_PRE_PREF_ENG_CTRL_PARTIAL_UV_SWAP (1 << 14)
#define IPU_PRE_PREF_ENG_CTRL_TPR_COOR_OFFSET_EN (1 << 15)
#define IPU_PRE_PREFETCH_ENG_INPUT_SIZE 0x0a0
#define IPU_PRE_PREFETCH_ENG_INPUT_SIZE_WIDTH(v) ((v & 0xffff) << 0)
#define IPU_PRE_PREFETCH_ENG_INPUT_SIZE_HEIGHT(v) ((v & 0xffff) << 16)
#define IPU_PRE_PREFETCH_ENG_PITCH 0x0d0
#define IPU_PRE_PREFETCH_ENG_PITCH_Y(v) ((v & 0xffff) << 0)
#define IPU_PRE_PREFETCH_ENG_PITCH_UV(v) ((v & 0xffff) << 16)
#define IPU_PRE_STORE_ENG_CTRL 0x110
#define IPU_PRE_STORE_ENG_CTRL_STORE_EN (1 << 0)
#define IPU_PRE_STORE_ENG_CTRL_WR_NUM_BYTES(v) ((v & 0x7) << 1)
#define IPU_PRE_STORE_ENG_CTRL_OUTPUT_ACTIVE_BPP(v) ((v & 0x3) << 4)
#define IPU_PRE_STORE_ENG_STATUS 0x120
#define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_X_MASK 0xffff
#define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_X_SHIFT 0
#define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_MASK 0x3fff
#define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_SHIFT 16
#define IPU_PRE_STORE_ENG_STATUS_STORE_FIFO_FULL (1 << 30)
#define IPU_PRE_STORE_ENG_STATUS_STORE_FIELD (1 << 31)
#define IPU_PRE_STORE_ENG_SIZE 0x130
#define IPU_PRE_STORE_ENG_SIZE_INPUT_WIDTH(v) ((v & 0xffff) << 0)
#define IPU_PRE_STORE_ENG_SIZE_INPUT_HEIGHT(v) ((v & 0xffff) << 16)
#define IPU_PRE_STORE_ENG_PITCH 0x140
#define IPU_PRE_STORE_ENG_PITCH_OUT_PITCH(v) ((v & 0xffff) << 0)
#define IPU_PRE_STORE_ENG_ADDR 0x150
struct ipu_pre {
struct list_head list;
struct device *dev;
void __iomem *regs;
struct clk *clk_axi;
struct gen_pool *iram;
dma_addr_t buffer_paddr;
void *buffer_virt;
struct {
bool in_use;
uint64_t modifier;
unsigned int height;
unsigned int safe_window_end;
unsigned int bufaddr;
u32 ctrl;
u8 cpp;
} cur;
};
static DEFINE_MUTEX(ipu_pre_list_mutex);
static LIST_HEAD(ipu_pre_list);
static int available_pres;
int ipu_pre_get_available_count(void)
{
return available_pres;
}
struct ipu_pre *
ipu_pre_lookup_by_phandle(struct device *dev, const char *name, int index)
{
struct device_node *pre_node __free(device_node) =
of_parse_phandle(dev->of_node, name, index);
struct ipu_pre *pre;
mutex_lock(&ipu_pre_list_mutex);
list_for_each_entry(pre, &ipu_pre_list, list) {
if (pre_node == pre->dev->of_node) {
mutex_unlock(&ipu_pre_list_mutex);
device_link_add(dev, pre->dev,
DL_FLAG_AUTOREMOVE_CONSUMER);
return pre;
}
}
mutex_unlock(&ipu_pre_list_mutex);
return NULL;
}
int ipu_pre_get(struct ipu_pre *pre)
{
u32 val;
if (pre->cur.in_use)
return -EBUSY;
/* first get the engine out of reset and remove clock gating */
writel(0, pre->regs + IPU_PRE_CTRL);
/* init defaults that should be applied to all streams */
val = IPU_PRE_CTRL_HANDSHAKE_ABORT_SKIP_EN |
IPU_PRE_CTRL_HANDSHAKE_EN |
IPU_PRE_CTRL_TPR_REST_SEL |
IPU_PRE_CTRL_SDW_UPDATE;
writel(val, pre->regs + IPU_PRE_CTRL);
pre->cur.in_use = true;
return 0;
}
void ipu_pre_put(struct ipu_pre *pre)
{
writel(IPU_PRE_CTRL_SFTRST, pre->regs + IPU_PRE_CTRL);
pre->cur.in_use = false;
}
static inline void
ipu_pre_update_safe_window(struct ipu_pre *pre)
{
if (pre->cur.modifier == DRM_FORMAT_MOD_LINEAR)
pre->cur.safe_window_end = pre->cur.height - 2;
else
pre->cur.safe_window_end = DIV_ROUND_UP(pre->cur.height, 4) - 1;
}
static void
ipu_pre_configure_modifier(struct ipu_pre *pre, uint64_t modifier)
{
u32 val;
val = readl(pre->regs + IPU_PRE_TPR_CTRL);
val &= ~IPU_PRE_TPR_CTRL_TILE_FORMAT_MASK;
if (modifier != DRM_FORMAT_MOD_LINEAR) {
/* only support single buffer formats for now */
val |= IPU_PRE_TPR_CTRL_TILE_FORMAT_SINGLE_BUF;
if (modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED)
val |= IPU_PRE_TPR_CTRL_TILE_FORMAT_SUPER_TILED;
if (pre->cur.cpp == 2)
val |= IPU_PRE_TPR_CTRL_TILE_FORMAT_16_BIT;
}
writel(val, pre->regs + IPU_PRE_TPR_CTRL);
if (modifier == DRM_FORMAT_MOD_LINEAR)
pre->cur.ctrl &= ~IPU_PRE_CTRL_BLOCK_EN;
else
pre->cur.ctrl |= IPU_PRE_CTRL_BLOCK_EN;
pre->cur.modifier = modifier;
}
void ipu_pre_configure(struct ipu_pre *pre, unsigned int width,
unsigned int height, unsigned int stride, u32 format,
uint64_t modifier, unsigned int bufaddr)
{
const struct drm_format_info *info = drm_format_info(format);
u32 active_bpp = info->cpp[0] >> 1;
u32 val;
pre->cur.bufaddr = bufaddr;
pre->cur.height = height;
pre->cur.cpp = info->cpp[0];
pre->cur.ctrl = readl(pre->regs + IPU_PRE_CTRL);
/* calculate safe window for ctrl register updates */
ipu_pre_update_safe_window(pre);
writel(bufaddr, pre->regs + IPU_PRE_CUR_BUF);
writel(bufaddr, pre->regs + IPU_PRE_NEXT_BUF);
val = IPU_PRE_PREF_ENG_CTRL_INPUT_PIXEL_FORMAT(0) |
IPU_PRE_PREF_ENG_CTRL_INPUT_ACTIVE_BPP(active_bpp) |
IPU_PRE_PREF_ENG_CTRL_RD_NUM_BYTES(4) |
IPU_PRE_PREF_ENG_CTRL_SHIFT_BYPASS |
IPU_PRE_PREF_ENG_CTRL_PREFETCH_EN;
writel(val, pre->regs + IPU_PRE_PREFETCH_ENG_CTRL);
val = IPU_PRE_PREFETCH_ENG_INPUT_SIZE_WIDTH(width) |
IPU_PRE_PREFETCH_ENG_INPUT_SIZE_HEIGHT(height);
writel(val, pre->regs + IPU_PRE_PREFETCH_ENG_INPUT_SIZE);
val = IPU_PRE_PREFETCH_ENG_PITCH_Y(stride);
writel(val, pre->regs + IPU_PRE_PREFETCH_ENG_PITCH);
val = IPU_PRE_STORE_ENG_CTRL_OUTPUT_ACTIVE_BPP(active_bpp) |
IPU_PRE_STORE_ENG_CTRL_WR_NUM_BYTES(4) |
IPU_PRE_STORE_ENG_CTRL_STORE_EN;
writel(val, pre->regs + IPU_PRE_STORE_ENG_CTRL);
val = IPU_PRE_STORE_ENG_SIZE_INPUT_WIDTH(width) |
IPU_PRE_STORE_ENG_SIZE_INPUT_HEIGHT(height);
writel(val, pre->regs + IPU_PRE_STORE_ENG_SIZE);
val = IPU_PRE_STORE_ENG_PITCH_OUT_PITCH(stride);
writel(val, pre->regs + IPU_PRE_STORE_ENG_PITCH);
writel(pre->buffer_paddr, pre->regs + IPU_PRE_STORE_ENG_ADDR);
ipu_pre_configure_modifier(pre, modifier);
pre->cur.ctrl |= IPU_PRE_CTRL_EN_REPEAT | IPU_PRE_CTRL_ENABLE;
writel(pre->cur.ctrl | IPU_PRE_CTRL_SDW_UPDATE,
pre->regs + IPU_PRE_CTRL);
}
void ipu_pre_update(struct ipu_pre *pre, uint64_t modifier, unsigned int bufaddr)
{
if (bufaddr == pre->cur.bufaddr &&
modifier == pre->cur.modifier)
return;
writel(bufaddr, pre->regs + IPU_PRE_NEXT_BUF);
pre->cur.bufaddr = bufaddr;
if (modifier != pre->cur.modifier)
ipu_pre_configure_modifier(pre, modifier);
for (int i = 0;; i++) {
unsigned short current_yblock;
u32 val;
if (i > 500) {
dev_warn(pre->dev, "timeout waiting for PRE safe window\n");
return;
}
val = readl(pre->regs + IPU_PRE_STORE_ENG_STATUS);
current_yblock =
(val >> IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_SHIFT) &
IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_MASK;
if (current_yblock != 0 &&
current_yblock < pre->cur.safe_window_end)
break;
udelay(10);
cpu_relax();
}
writel(pre->cur.ctrl | IPU_PRE_CTRL_SDW_UPDATE,
pre->regs + IPU_PRE_CTRL);
/* calculate safe window for the next update with the new modifier */
ipu_pre_update_safe_window(pre);
}
bool ipu_pre_update_pending(struct ipu_pre *pre)
{
return !!(readl_relaxed(pre->regs + IPU_PRE_CTRL) &
IPU_PRE_CTRL_SDW_UPDATE);
}
u32 ipu_pre_get_baddr(struct ipu_pre *pre)
{
return (u32)pre->buffer_paddr;
}
static int ipu_pre_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ipu_pre *pre;
pre = devm_kzalloc(dev, sizeof(*pre), GFP_KERNEL);
if (!pre)
return -ENOMEM;
pre->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(pre->regs))
return PTR_ERR(pre->regs);
pre->clk_axi = devm_clk_get(dev, "axi");
if (IS_ERR(pre->clk_axi))
return PTR_ERR(pre->clk_axi);
pre->iram = of_gen_pool_get(dev->of_node, "fsl,iram", 0);
if (!pre->iram)
return -EPROBE_DEFER;
/*
* Allocate IRAM buffer with maximum size. This could be made dynamic,
* but as there is no other user of this IRAM region and we can fit all
* max sized buffers into it, there is no need yet.
*/
pre->buffer_virt = gen_pool_dma_alloc(pre->iram, IPU_PRE_MAX_WIDTH *
IPU_PRE_NUM_SCANLINES * 4,
&pre->buffer_paddr);
if (!pre->buffer_virt)
return -ENOMEM;
clk_prepare_enable(pre->clk_axi);
pre->dev = dev;
platform_set_drvdata(pdev, pre);
mutex_lock(&ipu_pre_list_mutex);
list_add(&pre->list, &ipu_pre_list);
available_pres++;
mutex_unlock(&ipu_pre_list_mutex);
return 0;
}
static void ipu_pre_remove(struct platform_device *pdev)
{
struct ipu_pre *pre = platform_get_drvdata(pdev);
mutex_lock(&ipu_pre_list_mutex);
list_del(&pre->list);
available_pres--;
mutex_unlock(&ipu_pre_list_mutex);
clk_disable_unprepare(pre->clk_axi);
if (pre->buffer_virt)
gen_pool_free(pre->iram, (unsigned long)pre->buffer_virt,
IPU_PRE_MAX_WIDTH * IPU_PRE_NUM_SCANLINES * 4);
}
static const struct of_device_id ipu_pre_dt_ids[] = {
{ .compatible = "fsl,imx6qp-pre", },
{ /* sentinel */ },
};
struct platform_driver ipu_pre_drv = {
.probe = ipu_pre_probe,
.remove = ipu_pre_remove,
.driver = {
.name = "imx-ipu-pre",
.of_match_table = ipu_pre_dt_ids,
},
};
+478
View File
@@ -0,0 +1,478 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2017 Lucas Stach, Pengutronix
*/
#include <drm/drm_fourcc.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/iopoll.h>
#include <linux/mfd/syscon.h>
#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <video/imx-ipu-v3.h>
#include "ipu-prv.h"
#define IPU_PRG_CTL 0x00
#define IPU_PRG_CTL_BYPASS(i) (1 << (0 + i))
#define IPU_PRG_CTL_SOFT_ARID_MASK 0x3
#define IPU_PRG_CTL_SOFT_ARID_SHIFT(i) (8 + i * 2)
#define IPU_PRG_CTL_SOFT_ARID(i, v) ((v & 0x3) << (8 + 2 * i))
#define IPU_PRG_CTL_SO(i) (1 << (16 + i))
#define IPU_PRG_CTL_VFLIP(i) (1 << (19 + i))
#define IPU_PRG_CTL_BLOCK_MODE(i) (1 << (22 + i))
#define IPU_PRG_CTL_CNT_LOAD_EN(i) (1 << (25 + i))
#define IPU_PRG_CTL_SOFTRST (1 << 30)
#define IPU_PRG_CTL_SHADOW_EN (1 << 31)
#define IPU_PRG_STATUS 0x04
#define IPU_PRG_STATUS_BUFFER0_READY(i) (1 << (0 + i * 2))
#define IPU_PRG_STATUS_BUFFER1_READY(i) (1 << (1 + i * 2))
#define IPU_PRG_QOS 0x08
#define IPU_PRG_QOS_ARID_MASK 0xf
#define IPU_PRG_QOS_ARID_SHIFT(i) (0 + i * 4)
#define IPU_PRG_REG_UPDATE 0x0c
#define IPU_PRG_REG_UPDATE_REG_UPDATE (1 << 0)
#define IPU_PRG_STRIDE(i) (0x10 + i * 0x4)
#define IPU_PRG_STRIDE_STRIDE_MASK 0x3fff
#define IPU_PRG_CROP_LINE 0x1c
#define IPU_PRG_THD 0x20
#define IPU_PRG_BADDR(i) (0x24 + i * 0x4)
#define IPU_PRG_OFFSET(i) (0x30 + i * 0x4)
#define IPU_PRG_ILO(i) (0x3c + i * 0x4)
#define IPU_PRG_HEIGHT(i) (0x48 + i * 0x4)
#define IPU_PRG_HEIGHT_PRE_HEIGHT_MASK 0xfff
#define IPU_PRG_HEIGHT_PRE_HEIGHT_SHIFT 0
#define IPU_PRG_HEIGHT_IPU_HEIGHT_MASK 0xfff
#define IPU_PRG_HEIGHT_IPU_HEIGHT_SHIFT 16
struct ipu_prg_channel {
bool enabled;
int used_pre;
};
struct ipu_prg {
struct list_head list;
struct device *dev;
int id;
void __iomem *regs;
struct clk *clk_ipg, *clk_axi;
struct regmap *iomuxc_gpr;
struct ipu_pre *pres[3];
struct ipu_prg_channel chan[3];
};
static DEFINE_MUTEX(ipu_prg_list_mutex);
static LIST_HEAD(ipu_prg_list);
struct ipu_prg *
ipu_prg_lookup_by_phandle(struct device *dev, const char *name, int ipu_id)
{
struct device_node *prg_node = of_parse_phandle(dev->of_node,
name, 0);
struct ipu_prg *prg;
mutex_lock(&ipu_prg_list_mutex);
list_for_each_entry(prg, &ipu_prg_list, list) {
if (prg_node == prg->dev->of_node) {
mutex_unlock(&ipu_prg_list_mutex);
device_link_add(dev, prg->dev,
DL_FLAG_AUTOREMOVE_CONSUMER);
prg->id = ipu_id;
of_node_put(prg_node);
return prg;
}
}
mutex_unlock(&ipu_prg_list_mutex);
of_node_put(prg_node);
return NULL;
}
int ipu_prg_max_active_channels(void)
{
return ipu_pre_get_available_count();
}
EXPORT_SYMBOL_GPL(ipu_prg_max_active_channels);
bool ipu_prg_present(struct ipu_soc *ipu)
{
if (ipu->prg_priv)
return true;
return false;
}
EXPORT_SYMBOL_GPL(ipu_prg_present);
bool ipu_prg_format_supported(struct ipu_soc *ipu, uint32_t format,
uint64_t modifier)
{
const struct drm_format_info *info = drm_format_info(format);
if (info->num_planes != 1)
return false;
switch (modifier) {
case DRM_FORMAT_MOD_LINEAR:
case DRM_FORMAT_MOD_VIVANTE_TILED:
case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED:
return true;
default:
return false;
}
}
EXPORT_SYMBOL_GPL(ipu_prg_format_supported);
int ipu_prg_enable(struct ipu_soc *ipu)
{
struct ipu_prg *prg = ipu->prg_priv;
if (!prg)
return 0;
return pm_runtime_get_sync(prg->dev);
}
EXPORT_SYMBOL_GPL(ipu_prg_enable);
void ipu_prg_disable(struct ipu_soc *ipu)
{
struct ipu_prg *prg = ipu->prg_priv;
if (!prg)
return;
pm_runtime_put(prg->dev);
}
EXPORT_SYMBOL_GPL(ipu_prg_disable);
/*
* The channel configuartion functions below are not thread safe, as they
* must be only called from the atomic commit path in the DRM driver, which
* is properly serialized.
*/
static int ipu_prg_ipu_to_prg_chan(int ipu_chan)
{
/*
* This isn't clearly documented in the RM, but IPU to PRG channel
* assignment is fixed, as only with this mapping the control signals
* match up.
*/
switch (ipu_chan) {
case IPUV3_CHANNEL_MEM_BG_SYNC:
return 0;
case IPUV3_CHANNEL_MEM_FG_SYNC:
return 1;
case IPUV3_CHANNEL_MEM_DC_SYNC:
return 2;
default:
return -EINVAL;
}
}
static int ipu_prg_get_pre(struct ipu_prg *prg, int prg_chan)
{
int i, ret;
/* channel 0 is special as it is hardwired to one of the PREs */
if (prg_chan == 0) {
ret = ipu_pre_get(prg->pres[0]);
if (ret)
goto fail;
prg->chan[prg_chan].used_pre = 0;
return 0;
}
for (i = 1; i < 3; i++) {
ret = ipu_pre_get(prg->pres[i]);
if (!ret) {
u32 val, mux;
int shift;
prg->chan[prg_chan].used_pre = i;
/* configure the PRE to PRG channel mux */
shift = (i == 1) ? 12 : 14;
mux = (prg->id << 1) | (prg_chan - 1);
regmap_update_bits(prg->iomuxc_gpr, IOMUXC_GPR5,
0x3 << shift, mux << shift);
/* check other mux, must not point to same channel */
shift = (i == 1) ? 14 : 12;
regmap_read(prg->iomuxc_gpr, IOMUXC_GPR5, &val);
if (((val >> shift) & 0x3) == mux) {
regmap_update_bits(prg->iomuxc_gpr, IOMUXC_GPR5,
0x3 << shift,
(mux ^ 0x1) << shift);
}
return 0;
}
}
fail:
dev_err(prg->dev, "could not get PRE for PRG chan %d", prg_chan);
return ret;
}
static void ipu_prg_put_pre(struct ipu_prg *prg, int prg_chan)
{
struct ipu_prg_channel *chan = &prg->chan[prg_chan];
ipu_pre_put(prg->pres[chan->used_pre]);
chan->used_pre = -1;
}
void ipu_prg_channel_disable(struct ipuv3_channel *ipu_chan)
{
int prg_chan = ipu_prg_ipu_to_prg_chan(ipu_chan->num);
struct ipu_prg *prg = ipu_chan->ipu->prg_priv;
struct ipu_prg_channel *chan;
u32 val;
if (prg_chan < 0)
return;
chan = &prg->chan[prg_chan];
if (!chan->enabled)
return;
pm_runtime_get_sync(prg->dev);
val = readl(prg->regs + IPU_PRG_CTL);
val |= IPU_PRG_CTL_BYPASS(prg_chan);
writel(val, prg->regs + IPU_PRG_CTL);
val = IPU_PRG_REG_UPDATE_REG_UPDATE;
writel(val, prg->regs + IPU_PRG_REG_UPDATE);
pm_runtime_put(prg->dev);
ipu_prg_put_pre(prg, prg_chan);
chan->enabled = false;
}
EXPORT_SYMBOL_GPL(ipu_prg_channel_disable);
int ipu_prg_channel_configure(struct ipuv3_channel *ipu_chan,
unsigned int axi_id, unsigned int width,
unsigned int height, unsigned int stride,
u32 format, uint64_t modifier, unsigned long *eba)
{
int prg_chan = ipu_prg_ipu_to_prg_chan(ipu_chan->num);
struct ipu_prg *prg = ipu_chan->ipu->prg_priv;
struct ipu_prg_channel *chan;
u32 val;
int ret;
if (prg_chan < 0)
return prg_chan;
chan = &prg->chan[prg_chan];
if (chan->enabled) {
ipu_pre_update(prg->pres[chan->used_pre], modifier, *eba);
return 0;
}
ret = ipu_prg_get_pre(prg, prg_chan);
if (ret)
return ret;
ipu_pre_configure(prg->pres[chan->used_pre],
width, height, stride, format, modifier, *eba);
pm_runtime_get_sync(prg->dev);
val = (stride - 1) & IPU_PRG_STRIDE_STRIDE_MASK;
writel(val, prg->regs + IPU_PRG_STRIDE(prg_chan));
val = ((height & IPU_PRG_HEIGHT_PRE_HEIGHT_MASK) <<
IPU_PRG_HEIGHT_PRE_HEIGHT_SHIFT) |
((height & IPU_PRG_HEIGHT_IPU_HEIGHT_MASK) <<
IPU_PRG_HEIGHT_IPU_HEIGHT_SHIFT);
writel(val, prg->regs + IPU_PRG_HEIGHT(prg_chan));
val = ipu_pre_get_baddr(prg->pres[chan->used_pre]);
*eba = val;
writel(val, prg->regs + IPU_PRG_BADDR(prg_chan));
val = readl(prg->regs + IPU_PRG_CTL);
/* config AXI ID */
val &= ~(IPU_PRG_CTL_SOFT_ARID_MASK <<
IPU_PRG_CTL_SOFT_ARID_SHIFT(prg_chan));
val |= IPU_PRG_CTL_SOFT_ARID(prg_chan, axi_id);
/* enable channel */
val &= ~IPU_PRG_CTL_BYPASS(prg_chan);
writel(val, prg->regs + IPU_PRG_CTL);
val = IPU_PRG_REG_UPDATE_REG_UPDATE;
writel(val, prg->regs + IPU_PRG_REG_UPDATE);
/* wait for both double buffers to be filled */
readl_poll_timeout(prg->regs + IPU_PRG_STATUS, val,
(val & IPU_PRG_STATUS_BUFFER0_READY(prg_chan)) &&
(val & IPU_PRG_STATUS_BUFFER1_READY(prg_chan)),
5, 1000);
pm_runtime_put(prg->dev);
chan->enabled = true;
return 0;
}
EXPORT_SYMBOL_GPL(ipu_prg_channel_configure);
bool ipu_prg_channel_configure_pending(struct ipuv3_channel *ipu_chan)
{
int prg_chan = ipu_prg_ipu_to_prg_chan(ipu_chan->num);
struct ipu_prg *prg = ipu_chan->ipu->prg_priv;
struct ipu_prg_channel *chan;
if (prg_chan < 0)
return false;
chan = &prg->chan[prg_chan];
WARN_ON(!chan->enabled);
return ipu_pre_update_pending(prg->pres[chan->used_pre]);
}
EXPORT_SYMBOL_GPL(ipu_prg_channel_configure_pending);
static int ipu_prg_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ipu_prg *prg;
u32 val;
int i, ret;
prg = devm_kzalloc(dev, sizeof(*prg), GFP_KERNEL);
if (!prg)
return -ENOMEM;
prg->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(prg->regs))
return PTR_ERR(prg->regs);
prg->clk_ipg = devm_clk_get(dev, "ipg");
if (IS_ERR(prg->clk_ipg))
return PTR_ERR(prg->clk_ipg);
prg->clk_axi = devm_clk_get(dev, "axi");
if (IS_ERR(prg->clk_axi))
return PTR_ERR(prg->clk_axi);
prg->iomuxc_gpr =
syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr");
if (IS_ERR(prg->iomuxc_gpr))
return PTR_ERR(prg->iomuxc_gpr);
for (i = 0; i < 3; i++) {
prg->pres[i] = ipu_pre_lookup_by_phandle(dev, "fsl,pres", i);
if (!prg->pres[i])
return -EPROBE_DEFER;
}
ret = clk_prepare_enable(prg->clk_ipg);
if (ret)
return ret;
ret = clk_prepare_enable(prg->clk_axi);
if (ret) {
clk_disable_unprepare(prg->clk_ipg);
return ret;
}
/* init to free running mode */
val = readl(prg->regs + IPU_PRG_CTL);
val |= IPU_PRG_CTL_SHADOW_EN;
writel(val, prg->regs + IPU_PRG_CTL);
/* disable address threshold */
writel(0xffffffff, prg->regs + IPU_PRG_THD);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
prg->dev = dev;
platform_set_drvdata(pdev, prg);
mutex_lock(&ipu_prg_list_mutex);
list_add(&prg->list, &ipu_prg_list);
mutex_unlock(&ipu_prg_list_mutex);
return 0;
}
static void ipu_prg_remove(struct platform_device *pdev)
{
struct ipu_prg *prg = platform_get_drvdata(pdev);
mutex_lock(&ipu_prg_list_mutex);
list_del(&prg->list);
mutex_unlock(&ipu_prg_list_mutex);
}
#ifdef CONFIG_PM
static int prg_suspend(struct device *dev)
{
struct ipu_prg *prg = dev_get_drvdata(dev);
clk_disable_unprepare(prg->clk_axi);
clk_disable_unprepare(prg->clk_ipg);
return 0;
}
static int prg_resume(struct device *dev)
{
struct ipu_prg *prg = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(prg->clk_ipg);
if (ret)
return ret;
ret = clk_prepare_enable(prg->clk_axi);
if (ret) {
clk_disable_unprepare(prg->clk_ipg);
return ret;
}
return 0;
}
#endif
static const struct dev_pm_ops prg_pm_ops = {
SET_RUNTIME_PM_OPS(prg_suspend, prg_resume, NULL)
};
static const struct of_device_id ipu_prg_dt_ids[] = {
{ .compatible = "fsl,imx6qp-prg", },
{ /* sentinel */ },
};
struct platform_driver ipu_prg_drv = {
.probe = ipu_prg_probe,
.remove = ipu_prg_remove,
.driver = {
.name = "imx-ipu-prg",
.pm = &prg_pm_ops,
.of_match_table = ipu_prg_dt_ids,
},
};
+273
View File
@@ -0,0 +1,273 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*/
#ifndef __IPU_PRV_H__
#define __IPU_PRV_H__
struct ipu_soc;
#include <linux/io.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <video/imx-ipu-v3.h>
#define IPU_MCU_T_DEFAULT 8
#define IPU_CM_IDMAC_REG_OFS 0x00008000
#define IPU_CM_IC_REG_OFS 0x00020000
#define IPU_CM_IRT_REG_OFS 0x00028000
#define IPU_CM_CSI0_REG_OFS 0x00030000
#define IPU_CM_CSI1_REG_OFS 0x00038000
#define IPU_CM_SMFC_REG_OFS 0x00050000
#define IPU_CM_DC_REG_OFS 0x00058000
#define IPU_CM_DMFC_REG_OFS 0x00060000
/* Register addresses */
/* IPU Common registers */
#define IPU_CM_REG(offset) (offset)
#define IPU_CONF IPU_CM_REG(0)
#define IPU_SRM_PRI1 IPU_CM_REG(0x00a0)
#define IPU_SRM_PRI2 IPU_CM_REG(0x00a4)
#define IPU_FS_PROC_FLOW1 IPU_CM_REG(0x00a8)
#define IPU_FS_PROC_FLOW2 IPU_CM_REG(0x00ac)
#define IPU_FS_PROC_FLOW3 IPU_CM_REG(0x00b0)
#define IPU_FS_DISP_FLOW1 IPU_CM_REG(0x00b4)
#define IPU_FS_DISP_FLOW2 IPU_CM_REG(0x00b8)
#define IPU_SKIP IPU_CM_REG(0x00bc)
#define IPU_DISP_ALT_CONF IPU_CM_REG(0x00c0)
#define IPU_DISP_GEN IPU_CM_REG(0x00c4)
#define IPU_DISP_ALT1 IPU_CM_REG(0x00c8)
#define IPU_DISP_ALT2 IPU_CM_REG(0x00cc)
#define IPU_DISP_ALT3 IPU_CM_REG(0x00d0)
#define IPU_DISP_ALT4 IPU_CM_REG(0x00d4)
#define IPU_SNOOP IPU_CM_REG(0x00d8)
#define IPU_MEM_RST IPU_CM_REG(0x00dc)
#define IPU_PM IPU_CM_REG(0x00e0)
#define IPU_GPR IPU_CM_REG(0x00e4)
#define IPU_CHA_DB_MODE_SEL(ch) IPU_CM_REG(0x0150 + 4 * ((ch) / 32))
#define IPU_ALT_CHA_DB_MODE_SEL(ch) IPU_CM_REG(0x0168 + 4 * ((ch) / 32))
#define IPU_CHA_CUR_BUF(ch) IPU_CM_REG(0x023C + 4 * ((ch) / 32))
#define IPU_ALT_CUR_BUF0 IPU_CM_REG(0x0244)
#define IPU_ALT_CUR_BUF1 IPU_CM_REG(0x0248)
#define IPU_SRM_STAT IPU_CM_REG(0x024C)
#define IPU_PROC_TASK_STAT IPU_CM_REG(0x0250)
#define IPU_DISP_TASK_STAT IPU_CM_REG(0x0254)
#define IPU_CHA_BUF0_RDY(ch) IPU_CM_REG(0x0268 + 4 * ((ch) / 32))
#define IPU_CHA_BUF1_RDY(ch) IPU_CM_REG(0x0270 + 4 * ((ch) / 32))
#define IPU_CHA_BUF2_RDY(ch) IPU_CM_REG(0x0288 + 4 * ((ch) / 32))
#define IPU_ALT_CHA_BUF0_RDY(ch) IPU_CM_REG(0x0278 + 4 * ((ch) / 32))
#define IPU_ALT_CHA_BUF1_RDY(ch) IPU_CM_REG(0x0280 + 4 * ((ch) / 32))
#define IPU_INT_CTRL(n) IPU_CM_REG(0x003C + 4 * (n))
#define IPU_INT_STAT(n) IPU_CM_REG(0x0200 + 4 * (n))
/* SRM_PRI2 */
#define DP_S_SRM_MODE_MASK (0x3 << 3)
#define DP_S_SRM_MODE_NOW (0x3 << 3)
#define DP_S_SRM_MODE_NEXT_FRAME (0x1 << 3)
/* FS_PROC_FLOW1 */
#define FS_PRPENC_ROT_SRC_SEL_MASK (0xf << 0)
#define FS_PRPENC_ROT_SRC_SEL_ENC (0x7 << 0)
#define FS_PRPVF_ROT_SRC_SEL_MASK (0xf << 8)
#define FS_PRPVF_ROT_SRC_SEL_VF (0x8 << 8)
#define FS_PP_SRC_SEL_MASK (0xf << 12)
#define FS_PP_ROT_SRC_SEL_MASK (0xf << 16)
#define FS_PP_ROT_SRC_SEL_PP (0x5 << 16)
#define FS_VDI1_SRC_SEL_MASK (0x3 << 20)
#define FS_VDI3_SRC_SEL_MASK (0x3 << 20)
#define FS_PRP_SRC_SEL_MASK (0xf << 24)
#define FS_VDI_SRC_SEL_MASK (0x3 << 28)
#define FS_VDI_SRC_SEL_CSI_DIRECT (0x1 << 28)
#define FS_VDI_SRC_SEL_VDOA (0x2 << 28)
/* FS_PROC_FLOW2 */
#define FS_PRP_ENC_DEST_SEL_MASK (0xf << 0)
#define FS_PRP_ENC_DEST_SEL_IRT_ENC (0x1 << 0)
#define FS_PRPVF_DEST_SEL_MASK (0xf << 4)
#define FS_PRPVF_DEST_SEL_IRT_VF (0x1 << 4)
#define FS_PRPVF_ROT_DEST_SEL_MASK (0xf << 8)
#define FS_PP_DEST_SEL_MASK (0xf << 12)
#define FS_PP_DEST_SEL_IRT_PP (0x3 << 12)
#define FS_PP_ROT_DEST_SEL_MASK (0xf << 16)
#define FS_PRPENC_ROT_DEST_SEL_MASK (0xf << 20)
#define FS_PRP_DEST_SEL_MASK (0xf << 24)
#define IPU_DI0_COUNTER_RELEASE (1 << 24)
#define IPU_DI1_COUNTER_RELEASE (1 << 25)
#define IPU_IDMAC_REG(offset) (offset)
#define IDMAC_CONF IPU_IDMAC_REG(0x0000)
#define IDMAC_CHA_EN(ch) IPU_IDMAC_REG(0x0004 + 4 * ((ch) / 32))
#define IDMAC_SEP_ALPHA IPU_IDMAC_REG(0x000c)
#define IDMAC_ALT_SEP_ALPHA IPU_IDMAC_REG(0x0010)
#define IDMAC_CHA_PRI(ch) IPU_IDMAC_REG(0x0014 + 4 * ((ch) / 32))
#define IDMAC_WM_EN(ch) IPU_IDMAC_REG(0x001c + 4 * ((ch) / 32))
#define IDMAC_CH_LOCK_EN_1 IPU_IDMAC_REG(0x0024)
#define IDMAC_CH_LOCK_EN_2 IPU_IDMAC_REG(0x0028)
#define IDMAC_SUB_ADDR_0 IPU_IDMAC_REG(0x002c)
#define IDMAC_SUB_ADDR_1 IPU_IDMAC_REG(0x0030)
#define IDMAC_SUB_ADDR_2 IPU_IDMAC_REG(0x0034)
#define IDMAC_BAND_EN(ch) IPU_IDMAC_REG(0x0040 + 4 * ((ch) / 32))
#define IDMAC_CHA_BUSY(ch) IPU_IDMAC_REG(0x0100 + 4 * ((ch) / 32))
#define IPU_NUM_IRQS (32 * 15)
enum ipu_modules {
IPU_CONF_CSI0_EN = (1 << 0),
IPU_CONF_CSI1_EN = (1 << 1),
IPU_CONF_IC_EN = (1 << 2),
IPU_CONF_ROT_EN = (1 << 3),
IPU_CONF_ISP_EN = (1 << 4),
IPU_CONF_DP_EN = (1 << 5),
IPU_CONF_DI0_EN = (1 << 6),
IPU_CONF_DI1_EN = (1 << 7),
IPU_CONF_SMFC_EN = (1 << 8),
IPU_CONF_DC_EN = (1 << 9),
IPU_CONF_DMFC_EN = (1 << 10),
IPU_CONF_VDI_EN = (1 << 12),
IPU_CONF_IDMAC_DIS = (1 << 22),
IPU_CONF_IC_DMFC_SEL = (1 << 25),
IPU_CONF_IC_DMFC_SYNC = (1 << 26),
IPU_CONF_VDI_DMFC_SYNC = (1 << 27),
IPU_CONF_CSI0_DATA_SOURCE = (1 << 28),
IPU_CONF_CSI1_DATA_SOURCE = (1 << 29),
IPU_CONF_IC_INPUT = (1 << 30),
IPU_CONF_CSI_SEL = (1 << 31),
};
struct ipuv3_channel {
unsigned int num;
struct ipu_soc *ipu;
struct list_head list;
};
struct ipu_cpmem;
struct ipu_csi;
struct ipu_dc_priv;
struct ipu_dmfc_priv;
struct ipu_di;
struct ipu_ic_priv;
struct ipu_vdi;
struct ipu_image_convert_priv;
struct ipu_smfc_priv;
struct ipu_pre;
struct ipu_prg;
struct ipu_devtype;
struct ipu_soc {
struct device *dev;
const struct ipu_devtype *devtype;
enum ipuv3_type ipu_type;
spinlock_t lock;
struct mutex channel_lock;
struct list_head channels;
void __iomem *cm_reg;
void __iomem *idmac_reg;
int id;
int usecount;
struct clk *clk;
int irq_sync;
int irq_err;
struct irq_domain *domain;
struct ipu_cpmem *cpmem_priv;
struct ipu_dc_priv *dc_priv;
struct ipu_dp_priv *dp_priv;
struct ipu_dmfc_priv *dmfc_priv;
struct ipu_di *di_priv[2];
struct ipu_csi *csi_priv[2];
struct ipu_ic_priv *ic_priv;
struct ipu_vdi *vdi_priv;
struct ipu_image_convert_priv *image_convert_priv;
struct ipu_smfc_priv *smfc_priv;
struct ipu_prg *prg_priv;
};
static inline u32 ipu_idmac_read(struct ipu_soc *ipu, unsigned offset)
{
return readl(ipu->idmac_reg + offset);
}
static inline void ipu_idmac_write(struct ipu_soc *ipu, u32 value,
unsigned offset)
{
writel(value, ipu->idmac_reg + offset);
}
void ipu_srm_dp_update(struct ipu_soc *ipu, bool sync);
int ipu_module_enable(struct ipu_soc *ipu, u32 mask);
int ipu_module_disable(struct ipu_soc *ipu, u32 mask);
int ipu_csi_init(struct ipu_soc *ipu, struct device *dev, int id,
unsigned long base, u32 module, struct clk *clk_ipu);
void ipu_csi_exit(struct ipu_soc *ipu, int id);
int ipu_ic_init(struct ipu_soc *ipu, struct device *dev,
unsigned long base, unsigned long tpmem_base);
void ipu_ic_exit(struct ipu_soc *ipu);
int ipu_vdi_init(struct ipu_soc *ipu, struct device *dev,
unsigned long base, u32 module);
void ipu_vdi_exit(struct ipu_soc *ipu);
int ipu_image_convert_init(struct ipu_soc *ipu, struct device *dev);
void ipu_image_convert_exit(struct ipu_soc *ipu);
int ipu_di_init(struct ipu_soc *ipu, struct device *dev, int id,
unsigned long base, u32 module, struct clk *ipu_clk);
void ipu_di_exit(struct ipu_soc *ipu, int id);
int ipu_dmfc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base,
struct clk *ipu_clk);
void ipu_dmfc_exit(struct ipu_soc *ipu);
int ipu_dp_init(struct ipu_soc *ipu, struct device *dev, unsigned long base);
void ipu_dp_exit(struct ipu_soc *ipu);
int ipu_dc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base,
unsigned long template_base);
void ipu_dc_exit(struct ipu_soc *ipu);
int ipu_cpmem_init(struct ipu_soc *ipu, struct device *dev, unsigned long base);
void ipu_cpmem_exit(struct ipu_soc *ipu);
int ipu_smfc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base);
void ipu_smfc_exit(struct ipu_soc *ipu);
struct ipu_pre *ipu_pre_lookup_by_phandle(struct device *dev, const char *name,
int index);
int ipu_pre_get_available_count(void);
int ipu_pre_get(struct ipu_pre *pre);
void ipu_pre_put(struct ipu_pre *pre);
u32 ipu_pre_get_baddr(struct ipu_pre *pre);
void ipu_pre_configure(struct ipu_pre *pre, unsigned int width,
unsigned int height, unsigned int stride, u32 format,
uint64_t modifier, unsigned int bufaddr);
void ipu_pre_update(struct ipu_pre *pre, uint64_t modifier, unsigned int bufaddr);
bool ipu_pre_update_pending(struct ipu_pre *pre);
struct ipu_prg *ipu_prg_lookup_by_phandle(struct device *dev, const char *name,
int ipu_id);
extern struct platform_driver ipu_pre_drv;
extern struct platform_driver ipu_prg_drv;
#endif /* __IPU_PRV_H__ */
+202
View File
@@ -0,0 +1,202 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved.
*/
#include <linux/export.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/errno.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <video/imx-ipu-v3.h>
#include "ipu-prv.h"
struct ipu_smfc {
struct ipu_smfc_priv *priv;
int chno;
bool inuse;
};
struct ipu_smfc_priv {
void __iomem *base;
spinlock_t lock;
struct ipu_soc *ipu;
struct ipu_smfc channel[4];
int use_count;
};
/*SMFC Registers */
#define SMFC_MAP 0x0000
#define SMFC_WMC 0x0004
#define SMFC_BS 0x0008
int ipu_smfc_set_burstsize(struct ipu_smfc *smfc, int burstsize)
{
struct ipu_smfc_priv *priv = smfc->priv;
unsigned long flags;
u32 val, shift;
spin_lock_irqsave(&priv->lock, flags);
shift = smfc->chno * 4;
val = readl(priv->base + SMFC_BS);
val &= ~(0xf << shift);
val |= burstsize << shift;
writel(val, priv->base + SMFC_BS);
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_smfc_set_burstsize);
int ipu_smfc_map_channel(struct ipu_smfc *smfc, int csi_id, int mipi_id)
{
struct ipu_smfc_priv *priv = smfc->priv;
unsigned long flags;
u32 val, shift;
spin_lock_irqsave(&priv->lock, flags);
shift = smfc->chno * 3;
val = readl(priv->base + SMFC_MAP);
val &= ~(0x7 << shift);
val |= ((csi_id << 2) | mipi_id) << shift;
writel(val, priv->base + SMFC_MAP);
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_smfc_map_channel);
int ipu_smfc_set_watermark(struct ipu_smfc *smfc, u32 set_level, u32 clr_level)
{
struct ipu_smfc_priv *priv = smfc->priv;
unsigned long flags;
u32 val, shift;
spin_lock_irqsave(&priv->lock, flags);
shift = smfc->chno * 6 + (smfc->chno > 1 ? 4 : 0);
val = readl(priv->base + SMFC_WMC);
val &= ~(0x3f << shift);
val |= ((clr_level << 3) | set_level) << shift;
writel(val, priv->base + SMFC_WMC);
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_smfc_set_watermark);
int ipu_smfc_enable(struct ipu_smfc *smfc)
{
struct ipu_smfc_priv *priv = smfc->priv;
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
if (!priv->use_count)
ipu_module_enable(priv->ipu, IPU_CONF_SMFC_EN);
priv->use_count++;
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_smfc_enable);
int ipu_smfc_disable(struct ipu_smfc *smfc)
{
struct ipu_smfc_priv *priv = smfc->priv;
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
priv->use_count--;
if (!priv->use_count)
ipu_module_disable(priv->ipu, IPU_CONF_SMFC_EN);
if (priv->use_count < 0)
priv->use_count = 0;
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_smfc_disable);
struct ipu_smfc *ipu_smfc_get(struct ipu_soc *ipu, unsigned int chno)
{
struct ipu_smfc_priv *priv = ipu->smfc_priv;
struct ipu_smfc *smfc, *ret;
unsigned long flags;
if (chno >= 4)
return ERR_PTR(-EINVAL);
smfc = &priv->channel[chno];
ret = smfc;
spin_lock_irqsave(&priv->lock, flags);
if (smfc->inuse) {
ret = ERR_PTR(-EBUSY);
goto unlock;
}
smfc->inuse = true;
unlock:
spin_unlock_irqrestore(&priv->lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(ipu_smfc_get);
void ipu_smfc_put(struct ipu_smfc *smfc)
{
struct ipu_smfc_priv *priv = smfc->priv;
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
smfc->inuse = false;
spin_unlock_irqrestore(&priv->lock, flags);
}
EXPORT_SYMBOL_GPL(ipu_smfc_put);
int ipu_smfc_init(struct ipu_soc *ipu, struct device *dev,
unsigned long base)
{
struct ipu_smfc_priv *priv;
int i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
ipu->smfc_priv = priv;
spin_lock_init(&priv->lock);
priv->ipu = ipu;
priv->base = devm_ioremap(dev, base, PAGE_SIZE);
if (!priv->base)
return -ENOMEM;
for (i = 0; i < 4; i++) {
priv->channel[i].priv = priv;
priv->channel[i].chno = i;
}
pr_debug("%s: ioremap 0x%08lx -> %p\n", __func__, base, priv->base);
return 0;
}
void ipu_smfc_exit(struct ipu_soc *ipu)
{
}
+223
View File
@@ -0,0 +1,223 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2012-2016 Mentor Graphics Inc.
* Copyright (C) 2005-2009 Freescale Semiconductor, Inc.
*/
#include <linux/io.h>
#include "ipu-prv.h"
struct ipu_vdi {
void __iomem *base;
u32 module;
spinlock_t lock;
int use_count;
struct ipu_soc *ipu;
};
/* VDI Register Offsets */
#define VDI_FSIZE 0x0000
#define VDI_C 0x0004
/* VDI Register Fields */
#define VDI_C_CH_420 (0 << 1)
#define VDI_C_CH_422 (1 << 1)
#define VDI_C_MOT_SEL_MASK (0x3 << 2)
#define VDI_C_MOT_SEL_FULL (2 << 2)
#define VDI_C_MOT_SEL_LOW (1 << 2)
#define VDI_C_MOT_SEL_MED (0 << 2)
#define VDI_C_BURST_SIZE1_4 (3 << 4)
#define VDI_C_BURST_SIZE2_4 (3 << 8)
#define VDI_C_BURST_SIZE3_4 (3 << 12)
#define VDI_C_BURST_SIZE_MASK 0xF
#define VDI_C_BURST_SIZE1_OFFSET 4
#define VDI_C_BURST_SIZE2_OFFSET 8
#define VDI_C_BURST_SIZE3_OFFSET 12
#define VDI_C_VWM1_SET_1 (0 << 16)
#define VDI_C_VWM1_SET_2 (1 << 16)
#define VDI_C_VWM1_CLR_2 (1 << 19)
#define VDI_C_VWM3_SET_1 (0 << 22)
#define VDI_C_VWM3_SET_2 (1 << 22)
#define VDI_C_VWM3_CLR_2 (1 << 25)
#define VDI_C_TOP_FIELD_MAN_1 (1 << 30)
#define VDI_C_TOP_FIELD_AUTO_1 (1 << 31)
static inline u32 ipu_vdi_read(struct ipu_vdi *vdi, unsigned int offset)
{
return readl(vdi->base + offset);
}
static inline void ipu_vdi_write(struct ipu_vdi *vdi, u32 value,
unsigned int offset)
{
writel(value, vdi->base + offset);
}
void ipu_vdi_set_field_order(struct ipu_vdi *vdi, v4l2_std_id std, u32 field)
{
bool top_field_0 = false;
unsigned long flags;
u32 reg;
switch (field) {
case V4L2_FIELD_INTERLACED_TB:
case V4L2_FIELD_SEQ_TB:
case V4L2_FIELD_TOP:
top_field_0 = true;
break;
case V4L2_FIELD_INTERLACED_BT:
case V4L2_FIELD_SEQ_BT:
case V4L2_FIELD_BOTTOM:
top_field_0 = false;
break;
default:
top_field_0 = (std & V4L2_STD_525_60) ? true : false;
break;
}
spin_lock_irqsave(&vdi->lock, flags);
reg = ipu_vdi_read(vdi, VDI_C);
if (top_field_0)
reg &= ~(VDI_C_TOP_FIELD_MAN_1 | VDI_C_TOP_FIELD_AUTO_1);
else
reg |= VDI_C_TOP_FIELD_MAN_1 | VDI_C_TOP_FIELD_AUTO_1;
ipu_vdi_write(vdi, reg, VDI_C);
spin_unlock_irqrestore(&vdi->lock, flags);
}
EXPORT_SYMBOL_GPL(ipu_vdi_set_field_order);
void ipu_vdi_set_motion(struct ipu_vdi *vdi, enum ipu_motion_sel motion_sel)
{
unsigned long flags;
u32 reg;
spin_lock_irqsave(&vdi->lock, flags);
reg = ipu_vdi_read(vdi, VDI_C);
reg &= ~VDI_C_MOT_SEL_MASK;
switch (motion_sel) {
case MED_MOTION:
reg |= VDI_C_MOT_SEL_MED;
break;
case HIGH_MOTION:
reg |= VDI_C_MOT_SEL_FULL;
break;
default:
reg |= VDI_C_MOT_SEL_LOW;
break;
}
ipu_vdi_write(vdi, reg, VDI_C);
spin_unlock_irqrestore(&vdi->lock, flags);
}
EXPORT_SYMBOL_GPL(ipu_vdi_set_motion);
void ipu_vdi_setup(struct ipu_vdi *vdi, u32 code, int xres, int yres)
{
unsigned long flags;
u32 pixel_fmt, reg;
spin_lock_irqsave(&vdi->lock, flags);
reg = ((yres - 1) << 16) | (xres - 1);
ipu_vdi_write(vdi, reg, VDI_FSIZE);
/*
* Full motion, only vertical filter is used.
* Burst size is 4 accesses
*/
if (code == MEDIA_BUS_FMT_UYVY8_2X8 ||
code == MEDIA_BUS_FMT_UYVY8_1X16 ||
code == MEDIA_BUS_FMT_YUYV8_2X8 ||
code == MEDIA_BUS_FMT_YUYV8_1X16)
pixel_fmt = VDI_C_CH_422;
else
pixel_fmt = VDI_C_CH_420;
reg = ipu_vdi_read(vdi, VDI_C);
reg |= pixel_fmt;
reg |= VDI_C_BURST_SIZE2_4;
reg |= VDI_C_BURST_SIZE1_4 | VDI_C_VWM1_CLR_2;
reg |= VDI_C_BURST_SIZE3_4 | VDI_C_VWM3_CLR_2;
ipu_vdi_write(vdi, reg, VDI_C);
spin_unlock_irqrestore(&vdi->lock, flags);
}
EXPORT_SYMBOL_GPL(ipu_vdi_setup);
int ipu_vdi_enable(struct ipu_vdi *vdi)
{
unsigned long flags;
spin_lock_irqsave(&vdi->lock, flags);
if (!vdi->use_count)
ipu_module_enable(vdi->ipu, vdi->module);
vdi->use_count++;
spin_unlock_irqrestore(&vdi->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_vdi_enable);
int ipu_vdi_disable(struct ipu_vdi *vdi)
{
unsigned long flags;
spin_lock_irqsave(&vdi->lock, flags);
if (vdi->use_count) {
if (!--vdi->use_count)
ipu_module_disable(vdi->ipu, vdi->module);
}
spin_unlock_irqrestore(&vdi->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(ipu_vdi_disable);
struct ipu_vdi *ipu_vdi_get(struct ipu_soc *ipu)
{
return ipu->vdi_priv;
}
EXPORT_SYMBOL_GPL(ipu_vdi_get);
void ipu_vdi_put(struct ipu_vdi *vdi)
{
}
EXPORT_SYMBOL_GPL(ipu_vdi_put);
int ipu_vdi_init(struct ipu_soc *ipu, struct device *dev,
unsigned long base, u32 module)
{
struct ipu_vdi *vdi;
vdi = devm_kzalloc(dev, sizeof(*vdi), GFP_KERNEL);
if (!vdi)
return -ENOMEM;
ipu->vdi_priv = vdi;
spin_lock_init(&vdi->lock);
vdi->module = module;
vdi->base = devm_ioremap(dev, base, PAGE_SIZE);
if (!vdi->base)
return -ENOMEM;
dev_dbg(dev, "VDI base: 0x%08lx remapped to %p\n", base, vdi->base);
vdi->ipu = ipu;
return 0;
}
void ipu_vdi_exit(struct ipu_soc *ipu)
{
}