TVout hardware block is responsible to dispatch the data flow coming from compositor block to any of the output (HDMI or Analog TV). It control when output are start/stop and configure according the require flow path.
Signed-off-by: Benjamin Gaignard <benjamin.gaignard at linaro.org> Signed-off-by: Vincent Abriou <vincent.abriou at st.com> Signed-off-by: Fabien Dessenne <fabien.dessenne at st.com> --- drivers/gpu/drm/sti/Makefile | 3 +- drivers/gpu/drm/sti/sti_hda.c | 372 +++++++++++++++++++++ drivers/gpu/drm/sti/sti_hda.h | 14 + drivers/gpu/drm/sti/sti_hdmi.c | 542 +++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi.h | 3 + drivers/gpu/drm/sti/sti_tvout.c | 703 ++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_tvout.h | 105 ++++++ 7 files changed, 1741 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/sti/sti_hda.h create mode 100644 drivers/gpu/drm/sti/sti_tvout.c create mode 100644 drivers/gpu/drm/sti/sti_tvout.h diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile index aa69ea0..45536c3 100644 --- a/drivers/gpu/drm/sti/Makefile +++ b/drivers/gpu/drm/sti/Makefile @@ -1,6 +1,7 @@ ccflags-y := -Iinclude/drm -stidrm-y := sti_hdmi.o \ +stidrm-y := sti_tvout.o \ + sti_hdmi.o \ sti_hdmi_tx3g0c55phy.o \ sti_hdmi_tx3g4c28phy.o \ sti_hda.o \ diff --git a/drivers/gpu/drm/sti/sti_hda.c b/drivers/gpu/drm/sti/sti_hda.c index e8e0719..d500684 100644 --- a/drivers/gpu/drm/sti/sti_hda.c +++ b/drivers/gpu/drm/sti/sti_hda.c @@ -11,6 +11,8 @@ #include <drm/drmP.h> +#include "sti_hda.h" + /* HDformatter registers */ #define HDA_ANA_CFG 0x0000 #define HDA_ANA_SCALE_CTRL_Y 0x0004 @@ -372,6 +374,376 @@ static int sti_hda_get_modes(struct drm_connector *drm_connector) return count; } +/* + * Start hd analog + * + * @connector: pointer on the tvout HD analog connector + * + * Return 0 on success + */ +static int sti_hda_start(struct sti_tvout_connector *connector) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + u32 val, i, mode_idx; + u32 src_filter_y, src_filter_c; + u32 *coef_y, *coef_c; + u32 filter_mode; + + DRM_DEBUG_DRIVER("\n"); + + /* Prepare/enable clocks */ + if (clk_prepare_enable(hda->clk_pix)) + DRM_ERROR("Failed to prepare/enable hda_pix clk\n"); + if (clk_prepare_enable(hda->clk_hddac)) + DRM_ERROR("Failed to prepare/enable hda_hddac clk\n"); + + hda->enabled = true; + + if (!hda_get_mode_idx(hda->mode, &mode_idx)) { + DRM_ERROR("Undefined mode\n"); + return 1; + } + + switch (hda_supported_modes[mode_idx].vid_cat) { + case VID_HD_148M: + DRM_ERROR("Beyond HD analog capabilities\n"); + return 1; + case VID_HD_74M: + /* HD use alternate 2x filter */ + filter_mode = CFG_AWG_FLTR_MODE_HD; + src_filter_y = HDA_ANA_SRC_Y_CFG_ALT_2X; + src_filter_c = HDA_ANA_SRC_C_CFG_ALT_2X; + coef_y = coef_y_alt_2x; + coef_c = coef_c_alt_2x; + break; + case VID_ED: + /* ED uses 4x filter */ + filter_mode = CFG_AWG_FLTR_MODE_ED; + src_filter_y = HDA_ANA_SRC_Y_CFG_4X; + src_filter_c = HDA_ANA_SRC_C_CFG_4X; + coef_y = coef_yc_4x; + coef_c = coef_yc_4x; + break; + case VID_SD: + DRM_ERROR("Not supported\n"); + return 1; + default: + DRM_ERROR("Undefined resolution\n"); + return 1; + } + DRM_DEBUG_DRIVER("Using HDA mode #%d\n", mode_idx); + + /* Enable HD Video DACs */ + hda_enable_hd_dacs(hda, true); + + /* Configure scaler */ + writel(SCALE_CTRL_Y_DFLT, hda->regs + HDA_ANA_SCALE_CTRL_Y); + writel(SCALE_CTRL_CB_DFLT, hda->regs + HDA_ANA_SCALE_CTRL_CB); + writel(SCALE_CTRL_CR_DFLT, hda->regs + HDA_ANA_SCALE_CTRL_CR); + + /* Configure sampler */ + writel(src_filter_y, hda->regs + HDA_ANA_SRC_Y_CFG); + writel(src_filter_c, hda->regs + HDA_ANA_SRC_C_CFG); + for (i = 0; i < SAMPLER_COEF_NB; i++) { + writel(coef_y[i], hda->regs + HDA_COEFF_Y_PH1_TAP123 + i * 4); + writel(coef_c[i], hda->regs + HDA_COEFF_C_PH1_TAP123 + i * 4); + } + + /* Configure main HDFormatter */ + val = 0; + val |= (hda->mode.flags & DRM_MODE_FLAG_INTERLACE) ? + 0 : CFG_AWG_ASYNC_VSYNC_MTD; + val |= (CFG_PBPR_SYNC_OFF_VAL << CFG_PBPR_SYNC_OFF_SHIFT); + val |= filter_mode; + writel(val, hda->regs + HDA_ANA_CFG); + + /* Configure AWG */ + sti_hda_configure_awg(hda, hda_supported_modes[mode_idx].awg_instr, + hda_supported_modes[mode_idx].nb_instr); + + /* Enable AWG */ + hda_reg_writemask(hda->regs + HDA_ANA_CFG, 1, CFG_AWG_ASYNC_EN); + + return 0; +} + +/* + * Stop HD analog + * + * @connector: pointer on the tvout HD analog connector + */ +static void sti_hda_stop(struct sti_tvout_connector *connector) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + + if (!hda->enabled) + return; + + DRM_DEBUG_DRIVER("\n"); + + /* Disable HD DAC and AWG */ + hda_reg_writemask(hda->regs + HDA_ANA_CFG, 0, CFG_AWG_ASYNC_EN); + hda_enable_hd_dacs(hda, false); + + /* Disable/unprepare hda clock */ + clk_disable_unprepare(hda->clk_hddac); + clk_disable_unprepare(hda->clk_pix); + + hda->enabled = false; +} + +/* + * Check if the drm display mode in supported by the HD analog + * + * @connector: pointer on the tvout HD analog connector + * @mode: drm display mode + * + * Return 0 if supported + */ +#define CLK_TOLERANCE_HZ 50 +static int sti_hda_check_mode(struct sti_tvout_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + int target = mode->clock * 1000; + int target_min = target - CLK_TOLERANCE_HZ; + int target_max = target + CLK_TOLERANCE_HZ; + int result; + int idx; + + if (!hda_get_mode_idx(*mode, &idx)) { + return 1; + } else { + result = clk_round_rate(hda->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("hda pixclk=%d not supported\n", + target); + return 1; + } + } + + return 0; +} + +/* + * Set the drm display mode in the local structure + * + * @connector: pointer on the tvout HD analog connector + * @mode: drm display mode + * + * Return 0 on success + */ +static int sti_hda_set_mode(struct sti_tvout_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + u32 mode_idx; + int hddac_rate; + int ret; + + DRM_DEBUG_DRIVER("\n"); + + memcpy(&hda->mode, mode, sizeof(struct drm_display_mode)); + + if (!hda_get_mode_idx(hda->mode, &mode_idx)) { + DRM_ERROR("Undefined mode\n"); + return 1; + } + + switch (hda_supported_modes[mode_idx].vid_cat) { + case VID_HD_74M: + /* HD use alternate 2x filter */ + hddac_rate = mode->clock * 1000 * 2; + break; + case VID_ED: + /* ED uses 4x filter */ + hddac_rate = mode->clock * 1000 * 4; + break; + default: + DRM_ERROR("Undefined mode\n"); + return 1; + } + + /* HD DAC = 148.5Mhz or 108 Mhz */ + ret = clk_set_rate(hda->clk_hddac, hddac_rate); + if (ret < 0) { + DRM_ERROR("Cannot set rate (%dHz) for hda_hddac clk\n", + hddac_rate); + return ret; + } + + /* HDformatter clock = compositor clock */ + ret = clk_set_rate(hda->clk_pix, mode->clock * 1000); + if (ret < 0) { + DRM_ERROR("Cannot set rate (%dHz) for hda_pix clk\n", + mode->clock * 1000); + return ret; + } + + return 0; +} + +/* + * Detect if HD analog is connected + * + * @connector: pointer on the tvout HD analog connector + * + * Return true if HD analog cable is connected which is assumed to be always + * the case + */ +static bool sti_hda_detect(struct sti_tvout_connector *connector) +{ + DRM_DEBUG_DRIVER("\n"); + + return true; +} + +/* + * Check if HD analog is enabled + * + * @connector: pointer on the tvout HD analog connector + * + * Return true if HD analog is enabled + */ +static bool sti_hda_is_enabled(struct sti_tvout_connector *connector) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + + return hda->enabled; +} + +/* + * Prepare/configure HD analog + * + * @connector: pointer on the tvout HD analog connector + */ +static void sti_hda_prepare(struct sti_tvout_connector *connector) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + + DRM_DEBUG_DRIVER("\n"); + + /* reset HDF */ + writel(0x00000000, hda->regs + HDA_ANA_CFG); + writel(0x00000000, hda->regs + HDA_ANA_ANC_CTRL); +} + +/* + * Debugfs + */ + +#define HDA_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \ + readl(hda->regs + reg)) + +static void hda_dbg_cfg(struct seq_file *m, int val) +{ + seq_puts(m, "\t AWG "); + seq_puts(m, val & CFG_AWG_ASYNC_EN ? "enabled" : "disabled"); +} + +static void hda_dbg_awgi(struct seq_file *m, void __iomem *reg) +{ + int i; + + seq_puts(m, "\n HDA_SYNC_AWGI "); + for (i = 0; i < 10; i++) + seq_printf(m, "%04X ", readl(reg + i * 4)); + seq_puts(m, "..."); +} + +static void hda_dbg_video_dacs_ctrl(struct seq_file *m, void __iomem *reg) +{ + u32 val = readl(reg); + u32 mask; + + switch ((u32)reg & VIDEO_DACS_CONTROL_MASK) { + case VIDEO_DACS_CONTROL_SYSCFG2535: + mask = DAC_CFG_HD_OFF_MASK; + break; + case VIDEO_DACS_CONTROL_SYSCFG5072: + mask = DAC_CFG_HD_HZUVW_OFF_MASK; + break; + default: + DRM_INFO("Video DACS control register not supported!"); + return; + } + + seq_puts(m, "\n"); + + seq_printf(m, "\n %-25s 0x%08X", "VIDEO_DACS_CONTROL", val); + seq_puts(m, "\tHD DACs "); + seq_puts(m, val & mask ? "disabled" : "enabled"); +} + +static void sti_hda_dbg_show(struct sti_tvout_connector *connector, + struct seq_file *m) +{ + struct sti_hda *hda = (struct sti_hda *)connector->priv; + + seq_puts(m, "\n"); + seq_printf(m, "\nHD Analog: (virt base addr = 0x%p)", hda->regs); + HDA_DBG_DUMP(HDA_ANA_CFG); + hda_dbg_cfg(m, readl(hda->regs + HDA_ANA_CFG)); + HDA_DBG_DUMP(HDA_ANA_SCALE_CTRL_Y); + HDA_DBG_DUMP(HDA_ANA_SCALE_CTRL_CB); + HDA_DBG_DUMP(HDA_ANA_SCALE_CTRL_CR); + HDA_DBG_DUMP(HDA_ANA_ANC_CTRL); + HDA_DBG_DUMP(HDA_ANA_SRC_Y_CFG); + HDA_DBG_DUMP(HDA_ANA_SRC_C_CFG); + hda_dbg_awgi(m, hda->regs + HDA_SYNC_AWGI); + if (hda->video_dacs_ctrl) + hda_dbg_video_dacs_ctrl(m, hda->video_dacs_ctrl); +} + +/* + * create the HD analog output + * + * @tvout: pointer on the tvout information + * + * Return pointer on the created tvout connector or NULL if error occurs + */ +struct sti_tvout_connector *sti_hda_create(struct sti_tvout *tvout) +{ + struct sti_hda *hda = container_of(hda_dev, struct sti_hda, dev); + struct device *dev = &hda->dev; + struct sti_tvout_connector *connector; + + DRM_DEBUG_DRIVER("\n"); + + if (!hda) { + DRM_INFO("%s: No hda device probed\n", __func__); + return NULL; + } + + connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL); + if (!connector) { + DRM_ERROR("Failed to allocate memory for connector\n"); + goto connector_alloc_failed; + } + + /* Set the drm device handle */ + hda->drm_dev = tvout->drm_dev; + + connector->priv = (void *)hda; + connector->start = sti_hda_start; + connector->stop = sti_hda_stop; + connector->get_modes = sti_hda_get_modes; + connector->check_mode = sti_hda_check_mode; + connector->set_mode = sti_hda_set_mode; + connector->detect = sti_hda_detect; + connector->is_enabled = sti_hda_is_enabled; + connector->prepare = sti_hda_prepare; + connector->dbg_show = sti_hda_dbg_show; + + return connector; + +connector_alloc_failed: + return NULL; +} static int sti_hda_bind(struct device *dev, struct device *master, void *data) { diff --git a/drivers/gpu/drm/sti/sti_hda.h b/drivers/gpu/drm/sti/sti_hda.h new file mode 100644 index 0000000..e88b30e --- /dev/null +++ b/drivers/gpu/drm/sti/sti_hda.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Fabien Dessenne <fabien.dessenne at st.com> for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_HDA_H_ +#define _STI_HDA_H_ + +#include "sti_tvout.h" + +struct sti_tvout_connector *sti_hda_create(struct sti_tvout *tvout); + +#endif diff --git a/drivers/gpu/drm/sti/sti_hdmi.c b/drivers/gpu/drm/sti/sti_hdmi.c index aa77510..7cad152 100644 --- a/drivers/gpu/drm/sti/sti_hdmi.c +++ b/drivers/gpu/drm/sti/sti_hdmi.c @@ -380,6 +380,548 @@ fail: return -1; } +/* + * Start hdmi + * + * @connector: pointer on the tvout hdmi connector + * + * Return -1 if error occurs + */ +static int sti_hdmi_start(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + int ret = 0; + + DRM_DEBUG_DRIVER("\n"); + + /* Prepare/enable clocks */ + if (clk_prepare_enable(hdmi->clk_pix)) + DRM_ERROR("Failed to prepare/enable hdmi_pix clk\n"); + if (clk_prepare_enable(hdmi->clk_tmds)) + DRM_ERROR("Failed to prepare/enable hdmi_tmds clk\n"); + if (clk_prepare_enable(hdmi->clk_phy)) + DRM_ERROR("Failed to prepare/enable hdmi_rejec_pll clk\n"); + + hdmi->enabled = true; + + /* Program hdmi serializer and start phy */ + ret = hdmi_phy_start(hdmi); + if (ret) { + DRM_ERROR("Unable to start hdmi phy\n"); + return ret; + } + + /* Program hdmi active area */ + hdmi_active_area(hdmi); + + /* Enable working interrupts */ + writel(HDMI_WORKING_INT, hdmi->regs + HDMI_INT_EN); + + /* Program hdmi config */ + hdmi_config(hdmi); + + /* Program AVI infoframe */ + ret = hdmi_avi_infoframe_config(hdmi); + if (ret) + DRM_ERROR("Unable to configure AVI infoframe\n"); + + /* Sw reset */ + ret = hdmi_swreset(hdmi); + if (ret) + DRM_ERROR("Unable to perform the hdmi sw reset\n"); + + return ret; +} + +/* + * Stop hdmi + * + * @connector: pointer on the tvout hdmi connector + */ +static void sti_hdmi_stop(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + u32 val; + u32 mask; + + if (!hdmi->enabled) + return; + + DRM_DEBUG_DRIVER("\n"); + + /* Disable HDMI */ + mask = HDMI_CFG_DEVICE_EN; + val = ~HDMI_CFG_DEVICE_EN; + + hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask); + + /* Stop the phy */ + hdmi_phy_stop(hdmi); + + /* Disable/unprepare hdmi clock */ + clk_disable_unprepare(hdmi->clk_phy); + clk_disable_unprepare(hdmi->clk_tmds); + clk_disable_unprepare(hdmi->clk_pix); + + hdmi->enabled = false; +} + +/* + * Check if the drm display mode in supported by the hdmi + * + * @connector: pointer on the tvout hdmi connector + * @mode: drm display mode + * + * Return -1 if not supported + */ +#define CLK_TOLERANCE_HZ 50 +static int sti_hdmi_check_mode(struct sti_tvout_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + int target = mode->clock * 1000; + int target_min = target - CLK_TOLERANCE_HZ; + int target_max = target + CLK_TOLERANCE_HZ; + int result; + + result = clk_round_rate(hdmi->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("hdmi pixclk=%d not supported\n", target); + return -1; + } + + return 0; +} + +/* + * Set the drm display mode in the local structure + * + * @connector: pointer on the tvout hdmi connector + * @mode: drm display mode + * + * Return -1 if error occurs + */ +/* FS bits */ +static int sti_hdmi_set_mode(struct sti_tvout_connector *connector, + struct drm_display_mode *mode) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + int ret; + + DRM_DEBUG_DRIVER("\n"); + + /* Copy the drm display mode in the connector local structure */ + memcpy(&hdmi->mode, mode, sizeof(struct drm_display_mode)); + + /* Update clock framerate according to the selected mode */ + ret = clk_set_rate(hdmi->clk_pix, mode->clock * 1000); + if (ret < 0) { + DRM_ERROR("Cannot set rate (%dHz) for hdmi_pix clk\n", + mode->clock * 1000); + return ret; + } + ret = clk_set_rate(hdmi->clk_phy, mode->clock * 1000); + if (ret < 0) { + DRM_ERROR("Cannot set rate (%dHz) for hdmi_rejection_pll clk\n", + mode->clock * 1000); + return ret; + } + + return 0; +} + +/* + * Detect if hdmi is connected + * + * @connector: pointer on the tvout hdmi connector + * + * Return true if hdmi cable is connected + */ +static bool sti_hdmi_detect(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + + DRM_DEBUG_DRIVER("\n"); + + if (hdmi->hpd) { + DRM_DEBUG_DRIVER("hdmi cable connected\n"); + return true; + } else + DRM_DEBUG_DRIVER("hdmi cable disconnected\n"); + + return false; +} + +/* + * Check if hdmi is enabled + * + * @connector: pointer on the tvout hdmi connector + * + * Return true if hdmi is enabled + */ +static bool sti_hdmi_is_enabled(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + + return hdmi->enabled; +} + +/* + * Prepare/configure hdmi + * + * @connector: pointer on the tvout hdmi connector + */ +static void sti_hdmi_prepare(struct sti_tvout_connector *connector) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + + DRM_DEBUG_DRIVER("\n"); + + /* HDMI initialisation */ + writel(0x00000000, hdmi->regs + HDMI_CFG); + writel(0xffffffff, hdmi->regs + HDMI_INT_CLR); + + /* Ensure the PHY is completely powered down */ + hdmi_phy_stop(hdmi); + + /* Set the default channel data to be a dark red */ + writel(0x0000, hdmi->regs + HDMI_DFLT_CHL0_DAT); + writel(0x0000, hdmi->regs + HDMI_DFLT_CHL1_DAT); + writel(0x0060, hdmi->regs + HDMI_DFLT_CHL2_DAT); +} + +/* + * Debugfs + */ +#define HDMI_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \ + readl(hdmi->regs + reg)) +#define HDMI_DBG_DUMP_DI(reg, slot) HDMI_DBG_DUMP(reg(slot)) +#define MAX_STRING_LENGTH 40 + +static void hdmi_dbg_cfg(struct seq_file *m, int val) +{ + int tmp; + char str[MAX_STRING_LENGTH]; + static const char *const mode[] = { "DVI", "HDMI" }; + static const char *const enable[] = { "disable", "enable" }; + static const char *const oess_ess[] = { "OESS enable", "ESS enable" }; + static const char *const polarity[] = { "normal", "inverted" }; + + seq_puts(m, "\t"); + + tmp = (val & HDMI_CFG_HDMI_NOT_DVI) >> HDMI_CFG_HDMI_NOT_DVI_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "mode: %s", mode[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_CFG_HDCP_EN) >> HDMI_CFG_HDCP_EN_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "HDCP: %s", enable[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_CFG_ESS_NOT_OESS) >> HDMI_CFG_ESS_NOT_OESS_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "HDCP mode: %s", oess_ess[tmp]); + seq_printf(m, "%-40s", str); + + seq_printf(m, "\n%-40s", ""); + + tmp = (val & HDMI_CFG_SINK_TERM_DET_EN) >> + HDMI_CFG_SINK_TERM_DET_EN_SHIFT; + snprintf(str, MAX_STRING_LENGTH, + "Sink term detection: %s", enable[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_CFG_H_SYNC_POL_NEG) >> HDMI_CFG_H_SYNC_POL_NEG_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "Hsync polarity: %s", polarity[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_CFG_V_SYNC_POL_NEG) >> HDMI_CFG_V_SYNC_POL_NEG_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "Vsync polarity: %s", polarity[tmp]); + seq_printf(m, "%-40s", str); + + seq_printf(m, "\n%-40s", ""); + + tmp = (val & HDMI_CFG_422_EN) >> HDMI_CFG_422_EN_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "YUV422 format: %s", enable[tmp]); + seq_printf(m, "%-40s", str); +} + +static void hdmi_dbg_sta(struct seq_file *m, int val) +{ + int tmp; + char str[MAX_STRING_LENGTH]; + static const char *const sink_term[] = { "not present", "present" }; + static const char *const pll_lck[] = { "not locked", "locked" }; + static const char *const hot_plug[] = { "not connected", "connected" }; + + seq_puts(m, "\t"); + + tmp = (val & HDMI_STA_FIFO_SAMPLES) >> HDMI_STA_FIFO_SAMPLES_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "fifo: %d samples", tmp); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_STA_SINK_TERM) >> HDMI_STA_SINK_TERM_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "sink term: %s", sink_term[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_STA_DLL_LCK) >> HDMI_STA_DLL_LCK_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "pll: %s", pll_lck[tmp]); + seq_printf(m, "%-40s", str); + + seq_printf(m, "\n%-40s", ""); + + tmp = (val & HDMI_STA_HOT_PLUG) >> HDMI_STA_HOT_PLUG_SHIFT; + snprintf(str, MAX_STRING_LENGTH, "hdmi cable: %s", hot_plug[tmp]); + seq_printf(m, "%-40s", str); +} + +static void hdmi_dbg_sw_di_cfg(struct seq_file *m, int val) +{ + int tmp; + char str[MAX_STRING_LENGTH]; + static const char *const en_di[] = { "no transmission", + "single transmission", "once every field", "once every frame" + }; + + seq_puts(m, "\t"); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 1)); + snprintf(str, MAX_STRING_LENGTH, "Data island 1: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 2)) >> 4; + snprintf(str, MAX_STRING_LENGTH, "Data island 2: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 3)) >> 8; + snprintf(str, MAX_STRING_LENGTH, "Data island 3: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + seq_printf(m, "\n%-40s", ""); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 4)) >> 12; + snprintf(str, MAX_STRING_LENGTH, "Data island 4: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 5)) >> 16; + snprintf(str, MAX_STRING_LENGTH, "Data island 5: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); + + tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 6)) >> 20; + snprintf(str, MAX_STRING_LENGTH, "Data island 6: %s", en_di[tmp]); + seq_printf(m, "%-40s", str); +} + +static void hdmi_dbg_xmin(struct seq_file *m, int val) +{ + seq_printf(m, "\tXmin: %4d", val & 0x0FFF); +} + +static void hdmi_dbg_xmax(struct seq_file *m, int val) +{ + seq_printf(m, "\tXmax: %4d", val & 0x0FFF); +} + +static void hdmi_dbg_ymin(struct seq_file *m, int val) +{ + seq_printf(m, "\tYmin: %4d", val & 0x0FFF); +} + +static void hdmi_dbg_ymax(struct seq_file *m, int val) +{ + seq_printf(m, "\tYmax: %4d", val & 0x0FFF); +} + +static void sti_hdmi_dbg_show(struct sti_tvout_connector *connector, + struct seq_file *m) +{ + struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv; + struct drm_display_mode *mode = &hdmi->mode; + struct hdmi_avi_infoframe info; + char str[MAX_STRING_LENGTH]; + + seq_puts(m, "\n"); + seq_printf(m, "\nHDMI: (virt base addr = 0x%p)", hdmi->regs); + HDMI_DBG_DUMP(HDMI_CFG); + hdmi_dbg_cfg(m, readl(hdmi->regs + HDMI_CFG)); + HDMI_DBG_DUMP(HDMI_INT_EN); + HDMI_DBG_DUMP(HDMI_INT_STA); + HDMI_DBG_DUMP(HDMI_INT_CLR); + HDMI_DBG_DUMP(HDMI_STA); + hdmi_dbg_sta(m, readl(hdmi->regs + HDMI_STA)); + HDMI_DBG_DUMP(HDMI_ACTIVE_VID_XMIN); + hdmi_dbg_xmin(m, readl(hdmi->regs + HDMI_ACTIVE_VID_XMIN)); + HDMI_DBG_DUMP(HDMI_ACTIVE_VID_XMAX); + hdmi_dbg_xmax(m, readl(hdmi->regs + HDMI_ACTIVE_VID_XMAX)); + HDMI_DBG_DUMP(HDMI_ACTIVE_VID_YMIN); + hdmi_dbg_ymin(m, readl(hdmi->regs + HDMI_ACTIVE_VID_YMIN)); + HDMI_DBG_DUMP(HDMI_ACTIVE_VID_YMAX); + hdmi_dbg_ymax(m, readl(hdmi->regs + HDMI_ACTIVE_VID_YMAX)); + HDMI_DBG_DUMP(HDMI_DFLT_CHL0_DAT); + HDMI_DBG_DUMP(HDMI_DFLT_CHL1_DAT); + HDMI_DBG_DUMP(HDMI_DFLT_CHL2_DAT); + HDMI_DBG_DUMP(HDMI_SW_DI_CFG); + hdmi_dbg_sw_di_cfg(m, readl(hdmi->regs + HDMI_SW_DI_CFG)); + if (hdmi->tx3g0c55phy) + sti_hdmi_tx3g0c55phy_show(hdmi, m); + else + sti_hdmi_tx3g4c28phy_show(hdmi, m); + seq_puts(m, "\n"); + + seq_printf(m, "\nAVI Infoframe (Data Island slot N=%d):", + HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_HEAD_WORD, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD0, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD1, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD2, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD3, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD4, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD5, HDMI_IFRAME_SLOT_AVI); + HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD6, HDMI_IFRAME_SLOT_AVI); + seq_puts(m, "\n"); + if (drm_hdmi_avi_infoframe_from_display_mode(&info, mode) == 0) { + static const char *const colorspace[] = { + "RGB", "YUV422", "YUV444" }; + static const char *const scan_mode[] = { + "none", "overscan", "underscan" }; + static const char *const colorimetry[] = { + "none", "ITU 601", "ITU 709", "extended" }; + static const char *const ext_colorimetry[] = { + "xvYCC 601", "xvYCC 709", "S YCC 601", "Adobe YCC 601", + "Adobe RGB" }; + static const char *const pict_aspect[] = { + "none", "4:3", "16:9" }; + static const char *const active_aspect[] = { + "", "", "16:9 top", "14:9 top", "16:9 center", "", "", + "", "picture", "4:3", "16:9", "14:9", "", "4:3 SP 14:9", + "16:9 SP 14:9", "16:9 SP 4:3" }; + static const char *const quant_range[] = { + "default", "4:3", "16:9" }; + static const char *const nups[] = { + "unknown", "horizontal", "vertical", "both" }; + static const char *const ycc_quant_range[] = { + "limited", "full" }; + static const char *const content_type[] = { + "none", "photo", "cinema", "game" }; + + snprintf(str, MAX_STRING_LENGTH, "\tversion:"); + seq_printf(m, "%-25s %d\n", str, info.version); + snprintf(str, MAX_STRING_LENGTH, "\tlength:"); + seq_printf(m, "%-25s %d\n", str, info.length); + snprintf(str, MAX_STRING_LENGTH, "\tcolorspace:"); + seq_printf(m, "%-25s %s\n", str, colorspace[info.colorspace]); + snprintf(str, MAX_STRING_LENGTH, "\tscan mode:"); + seq_printf(m, "%-25s %s\n", str, scan_mode[info.scan_mode]); + snprintf(str, MAX_STRING_LENGTH, "\tcolorimetry:"); + seq_printf(m, "%-25s %s\n", str, colorimetry[info.colorimetry]); + if (info.colorimetry == HDMI_COLORIMETRY_EXTENDED) { + snprintf(str, MAX_STRING_LENGTH, + " extended colorimetry:"); + seq_printf(m, "%-25s %s\n", str, + ext_colorimetry[info.extended_colorimetry]); + } + snprintf(str, MAX_STRING_LENGTH, "\tpicture aspect:"); + seq_printf(m, "%-25s %s\n", str, + pict_aspect[info.picture_aspect]); + snprintf(str, MAX_STRING_LENGTH, "\tactive aspect:"); + seq_printf(m, "%-25s %s\n", str, + active_aspect[info.active_aspect]); + snprintf(str, MAX_STRING_LENGTH, "\tquantization range:"); + seq_printf(m, "%-25s %s\n", str, + quant_range[info.quantization_range]); + snprintf(str, MAX_STRING_LENGTH, "\tycc quantization range:"); + seq_printf(m, "%-25s %s\n", str, + ycc_quant_range[info.ycc_quantization_range]); + snprintf(str, MAX_STRING_LENGTH, "\tnups:"); + seq_printf(m, "%-25s %s\n", str, nups[info.nups]); + snprintf(str, MAX_STRING_LENGTH, "\tpixel repeat:"); + seq_printf(m, "%-25s %d\n", str, info.pixel_repeat); + snprintf(str, MAX_STRING_LENGTH, "\tactive info valid:"); + seq_printf(m, "%-25s %s\n", str, + info.pixel_repeat ? "true" : "false"); + snprintf(str, MAX_STRING_LENGTH, "\titc:"); + seq_printf(m, "%-25s %s\n", str, info.itc ? "true" : "false"); + snprintf(str, MAX_STRING_LENGTH, "\ttop bar:"); + seq_printf(m, "%-25s %d\n", str, info.top_bar); + snprintf(str, MAX_STRING_LENGTH, "\tbottom bar:"); + seq_printf(m, "%-25s %d\n", str, info.bottom_bar); + snprintf(str, MAX_STRING_LENGTH, "\tleft bar:"); + seq_printf(m, "%-25s %d\n", str, info.left_bar); + snprintf(str, MAX_STRING_LENGTH, "\tright bar:"); + seq_printf(m, "%-25s %d\n", str, info.right_bar); + snprintf(str, MAX_STRING_LENGTH, "\tcontent type:"); + seq_printf(m, "%-25s %s\n", str, + content_type[info.content_type]); + snprintf(str, MAX_STRING_LENGTH, "\tCEA video code:"); + seq_printf(m, "%-25s %d\n", str, info.video_code); + } + + seq_printf(m, "\nHDMI mode: %dx%d%s @%d", + hdmi->mode.hdisplay, + hdmi->mode.vdisplay, + (hdmi->mode.flags & DRM_MODE_FLAG_INTERLACE) ? + "i" : "p", hdmi->mode.vrefresh); +} + +/* + * create the hdmi output + * + * @tvout: pointer on the tvout information + * + * Return pointer on the created tvout connector or NULL if error occurs + */ +struct sti_tvout_connector *sti_hdmi_create(struct sti_tvout *tvout) +{ + struct sti_hdmi *hdmi = container_of(hdmi_dev, struct sti_hdmi, dev); + struct device *dev = &hdmi->dev; + struct sti_tvout_connector *connector; + + DRM_DEBUG_DRIVER("\n"); + + if (!hdmi) { + DRM_INFO("%s: No hdmi device probed\n", __func__); + return NULL; + } + + connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL); + if (!connector) { + DRM_ERROR("Failed to allocate memory for connector\n"); + goto connector_alloc_failed; + } + + /* Set the drm device handle */ + hdmi->drm_dev = tvout->drm_dev; + + /* DDC i2c driver */ + if (i2c_add_driver(&ddc_driver)) { + DRM_ERROR("Failed to register ddc i2c driver\n"); + goto i2c_failed; + } + + /* Enable default interrupts */ + writel(HDMI_DEFAULT_INT, hdmi->regs + HDMI_INT_EN); + + connector->priv = (void *)hdmi; + connector->start = sti_hdmi_start; + connector->stop = sti_hdmi_stop; + connector->get_modes = sti_hdmi_get_modes; + connector->check_mode = sti_hdmi_check_mode; + connector->set_mode = sti_hdmi_set_mode; + connector->detect = sti_hdmi_detect; + connector->is_enabled = sti_hdmi_is_enabled; + connector->prepare = sti_hdmi_prepare; + connector->dbg_show = sti_hdmi_dbg_show; + + return connector; + +i2c_failed: + devm_kfree(dev, connector); +connector_alloc_failed: + return NULL; +} + static int sti_hdmi_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); diff --git a/drivers/gpu/drm/sti/sti_hdmi.h b/drivers/gpu/drm/sti/sti_hdmi.h index c14c683..ed90a2c 100644 --- a/drivers/gpu/drm/sti/sti_hdmi.h +++ b/drivers/gpu/drm/sti/sti_hdmi.h @@ -11,6 +11,8 @@ #include <drm/drmP.h> +#include "sti_tvout.h" + /* HDMI v2.9 macro cell */ #define HDMI_CFG 0x0000 #define HDMI_INT_EN 0x0004 @@ -181,6 +183,7 @@ struct hdmi_phy_config { u32 config[4]; }; +struct sti_tvout_connector *sti_hdmi_create(struct sti_tvout *tvout); void sti_hdmi_attach_ddc_client(struct i2c_client *ddc); int sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi); diff --git a/drivers/gpu/drm/sti/sti_tvout.c b/drivers/gpu/drm/sti/sti_tvout.c new file mode 100644 index 0000000..7c61ba1 --- /dev/null +++ b/drivers/gpu/drm/sti/sti_tvout.c @@ -0,0 +1,703 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Benjamin Gaignard <benjamin.gaignard at st.com> for STMicroelectronics. + * Author: Vincent Abriou <vincent.abriou at st.com> for STMicroelectronics + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "sti_tvout.h" +#include "sti_hdmi.h" +#include "sti_hda.h" + +/* glue regsiters */ +#define TVO_CSC_MAIN_M0 0x000 +#define TVO_CSC_MAIN_M1 0x004 +#define TVO_CSC_MAIN_M2 0x008 +#define TVO_CSC_MAIN_M3 0x00c +#define TVO_CSC_MAIN_M4 0x010 +#define TVO_CSC_MAIN_M5 0x014 +#define TVO_CSC_MAIN_M6 0x018 +#define TVO_CSC_MAIN_M7 0x01c +#define TVO_MAIN_IN_VID_FORMAT 0x030 +#define TVO_CSC_AUX_M0 0x100 +#define TVO_CSC_AUX_M1 0x104 +#define TVO_CSC_AUX_M2 0x108 +#define TVO_CSC_AUX_M3 0x10c +#define TVO_CSC_AUX_M4 0x110 +#define TVO_CSC_AUX_M5 0x114 +#define TVO_CSC_AUX_M6 0x118 +#define TVO_CSC_AUX_M7 0x11c +#define TVO_AUX_IN_VID_FORMAT 0x130 +#define TVO_VIP_HDF 0x400 +#define TVO_HD_SYNC_SEL 0x418 +#define TVO_HD_DAC_CFG_OFF 0x420 +#define TVO_VIP_HDMI 0x500 +#define TVO_HDMI_FORCE_COLOR_0 0x504 +#define TVO_HDMI_FORCE_COLOR_1 0x508 +#define TVO_HDMI_CLIP_VALUE_B_CB 0x50c +#define TVO_HDMI_CLIP_VALUE_Y_G 0x510 +#define TVO_HDMI_CLIP_VALUE_R_CR 0x514 +#define TVO_HDMI_SYNC_SEL 0x518 +#define TVO_HDMI_DFV_OBS 0x540 + +#define TVO_IN_FMT_SIGNED (1 << 0) +#define TVO_SYNC_EXT (1 << 4) + +#define TVO_VIP_REORDER_R_SHIFT 24 +#define TVO_VIP_REORDER_G_SHIFT 20 +#define TVO_VIP_REORDER_B_SHIFT 16 +#define TVO_VIP_REORDER_MASK 0x3 +#define TVO_VIP_REORDER_Y_G_SEL 0 +#define TVO_VIP_REORDER_CB_B_SEL 1 +#define TVO_VIP_REORDER_CR_R_SEL 2 + +#define TVO_VIP_CLIP_SHIFT 8 +#define TVO_VIP_CLIP_MASK 0x7 +#define TVO_VIP_CLIP_DISABLED 0 +#define TVO_VIP_CLIP_EAV_SAV 1 +#define TVO_VIP_CLIP_LIMITED_RANGE_RGB_Y 2 +#define TVO_VIP_CLIP_LIMITED_RANGE_CB_CR 3 +#define TVO_VIP_CLIP_PROG_RANGE 4 + +#define TVO_VIP_RND_SHIFT 4 +#define TVO_VIP_RND_MASK 0x3 +#define TVO_VIP_RND_8BIT_ROUNDED 0 +#define TVO_VIP_RND_10BIT_ROUNDED 1 +#define TVO_VIP_RND_12BIT_ROUNDED 2 + +#define TVO_VIP_SEL_INPUT_MASK 0xf +#define TVO_VIP_SEL_INPUT_MAIN 0x0 +#define TVO_VIP_SEL_INPUT_AUX 0x8 +#define TVO_VIP_SEL_INPUT_FORCE_COLOR 0xf +#define TVO_VIP_SEL_INPUT_BYPASS_MASK 0x1 +#define TVO_VIP_SEL_INPUT_BYPASSED 1 + +#define TVO_SYNC_MAIN_VTG_SET_REF 0x00 +#define TVO_SYNC_MAIN_VTG_SET_1 0x01 +#define TVO_SYNC_MAIN_VTG_SET_2 0x02 +#define TVO_SYNC_MAIN_VTG_SET_3 0x03 +#define TVO_SYNC_MAIN_VTG_SET_4 0x04 +#define TVO_SYNC_MAIN_VTG_SET_5 0x05 +#define TVO_SYNC_MAIN_VTG_SET_6 0x06 +#define TVO_SYNC_AUX_VTG_SET_REF 0x10 +#define TVO_SYNC_AUX_VTG_SET_1 0x11 +#define TVO_SYNC_AUX_VTG_SET_2 0x12 +#define TVO_SYNC_AUX_VTG_SET_3 0x13 +#define TVO_SYNC_AUX_VTG_SET_4 0x14 +#define TVO_SYNC_AUX_VTG_SET_5 0x15 +#define TVO_SYNC_AUX_VTG_SET_6 0x16 + +#define TVO_SYNC_HD_DCS_SHIFT 8 + +/* Preformatter conversion matrix */ +static const u32 rgb_to_ycbcr_601[8] = { + 0xF927082E, 0x04C9FEAB, 0x01D30964, 0xFA95FD3D, + 0x0000082E, 0x00002000, 0x00002000, 0x00000000 +}; + +/* 709 RGB to YCbCr */ +static const u32 rgb_to_ycbcr_709[8] = { + 0xF891082F, 0x0367FF40, 0x01280B71, 0xF9B1FE20, + 0x0000082F, 0x00002000, 0x00002000, 0x00000000 +}; + +/* + * Helper to write bit field + * + * @addr: register to update + * @val: value to write + * @mask: bit field mask to use + */ +static inline void tvout_reg_writemask(void __iomem *addr, u32 val, u32 mask) +{ + u32 old = readl(addr); + + val = (val & mask) | (old & ~mask); + writel(val, addr); +} + +/* + * Set the Channel order of a VIP + * + * @vip_reg: VIP regsiter + * @cr_r + * @y_g + * @cb_c : values for each output + */ +static void tvout_vip_set_color_order(void __iomem *vip_reg, + u32 cr_r, u32 y_g, u32 cb_b) +{ + u32 val, mask; + + mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_R_SHIFT; + mask |= TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_G_SHIFT; + mask |= TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_B_SHIFT; + val = cr_r << TVO_VIP_REORDER_R_SHIFT; + val |= y_g << TVO_VIP_REORDER_G_SHIFT; + val |= cb_b << TVO_VIP_REORDER_B_SHIFT; + tvout_reg_writemask(vip_reg, val, mask); +} + +/* + * Set the clipping mode of a VIP + * + * @vip_reg: VIP regsiter + * @range : clipping range + */ +static void tvout_vip_set_clip_mode(void __iomem *vip_reg, u32 range) +{ + tvout_reg_writemask(vip_reg, + range << TVO_VIP_CLIP_SHIFT, + TVO_VIP_CLIP_MASK << TVO_VIP_CLIP_SHIFT); +} + +/* + * Set the rounded value of a VIP + * + * @vip_reg: VIP regsiter + * @rnd: rounded val per component + */ +static void tvout_vip_set_rnd(void __iomem *vip_reg, u32 rnd) +{ + tvout_reg_writemask(vip_reg, + rnd << TVO_VIP_RND_SHIFT, + TVO_VIP_RND_MASK << TVO_VIP_RND_SHIFT); +} + +/* + * Select the VIP input + * + * @vip_reg: VIP regsiter + * @sel_input: selected_input (main/aux + conv) + */ +static void tvout_vip_set_sel_input(void __iomem *vip_reg, + bool main_path, + bool sel_input_logic_inverted, + enum sti_tvout_video_out_type video_out) +{ + u32 sel_input; + + if (main_path) + sel_input = TVO_VIP_SEL_INPUT_MAIN; + else + sel_input = TVO_VIP_SEL_INPUT_AUX; + + switch (video_out) { + case STI_TVOUT_VIDEO_OUT_RGB: + sel_input |= TVO_VIP_SEL_INPUT_BYPASSED; + break; + case STI_TVOUT_VIDEO_OUT_YUV: + sel_input &= ~TVO_VIP_SEL_INPUT_BYPASSED; + break; + } + + /* On stih407 chip the sel_input bypass mode logic is inverted */ + if (sel_input_logic_inverted) + sel_input = sel_input ^ TVO_VIP_SEL_INPUT_BYPASS_MASK; + + tvout_reg_writemask(vip_reg, sel_input, TVO_VIP_SEL_INPUT_MASK); +} + +/* + * Select the input video signed or unsigned + * + * @vip_reg: VIP regsiter + * @in_vid_signed: used video input format + */ +static void tvout_vip_set_in_vid_fmt(void __iomem *vip_reg, u32 in_vid_fmt) +{ + tvout_reg_writemask(vip_reg, in_vid_fmt, TVO_IN_FMT_SIGNED); +} + +/* + * Start VIP block for HDMI output + * + * @tvout: pointer on tvout structure + * @main_path: true if main path has to be used in the vip configuration + * else aux path is used. + */ +static void tvout_hdmi_start(struct sti_tvout *tvout, bool main_path) +{ + struct device_node *node = tvout->dev->of_node; + bool sel_input_logic_inverted = false; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (main_path) { + DRM_DEBUG_DRIVER("main vip for hdmi\n"); + /* Select the input sync for hdmi = VTG set 1 */ + writel(TVO_SYNC_MAIN_VTG_SET_1, + tvout->regs + TVO_HDMI_SYNC_SEL); + } else { + DRM_DEBUG_DRIVER("aux vip for hdmi\n"); + /* Select the input sync for hdmi = VTG set 1 */ + writel(TVO_SYNC_AUX_VTG_SET_1, tvout->regs + TVO_HDMI_SYNC_SEL); + } + + /* Set color channel order */ + tvout_vip_set_color_order(tvout->regs + TVO_VIP_HDMI, + TVO_VIP_REORDER_CR_R_SEL, + TVO_VIP_REORDER_Y_G_SEL, + TVO_VIP_REORDER_CB_B_SEL); + + /* Set clipping mode (Limited range RGB/Y) */ + tvout_vip_set_clip_mode(tvout->regs + TVO_VIP_HDMI, + TVO_VIP_CLIP_LIMITED_RANGE_RGB_Y); + + /* Set round mode (rounded to 8-bit per component) */ + tvout_vip_set_rnd(tvout->regs + TVO_VIP_HDMI, TVO_VIP_RND_8BIT_ROUNDED); + + if (of_device_is_compatible(node, "st,stih407-tvout")) { + /* Set input video format */ + tvout_vip_set_in_vid_fmt(tvout->regs + TVO_MAIN_IN_VID_FORMAT, + TVO_IN_FMT_SIGNED); + sel_input_logic_inverted = true; + } + + /* Input selection */ + tvout_vip_set_sel_input(tvout->regs + TVO_VIP_HDMI, + main_path, + sel_input_logic_inverted, + STI_TVOUT_VIDEO_OUT_RGB); +} + +/* + * Prepare/configure VIP block for HDMI output + * + * @tvout: pointer on tvout structure + */ +static void tvout_hdmi_prepare(struct sti_tvout *tvout) +{ + dev_dbg(tvout->dev, "%s\n", __func__); + + /* Reset VIP register */ + writel(0x00000000, tvout->regs + TVO_VIP_HDMI); +} + +/* + * Disable HDMI VIP + * + * @tvout: pointer on tvout structure + */ +static void tvout_hdmi_stop(struct sti_tvout *tvout) +{ + dev_dbg(tvout->dev, "%s\n", __func__); + + /* Reset VIP register */ + writel(0x00000000, tvout->regs + TVO_VIP_HDMI); +} + +/* + * Start HDF VIP and HD DAC + * + * @tvout: pointer on tvout structure + * @main_path: true if main path has to be used in the vip configuration + * else aux path is used. + */ +static void tvout_hda_start(struct sti_tvout *tvout, bool main_path) +{ + struct device_node *node = tvout->dev->of_node; + bool sel_input_logic_inverted = false; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (!main_path) { + DRM_ERROR("HD Analog on aux not implemented\n"); + return; + } + + DRM_DEBUG_DRIVER("main vip for HDF\n"); + + /* Set color channel order */ + tvout_vip_set_color_order(tvout->regs + TVO_VIP_HDF, + TVO_VIP_REORDER_CR_R_SEL, + TVO_VIP_REORDER_Y_G_SEL, + TVO_VIP_REORDER_CB_B_SEL); + + /* Set clipping mode (Limited range RGB/Y) */ + tvout_vip_set_clip_mode(tvout->regs + TVO_VIP_HDF, + TVO_VIP_CLIP_LIMITED_RANGE_CB_CR); + + /* Set round mode (rounded to 10-bit per component) */ + tvout_vip_set_rnd(tvout->regs + TVO_VIP_HDF, TVO_VIP_RND_10BIT_ROUNDED); + + if (of_device_is_compatible(node, "st,stih407-tvout")) { + /* Set input video format */ + tvout_vip_set_in_vid_fmt(tvout->regs + TVO_MAIN_IN_VID_FORMAT, + TVO_IN_FMT_SIGNED); + sel_input_logic_inverted = true; + } + + /* Input selection */ + tvout_vip_set_sel_input(tvout->regs + TVO_VIP_HDF, + main_path, + sel_input_logic_inverted, + STI_TVOUT_VIDEO_OUT_YUV); + + /* Select the input sync for HD analog = VTG set 3 + * and HD DCS = VTG set 2 */ + writel((TVO_SYNC_MAIN_VTG_SET_2 << TVO_SYNC_HD_DCS_SHIFT) | + TVO_SYNC_MAIN_VTG_SET_3, tvout->regs + TVO_HD_SYNC_SEL); + + /* Power up HD DAC */ + writel(0, tvout->regs + TVO_HD_DAC_CFG_OFF); +} + +/* + * Prepare/configure HDF VIP and HD DAC + * + * @tvout: pointer on tvout structure + */ +static void tvout_hda_prepare(struct sti_tvout *tvout) +{ + dev_dbg(tvout->dev, "%s\n", __func__); + + /* Reset VIP register */ + writel(0x00000000, tvout->regs + TVO_VIP_HDF); + + /* Power down HD DAC */ + writel(1, tvout->regs + TVO_HD_DAC_CFG_OFF); +} + +/* + * Stop HDF VIP and HD DAC + * + * @tvout: pointer on tvout structure + */ +static void tvout_hda_stop(struct sti_tvout *tvout) +{ + dev_dbg(tvout->dev, "%s\n", __func__); + + /* Reset VIP register */ + writel(0x00000000, tvout->regs + TVO_VIP_HDF); + + /* Power down HD DAC */ + writel(1, tvout->regs + TVO_HD_DAC_CFG_OFF); +} + +/* + * Check if the connector is connected + * + * @tvout: pointer on tvout structure + * @type: type of connector + * + * Return true if connected + */ +bool sti_tvout_connector_detect(struct sti_tvout *tvout, + enum sti_tvout_connector_type type) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->detect) + return connector->detect(connector); + + return false; +} + +/* + * Forward drm display mode information to the connector + * + * @tvout: pointer on tvout structure + * @mode: selected display mode + * @type: type of connector + * + * Return -1 if error occurs + */ +int sti_tvout_set_mode(struct sti_tvout *tvout, struct drm_display_mode *mode, + enum sti_tvout_connector_type type) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->set_mode) + return connector->set_mode(connector, mode); + + return -1; +} + +/* + * Get modes + * + * @tvout: pointer on tvout structure + * @type: type of connector + * @drm_connector: pointer on the connector + * + * Return Nb of modes, -1 if error + */ +int sti_tvout_get_modes(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + struct drm_connector *drm_connector) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->get_modes) + return connector->get_modes(drm_connector); + + return -1; +} + +/* + * Check if the mode is supported + * + * function used to filter unsupported mode + * + * @tvout: pointer on tvout structure + * @type: type of connector + * @mode: drm display mode + * + * Return -1 if error occurs + */ +int sti_tvout_check_mode(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + struct drm_display_mode *mode) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->check_mode) + return connector->check_mode(connector, mode); + + return -1; +} + +/* + * Prepare / initialize depending on the connector type + * + * @tvout: pointer on tvout structure + * @type: type of connector + * + * Return -1 if error occurs + */ +int sti_tvout_prepare(struct sti_tvout *tvout, + enum sti_tvout_connector_type type) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + int ret = -1; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (connector) + if (connector->prepare) + connector->prepare(connector); + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + tvout_hdmi_prepare(tvout); + ret = 0; + break; + case STI_TVOUT_CONNECTOR_HDA: + tvout_hda_prepare(tvout); + ret = 0; + break; + case STI_TVOUT_CONNECTOR_DVO: + case STI_TVOUT_CONNECTOR_DENC: + default: + /* Not yet supported */ + ret = -1; + break; + } + + return ret; +} + +/* + * Commit / start depending on the connector type + * + * @tvout: pointer on tvout structure + * @type: type of connector + * @main_path: true if main path need to be use (false for aux path) + * + * Return -1 if error occurs + */ +int sti_tvout_commit(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, bool main_path) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + int ret = 0; + u32 matrix_offset; + int i; + + dev_dbg(tvout->dev, "%s\n", __func__); + + if (!connector) + return -1; + + connector->main_path = main_path; + + if (connector->start) { + ret = connector->start(connector); + if (ret) { + DRM_ERROR("Unable to properly start connector\n"); + return -1; + } + } + + /* Set preformatter matrix */ + matrix_offset = main_path ? TVO_CSC_MAIN_M0 : TVO_CSC_AUX_M0; + for (i = 0; i < 8; i++) + writel(rgb_to_ycbcr_601[i], + tvout->regs + matrix_offset + (i * 4)); + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + tvout_hdmi_start(tvout, main_path); + return 0; + case STI_TVOUT_CONNECTOR_HDA: + tvout_hda_start(tvout, main_path); + return 0; + case STI_TVOUT_CONNECTOR_DVO: + case STI_TVOUT_CONNECTOR_DENC: + default: + /* Not yet supported */ + return -1; + } +} + +/* + * Disable / stop the tvout depending on the connector type + * + * @tvout: pointer on tvout structure + * @type: type of connector + */ +void sti_tvout_disable(struct sti_tvout *tvout, + enum sti_tvout_connector_type type) +{ + struct sti_tvout_connector *connector = tvout->connector[type]; + + dev_dbg(tvout->dev, "%s\n", __func__); + + switch (type) { + case STI_TVOUT_CONNECTOR_HDMI: + tvout_hdmi_stop(tvout); + break; + case STI_TVOUT_CONNECTOR_HDA: + tvout_hda_stop(tvout); + break; + case STI_TVOUT_CONNECTOR_DVO: + case STI_TVOUT_CONNECTOR_DENC: + default: + /* Not yet supported */ + return; + } + + if (connector) + if (connector->stop) + connector->stop(connector); +} + +static int sti_tvout_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct device_node *node = dev->of_node; + struct sti_tvout *tvout; + struct resource *res; + + dev_dbg(dev, "%s\n", __func__); + + if (!node) { + DRM_ERROR("no device node\n"); + return -ENODEV; + } + + tvout = devm_kzalloc(dev, sizeof(*tvout), GFP_KERNEL); + if (!tvout) { + DRM_ERROR("failed to allocate compositor context\n"); + return -ENOMEM; + } + DRM_DEBUG_DRIVER("tvout %p\n", tvout); + tvout->dev = dev; + + /* Get Memory ressources */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tvout-reg"); + if (!res) { + DRM_ERROR("Invalid glue resource\n"); + return -ENOMEM; + } + tvout->regs = devm_ioremap_nocache(dev, res->start, resource_size(res)); + if (IS_ERR(tvout->regs)) + return PTR_ERR(tvout->regs); + + /* Get reset resources */ + tvout->reset = devm_reset_control_get(dev, "tvout"); + /* Take tvout out of reset */ + if (!IS_ERR(tvout->reset)) + reset_control_deassert(tvout->reset); + + /* List supported tvout connector */ + tvout->connector_create[STI_TVOUT_CONNECTOR_HDMI] = sti_hdmi_create; + tvout->connector_create[STI_TVOUT_CONNECTOR_HDA] = sti_hda_create; + tvout->connector_create[STI_TVOUT_CONNECTOR_DVO] = NULL; + tvout->connector_create[STI_TVOUT_CONNECTOR_DENC] = NULL; + + platform_set_drvdata(pdev, tvout); + + return 0; +} + +static void sti_tvout_unbind(struct device *dev, struct device *master, + void *data) +{ + /* do nothing */ +} + +static const struct component_ops sti_tvout_ops = { + .bind = sti_tvout_bind, + .unbind = sti_tvout_unbind, +}; + +static int sti_tvout_probe(struct platform_device *pdev) +{ + DRM_INFO("%s\n", __func__); + return component_add(&pdev->dev, &sti_tvout_ops); +} + +static int sti_tvout_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sti_tvout_ops); + return 0; +} + +static struct of_device_id tvout_match_types[] = { + { + .compatible = "st,stih416-tvout", + }, + { + .compatible = "st,stih407-tvout", + }, + { /* end node */ } +}; +MODULE_DEVICE_TABLE(of, tvout_match_types); + +struct platform_driver sti_tvout_driver = { + .driver = { + .name = "sti-tvout", + .owner = THIS_MODULE, + .of_match_table = tvout_match_types, + }, + .probe = sti_tvout_probe, + .remove = sti_tvout_remove, +}; + +module_platform_driver(sti_tvout_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sti/sti_tvout.h b/drivers/gpu/drm/sti/sti_tvout.h new file mode 100644 index 0000000..f61a49c --- /dev/null +++ b/drivers/gpu/drm/sti/sti_tvout.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Vincent Abriou <vincent.abriou at st.com> for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _STI_TVOUT_H_ +#define _STI_TVOUT_H_ + +#include <linux/clk.h> + +/* + * STI TVout connector structure + * + * @priv: private structure associated to the connector type + * @start: start the connector + * @stop: stop the connector + * @get_modes: get modes potentially supported + * @check_mode: check if a mode is really supported + * @set_mode: set the drm display mode in the specific connector structure + * @detect: detect if connector is connected + * @prepare: prepare the connector + * @is_enabled: is the connector enabled + * @dbg_show: dump debug information + */ +struct sti_tvout_connector { + void *priv; + bool main_path; + int (*start)(struct sti_tvout_connector *connector); + void (*stop)(struct sti_tvout_connector *connector); + int (*get_modes)(struct drm_connector *drm_connector); + int (*check_mode)(struct sti_tvout_connector *connector, + struct drm_display_mode *mode); + int (*set_mode)(struct sti_tvout_connector *connector, + struct drm_display_mode *mode); + bool (*detect)(struct sti_tvout_connector *connector); + void (*prepare)(struct sti_tvout_connector *connector); + bool (*is_enabled)(struct sti_tvout_connector *connector); + void (*dbg_show)(struct sti_tvout_connector *connector, + struct seq_file *m); +}; + +/* + * enum listing the supported connector + */ +enum sti_tvout_connector_type { + STI_TVOUT_CONNECTOR_HDMI, + STI_TVOUT_CONNECTOR_HDA, + STI_TVOUT_CONNECTOR_DVO, + STI_TVOUT_CONNECTOR_DENC, + STI_TVOUT_CONNECTOR_MAX, +}; + +/* + * enum listing the supported output data format + */ +enum sti_tvout_video_out_type { + STI_TVOUT_VIDEO_OUT_RGB, + STI_TVOUT_VIDEO_OUT_YUV, +}; + +/* + * STI TVout structure + * + * @dev: pointer to driver device + * @regs: registers + * @reset: reset control of the tvout + * @hw_id: HW revision of the IP + * @connector_create: list of function to register a connector + * @connector: list of registered connector + */ +struct sti_tvout { + struct device *dev; + struct drm_device *drm_dev; + void __iomem *regs; + struct reset_control *reset; + int hw_id; + struct sti_tvout_connector *(*connector_create[STI_TVOUT_CONNECTOR_MAX]) + (struct sti_tvout *tvout); + struct sti_tvout_connector *connector[STI_TVOUT_CONNECTOR_MAX]; +}; + +bool sti_tvout_connector_detect(struct sti_tvout *tvout, + enum sti_tvout_connector_type type); +int sti_tvout_set_mode(struct sti_tvout *tvout, + struct drm_display_mode *mode, + enum sti_tvout_connector_type type); +int sti_tvout_get_modes(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + struct drm_connector *drm_connector); +int sti_tvout_check_mode(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + struct drm_display_mode *mode); +int sti_tvout_prepare(struct sti_tvout *tvout, + enum sti_tvout_connector_type type); +int sti_tvout_commit(struct sti_tvout *tvout, + enum sti_tvout_connector_type type, + bool main_path); +void sti_tvout_disable(struct sti_tvout *tvout, + enum sti_tvout_connector_type type); + +int sti_tvout_hdmi_dbg_show(struct seq_file *m, void *arg); +int sti_tvout_hda_dbg_show(struct seq_file *m, void *arg); + +#endif -- 1.9.1