2017-12-06 12:29:47 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2014-12-30 15:08:16 +01:00
|
|
|
/*
|
|
|
|
* Copyright (C) STMicroelectronics SA 2014
|
|
|
|
* Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/component.h>
|
2016-02-04 16:27:54 +01:00
|
|
|
#include <linux/debugfs.h>
|
2024-04-08 20:04:11 +03:00
|
|
|
#include <linux/io.h>
|
2014-12-30 15:08:16 +01:00
|
|
|
#include <linux/module.h>
|
2022-08-12 17:57:45 -03:00
|
|
|
#include <linux/of.h>
|
2014-12-30 15:08:16 +01:00
|
|
|
#include <linux/platform_device.h>
|
|
|
|
|
2015-03-19 13:35:16 +01:00
|
|
|
#include <drm/drm_atomic_helper.h>
|
2019-08-26 17:26:29 +02:00
|
|
|
#include <drm/drm_bridge.h>
|
2019-06-05 15:48:35 +02:00
|
|
|
#include <drm/drm_device.h>
|
2014-12-30 15:08:16 +01:00
|
|
|
#include <drm/drm_panel.h>
|
2019-06-05 15:48:35 +02:00
|
|
|
#include <drm/drm_print.h>
|
2019-01-17 22:03:34 +01:00
|
|
|
#include <drm/drm_probe_helper.h>
|
2014-12-30 15:08:16 +01:00
|
|
|
|
|
|
|
#include "sti_awg_utils.h"
|
2016-09-19 16:33:53 +03:00
|
|
|
#include "sti_drv.h"
|
2014-12-30 15:08:16 +01:00
|
|
|
#include "sti_mixer.h"
|
|
|
|
|
|
|
|
/* DVO registers */
|
|
|
|
#define DVO_AWG_DIGSYNC_CTRL 0x0000
|
|
|
|
#define DVO_DOF_CFG 0x0004
|
|
|
|
#define DVO_LUT_PROG_LOW 0x0008
|
|
|
|
#define DVO_LUT_PROG_MID 0x000C
|
|
|
|
#define DVO_LUT_PROG_HIGH 0x0010
|
|
|
|
#define DVO_DIGSYNC_INSTR_I 0x0100
|
|
|
|
|
|
|
|
#define DVO_AWG_CTRL_EN BIT(0)
|
|
|
|
#define DVO_AWG_FRAME_BASED_SYNC BIT(2)
|
|
|
|
|
|
|
|
#define DVO_DOF_EN_LOWBYTE BIT(0)
|
|
|
|
#define DVO_DOF_EN_MIDBYTE BIT(1)
|
|
|
|
#define DVO_DOF_EN_HIGHBYTE BIT(2)
|
|
|
|
#define DVO_DOF_EN BIT(6)
|
|
|
|
#define DVO_DOF_MOD_COUNT_SHIFT 8
|
|
|
|
|
|
|
|
#define DVO_LUT_ZERO 0
|
|
|
|
#define DVO_LUT_Y_G 1
|
|
|
|
#define DVO_LUT_Y_G_DEL 2
|
|
|
|
#define DVO_LUT_CB_B 3
|
|
|
|
#define DVO_LUT_CB_B_DEL 4
|
|
|
|
#define DVO_LUT_CR_R 5
|
|
|
|
#define DVO_LUT_CR_R_DEL 6
|
|
|
|
#define DVO_LUT_HOLD 7
|
|
|
|
|
|
|
|
struct dvo_config {
|
|
|
|
u32 flags;
|
|
|
|
u32 lowbyte;
|
|
|
|
u32 midbyte;
|
|
|
|
u32 highbyte;
|
|
|
|
int (*awg_fwgen_fct)(
|
|
|
|
struct awg_code_generation_params *fw_gen_params,
|
|
|
|
struct awg_timing *timing);
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct dvo_config rgb_24bit_de_cfg = {
|
|
|
|
.flags = (0L << DVO_DOF_MOD_COUNT_SHIFT),
|
|
|
|
.lowbyte = DVO_LUT_CR_R,
|
|
|
|
.midbyte = DVO_LUT_Y_G,
|
|
|
|
.highbyte = DVO_LUT_CB_B,
|
|
|
|
.awg_fwgen_fct = sti_awg_generate_code_data_enable_mode,
|
|
|
|
};
|
|
|
|
|
2019-09-09 12:12:53 +02:00
|
|
|
/*
|
2014-12-30 15:08:16 +01:00
|
|
|
* STI digital video output structure
|
|
|
|
*
|
|
|
|
* @dev: driver device
|
|
|
|
* @drm_dev: pointer to drm device
|
|
|
|
* @mode: current display mode selected
|
|
|
|
* @regs: dvo registers
|
|
|
|
* @clk_pix: pixel clock for dvo
|
|
|
|
* @clk: clock for dvo
|
|
|
|
* @clk_main_parent: dvo parent clock if main path used
|
|
|
|
* @clk_aux_parent: dvo parent clock if aux path used
|
|
|
|
* @panel_node: panel node reference from device tree
|
|
|
|
* @panel: reference to the panel connected to the dvo
|
|
|
|
* @enabled: true if dvo is enabled else false
|
|
|
|
* @encoder: drm_encoder it is bound
|
|
|
|
*/
|
|
|
|
struct sti_dvo {
|
|
|
|
struct device dev;
|
|
|
|
struct drm_device *drm_dev;
|
|
|
|
struct drm_display_mode mode;
|
|
|
|
void __iomem *regs;
|
|
|
|
struct clk *clk_pix;
|
|
|
|
struct clk *clk;
|
|
|
|
struct clk *clk_main_parent;
|
|
|
|
struct clk *clk_aux_parent;
|
|
|
|
struct device_node *panel_node;
|
|
|
|
struct drm_panel *panel;
|
|
|
|
struct dvo_config *config;
|
|
|
|
bool enabled;
|
|
|
|
struct drm_encoder *encoder;
|
2025-05-09 15:53:42 +02:00
|
|
|
struct drm_bridge bridge;
|
2014-12-30 15:08:16 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
struct sti_dvo_connector {
|
|
|
|
struct drm_connector drm_connector;
|
|
|
|
struct drm_encoder *encoder;
|
|
|
|
struct sti_dvo *dvo;
|
|
|
|
};
|
|
|
|
|
|
|
|
#define to_sti_dvo_connector(x) \
|
|
|
|
container_of(x, struct sti_dvo_connector, drm_connector)
|
|
|
|
|
|
|
|
#define BLANKING_LEVEL 16
|
2016-09-19 16:33:53 +03:00
|
|
|
static int dvo_awg_generate_code(struct sti_dvo *dvo, u8 *ram_size, u32 *ram_code)
|
2014-12-30 15:08:16 +01:00
|
|
|
{
|
|
|
|
struct drm_display_mode *mode = &dvo->mode;
|
|
|
|
struct dvo_config *config = dvo->config;
|
|
|
|
struct awg_code_generation_params fw_gen_params;
|
|
|
|
struct awg_timing timing;
|
|
|
|
|
|
|
|
fw_gen_params.ram_code = ram_code;
|
|
|
|
fw_gen_params.instruction_offset = 0;
|
|
|
|
|
|
|
|
timing.total_lines = mode->vtotal;
|
|
|
|
timing.active_lines = mode->vdisplay;
|
|
|
|
timing.blanking_lines = mode->vsync_start - mode->vdisplay;
|
|
|
|
timing.trailing_lines = mode->vtotal - mode->vsync_start;
|
|
|
|
timing.total_pixels = mode->htotal;
|
|
|
|
timing.active_pixels = mode->hdisplay;
|
|
|
|
timing.blanking_pixels = mode->hsync_start - mode->hdisplay;
|
|
|
|
timing.trailing_pixels = mode->htotal - mode->hsync_start;
|
|
|
|
timing.blanking_level = BLANKING_LEVEL;
|
|
|
|
|
|
|
|
if (config->awg_fwgen_fct(&fw_gen_params, &timing)) {
|
|
|
|
DRM_ERROR("AWG firmware not properly generated\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ram_size = fw_gen_params.instruction_offset;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Configure AWG, writing instructions
|
|
|
|
*
|
|
|
|
* @dvo: pointer to DVO structure
|
|
|
|
* @awg_ram_code: pointer to AWG instructions table
|
|
|
|
* @nb: nb of AWG instructions
|
|
|
|
*/
|
|
|
|
static void dvo_awg_configure(struct sti_dvo *dvo, u32 *awg_ram_code, int nb)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
DRM_DEBUG_DRIVER("\n");
|
|
|
|
|
|
|
|
for (i = 0; i < nb; i++)
|
|
|
|
writel(awg_ram_code[i],
|
|
|
|
dvo->regs + DVO_DIGSYNC_INSTR_I + i * 4);
|
|
|
|
for (i = nb; i < AWG_MAX_INST; i++)
|
|
|
|
writel(0, dvo->regs + DVO_DIGSYNC_INSTR_I + i * 4);
|
|
|
|
|
|
|
|
writel(DVO_AWG_CTRL_EN, dvo->regs + DVO_AWG_DIGSYNC_CTRL);
|
|
|
|
}
|
|
|
|
|
2016-02-04 16:27:54 +01:00
|
|
|
#define DBGFS_DUMP(reg) seq_printf(s, "\n %-25s 0x%08X", #reg, \
|
|
|
|
readl(dvo->regs + reg))
|
|
|
|
|
|
|
|
static void dvo_dbg_awg_microcode(struct seq_file *s, void __iomem *reg)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
seq_puts(s, "\n\n");
|
|
|
|
seq_puts(s, " DVO AWG microcode:");
|
|
|
|
for (i = 0; i < AWG_MAX_INST; i++) {
|
|
|
|
if (i % 8 == 0)
|
|
|
|
seq_printf(s, "\n %04X:", i);
|
|
|
|
seq_printf(s, " %04X", readl(reg + i * 4));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dvo_dbg_show(struct seq_file *s, void *data)
|
|
|
|
{
|
|
|
|
struct drm_info_node *node = s->private;
|
|
|
|
struct sti_dvo *dvo = (struct sti_dvo *)node->info_ent->data;
|
|
|
|
|
|
|
|
seq_printf(s, "DVO: (vaddr = 0x%p)", dvo->regs);
|
|
|
|
DBGFS_DUMP(DVO_AWG_DIGSYNC_CTRL);
|
|
|
|
DBGFS_DUMP(DVO_DOF_CFG);
|
|
|
|
DBGFS_DUMP(DVO_LUT_PROG_LOW);
|
|
|
|
DBGFS_DUMP(DVO_LUT_PROG_MID);
|
|
|
|
DBGFS_DUMP(DVO_LUT_PROG_HIGH);
|
|
|
|
dvo_dbg_awg_microcode(s, dvo->regs + DVO_DIGSYNC_INSTR_I);
|
2017-05-05 15:00:46 +02:00
|
|
|
seq_putc(s, '\n');
|
2016-02-04 16:27:54 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct drm_info_list dvo_debugfs_files[] = {
|
|
|
|
{ "dvo", dvo_dbg_show, 0, NULL },
|
|
|
|
};
|
|
|
|
|
2020-03-10 16:31:13 +03:00
|
|
|
static void dvo_debugfs_init(struct sti_dvo *dvo, struct drm_minor *minor)
|
2016-02-04 16:27:54 +01:00
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dvo_debugfs_files); i++)
|
|
|
|
dvo_debugfs_files[i].data = dvo;
|
|
|
|
|
2020-03-10 16:31:13 +03:00
|
|
|
drm_debugfs_create_files(dvo_debugfs_files,
|
|
|
|
ARRAY_SIZE(dvo_debugfs_files),
|
|
|
|
minor->debugfs_root, minor);
|
2016-02-04 16:27:54 +01:00
|
|
|
}
|
|
|
|
|
2014-12-30 15:08:16 +01:00
|
|
|
static void sti_dvo_disable(struct drm_bridge *bridge)
|
|
|
|
{
|
|
|
|
struct sti_dvo *dvo = bridge->driver_private;
|
|
|
|
|
|
|
|
if (!dvo->enabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
DRM_DEBUG_DRIVER("\n");
|
|
|
|
|
|
|
|
if (dvo->config->awg_fwgen_fct)
|
|
|
|
writel(0x00000000, dvo->regs + DVO_AWG_DIGSYNC_CTRL);
|
|
|
|
|
|
|
|
writel(0x00000000, dvo->regs + DVO_DOF_CFG);
|
|
|
|
|
2019-08-04 22:16:29 +02:00
|
|
|
drm_panel_disable(dvo->panel);
|
2014-12-30 15:08:16 +01:00
|
|
|
|
|
|
|
/* Disable/unprepare dvo clock */
|
|
|
|
clk_disable_unprepare(dvo->clk_pix);
|
|
|
|
clk_disable_unprepare(dvo->clk);
|
|
|
|
|
|
|
|
dvo->enabled = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sti_dvo_pre_enable(struct drm_bridge *bridge)
|
|
|
|
{
|
|
|
|
struct sti_dvo *dvo = bridge->driver_private;
|
|
|
|
struct dvo_config *config = dvo->config;
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
DRM_DEBUG_DRIVER("\n");
|
|
|
|
|
|
|
|
if (dvo->enabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Make sure DVO is disabled */
|
|
|
|
writel(0x00000000, dvo->regs + DVO_DOF_CFG);
|
|
|
|
writel(0x00000000, dvo->regs + DVO_AWG_DIGSYNC_CTRL);
|
|
|
|
|
|
|
|
if (config->awg_fwgen_fct) {
|
|
|
|
u8 nb_instr;
|
|
|
|
u32 awg_ram_code[AWG_MAX_INST];
|
|
|
|
/* Configure AWG */
|
|
|
|
if (!dvo_awg_generate_code(dvo, &nb_instr, awg_ram_code))
|
|
|
|
dvo_awg_configure(dvo, awg_ram_code, nb_instr);
|
|
|
|
else
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Prepare/enable clocks */
|
|
|
|
if (clk_prepare_enable(dvo->clk_pix))
|
|
|
|
DRM_ERROR("Failed to prepare/enable dvo_pix clk\n");
|
|
|
|
if (clk_prepare_enable(dvo->clk))
|
|
|
|
DRM_ERROR("Failed to prepare/enable dvo clk\n");
|
|
|
|
|
2019-08-04 22:16:29 +02:00
|
|
|
drm_panel_enable(dvo->panel);
|
2014-12-30 15:08:16 +01:00
|
|
|
|
|
|
|
/* Set LUT */
|
|
|
|
writel(config->lowbyte, dvo->regs + DVO_LUT_PROG_LOW);
|
|
|
|
writel(config->midbyte, dvo->regs + DVO_LUT_PROG_MID);
|
|
|
|
writel(config->highbyte, dvo->regs + DVO_LUT_PROG_HIGH);
|
|
|
|
|
|
|
|
/* Digital output formatter config */
|
|
|
|
val = (config->flags | DVO_DOF_EN);
|
|
|
|
writel(val, dvo->regs + DVO_DOF_CFG);
|
|
|
|
|
|
|
|
dvo->enabled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sti_dvo_set_mode(struct drm_bridge *bridge,
|
2018-04-06 17:39:01 +03:00
|
|
|
const struct drm_display_mode *mode,
|
|
|
|
const struct drm_display_mode *adjusted_mode)
|
2014-12-30 15:08:16 +01:00
|
|
|
{
|
|
|
|
struct sti_dvo *dvo = bridge->driver_private;
|
|
|
|
struct sti_mixer *mixer = to_sti_mixer(dvo->encoder->crtc);
|
|
|
|
int rate = mode->clock * 1000;
|
|
|
|
struct clk *clkp;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
DRM_DEBUG_DRIVER("\n");
|
|
|
|
|
drm/sti: Use drm_mode_copy()
struct drm_display_mode embeds a list head, so overwriting
the full struct with another one will corrupt the list
(if the destination mode is on a list). Use drm_mode_copy()
instead which explicitly preserves the list head of
the destination mode.
Even if we know the destination mode is not on any list
using drm_mode_copy() seems decent as it sets a good
example. Bad examples of not using it might eventually
get copied into code where preserving the list head
actually matters.
Obviously one case not covered here is when the mode
itself is embedded in a larger structure and the whole
structure is copied. But if we are careful when copying
into modes embedded in structures I think we can be a
little more reassured that bogus list heads haven't been
propagated in.
@is_mode_copy@
@@
drm_mode_copy(...)
{
...
}
@depends on !is_mode_copy@
struct drm_display_mode *mode;
expression E, S;
@@
(
- *mode = E
+ drm_mode_copy(mode, &E)
|
- memcpy(mode, E, S)
+ drm_mode_copy(mode, E)
)
@depends on !is_mode_copy@
struct drm_display_mode mode;
expression E;
@@
(
- mode = E
+ drm_mode_copy(&mode, &E)
|
- memcpy(&mode, E, S)
+ drm_mode_copy(&mode, E)
)
@@
struct drm_display_mode *mode;
@@
- &*mode
+ mode
Cc: Alain Volmat <alain.volmat@foss.st.com>
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20221107192545.9896-8-ville.syrjala@linux.intel.com
Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
2022-11-07 21:25:45 +02:00
|
|
|
drm_mode_copy(&dvo->mode, mode);
|
2014-12-30 15:08:16 +01:00
|
|
|
|
|
|
|
/* According to the path used (main or aux), the dvo clocks should
|
|
|
|
* have a different parent clock. */
|
|
|
|
if (mixer->id == STI_MIXER_MAIN)
|
|
|
|
clkp = dvo->clk_main_parent;
|
|
|
|
else
|
|
|
|
clkp = dvo->clk_aux_parent;
|
|
|
|
|
|
|
|
if (clkp) {
|
|
|
|
clk_set_parent(dvo->clk_pix, clkp);
|
|
|
|
clk_set_parent(dvo->clk, clkp);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* DVO clocks = compositor clock */
|
|
|
|
ret = clk_set_rate(dvo->clk_pix, rate);
|
|
|
|
if (ret < 0) {
|
|
|
|
DRM_ERROR("Cannot set rate (%dHz) for dvo_pix clk\n", rate);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = clk_set_rate(dvo->clk, rate);
|
|
|
|
if (ret < 0) {
|
|
|
|
DRM_ERROR("Cannot set rate (%dHz) for dvo clk\n", rate);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* For now, we only support 24bit data enable (DE) synchro format */
|
|
|
|
dvo->config = &rgb_24bit_de_cfg;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sti_dvo_bridge_nope(struct drm_bridge *bridge)
|
|
|
|
{
|
|
|
|
/* do nothing */
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct drm_bridge_funcs sti_dvo_bridge_funcs = {
|
|
|
|
.pre_enable = sti_dvo_pre_enable,
|
|
|
|
.enable = sti_dvo_bridge_nope,
|
|
|
|
.disable = sti_dvo_disable,
|
|
|
|
.post_disable = sti_dvo_bridge_nope,
|
|
|
|
.mode_set = sti_dvo_set_mode,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int sti_dvo_connector_get_modes(struct drm_connector *connector)
|
|
|
|
{
|
|
|
|
struct sti_dvo_connector *dvo_connector
|
|
|
|
= to_sti_dvo_connector(connector);
|
|
|
|
struct sti_dvo *dvo = dvo_connector->dvo;
|
|
|
|
|
|
|
|
if (dvo->panel)
|
2019-12-07 15:03:34 +01:00
|
|
|
return drm_panel_get_modes(dvo->panel, connector);
|
2014-12-30 15:08:16 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define CLK_TOLERANCE_HZ 50
|
|
|
|
|
drm/sti: Fix return type of sti_{dvo,hda,hdmi}_connector_mode_valid()
With clang's kernel control flow integrity (kCFI, CONFIG_CFI_CLANG),
indirect call targets are validated against the expected function
pointer prototype to make sure the call target is valid to help mitigate
ROP attacks. If they are not identical, there is a failure at run time,
which manifests as either a kernel panic or thread getting killed. A
proposed warning in clang aims to catch these at compile time, which
reveals:
drivers/gpu/drm/sti/sti_hda.c:637:16: error: incompatible function pointer types initializing 'enum drm_mode_status (*)(struct drm_connector *, struct drm_display_mode *)' with an expression of type 'int (struct drm_connector *, struct drm_display_mode *)' [-Werror,-Wincompatible-function-pointer-types-strict]
.mode_valid = sti_hda_connector_mode_valid,
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/gpu/drm/sti/sti_dvo.c:376:16: error: incompatible function pointer types initializing 'enum drm_mode_status (*)(struct drm_connector *, struct drm_display_mode *)' with an expression of type 'int (struct drm_connector *, struct drm_display_mode *)' [-Werror,-Wincompatible-function-pointer-types-strict]
.mode_valid = sti_dvo_connector_mode_valid,
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/gpu/drm/sti/sti_hdmi.c:1035:16: error: incompatible function pointer types initializing 'enum drm_mode_status (*)(struct drm_connector *, struct drm_display_mode *)' with an expression of type 'int (struct drm_connector *, struct drm_display_mode *)' [-Werror,-Wincompatible-function-pointer-types-strict]
.mode_valid = sti_hdmi_connector_mode_valid,
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
->mode_valid() in 'struct drm_connector_helper_funcs' expects a return
type of 'enum drm_mode_status', not 'int'. Adjust the return type of
sti_{dvo,hda,hdmi}_connector_mode_valid() to match the prototype's to
resolve the warning and CFI failure.
Link: https://github.com/ClangBuiltLinux/linux/issues/1750
Signed-off-by: Nathan Chancellor <nathan@kernel.org>
Reviewed-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Kees Cook <keescook@chromium.org>
Link: https://lore.kernel.org/r/20221102155623.3042869-1-nathan@kernel.org
2022-11-02 08:56:23 -07:00
|
|
|
static enum drm_mode_status
|
|
|
|
sti_dvo_connector_mode_valid(struct drm_connector *connector,
|
2024-12-14 15:37:09 +02:00
|
|
|
const struct drm_display_mode *mode)
|
2014-12-30 15:08:16 +01:00
|
|
|
{
|
|
|
|
int target = mode->clock * 1000;
|
|
|
|
int target_min = target - CLK_TOLERANCE_HZ;
|
|
|
|
int target_max = target + CLK_TOLERANCE_HZ;
|
|
|
|
int result;
|
|
|
|
struct sti_dvo_connector *dvo_connector
|
|
|
|
= to_sti_dvo_connector(connector);
|
|
|
|
struct sti_dvo *dvo = dvo_connector->dvo;
|
|
|
|
|
|
|
|
result = clk_round_rate(dvo->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("dvo pixclk=%d not supported\n", target);
|
|
|
|
return MODE_BAD;
|
|
|
|
}
|
|
|
|
|
|
|
|
return MODE_OK;
|
|
|
|
}
|
|
|
|
|
2015-09-02 13:44:15 +03:00
|
|
|
static const
|
|
|
|
struct drm_connector_helper_funcs sti_dvo_connector_helper_funcs = {
|
2014-12-30 15:08:16 +01:00
|
|
|
.get_modes = sti_dvo_connector_get_modes,
|
|
|
|
.mode_valid = sti_dvo_connector_mode_valid,
|
|
|
|
};
|
|
|
|
|
|
|
|
static enum drm_connector_status
|
|
|
|
sti_dvo_connector_detect(struct drm_connector *connector, bool force)
|
|
|
|
{
|
|
|
|
struct sti_dvo_connector *dvo_connector
|
|
|
|
= to_sti_dvo_connector(connector);
|
|
|
|
struct sti_dvo *dvo = dvo_connector->dvo;
|
|
|
|
|
|
|
|
DRM_DEBUG_DRIVER("\n");
|
|
|
|
|
2016-01-28 09:11:03 +01:00
|
|
|
if (!dvo->panel) {
|
2014-12-30 15:08:16 +01:00
|
|
|
dvo->panel = of_drm_find_panel(dvo->panel_node);
|
2018-05-09 15:00:39 +02:00
|
|
|
if (IS_ERR(dvo->panel))
|
|
|
|
dvo->panel = NULL;
|
2016-01-28 09:11:03 +01:00
|
|
|
}
|
2014-12-30 15:08:16 +01:00
|
|
|
|
|
|
|
if (dvo->panel)
|
2016-01-28 09:11:03 +01:00
|
|
|
return connector_status_connected;
|
2014-12-30 15:08:16 +01:00
|
|
|
|
|
|
|
return connector_status_disconnected;
|
|
|
|
}
|
|
|
|
|
2016-06-21 15:09:39 +02:00
|
|
|
static int sti_dvo_late_register(struct drm_connector *connector)
|
2014-12-30 15:08:16 +01:00
|
|
|
{
|
|
|
|
struct sti_dvo_connector *dvo_connector
|
|
|
|
= to_sti_dvo_connector(connector);
|
2016-06-21 15:09:39 +02:00
|
|
|
struct sti_dvo *dvo = dvo_connector->dvo;
|
|
|
|
|
2020-03-10 16:31:13 +03:00
|
|
|
dvo_debugfs_init(dvo, dvo->drm_dev->primary);
|
2014-12-30 15:08:16 +01:00
|
|
|
|
2016-06-21 15:09:39 +02:00
|
|
|
return 0;
|
2014-12-30 15:08:16 +01:00
|
|
|
}
|
|
|
|
|
2015-09-02 13:44:15 +03:00
|
|
|
static const struct drm_connector_funcs sti_dvo_connector_funcs = {
|
2014-12-30 15:08:16 +01:00
|
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
|
|
.detect = sti_dvo_connector_detect,
|
2016-06-21 15:09:39 +02:00
|
|
|
.destroy = drm_connector_cleanup,
|
2015-03-19 13:35:16 +01:00
|
|
|
.reset = drm_atomic_helper_connector_reset,
|
|
|
|
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
|
|
|
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
2016-06-21 15:09:39 +02:00
|
|
|
.late_register = sti_dvo_late_register,
|
2014-12-30 15:08:16 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct drm_encoder *sti_dvo_find_encoder(struct drm_device *dev)
|
|
|
|
{
|
|
|
|
struct drm_encoder *encoder;
|
|
|
|
|
|
|
|
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
|
|
|
|
if (encoder->encoder_type == DRM_MODE_ENCODER_LVDS)
|
|
|
|
return encoder;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sti_dvo_bind(struct device *dev, struct device *master, void *data)
|
|
|
|
{
|
|
|
|
struct sti_dvo *dvo = dev_get_drvdata(dev);
|
|
|
|
struct drm_device *drm_dev = data;
|
|
|
|
struct drm_encoder *encoder;
|
|
|
|
struct sti_dvo_connector *connector;
|
|
|
|
struct drm_connector *drm_connector;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* Set the drm device handle */
|
|
|
|
dvo->drm_dev = drm_dev;
|
|
|
|
|
|
|
|
encoder = sti_dvo_find_encoder(drm_dev);
|
|
|
|
if (!encoder)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL);
|
|
|
|
if (!connector)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
connector->dvo = dvo;
|
|
|
|
|
2025-05-09 15:53:42 +02:00
|
|
|
dvo->bridge.driver_private = dvo;
|
|
|
|
dvo->bridge.of_node = dvo->dev.of_node;
|
|
|
|
drm_bridge_add(&dvo->bridge);
|
2014-12-30 15:08:16 +01:00
|
|
|
|
2025-05-09 15:53:42 +02:00
|
|
|
err = drm_bridge_attach(encoder, &dvo->bridge, NULL, 0);
|
2021-03-23 23:50:08 +02:00
|
|
|
if (err)
|
2015-01-27 09:37:03 +10:00
|
|
|
return err;
|
|
|
|
|
2014-12-30 15:08:16 +01:00
|
|
|
connector->encoder = encoder;
|
|
|
|
dvo->encoder = encoder;
|
|
|
|
|
|
|
|
drm_connector = (struct drm_connector *)connector;
|
|
|
|
|
|
|
|
drm_connector->polled = DRM_CONNECTOR_POLL_HPD;
|
|
|
|
|
|
|
|
drm_connector_init(drm_dev, drm_connector,
|
|
|
|
&sti_dvo_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
|
|
|
|
drm_connector_helper_add(drm_connector,
|
|
|
|
&sti_dvo_connector_helper_funcs);
|
|
|
|
|
2018-07-09 10:40:07 +02:00
|
|
|
err = drm_connector_attach_encoder(drm_connector, encoder);
|
2014-12-30 15:08:16 +01:00
|
|
|
if (err) {
|
|
|
|
DRM_ERROR("Failed to attach a connector to a encoder\n");
|
|
|
|
goto err_sysfs;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_sysfs:
|
2025-05-09 15:53:42 +02:00
|
|
|
drm_bridge_remove(&dvo->bridge);
|
2014-12-30 15:08:16 +01:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sti_dvo_unbind(struct device *dev,
|
|
|
|
struct device *master, void *data)
|
|
|
|
{
|
2015-01-27 09:37:03 +10:00
|
|
|
struct sti_dvo *dvo = dev_get_drvdata(dev);
|
|
|
|
|
2025-05-09 15:53:42 +02:00
|
|
|
drm_bridge_remove(&dvo->bridge);
|
2014-12-30 15:08:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct component_ops sti_dvo_ops = {
|
|
|
|
.bind = sti_dvo_bind,
|
|
|
|
.unbind = sti_dvo_unbind,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int sti_dvo_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct device *dev = &pdev->dev;
|
|
|
|
struct sti_dvo *dvo;
|
|
|
|
struct device_node *np = dev->of_node;
|
|
|
|
|
|
|
|
DRM_INFO("%s\n", __func__);
|
|
|
|
|
2025-05-09 15:53:42 +02:00
|
|
|
dvo = devm_drm_bridge_alloc(dev, struct sti_dvo, bridge, &sti_dvo_bridge_funcs);
|
|
|
|
if (IS_ERR(dvo)) {
|
|
|
|
DRM_ERROR("Failed to allocate DVO\n");
|
|
|
|
return PTR_ERR(dvo);
|
2014-12-30 15:08:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
dvo->dev = pdev->dev;
|
2025-03-04 16:05:35 -05:00
|
|
|
dvo->regs = devm_platform_ioremap_resource_byname(pdev, "dvo-reg");
|
|
|
|
if (IS_ERR(dvo->regs))
|
|
|
|
return PTR_ERR(dvo->regs);
|
2014-12-30 15:08:16 +01:00
|
|
|
|
|
|
|
dvo->clk_pix = devm_clk_get(dev, "dvo_pix");
|
|
|
|
if (IS_ERR(dvo->clk_pix)) {
|
|
|
|
DRM_ERROR("Cannot get dvo_pix clock\n");
|
|
|
|
return PTR_ERR(dvo->clk_pix);
|
|
|
|
}
|
|
|
|
|
|
|
|
dvo->clk = devm_clk_get(dev, "dvo");
|
|
|
|
if (IS_ERR(dvo->clk)) {
|
|
|
|
DRM_ERROR("Cannot get dvo clock\n");
|
|
|
|
return PTR_ERR(dvo->clk);
|
|
|
|
}
|
|
|
|
|
|
|
|
dvo->clk_main_parent = devm_clk_get(dev, "main_parent");
|
|
|
|
if (IS_ERR(dvo->clk_main_parent)) {
|
|
|
|
DRM_DEBUG_DRIVER("Cannot get main_parent clock\n");
|
|
|
|
dvo->clk_main_parent = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
dvo->clk_aux_parent = devm_clk_get(dev, "aux_parent");
|
|
|
|
if (IS_ERR(dvo->clk_aux_parent)) {
|
|
|
|
DRM_DEBUG_DRIVER("Cannot get aux_parent clock\n");
|
|
|
|
dvo->clk_aux_parent = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
dvo->panel_node = of_parse_phandle(np, "sti,panel", 0);
|
|
|
|
if (!dvo->panel_node)
|
|
|
|
DRM_ERROR("No panel associated to the dvo output\n");
|
2016-07-05 10:04:50 +08:00
|
|
|
of_node_put(dvo->panel_node);
|
2014-12-30 15:08:16 +01:00
|
|
|
|
|
|
|
platform_set_drvdata(pdev, dvo);
|
|
|
|
|
|
|
|
return component_add(&pdev->dev, &sti_dvo_ops);
|
|
|
|
}
|
|
|
|
|
2023-05-07 18:26:04 +02:00
|
|
|
static void sti_dvo_remove(struct platform_device *pdev)
|
2014-12-30 15:08:16 +01:00
|
|
|
{
|
|
|
|
component_del(&pdev->dev, &sti_dvo_ops);
|
|
|
|
}
|
|
|
|
|
2017-06-20 10:12:11 +05:30
|
|
|
static const struct of_device_id dvo_of_match[] = {
|
2014-12-30 15:08:16 +01:00
|
|
|
{ .compatible = "st,stih407-dvo", },
|
|
|
|
{ /* end node */ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, dvo_of_match);
|
|
|
|
|
|
|
|
struct platform_driver sti_dvo_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "sti-dvo",
|
|
|
|
.of_match_table = dvo_of_match,
|
|
|
|
},
|
|
|
|
.probe = sti_dvo_probe,
|
2024-12-01 15:12:43 -08:00
|
|
|
.remove = sti_dvo_remove,
|
2014-12-30 15:08:16 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
|
|
|
|
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
|
|
|
|
MODULE_LICENSE("GPL");
|