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
+13
View File
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_STI
tristate "DRM Support for STMicroelectronics SoC stiH4xx Series"
depends on OF && DRM && (ARCH_STI || COMPILE_TEST)
select RESET_CONTROLLER
select DRM_CLIENT_SELECTION
select DRM_KMS_HELPER
select DRM_GEM_DMA_HELPER
select DRM_PANEL
select FW_LOADER
select SND_SOC_HDMI_CODEC if SND_SOC
help
Choose this option to enable DRM on STM stiH4xx chipset
+20
View File
@@ -0,0 +1,20 @@
# SPDX-License-Identifier: GPL-2.0
sti-drm-y := \
sti_mixer.o \
sti_gdp.o \
sti_vid.o \
sti_cursor.o \
sti_compositor.o \
sti_crtc.o \
sti_plane.o \
sti_hdmi.o \
sti_hdmi_tx3g4c28phy.o \
sti_dvo.o \
sti_awg_utils.o \
sti_vtg.o \
sti_hda.o \
sti_tvout.o \
sti_hqvdp.o \
sti_drv.o
obj-$(CONFIG_DRM_STI) = sti-drm.o
+58
View File
@@ -0,0 +1,58 @@
1. stiH display hardware IP
---------------------------
The STMicroelectronics stiH SoCs use a common chain of HW display IP blocks:
- The High Quality Video Display Processor (HQVDP) gets video frames from a
video decoder and does high quality video processing, including scaling.
- The Compositor is a multiplane, dual-mixer (Main & Aux) digital processor. It
has several inputs:
- The graphics planes are internally processed by the Generic Display
Pipeline (GDP).
- The video plug (VID) connects to the HQVDP output.
- The cursor handles ... a cursor.
- The TV OUT pre-formats (convert, clip, round) the compositor output data
- The HDMI / DVO / HD Analog / SD analog IP builds the video signals
- DVO (Digital Video Output) handles a 24bits parallel signal
- The HD analog signal is typically driven by a YCbCr cable, supporting up to
1080i mode.
- The SD analog signal is typically used for legacy TV
- The VTG (Video Timing Generators) build Vsync signals used by the other HW IP
Note that some stiH drivers support only a subset of thee HW IP.
.-------------. .-----------. .-----------.
GPU >-------------+GDP Main | | +---+ HDMI +--> HDMI
GPU >-------------+GDP mixer+---+ | :===========:
GPU >-------------+Cursor | | +---+ DVO +--> 24b//
------- | COMPOSITOR | | TV OUT | :===========:
| | | | | +---+ HD analog +--> YCbCr
Vid >--+ HQVDP +--+VID Aux +---+ | :===========:
dec | | | mixer| | +---+ SD analog +--> CVBS
'-------' '-------------' '-----------' '-----------'
.-----------.
| main+--> Vsync
| VTG |
| aux+--> Vsync
'-----------'
2. DRM / HW mapping
-------------------
These IP are mapped to the DRM objects as following:
- The CRTCs are mapped to the Compositor Main and Aux Mixers
- The Framebuffers and planes are mapped to the Compositor GDP (non video
buffers) and to HQVDP+VID (video buffers)
- The Cursor is mapped to the Compositor Cursor
- The Encoders are mapped to the TVOut
- The Bridges/Connectors are mapped to the HDMI / DVO / HD Analog / SD analog
FB & planes Cursor CRTC Encoders Bridges/Connectors
| | | | |
| | | | |
| .-------------. | .-----------. .-----------. |
+------------> |GDP | Main | | | +-> | | HDMI | <-+
+------------> |GDP v mixer|<+ | | | :===========: |
| |Cursor | | | +-> | | DVO | <-+
| ------- | COMPOSITOR | | |TV OUT | | :===========: |
| | | | | | | +-> | | HD analog | <-+
+-> | HQVDP | |VID Aux |<+ | | | :===========: |
| | | mixer| | +-> | | SD analog | <-+
'-------' '-------------' '-----------' '-----------'
@@ -0,0 +1,192 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
*/
#include <drm/drm_print.h>
#include "sti_awg_utils.h"
#define AWG_DELAY (-5)
#define AWG_OPCODE_OFFSET 10
#define AWG_MAX_ARG 0x3ff
enum opcode {
SET,
RPTSET,
RPLSET,
SKIP,
STOP,
REPEAT,
REPLAY,
JUMP,
HOLD,
};
static int awg_generate_instr(enum opcode opcode,
long int arg,
long int mux_sel,
long int data_en,
struct awg_code_generation_params *fwparams)
{
u32 instruction = 0;
u32 mux = (mux_sel << 8) & 0x1ff;
u32 data_enable = (data_en << 9) & 0x2ff;
long int arg_tmp = arg;
/* skip, repeat and replay arg should not exceed 1023.
* If user wants to exceed this value, the instruction should be
* duplicate and arg should be adjust for each duplicated instruction.
*
* mux_sel is used in case of SAV/EAV synchronization.
*/
while (arg_tmp > 0) {
arg = arg_tmp;
if (fwparams->instruction_offset >= AWG_MAX_INST) {
DRM_ERROR("too many number of instructions\n");
return -EINVAL;
}
switch (opcode) {
case SKIP:
/* leave 'arg' + 1 pixel elapsing without changing
* output bus */
arg--; /* pixel adjustment */
arg_tmp--;
if (arg < 0) {
/* SKIP instruction not needed */
return 0;
}
if (arg == 0) {
/* SKIP 0 not permitted but we want to skip 1
* pixel. So we transform SKIP into SET
* instruction */
opcode = SET;
break;
}
mux = 0;
data_enable = 0;
arg &= AWG_MAX_ARG;
break;
case REPEAT:
case REPLAY:
if (arg == 0) {
/* REPEAT or REPLAY instruction not needed */
return 0;
}
mux = 0;
data_enable = 0;
arg &= AWG_MAX_ARG;
break;
case JUMP:
mux = 0;
data_enable = 0;
arg |= 0x40; /* for jump instruction 7th bit is 1 */
arg &= AWG_MAX_ARG;
break;
case STOP:
arg = 0;
break;
case SET:
case RPTSET:
case RPLSET:
case HOLD:
arg &= (0x0ff);
break;
default:
DRM_ERROR("instruction %d does not exist\n", opcode);
return -EINVAL;
}
arg_tmp = arg_tmp - arg;
arg = ((arg + mux) + data_enable);
instruction = ((opcode) << AWG_OPCODE_OFFSET) | arg;
fwparams->ram_code[fwparams->instruction_offset] =
instruction & (0x3fff);
fwparams->instruction_offset++;
}
return 0;
}
static int awg_generate_line_signal(
struct awg_code_generation_params *fwparams,
struct awg_timing *timing)
{
long int val;
int ret = 0;
if (timing->trailing_pixels > 0) {
/* skip trailing pixel */
val = timing->blanking_level;
ret |= awg_generate_instr(RPLSET, val, 0, 0, fwparams);
val = timing->trailing_pixels - 1 + AWG_DELAY;
ret |= awg_generate_instr(SKIP, val, 0, 0, fwparams);
}
/* set DE signal high */
val = timing->blanking_level;
ret |= awg_generate_instr((timing->trailing_pixels > 0) ? SET : RPLSET,
val, 0, 1, fwparams);
if (timing->blanking_pixels > 0) {
/* skip the number of active pixel */
val = timing->active_pixels - 1;
ret |= awg_generate_instr(SKIP, val, 0, 1, fwparams);
/* set DE signal low */
val = timing->blanking_level;
ret |= awg_generate_instr(SET, val, 0, 0, fwparams);
}
return ret;
}
int sti_awg_generate_code_data_enable_mode(
struct awg_code_generation_params *fwparams,
struct awg_timing *timing)
{
long int val, tmp_val;
int ret = 0;
if (timing->trailing_lines > 0) {
/* skip trailing lines */
val = timing->blanking_level;
ret |= awg_generate_instr(RPLSET, val, 0, 0, fwparams);
val = timing->trailing_lines - 1;
ret |= awg_generate_instr(REPLAY, val, 0, 0, fwparams);
}
tmp_val = timing->active_lines - 1;
while (tmp_val > 0) {
/* generate DE signal for each line */
ret |= awg_generate_line_signal(fwparams, timing);
/* replay the sequence as many active lines defined */
ret |= awg_generate_instr(REPLAY,
min_t(int, AWG_MAX_ARG, tmp_val),
0, 0, fwparams);
tmp_val -= AWG_MAX_ARG;
}
if (timing->blanking_lines > 0) {
/* skip blanking lines */
val = timing->blanking_level;
ret |= awg_generate_instr(RPLSET, val, 0, 0, fwparams);
val = timing->blanking_lines - 1;
ret |= awg_generate_instr(REPLAY, val, 0, 0, fwparams);
}
return ret;
}
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
*/
#ifndef _STI_AWG_UTILS_H_
#define _STI_AWG_UTILS_H_
#include <linux/types.h>
#define AWG_MAX_INST 64
struct awg_code_generation_params {
u32 *ram_code;
u8 instruction_offset;
};
struct awg_timing {
u32 total_lines;
u32 active_lines;
u32 blanking_lines;
u32 trailing_lines;
u32 total_pixels;
u32 active_pixels;
u32 blanking_pixels;
u32 trailing_pixels;
u32 blanking_level;
};
int sti_awg_generate_code_data_enable_mode(
struct awg_code_generation_params *fw_gen_params,
struct awg_timing *timing);
#endif
@@ -0,0 +1,269 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
* Fabien Dessenne <fabien.dessenne@st.com>
* for STMicroelectronics.
*/
#include <linux/component.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <drm/drm_device.h>
#include <drm/drm_print.h>
#include <drm/drm_vblank.h>
#include "sti_compositor.h"
#include "sti_crtc.h"
#include "sti_cursor.h"
#include "sti_drv.h"
#include "sti_gdp.h"
#include "sti_plane.h"
#include "sti_vid.h"
#include "sti_vtg.h"
/*
* stiH407 compositor properties
*/
static const struct sti_compositor_data stih407_compositor_data = {
.nb_subdev = 8,
.subdev_desc = {
{STI_CURSOR_SUBDEV, (int)STI_CURSOR, 0x000},
{STI_GPD_SUBDEV, (int)STI_GDP_0, 0x100},
{STI_GPD_SUBDEV, (int)STI_GDP_1, 0x200},
{STI_GPD_SUBDEV, (int)STI_GDP_2, 0x300},
{STI_GPD_SUBDEV, (int)STI_GDP_3, 0x400},
{STI_VID_SUBDEV, (int)STI_HQVDP_0, 0x700},
{STI_MIXER_MAIN_SUBDEV, STI_MIXER_MAIN, 0xC00},
{STI_MIXER_AUX_SUBDEV, STI_MIXER_AUX, 0xD00},
},
};
void sti_compositor_debugfs_init(struct sti_compositor *compo,
struct drm_minor *minor)
{
unsigned int i;
for (i = 0; i < STI_MAX_VID; i++)
if (compo->vid[i])
vid_debugfs_init(compo->vid[i], minor);
for (i = 0; i < STI_MAX_MIXER; i++)
if (compo->mixer[i])
sti_mixer_debugfs_init(compo->mixer[i], minor);
}
static int sti_compositor_bind(struct device *dev,
struct device *master,
void *data)
{
struct sti_compositor *compo = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
unsigned int i, mixer_id = 0, vid_id = 0, crtc_id = 0;
struct sti_private *dev_priv = drm_dev->dev_private;
struct drm_plane *cursor = NULL;
struct drm_plane *primary = NULL;
struct sti_compositor_subdev_descriptor *desc = compo->data.subdev_desc;
unsigned int array_size = compo->data.nb_subdev;
dev_priv->compo = compo;
/* Register mixer subdev and video subdev first */
for (i = 0; i < array_size; i++) {
switch (desc[i].type) {
case STI_VID_SUBDEV:
compo->vid[vid_id++] =
sti_vid_create(compo->dev, drm_dev, desc[i].id,
compo->regs + desc[i].offset);
break;
case STI_MIXER_MAIN_SUBDEV:
case STI_MIXER_AUX_SUBDEV:
compo->mixer[mixer_id++] =
sti_mixer_create(compo->dev, drm_dev, desc[i].id,
compo->regs + desc[i].offset);
break;
case STI_GPD_SUBDEV:
case STI_CURSOR_SUBDEV:
/* Nothing to do, wait for the second round */
break;
default:
DRM_ERROR("Unknown subdev component type\n");
return 1;
}
}
/* Register the other subdevs, create crtc and planes */
for (i = 0; i < array_size; i++) {
enum drm_plane_type plane_type = DRM_PLANE_TYPE_OVERLAY;
if (crtc_id < mixer_id)
plane_type = DRM_PLANE_TYPE_PRIMARY;
switch (desc[i].type) {
case STI_MIXER_MAIN_SUBDEV:
case STI_MIXER_AUX_SUBDEV:
case STI_VID_SUBDEV:
/* Nothing to do, already done at the first round */
break;
case STI_CURSOR_SUBDEV:
cursor = sti_cursor_create(drm_dev, compo->dev,
desc[i].id,
compo->regs + desc[i].offset,
1);
if (!cursor) {
DRM_ERROR("Can't create CURSOR plane\n");
break;
}
break;
case STI_GPD_SUBDEV:
primary = sti_gdp_create(drm_dev, compo->dev,
desc[i].id,
compo->regs + desc[i].offset,
(1 << mixer_id) - 1,
plane_type);
if (!primary) {
DRM_ERROR("Can't create GDP plane\n");
break;
}
break;
default:
DRM_ERROR("Unknown subdev component type\n");
return 1;
}
/* The first planes are reserved for primary planes*/
if (crtc_id < mixer_id && primary) {
sti_crtc_init(drm_dev, compo->mixer[crtc_id],
primary, cursor);
crtc_id++;
cursor = NULL;
primary = NULL;
}
}
drm_vblank_init(drm_dev, crtc_id);
return 0;
}
static void sti_compositor_unbind(struct device *dev, struct device *master,
void *data)
{
/* do nothing */
}
static const struct component_ops sti_compositor_ops = {
.bind = sti_compositor_bind,
.unbind = sti_compositor_unbind,
};
static const struct of_device_id compositor_of_match[] = {
{
.compatible = "st,stih407-compositor",
.data = &stih407_compositor_data,
}, {
/* end node */
}
};
MODULE_DEVICE_TABLE(of, compositor_of_match);
static int sti_compositor_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct device_node *vtg_np;
struct sti_compositor *compo;
unsigned int i;
compo = devm_kzalloc(dev, sizeof(*compo), GFP_KERNEL);
if (!compo) {
DRM_ERROR("Failed to allocate compositor context\n");
return -ENOMEM;
}
compo->dev = dev;
for (i = 0; i < STI_MAX_MIXER; i++)
compo->vtg_vblank_nb[i].notifier_call = sti_crtc_vblank_cb;
/* populate data structure depending on compatibility */
BUG_ON(!of_match_node(compositor_of_match, np)->data);
memcpy(&compo->data, of_match_node(compositor_of_match, np)->data,
sizeof(struct sti_compositor_data));
compo->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(compo->regs)) {
DRM_ERROR("Register mapping failed\n");
return PTR_ERR(compo->regs);
}
/* Get clock resources */
compo->clk_compo_main = devm_clk_get(dev, "compo_main");
if (IS_ERR(compo->clk_compo_main)) {
DRM_ERROR("Cannot get compo_main clock\n");
return PTR_ERR(compo->clk_compo_main);
}
compo->clk_compo_aux = devm_clk_get(dev, "compo_aux");
if (IS_ERR(compo->clk_compo_aux)) {
DRM_ERROR("Cannot get compo_aux clock\n");
return PTR_ERR(compo->clk_compo_aux);
}
compo->clk_pix_main = devm_clk_get(dev, "pix_main");
if (IS_ERR(compo->clk_pix_main)) {
DRM_ERROR("Cannot get pix_main clock\n");
return PTR_ERR(compo->clk_pix_main);
}
compo->clk_pix_aux = devm_clk_get(dev, "pix_aux");
if (IS_ERR(compo->clk_pix_aux)) {
DRM_ERROR("Cannot get pix_aux clock\n");
return PTR_ERR(compo->clk_pix_aux);
}
/* Get reset resources */
compo->rst_main = devm_reset_control_get_shared(dev, "compo-main");
/* Take compo main out of reset */
if (!IS_ERR(compo->rst_main))
reset_control_deassert(compo->rst_main);
compo->rst_aux = devm_reset_control_get_shared(dev, "compo-aux");
/* Take compo aux out of reset */
if (!IS_ERR(compo->rst_aux))
reset_control_deassert(compo->rst_aux);
vtg_np = of_parse_phandle(pdev->dev.of_node, "st,vtg", 0);
if (vtg_np)
compo->vtg[STI_MIXER_MAIN] = of_vtg_find(vtg_np);
of_node_put(vtg_np);
vtg_np = of_parse_phandle(pdev->dev.of_node, "st,vtg", 1);
if (vtg_np)
compo->vtg[STI_MIXER_AUX] = of_vtg_find(vtg_np);
of_node_put(vtg_np);
platform_set_drvdata(pdev, compo);
return component_add(&pdev->dev, &sti_compositor_ops);
}
static void sti_compositor_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sti_compositor_ops);
}
struct platform_driver sti_compositor_driver = {
.driver = {
.name = "sti-compositor",
.of_match_table = compositor_of_match,
},
.probe = sti_compositor_probe,
.remove = sti_compositor_remove,
};
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,85 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
* Fabien Dessenne <fabien.dessenne@st.com>
* for STMicroelectronics.
*/
#ifndef _STI_COMPOSITOR_H_
#define _STI_COMPOSITOR_H_
#include <linux/clk.h>
#include <linux/kernel.h>
#include "sti_mixer.h"
#include "sti_plane.h"
#define WAIT_NEXT_VSYNC_MS 50 /*ms*/
#define STI_MAX_MIXER 2
#define STI_MAX_VID 1
enum sti_compositor_subdev_type {
STI_MIXER_MAIN_SUBDEV,
STI_MIXER_AUX_SUBDEV,
STI_GPD_SUBDEV,
STI_VID_SUBDEV,
STI_CURSOR_SUBDEV,
};
struct sti_compositor_subdev_descriptor {
enum sti_compositor_subdev_type type;
int id;
unsigned int offset;
};
/**
* STI Compositor data structure
*
* @nb_subdev: number of subdevices supported by the compositor
* @subdev_desc: subdev list description
*/
#define MAX_SUBDEV 9
struct sti_compositor_data {
unsigned int nb_subdev;
struct sti_compositor_subdev_descriptor subdev_desc[MAX_SUBDEV];
};
/**
* STI Compositor structure
*
* @dev: driver device
* @regs: registers (main)
* @data: device data
* @clk_compo_main: clock for main compo
* @clk_compo_aux: clock for aux compo
* @clk_pix_main: pixel clock for main path
* @clk_pix_aux: pixel clock for aux path
* @rst_main: reset control of the main path
* @rst_aux: reset control of the aux path
* @mixer: array of mixers
* @vid: array of vids
* @vtg: array of vtgs
* @vtg_vblank_nb: array of callbacks for VTG VSYNC notification
*/
struct sti_compositor {
struct device *dev;
void __iomem *regs;
struct sti_compositor_data data;
struct clk *clk_compo_main;
struct clk *clk_compo_aux;
struct clk *clk_pix_main;
struct clk *clk_pix_aux;
struct reset_control *rst_main;
struct reset_control *rst_aux;
struct sti_mixer *mixer[STI_MAX_MIXER];
struct sti_vid *vid[STI_MAX_VID];
struct sti_vtg *vtg[STI_MAX_MIXER];
struct notifier_block vtg_vblank_nb[STI_MAX_MIXER];
};
void sti_compositor_debugfs_init(struct sti_compositor *compo,
struct drm_minor *minor);
#endif
+368
View File
@@ -0,0 +1,368 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
* Fabien Dessenne <fabien.dessenne@st.com>
* for STMicroelectronics.
*/
#include <linux/clk.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_device.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include "sti_compositor.h"
#include "sti_crtc.h"
#include "sti_drv.h"
#include "sti_vid.h"
#include "sti_vtg.h"
static void sti_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct sti_mixer *mixer = to_sti_mixer(crtc);
DRM_DEBUG_DRIVER("\n");
mixer->status = STI_MIXER_READY;
drm_crtc_vblank_on(crtc);
}
static void sti_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct sti_mixer *mixer = to_sti_mixer(crtc);
DRM_DEBUG_DRIVER("\n");
mixer->status = STI_MIXER_DISABLING;
drm_crtc_wait_one_vblank(crtc);
}
static int
sti_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode)
{
struct sti_mixer *mixer = to_sti_mixer(crtc);
struct device *dev = mixer->dev;
struct sti_compositor *compo = dev_get_drvdata(dev);
struct clk *compo_clk, *pix_clk;
int rate = mode->clock * 1000;
DRM_DEBUG_KMS("CRTC:%d (%s) mode: (%s)\n",
crtc->base.id, sti_mixer_to_str(mixer), mode->name);
DRM_DEBUG_KMS(DRM_MODE_FMT "\n", DRM_MODE_ARG(mode));
if (mixer->id == STI_MIXER_MAIN) {
compo_clk = compo->clk_compo_main;
pix_clk = compo->clk_pix_main;
} else {
compo_clk = compo->clk_compo_aux;
pix_clk = compo->clk_pix_aux;
}
/* Prepare and enable the compo IP clock */
if (clk_prepare_enable(compo_clk)) {
DRM_INFO("Failed to prepare/enable compositor clk\n");
goto compo_error;
}
/* Set rate and prepare/enable pixel clock */
if (clk_set_rate(pix_clk, rate) < 0) {
DRM_ERROR("Cannot set rate (%dHz) for pix clk\n", rate);
goto pix_error;
}
if (clk_prepare_enable(pix_clk)) {
DRM_ERROR("Failed to prepare/enable pix clk\n");
goto pix_error;
}
sti_vtg_set_config(compo->vtg[mixer->id], &crtc->mode);
if (sti_mixer_active_video_area(mixer, &crtc->mode)) {
DRM_ERROR("Can't set active video area\n");
goto mixer_error;
}
return 0;
mixer_error:
clk_disable_unprepare(pix_clk);
pix_error:
clk_disable_unprepare(compo_clk);
compo_error:
return -EINVAL;
}
static void sti_crtc_disable(struct drm_crtc *crtc)
{
struct sti_mixer *mixer = to_sti_mixer(crtc);
struct device *dev = mixer->dev;
struct sti_compositor *compo = dev_get_drvdata(dev);
DRM_DEBUG_KMS("CRTC:%d (%s)\n", crtc->base.id, sti_mixer_to_str(mixer));
/* Disable Background */
sti_mixer_set_background_status(mixer, false);
drm_crtc_vblank_off(crtc);
/* Disable pixel clock and compo IP clocks */
if (mixer->id == STI_MIXER_MAIN) {
clk_disable_unprepare(compo->clk_pix_main);
clk_disable_unprepare(compo->clk_compo_main);
} else {
clk_disable_unprepare(compo->clk_pix_aux);
clk_disable_unprepare(compo->clk_compo_aux);
}
mixer->status = STI_MIXER_DISABLED;
}
static void
sti_crtc_mode_set_nofb(struct drm_crtc *crtc)
{
sti_crtc_mode_set(crtc, &crtc->state->adjusted_mode);
}
static void sti_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct drm_device *drm_dev = crtc->dev;
struct sti_mixer *mixer = to_sti_mixer(crtc);
struct sti_compositor *compo = dev_get_drvdata(mixer->dev);
struct drm_plane *p;
struct drm_pending_vblank_event *event;
unsigned long flags;
DRM_DEBUG_DRIVER("\n");
/* perform plane actions */
list_for_each_entry(p, &drm_dev->mode_config.plane_list, head) {
struct sti_plane *plane = to_sti_plane(p);
switch (plane->status) {
case STI_PLANE_UPDATED:
/* ignore update for other CRTC */
if (p->state->crtc != crtc)
continue;
/* update planes tag as updated */
DRM_DEBUG_DRIVER("update plane %s\n",
sti_plane_to_str(plane));
if (sti_mixer_set_plane_depth(mixer, plane)) {
DRM_ERROR("Cannot set plane %s depth\n",
sti_plane_to_str(plane));
break;
}
if (sti_mixer_set_plane_status(mixer, plane, true)) {
DRM_ERROR("Cannot enable plane %s at mixer\n",
sti_plane_to_str(plane));
break;
}
/* if plane is HQVDP_0 then commit the vid[0] */
if (plane->desc == STI_HQVDP_0)
sti_vid_commit(compo->vid[0], p->state);
plane->status = STI_PLANE_READY;
break;
case STI_PLANE_DISABLING:
/* disabling sequence for planes tag as disabling */
DRM_DEBUG_DRIVER("disable plane %s from mixer\n",
sti_plane_to_str(plane));
if (sti_mixer_set_plane_status(mixer, plane, false)) {
DRM_ERROR("Cannot disable plane %s at mixer\n",
sti_plane_to_str(plane));
continue;
}
if (plane->desc == STI_CURSOR)
/* tag plane status for disabled */
plane->status = STI_PLANE_DISABLED;
else
/* tag plane status for flushing */
plane->status = STI_PLANE_FLUSHING;
/* if plane is HQVDP_0 then disable the vid[0] */
if (plane->desc == STI_HQVDP_0)
sti_vid_disable(compo->vid[0]);
break;
default:
/* Other status case are not handled */
break;
}
}
event = crtc->state->event;
if (event) {
crtc->state->event = NULL;
spin_lock_irqsave(&crtc->dev->event_lock, flags);
if (drm_crtc_vblank_get(crtc) == 0)
drm_crtc_arm_vblank_event(crtc, event);
else
drm_crtc_send_vblank_event(crtc, event);
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
}
}
static const struct drm_crtc_helper_funcs sti_crtc_helper_funcs = {
.mode_set_nofb = sti_crtc_mode_set_nofb,
.atomic_flush = sti_crtc_atomic_flush,
.atomic_enable = sti_crtc_atomic_enable,
.atomic_disable = sti_crtc_atomic_disable,
};
static void sti_crtc_destroy(struct drm_crtc *crtc)
{
DRM_DEBUG_KMS("\n");
drm_crtc_cleanup(crtc);
}
static int sti_crtc_set_property(struct drm_crtc *crtc,
struct drm_property *property,
uint64_t val)
{
DRM_DEBUG_KMS("\n");
return 0;
}
int sti_crtc_vblank_cb(struct notifier_block *nb,
unsigned long event, void *data)
{
struct sti_compositor *compo;
struct drm_crtc *crtc = data;
struct sti_mixer *mixer;
unsigned int pipe;
pipe = drm_crtc_index(crtc);
compo = container_of(nb, struct sti_compositor, vtg_vblank_nb[pipe]);
mixer = compo->mixer[pipe];
if ((event != VTG_TOP_FIELD_EVENT) &&
(event != VTG_BOTTOM_FIELD_EVENT)) {
DRM_ERROR("unknown event: %lu\n", event);
return -EINVAL;
}
drm_crtc_handle_vblank(crtc);
if (mixer->status == STI_MIXER_DISABLING) {
struct drm_plane *p;
/* Disable mixer only if all overlay planes (GDP and VDP)
* are disabled */
list_for_each_entry(p, &crtc->dev->mode_config.plane_list,
head) {
struct sti_plane *plane = to_sti_plane(p);
if ((plane->desc & STI_PLANE_TYPE_MASK) <= STI_VDP)
if (plane->status != STI_PLANE_DISABLED)
return 0;
}
sti_crtc_disable(crtc);
}
return 0;
}
static int sti_crtc_enable_vblank(struct drm_crtc *crtc)
{
struct drm_device *dev = crtc->dev;
unsigned int pipe = crtc->index;
struct sti_private *dev_priv = dev->dev_private;
struct sti_compositor *compo = dev_priv->compo;
struct notifier_block *vtg_vblank_nb = &compo->vtg_vblank_nb[pipe];
struct sti_vtg *vtg = compo->vtg[pipe];
DRM_DEBUG_DRIVER("\n");
if (sti_vtg_register_client(vtg, vtg_vblank_nb, crtc)) {
DRM_ERROR("Cannot register VTG notifier\n");
return -EINVAL;
}
return 0;
}
static void sti_crtc_disable_vblank(struct drm_crtc *crtc)
{
struct drm_device *drm_dev = crtc->dev;
unsigned int pipe = crtc->index;
struct sti_private *priv = drm_dev->dev_private;
struct sti_compositor *compo = priv->compo;
struct notifier_block *vtg_vblank_nb = &compo->vtg_vblank_nb[pipe];
struct sti_vtg *vtg = compo->vtg[pipe];
DRM_DEBUG_DRIVER("\n");
if (sti_vtg_unregister_client(vtg, vtg_vblank_nb))
DRM_DEBUG_DRIVER("Warning: cannot unregister VTG notifier\n");
}
static int sti_crtc_late_register(struct drm_crtc *crtc)
{
struct sti_mixer *mixer = to_sti_mixer(crtc);
struct sti_compositor *compo = dev_get_drvdata(mixer->dev);
if (drm_crtc_index(crtc) == 0)
sti_compositor_debugfs_init(compo, crtc->dev->primary);
return 0;
}
static const struct drm_crtc_funcs sti_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.destroy = sti_crtc_destroy,
.set_property = sti_crtc_set_property,
.reset = drm_atomic_helper_crtc_reset,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
.late_register = sti_crtc_late_register,
.enable_vblank = sti_crtc_enable_vblank,
.disable_vblank = sti_crtc_disable_vblank,
};
bool sti_crtc_is_main(struct drm_crtc *crtc)
{
struct sti_mixer *mixer = to_sti_mixer(crtc);
if (mixer->id == STI_MIXER_MAIN)
return true;
return false;
}
int sti_crtc_init(struct drm_device *drm_dev, struct sti_mixer *mixer,
struct drm_plane *primary, struct drm_plane *cursor)
{
struct drm_crtc *crtc = &mixer->drm_crtc;
int res;
res = drm_crtc_init_with_planes(drm_dev, crtc, primary, cursor,
&sti_crtc_funcs, NULL);
if (res) {
DRM_ERROR("Can't initialize CRTC\n");
return -EINVAL;
}
drm_crtc_helper_add(crtc, &sti_crtc_helper_funcs);
DRM_DEBUG_DRIVER("drm CRTC:%d mapped to %s\n",
crtc->base.id, sti_mixer_to_str(mixer));
return 0;
}
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
*/
#ifndef _STI_CRTC_H_
#define _STI_CRTC_H_
struct drm_crtc;
struct drm_device;
struct drm_plane;
struct notifier_block;
struct sti_mixer;
int sti_crtc_init(struct drm_device *drm_dev, struct sti_mixer *mixer,
struct drm_plane *primary, struct drm_plane *cursor);
int sti_crtc_vblank_cb(struct notifier_block *nb,
unsigned long event, void *data);
bool sti_crtc_is_main(struct drm_crtc *drm_crtc);
#endif
@@ -0,0 +1,420 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Vincent Abriou <vincent.abriou@st.com>
* Fabien Dessenne <fabien.dessenne@st.com>
* for STMicroelectronics.
*/
#include <linux/dma-mapping.h>
#include <linux/seq_file.h>
#include <drm/drm_atomic.h>
#include <drm/drm_device.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_print.h>
#include "sti_compositor.h"
#include "sti_cursor.h"
#include "sti_plane.h"
#include "sti_vtg.h"
/* Registers */
#define CUR_CTL 0x00
#define CUR_VPO 0x0C
#define CUR_PML 0x14
#define CUR_PMP 0x18
#define CUR_SIZE 0x1C
#define CUR_CML 0x20
#define CUR_AWS 0x28
#define CUR_AWE 0x2C
#define CUR_CTL_CLUT_UPDATE BIT(1)
#define STI_CURS_MIN_SIZE 1
#define STI_CURS_MAX_SIZE 128
/*
* pixmap dma buffer structure
*
* @paddr: physical address
* @size: buffer size
* @base: virtual address
*/
struct dma_pixmap {
dma_addr_t paddr;
size_t size;
void *base;
};
/*
* STI Cursor structure
*
* @sti_plane: sti_plane structure
* @dev: driver device
* @regs: cursor registers
* @width: cursor width
* @height: cursor height
* @clut: color look up table
* @clut_paddr: color look up table physical address
* @pixmap: pixmap dma buffer (clut8-format cursor)
*/
struct sti_cursor {
struct sti_plane plane;
struct device *dev;
void __iomem *regs;
unsigned int width;
unsigned int height;
unsigned short *clut;
dma_addr_t clut_paddr;
struct dma_pixmap pixmap;
};
static const uint32_t cursor_supported_formats[] = {
DRM_FORMAT_ARGB8888,
};
#define to_sti_cursor(x) container_of(x, struct sti_cursor, plane)
#define DBGFS_DUMP(reg) seq_printf(s, "\n %-25s 0x%08X", #reg, \
readl(cursor->regs + reg))
static void cursor_dbg_vpo(struct seq_file *s, u32 val)
{
seq_printf(s, "\txdo:%4d\tydo:%4d", val & 0x0FFF, (val >> 16) & 0x0FFF);
}
static void cursor_dbg_size(struct seq_file *s, u32 val)
{
seq_printf(s, "\t%d x %d", val & 0x07FF, (val >> 16) & 0x07FF);
}
static void cursor_dbg_pml(struct seq_file *s,
struct sti_cursor *cursor, u32 val)
{
if (cursor->pixmap.paddr == val)
seq_printf(s, "\tVirt @: %p", cursor->pixmap.base);
}
static void cursor_dbg_cml(struct seq_file *s,
struct sti_cursor *cursor, u32 val)
{
if (cursor->clut_paddr == val)
seq_printf(s, "\tVirt @: %p", cursor->clut);
}
static int cursor_dbg_show(struct seq_file *s, void *data)
{
struct drm_info_node *node = s->private;
struct sti_cursor *cursor = (struct sti_cursor *)node->info_ent->data;
seq_printf(s, "%s: (vaddr = 0x%p)",
sti_plane_to_str(&cursor->plane), cursor->regs);
DBGFS_DUMP(CUR_CTL);
DBGFS_DUMP(CUR_VPO);
cursor_dbg_vpo(s, readl(cursor->regs + CUR_VPO));
DBGFS_DUMP(CUR_PML);
cursor_dbg_pml(s, cursor, readl(cursor->regs + CUR_PML));
DBGFS_DUMP(CUR_PMP);
DBGFS_DUMP(CUR_SIZE);
cursor_dbg_size(s, readl(cursor->regs + CUR_SIZE));
DBGFS_DUMP(CUR_CML);
cursor_dbg_cml(s, cursor, readl(cursor->regs + CUR_CML));
DBGFS_DUMP(CUR_AWS);
DBGFS_DUMP(CUR_AWE);
seq_putc(s, '\n');
return 0;
}
static struct drm_info_list cursor_debugfs_files[] = {
{ "cursor", cursor_dbg_show, 0, NULL },
};
static void cursor_debugfs_init(struct sti_cursor *cursor,
struct drm_minor *minor)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(cursor_debugfs_files); i++)
cursor_debugfs_files[i].data = cursor;
drm_debugfs_create_files(cursor_debugfs_files,
ARRAY_SIZE(cursor_debugfs_files),
minor->debugfs_root, minor);
}
static void sti_cursor_argb8888_to_clut8(struct sti_cursor *cursor, u32 *src)
{
u8 *dst = cursor->pixmap.base;
unsigned int i, j;
u32 a, r, g, b;
for (i = 0; i < cursor->height; i++) {
for (j = 0; j < cursor->width; j++) {
/* Pick the 2 higher bits of each component */
a = (*src >> 30) & 3;
r = (*src >> 22) & 3;
g = (*src >> 14) & 3;
b = (*src >> 6) & 3;
*dst = a << 6 | r << 4 | g << 2 | b;
src++;
dst++;
}
}
}
static void sti_cursor_init(struct sti_cursor *cursor)
{
unsigned short *base = cursor->clut;
unsigned int a, r, g, b;
/* Assign CLUT values, ARGB444 format */
for (a = 0; a < 4; a++)
for (r = 0; r < 4; r++)
for (g = 0; g < 4; g++)
for (b = 0; b < 4; b++)
*base++ = (a * 5) << 12 |
(r * 5) << 8 |
(g * 5) << 4 |
(b * 5);
}
static int sti_cursor_atomic_check(struct drm_plane *drm_plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
drm_plane);
struct sti_plane *plane = to_sti_plane(drm_plane);
struct sti_cursor *cursor = to_sti_cursor(plane);
struct drm_crtc *crtc = new_plane_state->crtc;
struct drm_framebuffer *fb = new_plane_state->fb;
struct drm_crtc_state *crtc_state;
struct drm_display_mode *mode;
int dst_x, dst_y, dst_w, dst_h;
int src_w, src_h;
/* no need for further checks if the plane is being disabled */
if (!crtc || !fb)
return 0;
crtc_state = drm_atomic_get_crtc_state(state, crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
mode = &crtc_state->mode;
dst_x = new_plane_state->crtc_x;
dst_y = new_plane_state->crtc_y;
dst_w = clamp_val(new_plane_state->crtc_w, 0,
mode->crtc_hdisplay - dst_x);
dst_h = clamp_val(new_plane_state->crtc_h, 0,
mode->crtc_vdisplay - dst_y);
/* src_x are in 16.16 format */
src_w = new_plane_state->src_w >> 16;
src_h = new_plane_state->src_h >> 16;
if (src_w < STI_CURS_MIN_SIZE ||
src_h < STI_CURS_MIN_SIZE ||
src_w > STI_CURS_MAX_SIZE ||
src_h > STI_CURS_MAX_SIZE) {
DRM_ERROR("Invalid cursor size (%dx%d)\n",
src_w, src_h);
return -EINVAL;
}
/* If the cursor size has changed, re-allocated the pixmap */
if (!cursor->pixmap.base ||
(cursor->width != src_w) ||
(cursor->height != src_h)) {
cursor->width = src_w;
cursor->height = src_h;
if (cursor->pixmap.base)
dma_free_wc(cursor->dev, cursor->pixmap.size,
cursor->pixmap.base, cursor->pixmap.paddr);
cursor->pixmap.size = cursor->width * cursor->height;
cursor->pixmap.base = dma_alloc_wc(cursor->dev,
cursor->pixmap.size,
&cursor->pixmap.paddr,
GFP_KERNEL | GFP_DMA);
if (!cursor->pixmap.base) {
DRM_ERROR("Failed to allocate memory for pixmap\n");
return -EINVAL;
}
}
if (!drm_fb_dma_get_gem_obj(fb, 0)) {
DRM_ERROR("Can't get DMA GEM object for fb\n");
return -EINVAL;
}
DRM_DEBUG_KMS("CRTC:%d (%s) drm plane:%d (%s)\n",
crtc->base.id, sti_mixer_to_str(to_sti_mixer(crtc)),
drm_plane->base.id, sti_plane_to_str(plane));
DRM_DEBUG_KMS("(%dx%d)@(%d,%d)\n", dst_w, dst_h, dst_x, dst_y);
return 0;
}
static void sti_cursor_atomic_update(struct drm_plane *drm_plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *newstate = drm_atomic_get_new_plane_state(state,
drm_plane);
struct sti_plane *plane = to_sti_plane(drm_plane);
struct sti_cursor *cursor = to_sti_cursor(plane);
struct drm_crtc *crtc = newstate->crtc;
struct drm_framebuffer *fb = newstate->fb;
struct drm_display_mode *mode;
int dst_x, dst_y;
struct drm_gem_dma_object *dma_obj;
u32 y, x;
u32 val;
if (!crtc || !fb)
return;
mode = &crtc->mode;
dst_x = newstate->crtc_x;
dst_y = newstate->crtc_y;
dma_obj = drm_fb_dma_get_gem_obj(fb, 0);
/* Convert ARGB8888 to CLUT8 */
sti_cursor_argb8888_to_clut8(cursor, (u32 *)dma_obj->vaddr);
/* AWS and AWE depend on the mode */
y = sti_vtg_get_line_number(*mode, 0);
x = sti_vtg_get_pixel_number(*mode, 0);
val = y << 16 | x;
writel(val, cursor->regs + CUR_AWS);
y = sti_vtg_get_line_number(*mode, mode->vdisplay - 1);
x = sti_vtg_get_pixel_number(*mode, mode->hdisplay - 1);
val = y << 16 | x;
writel(val, cursor->regs + CUR_AWE);
/* Set memory location, size, and position */
writel(cursor->pixmap.paddr, cursor->regs + CUR_PML);
writel(cursor->width, cursor->regs + CUR_PMP);
writel(cursor->height << 16 | cursor->width, cursor->regs + CUR_SIZE);
y = sti_vtg_get_line_number(*mode, dst_y);
x = sti_vtg_get_pixel_number(*mode, dst_x);
writel((y << 16) | x, cursor->regs + CUR_VPO);
/* Set and fetch CLUT */
writel(cursor->clut_paddr, cursor->regs + CUR_CML);
writel(CUR_CTL_CLUT_UPDATE, cursor->regs + CUR_CTL);
sti_plane_update_fps(plane, true, false);
plane->status = STI_PLANE_UPDATED;
}
static void sti_cursor_atomic_disable(struct drm_plane *drm_plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *oldstate = drm_atomic_get_old_plane_state(state,
drm_plane);
struct sti_plane *plane = to_sti_plane(drm_plane);
if (!oldstate->crtc) {
DRM_DEBUG_DRIVER("drm plane:%d not enabled\n",
drm_plane->base.id);
return;
}
DRM_DEBUG_DRIVER("CRTC:%d (%s) drm plane:%d (%s)\n",
oldstate->crtc->base.id,
sti_mixer_to_str(to_sti_mixer(oldstate->crtc)),
drm_plane->base.id, sti_plane_to_str(plane));
plane->status = STI_PLANE_DISABLING;
}
static const struct drm_plane_helper_funcs sti_cursor_helpers_funcs = {
.atomic_check = sti_cursor_atomic_check,
.atomic_update = sti_cursor_atomic_update,
.atomic_disable = sti_cursor_atomic_disable,
};
static int sti_cursor_late_register(struct drm_plane *drm_plane)
{
struct sti_plane *plane = to_sti_plane(drm_plane);
struct sti_cursor *cursor = to_sti_cursor(plane);
cursor_debugfs_init(cursor, drm_plane->dev->primary);
return 0;
}
static const struct drm_plane_funcs sti_cursor_plane_helpers_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
.late_register = sti_cursor_late_register,
};
struct drm_plane *sti_cursor_create(struct drm_device *drm_dev,
struct device *dev, int desc,
void __iomem *baseaddr,
unsigned int possible_crtcs)
{
struct sti_cursor *cursor;
size_t size;
int res;
cursor = devm_kzalloc(dev, sizeof(*cursor), GFP_KERNEL);
if (!cursor) {
DRM_ERROR("Failed to allocate memory for cursor\n");
return NULL;
}
/* Allocate clut buffer */
size = 0x100 * sizeof(unsigned short);
cursor->clut = dma_alloc_wc(dev, size, &cursor->clut_paddr,
GFP_KERNEL | GFP_DMA);
if (!cursor->clut) {
DRM_ERROR("Failed to allocate memory for cursor clut\n");
goto err_clut;
}
cursor->dev = dev;
cursor->regs = baseaddr;
cursor->plane.desc = desc;
cursor->plane.status = STI_PLANE_DISABLED;
sti_cursor_init(cursor);
res = drm_universal_plane_init(drm_dev, &cursor->plane.drm_plane,
possible_crtcs,
&sti_cursor_plane_helpers_funcs,
cursor_supported_formats,
ARRAY_SIZE(cursor_supported_formats),
NULL, DRM_PLANE_TYPE_CURSOR, NULL);
if (res) {
DRM_ERROR("Failed to initialize universal plane\n");
goto err_plane;
}
drm_plane_helper_add(&cursor->plane.drm_plane,
&sti_cursor_helpers_funcs);
sti_plane_init_property(&cursor->plane, DRM_PLANE_TYPE_CURSOR);
return &cursor->plane.drm_plane;
err_plane:
dma_free_wc(dev, size, cursor->clut, cursor->clut_paddr);
err_clut:
devm_kfree(dev, cursor);
return NULL;
}
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2013
* Authors: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
*/
#ifndef _STI_CURSOR_H_
#define _STI_CURSOR_H_
struct drm_device;
struct device;
struct drm_plane *sti_cursor_create(struct drm_device *drm_dev,
struct device *dev, int desc,
void __iomem *baseaddr,
unsigned int possible_crtcs);
#endif
+300
View File
@@ -0,0 +1,300 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
*/
#include <linux/component.h>
#include <linux/debugfs.h>
#include <linux/dma-mapping.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <drm/clients/drm_client_setup.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_debugfs.h>
#include <drm/drm_drv.h>
#include <drm/drm_fbdev_dma.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include "sti_drv.h"
#include "sti_plane.h"
#define DRIVER_NAME "sti"
#define DRIVER_DESC "STMicroelectronics SoC DRM"
#define DRIVER_MAJOR 1
#define DRIVER_MINOR 0
#define STI_MAX_FB_HEIGHT 4096
#define STI_MAX_FB_WIDTH 4096
static int sti_drm_fps_get(void *data, u64 *val)
{
struct drm_device *drm_dev = data;
struct drm_plane *p;
unsigned int i = 0;
*val = 0;
list_for_each_entry(p, &drm_dev->mode_config.plane_list, head) {
struct sti_plane *plane = to_sti_plane(p);
*val |= plane->fps_info.output << i;
i++;
}
return 0;
}
static int sti_drm_fps_set(void *data, u64 val)
{
struct drm_device *drm_dev = data;
struct drm_plane *p;
unsigned int i = 0;
list_for_each_entry(p, &drm_dev->mode_config.plane_list, head) {
struct sti_plane *plane = to_sti_plane(p);
memset(&plane->fps_info, 0, sizeof(plane->fps_info));
plane->fps_info.output = (val >> i) & 1;
i++;
}
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(sti_drm_fps_fops,
sti_drm_fps_get, sti_drm_fps_set, "%llu\n");
static int sti_drm_fps_dbg_show(struct seq_file *s, void *data)
{
struct drm_info_node *node = s->private;
struct drm_device *dev = node->minor->dev;
struct drm_plane *p;
list_for_each_entry(p, &dev->mode_config.plane_list, head) {
struct sti_plane *plane = to_sti_plane(p);
seq_printf(s, "%s%s\n",
plane->fps_info.fps_str,
plane->fps_info.fips_str);
}
return 0;
}
static struct drm_info_list sti_drm_dbg_list[] = {
{"fps_get", sti_drm_fps_dbg_show, 0},
};
static void sti_drm_dbg_init(struct drm_minor *minor)
{
drm_debugfs_create_files(sti_drm_dbg_list,
ARRAY_SIZE(sti_drm_dbg_list),
minor->debugfs_root, minor);
debugfs_create_file("fps_show", S_IRUGO | S_IWUSR, minor->debugfs_root,
minor->dev, &sti_drm_fps_fops);
DRM_INFO("%s: debugfs installed\n", DRIVER_NAME);
}
static const struct drm_mode_config_funcs sti_mode_config_funcs = {
.fb_create = drm_gem_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
static void sti_mode_config_init(struct drm_device *dev)
{
dev->mode_config.min_width = 0;
dev->mode_config.min_height = 0;
/*
* set max width and height as default value.
* this value would be used to check framebuffer size limitation
* at drm_mode_addfb().
*/
dev->mode_config.max_width = STI_MAX_FB_WIDTH;
dev->mode_config.max_height = STI_MAX_FB_HEIGHT;
dev->mode_config.funcs = &sti_mode_config_funcs;
dev->mode_config.normalize_zpos = true;
}
DEFINE_DRM_GEM_DMA_FOPS(sti_driver_fops);
static const struct drm_driver sti_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
.fops = &sti_driver_fops,
DRM_GEM_DMA_DRIVER_OPS,
DRM_FBDEV_DMA_DRIVER_OPS,
.debugfs_init = sti_drm_dbg_init,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
};
static int sti_init(struct drm_device *ddev)
{
struct sti_private *private;
private = kzalloc_obj(*private);
if (!private)
return -ENOMEM;
ddev->dev_private = (void *)private;
dev_set_drvdata(ddev->dev, ddev);
private->drm_dev = ddev;
drm_mode_config_init(ddev);
sti_mode_config_init(ddev);
drm_kms_helper_poll_init(ddev);
return 0;
}
static void sti_cleanup(struct drm_device *ddev)
{
struct sti_private *private = ddev->dev_private;
drm_kms_helper_poll_fini(ddev);
drm_atomic_helper_shutdown(ddev);
drm_mode_config_cleanup(ddev);
component_unbind_all(ddev->dev, ddev);
dev_set_drvdata(ddev->dev, NULL);
kfree(private);
ddev->dev_private = NULL;
}
static int sti_bind(struct device *dev)
{
struct drm_device *ddev;
int ret;
ddev = drm_dev_alloc(&sti_driver, dev);
if (IS_ERR(ddev))
return PTR_ERR(ddev);
ret = sti_init(ddev);
if (ret)
goto err_drm_dev_put;
ret = component_bind_all(ddev->dev, ddev);
if (ret)
goto err_cleanup;
ret = drm_dev_register(ddev, 0);
if (ret)
goto err_cleanup;
drm_mode_config_reset(ddev);
drm_client_setup(ddev, NULL);
return 0;
err_cleanup:
sti_cleanup(ddev);
err_drm_dev_put:
drm_dev_put(ddev);
return ret;
}
static void sti_unbind(struct device *dev)
{
struct drm_device *ddev = dev_get_drvdata(dev);
drm_dev_unregister(ddev);
sti_cleanup(ddev);
drm_dev_put(ddev);
}
static const struct component_master_ops sti_ops = {
.bind = sti_bind,
.unbind = sti_unbind,
};
static int sti_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret;
ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
if (ret)
return ret;
devm_of_platform_populate(dev);
return drm_of_component_probe(dev, component_compare_of, &sti_ops);
}
static void sti_platform_remove(struct platform_device *pdev)
{
component_master_del(&pdev->dev, &sti_ops);
}
static void sti_platform_shutdown(struct platform_device *pdev)
{
drm_atomic_helper_shutdown(platform_get_drvdata(pdev));
}
static const struct of_device_id sti_dt_ids[] = {
{ .compatible = "st,sti-display-subsystem", },
{ /* end node */ },
};
MODULE_DEVICE_TABLE(of, sti_dt_ids);
static struct platform_driver sti_platform_driver = {
.probe = sti_platform_probe,
.remove = sti_platform_remove,
.shutdown = sti_platform_shutdown,
.driver = {
.name = DRIVER_NAME,
.of_match_table = sti_dt_ids,
},
};
static struct platform_driver * const drivers[] = {
&sti_tvout_driver,
&sti_hqvdp_driver,
&sti_hdmi_driver,
&sti_hda_driver,
&sti_dvo_driver,
&sti_vtg_driver,
&sti_compositor_driver,
&sti_platform_driver,
};
static int sti_drm_init(void)
{
if (drm_firmware_drivers_only())
return -ENODEV;
return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
}
module_init(sti_drm_init);
static void sti_drm_exit(void)
{
platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
}
module_exit(sti_drm_exit);
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
MODULE_LICENSE("GPL");
+38
View File
@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
*/
#ifndef _STI_DRV_H_
#define _STI_DRV_H_
#include <linux/platform_device.h>
struct drm_device;
struct drm_property;
struct sti_compositor;
/**
* STI drm private structure
* This structure is stored as private in the drm_device
*
* @compo: compositor
* @plane_zorder_property: z-order property for CRTC planes
* @drm_dev: drm device
*/
struct sti_private {
struct sti_compositor *compo;
struct drm_property *plane_zorder_property;
struct drm_device *drm_dev;
};
extern struct platform_driver sti_tvout_driver;
extern struct platform_driver sti_hqvdp_driver;
extern struct platform_driver sti_hdmi_driver;
extern struct platform_driver sti_hda_driver;
extern struct platform_driver sti_dvo_driver;
extern struct platform_driver sti_vtg_driver;
extern struct platform_driver sti_compositor_driver;
#endif
+578
View File
@@ -0,0 +1,578 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/debugfs.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_device.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include "sti_awg_utils.h"
#include "sti_drv.h"
#include "sti_mixer.h"
/* DVO registers */
#define DVO_AWG_DIGSYNC_CTRL 0x0000
#define DVO_DOF_CFG 0x0004
#define DVO_LUT_PROG_LOW 0x0008
#define DVO_LUT_PROG_MID 0x000C
#define DVO_LUT_PROG_HIGH 0x0010
#define DVO_DIGSYNC_INSTR_I 0x0100
#define DVO_AWG_CTRL_EN BIT(0)
#define DVO_AWG_FRAME_BASED_SYNC BIT(2)
#define DVO_DOF_EN_LOWBYTE BIT(0)
#define DVO_DOF_EN_MIDBYTE BIT(1)
#define DVO_DOF_EN_HIGHBYTE BIT(2)
#define DVO_DOF_EN BIT(6)
#define DVO_DOF_MOD_COUNT_SHIFT 8
#define DVO_LUT_ZERO 0
#define DVO_LUT_Y_G 1
#define DVO_LUT_Y_G_DEL 2
#define DVO_LUT_CB_B 3
#define DVO_LUT_CB_B_DEL 4
#define DVO_LUT_CR_R 5
#define DVO_LUT_CR_R_DEL 6
#define DVO_LUT_HOLD 7
struct dvo_config {
u32 flags;
u32 lowbyte;
u32 midbyte;
u32 highbyte;
int (*awg_fwgen_fct)(
struct awg_code_generation_params *fw_gen_params,
struct awg_timing *timing);
};
static struct dvo_config rgb_24bit_de_cfg = {
.flags = (0L << DVO_DOF_MOD_COUNT_SHIFT),
.lowbyte = DVO_LUT_CR_R,
.midbyte = DVO_LUT_Y_G,
.highbyte = DVO_LUT_CB_B,
.awg_fwgen_fct = sti_awg_generate_code_data_enable_mode,
};
/*
* STI digital video output structure
*
* @dev: driver device
* @drm_dev: pointer to drm device
* @mode: current display mode selected
* @regs: dvo registers
* @clk_pix: pixel clock for dvo
* @clk: clock for dvo
* @clk_main_parent: dvo parent clock if main path used
* @clk_aux_parent: dvo parent clock if aux path used
* @panel_node: panel node reference from device tree
* @panel: reference to the panel connected to the dvo
* @enabled: true if dvo is enabled else false
* @encoder: drm_encoder it is bound
*/
struct sti_dvo {
struct device dev;
struct drm_device *drm_dev;
struct drm_display_mode mode;
void __iomem *regs;
struct clk *clk_pix;
struct clk *clk;
struct clk *clk_main_parent;
struct clk *clk_aux_parent;
struct device_node *panel_node;
struct drm_panel *panel;
struct dvo_config *config;
bool enabled;
struct drm_encoder *encoder;
struct drm_bridge bridge;
};
struct sti_dvo_connector {
struct drm_connector drm_connector;
struct drm_encoder *encoder;
struct sti_dvo *dvo;
};
#define to_sti_dvo_connector(x) \
container_of(x, struct sti_dvo_connector, drm_connector)
#define BLANKING_LEVEL 16
static int dvo_awg_generate_code(struct sti_dvo *dvo, u8 *ram_size, u32 *ram_code)
{
struct drm_display_mode *mode = &dvo->mode;
struct dvo_config *config = dvo->config;
struct awg_code_generation_params fw_gen_params;
struct awg_timing timing;
fw_gen_params.ram_code = ram_code;
fw_gen_params.instruction_offset = 0;
timing.total_lines = mode->vtotal;
timing.active_lines = mode->vdisplay;
timing.blanking_lines = mode->vsync_start - mode->vdisplay;
timing.trailing_lines = mode->vtotal - mode->vsync_start;
timing.total_pixels = mode->htotal;
timing.active_pixels = mode->hdisplay;
timing.blanking_pixels = mode->hsync_start - mode->hdisplay;
timing.trailing_pixels = mode->htotal - mode->hsync_start;
timing.blanking_level = BLANKING_LEVEL;
if (config->awg_fwgen_fct(&fw_gen_params, &timing)) {
DRM_ERROR("AWG firmware not properly generated\n");
return -EINVAL;
}
*ram_size = fw_gen_params.instruction_offset;
return 0;
}
/* Configure AWG, writing instructions
*
* @dvo: pointer to DVO structure
* @awg_ram_code: pointer to AWG instructions table
* @nb: nb of AWG instructions
*/
static void dvo_awg_configure(struct sti_dvo *dvo, u32 *awg_ram_code, int nb)
{
int i;
DRM_DEBUG_DRIVER("\n");
for (i = 0; i < nb; i++)
writel(awg_ram_code[i],
dvo->regs + DVO_DIGSYNC_INSTR_I + i * 4);
for (i = nb; i < AWG_MAX_INST; i++)
writel(0, dvo->regs + DVO_DIGSYNC_INSTR_I + i * 4);
writel(DVO_AWG_CTRL_EN, dvo->regs + DVO_AWG_DIGSYNC_CTRL);
}
#define DBGFS_DUMP(reg) seq_printf(s, "\n %-25s 0x%08X", #reg, \
readl(dvo->regs + reg))
static void dvo_dbg_awg_microcode(struct seq_file *s, void __iomem *reg)
{
unsigned int i;
seq_puts(s, "\n\n");
seq_puts(s, " DVO AWG microcode:");
for (i = 0; i < AWG_MAX_INST; i++) {
if (i % 8 == 0)
seq_printf(s, "\n %04X:", i);
seq_printf(s, " %04X", readl(reg + i * 4));
}
}
static int dvo_dbg_show(struct seq_file *s, void *data)
{
struct drm_info_node *node = s->private;
struct sti_dvo *dvo = (struct sti_dvo *)node->info_ent->data;
seq_printf(s, "DVO: (vaddr = 0x%p)", dvo->regs);
DBGFS_DUMP(DVO_AWG_DIGSYNC_CTRL);
DBGFS_DUMP(DVO_DOF_CFG);
DBGFS_DUMP(DVO_LUT_PROG_LOW);
DBGFS_DUMP(DVO_LUT_PROG_MID);
DBGFS_DUMP(DVO_LUT_PROG_HIGH);
dvo_dbg_awg_microcode(s, dvo->regs + DVO_DIGSYNC_INSTR_I);
seq_putc(s, '\n');
return 0;
}
static struct drm_info_list dvo_debugfs_files[] = {
{ "dvo", dvo_dbg_show, 0, NULL },
};
static void dvo_debugfs_init(struct sti_dvo *dvo, struct drm_minor *minor)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(dvo_debugfs_files); i++)
dvo_debugfs_files[i].data = dvo;
drm_debugfs_create_files(dvo_debugfs_files,
ARRAY_SIZE(dvo_debugfs_files),
minor->debugfs_root, minor);
}
static void sti_dvo_disable(struct drm_bridge *bridge)
{
struct sti_dvo *dvo = bridge->driver_private;
if (!dvo->enabled)
return;
DRM_DEBUG_DRIVER("\n");
if (dvo->config->awg_fwgen_fct)
writel(0x00000000, dvo->regs + DVO_AWG_DIGSYNC_CTRL);
writel(0x00000000, dvo->regs + DVO_DOF_CFG);
drm_panel_disable(dvo->panel);
/* Disable/unprepare dvo clock */
clk_disable_unprepare(dvo->clk_pix);
clk_disable_unprepare(dvo->clk);
dvo->enabled = false;
}
static void sti_dvo_pre_enable(struct drm_bridge *bridge)
{
struct sti_dvo *dvo = bridge->driver_private;
struct dvo_config *config = dvo->config;
u32 val;
DRM_DEBUG_DRIVER("\n");
if (dvo->enabled)
return;
/* Make sure DVO is disabled */
writel(0x00000000, dvo->regs + DVO_DOF_CFG);
writel(0x00000000, dvo->regs + DVO_AWG_DIGSYNC_CTRL);
if (config->awg_fwgen_fct) {
u8 nb_instr;
u32 awg_ram_code[AWG_MAX_INST];
/* Configure AWG */
if (!dvo_awg_generate_code(dvo, &nb_instr, awg_ram_code))
dvo_awg_configure(dvo, awg_ram_code, nb_instr);
else
return;
}
/* Prepare/enable clocks */
if (clk_prepare_enable(dvo->clk_pix))
DRM_ERROR("Failed to prepare/enable dvo_pix clk\n");
if (clk_prepare_enable(dvo->clk))
DRM_ERROR("Failed to prepare/enable dvo clk\n");
drm_panel_enable(dvo->panel);
/* Set LUT */
writel(config->lowbyte, dvo->regs + DVO_LUT_PROG_LOW);
writel(config->midbyte, dvo->regs + DVO_LUT_PROG_MID);
writel(config->highbyte, dvo->regs + DVO_LUT_PROG_HIGH);
/* Digital output formatter config */
val = (config->flags | DVO_DOF_EN);
writel(val, dvo->regs + DVO_DOF_CFG);
dvo->enabled = true;
}
static void sti_dvo_set_mode(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
{
struct sti_dvo *dvo = bridge->driver_private;
struct sti_mixer *mixer = to_sti_mixer(dvo->encoder->crtc);
int rate = mode->clock * 1000;
struct clk *clkp;
int ret;
DRM_DEBUG_DRIVER("\n");
drm_mode_copy(&dvo->mode, mode);
/* According to the path used (main or aux), the dvo clocks should
* have a different parent clock. */
if (mixer->id == STI_MIXER_MAIN)
clkp = dvo->clk_main_parent;
else
clkp = dvo->clk_aux_parent;
if (clkp) {
clk_set_parent(dvo->clk_pix, clkp);
clk_set_parent(dvo->clk, clkp);
}
/* DVO clocks = compositor clock */
ret = clk_set_rate(dvo->clk_pix, rate);
if (ret < 0) {
DRM_ERROR("Cannot set rate (%dHz) for dvo_pix clk\n", rate);
return;
}
ret = clk_set_rate(dvo->clk, rate);
if (ret < 0) {
DRM_ERROR("Cannot set rate (%dHz) for dvo clk\n", rate);
return;
}
/* For now, we only support 24bit data enable (DE) synchro format */
dvo->config = &rgb_24bit_de_cfg;
}
static void sti_dvo_bridge_nope(struct drm_bridge *bridge)
{
/* do nothing */
}
static const struct drm_bridge_funcs sti_dvo_bridge_funcs = {
.pre_enable = sti_dvo_pre_enable,
.enable = sti_dvo_bridge_nope,
.disable = sti_dvo_disable,
.post_disable = sti_dvo_bridge_nope,
.mode_set = sti_dvo_set_mode,
};
static int sti_dvo_connector_get_modes(struct drm_connector *connector)
{
struct sti_dvo_connector *dvo_connector
= to_sti_dvo_connector(connector);
struct sti_dvo *dvo = dvo_connector->dvo;
if (dvo->panel)
return drm_panel_get_modes(dvo->panel, connector);
return 0;
}
#define CLK_TOLERANCE_HZ 50
static enum drm_mode_status
sti_dvo_connector_mode_valid(struct drm_connector *connector,
const struct drm_display_mode *mode)
{
int target = mode->clock * 1000;
int target_min = target - CLK_TOLERANCE_HZ;
int target_max = target + CLK_TOLERANCE_HZ;
int result;
struct sti_dvo_connector *dvo_connector
= to_sti_dvo_connector(connector);
struct sti_dvo *dvo = dvo_connector->dvo;
result = clk_round_rate(dvo->clk_pix, target);
DRM_DEBUG_DRIVER("target rate = %d => available rate = %d\n",
target, result);
if ((result < target_min) || (result > target_max)) {
DRM_DEBUG_DRIVER("dvo pixclk=%d not supported\n", target);
return MODE_BAD;
}
return MODE_OK;
}
static const
struct drm_connector_helper_funcs sti_dvo_connector_helper_funcs = {
.get_modes = sti_dvo_connector_get_modes,
.mode_valid = sti_dvo_connector_mode_valid,
};
static enum drm_connector_status
sti_dvo_connector_detect(struct drm_connector *connector, bool force)
{
struct sti_dvo_connector *dvo_connector
= to_sti_dvo_connector(connector);
struct sti_dvo *dvo = dvo_connector->dvo;
DRM_DEBUG_DRIVER("\n");
if (!dvo->panel) {
dvo->panel = of_drm_find_panel(dvo->panel_node);
if (IS_ERR(dvo->panel))
dvo->panel = NULL;
}
if (dvo->panel)
return connector_status_connected;
return connector_status_disconnected;
}
static int sti_dvo_late_register(struct drm_connector *connector)
{
struct sti_dvo_connector *dvo_connector
= to_sti_dvo_connector(connector);
struct sti_dvo *dvo = dvo_connector->dvo;
dvo_debugfs_init(dvo, dvo->drm_dev->primary);
return 0;
}
static const struct drm_connector_funcs sti_dvo_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = sti_dvo_connector_detect,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
.late_register = sti_dvo_late_register,
};
static struct drm_encoder *sti_dvo_find_encoder(struct drm_device *dev)
{
struct drm_encoder *encoder;
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
if (encoder->encoder_type == DRM_MODE_ENCODER_LVDS)
return encoder;
}
return NULL;
}
static int sti_dvo_bind(struct device *dev, struct device *master, void *data)
{
struct sti_dvo *dvo = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
struct drm_encoder *encoder;
struct sti_dvo_connector *connector;
struct drm_connector *drm_connector;
int err;
/* Set the drm device handle */
dvo->drm_dev = drm_dev;
encoder = sti_dvo_find_encoder(drm_dev);
if (!encoder)
return -ENOMEM;
connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL);
if (!connector)
return -ENOMEM;
connector->dvo = dvo;
dvo->bridge.driver_private = dvo;
dvo->bridge.of_node = dvo->dev.of_node;
drm_bridge_add(&dvo->bridge);
err = drm_bridge_attach(encoder, &dvo->bridge, NULL, 0);
if (err)
return err;
connector->encoder = encoder;
dvo->encoder = encoder;
drm_connector = (struct drm_connector *)connector;
drm_connector->polled = DRM_CONNECTOR_POLL_HPD;
drm_connector_init(drm_dev, drm_connector,
&sti_dvo_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
drm_connector_helper_add(drm_connector,
&sti_dvo_connector_helper_funcs);
err = drm_connector_attach_encoder(drm_connector, encoder);
if (err) {
DRM_ERROR("Failed to attach a connector to a encoder\n");
goto err_sysfs;
}
return 0;
err_sysfs:
drm_bridge_remove(&dvo->bridge);
return -EINVAL;
}
static void sti_dvo_unbind(struct device *dev,
struct device *master, void *data)
{
struct sti_dvo *dvo = dev_get_drvdata(dev);
drm_bridge_remove(&dvo->bridge);
}
static const struct component_ops sti_dvo_ops = {
.bind = sti_dvo_bind,
.unbind = sti_dvo_unbind,
};
static int sti_dvo_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sti_dvo *dvo;
struct device_node *np = dev->of_node;
DRM_INFO("%s\n", __func__);
dvo = devm_drm_bridge_alloc(dev, struct sti_dvo, bridge, &sti_dvo_bridge_funcs);
if (IS_ERR(dvo)) {
DRM_ERROR("Failed to allocate DVO\n");
return PTR_ERR(dvo);
}
dvo->dev = pdev->dev;
dvo->regs = devm_platform_ioremap_resource_byname(pdev, "dvo-reg");
if (IS_ERR(dvo->regs))
return PTR_ERR(dvo->regs);
dvo->clk_pix = devm_clk_get(dev, "dvo_pix");
if (IS_ERR(dvo->clk_pix)) {
DRM_ERROR("Cannot get dvo_pix clock\n");
return PTR_ERR(dvo->clk_pix);
}
dvo->clk = devm_clk_get(dev, "dvo");
if (IS_ERR(dvo->clk)) {
DRM_ERROR("Cannot get dvo clock\n");
return PTR_ERR(dvo->clk);
}
dvo->clk_main_parent = devm_clk_get(dev, "main_parent");
if (IS_ERR(dvo->clk_main_parent)) {
DRM_DEBUG_DRIVER("Cannot get main_parent clock\n");
dvo->clk_main_parent = NULL;
}
dvo->clk_aux_parent = devm_clk_get(dev, "aux_parent");
if (IS_ERR(dvo->clk_aux_parent)) {
DRM_DEBUG_DRIVER("Cannot get aux_parent clock\n");
dvo->clk_aux_parent = NULL;
}
dvo->panel_node = of_parse_phandle(np, "sti,panel", 0);
if (!dvo->panel_node)
DRM_ERROR("No panel associated to the dvo output\n");
of_node_put(dvo->panel_node);
platform_set_drvdata(pdev, dvo);
return component_add(&pdev->dev, &sti_dvo_ops);
}
static void sti_dvo_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sti_dvo_ops);
}
static const struct of_device_id dvo_of_match[] = {
{ .compatible = "st,stih407-dvo", },
{ /* end node */ }
};
MODULE_DEVICE_TABLE(of, dvo_of_match);
struct platform_driver sti_dvo_driver = {
.driver = {
.name = "sti-dvo",
.of_match_table = dvo_of_match,
},
.probe = sti_dvo_probe,
.remove = sti_dvo_remove,
};
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
MODULE_LICENSE("GPL");
+964
View File
@@ -0,0 +1,964 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
* Fabien Dessenne <fabien.dessenne@st.com>
* for STMicroelectronics.
*/
#include <linux/dma-mapping.h>
#include <linux/of.h>
#include <linux/seq_file.h>
#include <drm/drm_atomic.h>
#include <drm/drm_device.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_print.h>
#include "sti_compositor.h"
#include "sti_gdp.h"
#include "sti_plane.h"
#include "sti_vtg.h"
#define ALPHASWITCH BIT(6)
#define ENA_COLOR_FILL BIT(8)
#define BIGNOTLITTLE BIT(23)
#define WAIT_NEXT_VSYNC BIT(31)
/* GDP color formats */
#define GDP_RGB565 0x00
#define GDP_RGB888 0x01
#define GDP_RGB888_32 0x02
#define GDP_XBGR8888 (GDP_RGB888_32 | BIGNOTLITTLE | ALPHASWITCH)
#define GDP_ARGB8565 0x04
#define GDP_ARGB8888 0x05
#define GDP_ABGR8888 (GDP_ARGB8888 | BIGNOTLITTLE | ALPHASWITCH)
#define GDP_ARGB1555 0x06
#define GDP_ARGB4444 0x07
#define GDP2STR(fmt) { GDP_ ## fmt, #fmt }
static struct gdp_format_to_str {
int format;
char name[20];
} gdp_format_to_str[] = {
GDP2STR(RGB565),
GDP2STR(RGB888),
GDP2STR(RGB888_32),
GDP2STR(XBGR8888),
GDP2STR(ARGB8565),
GDP2STR(ARGB8888),
GDP2STR(ABGR8888),
GDP2STR(ARGB1555),
GDP2STR(ARGB4444)
};
#define GAM_GDP_CTL_OFFSET 0x00
#define GAM_GDP_AGC_OFFSET 0x04
#define GAM_GDP_VPO_OFFSET 0x0C
#define GAM_GDP_VPS_OFFSET 0x10
#define GAM_GDP_PML_OFFSET 0x14
#define GAM_GDP_PMP_OFFSET 0x18
#define GAM_GDP_SIZE_OFFSET 0x1C
#define GAM_GDP_NVN_OFFSET 0x24
#define GAM_GDP_KEY1_OFFSET 0x28
#define GAM_GDP_KEY2_OFFSET 0x2C
#define GAM_GDP_PPT_OFFSET 0x34
#define GAM_GDP_CML_OFFSET 0x3C
#define GAM_GDP_MST_OFFSET 0x68
#define GAM_GDP_ALPHARANGE_255 BIT(5)
#define GAM_GDP_AGC_FULL_RANGE 0x00808080
#define GAM_GDP_PPT_IGNORE (BIT(1) | BIT(0))
#define GAM_GDP_SIZE_MAX_WIDTH 3840
#define GAM_GDP_SIZE_MAX_HEIGHT 2160
#define GDP_NODE_NB_BANK 2
#define GDP_NODE_PER_FIELD 2
struct sti_gdp_node {
u32 gam_gdp_ctl;
u32 gam_gdp_agc;
u32 reserved1;
u32 gam_gdp_vpo;
u32 gam_gdp_vps;
u32 gam_gdp_pml;
u32 gam_gdp_pmp;
u32 gam_gdp_size;
u32 reserved2;
u32 gam_gdp_nvn;
u32 gam_gdp_key1;
u32 gam_gdp_key2;
u32 reserved3;
u32 gam_gdp_ppt;
u32 reserved4;
u32 gam_gdp_cml;
};
struct sti_gdp_node_list {
struct sti_gdp_node *top_field;
dma_addr_t top_field_paddr;
struct sti_gdp_node *btm_field;
dma_addr_t btm_field_paddr;
};
/*
* STI GDP structure
*
* @sti_plane: sti_plane structure
* @dev: driver device
* @regs: gdp registers
* @clk_pix: pixel clock for the current gdp
* @clk_main_parent: gdp parent clock if main path used
* @clk_aux_parent: gdp parent clock if aux path used
* @vtg_field_nb: callback for VTG FIELD (top or bottom) notification
* @is_curr_top: true if the current node processed is the top field
* @node_list: array of node list
* @vtg: registered vtg
*/
struct sti_gdp {
struct sti_plane plane;
struct device *dev;
void __iomem *regs;
struct clk *clk_pix;
struct clk *clk_main_parent;
struct clk *clk_aux_parent;
struct notifier_block vtg_field_nb;
bool is_curr_top;
struct sti_gdp_node_list node_list[GDP_NODE_NB_BANK];
struct sti_vtg *vtg;
};
#define to_sti_gdp(x) container_of(x, struct sti_gdp, plane)
static const uint32_t gdp_supported_formats[] = {
DRM_FORMAT_XRGB8888,
DRM_FORMAT_XBGR8888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_ABGR8888,
DRM_FORMAT_ARGB4444,
DRM_FORMAT_ARGB1555,
DRM_FORMAT_RGB565,
DRM_FORMAT_RGB888,
};
#define DBGFS_DUMP(reg) seq_printf(s, "\n %-25s 0x%08X", #reg, \
readl(gdp->regs + reg ## _OFFSET))
static void gdp_dbg_ctl(struct seq_file *s, int val)
{
int i;
seq_puts(s, "\tColor:");
for (i = 0; i < ARRAY_SIZE(gdp_format_to_str); i++) {
if (gdp_format_to_str[i].format == (val & 0x1F)) {
seq_puts(s, gdp_format_to_str[i].name);
break;
}
}
if (i == ARRAY_SIZE(gdp_format_to_str))
seq_puts(s, "<UNKNOWN>");
seq_printf(s, "\tWaitNextVsync:%d", val & WAIT_NEXT_VSYNC ? 1 : 0);
}
static void gdp_dbg_vpo(struct seq_file *s, int val)
{
seq_printf(s, "\txdo:%4d\tydo:%4d", val & 0xFFFF, (val >> 16) & 0xFFFF);
}
static void gdp_dbg_vps(struct seq_file *s, int val)
{
seq_printf(s, "\txds:%4d\tyds:%4d", val & 0xFFFF, (val >> 16) & 0xFFFF);
}
static void gdp_dbg_size(struct seq_file *s, int val)
{
seq_printf(s, "\t%d x %d", val & 0xFFFF, (val >> 16) & 0xFFFF);
}
static void gdp_dbg_nvn(struct seq_file *s, struct sti_gdp *gdp, int val)
{
void *base = NULL;
unsigned int i;
for (i = 0; i < GDP_NODE_NB_BANK; i++) {
if (gdp->node_list[i].top_field_paddr == val) {
base = gdp->node_list[i].top_field;
break;
}
if (gdp->node_list[i].btm_field_paddr == val) {
base = gdp->node_list[i].btm_field;
break;
}
}
if (base)
seq_printf(s, "\tVirt @: %p", base);
}
static void gdp_dbg_ppt(struct seq_file *s, int val)
{
if (val & GAM_GDP_PPT_IGNORE)
seq_puts(s, "\tNot displayed on mixer!");
}
static void gdp_dbg_mst(struct seq_file *s, int val)
{
if (val & 1)
seq_puts(s, "\tBUFFER UNDERFLOW!");
}
static int gdp_dbg_show(struct seq_file *s, void *data)
{
struct drm_info_node *node = s->private;
struct sti_gdp *gdp = (struct sti_gdp *)node->info_ent->data;
struct drm_plane *drm_plane = &gdp->plane.drm_plane;
struct drm_crtc *crtc;
drm_modeset_lock(&drm_plane->mutex, NULL);
crtc = drm_plane->state->crtc;
drm_modeset_unlock(&drm_plane->mutex);
seq_printf(s, "%s: (vaddr = 0x%p)",
sti_plane_to_str(&gdp->plane), gdp->regs);
DBGFS_DUMP(GAM_GDP_CTL);
gdp_dbg_ctl(s, readl(gdp->regs + GAM_GDP_CTL_OFFSET));
DBGFS_DUMP(GAM_GDP_AGC);
DBGFS_DUMP(GAM_GDP_VPO);
gdp_dbg_vpo(s, readl(gdp->regs + GAM_GDP_VPO_OFFSET));
DBGFS_DUMP(GAM_GDP_VPS);
gdp_dbg_vps(s, readl(gdp->regs + GAM_GDP_VPS_OFFSET));
DBGFS_DUMP(GAM_GDP_PML);
DBGFS_DUMP(GAM_GDP_PMP);
DBGFS_DUMP(GAM_GDP_SIZE);
gdp_dbg_size(s, readl(gdp->regs + GAM_GDP_SIZE_OFFSET));
DBGFS_DUMP(GAM_GDP_NVN);
gdp_dbg_nvn(s, gdp, readl(gdp->regs + GAM_GDP_NVN_OFFSET));
DBGFS_DUMP(GAM_GDP_KEY1);
DBGFS_DUMP(GAM_GDP_KEY2);
DBGFS_DUMP(GAM_GDP_PPT);
gdp_dbg_ppt(s, readl(gdp->regs + GAM_GDP_PPT_OFFSET));
DBGFS_DUMP(GAM_GDP_CML);
DBGFS_DUMP(GAM_GDP_MST);
gdp_dbg_mst(s, readl(gdp->regs + GAM_GDP_MST_OFFSET));
seq_puts(s, "\n\n");
if (!crtc)
seq_puts(s, " Not connected to any DRM CRTC\n");
else
seq_printf(s, " Connected to DRM CRTC #%d (%s)\n",
crtc->base.id, sti_mixer_to_str(to_sti_mixer(crtc)));
return 0;
}
static void gdp_node_dump_node(struct seq_file *s, struct sti_gdp_node *node)
{
seq_printf(s, "\t@:0x%p", node);
seq_printf(s, "\n\tCTL 0x%08X", node->gam_gdp_ctl);
gdp_dbg_ctl(s, node->gam_gdp_ctl);
seq_printf(s, "\n\tAGC 0x%08X", node->gam_gdp_agc);
seq_printf(s, "\n\tVPO 0x%08X", node->gam_gdp_vpo);
gdp_dbg_vpo(s, node->gam_gdp_vpo);
seq_printf(s, "\n\tVPS 0x%08X", node->gam_gdp_vps);
gdp_dbg_vps(s, node->gam_gdp_vps);
seq_printf(s, "\n\tPML 0x%08X", node->gam_gdp_pml);
seq_printf(s, "\n\tPMP 0x%08X", node->gam_gdp_pmp);
seq_printf(s, "\n\tSIZE 0x%08X", node->gam_gdp_size);
gdp_dbg_size(s, node->gam_gdp_size);
seq_printf(s, "\n\tNVN 0x%08X", node->gam_gdp_nvn);
seq_printf(s, "\n\tKEY1 0x%08X", node->gam_gdp_key1);
seq_printf(s, "\n\tKEY2 0x%08X", node->gam_gdp_key2);
seq_printf(s, "\n\tPPT 0x%08X", node->gam_gdp_ppt);
gdp_dbg_ppt(s, node->gam_gdp_ppt);
seq_printf(s, "\n\tCML 0x%08X\n", node->gam_gdp_cml);
}
static int gdp_node_dbg_show(struct seq_file *s, void *arg)
{
struct drm_info_node *node = s->private;
struct sti_gdp *gdp = (struct sti_gdp *)node->info_ent->data;
unsigned int b;
for (b = 0; b < GDP_NODE_NB_BANK; b++) {
seq_printf(s, "\n%s[%d].top", sti_plane_to_str(&gdp->plane), b);
gdp_node_dump_node(s, gdp->node_list[b].top_field);
seq_printf(s, "\n%s[%d].btm", sti_plane_to_str(&gdp->plane), b);
gdp_node_dump_node(s, gdp->node_list[b].btm_field);
}
return 0;
}
static struct drm_info_list gdp0_debugfs_files[] = {
{ "gdp0", gdp_dbg_show, 0, NULL },
{ "gdp0_node", gdp_node_dbg_show, 0, NULL },
};
static struct drm_info_list gdp1_debugfs_files[] = {
{ "gdp1", gdp_dbg_show, 0, NULL },
{ "gdp1_node", gdp_node_dbg_show, 0, NULL },
};
static struct drm_info_list gdp2_debugfs_files[] = {
{ "gdp2", gdp_dbg_show, 0, NULL },
{ "gdp2_node", gdp_node_dbg_show, 0, NULL },
};
static struct drm_info_list gdp3_debugfs_files[] = {
{ "gdp3", gdp_dbg_show, 0, NULL },
{ "gdp3_node", gdp_node_dbg_show, 0, NULL },
};
static int gdp_debugfs_init(struct sti_gdp *gdp, struct drm_minor *minor)
{
unsigned int i;
struct drm_info_list *gdp_debugfs_files;
int nb_files;
switch (gdp->plane.desc) {
case STI_GDP_0:
gdp_debugfs_files = gdp0_debugfs_files;
nb_files = ARRAY_SIZE(gdp0_debugfs_files);
break;
case STI_GDP_1:
gdp_debugfs_files = gdp1_debugfs_files;
nb_files = ARRAY_SIZE(gdp1_debugfs_files);
break;
case STI_GDP_2:
gdp_debugfs_files = gdp2_debugfs_files;
nb_files = ARRAY_SIZE(gdp2_debugfs_files);
break;
case STI_GDP_3:
gdp_debugfs_files = gdp3_debugfs_files;
nb_files = ARRAY_SIZE(gdp3_debugfs_files);
break;
default:
return -EINVAL;
}
for (i = 0; i < nb_files; i++)
gdp_debugfs_files[i].data = gdp;
drm_debugfs_create_files(gdp_debugfs_files,
nb_files,
minor->debugfs_root, minor);
return 0;
}
static int sti_gdp_fourcc2format(int fourcc)
{
switch (fourcc) {
case DRM_FORMAT_XRGB8888:
return GDP_RGB888_32;
case DRM_FORMAT_XBGR8888:
return GDP_XBGR8888;
case DRM_FORMAT_ARGB8888:
return GDP_ARGB8888;
case DRM_FORMAT_ABGR8888:
return GDP_ABGR8888;
case DRM_FORMAT_ARGB4444:
return GDP_ARGB4444;
case DRM_FORMAT_ARGB1555:
return GDP_ARGB1555;
case DRM_FORMAT_RGB565:
return GDP_RGB565;
case DRM_FORMAT_RGB888:
return GDP_RGB888;
}
return -1;
}
static int sti_gdp_get_alpharange(int format)
{
switch (format) {
case GDP_ARGB8565:
case GDP_ARGB8888:
case GDP_ABGR8888:
return GAM_GDP_ALPHARANGE_255;
}
return 0;
}
/**
* sti_gdp_get_free_nodes
* @gdp: gdp pointer
*
* Look for a GDP node list that is not currently read by the HW.
*
* RETURNS:
* Pointer to the free GDP node list
*/
static struct sti_gdp_node_list *sti_gdp_get_free_nodes(struct sti_gdp *gdp)
{
int hw_nvn;
unsigned int i;
hw_nvn = readl(gdp->regs + GAM_GDP_NVN_OFFSET);
if (!hw_nvn)
goto end;
for (i = 0; i < GDP_NODE_NB_BANK; i++)
if ((hw_nvn != gdp->node_list[i].btm_field_paddr) &&
(hw_nvn != gdp->node_list[i].top_field_paddr))
return &gdp->node_list[i];
/* in hazardous cases restart with the first node */
DRM_ERROR("inconsistent NVN for %s: 0x%08X\n",
sti_plane_to_str(&gdp->plane), hw_nvn);
end:
return &gdp->node_list[0];
}
/**
* sti_gdp_get_current_nodes
* @gdp: gdp pointer
*
* Look for GDP nodes that are currently read by the HW.
*
* RETURNS:
* Pointer to the current GDP node list
*/
static
struct sti_gdp_node_list *sti_gdp_get_current_nodes(struct sti_gdp *gdp)
{
int hw_nvn;
unsigned int i;
hw_nvn = readl(gdp->regs + GAM_GDP_NVN_OFFSET);
if (!hw_nvn)
goto end;
for (i = 0; i < GDP_NODE_NB_BANK; i++)
if ((hw_nvn == gdp->node_list[i].btm_field_paddr) ||
(hw_nvn == gdp->node_list[i].top_field_paddr))
return &gdp->node_list[i];
end:
DRM_DEBUG_DRIVER("Warning, NVN 0x%08X for %s does not match any node\n",
hw_nvn, sti_plane_to_str(&gdp->plane));
return NULL;
}
/**
* sti_gdp_disable
* @gdp: gdp pointer
*
* Disable a GDP.
*/
static void sti_gdp_disable(struct sti_gdp *gdp)
{
unsigned int i;
DRM_DEBUG_DRIVER("%s\n", sti_plane_to_str(&gdp->plane));
/* Set the nodes as 'to be ignored on mixer' */
for (i = 0; i < GDP_NODE_NB_BANK; i++) {
gdp->node_list[i].top_field->gam_gdp_ppt |= GAM_GDP_PPT_IGNORE;
gdp->node_list[i].btm_field->gam_gdp_ppt |= GAM_GDP_PPT_IGNORE;
}
if (sti_vtg_unregister_client(gdp->vtg, &gdp->vtg_field_nb))
DRM_DEBUG_DRIVER("Warning: cannot unregister VTG notifier\n");
if (gdp->clk_pix)
clk_disable_unprepare(gdp->clk_pix);
gdp->plane.status = STI_PLANE_DISABLED;
gdp->vtg = NULL;
}
/**
* sti_gdp_field_cb
* @nb: notifier block
* @event: event message
* @data: private data
*
* Handle VTG top field and bottom field event.
*
* RETURNS:
* 0 on success.
*/
static int sti_gdp_field_cb(struct notifier_block *nb,
unsigned long event, void *data)
{
struct sti_gdp *gdp = container_of(nb, struct sti_gdp, vtg_field_nb);
if (gdp->plane.status == STI_PLANE_FLUSHING) {
/* disable need to be synchronize on vsync event */
DRM_DEBUG_DRIVER("Vsync event received => disable %s\n",
sti_plane_to_str(&gdp->plane));
sti_gdp_disable(gdp);
}
switch (event) {
case VTG_TOP_FIELD_EVENT:
gdp->is_curr_top = true;
break;
case VTG_BOTTOM_FIELD_EVENT:
gdp->is_curr_top = false;
break;
default:
DRM_ERROR("unsupported event: %lu\n", event);
break;
}
return 0;
}
static void sti_gdp_init(struct sti_gdp *gdp)
{
struct device_node *np = gdp->dev->of_node;
dma_addr_t dma_addr;
void *base;
unsigned int i, size;
/* Allocate all the nodes within a single memory page */
size = sizeof(struct sti_gdp_node) *
GDP_NODE_PER_FIELD * GDP_NODE_NB_BANK;
base = dma_alloc_wc(gdp->dev, size, &dma_addr, GFP_KERNEL);
if (!base) {
DRM_ERROR("Failed to allocate memory for GDP node\n");
return;
}
memset(base, 0, size);
for (i = 0; i < GDP_NODE_NB_BANK; i++) {
if (dma_addr & 0xF) {
DRM_ERROR("Mem alignment failed\n");
return;
}
gdp->node_list[i].top_field = base;
gdp->node_list[i].top_field_paddr = dma_addr;
DRM_DEBUG_DRIVER("node[%d].top_field=%p\n", i, base);
base += sizeof(struct sti_gdp_node);
dma_addr += sizeof(struct sti_gdp_node);
if (dma_addr & 0xF) {
DRM_ERROR("Mem alignment failed\n");
return;
}
gdp->node_list[i].btm_field = base;
gdp->node_list[i].btm_field_paddr = dma_addr;
DRM_DEBUG_DRIVER("node[%d].btm_field=%p\n", i, base);
base += sizeof(struct sti_gdp_node);
dma_addr += sizeof(struct sti_gdp_node);
}
if (of_device_is_compatible(np, "st,stih407-compositor")) {
/* GDP of STiH407 chip have its own pixel clock */
char *clk_name;
switch (gdp->plane.desc) {
case STI_GDP_0:
clk_name = "pix_gdp1";
break;
case STI_GDP_1:
clk_name = "pix_gdp2";
break;
case STI_GDP_2:
clk_name = "pix_gdp3";
break;
case STI_GDP_3:
clk_name = "pix_gdp4";
break;
default:
DRM_ERROR("GDP id not recognized\n");
return;
}
gdp->clk_pix = devm_clk_get(gdp->dev, clk_name);
if (IS_ERR(gdp->clk_pix))
DRM_ERROR("Cannot get %s clock\n", clk_name);
gdp->clk_main_parent = devm_clk_get(gdp->dev, "main_parent");
if (IS_ERR(gdp->clk_main_parent))
DRM_ERROR("Cannot get main_parent clock\n");
gdp->clk_aux_parent = devm_clk_get(gdp->dev, "aux_parent");
if (IS_ERR(gdp->clk_aux_parent))
DRM_ERROR("Cannot get aux_parent clock\n");
}
}
/**
* sti_gdp_get_dst
* @dev: device
* @dst: requested destination size
* @src: source size
*
* Return the cropped / clamped destination size
*
* RETURNS:
* cropped / clamped destination size
*/
static int sti_gdp_get_dst(struct device *dev, int dst, int src)
{
if (dst == src)
return dst;
if (dst < src) {
dev_dbg(dev, "WARNING: GDP scale not supported, will crop\n");
return dst;
}
dev_dbg(dev, "WARNING: GDP scale not supported, will clamp\n");
return src;
}
static int sti_gdp_atomic_check(struct drm_plane *drm_plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
drm_plane);
struct sti_plane *plane = to_sti_plane(drm_plane);
struct sti_gdp *gdp = to_sti_gdp(plane);
struct drm_crtc *crtc = new_plane_state->crtc;
struct drm_framebuffer *fb = new_plane_state->fb;
struct drm_crtc_state *crtc_state;
struct sti_mixer *mixer;
struct drm_display_mode *mode;
int dst_x, dst_y, dst_w, dst_h;
int src_x, src_y, src_w, src_h;
int format;
/* no need for further checks if the plane is being disabled */
if (!crtc || !fb)
return 0;
mixer = to_sti_mixer(crtc);
crtc_state = drm_atomic_get_crtc_state(state, crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
mode = &crtc_state->mode;
dst_x = new_plane_state->crtc_x;
dst_y = new_plane_state->crtc_y;
dst_w = clamp_val(new_plane_state->crtc_w, 0, mode->hdisplay - dst_x);
dst_h = clamp_val(new_plane_state->crtc_h, 0, mode->vdisplay - dst_y);
/* src_x are in 16.16 format */
src_x = new_plane_state->src_x >> 16;
src_y = new_plane_state->src_y >> 16;
src_w = clamp_val(new_plane_state->src_w >> 16, 0,
GAM_GDP_SIZE_MAX_WIDTH);
src_h = clamp_val(new_plane_state->src_h >> 16, 0,
GAM_GDP_SIZE_MAX_HEIGHT);
format = sti_gdp_fourcc2format(fb->format->format);
if (format == -1) {
DRM_ERROR("Format not supported by GDP %.4s\n",
(char *)&fb->format->format);
return -EINVAL;
}
if (!drm_fb_dma_get_gem_obj(fb, 0)) {
DRM_ERROR("Can't get DMA GEM object for fb\n");
return -EINVAL;
}
/* Set gdp clock */
if (mode->clock && gdp->clk_pix) {
struct clk *clkp;
int rate = mode->clock * 1000;
int res;
/*
* According to the mixer used, the gdp pixel clock
* should have a different parent clock.
*/
if (mixer->id == STI_MIXER_MAIN)
clkp = gdp->clk_main_parent;
else
clkp = gdp->clk_aux_parent;
if (clkp)
clk_set_parent(gdp->clk_pix, clkp);
res = clk_set_rate(gdp->clk_pix, rate);
if (res < 0) {
DRM_ERROR("Cannot set rate (%dHz) for gdp\n",
rate);
return -EINVAL;
}
}
DRM_DEBUG_KMS("CRTC:%d (%s) drm plane:%d (%s)\n",
crtc->base.id, sti_mixer_to_str(mixer),
drm_plane->base.id, sti_plane_to_str(plane));
DRM_DEBUG_KMS("%s dst=(%dx%d)@(%d,%d) - src=(%dx%d)@(%d,%d)\n",
sti_plane_to_str(plane),
dst_w, dst_h, dst_x, dst_y,
src_w, src_h, src_x, src_y);
return 0;
}
static void sti_gdp_atomic_update(struct drm_plane *drm_plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *oldstate = drm_atomic_get_old_plane_state(state,
drm_plane);
struct drm_plane_state *newstate = drm_atomic_get_new_plane_state(state,
drm_plane);
struct sti_plane *plane = to_sti_plane(drm_plane);
struct sti_gdp *gdp = to_sti_gdp(plane);
struct drm_crtc *crtc = newstate->crtc;
struct drm_framebuffer *fb = newstate->fb;
struct drm_display_mode *mode;
int dst_x, dst_y, dst_w, dst_h;
int src_x, src_y, src_w, src_h;
struct drm_gem_dma_object *dma_obj;
struct sti_gdp_node_list *list;
struct sti_gdp_node_list *curr_list;
struct sti_gdp_node *top_field, *btm_field;
u32 dma_updated_top;
u32 dma_updated_btm;
int format;
unsigned int bpp;
u32 ydo, xdo, yds, xds;
if (!crtc || !fb)
return;
if ((oldstate->fb == newstate->fb) &&
(oldstate->crtc_x == newstate->crtc_x) &&
(oldstate->crtc_y == newstate->crtc_y) &&
(oldstate->crtc_w == newstate->crtc_w) &&
(oldstate->crtc_h == newstate->crtc_h) &&
(oldstate->src_x == newstate->src_x) &&
(oldstate->src_y == newstate->src_y) &&
(oldstate->src_w == newstate->src_w) &&
(oldstate->src_h == newstate->src_h)) {
/* No change since last update, do not post cmd */
DRM_DEBUG_DRIVER("No change, not posting cmd\n");
plane->status = STI_PLANE_UPDATED;
return;
}
if (!gdp->vtg) {
struct sti_compositor *compo = dev_get_drvdata(gdp->dev);
struct sti_mixer *mixer = to_sti_mixer(crtc);
/* Register gdp callback */
gdp->vtg = compo->vtg[mixer->id];
sti_vtg_register_client(gdp->vtg, &gdp->vtg_field_nb, crtc);
clk_prepare_enable(gdp->clk_pix);
}
mode = &crtc->mode;
dst_x = newstate->crtc_x;
dst_y = newstate->crtc_y;
dst_w = clamp_val(newstate->crtc_w, 0, mode->hdisplay - dst_x);
dst_h = clamp_val(newstate->crtc_h, 0, mode->vdisplay - dst_y);
/* src_x are in 16.16 format */
src_x = newstate->src_x >> 16;
src_y = newstate->src_y >> 16;
src_w = clamp_val(newstate->src_w >> 16, 0, GAM_GDP_SIZE_MAX_WIDTH);
src_h = clamp_val(newstate->src_h >> 16, 0, GAM_GDP_SIZE_MAX_HEIGHT);
list = sti_gdp_get_free_nodes(gdp);
top_field = list->top_field;
btm_field = list->btm_field;
dev_dbg(gdp->dev, "%s %s top_node:0x%p btm_node:0x%p\n", __func__,
sti_plane_to_str(plane), top_field, btm_field);
/* build the top field */
top_field->gam_gdp_agc = GAM_GDP_AGC_FULL_RANGE;
top_field->gam_gdp_ctl = WAIT_NEXT_VSYNC;
format = sti_gdp_fourcc2format(fb->format->format);
top_field->gam_gdp_ctl |= format;
top_field->gam_gdp_ctl |= sti_gdp_get_alpharange(format);
top_field->gam_gdp_ppt &= ~GAM_GDP_PPT_IGNORE;
dma_obj = drm_fb_dma_get_gem_obj(fb, 0);
DRM_DEBUG_DRIVER("drm FB:%d format:%.4s phys@:0x%lx\n", fb->base.id,
(char *)&fb->format->format,
(unsigned long) dma_obj->dma_addr);
/* pixel memory location */
bpp = fb->format->cpp[0];
top_field->gam_gdp_pml = (u32) dma_obj->dma_addr + fb->offsets[0];
top_field->gam_gdp_pml += src_x * bpp;
top_field->gam_gdp_pml += src_y * fb->pitches[0];
/* output parameters (clamped / cropped) */
dst_w = sti_gdp_get_dst(gdp->dev, dst_w, src_w);
dst_h = sti_gdp_get_dst(gdp->dev, dst_h, src_h);
ydo = sti_vtg_get_line_number(*mode, dst_y);
yds = sti_vtg_get_line_number(*mode, dst_y + dst_h - 1);
xdo = sti_vtg_get_pixel_number(*mode, dst_x);
xds = sti_vtg_get_pixel_number(*mode, dst_x + dst_w - 1);
top_field->gam_gdp_vpo = (ydo << 16) | xdo;
top_field->gam_gdp_vps = (yds << 16) | xds;
/* input parameters */
src_w = dst_w;
top_field->gam_gdp_pmp = fb->pitches[0];
top_field->gam_gdp_size = src_h << 16 | src_w;
/* Same content and chained together */
memcpy(btm_field, top_field, sizeof(*btm_field));
top_field->gam_gdp_nvn = list->btm_field_paddr;
btm_field->gam_gdp_nvn = list->top_field_paddr;
/* Interlaced mode */
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
btm_field->gam_gdp_pml = top_field->gam_gdp_pml +
fb->pitches[0];
/* Update the NVN field of the 'right' field of the current GDP node
* (being used by the HW) with the address of the updated ('free') top
* field GDP node.
* - In interlaced mode the 'right' field is the bottom field as we
* update frames starting from their top field
* - In progressive mode, we update both bottom and top fields which
* are equal nodes.
* At the next VSYNC, the updated node list will be used by the HW.
*/
curr_list = sti_gdp_get_current_nodes(gdp);
dma_updated_top = list->top_field_paddr;
dma_updated_btm = list->btm_field_paddr;
dev_dbg(gdp->dev, "Current NVN:0x%X\n",
readl(gdp->regs + GAM_GDP_NVN_OFFSET));
dev_dbg(gdp->dev, "Posted buff: %lx current buff: %x\n",
(unsigned long) dma_obj->dma_addr,
readl(gdp->regs + GAM_GDP_PML_OFFSET));
if (!curr_list) {
/* First update or invalid node should directly write in the
* hw register */
DRM_DEBUG_DRIVER("%s first update (or invalid node)\n",
sti_plane_to_str(plane));
writel(gdp->is_curr_top ?
dma_updated_btm : dma_updated_top,
gdp->regs + GAM_GDP_NVN_OFFSET);
goto end;
}
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
if (gdp->is_curr_top) {
/* Do not update in the middle of the frame, but
* postpone the update after the bottom field has
* been displayed */
curr_list->btm_field->gam_gdp_nvn = dma_updated_top;
} else {
/* Direct update to avoid one frame delay */
writel(dma_updated_top,
gdp->regs + GAM_GDP_NVN_OFFSET);
}
} else {
/* Direct update for progressive to avoid one frame delay */
writel(dma_updated_top, gdp->regs + GAM_GDP_NVN_OFFSET);
}
end:
sti_plane_update_fps(plane, true, false);
plane->status = STI_PLANE_UPDATED;
}
static void sti_gdp_atomic_disable(struct drm_plane *drm_plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *oldstate = drm_atomic_get_old_plane_state(state,
drm_plane);
struct sti_plane *plane = to_sti_plane(drm_plane);
if (!oldstate->crtc) {
DRM_DEBUG_DRIVER("drm plane:%d not enabled\n",
drm_plane->base.id);
return;
}
DRM_DEBUG_DRIVER("CRTC:%d (%s) drm plane:%d (%s)\n",
oldstate->crtc->base.id,
sti_mixer_to_str(to_sti_mixer(oldstate->crtc)),
drm_plane->base.id, sti_plane_to_str(plane));
plane->status = STI_PLANE_DISABLING;
}
static const struct drm_plane_helper_funcs sti_gdp_helpers_funcs = {
.atomic_check = sti_gdp_atomic_check,
.atomic_update = sti_gdp_atomic_update,
.atomic_disable = sti_gdp_atomic_disable,
};
static int sti_gdp_late_register(struct drm_plane *drm_plane)
{
struct sti_plane *plane = to_sti_plane(drm_plane);
struct sti_gdp *gdp = to_sti_gdp(plane);
return gdp_debugfs_init(gdp, drm_plane->dev->primary);
}
static const struct drm_plane_funcs sti_gdp_plane_helpers_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
.late_register = sti_gdp_late_register,
};
struct drm_plane *sti_gdp_create(struct drm_device *drm_dev,
struct device *dev, int desc,
void __iomem *baseaddr,
unsigned int possible_crtcs,
enum drm_plane_type type)
{
struct sti_gdp *gdp;
int res;
gdp = devm_kzalloc(dev, sizeof(*gdp), GFP_KERNEL);
if (!gdp) {
DRM_ERROR("Failed to allocate memory for GDP\n");
return NULL;
}
gdp->dev = dev;
gdp->regs = baseaddr;
gdp->plane.desc = desc;
gdp->plane.status = STI_PLANE_DISABLED;
gdp->vtg_field_nb.notifier_call = sti_gdp_field_cb;
sti_gdp_init(gdp);
res = drm_universal_plane_init(drm_dev, &gdp->plane.drm_plane,
possible_crtcs,
&sti_gdp_plane_helpers_funcs,
gdp_supported_formats,
ARRAY_SIZE(gdp_supported_formats),
NULL, type, NULL);
if (res) {
DRM_ERROR("Failed to initialize universal plane\n");
goto err;
}
drm_plane_helper_add(&gdp->plane.drm_plane, &sti_gdp_helpers_funcs);
sti_plane_init_property(&gdp->plane, type);
return &gdp->plane.drm_plane;
err:
devm_kfree(dev, gdp);
return NULL;
}
+24
View File
@@ -0,0 +1,24 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
* Fabien Dessenne <fabien.dessenne@st.com>
* for STMicroelectronics.
*/
#ifndef _STI_GDP_H_
#define _STI_GDP_H_
#include <linux/types.h>
#include <drm/drm_plane.h>
struct drm_device;
struct device;
struct drm_plane *sti_gdp_create(struct drm_device *drm_dev,
struct device *dev, int desc,
void __iomem *baseaddr,
unsigned int possible_crtcs,
enum drm_plane_type type);
#endif
+814
View File
@@ -0,0 +1,814 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_debugfs.h>
#include <drm/drm_device.h>
#include <drm/drm_file.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
/* HDformatter registers */
#define HDA_ANA_CFG 0x0000
#define HDA_ANA_SCALE_CTRL_Y 0x0004
#define HDA_ANA_SCALE_CTRL_CB 0x0008
#define HDA_ANA_SCALE_CTRL_CR 0x000C
#define HDA_ANA_ANC_CTRL 0x0010
#define HDA_ANA_SRC_Y_CFG 0x0014
#define HDA_COEFF_Y_PH1_TAP123 0x0018
#define HDA_COEFF_Y_PH1_TAP456 0x001C
#define HDA_COEFF_Y_PH2_TAP123 0x0020
#define HDA_COEFF_Y_PH2_TAP456 0x0024
#define HDA_COEFF_Y_PH3_TAP123 0x0028
#define HDA_COEFF_Y_PH3_TAP456 0x002C
#define HDA_COEFF_Y_PH4_TAP123 0x0030
#define HDA_COEFF_Y_PH4_TAP456 0x0034
#define HDA_ANA_SRC_C_CFG 0x0040
#define HDA_COEFF_C_PH1_TAP123 0x0044
#define HDA_COEFF_C_PH1_TAP456 0x0048
#define HDA_COEFF_C_PH2_TAP123 0x004C
#define HDA_COEFF_C_PH2_TAP456 0x0050
#define HDA_COEFF_C_PH3_TAP123 0x0054
#define HDA_COEFF_C_PH3_TAP456 0x0058
#define HDA_COEFF_C_PH4_TAP123 0x005C
#define HDA_COEFF_C_PH4_TAP456 0x0060
#define HDA_SYNC_AWGI 0x0300
/* HDA_ANA_CFG */
#define CFG_AWG_ASYNC_EN BIT(0)
#define CFG_AWG_ASYNC_HSYNC_MTD BIT(1)
#define CFG_AWG_ASYNC_VSYNC_MTD BIT(2)
#define CFG_AWG_SYNC_DEL BIT(3)
#define CFG_AWG_FLTR_MODE_SHIFT 4
#define CFG_AWG_FLTR_MODE_MASK (0xF << CFG_AWG_FLTR_MODE_SHIFT)
#define CFG_AWG_FLTR_MODE_SD (0 << CFG_AWG_FLTR_MODE_SHIFT)
#define CFG_AWG_FLTR_MODE_ED (1 << CFG_AWG_FLTR_MODE_SHIFT)
#define CFG_AWG_FLTR_MODE_HD (2 << CFG_AWG_FLTR_MODE_SHIFT)
#define CFG_SYNC_ON_PBPR_MASK BIT(8)
#define CFG_PREFILTER_EN_MASK BIT(9)
#define CFG_PBPR_SYNC_OFF_SHIFT 16
#define CFG_PBPR_SYNC_OFF_MASK (0x7FF << CFG_PBPR_SYNC_OFF_SHIFT)
#define CFG_PBPR_SYNC_OFF_VAL 0x117 /* Voltage dependent. stiH416 */
/* Default scaling values */
#define SCALE_CTRL_Y_DFLT 0x00C50256
#define SCALE_CTRL_CB_DFLT 0x00DB0249
#define SCALE_CTRL_CR_DFLT 0x00DB0249
/* Video DACs control */
#define DAC_CFG_HD_HZUVW_OFF_MASK BIT(1)
/* Upsampler values for the alternative 2X Filter */
#define SAMPLER_COEF_NB 8
#define HDA_ANA_SRC_Y_CFG_ALT_2X 0x01130000
static u32 coef_y_alt_2x[] = {
0x00FE83FB, 0x1F900401, 0x00000000, 0x00000000,
0x00F408F9, 0x055F7C25, 0x00000000, 0x00000000
};
#define HDA_ANA_SRC_C_CFG_ALT_2X 0x01750004
static u32 coef_c_alt_2x[] = {
0x001305F7, 0x05274BD0, 0x00000000, 0x00000000,
0x0004907C, 0x09C80B9D, 0x00000000, 0x00000000
};
/* Upsampler values for the 4X Filter */
#define HDA_ANA_SRC_Y_CFG_4X 0x01ED0005
#define HDA_ANA_SRC_C_CFG_4X 0x01ED0004
static u32 coef_yc_4x[] = {
0x00FC827F, 0x008FE20B, 0x00F684FC, 0x050F7C24,
0x00F4857C, 0x0A1F402E, 0x00FA027F, 0x0E076E1D
};
/* AWG instructions for some video modes */
#define AWG_MAX_INST 64
/* 720p@50 */
static u32 AWGi_720p_50[] = {
0x00000971, 0x00000C26, 0x0000013B, 0x00000CDA,
0x00000104, 0x00000E7E, 0x00000E7F, 0x0000013B,
0x00000D8E, 0x00000104, 0x00001804, 0x00000971,
0x00000C26, 0x0000003B, 0x00000FB4, 0x00000FB5,
0x00000104, 0x00001AE8
};
#define NN_720p_50 ARRAY_SIZE(AWGi_720p_50)
/* 720p@60 */
static u32 AWGi_720p_60[] = {
0x00000971, 0x00000C26, 0x0000013B, 0x00000CDA,
0x00000104, 0x00000E7E, 0x00000E7F, 0x0000013B,
0x00000C44, 0x00000104, 0x00001804, 0x00000971,
0x00000C26, 0x0000003B, 0x00000F0F, 0x00000F10,
0x00000104, 0x00001AE8
};
#define NN_720p_60 ARRAY_SIZE(AWGi_720p_60)
/* 1080p@30 */
static u32 AWGi_1080p_30[] = {
0x00000971, 0x00000C2A, 0x0000013B, 0x00000C56,
0x00000104, 0x00000FDC, 0x00000FDD, 0x0000013B,
0x00000C2A, 0x00000104, 0x00001804, 0x00000971,
0x00000C2A, 0x0000003B, 0x00000EBE, 0x00000EBF,
0x00000EBF, 0x00000104, 0x00001A2F, 0x00001C4B,
0x00001C52
};
#define NN_1080p_30 ARRAY_SIZE(AWGi_1080p_30)
/* 1080p@25 */
static u32 AWGi_1080p_25[] = {
0x00000971, 0x00000C2A, 0x0000013B, 0x00000C56,
0x00000104, 0x00000FDC, 0x00000FDD, 0x0000013B,
0x00000DE2, 0x00000104, 0x00001804, 0x00000971,
0x00000C2A, 0x0000003B, 0x00000F51, 0x00000F51,
0x00000F52, 0x00000104, 0x00001A2F, 0x00001C4B,
0x00001C52
};
#define NN_1080p_25 ARRAY_SIZE(AWGi_1080p_25)
/* 1080p@24 */
static u32 AWGi_1080p_24[] = {
0x00000971, 0x00000C2A, 0x0000013B, 0x00000C56,
0x00000104, 0x00000FDC, 0x00000FDD, 0x0000013B,
0x00000E50, 0x00000104, 0x00001804, 0x00000971,
0x00000C2A, 0x0000003B, 0x00000F76, 0x00000F76,
0x00000F76, 0x00000104, 0x00001A2F, 0x00001C4B,
0x00001C52
};
#define NN_1080p_24 ARRAY_SIZE(AWGi_1080p_24)
/* 720x480p@60 */
static u32 AWGi_720x480p_60[] = {
0x00000904, 0x00000F18, 0x0000013B, 0x00001805,
0x00000904, 0x00000C3D, 0x0000003B, 0x00001A06
};
#define NN_720x480p_60 ARRAY_SIZE(AWGi_720x480p_60)
/* Video mode category */
enum sti_hda_vid_cat {
VID_SD,
VID_ED,
VID_HD_74M,
VID_HD_148M
};
struct sti_hda_video_config {
struct drm_display_mode mode;
u32 *awg_instr;
int nb_instr;
enum sti_hda_vid_cat vid_cat;
};
/* HD analog supported modes
* Interlaced modes may be added when supported by the whole display chain
*/
static const struct sti_hda_video_config hda_supported_modes[] = {
/* 1080p30 74.250Mhz */
{{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008,
2052, 2200, 0, 1080, 1084, 1089, 1125, 0,
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)},
AWGi_1080p_30, NN_1080p_30, VID_HD_74M},
/* 1080p30 74.176Mhz */
{{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74176, 1920, 2008,
2052, 2200, 0, 1080, 1084, 1089, 1125, 0,
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)},
AWGi_1080p_30, NN_1080p_30, VID_HD_74M},
/* 1080p24 74.250Mhz */
{{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2558,
2602, 2750, 0, 1080, 1084, 1089, 1125, 0,
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)},
AWGi_1080p_24, NN_1080p_24, VID_HD_74M},
/* 1080p24 74.176Mhz */
{{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74176, 1920, 2558,
2602, 2750, 0, 1080, 1084, 1089, 1125, 0,
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)},
AWGi_1080p_24, NN_1080p_24, VID_HD_74M},
/* 1080p25 74.250Mhz */
{{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448,
2492, 2640, 0, 1080, 1084, 1089, 1125, 0,
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)},
AWGi_1080p_25, NN_1080p_25, VID_HD_74M},
/* 720p60 74.250Mhz */
{{DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390,
1430, 1650, 0, 720, 725, 730, 750, 0,
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)},
AWGi_720p_60, NN_720p_60, VID_HD_74M},
/* 720p60 74.176Mhz */
{{DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74176, 1280, 1390,
1430, 1650, 0, 720, 725, 730, 750, 0,
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)},
AWGi_720p_60, NN_720p_60, VID_HD_74M},
/* 720p50 74.250Mhz */
{{DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1720,
1760, 1980, 0, 720, 725, 730, 750, 0,
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)},
AWGi_720p_50, NN_720p_50, VID_HD_74M},
/* 720x480p60 27.027Mhz */
{{DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27027, 720, 736,
798, 858, 0, 480, 489, 495, 525, 0,
DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC)},
AWGi_720x480p_60, NN_720x480p_60, VID_ED},
/* 720x480p60 27.000Mhz */
{{DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736,
798, 858, 0, 480, 489, 495, 525, 0,
DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC)},
AWGi_720x480p_60, NN_720x480p_60, VID_ED}
};
/*
* STI hd analog structure
*
* @dev: driver device
* @drm_dev: pointer to drm device
* @mode: current display mode selected
* @regs: HD analog register
* @video_dacs_ctrl: video DACS control register
* @enabled: true if HD analog is enabled else false
*/
struct sti_hda {
struct device dev;
struct drm_device *drm_dev;
struct drm_display_mode mode;
struct drm_bridge bridge;
void __iomem *regs;
void __iomem *video_dacs_ctrl;
struct clk *clk_pix;
struct clk *clk_hddac;
bool enabled;
};
struct sti_hda_connector {
struct drm_connector drm_connector;
struct drm_encoder *encoder;
struct sti_hda *hda;
};
#define to_sti_hda_connector(x) \
container_of(x, struct sti_hda_connector, drm_connector)
static struct sti_hda *drm_bridge_to_sti_hda(struct drm_bridge *bridge)
{
return container_of(bridge, struct sti_hda, bridge);
}
static u32 hda_read(struct sti_hda *hda, int offset)
{
return readl(hda->regs + offset);
}
static void hda_write(struct sti_hda *hda, u32 val, int offset)
{
writel(val, hda->regs + offset);
}
/**
* hda_get_mode_idx - Search for a video mode in the supported modes table
*
* @mode: mode being searched
* @idx: index of the found mode
*
* Return true if mode is found
*/
static bool hda_get_mode_idx(const struct drm_display_mode *mode, int *idx)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(hda_supported_modes); i++)
if (drm_mode_equal(&hda_supported_modes[i].mode, mode)) {
*idx = i;
return true;
}
return false;
}
/**
* hda_enable_hd_dacs - Enable the HD DACS
*
* @hda: pointer to HD analog structure
* @enable: true if HD DACS need to be enabled, else false
*/
static void hda_enable_hd_dacs(struct sti_hda *hda, bool enable)
{
if (hda->video_dacs_ctrl) {
u32 val;
val = readl(hda->video_dacs_ctrl);
if (enable)
val &= ~DAC_CFG_HD_HZUVW_OFF_MASK;
else
val |= DAC_CFG_HD_HZUVW_OFF_MASK;
writel(val, hda->video_dacs_ctrl);
}
}
#define DBGFS_DUMP(reg) seq_printf(s, "\n %-25s 0x%08X", #reg, \
readl(hda->regs + reg))
static void hda_dbg_cfg(struct seq_file *s, int val)
{
seq_puts(s, "\tAWG ");
seq_puts(s, val & CFG_AWG_ASYNC_EN ? "enabled" : "disabled");
}
static void hda_dbg_awg_microcode(struct seq_file *s, void __iomem *reg)
{
unsigned int i;
seq_puts(s, "\n\n HDA AWG microcode:");
for (i = 0; i < AWG_MAX_INST; i++) {
if (i % 8 == 0)
seq_printf(s, "\n %04X:", i);
seq_printf(s, " %04X", readl(reg + i * 4));
}
}
static void hda_dbg_video_dacs_ctrl(struct seq_file *s, void __iomem *reg)
{
u32 val = readl(reg);
seq_printf(s, "\n\n %-25s 0x%08X", "VIDEO_DACS_CONTROL", val);
seq_puts(s, "\tHD DACs ");
seq_puts(s, val & DAC_CFG_HD_HZUVW_OFF_MASK ? "disabled" : "enabled");
}
static int hda_dbg_show(struct seq_file *s, void *data)
{
struct drm_info_node *node = s->private;
struct sti_hda *hda = (struct sti_hda *)node->info_ent->data;
seq_printf(s, "HD Analog: (vaddr = 0x%p)", hda->regs);
DBGFS_DUMP(HDA_ANA_CFG);
hda_dbg_cfg(s, readl(hda->regs + HDA_ANA_CFG));
DBGFS_DUMP(HDA_ANA_SCALE_CTRL_Y);
DBGFS_DUMP(HDA_ANA_SCALE_CTRL_CB);
DBGFS_DUMP(HDA_ANA_SCALE_CTRL_CR);
DBGFS_DUMP(HDA_ANA_ANC_CTRL);
DBGFS_DUMP(HDA_ANA_SRC_Y_CFG);
DBGFS_DUMP(HDA_ANA_SRC_C_CFG);
hda_dbg_awg_microcode(s, hda->regs + HDA_SYNC_AWGI);
if (hda->video_dacs_ctrl)
hda_dbg_video_dacs_ctrl(s, hda->video_dacs_ctrl);
seq_putc(s, '\n');
return 0;
}
static struct drm_info_list hda_debugfs_files[] = {
{ "hda", hda_dbg_show, 0, NULL },
};
static void hda_debugfs_init(struct sti_hda *hda, struct drm_minor *minor)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(hda_debugfs_files); i++)
hda_debugfs_files[i].data = hda;
drm_debugfs_create_files(hda_debugfs_files,
ARRAY_SIZE(hda_debugfs_files),
minor->debugfs_root, minor);
}
/**
* sti_hda_configure_awg - Configure AWG, writing instructions
*
* @hda: pointer to HD analog structure
* @awg_instr: pointer to AWG instructions table
* @nb: nb of AWG instructions
*/
static void sti_hda_configure_awg(struct sti_hda *hda, u32 *awg_instr, int nb)
{
unsigned int i;
DRM_DEBUG_DRIVER("\n");
for (i = 0; i < nb; i++)
hda_write(hda, awg_instr[i], HDA_SYNC_AWGI + i * 4);
for (i = nb; i < AWG_MAX_INST; i++)
hda_write(hda, 0, HDA_SYNC_AWGI + i * 4);
}
static void sti_hda_disable(struct drm_bridge *bridge)
{
struct sti_hda *hda = drm_bridge_to_sti_hda(bridge);
u32 val;
if (!hda->enabled)
return;
DRM_DEBUG_DRIVER("\n");
/* Disable HD DAC and AWG */
val = hda_read(hda, HDA_ANA_CFG);
val &= ~CFG_AWG_ASYNC_EN;
hda_write(hda, val, HDA_ANA_CFG);
hda_write(hda, 0, HDA_ANA_ANC_CTRL);
hda_enable_hd_dacs(hda, false);
/* Disable/unprepare hda clock */
clk_disable_unprepare(hda->clk_hddac);
clk_disable_unprepare(hda->clk_pix);
hda->enabled = false;
}
static void sti_hda_pre_enable(struct drm_bridge *bridge)
{
struct sti_hda *hda = drm_bridge_to_sti_hda(bridge);
u32 val, i, mode_idx;
u32 src_filter_y, src_filter_c;
u32 *coef_y, *coef_c;
u32 filter_mode;
DRM_DEBUG_DRIVER("\n");
if (hda->enabled)
return;
/* Prepare/enable clocks */
if (clk_prepare_enable(hda->clk_pix))
DRM_ERROR("Failed to prepare/enable hda_pix clk\n");
if (clk_prepare_enable(hda->clk_hddac))
DRM_ERROR("Failed to prepare/enable hda_hddac clk\n");
if (!hda_get_mode_idx(&hda->mode, &mode_idx)) {
DRM_ERROR("Undefined mode\n");
return;
}
switch (hda_supported_modes[mode_idx].vid_cat) {
case VID_HD_148M:
DRM_ERROR("Beyond HD analog capabilities\n");
return;
case VID_HD_74M:
/* HD use alternate 2x filter */
filter_mode = CFG_AWG_FLTR_MODE_HD;
src_filter_y = HDA_ANA_SRC_Y_CFG_ALT_2X;
src_filter_c = HDA_ANA_SRC_C_CFG_ALT_2X;
coef_y = coef_y_alt_2x;
coef_c = coef_c_alt_2x;
break;
case VID_ED:
/* ED uses 4x filter */
filter_mode = CFG_AWG_FLTR_MODE_ED;
src_filter_y = HDA_ANA_SRC_Y_CFG_4X;
src_filter_c = HDA_ANA_SRC_C_CFG_4X;
coef_y = coef_yc_4x;
coef_c = coef_yc_4x;
break;
case VID_SD:
DRM_ERROR("Not supported\n");
return;
default:
DRM_ERROR("Undefined resolution\n");
return;
}
DRM_DEBUG_DRIVER("Using HDA mode #%d\n", mode_idx);
/* Enable HD Video DACs */
hda_enable_hd_dacs(hda, true);
/* Configure scaler */
hda_write(hda, SCALE_CTRL_Y_DFLT, HDA_ANA_SCALE_CTRL_Y);
hda_write(hda, SCALE_CTRL_CB_DFLT, HDA_ANA_SCALE_CTRL_CB);
hda_write(hda, SCALE_CTRL_CR_DFLT, HDA_ANA_SCALE_CTRL_CR);
/* Configure sampler */
hda_write(hda , src_filter_y, HDA_ANA_SRC_Y_CFG);
hda_write(hda, src_filter_c, HDA_ANA_SRC_C_CFG);
for (i = 0; i < SAMPLER_COEF_NB; i++) {
hda_write(hda, coef_y[i], HDA_COEFF_Y_PH1_TAP123 + i * 4);
hda_write(hda, coef_c[i], HDA_COEFF_C_PH1_TAP123 + i * 4);
}
/* Configure main HDFormatter */
val = 0;
val |= (hda->mode.flags & DRM_MODE_FLAG_INTERLACE) ?
0 : CFG_AWG_ASYNC_VSYNC_MTD;
val |= (CFG_PBPR_SYNC_OFF_VAL << CFG_PBPR_SYNC_OFF_SHIFT);
val |= filter_mode;
hda_write(hda, val, HDA_ANA_CFG);
/* Configure AWG */
sti_hda_configure_awg(hda, hda_supported_modes[mode_idx].awg_instr,
hda_supported_modes[mode_idx].nb_instr);
/* Enable AWG */
val = hda_read(hda, HDA_ANA_CFG);
val |= CFG_AWG_ASYNC_EN;
hda_write(hda, val, HDA_ANA_CFG);
hda->enabled = true;
}
static void sti_hda_set_mode(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
{
struct sti_hda *hda = drm_bridge_to_sti_hda(bridge);
u32 mode_idx;
int hddac_rate;
int ret;
DRM_DEBUG_DRIVER("\n");
drm_mode_copy(&hda->mode, mode);
if (!hda_get_mode_idx(&hda->mode, &mode_idx)) {
DRM_ERROR("Undefined mode\n");
return;
}
switch (hda_supported_modes[mode_idx].vid_cat) {
case VID_HD_74M:
/* HD use alternate 2x filter */
hddac_rate = mode->clock * 1000 * 2;
break;
case VID_ED:
/* ED uses 4x filter */
hddac_rate = mode->clock * 1000 * 4;
break;
default:
DRM_ERROR("Undefined mode\n");
return;
}
/* HD DAC = 148.5Mhz or 108 Mhz */
ret = clk_set_rate(hda->clk_hddac, hddac_rate);
if (ret < 0)
DRM_ERROR("Cannot set rate (%dHz) for hda_hddac clk\n",
hddac_rate);
/* HDformatter clock = compositor clock */
ret = clk_set_rate(hda->clk_pix, mode->clock * 1000);
if (ret < 0)
DRM_ERROR("Cannot set rate (%dHz) for hda_pix clk\n",
mode->clock * 1000);
}
static void sti_hda_bridge_nope(struct drm_bridge *bridge)
{
/* do nothing */
}
static const struct drm_bridge_funcs sti_hda_bridge_funcs = {
.pre_enable = sti_hda_pre_enable,
.enable = sti_hda_bridge_nope,
.disable = sti_hda_disable,
.post_disable = sti_hda_bridge_nope,
.mode_set = sti_hda_set_mode,
};
static int sti_hda_connector_get_modes(struct drm_connector *connector)
{
unsigned int i;
int count = 0;
struct sti_hda_connector *hda_connector
= to_sti_hda_connector(connector);
struct sti_hda *hda = hda_connector->hda;
DRM_DEBUG_DRIVER("\n");
for (i = 0; i < ARRAY_SIZE(hda_supported_modes); i++) {
struct drm_display_mode *mode =
drm_mode_duplicate(hda->drm_dev,
&hda_supported_modes[i].mode);
if (!mode)
continue;
/* the first mode is the preferred mode */
if (i == 0)
mode->type |= DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
count++;
}
return count;
}
#define CLK_TOLERANCE_HZ 50
static enum drm_mode_status
sti_hda_connector_mode_valid(struct drm_connector *connector,
const struct drm_display_mode *mode)
{
int target = mode->clock * 1000;
int target_min = target - CLK_TOLERANCE_HZ;
int target_max = target + CLK_TOLERANCE_HZ;
int result;
int idx;
struct sti_hda_connector *hda_connector
= to_sti_hda_connector(connector);
struct sti_hda *hda = hda_connector->hda;
if (!hda_get_mode_idx(mode, &idx)) {
return MODE_BAD;
} else {
result = clk_round_rate(hda->clk_pix, target);
DRM_DEBUG_DRIVER("target rate = %d => available rate = %d\n",
target, result);
if ((result < target_min) || (result > target_max)) {
DRM_DEBUG_DRIVER("hda pixclk=%d not supported\n",
target);
return MODE_BAD;
}
}
return MODE_OK;
}
static const
struct drm_connector_helper_funcs sti_hda_connector_helper_funcs = {
.get_modes = sti_hda_connector_get_modes,
.mode_valid = sti_hda_connector_mode_valid,
};
static int sti_hda_late_register(struct drm_connector *connector)
{
struct sti_hda_connector *hda_connector
= to_sti_hda_connector(connector);
struct sti_hda *hda = hda_connector->hda;
hda_debugfs_init(hda, hda->drm_dev->primary);
return 0;
}
static const struct drm_connector_funcs sti_hda_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
.late_register = sti_hda_late_register,
};
static struct drm_encoder *sti_hda_find_encoder(struct drm_device *dev)
{
struct drm_encoder *encoder;
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
if (encoder->encoder_type == DRM_MODE_ENCODER_DAC)
return encoder;
}
return NULL;
}
static int sti_hda_bind(struct device *dev, struct device *master, void *data)
{
struct sti_hda *hda = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
struct drm_encoder *encoder;
struct sti_hda_connector *connector;
struct drm_connector *drm_connector;
int err;
/* Set the drm device handle */
hda->drm_dev = drm_dev;
encoder = sti_hda_find_encoder(drm_dev);
if (!encoder)
return -ENOMEM;
connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL);
if (!connector)
return -ENOMEM;
connector->hda = hda;
drm_bridge_attach(encoder, &hda->bridge, NULL, 0);
connector->encoder = encoder;
drm_connector = (struct drm_connector *)connector;
drm_connector->polled = DRM_CONNECTOR_POLL_HPD;
drm_connector_init(drm_dev, drm_connector,
&sti_hda_connector_funcs, DRM_MODE_CONNECTOR_Component);
drm_connector_helper_add(drm_connector,
&sti_hda_connector_helper_funcs);
err = drm_connector_attach_encoder(drm_connector, encoder);
if (err) {
DRM_ERROR("Failed to attach a connector to a encoder\n");
goto err_sysfs;
}
/* force to disable hd dacs at startup */
hda_enable_hd_dacs(hda, false);
return 0;
err_sysfs:
return -EINVAL;
}
static void sti_hda_unbind(struct device *dev,
struct device *master, void *data)
{
}
static const struct component_ops sti_hda_ops = {
.bind = sti_hda_bind,
.unbind = sti_hda_unbind,
};
static int sti_hda_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sti_hda *hda;
struct resource *res;
int ret;
DRM_INFO("%s\n", __func__);
hda = devm_drm_bridge_alloc(dev, struct sti_hda, bridge, &sti_hda_bridge_funcs);
if (IS_ERR(hda))
return PTR_ERR(hda);
hda->dev = pdev->dev;
hda->regs = devm_platform_ioremap_resource_byname(pdev, "hda-reg");
if (IS_ERR(hda->regs))
return PTR_ERR(hda->regs);
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"video-dacs-ctrl");
if (res) {
hda->video_dacs_ctrl = devm_ioremap(dev, res->start,
resource_size(res));
if (!hda->video_dacs_ctrl)
return -ENOMEM;
} else {
/* If no existing video-dacs-ctrl resource continue the probe */
DRM_DEBUG_DRIVER("No video-dacs-ctrl resource\n");
hda->video_dacs_ctrl = NULL;
}
/* Get clock resources */
hda->clk_pix = devm_clk_get(dev, "pix");
if (IS_ERR(hda->clk_pix)) {
DRM_ERROR("Cannot get hda_pix clock\n");
return PTR_ERR(hda->clk_pix);
}
hda->clk_hddac = devm_clk_get(dev, "hddac");
if (IS_ERR(hda->clk_hddac)) {
DRM_ERROR("Cannot get hda_hddac clock\n");
return PTR_ERR(hda->clk_hddac);
}
ret = devm_drm_bridge_add(dev, &hda->bridge);
if (ret)
return ret;
platform_set_drvdata(pdev, hda);
return component_add(&pdev->dev, &sti_hda_ops);
}
static void sti_hda_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sti_hda_ops);
}
static const struct of_device_id hda_of_match[] = {
{ .compatible = "st,stih416-hda", },
{ .compatible = "st,stih407-hda", },
{ /* end node */ }
};
MODULE_DEVICE_TABLE(of, hda_of_match);
struct platform_driver sti_hda_driver = {
.driver = {
.name = "sti-hda",
.of_match_table = hda_of_match,
},
.probe = sti_hda_probe,
.remove = sti_hda_remove,
};
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
+114
View File
@@ -0,0 +1,114 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
*/
#ifndef _STI_HDMI_H_
#define _STI_HDMI_H_
#include <linux/hdmi.h>
#include <linux/platform_device.h>
#include <media/cec-notifier.h>
#include <drm/drm_bridge.h>
#include <drm/drm_modes.h>
#include <drm/drm_property.h>
#define HDMI_STA 0x0010
#define HDMI_STA_DLL_LCK BIT(5)
#define HDMI_STA_HOT_PLUG BIT(4)
struct sti_hdmi;
struct hdmi_phy_ops {
bool (*start)(struct sti_hdmi *hdmi);
void (*stop)(struct sti_hdmi *hdmi);
};
struct hdmi_audio_params {
bool enabled;
unsigned int sample_width;
unsigned int sample_rate;
struct hdmi_audio_infoframe cea;
};
#define DEFAULT_COLORSPACE_MODE HDMI_COLORSPACE_RGB
/**
* STI hdmi structure
*
* @dev: driver device
* @drm_dev: pointer to drm device
* @mode: current display mode selected
* @regs: hdmi register
* @syscfg: syscfg register for pll rejection configuration
* @clk_pix: hdmi pixel clock
* @clk_tmds: hdmi tmds clock
* @clk_phy: hdmi phy clock
* @clk_audio: hdmi audio clock
* @irq: hdmi interrupt number
* @irq_status: interrupt status register
* @phy_ops: phy start/stop operations
* @enabled: true if hdmi is enabled else false
* @hpd: hot plug detect status
* @wait_event: wait event
* @event_received: wait event status
* @reset: reset control of the hdmi phy
* @ddc_adapt: i2c ddc adapter
* @colorspace: current colorspace selected
* @audio_pdev: ASoC hdmi-codec platform device
* @audio: hdmi audio parameters.
* @drm_connector: hdmi connector
* @notifier: hotplug detect notifier
*/
struct sti_hdmi {
struct device dev;
struct drm_device *drm_dev;
struct drm_display_mode mode;
void __iomem *regs;
void __iomem *syscfg;
struct clk *clk_pix;
struct clk *clk_tmds;
struct clk *clk_phy;
struct clk *clk_audio;
int irq;
u32 irq_status;
struct hdmi_phy_ops *phy_ops;
bool enabled;
bool hpd;
wait_queue_head_t wait_event;
bool event_received;
struct reset_control *reset;
struct i2c_adapter *ddc_adapt;
enum hdmi_colorspace colorspace;
struct platform_device *audio_pdev;
struct hdmi_audio_params audio;
struct drm_connector *drm_connector;
struct cec_notifier *notifier;
struct drm_bridge bridge;
};
u32 hdmi_read(struct sti_hdmi *hdmi, int offset);
void hdmi_write(struct sti_hdmi *hdmi, u32 val, int offset);
/**
* hdmi phy config structure
*
* A pointer to an array of these structures is passed to a TMDS (HDMI) output
* via the control interface to provide board and SoC specific
* configurations of the HDMI PHY. Each entry in the array specifies a hardware
* specific configuration for a given TMDS clock frequency range.
*
* @min_tmds_freq: Lower bound of TMDS clock frequency this entry applies to
* @max_tmds_freq: Upper bound of TMDS clock frequency this entry applies to
* @config: SoC specific register configuration
*/
struct hdmi_phy_config {
u32 min_tmds_freq;
u32 max_tmds_freq;
u32 config[4];
};
#endif
@@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
*/
#include <linux/hdmi.h>
#include <drm/drm_print.h>
#include "sti_hdmi_tx3g4c28phy.h"
#define HDMI_SRZ_CFG 0x504
#define HDMI_SRZ_PLL_CFG 0x510
#define HDMI_SRZ_ICNTL 0x518
#define HDMI_SRZ_CALCODE_EXT 0x520
#define HDMI_SRZ_CFG_EN BIT(0)
#define HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT BIT(1)
#define HDMI_SRZ_CFG_EXTERNAL_DATA BIT(16)
#define HDMI_SRZ_CFG_RBIAS_EXT BIT(17)
#define HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION BIT(18)
#define HDMI_SRZ_CFG_EN_BIASRES_DETECTION BIT(19)
#define HDMI_SRZ_CFG_EN_SRC_TERMINATION BIT(24)
#define HDMI_SRZ_CFG_INTERNAL_MASK (HDMI_SRZ_CFG_EN | \
HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT | \
HDMI_SRZ_CFG_EXTERNAL_DATA | \
HDMI_SRZ_CFG_RBIAS_EXT | \
HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION | \
HDMI_SRZ_CFG_EN_BIASRES_DETECTION | \
HDMI_SRZ_CFG_EN_SRC_TERMINATION)
#define PLL_CFG_EN BIT(0)
#define PLL_CFG_NDIV_SHIFT (8)
#define PLL_CFG_IDF_SHIFT (16)
#define PLL_CFG_ODF_SHIFT (24)
#define ODF_DIV_1 (0)
#define ODF_DIV_2 (1)
#define ODF_DIV_4 (2)
#define ODF_DIV_8 (3)
#define HDMI_TIMEOUT_PLL_LOCK 50 /*milliseconds */
struct plldividers_s {
uint32_t min;
uint32_t max;
uint32_t idf;
uint32_t odf;
};
/*
* Functional specification recommended values
*/
#define NB_PLL_MODE 5
static struct plldividers_s plldividers[NB_PLL_MODE] = {
{0, 20000000, 1, ODF_DIV_8},
{20000000, 42500000, 2, ODF_DIV_8},
{42500000, 85000000, 4, ODF_DIV_4},
{85000000, 170000000, 8, ODF_DIV_2},
{170000000, 340000000, 16, ODF_DIV_1}
};
#define NB_HDMI_PHY_CONFIG 2
static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = {
{0, 250000000, {0x0, 0x0, 0x0, 0x0} },
{250000000, 300000000, {0x1110, 0x0, 0x0, 0x0} },
};
/**
* sti_hdmi_tx3g4c28phy_start - Start hdmi phy macro cell tx3g4c28
*
* @hdmi: pointer on the hdmi internal structure
*
* Return false if an error occur
*/
static bool sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi)
{
u32 ckpxpll = hdmi->mode.clock * 1000;
u32 val, tmdsck, idf, odf, pllctrl = 0;
bool foundplldivides = false;
int i;
DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll);
for (i = 0; i < NB_PLL_MODE; i++) {
if (ckpxpll >= plldividers[i].min &&
ckpxpll < plldividers[i].max) {
idf = plldividers[i].idf;
odf = plldividers[i].odf;
foundplldivides = true;
break;
}
}
if (!foundplldivides) {
DRM_ERROR("input TMDS clock speed (%d) not supported\n",
ckpxpll);
goto err;
}
/* Assuming no pixel repetition and 24bits color */
tmdsck = ckpxpll;
pllctrl |= 40 << PLL_CFG_NDIV_SHIFT;
if (tmdsck > HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ) {
DRM_ERROR("output TMDS clock (%d) out of range\n", tmdsck);
goto err;
}
pllctrl |= idf << PLL_CFG_IDF_SHIFT;
pllctrl |= odf << PLL_CFG_ODF_SHIFT;
/*
* Configure and power up the PHY PLL
*/
hdmi->event_received = false;
DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl);
hdmi_write(hdmi, (pllctrl | PLL_CFG_EN), HDMI_SRZ_PLL_CFG);
/* wait PLL interrupt */
wait_event_interruptible_timeout(hdmi->wait_event,
hdmi->event_received == true,
msecs_to_jiffies
(HDMI_TIMEOUT_PLL_LOCK));
if ((hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK) == 0) {
DRM_ERROR("hdmi phy pll not locked\n");
goto err;
}
DRM_DEBUG_DRIVER("got PHY PLL Lock\n");
val = (HDMI_SRZ_CFG_EN |
HDMI_SRZ_CFG_EXTERNAL_DATA |
HDMI_SRZ_CFG_EN_BIASRES_DETECTION |
HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION);
if (tmdsck > HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ)
val |= HDMI_SRZ_CFG_EN_SRC_TERMINATION;
/*
* To configure the source termination and pre-emphasis appropriately
* for different high speed TMDS clock frequencies a phy configuration
* table must be provided, tailored to the SoC and board combination.
*/
for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) {
if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) &&
(hdmiphy_config[i].max_tmds_freq >= tmdsck)) {
val |= (hdmiphy_config[i].config[0]
& ~HDMI_SRZ_CFG_INTERNAL_MASK);
hdmi_write(hdmi, val, HDMI_SRZ_CFG);
val = hdmiphy_config[i].config[1];
hdmi_write(hdmi, val, HDMI_SRZ_ICNTL);
val = hdmiphy_config[i].config[2];
hdmi_write(hdmi, val, HDMI_SRZ_CALCODE_EXT);
DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x\n",
hdmiphy_config[i].config[0],
hdmiphy_config[i].config[1],
hdmiphy_config[i].config[2]);
return true;
}
}
/*
* Default, power up the serializer with no pre-emphasis or
* output swing correction
*/
hdmi_write(hdmi, val, HDMI_SRZ_CFG);
hdmi_write(hdmi, 0x0, HDMI_SRZ_ICNTL);
hdmi_write(hdmi, 0x0, HDMI_SRZ_CALCODE_EXT);
return true;
err:
return false;
}
/**
* sti_hdmi_tx3g4c28phy_stop - Stop hdmi phy macro cell tx3g4c28
*
* @hdmi: pointer on the hdmi internal structure
*/
static void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi)
{
int val = 0;
DRM_DEBUG_DRIVER("\n");
hdmi->event_received = false;
val = HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION;
val |= HDMI_SRZ_CFG_EN_BIASRES_DETECTION;
hdmi_write(hdmi, val, HDMI_SRZ_CFG);
hdmi_write(hdmi, 0, HDMI_SRZ_PLL_CFG);
/* wait PLL interrupt */
wait_event_interruptible_timeout(hdmi->wait_event,
hdmi->event_received == true,
msecs_to_jiffies
(HDMI_TIMEOUT_PLL_LOCK));
if (hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK)
DRM_ERROR("hdmi phy pll not well disabled\n");
}
struct hdmi_phy_ops tx3g4c28phy_ops = {
.start = sti_hdmi_tx3g4c28phy_start,
.stop = sti_hdmi_tx3g4c28phy_stop,
};
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
*/
#ifndef _STI_HDMI_TX3G4C28PHY_H_
#define _STI_HDMI_TX3G4C28PHY_H_
#include "sti_hdmi.h"
extern struct hdmi_phy_ops tx3g4c28phy_ops;
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,373 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
*/
#ifndef _STI_HQVDP_LUT_H_
#define _STI_HQVDP_LUT_H_
#define NB_COEF 128
#define SHIFT_LUT_A_LEGACY 8
#define SHIFT_LUT_B 8
#define SHIFT_LUT_C_Y_LEGACY 8
#define SHIFT_LUT_C_C_LEGACY 8
#define SHIFT_LUT_D_Y_LEGACY 8
#define SHIFT_LUT_D_C_LEGACY 8
#define SHIFT_LUT_E_Y_LEGACY 8
#define SHIFT_LUT_E_C_LEGACY 8
#define SHIFT_LUT_F_Y_LEGACY 8
#define SHIFT_LUT_F_C_LEGACY 8
static const u32 coef_lut_a_legacy[NB_COEF] = {
0x0000ffff, 0x00010000, 0x000100ff, 0x00000000,
0x00000000, 0x00050000, 0xfffc00ff, 0x00000000,
0x00000000, 0x00090000, 0xfff900fe, 0x00000000,
0x00000000, 0x0010ffff, 0xfff600fb, 0x00000000,
0x00000000, 0x0017fffe, 0xfff400f7, 0x00000000,
0x00000000, 0x001ffffd, 0xfff200f2, 0x00000000,
0x00000000, 0x0027fffc, 0xfff100ec, 0x00000000,
0x00000000, 0x0030fffb, 0xfff000e5, 0x00000000,
0x00000000, 0x003afffa, 0xffee00de, 0x00000000,
0x00000000, 0x0044fff9, 0xffed00d6, 0x00000000,
0x00000000, 0x004efff8, 0xffed00cd, 0x00000000,
0x00000000, 0x0059fff6, 0xffed00c4, 0x00000000,
0x00000000, 0x0064fff5, 0xffed00ba, 0x00000000,
0x00000000, 0x006ffff3, 0xffee00b0, 0x00000000,
0x00000000, 0x007afff2, 0xffee00a6, 0x00000000,
0x00000000, 0x0085fff1, 0xffef009b, 0x00000000,
0x00000000, 0x0090fff0, 0xfff00090, 0x00000000,
0x00000000, 0x009bffef, 0xfff10085, 0x00000000,
0x00000000, 0x00a6ffee, 0xfff2007a, 0x00000000,
0x00000000, 0x00b0ffee, 0xfff3006f, 0x00000000,
0x00000000, 0x00baffed, 0xfff50064, 0x00000000,
0x00000000, 0x00c4ffed, 0xfff60059, 0x00000000,
0x00000000, 0x00cdffed, 0xfff8004e, 0x00000000,
0x00000000, 0x00d6ffed, 0xfff90044, 0x00000000,
0x00000000, 0x00deffee, 0xfffa003a, 0x00000000,
0x00000000, 0x00e5fff0, 0xfffb0030, 0x00000000,
0x00000000, 0x00ecfff1, 0xfffc0027, 0x00000000,
0x00000000, 0x00f2fff2, 0xfffd001f, 0x00000000,
0x00000000, 0x00f7fff4, 0xfffe0017, 0x00000000,
0x00000000, 0x00fbfff6, 0xffff0010, 0x00000000,
0x00000000, 0x00fefff9, 0x00000009, 0x00000000,
0x00000000, 0x00fffffc, 0x00000005, 0x00000000
};
static const u32 coef_lut_b[NB_COEF] = {
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000,
0x00000000, 0x00000000, 0x00000100, 0x00000000
};
static const u32 coef_lut_c_y_legacy[NB_COEF] = {
0x00060004, 0x0038ffe1, 0x003800be, 0x0006ffe1,
0x00050005, 0x0042ffe1, 0x003800b3, 0x0007ffe1,
0x00040006, 0x0046ffe1, 0x003300b2, 0x0008ffe2,
0x00030007, 0x004cffe1, 0x002e00b1, 0x0008ffe2,
0x00020006, 0x0051ffe2, 0x002900b0, 0x0009ffe3,
0x00010008, 0x0056ffe2, 0x002400ae, 0x0009ffe4,
0xffff0008, 0x005cffe3, 0x001f00ad, 0x000affe4,
0xfffe0008, 0x0062ffe4, 0x001a00ab, 0x000affe5,
0xfffd000a, 0x0066ffe5, 0x001500a8, 0x000bffe6,
0xfffc0009, 0x006bffe7, 0x001100a5, 0x000bffe8,
0xfffa000a, 0x0070ffe8, 0x000d00a3, 0x000bffe9,
0xfff9000b, 0x0076ffea, 0x0008009f, 0x000bffea,
0xfff7000b, 0x007affec, 0x0005009b, 0x000cffec,
0xfff6000b, 0x007effef, 0x00010098, 0x000cffed,
0xfff4000b, 0x0084fff1, 0xfffd0095, 0x000cffee,
0xfff3000b, 0x0088fff4, 0xfffa0090, 0x000cfff0,
0xfff1000b, 0x008dfff7, 0xfff7008d, 0x000bfff1,
0xfff0000c, 0x0090fffa, 0xfff40088, 0x000bfff3,
0xffee000c, 0x0095fffd, 0xfff10084, 0x000bfff4,
0xffed000c, 0x00980001, 0xffef007e, 0x000bfff6,
0xffec000c, 0x009b0005, 0xffec007a, 0x000bfff7,
0xffea000b, 0x009f0008, 0xffea0076, 0x000bfff9,
0xffe9000b, 0x00a3000d, 0xffe80070, 0x000afffa,
0xffe8000b, 0x00a50011, 0xffe7006b, 0x0009fffc,
0xffe6000b, 0x00a80015, 0xffe50066, 0x000afffd,
0xffe5000a, 0x00ab001a, 0xffe40062, 0x0008fffe,
0xffe4000a, 0x00ad001f, 0xffe3005c, 0x0008ffff,
0xffe40009, 0x00ae0024, 0xffe20056, 0x00080001,
0xffe30009, 0x00b00029, 0xffe20051, 0x00060002,
0xffe20008, 0x00b1002e, 0xffe1004c, 0x00070003,
0xffe20008, 0x00b20033, 0xffe10046, 0x00060004,
0xffe10007, 0x00b30038, 0xffe10042, 0x00050005
};
static const u32 coef_lut_c_c_legacy[NB_COEF] = {
0x0001fff3, 0x003afffb, 0x003a00a1, 0x0001fffb,
0x0001fff5, 0x0041fffb, 0x0038009a, 0x0001fffb,
0x0001fff5, 0x0046fffb, 0x00340099, 0x0001fffb,
0x0001fff7, 0x0049fffb, 0x00300098, 0x0001fffb,
0x0001fff9, 0x004cfffb, 0x002d0096, 0x0001fffb,
0x0001fffa, 0x004ffffc, 0x00290095, 0x0001fffb,
0x0001fff9, 0x0054fffd, 0x00250093, 0x0001fffc,
0x0001fffa, 0x0058fffd, 0x00220092, 0x0000fffc,
0x0001fffb, 0x005bfffe, 0x001f0090, 0x0000fffc,
0x0001fffd, 0x005effff, 0x001c008c, 0x0000fffd,
0x0001fffd, 0x00620000, 0x0019008a, 0x0000fffd,
0x0001fffe, 0x00660001, 0x00160088, 0xfffffffd,
0x0000fffe, 0x006a0003, 0x00130085, 0xfffffffe,
0x0000fffe, 0x006e0004, 0x00100083, 0xfffffffe,
0x0000fffe, 0x00710006, 0x000e007f, 0xffffffff,
0x0000fffe, 0x00750008, 0x000c007c, 0xfffeffff,
0xfffffffe, 0x0079000a, 0x000a0079, 0xfffeffff,
0xfffffffe, 0x007c000c, 0x00080075, 0xfffe0000,
0xffffffff, 0x007f000e, 0x00060071, 0xfffe0000,
0xfffeffff, 0x00830010, 0x0004006e, 0xfffe0000,
0xfffeffff, 0x00850013, 0x0003006a, 0xfffe0000,
0xfffdffff, 0x00880016, 0x00010066, 0xfffe0001,
0xfffd0000, 0x008a0019, 0x00000062, 0xfffd0001,
0xfffd0000, 0x008c001c, 0xffff005e, 0xfffd0001,
0xfffc0000, 0x0090001f, 0xfffe005b, 0xfffb0001,
0xfffc0000, 0x00920022, 0xfffd0058, 0xfffa0001,
0xfffc0001, 0x00930025, 0xfffd0054, 0xfff90001,
0xfffb0001, 0x00950029, 0xfffc004f, 0xfffa0001,
0xfffb0001, 0x0096002d, 0xfffb004c, 0xfff90001,
0xfffb0001, 0x00980030, 0xfffb0049, 0xfff70001,
0xfffb0001, 0x00990034, 0xfffb0046, 0xfff50001,
0xfffb0001, 0x009a0038, 0xfffb0041, 0xfff50001
};
static const u32 coef_lut_d_y_legacy[NB_COEF] = {
0xfff80009, 0x0046ffec, 0x004600a3, 0xfff8ffec,
0xfff70009, 0x004effed, 0x0044009d, 0xfff9ffeb,
0xfff6000a, 0x0052ffee, 0x003f009d, 0xfffaffea,
0xfff50009, 0x0057ffef, 0x003b009d, 0xfffbffe9,
0xfff50008, 0x005bfff0, 0x0037009c, 0xfffcffe9,
0xfff40008, 0x005ffff2, 0x0033009b, 0xfffcffe9,
0xfff30007, 0x0064fff3, 0x002f009b, 0xfffdffe8,
0xfff20007, 0x0068fff5, 0x002b0099, 0xfffeffe8,
0xfff10008, 0x006bfff7, 0x00270097, 0xffffffe8,
0xfff00007, 0x006ffff9, 0x00230097, 0xffffffe8,
0xffef0006, 0x0073fffb, 0x00200095, 0x0000ffe8,
0xffee0005, 0x0077fffe, 0x001c0093, 0x0000ffe9,
0xffee0005, 0x007a0000, 0x00180091, 0x0001ffe9,
0xffed0005, 0x007d0003, 0x0015008e, 0x0002ffe9,
0xffec0005, 0x00800006, 0x0012008b, 0x0002ffea,
0xffeb0004, 0x00840008, 0x000e008a, 0x0003ffea,
0xffeb0003, 0x0087000b, 0x000b0087, 0x0003ffeb,
0xffea0003, 0x008a000e, 0x00080084, 0x0004ffeb,
0xffea0002, 0x008b0012, 0x00060080, 0x0005ffec,
0xffe90002, 0x008e0015, 0x0003007d, 0x0005ffed,
0xffe90001, 0x00910018, 0x0000007a, 0x0005ffee,
0xffe90000, 0x0093001c, 0xfffe0077, 0x0005ffee,
0xffe80000, 0x00950020, 0xfffb0073, 0x0006ffef,
0xffe8ffff, 0x00970023, 0xfff9006f, 0x0007fff0,
0xffe8ffff, 0x00970027, 0xfff7006b, 0x0008fff1,
0xffe8fffe, 0x0099002b, 0xfff50068, 0x0007fff2,
0xffe8fffd, 0x009b002f, 0xfff30064, 0x0007fff3,
0xffe9fffc, 0x009b0033, 0xfff2005f, 0x0008fff4,
0xffe9fffc, 0x009c0037, 0xfff0005b, 0x0008fff5,
0xffe9fffb, 0x009d003b, 0xffef0057, 0x0009fff5,
0xffeafffa, 0x009d003f, 0xffee0052, 0x000afff6,
0xffebfff9, 0x009d0044, 0xffed004e, 0x0009fff7
};
static const u32 coef_lut_d_c_legacy[NB_COEF] = {
0xfffeffff, 0x003fffff, 0x003f0089, 0xfffeffff,
0xfffe0000, 0x00460000, 0x0042007d, 0xfffffffe,
0xfffe0000, 0x00490001, 0x003f007d, 0xfffffffd,
0xfffd0001, 0x004b0002, 0x003c007d, 0x0000fffc,
0xfffd0001, 0x004e0003, 0x0039007c, 0x0000fffc,
0xfffc0001, 0x00510005, 0x0036007c, 0x0000fffb,
0xfffc0001, 0x00540006, 0x0033007b, 0x0001fffa,
0xfffc0003, 0x00550008, 0x00310078, 0x0001fffa,
0xfffb0003, 0x00580009, 0x002e0078, 0x0001fffa,
0xfffb0002, 0x005b000b, 0x002b0077, 0x0002fff9,
0xfffa0003, 0x005e000d, 0x00280075, 0x0002fff9,
0xfffa0002, 0x0060000f, 0x00260074, 0x0002fff9,
0xfffa0004, 0x00610011, 0x00230072, 0x0002fff9,
0xfffa0004, 0x00640013, 0x00200070, 0x0002fff9,
0xfff90004, 0x00660015, 0x001e006e, 0x0003fff9,
0xfff90004, 0x00680017, 0x001c006c, 0x0003fff9,
0xfff90003, 0x006b0019, 0x0019006b, 0x0003fff9,
0xfff90003, 0x006c001c, 0x00170068, 0x0004fff9,
0xfff90003, 0x006e001e, 0x00150066, 0x0004fff9,
0xfff90002, 0x00700020, 0x00130064, 0x0004fffa,
0xfff90002, 0x00720023, 0x00110061, 0x0004fffa,
0xfff90002, 0x00740026, 0x000f0060, 0x0002fffa,
0xfff90002, 0x00750028, 0x000d005e, 0x0003fffa,
0xfff90002, 0x0077002b, 0x000b005b, 0x0002fffb,
0xfffa0001, 0x0078002e, 0x00090058, 0x0003fffb,
0xfffa0001, 0x00780031, 0x00080055, 0x0003fffc,
0xfffa0001, 0x007b0033, 0x00060054, 0x0001fffc,
0xfffb0000, 0x007c0036, 0x00050051, 0x0001fffc,
0xfffc0000, 0x007c0039, 0x0003004e, 0x0001fffd,
0xfffc0000, 0x007d003c, 0x0002004b, 0x0001fffd,
0xfffdffff, 0x007d003f, 0x00010049, 0x0000fffe,
0xfffeffff, 0x007d0042, 0x00000046, 0x0000fffe
};
static const u32 coef_lut_e_y_legacy[NB_COEF] = {
0xfff10001, 0x00490004, 0x00490083, 0xfff10004,
0xfff10000, 0x00500006, 0x004b007b, 0xfff10002,
0xfff10000, 0x00530007, 0x0048007b, 0xfff10001,
0xfff10000, 0x00550009, 0x0046007a, 0xfff10000,
0xfff1fffe, 0x0058000b, 0x0043007b, 0xfff2fffe,
0xfff1ffff, 0x005a000d, 0x0040007a, 0xfff2fffd,
0xfff1fffd, 0x005d000f, 0x003e007a, 0xfff2fffc,
0xfff1fffd, 0x005f0011, 0x003b0079, 0xfff3fffb,
0xfff1fffc, 0x00610013, 0x00390079, 0xfff3fffa,
0xfff1fffb, 0x00640015, 0x00360079, 0xfff3fff9,
0xfff1fffa, 0x00660017, 0x00340078, 0xfff4fff8,
0xfff1fffb, 0x00680019, 0x00310077, 0xfff4fff7,
0xfff2fff9, 0x006a001b, 0x002f0076, 0xfff5fff6,
0xfff2fff9, 0x006c001e, 0x002c0075, 0xfff5fff5,
0xfff2fff9, 0x006d0020, 0x002a0073, 0xfff6fff5,
0xfff3fff7, 0x00700022, 0x00270073, 0xfff6fff4,
0xfff3fff7, 0x00710025, 0x00250071, 0xfff7fff3,
0xfff4fff6, 0x00730027, 0x00220070, 0xfff7fff3,
0xfff5fff6, 0x0073002a, 0x0020006d, 0xfff9fff2,
0xfff5fff5, 0x0075002c, 0x001e006c, 0xfff9fff2,
0xfff6fff5, 0x0076002f, 0x001b006a, 0xfff9fff2,
0xfff7fff4, 0x00770031, 0x00190068, 0xfffbfff1,
0xfff8fff4, 0x00780034, 0x00170066, 0xfffafff1,
0xfff9fff3, 0x00790036, 0x00150064, 0xfffbfff1,
0xfffafff3, 0x00790039, 0x00130061, 0xfffcfff1,
0xfffbfff3, 0x0079003b, 0x0011005f, 0xfffdfff1,
0xfffcfff2, 0x007a003e, 0x000f005d, 0xfffdfff1,
0xfffdfff2, 0x007a0040, 0x000d005a, 0xfffffff1,
0xfffefff2, 0x007b0043, 0x000b0058, 0xfffefff1,
0x0000fff1, 0x007a0046, 0x00090055, 0x0000fff1,
0x0001fff1, 0x007b0048, 0x00070053, 0x0000fff1,
0x0002fff1, 0x007b004b, 0x00060050, 0x0000fff1
};
static const u32 coef_lut_e_c_legacy[NB_COEF] = {
0xfffa0001, 0x003f0010, 0x003f006d, 0xfffa0010,
0xfffb0002, 0x00440011, 0x00440062, 0xfffa000e,
0xfffb0001, 0x00460013, 0x00420062, 0xfffa000d,
0xfffb0000, 0x00480014, 0x00410062, 0xfffa000c,
0xfffb0001, 0x00490015, 0x003f0061, 0xfffb000b,
0xfffb0000, 0x004b0017, 0x003d0061, 0xfffb000a,
0xfffb0000, 0x004d0018, 0x003b0062, 0xfffb0008,
0xfffcffff, 0x004f001a, 0x00390061, 0xfffb0007,
0xfffc0000, 0x004f001c, 0x00380060, 0xfffb0006,
0xfffcffff, 0x0052001d, 0x00360060, 0xfffb0005,
0xfffdfffe, 0x0053001f, 0x00340060, 0xfffb0004,
0xfffdfffe, 0x00540021, 0x0032005e, 0xfffc0004,
0xfffeffff, 0x00550022, 0x0030005d, 0xfffc0003,
0xfffeffff, 0x00560024, 0x002f005c, 0xfffc0002,
0xfffffffd, 0x00580026, 0x002d005c, 0xfffc0001,
0xfffffffd, 0x005a0027, 0x002b005c, 0xfffc0000,
0x0000fffd, 0x005a0029, 0x0029005a, 0xfffd0000,
0x0000fffc, 0x005c002b, 0x0027005a, 0xfffdffff,
0x0001fffc, 0x005c002d, 0x00260058, 0xfffdffff,
0x0002fffc, 0x005c002f, 0x00240056, 0xfffffffe,
0x0003fffc, 0x005d0030, 0x00220055, 0xfffffffe,
0x0004fffc, 0x005e0032, 0x00210054, 0xfffefffd,
0x0004fffb, 0x00600034, 0x001f0053, 0xfffefffd,
0x0005fffb, 0x00600036, 0x001d0052, 0xfffffffc,
0x0006fffb, 0x00600038, 0x001c004f, 0x0000fffc,
0x0007fffb, 0x00610039, 0x001a004f, 0xfffffffc,
0x0008fffb, 0x0062003b, 0x0018004d, 0x0000fffb,
0x000afffb, 0x0061003d, 0x0017004b, 0x0000fffb,
0x000bfffb, 0x0061003f, 0x00150049, 0x0001fffb,
0x000cfffa, 0x00620041, 0x00140048, 0x0000fffb,
0x000dfffa, 0x00620042, 0x00130046, 0x0001fffb,
0x000efffa, 0x00620044, 0x00110044, 0x0002fffb
};
static const u32 coef_lut_f_y_legacy[NB_COEF] = {
0xfff6fff0, 0x00490012, 0x0049006e, 0xfff60012,
0xfff7fff1, 0x004e0013, 0x00490068, 0xfff60010,
0xfff7fff2, 0x004f0015, 0x00470067, 0xfff6000f,
0xfff7fff5, 0x004f0017, 0x00450065, 0xfff6000e,
0xfff8fff5, 0x00500018, 0x00440065, 0xfff6000c,
0xfff8fff6, 0x0051001a, 0x00420064, 0xfff6000b,
0xfff8fff6, 0x0052001c, 0x00400064, 0xfff6000a,
0xfff9fff6, 0x0054001d, 0x003e0064, 0xfff60008,
0xfff9fff8, 0x0054001f, 0x003c0063, 0xfff60007,
0xfffafff8, 0x00550021, 0x003a0062, 0xfff60006,
0xfffbfff7, 0x00560022, 0x00390062, 0xfff60005,
0xfffbfff8, 0x00570024, 0x00370061, 0xfff60004,
0xfffcfff8, 0x00580026, 0x00350060, 0xfff60003,
0xfffdfff8, 0x00590028, 0x0033005f, 0xfff60002,
0xfffdfff7, 0x005b002a, 0x0031005f, 0xfff60001,
0xfffefff7, 0x005c002c, 0x002f005e, 0xfff60000,
0xfffffff6, 0x005e002d, 0x002d005e, 0xfff6ffff,
0x0000fff6, 0x005e002f, 0x002c005c, 0xfff7fffe,
0x0001fff6, 0x005f0031, 0x002a005b, 0xfff7fffd,
0x0002fff6, 0x005f0033, 0x00280059, 0xfff8fffd,
0x0003fff6, 0x00600035, 0x00260058, 0xfff8fffc,
0x0004fff6, 0x00610037, 0x00240057, 0xfff8fffb,
0x0005fff6, 0x00620039, 0x00220056, 0xfff7fffb,
0x0006fff6, 0x0062003a, 0x00210055, 0xfff8fffa,
0x0007fff6, 0x0063003c, 0x001f0054, 0xfff8fff9,
0x0008fff6, 0x0064003e, 0x001d0054, 0xfff6fff9,
0x000afff6, 0x00640040, 0x001c0052, 0xfff6fff8,
0x000bfff6, 0x00640042, 0x001a0051, 0xfff6fff8,
0x000cfff6, 0x00650044, 0x00180050, 0xfff5fff8,
0x000efff6, 0x00650045, 0x0017004f, 0xfff5fff7,
0x000ffff6, 0x00670047, 0x0015004f, 0xfff2fff7,
0x0010fff6, 0x00680049, 0x0013004e, 0xfff1fff7
};
static const u32 coef_lut_f_c_legacy[NB_COEF] = {
0x0000fffb, 0x003a001a, 0x003a005d, 0x0000001a,
0x0001fffb, 0x003f001b, 0x00400051, 0x00000019,
0x0001fffc, 0x0040001c, 0x003f0051, 0x00000017,
0x0002fffb, 0x0042001d, 0x003e0051, 0xffff0016,
0x0002fffb, 0x0043001e, 0x003d0051, 0xffff0015,
0x0003fffc, 0x00430020, 0x003b0050, 0xffff0014,
0x0003fffb, 0x00450021, 0x003a0051, 0xfffe0013,
0x0004fffc, 0x00450022, 0x00390050, 0xfffe0012,
0x0005fffc, 0x00460023, 0x0038004f, 0xfffe0011,
0x0005fffb, 0x00480025, 0x00360050, 0xfffd0010,
0x0006fffc, 0x00480026, 0x0035004f, 0xfffd000f,
0x0006fffc, 0x00490027, 0x0034004f, 0xfffd000e,
0x0007fffd, 0x00490028, 0x0033004e, 0xfffd000d,
0x0008fffc, 0x004a002a, 0x0031004d, 0xfffd000d,
0x0009fffd, 0x004a002b, 0x0030004d, 0xfffc000c,
0x0009fffc, 0x004c002c, 0x002f004d, 0xfffc000b,
0x000afffc, 0x004c002e, 0x002e004c, 0xfffc000a,
0x000bfffc, 0x004d002f, 0x002c004c, 0xfffc0009,
0x000cfffc, 0x004d0030, 0x002b004a, 0xfffd0009,
0x000dfffd, 0x004d0031, 0x002a004a, 0xfffc0008,
0x000dfffd, 0x004e0033, 0x00280049, 0xfffd0007,
0x000efffd, 0x004f0034, 0x00270049, 0xfffc0006,
0x000ffffd, 0x004f0035, 0x00260048, 0xfffc0006,
0x0010fffd, 0x00500036, 0x00250048, 0xfffb0005,
0x0011fffe, 0x004f0038, 0x00230046, 0xfffc0005,
0x0012fffe, 0x00500039, 0x00220045, 0xfffc0004,
0x0013fffe, 0x0051003a, 0x00210045, 0xfffb0003,
0x0014ffff, 0x0050003b, 0x00200043, 0xfffc0003,
0x0015ffff, 0x0051003d, 0x001e0043, 0xfffb0002,
0x0016ffff, 0x0051003e, 0x001d0042, 0xfffb0002,
0x00170000, 0x0051003f, 0x001c0040, 0xfffc0001,
0x00190000, 0x00510040, 0x001b003f, 0xfffb0001
};
#endif
+378
View File
@@ -0,0 +1,378 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
* Fabien Dessenne <fabien.dessenne@st.com>
* for STMicroelectronics.
*/
#include <linux/moduleparam.h>
#include <linux/seq_file.h>
#include <drm/drm_print.h>
#include "sti_compositor.h"
#include "sti_mixer.h"
#include "sti_vtg.h"
/* Module parameter to set the background color of the mixer */
static unsigned int bkg_color = 0x000000;
MODULE_PARM_DESC(bkgcolor, "Value of the background color 0xRRGGBB");
module_param_named(bkgcolor, bkg_color, int, 0644);
/* regs offset */
#define GAM_MIXER_CTL 0x00
#define GAM_MIXER_BKC 0x04
#define GAM_MIXER_BCO 0x0C
#define GAM_MIXER_BCS 0x10
#define GAM_MIXER_AVO 0x28
#define GAM_MIXER_AVS 0x2C
#define GAM_MIXER_CRB 0x34
#define GAM_MIXER_ACT 0x38
#define GAM_MIXER_MBP 0x3C
#define GAM_MIXER_MX0 0x80
/* id for depth of CRB reg */
#define GAM_DEPTH_VID0_ID 1
#define GAM_DEPTH_VID1_ID 2
#define GAM_DEPTH_GDP0_ID 3
#define GAM_DEPTH_GDP1_ID 4
#define GAM_DEPTH_GDP2_ID 5
#define GAM_DEPTH_GDP3_ID 6
#define GAM_DEPTH_MASK_ID 7
/* mask in CTL reg */
#define GAM_CTL_BACK_MASK BIT(0)
#define GAM_CTL_VID0_MASK BIT(1)
#define GAM_CTL_VID1_MASK BIT(2)
#define GAM_CTL_GDP0_MASK BIT(3)
#define GAM_CTL_GDP1_MASK BIT(4)
#define GAM_CTL_GDP2_MASK BIT(5)
#define GAM_CTL_GDP3_MASK BIT(6)
#define GAM_CTL_CURSOR_MASK BIT(9)
const char *sti_mixer_to_str(struct sti_mixer *mixer)
{
switch (mixer->id) {
case STI_MIXER_MAIN:
return "MAIN_MIXER";
case STI_MIXER_AUX:
return "AUX_MIXER";
default:
return "<UNKNOWN MIXER>";
}
}
static inline u32 sti_mixer_reg_read(struct sti_mixer *mixer, u32 reg_id)
{
return readl(mixer->regs + reg_id);
}
static inline void sti_mixer_reg_write(struct sti_mixer *mixer,
u32 reg_id, u32 val)
{
writel(val, mixer->regs + reg_id);
}
#define DBGFS_DUMP(reg) seq_printf(s, "\n %-25s 0x%08X", #reg, \
sti_mixer_reg_read(mixer, reg))
static void mixer_dbg_ctl(struct seq_file *s, int val)
{
unsigned int i;
int count = 0;
char *const disp_layer[] = {"BKG", "VID0", "VID1", "GDP0",
"GDP1", "GDP2", "GDP3"};
seq_puts(s, "\tEnabled: ");
for (i = 0; i < 7; i++) {
if (val & 1) {
seq_printf(s, "%s ", disp_layer[i]);
count++;
}
val = val >> 1;
}
val = val >> 2;
if (val & 1) {
seq_puts(s, "CURS ");
count++;
}
if (!count)
seq_puts(s, "Nothing");
}
static void mixer_dbg_crb(struct seq_file *s, int val)
{
int i;
seq_puts(s, "\tDepth: ");
for (i = 0; i < GAM_MIXER_NB_DEPTH_LEVEL; i++) {
switch (val & GAM_DEPTH_MASK_ID) {
case GAM_DEPTH_VID0_ID:
seq_puts(s, "VID0");
break;
case GAM_DEPTH_VID1_ID:
seq_puts(s, "VID1");
break;
case GAM_DEPTH_GDP0_ID:
seq_puts(s, "GDP0");
break;
case GAM_DEPTH_GDP1_ID:
seq_puts(s, "GDP1");
break;
case GAM_DEPTH_GDP2_ID:
seq_puts(s, "GDP2");
break;
case GAM_DEPTH_GDP3_ID:
seq_puts(s, "GDP3");
break;
default:
seq_puts(s, "---");
}
if (i < GAM_MIXER_NB_DEPTH_LEVEL - 1)
seq_puts(s, " < ");
val = val >> 3;
}
}
static void mixer_dbg_mxn(struct seq_file *s, void __iomem *addr)
{
int i;
for (i = 1; i < 8; i++)
seq_printf(s, "-0x%08X", (int)readl(addr + i * 4));
}
static int mixer_dbg_show(struct seq_file *s, void *arg)
{
struct drm_info_node *node = s->private;
struct sti_mixer *mixer = (struct sti_mixer *)node->info_ent->data;
seq_printf(s, "%s: (vaddr = 0x%p)",
sti_mixer_to_str(mixer), mixer->regs);
DBGFS_DUMP(GAM_MIXER_CTL);
mixer_dbg_ctl(s, sti_mixer_reg_read(mixer, GAM_MIXER_CTL));
DBGFS_DUMP(GAM_MIXER_BKC);
DBGFS_DUMP(GAM_MIXER_BCO);
DBGFS_DUMP(GAM_MIXER_BCS);
DBGFS_DUMP(GAM_MIXER_AVO);
DBGFS_DUMP(GAM_MIXER_AVS);
DBGFS_DUMP(GAM_MIXER_CRB);
mixer_dbg_crb(s, sti_mixer_reg_read(mixer, GAM_MIXER_CRB));
DBGFS_DUMP(GAM_MIXER_ACT);
DBGFS_DUMP(GAM_MIXER_MBP);
DBGFS_DUMP(GAM_MIXER_MX0);
mixer_dbg_mxn(s, mixer->regs + GAM_MIXER_MX0);
seq_putc(s, '\n');
return 0;
}
static struct drm_info_list mixer0_debugfs_files[] = {
{ "mixer_main", mixer_dbg_show, 0, NULL },
};
static struct drm_info_list mixer1_debugfs_files[] = {
{ "mixer_aux", mixer_dbg_show, 0, NULL },
};
void sti_mixer_debugfs_init(struct sti_mixer *mixer, struct drm_minor *minor)
{
unsigned int i;
struct drm_info_list *mixer_debugfs_files;
int nb_files;
switch (mixer->id) {
case STI_MIXER_MAIN:
mixer_debugfs_files = mixer0_debugfs_files;
nb_files = ARRAY_SIZE(mixer0_debugfs_files);
break;
case STI_MIXER_AUX:
mixer_debugfs_files = mixer1_debugfs_files;
nb_files = ARRAY_SIZE(mixer1_debugfs_files);
break;
default:
return;
}
for (i = 0; i < nb_files; i++)
mixer_debugfs_files[i].data = mixer;
drm_debugfs_create_files(mixer_debugfs_files,
nb_files,
minor->debugfs_root, minor);
}
void sti_mixer_set_background_status(struct sti_mixer *mixer, bool enable)
{
u32 val = sti_mixer_reg_read(mixer, GAM_MIXER_CTL);
val &= ~GAM_CTL_BACK_MASK;
val |= enable;
sti_mixer_reg_write(mixer, GAM_MIXER_CTL, val);
}
static void sti_mixer_set_background_color(struct sti_mixer *mixer,
unsigned int rgb)
{
sti_mixer_reg_write(mixer, GAM_MIXER_BKC, rgb);
}
static void sti_mixer_set_background_area(struct sti_mixer *mixer,
struct drm_display_mode *mode)
{
u32 ydo, xdo, yds, xds;
ydo = sti_vtg_get_line_number(*mode, 0);
yds = sti_vtg_get_line_number(*mode, mode->vdisplay - 1);
xdo = sti_vtg_get_pixel_number(*mode, 0);
xds = sti_vtg_get_pixel_number(*mode, mode->hdisplay - 1);
sti_mixer_reg_write(mixer, GAM_MIXER_BCO, ydo << 16 | xdo);
sti_mixer_reg_write(mixer, GAM_MIXER_BCS, yds << 16 | xds);
}
int sti_mixer_set_plane_depth(struct sti_mixer *mixer, struct sti_plane *plane)
{
int plane_id, depth = plane->drm_plane.state->normalized_zpos;
unsigned int i;
u32 mask, val;
switch (plane->desc) {
case STI_GDP_0:
plane_id = GAM_DEPTH_GDP0_ID;
break;
case STI_GDP_1:
plane_id = GAM_DEPTH_GDP1_ID;
break;
case STI_GDP_2:
plane_id = GAM_DEPTH_GDP2_ID;
break;
case STI_GDP_3:
plane_id = GAM_DEPTH_GDP3_ID;
break;
case STI_HQVDP_0:
plane_id = GAM_DEPTH_VID0_ID;
break;
case STI_CURSOR:
/* no need to set depth for cursor */
return 0;
default:
DRM_ERROR("Unknown plane %d\n", plane->desc);
return 1;
}
/* Search if a previous depth was already assigned to the plane */
val = sti_mixer_reg_read(mixer, GAM_MIXER_CRB);
for (i = 0; i < GAM_MIXER_NB_DEPTH_LEVEL; i++) {
mask = GAM_DEPTH_MASK_ID << (3 * i);
if ((val & mask) == plane_id << (3 * i))
break;
}
mask |= GAM_DEPTH_MASK_ID << (3 * depth);
plane_id = plane_id << (3 * depth);
DRM_DEBUG_DRIVER("%s %s depth=%d\n", sti_mixer_to_str(mixer),
sti_plane_to_str(plane), depth);
dev_dbg(mixer->dev, "GAM_MIXER_CRB val 0x%x mask 0x%x\n",
plane_id, mask);
val &= ~mask;
val |= plane_id;
sti_mixer_reg_write(mixer, GAM_MIXER_CRB, val);
dev_dbg(mixer->dev, "Read GAM_MIXER_CRB 0x%x\n",
sti_mixer_reg_read(mixer, GAM_MIXER_CRB));
return 0;
}
int sti_mixer_active_video_area(struct sti_mixer *mixer,
struct drm_display_mode *mode)
{
u32 ydo, xdo, yds, xds;
ydo = sti_vtg_get_line_number(*mode, 0);
yds = sti_vtg_get_line_number(*mode, mode->vdisplay - 1);
xdo = sti_vtg_get_pixel_number(*mode, 0);
xds = sti_vtg_get_pixel_number(*mode, mode->hdisplay - 1);
DRM_DEBUG_DRIVER("%s active video area xdo:%d ydo:%d xds:%d yds:%d\n",
sti_mixer_to_str(mixer), xdo, ydo, xds, yds);
sti_mixer_reg_write(mixer, GAM_MIXER_AVO, ydo << 16 | xdo);
sti_mixer_reg_write(mixer, GAM_MIXER_AVS, yds << 16 | xds);
sti_mixer_set_background_color(mixer, bkg_color);
sti_mixer_set_background_area(mixer, mode);
sti_mixer_set_background_status(mixer, true);
return 0;
}
static u32 sti_mixer_get_plane_mask(struct sti_plane *plane)
{
switch (plane->desc) {
case STI_BACK:
return GAM_CTL_BACK_MASK;
case STI_GDP_0:
return GAM_CTL_GDP0_MASK;
case STI_GDP_1:
return GAM_CTL_GDP1_MASK;
case STI_GDP_2:
return GAM_CTL_GDP2_MASK;
case STI_GDP_3:
return GAM_CTL_GDP3_MASK;
case STI_HQVDP_0:
return GAM_CTL_VID0_MASK;
case STI_CURSOR:
return GAM_CTL_CURSOR_MASK;
default:
return 0;
}
}
int sti_mixer_set_plane_status(struct sti_mixer *mixer,
struct sti_plane *plane, bool status)
{
u32 mask, val;
DRM_DEBUG_DRIVER("%s %s %s\n", status ? "enable" : "disable",
sti_mixer_to_str(mixer), sti_plane_to_str(plane));
mask = sti_mixer_get_plane_mask(plane);
if (!mask) {
DRM_ERROR("Can't find layer mask\n");
return -EINVAL;
}
val = sti_mixer_reg_read(mixer, GAM_MIXER_CTL);
val &= ~mask;
val |= status ? mask : 0;
sti_mixer_reg_write(mixer, GAM_MIXER_CTL, val);
return 0;
}
struct sti_mixer *sti_mixer_create(struct device *dev,
struct drm_device *drm_dev,
int id,
void __iomem *baseaddr)
{
struct sti_mixer *mixer = devm_kzalloc(dev, sizeof(*mixer), GFP_KERNEL);
dev_dbg(dev, "%s\n", __func__);
if (!mixer) {
DRM_ERROR("Failed to allocated memory for mixer\n");
return NULL;
}
mixer->regs = baseaddr;
mixer->dev = dev;
mixer->id = id;
DRM_DEBUG_DRIVER("%s created. Regs=%p\n",
sti_mixer_to_str(mixer), mixer->regs);
return mixer;
}
@@ -0,0 +1,69 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
* Fabien Dessenne <fabien.dessenne@st.com>
* for STMicroelectronics.
*/
#ifndef _STI_MIXER_H_
#define _STI_MIXER_H_
#include <drm/drm_crtc.h>
#include <drm/drm_debugfs.h>
#include <drm/drm_file.h>
#include "sti_plane.h"
struct device;
#define to_sti_mixer(x) container_of(x, struct sti_mixer, drm_crtc)
enum sti_mixer_status {
STI_MIXER_READY,
STI_MIXER_DISABLING,
STI_MIXER_DISABLED,
};
/**
* STI Mixer subdevice structure
*
* @dev: driver device
* @regs: mixer registers
* @id: id of the mixer
* @drm_crtc: crtc object link to the mixer
* @status: to know the status of the mixer
*/
struct sti_mixer {
struct device *dev;
void __iomem *regs;
int id;
struct drm_crtc drm_crtc;
enum sti_mixer_status status;
};
const char *sti_mixer_to_str(struct sti_mixer *mixer);
struct sti_mixer *sti_mixer_create(struct device *dev,
struct drm_device *drm_dev,
int id,
void __iomem *baseaddr);
int sti_mixer_set_plane_status(struct sti_mixer *mixer,
struct sti_plane *plane, bool status);
int sti_mixer_set_plane_depth(struct sti_mixer *mixer, struct sti_plane *plane);
int sti_mixer_active_video_area(struct sti_mixer *mixer,
struct drm_display_mode *mode);
void sti_mixer_set_background_status(struct sti_mixer *mixer, bool enable);
void sti_mixer_debugfs_init(struct sti_mixer *mixer, struct drm_minor *minor);
/* depth in Cross-bar control = z order */
#define GAM_MIXER_NB_DEPTH_LEVEL 6
#define STI_MIXER_MAIN 0
#define STI_MIXER_AUX 1
#endif
+140
View File
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
* Fabien Dessenne <fabien.dessenne@st.com>
* for STMicroelectronics.
*/
#include <linux/types.h>
#include <drm/drm_blend.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_print.h>
#include "sti_compositor.h"
#include "sti_drv.h"
#include "sti_plane.h"
const char *sti_plane_to_str(struct sti_plane *plane)
{
switch (plane->desc) {
case STI_GDP_0:
return "GDP0";
case STI_GDP_1:
return "GDP1";
case STI_GDP_2:
return "GDP2";
case STI_GDP_3:
return "GDP3";
case STI_HQVDP_0:
return "HQVDP0";
case STI_CURSOR:
return "CURSOR";
default:
return "<UNKNOWN PLANE>";
}
}
#define STI_FPS_INTERVAL_MS 3000
void sti_plane_update_fps(struct sti_plane *plane,
bool new_frame,
bool new_field)
{
struct drm_plane_state *state = plane->drm_plane.state;
ktime_t now;
struct sti_fps_info *fps;
int fpks, fipks, ms_since_last, num_frames, num_fields;
now = ktime_get();
/* Compute number of frame updates */
fps = &plane->fps_info;
if (new_field)
fps->curr_field_counter++;
/* do not perform fps calcul if new_frame is false */
if (!new_frame)
return;
fps->curr_frame_counter++;
ms_since_last = ktime_to_ms(ktime_sub(now, fps->last_timestamp));
num_frames = fps->curr_frame_counter - fps->last_frame_counter;
if (num_frames <= 0 || ms_since_last < STI_FPS_INTERVAL_MS)
return;
fps->last_timestamp = now;
fps->last_frame_counter = fps->curr_frame_counter;
if (state->fb) {
fpks = (num_frames * 1000000) / ms_since_last;
snprintf(plane->fps_info.fps_str, FPS_LENGTH,
"%-8s %4dx%-4d %.4s @ %3d.%-3.3d fps (%s)",
plane->drm_plane.name,
state->fb->width,
state->fb->height,
(char *)&state->fb->format->format,
fpks / 1000, fpks % 1000,
sti_plane_to_str(plane));
}
if (fps->curr_field_counter) {
/* Compute number of field updates */
num_fields = fps->curr_field_counter - fps->last_field_counter;
fps->last_field_counter = fps->curr_field_counter;
fipks = (num_fields * 1000000) / ms_since_last;
snprintf(plane->fps_info.fips_str,
FPS_LENGTH, " - %3d.%-3.3d field/sec",
fipks / 1000, fipks % 1000);
} else {
plane->fps_info.fips_str[0] = '\0';
}
if (fps->output)
DRM_INFO("%s%s\n",
plane->fps_info.fps_str,
plane->fps_info.fips_str);
}
static int sti_plane_get_default_zpos(enum drm_plane_type type)
{
switch (type) {
case DRM_PLANE_TYPE_PRIMARY:
return 0;
case DRM_PLANE_TYPE_OVERLAY:
return 1;
case DRM_PLANE_TYPE_CURSOR:
return 7;
}
return 0;
}
static void sti_plane_attach_zorder_property(struct drm_plane *drm_plane,
enum drm_plane_type type)
{
int zpos = sti_plane_get_default_zpos(type);
switch (type) {
case DRM_PLANE_TYPE_PRIMARY:
case DRM_PLANE_TYPE_OVERLAY:
drm_plane_create_zpos_property(drm_plane, zpos, 0, 6);
break;
case DRM_PLANE_TYPE_CURSOR:
drm_plane_create_zpos_immutable_property(drm_plane, zpos);
break;
}
}
void sti_plane_init_property(struct sti_plane *plane,
enum drm_plane_type type)
{
sti_plane_attach_zorder_property(&plane->drm_plane, type);
DRM_DEBUG_DRIVER("drm plane:%d mapped to %s\n",
plane->drm_plane.base.id, sti_plane_to_str(plane));
}
@@ -0,0 +1,83 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
*/
#ifndef _STI_PLANE_H_
#define _STI_PLANE_H_
#include <drm/drm_atomic_helper.h>
#define to_sti_plane(x) container_of(x, struct sti_plane, drm_plane)
#define STI_PLANE_TYPE_SHIFT 8
#define STI_PLANE_TYPE_MASK (~((1 << STI_PLANE_TYPE_SHIFT) - 1))
enum sti_plane_type {
STI_GDP = 1 << STI_PLANE_TYPE_SHIFT,
STI_VDP = 2 << STI_PLANE_TYPE_SHIFT,
STI_CUR = 3 << STI_PLANE_TYPE_SHIFT,
STI_BCK = 4 << STI_PLANE_TYPE_SHIFT
};
enum sti_plane_id_of_type {
STI_ID_0 = 0,
STI_ID_1 = 1,
STI_ID_2 = 2,
STI_ID_3 = 3
};
enum sti_plane_desc {
STI_GDP_0 = STI_GDP | STI_ID_0,
STI_GDP_1 = STI_GDP | STI_ID_1,
STI_GDP_2 = STI_GDP | STI_ID_2,
STI_GDP_3 = STI_GDP | STI_ID_3,
STI_HQVDP_0 = STI_VDP | STI_ID_0,
STI_CURSOR = STI_CUR,
STI_BACK = STI_BCK
};
enum sti_plane_status {
STI_PLANE_READY,
STI_PLANE_UPDATED,
STI_PLANE_DISABLING,
STI_PLANE_FLUSHING,
STI_PLANE_DISABLED,
};
#define FPS_LENGTH 128
struct sti_fps_info {
bool output;
unsigned int curr_frame_counter;
unsigned int last_frame_counter;
unsigned int curr_field_counter;
unsigned int last_field_counter;
ktime_t last_timestamp;
char fps_str[FPS_LENGTH];
char fips_str[FPS_LENGTH];
};
/**
* STI plane structure
*
* @plane: drm plane it is bound to (if any)
* @desc: plane type & id
* @status: to know the status of the plane
* @fps_info: frame per second info
*/
struct sti_plane {
struct drm_plane drm_plane;
enum sti_plane_desc desc;
enum sti_plane_status status;
struct sti_fps_info fps_info;
};
const char *sti_plane_to_str(struct sti_plane *plane);
void sti_plane_update_fps(struct sti_plane *plane,
bool new_frame,
bool new_field);
void sti_plane_init_property(struct sti_plane *plane,
enum drm_plane_type type);
#endif
+889
View File
@@ -0,0 +1,889 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
* Vincent Abriou <vincent.abriou@st.com>
* for STMicroelectronics.
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/seq_file.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_debugfs.h>
#include <drm/drm_device.h>
#include <drm/drm_file.h>
#include <drm/drm_print.h>
#include "sti_crtc.h"
#include "sti_drv.h"
#include "sti_vtg.h"
/* glue registers */
#define TVO_CSC_MAIN_M0 0x000
#define TVO_CSC_MAIN_M1 0x004
#define TVO_CSC_MAIN_M2 0x008
#define TVO_CSC_MAIN_M3 0x00c
#define TVO_CSC_MAIN_M4 0x010
#define TVO_CSC_MAIN_M5 0x014
#define TVO_CSC_MAIN_M6 0x018
#define TVO_CSC_MAIN_M7 0x01c
#define TVO_MAIN_IN_VID_FORMAT 0x030
#define TVO_CSC_AUX_M0 0x100
#define TVO_CSC_AUX_M1 0x104
#define TVO_CSC_AUX_M2 0x108
#define TVO_CSC_AUX_M3 0x10c
#define TVO_CSC_AUX_M4 0x110
#define TVO_CSC_AUX_M5 0x114
#define TVO_CSC_AUX_M6 0x118
#define TVO_CSC_AUX_M7 0x11c
#define TVO_AUX_IN_VID_FORMAT 0x130
#define TVO_VIP_HDF 0x400
#define TVO_HD_SYNC_SEL 0x418
#define TVO_HD_DAC_CFG_OFF 0x420
#define TVO_VIP_HDMI 0x500
#define TVO_HDMI_FORCE_COLOR_0 0x504
#define TVO_HDMI_FORCE_COLOR_1 0x508
#define TVO_HDMI_CLIP_VALUE_B_CB 0x50c
#define TVO_HDMI_CLIP_VALUE_Y_G 0x510
#define TVO_HDMI_CLIP_VALUE_R_CR 0x514
#define TVO_HDMI_SYNC_SEL 0x518
#define TVO_HDMI_DFV_OBS 0x540
#define TVO_VIP_DVO 0x600
#define TVO_DVO_SYNC_SEL 0x618
#define TVO_DVO_CONFIG 0x620
#define TVO_IN_FMT_SIGNED BIT(0)
#define TVO_SYNC_EXT BIT(4)
#define TVO_VIP_REORDER_R_SHIFT 24
#define TVO_VIP_REORDER_G_SHIFT 20
#define TVO_VIP_REORDER_B_SHIFT 16
#define TVO_VIP_REORDER_MASK 0x3
#define TVO_VIP_REORDER_Y_G_SEL 0
#define TVO_VIP_REORDER_CB_B_SEL 1
#define TVO_VIP_REORDER_CR_R_SEL 2
#define TVO_VIP_CLIP_SHIFT 8
#define TVO_VIP_CLIP_MASK 0x7
#define TVO_VIP_CLIP_DISABLED 0
#define TVO_VIP_CLIP_EAV_SAV 1
#define TVO_VIP_CLIP_LIMITED_RANGE_RGB_Y 2
#define TVO_VIP_CLIP_LIMITED_RANGE_CB_CR 3
#define TVO_VIP_CLIP_PROG_RANGE 4
#define TVO_VIP_RND_SHIFT 4
#define TVO_VIP_RND_MASK 0x3
#define TVO_VIP_RND_8BIT_ROUNDED 0
#define TVO_VIP_RND_10BIT_ROUNDED 1
#define TVO_VIP_RND_12BIT_ROUNDED 2
#define TVO_VIP_SEL_INPUT_MASK 0xf
#define TVO_VIP_SEL_INPUT_MAIN 0x0
#define TVO_VIP_SEL_INPUT_AUX 0x8
#define TVO_VIP_SEL_INPUT_FORCE_COLOR 0xf
#define TVO_VIP_SEL_INPUT_BYPASS_MASK 0x1
#define TVO_VIP_SEL_INPUT_BYPASSED 1
#define TVO_SYNC_MAIN_VTG_SET_REF 0x00
#define TVO_SYNC_AUX_VTG_SET_REF 0x10
#define TVO_SYNC_HD_DCS_SHIFT 8
#define TVO_SYNC_DVO_PAD_HSYNC_SHIFT 8
#define TVO_SYNC_DVO_PAD_VSYNC_SHIFT 16
#define ENCODER_CRTC_MASK (BIT(0) | BIT(1))
#define TVO_MIN_HD_HEIGHT 720
/* enum listing the supported output data format */
enum sti_tvout_video_out_type {
STI_TVOUT_VIDEO_OUT_RGB,
STI_TVOUT_VIDEO_OUT_YUV,
};
struct sti_tvout {
struct device *dev;
struct drm_device *drm_dev;
void __iomem *regs;
struct reset_control *reset;
struct drm_encoder *hdmi;
struct drm_encoder *hda;
struct drm_encoder *dvo;
bool debugfs_registered;
};
struct sti_tvout_encoder {
struct drm_encoder encoder;
struct sti_tvout *tvout;
};
#define to_sti_tvout_encoder(x) \
container_of(x, struct sti_tvout_encoder, encoder)
#define to_sti_tvout(x) to_sti_tvout_encoder(x)->tvout
/* preformatter conversion matrix */
static const u32 rgb_to_ycbcr_601[8] = {
0xF927082E, 0x04C9FEAB, 0x01D30964, 0xFA95FD3D,
0x0000082E, 0x00002000, 0x00002000, 0x00000000
};
/* 709 RGB to YCbCr */
static const u32 rgb_to_ycbcr_709[8] = {
0xF891082F, 0x0367FF40, 0x01280B71, 0xF9B1FE20,
0x0000082F, 0x00002000, 0x00002000, 0x00000000
};
static u32 tvout_read(struct sti_tvout *tvout, int offset)
{
return readl(tvout->regs + offset);
}
static void tvout_write(struct sti_tvout *tvout, u32 val, int offset)
{
writel(val, tvout->regs + offset);
}
/**
* tvout_vip_set_color_order - Set the clipping mode of a VIP
*
* @tvout: tvout structure
* @reg: register to set
* @cr_r: red chroma or red order
* @y_g: y or green order
* @cb_b: blue chroma or blue order
*/
static void tvout_vip_set_color_order(struct sti_tvout *tvout, int reg,
u32 cr_r, u32 y_g, u32 cb_b)
{
u32 val = tvout_read(tvout, reg);
val &= ~(TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_R_SHIFT);
val &= ~(TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_G_SHIFT);
val &= ~(TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_B_SHIFT);
val |= cr_r << TVO_VIP_REORDER_R_SHIFT;
val |= y_g << TVO_VIP_REORDER_G_SHIFT;
val |= cb_b << TVO_VIP_REORDER_B_SHIFT;
tvout_write(tvout, val, reg);
}
/**
* tvout_vip_set_clip_mode - Set the clipping mode of a VIP
*
* @tvout: tvout structure
* @reg: register to set
* @range: clipping range
*/
static void tvout_vip_set_clip_mode(struct sti_tvout *tvout, int reg, u32 range)
{
u32 val = tvout_read(tvout, reg);
val &= ~(TVO_VIP_CLIP_MASK << TVO_VIP_CLIP_SHIFT);
val |= range << TVO_VIP_CLIP_SHIFT;
tvout_write(tvout, val, reg);
}
/**
* tvout_vip_set_rnd - Set the rounded value of a VIP
*
* @tvout: tvout structure
* @reg: register to set
* @rnd: rounded val per component
*/
static void tvout_vip_set_rnd(struct sti_tvout *tvout, int reg, u32 rnd)
{
u32 val = tvout_read(tvout, reg);
val &= ~(TVO_VIP_RND_MASK << TVO_VIP_RND_SHIFT);
val |= rnd << TVO_VIP_RND_SHIFT;
tvout_write(tvout, val, reg);
}
/**
* tvout_vip_set_sel_input - Select the VIP input
*
* @tvout: tvout structure
* @reg: register to set
* @main_path: main or auxiliary path
* @video_out: selected_input (main/aux + conv)
*/
static void tvout_vip_set_sel_input(struct sti_tvout *tvout,
int reg,
bool main_path,
enum sti_tvout_video_out_type video_out)
{
u32 sel_input;
u32 val = tvout_read(tvout, reg);
if (main_path)
sel_input = TVO_VIP_SEL_INPUT_MAIN;
else
sel_input = TVO_VIP_SEL_INPUT_AUX;
switch (video_out) {
case STI_TVOUT_VIDEO_OUT_RGB:
sel_input |= TVO_VIP_SEL_INPUT_BYPASSED;
break;
case STI_TVOUT_VIDEO_OUT_YUV:
sel_input &= ~TVO_VIP_SEL_INPUT_BYPASSED;
break;
}
/* on stih407 chip the sel_input bypass mode logic is inverted */
sel_input = sel_input ^ TVO_VIP_SEL_INPUT_BYPASS_MASK;
val &= ~TVO_VIP_SEL_INPUT_MASK;
val |= sel_input;
tvout_write(tvout, val, reg);
}
/**
* tvout_vip_set_in_vid_fmt - Select the input video signed or unsigned
*
* @tvout: tvout structure
* @reg: register to set
* @in_vid_fmt: used video input format
*/
static void tvout_vip_set_in_vid_fmt(struct sti_tvout *tvout,
int reg, u32 in_vid_fmt)
{
u32 val = tvout_read(tvout, reg);
val &= ~TVO_IN_FMT_SIGNED;
val |= in_vid_fmt;
tvout_write(tvout, val, reg);
}
/**
* tvout_preformatter_set_matrix - Set preformatter matrix
*
* @tvout: tvout structure
* @mode: display mode structure
*/
static void tvout_preformatter_set_matrix(struct sti_tvout *tvout,
struct drm_display_mode *mode)
{
unsigned int i;
const u32 *pf_matrix;
if (mode->vdisplay >= TVO_MIN_HD_HEIGHT)
pf_matrix = rgb_to_ycbcr_709;
else
pf_matrix = rgb_to_ycbcr_601;
for (i = 0; i < 8; i++) {
tvout_write(tvout, *(pf_matrix + i),
TVO_CSC_MAIN_M0 + (i * 4));
tvout_write(tvout, *(pf_matrix + i),
TVO_CSC_AUX_M0 + (i * 4));
}
}
/**
* tvout_dvo_start - Start VIP block for DVO output
*
* @tvout: pointer on tvout structure
* @main_path: true if main path has to be used in the vip configuration
* else aux path is used.
*/
static void tvout_dvo_start(struct sti_tvout *tvout, bool main_path)
{
u32 tvo_in_vid_format;
int val, tmp;
dev_dbg(tvout->dev, "%s\n", __func__);
if (main_path) {
DRM_DEBUG_DRIVER("main vip for DVO\n");
/* Select the input sync for dvo */
tmp = TVO_SYNC_MAIN_VTG_SET_REF | VTG_SYNC_ID_DVO;
val = tmp << TVO_SYNC_DVO_PAD_VSYNC_SHIFT;
val |= tmp << TVO_SYNC_DVO_PAD_HSYNC_SHIFT;
val |= tmp;
tvout_write(tvout, val, TVO_DVO_SYNC_SEL);
tvo_in_vid_format = TVO_MAIN_IN_VID_FORMAT;
} else {
DRM_DEBUG_DRIVER("aux vip for DVO\n");
/* Select the input sync for dvo */
tmp = TVO_SYNC_AUX_VTG_SET_REF | VTG_SYNC_ID_DVO;
val = tmp << TVO_SYNC_DVO_PAD_VSYNC_SHIFT;
val |= tmp << TVO_SYNC_DVO_PAD_HSYNC_SHIFT;
val |= tmp;
tvout_write(tvout, val, TVO_DVO_SYNC_SEL);
tvo_in_vid_format = TVO_AUX_IN_VID_FORMAT;
}
/* Set color channel order */
tvout_vip_set_color_order(tvout, TVO_VIP_DVO,
TVO_VIP_REORDER_CR_R_SEL,
TVO_VIP_REORDER_Y_G_SEL,
TVO_VIP_REORDER_CB_B_SEL);
/* Set clipping mode */
tvout_vip_set_clip_mode(tvout, TVO_VIP_DVO, TVO_VIP_CLIP_DISABLED);
/* Set round mode (rounded to 8-bit per component) */
tvout_vip_set_rnd(tvout, TVO_VIP_DVO, TVO_VIP_RND_8BIT_ROUNDED);
/* Set input video format */
tvout_vip_set_in_vid_fmt(tvout, tvo_in_vid_format, TVO_IN_FMT_SIGNED);
/* Input selection */
tvout_vip_set_sel_input(tvout, TVO_VIP_DVO, main_path,
STI_TVOUT_VIDEO_OUT_RGB);
}
/**
* tvout_hdmi_start - Start VIP block for HDMI output
*
* @tvout: pointer on tvout structure
* @main_path: true if main path has to be used in the vip configuration
* else aux path is used.
*/
static void tvout_hdmi_start(struct sti_tvout *tvout, bool main_path)
{
u32 tvo_in_vid_format;
dev_dbg(tvout->dev, "%s\n", __func__);
if (main_path) {
DRM_DEBUG_DRIVER("main vip for hdmi\n");
/* select the input sync for hdmi */
tvout_write(tvout,
TVO_SYNC_MAIN_VTG_SET_REF | VTG_SYNC_ID_HDMI,
TVO_HDMI_SYNC_SEL);
tvo_in_vid_format = TVO_MAIN_IN_VID_FORMAT;
} else {
DRM_DEBUG_DRIVER("aux vip for hdmi\n");
/* select the input sync for hdmi */
tvout_write(tvout,
TVO_SYNC_AUX_VTG_SET_REF | VTG_SYNC_ID_HDMI,
TVO_HDMI_SYNC_SEL);
tvo_in_vid_format = TVO_AUX_IN_VID_FORMAT;
}
/* set color channel order */
tvout_vip_set_color_order(tvout, TVO_VIP_HDMI,
TVO_VIP_REORDER_CR_R_SEL,
TVO_VIP_REORDER_Y_G_SEL,
TVO_VIP_REORDER_CB_B_SEL);
/* set clipping mode */
tvout_vip_set_clip_mode(tvout, TVO_VIP_HDMI, TVO_VIP_CLIP_DISABLED);
/* set round mode (rounded to 8-bit per component) */
tvout_vip_set_rnd(tvout, TVO_VIP_HDMI, TVO_VIP_RND_8BIT_ROUNDED);
/* set input video format */
tvout_vip_set_in_vid_fmt(tvout, tvo_in_vid_format, TVO_IN_FMT_SIGNED);
/* input selection */
tvout_vip_set_sel_input(tvout, TVO_VIP_HDMI, main_path,
STI_TVOUT_VIDEO_OUT_RGB);
}
/**
* tvout_hda_start - Start HDF VIP and HD DAC
*
* @tvout: pointer on tvout structure
* @main_path: true if main path has to be used in the vip configuration
* else aux path is used.
*/
static void tvout_hda_start(struct sti_tvout *tvout, bool main_path)
{
u32 tvo_in_vid_format;
int val;
dev_dbg(tvout->dev, "%s\n", __func__);
if (main_path) {
DRM_DEBUG_DRIVER("main vip for HDF\n");
/* Select the input sync for HD analog and HD DCS */
val = TVO_SYNC_MAIN_VTG_SET_REF | VTG_SYNC_ID_HDDCS;
val = val << TVO_SYNC_HD_DCS_SHIFT;
val |= TVO_SYNC_MAIN_VTG_SET_REF | VTG_SYNC_ID_HDF;
tvout_write(tvout, val, TVO_HD_SYNC_SEL);
tvo_in_vid_format = TVO_MAIN_IN_VID_FORMAT;
} else {
DRM_DEBUG_DRIVER("aux vip for HDF\n");
/* Select the input sync for HD analog and HD DCS */
val = TVO_SYNC_AUX_VTG_SET_REF | VTG_SYNC_ID_HDDCS;
val = val << TVO_SYNC_HD_DCS_SHIFT;
val |= TVO_SYNC_AUX_VTG_SET_REF | VTG_SYNC_ID_HDF;
tvout_write(tvout, val, TVO_HD_SYNC_SEL);
tvo_in_vid_format = TVO_AUX_IN_VID_FORMAT;
}
/* set color channel order */
tvout_vip_set_color_order(tvout, TVO_VIP_HDF,
TVO_VIP_REORDER_CR_R_SEL,
TVO_VIP_REORDER_Y_G_SEL,
TVO_VIP_REORDER_CB_B_SEL);
/* set clipping mode */
tvout_vip_set_clip_mode(tvout, TVO_VIP_HDF, TVO_VIP_CLIP_DISABLED);
/* set round mode (rounded to 10-bit per component) */
tvout_vip_set_rnd(tvout, TVO_VIP_HDF, TVO_VIP_RND_10BIT_ROUNDED);
/* Set input video format */
tvout_vip_set_in_vid_fmt(tvout, tvo_in_vid_format, TVO_IN_FMT_SIGNED);
/* Input selection */
tvout_vip_set_sel_input(tvout, TVO_VIP_HDF, main_path,
STI_TVOUT_VIDEO_OUT_YUV);
/* power up HD DAC */
tvout_write(tvout, 0, TVO_HD_DAC_CFG_OFF);
}
#define DBGFS_DUMP(reg) seq_printf(s, "\n %-25s 0x%08X", #reg, \
readl(tvout->regs + reg))
static void tvout_dbg_vip(struct seq_file *s, int val)
{
int r, g, b, tmp, mask;
char *const reorder[] = {"Y_G", "Cb_B", "Cr_R"};
char *const clipping[] = {"No", "EAV/SAV", "Limited range RGB/Y",
"Limited range Cb/Cr", "decided by register"};
char *const round[] = {"8-bit", "10-bit", "12-bit"};
char *const input_sel[] = {"Main (color matrix enabled)",
"Main (color matrix by-passed)",
"", "", "", "", "", "",
"Aux (color matrix enabled)",
"Aux (color matrix by-passed)",
"", "", "", "", "", "Force value"};
seq_putc(s, '\t');
mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_R_SHIFT;
r = (val & mask) >> TVO_VIP_REORDER_R_SHIFT;
mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_G_SHIFT;
g = (val & mask) >> TVO_VIP_REORDER_G_SHIFT;
mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_B_SHIFT;
b = (val & mask) >> TVO_VIP_REORDER_B_SHIFT;
seq_printf(s, "%-24s %s->%s %s->%s %s->%s\n", "Reorder:",
reorder[r], reorder[TVO_VIP_REORDER_CR_R_SEL],
reorder[g], reorder[TVO_VIP_REORDER_Y_G_SEL],
reorder[b], reorder[TVO_VIP_REORDER_CB_B_SEL]);
seq_puts(s, "\t\t\t\t\t");
mask = TVO_VIP_CLIP_MASK << TVO_VIP_CLIP_SHIFT;
tmp = (val & mask) >> TVO_VIP_CLIP_SHIFT;
seq_printf(s, "%-24s %s\n", "Clipping:", clipping[tmp]);
seq_puts(s, "\t\t\t\t\t");
mask = TVO_VIP_RND_MASK << TVO_VIP_RND_SHIFT;
tmp = (val & mask) >> TVO_VIP_RND_SHIFT;
seq_printf(s, "%-24s input data rounded to %s per component\n",
"Round:", round[tmp]);
seq_puts(s, "\t\t\t\t\t");
tmp = (val & TVO_VIP_SEL_INPUT_MASK);
seq_printf(s, "%-24s %s", "Input selection:", input_sel[tmp]);
}
static void tvout_dbg_hd_dac_cfg(struct seq_file *s, int val)
{
seq_printf(s, "\t%-24s %s", "HD DAC:",
val & 1 ? "disabled" : "enabled");
}
static int tvout_dbg_show(struct seq_file *s, void *data)
{
struct drm_info_node *node = s->private;
struct sti_tvout *tvout = (struct sti_tvout *)node->info_ent->data;
struct drm_crtc *crtc;
seq_printf(s, "TVOUT: (vaddr = 0x%p)", tvout->regs);
seq_puts(s, "\n\n HDMI encoder: ");
crtc = tvout->hdmi->crtc;
if (crtc) {
seq_printf(s, "connected to %s path",
sti_crtc_is_main(crtc) ? "main" : "aux");
DBGFS_DUMP(TVO_HDMI_SYNC_SEL);
DBGFS_DUMP(TVO_VIP_HDMI);
tvout_dbg_vip(s, readl(tvout->regs + TVO_VIP_HDMI));
} else {
seq_puts(s, "disabled");
}
seq_puts(s, "\n\n DVO encoder: ");
crtc = tvout->dvo->crtc;
if (crtc) {
seq_printf(s, "connected to %s path",
sti_crtc_is_main(crtc) ? "main" : "aux");
DBGFS_DUMP(TVO_DVO_SYNC_SEL);
DBGFS_DUMP(TVO_DVO_CONFIG);
DBGFS_DUMP(TVO_VIP_DVO);
tvout_dbg_vip(s, readl(tvout->regs + TVO_VIP_DVO));
} else {
seq_puts(s, "disabled");
}
seq_puts(s, "\n\n HDA encoder: ");
crtc = tvout->hda->crtc;
if (crtc) {
seq_printf(s, "connected to %s path",
sti_crtc_is_main(crtc) ? "main" : "aux");
DBGFS_DUMP(TVO_HD_SYNC_SEL);
DBGFS_DUMP(TVO_HD_DAC_CFG_OFF);
tvout_dbg_hd_dac_cfg(s,
readl(tvout->regs + TVO_HD_DAC_CFG_OFF));
DBGFS_DUMP(TVO_VIP_HDF);
tvout_dbg_vip(s, readl(tvout->regs + TVO_VIP_HDF));
} else {
seq_puts(s, "disabled");
}
seq_puts(s, "\n\n main path configuration");
DBGFS_DUMP(TVO_CSC_MAIN_M0);
DBGFS_DUMP(TVO_CSC_MAIN_M1);
DBGFS_DUMP(TVO_CSC_MAIN_M2);
DBGFS_DUMP(TVO_CSC_MAIN_M3);
DBGFS_DUMP(TVO_CSC_MAIN_M4);
DBGFS_DUMP(TVO_CSC_MAIN_M5);
DBGFS_DUMP(TVO_CSC_MAIN_M6);
DBGFS_DUMP(TVO_CSC_MAIN_M7);
DBGFS_DUMP(TVO_MAIN_IN_VID_FORMAT);
seq_puts(s, "\n\n auxiliary path configuration");
DBGFS_DUMP(TVO_CSC_AUX_M0);
DBGFS_DUMP(TVO_CSC_AUX_M2);
DBGFS_DUMP(TVO_CSC_AUX_M3);
DBGFS_DUMP(TVO_CSC_AUX_M4);
DBGFS_DUMP(TVO_CSC_AUX_M5);
DBGFS_DUMP(TVO_CSC_AUX_M6);
DBGFS_DUMP(TVO_CSC_AUX_M7);
DBGFS_DUMP(TVO_AUX_IN_VID_FORMAT);
seq_putc(s, '\n');
return 0;
}
static struct drm_info_list tvout_debugfs_files[] = {
{ "tvout", tvout_dbg_show, 0, NULL },
};
static void tvout_debugfs_init(struct sti_tvout *tvout, struct drm_minor *minor)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(tvout_debugfs_files); i++)
tvout_debugfs_files[i].data = tvout;
drm_debugfs_create_files(tvout_debugfs_files,
ARRAY_SIZE(tvout_debugfs_files),
minor->debugfs_root, minor);
}
static void sti_tvout_encoder_dpms(struct drm_encoder *encoder, int mode)
{
}
static void sti_tvout_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
}
static void sti_tvout_encoder_destroy(struct drm_encoder *encoder)
{
struct sti_tvout_encoder *sti_encoder = to_sti_tvout_encoder(encoder);
drm_encoder_cleanup(encoder);
kfree(sti_encoder);
}
static int sti_tvout_late_register(struct drm_encoder *encoder)
{
struct sti_tvout *tvout = to_sti_tvout(encoder);
if (tvout->debugfs_registered)
return 0;
tvout_debugfs_init(tvout, encoder->dev->primary);
tvout->debugfs_registered = true;
return 0;
}
static void sti_tvout_early_unregister(struct drm_encoder *encoder)
{
struct sti_tvout *tvout = to_sti_tvout(encoder);
if (!tvout->debugfs_registered)
return;
tvout->debugfs_registered = false;
}
static const struct drm_encoder_funcs sti_tvout_encoder_funcs = {
.destroy = sti_tvout_encoder_destroy,
.late_register = sti_tvout_late_register,
.early_unregister = sti_tvout_early_unregister,
};
static void sti_dvo_encoder_enable(struct drm_encoder *encoder)
{
struct sti_tvout *tvout = to_sti_tvout(encoder);
tvout_preformatter_set_matrix(tvout, &encoder->crtc->mode);
tvout_dvo_start(tvout, sti_crtc_is_main(encoder->crtc));
}
static void sti_dvo_encoder_disable(struct drm_encoder *encoder)
{
struct sti_tvout *tvout = to_sti_tvout(encoder);
/* Reset VIP register */
tvout_write(tvout, 0x0, TVO_VIP_DVO);
}
static const struct drm_encoder_helper_funcs sti_dvo_encoder_helper_funcs = {
.dpms = sti_tvout_encoder_dpms,
.mode_set = sti_tvout_encoder_mode_set,
.enable = sti_dvo_encoder_enable,
.disable = sti_dvo_encoder_disable,
};
static struct drm_encoder *
sti_tvout_create_dvo_encoder(struct drm_device *dev,
struct sti_tvout *tvout)
{
struct sti_tvout_encoder *encoder;
struct drm_encoder *drm_encoder;
encoder = devm_kzalloc(tvout->dev, sizeof(*encoder), GFP_KERNEL);
if (!encoder)
return NULL;
encoder->tvout = tvout;
drm_encoder = &encoder->encoder;
drm_encoder->possible_crtcs = ENCODER_CRTC_MASK;
drm_encoder_init(dev, drm_encoder,
&sti_tvout_encoder_funcs, DRM_MODE_ENCODER_LVDS,
NULL);
drm_encoder_helper_add(drm_encoder, &sti_dvo_encoder_helper_funcs);
return drm_encoder;
}
static void sti_hda_encoder_enable(struct drm_encoder *encoder)
{
struct sti_tvout *tvout = to_sti_tvout(encoder);
tvout_preformatter_set_matrix(tvout, &encoder->crtc->mode);
tvout_hda_start(tvout, sti_crtc_is_main(encoder->crtc));
}
static void sti_hda_encoder_disable(struct drm_encoder *encoder)
{
struct sti_tvout *tvout = to_sti_tvout(encoder);
/* reset VIP register */
tvout_write(tvout, 0x0, TVO_VIP_HDF);
/* power down HD DAC */
tvout_write(tvout, 1, TVO_HD_DAC_CFG_OFF);
}
static const struct drm_encoder_helper_funcs sti_hda_encoder_helper_funcs = {
.dpms = sti_tvout_encoder_dpms,
.mode_set = sti_tvout_encoder_mode_set,
.commit = sti_hda_encoder_enable,
.disable = sti_hda_encoder_disable,
};
static struct drm_encoder *sti_tvout_create_hda_encoder(struct drm_device *dev,
struct sti_tvout *tvout)
{
struct sti_tvout_encoder *encoder;
struct drm_encoder *drm_encoder;
encoder = devm_kzalloc(tvout->dev, sizeof(*encoder), GFP_KERNEL);
if (!encoder)
return NULL;
encoder->tvout = tvout;
drm_encoder = &encoder->encoder;
drm_encoder->possible_crtcs = ENCODER_CRTC_MASK;
drm_encoder_init(dev, drm_encoder,
&sti_tvout_encoder_funcs, DRM_MODE_ENCODER_DAC, NULL);
drm_encoder_helper_add(drm_encoder, &sti_hda_encoder_helper_funcs);
return drm_encoder;
}
static void sti_hdmi_encoder_enable(struct drm_encoder *encoder)
{
struct sti_tvout *tvout = to_sti_tvout(encoder);
tvout_preformatter_set_matrix(tvout, &encoder->crtc->mode);
tvout_hdmi_start(tvout, sti_crtc_is_main(encoder->crtc));
}
static void sti_hdmi_encoder_disable(struct drm_encoder *encoder)
{
struct sti_tvout *tvout = to_sti_tvout(encoder);
/* reset VIP register */
tvout_write(tvout, 0x0, TVO_VIP_HDMI);
}
static const struct drm_encoder_helper_funcs sti_hdmi_encoder_helper_funcs = {
.dpms = sti_tvout_encoder_dpms,
.mode_set = sti_tvout_encoder_mode_set,
.commit = sti_hdmi_encoder_enable,
.disable = sti_hdmi_encoder_disable,
};
static struct drm_encoder *sti_tvout_create_hdmi_encoder(struct drm_device *dev,
struct sti_tvout *tvout)
{
struct sti_tvout_encoder *encoder;
struct drm_encoder *drm_encoder;
encoder = devm_kzalloc(tvout->dev, sizeof(*encoder), GFP_KERNEL);
if (!encoder)
return NULL;
encoder->tvout = tvout;
drm_encoder = &encoder->encoder;
drm_encoder->possible_crtcs = ENCODER_CRTC_MASK;
drm_encoder_init(dev, drm_encoder,
&sti_tvout_encoder_funcs, DRM_MODE_ENCODER_TMDS, NULL);
drm_encoder_helper_add(drm_encoder, &sti_hdmi_encoder_helper_funcs);
return drm_encoder;
}
static void sti_tvout_create_encoders(struct drm_device *dev,
struct sti_tvout *tvout)
{
tvout->hdmi = sti_tvout_create_hdmi_encoder(dev, tvout);
tvout->hda = sti_tvout_create_hda_encoder(dev, tvout);
tvout->dvo = sti_tvout_create_dvo_encoder(dev, tvout);
tvout->hdmi->possible_clones = drm_encoder_mask(tvout->hdmi) |
drm_encoder_mask(tvout->hda) | drm_encoder_mask(tvout->dvo);
tvout->hda->possible_clones = drm_encoder_mask(tvout->hdmi) |
drm_encoder_mask(tvout->hda) | drm_encoder_mask(tvout->dvo);
tvout->dvo->possible_clones = drm_encoder_mask(tvout->hdmi) |
drm_encoder_mask(tvout->hda) | drm_encoder_mask(tvout->dvo);
}
static void sti_tvout_destroy_encoders(struct sti_tvout *tvout)
{
if (tvout->hdmi)
drm_encoder_cleanup(tvout->hdmi);
tvout->hdmi = NULL;
if (tvout->hda)
drm_encoder_cleanup(tvout->hda);
tvout->hda = NULL;
if (tvout->dvo)
drm_encoder_cleanup(tvout->dvo);
tvout->dvo = NULL;
}
static int sti_tvout_bind(struct device *dev, struct device *master, void *data)
{
struct sti_tvout *tvout = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
tvout->drm_dev = drm_dev;
sti_tvout_create_encoders(drm_dev, tvout);
return 0;
}
static void sti_tvout_unbind(struct device *dev, struct device *master,
void *data)
{
struct sti_tvout *tvout = dev_get_drvdata(dev);
sti_tvout_destroy_encoders(tvout);
}
static const struct component_ops sti_tvout_ops = {
.bind = sti_tvout_bind,
.unbind = sti_tvout_unbind,
};
static int sti_tvout_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct sti_tvout *tvout;
DRM_INFO("%s\n", __func__);
if (!node)
return -ENODEV;
tvout = devm_kzalloc(dev, sizeof(*tvout), GFP_KERNEL);
if (!tvout)
return -ENOMEM;
tvout->dev = dev;
tvout->regs = devm_platform_ioremap_resource_byname(pdev, "tvout-reg");
if (IS_ERR(tvout->regs))
return PTR_ERR(tvout->regs);
/* get reset resources */
tvout->reset = devm_reset_control_get(dev, "tvout");
/* take tvout out of reset */
if (!IS_ERR(tvout->reset))
reset_control_deassert(tvout->reset);
platform_set_drvdata(pdev, tvout);
return component_add(dev, &sti_tvout_ops);
}
static void sti_tvout_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sti_tvout_ops);
}
static const struct of_device_id tvout_of_match[] = {
{ .compatible = "st,stih407-tvout", },
{ /* end node */ }
};
MODULE_DEVICE_TABLE(of, tvout_of_match);
struct platform_driver sti_tvout_driver = {
.driver = {
.name = "sti-tvout",
.of_match_table = tvout_of_match,
},
.probe = sti_tvout_probe,
.remove = sti_tvout_remove,
};
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
MODULE_LICENSE("GPL");
+225
View File
@@ -0,0 +1,225 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
*/
#include <linux/seq_file.h>
#include <drm/drm_debugfs.h>
#include <drm/drm_file.h>
#include <drm/drm_print.h>
#include "sti_plane.h"
#include "sti_vid.h"
#include "sti_vtg.h"
/* Registers */
#define VID_CTL 0x00
#define VID_ALP 0x04
#define VID_CLF 0x08
#define VID_VPO 0x0C
#define VID_VPS 0x10
#define VID_KEY1 0x28
#define VID_KEY2 0x2C
#define VID_MPR0 0x30
#define VID_MPR1 0x34
#define VID_MPR2 0x38
#define VID_MPR3 0x3C
#define VID_MST 0x68
#define VID_BC 0x70
#define VID_TINT 0x74
#define VID_CSAT 0x78
/* Registers values */
#define VID_CTL_IGNORE (BIT(31) | BIT(30))
#define VID_CTL_PSI_ENABLE (BIT(2) | BIT(1) | BIT(0))
#define VID_ALP_OPAQUE 0x00000080
#define VID_BC_DFLT 0x00008000
#define VID_TINT_DFLT 0x00000000
#define VID_CSAT_DFLT 0x00000080
/* YCbCr to RGB BT709:
* R = Y+1.5391Cr
* G = Y-0.4590Cr-0.1826Cb
* B = Y+1.8125Cb */
#define VID_MPR0_BT709 0x0A800000
#define VID_MPR1_BT709 0x0AC50000
#define VID_MPR2_BT709 0x07150545
#define VID_MPR3_BT709 0x00000AE8
/* YCbCr to RGB BT709:
* R = Y+1.3711Cr
* G = Y-0.6992Cr-0.3359Cb
* B = Y+1.7344Cb
*/
#define VID_MPR0_BT601 0x0A800000
#define VID_MPR1_BT601 0x0AAF0000
#define VID_MPR2_BT601 0x094E0754
#define VID_MPR3_BT601 0x00000ADD
#define VID_MIN_HD_HEIGHT 720
#define DBGFS_DUMP(reg) seq_printf(s, "\n %-25s 0x%08X", #reg, \
readl(vid->regs + reg))
static void vid_dbg_ctl(struct seq_file *s, int val)
{
val = val >> 30;
seq_putc(s, '\t');
if (!(val & 1))
seq_puts(s, "NOT ");
seq_puts(s, "ignored on main mixer - ");
if (!(val & 2))
seq_puts(s, "NOT ");
seq_puts(s, "ignored on aux mixer");
}
static void vid_dbg_vpo(struct seq_file *s, int val)
{
seq_printf(s, "\txdo:%4d\tydo:%4d", val & 0x0FFF, (val >> 16) & 0x0FFF);
}
static void vid_dbg_vps(struct seq_file *s, int val)
{
seq_printf(s, "\txds:%4d\tyds:%4d", val & 0x0FFF, (val >> 16) & 0x0FFF);
}
static void vid_dbg_mst(struct seq_file *s, int val)
{
if (val & 1)
seq_puts(s, "\tBUFFER UNDERFLOW!");
}
static int vid_dbg_show(struct seq_file *s, void *arg)
{
struct drm_info_node *node = s->private;
struct sti_vid *vid = (struct sti_vid *)node->info_ent->data;
seq_printf(s, "VID: (vaddr= 0x%p)", vid->regs);
DBGFS_DUMP(VID_CTL);
vid_dbg_ctl(s, readl(vid->regs + VID_CTL));
DBGFS_DUMP(VID_ALP);
DBGFS_DUMP(VID_CLF);
DBGFS_DUMP(VID_VPO);
vid_dbg_vpo(s, readl(vid->regs + VID_VPO));
DBGFS_DUMP(VID_VPS);
vid_dbg_vps(s, readl(vid->regs + VID_VPS));
DBGFS_DUMP(VID_KEY1);
DBGFS_DUMP(VID_KEY2);
DBGFS_DUMP(VID_MPR0);
DBGFS_DUMP(VID_MPR1);
DBGFS_DUMP(VID_MPR2);
DBGFS_DUMP(VID_MPR3);
DBGFS_DUMP(VID_MST);
vid_dbg_mst(s, readl(vid->regs + VID_MST));
DBGFS_DUMP(VID_BC);
DBGFS_DUMP(VID_TINT);
DBGFS_DUMP(VID_CSAT);
seq_putc(s, '\n');
return 0;
}
static struct drm_info_list vid_debugfs_files[] = {
{ "vid", vid_dbg_show, 0, NULL },
};
void vid_debugfs_init(struct sti_vid *vid, struct drm_minor *minor)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(vid_debugfs_files); i++)
vid_debugfs_files[i].data = vid;
drm_debugfs_create_files(vid_debugfs_files,
ARRAY_SIZE(vid_debugfs_files),
minor->debugfs_root, minor);
}
void sti_vid_commit(struct sti_vid *vid,
struct drm_plane_state *state)
{
struct drm_crtc *crtc = state->crtc;
struct drm_display_mode *mode = &crtc->mode;
int dst_x = state->crtc_x;
int dst_y = state->crtc_y;
int dst_w = clamp_val(state->crtc_w, 0, mode->hdisplay - dst_x);
int dst_h = clamp_val(state->crtc_h, 0, mode->vdisplay - dst_y);
int src_h = state->src_h >> 16;
u32 val, ydo, xdo, yds, xds;
/* Input / output size
* Align to upper even value */
dst_w = ALIGN(dst_w, 2);
dst_h = ALIGN(dst_h, 2);
/* Unmask */
val = readl(vid->regs + VID_CTL);
val &= ~VID_CTL_IGNORE;
writel(val, vid->regs + VID_CTL);
ydo = sti_vtg_get_line_number(*mode, dst_y);
yds = sti_vtg_get_line_number(*mode, dst_y + dst_h - 1);
xdo = sti_vtg_get_pixel_number(*mode, dst_x);
xds = sti_vtg_get_pixel_number(*mode, dst_x + dst_w - 1);
writel((ydo << 16) | xdo, vid->regs + VID_VPO);
writel((yds << 16) | xds, vid->regs + VID_VPS);
/* Color conversion parameters */
if (src_h >= VID_MIN_HD_HEIGHT) {
writel(VID_MPR0_BT709, vid->regs + VID_MPR0);
writel(VID_MPR1_BT709, vid->regs + VID_MPR1);
writel(VID_MPR2_BT709, vid->regs + VID_MPR2);
writel(VID_MPR3_BT709, vid->regs + VID_MPR3);
} else {
writel(VID_MPR0_BT601, vid->regs + VID_MPR0);
writel(VID_MPR1_BT601, vid->regs + VID_MPR1);
writel(VID_MPR2_BT601, vid->regs + VID_MPR2);
writel(VID_MPR3_BT601, vid->regs + VID_MPR3);
}
}
void sti_vid_disable(struct sti_vid *vid)
{
u32 val;
/* Mask */
val = readl(vid->regs + VID_CTL);
val |= VID_CTL_IGNORE;
writel(val, vid->regs + VID_CTL);
}
static void sti_vid_init(struct sti_vid *vid)
{
/* Enable PSI, Mask layer */
writel(VID_CTL_PSI_ENABLE | VID_CTL_IGNORE, vid->regs + VID_CTL);
/* Opaque */
writel(VID_ALP_OPAQUE, vid->regs + VID_ALP);
/* Brightness, contrast, tint, saturation */
writel(VID_BC_DFLT, vid->regs + VID_BC);
writel(VID_TINT_DFLT, vid->regs + VID_TINT);
writel(VID_CSAT_DFLT, vid->regs + VID_CSAT);
}
struct sti_vid *sti_vid_create(struct device *dev, struct drm_device *drm_dev,
int id, void __iomem *baseaddr)
{
struct sti_vid *vid;
vid = devm_kzalloc(dev, sizeof(*vid), GFP_KERNEL);
if (!vid) {
DRM_ERROR("Failed to allocate memory for VID\n");
return NULL;
}
vid->dev = dev;
vid->regs = baseaddr;
vid->id = id;
sti_vid_init(vid);
return vid;
}
+31
View File
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
*/
#ifndef _STI_VID_H_
#define _STI_VID_H_
/**
* STI VID structure
*
* @dev: driver device
* @regs: vid registers
* @id: id of the vid
*/
struct sti_vid {
struct device *dev;
void __iomem *regs;
int id;
};
void sti_vid_commit(struct sti_vid *vid,
struct drm_plane_state *state);
void sti_vid_disable(struct sti_vid *vid);
struct sti_vid *sti_vid_create(struct device *dev, struct drm_device *drm_dev,
int id, void __iomem *baseaddr);
void vid_debugfs_init(struct sti_vid *vid, struct drm_minor *minor);
#endif
+438
View File
@@ -0,0 +1,438 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
* Fabien Dessenne <fabien.dessenne@st.com>
* Vincent Abriou <vincent.abriou@st.com>
* for STMicroelectronics.
*/
#include <linux/module.h>
#include <linux/io.h>
#include <linux/notifier.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <drm/drm_modes.h>
#include <drm/drm_print.h>
#include "sti_drv.h"
#include "sti_vtg.h"
#define VTG_MODE_MASTER 0
/* registers offset */
#define VTG_MODE 0x0000
#define VTG_CLKLN 0x0008
#define VTG_HLFLN 0x000C
#define VTG_DRST_AUTOC 0x0010
#define VTG_VID_TFO 0x0040
#define VTG_VID_TFS 0x0044
#define VTG_VID_BFO 0x0048
#define VTG_VID_BFS 0x004C
#define VTG_HOST_ITS 0x0078
#define VTG_HOST_ITS_BCLR 0x007C
#define VTG_HOST_ITM_BCLR 0x0088
#define VTG_HOST_ITM_BSET 0x008C
#define VTG_H_HD_1 0x00C0
#define VTG_TOP_V_VD_1 0x00C4
#define VTG_BOT_V_VD_1 0x00C8
#define VTG_TOP_V_HD_1 0x00CC
#define VTG_BOT_V_HD_1 0x00D0
#define VTG_H_HD_2 0x00E0
#define VTG_TOP_V_VD_2 0x00E4
#define VTG_BOT_V_VD_2 0x00E8
#define VTG_TOP_V_HD_2 0x00EC
#define VTG_BOT_V_HD_2 0x00F0
#define VTG_H_HD_3 0x0100
#define VTG_TOP_V_VD_3 0x0104
#define VTG_BOT_V_VD_3 0x0108
#define VTG_TOP_V_HD_3 0x010C
#define VTG_BOT_V_HD_3 0x0110
#define VTG_H_HD_4 0x0120
#define VTG_TOP_V_VD_4 0x0124
#define VTG_BOT_V_VD_4 0x0128
#define VTG_TOP_V_HD_4 0x012c
#define VTG_BOT_V_HD_4 0x0130
#define VTG_IRQ_BOTTOM BIT(0)
#define VTG_IRQ_TOP BIT(1)
#define VTG_IRQ_MASK (VTG_IRQ_TOP | VTG_IRQ_BOTTOM)
/* Delay introduced by the HDMI in nb of pixel */
#define HDMI_DELAY (5)
/* Delay introduced by the DVO in nb of pixel */
#define DVO_DELAY (7)
/* delay introduced by the Arbitrary Waveform Generator in nb of pixels */
#define AWG_DELAY_HD (-9)
#define AWG_DELAY_ED (-8)
#define AWG_DELAY_SD (-7)
/*
* STI VTG register offset structure
*
*@h_hd: stores the VTG_H_HD_x register offset
*@top_v_vd: stores the VTG_TOP_V_VD_x register offset
*@bot_v_vd: stores the VTG_BOT_V_VD_x register offset
*@top_v_hd: stores the VTG_TOP_V_HD_x register offset
*@bot_v_hd: stores the VTG_BOT_V_HD_x register offset
*/
struct sti_vtg_regs_offs {
u32 h_hd;
u32 top_v_vd;
u32 bot_v_vd;
u32 top_v_hd;
u32 bot_v_hd;
};
#define VTG_MAX_SYNC_OUTPUT 4
static const struct sti_vtg_regs_offs vtg_regs_offs[VTG_MAX_SYNC_OUTPUT] = {
{ VTG_H_HD_1,
VTG_TOP_V_VD_1, VTG_BOT_V_VD_1, VTG_TOP_V_HD_1, VTG_BOT_V_HD_1 },
{ VTG_H_HD_2,
VTG_TOP_V_VD_2, VTG_BOT_V_VD_2, VTG_TOP_V_HD_2, VTG_BOT_V_HD_2 },
{ VTG_H_HD_3,
VTG_TOP_V_VD_3, VTG_BOT_V_VD_3, VTG_TOP_V_HD_3, VTG_BOT_V_HD_3 },
{ VTG_H_HD_4,
VTG_TOP_V_VD_4, VTG_BOT_V_VD_4, VTG_TOP_V_HD_4, VTG_BOT_V_HD_4 }
};
/*
* STI VTG synchronisation parameters structure
*
*@hsync: sample number falling and rising edge
*@vsync_line_top: vertical top field line number falling and rising edge
*@vsync_line_bot: vertical bottom field line number falling and rising edge
*@vsync_off_top: vertical top field sample number rising and falling edge
*@vsync_off_bot: vertical bottom field sample number rising and falling edge
*/
struct sti_vtg_sync_params {
u32 hsync;
u32 vsync_line_top;
u32 vsync_line_bot;
u32 vsync_off_top;
u32 vsync_off_bot;
};
/*
* STI VTG structure
*
* @regs: register mapping
* @sync_params: synchronisation parameters used to generate timings
* @irq: VTG irq
* @irq_status: store the IRQ status value
* @notifier_list: notifier callback
* @crtc: the CRTC for vblank event
*/
struct sti_vtg {
void __iomem *regs;
struct sti_vtg_sync_params sync_params[VTG_MAX_SYNC_OUTPUT];
int irq;
u32 irq_status;
struct raw_notifier_head notifier_list;
struct drm_crtc *crtc;
};
struct sti_vtg *of_vtg_find(struct device_node *np)
{
struct platform_device *pdev;
struct sti_vtg *vtg;
pdev = of_find_device_by_node(np);
if (!pdev)
return NULL;
vtg = platform_get_drvdata(pdev);
put_device(&pdev->dev);
return vtg;
}
static void vtg_reset(struct sti_vtg *vtg)
{
writel(1, vtg->regs + VTG_DRST_AUTOC);
}
static void vtg_set_output_window(void __iomem *regs,
const struct drm_display_mode *mode)
{
u32 video_top_field_start;
u32 video_top_field_stop;
u32 video_bottom_field_start;
u32 video_bottom_field_stop;
u32 xstart = sti_vtg_get_pixel_number(*mode, 0);
u32 ystart = sti_vtg_get_line_number(*mode, 0);
u32 xstop = sti_vtg_get_pixel_number(*mode, mode->hdisplay - 1);
u32 ystop = sti_vtg_get_line_number(*mode, mode->vdisplay - 1);
/* Set output window to fit the display mode selected */
video_top_field_start = (ystart << 16) | xstart;
video_top_field_stop = (ystop << 16) | xstop;
/* Only progressive supported for now */
video_bottom_field_start = video_top_field_start;
video_bottom_field_stop = video_top_field_stop;
writel(video_top_field_start, regs + VTG_VID_TFO);
writel(video_top_field_stop, regs + VTG_VID_TFS);
writel(video_bottom_field_start, regs + VTG_VID_BFO);
writel(video_bottom_field_stop, regs + VTG_VID_BFS);
}
static void vtg_set_hsync_vsync_pos(struct sti_vtg_sync_params *sync,
int delay,
const struct drm_display_mode *mode)
{
long clocksperline, start, stop;
u32 risesync_top, fallsync_top;
u32 risesync_offs_top, fallsync_offs_top;
clocksperline = mode->htotal;
/* Get the hsync position */
start = 0;
stop = mode->hsync_end - mode->hsync_start;
start += delay;
stop += delay;
if (start < 0)
start += clocksperline;
else if (start >= clocksperline)
start -= clocksperline;
if (stop < 0)
stop += clocksperline;
else if (stop >= clocksperline)
stop -= clocksperline;
sync->hsync = (stop << 16) | start;
/* Get the vsync position */
if (delay >= 0) {
risesync_top = 1;
fallsync_top = risesync_top;
fallsync_top += mode->vsync_end - mode->vsync_start;
fallsync_offs_top = (u32)delay;
risesync_offs_top = (u32)delay;
} else {
risesync_top = mode->vtotal;
fallsync_top = mode->vsync_end - mode->vsync_start;
fallsync_offs_top = clocksperline + delay;
risesync_offs_top = clocksperline + delay;
}
sync->vsync_line_top = (fallsync_top << 16) | risesync_top;
sync->vsync_off_top = (fallsync_offs_top << 16) | risesync_offs_top;
/* Only progressive supported for now */
sync->vsync_line_bot = sync->vsync_line_top;
sync->vsync_off_bot = sync->vsync_off_top;
}
static void vtg_set_mode(struct sti_vtg *vtg,
int type,
struct sti_vtg_sync_params *sync,
const struct drm_display_mode *mode)
{
unsigned int i;
/* Set the number of clock cycles per line */
writel(mode->htotal, vtg->regs + VTG_CLKLN);
/* Set Half Line Per Field (only progressive supported for now) */
writel(mode->vtotal * 2, vtg->regs + VTG_HLFLN);
/* Program output window */
vtg_set_output_window(vtg->regs, mode);
/* Set hsync and vsync position for HDMI */
vtg_set_hsync_vsync_pos(&sync[VTG_SYNC_ID_HDMI - 1], HDMI_DELAY, mode);
/* Set hsync and vsync position for HD DCS */
vtg_set_hsync_vsync_pos(&sync[VTG_SYNC_ID_HDDCS - 1], 0, mode);
/* Set hsync and vsync position for HDF */
vtg_set_hsync_vsync_pos(&sync[VTG_SYNC_ID_HDF - 1], AWG_DELAY_HD, mode);
/* Set hsync and vsync position for DVO */
vtg_set_hsync_vsync_pos(&sync[VTG_SYNC_ID_DVO - 1], DVO_DELAY, mode);
/* Progam the syncs outputs */
for (i = 0; i < VTG_MAX_SYNC_OUTPUT ; i++) {
writel(sync[i].hsync,
vtg->regs + vtg_regs_offs[i].h_hd);
writel(sync[i].vsync_line_top,
vtg->regs + vtg_regs_offs[i].top_v_vd);
writel(sync[i].vsync_line_bot,
vtg->regs + vtg_regs_offs[i].bot_v_vd);
writel(sync[i].vsync_off_top,
vtg->regs + vtg_regs_offs[i].top_v_hd);
writel(sync[i].vsync_off_bot,
vtg->regs + vtg_regs_offs[i].bot_v_hd);
}
/* mode */
writel(type, vtg->regs + VTG_MODE);
}
static void vtg_enable_irq(struct sti_vtg *vtg)
{
/* clear interrupt status and mask */
writel(0xFFFF, vtg->regs + VTG_HOST_ITS_BCLR);
writel(0xFFFF, vtg->regs + VTG_HOST_ITM_BCLR);
writel(VTG_IRQ_MASK, vtg->regs + VTG_HOST_ITM_BSET);
}
void sti_vtg_set_config(struct sti_vtg *vtg,
const struct drm_display_mode *mode)
{
/* write configuration */
vtg_set_mode(vtg, VTG_MODE_MASTER, vtg->sync_params, mode);
vtg_reset(vtg);
vtg_enable_irq(vtg);
}
/**
* sti_vtg_get_line_number
*
* @mode: display mode to be used
* @y: line
*
* Return the line number according to the display mode taking
* into account the Sync and Back Porch information.
* Video frame line numbers start at 1, y starts at 0.
* In interlaced modes the start line is the field line number of the odd
* field, but y is still defined as a progressive frame.
*/
u32 sti_vtg_get_line_number(struct drm_display_mode mode, int y)
{
u32 start_line = mode.vtotal - mode.vsync_start + 1;
if (mode.flags & DRM_MODE_FLAG_INTERLACE)
start_line *= 2;
return start_line + y;
}
/**
* sti_vtg_get_pixel_number
*
* @mode: display mode to be used
* @x: row
*
* Return the pixel number according to the display mode taking
* into account the Sync and Back Porch information.
* Pixels are counted from 0.
*/
u32 sti_vtg_get_pixel_number(struct drm_display_mode mode, int x)
{
return mode.htotal - mode.hsync_start + x;
}
int sti_vtg_register_client(struct sti_vtg *vtg, struct notifier_block *nb,
struct drm_crtc *crtc)
{
vtg->crtc = crtc;
return raw_notifier_chain_register(&vtg->notifier_list, nb);
}
int sti_vtg_unregister_client(struct sti_vtg *vtg, struct notifier_block *nb)
{
return raw_notifier_chain_unregister(&vtg->notifier_list, nb);
}
static irqreturn_t vtg_irq_thread(int irq, void *arg)
{
struct sti_vtg *vtg = arg;
u32 event;
event = (vtg->irq_status & VTG_IRQ_TOP) ?
VTG_TOP_FIELD_EVENT : VTG_BOTTOM_FIELD_EVENT;
raw_notifier_call_chain(&vtg->notifier_list, event, vtg->crtc);
return IRQ_HANDLED;
}
static irqreturn_t vtg_irq(int irq, void *arg)
{
struct sti_vtg *vtg = arg;
vtg->irq_status = readl(vtg->regs + VTG_HOST_ITS);
writel(vtg->irq_status, vtg->regs + VTG_HOST_ITS_BCLR);
/* force sync bus write */
readl(vtg->regs + VTG_HOST_ITS);
return IRQ_WAKE_THREAD;
}
static int vtg_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sti_vtg *vtg;
int ret;
vtg = devm_kzalloc(dev, sizeof(*vtg), GFP_KERNEL);
if (!vtg)
return -ENOMEM;
vtg->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(vtg->regs)) {
DRM_ERROR("failed to remap I/O memory\n");
return PTR_ERR(vtg->regs);
}
vtg->irq = platform_get_irq(pdev, 0);
if (vtg->irq < 0) {
DRM_ERROR("Failed to get VTG interrupt\n");
return vtg->irq;
}
RAW_INIT_NOTIFIER_HEAD(&vtg->notifier_list);
ret = devm_request_threaded_irq(dev, vtg->irq, vtg_irq,
vtg_irq_thread, IRQF_ONESHOT,
dev_name(dev), vtg);
if (ret < 0) {
DRM_ERROR("Failed to register VTG interrupt\n");
return ret;
}
platform_set_drvdata(pdev, vtg);
DRM_INFO("%s %s\n", __func__, dev_name(dev));
return 0;
}
static const struct of_device_id vtg_of_match[] = {
{ .compatible = "st,vtg", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, vtg_of_match);
struct platform_driver sti_vtg_driver = {
.driver = {
.name = "sti-vtg",
.of_match_table = vtg_of_match,
},
.probe = vtg_probe,
};
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
MODULE_LICENSE("GPL");
+34
View File
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
*/
#ifndef _STI_VTG_H_
#define _STI_VTG_H_
#define VTG_TOP_FIELD_EVENT 1
#define VTG_BOTTOM_FIELD_EVENT 2
#define VTG_SYNC_ID_HDMI 1
#define VTG_SYNC_ID_HDDCS 2
#define VTG_SYNC_ID_HDF 3
#define VTG_SYNC_ID_DVO 4
struct sti_vtg;
struct drm_crtc;
struct drm_display_mode;
struct notifier_block;
struct sti_vtg *of_vtg_find(struct device_node *np);
void sti_vtg_set_config(struct sti_vtg *vtg,
const struct drm_display_mode *mode);
int sti_vtg_register_client(struct sti_vtg *vtg, struct notifier_block *nb,
struct drm_crtc *crtc);
int sti_vtg_unregister_client(struct sti_vtg *vtg,
struct notifier_block *nb);
u32 sti_vtg_get_line_number(struct drm_display_mode mode, int y);
u32 sti_vtg_get_pixel_number(struct drm_display_mode mode, int x);
#endif