diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/gpu/drm/sti/sti_vtg.c | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/sti/sti_vtg.c')
-rw-r--r-- | drivers/gpu/drm/sti/sti_vtg.c | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/drivers/gpu/drm/sti/sti_vtg.c b/drivers/gpu/drm/sti/sti_vtg.c new file mode 100644 index 000000000..6c421644d --- /dev/null +++ b/drivers/gpu/drm/sti/sti_vtg.c @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Benjamin Gaignard <benjamin.gaignard@st.com> + * Fabien Dessenne <fabien.dessenne@st.com> + * Vincent Abriou <vincent.abriou@st.com> + * for STMicroelectronics. + */ + +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> + +#include <drm/drmP.h> + +#include "sti_drv.h" +#include "sti_vtg.h" + +#define VTG_MODE_MASTER 0 + +/* registers offset */ +#define VTG_MODE 0x0000 +#define VTG_CLKLN 0x0008 +#define VTG_HLFLN 0x000C +#define VTG_DRST_AUTOC 0x0010 +#define VTG_VID_TFO 0x0040 +#define VTG_VID_TFS 0x0044 +#define VTG_VID_BFO 0x0048 +#define VTG_VID_BFS 0x004C + +#define VTG_HOST_ITS 0x0078 +#define VTG_HOST_ITS_BCLR 0x007C +#define VTG_HOST_ITM_BCLR 0x0088 +#define VTG_HOST_ITM_BSET 0x008C + +#define VTG_H_HD_1 0x00C0 +#define VTG_TOP_V_VD_1 0x00C4 +#define VTG_BOT_V_VD_1 0x00C8 +#define VTG_TOP_V_HD_1 0x00CC +#define VTG_BOT_V_HD_1 0x00D0 + +#define VTG_H_HD_2 0x00E0 +#define VTG_TOP_V_VD_2 0x00E4 +#define VTG_BOT_V_VD_2 0x00E8 +#define VTG_TOP_V_HD_2 0x00EC +#define VTG_BOT_V_HD_2 0x00F0 + +#define VTG_H_HD_3 0x0100 +#define VTG_TOP_V_VD_3 0x0104 +#define VTG_BOT_V_VD_3 0x0108 +#define VTG_TOP_V_HD_3 0x010C +#define VTG_BOT_V_HD_3 0x0110 + +#define VTG_H_HD_4 0x0120 +#define VTG_TOP_V_VD_4 0x0124 +#define VTG_BOT_V_VD_4 0x0128 +#define VTG_TOP_V_HD_4 0x012c +#define VTG_BOT_V_HD_4 0x0130 + +#define VTG_IRQ_BOTTOM BIT(0) +#define VTG_IRQ_TOP BIT(1) +#define VTG_IRQ_MASK (VTG_IRQ_TOP | VTG_IRQ_BOTTOM) + +/* Delay introduced by the HDMI in nb of pixel */ +#define HDMI_DELAY (5) + +/* Delay introduced by the DVO in nb of pixel */ +#define DVO_DELAY (7) + +/* delay introduced by the Arbitrary Waveform Generator in nb of pixels */ +#define AWG_DELAY_HD (-9) +#define AWG_DELAY_ED (-8) +#define AWG_DELAY_SD (-7) + +/* + * STI VTG register offset structure + * + *@h_hd: stores the VTG_H_HD_x register offset + *@top_v_vd: stores the VTG_TOP_V_VD_x register offset + *@bot_v_vd: stores the VTG_BOT_V_VD_x register offset + *@top_v_hd: stores the VTG_TOP_V_HD_x register offset + *@bot_v_hd: stores the VTG_BOT_V_HD_x register offset + */ +struct sti_vtg_regs_offs { + u32 h_hd; + u32 top_v_vd; + u32 bot_v_vd; + u32 top_v_hd; + u32 bot_v_hd; +}; + +#define VTG_MAX_SYNC_OUTPUT 4 +static const struct sti_vtg_regs_offs vtg_regs_offs[VTG_MAX_SYNC_OUTPUT] = { + { VTG_H_HD_1, + VTG_TOP_V_VD_1, VTG_BOT_V_VD_1, VTG_TOP_V_HD_1, VTG_BOT_V_HD_1 }, + { VTG_H_HD_2, + VTG_TOP_V_VD_2, VTG_BOT_V_VD_2, VTG_TOP_V_HD_2, VTG_BOT_V_HD_2 }, + { VTG_H_HD_3, + VTG_TOP_V_VD_3, VTG_BOT_V_VD_3, VTG_TOP_V_HD_3, VTG_BOT_V_HD_3 }, + { VTG_H_HD_4, + VTG_TOP_V_VD_4, VTG_BOT_V_VD_4, VTG_TOP_V_HD_4, VTG_BOT_V_HD_4 } +}; + +/* + * STI VTG synchronisation parameters structure + * + *@hsync: sample number falling and rising edge + *@vsync_line_top: vertical top field line number falling and rising edge + *@vsync_line_bot: vertical bottom field line number falling and rising edge + *@vsync_off_top: vertical top field sample number rising and falling edge + *@vsync_off_bot: vertical bottom field sample number rising and falling edge + */ +struct sti_vtg_sync_params { + u32 hsync; + u32 vsync_line_top; + u32 vsync_line_bot; + u32 vsync_off_top; + u32 vsync_off_bot; +}; + +/** + * STI VTG structure + * + * @regs: register mapping + * @sync_params: synchronisation parameters used to generate timings + * @irq: VTG irq + * @irq_status: store the IRQ status value + * @notifier_list: notifier callback + * @crtc: the CRTC for vblank event + */ +struct sti_vtg { + void __iomem *regs; + struct sti_vtg_sync_params sync_params[VTG_MAX_SYNC_OUTPUT]; + int irq; + u32 irq_status; + struct raw_notifier_head notifier_list; + struct drm_crtc *crtc; +}; + +struct sti_vtg *of_vtg_find(struct device_node *np) +{ + struct platform_device *pdev; + + pdev = of_find_device_by_node(np); + if (!pdev) + return NULL; + + return (struct sti_vtg *)platform_get_drvdata(pdev); +} + +static void vtg_reset(struct sti_vtg *vtg) +{ + writel(1, vtg->regs + VTG_DRST_AUTOC); +} + +static void vtg_set_output_window(void __iomem *regs, + const struct drm_display_mode *mode) +{ + u32 video_top_field_start; + u32 video_top_field_stop; + u32 video_bottom_field_start; + u32 video_bottom_field_stop; + u32 xstart = sti_vtg_get_pixel_number(*mode, 0); + u32 ystart = sti_vtg_get_line_number(*mode, 0); + u32 xstop = sti_vtg_get_pixel_number(*mode, mode->hdisplay - 1); + u32 ystop = sti_vtg_get_line_number(*mode, mode->vdisplay - 1); + + /* Set output window to fit the display mode selected */ + video_top_field_start = (ystart << 16) | xstart; + video_top_field_stop = (ystop << 16) | xstop; + + /* Only progressive supported for now */ + video_bottom_field_start = video_top_field_start; + video_bottom_field_stop = video_top_field_stop; + + writel(video_top_field_start, regs + VTG_VID_TFO); + writel(video_top_field_stop, regs + VTG_VID_TFS); + writel(video_bottom_field_start, regs + VTG_VID_BFO); + writel(video_bottom_field_stop, regs + VTG_VID_BFS); +} + +static void vtg_set_hsync_vsync_pos(struct sti_vtg_sync_params *sync, + int delay, + const struct drm_display_mode *mode) +{ + long clocksperline, start, stop; + u32 risesync_top, fallsync_top; + u32 risesync_offs_top, fallsync_offs_top; + + clocksperline = mode->htotal; + + /* Get the hsync position */ + start = 0; + stop = mode->hsync_end - mode->hsync_start; + + start += delay; + stop += delay; + + if (start < 0) + start += clocksperline; + else if (start >= clocksperline) + start -= clocksperline; + + if (stop < 0) + stop += clocksperline; + else if (stop >= clocksperline) + stop -= clocksperline; + + sync->hsync = (stop << 16) | start; + + /* Get the vsync position */ + if (delay >= 0) { + risesync_top = 1; + fallsync_top = risesync_top; + fallsync_top += mode->vsync_end - mode->vsync_start; + + fallsync_offs_top = (u32)delay; + risesync_offs_top = (u32)delay; + } else { + risesync_top = mode->vtotal; + fallsync_top = mode->vsync_end - mode->vsync_start; + + fallsync_offs_top = clocksperline + delay; + risesync_offs_top = clocksperline + delay; + } + + sync->vsync_line_top = (fallsync_top << 16) | risesync_top; + sync->vsync_off_top = (fallsync_offs_top << 16) | risesync_offs_top; + + /* Only progressive supported for now */ + sync->vsync_line_bot = sync->vsync_line_top; + sync->vsync_off_bot = sync->vsync_off_top; +} + +static void vtg_set_mode(struct sti_vtg *vtg, + int type, + struct sti_vtg_sync_params *sync, + const struct drm_display_mode *mode) +{ + unsigned int i; + + /* Set the number of clock cycles per line */ + writel(mode->htotal, vtg->regs + VTG_CLKLN); + + /* Set Half Line Per Field (only progressive supported for now) */ + writel(mode->vtotal * 2, vtg->regs + VTG_HLFLN); + + /* Program output window */ + vtg_set_output_window(vtg->regs, mode); + + /* Set hsync and vsync position for HDMI */ + vtg_set_hsync_vsync_pos(&sync[VTG_SYNC_ID_HDMI - 1], HDMI_DELAY, mode); + + /* Set hsync and vsync position for HD DCS */ + vtg_set_hsync_vsync_pos(&sync[VTG_SYNC_ID_HDDCS - 1], 0, mode); + + /* Set hsync and vsync position for HDF */ + vtg_set_hsync_vsync_pos(&sync[VTG_SYNC_ID_HDF - 1], AWG_DELAY_HD, mode); + + /* Set hsync and vsync position for DVO */ + vtg_set_hsync_vsync_pos(&sync[VTG_SYNC_ID_DVO - 1], DVO_DELAY, mode); + + /* Progam the syncs outputs */ + for (i = 0; i < VTG_MAX_SYNC_OUTPUT ; i++) { + writel(sync[i].hsync, + vtg->regs + vtg_regs_offs[i].h_hd); + writel(sync[i].vsync_line_top, + vtg->regs + vtg_regs_offs[i].top_v_vd); + writel(sync[i].vsync_line_bot, + vtg->regs + vtg_regs_offs[i].bot_v_vd); + writel(sync[i].vsync_off_top, + vtg->regs + vtg_regs_offs[i].top_v_hd); + writel(sync[i].vsync_off_bot, + vtg->regs + vtg_regs_offs[i].bot_v_hd); + } + + /* mode */ + writel(type, vtg->regs + VTG_MODE); +} + +static void vtg_enable_irq(struct sti_vtg *vtg) +{ + /* clear interrupt status and mask */ + writel(0xFFFF, vtg->regs + VTG_HOST_ITS_BCLR); + writel(0xFFFF, vtg->regs + VTG_HOST_ITM_BCLR); + writel(VTG_IRQ_MASK, vtg->regs + VTG_HOST_ITM_BSET); +} + +void sti_vtg_set_config(struct sti_vtg *vtg, + const struct drm_display_mode *mode) +{ + /* write configuration */ + vtg_set_mode(vtg, VTG_MODE_MASTER, vtg->sync_params, mode); + + vtg_reset(vtg); + + vtg_enable_irq(vtg); +} + +/** + * sti_vtg_get_line_number + * + * @mode: display mode to be used + * @y: line + * + * Return the line number according to the display mode taking + * into account the Sync and Back Porch information. + * Video frame line numbers start at 1, y starts at 0. + * In interlaced modes the start line is the field line number of the odd + * field, but y is still defined as a progressive frame. + */ +u32 sti_vtg_get_line_number(struct drm_display_mode mode, int y) +{ + u32 start_line = mode.vtotal - mode.vsync_start + 1; + + if (mode.flags & DRM_MODE_FLAG_INTERLACE) + start_line *= 2; + + return start_line + y; +} + +/** + * sti_vtg_get_pixel_number + * + * @mode: display mode to be used + * @x: row + * + * Return the pixel number according to the display mode taking + * into account the Sync and Back Porch information. + * Pixels are counted from 0. + */ +u32 sti_vtg_get_pixel_number(struct drm_display_mode mode, int x) +{ + return mode.htotal - mode.hsync_start + x; +} + +int sti_vtg_register_client(struct sti_vtg *vtg, struct notifier_block *nb, + struct drm_crtc *crtc) +{ + vtg->crtc = crtc; + return raw_notifier_chain_register(&vtg->notifier_list, nb); +} + +int sti_vtg_unregister_client(struct sti_vtg *vtg, struct notifier_block *nb) +{ + return raw_notifier_chain_unregister(&vtg->notifier_list, nb); +} + +static irqreturn_t vtg_irq_thread(int irq, void *arg) +{ + struct sti_vtg *vtg = arg; + u32 event; + + event = (vtg->irq_status & VTG_IRQ_TOP) ? + VTG_TOP_FIELD_EVENT : VTG_BOTTOM_FIELD_EVENT; + + raw_notifier_call_chain(&vtg->notifier_list, event, vtg->crtc); + + return IRQ_HANDLED; +} + +static irqreturn_t vtg_irq(int irq, void *arg) +{ + struct sti_vtg *vtg = arg; + + vtg->irq_status = readl(vtg->regs + VTG_HOST_ITS); + + writel(vtg->irq_status, vtg->regs + VTG_HOST_ITS_BCLR); + + /* force sync bus write */ + readl(vtg->regs + VTG_HOST_ITS); + + return IRQ_WAKE_THREAD; +} + +static int vtg_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sti_vtg *vtg; + struct resource *res; + int ret; + + vtg = devm_kzalloc(dev, sizeof(*vtg), GFP_KERNEL); + if (!vtg) + return -ENOMEM; + + /* Get Memory ressources */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + DRM_ERROR("Get memory resource failed\n"); + return -ENOMEM; + } + vtg->regs = devm_ioremap_nocache(dev, res->start, resource_size(res)); + if (!vtg->regs) { + DRM_ERROR("failed to remap I/O memory\n"); + return -ENOMEM; + } + + vtg->irq = platform_get_irq(pdev, 0); + if (vtg->irq < 0) { + DRM_ERROR("Failed to get VTG interrupt\n"); + return vtg->irq; + } + + RAW_INIT_NOTIFIER_HEAD(&vtg->notifier_list); + + ret = devm_request_threaded_irq(dev, vtg->irq, vtg_irq, + vtg_irq_thread, IRQF_ONESHOT, + dev_name(dev), vtg); + if (ret < 0) { + DRM_ERROR("Failed to register VTG interrupt\n"); + return ret; + } + + platform_set_drvdata(pdev, vtg); + + DRM_INFO("%s %s\n", __func__, dev_name(dev)); + + return 0; +} + +static const struct of_device_id vtg_of_match[] = { + { .compatible = "st,vtg", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vtg_of_match); + +struct platform_driver sti_vtg_driver = { + .driver = { + .name = "sti-vtg", + .owner = THIS_MODULE, + .of_match_table = vtg_of_match, + }, + .probe = vtg_probe, +}; + +MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver"); +MODULE_LICENSE("GPL"); |