diff options
Diffstat (limited to 'drivers/gpu/drm/xlnx/xlnx_sdi_timing.c')
-rw-r--r-- | drivers/gpu/drm/xlnx/xlnx_sdi_timing.c | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/drivers/gpu/drm/xlnx/xlnx_sdi_timing.c b/drivers/gpu/drm/xlnx/xlnx_sdi_timing.c new file mode 100644 index 000000000000..61ee98e87fdc --- /dev/null +++ b/drivers/gpu/drm/xlnx/xlnx_sdi_timing.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx FPGA SDI Tx timing controller driver + * + * Copyright (c) 2017 Xilinx Pvt., Ltd + * + * Contacts: Saurabh Sengar <saurabhs@xilinx.com> + */ + +#include <drm/drmP.h> +#include <linux/device.h> +#include <video/videomode.h> +#include "xlnx_sdi_timing.h" + +/* timing controller register offsets */ +#define XSTC_CTL 0x00 +#define XSTC_STATS 0x04 +#define XSTC_ERROR 0x08 +#define XSTC_GASIZE 0x60 +#define XSTC_GENC 0x68 +#define XSTC_GPOL 0x6c +#define XSTC_GHSIZE 0x70 +#define XSTC_GVSIZE 0x74 +#define XSTC_GHSYNC 0x78 +#define XSTC_GVBH_F0 0x7c +#define XSTC_GVSYNC_F0 0x80 +#define XSTC_GVSH_F0 0x84 +#define XSTC_GVBH_F1 0x88 +#define XSTC_GVSYNC_F1 0x8C +#define XSTC_GVSH_F1 0x90 +#define XSTC_GASIZE_F1 0x94 +#define XSTC_OFFSET 0x10000 + +/* timing controller register bit */ +#define XSTC_CTL_FIP BIT(6) /* field id polarity */ +#define XSTC_CTL_ACP BIT(5) /* active chroma polarity */ +#define XSTC_CTL_AVP BIT(4) /* active video polarity */ +#define XSTC_CTL_HSP BIT(3) /* hori sync polarity */ +#define XSTC_CTL_VSP BIT(2) /* vert sync polarity */ +#define XSTC_CTL_HBP BIT(1) /* hori blank polarity */ +#define XSTC_CTL_VBP BIT(0) /* vert blank polarity */ +#define XSTC_CTL_FIPSS BIT(26) /* field id polarity source */ +#define XSTC_CTL_ACPSS BIT(25) /* active chroma polarity src */ +#define XSTC_CTL_AVPSS BIT(24) /* active video polarity src */ +#define XSTC_CTL_HSPSS BIT(23) /* hori sync polarity src */ +#define XSTC_CTL_VSPSS BIT(22) /* vert sync polarity src */ +#define XSTC_CTL_HBPSS BIT(21) /* hori blank polarity src */ +#define XSTC_CTL_VBPSS BIT(20) /* vert blank polarity src */ +#define XSTC_CTL_VCSS BIT(18) /* chroma src */ +#define XSTC_CTL_VASS BIT(17) /* vertical offset src */ +#define XSTC_CTL_VBSS BIT(16) /* vertical sync end src */ +#define XSTC_CTL_VSSS BIT(15) /* vertical sync start src */ +#define XSTC_CTL_VFSS BIT(14) /* vertical active size src */ +#define XSTC_CTL_VTSS BIT(13) /* vertical frame size src */ +#define XSTC_CTL_HBSS BIT(11) /* horiz sync end src */ +#define XSTC_CTL_HSSS BIT(10) /* horiz sync start src */ +#define XSTC_CTL_HFSS BIT(9) /* horiz active size src */ +#define XSTC_CTL_HTSS BIT(8) /* horiz frame size src */ +#define XSTC_CTL_GE BIT(2) /* timing generator enable */ +#define XSTC_CTL_RU BIT(1) /* timing register update */ + +/* timing generator horizontal 1 */ +#define XSTC_GH1_BPSTART_MASK GENMASK(28, 16) +#define XSTC_GH1_BPSTART_SHIFT 16 +#define XSTC_GH1_SYNCSTART_MASK GENMASK(12, 0) +/* timing generator vertical 1 (filed 0) */ +#define XSTC_GV1_BPSTART_MASK GENMASK(28, 16) +#define XSTC_GV1_BPSTART_SHIFT 16 +#define XSTC_GV1_SYNCSTART_MASK GENMASK(12, 0) +/* timing generator/detector vblank/vsync horizontal offset registers */ +#define XSTC_XVXHOX_HEND_MASK GENMASK(28, 16) +#define XSTC_XVXHOX_HEND_SHIFT 16 +#define XSTC_XVXHOX_HSTART_MASK GENMASK(12, 0) + +#define XSTC_GHFRAME_HSIZE GENMASK(12, 0) +#define XSTC_GVFRAME_HSIZE_F1 GENMASK(12, 0) +#define XSTC_GA_ACTSIZE_MASK GENMASK(12, 0) +/* reset register bit definition */ +#define XSTC_RST BIT(31) +/* Interlaced bit in XSTC_GENC */ +#define XSTC_GENC_INTERL BIT(6) + +/** + * struct xlnx_stc_polarity - timing signal polarity + * + * @field_id: field ID polarity + * @vblank: vblank polarity + * @vsync: vsync polarity + * @hblank: hblank polarity + * @hsync: hsync polarity + */ +struct xlnx_stc_polarity { + u8 field_id; + u8 vblank; + u8 vsync; + u8 hblank; + u8 hsync; +}; + +/** + * struct xlnx_stc_hori_off - timing signal horizontal offset + * + * @v0blank_hori_start: vblank horizontal start (field 0) + * @v0blank_hori_end: vblank horizontal end (field 0) + * @v0sync_hori_start: vsync horizontal start (field 0) + * @v0sync_hori_end: vsync horizontal end (field 0) + * @v1blank_hori_start: vblank horizontal start (field 1) + * @v1blank_hori_end: vblank horizontal end (field 1) + * @v1sync_hori_start: vsync horizontal start (field 1) + * @v1sync_hori_end: vsync horizontal end (field 1) + */ +struct xlnx_stc_hori_off { + u16 v0blank_hori_start; + u16 v0blank_hori_end; + u16 v0sync_hori_start; + u16 v0sync_hori_end; + u16 v1blank_hori_start; + u16 v1blank_hori_end; + u16 v1sync_hori_start; + u16 v1sync_hori_end; +}; + +/** + * xlnx_stc_writel - Memory mapped SDI Tx timing controller write + * @base: Pointer to SDI Tx registers base + * @offset: Register offset + * @val: value to be written + * + * This function writes the value to SDI TX timing controller registers + */ +static inline void xlnx_stc_writel(void __iomem *base, int offset, u32 val) +{ + writel(val, base + XSTC_OFFSET + offset); +} + +/** + * xlnx_stc_readl - Memory mapped timing controllerregister read + * @base: Pointer to SDI Tx registers base + * @offset: Register offset + * + * Return: The contents of the SDI Tx timing controller register + * + * This function returns the contents of the corresponding SDI Tx register. + */ +static inline u32 xlnx_stc_readl(void __iomem *base, int offset) +{ + return readl(base + XSTC_OFFSET + offset); +} + +/** + * xlnx_stc_enable - Enable timing controller + * @base: Base address of SDI Tx subsystem + * + * This function enables the SDI Tx subsystem's timing controller + */ +void xlnx_stc_enable(void __iomem *base) +{ + u32 reg; + + reg = xlnx_stc_readl(base, XSTC_CTL); + xlnx_stc_writel(base, XSTC_CTL, reg | XSTC_CTL_GE); +} + +/** + * xlnx_stc_disable - Disable timing controller + * @base: Base address of SDI Tx subsystem + * + * This function disables the SDI Tx subsystem's timing controller + */ +void xlnx_stc_disable(void __iomem *base) +{ + u32 reg; + + reg = xlnx_stc_readl(base, XSTC_CTL); + xlnx_stc_writel(base, XSTC_CTL, reg & ~XSTC_CTL_GE); +} + +/** + * xlnx_stc_reset - Reset timing controller + * @base: Base address of SDI Tx subsystem + * + * This function resets the SDI Tx subsystem's timing controller + */ +void xlnx_stc_reset(void __iomem *base) +{ + u32 reg; + + xlnx_stc_writel(base, XSTC_CTL, XSTC_RST); + + /* enable register update */ + reg = xlnx_stc_readl(base, XSTC_CTL); + xlnx_stc_writel(base, XSTC_CTL, reg | XSTC_CTL_RU); +} + +/** + * xlnx_stc_polarity - Configure timing signal polarity + * @base: Base address of SDI Tx subsystem + * @polarity: timing signal polarity data + * + * This function configure timing signal polarity + */ +static void xlnx_stc_polarity(void __iomem *base, + struct xlnx_stc_polarity *polarity) +{ + u32 reg = 0; + + reg = XSTC_CTL_ACP; + reg |= XSTC_CTL_AVP; + if (polarity->field_id) + reg |= XSTC_CTL_FIP; + if (polarity->vblank) + reg |= XSTC_CTL_VBP; + if (polarity->vsync) + reg |= XSTC_CTL_VSP; + if (polarity->hblank) + reg |= XSTC_CTL_HBP; + if (polarity->hsync) + reg |= XSTC_CTL_HSP; + + xlnx_stc_writel(base, XSTC_GPOL, reg); +} + +/** + * xlnx_stc_hori_off - Configure horzontal timing offset + * @base: Base address of SDI Tx subsystem + * @hori_off: horizontal offset configuration data + * @flags: Display flags + * + * This function configure horizontal offset + */ +static void xlnx_stc_hori_off(void __iomem *base, + struct xlnx_stc_hori_off *hori_off, + enum display_flags flags) +{ + u32 reg; + + /* Calculate and update Generator VBlank Hori field 0 */ + reg = hori_off->v0blank_hori_start & XSTC_XVXHOX_HSTART_MASK; + reg |= (hori_off->v0blank_hori_end << XSTC_XVXHOX_HEND_SHIFT) & + XSTC_XVXHOX_HEND_MASK; + xlnx_stc_writel(base, XSTC_GVBH_F0, reg); + + /* Calculate and update Generator VSync Hori field 0 */ + reg = hori_off->v0sync_hori_start & XSTC_XVXHOX_HSTART_MASK; + reg |= (hori_off->v0sync_hori_end << XSTC_XVXHOX_HEND_SHIFT) & + XSTC_XVXHOX_HEND_MASK; + xlnx_stc_writel(base, XSTC_GVSH_F0, reg); + + /* Calculate and update Generator VBlank Hori field 1 */ + if (flags & DISPLAY_FLAGS_INTERLACED) { + reg = hori_off->v1blank_hori_start & XSTC_XVXHOX_HSTART_MASK; + reg |= (hori_off->v1blank_hori_end << XSTC_XVXHOX_HEND_SHIFT) & + XSTC_XVXHOX_HEND_MASK; + xlnx_stc_writel(base, XSTC_GVBH_F1, reg); + } + + /* Calculate and update Generator VBlank Hori field 1 */ + if (flags & DISPLAY_FLAGS_INTERLACED) { + reg = hori_off->v1sync_hori_start & XSTC_XVXHOX_HSTART_MASK; + reg |= (hori_off->v1sync_hori_end << XSTC_XVXHOX_HEND_SHIFT) & + XSTC_XVXHOX_HEND_MASK; + xlnx_stc_writel(base, XSTC_GVSH_F1, reg); + } +} + +/** + * xlnx_stc_src - Configure timing source + * @base: Base address of SDI Tx subsystem + * + * This function configure timing source + */ +static void xlnx_stc_src(void __iomem *base) +{ + u32 reg; + + reg = xlnx_stc_readl(base, XSTC_CTL); + reg |= XSTC_CTL_VCSS; + reg |= XSTC_CTL_VASS; + reg |= XSTC_CTL_VBSS; + reg |= XSTC_CTL_VSSS; + reg |= XSTC_CTL_VFSS; + reg |= XSTC_CTL_VTSS; + reg |= XSTC_CTL_HBSS; + reg |= XSTC_CTL_HSSS; + reg |= XSTC_CTL_HFSS; + reg |= XSTC_CTL_HTSS; + xlnx_stc_writel(base, XSTC_CTL, reg); +} + +/** + * xlnx_stc_sig - Generates timing signal + * @base: Base address of SDI Tx subsystem + * @vm: video mode + * + * This function generated the timing for given vide mode + */ +void xlnx_stc_sig(void __iomem *base, struct videomode *vm) +{ + u32 reg; + u32 htotal, hactive, hsync_start, hbackporch_start; + u32 vtotal, vactive, vsync_start, vbackporch_start; + struct xlnx_stc_hori_off hori_off; + struct xlnx_stc_polarity polarity; + + reg = xlnx_stc_readl(base, XSTC_CTL); + xlnx_stc_writel(base, XSTC_CTL, reg & ~XSTC_CTL_RU); + + htotal = vm->hactive + vm->hfront_porch + vm->hsync_len + + vm->hback_porch; + vtotal = vm->vactive + vm->vfront_porch + vm->vsync_len + + vm->vback_porch; + hactive = vm->hactive; + vactive = vm->vactive; + hsync_start = vm->hactive + vm->hfront_porch; + vsync_start = vm->vactive + vm->vfront_porch; + hbackporch_start = hsync_start + vm->hsync_len; + vbackporch_start = vsync_start + vm->vsync_len; + + DRM_DEBUG_DRIVER("ha: %d, va: %d\n", hactive, vactive); + DRM_DEBUG_DRIVER("hs: %d, hb: %d\n", hsync_start, hbackporch_start); + DRM_DEBUG_DRIVER("vs: %d, vb: %d\n", vsync_start, vbackporch_start); + DRM_DEBUG_DRIVER("ht: %d, vt: %d\n", htotal, vtotal); + + reg = htotal & XSTC_GHFRAME_HSIZE; + xlnx_stc_writel(base, XSTC_GHSIZE, reg); + reg = vtotal & XSTC_GVFRAME_HSIZE_F1; + if (vm->flags & DISPLAY_FLAGS_INTERLACED) { + if (vm->pixelclock == 148500000) + reg |= (reg + 2) << + XSTC_GV1_BPSTART_SHIFT; + else + reg |= (reg + 1) << + XSTC_GV1_BPSTART_SHIFT; + } else { + reg |= reg << XSTC_GV1_BPSTART_SHIFT; + } + xlnx_stc_writel(base, XSTC_GVSIZE, reg); + reg = hactive & XSTC_GA_ACTSIZE_MASK; + reg |= (vactive & XSTC_GA_ACTSIZE_MASK) << 16; + xlnx_stc_writel(base, XSTC_GASIZE, reg); + + if (vm->flags & DISPLAY_FLAGS_INTERLACED) { + if (vactive == 243) + reg = ((vactive + 1) & XSTC_GA_ACTSIZE_MASK) << 16; + else + reg = (vactive & XSTC_GA_ACTSIZE_MASK) << 16; + xlnx_stc_writel(base, XSTC_GASIZE_F1, reg); + } + + reg = hsync_start & XSTC_GH1_SYNCSTART_MASK; + reg |= (hbackporch_start << XSTC_GH1_BPSTART_SHIFT) & + XSTC_GH1_BPSTART_MASK; + xlnx_stc_writel(base, XSTC_GHSYNC, reg); + reg = vsync_start & XSTC_GV1_SYNCSTART_MASK; + reg |= (vbackporch_start << XSTC_GV1_BPSTART_SHIFT) & + XSTC_GV1_BPSTART_MASK; + + /* + * Fix the Vsync_vstart and vsync_vend of Field 0 + * for all interlaced modes including 3GB. + */ + if (vm->flags & DISPLAY_FLAGS_INTERLACED) + reg = ((((reg & XSTC_GV1_BPSTART_MASK) >> + XSTC_GV1_BPSTART_SHIFT) - 1) << + XSTC_GV1_BPSTART_SHIFT) | + ((reg & XSTC_GV1_SYNCSTART_MASK) - 1); + + xlnx_stc_writel(base, XSTC_GVSYNC_F0, reg); + + /* + * Fix the Vsync_vstart and vsync_vend of Field 1 + * for interlaced and 3GB modes. + */ + if (vm->flags & DISPLAY_FLAGS_INTERLACED) { + if (vm->pixelclock == 148500000) + /* Revert and increase by 1 for 3GB mode */ + reg = ((((reg & XSTC_GV1_BPSTART_MASK) >> + XSTC_GV1_BPSTART_SHIFT) + 2) << + XSTC_GV1_BPSTART_SHIFT) | + ((reg & XSTC_GV1_SYNCSTART_MASK) + 2); + else + /* Only revert the reduction */ + reg = ((((reg & XSTC_GV1_BPSTART_MASK) >> + XSTC_GV1_BPSTART_SHIFT) + 1) << + XSTC_GV1_BPSTART_SHIFT) | + ((reg & XSTC_GV1_SYNCSTART_MASK) + 1); + } + + hori_off.v0blank_hori_start = hactive; + hori_off.v0blank_hori_end = hactive; + hori_off.v0sync_hori_start = hsync_start; + hori_off.v0sync_hori_end = hsync_start; + hori_off.v1blank_hori_start = hactive; + hori_off.v1blank_hori_end = hactive; + + if (vm->flags & DISPLAY_FLAGS_INTERLACED) { + hori_off.v1sync_hori_start = hsync_start - (htotal / 2); + hori_off.v1sync_hori_end = hsync_start - (htotal / 2); + xlnx_stc_writel(base, XSTC_GVSYNC_F1, reg); + reg = xlnx_stc_readl(base, XSTC_GENC) + | XSTC_GENC_INTERL; + xlnx_stc_writel(base, XSTC_GENC, reg); + } else { + hori_off.v1sync_hori_start = hsync_start; + hori_off.v1sync_hori_end = hsync_start; + reg = xlnx_stc_readl(base, XSTC_GENC) + & ~XSTC_GENC_INTERL; + xlnx_stc_writel(base, XSTC_GENC, reg); + } + + xlnx_stc_hori_off(base, &hori_off, vm->flags); + /* set up polarity */ + memset(&polarity, 0x0, sizeof(polarity)); + polarity.hsync = !!(vm->flags & DISPLAY_FLAGS_HSYNC_LOW); + polarity.vsync = !!(vm->flags & DISPLAY_FLAGS_VSYNC_LOW); + polarity.hblank = !!(vm->flags & DISPLAY_FLAGS_HSYNC_LOW); + polarity.vblank = !!(vm->flags & DISPLAY_FLAGS_VSYNC_LOW); + polarity.field_id = !!(vm->flags & DISPLAY_FLAGS_INTERLACED); + xlnx_stc_polarity(base, &polarity); + + xlnx_stc_src(base); + + reg = xlnx_stc_readl(base, XSTC_CTL); + xlnx_stc_writel(base, XSTC_CTL, reg | XSTC_CTL_RU); +} |