| /* |
| * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. |
| |
| * The code contained herein is licensed under the GNU General Public |
| * License. You may obtain a copy of the GNU General Public License |
| * Version 2 or later at the following locations: |
| * |
| * http://www.opensource.org/licenses/gpl-license.html |
| * http://www.gnu.org/copyleft/gpl.html |
| */ |
| #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/platform_device.h> |
| #include <linux/regulator/machine.h> |
| #include <asm/mach-types.h> |
| |
| #include <video/mxc_hdmi.h> |
| #include <linux/ipu-v3.h> |
| #include <video/mxc_edid.h> |
| #include "../mxc/ipu3/ipu_prv.h" |
| #include <linux/mfd/mxc-hdmi-core.h> |
| #include <linux/of_device.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/mfd/mxc-hdmi-core.h> |
| |
| struct mxc_hdmi_data { |
| struct platform_device *pdev; |
| unsigned long __iomem *reg_base; |
| unsigned long reg_phys_base; |
| struct device *dev; |
| }; |
| |
| static void __iomem *hdmi_base; |
| static struct clk *isfr_clk; |
| static struct clk *iahb_clk; |
| static struct clk *mipi_core_clk; |
| static spinlock_t irq_spinlock; |
| static spinlock_t edid_spinlock; |
| static unsigned int sample_rate; |
| static unsigned long pixel_clk_rate; |
| static struct clk *pixel_clk; |
| static int hdmi_ratio; |
| int mxc_hdmi_ipu_id; |
| int mxc_hdmi_disp_id; |
| static struct mxc_edid_cfg hdmi_core_edid_cfg; |
| static int hdmi_core_init; |
| static unsigned int hdmi_dma_running; |
| static struct snd_pcm_substream *hdmi_audio_stream_playback; |
| static unsigned int hdmi_cable_state; |
| static unsigned int hdmi_blank_state; |
| static unsigned int hdmi_abort_state; |
| static spinlock_t hdmi_audio_lock, hdmi_blank_state_lock, hdmi_cable_state_lock; |
| |
| unsigned int hdmi_set_cable_state(unsigned int state) |
| { |
| unsigned long flags; |
| struct snd_pcm_substream *substream = hdmi_audio_stream_playback; |
| |
| spin_lock_irqsave(&hdmi_cable_state_lock, flags); |
| hdmi_cable_state = state; |
| spin_unlock_irqrestore(&hdmi_cable_state_lock, flags); |
| |
| #ifndef CONFIG_MFD_MXC_HDMI_ANDROID |
| if (check_hdmi_state() && substream && hdmi_abort_state) { |
| hdmi_abort_state = 0; |
| substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START); |
| } |
| #endif |
| return 0; |
| } |
| EXPORT_SYMBOL(hdmi_set_cable_state); |
| |
| unsigned int hdmi_set_blank_state(unsigned int state) |
| { |
| unsigned long flags; |
| struct snd_pcm_substream *substream = hdmi_audio_stream_playback; |
| |
| spin_lock_irqsave(&hdmi_blank_state_lock, flags); |
| hdmi_blank_state = state; |
| spin_unlock_irqrestore(&hdmi_blank_state_lock, flags); |
| |
| #ifndef CONFIG_MFD_MXC_HDMI_ANDROID |
| if (check_hdmi_state() && substream && hdmi_abort_state) { |
| hdmi_abort_state = 0; |
| substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START); |
| } |
| #endif |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(hdmi_set_blank_state); |
| |
| #ifdef CONFIG_SND_SOC_IMX_HDMI_DMA |
| static void hdmi_audio_abort_stream(struct snd_pcm_substream *substream) |
| { |
| unsigned long flags; |
| |
| snd_pcm_stream_lock_irqsave(substream, flags); |
| |
| #ifndef CONFIG_MFD_MXC_HDMI_ANDROID |
| if (snd_pcm_running(substream)) { |
| hdmi_abort_state = 1; |
| substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP); |
| } |
| #else |
| if (snd_pcm_running(substream)) |
| snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); |
| #endif |
| |
| snd_pcm_stream_unlock_irqrestore(substream, flags); |
| } |
| |
| int mxc_hdmi_abort_stream(void) |
| { |
| unsigned long flags; |
| spin_lock_irqsave(&hdmi_audio_lock, flags); |
| if (hdmi_audio_stream_playback) |
| hdmi_audio_abort_stream(hdmi_audio_stream_playback); |
| spin_unlock_irqrestore(&hdmi_audio_lock, flags); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(mxc_hdmi_abort_stream); |
| #else |
| int mxc_hdmi_abort_stream(void) |
| { |
| return 0; |
| } |
| EXPORT_SYMBOL(mxc_hdmi_abort_stream); |
| #endif |
| |
| int check_hdmi_state(void) |
| { |
| unsigned long flags1, flags2; |
| unsigned int ret; |
| |
| spin_lock_irqsave(&hdmi_cable_state_lock, flags1); |
| spin_lock_irqsave(&hdmi_blank_state_lock, flags2); |
| |
| ret = hdmi_cable_state && hdmi_blank_state; |
| |
| spin_unlock_irqrestore(&hdmi_blank_state_lock, flags2); |
| spin_unlock_irqrestore(&hdmi_cable_state_lock, flags1); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(check_hdmi_state); |
| |
| #ifdef CONFIG_SND_SOC_IMX_HDMI_DMA |
| int mxc_hdmi_register_audio(struct snd_pcm_substream *substream) |
| { |
| unsigned long flags, flags1; |
| int ret = 0; |
| |
| if (!substream) |
| return -EINVAL; |
| |
| snd_pcm_stream_lock_irqsave(substream, flags); |
| |
| if (check_hdmi_state()) { |
| spin_lock_irqsave(&hdmi_audio_lock, flags1); |
| if (hdmi_audio_stream_playback) { |
| pr_err("%s unconsist hdmi auido stream!\n", __func__); |
| ret = -EINVAL; |
| } |
| hdmi_audio_stream_playback = substream; |
| hdmi_abort_state = 0; |
| spin_unlock_irqrestore(&hdmi_audio_lock, flags1); |
| } else |
| ret = -EINVAL; |
| |
| snd_pcm_stream_unlock_irqrestore(substream, flags); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(mxc_hdmi_register_audio); |
| #endif |
| |
| void mxc_hdmi_unregister_audio(struct snd_pcm_substream *substream) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&hdmi_audio_lock, flags); |
| hdmi_audio_stream_playback = NULL; |
| hdmi_abort_state = 0; |
| spin_unlock_irqrestore(&hdmi_audio_lock, flags); |
| } |
| EXPORT_SYMBOL(mxc_hdmi_unregister_audio); |
| |
| u8 hdmi_readb(unsigned int reg) |
| { |
| u8 value; |
| |
| value = __raw_readb(hdmi_base + reg); |
| |
| return value; |
| } |
| EXPORT_SYMBOL(hdmi_readb); |
| |
| #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 |
| EXPORT_SYMBOL(hdmi_check_overflow); |
| |
| void hdmi_writeb(u8 value, unsigned int reg) |
| { |
| hdmi_check_overflow(); |
| __raw_writeb(value, hdmi_base + reg); |
| hdmi_check_overflow(); |
| } |
| EXPORT_SYMBOL(hdmi_writeb); |
| |
| 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); |
| } |
| EXPORT_SYMBOL(hdmi_mask_writeb); |
| |
| 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); |
| } |
| EXPORT_SYMBOL(hdmi_read4); |
| |
| 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); |
| } |
| EXPORT_SYMBOL(hdmi_write4); |
| |
| 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; |
| |
| if (!hdmi_dma_running) { |
| hdmi_writeb(value & 0xff, HDMI_AUD_N1); |
| hdmi_writeb(0, HDMI_AUD_N2); |
| hdmi_writeb(0, HDMI_AUD_N3); |
| } |
| |
| 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; |
| |
| if (!hdmi_dma_running) { |
| hdmi_writeb(cts & 0xff, HDMI_AUD_CTS1); |
| hdmi_writeb(0, HDMI_AUD_CTS2); |
| hdmi_writeb(0, HDMI_AUD_CTS3); |
| } |
| |
| /* 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 == 25174000) |
| 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 if (pixel_clk == 297000000) |
| n = (ratio == 150) ? 6144 : 3072; |
| else |
| n = 4096; |
| break; |
| |
| case 44100: |
| if (pixel_clk == 25174000) |
| n = 7007; |
| else if (pixel_clk == 74170000) |
| n = 17836; |
| else if (pixel_clk == 148350000) |
| n = (ratio == 150) ? 17836 : 8918; |
| else if (pixel_clk == 297000000) |
| n = (ratio == 150) ? 9408 : 4704; |
| else |
| n = 6272; |
| break; |
| |
| case 48000: |
| if (pixel_clk == 25174000) |
| 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 if (pixel_clk == 297000000) |
| n = (ratio == 150) ? 10240 : 5120; |
| 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; |
| } else if (pixel_clk == 25174000) { |
| cts = 28125; |
| 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; |
| case 25174000: |
| cts = 28125l; |
| 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 25174000: |
| cts = 31250; |
| 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_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; |
| } |
| |
| 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_cts(clk_cts); |
| hdmi_set_clock_regenerator_n(clk_n); |
| } |
| |
| static int hdmi_core_get_of_property(struct platform_device *pdev) |
| { |
| struct device_node *np = pdev->dev.of_node; |
| int err; |
| int ipu_id, disp_id; |
| |
| err = of_property_read_u32(np, "ipu_id", &ipu_id); |
| if (err) { |
| dev_dbg(&pdev->dev, "get of property ipu_id fail\n"); |
| return err; |
| } |
| err = of_property_read_u32(np, "disp_id", &disp_id); |
| if (err) { |
| dev_dbg(&pdev->dev, "get of property disp_id fail\n"); |
| return err; |
| } |
| |
| mxc_hdmi_ipu_id = ipu_id; |
| mxc_hdmi_disp_id = disp_id; |
| |
| return err; |
| } |
| |
| /* 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(); |
| } |
| } |
| EXPORT_SYMBOL(hdmi_init_clk_regenerator); |
| |
| void hdmi_clk_regenerator_update_pixel_clock(u32 pixclock) |
| { |
| |
| if (!pixclock) |
| return; |
| /* Translate pixel clock in ps (pico seconds) to Hz */ |
| pixel_clk_rate = PICOS2KHZ(pixclock) * 1000UL; |
| hdmi_set_clk_regenerator(); |
| } |
| EXPORT_SYMBOL(hdmi_clk_regenerator_update_pixel_clock); |
| |
| void hdmi_set_dma_mode(unsigned int dma_running) |
| { |
| hdmi_dma_running = dma_running; |
| hdmi_set_clk_regenerator(); |
| } |
| EXPORT_SYMBOL(hdmi_set_dma_mode); |
| |
| void hdmi_set_sample_rate(unsigned int rate) |
| { |
| sample_rate = rate; |
| } |
| EXPORT_SYMBOL(hdmi_set_sample_rate); |
| |
| void hdmi_set_edid_cfg(struct mxc_edid_cfg *cfg) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&edid_spinlock, flags); |
| memcpy(&hdmi_core_edid_cfg, cfg, sizeof(struct mxc_edid_cfg)); |
| spin_unlock_irqrestore(&edid_spinlock, flags); |
| } |
| EXPORT_SYMBOL(hdmi_set_edid_cfg); |
| |
| void hdmi_get_edid_cfg(struct mxc_edid_cfg *cfg) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&edid_spinlock, flags); |
| memcpy(cfg, &hdmi_core_edid_cfg, sizeof(struct mxc_edid_cfg)); |
| spin_unlock_irqrestore(&edid_spinlock, flags); |
| } |
| EXPORT_SYMBOL(hdmi_get_edid_cfg); |
| |
| void hdmi_set_registered(int registered) |
| { |
| hdmi_core_init = registered; |
| } |
| EXPORT_SYMBOL(hdmi_set_registered); |
| |
| int hdmi_get_registered(void) |
| { |
| return hdmi_core_init; |
| } |
| EXPORT_SYMBOL(hdmi_get_registered); |
| |
| static int mxc_hdmi_core_probe(struct platform_device *pdev) |
| { |
| struct mxc_hdmi_data *hdmi_data; |
| struct resource *res; |
| unsigned long flags; |
| int ret = 0; |
| |
| #ifdef DEBUG |
| overflow_lo = false; |
| overflow_hi = false; |
| #endif |
| |
| hdmi_core_init = 0; |
| hdmi_dma_running = 0; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!res) |
| return -ENOENT; |
| |
| ret = hdmi_core_get_of_property(pdev); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "get hdmi of property fail\n"); |
| return -ENOENT; |
| } |
| |
| hdmi_data = devm_kzalloc(&pdev->dev, 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; |
| |
| spin_lock_init(&irq_spinlock); |
| spin_lock_init(&edid_spinlock); |
| |
| |
| spin_lock_init(&hdmi_cable_state_lock); |
| spin_lock_init(&hdmi_blank_state_lock); |
| spin_lock_init(&hdmi_audio_lock); |
| |
| spin_lock_irqsave(&hdmi_cable_state_lock, flags); |
| hdmi_cable_state = 0; |
| spin_unlock_irqrestore(&hdmi_cable_state_lock, flags); |
| |
| spin_lock_irqsave(&hdmi_blank_state_lock, flags); |
| hdmi_blank_state = 0; |
| spin_unlock_irqrestore(&hdmi_blank_state_lock, flags); |
| |
| spin_lock_irqsave(&hdmi_audio_lock, flags); |
| hdmi_audio_stream_playback = NULL; |
| hdmi_abort_state = 0; |
| spin_unlock_irqrestore(&hdmi_audio_lock, flags); |
| |
| mipi_core_clk = clk_get(&hdmi_data->pdev->dev, "mipi_core"); |
| if (IS_ERR(mipi_core_clk)) { |
| ret = PTR_ERR(mipi_core_clk); |
| dev_err(&hdmi_data->pdev->dev, |
| "Unable to get mipi core clk: %d\n", ret); |
| goto eclkg; |
| } |
| |
| ret = clk_prepare_enable(mipi_core_clk); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Cannot enable mipi core clock: %d\n", ret); |
| goto eclke; |
| } |
| |
| isfr_clk = clk_get(&hdmi_data->pdev->dev, "hdmi_isfr"); |
| 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 eclkg1; |
| } |
| |
| ret = clk_prepare_enable(isfr_clk); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Cannot enable HDMI clock: %d\n", ret); |
| goto eclke1; |
| } |
| |
| pr_debug("%s isfr_clk:%d\n", __func__, |
| (int)clk_get_rate(isfr_clk)); |
| |
| iahb_clk = clk_get(&hdmi_data->pdev->dev, "hdmi_iahb"); |
| 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_prepare_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 = hdmi_data->reg_base; |
| |
| pr_debug("\n%s hdmi hw base = 0x%08x\n\n", __func__, (int)res->start); |
| |
| initialize_hdmi_ih_mutes(); |
| |
| /* Disable HDMI clocks until video/audio sub-drivers are initialized */ |
| clk_disable_unprepare(isfr_clk); |
| clk_disable_unprepare(iahb_clk); |
| clk_disable_unprepare(mipi_core_clk); |
| |
| /* Replace platform data coming in with a local struct */ |
| platform_set_drvdata(pdev, hdmi_data); |
| |
| return ret; |
| |
| eirq: |
| release_mem_region(res->start, resource_size(res)); |
| emem: |
| clk_disable_unprepare(iahb_clk); |
| eclke2: |
| clk_put(iahb_clk); |
| eclkg2: |
| clk_disable_unprepare(isfr_clk); |
| eclke1: |
| clk_put(isfr_clk); |
| eclkg1: |
| clk_disable_unprepare(mipi_core_clk); |
| eclke: |
| clk_put(mipi_core_clk); |
| eclkg: |
| 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)); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id imx_hdmi_dt_ids[] = { |
| { .compatible = "fsl,imx6q-hdmi-core", }, |
| { .compatible = "fsl,imx6dl-hdmi-core", }, |
| { /* sentinel */ } |
| }; |
| |
| static struct platform_driver mxc_hdmi_core_driver = { |
| .driver = { |
| .name = "mxc_hdmi_core", |
| .of_match_table = imx_hdmi_dt_ids, |
| .owner = THIS_MODULE, |
| }, |
| .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"); |