mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-10-29 23:28:52 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
15
drivers/gpu/drm/sti/Kconfig
Normal file
15
drivers/gpu/drm/sti/Kconfig
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
config DRM_STI
|
||||
tristate "DRM Support for STMicroelectronics SoC stiH41x Series"
|
||||
depends on DRM && (SOC_STIH415 || SOC_STIH416 || ARCH_MULTIPLATFORM)
|
||||
select RESET_CONTROLLER
|
||||
select DRM_KMS_HELPER
|
||||
select DRM_GEM_CMA_HELPER
|
||||
select DRM_KMS_CMA_HELPER
|
||||
help
|
||||
Choose this option to enable DRM on STM stiH41x chipset
|
||||
|
||||
config DRM_STI_FBDEV
|
||||
bool "DRM frame buffer device for STMicroelectronics SoC stiH41x Serie"
|
||||
depends on DRM_STI
|
||||
help
|
||||
Choose this option to enable FBDEV on top of DRM for STM stiH41x chipset
|
||||
21
drivers/gpu/drm/sti/Makefile
Normal file
21
drivers/gpu/drm/sti/Makefile
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
sticompositor-y := \
|
||||
sti_layer.o \
|
||||
sti_mixer.o \
|
||||
sti_gdp.o \
|
||||
sti_vid.o \
|
||||
sti_compositor.o \
|
||||
sti_drm_crtc.o \
|
||||
sti_drm_plane.o
|
||||
|
||||
stihdmi-y := sti_hdmi.o \
|
||||
sti_hdmi_tx3g0c55phy.o \
|
||||
sti_hdmi_tx3g4c28phy.o \
|
||||
|
||||
obj-$(CONFIG_DRM_STI) = \
|
||||
sti_vtg.o \
|
||||
sti_vtac.o \
|
||||
stihdmi.o \
|
||||
sti_hda.o \
|
||||
sti_tvout.o \
|
||||
sticompositor.o \
|
||||
sti_drm_drv.o
|
||||
58
drivers/gpu/drm/sti/NOTES
Normal file
58
drivers/gpu/drm/sti/NOTES
Normal 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 | <-+
|
||||
'-------' '-------------' '-----------' '-----------'
|
||||
281
drivers/gpu/drm/sti/sti_compositor.c
Normal file
281
drivers/gpu/drm/sti/sti_compositor.c
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
|
||||
* Fabien Dessenne <fabien.dessenne@st.com>
|
||||
* for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include <linux/component.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
|
||||
#include "sti_compositor.h"
|
||||
#include "sti_drm_crtc.h"
|
||||
#include "sti_drm_drv.h"
|
||||
#include "sti_drm_plane.h"
|
||||
#include "sti_gdp.h"
|
||||
#include "sti_vtg.h"
|
||||
|
||||
/*
|
||||
* stiH407 compositor properties
|
||||
*/
|
||||
struct sti_compositor_data stih407_compositor_data = {
|
||||
.nb_subdev = 6,
|
||||
.subdev_desc = {
|
||||
{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_VID_0, 0x700},
|
||||
{STI_MIXER_MAIN_SUBDEV, STI_MIXER_MAIN, 0xC00}
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* stiH416 compositor properties
|
||||
* Note:
|
||||
* on stih416 MIXER_AUX has a different base address from MIXER_MAIN
|
||||
* Moreover, GDPx is different for Main and Aux Mixer. So this subdev map does
|
||||
* not fit for stiH416 if we want to enable the MIXER_AUX.
|
||||
*/
|
||||
struct sti_compositor_data stih416_compositor_data = {
|
||||
.nb_subdev = 3,
|
||||
.subdev_desc = {
|
||||
{STI_GPD_SUBDEV, (int)STI_GDP_0, 0x100},
|
||||
{STI_GPD_SUBDEV, (int)STI_GDP_1, 0x200},
|
||||
{STI_MIXER_MAIN_SUBDEV, STI_MIXER_MAIN, 0xC00}
|
||||
},
|
||||
};
|
||||
|
||||
static int sti_compositor_init_subdev(struct sti_compositor *compo,
|
||||
struct sti_compositor_subdev_descriptor *desc,
|
||||
unsigned int array_size)
|
||||
{
|
||||
unsigned int i, mixer_id = 0, layer_id = 0;
|
||||
|
||||
for (i = 0; i < array_size; i++) {
|
||||
switch (desc[i].type) {
|
||||
case STI_MIXER_MAIN_SUBDEV:
|
||||
case STI_MIXER_AUX_SUBDEV:
|
||||
compo->mixer[mixer_id++] =
|
||||
sti_mixer_create(compo->dev, desc[i].id,
|
||||
compo->regs + desc[i].offset);
|
||||
break;
|
||||
case STI_GPD_SUBDEV:
|
||||
case STI_VID_SUBDEV:
|
||||
compo->layer[layer_id++] =
|
||||
sti_layer_create(compo->dev, desc[i].id,
|
||||
compo->regs + desc[i].offset);
|
||||
break;
|
||||
/* case STI_CURSOR_SUBDEV : TODO */
|
||||
default:
|
||||
DRM_ERROR("Unknow subdev compoment type\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
compo->nb_mixers = mixer_id;
|
||||
compo->nb_layers = layer_id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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, crtc = 0, plane = 0;
|
||||
struct sti_drm_private *dev_priv = drm_dev->dev_private;
|
||||
struct drm_plane *cursor = NULL;
|
||||
struct drm_plane *primary = NULL;
|
||||
|
||||
dev_priv->compo = compo;
|
||||
|
||||
for (i = 0; i < compo->nb_layers; i++) {
|
||||
if (compo->layer[i]) {
|
||||
enum sti_layer_desc desc = compo->layer[i]->desc;
|
||||
enum sti_layer_type type = desc & STI_LAYER_TYPE_MASK;
|
||||
enum drm_plane_type plane_type = DRM_PLANE_TYPE_OVERLAY;
|
||||
|
||||
if (compo->mixer[crtc])
|
||||
plane_type = DRM_PLANE_TYPE_PRIMARY;
|
||||
|
||||
switch (type) {
|
||||
case STI_CUR:
|
||||
cursor = sti_drm_plane_init(drm_dev,
|
||||
compo->layer[i],
|
||||
(1 << crtc) - 1,
|
||||
DRM_PLANE_TYPE_CURSOR);
|
||||
break;
|
||||
case STI_GDP:
|
||||
case STI_VID:
|
||||
primary = sti_drm_plane_init(drm_dev,
|
||||
compo->layer[i],
|
||||
(1 << crtc) - 1, plane_type);
|
||||
plane++;
|
||||
break;
|
||||
case STI_BCK:
|
||||
break;
|
||||
}
|
||||
|
||||
/* The first planes are reserved for primary planes*/
|
||||
if (compo->mixer[crtc]) {
|
||||
sti_drm_crtc_init(drm_dev, compo->mixer[crtc],
|
||||
primary, cursor);
|
||||
crtc++;
|
||||
cursor = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drm_vblank_init(drm_dev, crtc);
|
||||
/* Allow usage of vblank without having to call drm_irq_install */
|
||||
drm_dev->irq_enabled = 1;
|
||||
|
||||
DRM_DEBUG_DRIVER("Initialized %d DRM CRTC(s) and %d DRM plane(s)\n",
|
||||
crtc, plane);
|
||||
DRM_DEBUG_DRIVER("DRM plane(s) for VID/VDP not created yet\n");
|
||||
|
||||
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,stih416-compositor",
|
||||
.data = &stih416_compositor_data,
|
||||
}, {
|
||||
.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;
|
||||
struct resource *res;
|
||||
int err;
|
||||
|
||||
compo = devm_kzalloc(dev, sizeof(*compo), GFP_KERNEL);
|
||||
if (!compo) {
|
||||
DRM_ERROR("Failed to allocate compositor context\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
compo->dev = dev;
|
||||
compo->vtg_vblank_nb.notifier_call = sti_drm_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));
|
||||
|
||||
/* Get Memory ressources */
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (res == NULL) {
|
||||
DRM_ERROR("Get memory resource failed\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
compo->regs = devm_ioremap(dev, res->start, resource_size(res));
|
||||
if (compo->regs == NULL) {
|
||||
DRM_ERROR("Register mapping failed\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
/* 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(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(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_main = of_vtg_find(vtg_np);
|
||||
|
||||
vtg_np = of_parse_phandle(pdev->dev.of_node, "st,vtg", 1);
|
||||
if (vtg_np)
|
||||
compo->vtg_aux = of_vtg_find(vtg_np);
|
||||
|
||||
/* Initialize compositor subdevices */
|
||||
err = sti_compositor_init_subdev(compo, compo->data.subdev_desc,
|
||||
compo->data.nb_subdev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
platform_set_drvdata(pdev, compo);
|
||||
|
||||
return component_add(&pdev->dev, &sti_compositor_ops);
|
||||
}
|
||||
|
||||
static int sti_compositor_remove(struct platform_device *pdev)
|
||||
{
|
||||
component_del(&pdev->dev, &sti_compositor_ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver sti_compositor_driver = {
|
||||
.driver = {
|
||||
.name = "sti-compositor",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = compositor_of_match,
|
||||
},
|
||||
.probe = sti_compositor_probe,
|
||||
.remove = sti_compositor_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(sti_compositor_driver);
|
||||
|
||||
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
90
drivers/gpu/drm/sti/sti_compositor.h
Normal file
90
drivers/gpu/drm/sti/sti_compositor.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
|
||||
* Fabien Dessenne <fabien.dessenne@st.com>
|
||||
* for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#ifndef _STI_COMPOSITOR_H_
|
||||
#define _STI_COMPOSITOR_H_
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#include "sti_layer.h"
|
||||
#include "sti_mixer.h"
|
||||
|
||||
#define WAIT_NEXT_VSYNC_MS 50 /*ms*/
|
||||
|
||||
#define STI_MAX_LAYER 8
|
||||
#define STI_MAX_MIXER 2
|
||||
|
||||
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
|
||||
* @vtg_main: vtg for main data path
|
||||
* @vtg_aux: vtg for auxillary data path
|
||||
* @layer: array of layers
|
||||
* @nb_mixers: number of mixers for this compositor
|
||||
* @nb_layers: number of layers (GDP,VID,...) for this compositor
|
||||
* @enable: true if compositor is enable else false
|
||||
* @vtg_vblank_nb: callback 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_vtg *vtg_main;
|
||||
struct sti_vtg *vtg_aux;
|
||||
struct sti_layer *layer[STI_MAX_LAYER];
|
||||
int nb_mixers;
|
||||
int nb_layers;
|
||||
bool enable;
|
||||
struct notifier_block vtg_vblank_nb;
|
||||
};
|
||||
|
||||
#endif
|
||||
421
drivers/gpu/drm/sti/sti_drm_crtc.c
Normal file
421
drivers/gpu/drm/sti/sti_drm_crtc.c
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
|
||||
* Fabien Dessenne <fabien.dessenne@st.com>
|
||||
* for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
|
||||
#include "sti_compositor.h"
|
||||
#include "sti_drm_drv.h"
|
||||
#include "sti_drm_crtc.h"
|
||||
#include "sti_vtg.h"
|
||||
|
||||
static void sti_drm_crtc_dpms(struct drm_crtc *crtc, int mode)
|
||||
{
|
||||
DRM_DEBUG_KMS("\n");
|
||||
}
|
||||
|
||||
static void sti_drm_crtc_prepare(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);
|
||||
|
||||
compo->enable = true;
|
||||
|
||||
/* Prepare and enable the compo IP clock */
|
||||
if (mixer->id == STI_MIXER_MAIN) {
|
||||
if (clk_prepare_enable(compo->clk_compo_main))
|
||||
DRM_INFO("Failed to prepare/enable compo_main clk\n");
|
||||
} else {
|
||||
if (clk_prepare_enable(compo->clk_compo_aux))
|
||||
DRM_INFO("Failed to prepare/enable compo_aux clk\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void sti_drm_crtc_commit(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);
|
||||
struct sti_layer *layer;
|
||||
|
||||
if ((!mixer || !compo)) {
|
||||
DRM_ERROR("Can not find mixer or compositor)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* get GDP which is reserved to the CRTC FB */
|
||||
layer = to_sti_layer(crtc->primary);
|
||||
if (layer)
|
||||
sti_layer_commit(layer);
|
||||
else
|
||||
DRM_ERROR("Can not find CRTC dedicated plane (GDP0)\n");
|
||||
|
||||
/* Enable layer on mixer */
|
||||
if (sti_mixer_set_layer_status(mixer, layer, true))
|
||||
DRM_ERROR("Can not enable layer at mixer\n");
|
||||
}
|
||||
|
||||
static bool sti_drm_crtc_mode_fixup(struct drm_crtc *crtc,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
/* accept the provided drm_display_mode, do not fix it up */
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
sti_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode, int x, int y,
|
||||
struct drm_framebuffer *old_fb)
|
||||
{
|
||||
struct sti_mixer *mixer = to_sti_mixer(crtc);
|
||||
struct device *dev = mixer->dev;
|
||||
struct sti_compositor *compo = dev_get_drvdata(dev);
|
||||
struct sti_layer *layer;
|
||||
struct clk *clk;
|
||||
int rate = mode->clock * 1000;
|
||||
int res;
|
||||
unsigned int w, h;
|
||||
|
||||
DRM_DEBUG_KMS("CRTC:%d (%s) fb:%d mode:%d (%s)\n",
|
||||
crtc->base.id, sti_mixer_to_str(mixer),
|
||||
crtc->primary->fb->base.id, mode->base.id, mode->name);
|
||||
|
||||
DRM_DEBUG_KMS("%d %d %d %d %d %d %d %d %d %d 0x%x 0x%x\n",
|
||||
mode->vrefresh, mode->clock,
|
||||
mode->hdisplay,
|
||||
mode->hsync_start, mode->hsync_end,
|
||||
mode->htotal,
|
||||
mode->vdisplay,
|
||||
mode->vsync_start, mode->vsync_end,
|
||||
mode->vtotal, mode->type, mode->flags);
|
||||
|
||||
/* Set rate and prepare/enable pixel clock */
|
||||
if (mixer->id == STI_MIXER_MAIN)
|
||||
clk = compo->clk_pix_main;
|
||||
else
|
||||
clk = compo->clk_pix_aux;
|
||||
|
||||
res = clk_set_rate(clk, rate);
|
||||
if (res < 0) {
|
||||
DRM_ERROR("Cannot set rate (%dHz) for pix clk\n", rate);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (clk_prepare_enable(clk)) {
|
||||
DRM_ERROR("Failed to prepare/enable pix clk\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sti_vtg_set_config(mixer->id == STI_MIXER_MAIN ?
|
||||
compo->vtg_main : compo->vtg_aux, &crtc->mode);
|
||||
|
||||
/* a GDP is reserved to the CRTC FB */
|
||||
layer = to_sti_layer(crtc->primary);
|
||||
if (!layer) {
|
||||
DRM_ERROR("Can not find GDP0)\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* copy the mode data adjusted by mode_fixup() into crtc->mode
|
||||
* so that hardware can be set to proper mode
|
||||
*/
|
||||
memcpy(&crtc->mode, adjusted_mode, sizeof(*adjusted_mode));
|
||||
|
||||
res = sti_mixer_set_layer_depth(mixer, layer);
|
||||
if (res) {
|
||||
DRM_ERROR("Can not set layer depth\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
res = sti_mixer_active_video_area(mixer, &crtc->mode);
|
||||
if (res) {
|
||||
DRM_ERROR("Can not set active video area\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
w = crtc->primary->fb->width - x;
|
||||
h = crtc->primary->fb->height - y;
|
||||
|
||||
return sti_layer_prepare(layer, crtc->primary->fb, &crtc->mode,
|
||||
mixer->id, 0, 0, w, h, x, y, w, h);
|
||||
}
|
||||
|
||||
static int sti_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
struct drm_framebuffer *old_fb)
|
||||
{
|
||||
struct sti_mixer *mixer = to_sti_mixer(crtc);
|
||||
struct sti_layer *layer;
|
||||
unsigned int w, h;
|
||||
int ret;
|
||||
|
||||
DRM_DEBUG_KMS("CRTC:%d (%s) fb:%d (%d,%d)\n",
|
||||
crtc->base.id, sti_mixer_to_str(mixer),
|
||||
crtc->primary->fb->base.id, x, y);
|
||||
|
||||
/* GDP is reserved to the CRTC FB */
|
||||
layer = to_sti_layer(crtc->primary);
|
||||
if (!layer) {
|
||||
DRM_ERROR("Can not find GDP0)\n");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
w = crtc->primary->fb->width - crtc->x;
|
||||
h = crtc->primary->fb->height - crtc->y;
|
||||
|
||||
ret = sti_layer_prepare(layer, crtc->primary->fb, &crtc->mode,
|
||||
mixer->id, 0, 0, w, h,
|
||||
crtc->x, crtc->y, w, h);
|
||||
if (ret) {
|
||||
DRM_ERROR("Can not prepare layer\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
sti_drm_crtc_commit(crtc);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sti_drm_crtc_load_lut(struct drm_crtc *crtc)
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
static void sti_drm_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);
|
||||
struct sti_layer *layer;
|
||||
|
||||
if (!compo->enable)
|
||||
return;
|
||||
|
||||
DRM_DEBUG_KMS("CRTC:%d (%s)\n", crtc->base.id, sti_mixer_to_str(mixer));
|
||||
|
||||
/* Disable Background */
|
||||
sti_mixer_set_background_status(mixer, false);
|
||||
|
||||
/* Disable GDP */
|
||||
layer = to_sti_layer(crtc->primary);
|
||||
if (!layer) {
|
||||
DRM_ERROR("Cannot find GDP0\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Disable layer at mixer level */
|
||||
if (sti_mixer_set_layer_status(mixer, layer, false))
|
||||
DRM_ERROR("Can not disable %s layer at mixer\n",
|
||||
sti_layer_to_str(layer));
|
||||
|
||||
/* Wait a while to be sure that a Vsync event is received */
|
||||
msleep(WAIT_NEXT_VSYNC_MS);
|
||||
|
||||
/* Then disable layer itself */
|
||||
sti_layer_disable(layer);
|
||||
|
||||
drm_vblank_off(crtc->dev, mixer->id);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
compo->enable = false;
|
||||
}
|
||||
|
||||
static struct drm_crtc_helper_funcs sti_crtc_helper_funcs = {
|
||||
.dpms = sti_drm_crtc_dpms,
|
||||
.prepare = sti_drm_crtc_prepare,
|
||||
.commit = sti_drm_crtc_commit,
|
||||
.mode_fixup = sti_drm_crtc_mode_fixup,
|
||||
.mode_set = sti_drm_crtc_mode_set,
|
||||
.mode_set_base = sti_drm_crtc_mode_set_base,
|
||||
.load_lut = sti_drm_crtc_load_lut,
|
||||
.disable = sti_drm_crtc_disable,
|
||||
};
|
||||
|
||||
static int sti_drm_crtc_page_flip(struct drm_crtc *crtc,
|
||||
struct drm_framebuffer *fb,
|
||||
struct drm_pending_vblank_event *event,
|
||||
uint32_t page_flip_flags)
|
||||
{
|
||||
struct drm_device *drm_dev = crtc->dev;
|
||||
struct drm_framebuffer *old_fb;
|
||||
struct sti_mixer *mixer = to_sti_mixer(crtc);
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
DRM_DEBUG_KMS("fb %d --> fb %d\n",
|
||||
crtc->primary->fb->base.id, fb->base.id);
|
||||
|
||||
mutex_lock(&drm_dev->struct_mutex);
|
||||
|
||||
old_fb = crtc->primary->fb;
|
||||
crtc->primary->fb = fb;
|
||||
ret = sti_drm_crtc_mode_set_base(crtc, crtc->x, crtc->y, old_fb);
|
||||
if (ret) {
|
||||
DRM_ERROR("failed\n");
|
||||
crtc->primary->fb = old_fb;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (event) {
|
||||
event->pipe = mixer->id;
|
||||
|
||||
ret = drm_vblank_get(drm_dev, event->pipe);
|
||||
if (ret) {
|
||||
DRM_ERROR("Cannot get vblank\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&drm_dev->event_lock, flags);
|
||||
if (mixer->pending_event) {
|
||||
drm_vblank_put(drm_dev, event->pipe);
|
||||
ret = -EBUSY;
|
||||
} else {
|
||||
mixer->pending_event = event;
|
||||
}
|
||||
spin_unlock_irqrestore(&drm_dev->event_lock, flags);
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&drm_dev->struct_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sti_drm_crtc_destroy(struct drm_crtc *crtc)
|
||||
{
|
||||
DRM_DEBUG_KMS("\n");
|
||||
drm_crtc_cleanup(crtc);
|
||||
}
|
||||
|
||||
static int sti_drm_crtc_set_property(struct drm_crtc *crtc,
|
||||
struct drm_property *property,
|
||||
uint64_t val)
|
||||
{
|
||||
DRM_DEBUG_KMS("\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sti_drm_crtc_vblank_cb(struct notifier_block *nb,
|
||||
unsigned long event, void *data)
|
||||
{
|
||||
struct drm_device *drm_dev;
|
||||
struct sti_compositor *compo =
|
||||
container_of(nb, struct sti_compositor, vtg_vblank_nb);
|
||||
int *crtc = data;
|
||||
unsigned long flags;
|
||||
struct sti_drm_private *priv;
|
||||
|
||||
drm_dev = compo->mixer[*crtc]->drm_crtc.dev;
|
||||
priv = drm_dev->dev_private;
|
||||
|
||||
if ((event != VTG_TOP_FIELD_EVENT) &&
|
||||
(event != VTG_BOTTOM_FIELD_EVENT)) {
|
||||
DRM_ERROR("unknown event: %lu\n", event);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
drm_handle_vblank(drm_dev, *crtc);
|
||||
|
||||
spin_lock_irqsave(&drm_dev->event_lock, flags);
|
||||
if (compo->mixer[*crtc]->pending_event) {
|
||||
drm_send_vblank_event(drm_dev, -1,
|
||||
compo->mixer[*crtc]->pending_event);
|
||||
drm_vblank_put(drm_dev, *crtc);
|
||||
compo->mixer[*crtc]->pending_event = NULL;
|
||||
}
|
||||
spin_unlock_irqrestore(&drm_dev->event_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sti_drm_crtc_enable_vblank(struct drm_device *dev, int crtc)
|
||||
{
|
||||
struct sti_drm_private *dev_priv = dev->dev_private;
|
||||
struct sti_compositor *compo = dev_priv->compo;
|
||||
struct notifier_block *vtg_vblank_nb = &compo->vtg_vblank_nb;
|
||||
|
||||
if (sti_vtg_register_client(crtc == STI_MIXER_MAIN ?
|
||||
compo->vtg_main : compo->vtg_aux,
|
||||
vtg_vblank_nb, crtc)) {
|
||||
DRM_ERROR("Cannot register VTG notifier\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(sti_drm_crtc_enable_vblank);
|
||||
|
||||
void sti_drm_crtc_disable_vblank(struct drm_device *dev, int crtc)
|
||||
{
|
||||
struct sti_drm_private *priv = dev->dev_private;
|
||||
struct sti_compositor *compo = priv->compo;
|
||||
struct notifier_block *vtg_vblank_nb = &compo->vtg_vblank_nb;
|
||||
unsigned long flags;
|
||||
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
if (sti_vtg_unregister_client(crtc == STI_MIXER_MAIN ?
|
||||
compo->vtg_main : compo->vtg_aux, vtg_vblank_nb))
|
||||
DRM_DEBUG_DRIVER("Warning: cannot unregister VTG notifier\n");
|
||||
|
||||
/* free the resources of the pending requests */
|
||||
spin_lock_irqsave(&dev->event_lock, flags);
|
||||
if (compo->mixer[crtc]->pending_event) {
|
||||
drm_vblank_put(dev, crtc);
|
||||
compo->mixer[crtc]->pending_event = NULL;
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->event_lock, flags);
|
||||
|
||||
}
|
||||
EXPORT_SYMBOL(sti_drm_crtc_disable_vblank);
|
||||
|
||||
static struct drm_crtc_funcs sti_crtc_funcs = {
|
||||
.set_config = drm_crtc_helper_set_config,
|
||||
.page_flip = sti_drm_crtc_page_flip,
|
||||
.destroy = sti_drm_crtc_destroy,
|
||||
.set_property = sti_drm_crtc_set_property,
|
||||
};
|
||||
|
||||
bool sti_drm_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_drm_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);
|
||||
if (res) {
|
||||
DRM_ERROR("Can not initialze 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;
|
||||
}
|
||||
22
drivers/gpu/drm/sti/sti_drm_crtc.h
Normal file
22
drivers/gpu/drm/sti/sti_drm_crtc.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#ifndef _STI_DRM_CRTC_H_
|
||||
#define _STI_DRM_CRTC_H_
|
||||
|
||||
#include <drm/drmP.h>
|
||||
|
||||
struct sti_mixer;
|
||||
|
||||
int sti_drm_crtc_init(struct drm_device *drm_dev, struct sti_mixer *mixer,
|
||||
struct drm_plane *primary, struct drm_plane *cursor);
|
||||
int sti_drm_crtc_enable_vblank(struct drm_device *dev, int crtc);
|
||||
void sti_drm_crtc_disable_vblank(struct drm_device *dev, int crtc);
|
||||
int sti_drm_crtc_vblank_cb(struct notifier_block *nb,
|
||||
unsigned long event, void *data);
|
||||
bool sti_drm_crtc_is_main(struct drm_crtc *drm_crtc);
|
||||
|
||||
#endif
|
||||
241
drivers/gpu/drm/sti/sti_drm_drv.c
Normal file
241
drivers/gpu/drm/sti/sti_drm_drv.c
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
|
||||
#include <linux/component.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_gem_cma_helper.h>
|
||||
#include <drm/drm_fb_cma_helper.h>
|
||||
|
||||
#include "sti_drm_drv.h"
|
||||
#include "sti_drm_crtc.h"
|
||||
|
||||
#define DRIVER_NAME "sti"
|
||||
#define DRIVER_DESC "STMicroelectronics SoC DRM"
|
||||
#define DRIVER_DATE "20140601"
|
||||
#define DRIVER_MAJOR 1
|
||||
#define DRIVER_MINOR 0
|
||||
|
||||
#define STI_MAX_FB_HEIGHT 4096
|
||||
#define STI_MAX_FB_WIDTH 4096
|
||||
|
||||
static struct drm_mode_config_funcs sti_drm_mode_config_funcs = {
|
||||
.fb_create = drm_fb_cma_create,
|
||||
};
|
||||
|
||||
static void sti_drm_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_HEIGHT;
|
||||
dev->mode_config.max_height = STI_MAX_FB_WIDTH;
|
||||
|
||||
dev->mode_config.funcs = &sti_drm_mode_config_funcs;
|
||||
}
|
||||
|
||||
static int sti_drm_load(struct drm_device *dev, unsigned long flags)
|
||||
{
|
||||
struct sti_drm_private *private;
|
||||
int ret;
|
||||
|
||||
private = kzalloc(sizeof(struct sti_drm_private), GFP_KERNEL);
|
||||
if (!private) {
|
||||
DRM_ERROR("Failed to allocate private\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
dev->dev_private = (void *)private;
|
||||
private->drm_dev = dev;
|
||||
|
||||
drm_mode_config_init(dev);
|
||||
drm_kms_helper_poll_init(dev);
|
||||
|
||||
sti_drm_mode_config_init(dev);
|
||||
|
||||
ret = component_bind_all(dev->dev, dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
drm_helper_disable_unused_functions(dev);
|
||||
|
||||
#ifdef CONFIG_DRM_STI_FBDEV
|
||||
drm_fbdev_cma_init(dev, 32,
|
||||
dev->mode_config.num_crtc,
|
||||
dev->mode_config.num_connector);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations sti_drm_driver_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = drm_open,
|
||||
.mmap = drm_gem_cma_mmap,
|
||||
.poll = drm_poll,
|
||||
.read = drm_read,
|
||||
.unlocked_ioctl = drm_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = drm_compat_ioctl,
|
||||
#endif
|
||||
.release = drm_release,
|
||||
};
|
||||
|
||||
static struct dma_buf *sti_drm_gem_prime_export(struct drm_device *dev,
|
||||
struct drm_gem_object *obj,
|
||||
int flags)
|
||||
{
|
||||
/* we want to be able to write in mmapped buffer */
|
||||
flags |= O_RDWR;
|
||||
return drm_gem_prime_export(dev, obj, flags);
|
||||
}
|
||||
|
||||
static struct drm_driver sti_drm_driver = {
|
||||
.driver_features = DRIVER_HAVE_IRQ | DRIVER_MODESET |
|
||||
DRIVER_GEM | DRIVER_PRIME,
|
||||
.load = sti_drm_load,
|
||||
.gem_free_object = drm_gem_cma_free_object,
|
||||
.gem_vm_ops = &drm_gem_cma_vm_ops,
|
||||
.dumb_create = drm_gem_cma_dumb_create,
|
||||
.dumb_map_offset = drm_gem_cma_dumb_map_offset,
|
||||
.dumb_destroy = drm_gem_dumb_destroy,
|
||||
.fops = &sti_drm_driver_fops,
|
||||
|
||||
.get_vblank_counter = drm_vblank_count,
|
||||
.enable_vblank = sti_drm_crtc_enable_vblank,
|
||||
.disable_vblank = sti_drm_crtc_disable_vblank,
|
||||
|
||||
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
|
||||
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
|
||||
.gem_prime_export = sti_drm_gem_prime_export,
|
||||
.gem_prime_import = drm_gem_prime_import,
|
||||
.gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
|
||||
.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
|
||||
.gem_prime_vmap = drm_gem_cma_prime_vmap,
|
||||
.gem_prime_vunmap = drm_gem_cma_prime_vunmap,
|
||||
.gem_prime_mmap = drm_gem_cma_prime_mmap,
|
||||
|
||||
.name = DRIVER_NAME,
|
||||
.desc = DRIVER_DESC,
|
||||
.date = DRIVER_DATE,
|
||||
.major = DRIVER_MAJOR,
|
||||
.minor = DRIVER_MINOR,
|
||||
};
|
||||
|
||||
static int compare_of(struct device *dev, void *data)
|
||||
{
|
||||
return dev->of_node == data;
|
||||
}
|
||||
|
||||
static int sti_drm_bind(struct device *dev)
|
||||
{
|
||||
return drm_platform_init(&sti_drm_driver, to_platform_device(dev));
|
||||
}
|
||||
|
||||
static void sti_drm_unbind(struct device *dev)
|
||||
{
|
||||
drm_put_dev(dev_get_drvdata(dev));
|
||||
}
|
||||
|
||||
static const struct component_master_ops sti_drm_ops = {
|
||||
.bind = sti_drm_bind,
|
||||
.unbind = sti_drm_unbind,
|
||||
};
|
||||
|
||||
static int sti_drm_master_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *node = dev->parent->of_node;
|
||||
struct device_node *child_np;
|
||||
struct component_match *match = NULL;
|
||||
|
||||
dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
|
||||
|
||||
child_np = of_get_next_available_child(node, NULL);
|
||||
|
||||
while (child_np) {
|
||||
component_match_add(dev, &match, compare_of, child_np);
|
||||
of_node_put(child_np);
|
||||
child_np = of_get_next_available_child(node, child_np);
|
||||
}
|
||||
|
||||
return component_master_add_with_match(dev, &sti_drm_ops, match);
|
||||
}
|
||||
|
||||
static int sti_drm_master_remove(struct platform_device *pdev)
|
||||
{
|
||||
component_master_del(&pdev->dev, &sti_drm_ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver sti_drm_master_driver = {
|
||||
.probe = sti_drm_master_probe,
|
||||
.remove = sti_drm_master_remove,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = DRIVER_NAME "__master",
|
||||
},
|
||||
};
|
||||
|
||||
static int sti_drm_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *node = dev->of_node;
|
||||
struct platform_device *master;
|
||||
|
||||
of_platform_populate(node, NULL, NULL, dev);
|
||||
|
||||
platform_driver_register(&sti_drm_master_driver);
|
||||
master = platform_device_register_resndata(dev,
|
||||
DRIVER_NAME "__master", -1,
|
||||
NULL, 0, NULL, 0);
|
||||
if (IS_ERR(master))
|
||||
return PTR_ERR(master);
|
||||
|
||||
platform_set_drvdata(pdev, master);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sti_drm_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct platform_device *master = platform_get_drvdata(pdev);
|
||||
|
||||
of_platform_depopulate(&pdev->dev);
|
||||
platform_device_unregister(master);
|
||||
platform_driver_unregister(&sti_drm_master_driver);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id sti_drm_dt_ids[] = {
|
||||
{ .compatible = "st,sti-display-subsystem", },
|
||||
{ /* end node */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sti_drm_dt_ids);
|
||||
|
||||
static struct platform_driver sti_drm_platform_driver = {
|
||||
.probe = sti_drm_platform_probe,
|
||||
.remove = sti_drm_platform_remove,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = DRIVER_NAME,
|
||||
.of_match_table = sti_drm_dt_ids,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(sti_drm_platform_driver);
|
||||
|
||||
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
29
drivers/gpu/drm/sti/sti_drm_drv.h
Normal file
29
drivers/gpu/drm/sti/sti_drm_drv.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#ifndef _STI_DRM_DRV_H_
|
||||
#define _STI_DRM_DRV_H_
|
||||
|
||||
#include <drm/drmP.h>
|
||||
|
||||
struct sti_compositor;
|
||||
struct sti_tvout;
|
||||
|
||||
/**
|
||||
* 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_drm_private {
|
||||
struct sti_compositor *compo;
|
||||
struct drm_property *plane_zorder_property;
|
||||
struct drm_device *drm_dev;
|
||||
};
|
||||
|
||||
#endif
|
||||
195
drivers/gpu/drm/sti/sti_drm_plane.c
Normal file
195
drivers/gpu/drm/sti/sti_drm_plane.c
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
|
||||
* Fabien Dessenne <fabien.dessenne@st.com>
|
||||
* for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include "sti_compositor.h"
|
||||
#include "sti_drm_drv.h"
|
||||
#include "sti_drm_plane.h"
|
||||
#include "sti_vtg.h"
|
||||
|
||||
enum sti_layer_desc sti_layer_default_zorder[] = {
|
||||
STI_GDP_0,
|
||||
STI_VID_0,
|
||||
STI_GDP_1,
|
||||
STI_VID_1,
|
||||
STI_GDP_2,
|
||||
STI_GDP_3,
|
||||
};
|
||||
|
||||
/* (Background) < GDP0 < VID0 < GDP1 < VID1 < GDP2 < GDP3 < (ForeGround) */
|
||||
|
||||
static int
|
||||
sti_drm_update_plane(struct drm_plane *plane, struct drm_crtc *crtc,
|
||||
struct drm_framebuffer *fb, int crtc_x, int crtc_y,
|
||||
unsigned int crtc_w, unsigned int crtc_h,
|
||||
uint32_t src_x, uint32_t src_y,
|
||||
uint32_t src_w, uint32_t src_h)
|
||||
{
|
||||
struct sti_layer *layer = to_sti_layer(plane);
|
||||
struct sti_mixer *mixer = to_sti_mixer(crtc);
|
||||
int res;
|
||||
|
||||
DRM_DEBUG_KMS("CRTC:%d (%s) drm plane:%d (%s) drm fb:%d\n",
|
||||
crtc->base.id, sti_mixer_to_str(mixer),
|
||||
plane->base.id, sti_layer_to_str(layer), fb->base.id);
|
||||
DRM_DEBUG_KMS("(%dx%d)@(%d,%d)\n", crtc_w, crtc_h, crtc_x, crtc_y);
|
||||
|
||||
res = sti_mixer_set_layer_depth(mixer, layer);
|
||||
if (res) {
|
||||
DRM_ERROR("Can not set layer depth\n");
|
||||
return res;
|
||||
}
|
||||
|
||||
/* src_x are in 16.16 format. */
|
||||
res = sti_layer_prepare(layer, fb, &crtc->mode, mixer->id,
|
||||
crtc_x, crtc_y, crtc_w, crtc_h,
|
||||
src_x >> 16, src_y >> 16,
|
||||
src_w >> 16, src_h >> 16);
|
||||
if (res) {
|
||||
DRM_ERROR("Layer prepare failed\n");
|
||||
return res;
|
||||
}
|
||||
|
||||
res = sti_layer_commit(layer);
|
||||
if (res) {
|
||||
DRM_ERROR("Layer commit failed\n");
|
||||
return res;
|
||||
}
|
||||
|
||||
res = sti_mixer_set_layer_status(mixer, layer, true);
|
||||
if (res) {
|
||||
DRM_ERROR("Can not enable layer at mixer\n");
|
||||
return res;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sti_drm_disable_plane(struct drm_plane *plane)
|
||||
{
|
||||
struct sti_layer *layer;
|
||||
struct sti_mixer *mixer;
|
||||
int lay_res, mix_res;
|
||||
|
||||
if (!plane->crtc) {
|
||||
DRM_DEBUG_DRIVER("drm plane:%d not enabled\n", plane->base.id);
|
||||
return 0;
|
||||
}
|
||||
layer = to_sti_layer(plane);
|
||||
mixer = to_sti_mixer(plane->crtc);
|
||||
|
||||
DRM_DEBUG_DRIVER("CRTC:%d (%s) drm plane:%d (%s)\n",
|
||||
plane->crtc->base.id, sti_mixer_to_str(mixer),
|
||||
plane->base.id, sti_layer_to_str(layer));
|
||||
|
||||
/* Disable layer at mixer level */
|
||||
mix_res = sti_mixer_set_layer_status(mixer, layer, false);
|
||||
if (mix_res)
|
||||
DRM_ERROR("Can not disable layer at mixer\n");
|
||||
|
||||
/* Wait a while to be sure that a Vsync event is received */
|
||||
msleep(WAIT_NEXT_VSYNC_MS);
|
||||
|
||||
/* Then disable layer itself */
|
||||
lay_res = sti_layer_disable(layer);
|
||||
if (lay_res)
|
||||
DRM_ERROR("Layer disable failed\n");
|
||||
|
||||
if (lay_res || mix_res)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sti_drm_plane_destroy(struct drm_plane *plane)
|
||||
{
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
sti_drm_disable_plane(plane);
|
||||
drm_plane_cleanup(plane);
|
||||
}
|
||||
|
||||
static int sti_drm_plane_set_property(struct drm_plane *plane,
|
||||
struct drm_property *property,
|
||||
uint64_t val)
|
||||
{
|
||||
struct drm_device *dev = plane->dev;
|
||||
struct sti_drm_private *private = dev->dev_private;
|
||||
struct sti_layer *layer = to_sti_layer(plane);
|
||||
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
if (property == private->plane_zorder_property) {
|
||||
layer->zorder = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static struct drm_plane_funcs sti_drm_plane_funcs = {
|
||||
.update_plane = sti_drm_update_plane,
|
||||
.disable_plane = sti_drm_disable_plane,
|
||||
.destroy = sti_drm_plane_destroy,
|
||||
.set_property = sti_drm_plane_set_property,
|
||||
};
|
||||
|
||||
static void sti_drm_plane_attach_zorder_property(struct drm_plane *plane,
|
||||
uint64_t default_val)
|
||||
{
|
||||
struct drm_device *dev = plane->dev;
|
||||
struct sti_drm_private *private = dev->dev_private;
|
||||
struct drm_property *prop;
|
||||
struct sti_layer *layer = to_sti_layer(plane);
|
||||
|
||||
prop = private->plane_zorder_property;
|
||||
if (!prop) {
|
||||
prop = drm_property_create_range(dev, 0, "zpos", 0,
|
||||
GAM_MIXER_NB_DEPTH_LEVEL - 1);
|
||||
if (!prop)
|
||||
return;
|
||||
|
||||
private->plane_zorder_property = prop;
|
||||
}
|
||||
|
||||
drm_object_attach_property(&plane->base, prop, default_val);
|
||||
layer->zorder = default_val;
|
||||
}
|
||||
|
||||
struct drm_plane *sti_drm_plane_init(struct drm_device *dev,
|
||||
struct sti_layer *layer,
|
||||
unsigned int possible_crtcs,
|
||||
enum drm_plane_type type)
|
||||
{
|
||||
int err, i;
|
||||
uint64_t default_zorder = 0;
|
||||
|
||||
err = drm_universal_plane_init(dev, &layer->plane, possible_crtcs,
|
||||
&sti_drm_plane_funcs,
|
||||
sti_layer_get_formats(layer),
|
||||
sti_layer_get_nb_formats(layer), type);
|
||||
if (err) {
|
||||
DRM_ERROR("Failed to initialize plane\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sti_layer_default_zorder); i++)
|
||||
if (sti_layer_default_zorder[i] == layer->desc)
|
||||
break;
|
||||
|
||||
default_zorder = i;
|
||||
|
||||
if (type == DRM_PLANE_TYPE_OVERLAY)
|
||||
sti_drm_plane_attach_zorder_property(&layer->plane,
|
||||
default_zorder);
|
||||
|
||||
DRM_DEBUG_DRIVER("drm plane:%d mapped to %s with zorder:%llu\n",
|
||||
layer->plane.base.id,
|
||||
sti_layer_to_str(layer), default_zorder);
|
||||
|
||||
return &layer->plane;
|
||||
}
|
||||
18
drivers/gpu/drm/sti/sti_drm_plane.h
Normal file
18
drivers/gpu/drm/sti/sti_drm_plane.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#ifndef _STI_DRM_PLANE_H_
|
||||
#define _STI_DRM_PLANE_H_
|
||||
|
||||
#include <drm/drmP.h>
|
||||
|
||||
struct sti_layer;
|
||||
|
||||
struct drm_plane *sti_drm_plane_init(struct drm_device *dev,
|
||||
struct sti_layer *layer,
|
||||
unsigned int possible_crtcs,
|
||||
enum drm_plane_type type);
|
||||
#endif
|
||||
549
drivers/gpu/drm/sti/sti_gdp.c
Normal file
549
drivers/gpu/drm/sti/sti_gdp.c
Normal file
|
|
@ -0,0 +1,549 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
|
||||
* Fabien Dessenne <fabien.dessenne@st.com>
|
||||
* for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include "sti_compositor.h"
|
||||
#include "sti_gdp.h"
|
||||
#include "sti_layer.h"
|
||||
#include "sti_vtg.h"
|
||||
|
||||
#define ENA_COLOR_FILL BIT(8)
|
||||
#define WAIT_NEXT_VSYNC BIT(31)
|
||||
|
||||
/* GDP color formats */
|
||||
#define GDP_RGB565 0x00
|
||||
#define GDP_RGB888 0x01
|
||||
#define GDP_RGB888_32 0x02
|
||||
#define GDP_ARGB8565 0x04
|
||||
#define GDP_ARGB8888 0x05
|
||||
#define GDP_ARGB1555 0x06
|
||||
#define GDP_ARGB4444 0x07
|
||||
#define GDP_CLUT8 0x0B
|
||||
#define GDP_YCBR888 0x10
|
||||
#define GDP_YCBR422R 0x12
|
||||
#define GDP_AYCBR8888 0x15
|
||||
|
||||
#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 0x7FF
|
||||
|
||||
#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;
|
||||
struct sti_gdp_node *btm_field;
|
||||
};
|
||||
|
||||
/**
|
||||
* STI GDP structure
|
||||
*
|
||||
* @layer: layer structure
|
||||
* @clk_pix: pixel clock for the current gdp
|
||||
* @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
|
||||
*/
|
||||
struct sti_gdp {
|
||||
struct sti_layer layer;
|
||||
struct clk *clk_pix;
|
||||
struct notifier_block vtg_field_nb;
|
||||
bool is_curr_top;
|
||||
struct sti_gdp_node_list node_list[GDP_NODE_NB_BANK];
|
||||
};
|
||||
|
||||
#define to_sti_gdp(x) container_of(x, struct sti_gdp, layer)
|
||||
|
||||
static const uint32_t gdp_supported_formats[] = {
|
||||
DRM_FORMAT_XRGB8888,
|
||||
DRM_FORMAT_ARGB8888,
|
||||
DRM_FORMAT_ARGB4444,
|
||||
DRM_FORMAT_ARGB1555,
|
||||
DRM_FORMAT_RGB565,
|
||||
DRM_FORMAT_RGB888,
|
||||
DRM_FORMAT_AYUV,
|
||||
DRM_FORMAT_YUV444,
|
||||
DRM_FORMAT_VYUY,
|
||||
DRM_FORMAT_C8,
|
||||
};
|
||||
|
||||
static const uint32_t *sti_gdp_get_formats(struct sti_layer *layer)
|
||||
{
|
||||
return gdp_supported_formats;
|
||||
}
|
||||
|
||||
static unsigned int sti_gdp_get_nb_formats(struct sti_layer *layer)
|
||||
{
|
||||
return ARRAY_SIZE(gdp_supported_formats);
|
||||
}
|
||||
|
||||
static int sti_gdp_fourcc2format(int fourcc)
|
||||
{
|
||||
switch (fourcc) {
|
||||
case DRM_FORMAT_XRGB8888:
|
||||
return GDP_RGB888_32;
|
||||
case DRM_FORMAT_ARGB8888:
|
||||
return GDP_ARGB8888;
|
||||
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;
|
||||
case DRM_FORMAT_AYUV:
|
||||
return GDP_AYCBR8888;
|
||||
case DRM_FORMAT_YUV444:
|
||||
return GDP_YCBR888;
|
||||
case DRM_FORMAT_VYUY:
|
||||
return GDP_YCBR422R;
|
||||
case DRM_FORMAT_C8:
|
||||
return GDP_CLUT8;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int sti_gdp_get_alpharange(int format)
|
||||
{
|
||||
switch (format) {
|
||||
case GDP_ARGB8565:
|
||||
case GDP_ARGB8888:
|
||||
case GDP_AYCBR8888:
|
||||
return GAM_GDP_ALPHARANGE_255;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sti_gdp_get_free_nodes
|
||||
* @layer: gdp layer
|
||||
*
|
||||
* 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_layer *layer)
|
||||
{
|
||||
int hw_nvn;
|
||||
void *virt_nvn;
|
||||
struct sti_gdp *gdp = to_sti_gdp(layer);
|
||||
unsigned int i;
|
||||
|
||||
hw_nvn = readl(layer->regs + GAM_GDP_NVN_OFFSET);
|
||||
if (!hw_nvn)
|
||||
goto end;
|
||||
|
||||
virt_nvn = dma_to_virt(layer->dev, (dma_addr_t) hw_nvn);
|
||||
|
||||
for (i = 0; i < GDP_NODE_NB_BANK; i++)
|
||||
if ((virt_nvn != gdp->node_list[i].btm_field) &&
|
||||
(virt_nvn != gdp->node_list[i].top_field))
|
||||
return &gdp->node_list[i];
|
||||
|
||||
/* in hazardious cases restart with the first node */
|
||||
DRM_ERROR("inconsistent NVN for %s: 0x%08X\n",
|
||||
sti_layer_to_str(layer), hw_nvn);
|
||||
|
||||
end:
|
||||
return &gdp->node_list[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* sti_gdp_get_current_nodes
|
||||
* @layer: GDP layer
|
||||
*
|
||||
* 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_layer *layer)
|
||||
{
|
||||
int hw_nvn;
|
||||
void *virt_nvn;
|
||||
struct sti_gdp *gdp = to_sti_gdp(layer);
|
||||
unsigned int i;
|
||||
|
||||
hw_nvn = readl(layer->regs + GAM_GDP_NVN_OFFSET);
|
||||
if (!hw_nvn)
|
||||
goto end;
|
||||
|
||||
virt_nvn = dma_to_virt(layer->dev, (dma_addr_t) hw_nvn);
|
||||
|
||||
for (i = 0; i < GDP_NODE_NB_BANK; i++)
|
||||
if ((virt_nvn == gdp->node_list[i].btm_field) ||
|
||||
(virt_nvn == gdp->node_list[i].top_field))
|
||||
return &gdp->node_list[i];
|
||||
|
||||
end:
|
||||
DRM_DEBUG_DRIVER("Warning, NVN 0x%08X for %s does not match any node\n",
|
||||
hw_nvn, sti_layer_to_str(layer));
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* sti_gdp_prepare_layer
|
||||
* @lay: gdp layer
|
||||
* @first_prepare: true if it is the first time this function is called
|
||||
*
|
||||
* Update the free GDP node list according to the layer properties.
|
||||
*
|
||||
* RETURNS:
|
||||
* 0 on success.
|
||||
*/
|
||||
static int sti_gdp_prepare_layer(struct sti_layer *layer, bool first_prepare)
|
||||
{
|
||||
struct sti_gdp_node_list *list;
|
||||
struct sti_gdp_node *top_field, *btm_field;
|
||||
struct drm_display_mode *mode = layer->mode;
|
||||
struct device *dev = layer->dev;
|
||||
struct sti_gdp *gdp = to_sti_gdp(layer);
|
||||
struct sti_compositor *compo = dev_get_drvdata(dev);
|
||||
int format;
|
||||
unsigned int depth, bpp;
|
||||
int rate = mode->clock * 1000;
|
||||
int res;
|
||||
u32 ydo, xdo, yds, xds;
|
||||
|
||||
list = sti_gdp_get_free_nodes(layer);
|
||||
top_field = list->top_field;
|
||||
btm_field = list->btm_field;
|
||||
|
||||
dev_dbg(dev, "%s %s top_node:0x%p btm_node:0x%p\n", __func__,
|
||||
sti_layer_to_str(layer), top_field, btm_field);
|
||||
|
||||
/* Build the top field from layer params */
|
||||
top_field->gam_gdp_agc = GAM_GDP_AGC_FULL_RANGE;
|
||||
top_field->gam_gdp_ctl = WAIT_NEXT_VSYNC;
|
||||
format = sti_gdp_fourcc2format(layer->format);
|
||||
if (format == -1) {
|
||||
DRM_ERROR("Format not supported by GDP %.4s\n",
|
||||
(char *)&layer->format);
|
||||
return 1;
|
||||
}
|
||||
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;
|
||||
|
||||
/* pixel memory location */
|
||||
drm_fb_get_bpp_depth(layer->format, &depth, &bpp);
|
||||
top_field->gam_gdp_pml = (u32) layer->paddr + layer->offsets[0];
|
||||
top_field->gam_gdp_pml += layer->src_x * (bpp >> 3);
|
||||
top_field->gam_gdp_pml += layer->src_y * layer->pitches[0];
|
||||
|
||||
/* input parameters */
|
||||
top_field->gam_gdp_pmp = layer->pitches[0];
|
||||
top_field->gam_gdp_size =
|
||||
clamp_val(layer->src_h, 0, GAM_GDP_SIZE_MAX) << 16 |
|
||||
clamp_val(layer->src_w, 0, GAM_GDP_SIZE_MAX);
|
||||
|
||||
/* output parameters */
|
||||
ydo = sti_vtg_get_line_number(*mode, layer->dst_y);
|
||||
yds = sti_vtg_get_line_number(*mode, layer->dst_y + layer->dst_h - 1);
|
||||
xdo = sti_vtg_get_pixel_number(*mode, layer->dst_x);
|
||||
xds = sti_vtg_get_pixel_number(*mode, layer->dst_x + layer->dst_w - 1);
|
||||
top_field->gam_gdp_vpo = (ydo << 16) | xdo;
|
||||
top_field->gam_gdp_vps = (yds << 16) | xds;
|
||||
|
||||
/* Same content and chained together */
|
||||
memcpy(btm_field, top_field, sizeof(*btm_field));
|
||||
top_field->gam_gdp_nvn = virt_to_dma(dev, btm_field);
|
||||
btm_field->gam_gdp_nvn = virt_to_dma(dev, top_field);
|
||||
|
||||
/* Interlaced mode */
|
||||
if (layer->mode->flags & DRM_MODE_FLAG_INTERLACE)
|
||||
btm_field->gam_gdp_pml = top_field->gam_gdp_pml +
|
||||
layer->pitches[0];
|
||||
|
||||
if (first_prepare) {
|
||||
/* Register gdp callback */
|
||||
if (sti_vtg_register_client(layer->mixer_id == STI_MIXER_MAIN ?
|
||||
compo->vtg_main : compo->vtg_aux,
|
||||
&gdp->vtg_field_nb, layer->mixer_id)) {
|
||||
DRM_ERROR("Cannot register VTG notifier\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Set and enable gdp clock */
|
||||
if (gdp->clk_pix) {
|
||||
res = clk_set_rate(gdp->clk_pix, rate);
|
||||
if (res < 0) {
|
||||
DRM_ERROR("Cannot set rate (%dHz) for gdp\n",
|
||||
rate);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (clk_prepare_enable(gdp->clk_pix)) {
|
||||
DRM_ERROR("Failed to prepare/enable gdp\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sti_gdp_commit_layer
|
||||
* @lay: gdp layer
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* RETURNS:
|
||||
* 0 on success.
|
||||
*/
|
||||
static int sti_gdp_commit_layer(struct sti_layer *layer)
|
||||
{
|
||||
struct sti_gdp_node_list *updated_list = sti_gdp_get_free_nodes(layer);
|
||||
struct sti_gdp_node *updated_top_node = updated_list->top_field;
|
||||
struct sti_gdp_node *updated_btm_node = updated_list->btm_field;
|
||||
struct sti_gdp *gdp = to_sti_gdp(layer);
|
||||
u32 dma_updated_top = virt_to_dma(layer->dev, updated_top_node);
|
||||
u32 dma_updated_btm = virt_to_dma(layer->dev, updated_btm_node);
|
||||
struct sti_gdp_node_list *curr_list = sti_gdp_get_current_nodes(layer);
|
||||
|
||||
dev_dbg(layer->dev, "%s %s top/btm_node:0x%p/0x%p\n", __func__,
|
||||
sti_layer_to_str(layer),
|
||||
updated_top_node, updated_btm_node);
|
||||
dev_dbg(layer->dev, "Current NVN:0x%X\n",
|
||||
readl(layer->regs + GAM_GDP_NVN_OFFSET));
|
||||
dev_dbg(layer->dev, "Posted buff: %lx current buff: %x\n",
|
||||
(unsigned long)layer->paddr,
|
||||
readl(layer->regs + GAM_GDP_PML_OFFSET));
|
||||
|
||||
if (curr_list == NULL) {
|
||||
/* First update or invalid node should directly write in the
|
||||
* hw register */
|
||||
DRM_DEBUG_DRIVER("%s first update (or invalid node)",
|
||||
sti_layer_to_str(layer));
|
||||
|
||||
writel(gdp->is_curr_top == true ?
|
||||
dma_updated_btm : dma_updated_top,
|
||||
layer->regs + GAM_GDP_NVN_OFFSET);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (layer->mode->flags & DRM_MODE_FLAG_INTERLACE) {
|
||||
if (gdp->is_curr_top == true) {
|
||||
/* 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,
|
||||
layer->regs + GAM_GDP_NVN_OFFSET);
|
||||
}
|
||||
} else {
|
||||
/* Direct update for progressive to avoid one frame delay */
|
||||
writel(dma_updated_top, layer->regs + GAM_GDP_NVN_OFFSET);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sti_gdp_disable_layer
|
||||
* @lay: gdp layer
|
||||
*
|
||||
* Disable a GDP.
|
||||
*
|
||||
* RETURNS:
|
||||
* 0 on success.
|
||||
*/
|
||||
static int sti_gdp_disable_layer(struct sti_layer *layer)
|
||||
{
|
||||
unsigned int i;
|
||||
struct sti_gdp *gdp = to_sti_gdp(layer);
|
||||
struct sti_compositor *compo = dev_get_drvdata(layer->dev);
|
||||
|
||||
DRM_DEBUG_DRIVER("%s\n", sti_layer_to_str(layer));
|
||||
|
||||
/* 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(layer->mixer_id == STI_MIXER_MAIN ?
|
||||
compo->vtg_main : compo->vtg_aux, &gdp->vtg_field_nb))
|
||||
DRM_DEBUG_DRIVER("Warning: cannot unregister VTG notifier\n");
|
||||
|
||||
if (gdp->clk_pix)
|
||||
clk_disable_unprepare(gdp->clk_pix);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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);
|
||||
|
||||
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_layer *layer)
|
||||
{
|
||||
struct sti_gdp *gdp = to_sti_gdp(layer);
|
||||
struct device_node *np = layer->dev->of_node;
|
||||
dma_addr_t dma;
|
||||
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_writecombine(layer->dev,
|
||||
size, &dma, GFP_KERNEL | GFP_DMA);
|
||||
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 (virt_to_dma(layer->dev, base) & 0xF) {
|
||||
DRM_ERROR("Mem alignment failed\n");
|
||||
return;
|
||||
}
|
||||
gdp->node_list[i].top_field = base;
|
||||
DRM_DEBUG_DRIVER("node[%d].top_field=%p\n", i, base);
|
||||
base += sizeof(struct sti_gdp_node);
|
||||
|
||||
if (virt_to_dma(layer->dev, base) & 0xF) {
|
||||
DRM_ERROR("Mem alignment failed\n");
|
||||
return;
|
||||
}
|
||||
gdp->node_list[i].btm_field = base;
|
||||
DRM_DEBUG_DRIVER("node[%d].btm_field=%p\n", i, base);
|
||||
base += 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 (layer->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(layer->dev, clk_name);
|
||||
if (IS_ERR(gdp->clk_pix))
|
||||
DRM_ERROR("Cannot get %s clock\n", clk_name);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct sti_layer_funcs gdp_ops = {
|
||||
.get_formats = sti_gdp_get_formats,
|
||||
.get_nb_formats = sti_gdp_get_nb_formats,
|
||||
.init = sti_gdp_init,
|
||||
.prepare = sti_gdp_prepare_layer,
|
||||
.commit = sti_gdp_commit_layer,
|
||||
.disable = sti_gdp_disable_layer,
|
||||
};
|
||||
|
||||
struct sti_layer *sti_gdp_create(struct device *dev, int id)
|
||||
{
|
||||
struct sti_gdp *gdp;
|
||||
|
||||
gdp = devm_kzalloc(dev, sizeof(*gdp), GFP_KERNEL);
|
||||
if (!gdp) {
|
||||
DRM_ERROR("Failed to allocate memory for GDP\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gdp->layer.ops = &gdp_ops;
|
||||
gdp->vtg_field_nb.notifier_call = sti_gdp_field_cb;
|
||||
|
||||
return (struct sti_layer *)gdp;
|
||||
}
|
||||
16
drivers/gpu/drm/sti/sti_gdp.h
Normal file
16
drivers/gpu/drm/sti/sti_gdp.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
|
||||
* Fabien Dessenne <fabien.dessenne@st.com>
|
||||
* for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#ifndef _STI_GDP_H_
|
||||
#define _STI_GDP_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct sti_layer *sti_gdp_create(struct device *dev, int id);
|
||||
|
||||
#endif
|
||||
794
drivers/gpu/drm/sti/sti_hda.c
Normal file
794
drivers/gpu/drm/sti/sti_hda.c
Normal file
|
|
@ -0,0 +1,794 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_crtc_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 VIDEO_DACS_CONTROL_MASK 0x0FFF
|
||||
#define VIDEO_DACS_CONTROL_SYSCFG2535 0x085C /* for stih416 */
|
||||
#define DAC_CFG_HD_OFF_SHIFT 5
|
||||
#define DAC_CFG_HD_OFF_MASK (0x7 << DAC_CFG_HD_OFF_SHIFT)
|
||||
#define VIDEO_DACS_CONTROL_SYSCFG5072 0x0120 /* for stih407 */
|
||||
#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;
|
||||
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 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
u32 mask;
|
||||
|
||||
if (hda->video_dacs_ctrl) {
|
||||
u32 val;
|
||||
|
||||
switch ((u32)hda->video_dacs_ctrl & VIDEO_DACS_CONTROL_MASK) {
|
||||
case VIDEO_DACS_CONTROL_SYSCFG2535:
|
||||
mask = DAC_CFG_HD_OFF_MASK;
|
||||
break;
|
||||
case VIDEO_DACS_CONTROL_SYSCFG5072:
|
||||
mask = DAC_CFG_HD_HZUVW_OFF_MASK;
|
||||
break;
|
||||
default:
|
||||
DRM_INFO("Video DACS control register not supported!");
|
||||
return;
|
||||
}
|
||||
|
||||
val = readl(hda->video_dacs_ctrl);
|
||||
if (enable)
|
||||
val &= ~mask;
|
||||
else
|
||||
val |= mask;
|
||||
|
||||
writel(val, hda->video_dacs_ctrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = bridge->driver_private;
|
||||
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 = bridge->driver_private;
|
||||
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,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct sti_hda *hda = bridge->driver_private;
|
||||
u32 mode_idx;
|
||||
int hddac_rate;
|
||||
int ret;
|
||||
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
memcpy(&hda->mode, mode, sizeof(struct drm_display_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 void sti_hda_brigde_destroy(struct drm_bridge *bridge)
|
||||
{
|
||||
drm_bridge_cleanup(bridge);
|
||||
kfree(bridge);
|
||||
}
|
||||
|
||||
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,
|
||||
.destroy = sti_hda_brigde_destroy,
|
||||
};
|
||||
|
||||
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;
|
||||
mode->vrefresh = drm_mode_vrefresh(mode);
|
||||
|
||||
/* the first mode is the preferred mode */
|
||||
if (i == 0)
|
||||
mode->type |= DRM_MODE_TYPE_PREFERRED;
|
||||
|
||||
drm_mode_probed_add(connector, mode);
|
||||
count++;
|
||||
}
|
||||
|
||||
drm_mode_sort(&connector->modes);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
#define CLK_TOLERANCE_HZ 50
|
||||
|
||||
static int sti_hda_connector_mode_valid(struct drm_connector *connector,
|
||||
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;
|
||||
}
|
||||
|
||||
struct drm_encoder *sti_hda_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
struct sti_hda_connector *hda_connector
|
||||
= to_sti_hda_connector(connector);
|
||||
|
||||
/* Best encoder is the one associated during connector creation */
|
||||
return hda_connector->encoder;
|
||||
}
|
||||
|
||||
static struct drm_connector_helper_funcs sti_hda_connector_helper_funcs = {
|
||||
.get_modes = sti_hda_connector_get_modes,
|
||||
.mode_valid = sti_hda_connector_mode_valid,
|
||||
.best_encoder = sti_hda_best_encoder,
|
||||
};
|
||||
|
||||
static enum drm_connector_status
|
||||
sti_hda_connector_detect(struct drm_connector *connector, bool force)
|
||||
{
|
||||
return connector_status_connected;
|
||||
}
|
||||
|
||||
static void sti_hda_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct sti_hda_connector *hda_connector
|
||||
= to_sti_hda_connector(connector);
|
||||
|
||||
drm_connector_unregister(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
kfree(hda_connector);
|
||||
}
|
||||
|
||||
static struct drm_connector_funcs sti_hda_connector_funcs = {
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.detect = sti_hda_connector_detect,
|
||||
.destroy = sti_hda_connector_destroy,
|
||||
};
|
||||
|
||||
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;
|
||||
struct drm_bridge *bridge;
|
||||
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;
|
||||
|
||||
bridge = devm_kzalloc(dev, sizeof(*bridge), GFP_KERNEL);
|
||||
if (!bridge)
|
||||
return -ENOMEM;
|
||||
|
||||
bridge->driver_private = hda;
|
||||
drm_bridge_init(drm_dev, bridge, &sti_hda_bridge_funcs);
|
||||
|
||||
encoder->bridge = bridge;
|
||||
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_register(drm_connector);
|
||||
if (err)
|
||||
goto err_connector;
|
||||
|
||||
err = drm_mode_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_connector_unregister(drm_connector);
|
||||
err_connector:
|
||||
drm_bridge_cleanup(bridge);
|
||||
drm_connector_cleanup(drm_connector);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void sti_hda_unbind(struct device *dev,
|
||||
struct device *master, void *data)
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
DRM_INFO("%s\n", __func__);
|
||||
|
||||
hda = devm_kzalloc(dev, sizeof(*hda), GFP_KERNEL);
|
||||
if (!hda)
|
||||
return -ENOMEM;
|
||||
|
||||
hda->dev = pdev->dev;
|
||||
|
||||
/* Get resources */
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hda-reg");
|
||||
if (!res) {
|
||||
DRM_ERROR("Invalid hda resource\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
hda->regs = devm_ioremap_nocache(dev, res->start, resource_size(res));
|
||||
if (!hda->regs)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||
"video-dacs-ctrl");
|
||||
if (res) {
|
||||
hda->video_dacs_ctrl = devm_ioremap_nocache(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);
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, hda);
|
||||
|
||||
return component_add(&pdev->dev, &sti_hda_ops);
|
||||
}
|
||||
|
||||
static int sti_hda_remove(struct platform_device *pdev)
|
||||
{
|
||||
component_del(&pdev->dev, &sti_hda_ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = hda_of_match,
|
||||
},
|
||||
.probe = sti_hda_probe,
|
||||
.remove = sti_hda_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(sti_hda_driver);
|
||||
|
||||
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
809
drivers/gpu/drm/sti/sti_hdmi.c
Normal file
809
drivers/gpu/drm/sti/sti_hdmi.c
Normal file
|
|
@ -0,0 +1,809 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/hdmi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_edid.h>
|
||||
|
||||
#include "sti_hdmi.h"
|
||||
#include "sti_hdmi_tx3g4c28phy.h"
|
||||
#include "sti_hdmi_tx3g0c55phy.h"
|
||||
#include "sti_vtg.h"
|
||||
|
||||
#define HDMI_CFG 0x0000
|
||||
#define HDMI_INT_EN 0x0004
|
||||
#define HDMI_INT_STA 0x0008
|
||||
#define HDMI_INT_CLR 0x000C
|
||||
#define HDMI_STA 0x0010
|
||||
#define HDMI_ACTIVE_VID_XMIN 0x0100
|
||||
#define HDMI_ACTIVE_VID_XMAX 0x0104
|
||||
#define HDMI_ACTIVE_VID_YMIN 0x0108
|
||||
#define HDMI_ACTIVE_VID_YMAX 0x010C
|
||||
#define HDMI_DFLT_CHL0_DAT 0x0110
|
||||
#define HDMI_DFLT_CHL1_DAT 0x0114
|
||||
#define HDMI_DFLT_CHL2_DAT 0x0118
|
||||
#define HDMI_SW_DI_1_HEAD_WORD 0x0210
|
||||
#define HDMI_SW_DI_1_PKT_WORD0 0x0214
|
||||
#define HDMI_SW_DI_1_PKT_WORD1 0x0218
|
||||
#define HDMI_SW_DI_1_PKT_WORD2 0x021C
|
||||
#define HDMI_SW_DI_1_PKT_WORD3 0x0220
|
||||
#define HDMI_SW_DI_1_PKT_WORD4 0x0224
|
||||
#define HDMI_SW_DI_1_PKT_WORD5 0x0228
|
||||
#define HDMI_SW_DI_1_PKT_WORD6 0x022C
|
||||
#define HDMI_SW_DI_CFG 0x0230
|
||||
|
||||
#define HDMI_IFRAME_SLOT_AVI 1
|
||||
|
||||
#define XCAT(prefix, x, suffix) prefix ## x ## suffix
|
||||
#define HDMI_SW_DI_N_HEAD_WORD(x) XCAT(HDMI_SW_DI_, x, _HEAD_WORD)
|
||||
#define HDMI_SW_DI_N_PKT_WORD0(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD0)
|
||||
#define HDMI_SW_DI_N_PKT_WORD1(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD1)
|
||||
#define HDMI_SW_DI_N_PKT_WORD2(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD2)
|
||||
#define HDMI_SW_DI_N_PKT_WORD3(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD3)
|
||||
#define HDMI_SW_DI_N_PKT_WORD4(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD4)
|
||||
#define HDMI_SW_DI_N_PKT_WORD5(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD5)
|
||||
#define HDMI_SW_DI_N_PKT_WORD6(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD6)
|
||||
|
||||
#define HDMI_IFRAME_DISABLED 0x0
|
||||
#define HDMI_IFRAME_SINGLE_SHOT 0x1
|
||||
#define HDMI_IFRAME_FIELD 0x2
|
||||
#define HDMI_IFRAME_FRAME 0x3
|
||||
#define HDMI_IFRAME_MASK 0x3
|
||||
#define HDMI_IFRAME_CFG_DI_N(x, n) ((x) << ((n-1)*4)) /* n from 1 to 6 */
|
||||
|
||||
#define HDMI_CFG_DEVICE_EN BIT(0)
|
||||
#define HDMI_CFG_HDMI_NOT_DVI BIT(1)
|
||||
#define HDMI_CFG_HDCP_EN BIT(2)
|
||||
#define HDMI_CFG_ESS_NOT_OESS BIT(3)
|
||||
#define HDMI_CFG_H_SYNC_POL_NEG BIT(4)
|
||||
#define HDMI_CFG_SINK_TERM_DET_EN BIT(5)
|
||||
#define HDMI_CFG_V_SYNC_POL_NEG BIT(6)
|
||||
#define HDMI_CFG_422_EN BIT(8)
|
||||
#define HDMI_CFG_FIFO_OVERRUN_CLR BIT(12)
|
||||
#define HDMI_CFG_FIFO_UNDERRUN_CLR BIT(13)
|
||||
#define HDMI_CFG_SW_RST_EN BIT(31)
|
||||
|
||||
#define HDMI_INT_GLOBAL BIT(0)
|
||||
#define HDMI_INT_SW_RST BIT(1)
|
||||
#define HDMI_INT_PIX_CAP BIT(3)
|
||||
#define HDMI_INT_HOT_PLUG BIT(4)
|
||||
#define HDMI_INT_DLL_LCK BIT(5)
|
||||
#define HDMI_INT_NEW_FRAME BIT(6)
|
||||
#define HDMI_INT_GENCTRL_PKT BIT(7)
|
||||
#define HDMI_INT_SINK_TERM_PRESENT BIT(11)
|
||||
|
||||
#define HDMI_DEFAULT_INT (HDMI_INT_SINK_TERM_PRESENT \
|
||||
| HDMI_INT_DLL_LCK \
|
||||
| HDMI_INT_HOT_PLUG \
|
||||
| HDMI_INT_GLOBAL)
|
||||
|
||||
#define HDMI_WORKING_INT (HDMI_INT_SINK_TERM_PRESENT \
|
||||
| HDMI_INT_GENCTRL_PKT \
|
||||
| HDMI_INT_NEW_FRAME \
|
||||
| HDMI_INT_DLL_LCK \
|
||||
| HDMI_INT_HOT_PLUG \
|
||||
| HDMI_INT_PIX_CAP \
|
||||
| HDMI_INT_SW_RST \
|
||||
| HDMI_INT_GLOBAL)
|
||||
|
||||
#define HDMI_STA_SW_RST BIT(1)
|
||||
|
||||
struct sti_hdmi_connector {
|
||||
struct drm_connector drm_connector;
|
||||
struct drm_encoder *encoder;
|
||||
struct sti_hdmi *hdmi;
|
||||
};
|
||||
|
||||
#define to_sti_hdmi_connector(x) \
|
||||
container_of(x, struct sti_hdmi_connector, drm_connector)
|
||||
|
||||
u32 hdmi_read(struct sti_hdmi *hdmi, int offset)
|
||||
{
|
||||
return readl(hdmi->regs + offset);
|
||||
}
|
||||
|
||||
void hdmi_write(struct sti_hdmi *hdmi, u32 val, int offset)
|
||||
{
|
||||
writel(val, hdmi->regs + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* HDMI interrupt handler threaded
|
||||
*
|
||||
* @irq: irq number
|
||||
* @arg: connector structure
|
||||
*/
|
||||
static irqreturn_t hdmi_irq_thread(int irq, void *arg)
|
||||
{
|
||||
struct sti_hdmi *hdmi = arg;
|
||||
|
||||
/* Hot plug/unplug IRQ */
|
||||
if (hdmi->irq_status & HDMI_INT_HOT_PLUG) {
|
||||
/* read gpio to get the status */
|
||||
hdmi->hpd = gpio_get_value(hdmi->hpd_gpio);
|
||||
if (hdmi->drm_dev)
|
||||
drm_helper_hpd_irq_event(hdmi->drm_dev);
|
||||
}
|
||||
|
||||
/* Sw reset and PLL lock are exclusive so we can use the same
|
||||
* event to signal them
|
||||
*/
|
||||
if (hdmi->irq_status & (HDMI_INT_SW_RST | HDMI_INT_DLL_LCK)) {
|
||||
hdmi->event_received = true;
|
||||
wake_up_interruptible(&hdmi->wait_event);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* HDMI interrupt handler
|
||||
*
|
||||
* @irq: irq number
|
||||
* @arg: connector structure
|
||||
*/
|
||||
static irqreturn_t hdmi_irq(int irq, void *arg)
|
||||
{
|
||||
struct sti_hdmi *hdmi = arg;
|
||||
|
||||
/* read interrupt status */
|
||||
hdmi->irq_status = hdmi_read(hdmi, HDMI_INT_STA);
|
||||
|
||||
/* clear interrupt status */
|
||||
hdmi_write(hdmi, hdmi->irq_status, HDMI_INT_CLR);
|
||||
|
||||
/* force sync bus write */
|
||||
hdmi_read(hdmi, HDMI_INT_STA);
|
||||
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set hdmi active area depending on the drm display mode selected
|
||||
*
|
||||
* @hdmi: pointer on the hdmi internal structure
|
||||
*/
|
||||
static void hdmi_active_area(struct sti_hdmi *hdmi)
|
||||
{
|
||||
u32 xmin, xmax;
|
||||
u32 ymin, ymax;
|
||||
|
||||
xmin = sti_vtg_get_pixel_number(hdmi->mode, 0);
|
||||
xmax = sti_vtg_get_pixel_number(hdmi->mode, hdmi->mode.hdisplay - 1);
|
||||
ymin = sti_vtg_get_line_number(hdmi->mode, 0);
|
||||
ymax = sti_vtg_get_line_number(hdmi->mode, hdmi->mode.vdisplay - 1);
|
||||
|
||||
hdmi_write(hdmi, xmin, HDMI_ACTIVE_VID_XMIN);
|
||||
hdmi_write(hdmi, xmax, HDMI_ACTIVE_VID_XMAX);
|
||||
hdmi_write(hdmi, ymin, HDMI_ACTIVE_VID_YMIN);
|
||||
hdmi_write(hdmi, ymax, HDMI_ACTIVE_VID_YMAX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overall hdmi configuration
|
||||
*
|
||||
* @hdmi: pointer on the hdmi internal structure
|
||||
*/
|
||||
static void hdmi_config(struct sti_hdmi *hdmi)
|
||||
{
|
||||
u32 conf;
|
||||
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
/* Clear overrun and underrun fifo */
|
||||
conf = HDMI_CFG_FIFO_OVERRUN_CLR | HDMI_CFG_FIFO_UNDERRUN_CLR;
|
||||
|
||||
/* Enable HDMI mode not DVI */
|
||||
conf |= HDMI_CFG_HDMI_NOT_DVI | HDMI_CFG_ESS_NOT_OESS;
|
||||
|
||||
/* Enable sink term detection */
|
||||
conf |= HDMI_CFG_SINK_TERM_DET_EN;
|
||||
|
||||
/* Set Hsync polarity */
|
||||
if (hdmi->mode.flags & DRM_MODE_FLAG_NHSYNC) {
|
||||
DRM_DEBUG_DRIVER("H Sync Negative\n");
|
||||
conf |= HDMI_CFG_H_SYNC_POL_NEG;
|
||||
}
|
||||
|
||||
/* Set Vsync polarity */
|
||||
if (hdmi->mode.flags & DRM_MODE_FLAG_NVSYNC) {
|
||||
DRM_DEBUG_DRIVER("V Sync Negative\n");
|
||||
conf |= HDMI_CFG_V_SYNC_POL_NEG;
|
||||
}
|
||||
|
||||
/* Enable HDMI */
|
||||
conf |= HDMI_CFG_DEVICE_EN;
|
||||
|
||||
hdmi_write(hdmi, conf, HDMI_CFG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare and configure the AVI infoframe
|
||||
*
|
||||
* AVI infoframe are transmitted at least once per two video field and
|
||||
* contains information about HDMI transmission mode such as color space,
|
||||
* colorimetry, ...
|
||||
*
|
||||
* @hdmi: pointer on the hdmi internal structure
|
||||
*
|
||||
* Return negative value if error occurs
|
||||
*/
|
||||
static int hdmi_avi_infoframe_config(struct sti_hdmi *hdmi)
|
||||
{
|
||||
struct drm_display_mode *mode = &hdmi->mode;
|
||||
struct hdmi_avi_infoframe infoframe;
|
||||
u8 buffer[HDMI_INFOFRAME_SIZE(AVI)];
|
||||
u8 *frame = buffer + HDMI_INFOFRAME_HEADER_SIZE;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
ret = drm_hdmi_avi_infoframe_from_display_mode(&infoframe, mode);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("failed to setup AVI infoframe: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* fixed infoframe configuration not linked to the mode */
|
||||
infoframe.colorspace = HDMI_COLORSPACE_RGB;
|
||||
infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT;
|
||||
infoframe.colorimetry = HDMI_COLORIMETRY_NONE;
|
||||
|
||||
ret = hdmi_avi_infoframe_pack(&infoframe, buffer, sizeof(buffer));
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("failed to pack AVI infoframe: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Disable transmission slot for AVI infoframe */
|
||||
val = hdmi_read(hdmi, HDMI_SW_DI_CFG);
|
||||
val &= ~HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, HDMI_IFRAME_SLOT_AVI);
|
||||
hdmi_write(hdmi, val, HDMI_SW_DI_CFG);
|
||||
|
||||
/* Infoframe header */
|
||||
val = buffer[0x0];
|
||||
val |= buffer[0x1] << 8;
|
||||
val |= buffer[0x2] << 16;
|
||||
hdmi_write(hdmi, val, HDMI_SW_DI_N_HEAD_WORD(HDMI_IFRAME_SLOT_AVI));
|
||||
|
||||
/* Infoframe packet bytes */
|
||||
val = frame[0x0];
|
||||
val |= frame[0x1] << 8;
|
||||
val |= frame[0x2] << 16;
|
||||
val |= frame[0x3] << 24;
|
||||
hdmi_write(hdmi, val, HDMI_SW_DI_N_PKT_WORD0(HDMI_IFRAME_SLOT_AVI));
|
||||
|
||||
val = frame[0x4];
|
||||
val |= frame[0x5] << 8;
|
||||
val |= frame[0x6] << 16;
|
||||
val |= frame[0x7] << 24;
|
||||
hdmi_write(hdmi, val, HDMI_SW_DI_N_PKT_WORD1(HDMI_IFRAME_SLOT_AVI));
|
||||
|
||||
val = frame[0x8];
|
||||
val |= frame[0x9] << 8;
|
||||
val |= frame[0xA] << 16;
|
||||
val |= frame[0xB] << 24;
|
||||
hdmi_write(hdmi, val, HDMI_SW_DI_N_PKT_WORD2(HDMI_IFRAME_SLOT_AVI));
|
||||
|
||||
val = frame[0xC];
|
||||
hdmi_write(hdmi, val, HDMI_SW_DI_N_PKT_WORD3(HDMI_IFRAME_SLOT_AVI));
|
||||
|
||||
/* Enable transmission slot for AVI infoframe
|
||||
* According to the hdmi specification, AVI infoframe should be
|
||||
* transmitted at least once per two video fields
|
||||
*/
|
||||
val = hdmi_read(hdmi, HDMI_SW_DI_CFG);
|
||||
val |= HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_FIELD, HDMI_IFRAME_SLOT_AVI);
|
||||
hdmi_write(hdmi, val, HDMI_SW_DI_CFG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Software reset of the hdmi subsystem
|
||||
*
|
||||
* @hdmi: pointer on the hdmi internal structure
|
||||
*
|
||||
*/
|
||||
#define HDMI_TIMEOUT_SWRESET 100 /*milliseconds */
|
||||
static void hdmi_swreset(struct sti_hdmi *hdmi)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
/* Enable hdmi_audio clock only during hdmi reset */
|
||||
if (clk_prepare_enable(hdmi->clk_audio))
|
||||
DRM_INFO("Failed to prepare/enable hdmi_audio clk\n");
|
||||
|
||||
/* Sw reset */
|
||||
hdmi->event_received = false;
|
||||
|
||||
val = hdmi_read(hdmi, HDMI_CFG);
|
||||
val |= HDMI_CFG_SW_RST_EN;
|
||||
hdmi_write(hdmi, val, HDMI_CFG);
|
||||
|
||||
/* Wait reset completed */
|
||||
wait_event_interruptible_timeout(hdmi->wait_event,
|
||||
hdmi->event_received == true,
|
||||
msecs_to_jiffies
|
||||
(HDMI_TIMEOUT_SWRESET));
|
||||
|
||||
/*
|
||||
* HDMI_STA_SW_RST bit is set to '1' when SW_RST bit in HDMI_CFG is
|
||||
* set to '1' and clk_audio is running.
|
||||
*/
|
||||
if ((hdmi_read(hdmi, HDMI_STA) & HDMI_STA_SW_RST) == 0)
|
||||
DRM_DEBUG_DRIVER("Warning: HDMI sw reset timeout occurs\n");
|
||||
|
||||
val = hdmi_read(hdmi, HDMI_CFG);
|
||||
val &= ~HDMI_CFG_SW_RST_EN;
|
||||
hdmi_write(hdmi, val, HDMI_CFG);
|
||||
|
||||
/* Disable hdmi_audio clock. Not used anymore for drm purpose */
|
||||
clk_disable_unprepare(hdmi->clk_audio);
|
||||
}
|
||||
|
||||
static void sti_hdmi_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct sti_hdmi *hdmi = bridge->driver_private;
|
||||
|
||||
u32 val = hdmi_read(hdmi, HDMI_CFG);
|
||||
|
||||
if (!hdmi->enabled)
|
||||
return;
|
||||
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
/* Disable HDMI */
|
||||
val &= ~HDMI_CFG_DEVICE_EN;
|
||||
hdmi_write(hdmi, val, HDMI_CFG);
|
||||
|
||||
hdmi_write(hdmi, 0xffffffff, HDMI_INT_CLR);
|
||||
|
||||
/* Stop the phy */
|
||||
hdmi->phy_ops->stop(hdmi);
|
||||
|
||||
/* Set the default channel data to be a dark red */
|
||||
hdmi_write(hdmi, 0x0000, HDMI_DFLT_CHL0_DAT);
|
||||
hdmi_write(hdmi, 0x0000, HDMI_DFLT_CHL1_DAT);
|
||||
hdmi_write(hdmi, 0x0060, HDMI_DFLT_CHL2_DAT);
|
||||
|
||||
/* Disable/unprepare hdmi clock */
|
||||
clk_disable_unprepare(hdmi->clk_phy);
|
||||
clk_disable_unprepare(hdmi->clk_tmds);
|
||||
clk_disable_unprepare(hdmi->clk_pix);
|
||||
|
||||
hdmi->enabled = false;
|
||||
}
|
||||
|
||||
static void sti_hdmi_pre_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct sti_hdmi *hdmi = bridge->driver_private;
|
||||
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
if (hdmi->enabled)
|
||||
return;
|
||||
|
||||
/* Prepare/enable clocks */
|
||||
if (clk_prepare_enable(hdmi->clk_pix))
|
||||
DRM_ERROR("Failed to prepare/enable hdmi_pix clk\n");
|
||||
if (clk_prepare_enable(hdmi->clk_tmds))
|
||||
DRM_ERROR("Failed to prepare/enable hdmi_tmds clk\n");
|
||||
if (clk_prepare_enable(hdmi->clk_phy))
|
||||
DRM_ERROR("Failed to prepare/enable hdmi_rejec_pll clk\n");
|
||||
|
||||
hdmi->enabled = true;
|
||||
|
||||
/* Program hdmi serializer and start phy */
|
||||
if (!hdmi->phy_ops->start(hdmi)) {
|
||||
DRM_ERROR("Unable to start hdmi phy\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Program hdmi active area */
|
||||
hdmi_active_area(hdmi);
|
||||
|
||||
/* Enable working interrupts */
|
||||
hdmi_write(hdmi, HDMI_WORKING_INT, HDMI_INT_EN);
|
||||
|
||||
/* Program hdmi config */
|
||||
hdmi_config(hdmi);
|
||||
|
||||
/* Program AVI infoframe */
|
||||
if (hdmi_avi_infoframe_config(hdmi))
|
||||
DRM_ERROR("Unable to configure AVI infoframe\n");
|
||||
|
||||
/* Sw reset */
|
||||
hdmi_swreset(hdmi);
|
||||
}
|
||||
|
||||
static void sti_hdmi_set_mode(struct drm_bridge *bridge,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct sti_hdmi *hdmi = bridge->driver_private;
|
||||
int ret;
|
||||
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
/* Copy the drm display mode in the connector local structure */
|
||||
memcpy(&hdmi->mode, mode, sizeof(struct drm_display_mode));
|
||||
|
||||
/* Update clock framerate according to the selected mode */
|
||||
ret = clk_set_rate(hdmi->clk_pix, mode->clock * 1000);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("Cannot set rate (%dHz) for hdmi_pix clk\n",
|
||||
mode->clock * 1000);
|
||||
return;
|
||||
}
|
||||
ret = clk_set_rate(hdmi->clk_phy, mode->clock * 1000);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("Cannot set rate (%dHz) for hdmi_rejection_pll clk\n",
|
||||
mode->clock * 1000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void sti_hdmi_bridge_nope(struct drm_bridge *bridge)
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
static void sti_hdmi_brigde_destroy(struct drm_bridge *bridge)
|
||||
{
|
||||
drm_bridge_cleanup(bridge);
|
||||
kfree(bridge);
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs sti_hdmi_bridge_funcs = {
|
||||
.pre_enable = sti_hdmi_pre_enable,
|
||||
.enable = sti_hdmi_bridge_nope,
|
||||
.disable = sti_hdmi_disable,
|
||||
.post_disable = sti_hdmi_bridge_nope,
|
||||
.mode_set = sti_hdmi_set_mode,
|
||||
.destroy = sti_hdmi_brigde_destroy,
|
||||
};
|
||||
|
||||
static int sti_hdmi_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct i2c_adapter *i2c_adap;
|
||||
struct edid *edid;
|
||||
int count;
|
||||
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
i2c_adap = i2c_get_adapter(1);
|
||||
if (!i2c_adap)
|
||||
goto fail;
|
||||
|
||||
edid = drm_get_edid(connector, i2c_adap);
|
||||
if (!edid)
|
||||
goto fail;
|
||||
|
||||
count = drm_add_edid_modes(connector, edid);
|
||||
drm_mode_connector_update_edid_property(connector, edid);
|
||||
|
||||
kfree(edid);
|
||||
return count;
|
||||
|
||||
fail:
|
||||
DRM_ERROR("Can not read HDMI EDID\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CLK_TOLERANCE_HZ 50
|
||||
|
||||
static int sti_hdmi_connector_mode_valid(struct drm_connector *connector,
|
||||
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_hdmi_connector *hdmi_connector
|
||||
= to_sti_hdmi_connector(connector);
|
||||
struct sti_hdmi *hdmi = hdmi_connector->hdmi;
|
||||
|
||||
|
||||
result = clk_round_rate(hdmi->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("hdmi pixclk=%d not supported\n", target);
|
||||
return MODE_BAD;
|
||||
}
|
||||
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
struct drm_encoder *sti_hdmi_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
struct sti_hdmi_connector *hdmi_connector
|
||||
= to_sti_hdmi_connector(connector);
|
||||
|
||||
/* Best encoder is the one associated during connector creation */
|
||||
return hdmi_connector->encoder;
|
||||
}
|
||||
|
||||
static struct drm_connector_helper_funcs sti_hdmi_connector_helper_funcs = {
|
||||
.get_modes = sti_hdmi_connector_get_modes,
|
||||
.mode_valid = sti_hdmi_connector_mode_valid,
|
||||
.best_encoder = sti_hdmi_best_encoder,
|
||||
};
|
||||
|
||||
/* get detection status of display device */
|
||||
static enum drm_connector_status
|
||||
sti_hdmi_connector_detect(struct drm_connector *connector, bool force)
|
||||
{
|
||||
struct sti_hdmi_connector *hdmi_connector
|
||||
= to_sti_hdmi_connector(connector);
|
||||
struct sti_hdmi *hdmi = hdmi_connector->hdmi;
|
||||
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
if (hdmi->hpd) {
|
||||
DRM_DEBUG_DRIVER("hdmi cable connected\n");
|
||||
return connector_status_connected;
|
||||
}
|
||||
|
||||
DRM_DEBUG_DRIVER("hdmi cable disconnected\n");
|
||||
return connector_status_disconnected;
|
||||
}
|
||||
|
||||
static void sti_hdmi_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct sti_hdmi_connector *hdmi_connector
|
||||
= to_sti_hdmi_connector(connector);
|
||||
|
||||
drm_connector_unregister(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
kfree(hdmi_connector);
|
||||
}
|
||||
|
||||
static struct drm_connector_funcs sti_hdmi_connector_funcs = {
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.detect = sti_hdmi_connector_detect,
|
||||
.destroy = sti_hdmi_connector_destroy,
|
||||
};
|
||||
|
||||
static struct drm_encoder *sti_hdmi_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_TMDS)
|
||||
return encoder;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int sti_hdmi_bind(struct device *dev, struct device *master, void *data)
|
||||
{
|
||||
struct sti_hdmi *hdmi = dev_get_drvdata(dev);
|
||||
struct drm_device *drm_dev = data;
|
||||
struct drm_encoder *encoder;
|
||||
struct sti_hdmi_connector *connector;
|
||||
struct drm_connector *drm_connector;
|
||||
struct drm_bridge *bridge;
|
||||
struct i2c_adapter *i2c_adap;
|
||||
int err;
|
||||
|
||||
i2c_adap = i2c_get_adapter(1);
|
||||
if (!i2c_adap)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
/* Set the drm device handle */
|
||||
hdmi->drm_dev = drm_dev;
|
||||
|
||||
encoder = sti_hdmi_find_encoder(drm_dev);
|
||||
if (!encoder)
|
||||
return -ENOMEM;
|
||||
|
||||
connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL);
|
||||
if (!connector)
|
||||
return -ENOMEM;
|
||||
|
||||
connector->hdmi = hdmi;
|
||||
|
||||
bridge = devm_kzalloc(dev, sizeof(*bridge), GFP_KERNEL);
|
||||
if (!bridge)
|
||||
return -ENOMEM;
|
||||
|
||||
bridge->driver_private = hdmi;
|
||||
drm_bridge_init(drm_dev, bridge, &sti_hdmi_bridge_funcs);
|
||||
|
||||
encoder->bridge = bridge;
|
||||
connector->encoder = encoder;
|
||||
|
||||
drm_connector = (struct drm_connector *)connector;
|
||||
|
||||
drm_connector->polled = DRM_CONNECTOR_POLL_HPD;
|
||||
|
||||
drm_connector_init(drm_dev, drm_connector,
|
||||
&sti_hdmi_connector_funcs, DRM_MODE_CONNECTOR_HDMIA);
|
||||
drm_connector_helper_add(drm_connector,
|
||||
&sti_hdmi_connector_helper_funcs);
|
||||
|
||||
err = drm_connector_register(drm_connector);
|
||||
if (err)
|
||||
goto err_connector;
|
||||
|
||||
err = drm_mode_connector_attach_encoder(drm_connector, encoder);
|
||||
if (err) {
|
||||
DRM_ERROR("Failed to attach a connector to a encoder\n");
|
||||
goto err_sysfs;
|
||||
}
|
||||
|
||||
/* Enable default interrupts */
|
||||
hdmi_write(hdmi, HDMI_DEFAULT_INT, HDMI_INT_EN);
|
||||
|
||||
return 0;
|
||||
|
||||
err_sysfs:
|
||||
drm_connector_unregister(drm_connector);
|
||||
err_connector:
|
||||
drm_bridge_cleanup(bridge);
|
||||
drm_connector_cleanup(drm_connector);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void sti_hdmi_unbind(struct device *dev,
|
||||
struct device *master, void *data)
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
static const struct component_ops sti_hdmi_ops = {
|
||||
.bind = sti_hdmi_bind,
|
||||
.unbind = sti_hdmi_unbind,
|
||||
};
|
||||
|
||||
static const struct of_device_id hdmi_of_match[] = {
|
||||
{
|
||||
.compatible = "st,stih416-hdmi",
|
||||
.data = &tx3g0c55phy_ops,
|
||||
}, {
|
||||
.compatible = "st,stih407-hdmi",
|
||||
.data = &tx3g4c28phy_ops,
|
||||
}, {
|
||||
/* end node */
|
||||
}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, hdmi_of_match);
|
||||
|
||||
static int sti_hdmi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct sti_hdmi *hdmi;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
DRM_INFO("%s\n", __func__);
|
||||
|
||||
hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
|
||||
if (!hdmi)
|
||||
return -ENOMEM;
|
||||
|
||||
hdmi->dev = pdev->dev;
|
||||
|
||||
/* Get resources */
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hdmi-reg");
|
||||
if (!res) {
|
||||
DRM_ERROR("Invalid hdmi resource\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
hdmi->regs = devm_ioremap_nocache(dev, res->start, resource_size(res));
|
||||
if (!hdmi->regs)
|
||||
return -ENOMEM;
|
||||
|
||||
if (of_device_is_compatible(np, "st,stih416-hdmi")) {
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||
"syscfg");
|
||||
if (!res) {
|
||||
DRM_ERROR("Invalid syscfg resource\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
hdmi->syscfg = devm_ioremap_nocache(dev, res->start,
|
||||
resource_size(res));
|
||||
if (!hdmi->syscfg)
|
||||
return -ENOMEM;
|
||||
|
||||
}
|
||||
|
||||
hdmi->phy_ops = (struct hdmi_phy_ops *)
|
||||
of_match_node(hdmi_of_match, np)->data;
|
||||
|
||||
/* Get clock resources */
|
||||
hdmi->clk_pix = devm_clk_get(dev, "pix");
|
||||
if (IS_ERR(hdmi->clk_pix)) {
|
||||
DRM_ERROR("Cannot get hdmi_pix clock\n");
|
||||
return PTR_ERR(hdmi->clk_pix);
|
||||
}
|
||||
|
||||
hdmi->clk_tmds = devm_clk_get(dev, "tmds");
|
||||
if (IS_ERR(hdmi->clk_tmds)) {
|
||||
DRM_ERROR("Cannot get hdmi_tmds clock\n");
|
||||
return PTR_ERR(hdmi->clk_tmds);
|
||||
}
|
||||
|
||||
hdmi->clk_phy = devm_clk_get(dev, "phy");
|
||||
if (IS_ERR(hdmi->clk_phy)) {
|
||||
DRM_ERROR("Cannot get hdmi_phy clock\n");
|
||||
return PTR_ERR(hdmi->clk_phy);
|
||||
}
|
||||
|
||||
hdmi->clk_audio = devm_clk_get(dev, "audio");
|
||||
if (IS_ERR(hdmi->clk_audio)) {
|
||||
DRM_ERROR("Cannot get hdmi_audio clock\n");
|
||||
return PTR_ERR(hdmi->clk_audio);
|
||||
}
|
||||
|
||||
hdmi->hpd_gpio = of_get_named_gpio(np, "hdmi,hpd-gpio", 0);
|
||||
if (hdmi->hpd_gpio < 0) {
|
||||
DRM_ERROR("Failed to get hdmi hpd-gpio\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
hdmi->hpd = gpio_get_value(hdmi->hpd_gpio);
|
||||
|
||||
init_waitqueue_head(&hdmi->wait_event);
|
||||
|
||||
hdmi->irq = platform_get_irq_byname(pdev, "irq");
|
||||
|
||||
ret = devm_request_threaded_irq(dev, hdmi->irq, hdmi_irq,
|
||||
hdmi_irq_thread, IRQF_ONESHOT, dev_name(dev), hdmi);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to register HDMI interrupt\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
hdmi->reset = devm_reset_control_get(dev, "hdmi");
|
||||
/* Take hdmi out of reset */
|
||||
if (!IS_ERR(hdmi->reset))
|
||||
reset_control_deassert(hdmi->reset);
|
||||
|
||||
platform_set_drvdata(pdev, hdmi);
|
||||
|
||||
return component_add(&pdev->dev, &sti_hdmi_ops);
|
||||
}
|
||||
|
||||
static int sti_hdmi_remove(struct platform_device *pdev)
|
||||
{
|
||||
component_del(&pdev->dev, &sti_hdmi_ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct platform_driver sti_hdmi_driver = {
|
||||
.driver = {
|
||||
.name = "sti-hdmi",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = hdmi_of_match,
|
||||
},
|
||||
.probe = sti_hdmi_probe,
|
||||
.remove = sti_hdmi_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(sti_hdmi_driver);
|
||||
|
||||
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
88
drivers/gpu/drm/sti/sti_hdmi.h
Normal file
88
drivers/gpu/drm/sti/sti_hdmi.h
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#ifndef _STI_HDMI_H_
|
||||
#define _STI_HDMI_H_
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
|
||||
#define HDMI_STA 0x0010
|
||||
#define HDMI_STA_DLL_LCK BIT(5)
|
||||
|
||||
struct sti_hdmi;
|
||||
|
||||
struct hdmi_phy_ops {
|
||||
bool (*start)(struct sti_hdmi *hdmi);
|
||||
void (*stop)(struct sti_hdmi *hdmi);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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_gpio: hdmi hot plug detect gpio number
|
||||
* @hpd: hot plug detect status
|
||||
* @wait_event: wait event
|
||||
* @event_received: wait event status
|
||||
* @reset: reset control of the hdmi phy
|
||||
*/
|
||||
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;
|
||||
int hpd_gpio;
|
||||
bool hpd;
|
||||
wait_queue_head_t wait_event;
|
||||
bool event_received;
|
||||
struct reset_control *reset;
|
||||
};
|
||||
|
||||
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
|
||||
336
drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c
Normal file
336
drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include "sti_hdmi_tx3g0c55phy.h"
|
||||
|
||||
#define HDMI_SRZ_PLL_CFG 0x0504
|
||||
#define HDMI_SRZ_TAP_1 0x0508
|
||||
#define HDMI_SRZ_TAP_2 0x050C
|
||||
#define HDMI_SRZ_TAP_3 0x0510
|
||||
#define HDMI_SRZ_CTRL 0x0514
|
||||
|
||||
#define HDMI_SRZ_PLL_CFG_POWER_DOWN BIT(0)
|
||||
#define HDMI_SRZ_PLL_CFG_VCOR_SHIFT 1
|
||||
#define HDMI_SRZ_PLL_CFG_VCOR_425MHZ 0
|
||||
#define HDMI_SRZ_PLL_CFG_VCOR_850MHZ 1
|
||||
#define HDMI_SRZ_PLL_CFG_VCOR_1700MHZ 2
|
||||
#define HDMI_SRZ_PLL_CFG_VCOR_3000MHZ 3
|
||||
#define HDMI_SRZ_PLL_CFG_VCOR_MASK 3
|
||||
#define HDMI_SRZ_PLL_CFG_VCOR(x) (x << HDMI_SRZ_PLL_CFG_VCOR_SHIFT)
|
||||
#define HDMI_SRZ_PLL_CFG_NDIV_SHIFT 8
|
||||
#define HDMI_SRZ_PLL_CFG_NDIV_MASK (0x1F << HDMI_SRZ_PLL_CFG_NDIV_SHIFT)
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_SHIFT 16
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_13_5_MHZ 0x1
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_25_2_MHZ 0x4
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_27_MHZ 0x5
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_33_75_MHZ 0x6
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_40_5_MHZ 0x7
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_54_MHZ 0x8
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_67_5_MHZ 0x9
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_74_25_MHZ 0xA
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_81_MHZ 0xB
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_82_5_MHZ 0xC
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_108_MHZ 0xD
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_148_5_MHZ 0xE
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_165_MHZ 0xF
|
||||
#define HDMI_SRZ_PLL_CFG_MODE_MASK 0xF
|
||||
#define HDMI_SRZ_PLL_CFG_MODE(x) (x << HDMI_SRZ_PLL_CFG_MODE_SHIFT)
|
||||
|
||||
#define HDMI_SRZ_CTRL_POWER_DOWN (1 << 0)
|
||||
#define HDMI_SRZ_CTRL_EXTERNAL_DATA_EN (1 << 1)
|
||||
|
||||
/* sysconf registers */
|
||||
#define HDMI_REJECTION_PLL_CONFIGURATION 0x0858 /* SYSTEM_CONFIG2534 */
|
||||
#define HDMI_REJECTION_PLL_STATUS 0x0948 /* SYSTEM_CONFIG2594 */
|
||||
|
||||
#define REJECTION_PLL_HDMI_ENABLE_SHIFT 0
|
||||
#define REJECTION_PLL_HDMI_ENABLE_MASK (0x1 << REJECTION_PLL_HDMI_ENABLE_SHIFT)
|
||||
#define REJECTION_PLL_HDMI_PDIV_SHIFT 24
|
||||
#define REJECTION_PLL_HDMI_PDIV_MASK (0x7 << REJECTION_PLL_HDMI_PDIV_SHIFT)
|
||||
#define REJECTION_PLL_HDMI_NDIV_SHIFT 16
|
||||
#define REJECTION_PLL_HDMI_NDIV_MASK (0xFF << REJECTION_PLL_HDMI_NDIV_SHIFT)
|
||||
#define REJECTION_PLL_HDMI_MDIV_SHIFT 8
|
||||
#define REJECTION_PLL_HDMI_MDIV_MASK (0xFF << REJECTION_PLL_HDMI_MDIV_SHIFT)
|
||||
|
||||
#define REJECTION_PLL_HDMI_REJ_PLL_LOCK BIT(0)
|
||||
|
||||
#define HDMI_TIMEOUT_PLL_LOCK 50 /*milliseconds */
|
||||
|
||||
/**
|
||||
* pll mode 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. The array
|
||||
* should be terminated with an entry that has all fields set to zero.
|
||||
*
|
||||
* @min: Lower bound of TMDS clock frequency this entry applies to
|
||||
* @max: Upper bound of TMDS clock frequency this entry applies to
|
||||
* @mode: SoC specific register configuration
|
||||
*/
|
||||
struct pllmode {
|
||||
u32 min;
|
||||
u32 max;
|
||||
u32 mode;
|
||||
};
|
||||
|
||||
#define NB_PLL_MODE 7
|
||||
static struct pllmode pllmodes[NB_PLL_MODE] = {
|
||||
{13500000, 13513500, HDMI_SRZ_PLL_CFG_MODE_13_5_MHZ},
|
||||
{25174800, 25200000, HDMI_SRZ_PLL_CFG_MODE_25_2_MHZ},
|
||||
{27000000, 27027000, HDMI_SRZ_PLL_CFG_MODE_27_MHZ},
|
||||
{54000000, 54054000, HDMI_SRZ_PLL_CFG_MODE_54_MHZ},
|
||||
{72000000, 74250000, HDMI_SRZ_PLL_CFG_MODE_74_25_MHZ},
|
||||
{108000000, 108108000, HDMI_SRZ_PLL_CFG_MODE_108_MHZ},
|
||||
{148351648, 297000000, HDMI_SRZ_PLL_CFG_MODE_148_5_MHZ}
|
||||
};
|
||||
|
||||
#define NB_HDMI_PHY_CONFIG 5
|
||||
static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = {
|
||||
{0, 40000000, {0x00101010, 0x00101010, 0x00101010, 0x02} },
|
||||
{40000000, 140000000, {0x00111111, 0x00111111, 0x00111111, 0x02} },
|
||||
{140000000, 160000000, {0x00131313, 0x00101010, 0x00101010, 0x02} },
|
||||
{160000000, 250000000, {0x00131313, 0x00111111, 0x00111111, 0x03FE} },
|
||||
{250000000, 300000000, {0x00151515, 0x00101010, 0x00101010, 0x03FE} },
|
||||
};
|
||||
|
||||
#define PLL_CHANGE_DELAY 1 /* ms */
|
||||
|
||||
/**
|
||||
* Disable the pll rejection
|
||||
*
|
||||
* @hdmi: pointer on the hdmi internal structure
|
||||
*
|
||||
* return true if the pll has been disabled
|
||||
*/
|
||||
static bool disable_pll_rejection(struct sti_hdmi *hdmi)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION);
|
||||
val &= ~REJECTION_PLL_HDMI_ENABLE_MASK;
|
||||
writel(val, hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION);
|
||||
|
||||
msleep(PLL_CHANGE_DELAY);
|
||||
val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS);
|
||||
|
||||
return !(val & REJECTION_PLL_HDMI_REJ_PLL_LOCK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the old BCH/rejection PLL is now reused to provide the CLKPXPLL
|
||||
* clock input to the new PHY PLL that generates the serializer clock
|
||||
* (TMDS*10) and the TMDS clock which is now fed back into the HDMI
|
||||
* formatter instead of the TMDS clock line from ClockGenB.
|
||||
*
|
||||
* @hdmi: pointer on the hdmi internal structure
|
||||
*
|
||||
* return true if pll has been correctly set
|
||||
*/
|
||||
static bool enable_pll_rejection(struct sti_hdmi *hdmi)
|
||||
{
|
||||
unsigned int inputclock;
|
||||
u32 mdiv, ndiv, pdiv, val;
|
||||
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
if (!disable_pll_rejection(hdmi))
|
||||
return false;
|
||||
|
||||
inputclock = hdmi->mode.clock * 1000;
|
||||
|
||||
DRM_DEBUG_DRIVER("hdmi rejection pll input clock = %dHz\n", inputclock);
|
||||
|
||||
|
||||
/* Power up the HDMI rejection PLL
|
||||
* Note: On this SoC (stiH416) we are forced to have the input clock
|
||||
* be equal to the HDMI pixel clock.
|
||||
*
|
||||
* The values here have been suggested by validation however they are
|
||||
* still provisional and subject to change.
|
||||
*
|
||||
* PLLout = (Fin*Mdiv) / ((2 * Ndiv) / 2^Pdiv)
|
||||
*/
|
||||
if (inputclock < 50000000) {
|
||||
/*
|
||||
* For slower clocks we need to multiply more to keep the
|
||||
* internal VCO frequency within the physical specification
|
||||
* of the PLL.
|
||||
*/
|
||||
pdiv = 4;
|
||||
ndiv = 240;
|
||||
mdiv = 30;
|
||||
} else {
|
||||
pdiv = 2;
|
||||
ndiv = 60;
|
||||
mdiv = 30;
|
||||
}
|
||||
|
||||
val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION);
|
||||
|
||||
val &= ~(REJECTION_PLL_HDMI_PDIV_MASK |
|
||||
REJECTION_PLL_HDMI_NDIV_MASK |
|
||||
REJECTION_PLL_HDMI_MDIV_MASK |
|
||||
REJECTION_PLL_HDMI_ENABLE_MASK);
|
||||
|
||||
val |= (pdiv << REJECTION_PLL_HDMI_PDIV_SHIFT) |
|
||||
(ndiv << REJECTION_PLL_HDMI_NDIV_SHIFT) |
|
||||
(mdiv << REJECTION_PLL_HDMI_MDIV_SHIFT) |
|
||||
(0x1 << REJECTION_PLL_HDMI_ENABLE_SHIFT);
|
||||
|
||||
writel(val, hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION);
|
||||
|
||||
msleep(PLL_CHANGE_DELAY);
|
||||
val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS);
|
||||
|
||||
return (val & REJECTION_PLL_HDMI_REJ_PLL_LOCK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start hdmi phy macro cell tx3g0c55
|
||||
*
|
||||
* @hdmi: pointer on the hdmi internal structure
|
||||
*
|
||||
* Return false if an error occur
|
||||
*/
|
||||
static bool sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi)
|
||||
{
|
||||
u32 ckpxpll = hdmi->mode.clock * 1000;
|
||||
u32 val, tmdsck, freqvco, pllctrl = 0;
|
||||
unsigned int i;
|
||||
|
||||
if (!enable_pll_rejection(hdmi))
|
||||
return false;
|
||||
|
||||
DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll);
|
||||
|
||||
/* Assuming no pixel repetition and 24bits color */
|
||||
tmdsck = ckpxpll;
|
||||
pllctrl = 2 << HDMI_SRZ_PLL_CFG_NDIV_SHIFT;
|
||||
|
||||
/*
|
||||
* Setup the PLL mode parameter based on the ckpxpll. If we haven't got
|
||||
* a clock frequency supported by one of the specific PLL modes then we
|
||||
* will end up using the generic mode (0) which only supports a 10x
|
||||
* multiplier, hence only 24bit color.
|
||||
*/
|
||||
for (i = 0; i < NB_PLL_MODE; i++) {
|
||||
if (ckpxpll >= pllmodes[i].min && ckpxpll <= pllmodes[i].max)
|
||||
pllctrl |= HDMI_SRZ_PLL_CFG_MODE(pllmodes[i].mode);
|
||||
}
|
||||
|
||||
freqvco = tmdsck * 10;
|
||||
if (freqvco <= 425000000UL)
|
||||
pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_425MHZ);
|
||||
else if (freqvco <= 850000000UL)
|
||||
pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_850MHZ);
|
||||
else if (freqvco <= 1700000000UL)
|
||||
pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_1700MHZ);
|
||||
else if (freqvco <= 2970000000UL)
|
||||
pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_3000MHZ);
|
||||
else {
|
||||
DRM_ERROR("PHY serializer clock out of range\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure and power up the PHY PLL
|
||||
*/
|
||||
hdmi->event_received = false;
|
||||
DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl);
|
||||
hdmi_write(hdmi, pllctrl, 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");
|
||||
|
||||
/*
|
||||
* 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_write(hdmi, val, HDMI_SRZ_TAP_1);
|
||||
val = hdmiphy_config[i].config[1];
|
||||
hdmi_write(hdmi, val, HDMI_SRZ_TAP_2);
|
||||
val = hdmiphy_config[i].config[2];
|
||||
hdmi_write(hdmi, val, HDMI_SRZ_TAP_3);
|
||||
val = hdmiphy_config[i].config[3];
|
||||
val |= HDMI_SRZ_CTRL_EXTERNAL_DATA_EN;
|
||||
val &= ~HDMI_SRZ_CTRL_POWER_DOWN;
|
||||
hdmi_write(hdmi, val, HDMI_SRZ_CTRL);
|
||||
|
||||
DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x 0x%x\n",
|
||||
hdmiphy_config[i].config[0],
|
||||
hdmiphy_config[i].config[1],
|
||||
hdmiphy_config[i].config[2],
|
||||
hdmiphy_config[i].config[3]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Default, power up the serializer with no pre-emphasis or source
|
||||
* termination.
|
||||
*/
|
||||
hdmi_write(hdmi, 0x0, HDMI_SRZ_TAP_1);
|
||||
hdmi_write(hdmi, 0x0, HDMI_SRZ_TAP_2);
|
||||
hdmi_write(hdmi, 0x0, HDMI_SRZ_TAP_3);
|
||||
hdmi_write(hdmi, HDMI_SRZ_CTRL_EXTERNAL_DATA_EN, HDMI_SRZ_CTRL);
|
||||
|
||||
return true;
|
||||
|
||||
err:
|
||||
disable_pll_rejection(hdmi);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop hdmi phy macro cell tx3g0c55
|
||||
*
|
||||
* @hdmi: pointer on the hdmi internal structure
|
||||
*/
|
||||
static void sti_hdmi_tx3g0c55phy_stop(struct sti_hdmi *hdmi)
|
||||
{
|
||||
DRM_DEBUG_DRIVER("\n");
|
||||
|
||||
hdmi->event_received = false;
|
||||
|
||||
hdmi_write(hdmi, HDMI_SRZ_CTRL_POWER_DOWN, HDMI_SRZ_CTRL);
|
||||
hdmi_write(hdmi, HDMI_SRZ_PLL_CFG_POWER_DOWN, 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");
|
||||
|
||||
disable_pll_rejection(hdmi);
|
||||
}
|
||||
|
||||
struct hdmi_phy_ops tx3g0c55phy_ops = {
|
||||
.start = sti_hdmi_tx3g0c55phy_start,
|
||||
.stop = sti_hdmi_tx3g0c55phy_stop,
|
||||
};
|
||||
14
drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.h
Normal file
14
drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#ifndef _STI_HDMI_TX3G0C55PHY_H_
|
||||
#define _STI_HDMI_TX3G0C55PHY_H_
|
||||
|
||||
#include "sti_hdmi.h"
|
||||
|
||||
extern struct hdmi_phy_ops tx3g0c55phy_ops;
|
||||
|
||||
#endif
|
||||
211
drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c
Normal file
211
drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#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} },
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 > 340000000) {
|
||||
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 > 165000000)
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
14
drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.h
Normal file
14
drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#ifndef _STI_HDMI_TX3G4C28PHY_H_
|
||||
#define _STI_HDMI_TX3G4C28PHY_H_
|
||||
|
||||
#include "sti_hdmi.h"
|
||||
|
||||
extern struct hdmi_phy_ops tx3g4c28phy_ops;
|
||||
|
||||
#endif
|
||||
197
drivers/gpu/drm/sti/sti_layer.c
Normal file
197
drivers/gpu/drm/sti/sti_layer.c
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
|
||||
* Fabien Dessenne <fabien.dessenne@st.com>
|
||||
* for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_gem_cma_helper.h>
|
||||
#include <drm/drm_fb_cma_helper.h>
|
||||
|
||||
#include "sti_compositor.h"
|
||||
#include "sti_gdp.h"
|
||||
#include "sti_layer.h"
|
||||
#include "sti_vid.h"
|
||||
|
||||
const char *sti_layer_to_str(struct sti_layer *layer)
|
||||
{
|
||||
switch (layer->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_VID_0:
|
||||
return "VID0";
|
||||
case STI_VID_1:
|
||||
return "VID1";
|
||||
case STI_CURSOR:
|
||||
return "CURSOR";
|
||||
default:
|
||||
return "<UNKNOWN LAYER>";
|
||||
}
|
||||
}
|
||||
|
||||
struct sti_layer *sti_layer_create(struct device *dev, int desc,
|
||||
void __iomem *baseaddr)
|
||||
{
|
||||
|
||||
struct sti_layer *layer = NULL;
|
||||
|
||||
switch (desc & STI_LAYER_TYPE_MASK) {
|
||||
case STI_GDP:
|
||||
layer = sti_gdp_create(dev, desc);
|
||||
break;
|
||||
case STI_VID:
|
||||
layer = sti_vid_create(dev);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!layer) {
|
||||
DRM_ERROR("Failed to create layer\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
layer->desc = desc;
|
||||
layer->dev = dev;
|
||||
layer->regs = baseaddr;
|
||||
|
||||
layer->ops->init(layer);
|
||||
|
||||
DRM_DEBUG_DRIVER("%s created\n", sti_layer_to_str(layer));
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
int sti_layer_prepare(struct sti_layer *layer, struct drm_framebuffer *fb,
|
||||
struct drm_display_mode *mode, int mixer_id,
|
||||
int dest_x, int dest_y, int dest_w, int dest_h,
|
||||
int src_x, int src_y, int src_w, int src_h)
|
||||
{
|
||||
int ret;
|
||||
unsigned int i;
|
||||
struct drm_gem_cma_object *cma_obj;
|
||||
|
||||
if (!layer || !fb || !mode) {
|
||||
DRM_ERROR("Null fb, layer or mode\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
|
||||
if (!cma_obj) {
|
||||
DRM_ERROR("Can't get CMA GEM object for fb\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
layer->fb = fb;
|
||||
layer->mode = mode;
|
||||
layer->mixer_id = mixer_id;
|
||||
layer->dst_x = dest_x;
|
||||
layer->dst_y = dest_y;
|
||||
layer->dst_w = clamp_val(dest_w, 0, mode->crtc_hdisplay - dest_x);
|
||||
layer->dst_h = clamp_val(dest_h, 0, mode->crtc_vdisplay - dest_y);
|
||||
layer->src_x = src_x;
|
||||
layer->src_y = src_y;
|
||||
layer->src_w = src_w;
|
||||
layer->src_h = src_h;
|
||||
layer->format = fb->pixel_format;
|
||||
layer->paddr = cma_obj->paddr;
|
||||
for (i = 0; i < 4; i++) {
|
||||
layer->pitches[i] = fb->pitches[i];
|
||||
layer->offsets[i] = fb->offsets[i];
|
||||
}
|
||||
|
||||
DRM_DEBUG_DRIVER("%s is associated with mixer_id %d\n",
|
||||
sti_layer_to_str(layer),
|
||||
layer->mixer_id);
|
||||
DRM_DEBUG_DRIVER("%s dst=(%dx%d)@(%d,%d) - src=(%dx%d)@(%d,%d)\n",
|
||||
sti_layer_to_str(layer),
|
||||
layer->dst_w, layer->dst_h, layer->dst_x, layer->dst_y,
|
||||
layer->src_w, layer->src_h, layer->src_x,
|
||||
layer->src_y);
|
||||
|
||||
DRM_DEBUG_DRIVER("drm FB:%d format:%.4s phys@:0x%lx\n", fb->base.id,
|
||||
(char *)&layer->format, (unsigned long)layer->paddr);
|
||||
|
||||
if (!layer->ops->prepare)
|
||||
goto err_no_prepare;
|
||||
|
||||
ret = layer->ops->prepare(layer, !layer->enabled);
|
||||
if (!ret)
|
||||
layer->enabled = true;
|
||||
|
||||
return ret;
|
||||
|
||||
err_no_prepare:
|
||||
DRM_ERROR("Cannot prepare\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int sti_layer_commit(struct sti_layer *layer)
|
||||
{
|
||||
if (!layer)
|
||||
return 1;
|
||||
|
||||
if (!layer->ops->commit)
|
||||
goto err_no_commit;
|
||||
|
||||
return layer->ops->commit(layer);
|
||||
|
||||
err_no_commit:
|
||||
DRM_ERROR("Cannot commit\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int sti_layer_disable(struct sti_layer *layer)
|
||||
{
|
||||
int ret;
|
||||
|
||||
DRM_DEBUG_DRIVER("%s\n", sti_layer_to_str(layer));
|
||||
if (!layer)
|
||||
return 1;
|
||||
|
||||
if (!layer->enabled)
|
||||
return 0;
|
||||
|
||||
if (!layer->ops->disable)
|
||||
goto err_no_disable;
|
||||
|
||||
ret = layer->ops->disable(layer);
|
||||
if (!ret)
|
||||
layer->enabled = false;
|
||||
else
|
||||
DRM_ERROR("Disable failed\n");
|
||||
|
||||
return ret;
|
||||
|
||||
err_no_disable:
|
||||
DRM_ERROR("Cannot disable\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const uint32_t *sti_layer_get_formats(struct sti_layer *layer)
|
||||
{
|
||||
if (!layer)
|
||||
return NULL;
|
||||
|
||||
if (!layer->ops->get_formats)
|
||||
return NULL;
|
||||
|
||||
return layer->ops->get_formats(layer);
|
||||
}
|
||||
|
||||
unsigned int sti_layer_get_nb_formats(struct sti_layer *layer)
|
||||
{
|
||||
if (!layer)
|
||||
return 0;
|
||||
|
||||
if (!layer->ops->get_nb_formats)
|
||||
return 0;
|
||||
|
||||
return layer->ops->get_nb_formats(layer);
|
||||
}
|
||||
123
drivers/gpu/drm/sti/sti_layer.h
Normal file
123
drivers/gpu/drm/sti/sti_layer.h
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
|
||||
* Fabien Dessenne <fabien.dessenne@st.com>
|
||||
* for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#ifndef _STI_LAYER_H_
|
||||
#define _STI_LAYER_H_
|
||||
|
||||
#include <drm/drmP.h>
|
||||
|
||||
#define to_sti_layer(x) container_of(x, struct sti_layer, plane)
|
||||
|
||||
#define STI_LAYER_TYPE_SHIFT 8
|
||||
#define STI_LAYER_TYPE_MASK (~((1<<STI_LAYER_TYPE_SHIFT)-1))
|
||||
|
||||
struct sti_layer;
|
||||
|
||||
enum sti_layer_type {
|
||||
STI_GDP = 1 << STI_LAYER_TYPE_SHIFT,
|
||||
STI_VID = 2 << STI_LAYER_TYPE_SHIFT,
|
||||
STI_CUR = 3 << STI_LAYER_TYPE_SHIFT,
|
||||
STI_BCK = 4 << STI_LAYER_TYPE_SHIFT
|
||||
};
|
||||
|
||||
enum sti_layer_id_of_type {
|
||||
STI_ID_0 = 0,
|
||||
STI_ID_1 = 1,
|
||||
STI_ID_2 = 2,
|
||||
STI_ID_3 = 3
|
||||
};
|
||||
|
||||
enum sti_layer_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_VID_0 = STI_VID | STI_ID_0,
|
||||
STI_VID_1 = STI_VID | STI_ID_1,
|
||||
STI_CURSOR = STI_CUR,
|
||||
STI_BACK = STI_BCK
|
||||
};
|
||||
|
||||
/**
|
||||
* STI layer functions structure
|
||||
*
|
||||
* @get_formats: get layer supported formats
|
||||
* @get_nb_formats: get number of format supported
|
||||
* @init: initialize the layer
|
||||
* @prepare: prepare layer before rendering
|
||||
* @commit: set layer for rendering
|
||||
* @disable: disable layer
|
||||
*/
|
||||
struct sti_layer_funcs {
|
||||
const uint32_t* (*get_formats)(struct sti_layer *layer);
|
||||
unsigned int (*get_nb_formats)(struct sti_layer *layer);
|
||||
void (*init)(struct sti_layer *layer);
|
||||
int (*prepare)(struct sti_layer *layer, bool first_prepare);
|
||||
int (*commit)(struct sti_layer *layer);
|
||||
int (*disable)(struct sti_layer *layer);
|
||||
};
|
||||
|
||||
/**
|
||||
* STI layer structure
|
||||
*
|
||||
* @plane: drm plane it is bound to (if any)
|
||||
* @fb: drm fb it is bound to
|
||||
* @mode: display mode
|
||||
* @desc: layer type & id
|
||||
* @device: driver device
|
||||
* @regs: layer registers
|
||||
* @ops: layer functions
|
||||
* @zorder: layer z-order
|
||||
* @mixer_id: id of the mixer used to display the layer
|
||||
* @enabled: to know if the layer is active or not
|
||||
* @src_x src_y: coordinates of the input (fb) area
|
||||
* @src_w src_h: size of the input (fb) area
|
||||
* @dst_x dst_y: coordinates of the output (crtc) area
|
||||
* @dst_w dst_h: size of the output (crtc) area
|
||||
* @format: format
|
||||
* @pitches: pitch of 'planes' (eg: Y, U, V)
|
||||
* @offsets: offset of 'planes'
|
||||
* @paddr: physical address of the input buffer
|
||||
*/
|
||||
struct sti_layer {
|
||||
struct drm_plane plane;
|
||||
struct drm_framebuffer *fb;
|
||||
struct drm_display_mode *mode;
|
||||
enum sti_layer_desc desc;
|
||||
struct device *dev;
|
||||
void __iomem *regs;
|
||||
const struct sti_layer_funcs *ops;
|
||||
int zorder;
|
||||
int mixer_id;
|
||||
bool enabled;
|
||||
int src_x, src_y;
|
||||
int src_w, src_h;
|
||||
int dst_x, dst_y;
|
||||
int dst_w, dst_h;
|
||||
uint32_t format;
|
||||
unsigned int pitches[4];
|
||||
unsigned int offsets[4];
|
||||
dma_addr_t paddr;
|
||||
};
|
||||
|
||||
struct sti_layer *sti_layer_create(struct device *dev, int desc,
|
||||
void __iomem *baseaddr);
|
||||
int sti_layer_prepare(struct sti_layer *layer, struct drm_framebuffer *fb,
|
||||
struct drm_display_mode *mode,
|
||||
int mixer_id,
|
||||
int dest_x, int dest_y,
|
||||
int dest_w, int dest_h,
|
||||
int src_x, int src_y,
|
||||
int src_w, int src_h);
|
||||
int sti_layer_commit(struct sti_layer *layer);
|
||||
int sti_layer_disable(struct sti_layer *layer);
|
||||
const uint32_t *sti_layer_get_formats(struct sti_layer *layer);
|
||||
unsigned int sti_layer_get_nb_formats(struct sti_layer *layer);
|
||||
const char *sti_layer_to_str(struct sti_layer *layer);
|
||||
|
||||
#endif
|
||||
249
drivers/gpu/drm/sti/sti_mixer.c
Normal file
249
drivers/gpu/drm/sti/sti_mixer.c
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
|
||||
* Fabien Dessenne <fabien.dessenne@st.com>
|
||||
* for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include "sti_compositor.h"
|
||||
#include "sti_mixer.h"
|
||||
#include "sti_vtg.h"
|
||||
|
||||
/* Identity: G=Y , B=Cb , R=Cr */
|
||||
static const u32 mixerColorSpaceMatIdentity[] = {
|
||||
0x10000000, 0x00000000, 0x10000000, 0x00001000,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000
|
||||
};
|
||||
|
||||
/* 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)
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
u8 red, u8 green, u8 blue)
|
||||
{
|
||||
u32 val = (red << 16) | (green << 8) | blue;
|
||||
|
||||
sti_mixer_reg_write(mixer, GAM_MIXER_BKC, val);
|
||||
}
|
||||
|
||||
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_layer_depth(struct sti_mixer *mixer, struct sti_layer *layer)
|
||||
{
|
||||
int layer_id = 0, depth = layer->zorder;
|
||||
u32 mask, val;
|
||||
|
||||
if (depth >= GAM_MIXER_NB_DEPTH_LEVEL)
|
||||
return 1;
|
||||
|
||||
switch (layer->desc) {
|
||||
case STI_GDP_0:
|
||||
layer_id = GAM_DEPTH_GDP0_ID;
|
||||
break;
|
||||
case STI_GDP_1:
|
||||
layer_id = GAM_DEPTH_GDP1_ID;
|
||||
break;
|
||||
case STI_GDP_2:
|
||||
layer_id = GAM_DEPTH_GDP2_ID;
|
||||
break;
|
||||
case STI_GDP_3:
|
||||
layer_id = GAM_DEPTH_GDP3_ID;
|
||||
break;
|
||||
case STI_VID_0:
|
||||
layer_id = GAM_DEPTH_VID0_ID;
|
||||
break;
|
||||
case STI_VID_1:
|
||||
layer_id = GAM_DEPTH_VID1_ID;
|
||||
break;
|
||||
default:
|
||||
DRM_ERROR("Unknown layer %d\n", layer->desc);
|
||||
return 1;
|
||||
}
|
||||
mask = GAM_DEPTH_MASK_ID << (3 * depth);
|
||||
layer_id = layer_id << (3 * depth);
|
||||
|
||||
DRM_DEBUG_DRIVER("%s %s depth=%d\n", sti_mixer_to_str(mixer),
|
||||
sti_layer_to_str(layer), depth);
|
||||
dev_dbg(mixer->dev, "GAM_MIXER_CRB val 0x%x mask 0x%x\n",
|
||||
layer_id, mask);
|
||||
|
||||
val = sti_mixer_reg_read(mixer, GAM_MIXER_CRB);
|
||||
val &= ~mask;
|
||||
val |= layer_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, 0xFF, 0, 0);
|
||||
|
||||
sti_mixer_set_background_area(mixer, mode);
|
||||
sti_mixer_set_background_status(mixer, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 sti_mixer_get_layer_mask(struct sti_layer *layer)
|
||||
{
|
||||
switch (layer->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_VID_0:
|
||||
return GAM_CTL_VID0_MASK;
|
||||
case STI_VID_1:
|
||||
return GAM_CTL_VID1_MASK;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int sti_mixer_set_layer_status(struct sti_mixer *mixer,
|
||||
struct sti_layer *layer, bool status)
|
||||
{
|
||||
u32 mask, val;
|
||||
|
||||
DRM_DEBUG_DRIVER("%s %s %s\n", status ? "enable" : "disable",
|
||||
sti_mixer_to_str(mixer), sti_layer_to_str(layer));
|
||||
|
||||
mask = sti_mixer_get_layer_mask(layer);
|
||||
if (!mask) {
|
||||
DRM_ERROR("Can not 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;
|
||||
}
|
||||
|
||||
void sti_mixer_set_matrix(struct sti_mixer *mixer)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mixerColorSpaceMatIdentity); i++)
|
||||
sti_mixer_reg_write(mixer, GAM_MIXER_MX0 + (i * 4),
|
||||
mixerColorSpaceMatIdentity[i]);
|
||||
}
|
||||
|
||||
struct sti_mixer *sti_mixer_create(struct device *dev, int id,
|
||||
void __iomem *baseaddr)
|
||||
{
|
||||
struct sti_mixer *mixer = devm_kzalloc(dev, sizeof(*mixer), GFP_KERNEL);
|
||||
struct device_node *np = dev->of_node;
|
||||
|
||||
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;
|
||||
|
||||
if (of_device_is_compatible(np, "st,stih416-compositor"))
|
||||
sti_mixer_set_matrix(mixer);
|
||||
|
||||
DRM_DEBUG_DRIVER("%s created. Regs=%p\n",
|
||||
sti_mixer_to_str(mixer), mixer->regs);
|
||||
|
||||
return mixer;
|
||||
}
|
||||
54
drivers/gpu/drm/sti/sti_mixer.h
Normal file
54
drivers/gpu/drm/sti/sti_mixer.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
|
||||
* Fabien Dessenne <fabien.dessenne@st.com>
|
||||
* for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#ifndef _STI_MIXER_H_
|
||||
#define _STI_MIXER_H_
|
||||
|
||||
#include <drm/drmP.h>
|
||||
|
||||
#include "sti_layer.h"
|
||||
|
||||
#define to_sti_mixer(x) container_of(x, struct sti_mixer, drm_crtc)
|
||||
|
||||
/**
|
||||
* STI Mixer subdevice structure
|
||||
*
|
||||
* @dev: driver device
|
||||
* @regs: mixer registers
|
||||
* @id: id of the mixer
|
||||
* @drm_crtc: crtc object link to the mixer
|
||||
* @pending_event: set if a flip event is pending on crtc
|
||||
*/
|
||||
struct sti_mixer {
|
||||
struct device *dev;
|
||||
void __iomem *regs;
|
||||
int id;
|
||||
struct drm_crtc drm_crtc;
|
||||
struct drm_pending_vblank_event *pending_event;
|
||||
};
|
||||
|
||||
const char *sti_mixer_to_str(struct sti_mixer *mixer);
|
||||
|
||||
struct sti_mixer *sti_mixer_create(struct device *dev, int id,
|
||||
void __iomem *baseaddr);
|
||||
|
||||
int sti_mixer_set_layer_status(struct sti_mixer *mixer,
|
||||
struct sti_layer *layer, bool status);
|
||||
int sti_mixer_set_layer_depth(struct sti_mixer *mixer, struct sti_layer *layer);
|
||||
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);
|
||||
|
||||
/* depth in Cross-bar control = z order */
|
||||
#define GAM_MIXER_NB_DEPTH_LEVEL 7
|
||||
|
||||
#define STI_MIXER_MAIN 0
|
||||
#define STI_MIXER_AUX 1
|
||||
|
||||
#endif
|
||||
648
drivers/gpu/drm/sti/sti_tvout.c
Normal file
648
drivers/gpu/drm/sti/sti_tvout.c
Normal file
|
|
@ -0,0 +1,648 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Authors: Benjamin Gaignard <benjamin.gaignard@st.com>
|
||||
* Vincent Abriou <vincent.abriou@st.com>
|
||||
* for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_crtc_helper.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_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_MAIN_VTG_SET_1 0x01
|
||||
#define TVO_SYNC_MAIN_VTG_SET_2 0x02
|
||||
#define TVO_SYNC_MAIN_VTG_SET_3 0x03
|
||||
#define TVO_SYNC_MAIN_VTG_SET_4 0x04
|
||||
#define TVO_SYNC_MAIN_VTG_SET_5 0x05
|
||||
#define TVO_SYNC_MAIN_VTG_SET_6 0x06
|
||||
#define TVO_SYNC_AUX_VTG_SET_REF 0x10
|
||||
#define TVO_SYNC_AUX_VTG_SET_1 0x11
|
||||
#define TVO_SYNC_AUX_VTG_SET_2 0x12
|
||||
#define TVO_SYNC_AUX_VTG_SET_3 0x13
|
||||
#define TVO_SYNC_AUX_VTG_SET_4 0x14
|
||||
#define TVO_SYNC_AUX_VTG_SET_5 0x15
|
||||
#define TVO_SYNC_AUX_VTG_SET_6 0x16
|
||||
|
||||
#define TVO_SYNC_HD_DCS_SHIFT 8
|
||||
|
||||
#define ENCODER_MAIN_CRTC_MASK BIT(0)
|
||||
|
||||
/* 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 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the clipping mode of a VIP
|
||||
*
|
||||
* @tvout: tvout structure
|
||||
* @cr_r:
|
||||
* @y_g:
|
||||
* @cb_b:
|
||||
*/
|
||||
static void tvout_vip_set_color_order(struct sti_tvout *tvout,
|
||||
u32 cr_r, u32 y_g, u32 cb_b)
|
||||
{
|
||||
u32 val = tvout_read(tvout, TVO_VIP_HDMI);
|
||||
|
||||
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, TVO_VIP_HDMI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the clipping mode of a VIP
|
||||
*
|
||||
* @tvout: tvout structure
|
||||
* @range: clipping range
|
||||
*/
|
||||
static void tvout_vip_set_clip_mode(struct sti_tvout *tvout, u32 range)
|
||||
{
|
||||
u32 val = tvout_read(tvout, TVO_VIP_HDMI);
|
||||
|
||||
val &= ~(TVO_VIP_CLIP_MASK << TVO_VIP_CLIP_SHIFT);
|
||||
val |= range << TVO_VIP_CLIP_SHIFT;
|
||||
tvout_write(tvout, val, TVO_VIP_HDMI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rounded value of a VIP
|
||||
*
|
||||
* @tvout: tvout structure
|
||||
* @rnd: rounded val per component
|
||||
*/
|
||||
static void tvout_vip_set_rnd(struct sti_tvout *tvout, u32 rnd)
|
||||
{
|
||||
u32 val = tvout_read(tvout, TVO_VIP_HDMI);
|
||||
|
||||
val &= ~(TVO_VIP_RND_MASK << TVO_VIP_RND_SHIFT);
|
||||
val |= rnd << TVO_VIP_RND_SHIFT;
|
||||
tvout_write(tvout, val, TVO_VIP_HDMI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the VIP input
|
||||
*
|
||||
* @tvout: tvout structure
|
||||
* @sel_input: selected_input (main/aux + conv)
|
||||
*/
|
||||
static void tvout_vip_set_sel_input(struct sti_tvout *tvout,
|
||||
bool main_path,
|
||||
bool sel_input_logic_inverted,
|
||||
enum sti_tvout_video_out_type video_out)
|
||||
{
|
||||
u32 sel_input;
|
||||
u32 val = tvout_read(tvout, TVO_VIP_HDMI);
|
||||
|
||||
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 */
|
||||
if (sel_input_logic_inverted)
|
||||
sel_input = sel_input ^ TVO_VIP_SEL_INPUT_BYPASS_MASK;
|
||||
|
||||
val &= ~TVO_VIP_SEL_INPUT_MASK;
|
||||
val |= sel_input;
|
||||
tvout_write(tvout, val, TVO_VIP_HDMI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the input video signed or unsigned
|
||||
*
|
||||
* @tvout: tvout structure
|
||||
* @in_vid_signed: used video input format
|
||||
*/
|
||||
static void tvout_vip_set_in_vid_fmt(struct sti_tvout *tvout, u32 in_vid_fmt)
|
||||
{
|
||||
u32 val = tvout_read(tvout, TVO_VIP_HDMI);
|
||||
|
||||
val &= ~TVO_IN_FMT_SIGNED;
|
||||
val |= in_vid_fmt;
|
||||
tvout_write(tvout, val, TVO_MAIN_IN_VID_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
struct device_node *node = tvout->dev->of_node;
|
||||
bool sel_input_logic_inverted = false;
|
||||
|
||||
dev_dbg(tvout->dev, "%s\n", __func__);
|
||||
|
||||
if (main_path) {
|
||||
DRM_DEBUG_DRIVER("main vip for hdmi\n");
|
||||
/* select the input sync for hdmi = VTG set 1 */
|
||||
tvout_write(tvout, TVO_SYNC_MAIN_VTG_SET_1, TVO_HDMI_SYNC_SEL);
|
||||
} else {
|
||||
DRM_DEBUG_DRIVER("aux vip for hdmi\n");
|
||||
/* select the input sync for hdmi = VTG set 1 */
|
||||
tvout_write(tvout, TVO_SYNC_AUX_VTG_SET_1, TVO_HDMI_SYNC_SEL);
|
||||
}
|
||||
|
||||
/* set color channel order */
|
||||
tvout_vip_set_color_order(tvout,
|
||||
TVO_VIP_REORDER_CR_R_SEL,
|
||||
TVO_VIP_REORDER_Y_G_SEL,
|
||||
TVO_VIP_REORDER_CB_B_SEL);
|
||||
|
||||
/* set clipping mode (Limited range RGB/Y) */
|
||||
tvout_vip_set_clip_mode(tvout, TVO_VIP_CLIP_LIMITED_RANGE_RGB_Y);
|
||||
|
||||
/* set round mode (rounded to 8-bit per component) */
|
||||
tvout_vip_set_rnd(tvout, TVO_VIP_RND_8BIT_ROUNDED);
|
||||
|
||||
if (of_device_is_compatible(node, "st,stih407-tvout")) {
|
||||
/* set input video format */
|
||||
tvout_vip_set_in_vid_fmt(tvout->regs + TVO_MAIN_IN_VID_FORMAT,
|
||||
TVO_IN_FMT_SIGNED);
|
||||
sel_input_logic_inverted = true;
|
||||
}
|
||||
|
||||
/* input selection */
|
||||
tvout_vip_set_sel_input(tvout, main_path,
|
||||
sel_input_logic_inverted, STI_TVOUT_VIDEO_OUT_RGB);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
struct device_node *node = tvout->dev->of_node;
|
||||
bool sel_input_logic_inverted = false;
|
||||
|
||||
dev_dbg(tvout->dev, "%s\n", __func__);
|
||||
|
||||
if (!main_path) {
|
||||
DRM_ERROR("HD Analog on aux not implemented\n");
|
||||
return;
|
||||
}
|
||||
|
||||
DRM_DEBUG_DRIVER("main vip for HDF\n");
|
||||
|
||||
/* set color channel order */
|
||||
tvout_vip_set_color_order(tvout->regs + TVO_VIP_HDF,
|
||||
TVO_VIP_REORDER_CR_R_SEL,
|
||||
TVO_VIP_REORDER_Y_G_SEL,
|
||||
TVO_VIP_REORDER_CB_B_SEL);
|
||||
|
||||
/* set clipping mode (Limited range RGB/Y) */
|
||||
tvout_vip_set_clip_mode(tvout->regs + TVO_VIP_HDF,
|
||||
TVO_VIP_CLIP_LIMITED_RANGE_CB_CR);
|
||||
|
||||
/* set round mode (rounded to 10-bit per component) */
|
||||
tvout_vip_set_rnd(tvout->regs + TVO_VIP_HDF, TVO_VIP_RND_10BIT_ROUNDED);
|
||||
|
||||
if (of_device_is_compatible(node, "st,stih407-tvout")) {
|
||||
/* set input video format */
|
||||
tvout_vip_set_in_vid_fmt(tvout, TVO_IN_FMT_SIGNED);
|
||||
sel_input_logic_inverted = true;
|
||||
}
|
||||
|
||||
/* Input selection */
|
||||
tvout_vip_set_sel_input(tvout->regs + TVO_VIP_HDF,
|
||||
main_path,
|
||||
sel_input_logic_inverted,
|
||||
STI_TVOUT_VIDEO_OUT_YUV);
|
||||
|
||||
/* select the input sync for HD analog = VTG set 3
|
||||
* and HD DCS = VTG set 2 */
|
||||
tvout_write(tvout,
|
||||
(TVO_SYNC_MAIN_VTG_SET_2 << TVO_SYNC_HD_DCS_SHIFT)
|
||||
| TVO_SYNC_MAIN_VTG_SET_3,
|
||||
TVO_HD_SYNC_SEL);
|
||||
|
||||
/* power up HD DAC */
|
||||
tvout_write(tvout, 0, TVO_HD_DAC_CFG_OFF);
|
||||
}
|
||||
|
||||
static void sti_tvout_encoder_dpms(struct drm_encoder *encoder, int mode)
|
||||
{
|
||||
}
|
||||
|
||||
static bool sti_tvout_encoder_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
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_prepare(struct drm_encoder *encoder)
|
||||
{
|
||||
}
|
||||
|
||||
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 const struct drm_encoder_funcs sti_tvout_encoder_funcs = {
|
||||
.destroy = sti_tvout_encoder_destroy,
|
||||
};
|
||||
|
||||
static void sti_hda_encoder_commit(struct drm_encoder *encoder)
|
||||
{
|
||||
struct sti_tvout *tvout = to_sti_tvout(encoder);
|
||||
|
||||
tvout_hda_start(tvout, true);
|
||||
}
|
||||
|
||||
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_fixup = sti_tvout_encoder_mode_fixup,
|
||||
.mode_set = sti_tvout_encoder_mode_set,
|
||||
.prepare = sti_tvout_encoder_prepare,
|
||||
.commit = sti_hda_encoder_commit,
|
||||
.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 = (struct drm_encoder *) encoder;
|
||||
|
||||
drm_encoder->possible_crtcs = ENCODER_MAIN_CRTC_MASK;
|
||||
drm_encoder->possible_clones = 1 << 0;
|
||||
|
||||
drm_encoder_init(dev, drm_encoder,
|
||||
&sti_tvout_encoder_funcs, DRM_MODE_ENCODER_DAC);
|
||||
|
||||
drm_encoder_helper_add(drm_encoder, &sti_hda_encoder_helper_funcs);
|
||||
|
||||
return drm_encoder;
|
||||
}
|
||||
|
||||
static void sti_hdmi_encoder_commit(struct drm_encoder *encoder)
|
||||
{
|
||||
struct sti_tvout *tvout = to_sti_tvout(encoder);
|
||||
|
||||
tvout_hdmi_start(tvout, true);
|
||||
}
|
||||
|
||||
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_fixup = sti_tvout_encoder_mode_fixup,
|
||||
.mode_set = sti_tvout_encoder_mode_set,
|
||||
.prepare = sti_tvout_encoder_prepare,
|
||||
.commit = sti_hdmi_encoder_commit,
|
||||
.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 = (struct drm_encoder *) encoder;
|
||||
|
||||
drm_encoder->possible_crtcs = ENCODER_MAIN_CRTC_MASK;
|
||||
drm_encoder->possible_clones = 1 << 1;
|
||||
|
||||
drm_encoder_init(dev, drm_encoder,
|
||||
&sti_tvout_encoder_funcs, DRM_MODE_ENCODER_TMDS);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
tvout->drm_dev = drm_dev;
|
||||
|
||||
/* set preformatter matrix */
|
||||
for (i = 0; i < 8; i++) {
|
||||
tvout_write(tvout, rgb_to_ycbcr_601[i],
|
||||
TVO_CSC_MAIN_M0 + (i * 4));
|
||||
tvout_write(tvout, rgb_to_ycbcr_601[i],
|
||||
TVO_CSC_AUX_M0 + (i * 4));
|
||||
}
|
||||
|
||||
sti_tvout_create_encoders(drm_dev, tvout);
|
||||
|
||||
ret = component_bind_all(dev, drm_dev);
|
||||
if (ret)
|
||||
sti_tvout_destroy_encoders(tvout);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sti_tvout_unbind(struct device *dev, struct device *master,
|
||||
void *data)
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
static const struct component_ops sti_tvout_ops = {
|
||||
.bind = sti_tvout_bind,
|
||||
.unbind = sti_tvout_unbind,
|
||||
};
|
||||
|
||||
static int compare_of(struct device *dev, void *data)
|
||||
{
|
||||
return dev->of_node == data;
|
||||
}
|
||||
|
||||
static int sti_tvout_master_bind(struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sti_tvout_master_unbind(struct device *dev)
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
static const struct component_master_ops sti_tvout_master_ops = {
|
||||
.bind = sti_tvout_master_bind,
|
||||
.unbind = sti_tvout_master_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;
|
||||
struct resource *res;
|
||||
struct device_node *child_np;
|
||||
struct component_match *match = NULL;
|
||||
|
||||
DRM_INFO("%s\n", __func__);
|
||||
|
||||
if (!node)
|
||||
return -ENODEV;
|
||||
|
||||
tvout = devm_kzalloc(dev, sizeof(*tvout), GFP_KERNEL);
|
||||
if (!tvout)
|
||||
return -ENOMEM;
|
||||
|
||||
tvout->dev = dev;
|
||||
|
||||
/* get Memory ressources */
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tvout-reg");
|
||||
if (!res) {
|
||||
DRM_ERROR("Invalid glue resource\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
tvout->regs = devm_ioremap_nocache(dev, res->start, resource_size(res));
|
||||
if (!tvout->regs)
|
||||
return -ENOMEM;
|
||||
|
||||
/* 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);
|
||||
|
||||
of_platform_populate(node, NULL, NULL, dev);
|
||||
|
||||
child_np = of_get_next_available_child(node, NULL);
|
||||
|
||||
while (child_np) {
|
||||
component_match_add(dev, &match, compare_of, child_np);
|
||||
of_node_put(child_np);
|
||||
child_np = of_get_next_available_child(node, child_np);
|
||||
}
|
||||
|
||||
component_master_add_with_match(dev, &sti_tvout_master_ops, match);
|
||||
|
||||
return component_add(dev, &sti_tvout_ops);
|
||||
}
|
||||
|
||||
static int sti_tvout_remove(struct platform_device *pdev)
|
||||
{
|
||||
component_master_del(&pdev->dev, &sti_tvout_master_ops);
|
||||
component_del(&pdev->dev, &sti_tvout_ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id tvout_of_match[] = {
|
||||
{ .compatible = "st,stih416-tvout", },
|
||||
{ .compatible = "st,stih407-tvout", },
|
||||
{ /* end node */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tvout_of_match);
|
||||
|
||||
struct platform_driver sti_tvout_driver = {
|
||||
.driver = {
|
||||
.name = "sti-tvout",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = tvout_of_match,
|
||||
},
|
||||
.probe = sti_tvout_probe,
|
||||
.remove = sti_tvout_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(sti_tvout_driver);
|
||||
|
||||
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
138
drivers/gpu/drm/sti/sti_vid.c
Normal file
138
drivers/gpu/drm/sti/sti_vid.c
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
|
||||
#include "sti_layer.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
|
||||
|
||||
static int sti_vid_prepare_layer(struct sti_layer *vid, bool first_prepare)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Unmask */
|
||||
val = readl(vid->regs + VID_CTL);
|
||||
val &= ~VID_CTL_IGNORE;
|
||||
writel(val, vid->regs + VID_CTL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sti_vid_commit_layer(struct sti_layer *vid)
|
||||
{
|
||||
struct drm_display_mode *mode = vid->mode;
|
||||
u32 ydo, xdo, yds, xds;
|
||||
|
||||
ydo = sti_vtg_get_line_number(*mode, vid->dst_y);
|
||||
yds = sti_vtg_get_line_number(*mode, vid->dst_y + vid->dst_h - 1);
|
||||
xdo = sti_vtg_get_pixel_number(*mode, vid->dst_x);
|
||||
xds = sti_vtg_get_pixel_number(*mode, vid->dst_x + vid->dst_w - 1);
|
||||
|
||||
writel((ydo << 16) | xdo, vid->regs + VID_VPO);
|
||||
writel((yds << 16) | xds, vid->regs + VID_VPS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sti_vid_disable_layer(struct sti_layer *vid)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Mask */
|
||||
val = readl(vid->regs + VID_CTL);
|
||||
val |= VID_CTL_IGNORE;
|
||||
writel(val, vid->regs + VID_CTL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const uint32_t *sti_vid_get_formats(struct sti_layer *layer)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static unsigned int sti_vid_get_nb_formats(struct sti_layer *layer)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sti_vid_init(struct sti_layer *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);
|
||||
|
||||
/* Color conversion parameters */
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
static const struct sti_layer_funcs vid_ops = {
|
||||
.get_formats = sti_vid_get_formats,
|
||||
.get_nb_formats = sti_vid_get_nb_formats,
|
||||
.init = sti_vid_init,
|
||||
.prepare = sti_vid_prepare_layer,
|
||||
.commit = sti_vid_commit_layer,
|
||||
.disable = sti_vid_disable_layer,
|
||||
};
|
||||
|
||||
struct sti_layer *sti_vid_create(struct device *dev)
|
||||
{
|
||||
struct sti_layer *vid;
|
||||
|
||||
vid = devm_kzalloc(dev, sizeof(*vid), GFP_KERNEL);
|
||||
if (!vid) {
|
||||
DRM_ERROR("Failed to allocate memory for VID\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
vid->ops = &vid_ops;
|
||||
|
||||
return vid;
|
||||
}
|
||||
12
drivers/gpu/drm/sti/sti_vid.h
Normal file
12
drivers/gpu/drm/sti/sti_vid.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#ifndef _STI_VID_H_
|
||||
#define _STI_VID_H_
|
||||
|
||||
struct sti_layer *sti_vid_create(struct device *dev);
|
||||
|
||||
#endif
|
||||
223
drivers/gpu/drm/sti/sti_vtac.c
Normal file
223
drivers/gpu/drm/sti/sti_vtac.c
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
|
||||
/* registers offset */
|
||||
#define VTAC_CONFIG 0x00
|
||||
#define VTAC_RX_FIFO_CONFIG 0x04
|
||||
#define VTAC_FIFO_CONFIG_VAL 0x04
|
||||
|
||||
#define VTAC_SYS_CFG8521 0x824
|
||||
#define VTAC_SYS_CFG8522 0x828
|
||||
|
||||
/* Number of phyts per pixel */
|
||||
#define VTAC_2_5_PPP 0x0005
|
||||
#define VTAC_3_PPP 0x0006
|
||||
#define VTAC_4_PPP 0x0008
|
||||
#define VTAC_5_PPP 0x000A
|
||||
#define VTAC_6_PPP 0x000C
|
||||
#define VTAC_13_PPP 0x001A
|
||||
#define VTAC_14_PPP 0x001C
|
||||
#define VTAC_15_PPP 0x001E
|
||||
#define VTAC_16_PPP 0x0020
|
||||
#define VTAC_17_PPP 0x0022
|
||||
#define VTAC_18_PPP 0x0024
|
||||
|
||||
/* enable bits */
|
||||
#define VTAC_ENABLE 0x3003
|
||||
|
||||
#define VTAC_TX_PHY_ENABLE_CLK_PHY BIT(0)
|
||||
#define VTAC_TX_PHY_ENABLE_CLK_DLL BIT(1)
|
||||
#define VTAC_TX_PHY_PLL_NOT_OSC_MODE BIT(3)
|
||||
#define VTAC_TX_PHY_RST_N_DLL_SWITCH BIT(4)
|
||||
#define VTAC_TX_PHY_PROG_N3 BIT(9)
|
||||
|
||||
|
||||
/**
|
||||
* VTAC mode structure
|
||||
*
|
||||
* @vid_in_width: Video Data Resolution
|
||||
* @phyts_width: Width of phyt buses(phyt low and phyt high).
|
||||
* @phyts_per_pixel: Number of phyts sent per pixel
|
||||
*/
|
||||
struct sti_vtac_mode {
|
||||
u32 vid_in_width;
|
||||
u32 phyts_width;
|
||||
u32 phyts_per_pixel;
|
||||
};
|
||||
|
||||
static const struct sti_vtac_mode vtac_mode_main = {
|
||||
.vid_in_width = 0x2,
|
||||
.phyts_width = 0x2,
|
||||
.phyts_per_pixel = VTAC_5_PPP,
|
||||
};
|
||||
static const struct sti_vtac_mode vtac_mode_aux = {
|
||||
.vid_in_width = 0x1,
|
||||
.phyts_width = 0x0,
|
||||
.phyts_per_pixel = VTAC_17_PPP,
|
||||
};
|
||||
|
||||
/**
|
||||
* VTAC structure
|
||||
*
|
||||
* @dev: pointer to device structure
|
||||
* @regs: ioremapped registers for RX and TX devices
|
||||
* @phy_regs: phy registers for TX device
|
||||
* @clk: clock
|
||||
* @mode: main or auxillary configuration mode
|
||||
*/
|
||||
struct sti_vtac {
|
||||
struct device *dev;
|
||||
void __iomem *regs;
|
||||
void __iomem *phy_regs;
|
||||
struct clk *clk;
|
||||
const struct sti_vtac_mode *mode;
|
||||
};
|
||||
|
||||
static void sti_vtac_rx_set_config(struct sti_vtac *vtac)
|
||||
{
|
||||
u32 config;
|
||||
|
||||
/* Enable VTAC clock */
|
||||
if (clk_prepare_enable(vtac->clk))
|
||||
DRM_ERROR("Failed to prepare/enable vtac_rx clock.\n");
|
||||
|
||||
writel(VTAC_FIFO_CONFIG_VAL, vtac->regs + VTAC_RX_FIFO_CONFIG);
|
||||
|
||||
config = VTAC_ENABLE;
|
||||
config |= vtac->mode->vid_in_width << 4;
|
||||
config |= vtac->mode->phyts_width << 16;
|
||||
config |= vtac->mode->phyts_per_pixel << 23;
|
||||
writel(config, vtac->regs + VTAC_CONFIG);
|
||||
}
|
||||
|
||||
static void sti_vtac_tx_set_config(struct sti_vtac *vtac)
|
||||
{
|
||||
u32 phy_config;
|
||||
u32 config;
|
||||
|
||||
/* Enable VTAC clock */
|
||||
if (clk_prepare_enable(vtac->clk))
|
||||
DRM_ERROR("Failed to prepare/enable vtac_tx clock.\n");
|
||||
|
||||
/* Configure vtac phy */
|
||||
phy_config = 0x00000000;
|
||||
writel(phy_config, vtac->phy_regs + VTAC_SYS_CFG8522);
|
||||
phy_config = VTAC_TX_PHY_ENABLE_CLK_PHY;
|
||||
writel(phy_config, vtac->phy_regs + VTAC_SYS_CFG8521);
|
||||
phy_config = readl(vtac->phy_regs + VTAC_SYS_CFG8521);
|
||||
phy_config |= VTAC_TX_PHY_PROG_N3;
|
||||
writel(phy_config, vtac->phy_regs + VTAC_SYS_CFG8521);
|
||||
phy_config = readl(vtac->phy_regs + VTAC_SYS_CFG8521);
|
||||
phy_config |= VTAC_TX_PHY_ENABLE_CLK_DLL;
|
||||
writel(phy_config, vtac->phy_regs + VTAC_SYS_CFG8521);
|
||||
phy_config = readl(vtac->phy_regs + VTAC_SYS_CFG8521);
|
||||
phy_config |= VTAC_TX_PHY_RST_N_DLL_SWITCH;
|
||||
writel(phy_config, vtac->phy_regs + VTAC_SYS_CFG8521);
|
||||
phy_config = readl(vtac->phy_regs + VTAC_SYS_CFG8521);
|
||||
phy_config |= VTAC_TX_PHY_PLL_NOT_OSC_MODE;
|
||||
writel(phy_config, vtac->phy_regs + VTAC_SYS_CFG8521);
|
||||
|
||||
/* Configure vtac tx */
|
||||
config = VTAC_ENABLE;
|
||||
config |= vtac->mode->vid_in_width << 4;
|
||||
config |= vtac->mode->phyts_width << 16;
|
||||
config |= vtac->mode->phyts_per_pixel << 23;
|
||||
writel(config, vtac->regs + VTAC_CONFIG);
|
||||
}
|
||||
|
||||
static const struct of_device_id vtac_of_match[] = {
|
||||
{
|
||||
.compatible = "st,vtac-main",
|
||||
.data = &vtac_mode_main,
|
||||
}, {
|
||||
.compatible = "st,vtac-aux",
|
||||
.data = &vtac_mode_aux,
|
||||
}, {
|
||||
/* end node */
|
||||
}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, vtac_of_match);
|
||||
|
||||
static int sti_vtac_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
const struct of_device_id *id;
|
||||
struct sti_vtac *vtac;
|
||||
struct resource *res;
|
||||
|
||||
vtac = devm_kzalloc(dev, sizeof(*vtac), GFP_KERNEL);
|
||||
if (!vtac)
|
||||
return -ENOMEM;
|
||||
|
||||
vtac->dev = dev;
|
||||
|
||||
id = of_match_node(vtac_of_match, np);
|
||||
if (!id)
|
||||
return -ENOMEM;
|
||||
|
||||
vtac->mode = id->data;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
DRM_ERROR("Invalid resource\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
vtac->regs = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(vtac->regs))
|
||||
return PTR_ERR(vtac->regs);
|
||||
|
||||
|
||||
vtac->clk = devm_clk_get(dev, "vtac");
|
||||
if (IS_ERR(vtac->clk)) {
|
||||
DRM_ERROR("Cannot get vtac clock\n");
|
||||
return PTR_ERR(vtac->clk);
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
if (res) {
|
||||
vtac->phy_regs = devm_ioremap_nocache(dev, res->start,
|
||||
resource_size(res));
|
||||
sti_vtac_tx_set_config(vtac);
|
||||
} else {
|
||||
|
||||
sti_vtac_rx_set_config(vtac);
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, vtac);
|
||||
DRM_INFO("%s %s\n", __func__, dev_name(vtac->dev));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sti_vtac_remove(struct platform_device *pdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct platform_driver sti_vtac_driver = {
|
||||
.driver = {
|
||||
.name = "sti-vtac",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = vtac_of_match,
|
||||
},
|
||||
.probe = sti_vtac_probe,
|
||||
.remove = sti_vtac_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(sti_vtac_driver);
|
||||
|
||||
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
366
drivers/gpu/drm/sti/sti_vtg.c
Normal file
366
drivers/gpu/drm/sti/sti_vtg.c
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
/*
|
||||
* 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.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
|
||||
#include "sti_vtg.h"
|
||||
|
||||
#define VTG_TYPE_MASTER 0
|
||||
#define VTG_TYPE_SLAVE_BY_EXT0 1
|
||||
|
||||
/* 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_IRQ_BOTTOM BIT(0)
|
||||
#define VTG_IRQ_TOP BIT(1)
|
||||
#define VTG_IRQ_MASK (VTG_IRQ_TOP | VTG_IRQ_BOTTOM)
|
||||
|
||||
/* 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)
|
||||
|
||||
LIST_HEAD(vtg_lookup);
|
||||
|
||||
/**
|
||||
* STI VTG structure
|
||||
*
|
||||
* @dev: pointer to device driver
|
||||
* @data: data associated to the device
|
||||
* @irq: VTG irq
|
||||
* @type: VTG type (main or aux)
|
||||
* @notifier_list: notifier callback
|
||||
* @crtc_id: the crtc id for vblank event
|
||||
* @slave: slave vtg
|
||||
* @link: List node to link the structure in lookup list
|
||||
*/
|
||||
struct sti_vtg {
|
||||
struct device *dev;
|
||||
struct device_node *np;
|
||||
void __iomem *regs;
|
||||
int irq;
|
||||
u32 irq_status;
|
||||
struct raw_notifier_head notifier_list;
|
||||
int crtc_id;
|
||||
struct sti_vtg *slave;
|
||||
struct list_head link;
|
||||
};
|
||||
|
||||
static void vtg_register(struct sti_vtg *vtg)
|
||||
{
|
||||
list_add_tail(&vtg->link, &vtg_lookup);
|
||||
}
|
||||
|
||||
struct sti_vtg *of_vtg_find(struct device_node *np)
|
||||
{
|
||||
struct sti_vtg *vtg;
|
||||
|
||||
list_for_each_entry(vtg, &vtg_lookup, link) {
|
||||
if (vtg->np == np)
|
||||
return vtg;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(of_vtg_find);
|
||||
|
||||
static void vtg_reset(struct sti_vtg *vtg)
|
||||
{
|
||||
/* reset slave and then master */
|
||||
if (vtg->slave)
|
||||
vtg_reset(vtg->slave);
|
||||
|
||||
writel(1, vtg->regs + VTG_DRST_AUTOC);
|
||||
}
|
||||
|
||||
static void vtg_set_mode(struct sti_vtg *vtg,
|
||||
int type, const struct drm_display_mode *mode)
|
||||
{
|
||||
u32 tmp;
|
||||
|
||||
if (vtg->slave)
|
||||
vtg_set_mode(vtg->slave, VTG_TYPE_SLAVE_BY_EXT0, mode);
|
||||
|
||||
writel(mode->htotal, vtg->regs + VTG_CLKLN);
|
||||
writel(mode->vtotal * 2, vtg->regs + VTG_HLFLN);
|
||||
|
||||
tmp = (mode->vtotal - mode->vsync_start + 1) << 16;
|
||||
tmp |= mode->htotal - mode->hsync_start;
|
||||
writel(tmp, vtg->regs + VTG_VID_TFO);
|
||||
writel(tmp, vtg->regs + VTG_VID_BFO);
|
||||
|
||||
tmp = (mode->vdisplay + mode->vtotal - mode->vsync_start + 1) << 16;
|
||||
tmp |= mode->hdisplay + mode->htotal - mode->hsync_start;
|
||||
writel(tmp, vtg->regs + VTG_VID_TFS);
|
||||
writel(tmp, vtg->regs + VTG_VID_BFS);
|
||||
|
||||
/* prepare VTG set 1 and 2 for HDMI and VTG set 3 for HD DAC */
|
||||
tmp = (mode->hsync_end - mode->hsync_start) << 16;
|
||||
writel(tmp, vtg->regs + VTG_H_HD_1);
|
||||
writel(tmp, vtg->regs + VTG_H_HD_2);
|
||||
|
||||
tmp = (mode->vsync_end - mode->vsync_start + 1) << 16;
|
||||
tmp |= 1;
|
||||
writel(tmp, vtg->regs + VTG_TOP_V_VD_1);
|
||||
writel(tmp, vtg->regs + VTG_BOT_V_VD_1);
|
||||
writel(0, vtg->regs + VTG_TOP_V_HD_1);
|
||||
writel(0, vtg->regs + VTG_BOT_V_HD_1);
|
||||
|
||||
/* prepare VTG set 2 for for HD DCS */
|
||||
writel(tmp, vtg->regs + VTG_TOP_V_VD_2);
|
||||
writel(tmp, vtg->regs + VTG_BOT_V_VD_2);
|
||||
writel(0, vtg->regs + VTG_TOP_V_HD_2);
|
||||
writel(0, vtg->regs + VTG_BOT_V_HD_2);
|
||||
|
||||
/* prepare VTG set 3 for HD Analog in HD mode */
|
||||
tmp = (mode->hsync_end - mode->hsync_start + AWG_DELAY_HD) << 16;
|
||||
tmp |= mode->htotal + AWG_DELAY_HD;
|
||||
writel(tmp, vtg->regs + VTG_H_HD_3);
|
||||
|
||||
tmp = (mode->vsync_end - mode->vsync_start) << 16;
|
||||
tmp |= mode->vtotal;
|
||||
writel(tmp, vtg->regs + VTG_TOP_V_VD_3);
|
||||
writel(tmp, vtg->regs + VTG_BOT_V_VD_3);
|
||||
|
||||
tmp = (mode->htotal + AWG_DELAY_HD) << 16;
|
||||
tmp |= mode->htotal + AWG_DELAY_HD;
|
||||
writel(tmp, vtg->regs + VTG_TOP_V_HD_3);
|
||||
writel(tmp, vtg->regs + VTG_BOT_V_HD_3);
|
||||
|
||||
/* 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_TYPE_MASTER, mode);
|
||||
|
||||
vtg_reset(vtg);
|
||||
|
||||
/* enable irq for the vtg vblank synchro */
|
||||
if (vtg->slave)
|
||||
vtg_enable_irq(vtg->slave);
|
||||
else
|
||||
vtg_enable_irq(vtg);
|
||||
}
|
||||
EXPORT_SYMBOL(sti_vtg_set_config);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
EXPORT_SYMBOL(sti_vtg_get_line_number);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
EXPORT_SYMBOL(sti_vtg_get_pixel_number);
|
||||
|
||||
int sti_vtg_register_client(struct sti_vtg *vtg,
|
||||
struct notifier_block *nb, int crtc_id)
|
||||
{
|
||||
if (vtg->slave)
|
||||
return sti_vtg_register_client(vtg->slave, nb, crtc_id);
|
||||
|
||||
vtg->crtc_id = crtc_id;
|
||||
return raw_notifier_chain_register(&vtg->notifier_list, nb);
|
||||
}
|
||||
EXPORT_SYMBOL(sti_vtg_register_client);
|
||||
|
||||
int sti_vtg_unregister_client(struct sti_vtg *vtg, struct notifier_block *nb)
|
||||
{
|
||||
if (vtg->slave)
|
||||
return sti_vtg_unregister_client(vtg->slave, nb);
|
||||
|
||||
return raw_notifier_chain_unregister(&vtg->notifier_list, nb);
|
||||
}
|
||||
EXPORT_SYMBOL(sti_vtg_unregister_client);
|
||||
|
||||
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_id);
|
||||
|
||||
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 device_node *np;
|
||||
struct sti_vtg *vtg;
|
||||
struct resource *res;
|
||||
char irq_name[32];
|
||||
int ret;
|
||||
|
||||
vtg = devm_kzalloc(dev, sizeof(*vtg), GFP_KERNEL);
|
||||
if (!vtg)
|
||||
return -ENOMEM;
|
||||
|
||||
vtg->dev = dev;
|
||||
vtg->np = pdev->dev.of_node;
|
||||
|
||||
/* Get Memory ressources */
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
DRM_ERROR("Get memory resource failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
vtg->regs = devm_ioremap_nocache(dev, res->start, resource_size(res));
|
||||
|
||||
np = of_parse_phandle(pdev->dev.of_node, "st,slave", 0);
|
||||
if (np) {
|
||||
vtg->slave = of_vtg_find(np);
|
||||
|
||||
if (!vtg->slave)
|
||||
return -EPROBE_DEFER;
|
||||
} else {
|
||||
vtg->irq = platform_get_irq(pdev, 0);
|
||||
if (IS_ERR_VALUE(vtg->irq)) {
|
||||
DRM_ERROR("Failed to get VTG interrupt\n");
|
||||
return vtg->irq;
|
||||
}
|
||||
|
||||
snprintf(irq_name, sizeof(irq_name), "vsync-%s",
|
||||
dev_name(vtg->dev));
|
||||
|
||||
RAW_INIT_NOTIFIER_HEAD(&vtg->notifier_list);
|
||||
|
||||
ret = devm_request_threaded_irq(dev, vtg->irq, vtg_irq,
|
||||
vtg_irq_thread, IRQF_ONESHOT, irq_name, vtg);
|
||||
if (IS_ERR_VALUE(ret)) {
|
||||
DRM_ERROR("Failed to register VTG interrupt\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
vtg_register(vtg);
|
||||
platform_set_drvdata(pdev, vtg);
|
||||
|
||||
DRM_INFO("%s %s\n", __func__, dev_name(vtg->dev));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vtg_remove(struct platform_device *pdev)
|
||||
{
|
||||
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",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = vtg_of_match,
|
||||
},
|
||||
.probe = vtg_probe,
|
||||
.remove = vtg_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(sti_vtg_driver);
|
||||
|
||||
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
28
drivers/gpu/drm/sti/sti_vtg.h
Normal file
28
drivers/gpu/drm/sti/sti_vtg.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (C) STMicroelectronics SA 2014
|
||||
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#ifndef _STI_VTG_H_
|
||||
#define _STI_VTG_H_
|
||||
|
||||
#define VTG_TOP_FIELD_EVENT 1
|
||||
#define VTG_BOTTOM_FIELD_EVENT 2
|
||||
|
||||
struct sti_vtg;
|
||||
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, int crtc_id);
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue