[PATCH v1 04/19] drm: sti: add HDMI driver
Benjamin Gaignard
benjamin.gaignard at linaro.org
Tue Apr 8 05:19:11 PDT 2014
Add driver for HDMI ouput
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 | 5 +
drivers/gpu/drm/sti/sti_hdmi.c | 503 +++++++++++++++++++++++++++++
drivers/gpu/drm/sti/sti_hdmi.h | 195 +++++++++++
drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c | 398 +++++++++++++++++++++++
drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c | 224 +++++++++++++
5 files changed, 1325 insertions(+)
create mode 100644 drivers/gpu/drm/sti/sti_hdmi.c
create mode 100644 drivers/gpu/drm/sti/sti_hdmi.h
create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c
create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c
diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile
index 79fdcb6..2bb3035 100644
--- a/drivers/gpu/drm/sti/Makefile
+++ b/drivers/gpu/drm/sti/Makefile
@@ -1,4 +1,9 @@
ccflags-y := -Iinclude/drm
+stidrm-y := sti_hdmi.o \
+ sti_hdmi_tx3g0c55phy.o \
+ sti_hdmi_tx3g4c28phy.o
+
+obj-$(CONFIG_DRM_STI) += stidrm.o
obj-$(CONFIG_VTAC_STI) += sti_vtac_tx.o sti_vtac_rx.o
obj-$(CONFIG_VTG_STI) += sti_vtg.o sti_vtg_utils.o
diff --git a/drivers/gpu/drm/sti/sti_hdmi.c b/drivers/gpu/drm/sti/sti_hdmi.c
new file mode 100644
index 0000000..5bbee6b
--- /dev/null
+++ b/drivers/gpu/drm/sti/sti_hdmi.c
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2013
+ * 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/hdmi.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+
+#include "sti_hdmi.h"
+#include "sti_vtg_utils.h"
+
+/* Reference to the hdmi device */
+struct device *hdmi_dev;
+
+/*
+ * Helper to write bit field
+ *
+ * @addr: register to update
+ * @val: value to write
+ * @mask: bit field mask to use
+ */
+static inline void hdmi_reg_writemask(void __iomem *addr, u32 val, u32 mask)
+{
+ u32 old = readl(addr);
+
+ val = (val & mask) | (old & ~mask);
+ writel(val, addr);
+}
+
+/*
+ * HDMI interrupt handler
+ *
+ * @irq: irq number
+ * @arg: connector structure
+ */
+static irqreturn_t hdmi_irq_thread(int irq, void *arg)
+{
+ struct sti_hdmi *hdmi = arg;
+ u32 status;
+
+ /* read interrupt status */
+ status = readl(hdmi->regs + HDMI_INT_STA);
+
+ /* PLL lock interrupt */
+ if (status & HDMI_INT_DLL_LCK) {
+ hdmi->event_received = true;
+ wake_up_interruptible(&hdmi->wait_event);
+ }
+
+ /* Hot plug detection */
+ if (status & HDMI_INT_HOT_PLUG) {
+ hdmi->hpd = gpio_get_value(hdmi->hpd_gpio);
+ if (hdmi->drm_dev)
+ drm_helper_hpd_irq_event(hdmi->drm_dev);
+ }
+
+ /* Sw reset completed */
+ if (status & HDMI_INT_SW_RST) {
+ hdmi->event_received = true;
+ wake_up_interruptible(&hdmi->wait_event);
+ }
+
+ /* clear interrupt status */
+ writel(status, hdmi->regs + HDMI_INT_CLR);
+
+ /* TODO: check why this sync bus write solves the problem which
+ * is that without this line, the handler is sometimes called twice
+ */
+ /* sync bus write */
+ readl(hdmi->regs + HDMI_INT_STA);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Start hdmi phy interface
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ *
+ * Return -1 if error occurs
+ */
+static int hdmi_phy_start(struct sti_hdmi *hdmi)
+{
+ DRM_DEBUG_DRIVER("\n");
+
+ if (hdmi->tx3g0c55phy)
+ return sti_hdmi_tx3g0c55phy_start(hdmi);
+
+ return sti_hdmi_tx3g4c28phy_start(hdmi);
+}
+
+/*
+ * Stop hdmi phy interface
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ */
+static void hdmi_phy_stop(struct sti_hdmi *hdmi)
+{
+ DRM_DEBUG_DRIVER("\n");
+
+ if (hdmi->tx3g0c55phy)
+ sti_hdmi_tx3g0c55phy_stop(hdmi);
+ else
+ sti_hdmi_tx3g4c28phy_stop(hdmi);
+}
+
+/*
+ * Set hdmi active area depending on the drm display mode selected
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ */
+static void hdmi_active_area(struct sti_hdmi *hdmi)
+{
+ u32 xmin, xmax;
+ u32 ymin, ymax;
+
+ DRM_DEBUG_DRIVER("\n");
+
+ /*
+ * Active Front Sync Back Active
+ * Region Porch Porch Region
+ * <---------------><-------->0<---------><--------><----------------->
+ *
+ * ///////////////| | ///////////////|
+ * /////////////// | | /////////////// |
+ * /////////////// |......... ..........|/////////////// |
+ * 0___________ x/ymin x/ymax
+ *
+ * <--[hv]display--> <--[hv]display-->
+ * <--[hv]sync_start---------> <--[hv]sync_start-
+ * <--[hv]sync_end-----------------------> <--[hv]sync_end---
+ * <--[hv]total------------------------------------> <--[hv]total------
+ */
+
+ xmin = sti_vtg_get_pixel_number(hdmi->mode, 0);
+ xmax = sti_vtg_get_pixel_number(hdmi->mode, hdmi->mode.hdisplay - 1);
+ ymin = sti_vtg_get_line_number(hdmi->mode, 0);
+ ymax = sti_vtg_get_line_number(hdmi->mode, hdmi->mode.vdisplay - 1);
+
+ writel(xmin, hdmi->regs + HDMI_ACTIVE_VID_XMIN);
+ writel(xmax, hdmi->regs + HDMI_ACTIVE_VID_XMAX);
+ writel(ymin, hdmi->regs + HDMI_ACTIVE_VID_YMIN);
+ writel(ymax, hdmi->regs + HDMI_ACTIVE_VID_YMAX);
+
+ DRM_DEBUG_DRIVER("xmin=%d xmax=%d ymin=%d ymax=%d\n",
+ xmin, xmax, ymin, ymax);
+}
+
+/*
+ * Overall hdmi configuration
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ */
+static void hdmi_config(struct sti_hdmi *hdmi)
+{
+ u32 val;
+ u32 mask;
+
+ DRM_DEBUG_DRIVER("\n");
+
+ /* Clear overrun and underrun fifo */
+ mask = HDMI_CFG_FIFO_OVERRUN_CLR | HDMI_CFG_FIFO_UNDERRUN_CLR;
+ val = HDMI_CFG_FIFO_OVERRUN_CLR | HDMI_CFG_FIFO_UNDERRUN_CLR;
+
+ /* Enable HDMI mode not DVI */
+ mask |= HDMI_CFG_HDMI_NOT_DVI | HDMI_CFG_ESS_NOT_OESS;
+ val |= HDMI_CFG_HDMI_NOT_DVI | HDMI_CFG_ESS_NOT_OESS;
+
+ /* Enable sink term detection */
+ mask |= HDMI_CFG_SINK_TERM_DET_EN;
+ val |= HDMI_CFG_SINK_TERM_DET_EN;
+
+ /* Set Hsync polarity */
+ if ((hdmi->mode.flags && DRM_MODE_FLAG_NHSYNC)
+ == DRM_MODE_FLAG_NHSYNC) {
+ DRM_DEBUG_DRIVER("H Sync Negative\n");
+ mask |= HDMI_CFG_H_SYNC_POL_NEG;
+ val |= HDMI_CFG_H_SYNC_POL_NEG;
+ }
+
+ /* Set Vsync polarity */
+ if ((hdmi->mode.flags && DRM_MODE_FLAG_NVSYNC)
+ == DRM_MODE_FLAG_NVSYNC) {
+ DRM_DEBUG_DRIVER("V Sync Negative\n");
+ mask |= HDMI_CFG_V_SYNC_POL_NEG;
+ val |= HDMI_CFG_V_SYNC_POL_NEG;
+ }
+
+ /* Enable HDMI */
+ mask |= HDMI_CFG_DEVICE_EN;
+ val |= HDMI_CFG_DEVICE_EN;
+
+ hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask);
+}
+
+/*
+ * Prepare and configure the AVI infoframe
+ *
+ * AVI infoframe are transmitted at least once per two video field and
+ * contains information about HDMI transmission mode such as color space,
+ * colorimetry, ...
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ *
+ * Return negative value if error occurs
+ */
+static int hdmi_avi_infoframe_config(struct sti_hdmi *hdmi)
+{
+ struct drm_display_mode *mode = &hdmi->mode;
+ struct hdmi_avi_infoframe infoframe;
+ u8 buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE];
+ u8 *frame = buffer + HDMI_INFOFRAME_HEADER_SIZE - 1;
+ u32 val;
+ u32 mask;
+ int ret;
+
+ DRM_DEBUG_DRIVER("\n");
+
+ ret = drm_hdmi_avi_infoframe_from_display_mode(&infoframe, mode);
+ if (ret < 0) {
+ DRM_ERROR("failed to setup AVI infoframe: %d\n", ret);
+ return ret;
+ }
+
+ /* TODO: remove static infoframe configuration */
+ infoframe.colorspace = HDMI_COLORSPACE_RGB;
+ infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT;
+ infoframe.colorimetry = HDMI_COLORIMETRY_NONE;
+ infoframe.pixel_repeat = 0;
+
+ ret = hdmi_avi_infoframe_pack(&infoframe, buffer, sizeof(buffer));
+ if (ret < 0) {
+ DRM_ERROR("failed to pack AVI infoframe: %d\n", ret);
+ return ret;
+ }
+
+ /* Disable transmission slot for AVI infoframe */
+ val = HDMI_IFRAME_DISABLED;
+ mask = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, HDMI_IFRAME_SLOT_AVI);
+ hdmi_reg_writemask(hdmi->regs + HDMI_SW_DI_CFG, val, mask);
+
+ /* Infoframe header */
+ val = buffer[0x0];
+ val |= buffer[0x1] << 8;
+ val |= buffer[0x2] << 16;
+ writel(val, hdmi->regs + HDMI_SW_DI_N_HEAD_WORD(HDMI_IFRAME_SLOT_AVI));
+ /* Infoframe packet bytes */
+ val = frame[0x0];
+ val |= frame[0x1] << 8;
+ val |= frame[0x2] << 16;
+ val |= frame[0x3] << 24;
+ writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD0(HDMI_IFRAME_SLOT_AVI));
+ val = frame[0x4];
+ val |= frame[0x5] << 8;
+ val |= frame[0x6] << 16;
+ val |= frame[0x7] << 24;
+ writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD1(HDMI_IFRAME_SLOT_AVI));
+ val = frame[0x8];
+ val |= frame[0x9] << 8;
+ val |= frame[0xA] << 16;
+ val |= frame[0xB] << 24;
+ writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD2(HDMI_IFRAME_SLOT_AVI));
+ val = frame[0xC];
+ val |= frame[0xD] << 8;
+ writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD3(HDMI_IFRAME_SLOT_AVI));
+
+ /* Enable transmission slot for AVI infoframe */
+ /* According to the hdmi specification, AVI infoframe should be
+ * transmitted at least once per two video fields */
+ val = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_FIELD, HDMI_IFRAME_SLOT_AVI);
+ mask = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, HDMI_IFRAME_SLOT_AVI);
+ hdmi_reg_writemask(hdmi->regs + HDMI_SW_DI_CFG, val, mask);
+
+ return 0;
+}
+
+/*
+ * Software reset of the hdmi subsystem
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ *
+ * Return -1 if error occurs
+ */
+#define HDMI_TIMEOUT_SWRESET 100 /*milliseconds */
+static int hdmi_swreset(struct sti_hdmi *hdmi)
+{
+ u32 val;
+ u32 mask;
+
+ DRM_DEBUG_DRIVER("\n");
+
+ /* Enable hdmi_audio clock only during hdmi reset */
+ if (clk_prepare_enable(hdmi->clk_audio))
+ DRM_INFO("Failed to prepare/enable hdmi_audio clk\n");
+
+ /* Sw reset */
+ mask = HDMI_CFG_SW_RST_EN;
+ val = HDMI_CFG_SW_RST_EN;
+
+ hdmi->event_received = false;
+ hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask);
+
+ /* Wait reset completed */
+ wait_event_interruptible_timeout(hdmi->wait_event,
+ hdmi->event_received == true,
+ msecs_to_jiffies
+ (HDMI_TIMEOUT_SWRESET));
+
+ /*
+ * HDMI_STA_SW_RST bit is set to '1' when SW_RST bit in HDMI_CFG is
+ * set to '1' and clk_audio is running.
+ */
+ if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_SW_RST) == 0)
+ DRM_INFO("Warning: HDMI sw reset timeout occurs\n");
+
+ mask = HDMI_CFG_SW_RST_EN;
+ val = ~HDMI_CFG_SW_RST_EN;
+ hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask);
+
+ /* Disable hdmi_audio clock. Not used anymore for drm purpose. */
+ clk_disable_unprepare(hdmi->clk_audio);
+
+ return 0;
+}
+
+/*
+ * Attach the I2C ddc client to allow hdmi i2c communication
+ *
+ * @ddc: i2c client
+ */
+static struct i2c_client *hdmi_ddc;
+void sti_hdmi_attach_ddc_client(struct i2c_client *ddc)
+{
+ DRM_DEBUG_DRIVER("\n");
+
+ if (ddc)
+ hdmi_ddc = ddc;
+}
+
+/*
+ * Get modes from edid
+ *
+ * @drm_connector: pointer on the drm connector
+ */
+static int sti_hdmi_get_modes(struct drm_connector *drm_connector)
+{
+ struct edid *edid;
+ int count;
+
+ DRM_DEBUG_DRIVER("\n");
+
+ if ((!hdmi_ddc) || (!hdmi_ddc->adapter))
+ goto fail;
+
+ edid = drm_get_edid(drm_connector, hdmi_ddc->adapter);
+ if (!edid)
+ goto fail;
+
+ count = drm_add_edid_modes(drm_connector, edid);
+ if (count)
+ drm_mode_connector_update_edid_property(drm_connector, edid);
+ else
+ DRM_ERROR("Add edid modes failed\n");
+
+ kfree(edid);
+ return count;
+
+fail:
+ DRM_ERROR("Can not read HDMI EDID\n");
+ return -1;
+}
+
+static int sti_hdmi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sti_hdmi *hdmi;
+ struct device_node *np = dev->of_node;
+ struct resource *res;
+ int ret;
+
+ DRM_INFO("%s\n", __func__);
+
+ hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+ if (!hdmi) {
+ DRM_ERROR("Failed to allocate memory for hdmi\n");
+ return -ENOMEM;
+ }
+
+ hdmi->dev = pdev->dev;
+
+ /* Get resources */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hdmi-reg");
+ if (!res) {
+ DRM_ERROR("Invalid hdmi resource\n");
+ return -ENOMEM;
+ }
+ hdmi->regs = devm_ioremap_nocache(dev, res->start, resource_size(res));
+ if (IS_ERR(hdmi->regs))
+ return PTR_ERR(hdmi->regs);
+
+ if (of_device_is_compatible(np, "st,stih416-hdmi")) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "syscfg");
+ if (!res) {
+ DRM_ERROR("Invalid syscfg resource\n");
+ return -ENOMEM;
+ }
+ hdmi->syscfg = devm_ioremap_nocache(dev, res->start,
+ resource_size(res));
+ if (IS_ERR(hdmi->syscfg))
+ return PTR_ERR(hdmi->syscfg);
+
+ hdmi->tx3g0c55phy = true;
+ }
+
+ /* Get clock resources */
+ hdmi->clk_pix = devm_clk_get(dev, "hdmi_pix");
+ if (IS_ERR(hdmi->clk_pix)) {
+ DRM_ERROR("Cannot get hdmi_pix clock\n");
+ return PTR_ERR(hdmi->clk_pix);
+ }
+
+ hdmi->clk_tmds = devm_clk_get(dev, "hdmi_tmds");
+ if (IS_ERR(hdmi->clk_tmds)) {
+ DRM_ERROR("Cannot get hdmi_tmds clock\n");
+ return PTR_ERR(hdmi->clk_tmds);
+ }
+
+ hdmi->clk_phy = devm_clk_get(dev, "hdmi_phy");
+ if (IS_ERR(hdmi->clk_phy)) {
+ DRM_ERROR("Cannot get hdmi_phy clock\n");
+ return PTR_ERR(hdmi->clk_phy);
+ }
+
+ hdmi->clk_audio = devm_clk_get(dev, "hdmi_audio");
+ if (IS_ERR(hdmi->clk_audio)) {
+ DRM_ERROR("Cannot get hdmi_audio clock\n");
+ return PTR_ERR(hdmi->clk_audio);
+ }
+
+ hdmi->hpd_gpio = of_get_named_gpio(np, "hdmi,hpd-gpio", 0);
+ if (hdmi->hpd_gpio < 0) {
+ DRM_ERROR("Failed to get hdmi hpd-gpio\n");
+ return -EIO;
+ }
+
+ hdmi->hpd = gpio_get_value(hdmi->hpd_gpio);
+
+ init_waitqueue_head(&hdmi->wait_event);
+
+ /* Get irq ressources */
+ hdmi->irq = platform_get_irq_byname(pdev, "hdmi_irq");
+
+ ret = devm_request_threaded_irq(dev, hdmi->irq, NULL,
+ hdmi_irq_thread, IRQF_ONESHOT,
+ "hdmi_irq", hdmi);
+ if (ret) {
+ DRM_ERROR("Failed to register hdmi interrupt\n");
+ return ret;
+ }
+
+ /* Get reset resources */
+ hdmi->reset = devm_reset_control_get(dev, "hdmi");
+ /* Take hdmi out of reset */
+ if (!IS_ERR(hdmi->reset))
+ reset_control_deassert(hdmi->reset);
+
+ hdmi_dev = &hdmi->dev;
+
+ platform_set_drvdata(pdev, hdmi);
+
+ return 0;
+}
+
+static struct of_device_id hdmi_match_types[] = {
+ {
+ .compatible = "st,stih416-hdmi",
+ },
+ {
+ .compatible = "st,stih407-hdmi",
+ },
+ { /* end node */ }
+};
+
+struct platform_driver sti_hdmi_driver = {
+ .driver = {
+ .name = "sti-hdmi",
+ .owner = THIS_MODULE,
+ .of_match_table = hdmi_match_types,
+ },
+ .probe = sti_hdmi_probe,
+};
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sti/sti_hdmi.h b/drivers/gpu/drm/sti/sti_hdmi.h
new file mode 100644
index 0000000..c14c683
--- /dev/null
+++ b/drivers/gpu/drm/sti/sti_hdmi.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2013
+ * Authors: Vincent Abriou <vincent.abriou at st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#ifndef _STI_HDMI_H_
+#define _STI_HDMI_H_
+
+#include <linux/platform_device.h>
+
+#include <drm/drmP.h>
+
+/* HDMI v2.9 macro cell */
+#define HDMI_CFG 0x0000
+#define HDMI_INT_EN 0x0004
+#define HDMI_INT_STA 0x0008
+#define HDMI_INT_CLR 0x000C
+#define HDMI_STA 0x0010
+#define HDMI_ACTIVE_VID_XMIN 0x0100
+#define HDMI_ACTIVE_VID_XMAX 0x0104
+#define HDMI_ACTIVE_VID_YMIN 0x0108
+#define HDMI_ACTIVE_VID_YMAX 0x010C
+#define HDMI_DFLT_CHL0_DAT 0x0110
+#define HDMI_DFLT_CHL1_DAT 0x0114
+#define HDMI_DFLT_CHL2_DAT 0x0118
+#define HDMI_SW_DI_1_HEAD_WORD 0x0210
+#define HDMI_SW_DI_1_PKT_WORD0 0x0214
+#define HDMI_SW_DI_1_PKT_WORD1 0x0218
+#define HDMI_SW_DI_1_PKT_WORD2 0x021C
+#define HDMI_SW_DI_1_PKT_WORD3 0x0220
+#define HDMI_SW_DI_1_PKT_WORD4 0x0224
+#define HDMI_SW_DI_1_PKT_WORD5 0x0228
+#define HDMI_SW_DI_1_PKT_WORD6 0x022C
+#define HDMI_SW_DI_CFG 0x0230
+
+#define HDMI_IFRAME_SLOT_AVI 1
+
+#define XCAT(prefix, x, suffix) prefix ## x ## suffix
+#define HDMI_SW_DI_N_HEAD_WORD(x) XCAT(HDMI_SW_DI_, x, _HEAD_WORD)
+#define HDMI_SW_DI_N_PKT_WORD0(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD0)
+#define HDMI_SW_DI_N_PKT_WORD1(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD1)
+#define HDMI_SW_DI_N_PKT_WORD2(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD2)
+#define HDMI_SW_DI_N_PKT_WORD3(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD3)
+#define HDMI_SW_DI_N_PKT_WORD4(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD4)
+#define HDMI_SW_DI_N_PKT_WORD5(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD5)
+#define HDMI_SW_DI_N_PKT_WORD6(x) XCAT(HDMI_SW_DI_, x, _PKT_WORD6)
+
+#define HDMI_IFRAME_DISABLED 0x0
+#define HDMI_IFRAME_SINGLE_SHOT 0x1
+#define HDMI_IFRAME_FIELD 0x2
+#define HDMI_IFRAME_FRAME 0x3
+#define HDMI_IFRAME_MASK 0x3
+#define HDMI_IFRAME_CFG_DI_N(x, n) ((x) << ((n-1)*4)) /* n from 1 to 6 */
+
+#define HDMI_CFG_DEVICE_EN_SHIFT 0
+#define HDMI_CFG_DEVICE_EN (1 << HDMI_CFG_DEVICE_EN_SHIFT)
+#define HDMI_CFG_HDMI_NOT_DVI_SHIFT 1
+#define HDMI_CFG_HDMI_NOT_DVI (1 << HDMI_CFG_HDMI_NOT_DVI_SHIFT)
+#define HDMI_CFG_HDCP_EN_SHIFT 2
+#define HDMI_CFG_HDCP_EN (1 << HDMI_CFG_HDCP_EN_SHIFT)
+#define HDMI_CFG_ESS_NOT_OESS_SHIFT 3
+#define HDMI_CFG_ESS_NOT_OESS (1 << HDMI_CFG_ESS_NOT_OESS_SHIFT)
+#define HDMI_CFG_H_SYNC_POL_NEG_SHIFT 4
+#define HDMI_CFG_H_SYNC_POL_NEG (1 << HDMI_CFG_H_SYNC_POL_NEG_SHIFT)
+#define HDMI_CFG_SINK_TERM_DET_EN_SHIFT 5
+#define HDMI_CFG_SINK_TERM_DET_EN (1 << HDMI_CFG_SINK_TERM_DET_EN_SHIFT)
+#define HDMI_CFG_V_SYNC_POL_NEG_SHIFT 6
+#define HDMI_CFG_V_SYNC_POL_NEG (1 << HDMI_CFG_V_SYNC_POL_NEG_SHIFT)
+#define HDMI_CFG_422_EN_SHIFT 8
+#define HDMI_CFG_422_EN (1 << HDMI_CFG_422_EN_SHIFT)
+#define HDMI_CFG_FIFO_OVERRUN_CLR_SHIFT 12
+#define HDMI_CFG_FIFO_OVERRUN_CLR (1 << HDMI_CFG_FIFO_OVERRUN_CLR_SHIFT)
+#define HDMI_CFG_FIFO_UNDERRUN_CLR_SHIFT 13
+#define HDMI_CFG_FIFO_UNDERRUN_CLR (1 << HDMI_CFG_FIFO_UNDERRUN_CLR_SHIFT)
+#define HDMI_CFG_SW_RST_EN_SHIFT 31
+#define HDMI_CFG_SW_RST_EN (1 << HDMI_CFG_SW_RST_EN_SHIFT)
+
+#define HDMI_INT_GLOBAL (1 << 0)
+#define HDMI_INT_SW_RST (1 << 1)
+#define HDMI_INT_IFRAME (1 << 2)
+#define HDMI_INT_PIX_CAP (1 << 3)
+#define HDMI_INT_HOT_PLUG (1 << 4)
+#define HDMI_INT_DLL_LCK (1 << 5)
+#define HDMI_INT_NEW_FRAME (1 << 6)
+#define HDMI_INT_GENCTRL_PKT (1 << 7)
+#define HDMI_INT_SPDIF_FIFO_OVERRUN (1 << 8)
+#define HDMI_INT_VID_FIFO_UNDERRUN (1 << 9)
+#define HDMI_INT_VID_FIFO_OVERRUN (1 << 10)
+#define HDMI_INT_SINK_TERM_PRESENT (1 << 11)
+#define HDMI_INT_DI_2 (1 << 16)
+#define HDMI_INT_DI_3 (1 << 17)
+#define HDMI_INT_DI_4 (1 << 18)
+#define HDMI_INT_DI_5 (1 << 19)
+#define HDMI_INT_DI_6 (1 << 20)
+#define HDMI_INT_DI_DMA_VSYNC_DONE (1 << 21)
+#define HDMI_INT_DI_VSYNC_DONE (1 << 22)
+
+#define HDMI_DEFAULT_INT (HDMI_INT_SINK_TERM_PRESENT \
+ | HDMI_INT_DLL_LCK \
+ | HDMI_INT_HOT_PLUG \
+ | HDMI_INT_GLOBAL)
+
+#define HDMI_WORKING_INT (HDMI_INT_SINK_TERM_PRESENT \
+ | HDMI_INT_GENCTRL_PKT \
+ | HDMI_INT_NEW_FRAME \
+ | HDMI_INT_DLL_LCK \
+ | HDMI_INT_HOT_PLUG \
+ | HDMI_INT_PIX_CAP \
+ | HDMI_INT_SW_RST \
+ | HDMI_INT_GLOBAL)
+
+#define HDMI_STA_SW_RST_SHIFT 1
+#define HDMI_STA_SW_RST (1 << HDMI_STA_SW_RST_SHIFT)
+#define HDMI_STA_PIX_CAP_SHIFT 3
+#define HDMI_STA_PIX_CAP (1 << HDMI_STA_PIX_CAP_SHIFT)
+#define HDMI_STA_HOT_PLUG_SHIFT 4
+#define HDMI_STA_HOT_PLUG (1 << HDMI_STA_HOT_PLUG_SHIFT)
+#define HDMI_STA_DLL_LCK_SHIFT 5
+#define HDMI_STA_DLL_LCK (1 << HDMI_STA_DLL_LCK_SHIFT)
+#define HDMI_STA_SINK_TERM_SHIFT 6
+#define HDMI_STA_SINK_TERM (1 << HDMI_STA_SINK_TERM_SHIFT)
+#define HDMI_STA_FIFO_SAMPLES_SHIFT 8
+#define HDMI_STA_FIFO_SAMPLES (0x1F << HDMI_STA_FIFO_SAMPLES_SHIFT)
+
+/*
+ * STI hdmi structure
+ *
+ * @dev: driver device
+ * @drm_dev: pointer to drm device
+ * @mode: current display mode selected
+ * @regs: hdmi register
+ * @syscfg: syscfg register for pll rejection configuration
+ * @clk_pix: hdmi pixel clock
+ * @clk_tmds: hdmi tmds clock
+ * @clk_phy: hdmi phy clock
+ * @clk_audio: hdmi audio clock
+ * @irq: hdmi interrupt number
+ * @tx3g0c55phy: true if 3g0c55phy is supported
+ * @enabled: true if hdmi is enabled else false
+ * @hpd_gpio: hdmi hot plug detect gpio number
+ * @hpd: hot plug detect status
+ * @wait_event: wait event
+ * @event_received: wait event status
+ * @reset: reset control of the hdmi phy
+ */
+struct sti_hdmi {
+ struct device dev;
+ struct drm_device *drm_dev;
+ struct drm_display_mode mode;
+ void __iomem *regs;
+ void __iomem *syscfg;
+ struct clk *clk_pix;
+ struct clk *clk_tmds;
+ struct clk *clk_phy;
+ struct clk *clk_audio;
+ int irq;
+ bool tx3g0c55phy;
+ bool enabled;
+ int hpd_gpio;
+ bool hpd;
+ wait_queue_head_t wait_event;
+ bool event_received;
+ struct reset_control *reset;
+};
+
+/* hdmi phy config structure
+ *
+ * A pointer to an array of these structures is passed to a TMDS (HDMI) output
+ * via the control interface to provide board and SoC specific
+ * configurations of the HDMI PHY. Each entry in the array specifies a hardware
+ * specific configuration for a given TMDS clock frequency range.
+ *
+ * @min_tmds_freq: Lower bound of TMDS clock frequency this entry applies to
+ * @max_tmds_freq: Upper bound of TMDS clock frequency this entry applies to
+ * @config: SoC specific register configuration
+ */
+struct hdmi_phy_config {
+ u32 min_tmds_freq;
+ u32 max_tmds_freq;
+ u32 config[4];
+};
+
+void sti_hdmi_attach_ddc_client(struct i2c_client *ddc);
+
+int sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi);
+void sti_hdmi_tx3g0c55phy_stop(struct sti_hdmi *hdmi);
+void sti_hdmi_tx3g0c55phy_show(struct sti_hdmi *hdmi, struct seq_file *m);
+
+int sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi);
+void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi);
+void sti_hdmi_tx3g4c28phy_show(struct sti_hdmi *hdmi, struct seq_file *m);
+
+extern struct i2c_driver ddc_driver;
+#endif
diff --git a/drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c b/drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c
new file mode 100644
index 0000000..547e9ee
--- /dev/null
+++ b/drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2013
+ * Author: Vincent Abriou <vincent.abriou at st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include "sti_hdmi.h"
+
+#define HDMI_SRZ_PLL_CFG 0x0504
+#define HDMI_SRZ_TAP_1 0x0508
+#define HDMI_SRZ_TAP_2 0x050C
+#define HDMI_SRZ_TAP_3 0x0510
+#define HDMI_SRZ_CTRL 0x0514
+
+#define HDMI_SRZ_PLL_CFG_POWER_DOWN (1 << 0)
+#define HDMI_SRZ_PLL_CFG_VCOR_SHIFT 1
+#define HDMI_SRZ_PLL_CFG_VCOR_425MHZ 0
+#define HDMI_SRZ_PLL_CFG_VCOR_850MHZ 1
+#define HDMI_SRZ_PLL_CFG_VCOR_1700MHZ 2
+#define HDMI_SRZ_PLL_CFG_VCOR_3000MHZ 3
+#define HDMI_SRZ_PLL_CFG_VCOR_MASK 3
+#define HDMI_SRZ_PLL_CFG_VCOR(x) (x << HDMI_SRZ_PLL_CFG_VCOR_SHIFT)
+#define HDMI_SRZ_PLL_CFG_NDIV_SHIFT 8
+#define HDMI_SRZ_PLL_CFG_NDIV_MASK (0x1F << HDMI_SRZ_PLL_CFG_NDIV_SHIFT)
+#define HDMI_SRZ_PLL_CFG_MODE_SHIFT 16
+#define HDMI_SRZ_PLL_CFG_MODE_13_5_MHZ 0x1
+#define HDMI_SRZ_PLL_CFG_MODE_25_2_MHZ 0x4
+#define HDMI_SRZ_PLL_CFG_MODE_27_MHZ 0x5
+#define HDMI_SRZ_PLL_CFG_MODE_33_75_MHZ 0x6
+#define HDMI_SRZ_PLL_CFG_MODE_40_5_MHZ 0x7
+#define HDMI_SRZ_PLL_CFG_MODE_54_MHZ 0x8
+#define HDMI_SRZ_PLL_CFG_MODE_67_5_MHZ 0x9
+#define HDMI_SRZ_PLL_CFG_MODE_74_25_MHZ 0xA
+#define HDMI_SRZ_PLL_CFG_MODE_81_MHZ 0xB
+#define HDMI_SRZ_PLL_CFG_MODE_82_5_MHZ 0xC
+#define HDMI_SRZ_PLL_CFG_MODE_108_MHZ 0xD
+#define HDMI_SRZ_PLL_CFG_MODE_148_5_MHZ 0xE
+#define HDMI_SRZ_PLL_CFG_MODE_165_MHZ 0xF
+#define HDMI_SRZ_PLL_CFG_MODE_MASK 0xF
+#define HDMI_SRZ_PLL_CFG_MODE(x) (x << HDMI_SRZ_PLL_CFG_MODE_SHIFT)
+
+#define HDMI_SRZ_CTRL_POWER_DOWN (1 << 0)
+#define HDMI_SRZ_CTRL_EXTERNAL_DATA_EN (1 << 1)
+
+/* sysconf registers */
+#define HDMI_REJECTION_PLL_CONFIGURATION 0x0858 /* SYSTEM_CONFIG2534 */
+#define HDMI_REJECTION_PLL_STATUS 0x0948 /* SYSTEM_CONFIG2594 */
+
+#define REJECTION_PLL_HDMI_ENABLE_SHIFT 0
+#define REJECTION_PLL_HDMI_ENABLE_MASK (0x1 << REJECTION_PLL_HDMI_ENABLE_SHIFT)
+#define REJECTION_PLL_HDMI_PDIV_SHIFT 24
+#define REJECTION_PLL_HDMI_PDIV_MASK (0x7 << REJECTION_PLL_HDMI_PDIV_SHIFT)
+#define REJECTION_PLL_HDMI_NDIV_SHIFT 16
+#define REJECTION_PLL_HDMI_NDIV_MASK (0xFF << REJECTION_PLL_HDMI_NDIV_SHIFT)
+#define REJECTION_PLL_HDMI_MDIV_SHIFT 8
+#define REJECTION_PLL_HDMI_MDIV_MASK (0xFF << REJECTION_PLL_HDMI_MDIV_SHIFT)
+
+#define REJECTION_PLL_HDMI_REJ_PLL_LOCK (0x1 << 0)
+
+#define HDMI_TIMEOUT_PLL_LOCK 50 /*milliseconds */
+
+#define HDMI_WAIT_PLL_REJECTION_STATUS 1000
+
+/* pll mode structure
+ *
+ * A pointer to an array of these structures is passed to a TMDS (HDMI) output
+ * via the control interface to provide board and SoC specific
+ * configurations of the HDMI PHY. Each entry in the array specifies a hardware
+ * specific configuration for a given TMDS clock frequency range. The array
+ * should be terminated with an entry that has all fields set to zero.
+ *
+ * @min: Lower bound of TMDS clock frequency this entry applies to
+ * @max: Upper bound of TMDS clock frequency this entry applies to
+ * @mode: SoC specific register configuration
+ */
+struct pllmode {
+ u32 min;
+ u32 max;
+ u32 mode;
+};
+#define NB_PLL_MODE 7
+static struct pllmode pllmodes[NB_PLL_MODE] = {
+ {13500000, 13513500, HDMI_SRZ_PLL_CFG_MODE_13_5_MHZ},
+ {25174800, 25200000, HDMI_SRZ_PLL_CFG_MODE_25_2_MHZ},
+ {27000000, 27027000, HDMI_SRZ_PLL_CFG_MODE_27_MHZ},
+ {54000000, 54054000, HDMI_SRZ_PLL_CFG_MODE_54_MHZ},
+ {72000000, 74250000, HDMI_SRZ_PLL_CFG_MODE_74_25_MHZ},
+ {108000000, 108108000, HDMI_SRZ_PLL_CFG_MODE_108_MHZ},
+ {148351648, 297000000, HDMI_SRZ_PLL_CFG_MODE_148_5_MHZ}
+};
+
+#define NB_HDMI_PHY_CONFIG 5
+static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = {
+ {0, 40000000, {0x00101010, 0x00101010, 0x00101010, 0x02} },
+ {40000000, 140000000, {0x00111111, 0x00111111, 0x00111111, 0x02} },
+ {140000000, 160000000, {0x00131313, 0x00101010, 0x00101010, 0x02} },
+ {160000000, 250000000, {0x00131313, 0x00111111, 0x00111111, 0x03FE} },
+ {250000000, 300000000, {0x00151515, 0x00101010, 0x00101010, 0x03FE} },
+};
+
+/*
+ * Helper to write bit field
+ *
+ * @addr: register to update
+ * @val: value to write
+ * @mask: bit field mask to use
+ */
+static inline void reg_writemask(void __iomem *addr, u32 val, u32 mask)
+{
+ u32 old = readl(addr);
+
+ val = (val & mask) | (old & ~mask);
+ writel(val, addr);
+}
+
+/*
+ * Enable the old BCH/rejection PLL is now reused to provide the CLKPXPLL
+ * clock input to the new PHY PLL that generates the serializer clock
+ * (TMDS*10) and the TMDS clock which is now fed back into the HDMI
+ * formatter instead of the TMDS clock line from ClockGenB.
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ *
+ * Return -1 if error occurs
+ */
+static int enable_pll_rejection(struct sti_hdmi *hdmi)
+{
+ int inputclock;
+ u32 mdiv;
+ u32 ndiv;
+ u32 pdiv;
+ u32 mask;
+ u32 val;
+ int i;
+
+ DRM_DEBUG_DRIVER("\n");
+
+ inputclock = hdmi->mode.clock * 1000;
+
+ DRM_DEBUG_DRIVER("hdmi rejection pll input clock = %dHz\n", inputclock);
+
+ /* Force to power down the HDMI rejection PLL */
+ mask = REJECTION_PLL_HDMI_ENABLE_MASK;
+ val = 0x0 << REJECTION_PLL_HDMI_ENABLE_SHIFT;
+ reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION,
+ val, mask);
+
+ /* Check the HDMI rejection PLL is really down */
+ for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) {
+ val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS);
+ if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) == 0)
+ break;
+ }
+ if (i == HDMI_WAIT_PLL_REJECTION_STATUS) {
+ DRM_ERROR("hdmi rejection pll is not well powered down\n");
+ return -1;
+ }
+
+ /* Power up the HDMI rejection PLL */
+ /*
+ * Note: On this SoC (stiH416) we are forced to have the input clock
+ * be equal to the HDMI pixel clock.
+ *
+ * The values here have been suggested by validation however they are
+ * still provisional and subject to change.
+ *
+ * PLLout = (Fin*Mdiv) / ((2 * Ndiv) / 2^Pdiv)
+ */
+ if (inputclock < 50000000) {
+ /*
+ * For slower clocks we need to multiply more to keep the
+ * internal VCO frequency within the physical specification
+ * of the PLL.
+ */
+ pdiv = 4;
+ ndiv = 240;
+ mdiv = 30;
+ } else {
+ pdiv = 2;
+ ndiv = 60;
+ mdiv = 30;
+ }
+
+ mask = REJECTION_PLL_HDMI_PDIV_MASK |
+ REJECTION_PLL_HDMI_NDIV_MASK |
+ REJECTION_PLL_HDMI_MDIV_MASK | REJECTION_PLL_HDMI_ENABLE_MASK;
+ val = (pdiv << REJECTION_PLL_HDMI_PDIV_SHIFT) |
+ (ndiv << REJECTION_PLL_HDMI_NDIV_SHIFT) |
+ (mdiv << REJECTION_PLL_HDMI_MDIV_SHIFT) |
+ (0x1 << REJECTION_PLL_HDMI_ENABLE_SHIFT);
+ reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION,
+ val, mask);
+
+ /* Check the HDMI rejection PLL is really up */
+ for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) {
+ val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS);
+ if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) != 0)
+ break;
+ }
+ if (i == HDMI_WAIT_PLL_REJECTION_STATUS) {
+ DRM_ERROR("hdmi rejection pll is not well powered up\n");
+ return -1;
+ }
+
+ DRM_DEBUG_DRIVER("hdmi rejection pll locked\n");
+
+ return 0;
+}
+
+/*
+ * Disable the pll rejection
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ */
+static void disable_pll_rejection(struct sti_hdmi *hdmi)
+{
+ int i;
+ u32 val;
+ u32 mask;
+
+ DRM_DEBUG_DRIVER("\n");
+
+ mask = REJECTION_PLL_HDMI_ENABLE_MASK;
+ val = 0x0 << REJECTION_PLL_HDMI_ENABLE_SHIFT;
+ reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION,
+ val, mask);
+
+ /* Check the HDMI rejection PLL is really down */
+ for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) {
+ val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS);
+ if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) == 0)
+ break;
+ }
+ if (i == HDMI_WAIT_PLL_REJECTION_STATUS)
+ DRM_ERROR("hdmi rejection pll is not well powered down\n");
+ else
+ DRM_DEBUG_DRIVER("hdmi rejection pll is powered down\n");
+}
+
+/*
+ * Start hdmi phy macro cell tx3g0c55
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ *
+ * Return -1 if error occurs
+ */
+int sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi)
+{
+ u32 ckpxpll = hdmi->mode.clock * 1000;
+ u32 tmdsck;
+ u32 freqvco;
+ u32 pllctrl = 0;
+ u32 val;
+ int i;
+
+ if (enable_pll_rejection(hdmi))
+ return -1;
+
+ DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll);
+
+ /* TODO: manage DeepColor (30, 36 and 48 bits) and pixel repetition */
+
+ /* Assuming no pixel repetition and 24bits color */
+ tmdsck = ckpxpll;
+ pllctrl = 2 << HDMI_SRZ_PLL_CFG_NDIV_SHIFT;
+
+ /*
+ * Setup the PLL mode parameter based on the ckpxpll. If we haven't got
+ * a clock frequency supported by one of the specific PLL modes then we
+ * will end up using the generic mode (0) which only supports a 10x
+ * multiplier, hence only 24bit color.
+ */
+ for (i = 0; i < NB_PLL_MODE; i++) {
+ if (ckpxpll >= pllmodes[i].min && ckpxpll <= pllmodes[i].max)
+ pllctrl |= HDMI_SRZ_PLL_CFG_MODE(pllmodes[i].mode);
+ }
+
+ freqvco = tmdsck * 10;
+ if (freqvco <= 425000000UL)
+ pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_425MHZ);
+ else if (freqvco <= 850000000UL)
+ pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_850MHZ);
+ else if (freqvco <= 1700000000UL)
+ pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_1700MHZ);
+ else if (freqvco <= 2970000000UL)
+ pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_3000MHZ);
+ else {
+ DRM_ERROR("PHY serializer clock out of range\n");
+ goto err;
+ }
+
+ /*
+ * Configure and power up the PHY PLL
+ */
+ hdmi->event_received = false;
+ DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl);
+ writel(pllctrl, hdmi->regs + HDMI_SRZ_PLL_CFG);
+
+ /* wait PLL interrupt */
+ wait_event_interruptible_timeout(hdmi->wait_event,
+ hdmi->event_received == true,
+ msecs_to_jiffies
+ (HDMI_TIMEOUT_PLL_LOCK));
+
+ if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 0) {
+ DRM_ERROR("hdmi phy pll not locked\n");
+ goto err;
+ }
+
+ DRM_DEBUG_DRIVER("got PHY PLL Lock\n");
+
+ /*
+ * To configure the source termination and pre-emphasis appropriately
+ * for different high speed TMDS clock frequencies a phy configuration
+ * table must be provided, tailored to the SoC and board combination.
+ */
+ for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) {
+ if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) &&
+ (hdmiphy_config[i].max_tmds_freq >= tmdsck)) {
+ val = hdmiphy_config[i].config[0];
+ writel(val, hdmi->regs + HDMI_SRZ_TAP_1);
+ val = hdmiphy_config[i].config[1];
+ writel(val, hdmi->regs + HDMI_SRZ_TAP_2);
+ val = hdmiphy_config[i].config[2];
+ writel(val, hdmi->regs + HDMI_SRZ_TAP_3);
+ val = hdmiphy_config[i].config[3];
+ val |= HDMI_SRZ_CTRL_EXTERNAL_DATA_EN;
+ val &= ~HDMI_SRZ_CTRL_POWER_DOWN;
+ writel(val, hdmi->regs + HDMI_SRZ_CTRL);
+
+ DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x 0x%x\n",
+ hdmiphy_config[i].config[0],
+ hdmiphy_config[i].config[1],
+ hdmiphy_config[i].config[2],
+ hdmiphy_config[i].config[3]);
+ return 0;
+ }
+ }
+
+ /*
+ * Default, power up the serializer with no pre-emphasis or source
+ * termination.
+ */
+ writel(0x0, hdmi->regs + HDMI_SRZ_TAP_1);
+ writel(0x0, hdmi->regs + HDMI_SRZ_TAP_2);
+ writel(0x0, hdmi->regs + HDMI_SRZ_TAP_3);
+ writel(HDMI_SRZ_CTRL_EXTERNAL_DATA_EN, hdmi->regs + HDMI_SRZ_CTRL);
+
+ return 0;
+
+err:
+ disable_pll_rejection(hdmi);
+
+ return -1;
+}
+
+/*
+ * Stop hdmi phy macro cell tx3g0c55
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ */
+void sti_hdmi_tx3g0c55phy_stop(struct sti_hdmi *hdmi)
+{
+ DRM_DEBUG_DRIVER("\n");
+
+ hdmi->event_received = false;
+
+ writel(HDMI_SRZ_CTRL_POWER_DOWN, hdmi->regs + HDMI_SRZ_CTRL);
+ writel(HDMI_SRZ_PLL_CFG_POWER_DOWN, hdmi->regs + HDMI_SRZ_PLL_CFG);
+
+ /* wait PLL interrupt */
+ wait_event_interruptible_timeout(hdmi->wait_event,
+ hdmi->event_received == true,
+ msecs_to_jiffies
+ (HDMI_TIMEOUT_PLL_LOCK));
+
+ if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 1)
+ DRM_ERROR("hdmi phy pll not well disabled\n");
+ else
+ DRM_DEBUG_DRIVER("hdmi phy pll disabled\n");
+
+ disable_pll_rejection(hdmi);
+}
+
+/*
+ * Debugfs
+ */
+#define HDMI_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \
+ readl(hdmi->regs + reg))
+void sti_hdmi_tx3g0c55phy_show(struct sti_hdmi *hdmi, struct seq_file *m)
+{
+ HDMI_DBG_DUMP(HDMI_SRZ_PLL_CFG);
+ HDMI_DBG_DUMP(HDMI_SRZ_TAP_1);
+ HDMI_DBG_DUMP(HDMI_SRZ_TAP_2);
+ HDMI_DBG_DUMP(HDMI_SRZ_TAP_3);
+ HDMI_DBG_DUMP(HDMI_SRZ_CTRL);
+ seq_puts(m, "\n");
+}
diff --git a/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c b/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c
new file mode 100644
index 0000000..6e0bc2c
--- /dev/null
+++ b/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2013
+ * Author: Vincent Abriou <vincent.abriou at st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include "sti_hdmi.h"
+
+#define HDMI_SRZ_CFG 0x504
+#define HDMI_SRZ_PLL_CFG 0x510
+#define HDMI_SRZ_ICNTL 0x518
+#define HDMI_SRZ_CALCODE_EXT 0x520
+
+#define HDMI_SRZ_CFG_EN (1L<<0)
+#define HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT (1L<<1)
+#define HDMI_SRZ_CFG_EXTERNAL_DATA (1L<<16)
+#define HDMI_SRZ_CFG_RBIAS_EXT (1L<<17)
+#define HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION (1L<<18)
+#define HDMI_SRZ_CFG_EN_BIASRES_DETECTION (1L<<19)
+#define HDMI_SRZ_CFG_EN_SRC_TERMINATION (1L<<24)
+
+#define HDMI_SRZ_CFG_INTERNAL_MASK (HDMI_SRZ_CFG_EN | \
+ HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT | \
+ HDMI_SRZ_CFG_EXTERNAL_DATA | \
+ HDMI_SRZ_CFG_RBIAS_EXT | \
+ HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION | \
+ HDMI_SRZ_CFG_EN_BIASRES_DETECTION | \
+ HDMI_SRZ_CFG_EN_SRC_TERMINATION)
+
+#define PLL_CFG_EN (1L<<0)
+#define PLL_CFG_NDIV_SHIFT (8)
+#define PLL_CFG_IDF_SHIFT (16)
+#define PLL_CFG_ODF_SHIFT (24)
+
+#define ODF_DIV_1 (0)
+#define ODF_DIV_2 (1)
+#define ODF_DIV_4 (2)
+#define ODF_DIV_8 (3)
+
+#define HDMI_TIMEOUT_PLL_LOCK 50 /*milliseconds */
+
+struct plldividers_s {
+ uint32_t min;
+ uint32_t max;
+ uint32_t idf;
+ uint32_t odf;
+};
+
+/*
+ * Functional specification recommended values
+ */
+#define NB_PLL_MODE 5
+static struct plldividers_s plldividers[NB_PLL_MODE] = {
+ {0, 20000000, 1, ODF_DIV_8},
+ {20000000, 42500000, 2, ODF_DIV_8},
+ {42500000, 85000000, 4, ODF_DIV_4},
+ {85000000, 170000000, 8, ODF_DIV_2},
+ {170000000, 340000000, 16, ODF_DIV_1}
+};
+
+#define NB_HDMI_PHY_CONFIG 2
+static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = {
+ {0, 250000000, {0x0, 0x0, 0x0, 0x0} },
+ {250000000, 300000000, {0x1110, 0x0, 0x0, 0x0} },
+};
+
+/*
+ * Start hdmi phy macro cell tx3g4c28
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ *
+ * Return -1 if error occurs
+ */
+int sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi)
+{
+ u32 ckpxpll = hdmi->mode.clock * 1000;
+ u32 tmdsck;
+ u32 idf;
+ u32 odf;
+ u32 pllctrl = 0;
+ u32 val;
+ int i;
+ bool foundplldivides = false;
+
+ DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll);
+
+ for (i = 0; i < NB_PLL_MODE; i++) {
+ if (ckpxpll >= plldividers[i].min &&
+ ckpxpll < plldividers[i].max) {
+ idf = plldividers[i].idf;
+ odf = plldividers[i].odf;
+ foundplldivides = true;
+ break;
+ }
+ }
+
+ if (!foundplldivides) {
+ DRM_ERROR("input TMDS clock speed (%d) not supported\n",
+ ckpxpll);
+ goto err;
+ }
+
+ /* TODO: manage DeepColor (30, 36 and 48 bits) and pixel repetition */
+
+ /* Assuming no pixel repetition and 24bits color */
+ tmdsck = ckpxpll;
+ pllctrl |= 40 << PLL_CFG_NDIV_SHIFT;
+
+ if (tmdsck > 340000000) {
+ DRM_ERROR("output TMDS clock (%d) out of range\n", tmdsck);
+ goto err;
+ }
+
+ pllctrl |= idf << PLL_CFG_IDF_SHIFT;
+ pllctrl |= odf << PLL_CFG_ODF_SHIFT;
+
+ /*
+ * Configure and power up the PHY PLL
+ */
+ hdmi->event_received = false;
+ DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl);
+ writel((pllctrl | PLL_CFG_EN), hdmi->regs + HDMI_SRZ_PLL_CFG);
+
+ /* wait PLL interrupt */
+ wait_event_interruptible_timeout(hdmi->wait_event,
+ hdmi->event_received == true,
+ msecs_to_jiffies
+ (HDMI_TIMEOUT_PLL_LOCK));
+
+ if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 0) {
+ DRM_ERROR("hdmi phy pll not locked\n");
+ goto err;
+ }
+
+ DRM_DEBUG_DRIVER("got PHY PLL Lock\n");
+
+ val = (HDMI_SRZ_CFG_EN |
+ HDMI_SRZ_CFG_EXTERNAL_DATA |
+ HDMI_SRZ_CFG_EN_BIASRES_DETECTION |
+ HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION);
+
+ if (tmdsck > 165000000)
+ val |= HDMI_SRZ_CFG_EN_SRC_TERMINATION;
+
+ /*
+ * To configure the source termination and pre-emphasis appropriately
+ * for different high speed TMDS clock frequencies a phy configuration
+ * table must be provided, tailored to the SoC and board combination.
+ */
+ for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) {
+ if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) &&
+ (hdmiphy_config[i].max_tmds_freq >= tmdsck)) {
+ val |= (hdmiphy_config[i].config[0]
+ & ~HDMI_SRZ_CFG_INTERNAL_MASK);
+ writel(val, hdmi->regs + HDMI_SRZ_CFG);
+ val = hdmiphy_config[i].config[1];
+ writel(val, hdmi->regs + HDMI_SRZ_ICNTL);
+ val = hdmiphy_config[i].config[2];
+ writel(val, hdmi->regs + HDMI_SRZ_CALCODE_EXT);
+ DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x\n",
+ hdmiphy_config[i].config[0],
+ hdmiphy_config[i].config[1],
+ hdmiphy_config[i].config[2]);
+ return 0;
+ }
+ }
+
+ /*
+ * Default, power up the serializer with no pre-emphasis or
+ * output swing correction
+ */
+ writel(val, hdmi->regs + HDMI_SRZ_CFG);
+ writel(0x0, hdmi->regs + HDMI_SRZ_ICNTL);
+ writel(0x0, hdmi->regs + HDMI_SRZ_CALCODE_EXT);
+
+ return 0;
+
+err:
+ return -1;
+}
+
+/*
+ * Stop hdmi phy macro cell tx3g4c28
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ */
+void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi)
+{
+ int val = 0;
+
+ DRM_DEBUG_DRIVER("\n");
+
+ hdmi->event_received = false;
+
+ val = HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION;
+ val |= HDMI_SRZ_CFG_EN_BIASRES_DETECTION;
+ writel(val, hdmi->regs + HDMI_SRZ_CFG);
+ writel(0, hdmi->regs + HDMI_SRZ_PLL_CFG);
+
+ /* wait PLL interrupt */
+ wait_event_interruptible_timeout(hdmi->wait_event,
+ hdmi->event_received == true,
+ msecs_to_jiffies
+ (HDMI_TIMEOUT_PLL_LOCK));
+
+ if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 1)
+ DRM_ERROR("hdmi phy pll not well disabled\n");
+ else
+ DRM_DEBUG_DRIVER("hdmi phy pll disabled\n");
+}
+
+/*
+ * Debugfs
+ */
+#define HDMI_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \
+ readl(hdmi->regs + reg))
+void sti_hdmi_tx3g4c28phy_show(struct sti_hdmi *hdmi, struct seq_file *m)
+{
+ HDMI_DBG_DUMP(HDMI_SRZ_CFG);
+ HDMI_DBG_DUMP(HDMI_SRZ_PLL_CFG);
+ HDMI_DBG_DUMP(HDMI_SRZ_ICNTL);
+ HDMI_DBG_DUMP(HDMI_SRZ_CALCODE_EXT);
+ seq_puts(m, "\n");
+}
--
1.9.0
More information about the dri-devel
mailing list