This series of patches add the support of DRM/KMS drivers for STMicroelectronics chipsets stih416 and stih407.
patcheset version 2: - Use componentized device instead of register sub-devices in master driver probe function - Fix Makefile and Kconfig to only allow built-in compilation
patches could be found here: git://git.linaro.org/people/benjamin.gaignard/kernel.git on branch: drm_kms_for_next-v2 patcheset version 1: - First path submission
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.
Benjamin Gaignard (18): drm: sti: add bindings for DRM driver drm: sti: add VTG driver drm: sti: add VTAC drivers drm: sti: add HDMI driver drm: sti: add I2C client driver for HDMI drm: sti: add HDA driver drm: sti: add TVOut driver drm: sti: add sti layer interface definition drm: sti: add GDP layer drm: sti: add VID layer drm: sti: add Mixer drm: sti: add Compositor drm: sti: add debug to GDP drm: sti: add debug to VID drm: sti: add debug to TVout drm: sti: add debug to mixer drm: sti: Add DRM driver itself drm: sti: add HQVDP driver
Vincent Abriou (1): drm: sti: rename mixer registers to be main/aux independent
.../devicetree/bindings/gpu/st,stih4xx.txt | 187 ++++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sti/Kconfig | 26 + drivers/gpu/drm/sti/Makefile | 23 + drivers/gpu/drm/sti/sti_compositor.c | 222 ++++ drivers/gpu/drm/sti/sti_compositor.h | 84 ++ drivers/gpu/drm/sti/sti_ddc.c | 56 + drivers/gpu/drm/sti/sti_drm_connector.c | 195 ++++ drivers/gpu/drm/sti/sti_drm_connector.h | 16 + drivers/gpu/drm/sti/sti_drm_crtc.c | 440 ++++++++ drivers/gpu/drm/sti/sti_drm_crtc.h | 21 + drivers/gpu/drm/sti/sti_drm_drv.c | 447 ++++++++ drivers/gpu/drm/sti/sti_drm_drv.h | 39 + drivers/gpu/drm/sti/sti_drm_encoder.c | 201 ++++ drivers/gpu/drm/sti/sti_drm_encoder.h | 16 + drivers/gpu/drm/sti/sti_drm_plane.c | 195 ++++ drivers/gpu/drm/sti/sti_drm_plane.h | 16 + drivers/gpu/drm/sti/sti_gdp.c | 726 +++++++++++++ drivers/gpu/drm/sti/sti_gdp.h | 75 ++ drivers/gpu/drm/sti/sti_hda.c | 853 +++++++++++++++ drivers/gpu/drm/sti/sti_hda.h | 14 + drivers/gpu/drm/sti/sti_hdmi.c | 1072 +++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi.h | 198 ++++ drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c | 398 +++++++ drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c | 224 ++++ drivers/gpu/drm/sti/sti_hqvdp.c | 1107 ++++++++++++++++++++ drivers/gpu/drm/sti/sti_hqvdp.h | 55 + drivers/gpu/drm/sti/sti_hqvdp_lut.h | 373 +++++++ drivers/gpu/drm/sti/sti_layer.c | 324 ++++++ drivers/gpu/drm/sti/sti_layer.h | 116 ++ drivers/gpu/drm/sti/sti_mixer.c | 405 +++++++ drivers/gpu/drm/sti/sti_mixer.h | 54 + drivers/gpu/drm/sti/sti_tvout.c | 884 ++++++++++++++++ drivers/gpu/drm/sti/sti_tvout.h | 105 ++ drivers/gpu/drm/sti/sti_vid.c | 259 +++++ drivers/gpu/drm/sti/sti_vid.h | 34 + drivers/gpu/drm/sti/sti_vtac_rx.c | 167 +++ drivers/gpu/drm/sti/sti_vtac_tx.c | 182 ++++ drivers/gpu/drm/sti/sti_vtac_utils.h | 52 + drivers/gpu/drm/sti/sti_vtg.c | 467 +++++++++ drivers/gpu/drm/sti/sti_vtg.h | 20 + drivers/gpu/drm/sti/sti_vtg_utils.c | 99 ++ drivers/gpu/drm/sti/sti_vtg_utils.h | 29 + 44 files changed, 10479 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpu/st,stih4xx.txt create mode 100644 drivers/gpu/drm/sti/Kconfig create mode 100644 drivers/gpu/drm/sti/Makefile create mode 100644 drivers/gpu/drm/sti/sti_compositor.c create mode 100644 drivers/gpu/drm/sti/sti_compositor.h create mode 100644 drivers/gpu/drm/sti/sti_ddc.c create mode 100644 drivers/gpu/drm/sti/sti_drm_connector.c create mode 100644 drivers/gpu/drm/sti/sti_drm_connector.h create mode 100644 drivers/gpu/drm/sti/sti_drm_crtc.c create mode 100644 drivers/gpu/drm/sti/sti_drm_crtc.h create mode 100644 drivers/gpu/drm/sti/sti_drm_drv.c create mode 100644 drivers/gpu/drm/sti/sti_drm_drv.h create mode 100644 drivers/gpu/drm/sti/sti_drm_encoder.c create mode 100644 drivers/gpu/drm/sti/sti_drm_encoder.h create mode 100644 drivers/gpu/drm/sti/sti_drm_plane.c create mode 100644 drivers/gpu/drm/sti/sti_drm_plane.h create mode 100644 drivers/gpu/drm/sti/sti_gdp.c create mode 100644 drivers/gpu/drm/sti/sti_gdp.h create mode 100644 drivers/gpu/drm/sti/sti_hda.c create mode 100644 drivers/gpu/drm/sti/sti_hda.h create mode 100644 drivers/gpu/drm/sti/sti_hdmi.c create mode 100644 drivers/gpu/drm/sti/sti_hdmi.h create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c create mode 100644 drivers/gpu/drm/sti/sti_hqvdp.c create mode 100644 drivers/gpu/drm/sti/sti_hqvdp.h create mode 100644 drivers/gpu/drm/sti/sti_hqvdp_lut.h create mode 100644 drivers/gpu/drm/sti/sti_layer.c create mode 100644 drivers/gpu/drm/sti/sti_layer.h create mode 100644 drivers/gpu/drm/sti/sti_mixer.c create mode 100644 drivers/gpu/drm/sti/sti_mixer.h create mode 100644 drivers/gpu/drm/sti/sti_tvout.c create mode 100644 drivers/gpu/drm/sti/sti_tvout.h create mode 100644 drivers/gpu/drm/sti/sti_vid.c create mode 100644 drivers/gpu/drm/sti/sti_vid.h create mode 100644 drivers/gpu/drm/sti/sti_vtac_rx.c create mode 100644 drivers/gpu/drm/sti/sti_vtac_tx.c create mode 100644 drivers/gpu/drm/sti/sti_vtac_utils.h create mode 100644 drivers/gpu/drm/sti/sti_vtg.c create mode 100644 drivers/gpu/drm/sti/sti_vtg.h create mode 100644 drivers/gpu/drm/sti/sti_vtg_utils.c create mode 100644 drivers/gpu/drm/sti/sti_vtg_utils.h
Add DRM/KMS driver bindings documentation. Describe the required properties for each of the hardware IPs drivers.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- .../devicetree/bindings/gpu/st,stih4xx.txt | 187 +++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpu/st,stih4xx.txt
diff --git a/Documentation/devicetree/bindings/gpu/st,stih4xx.txt b/Documentation/devicetree/bindings/gpu/st,stih4xx.txt new file mode 100644 index 0000000..4755760 --- /dev/null +++ b/Documentation/devicetree/bindings/gpu/st,stih4xx.txt @@ -0,0 +1,187 @@ +STMicroelectronics stih4xx platforms + +- 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 + +- sti-compositor: frame compositor engine + 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. + +- sti-vtac: video timing advanced inter dye communication Rx and TX + Required properties: + - compatible: "st,stih<chip>-vtac-rx" or "st,stih<chip>-vtac-tx" + - 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. + - vtac-rx-aux: Must be set to indicated that the device is dedicated to + auxillary data path if not the device is used for main data path. + - vtac-tx-aux: Must be set to indicated that the device is dedicated to + auxillary data path if not the device is used for main data path. + +- sti-vtg: video timing generator + Required properties: + - compatible: "st,stih<chip>-vtg" + - 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 : VTG interrupt number to the CPU. + - interrupt-names: name of the interrupts listed in interrupts property in + the same order. + - vtg-aux: Must be set to indicated that the device is dedicated to + auxillary data path if not the device is used for main data path. + +- sti-tvout: video out hardware block + 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 + 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: + - 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: + +/ { + ... + 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>; + }; + + sti-vtac-rx-main@fee82800 { + compatible = "st,stih416-vtac-rx"; + reg = <0xfee82800 0x200>; + clock-names = "vtac_main_phy"; + clocks = <&CLK_M_A2_DIV0 CLK_M_VTAC_MAIN_PHY>; + }; + + sti-vtac-rx-aux@fee82a00 { + compatible = "st,stih416-vtac-rx"; + reg = <0xfee82a00 0x200>; + clock-names = "vtac_aux_phy"; + clocks = <&CLK_M_A2_DIV0 CLK_M_VTAC_AUX_PHY>; + vtac-rx-aux; + }; + + sti-vtac-tx-main@fd349000 { + compatible = "st,stih416-vtac-tx"; + reg = <0xfd349000 0x200>, <0xfd320000 0x10000>; + reg-names = "vtac-tx", "vtac-phy"; + clock-names = "vtac_tx_phy"; + clocks = <&CLK_S_A1_HS CLK_S_VTAC_TX_PHY>; + }; + + sti-vtac-tx-aux@fd349200 { + compatible = "st,stih416-vtac-tx"; + reg = <0xfd349200 0x200>, <0xfd320000 0x10000>; + reg-names = "vtac-tx", "vtac-phy"; + clock-names = "vtac_tx_phy"; + clocks = <&CLK_S_A1_HS CLK_S_VTAC_TX_PHY>; + vtac-tx-aux; + }; + + sti-vtg-main@fd348000 { + compatible = "st,stih416-vtg"; + reg = <0xfd348000 0x400>, <0xfe85A800 0x300>; + reg-names = "master", "slave"; + interrupts = <GIC_SPI 175 IRQ_TYPE_NONE>; + interrupt-names = "synchro_irq"; + }; + + sti-vtg-aux@fd348400 { + compatible = "st,stih416-vtg"; + reg = <0xfd348400 0x400>, <0xfe858200 0x300>; + reg-names = "master", "slave"; + interrupts = <GIC_SPI 176 IRQ_TYPE_NONE>; + interrupt-names = "synchro_irq"; + 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>; + }; + + 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 = "hdmi_irq"; + clock-names = "hdmi_pix", "hdmi_tmds", "hdmi_phy", "hdmi_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 = "hda_pix", "hda_hddac"; + clocks = <&CLOCKGEN_C_VCC CLK_S_PIX_HD>, <&CLOCKGEN_C_VCC CLK_S_HDDAC>; + }; + }; + ... +};
Video Time Generator drivers are used to synchronize the compositor and tvout hardware IPs by providing line count, sample count, synchronization signals (HSYNC, VSYNC) and top and bottom fields indication. VTG are used by pair for each data path (main or auxiliary): one for master and one for slave.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sti/Kconfig | 11 + drivers/gpu/drm/sti/Makefile | 3 + drivers/gpu/drm/sti/sti_vtg.c | 467 ++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_vtg.h | 20 ++ drivers/gpu/drm/sti/sti_vtg_utils.c | 99 ++++++++ drivers/gpu/drm/sti/sti_vtg_utils.h | 29 +++ 8 files changed, 632 insertions(+) create mode 100644 drivers/gpu/drm/sti/Kconfig create mode 100644 drivers/gpu/drm/sti/Makefile create mode 100644 drivers/gpu/drm/sti/sti_vtg.c create mode 100644 drivers/gpu/drm/sti/sti_vtg.h create mode 100644 drivers/gpu/drm/sti/sti_vtg_utils.c create mode 100644 drivers/gpu/drm/sti/sti_vtg_utils.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index d1cc2f6..0e30029 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -201,3 +201,5 @@ source "drivers/gpu/drm/tegra/Kconfig" source "drivers/gpu/drm/panel/Kconfig"
source "drivers/gpu/drm/bridge/Kconfig" + +source "drivers/gpu/drm/sti/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 5e792b0..44f7b17 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_DRM_QXL) += qxl/ obj-$(CONFIG_DRM_BOCHS) += bochs/ obj-$(CONFIG_DRM_MSM) += msm/ obj-$(CONFIG_DRM_TEGRA) += tegra/ +obj-$(CONFIG_DRM_STI) += sti/ obj-y += i2c/ obj-y += panel/ obj-y += bridge/ diff --git a/drivers/gpu/drm/sti/Kconfig b/drivers/gpu/drm/sti/Kconfig new file mode 100644 index 0000000..3fff278 --- /dev/null +++ b/drivers/gpu/drm/sti/Kconfig @@ -0,0 +1,11 @@ +config DRM_STI + bool "DRM Support for STMicroelectronics SoC stiH41x Series" + depends on DRM && (SOC_STIH415 || SOC_STIH416 || ARCH_MULTIPLATFORM) + help + Choose this option to enable DRM on STM stiH41x chipset + +config VTG_STI + bool "Video Timing Generator for STMicroelectronics SoC stiH41x Series" + depends on DRM_STI + help + Choose this option to enable VTG on STM stiH41x chipset diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile new file mode 100644 index 0000000..33216e1 --- /dev/null +++ b/drivers/gpu/drm/sti/Makefile @@ -0,0 +1,3 @@ +ccflags-y := -Iinclude/drm + +obj-$(CONFIG_VTG_STI) += sti_vtg.o sti_vtg_utils.o diff --git a/drivers/gpu/drm/sti/sti_vtg.c b/drivers/gpu/drm/sti/sti_vtg.c new file mode 100644 index 0000000..faf7539 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg.c @@ -0,0 +1,467 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * Vincent Abriou vincent.abriou@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/notifier.h> + +#include "sti_vtg_utils.h" +#include "sti_vtg.h" + +#define VTG_TYPE_MASTER 0 +#define VTG_TYPE_SLAVE_BY_EXT0 1 + +/* registers offset */ +#define VTG_MODE 0x0000 +#define VTG_CLKLN 0x0008 +#define VTG_HLFLN 0x000C +#define VTG_DRST_AUTOC 0x0010 +#define VTG_VID_TFO 0x0040 +#define VTG_VID_TFS 0x0044 +#define VTG_VID_BFO 0x0048 +#define VTG_VID_BFS 0x004C + +#define VTG_HOST_ITS 0x0078 +#define VTG_HOST_ITS_BCLR 0x007C +#define VTG_HOST_ITM_BCLR 0x0088 +#define VTG_HOST_ITM_BSET 0x008C + +#define VTG_H_HD_1 0x00C0 +#define VTG_TOP_V_VD_1 0x00C4 +#define VTG_BOT_V_VD_1 0x00C8 +#define VTG_TOP_V_HD_1 0x00CC +#define VTG_BOT_V_HD_1 0x00D0 + +#define VTG_H_HD_2 0x00E0 +#define VTG_TOP_V_VD_2 0x00E4 +#define VTG_BOT_V_VD_2 0x00E8 +#define VTG_TOP_V_HD_2 0x00EC +#define VTG_BOT_V_HD_2 0x00F0 + +#define VTG_H_HD_3 0x0100 +#define VTG_TOP_V_VD_3 0x0104 +#define VTG_BOT_V_VD_3 0x0108 +#define VTG_TOP_V_HD_3 0x010C +#define VTG_BOT_V_HD_3 0x0110 + +/* IRQ mask */ +#define VTG_IRQ_TOP_FIELD_MASK (1L << 1) +#define VTG_IRQ_BOTTOM_FIELD_MASK (1L << 0) +#define VTG_IRQ_MASK (VTG_IRQ_TOP_FIELD_MASK | \ + VTG_IRQ_BOTTOM_FIELD_MASK) + +/* Delay introduced by the AWG in nb of pixels */ +#define AWG_DELAY_HD (-9) +#define AWG_DELAY_ED (-8) +#define AWG_DELAY_SD (-7) + +static const struct of_device_id vtg_match_types[]; + +/* + * STI VTG data structure + * + * @nb_reg: number of memory resources to register + * @reg_names: names of the memory resources to register + * @regs: ioremapped registers + */ +#define MAX_MEM_REGION 2 +#define VTG_MASTER 0 +#define VTG_SLAVE 1 +struct sti_vtg_data { + int nb_reg; + char *reg_names[MAX_MEM_REGION]; + void __iomem *regs[MAX_MEM_REGION]; +}; + +/* + * STI VTG structure + * + * @dev: pointer to device driver + * @data: data associated to the device + * @irq: VTG irq + * @type: VTG type (main or aux) + * @notifier_list: notifier callback + */ +struct sti_vtg { + struct device *dev; + struct sti_vtg_data data; + int irq; + int type; + struct raw_notifier_head notifier_list; +}; + +/* + * STiH416: + * -------- + * VTG slave is connected to the VTG master by the ext0 input (ext1 input is + * left unconnected). + * + * MPE ! ! SAS + * ________ ! ! ________ + * | | ! ! ext0 | | + * | | _________! !_________ ----->| | + * | VTG | hsync | ! ! | hsync | | VTG | + * | |------------>| VTAC Tx !==>! VTAC Rx |-------- | | + * | master | vsync |_________! !_________| vsync | slave | + * | | ! ! ---->| | + * |________| ! ! ext1 |________| + * ! ! | + * ! ! synchro_irq + * */ +struct sti_vtg_data stih416_vtg_data = { + .nb_reg = 2, + .reg_names = {"master", "slave"}, +}; + +/* + * STiH407: + * -------- + * Only VTG master is used. There is no MPE and SAS domain => only one domain. + * + * MPE + * ________ + * | | + * | | + * | VTG | hsync + * | |------------> + * | master | vsync + * | | + * |________| + * | + * synchro_irq + * */ +struct sti_vtg_data stih407_vtg_data = { + .nb_reg = 1, + .reg_names = {"master"}, +}; + +static int vtg_reset(void __iomem *regs) +{ + writel(1, regs + VTG_DRST_AUTOC); + return 0; +} + +static int vtg_reg_dump(struct device *dev, void __iomem *regs) +{ + dev_dbg(dev, "regs %p\n", regs); + dev_dbg(dev, "VTG_MODE 0x%x\n", readl(regs + VTG_MODE)); + dev_dbg(dev, "VTG_CLKLN 0x%x\n", readl(regs + VTG_CLKLN)); + dev_dbg(dev, "VTG_HLFLN 0x%x\n", readl(regs + VTG_HLFLN)); + dev_dbg(dev, "VTG_VID_TFO 0x%x\n", readl(regs + VTG_VID_TFO)); + dev_dbg(dev, "VTG_VID_BFO 0x%x\n", readl(regs + VTG_VID_BFO)); + dev_dbg(dev, "VTG_VID_TFS 0x%x\n", readl(regs + VTG_VID_TFS)); + dev_dbg(dev, "VTG_VID_BFS 0x%x\n", readl(regs + VTG_VID_BFS)); + dev_dbg(dev, "VTG_H_HD_1 0x%x\n", readl(regs + VTG_H_HD_1)); + dev_dbg(dev, "VTG_TOP_V_VD_1 0x%x\n", readl(regs + VTG_TOP_V_VD_1)); + dev_dbg(dev, "VTG_BOT_V_VD_1 0x%x\n", readl(regs + VTG_BOT_V_VD_1)); + dev_dbg(dev, "VTG_TOP_V_HD_1 0x%x\n", readl(regs + VTG_TOP_V_HD_1)); + dev_dbg(dev, "VTG_BOT_V_HD_1 0x%x\n", readl(regs + VTG_BOT_V_HD_1)); + dev_dbg(dev, "VTG_H_HD_2 0x%x\n", readl(regs + VTG_H_HD_2)); + dev_dbg(dev, "VTG_TOP_V_VD_2 0x%x\n", readl(regs + VTG_TOP_V_VD_2)); + dev_dbg(dev, "VTG_BOT_V_VD_2 0x%x\n", readl(regs + VTG_BOT_V_VD_2)); + dev_dbg(dev, "VTG_TOP_V_HD_2 0x%x\n", readl(regs + VTG_TOP_V_HD_2)); + dev_dbg(dev, "VTG_BOT_V_HD_2 0x%x\n", readl(regs + VTG_BOT_V_HD_2)); + dev_dbg(dev, "VTG_H_HD_3 0x%x\n", readl(regs + VTG_H_HD_3)); + dev_dbg(dev, "VTG_TOP_V_VD_3 0x%x\n", readl(regs + VTG_TOP_V_VD_3)); + dev_dbg(dev, "VTG_BOT_V_VD_3 0x%x\n", readl(regs + VTG_BOT_V_VD_3)); + dev_dbg(dev, "VTG_TOP_V_HD_3 0x%x\n", readl(regs + VTG_TOP_V_HD_3)); + dev_dbg(dev, "VTG_BOT_V_HD_3 0x%x\n", readl(regs + VTG_BOT_V_HD_3)); + return 0; +} + +static int vtg_write_reg(void __iomem *regs, + int type, const struct drm_display_mode *mode) +{ + int fo, fs, h_hd, v_vd, v_hd; + + writel(mode->htotal, regs + VTG_CLKLN); + writel(mode->vtotal * 2, regs + VTG_HLFLN); + + fo = (mode->vtotal - mode->vsync_start + 1) << 16; + fo |= mode->htotal - mode->hsync_start; + writel(fo, regs + VTG_VID_TFO); + writel(fo, regs + VTG_VID_BFO); + + fs = (mode->vdisplay + mode->vtotal - mode->vsync_start + 1) << 16; + fs |= mode->hdisplay + mode->htotal - mode->hsync_start; + writel(fs, regs + VTG_VID_TFS); + writel(fs, regs + VTG_VID_BFS); + + /* Prepare VTG set 1 for HDMI and VTG set 3 for HD DAC */ + h_hd = (mode->hsync_end - mode->hsync_start) << 16; + writel(h_hd, regs + VTG_H_HD_1); + + v_vd = (mode->vsync_end - mode->vsync_start + 1) << 16; + v_vd |= 1; + writel(v_vd, regs + VTG_TOP_V_VD_1); + writel(v_vd, regs + VTG_BOT_V_VD_1); + writel(0, regs + VTG_TOP_V_HD_1); + writel(0, regs + VTG_BOT_V_HD_1); + + /* Prepare VTG set 2 for for HD DCS */ + writel(h_hd, regs + VTG_H_HD_2); + writel(v_vd, regs + VTG_TOP_V_VD_2); + writel(v_vd, regs + VTG_BOT_V_VD_2); + writel(0, regs + VTG_TOP_V_HD_2); + writel(0, regs + VTG_BOT_V_HD_2); + + /* Prepare VTG set 3 for HD Analog in HD mode */ + h_hd = (mode->hsync_end - mode->hsync_start + AWG_DELAY_HD) << 16; + h_hd |= mode->htotal + AWG_DELAY_HD; + writel(h_hd, regs + VTG_H_HD_3); + + v_vd = (mode->vsync_end - mode->vsync_start) << 16; + v_vd |= mode->vtotal; + writel(v_vd, regs + VTG_TOP_V_VD_3); + writel(v_vd, regs + VTG_BOT_V_VD_3); + + v_hd = (mode->htotal + AWG_DELAY_HD) << 16; + v_hd |= mode->htotal + AWG_DELAY_HD; + writel(v_hd, regs + VTG_TOP_V_HD_3); + writel(v_hd, regs + VTG_BOT_V_HD_3); + + /* Mode */ + writel(type, regs + VTG_MODE); + return 0; +} + +static int vtg_disable_irq(void __iomem *regs) +{ + /* Clear interrupt status and mask */ + writel(0xFFFF, regs + VTG_HOST_ITS_BCLR); + writel(0xFFFF, regs + VTG_HOST_ITM_BCLR); + return 0; +} + +static int vtg_enable_irq(void __iomem *regs) +{ + vtg_disable_irq(regs); + writel(VTG_IRQ_MASK, regs + VTG_HOST_ITM_BSET); + return 0; +} + +int vtg_set_config(struct device *dev, const struct drm_display_mode *mode) +{ + struct sti_vtg *vtg = dev_get_drvdata(dev); + struct sti_vtg_data *data = &vtg->data; + + if (!vtg) + return 1; + + if (!data->regs[VTG_MASTER]) + return 1; + + /* write slave / master configuration */ + if (data->regs[VTG_SLAVE]) + vtg_write_reg(data->regs[VTG_SLAVE], + VTG_TYPE_SLAVE_BY_EXT0, mode); + + vtg_write_reg(data->regs[VTG_MASTER], VTG_TYPE_MASTER, mode); + + /* reset slave and then master */ + if (data->regs[VTG_SLAVE]) + vtg_reset(data->regs[VTG_SLAVE]); + + vtg_reset(data->regs[VTG_MASTER]); + + /* Enable irq for the vtg vblank synchro */ + if (data->regs[VTG_SLAVE]) + vtg_enable_irq(data->regs[VTG_SLAVE]); + else + vtg_enable_irq(data->regs[VTG_MASTER]); + + if (data->regs[VTG_SLAVE]) + vtg_reg_dump(dev, data->regs[VTG_SLAVE]); + + vtg_reg_dump(dev, data->regs[VTG_MASTER]); + + return 0; +} + +int vtg_register_client(struct device *dev, struct notifier_block *nb) +{ + struct sti_vtg *vtg = dev_get_drvdata(dev); + return raw_notifier_chain_register(&vtg->notifier_list, nb); +} + +int vtg_unregister_client(struct device *dev, struct notifier_block *nb) +{ + struct sti_vtg *vtg = dev_get_drvdata(dev); + return raw_notifier_chain_unregister(&vtg->notifier_list, nb); +} + +static irqreturn_t vtg_irq_thread(int irq, void *arg) +{ + struct sti_vtg *vtg = arg; + struct sti_vtg_data *data = &vtg->data; + void __iomem *regs; + u32 status; + + if (data->regs[VTG_SLAVE]) + regs = data->regs[VTG_SLAVE]; + else + regs = data->regs[VTG_MASTER]; + + status = readl(regs + VTG_HOST_ITS); + + writel(status, regs + VTG_HOST_ITS_BCLR); + /* TODO: check why this sync bus write solves the problem which + * is that without this line, the handler is sometimes called twice, + * first with status = 1 or 2, and immediately after with status=0 + */ + /* sync bus write */ + readl(regs + VTG_HOST_ITS); + + if (status & VTG_IRQ_TOP_FIELD_MASK) { + raw_notifier_call_chain(&vtg->notifier_list, + VTG_TOP_FIELD_EVENT, &vtg->type); + } else { + raw_notifier_call_chain(&vtg->notifier_list, + VTG_BOTTOM_FIELD_EVENT, &vtg->type); + } + + return IRQ_HANDLED; +} + +static const char *vtg_get_name(struct sti_vtg *vtg) +{ + if (vtg->type == VTG_MAIN) + return "main"; + else if (vtg->type == VTG_AUX) + return "aux"; + else + return "?"; +} + +static int vtg_compositor_bind(struct device *dev, struct device *master, + void *d) +{ + struct platform_device *pdev = to_platform_device(dev); + struct device_node *np = dev->of_node; + struct sti_vtg *vtg; + struct resource *res; + char irq_name[32]; + struct sti_vtg_data *data; + int i; + int ret; + + if (!np) + return -ENXIO; + + vtg = devm_kzalloc(dev, sizeof(*vtg), GFP_KERNEL); + if (!vtg) { + DRM_ERROR("Failed to allocate VTG context\n"); + return -ENOMEM; + } + + vtg->dev = dev; + + /* populate data structure depending on compatibility */ + BUG_ON(!of_match_node(vtg_match_types, np)->data); + + memcpy(&vtg->data, of_match_node(vtg_match_types, np)->data, + sizeof(struct sti_vtg_data)); + + data = &vtg->data; + + /* Get resources */ + for (i = 0; i < data->nb_reg; i++) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + data->reg_names[i]); + if (!res) { + DRM_ERROR("Invalid resource\n"); + return -ENOMEM; + } + data->regs[i] = devm_ioremap_nocache(dev, + res->start, + resource_size(res)); + if (IS_ERR(data->regs[i])) + return PTR_ERR(data->regs[i]); + } + + vtg->irq = platform_get_irq_byname(pdev, "synchro_irq"); + if (IS_ERR_VALUE(vtg->irq)) { + DRM_ERROR("Failed to get VTG interrupt\n"); + return vtg->irq; + } + + snprintf(irq_name, sizeof(irq_name), "vsync-%s", vtg_get_name(vtg)); + + RAW_INIT_NOTIFIER_HEAD(&vtg->notifier_list); + + ret = devm_request_threaded_irq(dev, vtg->irq, NULL, vtg_irq_thread, + IRQF_ONESHOT, irq_name, vtg); + if (IS_ERR_VALUE(ret)) { + DRM_ERROR("Failed to register VTG interrupt\n"); + return ret; + } + + if (of_property_read_bool(np, "vtg-aux")) { + vtg->type = VTG_AUX; + vtg_aux = dev; + } else { + vtg->type = VTG_MAIN; + vtg_main = dev; + } + + platform_set_drvdata(pdev, vtg); + + DRM_INFO("%s VTG %s\n", __func__, vtg_get_name(vtg)); + + return 0; +} + +static void vtg_compositor_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops vtg_compositor_ops = { + .bind = vtg_compositor_bind, + .unbind = vtg_compositor_unbind, +}; + +static int vtg_compositor_probe(struct platform_device *pdev) +{ + DRM_INFO("%s\n", __func__); + return component_add(&pdev->dev, &vtg_compositor_ops); +} + +static int vtg_compositor_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vtg_compositor_ops); + return 0; +} + +static const struct of_device_id vtg_match_types[] = { + { + .compatible = "st,stih416-vtg", + .data = &stih416_vtg_data, + }, + { + .compatible = "st,stih407-vtg", + .data = &stih407_vtg_data, + }, + { /* end node */ } +}; +MODULE_DEVICE_TABLE(of, vtg_match_types); + +struct platform_driver sti_vtg_driver = { + .driver = { + .name = "sti-vtg", + .owner = THIS_MODULE, + .of_match_table = vtg_match_types, + }, + .probe = vtg_compositor_probe, + .remove = vtg_compositor_remove, +}; + +module_platform_driver(sti_vtg_driver); diff --git a/drivers/gpu/drm/sti/sti_vtg.h b/drivers/gpu/drm/sti/sti_vtg.h new file mode 100644 index 0000000..da67eb3 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_VTG_H_ +#define _STI_VTG_H_ + +#include <linux/platform_device.h> +#include <drm/drmP.h> + +extern struct device *vtg_main; +extern struct device *vtg_aux; + +int vtg_set_config(struct device *dev, const struct drm_display_mode *mode); +int vtg_register_client(struct device *dev, struct notifier_block *nb); +int vtg_unregister_client(struct device *dev, struct notifier_block *nb); + +#endif diff --git a/drivers/gpu/drm/sti/sti_vtg_utils.c b/drivers/gpu/drm/sti/sti_vtg_utils.c new file mode 100644 index 0000000..06bb4b4 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg_utils.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/platform_device.h> + +#include "sti_vtg_utils.h" +#include "sti_vtg.h" + +struct device *vtg_main; +struct device *vtg_aux; + +int sti_vtg_setconfig(int main_aux, const struct drm_display_mode *mode) +{ + if (main_aux == VTG_MAIN && vtg_main) + return vtg_set_config(vtg_main, mode); + + if (main_aux == VTG_AUX && vtg_aux) + return vtg_set_config(vtg_aux, mode); + + return 1; +} + +int sti_vtg_register_client(int main_aux, struct notifier_block *nb) +{ + if (main_aux == VTG_MAIN && vtg_main) + return vtg_register_client(vtg_main, nb); + + if (main_aux == VTG_AUX && vtg_aux) + return vtg_register_client(vtg_aux, nb); + + return 1; +} + +int sti_vtg_unregister_client(int main_aux, struct notifier_block *nb) +{ + if (main_aux == VTG_MAIN && vtg_main) + return vtg_unregister_client(vtg_main, nb); + + if (main_aux == VTG_AUX && vtg_aux) + return vtg_unregister_client(vtg_aux, nb); + + return 1; +} + +/* + * Active Front Sync Back Active + * Region Porch Porch Region + * <---------------><-------->0<---------><--------><-----------------> + * + * ///////////////| | ///////////////| + * /////////////// | | /////////////// | + * /////////////// |......... ..........|/////////////// | + * 0___________ x/ymin x/ymax + * + * <--[hv]display--> <--[hv]display--> + * <--[hv]sync_start---------> <--[hv]sync_start- + * <--[hv]sync_end-----------------------> <--[hv]sync_end--- + * <--[hv]total------------------------------------> <--[hv]total------ + */ + +/* + * sti_vtg_get_line_number + * + * @mode: display mode to be used + * @y: line + * + * Return the line number according to the display mode taking + * into account the Sync and Back Porch information. + * Video frame line numbers start at 1, y starts at 0. + * In interlaced modes the start line is the field line number of the odd + * field, but y is still defined as a progressive frame. + */ +u32 sti_vtg_get_line_number(struct drm_display_mode mode, int y) +{ + int start_line = mode.vtotal - mode.vsync_start + 1; + + if (mode.flags & DRM_MODE_FLAG_INTERLACE) + start_line *= 2; + + return start_line + y; +} + +/* + * sti_vtg_get_pixel_number + * + * @mode: display mode to be used + * @x: row + * + * Return the pixel number according to the display mode taking + * into account the Sync and Back Porch information. + * Pixels are counted from 0. + */ +u32 sti_vtg_get_pixel_number(struct drm_display_mode mode, int x) +{ + return mode.htotal - mode.hsync_start + x; +} diff --git a/drivers/gpu/drm/sti/sti_vtg_utils.h b/drivers/gpu/drm/sti/sti_vtg_utils.h new file mode 100644 index 0000000..fea2852 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg_utils.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * 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_VTG_UTILS_H_ +#define _STI_VTG_UTILS_H_ + +#include <drm/drmP.h> + +#define WAIT_NEXT_VSYNC_MS 50 /*ms*/ + +#define VTG_MAIN 0 +#define VTG_AUX 1 + +#define VTG_TOP_FIELD_EVENT 1 +#define VTG_BOTTOM_FIELD_EVENT 2 + +int sti_vtg_setconfig(int main_aux, const struct drm_display_mode *mode); +int sti_vtg_register_client(int main_aux, struct notifier_block *nb); +int sti_vtg_unregister_client(int main_aux, struct notifier_block *nb); + +u32 sti_vtg_get_line_number(struct drm_display_mode mode, int y); +u32 sti_vtg_get_pixel_number(struct drm_display_mode mode, int x); + +#endif
Video Trafic Advance Communication Rx and Tx drivers are designed for inter-die communication.
Both Tx and Rx must share the same configuration to communicate that is why vtac_mode[] is shared in sti_vtac_utils.h.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/Kconfig | 6 ++ drivers/gpu/drm/sti/Makefile | 1 + drivers/gpu/drm/sti/sti_vtac_rx.c | 167 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_vtac_tx.c | 182 +++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_vtac_utils.h | 52 ++++++++++ 5 files changed, 408 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_vtac_rx.c create mode 100644 drivers/gpu/drm/sti/sti_vtac_tx.c create mode 100644 drivers/gpu/drm/sti/sti_vtac_utils.h
diff --git a/drivers/gpu/drm/sti/Kconfig b/drivers/gpu/drm/sti/Kconfig index 3fff278..cdd5119 100644 --- a/drivers/gpu/drm/sti/Kconfig +++ b/drivers/gpu/drm/sti/Kconfig @@ -4,6 +4,12 @@ config DRM_STI help Choose this option to enable DRM on STM stiH41x chipset
+config VTAC_STI + tristate "Video Trafic Advance Communication Rx and Tx for STMicroelectronics SoC stiH41x Series" + depends on DRM_STI + help + Choose this option to enable VTAC on STM stiH41x chipset + config VTG_STI bool "Video Timing Generator for STMicroelectronics SoC stiH41x Series" depends on DRM_STI diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 33216e1..79fdcb6 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -1,3 +1,4 @@ ccflags-y := -Iinclude/drm
+obj-$(CONFIG_VTAC_STI) += sti_vtac_tx.o sti_vtac_rx.o obj-$(CONFIG_VTG_STI) += sti_vtg.o sti_vtg_utils.o diff --git a/drivers/gpu/drm/sti/sti_vtac_rx.c b/drivers/gpu/drm/sti/sti_vtac_rx.c new file mode 100644 index 0000000..5eefd94 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtac_rx.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include <drm/drmP.h> + +#include "sti_vtac_utils.h" + +#define VTAC_RX_CONFIG 0x00 +#define VTAC_RX_FIFO_CONFIG 0x04 + +#define VTAC_SW_RST_AUTOC 0x02 +#define VTAC_FIFO_CONFIG_VAL 0x04 + +/* + * VTAC RX structure + * + * @dev: pointer to device structure + * @regs: ioremapped regsiters + * @clk: clock + * @type: main or aux device + */ +struct sti_vtac_rx { + struct device *dev; + void __iomem *regs; + struct clk *clk; + int type; +}; + +static void sti_vtac_rx_reg_dump(struct sti_vtac_rx *vtac_rx) +{ + dev_dbg(vtac_rx->dev, "vtac_rx->regs %p\n", vtac_rx->regs); + dev_dbg(vtac_rx->dev, "VTAC_RX_CONFIG 0x%x\n", + readl(vtac_rx->regs + VTAC_RX_CONFIG)); + dev_dbg(vtac_rx->dev, "VTAC_RX_FIFO_CONFIG 0x%x\n", + readl(vtac_rx->regs + VTAC_RX_FIFO_CONFIG)); +} + +static void sti_vtac_rx_set_config(struct sti_vtac_rx *vtac_rx) +{ + int i; + u32 rx_config = EVEN_PARITY | ODD_PARITY | SW_RST_AUTOC | ENABLE; + + /* Enable VTAC clock */ + if (clk_prepare_enable(vtac_rx->clk)) + DRM_ERROR("Failed to prepare/enable vtac_rx clock.\n"); + + for (i = 0; i < ARRAY_SIZE(vtac_modes); i++) { + if (vtac_modes[i].type == vtac_rx->type) { + writel(VTAC_FIFO_CONFIG_VAL, + vtac_rx->regs + VTAC_RX_FIFO_CONFIG); + rx_config |= vtac_modes[i].vid_in_width << 4; + rx_config |= vtac_modes[i].phyts_width << 16; + rx_config |= vtac_modes[i].phyts_per_pixel << 23; + rx_config |= VTAC_SW_RST_AUTOC; + writel(rx_config, vtac_rx->regs + VTAC_RX_CONFIG); + } + } +} + +static int vtac_rx_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct device_node *np = dev->of_node; + struct sti_vtac_rx *vtac_rx; + struct resource *res; + + vtac_rx = devm_kzalloc(dev, sizeof(*vtac_rx), GFP_KERNEL); + if (!vtac_rx) { + DRM_ERROR("Failed to allocate VTAC RX context\n"); + return -ENOMEM; + } + vtac_rx->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + DRM_ERROR("Invalid resource\n"); + return -ENOMEM; + } + vtac_rx->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(vtac_rx->regs)) + return PTR_ERR(vtac_rx->regs); + + vtac_rx->type = VTAC_MAIN; + if (np) + if (of_property_read_bool(np, "vtac-rx-aux")) + vtac_rx->type = VTAC_AUX; + + if (vtac_rx->type == VTAC_MAIN) { + vtac_rx->clk = devm_clk_get(dev, "vtac_main_phy"); + if (IS_ERR(vtac_rx->clk)) { + DRM_ERROR("Cannot get vtac_main_phy clock\n"); + return PTR_ERR(vtac_rx->clk); + } + } else { + vtac_rx->clk = devm_clk_get(dev, "vtac_aux_phy"); + if (IS_ERR(vtac_rx->clk)) { + DRM_ERROR("Cannot get vtac_aux_phy clock\n"); + return PTR_ERR(vtac_rx->clk); + } + } + + sti_vtac_rx_set_config(vtac_rx); + sti_vtac_rx_reg_dump(vtac_rx); + + platform_set_drvdata(pdev, vtac_rx); + DRM_INFO("%s VTAC RX %s\n", __func__, + vtac_rx->type == VTAC_MAIN ? "main" : "aux"); + return 0; +} + +static void vtac_rx_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops vtac_rx_ops = { + .bind = vtac_rx_bind, + .unbind = vtac_rx_unbind, +}; + +static int sti_vtac_rx_probe(struct platform_device *pdev) +{ + DRM_INFO("%s\n", __func__); + return component_add(&pdev->dev, &vtac_rx_ops); +} + +static int sti_vtac_rx_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vtac_rx_ops); + return 0; +} + +static struct of_device_id vtac_rx_match_types[] = { + { + .compatible = "st,stih416-vtac-rx", + }, { + /* end node */ + } +}; + +MODULE_DEVICE_TABLE(of, vtac_rx_match_types); + +struct platform_driver sti_vtac_rx_driver = { + .driver = { + .name = "sti-vtac-rx", + .owner = THIS_MODULE, + .of_match_table = vtac_rx_match_types, + }, + .probe = sti_vtac_rx_probe, + .remove = sti_vtac_rx_remove, +}; + +module_platform_driver(sti_vtac_rx_driver); + +MODULE_AUTHOR("Benjamin Gaignard benjamin.gaignard@st.com"); +MODULE_DESCRIPTION("STMicroelectronics SoC VTAC_RX driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sti/sti_vtac_tx.c b/drivers/gpu/drm/sti/sti_vtac_tx.c new file mode 100644 index 0000000..6f1bd03 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtac_tx.c @@ -0,0 +1,182 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <drm/drmP.h> + +#include "sti_vtac_utils.h" + +#define VTAC_TX_CONFIG 0x00 + +#define VTAC_SW_RST_AUTOC 0x02 + +#define VTAC_TX_PHY_ENABLE_CLK_PHY (0x01 << 0) +#define VTAC_TX_PHY_PROG_N3 (0x04 << 7) +#define VTAC_TX_PHY_ENABLE_CLK_DLL (0x01 << 1) +#define VTAC_TX_PHY_RST_N_DLL_SWITCH (0x01 << 4) +#define VTAC_TX_PHY_PLL_NOT_OSC_MODE (0x01 << 3) + +/* + * VTAC TX structure + * + * @dev: pointer to device structure + * @regs: ioremapped regsiters + * @clk: clock + * @type: main or aux device + */ +struct sti_vtac_tx { + struct device *dev; + void __iomem *tx_regs; + void __iomem *phy_regs; + struct clk *clk; + int type; +}; + +static void sti_vtac_tx_set_config(struct sti_vtac_tx *vtac_tx) +{ + int i; + u32 phy_config; + u32 tx_config = EVEN_PARITY | ODD_PARITY | SW_RST_AUTOC | ENABLE; + + /* Enable VTAC clock */ + if (clk_prepare_enable(vtac_tx->clk)) + DRM_ERROR("Failed to prepare/enable vtac_tx clock.\n"); + + /* Configure vtac phy */ + phy_config = 0x00000000; + writel(phy_config, vtac_tx->phy_regs + 0x828); /* SYS_CFG8522 */ + phy_config = VTAC_TX_PHY_ENABLE_CLK_PHY; + writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */ + phy_config = readl(vtac_tx->phy_regs + 0x824); + phy_config |= VTAC_TX_PHY_PROG_N3; + writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */ + phy_config = readl(vtac_tx->phy_regs + 0x824); + phy_config |= VTAC_TX_PHY_ENABLE_CLK_DLL; + writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */ + phy_config = readl(vtac_tx->phy_regs + 0x824); + phy_config |= VTAC_TX_PHY_RST_N_DLL_SWITCH; + writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */ + phy_config = readl(vtac_tx->phy_regs + 0x824); + phy_config |= VTAC_TX_PHY_PLL_NOT_OSC_MODE; + writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */ + + /* Configure vtac tx */ + for (i = 0; i < ARRAY_SIZE(vtac_modes); i++) { + if (vtac_modes[i].type == vtac_tx->type) { + tx_config |= vtac_modes[i].vid_in_width << 4; + tx_config |= vtac_modes[i].phyts_width << 16; + tx_config |= vtac_modes[i].phyts_per_pixel << 23; + tx_config |= VTAC_SW_RST_AUTOC; + writel(tx_config, vtac_tx->tx_regs + VTAC_TX_CONFIG); + } + } +} + +static int vtac_tx_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct device_node *np = dev->of_node; + struct sti_vtac_tx *vtac_tx; + struct resource *res; + + DRM_INFO("%s\n", __func__); + + vtac_tx = devm_kzalloc(dev, sizeof(*vtac_tx), GFP_KERNEL); + if (!vtac_tx) { + DRM_ERROR("Failed to allocate VTAC TX context\n"); + return -ENOMEM; + } + vtac_tx->dev = dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vtac-tx"); + if (!res) { + DRM_ERROR("Invalid resource 'vtac-tx'\n"); + return -ENOMEM; + } + vtac_tx->tx_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(vtac_tx->tx_regs)) + return PTR_ERR(vtac_tx->tx_regs); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vtac-phy"); + if (!res) { + DRM_ERROR("Invalid resource 'vtac-phy'\n"); + return -ENOMEM; + } + vtac_tx->phy_regs = devm_ioremap_nocache(dev, res->start, + resource_size(res)); + if (IS_ERR(vtac_tx->phy_regs)) + return PTR_ERR(vtac_tx->phy_regs); + + vtac_tx->type = VTAC_MAIN; + if (np) + if (of_property_read_bool(np, "vtac-tx-aux")) + vtac_tx->type = VTAC_AUX; + + vtac_tx->clk = devm_clk_get(dev, "vtac_tx_phy"); + if (IS_ERR(vtac_tx->clk)) { + DRM_ERROR("Cannot get vtac_tx_phy clock\n"); + return PTR_ERR(vtac_tx->clk); + } + + sti_vtac_tx_set_config(vtac_tx); + + platform_set_drvdata(pdev, vtac_tx); + DRM_INFO("%s VTAC TX %s\n", __func__, + vtac_tx->type == VTAC_MAIN ? "main" : "aux"); + return 0; +} + +static void vtac_tx_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops vtac_tx_ops = { + .bind = vtac_tx_bind, + .unbind = vtac_tx_unbind, +}; + +static int sti_vtac_tx_probe(struct platform_device *pdev) +{ + DRM_INFO("%s\n", __func__); + return component_add(&pdev->dev, &vtac_tx_ops); +} + +static int sti_vtac_tx_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vtac_tx_ops); + return 0; +} + +static struct of_device_id vtac_tx_match_types[] = { + { + .compatible = "st,stih416-vtac-tx", + }, { + /* end node */ + } +}; +MODULE_DEVICE_TABLE(of, vtac_tx_match_types); + +struct platform_driver sti_vtac_tx_driver = { + .driver = { + .name = "sti-vtac-tx", + .owner = THIS_MODULE, + .of_match_table = vtac_tx_match_types, + }, + .probe = sti_vtac_tx_probe, + .remove = sti_vtac_tx_remove, +}; + +module_platform_driver(sti_vtac_tx_driver); + +MODULE_AUTHOR("Benjamin Gaignard benjamin.gaignard@st.com"); +MODULE_DESCRIPTION("STMicroelectronics SoC VTAC_TX driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sti/sti_vtac_utils.h b/drivers/gpu/drm/sti/sti_vtac_utils.h new file mode 100644 index 0000000..ce549de --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtac_utils.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_VTAC_UTILS_H_ +#define _STI_VTAC_UTILS_H_ + +#define VTAC_MAIN 0x8000 +#define VTAC_AUX 0x4000 + +/* Number of phyts per pixel */ +#define VTAC_2_5_PPP 0x0005 +#define VTAC_3_PPP 0x0006 +#define VTAC_4_PPP 0x0008 +#define VTAC_5_PPP 0x000A +#define VTAC_6_PPP 0x000C +#define VTAC_13_PPP 0x001A +#define VTAC_14_PPP 0x001C +#define VTAC_15_PPP 0x001E +#define VTAC_16_PPP 0x0020 +#define VTAC_17_PPP 0x0022 +#define VTAC_18_PPP 0x0024 + +/* enable bits */ +#define EVEN_PARITY (1 << 13) +#define ODD_PARITY (1 << 12) +#define SW_RST_AUTOC (1 << 1) +#define ENABLE (1 << 0) + +/* + * VTAC mode structure + * + * @type: main, aux or dvo device + * @vid_in_width: Video Data Resolution + * @phyts_width: Width of phyt buses(phyt low and phyt high). + * @phyts_per_pixel: Number of phyts sent per pixel + */ +struct sti_vtac_mode { + int type; + int vid_in_width; + int phyts_width; + int phyts_per_pixel; +}; + +static struct sti_vtac_mode vtac_modes[] = { + {VTAC_MAIN, 0x2, 0x2, VTAC_5_PPP}, /* Main RGB 12 */ + {VTAC_AUX, 0x1, 0x0, VTAC_17_PPP}, /* Aux 10 */ +}; + +#endif
Add driver for HDMI ouput
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/Makefile | 5 + drivers/gpu/drm/sti/sti_hdmi.c | 530 +++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi.h | 195 +++++++++++ drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c | 398 ++++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c | 224 ++++++++++++ 5 files changed, 1352 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_hdmi.c create mode 100644 drivers/gpu/drm/sti/sti_hdmi.h create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 79fdcb6..5295fc7 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -1,4 +1,9 @@ ccflags-y := -Iinclude/drm
+stidrm-y := sti_hdmi.o \ + sti_hdmi_tx3g0c55phy.o \ + sti_hdmi_tx3g4c28phy.o + obj-$(CONFIG_VTAC_STI) += sti_vtac_tx.o sti_vtac_rx.o obj-$(CONFIG_VTG_STI) += sti_vtg.o sti_vtg_utils.o +obj-$(CONFIG_DRM_STI) += stidrm.o \ No newline at end of file diff --git a/drivers/gpu/drm/sti/sti_hdmi.c b/drivers/gpu/drm/sti/sti_hdmi.c new file mode 100644 index 0000000..aa77510 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hdmi.c @@ -0,0 +1,530 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Vincent Abriou vincent.abriou@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/hdmi.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> + +#include "sti_hdmi.h" +#include "sti_vtg_utils.h" + +/* Reference to the hdmi device */ +struct device *hdmi_dev; + +/* + * Helper to write bit field + * + * @addr: register to update + * @val: value to write + * @mask: bit field mask to use + */ +static inline void hdmi_reg_writemask(void __iomem *addr, u32 val, u32 mask) +{ + u32 old = readl(addr); + + val = (val & mask) | (old & ~mask); + writel(val, addr); +} + +/* + * HDMI interrupt handler + * + * @irq: irq number + * @arg: connector structure + */ +static irqreturn_t hdmi_irq_thread(int irq, void *arg) +{ + struct sti_hdmi *hdmi = arg; + u32 status; + + /* read interrupt status */ + status = readl(hdmi->regs + HDMI_INT_STA); + + /* PLL lock interrupt */ + if (status & HDMI_INT_DLL_LCK) { + hdmi->event_received = true; + wake_up_interruptible(&hdmi->wait_event); + } + + /* Hot plug detection */ + if (status & HDMI_INT_HOT_PLUG) { + hdmi->hpd = gpio_get_value(hdmi->hpd_gpio); + if (hdmi->drm_dev) + drm_helper_hpd_irq_event(hdmi->drm_dev); + } + + /* Sw reset completed */ + if (status & HDMI_INT_SW_RST) { + hdmi->event_received = true; + wake_up_interruptible(&hdmi->wait_event); + } + + /* clear interrupt status */ + writel(status, hdmi->regs + HDMI_INT_CLR); + + /* TODO: check why this sync bus write solves the problem which + * is that without this line, the handler is sometimes called twice + */ + /* sync bus write */ + readl(hdmi->regs + HDMI_INT_STA); + + return IRQ_HANDLED; +} + +/* + * Start hdmi phy interface + * + * @hdmi: pointer on the hdmi internal structure + * + * Return -1 if error occurs + */ +static int hdmi_phy_start(struct sti_hdmi *hdmi) +{ + DRM_DEBUG_DRIVER("\n"); + + if (hdmi->tx3g0c55phy) + return sti_hdmi_tx3g0c55phy_start(hdmi); + + return sti_hdmi_tx3g4c28phy_start(hdmi); +} + +/* + * Stop hdmi phy interface + * + * @hdmi: pointer on the hdmi internal structure + */ +static void hdmi_phy_stop(struct sti_hdmi *hdmi) +{ + DRM_DEBUG_DRIVER("\n"); + + if (hdmi->tx3g0c55phy) + sti_hdmi_tx3g0c55phy_stop(hdmi); + else + sti_hdmi_tx3g4c28phy_stop(hdmi); +} + +/* + * Set hdmi active area depending on the drm display mode selected + * + * @hdmi: pointer on the hdmi internal structure + */ +static void hdmi_active_area(struct sti_hdmi *hdmi) +{ + u32 xmin, xmax; + u32 ymin, ymax; + + DRM_DEBUG_DRIVER("\n"); + + /* + * Active Front Sync Back Active + * Region Porch Porch Region + * <---------------><-------->0<---------><--------><-----------------> + * + * ///////////////| | ///////////////| + * /////////////// | | /////////////// | + * /////////////// |......... ..........|/////////////// | + * 0___________ x/ymin x/ymax + * + * <--[hv]display--> <--[hv]display--> + * <--[hv]sync_start---------> <--[hv]sync_start- + * <--[hv]sync_end-----------------------> <--[hv]sync_end--- + * <--[hv]total------------------------------------> <--[hv]total------ + */ + + xmin = sti_vtg_get_pixel_number(hdmi->mode, 0); + xmax = sti_vtg_get_pixel_number(hdmi->mode, hdmi->mode.hdisplay - 1); + ymin = sti_vtg_get_line_number(hdmi->mode, 0); + ymax = sti_vtg_get_line_number(hdmi->mode, hdmi->mode.vdisplay - 1); + + writel(xmin, hdmi->regs + HDMI_ACTIVE_VID_XMIN); + writel(xmax, hdmi->regs + HDMI_ACTIVE_VID_XMAX); + writel(ymin, hdmi->regs + HDMI_ACTIVE_VID_YMIN); + writel(ymax, hdmi->regs + HDMI_ACTIVE_VID_YMAX); + + DRM_DEBUG_DRIVER("xmin=%d xmax=%d ymin=%d ymax=%d\n", + xmin, xmax, ymin, ymax); +} + +/* + * Overall hdmi configuration + * + * @hdmi: pointer on the hdmi internal structure + */ +static void hdmi_config(struct sti_hdmi *hdmi) +{ + u32 val; + u32 mask; + + DRM_DEBUG_DRIVER("\n"); + + /* Clear overrun and underrun fifo */ + mask = HDMI_CFG_FIFO_OVERRUN_CLR | HDMI_CFG_FIFO_UNDERRUN_CLR; + val = HDMI_CFG_FIFO_OVERRUN_CLR | HDMI_CFG_FIFO_UNDERRUN_CLR; + + /* Enable HDMI mode not DVI */ + mask |= HDMI_CFG_HDMI_NOT_DVI | HDMI_CFG_ESS_NOT_OESS; + val |= HDMI_CFG_HDMI_NOT_DVI | HDMI_CFG_ESS_NOT_OESS; + + /* Enable sink term detection */ + mask |= HDMI_CFG_SINK_TERM_DET_EN; + val |= HDMI_CFG_SINK_TERM_DET_EN; + + /* Set Hsync polarity */ + if ((hdmi->mode.flags && DRM_MODE_FLAG_NHSYNC) + == DRM_MODE_FLAG_NHSYNC) { + DRM_DEBUG_DRIVER("H Sync Negative\n"); + mask |= HDMI_CFG_H_SYNC_POL_NEG; + val |= HDMI_CFG_H_SYNC_POL_NEG; + } + + /* Set Vsync polarity */ + if ((hdmi->mode.flags && DRM_MODE_FLAG_NVSYNC) + == DRM_MODE_FLAG_NVSYNC) { + DRM_DEBUG_DRIVER("V Sync Negative\n"); + mask |= HDMI_CFG_V_SYNC_POL_NEG; + val |= HDMI_CFG_V_SYNC_POL_NEG; + } + + /* Enable HDMI */ + mask |= HDMI_CFG_DEVICE_EN; + val |= HDMI_CFG_DEVICE_EN; + + hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask); +} + +/* + * Prepare and configure the AVI infoframe + * + * AVI infoframe are transmitted at least once per two video field and + * contains information about HDMI transmission mode such as color space, + * colorimetry, ... + * + * @hdmi: pointer on the hdmi internal structure + * + * Return negative value if error occurs + */ +static int hdmi_avi_infoframe_config(struct sti_hdmi *hdmi) +{ + struct drm_display_mode *mode = &hdmi->mode; + struct hdmi_avi_infoframe infoframe; + u8 buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE]; + u8 *frame = buffer + HDMI_INFOFRAME_HEADER_SIZE - 1; + u32 val; + u32 mask; + int ret; + + DRM_DEBUG_DRIVER("\n"); + + ret = drm_hdmi_avi_infoframe_from_display_mode(&infoframe, mode); + if (ret < 0) { + DRM_ERROR("failed to setup AVI infoframe: %d\n", ret); + return ret; + } + + /* TODO: remove static infoframe configuration */ + infoframe.colorspace = HDMI_COLORSPACE_RGB; + infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT; + infoframe.colorimetry = HDMI_COLORIMETRY_NONE; + infoframe.pixel_repeat = 0; + + ret = hdmi_avi_infoframe_pack(&infoframe, buffer, sizeof(buffer)); + if (ret < 0) { + DRM_ERROR("failed to pack AVI infoframe: %d\n", ret); + return ret; + } + + /* Disable transmission slot for AVI infoframe */ + val = HDMI_IFRAME_DISABLED; + mask = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, HDMI_IFRAME_SLOT_AVI); + hdmi_reg_writemask(hdmi->regs + HDMI_SW_DI_CFG, val, mask); + + /* Infoframe header */ + val = buffer[0x0]; + val |= buffer[0x1] << 8; + val |= buffer[0x2] << 16; + writel(val, hdmi->regs + HDMI_SW_DI_N_HEAD_WORD(HDMI_IFRAME_SLOT_AVI)); + /* Infoframe packet bytes */ + val = frame[0x0]; + val |= frame[0x1] << 8; + val |= frame[0x2] << 16; + val |= frame[0x3] << 24; + writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD0(HDMI_IFRAME_SLOT_AVI)); + val = frame[0x4]; + val |= frame[0x5] << 8; + val |= frame[0x6] << 16; + val |= frame[0x7] << 24; + writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD1(HDMI_IFRAME_SLOT_AVI)); + val = frame[0x8]; + val |= frame[0x9] << 8; + val |= frame[0xA] << 16; + val |= frame[0xB] << 24; + writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD2(HDMI_IFRAME_SLOT_AVI)); + val = frame[0xC]; + val |= frame[0xD] << 8; + writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD3(HDMI_IFRAME_SLOT_AVI)); + + /* Enable transmission slot for AVI infoframe */ + /* According to the hdmi specification, AVI infoframe should be + * transmitted at least once per two video fields */ + val = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_FIELD, HDMI_IFRAME_SLOT_AVI); + mask = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, HDMI_IFRAME_SLOT_AVI); + hdmi_reg_writemask(hdmi->regs + HDMI_SW_DI_CFG, val, mask); + + return 0; +} + +/* + * Software reset of the hdmi subsystem + * + * @hdmi: pointer on the hdmi internal structure + * + * Return -1 if error occurs + */ +#define HDMI_TIMEOUT_SWRESET 100 /*milliseconds */ +static int hdmi_swreset(struct sti_hdmi *hdmi) +{ + u32 val; + u32 mask; + + DRM_DEBUG_DRIVER("\n"); + + /* Enable hdmi_audio clock only during hdmi reset */ + if (clk_prepare_enable(hdmi->clk_audio)) + DRM_INFO("Failed to prepare/enable hdmi_audio clk\n"); + + /* Sw reset */ + mask = HDMI_CFG_SW_RST_EN; + val = HDMI_CFG_SW_RST_EN; + + hdmi->event_received = false; + hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask); + + /* Wait reset completed */ + wait_event_interruptible_timeout(hdmi->wait_event, + hdmi->event_received == true, + msecs_to_jiffies + (HDMI_TIMEOUT_SWRESET)); + + /* + * HDMI_STA_SW_RST bit is set to '1' when SW_RST bit in HDMI_CFG is + * set to '1' and clk_audio is running. + */ + if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_SW_RST) == 0) + DRM_INFO("Warning: HDMI sw reset timeout occurs\n"); + + mask = HDMI_CFG_SW_RST_EN; + val = ~HDMI_CFG_SW_RST_EN; + hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask); + + /* Disable hdmi_audio clock. Not used anymore for drm purpose. */ + clk_disable_unprepare(hdmi->clk_audio); + + return 0; +} + +/* + * Attach the I2C ddc client to allow hdmi i2c communication + * + * @ddc: i2c client + */ +static struct i2c_client *hdmi_ddc; +void sti_hdmi_attach_ddc_client(struct i2c_client *ddc) +{ + DRM_DEBUG_DRIVER("\n"); + + if (ddc) + hdmi_ddc = ddc; +} + +/* + * Get modes from edid + * + * @drm_connector: pointer on the drm connector + */ +static int sti_hdmi_get_modes(struct drm_connector *drm_connector) +{ + struct edid *edid; + int count; + + DRM_DEBUG_DRIVER("\n"); + + if ((!hdmi_ddc) || (!hdmi_ddc->adapter)) + goto fail; + + edid = drm_get_edid(drm_connector, hdmi_ddc->adapter); + if (!edid) + goto fail; + + count = drm_add_edid_modes(drm_connector, edid); + if (count) + drm_mode_connector_update_edid_property(drm_connector, edid); + else + DRM_ERROR("Add edid modes failed\n"); + + kfree(edid); + return count; + +fail: + DRM_ERROR("Can not read HDMI EDID\n"); + return -1; +} + +static int sti_hdmi_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sti_hdmi *hdmi; + struct device_node *np = dev->of_node; + struct resource *res; + int ret; + + DRM_INFO("%s\n", __func__); + + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) { + DRM_ERROR("Failed to allocate memory for hdmi\n"); + return -ENOMEM; + } + + hdmi->dev = pdev->dev; + + /* Get resources */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hdmi-reg"); + if (!res) { + DRM_ERROR("Invalid hdmi resource\n"); + return -ENOMEM; + } + hdmi->regs = devm_ioremap_nocache(dev, res->start, resource_size(res)); + if (IS_ERR(hdmi->regs)) + return PTR_ERR(hdmi->regs); + + if (of_device_is_compatible(np, "st,stih416-hdmi")) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "syscfg"); + if (!res) { + DRM_ERROR("Invalid syscfg resource\n"); + return -ENOMEM; + } + hdmi->syscfg = devm_ioremap_nocache(dev, res->start, + resource_size(res)); + if (IS_ERR(hdmi->syscfg)) + return PTR_ERR(hdmi->syscfg); + + hdmi->tx3g0c55phy = true; + } + + /* Get clock resources */ + hdmi->clk_pix = devm_clk_get(dev, "hdmi_pix"); + if (IS_ERR(hdmi->clk_pix)) { + DRM_ERROR("Cannot get hdmi_pix clock\n"); + return PTR_ERR(hdmi->clk_pix); + } + + hdmi->clk_tmds = devm_clk_get(dev, "hdmi_tmds"); + if (IS_ERR(hdmi->clk_tmds)) { + DRM_ERROR("Cannot get hdmi_tmds clock\n"); + return PTR_ERR(hdmi->clk_tmds); + } + + hdmi->clk_phy = devm_clk_get(dev, "hdmi_phy"); + if (IS_ERR(hdmi->clk_phy)) { + DRM_ERROR("Cannot get hdmi_phy clock\n"); + return PTR_ERR(hdmi->clk_phy); + } + + hdmi->clk_audio = devm_clk_get(dev, "hdmi_audio"); + if (IS_ERR(hdmi->clk_audio)) { + DRM_ERROR("Cannot get hdmi_audio clock\n"); + return PTR_ERR(hdmi->clk_audio); + } + + hdmi->hpd_gpio = of_get_named_gpio(np, "hdmi,hpd-gpio", 0); + if (hdmi->hpd_gpio < 0) { + DRM_ERROR("Failed to get hdmi hpd-gpio\n"); + return -EIO; + } + + hdmi->hpd = gpio_get_value(hdmi->hpd_gpio); + + init_waitqueue_head(&hdmi->wait_event); + + /* Get irq ressources */ + hdmi->irq = platform_get_irq_byname(pdev, "hdmi_irq"); + + ret = devm_request_threaded_irq(dev, hdmi->irq, NULL, + hdmi_irq_thread, IRQF_ONESHOT, + "hdmi_irq", hdmi); + if (ret) { + DRM_ERROR("Failed to register hdmi interrupt\n"); + return ret; + } + + /* Get reset resources */ + hdmi->reset = devm_reset_control_get(dev, "hdmi"); + /* Take hdmi out of reset */ + if (!IS_ERR(hdmi->reset)) + reset_control_deassert(hdmi->reset); + + hdmi_dev = &hdmi->dev; + + platform_set_drvdata(pdev, hdmi); + + return 0; +} + +static void sti_hdmi_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops sti_hdmi_ops = { + .bind = sti_hdmi_bind, + .unbind = sti_hdmi_unbind, +}; + +static int sti_hdmi_probe(struct platform_device *pdev) +{ + DRM_INFO("%s\n", __func__); + return component_add(&pdev->dev, &sti_hdmi_ops); +} + +static int sti_hdmi_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sti_hdmi_ops); + return 0; +} + +static struct of_device_id hdmi_match_types[] = { + { + .compatible = "st,stih416-hdmi", + }, + { + .compatible = "st,stih407-hdmi", + }, + { /* end node */ } +}; +MODULE_DEVICE_TABLE(of, hdmi_match_types); + +struct platform_driver sti_hdmi_driver = { + .driver = { + .name = "sti-hdmi", + .owner = THIS_MODULE, + .of_match_table = hdmi_match_types, + }, + .probe = sti_hdmi_probe, + .remove = sti_hdmi_remove, +}; +module_platform_driver(sti_hdmi_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sti/sti_hdmi.h b/drivers/gpu/drm/sti/sti_hdmi.h new file mode 100644 index 0000000..c14c683 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hdmi.h @@ -0,0 +1,195 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: 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> + +/* HDMI v2.9 macro cell */ +#define HDMI_CFG 0x0000 +#define HDMI_INT_EN 0x0004 +#define HDMI_INT_STA 0x0008 +#define HDMI_INT_CLR 0x000C +#define HDMI_STA 0x0010 +#define HDMI_ACTIVE_VID_XMIN 0x0100 +#define HDMI_ACTIVE_VID_XMAX 0x0104 +#define HDMI_ACTIVE_VID_YMIN 0x0108 +#define HDMI_ACTIVE_VID_YMAX 0x010C +#define HDMI_DFLT_CHL0_DAT 0x0110 +#define HDMI_DFLT_CHL1_DAT 0x0114 +#define HDMI_DFLT_CHL2_DAT 0x0118 +#define HDMI_SW_DI_1_HEAD_WORD 0x0210 +#define HDMI_SW_DI_1_PKT_WORD0 0x0214 +#define HDMI_SW_DI_1_PKT_WORD1 0x0218 +#define HDMI_SW_DI_1_PKT_WORD2 0x021C +#define HDMI_SW_DI_1_PKT_WORD3 0x0220 +#define HDMI_SW_DI_1_PKT_WORD4 0x0224 +#define HDMI_SW_DI_1_PKT_WORD5 0x0228 +#define HDMI_SW_DI_1_PKT_WORD6 0x022C +#define HDMI_SW_DI_CFG 0x0230 + +#define HDMI_IFRAME_SLOT_AVI 1 + +#define XCAT(prefix, x, suffix) prefix ## x ## suffix +#define HDMI_SW_DI_N_HEAD_WORD(x) XCAT(HDMI_SW_DI_, x, _HEAD_WORD) +#define HDMI_SW_DI_N_PKT_WORD0(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD0) +#define HDMI_SW_DI_N_PKT_WORD1(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD1) +#define HDMI_SW_DI_N_PKT_WORD2(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD2) +#define HDMI_SW_DI_N_PKT_WORD3(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD3) +#define HDMI_SW_DI_N_PKT_WORD4(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD4) +#define HDMI_SW_DI_N_PKT_WORD5(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD5) +#define HDMI_SW_DI_N_PKT_WORD6(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD6) + +#define HDMI_IFRAME_DISABLED 0x0 +#define HDMI_IFRAME_SINGLE_SHOT 0x1 +#define HDMI_IFRAME_FIELD 0x2 +#define HDMI_IFRAME_FRAME 0x3 +#define HDMI_IFRAME_MASK 0x3 +#define HDMI_IFRAME_CFG_DI_N(x, n) ((x) << ((n-1)*4)) /* n from 1 to 6 */ + +#define HDMI_CFG_DEVICE_EN_SHIFT 0 +#define HDMI_CFG_DEVICE_EN (1 << HDMI_CFG_DEVICE_EN_SHIFT) +#define HDMI_CFG_HDMI_NOT_DVI_SHIFT 1 +#define HDMI_CFG_HDMI_NOT_DVI (1 << HDMI_CFG_HDMI_NOT_DVI_SHIFT) +#define HDMI_CFG_HDCP_EN_SHIFT 2 +#define HDMI_CFG_HDCP_EN (1 << HDMI_CFG_HDCP_EN_SHIFT) +#define HDMI_CFG_ESS_NOT_OESS_SHIFT 3 +#define HDMI_CFG_ESS_NOT_OESS (1 << HDMI_CFG_ESS_NOT_OESS_SHIFT) +#define HDMI_CFG_H_SYNC_POL_NEG_SHIFT 4 +#define HDMI_CFG_H_SYNC_POL_NEG (1 << HDMI_CFG_H_SYNC_POL_NEG_SHIFT) +#define HDMI_CFG_SINK_TERM_DET_EN_SHIFT 5 +#define HDMI_CFG_SINK_TERM_DET_EN (1 << HDMI_CFG_SINK_TERM_DET_EN_SHIFT) +#define HDMI_CFG_V_SYNC_POL_NEG_SHIFT 6 +#define HDMI_CFG_V_SYNC_POL_NEG (1 << HDMI_CFG_V_SYNC_POL_NEG_SHIFT) +#define HDMI_CFG_422_EN_SHIFT 8 +#define HDMI_CFG_422_EN (1 << HDMI_CFG_422_EN_SHIFT) +#define HDMI_CFG_FIFO_OVERRUN_CLR_SHIFT 12 +#define HDMI_CFG_FIFO_OVERRUN_CLR (1 << HDMI_CFG_FIFO_OVERRUN_CLR_SHIFT) +#define HDMI_CFG_FIFO_UNDERRUN_CLR_SHIFT 13 +#define HDMI_CFG_FIFO_UNDERRUN_CLR (1 << HDMI_CFG_FIFO_UNDERRUN_CLR_SHIFT) +#define HDMI_CFG_SW_RST_EN_SHIFT 31 +#define HDMI_CFG_SW_RST_EN (1 << HDMI_CFG_SW_RST_EN_SHIFT) + +#define HDMI_INT_GLOBAL (1 << 0) +#define HDMI_INT_SW_RST (1 << 1) +#define HDMI_INT_IFRAME (1 << 2) +#define HDMI_INT_PIX_CAP (1 << 3) +#define HDMI_INT_HOT_PLUG (1 << 4) +#define HDMI_INT_DLL_LCK (1 << 5) +#define HDMI_INT_NEW_FRAME (1 << 6) +#define HDMI_INT_GENCTRL_PKT (1 << 7) +#define HDMI_INT_SPDIF_FIFO_OVERRUN (1 << 8) +#define HDMI_INT_VID_FIFO_UNDERRUN (1 << 9) +#define HDMI_INT_VID_FIFO_OVERRUN (1 << 10) +#define HDMI_INT_SINK_TERM_PRESENT (1 << 11) +#define HDMI_INT_DI_2 (1 << 16) +#define HDMI_INT_DI_3 (1 << 17) +#define HDMI_INT_DI_4 (1 << 18) +#define HDMI_INT_DI_5 (1 << 19) +#define HDMI_INT_DI_6 (1 << 20) +#define HDMI_INT_DI_DMA_VSYNC_DONE (1 << 21) +#define HDMI_INT_DI_VSYNC_DONE (1 << 22) + +#define HDMI_DEFAULT_INT (HDMI_INT_SINK_TERM_PRESENT \ + | HDMI_INT_DLL_LCK \ + | HDMI_INT_HOT_PLUG \ + | HDMI_INT_GLOBAL) + +#define HDMI_WORKING_INT (HDMI_INT_SINK_TERM_PRESENT \ + | HDMI_INT_GENCTRL_PKT \ + | HDMI_INT_NEW_FRAME \ + | HDMI_INT_DLL_LCK \ + | HDMI_INT_HOT_PLUG \ + | HDMI_INT_PIX_CAP \ + | HDMI_INT_SW_RST \ + | HDMI_INT_GLOBAL) + +#define HDMI_STA_SW_RST_SHIFT 1 +#define HDMI_STA_SW_RST (1 << HDMI_STA_SW_RST_SHIFT) +#define HDMI_STA_PIX_CAP_SHIFT 3 +#define HDMI_STA_PIX_CAP (1 << HDMI_STA_PIX_CAP_SHIFT) +#define HDMI_STA_HOT_PLUG_SHIFT 4 +#define HDMI_STA_HOT_PLUG (1 << HDMI_STA_HOT_PLUG_SHIFT) +#define HDMI_STA_DLL_LCK_SHIFT 5 +#define HDMI_STA_DLL_LCK (1 << HDMI_STA_DLL_LCK_SHIFT) +#define HDMI_STA_SINK_TERM_SHIFT 6 +#define HDMI_STA_SINK_TERM (1 << HDMI_STA_SINK_TERM_SHIFT) +#define HDMI_STA_FIFO_SAMPLES_SHIFT 8 +#define HDMI_STA_FIFO_SAMPLES (0x1F << HDMI_STA_FIFO_SAMPLES_SHIFT) + +/* + * 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 + * @tx3g0c55phy: true if 3g0c55phy is supported + * @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; + bool tx3g0c55phy; + bool enabled; + int hpd_gpio; + bool hpd; + wait_queue_head_t wait_event; + bool event_received; + struct reset_control *reset; +}; + +/* 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]; +}; + +void sti_hdmi_attach_ddc_client(struct i2c_client *ddc); + +int sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi); +void sti_hdmi_tx3g0c55phy_stop(struct sti_hdmi *hdmi); +void sti_hdmi_tx3g0c55phy_show(struct sti_hdmi *hdmi, struct seq_file *m); + +int sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi); +void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi); +void sti_hdmi_tx3g4c28phy_show(struct sti_hdmi *hdmi, struct seq_file *m); + +extern struct i2c_driver ddc_driver; +#endif diff --git a/drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c b/drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c new file mode 100644 index 0000000..547e9ee --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c @@ -0,0 +1,398 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Vincent Abriou vincent.abriou@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include "sti_hdmi.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 (1 << 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 (0x1 << 0) + +#define HDMI_TIMEOUT_PLL_LOCK 50 /*milliseconds */ + +#define HDMI_WAIT_PLL_REJECTION_STATUS 1000 + +/* 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} }, +}; + +/* + * Helper to write bit field + * + * @addr: register to update + * @val: value to write + * @mask: bit field mask to use + */ +static inline void reg_writemask(void __iomem *addr, u32 val, u32 mask) +{ + u32 old = readl(addr); + + val = (val & mask) | (old & ~mask); + writel(val, addr); +} + +/* + * 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 -1 if error occurs + */ +static int enable_pll_rejection(struct sti_hdmi *hdmi) +{ + int inputclock; + u32 mdiv; + u32 ndiv; + u32 pdiv; + u32 mask; + u32 val; + int i; + + DRM_DEBUG_DRIVER("\n"); + + inputclock = hdmi->mode.clock * 1000; + + DRM_DEBUG_DRIVER("hdmi rejection pll input clock = %dHz\n", inputclock); + + /* Force to power down the HDMI rejection PLL */ + mask = REJECTION_PLL_HDMI_ENABLE_MASK; + val = 0x0 << REJECTION_PLL_HDMI_ENABLE_SHIFT; + reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION, + val, mask); + + /* Check the HDMI rejection PLL is really down */ + for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) { + val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS); + if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) == 0) + break; + } + if (i == HDMI_WAIT_PLL_REJECTION_STATUS) { + DRM_ERROR("hdmi rejection pll is not well powered down\n"); + return -1; + } + + /* 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; + } + + mask = 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); + reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION, + val, mask); + + /* Check the HDMI rejection PLL is really up */ + for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) { + val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS); + if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) != 0) + break; + } + if (i == HDMI_WAIT_PLL_REJECTION_STATUS) { + DRM_ERROR("hdmi rejection pll is not well powered up\n"); + return -1; + } + + DRM_DEBUG_DRIVER("hdmi rejection pll locked\n"); + + return 0; +} + +/* + * Disable the pll rejection + * + * @hdmi: pointer on the hdmi internal structure + */ +static void disable_pll_rejection(struct sti_hdmi *hdmi) +{ + int i; + u32 val; + u32 mask; + + DRM_DEBUG_DRIVER("\n"); + + mask = REJECTION_PLL_HDMI_ENABLE_MASK; + val = 0x0 << REJECTION_PLL_HDMI_ENABLE_SHIFT; + reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION, + val, mask); + + /* Check the HDMI rejection PLL is really down */ + for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) { + val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS); + if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) == 0) + break; + } + if (i == HDMI_WAIT_PLL_REJECTION_STATUS) + DRM_ERROR("hdmi rejection pll is not well powered down\n"); + else + DRM_DEBUG_DRIVER("hdmi rejection pll is powered down\n"); +} + +/* + * Start hdmi phy macro cell tx3g0c55 + * + * @hdmi: pointer on the hdmi internal structure + * + * Return -1 if error occurs + */ +int sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi) +{ + u32 ckpxpll = hdmi->mode.clock * 1000; + u32 tmdsck; + u32 freqvco; + u32 pllctrl = 0; + u32 val; + int i; + + if (enable_pll_rejection(hdmi)) + return -1; + + DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll); + + /* TODO: manage DeepColor (30, 36 and 48 bits) and pixel repetition */ + + /* 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); + writel(pllctrl, hdmi->regs + 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 ((readl(hdmi->regs + 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]; + writel(val, hdmi->regs + HDMI_SRZ_TAP_1); + val = hdmiphy_config[i].config[1]; + writel(val, hdmi->regs + HDMI_SRZ_TAP_2); + val = hdmiphy_config[i].config[2]; + writel(val, hdmi->regs + HDMI_SRZ_TAP_3); + val = hdmiphy_config[i].config[3]; + val |= HDMI_SRZ_CTRL_EXTERNAL_DATA_EN; + val &= ~HDMI_SRZ_CTRL_POWER_DOWN; + writel(val, hdmi->regs + 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 0; + } + } + + /* + * Default, power up the serializer with no pre-emphasis or source + * termination. + */ + writel(0x0, hdmi->regs + HDMI_SRZ_TAP_1); + writel(0x0, hdmi->regs + HDMI_SRZ_TAP_2); + writel(0x0, hdmi->regs + HDMI_SRZ_TAP_3); + writel(HDMI_SRZ_CTRL_EXTERNAL_DATA_EN, hdmi->regs + HDMI_SRZ_CTRL); + + return 0; + +err: + disable_pll_rejection(hdmi); + + return -1; +} + +/* + * Stop hdmi phy macro cell tx3g0c55 + * + * @hdmi: pointer on the hdmi internal structure + */ +void sti_hdmi_tx3g0c55phy_stop(struct sti_hdmi *hdmi) +{ + DRM_DEBUG_DRIVER("\n"); + + hdmi->event_received = false; + + writel(HDMI_SRZ_CTRL_POWER_DOWN, hdmi->regs + HDMI_SRZ_CTRL); + writel(HDMI_SRZ_PLL_CFG_POWER_DOWN, hdmi->regs + 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 ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 1) + DRM_ERROR("hdmi phy pll not well disabled\n"); + else + DRM_DEBUG_DRIVER("hdmi phy pll disabled\n"); + + disable_pll_rejection(hdmi); +} + +/* + * Debugfs + */ +#define HDMI_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \ + readl(hdmi->regs + reg)) +void sti_hdmi_tx3g0c55phy_show(struct sti_hdmi *hdmi, struct seq_file *m) +{ + HDMI_DBG_DUMP(HDMI_SRZ_PLL_CFG); + HDMI_DBG_DUMP(HDMI_SRZ_TAP_1); + HDMI_DBG_DUMP(HDMI_SRZ_TAP_2); + HDMI_DBG_DUMP(HDMI_SRZ_TAP_3); + HDMI_DBG_DUMP(HDMI_SRZ_CTRL); + seq_puts(m, "\n"); +} diff --git a/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c b/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c new file mode 100644 index 0000000..6e0bc2c --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Vincent Abriou vincent.abriou@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include "sti_hdmi.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 (1L<<0) +#define HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT (1L<<1) +#define HDMI_SRZ_CFG_EXTERNAL_DATA (1L<<16) +#define HDMI_SRZ_CFG_RBIAS_EXT (1L<<17) +#define HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION (1L<<18) +#define HDMI_SRZ_CFG_EN_BIASRES_DETECTION (1L<<19) +#define HDMI_SRZ_CFG_EN_SRC_TERMINATION (1L<<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 (1L<<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 -1 if error occurs + */ +int sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi) +{ + u32 ckpxpll = hdmi->mode.clock * 1000; + u32 tmdsck; + u32 idf; + u32 odf; + u32 pllctrl = 0; + u32 val; + int i; + bool foundplldivides = false; + + 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; + } + + /* TODO: manage DeepColor (30, 36 and 48 bits) and pixel repetition */ + + /* 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); + writel((pllctrl | PLL_CFG_EN), hdmi->regs + 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 ((readl(hdmi->regs + 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); + writel(val, hdmi->regs + HDMI_SRZ_CFG); + val = hdmiphy_config[i].config[1]; + writel(val, hdmi->regs + HDMI_SRZ_ICNTL); + val = hdmiphy_config[i].config[2]; + writel(val, hdmi->regs + 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 0; + } + } + + /* + * Default, power up the serializer with no pre-emphasis or + * output swing correction + */ + writel(val, hdmi->regs + HDMI_SRZ_CFG); + writel(0x0, hdmi->regs + HDMI_SRZ_ICNTL); + writel(0x0, hdmi->regs + HDMI_SRZ_CALCODE_EXT); + + return 0; + +err: + return -1; +} + +/* + * Stop hdmi phy macro cell tx3g4c28 + * + * @hdmi: pointer on the hdmi internal structure + */ +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; + writel(val, hdmi->regs + HDMI_SRZ_CFG); + writel(0, hdmi->regs + 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 ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 1) + DRM_ERROR("hdmi phy pll not well disabled\n"); + else + DRM_DEBUG_DRIVER("hdmi phy pll disabled\n"); +} + +/* + * Debugfs + */ +#define HDMI_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \ + readl(hdmi->regs + reg)) +void sti_hdmi_tx3g4c28phy_show(struct sti_hdmi *hdmi, struct seq_file *m) +{ + HDMI_DBG_DUMP(HDMI_SRZ_CFG); + HDMI_DBG_DUMP(HDMI_SRZ_PLL_CFG); + HDMI_DBG_DUMP(HDMI_SRZ_ICNTL); + HDMI_DBG_DUMP(HDMI_SRZ_CALCODE_EXT); + seq_puts(m, "\n"); +}
Add I2C client driver to retrieve EDID.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/Makefile | 3 ++- drivers/gpu/drm/sti/sti_ddc.c | 56 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/sti/sti_ddc.c
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 5295fc7..6c474eb 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -2,7 +2,8 @@ ccflags-y := -Iinclude/drm
stidrm-y := sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ - sti_hdmi_tx3g4c28phy.o + sti_hdmi_tx3g4c28phy.o \ + sti_ddc.o
obj-$(CONFIG_VTAC_STI) += sti_vtac_tx.o sti_vtac_rx.o obj-$(CONFIG_VTG_STI) += sti_vtg.o sti_vtg_utils.o diff --git a/drivers/gpu/drm/sti/sti_ddc.c b/drivers/gpu/drm/sti/sti_ddc.c new file mode 100644 index 0000000..147a704 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_ddc.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ +#include <drm/drmP.h> + +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/module.h> + +#include "sti_hdmi.h" + +static int sti_hdmi_ddc_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + sti_hdmi_attach_ddc_client(client); + + DRM_INFO("%s attached %s into i2c adapter successfully\n", + __func__, client->name); + + return 0; +} + +static int sti_hdmi_ddc_remove(struct i2c_client *client) +{ + DRM_INFO("%s detached %s from i2c adapter successfully\n", + __func__, client->name); + + return 0; +} + +static struct i2c_device_id sti_ddc_idtable[] = { + {"sti-hdmiddc", 0}, + {}, +}; + +static struct of_device_id hdmiddc_match_types[] = { + { + .compatible = "st,sti-hdmiddc", + }, { + /* end node */ + } +}; + +struct i2c_driver ddc_driver = { + .driver = { + .name = "sti-hdmiddc", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(hdmiddc_match_types), + }, + .id_table = sti_ddc_idtable, + .probe = sti_hdmi_ddc_probe, + .remove = sti_hdmi_ddc_remove, + .command = NULL, +};
Add driver to support analog TV ouput.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/Makefile | 1 + drivers/gpu/drm/sti/sti_hda.c | 481 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 482 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_hda.c
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 6c474eb..aa69ea0 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm stidrm-y := sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ + sti_hda.o \ sti_ddc.o
obj-$(CONFIG_VTAC_STI) += sti_vtac_tx.o sti_vtac_rx.o diff --git a/drivers/gpu/drm/sti/sti_hda.c b/drivers/gpu/drm/sti/sti_hda.c new file mode 100644 index 0000000..e8e0719 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hda.c @@ -0,0 +1,481 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Author: Fabien Dessenne fabien.dessenne@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <drm/drmP.h> + +/* HDformatter registers */ +#define HDA_ANA_CFG 0x0000 +#define HDA_ANA_SCALE_CTRL_Y 0x0004 +#define HDA_ANA_SCALE_CTRL_CB 0x0008 +#define HDA_ANA_SCALE_CTRL_CR 0x000C +#define HDA_ANA_ANC_CTRL 0x0010 +#define HDA_ANA_SRC_Y_CFG 0x0014 +#define HDA_COEFF_Y_PH1_TAP123 0x0018 +#define HDA_COEFF_Y_PH1_TAP456 0x001C +#define HDA_COEFF_Y_PH2_TAP123 0x0020 +#define HDA_COEFF_Y_PH2_TAP456 0x0024 +#define HDA_COEFF_Y_PH3_TAP123 0x0028 +#define HDA_COEFF_Y_PH3_TAP456 0x002C +#define HDA_COEFF_Y_PH4_TAP123 0x0030 +#define HDA_COEFF_Y_PH4_TAP456 0x0034 +#define HDA_ANA_SRC_C_CFG 0x0040 +#define HDA_COEFF_C_PH1_TAP123 0x0044 +#define HDA_COEFF_C_PH1_TAP456 0x0048 +#define HDA_COEFF_C_PH2_TAP123 0x004C +#define HDA_COEFF_C_PH2_TAP456 0x0050 +#define HDA_COEFF_C_PH3_TAP123 0x0054 +#define HDA_COEFF_C_PH3_TAP456 0x0058 +#define HDA_COEFF_C_PH4_TAP123 0x005C +#define HDA_COEFF_C_PH4_TAP456 0x0060 +#define HDA_SYNC_AWGI 0x0300 + +/* HDA_ANA_CFG */ +#define CFG_AWG_ASYNC_EN (1 << 0) +#define CFG_AWG_ASYNC_HSYNC_MTD (1 << 1) +#define CFG_AWG_ASYNC_VSYNC_MTD (1 << 2) +#define CFG_AWG_SYNC_DEL (1 << 3) +#define CFG_AWG_FLTR_MODE_SHIFT 4 +#define CFG_AWG_FLTR_MODE_MASK (0xF << CFG_AWG_FLTR_MODE_SHIFT) +#define CFG_AWG_FLTR_MODE_SD (0 << CFG_AWG_FLTR_MODE_SHIFT) +#define CFG_AWG_FLTR_MODE_ED (1 << CFG_AWG_FLTR_MODE_SHIFT) +#define CFG_AWG_FLTR_MODE_HD (2 << CFG_AWG_FLTR_MODE_SHIFT) +#define CFG_SYNC_ON_PBPR_MASK (1 << 8) +#define CFG_PREFILTER_EN_MASK (1 << 9) +#define CFG_PBPR_SYNC_OFF_SHIFT 16 +#define CFG_PBPR_SYNC_OFF_MASK (0x7FF << CFG_PBPR_SYNC_OFF_SHIFT) +#define CFG_PBPR_SYNC_OFF_VAL 0x117 /* Voltage dependent. stiH416 */ + +/* Default scaling values */ +#define SCALE_CTRL_Y_DFLT 0x00C50256 +#define SCALE_CTRL_CB_DFLT 0x00DB0249 +#define SCALE_CTRL_CR_DFLT 0x00DB0249 + +/* Video DACs control */ +#define VIDEO_DACS_CONTROL_MASK 0x0FFF +#define VIDEO_DACS_CONTROL_SYSCFG2535 0x085C /* for stih416 */ +#define DAC_CFG_HD_OFF_SHIFT 5 +#define DAC_CFG_HD_OFF_MASK (0x7 << DAC_CFG_HD_OFF_SHIFT) +#define VIDEO_DACS_CONTROL_SYSCFG5072 0x0120 /* for stih407 */ +#define DAC_CFG_HD_HZUVW_OFF_SHIFT 1 +#define DAC_CFG_HD_HZUVW_OFF_MASK (0x1 << DAC_CFG_HD_HZUVW_OFF_SHIFT) + + +/* Upsampler values for the alternative 2X Filter */ +#define SAMPLER_COEF_NB 8 +#define HDA_ANA_SRC_Y_CFG_ALT_2X 0x01130000 +static u32 coef_y_alt_2x[] = { + 0x00FE83FB, 0x1F900401, 0x00000000, 0x00000000, + 0x00F408F9, 0x055F7C25, 0x00000000, 0x00000000 +}; + +#define HDA_ANA_SRC_C_CFG_ALT_2X 0x01750004 +static u32 coef_c_alt_2x[] = { + 0x001305F7, 0x05274BD0, 0x00000000, 0x00000000, + 0x0004907C, 0x09C80B9D, 0x00000000, 0x00000000 +}; + +/* Upsampler values for the 4X Filter */ +#define HDA_ANA_SRC_Y_CFG_4X 0x01ED0005 +#define HDA_ANA_SRC_C_CFG_4X 0x01ED0004 +static u32 coef_yc_4x[] = { + 0x00FC827F, 0x008FE20B, 0x00F684FC, 0x050F7C24, + 0x00F4857C, 0x0A1F402E, 0x00FA027F, 0x0E076E1D +}; + +/* AWG instructions for some video modes */ +#define AWG_MAX_INST 64 + +/* 720p@50 */ +static u32 AWGi_720p_50[] = { + 0x00000971, 0x00000C26, 0x0000013B, 0x00000CDA, + 0x00000104, 0x00000E7E, 0x00000E7F, 0x0000013B, + 0x00000D8E, 0x00000104, 0x00001804, 0x00000971, + 0x00000C26, 0x0000003B, 0x00000FB4, 0x00000FB5, + 0x00000104, 0x00001AE8 +}; + +#define NN_720p_50 ARRAY_SIZE(AWGi_720p_50) + +/* 720p@60 */ +static u32 AWGi_720p_60[] = { + 0x00000971, 0x00000C26, 0x0000013B, 0x00000CDA, + 0x00000104, 0x00000E7E, 0x00000E7F, 0x0000013B, + 0x00000C44, 0x00000104, 0x00001804, 0x00000971, + 0x00000C26, 0x0000003B, 0x00000F0F, 0x00000F10, + 0x00000104, 0x00001AE8 +}; + +#define NN_720p_60 ARRAY_SIZE(AWGi_720p_60) + +/* 1080p@30 */ +static u32 AWGi_1080p_30[] = { + 0x00000971, 0x00000C2A, 0x0000013B, 0x00000C56, + 0x00000104, 0x00000FDC, 0x00000FDD, 0x0000013B, + 0x00000C2A, 0x00000104, 0x00001804, 0x00000971, + 0x00000C2A, 0x0000003B, 0x00000EBE, 0x00000EBF, + 0x00000EBF, 0x00000104, 0x00001A2F, 0x00001C4B, + 0x00001C52 +}; + +#define NN_1080p_30 ARRAY_SIZE(AWGi_1080p_30) + +/* 1080p@25 */ +static u32 AWGi_1080p_25[] = { + 0x00000971, 0x00000C2A, 0x0000013B, 0x00000C56, + 0x00000104, 0x00000FDC, 0x00000FDD, 0x0000013B, + 0x00000DE2, 0x00000104, 0x00001804, 0x00000971, + 0x00000C2A, 0x0000003B, 0x00000F51, 0x00000F51, + 0x00000F52, 0x00000104, 0x00001A2F, 0x00001C4B, + 0x00001C52 +}; + +#define NN_1080p_25 ARRAY_SIZE(AWGi_1080p_25) + +/* 1080p@24 */ +static u32 AWGi_1080p_24[] = { + 0x00000971, 0x00000C2A, 0x0000013B, 0x00000C56, + 0x00000104, 0x00000FDC, 0x00000FDD, 0x0000013B, + 0x00000E50, 0x00000104, 0x00001804, 0x00000971, + 0x00000C2A, 0x0000003B, 0x00000F76, 0x00000F76, + 0x00000F76, 0x00000104, 0x00001A2F, 0x00001C4B, + 0x00001C52 +}; + +#define NN_1080p_24 ARRAY_SIZE(AWGi_1080p_24) + +/* 720x480p@60 */ +static u32 AWGi_720x480p_60[] = { + 0x00000904, 0x00000F18, 0x0000013B, 0x00001805, + 0x00000904, 0x00000C3D, 0x0000003B, 0x00001A06 +}; + +#define NN_720x480p_60 ARRAY_SIZE(AWGi_720x480p_60) + +/* Video mode category */ +enum sti_hda_vid_cat { + VID_SD, + VID_ED, + VID_HD_74M, + VID_HD_148M +}; + +struct sti_hda_video_config { + struct drm_display_mode mode; + u32 *awg_instr; + int nb_instr; + enum sti_hda_vid_cat vid_cat; +}; + +/* HD analog supported modes */ +/* Interlaced modes may be added when supported by the whole display chain */ +static const struct sti_hda_video_config hda_supported_modes[] = { + /* 1080p30 74.250Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_30, NN_1080p_30, VID_HD_74M}, + /* 1080p30 74.176Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74176, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_30, NN_1080p_30, VID_HD_74M}, + /* 1080p24 74.250Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2558, + 2602, 2750, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_24, NN_1080p_24, VID_HD_74M}, + /* 1080p24 74.176Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74176, 1920, 2558, + 2602, 2750, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_24, NN_1080p_24, VID_HD_74M}, + /* 1080p25 74.250Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448, + 2492, 2640, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_25, NN_1080p_25, VID_HD_74M}, + /* 720p60 74.250Mhz */ + {{DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390, + 1430, 1650, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_720p_60, NN_720p_60, VID_HD_74M}, + /* 720p60 74.176Mhz */ + {{DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74176, 1280, 1390, + 1430, 1650, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_720p_60, NN_720p_60, VID_HD_74M}, + /* 720p50 74.250Mhz */ + {{DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1720, + 1760, 1980, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_720p_50, NN_720p_50, VID_HD_74M}, + /* 720x480p60 27.027Mhz */ + {{DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27027, 720, 736, + 798, 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC)}, + AWGi_720x480p_60, NN_720x480p_60, VID_ED}, + /* 720x480p60 27.000Mhz */ + {{DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, + 798, 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC)}, + AWGi_720x480p_60, NN_720x480p_60, VID_ED} +}; + +/* Index (in supported modes table) of the preferred video mode */ +#define HDA_PREF_MODE_IDX 0 + +/* + * STI hd analog structure + * + * @dev: driver device + * @drm_dev: pointer to drm device + * @mode: current display mode selected + * @regs: HD analog register + * @video_dacs_ctrl: video DACS control register + * @enabled: true if HD analog is enabled else false + */ +struct sti_hda { + struct device dev; + struct drm_device *drm_dev; + struct drm_display_mode mode; + void __iomem *regs; + void __iomem *video_dacs_ctrl; + struct clk *clk_pix; + struct clk *clk_hddac; + bool enabled; +}; + +/* Reference to the hda device */ +struct device *hda_dev; + +/* + * Helper to write bit field + * + * @addr: register to update + * @val: value to write + * @mask: bit field mask to use + */ +static inline void hda_reg_writemask(void __iomem *addr, u32 val, u32 mask) +{ + u32 old = readl(addr); + + val = (val & mask) | (old & ~mask); + writel(val, addr); +} + +/* Search for a video mode in the supported modes table + * + * @mode: mode being searched + * @idx: index of the found mode + * + * Return true if mode is found + */ +static bool hda_get_mode_idx(struct drm_display_mode mode, int *idx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(hda_supported_modes); i++) + if (drm_mode_equal(&hda_supported_modes[i].mode, &mode)) { + *idx = i; + return true; + } + return false; +} + +/* Enable the HD DACS + * + * @hda: pointer to HD analog structure + * @enable: true if HD DACS need to be enabled, else false + */ +static void hda_enable_hd_dacs(struct sti_hda *hda, bool enable) +{ + u32 mask; + + if (hda->video_dacs_ctrl) { + switch ((u32)hda->video_dacs_ctrl & VIDEO_DACS_CONTROL_MASK) { + case VIDEO_DACS_CONTROL_SYSCFG2535: + mask = DAC_CFG_HD_OFF_MASK; + break; + case VIDEO_DACS_CONTROL_SYSCFG5072: + mask = DAC_CFG_HD_HZUVW_OFF_MASK; + break; + default: + DRM_INFO("Video DACS control register not supported!"); + return; + } + + if (enable) + hda_reg_writemask(hda->video_dacs_ctrl, 0x000000, mask); + else + hda_reg_writemask(hda->video_dacs_ctrl, 0xFFFFFF, mask); + } + +} + +/* Configure AWG, writing instructions + * + * @hda: pointer to HD analog structure + * @awg_instr: pointer to AWG instructions table + * @nb: nb of AWG instructions + */ +static void sti_hda_configure_awg(struct sti_hda *hda, u32 *awg_instr, int nb) +{ + int i; + + DRM_DEBUG_DRIVER("\n"); + + for (i = 0; i < nb; i++) + writel(awg_instr[i], hda->regs + HDA_SYNC_AWGI + i * 4); + for (i = nb; i < AWG_MAX_INST; i++) + writel(0, hda->regs + HDA_SYNC_AWGI + i * 4); +} + +/* + * Get modes + * + * @drm_connector: pointer on the drm connector + * + * Return number of modes + */ +static int sti_hda_get_modes(struct drm_connector *drm_connector) +{ + struct drm_device *dev = drm_connector->dev; + struct drm_display_mode *mode; + int i, count; + + DRM_DEBUG_DRIVER("\n"); + + for (i = 0, count = 0; i < ARRAY_SIZE(hda_supported_modes); i++) { + mode = drm_mode_duplicate(dev, &hda_supported_modes[i].mode); + if (!mode) + continue; + mode->vrefresh = drm_mode_vrefresh(mode); + + /* Set the preferred mode */ + if (i == HDA_PREF_MODE_IDX) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_probed_add(drm_connector, mode); + count++; + } + + drm_mode_sort(&drm_connector->modes); + + return count; +} + + +static int sti_hda_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sti_hda *hda; + struct resource *res; + + DRM_INFO("%s\n", __func__); + + hda = devm_kzalloc(dev, sizeof(*hda), GFP_KERNEL); + if (!hda) { + DRM_ERROR("Failed to allocate memory for HD analog\n"); + return -ENOMEM; + } + + hda->dev = pdev->dev; + + /* Get resources */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hda-reg"); + if (!res) { + DRM_ERROR("Invalid hda resource\n"); + return -ENOMEM; + } + hda->regs = devm_ioremap_nocache(dev, res->start, resource_size(res)); + if (IS_ERR(hda->regs)) + return PTR_ERR(hda->regs); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "video-dacs-ctrl"); + if (res) { + hda->video_dacs_ctrl = devm_ioremap_nocache(dev, res->start, + resource_size(res)); + if (IS_ERR(hda->video_dacs_ctrl)) + return PTR_ERR(hda->video_dacs_ctrl); + } else { + /* If no existing video-dacs-ctrl resource continue the probe */ + DRM_DEBUG_DRIVER("No video-dacs-ctrl resource\n"); + hda->video_dacs_ctrl = NULL; + } + + /* Get clock resources */ + hda->clk_pix = devm_clk_get(dev, "hda_pix"); + if (IS_ERR(hda->clk_pix)) { + DRM_ERROR("Cannot get hda_pix clock\n"); + return PTR_ERR(hda->clk_pix); + } + + hda->clk_hddac = devm_clk_get(dev, "hda_hddac"); + if (IS_ERR(hda->clk_hddac)) { + DRM_ERROR("Cannot get hda_hddac clock\n"); + return PTR_ERR(hda->clk_hddac); + } + + hda_dev = &hda->dev; + + platform_set_drvdata(pdev, hda); + + return 0; +} + +static void sti_hda_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops sti_hda_ops = { + .bind = sti_hda_bind, + .unbind = sti_hda_unbind, +}; + +static int sti_hda_probe(struct platform_device *pdev) +{ + DRM_INFO("%s\n", __func__); + return component_add(&pdev->dev, &sti_hda_ops); +} + +static int sti_hda_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sti_hda_ops); + return 0; +} + +static struct of_device_id hda_match_types[] = { + { + .compatible = "st,stih416-hda", + }, + { + .compatible = "st,stih407-hda", + }, + { /* end node */ } +}; +MODULE_DEVICE_TABLE(of, hda_match_types); + +struct platform_driver sti_hda_driver = { + .driver = { + .name = "sti-hda", + .owner = THIS_MODULE, + .of_match_table = hda_match_types, + }, + .probe = sti_hda_probe, + .remove = sti_hda_remove, +}; + +module_platform_driver(sti_hda_driver); + +MODULE_LICENSE("GPL");
TVout hardware block is responsible to dispatch the data flow coming from compositor block to any of the output (HDMI or Analog TV). It control when output are start/stop and configure according the require flow path.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/Makefile | 3 +- drivers/gpu/drm/sti/sti_hda.c | 372 +++++++++++++++++++++ drivers/gpu/drm/sti/sti_hda.h | 14 + drivers/gpu/drm/sti/sti_hdmi.c | 542 +++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi.h | 3 + drivers/gpu/drm/sti/sti_tvout.c | 703 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_tvout.h | 105 ++++++ 7 files changed, 1741 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/sti/sti_hda.h create mode 100644 drivers/gpu/drm/sti/sti_tvout.c create mode 100644 drivers/gpu/drm/sti/sti_tvout.h
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index aa69ea0..45536c3 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -1,6 +1,7 @@ ccflags-y := -Iinclude/drm
-stidrm-y := sti_hdmi.o \ +stidrm-y := sti_tvout.o \ + sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ diff --git a/drivers/gpu/drm/sti/sti_hda.c b/drivers/gpu/drm/sti/sti_hda.c index e8e0719..d500684 100644 --- a/drivers/gpu/drm/sti/sti_hda.c +++ b/drivers/gpu/drm/sti/sti_hda.c @@ -11,6 +11,8 @@
#include <drm/drmP.h>
+#include "sti_hda.h" + /* HDformatter registers */ #define HDA_ANA_CFG 0x0000 #define HDA_ANA_SCALE_CTRL_Y 0x0004 @@ -372,6 +374,376 @@ static int sti_hda_get_modes(struct drm_connector *drm_connector) return count; }
+/* + * Start hd analog + * + * @connector: pointer on the tvout HD analog connector + * + * Return 0 on success + */ +static int sti_hda_start(struct sti_tvout_connector *connector) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + u32 val, i, mode_idx; + u32 src_filter_y, src_filter_c; + u32 *coef_y, *coef_c; + u32 filter_mode; + + DRM_DEBUG_DRIVER("\n"); + + /* Prepare/enable clocks */ + if (clk_prepare_enable(hda->clk_pix)) + DRM_ERROR("Failed to prepare/enable hda_pix clk\n"); + if (clk_prepare_enable(hda->clk_hddac)) + DRM_ERROR("Failed to prepare/enable hda_hddac clk\n"); + + hda->enabled = true; + + if (!hda_get_mode_idx(hda->mode, &mode_idx)) { + DRM_ERROR("Undefined mode\n"); + return 1; + } + + switch (hda_supported_modes[mode_idx].vid_cat) { + case VID_HD_148M: + DRM_ERROR("Beyond HD analog capabilities\n"); + return 1; + case VID_HD_74M: + /* HD use alternate 2x filter */ + filter_mode = CFG_AWG_FLTR_MODE_HD; + src_filter_y = HDA_ANA_SRC_Y_CFG_ALT_2X; + src_filter_c = HDA_ANA_SRC_C_CFG_ALT_2X; + coef_y = coef_y_alt_2x; + coef_c = coef_c_alt_2x; + break; + case VID_ED: + /* ED uses 4x filter */ + filter_mode = CFG_AWG_FLTR_MODE_ED; + src_filter_y = HDA_ANA_SRC_Y_CFG_4X; + src_filter_c = HDA_ANA_SRC_C_CFG_4X; + coef_y = coef_yc_4x; + coef_c = coef_yc_4x; + break; + case VID_SD: + DRM_ERROR("Not supported\n"); + return 1; + default: + DRM_ERROR("Undefined resolution\n"); + return 1; + } + DRM_DEBUG_DRIVER("Using HDA mode #%d\n", mode_idx); + + /* Enable HD Video DACs */ + hda_enable_hd_dacs(hda, true); + + /* Configure scaler */ + writel(SCALE_CTRL_Y_DFLT, hda->regs + HDA_ANA_SCALE_CTRL_Y); + writel(SCALE_CTRL_CB_DFLT, hda->regs + HDA_ANA_SCALE_CTRL_CB); + writel(SCALE_CTRL_CR_DFLT, hda->regs + HDA_ANA_SCALE_CTRL_CR); + + /* Configure sampler */ + writel(src_filter_y, hda->regs + HDA_ANA_SRC_Y_CFG); + writel(src_filter_c, hda->regs + HDA_ANA_SRC_C_CFG); + for (i = 0; i < SAMPLER_COEF_NB; i++) { + writel(coef_y[i], hda->regs + HDA_COEFF_Y_PH1_TAP123 + i * 4); + writel(coef_c[i], hda->regs + HDA_COEFF_C_PH1_TAP123 + i * 4); + } + + /* Configure main HDFormatter */ + val = 0; + val |= (hda->mode.flags & DRM_MODE_FLAG_INTERLACE) ? + 0 : CFG_AWG_ASYNC_VSYNC_MTD; + val |= (CFG_PBPR_SYNC_OFF_VAL << CFG_PBPR_SYNC_OFF_SHIFT); + val |= filter_mode; + writel(val, hda->regs + HDA_ANA_CFG); + + /* Configure AWG */ + sti_hda_configure_awg(hda, hda_supported_modes[mode_idx].awg_instr, + hda_supported_modes[mode_idx].nb_instr); + + /* Enable AWG */ + hda_reg_writemask(hda->regs + HDA_ANA_CFG, 1, CFG_AWG_ASYNC_EN); + + return 0; +} + +/* + * Stop HD analog + * + * @connector: pointer on the tvout HD analog connector + */ +static void sti_hda_stop(struct sti_tvout_connector *connector) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + + if (!hda->enabled) + return; + + DRM_DEBUG_DRIVER("\n"); + + /* Disable HD DAC and AWG */ + hda_reg_writemask(hda->regs + HDA_ANA_CFG, 0, CFG_AWG_ASYNC_EN); + hda_enable_hd_dacs(hda, false); + + /* Disable/unprepare hda clock */ + clk_disable_unprepare(hda->clk_hddac); + clk_disable_unprepare(hda->clk_pix); + + hda->enabled = false; +} + +/* + * Check if the drm display mode in supported by the HD analog + * + * @connector: pointer on the tvout HD analog connector + * @mode: drm display mode + * + * Return 0 if supported + */ +#define CLK_TOLERANCE_HZ 50 +static int sti_hda_check_mode(struct sti_tvout_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + int target = mode->clock * 1000; + int target_min = target - CLK_TOLERANCE_HZ; + int target_max = target + CLK_TOLERANCE_HZ; + int result; + int idx; + + if (!hda_get_mode_idx(*mode, &idx)) { + return 1; + } else { + result = clk_round_rate(hda->clk_pix, target); + + DRM_DEBUG_DRIVER("target rate = %d => available rate = %d\n", + target, result); + + if ((result < target_min) || (result > target_max)) { + DRM_DEBUG_DRIVER("hda pixclk=%d not supported\n", + target); + return 1; + } + } + + return 0; +} + +/* + * Set the drm display mode in the local structure + * + * @connector: pointer on the tvout HD analog connector + * @mode: drm display mode + * + * Return 0 on success + */ +static int sti_hda_set_mode(struct sti_tvout_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + u32 mode_idx; + int hddac_rate; + int ret; + + DRM_DEBUG_DRIVER("\n"); + + memcpy(&hda->mode, mode, sizeof(struct drm_display_mode)); + + if (!hda_get_mode_idx(hda->mode, &mode_idx)) { + DRM_ERROR("Undefined mode\n"); + return 1; + } + + switch (hda_supported_modes[mode_idx].vid_cat) { + case VID_HD_74M: + /* HD use alternate 2x filter */ + hddac_rate = mode->clock * 1000 * 2; + break; + case VID_ED: + /* ED uses 4x filter */ + hddac_rate = mode->clock * 1000 * 4; + break; + default: + DRM_ERROR("Undefined mode\n"); + return 1; + } + + /* HD DAC = 148.5Mhz or 108 Mhz */ + ret = clk_set_rate(hda->clk_hddac, hddac_rate); + if (ret < 0) { + DRM_ERROR("Cannot set rate (%dHz) for hda_hddac clk\n", + hddac_rate); + return ret; + } + + /* HDformatter clock = compositor clock */ + ret = clk_set_rate(hda->clk_pix, mode->clock * 1000); + if (ret < 0) { + DRM_ERROR("Cannot set rate (%dHz) for hda_pix clk\n", + mode->clock * 1000); + return ret; + } + + return 0; +} + +/* + * Detect if HD analog is connected + * + * @connector: pointer on the tvout HD analog connector + * + * Return true if HD analog cable is connected which is assumed to be always + * the case + */ +static bool sti_hda_detect(struct sti_tvout_connector *connector) +{ + DRM_DEBUG_DRIVER("\n"); + + return true; +} + +/* + * Check if HD analog is enabled + * + * @connector: pointer on the tvout HD analog connector + * + * Return true if HD analog is enabled + */ +static bool sti_hda_is_enabled(struct sti_tvout_connector *connector) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + + return hda->enabled; +} + +/* + * Prepare/configure HD analog + * + * @connector: pointer on the tvout HD analog connector + */ +static void sti_hda_prepare(struct sti_tvout_connector *connector) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + + DRM_DEBUG_DRIVER("\n"); + + /* reset HDF */ + writel(0x00000000, hda->regs + HDA_ANA_CFG); + writel(0x00000000, hda->regs + HDA_ANA_ANC_CTRL); +} + +/* + * Debugfs + */ + +#define HDA_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \ + readl(hda->regs + reg)) + +static void hda_dbg_cfg(struct seq_file *m, int val) +{ + seq_puts(m, "\t AWG "); + seq_puts(m, val & CFG_AWG_ASYNC_EN ? "enabled" : "disabled"); +} + +static void hda_dbg_awgi(struct seq_file *m, void __iomem *reg) +{ + int i; + + seq_puts(m, "\n HDA_SYNC_AWGI "); + for (i = 0; i < 10; i++) + seq_printf(m, "%04X ", readl(reg + i * 4)); + seq_puts(m, "..."); +} + +static void hda_dbg_video_dacs_ctrl(struct seq_file *m, void __iomem *reg) +{ + u32 val = readl(reg); + u32 mask; + + switch ((u32)reg & VIDEO_DACS_CONTROL_MASK) { + case VIDEO_DACS_CONTROL_SYSCFG2535: + mask = DAC_CFG_HD_OFF_MASK; + break; + case VIDEO_DACS_CONTROL_SYSCFG5072: + mask = DAC_CFG_HD_HZUVW_OFF_MASK; + break; + default: + DRM_INFO("Video DACS control register not supported!"); + return; + } + + seq_puts(m, "\n"); + + seq_printf(m, "\n %-25s 0x%08X", "VIDEO_DACS_CONTROL", val); + seq_puts(m, "\tHD DACs "); + seq_puts(m, val & mask ? "disabled" : "enabled"); +} + +static void sti_hda_dbg_show(struct sti_tvout_connector *connector, + struct seq_file *m) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + + seq_puts(m, "\n"); + seq_printf(m, "\nHD Analog: (virt base addr = 0x%p)", hda->regs); + HDA_DBG_DUMP(HDA_ANA_CFG); + hda_dbg_cfg(m, readl(hda->regs + HDA_ANA_CFG)); + HDA_DBG_DUMP(HDA_ANA_SCALE_CTRL_Y); + HDA_DBG_DUMP(HDA_ANA_SCALE_CTRL_CB); + HDA_DBG_DUMP(HDA_ANA_SCALE_CTRL_CR); + HDA_DBG_DUMP(HDA_ANA_ANC_CTRL); + HDA_DBG_DUMP(HDA_ANA_SRC_Y_CFG); + HDA_DBG_DUMP(HDA_ANA_SRC_C_CFG); + hda_dbg_awgi(m, hda->regs + HDA_SYNC_AWGI); + if (hda->video_dacs_ctrl) + hda_dbg_video_dacs_ctrl(m, hda->video_dacs_ctrl); +} + +/* + * create the HD analog output + * + * @tvout: pointer on the tvout information + * + * Return pointer on the created tvout connector or NULL if error occurs + */ +struct sti_tvout_connector *sti_hda_create(struct sti_tvout *tvout) +{ + struct sti_hda *hda = container_of(hda_dev, struct sti_hda, dev); + struct device *dev = &hda->dev; + struct sti_tvout_connector *connector; + + DRM_DEBUG_DRIVER("\n"); + + if (!hda) { + DRM_INFO("%s: No hda device probed\n", __func__); + return NULL; + } + + connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL); + if (!connector) { + DRM_ERROR("Failed to allocate memory for connector\n"); + goto connector_alloc_failed; + } + + /* Set the drm device handle */ + hda->drm_dev = tvout->drm_dev; + + connector->priv = (void *)hda; + connector->start = sti_hda_start; + connector->stop = sti_hda_stop; + connector->get_modes = sti_hda_get_modes; + connector->check_mode = sti_hda_check_mode; + connector->set_mode = sti_hda_set_mode; + connector->detect = sti_hda_detect; + connector->is_enabled = sti_hda_is_enabled; + connector->prepare = sti_hda_prepare; + connector->dbg_show = sti_hda_dbg_show; + + return connector; + +connector_alloc_failed: + return NULL; +}
static int sti_hda_bind(struct device *dev, struct device *master, void *data) { diff --git a/drivers/gpu/drm/sti/sti_hda.h b/drivers/gpu/drm/sti/sti_hda.h new file mode 100644 index 0000000..e88b30e --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hda.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Fabien Dessenne fabien.dessenne@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_HDA_H_ +#define _STI_HDA_H_ + +#include "sti_tvout.h" + +struct sti_tvout_connector *sti_hda_create(struct sti_tvout *tvout); + +#endif diff --git a/drivers/gpu/drm/sti/sti_hdmi.c b/drivers/gpu/drm/sti/sti_hdmi.c index aa77510..7cad152 100644 --- a/drivers/gpu/drm/sti/sti_hdmi.c +++ b/drivers/gpu/drm/sti/sti_hdmi.c @@ -380,6 +380,548 @@ fail: return -1; }
+/* + * Start hdmi + * + * @connector: pointer on the tvout hdmi connector + * + * Return -1 if error occurs + */ +static int sti_hdmi_start(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + int ret = 0; + + DRM_DEBUG_DRIVER("\n"); + + /* Prepare/enable clocks */ + if (clk_prepare_enable(hdmi->clk_pix)) + DRM_ERROR("Failed to prepare/enable hdmi_pix clk\n"); + if (clk_prepare_enable(hdmi->clk_tmds)) + DRM_ERROR("Failed to prepare/enable hdmi_tmds clk\n"); + if (clk_prepare_enable(hdmi->clk_phy)) + DRM_ERROR("Failed to prepare/enable hdmi_rejec_pll clk\n"); + + hdmi->enabled = true; + + /* Program hdmi serializer and start phy */ + ret = hdmi_phy_start(hdmi); + if (ret) { + DRM_ERROR("Unable to start hdmi phy\n"); + return ret; + } + + /* Program hdmi active area */ + hdmi_active_area(hdmi); + + /* Enable working interrupts */ + writel(HDMI_WORKING_INT, hdmi->regs + HDMI_INT_EN); + + /* Program hdmi config */ + hdmi_config(hdmi); + + /* Program AVI infoframe */ + ret = hdmi_avi_infoframe_config(hdmi); + if (ret) + DRM_ERROR("Unable to configure AVI infoframe\n"); + + /* Sw reset */ + ret = hdmi_swreset(hdmi); + if (ret) + DRM_ERROR("Unable to perform the hdmi sw reset\n"); + + return ret; +} + +/* + * Stop hdmi + * + * @connector: pointer on the tvout hdmi connector + */ +static void sti_hdmi_stop(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + u32 val; + u32 mask; + + if (!hdmi->enabled) + return; + + DRM_DEBUG_DRIVER("\n"); + + /* Disable HDMI */ + mask = HDMI_CFG_DEVICE_EN; + val = ~HDMI_CFG_DEVICE_EN; + + hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask); + + /* Stop the phy */ + hdmi_phy_stop(hdmi); + + /* Disable/unprepare hdmi clock */ + clk_disable_unprepare(hdmi->clk_phy); + clk_disable_unprepare(hdmi->clk_tmds); + clk_disable_unprepare(hdmi->clk_pix); + + hdmi->enabled = false; +} + +/* + * Check if the drm display mode in supported by the hdmi + * + * @connector: pointer on the tvout hdmi connector + * @mode: drm display mode + * + * Return -1 if not supported + */ +#define CLK_TOLERANCE_HZ 50 +static int sti_hdmi_check_mode(struct sti_tvout_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + int target = mode->clock * 1000; + int target_min = target - CLK_TOLERANCE_HZ; + int target_max = target + CLK_TOLERANCE_HZ; + int result; + + result = clk_round_rate(hdmi->clk_pix, target); + + DRM_DEBUG_DRIVER("target rate = %d => available rate = %d\n", + target, result); + + if ((result < target_min) || (result > target_max)) { + DRM_DEBUG_DRIVER("hdmi pixclk=%d not supported\n", target); + return -1; + } + + return 0; +} + +/* + * Set the drm display mode in the local structure + * + * @connector: pointer on the tvout hdmi connector + * @mode: drm display mode + * + * Return -1 if error occurs + */ +/* FS bits */ +static int sti_hdmi_set_mode(struct sti_tvout_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + int ret; + + DRM_DEBUG_DRIVER("\n"); + + /* Copy the drm display mode in the connector local structure */ + memcpy(&hdmi->mode, mode, sizeof(struct drm_display_mode)); + + /* Update clock framerate according to the selected mode */ + ret = clk_set_rate(hdmi->clk_pix, mode->clock * 1000); + if (ret < 0) { + DRM_ERROR("Cannot set rate (%dHz) for hdmi_pix clk\n", + mode->clock * 1000); + return ret; + } + ret = clk_set_rate(hdmi->clk_phy, mode->clock * 1000); + if (ret < 0) { + DRM_ERROR("Cannot set rate (%dHz) for hdmi_rejection_pll clk\n", + mode->clock * 1000); + return ret; + } + + return 0; +} + +/* + * Detect if hdmi is connected + * + * @connector: pointer on the tvout hdmi connector + * + * Return true if hdmi cable is connected + */ +static bool sti_hdmi_detect(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + + DRM_DEBUG_DRIVER("\n"); + + if (hdmi->hpd) { + DRM_DEBUG_DRIVER("hdmi cable connected\n"); + return true; + } else + DRM_DEBUG_DRIVER("hdmi cable disconnected\n"); + + return false; +} + +/* + * Check if hdmi is enabled + * + * @connector: pointer on the tvout hdmi connector + * + * Return true if hdmi is enabled + */ +static bool sti_hdmi_is_enabled(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + + return hdmi->enabled; +} + +/* + * Prepare/configure hdmi + * + * @connector: pointer on the tvout hdmi connector + */ +static void sti_hdmi_prepare(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + + DRM_DEBUG_DRIVER("\n"); + + /* HDMI initialisation */ + writel(0x00000000, hdmi->regs + HDMI_CFG); + writel(0xffffffff, hdmi->regs + HDMI_INT_CLR); + + /* Ensure the PHY is completely powered down */ + hdmi_phy_stop(hdmi); + + /* Set the default channel data to be a dark red */ + writel(0x0000, hdmi->regs + HDMI_DFLT_CHL0_DAT); + writel(0x0000, hdmi->regs + HDMI_DFLT_CHL1_DAT); + writel(0x0060, hdmi->regs + HDMI_DFLT_CHL2_DAT); +} + +/* + * Debugfs + */ +#define HDMI_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \ + readl(hdmi->regs + reg)) +#define HDMI_DBG_DUMP_DI(reg, slot) HDMI_DBG_DUMP(reg(slot)) +#define MAX_STRING_LENGTH 40 + +static void hdmi_dbg_cfg(struct seq_file *m, int val) +{ + int tmp; + char str[MAX_STRING_LENGTH]; + static const char *const mode[] = { "DVI", "HDMI" }; + static const char *const enable[] = { "disable", "enable" }; + static const char *const oess_ess[] = { "OESS enable", "ESS enable" }; + static const char *const polarity[] = { "normal", "inverted" }; + + seq_puts(m, "\t"); + + tmp = (val & HDMI_CFG_HDMI_NOT_DVI) >> HDMI_CFG_HDMI_NOT_DVI_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "mode: %s", mode[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_CFG_HDCP_EN) >> HDMI_CFG_HDCP_EN_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "HDCP: %s", enable[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_CFG_ESS_NOT_OESS) >> HDMI_CFG_ESS_NOT_OESS_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "HDCP mode: %s", oess_ess[tmp]); + seq_printf(m, "%-40s", str); + + seq_printf(m, "\n%-40s", ""); + + tmp = (val & HDMI_CFG_SINK_TERM_DET_EN) >> + HDMI_CFG_SINK_TERM_DET_EN_SHIFT; + snprintf(str, MAX_STRING_LENGTH, + "Sink term detection: %s", enable[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_CFG_H_SYNC_POL_NEG) >> HDMI_CFG_H_SYNC_POL_NEG_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "Hsync polarity: %s", polarity[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_CFG_V_SYNC_POL_NEG) >> HDMI_CFG_V_SYNC_POL_NEG_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "Vsync polarity: %s", polarity[tmp]); + seq_printf(m, "%-40s", str); + + seq_printf(m, "\n%-40s", ""); + + tmp = (val & HDMI_CFG_422_EN) >> HDMI_CFG_422_EN_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "YUV422 format: %s", enable[tmp]); + seq_printf(m, "%-40s", str); +} + +static void hdmi_dbg_sta(struct seq_file *m, int val) +{ + int tmp; + char str[MAX_STRING_LENGTH]; + static const char *const sink_term[] = { "not present", "present" }; + static const char *const pll_lck[] = { "not locked", "locked" }; + static const char *const hot_plug[] = { "not connected", "connected" }; + + seq_puts(m, "\t"); + + tmp = (val & HDMI_STA_FIFO_SAMPLES) >> HDMI_STA_FIFO_SAMPLES_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "fifo: %d samples", tmp); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_STA_SINK_TERM) >> HDMI_STA_SINK_TERM_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "sink term: %s", sink_term[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_STA_DLL_LCK) >> HDMI_STA_DLL_LCK_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "pll: %s", pll_lck[tmp]); + seq_printf(m, "%-40s", str); + + seq_printf(m, "\n%-40s", ""); + + tmp = (val & HDMI_STA_HOT_PLUG) >> HDMI_STA_HOT_PLUG_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "hdmi cable: %s", hot_plug[tmp]); + seq_printf(m, "%-40s", str); +} + +static void hdmi_dbg_sw_di_cfg(struct seq_file *m, int val) +{ + int tmp; + char str[MAX_STRING_LENGTH]; + static const char *const en_di[] = { "no transmission", + "single transmission", "once every field", "once every frame" + }; + + seq_puts(m, "\t"); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 1)); + snprintf(str, MAX_STRING_LENGTH, "Data island 1: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 2)) >> 4; + snprintf(str, MAX_STRING_LENGTH, "Data island 2: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 3)) >> 8; + snprintf(str, MAX_STRING_LENGTH, "Data island 3: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + seq_printf(m, "\n%-40s", ""); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 4)) >> 12; + snprintf(str, MAX_STRING_LENGTH, "Data island 4: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 5)) >> 16; + snprintf(str, MAX_STRING_LENGTH, "Data island 5: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 6)) >> 20; + snprintf(str, MAX_STRING_LENGTH, "Data island 6: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); +} + +static void hdmi_dbg_xmin(struct seq_file *m, int val) +{ + seq_printf(m, "\tXmin: %4d", val & 0x0FFF); +} + +static void hdmi_dbg_xmax(struct seq_file *m, int val) +{ + seq_printf(m, "\tXmax: %4d", val & 0x0FFF); +} + +static void hdmi_dbg_ymin(struct seq_file *m, int val) +{ + seq_printf(m, "\tYmin: %4d", val & 0x0FFF); +} + +static void hdmi_dbg_ymax(struct seq_file *m, int val) +{ + seq_printf(m, "\tYmax: %4d", val & 0x0FFF); +} + +static void sti_hdmi_dbg_show(struct sti_tvout_connector *connector, + struct seq_file *m) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + struct drm_display_mode *mode = &hdmi->mode; + struct hdmi_avi_infoframe info; + char str[MAX_STRING_LENGTH]; + + seq_puts(m, "\n"); + seq_printf(m, "\nHDMI: (virt base addr = 0x%p)", hdmi->regs); + HDMI_DBG_DUMP(HDMI_CFG); + hdmi_dbg_cfg(m, readl(hdmi->regs + HDMI_CFG)); + HDMI_DBG_DUMP(HDMI_INT_EN); + HDMI_DBG_DUMP(HDMI_INT_STA); + HDMI_DBG_DUMP(HDMI_INT_CLR); + HDMI_DBG_DUMP(HDMI_STA); + hdmi_dbg_sta(m, readl(hdmi->regs + HDMI_STA)); + HDMI_DBG_DUMP(HDMI_ACTIVE_VID_XMIN); + hdmi_dbg_xmin(m, readl(hdmi->regs + HDMI_ACTIVE_VID_XMIN)); + HDMI_DBG_DUMP(HDMI_ACTIVE_VID_XMAX); + hdmi_dbg_xmax(m, readl(hdmi->regs + HDMI_ACTIVE_VID_XMAX)); + HDMI_DBG_DUMP(HDMI_ACTIVE_VID_YMIN); + hdmi_dbg_ymin(m, readl(hdmi->regs + HDMI_ACTIVE_VID_YMIN)); + HDMI_DBG_DUMP(HDMI_ACTIVE_VID_YMAX); + hdmi_dbg_ymax(m, readl(hdmi->regs + HDMI_ACTIVE_VID_YMAX)); + HDMI_DBG_DUMP(HDMI_DFLT_CHL0_DAT); + HDMI_DBG_DUMP(HDMI_DFLT_CHL1_DAT); + HDMI_DBG_DUMP(HDMI_DFLT_CHL2_DAT); + HDMI_DBG_DUMP(HDMI_SW_DI_CFG); + hdmi_dbg_sw_di_cfg(m, readl(hdmi->regs + HDMI_SW_DI_CFG)); + if (hdmi->tx3g0c55phy) + sti_hdmi_tx3g0c55phy_show(hdmi, m); + else + sti_hdmi_tx3g4c28phy_show(hdmi, m); + seq_puts(m, "\n"); + + seq_printf(m, "\nAVI Infoframe (Data Island slot N=%d):", + HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_HEAD_WORD, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD0, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD1, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD2, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD3, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD4, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD5, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD6, HDMI_IFRAME_SLOT_AVI); + seq_puts(m, "\n"); + if (drm_hdmi_avi_infoframe_from_display_mode(&info, mode) == 0) { + static const char *const colorspace[] = { + "RGB", "YUV422", "YUV444" }; + static const char *const scan_mode[] = { + "none", "overscan", "underscan" }; + static const char *const colorimetry[] = { + "none", "ITU 601", "ITU 709", "extended" }; + static const char *const ext_colorimetry[] = { + "xvYCC 601", "xvYCC 709", "S YCC 601", "Adobe YCC 601", + "Adobe RGB" }; + static const char *const pict_aspect[] = { + "none", "4:3", "16:9" }; + static const char *const active_aspect[] = { + "", "", "16:9 top", "14:9 top", "16:9 center", "", "", + "", "picture", "4:3", "16:9", "14:9", "", "4:3 SP 14:9", + "16:9 SP 14:9", "16:9 SP 4:3" }; + static const char *const quant_range[] = { + "default", "4:3", "16:9" }; + static const char *const nups[] = { + "unknown", "horizontal", "vertical", "both" }; + static const char *const ycc_quant_range[] = { + "limited", "full" }; + static const char *const content_type[] = { + "none", "photo", "cinema", "game" }; + + snprintf(str, MAX_STRING_LENGTH, "\tversion:"); + seq_printf(m, "%-25s %d\n", str, info.version); + snprintf(str, MAX_STRING_LENGTH, "\tlength:"); + seq_printf(m, "%-25s %d\n", str, info.length); + snprintf(str, MAX_STRING_LENGTH, "\tcolorspace:"); + seq_printf(m, "%-25s %s\n", str, colorspace[info.colorspace]); + snprintf(str, MAX_STRING_LENGTH, "\tscan mode:"); + seq_printf(m, "%-25s %s\n", str, scan_mode[info.scan_mode]); + snprintf(str, MAX_STRING_LENGTH, "\tcolorimetry:"); + seq_printf(m, "%-25s %s\n", str, colorimetry[info.colorimetry]); + if (info.colorimetry == HDMI_COLORIMETRY_EXTENDED) { + snprintf(str, MAX_STRING_LENGTH, + " extended colorimetry:"); + seq_printf(m, "%-25s %s\n", str, + ext_colorimetry[info.extended_colorimetry]); + } + snprintf(str, MAX_STRING_LENGTH, "\tpicture aspect:"); + seq_printf(m, "%-25s %s\n", str, + pict_aspect[info.picture_aspect]); + snprintf(str, MAX_STRING_LENGTH, "\tactive aspect:"); + seq_printf(m, "%-25s %s\n", str, + active_aspect[info.active_aspect]); + snprintf(str, MAX_STRING_LENGTH, "\tquantization range:"); + seq_printf(m, "%-25s %s\n", str, + quant_range[info.quantization_range]); + snprintf(str, MAX_STRING_LENGTH, "\tycc quantization range:"); + seq_printf(m, "%-25s %s\n", str, + ycc_quant_range[info.ycc_quantization_range]); + snprintf(str, MAX_STRING_LENGTH, "\tnups:"); + seq_printf(m, "%-25s %s\n", str, nups[info.nups]); + snprintf(str, MAX_STRING_LENGTH, "\tpixel repeat:"); + seq_printf(m, "%-25s %d\n", str, info.pixel_repeat); + snprintf(str, MAX_STRING_LENGTH, "\tactive info valid:"); + seq_printf(m, "%-25s %s\n", str, + info.pixel_repeat ? "true" : "false"); + snprintf(str, MAX_STRING_LENGTH, "\titc:"); + seq_printf(m, "%-25s %s\n", str, info.itc ? "true" : "false"); + snprintf(str, MAX_STRING_LENGTH, "\ttop bar:"); + seq_printf(m, "%-25s %d\n", str, info.top_bar); + snprintf(str, MAX_STRING_LENGTH, "\tbottom bar:"); + seq_printf(m, "%-25s %d\n", str, info.bottom_bar); + snprintf(str, MAX_STRING_LENGTH, "\tleft bar:"); + seq_printf(m, "%-25s %d\n", str, info.left_bar); + snprintf(str, MAX_STRING_LENGTH, "\tright bar:"); + seq_printf(m, "%-25s %d\n", str, info.right_bar); + snprintf(str, MAX_STRING_LENGTH, "\tcontent type:"); + seq_printf(m, "%-25s %s\n", str, + content_type[info.content_type]); + snprintf(str, MAX_STRING_LENGTH, "\tCEA video code:"); + seq_printf(m, "%-25s %d\n", str, info.video_code); + } + + seq_printf(m, "\nHDMI mode: %dx%d%s @%d", + hdmi->mode.hdisplay, + hdmi->mode.vdisplay, + (hdmi->mode.flags & DRM_MODE_FLAG_INTERLACE) ? + "i" : "p", hdmi->mode.vrefresh); +} + +/* + * create the hdmi output + * + * @tvout: pointer on the tvout information + * + * Return pointer on the created tvout connector or NULL if error occurs + */ +struct sti_tvout_connector *sti_hdmi_create(struct sti_tvout *tvout) +{ + struct sti_hdmi *hdmi = container_of(hdmi_dev, struct sti_hdmi, dev); + struct device *dev = &hdmi->dev; + struct sti_tvout_connector *connector; + + DRM_DEBUG_DRIVER("\n"); + + if (!hdmi) { + DRM_INFO("%s: No hdmi device probed\n", __func__); + return NULL; + } + + connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL); + if (!connector) { + DRM_ERROR("Failed to allocate memory for connector\n"); + goto connector_alloc_failed; + } + + /* Set the drm device handle */ + hdmi->drm_dev = tvout->drm_dev; + + /* DDC i2c driver */ + if (i2c_add_driver(&ddc_driver)) { + DRM_ERROR("Failed to register ddc i2c driver\n"); + goto i2c_failed; + } + + /* Enable default interrupts */ + writel(HDMI_DEFAULT_INT, hdmi->regs + HDMI_INT_EN); + + connector->priv = (void *)hdmi; + connector->start = sti_hdmi_start; + connector->stop = sti_hdmi_stop; + connector->get_modes = sti_hdmi_get_modes; + connector->check_mode = sti_hdmi_check_mode; + connector->set_mode = sti_hdmi_set_mode; + connector->detect = sti_hdmi_detect; + connector->is_enabled = sti_hdmi_is_enabled; + connector->prepare = sti_hdmi_prepare; + connector->dbg_show = sti_hdmi_dbg_show; + + return connector; + +i2c_failed: + devm_kfree(dev, connector); +connector_alloc_failed: + return NULL; +} + static int sti_hdmi_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); diff --git a/drivers/gpu/drm/sti/sti_hdmi.h b/drivers/gpu/drm/sti/sti_hdmi.h index c14c683..ed90a2c 100644 --- a/drivers/gpu/drm/sti/sti_hdmi.h +++ b/drivers/gpu/drm/sti/sti_hdmi.h @@ -11,6 +11,8 @@
#include <drm/drmP.h>
+#include "sti_tvout.h" + /* HDMI v2.9 macro cell */ #define HDMI_CFG 0x0000 #define HDMI_INT_EN 0x0004 @@ -181,6 +183,7 @@ struct hdmi_phy_config { u32 config[4]; };
+struct sti_tvout_connector *sti_hdmi_create(struct sti_tvout *tvout); void sti_hdmi_attach_ddc_client(struct i2c_client *ddc);
int sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi); diff --git a/drivers/gpu/drm/sti/sti_tvout.c b/drivers/gpu/drm/sti/sti_tvout.c new file mode 100644 index 0000000..7c61ba1 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_tvout.c @@ -0,0 +1,703 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * Author: Vincent Abriou vincent.abriou@st.com for STMicroelectronics + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "sti_tvout.h" +#include "sti_hdmi.h" +#include "sti_hda.h" + +/* glue regsiters */ +#define TVO_CSC_MAIN_M0 0x000 +#define TVO_CSC_MAIN_M1 0x004 +#define TVO_CSC_MAIN_M2 0x008 +#define TVO_CSC_MAIN_M3 0x00c +#define TVO_CSC_MAIN_M4 0x010 +#define TVO_CSC_MAIN_M5 0x014 +#define TVO_CSC_MAIN_M6 0x018 +#define TVO_CSC_MAIN_M7 0x01c +#define TVO_MAIN_IN_VID_FORMAT 0x030 +#define TVO_CSC_AUX_M0 0x100 +#define TVO_CSC_AUX_M1 0x104 +#define TVO_CSC_AUX_M2 0x108 +#define TVO_CSC_AUX_M3 0x10c +#define TVO_CSC_AUX_M4 0x110 +#define TVO_CSC_AUX_M5 0x114 +#define TVO_CSC_AUX_M6 0x118 +#define TVO_CSC_AUX_M7 0x11c +#define TVO_AUX_IN_VID_FORMAT 0x130 +#define TVO_VIP_HDF 0x400 +#define TVO_HD_SYNC_SEL 0x418 +#define TVO_HD_DAC_CFG_OFF 0x420 +#define TVO_VIP_HDMI 0x500 +#define TVO_HDMI_FORCE_COLOR_0 0x504 +#define TVO_HDMI_FORCE_COLOR_1 0x508 +#define TVO_HDMI_CLIP_VALUE_B_CB 0x50c +#define TVO_HDMI_CLIP_VALUE_Y_G 0x510 +#define TVO_HDMI_CLIP_VALUE_R_CR 0x514 +#define TVO_HDMI_SYNC_SEL 0x518 +#define TVO_HDMI_DFV_OBS 0x540 + +#define TVO_IN_FMT_SIGNED (1 << 0) +#define TVO_SYNC_EXT (1 << 4) + +#define TVO_VIP_REORDER_R_SHIFT 24 +#define TVO_VIP_REORDER_G_SHIFT 20 +#define TVO_VIP_REORDER_B_SHIFT 16 +#define TVO_VIP_REORDER_MASK 0x3 +#define TVO_VIP_REORDER_Y_G_SEL 0 +#define TVO_VIP_REORDER_CB_B_SEL 1 +#define TVO_VIP_REORDER_CR_R_SEL 2 + +#define TVO_VIP_CLIP_SHIFT 8 +#define TVO_VIP_CLIP_MASK 0x7 +#define TVO_VIP_CLIP_DISABLED 0 +#define TVO_VIP_CLIP_EAV_SAV 1 +#define TVO_VIP_CLIP_LIMITED_RANGE_RGB_Y 2 +#define TVO_VIP_CLIP_LIMITED_RANGE_CB_CR 3 +#define TVO_VIP_CLIP_PROG_RANGE 4 + +#define TVO_VIP_RND_SHIFT 4 +#define TVO_VIP_RND_MASK 0x3 +#define TVO_VIP_RND_8BIT_ROUNDED 0 +#define TVO_VIP_RND_10BIT_ROUNDED 1 +#define TVO_VIP_RND_12BIT_ROUNDED 2 + +#define TVO_VIP_SEL_INPUT_MASK 0xf +#define TVO_VIP_SEL_INPUT_MAIN 0x0 +#define TVO_VIP_SEL_INPUT_AUX 0x8 +#define TVO_VIP_SEL_INPUT_FORCE_COLOR 0xf +#define TVO_VIP_SEL_INPUT_BYPASS_MASK 0x1 +#define TVO_VIP_SEL_INPUT_BYPASSED 1 + +#define TVO_SYNC_MAIN_VTG_SET_REF 0x00 +#define TVO_SYNC_MAIN_VTG_SET_1 0x01 +#define TVO_SYNC_MAIN_VTG_SET_2 0x02 +#define TVO_SYNC_MAIN_VTG_SET_3 0x03 +#define TVO_SYNC_MAIN_VTG_SET_4 0x04 +#define TVO_SYNC_MAIN_VTG_SET_5 0x05 +#define TVO_SYNC_MAIN_VTG_SET_6 0x06 +#define TVO_SYNC_AUX_VTG_SET_REF 0x10 +#define TVO_SYNC_AUX_VTG_SET_1 0x11 +#define TVO_SYNC_AUX_VTG_SET_2 0x12 +#define TVO_SYNC_AUX_VTG_SET_3 0x13 +#define TVO_SYNC_AUX_VTG_SET_4 0x14 +#define TVO_SYNC_AUX_VTG_SET_5 0x15 +#define TVO_SYNC_AUX_VTG_SET_6 0x16 + +#define TVO_SYNC_HD_DCS_SHIFT 8 + +/* Preformatter conversion matrix */ +static const u32 rgb_to_ycbcr_601[8] = { + 0xF927082E, 0x04C9FEAB, 0x01D30964, 0xFA95FD3D, + 0x0000082E, 0x00002000, 0x00002000, 0x00000000 +}; + +/* 709 RGB to YCbCr */ +static const u32 rgb_to_ycbcr_709[8] = { + 0xF891082F, 0x0367FF40, 0x01280B71, 0xF9B1FE20, + 0x0000082F, 0x00002000, 0x00002000, 0x00000000 +}; + +/* + * Helper to write bit field + * + * @addr: register to update + * @val: value to write + * @mask: bit field mask to use + */ +static inline void tvout_reg_writemask(void __iomem *addr, u32 val, u32 mask) +{ + u32 old = readl(addr); + + val = (val & mask) | (old & ~mask); + writel(val, addr); +} + +/* + * Set the Channel order of a VIP + * + * @vip_reg: VIP regsiter + * @cr_r + * @y_g + * @cb_c : values for each output + */ +static void tvout_vip_set_color_order(void __iomem *vip_reg, + u32 cr_r, u32 y_g, u32 cb_b) +{ + u32 val, mask; + + mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_R_SHIFT; + mask |= TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_G_SHIFT; + mask |= TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_B_SHIFT; + val = cr_r << TVO_VIP_REORDER_R_SHIFT; + val |= y_g << TVO_VIP_REORDER_G_SHIFT; + val |= cb_b << TVO_VIP_REORDER_B_SHIFT; + tvout_reg_writemask(vip_reg, val, mask); +} + +/* + * Set the clipping mode of a VIP + * + * @vip_reg: VIP regsiter + * @range : clipping range + */ +static void tvout_vip_set_clip_mode(void __iomem *vip_reg, u32 range) +{ + tvout_reg_writemask(vip_reg, + range << TVO_VIP_CLIP_SHIFT, + TVO_VIP_CLIP_MASK << TVO_VIP_CLIP_SHIFT); +} + +/* + * Set the rounded value of a VIP + * + * @vip_reg: VIP regsiter + * @rnd: rounded val per component + */ +static void tvout_vip_set_rnd(void __iomem *vip_reg, u32 rnd) +{ + tvout_reg_writemask(vip_reg, + rnd << TVO_VIP_RND_SHIFT, + TVO_VIP_RND_MASK << TVO_VIP_RND_SHIFT); +} + +/* + * Select the VIP input + * + * @vip_reg: VIP regsiter + * @sel_input: selected_input (main/aux + conv) + */ +static void tvout_vip_set_sel_input(void __iomem *vip_reg, + bool main_path, + bool sel_input_logic_inverted, + enum sti_tvout_video_out_type video_out) +{ + u32 sel_input; + + if (main_path) + sel_input = TVO_VIP_SEL_INPUT_MAIN; + else + sel_input = TVO_VIP_SEL_INPUT_AUX; + + switch (video_out) { + case STI_TVOUT_VIDEO_OUT_RGB: + sel_input |= TVO_VIP_SEL_INPUT_BYPASSED; + break; + case STI_TVOUT_VIDEO_OUT_YUV: + sel_input &= ~TVO_VIP_SEL_INPUT_BYPASSED; + break; + } + + /* On stih407 chip the sel_input bypass mode logic is inverted */ + if (sel_input_logic_inverted) + sel_input = sel_input ^ TVO_VIP_SEL_INPUT_BYPASS_MASK; + + tvout_reg_writemask(vip_reg, sel_input, TVO_VIP_SEL_INPUT_MASK); +} + +/* + * Select the input video signed or unsigned + * + * @vip_reg: VIP regsiter + * @in_vid_signed: used video input format + */ +static void tvout_vip_set_in_vid_fmt(void __iomem *vip_reg, u32 in_vid_fmt) +{ + tvout_reg_writemask(vip_reg, in_vid_fmt, TVO_IN_FMT_SIGNED); +} + +/* + * Start VIP block for HDMI output + * + * @tvout: pointer on tvout structure + * @main_path: true if main path has to be used in the vip configuration + * else aux path is used. + */ +static void tvout_hdmi_start(struct sti_tvout *tvout, bool main_path) +{ + struct device_node *node = tvout->dev->of_node; + bool sel_input_logic_inverted = false; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (main_path) { + DRM_DEBUG_DRIVER("main vip for hdmi\n"); + /* Select the input sync for hdmi = VTG set 1 */ + writel(TVO_SYNC_MAIN_VTG_SET_1, + tvout->regs + TVO_HDMI_SYNC_SEL); + } else { + DRM_DEBUG_DRIVER("aux vip for hdmi\n"); + /* Select the input sync for hdmi = VTG set 1 */ + writel(TVO_SYNC_AUX_VTG_SET_1, tvout->regs + TVO_HDMI_SYNC_SEL); + } + + /* Set color channel order */ + tvout_vip_set_color_order(tvout->regs + TVO_VIP_HDMI, + TVO_VIP_REORDER_CR_R_SEL, + TVO_VIP_REORDER_Y_G_SEL, + TVO_VIP_REORDER_CB_B_SEL); + + /* Set clipping mode (Limited range RGB/Y) */ + tvout_vip_set_clip_mode(tvout->regs + TVO_VIP_HDMI, + TVO_VIP_CLIP_LIMITED_RANGE_RGB_Y); + + /* Set round mode (rounded to 8-bit per component) */ + tvout_vip_set_rnd(tvout->regs + TVO_VIP_HDMI, TVO_VIP_RND_8BIT_ROUNDED); + + if (of_device_is_compatible(node, "st,stih407-tvout")) { + /* Set input video format */ + tvout_vip_set_in_vid_fmt(tvout->regs + TVO_MAIN_IN_VID_FORMAT, + TVO_IN_FMT_SIGNED); + sel_input_logic_inverted = true; + } + + /* Input selection */ + tvout_vip_set_sel_input(tvout->regs + TVO_VIP_HDMI, + main_path, + sel_input_logic_inverted, + STI_TVOUT_VIDEO_OUT_RGB); +} + +/* + * Prepare/configure VIP block for HDMI output + * + * @tvout: pointer on tvout structure + */ +static void tvout_hdmi_prepare(struct sti_tvout *tvout) +{ + dev_dbg(tvout->dev, "%s\n", __func__); + + /* Reset VIP register */ + writel(0x00000000, tvout->regs + TVO_VIP_HDMI); +} + +/* + * Disable HDMI VIP + * + * @tvout: pointer on tvout structure + */ +static void tvout_hdmi_stop(struct sti_tvout *tvout) +{ + dev_dbg(tvout->dev, "%s\n", __func__); + + /* Reset VIP register */ + writel(0x00000000, tvout->regs + TVO_VIP_HDMI); +} + +/* + * Start HDF VIP and HD DAC + * + * @tvout: pointer on tvout structure + * @main_path: true if main path has to be used in the vip configuration + * else aux path is used. + */ +static void tvout_hda_start(struct sti_tvout *tvout, bool main_path) +{ + struct device_node *node = tvout->dev->of_node; + bool sel_input_logic_inverted = false; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (!main_path) { + DRM_ERROR("HD Analog on aux not implemented\n"); + return; + } + + DRM_DEBUG_DRIVER("main vip for HDF\n"); + + /* Set color channel order */ + tvout_vip_set_color_order(tvout->regs + TVO_VIP_HDF, + TVO_VIP_REORDER_CR_R_SEL, + TVO_VIP_REORDER_Y_G_SEL, + TVO_VIP_REORDER_CB_B_SEL); + + /* Set clipping mode (Limited range RGB/Y) */ + tvout_vip_set_clip_mode(tvout->regs + TVO_VIP_HDF, + TVO_VIP_CLIP_LIMITED_RANGE_CB_CR); + + /* Set round mode (rounded to 10-bit per component) */ + tvout_vip_set_rnd(tvout->regs + TVO_VIP_HDF, TVO_VIP_RND_10BIT_ROUNDED); + + if (of_device_is_compatible(node, "st,stih407-tvout")) { + /* Set input video format */ + tvout_vip_set_in_vid_fmt(tvout->regs + TVO_MAIN_IN_VID_FORMAT, + TVO_IN_FMT_SIGNED); + sel_input_logic_inverted = true; + } + + /* Input selection */ + tvout_vip_set_sel_input(tvout->regs + TVO_VIP_HDF, + main_path, + sel_input_logic_inverted, + STI_TVOUT_VIDEO_OUT_YUV); + + /* Select the input sync for HD analog = VTG set 3 + * and HD DCS = VTG set 2 */ + writel((TVO_SYNC_MAIN_VTG_SET_2 << TVO_SYNC_HD_DCS_SHIFT) | + TVO_SYNC_MAIN_VTG_SET_3, tvout->regs + TVO_HD_SYNC_SEL); + + /* Power up HD DAC */ + writel(0, tvout->regs + TVO_HD_DAC_CFG_OFF); +} + +/* + * Prepare/configure HDF VIP and HD DAC + * + * @tvout: pointer on tvout structure + */ +static void tvout_hda_prepare(struct sti_tvout *tvout) +{ + dev_dbg(tvout->dev, "%s\n", __func__); + + /* Reset VIP register */ + writel(0x00000000, tvout->regs + TVO_VIP_HDF); + + /* Power down HD DAC */ + writel(1, tvout->regs + TVO_HD_DAC_CFG_OFF); +} + +/* + * Stop HDF VIP and HD DAC + * + * @tvout: pointer on tvout structure + */ +static void tvout_hda_stop(struct sti_tvout *tvout) +{ + dev_dbg(tvout->dev, "%s\n", __func__); + + /* Reset VIP register */ + writel(0x00000000, tvout->regs + TVO_VIP_HDF); + + /* Power down HD DAC */ + writel(1, tvout->regs + TVO_HD_DAC_CFG_OFF); +} + +/* + * Check if the connector is connected + * + * @tvout: pointer on tvout structure + * @type: type of connector + * + * Return true if connected + */ +bool sti_tvout_connector_detect(struct sti_tvout *tvout, + enum sti_tvout_connector_type type) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->detect) + return connector->detect(connector); + + return false; +} + +/* + * Forward drm display mode information to the connector + * + * @tvout: pointer on tvout structure + * @mode: selected display mode + * @type: type of connector + * + * Return -1 if error occurs + */ +int sti_tvout_set_mode(struct sti_tvout *tvout, struct drm_display_mode *mode, + enum sti_tvout_connector_type type) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->set_mode) + return connector->set_mode(connector, mode); + + return -1; +} + +/* + * Get modes + * + * @tvout: pointer on tvout structure + * @type: type of connector + * @drm_connector: pointer on the connector + * + * Return Nb of modes, -1 if error + */ +int sti_tvout_get_modes(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + struct drm_connector *drm_connector) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->get_modes) + return connector->get_modes(drm_connector); + + return -1; +} + +/* + * Check if the mode is supported + * + * function used to filter unsupported mode + * + * @tvout: pointer on tvout structure + * @type: type of connector + * @mode: drm display mode + * + * Return -1 if error occurs + */ +int sti_tvout_check_mode(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + struct drm_display_mode *mode) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->check_mode) + return connector->check_mode(connector, mode); + + return -1; +} + +/* + * Prepare / initialize depending on the connector type + * + * @tvout: pointer on tvout structure + * @type: type of connector + * + * Return -1 if error occurs + */ +int sti_tvout_prepare(struct sti_tvout *tvout, + enum sti_tvout_connector_type type) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + int ret = -1; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->prepare) + connector->prepare(connector); + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + tvout_hdmi_prepare(tvout); + ret = 0; + break; + case STI_TVOUT_CONNECTOR_HDA: + tvout_hda_prepare(tvout); + ret = 0; + break; + case STI_TVOUT_CONNECTOR_DVO: + case STI_TVOUT_CONNECTOR_DENC: + default: + /* Not yet supported */ + ret = -1; + break; + } + + return ret; +} + +/* + * Commit / start depending on the connector type + * + * @tvout: pointer on tvout structure + * @type: type of connector + * @main_path: true if main path need to be use (false for aux path) + * + * Return -1 if error occurs + */ +int sti_tvout_commit(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, bool main_path) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + int ret = 0; + u32 matrix_offset; + int i; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (!connector) + return -1; + + connector->main_path = main_path; + + if (connector->start) { + ret = connector->start(connector); + if (ret) { + DRM_ERROR("Unable to properly start connector\n"); + return -1; + } + } + + /* Set preformatter matrix */ + matrix_offset = main_path ? TVO_CSC_MAIN_M0 : TVO_CSC_AUX_M0; + for (i = 0; i < 8; i++) + writel(rgb_to_ycbcr_601[i], + tvout->regs + matrix_offset + (i * 4)); + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + tvout_hdmi_start(tvout, main_path); + return 0; + case STI_TVOUT_CONNECTOR_HDA: + tvout_hda_start(tvout, main_path); + return 0; + case STI_TVOUT_CONNECTOR_DVO: + case STI_TVOUT_CONNECTOR_DENC: + default: + /* Not yet supported */ + return -1; + } +} + +/* + * Disable / stop the tvout depending on the connector type + * + * @tvout: pointer on tvout structure + * @type: type of connector + */ +void sti_tvout_disable(struct sti_tvout *tvout, + enum sti_tvout_connector_type type) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + tvout_hdmi_stop(tvout); + break; + case STI_TVOUT_CONNECTOR_HDA: + tvout_hda_stop(tvout); + break; + case STI_TVOUT_CONNECTOR_DVO: + case STI_TVOUT_CONNECTOR_DENC: + default: + /* Not yet supported */ + return; + } + + if (connector) + if (connector->stop) + connector->stop(connector); +} + +static int sti_tvout_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct device_node *node = dev->of_node; + struct sti_tvout *tvout; + struct resource *res; + + dev_dbg(dev, "%s\n", __func__); + + if (!node) { + DRM_ERROR("no device node\n"); + return -ENODEV; + } + + tvout = devm_kzalloc(dev, sizeof(*tvout), GFP_KERNEL); + if (!tvout) { + DRM_ERROR("failed to allocate compositor context\n"); + return -ENOMEM; + } + DRM_DEBUG_DRIVER("tvout %p\n", tvout); + tvout->dev = dev; + + /* Get Memory ressources */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tvout-reg"); + if (!res) { + DRM_ERROR("Invalid glue resource\n"); + return -ENOMEM; + } + tvout->regs = devm_ioremap_nocache(dev, res->start, resource_size(res)); + if (IS_ERR(tvout->regs)) + return PTR_ERR(tvout->regs); + + /* Get reset resources */ + tvout->reset = devm_reset_control_get(dev, "tvout"); + /* Take tvout out of reset */ + if (!IS_ERR(tvout->reset)) + reset_control_deassert(tvout->reset); + + /* List supported tvout connector */ + tvout->connector_create[STI_TVOUT_CONNECTOR_HDMI] = sti_hdmi_create; + tvout->connector_create[STI_TVOUT_CONNECTOR_HDA] = sti_hda_create; + tvout->connector_create[STI_TVOUT_CONNECTOR_DVO] = NULL; + tvout->connector_create[STI_TVOUT_CONNECTOR_DENC] = NULL; + + platform_set_drvdata(pdev, tvout); + + return 0; +} + +static void sti_tvout_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops sti_tvout_ops = { + .bind = sti_tvout_bind, + .unbind = sti_tvout_unbind, +}; + +static int sti_tvout_probe(struct platform_device *pdev) +{ + DRM_INFO("%s\n", __func__); + return component_add(&pdev->dev, &sti_tvout_ops); +} + +static int sti_tvout_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sti_tvout_ops); + return 0; +} + +static struct of_device_id tvout_match_types[] = { + { + .compatible = "st,stih416-tvout", + }, + { + .compatible = "st,stih407-tvout", + }, + { /* end node */ } +}; +MODULE_DEVICE_TABLE(of, tvout_match_types); + +struct platform_driver sti_tvout_driver = { + .driver = { + .name = "sti-tvout", + .owner = THIS_MODULE, + .of_match_table = tvout_match_types, + }, + .probe = sti_tvout_probe, + .remove = sti_tvout_remove, +}; + +module_platform_driver(sti_tvout_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sti/sti_tvout.h b/drivers/gpu/drm/sti/sti_tvout.h new file mode 100644 index 0000000..f61a49c --- /dev/null +++ b/drivers/gpu/drm/sti/sti_tvout.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Vincent Abriou vincent.abriou@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_TVOUT_H_ +#define _STI_TVOUT_H_ + +#include <linux/clk.h> + +/* + * STI TVout connector structure + * + * @priv: private structure associated to the connector type + * @start: start the connector + * @stop: stop the connector + * @get_modes: get modes potentially supported + * @check_mode: check if a mode is really supported + * @set_mode: set the drm display mode in the specific connector structure + * @detect: detect if connector is connected + * @prepare: prepare the connector + * @is_enabled: is the connector enabled + * @dbg_show: dump debug information + */ +struct sti_tvout_connector { + void *priv; + bool main_path; + int (*start)(struct sti_tvout_connector *connector); + void (*stop)(struct sti_tvout_connector *connector); + int (*get_modes)(struct drm_connector *drm_connector); + int (*check_mode)(struct sti_tvout_connector *connector, + struct drm_display_mode *mode); + int (*set_mode)(struct sti_tvout_connector *connector, + struct drm_display_mode *mode); + bool (*detect)(struct sti_tvout_connector *connector); + void (*prepare)(struct sti_tvout_connector *connector); + bool (*is_enabled)(struct sti_tvout_connector *connector); + void (*dbg_show)(struct sti_tvout_connector *connector, + struct seq_file *m); +}; + +/* + * enum listing the supported connector + */ +enum sti_tvout_connector_type { + STI_TVOUT_CONNECTOR_HDMI, + STI_TVOUT_CONNECTOR_HDA, + STI_TVOUT_CONNECTOR_DVO, + STI_TVOUT_CONNECTOR_DENC, + STI_TVOUT_CONNECTOR_MAX, +}; + +/* + * enum listing the supported output data format + */ +enum sti_tvout_video_out_type { + STI_TVOUT_VIDEO_OUT_RGB, + STI_TVOUT_VIDEO_OUT_YUV, +}; + +/* + * STI TVout structure + * + * @dev: pointer to driver device + * @regs: registers + * @reset: reset control of the tvout + * @hw_id: HW revision of the IP + * @connector_create: list of function to register a connector + * @connector: list of registered connector + */ +struct sti_tvout { + struct device *dev; + struct drm_device *drm_dev; + void __iomem *regs; + struct reset_control *reset; + int hw_id; + struct sti_tvout_connector *(*connector_create[STI_TVOUT_CONNECTOR_MAX]) + (struct sti_tvout *tvout); + struct sti_tvout_connector *connector[STI_TVOUT_CONNECTOR_MAX]; +}; + +bool sti_tvout_connector_detect(struct sti_tvout *tvout, + enum sti_tvout_connector_type type); +int sti_tvout_set_mode(struct sti_tvout *tvout, + struct drm_display_mode *mode, + enum sti_tvout_connector_type type); +int sti_tvout_get_modes(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + struct drm_connector *drm_connector); +int sti_tvout_check_mode(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + struct drm_display_mode *mode); +int sti_tvout_prepare(struct sti_tvout *tvout, + enum sti_tvout_connector_type type); +int sti_tvout_commit(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + bool main_path); +void sti_tvout_disable(struct sti_tvout *tvout, + enum sti_tvout_connector_type type); + +int sti_tvout_hdmi_dbg_show(struct seq_file *m, void *arg); +int sti_tvout_hda_dbg_show(struct seq_file *m, void *arg); + +#endif
STI hardware have various input sub-devices before mixing block. Each type of sub-device have different capabilities for scaling, filtering or accepted pixel format. This layer interface abstract those differences and make the interaction with compositor more simple.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/sti_layer.h | 104 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_layer.h
diff --git a/drivers/gpu/drm/sti/sti_layer.h b/drivers/gpu/drm/sti/sti_layer.h new file mode 100644 index 0000000..ed8386a --- /dev/null +++ b/drivers/gpu/drm/sti/sti_layer.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * 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)) + + +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 +}; + +struct sti_fps_info { + bool output; + int curr_frame_counter; + int last_frame_counter; + struct timespec last_timestamp; +}; + +/* + * 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 + * @zorder: layer z-order + * @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 + * @fps_info: frame per second info + */ +struct sti_layer { + struct drm_plane plane; + struct drm_framebuffer *fb; + struct drm_display_mode *mode; + enum sti_layer_desc desc; + int zorder; + 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; + int pitches[4]; + int offsets[4]; + dma_addr_t paddr; + struct sti_fps_info fps_info; +}; + +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 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); +int sti_layer_get_nb_formats(struct sti_layer *layer); +struct sti_layer *sti_layer_find_layer(struct sti_layer *layer[], + enum sti_layer_desc desc); +const char *sti_layer_to_str(struct sti_layer *layer); +#endif
Generic Display Pipeline are one of the compositor input sub-devices. GDP are dedicated to graphic input like RGB plans.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/Makefile | 3 +- drivers/gpu/drm/sti/sti_gdp.c | 491 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_gdp.h | 73 ++++++ drivers/gpu/drm/sti/sti_layer.h | 5 + 4 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/sti/sti_gdp.c create mode 100644 drivers/gpu/drm/sti/sti_gdp.h
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 45536c3..cc04475 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -1,10 +1,11 @@ ccflags-y := -Iinclude/drm
-stidrm-y := sti_tvout.o \ +stidrm-y := sti_tvout.o \ sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ + sti_gdp.o \ sti_ddc.o
obj-$(CONFIG_VTAC_STI) += sti_vtac_tx.o sti_vtac_rx.o diff --git a/drivers/gpu/drm/sti/sti_gdp.c b/drivers/gpu/drm/sti/sti_gdp.c new file mode 100644 index 0000000..00ff7f9 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_gdp.c @@ -0,0 +1,491 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * 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_layer.h" +#include "sti_gdp.h" +#include "sti_vtg_utils.h" + +#define ENA_COLOR_FILL (1 << 8) +#define WAIT_NEXT_VSYNC (1 << 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 (1 << 5) +#define GAM_GDP_AGC_FULL_RANGE 0x00808080 +#define GAM_GDP_PPT_IGNORE ((1 << 1) | (1 << 0)) +#define GAM_GDP_SIZE_MAX 0x7FF + +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(void) +{ + return gdp_supported_formats; +} + +static int sti_gdp_get_nb_formats(void) +{ + 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 = layer->gdp; + int i; + + hw_nvn = readl(gdp->regs + GAM_GDP_NVN_OFFSET); + if (!hw_nvn) + goto end; + + virt_nvn = dma_to_virt(gdp->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: + 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 = layer->gdp; + int i; + + hw_nvn = readl(gdp->regs + GAM_GDP_NVN_OFFSET); + if (!hw_nvn) + goto end; + + virt_nvn = dma_to_virt(gdp->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: + 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(void *lay, bool first_prepare) +{ + struct sti_layer *layer = (struct sti_layer *)lay; + 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->gdp->dev; + int format, 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; + + /* 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, + &layer->gdp->vtg_field_nb)) { + DRM_ERROR("Cannot register VTG notifier\n"); + return 1; + } + + /* Set and enable gdp clock */ + if (layer->gdp->clk_pix) { + res = clk_set_rate(layer->gdp->clk_pix, rate); + if (res < 0) { + DRM_ERROR("Cannot set rate (%dHz) for gdp\n", + rate); + return 1; + } + + if (clk_prepare_enable(layer->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(void *lay) +{ + struct sti_layer *layer = (struct sti_layer *)lay; + 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 = layer->gdp; + u32 dma_updated_top = virt_to_dma(gdp->dev, updated_top_node); + u32 dma_updated_btm = virt_to_dma(gdp->dev, updated_btm_node); + struct sti_gdp_node_list *curr_list = sti_gdp_get_current_nodes(layer); + + dev_dbg(gdp->dev, "Current NVN:0x%X\n", + readl(gdp->regs + GAM_GDP_NVN_OFFSET)); + dev_dbg(gdp->dev, "Posted buff: %lx current buff: %x\n", + (unsigned long)layer->paddr, + readl(gdp->regs + GAM_GDP_PML_OFFSET)); + + if (curr_list == NULL) { + /* First update or invalid node should directly write in the + * hw register */ + writel(gdp->is_curr_top == true ? + dma_updated_btm : dma_updated_top, + gdp->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, gdp->regs + GAM_GDP_NVN_OFFSET); + } + } else { + /* Direct update for progressive to avoid one frame delay */ + writel(dma_updated_top, gdp->regs + GAM_GDP_NVN_OFFSET); + } + + return 0; +} + +/** + * sti_gdp_disable_layer + * @lay: gdp layer + * + * Disable a GDP. + * + * RETURNS: + * 0 on success. + */ +static int sti_gdp_disable_layer(void *lay) +{ + int i; + struct sti_layer *layer = (struct sti_layer *)lay; + struct sti_gdp *gdp = layer->gdp; + + /* 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, &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; +} + +/** + * sti_gdp_create + * @dev: device + * @id: gdp id + * @baseaddr: IO addr + * + * Create a gdp object. Allocate memory and initialize parameters. + * + * + * RETURNS: + * Pointer to the created gdp object. + */ +struct sti_gdp *sti_gdp_create(struct device *dev, int id, + void __iomem *baseaddr) +{ + struct sti_gdp *gdp; + struct device_node *np = dev->of_node; + dma_addr_t dma; + void *base; + int i, size; + + gdp = devm_kzalloc(dev, sizeof(*gdp), GFP_KERNEL); + if (!gdp) { + DRM_ERROR("Failed to allocate memory for GDP\n"); + return NULL; + } + + /* 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(dev, size, &dma, GFP_KERNEL | GFP_DMA); + if (!base) { + DRM_ERROR("Failed to allocate memory for GDP node\n"); + goto mem_err; + } + memset(base, 0, size); + + for (i = 0; i < GDP_NODE_NB_BANK; i++) { + if (virt_to_dma(dev, base) & 0xF) { + DRM_ERROR("Mem alignment failed\n"); + goto mem_err; + } + 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(dev, base) & 0xF) { + DRM_ERROR("Mem alignment failed\n"); + goto mem_err; + } + gdp->node_list[i].btm_field = base; + DRM_DEBUG_DRIVER("node[%d].btm_field=%p\n", i, base); + base += sizeof(struct sti_gdp_node); + } + + gdp->dev = dev; + gdp->regs = baseaddr; + gdp->get_formats = sti_gdp_get_formats; + gdp->get_nb_formats = sti_gdp_get_nb_formats; + gdp->prepare = sti_gdp_prepare_layer; + gdp->commit = sti_gdp_commit_layer; + gdp->disable = sti_gdp_disable_layer; + gdp->vtg_field_nb.notifier_call = sti_gdp_field_cb; + + if (of_device_is_compatible(np, "st,stih407-compositor")) { + /* GDP of STiH407 chip have its own pixel clock */ + char *clk_name; + + switch (id) { + 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"); + goto err_clk_pix_gdp; + } + + gdp->clk_pix = devm_clk_get(dev, clk_name); + if (IS_ERR(gdp->clk_pix)) { + DRM_ERROR("Cannot get %s clock\n", clk_name); + goto err_clk_pix_gdp; + } + } + + return gdp; + +err_clk_pix_gdp: +mem_err: + devm_kfree(dev, gdp); + return NULL; +} diff --git a/drivers/gpu/drm/sti/sti_gdp.h b/drivers/gpu/drm/sti/sti_gdp.h new file mode 100644 index 0000000..4f7f40b --- /dev/null +++ b/drivers/gpu/drm/sti/sti_gdp.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * 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> + +#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 + * + * @device: driver device + * @regs: subdevice register + * @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 + * @get_formats: get GDP supported formats + * @get_nb_formats: get number of format supported + * @prepare: prepare GDP before rendering + * @commit: set GDP for rendering + * @disable: disable GDP + * @node_list: array of node list + */ +struct sti_gdp { + struct device *dev; + void __iomem *regs; + struct clk *clk_pix; + struct notifier_block vtg_field_nb; + bool is_curr_top; + const uint32_t* (*get_formats)(void); + int (*get_nb_formats)(void); + int (*prepare)(void *layer, bool first_prepare); + int (*commit)(void *layer); + int (*disable)(void *layer); + struct sti_gdp_node_list node_list[GDP_NODE_NB_BANK]; +}; + +struct sti_gdp *sti_gdp_create(struct device *dev, int id, + void __iomem *baseaddr); + +#endif diff --git a/drivers/gpu/drm/sti/sti_layer.h b/drivers/gpu/drm/sti/sti_layer.h index ed8386a..45cd1ea 100644 --- a/drivers/gpu/drm/sti/sti_layer.h +++ b/drivers/gpu/drm/sti/sti_layer.h @@ -10,6 +10,7 @@ #define _STI_LAYER_H_
#include <drm/drmP.h> +#include "sti_gdp.h"
#define to_sti_layer(x) container_of(x, struct sti_layer, plane)
@@ -67,6 +68,7 @@ struct sti_fps_info { * @offsets: offset of 'planes' * @paddr: physical address of the input buffer * @fps_info: frame per second info + * @gdp: related GDP (if the layer is a GDP) */ struct sti_layer { struct drm_plane plane; @@ -74,6 +76,7 @@ struct sti_layer { struct drm_display_mode *mode; enum sti_layer_desc desc; int zorder; + int mixer_id; bool enabled; int src_x, src_y; int src_w, src_h; @@ -84,12 +87,14 @@ struct sti_layer { int offsets[4]; dma_addr_t paddr; struct sti_fps_info fps_info; + struct sti_gdp *gdp; };
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,
VIDeo plug are one of the compositor input sub-devices. VID are dedicated to video inputs like YUV plans.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/Makefile | 1 + drivers/gpu/drm/sti/sti_layer.h | 4 ++ drivers/gpu/drm/sti/sti_vid.c | 138 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_vid.h | 33 ++++++++++ 4 files changed, 176 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_vid.c create mode 100644 drivers/gpu/drm/sti/sti_vid.h
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index cc04475..b9a3b74 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -6,6 +6,7 @@ stidrm-y := sti_tvout.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ sti_gdp.o \ + sti_vid.o \ sti_ddc.o
obj-$(CONFIG_VTAC_STI) += sti_vtac_tx.o sti_vtac_rx.o diff --git a/drivers/gpu/drm/sti/sti_layer.h b/drivers/gpu/drm/sti/sti_layer.h index 45cd1ea..bf3a14f 100644 --- a/drivers/gpu/drm/sti/sti_layer.h +++ b/drivers/gpu/drm/sti/sti_layer.h @@ -11,6 +11,7 @@
#include <drm/drmP.h> #include "sti_gdp.h" +#include "sti_vid.h"
#define to_sti_layer(x) container_of(x, struct sti_layer, plane)
@@ -58,6 +59,7 @@ struct sti_fps_info { * @mode: display mode * @desc: layer type & id * @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 @@ -69,6 +71,7 @@ struct sti_fps_info { * @paddr: physical address of the input buffer * @fps_info: frame per second info * @gdp: related GDP (if the layer is a GDP) + * @vid: related VID (if the layer is a VID/VDP) */ struct sti_layer { struct drm_plane plane; @@ -88,6 +91,7 @@ struct sti_layer { dma_addr_t paddr; struct sti_fps_info fps_info; struct sti_gdp *gdp; + struct sti_vid *vid; };
struct sti_layer *sti_layer_create(struct device *dev, int desc, diff --git a/drivers/gpu/drm/sti/sti_vid.c b/drivers/gpu/drm/sti/sti_vid.c new file mode 100644 index 0000000..710665d --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vid.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Fabien Dessenne fabien.dessenne@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <drm/drmP.h> + +#include "sti_vid.h" +#include "sti_layer.h" +#include "sti_vtg_utils.h" + +/* Registers */ +#define VID_CTL 0x00 +#define VID_ALP 0x04 +#define VID_CLF 0x08 +#define VID_VPO 0x0C +#define VID_VPS 0x10 +#define VID_KEY1 0x28 +#define VID_KEY2 0x2C +#define VID_MPR0 0x30 +#define VID_MPR1 0x34 +#define VID_MPR2 0x38 +#define VID_MPR3 0x3C +#define VID_MST 0x68 +#define VID_BC 0x70 +#define VID_TINT 0x74 +#define VID_CSAT 0x78 + +/* Registers values */ +#define VID_CTL_IGNORE ((1<<30) | (1<<31)) +#define VID_CTL_PSI_ENABLE ((1<<2) | (1<<1) | (1<<0)) +#define VID_ALP_OPAQUE 0x00000080 +#define VID_BC_DFLT 0x00008000 +#define VID_TINT_DFLT 0x00000000 +#define VID_CSAT_DFLT 0x00000080 +/* YCbCr to RGB BT709: + * R = Y+1.5391Cr + * G = Y-0.4590Cr-0.1826Cb + * B = Y+1.8125Cb */ +#define VID_MPR0_BT709 0x0A800000 +#define VID_MPR1_BT709 0x0AC50000 +#define VID_MPR2_BT709 0x07150545 +#define VID_MPR3_BT709 0x00000AE8 + +static int sti_vid_prepare_layer(void *lay, bool first_prepare) +{ + u32 val; + struct sti_layer *layer = (struct sti_layer *)lay; + struct sti_vid *vid = layer->vid; + + /* Unmask */ + val = readl(vid->regs + VID_CTL); + val &= ~VID_CTL_IGNORE; + writel(val, vid->regs + VID_CTL); + + return 0; +} + +static int sti_vid_commit_layer(void *lay) +{ + struct sti_layer *layer = (struct sti_layer *)lay; + struct sti_vid *vid = layer->vid; + struct drm_display_mode *mode = layer->mode; + u32 ydo, xdo, yds, xds; + + 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); + + writel((ydo << 16) | xdo, vid->regs + VID_VPO); + writel((yds << 16) | xds, vid->regs + VID_VPS); + + return 0; +} + +static int sti_vid_disable_layer(void *lay) +{ + u32 val; + struct sti_layer *layer = (struct sti_layer *)lay; + struct sti_vid *vid = layer->vid; + + /* Mask */ + val = readl(vid->regs + VID_CTL); + val |= VID_CTL_IGNORE; + writel(val, vid->regs + VID_CTL); + + return 0; +} + +static void sti_vid_set_default(struct sti_vid *vid) +{ + /* Enable PSI, Mask layer */ + writel(VID_CTL_PSI_ENABLE | VID_CTL_IGNORE, vid->regs + VID_CTL); + + /* Opaque */ + writel(VID_ALP_OPAQUE, vid->regs + VID_ALP); + + /* Color conversion parameters */ + writel(VID_MPR0_BT709, vid->regs + VID_MPR0); + writel(VID_MPR1_BT709, vid->regs + VID_MPR1); + writel(VID_MPR2_BT709, vid->regs + VID_MPR2); + writel(VID_MPR3_BT709, vid->regs + VID_MPR3); + + /* Brightness, contrast, tint, saturation */ + writel(VID_BC_DFLT, vid->regs + VID_BC); + writel(VID_TINT_DFLT, vid->regs + VID_TINT); + writel(VID_CSAT_DFLT, vid->regs + VID_CSAT); +} + +struct sti_vid *sti_vid_create(struct device *dev, void __iomem *baseaddr) +{ + struct sti_vid *vid; + + DRM_DEBUG_DRIVER("\n"); + + vid = devm_kzalloc(dev, sizeof(*vid), GFP_KERNEL); + if (!vid) { + DRM_ERROR("Failed to allocate memory for VID\n"); + return NULL; + } + + vid->dev = dev; + vid->regs = baseaddr; + vid->prepare = sti_vid_prepare_layer; + vid->commit = sti_vid_commit_layer; + vid->disable = sti_vid_disable_layer; + /* As the VID input is HW-mapped to the VDP output, the supported + * formats are under the VDP control */ + vid->get_formats = NULL; + vid->get_nb_formats = NULL; + + /* Set default configuration (static) */ + sti_vid_set_default(vid); + + return vid; +} diff --git a/drivers/gpu/drm/sti/sti_vid.h b/drivers/gpu/drm/sti/sti_vid.h new file mode 100644 index 0000000..dd4fd95 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vid.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Fabien Dessenne fabien.dessenne@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_VID_H_ +#define _STI_VID_H_ + +/* + * STI VID structure + * + * @device: driver device + * @regs: subdevice register + * @get_formats: get VID supported formats + * @get_nb_formats: get number of format supported + * @prepare: prepare VID before rendering + * @commit: set VID for rendering + * @disable: disable VID + */ +struct sti_vid { + struct device *dev; + void __iomem *regs; + const uint32_t* (*get_formats)(void); + int (*get_nb_formats)(void); + int (*prepare)(void *layer, bool first_prepare); + int (*commit)(void *layer); + int (*disable)(void *layer); +}; + +struct sti_vid *sti_vid_create(struct device *dev, void __iomem *baseaddr); + +#endif
Mixer hardware IP is responsible of mixing the different inputs layers. Z-order is managed by the mixer. We could 2 mixers: one for main path and one for auxilary path
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/Makefile | 1 + drivers/gpu/drm/sti/sti_mixer.c | 241 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_mixer.h | 52 +++++++++ 3 files changed, 294 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_mixer.c create mode 100644 drivers/gpu/drm/sti/sti_mixer.h
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index b9a3b74..3d52d2a 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -5,6 +5,7 @@ stidrm-y := sti_tvout.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ + sti_mixer.o \ sti_gdp.o \ sti_vid.o \ sti_ddc.o diff --git a/drivers/gpu/drm/sti/sti_mixer.c b/drivers/gpu/drm/sti/sti_mixer.c new file mode 100644 index 0000000..ab8daf6 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_mixer.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * 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_mixer.h" +#include "sti_vtg_utils.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_MAIN_CTL 0x00 +#define GAM_MIXER_MAIN_BKC 0x04 +#define GAM_MIXER_MAIN_BCO 0x0C +#define GAM_MIXER_MAIN_BCS 0x10 +#define GAM_MIXER_MAIN_AVO 0x28 +#define GAM_MIXER_MAIN_AVS 0x2C +#define GAM_MIXER_MAIN_CRB 0x34 +#define GAM_MIXER_MAIN_ACT 0x38 +#define GAM_MIXER_MAIN_MBP 0x3C +#define GAM_MIXER_MAIN_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 (1 << 0) +#define GAM_CTL_VID0_MASK (1 << 1) +#define GAM_CTL_VID1_MASK (1 << 2) +#define GAM_CTL_GDP0_MASK (1 << 3) +#define GAM_CTL_GDP1_MASK (1 << 4) +#define GAM_CTL_GDP2_MASK (1 << 5) +#define GAM_CTL_GDP3_MASK (1 << 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); +} + +static inline void sti_mixer_reg_writemask(struct sti_mixer *mixer, + u32 reg_id, u32 val, u32 mask) +{ + u32 old = sti_mixer_reg_read(mixer, reg_id); + + val = (val & mask) | (old & ~mask); + writel(val, mixer->regs + reg_id); +} + +void sti_mixer_set_background_status(struct sti_mixer *mixer, bool enable) +{ + sti_mixer_reg_writemask(mixer, + GAM_MIXER_MAIN_CTL, enable, GAM_CTL_BACK_MASK); +} + +static void sti_mixer_set_background_color(struct sti_mixer *mixer, + int red, int green, int blue) +{ + u32 val = (red << 16) | (green << 8) | blue; + + sti_mixer_reg_write(mixer, GAM_MIXER_MAIN_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_MAIN_BCO, ydo << 16 | xdo); + sti_mixer_reg_write(mixer, GAM_MIXER_MAIN_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; + + 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); + + dev_dbg(mixer->dev, "GAM_MIXER_MAIN_CRB val 0x%x mask 0x%x\n", + layer_id, mask); + sti_mixer_reg_writemask(mixer, GAM_MIXER_MAIN_CRB, layer_id, mask); + + dev_dbg(mixer->dev, "Read GAM_MIXER_MAIN_CRB 0x%x\n", + sti_mixer_reg_read(mixer, GAM_MIXER_MAIN_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_MAIN_AVO, ydo << 16 | xdo); + sti_mixer_reg_write(mixer, GAM_MIXER_MAIN_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; + + mask = sti_mixer_get_layer_mask(layer); + if (!mask) { + DRM_ERROR("Can not find layer mask\n"); + return -1; + } + val = status ? mask : 0; + sti_mixer_reg_writemask(mixer, GAM_MIXER_MAIN_CTL, val, mask); + return 0; +} + +void sti_mixer_set_matrix(struct sti_mixer *mixer) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(MixerColorSpaceMatIdentity); i++) + sti_mixer_reg_write(mixer, GAM_MIXER_MAIN_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; +} diff --git a/drivers/gpu/drm/sti/sti_mixer.h b/drivers/gpu/drm/sti/sti_mixer.h new file mode 100644 index 0000000..a05e21f --- /dev/null +++ b/drivers/gpu/drm/sti/sti_mixer.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_MIXER_H_ +#define _STI_MIXER_H_ + +#include <drm/drmP.h> + +#include "sti_layer.h" + +#define to_sti_mixer(x) container_of(x, struct sti_mixer, drm_crtc) + +/* + * STI Mixer subdevice structure + * + * @dev: driver device + * @regs: mixer registers + * @id: id of the mixer + * @drm_crtc: crtc object link to the mixer + */ +struct sti_mixer { + struct device *dev; + void __iomem *regs; + int id; + struct drm_crtc drm_crtc; +}; + +const char *sti_mixer_to_str(struct sti_mixer *mixer); + +struct sti_mixer *sti_mixer_create(struct device *dev, int id, + void __iomem *baseaddr); + +int sti_mixer_set_layer_status(struct sti_mixer *mixer, + struct sti_layer *layer, bool status); +int sti_mixer_set_layer_depth(struct sti_mixer *mixer, struct sti_layer *layer); +int sti_mixer_active_video_area(struct sti_mixer *mixer, + struct drm_display_mode *mode); + +void sti_mixer_set_background_status(struct sti_mixer *mixer, bool enable); + +/* depth in Cross-bar control = z order */ +#define GAM_MIXER_NB_DEPTH_LEVEL 7 + +#define STI_MIXER_MAIN 0 +#define STI_MIXER_AUX 1 + +#endif
Compositor control all the input sub-devices and the mixer. It is the main entry point for composition. Layer interface is used to control the layer.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/Kconfig | 1 + drivers/gpu/drm/sti/Makefile | 2 + drivers/gpu/drm/sti/sti_compositor.c | 187 +++++++++++++++++++++ drivers/gpu/drm/sti/sti_compositor.h | 84 ++++++++++ drivers/gpu/drm/sti/sti_layer.c | 309 +++++++++++++++++++++++++++++++++++ 5 files changed, 583 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_compositor.c create mode 100644 drivers/gpu/drm/sti/sti_compositor.h create mode 100644 drivers/gpu/drm/sti/sti_layer.c
diff --git a/drivers/gpu/drm/sti/Kconfig b/drivers/gpu/drm/sti/Kconfig index cdd5119..9c1218c 100644 --- a/drivers/gpu/drm/sti/Kconfig +++ b/drivers/gpu/drm/sti/Kconfig @@ -1,6 +1,7 @@ config DRM_STI bool "DRM Support for STMicroelectronics SoC stiH41x Series" depends on DRM && (SOC_STIH415 || SOC_STIH416 || ARCH_MULTIPLATFORM) + select DRM_KMS_CMA_HELPER help Choose this option to enable DRM on STM stiH41x chipset
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 3d52d2a..3b804d4 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -5,6 +5,8 @@ stidrm-y := sti_tvout.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ + sti_compositor.o \ + sti_layer.o \ sti_mixer.o \ sti_gdp.o \ sti_vid.o \ diff --git a/drivers/gpu/drm/sti/sti_compositor.c b/drivers/gpu/drm/sti/sti_compositor.c new file mode 100644 index 0000000..a163344 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_compositor.c @@ -0,0 +1,187 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * 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/module.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <drm/drmP.h> + +#include "sti_compositor.h" +#include "sti_gdp.h" + +static const struct of_device_id compositor_match_types[]; + +/* + * 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, + int array_size) +{ + int i, mixer_id = 0, layer_id = 0; + + dev_dbg(compo->dev, "%s\n", __func__); + 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_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct sti_compositor *compo; + struct resource *res; + int err; + + DRM_INFO("%s\n", __func__); + compo = devm_kzalloc(dev, sizeof(*compo), GFP_KERNEL); + if (!compo) { + DRM_ERROR("Failed to allocate compositor context\n"); + return -ENOMEM; + } + DRM_DEBUG_DRIVER("Compositor %p\n", compo); + compo->dev = dev; + + /* populate data structure depending on compatibility */ + BUG_ON(!of_match_node(compositor_match_types, np)->data); + + memcpy(&compo->data, of_match_node(compositor_match_types, 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); + + /* 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 0; +} + +static const struct of_device_id compositor_match_types[] = { + { + .compatible = "st,stih416-compositor", + .data = &stih416_compositor_data, + }, + { + .compatible = "st,stih407-compositor", + .data = &stih407_compositor_data, + }, + { /* end node */ } + +}; + +struct platform_driver sti_compositor_driver = { + .driver = { + .name = "sti-compositor", + .owner = THIS_MODULE, + .of_match_table = compositor_match_types, + }, + .probe = sti_compositor_probe, +}; diff --git a/drivers/gpu/drm/sti/sti_compositor.h b/drivers/gpu/drm/sti/sti_compositor.h new file mode 100644 index 0000000..599a60d --- /dev/null +++ b/drivers/gpu/drm/sti/sti_compositor.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * 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 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 { + 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 + * @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_layer *layer[STI_MAX_LAYER]; + int nb_mixers; + int nb_layers; + bool enable; + struct notifier_block vtg_vblank_nb; +}; + +#endif diff --git a/drivers/gpu/drm/sti/sti_layer.c b/drivers/gpu/drm/sti/sti_layer.c new file mode 100644 index 0000000..54c8694 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_layer.c @@ -0,0 +1,309 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * 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_layer.h" +#include "sti_compositor.h" +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_cma_helper.h> + +#define STI_FPS_INTERVAL_MS 3000 + +struct sti_layer *sti_layer_find_layer(struct sti_layer *layer[], + enum sti_layer_desc desc) +{ + int i; + + for (i = 0; i < STI_MAX_LAYER; i++) + if (layer[i] && (layer[i]->desc == desc)) + return layer[i]; + return NULL; +} + +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>"; + } +} + +static int timespec_ms_diff(struct timespec lhs, struct timespec rhs) +{ + struct timespec tmp_ts = timespec_sub(lhs, rhs); + u64 tmp_ns = (u64) timespec_to_ns(&tmp_ts); + + do_div(tmp_ns, NSEC_PER_MSEC); + + return (u32) tmp_ns; +} + +static void sti_layer_update_fps(struct sti_layer *layer) +{ + struct timespec now; + struct sti_fps_info *fps; + int fpks, ms_since_last, num_frames; + + getrawmonotonic(&now); + + fps = &layer->fps_info; + fps->curr_frame_counter++; + ms_since_last = timespec_ms_diff(now, fps->last_timestamp); + num_frames = fps->curr_frame_counter - fps->last_frame_counter; + + if (num_frames > 1 && ms_since_last >= STI_FPS_INTERVAL_MS) { + fps->last_timestamp = now; + fps->last_frame_counter = fps->curr_frame_counter; + fpks = (num_frames * 1000000) / ms_since_last; + if (fps->output) + DRM_INFO("%s @ %d.%.3d fps\n", sti_layer_to_str(layer), + fpks / 1000, fpks % 1000); + } +} + +struct sti_layer *sti_layer_create(struct device *dev, int desc, + void __iomem *baseaddr) +{ + struct sti_layer *layer; + + layer = devm_kzalloc(dev, sizeof(*layer), GFP_KERNEL); + if (!layer) { + DRM_ERROR("Failed to allocate memory for layer\n"); + return NULL; + } + + switch (desc & STI_LAYER_TYPE_MASK) { + case STI_GDP: + layer->gdp = sti_gdp_create(dev, desc, baseaddr); + if (!layer->gdp) + goto err; + break; + case STI_VID: + layer->vid = sti_vid_create(dev, baseaddr); + if (!layer->vid) + goto err; + break; + default: + goto err; + } + + layer->desc = desc; + DRM_DEBUG_DRIVER("%s created\n", sti_layer_to_str(layer)); + + return layer; +err: + devm_kfree(dev, layer); + DRM_ERROR("Failed to create layer\n"); + return NULL; +} + +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, 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); + + /* Prepare layer specificities */ + switch (layer->desc & STI_LAYER_TYPE_MASK) { + case STI_GDP: + if (!layer->gdp) + goto err_no_prepare; + ret = layer->gdp->prepare(layer, !layer->enabled); + break; + case STI_VID: + if (!layer->vid) + goto err_no_prepare; + ret = layer->vid->prepare(layer, !layer->enabled); + break; + default: + goto err_no_prepare; + } + + 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) +{ + int ret; + + if (!layer) + return 1; + + switch (layer->desc & STI_LAYER_TYPE_MASK) { + case STI_GDP: + if (!layer->gdp) + goto err_no_commit; + ret = layer->gdp->commit(layer); + break; + case STI_VID: + if (!layer->vid) + goto err_no_commit; + ret = layer->vid->commit(layer); + break; + default: + goto err_no_commit; + } + + if (!ret) + sti_layer_update_fps(layer); + + return ret; + +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; + + switch (layer->desc & STI_LAYER_TYPE_MASK) { + case STI_GDP: + if (!layer->gdp) + goto err_no_disable; + ret = layer->gdp->disable(layer); + break; + case STI_VID: + if (!layer->vid) + goto err_no_disable; + ret = layer->vid->disable(layer); + break; + default: + goto err_no_disable; + } + + 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) +{ + const uint32_t *(*get_formats)(void) = NULL; + + if (!layer) + return NULL; + + switch (layer->desc & STI_LAYER_TYPE_MASK) { + case STI_GDP: + if (layer->gdp) + get_formats = layer->gdp->get_formats; + break; + default: + break; + } + + if (!get_formats) { + DRM_ERROR("Cannot get formats\n"); + return NULL; + } + + return get_formats(); +} + +int sti_layer_get_nb_formats(struct sti_layer *layer) +{ + int (*get_nb_formats)(void) = NULL; + + if (!layer) + return 0; + + switch (layer->desc & STI_LAYER_TYPE_MASK) { + case STI_GDP: + if (layer->gdp) + get_nb_formats = layer->gdp->get_nb_formats; + break; + default: + break; + } + + if (!get_nb_formats) { + DRM_ERROR("Cannot get nb formats\n"); + return 0; + } + + return get_nb_formats(); +}
Allow to get more detailed debug information on GDP
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/sti_drm_drv.h | 36 ++++++ drivers/gpu/drm/sti/sti_gdp.c | 235 ++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_gdp.h | 2 + 3 files changed, 273 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_drm_drv.h
diff --git a/drivers/gpu/drm/sti/sti_drm_drv.h b/drivers/gpu/drm/sti/sti_drm_drv.h new file mode 100644 index 0000000..5660196 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_drv.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * 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 <linux/platform_device.h> + +#include <drm/drmP.h> + +#include "sti_compositor.h" +#include "sti_tvout.h" + +/* + * STI drm private structure + * This structure is stored as private in the drm_device + * + * @compo: compositor + * @tvout: TV OUT + * @pageflip_evt_list: list of pending page flip requests + * @plane_zorder_property: z-order property for CRTC planes + */ +struct sti_drm_private { + struct sti_compositor *compo; + struct sti_tvout *tvout; + struct list_head pageflip_evt_list; + struct drm_property *plane_zorder_property; +}; + +#define STI_MAX_FB_HEIGHT 4096 +#define STI_MAX_FB_WIDTH 4096 + +#endif diff --git a/drivers/gpu/drm/sti/sti_gdp.c b/drivers/gpu/drm/sti/sti_gdp.c index 00ff7f9..095d707 100644 --- a/drivers/gpu/drm/sti/sti_gdp.c +++ b/drivers/gpu/drm/sti/sti_gdp.c @@ -9,9 +9,11 @@ #include <linux/clk.h> #include <linux/dma-mapping.h>
+#include "sti_drm_drv.h" #include "sti_layer.h" #include "sti_gdp.h" #include "sti_vtg_utils.h" +#include "sti_compositor.h"
#define ENA_COLOR_FILL (1 << 8) #define WAIT_NEXT_VSYNC (1 << 31) @@ -29,6 +31,25 @@ #define GDP_YCBR422R 0x12 #define GDP_AYCBR8888 0x15
+#define GDP2STR(fmt) { GDP_ ## fmt, #fmt } + +static struct gdp_format_to_str { + int format; + char name[20]; +} sti_gdp_format_to_str[] = { + GDP2STR(RGB565), + GDP2STR(RGB888), + GDP2STR(RGB888_32), + GDP2STR(ARGB8565), + GDP2STR(ARGB8888), + GDP2STR(ARGB1555), + GDP2STR(ARGB4444), + GDP2STR(CLUT8), + GDP2STR(YCBR888), + GDP2STR(YCBR422R), + GDP2STR(AYCBR8888) + }; + #define GAM_GDP_CTL_OFFSET 0x00 #define GAM_GDP_AGC_OFFSET 0x04 #define GAM_GDP_VPO_OFFSET 0x0C @@ -137,6 +158,9 @@ static struct sti_gdp_node_list *sti_gdp_get_free_nodes(struct sti_layer *layer) return &gdp->node_list[i];
end: + DRM_DEBUG_DRIVER("Warning, inconsistent NVN for %s: 0x%08X\n", + sti_layer_to_str(layer), hw_nvn); + return &gdp->node_list[0]; }
@@ -169,6 +193,8 @@ struct sti_gdp_node_list *sti_gdp_get_current_nodes(struct sti_layer *layer) 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; }
@@ -198,6 +224,9 @@ static int sti_gdp_prepare_layer(void *lay, bool first_prepare) 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; @@ -294,6 +323,9 @@ static int sti_gdp_commit_layer(void *lay) u32 dma_updated_btm = virt_to_dma(gdp->dev, updated_btm_node); struct sti_gdp_node_list *curr_list = sti_gdp_get_current_nodes(layer);
+ dev_dbg(gdp->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(gdp->dev, "Current NVN:0x%X\n", readl(gdp->regs + GAM_GDP_NVN_OFFSET)); dev_dbg(gdp->dev, "Posted buff: %lx current buff: %x\n", @@ -303,6 +335,8 @@ static int sti_gdp_commit_layer(void *lay) 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, gdp->regs + GAM_GDP_NVN_OFFSET); @@ -342,6 +376,8 @@ static int sti_gdp_disable_layer(void *lay) struct sti_layer *layer = (struct sti_layer *)lay; struct sti_gdp *gdp = layer->gdp;
+ 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; @@ -489,3 +525,202 @@ mem_err: devm_kfree(dev, gdp); return NULL; } + +static void sti_gdp_dbg_ctl(struct seq_file *m, int val) +{ + int i; + + seq_puts(m, "\tColor:"); + for (i = 0; i < ARRAY_SIZE(sti_gdp_format_to_str); i++) { + if (sti_gdp_format_to_str[i].format == (val & 0x1F)) { + seq_printf(m, sti_gdp_format_to_str[i].name); + break; + } + } + if (i == ARRAY_SIZE(sti_gdp_format_to_str)) + seq_puts(m, "<UNKNOWN>"); + + seq_printf(m, "\tWaitNextVsync:%d", val & WAIT_NEXT_VSYNC ? 1 : 0); +} + +static void sti_gdp_dbg_vpo(struct seq_file *m, int val) +{ + seq_printf(m, "\txdo:%4d\tydo:%4d", val & 0x0FFF, (val >> 16) & 0x0FFF); +} + +static void sti_gdp_dbg_vps(struct seq_file *m, int val) +{ + seq_printf(m, "\txds:%4d\tyds:%4d", val & 0x0FFF, (val >> 16) & 0x0FFF); +} + +static void sti_gdp_dbg_size(struct seq_file *m, int val) +{ + seq_printf(m, "\t%d x %d", val & 0x07FF, (val >> 16) & 0x07FF); +} + +static void sti_gdp_dbg_nvn(struct seq_file *m, struct device *dev, int val) +{ + seq_puts(m, "\tVirt @: "); + if (val) + seq_printf(m, "%p", dma_to_virt(dev, (dma_addr_t) val)); +} + +static void sti_gdp_dbg_ppt(struct seq_file *m, int val) +{ + if (val & GAM_GDP_PPT_IGNORE) + seq_puts(m, "\tNot displayed on mixer!"); +} + +static void sti_gdp_dbg_mst(struct seq_file *m, int val) +{ + if (val & 1) + seq_puts(m, "\tBUFFER UNDERFLOW!"); +} + +#define GDP_DBG_DUMP(reg) seq_printf(m, "\n " #reg "\t 0x%08X", \ + readl(gdp->regs + reg ## _OFFSET)) + +int sti_gdp_dbg_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_compositor *compo = dev_priv->compo; + int i, ret; + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret) + return ret; + + if (compo == NULL) { + seq_puts(m, "No compositor available\n"); + goto out; + } + + for (i = 0; i < compo->nb_layers; i++) { + struct drm_plane *plane; + struct drm_crtc *crtc; + struct drm_framebuffer *fb; + struct sti_layer *layer = compo->layer[i]; + struct sti_gdp *gdp = layer->gdp; + if ((layer->desc & STI_LAYER_TYPE_MASK) != STI_GDP) + continue; + seq_printf(m, "\n%s (associated with the mixer_id %d)", + sti_layer_to_str(layer), layer->mixer_id); + seq_printf(m, "\t %d frame updates", + layer->fps_info.curr_frame_counter); + + GDP_DBG_DUMP(GAM_GDP_CTL); + sti_gdp_dbg_ctl(m, readl(gdp->regs + GAM_GDP_CTL_OFFSET)); + GDP_DBG_DUMP(GAM_GDP_AGC); + GDP_DBG_DUMP(GAM_GDP_VPO); + sti_gdp_dbg_vpo(m, readl(gdp->regs + GAM_GDP_VPO_OFFSET)); + GDP_DBG_DUMP(GAM_GDP_VPS); + sti_gdp_dbg_vps(m, readl(gdp->regs + GAM_GDP_VPS_OFFSET)); + GDP_DBG_DUMP(GAM_GDP_PML); + GDP_DBG_DUMP(GAM_GDP_PMP); + GDP_DBG_DUMP(GAM_GDP_SIZE); + sti_gdp_dbg_size(m, readl(gdp->regs + GAM_GDP_SIZE_OFFSET)); + GDP_DBG_DUMP(GAM_GDP_NVN); + sti_gdp_dbg_nvn(m, gdp->dev, + readl(gdp->regs + GAM_GDP_NVN_OFFSET)); + GDP_DBG_DUMP(GAM_GDP_KEY1); + GDP_DBG_DUMP(GAM_GDP_KEY2); + GDP_DBG_DUMP(GAM_GDP_PPT); + sti_gdp_dbg_ppt(m, readl(gdp->regs + GAM_GDP_PPT_OFFSET)); + GDP_DBG_DUMP(GAM_GDP_CML); + GDP_DBG_DUMP(GAM_GDP_MST); + sti_gdp_dbg_mst(m, readl(gdp->regs + GAM_GDP_MST_OFFSET)); + seq_puts(m, "\n"); + + plane = &layer->plane; + if (!plane->base.id) { + seq_puts(m, " Not connected to any DRM PLANE\n"); + continue; + } + seq_printf(m, " Connected to DRM PLANE #%d which is:\n", + plane->base.id); + + crtc = plane->crtc; + if (!crtc) { + seq_puts(m, "\tNot connected to any DRM CRTC\n"); + continue; + } + seq_printf(m, "\tConnected to DRM CRTC #%d\n", crtc->base.id); + + fb = crtc->fb; + if (!fb) { + seq_puts(m, "\tNot connected to any DRM FB\n"); + continue; + } + seq_printf(m, "\tConnected to DRM FB #%d, %dx%d, %.4s\n", + fb->base.id, + fb->width, fb->height, (char *)&fb->pixel_format); + } + +out: + mutex_unlock(&dev->struct_mutex); + return 0; +} + +static void sti_gdp_node_dump_node(struct seq_file *m, + struct sti_gdp_node *node) +{ + seq_printf(m, "\t@:0x%p", node); + seq_printf(m, "\n\tCTL 0x%08X", node->gam_gdp_ctl); + sti_gdp_dbg_ctl(m, node->gam_gdp_ctl); + seq_printf(m, "\n\tAGC 0x%08X", node->gam_gdp_agc); + seq_printf(m, "\n\tVPO 0x%08X", node->gam_gdp_vpo); + sti_gdp_dbg_vpo(m, node->gam_gdp_vpo); + seq_printf(m, "\n\tVPS 0x%08X", node->gam_gdp_vps); + sti_gdp_dbg_vps(m, node->gam_gdp_vps); + seq_printf(m, "\n\tPML 0x%08X", node->gam_gdp_pml); + seq_printf(m, "\n\tPMP 0x%08X", node->gam_gdp_pmp); + seq_printf(m, "\n\tSIZE 0x%08X", node->gam_gdp_size); + sti_gdp_dbg_size(m, node->gam_gdp_size); + seq_printf(m, "\n\tNVN 0x%08X", node->gam_gdp_nvn); + seq_printf(m, "\n\tKEY1 0x%08X", node->gam_gdp_key1); + seq_printf(m, "\n\tKEY2 0x%08X", node->gam_gdp_key2); + seq_printf(m, "\n\tPPT 0x%08X", node->gam_gdp_ppt); + sti_gdp_dbg_ppt(m, node->gam_gdp_ppt); + seq_printf(m, "\n\tCML 0x%08X", node->gam_gdp_cml); + seq_puts(m, "\n"); +} + +int sti_gdp_node_dbg_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_compositor *compo = dev_priv->compo; + int i, ret; + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret) + return ret; + + if (!compo) { + seq_puts(m, "No compositor available\n"); + goto out; + } + + for (i = 0; i < compo->nb_layers; i++) { + int b; + struct sti_layer *layer = compo->layer[i]; + struct sti_gdp *gdp = layer->gdp; + if ((layer->desc & STI_LAYER_TYPE_MASK) != STI_GDP) + continue; + + for (b = 0; b < GDP_NODE_NB_BANK; b++) { + seq_printf(m, "\n%s[%d].top", + sti_layer_to_str(layer), b); + sti_gdp_node_dump_node(m, gdp->node_list[b].top_field); + seq_printf(m, "\n%s[%d].btm", + sti_layer_to_str(layer), b); + sti_gdp_node_dump_node(m, gdp->node_list[b].btm_field); + } + } +out: + mutex_unlock(&dev->struct_mutex); + return 0; +} diff --git a/drivers/gpu/drm/sti/sti_gdp.h b/drivers/gpu/drm/sti/sti_gdp.h index 4f7f40b..84875ff 100644 --- a/drivers/gpu/drm/sti/sti_gdp.h +++ b/drivers/gpu/drm/sti/sti_gdp.h @@ -69,5 +69,7 @@ struct sti_gdp {
struct sti_gdp *sti_gdp_create(struct device *dev, int id, void __iomem *baseaddr); +int sti_gdp_dbg_show(struct seq_file *m, void *arg); +int sti_gdp_node_dbg_show(struct seq_file *m, void *arg);
#endif
Make VIdeo plug more verbose on what is on going
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/sti_vid.c | 121 ++++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_vid.h | 1 + 2 files changed, 122 insertions(+)
diff --git a/drivers/gpu/drm/sti/sti_vid.c b/drivers/gpu/drm/sti/sti_vid.c index 710665d..35f60d8 100644 --- a/drivers/gpu/drm/sti/sti_vid.c +++ b/drivers/gpu/drm/sti/sti_vid.c @@ -8,6 +8,8 @@
#include "sti_vid.h" #include "sti_layer.h" +#include "sti_compositor.h" +#include "sti_drm_drv.h" #include "sti_vtg_utils.h"
/* Registers */ @@ -49,6 +51,8 @@ static int sti_vid_prepare_layer(void *lay, bool first_prepare) struct sti_layer *layer = (struct sti_layer *)lay; struct sti_vid *vid = layer->vid;
+ dev_dbg(vid->dev, "%s %s\n", __func__, sti_layer_to_str(layer)); + /* Unmask */ val = readl(vid->regs + VID_CTL); val &= ~VID_CTL_IGNORE; @@ -64,6 +68,8 @@ static int sti_vid_commit_layer(void *lay) struct drm_display_mode *mode = layer->mode; u32 ydo, xdo, yds, xds;
+ dev_dbg(vid->dev, "%s %s\n", __func__, sti_layer_to_str(layer)); + 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); @@ -81,6 +87,8 @@ static int sti_vid_disable_layer(void *lay) struct sti_layer *layer = (struct sti_layer *)lay; struct sti_vid *vid = layer->vid;
+ DRM_DEBUG_DRIVER("%s\n", sti_layer_to_str(layer)); + /* Mask */ val = readl(vid->regs + VID_CTL); val |= VID_CTL_IGNORE; @@ -136,3 +144,116 @@ struct sti_vid *sti_vid_create(struct device *dev, void __iomem *baseaddr)
return vid; } + +static void sti_vid_dbg_ctl(struct seq_file *m, int val) +{ + val = val >> 30; + seq_puts(m, "\t"); + + if (!(val & 1)) + seq_puts(m, "NOT "); + seq_puts(m, "ignored on main mixer - "); + + if (!(val & 2)) + seq_puts(m, "NOT "); + seq_puts(m, "ignored on aux mixer"); +} + +static void sti_vid_dbg_vpo(struct seq_file *m, int val) +{ + seq_printf(m, "\txdo:%4d\tydo:%4d", val & 0x0FFF, (val >> 16) & 0x0FFF); +} + +static void sti_vid_dbg_vps(struct seq_file *m, int val) +{ + seq_printf(m, "\txds:%4d\tyds:%4d", val & 0x0FFF, (val >> 16) & 0x0FFF); +} + +static void sti_vid_dbg_mst(struct seq_file *m, int val) +{ + if (val & 1) + seq_puts(m, "\tBUFFER UNDERFLOW!"); +} + +#define DBG_DUMP(reg) \ + seq_printf(m, "\n " #reg "\t 0x%08X", readl(vid->regs + reg)) + +int sti_vid_dbg_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_compositor *compo = dev_priv->compo; + enum sti_layer_desc vid_id = *(enum sti_layer_desc *)arg; + struct sti_layer *layer; + struct sti_vid *vid; + struct drm_plane *plane; + struct drm_crtc *crtc; + struct drm_framebuffer *fb; + + if (compo == NULL) { + seq_puts(m, "No compo available\n"); + goto out; + } + + layer = sti_layer_find_layer(compo->layer, vid_id); + if (!layer) { + seq_puts(m, "Cannot find video layer\n"); + goto out; + } + vid = layer->vid; + + seq_printf(m, "\n%s (associated with the mixer_id %d)", + sti_layer_to_str(layer), layer->mixer_id); + seq_printf(m, "\t %d frame updates", + layer->fps_info.curr_frame_counter); + + DBG_DUMP(VID_CTL); + sti_vid_dbg_ctl(m, readl(vid->regs + VID_CTL)); + DBG_DUMP(VID_ALP); + DBG_DUMP(VID_CLF); + DBG_DUMP(VID_VPO); + sti_vid_dbg_vpo(m, readl(vid->regs + VID_VPO)); + DBG_DUMP(VID_VPS); + sti_vid_dbg_vps(m, readl(vid->regs + VID_VPS)); + DBG_DUMP(VID_KEY1); + DBG_DUMP(VID_KEY2); + DBG_DUMP(VID_MPR0); + DBG_DUMP(VID_MPR1); + DBG_DUMP(VID_MPR2); + DBG_DUMP(VID_MPR3); + DBG_DUMP(VID_MST); + sti_vid_dbg_mst(m, readl(vid->regs + VID_MST)); + DBG_DUMP(VID_BC); + DBG_DUMP(VID_TINT); + DBG_DUMP(VID_CSAT); + + seq_puts(m, "\n"); + + plane = &layer->plane; + if (!plane->base.id) { + seq_puts(m, " Not connected to any DRM PLANE\n"); + goto out; + } + seq_printf(m, " Connected to DRM PLANE #%d which is:\n", + plane->base.id); + + crtc = plane->crtc; + if (!crtc) { + seq_puts(m, "\tNot connected to any DRM CRTC\n"); + goto out; + } + seq_printf(m, "\tConnected to DRM CRTC #%d\n", crtc->base.id); + + fb = crtc->fb; + if (!fb) { + seq_puts(m, "\tNot connected to any DRM FB\n"); + goto out; + } + seq_printf(m, "\tConnected to DRM FB #%d, %dx%d, %.4s\n", + fb->base.id, + fb->width, fb->height, (char *)&fb->pixel_format); + +out: + return 0; +} diff --git a/drivers/gpu/drm/sti/sti_vid.h b/drivers/gpu/drm/sti/sti_vid.h index dd4fd95..6261ade 100644 --- a/drivers/gpu/drm/sti/sti_vid.h +++ b/drivers/gpu/drm/sti/sti_vid.h @@ -29,5 +29,6 @@ struct sti_vid { };
struct sti_vid *sti_vid_create(struct device *dev, void __iomem *baseaddr); +int sti_vid_dbg_show(struct seq_file *m, void *arg);
#endif
Use debugfs to dump information about TVout
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/sti_tvout.c | 181 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+)
diff --git a/drivers/gpu/drm/sti/sti_tvout.c b/drivers/gpu/drm/sti/sti_tvout.c index 7c61ba1..95251c3 100644 --- a/drivers/gpu/drm/sti/sti_tvout.c +++ b/drivers/gpu/drm/sti/sti_tvout.c @@ -18,6 +18,7 @@ #include "sti_tvout.h" #include "sti_hdmi.h" #include "sti_hda.h" +#include "sti_drm_drv.h"
/* glue regsiters */ #define TVO_CSC_MAIN_M0 0x000 @@ -605,6 +606,186 @@ void sti_tvout_disable(struct sti_tvout *tvout, connector->stop(connector); }
+/* + * Debugfs + */ +#define TVOUT_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \ + readl(tvout->regs + reg)) +#define TVOUT_CONNECTOR_NAME_MAX_LENGHT 10 +#define MAX_STRING_LENGTH 55 + +static int sti_tvout_dbg_type_to_connector_name(enum sti_tvout_connector_type + type, char *name) +{ + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + snprintf(name, TVOUT_CONNECTOR_NAME_MAX_LENGHT, "HDMI"); + break; + case STI_TVOUT_CONNECTOR_HDA: + snprintf(name, TVOUT_CONNECTOR_NAME_MAX_LENGHT, "HD Analog"); + break; + default: + return -1; + } + + return 0; +} + +static void tvout_dbg_vip(struct seq_file *m, int val) +{ + int r, g, b, tmp, mask; + char str[MAX_STRING_LENGTH]; + static const char *const reorder[] = { "Y_G", "Cb_B", "Cr_R" }; + static const char *const clipping[] = { "No", "EAV/SAV", + "Limited range RGB/Y", "Limited range Cb/Cr", + "decided by register" }; + static const char *const round[] = { "8-bit", "10-bit", "12-bit" }; + static const char *const input_sel[] = { "Main (color matrix enabled)", + "Main (color matrix by-passed)", "", "", "", "", "", "", + "Aux (color matrix enabled)", "Aux (color matrix by-passed)", + "", "", "", "", "", "Force value" + }; + + seq_puts(m, "\t"); + + mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_R_SHIFT; + r = (val & mask) >> TVO_VIP_REORDER_R_SHIFT; + mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_G_SHIFT; + g = (val & mask) >> TVO_VIP_REORDER_G_SHIFT; + mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_B_SHIFT; + b = (val & mask) >> TVO_VIP_REORDER_B_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "Reorder: %s->%s %s->%s %s->%s", + reorder[r], reorder[TVO_VIP_REORDER_CR_R_SEL], + reorder[g], reorder[TVO_VIP_REORDER_Y_G_SEL], + reorder[b], reorder[TVO_VIP_REORDER_CB_B_SEL]); + seq_printf(m, "%-55s", str); + + mask = TVO_VIP_CLIP_MASK << TVO_VIP_CLIP_SHIFT; + tmp = (val & mask) >> TVO_VIP_CLIP_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "Clipping: %s", clipping[tmp]); + seq_printf(m, "%-55s", str); + + seq_printf(m, "\n%-40s", ""); + + mask = TVO_VIP_RND_MASK << TVO_VIP_RND_SHIFT; + tmp = (val & mask) >> TVO_VIP_RND_SHIFT; + snprintf(str, MAX_STRING_LENGTH, + "Round: input data rounded to %s per component", round[tmp]); + seq_printf(m, "%-55s", str); + + tmp = (val & TVO_VIP_SEL_INPUT_MASK); + snprintf(str, MAX_STRING_LENGTH, "Input selection: %s", input_sel[tmp]); + seq_printf(m, "%-55s", str); +} + +static void tvout_dbg_hd_dac_cfg(struct seq_file *m, int val) +{ + seq_puts(m, "\t HD DAC "); + seq_puts(m, val & 1 ? "disabled" : "enabled"); +} + +static int tvout_dbg_show(struct seq_file *m, + enum sti_tvout_connector_type type) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_tvout *tvout = dev_priv->tvout; + struct sti_tvout_connector *connector = tvout->connector[type]; + char name[TVOUT_CONNECTOR_NAME_MAX_LENGHT]; + int ret; + + if (!connector) + return -1; + + if (!connector->is_enabled) + return -1; + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret) + return ret; + + if (tvout == NULL) { + seq_puts(m, "No tvout available"); + goto out; + } + + ret = sti_tvout_dbg_type_to_connector_name(type, name); + if (ret) { + seq_puts(m, "No connector!"); + goto out; + } + + seq_printf(m, "\n%s connector: ", name); + seq_printf(m, "\nTVOUT: (virt base addr = 0x%p)", tvout->regs); + if (connector->is_enabled(connector)) { + if (connector->main_path) { + seq_puts(m, "\n Connected to the main path"); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M0); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M1); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M2); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M3); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M4); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M5); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M6); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M7); + TVOUT_DBG_DUMP(TVO_MAIN_IN_VID_FORMAT); + } else { + seq_puts(m, "\n Connected to the auxiliary path"); + TVOUT_DBG_DUMP(TVO_CSC_AUX_M0); + TVOUT_DBG_DUMP(TVO_CSC_AUX_M2); + TVOUT_DBG_DUMP(TVO_CSC_AUX_M3); + TVOUT_DBG_DUMP(TVO_CSC_AUX_M4); + TVOUT_DBG_DUMP(TVO_CSC_AUX_M5); + TVOUT_DBG_DUMP(TVO_CSC_AUX_M6); + TVOUT_DBG_DUMP(TVO_CSC_AUX_M7); + TVOUT_DBG_DUMP(TVO_AUX_IN_VID_FORMAT); + } + } else + seq_puts(m, " Disabled"); + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + TVOUT_DBG_DUMP(TVO_VIP_HDMI); + tvout_dbg_vip(m, readl(tvout->regs + TVO_VIP_HDMI)); + TVOUT_DBG_DUMP(TVO_HDMI_FORCE_COLOR_0); + TVOUT_DBG_DUMP(TVO_HDMI_FORCE_COLOR_1); + TVOUT_DBG_DUMP(TVO_HDMI_CLIP_VALUE_B_CB); + TVOUT_DBG_DUMP(TVO_HDMI_CLIP_VALUE_Y_G); + TVOUT_DBG_DUMP(TVO_HDMI_CLIP_VALUE_R_CR); + TVOUT_DBG_DUMP(TVO_HDMI_SYNC_SEL); + TVOUT_DBG_DUMP(TVO_HDMI_DFV_OBS); + break; + case STI_TVOUT_CONNECTOR_HDA: + TVOUT_DBG_DUMP(TVO_VIP_HDF); + tvout_dbg_vip(m, readl(tvout->regs + TVO_VIP_HDF)); + TVOUT_DBG_DUMP(TVO_HD_SYNC_SEL); + TVOUT_DBG_DUMP(TVO_HD_DAC_CFG_OFF); + tvout_dbg_hd_dac_cfg(m, + readl(tvout->regs + TVO_HD_DAC_CFG_OFF)); + break; + default: + goto out; + } + + if (connector->dbg_show) + connector->dbg_show(connector, m); +out: + seq_puts(m, "\n\n"); + mutex_unlock(&dev->struct_mutex); + return 0; +} + +int sti_tvout_hdmi_dbg_show(struct seq_file *m, void *arg) +{ + return tvout_dbg_show(m, STI_TVOUT_CONNECTOR_HDMI); +} + +int sti_tvout_hda_dbg_show(struct seq_file *m, void *arg) +{ + return tvout_dbg_show(m, STI_TVOUT_CONNECTOR_HDA); +} + static int sti_tvout_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev);
Make mixer driver more verbose
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/sti_mixer.c | 164 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_mixer.h | 2 + 2 files changed, 166 insertions(+)
diff --git a/drivers/gpu/drm/sti/sti_mixer.c b/drivers/gpu/drm/sti/sti_mixer.c index ab8daf6..fce72e4 100644 --- a/drivers/gpu/drm/sti/sti_mixer.c +++ b/drivers/gpu/drm/sti/sti_mixer.c @@ -6,7 +6,9 @@ * License terms: GNU General Public License (GPL), version 2 */
+#include "sti_drm_drv.h" #include "sti_mixer.h" +#include "sti_compositor.h" #include "sti_vtg_utils.h"
/* Identity: G=Y , B=Cb , R=Cr */ @@ -139,6 +141,8 @@ int sti_mixer_set_layer_depth(struct sti_mixer *mixer, struct sti_layer *layer) 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_MAIN_CRB val 0x%x mask 0x%x\n", layer_id, mask); sti_mixer_reg_writemask(mixer, GAM_MIXER_MAIN_CRB, layer_id, mask); @@ -197,6 +201,9 @@ int sti_mixer_set_layer_status(struct sti_mixer *mixer, { 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"); @@ -239,3 +246,160 @@ struct sti_mixer *sti_mixer_create(struct device *dev, int id,
return mixer; } + +static void sti_mixer_dbg_ctl(struct seq_file *m, int val) +{ + int count = 0; + + seq_puts(m, "\tEnabled: "); + if (val & 1) { + seq_puts(m, "BKG "); + count++; + } + val = val >> 1; + + if (val & 1) { + seq_puts(m, "VID0 "); + count++; + } + val = val >> 1; + if (val & 1) { + seq_puts(m, "VID1 "); + count++; + } + val = val >> 1; + if (val & 1) { + seq_puts(m, "GDP0 "); + count++; + } + val = val >> 1; + if (val & 1) { + seq_puts(m, "GDP1 "); + count++; + } + val = val >> 1; + if (val & 1) { + seq_puts(m, "GDP2 "); + count++; + } + val = val >> 1; + if (val & 1) { + seq_puts(m, "GDP3 "); + count++; + } + if (!count) + seq_puts(m, "Nothing"); +} + +static void sti_mixer_dbg_crb(struct seq_file *m, int val) +{ + int i; + + seq_puts(m, "\tDepth: "); + for (i = 0; i < GAM_MIXER_NB_DEPTH_LEVEL; i++) { + switch (val & GAM_DEPTH_MASK_ID) { + case GAM_DEPTH_VID0_ID: + seq_puts(m, "VID0"); + break; + case GAM_DEPTH_VID1_ID: + seq_puts(m, "VID1"); + break; + case GAM_DEPTH_GDP0_ID: + seq_puts(m, "GDP0"); + break; + case GAM_DEPTH_GDP1_ID: + seq_puts(m, "GDP1"); + break; + case GAM_DEPTH_GDP2_ID: + seq_puts(m, "GDP2"); + break; + case GAM_DEPTH_GDP3_ID: + seq_puts(m, "GDP3"); + break; + default: + seq_puts(m, "---"); + } + if (i < GAM_MIXER_NB_DEPTH_LEVEL - 1) + seq_puts(m, " < "); + val = val >> 3; + } +} + +static void sti_mixer_dbg_mxn(struct seq_file *m, void *addr) +{ + int i; + + for (i = 1; i < 8; i++) + seq_printf(m, "-0x%08X", (int)readl(addr + i * 4)); +} + +#define MIXER_DBG_DUMP(reg) seq_printf(m, "\n " #reg "\t 0x%08X", \ + sti_mixer_reg_read(mixer, reg)) + +int sti_mixer_dbg_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_compositor *compo = dev_priv->compo; + int i, ret; + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret) + return ret; + + if (compo == NULL) { + seq_puts(m, "No compositor available\n"); + goto out; + } + + for (i = 0; i < compo->nb_mixers; i++) { + struct drm_crtc *crtc; + struct drm_framebuffer *fb; + struct sti_mixer *mixer = compo->mixer[i]; + + seq_printf(m, "\n%s", sti_mixer_to_str(mixer)); + MIXER_DBG_DUMP(GAM_MIXER_MAIN_CTL); + sti_mixer_dbg_ctl(m, + sti_mixer_reg_read(mixer, + GAM_MIXER_MAIN_CTL)); + MIXER_DBG_DUMP(GAM_MIXER_MAIN_BKC); + MIXER_DBG_DUMP(GAM_MIXER_MAIN_BCO); + MIXER_DBG_DUMP(GAM_MIXER_MAIN_BCS); + MIXER_DBG_DUMP(GAM_MIXER_MAIN_AVO); + MIXER_DBG_DUMP(GAM_MIXER_MAIN_AVS); + MIXER_DBG_DUMP(GAM_MIXER_MAIN_CRB); + sti_mixer_dbg_crb(m, + sti_mixer_reg_read(mixer, + GAM_MIXER_MAIN_CRB)); + MIXER_DBG_DUMP(GAM_MIXER_MAIN_ACT); + MIXER_DBG_DUMP(GAM_MIXER_MAIN_MBP); + MIXER_DBG_DUMP(GAM_MIXER_MAIN_MX0); + sti_mixer_dbg_mxn(m, mixer->regs + GAM_MIXER_MAIN_MX0); + seq_puts(m, "\n"); + + crtc = &mixer->drm_crtc; + if (!crtc) { + seq_puts(m, " Not connected to any DRM CRTC\n"); + continue; + } + seq_printf(m, " Connected to DRM CRTC #%d which is:\n", + crtc->base.id); + seq_puts(m, crtc->enabled ? "\tEnabled\n" : "\tDisabled\n"); + seq_printf(m, "\tMode: %s %dHz\n", + crtc->mode.name, crtc->mode.vrefresh); + seq_printf(m, "\tat %d,%d\n", crtc->x, crtc->y); + + fb = crtc->fb; + if (!fb) { + seq_puts(m, "\tNot connected to any DRM FB\n"); + continue; + } + seq_printf(m, "\tConnected to DRM FB #%d, %dx%d, %.4s\n", + fb->base.id, + fb->width, fb->height, (char *)&fb->pixel_format); + } +out: + mutex_unlock(&dev->struct_mutex); + return 0; +} diff --git a/drivers/gpu/drm/sti/sti_mixer.h b/drivers/gpu/drm/sti/sti_mixer.h index a05e21f..abdd1ad 100644 --- a/drivers/gpu/drm/sti/sti_mixer.h +++ b/drivers/gpu/drm/sti/sti_mixer.h @@ -43,6 +43,8 @@ int sti_mixer_active_video_area(struct sti_mixer *mixer,
void sti_mixer_set_background_status(struct sti_mixer *mixer, bool enable);
+int sti_mixer_dbg_show(struct seq_file *m, void *arg); + /* depth in Cross-bar control = z order */ #define GAM_MIXER_NB_DEPTH_LEVEL 7
Make the link between all the hardware drivers and DRM/KMS interface. Create the driver itself and make it register all the sub-components. Use GEM CMA helpers for buffer allocation.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/Kconfig | 8 + drivers/gpu/drm/sti/Makefile | 7 +- drivers/gpu/drm/sti/sti_compositor.c | 41 ++- drivers/gpu/drm/sti/sti_drm_connector.c | 195 ++++++++++++++ drivers/gpu/drm/sti/sti_drm_connector.h | 16 ++ drivers/gpu/drm/sti/sti_drm_crtc.c | 440 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_drm_crtc.h | 21 ++ drivers/gpu/drm/sti/sti_drm_drv.c | 435 +++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_drm_encoder.c | 201 +++++++++++++++ drivers/gpu/drm/sti/sti_drm_encoder.h | 16 ++ drivers/gpu/drm/sti/sti_drm_plane.c | 195 ++++++++++++++ drivers/gpu/drm/sti/sti_drm_plane.h | 16 ++ 12 files changed, 1587 insertions(+), 4 deletions(-) create mode 100644 drivers/gpu/drm/sti/sti_drm_connector.c create mode 100644 drivers/gpu/drm/sti/sti_drm_connector.h create mode 100644 drivers/gpu/drm/sti/sti_drm_crtc.c create mode 100644 drivers/gpu/drm/sti/sti_drm_crtc.h create mode 100644 drivers/gpu/drm/sti/sti_drm_drv.c create mode 100644 drivers/gpu/drm/sti/sti_drm_encoder.c create mode 100644 drivers/gpu/drm/sti/sti_drm_encoder.h create mode 100644 drivers/gpu/drm/sti/sti_drm_plane.c create mode 100644 drivers/gpu/drm/sti/sti_drm_plane.h
diff --git a/drivers/gpu/drm/sti/Kconfig b/drivers/gpu/drm/sti/Kconfig index 9c1218c..11b6d62 100644 --- a/drivers/gpu/drm/sti/Kconfig +++ b/drivers/gpu/drm/sti/Kconfig @@ -1,10 +1,18 @@ config DRM_STI bool "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 + config VTAC_STI tristate "Video Trafic Advance Communication Rx and Tx for STMicroelectronics SoC stiH41x Series" depends on DRM_STI diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 3b804d4..8eee160 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -1,6 +1,11 @@ ccflags-y := -Iinclude/drm
-stidrm-y := sti_tvout.o \ +stidrm-y := sti_drm_drv.o \ + sti_drm_crtc.o \ + sti_drm_plane.o \ + sti_drm_connector.o \ + sti_drm_encoder.o \ + sti_tvout.o \ sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ diff --git a/drivers/gpu/drm/sti/sti_compositor.c b/drivers/gpu/drm/sti/sti_compositor.c index a163344..13ea362 100644 --- a/drivers/gpu/drm/sti/sti_compositor.c +++ b/drivers/gpu/drm/sti/sti_compositor.c @@ -6,6 +6,7 @@ * 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> @@ -14,6 +15,7 @@
#include "sti_compositor.h" #include "sti_gdp.h" +#include "sti_drm_crtc.h"
static const struct of_device_id compositor_match_types[];
@@ -82,9 +84,10 @@ static int sti_compositor_init_subdev(struct sti_compositor *compo, return 0; }
-static int sti_compositor_probe(struct platform_device *pdev) +static int sti_compositor_bind(struct device *dev, struct device *master, + void *data) { - struct device *dev = &pdev->dev; + struct platform_device *pdev = to_platform_device(dev); struct device_node *np = dev->of_node; struct sti_compositor *compo; struct resource *res; @@ -98,6 +101,7 @@ static int sti_compositor_probe(struct platform_device *pdev) } DRM_DEBUG_DRIVER("Compositor %p\n", compo); 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_match_types, np)->data); @@ -164,6 +168,29 @@ static int sti_compositor_probe(struct platform_device *pdev) 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 int sti_compositor_probe(struct platform_device *pdev) +{ + DRM_INFO("%s\n", __func__); + 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 const struct of_device_id compositor_match_types[] = { { .compatible = "st,stih416-compositor", @@ -176,12 +203,20 @@ static const struct of_device_id compositor_match_types[] = { { /* end node */ }
}; +MODULE_DEVICE_TABLE(of, compositor_match_types);
-struct platform_driver sti_compositor_driver = { +static struct platform_driver sti_compositor_driver = { .driver = { .name = "sti-compositor", .owner = THIS_MODULE, .of_match_table = compositor_match_types, }, .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"); diff --git a/drivers/gpu/drm/sti/sti_drm_connector.c b/drivers/gpu/drm/sti/sti_drm_connector.c new file mode 100644 index 0000000..b34a402 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_connector.c @@ -0,0 +1,195 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "sti_drm_connector.h" +#include "sti_drm_drv.h" + +#define to_sti_connector(x) container_of(x, struct sti_connector, drm_connector) + +/* + * sti specific connector structure + * + * @drm_encoder: connector object + * @encoder: associated encoder + * @tvout: pointer on tvout driver + * @type: tvout connector type + */ +struct sti_connector { + struct drm_connector drm_connector; + struct drm_encoder *encoder; + struct sti_tvout *tvout; + enum sti_tvout_connector_type type; +}; + +static int sti_drm_connector_get_modes(struct drm_connector *connector) +{ + struct sti_connector *sti_connector = to_sti_connector(connector); + struct drm_device *dev = connector->dev; + + dev_dbg(dev->dev, "%s\n", __func__); + + return sti_tvout_get_modes(sti_connector->tvout, sti_connector->type, + connector); +} + +static int sti_drm_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_connector *sti_connector = to_sti_connector(connector); + struct drm_device *dev = connector->dev; + int ret = MODE_BAD; + + dev_dbg(dev->dev, "%s\n", __func__); + + if (!sti_tvout_check_mode + (sti_connector->tvout, sti_connector->type, mode)) + ret = MODE_OK; + + return ret; +} + +struct drm_encoder *sti_drm_best_encoder(struct drm_connector *connector) +{ + struct sti_connector *sti_connector = to_sti_connector(connector); + struct drm_device *dev = connector->dev; + + dev_dbg(dev->dev, "%s\n", __func__); + + /* Best encoder is the one associated during connector creation */ + return sti_connector->encoder; +} + +static struct drm_connector_helper_funcs sti_drm_connector_helper_funcs = { + .get_modes = sti_drm_connector_get_modes, + .mode_valid = sti_drm_connector_mode_valid, + .best_encoder = sti_drm_best_encoder, +}; + +static void sti_drm_connector_dpms(struct drm_connector *connector, int mode) +{ + struct drm_device *dev = connector->dev; + + dev_dbg(dev->dev, "%s\n", __func__); + + drm_helper_connector_dpms(connector, mode); +} + +/* get detection status of display device. */ +static enum drm_connector_status +sti_drm_connector_detect(struct drm_connector *connector, bool force) +{ + enum drm_connector_status status = connector_status_disconnected; + struct sti_connector *sti_connector = to_sti_connector(connector); + struct drm_device *dev = connector->dev; + + dev_dbg(dev->dev, "%s\n", __func__); + + if (sti_tvout_connector_detect(sti_connector->tvout, + sti_connector->type)) + status = connector_status_connected; + + return status; +} + +static void sti_drm_connector_destroy(struct drm_connector *connector) +{ + struct sti_connector *sti_connector = to_sti_connector(connector); + struct drm_device *dev = connector->dev; + + dev_dbg(dev->dev, "%s\n", __func__); + + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); + kfree(sti_connector); +} + +static struct drm_connector_funcs sti_drm_connector_funcs = { + .dpms = sti_drm_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = sti_drm_connector_detect, + .destroy = sti_drm_connector_destroy, +}; + +struct drm_connector *sti_drm_connector_create(struct drm_device *dev, + struct sti_tvout *tvout, + struct drm_encoder *encoder, + enum sti_tvout_connector_type + type) +{ + struct sti_connector *sti_connector; + struct drm_connector *connector; + int connector_type; + int err; + + dev_dbg(dev->dev, "%s\n", __func__); + + /* Create the tvout connector according to the type */ + tvout->connector[type] = tvout->connector_create[type] (tvout); + if (!tvout->connector[type]) { + DRM_INFO("%s: failed to create connector (type = %d)\n", + __func__, type); + return NULL; + } + + sti_connector = kzalloc(sizeof(*sti_connector), GFP_KERNEL); + if (!sti_connector) { + DRM_ERROR("failed to allocate connector\n"); + return NULL; + } + + connector = &sti_connector->drm_connector; + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + connector_type = DRM_MODE_CONNECTOR_HDMIA; + connector->polled = DRM_CONNECTOR_POLL_HPD; + break; + case STI_TVOUT_CONNECTOR_HDA: + connector_type = DRM_MODE_CONNECTOR_Component; + break; + case STI_TVOUT_CONNECTOR_DVO: + connector_type = DRM_MODE_CONNECTOR_LVDS; + break; + case STI_TVOUT_CONNECTOR_DENC: + connector_type = DRM_MODE_CONNECTOR_Composite; + break; + default: + connector_type = DRM_MODE_CONNECTOR_Unknown; + break; + } + + drm_connector_init(dev, connector, &sti_drm_connector_funcs, + connector_type); + drm_connector_helper_add(connector, &sti_drm_connector_helper_funcs); + + err = drm_sysfs_connector_add(connector); + if (err) + goto err_connector; + + sti_connector->tvout = tvout; + sti_connector->encoder = encoder; + sti_connector->type = type; + + err = drm_mode_connector_attach_encoder(connector, encoder); + if (err) { + DRM_ERROR("Failed to attach a connector to a encoder\n"); + goto err_sysfs; + } + + DRM_DEBUG_DRIVER("Connector has been created\n"); + + return connector; + +err_sysfs: + drm_sysfs_connector_remove(connector); +err_connector: + drm_connector_cleanup(connector); + kfree(sti_connector); + return NULL; +} diff --git a/drivers/gpu/drm/sti/sti_drm_connector.h b/drivers/gpu/drm/sti/sti_drm_connector.h new file mode 100644 index 0000000..068c674 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_connector.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_DRM_CONNECTOR_H_ +#define _STI_DRM_CONNECTOR_H_ + +#include "sti_tvout.h" + +struct drm_connector *sti_drm_connector_create(struct drm_device *dev, + struct sti_tvout *tvout, struct drm_encoder *encoder, + enum sti_tvout_connector_type type); + +#endif diff --git a/drivers/gpu/drm/sti/sti_drm_crtc.c b/drivers/gpu/drm/sti/sti_drm_crtc.c new file mode 100644 index 0000000..5c06d70 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_crtc.c @@ -0,0 +1,440 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * 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_drm_drv.h" +#include "sti_drm_crtc.h" +#include "sti_compositor.h" +#include "sti_vtg_utils.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; + + dev_dbg(dev, "%s\n", __func__); + if ((!mixer || !compo)) { + DRM_ERROR("Can not find mixer or compositor)\n"); + return; + } + + /* Find GDP0 which is reserved to the CRTC FB */ + layer = sti_layer_find_layer(compo->layer, STI_GDP_0); + 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 */ + dev_dbg(crtc->dev->dev, "%s\n", __func__); + 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->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 1; + } + if (clk_prepare_enable(clk)) { + DRM_ERROR("Failed to prepare/enable pix clk\n"); + return 1; + } + + sti_vtg_setconfig(mixer->id == STI_MIXER_MAIN ? VTG_MAIN : VTG_AUX, + &crtc->mode); + + /* GDP0 is reserved to the CRTC FB */ + layer = sti_layer_find_layer(compo->layer, STI_GDP_0); + if (!layer) { + DRM_ERROR("Can not find GDP0)\n"); + return 1; + } + + /* 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 1; + } + res = sti_mixer_active_video_area(mixer, &crtc->mode); + if (res) { + DRM_ERROR("Can not set active video area\n"); + return 1; + } + + if ((mode->hdisplay != crtc->fb->width) || + (mode->vdisplay != crtc->fb->height)) + DRM_DEBUG_KMS("WARNING: fb and display mode sizes differ\n"); + + w = crtc->fb->width - x; + h = crtc->fb->height - y; + + if ((w <= 0) || (h <= 0)) { + DRM_ERROR("Coordinates outside FB\n"); + return -EINVAL; + } + + return sti_layer_prepare(layer, crtc->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 = 0; + + DRM_DEBUG_KMS("CRTC:%d (%s) fb:%d (%d,%d)\n", + crtc->base.id, sti_mixer_to_str(mixer), + crtc->fb->base.id, x, y); + + /* GDP0 is reserved to the CRTC FB */ + layer = sti_layer_find_layer(compo->layer, STI_GDP_0); + if (!layer) { + DRM_ERROR("Can not find GDP0)\n"); + ret = -1; + goto out; + } + + w = crtc->fb->width - crtc->x; + h = crtc->fb->height - crtc->y; + + if ((w <= 0) || (h <= 0)) { + DRM_ERROR("Coordinates outside FB\n"); + ret = -EINVAL; + goto out; + } + + ret = sti_layer_prepare(layer, crtc->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) +{ + dev_dbg(crtc->dev->dev, "%s\n", __func__); +} + +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 GDP0 */ + layer = sti_layer_find_layer(compo->layer, STI_GDP_0); + 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 sti_drm_private *dev_priv = drm_dev->dev_private; + struct drm_framebuffer *old_fb; + struct sti_mixer *mixer = to_sti_mixer(crtc); + unsigned long flags; + int ret = 0; + + DRM_DEBUG_KMS("fb %d --> fb %d\n", crtc->fb->base.id, fb->base.id); + + mutex_lock(&drm_dev->struct_mutex); + + old_fb = crtc->fb; + crtc->fb = fb; + ret = sti_drm_crtc_mode_set_base(crtc, crtc->x, crtc->y, old_fb); + if (ret) { + DRM_ERROR("failed\n"); + crtc->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); + list_add_tail(&event->base.link, &dev_priv->pageflip_evt_list); + 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 sti_compositor *compo = container_of(nb, + struct sti_compositor, + vtg_vblank_nb); + struct drm_device *drm_dev; + int *crtc = data; + unsigned long flags; + struct drm_pending_vblank_event *e, *t; + struct sti_drm_private *priv; + + drm_dev = compo->mixer[*crtc]->drm_crtc.dev; + priv = drm_dev->dev_private; + + dev_dbg(drm_dev->dev, "%s\n", __func__); + + 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); + list_for_each_entry_safe(e, t, &priv->pageflip_evt_list, base.link) { + if (*crtc != e->pipe) + continue; + + list_del(&e->base.link); + drm_send_vblank_event(drm_dev, -1, e); + drm_vblank_put(drm_dev, *crtc); + } + 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; + DRM_DEBUG_DRIVER("\n"); + + if (sti_vtg_register_client(crtc, vtg_vblank_nb)) { + DRM_ERROR("Cannot register VTG notifier\n"); + return 1; + } + + return 0; +} + +void sti_drm_crtc_disable_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; + unsigned long flags; + struct drm_pending_vblank_event *e, *t; + struct sti_drm_private *priv = dev->dev_private; + DRM_DEBUG_DRIVER("\n"); + + if (sti_vtg_unregister_client(crtc, 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); + list_for_each_entry_safe(e, t, &priv->pageflip_evt_list, base.link) { + if (crtc != e->pipe) + continue; + list_del(&e->base.link); + drm_vblank_put(dev, crtc); + } + spin_unlock_irqrestore(&dev->event_lock, flags); + +} + +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_crtc *crtc = &mixer->drm_crtc; + int res; + + dev_dbg(drm_dev->dev, "%s\n", __func__); + res = drm_crtc_init(drm_dev, crtc, &sti_crtc_funcs); + if (res) { + DRM_ERROR("Can not initialze CRTC\n"); + return 1; + } + + 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; +} diff --git a/drivers/gpu/drm/sti/sti_drm_crtc.h b/drivers/gpu/drm/sti/sti_drm_crtc.h new file mode 100644 index 0000000..9d31e41 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_crtc.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * 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> + +#include "sti_mixer.h" + +int sti_drm_crtc_init(struct drm_device *drm_dev, struct sti_mixer *mixer); +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 diff --git a/drivers/gpu/drm/sti/sti_drm_drv.c b/drivers/gpu/drm/sti/sti_drm_drv.c new file mode 100644 index 0000000..952efb6 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_drv.c @@ -0,0 +1,435 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * 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" +#include "sti_drm_plane.h" +#include "sti_compositor.h" +#include "sti_gdp.h" +#include "sti_drm_encoder.h" +#include "sti_drm_connector.h" +#include "sti_tvout.h" + +#define DRIVER_NAME "sti" +#define DRIVER_DESC "STMicroelectronics SoC DRM" +#define DRIVER_DATE "20130905" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +#ifdef CONFIG_DRM_STI_FBDEV +#define FBDEV_CREATE_DELAY 2000 +struct drm_device *sti_drm_device; + +static void stid_fbdev_create(struct work_struct *dummy) +{ + if (!sti_drm_device) + return; + + drm_fbdev_cma_init(sti_drm_device, 32, + sti_drm_device->mode_config.num_crtc, + sti_drm_device->mode_config.num_connector); +} + +static DECLARE_DELAYED_WORK(sti_fbdev_work, stid_fbdev_create); +#endif + +static int sti_drm_tvout_init(struct device *dev, void *data) +{ + struct sti_tvout *tvout = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + struct drm_encoder *encoder; + int i; + struct sti_drm_private *dev_priv = drm_dev->dev_private; + + dev_priv->tvout = tvout; + + tvout->drm_dev = drm_dev; + + /* Register all encoder/connector couples supported by tvout */ + for (i = 0; i < STI_TVOUT_CONNECTOR_MAX; i++) { + if (tvout->connector_create[i] != NULL) { + encoder = sti_drm_encoder_create(drm_dev, tvout, + 1 << 0, i); + sti_drm_connector_create(drm_dev, tvout, encoder, i); + } + } + + return 0; +} + +static int sti_drm_compositor_init(struct device *dev, void *data) +{ + struct sti_compositor *compo = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + int i, crtc = 0, plane = 0; + struct sti_drm_private *dev_priv = drm_dev->dev_private; + + dev_dbg(dev, "%s\n", __func__); + + dev_priv->compo = compo; + INIT_LIST_HEAD(&dev_priv->pageflip_evt_list); + + for (i = 0; i < compo->nb_mixers; i++) { + if (compo->mixer[i]) { + sti_drm_crtc_init(drm_dev, compo->mixer[i]); + crtc++; + } + } + if (crtc == 0) { + DRM_ERROR("No CRTC available\n"); + return 1; + } + + drm_vblank_init(drm_dev, crtc); + /* Allow usage of vblank without having to call drm_irq_install */ + drm_dev->irq_enabled = 1; + + for (i = 0; i < compo->nb_layers; i++) { + if (compo->layer[i]) { + /* Create planes for GDP. + * except GDP0 as it is reserved for CRTC FB */ + enum sti_layer_desc desc = compo->layer[i]->desc; + enum sti_layer_type type = desc & STI_LAYER_TYPE_MASK; + + if ((type == STI_GDP) && (desc != STI_GDP_0)) { + sti_drm_plane_init(drm_dev, compo->layer[i], + (1 << crtc) - 1); + plane++; + } + } + } + + 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 struct drm_mode_config_funcs sti_drm_mode_config_funcs = { + .fb_create = drm_fb_cma_create, +}; + +#ifdef CONFIG_DEBUG_FS +static struct drm_info_list sti_drm_dbg_list[] = { + {"gdp", sti_gdp_dbg_show, 0}, + {"node", sti_gdp_node_dbg_show, 0}, + {"mixer", sti_mixer_dbg_show, 0}, + {"hdmi", sti_tvout_hdmi_dbg_show, 0, NULL}, + {"hda", sti_tvout_hda_dbg_show, 0, NULL}, +}; + +static int sti_drm_fps_get(void *data, u64 *val) +{ + struct drm_device *dev = data; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_compositor *compo = dev_priv->compo; + int i; + + *val = 0; + for (i = 0; i < compo->nb_layers; i++) + *val |= (compo->layer[i]->fps_info.output) << i; + + return 0; +} + +static int sti_drm_fps_set(void *data, u64 val) +{ + struct drm_device *dev = data; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_compositor *compo = dev_priv->compo; + int i; + + for (i = 0; i < compo->nb_layers; i++) + compo->layer[i]->fps_info.output = (val >> i) & 1; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(sti_drm_fps_fops, + sti_drm_fps_get, sti_drm_fps_set, "%llu\n"); + +static int sti_drm_debugfs_create(struct dentry *root, struct drm_minor *minor, + const char *name, + const struct file_operations *fops) +{ + struct drm_device *dev = minor->dev; + struct drm_info_node *node; + struct dentry *ent; + + ent = debugfs_create_file(name, S_IRUGO | S_IWUSR, root, dev, fops); + if (IS_ERR(ent)) + return PTR_ERR(ent); + + node = kmalloc(sizeof(struct drm_info_node), GFP_KERNEL); + if (node == NULL) { + debugfs_remove(ent); + return -ENOMEM; + } + + node->minor = minor; + node->dent = ent; + node->info_ent = (void *)fops; + + mutex_lock(&minor->debugfs_lock); + list_add(&node->list, &minor->debugfs_list); + mutex_unlock(&minor->debugfs_lock); + + return 0; +} + +int sti_drm_dbg_init(struct drm_minor *minor) +{ + int ret; + + ret = sti_drm_debugfs_create(minor->debugfs_root, minor, "fps_show", + &sti_drm_fps_fops); + if (ret) + goto err; + + ret = drm_debugfs_create_files(sti_drm_dbg_list, + ARRAY_SIZE(sti_drm_dbg_list), + minor->debugfs_root, minor); + if (ret) + goto err; + + DRM_INFO("%s debugfs installed\n", DRIVER_NAME); + + return ret; +err: + DRM_ERROR("Cannot install debugfs\n"); + return ret; +} + +void sti_drm_dbg_cleanup(struct drm_minor *minor) +{ + drm_debugfs_remove_files(sti_drm_dbg_list, + ARRAY_SIZE(sti_drm_dbg_list), minor); + + drm_debugfs_remove_files((struct drm_info_list *)&sti_drm_fps_fops, + 1, minor); +} +#endif + +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; + struct device_driver *drv; + int err; + + DRM_INFO("%s drm_device: %p\n", __func__, dev); + + 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; + + drm_mode_config_init(dev); + drm_kms_helper_poll_init(dev); + + sti_drm_mode_config_init(dev); + + component_bind_all(dev->dev, dev); + + /* look for compositors HW */ + drv = driver_find("sti-compositor", &platform_bus_type); + if (!drv) { + DRM_ERROR("No compositor found\n"); + return -ENOMEM; + } + err = driver_for_each_device(drv, NULL, dev, sti_drm_compositor_init); + if (err) + DRM_ERROR("Failed to initialize CRTC\n"); + + /* look for TVOUT driver */ + drv = driver_find("sti-tvout", &platform_bus_type); + if (!drv) { + DRM_ERROR("No tvout found\n"); + return -ENOMEM; + } + err = driver_for_each_device(drv, NULL, dev, sti_drm_tvout_init); + if (err) + DRM_ERROR("Failed to initialize TVOUT\n"); + + drm_helper_disable_unused_functions(dev); +#ifdef CONFIG_DRM_STI_FBDEV + sti_drm_device = dev; + + schedule_delayed_work(&sti_fbdev_work, + msecs_to_jiffies(FBDEV_CREATE_DELAY)); +#endif + return err; +} + +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, + +#ifdef CONFIG_DEBUG_FS + .debugfs_init = sti_drm_dbg_init, + .debugfs_cleanup = sti_drm_dbg_cleanup, +#endif + .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 1; +} + +static int sti_drm_add_components(struct device *master, struct master *m) +{ + struct device_node *np = master->of_node; + struct device_node *child_np; + + child_np = of_get_next_available_child(np, NULL); + + while (child_np) { + DRM_INFO("add child %s\n", child_np->name); + component_master_add_child(m, compare_of, child_np); + of_node_put(child_np); + child_np = of_get_next_available_child(np, child_np); + } + + return 0; +} + +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 = { + .add_components = sti_drm_add_components, + .bind = sti_drm_bind, + .unbind = sti_drm_unbind, +}; + +static int sti_drm_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + + DRM_INFO("%s\n", __func__); + + of_platform_populate(node, NULL, NULL, dev); + + dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + + return component_master_add(&pdev->dev, &sti_drm_ops); +} + +static int sti_drm_platform_remove(struct platform_device *pdev) +{ + DRM_INFO("%s\n", __func__); + + component_master_del(&pdev->dev, &sti_drm_ops); + + 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"); diff --git a/drivers/gpu/drm/sti/sti_drm_encoder.c b/drivers/gpu/drm/sti/sti_drm_encoder.c new file mode 100644 index 0000000..96194cf --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_encoder.c @@ -0,0 +1,201 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "sti_drm_crtc.h" +#include "sti_drm_encoder.h" +#include "sti_tvout.h" + +#define to_sti_encoder(x) container_of(x, struct sti_encoder, drm_encoder) + +#define ENCODER_MAIN_CRTC_MASK (1 << 0) + +/* + * sti specific encoder structure. + * + * @drm_encoder: encoder object. + * @tvout: pointer on tvout driver. + * @type: tvout connector type. + */ +struct sti_encoder { + struct drm_encoder drm_encoder; + struct sti_tvout *tvout; + enum sti_tvout_connector_type type; +}; + +static void sti_drm_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool sti_drm_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void sti_drm_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct sti_encoder *sti_encoder = to_sti_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct drm_connector *connector; + + dev_dbg(dev->dev, "%s\n", __func__); + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->encoder == encoder) { + sti_tvout_set_mode(sti_encoder->tvout, mode, + sti_encoder->type); + break; + } + } +} + +static void sti_drm_encoder_prepare(struct drm_encoder *encoder) +{ + struct sti_encoder *sti_encoder = to_sti_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct drm_connector *connector; + + dev_dbg(dev->dev, "%s\n", __func__); + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->encoder == encoder) { + sti_tvout_prepare(sti_encoder->tvout, + sti_encoder->type); + break; + } + } +} + +static void sti_drm_encoder_commit(struct drm_encoder *encoder) +{ + struct sti_encoder *sti_encoder = to_sti_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct drm_connector *connector; + + dev_dbg(dev->dev, "%s\n", __func__); + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->encoder == encoder) { + sti_tvout_commit(sti_encoder->tvout, + sti_encoder->type, + sti_drm_crtc_is_main(encoder->crtc)); + break; + } + } +} + +static void sti_drm_encoder_disable(struct drm_encoder *encoder) +{ + struct sti_encoder *sti_encoder = to_sti_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct drm_connector *connector; + struct drm_connector_helper_funcs *connector_funcs; + + dev_dbg(dev->dev, "%s\n", __func__); + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + connector_funcs = connector->helper_private; + if (connector_funcs->best_encoder(connector) == encoder) { + sti_tvout_disable(sti_encoder->tvout, + sti_encoder->type); + break; + } + } +} + +static const struct drm_encoder_helper_funcs sti_drm_encoder_helper_funcs = { + .dpms = sti_drm_encoder_dpms, + .mode_fixup = sti_drm_encoder_mode_fixup, + .mode_set = sti_drm_encoder_mode_set, + .prepare = sti_drm_encoder_prepare, + .commit = sti_drm_encoder_commit, + .disable = sti_drm_encoder_disable, +}; + +static void sti_drm_encoder_destroy(struct drm_encoder *encoder) +{ + struct sti_encoder *sti_encoder = to_sti_encoder(encoder); + struct drm_device *dev = encoder->dev; + + dev_dbg(dev->dev, "%s\n", __func__); + + drm_encoder_cleanup(encoder); + kfree(sti_encoder); +} + +static const struct drm_encoder_funcs sti_drm_encoder_funcs = { + .destroy = sti_drm_encoder_destroy, +}; + +struct drm_encoder *sti_drm_encoder_create(struct drm_device *dev, + struct sti_tvout *tvout, + unsigned int possible_crtcs, + enum sti_tvout_connector_type type) +{ + struct sti_encoder *sti_encoder; + struct drm_encoder *encoder; + int encoder_type; + + dev_dbg(dev->dev, "%s\n", __func__); + + if (!dev || !tvout || !possible_crtcs) + return NULL; + + sti_encoder = kzalloc(sizeof(*sti_encoder), GFP_KERNEL); + if (!sti_encoder) { + DRM_ERROR("failed to allocate encoder\n"); + return NULL; + } + + sti_encoder->tvout = tvout; + sti_encoder->type = type; + encoder = &sti_encoder->drm_encoder; + encoder->possible_crtcs = possible_crtcs; + + /* HDMI connector only supports main crtc */ + if (type == STI_TVOUT_CONNECTOR_HDMI) + encoder->possible_crtcs &= ENCODER_MAIN_CRTC_MASK; + + /* HD Analog connector may support aux CRTC. Not implemented yet */ + if (type == STI_TVOUT_CONNECTOR_HDA) + encoder->possible_crtcs &= ENCODER_MAIN_CRTC_MASK; + + DRM_DEBUG_DRIVER("possible_crtcs = 0x%x\n", encoder->possible_crtcs); + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + encoder_type = DRM_MODE_ENCODER_TMDS; + encoder->possible_clones = 1 << STI_TVOUT_CONNECTOR_HDA; + break; + case STI_TVOUT_CONNECTOR_HDA: + encoder_type = DRM_MODE_ENCODER_DAC; + encoder->possible_clones = 1 << STI_TVOUT_CONNECTOR_HDMI; + break; + case STI_TVOUT_CONNECTOR_DENC: + encoder_type = DRM_MODE_ENCODER_DAC; + break; + case STI_TVOUT_CONNECTOR_DVO: + encoder_type = DRM_MODE_ENCODER_LVDS; + break; + default: + encoder_type = DRM_MODE_ENCODER_NONE; + break; + } + + drm_encoder_init(dev, encoder, &sti_drm_encoder_funcs, encoder_type); + + drm_encoder_helper_add(encoder, &sti_drm_encoder_helper_funcs); + + DRM_DEBUG_DRIVER("encoder has been created\n"); + + return encoder; +} diff --git a/drivers/gpu/drm/sti/sti_drm_encoder.h b/drivers/gpu/drm/sti/sti_drm_encoder.h new file mode 100644 index 0000000..35d9945 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_encoder.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_DRM_ENCODER_H_ +#define _STI_DRM_ENCODER_H_ + +#include "sti_tvout.h" + +struct drm_encoder *sti_drm_encoder_create(struct drm_device *dev, + struct sti_tvout *tvout, unsigned int possible_crtcs, + enum sti_tvout_connector_type type); + +#endif diff --git a/drivers/gpu/drm/sti/sti_drm_plane.c b/drivers/gpu/drm/sti/sti_drm_plane.c new file mode 100644 index 0000000..e34c33a --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_plane.c @@ -0,0 +1,195 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * 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_drm_drv.h" +#include "sti_drm_plane.h" +#include "sti_compositor.h" +#include "sti_vtg_utils.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 1; + else + 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); + + dev_dbg(dev->dev, "%s zorder:%llu\n", __func__, default_val); + + 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) +{ + int err, i; + uint64_t default_zorder = 0; + + dev_dbg(dev->dev, "%s\n", __func__); + + err = drm_plane_init(dev, &layer->plane, possible_crtcs, + &sti_drm_plane_funcs, + sti_layer_get_formats(layer), + sti_layer_get_nb_formats(layer), false); + 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; + + 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; +} diff --git a/drivers/gpu/drm/sti/sti_drm_plane.h b/drivers/gpu/drm/sti/sti_drm_plane.h new file mode 100644 index 0000000..d182066 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_plane.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * 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> +#include "sti_layer.h" + +struct drm_plane *sti_drm_plane_init(struct drm_device *dev, + struct sti_layer *layer, unsigned int possible_crtcs); + +#endif
High Quality Video Display Plug is a data path for doing high quality treatments (filtering, scaling) on video flow.
HQVDP IP is tunneled into compositor via VDP input.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/Kconfig | 2 +- drivers/gpu/drm/sti/Makefile | 1 + drivers/gpu/drm/sti/sti_drm_drv.c | 12 + drivers/gpu/drm/sti/sti_drm_drv.h | 3 + drivers/gpu/drm/sti/sti_hqvdp.c | 1107 +++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_hqvdp.h | 55 ++ drivers/gpu/drm/sti/sti_hqvdp_lut.h | 373 ++++++++++++ drivers/gpu/drm/sti/sti_layer.c | 27 +- drivers/gpu/drm/sti/sti_layer.h | 3 + 9 files changed, 1576 insertions(+), 7 deletions(-) create mode 100644 drivers/gpu/drm/sti/sti_hqvdp.c create mode 100644 drivers/gpu/drm/sti/sti_hqvdp.h create mode 100644 drivers/gpu/drm/sti/sti_hqvdp_lut.h
diff --git a/drivers/gpu/drm/sti/Kconfig b/drivers/gpu/drm/sti/Kconfig index 11b6d62..2c80bcf 100644 --- a/drivers/gpu/drm/sti/Kconfig +++ b/drivers/gpu/drm/sti/Kconfig @@ -14,7 +14,7 @@ config DRM_STI_FBDEV Choose this option to enable FBDEV on top of DRM for STM stiH41x chipset
config VTAC_STI - tristate "Video Trafic Advance Communication Rx and Tx for STMicroelectronics SoC stiH41x Series" + bool "Video Trafic Advance Communication Rx and Tx for STMicroelectronics SoC stiH41x Series" depends on DRM_STI help Choose this option to enable VTAC on STM stiH41x chipset diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 8eee160..b71f0a7 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -10,6 +10,7 @@ stidrm-y := sti_drm_drv.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ + sti_hqvdp.o \ sti_compositor.o \ sti_layer.o \ sti_mixer.o \ diff --git a/drivers/gpu/drm/sti/sti_drm_drv.c b/drivers/gpu/drm/sti/sti_drm_drv.c index 952efb6..ccd1f17 100644 --- a/drivers/gpu/drm/sti/sti_drm_drv.c +++ b/drivers/gpu/drm/sti/sti_drm_drv.c @@ -23,6 +23,7 @@ #include "sti_drm_encoder.h" #include "sti_drm_connector.h" #include "sti_tvout.h" +#include "sti_hqvdp.h"
#define DRIVER_NAME "sti" #define DRIVER_DESC "STMicroelectronics SoC DRM" @@ -131,6 +132,7 @@ static struct drm_info_list sti_drm_dbg_list[] = { {"mixer", sti_mixer_dbg_show, 0}, {"hdmi", sti_tvout_hdmi_dbg_show, 0, NULL}, {"hda", sti_tvout_hda_dbg_show, 0, NULL}, + {"vdp", sti_hqvdp_dbg_show, 0, NULL}, };
static int sti_drm_fps_get(void *data, u64 *val) @@ -283,6 +285,16 @@ static int sti_drm_load(struct drm_device *dev, unsigned long flags) if (err) DRM_ERROR("Failed to initialize TVOUT\n");
+ /* Load HQVDP firmware */ + drv = driver_find("sti-hqvdp", &platform_bus_type); + if (!drv) { + DRM_ERROR("No HQVDP found\n"); + return -ENOMEM; + } + err = driver_for_each_device(drv, NULL, dev, sti_hqvdp_load_init); + if (err) + DRM_ERROR("Failed to initialize HQVDP\n"); + drm_helper_disable_unused_functions(dev); #ifdef CONFIG_DRM_STI_FBDEV sti_drm_device = dev; diff --git a/drivers/gpu/drm/sti/sti_drm_drv.h b/drivers/gpu/drm/sti/sti_drm_drv.h index 5660196..f84897f 100644 --- a/drivers/gpu/drm/sti/sti_drm_drv.h +++ b/drivers/gpu/drm/sti/sti_drm_drv.h @@ -13,6 +13,7 @@
#include "sti_compositor.h" #include "sti_tvout.h" +#include "sti_hqvdp.h"
/* * STI drm private structure @@ -20,12 +21,14 @@ * * @compo: compositor * @tvout: TV OUT + * @hqvdp: HQVDP * @pageflip_evt_list: list of pending page flip requests * @plane_zorder_property: z-order property for CRTC planes */ struct sti_drm_private { struct sti_compositor *compo; struct sti_tvout *tvout; + struct sti_hqvdp *hqvdp; struct list_head pageflip_evt_list; struct drm_property *plane_zorder_property; }; diff --git a/drivers/gpu/drm/sti/sti_hqvdp.c b/drivers/gpu/drm/sti/sti_hqvdp.c new file mode 100644 index 0000000..839d19e --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hqvdp.c @@ -0,0 +1,1107 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Fabien Dessenne fabien.dessenne@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/firmware.h> +#include <linux/reset.h> + +#include <drm/drmP.h> + +#include "sti_hqvdp.h" +#include "sti_hqvdp_lut.h" +#include "sti_layer.h" +#include "sti_compositor.h" +#include "sti_vid.h" +#include "sti_drm_drv.h" +#include "sti_drm_plane.h" + +/* Firmware name */ +#define HQVDP_FMW_NAME "hqvdp-stih407.bin" + +/* Regs address */ +#define HQVDP_DMEM 0x00000000 /* 0x00000000 */ +#define HQVDP_PMEM 0x00040000 /* 0x00040000 */ +#define HQVDP_RD_PLUG 0x000E0000 /* 0x000E0000 */ +#define HQVDP_RD_PLUG_CONTROL (HQVDP_RD_PLUG + 0x1000) /* 0x000E1000 */ +#define HQVDP_RD_PLUG_PAGE_SIZE (HQVDP_RD_PLUG + 0x1004) /* 0x000E1004 */ +#define HQVDP_RD_PLUG_MIN_OPC (HQVDP_RD_PLUG + 0x1008) /* 0x000E1008 */ +#define HQVDP_RD_PLUG_MAX_OPC (HQVDP_RD_PLUG + 0x100C) /* 0x000E100C */ +#define HQVDP_RD_PLUG_MAX_CHK (HQVDP_RD_PLUG + 0x1010) /* 0x000E1010 */ +#define HQVDP_RD_PLUG_MAX_MSG (HQVDP_RD_PLUG + 0x1014) /* 0x000E1014 */ +#define HQVDP_RD_PLUG_MIN_SPACE (HQVDP_RD_PLUG + 0x1018) /* 0x000E1018 */ +#define HQVDP_WR_PLUG 0x000E2000 /* 0x000E2000 */ +#define HQVDP_WR_PLUG_CONTROL (HQVDP_WR_PLUG + 0x1000) /* 0x000E3000 */ +#define HQVDP_WR_PLUG_PAGE_SIZE (HQVDP_WR_PLUG + 0x1004) /* 0x000E3004 */ +#define HQVDP_WR_PLUG_MIN_OPC (HQVDP_WR_PLUG + 0x1008) /* 0x000E3008 */ +#define HQVDP_WR_PLUG_MAX_OPC (HQVDP_WR_PLUG + 0x100C) /* 0x000E300C */ +#define HQVDP_WR_PLUG_MAX_CHK (HQVDP_WR_PLUG + 0x1010) /* 0x000E3010 */ +#define HQVDP_WR_PLUG_MAX_MSG (HQVDP_WR_PLUG + 0x1014) /* 0x000E3014 */ +#define HQVDP_WR_PLUG_MIN_SPACE (HQVDP_WR_PLUG + 0x1018) /* 0x000E3018 */ +#define HQVDP_MBX 0x000E4000 /* 0x000E4000 */ +#define HQVDP_MBX_IRQ_TO_XP70 (HQVDP_MBX + 0x0000) /* 0x000E4000 */ +#define HQVDP_MBX_INFO_HOST (HQVDP_MBX + 0x0004) /* 0x000E4004 */ +#define HQVDP_MBX_IRQ_TO_HOST (HQVDP_MBX + 0x0008) /* 0x000E4008 */ +#define HQVDP_MBX_INFO_XP70 (HQVDP_MBX + 0x000C) /* 0x000E400C */ +#define HQVDP_MBX_SW_RESET_CTRL (HQVDP_MBX + 0x0010) /* 0x000E4010 */ +#define HQVDP_MBX_STARTUP_CTRL1 (HQVDP_MBX + 0x0014) /* 0x000E4014 */ +#define HQVDP_MBX_STARTUP_CTRL2 (HQVDP_MBX + 0x0018) /* 0x000E4018 */ +#define HQVDP_MBX_GP_STATUS (HQVDP_MBX + 0x001C) /* 0x000E401C */ +#define HQVDP_MBX_NEXT_CMD (HQVDP_MBX + 0x0020) /* 0x000E4020 */ +#define HQVDP_MBX_CURRENT_CMD (HQVDP_MBX + 0x0024) /* 0x000E4024 */ +#define HQVDP_MBX_SOFT_VSYNC (HQVDP_MBX + 0x0028) /* 0x000E4028 */ + +/* Plugs config */ +#define PLUG_CONTROL_ENABLE 0x00000001 +#define PLUG_PAGE_SIZE_256 0x00000002 +#define PLUG_MIN_OPC_8 0x00000003 +#define PLUG_MAX_OPC_64 0x00000006 +#define PLUG_MAX_CHK_2X 0x00000001 +#define PLUG_MAX_MSG_1X 0x00000000 +#define PLUG_MIN_SPACE_1 0x00000000 + +/* SW reset CTRL */ +#define SW_RESET_CTRL_FULL (1 << 0) +#define SW_RESET_CTRL_CORE (1 << 1) + +/* Startup ctrl 1 */ +#define STARTUP_CTRL1_RST_DONE (1 << 0) +#define STARTUP_CTRL1_AUTH_IDLE (1 << 2) + +/* Startup ctrl 2 */ +#define STARTUP_CTRL2_FETCH_EN (1 << 1) + +/* Info xP70 */ +#define INFO_XP70_FW_READY (1 << 15) +#define INFO_XP70_FW_PROCESSING (1 << 14) +#define INFO_XP70_FW_INITQUEUES (1 << 13) + +/* SOFT_VSYNC */ +#define SOFT_VSYNC_HW 0x00000000 +#define SOFT_VSYNC_SW_CMD 0x00000001 +#define SOFT_VSYNC_SW_CTRL_IRQ 0x00000003 + +/* Reset & boot poll config */ +#define POLL_MAX_ATTEMPT 50 +#define POLL_DELAY_MS 20 + +#define SCALE_FACTOR 8192 +#define SCALE_MAX_FOR_LEG_LUT_F 4096 +#define SCALE_MAX_FOR_LEG_LUT_E 4915 +#define SCALE_MAX_FOR_LEG_LUT_D 6654 +#define SCALE_MAX_FOR_LEG_LUT_C 8192 + +enum sti_hvsrc_orient { + HVSRC_HORI, + HVSRC_VERT +}; + +/* Command structures */ +struct sti_hqvdp_top { + u32 config; + u32 mem_format; + u32 current_luma; + u32 current_enh_luma; + u32 current_right_luma; + u32 current_enh_right_luma; + u32 current_chroma; + u32 current_enh_chroma; + u32 current_right_chroma; + u32 current_enh_right_chroma; + u32 output_luma; + u32 output_chroma; + u32 luma_src_pitch; + u32 luma_enh_src_pitch; + u32 luma_right_src_pitch; + u32 luma_enh_right_src_pitch; + u32 chroma_src_pitch; + u32 chroma_enh_src_pitch; + u32 chroma_right_src_pitch; + u32 chroma_enh_right_src_pitch; + u32 luma_processed_pitch; + u32 chroma_processed_pitch; + u32 input_frame_size; + u32 input_viewport_ori; + u32 input_viewport_ori_right; + u32 input_viewport_size; + u32 left_view_border_width; + u32 right_view_border_width; + u32 left_view_3d_offset_width; + u32 right_view_3d_offset_width; + u32 side_stripe_color; + u32 crc_reset_ctrl; +}; +/* Default Config: progressive, No IT, No pass thru, 3 fields */ +#define TOP_CONFIG_DFLT 0x00000001 +/* Default MemFormat: in=420_raster_dual out=444_raster;opaque Mem2Tv mode */ +#define TOP_MEM_FORMAT_DFLT 0x00018060 +/* Min/Max size */ +#define MAX_WIDTH 0x1FFF +#define MAX_HEIGHT 0x0FFF +#define MIN_WIDTH 0x0030 +#define MIN_HEIGHT 0x0010 + +struct sti_hqvdp_vc1re { + u32 ctrl_prv_csdi; + u32 ctrl_cur_csdi; + u32 ctrl_nxt_csdi; + u32 ctrl_cur_fmd; + u32 ctrl_nxt_fmd; +}; + +struct sti_hqvdp_fmd { + u32 config; + u32 viewport_ori; + u32 viewport_size; + u32 next_next_luma; + u32 next_next_right_luma; + u32 next_next_next_luma; + u32 next_next_next_right_luma; + u32 threshold_scd; + u32 threshold_rfd; + u32 threshold_move; + u32 threshold_cfd; +}; + +struct sti_hqvdp_csdi { + u32 config; + u32 config2; + u32 dcdi_config; + u32 prev_luma; + u32 prev_enh_luma; + u32 prev_right_luma; + u32 prev_enh_right_luma; + u32 next_luma; + u32 next_enh_luma; + u32 next_right_luma; + u32 next_enh_right_luma; + u32 prev_chroma; + u32 prev_enh_chroma; + u32 prev_right_chroma; + u32 prev_enh_right_chroma; + u32 next_chroma; + u32 next_enh_chroma; + u32 next_right_chroma; + u32 next_enh_right_chroma; + u32 prev_motion; + u32 prev_right_motion; + u32 cur_motion; + u32 cur_right_motion; + u32 next_motion; + u32 next_right_motion; +}; + +struct sti_hqvdp_hvsrc { + u32 hor_panoramic_ctrl; + u32 output_picture_size; + u32 init_horizontal; + u32 init_vertical; + u32 param_ctrl; + u32 yh_coef[NB_COEF]; + u32 ch_coef[NB_COEF]; + u32 yv_coef[NB_COEF]; + u32 cv_coef[NB_COEF]; + u32 hori_shift; + u32 vert_shift; +}; +/* Default ParamCtrl: all controls enabled */ +#define HVSRC_PARAM_CTRL_DFLT 0xFFFFFFFF + +struct sti_hqvdp_iqi { + u32 config; + u32 demo_wind_size; + u32 pk_config; + u32 coeff0_coeff1; + u32 coeff2_coeff3; + u32 coeff4; + u32 pk_lut; + u32 pk_gain; + u32 pk_coring_level; + u32 cti_config; + u32 le_config; + u32 le_lut[64]; + u32 con_bri; + u32 sat_gain; + u32 pxf_conf; + u32 default_color; +}; +/* Default Config : IQI bypassed */ +#define IQI_CONFIG_DFLT 0x00000001 +/* Default Contrast & Brightness gain = 256 */ +#define IQI_CON_BRI_DFLT 0x00000100 +/* Default Saturation gain = 256 */ +#define IQI_SAT_GAIN_DFLT 0x00000100 +/* Default PxfConf : P2I bypassed */ +#define IQI_PXF_CONF_DFLT 0x00000001 + +struct sti_hqvdp_top_status { + u32 processing_time; + u32 input_y_crc; + u32 input_uv_crc; +}; + +struct sti_hqvdp_fmd_status { + u32 fmd_repeat_move_status; + u32 fmd_scene_count_status; + u32 cfd_sum; + u32 field_sum; + u32 next_y_fmd_crc; + u32 next_next_y_fmd_crc; + u32 next_next_next_y_fmd_crc; +}; + +struct sti_hqvdp_csdi_status { + u32 prev_y_csdi_crc; + u32 cur_y_csdi_crc; + u32 next_y_csdi_crc; + u32 prev_uv_csdi_crc; + u32 cur_uv_csdi_crc; + u32 next_uv_csdi_crc; + u32 y_csdi_crc; + u32 uv_csdi_crc; + u32 uv_cup_crc; + u32 mot_csdi_crc; + u32 mot_cur_csdi_crc; + u32 mot_prev_csdi_crc; +}; + +struct sti_hqvdp_hvsrc_status { + u32 y_hvsrc_crc; + u32 u_hvsrc_crc; + u32 v_hvsrc_crc; +}; + +struct sti_hqvdp_iqi_status { + u32 pxf_it_status; + u32 y_iqi_crc; + u32 u_iqi_crc; + u32 v_iqi_crc; +}; + +/* Main commands. We use 2 commands one being processed by the firmware, one + * ready to be fetched upon next Vsync*/ +#define NB_VDP_CMD 2 +struct sti_hqvdp_cmd { + struct sti_hqvdp_top top; + struct sti_hqvdp_vc1re vc1re; + struct sti_hqvdp_fmd fmd; + struct sti_hqvdp_csdi csdi; + struct sti_hqvdp_hvsrc hvsrc; + struct sti_hqvdp_iqi iqi; + struct sti_hqvdp_top_status top_status; + struct sti_hqvdp_fmd_status fmd_status; + struct sti_hqvdp_csdi_status csdi_status; + struct sti_hqvdp_hvsrc_status hvsrc_status; + struct sti_hqvdp_iqi_status iqi_status; +}; + +/* + * HQVDP supported formats: + * Description Use Case fourCC + * 4:2:0 raster, dual buffer Video decoder output NV12 + * 4:2:2 raster, dual buffer 8bpp DVP/HDMI NV16 + * 4:2:2 raster, dual buffer 10bpp DVP/HDMI <None> + * 4:2:2 raster, single buffer DVP/HDMI UYVY + * 4:4:4 raster, single buffer + alpha GPU, BDISP AYUV # + * # The byte order seems to be UYVA, so not exactly AYUV format + * 4:4:4 raster, single buffer no alpha 8bpp DVP/HDMI IYU2 + * 4:4:4 raster, single buffer no alpha 10bpp DVP/HDMI <None> + * 4:4:4 raster, single buffer no alpha DVP/HDMI IYU2 + * 4:2:0 Macroblock omega2, dual buffer Video decoder output <None> + */ +static const uint32_t vdp_supported_formats[] = { + /* Consider only the Delta "decoder output" use case */ + DRM_FORMAT_NV12, +}; + +/** + * sti_vdp_get_formats + * + * Return the list of supported pixel formats. + */ +static const uint32_t *sti_vdp_get_formats(void) +{ + return vdp_supported_formats; +} + +/** + * sti_vdp_get_nb_formats + * + * Return the number of supported pixel formats. + */ +static int sti_vdp_get_nb_formats(void) +{ + return ARRAY_SIZE(vdp_supported_formats); +} + +/** + * sti_vdp_get_free_cmd + * @layer: vdp layer + * + * Look for a vdp_cmd that is not being used (or about to be used) by the FW. + * + * RETURNS: + * Pointer to a free vdp_cmd, NULL if none is available + */ +static struct sti_hqvdp_cmd *sti_vdp_get_free_cmd(struct sti_layer *layer) +{ + int curr_cmd, next_cmd; + void *virt_curr, *virt_next; + struct sti_vdp *vdp = layer->vdp; + struct sti_hqvdp_cmd *cmd = (struct sti_hqvdp_cmd *)vdp->private; + int i; + + curr_cmd = readl(vdp->regs + HQVDP_MBX_CURRENT_CMD); + virt_curr = dma_to_virt(vdp->dev, (dma_addr_t) curr_cmd); + + next_cmd = readl(vdp->regs + HQVDP_MBX_NEXT_CMD); + virt_next = dma_to_virt(vdp->dev, (dma_addr_t) next_cmd); + + for (i = 0; i < NB_VDP_CMD; i++) + if ((&cmd[i] != virt_curr) && (&cmd[i] != virt_next)) + return &cmd[i]; + + return NULL; +} + +/** + * sti_vdp_update_hvsrc + * @orient: horizontal or vertical + * @scale: scaling/zoom factor + * @hvsrc: the structure containing the LUT coef + * + * Update the Y and C Lut coef, as well as the shift param + * + * RETURNS: + * None. + */ +static void sti_vdp_update_hvsrc(enum sti_hvsrc_orient orient, int scale, + struct sti_hqvdp_hvsrc *hvsrc) +{ + const int *coef_c, *coef_y; + int shift_c, shift_y; + + /* Get the appropriate coef tables */ + if (scale < SCALE_MAX_FOR_LEG_LUT_F) { + coef_y = coef_lut_f_y_legacy; + coef_c = coef_lut_f_c_legacy; + shift_y = SHIFT_LUT_F_Y_LEGACY; + shift_c = SHIFT_LUT_F_C_LEGACY; + } else if (scale < SCALE_MAX_FOR_LEG_LUT_E) { + coef_y = coef_lut_e_y_legacy; + coef_c = coef_lut_e_c_legacy; + shift_y = SHIFT_LUT_E_Y_LEGACY; + shift_c = SHIFT_LUT_E_C_LEGACY; + } else if (scale < SCALE_MAX_FOR_LEG_LUT_D) { + coef_y = coef_lut_d_y_legacy; + coef_c = coef_lut_d_c_legacy; + shift_y = SHIFT_LUT_D_Y_LEGACY; + shift_c = SHIFT_LUT_D_C_LEGACY; + } else if (scale < SCALE_MAX_FOR_LEG_LUT_C) { + coef_y = coef_lut_c_y_legacy; + coef_c = coef_lut_c_c_legacy; + shift_y = SHIFT_LUT_C_Y_LEGACY; + shift_c = SHIFT_LUT_C_C_LEGACY; + } else if (scale == SCALE_MAX_FOR_LEG_LUT_C) { + coef_y = coef_c = coef_lut_b; + shift_y = shift_c = SHIFT_LUT_B; + } else { + coef_y = coef_c = coef_lut_a_legacy; + shift_y = shift_c = SHIFT_LUT_A_LEGACY; + } + + if (orient == HVSRC_HORI) { + hvsrc->hori_shift = (shift_c << 16) | shift_y; + memcpy(hvsrc->yh_coef, coef_y, sizeof(hvsrc->yh_coef)); + memcpy(hvsrc->ch_coef, coef_c, sizeof(hvsrc->ch_coef)); + } else { + hvsrc->vert_shift = (shift_c << 16) | shift_y; + memcpy(hvsrc->yv_coef, coef_y, sizeof(hvsrc->yv_coef)); + memcpy(hvsrc->cv_coef, coef_c, sizeof(hvsrc->cv_coef)); + } +} + +/** + * sti_vdp_prepare_layer + * @lay: video layer + * + * Prepares a command for the firmware + * + * RETURNS: + * 0 on success. + */ +static int sti_vdp_prepare_layer(void *lay, bool first_prepare) +{ + struct sti_layer *layer = (struct sti_layer *)lay; + struct sti_vdp *vdp = layer->vdp; + struct sti_hqvdp_cmd *cmd; + int scale_h, scale_v; + + dev_dbg(vdp->dev, "%s %s\n", __func__, sti_layer_to_str(layer)); + + cmd = sti_vdp_get_free_cmd(layer); + if (!cmd) { + DRM_ERROR("No available vdp_cmd now\n"); + return -EBUSY; + } + + /* Static parameters */ + cmd->top.config = TOP_CONFIG_DFLT; + cmd->top.mem_format = TOP_MEM_FORMAT_DFLT; + cmd->hvsrc.param_ctrl = HVSRC_PARAM_CTRL_DFLT; + /* VC1RE, FMD, CSDI bypassed : keep everything set to 0 + * IQI/P2I bypassed */ + cmd->iqi.config = IQI_CONFIG_DFLT; + cmd->iqi.con_bri = IQI_CON_BRI_DFLT; + cmd->iqi.sat_gain = IQI_SAT_GAIN_DFLT; + cmd->iqi.pxf_conf = IQI_PXF_CONF_DFLT; + + /* Buffer planes address */ + cmd->top.current_luma = (u32) layer->paddr + layer->offsets[0]; + cmd->top.current_chroma = (u32) layer->paddr + layer->offsets[1]; + /* Pitches */ + cmd->top.luma_processed_pitch = cmd->top.luma_src_pitch = + layer->pitches[0]; + cmd->top.chroma_processed_pitch = cmd->top.chroma_src_pitch = + layer->pitches[1]; + + /* Input / output size */ + if ((layer->src_w > MAX_WIDTH) || (layer->src_w < MIN_WIDTH) || + (layer->src_h > MAX_HEIGHT) || (layer->src_h < MIN_HEIGHT) || + (layer->dst_w > MAX_WIDTH) || (layer->dst_w < MIN_WIDTH) || + (layer->dst_h > MAX_HEIGHT) || (layer->dst_h < MIN_HEIGHT) || + (layer->dst_w & 1) || (layer->dst_h & 1)) { + DRM_ERROR("Invalid in/out size %dx%d -> %dx%d\n", + layer->src_w, layer->src_h, + layer->dst_w, layer->dst_h); + return -EINVAL; + } + cmd->top.input_viewport_size = cmd->top.input_frame_size = + layer->src_h << 16 | layer->src_w; + cmd->hvsrc.output_picture_size = layer->dst_h << 16 | layer->dst_w; + + /* Update hvsrc lut coef */ + scale_h = SCALE_FACTOR * layer->dst_w / layer->src_w; + sti_vdp_update_hvsrc(HVSRC_HORI, scale_h, &cmd->hvsrc); + + scale_v = SCALE_FACTOR * layer->dst_h / layer->src_h; + sti_vdp_update_hvsrc(HVSRC_VERT, scale_v, &cmd->hvsrc); + + /* Prevent VTG shutdown */ + if (first_prepare && clk_prepare_enable(vdp->clk_pix_main)) { + DRM_ERROR("Failed to prepare/enable pix main clk\n"); + return -ENXIO; + } + + return 0; +} + +/** + * sti_vdp_commit_layer + * @lay: video layer + * + * Send the address of the prepared command to the firmware + * + * RETURNS: + * 0 on success. + */ +static int sti_vdp_commit_layer(void *lay) +{ + struct sti_layer *layer = (struct sti_layer *)lay; + struct sti_vdp *vdp = layer->vdp; + struct sti_hqvdp_cmd *cmd; + u32 dma_cmd; + + dev_dbg(vdp->dev, "%s %s\n", __func__, sti_layer_to_str(layer)); + + cmd = sti_vdp_get_free_cmd(layer); + if (!cmd) { + DRM_ERROR("No available vdp_cmd now\n"); + return -EBUSY; + } + + /* Post to mailbox */ + dma_cmd = virt_to_dma(vdp->dev, cmd); + writel(dma_cmd, vdp->regs + HQVDP_MBX_NEXT_CMD); + + dev_dbg(vdp->dev, "%s Posted command:0x%x\n", __func__, dma_cmd); + + return 0; +} + +/** + * sti_vdp_disable_layer + * @lay: video layer + * + * Set next_cmd to NULL + * + * RETURNS: + * 0 on success. + */ +static int sti_vdp_disable_layer(void *lay) +{ + struct sti_layer *layer = (struct sti_layer *)lay; + struct sti_vdp *vdp = layer->vdp; + int i; + + DRM_DEBUG_DRIVER("%s\n", sti_layer_to_str(layer)); + + writel(0, vdp->regs + HQVDP_MBX_NEXT_CMD); + + for (i = 0; i < POLL_MAX_ATTEMPT; i++) { + if (readl(vdp->regs + HQVDP_MBX_INFO_XP70) + & INFO_XP70_FW_READY) + break; + msleep(POLL_DELAY_MS); + } + + /* VTG can stop now */ + clk_disable_unprepare(vdp->clk_pix_main); + + if (i == POLL_MAX_ATTEMPT) { + DRM_ERROR("XP70 could not revert to idle\n"); + return -ENXIO; + } + + return 0; +} + +/** + * sti_hqvdp_reg_init_vdp + * @hqvdp: hqvdp descriptor + * @compo: compositor descriptor + * @drm_dev: DRM device + * + * Add the vdp descriptor to the main Video layer owned by the compositor. + * Prepare vdp_cmd with its default config. + * + * RETURNS: + * 0 on success. + */ +static int sti_hqvdp_reg_init_vdp(struct sti_compositor *compo, + struct sti_hqvdp *hqvdp, + struct drm_device *drm_dev) +{ + struct sti_layer *vid_layer; + struct sti_vdp *vdp; + int size; + void *vdp_cmd; + dma_addr_t dma; + + if (!compo) { + DRM_ERROR("Cannot find compositor\n"); + return -ENOMEM; + } + + vid_layer = sti_layer_find_layer(compo->layer, STI_VID_0); + if (!vid_layer) { + DRM_ERROR("Cannot find Main video layer\n"); + return -ENXIO; + } + + /* Prepare the vdp struct */ + vdp = devm_kzalloc(hqvdp->dev, sizeof(*vdp), GFP_KERNEL); + if (!vdp) { + DRM_ERROR("Failed to allocate memory for VDP\n"); + return -ENOMEM; + } + + vdp->dev = hqvdp->dev; + vdp->regs = hqvdp->regs; + vdp->clk_pix_main = hqvdp->clk_pix_main; + vdp->get_formats = sti_vdp_get_formats; + vdp->get_nb_formats = sti_vdp_get_nb_formats; + vdp->prepare = sti_vdp_prepare_layer; + vdp->commit = sti_vdp_commit_layer; + vdp->disable = sti_vdp_disable_layer; + + /* Allocate memory for the VDP commands */ + size = NB_VDP_CMD * sizeof(struct sti_hqvdp_cmd); + vdp_cmd = dma_alloc_writecombine(vdp->dev, size, + &dma, GFP_KERNEL | GFP_DMA); + if (!vdp_cmd) { + DRM_ERROR("Failed to allocate memory for VDP cmd\n"); + return -ENOMEM; + } + memset(vdp_cmd, 0, size); + vdp->private = vdp_cmd; + + vid_layer->vdp = vdp; + + /* Register the DRM vid/vdp layer */ + sti_drm_plane_init(drm_dev, vid_layer, 1); + DRM_DEBUG_DRIVER("Added DRM plane for Main VID-VDP\n"); + + return 0; +} + +/** + * sti_hqvdp_init_plugs + * @hqvdp: hqvdp descriptor + * + * Initialize the Read and Write plugs + * + * RETURNS: + * none. + */ +static void sti_hqvdp_init_plugs(struct sti_hqvdp *hqvdp) +{ + /* Configure Plugs (same for RD & WR) */ + writel(PLUG_PAGE_SIZE_256, hqvdp->regs + HQVDP_RD_PLUG_PAGE_SIZE); + writel(PLUG_MIN_OPC_8, hqvdp->regs + HQVDP_RD_PLUG_MIN_OPC); + writel(PLUG_MAX_OPC_64, hqvdp->regs + HQVDP_RD_PLUG_MAX_OPC); + writel(PLUG_MAX_CHK_2X, hqvdp->regs + HQVDP_RD_PLUG_MAX_CHK); + writel(PLUG_MAX_MSG_1X, hqvdp->regs + HQVDP_RD_PLUG_MAX_MSG); + writel(PLUG_MIN_SPACE_1, hqvdp->regs + HQVDP_RD_PLUG_MIN_SPACE); + writel(PLUG_CONTROL_ENABLE, hqvdp->regs + HQVDP_RD_PLUG_CONTROL); + + writel(PLUG_PAGE_SIZE_256, hqvdp->regs + HQVDP_WR_PLUG_PAGE_SIZE); + writel(PLUG_MIN_OPC_8, hqvdp->regs + HQVDP_WR_PLUG_MIN_OPC); + writel(PLUG_MAX_OPC_64, hqvdp->regs + HQVDP_WR_PLUG_MAX_OPC); + writel(PLUG_MAX_CHK_2X, hqvdp->regs + HQVDP_WR_PLUG_MAX_CHK); + writel(PLUG_MAX_MSG_1X, hqvdp->regs + HQVDP_WR_PLUG_MAX_MSG); + writel(PLUG_MIN_SPACE_1, hqvdp->regs + HQVDP_WR_PLUG_MIN_SPACE); + writel(PLUG_CONTROL_ENABLE, hqvdp->regs + HQVDP_WR_PLUG_CONTROL); +} + +/** + * sti_hqvdp_start_xp70 + * @firmware: firmware found + * @ctxt: drm device + * + * Run the xP70 initialization sequence + */ +static void sti_hqvdp_start_xp70(const struct firmware *firmware, void *ctxt) +{ + struct drm_device *drm_dev = ctxt; + struct sti_drm_private *dev_priv = drm_dev->dev_private; + struct sti_hqvdp *hqvdp = dev_priv->hqvdp; + u32 *fw_rd_plug, *fw_wr_plug, *fw_pmem, *fw_dmem; + u8 *data; + int i; + struct fw_header { + int rd_size; + int wr_size; + int pmem_size; + int dmem_size; + } *header; + + DRM_DEBUG_DRIVER("\n"); + /* Check firmware parts */ + if (!firmware) { + DRM_ERROR("Firmware not available\n"); + return; + } + + header = (struct fw_header *)firmware->data; + if (firmware->size < sizeof(*header)) { + DRM_ERROR("Invalid firmware size (%d)\n", firmware->size); + goto out; + } + if ((sizeof(*header) + header->rd_size + header->wr_size + + header->pmem_size + header->dmem_size) != firmware->size) { + DRM_ERROR("Invalid fmw structure (%d+%d+%d+%d+%d != %d)\n", + sizeof(*header), header->rd_size, header->wr_size, + header->pmem_size, header->dmem_size, firmware->size); + goto out; + } + + data = (u8 *) firmware->data; + data += sizeof(*header); + fw_rd_plug = (void *)data; + data += header->rd_size; + fw_wr_plug = (void *)data; + data += header->wr_size; + fw_pmem = (void *)data; + data += header->pmem_size; + fw_dmem = (void *)data; + + /* Enable clock */ + if (clk_prepare_enable(hqvdp->clk)) + DRM_ERROR("Failed to prepare/enable HQVDP clk\n"); + + /* Reset */ + writel(SW_RESET_CTRL_FULL, hqvdp->regs + HQVDP_MBX_SW_RESET_CTRL); + + for (i = 0; i < POLL_MAX_ATTEMPT; i++) { + if (readl(hqvdp->regs + HQVDP_MBX_STARTUP_CTRL1) + & STARTUP_CTRL1_RST_DONE) + break; + msleep(POLL_DELAY_MS); + } + if (i == POLL_MAX_ATTEMPT) { + DRM_ERROR("Could not reset\n"); + goto out; + } + + /* Init Read & Write plugs */ + for (i = 0; i < header->rd_size / 4; i++) + writel(fw_rd_plug[i], hqvdp->regs + HQVDP_RD_PLUG + i * 4); + for (i = 0; i < header->wr_size / 4; i++) + writel(fw_wr_plug[i], hqvdp->regs + HQVDP_WR_PLUG + i * 4); + + sti_hqvdp_init_plugs(hqvdp); + + /* Authorize Idle Mode */ + writel(STARTUP_CTRL1_AUTH_IDLE, hqvdp->regs + HQVDP_MBX_STARTUP_CTRL1); + + /* Prevent VTG interruption during the boot */ + writel(SOFT_VSYNC_SW_CTRL_IRQ, hqvdp->regs + HQVDP_MBX_SOFT_VSYNC); + writel(0, hqvdp->regs + HQVDP_MBX_NEXT_CMD); + + /* Download PMEM & DMEM */ + for (i = 0; i < header->pmem_size / 4; i++) + writel(fw_pmem[i], hqvdp->regs + HQVDP_PMEM + i * 4); + for (i = 0; i < header->dmem_size / 4; i++) + writel(fw_dmem[i], hqvdp->regs + HQVDP_DMEM + i * 4); + + /* Enable fetch */ + writel(STARTUP_CTRL2_FETCH_EN, hqvdp->regs + HQVDP_MBX_STARTUP_CTRL2); + + /* Wait end of boot */ + for (i = 0; i < POLL_MAX_ATTEMPT; i++) { + if (readl(hqvdp->regs + HQVDP_MBX_INFO_XP70) + & INFO_XP70_FW_READY) + break; + msleep(POLL_DELAY_MS); + } + if (i == POLL_MAX_ATTEMPT) { + DRM_ERROR("Could not boot\n"); + goto out; + } + + /* Launch Vsync */ + writel(SOFT_VSYNC_HW, hqvdp->regs + HQVDP_MBX_SOFT_VSYNC); + + DRM_INFO("HQVDP XP70 started\n"); + + /* Prepare and register the VDP to the compositor Main video layer */ + if (sti_hqvdp_reg_init_vdp(dev_priv->compo, hqvdp, drm_dev)) { + DRM_ERROR("Could not register to compo\n"); + goto out; + } + +out: + release_firmware(firmware); + return; +} + +/** + * sti_hqvdp_load_init + * @pdev: platform device + * + * Request to load firmware from filesystem + * + * RETURNS: + * 0 on success. + */ +int sti_hqvdp_load_init(struct device *dev, void *data) +{ + int err; + struct sti_hqvdp *hqvdp = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + struct sti_drm_private *dev_priv = drm_dev->dev_private; + + DRM_DEBUG_DRIVER("\n"); + + dev_priv->hqvdp = hqvdp; + + /* Request for firmware */ + err = request_firmware_nowait(THIS_MODULE, true, HQVDP_FMW_NAME, + hqvdp->dev, GFP_KERNEL, drm_dev, + sti_hqvdp_start_xp70); + if (err) { + DRM_ERROR("Could not request %s\n", HQVDP_FMW_NAME); + return err; + } + + return err; +} + +/* + * DEBUGFS + */ +static const char *sti_hqvdp_dbg_get_lut(u32 *coef) +{ + if (!memcmp(coef, coef_lut_a_legacy, 16)) + return "LUT A"; + if (!memcmp(coef, coef_lut_b, 16)) + return "LUT B"; + if (!memcmp(coef, coef_lut_c_y_legacy, 16)) + return "LUT C Y"; + if (!memcmp(coef, coef_lut_c_c_legacy, 16)) + return "LUT C C"; + if (!memcmp(coef, coef_lut_d_y_legacy, 16)) + return "LUT D Y"; + if (!memcmp(coef, coef_lut_d_c_legacy, 16)) + return "LUT D C"; + if (!memcmp(coef, coef_lut_e_y_legacy, 16)) + return "LUT E Y"; + if (!memcmp(coef, coef_lut_e_c_legacy, 16)) + return "LUT E C"; + if (!memcmp(coef, coef_lut_f_y_legacy, 16)) + return "LUT F Y"; + if (!memcmp(coef, coef_lut_f_c_legacy, 16)) + return "LUT F C"; + return "<Unkown>"; +} + +static void sti_hqvdp_dbg_dump_cmd(struct device *dev, struct seq_file *m, + struct sti_hqvdp_cmd *c) +{ + int src_w, src_h, dst_w, dst_h; + + seq_puts(m, "\n\tTOP:"); + seq_printf(m, "\n\tConfig 0x%08X", c->top.config); + seq_printf(m, "\n\tMemFormat 0x%08X", c->top.mem_format); + seq_printf(m, "\n\tCurrentY 0x%08X", c->top.current_luma); + seq_printf(m, "\t(0x%p)", + dma_to_virt(dev, (dma_addr_t) c->top.current_luma)); + seq_printf(m, "\n\tCurrentC 0x%08X", c->top.current_chroma); + seq_printf(m, "\t(0x%p)", + dma_to_virt(dev, (dma_addr_t) c->top.current_chroma)); + seq_printf(m, "\n\tYSrcPitch 0x%08X", c->top.luma_src_pitch); + seq_printf(m, "\n\tCSrcPitch 0x%08X", c->top.chroma_src_pitch); + seq_printf(m, "\n\tInputFrameSize 0x%08X", c->top.input_frame_size); + seq_printf(m, "\t%dx%d", + c->top.input_frame_size & 0x0000FFFF, + c->top.input_frame_size >> 16); + seq_printf(m, "\n\tInputViewportSize 0x%08X", + c->top.input_viewport_size); + src_w = c->top.input_viewport_size & 0x0000FFFF; + src_h = c->top.input_viewport_size >> 16; + seq_printf(m, "\t%dx%d", src_w, src_h); + + seq_puts(m, "\n\tHVSRC:"); + seq_printf(m, "\n\tOutputPictureSize 0x%08X", + c->hvsrc.output_picture_size); + dst_w = c->hvsrc.output_picture_size & 0x0000FFFF; + dst_h = c->hvsrc.output_picture_size >> 16; + seq_printf(m, "\t%dx%d", dst_w, dst_h); + seq_printf(m, "\n\tParamCtrl 0x%08X", c->hvsrc.param_ctrl); + + seq_printf(m, "\n\tyh_coef %s", + sti_hqvdp_dbg_get_lut(c->hvsrc.yh_coef)); + seq_printf(m, "\n\tch_coef %s", + sti_hqvdp_dbg_get_lut(c->hvsrc.ch_coef)); + seq_printf(m, "\n\tyv_coef %s", + sti_hqvdp_dbg_get_lut(c->hvsrc.yv_coef)); + seq_printf(m, "\n\tcv_coef %s", + sti_hqvdp_dbg_get_lut(c->hvsrc.cv_coef)); + + seq_puts(m, "\n\tScaleH "); + if (dst_w > src_w) + seq_printf(m, "%d/1", dst_w / src_w); + else + seq_printf(m, "1/%d", src_w / dst_w); + + seq_puts(m, "\n\tScaleV "); + if (dst_h > src_h) + seq_printf(m, "%d/1", dst_h / src_h); + else + seq_printf(m, "1/%d", src_h / dst_h); +} + +#define DBG_DUMP(reg) \ + seq_printf(m, "\n " #reg "\t 0x%08X", readl(hqvdp->regs + reg)) + +int sti_hqvdp_dbg_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_hqvdp *hqvdp = dev_priv->hqvdp; + enum sti_layer_desc vid_layer_id = STI_VID_0; + struct sti_hqvdp_cmd *cmds; + struct sti_layer *layer; + struct sti_vdp *vdp; + int ret, cmd, infoxp70; + void *virt; + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret) + return ret; + + if (hqvdp == NULL) { + seq_puts(m, "\nNo HQVDP available"); + goto out; + } + + /* Display vid0 compositor debug info */ + sti_vid_dbg_show(m, &vid_layer_id); + + seq_printf(m, "\nHQVDP: (virt base addr = 0x%p)", hqvdp->regs); + + layer = sti_layer_find_layer(dev_priv->compo->layer, STI_VID_0); + if (!layer) { + seq_puts(m, "\nCannot find Main video layer"); + goto out; + } + + vdp = layer->vdp; + if (!vdp) { + seq_puts(m, "\nCannot find vdp"); + goto out; + } + cmds = (struct sti_hqvdp_cmd *)vdp->private; + + /* Last command */ + cmd = readl(vdp->regs + HQVDP_MBX_CURRENT_CMD); + virt = dma_to_virt(vdp->dev, (dma_addr_t) cmd); + seq_printf(m, "\n\nLast command address @ 0x%x (0x%p)", cmd, virt); + + if ((virt == &cmds[0]) || (virt == &cmds[1])) + sti_hqvdp_dbg_dump_cmd(vdp->dev, m, + (struct sti_hqvdp_cmd *)virt); + else + seq_puts(m, "\nUnknown command"); + + /* Next command */ + cmd = readl(vdp->regs + HQVDP_MBX_NEXT_CMD); + virt = dma_to_virt(vdp->dev, (dma_addr_t) cmd); + seq_printf(m, "\n\nNext command address @ 0x%x (0x%p)", cmd, virt); + + if ((virt == &cmds[0]) || (virt == &cmds[1])) + sti_hqvdp_dbg_dump_cmd(vdp->dev, m, + (struct sti_hqvdp_cmd *)virt); + else + seq_puts(m, "\nUnknown command"); + + /* MBX regs */ + DBG_DUMP(HQVDP_MBX_IRQ_TO_XP70); + DBG_DUMP(HQVDP_MBX_INFO_HOST); + DBG_DUMP(HQVDP_MBX_IRQ_TO_HOST); + + DBG_DUMP(HQVDP_MBX_INFO_XP70); + infoxp70 = readl(hqvdp->regs + HQVDP_MBX_INFO_XP70); + seq_puts(m, "\tFirmware state: "); + if (infoxp70 & INFO_XP70_FW_READY) + seq_puts(m, "idle and ready"); + else if (infoxp70 & INFO_XP70_FW_PROCESSING) + seq_puts(m, "processing a picture"); + else if (infoxp70 & INFO_XP70_FW_INITQUEUES) + seq_puts(m, "programming queues"); + else + seq_puts(m, "NOT READY"); + + DBG_DUMP(HQVDP_MBX_SW_RESET_CTRL); + DBG_DUMP(HQVDP_MBX_STARTUP_CTRL1); + if (readl(hqvdp->regs + HQVDP_MBX_STARTUP_CTRL1) + & STARTUP_CTRL1_RST_DONE) + seq_puts(m, "\tReset is done"); + else + seq_puts(m, "\tReset is NOT done"); + DBG_DUMP(HQVDP_MBX_STARTUP_CTRL2); + if (readl(hqvdp->regs + HQVDP_MBX_STARTUP_CTRL2) + & STARTUP_CTRL2_FETCH_EN) + seq_puts(m, "\tFetch is enabled"); + else + seq_puts(m, "\tFetch is NOT enabled"); + DBG_DUMP(HQVDP_MBX_GP_STATUS); + DBG_DUMP(HQVDP_MBX_NEXT_CMD); + DBG_DUMP(HQVDP_MBX_CURRENT_CMD); + DBG_DUMP(HQVDP_MBX_SOFT_VSYNC); + if (!(readl(hqvdp->regs + HQVDP_MBX_SOFT_VSYNC) & 3)) + seq_puts(m, "\tHW Vsync"); + else + seq_puts(m, "\tSW Vsync ?!?!"); +out: + seq_puts(m, "\n"); + + mutex_unlock(&dev->struct_mutex); + return 0; +} + +static int sti_hqvdp_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sti_hqvdp *hqvdp; + struct resource *res; + + DRM_DEBUG_DRIVER("\n"); + + hqvdp = devm_kzalloc(dev, sizeof(*hqvdp), GFP_KERNEL); + if (!hqvdp) { + DRM_ERROR("Failed to allocate HQVDP context\n"); + return -ENOMEM; + } + hqvdp->dev = dev; + + /* Get Memory ressources */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + DRM_ERROR("Get memory resource failed\n"); + return -ENXIO; + } + hqvdp->regs = devm_ioremap(dev, res->start, resource_size(res)); + if (hqvdp->regs == NULL) { + DRM_ERROR("Register mapping failed\n"); + return -ENXIO; + } + + /* Get clock resources */ + hqvdp->clk = devm_clk_get(dev, "hqvdp"); + hqvdp->clk_pix_main = devm_clk_get(dev, "pix_main"); + if (IS_ERR(hqvdp->clk) || IS_ERR(hqvdp->clk)) { + DRM_ERROR("Cannot get clocks\n"); + return -ENXIO; + } + + /* Get reset resources */ + hqvdp->reset = devm_reset_control_get(dev, "hqvdp"); + if (!IS_ERR(hqvdp->reset)) + reset_control_deassert(hqvdp->reset); + + platform_set_drvdata(pdev, hqvdp); + + DRM_INFO("%s Done, regs=0x%p\n", __func__, hqvdp->regs); + + return 0; +} +static void sti_hqvdp_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops sti_hqvdp_ops = { + .bind = sti_hqvdp_bind, + .unbind = sti_hqvdp_unbind, +}; + +static int sti_hqvdp_probe(struct platform_device *pdev) +{ + DRM_INFO("%s\n", __func__); + return component_add(&pdev->dev, &sti_hqvdp_ops); +} + +static int sti_hqvdp_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sti_hqvdp_ops); + return 0; +} + +static struct of_device_id hqvdp_match_types[] = { + { + .compatible = "st,stih407-hqvdp", + }, + { + /* end node */ + } +}; +MODULE_DEVICE_TABLE(of, hqvdp_match_types); + +struct platform_driver sti_hqvdp_driver = { + .driver = { + .name = "sti-hqvdp", + .owner = THIS_MODULE, + .of_match_table = hqvdp_match_types, + }, + .probe = sti_hqvdp_probe, + .remove = sti_hqvdp_remove, +}; + +module_platform_driver(sti_hqvdp_driver); diff --git a/drivers/gpu/drm/sti/sti_hqvdp.h b/drivers/gpu/drm/sti/sti_hqvdp.h new file mode 100644 index 0000000..4d1085d --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hqvdp.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Fabien Dessenne fabien.dessenne@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_HQVDP_H_ +#define _STI_HQVDP_H_ + +/* + * STI HQVDP structure + * + * @dev: driver device + * @regs: registers + * @clk: IP clock + * @clk_pix_main: pix main clock + * @reset: reset control + */ +struct sti_hqvdp { + struct device *dev; + void __iomem *regs; + struct clk *clk; + struct clk *clk_pix_main; + struct reset_control *reset; +}; + +/* + * STI VDP structure + * + * @device: driver device + * @regs: subdevice register + * @get_formats: get VDP supported formats + * @get_nb_formats: get number of format supported + * @prepare: prepare VDP before rendering + * @commit: set VDP for rendering + * @disable: disable VDP + * @clk_pix_main: pix main clock + * @private: private data (ex: xp 70 cmd) + */ +struct sti_vdp { + struct device *dev; + void __iomem *regs; + const uint32_t* (*get_formats)(void); + int (*get_nb_formats)(void); + int (*prepare)(void *layer, bool first_prepare); + int (*commit)(void *layer); + int (*disable)(void *layer); + struct clk *clk_pix_main; + void *private; +}; + +int sti_hqvdp_load_init(struct device *dev, void *data); +int sti_hqvdp_dbg_show(struct seq_file *m, void *arg); + +#endif diff --git a/drivers/gpu/drm/sti/sti_hqvdp_lut.h b/drivers/gpu/drm/sti/sti_hqvdp_lut.h new file mode 100644 index 0000000..619af7f --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hqvdp_lut.h @@ -0,0 +1,373 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Fabien Dessenne fabien.dessenne@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_HQVDP_LUT_H_ +#define _STI_HQVDP_LUT_H_ + +#define NB_COEF 128 + +#define SHIFT_LUT_A_LEGACY 8 +#define SHIFT_LUT_B 8 +#define SHIFT_LUT_C_Y_LEGACY 8 +#define SHIFT_LUT_C_C_LEGACY 8 +#define SHIFT_LUT_D_Y_LEGACY 8 +#define SHIFT_LUT_D_C_LEGACY 8 +#define SHIFT_LUT_E_Y_LEGACY 8 +#define SHIFT_LUT_E_C_LEGACY 8 +#define SHIFT_LUT_F_Y_LEGACY 8 +#define SHIFT_LUT_F_C_LEGACY 8 + +static const u32 coef_lut_a_legacy[NB_COEF] = { + 0x0000ffff, 0x00010000, 0x000100ff, 0x00000000, + 0x00000000, 0x00050000, 0xfffc00ff, 0x00000000, + 0x00000000, 0x00090000, 0xfff900fe, 0x00000000, + 0x00000000, 0x0010ffff, 0xfff600fb, 0x00000000, + 0x00000000, 0x0017fffe, 0xfff400f7, 0x00000000, + 0x00000000, 0x001ffffd, 0xfff200f2, 0x00000000, + 0x00000000, 0x0027fffc, 0xfff100ec, 0x00000000, + 0x00000000, 0x0030fffb, 0xfff000e5, 0x00000000, + 0x00000000, 0x003afffa, 0xffee00de, 0x00000000, + 0x00000000, 0x0044fff9, 0xffed00d6, 0x00000000, + 0x00000000, 0x004efff8, 0xffed00cd, 0x00000000, + 0x00000000, 0x0059fff6, 0xffed00c4, 0x00000000, + 0x00000000, 0x0064fff5, 0xffed00ba, 0x00000000, + 0x00000000, 0x006ffff3, 0xffee00b0, 0x00000000, + 0x00000000, 0x007afff2, 0xffee00a6, 0x00000000, + 0x00000000, 0x0085fff1, 0xffef009b, 0x00000000, + 0x00000000, 0x0090fff0, 0xfff00090, 0x00000000, + 0x00000000, 0x009bffef, 0xfff10085, 0x00000000, + 0x00000000, 0x00a6ffee, 0xfff2007a, 0x00000000, + 0x00000000, 0x00b0ffee, 0xfff3006f, 0x00000000, + 0x00000000, 0x00baffed, 0xfff50064, 0x00000000, + 0x00000000, 0x00c4ffed, 0xfff60059, 0x00000000, + 0x00000000, 0x00cdffed, 0xfff8004e, 0x00000000, + 0x00000000, 0x00d6ffed, 0xfff90044, 0x00000000, + 0x00000000, 0x00deffee, 0xfffa003a, 0x00000000, + 0x00000000, 0x00e5fff0, 0xfffb0030, 0x00000000, + 0x00000000, 0x00ecfff1, 0xfffc0027, 0x00000000, + 0x00000000, 0x00f2fff2, 0xfffd001f, 0x00000000, + 0x00000000, 0x00f7fff4, 0xfffe0017, 0x00000000, + 0x00000000, 0x00fbfff6, 0xffff0010, 0x00000000, + 0x00000000, 0x00fefff9, 0x00000009, 0x00000000, + 0x00000000, 0x00fffffc, 0x00000005, 0x00000000 +}; + +static const u32 coef_lut_b[NB_COEF] = { + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000, + 0x00000000, 0x00000000, 0x00000100, 0x00000000 +}; + +static const u32 coef_lut_c_y_legacy[NB_COEF] = { + 0x00060004, 0x0038ffe1, 0x003800be, 0x0006ffe1, + 0x00050005, 0x0042ffe1, 0x003800b3, 0x0007ffe1, + 0x00040006, 0x0046ffe1, 0x003300b2, 0x0008ffe2, + 0x00030007, 0x004cffe1, 0x002e00b1, 0x0008ffe2, + 0x00020006, 0x0051ffe2, 0x002900b0, 0x0009ffe3, + 0x00010008, 0x0056ffe2, 0x002400ae, 0x0009ffe4, + 0xffff0008, 0x005cffe3, 0x001f00ad, 0x000affe4, + 0xfffe0008, 0x0062ffe4, 0x001a00ab, 0x000affe5, + 0xfffd000a, 0x0066ffe5, 0x001500a8, 0x000bffe6, + 0xfffc0009, 0x006bffe7, 0x001100a5, 0x000bffe8, + 0xfffa000a, 0x0070ffe8, 0x000d00a3, 0x000bffe9, + 0xfff9000b, 0x0076ffea, 0x0008009f, 0x000bffea, + 0xfff7000b, 0x007affec, 0x0005009b, 0x000cffec, + 0xfff6000b, 0x007effef, 0x00010098, 0x000cffed, + 0xfff4000b, 0x0084fff1, 0xfffd0095, 0x000cffee, + 0xfff3000b, 0x0088fff4, 0xfffa0090, 0x000cfff0, + 0xfff1000b, 0x008dfff7, 0xfff7008d, 0x000bfff1, + 0xfff0000c, 0x0090fffa, 0xfff40088, 0x000bfff3, + 0xffee000c, 0x0095fffd, 0xfff10084, 0x000bfff4, + 0xffed000c, 0x00980001, 0xffef007e, 0x000bfff6, + 0xffec000c, 0x009b0005, 0xffec007a, 0x000bfff7, + 0xffea000b, 0x009f0008, 0xffea0076, 0x000bfff9, + 0xffe9000b, 0x00a3000d, 0xffe80070, 0x000afffa, + 0xffe8000b, 0x00a50011, 0xffe7006b, 0x0009fffc, + 0xffe6000b, 0x00a80015, 0xffe50066, 0x000afffd, + 0xffe5000a, 0x00ab001a, 0xffe40062, 0x0008fffe, + 0xffe4000a, 0x00ad001f, 0xffe3005c, 0x0008ffff, + 0xffe40009, 0x00ae0024, 0xffe20056, 0x00080001, + 0xffe30009, 0x00b00029, 0xffe20051, 0x00060002, + 0xffe20008, 0x00b1002e, 0xffe1004c, 0x00070003, + 0xffe20008, 0x00b20033, 0xffe10046, 0x00060004, + 0xffe10007, 0x00b30038, 0xffe10042, 0x00050005 +}; + +static const u32 coef_lut_c_c_legacy[NB_COEF] = { + 0x0001fff3, 0x003afffb, 0x003a00a1, 0x0001fffb, + 0x0001fff5, 0x0041fffb, 0x0038009a, 0x0001fffb, + 0x0001fff5, 0x0046fffb, 0x00340099, 0x0001fffb, + 0x0001fff7, 0x0049fffb, 0x00300098, 0x0001fffb, + 0x0001fff9, 0x004cfffb, 0x002d0096, 0x0001fffb, + 0x0001fffa, 0x004ffffc, 0x00290095, 0x0001fffb, + 0x0001fff9, 0x0054fffd, 0x00250093, 0x0001fffc, + 0x0001fffa, 0x0058fffd, 0x00220092, 0x0000fffc, + 0x0001fffb, 0x005bfffe, 0x001f0090, 0x0000fffc, + 0x0001fffd, 0x005effff, 0x001c008c, 0x0000fffd, + 0x0001fffd, 0x00620000, 0x0019008a, 0x0000fffd, + 0x0001fffe, 0x00660001, 0x00160088, 0xfffffffd, + 0x0000fffe, 0x006a0003, 0x00130085, 0xfffffffe, + 0x0000fffe, 0x006e0004, 0x00100083, 0xfffffffe, + 0x0000fffe, 0x00710006, 0x000e007f, 0xffffffff, + 0x0000fffe, 0x00750008, 0x000c007c, 0xfffeffff, + 0xfffffffe, 0x0079000a, 0x000a0079, 0xfffeffff, + 0xfffffffe, 0x007c000c, 0x00080075, 0xfffe0000, + 0xffffffff, 0x007f000e, 0x00060071, 0xfffe0000, + 0xfffeffff, 0x00830010, 0x0004006e, 0xfffe0000, + 0xfffeffff, 0x00850013, 0x0003006a, 0xfffe0000, + 0xfffdffff, 0x00880016, 0x00010066, 0xfffe0001, + 0xfffd0000, 0x008a0019, 0x00000062, 0xfffd0001, + 0xfffd0000, 0x008c001c, 0xffff005e, 0xfffd0001, + 0xfffc0000, 0x0090001f, 0xfffe005b, 0xfffb0001, + 0xfffc0000, 0x00920022, 0xfffd0058, 0xfffa0001, + 0xfffc0001, 0x00930025, 0xfffd0054, 0xfff90001, + 0xfffb0001, 0x00950029, 0xfffc004f, 0xfffa0001, + 0xfffb0001, 0x0096002d, 0xfffb004c, 0xfff90001, + 0xfffb0001, 0x00980030, 0xfffb0049, 0xfff70001, + 0xfffb0001, 0x00990034, 0xfffb0046, 0xfff50001, + 0xfffb0001, 0x009a0038, 0xfffb0041, 0xfff50001 +}; + +static const u32 coef_lut_d_y_legacy[NB_COEF] = { + 0xfff80009, 0x0046ffec, 0x004600a3, 0xfff8ffec, + 0xfff70009, 0x004effed, 0x0044009d, 0xfff9ffeb, + 0xfff6000a, 0x0052ffee, 0x003f009d, 0xfffaffea, + 0xfff50009, 0x0057ffef, 0x003b009d, 0xfffbffe9, + 0xfff50008, 0x005bfff0, 0x0037009c, 0xfffcffe9, + 0xfff40008, 0x005ffff2, 0x0033009b, 0xfffcffe9, + 0xfff30007, 0x0064fff3, 0x002f009b, 0xfffdffe8, + 0xfff20007, 0x0068fff5, 0x002b0099, 0xfffeffe8, + 0xfff10008, 0x006bfff7, 0x00270097, 0xffffffe8, + 0xfff00007, 0x006ffff9, 0x00230097, 0xffffffe8, + 0xffef0006, 0x0073fffb, 0x00200095, 0x0000ffe8, + 0xffee0005, 0x0077fffe, 0x001c0093, 0x0000ffe9, + 0xffee0005, 0x007a0000, 0x00180091, 0x0001ffe9, + 0xffed0005, 0x007d0003, 0x0015008e, 0x0002ffe9, + 0xffec0005, 0x00800006, 0x0012008b, 0x0002ffea, + 0xffeb0004, 0x00840008, 0x000e008a, 0x0003ffea, + 0xffeb0003, 0x0087000b, 0x000b0087, 0x0003ffeb, + 0xffea0003, 0x008a000e, 0x00080084, 0x0004ffeb, + 0xffea0002, 0x008b0012, 0x00060080, 0x0005ffec, + 0xffe90002, 0x008e0015, 0x0003007d, 0x0005ffed, + 0xffe90001, 0x00910018, 0x0000007a, 0x0005ffee, + 0xffe90000, 0x0093001c, 0xfffe0077, 0x0005ffee, + 0xffe80000, 0x00950020, 0xfffb0073, 0x0006ffef, + 0xffe8ffff, 0x00970023, 0xfff9006f, 0x0007fff0, + 0xffe8ffff, 0x00970027, 0xfff7006b, 0x0008fff1, + 0xffe8fffe, 0x0099002b, 0xfff50068, 0x0007fff2, + 0xffe8fffd, 0x009b002f, 0xfff30064, 0x0007fff3, + 0xffe9fffc, 0x009b0033, 0xfff2005f, 0x0008fff4, + 0xffe9fffc, 0x009c0037, 0xfff0005b, 0x0008fff5, + 0xffe9fffb, 0x009d003b, 0xffef0057, 0x0009fff5, + 0xffeafffa, 0x009d003f, 0xffee0052, 0x000afff6, + 0xffebfff9, 0x009d0044, 0xffed004e, 0x0009fff7 +}; + +static const u32 coef_lut_d_c_legacy[NB_COEF] = { + 0xfffeffff, 0x003fffff, 0x003f0089, 0xfffeffff, + 0xfffe0000, 0x00460000, 0x0042007d, 0xfffffffe, + 0xfffe0000, 0x00490001, 0x003f007d, 0xfffffffd, + 0xfffd0001, 0x004b0002, 0x003c007d, 0x0000fffc, + 0xfffd0001, 0x004e0003, 0x0039007c, 0x0000fffc, + 0xfffc0001, 0x00510005, 0x0036007c, 0x0000fffb, + 0xfffc0001, 0x00540006, 0x0033007b, 0x0001fffa, + 0xfffc0003, 0x00550008, 0x00310078, 0x0001fffa, + 0xfffb0003, 0x00580009, 0x002e0078, 0x0001fffa, + 0xfffb0002, 0x005b000b, 0x002b0077, 0x0002fff9, + 0xfffa0003, 0x005e000d, 0x00280075, 0x0002fff9, + 0xfffa0002, 0x0060000f, 0x00260074, 0x0002fff9, + 0xfffa0004, 0x00610011, 0x00230072, 0x0002fff9, + 0xfffa0004, 0x00640013, 0x00200070, 0x0002fff9, + 0xfff90004, 0x00660015, 0x001e006e, 0x0003fff9, + 0xfff90004, 0x00680017, 0x001c006c, 0x0003fff9, + 0xfff90003, 0x006b0019, 0x0019006b, 0x0003fff9, + 0xfff90003, 0x006c001c, 0x00170068, 0x0004fff9, + 0xfff90003, 0x006e001e, 0x00150066, 0x0004fff9, + 0xfff90002, 0x00700020, 0x00130064, 0x0004fffa, + 0xfff90002, 0x00720023, 0x00110061, 0x0004fffa, + 0xfff90002, 0x00740026, 0x000f0060, 0x0002fffa, + 0xfff90002, 0x00750028, 0x000d005e, 0x0003fffa, + 0xfff90002, 0x0077002b, 0x000b005b, 0x0002fffb, + 0xfffa0001, 0x0078002e, 0x00090058, 0x0003fffb, + 0xfffa0001, 0x00780031, 0x00080055, 0x0003fffc, + 0xfffa0001, 0x007b0033, 0x00060054, 0x0001fffc, + 0xfffb0000, 0x007c0036, 0x00050051, 0x0001fffc, + 0xfffc0000, 0x007c0039, 0x0003004e, 0x0001fffd, + 0xfffc0000, 0x007d003c, 0x0002004b, 0x0001fffd, + 0xfffdffff, 0x007d003f, 0x00010049, 0x0000fffe, + 0xfffeffff, 0x007d0042, 0x00000046, 0x0000fffe +}; + +static const u32 coef_lut_e_y_legacy[NB_COEF] = { + 0xfff10001, 0x00490004, 0x00490083, 0xfff10004, + 0xfff10000, 0x00500006, 0x004b007b, 0xfff10002, + 0xfff10000, 0x00530007, 0x0048007b, 0xfff10001, + 0xfff10000, 0x00550009, 0x0046007a, 0xfff10000, + 0xfff1fffe, 0x0058000b, 0x0043007b, 0xfff2fffe, + 0xfff1ffff, 0x005a000d, 0x0040007a, 0xfff2fffd, + 0xfff1fffd, 0x005d000f, 0x003e007a, 0xfff2fffc, + 0xfff1fffd, 0x005f0011, 0x003b0079, 0xfff3fffb, + 0xfff1fffc, 0x00610013, 0x00390079, 0xfff3fffa, + 0xfff1fffb, 0x00640015, 0x00360079, 0xfff3fff9, + 0xfff1fffa, 0x00660017, 0x00340078, 0xfff4fff8, + 0xfff1fffb, 0x00680019, 0x00310077, 0xfff4fff7, + 0xfff2fff9, 0x006a001b, 0x002f0076, 0xfff5fff6, + 0xfff2fff9, 0x006c001e, 0x002c0075, 0xfff5fff5, + 0xfff2fff9, 0x006d0020, 0x002a0073, 0xfff6fff5, + 0xfff3fff7, 0x00700022, 0x00270073, 0xfff6fff4, + 0xfff3fff7, 0x00710025, 0x00250071, 0xfff7fff3, + 0xfff4fff6, 0x00730027, 0x00220070, 0xfff7fff3, + 0xfff5fff6, 0x0073002a, 0x0020006d, 0xfff9fff2, + 0xfff5fff5, 0x0075002c, 0x001e006c, 0xfff9fff2, + 0xfff6fff5, 0x0076002f, 0x001b006a, 0xfff9fff2, + 0xfff7fff4, 0x00770031, 0x00190068, 0xfffbfff1, + 0xfff8fff4, 0x00780034, 0x00170066, 0xfffafff1, + 0xfff9fff3, 0x00790036, 0x00150064, 0xfffbfff1, + 0xfffafff3, 0x00790039, 0x00130061, 0xfffcfff1, + 0xfffbfff3, 0x0079003b, 0x0011005f, 0xfffdfff1, + 0xfffcfff2, 0x007a003e, 0x000f005d, 0xfffdfff1, + 0xfffdfff2, 0x007a0040, 0x000d005a, 0xfffffff1, + 0xfffefff2, 0x007b0043, 0x000b0058, 0xfffefff1, + 0x0000fff1, 0x007a0046, 0x00090055, 0x0000fff1, + 0x0001fff1, 0x007b0048, 0x00070053, 0x0000fff1, + 0x0002fff1, 0x007b004b, 0x00060050, 0x0000fff1 +}; + +static const u32 coef_lut_e_c_legacy[NB_COEF] = { + 0xfffa0001, 0x003f0010, 0x003f006d, 0xfffa0010, + 0xfffb0002, 0x00440011, 0x00440062, 0xfffa000e, + 0xfffb0001, 0x00460013, 0x00420062, 0xfffa000d, + 0xfffb0000, 0x00480014, 0x00410062, 0xfffa000c, + 0xfffb0001, 0x00490015, 0x003f0061, 0xfffb000b, + 0xfffb0000, 0x004b0017, 0x003d0061, 0xfffb000a, + 0xfffb0000, 0x004d0018, 0x003b0062, 0xfffb0008, + 0xfffcffff, 0x004f001a, 0x00390061, 0xfffb0007, + 0xfffc0000, 0x004f001c, 0x00380060, 0xfffb0006, + 0xfffcffff, 0x0052001d, 0x00360060, 0xfffb0005, + 0xfffdfffe, 0x0053001f, 0x00340060, 0xfffb0004, + 0xfffdfffe, 0x00540021, 0x0032005e, 0xfffc0004, + 0xfffeffff, 0x00550022, 0x0030005d, 0xfffc0003, + 0xfffeffff, 0x00560024, 0x002f005c, 0xfffc0002, + 0xfffffffd, 0x00580026, 0x002d005c, 0xfffc0001, + 0xfffffffd, 0x005a0027, 0x002b005c, 0xfffc0000, + 0x0000fffd, 0x005a0029, 0x0029005a, 0xfffd0000, + 0x0000fffc, 0x005c002b, 0x0027005a, 0xfffdffff, + 0x0001fffc, 0x005c002d, 0x00260058, 0xfffdffff, + 0x0002fffc, 0x005c002f, 0x00240056, 0xfffffffe, + 0x0003fffc, 0x005d0030, 0x00220055, 0xfffffffe, + 0x0004fffc, 0x005e0032, 0x00210054, 0xfffefffd, + 0x0004fffb, 0x00600034, 0x001f0053, 0xfffefffd, + 0x0005fffb, 0x00600036, 0x001d0052, 0xfffffffc, + 0x0006fffb, 0x00600038, 0x001c004f, 0x0000fffc, + 0x0007fffb, 0x00610039, 0x001a004f, 0xfffffffc, + 0x0008fffb, 0x0062003b, 0x0018004d, 0x0000fffb, + 0x000afffb, 0x0061003d, 0x0017004b, 0x0000fffb, + 0x000bfffb, 0x0061003f, 0x00150049, 0x0001fffb, + 0x000cfffa, 0x00620041, 0x00140048, 0x0000fffb, + 0x000dfffa, 0x00620042, 0x00130046, 0x0001fffb, + 0x000efffa, 0x00620044, 0x00110044, 0x0002fffb +}; + +static const u32 coef_lut_f_y_legacy[NB_COEF] = { + 0xfff6fff0, 0x00490012, 0x0049006e, 0xfff60012, + 0xfff7fff1, 0x004e0013, 0x00490068, 0xfff60010, + 0xfff7fff2, 0x004f0015, 0x00470067, 0xfff6000f, + 0xfff7fff5, 0x004f0017, 0x00450065, 0xfff6000e, + 0xfff8fff5, 0x00500018, 0x00440065, 0xfff6000c, + 0xfff8fff6, 0x0051001a, 0x00420064, 0xfff6000b, + 0xfff8fff6, 0x0052001c, 0x00400064, 0xfff6000a, + 0xfff9fff6, 0x0054001d, 0x003e0064, 0xfff60008, + 0xfff9fff8, 0x0054001f, 0x003c0063, 0xfff60007, + 0xfffafff8, 0x00550021, 0x003a0062, 0xfff60006, + 0xfffbfff7, 0x00560022, 0x00390062, 0xfff60005, + 0xfffbfff8, 0x00570024, 0x00370061, 0xfff60004, + 0xfffcfff8, 0x00580026, 0x00350060, 0xfff60003, + 0xfffdfff8, 0x00590028, 0x0033005f, 0xfff60002, + 0xfffdfff7, 0x005b002a, 0x0031005f, 0xfff60001, + 0xfffefff7, 0x005c002c, 0x002f005e, 0xfff60000, + 0xfffffff6, 0x005e002d, 0x002d005e, 0xfff6ffff, + 0x0000fff6, 0x005e002f, 0x002c005c, 0xfff7fffe, + 0x0001fff6, 0x005f0031, 0x002a005b, 0xfff7fffd, + 0x0002fff6, 0x005f0033, 0x00280059, 0xfff8fffd, + 0x0003fff6, 0x00600035, 0x00260058, 0xfff8fffc, + 0x0004fff6, 0x00610037, 0x00240057, 0xfff8fffb, + 0x0005fff6, 0x00620039, 0x00220056, 0xfff7fffb, + 0x0006fff6, 0x0062003a, 0x00210055, 0xfff8fffa, + 0x0007fff6, 0x0063003c, 0x001f0054, 0xfff8fff9, + 0x0008fff6, 0x0064003e, 0x001d0054, 0xfff6fff9, + 0x000afff6, 0x00640040, 0x001c0052, 0xfff6fff8, + 0x000bfff6, 0x00640042, 0x001a0051, 0xfff6fff8, + 0x000cfff6, 0x00650044, 0x00180050, 0xfff5fff8, + 0x000efff6, 0x00650045, 0x0017004f, 0xfff5fff7, + 0x000ffff6, 0x00670047, 0x0015004f, 0xfff2fff7, + 0x0010fff6, 0x00680049, 0x0013004e, 0xfff1fff7 +}; + +static const u32 coef_lut_f_c_legacy[NB_COEF] = { + 0x0000fffb, 0x003a001a, 0x003a005d, 0x0000001a, + 0x0001fffb, 0x003f001b, 0x00400051, 0x00000019, + 0x0001fffc, 0x0040001c, 0x003f0051, 0x00000017, + 0x0002fffb, 0x0042001d, 0x003e0051, 0xffff0016, + 0x0002fffb, 0x0043001e, 0x003d0051, 0xffff0015, + 0x0003fffc, 0x00430020, 0x003b0050, 0xffff0014, + 0x0003fffb, 0x00450021, 0x003a0051, 0xfffe0013, + 0x0004fffc, 0x00450022, 0x00390050, 0xfffe0012, + 0x0005fffc, 0x00460023, 0x0038004f, 0xfffe0011, + 0x0005fffb, 0x00480025, 0x00360050, 0xfffd0010, + 0x0006fffc, 0x00480026, 0x0035004f, 0xfffd000f, + 0x0006fffc, 0x00490027, 0x0034004f, 0xfffd000e, + 0x0007fffd, 0x00490028, 0x0033004e, 0xfffd000d, + 0x0008fffc, 0x004a002a, 0x0031004d, 0xfffd000d, + 0x0009fffd, 0x004a002b, 0x0030004d, 0xfffc000c, + 0x0009fffc, 0x004c002c, 0x002f004d, 0xfffc000b, + 0x000afffc, 0x004c002e, 0x002e004c, 0xfffc000a, + 0x000bfffc, 0x004d002f, 0x002c004c, 0xfffc0009, + 0x000cfffc, 0x004d0030, 0x002b004a, 0xfffd0009, + 0x000dfffd, 0x004d0031, 0x002a004a, 0xfffc0008, + 0x000dfffd, 0x004e0033, 0x00280049, 0xfffd0007, + 0x000efffd, 0x004f0034, 0x00270049, 0xfffc0006, + 0x000ffffd, 0x004f0035, 0x00260048, 0xfffc0006, + 0x0010fffd, 0x00500036, 0x00250048, 0xfffb0005, + 0x0011fffe, 0x004f0038, 0x00230046, 0xfffc0005, + 0x0012fffe, 0x00500039, 0x00220045, 0xfffc0004, + 0x0013fffe, 0x0051003a, 0x00210045, 0xfffb0003, + 0x0014ffff, 0x0050003b, 0x00200043, 0xfffc0003, + 0x0015ffff, 0x0051003d, 0x001e0043, 0xfffb0002, + 0x0016ffff, 0x0051003e, 0x001d0042, 0xfffb0002, + 0x00170000, 0x0051003f, 0x001c0040, 0xfffc0001, + 0x00190000, 0x00510040, 0x001b003f, 0xfffb0001 +}; + +#endif diff --git a/drivers/gpu/drm/sti/sti_layer.c b/drivers/gpu/drm/sti/sti_layer.c index 54c8694..9d7cd3e 100644 --- a/drivers/gpu/drm/sti/sti_layer.c +++ b/drivers/gpu/drm/sti/sti_layer.c @@ -100,6 +100,8 @@ struct sti_layer *sti_layer_create(struct device *dev, int desc, layer->vid = sti_vid_create(dev, baseaddr); if (!layer->vid) goto err; + /* The VDP descriptor is not created now, but will be added + * later upon HQVDP driver probe */ break; default: goto err; @@ -172,9 +174,10 @@ int sti_layer_prepare(struct sti_layer *layer, struct drm_framebuffer *fb, ret = layer->gdp->prepare(layer, !layer->enabled); break; case STI_VID: - if (!layer->vid) + if ((!layer->vid) || (!layer->vdp)) goto err_no_prepare; - ret = layer->vid->prepare(layer, !layer->enabled); + ret = layer->vdp->prepare(layer, !layer->enabled); + ret |= layer->vid->prepare(layer, !layer->enabled); break; default: goto err_no_prepare; @@ -204,9 +207,10 @@ int sti_layer_commit(struct sti_layer *layer) ret = layer->gdp->commit(layer); break; case STI_VID: - if (!layer->vid) + if ((!layer->vid) || (!layer->vdp)) goto err_no_commit; - ret = layer->vid->commit(layer); + ret = layer->vdp->commit(layer); + ret |= layer->vid->commit(layer); break; default: goto err_no_commit; @@ -240,9 +244,10 @@ int sti_layer_disable(struct sti_layer *layer) ret = layer->gdp->disable(layer); break; case STI_VID: - if (!layer->vid) + if ((!layer->vid) || (!layer->vdp)) goto err_no_disable; - ret = layer->vid->disable(layer); + ret = layer->vdp->disable(layer); + ret |= layer->vid->disable(layer); break; default: goto err_no_disable; @@ -272,6 +277,11 @@ const uint32_t *sti_layer_get_formats(struct sti_layer *layer) if (layer->gdp) get_formats = layer->gdp->get_formats; break; + case STI_VID: + /* The single input of [VDP+VID] is VDP */ + if (layer->vdp) + get_formats = layer->vdp->get_formats; + break; default: break; } @@ -296,6 +306,11 @@ int sti_layer_get_nb_formats(struct sti_layer *layer) if (layer->gdp) get_nb_formats = layer->gdp->get_nb_formats; break; + case STI_VID: + /* The single input of [VDP+VID] is VDP */ + if (layer->vdp) + get_nb_formats = layer->vdp->get_nb_formats; + break; default: break; } diff --git a/drivers/gpu/drm/sti/sti_layer.h b/drivers/gpu/drm/sti/sti_layer.h index bf3a14f..bdfb273 100644 --- a/drivers/gpu/drm/sti/sti_layer.h +++ b/drivers/gpu/drm/sti/sti_layer.h @@ -12,6 +12,7 @@ #include <drm/drmP.h> #include "sti_gdp.h" #include "sti_vid.h" +#include "sti_hqvdp.h"
#define to_sti_layer(x) container_of(x, struct sti_layer, plane)
@@ -72,6 +73,7 @@ struct sti_fps_info { * @fps_info: frame per second info * @gdp: related GDP (if the layer is a GDP) * @vid: related VID (if the layer is a VID/VDP) + * @vdp: related VDP (if the layer is a VID/VDP) */ struct sti_layer { struct drm_plane plane; @@ -92,6 +94,7 @@ struct sti_layer { struct sti_fps_info fps_info; struct sti_gdp *gdp; struct sti_vid *vid; + struct sti_vdp *vdp; };
struct sti_layer *sti_layer_create(struct device *dev, int desc,
From: Vincent Abriou vincent.abriou@st.com
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org Signed-off-by: Vincent Abriou vincent.abriou@st.com Signed-off-by: Fabien Dessenne fabien.dessenne@st.com --- drivers/gpu/drm/sti/sti_mixer.c | 70 ++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 35 deletions(-)
diff --git a/drivers/gpu/drm/sti/sti_mixer.c b/drivers/gpu/drm/sti/sti_mixer.c index fce72e4..73d5405 100644 --- a/drivers/gpu/drm/sti/sti_mixer.c +++ b/drivers/gpu/drm/sti/sti_mixer.c @@ -18,16 +18,16 @@ static const u32 MixerColorSpaceMatIdentity[] = { };
/* regs offset */ -#define GAM_MIXER_MAIN_CTL 0x00 -#define GAM_MIXER_MAIN_BKC 0x04 -#define GAM_MIXER_MAIN_BCO 0x0C -#define GAM_MIXER_MAIN_BCS 0x10 -#define GAM_MIXER_MAIN_AVO 0x28 -#define GAM_MIXER_MAIN_AVS 0x2C -#define GAM_MIXER_MAIN_CRB 0x34 -#define GAM_MIXER_MAIN_ACT 0x38 -#define GAM_MIXER_MAIN_MBP 0x3C -#define GAM_MIXER_MAIN_MX0 0x80 +#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 @@ -82,7 +82,7 @@ static inline void sti_mixer_reg_writemask(struct sti_mixer *mixer, void sti_mixer_set_background_status(struct sti_mixer *mixer, bool enable) { sti_mixer_reg_writemask(mixer, - GAM_MIXER_MAIN_CTL, enable, GAM_CTL_BACK_MASK); + GAM_MIXER_CTL, enable, GAM_CTL_BACK_MASK); }
static void sti_mixer_set_background_color(struct sti_mixer *mixer, @@ -90,7 +90,7 @@ static void sti_mixer_set_background_color(struct sti_mixer *mixer, { u32 val = (red << 16) | (green << 8) | blue;
- sti_mixer_reg_write(mixer, GAM_MIXER_MAIN_BKC, val); + sti_mixer_reg_write(mixer, GAM_MIXER_BKC, val); }
static void sti_mixer_set_background_area(struct sti_mixer *mixer, @@ -103,8 +103,8 @@ static void sti_mixer_set_background_area(struct sti_mixer *mixer, 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_MAIN_BCO, ydo << 16 | xdo); - sti_mixer_reg_write(mixer, GAM_MIXER_MAIN_BCS, yds << 16 | xds); + 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) @@ -143,12 +143,12 @@ int sti_mixer_set_layer_depth(struct sti_mixer *mixer, struct sti_layer *layer)
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_MAIN_CRB val 0x%x mask 0x%x\n", + dev_dbg(mixer->dev, "GAM_MIXER_CRB val 0x%x mask 0x%x\n", layer_id, mask); - sti_mixer_reg_writemask(mixer, GAM_MIXER_MAIN_CRB, layer_id, mask); + sti_mixer_reg_writemask(mixer, GAM_MIXER_CRB, layer_id, mask);
- dev_dbg(mixer->dev, "Read GAM_MIXER_MAIN_CRB 0x%x\n", - sti_mixer_reg_read(mixer, GAM_MIXER_MAIN_CRB)); + dev_dbg(mixer->dev, "Read GAM_MIXER_CRB 0x%x\n", + sti_mixer_reg_read(mixer, GAM_MIXER_CRB)); return 0; }
@@ -164,8 +164,8 @@ int sti_mixer_active_video_area(struct sti_mixer *mixer,
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_MAIN_AVO, ydo << 16 | xdo); - sti_mixer_reg_write(mixer, GAM_MIXER_MAIN_AVS, yds << 16 | xds); + 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);
@@ -210,7 +210,7 @@ int sti_mixer_set_layer_status(struct sti_mixer *mixer, return -1; } val = status ? mask : 0; - sti_mixer_reg_writemask(mixer, GAM_MIXER_MAIN_CTL, val, mask); + sti_mixer_reg_writemask(mixer, GAM_MIXER_CTL, val, mask); return 0; }
@@ -219,7 +219,7 @@ void sti_mixer_set_matrix(struct sti_mixer *mixer) int i;
for (i = 0; i < ARRAY_SIZE(MixerColorSpaceMatIdentity); i++) - sti_mixer_reg_write(mixer, GAM_MIXER_MAIN_MX0 + (i * 4), + sti_mixer_reg_write(mixer, GAM_MIXER_MX0 + (i * 4), MixerColorSpaceMatIdentity[i]); }
@@ -359,23 +359,23 @@ int sti_mixer_dbg_show(struct seq_file *m, void *arg) struct sti_mixer *mixer = compo->mixer[i];
seq_printf(m, "\n%s", sti_mixer_to_str(mixer)); - MIXER_DBG_DUMP(GAM_MIXER_MAIN_CTL); + MIXER_DBG_DUMP(GAM_MIXER_CTL); sti_mixer_dbg_ctl(m, sti_mixer_reg_read(mixer, - GAM_MIXER_MAIN_CTL)); - MIXER_DBG_DUMP(GAM_MIXER_MAIN_BKC); - MIXER_DBG_DUMP(GAM_MIXER_MAIN_BCO); - MIXER_DBG_DUMP(GAM_MIXER_MAIN_BCS); - MIXER_DBG_DUMP(GAM_MIXER_MAIN_AVO); - MIXER_DBG_DUMP(GAM_MIXER_MAIN_AVS); - MIXER_DBG_DUMP(GAM_MIXER_MAIN_CRB); + GAM_MIXER_CTL)); + MIXER_DBG_DUMP(GAM_MIXER_BKC); + MIXER_DBG_DUMP(GAM_MIXER_BCO); + MIXER_DBG_DUMP(GAM_MIXER_BCS); + MIXER_DBG_DUMP(GAM_MIXER_AVO); + MIXER_DBG_DUMP(GAM_MIXER_AVS); + MIXER_DBG_DUMP(GAM_MIXER_CRB); sti_mixer_dbg_crb(m, sti_mixer_reg_read(mixer, - GAM_MIXER_MAIN_CRB)); - MIXER_DBG_DUMP(GAM_MIXER_MAIN_ACT); - MIXER_DBG_DUMP(GAM_MIXER_MAIN_MBP); - MIXER_DBG_DUMP(GAM_MIXER_MAIN_MX0); - sti_mixer_dbg_mxn(m, mixer->regs + GAM_MIXER_MAIN_MX0); + GAM_MIXER_CRB)); + MIXER_DBG_DUMP(GAM_MIXER_ACT); + MIXER_DBG_DUMP(GAM_MIXER_MBP); + MIXER_DBG_DUMP(GAM_MIXER_MX0); + sti_mixer_dbg_mxn(m, mixer->regs + GAM_MIXER_MX0); seq_puts(m, "\n");
crtc = &mixer->drm_crtc;
linaro-mm-sig@lists.linaro.org