aboutsummaryrefslogtreecommitdiff
path: root/drivers/mfd/mxc-hdmi-core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mfd/mxc-hdmi-core.c')
-rw-r--r--drivers/mfd/mxc-hdmi-core.c670
1 files changed, 670 insertions, 0 deletions
diff --git a/drivers/mfd/mxc-hdmi-core.c b/drivers/mfd/mxc-hdmi-core.c
new file mode 100644
index 00000000000..24afc502cea
--- /dev/null
+++ b/drivers/mfd/mxc-hdmi-core.c
@@ -0,0 +1,670 @@
+/*
+ * Copyright (C) 2011 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/spinlock.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/of.h>
+
+#include <linux/platform_device.h>
+#include <linux/regulator/machine.h>
+#include <asm/mach-types.h>
+
+#include <mach/clock.h>
+#include <mach/mxc_hdmi.h>
+#include <mach/ipu-v3.h>
+#include "../mxc/ipu3/ipu_prv.h"
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <linux/fsl_devices.h>
+
+struct mxc_hdmi_data {
+ struct platform_device *pdev;
+ unsigned long __iomem *reg_base;
+ unsigned long reg_phys_base;
+ struct device *dev;
+};
+
+static unsigned long hdmi_base;
+struct clk *isfr_clk;
+struct clk *iahb_clk;
+static unsigned int irq_enable_cnt;
+spinlock_t irq_spinlock;
+bool irq_initialized;
+bool irq_enabled;
+unsigned int sample_rate;
+unsigned long pixel_clk_rate;
+struct clk *pixel_clk;
+int hdmi_ratio;
+int mxc_hdmi_ipu_id;
+int mxc_hdmi_disp_id;
+
+u8 hdmi_readb(unsigned int reg)
+{
+ u8 value;
+
+ value = __raw_readb(hdmi_base + reg);
+
+ pr_debug("hdmi rd: 0x%04x = 0x%02x\n", reg, value);
+
+ return value;
+}
+
+#ifdef DEBUG
+static bool overflow_lo;
+static bool overflow_hi;
+
+bool hdmi_check_overflow(void)
+{
+ u8 val, lo, hi;
+
+ val = hdmi_readb(HDMI_IH_FC_STAT2);
+ lo = (val & HDMI_IH_FC_STAT2_LOW_PRIORITY_OVERFLOW) != 0;
+ hi = (val & HDMI_IH_FC_STAT2_HIGH_PRIORITY_OVERFLOW) != 0;
+
+ if ((lo != overflow_lo) || (hi != overflow_hi)) {
+ pr_debug("%s LowPriority=%d HighPriority=%d <=======================\n",
+ __func__, lo, hi);
+ overflow_lo = lo;
+ overflow_hi = hi;
+ return true;
+ }
+ return false;
+}
+#else
+bool hdmi_check_overflow(void)
+{
+ return false;
+}
+#endif
+
+void hdmi_writeb(u8 value, unsigned int reg)
+{
+ hdmi_check_overflow();
+ pr_debug("hdmi wr: 0x%04x = 0x%02x\n", reg, value);
+ __raw_writeb(value, hdmi_base + reg);
+ hdmi_check_overflow();
+}
+
+void hdmi_mask_writeb(u8 data, unsigned int reg, u8 shift, u8 mask)
+{
+ u8 value = hdmi_readb(reg) & ~mask;
+ value |= (data << shift) & mask;
+ hdmi_writeb(value, reg);
+}
+
+unsigned int hdmi_read4(unsigned int reg)
+{
+ /* read a four byte address from registers */
+ return (hdmi_readb(reg + 3) << 24) |
+ (hdmi_readb(reg + 2) << 16) |
+ (hdmi_readb(reg + 1) << 8) |
+ hdmi_readb(reg);
+}
+
+void hdmi_write4(unsigned int value, unsigned int reg)
+{
+ /* write a four byte address to hdmi regs */
+ hdmi_writeb(value & 0xff, reg);
+ hdmi_writeb((value >> 8) & 0xff, reg + 1);
+ hdmi_writeb((value >> 16) & 0xff, reg + 2);
+ hdmi_writeb((value >> 24) & 0xff, reg + 3);
+}
+
+void hdmi_irq_init()
+{
+ /* First time IRQ is initialized, set enable_cnt to 1,
+ * since IRQ starts out enabled after request_irq */
+ if (!irq_initialized) {
+ irq_enable_cnt = 1;
+ irq_initialized = true;
+ irq_enabled = true;
+ }
+}
+
+void hdmi_irq_enable(int irq)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&irq_spinlock, flags);
+
+ if (!irq_enabled) {
+ enable_irq(irq);
+ irq_enabled = true;
+ }
+
+ irq_enable_cnt++;
+
+ spin_unlock_irqrestore(&irq_spinlock, flags);
+}
+
+unsigned int hdmi_irq_disable(int irq)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&irq_spinlock, flags);
+
+ WARN_ON (irq_enable_cnt == 0);
+
+ irq_enable_cnt--;
+
+ /* Only disable HDMI IRQ if IAHB clk is off */
+ if ((irq_enable_cnt == 0) && (clk_get_usecount(iahb_clk) == 0)) {
+ disable_irq_nosync(irq);
+ irq_enabled = false;
+ spin_unlock_irqrestore(&irq_spinlock, flags);
+ return IRQ_DISABLE_SUCCEED;
+ }
+
+ spin_unlock_irqrestore(&irq_spinlock, flags);
+
+ return IRQ_DISABLE_FAIL;
+}
+
+static void initialize_hdmi_ih_mutes(void)
+{
+ u8 ih_mute;
+
+ /*
+ * Boot up defaults are:
+ * HDMI_IH_MUTE = 0x03 (disabled)
+ * HDMI_IH_MUTE_* = 0x00 (enabled)
+ */
+
+ /* Disable top level interrupt bits in HDMI block */
+ ih_mute = hdmi_readb(HDMI_IH_MUTE) |
+ HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT |
+ HDMI_IH_MUTE_MUTE_ALL_INTERRUPT;
+
+ hdmi_writeb(ih_mute, HDMI_IH_MUTE);
+
+ /* by default mask all interrupts */
+ hdmi_writeb(0xff, HDMI_VP_MASK);
+ hdmi_writeb(0xff, HDMI_FC_MASK0);
+ hdmi_writeb(0xff, HDMI_FC_MASK1);
+ hdmi_writeb(0xff, HDMI_FC_MASK2);
+ hdmi_writeb(0xff, HDMI_PHY_MASK0);
+ hdmi_writeb(0xff, HDMI_PHY_I2CM_INT_ADDR);
+ hdmi_writeb(0xff, HDMI_PHY_I2CM_CTLINT_ADDR);
+ hdmi_writeb(0xff, HDMI_AUD_INT);
+ hdmi_writeb(0xff, HDMI_AUD_SPDIFINT);
+ hdmi_writeb(0xff, HDMI_AUD_HBR_MASK);
+ hdmi_writeb(0xff, HDMI_GP_MASK);
+ hdmi_writeb(0xff, HDMI_A_APIINTMSK);
+ hdmi_writeb(0xff, HDMI_CEC_MASK);
+ hdmi_writeb(0xff, HDMI_I2CM_INT);
+ hdmi_writeb(0xff, HDMI_I2CM_CTLINT);
+
+ /* Disable interrupts in the IH_MUTE_* registers */
+ hdmi_writeb(0xff, HDMI_IH_MUTE_FC_STAT0);
+ hdmi_writeb(0xff, HDMI_IH_MUTE_FC_STAT1);
+ hdmi_writeb(0xff, HDMI_IH_MUTE_FC_STAT2);
+ hdmi_writeb(0xff, HDMI_IH_MUTE_AS_STAT0);
+ hdmi_writeb(0xff, HDMI_IH_MUTE_PHY_STAT0);
+ hdmi_writeb(0xff, HDMI_IH_MUTE_I2CM_STAT0);
+ hdmi_writeb(0xff, HDMI_IH_MUTE_CEC_STAT0);
+ hdmi_writeb(0xff, HDMI_IH_MUTE_VP_STAT0);
+ hdmi_writeb(0xff, HDMI_IH_MUTE_I2CMPHY_STAT0);
+ hdmi_writeb(0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+
+ /* Enable top level interrupt bits in HDMI block */
+ ih_mute &= ~(HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT |
+ HDMI_IH_MUTE_MUTE_ALL_INTERRUPT);
+ hdmi_writeb(ih_mute, HDMI_IH_MUTE);
+}
+
+static void hdmi_set_clock_regenerator_n(unsigned int value)
+{
+ u8 val;
+
+ hdmi_writeb(value & 0xff, HDMI_AUD_N1);
+ hdmi_writeb((value >> 8) & 0xff, HDMI_AUD_N2);
+ hdmi_writeb((value >> 16) & 0x0f, HDMI_AUD_N3);
+
+ /* nshift factor = 0 */
+ val = hdmi_readb(HDMI_AUD_CTS3);
+ val &= ~HDMI_AUD_CTS3_N_SHIFT_MASK;
+ hdmi_writeb(val, HDMI_AUD_CTS3);
+}
+
+static void hdmi_set_clock_regenerator_cts(unsigned int cts)
+{
+ u8 val;
+
+ /* Must be set/cleared first */
+ val = hdmi_readb(HDMI_AUD_CTS3);
+ val &= ~HDMI_AUD_CTS3_CTS_MANUAL;
+ hdmi_writeb(val, HDMI_AUD_CTS3);
+
+ hdmi_writeb(cts & 0xff, HDMI_AUD_CTS1);
+ hdmi_writeb((cts >> 8) & 0xff, HDMI_AUD_CTS2);
+ hdmi_writeb(((cts >> 16) & HDMI_AUD_CTS3_AUDCTS19_16_MASK) |
+ HDMI_AUD_CTS3_CTS_MANUAL, HDMI_AUD_CTS3);
+}
+
+static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk,
+ unsigned int ratio)
+{
+ unsigned int n = (128 * freq) / 1000;
+
+ switch (freq) {
+ case 32000:
+ if (pixel_clk == 25170000)
+ n = (ratio == 150) ? 9152 : 4576;
+ else if (pixel_clk == 27020000)
+ n = (ratio == 150) ? 8192 : 4096;
+ else if (pixel_clk == 74170000 || pixel_clk == 148350000)
+ n = 11648;
+ else
+ n = 4096;
+ break;
+
+ case 44100:
+ if (pixel_clk == 25170000)
+ n = 7007;
+ else if (pixel_clk == 74170000)
+ n = 17836;
+ else if (pixel_clk == 148350000)
+ n = (ratio == 150) ? 17836 : 8918;
+ else
+ n = 6272;
+ break;
+
+ case 48000:
+ if (pixel_clk == 25170000)
+ n = (ratio == 150) ? 9152 : 6864;
+ else if (pixel_clk == 27020000)
+ n = (ratio == 150) ? 8192 : 6144;
+ else if (pixel_clk == 74170000)
+ n = 11648;
+ else if (pixel_clk == 148350000)
+ n = (ratio == 150) ? 11648 : 5824;
+ else
+ n = 6144;
+ break;
+
+ case 88200:
+ n = hdmi_compute_n(44100, pixel_clk, ratio) * 2;
+ break;
+
+ case 96000:
+ n = hdmi_compute_n(48000, pixel_clk, ratio) * 2;
+ break;
+
+ case 176400:
+ n = hdmi_compute_n(44100, pixel_clk, ratio) * 4;
+ break;
+
+ case 192000:
+ n = hdmi_compute_n(48000, pixel_clk, ratio) * 4;
+ break;
+
+ default:
+ break;
+ }
+
+ return n;
+}
+
+static unsigned int hdmi_compute_cts(unsigned int freq, unsigned long pixel_clk,
+ unsigned int ratio)
+{
+ unsigned int cts = 0;
+ switch (freq) {
+ case 32000:
+ if (pixel_clk == 297000000) {
+ cts = 222750;
+ break;
+ }
+ case 48000:
+ case 96000:
+ case 192000:
+ switch (pixel_clk) {
+ case 25200000:
+ case 27000000:
+ case 54000000:
+ case 74250000:
+ case 148500000:
+ cts = pixel_clk / 1000;
+ break;
+ case 297000000:
+ cts = 247500;
+ break;
+ /*
+ * All other TMDS clocks are not supported by
+ * DWC_hdmi_tx. The TMDS clocks divided or
+ * multiplied by 1,001 coefficients are not
+ * supported.
+ */
+ default:
+ break;
+ }
+ break;
+ case 44100:
+ case 88200:
+ case 176400:
+ switch (pixel_clk) {
+ case 25200000:
+ cts = 28000;
+ break;
+ case 27000000:
+ cts = 30000;
+ break;
+ case 54000000:
+ cts = 60000;
+ break;
+ case 74250000:
+ cts = 82500;
+ break;
+ case 148500000:
+ cts = 165000;
+ break;
+ case 297000000:
+ cts = 247500;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ if (ratio == 100)
+ return cts;
+ else
+ return (cts * ratio) / 100;
+}
+
+static void hdmi_get_pixel_clk(void)
+{
+ struct ipu_soc *ipu;
+ unsigned long rate;
+
+ if (pixel_clk == NULL) {
+ ipu = ipu_get_soc(mxc_hdmi_ipu_id);
+ pixel_clk = clk_get(ipu->dev, "pixel_clk_0");
+ if (IS_ERR(pixel_clk)) {
+ pr_err("%s could not get pixel_clk_0\n", __func__);
+ return;
+ }
+ }
+
+ rate = clk_get_rate(pixel_clk);
+ if (rate != 0)
+ pixel_clk_rate = rate;
+}
+
+static void hdmi_set_clk_regenerator(void)
+{
+ unsigned int clk_n, clk_cts;
+
+ clk_n = hdmi_compute_n(sample_rate, pixel_clk_rate, hdmi_ratio);
+ clk_cts = hdmi_compute_cts(sample_rate, pixel_clk_rate, hdmi_ratio);
+
+ if (clk_cts == 0) {
+ pr_debug("%s: pixel clock not supported: %d\n",
+ __func__, (int)pixel_clk_rate);
+ return;
+ }
+
+ clk_enable(isfr_clk);
+ clk_enable(iahb_clk);
+
+ pr_debug("%s: samplerate=%d ratio=%d pixelclk=%d N=%d cts=%d\n",
+ __func__, sample_rate, hdmi_ratio, (int)pixel_clk_rate,
+ clk_n, clk_cts);
+
+ hdmi_set_clock_regenerator_n(clk_n);
+ hdmi_set_clock_regenerator_cts(clk_cts);
+
+ clk_disable(iahb_clk);
+ clk_disable(isfr_clk);
+}
+
+/* Need to run this before phy is enabled the first time to prevent
+ * overflow condition in HDMI_IH_FC_STAT2 */
+void hdmi_init_clk_regenerator(void)
+{
+ if (pixel_clk_rate == 0) {
+ pixel_clk_rate = 74250000;
+ hdmi_set_clk_regenerator();
+ }
+}
+
+void hdmi_clk_regenerator_update_pixel_clock(void)
+{
+ /* Get pixel clock from ipu */
+ hdmi_get_pixel_clk();
+ hdmi_set_clk_regenerator();
+}
+
+void hdmi_set_sample_rate(unsigned int rate)
+{
+ sample_rate = rate;
+ hdmi_set_clk_regenerator();
+}
+
+static void hdmi_init(int ipu_id, int disp_id)
+{
+ int hdmi_mux_setting;
+
+ if ((ipu_id > 1) || (ipu_id < 0)) {
+ printk(KERN_ERR"Invalid IPU select for HDMI: %d. Set to 0\n",
+ ipu_id);
+ ipu_id = 0;
+ }
+
+ if ((disp_id > 1) || (disp_id < 0)) {
+ printk(KERN_ERR"Invalid DI select for HDMI: %d. Set to 0\n",
+ disp_id);
+ disp_id = 0;
+ }
+
+ /* Configure the connection between IPU1/2 and HDMI */
+ hdmi_mux_setting = 2*ipu_id + disp_id;
+
+ /* GPR3, bits 2-3 = HDMI_MUX_CTL */
+ /*mxc_iomux_set_gpr_register(3, 2, 2, hdmi_mux_setting);*/
+}
+
+static struct fsl_mxc_hdmi_platform_data hdmi_vdata = {
+ .init = hdmi_init,
+};
+
+static int mxc_hdmi_core_probe(struct platform_device *pdev)
+{
+ struct fsl_mxc_hdmi_core_platform_data *pdata = pdev->dev.platform_data;
+ struct mxc_hdmi_data *hdmi_data;
+ struct resource *res;
+ int ret = 0;
+ struct platform_device_info pdevinfo_hdmi_v;
+
+#ifdef DEBUG
+ overflow_lo = false;
+ overflow_hi = false;
+#endif
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOENT;
+
+ hdmi_data = kzalloc(sizeof(struct mxc_hdmi_data), GFP_KERNEL);
+ if (!hdmi_data) {
+ dev_err(&pdev->dev, "Couldn't allocate mxc hdmi mfd device\n");
+ return -ENOMEM;
+ }
+ hdmi_data->pdev = pdev;
+
+ pixel_clk = NULL;
+ sample_rate = 48000;
+ pixel_clk_rate = 0;
+ hdmi_ratio = 100;
+
+ irq_enable_cnt = 0;
+ irq_initialized = false;
+ irq_enabled = true;
+ spin_lock_init(&irq_spinlock);
+
+ isfr_clk = clk_get(&hdmi_data->pdev->dev, "hdmi_isfr_clk");
+ if (IS_ERR(isfr_clk)) {
+ ret = PTR_ERR(isfr_clk);
+ dev_err(&hdmi_data->pdev->dev,
+ "Unable to get HDMI isfr clk: %d\n", ret);
+ goto eclkg;
+ }
+
+ ret = clk_enable(isfr_clk);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Cannot enable HDMI clock: %d\n", ret);
+ goto eclke;
+ }
+
+ pr_debug("%s isfr_clk:%d\n", __func__,
+ (int)clk_get_rate(isfr_clk));
+
+ iahb_clk = clk_get(&hdmi_data->pdev->dev, "hdmi_iahb_clk");
+ if (IS_ERR(iahb_clk)) {
+ ret = PTR_ERR(iahb_clk);
+ dev_err(&hdmi_data->pdev->dev,
+ "Unable to get HDMI iahb clk: %d\n", ret);
+ goto eclkg2;
+ }
+
+ ret = clk_enable(iahb_clk);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Cannot enable HDMI clock: %d\n", ret);
+ goto eclke2;
+ }
+
+ hdmi_data->reg_phys_base = res->start;
+ if (!request_mem_region(res->start, resource_size(res),
+ dev_name(&pdev->dev))) {
+ dev_err(&pdev->dev, "request_mem_region failed\n");
+ ret = -EBUSY;
+ goto emem;
+ }
+
+ hdmi_data->reg_base = ioremap(res->start, resource_size(res));
+ if (!hdmi_data->reg_base) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto eirq;
+ }
+ hdmi_base = (unsigned long)hdmi_data->reg_base;
+
+ pr_debug("\n%s hdmi hw base = 0x%08x\n\n", __func__, (int)res->start);
+
+ if (pdata) {
+ mxc_hdmi_ipu_id = pdata->ipu_id;
+ mxc_hdmi_disp_id = pdata->disp_id;
+ } else {
+ of_property_read_u32(pdev->dev.of_node, "ipu", &mxc_hdmi_ipu_id);
+ of_property_read_u32(pdev->dev.of_node, "di", &mxc_hdmi_disp_id);
+ }
+
+ initialize_hdmi_ih_mutes();
+
+ /* Disable HDMI clocks until video/audio sub-drivers are initialized */
+ clk_disable(isfr_clk);
+ clk_disable(iahb_clk);
+
+ /* Replace platform data coming in with a local struct */
+ platform_set_drvdata(pdev, hdmi_data);
+
+ /* register hdmi video */
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ pdevinfo_hdmi_v.name = "mxc_hdmi";
+ pdevinfo_hdmi_v.id = pdev->id;
+ pdevinfo_hdmi_v.res = res;
+ pdevinfo_hdmi_v.num_res = 1;
+ pdevinfo_hdmi_v.data = &hdmi_vdata;
+ pdevinfo_hdmi_v.size_data = sizeof(hdmi_vdata);
+ pdevinfo_hdmi_v.dma_mask = DMA_BIT_MASK(32);
+ pdevinfo_hdmi_v.parent = &pdev->dev;
+ platform_device_register_full(&pdevinfo_hdmi_v);
+
+ return ret;
+
+eirq:
+ release_mem_region(res->start, resource_size(res));
+emem:
+ clk_disable(iahb_clk);
+eclke2:
+ clk_put(iahb_clk);
+eclkg2:
+ clk_disable(isfr_clk);
+eclke:
+ clk_put(isfr_clk);
+eclkg:
+ kfree(hdmi_data);
+ return ret;
+}
+
+
+static int __exit mxc_hdmi_core_remove(struct platform_device *pdev)
+{
+ struct mxc_hdmi_data *hdmi_data = platform_get_drvdata(pdev);
+ struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ iounmap(hdmi_data->reg_base);
+ release_mem_region(res->start, resource_size(res));
+
+ kfree(hdmi_data);
+
+ return 0;
+}
+
+static const struct of_device_id mxc_hdmi_core_dt_ids[] = {
+ { .compatible = "fsl,imx6q-hdmi-core", },
+ { /* sentinel */ }
+};
+
+static struct platform_driver mxc_hdmi_core_driver = {
+ .driver = {
+ .name = "mxc_hdmi_core",
+ .owner = THIS_MODULE,
+ .of_match_table = mxc_hdmi_core_dt_ids,
+ },
+ .remove = __exit_p(mxc_hdmi_core_remove),
+};
+
+static int __init mxc_hdmi_core_init(void)
+{
+ return platform_driver_probe(&mxc_hdmi_core_driver,
+ mxc_hdmi_core_probe);
+}
+
+static void __exit mxc_hdmi_core_exit(void)
+{
+ platform_driver_unregister(&mxc_hdmi_core_driver);
+}
+
+subsys_initcall(mxc_hdmi_core_init);
+module_exit(mxc_hdmi_core_exit);
+
+MODULE_DESCRIPTION("Core driver for Freescale i.Mx on-chip HDMI");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");