1
0
Fork 0
alistair23-linux/drivers/gpu/imx/dpu/dpu-framegen.c

598 lines
14 KiB
C

/*
* Copyright (C) 2016 Freescale Semiconductor, Inc.
* Copyright 2017-2020 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/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <drm/drm_mode.h>
#include <video/dpu.h>
#include "dpu-prv.h"
#define FGSTCTRL 0x8
#define FGSYNCMODE_MASK 0x6
#define HTCFG1 0xC
#define HTOTAL(n) ((((n) - 1) & 0x3FFF) << 16)
#define HACT(n) ((n) & 0x3FFF)
#define HTCFG2 0x10
#define HSEN BIT(31)
#define HSBP(n) ((((n) - 1) & 0x3FFF) << 16)
#define HSYNC(n) (((n) - 1) & 0x3FFF)
#define VTCFG1 0x14
#define VTOTAL(n) ((((n) - 1) & 0x3FFF) << 16)
#define VACT(n) ((n) & 0x3FFF)
#define VTCFG2 0x18
#define VSEN BIT(31)
#define VSBP(n) ((((n) - 1) & 0x3FFF) << 16)
#define VSYNC(n) (((n) - 1) & 0x3FFF)
#define INTCONFIG(n) (0x1C + 4 * (n))
#define EN BIT(31)
#define ROW(n) (((n) & 0x3FFF) << 16)
#define COL(n) ((n) & 0x3FFF)
#define PKICKCONFIG 0x2C
#define SKICKCONFIG 0x30
#define SECSTATCONFIG 0x34
#define FGSRCR1 0x38
#define FGSRCR2 0x3C
#define FGSRCR3 0x40
#define FGSRCR4 0x44
#define FGSRCR5 0x48
#define FGSRCR6 0x4C
#define FGKSDR 0x50
#define PACFG 0x54
#define STARTX(n) (((n) + 1) & 0x3FFF)
#define STARTY(n) (((((n) + 1) & 0x3FFF)) << 16)
#define SACFG 0x58
#define FGINCTRL 0x5C
#define FGDM_MASK 0x7
#define ENPRIMALPHA BIT(3)
#define ENSECALPHA BIT(4)
#define FGINCTRLPANIC 0x60
#define FGCCR 0x64
#define CCALPHA(a) (((a) & 0x1) << 30)
#define CCRED(r) (((r) & 0x3FF) << 20)
#define CCGREEN(g) (((g) & 0x3FF) << 10)
#define CCBLUE(b) ((b) & 0x3FF)
#define FGENABLE 0x68
#define FGEN BIT(0)
#define FGSLR 0x6C
#define FGENSTS 0x70
#define ENSTS BIT(0)
#define FGTIMESTAMP 0x74
#define LINEINDEX_MASK 0x3FFF
#define LINEINDEX_SHIFT 0
#define FRAMEINDEX_MASK 0xFFFFC000
#define FRAMEINDEX_SHIFT 14
#define FGCHSTAT 0x78
#define SECSYNCSTAT BIT(24)
#define SFIFOEMPTY BIT(16)
#define FGCHSTATCLR 0x7C
#define CLRSECSTAT BIT(16)
#define FGSKEWMON 0x80
#define FGSFIFOMIN 0x84
#define FGSFIFOMAX 0x88
#define FGSFIFOFILLCLR 0x8C
#define FGSREPD 0x90
#define FGSRFTD 0x94
#define KHZ 1000
#define PLL_MIN_FREQ_HZ 648000000
struct dpu_framegen {
void __iomem *base;
struct clk *clk_pll;
struct clk *clk_bypass;
struct clk *clk_disp;
struct clk *clk_disp_lpcg;
struct mutex mutex;
int id;
unsigned int encoder_type;
bool inuse;
bool use_bypass_clk;
bool side_by_side;
struct dpu_soc *dpu;
};
static inline u32 dpu_fg_read(struct dpu_framegen *fg, unsigned int offset)
{
return readl(fg->base + offset);
}
static inline void dpu_fg_write(struct dpu_framegen *fg,
unsigned int offset, u32 value)
{
writel(value, fg->base + offset);
}
void framegen_enable(struct dpu_framegen *fg)
{
dpu_fg_write(fg, FGENABLE, FGEN);
}
EXPORT_SYMBOL_GPL(framegen_enable);
void framegen_disable(struct dpu_framegen *fg)
{
dpu_fg_write(fg, FGENABLE, 0);
}
EXPORT_SYMBOL_GPL(framegen_disable);
void framegen_enable_pixel_link(struct dpu_framegen *fg)
{
struct dpu_soc *dpu = fg->dpu;
const struct dpu_data *data = dpu->data;
if (!(data->has_dual_ldb && fg->encoder_type == DRM_MODE_ENCODER_LVDS))
dpu_pxlink_set_mst_enable(fg->dpu, fg->id, true);
if (fg->encoder_type == DRM_MODE_ENCODER_DPI) {
dpu_pxlink_set_mst_valid(fg->dpu, fg->id, true);
dpu_pxlink_set_sync_ctrl(fg->dpu, fg->id, true);
}
}
EXPORT_SYMBOL_GPL(framegen_enable_pixel_link);
void framegen_disable_pixel_link(struct dpu_framegen *fg)
{
struct dpu_soc *dpu = fg->dpu;
const struct dpu_data *data = dpu->data;
if (fg->encoder_type == DRM_MODE_ENCODER_DPI) {
dpu_pxlink_set_mst_valid(fg->dpu, fg->id, false);
dpu_pxlink_set_sync_ctrl(fg->dpu, fg->id, false);
}
if (!(data->has_dual_ldb && fg->encoder_type == DRM_MODE_ENCODER_LVDS))
dpu_pxlink_set_mst_enable(fg->dpu, fg->id, false);
}
EXPORT_SYMBOL_GPL(framegen_disable_pixel_link);
void framegen_shdtokgen(struct dpu_framegen *fg)
{
dpu_fg_write(fg, FGSLR, SHDTOKGEN);
}
EXPORT_SYMBOL_GPL(framegen_shdtokgen);
void framegen_syncmode(struct dpu_framegen *fg, fgsyncmode_t mode)
{
u32 val;
val = dpu_fg_read(fg, FGSTCTRL);
val &= ~FGSYNCMODE_MASK;
val |= mode;
dpu_fg_write(fg, FGSTCTRL, val);
dpu_pxlink_set_dc_sync_mode(fg->dpu, mode != FGSYNCMODE__OFF);
}
EXPORT_SYMBOL_GPL(framegen_syncmode);
void framegen_cfg_videomode(struct dpu_framegen *fg, struct drm_display_mode *m,
bool side_by_side, unsigned int encoder_type)
{
struct dpu_soc *dpu = fg->dpu;
u32 hact, htotal, hsync, hsbp;
u32 vact, vtotal, vsync, vsbp;
u32 kick_row, kick_col;
u32 val;
unsigned long disp_clock_rate, pll_clock_rate = 0;
int div = 0;
fg->side_by_side = side_by_side;
fg->encoder_type = encoder_type;
hact = m->crtc_hdisplay;
htotal = m->crtc_htotal;
hsync = m->crtc_hsync_end - m->crtc_hsync_start;
hsbp = m->crtc_htotal - m->crtc_hsync_start;
if (side_by_side) {
hact /= 2;
htotal /= 2;
hsync /= 2;
hsbp /= 2;
}
vact = m->crtc_vdisplay;
vtotal = m->crtc_vtotal;
vsync = m->crtc_vsync_end - m->crtc_vsync_start;
vsbp = m->crtc_vtotal - m->crtc_vsync_start;
/* video mode */
dpu_fg_write(fg, HTCFG1, HACT(hact) | HTOTAL(htotal));
dpu_fg_write(fg, HTCFG2, HSYNC(hsync) | HSBP(hsbp) | HSEN);
dpu_fg_write(fg, VTCFG1, VACT(vact) | VTOTAL(vtotal));
dpu_fg_write(fg, VTCFG2, VSYNC(vsync) | VSBP(vsbp) | VSEN);
kick_col = hact + 1;
kick_row = vact;
/*
* FrameGen as slave needs to be kicked later for
* one line comparing to the master.
*/
if (side_by_side && framegen_is_slave(fg))
kick_row++;
/* pkickconfig */
dpu_fg_write(fg, PKICKCONFIG, COL(kick_col) | ROW(kick_row) | EN);
/* skikconfig */
dpu_fg_write(fg, SKICKCONFIG, COL(kick_col) | ROW(kick_row) | EN);
/* primary and secondary area position config */
dpu_fg_write(fg, PACFG, STARTX(0) | STARTY(0));
dpu_fg_write(fg, SACFG, STARTX(0) | STARTY(0));
/* alpha */
val = dpu_fg_read(fg, FGINCTRL);
val &= ~(ENPRIMALPHA | ENSECALPHA);
dpu_fg_write(fg, FGINCTRL, val);
val = dpu_fg_read(fg, FGINCTRLPANIC);
val &= ~(ENPRIMALPHA | ENSECALPHA);
dpu_fg_write(fg, FGINCTRLPANIC, val);
/* constant color */
dpu_fg_write(fg, FGCCR, 0);
disp_clock_rate = m->crtc_clock * 1000;
if (encoder_type == DRM_MODE_ENCODER_TMDS) {
if (side_by_side)
dpu_pxlink_set_mst_addr(dpu, fg->id, fg->id ? 2 : 1);
else
dpu_pxlink_set_mst_addr(dpu, fg->id, 1);
clk_set_parent(fg->clk_disp, fg->clk_bypass);
fg->use_bypass_clk = true;
} else {
dpu_pxlink_set_mst_addr(dpu, fg->id,
encoder_type == DRM_MODE_ENCODER_DPI ? 1 : 0);
clk_set_parent(fg->clk_disp, fg->clk_pll);
/* find an even divisor for PLL */
do {
div += 2;
pll_clock_rate = disp_clock_rate * div;
} while (pll_clock_rate < PLL_MIN_FREQ_HZ);
clk_set_rate(fg->clk_pll, pll_clock_rate);
clk_set_rate(fg->clk_disp, disp_clock_rate);
fg->use_bypass_clk = false;
}
}
EXPORT_SYMBOL_GPL(framegen_cfg_videomode);
void framegen_pkickconfig(struct dpu_framegen *fg, bool enable)
{
u32 val;
val = dpu_fg_read(fg, PKICKCONFIG);
if (enable)
val |= EN;
else
val &= ~EN;
dpu_fg_write(fg, PKICKCONFIG, val);
}
EXPORT_SYMBOL_GPL(framegen_pkickconfig);
void framegen_syncmode_fixup(struct dpu_framegen *fg, bool enable)
{
u32 val;
val = dpu_fg_read(fg, SECSTATCONFIG);
if (enable)
val |= BIT(7);
else
val &= ~BIT(7);
dpu_fg_write(fg, SECSTATCONFIG, val);
}
EXPORT_SYMBOL_GPL(framegen_syncmode_fixup);
void framegen_displaymode(struct dpu_framegen *fg, fgdm_t mode)
{
u32 val;
val = dpu_fg_read(fg, FGINCTRL);
val &= ~FGDM_MASK;
val |= mode;
dpu_fg_write(fg, FGINCTRL, val);
}
EXPORT_SYMBOL_GPL(framegen_displaymode);
void framegen_panic_displaymode(struct dpu_framegen *fg, fgdm_t mode)
{
u32 val;
val = dpu_fg_read(fg, FGINCTRLPANIC);
val &= ~FGDM_MASK;
val |= mode;
dpu_fg_write(fg, FGINCTRLPANIC, val);
}
EXPORT_SYMBOL_GPL(framegen_panic_displaymode);
void framegen_wait_done(struct dpu_framegen *fg, struct drm_display_mode *m)
{
unsigned long timeout, pending_framedur_jiffies;
int frame_size = m->crtc_htotal * m->crtc_vtotal;
int dotclock, pending_framedur_ns;
u32 val;
dotclock = clk_get_rate(fg->clk_disp) / KHZ;
if (dotclock == 0) {
/* fall back to display mode's clock */
dotclock = m->crtc_clock;
}
/*
* The SoC designer indicates that there are two pending frames
* to complete in the worst case.
* So, three pending frames are enough for sure.
*/
pending_framedur_ns = div_u64((u64) 3 * frame_size * 1000000, dotclock);
pending_framedur_jiffies = nsecs_to_jiffies(pending_framedur_ns);
if (pending_framedur_jiffies > (3 * HZ)) {
pending_framedur_jiffies = 3 * HZ;
dev_warn(fg->dpu->dev,
"truncate FrameGen%d pending frame duration to 3sec\n",
fg->id);
}
timeout = jiffies + pending_framedur_jiffies;
do {
val = dpu_fg_read(fg, FGENSTS);
} while ((val & ENSTS) && time_before(jiffies, timeout));
dev_dbg(fg->dpu->dev, "FrameGen%d pending frame duration is %ums\n",
fg->id, jiffies_to_msecs(pending_framedur_jiffies));
if (val & ENSTS)
dev_err(fg->dpu->dev, "failed to wait for FrameGen%d done\n",
fg->id);
}
EXPORT_SYMBOL_GPL(framegen_wait_done);
static inline u32 framegen_frame_index(u32 stamp)
{
return (stamp & FRAMEINDEX_MASK) >> FRAMEINDEX_SHIFT;
}
static inline u32 framegen_line_index(u32 stamp)
{
return (stamp & LINEINDEX_MASK) >> LINEINDEX_SHIFT;
}
void framegen_read_timestamp(struct dpu_framegen *fg,
u32 *frame_index, u32 *line_index)
{
u32 stamp;
stamp = dpu_fg_read(fg, FGTIMESTAMP);
*frame_index = framegen_frame_index(stamp);
*line_index = framegen_line_index(stamp);
}
EXPORT_SYMBOL_GPL(framegen_read_timestamp);
void framegen_wait_for_frame_counter_moving(struct dpu_framegen *fg)
{
u32 frame_index, line_index, last_frame_index;
unsigned long timeout = jiffies + msecs_to_jiffies(100);
framegen_read_timestamp(fg, &frame_index, &line_index);
do {
last_frame_index = frame_index;
framegen_read_timestamp(fg, &frame_index, &line_index);
} while (last_frame_index == frame_index &&
time_before(jiffies, timeout));
if (last_frame_index == frame_index)
dev_err(fg->dpu->dev,
"failed to wait for FrameGen%d frame counter moving\n",
fg->id);
else
dev_dbg(fg->dpu->dev,
"FrameGen%d frame counter moves - last %u, curr %d\n",
fg->id, last_frame_index, frame_index);
}
EXPORT_SYMBOL_GPL(framegen_wait_for_frame_counter_moving);
bool framegen_secondary_requests_to_read_empty_fifo(struct dpu_framegen *fg)
{
u32 val;
bool empty;
val = dpu_fg_read(fg, FGCHSTAT);
empty = !!(val & SFIFOEMPTY);
if (empty)
dev_dbg(fg->dpu->dev,
"FrameGen%d secondary requests to read empty FIFO\n",
fg->id);
return empty;
}
EXPORT_SYMBOL_GPL(framegen_secondary_requests_to_read_empty_fifo);
void framegen_secondary_clear_channel_status(struct dpu_framegen *fg)
{
dpu_fg_write(fg, FGCHSTATCLR, CLRSECSTAT);
}
EXPORT_SYMBOL_GPL(framegen_secondary_clear_channel_status);
bool framegen_secondary_is_syncup(struct dpu_framegen *fg)
{
u32 val = dpu_fg_read(fg, FGCHSTAT);
return val & SECSYNCSTAT;
}
EXPORT_SYMBOL_GPL(framegen_secondary_is_syncup);
void framegen_wait_for_secondary_syncup(struct dpu_framegen *fg)
{
unsigned long timeout = jiffies + msecs_to_jiffies(100);
bool syncup;
do {
syncup = framegen_secondary_is_syncup(fg);
} while (!syncup && time_before(jiffies, timeout));
if (syncup)
dev_dbg(fg->dpu->dev, "FrameGen%d secondary syncup\n", fg->id);
else
dev_err(fg->dpu->dev,
"failed to wait for FrameGen%d secondary syncup\n",
fg->id);
}
EXPORT_SYMBOL_GPL(framegen_wait_for_secondary_syncup);
void framegen_enable_clock(struct dpu_framegen *fg)
{
if (!fg->use_bypass_clk)
clk_prepare_enable(fg->clk_pll);
clk_prepare_enable(fg->clk_disp);
clk_prepare_enable(fg->clk_disp_lpcg);
}
EXPORT_SYMBOL_GPL(framegen_enable_clock);
void framegen_disable_clock(struct dpu_framegen *fg)
{
if (!fg->use_bypass_clk)
clk_disable_unprepare(fg->clk_pll);
clk_disable_unprepare(fg->clk_disp);
clk_disable_unprepare(fg->clk_disp_lpcg);
}
EXPORT_SYMBOL_GPL(framegen_disable_clock);
bool framegen_is_master(struct dpu_framegen *fg)
{
const struct dpu_data *data = fg->dpu->data;
return fg->id == data->master_stream_id;
}
EXPORT_SYMBOL_GPL(framegen_is_master);
bool framegen_is_slave(struct dpu_framegen *fg)
{
return !framegen_is_master(fg);
}
EXPORT_SYMBOL_GPL(framegen_is_slave);
struct dpu_framegen *dpu_fg_get(struct dpu_soc *dpu, int id)
{
struct dpu_framegen *fg;
int i;
for (i = 0; i < ARRAY_SIZE(fg_ids); i++)
if (fg_ids[i] == id)
break;
if (i == ARRAY_SIZE(fg_ids))
return ERR_PTR(-EINVAL);
fg = dpu->fg_priv[i];
mutex_lock(&fg->mutex);
if (fg->inuse) {
mutex_unlock(&fg->mutex);
return ERR_PTR(-EBUSY);
}
fg->inuse = true;
mutex_unlock(&fg->mutex);
return fg;
}
EXPORT_SYMBOL_GPL(dpu_fg_get);
void dpu_fg_put(struct dpu_framegen *fg)
{
mutex_lock(&fg->mutex);
fg->inuse = false;
mutex_unlock(&fg->mutex);
}
EXPORT_SYMBOL_GPL(dpu_fg_put);
struct dpu_framegen *dpu_aux_fg_peek(struct dpu_framegen *fg)
{
return fg->dpu->fg_priv[fg->id ^ 1];
}
EXPORT_SYMBOL_GPL(dpu_aux_fg_peek);
void _dpu_fg_init(struct dpu_soc *dpu, unsigned int id)
{
struct dpu_framegen *fg;
int i;
for (i = 0; i < ARRAY_SIZE(fg_ids); i++)
if (fg_ids[i] == id)
break;
if (WARN_ON(i == ARRAY_SIZE(fg_ids)))
return;
fg = dpu->fg_priv[i];
framegen_syncmode(fg, FGSYNCMODE__OFF);
}
int dpu_fg_init(struct dpu_soc *dpu, unsigned int id,
unsigned long unused, unsigned long base)
{
struct dpu_framegen *fg;
fg = devm_kzalloc(dpu->dev, sizeof(*fg), GFP_KERNEL);
if (!fg)
return -ENOMEM;
dpu->fg_priv[id] = fg;
fg->base = devm_ioremap(dpu->dev, base, SZ_256);
if (!fg->base)
return -ENOMEM;
fg->clk_pll = devm_clk_get(dpu->dev, id ? "pll1" : "pll0");
if (IS_ERR(fg->clk_pll))
return PTR_ERR(fg->clk_pll);
fg->clk_bypass = devm_clk_get(dpu->dev, "bypass0");
if (IS_ERR(fg->clk_bypass))
return PTR_ERR(fg->clk_bypass);
fg->clk_disp = devm_clk_get(dpu->dev, id ? "disp1" : "disp0");
if (IS_ERR(fg->clk_disp))
return PTR_ERR(fg->clk_disp);
fg->clk_disp_lpcg = devm_clk_get(dpu->dev, id ? "disp1_lpcg" : "disp0_lpcg");
if (IS_ERR(fg->clk_disp_lpcg))
return PTR_ERR(fg->clk_disp_lpcg);
fg->dpu = dpu;
fg->id = id;
mutex_init(&fg->mutex);
_dpu_fg_init(dpu, id);
return 0;
}