This series of patches add the support of DRM/KMS drivers for STMicroelectronics chipsets stih416 and stih407.
version 4: - Remove depency between TVout it subdevices HDMI and HDA - Rework and simplify VTG and VTAC code - Fix numbers of typo and indentation - Remove debug (will be push in separate patches) - Fix remarks done in previous patcheset patches could be found here: git://git.linaro.org/people/benjamin.gaignard/kernel.git on branch: drm_kms_for_next-v4
version 3: - Correctly split code between probe and bind funtions - Squash some commits - remove HQ-VDP device code to have a smaller patcheset, we will introduce it later.
patches could be found here: git://git.linaro.org/people/benjamin.gaignard/kernel.git on branch: drm_kms_for_next-v3
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 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 (11): 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 HDA driver drm: sti: add TVOut driver drm: sti: add GDP layer drm: sti: add VID layer drm: sti: add Mixer drm: sti: add Compositor drm: sti: Add DRM driver itself
.../devicetree/bindings/gpu/st,stih4xx.txt | 189 ++++++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sti/Kconfig | 14 + drivers/gpu/drm/sti/Makefile | 19 + drivers/gpu/drm/sti/sti_compositor.c | 265 ++++++++ drivers/gpu/drm/sti/sti_compositor.h | 90 +++ drivers/gpu/drm/sti/sti_drm_connector.c | 114 ++++ drivers/gpu/drm/sti/sti_drm_connector.h | 55 ++ drivers/gpu/drm/sti/sti_drm_crtc.c | 436 ++++++++++++ drivers/gpu/drm/sti/sti_drm_crtc.h | 21 + drivers/gpu/drm/sti/sti_drm_drv.c | 229 +++++++ drivers/gpu/drm/sti/sti_drm_drv.h | 35 + drivers/gpu/drm/sti/sti_drm_encoder.c | 108 +++ drivers/gpu/drm/sti/sti_drm_encoder.h | 17 + drivers/gpu/drm/sti/sti_drm_plane.c | 192 ++++++ drivers/gpu/drm/sti/sti_drm_plane.h | 17 + drivers/gpu/drm/sti/sti_gdp.c | 512 ++++++++++++++ drivers/gpu/drm/sti/sti_gdp.h | 73 ++ drivers/gpu/drm/sti/sti_hda.c | 727 ++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi.c | 748 +++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi.h | 88 +++ drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c | 336 +++++++++ drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.h | 14 + drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c | 211 ++++++ drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.h | 14 + drivers/gpu/drm/sti/sti_layer.c | 312 +++++++++ drivers/gpu/drm/sti/sti_layer.h | 114 ++++ drivers/gpu/drm/sti/sti_mixer.c | 249 +++++++ drivers/gpu/drm/sti/sti_mixer.h | 52 ++ drivers/gpu/drm/sti/sti_tvout.c | 687 +++++++++++++++++++ drivers/gpu/drm/sti/sti_tvout.h | 18 + drivers/gpu/drm/sti/sti_vid.c | 145 ++++ drivers/gpu/drm/sti/sti_vid.h | 33 + drivers/gpu/drm/sti/sti_vtac.c | 211 ++++++ drivers/gpu/drm/sti/sti_vtg.c | 356 ++++++++++ drivers/gpu/drm/sti/sti_vtg.h | 28 + 37 files changed, 6732 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_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_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_tx3g0c55phy.h create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.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.c create mode 100644 drivers/gpu/drm/sti/sti_vtg.c create mode 100644 drivers/gpu/drm/sti/sti_vtg.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 | 189 +++++++++++++++++++++ 1 file changed, 189 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..59a59a7 --- /dev/null +++ b/Documentation/devicetree/bindings/gpu/st,stih4xx.txt @@ -0,0 +1,189 @@ +STMicroelectronics stih4xx platforms + +- sti-vtg: video timing generator + Required properties: + - compatible: "st,vtg" + - reg: Physical base address of the IP registers and length of memory mapped region. + Optional properties: + - interrupts : VTG interrupt number to the CPU. + - st,slave: phandle on a slave vtg + +- sti-vtac: video timing advanced inter dye communication Rx and TX + Required properties: + - compatible: "st,vtac-main" or "st,vtac-aux" + - reg: Physical base address of the IP registers and length of memory mapped region. + - clocks: from common clock binding: handle hardware IP needed clocks, the + number of clocks may depend of the SoC type. + See ../clocks/clock-bindings.txt for details. + - clock-names: names of the clocks listed in clocks property in the same + order. + +- sti-display-subsystem: Master device for DRM sub-components + This device must be the parent of all the sub-components and is responsible + of bind them. + Required properties: + - compatible: "st,sti-display-subsystem" + - ranges: to allow probing of subdevices + +- sti-compositor: frame compositor engine + must be a child of sti-display-subsystem + Required properties: + - compatible: "st,stih<chip>-compositor" + - reg: Physical base address of the IP registers and length of memory mapped region. + - clocks: from common clock binding: handle hardware IP needed clocks, the + number of clocks may depend of the SoC type. + See ../clocks/clock-bindings.txt for details. + - clock-names: names of the clocks listed in clocks property in the same + order. + - resets: resets to be used by the device + See ../reset/reset.txt for details. + - reset-names: names of the resets listed in resets property in the same + order. + - st,vtg: phandle(s) on vtg device (main and aux) nodes. + +- sti-tvout: video out hardware block + must be a child of sti-display-subsystem + Required properties: + - compatible: "st,stih<chip>-tvout" + - reg: Physical base address of the IP registers and length of memory mapped region. + - reg-names: names of the mapped memory regions listed in regs property in + the same order. + - resets: resets to be used by the device + See ../reset/reset.txt for details. + - reset-names: names of the resets listed in resets property in the same + order. + - ranges: to allow probing of subdevices + +- sti-hdmi: hdmi output block + must be a child of sti-tvout + Required properties: + - compatible: "st,stih<chip>-hdmi"; + - reg: Physical base address of the IP registers and length of memory mapped region. + - reg-names: names of the mapped memory regions listed in regs property in + the same order. + - interrupts : HDMI interrupt number to the CPU. + - interrupt-names: name of the interrupts listed in interrupts property in + the same order + - clocks: from common clock binding: handle hardware IP needed clocks, the + number of clocks may depend of the SoC type. + - clock-names: names of the clocks listed in clocks property in the same + order. + - hdmi,hpd-gpio: gpio id to detect if an hdmi cable is plugged or not. + +sti-hda: + Required properties: + must be a child of sti-tvout + - compatible: "st,stih<chip>-hda" + - reg: Physical base address of the IP registers and length of memory mapped region. + - reg-names: names of the mapped memory regions listed in regs property in + the same order. + - clocks: from common clock binding: handle hardware IP needed clocks, the + number of clocks may depend of the SoC type. + See ../clocks/clock-bindings.txt for details. + - clock-names: names of the clocks listed in clocks property in the same + order. + +Example: + +/ { + ... + + vtg_main_slave: sti-vtg-main-slave@fe85A800 { + compatible = "st,vtg"; + reg = <0xfe85A800 0x300>; + interrupts = <GIC_SPI 175 IRQ_TYPE_NONE>; + }; + + vtg_main: sti-vtg-main-master@fd348000 { + compatible = "st,vtg"; + reg = <0xfd348000 0x400>; + st,slave = <&vtg_main_slave>; + }; + + vtg_aux_slave: sti-vtg-aux-slave@fd348400 { + compatible = "st,vtg"; + reg = <0xfe858200 0x300>; + interrupts = <GIC_SPI 176 IRQ_TYPE_NONE>; + }; + + vtg_aux: sti-vtg-aux-master@fd348400 { + compatible = "st,vtg"; + reg = <0xfd348400 0x400>; + st,slave = <&vtg_aux_slave>; + }; + + + sti-vtac-rx-main@fee82800 { + compatible = "st,vtac-main"; + reg = <0xfee82800 0x200>; + clock-names = "vtac"; + clocks = <&CLK_M_A2_DIV0 CLK_M_VTAC_MAIN_PHY>; + }; + + sti-vtac-rx-aux@fee82a00 { + compatible = "st,vtac-aux"; + reg = <0xfee82a00 0x200>; + clock-names = "vtac"; + clocks = <&CLK_M_A2_DIV0 CLK_M_VTAC_AUX_PHY>; + }; + + sti-vtac-tx-main@fd349000 { + compatible = "st,vtac-main"; + reg = <0xfd349000 0x200>, <0xfd320000 0x10000>; + clock-names = "vtac"; + clocks = <&CLK_S_A1_HS CLK_S_VTAC_TX_PHY>; + }; + + sti-vtac-tx-aux@fd349200 { + compatible = "st,vtac-aux"; + reg = <0xfd349200 0x200>, <0xfd320000 0x10000>; + clock-names = "vtac"; + clocks = <&CLK_S_A1_HS CLK_S_VTAC_TX_PHY>; + }; + + sti-display-subsystem { + compatible = "st,sti-display-subsystem"; + ranges; + + sti-compositor@fd340000 { + compatible = "st,stih416-compositor"; + reg = <0xfd340000 0x1000>; + clock-names = "compo_main", "compo_aux", + "pix_main", "pix_aux"; + clocks = <&CLK_M_A2_DIV1 CLK_M_COMPO_MAIN>, <&CLK_M_A2_DIV1 CLK_M_COMPO_AUX>, + <&CLOCKGEN_C_VCC CLK_S_PIX_MAIN>, <&CLOCKGEN_C_VCC CLK_S_PIX_AUX>; + reset-names = "compo-main", "compo-aux"; + resets = <&softreset STIH416_COMPO_M_SOFTRESET>, <&softreset STIH416_COMPO_A_SOFTRESET>; + st,vtg = <&vtg_main>, <&vtg_aux>; + }; + + sti-tvout@fe000000 { + compatible = "st,stih416-tvout"; + reg = <0xfe000000 0x1000>, <0xfe85a000 0x400>, <0xfe830000 0x10000>; + reg-names = "tvout-reg", "hda-reg", "syscfg"; + reset-names = "tvout"; + resets = <&softreset STIH416_HDTVOUT_SOFTRESET>; + ranges; + + sti-hdmi@fe85c000 { + compatible = "st,stih416-hdmi"; + reg = <0xfe85c000 0x1000>, <0xfe830000 0x10000>; + reg-names = "hdmi-reg", "syscfg"; + interrupts = <GIC_SPI 173 IRQ_TYPE_NONE>; + interrupt-names = "irq"; + clock-names = "pix", "tmds", "phy", "audio"; + clocks = <&CLOCKGEN_C_VCC CLK_S_PIX_HDMI>, <&CLOCKGEN_C_VCC CLK_S_TMDS_HDMI>, <&CLOCKGEN_C_VCC CLK_S_HDMI_REJECT_PLL>, <&CLOCKGEN_B1 CLK_S_PCM_0>; + hdmi,hpd-gpio = <&PIO2 5>; + }; + + sti-hda@fe85a000 { + compatible = "st,stih416-hda"; + reg = <0xfe85a000 0x400>, <0xfe83085c 0x4>; + reg-names = "hda-reg", "video-dacs-ctrl"; + clock-names = "pix", "hddac"; + clocks = <&CLOCKGEN_C_VCC CLK_S_PIX_HD>, <&CLOCKGEN_C_VCC CLK_S_HDDAC>; + }; + }; + }; + ... +};
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 --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sti/Kconfig | 5 + drivers/gpu/drm/sti/Makefile | 2 + drivers/gpu/drm/sti/sti_vtg.c | 356 ++++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_vtg.h | 28 ++++ 6 files changed, 394 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
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..cbd664b --- /dev/null +++ b/drivers/gpu/drm/sti/Kconfig @@ -0,0 +1,5 @@ +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 diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile new file mode 100644 index 0000000..3658a13e --- /dev/null +++ b/drivers/gpu/drm/sti/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_DRM_STI) += \ + sti_vtg.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..49f95d4 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg.c @@ -0,0 +1,356 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * Vincent Abriou vincent.abriou@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/platform_device.h> + +#include <drm/drmP.h> + +#include "sti_vtg.h" + +#define VTG_TYPE_MASTER 0 +#define VTG_TYPE_SLAVE_BY_EXT0 1 + +/* registers offset */ +#define VTG_MODE 0x0000 +#define VTG_CLKLN 0x0008 +#define VTG_HLFLN 0x000C +#define VTG_DRST_AUTOC 0x0010 +#define VTG_VID_TFO 0x0040 +#define VTG_VID_TFS 0x0044 +#define VTG_VID_BFO 0x0048 +#define VTG_VID_BFS 0x004C + +#define VTG_HOST_ITS 0x0078 +#define VTG_HOST_ITS_BCLR 0x007C +#define VTG_HOST_ITM_BCLR 0x0088 +#define VTG_HOST_ITM_BSET 0x008C + +#define VTG_H_HD_1 0x00C0 +#define VTG_TOP_V_VD_1 0x00C4 +#define VTG_BOT_V_VD_1 0x00C8 +#define VTG_TOP_V_HD_1 0x00CC +#define VTG_BOT_V_HD_1 0x00D0 + +#define VTG_H_HD_2 0x00E0 +#define VTG_TOP_V_VD_2 0x00E4 +#define VTG_BOT_V_VD_2 0x00E8 +#define VTG_TOP_V_HD_2 0x00EC +#define VTG_BOT_V_HD_2 0x00F0 + +#define VTG_H_HD_3 0x0100 +#define VTG_TOP_V_VD_3 0x0104 +#define VTG_BOT_V_VD_3 0x0108 +#define VTG_TOP_V_HD_3 0x010C +#define VTG_BOT_V_HD_3 0x0110 + +#define VTG_IRQ_BOTTOM BIT(0) +#define VTG_IRQ_TOP BIT(1) +#define VTG_IRQ_MASK (VTG_IRQ_TOP | VTG_IRQ_BOTTOM) + +/* delay introduced by the Arbitrary Waveform Generator in nb of pixels */ +#define AWG_DELAY_HD (-9) +#define AWG_DELAY_ED (-8) +#define AWG_DELAY_SD (-7) + +LIST_HEAD(vtg_lookup); + +/** + * STI VTG structure + * + * @dev: pointer to device driver + * @data: data associated to the device + * @irq: VTG irq + * @type: VTG type (main or aux) + * @notifier_list: notifier callback + * @crtc_id: the crtc id for vblank event + * @slave: slave vtg + * @link: List node to link the structure in lookup list + */ +struct sti_vtg { + struct device *dev; + struct device_node *np; + void __iomem *regs; + int irq; + u32 irq_status; + struct raw_notifier_head notifier_list; + int crtc_id; + struct sti_vtg *slave; + struct list_head link; +}; + +static void vtg_register(struct sti_vtg *vtg) +{ + list_add_tail(&vtg->link, &vtg_lookup); +} + +struct sti_vtg *of_vtg_find(struct device_node *np) +{ + struct sti_vtg *vtg; + + list_for_each_entry(vtg, &vtg_lookup, link) { + if (vtg->np == np) + return vtg; + } + return NULL; +} + +static void vtg_reset(struct sti_vtg *vtg) +{ + /* reset slave and then master */ + if (vtg->slave) + vtg_reset(vtg->slave); + + writel(1, vtg->regs + VTG_DRST_AUTOC); +} + +static void vtg_set_mode(struct sti_vtg *vtg, + int type, const struct drm_display_mode *mode) +{ + u32 tmp; + + if (vtg->slave) + vtg_set_mode(vtg->slave, VTG_TYPE_SLAVE_BY_EXT0, mode); + + writel(mode->htotal, vtg->regs + VTG_CLKLN); + writel(mode->vtotal * 2, vtg->regs + VTG_HLFLN); + + tmp = (mode->vtotal - mode->vsync_start + 1) << 16; + tmp |= mode->htotal - mode->hsync_start; + writel(tmp, vtg->regs + VTG_VID_TFO); + writel(tmp, vtg->regs + VTG_VID_BFO); + + tmp = (mode->vdisplay + mode->vtotal - mode->vsync_start + 1) << 16; + tmp |= mode->hdisplay + mode->htotal - mode->hsync_start; + writel(tmp, vtg->regs + VTG_VID_TFS); + writel(tmp, vtg->regs + VTG_VID_BFS); + + /* prepare VTG set 1 and 2 for HDMI and VTG set 3 for HD DAC */ + tmp = (mode->hsync_end - mode->hsync_start) << 16; + writel(tmp, vtg->regs + VTG_H_HD_1); + writel(tmp, vtg->regs + VTG_H_HD_2); + + tmp = (mode->vsync_end - mode->vsync_start + 1) << 16; + tmp |= 1; + writel(tmp, vtg->regs + VTG_TOP_V_VD_1); + writel(tmp, vtg->regs + VTG_BOT_V_VD_1); + writel(0, vtg->regs + VTG_TOP_V_HD_1); + writel(0, vtg->regs + VTG_BOT_V_HD_1); + + /* prepare VTG set 2 for for HD DCS */ + writel(tmp, vtg->regs + VTG_TOP_V_VD_2); + writel(tmp, vtg->regs + VTG_BOT_V_VD_2); + writel(0, vtg->regs + VTG_TOP_V_HD_2); + writel(0, vtg->regs + VTG_BOT_V_HD_2); + + /* prepare VTG set 3 for HD Analog in HD mode */ + tmp = (mode->hsync_end - mode->hsync_start + AWG_DELAY_HD) << 16; + tmp |= mode->htotal + AWG_DELAY_HD; + writel(tmp, vtg->regs + VTG_H_HD_3); + + tmp = (mode->vsync_end - mode->vsync_start) << 16; + tmp |= mode->vtotal; + writel(tmp, vtg->regs + VTG_TOP_V_VD_3); + writel(tmp, vtg->regs + VTG_BOT_V_VD_3); + + tmp = (mode->htotal + AWG_DELAY_HD) << 16; + tmp |= mode->htotal + AWG_DELAY_HD; + writel(tmp, vtg->regs + VTG_TOP_V_HD_3); + writel(tmp, vtg->regs + VTG_BOT_V_HD_3); + + /* mode */ + writel(type, vtg->regs + VTG_MODE); +} + +static void vtg_enable_irq(struct sti_vtg *vtg) +{ + /* clear interrupt status and mask */ + writel(0xFFFF, vtg->regs + VTG_HOST_ITS_BCLR); + writel(0xFFFF, vtg->regs + VTG_HOST_ITM_BCLR); + writel(VTG_IRQ_MASK, vtg->regs + VTG_HOST_ITM_BSET); +} + +void sti_vtg_set_config(struct sti_vtg *vtg, + const struct drm_display_mode *mode) +{ + /* write configuration */ + vtg_set_mode(vtg, VTG_TYPE_MASTER, mode); + + vtg_reset(vtg); + + /* enable irq for the vtg vblank synchro */ + if (vtg->slave) + vtg_enable_irq(vtg->slave); + else + vtg_enable_irq(vtg); +} + +/** + * sti_vtg_get_line_number + * + * @mode: display mode to be used + * @y: line + * + * Return the line number according to the display mode taking + * into account the Sync and Back Porch information. + * Video frame line numbers start at 1, y starts at 0. + * In interlaced modes the start line is the field line number of the odd + * field, but y is still defined as a progressive frame. + */ +u32 sti_vtg_get_line_number(struct drm_display_mode mode, int y) +{ + u32 start_line = mode.vtotal - mode.vsync_start + 1; + + if (mode.flags & DRM_MODE_FLAG_INTERLACE) + start_line *= 2; + + return start_line + y; +} + +/** + * sti_vtg_get_pixel_number + * + * @mode: display mode to be used + * @x: row + * + * Return the pixel number according to the display mode taking + * into account the Sync and Back Porch information. + * Pixels are counted from 0. + */ +u32 sti_vtg_get_pixel_number(struct drm_display_mode mode, int x) +{ + return mode.htotal - mode.hsync_start + x; +} + +int sti_vtg_register_client(struct sti_vtg *vtg, + struct notifier_block *nb, int crtc_id) +{ + if (vtg->slave) + return sti_vtg_register_client(vtg->slave, nb, crtc_id); + + vtg->crtc_id = crtc_id; + return raw_notifier_chain_register(&vtg->notifier_list, nb); +} + +int sti_vtg_unregister_client(struct sti_vtg *vtg, struct notifier_block *nb) +{ + if (vtg->slave) + return sti_vtg_unregister_client(vtg->slave, nb); + + return raw_notifier_chain_unregister(&vtg->notifier_list, nb); +} + +static irqreturn_t vtg_irq_thread(int irq, void *arg) +{ + struct sti_vtg *vtg = arg; + u32 event; + + event = (vtg->irq_status & VTG_IRQ_TOP) ? + VTG_TOP_FIELD_EVENT : VTG_BOTTOM_FIELD_EVENT; + + raw_notifier_call_chain(&vtg->notifier_list, event, &vtg->crtc_id); + + return IRQ_HANDLED; +} + +static irqreturn_t vtg_irq(int irq, void *arg) +{ + struct sti_vtg *vtg = arg; + + vtg->irq_status = readl(vtg->regs + VTG_HOST_ITS); + + writel(vtg->irq_status, vtg->regs + VTG_HOST_ITS_BCLR); + + /* force sync bus write */ + readl(vtg->regs + VTG_HOST_ITS); + + return IRQ_WAKE_THREAD; +} + +static int vtg_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np; + struct sti_vtg *vtg; + struct resource *res; + char irq_name[32]; + int ret; + + vtg = devm_kzalloc(dev, sizeof(*vtg), GFP_KERNEL); + if (!vtg) + return -ENOMEM; + + vtg->dev = dev; + vtg->np = pdev->dev.of_node; + + /* Get Memory ressources */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + DRM_ERROR("Get memory resource failed\n"); + return -ENOMEM; + } + vtg->regs = devm_ioremap_nocache(dev, res->start, resource_size(res)); + + np = of_parse_phandle(pdev->dev.of_node, "st,slave", 0); + if (np) { + vtg->slave = of_vtg_find(np); + + if (!vtg->slave) + return -EPROBE_DEFER; + } else { + vtg->irq = platform_get_irq(pdev, 0); + if (IS_ERR_VALUE(vtg->irq)) { + DRM_ERROR("Failed to get VTG interrupt\n"); + return vtg->irq; + } + + snprintf(irq_name, sizeof(irq_name), "vsync-%s", + dev_name(vtg->dev)); + + RAW_INIT_NOTIFIER_HEAD(&vtg->notifier_list); + + ret = devm_request_threaded_irq(dev, vtg->irq, vtg_irq, + vtg_irq_thread, IRQF_ONESHOT, irq_name, vtg); + if (IS_ERR_VALUE(ret)) { + DRM_ERROR("Failed to register VTG interrupt\n"); + return ret; + } + } + + vtg_register(vtg); + platform_set_drvdata(pdev, vtg); + + DRM_INFO("%s %s\n", __func__, dev_name(vtg->dev)); + + return 0; +} + +static int vtg_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id vtg_of_match[] = { + { .compatible = "st,vtg", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vtg_of_match); + +struct platform_driver sti_vtg_driver = { + .driver = { + .name = "sti-vtg", + .owner = THIS_MODULE, + .of_match_table = vtg_of_match, + }, + .probe = vtg_probe, + .remove = vtg_remove, +}; + +module_platform_driver(sti_vtg_driver); diff --git a/drivers/gpu/drm/sti/sti_vtg.h b/drivers/gpu/drm/sti/sti_vtg.h new file mode 100644 index 0000000..e84d23f --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_VTG_H_ +#define _STI_VTG_H_ + +#define VTG_TOP_FIELD_EVENT 1 +#define VTG_BOTTOM_FIELD_EVENT 2 + +struct sti_vtg; +struct drm_display_mode; +struct notifier_block; + +struct sti_vtg *of_vtg_find(struct device_node *np); +void sti_vtg_set_config(struct sti_vtg *vtg, + const struct drm_display_mode *mode); +int sti_vtg_register_client(struct sti_vtg *vtg, + struct notifier_block *nb, int crtc_id); +int sti_vtg_unregister_client(struct sti_vtg *vtg, + struct notifier_block *nb); + +u32 sti_vtg_get_line_number(struct drm_display_mode mode, int y); +u32 sti_vtg_get_pixel_number(struct drm_display_mode mode, int x); + +#endif
Video Traffic Advance Communication Rx and Tx drivers are designed for inter-die communication.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Makefile | 3 +- drivers/gpu/drm/sti/sti_vtac.c | 211 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/sti/sti_vtac.c
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 3658a13e..52e2e80 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_DRM_STI) += \ - sti_vtg.o + sti_vtg.o \ + sti_vtac.o diff --git a/drivers/gpu/drm/sti/sti_vtac.c b/drivers/gpu/drm/sti/sti_vtac.c new file mode 100644 index 0000000..5226a04 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtac.c @@ -0,0 +1,211 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include <drm/drmP.h> + +/* registers offset */ +#define VTAC_CONFIG 0x00 +#define VTAC_RX_FIFO_CONFIG 0x04 +#define VTAC_FIFO_CONFIG_VAL 0x04 + +#define VTAC_SYS_CFG8521 0x824 +#define VTAC_SYS_CFG8522 0x828 + +/* Number of phyts per pixel */ +#define VTAC_2_5_PPP 0x0005 +#define VTAC_3_PPP 0x0006 +#define VTAC_4_PPP 0x0008 +#define VTAC_5_PPP 0x000A +#define VTAC_6_PPP 0x000C +#define VTAC_13_PPP 0x001A +#define VTAC_14_PPP 0x001C +#define VTAC_15_PPP 0x001E +#define VTAC_16_PPP 0x0020 +#define VTAC_17_PPP 0x0022 +#define VTAC_18_PPP 0x0024 + +/* enable bits */ +#define VTAC_ENABLE 0x3003 + +#define VTAC_TX_PHY_ENABLE_CLK_PHY BIT(0) +#define VTAC_TX_PHY_ENABLE_CLK_DLL BIT(1) +#define VTAC_TX_PHY_PLL_NOT_OSC_MODE BIT(3) +#define VTAC_TX_PHY_RST_N_DLL_SWITCH BIT(4) +#define VTAC_TX_PHY_PROG_N3 BIT(9) + + +/** + * VTAC mode structure + * + * @vid_in_width: Video Data Resolution + * @phyts_width: Width of phyt buses(phyt low and phyt high). + * @phyts_per_pixel: Number of phyts sent per pixel + */ +struct sti_vtac_mode { + u32 vid_in_width; + u32 phyts_width; + u32 phyts_per_pixel; +}; + +static const struct sti_vtac_mode vtac_mode_main = {0x2, 0x2, VTAC_5_PPP}; +static const struct sti_vtac_mode vtac_mode_aux = {0x1, 0x0, VTAC_17_PPP}; + +/** + * VTAC structure + * + * @dev: pointer to device structure + * @regs: ioremapped registers for RX and TX devices + * @phy_regs: phy registers for TX device + * @clk: clock + * @mode: main or auxillary configuration mode + */ +struct sti_vtac { + struct device *dev; + void __iomem *regs; + void __iomem *phy_regs; + struct clk *clk; + const struct sti_vtac_mode *mode; +}; + +static void sti_vtac_rx_set_config(struct sti_vtac *vtac) +{ + u32 config; + + /* Enable VTAC clock */ + if (clk_prepare_enable(vtac->clk)) + DRM_ERROR("Failed to prepare/enable vtac_rx clock.\n"); + + writel(VTAC_FIFO_CONFIG_VAL, vtac->regs + VTAC_RX_FIFO_CONFIG); + + config = VTAC_ENABLE; + config |= vtac->mode->vid_in_width << 4; + config |= vtac->mode->phyts_width << 16; + config |= vtac->mode->phyts_per_pixel << 23; + writel(config, vtac->regs + VTAC_CONFIG); +} + +static void sti_vtac_tx_set_config(struct sti_vtac *vtac) +{ + u32 phy_config; + u32 config; + + /* Enable VTAC clock */ + if (clk_prepare_enable(vtac->clk)) + DRM_ERROR("Failed to prepare/enable vtac_tx clock.\n"); + + /* Configure vtac phy */ + phy_config = 0x00000000; + writel(phy_config, vtac->phy_regs + VTAC_SYS_CFG8522); + phy_config = VTAC_TX_PHY_ENABLE_CLK_PHY; + writel(phy_config, vtac->phy_regs + VTAC_SYS_CFG8521); + phy_config = readl(vtac->phy_regs + VTAC_SYS_CFG8521); + phy_config |= VTAC_TX_PHY_PROG_N3; + writel(phy_config, vtac->phy_regs + VTAC_SYS_CFG8521); + phy_config = readl(vtac->phy_regs + VTAC_SYS_CFG8521); + phy_config |= VTAC_TX_PHY_ENABLE_CLK_DLL; + writel(phy_config, vtac->phy_regs + VTAC_SYS_CFG8521); + phy_config = readl(vtac->phy_regs + VTAC_SYS_CFG8521); + phy_config |= VTAC_TX_PHY_RST_N_DLL_SWITCH; + writel(phy_config, vtac->phy_regs + VTAC_SYS_CFG8521); + phy_config = readl(vtac->phy_regs + VTAC_SYS_CFG8521); + phy_config |= VTAC_TX_PHY_PLL_NOT_OSC_MODE; + writel(phy_config, vtac->phy_regs + VTAC_SYS_CFG8521); + + /* Configure vtac tx */ + config = VTAC_ENABLE; + config |= vtac->mode->vid_in_width << 4; + config |= vtac->mode->phyts_width << 16; + config |= vtac->mode->phyts_per_pixel << 23; + writel(config, vtac->regs + VTAC_CONFIG); +} + +static const struct of_device_id vtac_of_match[] = { + { + .compatible = "st,vtac-main", + .data = &vtac_mode_main, + }, { + .compatible = "st,vtac-aux", + .data = &vtac_mode_aux, + }, { + /* end node */ + } +}; +MODULE_DEVICE_TABLE(of, vtac_of_match); + +static int sti_vtac_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + const struct of_device_id *id; + struct sti_vtac *vtac; + struct resource *res; + + vtac = devm_kzalloc(dev, sizeof(*vtac), GFP_KERNEL); + if (!vtac) + return -ENOMEM; + + vtac->dev = dev; + + id = of_match_node(vtac_of_match, np); + if (!id) + return -ENOMEM; + + vtac->mode = id->data; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + DRM_ERROR("Invalid resource\n"); + return -ENOMEM; + } + vtac->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(vtac->regs)) + return PTR_ERR(vtac->regs); + + + vtac->clk = devm_clk_get(dev, "vtac"); + if (IS_ERR(vtac->clk)) { + DRM_ERROR("Cannot get vtac clock\n"); + return PTR_ERR(vtac->clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + vtac->phy_regs = devm_ioremap_nocache(dev, res->start, + resource_size(res)); + sti_vtac_tx_set_config(vtac); + } else { + + sti_vtac_rx_set_config(vtac); + } + + platform_set_drvdata(pdev, vtac); + DRM_INFO("%s %s\n", __func__, dev_name(vtac->dev)); + + return 0; +} + +static int sti_vtac_remove(struct platform_device *pdev) +{ + return 0; +} + +struct platform_driver sti_vtac_driver = { + .driver = { + .name = "sti-vtac", + .owner = THIS_MODULE, + .of_match_table = vtac_of_match, + }, + .probe = sti_vtac_probe, + .remove = sti_vtac_remove, +}; + +module_platform_driver(sti_vtac_driver);
Add driver to support analog TV ouput.
As HDMI driver HDA use sti_connector structure to provide helper functions.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Makefile | 3 +- drivers/gpu/drm/sti/sti_hda.c | 723 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 725 insertions(+), 1 deletion(-) 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 5521a85..df47171 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -3,4 +3,5 @@ obj-$(CONFIG_DRM_STI) += \ sti_vtac.o \ sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ - sti_hdmi_tx3g4c28phy.o \ No newline at end of file + 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 new file mode 100644 index 0000000..9a6278d --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hda.c @@ -0,0 +1,723 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Author: Fabien Dessenne fabien.dessenne@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <drm/drmP.h> + +#include "sti_drm_connector.h" +#include "sti_drm_encoder.h" + +/* HDformatter registers */ +#define HDA_ANA_CFG 0x0000 +#define HDA_ANA_SCALE_CTRL_Y 0x0004 +#define HDA_ANA_SCALE_CTRL_CB 0x0008 +#define HDA_ANA_SCALE_CTRL_CR 0x000C +#define HDA_ANA_ANC_CTRL 0x0010 +#define HDA_ANA_SRC_Y_CFG 0x0014 +#define HDA_COEFF_Y_PH1_TAP123 0x0018 +#define HDA_COEFF_Y_PH1_TAP456 0x001C +#define HDA_COEFF_Y_PH2_TAP123 0x0020 +#define HDA_COEFF_Y_PH2_TAP456 0x0024 +#define HDA_COEFF_Y_PH3_TAP123 0x0028 +#define HDA_COEFF_Y_PH3_TAP456 0x002C +#define HDA_COEFF_Y_PH4_TAP123 0x0030 +#define HDA_COEFF_Y_PH4_TAP456 0x0034 +#define HDA_ANA_SRC_C_CFG 0x0040 +#define HDA_COEFF_C_PH1_TAP123 0x0044 +#define HDA_COEFF_C_PH1_TAP456 0x0048 +#define HDA_COEFF_C_PH2_TAP123 0x004C +#define HDA_COEFF_C_PH2_TAP456 0x0050 +#define HDA_COEFF_C_PH3_TAP123 0x0054 +#define HDA_COEFF_C_PH3_TAP456 0x0058 +#define HDA_COEFF_C_PH4_TAP123 0x005C +#define HDA_COEFF_C_PH4_TAP456 0x0060 +#define HDA_SYNC_AWGI 0x0300 + +/* HDA_ANA_CFG */ +#define CFG_AWG_ASYNC_EN BIT(0) +#define CFG_AWG_ASYNC_HSYNC_MTD BIT(1) +#define CFG_AWG_ASYNC_VSYNC_MTD BIT(2) +#define CFG_AWG_SYNC_DEL BIT(3) +#define CFG_AWG_FLTR_MODE_SHIFT 4 +#define CFG_AWG_FLTR_MODE_MASK (0xF << CFG_AWG_FLTR_MODE_SHIFT) +#define CFG_AWG_FLTR_MODE_SD (0 << CFG_AWG_FLTR_MODE_SHIFT) +#define CFG_AWG_FLTR_MODE_ED (1 << CFG_AWG_FLTR_MODE_SHIFT) +#define CFG_AWG_FLTR_MODE_HD (2 << CFG_AWG_FLTR_MODE_SHIFT) +#define CFG_SYNC_ON_PBPR_MASK BIT(8) +#define CFG_PREFILTER_EN_MASK BIT(9) +#define CFG_PBPR_SYNC_OFF_SHIFT 16 +#define CFG_PBPR_SYNC_OFF_MASK (0x7FF << CFG_PBPR_SYNC_OFF_SHIFT) +#define CFG_PBPR_SYNC_OFF_VAL 0x117 /* Voltage dependent. stiH416 */ + +/* Default scaling values */ +#define SCALE_CTRL_Y_DFLT 0x00C50256 +#define SCALE_CTRL_CB_DFLT 0x00DB0249 +#define SCALE_CTRL_CR_DFLT 0x00DB0249 + +/* Video DACs control */ +#define VIDEO_DACS_CONTROL_MASK 0x0FFF +#define VIDEO_DACS_CONTROL_SYSCFG2535 0x085C /* for stih416 */ +#define DAC_CFG_HD_OFF_SHIFT 5 +#define DAC_CFG_HD_OFF_MASK (0x7 << DAC_CFG_HD_OFF_SHIFT) +#define VIDEO_DACS_CONTROL_SYSCFG5072 0x0120 /* for stih407 */ +#define DAC_CFG_HD_HZUVW_OFF_MASK BIT(1) + + +/* Upsampler values for the alternative 2X Filter */ +#define SAMPLER_COEF_NB 8 +#define HDA_ANA_SRC_Y_CFG_ALT_2X 0x01130000 +static u32 coef_y_alt_2x[] = { + 0x00FE83FB, 0x1F900401, 0x00000000, 0x00000000, + 0x00F408F9, 0x055F7C25, 0x00000000, 0x00000000 +}; + +#define HDA_ANA_SRC_C_CFG_ALT_2X 0x01750004 +static u32 coef_c_alt_2x[] = { + 0x001305F7, 0x05274BD0, 0x00000000, 0x00000000, + 0x0004907C, 0x09C80B9D, 0x00000000, 0x00000000 +}; + +/* Upsampler values for the 4X Filter */ +#define HDA_ANA_SRC_Y_CFG_4X 0x01ED0005 +#define HDA_ANA_SRC_C_CFG_4X 0x01ED0004 +static u32 coef_yc_4x[] = { + 0x00FC827F, 0x008FE20B, 0x00F684FC, 0x050F7C24, + 0x00F4857C, 0x0A1F402E, 0x00FA027F, 0x0E076E1D +}; + +/* AWG instructions for some video modes */ +#define AWG_MAX_INST 64 + +/* 720p@50 */ +static u32 AWGi_720p_50[] = { + 0x00000971, 0x00000C26, 0x0000013B, 0x00000CDA, + 0x00000104, 0x00000E7E, 0x00000E7F, 0x0000013B, + 0x00000D8E, 0x00000104, 0x00001804, 0x00000971, + 0x00000C26, 0x0000003B, 0x00000FB4, 0x00000FB5, + 0x00000104, 0x00001AE8 +}; + +#define NN_720p_50 ARRAY_SIZE(AWGi_720p_50) + +/* 720p@60 */ +static u32 AWGi_720p_60[] = { + 0x00000971, 0x00000C26, 0x0000013B, 0x00000CDA, + 0x00000104, 0x00000E7E, 0x00000E7F, 0x0000013B, + 0x00000C44, 0x00000104, 0x00001804, 0x00000971, + 0x00000C26, 0x0000003B, 0x00000F0F, 0x00000F10, + 0x00000104, 0x00001AE8 +}; + +#define NN_720p_60 ARRAY_SIZE(AWGi_720p_60) + +/* 1080p@30 */ +static u32 AWGi_1080p_30[] = { + 0x00000971, 0x00000C2A, 0x0000013B, 0x00000C56, + 0x00000104, 0x00000FDC, 0x00000FDD, 0x0000013B, + 0x00000C2A, 0x00000104, 0x00001804, 0x00000971, + 0x00000C2A, 0x0000003B, 0x00000EBE, 0x00000EBF, + 0x00000EBF, 0x00000104, 0x00001A2F, 0x00001C4B, + 0x00001C52 +}; + +#define NN_1080p_30 ARRAY_SIZE(AWGi_1080p_30) + +/* 1080p@25 */ +static u32 AWGi_1080p_25[] = { + 0x00000971, 0x00000C2A, 0x0000013B, 0x00000C56, + 0x00000104, 0x00000FDC, 0x00000FDD, 0x0000013B, + 0x00000DE2, 0x00000104, 0x00001804, 0x00000971, + 0x00000C2A, 0x0000003B, 0x00000F51, 0x00000F51, + 0x00000F52, 0x00000104, 0x00001A2F, 0x00001C4B, + 0x00001C52 +}; + +#define NN_1080p_25 ARRAY_SIZE(AWGi_1080p_25) + +/* 1080p@24 */ +static u32 AWGi_1080p_24[] = { + 0x00000971, 0x00000C2A, 0x0000013B, 0x00000C56, + 0x00000104, 0x00000FDC, 0x00000FDD, 0x0000013B, + 0x00000E50, 0x00000104, 0x00001804, 0x00000971, + 0x00000C2A, 0x0000003B, 0x00000F76, 0x00000F76, + 0x00000F76, 0x00000104, 0x00001A2F, 0x00001C4B, + 0x00001C52 +}; + +#define NN_1080p_24 ARRAY_SIZE(AWGi_1080p_24) + +/* 720x480p@60 */ +static u32 AWGi_720x480p_60[] = { + 0x00000904, 0x00000F18, 0x0000013B, 0x00001805, + 0x00000904, 0x00000C3D, 0x0000003B, 0x00001A06 +}; + +#define NN_720x480p_60 ARRAY_SIZE(AWGi_720x480p_60) + +/* Video mode category */ +enum sti_hda_vid_cat { + VID_SD, + VID_ED, + VID_HD_74M, + VID_HD_148M +}; + +struct sti_hda_video_config { + struct drm_display_mode mode; + u32 *awg_instr; + int nb_instr; + enum sti_hda_vid_cat vid_cat; +}; + +/* HD analog supported modes + * Interlaced modes may be added when supported by the whole display chain + */ +static const struct sti_hda_video_config hda_supported_modes[] = { + /* 1080p30 74.250Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_30, NN_1080p_30, VID_HD_74M}, + /* 1080p30 74.176Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74176, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_30, NN_1080p_30, VID_HD_74M}, + /* 1080p24 74.250Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2558, + 2602, 2750, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_24, NN_1080p_24, VID_HD_74M}, + /* 1080p24 74.176Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74176, 1920, 2558, + 2602, 2750, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_24, NN_1080p_24, VID_HD_74M}, + /* 1080p25 74.250Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448, + 2492, 2640, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_25, NN_1080p_25, VID_HD_74M}, + /* 720p60 74.250Mhz */ + {{DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390, + 1430, 1650, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_720p_60, NN_720p_60, VID_HD_74M}, + /* 720p60 74.176Mhz */ + {{DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74176, 1280, 1390, + 1430, 1650, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_720p_60, NN_720p_60, VID_HD_74M}, + /* 720p50 74.250Mhz */ + {{DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1720, + 1760, 1980, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_720p_50, NN_720p_50, VID_HD_74M}, + /* 720x480p60 27.027Mhz */ + {{DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27027, 720, 736, + 798, 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC)}, + AWGi_720x480p_60, NN_720x480p_60, VID_ED}, + /* 720x480p60 27.000Mhz */ + {{DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, + 798, 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC)}, + AWGi_720x480p_60, NN_720x480p_60, VID_ED} +}; + +/** + * STI hd analog structure + * + * @dev: driver device + * @drm_dev: pointer to drm device + * @mode: current display mode selected + * @regs: HD analog register + * @video_dacs_ctrl: video DACS control register + * @enabled: true if HD analog is enabled else false + */ +struct sti_hda { + struct device dev; + struct drm_device *drm_dev; + struct drm_display_mode mode; + void __iomem *regs; + void __iomem *video_dacs_ctrl; + struct clk *clk_pix; + struct clk *clk_hddac; + bool enabled; +}; + +static u32 hda_read(struct sti_hda *hda, int offset) +{ + return readl(hda->regs + offset); +} + +static void hda_write(struct sti_hda *hda, u32 val, int offset) +{ + writel(val, hda->regs + offset); +} + +/** + * Search for a video mode in the supported modes table + * + * @mode: mode being searched + * @idx: index of the found mode + * + * Return true if mode is found + */ +static bool hda_get_mode_idx(struct drm_display_mode mode, int *idx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(hda_supported_modes); i++) + if (drm_mode_equal(&hda_supported_modes[i].mode, &mode)) { + *idx = i; + return true; + } + return false; +} + +/** + * Enable the HD DACS + * + * @hda: pointer to HD analog structure + * @enable: true if HD DACS need to be enabled, else false + */ +static void hda_enable_hd_dacs(struct sti_hda *hda, bool enable) +{ + u32 mask; + + if (hda->video_dacs_ctrl) { + u32 val; + + switch ((u32)hda->video_dacs_ctrl & VIDEO_DACS_CONTROL_MASK) { + case VIDEO_DACS_CONTROL_SYSCFG2535: + mask = DAC_CFG_HD_OFF_MASK; + break; + case VIDEO_DACS_CONTROL_SYSCFG5072: + mask = DAC_CFG_HD_HZUVW_OFF_MASK; + break; + default: + DRM_INFO("Video DACS control register not supported!"); + return; + } + + val = readl(hda->video_dacs_ctrl); + if (enable) + val &= ~mask; + else + val |= mask; + + writel(val, hda->video_dacs_ctrl); + } + +} + +/** + * Configure AWG, writing instructions + * + * @hda: pointer to HD analog structure + * @awg_instr: pointer to AWG instructions table + * @nb: nb of AWG instructions + */ +static void sti_hda_configure_awg(struct sti_hda *hda, u32 *awg_instr, int nb) +{ + unsigned int i; + + DRM_DEBUG_DRIVER("\n"); + + for (i = 0; i < nb; i++) + hda_write(hda, awg_instr[i], HDA_SYNC_AWGI + i * 4); + for (i = nb; i < AWG_MAX_INST; i++) + hda_write(hda, 0, HDA_SYNC_AWGI + i * 4); +} + +/** + * 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; + unsigned int i; + int count = 0; + + DRM_DEBUG_DRIVER("\n"); + + for (i = 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); + + /* the first mode is the preferred mode */ + if (i == 0) + 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_start(struct drm_connector *connector) +{ + struct sti_connector *sti_connector = to_sti_connector(connector); + struct sti_hda *hda = dev_get_drvdata(sti_connector->dev); + 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 */ + hda_write(hda, SCALE_CTRL_Y_DFLT, HDA_ANA_SCALE_CTRL_Y); + hda_write(hda, SCALE_CTRL_CB_DFLT, HDA_ANA_SCALE_CTRL_CB); + hda_write(hda, SCALE_CTRL_CR_DFLT, HDA_ANA_SCALE_CTRL_CR); + + /* Configure sampler */ + hda_write(hda , src_filter_y, HDA_ANA_SRC_Y_CFG); + hda_write(hda, src_filter_c, HDA_ANA_SRC_C_CFG); + for (i = 0; i < SAMPLER_COEF_NB; i++) { + hda_write(hda, coef_y[i], HDA_COEFF_Y_PH1_TAP123 + i * 4); + hda_write(hda, coef_c[i], HDA_COEFF_C_PH1_TAP123 + i * 4); + } + + /* Configure main HDFormatter */ + val = 0; + val |= (hda->mode.flags & DRM_MODE_FLAG_INTERLACE) ? + 0 : CFG_AWG_ASYNC_VSYNC_MTD; + val |= (CFG_PBPR_SYNC_OFF_VAL << CFG_PBPR_SYNC_OFF_SHIFT); + val |= filter_mode; + hda_write(hda, val, HDA_ANA_CFG); + + /* Configure AWG */ + sti_hda_configure_awg(hda, hda_supported_modes[mode_idx].awg_instr, + hda_supported_modes[mode_idx].nb_instr); + + /* Enable AWG */ + val = hda_read(hda, HDA_ANA_CFG); + val |= CFG_AWG_ASYNC_EN; + hda_write(hda, val, HDA_ANA_CFG); + + return 0; +} + +static void sti_hda_stop(struct drm_connector *connector) +{ + struct sti_connector *sti_connector = to_sti_connector(connector); + struct sti_hda *hda = dev_get_drvdata(sti_connector->dev); + u32 val; + + if (!hda->enabled) + return; + + DRM_DEBUG_DRIVER("\n"); + + /* Disable HD DAC and AWG */ + val = hda_read(hda, HDA_ANA_CFG); + val &= ~CFG_AWG_ASYNC_EN; + hda_write(hda, val, HDA_ANA_CFG); + + hda_enable_hd_dacs(hda, false); + + /* Disable/unprepare hda clock */ + clk_disable_unprepare(hda->clk_hddac); + clk_disable_unprepare(hda->clk_pix); + + hda->enabled = false; +} + +#define CLK_TOLERANCE_HZ 50 + +static int sti_hda_check_mode(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_connector *sti_connector = to_sti_connector(connector); + struct sti_hda *hda = dev_get_drvdata(sti_connector->dev); + 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 MODE_BAD; + } else { + result = clk_round_rate(hda->clk_pix, target); + + DRM_DEBUG_DRIVER("target rate = %d => available rate = %d\n", + target, result); + + if ((result < target_min) || (result > target_max)) { + DRM_DEBUG_DRIVER("hda pixclk=%d not supported\n", + target); + return MODE_BAD; + } + } + + return MODE_OK; +} + +static int sti_hda_set_mode(struct drm_connector *connector, + struct drm_display_mode *mode, bool main_path) +{ + struct sti_connector *sti_connector = to_sti_connector(connector); + struct sti_hda *hda = dev_get_drvdata(sti_connector->dev); + 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; +} + +static int sti_hda_detect(struct drm_connector *connector) +{ + return connector_status_connected; +} + +static bool sti_hda_is_enabled(struct drm_connector *connector) +{ + struct sti_connector *sti_connector = to_sti_connector(connector); + struct sti_hda *hda = dev_get_drvdata(sti_connector->dev); + + return hda->enabled; +} + +static void sti_hda_prepare(struct drm_connector *connector) +{ + struct sti_connector *sti_connector = to_sti_connector(connector); + struct sti_hda *hda = dev_get_drvdata(sti_connector->dev); + + DRM_DEBUG_DRIVER("\n"); + + /* reset HDF */ + hda_write(hda, 0, HDA_ANA_CFG); + hda_write(hda, 0, HDA_ANA_ANC_CTRL); +} + +static int sti_hda_bind(struct device *dev, struct device *master, void *data) +{ + struct sti_hda *hda = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + struct drm_encoder *encoder; + struct sti_connector *connector; + + /* Set the drm device handle */ + hda->drm_dev = drm_dev; + + encoder = devm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL); + if (!encoder) + return -ENOMEM; + + encoder->possible_crtcs = ENCODER_MAIN_CRTC_MASK; + encoder->possible_clones = 1 << STI_CONNECTOR_HDMI; + + connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL); + if (!connector) + return -ENOMEM; + + connector->dev = dev; + connector->master = master; + connector->encoder = encoder; + 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->type = STI_CONNECTOR_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) +{ + struct device *dev = &pdev->dev; + struct sti_hda *hda; + struct resource *res; + + DRM_INFO("%s\n", __func__); + + hda = devm_kzalloc(dev, sizeof(*hda), GFP_KERNEL); + if (!hda) + return -ENOMEM; + + hda->dev = pdev->dev; + + /* Get resources */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hda-reg"); + if (!res) { + DRM_ERROR("Invalid hda resource\n"); + return -ENOMEM; + } + hda->regs = devm_ioremap_nocache(dev, res->start, resource_size(res)); + if (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, "pix"); + if (IS_ERR(hda->clk_pix)) { + DRM_ERROR("Cannot get hda_pix clock\n"); + return PTR_ERR(hda->clk_pix); + } + + hda->clk_hddac = devm_clk_get(dev, "hddac"); + if (IS_ERR(hda->clk_hddac)) { + DRM_ERROR("Cannot get hda_hddac clock\n"); + return PTR_ERR(hda->clk_hddac); + } + + platform_set_drvdata(pdev, hda); + + return component_add(&pdev->dev, &sti_hda_ops); +} + +static int sti_hda_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sti_hda_ops); + return 0; +} + +static struct of_device_id hda_of_match[] = { + { .compatible = "st,stih416-hda", }, + { .compatible = "st,stih407-hda", }, + { /* end node */ } +}; +MODULE_DEVICE_TABLE(of, hda_of_match); + +struct platform_driver sti_hda_driver = { + .driver = { + .name = "sti-hda", + .owner = THIS_MODULE, + .of_match_table = hda_of_match, + }, + .probe = sti_hda_probe, + .remove = sti_hda_remove, +}; + +module_platform_driver(sti_hda_driver);
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.
TVout is the parent of HDMI and HDA drivers and bind them at runtime.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Makefile | 3 +- drivers/gpu/drm/sti/sti_tvout.c | 678 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_tvout.h | 18 ++ 3 files changed, 698 insertions(+), 1 deletion(-) 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 df47171..dcc9568 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -4,4 +4,5 @@ obj-$(CONFIG_DRM_STI) += \ sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ - sti_hda.o + sti_hda.o \ + sti_tvout.o \ No newline at end of file diff --git a/drivers/gpu/drm/sti/sti_tvout.c b/drivers/gpu/drm/sti/sti_tvout.c new file mode 100644 index 0000000..d778e35 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_tvout.c @@ -0,0 +1,678 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Vincent Abriou vincent.abriou@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "sti_drm_connector.h" +#include "sti_drm_encoder.h" +#include "sti_tvout.h" + +/* glue registers */ +#define TVO_CSC_MAIN_M0 0x000 +#define TVO_CSC_MAIN_M1 0x004 +#define TVO_CSC_MAIN_M2 0x008 +#define TVO_CSC_MAIN_M3 0x00c +#define TVO_CSC_MAIN_M4 0x010 +#define TVO_CSC_MAIN_M5 0x014 +#define TVO_CSC_MAIN_M6 0x018 +#define TVO_CSC_MAIN_M7 0x01c +#define TVO_MAIN_IN_VID_FORMAT 0x030 +#define TVO_CSC_AUX_M0 0x100 +#define TVO_CSC_AUX_M1 0x104 +#define TVO_CSC_AUX_M2 0x108 +#define TVO_CSC_AUX_M3 0x10c +#define TVO_CSC_AUX_M4 0x110 +#define TVO_CSC_AUX_M5 0x114 +#define TVO_CSC_AUX_M6 0x118 +#define TVO_CSC_AUX_M7 0x11c +#define TVO_AUX_IN_VID_FORMAT 0x130 +#define TVO_VIP_HDF 0x400 +#define TVO_HD_SYNC_SEL 0x418 +#define TVO_HD_DAC_CFG_OFF 0x420 +#define TVO_VIP_HDMI 0x500 +#define TVO_HDMI_FORCE_COLOR_0 0x504 +#define TVO_HDMI_FORCE_COLOR_1 0x508 +#define TVO_HDMI_CLIP_VALUE_B_CB 0x50c +#define TVO_HDMI_CLIP_VALUE_Y_G 0x510 +#define TVO_HDMI_CLIP_VALUE_R_CR 0x514 +#define TVO_HDMI_SYNC_SEL 0x518 +#define TVO_HDMI_DFV_OBS 0x540 + +#define TVO_IN_FMT_SIGNED BIT(0) +#define TVO_SYNC_EXT BIT(4) + +#define TVO_VIP_REORDER_R_SHIFT 24 +#define TVO_VIP_REORDER_G_SHIFT 20 +#define TVO_VIP_REORDER_B_SHIFT 16 +#define TVO_VIP_REORDER_MASK 0x3 +#define TVO_VIP_REORDER_Y_G_SEL 0 +#define TVO_VIP_REORDER_CB_B_SEL 1 +#define TVO_VIP_REORDER_CR_R_SEL 2 + +#define TVO_VIP_CLIP_SHIFT 8 +#define TVO_VIP_CLIP_MASK 0x7 +#define TVO_VIP_CLIP_DISABLED 0 +#define TVO_VIP_CLIP_EAV_SAV 1 +#define TVO_VIP_CLIP_LIMITED_RANGE_RGB_Y 2 +#define TVO_VIP_CLIP_LIMITED_RANGE_CB_CR 3 +#define TVO_VIP_CLIP_PROG_RANGE 4 + +#define TVO_VIP_RND_SHIFT 4 +#define TVO_VIP_RND_MASK 0x3 +#define TVO_VIP_RND_8BIT_ROUNDED 0 +#define TVO_VIP_RND_10BIT_ROUNDED 1 +#define TVO_VIP_RND_12BIT_ROUNDED 2 + +#define TVO_VIP_SEL_INPUT_MASK 0xf +#define TVO_VIP_SEL_INPUT_MAIN 0x0 +#define TVO_VIP_SEL_INPUT_AUX 0x8 +#define TVO_VIP_SEL_INPUT_FORCE_COLOR 0xf +#define TVO_VIP_SEL_INPUT_BYPASS_MASK 0x1 +#define TVO_VIP_SEL_INPUT_BYPASSED 1 + +#define TVO_SYNC_MAIN_VTG_SET_REF 0x00 +#define TVO_SYNC_MAIN_VTG_SET_1 0x01 +#define TVO_SYNC_MAIN_VTG_SET_2 0x02 +#define TVO_SYNC_MAIN_VTG_SET_3 0x03 +#define TVO_SYNC_MAIN_VTG_SET_4 0x04 +#define TVO_SYNC_MAIN_VTG_SET_5 0x05 +#define TVO_SYNC_MAIN_VTG_SET_6 0x06 +#define TVO_SYNC_AUX_VTG_SET_REF 0x10 +#define TVO_SYNC_AUX_VTG_SET_1 0x11 +#define TVO_SYNC_AUX_VTG_SET_2 0x12 +#define TVO_SYNC_AUX_VTG_SET_3 0x13 +#define TVO_SYNC_AUX_VTG_SET_4 0x14 +#define TVO_SYNC_AUX_VTG_SET_5 0x15 +#define TVO_SYNC_AUX_VTG_SET_6 0x16 + +#define TVO_SYNC_HD_DCS_SHIFT 8 + +/* 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 + */ +struct sti_tvout { + struct device *dev; + void __iomem *regs; + struct reset_control *reset; +}; + +/* preformatter conversion matrix */ +static const u32 rgb_to_ycbcr_601[8] = { + 0xF927082E, 0x04C9FEAB, 0x01D30964, 0xFA95FD3D, + 0x0000082E, 0x00002000, 0x00002000, 0x00000000 +}; + +/* 709 RGB to YCbCr */ +static const u32 rgb_to_ycbcr_709[8] = { + 0xF891082F, 0x0367FF40, 0x01280B71, 0xF9B1FE20, + 0x0000082F, 0x00002000, 0x00002000, 0x00000000 +}; + +static u32 tvout_read(struct sti_tvout *tvout, int offset) +{ + return readl(tvout->regs + offset); +} + +static void tvout_write(struct sti_tvout *tvout, u32 val, int offset) +{ + writel(val, tvout->regs + offset); +} + +/** + * Set the clipping mode of a VIP + * + * @tvout: tvout structure + * @cr_r: + * @y_g: + * @cb_b: + */ +static void tvout_vip_set_color_order(struct sti_tvout *tvout, + u32 cr_r, u32 y_g, u32 cb_b) +{ + u32 val = tvout_read(tvout, TVO_VIP_HDMI); + + val &= ~(TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_R_SHIFT); + val &= ~(TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_G_SHIFT); + val &= ~(TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_B_SHIFT); + val |= cr_r << TVO_VIP_REORDER_R_SHIFT; + val |= y_g << TVO_VIP_REORDER_G_SHIFT; + val |= cb_b << TVO_VIP_REORDER_B_SHIFT; + + tvout_write(tvout, val, TVO_VIP_HDMI); +} + +/** + * Set the clipping mode of a VIP + * + * @tvout: tvout structure + * @range: clipping range + */ +static void tvout_vip_set_clip_mode(struct sti_tvout *tvout, u32 range) +{ + u32 val = tvout_read(tvout, TVO_VIP_HDMI); + + val &= ~(TVO_VIP_CLIP_MASK << TVO_VIP_CLIP_SHIFT); + val |= range << TVO_VIP_CLIP_SHIFT; + tvout_write(tvout, val, TVO_VIP_HDMI); +} + +/** + * Set the rounded value of a VIP + * + * @tvout: tvout structure + * @rnd: rounded val per component + */ +static void tvout_vip_set_rnd(struct sti_tvout *tvout, u32 rnd) +{ + u32 val = tvout_read(tvout, TVO_VIP_HDMI); + + val &= ~(TVO_VIP_RND_MASK << TVO_VIP_RND_SHIFT); + val |= rnd << TVO_VIP_RND_SHIFT; + tvout_write(tvout, val, TVO_VIP_HDMI); +} + +/** + * Select the VIP input + * + * @tvout: tvout structure + * @sel_input: selected_input (main/aux + conv) + */ +static void tvout_vip_set_sel_input(struct sti_tvout *tvout, + bool main_path, + bool sel_input_logic_inverted, + enum sti_tvout_video_out_type video_out) +{ + u32 sel_input; + u32 val = tvout_read(tvout, TVO_VIP_HDMI); + + if (main_path) + sel_input = TVO_VIP_SEL_INPUT_MAIN; + else + sel_input = TVO_VIP_SEL_INPUT_AUX; + + switch (video_out) { + case STI_TVOUT_VIDEO_OUT_RGB: + sel_input |= TVO_VIP_SEL_INPUT_BYPASSED; + break; + case STI_TVOUT_VIDEO_OUT_YUV: + sel_input &= ~TVO_VIP_SEL_INPUT_BYPASSED; + break; + } + + /* on stih407 chip the sel_input bypass mode logic is inverted */ + if (sel_input_logic_inverted) + sel_input = sel_input ^ TVO_VIP_SEL_INPUT_BYPASS_MASK; + + val &= ~TVO_VIP_SEL_INPUT_MASK; + val |= sel_input; + tvout_write(tvout, val, TVO_VIP_HDMI); +} + +/** + * Select the input video signed or unsigned + * + * @tvout: tvout structure + * @in_vid_signed: used video input format + */ +static void tvout_vip_set_in_vid_fmt(struct sti_tvout *tvout, u32 in_vid_fmt) +{ + u32 val = tvout_read(tvout, TVO_VIP_HDMI); + + val &= ~TVO_IN_FMT_SIGNED; + val |= in_vid_fmt; + tvout_write(tvout, val, TVO_MAIN_IN_VID_FORMAT); +} + +/** + * Start VIP block for HDMI output + * + * @tvout: pointer on tvout structure + * @main_path: true if main path has to be used in the vip configuration + * else aux path is used. + */ +static void tvout_hdmi_start(struct sti_tvout *tvout, bool main_path) +{ + struct device_node *node = tvout->dev->of_node; + bool sel_input_logic_inverted = false; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (main_path) { + DRM_DEBUG_DRIVER("main vip for hdmi\n"); + /* select the input sync for hdmi = VTG set 1 */ + 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, + TVO_VIP_REORDER_CR_R_SEL, + TVO_VIP_REORDER_Y_G_SEL, + TVO_VIP_REORDER_CB_B_SEL); + + /* set clipping mode (Limited range RGB/Y) */ + tvout_vip_set_clip_mode(tvout, TVO_VIP_CLIP_LIMITED_RANGE_RGB_Y); + + /* set round mode (rounded to 8-bit per component) */ + tvout_vip_set_rnd(tvout, TVO_VIP_RND_8BIT_ROUNDED); + + if (of_device_is_compatible(node, "st,stih407-tvout")) { + /* set input video format */ + tvout_vip_set_in_vid_fmt(tvout->regs + TVO_MAIN_IN_VID_FORMAT, + TVO_IN_FMT_SIGNED); + sel_input_logic_inverted = true; + } + + /* input selection */ + tvout_vip_set_sel_input(tvout, main_path, + sel_input_logic_inverted, STI_TVOUT_VIDEO_OUT_RGB); +} + +/** + * 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, 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); +} + +/** + * Forward drm display mode information to the connector + * + * @c: drm_connector + * @mode: mode to be set + * @main_path: indicated if the connector use the main or auxillary data path + * + * Return negative value if error occurs + */ +int sti_tvout_set_mode(struct drm_connector *c, + struct drm_display_mode *mode, bool main_path) +{ + struct sti_connector *connector = to_sti_connector(c); + + if (connector) + if (connector->set_mode) + return connector->set_mode(c, mode, main_path); + + return -EINVAL; +} + +/** + * Prepare / initialize depending on the connector type + * + * @connector: drm_connector + * + * Return negative value if error occurs + */ +int sti_tvout_prepare(struct drm_connector *c) +{ + struct sti_connector *connector = to_sti_connector(c); + struct sti_tvout *tvout = dev_get_drvdata(connector->master); + int ret; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->prepare) + connector->prepare(c); + + switch (connector->type) { + case STI_CONNECTOR_HDMI: + tvout_hdmi_prepare(tvout); + ret = 0; + break; + case STI_CONNECTOR_HDA: + tvout_hda_prepare(tvout); + ret = 0; + break; + case STI_CONNECTOR_DVO: + case STI_CONNECTOR_DENC: + default: + /* not yet supported */ + ret = -EINVAL; + 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 negative value if error occurs + */ +int sti_tvout_commit(struct drm_connector *c, bool main_path) +{ + struct sti_connector *connector = to_sti_connector(c); + struct sti_tvout *tvout = dev_get_drvdata(connector->master); + int type = connector->type; + u32 matrix_offset; + unsigned int i; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (!connector) + return -EINVAL; + + if (connector->start) { + if (connector->start(c)) { + DRM_ERROR("Unable to properly start connector\n"); + return -EINVAL; + } + } + + /* 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_CONNECTOR_HDMI: + tvout_hdmi_start(tvout, main_path); + return 0; + case STI_CONNECTOR_HDA: + tvout_hda_start(tvout, main_path); + return 0; + case STI_CONNECTOR_DVO: + case STI_CONNECTOR_DENC: + default: + /* not yet supported */ + return -EINVAL; + } +} + +/** + * Disable / stop the tvout depending on the connector type + * + * @tvout: pointer on tvout structure + * @type: type of connector + */ +void sti_tvout_disable(struct drm_connector *c) +{ + struct sti_connector *connector = to_sti_connector(c); + struct sti_tvout *tvout = dev_get_drvdata(connector->master); + int type = connector->type; + + switch (type) { + case STI_CONNECTOR_HDMI: + tvout_hdmi_stop(tvout); + break; + case STI_CONNECTOR_HDA: + tvout_hda_stop(tvout); + break; + case STI_CONNECTOR_DVO: + case STI_CONNECTOR_DENC: + default: + /* not yet supported */ + return; + } + + if (connector) + if (connector->stop) + connector->stop(c); +} + +static int sti_tvout_bind(struct device *dev, struct device *master, void *data) +{ + 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 compare_of(struct device *dev, void *data) +{ + struct device_node *np = data; + + return dev->of_node == np; +} + +static int sti_tvout_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("TVout 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_tvout_master_bind(struct device *dev) +{ + return 0; +} + +static void sti_tvout_master_unbind(struct device *dev) +{ + /* do nothing */ +} + +static const struct component_master_ops sti_tvout_master_ops = { + .add_components = sti_tvout_add_components, + .bind = sti_tvout_master_bind, + .unbind = sti_tvout_master_unbind, +}; + +static int sti_tvout_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct sti_tvout *tvout; + struct resource *res; + + DRM_INFO("%s\n", __func__); + + if (!node) + return -ENODEV; + + tvout = devm_kzalloc(dev, sizeof(*tvout), GFP_KERNEL); + if (!tvout) + return -ENOMEM; + + DRM_INFO("%s tvout %p\n", __func__, tvout); + + tvout->dev = dev; + DRM_INFO("%s tvout->dev %p\n", __func__, tvout->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); + + platform_set_drvdata(pdev, tvout); + + of_platform_populate(node, NULL, NULL, dev); + + component_master_add(&pdev->dev, &sti_tvout_master_ops); + + return component_add(&pdev->dev, &sti_tvout_ops); +} + +static int sti_tvout_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &sti_tvout_master_ops); + component_del(&pdev->dev, &sti_tvout_ops); + return 0; +} + +static struct of_device_id tvout_of_match[] = { + { .compatible = "st,stih416-tvout", }, + { .compatible = "st,stih407-tvout", }, + { /* end node */ } +}; +MODULE_DEVICE_TABLE(of, tvout_of_match); + +struct platform_driver sti_tvout_driver = { + .driver = { + .name = "sti-tvout", + .owner = THIS_MODULE, + .of_match_table = tvout_of_match, + }, + .probe = sti_tvout_probe, + .remove = sti_tvout_remove, +}; + +module_platform_driver(sti_tvout_driver); diff --git a/drivers/gpu/drm/sti/sti_tvout.h b/drivers/gpu/drm/sti/sti_tvout.h new file mode 100644 index 0000000..81dceff --- /dev/null +++ b/drivers/gpu/drm/sti/sti_tvout.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Author: Vincent Abriou vincent.abriou@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_TVOUT_H_ +#define _STI_TVOUT_H_ + +#include <drm/drmP.h> + +int sti_tvout_set_mode(struct drm_connector *c, + struct drm_display_mode *mode, bool main_path); +int sti_tvout_prepare(struct drm_connector *c); +int sti_tvout_commit(struct drm_connector *c, bool main_path); +void sti_tvout_disable(struct drm_connector *c); + +#endif
On Thu, May 29, 2014 at 2:37 AM, Benjamin Gaignard benjamin.gaignard@linaro.org wrote:
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.
TVout is the parent of HDMI and HDA drivers and bind them at runtime.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org
drivers/gpu/drm/sti/Makefile | 3 +- drivers/gpu/drm/sti/sti_tvout.c | 678 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_tvout.h | 18 ++ 3 files changed, 698 insertions(+), 1 deletion(-) 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 df47171..dcc9568 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -4,4 +4,5 @@ obj-$(CONFIG_DRM_STI) += \ sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \
sti_hda.o
sti_hda.o \
sti_tvout.o
\ No newline at end of file diff --git a/drivers/gpu/drm/sti/sti_tvout.c b/drivers/gpu/drm/sti/sti_tvout.c new file mode 100644 index 0000000..d778e35 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_tvout.c @@ -0,0 +1,678 @@ +/*
- Copyright (C) STMicroelectronics SA 2014
- Authors: Benjamin Gaignard benjamin.gaignard@st.com
Vincent Abriou <vincent.abriou@st.com>
for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/reset.h>
+#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h>
+#include "sti_drm_connector.h" +#include "sti_drm_encoder.h" +#include "sti_tvout.h"
+/* glue registers */ +#define TVO_CSC_MAIN_M0 0x000 +#define TVO_CSC_MAIN_M1 0x004 +#define TVO_CSC_MAIN_M2 0x008 +#define TVO_CSC_MAIN_M3 0x00c +#define TVO_CSC_MAIN_M4 0x010 +#define TVO_CSC_MAIN_M5 0x014 +#define TVO_CSC_MAIN_M6 0x018 +#define TVO_CSC_MAIN_M7 0x01c +#define TVO_MAIN_IN_VID_FORMAT 0x030 +#define TVO_CSC_AUX_M0 0x100 +#define TVO_CSC_AUX_M1 0x104 +#define TVO_CSC_AUX_M2 0x108 +#define TVO_CSC_AUX_M3 0x10c +#define TVO_CSC_AUX_M4 0x110 +#define TVO_CSC_AUX_M5 0x114 +#define TVO_CSC_AUX_M6 0x118 +#define TVO_CSC_AUX_M7 0x11c +#define TVO_AUX_IN_VID_FORMAT 0x130 +#define TVO_VIP_HDF 0x400 +#define TVO_HD_SYNC_SEL 0x418 +#define TVO_HD_DAC_CFG_OFF 0x420 +#define TVO_VIP_HDMI 0x500 +#define TVO_HDMI_FORCE_COLOR_0 0x504 +#define TVO_HDMI_FORCE_COLOR_1 0x508 +#define TVO_HDMI_CLIP_VALUE_B_CB 0x50c +#define TVO_HDMI_CLIP_VALUE_Y_G 0x510 +#define TVO_HDMI_CLIP_VALUE_R_CR 0x514 +#define TVO_HDMI_SYNC_SEL 0x518 +#define TVO_HDMI_DFV_OBS 0x540
+#define TVO_IN_FMT_SIGNED BIT(0) +#define TVO_SYNC_EXT BIT(4)
+#define TVO_VIP_REORDER_R_SHIFT 24 +#define TVO_VIP_REORDER_G_SHIFT 20 +#define TVO_VIP_REORDER_B_SHIFT 16 +#define TVO_VIP_REORDER_MASK 0x3 +#define TVO_VIP_REORDER_Y_G_SEL 0 +#define TVO_VIP_REORDER_CB_B_SEL 1 +#define TVO_VIP_REORDER_CR_R_SEL 2
+#define TVO_VIP_CLIP_SHIFT 8 +#define TVO_VIP_CLIP_MASK 0x7 +#define TVO_VIP_CLIP_DISABLED 0 +#define TVO_VIP_CLIP_EAV_SAV 1 +#define TVO_VIP_CLIP_LIMITED_RANGE_RGB_Y 2 +#define TVO_VIP_CLIP_LIMITED_RANGE_CB_CR 3 +#define TVO_VIP_CLIP_PROG_RANGE 4
+#define TVO_VIP_RND_SHIFT 4 +#define TVO_VIP_RND_MASK 0x3 +#define TVO_VIP_RND_8BIT_ROUNDED 0 +#define TVO_VIP_RND_10BIT_ROUNDED 1 +#define TVO_VIP_RND_12BIT_ROUNDED 2
+#define TVO_VIP_SEL_INPUT_MASK 0xf +#define TVO_VIP_SEL_INPUT_MAIN 0x0 +#define TVO_VIP_SEL_INPUT_AUX 0x8 +#define TVO_VIP_SEL_INPUT_FORCE_COLOR 0xf +#define TVO_VIP_SEL_INPUT_BYPASS_MASK 0x1 +#define TVO_VIP_SEL_INPUT_BYPASSED 1
+#define TVO_SYNC_MAIN_VTG_SET_REF 0x00 +#define TVO_SYNC_MAIN_VTG_SET_1 0x01 +#define TVO_SYNC_MAIN_VTG_SET_2 0x02 +#define TVO_SYNC_MAIN_VTG_SET_3 0x03 +#define TVO_SYNC_MAIN_VTG_SET_4 0x04 +#define TVO_SYNC_MAIN_VTG_SET_5 0x05 +#define TVO_SYNC_MAIN_VTG_SET_6 0x06 +#define TVO_SYNC_AUX_VTG_SET_REF 0x10 +#define TVO_SYNC_AUX_VTG_SET_1 0x11 +#define TVO_SYNC_AUX_VTG_SET_2 0x12 +#define TVO_SYNC_AUX_VTG_SET_3 0x13 +#define TVO_SYNC_AUX_VTG_SET_4 0x14 +#define TVO_SYNC_AUX_VTG_SET_5 0x15 +#define TVO_SYNC_AUX_VTG_SET_6 0x16
+#define TVO_SYNC_HD_DCS_SHIFT 8
+/* 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
- */
+struct sti_tvout {
struct device *dev;
void __iomem *regs;
struct reset_control *reset;
+};
+/* preformatter conversion matrix */ +static const u32 rgb_to_ycbcr_601[8] = {
0xF927082E, 0x04C9FEAB, 0x01D30964, 0xFA95FD3D,
0x0000082E, 0x00002000, 0x00002000, 0x00000000
+};
+/* 709 RGB to YCbCr */ +static const u32 rgb_to_ycbcr_709[8] = {
0xF891082F, 0x0367FF40, 0x01280B71, 0xF9B1FE20,
0x0000082F, 0x00002000, 0x00002000, 0x00000000
+};
+static u32 tvout_read(struct sti_tvout *tvout, int offset) +{
return readl(tvout->regs + offset);
+}
+static void tvout_write(struct sti_tvout *tvout, u32 val, int offset) +{
writel(val, tvout->regs + offset);
+}
+/**
- Set the clipping mode of a VIP
- @tvout: tvout structure
- @cr_r:
- @y_g:
- @cb_b:
- */
+static void tvout_vip_set_color_order(struct sti_tvout *tvout,
u32 cr_r, u32 y_g, u32 cb_b)
+{
u32 val = tvout_read(tvout, TVO_VIP_HDMI);
val &= ~(TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_R_SHIFT);
val &= ~(TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_G_SHIFT);
val &= ~(TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_B_SHIFT);
val |= cr_r << TVO_VIP_REORDER_R_SHIFT;
val |= y_g << TVO_VIP_REORDER_G_SHIFT;
val |= cb_b << TVO_VIP_REORDER_B_SHIFT;
tvout_write(tvout, val, TVO_VIP_HDMI);
+}
+/**
- Set the clipping mode of a VIP
- @tvout: tvout structure
- @range: clipping range
- */
+static void tvout_vip_set_clip_mode(struct sti_tvout *tvout, u32 range) +{
u32 val = tvout_read(tvout, TVO_VIP_HDMI);
val &= ~(TVO_VIP_CLIP_MASK << TVO_VIP_CLIP_SHIFT);
val |= range << TVO_VIP_CLIP_SHIFT;
tvout_write(tvout, val, TVO_VIP_HDMI);
+}
+/**
- Set the rounded value of a VIP
- @tvout: tvout structure
- @rnd: rounded val per component
- */
+static void tvout_vip_set_rnd(struct sti_tvout *tvout, u32 rnd) +{
u32 val = tvout_read(tvout, TVO_VIP_HDMI);
val &= ~(TVO_VIP_RND_MASK << TVO_VIP_RND_SHIFT);
val |= rnd << TVO_VIP_RND_SHIFT;
tvout_write(tvout, val, TVO_VIP_HDMI);
+}
+/**
- Select the VIP input
- @tvout: tvout structure
- @sel_input: selected_input (main/aux + conv)
- */
+static void tvout_vip_set_sel_input(struct sti_tvout *tvout,
bool main_path,
bool sel_input_logic_inverted,
enum sti_tvout_video_out_type video_out)
+{
u32 sel_input;
u32 val = tvout_read(tvout, TVO_VIP_HDMI);
if (main_path)
sel_input = TVO_VIP_SEL_INPUT_MAIN;
else
sel_input = TVO_VIP_SEL_INPUT_AUX;
switch (video_out) {
case STI_TVOUT_VIDEO_OUT_RGB:
sel_input |= TVO_VIP_SEL_INPUT_BYPASSED;
break;
case STI_TVOUT_VIDEO_OUT_YUV:
sel_input &= ~TVO_VIP_SEL_INPUT_BYPASSED;
break;
}
/* on stih407 chip the sel_input bypass mode logic is inverted */
if (sel_input_logic_inverted)
sel_input = sel_input ^ TVO_VIP_SEL_INPUT_BYPASS_MASK;
val &= ~TVO_VIP_SEL_INPUT_MASK;
val |= sel_input;
tvout_write(tvout, val, TVO_VIP_HDMI);
+}
+/**
- Select the input video signed or unsigned
- @tvout: tvout structure
- @in_vid_signed: used video input format
- */
+static void tvout_vip_set_in_vid_fmt(struct sti_tvout *tvout, u32 in_vid_fmt) +{
u32 val = tvout_read(tvout, TVO_VIP_HDMI);
val &= ~TVO_IN_FMT_SIGNED;
val |= in_vid_fmt;
tvout_write(tvout, val, TVO_MAIN_IN_VID_FORMAT);
+}
+/**
- Start VIP block for HDMI output
- @tvout: pointer on tvout structure
- @main_path: true if main path has to be used in the vip configuration
else aux path is used.
- */
+static void tvout_hdmi_start(struct sti_tvout *tvout, bool main_path) +{
struct device_node *node = tvout->dev->of_node;
bool sel_input_logic_inverted = false;
dev_dbg(tvout->dev, "%s\n", __func__);
if (main_path) {
DRM_DEBUG_DRIVER("main vip for hdmi\n");
/* select the input sync for hdmi = VTG set 1 */
writel(TVO_SYNC_MAIN_VTG_SET_1,
tvout->regs + TVO_HDMI_SYNC_SEL);
btw, I notice a few places that bypass tvout_write()/tvout_readl().. but if there is a reason, it at least doesn't seem obvious..
} 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,
TVO_VIP_REORDER_CR_R_SEL,
TVO_VIP_REORDER_Y_G_SEL,
TVO_VIP_REORDER_CB_B_SEL);
/* set clipping mode (Limited range RGB/Y) */
tvout_vip_set_clip_mode(tvout, TVO_VIP_CLIP_LIMITED_RANGE_RGB_Y);
/* set round mode (rounded to 8-bit per component) */
tvout_vip_set_rnd(tvout, TVO_VIP_RND_8BIT_ROUNDED);
if (of_device_is_compatible(node, "st,stih407-tvout")) {
/* set input video format */
tvout_vip_set_in_vid_fmt(tvout->regs + TVO_MAIN_IN_VID_FORMAT,
TVO_IN_FMT_SIGNED);
sel_input_logic_inverted = true;
}
/* input selection */
tvout_vip_set_sel_input(tvout, main_path,
sel_input_logic_inverted, STI_TVOUT_VIDEO_OUT_RGB);
+}
+/**
- 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, 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);
+}
+/**
- Forward drm display mode information to the connector
- @c: drm_connector
- @mode: mode to be set
- @main_path: indicated if the connector use the main or auxillary data path
- Return negative value if error occurs
- */
+int sti_tvout_set_mode(struct drm_connector *c,
struct drm_display_mode *mode, bool main_path)
+{
struct sti_connector *connector = to_sti_connector(c);
if (connector)
if (connector->set_mode)
return connector->set_mode(c, mode, main_path);
hmm, I do wonder a bit whether it would make more sense to split sti_connector into a bridge + connector, and move stuff like set_mode() into the bridge half of it? Normally that sort of thing would make more sense in the encoder, but for msm I actually have separate hw blocks (ie. encoder is part of display controller block, but hdmi_bridge or hypothetical dp_bridge/dsi_bridge, are a different block with (in some cases) their own irq, (in all cases) their own register space, etc. So the bridge/connector split worked pretty nicely there. I didn't look closely at why you have things split up this way, but if the reasons are similar then I'd suggest sti_bridge/sti_connector..
return -EINVAL;
+}
+/**
- Prepare / initialize depending on the connector type
- @connector: drm_connector
- Return negative value if error occurs
- */
+int sti_tvout_prepare(struct drm_connector *c) +{
struct sti_connector *connector = to_sti_connector(c);
struct sti_tvout *tvout = dev_get_drvdata(connector->master);
int ret;
dev_dbg(tvout->dev, "%s\n", __func__);
if (connector)
if (connector->prepare)
connector->prepare(c);
switch (connector->type) {
hmm, ok, so couple issues here..
first, either that 'if (connector)' above is pointless (because you never expect connector to be NULL) or you're going to crash deref'ing connector->type.
Also, what is the purpose of this switch, vs just having the connector specific implementation of connector->prepare() (which maybe should actually be in sti_bridge) do call right thing?
Same comment for _commit() and _disable() below.
BR, -R
case STI_CONNECTOR_HDMI:
tvout_hdmi_prepare(tvout);
ret = 0;
break;
case STI_CONNECTOR_HDA:
tvout_hda_prepare(tvout);
ret = 0;
break;
case STI_CONNECTOR_DVO:
case STI_CONNECTOR_DENC:
default:
/* not yet supported */
ret = -EINVAL;
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 negative value if error occurs
- */
+int sti_tvout_commit(struct drm_connector *c, bool main_path) +{
struct sti_connector *connector = to_sti_connector(c);
struct sti_tvout *tvout = dev_get_drvdata(connector->master);
int type = connector->type;
u32 matrix_offset;
unsigned int i;
dev_dbg(tvout->dev, "%s\n", __func__);
if (!connector)
return -EINVAL;
if (connector->start) {
if (connector->start(c)) {
DRM_ERROR("Unable to properly start connector\n");
return -EINVAL;
}
}
/* 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_CONNECTOR_HDMI:
tvout_hdmi_start(tvout, main_path);
return 0;
case STI_CONNECTOR_HDA:
tvout_hda_start(tvout, main_path);
return 0;
case STI_CONNECTOR_DVO:
case STI_CONNECTOR_DENC:
default:
/* not yet supported */
return -EINVAL;
}
+}
+/**
- Disable / stop the tvout depending on the connector type
- @tvout: pointer on tvout structure
- @type: type of connector
- */
+void sti_tvout_disable(struct drm_connector *c) +{
struct sti_connector *connector = to_sti_connector(c);
struct sti_tvout *tvout = dev_get_drvdata(connector->master);
int type = connector->type;
switch (type) {
case STI_CONNECTOR_HDMI:
tvout_hdmi_stop(tvout);
break;
case STI_CONNECTOR_HDA:
tvout_hda_stop(tvout);
break;
case STI_CONNECTOR_DVO:
case STI_CONNECTOR_DENC:
default:
/* not yet supported */
return;
}
if (connector)
if (connector->stop)
connector->stop(c);
+}
+static int sti_tvout_bind(struct device *dev, struct device *master, void *data) +{
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 compare_of(struct device *dev, void *data) +{
struct device_node *np = data;
return dev->of_node == np;
+}
+static int sti_tvout_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("TVout 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_tvout_master_bind(struct device *dev) +{
return 0;
+}
+static void sti_tvout_master_unbind(struct device *dev) +{
/* do nothing */
+}
+static const struct component_master_ops sti_tvout_master_ops = {
.add_components = sti_tvout_add_components,
.bind = sti_tvout_master_bind,
.unbind = sti_tvout_master_unbind,
+};
+static int sti_tvout_probe(struct platform_device *pdev) +{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct sti_tvout *tvout;
struct resource *res;
DRM_INFO("%s\n", __func__);
if (!node)
return -ENODEV;
tvout = devm_kzalloc(dev, sizeof(*tvout), GFP_KERNEL);
if (!tvout)
return -ENOMEM;
DRM_INFO("%s tvout %p\n", __func__, tvout);
tvout->dev = dev;
DRM_INFO("%s tvout->dev %p\n", __func__, tvout->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);
platform_set_drvdata(pdev, tvout);
of_platform_populate(node, NULL, NULL, dev);
component_master_add(&pdev->dev, &sti_tvout_master_ops);
return component_add(&pdev->dev, &sti_tvout_ops);
+}
+static int sti_tvout_remove(struct platform_device *pdev) +{
component_master_del(&pdev->dev, &sti_tvout_master_ops);
component_del(&pdev->dev, &sti_tvout_ops);
return 0;
+}
+static struct of_device_id tvout_of_match[] = {
{ .compatible = "st,stih416-tvout", },
{ .compatible = "st,stih407-tvout", },
{ /* end node */ }
+}; +MODULE_DEVICE_TABLE(of, tvout_of_match);
+struct platform_driver sti_tvout_driver = {
.driver = {
.name = "sti-tvout",
.owner = THIS_MODULE,
.of_match_table = tvout_of_match,
},
.probe = sti_tvout_probe,
.remove = sti_tvout_remove,
+};
+module_platform_driver(sti_tvout_driver); diff --git a/drivers/gpu/drm/sti/sti_tvout.h b/drivers/gpu/drm/sti/sti_tvout.h new file mode 100644 index 0000000..81dceff --- /dev/null +++ b/drivers/gpu/drm/sti/sti_tvout.h @@ -0,0 +1,18 @@ +/*
- Copyright (C) STMicroelectronics SA 2014
- Author: Vincent Abriou vincent.abriou@st.com for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#ifndef _STI_TVOUT_H_ +#define _STI_TVOUT_H_
+#include <drm/drmP.h>
+int sti_tvout_set_mode(struct drm_connector *c,
struct drm_display_mode *mode, bool main_path);
+int sti_tvout_prepare(struct drm_connector *c); +int sti_tvout_commit(struct drm_connector *c, bool main_path); +void sti_tvout_disable(struct drm_connector *c);
+#endif
1.9.1
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
Generic Display Pipeline are one of the compositor input sub-devices. GDP are dedicated to graphic input like RGB plans. GDP is part of Compositor hardware block which will be introduce later.
A sti_layer structure is used to abstract GDP calls from Compositor.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Makefile | 3 +- drivers/gpu/drm/sti/sti_gdp.c | 482 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_gdp.h | 73 ++++++ drivers/gpu/drm/sti/sti_layer.h | 111 +++++++++ 4 files changed, 668 insertions(+), 1 deletion(-) 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_layer.h
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index dcc9568..1745697 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -5,4 +5,5 @@ obj-$(CONFIG_DRM_STI) += \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ - sti_tvout.o \ No newline at end of file + sti_tvout.o \ + sti_gdp.o \ No newline at end of file diff --git a/drivers/gpu/drm/sti/sti_gdp.c b/drivers/gpu/drm/sti/sti_gdp.c new file mode 100644 index 0000000..a014214 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_gdp.c @@ -0,0 +1,482 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/dma-mapping.h> + +#include "sti_gdp.h" +#include "sti_layer.h" +#include "sti_vtg.h" + +#define ENA_COLOR_FILL BIT(8) +#define WAIT_NEXT_VSYNC BIT(31) + +/* GDP color formats */ +#define GDP_RGB565 0x00 +#define GDP_RGB888 0x01 +#define GDP_RGB888_32 0x02 +#define GDP_ARGB8565 0x04 +#define GDP_ARGB8888 0x05 +#define GDP_ARGB1555 0x06 +#define GDP_ARGB4444 0x07 +#define GDP_CLUT8 0x0B +#define GDP_YCBR888 0x10 +#define GDP_YCBR422R 0x12 +#define GDP_AYCBR8888 0x15 + +#define GAM_GDP_CTL_OFFSET 0x00 +#define GAM_GDP_AGC_OFFSET 0x04 +#define GAM_GDP_VPO_OFFSET 0x0C +#define GAM_GDP_VPS_OFFSET 0x10 +#define GAM_GDP_PML_OFFSET 0x14 +#define GAM_GDP_PMP_OFFSET 0x18 +#define GAM_GDP_SIZE_OFFSET 0x1C +#define GAM_GDP_NVN_OFFSET 0x24 +#define GAM_GDP_KEY1_OFFSET 0x28 +#define GAM_GDP_KEY2_OFFSET 0x2C +#define GAM_GDP_PPT_OFFSET 0x34 +#define GAM_GDP_CML_OFFSET 0x3C +#define GAM_GDP_MST_OFFSET 0x68 + +#define GAM_GDP_ALPHARANGE_255 BIT(5) +#define GAM_GDP_AGC_FULL_RANGE 0x00808080 +#define GAM_GDP_PPT_IGNORE (BIT(1) | BIT(0)) +#define GAM_GDP_SIZE_MAX 0x7FF + +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 unsigned 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; + unsigned 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; + unsigned 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; + unsigned int depth, bpp; + int rate = mode->clock * 1000; + int res; + u32 ydo, xdo, yds, xds; + + list = sti_gdp_get_free_nodes(layer); + top_field = list->top_field; + btm_field = list->btm_field; + + /* 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) { + /* 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) +{ + unsigned 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 (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; + unsigned 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..3618039 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_gdp.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_GDP_H_ +#define _STI_GDP_H_ + +#include <linux/types.h> + +#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); + unsigned 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 new file mode 100644 index 0000000..7ba5a40 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_layer.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_LAYER_H_ +#define _STI_LAYER_H_ + +#include <drm/drmP.h> +#include "sti_gdp.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; + unsigned int curr_frame_counter; + unsigned 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 + * @mixer_id: id of the mixer used to display the layer + * @enabled: to know if the layer is active or not + * @src_x src_y: coordinates of the input (fb) area + * @src_w src_h: size of the input (fb) area + * @dst_x dst_y: coordinates of the output (crtc) area + * @dst_w dst_h: size of the output (crtc) area + * @format: format + * @pitches: pitch of 'planes' (eg: Y, U, V) + * @offsets: offset of 'planes' + * @paddr: physical address of the input buffer + * @fps_info: frame per second info + * @gdp: related GDP (if the layer is a GDP) + */ +struct sti_layer { + struct drm_plane plane; + struct drm_framebuffer *fb; + 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; + int dst_x, dst_y; + int dst_w, dst_h; + uint32_t format; + unsigned int pitches[4]; + unsigned int offsets[4]; + dma_addr_t paddr; + struct sti_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, + int src_w, int src_h); +int sti_layer_commit(struct sti_layer *layer); +int sti_layer_disable(struct sti_layer *layer); +const uint32_t *sti_layer_get_formats(struct sti_layer *layer); +unsigned int sti_layer_get_nb_formats(struct sti_layer *layer); +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
On Thu, May 29, 2014 at 2:37 AM, Benjamin Gaignard benjamin.gaignard@linaro.org wrote:
Generic Display Pipeline are one of the compositor input sub-devices. GDP are dedicated to graphic input like RGB plans. GDP is part of Compositor hardware block which will be introduce later.
A sti_layer structure is used to abstract GDP calls from Compositor.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org
drivers/gpu/drm/sti/Makefile | 3 +- drivers/gpu/drm/sti/sti_gdp.c | 482 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_gdp.h | 73 ++++++ drivers/gpu/drm/sti/sti_layer.h | 111 +++++++++ 4 files changed, 668 insertions(+), 1 deletion(-) 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_layer.h
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index dcc9568..1745697 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -5,4 +5,5 @@ obj-$(CONFIG_DRM_STI) += \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \
sti_tvout.o
\ No newline at end of file
sti_tvout.o \
sti_gdp.o
\ No newline at end of file diff --git a/drivers/gpu/drm/sti/sti_gdp.c b/drivers/gpu/drm/sti/sti_gdp.c new file mode 100644 index 0000000..a014214 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_gdp.c @@ -0,0 +1,482 @@ +/*
- Copyright (C) STMicroelectronics SA 2014
- Authors: Benjamin Gaignard benjamin.gaignard@st.com
Fabien Dessenne <fabien.dessenne@st.com>
for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#include <linux/clk.h> +#include <linux/dma-mapping.h>
+#include "sti_gdp.h" +#include "sti_layer.h" +#include "sti_vtg.h"
+#define ENA_COLOR_FILL BIT(8) +#define WAIT_NEXT_VSYNC BIT(31)
+/* GDP color formats */ +#define GDP_RGB565 0x00 +#define GDP_RGB888 0x01 +#define GDP_RGB888_32 0x02 +#define GDP_ARGB8565 0x04 +#define GDP_ARGB8888 0x05 +#define GDP_ARGB1555 0x06 +#define GDP_ARGB4444 0x07 +#define GDP_CLUT8 0x0B +#define GDP_YCBR888 0x10 +#define GDP_YCBR422R 0x12 +#define GDP_AYCBR8888 0x15
+#define GAM_GDP_CTL_OFFSET 0x00 +#define GAM_GDP_AGC_OFFSET 0x04 +#define GAM_GDP_VPO_OFFSET 0x0C +#define GAM_GDP_VPS_OFFSET 0x10 +#define GAM_GDP_PML_OFFSET 0x14 +#define GAM_GDP_PMP_OFFSET 0x18 +#define GAM_GDP_SIZE_OFFSET 0x1C +#define GAM_GDP_NVN_OFFSET 0x24 +#define GAM_GDP_KEY1_OFFSET 0x28 +#define GAM_GDP_KEY2_OFFSET 0x2C +#define GAM_GDP_PPT_OFFSET 0x34 +#define GAM_GDP_CML_OFFSET 0x3C +#define GAM_GDP_MST_OFFSET 0x68
+#define GAM_GDP_ALPHARANGE_255 BIT(5) +#define GAM_GDP_AGC_FULL_RANGE 0x00808080 +#define GAM_GDP_PPT_IGNORE (BIT(1) | BIT(0)) +#define GAM_GDP_SIZE_MAX 0x7FF
+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 unsigned 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;
unsigned 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];
hmm, from looks of it, you don't expect to fall past the end of the node list.. is that an error case, in which case a WARN_ON() might be appropriate? The 'goto end' part I'm less sure about (for different generation of hw?) so not sure if the WARN should be after or before the 'end' label.
+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;
unsigned 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;
unsigned int depth, bpp;
int rate = mode->clock * 1000;
int res;
u32 ydo, xdo, yds, xds;
list = sti_gdp_get_free_nodes(layer);
top_field = list->top_field;
btm_field = list->btm_field;
/* 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) {
/* 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) +{
unsigned 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 (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;
unsigned 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;
generally people like to split out the fxn ptrs to be in their own _funcs struct, so it can be const.
BR, -R
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..3618039 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_gdp.h @@ -0,0 +1,73 @@ +/*
- Copyright (C) STMicroelectronics SA 2014
- Authors: Benjamin Gaignard benjamin.gaignard@st.com
Fabien Dessenne <fabien.dessenne@st.com>
for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#ifndef _STI_GDP_H_ +#define _STI_GDP_H_
+#include <linux/types.h>
+#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);
unsigned 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 new file mode 100644 index 0000000..7ba5a40 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_layer.h @@ -0,0 +1,111 @@ +/*
- Copyright (C) STMicroelectronics SA 2014
- Authors: Benjamin Gaignard benjamin.gaignard@st.com
Fabien Dessenne <fabien.dessenne@st.com>
for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#ifndef _STI_LAYER_H_ +#define _STI_LAYER_H_
+#include <drm/drmP.h> +#include "sti_gdp.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;
unsigned int curr_frame_counter;
unsigned 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
- @mixer_id: id of the mixer used to display the layer
- @enabled: to know if the layer is active or not
- @src_x src_y: coordinates of the input (fb) area
- @src_w src_h: size of the input (fb) area
- @dst_x dst_y: coordinates of the output (crtc) area
- @dst_w dst_h: size of the output (crtc) area
- @format: format
- @pitches: pitch of 'planes' (eg: Y, U, V)
- @offsets: offset of 'planes'
- @paddr: physical address of the input buffer
- @fps_info: frame per second info
- @gdp: related GDP (if the layer is a GDP)
- */
+struct sti_layer {
struct drm_plane plane;
struct drm_framebuffer *fb;
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;
int dst_x, dst_y;
int dst_w, dst_h;
uint32_t format;
unsigned int pitches[4];
unsigned int offsets[4];
dma_addr_t paddr;
struct sti_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,
int src_w, int src_h);
+int sti_layer_commit(struct sti_layer *layer); +int sti_layer_disable(struct sti_layer *layer); +const uint32_t *sti_layer_get_formats(struct sti_layer *layer); +unsigned int sti_layer_get_nb_formats(struct sti_layer *layer); +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
1.9.1
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
VIDeo plug are one of the compositor input sub-devices. VID are dedicated to video inputs like YUV plans.
Like GDP, VID are part of Compositor hardware block and use sti_layer structure to provide an abstraction for Compositor calls.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Makefile | 3 +- drivers/gpu/drm/sti/sti_layer.h | 3 + drivers/gpu/drm/sti/sti_vid.c | 138 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_vid.h | 33 ++++++++++ 4 files changed, 176 insertions(+), 1 deletion(-) 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 1745697..bb0f6b3 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -6,4 +6,5 @@ obj-$(CONFIG_DRM_STI) += \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ sti_tvout.o \ - sti_gdp.o \ No newline at end of file + sti_gdp.o \ + sti_vid.o \ No newline at end of file diff --git a/drivers/gpu/drm/sti/sti_layer.h b/drivers/gpu/drm/sti/sti_layer.h index 7ba5a40..c8a159d 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)
@@ -70,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; @@ -89,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..0197291 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vid.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Author: Fabien Dessenne fabien.dessenne@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <drm/drmP.h> + +#include "sti_layer.h" +#include "sti_vid.h" +#include "sti_vtg.h" + +/* Registers */ +#define VID_CTL 0x00 +#define VID_ALP 0x04 +#define VID_CLF 0x08 +#define VID_VPO 0x0C +#define VID_VPS 0x10 +#define VID_KEY1 0x28 +#define VID_KEY2 0x2C +#define VID_MPR0 0x30 +#define VID_MPR1 0x34 +#define VID_MPR2 0x38 +#define VID_MPR3 0x3C +#define VID_MST 0x68 +#define VID_BC 0x70 +#define VID_TINT 0x74 +#define VID_CSAT 0x78 + +/* Registers values */ +#define VID_CTL_IGNORE (BIT(31) | BIT(30)) +#define VID_CTL_PSI_ENABLE (BIT(2) | BIT(1) | BIT(0)) +#define VID_ALP_OPAQUE 0x00000080 +#define VID_BC_DFLT 0x00008000 +#define VID_TINT_DFLT 0x00000000 +#define VID_CSAT_DFLT 0x00000080 +/* YCbCr to RGB BT709: + * R = Y+1.5391Cr + * G = Y-0.4590Cr-0.1826Cb + * B = Y+1.8125Cb */ +#define VID_MPR0_BT709 0x0A800000 +#define VID_MPR1_BT709 0x0AC50000 +#define VID_MPR2_BT709 0x07150545 +#define VID_MPR3_BT709 0x00000AE8 + +static int sti_vid_prepare_layer(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..4a502b0 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vid.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Author: Fabien Dessenne fabien.dessenne@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_VID_H_ +#define _STI_VID_H_ + +/* + * 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); + unsigned 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 auxillary path
Mixers are part of Compositor hardware block
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Makefile | 1 + drivers/gpu/drm/sti/sti_mixer.c | 243 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_mixer.h | 52 +++++++++ 3 files changed, 296 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 bb0f6b3..ce03e15 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -6,5 +6,6 @@ obj-$(CONFIG_DRM_STI) += \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ sti_tvout.o \ + sti_mixer.o \ sti_gdp.o \ sti_vid.o \ No newline at end of file diff --git a/drivers/gpu/drm/sti/sti_mixer.c b/drivers/gpu/drm/sti/sti_mixer.c new file mode 100644 index 0000000..c812c68 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_mixer.c @@ -0,0 +1,243 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include "sti_mixer.h" +#include "sti_vtg.h" + +/* Identity: G=Y , B=Cb , R=Cr */ +static const u32 MixerColorSpaceMatIdentity[] = { + 0x10000000, 0x00000000, 0x10000000, 0x00001000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 +}; + +/* regs offset */ +#define GAM_MIXER_CTL 0x00 +#define GAM_MIXER_BKC 0x04 +#define GAM_MIXER_BCO 0x0C +#define GAM_MIXER_BCS 0x10 +#define GAM_MIXER_AVO 0x28 +#define GAM_MIXER_AVS 0x2C +#define GAM_MIXER_CRB 0x34 +#define GAM_MIXER_ACT 0x38 +#define GAM_MIXER_MBP 0x3C +#define GAM_MIXER_MX0 0x80 + +/* id for depth of CRB reg */ +#define GAM_DEPTH_VID0_ID 1 +#define GAM_DEPTH_VID1_ID 2 +#define GAM_DEPTH_GDP0_ID 3 +#define GAM_DEPTH_GDP1_ID 4 +#define GAM_DEPTH_GDP2_ID 5 +#define GAM_DEPTH_GDP3_ID 6 +#define GAM_DEPTH_MASK_ID 7 + +/* mask in CTL reg */ +#define GAM_CTL_BACK_MASK BIT(0) +#define GAM_CTL_VID0_MASK BIT(1) +#define GAM_CTL_VID1_MASK BIT(2) +#define GAM_CTL_GDP0_MASK BIT(3) +#define GAM_CTL_GDP1_MASK BIT(4) +#define GAM_CTL_GDP2_MASK BIT(5) +#define GAM_CTL_GDP3_MASK BIT(6) + +const char *sti_mixer_to_str(struct sti_mixer *mixer) +{ + switch (mixer->id) { + case STI_MIXER_MAIN: + return "MAIN_MIXER"; + case STI_MIXER_AUX: + return "AUX_MIXER"; + default: + return "<UNKNOWN MIXER>"; + } +} + +static inline u32 sti_mixer_reg_read(struct sti_mixer *mixer, u32 reg_id) +{ + return readl(mixer->regs + reg_id); +} + +static inline void sti_mixer_reg_write(struct sti_mixer *mixer, + u32 reg_id, u32 val) +{ + writel(val, mixer->regs + reg_id); +} + +void sti_mixer_set_background_status(struct sti_mixer *mixer, bool enable) +{ + u32 val = sti_mixer_reg_read(mixer, GAM_MIXER_CTL); + + val &= ~GAM_CTL_BACK_MASK; + val |= enable; + sti_mixer_reg_write(mixer, GAM_MIXER_CTL, val); +} + +static void sti_mixer_set_background_color(struct sti_mixer *mixer, + u8 red, u8 green, u8 blue) +{ + u32 val = (red << 16) | (green << 8) | blue; + + sti_mixer_reg_write(mixer, GAM_MIXER_BKC, val); +} + +static void sti_mixer_set_background_area(struct sti_mixer *mixer, + struct drm_display_mode *mode) +{ + u32 ydo, xdo, yds, xds; + + ydo = sti_vtg_get_line_number(*mode, 0); + yds = sti_vtg_get_line_number(*mode, mode->vdisplay - 1); + xdo = sti_vtg_get_pixel_number(*mode, 0); + xds = sti_vtg_get_pixel_number(*mode, mode->hdisplay - 1); + + sti_mixer_reg_write(mixer, GAM_MIXER_BCO, ydo << 16 | xdo); + sti_mixer_reg_write(mixer, GAM_MIXER_BCS, yds << 16 | xds); +} + +int sti_mixer_set_layer_depth(struct sti_mixer *mixer, struct sti_layer *layer) +{ + int layer_id = 0, depth = layer->zorder; + u32 mask, val; + + if (depth >= GAM_MIXER_NB_DEPTH_LEVEL) + return 1; + + switch (layer->desc) { + case STI_GDP_0: + layer_id = GAM_DEPTH_GDP0_ID; + break; + case STI_GDP_1: + layer_id = GAM_DEPTH_GDP1_ID; + break; + case STI_GDP_2: + layer_id = GAM_DEPTH_GDP2_ID; + break; + case STI_GDP_3: + layer_id = GAM_DEPTH_GDP3_ID; + break; + case STI_VID_0: + layer_id = GAM_DEPTH_VID0_ID; + break; + case STI_VID_1: + layer_id = GAM_DEPTH_VID1_ID; + break; + default: + DRM_ERROR("Unknown layer %d\n", layer->desc); + return 1; + } + mask = GAM_DEPTH_MASK_ID << (3 * depth); + layer_id = layer_id << (3 * depth); + + dev_dbg(mixer->dev, "GAM_MIXER_CRB val 0x%x mask 0x%x\n", + layer_id, mask); + + val = sti_mixer_reg_read(mixer, GAM_MIXER_CRB); + val &= ~mask; + val |= layer_id; + sti_mixer_reg_write(mixer, GAM_MIXER_CRB, val); + + dev_dbg(mixer->dev, "Read GAM_MIXER_CRB 0x%x\n", + sti_mixer_reg_read(mixer, GAM_MIXER_CRB)); + return 0; +} + +int sti_mixer_active_video_area(struct sti_mixer *mixer, + struct drm_display_mode *mode) +{ + u32 ydo, xdo, yds, xds; + + ydo = sti_vtg_get_line_number(*mode, 0); + yds = sti_vtg_get_line_number(*mode, mode->vdisplay - 1); + xdo = sti_vtg_get_pixel_number(*mode, 0); + xds = sti_vtg_get_pixel_number(*mode, mode->hdisplay - 1); + + DRM_DEBUG_DRIVER("%s active video area xdo:%d ydo:%d xds:%d yds:%d\n", + sti_mixer_to_str(mixer), xdo, ydo, xds, yds); + sti_mixer_reg_write(mixer, GAM_MIXER_AVO, ydo << 16 | xdo); + sti_mixer_reg_write(mixer, GAM_MIXER_AVS, yds << 16 | xds); + + sti_mixer_set_background_color(mixer, 0xFF, 0, 0); + + sti_mixer_set_background_area(mixer, mode); + sti_mixer_set_background_status(mixer, true); + return 0; +} + +static u32 sti_mixer_get_layer_mask(struct sti_layer *layer) +{ + switch (layer->desc) { + case STI_BACK: + return GAM_CTL_BACK_MASK; + case STI_GDP_0: + return GAM_CTL_GDP0_MASK; + case STI_GDP_1: + return GAM_CTL_GDP1_MASK; + case STI_GDP_2: + return GAM_CTL_GDP2_MASK; + case STI_GDP_3: + return GAM_CTL_GDP3_MASK; + case STI_VID_0: + return GAM_CTL_VID0_MASK; + case STI_VID_1: + return GAM_CTL_VID1_MASK; + default: + return 0; + } +} + +int sti_mixer_set_layer_status(struct sti_mixer *mixer, + struct sti_layer *layer, bool status) +{ + u32 mask, val; + + mask = sti_mixer_get_layer_mask(layer); + if (!mask) { + DRM_ERROR("Can not find layer mask\n"); + return -EINVAL; + } + + val = sti_mixer_reg_read(mixer, GAM_MIXER_CTL); + val &= ~mask; + val |= status ? mask : 0; + sti_mixer_reg_write(mixer, GAM_MIXER_CTL, val); + + return 0; +} + +void sti_mixer_set_matrix(struct sti_mixer *mixer) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(MixerColorSpaceMatIdentity); i++) + sti_mixer_reg_write(mixer, GAM_MIXER_MX0 + (i * 4), + MixerColorSpaceMatIdentity[i]); +} + +struct sti_mixer *sti_mixer_create(struct device *dev, int id, + void __iomem *baseaddr) +{ + struct sti_mixer *mixer = devm_kzalloc(dev, sizeof(*mixer), GFP_KERNEL); + struct device_node *np = dev->of_node; + + dev_dbg(dev, "%s\n", __func__); + if (!mixer) { + DRM_ERROR("Failed to allocated memory for mixer\n"); + return NULL; + } + mixer->regs = baseaddr; + mixer->dev = dev; + mixer->id = id; + + if (of_device_is_compatible(np, "st,stih416-compositor")) + sti_mixer_set_matrix(mixer); + + DRM_DEBUG_DRIVER("%s created. Regs=%p\n", + sti_mixer_to_str(mixer), mixer->regs); + + return mixer; +} diff --git a/drivers/gpu/drm/sti/sti_mixer.h b/drivers/gpu/drm/sti/sti_mixer.h new file mode 100644 index 0000000..2d86378 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_mixer.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_MIXER_H_ +#define _STI_MIXER_H_ + +#include <drm/drmP.h> + +#include "sti_layer.h" + +#define to_sti_mixer(x) container_of(x, struct sti_mixer, drm_crtc) + +/** + * STI Mixer subdevice structure + * + * @dev: driver device + * @regs: mixer registers + * @id: id of the mixer + * @drm_crtc: crtc object link to the mixer + */ +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
On Thu, May 29, 2014 at 2:37 AM, Benjamin Gaignard benjamin.gaignard@linaro.org wrote:
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 auxillary path
Mixers are part of Compositor hardware block
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org
drivers/gpu/drm/sti/Makefile | 1 + drivers/gpu/drm/sti/sti_mixer.c | 243 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_mixer.h | 52 +++++++++ 3 files changed, 296 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 bb0f6b3..ce03e15 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -6,5 +6,6 @@ obj-$(CONFIG_DRM_STI) += \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ sti_tvout.o \
sti_mixer.o \ sti_gdp.o \ sti_vid.o
\ No newline at end of file diff --git a/drivers/gpu/drm/sti/sti_mixer.c b/drivers/gpu/drm/sti/sti_mixer.c new file mode 100644 index 0000000..c812c68 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_mixer.c @@ -0,0 +1,243 @@ +/*
- Copyright (C) STMicroelectronics SA 2014
- Authors: Benjamin Gaignard benjamin.gaignard@st.com
Fabien Dessenne <fabien.dessenne@st.com>
for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#include "sti_mixer.h" +#include "sti_vtg.h"
+/* Identity: G=Y , B=Cb , R=Cr */ +static const u32 MixerColorSpaceMatIdentity[] = {
minor nit: CamelCase..
BR, -R
0x10000000, 0x00000000, 0x10000000, 0x00001000,
0x00000000, 0x00000000, 0x00000000, 0x00000000
+};
+/* regs offset */ +#define GAM_MIXER_CTL 0x00 +#define GAM_MIXER_BKC 0x04 +#define GAM_MIXER_BCO 0x0C +#define GAM_MIXER_BCS 0x10 +#define GAM_MIXER_AVO 0x28 +#define GAM_MIXER_AVS 0x2C +#define GAM_MIXER_CRB 0x34 +#define GAM_MIXER_ACT 0x38 +#define GAM_MIXER_MBP 0x3C +#define GAM_MIXER_MX0 0x80
+/* id for depth of CRB reg */ +#define GAM_DEPTH_VID0_ID 1 +#define GAM_DEPTH_VID1_ID 2 +#define GAM_DEPTH_GDP0_ID 3 +#define GAM_DEPTH_GDP1_ID 4 +#define GAM_DEPTH_GDP2_ID 5 +#define GAM_DEPTH_GDP3_ID 6 +#define GAM_DEPTH_MASK_ID 7
+/* mask in CTL reg */ +#define GAM_CTL_BACK_MASK BIT(0) +#define GAM_CTL_VID0_MASK BIT(1) +#define GAM_CTL_VID1_MASK BIT(2) +#define GAM_CTL_GDP0_MASK BIT(3) +#define GAM_CTL_GDP1_MASK BIT(4) +#define GAM_CTL_GDP2_MASK BIT(5) +#define GAM_CTL_GDP3_MASK BIT(6)
+const char *sti_mixer_to_str(struct sti_mixer *mixer) +{
switch (mixer->id) {
case STI_MIXER_MAIN:
return "MAIN_MIXER";
case STI_MIXER_AUX:
return "AUX_MIXER";
default:
return "<UNKNOWN MIXER>";
}
+}
+static inline u32 sti_mixer_reg_read(struct sti_mixer *mixer, u32 reg_id) +{
return readl(mixer->regs + reg_id);
+}
+static inline void sti_mixer_reg_write(struct sti_mixer *mixer,
u32 reg_id, u32 val)
+{
writel(val, mixer->regs + reg_id);
+}
+void sti_mixer_set_background_status(struct sti_mixer *mixer, bool enable) +{
u32 val = sti_mixer_reg_read(mixer, GAM_MIXER_CTL);
val &= ~GAM_CTL_BACK_MASK;
val |= enable;
sti_mixer_reg_write(mixer, GAM_MIXER_CTL, val);
+}
+static void sti_mixer_set_background_color(struct sti_mixer *mixer,
u8 red, u8 green, u8 blue)
+{
u32 val = (red << 16) | (green << 8) | blue;
sti_mixer_reg_write(mixer, GAM_MIXER_BKC, val);
+}
+static void sti_mixer_set_background_area(struct sti_mixer *mixer,
struct drm_display_mode *mode)
+{
u32 ydo, xdo, yds, xds;
ydo = sti_vtg_get_line_number(*mode, 0);
yds = sti_vtg_get_line_number(*mode, mode->vdisplay - 1);
xdo = sti_vtg_get_pixel_number(*mode, 0);
xds = sti_vtg_get_pixel_number(*mode, mode->hdisplay - 1);
sti_mixer_reg_write(mixer, GAM_MIXER_BCO, ydo << 16 | xdo);
sti_mixer_reg_write(mixer, GAM_MIXER_BCS, yds << 16 | xds);
+}
+int sti_mixer_set_layer_depth(struct sti_mixer *mixer, struct sti_layer *layer) +{
int layer_id = 0, depth = layer->zorder;
u32 mask, val;
if (depth >= GAM_MIXER_NB_DEPTH_LEVEL)
return 1;
switch (layer->desc) {
case STI_GDP_0:
layer_id = GAM_DEPTH_GDP0_ID;
break;
case STI_GDP_1:
layer_id = GAM_DEPTH_GDP1_ID;
break;
case STI_GDP_2:
layer_id = GAM_DEPTH_GDP2_ID;
break;
case STI_GDP_3:
layer_id = GAM_DEPTH_GDP3_ID;
break;
case STI_VID_0:
layer_id = GAM_DEPTH_VID0_ID;
break;
case STI_VID_1:
layer_id = GAM_DEPTH_VID1_ID;
break;
default:
DRM_ERROR("Unknown layer %d\n", layer->desc);
return 1;
}
mask = GAM_DEPTH_MASK_ID << (3 * depth);
layer_id = layer_id << (3 * depth);
dev_dbg(mixer->dev, "GAM_MIXER_CRB val 0x%x mask 0x%x\n",
layer_id, mask);
val = sti_mixer_reg_read(mixer, GAM_MIXER_CRB);
val &= ~mask;
val |= layer_id;
sti_mixer_reg_write(mixer, GAM_MIXER_CRB, val);
dev_dbg(mixer->dev, "Read GAM_MIXER_CRB 0x%x\n",
sti_mixer_reg_read(mixer, GAM_MIXER_CRB));
return 0;
+}
+int sti_mixer_active_video_area(struct sti_mixer *mixer,
struct drm_display_mode *mode)
+{
u32 ydo, xdo, yds, xds;
ydo = sti_vtg_get_line_number(*mode, 0);
yds = sti_vtg_get_line_number(*mode, mode->vdisplay - 1);
xdo = sti_vtg_get_pixel_number(*mode, 0);
xds = sti_vtg_get_pixel_number(*mode, mode->hdisplay - 1);
DRM_DEBUG_DRIVER("%s active video area xdo:%d ydo:%d xds:%d yds:%d\n",
sti_mixer_to_str(mixer), xdo, ydo, xds, yds);
sti_mixer_reg_write(mixer, GAM_MIXER_AVO, ydo << 16 | xdo);
sti_mixer_reg_write(mixer, GAM_MIXER_AVS, yds << 16 | xds);
sti_mixer_set_background_color(mixer, 0xFF, 0, 0);
sti_mixer_set_background_area(mixer, mode);
sti_mixer_set_background_status(mixer, true);
return 0;
+}
+static u32 sti_mixer_get_layer_mask(struct sti_layer *layer) +{
switch (layer->desc) {
case STI_BACK:
return GAM_CTL_BACK_MASK;
case STI_GDP_0:
return GAM_CTL_GDP0_MASK;
case STI_GDP_1:
return GAM_CTL_GDP1_MASK;
case STI_GDP_2:
return GAM_CTL_GDP2_MASK;
case STI_GDP_3:
return GAM_CTL_GDP3_MASK;
case STI_VID_0:
return GAM_CTL_VID0_MASK;
case STI_VID_1:
return GAM_CTL_VID1_MASK;
default:
return 0;
}
+}
+int sti_mixer_set_layer_status(struct sti_mixer *mixer,
struct sti_layer *layer, bool status)
+{
u32 mask, val;
mask = sti_mixer_get_layer_mask(layer);
if (!mask) {
DRM_ERROR("Can not find layer mask\n");
return -EINVAL;
}
val = sti_mixer_reg_read(mixer, GAM_MIXER_CTL);
val &= ~mask;
val |= status ? mask : 0;
sti_mixer_reg_write(mixer, GAM_MIXER_CTL, val);
return 0;
+}
+void sti_mixer_set_matrix(struct sti_mixer *mixer) +{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(MixerColorSpaceMatIdentity); i++)
sti_mixer_reg_write(mixer, GAM_MIXER_MX0 + (i * 4),
MixerColorSpaceMatIdentity[i]);
+}
+struct sti_mixer *sti_mixer_create(struct device *dev, int id,
void __iomem *baseaddr)
+{
struct sti_mixer *mixer = devm_kzalloc(dev, sizeof(*mixer), GFP_KERNEL);
struct device_node *np = dev->of_node;
dev_dbg(dev, "%s\n", __func__);
if (!mixer) {
DRM_ERROR("Failed to allocated memory for mixer\n");
return NULL;
}
mixer->regs = baseaddr;
mixer->dev = dev;
mixer->id = id;
if (of_device_is_compatible(np, "st,stih416-compositor"))
sti_mixer_set_matrix(mixer);
DRM_DEBUG_DRIVER("%s created. Regs=%p\n",
sti_mixer_to_str(mixer), mixer->regs);
return mixer;
+} diff --git a/drivers/gpu/drm/sti/sti_mixer.h b/drivers/gpu/drm/sti/sti_mixer.h new file mode 100644 index 0000000..2d86378 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_mixer.h @@ -0,0 +1,52 @@ +/*
- Copyright (C) STMicroelectronics SA 2014
- Authors: Benjamin Gaignard benjamin.gaignard@st.com
Fabien Dessenne <fabien.dessenne@st.com>
for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#ifndef _STI_MIXER_H_ +#define _STI_MIXER_H_
+#include <drm/drmP.h>
+#include "sti_layer.h"
+#define to_sti_mixer(x) container_of(x, struct sti_mixer, drm_crtc)
+/**
- STI Mixer subdevice structure
- @dev: driver device
- @regs: mixer registers
- @id: id of the mixer
- @drm_crtc: crtc object link to the mixer
- */
+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
1.9.1
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
Compositor control all the input sub-device (VID, GDP) and the mixer(s). It is the main entry point for composition. Layer interface is used to control the abstracted layers.
Add debug in mixer, GDP and VID
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Kconfig | 1 + drivers/gpu/drm/sti/Makefile | 2 + drivers/gpu/drm/sti/sti_compositor.c | 220 ++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_compositor.h | 90 ++++++++++ drivers/gpu/drm/sti/sti_gdp.c | 30 ++++ drivers/gpu/drm/sti/sti_layer.c | 312 +++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_mixer.c | 6 + drivers/gpu/drm/sti/sti_vid.c | 7 + 8 files changed, 668 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 cbd664b..1013570 100644 --- a/drivers/gpu/drm/sti/Kconfig +++ b/drivers/gpu/drm/sti/Kconfig @@ -1,5 +1,6 @@ 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 ce03e15..a0612cc 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -1,11 +1,13 @@ obj-$(CONFIG_DRM_STI) += \ sti_vtg.o \ sti_vtac.o \ + sti_compositor.o \ sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ sti_tvout.o \ + sti_layer.o \ sti_mixer.o \ sti_gdp.o \ sti_vid.o \ No newline at end of file diff --git a/drivers/gpu/drm/sti/sti_compositor.c b/drivers/gpu/drm/sti/sti_compositor.c new file mode 100644 index 0000000..3100917 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_compositor.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <drm/drmP.h> + +#include "sti_compositor.h" +#include "sti_gdp.h" +#include "sti_vtg.h" + +/* + * stiH407 compositor properties + */ +struct sti_compositor_data stih407_compositor_data = { + .nb_subdev = 6, + .subdev_desc = { + {STI_GPD_SUBDEV, (int)STI_GDP_0, 0x100}, + {STI_GPD_SUBDEV, (int)STI_GDP_1, 0x200}, + {STI_GPD_SUBDEV, (int)STI_GDP_2, 0x300}, + {STI_GPD_SUBDEV, (int)STI_GDP_3, 0x400}, + {STI_VID_SUBDEV, (int)STI_VID_0, 0x700}, + {STI_MIXER_MAIN_SUBDEV, STI_MIXER_MAIN, 0xC00} + }, +}; + +/* + * stiH416 compositor properties + * Note: + * on stih416 MIXER_AUX has a different base address from MIXER_MAIN + * Moreover, GDPx is different for Main and Aux Mixer. So this subdev map does + * not fit for stiH416 if we want to enable the MIXER_AUX. + */ +struct sti_compositor_data stih416_compositor_data = { + .nb_subdev = 3, + .subdev_desc = { + {STI_GPD_SUBDEV, (int)STI_GDP_0, 0x100}, + {STI_GPD_SUBDEV, (int)STI_GDP_1, 0x200}, + {STI_MIXER_MAIN_SUBDEV, STI_MIXER_MAIN, 0xC00} + }, +}; + +static int sti_compositor_init_subdev(struct sti_compositor *compo, + struct sti_compositor_subdev_descriptor *desc, + unsigned int array_size) +{ + unsigned int i, mixer_id = 0, layer_id = 0; + + for (i = 0; i < array_size; i++) { + switch (desc[i].type) { + case STI_MIXER_MAIN_SUBDEV: + case STI_MIXER_AUX_SUBDEV: + compo->mixer[mixer_id++] = + sti_mixer_create(compo->dev, desc[i].id, + compo->regs + desc[i].offset); + break; + case STI_GPD_SUBDEV: + case STI_VID_SUBDEV: + compo->layer[layer_id++] = + sti_layer_create(compo->dev, desc[i].id, + compo->regs + desc[i].offset); + break; + /* case STI_CURSOR_SUBDEV : TODO */ + default: + DRM_ERROR("Unknow subdev compoment type\n"); + return 1; + } + + } + compo->nb_mixers = mixer_id; + compo->nb_layers = layer_id; + + return 0; +} + +static int sti_compositor_bind(struct device *dev, struct device *master, + void *data) +{ + /* to be filled with drm driver functions */ + return 0; +} + +static void sti_compositor_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops sti_compositor_ops = { + .bind = sti_compositor_bind, + .unbind = sti_compositor_unbind, +}; + +static const struct of_device_id compositor_of_match[] = { + { + .compatible = "st,stih416-compositor", + .data = &stih416_compositor_data, + }, { + .compatible = "st,stih407-compositor", + .data = &stih407_compositor_data, + }, { + /* end node */ + } +}; +MODULE_DEVICE_TABLE(of, compositor_of_match); + +static int sti_compositor_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *vtg_np; + struct sti_compositor *compo; + struct resource *res; + int err; + + compo = devm_kzalloc(dev, sizeof(*compo), GFP_KERNEL); + if (!compo) { + DRM_ERROR("Failed to allocate compositor context\n"); + return -ENOMEM; + } + compo->dev = dev; + + /* populate data structure depending on compatibility */ + BUG_ON(!of_match_node(compositor_of_match, np)->data); + + memcpy(&compo->data, of_match_node(compositor_of_match, np)->data, + sizeof(struct sti_compositor_data)); + + /* Get Memory ressources */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + DRM_ERROR("Get memory resource failed\n"); + return -ENXIO; + } + compo->regs = devm_ioremap(dev, res->start, resource_size(res)); + if (compo->regs == NULL) { + DRM_ERROR("Register mapping failed\n"); + return -ENXIO; + } + + /* Get clock resources */ + compo->clk_compo_main = devm_clk_get(dev, "compo_main"); + if (IS_ERR(compo->clk_compo_main)) { + DRM_ERROR("Cannot get compo_main clock\n"); + return PTR_ERR(compo->clk_compo_main); + } + + compo->clk_compo_aux = devm_clk_get(dev, "compo_aux"); + if (IS_ERR(compo->clk_compo_aux)) { + DRM_ERROR("Cannot get compo_aux clock\n"); + return PTR_ERR(compo->clk_compo_aux); + } + + compo->clk_pix_main = devm_clk_get(dev, "pix_main"); + if (IS_ERR(compo->clk_pix_main)) { + DRM_ERROR("Cannot get pix_main clock\n"); + return PTR_ERR(compo->clk_pix_main); + } + + compo->clk_pix_aux = devm_clk_get(dev, "pix_aux"); + if (IS_ERR(compo->clk_pix_aux)) { + DRM_ERROR("Cannot get pix_aux clock\n"); + return PTR_ERR(compo->clk_pix_aux); + } + + /* Get reset resources */ + compo->rst_main = devm_reset_control_get(dev, "compo-main"); + /* Take compo main out of reset */ + if (!IS_ERR(compo->rst_main)) + reset_control_deassert(compo->rst_main); + + compo->rst_aux = devm_reset_control_get(dev, "compo-aux"); + /* Take compo aux out of reset */ + if (!IS_ERR(compo->rst_aux)) + reset_control_deassert(compo->rst_aux); + + vtg_np = of_parse_phandle(pdev->dev.of_node, "st,vtg", 0); + if (vtg_np) + compo->vtg_main = of_vtg_find(vtg_np); + + vtg_np = of_parse_phandle(pdev->dev.of_node, "st,vtg", 1); + if (vtg_np) + compo->vtg_aux = of_vtg_find(vtg_np); + + /* Initialize compositor subdevices */ + err = sti_compositor_init_subdev(compo, compo->data.subdev_desc, + compo->data.nb_subdev); + if (err) + return err; + + platform_set_drvdata(pdev, compo); + + return component_add(&pdev->dev, &sti_compositor_ops); +} + +static int sti_compositor_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sti_compositor_ops); + return 0; +} + +static struct platform_driver sti_compositor_driver = { + .driver = { + .name = "sti-compositor", + .owner = THIS_MODULE, + .of_match_table = compositor_of_match, + }, + .probe = sti_compositor_probe, + .remove = sti_compositor_remove, +}; + +module_platform_driver(sti_compositor_driver); diff --git a/drivers/gpu/drm/sti/sti_compositor.h b/drivers/gpu/drm/sti/sti_compositor.h new file mode 100644 index 0000000..3ea19db --- /dev/null +++ b/drivers/gpu/drm/sti/sti_compositor.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_COMPOSITOR_H_ +#define _STI_COMPOSITOR_H_ + +#include <linux/clk.h> +#include <linux/kernel.h> + +#include "sti_layer.h" +#include "sti_mixer.h" + +#define WAIT_NEXT_VSYNC_MS 50 /*ms*/ + +#define STI_MAX_LAYER 8 +#define STI_MAX_MIXER 2 + +enum sti_compositor_subdev_type { + STI_MIXER_MAIN_SUBDEV, + STI_MIXER_AUX_SUBDEV, + STI_GPD_SUBDEV, + STI_VID_SUBDEV, + STI_CURSOR_SUBDEV, +}; + +struct sti_compositor_subdev_descriptor { + enum sti_compositor_subdev_type type; + int id; + unsigned int offset; +}; + +/** + * STI Compositor data structure + * + * @nb_subdev: number of subdevices supported by the compositor + * @subdev_desc: subdev list description + */ +#define MAX_SUBDEV 9 +struct sti_compositor_data { + unsigned int nb_subdev; + struct sti_compositor_subdev_descriptor subdev_desc[MAX_SUBDEV]; +}; + +/** + * STI Compositor structure + * + * @dev: driver device + * @regs: registers (main) + * @data: device data + * @clk_compo_main: clock for main compo + * @clk_compo_aux: clock for aux compo + * @clk_pix_main: pixel clock for main path + * @clk_pix_aux: pixel clock for aux path + * @rst_main: reset control of the main path + * @rst_aux: reset control of the aux path + * @mixer: array of mixers + * @vtg_main: vtg for main data path + * @vtg_aux: vtg for auxillary data path + * @layer: array of layers + * @nb_mixers: number of mixers for this compositor + * @nb_layers: number of layers (GDP,VID,...) for this compositor + * @enable: true if compositor is enable else false + * @vtg_vblank_nb: callback for VTG VSYNC notification + */ +struct sti_compositor { + struct device *dev; + void __iomem *regs; + struct sti_compositor_data data; + struct clk *clk_compo_main; + struct clk *clk_compo_aux; + struct clk *clk_pix_main; + struct clk *clk_pix_aux; + struct reset_control *rst_main; + struct reset_control *rst_aux; + struct sti_mixer *mixer[STI_MAX_MIXER]; + struct sti_vtg *vtg_main; + struct sti_vtg *vtg_aux; + struct sti_layer *layer[STI_MAX_LAYER]; + int nb_mixers; + int nb_layers; + bool enable; + struct notifier_block vtg_vblank_nb; +}; + +#endif diff --git a/drivers/gpu/drm/sti/sti_gdp.c b/drivers/gpu/drm/sti/sti_gdp.c index a014214..af5edc5 100644 --- a/drivers/gpu/drm/sti/sti_gdp.c +++ b/drivers/gpu/drm/sti/sti_gdp.c @@ -9,6 +9,7 @@ #include <linux/clk.h> #include <linux/dma-mapping.h>
+#include "sti_compositor.h" #include "sti_gdp.h" #include "sti_layer.h" #include "sti_vtg.h" @@ -137,6 +138,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 +173,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; }
@@ -189,6 +195,7 @@ static int sti_gdp_prepare_layer(void *lay, bool first_prepare) struct sti_gdp_node *top_field, *btm_field; struct drm_display_mode *mode = layer->mode; struct device *dev = layer->gdp->dev; + struct sti_compositor *compo = dev_get_drvdata(dev); int format; unsigned int depth, bpp; int rate = mode->clock * 1000; @@ -199,6 +206,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; @@ -243,6 +253,14 @@ static int sti_gdp_prepare_layer(void *lay, bool first_prepare) layer->pitches[0];
if (first_prepare) { + /* Register gdp callback */ + if (sti_vtg_register_client(layer->mixer_id == STI_MIXER_MAIN ? + compo->vtg_main : compo->vtg_aux, + &layer->gdp->vtg_field_nb, layer->mixer_id)) { + 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); @@ -288,6 +306,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", @@ -297,6 +318,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); @@ -335,6 +358,9 @@ static int sti_gdp_disable_layer(void *lay) unsigned int i; struct sti_layer *layer = (struct sti_layer *)lay; struct sti_gdp *gdp = layer->gdp; + struct sti_compositor *compo = dev_get_drvdata(layer->gdp->dev); + + DRM_DEBUG_DRIVER("%s\n", sti_layer_to_str(layer));
/* Set the nodes as 'to be ignored on mixer' */ for (i = 0; i < GDP_NODE_NB_BANK; i++) { @@ -342,6 +368,10 @@ static int sti_gdp_disable_layer(void *lay) gdp->node_list[i].btm_field->gam_gdp_ppt |= GAM_GDP_PPT_IGNORE; }
+ if (sti_vtg_unregister_client(layer->mixer_id == STI_MIXER_MAIN ? + compo->vtg_main : compo->vtg_aux, &gdp->vtg_field_nb)) + DRM_DEBUG_DRIVER("Warning: cannot unregister VTG notifier\n"); + if (gdp->clk_pix) clk_disable_unprepare(gdp->clk_pix);
diff --git a/drivers/gpu/drm/sti/sti_layer.c b/drivers/gpu/drm/sti/sti_layer.c new file mode 100644 index 0000000..cc7186d --- /dev/null +++ b/drivers/gpu/drm/sti/sti_layer.c @@ -0,0 +1,312 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <drm/drmP.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_cma_helper.h> + +#include "sti_compositor.h" +#include "sti_layer.h" + +#define STI_FPS_INTERVAL_MS 3000 + +struct sti_layer *sti_layer_find_layer(struct sti_layer *layer[], + enum sti_layer_desc desc) +{ + unsigned 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); + s64 tmp_ns = timespec_to_ns(&tmp_ts); + + do_div(tmp_ns, NSEC_PER_MSEC); + + return (int) 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; + unsigned int i; + struct drm_gem_cma_object *cma_obj; + + if (!layer || !fb || !mode) { + DRM_ERROR("Null fb, layer or mode\n"); + return 1; + } + + cma_obj = drm_fb_cma_get_gem_obj(fb, 0); + if (!cma_obj) { + DRM_ERROR("Can't get CMA GEM object for fb\n"); + return 1; + } + + layer->fb = fb; + layer->mode = mode; + layer->mixer_id = mixer_id; + layer->dst_x = dest_x; + layer->dst_y = dest_y; + layer->dst_w = clamp_val(dest_w, 0, mode->crtc_hdisplay - dest_x); + layer->dst_h = clamp_val(dest_h, 0, mode->crtc_vdisplay - dest_y); + layer->src_x = src_x; + layer->src_y = src_y; + layer->src_w = src_w; + layer->src_h = src_h; + layer->format = fb->pixel_format; + layer->paddr = cma_obj->paddr; + for (i = 0; i < 4; i++) { + layer->pitches[i] = fb->pitches[i]; + layer->offsets[i] = fb->offsets[i]; + } + + DRM_DEBUG_DRIVER("%s is associated with mixer_id %d\n", + sti_layer_to_str(layer), + layer->mixer_id); + DRM_DEBUG_DRIVER("%s dst=(%dx%d)@(%d,%d) - src=(%dx%d)@(%d,%d)\n", + sti_layer_to_str(layer), + layer->dst_w, layer->dst_h, layer->dst_x, layer->dst_y, + layer->src_w, layer->src_h, layer->src_x, + layer->src_y); + + DRM_DEBUG_DRIVER("drm FB:%d format:%.4s phys@:0x%lx\n", fb->base.id, + (char *)&layer->format, (unsigned long)layer->paddr); + + /* 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(); +} + +unsigned int sti_layer_get_nb_formats(struct sti_layer *layer) +{ + unsigned 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(); +} diff --git a/drivers/gpu/drm/sti/sti_mixer.c b/drivers/gpu/drm/sti/sti_mixer.c index c812c68..c928d85 100644 --- a/drivers/gpu/drm/sti/sti_mixer.c +++ b/drivers/gpu/drm/sti/sti_mixer.c @@ -6,6 +6,7 @@ * License terms: GNU General Public License (GPL), version 2 */
+#include "sti_compositor.h" #include "sti_mixer.h" #include "sti_vtg.h"
@@ -133,6 +134,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_CRB val 0x%x mask 0x%x\n", layer_id, mask);
@@ -195,6 +198,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"); diff --git a/drivers/gpu/drm/sti/sti_vid.c b/drivers/gpu/drm/sti/sti_vid.c index 0197291..f75bda2 100644 --- a/drivers/gpu/drm/sti/sti_vid.c +++ b/drivers/gpu/drm/sti/sti_vid.c @@ -6,6 +6,7 @@
#include <drm/drmP.h>
+#include "sti_compositor.h" #include "sti_layer.h" #include "sti_vid.h" #include "sti_vtg.h" @@ -49,6 +50,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 +67,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 +86,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;
On Thu, May 29, 2014 at 2:37 AM, Benjamin Gaignard benjamin.gaignard@linaro.org wrote:
Compositor control all the input sub-device (VID, GDP) and the mixer(s). It is the main entry point for composition. Layer interface is used to control the abstracted layers.
Add debug in mixer, GDP and VID
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org
drivers/gpu/drm/sti/Kconfig | 1 + drivers/gpu/drm/sti/Makefile | 2 + drivers/gpu/drm/sti/sti_compositor.c | 220 ++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_compositor.h | 90 ++++++++++ drivers/gpu/drm/sti/sti_gdp.c | 30 ++++ drivers/gpu/drm/sti/sti_layer.c | 312 +++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_mixer.c | 6 + drivers/gpu/drm/sti/sti_vid.c | 7 + 8 files changed, 668 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 cbd664b..1013570 100644 --- a/drivers/gpu/drm/sti/Kconfig +++ b/drivers/gpu/drm/sti/Kconfig @@ -1,5 +1,6 @@ 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 ce03e15..a0612cc 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -1,11 +1,13 @@ obj-$(CONFIG_DRM_STI) += \ sti_vtg.o \ sti_vtac.o \
sti_compositor.o \ sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ sti_tvout.o \
sti_layer.o \ sti_mixer.o \ sti_gdp.o \ sti_vid.o
\ No newline at end of file diff --git a/drivers/gpu/drm/sti/sti_compositor.c b/drivers/gpu/drm/sti/sti_compositor.c new file mode 100644 index 0000000..3100917 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_compositor.c @@ -0,0 +1,220 @@ +/*
- Copyright (C) STMicroelectronics SA 2014
- Authors: Benjamin Gaignard benjamin.gaignard@st.com
Fabien Dessenne <fabien.dessenne@st.com>
for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/reset.h>
+#include <drm/drmP.h>
+#include "sti_compositor.h" +#include "sti_gdp.h" +#include "sti_vtg.h"
+/*
- stiH407 compositor properties
- */
+struct sti_compositor_data stih407_compositor_data = {
.nb_subdev = 6,
.subdev_desc = {
{STI_GPD_SUBDEV, (int)STI_GDP_0, 0x100},
{STI_GPD_SUBDEV, (int)STI_GDP_1, 0x200},
{STI_GPD_SUBDEV, (int)STI_GDP_2, 0x300},
{STI_GPD_SUBDEV, (int)STI_GDP_3, 0x400},
{STI_VID_SUBDEV, (int)STI_VID_0, 0x700},
{STI_MIXER_MAIN_SUBDEV, STI_MIXER_MAIN, 0xC00}
},
+};
+/*
- stiH416 compositor properties
- Note:
- on stih416 MIXER_AUX has a different base address from MIXER_MAIN
- Moreover, GDPx is different for Main and Aux Mixer. So this subdev map does
- not fit for stiH416 if we want to enable the MIXER_AUX.
- */
+struct sti_compositor_data stih416_compositor_data = {
.nb_subdev = 3,
.subdev_desc = {
{STI_GPD_SUBDEV, (int)STI_GDP_0, 0x100},
{STI_GPD_SUBDEV, (int)STI_GDP_1, 0x200},
{STI_MIXER_MAIN_SUBDEV, STI_MIXER_MAIN, 0xC00}
},
+};
+static int sti_compositor_init_subdev(struct sti_compositor *compo,
struct sti_compositor_subdev_descriptor *desc,
unsigned int array_size)
+{
unsigned int i, mixer_id = 0, layer_id = 0;
for (i = 0; i < array_size; i++) {
switch (desc[i].type) {
case STI_MIXER_MAIN_SUBDEV:
case STI_MIXER_AUX_SUBDEV:
compo->mixer[mixer_id++] =
sti_mixer_create(compo->dev, desc[i].id,
compo->regs + desc[i].offset);
break;
case STI_GPD_SUBDEV:
case STI_VID_SUBDEV:
compo->layer[layer_id++] =
sti_layer_create(compo->dev, desc[i].id,
compo->regs + desc[i].offset);
break;
/* case STI_CURSOR_SUBDEV : TODO */
default:
DRM_ERROR("Unknow subdev compoment type\n");
return 1;
}
}
compo->nb_mixers = mixer_id;
compo->nb_layers = layer_id;
return 0;
+}
+static int sti_compositor_bind(struct device *dev, struct device *master,
void *data)
+{
/* to be filled with drm driver functions */
return 0;
+}
+static void sti_compositor_unbind(struct device *dev, struct device *master,
void *data)
+{
/* do nothing */
+}
+static const struct component_ops sti_compositor_ops = {
.bind = sti_compositor_bind,
.unbind = sti_compositor_unbind,
+};
+static const struct of_device_id compositor_of_match[] = {
{
.compatible = "st,stih416-compositor",
.data = &stih416_compositor_data,
}, {
.compatible = "st,stih407-compositor",
.data = &stih407_compositor_data,
}, {
/* end node */
}
+}; +MODULE_DEVICE_TABLE(of, compositor_of_match);
+static int sti_compositor_probe(struct platform_device *pdev) +{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct device_node *vtg_np;
struct sti_compositor *compo;
struct resource *res;
int err;
compo = devm_kzalloc(dev, sizeof(*compo), GFP_KERNEL);
if (!compo) {
DRM_ERROR("Failed to allocate compositor context\n");
return -ENOMEM;
}
compo->dev = dev;
/* populate data structure depending on compatibility */
BUG_ON(!of_match_node(compositor_of_match, np)->data);
memcpy(&compo->data, of_match_node(compositor_of_match, np)->data,
sizeof(struct sti_compositor_data));
/* Get Memory ressources */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
DRM_ERROR("Get memory resource failed\n");
return -ENXIO;
}
compo->regs = devm_ioremap(dev, res->start, resource_size(res));
if (compo->regs == NULL) {
DRM_ERROR("Register mapping failed\n");
return -ENXIO;
}
/* Get clock resources */
compo->clk_compo_main = devm_clk_get(dev, "compo_main");
if (IS_ERR(compo->clk_compo_main)) {
DRM_ERROR("Cannot get compo_main clock\n");
return PTR_ERR(compo->clk_compo_main);
}
compo->clk_compo_aux = devm_clk_get(dev, "compo_aux");
if (IS_ERR(compo->clk_compo_aux)) {
DRM_ERROR("Cannot get compo_aux clock\n");
return PTR_ERR(compo->clk_compo_aux);
}
compo->clk_pix_main = devm_clk_get(dev, "pix_main");
if (IS_ERR(compo->clk_pix_main)) {
DRM_ERROR("Cannot get pix_main clock\n");
return PTR_ERR(compo->clk_pix_main);
}
compo->clk_pix_aux = devm_clk_get(dev, "pix_aux");
if (IS_ERR(compo->clk_pix_aux)) {
DRM_ERROR("Cannot get pix_aux clock\n");
return PTR_ERR(compo->clk_pix_aux);
}
/* Get reset resources */
compo->rst_main = devm_reset_control_get(dev, "compo-main");
/* Take compo main out of reset */
if (!IS_ERR(compo->rst_main))
reset_control_deassert(compo->rst_main);
compo->rst_aux = devm_reset_control_get(dev, "compo-aux");
/* Take compo aux out of reset */
if (!IS_ERR(compo->rst_aux))
reset_control_deassert(compo->rst_aux);
vtg_np = of_parse_phandle(pdev->dev.of_node, "st,vtg", 0);
if (vtg_np)
compo->vtg_main = of_vtg_find(vtg_np);
vtg_np = of_parse_phandle(pdev->dev.of_node, "st,vtg", 1);
if (vtg_np)
compo->vtg_aux = of_vtg_find(vtg_np);
/* Initialize compositor subdevices */
err = sti_compositor_init_subdev(compo, compo->data.subdev_desc,
compo->data.nb_subdev);
if (err)
return err;
platform_set_drvdata(pdev, compo);
return component_add(&pdev->dev, &sti_compositor_ops);
+}
+static int sti_compositor_remove(struct platform_device *pdev) +{
component_del(&pdev->dev, &sti_compositor_ops);
return 0;
+}
+static struct platform_driver sti_compositor_driver = {
.driver = {
.name = "sti-compositor",
.owner = THIS_MODULE,
.of_match_table = compositor_of_match,
},
.probe = sti_compositor_probe,
.remove = sti_compositor_remove,
+};
+module_platform_driver(sti_compositor_driver); diff --git a/drivers/gpu/drm/sti/sti_compositor.h b/drivers/gpu/drm/sti/sti_compositor.h new file mode 100644 index 0000000..3ea19db --- /dev/null +++ b/drivers/gpu/drm/sti/sti_compositor.h @@ -0,0 +1,90 @@ +/*
- Copyright (C) STMicroelectronics SA 2014
- Authors: Benjamin Gaignard benjamin.gaignard@st.com
Fabien Dessenne <fabien.dessenne@st.com>
for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#ifndef _STI_COMPOSITOR_H_ +#define _STI_COMPOSITOR_H_
+#include <linux/clk.h> +#include <linux/kernel.h>
+#include "sti_layer.h" +#include "sti_mixer.h"
+#define WAIT_NEXT_VSYNC_MS 50 /*ms*/
+#define STI_MAX_LAYER 8 +#define STI_MAX_MIXER 2
+enum sti_compositor_subdev_type {
STI_MIXER_MAIN_SUBDEV,
STI_MIXER_AUX_SUBDEV,
STI_GPD_SUBDEV,
STI_VID_SUBDEV,
STI_CURSOR_SUBDEV,
+};
+struct sti_compositor_subdev_descriptor {
enum sti_compositor_subdev_type type;
int id;
unsigned int offset;
+};
+/**
- STI Compositor data structure
- @nb_subdev: number of subdevices supported by the compositor
- @subdev_desc: subdev list description
- */
+#define MAX_SUBDEV 9 +struct sti_compositor_data {
unsigned int nb_subdev;
struct sti_compositor_subdev_descriptor subdev_desc[MAX_SUBDEV];
+};
+/**
- STI Compositor structure
- @dev: driver device
- @regs: registers (main)
- @data: device data
- @clk_compo_main: clock for main compo
- @clk_compo_aux: clock for aux compo
- @clk_pix_main: pixel clock for main path
- @clk_pix_aux: pixel clock for aux path
- @rst_main: reset control of the main path
- @rst_aux: reset control of the aux path
- @mixer: array of mixers
- @vtg_main: vtg for main data path
- @vtg_aux: vtg for auxillary data path
- @layer: array of layers
- @nb_mixers: number of mixers for this compositor
- @nb_layers: number of layers (GDP,VID,...) for this compositor
- @enable: true if compositor is enable else false
- @vtg_vblank_nb: callback for VTG VSYNC notification
- */
+struct sti_compositor {
struct device *dev;
void __iomem *regs;
struct sti_compositor_data data;
struct clk *clk_compo_main;
struct clk *clk_compo_aux;
struct clk *clk_pix_main;
struct clk *clk_pix_aux;
struct reset_control *rst_main;
struct reset_control *rst_aux;
struct sti_mixer *mixer[STI_MAX_MIXER];
struct sti_vtg *vtg_main;
struct sti_vtg *vtg_aux;
struct sti_layer *layer[STI_MAX_LAYER];
int nb_mixers;
int nb_layers;
bool enable;
struct notifier_block vtg_vblank_nb;
+};
+#endif diff --git a/drivers/gpu/drm/sti/sti_gdp.c b/drivers/gpu/drm/sti/sti_gdp.c index a014214..af5edc5 100644 --- a/drivers/gpu/drm/sti/sti_gdp.c +++ b/drivers/gpu/drm/sti/sti_gdp.c @@ -9,6 +9,7 @@ #include <linux/clk.h> #include <linux/dma-mapping.h>
+#include "sti_compositor.h" #include "sti_gdp.h" #include "sti_layer.h" #include "sti_vtg.h" @@ -137,6 +138,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 +173,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;
}
@@ -189,6 +195,7 @@ static int sti_gdp_prepare_layer(void *lay, bool first_prepare) struct sti_gdp_node *top_field, *btm_field; struct drm_display_mode *mode = layer->mode; struct device *dev = layer->gdp->dev;
struct sti_compositor *compo = dev_get_drvdata(dev); int format; unsigned int depth, bpp; int rate = mode->clock * 1000;
@@ -199,6 +206,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;
@@ -243,6 +253,14 @@ static int sti_gdp_prepare_layer(void *lay, bool first_prepare) layer->pitches[0];
if (first_prepare) {
/* Register gdp callback */
if (sti_vtg_register_client(layer->mixer_id == STI_MIXER_MAIN ?
compo->vtg_main : compo->vtg_aux,
&layer->gdp->vtg_field_nb, layer->mixer_id)) {
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);
@@ -288,6 +306,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",
@@ -297,6 +318,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);
@@ -335,6 +358,9 @@ static int sti_gdp_disable_layer(void *lay) unsigned int i; struct sti_layer *layer = (struct sti_layer *)lay; struct sti_gdp *gdp = layer->gdp;
struct sti_compositor *compo = dev_get_drvdata(layer->gdp->dev);
DRM_DEBUG_DRIVER("%s\n", sti_layer_to_str(layer)); /* Set the nodes as 'to be ignored on mixer' */ for (i = 0; i < GDP_NODE_NB_BANK; i++) {
@@ -342,6 +368,10 @@ static int sti_gdp_disable_layer(void *lay) gdp->node_list[i].btm_field->gam_gdp_ppt |= GAM_GDP_PPT_IGNORE; }
if (sti_vtg_unregister_client(layer->mixer_id == STI_MIXER_MAIN ?
compo->vtg_main : compo->vtg_aux, &gdp->vtg_field_nb))
DRM_DEBUG_DRIVER("Warning: cannot unregister VTG notifier\n");
if (gdp->clk_pix) clk_disable_unprepare(gdp->clk_pix);
diff --git a/drivers/gpu/drm/sti/sti_layer.c b/drivers/gpu/drm/sti/sti_layer.c new file mode 100644 index 0000000..cc7186d --- /dev/null +++ b/drivers/gpu/drm/sti/sti_layer.c @@ -0,0 +1,312 @@ +/*
- Copyright (C) STMicroelectronics SA 2014
- Authors: Benjamin Gaignard benjamin.gaignard@st.com
Fabien Dessenne <fabien.dessenne@st.com>
for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#include <drm/drmP.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_cma_helper.h>
+#include "sti_compositor.h" +#include "sti_layer.h"
+#define STI_FPS_INTERVAL_MS 3000
+struct sti_layer *sti_layer_find_layer(struct sti_layer *layer[],
enum sti_layer_desc desc)
+{
unsigned 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);
s64 tmp_ns = timespec_to_ns(&tmp_ts);
do_div(tmp_ns, NSEC_PER_MSEC);
return (int) 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);
}
+}
btw, this fps stuff, as far as I can tell is only for perf/debug traces? In which case, I'd drop this (and 'struct sti_fps_info', etc) and look into adding trace events instead. This would let you log in more detail, correlated to cpu load (and eventual fence traces, and whatever other gpu related traces you feel like adding). That gives a much more detailed view of system performance. I'd done something similar in msm, although not pushed upstream yet:
http://bloggingthemonkey.blogspot.com/2013/09/freedreno-update-moar-fps.html
but when Maarten's fence traces are removed, I should be able to replace most of the msm specific trace events with generic ones (which was only reason I didn't push msm trace events upstream yet).
I would suggest adding the trace events as a follow-up patch, rather than as part of the initial patchset. Or at least I'm not sure offhand if trace events are considered userspace ABI or not, so having it as a follow-up patch avoids delaying the main part of the driver review.
+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;
unsigned int i;
struct drm_gem_cma_object *cma_obj;
if (!layer || !fb || !mode) {
DRM_ERROR("Null fb, layer or mode\n");
return 1;
}
cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
if (!cma_obj) {
DRM_ERROR("Can't get CMA GEM object for fb\n");
return 1;
}
layer->fb = fb;
layer->mode = mode;
layer->mixer_id = mixer_id;
layer->dst_x = dest_x;
layer->dst_y = dest_y;
layer->dst_w = clamp_val(dest_w, 0, mode->crtc_hdisplay - dest_x);
layer->dst_h = clamp_val(dest_h, 0, mode->crtc_vdisplay - dest_y);
layer->src_x = src_x;
layer->src_y = src_y;
layer->src_w = src_w;
layer->src_h = src_h;
layer->format = fb->pixel_format;
layer->paddr = cma_obj->paddr;
for (i = 0; i < 4; i++) {
layer->pitches[i] = fb->pitches[i];
layer->offsets[i] = fb->offsets[i];
}
DRM_DEBUG_DRIVER("%s is associated with mixer_id %d\n",
sti_layer_to_str(layer),
layer->mixer_id);
DRM_DEBUG_DRIVER("%s dst=(%dx%d)@(%d,%d) - src=(%dx%d)@(%d,%d)\n",
sti_layer_to_str(layer),
layer->dst_w, layer->dst_h, layer->dst_x, layer->dst_y,
layer->src_w, layer->src_h, layer->src_x,
layer->src_y);
DRM_DEBUG_DRIVER("drm FB:%d format:%.4s phys@:0x%lx\n", fb->base.id,
(char *)&layer->format, (unsigned long)layer->paddr);
/* Prepare layer specificities */
switch (layer->desc & STI_LAYER_TYPE_MASK) {
you do have this 'switch (layer_type)' thing going in a few places.. maybe time for 'struct sti_layer_funcs', and then at init time plug in appropriate gdp_layer_funcs/sti_layer_funcs/etc depending on layer type?
BR, -R
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();
+}
+unsigned int sti_layer_get_nb_formats(struct sti_layer *layer) +{
unsigned 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();
+} diff --git a/drivers/gpu/drm/sti/sti_mixer.c b/drivers/gpu/drm/sti/sti_mixer.c index c812c68..c928d85 100644 --- a/drivers/gpu/drm/sti/sti_mixer.c +++ b/drivers/gpu/drm/sti/sti_mixer.c @@ -6,6 +6,7 @@
- License terms: GNU General Public License (GPL), version 2
*/
+#include "sti_compositor.h" #include "sti_mixer.h" #include "sti_vtg.h"
@@ -133,6 +134,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_CRB val 0x%x mask 0x%x\n", layer_id, mask);
@@ -195,6 +198,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");
diff --git a/drivers/gpu/drm/sti/sti_vid.c b/drivers/gpu/drm/sti/sti_vid.c index 0197291..f75bda2 100644 --- a/drivers/gpu/drm/sti/sti_vid.c +++ b/drivers/gpu/drm/sti/sti_vid.c @@ -6,6 +6,7 @@
#include <drm/drmP.h>
+#include "sti_compositor.h" #include "sti_layer.h" #include "sti_vid.h" #include "sti_vtg.h" @@ -49,6 +50,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 +67,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 +86,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;
-- 1.9.1
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
On Thu, May 29, 2014 at 2:36 AM, Benjamin Gaignard benjamin.gaignard@linaro.org wrote:
This series of patches add the support of DRM/KMS drivers for STMicroelectronics chipsets stih416 and stih407.
version 4: - Remove depency between TVout it subdevices HDMI and HDA - Rework and simplify VTG and VTAC code - Fix numbers of typo and indentation - Remove debug (will be push in separate patches) - Fix remarks done in previous patcheset
patches could be found here: git://git.linaro.org/people/benjamin.gaignard/kernel.git on branch: drm_kms_for_next-v4
version 3: - Correctly split code between probe and bind funtions - Squash some commits - remove HQ-VDP device code to have a smaller patcheset, we will introduce it later.
patches could be found here: git://git.linaro.org/people/benjamin.gaignard/kernel.git on branch: drm_kms_for_next-v3
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
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.
I think it would be helpful to describe how you map the hardware to kms constructs (planes/crtcs/etc).
Also, I kinda like sticking this sort of info (along w/ block diagrams above, etc), in a notes/readme file. Although I seem to be in the minority about that. But plenty of times when diving into other drivers I've wished they had done the same :-)
For msm I think I more or less copy/paste the cover-letter and drm/msm/NOTES..
BR, -R
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 (11): 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 HDA driver drm: sti: add TVOut driver drm: sti: add GDP layer drm: sti: add VID layer drm: sti: add Mixer drm: sti: add Compositor drm: sti: Add DRM driver itself
.../devicetree/bindings/gpu/st,stih4xx.txt | 189 ++++++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sti/Kconfig | 14 + drivers/gpu/drm/sti/Makefile | 19 + drivers/gpu/drm/sti/sti_compositor.c | 265 ++++++++ drivers/gpu/drm/sti/sti_compositor.h | 90 +++ drivers/gpu/drm/sti/sti_drm_connector.c | 114 ++++ drivers/gpu/drm/sti/sti_drm_connector.h | 55 ++ drivers/gpu/drm/sti/sti_drm_crtc.c | 436 ++++++++++++ drivers/gpu/drm/sti/sti_drm_crtc.h | 21 + drivers/gpu/drm/sti/sti_drm_drv.c | 229 +++++++ drivers/gpu/drm/sti/sti_drm_drv.h | 35 + drivers/gpu/drm/sti/sti_drm_encoder.c | 108 +++ drivers/gpu/drm/sti/sti_drm_encoder.h | 17 + drivers/gpu/drm/sti/sti_drm_plane.c | 192 ++++++ drivers/gpu/drm/sti/sti_drm_plane.h | 17 + drivers/gpu/drm/sti/sti_gdp.c | 512 ++++++++++++++ drivers/gpu/drm/sti/sti_gdp.h | 73 ++ drivers/gpu/drm/sti/sti_hda.c | 727 ++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi.c | 748 +++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi.h | 88 +++ drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c | 336 +++++++++ drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.h | 14 + drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c | 211 ++++++ drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.h | 14 + drivers/gpu/drm/sti/sti_layer.c | 312 +++++++++ drivers/gpu/drm/sti/sti_layer.h | 114 ++++ drivers/gpu/drm/sti/sti_mixer.c | 249 +++++++ drivers/gpu/drm/sti/sti_mixer.h | 52 ++ drivers/gpu/drm/sti/sti_tvout.c | 687 +++++++++++++++++++ drivers/gpu/drm/sti/sti_tvout.h | 18 + drivers/gpu/drm/sti/sti_vid.c | 145 ++++ drivers/gpu/drm/sti/sti_vid.h | 33 + drivers/gpu/drm/sti/sti_vtac.c | 211 ++++++ drivers/gpu/drm/sti/sti_vtg.c | 356 ++++++++++ drivers/gpu/drm/sti/sti_vtg.h | 28 + 37 files changed, 6732 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_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_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_tx3g0c55phy.h create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.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.c create mode 100644 drivers/gpu/drm/sti/sti_vtg.c create mode 100644 drivers/gpu/drm/sti/sti_vtg.h
-- 1.9.1
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
On Wed, Jun 11, 2014 at 2:37 PM, Rob Clark robdclark@gmail.com wrote:
On Thu, May 29, 2014 at 2:36 AM, Benjamin Gaignard benjamin.gaignard@linaro.org wrote:
This series of patches add the support of DRM/KMS drivers for STMicroelectronics chipsets stih416 and stih407.
version 4: - Remove depency between TVout it subdevices HDMI and HDA - Rework and simplify VTG and VTAC code - Fix numbers of typo and indentation - Remove debug (will be push in separate patches) - Fix remarks done in previous patcheset
patches could be found here: git://git.linaro.org/people/benjamin.gaignard/kernel.git on branch: drm_kms_for_next-v4
version 3: - Correctly split code between probe and bind funtions - Squash some commits - remove HQ-VDP device code to have a smaller patcheset, we will introduce it later.
patches could be found here: git://git.linaro.org/people/benjamin.gaignard/kernel.git on branch: drm_kms_for_next-v3
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
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.
I think it would be helpful to describe how you map the hardware to kms constructs (planes/crtcs/etc).
Also, I kinda like sticking this sort of info (along w/ block diagrams above, etc), in a notes/readme file. Although I seem to be in the minority about that. But plenty of times when diving into other drivers I've wished they had done the same :-)
For msm I think I more or less copy/paste the cover-letter and drm/msm/NOTES..
btw, Daniel started converting i915 over to docbook.. I probably should look at converting some/all of my NOTES over to docbook one of these days..
Anyway, I think I mostly managed to cover the parts that Thierry did not get to yet. Although you did seem to squash/drop/rearrange some parts, so hopefully I didn't miss something as a result.
BR, -R
BR, -R
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 (11): 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 HDA driver drm: sti: add TVOut driver drm: sti: add GDP layer drm: sti: add VID layer drm: sti: add Mixer drm: sti: add Compositor drm: sti: Add DRM driver itself
.../devicetree/bindings/gpu/st,stih4xx.txt | 189 ++++++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sti/Kconfig | 14 + drivers/gpu/drm/sti/Makefile | 19 + drivers/gpu/drm/sti/sti_compositor.c | 265 ++++++++ drivers/gpu/drm/sti/sti_compositor.h | 90 +++ drivers/gpu/drm/sti/sti_drm_connector.c | 114 ++++ drivers/gpu/drm/sti/sti_drm_connector.h | 55 ++ drivers/gpu/drm/sti/sti_drm_crtc.c | 436 ++++++++++++ drivers/gpu/drm/sti/sti_drm_crtc.h | 21 + drivers/gpu/drm/sti/sti_drm_drv.c | 229 +++++++ drivers/gpu/drm/sti/sti_drm_drv.h | 35 + drivers/gpu/drm/sti/sti_drm_encoder.c | 108 +++ drivers/gpu/drm/sti/sti_drm_encoder.h | 17 + drivers/gpu/drm/sti/sti_drm_plane.c | 192 ++++++ drivers/gpu/drm/sti/sti_drm_plane.h | 17 + drivers/gpu/drm/sti/sti_gdp.c | 512 ++++++++++++++ drivers/gpu/drm/sti/sti_gdp.h | 73 ++ drivers/gpu/drm/sti/sti_hda.c | 727 ++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi.c | 748 +++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi.h | 88 +++ drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c | 336 +++++++++ drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.h | 14 + drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c | 211 ++++++ drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.h | 14 + drivers/gpu/drm/sti/sti_layer.c | 312 +++++++++ drivers/gpu/drm/sti/sti_layer.h | 114 ++++ drivers/gpu/drm/sti/sti_mixer.c | 249 +++++++ drivers/gpu/drm/sti/sti_mixer.h | 52 ++ drivers/gpu/drm/sti/sti_tvout.c | 687 +++++++++++++++++++ drivers/gpu/drm/sti/sti_tvout.h | 18 + drivers/gpu/drm/sti/sti_vid.c | 145 ++++ drivers/gpu/drm/sti/sti_vid.h | 33 + drivers/gpu/drm/sti/sti_vtac.c | 211 ++++++ drivers/gpu/drm/sti/sti_vtg.c | 356 ++++++++++ drivers/gpu/drm/sti/sti_vtg.h | 28 + 37 files changed, 6732 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_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_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_tx3g0c55phy.h create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.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.c create mode 100644 drivers/gpu/drm/sti/sti_vtg.c create mode 100644 drivers/gpu/drm/sti/sti_vtg.h
-- 1.9.1
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
linaro-mm-sig@lists.linaro.org