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