This series of patches add the support of DRM/KMS drivers for STMicroelectronics chipsets stih416 and stih407.
patcheset 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
patcheset version 2: - Use componentized device instead of register sub-devices in master driver probe function - Fix Makefile and Kconfig to only allow built-in compilation
patches could be found here: git://git.linaro.org/people/benjamin.gaignard/kernel.git on branch: drm_kms_for_next-v2 patcheset version 1: - First path submission
Hardware is split in two main blocks: Compositor and TVout. Each of them includes specific hardware IPs and the display timing are controlled by a specific Video Timing Generator hardware IP (VTG).
Compositor is made of the follow hardware IPs: - GDP (Generic Display Pipeline) which is an entry point for graphic (RGB) buffers - VDP (Video Diplay Pipeline) which is an entry point for video (YUV) buffers - HQVDP (High Quality Video Display Processor) that supports scaling, deinterlacing and some miscellaneous image quality improvements. It fetches the Video decoded buffers from memory, processes them and pushes them to the Compositor through a HW dedicated bus. - Mixer is responsible of mixing all the entries depending of their respective z-order and layout
TVout is divided in 3 parts: - HDMI to generate HDMI signals, depending of chipset version HDMI phy can change. - HDA to generate signals for HD analog TV - VIP to control/switch data path coming from Compositor
On stih416 compositor and Tvout are on different dies so a Video Trafic Advance inter-die Communication mechanism (VTAC) is needed.
+---------------------------------------------+ +----------------------------------------+ | +-------------------------------+ +----+ | | +----+ +--------------------------+ | | | | | | | | | | | +---------+ +----+ | | | | +----+ +------+ | | | | | | | | | VIP |---->|HDMI| | | | | |GPD +------------->| | | | | | | | | | | | +----+ | | | | +----+ |Mixer |--|-->| | | | | |---|->| switcher| | | | | | | | | | | | | | | | | +----+ | | | | | | | | | | | | | | | |---->|HDA | | | | | +------+ | |VTAC|========>|VTAC| | +---------+ +----+ | | | | | | | | | | | | | | | | Compositor | | | | | | | | TVout | | | +-------------------------------+ | | | | | | +--------------------------+ | | ^ | | | | | | ^ | | | | | | | | | | | | +--------------+ | | | | | | +-------------+ | | | VTG (master) |----->| | | | | |----->| VTG (slave) | | | +--------------+ +----+ | | +----+ +-------------+ | |Digital die | | Analog Die| +---------------------------------------------+ +----------------------------------------+
On stih407 Compositor and Tvout are on the same die
+-----------------------------------------------------------------+ | +-------------------------------+ +--------------------------+ | | | | | +---------+ +----+ | | | | +----+ +------+ | | | VIP |---->|HDMI| | | | | |GPD +------------->| | | | | | +----+ | | | | +----+ |Mixer |--|--|->| switcher| | | | | +----+ +-----+ | | | | | | +----+ | | | | |VDP +-->+HQVDP+--->| | | | | |---->|HDA | | | | | +----+ +-----+ +------+ | | +---------+ +----+ | | | | | | | | | | Compositor | | TVout | | | +-------------------------------+ +--------------------------+ | | ^ ^ | | | | | | +--------------+ | | | VTG | | | +--------------+ | |Digital die | +-----------------------------------------------------------------+
In addition of the drivers for the IPs listed before a thin I2C driver (hdmiddc) is used by HDMI driver to retrieve EDID for monitor.
To unify interfaces of GDP and VDP we create a "layer" interface called by compositor to control both GPD and VDP.
Hardware have memory contraints (alignment, contiguous) so we use CMA drm helpers functions to allocate frame buffer.
File naming convention is: - sti_* for IPs drivers - sti_drm_* for drm functions implementation.
Benjamin Gaignard (16): drm: sti: add VTG driver drm: sti: add VTAC drivers drm: sti: add HDMI driver drm: sti: add I2C client driver for HDMI drm: sti: add HDA driver drm: sti: add TVOut driver drm: sti: add sti layer interface definition drm: sti: add GDP layer drm: sti: add VID layer drm: sti: add Mixer drm: sti: add Compositor drm: sti: add debug to GDP drm: sti: add debug to VID drm: sti: add debug to TVout drm: sti: add debug to mixer drm: sti: Add DRM driver itself
drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sti/Kconfig | 26 + drivers/gpu/drm/sti/Makefile | 22 + drivers/gpu/drm/sti/sti_compositor.c | 267 +++++++ drivers/gpu/drm/sti/sti_compositor.h | 84 +++ drivers/gpu/drm/sti/sti_ddc.c | 56 ++ drivers/gpu/drm/sti/sti_drm_connector.c | 195 +++++ drivers/gpu/drm/sti/sti_drm_connector.h | 16 + drivers/gpu/drm/sti/sti_drm_crtc.c | 440 ++++++++++++ drivers/gpu/drm/sti/sti_drm_crtc.h | 21 + drivers/gpu/drm/sti/sti_drm_drv.c | 338 +++++++++ drivers/gpu/drm/sti/sti_drm_drv.h | 36 + drivers/gpu/drm/sti/sti_drm_encoder.c | 201 ++++++ drivers/gpu/drm/sti/sti_drm_encoder.h | 16 + drivers/gpu/drm/sti/sti_drm_plane.c | 195 +++++ drivers/gpu/drm/sti/sti_drm_plane.h | 16 + drivers/gpu/drm/sti/sti_gdp.c | 726 +++++++++++++++++++ drivers/gpu/drm/sti/sti_gdp.h | 75 ++ drivers/gpu/drm/sti/sti_hda.c | 852 ++++++++++++++++++++++ drivers/gpu/drm/sti/sti_hda.h | 14 + drivers/gpu/drm/sti/sti_hdmi.c | 1071 ++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi.h | 198 +++++ drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c | 398 +++++++++++ drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c | 224 ++++++ drivers/gpu/drm/sti/sti_layer.c | 309 ++++++++ drivers/gpu/drm/sti/sti_layer.h | 113 +++ drivers/gpu/drm/sti/sti_mixer.c | 405 +++++++++++ drivers/gpu/drm/sti/sti_mixer.h | 54 ++ drivers/gpu/drm/sti/sti_tvout.c | 904 +++++++++++++++++++++++ drivers/gpu/drm/sti/sti_tvout.h | 105 +++ drivers/gpu/drm/sti/sti_vid.c | 259 +++++++ drivers/gpu/drm/sti/sti_vid.h | 34 + drivers/gpu/drm/sti/sti_vtac_rx.c | 169 +++++ drivers/gpu/drm/sti/sti_vtac_tx.c | 182 +++++ drivers/gpu/drm/sti/sti_vtac_utils.h | 52 ++ drivers/gpu/drm/sti/sti_vtg.c | 468 ++++++++++++ drivers/gpu/drm/sti/sti_vtg.h | 20 + drivers/gpu/drm/sti/sti_vtg_utils.c | 99 +++ drivers/gpu/drm/sti/sti_vtg_utils.h | 29 + 40 files changed, 8692 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_compositor.c create mode 100644 drivers/gpu/drm/sti/sti_compositor.h create mode 100644 drivers/gpu/drm/sti/sti_ddc.c create mode 100644 drivers/gpu/drm/sti/sti_drm_connector.c create mode 100644 drivers/gpu/drm/sti/sti_drm_connector.h create mode 100644 drivers/gpu/drm/sti/sti_drm_crtc.c create mode 100644 drivers/gpu/drm/sti/sti_drm_crtc.h create mode 100644 drivers/gpu/drm/sti/sti_drm_drv.c create mode 100644 drivers/gpu/drm/sti/sti_drm_drv.h create mode 100644 drivers/gpu/drm/sti/sti_drm_encoder.c create mode 100644 drivers/gpu/drm/sti/sti_drm_encoder.h create mode 100644 drivers/gpu/drm/sti/sti_drm_plane.c create mode 100644 drivers/gpu/drm/sti/sti_drm_plane.h create mode 100644 drivers/gpu/drm/sti/sti_gdp.c create mode 100644 drivers/gpu/drm/sti/sti_gdp.h create mode 100644 drivers/gpu/drm/sti/sti_hda.c create mode 100644 drivers/gpu/drm/sti/sti_hda.h create mode 100644 drivers/gpu/drm/sti/sti_hdmi.c create mode 100644 drivers/gpu/drm/sti/sti_hdmi.h create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c create mode 100644 drivers/gpu/drm/sti/sti_layer.c create mode 100644 drivers/gpu/drm/sti/sti_layer.h create mode 100644 drivers/gpu/drm/sti/sti_mixer.c create mode 100644 drivers/gpu/drm/sti/sti_mixer.h create mode 100644 drivers/gpu/drm/sti/sti_tvout.c create mode 100644 drivers/gpu/drm/sti/sti_tvout.h create mode 100644 drivers/gpu/drm/sti/sti_vid.c create mode 100644 drivers/gpu/drm/sti/sti_vid.h create mode 100644 drivers/gpu/drm/sti/sti_vtac_rx.c create mode 100644 drivers/gpu/drm/sti/sti_vtac_tx.c create mode 100644 drivers/gpu/drm/sti/sti_vtac_utils.h create mode 100644 drivers/gpu/drm/sti/sti_vtg.c create mode 100644 drivers/gpu/drm/sti/sti_vtg.h create mode 100644 drivers/gpu/drm/sti/sti_vtg_utils.c create mode 100644 drivers/gpu/drm/sti/sti_vtg_utils.h
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 | 11 + drivers/gpu/drm/sti/Makefile | 3 + drivers/gpu/drm/sti/sti_vtg.c | 468 ++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_vtg.h | 20 ++ drivers/gpu/drm/sti/sti_vtg_utils.c | 99 ++++++++ drivers/gpu/drm/sti/sti_vtg_utils.h | 29 +++ 8 files changed, 633 insertions(+) create mode 100644 drivers/gpu/drm/sti/Kconfig create mode 100644 drivers/gpu/drm/sti/Makefile create mode 100644 drivers/gpu/drm/sti/sti_vtg.c create mode 100644 drivers/gpu/drm/sti/sti_vtg.h create mode 100644 drivers/gpu/drm/sti/sti_vtg_utils.c create mode 100644 drivers/gpu/drm/sti/sti_vtg_utils.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index d1cc2f6..0e30029 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -201,3 +201,5 @@ source "drivers/gpu/drm/tegra/Kconfig" source "drivers/gpu/drm/panel/Kconfig"
source "drivers/gpu/drm/bridge/Kconfig" + +source "drivers/gpu/drm/sti/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 5e792b0..44f7b17 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_DRM_QXL) += qxl/ obj-$(CONFIG_DRM_BOCHS) += bochs/ obj-$(CONFIG_DRM_MSM) += msm/ obj-$(CONFIG_DRM_TEGRA) += tegra/ +obj-$(CONFIG_DRM_STI) += sti/ obj-y += i2c/ obj-y += panel/ obj-y += bridge/ diff --git a/drivers/gpu/drm/sti/Kconfig b/drivers/gpu/drm/sti/Kconfig new file mode 100644 index 0000000..3fff278 --- /dev/null +++ b/drivers/gpu/drm/sti/Kconfig @@ -0,0 +1,11 @@ +config DRM_STI + bool "DRM Support for STMicroelectronics SoC stiH41x Series" + depends on DRM && (SOC_STIH415 || SOC_STIH416 || ARCH_MULTIPLATFORM) + help + Choose this option to enable DRM on STM stiH41x chipset + +config VTG_STI + bool "Video Timing Generator for STMicroelectronics SoC stiH41x Series" + depends on DRM_STI + help + Choose this option to enable VTG on STM stiH41x chipset diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile new file mode 100644 index 0000000..33216e1 --- /dev/null +++ b/drivers/gpu/drm/sti/Makefile @@ -0,0 +1,3 @@ +ccflags-y := -Iinclude/drm + +obj-$(CONFIG_VTG_STI) += sti_vtg.o sti_vtg_utils.o diff --git a/drivers/gpu/drm/sti/sti_vtg.c b/drivers/gpu/drm/sti/sti_vtg.c new file mode 100644 index 0000000..75d7125 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg.c @@ -0,0 +1,468 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * Vincent Abriou vincent.abriou@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/notifier.h> + +#include "sti_vtg_utils.h" +#include "sti_vtg.h" + +#define VTG_TYPE_MASTER 0 +#define VTG_TYPE_SLAVE_BY_EXT0 1 + +/* registers offset */ +#define VTG_MODE 0x0000 +#define VTG_CLKLN 0x0008 +#define VTG_HLFLN 0x000C +#define VTG_DRST_AUTOC 0x0010 +#define VTG_VID_TFO 0x0040 +#define VTG_VID_TFS 0x0044 +#define VTG_VID_BFO 0x0048 +#define VTG_VID_BFS 0x004C + +#define VTG_HOST_ITS 0x0078 +#define VTG_HOST_ITS_BCLR 0x007C +#define VTG_HOST_ITM_BCLR 0x0088 +#define VTG_HOST_ITM_BSET 0x008C + +#define VTG_H_HD_1 0x00C0 +#define VTG_TOP_V_VD_1 0x00C4 +#define VTG_BOT_V_VD_1 0x00C8 +#define VTG_TOP_V_HD_1 0x00CC +#define VTG_BOT_V_HD_1 0x00D0 + +#define VTG_H_HD_2 0x00E0 +#define VTG_TOP_V_VD_2 0x00E4 +#define VTG_BOT_V_VD_2 0x00E8 +#define VTG_TOP_V_HD_2 0x00EC +#define VTG_BOT_V_HD_2 0x00F0 + +#define VTG_H_HD_3 0x0100 +#define VTG_TOP_V_VD_3 0x0104 +#define VTG_BOT_V_VD_3 0x0108 +#define VTG_TOP_V_HD_3 0x010C +#define VTG_BOT_V_HD_3 0x0110 + +/* IRQ mask */ +#define VTG_IRQ_TOP_FIELD_MASK (1L << 1) +#define VTG_IRQ_BOTTOM_FIELD_MASK (1L << 0) +#define VTG_IRQ_MASK (VTG_IRQ_TOP_FIELD_MASK | \ + VTG_IRQ_BOTTOM_FIELD_MASK) + +/* Delay introduced by the AWG in nb of pixels */ +#define AWG_DELAY_HD (-9) +#define AWG_DELAY_ED (-8) +#define AWG_DELAY_SD (-7) + +static const struct of_device_id vtg_match_types[]; + +/* + * STI VTG data structure + * + * @nb_reg: number of memory resources to register + * @reg_names: names of the memory resources to register + * @regs: ioremapped registers + */ +#define MAX_MEM_REGION 2 +#define VTG_MASTER 0 +#define VTG_SLAVE 1 +struct sti_vtg_data { + int nb_reg; + char *reg_names[MAX_MEM_REGION]; + void __iomem *regs[MAX_MEM_REGION]; +}; + +/* + * STI VTG structure + * + * @dev: pointer to device driver + * @data: data associated to the device + * @irq: VTG irq + * @type: VTG type (main or aux) + * @notifier_list: notifier callback + */ +struct sti_vtg { + struct device *dev; + struct sti_vtg_data data; + int irq; + int type; + struct raw_notifier_head notifier_list; +}; + +/* + * STiH416: + * -------- + * VTG slave is connected to the VTG master by the ext0 input (ext1 input is + * left unconnected). + * + * MPE ! ! SAS + * ________ ! ! ________ + * | | ! ! ext0 | | + * | | _________! !_________ ----->| | + * | VTG | hsync | ! ! | hsync | | VTG | + * | |------------>| VTAC Tx !==>! VTAC Rx |-------- | | + * | master | vsync |_________! !_________| vsync | slave | + * | | ! ! ---->| | + * |________| ! ! ext1 |________| + * ! ! | + * ! ! synchro_irq + * */ +struct sti_vtg_data stih416_vtg_data = { + .nb_reg = 2, + .reg_names = {"master", "slave"}, +}; + +/* + * STiH407: + * -------- + * Only VTG master is used. There is no MPE and SAS domain => only one domain. + * + * MPE + * ________ + * | | + * | | + * | VTG | hsync + * | |------------> + * | master | vsync + * | | + * |________| + * | + * synchro_irq + * */ +struct sti_vtg_data stih407_vtg_data = { + .nb_reg = 1, + .reg_names = {"master"}, +}; + +static int vtg_reset(void __iomem *regs) +{ + writel(1, regs + VTG_DRST_AUTOC); + return 0; +} + +static int vtg_reg_dump(struct device *dev, void __iomem *regs) +{ + dev_dbg(dev, "regs %p\n", regs); + dev_dbg(dev, "VTG_MODE 0x%x\n", readl(regs + VTG_MODE)); + dev_dbg(dev, "VTG_CLKLN 0x%x\n", readl(regs + VTG_CLKLN)); + dev_dbg(dev, "VTG_HLFLN 0x%x\n", readl(regs + VTG_HLFLN)); + dev_dbg(dev, "VTG_VID_TFO 0x%x\n", readl(regs + VTG_VID_TFO)); + dev_dbg(dev, "VTG_VID_BFO 0x%x\n", readl(regs + VTG_VID_BFO)); + dev_dbg(dev, "VTG_VID_TFS 0x%x\n", readl(regs + VTG_VID_TFS)); + dev_dbg(dev, "VTG_VID_BFS 0x%x\n", readl(regs + VTG_VID_BFS)); + dev_dbg(dev, "VTG_H_HD_1 0x%x\n", readl(regs + VTG_H_HD_1)); + dev_dbg(dev, "VTG_TOP_V_VD_1 0x%x\n", readl(regs + VTG_TOP_V_VD_1)); + dev_dbg(dev, "VTG_BOT_V_VD_1 0x%x\n", readl(regs + VTG_BOT_V_VD_1)); + dev_dbg(dev, "VTG_TOP_V_HD_1 0x%x\n", readl(regs + VTG_TOP_V_HD_1)); + dev_dbg(dev, "VTG_BOT_V_HD_1 0x%x\n", readl(regs + VTG_BOT_V_HD_1)); + dev_dbg(dev, "VTG_H_HD_2 0x%x\n", readl(regs + VTG_H_HD_2)); + dev_dbg(dev, "VTG_TOP_V_VD_2 0x%x\n", readl(regs + VTG_TOP_V_VD_2)); + dev_dbg(dev, "VTG_BOT_V_VD_2 0x%x\n", readl(regs + VTG_BOT_V_VD_2)); + dev_dbg(dev, "VTG_TOP_V_HD_2 0x%x\n", readl(regs + VTG_TOP_V_HD_2)); + dev_dbg(dev, "VTG_BOT_V_HD_2 0x%x\n", readl(regs + VTG_BOT_V_HD_2)); + dev_dbg(dev, "VTG_H_HD_3 0x%x\n", readl(regs + VTG_H_HD_3)); + dev_dbg(dev, "VTG_TOP_V_VD_3 0x%x\n", readl(regs + VTG_TOP_V_VD_3)); + dev_dbg(dev, "VTG_BOT_V_VD_3 0x%x\n", readl(regs + VTG_BOT_V_VD_3)); + dev_dbg(dev, "VTG_TOP_V_HD_3 0x%x\n", readl(regs + VTG_TOP_V_HD_3)); + dev_dbg(dev, "VTG_BOT_V_HD_3 0x%x\n", readl(regs + VTG_BOT_V_HD_3)); + return 0; +} + +static int vtg_write_reg(void __iomem *regs, + int type, const struct drm_display_mode *mode) +{ + int fo, fs, h_hd, v_vd, v_hd; + + writel(mode->htotal, regs + VTG_CLKLN); + writel(mode->vtotal * 2, regs + VTG_HLFLN); + + fo = (mode->vtotal - mode->vsync_start + 1) << 16; + fo |= mode->htotal - mode->hsync_start; + writel(fo, regs + VTG_VID_TFO); + writel(fo, regs + VTG_VID_BFO); + + fs = (mode->vdisplay + mode->vtotal - mode->vsync_start + 1) << 16; + fs |= mode->hdisplay + mode->htotal - mode->hsync_start; + writel(fs, regs + VTG_VID_TFS); + writel(fs, regs + VTG_VID_BFS); + + /* Prepare VTG set 1 for HDMI and VTG set 3 for HD DAC */ + h_hd = (mode->hsync_end - mode->hsync_start) << 16; + writel(h_hd, regs + VTG_H_HD_1); + + v_vd = (mode->vsync_end - mode->vsync_start + 1) << 16; + v_vd |= 1; + writel(v_vd, regs + VTG_TOP_V_VD_1); + writel(v_vd, regs + VTG_BOT_V_VD_1); + writel(0, regs + VTG_TOP_V_HD_1); + writel(0, regs + VTG_BOT_V_HD_1); + + /* Prepare VTG set 2 for for HD DCS */ + writel(h_hd, regs + VTG_H_HD_2); + writel(v_vd, regs + VTG_TOP_V_VD_2); + writel(v_vd, regs + VTG_BOT_V_VD_2); + writel(0, regs + VTG_TOP_V_HD_2); + writel(0, regs + VTG_BOT_V_HD_2); + + /* Prepare VTG set 3 for HD Analog in HD mode */ + h_hd = (mode->hsync_end - mode->hsync_start + AWG_DELAY_HD) << 16; + h_hd |= mode->htotal + AWG_DELAY_HD; + writel(h_hd, regs + VTG_H_HD_3); + + v_vd = (mode->vsync_end - mode->vsync_start) << 16; + v_vd |= mode->vtotal; + writel(v_vd, regs + VTG_TOP_V_VD_3); + writel(v_vd, regs + VTG_BOT_V_VD_3); + + v_hd = (mode->htotal + AWG_DELAY_HD) << 16; + v_hd |= mode->htotal + AWG_DELAY_HD; + writel(v_hd, regs + VTG_TOP_V_HD_3); + writel(v_hd, regs + VTG_BOT_V_HD_3); + + /* Mode */ + writel(type, regs + VTG_MODE); + return 0; +} + +static int vtg_disable_irq(void __iomem *regs) +{ + /* Clear interrupt status and mask */ + writel(0xFFFF, regs + VTG_HOST_ITS_BCLR); + writel(0xFFFF, regs + VTG_HOST_ITM_BCLR); + return 0; +} + +static int vtg_enable_irq(void __iomem *regs) +{ + vtg_disable_irq(regs); + writel(VTG_IRQ_MASK, regs + VTG_HOST_ITM_BSET); + return 0; +} + +int vtg_set_config(struct device *dev, const struct drm_display_mode *mode) +{ + struct sti_vtg *vtg = dev_get_drvdata(dev); + struct sti_vtg_data *data = &vtg->data; + + if (!vtg) + return 1; + + if (!data->regs[VTG_MASTER]) + return 1; + + /* write slave / master configuration */ + if (data->regs[VTG_SLAVE]) + vtg_write_reg(data->regs[VTG_SLAVE], + VTG_TYPE_SLAVE_BY_EXT0, mode); + + vtg_write_reg(data->regs[VTG_MASTER], VTG_TYPE_MASTER, mode); + + /* reset slave and then master */ + if (data->regs[VTG_SLAVE]) + vtg_reset(data->regs[VTG_SLAVE]); + + vtg_reset(data->regs[VTG_MASTER]); + + /* Enable irq for the vtg vblank synchro */ + if (data->regs[VTG_SLAVE]) + vtg_enable_irq(data->regs[VTG_SLAVE]); + else + vtg_enable_irq(data->regs[VTG_MASTER]); + + if (data->regs[VTG_SLAVE]) + vtg_reg_dump(dev, data->regs[VTG_SLAVE]); + + vtg_reg_dump(dev, data->regs[VTG_MASTER]); + + return 0; +} + +int vtg_register_client(struct device *dev, struct notifier_block *nb) +{ + struct sti_vtg *vtg = dev_get_drvdata(dev); + return raw_notifier_chain_register(&vtg->notifier_list, nb); +} + +int vtg_unregister_client(struct device *dev, struct notifier_block *nb) +{ + struct sti_vtg *vtg = dev_get_drvdata(dev); + return raw_notifier_chain_unregister(&vtg->notifier_list, nb); +} + +static irqreturn_t vtg_irq_thread(int irq, void *arg) +{ + struct sti_vtg *vtg = arg; + struct sti_vtg_data *data = &vtg->data; + void __iomem *regs; + u32 status; + + if (data->regs[VTG_SLAVE]) + regs = data->regs[VTG_SLAVE]; + else + regs = data->regs[VTG_MASTER]; + + status = readl(regs + VTG_HOST_ITS); + + writel(status, regs + VTG_HOST_ITS_BCLR); + /* TODO: check why this sync bus write solves the problem which + * is that without this line, the handler is sometimes called twice, + * first with status = 1 or 2, and immediately after with status=0 + */ + /* sync bus write */ + readl(regs + VTG_HOST_ITS); + + if (status & VTG_IRQ_TOP_FIELD_MASK) { + raw_notifier_call_chain(&vtg->notifier_list, + VTG_TOP_FIELD_EVENT, &vtg->type); + } else { + raw_notifier_call_chain(&vtg->notifier_list, + VTG_BOTTOM_FIELD_EVENT, &vtg->type); + } + + return IRQ_HANDLED; +} + +static const char *vtg_get_name(struct sti_vtg *vtg) +{ + if (vtg->type == VTG_MAIN) + return "main"; + else if (vtg->type == VTG_AUX) + return "aux"; + else + return "?"; +} + +static int vtg_compositor_bind(struct device *dev, struct device *master, + void *d) +{ + return 0; +} + +static void vtg_compositor_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops vtg_compositor_ops = { + .bind = vtg_compositor_bind, + .unbind = vtg_compositor_unbind, +}; + +static int vtg_compositor_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct sti_vtg *vtg; + struct resource *res; + char irq_name[32]; + struct sti_vtg_data *data; + int i; + int ret; + + DRM_INFO("%s\n", __func__); + + if (!np) + return -ENXIO; + + vtg = devm_kzalloc(dev, sizeof(*vtg), GFP_KERNEL); + if (!vtg) { + DRM_ERROR("Failed to allocate VTG context\n"); + return -ENOMEM; + } + + vtg->dev = dev; + + /* populate data structure depending on compatibility */ + BUG_ON(!of_match_node(vtg_match_types, np)->data); + + memcpy(&vtg->data, of_match_node(vtg_match_types, np)->data, + sizeof(struct sti_vtg_data)); + + data = &vtg->data; + + /* Get resources */ + for (i = 0; i < data->nb_reg; i++) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + data->reg_names[i]); + if (!res) { + DRM_ERROR("Invalid resource\n"); + return -ENOMEM; + } + data->regs[i] = devm_ioremap_nocache(dev, + res->start, + resource_size(res)); + if (IS_ERR(data->regs[i])) + return PTR_ERR(data->regs[i]); + } + + vtg->irq = platform_get_irq_byname(pdev, "synchro_irq"); + if (IS_ERR_VALUE(vtg->irq)) { + DRM_ERROR("Failed to get VTG interrupt\n"); + return vtg->irq; + } + + snprintf(irq_name, sizeof(irq_name), "vsync-%s", vtg_get_name(vtg)); + + RAW_INIT_NOTIFIER_HEAD(&vtg->notifier_list); + + ret = devm_request_threaded_irq(dev, vtg->irq, NULL, vtg_irq_thread, + IRQF_ONESHOT, irq_name, vtg); + if (IS_ERR_VALUE(ret)) { + DRM_ERROR("Failed to register VTG interrupt\n"); + return ret; + } + + if (of_property_read_bool(np, "vtg-aux")) { + vtg->type = VTG_AUX; + vtg_aux = dev; + } else { + vtg->type = VTG_MAIN; + vtg_main = dev; + } + + platform_set_drvdata(pdev, vtg); + + DRM_INFO("%s VTG %s\n", __func__, vtg_get_name(vtg)); + + return component_add(&pdev->dev, &vtg_compositor_ops); +} + +static int vtg_compositor_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vtg_compositor_ops); + return 0; +} + +static const struct of_device_id vtg_match_types[] = { + { + .compatible = "st,stih416-vtg", + .data = &stih416_vtg_data, + }, + { + .compatible = "st,stih407-vtg", + .data = &stih407_vtg_data, + }, + { /* end node */ } +}; +MODULE_DEVICE_TABLE(of, vtg_match_types); + +struct platform_driver sti_vtg_driver = { + .driver = { + .name = "sti-vtg", + .owner = THIS_MODULE, + .of_match_table = vtg_match_types, + }, + .probe = vtg_compositor_probe, + .remove = vtg_compositor_remove, +}; + +module_platform_driver(sti_vtg_driver); diff --git a/drivers/gpu/drm/sti/sti_vtg.h b/drivers/gpu/drm/sti/sti_vtg.h new file mode 100644 index 0000000..da67eb3 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_VTG_H_ +#define _STI_VTG_H_ + +#include <linux/platform_device.h> +#include <drm/drmP.h> + +extern struct device *vtg_main; +extern struct device *vtg_aux; + +int vtg_set_config(struct device *dev, const struct drm_display_mode *mode); +int vtg_register_client(struct device *dev, struct notifier_block *nb); +int vtg_unregister_client(struct device *dev, struct notifier_block *nb); + +#endif diff --git a/drivers/gpu/drm/sti/sti_vtg_utils.c b/drivers/gpu/drm/sti/sti_vtg_utils.c new file mode 100644 index 0000000..06bb4b4 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg_utils.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/platform_device.h> + +#include "sti_vtg_utils.h" +#include "sti_vtg.h" + +struct device *vtg_main; +struct device *vtg_aux; + +int sti_vtg_setconfig(int main_aux, const struct drm_display_mode *mode) +{ + if (main_aux == VTG_MAIN && vtg_main) + return vtg_set_config(vtg_main, mode); + + if (main_aux == VTG_AUX && vtg_aux) + return vtg_set_config(vtg_aux, mode); + + return 1; +} + +int sti_vtg_register_client(int main_aux, struct notifier_block *nb) +{ + if (main_aux == VTG_MAIN && vtg_main) + return vtg_register_client(vtg_main, nb); + + if (main_aux == VTG_AUX && vtg_aux) + return vtg_register_client(vtg_aux, nb); + + return 1; +} + +int sti_vtg_unregister_client(int main_aux, struct notifier_block *nb) +{ + if (main_aux == VTG_MAIN && vtg_main) + return vtg_unregister_client(vtg_main, nb); + + if (main_aux == VTG_AUX && vtg_aux) + return vtg_unregister_client(vtg_aux, nb); + + return 1; +} + +/* + * Active Front Sync Back Active + * Region Porch Porch Region + * <---------------><-------->0<---------><--------><-----------------> + * + * ///////////////| | ///////////////| + * /////////////// | | /////////////// | + * /////////////// |......... ..........|/////////////// | + * 0___________ x/ymin x/ymax + * + * <--[hv]display--> <--[hv]display--> + * <--[hv]sync_start---------> <--[hv]sync_start- + * <--[hv]sync_end-----------------------> <--[hv]sync_end--- + * <--[hv]total------------------------------------> <--[hv]total------ + */ + +/* + * sti_vtg_get_line_number + * + * @mode: display mode to be used + * @y: line + * + * Return the line number according to the display mode taking + * into account the Sync and Back Porch information. + * Video frame line numbers start at 1, y starts at 0. + * In interlaced modes the start line is the field line number of the odd + * field, but y is still defined as a progressive frame. + */ +u32 sti_vtg_get_line_number(struct drm_display_mode mode, int y) +{ + int start_line = mode.vtotal - mode.vsync_start + 1; + + if (mode.flags & DRM_MODE_FLAG_INTERLACE) + start_line *= 2; + + return start_line + y; +} + +/* + * sti_vtg_get_pixel_number + * + * @mode: display mode to be used + * @x: row + * + * Return the pixel number according to the display mode taking + * into account the Sync and Back Porch information. + * Pixels are counted from 0. + */ +u32 sti_vtg_get_pixel_number(struct drm_display_mode mode, int x) +{ + return mode.htotal - mode.hsync_start + x; +} diff --git a/drivers/gpu/drm/sti/sti_vtg_utils.h b/drivers/gpu/drm/sti/sti_vtg_utils.h new file mode 100644 index 0000000..fea2852 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg_utils.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_VTG_UTILS_H_ +#define _STI_VTG_UTILS_H_ + +#include <drm/drmP.h> + +#define WAIT_NEXT_VSYNC_MS 50 /*ms*/ + +#define VTG_MAIN 0 +#define VTG_AUX 1 + +#define VTG_TOP_FIELD_EVENT 1 +#define VTG_BOTTOM_FIELD_EVENT 2 + +int sti_vtg_setconfig(int main_aux, const struct drm_display_mode *mode); +int sti_vtg_register_client(int main_aux, struct notifier_block *nb); +int sti_vtg_unregister_client(int main_aux, struct notifier_block *nb); + +u32 sti_vtg_get_line_number(struct drm_display_mode mode, int y); +u32 sti_vtg_get_pixel_number(struct drm_display_mode mode, int x); + +#endif
On Tue, May 20, 2014 at 03:56:11PM +0200, Benjamin Gaignard wrote:
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.
Please wrap commit messages at 72 characters.
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile
[...]
new file mode 100644 index 0000000..33216e1 --- /dev/null +++ b/drivers/gpu/drm/sti/Makefile @@ -0,0 +1,3 @@ +ccflags-y := -Iinclude/drm
I don't think you need this. If you include DRM headers you should be prefixing them with drm/ anyway.
diff --git a/drivers/gpu/drm/sti/sti_vtg.c b/drivers/gpu/drm/sti/sti_vtg.c
[...]
+#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/notifier.h>
I prefer these to be sorted alphabetically.
+#include "sti_vtg_utils.h" +#include "sti_vtg.h"
+#define VTG_TYPE_MASTER 0 +#define VTG_TYPE_SLAVE_BY_EXT0 1
This is somewhat odd. I'll try to make some suggestions later on on how to possibly improve on this.
+/* registers offset */ +#define VTG_MODE 0x0000 +#define VTG_CLKLN 0x0008 +#define VTG_HLFLN 0x000C +#define VTG_DRST_AUTOC 0x0010 +#define VTG_VID_TFO 0x0040 +#define VTG_VID_TFS 0x0044 +#define VTG_VID_BFO 0x0048 +#define VTG_VID_BFS 0x004C
+#define VTG_HOST_ITS 0x0078 +#define VTG_HOST_ITS_BCLR 0x007C +#define VTG_HOST_ITM_BCLR 0x0088 +#define VTG_HOST_ITM_BSET 0x008C
+#define VTG_H_HD_1 0x00C0 +#define VTG_TOP_V_VD_1 0x00C4 +#define VTG_BOT_V_VD_1 0x00C8 +#define VTG_TOP_V_HD_1 0x00CC +#define VTG_BOT_V_HD_1 0x00D0
+#define VTG_H_HD_2 0x00E0 +#define VTG_TOP_V_VD_2 0x00E4 +#define VTG_BOT_V_VD_2 0x00E8 +#define VTG_TOP_V_HD_2 0x00EC +#define VTG_BOT_V_HD_2 0x00F0
+#define VTG_H_HD_3 0x0100 +#define VTG_TOP_V_VD_3 0x0104 +#define VTG_BOT_V_VD_3 0x0108 +#define VTG_TOP_V_HD_3 0x010C +#define VTG_BOT_V_HD_3 0x0110
+/* IRQ mask */
These bits are also mirrored in the status register, so "mask" isn't entirely accurate.
+#define VTG_IRQ_TOP_FIELD_MASK (1L << 1) +#define VTG_IRQ_BOTTOM_FIELD_MASK (1L << 0)
I think for these it's safe to leave out the _MASK suffix since they are really just individual bits in the interrupt registers.
+#define VTG_IRQ_MASK (VTG_IRQ_TOP_FIELD_MASK | \
VTG_IRQ_BOTTOM_FIELD_MASK)
You should be using tabs and spaces more consistently. The general rule of thumb is to indent using tabs and use spaces only to pad and align.
+/* Delay introduced by the AWG in nb of pixels */
What's an AWG?
+#define AWG_DELAY_HD (-9) +#define AWG_DELAY_ED (-8) +#define AWG_DELAY_SD (-7)
+static const struct of_device_id vtg_match_types[];
It's more typical to avoid this kind of forward declaration. Instead you can move the vtg_match_types table right before the first reference (i.e. vtg_compositor_probe()). Also _types is an odd suffix. Maybe vtg_of_match?
+/*
- STI VTG data structure
- @nb_reg: number of memory resources to register
- @reg_names: names of the memory resources to register
- @regs: ioremapped registers
- */
+#define MAX_MEM_REGION 2 +#define VTG_MASTER 0 +#define VTG_SLAVE 1 +struct sti_vtg_data {
- int nb_reg;
- char *reg_names[MAX_MEM_REGION];
- void __iomem *regs[MAX_MEM_REGION];
+};
The primary reason why I dislike this is that it requires you to clutter your code with conditionals as to the type of VTG. On the other hand, if you can expose both master and slave VTGs as separate devices you can handle them more uniformly.
+/*
- STI VTG structure
- @dev: pointer to device driver
- @data: data associated to the device
- @irq: VTG irq
- @type: VTG type (main or aux)
- @notifier_list: notifier callback
- */
+struct sti_vtg {
- struct device *dev;
- struct sti_vtg_data data;
- int irq;
- int type;
- struct raw_notifier_head notifier_list;
+};
+/*
- STiH416:
- VTG slave is connected to the VTG master by the ext0 input (ext1 input is
- left unconnected).
MPE ! ! SAS
What are MPE and SAS?
- ________ ! ! ________
- | | ! ! ext0 | |
- | | _________! !_________ ----->| |
- | VTG | hsync | ! ! | hsync | | VTG |
- | |------------>| VTAC Tx !==>! VTAC Rx |-------- | |
- | master | vsync |_________! !_________| vsync | slave |
- | | ! ! ---->| |
- |________| ! ! ext1 |________|
! ! |
! ! synchro_irq
- */
+struct sti_vtg_data stih416_vtg_data = {
- .nb_reg = 2,
- .reg_names = {"master", "slave"},
+};
It seems to me like a better representation might be to split this into two separate devices, one being the master, the other the slave. That only works if the register spaces are reasonably well separated.
+/*
- STiH407:
- Only VTG master is used. There is no MPE and SAS domain => only one domain.
MPE
- | |
- | |
- | VTG | hsync
- | |------------>
- | master | vsync
- | |
- |________|
|
- synchro_irq
- */
+struct sti_vtg_data stih407_vtg_data = {
- .nb_reg = 1,
- .reg_names = {"master"},
+};
+static int vtg_reset(void __iomem *regs) +{
- writel(1, regs + VTG_DRST_AUTOC);
- return 0;
+}
This can't fail, so no need to make it return an int. Also I think this would be more consistent if it took a struct sti_vtg * instead of a pointer to the registers.
+static int vtg_reg_dump(struct device *dev, void __iomem *regs) +{
- dev_dbg(dev, "regs %p\n", regs);
- dev_dbg(dev, "VTG_MODE 0x%x\n", readl(regs + VTG_MODE));
- dev_dbg(dev, "VTG_CLKLN 0x%x\n", readl(regs + VTG_CLKLN));
- dev_dbg(dev, "VTG_HLFLN 0x%x\n", readl(regs + VTG_HLFLN));
- dev_dbg(dev, "VTG_VID_TFO 0x%x\n", readl(regs + VTG_VID_TFO));
- dev_dbg(dev, "VTG_VID_BFO 0x%x\n", readl(regs + VTG_VID_BFO));
- dev_dbg(dev, "VTG_VID_TFS 0x%x\n", readl(regs + VTG_VID_TFS));
- dev_dbg(dev, "VTG_VID_BFS 0x%x\n", readl(regs + VTG_VID_BFS));
- dev_dbg(dev, "VTG_H_HD_1 0x%x\n", readl(regs + VTG_H_HD_1));
- dev_dbg(dev, "VTG_TOP_V_VD_1 0x%x\n", readl(regs + VTG_TOP_V_VD_1));
- dev_dbg(dev, "VTG_BOT_V_VD_1 0x%x\n", readl(regs + VTG_BOT_V_VD_1));
- dev_dbg(dev, "VTG_TOP_V_HD_1 0x%x\n", readl(regs + VTG_TOP_V_HD_1));
- dev_dbg(dev, "VTG_BOT_V_HD_1 0x%x\n", readl(regs + VTG_BOT_V_HD_1));
- dev_dbg(dev, "VTG_H_HD_2 0x%x\n", readl(regs + VTG_H_HD_2));
- dev_dbg(dev, "VTG_TOP_V_VD_2 0x%x\n", readl(regs + VTG_TOP_V_VD_2));
- dev_dbg(dev, "VTG_BOT_V_VD_2 0x%x\n", readl(regs + VTG_BOT_V_VD_2));
- dev_dbg(dev, "VTG_TOP_V_HD_2 0x%x\n", readl(regs + VTG_TOP_V_HD_2));
- dev_dbg(dev, "VTG_BOT_V_HD_2 0x%x\n", readl(regs + VTG_BOT_V_HD_2));
- dev_dbg(dev, "VTG_H_HD_3 0x%x\n", readl(regs + VTG_H_HD_3));
- dev_dbg(dev, "VTG_TOP_V_VD_3 0x%x\n", readl(regs + VTG_TOP_V_VD_3));
- dev_dbg(dev, "VTG_BOT_V_VD_3 0x%x\n", readl(regs + VTG_BOT_V_VD_3));
- dev_dbg(dev, "VTG_TOP_V_HD_3 0x%x\n", readl(regs + VTG_TOP_V_HD_3));
- dev_dbg(dev, "VTG_BOT_V_HD_3 0x%x\n", readl(regs + VTG_BOT_V_HD_3));
- return 0;
+}
Maybe this should be exposed via debugfs?
+static int vtg_write_reg(void __iomem *regs,
int type, const struct drm_display_mode *mode)
Perhaps this should be called something like vtg_set_mode() since it's evidently doing a whole lot more than just writing a register. Also it should take a struct sti_vtg *, in which case you can leave drop the type parameter as well.
+{
- int fo, fs, h_hd, v_vd, v_hd;
These should be u32. And you can probably get away with just a single one.
- writel(mode->htotal, regs + VTG_CLKLN);
- writel(mode->vtotal * 2, regs + VTG_HLFLN);
- fo = (mode->vtotal - mode->vsync_start + 1) << 16;
- fo |= mode->htotal - mode->hsync_start;
- writel(fo, regs + VTG_VID_TFO);
- writel(fo, regs + VTG_VID_BFO);
- fs = (mode->vdisplay + mode->vtotal - mode->vsync_start + 1) << 16;
- fs |= mode->hdisplay + mode->htotal - mode->hsync_start;
- writel(fs, regs + VTG_VID_TFS);
- writel(fs, regs + VTG_VID_BFS);
- /* Prepare VTG set 1 for HDMI and VTG set 3 for HD DAC */
- h_hd = (mode->hsync_end - mode->hsync_start) << 16;
- writel(h_hd, regs + VTG_H_HD_1);
- v_vd = (mode->vsync_end - mode->vsync_start + 1) << 16;
- v_vd |= 1;
- writel(v_vd, regs + VTG_TOP_V_VD_1);
- writel(v_vd, regs + VTG_BOT_V_VD_1);
- writel(0, regs + VTG_TOP_V_HD_1);
- writel(0, regs + VTG_BOT_V_HD_1);
- /* Prepare VTG set 2 for for HD DCS */
- writel(h_hd, regs + VTG_H_HD_2);
- writel(v_vd, regs + VTG_TOP_V_VD_2);
- writel(v_vd, regs + VTG_BOT_V_VD_2);
- writel(0, regs + VTG_TOP_V_HD_2);
- writel(0, regs + VTG_BOT_V_HD_2);
Does the ordering matter here? Otherwise the writes of the same value could be moved together and save a register.
- /* Prepare VTG set 3 for HD Analog in HD mode */
- h_hd = (mode->hsync_end - mode->hsync_start + AWG_DELAY_HD) << 16;
- h_hd |= mode->htotal + AWG_DELAY_HD;
- writel(h_hd, regs + VTG_H_HD_3);
- v_vd = (mode->vsync_end - mode->vsync_start) << 16;
- v_vd |= mode->vtotal;
- writel(v_vd, regs + VTG_TOP_V_VD_3);
- writel(v_vd, regs + VTG_BOT_V_VD_3);
- v_hd = (mode->htotal + AWG_DELAY_HD) << 16;
- v_hd |= mode->htotal + AWG_DELAY_HD;
- writel(v_hd, regs + VTG_TOP_V_HD_3);
- writel(v_hd, regs + VTG_BOT_V_HD_3);
It's somewhat odd that many of these TOP/BOT pairs are written with the same values, but since I have no idea what each of them is supposed to do I can't really say if that's intentional or might be wrong. I assume that you've tested this code and therefore it works as expected, but it is still odd to write the same value into so many different registers.
- /* Mode */
- writel(type, regs + VTG_MODE);
- return 0;
+}
+static int vtg_disable_irq(void __iomem *regs) +{
- /* Clear interrupt status and mask */
- writel(0xFFFF, regs + VTG_HOST_ITS_BCLR);
- writel(0xFFFF, regs + VTG_HOST_ITM_BCLR);
I think typically "disabling an interrupt" only means clearing (or setting, depending on the hardware) the mask. If you clear the interrupt status at the same time you're also dropping any pending interrupts.
Also I think a more common pattern would be to disable interrupts first to make sure that clearing them won't immediately trigger a new interrupt.
This should also take a struct sti_vtg * and not return anything since it will never fail.
- return 0;
+}
+static int vtg_enable_irq(void __iomem *regs) +{
- vtg_disable_irq(regs);
- writel(VTG_IRQ_MASK, regs + VTG_HOST_ITM_BSET);
- return 0;
+}
I think in this case it would probably be useful to open-code this rather than reuse vtg_disable_irq(). For instance when interrupts are to be enabled, why do they need to disable them first? It sounds like if that's what you really want, then you should do it using something explicit like this:
vtg_disable_irq(...); vtg_enable_irq(...);
+int vtg_set_config(struct device *dev, const struct drm_display_mode *mode) +{
- struct sti_vtg *vtg = dev_get_drvdata(dev);
This seems somewhat brittle. I'd recommend you turn this into something more robust. Just to illustrate, and I haven't looked at any other patches yet, so I may be completely wrong here, here's what I'd imagine the device tree for this to look like:
/ { /* master VTG */ vtg@00000000 { reg = <0x00000000 0x1000>;
st,slave = <&slave>; };
/* slave VTG */ slave: vtg@00001000 { reg = <0x00001000 0x1000>; };
... };
Then the VTG driver could register each of the devices as VTG and the master could look the slave up using something like:
struct platform_device *pdev; /* from .probe() */ struct sti_vtg *vtg; /* allocated earlier */ struct device_node *np; int err;
np = of_parse_phandle(pdev->dev.of_node, "st,slave", 0); if (np) { /* VTG is a master, lookup slave */ vtg->slave = of_vtg_find(np); /* * This could be -EPROBE_DEFER if the slave hasn't been * registered yet. */ if (IS_ERR(vtg->slave)) return PTR_ERR(vtg->slave); } else { /* VTG is a slave */ }
...
err = vtg_register(vtg); if (err < 0) { ... }
vtg_register() could simply add a VTG into a global list, which of_vtg_find() could traverse to find a VTG matching the device node parsed from the phandle.
- struct sti_vtg_data *data = &vtg->data;
- if (!vtg)
return 1;
- if (!data->regs[VTG_MASTER])
return 1;
With the above these additional checks become mostly unnecessary.
- /* write slave / master configuration */
- if (data->regs[VTG_SLAVE])
vtg_write_reg(data->regs[VTG_SLAVE],
VTG_TYPE_SLAVE_BY_EXT0, mode);
- vtg_write_reg(data->regs[VTG_MASTER], VTG_TYPE_MASTER, mode);
And this would become something like:
if (vtg->slave) vtg_set_mode(vtg->slave, mode);
vtg_set_mode(vtg, mode);
- /* reset slave and then master */
- if (data->regs[VTG_SLAVE])
vtg_reset(data->regs[VTG_SLAVE]);
- vtg_reset(data->regs[VTG_MASTER]);
if (vtg->slave) vtg_reset(vtg->slave);
vtg_reset(vtg);
- /* Enable irq for the vtg vblank synchro */
- if (data->regs[VTG_SLAVE])
vtg_enable_irq(data->regs[VTG_SLAVE]);
- else
vtg_enable_irq(data->regs[VTG_MASTER]);
if (vtg->slave) vtg_enable_irq(vtg->slave); else vtg_enable_irq(vtg);
- if (data->regs[VTG_SLAVE])
vtg_reg_dump(dev, data->regs[VTG_SLAVE]);
- vtg_reg_dump(dev, data->regs[VTG_MASTER]);
if (vtg->slave) vtg_reg_dump(vtg);
vtg_reg_dump(vtg);
Of course this in particular wouldn't be present in the final driver but rather exposed via each VTG's debugfs tree.
+int vtg_register_client(struct device *dev, struct notifier_block *nb) +{
- struct sti_vtg *vtg = dev_get_drvdata(dev);
- return raw_notifier_chain_register(&vtg->notifier_list, nb);
+}
+int vtg_unregister_client(struct device *dev, struct notifier_block *nb) +{
- struct sti_vtg *vtg = dev_get_drvdata(dev);
- return raw_notifier_chain_unregister(&vtg->notifier_list, nb);
+}
Can you explain what you need the notifier for? I suppose I'll see in some later patch, but at this rate it'll take me a while to get there. =) My gut tells me that we won't be needing it though.
+static irqreturn_t vtg_irq_thread(int irq, void *arg) +{
- struct sti_vtg *vtg = arg;
- struct sti_vtg_data *data = &vtg->data;
- void __iomem *regs;
- u32 status;
- if (data->regs[VTG_SLAVE])
regs = data->regs[VTG_SLAVE];
- else
regs = data->regs[VTG_MASTER];
- status = readl(regs + VTG_HOST_ITS);
- writel(status, regs + VTG_HOST_ITS_BCLR);
- /* TODO: check why this sync bus write solves the problem which
* is that without this line, the handler is sometimes called twice,
* first with status = 1 or 2, and immediately after with status=0
*/
- /* sync bus write */
- readl(regs + VTG_HOST_ITS);
I suspect this could be caused by wrongly writing the interrupt mask and status registers.
+static int vtg_compositor_bind(struct device *dev, struct device *master,
- void *d)
+{
- return 0;
+}
+static void vtg_compositor_unbind(struct device *dev, struct device *master,
- void *data)
+{
- /* do nothing */
+}
Why do you even need these if they're no-ops? How are you going to get at them from other drivers if you don't hook them up with something here? Perhaps that will be added in some subsequent patch, though.
+static const struct component_ops vtg_compositor_ops = {
- .bind = vtg_compositor_bind,
- .unbind = vtg_compositor_unbind,
+};
+static int vtg_compositor_probe(struct platform_device *pdev) +{
- struct device *dev = &pdev->dev;
- struct device_node *np = dev->of_node;
- struct sti_vtg *vtg;
- struct resource *res;
- char irq_name[32];
- struct sti_vtg_data *data;
- int i;
This can be unsigned.
- int ret;
- DRM_INFO("%s\n", __func__);
I don't think DRM_INFO is the loglevel you want here.
- if (!np)
return -ENXIO;
If the driver doesn't support non-OF configurations it's safe to assume that this will never be NULL.
- vtg = devm_kzalloc(dev, sizeof(*vtg), GFP_KERNEL);
- if (!vtg) {
DRM_ERROR("Failed to allocate VTG context\n");
return -ENOMEM;
- }
There's no need for the error message, since allocation failures will already be noisily reported by the allocator.
- vtg->dev = dev;
- /* populate data structure depending on compatibility */
- BUG_ON(!of_match_node(vtg_match_types, np)->data);
This is kind of pointless since the memcpy() below will oops equally well if ->data == NULL. I don't think you need to check this at all. If the device can't be matched in the table this function won't be called. And if .data has been set for the matching entry in the table this won't be NULL. Leaving the .data field unset doesn't make any sense for this driver.
- memcpy(&vtg->data, of_match_node(vtg_match_types, np)->data,
sizeof(struct sti_vtg_data));
Also maybe a good idea would be to store the result of of_match_node() in a temporary variable to help with readability.
Why copy memory here in the first place? This kind of data is supposed to be static configuration data, which is why it's typically static const. I've suggested some changes above to make the design somewhat more straightforward.
- data = &vtg->data;
- /* Get resources */
- for (i = 0; i < data->nb_reg; i++) {
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
data->reg_names[i]);
if (!res) {
DRM_ERROR("Invalid resource\n");
return -ENOMEM;
}
data->regs[i] = devm_ioremap_nocache(dev,
res->start,
resource_size(res));
You're supposed to also request regions to make them for exclusive use by this driver (to avoid other drivers to access the same registers without proper synchronization).
Also the canonical way to do this is using devm_ioremap_resource().
if (IS_ERR(data->regs[i]))
return PTR_ERR(data->regs[i]);
- }
- vtg->irq = platform_get_irq_byname(pdev, "synchro_irq");
- if (IS_ERR_VALUE(vtg->irq)) {
That's an odd way to write this. if (vtg->irq < 0) should be good enough.
DRM_ERROR("Failed to get VTG interrupt\n");
return vtg->irq;
- }
- snprintf(irq_name, sizeof(irq_name), "vsync-%s", vtg_get_name(vtg));
I think it would be just fine to use dev_name(vtg->dev) instead of generating this name, but meh.
- RAW_INIT_NOTIFIER_HEAD(&vtg->notifier_list);
- ret = devm_request_threaded_irq(dev, vtg->irq, NULL, vtg_irq_thread,
IRQF_ONESHOT, irq_name, vtg);
- if (IS_ERR_VALUE(ret)) {
DRM_ERROR("Failed to register VTG interrupt\n");
return ret;
- }
- if (of_property_read_bool(np, "vtg-aux")) {
vtg->type = VTG_AUX;
vtg_aux = dev;
- } else {
vtg->type = VTG_MAIN;
vtg_main = dev;
- }
I wonder if these couldn't be made dyna
- platform_set_drvdata(pdev, vtg);
- DRM_INFO("%s VTG %s\n", __func__, vtg_get_name(vtg));
This also looks like a leftover debug message.
- return component_add(&pdev->dev, &vtg_compositor_ops);
+}
+static int vtg_compositor_remove(struct platform_device *pdev) +{
- component_del(&pdev->dev, &vtg_compositor_ops);
- return 0;
+}
+static const struct of_device_id vtg_match_types[] = {
- {
.compatible = "st,stih416-vtg",
.data = &stih416_vtg_data,
},
- {
.compatible = "st,stih407-vtg",
.data = &stih407_vtg_data,
},
- { /* end node */ }
There's some funky indentation here.
+}; +MODULE_DEVICE_TABLE(of, vtg_match_types);
+struct platform_driver sti_vtg_driver = {
- .driver = {
.name = "sti-vtg",
.owner = THIS_MODULE,
.of_match_table = vtg_match_types,
},
Indentation is odd here again.
- .probe = vtg_compositor_probe,
- .remove = vtg_compositor_remove,
+};
+module_platform_driver(sti_vtg_driver); diff --git a/drivers/gpu/drm/sti/sti_vtg.h b/drivers/gpu/drm/sti/sti_vtg.h new file mode 100644 index 0000000..da67eb3 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg.h @@ -0,0 +1,20 @@ +/*
- Copyright (C) STMicroelectronics SA 2013
- Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#ifndef _STI_VTG_H_ +#define _STI_VTG_H_
+#include <linux/platform_device.h>
Why do you need this include? I don't see anything in this file that's defined in platform_device.h.
+#include <drm/drmP.h>
You don't actually need to include these here. You can simply forward declare the structures (which works because you only pass around pointers to them) like so:
struct drm_display_mode; struct notifier_block;
+extern struct device *vtg_main; +extern struct device *vtg_aux;
+int vtg_set_config(struct device *dev, const struct drm_display_mode *mode); +int vtg_register_client(struct device *dev, struct notifier_block *nb); +int vtg_unregister_client(struct device *dev, struct notifier_block *nb);
+#endif diff --git a/drivers/gpu/drm/sti/sti_vtg_utils.c b/drivers/gpu/drm/sti/sti_vtg_utils.c new file mode 100644 index 0000000..06bb4b4 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg_utils.c @@ -0,0 +1,99 @@ +/*
- Copyright (C) STMicroelectronics SA 2013
- Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#include <linux/platform_device.h>
+#include "sti_vtg_utils.h" +#include "sti_vtg.h"
+struct device *vtg_main; +struct device *vtg_aux;
I don't think these should be here. There is always a better way than globals.
+int sti_vtg_setconfig(int main_aux, const struct drm_display_mode *mode) +{
- if (main_aux == VTG_MAIN && vtg_main)
return vtg_set_config(vtg_main, mode);
- if (main_aux == VTG_AUX && vtg_aux)
return vtg_set_config(vtg_aux, mode);
- return 1;
+}
+int sti_vtg_register_client(int main_aux, struct notifier_block *nb) +{
- if (main_aux == VTG_MAIN && vtg_main)
return vtg_register_client(vtg_main, nb);
- if (main_aux == VTG_AUX && vtg_aux)
return vtg_register_client(vtg_aux, nb);
- return 1;
+}
+int sti_vtg_unregister_client(int main_aux, struct notifier_block *nb) +{
- if (main_aux == VTG_MAIN && vtg_main)
return vtg_unregister_client(vtg_main, nb);
- if (main_aux == VTG_AUX && vtg_aux)
return vtg_unregister_client(vtg_aux, nb);
- return 1;
+}
+/*
Active Front Sync Back Active
Region Porch Porch Region
- <---------------><-------->0<---------><--------><----------------->
- ///////////////| | ///////////////|
- /////////////// | | /////////////// |
- /////////////// |......... ..........|/////////////// |
0___________ x/ymin x/ymax
- <--[hv]display--> <--[hv]display-->
- <--[hv]sync_start---------> <--[hv]sync_start-
- <--[hv]sync_end-----------------------> <--[hv]sync_end---
- <--[hv]total------------------------------------> <--[hv]total------
- */
+/*
- sti_vtg_get_line_number
- @mode: display mode to be used
- @y: line
- Return the line number according to the display mode taking
- into account the Sync and Back Porch information.
- Video frame line numbers start at 1, y starts at 0.
- In interlaced modes the start line is the field line number of the odd
- field, but y is still defined as a progressive frame.
- */
+u32 sti_vtg_get_line_number(struct drm_display_mode mode, int y) +{
- int start_line = mode.vtotal - mode.vsync_start + 1;
- if (mode.flags & DRM_MODE_FLAG_INTERLACE)
start_line *= 2;
- return start_line + y;
+}
+/*
- sti_vtg_get_pixel_number
- @mode: display mode to be used
- @x: row
- Return the pixel number according to the display mode taking
- into account the Sync and Back Porch information.
- Pixels are counted from 0.
- */
+u32 sti_vtg_get_pixel_number(struct drm_display_mode mode, int x) +{
- return mode.htotal - mode.hsync_start + x;
+}
It's somewhat unclear as to what the purpose is of these. Perhaps it will become clearer when I see them used in a subsequent patch.
Also it looks like the comments are supposed to be kernel-doc, but they aren't really.
diff --git a/drivers/gpu/drm/sti/sti_vtg_utils.h b/drivers/gpu/drm/sti/sti_vtg_utils.h new file mode 100644 index 0000000..fea2852 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg_utils.h @@ -0,0 +1,29 @@ +/*
- Copyright (C) STMicroelectronics SA 2013
- Authors: Benjamin Gaignard benjamin.gaignard@st.com
Fabien Dessenne <fabien.dessenne@st.com>
for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#ifndef _STI_VTG_UTILS_H_ +#define _STI_VTG_UTILS_H_
+#include <drm/drmP.h>
+#define WAIT_NEXT_VSYNC_MS 50 /*ms*/
What is this used for?
+#define VTG_MAIN 0 +#define VTG_AUX 1
+#define VTG_TOP_FIELD_EVENT 1 +#define VTG_BOTTOM_FIELD_EVENT 2
I would hope that we can get rid of these somehow.
+int sti_vtg_setconfig(int main_aux, const struct drm_display_mode *mode); +int sti_vtg_register_client(int main_aux, struct notifier_block *nb); +int sti_vtg_unregister_client(int main_aux, struct notifier_block *nb);
And these.
It's intriguing hardware I must say, and certainly a challenge since it's so different from other DRM drivers. And it's taken quite some time to go through this one file, so I won't be able to review all patches in one go.
Thierry
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 | 11 + drivers/gpu/drm/sti/Makefile | 3 + drivers/gpu/drm/sti/sti_vtg.c | 468 ++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_vtg.h | 20 ++ drivers/gpu/drm/sti/sti_vtg_utils.c | 99 ++++++++ drivers/gpu/drm/sti/sti_vtg_utils.h | 29 +++ 8 files changed, 633 insertions(+) create mode 100644 drivers/gpu/drm/sti/Kconfig create mode 100644 drivers/gpu/drm/sti/Makefile create mode 100644 drivers/gpu/drm/sti/sti_vtg.c create mode 100644 drivers/gpu/drm/sti/sti_vtg.h create mode 100644 drivers/gpu/drm/sti/sti_vtg_utils.c create mode 100644 drivers/gpu/drm/sti/sti_vtg_utils.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index d1cc2f6..0e30029 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -201,3 +201,5 @@ source "drivers/gpu/drm/tegra/Kconfig" source "drivers/gpu/drm/panel/Kconfig" source "drivers/gpu/drm/bridge/Kconfig"
+source "drivers/gpu/drm/sti/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 5e792b0..44f7b17 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_DRM_QXL) += qxl/ obj-$(CONFIG_DRM_BOCHS) += bochs/ obj-$(CONFIG_DRM_MSM) += msm/ obj-$(CONFIG_DRM_TEGRA) += tegra/ +obj-$(CONFIG_DRM_STI) += sti/ obj-y += i2c/ obj-y += panel/ obj-y += bridge/ diff --git a/drivers/gpu/drm/sti/Kconfig b/drivers/gpu/drm/sti/Kconfig new file mode 100644 index 0000000..3fff278 --- /dev/null +++ b/drivers/gpu/drm/sti/Kconfig @@ -0,0 +1,11 @@ +config DRM_STI
- bool "DRM Support for STMicroelectronics SoC stiH41x Series"
- depends on DRM && (SOC_STIH415 || SOC_STIH416 || ARCH_MULTIPLATFORM)
- help
Choose this option to enable DRM on STM stiH41x chipset
+config VTG_STI
- bool "Video Timing Generator for STMicroelectronics SoC stiH41x Series"
- depends on DRM_STI
- help
Choose this option to enable VTG on STM stiH41x chipset
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile new file mode 100644 index 0000000..33216e1 --- /dev/null +++ b/drivers/gpu/drm/sti/Makefile @@ -0,0 +1,3 @@ +ccflags-y := -Iinclude/drm
Why is this required?
+obj-$(CONFIG_VTG_STI) += sti_vtg.o sti_vtg_utils.o diff --git a/drivers/gpu/drm/sti/sti_vtg.c b/drivers/gpu/drm/sti/sti_vtg.c new file mode 100644 index 0000000..75d7125 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg.c @@ -0,0 +1,468 @@ +/*
- Copyright (C) STMicroelectronics SA 2013
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/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/notifier.h>
+#include "sti_vtg_utils.h" +#include "sti_vtg.h"
+#define VTG_TYPE_MASTER 0 +#define VTG_TYPE_SLAVE_BY_EXT0 1
+/* registers offset */ +#define VTG_MODE 0x0000 +#define VTG_CLKLN 0x0008 +#define VTG_HLFLN 0x000C +#define VTG_DRST_AUTOC 0x0010 +#define VTG_VID_TFO 0x0040 +#define VTG_VID_TFS 0x0044 +#define VTG_VID_BFO 0x0048 +#define VTG_VID_BFS 0x004C
+#define VTG_HOST_ITS 0x0078 +#define VTG_HOST_ITS_BCLR 0x007C +#define VTG_HOST_ITM_BCLR 0x0088 +#define VTG_HOST_ITM_BSET 0x008C
+#define VTG_H_HD_1 0x00C0 +#define VTG_TOP_V_VD_1 0x00C4 +#define VTG_BOT_V_VD_1 0x00C8 +#define VTG_TOP_V_HD_1 0x00CC +#define VTG_BOT_V_HD_1 0x00D0
+#define VTG_H_HD_2 0x00E0 +#define VTG_TOP_V_VD_2 0x00E4 +#define VTG_BOT_V_VD_2 0x00E8 +#define VTG_TOP_V_HD_2 0x00EC +#define VTG_BOT_V_HD_2 0x00F0
+#define VTG_H_HD_3 0x0100 +#define VTG_TOP_V_VD_3 0x0104 +#define VTG_BOT_V_VD_3 0x0108 +#define VTG_TOP_V_HD_3 0x010C +#define VTG_BOT_V_HD_3 0x0110
+/* IRQ mask */ +#define VTG_IRQ_TOP_FIELD_MASK (1L << 1) +#define VTG_IRQ_BOTTOM_FIELD_MASK (1L << 0)
BIT()
+#define VTG_IRQ_MASK (VTG_IRQ_TOP_FIELD_MASK | \
VTG_IRQ_BOTTOM_FIELD_MASK)
+/* Delay introduced by the AWG in nb of pixels */ +#define AWG_DELAY_HD (-9) +#define AWG_DELAY_ED (-8) +#define AWG_DELAY_SD (-7)
+static const struct of_device_id vtg_match_types[];
+/*
Kernel doc style comments start with /**
- STI VTG data structure
- @nb_reg: number of memory resources to register
- @reg_names: names of the memory resources to register
- @regs: ioremapped registers
- */
+#define MAX_MEM_REGION 2 +#define VTG_MASTER 0 +#define VTG_SLAVE 1
I'd place these above the header.
+struct sti_vtg_data {
- int nb_reg;
- char *reg_names[MAX_MEM_REGION];
- void __iomem *regs[MAX_MEM_REGION];
+};
+/*
Kernel doc notation please - /**
I won't comment on this again from here.
- STI VTG structure
- @dev: pointer to device driver
- @data: data associated to the device
- @irq: VTG irq
- @type: VTG type (main or aux)
- @notifier_list: notifier callback
- */
+struct sti_vtg {
- struct device *dev;
- struct sti_vtg_data data;
- int irq;
- int type;
- struct raw_notifier_head notifier_list;
+};
+/*
- STiH416:
- VTG slave is connected to the VTG master by the ext0 input (ext1 input is
- left unconnected).
MPE ! ! SAS
- ________ ! ! ________
- | | ! ! ext0 | |
- | | _________! !_________ ----->| |
- | VTG | hsync | ! ! | hsync | | VTG |
- | |------------>| VTAC Tx !==>! VTAC Rx |-------- | |
- | master | vsync |_________! !_________| vsync | slave |
- | | ! ! ---->| |
- |________| ! ! ext1 |________|
! ! |
! ! synchro_irq
- */
+struct sti_vtg_data stih416_vtg_data = {
- .nb_reg = 2,
- .reg_names = {"master", "slave"},
+};
+/*
- STiH407:
- Only VTG master is used. There is no MPE and SAS domain => only one domain.
MPE
- | |
- | |
- | VTG | hsync
- | |------------>
- | master | vsync
- | |
- |________|
|
- synchro_irq
- */
Odd comment tail.
+struct sti_vtg_data stih407_vtg_data = {
- .nb_reg = 1,
- .reg_names = {"master"},
+};
+static int vtg_reset(void __iomem *regs) +{
- writel(1, regs + VTG_DRST_AUTOC);
- return 0;
+}
The return value of this is never checked. Make it an inline void.
+static int vtg_reg_dump(struct device *dev, void __iomem *regs) +{
- dev_dbg(dev, "regs %p\n", regs);
- dev_dbg(dev, "VTG_MODE 0x%x\n", readl(regs + VTG_MODE));
- dev_dbg(dev, "VTG_CLKLN 0x%x\n", readl(regs + VTG_CLKLN));
- dev_dbg(dev, "VTG_HLFLN 0x%x\n", readl(regs + VTG_HLFLN));
- dev_dbg(dev, "VTG_VID_TFO 0x%x\n", readl(regs + VTG_VID_TFO));
- dev_dbg(dev, "VTG_VID_BFO 0x%x\n", readl(regs + VTG_VID_BFO));
- dev_dbg(dev, "VTG_VID_TFS 0x%x\n", readl(regs + VTG_VID_TFS));
- dev_dbg(dev, "VTG_VID_BFS 0x%x\n", readl(regs + VTG_VID_BFS));
- dev_dbg(dev, "VTG_H_HD_1 0x%x\n", readl(regs + VTG_H_HD_1));
- dev_dbg(dev, "VTG_TOP_V_VD_1 0x%x\n", readl(regs + VTG_TOP_V_VD_1));
- dev_dbg(dev, "VTG_BOT_V_VD_1 0x%x\n", readl(regs + VTG_BOT_V_VD_1));
- dev_dbg(dev, "VTG_TOP_V_HD_1 0x%x\n", readl(regs + VTG_TOP_V_HD_1));
- dev_dbg(dev, "VTG_BOT_V_HD_1 0x%x\n", readl(regs + VTG_BOT_V_HD_1));
- dev_dbg(dev, "VTG_H_HD_2 0x%x\n", readl(regs + VTG_H_HD_2));
- dev_dbg(dev, "VTG_TOP_V_VD_2 0x%x\n", readl(regs + VTG_TOP_V_VD_2));
- dev_dbg(dev, "VTG_BOT_V_VD_2 0x%x\n", readl(regs + VTG_BOT_V_VD_2));
- dev_dbg(dev, "VTG_TOP_V_HD_2 0x%x\n", readl(regs + VTG_TOP_V_HD_2));
- dev_dbg(dev, "VTG_BOT_V_HD_2 0x%x\n", readl(regs + VTG_BOT_V_HD_2));
- dev_dbg(dev, "VTG_H_HD_3 0x%x\n", readl(regs + VTG_H_HD_3));
- dev_dbg(dev, "VTG_TOP_V_VD_3 0x%x\n", readl(regs + VTG_TOP_V_VD_3));
- dev_dbg(dev, "VTG_BOT_V_VD_3 0x%x\n", readl(regs + VTG_BOT_V_VD_3));
- dev_dbg(dev, "VTG_TOP_V_HD_3 0x%x\n", readl(regs + VTG_TOP_V_HD_3));
- dev_dbg(dev, "VTG_BOT_V_HD_3 0x%x\n", readl(regs + VTG_BOT_V_HD_3));
- return 0;
+}
+static int vtg_write_reg(void __iomem *regs,
int type, const struct drm_display_mode *mode)
+{
- int fo, fs, h_hd, v_vd, v_hd;
- writel(mode->htotal, regs + VTG_CLKLN);
- writel(mode->vtotal * 2, regs + VTG_HLFLN);
- fo = (mode->vtotal - mode->vsync_start + 1) << 16;
- fo |= mode->htotal - mode->hsync_start;
- writel(fo, regs + VTG_VID_TFO);
- writel(fo, regs + VTG_VID_BFO);
- fs = (mode->vdisplay + mode->vtotal - mode->vsync_start + 1) << 16;
- fs |= mode->hdisplay + mode->htotal - mode->hsync_start;
- writel(fs, regs + VTG_VID_TFS);
- writel(fs, regs + VTG_VID_BFS);
This is all hieroglyphics to people who don't know what 'fo' and 'fs' is. Can you supplement my ignorance with some nice comments please? The same should apply to any code which isn't doing anything obvious.
- /* Prepare VTG set 1 for HDMI and VTG set 3 for HD DAC */
- h_hd = (mode->hsync_end - mode->hsync_start) << 16;
- writel(h_hd, regs + VTG_H_HD_1);
- v_vd = (mode->vsync_end - mode->vsync_start + 1) << 16;
- v_vd |= 1;
- writel(v_vd, regs + VTG_TOP_V_VD_1);
- writel(v_vd, regs + VTG_BOT_V_VD_1);
- writel(0, regs + VTG_TOP_V_HD_1);
- writel(0, regs + VTG_BOT_V_HD_1);
- /* Prepare VTG set 2 for for HD DCS */
- writel(h_hd, regs + VTG_H_HD_2);
- writel(v_vd, regs + VTG_TOP_V_VD_2);
- writel(v_vd, regs + VTG_BOT_V_VD_2);
- writel(0, regs + VTG_TOP_V_HD_2);
- writel(0, regs + VTG_BOT_V_HD_2);
- /* Prepare VTG set 3 for HD Analog in HD mode */
- h_hd = (mode->hsync_end - mode->hsync_start + AWG_DELAY_HD) << 16;
- h_hd |= mode->htotal + AWG_DELAY_HD;
- writel(h_hd, regs + VTG_H_HD_3);
- v_vd = (mode->vsync_end - mode->vsync_start) << 16;
- v_vd |= mode->vtotal;
- writel(v_vd, regs + VTG_TOP_V_VD_3);
- writel(v_vd, regs + VTG_BOT_V_VD_3);
- v_hd = (mode->htotal + AWG_DELAY_HD) << 16;
- v_hd |= mode->htotal + AWG_DELAY_HD;
- writel(v_hd, regs + VTG_TOP_V_HD_3);
- writel(v_hd, regs + VTG_BOT_V_HD_3);
- /* Mode */
- writel(type, regs + VTG_MODE);
- return 0;
+}
+static int vtg_disable_irq(void __iomem *regs) +{
- /* Clear interrupt status and mask */
- writel(0xFFFF, regs + VTG_HOST_ITS_BCLR);
- writel(0xFFFF, regs + VTG_HOST_ITM_BCLR);
- return 0;
+}
What's the point in returning a value if a) it's always the same and b) it's never checked?
+static int vtg_enable_irq(void __iomem *regs) +{
- vtg_disable_irq(regs);
- writel(VTG_IRQ_MASK, regs + VTG_HOST_ITM_BSET);
- return 0;
+}
Same here and for every other int returning function which is never checked.
+int vtg_set_config(struct device *dev, const struct drm_display_mode *mode) +{
- struct sti_vtg *vtg = dev_get_drvdata(dev);
- struct sti_vtg_data *data = &vtg->data;
- if (!vtg)
return 1;
What does '1 mean? Please return a valid error code.
- if (!data->regs[VTG_MASTER])
return 1;
Same here and anywhere else in the patch-set which does this.
- /* write slave / master configuration */
- if (data->regs[VTG_SLAVE])
vtg_write_reg(data->regs[VTG_SLAVE],
VTG_TYPE_SLAVE_BY_EXT0, mode);
- vtg_write_reg(data->regs[VTG_MASTER], VTG_TYPE_MASTER, mode);
Any chance in this driver using Regmap?
- /* reset slave and then master */
- if (data->regs[VTG_SLAVE])
vtg_reset(data->regs[VTG_SLAVE]);
- vtg_reset(data->regs[VTG_MASTER]);
- /* Enable irq for the vtg vblank synchro */
- if (data->regs[VTG_SLAVE])
vtg_enable_irq(data->regs[VTG_SLAVE]);
- else
vtg_enable_irq(data->regs[VTG_MASTER]);
When you write slave/master configuration and reset a few lines up from here you write to master regardless and only conditionally write to slave. Why is that different here.
- if (data->regs[VTG_SLAVE])
vtg_reg_dump(dev, data->regs[VTG_SLAVE]);
- vtg_reg_dump(dev, data->regs[VTG_MASTER]);
Same here?
Why don't you bind all of these up into one if() instead of having 4 in a row that are exactly the same?
- return 0;
+}
+int vtg_register_client(struct device *dev, struct notifier_block *nb) +{
- struct sti_vtg *vtg = dev_get_drvdata(dev);
- return raw_notifier_chain_register(&vtg->notifier_list, nb);
+}
Do these need to be exported?
+int vtg_unregister_client(struct device *dev, struct notifier_block *nb) +{
- struct sti_vtg *vtg = dev_get_drvdata(dev);
- return raw_notifier_chain_unregister(&vtg->notifier_list, nb);
+}
+static irqreturn_t vtg_irq_thread(int irq, void *arg) +{
- struct sti_vtg *vtg = arg;
- struct sti_vtg_data *data = &vtg->data;
- void __iomem *regs;
- u32 status;
- if (data->regs[VTG_SLAVE])
regs = data->regs[VTG_SLAVE];
- else
regs = data->regs[VTG_MASTER];
So if slave is present master will never be serviced?
- status = readl(regs + VTG_HOST_ITS);
- writel(status, regs + VTG_HOST_ITS_BCLR);
- /* TODO: check why this sync bus write solves the problem which
* is that without this line, the handler is sometimes called twice,
* first with status = 1 or 2, and immediately after with status=0
*/
Fix this before upstreaming it.
- /* sync bus write */
- readl(regs + VTG_HOST_ITS);
- if (status & VTG_IRQ_TOP_FIELD_MASK) {
raw_notifier_call_chain(&vtg->notifier_list,
VTG_TOP_FIELD_EVENT, &vtg->type);
- } else {
raw_notifier_call_chain(&vtg->notifier_list,
VTG_BOTTOM_FIELD_EVENT, &vtg->type);
- }
event = (status & VTG_IRQ_TOP_FIELD_MASK) ? VTG_TOP_FIELD_EVENT : VTG_BOTTOM_FIELD_EVENT,
Then you only have to have one call.
- return IRQ_HANDLED;
+}
+static const char *vtg_get_name(struct sti_vtg *vtg) +{
- if (vtg->type == VTG_MAIN)
return "main";
- else if (vtg->type == VTG_AUX)
return "aux";
- else
return "?";
+}
+static int vtg_compositor_bind(struct device *dev, struct device *master,
- void *d)
+{
- return 0;
+}
+static void vtg_compositor_unbind(struct device *dev, struct device *master,
- void *data)
+{
- /* do nothing */
+}
+static const struct component_ops vtg_compositor_ops = {
- .bind = vtg_compositor_bind,
- .unbind = vtg_compositor_unbind,
+};
If they're pointless, why supply them? Does the framework insist on it?
+static int vtg_compositor_probe(struct platform_device *pdev) +{
- struct device *dev = &pdev->dev;
- struct device_node *np = dev->of_node;
- struct sti_vtg *vtg;
- struct resource *res;
- char irq_name[32];
- struct sti_vtg_data *data;
- int i;
- int ret;
- DRM_INFO("%s\n", __func__);
Pointless debug - keep it out of the Mainlined driver.
- if (!np)
return -ENXIO;
Not seen this before; -EINVAL or -ENODEV is common.
- vtg = devm_kzalloc(dev, sizeof(*vtg), GFP_KERNEL);
- if (!vtg) {
DRM_ERROR("Failed to allocate VTG context\n");
No need for OOM errors, just return -ENOMEM.
return -ENOMEM;
- }
- vtg->dev = dev;
- /* populate data structure depending on compatibility */
- BUG_ON(!of_match_node(vtg_match_types, np)->data);
I'm not sure killing the kernel over this is the correct thing to do.
Just return an error.
- memcpy(&vtg->data, of_match_node(vtg_match_types, np)->data,
sizeof(struct sti_vtg_data));
This is pretty ugly. Why aren't you just using pointers? At the very least, break these lines out.
- data = &vtg->data;
- /* Get resources */
- for (i = 0; i < data->nb_reg; i++) {
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
data->reg_names[i]);
if (!res) {
DRM_ERROR("Invalid resource\n");
return -ENOMEM;
}
No memory? Are you sure that's what it means?
data->regs[i] = devm_ioremap_nocache(dev,
res->start,
resource_size(res));
if (IS_ERR(data->regs[i]))
return PTR_ERR(data->regs[i]);
- }
- vtg->irq = platform_get_irq_byname(pdev, "synchro_irq");
- if (IS_ERR_VALUE(vtg->irq)) {
DRM_ERROR("Failed to get VTG interrupt\n");
return vtg->irq;
- }
- snprintf(irq_name, sizeof(irq_name), "vsync-%s", vtg_get_name(vtg));
- RAW_INIT_NOTIFIER_HEAD(&vtg->notifier_list);
- ret = devm_request_threaded_irq(dev, vtg->irq, NULL, vtg_irq_thread,
IRQF_ONESHOT, irq_name, vtg);
- if (IS_ERR_VALUE(ret)) {
DRM_ERROR("Failed to register VTG interrupt\n");
return ret;
- }
- if (of_property_read_bool(np, "vtg-aux")) {
vtg->type = VTG_AUX;
vtg_aux = dev;
- } else {
vtg->type = VTG_MAIN;
vtg_main = dev;
- }
- platform_set_drvdata(pdev, vtg);
- DRM_INFO("%s VTG %s\n", __func__, vtg_get_name(vtg));
Again, no need for this.
- return component_add(&pdev->dev, &vtg_compositor_ops);
+}
+static int vtg_compositor_remove(struct platform_device *pdev) +{
- component_del(&pdev->dev, &vtg_compositor_ops);
- return 0;
+}
+static const struct of_device_id vtg_match_types[] = {
- {
.compatible = "st,stih416-vtg",
.data = &stih416_vtg_data,
These need one more tab.
},
- {
.compatible = "st,stih407-vtg",
.data = &stih407_vtg_data,
},
In fact, this is all a little messy, use tabs throughout.
- { /* end node */ }
+}; +MODULE_DEVICE_TABLE(of, vtg_match_types);
+struct platform_driver sti_vtg_driver = {
- .driver = {
.name = "sti-vtg",
.owner = THIS_MODULE,
.of_match_table = vtg_match_types,
},
These tabs are not correct either. Did you run checkpatch.pl?
- .probe = vtg_compositor_probe,
- .remove = vtg_compositor_remove,
+};
+module_platform_driver(sti_vtg_driver); diff --git a/drivers/gpu/drm/sti/sti_vtg.h b/drivers/gpu/drm/sti/sti_vtg.h new file mode 100644 index 0000000..da67eb3 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg.h @@ -0,0 +1,20 @@ +/*
- Copyright (C) STMicroelectronics SA 2013
It's 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_
DRM_STI...?
+#include <linux/platform_device.h> +#include <drm/drmP.h>
+extern struct device *vtg_main; +extern struct device *vtg_aux;
+int vtg_set_config(struct device *dev, const struct drm_display_mode *mode); +int vtg_register_client(struct device *dev, struct notifier_block *nb); +int vtg_unregister_client(struct device *dev, struct notifier_block *nb);
+#endif diff --git a/drivers/gpu/drm/sti/sti_vtg_utils.c b/drivers/gpu/drm/sti/sti_vtg_utils.c new file mode 100644 index 0000000..06bb4b4 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg_utils.c @@ -0,0 +1,99 @@ +/*
- Copyright (C) STMicroelectronics SA 2013
- Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#include <linux/platform_device.h>
+#include "sti_vtg_utils.h" +#include "sti_vtg.h"
+struct device *vtg_main; +struct device *vtg_aux;
+int sti_vtg_setconfig(int main_aux, const struct drm_display_mode *mode) +{
- if (main_aux == VTG_MAIN && vtg_main)
return vtg_set_config(vtg_main, mode);
- if (main_aux == VTG_AUX && vtg_aux)
return vtg_set_config(vtg_aux, mode);
Any reason for the 'setconfig' and 'set_config' inconsistency?
- return 1;
Use proper return values.
+}
Do these need to be in a seperate file? Still looks VTG related?
+int sti_vtg_register_client(int main_aux, struct notifier_block *nb) +{
- if (main_aux == VTG_MAIN && vtg_main)
return vtg_register_client(vtg_main, nb);
- if (main_aux == VTG_AUX && vtg_aux)
return vtg_register_client(vtg_aux, nb);
- return 1;
+}
+int sti_vtg_unregister_client(int main_aux, struct notifier_block *nb) +{
- if (main_aux == VTG_MAIN && vtg_main)
return vtg_unregister_client(vtg_main, nb);
- if (main_aux == VTG_AUX && vtg_aux)
return vtg_unregister_client(vtg_aux, nb);
- return 1;
+}
+/*
Active Front Sync Back Active
Region Porch Porch Region
- <---------------><-------->0<---------><--------><----------------->
- ///////////////| | ///////////////|
- /////////////// | | /////////////// |
- /////////////// |......... ..........|/////////////// |
0___________ x/ymin x/ymax
- <--[hv]display--> <--[hv]display-->
- <--[hv]sync_start---------> <--[hv]sync_start-
- <--[hv]sync_end-----------------------> <--[hv]sync_end---
- <--[hv]total------------------------------------> <--[hv]total------
- */
I'm not sure if I'm any more enlightened by this?
+/*
/**
- sti_vtg_get_line_number
- @mode: display mode to be used
- @y: line
- Return the line number according to the display mode taking
- into account the Sync and Back Porch information.
- Video frame line numbers start at 1, y starts at 0.
- In interlaced modes the start line is the field line number of the odd
- field, but y is still defined as a progressive frame.
- */
+u32 sti_vtg_get_line_number(struct drm_display_mode mode, int y) +{
- int start_line = mode.vtotal - mode.vsync_start + 1;
- if (mode.flags & DRM_MODE_FLAG_INTERLACE)
start_line *= 2;
- return start_line + y;
+}
+/*
- sti_vtg_get_pixel_number
- @mode: display mode to be used
- @x: row
- Return the pixel number according to the display mode taking
- into account the Sync and Back Porch information.
- Pixels are counted from 0.
- */
+u32 sti_vtg_get_pixel_number(struct drm_display_mode mode, int x) +{
- return mode.htotal - mode.hsync_start + x;
+} diff --git a/drivers/gpu/drm/sti/sti_vtg_utils.h b/drivers/gpu/drm/sti/sti_vtg_utils.h new file mode 100644 index 0000000..fea2852 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg_utils.h @@ -0,0 +1,29 @@ +/*
- Copyright (C) STMicroelectronics SA 2013
- Authors: Benjamin Gaignard benjamin.gaignard@st.com
Fabien Dessenne <fabien.dessenne@st.com>
for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#ifndef _STI_VTG_UTILS_H_ +#define _STI_VTG_UTILS_H_
+#include <drm/drmP.h>
+#define WAIT_NEXT_VSYNC_MS 50 /*ms*/
+#define VTG_MAIN 0 +#define VTG_AUX 1
+#define VTG_TOP_FIELD_EVENT 1 +#define VTG_BOTTOM_FIELD_EVENT 2
+int sti_vtg_setconfig(int main_aux, const struct drm_display_mode *mode); +int sti_vtg_register_client(int main_aux, struct notifier_block *nb); +int sti_vtg_unregister_client(int main_aux, struct notifier_block *nb);
+u32 sti_vtg_get_line_number(struct drm_display_mode mode, int y); +u32 sti_vtg_get_pixel_number(struct drm_display_mode mode, int x);
+#endif
Video Trafic Advance Communication Rx and Tx drivers are designed for inter-die communication.
Both Tx and Rx must share the same configuration to communicate that is why vtac_mode[] is shared in sti_vtac_utils.h.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Kconfig | 6 ++ drivers/gpu/drm/sti/Makefile | 1 + drivers/gpu/drm/sti/sti_vtac_rx.c | 169 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_vtac_tx.c | 182 +++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_vtac_utils.h | 52 ++++++++++ 5 files changed, 410 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_vtac_rx.c create mode 100644 drivers/gpu/drm/sti/sti_vtac_tx.c create mode 100644 drivers/gpu/drm/sti/sti_vtac_utils.h
diff --git a/drivers/gpu/drm/sti/Kconfig b/drivers/gpu/drm/sti/Kconfig index 3fff278..87e6128 100644 --- a/drivers/gpu/drm/sti/Kconfig +++ b/drivers/gpu/drm/sti/Kconfig @@ -4,6 +4,12 @@ config DRM_STI help Choose this option to enable DRM on STM stiH41x chipset
+config VTAC_STI + bool "Video Trafic Advance Communication Rx and Tx for STMicroelectronics SoC stiH41x Series" + depends on DRM_STI + help + Choose this option to enable VTAC on STM stiH41x chipset + config VTG_STI bool "Video Timing Generator for STMicroelectronics SoC stiH41x Series" depends on DRM_STI diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 33216e1..79fdcb6 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -1,3 +1,4 @@ ccflags-y := -Iinclude/drm
+obj-$(CONFIG_VTAC_STI) += sti_vtac_tx.o sti_vtac_rx.o obj-$(CONFIG_VTG_STI) += sti_vtg.o sti_vtg_utils.o diff --git a/drivers/gpu/drm/sti/sti_vtac_rx.c b/drivers/gpu/drm/sti/sti_vtac_rx.c new file mode 100644 index 0000000..77eae19 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtac_rx.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include <drm/drmP.h> + +#include "sti_vtac_utils.h" + +#define VTAC_RX_CONFIG 0x00 +#define VTAC_RX_FIFO_CONFIG 0x04 + +#define VTAC_SW_RST_AUTOC 0x02 +#define VTAC_FIFO_CONFIG_VAL 0x04 + +/* + * VTAC RX structure + * + * @dev: pointer to device structure + * @regs: ioremapped regsiters + * @clk: clock + * @type: main or aux device + */ +struct sti_vtac_rx { + struct device *dev; + void __iomem *regs; + struct clk *clk; + int type; +}; + +static void sti_vtac_rx_reg_dump(struct sti_vtac_rx *vtac_rx) +{ + dev_dbg(vtac_rx->dev, "vtac_rx->regs %p\n", vtac_rx->regs); + dev_dbg(vtac_rx->dev, "VTAC_RX_CONFIG 0x%x\n", + readl(vtac_rx->regs + VTAC_RX_CONFIG)); + dev_dbg(vtac_rx->dev, "VTAC_RX_FIFO_CONFIG 0x%x\n", + readl(vtac_rx->regs + VTAC_RX_FIFO_CONFIG)); +} + +static void sti_vtac_rx_set_config(struct sti_vtac_rx *vtac_rx) +{ + int i; + u32 rx_config = EVEN_PARITY | ODD_PARITY | SW_RST_AUTOC | ENABLE; + + /* Enable VTAC clock */ + if (clk_prepare_enable(vtac_rx->clk)) + DRM_ERROR("Failed to prepare/enable vtac_rx clock.\n"); + + for (i = 0; i < ARRAY_SIZE(vtac_modes); i++) { + if (vtac_modes[i].type == vtac_rx->type) { + writel(VTAC_FIFO_CONFIG_VAL, + vtac_rx->regs + VTAC_RX_FIFO_CONFIG); + rx_config |= vtac_modes[i].vid_in_width << 4; + rx_config |= vtac_modes[i].phyts_width << 16; + rx_config |= vtac_modes[i].phyts_per_pixel << 23; + rx_config |= VTAC_SW_RST_AUTOC; + writel(rx_config, vtac_rx->regs + VTAC_RX_CONFIG); + } + } +} + +static int vtac_rx_bind(struct device *dev, struct device *master, void *data) +{ + return 0; +} + +static void vtac_rx_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops vtac_rx_ops = { + .bind = vtac_rx_bind, + .unbind = vtac_rx_unbind, +}; + +static int sti_vtac_rx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct sti_vtac_rx *vtac_rx; + struct resource *res; + + DRM_INFO("%s\n", __func__); + + vtac_rx = devm_kzalloc(dev, sizeof(*vtac_rx), GFP_KERNEL); + if (!vtac_rx) { + DRM_ERROR("Failed to allocate VTAC RX context\n"); + return -ENOMEM; + } + vtac_rx->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + DRM_ERROR("Invalid resource\n"); + return -ENOMEM; + } + vtac_rx->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(vtac_rx->regs)) + return PTR_ERR(vtac_rx->regs); + + vtac_rx->type = VTAC_MAIN; + if (np) + if (of_property_read_bool(np, "vtac-rx-aux")) + vtac_rx->type = VTAC_AUX; + + if (vtac_rx->type == VTAC_MAIN) { + vtac_rx->clk = devm_clk_get(dev, "vtac_main_phy"); + if (IS_ERR(vtac_rx->clk)) { + DRM_ERROR("Cannot get vtac_main_phy clock\n"); + return PTR_ERR(vtac_rx->clk); + } + } else { + vtac_rx->clk = devm_clk_get(dev, "vtac_aux_phy"); + if (IS_ERR(vtac_rx->clk)) { + DRM_ERROR("Cannot get vtac_aux_phy clock\n"); + return PTR_ERR(vtac_rx->clk); + } + } + + sti_vtac_rx_set_config(vtac_rx); + sti_vtac_rx_reg_dump(vtac_rx); + + platform_set_drvdata(pdev, vtac_rx); + DRM_INFO("%s VTAC RX %s\n", __func__, + vtac_rx->type == VTAC_MAIN ? "main" : "aux"); + + return component_add(&pdev->dev, &vtac_rx_ops); +} + +static int sti_vtac_rx_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vtac_rx_ops); + return 0; +} + +static struct of_device_id vtac_rx_match_types[] = { + { + .compatible = "st,stih416-vtac-rx", + }, { + /* end node */ + } +}; + +MODULE_DEVICE_TABLE(of, vtac_rx_match_types); + +struct platform_driver sti_vtac_rx_driver = { + .driver = { + .name = "sti-vtac-rx", + .owner = THIS_MODULE, + .of_match_table = vtac_rx_match_types, + }, + .probe = sti_vtac_rx_probe, + .remove = sti_vtac_rx_remove, +}; + +module_platform_driver(sti_vtac_rx_driver); + +MODULE_AUTHOR("Benjamin Gaignard benjamin.gaignard@st.com"); +MODULE_DESCRIPTION("STMicroelectronics SoC VTAC_RX driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sti/sti_vtac_tx.c b/drivers/gpu/drm/sti/sti_vtac_tx.c new file mode 100644 index 0000000..3c0a152 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtac_tx.c @@ -0,0 +1,182 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <drm/drmP.h> + +#include "sti_vtac_utils.h" + +#define VTAC_TX_CONFIG 0x00 + +#define VTAC_SW_RST_AUTOC 0x02 + +#define VTAC_TX_PHY_ENABLE_CLK_PHY (0x01 << 0) +#define VTAC_TX_PHY_PROG_N3 (0x04 << 7) +#define VTAC_TX_PHY_ENABLE_CLK_DLL (0x01 << 1) +#define VTAC_TX_PHY_RST_N_DLL_SWITCH (0x01 << 4) +#define VTAC_TX_PHY_PLL_NOT_OSC_MODE (0x01 << 3) + +/* + * VTAC TX structure + * + * @dev: pointer to device structure + * @regs: ioremapped regsiters + * @clk: clock + * @type: main or aux device + */ +struct sti_vtac_tx { + struct device *dev; + void __iomem *tx_regs; + void __iomem *phy_regs; + struct clk *clk; + int type; +}; + +static void sti_vtac_tx_set_config(struct sti_vtac_tx *vtac_tx) +{ + int i; + u32 phy_config; + u32 tx_config = EVEN_PARITY | ODD_PARITY | SW_RST_AUTOC | ENABLE; + + /* Enable VTAC clock */ + if (clk_prepare_enable(vtac_tx->clk)) + DRM_ERROR("Failed to prepare/enable vtac_tx clock.\n"); + + /* Configure vtac phy */ + phy_config = 0x00000000; + writel(phy_config, vtac_tx->phy_regs + 0x828); /* SYS_CFG8522 */ + phy_config = VTAC_TX_PHY_ENABLE_CLK_PHY; + writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */ + phy_config = readl(vtac_tx->phy_regs + 0x824); + phy_config |= VTAC_TX_PHY_PROG_N3; + writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */ + phy_config = readl(vtac_tx->phy_regs + 0x824); + phy_config |= VTAC_TX_PHY_ENABLE_CLK_DLL; + writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */ + phy_config = readl(vtac_tx->phy_regs + 0x824); + phy_config |= VTAC_TX_PHY_RST_N_DLL_SWITCH; + writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */ + phy_config = readl(vtac_tx->phy_regs + 0x824); + phy_config |= VTAC_TX_PHY_PLL_NOT_OSC_MODE; + writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */ + + /* Configure vtac tx */ + for (i = 0; i < ARRAY_SIZE(vtac_modes); i++) { + if (vtac_modes[i].type == vtac_tx->type) { + tx_config |= vtac_modes[i].vid_in_width << 4; + tx_config |= vtac_modes[i].phyts_width << 16; + tx_config |= vtac_modes[i].phyts_per_pixel << 23; + tx_config |= VTAC_SW_RST_AUTOC; + writel(tx_config, vtac_tx->tx_regs + VTAC_TX_CONFIG); + } + } +} + +static int vtac_tx_bind(struct device *dev, struct device *master, void *data) +{ + return 0; +} + +static void vtac_tx_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops vtac_tx_ops = { + .bind = vtac_tx_bind, + .unbind = vtac_tx_unbind, +}; + +static int sti_vtac_tx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct sti_vtac_tx *vtac_tx; + struct resource *res; + + DRM_INFO("%s\n", __func__); + + vtac_tx = devm_kzalloc(dev, sizeof(*vtac_tx), GFP_KERNEL); + if (!vtac_tx) { + DRM_ERROR("Failed to allocate VTAC TX context\n"); + return -ENOMEM; + } + vtac_tx->dev = dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vtac-tx"); + if (!res) { + DRM_ERROR("Invalid resource 'vtac-tx'\n"); + return -ENOMEM; + } + vtac_tx->tx_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(vtac_tx->tx_regs)) + return PTR_ERR(vtac_tx->tx_regs); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vtac-phy"); + if (!res) { + DRM_ERROR("Invalid resource 'vtac-phy'\n"); + return -ENOMEM; + } + vtac_tx->phy_regs = devm_ioremap_nocache(dev, res->start, + resource_size(res)); + if (IS_ERR(vtac_tx->phy_regs)) + return PTR_ERR(vtac_tx->phy_regs); + + vtac_tx->type = VTAC_MAIN; + if (np) + if (of_property_read_bool(np, "vtac-tx-aux")) + vtac_tx->type = VTAC_AUX; + + vtac_tx->clk = devm_clk_get(dev, "vtac_tx_phy"); + if (IS_ERR(vtac_tx->clk)) { + DRM_ERROR("Cannot get vtac_tx_phy clock\n"); + return PTR_ERR(vtac_tx->clk); + } + + sti_vtac_tx_set_config(vtac_tx); + + platform_set_drvdata(pdev, vtac_tx); + DRM_INFO("%s VTAC TX %s\n", __func__, + vtac_tx->type == VTAC_MAIN ? "main" : "aux"); + + return component_add(&pdev->dev, &vtac_tx_ops); +} + +static int sti_vtac_tx_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vtac_tx_ops); + return 0; +} + +static struct of_device_id vtac_tx_match_types[] = { + { + .compatible = "st,stih416-vtac-tx", + }, { + /* end node */ + } +}; +MODULE_DEVICE_TABLE(of, vtac_tx_match_types); + +struct platform_driver sti_vtac_tx_driver = { + .driver = { + .name = "sti-vtac-tx", + .owner = THIS_MODULE, + .of_match_table = vtac_tx_match_types, + }, + .probe = sti_vtac_tx_probe, + .remove = sti_vtac_tx_remove, +}; + +module_platform_driver(sti_vtac_tx_driver); + +MODULE_AUTHOR("Benjamin Gaignard benjamin.gaignard@st.com"); +MODULE_DESCRIPTION("STMicroelectronics SoC VTAC_TX driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sti/sti_vtac_utils.h b/drivers/gpu/drm/sti/sti_vtac_utils.h new file mode 100644 index 0000000..ce549de --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtac_utils.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_VTAC_UTILS_H_ +#define _STI_VTAC_UTILS_H_ + +#define VTAC_MAIN 0x8000 +#define VTAC_AUX 0x4000 + +/* Number of phyts per pixel */ +#define VTAC_2_5_PPP 0x0005 +#define VTAC_3_PPP 0x0006 +#define VTAC_4_PPP 0x0008 +#define VTAC_5_PPP 0x000A +#define VTAC_6_PPP 0x000C +#define VTAC_13_PPP 0x001A +#define VTAC_14_PPP 0x001C +#define VTAC_15_PPP 0x001E +#define VTAC_16_PPP 0x0020 +#define VTAC_17_PPP 0x0022 +#define VTAC_18_PPP 0x0024 + +/* enable bits */ +#define EVEN_PARITY (1 << 13) +#define ODD_PARITY (1 << 12) +#define SW_RST_AUTOC (1 << 1) +#define ENABLE (1 << 0) + +/* + * VTAC mode structure + * + * @type: main, aux or dvo device + * @vid_in_width: Video Data Resolution + * @phyts_width: Width of phyt buses(phyt low and phyt high). + * @phyts_per_pixel: Number of phyts sent per pixel + */ +struct sti_vtac_mode { + int type; + int vid_in_width; + int phyts_width; + int phyts_per_pixel; +}; + +static struct sti_vtac_mode vtac_modes[] = { + {VTAC_MAIN, 0x2, 0x2, VTAC_5_PPP}, /* Main RGB 12 */ + {VTAC_AUX, 0x1, 0x0, VTAC_17_PPP}, /* Aux 10 */ +}; + +#endif
First off: a lot of the general style comments from my review of patch 01/16 also apply here.
On Tue, May 20, 2014 at 03:56:12PM +0200, Benjamin Gaignard wrote:
Video Trafic Advance Communication Rx and Tx drivers are designed
s/Trafic/Traffic/?
diff --git a/drivers/gpu/drm/sti/Kconfig b/drivers/gpu/drm/sti/Kconfig
[...]
@@ -4,6 +4,12 @@ config DRM_STI help Choose this option to enable DRM on STM stiH41x chipset +config VTAC_STI
- bool "Video Trafic Advance Communication Rx and Tx for STMicroelectronics SoC stiH41x Series"
Here too.
- depends on DRM_STI
- help
Choose this option to enable VTAC on STM stiH41x chipset
I wonder how useful it is to make all of these user-selectable (or even Kconfig symbols to begin with). It seems like VTG and VTAC (and the same is likely true for other subdrivers) are essential to make this driver do anything useful, so in my opinion these should all be built unconditionally.
diff --git a/drivers/gpu/drm/sti/sti_vtac_rx.c b/drivers/gpu/drm/sti/sti_vtac_rx.c
[...]
@@ -0,0 +1,169 @@ +/*
- Copyright (C) STMicroelectronics SA 2013
Nit: this probably needs an update to include 2014.
+#include <linux/clk.h> +#include <linux/component.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h>
Should be sorted alphabetically.
+#include <drm/drmP.h>
+#include "sti_vtac_utils.h"
+#define VTAC_RX_CONFIG 0x00 +#define VTAC_RX_FIFO_CONFIG 0x04
+#define VTAC_SW_RST_AUTOC 0x02 +#define VTAC_FIFO_CONFIG_VAL 0x04
+/*
- VTAC RX structure
- @dev: pointer to device structure
- @regs: ioremapped regsiters
s/regsiters/registers/
- @clk: clock
- @type: main or aux device
- */
Also it's typically not necessary to document these driver-internal data structures. The meaning of the fields is usually obvious. Also these aren't usually exported to any documentation (because driver-internal) and hence kernel-doc isn't such a good fit.
+struct sti_vtac_rx {
- struct device *dev;
- void __iomem *regs;
- struct clk *clk;
- int type;
+};
+static void sti_vtac_rx_reg_dump(struct sti_vtac_rx *vtac_rx) +{
- dev_dbg(vtac_rx->dev, "vtac_rx->regs %p\n", vtac_rx->regs);
- dev_dbg(vtac_rx->dev, "VTAC_RX_CONFIG 0x%x\n",
readl(vtac_rx->regs + VTAC_RX_CONFIG));
- dev_dbg(vtac_rx->dev, "VTAC_RX_FIFO_CONFIG 0x%x\n",
readl(vtac_rx->regs + VTAC_RX_FIFO_CONFIG));
+}
Similar as in the previous patch, this would be desirable to have in debugfs.
+static void sti_vtac_rx_set_config(struct sti_vtac_rx *vtac_rx) +{
- int i;
- u32 rx_config = EVEN_PARITY | ODD_PARITY | SW_RST_AUTOC | ENABLE;
For readability I think it would make sense to move this closer to where the register is written.
- /* Enable VTAC clock */
- if (clk_prepare_enable(vtac_rx->clk))
DRM_ERROR("Failed to prepare/enable vtac_rx clock.\n");
- for (i = 0; i < ARRAY_SIZE(vtac_modes); i++) {
if (vtac_modes[i].type == vtac_rx->type) {
Rather than try to lookup the mode by type in a global table, could you not simply pass in the specific mode that should be configured as a parameter. That way you can probably leave out the type field from the sti_vtac_rx structure and use this in a similar way than what I suggested from the
writel(VTAC_FIFO_CONFIG_VAL,
vtac_rx->regs + VTAC_RX_FIFO_CONFIG);
rx_config |= vtac_modes[i].vid_in_width << 4;
rx_config |= vtac_modes[i].phyts_width << 16;
rx_config |= vtac_modes[i].phyts_per_pixel << 23;
rx_config |= VTAC_SW_RST_AUTOC;
writel(rx_config, vtac_rx->regs + VTAC_RX_CONFIG);
}
- }
+}
+static int vtac_rx_bind(struct device *dev, struct device *master, void *data) +{
- return 0;
+}
+static void vtac_rx_unbind(struct device *dev, struct device *master,
- void *data)
+{
- /* do nothing */
+}
+static const struct component_ops vtac_rx_ops = {
- .bind = vtac_rx_bind,
- .unbind = vtac_rx_unbind,
+};
+static int sti_vtac_rx_probe(struct platform_device *pdev) +{
- struct device *dev = &pdev->dev;
- struct device_node *np = dev->of_node;
- struct sti_vtac_rx *vtac_rx;
- struct resource *res;
- DRM_INFO("%s\n", __func__);
- vtac_rx = devm_kzalloc(dev, sizeof(*vtac_rx), GFP_KERNEL);
- if (!vtac_rx) {
DRM_ERROR("Failed to allocate VTAC RX context\n");
return -ENOMEM;
- }
- vtac_rx->dev = dev;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
DRM_ERROR("Invalid resource\n");
No need for this. devm_ioremap_resource() already checks for that and outputs an error message.
return -ENOMEM;
- }
- vtac_rx->regs = devm_ioremap_resource(dev, res);
- if (IS_ERR(vtac_rx->regs))
return PTR_ERR(vtac_rx->regs);
- vtac_rx->type = VTAC_MAIN;
- if (np)
if (of_property_read_bool(np, "vtac-rx-aux"))
vtac_rx->type = VTAC_AUX;
What exactly is the difference between auxiliary and main here? It seems like the only reason why you separate things here is because you use different names for the clocks and devices.
- if (vtac_rx->type == VTAC_MAIN) {
vtac_rx->clk = devm_clk_get(dev, "vtac_main_phy");
if (IS_ERR(vtac_rx->clk)) {
DRM_ERROR("Cannot get vtac_main_phy clock\n");
return PTR_ERR(vtac_rx->clk);
}
- } else {
vtac_rx->clk = devm_clk_get(dev, "vtac_aux_phy");
if (IS_ERR(vtac_rx->clk)) {
DRM_ERROR("Cannot get vtac_aux_phy clock\n");
return PTR_ERR(vtac_rx->clk);
}
- }
There should be no need to make the consumer name of the clock different for main or auxiliary types. From the above it seems like the clock is used to drive a PHY, in which case something like "phy" would be good enough. That way you don't have to conditionalize.
- sti_vtac_rx_set_config(vtac_rx);
- sti_vtac_rx_reg_dump(vtac_rx);
- platform_set_drvdata(pdev, vtac_rx);
- DRM_INFO("%s VTAC RX %s\n", __func__,
vtac_rx->type == VTAC_MAIN ? "main" : "aux");
- return component_add(&pdev->dev, &vtac_rx_ops);
+}
+static int sti_vtac_rx_remove(struct platform_device *pdev) +{
- component_del(&pdev->dev, &vtac_rx_ops);
- return 0;
+}
+static struct of_device_id vtac_rx_match_types[] = {
Should be static const.
- {
.compatible = "st,stih416-vtac-rx",
}, {
/* end node */
}
Funky indentation.
+};
+MODULE_DEVICE_TABLE(of, vtac_rx_match_types);
I think it's more common to not have a blank line between the }; and the MODULE_DEVICE_TABLE. I missed that for 01/12.
+struct platform_driver sti_vtac_rx_driver = {
- .driver = {
.name = "sti-vtac-rx",
.owner = THIS_MODULE,
.of_match_table = vtac_rx_match_types,
},
- .probe = sti_vtac_rx_probe,
- .remove = sti_vtac_rx_remove,
+};
+module_platform_driver(sti_vtac_rx_driver);
Same here, and that also applies to 01/12.
+MODULE_AUTHOR("Benjamin Gaignard benjamin.gaignard@st.com"); +MODULE_DESCRIPTION("STMicroelectronics SoC VTAC_RX driver"); +MODULE_LICENSE("GPL");
This doesn't match the license in the file header. This is also wrong in patch 01/12.
diff --git a/drivers/gpu/drm/sti/sti_vtac_tx.c b/drivers/gpu/drm/sti/sti_vtac_tx.c new file mode 100644 index 0000000..3c0a152 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtac_tx.c @@ -0,0 +1,182 @@ +/*
- Copyright (C) STMicroelectronics SA 2013
- Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#include <linux/clk.h> +#include <linux/component.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <drm/drmP.h>
+#include "sti_vtac_utils.h"
+#define VTAC_TX_CONFIG 0x00
+#define VTAC_SW_RST_AUTOC 0x02
+#define VTAC_TX_PHY_ENABLE_CLK_PHY (0x01 << 0) +#define VTAC_TX_PHY_PROG_N3 (0x04 << 7) +#define VTAC_TX_PHY_ENABLE_CLK_DLL (0x01 << 1) +#define VTAC_TX_PHY_RST_N_DLL_SWITCH (0x01 << 4) +#define VTAC_TX_PHY_PLL_NOT_OSC_MODE (0x01 << 3)
+/*
- VTAC TX structure
- @dev: pointer to device structure
- @regs: ioremapped regsiters
- @clk: clock
- @type: main or aux device
- */
+struct sti_vtac_tx {
- struct device *dev;
- void __iomem *tx_regs;
- void __iomem *phy_regs;
If these are separate register regions, perhaps the PHY part should be exposed using the generic PHY framework (see Documentation/phy.txt).
+static void sti_vtac_tx_set_config(struct sti_vtac_tx *vtac_tx) +{
- int i;
- u32 phy_config;
- u32 tx_config = EVEN_PARITY | ODD_PARITY | SW_RST_AUTOC | ENABLE;
Both of these are awfully long names. Perhaps you can use a single variable called value here.
- /* Enable VTAC clock */
- if (clk_prepare_enable(vtac_tx->clk))
DRM_ERROR("Failed to prepare/enable vtac_tx clock.\n");
This would typically be a fatal error, wouldn't it? So this should return (and probably return the error code as well).
- /* Configure vtac phy */
- phy_config = 0x00000000;
- writel(phy_config, vtac_tx->phy_regs + 0x828); /* SYS_CFG8522 */
- phy_config = VTAC_TX_PHY_ENABLE_CLK_PHY;
- writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */
- phy_config = readl(vtac_tx->phy_regs + 0x824);
- phy_config |= VTAC_TX_PHY_PROG_N3;
- writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */
- phy_config = readl(vtac_tx->phy_regs + 0x824);
- phy_config |= VTAC_TX_PHY_ENABLE_CLK_DLL;
- writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */
- phy_config = readl(vtac_tx->phy_regs + 0x824);
- phy_config |= VTAC_TX_PHY_RST_N_DLL_SWITCH;
- writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */
- phy_config = readl(vtac_tx->phy_regs + 0x824);
- phy_config |= VTAC_TX_PHY_PLL_NOT_OSC_MODE;
- writel(phy_config, vtac_tx->phy_regs + 0x824); /* SYS_CFG8521 */
Can we have a symbolic name for registers 0x824 and 0x828 here, please?
- /* Configure vtac tx */
- for (i = 0; i < ARRAY_SIZE(vtac_modes); i++) {
if (vtac_modes[i].type == vtac_tx->type) {
tx_config |= vtac_modes[i].vid_in_width << 4;
tx_config |= vtac_modes[i].phyts_width << 16;
tx_config |= vtac_modes[i].phyts_per_pixel << 23;
tx_config |= VTAC_SW_RST_AUTOC;
You already set SW_RST_AUTOC in tx_config and it seems to be the same value as VTAC_SW_RST_AUTOC. Also please provide only one name for a given field, either VTAC_SW_RST_AUTOC *or* SW_RST_AUTOC, not both.
writel(tx_config, vtac_tx->tx_regs + VTAC_TX_CONFIG);
Similarly to VTAC Rx I'd set the variable right before writing it to the register here to make it immediately obvious what value gets written.
Also this is the exact same programming sequence as for VTAC Rx, and the config register seems to be at the same offset as well, so I'm wondering if perhaps Rx and Tx VTACs are actually the same hardware. If so you can probably get away with deduplicating both drivers and parameterizing. If you move out the PHY register programming into a PHY driver, then most of that work is already done.
+static int sti_vtac_tx_probe(struct platform_device *pdev) +{
- struct device *dev = &pdev->dev;
- struct device_node *np = dev->of_node;
- struct sti_vtac_tx *vtac_tx;
- struct resource *res;
- DRM_INFO("%s\n", __func__);
- vtac_tx = devm_kzalloc(dev, sizeof(*vtac_tx), GFP_KERNEL);
- if (!vtac_tx) {
DRM_ERROR("Failed to allocate VTAC TX context\n");
return -ENOMEM;
- }
- vtac_tx->dev = dev;
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vtac-tx");
- if (!res) {
DRM_ERROR("Invalid resource 'vtac-tx'\n");
return -ENOMEM;
- }
- vtac_tx->tx_regs = devm_ioremap_resource(dev, res);
- if (IS_ERR(vtac_tx->tx_regs))
return PTR_ERR(vtac_tx->tx_regs);
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vtac-phy");
- if (!res) {
DRM_ERROR("Invalid resource 'vtac-phy'\n");
return -ENOMEM;
- }
- vtac_tx->phy_regs = devm_ioremap_nocache(dev, res->start,
resource_size(res));
- if (IS_ERR(vtac_tx->phy_regs))
return PTR_ERR(vtac_tx->phy_regs);
Why is this not using devm_ioremap_resource() like the tx_regs resource? Also if you move this to a separate PHY driver your resource management becomes a whole lot simpler (no need to worry about the tx_ or phy_ prefixes, ...).
+static struct of_device_id vtac_tx_match_types[] = {
vtac_tx_of_match? Or if you can unify both drivers: vtac_of_match. If some parameterization is required to make Tx and Rx work in one driver you could add different compatible strings here for each. More likely though I think that information could be derived from context.
- {
.compatible = "st,stih416-vtac-tx",
}, {
/* end node */
}
+};
Strange indentation here.
+MODULE_DEVICE_TABLE(of, vtac_tx_match_types);
+struct platform_driver sti_vtac_tx_driver = {
- .driver = {
.name = "sti-vtac-tx",
.owner = THIS_MODULE,
.of_match_table = vtac_tx_match_types,
},
And here.
diff --git a/drivers/gpu/drm/sti/sti_vtac_utils.h b/drivers/gpu/drm/sti/sti_vtac_utils.h
[...]
+/* Number of phyts per pixel */
What's a "phyt"?
+#define VTAC_2_5_PPP 0x0005 +#define VTAC_3_PPP 0x0006 +#define VTAC_4_PPP 0x0008 +#define VTAC_5_PPP 0x000A +#define VTAC_6_PPP 0x000C +#define VTAC_13_PPP 0x001A +#define VTAC_14_PPP 0x001C +#define VTAC_15_PPP 0x001E +#define VTAC_16_PPP 0x0020 +#define VTAC_17_PPP 0x0022 +#define VTAC_18_PPP 0x0024
+/* enable bits */ +#define EVEN_PARITY (1 << 13) +#define ODD_PARITY (1 << 12) +#define SW_RST_AUTOC (1 << 1) +#define ENABLE (1 << 0)
These could probably use a VTAC_ prefix for consistency.
+/*
- VTAC mode structure
- @type: main, aux or dvo device
- @vid_in_width: Video Data Resolution
These are probably obvious if you know the hardware or can look at the datasheet, but I have no idea what this field for example means. I had initially thought that it would contain some number of pixels, but the table of modes below taught me better. Having some explanation of the fields in this structure could be useful.
- @phyts_width: Width of phyt buses(phyt low and phyt high).
- @phyts_per_pixel: Number of phyts sent per pixel
- */
+struct sti_vtac_mode {
- int type;
- int vid_in_width;
- int phyts_width;
- int phyts_per_pixel;
+};
+static struct sti_vtac_mode vtac_modes[] = {
- {VTAC_MAIN, 0x2, 0x2, VTAC_5_PPP}, /* Main RGB 12 */
- {VTAC_AUX, 0x1, 0x0, VTAC_17_PPP}, /* Aux 10 */
+};
This doesn't really belong in a header. If you manage to unify both VTAC Tx and Rx you can simply move this into the VTAC driver source file and not expose it beyond that at all.
Thierry
Add driver for HDMI ouput
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Makefile | 5 + drivers/gpu/drm/sti/sti_hdmi.c | 529 +++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi.h | 195 +++++++++++ drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c | 398 ++++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c | 224 ++++++++++++ 5 files changed, 1351 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_hdmi.c create mode 100644 drivers/gpu/drm/sti/sti_hdmi.h create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 79fdcb6..5295fc7 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -1,4 +1,9 @@ ccflags-y := -Iinclude/drm
+stidrm-y := sti_hdmi.o \ + sti_hdmi_tx3g0c55phy.o \ + sti_hdmi_tx3g4c28phy.o + obj-$(CONFIG_VTAC_STI) += sti_vtac_tx.o sti_vtac_rx.o obj-$(CONFIG_VTG_STI) += sti_vtg.o sti_vtg_utils.o +obj-$(CONFIG_DRM_STI) += stidrm.o \ No newline at end of file diff --git a/drivers/gpu/drm/sti/sti_hdmi.c b/drivers/gpu/drm/sti/sti_hdmi.c new file mode 100644 index 0000000..02b0524 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hdmi.c @@ -0,0 +1,529 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Vincent Abriou vincent.abriou@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/hdmi.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> + +#include "sti_hdmi.h" +#include "sti_vtg_utils.h" + +/* Reference to the hdmi device */ +struct device *hdmi_dev; + +/* + * Helper to write bit field + * + * @addr: register to update + * @val: value to write + * @mask: bit field mask to use + */ +static inline void hdmi_reg_writemask(void __iomem *addr, u32 val, u32 mask) +{ + u32 old = readl(addr); + + val = (val & mask) | (old & ~mask); + writel(val, addr); +} + +/* + * HDMI interrupt handler + * + * @irq: irq number + * @arg: connector structure + */ +static irqreturn_t hdmi_irq_thread(int irq, void *arg) +{ + struct sti_hdmi *hdmi = arg; + u32 status; + + /* read interrupt status */ + status = readl(hdmi->regs + HDMI_INT_STA); + + /* PLL lock interrupt */ + if (status & HDMI_INT_DLL_LCK) { + hdmi->event_received = true; + wake_up_interruptible(&hdmi->wait_event); + } + + /* Hot plug detection */ + if (status & HDMI_INT_HOT_PLUG) { + hdmi->hpd = gpio_get_value(hdmi->hpd_gpio); + if (hdmi->drm_dev) + drm_helper_hpd_irq_event(hdmi->drm_dev); + } + + /* Sw reset completed */ + if (status & HDMI_INT_SW_RST) { + hdmi->event_received = true; + wake_up_interruptible(&hdmi->wait_event); + } + + /* clear interrupt status */ + writel(status, hdmi->regs + HDMI_INT_CLR); + + /* TODO: check why this sync bus write solves the problem which + * is that without this line, the handler is sometimes called twice + */ + /* sync bus write */ + readl(hdmi->regs + HDMI_INT_STA); + + return IRQ_HANDLED; +} + +/* + * Start hdmi phy interface + * + * @hdmi: pointer on the hdmi internal structure + * + * Return -1 if error occurs + */ +static int hdmi_phy_start(struct sti_hdmi *hdmi) +{ + DRM_DEBUG_DRIVER("\n"); + + if (hdmi->tx3g0c55phy) + return sti_hdmi_tx3g0c55phy_start(hdmi); + + return sti_hdmi_tx3g4c28phy_start(hdmi); +} + +/* + * Stop hdmi phy interface + * + * @hdmi: pointer on the hdmi internal structure + */ +static void hdmi_phy_stop(struct sti_hdmi *hdmi) +{ + DRM_DEBUG_DRIVER("\n"); + + if (hdmi->tx3g0c55phy) + sti_hdmi_tx3g0c55phy_stop(hdmi); + else + sti_hdmi_tx3g4c28phy_stop(hdmi); +} + +/* + * Set hdmi active area depending on the drm display mode selected + * + * @hdmi: pointer on the hdmi internal structure + */ +static void hdmi_active_area(struct sti_hdmi *hdmi) +{ + u32 xmin, xmax; + u32 ymin, ymax; + + DRM_DEBUG_DRIVER("\n"); + + /* + * Active Front Sync Back Active + * Region Porch Porch Region + * <---------------><-------->0<---------><--------><-----------------> + * + * ///////////////| | ///////////////| + * /////////////// | | /////////////// | + * /////////////// |......... ..........|/////////////// | + * 0___________ x/ymin x/ymax + * + * <--[hv]display--> <--[hv]display--> + * <--[hv]sync_start---------> <--[hv]sync_start- + * <--[hv]sync_end-----------------------> <--[hv]sync_end--- + * <--[hv]total------------------------------------> <--[hv]total------ + */ + + xmin = sti_vtg_get_pixel_number(hdmi->mode, 0); + xmax = sti_vtg_get_pixel_number(hdmi->mode, hdmi->mode.hdisplay - 1); + ymin = sti_vtg_get_line_number(hdmi->mode, 0); + ymax = sti_vtg_get_line_number(hdmi->mode, hdmi->mode.vdisplay - 1); + + writel(xmin, hdmi->regs + HDMI_ACTIVE_VID_XMIN); + writel(xmax, hdmi->regs + HDMI_ACTIVE_VID_XMAX); + writel(ymin, hdmi->regs + HDMI_ACTIVE_VID_YMIN); + writel(ymax, hdmi->regs + HDMI_ACTIVE_VID_YMAX); + + DRM_DEBUG_DRIVER("xmin=%d xmax=%d ymin=%d ymax=%d\n", + xmin, xmax, ymin, ymax); +} + +/* + * Overall hdmi configuration + * + * @hdmi: pointer on the hdmi internal structure + */ +static void hdmi_config(struct sti_hdmi *hdmi) +{ + u32 val; + u32 mask; + + DRM_DEBUG_DRIVER("\n"); + + /* Clear overrun and underrun fifo */ + mask = HDMI_CFG_FIFO_OVERRUN_CLR | HDMI_CFG_FIFO_UNDERRUN_CLR; + val = HDMI_CFG_FIFO_OVERRUN_CLR | HDMI_CFG_FIFO_UNDERRUN_CLR; + + /* Enable HDMI mode not DVI */ + mask |= HDMI_CFG_HDMI_NOT_DVI | HDMI_CFG_ESS_NOT_OESS; + val |= HDMI_CFG_HDMI_NOT_DVI | HDMI_CFG_ESS_NOT_OESS; + + /* Enable sink term detection */ + mask |= HDMI_CFG_SINK_TERM_DET_EN; + val |= HDMI_CFG_SINK_TERM_DET_EN; + + /* Set Hsync polarity */ + if ((hdmi->mode.flags && DRM_MODE_FLAG_NHSYNC) + == DRM_MODE_FLAG_NHSYNC) { + DRM_DEBUG_DRIVER("H Sync Negative\n"); + mask |= HDMI_CFG_H_SYNC_POL_NEG; + val |= HDMI_CFG_H_SYNC_POL_NEG; + } + + /* Set Vsync polarity */ + if ((hdmi->mode.flags && DRM_MODE_FLAG_NVSYNC) + == DRM_MODE_FLAG_NVSYNC) { + DRM_DEBUG_DRIVER("V Sync Negative\n"); + mask |= HDMI_CFG_V_SYNC_POL_NEG; + val |= HDMI_CFG_V_SYNC_POL_NEG; + } + + /* Enable HDMI */ + mask |= HDMI_CFG_DEVICE_EN; + val |= HDMI_CFG_DEVICE_EN; + + hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask); +} + +/* + * Prepare and configure the AVI infoframe + * + * AVI infoframe are transmitted at least once per two video field and + * contains information about HDMI transmission mode such as color space, + * colorimetry, ... + * + * @hdmi: pointer on the hdmi internal structure + * + * Return negative value if error occurs + */ +static int hdmi_avi_infoframe_config(struct sti_hdmi *hdmi) +{ + struct drm_display_mode *mode = &hdmi->mode; + struct hdmi_avi_infoframe infoframe; + u8 buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE]; + u8 *frame = buffer + HDMI_INFOFRAME_HEADER_SIZE - 1; + u32 val; + u32 mask; + int ret; + + DRM_DEBUG_DRIVER("\n"); + + ret = drm_hdmi_avi_infoframe_from_display_mode(&infoframe, mode); + if (ret < 0) { + DRM_ERROR("failed to setup AVI infoframe: %d\n", ret); + return ret; + } + + /* TODO: remove static infoframe configuration */ + infoframe.colorspace = HDMI_COLORSPACE_RGB; + infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT; + infoframe.colorimetry = HDMI_COLORIMETRY_NONE; + infoframe.pixel_repeat = 0; + + ret = hdmi_avi_infoframe_pack(&infoframe, buffer, sizeof(buffer)); + if (ret < 0) { + DRM_ERROR("failed to pack AVI infoframe: %d\n", ret); + return ret; + } + + /* Disable transmission slot for AVI infoframe */ + val = HDMI_IFRAME_DISABLED; + mask = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, HDMI_IFRAME_SLOT_AVI); + hdmi_reg_writemask(hdmi->regs + HDMI_SW_DI_CFG, val, mask); + + /* Infoframe header */ + val = buffer[0x0]; + val |= buffer[0x1] << 8; + val |= buffer[0x2] << 16; + writel(val, hdmi->regs + HDMI_SW_DI_N_HEAD_WORD(HDMI_IFRAME_SLOT_AVI)); + /* Infoframe packet bytes */ + val = frame[0x0]; + val |= frame[0x1] << 8; + val |= frame[0x2] << 16; + val |= frame[0x3] << 24; + writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD0(HDMI_IFRAME_SLOT_AVI)); + val = frame[0x4]; + val |= frame[0x5] << 8; + val |= frame[0x6] << 16; + val |= frame[0x7] << 24; + writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD1(HDMI_IFRAME_SLOT_AVI)); + val = frame[0x8]; + val |= frame[0x9] << 8; + val |= frame[0xA] << 16; + val |= frame[0xB] << 24; + writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD2(HDMI_IFRAME_SLOT_AVI)); + val = frame[0xC]; + val |= frame[0xD] << 8; + writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD3(HDMI_IFRAME_SLOT_AVI)); + + /* Enable transmission slot for AVI infoframe */ + /* According to the hdmi specification, AVI infoframe should be + * transmitted at least once per two video fields */ + val = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_FIELD, HDMI_IFRAME_SLOT_AVI); + mask = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, HDMI_IFRAME_SLOT_AVI); + hdmi_reg_writemask(hdmi->regs + HDMI_SW_DI_CFG, val, mask); + + return 0; +} + +/* + * Software reset of the hdmi subsystem + * + * @hdmi: pointer on the hdmi internal structure + * + * Return -1 if error occurs + */ +#define HDMI_TIMEOUT_SWRESET 100 /*milliseconds */ +static int hdmi_swreset(struct sti_hdmi *hdmi) +{ + u32 val; + u32 mask; + + DRM_DEBUG_DRIVER("\n"); + + /* Enable hdmi_audio clock only during hdmi reset */ + if (clk_prepare_enable(hdmi->clk_audio)) + DRM_INFO("Failed to prepare/enable hdmi_audio clk\n"); + + /* Sw reset */ + mask = HDMI_CFG_SW_RST_EN; + val = HDMI_CFG_SW_RST_EN; + + hdmi->event_received = false; + hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask); + + /* Wait reset completed */ + wait_event_interruptible_timeout(hdmi->wait_event, + hdmi->event_received == true, + msecs_to_jiffies + (HDMI_TIMEOUT_SWRESET)); + + /* + * HDMI_STA_SW_RST bit is set to '1' when SW_RST bit in HDMI_CFG is + * set to '1' and clk_audio is running. + */ + if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_SW_RST) == 0) + DRM_INFO("Warning: HDMI sw reset timeout occurs\n"); + + mask = HDMI_CFG_SW_RST_EN; + val = ~HDMI_CFG_SW_RST_EN; + hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask); + + /* Disable hdmi_audio clock. Not used anymore for drm purpose. */ + clk_disable_unprepare(hdmi->clk_audio); + + return 0; +} + +/* + * Attach the I2C ddc client to allow hdmi i2c communication + * + * @ddc: i2c client + */ +static struct i2c_client *hdmi_ddc; +void sti_hdmi_attach_ddc_client(struct i2c_client *ddc) +{ + DRM_DEBUG_DRIVER("\n"); + + if (ddc) + hdmi_ddc = ddc; +} + +/* + * Get modes from edid + * + * @drm_connector: pointer on the drm connector + */ +static int sti_hdmi_get_modes(struct drm_connector *drm_connector) +{ + struct edid *edid; + int count; + + DRM_DEBUG_DRIVER("\n"); + + if ((!hdmi_ddc) || (!hdmi_ddc->adapter)) + goto fail; + + edid = drm_get_edid(drm_connector, hdmi_ddc->adapter); + if (!edid) + goto fail; + + count = drm_add_edid_modes(drm_connector, edid); + if (count) + drm_mode_connector_update_edid_property(drm_connector, edid); + else + DRM_ERROR("Add edid modes failed\n"); + + kfree(edid); + return count; + +fail: + DRM_ERROR("Can not read HDMI EDID\n"); + return -1; +} + +static int sti_hdmi_bind(struct device *dev, struct device *master, void *data) +{ + return 0; +} + +static void sti_hdmi_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops sti_hdmi_ops = { + .bind = sti_hdmi_bind, + .unbind = sti_hdmi_unbind, +}; + +static int sti_hdmi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sti_hdmi *hdmi; + struct device_node *np = dev->of_node; + struct resource *res; + int ret; + + DRM_INFO("%s\n", __func__); + + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) { + DRM_ERROR("Failed to allocate memory for hdmi\n"); + return -ENOMEM; + } + + hdmi->dev = pdev->dev; + + /* Get resources */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hdmi-reg"); + if (!res) { + DRM_ERROR("Invalid hdmi resource\n"); + return -ENOMEM; + } + hdmi->regs = devm_ioremap_nocache(dev, res->start, resource_size(res)); + if (IS_ERR(hdmi->regs)) + return PTR_ERR(hdmi->regs); + + if (of_device_is_compatible(np, "st,stih416-hdmi")) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "syscfg"); + if (!res) { + DRM_ERROR("Invalid syscfg resource\n"); + return -ENOMEM; + } + hdmi->syscfg = devm_ioremap_nocache(dev, res->start, + resource_size(res)); + if (IS_ERR(hdmi->syscfg)) + return PTR_ERR(hdmi->syscfg); + + hdmi->tx3g0c55phy = true; + } + + /* Get clock resources */ + hdmi->clk_pix = devm_clk_get(dev, "hdmi_pix"); + if (IS_ERR(hdmi->clk_pix)) { + DRM_ERROR("Cannot get hdmi_pix clock\n"); + return PTR_ERR(hdmi->clk_pix); + } + + hdmi->clk_tmds = devm_clk_get(dev, "hdmi_tmds"); + if (IS_ERR(hdmi->clk_tmds)) { + DRM_ERROR("Cannot get hdmi_tmds clock\n"); + return PTR_ERR(hdmi->clk_tmds); + } + + hdmi->clk_phy = devm_clk_get(dev, "hdmi_phy"); + if (IS_ERR(hdmi->clk_phy)) { + DRM_ERROR("Cannot get hdmi_phy clock\n"); + return PTR_ERR(hdmi->clk_phy); + } + + hdmi->clk_audio = devm_clk_get(dev, "hdmi_audio"); + if (IS_ERR(hdmi->clk_audio)) { + DRM_ERROR("Cannot get hdmi_audio clock\n"); + return PTR_ERR(hdmi->clk_audio); + } + + hdmi->hpd_gpio = of_get_named_gpio(np, "hdmi,hpd-gpio", 0); + if (hdmi->hpd_gpio < 0) { + DRM_ERROR("Failed to get hdmi hpd-gpio\n"); + return -EIO; + } + + hdmi->hpd = gpio_get_value(hdmi->hpd_gpio); + + init_waitqueue_head(&hdmi->wait_event); + + /* Get irq ressources */ + hdmi->irq = platform_get_irq_byname(pdev, "hdmi_irq"); + + ret = devm_request_threaded_irq(dev, hdmi->irq, NULL, + hdmi_irq_thread, IRQF_ONESHOT, + "hdmi_irq", hdmi); + if (ret) { + DRM_ERROR("Failed to register hdmi interrupt\n"); + return ret; + } + + /* Get reset resources */ + hdmi->reset = devm_reset_control_get(dev, "hdmi"); + /* Take hdmi out of reset */ + if (!IS_ERR(hdmi->reset)) + reset_control_deassert(hdmi->reset); + + hdmi_dev = &hdmi->dev; + + platform_set_drvdata(pdev, hdmi); + + return component_add(&pdev->dev, &sti_hdmi_ops); +} + +static int sti_hdmi_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sti_hdmi_ops); + return 0; +} + +static struct of_device_id hdmi_match_types[] = { + { + .compatible = "st,stih416-hdmi", + }, + { + .compatible = "st,stih407-hdmi", + }, + { /* end node */ } +}; +MODULE_DEVICE_TABLE(of, hdmi_match_types); + +struct platform_driver sti_hdmi_driver = { + .driver = { + .name = "sti-hdmi", + .owner = THIS_MODULE, + .of_match_table = hdmi_match_types, + }, + .probe = sti_hdmi_probe, + .remove = sti_hdmi_remove, +}; +module_platform_driver(sti_hdmi_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sti/sti_hdmi.h b/drivers/gpu/drm/sti/sti_hdmi.h new file mode 100644 index 0000000..c14c683 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hdmi.h @@ -0,0 +1,195 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Vincent Abriou vincent.abriou@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_HDMI_H_ +#define _STI_HDMI_H_ + +#include <linux/platform_device.h> + +#include <drm/drmP.h> + +/* HDMI v2.9 macro cell */ +#define HDMI_CFG 0x0000 +#define HDMI_INT_EN 0x0004 +#define HDMI_INT_STA 0x0008 +#define HDMI_INT_CLR 0x000C +#define HDMI_STA 0x0010 +#define HDMI_ACTIVE_VID_XMIN 0x0100 +#define HDMI_ACTIVE_VID_XMAX 0x0104 +#define HDMI_ACTIVE_VID_YMIN 0x0108 +#define HDMI_ACTIVE_VID_YMAX 0x010C +#define HDMI_DFLT_CHL0_DAT 0x0110 +#define HDMI_DFLT_CHL1_DAT 0x0114 +#define HDMI_DFLT_CHL2_DAT 0x0118 +#define HDMI_SW_DI_1_HEAD_WORD 0x0210 +#define HDMI_SW_DI_1_PKT_WORD0 0x0214 +#define HDMI_SW_DI_1_PKT_WORD1 0x0218 +#define HDMI_SW_DI_1_PKT_WORD2 0x021C +#define HDMI_SW_DI_1_PKT_WORD3 0x0220 +#define HDMI_SW_DI_1_PKT_WORD4 0x0224 +#define HDMI_SW_DI_1_PKT_WORD5 0x0228 +#define HDMI_SW_DI_1_PKT_WORD6 0x022C +#define HDMI_SW_DI_CFG 0x0230 + +#define HDMI_IFRAME_SLOT_AVI 1 + +#define XCAT(prefix, x, suffix) prefix ## x ## suffix +#define HDMI_SW_DI_N_HEAD_WORD(x) XCAT(HDMI_SW_DI_, x, _HEAD_WORD) +#define HDMI_SW_DI_N_PKT_WORD0(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD0) +#define HDMI_SW_DI_N_PKT_WORD1(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD1) +#define HDMI_SW_DI_N_PKT_WORD2(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD2) +#define HDMI_SW_DI_N_PKT_WORD3(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD3) +#define HDMI_SW_DI_N_PKT_WORD4(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD4) +#define HDMI_SW_DI_N_PKT_WORD5(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD5) +#define HDMI_SW_DI_N_PKT_WORD6(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD6) + +#define HDMI_IFRAME_DISABLED 0x0 +#define HDMI_IFRAME_SINGLE_SHOT 0x1 +#define HDMI_IFRAME_FIELD 0x2 +#define HDMI_IFRAME_FRAME 0x3 +#define HDMI_IFRAME_MASK 0x3 +#define HDMI_IFRAME_CFG_DI_N(x, n) ((x) << ((n-1)*4)) /* n from 1 to 6 */ + +#define HDMI_CFG_DEVICE_EN_SHIFT 0 +#define HDMI_CFG_DEVICE_EN (1 << HDMI_CFG_DEVICE_EN_SHIFT) +#define HDMI_CFG_HDMI_NOT_DVI_SHIFT 1 +#define HDMI_CFG_HDMI_NOT_DVI (1 << HDMI_CFG_HDMI_NOT_DVI_SHIFT) +#define HDMI_CFG_HDCP_EN_SHIFT 2 +#define HDMI_CFG_HDCP_EN (1 << HDMI_CFG_HDCP_EN_SHIFT) +#define HDMI_CFG_ESS_NOT_OESS_SHIFT 3 +#define HDMI_CFG_ESS_NOT_OESS (1 << HDMI_CFG_ESS_NOT_OESS_SHIFT) +#define HDMI_CFG_H_SYNC_POL_NEG_SHIFT 4 +#define HDMI_CFG_H_SYNC_POL_NEG (1 << HDMI_CFG_H_SYNC_POL_NEG_SHIFT) +#define HDMI_CFG_SINK_TERM_DET_EN_SHIFT 5 +#define HDMI_CFG_SINK_TERM_DET_EN (1 << HDMI_CFG_SINK_TERM_DET_EN_SHIFT) +#define HDMI_CFG_V_SYNC_POL_NEG_SHIFT 6 +#define HDMI_CFG_V_SYNC_POL_NEG (1 << HDMI_CFG_V_SYNC_POL_NEG_SHIFT) +#define HDMI_CFG_422_EN_SHIFT 8 +#define HDMI_CFG_422_EN (1 << HDMI_CFG_422_EN_SHIFT) +#define HDMI_CFG_FIFO_OVERRUN_CLR_SHIFT 12 +#define HDMI_CFG_FIFO_OVERRUN_CLR (1 << HDMI_CFG_FIFO_OVERRUN_CLR_SHIFT) +#define HDMI_CFG_FIFO_UNDERRUN_CLR_SHIFT 13 +#define HDMI_CFG_FIFO_UNDERRUN_CLR (1 << HDMI_CFG_FIFO_UNDERRUN_CLR_SHIFT) +#define HDMI_CFG_SW_RST_EN_SHIFT 31 +#define HDMI_CFG_SW_RST_EN (1 << HDMI_CFG_SW_RST_EN_SHIFT) + +#define HDMI_INT_GLOBAL (1 << 0) +#define HDMI_INT_SW_RST (1 << 1) +#define HDMI_INT_IFRAME (1 << 2) +#define HDMI_INT_PIX_CAP (1 << 3) +#define HDMI_INT_HOT_PLUG (1 << 4) +#define HDMI_INT_DLL_LCK (1 << 5) +#define HDMI_INT_NEW_FRAME (1 << 6) +#define HDMI_INT_GENCTRL_PKT (1 << 7) +#define HDMI_INT_SPDIF_FIFO_OVERRUN (1 << 8) +#define HDMI_INT_VID_FIFO_UNDERRUN (1 << 9) +#define HDMI_INT_VID_FIFO_OVERRUN (1 << 10) +#define HDMI_INT_SINK_TERM_PRESENT (1 << 11) +#define HDMI_INT_DI_2 (1 << 16) +#define HDMI_INT_DI_3 (1 << 17) +#define HDMI_INT_DI_4 (1 << 18) +#define HDMI_INT_DI_5 (1 << 19) +#define HDMI_INT_DI_6 (1 << 20) +#define HDMI_INT_DI_DMA_VSYNC_DONE (1 << 21) +#define HDMI_INT_DI_VSYNC_DONE (1 << 22) + +#define HDMI_DEFAULT_INT (HDMI_INT_SINK_TERM_PRESENT \ + | HDMI_INT_DLL_LCK \ + | HDMI_INT_HOT_PLUG \ + | HDMI_INT_GLOBAL) + +#define HDMI_WORKING_INT (HDMI_INT_SINK_TERM_PRESENT \ + | HDMI_INT_GENCTRL_PKT \ + | HDMI_INT_NEW_FRAME \ + | HDMI_INT_DLL_LCK \ + | HDMI_INT_HOT_PLUG \ + | HDMI_INT_PIX_CAP \ + | HDMI_INT_SW_RST \ + | HDMI_INT_GLOBAL) + +#define HDMI_STA_SW_RST_SHIFT 1 +#define HDMI_STA_SW_RST (1 << HDMI_STA_SW_RST_SHIFT) +#define HDMI_STA_PIX_CAP_SHIFT 3 +#define HDMI_STA_PIX_CAP (1 << HDMI_STA_PIX_CAP_SHIFT) +#define HDMI_STA_HOT_PLUG_SHIFT 4 +#define HDMI_STA_HOT_PLUG (1 << HDMI_STA_HOT_PLUG_SHIFT) +#define HDMI_STA_DLL_LCK_SHIFT 5 +#define HDMI_STA_DLL_LCK (1 << HDMI_STA_DLL_LCK_SHIFT) +#define HDMI_STA_SINK_TERM_SHIFT 6 +#define HDMI_STA_SINK_TERM (1 << HDMI_STA_SINK_TERM_SHIFT) +#define HDMI_STA_FIFO_SAMPLES_SHIFT 8 +#define HDMI_STA_FIFO_SAMPLES (0x1F << HDMI_STA_FIFO_SAMPLES_SHIFT) + +/* + * STI hdmi structure + * + * @dev: driver device + * @drm_dev: pointer to drm device + * @mode: current display mode selected + * @regs: hdmi register + * @syscfg: syscfg register for pll rejection configuration + * @clk_pix: hdmi pixel clock + * @clk_tmds: hdmi tmds clock + * @clk_phy: hdmi phy clock + * @clk_audio: hdmi audio clock + * @irq: hdmi interrupt number + * @tx3g0c55phy: true if 3g0c55phy is supported + * @enabled: true if hdmi is enabled else false + * @hpd_gpio: hdmi hot plug detect gpio number + * @hpd: hot plug detect status + * @wait_event: wait event + * @event_received: wait event status + * @reset: reset control of the hdmi phy + */ +struct sti_hdmi { + struct device dev; + struct drm_device *drm_dev; + struct drm_display_mode mode; + void __iomem *regs; + void __iomem *syscfg; + struct clk *clk_pix; + struct clk *clk_tmds; + struct clk *clk_phy; + struct clk *clk_audio; + int irq; + bool tx3g0c55phy; + bool enabled; + int hpd_gpio; + bool hpd; + wait_queue_head_t wait_event; + bool event_received; + struct reset_control *reset; +}; + +/* hdmi phy config structure + * + * A pointer to an array of these structures is passed to a TMDS (HDMI) output + * via the control interface to provide board and SoC specific + * configurations of the HDMI PHY. Each entry in the array specifies a hardware + * specific configuration for a given TMDS clock frequency range. + * + * @min_tmds_freq: Lower bound of TMDS clock frequency this entry applies to + * @max_tmds_freq: Upper bound of TMDS clock frequency this entry applies to + * @config: SoC specific register configuration + */ +struct hdmi_phy_config { + u32 min_tmds_freq; + u32 max_tmds_freq; + u32 config[4]; +}; + +void sti_hdmi_attach_ddc_client(struct i2c_client *ddc); + +int sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi); +void sti_hdmi_tx3g0c55phy_stop(struct sti_hdmi *hdmi); +void sti_hdmi_tx3g0c55phy_show(struct sti_hdmi *hdmi, struct seq_file *m); + +int sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi); +void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi); +void sti_hdmi_tx3g4c28phy_show(struct sti_hdmi *hdmi, struct seq_file *m); + +extern struct i2c_driver ddc_driver; +#endif diff --git a/drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c b/drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c new file mode 100644 index 0000000..547e9ee --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c @@ -0,0 +1,398 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Vincent Abriou vincent.abriou@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include "sti_hdmi.h" + +#define HDMI_SRZ_PLL_CFG 0x0504 +#define HDMI_SRZ_TAP_1 0x0508 +#define HDMI_SRZ_TAP_2 0x050C +#define HDMI_SRZ_TAP_3 0x0510 +#define HDMI_SRZ_CTRL 0x0514 + +#define HDMI_SRZ_PLL_CFG_POWER_DOWN (1 << 0) +#define HDMI_SRZ_PLL_CFG_VCOR_SHIFT 1 +#define HDMI_SRZ_PLL_CFG_VCOR_425MHZ 0 +#define HDMI_SRZ_PLL_CFG_VCOR_850MHZ 1 +#define HDMI_SRZ_PLL_CFG_VCOR_1700MHZ 2 +#define HDMI_SRZ_PLL_CFG_VCOR_3000MHZ 3 +#define HDMI_SRZ_PLL_CFG_VCOR_MASK 3 +#define HDMI_SRZ_PLL_CFG_VCOR(x) (x << HDMI_SRZ_PLL_CFG_VCOR_SHIFT) +#define HDMI_SRZ_PLL_CFG_NDIV_SHIFT 8 +#define HDMI_SRZ_PLL_CFG_NDIV_MASK (0x1F << HDMI_SRZ_PLL_CFG_NDIV_SHIFT) +#define HDMI_SRZ_PLL_CFG_MODE_SHIFT 16 +#define HDMI_SRZ_PLL_CFG_MODE_13_5_MHZ 0x1 +#define HDMI_SRZ_PLL_CFG_MODE_25_2_MHZ 0x4 +#define HDMI_SRZ_PLL_CFG_MODE_27_MHZ 0x5 +#define HDMI_SRZ_PLL_CFG_MODE_33_75_MHZ 0x6 +#define HDMI_SRZ_PLL_CFG_MODE_40_5_MHZ 0x7 +#define HDMI_SRZ_PLL_CFG_MODE_54_MHZ 0x8 +#define HDMI_SRZ_PLL_CFG_MODE_67_5_MHZ 0x9 +#define HDMI_SRZ_PLL_CFG_MODE_74_25_MHZ 0xA +#define HDMI_SRZ_PLL_CFG_MODE_81_MHZ 0xB +#define HDMI_SRZ_PLL_CFG_MODE_82_5_MHZ 0xC +#define HDMI_SRZ_PLL_CFG_MODE_108_MHZ 0xD +#define HDMI_SRZ_PLL_CFG_MODE_148_5_MHZ 0xE +#define HDMI_SRZ_PLL_CFG_MODE_165_MHZ 0xF +#define HDMI_SRZ_PLL_CFG_MODE_MASK 0xF +#define HDMI_SRZ_PLL_CFG_MODE(x) (x << HDMI_SRZ_PLL_CFG_MODE_SHIFT) + +#define HDMI_SRZ_CTRL_POWER_DOWN (1 << 0) +#define HDMI_SRZ_CTRL_EXTERNAL_DATA_EN (1 << 1) + +/* sysconf registers */ +#define HDMI_REJECTION_PLL_CONFIGURATION 0x0858 /* SYSTEM_CONFIG2534 */ +#define HDMI_REJECTION_PLL_STATUS 0x0948 /* SYSTEM_CONFIG2594 */ + +#define REJECTION_PLL_HDMI_ENABLE_SHIFT 0 +#define REJECTION_PLL_HDMI_ENABLE_MASK (0x1 << REJECTION_PLL_HDMI_ENABLE_SHIFT) +#define REJECTION_PLL_HDMI_PDIV_SHIFT 24 +#define REJECTION_PLL_HDMI_PDIV_MASK (0x7 << REJECTION_PLL_HDMI_PDIV_SHIFT) +#define REJECTION_PLL_HDMI_NDIV_SHIFT 16 +#define REJECTION_PLL_HDMI_NDIV_MASK (0xFF << REJECTION_PLL_HDMI_NDIV_SHIFT) +#define REJECTION_PLL_HDMI_MDIV_SHIFT 8 +#define REJECTION_PLL_HDMI_MDIV_MASK (0xFF << REJECTION_PLL_HDMI_MDIV_SHIFT) + +#define REJECTION_PLL_HDMI_REJ_PLL_LOCK (0x1 << 0) + +#define HDMI_TIMEOUT_PLL_LOCK 50 /*milliseconds */ + +#define HDMI_WAIT_PLL_REJECTION_STATUS 1000 + +/* pll mode structure + * + * A pointer to an array of these structures is passed to a TMDS (HDMI) output + * via the control interface to provide board and SoC specific + * configurations of the HDMI PHY. Each entry in the array specifies a hardware + * specific configuration for a given TMDS clock frequency range. The array + * should be terminated with an entry that has all fields set to zero. + * + * @min: Lower bound of TMDS clock frequency this entry applies to + * @max: Upper bound of TMDS clock frequency this entry applies to + * @mode: SoC specific register configuration + */ +struct pllmode { + u32 min; + u32 max; + u32 mode; +}; +#define NB_PLL_MODE 7 +static struct pllmode pllmodes[NB_PLL_MODE] = { + {13500000, 13513500, HDMI_SRZ_PLL_CFG_MODE_13_5_MHZ}, + {25174800, 25200000, HDMI_SRZ_PLL_CFG_MODE_25_2_MHZ}, + {27000000, 27027000, HDMI_SRZ_PLL_CFG_MODE_27_MHZ}, + {54000000, 54054000, HDMI_SRZ_PLL_CFG_MODE_54_MHZ}, + {72000000, 74250000, HDMI_SRZ_PLL_CFG_MODE_74_25_MHZ}, + {108000000, 108108000, HDMI_SRZ_PLL_CFG_MODE_108_MHZ}, + {148351648, 297000000, HDMI_SRZ_PLL_CFG_MODE_148_5_MHZ} +}; + +#define NB_HDMI_PHY_CONFIG 5 +static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = { + {0, 40000000, {0x00101010, 0x00101010, 0x00101010, 0x02} }, + {40000000, 140000000, {0x00111111, 0x00111111, 0x00111111, 0x02} }, + {140000000, 160000000, {0x00131313, 0x00101010, 0x00101010, 0x02} }, + {160000000, 250000000, {0x00131313, 0x00111111, 0x00111111, 0x03FE} }, + {250000000, 300000000, {0x00151515, 0x00101010, 0x00101010, 0x03FE} }, +}; + +/* + * Helper to write bit field + * + * @addr: register to update + * @val: value to write + * @mask: bit field mask to use + */ +static inline void reg_writemask(void __iomem *addr, u32 val, u32 mask) +{ + u32 old = readl(addr); + + val = (val & mask) | (old & ~mask); + writel(val, addr); +} + +/* + * Enable the old BCH/rejection PLL is now reused to provide the CLKPXPLL + * clock input to the new PHY PLL that generates the serializer clock + * (TMDS*10) and the TMDS clock which is now fed back into the HDMI + * formatter instead of the TMDS clock line from ClockGenB. + * + * @hdmi: pointer on the hdmi internal structure + * + * Return -1 if error occurs + */ +static int enable_pll_rejection(struct sti_hdmi *hdmi) +{ + int inputclock; + u32 mdiv; + u32 ndiv; + u32 pdiv; + u32 mask; + u32 val; + int i; + + DRM_DEBUG_DRIVER("\n"); + + inputclock = hdmi->mode.clock * 1000; + + DRM_DEBUG_DRIVER("hdmi rejection pll input clock = %dHz\n", inputclock); + + /* Force to power down the HDMI rejection PLL */ + mask = REJECTION_PLL_HDMI_ENABLE_MASK; + val = 0x0 << REJECTION_PLL_HDMI_ENABLE_SHIFT; + reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION, + val, mask); + + /* Check the HDMI rejection PLL is really down */ + for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) { + val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS); + if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) == 0) + break; + } + if (i == HDMI_WAIT_PLL_REJECTION_STATUS) { + DRM_ERROR("hdmi rejection pll is not well powered down\n"); + return -1; + } + + /* Power up the HDMI rejection PLL */ + /* + * Note: On this SoC (stiH416) we are forced to have the input clock + * be equal to the HDMI pixel clock. + * + * The values here have been suggested by validation however they are + * still provisional and subject to change. + * + * PLLout = (Fin*Mdiv) / ((2 * Ndiv) / 2^Pdiv) + */ + if (inputclock < 50000000) { + /* + * For slower clocks we need to multiply more to keep the + * internal VCO frequency within the physical specification + * of the PLL. + */ + pdiv = 4; + ndiv = 240; + mdiv = 30; + } else { + pdiv = 2; + ndiv = 60; + mdiv = 30; + } + + mask = REJECTION_PLL_HDMI_PDIV_MASK | + REJECTION_PLL_HDMI_NDIV_MASK | + REJECTION_PLL_HDMI_MDIV_MASK | REJECTION_PLL_HDMI_ENABLE_MASK; + val = (pdiv << REJECTION_PLL_HDMI_PDIV_SHIFT) | + (ndiv << REJECTION_PLL_HDMI_NDIV_SHIFT) | + (mdiv << REJECTION_PLL_HDMI_MDIV_SHIFT) | + (0x1 << REJECTION_PLL_HDMI_ENABLE_SHIFT); + reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION, + val, mask); + + /* Check the HDMI rejection PLL is really up */ + for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) { + val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS); + if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) != 0) + break; + } + if (i == HDMI_WAIT_PLL_REJECTION_STATUS) { + DRM_ERROR("hdmi rejection pll is not well powered up\n"); + return -1; + } + + DRM_DEBUG_DRIVER("hdmi rejection pll locked\n"); + + return 0; +} + +/* + * Disable the pll rejection + * + * @hdmi: pointer on the hdmi internal structure + */ +static void disable_pll_rejection(struct sti_hdmi *hdmi) +{ + int i; + u32 val; + u32 mask; + + DRM_DEBUG_DRIVER("\n"); + + mask = REJECTION_PLL_HDMI_ENABLE_MASK; + val = 0x0 << REJECTION_PLL_HDMI_ENABLE_SHIFT; + reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION, + val, mask); + + /* Check the HDMI rejection PLL is really down */ + for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) { + val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS); + if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) == 0) + break; + } + if (i == HDMI_WAIT_PLL_REJECTION_STATUS) + DRM_ERROR("hdmi rejection pll is not well powered down\n"); + else + DRM_DEBUG_DRIVER("hdmi rejection pll is powered down\n"); +} + +/* + * Start hdmi phy macro cell tx3g0c55 + * + * @hdmi: pointer on the hdmi internal structure + * + * Return -1 if error occurs + */ +int sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi) +{ + u32 ckpxpll = hdmi->mode.clock * 1000; + u32 tmdsck; + u32 freqvco; + u32 pllctrl = 0; + u32 val; + int i; + + if (enable_pll_rejection(hdmi)) + return -1; + + DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll); + + /* TODO: manage DeepColor (30, 36 and 48 bits) and pixel repetition */ + + /* Assuming no pixel repetition and 24bits color */ + tmdsck = ckpxpll; + pllctrl = 2 << HDMI_SRZ_PLL_CFG_NDIV_SHIFT; + + /* + * Setup the PLL mode parameter based on the ckpxpll. If we haven't got + * a clock frequency supported by one of the specific PLL modes then we + * will end up using the generic mode (0) which only supports a 10x + * multiplier, hence only 24bit color. + */ + for (i = 0; i < NB_PLL_MODE; i++) { + if (ckpxpll >= pllmodes[i].min && ckpxpll <= pllmodes[i].max) + pllctrl |= HDMI_SRZ_PLL_CFG_MODE(pllmodes[i].mode); + } + + freqvco = tmdsck * 10; + if (freqvco <= 425000000UL) + pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_425MHZ); + else if (freqvco <= 850000000UL) + pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_850MHZ); + else if (freqvco <= 1700000000UL) + pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_1700MHZ); + else if (freqvco <= 2970000000UL) + pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_3000MHZ); + else { + DRM_ERROR("PHY serializer clock out of range\n"); + goto err; + } + + /* + * Configure and power up the PHY PLL + */ + hdmi->event_received = false; + DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl); + writel(pllctrl, hdmi->regs + HDMI_SRZ_PLL_CFG); + + /* wait PLL interrupt */ + wait_event_interruptible_timeout(hdmi->wait_event, + hdmi->event_received == true, + msecs_to_jiffies + (HDMI_TIMEOUT_PLL_LOCK)); + + if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 0) { + DRM_ERROR("hdmi phy pll not locked\n"); + goto err; + } + + DRM_DEBUG_DRIVER("got PHY PLL Lock\n"); + + /* + * To configure the source termination and pre-emphasis appropriately + * for different high speed TMDS clock frequencies a phy configuration + * table must be provided, tailored to the SoC and board combination. + */ + for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) { + if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) && + (hdmiphy_config[i].max_tmds_freq >= tmdsck)) { + val = hdmiphy_config[i].config[0]; + writel(val, hdmi->regs + HDMI_SRZ_TAP_1); + val = hdmiphy_config[i].config[1]; + writel(val, hdmi->regs + HDMI_SRZ_TAP_2); + val = hdmiphy_config[i].config[2]; + writel(val, hdmi->regs + HDMI_SRZ_TAP_3); + val = hdmiphy_config[i].config[3]; + val |= HDMI_SRZ_CTRL_EXTERNAL_DATA_EN; + val &= ~HDMI_SRZ_CTRL_POWER_DOWN; + writel(val, hdmi->regs + HDMI_SRZ_CTRL); + + DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x 0x%x\n", + hdmiphy_config[i].config[0], + hdmiphy_config[i].config[1], + hdmiphy_config[i].config[2], + hdmiphy_config[i].config[3]); + return 0; + } + } + + /* + * Default, power up the serializer with no pre-emphasis or source + * termination. + */ + writel(0x0, hdmi->regs + HDMI_SRZ_TAP_1); + writel(0x0, hdmi->regs + HDMI_SRZ_TAP_2); + writel(0x0, hdmi->regs + HDMI_SRZ_TAP_3); + writel(HDMI_SRZ_CTRL_EXTERNAL_DATA_EN, hdmi->regs + HDMI_SRZ_CTRL); + + return 0; + +err: + disable_pll_rejection(hdmi); + + return -1; +} + +/* + * Stop hdmi phy macro cell tx3g0c55 + * + * @hdmi: pointer on the hdmi internal structure + */ +void sti_hdmi_tx3g0c55phy_stop(struct sti_hdmi *hdmi) +{ + DRM_DEBUG_DRIVER("\n"); + + hdmi->event_received = false; + + writel(HDMI_SRZ_CTRL_POWER_DOWN, hdmi->regs + HDMI_SRZ_CTRL); + writel(HDMI_SRZ_PLL_CFG_POWER_DOWN, hdmi->regs + HDMI_SRZ_PLL_CFG); + + /* wait PLL interrupt */ + wait_event_interruptible_timeout(hdmi->wait_event, + hdmi->event_received == true, + msecs_to_jiffies + (HDMI_TIMEOUT_PLL_LOCK)); + + if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 1) + DRM_ERROR("hdmi phy pll not well disabled\n"); + else + DRM_DEBUG_DRIVER("hdmi phy pll disabled\n"); + + disable_pll_rejection(hdmi); +} + +/* + * Debugfs + */ +#define HDMI_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \ + readl(hdmi->regs + reg)) +void sti_hdmi_tx3g0c55phy_show(struct sti_hdmi *hdmi, struct seq_file *m) +{ + HDMI_DBG_DUMP(HDMI_SRZ_PLL_CFG); + HDMI_DBG_DUMP(HDMI_SRZ_TAP_1); + HDMI_DBG_DUMP(HDMI_SRZ_TAP_2); + HDMI_DBG_DUMP(HDMI_SRZ_TAP_3); + HDMI_DBG_DUMP(HDMI_SRZ_CTRL); + seq_puts(m, "\n"); +} diff --git a/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c b/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c new file mode 100644 index 0000000..6e0bc2c --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Vincent Abriou vincent.abriou@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include "sti_hdmi.h" + +#define HDMI_SRZ_CFG 0x504 +#define HDMI_SRZ_PLL_CFG 0x510 +#define HDMI_SRZ_ICNTL 0x518 +#define HDMI_SRZ_CALCODE_EXT 0x520 + +#define HDMI_SRZ_CFG_EN (1L<<0) +#define HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT (1L<<1) +#define HDMI_SRZ_CFG_EXTERNAL_DATA (1L<<16) +#define HDMI_SRZ_CFG_RBIAS_EXT (1L<<17) +#define HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION (1L<<18) +#define HDMI_SRZ_CFG_EN_BIASRES_DETECTION (1L<<19) +#define HDMI_SRZ_CFG_EN_SRC_TERMINATION (1L<<24) + +#define HDMI_SRZ_CFG_INTERNAL_MASK (HDMI_SRZ_CFG_EN | \ + HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT | \ + HDMI_SRZ_CFG_EXTERNAL_DATA | \ + HDMI_SRZ_CFG_RBIAS_EXT | \ + HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION | \ + HDMI_SRZ_CFG_EN_BIASRES_DETECTION | \ + HDMI_SRZ_CFG_EN_SRC_TERMINATION) + +#define PLL_CFG_EN (1L<<0) +#define PLL_CFG_NDIV_SHIFT (8) +#define PLL_CFG_IDF_SHIFT (16) +#define PLL_CFG_ODF_SHIFT (24) + +#define ODF_DIV_1 (0) +#define ODF_DIV_2 (1) +#define ODF_DIV_4 (2) +#define ODF_DIV_8 (3) + +#define HDMI_TIMEOUT_PLL_LOCK 50 /*milliseconds */ + +struct plldividers_s { + uint32_t min; + uint32_t max; + uint32_t idf; + uint32_t odf; +}; + +/* + * Functional specification recommended values + */ +#define NB_PLL_MODE 5 +static struct plldividers_s plldividers[NB_PLL_MODE] = { + {0, 20000000, 1, ODF_DIV_8}, + {20000000, 42500000, 2, ODF_DIV_8}, + {42500000, 85000000, 4, ODF_DIV_4}, + {85000000, 170000000, 8, ODF_DIV_2}, + {170000000, 340000000, 16, ODF_DIV_1} +}; + +#define NB_HDMI_PHY_CONFIG 2 +static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = { + {0, 250000000, {0x0, 0x0, 0x0, 0x0} }, + {250000000, 300000000, {0x1110, 0x0, 0x0, 0x0} }, +}; + +/* + * Start hdmi phy macro cell tx3g4c28 + * + * @hdmi: pointer on the hdmi internal structure + * + * Return -1 if error occurs + */ +int sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi) +{ + u32 ckpxpll = hdmi->mode.clock * 1000; + u32 tmdsck; + u32 idf; + u32 odf; + u32 pllctrl = 0; + u32 val; + int i; + bool foundplldivides = false; + + DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll); + + for (i = 0; i < NB_PLL_MODE; i++) { + if (ckpxpll >= plldividers[i].min && + ckpxpll < plldividers[i].max) { + idf = plldividers[i].idf; + odf = plldividers[i].odf; + foundplldivides = true; + break; + } + } + + if (!foundplldivides) { + DRM_ERROR("input TMDS clock speed (%d) not supported\n", + ckpxpll); + goto err; + } + + /* TODO: manage DeepColor (30, 36 and 48 bits) and pixel repetition */ + + /* Assuming no pixel repetition and 24bits color */ + tmdsck = ckpxpll; + pllctrl |= 40 << PLL_CFG_NDIV_SHIFT; + + if (tmdsck > 340000000) { + DRM_ERROR("output TMDS clock (%d) out of range\n", tmdsck); + goto err; + } + + pllctrl |= idf << PLL_CFG_IDF_SHIFT; + pllctrl |= odf << PLL_CFG_ODF_SHIFT; + + /* + * Configure and power up the PHY PLL + */ + hdmi->event_received = false; + DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl); + writel((pllctrl | PLL_CFG_EN), hdmi->regs + HDMI_SRZ_PLL_CFG); + + /* wait PLL interrupt */ + wait_event_interruptible_timeout(hdmi->wait_event, + hdmi->event_received == true, + msecs_to_jiffies + (HDMI_TIMEOUT_PLL_LOCK)); + + if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 0) { + DRM_ERROR("hdmi phy pll not locked\n"); + goto err; + } + + DRM_DEBUG_DRIVER("got PHY PLL Lock\n"); + + val = (HDMI_SRZ_CFG_EN | + HDMI_SRZ_CFG_EXTERNAL_DATA | + HDMI_SRZ_CFG_EN_BIASRES_DETECTION | + HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION); + + if (tmdsck > 165000000) + val |= HDMI_SRZ_CFG_EN_SRC_TERMINATION; + + /* + * To configure the source termination and pre-emphasis appropriately + * for different high speed TMDS clock frequencies a phy configuration + * table must be provided, tailored to the SoC and board combination. + */ + for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) { + if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) && + (hdmiphy_config[i].max_tmds_freq >= tmdsck)) { + val |= (hdmiphy_config[i].config[0] + & ~HDMI_SRZ_CFG_INTERNAL_MASK); + writel(val, hdmi->regs + HDMI_SRZ_CFG); + val = hdmiphy_config[i].config[1]; + writel(val, hdmi->regs + HDMI_SRZ_ICNTL); + val = hdmiphy_config[i].config[2]; + writel(val, hdmi->regs + HDMI_SRZ_CALCODE_EXT); + DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x\n", + hdmiphy_config[i].config[0], + hdmiphy_config[i].config[1], + hdmiphy_config[i].config[2]); + return 0; + } + } + + /* + * Default, power up the serializer with no pre-emphasis or + * output swing correction + */ + writel(val, hdmi->regs + HDMI_SRZ_CFG); + writel(0x0, hdmi->regs + HDMI_SRZ_ICNTL); + writel(0x0, hdmi->regs + HDMI_SRZ_CALCODE_EXT); + + return 0; + +err: + return -1; +} + +/* + * Stop hdmi phy macro cell tx3g4c28 + * + * @hdmi: pointer on the hdmi internal structure + */ +void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi) +{ + int val = 0; + + DRM_DEBUG_DRIVER("\n"); + + hdmi->event_received = false; + + val = HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION; + val |= HDMI_SRZ_CFG_EN_BIASRES_DETECTION; + writel(val, hdmi->regs + HDMI_SRZ_CFG); + writel(0, hdmi->regs + HDMI_SRZ_PLL_CFG); + + /* wait PLL interrupt */ + wait_event_interruptible_timeout(hdmi->wait_event, + hdmi->event_received == true, + msecs_to_jiffies + (HDMI_TIMEOUT_PLL_LOCK)); + + if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 1) + DRM_ERROR("hdmi phy pll not well disabled\n"); + else + DRM_DEBUG_DRIVER("hdmi phy pll disabled\n"); +} + +/* + * Debugfs + */ +#define HDMI_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \ + readl(hdmi->regs + reg)) +void sti_hdmi_tx3g4c28phy_show(struct sti_hdmi *hdmi, struct seq_file *m) +{ + HDMI_DBG_DUMP(HDMI_SRZ_CFG); + HDMI_DBG_DUMP(HDMI_SRZ_PLL_CFG); + HDMI_DBG_DUMP(HDMI_SRZ_ICNTL); + HDMI_DBG_DUMP(HDMI_SRZ_CALCODE_EXT); + seq_puts(m, "\n"); +}
On Tue, May 20, 2014 at 03:56:13PM +0200, Benjamin Gaignard wrote: [...]
diff --git a/drivers/gpu/drm/sti/sti_hdmi.c b/drivers/gpu/drm/sti/sti_hdmi.c
[...]
+/* Reference to the hdmi device */ +struct device *hdmi_dev;
No globals please.
+/*
- Helper to write bit field
- @addr: register to update
- @val: value to write
- @mask: bit field mask to use
- */
+static inline void hdmi_reg_writemask(void __iomem *addr, u32 val, u32 mask)
Given the hdmi_ prefix I'd expect this to take a struct sti_hdmi * as first argument. Maybe you can also leave out the _reg in there since it should be pretty obvious what this writes.
+/*
- HDMI interrupt handler
- @irq: irq number
- @arg: connector structure
- */
+static irqreturn_t hdmi_irq_thread(int irq, void *arg) +{
- struct sti_hdmi *hdmi = arg;
- u32 status;
- /* read interrupt status */
- status = readl(hdmi->regs + HDMI_INT_STA);
- /* PLL lock interrupt */
- if (status & HDMI_INT_DLL_LCK) {
hdmi->event_received = true;
wake_up_interruptible(&hdmi->wait_event);
- }
I think a more common way to do this is using a struct completion.
- /* Hot plug detection */
- if (status & HDMI_INT_HOT_PLUG) {
hdmi->hpd = gpio_get_value(hdmi->hpd_gpio);
Hmm... that's odd. You get an interrupt from the HDMI controller but need to read a GPIO to find out what the status is?
if (hdmi->drm_dev)
drm_helper_hpd_irq_event(hdmi->drm_dev);
- }
- /* Sw reset completed */
- if (status & HDMI_INT_SW_RST) {
hdmi->event_received = true;
wake_up_interruptible(&hdmi->wait_event);
- }
There's no way to distinguish this from HDMI_INT_DLL_LCK.
- /* clear interrupt status */
- writel(status, hdmi->regs + HDMI_INT_CLR);
- /* TODO: check why this sync bus write solves the problem which
* is that without this line, the handler is sometimes called twice
*/
- /* sync bus write */
- readl(hdmi->regs + HDMI_INT_STA);
This seems to be something that you're systematically doing wrong.
+/*
- Start hdmi phy interface
- @hdmi: pointer on the hdmi internal structure
- Return -1 if error occurs
- */
+static int hdmi_phy_start(struct sti_hdmi *hdmi) +{
- DRM_DEBUG_DRIVER("\n");
- if (hdmi->tx3g0c55phy)
return sti_hdmi_tx3g0c55phy_start(hdmi);
- return sti_hdmi_tx3g4c28phy_start(hdmi);
+}
This looks like a prime candidate for the generic PHY framework. That will allow you to simply store a struct phy * and use the generic API rather than special case every possible type of PHY.
+/*
- Stop hdmi phy interface
- @hdmi: pointer on the hdmi internal structure
- */
+static void hdmi_phy_stop(struct sti_hdmi *hdmi) +{
- DRM_DEBUG_DRIVER("\n");
- if (hdmi->tx3g0c55phy)
sti_hdmi_tx3g0c55phy_stop(hdmi);
- else
sti_hdmi_tx3g4c28phy_stop(hdmi);
+}
Same here. The generic PHY equivalents for this would be phy_power_on() and phy_power_off().
+/*
- Set hdmi active area depending on the drm display mode selected
- @hdmi: pointer on the hdmi internal structure
- */
+static void hdmi_active_area(struct sti_hdmi *hdmi) +{
- u32 xmin, xmax;
- u32 ymin, ymax;
- DRM_DEBUG_DRIVER("\n");
- /*
* Active Front Sync Back Active
* Region Porch Porch Region
* <---------------><-------->0<---------><--------><----------------->
*
* ///////////////| | ///////////////|
* /////////////// | | /////////////// |
* /////////////// |......... ..........|/////////////// |
* 0___________ x/ymin x/ymax
*
* <--[hv]display--> <--[hv]display-->
* <--[hv]sync_start---------> <--[hv]sync_start-
* <--[hv]sync_end-----------------------> <--[hv]sync_end---
* <--[hv]total------------------------------------> <--[hv]total------
*/
There's an instance of this picture somewhere else already, no need to keep a copy in every subdriver. Also this is something that people are supposed to know. Perhaps it should be moved to a central place for reference?
- xmin = sti_vtg_get_pixel_number(hdmi->mode, 0);
- xmax = sti_vtg_get_pixel_number(hdmi->mode, hdmi->mode.hdisplay - 1);
- ymin = sti_vtg_get_line_number(hdmi->mode, 0);
- ymax = sti_vtg_get_line_number(hdmi->mode, hdmi->mode.vdisplay - 1);
- writel(xmin, hdmi->regs + HDMI_ACTIVE_VID_XMIN);
- writel(xmax, hdmi->regs + HDMI_ACTIVE_VID_XMAX);
- writel(ymin, hdmi->regs + HDMI_ACTIVE_VID_YMIN);
- writel(ymax, hdmi->regs + HDMI_ACTIVE_VID_YMAX);
- DRM_DEBUG_DRIVER("xmin=%d xmax=%d ymin=%d ymax=%d\n",
xmin, xmax, ymin, ymax);
+}
I feel a midlayer coming up in subsequent patches. This looks like it should be more tightly coupled to DRM.
+/*
- Overall hdmi configuration
- @hdmi: pointer on the hdmi internal structure
- */
+static void hdmi_config(struct sti_hdmi *hdmi) +{
- u32 val;
- u32 mask;
- DRM_DEBUG_DRIVER("\n");
- /* Clear overrun and underrun fifo */
- mask = HDMI_CFG_FIFO_OVERRUN_CLR | HDMI_CFG_FIFO_UNDERRUN_CLR;
- val = HDMI_CFG_FIFO_OVERRUN_CLR | HDMI_CFG_FIFO_UNDERRUN_CLR;
- /* Enable HDMI mode not DVI */
- mask |= HDMI_CFG_HDMI_NOT_DVI | HDMI_CFG_ESS_NOT_OESS;
- val |= HDMI_CFG_HDMI_NOT_DVI | HDMI_CFG_ESS_NOT_OESS;
- /* Enable sink term detection */
- mask |= HDMI_CFG_SINK_TERM_DET_EN;
- val |= HDMI_CFG_SINK_TERM_DET_EN;
- /* Set Hsync polarity */
- if ((hdmi->mode.flags && DRM_MODE_FLAG_NHSYNC)
s/&&/&/
== DRM_MODE_FLAG_NHSYNC) {
DRM_DEBUG_DRIVER("H Sync Negative\n");
mask |= HDMI_CFG_H_SYNC_POL_NEG;
val |= HDMI_CFG_H_SYNC_POL_NEG;
- }
- /* Set Vsync polarity */
- if ((hdmi->mode.flags && DRM_MODE_FLAG_NVSYNC)
== DRM_MODE_FLAG_NVSYNC) {
DRM_DEBUG_DRIVER("V Sync Negative\n");
mask |= HDMI_CFG_V_SYNC_POL_NEG;
val |= HDMI_CFG_V_SYNC_POL_NEG;
- }
- /* Enable HDMI */
- mask |= HDMI_CFG_DEVICE_EN;
- val |= HDMI_CFG_DEVICE_EN;
- hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask);
Quite frankly I don't see how hdmi_reg_writemask() makes this any more readable. This is equally readable in my opinion:
value = hdmi_read(hdmi, HDMI_CFG); value &= ~HDMI...; value |= HDMI...; ... hdmi_write(hdmi, value, HDMI_CFG);
Of course since your mask and value are always the same in the above there's really no use in masking out and ORing back in the value in the first place, so all of these can simply be:
value = HDMI... | HDMI... | HDMI... | ...;
if (...) value |= HDMI...;
hdmi_write(hdmi, value, HDMI_CFG);
+/*
- Prepare and configure the AVI infoframe
- AVI infoframe are transmitted at least once per two video field and
- contains information about HDMI transmission mode such as color space,
- colorimetry, ...
- @hdmi: pointer on the hdmi internal structure
- Return negative value if error occurs
- */
+static int hdmi_avi_infoframe_config(struct sti_hdmi *hdmi) +{
- struct drm_display_mode *mode = &hdmi->mode;
- struct hdmi_avi_infoframe infoframe;
- u8 buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE];
I think you can use HDMI_INFOFRAME_SIZE(AVI) here.
- u8 *frame = buffer + HDMI_INFOFRAME_HEADER_SIZE - 1;
Why "- 1"? I think this warrants a comment.
- u32 val;
- u32 mask;
- int ret;
- DRM_DEBUG_DRIVER("\n");
- ret = drm_hdmi_avi_infoframe_from_display_mode(&infoframe, mode);
- if (ret < 0) {
DRM_ERROR("failed to setup AVI infoframe: %d\n", ret);
return ret;
- }
- /* TODO: remove static infoframe configuration */
- infoframe.colorspace = HDMI_COLORSPACE_RGB;
- infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT;
- infoframe.colorimetry = HDMI_COLORIMETRY_NONE;
- infoframe.pixel_repeat = 0;
Why is this even necessary in the first place? If it really is it's likely that the core is just missing something, in which case the drm_hdmi_avi_infoframe_from_display_mode() helper should be fixed.
- ret = hdmi_avi_infoframe_pack(&infoframe, buffer, sizeof(buffer));
- if (ret < 0) {
DRM_ERROR("failed to pack AVI infoframe: %d\n", ret);
return ret;
- }
- /* Disable transmission slot for AVI infoframe */
- val = HDMI_IFRAME_DISABLED;
- mask = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, HDMI_IFRAME_SLOT_AVI);
This is really impossible to parse by a human. I'd rather see this open- coded than obfuscated by some magic macro.
- hdmi_reg_writemask(hdmi->regs + HDMI_SW_DI_CFG, val, mask);
- /* Infoframe header */
- val = buffer[0x0];
- val |= buffer[0x1] << 8;
- val |= buffer[0x2] << 16;
- writel(val, hdmi->regs + HDMI_SW_DI_N_HEAD_WORD(HDMI_IFRAME_SLOT_AVI));
I think a hdmi_writel() helper would be useful here. Also the registers below seem to be all successive in memory, so maybe some sort of look would make this easier to read.
- /* Infoframe packet bytes */
- val = frame[0x0];
- val |= frame[0x1] << 8;
- val |= frame[0x2] << 16;
- val |= frame[0x3] << 24;
- writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD0(HDMI_IFRAME_SLOT_AVI));
- val = frame[0x4];
- val |= frame[0x5] << 8;
- val |= frame[0x6] << 16;
- val |= frame[0x7] << 24;
- writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD1(HDMI_IFRAME_SLOT_AVI));
- val = frame[0x8];
- val |= frame[0x9] << 8;
- val |= frame[0xA] << 16;
- val |= frame[0xB] << 24;
- writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD2(HDMI_IFRAME_SLOT_AVI));
- val = frame[0xC];
- val |= frame[0xD] << 8;
- writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD3(HDMI_IFRAME_SLOT_AVI));
- /* Enable transmission slot for AVI infoframe */
- /* According to the hdmi specification, AVI infoframe should be
* transmitted at least once per two video fields */
- val = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_FIELD, HDMI_IFRAME_SLOT_AVI);
- mask = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, HDMI_IFRAME_SLOT_AVI);
- hdmi_reg_writemask(hdmi->regs + HDMI_SW_DI_CFG, val, mask);
Again, not much gain in the hdmi_reg_writemask() helper here.
+/*
- Software reset of the hdmi subsystem
- @hdmi: pointer on the hdmi internal structure
- Return -1 if error occurs
No, this function never fails and always returns 0. Ideally it would propagate errors from lower layers if they can't be handled here.
- */
+#define HDMI_TIMEOUT_SWRESET 100 /*milliseconds */ +static int hdmi_swreset(struct sti_hdmi *hdmi) +{
- u32 val;
- u32 mask;
- DRM_DEBUG_DRIVER("\n");
- /* Enable hdmi_audio clock only during hdmi reset */
- if (clk_prepare_enable(hdmi->clk_audio))
DRM_INFO("Failed to prepare/enable hdmi_audio clk\n");
You should propagate this error. If the clock can't be enabled presumably the remainder of this function can't succeed, so there's no point continuing beyond this.
- /* Sw reset */
You should either spell this out ("software") or use all caps for abbreviations ("SW").
- mask = HDMI_CFG_SW_RST_EN;
- val = HDMI_CFG_SW_RST_EN;
- hdmi->event_received = false;
- hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask);
- /* Wait reset completed */
- wait_event_interruptible_timeout(hdmi->wait_event,
hdmi->event_received == true,
msecs_to_jiffies
(HDMI_TIMEOUT_SWRESET));
I think a completion would be better here. Perhaps even that would be overkill. You could also simply use a loop with a timeout where you check for the reset complete bit to be flagged.
- /*
* HDMI_STA_SW_RST bit is set to '1' when SW_RST bit in HDMI_CFG is
* set to '1' and clk_audio is running.
*/
- if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_SW_RST) == 0)
DRM_INFO("Warning: HDMI sw reset timeout occurs\n");
If this times out there's something from and you should return an error here so that appropriate action can be taken at higher levels.
+/*
- Attach the I2C ddc client to allow hdmi i2c communication
- @ddc: i2c client
- */
+static struct i2c_client *hdmi_ddc; +void sti_hdmi_attach_ddc_client(struct i2c_client *ddc) +{
- DRM_DEBUG_DRIVER("\n");
- if (ddc)
hdmi_ddc = ddc;
+}
This doesn't look right. All other drivers use an i2c_adapter directly, so this should do the same. Since this will be using device tree it can use the same mechanism that every other DT driver uses.
On that note, I haven't seen you post the device tree bindings for this yet. It would be good to get a look at that as well since it may help with understanding how this all fits together. Also it will need to be reviewed as well anyway and depending on the output the drivers may need significant rework.
+/*
- Get modes from edid
- @drm_connector: pointer on the drm connector
- */
+static int sti_hdmi_get_modes(struct drm_connector *drm_connector) +{
- struct edid *edid;
- int count;
- DRM_DEBUG_DRIVER("\n");
- if ((!hdmi_ddc) || (!hdmi_ddc->adapter))
goto fail;
- edid = drm_get_edid(drm_connector, hdmi_ddc->adapter);
- if (!edid)
goto fail;
You need to call drm_mode_connector_update_edid_property() even in case of failure here, since otherwise the connector will keep carrying a stale copy of the EDID.
- count = drm_add_edid_modes(drm_connector, edid);
- if (count)
drm_mode_connector_update_edid_property(drm_connector, edid);
- else
DRM_ERROR("Add edid modes failed\n");
s/edid/EDID/ or better yet, just drop this error message.
- kfree(edid);
- return count;
+fail:
- DRM_ERROR("Can not read HDMI EDID\n");
- return -1;
This function should return the number of modes probed, so 0 in case of failure.
+static int sti_hdmi_probe(struct platform_device *pdev) +{
- struct device *dev = &pdev->dev;
- struct sti_hdmi *hdmi;
- struct device_node *np = dev->of_node;
- struct resource *res;
- int ret;
- DRM_INFO("%s\n", __func__);
- hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
- if (!hdmi) {
DRM_ERROR("Failed to allocate memory for hdmi\n");
return -ENOMEM;
- }
- hdmi->dev = pdev->dev;
- /* Get resources */
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hdmi-reg");
If you move the PHY programming to a different driver then this becomes the only resource and you don't need a name. Regardless, though, I think something like "hdmi" or "regs" would be more appropriate here.
- if (!res) {
DRM_ERROR("Invalid hdmi resource\n");
return -ENOMEM;
- }
- hdmi->regs = devm_ioremap_nocache(dev, res->start, resource_size(res));
- if (IS_ERR(hdmi->regs))
return PTR_ERR(hdmi->regs);
- if (of_device_is_compatible(np, "st,stih416-hdmi")) {
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"syscfg");
if (!res) {
DRM_ERROR("Invalid syscfg resource\n");
return -ENOMEM;
}
hdmi->syscfg = devm_ioremap_nocache(dev, res->start,
resource_size(res));
if (IS_ERR(hdmi->syscfg))
return PTR_ERR(hdmi->syscfg);
hdmi->tx3g0c55phy = true;
- }
Again this seems like it would be better exposed as a generic PHY. That way you can simply request the PHY and use it if present. Perhaps this isn't really a PHY but something else entirely. What is this syscfg register range? Does it belong to some other device? I suspect that since you're not requesting the region this may in fact already be used by some other driver. In that case doing this is unsafe and you should provide a way to call into that other driver from this one to apply the corresponding configuration.
- /* Get clock resources */
- hdmi->clk_pix = devm_clk_get(dev, "hdmi_pix");
[...]
- hdmi->clk_tmds = devm_clk_get(dev, "hdmi_tmds");
[...]
- hdmi->clk_phy = devm_clk_get(dev, "hdmi_phy");
[...]
- hdmi->clk_audio = devm_clk_get(dev, "hdmi_audio");
You can drop the hdmi_ prefix on all of these clock names since they're relative to the HDMI device anyway.
- hdmi->hpd_gpio = of_get_named_gpio(np, "hdmi,hpd-gpio", 0);
This should use the gpiod API if at all possible.
- if (hdmi->hpd_gpio < 0) {
DRM_ERROR("Failed to get hdmi hpd-gpio\n");
return -EIO;
- }
- hdmi->hpd = gpio_get_value(hdmi->hpd_gpio);
Why do you cache this value here? Can't you simply read it directly when it's needed?
- init_waitqueue_head(&hdmi->wait_event);
- /* Get irq ressources */
- hdmi->irq = platform_get_irq_byname(pdev, "hdmi_irq");
Again, no need for the hdmi_ prefix. In this case it would also be an option to just name it "hdmi" if it's the only interrupt that this device can raise.
- ret = devm_request_threaded_irq(dev, hdmi->irq, NULL,
hdmi_irq_thread, IRQF_ONESHOT,
"hdmi_irq", hdmi);
Perhaps dev_name(dev) instead of "hdmi_irq". Also perhaps you should provide a hdmi_irq() to implement the hard IRQ handler (reading the interrupt status registers) and if you determine that you need to do something that may sleep (as in case of the hotplug detect) you can return IRQ_WAKE_THREAD to only run the threaded handler if necessary.
- if (ret) {
DRM_ERROR("Failed to register hdmi interrupt\n");
s/hdmi/HDMI/
return ret;
- }
- /* Get reset resources */
This comment is superfluous.
- hdmi->reset = devm_reset_control_get(dev, "hdmi");
- /* Take hdmi out of reset */
- if (!IS_ERR(hdmi->reset))
reset_control_deassert(hdmi->reset);
Shouldn't this rather be:
if (IS_ERR(hdmi->reset)) return PTR_ERR(hdmi->reset);
err = reset_control_deassert(hdmi->reset); if (err < 0) return err;
?
- hdmi_dev = &hdmi->dev;
- platform_set_drvdata(pdev, hdmi);
- return component_add(&pdev->dev, &sti_hdmi_ops);
+}
+static int sti_hdmi_remove(struct platform_device *pdev) +{
- component_del(&pdev->dev, &sti_hdmi_ops);
- return 0;
+}
+static struct of_device_id hdmi_match_types[] = {
- {
.compatible = "st,stih416-hdmi",
},
- {
.compatible = "st,stih407-hdmi",
},
- { /* end node */ }
+}; +MODULE_DEVICE_TABLE(of, hdmi_match_types);
Weird indentation again. And s/hdmi_match_types/hdmi_of_match/?
+struct platform_driver sti_hdmi_driver = {
- .driver = {
.name = "sti-hdmi",
.owner = THIS_MODULE,
.of_match_table = hdmi_match_types,
},
- .probe = sti_hdmi_probe,
- .remove = sti_hdmi_remove,
+}; +module_platform_driver(sti_hdmi_driver);
+MODULE_LICENSE("GPL");
I don't think you need that in every file that's built into a one module with other files. Only use it in the main source file.
Also s/GPL/GPL v2/.
diff --git a/drivers/gpu/drm/sti/sti_hdmi.h b/drivers/gpu/drm/sti/sti_hdmi.h
[...]
+/* HDMI v2.9 macro cell */
What does that mean?
+void sti_hdmi_attach_ddc_client(struct i2c_client *ddc);
+int sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi); +void sti_hdmi_tx3g0c55phy_stop(struct sti_hdmi *hdmi); +void sti_hdmi_tx3g0c55phy_show(struct sti_hdmi *hdmi, struct seq_file *m);
+int sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi); +void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi); +void sti_hdmi_tx3g4c28phy_show(struct sti_hdmi *hdmi, struct seq_file *m);
+extern struct i2c_driver ddc_driver;
You should be able to get rid of all of these.
diff --git a/drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c b/drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c
[...]
+/*
- Enable the old BCH/rejection PLL is now reused to provide the CLKPXPLL
- clock input to the new PHY PLL that generates the serializer clock
- (TMDS*10) and the TMDS clock which is now fed back into the HDMI
- formatter instead of the TMDS clock line from ClockGenB.
- @hdmi: pointer on the hdmi internal structure
- Return -1 if error occurs
Should return a proper error code.
- */
+static int enable_pll_rejection(struct sti_hdmi *hdmi) +{
- int inputclock;
unsigned?
- u32 mdiv;
- u32 ndiv;
- u32 pdiv;
- u32 mask;
- u32 val;
Variables of the same type can be declared on a single line to make this shorter.
- int i;
unsigned?
- inputclock = hdmi->mode.clock * 1000;
- DRM_DEBUG_DRIVER("hdmi rejection pll input clock = %dHz\n", inputclock);
- /* Force to power down the HDMI rejection PLL */
- mask = REJECTION_PLL_HDMI_ENABLE_MASK;
- val = 0x0 << REJECTION_PLL_HDMI_ENABLE_SHIFT;
- reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION,
val, mask);
- /* Check the HDMI rejection PLL is really down */
- for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) {
val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS);
if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) == 0)
break;
- }
- if (i == HDMI_WAIT_PLL_REJECTION_STATUS) {
DRM_ERROR("hdmi rejection pll is not well powered down\n");
return -1;
- }
Should this perhaps be a timed loop rather than a busy loop?
- /* Power up the HDMI rejection PLL */
- /*
* Note: On this SoC (stiH416) we are forced to have the input clock
* be equal to the HDMI pixel clock.
*
* The values here have been suggested by validation however they are
* still provisional and subject to change.
*
* PLLout = (Fin*Mdiv) / ((2 * Ndiv) / 2^Pdiv)
*/
- if (inputclock < 50000000) {
/*
* For slower clocks we need to multiply more to keep the
* internal VCO frequency within the physical specification
* of the PLL.
*/
pdiv = 4;
ndiv = 240;
mdiv = 30;
- } else {
pdiv = 2;
ndiv = 60;
mdiv = 30;
- }
- mask = REJECTION_PLL_HDMI_PDIV_MASK |
REJECTION_PLL_HDMI_NDIV_MASK |
REJECTION_PLL_HDMI_MDIV_MASK | REJECTION_PLL_HDMI_ENABLE_MASK;
- val = (pdiv << REJECTION_PLL_HDMI_PDIV_SHIFT) |
(ndiv << REJECTION_PLL_HDMI_NDIV_SHIFT) |
(mdiv << REJECTION_PLL_HDMI_MDIV_SHIFT) |
(0x1 << REJECTION_PLL_HDMI_ENABLE_SHIFT);
- reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION,
val, mask);
- /* Check the HDMI rejection PLL is really up */
- for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) {
val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS);
if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) != 0)
break;
- }
- if (i == HDMI_WAIT_PLL_REJECTION_STATUS) {
DRM_ERROR("hdmi rejection pll is not well powered up\n");
return -1;
- }
Same here.
- DRM_DEBUG_DRIVER("hdmi rejection pll locked\n");
- return 0;
+}
+/*
- Disable the pll rejection
- @hdmi: pointer on the hdmi internal structure
- */
+static void disable_pll_rejection(struct sti_hdmi *hdmi) +{
- int i;
- u32 val;
- u32 mask;
- DRM_DEBUG_DRIVER("\n");
- mask = REJECTION_PLL_HDMI_ENABLE_MASK;
- val = 0x0 << REJECTION_PLL_HDMI_ENABLE_SHIFT;
- reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION,
val, mask);
- /* Check the HDMI rejection PLL is really down */
- for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) {
val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS);
if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) == 0)
break;
- }
- if (i == HDMI_WAIT_PLL_REJECTION_STATUS)
DRM_ERROR("hdmi rejection pll is not well powered down\n");
- else
DRM_DEBUG_DRIVER("hdmi rejection pll is powered down\n");
And here.
+}
+/*
- Start hdmi phy macro cell tx3g0c55
- @hdmi: pointer on the hdmi internal structure
- Return -1 if error occurs
Should return a proper error code.
- */
+int sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi) +{
- u32 ckpxpll = hdmi->mode.clock * 1000;
- u32 tmdsck;
- u32 freqvco;
- u32 pllctrl = 0;
- u32 val;
- int i;
unsigned
- if (enable_pll_rejection(hdmi))
return -1;
err = enable_pll_rejection(hdmi); if (err < 0) return err;
- /* TODO: manage DeepColor (30, 36 and 48 bits) and pixel repetition */
- /* Assuming no pixel repetition and 24bits color */
- tmdsck = ckpxpll;
- pllctrl = 2 << HDMI_SRZ_PLL_CFG_NDIV_SHIFT;
- /*
* Setup the PLL mode parameter based on the ckpxpll. If we haven't got
* a clock frequency supported by one of the specific PLL modes then we
* will end up using the generic mode (0) which only supports a 10x
* multiplier, hence only 24bit color.
*/
- for (i = 0; i < NB_PLL_MODE; i++) {
if (ckpxpll >= pllmodes[i].min && ckpxpll <= pllmodes[i].max)
pllctrl |= HDMI_SRZ_PLL_CFG_MODE(pllmodes[i].mode);
- }
- freqvco = tmdsck * 10;
- if (freqvco <= 425000000UL)
pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_425MHZ);
- else if (freqvco <= 850000000UL)
pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_850MHZ);
- else if (freqvco <= 1700000000UL)
pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_1700MHZ);
- else if (freqvco <= 2970000000UL)
pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_3000MHZ);
- else {
DRM_ERROR("PHY serializer clock out of range\n");
goto err;
- }
This could be a table.
- /*
* Configure and power up the PHY PLL
*/
- hdmi->event_received = false;
- DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl);
- writel(pllctrl, hdmi->regs + HDMI_SRZ_PLL_CFG);
- /* wait PLL interrupt */
- wait_event_interruptible_timeout(hdmi->wait_event,
hdmi->event_received == true,
msecs_to_jiffies
(HDMI_TIMEOUT_PLL_LOCK));
- if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 0) {
DRM_ERROR("hdmi phy pll not locked\n");
goto err;
- }
There doesn't seem to be a need for this to be strictly interrupt driven. A simple timed loop would do equally well.
- DRM_DEBUG_DRIVER("got PHY PLL Lock\n");
- /*
* To configure the source termination and pre-emphasis appropriately
* for different high speed TMDS clock frequencies a phy configuration
* table must be provided, tailored to the SoC and board combination.
*/
- for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) {
if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) &&
(hdmiphy_config[i].max_tmds_freq >= tmdsck)) {
val = hdmiphy_config[i].config[0];
writel(val, hdmi->regs + HDMI_SRZ_TAP_1);
val = hdmiphy_config[i].config[1];
writel(val, hdmi->regs + HDMI_SRZ_TAP_2);
val = hdmiphy_config[i].config[2];
writel(val, hdmi->regs + HDMI_SRZ_TAP_3);
val = hdmiphy_config[i].config[3];
val |= HDMI_SRZ_CTRL_EXTERNAL_DATA_EN;
val &= ~HDMI_SRZ_CTRL_POWER_DOWN;
writel(val, hdmi->regs + HDMI_SRZ_CTRL);
DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x 0x%x\n",
hdmiphy_config[i].config[0],
hdmiphy_config[i].config[1],
hdmiphy_config[i].config[2],
hdmiphy_config[i].config[3]);
return 0;
}
- }
- /*
* Default, power up the serializer with no pre-emphasis or source
* termination.
*/
Should this case produce a warning?
- writel(0x0, hdmi->regs + HDMI_SRZ_TAP_1);
- writel(0x0, hdmi->regs + HDMI_SRZ_TAP_2);
- writel(0x0, hdmi->regs + HDMI_SRZ_TAP_3);
- writel(HDMI_SRZ_CTRL_EXTERNAL_DATA_EN, hdmi->regs + HDMI_SRZ_CTRL);
- return 0;
+err:
- disable_pll_rejection(hdmi);
- return -1;
+}
[...]
+/*
- Debugfs
- */
+#define HDMI_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \
readl(hdmi->regs + reg))
+void sti_hdmi_tx3g0c55phy_show(struct sti_hdmi *hdmi, struct seq_file *m) +{
- HDMI_DBG_DUMP(HDMI_SRZ_PLL_CFG);
- HDMI_DBG_DUMP(HDMI_SRZ_TAP_1);
- HDMI_DBG_DUMP(HDMI_SRZ_TAP_2);
- HDMI_DBG_DUMP(HDMI_SRZ_TAP_3);
- HDMI_DBG_DUMP(HDMI_SRZ_CTRL);
- seq_puts(m, "\n");
Why the empty line? Is this supposed to be used as part of a larger file? I'd recommend against doing that and rather have one debugfs entry for each device's register file.
diff --git a/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c b/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c
[...]
Mostly the same comments apply here as for the other PHY.
Thierry
Add I2C client driver to retrieve EDID.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Makefile | 3 ++- drivers/gpu/drm/sti/sti_ddc.c | 56 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/sti/sti_ddc.c
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 5295fc7..6c474eb 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -2,7 +2,8 @@ ccflags-y := -Iinclude/drm
stidrm-y := sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ - sti_hdmi_tx3g4c28phy.o + sti_hdmi_tx3g4c28phy.o \ + sti_ddc.o
obj-$(CONFIG_VTAC_STI) += sti_vtac_tx.o sti_vtac_rx.o obj-$(CONFIG_VTG_STI) += sti_vtg.o sti_vtg_utils.o diff --git a/drivers/gpu/drm/sti/sti_ddc.c b/drivers/gpu/drm/sti/sti_ddc.c new file mode 100644 index 0000000..147a704 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_ddc.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ +#include <drm/drmP.h> + +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/module.h> + +#include "sti_hdmi.h" + +static int sti_hdmi_ddc_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + sti_hdmi_attach_ddc_client(client); + + DRM_INFO("%s attached %s into i2c adapter successfully\n", + __func__, client->name); + + return 0; +} + +static int sti_hdmi_ddc_remove(struct i2c_client *client) +{ + DRM_INFO("%s detached %s from i2c adapter successfully\n", + __func__, client->name); + + return 0; +} + +static struct i2c_device_id sti_ddc_idtable[] = { + {"sti-hdmiddc", 0}, + {}, +}; + +static struct of_device_id hdmiddc_match_types[] = { + { + .compatible = "st,sti-hdmiddc", + }, { + /* end node */ + } +}; + +struct i2c_driver ddc_driver = { + .driver = { + .name = "sti-hdmiddc", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(hdmiddc_match_types), + }, + .id_table = sti_ddc_idtable, + .probe = sti_hdmi_ddc_probe, + .remove = sti_hdmi_ddc_remove, + .command = NULL, +};
On Tue, May 20, 2014 at 03:56:14PM +0200, Benjamin Gaignard wrote:
Add I2C client driver to retrieve EDID.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org
drivers/gpu/drm/sti/Makefile | 3 ++- drivers/gpu/drm/sti/sti_ddc.c | 56 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/sti/sti_ddc.c
With my comments about DDC in the previous patch addressed this can be completely dropped.
Thierry
Add driver to support analog TV ouput.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Makefile | 1 + drivers/gpu/drm/sti/sti_hda.c | 480 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 481 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_hda.c
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 6c474eb..aa69ea0 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm stidrm-y := sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ + sti_hda.o \ sti_ddc.o
obj-$(CONFIG_VTAC_STI) += sti_vtac_tx.o sti_vtac_rx.o diff --git a/drivers/gpu/drm/sti/sti_hda.c b/drivers/gpu/drm/sti/sti_hda.c new file mode 100644 index 0000000..315aa0e --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hda.c @@ -0,0 +1,480 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Author: Fabien Dessenne fabien.dessenne@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <drm/drmP.h> + +/* HDformatter registers */ +#define HDA_ANA_CFG 0x0000 +#define HDA_ANA_SCALE_CTRL_Y 0x0004 +#define HDA_ANA_SCALE_CTRL_CB 0x0008 +#define HDA_ANA_SCALE_CTRL_CR 0x000C +#define HDA_ANA_ANC_CTRL 0x0010 +#define HDA_ANA_SRC_Y_CFG 0x0014 +#define HDA_COEFF_Y_PH1_TAP123 0x0018 +#define HDA_COEFF_Y_PH1_TAP456 0x001C +#define HDA_COEFF_Y_PH2_TAP123 0x0020 +#define HDA_COEFF_Y_PH2_TAP456 0x0024 +#define HDA_COEFF_Y_PH3_TAP123 0x0028 +#define HDA_COEFF_Y_PH3_TAP456 0x002C +#define HDA_COEFF_Y_PH4_TAP123 0x0030 +#define HDA_COEFF_Y_PH4_TAP456 0x0034 +#define HDA_ANA_SRC_C_CFG 0x0040 +#define HDA_COEFF_C_PH1_TAP123 0x0044 +#define HDA_COEFF_C_PH1_TAP456 0x0048 +#define HDA_COEFF_C_PH2_TAP123 0x004C +#define HDA_COEFF_C_PH2_TAP456 0x0050 +#define HDA_COEFF_C_PH3_TAP123 0x0054 +#define HDA_COEFF_C_PH3_TAP456 0x0058 +#define HDA_COEFF_C_PH4_TAP123 0x005C +#define HDA_COEFF_C_PH4_TAP456 0x0060 +#define HDA_SYNC_AWGI 0x0300 + +/* HDA_ANA_CFG */ +#define CFG_AWG_ASYNC_EN (1 << 0) +#define CFG_AWG_ASYNC_HSYNC_MTD (1 << 1) +#define CFG_AWG_ASYNC_VSYNC_MTD (1 << 2) +#define CFG_AWG_SYNC_DEL (1 << 3) +#define CFG_AWG_FLTR_MODE_SHIFT 4 +#define CFG_AWG_FLTR_MODE_MASK (0xF << CFG_AWG_FLTR_MODE_SHIFT) +#define CFG_AWG_FLTR_MODE_SD (0 << CFG_AWG_FLTR_MODE_SHIFT) +#define CFG_AWG_FLTR_MODE_ED (1 << CFG_AWG_FLTR_MODE_SHIFT) +#define CFG_AWG_FLTR_MODE_HD (2 << CFG_AWG_FLTR_MODE_SHIFT) +#define CFG_SYNC_ON_PBPR_MASK (1 << 8) +#define CFG_PREFILTER_EN_MASK (1 << 9) +#define CFG_PBPR_SYNC_OFF_SHIFT 16 +#define CFG_PBPR_SYNC_OFF_MASK (0x7FF << CFG_PBPR_SYNC_OFF_SHIFT) +#define CFG_PBPR_SYNC_OFF_VAL 0x117 /* Voltage dependent. stiH416 */ + +/* Default scaling values */ +#define SCALE_CTRL_Y_DFLT 0x00C50256 +#define SCALE_CTRL_CB_DFLT 0x00DB0249 +#define SCALE_CTRL_CR_DFLT 0x00DB0249 + +/* Video DACs control */ +#define VIDEO_DACS_CONTROL_MASK 0x0FFF +#define VIDEO_DACS_CONTROL_SYSCFG2535 0x085C /* for stih416 */ +#define DAC_CFG_HD_OFF_SHIFT 5 +#define DAC_CFG_HD_OFF_MASK (0x7 << DAC_CFG_HD_OFF_SHIFT) +#define VIDEO_DACS_CONTROL_SYSCFG5072 0x0120 /* for stih407 */ +#define DAC_CFG_HD_HZUVW_OFF_SHIFT 1 +#define DAC_CFG_HD_HZUVW_OFF_MASK (0x1 << DAC_CFG_HD_HZUVW_OFF_SHIFT) + + +/* Upsampler values for the alternative 2X Filter */ +#define SAMPLER_COEF_NB 8 +#define HDA_ANA_SRC_Y_CFG_ALT_2X 0x01130000 +static u32 coef_y_alt_2x[] = { + 0x00FE83FB, 0x1F900401, 0x00000000, 0x00000000, + 0x00F408F9, 0x055F7C25, 0x00000000, 0x00000000 +}; + +#define HDA_ANA_SRC_C_CFG_ALT_2X 0x01750004 +static u32 coef_c_alt_2x[] = { + 0x001305F7, 0x05274BD0, 0x00000000, 0x00000000, + 0x0004907C, 0x09C80B9D, 0x00000000, 0x00000000 +}; + +/* Upsampler values for the 4X Filter */ +#define HDA_ANA_SRC_Y_CFG_4X 0x01ED0005 +#define HDA_ANA_SRC_C_CFG_4X 0x01ED0004 +static u32 coef_yc_4x[] = { + 0x00FC827F, 0x008FE20B, 0x00F684FC, 0x050F7C24, + 0x00F4857C, 0x0A1F402E, 0x00FA027F, 0x0E076E1D +}; + +/* AWG instructions for some video modes */ +#define AWG_MAX_INST 64 + +/* 720p@50 */ +static u32 AWGi_720p_50[] = { + 0x00000971, 0x00000C26, 0x0000013B, 0x00000CDA, + 0x00000104, 0x00000E7E, 0x00000E7F, 0x0000013B, + 0x00000D8E, 0x00000104, 0x00001804, 0x00000971, + 0x00000C26, 0x0000003B, 0x00000FB4, 0x00000FB5, + 0x00000104, 0x00001AE8 +}; + +#define NN_720p_50 ARRAY_SIZE(AWGi_720p_50) + +/* 720p@60 */ +static u32 AWGi_720p_60[] = { + 0x00000971, 0x00000C26, 0x0000013B, 0x00000CDA, + 0x00000104, 0x00000E7E, 0x00000E7F, 0x0000013B, + 0x00000C44, 0x00000104, 0x00001804, 0x00000971, + 0x00000C26, 0x0000003B, 0x00000F0F, 0x00000F10, + 0x00000104, 0x00001AE8 +}; + +#define NN_720p_60 ARRAY_SIZE(AWGi_720p_60) + +/* 1080p@30 */ +static u32 AWGi_1080p_30[] = { + 0x00000971, 0x00000C2A, 0x0000013B, 0x00000C56, + 0x00000104, 0x00000FDC, 0x00000FDD, 0x0000013B, + 0x00000C2A, 0x00000104, 0x00001804, 0x00000971, + 0x00000C2A, 0x0000003B, 0x00000EBE, 0x00000EBF, + 0x00000EBF, 0x00000104, 0x00001A2F, 0x00001C4B, + 0x00001C52 +}; + +#define NN_1080p_30 ARRAY_SIZE(AWGi_1080p_30) + +/* 1080p@25 */ +static u32 AWGi_1080p_25[] = { + 0x00000971, 0x00000C2A, 0x0000013B, 0x00000C56, + 0x00000104, 0x00000FDC, 0x00000FDD, 0x0000013B, + 0x00000DE2, 0x00000104, 0x00001804, 0x00000971, + 0x00000C2A, 0x0000003B, 0x00000F51, 0x00000F51, + 0x00000F52, 0x00000104, 0x00001A2F, 0x00001C4B, + 0x00001C52 +}; + +#define NN_1080p_25 ARRAY_SIZE(AWGi_1080p_25) + +/* 1080p@24 */ +static u32 AWGi_1080p_24[] = { + 0x00000971, 0x00000C2A, 0x0000013B, 0x00000C56, + 0x00000104, 0x00000FDC, 0x00000FDD, 0x0000013B, + 0x00000E50, 0x00000104, 0x00001804, 0x00000971, + 0x00000C2A, 0x0000003B, 0x00000F76, 0x00000F76, + 0x00000F76, 0x00000104, 0x00001A2F, 0x00001C4B, + 0x00001C52 +}; + +#define NN_1080p_24 ARRAY_SIZE(AWGi_1080p_24) + +/* 720x480p@60 */ +static u32 AWGi_720x480p_60[] = { + 0x00000904, 0x00000F18, 0x0000013B, 0x00001805, + 0x00000904, 0x00000C3D, 0x0000003B, 0x00001A06 +}; + +#define NN_720x480p_60 ARRAY_SIZE(AWGi_720x480p_60) + +/* Video mode category */ +enum sti_hda_vid_cat { + VID_SD, + VID_ED, + VID_HD_74M, + VID_HD_148M +}; + +struct sti_hda_video_config { + struct drm_display_mode mode; + u32 *awg_instr; + int nb_instr; + enum sti_hda_vid_cat vid_cat; +}; + +/* HD analog supported modes */ +/* Interlaced modes may be added when supported by the whole display chain */ +static const struct sti_hda_video_config hda_supported_modes[] = { + /* 1080p30 74.250Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_30, NN_1080p_30, VID_HD_74M}, + /* 1080p30 74.176Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74176, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_30, NN_1080p_30, VID_HD_74M}, + /* 1080p24 74.250Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2558, + 2602, 2750, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_24, NN_1080p_24, VID_HD_74M}, + /* 1080p24 74.176Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74176, 1920, 2558, + 2602, 2750, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_24, NN_1080p_24, VID_HD_74M}, + /* 1080p25 74.250Mhz */ + {{DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448, + 2492, 2640, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_1080p_25, NN_1080p_25, VID_HD_74M}, + /* 720p60 74.250Mhz */ + {{DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390, + 1430, 1650, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_720p_60, NN_720p_60, VID_HD_74M}, + /* 720p60 74.176Mhz */ + {{DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74176, 1280, 1390, + 1430, 1650, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_720p_60, NN_720p_60, VID_HD_74M}, + /* 720p50 74.250Mhz */ + {{DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1720, + 1760, 1980, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC)}, + AWGi_720p_50, NN_720p_50, VID_HD_74M}, + /* 720x480p60 27.027Mhz */ + {{DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27027, 720, 736, + 798, 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC)}, + AWGi_720x480p_60, NN_720x480p_60, VID_ED}, + /* 720x480p60 27.000Mhz */ + {{DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, + 798, 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC)}, + AWGi_720x480p_60, NN_720x480p_60, VID_ED} +}; + +/* Index (in supported modes table) of the preferred video mode */ +#define HDA_PREF_MODE_IDX 0 + +/* + * STI hd analog structure + * + * @dev: driver device + * @drm_dev: pointer to drm device + * @mode: current display mode selected + * @regs: HD analog register + * @video_dacs_ctrl: video DACS control register + * @enabled: true if HD analog is enabled else false + */ +struct sti_hda { + struct device dev; + struct drm_device *drm_dev; + struct drm_display_mode mode; + void __iomem *regs; + void __iomem *video_dacs_ctrl; + struct clk *clk_pix; + struct clk *clk_hddac; + bool enabled; +}; + +/* Reference to the hda device */ +struct device *hda_dev; + +/* + * Helper to write bit field + * + * @addr: register to update + * @val: value to write + * @mask: bit field mask to use + */ +static inline void hda_reg_writemask(void __iomem *addr, u32 val, u32 mask) +{ + u32 old = readl(addr); + + val = (val & mask) | (old & ~mask); + writel(val, addr); +} + +/* Search for a video mode in the supported modes table + * + * @mode: mode being searched + * @idx: index of the found mode + * + * Return true if mode is found + */ +static bool hda_get_mode_idx(struct drm_display_mode mode, int *idx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(hda_supported_modes); i++) + if (drm_mode_equal(&hda_supported_modes[i].mode, &mode)) { + *idx = i; + return true; + } + return false; +} + +/* Enable the HD DACS + * + * @hda: pointer to HD analog structure + * @enable: true if HD DACS need to be enabled, else false + */ +static void hda_enable_hd_dacs(struct sti_hda *hda, bool enable) +{ + u32 mask; + + if (hda->video_dacs_ctrl) { + switch ((u32)hda->video_dacs_ctrl & VIDEO_DACS_CONTROL_MASK) { + case VIDEO_DACS_CONTROL_SYSCFG2535: + mask = DAC_CFG_HD_OFF_MASK; + break; + case VIDEO_DACS_CONTROL_SYSCFG5072: + mask = DAC_CFG_HD_HZUVW_OFF_MASK; + break; + default: + DRM_INFO("Video DACS control register not supported!"); + return; + } + + if (enable) + hda_reg_writemask(hda->video_dacs_ctrl, 0x000000, mask); + else + hda_reg_writemask(hda->video_dacs_ctrl, 0xFFFFFF, mask); + } + +} + +/* Configure AWG, writing instructions + * + * @hda: pointer to HD analog structure + * @awg_instr: pointer to AWG instructions table + * @nb: nb of AWG instructions + */ +static void sti_hda_configure_awg(struct sti_hda *hda, u32 *awg_instr, int nb) +{ + int i; + + DRM_DEBUG_DRIVER("\n"); + + for (i = 0; i < nb; i++) + writel(awg_instr[i], hda->regs + HDA_SYNC_AWGI + i * 4); + for (i = nb; i < AWG_MAX_INST; i++) + writel(0, hda->regs + HDA_SYNC_AWGI + i * 4); +} + +/* + * Get modes + * + * @drm_connector: pointer on the drm connector + * + * Return number of modes + */ +static int sti_hda_get_modes(struct drm_connector *drm_connector) +{ + struct drm_device *dev = drm_connector->dev; + struct drm_display_mode *mode; + int i, count; + + DRM_DEBUG_DRIVER("\n"); + + for (i = 0, count = 0; i < ARRAY_SIZE(hda_supported_modes); i++) { + mode = drm_mode_duplicate(dev, &hda_supported_modes[i].mode); + if (!mode) + continue; + mode->vrefresh = drm_mode_vrefresh(mode); + + /* Set the preferred mode */ + if (i == HDA_PREF_MODE_IDX) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_probed_add(drm_connector, mode); + count++; + } + + drm_mode_sort(&drm_connector->modes); + + return count; +} + + +static int sti_hda_bind(struct device *dev, struct device *master, void *data) +{ + 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) { + DRM_ERROR("Failed to allocate memory for HD analog\n"); + return -ENOMEM; + } + + hda->dev = pdev->dev; + + /* Get resources */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hda-reg"); + if (!res) { + DRM_ERROR("Invalid hda resource\n"); + return -ENOMEM; + } + hda->regs = devm_ioremap_nocache(dev, res->start, resource_size(res)); + if (IS_ERR(hda->regs)) + return PTR_ERR(hda->regs); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "video-dacs-ctrl"); + if (res) { + hda->video_dacs_ctrl = devm_ioremap_nocache(dev, res->start, + resource_size(res)); + if (IS_ERR(hda->video_dacs_ctrl)) + return PTR_ERR(hda->video_dacs_ctrl); + } else { + /* If no existing video-dacs-ctrl resource continue the probe */ + DRM_DEBUG_DRIVER("No video-dacs-ctrl resource\n"); + hda->video_dacs_ctrl = NULL; + } + + /* Get clock resources */ + hda->clk_pix = devm_clk_get(dev, "hda_pix"); + if (IS_ERR(hda->clk_pix)) { + DRM_ERROR("Cannot get hda_pix clock\n"); + return PTR_ERR(hda->clk_pix); + } + + hda->clk_hddac = devm_clk_get(dev, "hda_hddac"); + if (IS_ERR(hda->clk_hddac)) { + DRM_ERROR("Cannot get hda_hddac clock\n"); + return PTR_ERR(hda->clk_hddac); + } + + hda_dev = &hda->dev; + + platform_set_drvdata(pdev, hda); + + return component_add(&pdev->dev, &sti_hda_ops); +} + +static int sti_hda_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sti_hda_ops); + return 0; +} + +static struct of_device_id hda_match_types[] = { + { + .compatible = "st,stih416-hda", + }, + { + .compatible = "st,stih407-hda", + }, + { /* end node */ } +}; +MODULE_DEVICE_TABLE(of, hda_match_types); + +struct platform_driver sti_hda_driver = { + .driver = { + .name = "sti-hda", + .owner = THIS_MODULE, + .of_match_table = hda_match_types, + }, + .probe = sti_hda_probe, + .remove = sti_hda_remove, +}; + +module_platform_driver(sti_hda_driver); + +MODULE_LICENSE("GPL");
On Tue, May 20, 2014 at 03:56:15PM +0200, Benjamin Gaignard wrote:
Add driver to support analog TV ouput.
Unfortunate that this has the same abbreviation as High-Definition Audio...
diff --git a/drivers/gpu/drm/sti/sti_hda.c b/drivers/gpu/drm/sti/sti_hda.c
[...]
+/* 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 */
syscfg is starting to look more and more like it could be a syscon driver or some other specialized, platform-specific driver.
+/* Index (in supported modes table) of the preferred video mode */ +#define HDA_PREF_MODE_IDX 0
I don't think this symbolic name is particularly useful.
+/* Reference to the hda device */ +struct device *hda_dev;
This shouldn't be needed.
+static void sti_hda_configure_awg(struct sti_hda *hda, u32 *awg_instr, int nb) +{
- int i;
unsigned
+/*
- Get modes
- @drm_connector: pointer on the drm connector
- Return number of modes
- */
+static int sti_hda_get_modes(struct drm_connector *drm_connector) +{
- struct drm_device *dev = drm_connector->dev;
- struct drm_display_mode *mode;
- int i, count;
unsigned for i. count should probably stay signed since DRM uses that throughout.
- DRM_DEBUG_DRIVER("\n");
- for (i = 0, count = 0; i < ARRAY_SIZE(hda_supported_modes); i++) {
You can initialize count when it's declared to make the loop more idiomatic.
mode = drm_mode_duplicate(dev, &hda_supported_modes[i].mode);
if (!mode)
continue;
mode->vrefresh = drm_mode_vrefresh(mode);
/* Set the preferred mode */
if (i == HDA_PREF_MODE_IDX)
mode->type |= DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(drm_connector, mode);
count++;
- }
- drm_mode_sort(&drm_connector->modes);
- return count;
+}
Gratuituous newline.
+static int sti_hda_probe(struct platform_device *pdev) +{
[...]
+}
+static int sti_hda_remove(struct platform_device *pdev) +{
[...]
+}
+static struct of_device_id hda_match_types[] = {
- {
.compatible = "st,stih416-hda",
},
- {
.compatible = "st,stih407-hda",
},
- { /* end node */ }
+}; +MODULE_DEVICE_TABLE(of, hda_match_types);
+struct platform_driver sti_hda_driver = {
- .driver = {
.name = "sti-hda",
.owner = THIS_MODULE,
.of_match_table = hda_match_types,
},
- .probe = sti_hda_probe,
- .remove = sti_hda_remove,
+};
+module_platform_driver(sti_hda_driver);
+MODULE_LICENSE("GPL");
Same comments here as for all previous patches.
Thierry
TVout hardware block is responsible to dispatch the data flow coming from compositor block to any of the output (HDMI or Analog TV). It control when output are start/stop and configure according the require flow path.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Makefile | 3 +- drivers/gpu/drm/sti/sti_hda.c | 372 +++++++++++++++++++++ drivers/gpu/drm/sti/sti_hda.h | 14 + drivers/gpu/drm/sti/sti_hdmi.c | 542 +++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi.h | 3 + drivers/gpu/drm/sti/sti_tvout.c | 702 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_tvout.h | 105 ++++++ 7 files changed, 1740 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/sti/sti_hda.h create mode 100644 drivers/gpu/drm/sti/sti_tvout.c create mode 100644 drivers/gpu/drm/sti/sti_tvout.h
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index aa69ea0..45536c3 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -1,6 +1,7 @@ ccflags-y := -Iinclude/drm
-stidrm-y := sti_hdmi.o \ +stidrm-y := sti_tvout.o \ + sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ diff --git a/drivers/gpu/drm/sti/sti_hda.c b/drivers/gpu/drm/sti/sti_hda.c index 315aa0e..dc55ed7 100644 --- a/drivers/gpu/drm/sti/sti_hda.c +++ b/drivers/gpu/drm/sti/sti_hda.c @@ -11,6 +11,8 @@
#include <drm/drmP.h>
+#include "sti_hda.h" + /* HDformatter registers */ #define HDA_ANA_CFG 0x0000 #define HDA_ANA_SCALE_CTRL_Y 0x0004 @@ -372,6 +374,376 @@ static int sti_hda_get_modes(struct drm_connector *drm_connector) return count; }
+/* + * Start hd analog + * + * @connector: pointer on the tvout HD analog connector + * + * Return 0 on success + */ +static int sti_hda_start(struct sti_tvout_connector *connector) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + u32 val, i, mode_idx; + u32 src_filter_y, src_filter_c; + u32 *coef_y, *coef_c; + u32 filter_mode; + + DRM_DEBUG_DRIVER("\n"); + + /* Prepare/enable clocks */ + if (clk_prepare_enable(hda->clk_pix)) + DRM_ERROR("Failed to prepare/enable hda_pix clk\n"); + if (clk_prepare_enable(hda->clk_hddac)) + DRM_ERROR("Failed to prepare/enable hda_hddac clk\n"); + + hda->enabled = true; + + if (!hda_get_mode_idx(hda->mode, &mode_idx)) { + DRM_ERROR("Undefined mode\n"); + return 1; + } + + switch (hda_supported_modes[mode_idx].vid_cat) { + case VID_HD_148M: + DRM_ERROR("Beyond HD analog capabilities\n"); + return 1; + case VID_HD_74M: + /* HD use alternate 2x filter */ + filter_mode = CFG_AWG_FLTR_MODE_HD; + src_filter_y = HDA_ANA_SRC_Y_CFG_ALT_2X; + src_filter_c = HDA_ANA_SRC_C_CFG_ALT_2X; + coef_y = coef_y_alt_2x; + coef_c = coef_c_alt_2x; + break; + case VID_ED: + /* ED uses 4x filter */ + filter_mode = CFG_AWG_FLTR_MODE_ED; + src_filter_y = HDA_ANA_SRC_Y_CFG_4X; + src_filter_c = HDA_ANA_SRC_C_CFG_4X; + coef_y = coef_yc_4x; + coef_c = coef_yc_4x; + break; + case VID_SD: + DRM_ERROR("Not supported\n"); + return 1; + default: + DRM_ERROR("Undefined resolution\n"); + return 1; + } + DRM_DEBUG_DRIVER("Using HDA mode #%d\n", mode_idx); + + /* Enable HD Video DACs */ + hda_enable_hd_dacs(hda, true); + + /* Configure scaler */ + writel(SCALE_CTRL_Y_DFLT, hda->regs + HDA_ANA_SCALE_CTRL_Y); + writel(SCALE_CTRL_CB_DFLT, hda->regs + HDA_ANA_SCALE_CTRL_CB); + writel(SCALE_CTRL_CR_DFLT, hda->regs + HDA_ANA_SCALE_CTRL_CR); + + /* Configure sampler */ + writel(src_filter_y, hda->regs + HDA_ANA_SRC_Y_CFG); + writel(src_filter_c, hda->regs + HDA_ANA_SRC_C_CFG); + for (i = 0; i < SAMPLER_COEF_NB; i++) { + writel(coef_y[i], hda->regs + HDA_COEFF_Y_PH1_TAP123 + i * 4); + writel(coef_c[i], hda->regs + HDA_COEFF_C_PH1_TAP123 + i * 4); + } + + /* Configure main HDFormatter */ + val = 0; + val |= (hda->mode.flags & DRM_MODE_FLAG_INTERLACE) ? + 0 : CFG_AWG_ASYNC_VSYNC_MTD; + val |= (CFG_PBPR_SYNC_OFF_VAL << CFG_PBPR_SYNC_OFF_SHIFT); + val |= filter_mode; + writel(val, hda->regs + HDA_ANA_CFG); + + /* Configure AWG */ + sti_hda_configure_awg(hda, hda_supported_modes[mode_idx].awg_instr, + hda_supported_modes[mode_idx].nb_instr); + + /* Enable AWG */ + hda_reg_writemask(hda->regs + HDA_ANA_CFG, 1, CFG_AWG_ASYNC_EN); + + return 0; +} + +/* + * Stop HD analog + * + * @connector: pointer on the tvout HD analog connector + */ +static void sti_hda_stop(struct sti_tvout_connector *connector) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + + if (!hda->enabled) + return; + + DRM_DEBUG_DRIVER("\n"); + + /* Disable HD DAC and AWG */ + hda_reg_writemask(hda->regs + HDA_ANA_CFG, 0, CFG_AWG_ASYNC_EN); + hda_enable_hd_dacs(hda, false); + + /* Disable/unprepare hda clock */ + clk_disable_unprepare(hda->clk_hddac); + clk_disable_unprepare(hda->clk_pix); + + hda->enabled = false; +} + +/* + * Check if the drm display mode in supported by the HD analog + * + * @connector: pointer on the tvout HD analog connector + * @mode: drm display mode + * + * Return 0 if supported + */ +#define CLK_TOLERANCE_HZ 50 +static int sti_hda_check_mode(struct sti_tvout_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + int target = mode->clock * 1000; + int target_min = target - CLK_TOLERANCE_HZ; + int target_max = target + CLK_TOLERANCE_HZ; + int result; + int idx; + + if (!hda_get_mode_idx(*mode, &idx)) { + return 1; + } else { + result = clk_round_rate(hda->clk_pix, target); + + DRM_DEBUG_DRIVER("target rate = %d => available rate = %d\n", + target, result); + + if ((result < target_min) || (result > target_max)) { + DRM_DEBUG_DRIVER("hda pixclk=%d not supported\n", + target); + return 1; + } + } + + return 0; +} + +/* + * Set the drm display mode in the local structure + * + * @connector: pointer on the tvout HD analog connector + * @mode: drm display mode + * + * Return 0 on success + */ +static int sti_hda_set_mode(struct sti_tvout_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + u32 mode_idx; + int hddac_rate; + int ret; + + DRM_DEBUG_DRIVER("\n"); + + memcpy(&hda->mode, mode, sizeof(struct drm_display_mode)); + + if (!hda_get_mode_idx(hda->mode, &mode_idx)) { + DRM_ERROR("Undefined mode\n"); + return 1; + } + + switch (hda_supported_modes[mode_idx].vid_cat) { + case VID_HD_74M: + /* HD use alternate 2x filter */ + hddac_rate = mode->clock * 1000 * 2; + break; + case VID_ED: + /* ED uses 4x filter */ + hddac_rate = mode->clock * 1000 * 4; + break; + default: + DRM_ERROR("Undefined mode\n"); + return 1; + } + + /* HD DAC = 148.5Mhz or 108 Mhz */ + ret = clk_set_rate(hda->clk_hddac, hddac_rate); + if (ret < 0) { + DRM_ERROR("Cannot set rate (%dHz) for hda_hddac clk\n", + hddac_rate); + return ret; + } + + /* HDformatter clock = compositor clock */ + ret = clk_set_rate(hda->clk_pix, mode->clock * 1000); + if (ret < 0) { + DRM_ERROR("Cannot set rate (%dHz) for hda_pix clk\n", + mode->clock * 1000); + return ret; + } + + return 0; +} + +/* + * Detect if HD analog is connected + * + * @connector: pointer on the tvout HD analog connector + * + * Return true if HD analog cable is connected which is assumed to be always + * the case + */ +static bool sti_hda_detect(struct sti_tvout_connector *connector) +{ + DRM_DEBUG_DRIVER("\n"); + + return true; +} + +/* + * Check if HD analog is enabled + * + * @connector: pointer on the tvout HD analog connector + * + * Return true if HD analog is enabled + */ +static bool sti_hda_is_enabled(struct sti_tvout_connector *connector) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + + return hda->enabled; +} + +/* + * Prepare/configure HD analog + * + * @connector: pointer on the tvout HD analog connector + */ +static void sti_hda_prepare(struct sti_tvout_connector *connector) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + + DRM_DEBUG_DRIVER("\n"); + + /* reset HDF */ + writel(0x00000000, hda->regs + HDA_ANA_CFG); + writel(0x00000000, hda->regs + HDA_ANA_ANC_CTRL); +} + +/* + * Debugfs + */ + +#define HDA_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \ + readl(hda->regs + reg)) + +static void hda_dbg_cfg(struct seq_file *m, int val) +{ + seq_puts(m, "\t AWG "); + seq_puts(m, val & CFG_AWG_ASYNC_EN ? "enabled" : "disabled"); +} + +static void hda_dbg_awgi(struct seq_file *m, void __iomem *reg) +{ + int i; + + seq_puts(m, "\n HDA_SYNC_AWGI "); + for (i = 0; i < 10; i++) + seq_printf(m, "%04X ", readl(reg + i * 4)); + seq_puts(m, "..."); +} + +static void hda_dbg_video_dacs_ctrl(struct seq_file *m, void __iomem *reg) +{ + u32 val = readl(reg); + u32 mask; + + switch ((u32)reg & VIDEO_DACS_CONTROL_MASK) { + case VIDEO_DACS_CONTROL_SYSCFG2535: + mask = DAC_CFG_HD_OFF_MASK; + break; + case VIDEO_DACS_CONTROL_SYSCFG5072: + mask = DAC_CFG_HD_HZUVW_OFF_MASK; + break; + default: + DRM_INFO("Video DACS control register not supported!"); + return; + } + + seq_puts(m, "\n"); + + seq_printf(m, "\n %-25s 0x%08X", "VIDEO_DACS_CONTROL", val); + seq_puts(m, "\tHD DACs "); + seq_puts(m, val & mask ? "disabled" : "enabled"); +} + +static void sti_hda_dbg_show(struct sti_tvout_connector *connector, + struct seq_file *m) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + + seq_puts(m, "\n"); + seq_printf(m, "\nHD Analog: (virt base addr = 0x%p)", hda->regs); + HDA_DBG_DUMP(HDA_ANA_CFG); + hda_dbg_cfg(m, readl(hda->regs + HDA_ANA_CFG)); + HDA_DBG_DUMP(HDA_ANA_SCALE_CTRL_Y); + HDA_DBG_DUMP(HDA_ANA_SCALE_CTRL_CB); + HDA_DBG_DUMP(HDA_ANA_SCALE_CTRL_CR); + HDA_DBG_DUMP(HDA_ANA_ANC_CTRL); + HDA_DBG_DUMP(HDA_ANA_SRC_Y_CFG); + HDA_DBG_DUMP(HDA_ANA_SRC_C_CFG); + hda_dbg_awgi(m, hda->regs + HDA_SYNC_AWGI); + if (hda->video_dacs_ctrl) + hda_dbg_video_dacs_ctrl(m, hda->video_dacs_ctrl); +} + +/* + * create the HD analog output + * + * @tvout: pointer on the tvout information + * + * Return pointer on the created tvout connector or NULL if error occurs + */ +struct sti_tvout_connector *sti_hda_create(struct sti_tvout *tvout) +{ + struct sti_hda *hda = container_of(hda_dev, struct sti_hda, dev); + struct device *dev = &hda->dev; + struct sti_tvout_connector *connector; + + DRM_DEBUG_DRIVER("\n"); + + if (!hda) { + DRM_INFO("%s: No hda device probed\n", __func__); + return NULL; + } + + connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL); + if (!connector) { + DRM_ERROR("Failed to allocate memory for connector\n"); + goto connector_alloc_failed; + } + + /* Set the drm device handle */ + hda->drm_dev = tvout->drm_dev; + + connector->priv = (void *)hda; + connector->start = sti_hda_start; + connector->stop = sti_hda_stop; + connector->get_modes = sti_hda_get_modes; + connector->check_mode = sti_hda_check_mode; + connector->set_mode = sti_hda_set_mode; + connector->detect = sti_hda_detect; + connector->is_enabled = sti_hda_is_enabled; + connector->prepare = sti_hda_prepare; + connector->dbg_show = sti_hda_dbg_show; + + return connector; + +connector_alloc_failed: + return NULL; +}
static int sti_hda_bind(struct device *dev, struct device *master, void *data) { diff --git a/drivers/gpu/drm/sti/sti_hda.h b/drivers/gpu/drm/sti/sti_hda.h new file mode 100644 index 0000000..e88b30e --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hda.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Fabien Dessenne fabien.dessenne@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_HDA_H_ +#define _STI_HDA_H_ + +#include "sti_tvout.h" + +struct sti_tvout_connector *sti_hda_create(struct sti_tvout *tvout); + +#endif diff --git a/drivers/gpu/drm/sti/sti_hdmi.c b/drivers/gpu/drm/sti/sti_hdmi.c index 02b0524..1cb79d5 100644 --- a/drivers/gpu/drm/sti/sti_hdmi.c +++ b/drivers/gpu/drm/sti/sti_hdmi.c @@ -380,6 +380,548 @@ fail: return -1; }
+/* + * Start hdmi + * + * @connector: pointer on the tvout hdmi connector + * + * Return -1 if error occurs + */ +static int sti_hdmi_start(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + int ret = 0; + + DRM_DEBUG_DRIVER("\n"); + + /* Prepare/enable clocks */ + if (clk_prepare_enable(hdmi->clk_pix)) + DRM_ERROR("Failed to prepare/enable hdmi_pix clk\n"); + if (clk_prepare_enable(hdmi->clk_tmds)) + DRM_ERROR("Failed to prepare/enable hdmi_tmds clk\n"); + if (clk_prepare_enable(hdmi->clk_phy)) + DRM_ERROR("Failed to prepare/enable hdmi_rejec_pll clk\n"); + + hdmi->enabled = true; + + /* Program hdmi serializer and start phy */ + ret = hdmi_phy_start(hdmi); + if (ret) { + DRM_ERROR("Unable to start hdmi phy\n"); + return ret; + } + + /* Program hdmi active area */ + hdmi_active_area(hdmi); + + /* Enable working interrupts */ + writel(HDMI_WORKING_INT, hdmi->regs + HDMI_INT_EN); + + /* Program hdmi config */ + hdmi_config(hdmi); + + /* Program AVI infoframe */ + ret = hdmi_avi_infoframe_config(hdmi); + if (ret) + DRM_ERROR("Unable to configure AVI infoframe\n"); + + /* Sw reset */ + ret = hdmi_swreset(hdmi); + if (ret) + DRM_ERROR("Unable to perform the hdmi sw reset\n"); + + return ret; +} + +/* + * Stop hdmi + * + * @connector: pointer on the tvout hdmi connector + */ +static void sti_hdmi_stop(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + u32 val; + u32 mask; + + if (!hdmi->enabled) + return; + + DRM_DEBUG_DRIVER("\n"); + + /* Disable HDMI */ + mask = HDMI_CFG_DEVICE_EN; + val = ~HDMI_CFG_DEVICE_EN; + + hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask); + + /* Stop the phy */ + hdmi_phy_stop(hdmi); + + /* Disable/unprepare hdmi clock */ + clk_disable_unprepare(hdmi->clk_phy); + clk_disable_unprepare(hdmi->clk_tmds); + clk_disable_unprepare(hdmi->clk_pix); + + hdmi->enabled = false; +} + +/* + * Check if the drm display mode in supported by the hdmi + * + * @connector: pointer on the tvout hdmi connector + * @mode: drm display mode + * + * Return -1 if not supported + */ +#define CLK_TOLERANCE_HZ 50 +static int sti_hdmi_check_mode(struct sti_tvout_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + int target = mode->clock * 1000; + int target_min = target - CLK_TOLERANCE_HZ; + int target_max = target + CLK_TOLERANCE_HZ; + int result; + + result = clk_round_rate(hdmi->clk_pix, target); + + DRM_DEBUG_DRIVER("target rate = %d => available rate = %d\n", + target, result); + + if ((result < target_min) || (result > target_max)) { + DRM_DEBUG_DRIVER("hdmi pixclk=%d not supported\n", target); + return -1; + } + + return 0; +} + +/* + * Set the drm display mode in the local structure + * + * @connector: pointer on the tvout hdmi connector + * @mode: drm display mode + * + * Return -1 if error occurs + */ +/* FS bits */ +static int sti_hdmi_set_mode(struct sti_tvout_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + int ret; + + DRM_DEBUG_DRIVER("\n"); + + /* Copy the drm display mode in the connector local structure */ + memcpy(&hdmi->mode, mode, sizeof(struct drm_display_mode)); + + /* Update clock framerate according to the selected mode */ + ret = clk_set_rate(hdmi->clk_pix, mode->clock * 1000); + if (ret < 0) { + DRM_ERROR("Cannot set rate (%dHz) for hdmi_pix clk\n", + mode->clock * 1000); + return ret; + } + ret = clk_set_rate(hdmi->clk_phy, mode->clock * 1000); + if (ret < 0) { + DRM_ERROR("Cannot set rate (%dHz) for hdmi_rejection_pll clk\n", + mode->clock * 1000); + return ret; + } + + return 0; +} + +/* + * Detect if hdmi is connected + * + * @connector: pointer on the tvout hdmi connector + * + * Return true if hdmi cable is connected + */ +static bool sti_hdmi_detect(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + + DRM_DEBUG_DRIVER("\n"); + + if (hdmi->hpd) { + DRM_DEBUG_DRIVER("hdmi cable connected\n"); + return true; + } else + DRM_DEBUG_DRIVER("hdmi cable disconnected\n"); + + return false; +} + +/* + * Check if hdmi is enabled + * + * @connector: pointer on the tvout hdmi connector + * + * Return true if hdmi is enabled + */ +static bool sti_hdmi_is_enabled(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + + return hdmi->enabled; +} + +/* + * Prepare/configure hdmi + * + * @connector: pointer on the tvout hdmi connector + */ +static void sti_hdmi_prepare(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + + DRM_DEBUG_DRIVER("\n"); + + /* HDMI initialisation */ + writel(0x00000000, hdmi->regs + HDMI_CFG); + writel(0xffffffff, hdmi->regs + HDMI_INT_CLR); + + /* Ensure the PHY is completely powered down */ + hdmi_phy_stop(hdmi); + + /* Set the default channel data to be a dark red */ + writel(0x0000, hdmi->regs + HDMI_DFLT_CHL0_DAT); + writel(0x0000, hdmi->regs + HDMI_DFLT_CHL1_DAT); + writel(0x0060, hdmi->regs + HDMI_DFLT_CHL2_DAT); +} + +/* + * Debugfs + */ +#define HDMI_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \ + readl(hdmi->regs + reg)) +#define HDMI_DBG_DUMP_DI(reg, slot) HDMI_DBG_DUMP(reg(slot)) +#define MAX_STRING_LENGTH 40 + +static void hdmi_dbg_cfg(struct seq_file *m, int val) +{ + int tmp; + char str[MAX_STRING_LENGTH]; + static const char *const mode[] = { "DVI", "HDMI" }; + static const char *const enable[] = { "disable", "enable" }; + static const char *const oess_ess[] = { "OESS enable", "ESS enable" }; + static const char *const polarity[] = { "normal", "inverted" }; + + seq_puts(m, "\t"); + + tmp = (val & HDMI_CFG_HDMI_NOT_DVI) >> HDMI_CFG_HDMI_NOT_DVI_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "mode: %s", mode[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_CFG_HDCP_EN) >> HDMI_CFG_HDCP_EN_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "HDCP: %s", enable[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_CFG_ESS_NOT_OESS) >> HDMI_CFG_ESS_NOT_OESS_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "HDCP mode: %s", oess_ess[tmp]); + seq_printf(m, "%-40s", str); + + seq_printf(m, "\n%-40s", ""); + + tmp = (val & HDMI_CFG_SINK_TERM_DET_EN) >> + HDMI_CFG_SINK_TERM_DET_EN_SHIFT; + snprintf(str, MAX_STRING_LENGTH, + "Sink term detection: %s", enable[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_CFG_H_SYNC_POL_NEG) >> HDMI_CFG_H_SYNC_POL_NEG_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "Hsync polarity: %s", polarity[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_CFG_V_SYNC_POL_NEG) >> HDMI_CFG_V_SYNC_POL_NEG_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "Vsync polarity: %s", polarity[tmp]); + seq_printf(m, "%-40s", str); + + seq_printf(m, "\n%-40s", ""); + + tmp = (val & HDMI_CFG_422_EN) >> HDMI_CFG_422_EN_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "YUV422 format: %s", enable[tmp]); + seq_printf(m, "%-40s", str); +} + +static void hdmi_dbg_sta(struct seq_file *m, int val) +{ + int tmp; + char str[MAX_STRING_LENGTH]; + static const char *const sink_term[] = { "not present", "present" }; + static const char *const pll_lck[] = { "not locked", "locked" }; + static const char *const hot_plug[] = { "not connected", "connected" }; + + seq_puts(m, "\t"); + + tmp = (val & HDMI_STA_FIFO_SAMPLES) >> HDMI_STA_FIFO_SAMPLES_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "fifo: %d samples", tmp); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_STA_SINK_TERM) >> HDMI_STA_SINK_TERM_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "sink term: %s", sink_term[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_STA_DLL_LCK) >> HDMI_STA_DLL_LCK_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "pll: %s", pll_lck[tmp]); + seq_printf(m, "%-40s", str); + + seq_printf(m, "\n%-40s", ""); + + tmp = (val & HDMI_STA_HOT_PLUG) >> HDMI_STA_HOT_PLUG_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "hdmi cable: %s", hot_plug[tmp]); + seq_printf(m, "%-40s", str); +} + +static void hdmi_dbg_sw_di_cfg(struct seq_file *m, int val) +{ + int tmp; + char str[MAX_STRING_LENGTH]; + static const char *const en_di[] = { "no transmission", + "single transmission", "once every field", "once every frame" + }; + + seq_puts(m, "\t"); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 1)); + snprintf(str, MAX_STRING_LENGTH, "Data island 1: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 2)) >> 4; + snprintf(str, MAX_STRING_LENGTH, "Data island 2: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 3)) >> 8; + snprintf(str, MAX_STRING_LENGTH, "Data island 3: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + seq_printf(m, "\n%-40s", ""); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 4)) >> 12; + snprintf(str, MAX_STRING_LENGTH, "Data island 4: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 5)) >> 16; + snprintf(str, MAX_STRING_LENGTH, "Data island 5: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 6)) >> 20; + snprintf(str, MAX_STRING_LENGTH, "Data island 6: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); +} + +static void hdmi_dbg_xmin(struct seq_file *m, int val) +{ + seq_printf(m, "\tXmin: %4d", val & 0x0FFF); +} + +static void hdmi_dbg_xmax(struct seq_file *m, int val) +{ + seq_printf(m, "\tXmax: %4d", val & 0x0FFF); +} + +static void hdmi_dbg_ymin(struct seq_file *m, int val) +{ + seq_printf(m, "\tYmin: %4d", val & 0x0FFF); +} + +static void hdmi_dbg_ymax(struct seq_file *m, int val) +{ + seq_printf(m, "\tYmax: %4d", val & 0x0FFF); +} + +static void sti_hdmi_dbg_show(struct sti_tvout_connector *connector, + struct seq_file *m) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + struct drm_display_mode *mode = &hdmi->mode; + struct hdmi_avi_infoframe info; + char str[MAX_STRING_LENGTH]; + + seq_puts(m, "\n"); + seq_printf(m, "\nHDMI: (virt base addr = 0x%p)", hdmi->regs); + HDMI_DBG_DUMP(HDMI_CFG); + hdmi_dbg_cfg(m, readl(hdmi->regs + HDMI_CFG)); + HDMI_DBG_DUMP(HDMI_INT_EN); + HDMI_DBG_DUMP(HDMI_INT_STA); + HDMI_DBG_DUMP(HDMI_INT_CLR); + HDMI_DBG_DUMP(HDMI_STA); + hdmi_dbg_sta(m, readl(hdmi->regs + HDMI_STA)); + HDMI_DBG_DUMP(HDMI_ACTIVE_VID_XMIN); + hdmi_dbg_xmin(m, readl(hdmi->regs + HDMI_ACTIVE_VID_XMIN)); + HDMI_DBG_DUMP(HDMI_ACTIVE_VID_XMAX); + hdmi_dbg_xmax(m, readl(hdmi->regs + HDMI_ACTIVE_VID_XMAX)); + HDMI_DBG_DUMP(HDMI_ACTIVE_VID_YMIN); + hdmi_dbg_ymin(m, readl(hdmi->regs + HDMI_ACTIVE_VID_YMIN)); + HDMI_DBG_DUMP(HDMI_ACTIVE_VID_YMAX); + hdmi_dbg_ymax(m, readl(hdmi->regs + HDMI_ACTIVE_VID_YMAX)); + HDMI_DBG_DUMP(HDMI_DFLT_CHL0_DAT); + HDMI_DBG_DUMP(HDMI_DFLT_CHL1_DAT); + HDMI_DBG_DUMP(HDMI_DFLT_CHL2_DAT); + HDMI_DBG_DUMP(HDMI_SW_DI_CFG); + hdmi_dbg_sw_di_cfg(m, readl(hdmi->regs + HDMI_SW_DI_CFG)); + if (hdmi->tx3g0c55phy) + sti_hdmi_tx3g0c55phy_show(hdmi, m); + else + sti_hdmi_tx3g4c28phy_show(hdmi, m); + seq_puts(m, "\n"); + + seq_printf(m, "\nAVI Infoframe (Data Island slot N=%d):", + HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_HEAD_WORD, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD0, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD1, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD2, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD3, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD4, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD5, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD6, HDMI_IFRAME_SLOT_AVI); + seq_puts(m, "\n"); + if (drm_hdmi_avi_infoframe_from_display_mode(&info, mode) == 0) { + static const char *const colorspace[] = { + "RGB", "YUV422", "YUV444" }; + static const char *const scan_mode[] = { + "none", "overscan", "underscan" }; + static const char *const colorimetry[] = { + "none", "ITU 601", "ITU 709", "extended" }; + static const char *const ext_colorimetry[] = { + "xvYCC 601", "xvYCC 709", "S YCC 601", "Adobe YCC 601", + "Adobe RGB" }; + static const char *const pict_aspect[] = { + "none", "4:3", "16:9" }; + static const char *const active_aspect[] = { + "", "", "16:9 top", "14:9 top", "16:9 center", "", "", + "", "picture", "4:3", "16:9", "14:9", "", "4:3 SP 14:9", + "16:9 SP 14:9", "16:9 SP 4:3" }; + static const char *const quant_range[] = { + "default", "4:3", "16:9" }; + static const char *const nups[] = { + "unknown", "horizontal", "vertical", "both" }; + static const char *const ycc_quant_range[] = { + "limited", "full" }; + static const char *const content_type[] = { + "none", "photo", "cinema", "game" }; + + snprintf(str, MAX_STRING_LENGTH, "\tversion:"); + seq_printf(m, "%-25s %d\n", str, info.version); + snprintf(str, MAX_STRING_LENGTH, "\tlength:"); + seq_printf(m, "%-25s %d\n", str, info.length); + snprintf(str, MAX_STRING_LENGTH, "\tcolorspace:"); + seq_printf(m, "%-25s %s\n", str, colorspace[info.colorspace]); + snprintf(str, MAX_STRING_LENGTH, "\tscan mode:"); + seq_printf(m, "%-25s %s\n", str, scan_mode[info.scan_mode]); + snprintf(str, MAX_STRING_LENGTH, "\tcolorimetry:"); + seq_printf(m, "%-25s %s\n", str, colorimetry[info.colorimetry]); + if (info.colorimetry == HDMI_COLORIMETRY_EXTENDED) { + snprintf(str, MAX_STRING_LENGTH, + " extended colorimetry:"); + seq_printf(m, "%-25s %s\n", str, + ext_colorimetry[info.extended_colorimetry]); + } + snprintf(str, MAX_STRING_LENGTH, "\tpicture aspect:"); + seq_printf(m, "%-25s %s\n", str, + pict_aspect[info.picture_aspect]); + snprintf(str, MAX_STRING_LENGTH, "\tactive aspect:"); + seq_printf(m, "%-25s %s\n", str, + active_aspect[info.active_aspect]); + snprintf(str, MAX_STRING_LENGTH, "\tquantization range:"); + seq_printf(m, "%-25s %s\n", str, + quant_range[info.quantization_range]); + snprintf(str, MAX_STRING_LENGTH, "\tycc quantization range:"); + seq_printf(m, "%-25s %s\n", str, + ycc_quant_range[info.ycc_quantization_range]); + snprintf(str, MAX_STRING_LENGTH, "\tnups:"); + seq_printf(m, "%-25s %s\n", str, nups[info.nups]); + snprintf(str, MAX_STRING_LENGTH, "\tpixel repeat:"); + seq_printf(m, "%-25s %d\n", str, info.pixel_repeat); + snprintf(str, MAX_STRING_LENGTH, "\tactive info valid:"); + seq_printf(m, "%-25s %s\n", str, + info.pixel_repeat ? "true" : "false"); + snprintf(str, MAX_STRING_LENGTH, "\titc:"); + seq_printf(m, "%-25s %s\n", str, info.itc ? "true" : "false"); + snprintf(str, MAX_STRING_LENGTH, "\ttop bar:"); + seq_printf(m, "%-25s %d\n", str, info.top_bar); + snprintf(str, MAX_STRING_LENGTH, "\tbottom bar:"); + seq_printf(m, "%-25s %d\n", str, info.bottom_bar); + snprintf(str, MAX_STRING_LENGTH, "\tleft bar:"); + seq_printf(m, "%-25s %d\n", str, info.left_bar); + snprintf(str, MAX_STRING_LENGTH, "\tright bar:"); + seq_printf(m, "%-25s %d\n", str, info.right_bar); + snprintf(str, MAX_STRING_LENGTH, "\tcontent type:"); + seq_printf(m, "%-25s %s\n", str, + content_type[info.content_type]); + snprintf(str, MAX_STRING_LENGTH, "\tCEA video code:"); + seq_printf(m, "%-25s %d\n", str, info.video_code); + } + + seq_printf(m, "\nHDMI mode: %dx%d%s @%d", + hdmi->mode.hdisplay, + hdmi->mode.vdisplay, + (hdmi->mode.flags & DRM_MODE_FLAG_INTERLACE) ? + "i" : "p", hdmi->mode.vrefresh); +} + +/* + * create the hdmi output + * + * @tvout: pointer on the tvout information + * + * Return pointer on the created tvout connector or NULL if error occurs + */ +struct sti_tvout_connector *sti_hdmi_create(struct sti_tvout *tvout) +{ + struct sti_hdmi *hdmi = container_of(hdmi_dev, struct sti_hdmi, dev); + struct device *dev = &hdmi->dev; + struct sti_tvout_connector *connector; + + DRM_DEBUG_DRIVER("\n"); + + if (!hdmi) { + DRM_INFO("%s: No hdmi device probed\n", __func__); + return NULL; + } + + connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL); + if (!connector) { + DRM_ERROR("Failed to allocate memory for connector\n"); + goto connector_alloc_failed; + } + + /* Set the drm device handle */ + hdmi->drm_dev = tvout->drm_dev; + + /* DDC i2c driver */ + if (i2c_add_driver(&ddc_driver)) { + DRM_ERROR("Failed to register ddc i2c driver\n"); + goto i2c_failed; + } + + /* Enable default interrupts */ + writel(HDMI_DEFAULT_INT, hdmi->regs + HDMI_INT_EN); + + connector->priv = (void *)hdmi; + connector->start = sti_hdmi_start; + connector->stop = sti_hdmi_stop; + connector->get_modes = sti_hdmi_get_modes; + connector->check_mode = sti_hdmi_check_mode; + connector->set_mode = sti_hdmi_set_mode; + connector->detect = sti_hdmi_detect; + connector->is_enabled = sti_hdmi_is_enabled; + connector->prepare = sti_hdmi_prepare; + connector->dbg_show = sti_hdmi_dbg_show; + + return connector; + +i2c_failed: + devm_kfree(dev, connector); +connector_alloc_failed: + return NULL; +} + static int sti_hdmi_bind(struct device *dev, struct device *master, void *data) { return 0; diff --git a/drivers/gpu/drm/sti/sti_hdmi.h b/drivers/gpu/drm/sti/sti_hdmi.h index c14c683..ed90a2c 100644 --- a/drivers/gpu/drm/sti/sti_hdmi.h +++ b/drivers/gpu/drm/sti/sti_hdmi.h @@ -11,6 +11,8 @@
#include <drm/drmP.h>
+#include "sti_tvout.h" + /* HDMI v2.9 macro cell */ #define HDMI_CFG 0x0000 #define HDMI_INT_EN 0x0004 @@ -181,6 +183,7 @@ struct hdmi_phy_config { u32 config[4]; };
+struct sti_tvout_connector *sti_hdmi_create(struct sti_tvout *tvout); void sti_hdmi_attach_ddc_client(struct i2c_client *ddc);
int sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi); diff --git a/drivers/gpu/drm/sti/sti_tvout.c b/drivers/gpu/drm/sti/sti_tvout.c new file mode 100644 index 0000000..3e679a0 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_tvout.c @@ -0,0 +1,702 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * Author: Vincent Abriou vincent.abriou@st.com for STMicroelectronics + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "sti_tvout.h" +#include "sti_hdmi.h" +#include "sti_hda.h" + +/* glue regsiters */ +#define TVO_CSC_MAIN_M0 0x000 +#define TVO_CSC_MAIN_M1 0x004 +#define TVO_CSC_MAIN_M2 0x008 +#define TVO_CSC_MAIN_M3 0x00c +#define TVO_CSC_MAIN_M4 0x010 +#define TVO_CSC_MAIN_M5 0x014 +#define TVO_CSC_MAIN_M6 0x018 +#define TVO_CSC_MAIN_M7 0x01c +#define TVO_MAIN_IN_VID_FORMAT 0x030 +#define TVO_CSC_AUX_M0 0x100 +#define TVO_CSC_AUX_M1 0x104 +#define TVO_CSC_AUX_M2 0x108 +#define TVO_CSC_AUX_M3 0x10c +#define TVO_CSC_AUX_M4 0x110 +#define TVO_CSC_AUX_M5 0x114 +#define TVO_CSC_AUX_M6 0x118 +#define TVO_CSC_AUX_M7 0x11c +#define TVO_AUX_IN_VID_FORMAT 0x130 +#define TVO_VIP_HDF 0x400 +#define TVO_HD_SYNC_SEL 0x418 +#define TVO_HD_DAC_CFG_OFF 0x420 +#define TVO_VIP_HDMI 0x500 +#define TVO_HDMI_FORCE_COLOR_0 0x504 +#define TVO_HDMI_FORCE_COLOR_1 0x508 +#define TVO_HDMI_CLIP_VALUE_B_CB 0x50c +#define TVO_HDMI_CLIP_VALUE_Y_G 0x510 +#define TVO_HDMI_CLIP_VALUE_R_CR 0x514 +#define TVO_HDMI_SYNC_SEL 0x518 +#define TVO_HDMI_DFV_OBS 0x540 + +#define TVO_IN_FMT_SIGNED (1 << 0) +#define TVO_SYNC_EXT (1 << 4) + +#define TVO_VIP_REORDER_R_SHIFT 24 +#define TVO_VIP_REORDER_G_SHIFT 20 +#define TVO_VIP_REORDER_B_SHIFT 16 +#define TVO_VIP_REORDER_MASK 0x3 +#define TVO_VIP_REORDER_Y_G_SEL 0 +#define TVO_VIP_REORDER_CB_B_SEL 1 +#define TVO_VIP_REORDER_CR_R_SEL 2 + +#define TVO_VIP_CLIP_SHIFT 8 +#define TVO_VIP_CLIP_MASK 0x7 +#define TVO_VIP_CLIP_DISABLED 0 +#define TVO_VIP_CLIP_EAV_SAV 1 +#define TVO_VIP_CLIP_LIMITED_RANGE_RGB_Y 2 +#define TVO_VIP_CLIP_LIMITED_RANGE_CB_CR 3 +#define TVO_VIP_CLIP_PROG_RANGE 4 + +#define TVO_VIP_RND_SHIFT 4 +#define TVO_VIP_RND_MASK 0x3 +#define TVO_VIP_RND_8BIT_ROUNDED 0 +#define TVO_VIP_RND_10BIT_ROUNDED 1 +#define TVO_VIP_RND_12BIT_ROUNDED 2 + +#define TVO_VIP_SEL_INPUT_MASK 0xf +#define TVO_VIP_SEL_INPUT_MAIN 0x0 +#define TVO_VIP_SEL_INPUT_AUX 0x8 +#define TVO_VIP_SEL_INPUT_FORCE_COLOR 0xf +#define TVO_VIP_SEL_INPUT_BYPASS_MASK 0x1 +#define TVO_VIP_SEL_INPUT_BYPASSED 1 + +#define TVO_SYNC_MAIN_VTG_SET_REF 0x00 +#define TVO_SYNC_MAIN_VTG_SET_1 0x01 +#define TVO_SYNC_MAIN_VTG_SET_2 0x02 +#define TVO_SYNC_MAIN_VTG_SET_3 0x03 +#define TVO_SYNC_MAIN_VTG_SET_4 0x04 +#define TVO_SYNC_MAIN_VTG_SET_5 0x05 +#define TVO_SYNC_MAIN_VTG_SET_6 0x06 +#define TVO_SYNC_AUX_VTG_SET_REF 0x10 +#define TVO_SYNC_AUX_VTG_SET_1 0x11 +#define TVO_SYNC_AUX_VTG_SET_2 0x12 +#define TVO_SYNC_AUX_VTG_SET_3 0x13 +#define TVO_SYNC_AUX_VTG_SET_4 0x14 +#define TVO_SYNC_AUX_VTG_SET_5 0x15 +#define TVO_SYNC_AUX_VTG_SET_6 0x16 + +#define TVO_SYNC_HD_DCS_SHIFT 8 + +/* Preformatter conversion matrix */ +static const u32 rgb_to_ycbcr_601[8] = { + 0xF927082E, 0x04C9FEAB, 0x01D30964, 0xFA95FD3D, + 0x0000082E, 0x00002000, 0x00002000, 0x00000000 +}; + +/* 709 RGB to YCbCr */ +static const u32 rgb_to_ycbcr_709[8] = { + 0xF891082F, 0x0367FF40, 0x01280B71, 0xF9B1FE20, + 0x0000082F, 0x00002000, 0x00002000, 0x00000000 +}; + +/* + * Helper to write bit field + * + * @addr: register to update + * @val: value to write + * @mask: bit field mask to use + */ +static inline void tvout_reg_writemask(void __iomem *addr, u32 val, u32 mask) +{ + u32 old = readl(addr); + + val = (val & mask) | (old & ~mask); + writel(val, addr); +} + +/* + * Set the Channel order of a VIP + * + * @vip_reg: VIP regsiter + * @cr_r + * @y_g + * @cb_c : values for each output + */ +static void tvout_vip_set_color_order(void __iomem *vip_reg, + u32 cr_r, u32 y_g, u32 cb_b) +{ + u32 val, mask; + + mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_R_SHIFT; + mask |= TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_G_SHIFT; + mask |= TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_B_SHIFT; + val = cr_r << TVO_VIP_REORDER_R_SHIFT; + val |= y_g << TVO_VIP_REORDER_G_SHIFT; + val |= cb_b << TVO_VIP_REORDER_B_SHIFT; + tvout_reg_writemask(vip_reg, val, mask); +} + +/* + * Set the clipping mode of a VIP + * + * @vip_reg: VIP regsiter + * @range : clipping range + */ +static void tvout_vip_set_clip_mode(void __iomem *vip_reg, u32 range) +{ + tvout_reg_writemask(vip_reg, + range << TVO_VIP_CLIP_SHIFT, + TVO_VIP_CLIP_MASK << TVO_VIP_CLIP_SHIFT); +} + +/* + * Set the rounded value of a VIP + * + * @vip_reg: VIP regsiter + * @rnd: rounded val per component + */ +static void tvout_vip_set_rnd(void __iomem *vip_reg, u32 rnd) +{ + tvout_reg_writemask(vip_reg, + rnd << TVO_VIP_RND_SHIFT, + TVO_VIP_RND_MASK << TVO_VIP_RND_SHIFT); +} + +/* + * Select the VIP input + * + * @vip_reg: VIP regsiter + * @sel_input: selected_input (main/aux + conv) + */ +static void tvout_vip_set_sel_input(void __iomem *vip_reg, + bool main_path, + bool sel_input_logic_inverted, + enum sti_tvout_video_out_type video_out) +{ + u32 sel_input; + + if (main_path) + sel_input = TVO_VIP_SEL_INPUT_MAIN; + else + sel_input = TVO_VIP_SEL_INPUT_AUX; + + switch (video_out) { + case STI_TVOUT_VIDEO_OUT_RGB: + sel_input |= TVO_VIP_SEL_INPUT_BYPASSED; + break; + case STI_TVOUT_VIDEO_OUT_YUV: + sel_input &= ~TVO_VIP_SEL_INPUT_BYPASSED; + break; + } + + /* On stih407 chip the sel_input bypass mode logic is inverted */ + if (sel_input_logic_inverted) + sel_input = sel_input ^ TVO_VIP_SEL_INPUT_BYPASS_MASK; + + tvout_reg_writemask(vip_reg, sel_input, TVO_VIP_SEL_INPUT_MASK); +} + +/* + * Select the input video signed or unsigned + * + * @vip_reg: VIP regsiter + * @in_vid_signed: used video input format + */ +static void tvout_vip_set_in_vid_fmt(void __iomem *vip_reg, u32 in_vid_fmt) +{ + tvout_reg_writemask(vip_reg, in_vid_fmt, TVO_IN_FMT_SIGNED); +} + +/* + * Start VIP block for HDMI output + * + * @tvout: pointer on tvout structure + * @main_path: true if main path has to be used in the vip configuration + * else aux path is used. + */ +static void tvout_hdmi_start(struct sti_tvout *tvout, bool main_path) +{ + struct device_node *node = tvout->dev->of_node; + bool sel_input_logic_inverted = false; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (main_path) { + DRM_DEBUG_DRIVER("main vip for hdmi\n"); + /* Select the input sync for hdmi = VTG set 1 */ + writel(TVO_SYNC_MAIN_VTG_SET_1, + tvout->regs + TVO_HDMI_SYNC_SEL); + } else { + DRM_DEBUG_DRIVER("aux vip for hdmi\n"); + /* Select the input sync for hdmi = VTG set 1 */ + writel(TVO_SYNC_AUX_VTG_SET_1, tvout->regs + TVO_HDMI_SYNC_SEL); + } + + /* Set color channel order */ + tvout_vip_set_color_order(tvout->regs + TVO_VIP_HDMI, + TVO_VIP_REORDER_CR_R_SEL, + TVO_VIP_REORDER_Y_G_SEL, + TVO_VIP_REORDER_CB_B_SEL); + + /* Set clipping mode (Limited range RGB/Y) */ + tvout_vip_set_clip_mode(tvout->regs + TVO_VIP_HDMI, + TVO_VIP_CLIP_LIMITED_RANGE_RGB_Y); + + /* Set round mode (rounded to 8-bit per component) */ + tvout_vip_set_rnd(tvout->regs + TVO_VIP_HDMI, TVO_VIP_RND_8BIT_ROUNDED); + + if (of_device_is_compatible(node, "st,stih407-tvout")) { + /* Set input video format */ + tvout_vip_set_in_vid_fmt(tvout->regs + TVO_MAIN_IN_VID_FORMAT, + TVO_IN_FMT_SIGNED); + sel_input_logic_inverted = true; + } + + /* Input selection */ + tvout_vip_set_sel_input(tvout->regs + TVO_VIP_HDMI, + main_path, + sel_input_logic_inverted, + STI_TVOUT_VIDEO_OUT_RGB); +} + +/* + * Prepare/configure VIP block for HDMI output + * + * @tvout: pointer on tvout structure + */ +static void tvout_hdmi_prepare(struct sti_tvout *tvout) +{ + dev_dbg(tvout->dev, "%s\n", __func__); + + /* Reset VIP register */ + writel(0x00000000, tvout->regs + TVO_VIP_HDMI); +} + +/* + * Disable HDMI VIP + * + * @tvout: pointer on tvout structure + */ +static void tvout_hdmi_stop(struct sti_tvout *tvout) +{ + dev_dbg(tvout->dev, "%s\n", __func__); + + /* Reset VIP register */ + writel(0x00000000, tvout->regs + TVO_VIP_HDMI); +} + +/* + * Start HDF VIP and HD DAC + * + * @tvout: pointer on tvout structure + * @main_path: true if main path has to be used in the vip configuration + * else aux path is used. + */ +static void tvout_hda_start(struct sti_tvout *tvout, bool main_path) +{ + struct device_node *node = tvout->dev->of_node; + bool sel_input_logic_inverted = false; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (!main_path) { + DRM_ERROR("HD Analog on aux not implemented\n"); + return; + } + + DRM_DEBUG_DRIVER("main vip for HDF\n"); + + /* Set color channel order */ + tvout_vip_set_color_order(tvout->regs + TVO_VIP_HDF, + TVO_VIP_REORDER_CR_R_SEL, + TVO_VIP_REORDER_Y_G_SEL, + TVO_VIP_REORDER_CB_B_SEL); + + /* Set clipping mode (Limited range RGB/Y) */ + tvout_vip_set_clip_mode(tvout->regs + TVO_VIP_HDF, + TVO_VIP_CLIP_LIMITED_RANGE_CB_CR); + + /* Set round mode (rounded to 10-bit per component) */ + tvout_vip_set_rnd(tvout->regs + TVO_VIP_HDF, TVO_VIP_RND_10BIT_ROUNDED); + + if (of_device_is_compatible(node, "st,stih407-tvout")) { + /* Set input video format */ + tvout_vip_set_in_vid_fmt(tvout->regs + TVO_MAIN_IN_VID_FORMAT, + TVO_IN_FMT_SIGNED); + sel_input_logic_inverted = true; + } + + /* Input selection */ + tvout_vip_set_sel_input(tvout->regs + TVO_VIP_HDF, + main_path, + sel_input_logic_inverted, + STI_TVOUT_VIDEO_OUT_YUV); + + /* Select the input sync for HD analog = VTG set 3 + * and HD DCS = VTG set 2 */ + writel((TVO_SYNC_MAIN_VTG_SET_2 << TVO_SYNC_HD_DCS_SHIFT) | + TVO_SYNC_MAIN_VTG_SET_3, tvout->regs + TVO_HD_SYNC_SEL); + + /* Power up HD DAC */ + writel(0, tvout->regs + TVO_HD_DAC_CFG_OFF); +} + +/* + * Prepare/configure HDF VIP and HD DAC + * + * @tvout: pointer on tvout structure + */ +static void tvout_hda_prepare(struct sti_tvout *tvout) +{ + dev_dbg(tvout->dev, "%s\n", __func__); + + /* Reset VIP register */ + writel(0x00000000, tvout->regs + TVO_VIP_HDF); + + /* Power down HD DAC */ + writel(1, tvout->regs + TVO_HD_DAC_CFG_OFF); +} + +/* + * Stop HDF VIP and HD DAC + * + * @tvout: pointer on tvout structure + */ +static void tvout_hda_stop(struct sti_tvout *tvout) +{ + dev_dbg(tvout->dev, "%s\n", __func__); + + /* Reset VIP register */ + writel(0x00000000, tvout->regs + TVO_VIP_HDF); + + /* Power down HD DAC */ + writel(1, tvout->regs + TVO_HD_DAC_CFG_OFF); +} + +/* + * Check if the connector is connected + * + * @tvout: pointer on tvout structure + * @type: type of connector + * + * Return true if connected + */ +bool sti_tvout_connector_detect(struct sti_tvout *tvout, + enum sti_tvout_connector_type type) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->detect) + return connector->detect(connector); + + return false; +} + +/* + * Forward drm display mode information to the connector + * + * @tvout: pointer on tvout structure + * @mode: selected display mode + * @type: type of connector + * + * Return -1 if error occurs + */ +int sti_tvout_set_mode(struct sti_tvout *tvout, struct drm_display_mode *mode, + enum sti_tvout_connector_type type) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->set_mode) + return connector->set_mode(connector, mode); + + return -1; +} + +/* + * Get modes + * + * @tvout: pointer on tvout structure + * @type: type of connector + * @drm_connector: pointer on the connector + * + * Return Nb of modes, -1 if error + */ +int sti_tvout_get_modes(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + struct drm_connector *drm_connector) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->get_modes) + return connector->get_modes(drm_connector); + + return -1; +} + +/* + * Check if the mode is supported + * + * function used to filter unsupported mode + * + * @tvout: pointer on tvout structure + * @type: type of connector + * @mode: drm display mode + * + * Return -1 if error occurs + */ +int sti_tvout_check_mode(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + struct drm_display_mode *mode) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->check_mode) + return connector->check_mode(connector, mode); + + return -1; +} + +/* + * Prepare / initialize depending on the connector type + * + * @tvout: pointer on tvout structure + * @type: type of connector + * + * Return -1 if error occurs + */ +int sti_tvout_prepare(struct sti_tvout *tvout, + enum sti_tvout_connector_type type) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + int ret = -1; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->prepare) + connector->prepare(connector); + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + tvout_hdmi_prepare(tvout); + ret = 0; + break; + case STI_TVOUT_CONNECTOR_HDA: + tvout_hda_prepare(tvout); + ret = 0; + break; + case STI_TVOUT_CONNECTOR_DVO: + case STI_TVOUT_CONNECTOR_DENC: + default: + /* Not yet supported */ + ret = -1; + break; + } + + return ret; +} + +/* + * Commit / start depending on the connector type + * + * @tvout: pointer on tvout structure + * @type: type of connector + * @main_path: true if main path need to be use (false for aux path) + * + * Return -1 if error occurs + */ +int sti_tvout_commit(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, bool main_path) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + int ret = 0; + u32 matrix_offset; + int i; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (!connector) + return -1; + + connector->main_path = main_path; + + if (connector->start) { + ret = connector->start(connector); + if (ret) { + DRM_ERROR("Unable to properly start connector\n"); + return -1; + } + } + + /* Set preformatter matrix */ + matrix_offset = main_path ? TVO_CSC_MAIN_M0 : TVO_CSC_AUX_M0; + for (i = 0; i < 8; i++) + writel(rgb_to_ycbcr_601[i], + tvout->regs + matrix_offset + (i * 4)); + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + tvout_hdmi_start(tvout, main_path); + return 0; + case STI_TVOUT_CONNECTOR_HDA: + tvout_hda_start(tvout, main_path); + return 0; + case STI_TVOUT_CONNECTOR_DVO: + case STI_TVOUT_CONNECTOR_DENC: + default: + /* Not yet supported */ + return -1; + } +} + +/* + * Disable / stop the tvout depending on the connector type + * + * @tvout: pointer on tvout structure + * @type: type of connector + */ +void sti_tvout_disable(struct sti_tvout *tvout, + enum sti_tvout_connector_type type) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + tvout_hdmi_stop(tvout); + break; + case STI_TVOUT_CONNECTOR_HDA: + tvout_hda_stop(tvout); + break; + case STI_TVOUT_CONNECTOR_DVO: + case STI_TVOUT_CONNECTOR_DENC: + default: + /* Not yet supported */ + return; + } + + if (connector) + if (connector->stop) + connector->stop(connector); +} + +static int sti_tvout_bind(struct device *dev, struct device *master, void *data) +{ + return 0; +} + +static void sti_tvout_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops sti_tvout_ops = { + .bind = sti_tvout_bind, + .unbind = sti_tvout_unbind, +}; + +static int sti_tvout_probe(struct platform_device *pdev) +{ + 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) { + DRM_ERROR("no device node\n"); + return -ENODEV; + } + + tvout = devm_kzalloc(dev, sizeof(*tvout), GFP_KERNEL); + if (!tvout) { + DRM_ERROR("failed to allocate compositor context\n"); + return -ENOMEM; + } + DRM_DEBUG_DRIVER("tvout %p\n", tvout); + tvout->dev = dev; + + /* Get Memory ressources */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tvout-reg"); + if (!res) { + DRM_ERROR("Invalid glue resource\n"); + return -ENOMEM; + } + tvout->regs = devm_ioremap_nocache(dev, res->start, resource_size(res)); + if (IS_ERR(tvout->regs)) + return PTR_ERR(tvout->regs); + + /* Get reset resources */ + tvout->reset = devm_reset_control_get(dev, "tvout"); + /* Take tvout out of reset */ + if (!IS_ERR(tvout->reset)) + reset_control_deassert(tvout->reset); + + /* List supported tvout connector */ + tvout->connector_create[STI_TVOUT_CONNECTOR_HDMI] = sti_hdmi_create; + tvout->connector_create[STI_TVOUT_CONNECTOR_HDA] = sti_hda_create; + tvout->connector_create[STI_TVOUT_CONNECTOR_DVO] = NULL; + tvout->connector_create[STI_TVOUT_CONNECTOR_DENC] = NULL; + + platform_set_drvdata(pdev, tvout); + + return component_add(&pdev->dev, &sti_tvout_ops); +} + +static int sti_tvout_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sti_tvout_ops); + return 0; +} + +static struct of_device_id tvout_match_types[] = { + { + .compatible = "st,stih416-tvout", + }, + { + .compatible = "st,stih407-tvout", + }, + { /* end node */ } +}; +MODULE_DEVICE_TABLE(of, tvout_match_types); + +struct platform_driver sti_tvout_driver = { + .driver = { + .name = "sti-tvout", + .owner = THIS_MODULE, + .of_match_table = tvout_match_types, + }, + .probe = sti_tvout_probe, + .remove = sti_tvout_remove, +}; + +module_platform_driver(sti_tvout_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sti/sti_tvout.h b/drivers/gpu/drm/sti/sti_tvout.h new file mode 100644 index 0000000..f61a49c --- /dev/null +++ b/drivers/gpu/drm/sti/sti_tvout.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Vincent Abriou vincent.abriou@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_TVOUT_H_ +#define _STI_TVOUT_H_ + +#include <linux/clk.h> + +/* + * STI TVout connector structure + * + * @priv: private structure associated to the connector type + * @start: start the connector + * @stop: stop the connector + * @get_modes: get modes potentially supported + * @check_mode: check if a mode is really supported + * @set_mode: set the drm display mode in the specific connector structure + * @detect: detect if connector is connected + * @prepare: prepare the connector + * @is_enabled: is the connector enabled + * @dbg_show: dump debug information + */ +struct sti_tvout_connector { + void *priv; + bool main_path; + int (*start)(struct sti_tvout_connector *connector); + void (*stop)(struct sti_tvout_connector *connector); + int (*get_modes)(struct drm_connector *drm_connector); + int (*check_mode)(struct sti_tvout_connector *connector, + struct drm_display_mode *mode); + int (*set_mode)(struct sti_tvout_connector *connector, + struct drm_display_mode *mode); + bool (*detect)(struct sti_tvout_connector *connector); + void (*prepare)(struct sti_tvout_connector *connector); + bool (*is_enabled)(struct sti_tvout_connector *connector); + void (*dbg_show)(struct sti_tvout_connector *connector, + struct seq_file *m); +}; + +/* + * enum listing the supported connector + */ +enum sti_tvout_connector_type { + STI_TVOUT_CONNECTOR_HDMI, + STI_TVOUT_CONNECTOR_HDA, + STI_TVOUT_CONNECTOR_DVO, + STI_TVOUT_CONNECTOR_DENC, + STI_TVOUT_CONNECTOR_MAX, +}; + +/* + * enum listing the supported output data format + */ +enum sti_tvout_video_out_type { + STI_TVOUT_VIDEO_OUT_RGB, + STI_TVOUT_VIDEO_OUT_YUV, +}; + +/* + * STI TVout structure + * + * @dev: pointer to driver device + * @regs: registers + * @reset: reset control of the tvout + * @hw_id: HW revision of the IP + * @connector_create: list of function to register a connector + * @connector: list of registered connector + */ +struct sti_tvout { + struct device *dev; + struct drm_device *drm_dev; + void __iomem *regs; + struct reset_control *reset; + int hw_id; + struct sti_tvout_connector *(*connector_create[STI_TVOUT_CONNECTOR_MAX]) + (struct sti_tvout *tvout); + struct sti_tvout_connector *connector[STI_TVOUT_CONNECTOR_MAX]; +}; + +bool sti_tvout_connector_detect(struct sti_tvout *tvout, + enum sti_tvout_connector_type type); +int sti_tvout_set_mode(struct sti_tvout *tvout, + struct drm_display_mode *mode, + enum sti_tvout_connector_type type); +int sti_tvout_get_modes(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + struct drm_connector *drm_connector); +int sti_tvout_check_mode(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + struct drm_display_mode *mode); +int sti_tvout_prepare(struct sti_tvout *tvout, + enum sti_tvout_connector_type type); +int sti_tvout_commit(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + bool main_path); +void sti_tvout_disable(struct sti_tvout *tvout, + enum sti_tvout_connector_type type); + +int sti_tvout_hdmi_dbg_show(struct seq_file *m, void *arg); +int sti_tvout_hda_dbg_show(struct seq_file *m, void *arg); + +#endif
STI hardware have various input sub-devices before mixing block. Each type of sub-device have different capabilities for scaling, filtering or accepted pixel format. This layer interface abstract those differences and make the interaction with compositor more simple.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/sti_layer.h | 104 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_layer.h
diff --git a/drivers/gpu/drm/sti/sti_layer.h b/drivers/gpu/drm/sti/sti_layer.h new file mode 100644 index 0000000..ed8386a --- /dev/null +++ b/drivers/gpu/drm/sti/sti_layer.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_LAYER_H_ +#define _STI_LAYER_H_ + +#include <drm/drmP.h> + +#define to_sti_layer(x) container_of(x, struct sti_layer, plane) + +#define STI_LAYER_TYPE_SHIFT 8 +#define STI_LAYER_TYPE_MASK (~((1<<STI_LAYER_TYPE_SHIFT)-1)) + + +enum sti_layer_type { + STI_GDP = 1 << STI_LAYER_TYPE_SHIFT, + STI_VID = 2 << STI_LAYER_TYPE_SHIFT, + STI_CUR = 3 << STI_LAYER_TYPE_SHIFT, + STI_BCK = 4 << STI_LAYER_TYPE_SHIFT +}; + +enum sti_layer_id_of_type { + STI_ID_0 = 0, + STI_ID_1 = 1, + STI_ID_2 = 2, + STI_ID_3 = 3 +}; + +enum sti_layer_desc { + STI_GDP_0 = STI_GDP | STI_ID_0, + STI_GDP_1 = STI_GDP | STI_ID_1, + STI_GDP_2 = STI_GDP | STI_ID_2, + STI_GDP_3 = STI_GDP | STI_ID_3, + STI_VID_0 = STI_VID | STI_ID_0, + STI_VID_1 = STI_VID | STI_ID_1, + STI_CURSOR = STI_CUR, + STI_BACK = STI_BCK +}; + +struct sti_fps_info { + bool output; + int curr_frame_counter; + int last_frame_counter; + struct timespec last_timestamp; +}; + +/* + * STI layer structure + * + * @plane: drm plane it is bound to (if any) + * @fb: drm fb it is bound to + * @mode: display mode + * @desc: layer type & id + * @zorder: layer z-order + * @enabled: to know if the layer is active or not + * @src_x src_y: coordinates of the input (fb) area + * @src_w src_h: size of the input (fb) area + * @dst_x dst_y: coordinates of the output (crtc) area + * @dst_w dst_h: size of the output (crtc) area + * @format: format + * @pitches: pitch of 'planes' (eg: Y, U, V) + * @offsets: offset of 'planes' + * @paddr: physical address of the input buffer + * @fps_info: frame per second info + */ +struct sti_layer { + struct drm_plane plane; + struct drm_framebuffer *fb; + struct drm_display_mode *mode; + enum sti_layer_desc desc; + int zorder; + bool enabled; + int src_x, src_y; + int src_w, src_h; + int dst_x, dst_y; + int dst_w, dst_h; + uint32_t format; + int pitches[4]; + int offsets[4]; + dma_addr_t paddr; + struct sti_fps_info fps_info; +}; + +struct sti_layer *sti_layer_create(struct device *dev, int desc, + void __iomem *baseaddr); +int sti_layer_prepare(struct sti_layer *layer, struct drm_framebuffer *fb, + struct drm_display_mode *mode, + int dest_x, int dest_y, + int dest_w, int dest_h, + int src_x, int src_y, + int src_w, int src_h); +int sti_layer_commit(struct sti_layer *layer); +int sti_layer_disable(struct sti_layer *layer); +const uint32_t *sti_layer_get_formats(struct sti_layer *layer); +int sti_layer_get_nb_formats(struct sti_layer *layer); +struct sti_layer *sti_layer_find_layer(struct sti_layer *layer[], + enum sti_layer_desc desc); +const char *sti_layer_to_str(struct sti_layer *layer); +#endif
Generic Display Pipeline are one of the compositor input sub-devices. GDP are dedicated to graphic input like RGB plans.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Makefile | 3 +- drivers/gpu/drm/sti/sti_gdp.c | 491 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_gdp.h | 73 ++++++ drivers/gpu/drm/sti/sti_layer.h | 5 + 4 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/sti/sti_gdp.c create mode 100644 drivers/gpu/drm/sti/sti_gdp.h
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 45536c3..cc04475 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -1,10 +1,11 @@ ccflags-y := -Iinclude/drm
-stidrm-y := sti_tvout.o \ +stidrm-y := sti_tvout.o \ sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ + sti_gdp.o \ sti_ddc.o
obj-$(CONFIG_VTAC_STI) += sti_vtac_tx.o sti_vtac_rx.o diff --git a/drivers/gpu/drm/sti/sti_gdp.c b/drivers/gpu/drm/sti/sti_gdp.c new file mode 100644 index 0000000..00ff7f9 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_gdp.c @@ -0,0 +1,491 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/dma-mapping.h> + +#include "sti_layer.h" +#include "sti_gdp.h" +#include "sti_vtg_utils.h" + +#define ENA_COLOR_FILL (1 << 8) +#define WAIT_NEXT_VSYNC (1 << 31) + +/* GDP color formats */ +#define GDP_RGB565 0x00 +#define GDP_RGB888 0x01 +#define GDP_RGB888_32 0x02 +#define GDP_ARGB8565 0x04 +#define GDP_ARGB8888 0x05 +#define GDP_ARGB1555 0x06 +#define GDP_ARGB4444 0x07 +#define GDP_CLUT8 0x0B +#define GDP_YCBR888 0x10 +#define GDP_YCBR422R 0x12 +#define GDP_AYCBR8888 0x15 + +#define GAM_GDP_CTL_OFFSET 0x00 +#define GAM_GDP_AGC_OFFSET 0x04 +#define GAM_GDP_VPO_OFFSET 0x0C +#define GAM_GDP_VPS_OFFSET 0x10 +#define GAM_GDP_PML_OFFSET 0x14 +#define GAM_GDP_PMP_OFFSET 0x18 +#define GAM_GDP_SIZE_OFFSET 0x1C +#define GAM_GDP_NVN_OFFSET 0x24 +#define GAM_GDP_KEY1_OFFSET 0x28 +#define GAM_GDP_KEY2_OFFSET 0x2C +#define GAM_GDP_PPT_OFFSET 0x34 +#define GAM_GDP_CML_OFFSET 0x3C +#define GAM_GDP_MST_OFFSET 0x68 + +#define GAM_GDP_ALPHARANGE_255 (1 << 5) +#define GAM_GDP_AGC_FULL_RANGE 0x00808080 +#define GAM_GDP_PPT_IGNORE ((1 << 1) | (1 << 0)) +#define GAM_GDP_SIZE_MAX 0x7FF + +static const uint32_t gdp_supported_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_AYUV, + DRM_FORMAT_YUV444, + DRM_FORMAT_VYUY, + DRM_FORMAT_C8, +}; + +static const uint32_t *sti_gdp_get_formats(void) +{ + return gdp_supported_formats; +} + +static int sti_gdp_get_nb_formats(void) +{ + return ARRAY_SIZE(gdp_supported_formats); +} + +static int sti_gdp_fourcc2format(int fourcc) +{ + switch (fourcc) { + case DRM_FORMAT_XRGB8888: + return GDP_RGB888_32; + case DRM_FORMAT_ARGB8888: + return GDP_ARGB8888; + case DRM_FORMAT_ARGB4444: + return GDP_ARGB4444; + case DRM_FORMAT_ARGB1555: + return GDP_ARGB1555; + case DRM_FORMAT_RGB565: + return GDP_RGB565; + case DRM_FORMAT_RGB888: + return GDP_RGB888; + case DRM_FORMAT_AYUV: + return GDP_AYCBR8888; + case DRM_FORMAT_YUV444: + return GDP_YCBR888; + case DRM_FORMAT_VYUY: + return GDP_YCBR422R; + case DRM_FORMAT_C8: + return GDP_CLUT8; + } + return -1; +} + +static int sti_gdp_get_alpharange(int format) +{ + switch (format) { + case GDP_ARGB8565: + case GDP_ARGB8888: + case GDP_AYCBR8888: + return GAM_GDP_ALPHARANGE_255; + } + return 0; +} + +/** + * sti_gdp_get_free_nodes + * @layer: gdp layer + * + * Look for a GDP node list that is not currently read by the HW. + * + * RETURNS: + * Pointer to the free GDP node list + */ +static struct sti_gdp_node_list *sti_gdp_get_free_nodes(struct sti_layer *layer) +{ + int hw_nvn; + void *virt_nvn; + struct sti_gdp *gdp = layer->gdp; + int i; + + hw_nvn = readl(gdp->regs + GAM_GDP_NVN_OFFSET); + if (!hw_nvn) + goto end; + + virt_nvn = dma_to_virt(gdp->dev, (dma_addr_t) hw_nvn); + + for (i = 0; i < GDP_NODE_NB_BANK; i++) + if ((virt_nvn != gdp->node_list[i].btm_field) && + (virt_nvn != gdp->node_list[i].top_field)) + return &gdp->node_list[i]; + +end: + return &gdp->node_list[0]; +} + +/** + * sti_gdp_get_current_nodes + * @layer: GDP layer + * + * Look for GDP nodes that are currently read by the HW. + * + * RETURNS: + * Pointer to the current GDP node list + */ +static +struct sti_gdp_node_list *sti_gdp_get_current_nodes(struct sti_layer *layer) +{ + int hw_nvn; + void *virt_nvn; + struct sti_gdp *gdp = layer->gdp; + int i; + + hw_nvn = readl(gdp->regs + GAM_GDP_NVN_OFFSET); + if (!hw_nvn) + goto end; + + virt_nvn = dma_to_virt(gdp->dev, (dma_addr_t) hw_nvn); + + for (i = 0; i < GDP_NODE_NB_BANK; i++) + if ((virt_nvn == gdp->node_list[i].btm_field) || + (virt_nvn == gdp->node_list[i].top_field)) + return &gdp->node_list[i]; + +end: + return NULL; +} + +/** + * sti_gdp_prepare_layer + * @lay: gdp layer + * @first_prepare: true if it is the first time this function is called + * + * Update the free GDP node list according to the layer properties. + * + * RETURNS: + * 0 on success. + */ +static int sti_gdp_prepare_layer(void *lay, bool first_prepare) +{ + struct sti_layer *layer = (struct sti_layer *)lay; + struct sti_gdp_node_list *list; + struct sti_gdp_node *top_field, *btm_field; + struct drm_display_mode *mode = layer->mode; + struct device *dev = layer->gdp->dev; + int format, depth, bpp; + int rate = mode->clock * 1000; + int res; + u32 ydo, xdo, yds, xds; + + list = sti_gdp_get_free_nodes(layer); + top_field = list->top_field; + btm_field = list->btm_field; + + /* Build the top field from layer params */ + top_field->gam_gdp_agc = GAM_GDP_AGC_FULL_RANGE; + top_field->gam_gdp_ctl = WAIT_NEXT_VSYNC; + format = sti_gdp_fourcc2format(layer->format); + if (format == -1) { + DRM_ERROR("Format not supported by GDP %.4s\n", + (char *)&layer->format); + return 1; + } + top_field->gam_gdp_ctl |= format; + top_field->gam_gdp_ctl |= sti_gdp_get_alpharange(format); + top_field->gam_gdp_ppt &= ~GAM_GDP_PPT_IGNORE; + + /* pixel memory location */ + drm_fb_get_bpp_depth(layer->format, &depth, &bpp); + top_field->gam_gdp_pml = (u32) layer->paddr + layer->offsets[0]; + top_field->gam_gdp_pml += layer->src_x * (bpp >> 3); + top_field->gam_gdp_pml += layer->src_y * layer->pitches[0]; + + /* input parameters */ + top_field->gam_gdp_pmp = layer->pitches[0]; + top_field->gam_gdp_size = + clamp_val(layer->src_h, 0, GAM_GDP_SIZE_MAX) << 16 | + clamp_val(layer->src_w, 0, GAM_GDP_SIZE_MAX); + + /* output parameters */ + ydo = sti_vtg_get_line_number(*mode, layer->dst_y); + yds = sti_vtg_get_line_number(*mode, layer->dst_y + layer->dst_h - 1); + xdo = sti_vtg_get_pixel_number(*mode, layer->dst_x); + xds = sti_vtg_get_pixel_number(*mode, layer->dst_x + layer->dst_w - 1); + top_field->gam_gdp_vpo = (ydo << 16) | xdo; + top_field->gam_gdp_vps = (yds << 16) | xds; + + /* Same content and chained together */ + memcpy(btm_field, top_field, sizeof(*btm_field)); + top_field->gam_gdp_nvn = virt_to_dma(dev, btm_field); + btm_field->gam_gdp_nvn = virt_to_dma(dev, top_field); + + /* Interlaced mode */ + if (layer->mode->flags & DRM_MODE_FLAG_INTERLACE) + btm_field->gam_gdp_pml = top_field->gam_gdp_pml + + layer->pitches[0]; + + if (first_prepare) { + /* Register gdp callback */ + if (sti_vtg_register_client(layer->mixer_id, + &layer->gdp->vtg_field_nb)) { + DRM_ERROR("Cannot register VTG notifier\n"); + return 1; + } + + /* Set and enable gdp clock */ + if (layer->gdp->clk_pix) { + res = clk_set_rate(layer->gdp->clk_pix, rate); + if (res < 0) { + DRM_ERROR("Cannot set rate (%dHz) for gdp\n", + rate); + return 1; + } + + if (clk_prepare_enable(layer->gdp->clk_pix)) { + DRM_ERROR("Failed to prepare/enable gdp\n"); + return 1; + } + } + } + + return 0; +} + +/** + * sti_gdp_commit_layer + * @lay: gdp layer + * + * Update the NVN field of the 'right' field of the current GDP node (being + * used by the HW) with the address of the updated ('free') top field GDP node. + * - In interlaced mode the 'right' field is the bottom field as we update + * frames starting from their top field + * - In progressive mode, we update both bottom and top fields which are + * equal nodes. + * At the next VSYNC, the updated node list will be used by the HW. + * + * RETURNS: + * 0 on success. + */ +static int sti_gdp_commit_layer(void *lay) +{ + struct sti_layer *layer = (struct sti_layer *)lay; + struct sti_gdp_node_list *updated_list = sti_gdp_get_free_nodes(layer); + struct sti_gdp_node *updated_top_node = updated_list->top_field; + struct sti_gdp_node *updated_btm_node = updated_list->btm_field; + struct sti_gdp *gdp = layer->gdp; + u32 dma_updated_top = virt_to_dma(gdp->dev, updated_top_node); + u32 dma_updated_btm = virt_to_dma(gdp->dev, updated_btm_node); + struct sti_gdp_node_list *curr_list = sti_gdp_get_current_nodes(layer); + + dev_dbg(gdp->dev, "Current NVN:0x%X\n", + readl(gdp->regs + GAM_GDP_NVN_OFFSET)); + dev_dbg(gdp->dev, "Posted buff: %lx current buff: %x\n", + (unsigned long)layer->paddr, + readl(gdp->regs + GAM_GDP_PML_OFFSET)); + + if (curr_list == NULL) { + /* First update or invalid node should directly write in the + * hw register */ + writel(gdp->is_curr_top == true ? + dma_updated_btm : dma_updated_top, + gdp->regs + GAM_GDP_NVN_OFFSET); + return 0; + } + + if (layer->mode->flags & DRM_MODE_FLAG_INTERLACE) { + if (gdp->is_curr_top == true) { + /* Do not update in the middle of the frame, but + * postpone the update after the bottom field has + * been displayed */ + curr_list->btm_field->gam_gdp_nvn = dma_updated_top; + } else { + /* Direct update to avoid one frame delay */ + writel(dma_updated_top, gdp->regs + GAM_GDP_NVN_OFFSET); + } + } else { + /* Direct update for progressive to avoid one frame delay */ + writel(dma_updated_top, gdp->regs + GAM_GDP_NVN_OFFSET); + } + + return 0; +} + +/** + * sti_gdp_disable_layer + * @lay: gdp layer + * + * Disable a GDP. + * + * RETURNS: + * 0 on success. + */ +static int sti_gdp_disable_layer(void *lay) +{ + int i; + struct sti_layer *layer = (struct sti_layer *)lay; + struct sti_gdp *gdp = layer->gdp; + + /* Set the nodes as 'to be ignored on mixer' */ + for (i = 0; i < GDP_NODE_NB_BANK; i++) { + gdp->node_list[i].top_field->gam_gdp_ppt |= GAM_GDP_PPT_IGNORE; + gdp->node_list[i].btm_field->gam_gdp_ppt |= GAM_GDP_PPT_IGNORE; + } + + if (sti_vtg_unregister_client(layer->mixer_id, &gdp->vtg_field_nb)) + DRM_DEBUG_DRIVER("Warning: cannot unregister VTG notifier\n"); + + if (gdp->clk_pix) + clk_disable_unprepare(gdp->clk_pix); + + return 0; +} + +/** + * sti_gdp_field_cb + * @nb: notifier block + * @event: event message + * @data: private data + * + * Handle VTG top field and bottom field event. + * + * RETURNS: + * 0 on success. + */ +int sti_gdp_field_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct sti_gdp *gdp = container_of(nb, struct sti_gdp, vtg_field_nb); + + switch (event) { + case VTG_TOP_FIELD_EVENT: + gdp->is_curr_top = true; + break; + case VTG_BOTTOM_FIELD_EVENT: + gdp->is_curr_top = false; + break; + default: + DRM_ERROR("unsupported event: %lu\n", event); + break; + } + + return 0; +} + +/** + * sti_gdp_create + * @dev: device + * @id: gdp id + * @baseaddr: IO addr + * + * Create a gdp object. Allocate memory and initialize parameters. + * + * + * RETURNS: + * Pointer to the created gdp object. + */ +struct sti_gdp *sti_gdp_create(struct device *dev, int id, + void __iomem *baseaddr) +{ + struct sti_gdp *gdp; + struct device_node *np = dev->of_node; + dma_addr_t dma; + void *base; + int i, size; + + gdp = devm_kzalloc(dev, sizeof(*gdp), GFP_KERNEL); + if (!gdp) { + DRM_ERROR("Failed to allocate memory for GDP\n"); + return NULL; + } + + /* Allocate all the nodes within a single memory page */ + size = sizeof(struct sti_gdp_node) * + GDP_NODE_PER_FIELD * GDP_NODE_NB_BANK; + + base = dma_alloc_writecombine(dev, size, &dma, GFP_KERNEL | GFP_DMA); + if (!base) { + DRM_ERROR("Failed to allocate memory for GDP node\n"); + goto mem_err; + } + memset(base, 0, size); + + for (i = 0; i < GDP_NODE_NB_BANK; i++) { + if (virt_to_dma(dev, base) & 0xF) { + DRM_ERROR("Mem alignment failed\n"); + goto mem_err; + } + gdp->node_list[i].top_field = base; + DRM_DEBUG_DRIVER("node[%d].top_field=%p\n", i, base); + base += sizeof(struct sti_gdp_node); + + if (virt_to_dma(dev, base) & 0xF) { + DRM_ERROR("Mem alignment failed\n"); + goto mem_err; + } + gdp->node_list[i].btm_field = base; + DRM_DEBUG_DRIVER("node[%d].btm_field=%p\n", i, base); + base += sizeof(struct sti_gdp_node); + } + + gdp->dev = dev; + gdp->regs = baseaddr; + gdp->get_formats = sti_gdp_get_formats; + gdp->get_nb_formats = sti_gdp_get_nb_formats; + gdp->prepare = sti_gdp_prepare_layer; + gdp->commit = sti_gdp_commit_layer; + gdp->disable = sti_gdp_disable_layer; + gdp->vtg_field_nb.notifier_call = sti_gdp_field_cb; + + if (of_device_is_compatible(np, "st,stih407-compositor")) { + /* GDP of STiH407 chip have its own pixel clock */ + char *clk_name; + + switch (id) { + case STI_GDP_0: + clk_name = "pix_gdp1"; + break; + case STI_GDP_1: + clk_name = "pix_gdp2"; + break; + case STI_GDP_2: + clk_name = "pix_gdp3"; + break; + case STI_GDP_3: + clk_name = "pix_gdp4"; + break; + default: + DRM_ERROR("GDP id not recognized\n"); + goto err_clk_pix_gdp; + } + + gdp->clk_pix = devm_clk_get(dev, clk_name); + if (IS_ERR(gdp->clk_pix)) { + DRM_ERROR("Cannot get %s clock\n", clk_name); + goto err_clk_pix_gdp; + } + } + + return gdp; + +err_clk_pix_gdp: +mem_err: + devm_kfree(dev, gdp); + return NULL; +} diff --git a/drivers/gpu/drm/sti/sti_gdp.h b/drivers/gpu/drm/sti/sti_gdp.h new file mode 100644 index 0000000..4f7f40b --- /dev/null +++ b/drivers/gpu/drm/sti/sti_gdp.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_GDP_H_ +#define _STI_GDP_H_ + +#include <linux/types.h> + +#define GDP_NODE_NB_BANK 2 +#define GDP_NODE_PER_FIELD 2 + +struct sti_gdp_node { + u32 gam_gdp_ctl; + u32 gam_gdp_agc; + u32 reserved1; + u32 gam_gdp_vpo; + u32 gam_gdp_vps; + u32 gam_gdp_pml; + u32 gam_gdp_pmp; + u32 gam_gdp_size; + u32 reserved2; + u32 gam_gdp_nvn; + u32 gam_gdp_key1; + u32 gam_gdp_key2; + u32 reserved3; + u32 gam_gdp_ppt; + u32 reserved4; + u32 gam_gdp_cml; +}; + +struct sti_gdp_node_list { + struct sti_gdp_node *top_field; + struct sti_gdp_node *btm_field; +}; + +/* + * STI GDP structure + * + * @device: driver device + * @regs: subdevice register + * @clk_pix: pixel clock for the current gdp + * @vtg_field_nb: callback for VTG FIELD (top or bottom) notification + * @is_curr_top: true if the current node processed is the top field + * @get_formats: get GDP supported formats + * @get_nb_formats: get number of format supported + * @prepare: prepare GDP before rendering + * @commit: set GDP for rendering + * @disable: disable GDP + * @node_list: array of node list + */ +struct sti_gdp { + struct device *dev; + void __iomem *regs; + struct clk *clk_pix; + struct notifier_block vtg_field_nb; + bool is_curr_top; + const uint32_t* (*get_formats)(void); + int (*get_nb_formats)(void); + int (*prepare)(void *layer, bool first_prepare); + int (*commit)(void *layer); + int (*disable)(void *layer); + struct sti_gdp_node_list node_list[GDP_NODE_NB_BANK]; +}; + +struct sti_gdp *sti_gdp_create(struct device *dev, int id, + void __iomem *baseaddr); + +#endif diff --git a/drivers/gpu/drm/sti/sti_layer.h b/drivers/gpu/drm/sti/sti_layer.h index ed8386a..45cd1ea 100644 --- a/drivers/gpu/drm/sti/sti_layer.h +++ b/drivers/gpu/drm/sti/sti_layer.h @@ -10,6 +10,7 @@ #define _STI_LAYER_H_
#include <drm/drmP.h> +#include "sti_gdp.h"
#define to_sti_layer(x) container_of(x, struct sti_layer, plane)
@@ -67,6 +68,7 @@ struct sti_fps_info { * @offsets: offset of 'planes' * @paddr: physical address of the input buffer * @fps_info: frame per second info + * @gdp: related GDP (if the layer is a GDP) */ struct sti_layer { struct drm_plane plane; @@ -74,6 +76,7 @@ struct sti_layer { struct drm_display_mode *mode; enum sti_layer_desc desc; int zorder; + int mixer_id; bool enabled; int src_x, src_y; int src_w, src_h; @@ -84,12 +87,14 @@ struct sti_layer { int offsets[4]; dma_addr_t paddr; struct sti_fps_info fps_info; + struct sti_gdp *gdp; };
struct sti_layer *sti_layer_create(struct device *dev, int desc, void __iomem *baseaddr); int sti_layer_prepare(struct sti_layer *layer, struct drm_framebuffer *fb, struct drm_display_mode *mode, + int mixer_id, int dest_x, int dest_y, int dest_w, int dest_h, int src_x, int src_y,
VIDeo plug are one of the compositor input sub-devices. VID are dedicated to video inputs like YUV plans.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Makefile | 1 + drivers/gpu/drm/sti/sti_layer.h | 4 ++ drivers/gpu/drm/sti/sti_vid.c | 138 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_vid.h | 33 ++++++++++ 4 files changed, 176 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_vid.c create mode 100644 drivers/gpu/drm/sti/sti_vid.h
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index cc04475..b9a3b74 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -6,6 +6,7 @@ stidrm-y := sti_tvout.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ sti_gdp.o \ + sti_vid.o \ sti_ddc.o
obj-$(CONFIG_VTAC_STI) += sti_vtac_tx.o sti_vtac_rx.o diff --git a/drivers/gpu/drm/sti/sti_layer.h b/drivers/gpu/drm/sti/sti_layer.h index 45cd1ea..bf3a14f 100644 --- a/drivers/gpu/drm/sti/sti_layer.h +++ b/drivers/gpu/drm/sti/sti_layer.h @@ -11,6 +11,7 @@
#include <drm/drmP.h> #include "sti_gdp.h" +#include "sti_vid.h"
#define to_sti_layer(x) container_of(x, struct sti_layer, plane)
@@ -58,6 +59,7 @@ struct sti_fps_info { * @mode: display mode * @desc: layer type & id * @zorder: layer z-order + * @mixer_id: id of the mixer used to display the layer * @enabled: to know if the layer is active or not * @src_x src_y: coordinates of the input (fb) area * @src_w src_h: size of the input (fb) area @@ -69,6 +71,7 @@ struct sti_fps_info { * @paddr: physical address of the input buffer * @fps_info: frame per second info * @gdp: related GDP (if the layer is a GDP) + * @vid: related VID (if the layer is a VID/VDP) */ struct sti_layer { struct drm_plane plane; @@ -88,6 +91,7 @@ struct sti_layer { dma_addr_t paddr; struct sti_fps_info fps_info; struct sti_gdp *gdp; + struct sti_vid *vid; };
struct sti_layer *sti_layer_create(struct device *dev, int desc, diff --git a/drivers/gpu/drm/sti/sti_vid.c b/drivers/gpu/drm/sti/sti_vid.c new file mode 100644 index 0000000..710665d --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vid.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Fabien Dessenne fabien.dessenne@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <drm/drmP.h> + +#include "sti_vid.h" +#include "sti_layer.h" +#include "sti_vtg_utils.h" + +/* Registers */ +#define VID_CTL 0x00 +#define VID_ALP 0x04 +#define VID_CLF 0x08 +#define VID_VPO 0x0C +#define VID_VPS 0x10 +#define VID_KEY1 0x28 +#define VID_KEY2 0x2C +#define VID_MPR0 0x30 +#define VID_MPR1 0x34 +#define VID_MPR2 0x38 +#define VID_MPR3 0x3C +#define VID_MST 0x68 +#define VID_BC 0x70 +#define VID_TINT 0x74 +#define VID_CSAT 0x78 + +/* Registers values */ +#define VID_CTL_IGNORE ((1<<30) | (1<<31)) +#define VID_CTL_PSI_ENABLE ((1<<2) | (1<<1) | (1<<0)) +#define VID_ALP_OPAQUE 0x00000080 +#define VID_BC_DFLT 0x00008000 +#define VID_TINT_DFLT 0x00000000 +#define VID_CSAT_DFLT 0x00000080 +/* YCbCr to RGB BT709: + * R = Y+1.5391Cr + * G = Y-0.4590Cr-0.1826Cb + * B = Y+1.8125Cb */ +#define VID_MPR0_BT709 0x0A800000 +#define VID_MPR1_BT709 0x0AC50000 +#define VID_MPR2_BT709 0x07150545 +#define VID_MPR3_BT709 0x00000AE8 + +static int sti_vid_prepare_layer(void *lay, bool first_prepare) +{ + u32 val; + struct sti_layer *layer = (struct sti_layer *)lay; + struct sti_vid *vid = layer->vid; + + /* Unmask */ + val = readl(vid->regs + VID_CTL); + val &= ~VID_CTL_IGNORE; + writel(val, vid->regs + VID_CTL); + + return 0; +} + +static int sti_vid_commit_layer(void *lay) +{ + struct sti_layer *layer = (struct sti_layer *)lay; + struct sti_vid *vid = layer->vid; + struct drm_display_mode *mode = layer->mode; + u32 ydo, xdo, yds, xds; + + ydo = sti_vtg_get_line_number(*mode, layer->dst_y); + yds = sti_vtg_get_line_number(*mode, layer->dst_y + layer->dst_h - 1); + xdo = sti_vtg_get_pixel_number(*mode, layer->dst_x); + xds = sti_vtg_get_pixel_number(*mode, layer->dst_x + layer->dst_w - 1); + + writel((ydo << 16) | xdo, vid->regs + VID_VPO); + writel((yds << 16) | xds, vid->regs + VID_VPS); + + return 0; +} + +static int sti_vid_disable_layer(void *lay) +{ + u32 val; + struct sti_layer *layer = (struct sti_layer *)lay; + struct sti_vid *vid = layer->vid; + + /* Mask */ + val = readl(vid->regs + VID_CTL); + val |= VID_CTL_IGNORE; + writel(val, vid->regs + VID_CTL); + + return 0; +} + +static void sti_vid_set_default(struct sti_vid *vid) +{ + /* Enable PSI, Mask layer */ + writel(VID_CTL_PSI_ENABLE | VID_CTL_IGNORE, vid->regs + VID_CTL); + + /* Opaque */ + writel(VID_ALP_OPAQUE, vid->regs + VID_ALP); + + /* Color conversion parameters */ + writel(VID_MPR0_BT709, vid->regs + VID_MPR0); + writel(VID_MPR1_BT709, vid->regs + VID_MPR1); + writel(VID_MPR2_BT709, vid->regs + VID_MPR2); + writel(VID_MPR3_BT709, vid->regs + VID_MPR3); + + /* Brightness, contrast, tint, saturation */ + writel(VID_BC_DFLT, vid->regs + VID_BC); + writel(VID_TINT_DFLT, vid->regs + VID_TINT); + writel(VID_CSAT_DFLT, vid->regs + VID_CSAT); +} + +struct sti_vid *sti_vid_create(struct device *dev, void __iomem *baseaddr) +{ + struct sti_vid *vid; + + DRM_DEBUG_DRIVER("\n"); + + vid = devm_kzalloc(dev, sizeof(*vid), GFP_KERNEL); + if (!vid) { + DRM_ERROR("Failed to allocate memory for VID\n"); + return NULL; + } + + vid->dev = dev; + vid->regs = baseaddr; + vid->prepare = sti_vid_prepare_layer; + vid->commit = sti_vid_commit_layer; + vid->disable = sti_vid_disable_layer; + /* As the VID input is HW-mapped to the VDP output, the supported + * formats are under the VDP control */ + vid->get_formats = NULL; + vid->get_nb_formats = NULL; + + /* Set default configuration (static) */ + sti_vid_set_default(vid); + + return vid; +} diff --git a/drivers/gpu/drm/sti/sti_vid.h b/drivers/gpu/drm/sti/sti_vid.h new file mode 100644 index 0000000..dd4fd95 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vid.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Fabien Dessenne fabien.dessenne@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_VID_H_ +#define _STI_VID_H_ + +/* + * STI VID structure + * + * @device: driver device + * @regs: subdevice register + * @get_formats: get VID supported formats + * @get_nb_formats: get number of format supported + * @prepare: prepare VID before rendering + * @commit: set VID for rendering + * @disable: disable VID + */ +struct sti_vid { + struct device *dev; + void __iomem *regs; + const uint32_t* (*get_formats)(void); + int (*get_nb_formats)(void); + int (*prepare)(void *layer, bool first_prepare); + int (*commit)(void *layer); + int (*disable)(void *layer); +}; + +struct sti_vid *sti_vid_create(struct device *dev, void __iomem *baseaddr); + +#endif
Mixer hardware IP is responsible of mixing the different inputs layers. Z-order is managed by the mixer. We could 2 mixers: one for main path and one for auxilary path
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Makefile | 1 + drivers/gpu/drm/sti/sti_mixer.c | 241 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_mixer.h | 52 +++++++++ 3 files changed, 294 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_mixer.c create mode 100644 drivers/gpu/drm/sti/sti_mixer.h
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index b9a3b74..3d52d2a 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -5,6 +5,7 @@ stidrm-y := sti_tvout.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ + sti_mixer.o \ sti_gdp.o \ sti_vid.o \ sti_ddc.o diff --git a/drivers/gpu/drm/sti/sti_mixer.c b/drivers/gpu/drm/sti/sti_mixer.c new file mode 100644 index 0000000..b6c8214 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_mixer.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include "sti_mixer.h" +#include "sti_vtg_utils.h" + +/* Identity: G=Y , B=Cb , R=Cr */ +static const u32 MixerColorSpaceMatIdentity[] = { + 0x10000000, 0x00000000, 0x10000000, 0x00001000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 +}; + +/* regs offset */ +#define GAM_MIXER_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 (1 << 0) +#define GAM_CTL_VID0_MASK (1 << 1) +#define GAM_CTL_VID1_MASK (1 << 2) +#define GAM_CTL_GDP0_MASK (1 << 3) +#define GAM_CTL_GDP1_MASK (1 << 4) +#define GAM_CTL_GDP2_MASK (1 << 5) +#define GAM_CTL_GDP3_MASK (1 << 6) + +const char *sti_mixer_to_str(struct sti_mixer *mixer) +{ + switch (mixer->id) { + case STI_MIXER_MAIN: + return "MAIN_MIXER"; + case STI_MIXER_AUX: + return "AUX_MIXER"; + default: + return "<UNKNOWN MIXER>"; + } +} + +static inline u32 sti_mixer_reg_read(struct sti_mixer *mixer, u32 reg_id) +{ + return readl(mixer->regs + reg_id); +} + +static inline void sti_mixer_reg_write(struct sti_mixer *mixer, + u32 reg_id, u32 val) +{ + writel(val, mixer->regs + reg_id); +} + +static inline void sti_mixer_reg_writemask(struct sti_mixer *mixer, + u32 reg_id, u32 val, u32 mask) +{ + u32 old = sti_mixer_reg_read(mixer, reg_id); + + val = (val & mask) | (old & ~mask); + writel(val, mixer->regs + reg_id); +} + +void sti_mixer_set_background_status(struct sti_mixer *mixer, bool enable) +{ + sti_mixer_reg_writemask(mixer, + GAM_MIXER_CTL, enable, GAM_CTL_BACK_MASK); +} + +static void sti_mixer_set_background_color(struct sti_mixer *mixer, + int red, int green, int blue) +{ + u32 val = (red << 16) | (green << 8) | blue; + + sti_mixer_reg_write(mixer, GAM_MIXER_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; + + 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); + sti_mixer_reg_writemask(mixer, GAM_MIXER_CRB, layer_id, mask); + + 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 -1; + } + val = status ? mask : 0; + sti_mixer_reg_writemask(mixer, GAM_MIXER_CTL, val, mask); + return 0; +} + +void sti_mixer_set_matrix(struct sti_mixer *mixer) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(MixerColorSpaceMatIdentity); i++) + sti_mixer_reg_write(mixer, GAM_MIXER_MX0 + (i * 4), + MixerColorSpaceMatIdentity[i]); +} + +struct sti_mixer *sti_mixer_create(struct device *dev, int id, + void __iomem *baseaddr) +{ + struct sti_mixer *mixer = devm_kzalloc(dev, sizeof(*mixer), GFP_KERNEL); + struct device_node *np = dev->of_node; + + dev_dbg(dev, "%s\n", __func__); + if (!mixer) { + DRM_ERROR("Failed to allocated memory for mixer\n"); + return NULL; + } + mixer->regs = baseaddr; + mixer->dev = dev; + mixer->id = id; + + if (of_device_is_compatible(np, "st,stih416-compositor")) + sti_mixer_set_matrix(mixer); + + DRM_DEBUG_DRIVER("%s created. Regs=%p\n", + sti_mixer_to_str(mixer), mixer->regs); + + return mixer; +} diff --git a/drivers/gpu/drm/sti/sti_mixer.h b/drivers/gpu/drm/sti/sti_mixer.h new file mode 100644 index 0000000..a05e21f --- /dev/null +++ b/drivers/gpu/drm/sti/sti_mixer.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_MIXER_H_ +#define _STI_MIXER_H_ + +#include <drm/drmP.h> + +#include "sti_layer.h" + +#define to_sti_mixer(x) container_of(x, struct sti_mixer, drm_crtc) + +/* + * STI Mixer subdevice structure + * + * @dev: driver device + * @regs: mixer registers + * @id: id of the mixer + * @drm_crtc: crtc object link to the mixer + */ +struct sti_mixer { + struct device *dev; + void __iomem *regs; + int id; + struct drm_crtc drm_crtc; +}; + +const char *sti_mixer_to_str(struct sti_mixer *mixer); + +struct sti_mixer *sti_mixer_create(struct device *dev, int id, + void __iomem *baseaddr); + +int sti_mixer_set_layer_status(struct sti_mixer *mixer, + struct sti_layer *layer, bool status); +int sti_mixer_set_layer_depth(struct sti_mixer *mixer, struct sti_layer *layer); +int sti_mixer_active_video_area(struct sti_mixer *mixer, + struct drm_display_mode *mode); + +void sti_mixer_set_background_status(struct sti_mixer *mixer, bool enable); + +/* depth in Cross-bar control = z order */ +#define GAM_MIXER_NB_DEPTH_LEVEL 7 + +#define STI_MIXER_MAIN 0 +#define STI_MIXER_AUX 1 + +#endif
Compositor control all the input sub-devices and the mixer. It is the main entry point for composition. Layer interface is used to control the layer.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Kconfig | 1 + drivers/gpu/drm/sti/Makefile | 2 + drivers/gpu/drm/sti/sti_compositor.c | 219 +++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_compositor.h | 84 ++++++++++ drivers/gpu/drm/sti/sti_layer.c | 309 +++++++++++++++++++++++++++++++++++ 5 files changed, 615 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 87e6128..76c2e4f 100644 --- a/drivers/gpu/drm/sti/Kconfig +++ b/drivers/gpu/drm/sti/Kconfig @@ -1,6 +1,7 @@ config DRM_STI bool "DRM Support for STMicroelectronics SoC stiH41x Series" depends on DRM && (SOC_STIH415 || SOC_STIH416 || ARCH_MULTIPLATFORM) + select DRM_KMS_CMA_HELPER help Choose this option to enable DRM on STM stiH41x chipset
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 3d52d2a..3b804d4 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -5,6 +5,8 @@ stidrm-y := sti_tvout.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ + sti_compositor.o \ + sti_layer.o \ sti_mixer.o \ sti_gdp.o \ sti_vid.o \ diff --git a/drivers/gpu/drm/sti/sti_compositor.c b/drivers/gpu/drm/sti/sti_compositor.c new file mode 100644 index 0000000..0c9ef6d --- /dev/null +++ b/drivers/gpu/drm/sti/sti_compositor.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/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" + +static const struct of_device_id compositor_match_types[]; + +/* + * stiH407 compositor properties + */ +struct sti_compositor_data stih407_compositor_data = { + .nb_subdev = 6, + .subdev_desc = { + {STI_GPD_SUBDEV, (int)STI_GDP_0, 0x100}, + {STI_GPD_SUBDEV, (int)STI_GDP_1, 0x200}, + {STI_GPD_SUBDEV, (int)STI_GDP_2, 0x300}, + {STI_GPD_SUBDEV, (int)STI_GDP_3, 0x400}, + {STI_VID_SUBDEV, (int)STI_VID_0, 0x700}, + {STI_MIXER_MAIN_SUBDEV, STI_MIXER_MAIN, 0xC00} + }, +}; + +/* + * stiH416 compositor properties + * Note: + * on stih416 MIXER_AUX has a different base address from MIXER_MAIN + * Moreover, GDPx is different for Main and Aux Mixer. So this subdev map does + * not fit for stiH416 if we want to enable the MIXER_AUX. + */ +struct sti_compositor_data stih416_compositor_data = { + .nb_subdev = 3, + .subdev_desc = { + {STI_GPD_SUBDEV, (int)STI_GDP_0, 0x100}, + {STI_GPD_SUBDEV, (int)STI_GDP_1, 0x200}, + {STI_MIXER_MAIN_SUBDEV, STI_MIXER_MAIN, 0xC00} + }, +}; + +static int sti_compositor_init_subdev(struct sti_compositor *compo, + struct sti_compositor_subdev_descriptor *desc, + int array_size) +{ + int i, mixer_id = 0, layer_id = 0; + + dev_dbg(compo->dev, "%s\n", __func__); + for (i = 0; i < array_size; i++) { + switch (desc[i].type) { + case STI_MIXER_MAIN_SUBDEV: + case STI_MIXER_AUX_SUBDEV: + compo->mixer[mixer_id++] = + sti_mixer_create(compo->dev, desc[i].id, + compo->regs + desc[i].offset); + break; + case STI_GPD_SUBDEV: + case STI_VID_SUBDEV: + compo->layer[layer_id++] = + sti_layer_create(compo->dev, desc[i].id, + compo->regs + desc[i].offset); + break; + /* case STI_CURSOR_SUBDEV : TODO */ + default: + DRM_ERROR("Unknow subdev compoment type\n"); + return 1; + } + + } + compo->nb_mixers = mixer_id; + compo->nb_layers = layer_id; + + return 0; +} + +static int sti_compositor_bind(struct device *dev, struct device *master, + void *data) +{ + return 0; +} + +static void sti_compositor_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops sti_compositor_ops = { + .bind = sti_compositor_bind, + .unbind = sti_compositor_unbind, +}; + +static int sti_compositor_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct sti_compositor *compo; + struct resource *res; + int err; + + DRM_INFO("%s\n", __func__); + compo = devm_kzalloc(dev, sizeof(*compo), GFP_KERNEL); + if (!compo) { + DRM_ERROR("Failed to allocate compositor context\n"); + return -ENOMEM; + } + DRM_DEBUG_DRIVER("Compositor %p\n", compo); + compo->dev = dev; + + /* populate data structure depending on compatibility */ + BUG_ON(!of_match_node(compositor_match_types, np)->data); + + memcpy(&compo->data, of_match_node(compositor_match_types, np)->data, + sizeof(struct sti_compositor_data)); + + /* Get Memory ressources */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + DRM_ERROR("Get memory resource failed\n"); + return -ENXIO; + } + compo->regs = devm_ioremap(dev, res->start, resource_size(res)); + if (compo->regs == NULL) { + DRM_ERROR("Register mapping failed\n"); + return -ENXIO; + } + + /* Get clock resources */ + compo->clk_compo_main = devm_clk_get(dev, "compo_main"); + if (IS_ERR(compo->clk_compo_main)) { + DRM_ERROR("Cannot get compo_main clock\n"); + return PTR_ERR(compo->clk_compo_main); + } + + compo->clk_compo_aux = devm_clk_get(dev, "compo_aux"); + if (IS_ERR(compo->clk_compo_aux)) { + DRM_ERROR("Cannot get compo_aux clock\n"); + return PTR_ERR(compo->clk_compo_aux); + } + + compo->clk_pix_main = devm_clk_get(dev, "pix_main"); + if (IS_ERR(compo->clk_pix_main)) { + DRM_ERROR("Cannot get pix_main clock\n"); + return PTR_ERR(compo->clk_pix_main); + } + + compo->clk_pix_aux = devm_clk_get(dev, "pix_aux"); + if (IS_ERR(compo->clk_pix_aux)) { + DRM_ERROR("Cannot get pix_aux clock\n"); + return PTR_ERR(compo->clk_pix_aux); + } + + /* Get reset resources */ + compo->rst_main = devm_reset_control_get(dev, "compo-main"); + /* Take compo main out of reset */ + if (!IS_ERR(compo->rst_main)) + reset_control_deassert(compo->rst_main); + + compo->rst_aux = devm_reset_control_get(dev, "compo-aux"); + /* Take compo aux out of reset */ + if (!IS_ERR(compo->rst_aux)) + reset_control_deassert(compo->rst_aux); + + /* Initialize compositor subdevices */ + err = sti_compositor_init_subdev(compo, compo->data.subdev_desc, + compo->data.nb_subdev); + if (err) + return err; + + platform_set_drvdata(pdev, compo); + + return component_add(&pdev->dev, &sti_compositor_ops); +} + +static int sti_compositor_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sti_compositor_ops); + return 0; +} + +static const struct of_device_id compositor_match_types[] = { + { + .compatible = "st,stih416-compositor", + .data = &stih416_compositor_data, + }, + { + .compatible = "st,stih407-compositor", + .data = &stih407_compositor_data, + }, + { /* end node */ } + +}; +MODULE_DEVICE_TABLE(of, compositor_match_types); + +static struct platform_driver sti_compositor_driver = { + .driver = { + .name = "sti-compositor", + .owner = THIS_MODULE, + .of_match_table = compositor_match_types, + }, + .probe = sti_compositor_probe, + .remove = sti_compositor_remove, +}; + +module_platform_driver(sti_compositor_driver); + +MODULE_AUTHOR("Benjamin Gaignard benjamin.gaignard@st.com"); +MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sti/sti_compositor.h b/drivers/gpu/drm/sti/sti_compositor.h new file mode 100644 index 0000000..599a60d --- /dev/null +++ b/drivers/gpu/drm/sti/sti_compositor.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_COMPOSITOR_H_ +#define _STI_COMPOSITOR_H_ + +#include <linux/clk.h> +#include <linux/kernel.h> + +#include "sti_layer.h" +#include "sti_mixer.h" + +#define STI_MAX_LAYER 8 +#define STI_MAX_MIXER 2 + +enum sti_compositor_subdev_type { + STI_MIXER_MAIN_SUBDEV, + STI_MIXER_AUX_SUBDEV, + STI_GPD_SUBDEV, + STI_VID_SUBDEV, + STI_CURSOR_SUBDEV, +}; + +struct sti_compositor_subdev_descriptor { + enum sti_compositor_subdev_type type; + int id; + unsigned int offset; +}; + +/* + * STI Compositor data structure + * + * @nb_subdev: number of subdevices supported by the compositor + * @subdev_desc: subdev list description + */ +#define MAX_SUBDEV 9 +struct sti_compositor_data { + int nb_subdev; + struct sti_compositor_subdev_descriptor subdev_desc[MAX_SUBDEV]; +}; + +/* + * STI Compositor structure + * + * @dev: driver device + * @regs: registers (main) + * @data: device data + * @clk_compo_main: clock for main compo + * @clk_compo_aux: clock for aux compo + * @clk_pix_main: pixel clock for main path + * @clk_pix_aux: pixel clock for aux path + * @rst_main: reset control of the main path + * @rst_aux: reset control of the aux path + * @mixer: array of mixers + * @layer: array of layers + * @nb_mixers: number of mixers for this compositor + * @nb_layers: number of layers (GDP,VID,...) for this compositor + * @enable: true if compositor is enable else false + * @vtg_vblank_nb: callback for VTG VSYNC notification + */ +struct sti_compositor { + struct device *dev; + void __iomem *regs; + struct sti_compositor_data data; + struct clk *clk_compo_main; + struct clk *clk_compo_aux; + struct clk *clk_pix_main; + struct clk *clk_pix_aux; + struct reset_control *rst_main; + struct reset_control *rst_aux; + struct sti_mixer *mixer[STI_MAX_MIXER]; + struct sti_layer *layer[STI_MAX_LAYER]; + int nb_mixers; + int nb_layers; + bool enable; + struct notifier_block vtg_vblank_nb; +}; + +#endif diff --git a/drivers/gpu/drm/sti/sti_layer.c b/drivers/gpu/drm/sti/sti_layer.c new file mode 100644 index 0000000..54c8694 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_layer.c @@ -0,0 +1,309 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include "sti_layer.h" +#include "sti_compositor.h" +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_cma_helper.h> + +#define STI_FPS_INTERVAL_MS 3000 + +struct sti_layer *sti_layer_find_layer(struct sti_layer *layer[], + enum sti_layer_desc desc) +{ + int i; + + for (i = 0; i < STI_MAX_LAYER; i++) + if (layer[i] && (layer[i]->desc == desc)) + return layer[i]; + return NULL; +} + +const char *sti_layer_to_str(struct sti_layer *layer) +{ + switch (layer->desc) { + case STI_GDP_0: + return "GDP0"; + case STI_GDP_1: + return "GDP1"; + case STI_GDP_2: + return "GDP2"; + case STI_GDP_3: + return "GDP3"; + case STI_VID_0: + return "VID0"; + case STI_VID_1: + return "VID1"; + case STI_CURSOR: + return "CURSOR"; + default: + return "<UNKNOWN LAYER>"; + } +} + +static int timespec_ms_diff(struct timespec lhs, struct timespec rhs) +{ + struct timespec tmp_ts = timespec_sub(lhs, rhs); + u64 tmp_ns = (u64) timespec_to_ns(&tmp_ts); + + do_div(tmp_ns, NSEC_PER_MSEC); + + return (u32) tmp_ns; +} + +static void sti_layer_update_fps(struct sti_layer *layer) +{ + struct timespec now; + struct sti_fps_info *fps; + int fpks, ms_since_last, num_frames; + + getrawmonotonic(&now); + + fps = &layer->fps_info; + fps->curr_frame_counter++; + ms_since_last = timespec_ms_diff(now, fps->last_timestamp); + num_frames = fps->curr_frame_counter - fps->last_frame_counter; + + if (num_frames > 1 && ms_since_last >= STI_FPS_INTERVAL_MS) { + fps->last_timestamp = now; + fps->last_frame_counter = fps->curr_frame_counter; + fpks = (num_frames * 1000000) / ms_since_last; + if (fps->output) + DRM_INFO("%s @ %d.%.3d fps\n", sti_layer_to_str(layer), + fpks / 1000, fpks % 1000); + } +} + +struct sti_layer *sti_layer_create(struct device *dev, int desc, + void __iomem *baseaddr) +{ + struct sti_layer *layer; + + layer = devm_kzalloc(dev, sizeof(*layer), GFP_KERNEL); + if (!layer) { + DRM_ERROR("Failed to allocate memory for layer\n"); + return NULL; + } + + switch (desc & STI_LAYER_TYPE_MASK) { + case STI_GDP: + layer->gdp = sti_gdp_create(dev, desc, baseaddr); + if (!layer->gdp) + goto err; + break; + case STI_VID: + layer->vid = sti_vid_create(dev, baseaddr); + if (!layer->vid) + goto err; + break; + default: + goto err; + } + + layer->desc = desc; + DRM_DEBUG_DRIVER("%s created\n", sti_layer_to_str(layer)); + + return layer; +err: + devm_kfree(dev, layer); + DRM_ERROR("Failed to create layer\n"); + return NULL; +} + +int sti_layer_prepare(struct sti_layer *layer, struct drm_framebuffer *fb, + struct drm_display_mode *mode, int mixer_id, + int dest_x, int dest_y, int dest_w, int dest_h, + int src_x, int src_y, int src_w, int src_h) +{ + int ret, i; + struct drm_gem_cma_object *cma_obj; + + if (!layer || !fb || !mode) { + DRM_ERROR("Null fb, layer or mode\n"); + return 1; + } + + cma_obj = drm_fb_cma_get_gem_obj(fb, 0); + if (!cma_obj) { + DRM_ERROR("Can't get CMA GEM object for fb\n"); + return 1; + } + + layer->fb = fb; + layer->mode = mode; + layer->mixer_id = mixer_id; + layer->dst_x = dest_x; + layer->dst_y = dest_y; + layer->dst_w = clamp_val(dest_w, 0, mode->crtc_hdisplay - dest_x); + layer->dst_h = clamp_val(dest_h, 0, mode->crtc_vdisplay - dest_y); + layer->src_x = src_x; + layer->src_y = src_y; + layer->src_w = src_w; + layer->src_h = src_h; + layer->format = fb->pixel_format; + layer->paddr = cma_obj->paddr; + for (i = 0; i < 4; i++) { + layer->pitches[i] = fb->pitches[i]; + layer->offsets[i] = fb->offsets[i]; + } + + DRM_DEBUG_DRIVER("%s is associated with mixer_id %d\n", + sti_layer_to_str(layer), + layer->mixer_id); + DRM_DEBUG_DRIVER("%s dst=(%dx%d)@(%d,%d) - src=(%dx%d)@(%d,%d)\n", + sti_layer_to_str(layer), + layer->dst_w, layer->dst_h, layer->dst_x, layer->dst_y, + layer->src_w, layer->src_h, layer->src_x, + layer->src_y); + + DRM_DEBUG_DRIVER("drm FB:%d format:%.4s phys@:0x%lx\n", fb->base.id, + (char *)&layer->format, (unsigned long)layer->paddr); + + /* Prepare layer specificities */ + switch (layer->desc & STI_LAYER_TYPE_MASK) { + case STI_GDP: + if (!layer->gdp) + goto err_no_prepare; + ret = layer->gdp->prepare(layer, !layer->enabled); + break; + case STI_VID: + if (!layer->vid) + goto err_no_prepare; + ret = layer->vid->prepare(layer, !layer->enabled); + break; + default: + goto err_no_prepare; + } + + if (!ret) + layer->enabled = true; + + return ret; + +err_no_prepare: + DRM_ERROR("Cannot prepare\n"); + return 1; +} + +int sti_layer_commit(struct sti_layer *layer) +{ + int ret; + + if (!layer) + return 1; + + switch (layer->desc & STI_LAYER_TYPE_MASK) { + case STI_GDP: + if (!layer->gdp) + goto err_no_commit; + ret = layer->gdp->commit(layer); + break; + case STI_VID: + if (!layer->vid) + goto err_no_commit; + ret = layer->vid->commit(layer); + break; + default: + goto err_no_commit; + } + + if (!ret) + sti_layer_update_fps(layer); + + return ret; + +err_no_commit: + DRM_ERROR("Cannot commit\n"); + return 1; +} + +int sti_layer_disable(struct sti_layer *layer) +{ + int ret; + + DRM_DEBUG_DRIVER("%s\n", sti_layer_to_str(layer)); + if (!layer) + return 1; + + if (!layer->enabled) + return 0; + + switch (layer->desc & STI_LAYER_TYPE_MASK) { + case STI_GDP: + if (!layer->gdp) + goto err_no_disable; + ret = layer->gdp->disable(layer); + break; + case STI_VID: + if (!layer->vid) + goto err_no_disable; + ret = layer->vid->disable(layer); + break; + default: + goto err_no_disable; + } + + if (!ret) + layer->enabled = false; + else + DRM_ERROR("Disable failed\n"); + + return ret; + +err_no_disable: + DRM_ERROR("Cannot disable\n"); + return 1; +} + +const uint32_t *sti_layer_get_formats(struct sti_layer *layer) +{ + const uint32_t *(*get_formats)(void) = NULL; + + if (!layer) + return NULL; + + switch (layer->desc & STI_LAYER_TYPE_MASK) { + case STI_GDP: + if (layer->gdp) + get_formats = layer->gdp->get_formats; + break; + default: + break; + } + + if (!get_formats) { + DRM_ERROR("Cannot get formats\n"); + return NULL; + } + + return get_formats(); +} + +int sti_layer_get_nb_formats(struct sti_layer *layer) +{ + int (*get_nb_formats)(void) = NULL; + + if (!layer) + return 0; + + switch (layer->desc & STI_LAYER_TYPE_MASK) { + case STI_GDP: + if (layer->gdp) + get_nb_formats = layer->gdp->get_nb_formats; + break; + default: + break; + } + + if (!get_nb_formats) { + DRM_ERROR("Cannot get nb formats\n"); + return 0; + } + + return get_nb_formats(); +}
Allow to get more detailed debug information on GDP
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/sti_drm_drv.h | 36 ++++++ drivers/gpu/drm/sti/sti_gdp.c | 235 ++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_gdp.h | 2 + 3 files changed, 273 insertions(+) create mode 100644 drivers/gpu/drm/sti/sti_drm_drv.h
diff --git a/drivers/gpu/drm/sti/sti_drm_drv.h b/drivers/gpu/drm/sti/sti_drm_drv.h new file mode 100644 index 0000000..5660196 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_drv.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_DRM_DRV_H_ +#define _STI_DRM_DRV_H_ + +#include <linux/platform_device.h> + +#include <drm/drmP.h> + +#include "sti_compositor.h" +#include "sti_tvout.h" + +/* + * STI drm private structure + * This structure is stored as private in the drm_device + * + * @compo: compositor + * @tvout: TV OUT + * @pageflip_evt_list: list of pending page flip requests + * @plane_zorder_property: z-order property for CRTC planes + */ +struct sti_drm_private { + struct sti_compositor *compo; + struct sti_tvout *tvout; + struct list_head pageflip_evt_list; + struct drm_property *plane_zorder_property; +}; + +#define STI_MAX_FB_HEIGHT 4096 +#define STI_MAX_FB_WIDTH 4096 + +#endif diff --git a/drivers/gpu/drm/sti/sti_gdp.c b/drivers/gpu/drm/sti/sti_gdp.c index 00ff7f9..095d707 100644 --- a/drivers/gpu/drm/sti/sti_gdp.c +++ b/drivers/gpu/drm/sti/sti_gdp.c @@ -9,9 +9,11 @@ #include <linux/clk.h> #include <linux/dma-mapping.h>
+#include "sti_drm_drv.h" #include "sti_layer.h" #include "sti_gdp.h" #include "sti_vtg_utils.h" +#include "sti_compositor.h"
#define ENA_COLOR_FILL (1 << 8) #define WAIT_NEXT_VSYNC (1 << 31) @@ -29,6 +31,25 @@ #define GDP_YCBR422R 0x12 #define GDP_AYCBR8888 0x15
+#define GDP2STR(fmt) { GDP_ ## fmt, #fmt } + +static struct gdp_format_to_str { + int format; + char name[20]; +} sti_gdp_format_to_str[] = { + GDP2STR(RGB565), + GDP2STR(RGB888), + GDP2STR(RGB888_32), + GDP2STR(ARGB8565), + GDP2STR(ARGB8888), + GDP2STR(ARGB1555), + GDP2STR(ARGB4444), + GDP2STR(CLUT8), + GDP2STR(YCBR888), + GDP2STR(YCBR422R), + GDP2STR(AYCBR8888) + }; + #define GAM_GDP_CTL_OFFSET 0x00 #define GAM_GDP_AGC_OFFSET 0x04 #define GAM_GDP_VPO_OFFSET 0x0C @@ -137,6 +158,9 @@ static struct sti_gdp_node_list *sti_gdp_get_free_nodes(struct sti_layer *layer) return &gdp->node_list[i];
end: + DRM_DEBUG_DRIVER("Warning, inconsistent NVN for %s: 0x%08X\n", + sti_layer_to_str(layer), hw_nvn); + return &gdp->node_list[0]; }
@@ -169,6 +193,8 @@ struct sti_gdp_node_list *sti_gdp_get_current_nodes(struct sti_layer *layer) return &gdp->node_list[i];
end: + DRM_DEBUG_DRIVER("Warning, NVN 0x%08X for %s does not match any node\n", + hw_nvn, sti_layer_to_str(layer)); return NULL; }
@@ -198,6 +224,9 @@ static int sti_gdp_prepare_layer(void *lay, bool first_prepare) top_field = list->top_field; btm_field = list->btm_field;
+ dev_dbg(dev, "%s %s top_node:0x%p btm_node:0x%p\n", __func__, + sti_layer_to_str(layer), top_field, btm_field); + /* Build the top field from layer params */ top_field->gam_gdp_agc = GAM_GDP_AGC_FULL_RANGE; top_field->gam_gdp_ctl = WAIT_NEXT_VSYNC; @@ -294,6 +323,9 @@ static int sti_gdp_commit_layer(void *lay) u32 dma_updated_btm = virt_to_dma(gdp->dev, updated_btm_node); struct sti_gdp_node_list *curr_list = sti_gdp_get_current_nodes(layer);
+ dev_dbg(gdp->dev, "%s %s top/btm_node:0x%p/0x%p\n", __func__, + sti_layer_to_str(layer), + updated_top_node, updated_btm_node); dev_dbg(gdp->dev, "Current NVN:0x%X\n", readl(gdp->regs + GAM_GDP_NVN_OFFSET)); dev_dbg(gdp->dev, "Posted buff: %lx current buff: %x\n", @@ -303,6 +335,8 @@ static int sti_gdp_commit_layer(void *lay) if (curr_list == NULL) { /* First update or invalid node should directly write in the * hw register */ + DRM_DEBUG_DRIVER("%s first update (or invalid node)", + sti_layer_to_str(layer)); writel(gdp->is_curr_top == true ? dma_updated_btm : dma_updated_top, gdp->regs + GAM_GDP_NVN_OFFSET); @@ -342,6 +376,8 @@ static int sti_gdp_disable_layer(void *lay) struct sti_layer *layer = (struct sti_layer *)lay; struct sti_gdp *gdp = layer->gdp;
+ DRM_DEBUG_DRIVER("%s\n", sti_layer_to_str(layer)); + /* Set the nodes as 'to be ignored on mixer' */ for (i = 0; i < GDP_NODE_NB_BANK; i++) { gdp->node_list[i].top_field->gam_gdp_ppt |= GAM_GDP_PPT_IGNORE; @@ -489,3 +525,202 @@ mem_err: devm_kfree(dev, gdp); return NULL; } + +static void sti_gdp_dbg_ctl(struct seq_file *m, int val) +{ + int i; + + seq_puts(m, "\tColor:"); + for (i = 0; i < ARRAY_SIZE(sti_gdp_format_to_str); i++) { + if (sti_gdp_format_to_str[i].format == (val & 0x1F)) { + seq_printf(m, sti_gdp_format_to_str[i].name); + break; + } + } + if (i == ARRAY_SIZE(sti_gdp_format_to_str)) + seq_puts(m, "<UNKNOWN>"); + + seq_printf(m, "\tWaitNextVsync:%d", val & WAIT_NEXT_VSYNC ? 1 : 0); +} + +static void sti_gdp_dbg_vpo(struct seq_file *m, int val) +{ + seq_printf(m, "\txdo:%4d\tydo:%4d", val & 0x0FFF, (val >> 16) & 0x0FFF); +} + +static void sti_gdp_dbg_vps(struct seq_file *m, int val) +{ + seq_printf(m, "\txds:%4d\tyds:%4d", val & 0x0FFF, (val >> 16) & 0x0FFF); +} + +static void sti_gdp_dbg_size(struct seq_file *m, int val) +{ + seq_printf(m, "\t%d x %d", val & 0x07FF, (val >> 16) & 0x07FF); +} + +static void sti_gdp_dbg_nvn(struct seq_file *m, struct device *dev, int val) +{ + seq_puts(m, "\tVirt @: "); + if (val) + seq_printf(m, "%p", dma_to_virt(dev, (dma_addr_t) val)); +} + +static void sti_gdp_dbg_ppt(struct seq_file *m, int val) +{ + if (val & GAM_GDP_PPT_IGNORE) + seq_puts(m, "\tNot displayed on mixer!"); +} + +static void sti_gdp_dbg_mst(struct seq_file *m, int val) +{ + if (val & 1) + seq_puts(m, "\tBUFFER UNDERFLOW!"); +} + +#define GDP_DBG_DUMP(reg) seq_printf(m, "\n " #reg "\t 0x%08X", \ + readl(gdp->regs + reg ## _OFFSET)) + +int sti_gdp_dbg_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_compositor *compo = dev_priv->compo; + int i, ret; + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret) + return ret; + + if (compo == NULL) { + seq_puts(m, "No compositor available\n"); + goto out; + } + + for (i = 0; i < compo->nb_layers; i++) { + struct drm_plane *plane; + struct drm_crtc *crtc; + struct drm_framebuffer *fb; + struct sti_layer *layer = compo->layer[i]; + struct sti_gdp *gdp = layer->gdp; + if ((layer->desc & STI_LAYER_TYPE_MASK) != STI_GDP) + continue; + seq_printf(m, "\n%s (associated with the mixer_id %d)", + sti_layer_to_str(layer), layer->mixer_id); + seq_printf(m, "\t %d frame updates", + layer->fps_info.curr_frame_counter); + + GDP_DBG_DUMP(GAM_GDP_CTL); + sti_gdp_dbg_ctl(m, readl(gdp->regs + GAM_GDP_CTL_OFFSET)); + GDP_DBG_DUMP(GAM_GDP_AGC); + GDP_DBG_DUMP(GAM_GDP_VPO); + sti_gdp_dbg_vpo(m, readl(gdp->regs + GAM_GDP_VPO_OFFSET)); + GDP_DBG_DUMP(GAM_GDP_VPS); + sti_gdp_dbg_vps(m, readl(gdp->regs + GAM_GDP_VPS_OFFSET)); + GDP_DBG_DUMP(GAM_GDP_PML); + GDP_DBG_DUMP(GAM_GDP_PMP); + GDP_DBG_DUMP(GAM_GDP_SIZE); + sti_gdp_dbg_size(m, readl(gdp->regs + GAM_GDP_SIZE_OFFSET)); + GDP_DBG_DUMP(GAM_GDP_NVN); + sti_gdp_dbg_nvn(m, gdp->dev, + readl(gdp->regs + GAM_GDP_NVN_OFFSET)); + GDP_DBG_DUMP(GAM_GDP_KEY1); + GDP_DBG_DUMP(GAM_GDP_KEY2); + GDP_DBG_DUMP(GAM_GDP_PPT); + sti_gdp_dbg_ppt(m, readl(gdp->regs + GAM_GDP_PPT_OFFSET)); + GDP_DBG_DUMP(GAM_GDP_CML); + GDP_DBG_DUMP(GAM_GDP_MST); + sti_gdp_dbg_mst(m, readl(gdp->regs + GAM_GDP_MST_OFFSET)); + seq_puts(m, "\n"); + + plane = &layer->plane; + if (!plane->base.id) { + seq_puts(m, " Not connected to any DRM PLANE\n"); + continue; + } + seq_printf(m, " Connected to DRM PLANE #%d which is:\n", + plane->base.id); + + crtc = plane->crtc; + if (!crtc) { + seq_puts(m, "\tNot connected to any DRM CRTC\n"); + continue; + } + seq_printf(m, "\tConnected to DRM CRTC #%d\n", crtc->base.id); + + fb = crtc->fb; + if (!fb) { + seq_puts(m, "\tNot connected to any DRM FB\n"); + continue; + } + seq_printf(m, "\tConnected to DRM FB #%d, %dx%d, %.4s\n", + fb->base.id, + fb->width, fb->height, (char *)&fb->pixel_format); + } + +out: + mutex_unlock(&dev->struct_mutex); + return 0; +} + +static void sti_gdp_node_dump_node(struct seq_file *m, + struct sti_gdp_node *node) +{ + seq_printf(m, "\t@:0x%p", node); + seq_printf(m, "\n\tCTL 0x%08X", node->gam_gdp_ctl); + sti_gdp_dbg_ctl(m, node->gam_gdp_ctl); + seq_printf(m, "\n\tAGC 0x%08X", node->gam_gdp_agc); + seq_printf(m, "\n\tVPO 0x%08X", node->gam_gdp_vpo); + sti_gdp_dbg_vpo(m, node->gam_gdp_vpo); + seq_printf(m, "\n\tVPS 0x%08X", node->gam_gdp_vps); + sti_gdp_dbg_vps(m, node->gam_gdp_vps); + seq_printf(m, "\n\tPML 0x%08X", node->gam_gdp_pml); + seq_printf(m, "\n\tPMP 0x%08X", node->gam_gdp_pmp); + seq_printf(m, "\n\tSIZE 0x%08X", node->gam_gdp_size); + sti_gdp_dbg_size(m, node->gam_gdp_size); + seq_printf(m, "\n\tNVN 0x%08X", node->gam_gdp_nvn); + seq_printf(m, "\n\tKEY1 0x%08X", node->gam_gdp_key1); + seq_printf(m, "\n\tKEY2 0x%08X", node->gam_gdp_key2); + seq_printf(m, "\n\tPPT 0x%08X", node->gam_gdp_ppt); + sti_gdp_dbg_ppt(m, node->gam_gdp_ppt); + seq_printf(m, "\n\tCML 0x%08X", node->gam_gdp_cml); + seq_puts(m, "\n"); +} + +int sti_gdp_node_dbg_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_compositor *compo = dev_priv->compo; + int i, ret; + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret) + return ret; + + if (!compo) { + seq_puts(m, "No compositor available\n"); + goto out; + } + + for (i = 0; i < compo->nb_layers; i++) { + int b; + struct sti_layer *layer = compo->layer[i]; + struct sti_gdp *gdp = layer->gdp; + if ((layer->desc & STI_LAYER_TYPE_MASK) != STI_GDP) + continue; + + for (b = 0; b < GDP_NODE_NB_BANK; b++) { + seq_printf(m, "\n%s[%d].top", + sti_layer_to_str(layer), b); + sti_gdp_node_dump_node(m, gdp->node_list[b].top_field); + seq_printf(m, "\n%s[%d].btm", + sti_layer_to_str(layer), b); + sti_gdp_node_dump_node(m, gdp->node_list[b].btm_field); + } + } +out: + mutex_unlock(&dev->struct_mutex); + return 0; +} diff --git a/drivers/gpu/drm/sti/sti_gdp.h b/drivers/gpu/drm/sti/sti_gdp.h index 4f7f40b..84875ff 100644 --- a/drivers/gpu/drm/sti/sti_gdp.h +++ b/drivers/gpu/drm/sti/sti_gdp.h @@ -69,5 +69,7 @@ struct sti_gdp {
struct sti_gdp *sti_gdp_create(struct device *dev, int id, void __iomem *baseaddr); +int sti_gdp_dbg_show(struct seq_file *m, void *arg); +int sti_gdp_node_dbg_show(struct seq_file *m, void *arg);
#endif
Make VIdeo plug more verbose on what is on going
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/sti_vid.c | 121 ++++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_vid.h | 1 + 2 files changed, 122 insertions(+)
diff --git a/drivers/gpu/drm/sti/sti_vid.c b/drivers/gpu/drm/sti/sti_vid.c index 710665d..35f60d8 100644 --- a/drivers/gpu/drm/sti/sti_vid.c +++ b/drivers/gpu/drm/sti/sti_vid.c @@ -8,6 +8,8 @@
#include "sti_vid.h" #include "sti_layer.h" +#include "sti_compositor.h" +#include "sti_drm_drv.h" #include "sti_vtg_utils.h"
/* Registers */ @@ -49,6 +51,8 @@ static int sti_vid_prepare_layer(void *lay, bool first_prepare) struct sti_layer *layer = (struct sti_layer *)lay; struct sti_vid *vid = layer->vid;
+ dev_dbg(vid->dev, "%s %s\n", __func__, sti_layer_to_str(layer)); + /* Unmask */ val = readl(vid->regs + VID_CTL); val &= ~VID_CTL_IGNORE; @@ -64,6 +68,8 @@ static int sti_vid_commit_layer(void *lay) struct drm_display_mode *mode = layer->mode; u32 ydo, xdo, yds, xds;
+ dev_dbg(vid->dev, "%s %s\n", __func__, sti_layer_to_str(layer)); + ydo = sti_vtg_get_line_number(*mode, layer->dst_y); yds = sti_vtg_get_line_number(*mode, layer->dst_y + layer->dst_h - 1); xdo = sti_vtg_get_pixel_number(*mode, layer->dst_x); @@ -81,6 +87,8 @@ static int sti_vid_disable_layer(void *lay) struct sti_layer *layer = (struct sti_layer *)lay; struct sti_vid *vid = layer->vid;
+ DRM_DEBUG_DRIVER("%s\n", sti_layer_to_str(layer)); + /* Mask */ val = readl(vid->regs + VID_CTL); val |= VID_CTL_IGNORE; @@ -136,3 +144,116 @@ struct sti_vid *sti_vid_create(struct device *dev, void __iomem *baseaddr)
return vid; } + +static void sti_vid_dbg_ctl(struct seq_file *m, int val) +{ + val = val >> 30; + seq_puts(m, "\t"); + + if (!(val & 1)) + seq_puts(m, "NOT "); + seq_puts(m, "ignored on main mixer - "); + + if (!(val & 2)) + seq_puts(m, "NOT "); + seq_puts(m, "ignored on aux mixer"); +} + +static void sti_vid_dbg_vpo(struct seq_file *m, int val) +{ + seq_printf(m, "\txdo:%4d\tydo:%4d", val & 0x0FFF, (val >> 16) & 0x0FFF); +} + +static void sti_vid_dbg_vps(struct seq_file *m, int val) +{ + seq_printf(m, "\txds:%4d\tyds:%4d", val & 0x0FFF, (val >> 16) & 0x0FFF); +} + +static void sti_vid_dbg_mst(struct seq_file *m, int val) +{ + if (val & 1) + seq_puts(m, "\tBUFFER UNDERFLOW!"); +} + +#define DBG_DUMP(reg) \ + seq_printf(m, "\n " #reg "\t 0x%08X", readl(vid->regs + reg)) + +int sti_vid_dbg_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_compositor *compo = dev_priv->compo; + enum sti_layer_desc vid_id = *(enum sti_layer_desc *)arg; + struct sti_layer *layer; + struct sti_vid *vid; + struct drm_plane *plane; + struct drm_crtc *crtc; + struct drm_framebuffer *fb; + + if (compo == NULL) { + seq_puts(m, "No compo available\n"); + goto out; + } + + layer = sti_layer_find_layer(compo->layer, vid_id); + if (!layer) { + seq_puts(m, "Cannot find video layer\n"); + goto out; + } + vid = layer->vid; + + seq_printf(m, "\n%s (associated with the mixer_id %d)", + sti_layer_to_str(layer), layer->mixer_id); + seq_printf(m, "\t %d frame updates", + layer->fps_info.curr_frame_counter); + + DBG_DUMP(VID_CTL); + sti_vid_dbg_ctl(m, readl(vid->regs + VID_CTL)); + DBG_DUMP(VID_ALP); + DBG_DUMP(VID_CLF); + DBG_DUMP(VID_VPO); + sti_vid_dbg_vpo(m, readl(vid->regs + VID_VPO)); + DBG_DUMP(VID_VPS); + sti_vid_dbg_vps(m, readl(vid->regs + VID_VPS)); + DBG_DUMP(VID_KEY1); + DBG_DUMP(VID_KEY2); + DBG_DUMP(VID_MPR0); + DBG_DUMP(VID_MPR1); + DBG_DUMP(VID_MPR2); + DBG_DUMP(VID_MPR3); + DBG_DUMP(VID_MST); + sti_vid_dbg_mst(m, readl(vid->regs + VID_MST)); + DBG_DUMP(VID_BC); + DBG_DUMP(VID_TINT); + DBG_DUMP(VID_CSAT); + + seq_puts(m, "\n"); + + plane = &layer->plane; + if (!plane->base.id) { + seq_puts(m, " Not connected to any DRM PLANE\n"); + goto out; + } + seq_printf(m, " Connected to DRM PLANE #%d which is:\n", + plane->base.id); + + crtc = plane->crtc; + if (!crtc) { + seq_puts(m, "\tNot connected to any DRM CRTC\n"); + goto out; + } + seq_printf(m, "\tConnected to DRM CRTC #%d\n", crtc->base.id); + + fb = crtc->fb; + if (!fb) { + seq_puts(m, "\tNot connected to any DRM FB\n"); + goto out; + } + seq_printf(m, "\tConnected to DRM FB #%d, %dx%d, %.4s\n", + fb->base.id, + fb->width, fb->height, (char *)&fb->pixel_format); + +out: + return 0; +} diff --git a/drivers/gpu/drm/sti/sti_vid.h b/drivers/gpu/drm/sti/sti_vid.h index dd4fd95..6261ade 100644 --- a/drivers/gpu/drm/sti/sti_vid.h +++ b/drivers/gpu/drm/sti/sti_vid.h @@ -29,5 +29,6 @@ struct sti_vid { };
struct sti_vid *sti_vid_create(struct device *dev, void __iomem *baseaddr); +int sti_vid_dbg_show(struct seq_file *m, void *arg);
#endif
Use debugfs to dump information about TVout
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/sti_tvout.c | 181 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+)
diff --git a/drivers/gpu/drm/sti/sti_tvout.c b/drivers/gpu/drm/sti/sti_tvout.c index 3e679a0..fb199c4 100644 --- a/drivers/gpu/drm/sti/sti_tvout.c +++ b/drivers/gpu/drm/sti/sti_tvout.c @@ -18,6 +18,7 @@ #include "sti_tvout.h" #include "sti_hdmi.h" #include "sti_hda.h" +#include "sti_drm_drv.h"
/* glue regsiters */ #define TVO_CSC_MAIN_M0 0x000 @@ -605,6 +606,186 @@ void sti_tvout_disable(struct sti_tvout *tvout, connector->stop(connector); }
+/* + * Debugfs + */ +#define TVOUT_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \ + readl(tvout->regs + reg)) +#define TVOUT_CONNECTOR_NAME_MAX_LENGHT 10 +#define MAX_STRING_LENGTH 55 + +static int sti_tvout_dbg_type_to_connector_name(enum sti_tvout_connector_type + type, char *name) +{ + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + snprintf(name, TVOUT_CONNECTOR_NAME_MAX_LENGHT, "HDMI"); + break; + case STI_TVOUT_CONNECTOR_HDA: + snprintf(name, TVOUT_CONNECTOR_NAME_MAX_LENGHT, "HD Analog"); + break; + default: + return -1; + } + + return 0; +} + +static void tvout_dbg_vip(struct seq_file *m, int val) +{ + int r, g, b, tmp, mask; + char str[MAX_STRING_LENGTH]; + static const char *const reorder[] = { "Y_G", "Cb_B", "Cr_R" }; + static const char *const clipping[] = { "No", "EAV/SAV", + "Limited range RGB/Y", "Limited range Cb/Cr", + "decided by register" }; + static const char *const round[] = { "8-bit", "10-bit", "12-bit" }; + static const char *const input_sel[] = { "Main (color matrix enabled)", + "Main (color matrix by-passed)", "", "", "", "", "", "", + "Aux (color matrix enabled)", "Aux (color matrix by-passed)", + "", "", "", "", "", "Force value" + }; + + seq_puts(m, "\t"); + + mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_R_SHIFT; + r = (val & mask) >> TVO_VIP_REORDER_R_SHIFT; + mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_G_SHIFT; + g = (val & mask) >> TVO_VIP_REORDER_G_SHIFT; + mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_B_SHIFT; + b = (val & mask) >> TVO_VIP_REORDER_B_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "Reorder: %s->%s %s->%s %s->%s", + reorder[r], reorder[TVO_VIP_REORDER_CR_R_SEL], + reorder[g], reorder[TVO_VIP_REORDER_Y_G_SEL], + reorder[b], reorder[TVO_VIP_REORDER_CB_B_SEL]); + seq_printf(m, "%-55s", str); + + mask = TVO_VIP_CLIP_MASK << TVO_VIP_CLIP_SHIFT; + tmp = (val & mask) >> TVO_VIP_CLIP_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "Clipping: %s", clipping[tmp]); + seq_printf(m, "%-55s", str); + + seq_printf(m, "\n%-40s", ""); + + mask = TVO_VIP_RND_MASK << TVO_VIP_RND_SHIFT; + tmp = (val & mask) >> TVO_VIP_RND_SHIFT; + snprintf(str, MAX_STRING_LENGTH, + "Round: input data rounded to %s per component", round[tmp]); + seq_printf(m, "%-55s", str); + + tmp = (val & TVO_VIP_SEL_INPUT_MASK); + snprintf(str, MAX_STRING_LENGTH, "Input selection: %s", input_sel[tmp]); + seq_printf(m, "%-55s", str); +} + +static void tvout_dbg_hd_dac_cfg(struct seq_file *m, int val) +{ + seq_puts(m, "\t HD DAC "); + seq_puts(m, val & 1 ? "disabled" : "enabled"); +} + +static int tvout_dbg_show(struct seq_file *m, + enum sti_tvout_connector_type type) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_tvout *tvout = dev_priv->tvout; + struct sti_tvout_connector *connector = tvout->connector[type]; + char name[TVOUT_CONNECTOR_NAME_MAX_LENGHT]; + int ret; + + if (!connector) + return -1; + + if (!connector->is_enabled) + return -1; + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret) + return ret; + + if (tvout == NULL) { + seq_puts(m, "No tvout available"); + goto out; + } + + ret = sti_tvout_dbg_type_to_connector_name(type, name); + if (ret) { + seq_puts(m, "No connector!"); + goto out; + } + + seq_printf(m, "\n%s connector: ", name); + seq_printf(m, "\nTVOUT: (virt base addr = 0x%p)", tvout->regs); + if (connector->is_enabled(connector)) { + if (connector->main_path) { + seq_puts(m, "\n Connected to the main path"); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M0); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M1); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M2); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M3); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M4); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M5); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M6); + TVOUT_DBG_DUMP(TVO_CSC_MAIN_M7); + TVOUT_DBG_DUMP(TVO_MAIN_IN_VID_FORMAT); + } else { + seq_puts(m, "\n Connected to the auxiliary path"); + TVOUT_DBG_DUMP(TVO_CSC_AUX_M0); + TVOUT_DBG_DUMP(TVO_CSC_AUX_M2); + TVOUT_DBG_DUMP(TVO_CSC_AUX_M3); + TVOUT_DBG_DUMP(TVO_CSC_AUX_M4); + TVOUT_DBG_DUMP(TVO_CSC_AUX_M5); + TVOUT_DBG_DUMP(TVO_CSC_AUX_M6); + TVOUT_DBG_DUMP(TVO_CSC_AUX_M7); + TVOUT_DBG_DUMP(TVO_AUX_IN_VID_FORMAT); + } + } else + seq_puts(m, " Disabled"); + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + TVOUT_DBG_DUMP(TVO_VIP_HDMI); + tvout_dbg_vip(m, readl(tvout->regs + TVO_VIP_HDMI)); + TVOUT_DBG_DUMP(TVO_HDMI_FORCE_COLOR_0); + TVOUT_DBG_DUMP(TVO_HDMI_FORCE_COLOR_1); + TVOUT_DBG_DUMP(TVO_HDMI_CLIP_VALUE_B_CB); + TVOUT_DBG_DUMP(TVO_HDMI_CLIP_VALUE_Y_G); + TVOUT_DBG_DUMP(TVO_HDMI_CLIP_VALUE_R_CR); + TVOUT_DBG_DUMP(TVO_HDMI_SYNC_SEL); + TVOUT_DBG_DUMP(TVO_HDMI_DFV_OBS); + break; + case STI_TVOUT_CONNECTOR_HDA: + TVOUT_DBG_DUMP(TVO_VIP_HDF); + tvout_dbg_vip(m, readl(tvout->regs + TVO_VIP_HDF)); + TVOUT_DBG_DUMP(TVO_HD_SYNC_SEL); + TVOUT_DBG_DUMP(TVO_HD_DAC_CFG_OFF); + tvout_dbg_hd_dac_cfg(m, + readl(tvout->regs + TVO_HD_DAC_CFG_OFF)); + break; + default: + goto out; + } + + if (connector->dbg_show) + connector->dbg_show(connector, m); +out: + seq_puts(m, "\n\n"); + mutex_unlock(&dev->struct_mutex); + return 0; +} + +int sti_tvout_hdmi_dbg_show(struct seq_file *m, void *arg) +{ + return tvout_dbg_show(m, STI_TVOUT_CONNECTOR_HDMI); +} + +int sti_tvout_hda_dbg_show(struct seq_file *m, void *arg) +{ + return tvout_dbg_show(m, STI_TVOUT_CONNECTOR_HDA); +} + static int sti_tvout_bind(struct device *dev, struct device *master, void *data) { return 0;
Make mixer driver more verbose
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/sti_mixer.c | 164 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_mixer.h | 2 + 2 files changed, 166 insertions(+)
diff --git a/drivers/gpu/drm/sti/sti_mixer.c b/drivers/gpu/drm/sti/sti_mixer.c index b6c8214..73d5405 100644 --- a/drivers/gpu/drm/sti/sti_mixer.c +++ b/drivers/gpu/drm/sti/sti_mixer.c @@ -6,7 +6,9 @@ * License terms: GNU General Public License (GPL), version 2 */
+#include "sti_drm_drv.h" #include "sti_mixer.h" +#include "sti_compositor.h" #include "sti_vtg_utils.h"
/* Identity: G=Y , B=Cb , R=Cr */ @@ -139,6 +141,8 @@ int sti_mixer_set_layer_depth(struct sti_mixer *mixer, struct sti_layer *layer) mask = GAM_DEPTH_MASK_ID << (3 * depth); layer_id = layer_id << (3 * depth);
+ DRM_DEBUG_DRIVER("%s %s depth=%d\n", sti_mixer_to_str(mixer), + sti_layer_to_str(layer), depth); dev_dbg(mixer->dev, "GAM_MIXER_CRB val 0x%x mask 0x%x\n", layer_id, mask); sti_mixer_reg_writemask(mixer, GAM_MIXER_CRB, layer_id, mask); @@ -197,6 +201,9 @@ int sti_mixer_set_layer_status(struct sti_mixer *mixer, { u32 mask, val;
+ DRM_DEBUG_DRIVER("%s %s %s\n", status ? "enable" : "disable", + sti_mixer_to_str(mixer), sti_layer_to_str(layer)); + mask = sti_mixer_get_layer_mask(layer); if (!mask) { DRM_ERROR("Can not find layer mask\n"); @@ -239,3 +246,160 @@ struct sti_mixer *sti_mixer_create(struct device *dev, int id,
return mixer; } + +static void sti_mixer_dbg_ctl(struct seq_file *m, int val) +{ + int count = 0; + + seq_puts(m, "\tEnabled: "); + if (val & 1) { + seq_puts(m, "BKG "); + count++; + } + val = val >> 1; + + if (val & 1) { + seq_puts(m, "VID0 "); + count++; + } + val = val >> 1; + if (val & 1) { + seq_puts(m, "VID1 "); + count++; + } + val = val >> 1; + if (val & 1) { + seq_puts(m, "GDP0 "); + count++; + } + val = val >> 1; + if (val & 1) { + seq_puts(m, "GDP1 "); + count++; + } + val = val >> 1; + if (val & 1) { + seq_puts(m, "GDP2 "); + count++; + } + val = val >> 1; + if (val & 1) { + seq_puts(m, "GDP3 "); + count++; + } + if (!count) + seq_puts(m, "Nothing"); +} + +static void sti_mixer_dbg_crb(struct seq_file *m, int val) +{ + int i; + + seq_puts(m, "\tDepth: "); + for (i = 0; i < GAM_MIXER_NB_DEPTH_LEVEL; i++) { + switch (val & GAM_DEPTH_MASK_ID) { + case GAM_DEPTH_VID0_ID: + seq_puts(m, "VID0"); + break; + case GAM_DEPTH_VID1_ID: + seq_puts(m, "VID1"); + break; + case GAM_DEPTH_GDP0_ID: + seq_puts(m, "GDP0"); + break; + case GAM_DEPTH_GDP1_ID: + seq_puts(m, "GDP1"); + break; + case GAM_DEPTH_GDP2_ID: + seq_puts(m, "GDP2"); + break; + case GAM_DEPTH_GDP3_ID: + seq_puts(m, "GDP3"); + break; + default: + seq_puts(m, "---"); + } + if (i < GAM_MIXER_NB_DEPTH_LEVEL - 1) + seq_puts(m, " < "); + val = val >> 3; + } +} + +static void sti_mixer_dbg_mxn(struct seq_file *m, void *addr) +{ + int i; + + for (i = 1; i < 8; i++) + seq_printf(m, "-0x%08X", (int)readl(addr + i * 4)); +} + +#define MIXER_DBG_DUMP(reg) seq_printf(m, "\n " #reg "\t 0x%08X", \ + sti_mixer_reg_read(mixer, reg)) + +int sti_mixer_dbg_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_compositor *compo = dev_priv->compo; + int i, ret; + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret) + return ret; + + if (compo == NULL) { + seq_puts(m, "No compositor available\n"); + goto out; + } + + for (i = 0; i < compo->nb_mixers; i++) { + struct drm_crtc *crtc; + struct drm_framebuffer *fb; + struct sti_mixer *mixer = compo->mixer[i]; + + seq_printf(m, "\n%s", sti_mixer_to_str(mixer)); + MIXER_DBG_DUMP(GAM_MIXER_CTL); + sti_mixer_dbg_ctl(m, + sti_mixer_reg_read(mixer, + GAM_MIXER_CTL)); + MIXER_DBG_DUMP(GAM_MIXER_BKC); + MIXER_DBG_DUMP(GAM_MIXER_BCO); + MIXER_DBG_DUMP(GAM_MIXER_BCS); + MIXER_DBG_DUMP(GAM_MIXER_AVO); + MIXER_DBG_DUMP(GAM_MIXER_AVS); + MIXER_DBG_DUMP(GAM_MIXER_CRB); + sti_mixer_dbg_crb(m, + sti_mixer_reg_read(mixer, + GAM_MIXER_CRB)); + MIXER_DBG_DUMP(GAM_MIXER_ACT); + MIXER_DBG_DUMP(GAM_MIXER_MBP); + MIXER_DBG_DUMP(GAM_MIXER_MX0); + sti_mixer_dbg_mxn(m, mixer->regs + GAM_MIXER_MX0); + seq_puts(m, "\n"); + + crtc = &mixer->drm_crtc; + if (!crtc) { + seq_puts(m, " Not connected to any DRM CRTC\n"); + continue; + } + seq_printf(m, " Connected to DRM CRTC #%d which is:\n", + crtc->base.id); + seq_puts(m, crtc->enabled ? "\tEnabled\n" : "\tDisabled\n"); + seq_printf(m, "\tMode: %s %dHz\n", + crtc->mode.name, crtc->mode.vrefresh); + seq_printf(m, "\tat %d,%d\n", crtc->x, crtc->y); + + fb = crtc->fb; + if (!fb) { + seq_puts(m, "\tNot connected to any DRM FB\n"); + continue; + } + seq_printf(m, "\tConnected to DRM FB #%d, %dx%d, %.4s\n", + fb->base.id, + fb->width, fb->height, (char *)&fb->pixel_format); + } +out: + mutex_unlock(&dev->struct_mutex); + return 0; +} diff --git a/drivers/gpu/drm/sti/sti_mixer.h b/drivers/gpu/drm/sti/sti_mixer.h index a05e21f..abdd1ad 100644 --- a/drivers/gpu/drm/sti/sti_mixer.h +++ b/drivers/gpu/drm/sti/sti_mixer.h @@ -43,6 +43,8 @@ int sti_mixer_active_video_area(struct sti_mixer *mixer,
void sti_mixer_set_background_status(struct sti_mixer *mixer, bool enable);
+int sti_mixer_dbg_show(struct seq_file *m, void *arg); + /* depth in Cross-bar control = z order */ #define GAM_MIXER_NB_DEPTH_LEVEL 7
Make the link between all the hardware drivers and DRM/KMS interface. Create the driver itself and make it register all the sub-components. Use GEM CMA helpers for buffer allocation.
Signed-off-by: Benjamin Gaignard benjamin.gaignard@linaro.org --- drivers/gpu/drm/sti/Kconfig | 8 + drivers/gpu/drm/sti/Makefile | 7 +- drivers/gpu/drm/sti/sti_compositor.c | 48 ++++ drivers/gpu/drm/sti/sti_drm_connector.c | 195 ++++++++++++++ drivers/gpu/drm/sti/sti_drm_connector.h | 16 ++ drivers/gpu/drm/sti/sti_drm_crtc.c | 440 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_drm_crtc.h | 21 ++ drivers/gpu/drm/sti/sti_drm_drv.c | 338 ++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_drm_encoder.c | 201 +++++++++++++++ drivers/gpu/drm/sti/sti_drm_encoder.h | 16 ++ drivers/gpu/drm/sti/sti_drm_plane.c | 195 ++++++++++++++ drivers/gpu/drm/sti/sti_drm_plane.h | 16 ++ drivers/gpu/drm/sti/sti_tvout.c | 21 ++ 13 files changed, 1521 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/sti/sti_drm_connector.c create mode 100644 drivers/gpu/drm/sti/sti_drm_connector.h create mode 100644 drivers/gpu/drm/sti/sti_drm_crtc.c create mode 100644 drivers/gpu/drm/sti/sti_drm_crtc.h create mode 100644 drivers/gpu/drm/sti/sti_drm_drv.c create mode 100644 drivers/gpu/drm/sti/sti_drm_encoder.c create mode 100644 drivers/gpu/drm/sti/sti_drm_encoder.h create mode 100644 drivers/gpu/drm/sti/sti_drm_plane.c create mode 100644 drivers/gpu/drm/sti/sti_drm_plane.h
diff --git a/drivers/gpu/drm/sti/Kconfig b/drivers/gpu/drm/sti/Kconfig index 76c2e4f..2c80bcf 100644 --- a/drivers/gpu/drm/sti/Kconfig +++ b/drivers/gpu/drm/sti/Kconfig @@ -1,10 +1,18 @@ config DRM_STI bool "DRM Support for STMicroelectronics SoC stiH41x Series" depends on DRM && (SOC_STIH415 || SOC_STIH416 || ARCH_MULTIPLATFORM) + select DRM_KMS_HELPER + select DRM_GEM_CMA_HELPER select DRM_KMS_CMA_HELPER help Choose this option to enable DRM on STM stiH41x chipset
+config DRM_STI_FBDEV + bool "DRM frame buffer device for STMicroelectronics SoC stiH41x Serie" + depends on DRM_STI + help + Choose this option to enable FBDEV on top of DRM for STM stiH41x chipset + config VTAC_STI bool "Video Trafic Advance Communication Rx and Tx for STMicroelectronics SoC stiH41x Series" depends on DRM_STI diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index 3b804d4..8eee160 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -1,6 +1,11 @@ ccflags-y := -Iinclude/drm
-stidrm-y := sti_tvout.o \ +stidrm-y := sti_drm_drv.o \ + sti_drm_crtc.o \ + sti_drm_plane.o \ + sti_drm_connector.o \ + sti_drm_encoder.o \ + sti_tvout.o \ sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ diff --git a/drivers/gpu/drm/sti/sti_compositor.c b/drivers/gpu/drm/sti/sti_compositor.c index 0c9ef6d..b0e2968 100644 --- a/drivers/gpu/drm/sti/sti_compositor.c +++ b/drivers/gpu/drm/sti/sti_compositor.c @@ -14,7 +14,10 @@ #include <drm/drmP.h>
#include "sti_compositor.h" +#include "sti_drm_drv.h" +#include "sti_drm_plane.h" #include "sti_gdp.h" +#include "sti_drm_crtc.h"
static const struct of_device_id compositor_match_types[];
@@ -86,6 +89,50 @@ static int sti_compositor_init_subdev(struct sti_compositor *compo, static int sti_compositor_bind(struct device *dev, struct device *master, void *data) { + struct sti_compositor *compo = dev_get_drvdata(master); + struct drm_device *drm_dev = data; + int i, crtc = 0, plane = 0; + struct sti_drm_private *dev_priv = drm_dev->dev_private; + + dev_dbg(dev, "%s\n", __func__); + + dev_priv->compo = compo; + INIT_LIST_HEAD(&dev_priv->pageflip_evt_list); + + for (i = 0; i < compo->nb_mixers; i++) { + if (compo->mixer[i]) { + sti_drm_crtc_init(drm_dev, compo->mixer[i]); + crtc++; + } + } + if (crtc == 0) { + DRM_ERROR("No CRTC available\n"); + return 1; + } + + drm_vblank_init(drm_dev, crtc); + /* Allow usage of vblank without having to call drm_irq_install */ + drm_dev->irq_enabled = 1; + + for (i = 0; i < compo->nb_layers; i++) { + if (compo->layer[i]) { + /* Create planes for GDP. + * except GDP0 as it is reserved for CRTC FB */ + enum sti_layer_desc desc = compo->layer[i]->desc; + enum sti_layer_type type = desc & STI_LAYER_TYPE_MASK; + + if ((type == STI_GDP) && (desc != STI_GDP_0)) { + sti_drm_plane_init(drm_dev, compo->layer[i], + (1 << crtc) - 1); + plane++; + } + } + } + + DRM_DEBUG_DRIVER("Initialized %d DRM CRTC(s) and %d DRM plane(s)\n", + crtc, plane); + DRM_DEBUG_DRIVER("DRM plane(s) for VID/VDP not created yet\n"); + return 0; }
@@ -116,6 +163,7 @@ static int sti_compositor_probe(struct platform_device *pdev) } DRM_DEBUG_DRIVER("Compositor %p\n", compo); compo->dev = dev; + compo->vtg_vblank_nb.notifier_call = sti_drm_crtc_vblank_cb;
/* populate data structure depending on compatibility */ BUG_ON(!of_match_node(compositor_match_types, np)->data); diff --git a/drivers/gpu/drm/sti/sti_drm_connector.c b/drivers/gpu/drm/sti/sti_drm_connector.c new file mode 100644 index 0000000..b34a402 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_connector.c @@ -0,0 +1,195 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "sti_drm_connector.h" +#include "sti_drm_drv.h" + +#define to_sti_connector(x) container_of(x, struct sti_connector, drm_connector) + +/* + * sti specific connector structure + * + * @drm_encoder: connector object + * @encoder: associated encoder + * @tvout: pointer on tvout driver + * @type: tvout connector type + */ +struct sti_connector { + struct drm_connector drm_connector; + struct drm_encoder *encoder; + struct sti_tvout *tvout; + enum sti_tvout_connector_type type; +}; + +static int sti_drm_connector_get_modes(struct drm_connector *connector) +{ + struct sti_connector *sti_connector = to_sti_connector(connector); + struct drm_device *dev = connector->dev; + + dev_dbg(dev->dev, "%s\n", __func__); + + return sti_tvout_get_modes(sti_connector->tvout, sti_connector->type, + connector); +} + +static int sti_drm_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_connector *sti_connector = to_sti_connector(connector); + struct drm_device *dev = connector->dev; + int ret = MODE_BAD; + + dev_dbg(dev->dev, "%s\n", __func__); + + if (!sti_tvout_check_mode + (sti_connector->tvout, sti_connector->type, mode)) + ret = MODE_OK; + + return ret; +} + +struct drm_encoder *sti_drm_best_encoder(struct drm_connector *connector) +{ + struct sti_connector *sti_connector = to_sti_connector(connector); + struct drm_device *dev = connector->dev; + + dev_dbg(dev->dev, "%s\n", __func__); + + /* Best encoder is the one associated during connector creation */ + return sti_connector->encoder; +} + +static struct drm_connector_helper_funcs sti_drm_connector_helper_funcs = { + .get_modes = sti_drm_connector_get_modes, + .mode_valid = sti_drm_connector_mode_valid, + .best_encoder = sti_drm_best_encoder, +}; + +static void sti_drm_connector_dpms(struct drm_connector *connector, int mode) +{ + struct drm_device *dev = connector->dev; + + dev_dbg(dev->dev, "%s\n", __func__); + + drm_helper_connector_dpms(connector, mode); +} + +/* get detection status of display device. */ +static enum drm_connector_status +sti_drm_connector_detect(struct drm_connector *connector, bool force) +{ + enum drm_connector_status status = connector_status_disconnected; + struct sti_connector *sti_connector = to_sti_connector(connector); + struct drm_device *dev = connector->dev; + + dev_dbg(dev->dev, "%s\n", __func__); + + if (sti_tvout_connector_detect(sti_connector->tvout, + sti_connector->type)) + status = connector_status_connected; + + return status; +} + +static void sti_drm_connector_destroy(struct drm_connector *connector) +{ + struct sti_connector *sti_connector = to_sti_connector(connector); + struct drm_device *dev = connector->dev; + + dev_dbg(dev->dev, "%s\n", __func__); + + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); + kfree(sti_connector); +} + +static struct drm_connector_funcs sti_drm_connector_funcs = { + .dpms = sti_drm_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = sti_drm_connector_detect, + .destroy = sti_drm_connector_destroy, +}; + +struct drm_connector *sti_drm_connector_create(struct drm_device *dev, + struct sti_tvout *tvout, + struct drm_encoder *encoder, + enum sti_tvout_connector_type + type) +{ + struct sti_connector *sti_connector; + struct drm_connector *connector; + int connector_type; + int err; + + dev_dbg(dev->dev, "%s\n", __func__); + + /* Create the tvout connector according to the type */ + tvout->connector[type] = tvout->connector_create[type] (tvout); + if (!tvout->connector[type]) { + DRM_INFO("%s: failed to create connector (type = %d)\n", + __func__, type); + return NULL; + } + + sti_connector = kzalloc(sizeof(*sti_connector), GFP_KERNEL); + if (!sti_connector) { + DRM_ERROR("failed to allocate connector\n"); + return NULL; + } + + connector = &sti_connector->drm_connector; + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + connector_type = DRM_MODE_CONNECTOR_HDMIA; + connector->polled = DRM_CONNECTOR_POLL_HPD; + break; + case STI_TVOUT_CONNECTOR_HDA: + connector_type = DRM_MODE_CONNECTOR_Component; + break; + case STI_TVOUT_CONNECTOR_DVO: + connector_type = DRM_MODE_CONNECTOR_LVDS; + break; + case STI_TVOUT_CONNECTOR_DENC: + connector_type = DRM_MODE_CONNECTOR_Composite; + break; + default: + connector_type = DRM_MODE_CONNECTOR_Unknown; + break; + } + + drm_connector_init(dev, connector, &sti_drm_connector_funcs, + connector_type); + drm_connector_helper_add(connector, &sti_drm_connector_helper_funcs); + + err = drm_sysfs_connector_add(connector); + if (err) + goto err_connector; + + sti_connector->tvout = tvout; + sti_connector->encoder = encoder; + sti_connector->type = type; + + err = drm_mode_connector_attach_encoder(connector, encoder); + if (err) { + DRM_ERROR("Failed to attach a connector to a encoder\n"); + goto err_sysfs; + } + + DRM_DEBUG_DRIVER("Connector has been created\n"); + + return connector; + +err_sysfs: + drm_sysfs_connector_remove(connector); +err_connector: + drm_connector_cleanup(connector); + kfree(sti_connector); + return NULL; +} diff --git a/drivers/gpu/drm/sti/sti_drm_connector.h b/drivers/gpu/drm/sti/sti_drm_connector.h new file mode 100644 index 0000000..068c674 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_connector.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_DRM_CONNECTOR_H_ +#define _STI_DRM_CONNECTOR_H_ + +#include "sti_tvout.h" + +struct drm_connector *sti_drm_connector_create(struct drm_device *dev, + struct sti_tvout *tvout, struct drm_encoder *encoder, + enum sti_tvout_connector_type type); + +#endif diff --git a/drivers/gpu/drm/sti/sti_drm_crtc.c b/drivers/gpu/drm/sti/sti_drm_crtc.c new file mode 100644 index 0000000..5c06d70 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_crtc.c @@ -0,0 +1,440 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "sti_drm_drv.h" +#include "sti_drm_crtc.h" +#include "sti_compositor.h" +#include "sti_vtg_utils.h" + +static void sti_drm_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + DRM_DEBUG_KMS("\n"); +} + +static void sti_drm_crtc_prepare(struct drm_crtc *crtc) +{ + struct sti_mixer *mixer = to_sti_mixer(crtc); + struct device *dev = mixer->dev; + struct sti_compositor *compo = dev_get_drvdata(dev); + + compo->enable = true; + + /* Prepare and enable the compo IP clock */ + if (mixer->id == STI_MIXER_MAIN) { + if (clk_prepare_enable(compo->clk_compo_main)) + DRM_INFO("Failed to prepare/enable compo_main clk\n"); + } else { + if (clk_prepare_enable(compo->clk_compo_aux)) + DRM_INFO("Failed to prepare/enable compo_aux clk\n"); + } +} + +static void sti_drm_crtc_commit(struct drm_crtc *crtc) +{ + struct sti_mixer *mixer = to_sti_mixer(crtc); + struct device *dev = mixer->dev; + struct sti_compositor *compo = dev_get_drvdata(dev); + struct sti_layer *layer; + + dev_dbg(dev, "%s\n", __func__); + if ((!mixer || !compo)) { + DRM_ERROR("Can not find mixer or compositor)\n"); + return; + } + + /* Find GDP0 which is reserved to the CRTC FB */ + layer = sti_layer_find_layer(compo->layer, STI_GDP_0); + if (layer) + sti_layer_commit(layer); + else + DRM_ERROR("Can not find CRTC dedicated plane (GDP0)\n"); + + /* Enable layer on mixer */ + if (sti_mixer_set_layer_status(mixer, layer, true)) + DRM_ERROR("Can not enable layer at mixer\n"); +} + +static bool sti_drm_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* accept the provided drm_display_mode, do not fix it up */ + dev_dbg(crtc->dev->dev, "%s\n", __func__); + return true; +} + +static int +sti_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct sti_mixer *mixer = to_sti_mixer(crtc); + struct device *dev = mixer->dev; + struct sti_compositor *compo = dev_get_drvdata(dev); + struct sti_layer *layer; + struct clk *clk; + int rate = mode->clock * 1000; + int res; + unsigned int w, h; + + DRM_DEBUG_KMS("CRTC:%d (%s) fb:%d mode:%d (%s)\n", + crtc->base.id, sti_mixer_to_str(mixer), + crtc->fb->base.id, mode->base.id, mode->name); + + DRM_DEBUG_KMS("%d %d %d %d %d %d %d %d %d %d 0x%x 0x%x\n", + mode->vrefresh, mode->clock, + mode->hdisplay, + mode->hsync_start, mode->hsync_end, + mode->htotal, + mode->vdisplay, + mode->vsync_start, mode->vsync_end, + mode->vtotal, mode->type, mode->flags); + + /* Set rate and prepare/enable pixel clock */ + if (mixer->id == STI_MIXER_MAIN) + clk = compo->clk_pix_main; + else + clk = compo->clk_pix_aux; + + res = clk_set_rate(clk, rate); + if (res < 0) { + DRM_ERROR("Cannot set rate (%dHz) for pix clk\n", rate); + return 1; + } + if (clk_prepare_enable(clk)) { + DRM_ERROR("Failed to prepare/enable pix clk\n"); + return 1; + } + + sti_vtg_setconfig(mixer->id == STI_MIXER_MAIN ? VTG_MAIN : VTG_AUX, + &crtc->mode); + + /* GDP0 is reserved to the CRTC FB */ + layer = sti_layer_find_layer(compo->layer, STI_GDP_0); + if (!layer) { + DRM_ERROR("Can not find GDP0)\n"); + return 1; + } + + /* copy the mode data adjusted by mode_fixup() into crtc->mode + * so that hardware can be set to proper mode */ + memcpy(&crtc->mode, adjusted_mode, sizeof(*adjusted_mode)); + + res = sti_mixer_set_layer_depth(mixer, layer); + if (res) { + DRM_ERROR("Can not set layer depth\n"); + return 1; + } + res = sti_mixer_active_video_area(mixer, &crtc->mode); + if (res) { + DRM_ERROR("Can not set active video area\n"); + return 1; + } + + if ((mode->hdisplay != crtc->fb->width) || + (mode->vdisplay != crtc->fb->height)) + DRM_DEBUG_KMS("WARNING: fb and display mode sizes differ\n"); + + w = crtc->fb->width - x; + h = crtc->fb->height - y; + + if ((w <= 0) || (h <= 0)) { + DRM_ERROR("Coordinates outside FB\n"); + return -EINVAL; + } + + return sti_layer_prepare(layer, crtc->fb, &crtc->mode, mixer->id, + 0, 0, w, h, x, y, w, h); +} + +static int sti_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct sti_mixer *mixer = to_sti_mixer(crtc); + struct device *dev = mixer->dev; + struct sti_compositor *compo = dev_get_drvdata(dev); + struct sti_layer *layer; + unsigned int w, h; + int ret = 0; + + DRM_DEBUG_KMS("CRTC:%d (%s) fb:%d (%d,%d)\n", + crtc->base.id, sti_mixer_to_str(mixer), + crtc->fb->base.id, x, y); + + /* GDP0 is reserved to the CRTC FB */ + layer = sti_layer_find_layer(compo->layer, STI_GDP_0); + if (!layer) { + DRM_ERROR("Can not find GDP0)\n"); + ret = -1; + goto out; + } + + w = crtc->fb->width - crtc->x; + h = crtc->fb->height - crtc->y; + + if ((w <= 0) || (h <= 0)) { + DRM_ERROR("Coordinates outside FB\n"); + ret = -EINVAL; + goto out; + } + + ret = sti_layer_prepare(layer, crtc->fb, &crtc->mode, mixer->id, + 0, 0, w, h, + crtc->x, crtc->y, w, h); + if (ret) { + DRM_ERROR("Can not prepare layer\n"); + goto out; + } + + sti_drm_crtc_commit(crtc); +out: + return ret; +} + +static void sti_drm_crtc_load_lut(struct drm_crtc *crtc) +{ + dev_dbg(crtc->dev->dev, "%s\n", __func__); +} + +static void sti_drm_crtc_disable(struct drm_crtc *crtc) +{ + struct sti_mixer *mixer = to_sti_mixer(crtc); + struct device *dev = mixer->dev; + struct sti_compositor *compo = dev_get_drvdata(dev); + struct sti_layer *layer; + + if (!compo->enable) + return; + + DRM_DEBUG_KMS("CRTC:%d (%s)\n", crtc->base.id, sti_mixer_to_str(mixer)); + + /* Disable Background */ + sti_mixer_set_background_status(mixer, false); + + /* Disable GDP0 */ + layer = sti_layer_find_layer(compo->layer, STI_GDP_0); + if (!layer) { + DRM_ERROR("Cannot find GDP0\n"); + return; + } + + /* Disable layer at mixer level */ + if (sti_mixer_set_layer_status(mixer, layer, false)) + DRM_ERROR("Can not disable %s layer at mixer\n", + sti_layer_to_str(layer)); + + /* Wait a while to be sure that a Vsync event is received */ + msleep(WAIT_NEXT_VSYNC_MS); + + /* Then disable layer itself */ + sti_layer_disable(layer); + + drm_vblank_off(crtc->dev, mixer->id); + + /* Disable pixel clock and compo IP clocks */ + if (mixer->id == STI_MIXER_MAIN) { + clk_disable_unprepare(compo->clk_pix_main); + clk_disable_unprepare(compo->clk_compo_main); + } else { + clk_disable_unprepare(compo->clk_pix_aux); + clk_disable_unprepare(compo->clk_compo_aux); + } + + compo->enable = false; +} + +static struct drm_crtc_helper_funcs sti_crtc_helper_funcs = { + .dpms = sti_drm_crtc_dpms, + .prepare = sti_drm_crtc_prepare, + .commit = sti_drm_crtc_commit, + .mode_fixup = sti_drm_crtc_mode_fixup, + .mode_set = sti_drm_crtc_mode_set, + .mode_set_base = sti_drm_crtc_mode_set_base, + .load_lut = sti_drm_crtc_load_lut, + .disable = sti_drm_crtc_disable, +}; + +static int sti_drm_crtc_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event, + uint32_t page_flip_flags) +{ + struct drm_device *drm_dev = crtc->dev; + struct sti_drm_private *dev_priv = drm_dev->dev_private; + struct drm_framebuffer *old_fb; + struct sti_mixer *mixer = to_sti_mixer(crtc); + unsigned long flags; + int ret = 0; + + DRM_DEBUG_KMS("fb %d --> fb %d\n", crtc->fb->base.id, fb->base.id); + + mutex_lock(&drm_dev->struct_mutex); + + old_fb = crtc->fb; + crtc->fb = fb; + ret = sti_drm_crtc_mode_set_base(crtc, crtc->x, crtc->y, old_fb); + if (ret) { + DRM_ERROR("failed\n"); + crtc->fb = old_fb; + goto out; + } + + if (event) { + event->pipe = mixer->id; + + ret = drm_vblank_get(drm_dev, event->pipe); + if (ret) { + DRM_ERROR("Cannot get vblank\n"); + goto out; + } + + spin_lock_irqsave(&drm_dev->event_lock, flags); + list_add_tail(&event->base.link, &dev_priv->pageflip_evt_list); + spin_unlock_irqrestore(&drm_dev->event_lock, flags); + } +out: + mutex_unlock(&drm_dev->struct_mutex); + return ret; +} + +static void sti_drm_crtc_destroy(struct drm_crtc *crtc) +{ + DRM_DEBUG_KMS("\n"); + + drm_crtc_cleanup(crtc); +} + +static int sti_drm_crtc_set_property(struct drm_crtc *crtc, + struct drm_property *property, + uint64_t val) +{ + DRM_DEBUG_KMS("\n"); + + return 0; +} + +int sti_drm_crtc_vblank_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct sti_compositor *compo = container_of(nb, + struct sti_compositor, + vtg_vblank_nb); + struct drm_device *drm_dev; + int *crtc = data; + unsigned long flags; + struct drm_pending_vblank_event *e, *t; + struct sti_drm_private *priv; + + drm_dev = compo->mixer[*crtc]->drm_crtc.dev; + priv = drm_dev->dev_private; + + dev_dbg(drm_dev->dev, "%s\n", __func__); + + if ((event != VTG_TOP_FIELD_EVENT) && + (event != VTG_BOTTOM_FIELD_EVENT)) { + DRM_ERROR("unknown event: %lu\n", event); + return -EINVAL; + } + + drm_handle_vblank(drm_dev, *crtc); + + spin_lock_irqsave(&drm_dev->event_lock, flags); + list_for_each_entry_safe(e, t, &priv->pageflip_evt_list, base.link) { + if (*crtc != e->pipe) + continue; + + list_del(&e->base.link); + drm_send_vblank_event(drm_dev, -1, e); + drm_vblank_put(drm_dev, *crtc); + } + spin_unlock_irqrestore(&drm_dev->event_lock, flags); + + return 0; +} + +int sti_drm_crtc_enable_vblank(struct drm_device *dev, int crtc) +{ + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_compositor *compo = dev_priv->compo; + struct notifier_block *vtg_vblank_nb = &compo->vtg_vblank_nb; + DRM_DEBUG_DRIVER("\n"); + + if (sti_vtg_register_client(crtc, vtg_vblank_nb)) { + DRM_ERROR("Cannot register VTG notifier\n"); + return 1; + } + + return 0; +} + +void sti_drm_crtc_disable_vblank(struct drm_device *dev, int crtc) +{ + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_compositor *compo = dev_priv->compo; + struct notifier_block *vtg_vblank_nb = &compo->vtg_vblank_nb; + unsigned long flags; + struct drm_pending_vblank_event *e, *t; + struct sti_drm_private *priv = dev->dev_private; + DRM_DEBUG_DRIVER("\n"); + + if (sti_vtg_unregister_client(crtc, vtg_vblank_nb)) + DRM_DEBUG_DRIVER("Warning: cannot unregister VTG notifier\n"); + + /* free the resources of the pending requests */ + spin_lock_irqsave(&dev->event_lock, flags); + list_for_each_entry_safe(e, t, &priv->pageflip_evt_list, base.link) { + if (crtc != e->pipe) + continue; + list_del(&e->base.link); + drm_vblank_put(dev, crtc); + } + spin_unlock_irqrestore(&dev->event_lock, flags); + +} + +static struct drm_crtc_funcs sti_crtc_funcs = { + .set_config = drm_crtc_helper_set_config, + .page_flip = sti_drm_crtc_page_flip, + .destroy = sti_drm_crtc_destroy, + .set_property = sti_drm_crtc_set_property, +}; + +bool sti_drm_crtc_is_main(struct drm_crtc *crtc) +{ + struct sti_mixer *mixer = to_sti_mixer(crtc); + + if (mixer->id == STI_MIXER_MAIN) + return true; + + return false; +} + +int sti_drm_crtc_init(struct drm_device *drm_dev, struct sti_mixer *mixer) +{ + struct drm_crtc *crtc = &mixer->drm_crtc; + int res; + + dev_dbg(drm_dev->dev, "%s\n", __func__); + res = drm_crtc_init(drm_dev, crtc, &sti_crtc_funcs); + if (res) { + DRM_ERROR("Can not initialze CRTC\n"); + return 1; + } + + drm_crtc_helper_add(crtc, &sti_crtc_helper_funcs); + + DRM_DEBUG_DRIVER("drm CRTC:%d mapped to %s\n", + crtc->base.id, sti_mixer_to_str(mixer)); + + return 0; +} diff --git a/drivers/gpu/drm/sti/sti_drm_crtc.h b/drivers/gpu/drm/sti/sti_drm_crtc.h new file mode 100644 index 0000000..9d31e41 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_crtc.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_DRM_CRTC_H_ +#define _STI_DRM_CRTC_H_ + +#include <drm/drmP.h> + +#include "sti_mixer.h" + +int sti_drm_crtc_init(struct drm_device *drm_dev, struct sti_mixer *mixer); +int sti_drm_crtc_enable_vblank(struct drm_device *dev, int crtc); +void sti_drm_crtc_disable_vblank(struct drm_device *dev, int crtc); +int sti_drm_crtc_vblank_cb(struct notifier_block *nb, + unsigned long event, void *data); +bool sti_drm_crtc_is_main(struct drm_crtc *drm_crtc); + +#endif diff --git a/drivers/gpu/drm/sti/sti_drm_drv.c b/drivers/gpu/drm/sti/sti_drm_drv.c new file mode 100644 index 0000000..30a2009 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_drv.c @@ -0,0 +1,338 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <drm/drmP.h> + +#include <linux/component.h> +#include <linux/debugfs.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_cma_helper.h> + +#include "sti_drm_drv.h" +#include "sti_drm_crtc.h" +#include "sti_compositor.h" +#include "sti_gdp.h" +#include "sti_tvout.h" + +#define DRIVER_NAME "sti" +#define DRIVER_DESC "STMicroelectronics SoC DRM" +#define DRIVER_DATE "20130905" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +#ifdef CONFIG_DRM_STI_FBDEV +#define FBDEV_CREATE_DELAY 2000 +struct drm_device *sti_drm_device; + +static void stid_fbdev_create(struct work_struct *dummy) +{ + if (!sti_drm_device) + return; + + drm_fbdev_cma_init(sti_drm_device, 32, + sti_drm_device->mode_config.num_crtc, + sti_drm_device->mode_config.num_connector); +} + +static DECLARE_DELAYED_WORK(sti_fbdev_work, stid_fbdev_create); +#endif + +static struct drm_mode_config_funcs sti_drm_mode_config_funcs = { + .fb_create = drm_fb_cma_create, +}; + +#ifdef CONFIG_DEBUG_FS +static struct drm_info_list sti_drm_dbg_list[] = { + {"gdp", sti_gdp_dbg_show, 0}, + {"node", sti_gdp_node_dbg_show, 0}, + {"mixer", sti_mixer_dbg_show, 0}, + {"hdmi", sti_tvout_hdmi_dbg_show, 0, NULL}, + {"hda", sti_tvout_hda_dbg_show, 0, NULL}, +}; + +static int sti_drm_fps_get(void *data, u64 *val) +{ + struct drm_device *dev = data; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_compositor *compo = dev_priv->compo; + int i; + + *val = 0; + for (i = 0; i < compo->nb_layers; i++) + *val |= (compo->layer[i]->fps_info.output) << i; + + return 0; +} + +static int sti_drm_fps_set(void *data, u64 val) +{ + struct drm_device *dev = data; + struct sti_drm_private *dev_priv = dev->dev_private; + struct sti_compositor *compo = dev_priv->compo; + int i; + + for (i = 0; i < compo->nb_layers; i++) + compo->layer[i]->fps_info.output = (val >> i) & 1; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(sti_drm_fps_fops, + sti_drm_fps_get, sti_drm_fps_set, "%llu\n"); + +static int sti_drm_debugfs_create(struct dentry *root, struct drm_minor *minor, + const char *name, + const struct file_operations *fops) +{ + struct drm_device *dev = minor->dev; + struct drm_info_node *node; + struct dentry *ent; + + ent = debugfs_create_file(name, S_IRUGO | S_IWUSR, root, dev, fops); + if (IS_ERR(ent)) + return PTR_ERR(ent); + + node = kmalloc(sizeof(struct drm_info_node), GFP_KERNEL); + if (node == NULL) { + debugfs_remove(ent); + return -ENOMEM; + } + + node->minor = minor; + node->dent = ent; + node->info_ent = (void *)fops; + + mutex_lock(&minor->debugfs_lock); + list_add(&node->list, &minor->debugfs_list); + mutex_unlock(&minor->debugfs_lock); + + return 0; +} + +int sti_drm_dbg_init(struct drm_minor *minor) +{ + int ret; + + ret = sti_drm_debugfs_create(minor->debugfs_root, minor, "fps_show", + &sti_drm_fps_fops); + if (ret) + goto err; + + ret = drm_debugfs_create_files(sti_drm_dbg_list, + ARRAY_SIZE(sti_drm_dbg_list), + minor->debugfs_root, minor); + if (ret) + goto err; + + DRM_INFO("%s debugfs installed\n", DRIVER_NAME); + + return ret; +err: + DRM_ERROR("Cannot install debugfs\n"); + return ret; +} + +void sti_drm_dbg_cleanup(struct drm_minor *minor) +{ + drm_debugfs_remove_files(sti_drm_dbg_list, + ARRAY_SIZE(sti_drm_dbg_list), minor); + + drm_debugfs_remove_files((struct drm_info_list *)&sti_drm_fps_fops, + 1, minor); +} +#endif + +static void sti_drm_mode_config_init(struct drm_device *dev) +{ + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + + /* + * set max width and height as default value. + * this value would be used to check framebuffer size limitation + * at drm_mode_addfb(). + */ + dev->mode_config.max_width = STI_MAX_FB_HEIGHT; + dev->mode_config.max_height = STI_MAX_FB_WIDTH; + + dev->mode_config.funcs = &sti_drm_mode_config_funcs; +} + +static int sti_drm_load(struct drm_device *dev, unsigned long flags) +{ + struct sti_drm_private *private; + int err; + + DRM_INFO("%s drm_device: %p\n", __func__, dev); + + private = kzalloc(sizeof(struct sti_drm_private), GFP_KERNEL); + if (!private) { + DRM_ERROR("Failed to allocate private\n"); + return -ENOMEM; + } + dev->dev_private = (void *)private; + + drm_mode_config_init(dev); + drm_kms_helper_poll_init(dev); + + sti_drm_mode_config_init(dev); + + component_bind_all(dev->dev, dev); + + drm_helper_disable_unused_functions(dev); +#ifdef CONFIG_DRM_STI_FBDEV + sti_drm_device = dev; + + schedule_delayed_work(&sti_fbdev_work, + msecs_to_jiffies(FBDEV_CREATE_DELAY)); +#endif + return err; +} + +static const struct file_operations sti_drm_driver_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .mmap = drm_gem_cma_mmap, + .poll = drm_poll, + .read = drm_read, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .release = drm_release, +}; + +static struct dma_buf *sti_drm_gem_prime_export(struct drm_device *dev, + struct drm_gem_object *obj, + int flags) +{ + /* we want to be able to write in mmapped buffer */ + flags |= O_RDWR; + return drm_gem_prime_export(dev, obj, flags); +} + +static struct drm_driver sti_drm_driver = { + .driver_features = DRIVER_HAVE_IRQ | DRIVER_MODESET | + DRIVER_GEM | DRIVER_PRIME, + .load = sti_drm_load, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .dumb_create = drm_gem_cma_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + .fops = &sti_drm_driver_fops, + + .get_vblank_counter = drm_vblank_count, + .enable_vblank = sti_drm_crtc_enable_vblank, + .disable_vblank = sti_drm_crtc_disable_vblank, + + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = sti_drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, + .gem_prime_vmap = drm_gem_cma_prime_vmap, + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, + .gem_prime_mmap = drm_gem_cma_prime_mmap, + +#ifdef CONFIG_DEBUG_FS + .debugfs_init = sti_drm_dbg_init, + .debugfs_cleanup = sti_drm_dbg_cleanup, +#endif + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, +}; + +static int compare_of(struct device *dev, void *data) +{ + return 1; +} + +static int sti_drm_add_components(struct device *master, struct master *m) +{ + struct device_node *np = master->of_node; + struct device_node *child_np; + + child_np = of_get_next_available_child(np, NULL); + + while (child_np) { + DRM_INFO("add child %s\n", child_np->name); + component_master_add_child(m, compare_of, child_np); + of_node_put(child_np); + child_np = of_get_next_available_child(np, child_np); + } + + return 0; +} + +static int sti_drm_bind(struct device *dev) +{ + return drm_platform_init(&sti_drm_driver, to_platform_device(dev)); +} + +static void sti_drm_unbind(struct device *dev) +{ + drm_put_dev(dev_get_drvdata(dev)); +} + +static const struct component_master_ops sti_drm_ops = { + .add_components = sti_drm_add_components, + .bind = sti_drm_bind, + .unbind = sti_drm_unbind, +}; + +static int sti_drm_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + + DRM_INFO("%s\n", __func__); + + of_platform_populate(node, NULL, NULL, dev); + + dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + + return component_master_add(&pdev->dev, &sti_drm_ops); +} + +static int sti_drm_platform_remove(struct platform_device *pdev) +{ + DRM_INFO("%s\n", __func__); + + component_master_del(&pdev->dev, &sti_drm_ops); + + return 0; +} + +static const struct of_device_id sti_drm_dt_ids[] = { + { .compatible = "st,sti-display-subsystem", }, + { /* end node */ }, +}; +MODULE_DEVICE_TABLE(of, sti_drm_dt_ids); + +static struct platform_driver sti_drm_platform_driver = { + .probe = sti_drm_platform_probe, + .remove = sti_drm_platform_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .of_match_table = sti_drm_dt_ids, + }, +}; + +module_platform_driver(sti_drm_platform_driver); + +MODULE_AUTHOR("Benjamin Gaignard benjamin.gaignard@st.com"); +MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sti/sti_drm_encoder.c b/drivers/gpu/drm/sti/sti_drm_encoder.c new file mode 100644 index 0000000..96194cf --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_encoder.c @@ -0,0 +1,201 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "sti_drm_crtc.h" +#include "sti_drm_encoder.h" +#include "sti_tvout.h" + +#define to_sti_encoder(x) container_of(x, struct sti_encoder, drm_encoder) + +#define ENCODER_MAIN_CRTC_MASK (1 << 0) + +/* + * sti specific encoder structure. + * + * @drm_encoder: encoder object. + * @tvout: pointer on tvout driver. + * @type: tvout connector type. + */ +struct sti_encoder { + struct drm_encoder drm_encoder; + struct sti_tvout *tvout; + enum sti_tvout_connector_type type; +}; + +static void sti_drm_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool sti_drm_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void sti_drm_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct sti_encoder *sti_encoder = to_sti_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct drm_connector *connector; + + dev_dbg(dev->dev, "%s\n", __func__); + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->encoder == encoder) { + sti_tvout_set_mode(sti_encoder->tvout, mode, + sti_encoder->type); + break; + } + } +} + +static void sti_drm_encoder_prepare(struct drm_encoder *encoder) +{ + struct sti_encoder *sti_encoder = to_sti_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct drm_connector *connector; + + dev_dbg(dev->dev, "%s\n", __func__); + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->encoder == encoder) { + sti_tvout_prepare(sti_encoder->tvout, + sti_encoder->type); + break; + } + } +} + +static void sti_drm_encoder_commit(struct drm_encoder *encoder) +{ + struct sti_encoder *sti_encoder = to_sti_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct drm_connector *connector; + + dev_dbg(dev->dev, "%s\n", __func__); + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->encoder == encoder) { + sti_tvout_commit(sti_encoder->tvout, + sti_encoder->type, + sti_drm_crtc_is_main(encoder->crtc)); + break; + } + } +} + +static void sti_drm_encoder_disable(struct drm_encoder *encoder) +{ + struct sti_encoder *sti_encoder = to_sti_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct drm_connector *connector; + struct drm_connector_helper_funcs *connector_funcs; + + dev_dbg(dev->dev, "%s\n", __func__); + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + connector_funcs = connector->helper_private; + if (connector_funcs->best_encoder(connector) == encoder) { + sti_tvout_disable(sti_encoder->tvout, + sti_encoder->type); + break; + } + } +} + +static const struct drm_encoder_helper_funcs sti_drm_encoder_helper_funcs = { + .dpms = sti_drm_encoder_dpms, + .mode_fixup = sti_drm_encoder_mode_fixup, + .mode_set = sti_drm_encoder_mode_set, + .prepare = sti_drm_encoder_prepare, + .commit = sti_drm_encoder_commit, + .disable = sti_drm_encoder_disable, +}; + +static void sti_drm_encoder_destroy(struct drm_encoder *encoder) +{ + struct sti_encoder *sti_encoder = to_sti_encoder(encoder); + struct drm_device *dev = encoder->dev; + + dev_dbg(dev->dev, "%s\n", __func__); + + drm_encoder_cleanup(encoder); + kfree(sti_encoder); +} + +static const struct drm_encoder_funcs sti_drm_encoder_funcs = { + .destroy = sti_drm_encoder_destroy, +}; + +struct drm_encoder *sti_drm_encoder_create(struct drm_device *dev, + struct sti_tvout *tvout, + unsigned int possible_crtcs, + enum sti_tvout_connector_type type) +{ + struct sti_encoder *sti_encoder; + struct drm_encoder *encoder; + int encoder_type; + + dev_dbg(dev->dev, "%s\n", __func__); + + if (!dev || !tvout || !possible_crtcs) + return NULL; + + sti_encoder = kzalloc(sizeof(*sti_encoder), GFP_KERNEL); + if (!sti_encoder) { + DRM_ERROR("failed to allocate encoder\n"); + return NULL; + } + + sti_encoder->tvout = tvout; + sti_encoder->type = type; + encoder = &sti_encoder->drm_encoder; + encoder->possible_crtcs = possible_crtcs; + + /* HDMI connector only supports main crtc */ + if (type == STI_TVOUT_CONNECTOR_HDMI) + encoder->possible_crtcs &= ENCODER_MAIN_CRTC_MASK; + + /* HD Analog connector may support aux CRTC. Not implemented yet */ + if (type == STI_TVOUT_CONNECTOR_HDA) + encoder->possible_crtcs &= ENCODER_MAIN_CRTC_MASK; + + DRM_DEBUG_DRIVER("possible_crtcs = 0x%x\n", encoder->possible_crtcs); + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + encoder_type = DRM_MODE_ENCODER_TMDS; + encoder->possible_clones = 1 << STI_TVOUT_CONNECTOR_HDA; + break; + case STI_TVOUT_CONNECTOR_HDA: + encoder_type = DRM_MODE_ENCODER_DAC; + encoder->possible_clones = 1 << STI_TVOUT_CONNECTOR_HDMI; + break; + case STI_TVOUT_CONNECTOR_DENC: + encoder_type = DRM_MODE_ENCODER_DAC; + break; + case STI_TVOUT_CONNECTOR_DVO: + encoder_type = DRM_MODE_ENCODER_LVDS; + break; + default: + encoder_type = DRM_MODE_ENCODER_NONE; + break; + } + + drm_encoder_init(dev, encoder, &sti_drm_encoder_funcs, encoder_type); + + drm_encoder_helper_add(encoder, &sti_drm_encoder_helper_funcs); + + DRM_DEBUG_DRIVER("encoder has been created\n"); + + return encoder; +} diff --git a/drivers/gpu/drm/sti/sti_drm_encoder.h b/drivers/gpu/drm/sti/sti_drm_encoder.h new file mode 100644 index 0000000..35d9945 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_encoder.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_DRM_ENCODER_H_ +#define _STI_DRM_ENCODER_H_ + +#include "sti_tvout.h" + +struct drm_encoder *sti_drm_encoder_create(struct drm_device *dev, + struct sti_tvout *tvout, unsigned int possible_crtcs, + enum sti_tvout_connector_type type); + +#endif diff --git a/drivers/gpu/drm/sti/sti_drm_plane.c b/drivers/gpu/drm/sti/sti_drm_plane.c new file mode 100644 index 0000000..e34c33a --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_plane.c @@ -0,0 +1,195 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Authors: Benjamin Gaignard benjamin.gaignard@st.com + * Fabien Dessenne fabien.dessenne@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ +#include "sti_drm_drv.h" +#include "sti_drm_plane.h" +#include "sti_compositor.h" +#include "sti_vtg_utils.h" + +enum sti_layer_desc sti_layer_default_zorder[] = { + STI_GDP_0, + STI_VID_0, + STI_GDP_1, + STI_VID_1, + STI_GDP_2, + STI_GDP_3, +}; + +/* (Background) < GDP0 < VID0 < GDP1 < VID1 < GDP2 < GDP3 < (ForeGround) */ + +static int +sti_drm_update_plane(struct drm_plane *plane, struct drm_crtc *crtc, + struct drm_framebuffer *fb, int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h) +{ + struct sti_layer *layer = to_sti_layer(plane); + struct sti_mixer *mixer = to_sti_mixer(crtc); + int res; + + DRM_DEBUG_KMS("CRTC:%d (%s) drm plane:%d (%s) drm fb:%d\n", + crtc->base.id, sti_mixer_to_str(mixer), + plane->base.id, sti_layer_to_str(layer), fb->base.id); + DRM_DEBUG_KMS("(%dx%d)@(%d,%d)\n", crtc_w, crtc_h, crtc_x, crtc_y); + + res = sti_mixer_set_layer_depth(mixer, layer); + if (res) { + DRM_ERROR("Can not set layer depth\n"); + return res; + } + + /* src_x are in 16.16 format. */ + res = sti_layer_prepare(layer, fb, &crtc->mode, mixer->id, + crtc_x, crtc_y, crtc_w, crtc_h, + src_x >> 16, src_y >> 16, + src_w >> 16, src_h >> 16); + if (res) { + DRM_ERROR("Layer prepare failed\n"); + return res; + } + + res = sti_layer_commit(layer); + if (res) { + DRM_ERROR("Layer commit failed\n"); + return res; + } + + res = sti_mixer_set_layer_status(mixer, layer, true); + if (res) { + DRM_ERROR("Can not enable layer at mixer\n"); + return res; + } + + return 0; +} + +static int sti_drm_disable_plane(struct drm_plane *plane) +{ + struct sti_layer *layer; + struct sti_mixer *mixer; + int lay_res, mix_res; + + if (!plane->crtc) { + DRM_DEBUG_DRIVER("drm plane:%d not enabled\n", plane->base.id); + return 0; + } + layer = to_sti_layer(plane); + mixer = to_sti_mixer(plane->crtc); + + DRM_DEBUG_DRIVER("CRTC:%d (%s) drm plane:%d (%s)\n", + plane->crtc->base.id, sti_mixer_to_str(mixer), + plane->base.id, sti_layer_to_str(layer)); + + /* Disable layer at mixer level */ + mix_res = sti_mixer_set_layer_status(mixer, layer, false); + if (mix_res) + DRM_ERROR("Can not disable layer at mixer\n"); + + /* Wait a while to be sure that a Vsync event is received */ + msleep(WAIT_NEXT_VSYNC_MS); + + /* Then disable layer itself */ + lay_res = sti_layer_disable(layer); + if (lay_res) + DRM_ERROR("Layer disable failed\n"); + + if (lay_res || mix_res) + return 1; + else + return 0; +} + +static void sti_drm_plane_destroy(struct drm_plane *plane) +{ + DRM_DEBUG_DRIVER("\n"); + + sti_drm_disable_plane(plane); + drm_plane_cleanup(plane); +} + +static int sti_drm_plane_set_property(struct drm_plane *plane, + struct drm_property *property, + uint64_t val) +{ + struct drm_device *dev = plane->dev; + struct sti_drm_private *private = dev->dev_private; + struct sti_layer *layer = to_sti_layer(plane); + + DRM_DEBUG_DRIVER("\n"); + + if (property == private->plane_zorder_property) { + layer->zorder = val; + return 0; + } + + return -EINVAL; +} + +static struct drm_plane_funcs sti_drm_plane_funcs = { + .update_plane = sti_drm_update_plane, + .disable_plane = sti_drm_disable_plane, + .destroy = sti_drm_plane_destroy, + .set_property = sti_drm_plane_set_property, +}; + +static void sti_drm_plane_attach_zorder_property(struct drm_plane *plane, + uint64_t default_val) +{ + struct drm_device *dev = plane->dev; + struct sti_drm_private *private = dev->dev_private; + struct drm_property *prop; + struct sti_layer *layer = to_sti_layer(plane); + + dev_dbg(dev->dev, "%s zorder:%llu\n", __func__, default_val); + + prop = private->plane_zorder_property; + if (!prop) { + prop = drm_property_create_range(dev, 0, "zpos", 0, + GAM_MIXER_NB_DEPTH_LEVEL - 1); + if (!prop) + return; + + private->plane_zorder_property = prop; + } + + drm_object_attach_property(&plane->base, prop, default_val); + layer->zorder = default_val; +} + +struct drm_plane *sti_drm_plane_init(struct drm_device *dev, + struct sti_layer *layer, + unsigned int possible_crtcs) +{ + int err, i; + uint64_t default_zorder = 0; + + dev_dbg(dev->dev, "%s\n", __func__); + + err = drm_plane_init(dev, &layer->plane, possible_crtcs, + &sti_drm_plane_funcs, + sti_layer_get_formats(layer), + sti_layer_get_nb_formats(layer), false); + if (err) { + DRM_ERROR("Failed to initialize plane\n"); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(sti_layer_default_zorder); i++) + if (sti_layer_default_zorder[i] == layer->desc) + break; + + default_zorder = i; + + sti_drm_plane_attach_zorder_property(&layer->plane, default_zorder); + + DRM_DEBUG_DRIVER("drm plane:%d mapped to %s with zorder:%llu\n", + layer->plane.base.id, + sti_layer_to_str(layer), default_zorder); + + return &layer->plane; +} diff --git a/drivers/gpu/drm/sti/sti_drm_plane.h b/drivers/gpu/drm/sti/sti_drm_plane.h new file mode 100644 index 0000000..d182066 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_drm_plane.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard benjamin.gaignard@st.com for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_DRM_PLANE_H_ +#define _STI_DRM_PLANE_H_ + +#include <drm/drmP.h> +#include "sti_layer.h" + +struct drm_plane *sti_drm_plane_init(struct drm_device *dev, + struct sti_layer *layer, unsigned int possible_crtcs); + +#endif diff --git a/drivers/gpu/drm/sti/sti_tvout.c b/drivers/gpu/drm/sti/sti_tvout.c index fb199c4..c25a104 100644 --- a/drivers/gpu/drm/sti/sti_tvout.c +++ b/drivers/gpu/drm/sti/sti_tvout.c @@ -19,6 +19,8 @@ #include "sti_hdmi.h" #include "sti_hda.h" #include "sti_drm_drv.h" +#include "sti_drm_encoder.h" +#include "sti_drm_connector.h"
/* glue regsiters */ #define TVO_CSC_MAIN_M0 0x000 @@ -788,6 +790,25 @@ int sti_tvout_hda_dbg_show(struct seq_file *m, void *arg)
static int sti_tvout_bind(struct device *dev, struct device *master, void *data) { + struct sti_tvout *tvout = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + struct drm_encoder *encoder; + int i; + struct sti_drm_private *dev_priv = drm_dev->dev_private; + + dev_priv->tvout = tvout; + + tvout->drm_dev = drm_dev; + + /* Register all encoder/connector couples supported by tvout */ + for (i = 0; i < STI_TVOUT_CONNECTOR_MAX; i++) { + if (tvout->connector_create[i] != NULL) { + encoder = sti_drm_encoder_create(drm_dev, tvout, + 1 << 0, i); + sti_drm_connector_create(drm_dev, tvout, encoder, i); + } + } + return 0; }
linaro-mm-sig@lists.linaro.org