18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) STMicroelectronics SA 2014
48c2ecf20Sopenharmony_ci * Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/clk.h>
88c2ecf20Sopenharmony_ci#include <linux/component.h>
98c2ecf20Sopenharmony_ci#include <linux/debugfs.h>
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/of_gpio.h>
128c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <drm/drm_atomic_helper.h>
158c2ecf20Sopenharmony_ci#include <drm/drm_bridge.h>
168c2ecf20Sopenharmony_ci#include <drm/drm_device.h>
178c2ecf20Sopenharmony_ci#include <drm/drm_panel.h>
188c2ecf20Sopenharmony_ci#include <drm/drm_print.h>
198c2ecf20Sopenharmony_ci#include <drm/drm_probe_helper.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include "sti_awg_utils.h"
228c2ecf20Sopenharmony_ci#include "sti_drv.h"
238c2ecf20Sopenharmony_ci#include "sti_mixer.h"
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci/* DVO registers */
268c2ecf20Sopenharmony_ci#define DVO_AWG_DIGSYNC_CTRL      0x0000
278c2ecf20Sopenharmony_ci#define DVO_DOF_CFG               0x0004
288c2ecf20Sopenharmony_ci#define DVO_LUT_PROG_LOW          0x0008
298c2ecf20Sopenharmony_ci#define DVO_LUT_PROG_MID          0x000C
308c2ecf20Sopenharmony_ci#define DVO_LUT_PROG_HIGH         0x0010
318c2ecf20Sopenharmony_ci#define DVO_DIGSYNC_INSTR_I       0x0100
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#define DVO_AWG_CTRL_EN           BIT(0)
348c2ecf20Sopenharmony_ci#define DVO_AWG_FRAME_BASED_SYNC  BIT(2)
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#define DVO_DOF_EN_LOWBYTE        BIT(0)
378c2ecf20Sopenharmony_ci#define DVO_DOF_EN_MIDBYTE        BIT(1)
388c2ecf20Sopenharmony_ci#define DVO_DOF_EN_HIGHBYTE       BIT(2)
398c2ecf20Sopenharmony_ci#define DVO_DOF_EN                BIT(6)
408c2ecf20Sopenharmony_ci#define DVO_DOF_MOD_COUNT_SHIFT   8
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#define DVO_LUT_ZERO              0
438c2ecf20Sopenharmony_ci#define DVO_LUT_Y_G               1
448c2ecf20Sopenharmony_ci#define DVO_LUT_Y_G_DEL           2
458c2ecf20Sopenharmony_ci#define DVO_LUT_CB_B              3
468c2ecf20Sopenharmony_ci#define DVO_LUT_CB_B_DEL          4
478c2ecf20Sopenharmony_ci#define DVO_LUT_CR_R              5
488c2ecf20Sopenharmony_ci#define DVO_LUT_CR_R_DEL          6
498c2ecf20Sopenharmony_ci#define DVO_LUT_HOLD              7
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistruct dvo_config {
528c2ecf20Sopenharmony_ci	u32 flags;
538c2ecf20Sopenharmony_ci	u32 lowbyte;
548c2ecf20Sopenharmony_ci	u32 midbyte;
558c2ecf20Sopenharmony_ci	u32 highbyte;
568c2ecf20Sopenharmony_ci	int (*awg_fwgen_fct)(
578c2ecf20Sopenharmony_ci			struct awg_code_generation_params *fw_gen_params,
588c2ecf20Sopenharmony_ci			struct awg_timing *timing);
598c2ecf20Sopenharmony_ci};
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistatic struct dvo_config rgb_24bit_de_cfg = {
628c2ecf20Sopenharmony_ci	.flags         = (0L << DVO_DOF_MOD_COUNT_SHIFT),
638c2ecf20Sopenharmony_ci	.lowbyte       = DVO_LUT_CR_R,
648c2ecf20Sopenharmony_ci	.midbyte       = DVO_LUT_Y_G,
658c2ecf20Sopenharmony_ci	.highbyte      = DVO_LUT_CB_B,
668c2ecf20Sopenharmony_ci	.awg_fwgen_fct = sti_awg_generate_code_data_enable_mode,
678c2ecf20Sopenharmony_ci};
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci/*
708c2ecf20Sopenharmony_ci * STI digital video output structure
718c2ecf20Sopenharmony_ci *
728c2ecf20Sopenharmony_ci * @dev: driver device
738c2ecf20Sopenharmony_ci * @drm_dev: pointer to drm device
748c2ecf20Sopenharmony_ci * @mode: current display mode selected
758c2ecf20Sopenharmony_ci * @regs: dvo registers
768c2ecf20Sopenharmony_ci * @clk_pix: pixel clock for dvo
778c2ecf20Sopenharmony_ci * @clk: clock for dvo
788c2ecf20Sopenharmony_ci * @clk_main_parent: dvo parent clock if main path used
798c2ecf20Sopenharmony_ci * @clk_aux_parent: dvo parent clock if aux path used
808c2ecf20Sopenharmony_ci * @panel_node: panel node reference from device tree
818c2ecf20Sopenharmony_ci * @panel: reference to the panel connected to the dvo
828c2ecf20Sopenharmony_ci * @enabled: true if dvo is enabled else false
838c2ecf20Sopenharmony_ci * @encoder: drm_encoder it is bound
848c2ecf20Sopenharmony_ci */
858c2ecf20Sopenharmony_cistruct sti_dvo {
868c2ecf20Sopenharmony_ci	struct device dev;
878c2ecf20Sopenharmony_ci	struct drm_device *drm_dev;
888c2ecf20Sopenharmony_ci	struct drm_display_mode mode;
898c2ecf20Sopenharmony_ci	void __iomem *regs;
908c2ecf20Sopenharmony_ci	struct clk *clk_pix;
918c2ecf20Sopenharmony_ci	struct clk *clk;
928c2ecf20Sopenharmony_ci	struct clk *clk_main_parent;
938c2ecf20Sopenharmony_ci	struct clk *clk_aux_parent;
948c2ecf20Sopenharmony_ci	struct device_node *panel_node;
958c2ecf20Sopenharmony_ci	struct drm_panel *panel;
968c2ecf20Sopenharmony_ci	struct dvo_config *config;
978c2ecf20Sopenharmony_ci	bool enabled;
988c2ecf20Sopenharmony_ci	struct drm_encoder *encoder;
998c2ecf20Sopenharmony_ci	struct drm_bridge *bridge;
1008c2ecf20Sopenharmony_ci};
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_cistruct sti_dvo_connector {
1038c2ecf20Sopenharmony_ci	struct drm_connector drm_connector;
1048c2ecf20Sopenharmony_ci	struct drm_encoder *encoder;
1058c2ecf20Sopenharmony_ci	struct sti_dvo *dvo;
1068c2ecf20Sopenharmony_ci};
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci#define to_sti_dvo_connector(x) \
1098c2ecf20Sopenharmony_ci	container_of(x, struct sti_dvo_connector, drm_connector)
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci#define BLANKING_LEVEL 16
1128c2ecf20Sopenharmony_cistatic int dvo_awg_generate_code(struct sti_dvo *dvo, u8 *ram_size, u32 *ram_code)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	struct drm_display_mode *mode = &dvo->mode;
1158c2ecf20Sopenharmony_ci	struct dvo_config *config = dvo->config;
1168c2ecf20Sopenharmony_ci	struct awg_code_generation_params fw_gen_params;
1178c2ecf20Sopenharmony_ci	struct awg_timing timing;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	fw_gen_params.ram_code = ram_code;
1208c2ecf20Sopenharmony_ci	fw_gen_params.instruction_offset = 0;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	timing.total_lines = mode->vtotal;
1238c2ecf20Sopenharmony_ci	timing.active_lines = mode->vdisplay;
1248c2ecf20Sopenharmony_ci	timing.blanking_lines = mode->vsync_start - mode->vdisplay;
1258c2ecf20Sopenharmony_ci	timing.trailing_lines = mode->vtotal - mode->vsync_start;
1268c2ecf20Sopenharmony_ci	timing.total_pixels = mode->htotal;
1278c2ecf20Sopenharmony_ci	timing.active_pixels = mode->hdisplay;
1288c2ecf20Sopenharmony_ci	timing.blanking_pixels = mode->hsync_start - mode->hdisplay;
1298c2ecf20Sopenharmony_ci	timing.trailing_pixels = mode->htotal - mode->hsync_start;
1308c2ecf20Sopenharmony_ci	timing.blanking_level = BLANKING_LEVEL;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	if (config->awg_fwgen_fct(&fw_gen_params, &timing)) {
1338c2ecf20Sopenharmony_ci		DRM_ERROR("AWG firmware not properly generated\n");
1348c2ecf20Sopenharmony_ci		return -EINVAL;
1358c2ecf20Sopenharmony_ci	}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	*ram_size = fw_gen_params.instruction_offset;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	return 0;
1408c2ecf20Sopenharmony_ci}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci/* Configure AWG, writing instructions
1438c2ecf20Sopenharmony_ci *
1448c2ecf20Sopenharmony_ci * @dvo: pointer to DVO structure
1458c2ecf20Sopenharmony_ci * @awg_ram_code: pointer to AWG instructions table
1468c2ecf20Sopenharmony_ci * @nb: nb of AWG instructions
1478c2ecf20Sopenharmony_ci */
1488c2ecf20Sopenharmony_cistatic void dvo_awg_configure(struct sti_dvo *dvo, u32 *awg_ram_code, int nb)
1498c2ecf20Sopenharmony_ci{
1508c2ecf20Sopenharmony_ci	int i;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	DRM_DEBUG_DRIVER("\n");
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	for (i = 0; i < nb; i++)
1558c2ecf20Sopenharmony_ci		writel(awg_ram_code[i],
1568c2ecf20Sopenharmony_ci		       dvo->regs + DVO_DIGSYNC_INSTR_I + i * 4);
1578c2ecf20Sopenharmony_ci	for (i = nb; i < AWG_MAX_INST; i++)
1588c2ecf20Sopenharmony_ci		writel(0, dvo->regs + DVO_DIGSYNC_INSTR_I + i * 4);
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	writel(DVO_AWG_CTRL_EN, dvo->regs + DVO_AWG_DIGSYNC_CTRL);
1618c2ecf20Sopenharmony_ci}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci#define DBGFS_DUMP(reg) seq_printf(s, "\n  %-25s 0x%08X", #reg, \
1648c2ecf20Sopenharmony_ci				   readl(dvo->regs + reg))
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_cistatic void dvo_dbg_awg_microcode(struct seq_file *s, void __iomem *reg)
1678c2ecf20Sopenharmony_ci{
1688c2ecf20Sopenharmony_ci	unsigned int i;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	seq_puts(s, "\n\n");
1718c2ecf20Sopenharmony_ci	seq_puts(s, "  DVO AWG microcode:");
1728c2ecf20Sopenharmony_ci	for (i = 0; i < AWG_MAX_INST; i++) {
1738c2ecf20Sopenharmony_ci		if (i % 8 == 0)
1748c2ecf20Sopenharmony_ci			seq_printf(s, "\n  %04X:", i);
1758c2ecf20Sopenharmony_ci		seq_printf(s, " %04X", readl(reg + i * 4));
1768c2ecf20Sopenharmony_ci	}
1778c2ecf20Sopenharmony_ci}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_cistatic int dvo_dbg_show(struct seq_file *s, void *data)
1808c2ecf20Sopenharmony_ci{
1818c2ecf20Sopenharmony_ci	struct drm_info_node *node = s->private;
1828c2ecf20Sopenharmony_ci	struct sti_dvo *dvo = (struct sti_dvo *)node->info_ent->data;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	seq_printf(s, "DVO: (vaddr = 0x%p)", dvo->regs);
1858c2ecf20Sopenharmony_ci	DBGFS_DUMP(DVO_AWG_DIGSYNC_CTRL);
1868c2ecf20Sopenharmony_ci	DBGFS_DUMP(DVO_DOF_CFG);
1878c2ecf20Sopenharmony_ci	DBGFS_DUMP(DVO_LUT_PROG_LOW);
1888c2ecf20Sopenharmony_ci	DBGFS_DUMP(DVO_LUT_PROG_MID);
1898c2ecf20Sopenharmony_ci	DBGFS_DUMP(DVO_LUT_PROG_HIGH);
1908c2ecf20Sopenharmony_ci	dvo_dbg_awg_microcode(s, dvo->regs + DVO_DIGSYNC_INSTR_I);
1918c2ecf20Sopenharmony_ci	seq_putc(s, '\n');
1928c2ecf20Sopenharmony_ci	return 0;
1938c2ecf20Sopenharmony_ci}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_cistatic struct drm_info_list dvo_debugfs_files[] = {
1968c2ecf20Sopenharmony_ci	{ "dvo", dvo_dbg_show, 0, NULL },
1978c2ecf20Sopenharmony_ci};
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_cistatic void dvo_debugfs_init(struct sti_dvo *dvo, struct drm_minor *minor)
2008c2ecf20Sopenharmony_ci{
2018c2ecf20Sopenharmony_ci	unsigned int i;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(dvo_debugfs_files); i++)
2048c2ecf20Sopenharmony_ci		dvo_debugfs_files[i].data = dvo;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	drm_debugfs_create_files(dvo_debugfs_files,
2078c2ecf20Sopenharmony_ci				 ARRAY_SIZE(dvo_debugfs_files),
2088c2ecf20Sopenharmony_ci				 minor->debugfs_root, minor);
2098c2ecf20Sopenharmony_ci}
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_cistatic void sti_dvo_disable(struct drm_bridge *bridge)
2128c2ecf20Sopenharmony_ci{
2138c2ecf20Sopenharmony_ci	struct sti_dvo *dvo = bridge->driver_private;
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	if (!dvo->enabled)
2168c2ecf20Sopenharmony_ci		return;
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	DRM_DEBUG_DRIVER("\n");
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	if (dvo->config->awg_fwgen_fct)
2218c2ecf20Sopenharmony_ci		writel(0x00000000, dvo->regs + DVO_AWG_DIGSYNC_CTRL);
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	writel(0x00000000, dvo->regs + DVO_DOF_CFG);
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	drm_panel_disable(dvo->panel);
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	/* Disable/unprepare dvo clock */
2288c2ecf20Sopenharmony_ci	clk_disable_unprepare(dvo->clk_pix);
2298c2ecf20Sopenharmony_ci	clk_disable_unprepare(dvo->clk);
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	dvo->enabled = false;
2328c2ecf20Sopenharmony_ci}
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_cistatic void sti_dvo_pre_enable(struct drm_bridge *bridge)
2358c2ecf20Sopenharmony_ci{
2368c2ecf20Sopenharmony_ci	struct sti_dvo *dvo = bridge->driver_private;
2378c2ecf20Sopenharmony_ci	struct dvo_config *config = dvo->config;
2388c2ecf20Sopenharmony_ci	u32 val;
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	DRM_DEBUG_DRIVER("\n");
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	if (dvo->enabled)
2438c2ecf20Sopenharmony_ci		return;
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	/* Make sure DVO is disabled */
2468c2ecf20Sopenharmony_ci	writel(0x00000000, dvo->regs + DVO_DOF_CFG);
2478c2ecf20Sopenharmony_ci	writel(0x00000000, dvo->regs + DVO_AWG_DIGSYNC_CTRL);
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	if (config->awg_fwgen_fct) {
2508c2ecf20Sopenharmony_ci		u8 nb_instr;
2518c2ecf20Sopenharmony_ci		u32 awg_ram_code[AWG_MAX_INST];
2528c2ecf20Sopenharmony_ci		/* Configure AWG */
2538c2ecf20Sopenharmony_ci		if (!dvo_awg_generate_code(dvo, &nb_instr, awg_ram_code))
2548c2ecf20Sopenharmony_ci			dvo_awg_configure(dvo, awg_ram_code, nb_instr);
2558c2ecf20Sopenharmony_ci		else
2568c2ecf20Sopenharmony_ci			return;
2578c2ecf20Sopenharmony_ci	}
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	/* Prepare/enable clocks */
2608c2ecf20Sopenharmony_ci	if (clk_prepare_enable(dvo->clk_pix))
2618c2ecf20Sopenharmony_ci		DRM_ERROR("Failed to prepare/enable dvo_pix clk\n");
2628c2ecf20Sopenharmony_ci	if (clk_prepare_enable(dvo->clk))
2638c2ecf20Sopenharmony_ci		DRM_ERROR("Failed to prepare/enable dvo clk\n");
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	drm_panel_enable(dvo->panel);
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	/* Set LUT */
2688c2ecf20Sopenharmony_ci	writel(config->lowbyte,  dvo->regs + DVO_LUT_PROG_LOW);
2698c2ecf20Sopenharmony_ci	writel(config->midbyte,  dvo->regs + DVO_LUT_PROG_MID);
2708c2ecf20Sopenharmony_ci	writel(config->highbyte, dvo->regs + DVO_LUT_PROG_HIGH);
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	/* Digital output formatter config */
2738c2ecf20Sopenharmony_ci	val = (config->flags | DVO_DOF_EN);
2748c2ecf20Sopenharmony_ci	writel(val, dvo->regs + DVO_DOF_CFG);
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	dvo->enabled = true;
2778c2ecf20Sopenharmony_ci}
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_cistatic void sti_dvo_set_mode(struct drm_bridge *bridge,
2808c2ecf20Sopenharmony_ci			     const struct drm_display_mode *mode,
2818c2ecf20Sopenharmony_ci			     const struct drm_display_mode *adjusted_mode)
2828c2ecf20Sopenharmony_ci{
2838c2ecf20Sopenharmony_ci	struct sti_dvo *dvo = bridge->driver_private;
2848c2ecf20Sopenharmony_ci	struct sti_mixer *mixer = to_sti_mixer(dvo->encoder->crtc);
2858c2ecf20Sopenharmony_ci	int rate = mode->clock * 1000;
2868c2ecf20Sopenharmony_ci	struct clk *clkp;
2878c2ecf20Sopenharmony_ci	int ret;
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci	DRM_DEBUG_DRIVER("\n");
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci	drm_mode_copy(&dvo->mode, mode);
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci	/* According to the path used (main or aux), the dvo clocks should
2948c2ecf20Sopenharmony_ci	 * have a different parent clock. */
2958c2ecf20Sopenharmony_ci	if (mixer->id == STI_MIXER_MAIN)
2968c2ecf20Sopenharmony_ci		clkp = dvo->clk_main_parent;
2978c2ecf20Sopenharmony_ci	else
2988c2ecf20Sopenharmony_ci		clkp = dvo->clk_aux_parent;
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	if (clkp) {
3018c2ecf20Sopenharmony_ci		clk_set_parent(dvo->clk_pix, clkp);
3028c2ecf20Sopenharmony_ci		clk_set_parent(dvo->clk, clkp);
3038c2ecf20Sopenharmony_ci	}
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_ci	/* DVO clocks = compositor clock */
3068c2ecf20Sopenharmony_ci	ret = clk_set_rate(dvo->clk_pix, rate);
3078c2ecf20Sopenharmony_ci	if (ret < 0) {
3088c2ecf20Sopenharmony_ci		DRM_ERROR("Cannot set rate (%dHz) for dvo_pix clk\n", rate);
3098c2ecf20Sopenharmony_ci		return;
3108c2ecf20Sopenharmony_ci	}
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci	ret = clk_set_rate(dvo->clk, rate);
3138c2ecf20Sopenharmony_ci	if (ret < 0) {
3148c2ecf20Sopenharmony_ci		DRM_ERROR("Cannot set rate (%dHz) for dvo clk\n", rate);
3158c2ecf20Sopenharmony_ci		return;
3168c2ecf20Sopenharmony_ci	}
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ci	/* For now, we only support 24bit data enable (DE) synchro format */
3198c2ecf20Sopenharmony_ci	dvo->config = &rgb_24bit_de_cfg;
3208c2ecf20Sopenharmony_ci}
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_cistatic void sti_dvo_bridge_nope(struct drm_bridge *bridge)
3238c2ecf20Sopenharmony_ci{
3248c2ecf20Sopenharmony_ci	/* do nothing */
3258c2ecf20Sopenharmony_ci}
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_cistatic const struct drm_bridge_funcs sti_dvo_bridge_funcs = {
3288c2ecf20Sopenharmony_ci	.pre_enable = sti_dvo_pre_enable,
3298c2ecf20Sopenharmony_ci	.enable = sti_dvo_bridge_nope,
3308c2ecf20Sopenharmony_ci	.disable = sti_dvo_disable,
3318c2ecf20Sopenharmony_ci	.post_disable = sti_dvo_bridge_nope,
3328c2ecf20Sopenharmony_ci	.mode_set = sti_dvo_set_mode,
3338c2ecf20Sopenharmony_ci};
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_cistatic int sti_dvo_connector_get_modes(struct drm_connector *connector)
3368c2ecf20Sopenharmony_ci{
3378c2ecf20Sopenharmony_ci	struct sti_dvo_connector *dvo_connector
3388c2ecf20Sopenharmony_ci		= to_sti_dvo_connector(connector);
3398c2ecf20Sopenharmony_ci	struct sti_dvo *dvo = dvo_connector->dvo;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	if (dvo->panel)
3428c2ecf20Sopenharmony_ci		return drm_panel_get_modes(dvo->panel, connector);
3438c2ecf20Sopenharmony_ci
3448c2ecf20Sopenharmony_ci	return 0;
3458c2ecf20Sopenharmony_ci}
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci#define CLK_TOLERANCE_HZ 50
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_cistatic enum drm_mode_status
3508c2ecf20Sopenharmony_cisti_dvo_connector_mode_valid(struct drm_connector *connector,
3518c2ecf20Sopenharmony_ci			     struct drm_display_mode *mode)
3528c2ecf20Sopenharmony_ci{
3538c2ecf20Sopenharmony_ci	int target = mode->clock * 1000;
3548c2ecf20Sopenharmony_ci	int target_min = target - CLK_TOLERANCE_HZ;
3558c2ecf20Sopenharmony_ci	int target_max = target + CLK_TOLERANCE_HZ;
3568c2ecf20Sopenharmony_ci	int result;
3578c2ecf20Sopenharmony_ci	struct sti_dvo_connector *dvo_connector
3588c2ecf20Sopenharmony_ci		= to_sti_dvo_connector(connector);
3598c2ecf20Sopenharmony_ci	struct sti_dvo *dvo = dvo_connector->dvo;
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	result = clk_round_rate(dvo->clk_pix, target);
3628c2ecf20Sopenharmony_ci
3638c2ecf20Sopenharmony_ci	DRM_DEBUG_DRIVER("target rate = %d => available rate = %d\n",
3648c2ecf20Sopenharmony_ci			 target, result);
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ci	if ((result < target_min) || (result > target_max)) {
3678c2ecf20Sopenharmony_ci		DRM_DEBUG_DRIVER("dvo pixclk=%d not supported\n", target);
3688c2ecf20Sopenharmony_ci		return MODE_BAD;
3698c2ecf20Sopenharmony_ci	}
3708c2ecf20Sopenharmony_ci
3718c2ecf20Sopenharmony_ci	return MODE_OK;
3728c2ecf20Sopenharmony_ci}
3738c2ecf20Sopenharmony_ci
3748c2ecf20Sopenharmony_cistatic const
3758c2ecf20Sopenharmony_cistruct drm_connector_helper_funcs sti_dvo_connector_helper_funcs = {
3768c2ecf20Sopenharmony_ci	.get_modes = sti_dvo_connector_get_modes,
3778c2ecf20Sopenharmony_ci	.mode_valid = sti_dvo_connector_mode_valid,
3788c2ecf20Sopenharmony_ci};
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_cistatic enum drm_connector_status
3818c2ecf20Sopenharmony_cisti_dvo_connector_detect(struct drm_connector *connector, bool force)
3828c2ecf20Sopenharmony_ci{
3838c2ecf20Sopenharmony_ci	struct sti_dvo_connector *dvo_connector
3848c2ecf20Sopenharmony_ci		= to_sti_dvo_connector(connector);
3858c2ecf20Sopenharmony_ci	struct sti_dvo *dvo = dvo_connector->dvo;
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci	DRM_DEBUG_DRIVER("\n");
3888c2ecf20Sopenharmony_ci
3898c2ecf20Sopenharmony_ci	if (!dvo->panel) {
3908c2ecf20Sopenharmony_ci		dvo->panel = of_drm_find_panel(dvo->panel_node);
3918c2ecf20Sopenharmony_ci		if (IS_ERR(dvo->panel))
3928c2ecf20Sopenharmony_ci			dvo->panel = NULL;
3938c2ecf20Sopenharmony_ci	}
3948c2ecf20Sopenharmony_ci
3958c2ecf20Sopenharmony_ci	if (dvo->panel)
3968c2ecf20Sopenharmony_ci		return connector_status_connected;
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_ci	return connector_status_disconnected;
3998c2ecf20Sopenharmony_ci}
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_cistatic int sti_dvo_late_register(struct drm_connector *connector)
4028c2ecf20Sopenharmony_ci{
4038c2ecf20Sopenharmony_ci	struct sti_dvo_connector *dvo_connector
4048c2ecf20Sopenharmony_ci		= to_sti_dvo_connector(connector);
4058c2ecf20Sopenharmony_ci	struct sti_dvo *dvo = dvo_connector->dvo;
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_ci	dvo_debugfs_init(dvo, dvo->drm_dev->primary);
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_ci	return 0;
4108c2ecf20Sopenharmony_ci}
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_cistatic const struct drm_connector_funcs sti_dvo_connector_funcs = {
4138c2ecf20Sopenharmony_ci	.fill_modes = drm_helper_probe_single_connector_modes,
4148c2ecf20Sopenharmony_ci	.detect = sti_dvo_connector_detect,
4158c2ecf20Sopenharmony_ci	.destroy = drm_connector_cleanup,
4168c2ecf20Sopenharmony_ci	.reset = drm_atomic_helper_connector_reset,
4178c2ecf20Sopenharmony_ci	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
4188c2ecf20Sopenharmony_ci	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
4198c2ecf20Sopenharmony_ci	.late_register = sti_dvo_late_register,
4208c2ecf20Sopenharmony_ci};
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_cistatic struct drm_encoder *sti_dvo_find_encoder(struct drm_device *dev)
4238c2ecf20Sopenharmony_ci{
4248c2ecf20Sopenharmony_ci	struct drm_encoder *encoder;
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_ci	list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
4278c2ecf20Sopenharmony_ci		if (encoder->encoder_type == DRM_MODE_ENCODER_LVDS)
4288c2ecf20Sopenharmony_ci			return encoder;
4298c2ecf20Sopenharmony_ci	}
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci	return NULL;
4328c2ecf20Sopenharmony_ci}
4338c2ecf20Sopenharmony_ci
4348c2ecf20Sopenharmony_cistatic int sti_dvo_bind(struct device *dev, struct device *master, void *data)
4358c2ecf20Sopenharmony_ci{
4368c2ecf20Sopenharmony_ci	struct sti_dvo *dvo = dev_get_drvdata(dev);
4378c2ecf20Sopenharmony_ci	struct drm_device *drm_dev = data;
4388c2ecf20Sopenharmony_ci	struct drm_encoder *encoder;
4398c2ecf20Sopenharmony_ci	struct sti_dvo_connector *connector;
4408c2ecf20Sopenharmony_ci	struct drm_connector *drm_connector;
4418c2ecf20Sopenharmony_ci	struct drm_bridge *bridge;
4428c2ecf20Sopenharmony_ci	int err;
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_ci	/* Set the drm device handle */
4458c2ecf20Sopenharmony_ci	dvo->drm_dev = drm_dev;
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_ci	encoder = sti_dvo_find_encoder(drm_dev);
4488c2ecf20Sopenharmony_ci	if (!encoder)
4498c2ecf20Sopenharmony_ci		return -ENOMEM;
4508c2ecf20Sopenharmony_ci
4518c2ecf20Sopenharmony_ci	connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL);
4528c2ecf20Sopenharmony_ci	if (!connector)
4538c2ecf20Sopenharmony_ci		return -ENOMEM;
4548c2ecf20Sopenharmony_ci
4558c2ecf20Sopenharmony_ci	connector->dvo = dvo;
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_ci	bridge = devm_kzalloc(dev, sizeof(*bridge), GFP_KERNEL);
4588c2ecf20Sopenharmony_ci	if (!bridge)
4598c2ecf20Sopenharmony_ci		return -ENOMEM;
4608c2ecf20Sopenharmony_ci
4618c2ecf20Sopenharmony_ci	bridge->driver_private = dvo;
4628c2ecf20Sopenharmony_ci	bridge->funcs = &sti_dvo_bridge_funcs;
4638c2ecf20Sopenharmony_ci	bridge->of_node = dvo->dev.of_node;
4648c2ecf20Sopenharmony_ci	drm_bridge_add(bridge);
4658c2ecf20Sopenharmony_ci
4668c2ecf20Sopenharmony_ci	err = drm_bridge_attach(encoder, bridge, NULL, 0);
4678c2ecf20Sopenharmony_ci	if (err) {
4688c2ecf20Sopenharmony_ci		DRM_ERROR("Failed to attach bridge\n");
4698c2ecf20Sopenharmony_ci		return err;
4708c2ecf20Sopenharmony_ci	}
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_ci	dvo->bridge = bridge;
4738c2ecf20Sopenharmony_ci	connector->encoder = encoder;
4748c2ecf20Sopenharmony_ci	dvo->encoder = encoder;
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_ci	drm_connector = (struct drm_connector *)connector;
4778c2ecf20Sopenharmony_ci
4788c2ecf20Sopenharmony_ci	drm_connector->polled = DRM_CONNECTOR_POLL_HPD;
4798c2ecf20Sopenharmony_ci
4808c2ecf20Sopenharmony_ci	drm_connector_init(drm_dev, drm_connector,
4818c2ecf20Sopenharmony_ci			   &sti_dvo_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
4828c2ecf20Sopenharmony_ci	drm_connector_helper_add(drm_connector,
4838c2ecf20Sopenharmony_ci				 &sti_dvo_connector_helper_funcs);
4848c2ecf20Sopenharmony_ci
4858c2ecf20Sopenharmony_ci	err = drm_connector_attach_encoder(drm_connector, encoder);
4868c2ecf20Sopenharmony_ci	if (err) {
4878c2ecf20Sopenharmony_ci		DRM_ERROR("Failed to attach a connector to a encoder\n");
4888c2ecf20Sopenharmony_ci		goto err_sysfs;
4898c2ecf20Sopenharmony_ci	}
4908c2ecf20Sopenharmony_ci
4918c2ecf20Sopenharmony_ci	return 0;
4928c2ecf20Sopenharmony_ci
4938c2ecf20Sopenharmony_cierr_sysfs:
4948c2ecf20Sopenharmony_ci	drm_bridge_remove(bridge);
4958c2ecf20Sopenharmony_ci	return -EINVAL;
4968c2ecf20Sopenharmony_ci}
4978c2ecf20Sopenharmony_ci
4988c2ecf20Sopenharmony_cistatic void sti_dvo_unbind(struct device *dev,
4998c2ecf20Sopenharmony_ci			   struct device *master, void *data)
5008c2ecf20Sopenharmony_ci{
5018c2ecf20Sopenharmony_ci	struct sti_dvo *dvo = dev_get_drvdata(dev);
5028c2ecf20Sopenharmony_ci
5038c2ecf20Sopenharmony_ci	drm_bridge_remove(dvo->bridge);
5048c2ecf20Sopenharmony_ci}
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_cistatic const struct component_ops sti_dvo_ops = {
5078c2ecf20Sopenharmony_ci	.bind = sti_dvo_bind,
5088c2ecf20Sopenharmony_ci	.unbind = sti_dvo_unbind,
5098c2ecf20Sopenharmony_ci};
5108c2ecf20Sopenharmony_ci
5118c2ecf20Sopenharmony_cistatic int sti_dvo_probe(struct platform_device *pdev)
5128c2ecf20Sopenharmony_ci{
5138c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
5148c2ecf20Sopenharmony_ci	struct sti_dvo *dvo;
5158c2ecf20Sopenharmony_ci	struct resource *res;
5168c2ecf20Sopenharmony_ci	struct device_node *np = dev->of_node;
5178c2ecf20Sopenharmony_ci
5188c2ecf20Sopenharmony_ci	DRM_INFO("%s\n", __func__);
5198c2ecf20Sopenharmony_ci
5208c2ecf20Sopenharmony_ci	dvo = devm_kzalloc(dev, sizeof(*dvo), GFP_KERNEL);
5218c2ecf20Sopenharmony_ci	if (!dvo) {
5228c2ecf20Sopenharmony_ci		DRM_ERROR("Failed to allocate memory for DVO\n");
5238c2ecf20Sopenharmony_ci		return -ENOMEM;
5248c2ecf20Sopenharmony_ci	}
5258c2ecf20Sopenharmony_ci
5268c2ecf20Sopenharmony_ci	dvo->dev = pdev->dev;
5278c2ecf20Sopenharmony_ci
5288c2ecf20Sopenharmony_ci	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dvo-reg");
5298c2ecf20Sopenharmony_ci	if (!res) {
5308c2ecf20Sopenharmony_ci		DRM_ERROR("Invalid dvo resource\n");
5318c2ecf20Sopenharmony_ci		return -ENOMEM;
5328c2ecf20Sopenharmony_ci	}
5338c2ecf20Sopenharmony_ci	dvo->regs = devm_ioremap(dev, res->start,
5348c2ecf20Sopenharmony_ci			resource_size(res));
5358c2ecf20Sopenharmony_ci	if (!dvo->regs)
5368c2ecf20Sopenharmony_ci		return -ENOMEM;
5378c2ecf20Sopenharmony_ci
5388c2ecf20Sopenharmony_ci	dvo->clk_pix = devm_clk_get(dev, "dvo_pix");
5398c2ecf20Sopenharmony_ci	if (IS_ERR(dvo->clk_pix)) {
5408c2ecf20Sopenharmony_ci		DRM_ERROR("Cannot get dvo_pix clock\n");
5418c2ecf20Sopenharmony_ci		return PTR_ERR(dvo->clk_pix);
5428c2ecf20Sopenharmony_ci	}
5438c2ecf20Sopenharmony_ci
5448c2ecf20Sopenharmony_ci	dvo->clk = devm_clk_get(dev, "dvo");
5458c2ecf20Sopenharmony_ci	if (IS_ERR(dvo->clk)) {
5468c2ecf20Sopenharmony_ci		DRM_ERROR("Cannot get dvo clock\n");
5478c2ecf20Sopenharmony_ci		return PTR_ERR(dvo->clk);
5488c2ecf20Sopenharmony_ci	}
5498c2ecf20Sopenharmony_ci
5508c2ecf20Sopenharmony_ci	dvo->clk_main_parent = devm_clk_get(dev, "main_parent");
5518c2ecf20Sopenharmony_ci	if (IS_ERR(dvo->clk_main_parent)) {
5528c2ecf20Sopenharmony_ci		DRM_DEBUG_DRIVER("Cannot get main_parent clock\n");
5538c2ecf20Sopenharmony_ci		dvo->clk_main_parent = NULL;
5548c2ecf20Sopenharmony_ci	}
5558c2ecf20Sopenharmony_ci
5568c2ecf20Sopenharmony_ci	dvo->clk_aux_parent = devm_clk_get(dev, "aux_parent");
5578c2ecf20Sopenharmony_ci	if (IS_ERR(dvo->clk_aux_parent)) {
5588c2ecf20Sopenharmony_ci		DRM_DEBUG_DRIVER("Cannot get aux_parent clock\n");
5598c2ecf20Sopenharmony_ci		dvo->clk_aux_parent = NULL;
5608c2ecf20Sopenharmony_ci	}
5618c2ecf20Sopenharmony_ci
5628c2ecf20Sopenharmony_ci	dvo->panel_node = of_parse_phandle(np, "sti,panel", 0);
5638c2ecf20Sopenharmony_ci	if (!dvo->panel_node)
5648c2ecf20Sopenharmony_ci		DRM_ERROR("No panel associated to the dvo output\n");
5658c2ecf20Sopenharmony_ci	of_node_put(dvo->panel_node);
5668c2ecf20Sopenharmony_ci
5678c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, dvo);
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci	return component_add(&pdev->dev, &sti_dvo_ops);
5708c2ecf20Sopenharmony_ci}
5718c2ecf20Sopenharmony_ci
5728c2ecf20Sopenharmony_cistatic int sti_dvo_remove(struct platform_device *pdev)
5738c2ecf20Sopenharmony_ci{
5748c2ecf20Sopenharmony_ci	component_del(&pdev->dev, &sti_dvo_ops);
5758c2ecf20Sopenharmony_ci	return 0;
5768c2ecf20Sopenharmony_ci}
5778c2ecf20Sopenharmony_ci
5788c2ecf20Sopenharmony_cistatic const struct of_device_id dvo_of_match[] = {
5798c2ecf20Sopenharmony_ci	{ .compatible = "st,stih407-dvo", },
5808c2ecf20Sopenharmony_ci	{ /* end node */ }
5818c2ecf20Sopenharmony_ci};
5828c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, dvo_of_match);
5838c2ecf20Sopenharmony_ci
5848c2ecf20Sopenharmony_cistruct platform_driver sti_dvo_driver = {
5858c2ecf20Sopenharmony_ci	.driver = {
5868c2ecf20Sopenharmony_ci		.name = "sti-dvo",
5878c2ecf20Sopenharmony_ci		.owner = THIS_MODULE,
5888c2ecf20Sopenharmony_ci		.of_match_table = dvo_of_match,
5898c2ecf20Sopenharmony_ci	},
5908c2ecf20Sopenharmony_ci	.probe = sti_dvo_probe,
5918c2ecf20Sopenharmony_ci	.remove = sti_dvo_remove,
5928c2ecf20Sopenharmony_ci};
5938c2ecf20Sopenharmony_ci
5948c2ecf20Sopenharmony_ciMODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
5958c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
5968c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
597