2017-12-06 19:29:47 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2014-12-30 22:08:16 +08: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 23:27:54 +08:00
|
|
|
#include <linux/debugfs.h>
|
2014-12-30 22:08:16 +08:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/of_gpio.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
|
2015-03-19 20:35:16 +08:00
|
|
|
#include <drm/drm_atomic_helper.h>
|
2019-08-26 23:26:29 +08:00
|
|
|
#include <drm/drm_bridge.h>
|
2019-06-05 21:48:35 +08:00
|
|
|
#include <drm/drm_device.h>
|
2014-12-30 22:08:16 +08:00
|
|
|
#include <drm/drm_panel.h>
|
2019-06-05 21:48:35 +08:00
|
|
|
#include <drm/drm_print.h>
|
2019-01-18 05:03:34 +08:00
|
|
|
#include <drm/drm_probe_helper.h>
|
2014-12-30 22:08:16 +08:00
|
|
|
|
|
|
|
#include "sti_awg_utils.h"
|
2016-09-19 21:33:53 +08:00
|
|
|
#include "sti_drv.h"
|
2014-12-30 22:08:16 +08: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 18:12:53 +08:00
|
|
|
/*
|
2014-12-30 22:08:16 +08: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;
|
2015-01-27 07:37:03 +08:00
|
|
|
struct drm_bridge *bridge;
|
2014-12-30 22:08:16 +08: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 21:33:53 +08:00
|
|
|
static int dvo_awg_generate_code(struct sti_dvo *dvo, u8 *ram_size, u32 *ram_code)
|
2014-12-30 22:08:16 +08: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 23:27:54 +08: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 21:00:46 +08:00
|
|
|
seq_putc(s, '\n');
|
2016-02-04 23:27:54 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct drm_info_list dvo_debugfs_files[] = {
|
|
|
|
{ "dvo", dvo_dbg_show, 0, NULL },
|
|
|
|
};
|
|
|
|
|
|
|
|
static int dvo_debugfs_init(struct sti_dvo *dvo, struct drm_minor *minor)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dvo_debugfs_files); i++)
|
|
|
|
dvo_debugfs_files[i].data = dvo;
|
|
|
|
|
|
|
|
return drm_debugfs_create_files(dvo_debugfs_files,
|
|
|
|
ARRAY_SIZE(dvo_debugfs_files),
|
|
|
|
minor->debugfs_root, minor);
|
|
|
|
}
|
|
|
|
|
2014-12-30 22:08:16 +08: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-05 04:16:29 +08:00
|
|
|
drm_panel_disable(dvo->panel);
|
2014-12-30 22:08:16 +08: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-05 04:16:29 +08:00
|
|
|
drm_panel_enable(dvo->panel);
|
2014-12-30 22:08:16 +08: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 22:39:01 +08:00
|
|
|
const struct drm_display_mode *mode,
|
|
|
|
const struct drm_display_mode *adjusted_mode)
|
2014-12-30 22:08:16 +08: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");
|
|
|
|
|
|
|
|
memcpy(&dvo->mode, mode, sizeof(struct drm_display_mode));
|
|
|
|
|
|
|
|
/* 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 22:03:34 +08:00
|
|
|
return drm_panel_get_modes(dvo->panel, connector);
|
2014-12-30 22:08:16 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define CLK_TOLERANCE_HZ 50
|
|
|
|
|
|
|
|
static int sti_dvo_connector_mode_valid(struct drm_connector *connector,
|
|
|
|
struct drm_display_mode *mode)
|
|
|
|
{
|
|
|
|
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 18:44:15 +08:00
|
|
|
static const
|
|
|
|
struct drm_connector_helper_funcs sti_dvo_connector_helper_funcs = {
|
2014-12-30 22:08:16 +08: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 16:11:03 +08:00
|
|
|
if (!dvo->panel) {
|
2014-12-30 22:08:16 +08:00
|
|
|
dvo->panel = of_drm_find_panel(dvo->panel_node);
|
2018-05-09 21:00:39 +08:00
|
|
|
if (IS_ERR(dvo->panel))
|
|
|
|
dvo->panel = NULL;
|
|
|
|
else
|
2016-01-28 16:11:03 +08:00
|
|
|
drm_panel_attach(dvo->panel, connector);
|
|
|
|
}
|
2014-12-30 22:08:16 +08:00
|
|
|
|
|
|
|
if (dvo->panel)
|
2016-01-28 16:11:03 +08:00
|
|
|
return connector_status_connected;
|
2014-12-30 22:08:16 +08:00
|
|
|
|
|
|
|
return connector_status_disconnected;
|
|
|
|
}
|
|
|
|
|
2016-06-21 21:09:39 +08:00
|
|
|
static int sti_dvo_late_register(struct drm_connector *connector)
|
2014-12-30 22:08:16 +08:00
|
|
|
{
|
|
|
|
struct sti_dvo_connector *dvo_connector
|
|
|
|
= to_sti_dvo_connector(connector);
|
2016-06-21 21:09:39 +08:00
|
|
|
struct sti_dvo *dvo = dvo_connector->dvo;
|
|
|
|
|
|
|
|
if (dvo_debugfs_init(dvo, dvo->drm_dev->primary)) {
|
|
|
|
DRM_ERROR("DVO debugfs setup failed\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2014-12-30 22:08:16 +08:00
|
|
|
|
2016-06-21 21:09:39 +08:00
|
|
|
return 0;
|
2014-12-30 22:08:16 +08:00
|
|
|
}
|
|
|
|
|
2015-09-02 18:44:15 +08:00
|
|
|
static const struct drm_connector_funcs sti_dvo_connector_funcs = {
|
2014-12-30 22:08:16 +08:00
|
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
|
|
.detect = sti_dvo_connector_detect,
|
2016-06-21 21:09:39 +08:00
|
|
|
.destroy = drm_connector_cleanup,
|
2015-03-19 20:35:16 +08: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 21:09:39 +08:00
|
|
|
.late_register = sti_dvo_late_register,
|
2014-12-30 22:08:16 +08: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;
|
|
|
|
struct drm_bridge *bridge;
|
|
|
|
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;
|
|
|
|
|
|
|
|
bridge = devm_kzalloc(dev, sizeof(*bridge), GFP_KERNEL);
|
|
|
|
if (!bridge)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
bridge->driver_private = dvo;
|
2015-01-27 07:37:03 +08:00
|
|
|
bridge->funcs = &sti_dvo_bridge_funcs;
|
|
|
|
bridge->of_node = dvo->dev.of_node;
|
2017-07-03 16:42:30 +08:00
|
|
|
drm_bridge_add(bridge);
|
2014-12-30 22:08:16 +08:00
|
|
|
|
drm/bridge: Extend bridge API to disable connector creation
Most bridge drivers create a DRM connector to model the connector at the
output of the bridge. This model is historical and has worked pretty
well so far, but causes several issues:
- It prevents supporting more complex display pipelines where DRM
connector operations are split over multiple components. For instance a
pipeline with a bridge connected to the DDC signals to read EDID data,
and another one connected to the HPD signal to detect connection and
disconnection, will not be possible to support through this model.
- It requires every bridge driver to implement similar connector
handling code, resulting in code duplication.
- It assumes that a bridge will either be wired to a connector or to
another bridge, but doesn't support bridges that can be used in both
positions very well (although there is some ad-hoc support for this in
the analogix_dp bridge driver).
In order to solve these issues, ownership of the connector should be
moved to the display controller driver (where it can be implemented
using helpers provided by the core).
Extend the bridge API to allow disabling connector creation in bridge
drivers as a first step towards the new model. The new flags argument to
the bridge .attach() operation allows instructing the bridge driver to
skip creating a connector. Unconditionally set the new flags argument to
0 for now to keep the existing behaviour, and modify all existing bridge
drivers to return an error when connector creation is not requested as
they don't support this feature yet.
The change is based on the following semantic patch, with manual review
and edits.
@ rule1 @
identifier funcs;
identifier fn;
@@
struct drm_bridge_funcs funcs = {
...,
.attach = fn
};
@ depends on rule1 @
identifier rule1.fn;
identifier bridge;
statement S, S1;
@@
int fn(
struct drm_bridge *bridge
+ , enum drm_bridge_attach_flags flags
)
{
... when != S
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
S1
...
}
@ depends on rule1 @
identifier rule1.fn;
identifier bridge, flags;
expression E1, E2, E3;
@@
int fn(
struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags
) {
<...
drm_bridge_attach(E1, E2, E3
+ , flags
)
...>
}
@@
expression E1, E2, E3;
@@
drm_bridge_attach(E1, E2, E3
+ , 0
)
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Acked-by: Sam Ravnborg <sam@ravnborg.org>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Tested-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20200226112514.12455-10-laurent.pinchart@ideasonboard.com
2020-02-26 19:24:29 +08:00
|
|
|
err = drm_bridge_attach(encoder, bridge, NULL, 0);
|
2015-01-27 07:37:03 +08:00
|
|
|
if (err) {
|
|
|
|
DRM_ERROR("Failed to attach bridge\n");
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
dvo->bridge = bridge;
|
2014-12-30 22:08:16 +08: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 16:40:07 +08:00
|
|
|
err = drm_connector_attach_encoder(drm_connector, encoder);
|
2014-12-30 22:08:16 +08:00
|
|
|
if (err) {
|
|
|
|
DRM_ERROR("Failed to attach a connector to a encoder\n");
|
|
|
|
goto err_sysfs;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_sysfs:
|
2015-01-27 07:37:03 +08:00
|
|
|
drm_bridge_remove(bridge);
|
2014-12-30 22:08:16 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sti_dvo_unbind(struct device *dev,
|
|
|
|
struct device *master, void *data)
|
|
|
|
{
|
2015-01-27 07:37:03 +08:00
|
|
|
struct sti_dvo *dvo = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
drm_bridge_remove(dvo->bridge);
|
2014-12-30 22:08:16 +08: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 resource *res;
|
|
|
|
struct device_node *np = dev->of_node;
|
|
|
|
|
|
|
|
DRM_INFO("%s\n", __func__);
|
|
|
|
|
|
|
|
dvo = devm_kzalloc(dev, sizeof(*dvo), GFP_KERNEL);
|
|
|
|
if (!dvo) {
|
|
|
|
DRM_ERROR("Failed to allocate memory for DVO\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
dvo->dev = pdev->dev;
|
|
|
|
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dvo-reg");
|
|
|
|
if (!res) {
|
|
|
|
DRM_ERROR("Invalid dvo resource\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2020-01-06 16:43:50 +08:00
|
|
|
dvo->regs = devm_ioremap(dev, res->start,
|
2014-12-30 22:08:16 +08:00
|
|
|
resource_size(res));
|
2015-06-08 21:25:52 +08:00
|
|
|
if (!dvo->regs)
|
|
|
|
return -ENOMEM;
|
2014-12-30 22:08:16 +08: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 22:08:16 +08:00
|
|
|
|
|
|
|
platform_set_drvdata(pdev, dvo);
|
|
|
|
|
|
|
|
return component_add(&pdev->dev, &sti_dvo_ops);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sti_dvo_remove(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
component_del(&pdev->dev, &sti_dvo_ops);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-06-20 12:42:11 +08:00
|
|
|
static const struct of_device_id dvo_of_match[] = {
|
2014-12-30 22:08:16 +08:00
|
|
|
{ .compatible = "st,stih407-dvo", },
|
|
|
|
{ /* end node */ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, dvo_of_match);
|
|
|
|
|
|
|
|
struct platform_driver sti_dvo_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "sti-dvo",
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.of_match_table = dvo_of_match,
|
|
|
|
},
|
|
|
|
.probe = sti_dvo_probe,
|
|
|
|
.remove = sti_dvo_remove,
|
|
|
|
};
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
|
|
|
|
MODULE_DESCRIPTION("STMicroelectronics SoC DRM driver");
|
|
|
|
MODULE_LICENSE("GPL");
|