提交 96b1b971 编写于 作者: D Dave Airlie

Merge branch 'drm_kms_for_next-v8' of...

Merge branch 'drm_kms_for_next-v8' of git://git.linaro.org/people/benjamin.gaignard/kernel into drm-next

This series of patches add the support of DRM/KMS drivers for STMicroelectronics
chipsets stih416 and stih407.

Hardware is split in two main blocks: Compositor and TVout. Each of them
includes specific hardware IPs and the display timing are controlled by a specific
Video Timing Generator hardware IP (VTG).

Compositor is made of the follow hardware IPs:
 - GDP (Generic Display Pipeline) which is an entry point for graphic (RGB)
   buffers
 - VDP (Video Diplay Pipeline) which is an entry point for video (YUV) buffers
 - HQVDP (High Quality Video Display Processor) that supports scaling,
   deinterlacing and some miscellaneous image quality improvements.
   It fetches the Video decoded buffers from memory, processes them and pushes
   them to the Compositor through a HW dedicated bus.
 - Mixer is responsible of mixing all the entries depending of their
   respective z-order and layout

TVout is divided in 3 parts:
 - HDMI to generate HDMI signals, depending of chipset version HDMI phy can
   change.
 - HDA to generate signals for HD analog TV
 - VIP to control/switch data path coming from Compositor

On stih416 compositor and Tvout are on different dies so a Video Trafic Advance
inter-die Communication mechanism (VTAC) is needed.

+---------------------------------------------+   +----------------------------------------+
| +-------------------------------+   +----+  |   |  +----+   +--------------------------+ |
| |                               |   |    |  |   |  |    |   |  +---------+     +----+  | |
| | +----+              +------+  |   |    |  |   |  |    |   |  | VIP     |---->|HDMI|  | |
| | |GPD +------------->|      |  |   |    |  |   |  |    |   |  |         |     +----+  | |
| | +----+              |Mixer |--|-->|    |  |   |  |    |---|->| switcher|             | |
| |                     |      |  |   |    |  |   |  |    |   |  |         |     +----+  | |
| |                     |      |  |   |    |  |   |  |    |   |  |         |---->|HDA |  | |
| |                     +------+  |   |VTAC|========>|VTAC|   |  +---------+     +----+  | |
| |                               |   |    |  |   |  |    |   |                          | |
| |         Compositor            |   |    |  |   |  |    |   |           TVout          | |
| +-------------------------------+   |    |  |   |  |    |   +--------------------------+ |
|                      ^              |    |  |   |  |    |             ^                  |
|                      |              |    |  |   |  |    |             |                  |
|               +--------------+      |    |  |   |  |    |      +-------------+           |
|               | VTG (master) |----->|    |  |   |  |    |----->| VTG (slave) |           |
|               +--------------+      +----+  |   |  +----+      +-------------+           |
|Digital die                                  |   |                              Analog Die|
+---------------------------------------------+   +----------------------------------------+

On stih407 Compositor and Tvout are on the same die

+-----------------------------------------------------------------+
| +-------------------------------+  +--------------------------+ |
| |                               |  |  +---------+     +----+  | |
| | +----+              +------+  |  |  | VIP     |---->|HDMI|  | |
| | |GPD +------------->|      |  |  |  |         |     +----+  | |
| | +----+              |Mixer |--|--|->| switcher|             | |
| | +----+   +-----+    |      |  |  |  |         |     +----+  | |
| | |VDP +-->+HQVDP+--->|      |  |  |  |         |---->|HDA |  | |
| | +----+   +-----+    +------+  |  |  +---------+     +----+  | |
| |                               |  |                          | |
| |         Compositor            |  |           TVout          | |
| +-------------------------------+  +--------------------------+ |
|                              ^        ^                         |
|                              |        |                         |
|                           +--------------+                      |
|                           |     VTG      |                      |
|                           +--------------+                      |
|Digital die                                                      |
+-----------------------------------------------------------------+

In addition of the drivers for the IPs listed before a thin I2C driver (hdmiddc) is used
by HDMI driver to retrieve EDID for monitor.

To unify interfaces of GDP and VDP we create a "layer" interface called by
compositor to control both GPD and VDP.

Hardware have memory contraints (alignment, contiguous) so we use CMA drm helpers functions
to allocate frame buffer.

File naming convention is:
 - sti_* for IPs drivers
 - sti_drm_* for drm functions implementation.

* 'drm_kms_for_next-v8' of git://git.linaro.org/people/benjamin.gaignard/kernel:
  drm: sti: Add DRM driver itself
  drm: sti: add Compositor
  drm: sti: add Mixer
  drm: sti: add VID layer
  drm: sti: add GDP layer
  drm: sti: add TVOut driver
  drm: sti: add HDA driver
  drm: sti: add HDMI driver
  drm: sti: add VTAC drivers
  drm: sti: add VTG driver
  drm: sti: add bindings for DRM driver
STMicroelectronics stih4xx platforms
- sti-vtg: video timing generator
Required properties:
- compatible: "st,vtg"
- reg: Physical base address of the IP registers and length of memory mapped region.
Optional properties:
- interrupts : VTG interrupt number to the CPU.
- st,slave: phandle on a slave vtg
- sti-vtac: video timing advanced inter dye communication Rx and TX
Required properties:
- compatible: "st,vtac-main" or "st,vtac-aux"
- reg: Physical base address of the IP registers and length of memory mapped region.
- clocks: from common clock binding: handle hardware IP needed clocks, the
number of clocks may depend of the SoC type.
See ../clocks/clock-bindings.txt for details.
- clock-names: names of the clocks listed in clocks property in the same
order.
- sti-display-subsystem: Master device for DRM sub-components
This device must be the parent of all the sub-components and is responsible
of bind them.
Required properties:
- compatible: "st,sti-display-subsystem"
- ranges: to allow probing of subdevices
- sti-compositor: frame compositor engine
must be a child of sti-display-subsystem
Required properties:
- compatible: "st,stih<chip>-compositor"
- reg: Physical base address of the IP registers and length of memory mapped region.
- clocks: from common clock binding: handle hardware IP needed clocks, the
number of clocks may depend of the SoC type.
See ../clocks/clock-bindings.txt for details.
- clock-names: names of the clocks listed in clocks property in the same
order.
- resets: resets to be used by the device
See ../reset/reset.txt for details.
- reset-names: names of the resets listed in resets property in the same
order.
- st,vtg: phandle(s) on vtg device (main and aux) nodes.
- sti-tvout: video out hardware block
must be a child of sti-display-subsystem
Required properties:
- compatible: "st,stih<chip>-tvout"
- reg: Physical base address of the IP registers and length of memory mapped region.
- reg-names: names of the mapped memory regions listed in regs property in
the same order.
- resets: resets to be used by the device
See ../reset/reset.txt for details.
- reset-names: names of the resets listed in resets property in the same
order.
- ranges: to allow probing of subdevices
- sti-hdmi: hdmi output block
must be a child of sti-tvout
Required properties:
- compatible: "st,stih<chip>-hdmi";
- reg: Physical base address of the IP registers and length of memory mapped region.
- reg-names: names of the mapped memory regions listed in regs property in
the same order.
- interrupts : HDMI interrupt number to the CPU.
- interrupt-names: name of the interrupts listed in interrupts property in
the same order
- clocks: from common clock binding: handle hardware IP needed clocks, the
number of clocks may depend of the SoC type.
- clock-names: names of the clocks listed in clocks property in the same
order.
- hdmi,hpd-gpio: gpio id to detect if an hdmi cable is plugged or not.
sti-hda:
Required properties:
must be a child of sti-tvout
- compatible: "st,stih<chip>-hda"
- reg: Physical base address of the IP registers and length of memory mapped region.
- reg-names: names of the mapped memory regions listed in regs property in
the same order.
- clocks: from common clock binding: handle hardware IP needed clocks, the
number of clocks may depend of the SoC type.
See ../clocks/clock-bindings.txt for details.
- clock-names: names of the clocks listed in clocks property in the same
order.
Example:
/ {
...
vtg_main_slave: sti-vtg-main-slave@fe85A800 {
compatible = "st,vtg";
reg = <0xfe85A800 0x300>;
interrupts = <GIC_SPI 175 IRQ_TYPE_NONE>;
};
vtg_main: sti-vtg-main-master@fd348000 {
compatible = "st,vtg";
reg = <0xfd348000 0x400>;
st,slave = <&vtg_main_slave>;
};
vtg_aux_slave: sti-vtg-aux-slave@fd348400 {
compatible = "st,vtg";
reg = <0xfe858200 0x300>;
interrupts = <GIC_SPI 176 IRQ_TYPE_NONE>;
};
vtg_aux: sti-vtg-aux-master@fd348400 {
compatible = "st,vtg";
reg = <0xfd348400 0x400>;
st,slave = <&vtg_aux_slave>;
};
sti-vtac-rx-main@fee82800 {
compatible = "st,vtac-main";
reg = <0xfee82800 0x200>;
clock-names = "vtac";
clocks = <&clk_m_a2_div0 CLK_M_VTAC_MAIN_PHY>;
};
sti-vtac-rx-aux@fee82a00 {
compatible = "st,vtac-aux";
reg = <0xfee82a00 0x200>;
clock-names = "vtac";
clocks = <&clk_m_a2_div0 CLK_M_VTAC_AUX_PHY>;
};
sti-vtac-tx-main@fd349000 {
compatible = "st,vtac-main";
reg = <0xfd349000 0x200>, <0xfd320000 0x10000>;
clock-names = "vtac";
clocks = <&clk_s_a1_hs CLK_S_VTAC_TX_PHY>;
};
sti-vtac-tx-aux@fd349200 {
compatible = "st,vtac-aux";
reg = <0xfd349200 0x200>, <0xfd320000 0x10000>;
clock-names = "vtac";
clocks = <&clk_s_a1_hs CLK_S_VTAC_TX_PHY>;
};
sti-display-subsystem {
compatible = "st,sti-display-subsystem";
ranges;
sti-compositor@fd340000 {
compatible = "st,stih416-compositor";
reg = <0xfd340000 0x1000>;
clock-names = "compo_main", "compo_aux",
"pix_main", "pix_aux";
clocks = <&clk_m_a2_div1 CLK_M_COMPO_MAIN>, <&clk_m_a2_div1 CLK_M_COMPO_AUX>,
<&clockgen_c_vcc CLK_S_PIX_MAIN>, <&clockgen_c_vcc CLK_S_PIX_AUX>;
reset-names = "compo-main", "compo-aux";
resets = <&softreset STIH416_COMPO_M_SOFTRESET>, <&softreset STIH416_COMPO_A_SOFTRESET>;
st,vtg = <&vtg_main>, <&vtg_aux>;
};
sti-tvout@fe000000 {
compatible = "st,stih416-tvout";
reg = <0xfe000000 0x1000>, <0xfe85a000 0x400>, <0xfe830000 0x10000>;
reg-names = "tvout-reg", "hda-reg", "syscfg";
reset-names = "tvout";
resets = <&softreset STIH416_HDTVOUT_SOFTRESET>;
ranges;
sti-hdmi@fe85c000 {
compatible = "st,stih416-hdmi";
reg = <0xfe85c000 0x1000>, <0xfe830000 0x10000>;
reg-names = "hdmi-reg", "syscfg";
interrupts = <GIC_SPI 173 IRQ_TYPE_NONE>;
interrupt-names = "irq";
clock-names = "pix", "tmds", "phy", "audio";
clocks = <&clockgen_c_vcc CLK_S_PIX_HDMI>, <&clockgen_c_vcc CLK_S_TMDS_HDMI>, <&clockgen_c_vcc CLK_S_HDMI_REJECT_PLL>, <&clockgen_b1 CLK_S_PCM_0>;
hdmi,hpd-gpio = <&PIO2 5>;
};
sti-hda@fe85a000 {
compatible = "st,stih416-hda";
reg = <0xfe85a000 0x400>, <0xfe83085c 0x4>;
reg-names = "hda-reg", "video-dacs-ctrl";
clock-names = "pix", "hddac";
clocks = <&clockgen_c_vcc CLK_S_PIX_HD>, <&clockgen_c_vcc CLK_S_HDDAC>;
};
};
};
...
};
...@@ -201,3 +201,5 @@ source "drivers/gpu/drm/msm/Kconfig" ...@@ -201,3 +201,5 @@ source "drivers/gpu/drm/msm/Kconfig"
source "drivers/gpu/drm/tegra/Kconfig" source "drivers/gpu/drm/tegra/Kconfig"
source "drivers/gpu/drm/panel/Kconfig" source "drivers/gpu/drm/panel/Kconfig"
source "drivers/gpu/drm/sti/Kconfig"
...@@ -64,6 +64,7 @@ obj-$(CONFIG_DRM_QXL) += qxl/ ...@@ -64,6 +64,7 @@ obj-$(CONFIG_DRM_QXL) += qxl/
obj-$(CONFIG_DRM_BOCHS) += bochs/ obj-$(CONFIG_DRM_BOCHS) += bochs/
obj-$(CONFIG_DRM_MSM) += msm/ obj-$(CONFIG_DRM_MSM) += msm/
obj-$(CONFIG_DRM_TEGRA) += tegra/ obj-$(CONFIG_DRM_TEGRA) += tegra/
obj-$(CONFIG_DRM_STI) += sti/
obj-y += i2c/ obj-y += i2c/
obj-y += panel/ obj-y += panel/
obj-y += bridge/ obj-y += bridge/
config DRM_STI
tristate "DRM Support for STMicroelectronics SoC stiH41x Series"
depends on DRM && (SOC_STIH415 || SOC_STIH416 || ARCH_MULTIPLATFORM)
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
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
\ No newline at end of file
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 | <-+
'-------' '-------------' '-----------' '-----------'
/*
* 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;
}
/* 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");
/*
* 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
/*
* 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 device *dev = mixer->dev;
struct sti_compositor *compo = dev_get_drvdata(dev);
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;
}
/*
* 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
/*
* 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 (!master)
return -EINVAL;
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");
/*
* 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
/*
* 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;
}
/*
* 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
/*
* 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;
}
/*
* 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
此差异已折叠。
此差异已折叠。
/*
* 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
/*
* 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,
};
/*
* 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
/*
* 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,
};
/*
* 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
/*
* 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);
}
/*
* 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
/*
* 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;
}
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册