blob: 42dfc7acac55dfdc37791c8a1a53488308fe7c88 [file] [log] [blame]
/*
* Copyright (C) 2016 Freescale Semiconductor, Inc.
* Copyright 2017-2019 NXP
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <linux/io.h>
#include <linux/media-bus-format.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <video/dpu.h>
#include <video/imx8-pc.h>
#include "dpu-prv.h"
#define SSQCNTS 0
#define SSQCYCLE 0x8
#define SWRESET 0xC
#define TCON_CTRL 0x10
#define BYPASS BIT(3)
#define RSDSINVCTRL 0x14
#define MAPBIT3_0 0x18
#define MAPBIT7_4 0x1C
#define MAPBIT11_8 0x20
#define MAPBIT15_12 0x24
#define MAPBIT19_16 0x28
#define MAPBIT23_20 0x2C
#define MAPBIT27_24 0x30
#define MAPBIT31_28 0x34
#define MAPBIT34_32 0x38
#define MAPBIT3_0_DUAL 0x3C
#define MAPBIT7_4_DUAL 0x40
#define MAPBIT11_8_DUAL 0x44
#define MAPBIT15_12_DUAL 0x48
#define MAPBIT19_16_DUAL 0x4C
#define MAPBIT23_20_DUAL 0x50
#define MAPBIT27_24_DUAL 0x54
#define MAPBIT31_28_DUAL 0x58
#define MAPBIT34_32_DUAL 0x5C
#define SPGPOSON(n) (0x60 + (n) * 16)
#define X(n) (((n) & 0x7FFF) << 16)
#define Y(n) ((n) & 0x7FFF)
#define SPGMASKON(n) (0x64 + (n) * 16)
#define SPGPOSOFF(n) (0x68 + (n) * 16)
#define SPGMASKOFF(n) (0x6C + (n) * 16)
#define SMXSIGS(n) (0x120 + (n) * 8)
#define SMXFCTTABLE(n) (0x124 + (n) * 8)
#define RESET_OVER_UNFERFLOW 0x180
#define DUAL_DEBUG 0x184
struct dpu_tcon {
void __iomem *base;
struct mutex mutex;
int id;
bool inuse;
struct dpu_soc *dpu;
struct pc *pc;
};
static inline u32 dpu_tcon_read(struct dpu_tcon *tcon, unsigned int offset)
{
return readl(tcon->base + offset);
}
static inline void dpu_tcon_write(struct dpu_tcon *tcon, u32 value,
unsigned int offset)
{
writel(value, tcon->base + offset);
}
int tcon_set_fmt(struct dpu_tcon *tcon, u32 bus_format)
{
mutex_lock(&tcon->mutex);
switch (bus_format) {
case MEDIA_BUS_FMT_RGB888_1X24:
dpu_tcon_write(tcon, 0x19181716, MAPBIT3_0);
dpu_tcon_write(tcon, 0x1d1c1b1a, MAPBIT7_4);
dpu_tcon_write(tcon, 0x0f0e0d0c, MAPBIT11_8);
dpu_tcon_write(tcon, 0x13121110, MAPBIT15_12);
dpu_tcon_write(tcon, 0x05040302, MAPBIT19_16);
dpu_tcon_write(tcon, 0x09080706, MAPBIT23_20);
break;
case MEDIA_BUS_FMT_RGB101010_1X30:
case MEDIA_BUS_FMT_RGB888_1X30_PADLO:
case MEDIA_BUS_FMT_RGB666_1X30_PADLO:
dpu_tcon_write(tcon, 0x17161514, MAPBIT3_0);
dpu_tcon_write(tcon, 0x1b1a1918, MAPBIT7_4);
dpu_tcon_write(tcon, 0x0b0a1d1c, MAPBIT11_8);
dpu_tcon_write(tcon, 0x0f0e0d0c, MAPBIT15_12);
dpu_tcon_write(tcon, 0x13121110, MAPBIT19_16);
dpu_tcon_write(tcon, 0x03020100, MAPBIT23_20);
dpu_tcon_write(tcon, 0x07060504, MAPBIT27_24);
dpu_tcon_write(tcon, 0x00000908, MAPBIT31_28);
break;
default:
mutex_unlock(&tcon->mutex);
return -EINVAL;
}
mutex_unlock(&tcon->mutex);
return 0;
}
EXPORT_SYMBOL_GPL(tcon_set_fmt);
/* This function is used to workaround TKT320590 which is related to DPR/PRG. */
void tcon_set_operation_mode(struct dpu_tcon *tcon)
{
u32 val;
mutex_lock(&tcon->mutex);
val = dpu_tcon_read(tcon, TCON_CTRL);
val &= ~BYPASS;
dpu_tcon_write(tcon, val, TCON_CTRL);
mutex_unlock(&tcon->mutex);
}
EXPORT_SYMBOL_GPL(tcon_set_operation_mode);
void tcon_cfg_videomode(struct dpu_tcon *tcon,
struct drm_display_mode *m, bool side_by_side)
{
struct dpu_soc *dpu = tcon->dpu;
const struct dpu_devtype *devtype = dpu->devtype;
u32 val;
int hdisplay, hsync_start, hsync_end;
int vdisplay, vsync_start, vsync_end;
int y;
hdisplay = m->hdisplay;
vdisplay = m->vdisplay;
hsync_start = m->hsync_start;
vsync_start = m->vsync_start;
hsync_end = m->hsync_end;
vsync_end = m->vsync_end;
if (side_by_side) {
hdisplay /= 2;
hsync_start /= 2;
hsync_end /= 2;
}
mutex_lock(&tcon->mutex);
/*
* TKT320590:
* Turn TCON into operation mode later after the first dumb frame is
* generated by DPU. This makes DPR/PRG be able to evade the frame.
*/
val = dpu_tcon_read(tcon, TCON_CTRL);
val |= BYPASS;
dpu_tcon_write(tcon, val, TCON_CTRL);
/* dsp_control[0]: hsync */
dpu_tcon_write(tcon, X(hsync_start), SPGPOSON(0));
dpu_tcon_write(tcon, 0xffff, SPGMASKON(0));
dpu_tcon_write(tcon, X(hsync_end), SPGPOSOFF(0));
dpu_tcon_write(tcon, 0xffff, SPGMASKOFF(0));
dpu_tcon_write(tcon, 0x2, SMXSIGS(0));
dpu_tcon_write(tcon, 0x1, SMXFCTTABLE(0));
/* dsp_control[1]: vsync */
dpu_tcon_write(tcon, X(hsync_start) | Y(vsync_start - 1), SPGPOSON(1));
dpu_tcon_write(tcon, 0x0, SPGMASKON(1));
dpu_tcon_write(tcon, X(hsync_start) | Y(vsync_end - 1), SPGPOSOFF(1));
dpu_tcon_write(tcon, 0x0, SPGMASKOFF(1));
dpu_tcon_write(tcon, 0x3, SMXSIGS(1));
dpu_tcon_write(tcon, 0x1, SMXFCTTABLE(1));
/* dsp_control[2]: data enable */
/* horizontal */
dpu_tcon_write(tcon, 0x0, SPGPOSON(2));
dpu_tcon_write(tcon, 0xffff, SPGMASKON(2));
dpu_tcon_write(tcon, X(hdisplay), SPGPOSOFF(2));
dpu_tcon_write(tcon, 0xffff, SPGMASKOFF(2));
/* vertical */
dpu_tcon_write(tcon, 0x0, SPGPOSON(3));
dpu_tcon_write(tcon, 0x7fff0000, SPGMASKON(3));
dpu_tcon_write(tcon, Y(vdisplay), SPGPOSOFF(3));
dpu_tcon_write(tcon, 0x7fff0000, SPGMASKOFF(3));
dpu_tcon_write(tcon, 0x2c, SMXSIGS(2));
dpu_tcon_write(tcon, 0x8, SMXFCTTABLE(2));
/* dsp_control[3]: kachuck */
y = vdisplay + 1;
/*
* If sync mode fixup is present, the kachuck signal from slave tcon
* should be one line later than the one from master tcon.
*/
if (side_by_side && tcon_is_slave(tcon) && devtype->has_syncmode_fixup)
y++;
dpu_tcon_write(tcon, X(0x0) | Y(y), SPGPOSON(4));
dpu_tcon_write(tcon, 0x0, SPGMASKON(4));
dpu_tcon_write(tcon, X(0x20) | Y(y), SPGPOSOFF(4));
dpu_tcon_write(tcon, 0x0, SPGMASKOFF(4));
dpu_tcon_write(tcon, 0x6, SMXSIGS(3));
dpu_tcon_write(tcon, 0x2, SMXFCTTABLE(3));
mutex_unlock(&tcon->mutex);
}
EXPORT_SYMBOL_GPL(tcon_cfg_videomode);
bool tcon_is_master(struct dpu_tcon *tcon)
{
const struct dpu_devtype *devtype = tcon->dpu->devtype;
return tcon->id == devtype->master_stream_id;
}
EXPORT_SYMBOL_GPL(tcon_is_master);
bool tcon_is_slave(struct dpu_tcon *tcon)
{
return !tcon_is_master(tcon);
}
EXPORT_SYMBOL_GPL(tcon_is_slave);
void tcon_configure_pc(struct dpu_tcon *tcon, unsigned int di,
unsigned int frame_width, u32 mode, u32 format)
{
if (WARN_ON(!tcon || !tcon->pc))
return;
pc_configure(tcon->pc, di, frame_width, mode, format);
}
EXPORT_SYMBOL_GPL(tcon_configure_pc);
void tcon_enable_pc(struct dpu_tcon *tcon)
{
if (WARN_ON(!tcon || !tcon->pc))
return;
pc_enable(tcon->pc);
}
EXPORT_SYMBOL_GPL(tcon_enable_pc);
void tcon_disable_pc(struct dpu_tcon *tcon)
{
if (WARN_ON(!tcon || !tcon->pc))
return;
pc_disable(tcon->pc);
}
EXPORT_SYMBOL_GPL(tcon_disable_pc);
struct dpu_tcon *dpu_tcon_get(struct dpu_soc *dpu, int id)
{
struct dpu_tcon *tcon;
int i;
for (i = 0; i < ARRAY_SIZE(tcon_ids); i++)
if (tcon_ids[i] == id)
break;
if (i == ARRAY_SIZE(tcon_ids))
return ERR_PTR(-EINVAL);
tcon = dpu->tcon_priv[i];
mutex_lock(&tcon->mutex);
if (tcon->inuse) {
mutex_unlock(&tcon->mutex);
return ERR_PTR(-EBUSY);
}
tcon->inuse = true;
mutex_unlock(&tcon->mutex);
return tcon;
}
EXPORT_SYMBOL_GPL(dpu_tcon_get);
void dpu_tcon_put(struct dpu_tcon *tcon)
{
mutex_lock(&tcon->mutex);
tcon->inuse = false;
mutex_unlock(&tcon->mutex);
}
EXPORT_SYMBOL_GPL(dpu_tcon_put);
struct dpu_tcon *dpu_aux_tcon_peek(struct dpu_tcon *tcon)
{
return tcon->dpu->tcon_priv[tcon->id ^ 1];
}
EXPORT_SYMBOL_GPL(dpu_aux_tcon_peek);
void _dpu_tcon_init(struct dpu_soc *dpu, unsigned int id)
{
}
int dpu_tcon_init(struct dpu_soc *dpu, unsigned int id,
unsigned long unused, unsigned long base)
{
struct dpu_tcon *tcon;
tcon = devm_kzalloc(dpu->dev, sizeof(*tcon), GFP_KERNEL);
if (!tcon)
return -ENOMEM;
dpu->tcon_priv[id] = tcon;
tcon->base = devm_ioremap(dpu->dev, base, SZ_512);
if (!tcon->base)
return -ENOMEM;
tcon->dpu = dpu;
tcon->id = id;
mutex_init(&tcon->mutex);
return 0;
}
void tcon_get_pc(struct dpu_tcon *tcon, void *data)
{
if (WARN_ON(!tcon))
return;
tcon->pc = data;
}