From 6a074b1c98af12fc2e375f65f810410137b5e953 Mon Sep 17 00:00:00 2001 From: Danny Nold Date: Fri, 14 Oct 2011 13:43:32 -0500 Subject: [PATCH] ENGR00160061 - MXC HDMI: Optimize HDMI clock management - Ensure HDMI clocks are disabled when leaving HDMI core probe function. - Create HDMI core api to allow HDMI sub-drivers to init, enable, and disable the HDMI IRQ. Required to optimally manage HDMI clocks, allow IAHB to be disabled, and still have video and audio sub-drivers able to receive interrupts. - Update code to adjust for decoupled ISFR and IAHB clocks. - Disable IAHB clocks whenever HDMI not plugged in. Signed-off-by: Danny Nold --- drivers/mfd/mxc-hdmi-core.c | 87 +++++++++++++- drivers/video/mxc_hdmi.c | 186 ++++++++++++++++++++++-------- include/linux/mfd/mxc-hdmi-core.h | 6 + 3 files changed, 231 insertions(+), 48 deletions(-) diff --git a/drivers/mfd/mxc-hdmi-core.c b/drivers/mfd/mxc-hdmi-core.c index 0e59dafd0a3b..3fe3d1e1f9f0 100644 --- a/drivers/mfd/mxc-hdmi-core.c +++ b/drivers/mfd/mxc-hdmi-core.c @@ -25,11 +25,15 @@ #include #include #include +#include +#include +#include #include #include #include +#include #include #include @@ -42,7 +46,11 @@ struct mxc_hdmi_data { }; static unsigned long hdmi_base; - +struct clk *iahb_clk; +static unsigned int irq_enable_cnt; +spinlock_t irq_spinlock; +bool irq_initialized; +bool irq_enabled; int mxc_hdmi_pixel_clk; int mxc_hdmi_ratio; @@ -88,6 +96,56 @@ void hdmi_write4(unsigned int value, unsigned int reg) 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; @@ -143,6 +201,11 @@ static int mxc_hdmi_core_probe(struct platform_device *pdev) } hdmi_data->pdev = pdev; + irq_enable_cnt = 0; + irq_initialized = false; + irq_enabled = true; + spin_lock_init(&irq_spinlock); + hdmi_data->isfr_clk = clk_get(&hdmi_data->pdev->dev, "hdmi_isfr_clk"); if (IS_ERR(hdmi_data->isfr_clk)) { ret = PTR_ERR(hdmi_data->isfr_clk); @@ -160,6 +223,20 @@ static int mxc_hdmi_core_probe(struct platform_device *pdev) pr_debug("%s isfr_clk:%d\n", __func__, (int)clk_get_rate(hdmi_data->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))) { @@ -180,6 +257,10 @@ static int mxc_hdmi_core_probe(struct platform_device *pdev) initialize_hdmi_ih_mutes(); + /* Disable HDMI clocks until video/audio sub-drivers are initialized */ + clk_disable(hdmi_data->isfr_clk); + clk_disable(iahb_clk); + /* Replace platform data coming in with a local struct */ platform_set_drvdata(pdev, hdmi_data); @@ -188,6 +269,10 @@ static int mxc_hdmi_core_probe(struct platform_device *pdev) eirq: release_mem_region(res->start, resource_size(res)); emem: + clk_disable(iahb_clk); +eclke2: + clk_put(iahb_clk); +eclkg2: clk_disable(hdmi_data->isfr_clk); eclke: clk_put(hdmi_data->isfr_clk); diff --git a/drivers/video/mxc_hdmi.c b/drivers/video/mxc_hdmi.c index 1c1009c027b3..ca3f7fa42016 100644 --- a/drivers/video/mxc_hdmi.c +++ b/drivers/video/mxc_hdmi.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -125,7 +126,8 @@ struct mxc_hdmi { struct platform_device *core_pdev; struct mxc_dispdrv_entry *disp_mxc_hdmi; struct fb_info *fbi; - struct clk *hdmi_clk; + struct clk *hdmi_isfr_clk; + struct clk *hdmi_iahb_clk; struct delayed_work det_work; struct notifier_block nb; @@ -137,6 +139,7 @@ struct mxc_hdmi { bool need_mode_change; bool cable_plugin; u8 latest_intr_stat; + bool irq_enabled; }; struct i2c_client *hdmi_i2c; @@ -931,20 +934,18 @@ void hdmi_phy_init(struct mxc_hdmi *hdmi, unsigned char de) { u8 val; - val = hdmi_readb(HDMI_PHY_CONF0); /* set the DE polarity */ - val |= (de << HDMI_PHY_CONF0_SELDATAENPOL_OFFSET) & + val = (de << HDMI_PHY_CONF0_SELDATAENPOL_OFFSET) & HDMI_PHY_CONF0_SELDATAENPOL_MASK; + /* set ENHPDRXSENSE to 1 */ + val |= HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE; /* set the interface control to 0 */ val |= (0 << HDMI_PHY_CONF0_SELDIPIF_OFFSET) & HDMI_PHY_CONF0_SELDIPIF_MASK; /* enable TMDS output */ val |= (1 << HDMI_PHY_CONF0_ENTMDS_OFFSET) & HDMI_PHY_CONF0_ENTMDS_MASK; - hdmi_writeb(val, HDMI_PHY_CONF0); - /* PHY power enable */ - val = hdmi_readb(HDMI_PHY_CONF0); val |= (1 << HDMI_PHY_CONF0_PDZ_OFFSET) & HDMI_PHY_CONF0_PDZ_MASK; hdmi_writeb(val, HDMI_PHY_CONF0); @@ -1267,6 +1268,8 @@ static void mxc_hdmi_rx_powerup(struct mxc_hdmi *hdmi) dev_dbg(&hdmi->pdev->dev, "rx power up\n"); + clk_enable(hdmi->hdmi_iahb_clk); + /* Enable HDMI PHY - Set PDDQ=0 and TXPWRON=1 */ val = hdmi_readb(HDMI_PHY_CONF0); val &= ~(HDMI_PHY_CONF0_GEN2_PDDQ_MASK | @@ -1300,6 +1303,8 @@ static void mxc_hdmi_rx_powerdown(struct mxc_hdmi *hdmi) val |= HDMI_PHY_CONF0_GEN2_PDDQ_ENABLE | HDMI_PHY_CONF0_GEN2_TXPWRON_POWER_OFF; hdmi_writeb(val, HDMI_PHY_CONF0); + + clk_disable(hdmi->hdmi_iahb_clk); } static int mxc_hdmi_cable_connected(struct mxc_hdmi *hdmi) @@ -1365,16 +1370,53 @@ static void det_worker(struct work_struct *work) container_of(delay_work, struct mxc_hdmi, det_work); u32 phy_int_stat, phy_int_pol, phy_int_mask; u8 val; + bool rx_powerdown = false; + int irq = platform_get_irq(hdmi->pdev, 0); + + if (!hdmi->irq_enabled) { + clk_enable(hdmi->hdmi_iahb_clk); + + /* Capture status - used in det_worker ISR */ + phy_int_stat = hdmi_readb(HDMI_IH_PHY_STAT0); + if ((phy_int_stat & + (HDMI_IH_PHY_RX_SENSE | HDMI_IH_PHY_STAT0_HPD)) == 0) { + clk_disable(hdmi->hdmi_iahb_clk); + return; /* No interrupts to handle */ + } + + dev_dbg(&hdmi->pdev->dev, "Hotplug interrupt received\n"); + + /* Unmask interrupts until handled */ + val = hdmi_readb(HDMI_PHY_MASK0); + val |= HDMI_PHY_RX_SENSE | HDMI_PHY_HPD; + hdmi_writeb(val, HDMI_PHY_MASK0); - /* Use saved interrupt status, since it was cleared in IST */ - phy_int_stat = hdmi->latest_intr_stat; - phy_int_pol = hdmi_readb(HDMI_PHY_POL0); + /* Clear Hotplug/RX Sense interrupts */ + hdmi_writeb(HDMI_IH_PHY_STAT0_HPD | + HDMI_IH_PHY_STAT0_TX_PHY_LOCK | + HDMI_IH_PHY_STAT0_RX_SENSE0 | + HDMI_IH_PHY_STAT0_RX_SENSE1 | + HDMI_IH_PHY_STAT0_RX_SENSE2 | + HDMI_IH_PHY_STAT0_RX_SENSE3, + HDMI_IH_PHY_STAT0); + + phy_int_pol = hdmi_readb(HDMI_PHY_POL0); + + clk_disable(hdmi->hdmi_iahb_clk); + } else { + /* Use saved interrupt status, since it was cleared in IST */ + phy_int_stat = hdmi->latest_intr_stat; + phy_int_pol = hdmi_readb(HDMI_PHY_POL0); + } + + /* Re-enable HDMI irq now that our interrupts have been masked off */ + hdmi_irq_enable(irq); /* check cable status */ if (phy_int_stat & HDMI_IH_PHY_STAT0_HPD) { /* cable connection changes */ if (phy_int_pol & HDMI_PHY_HPD) { - dev_dbg(&hdmi->pdev->dev, "EVENT=plugin"); + dev_dbg(&hdmi->pdev->dev, "EVENT=plugin\n"); mxc_hdmi_cable_connected(hdmi); /* Make HPD intr active low to capture unplug event */ @@ -1382,7 +1424,7 @@ static void det_worker(struct work_struct *work) val &= ~HDMI_PHY_HPD; hdmi_writeb(val, HDMI_PHY_POL0); } else if (!(phy_int_pol & HDMI_PHY_HPD)) { - dev_dbg(&hdmi->pdev->dev, "EVENT=plugout"); + dev_dbg(&hdmi->pdev->dev, "EVENT=plugout\n"); mxc_hdmi_cable_disconnected(hdmi); /* Make HPD intr active high to capture plugin event */ @@ -1390,7 +1432,7 @@ static void det_worker(struct work_struct *work) val |= HDMI_PHY_HPD; hdmi_writeb(val, HDMI_PHY_POL0); } else - dev_dbg(&hdmi->pdev->dev, "EVENT=none?"); + dev_dbg(&hdmi->pdev->dev, "EVENT=none?\n"); } /* check rx power */ @@ -1403,7 +1445,7 @@ static void det_worker(struct work_struct *work) val &= ~HDMI_PHY_RX_SENSE; hdmi_writeb(val, HDMI_PHY_POL0); } else if (!(phy_int_pol & HDMI_PHY_RX_SENSE)) { - mxc_hdmi_rx_powerdown(hdmi); + rx_powerdown = true; /* Change RX Sense pol to capture RX enable */ val = hdmi_readb(HDMI_PHY_POL0); @@ -1418,34 +1460,53 @@ static void det_worker(struct work_struct *work) phy_int_mask = hdmi_readb(HDMI_PHY_MASK0); phy_int_mask &= ~(HDMI_PHY_RX_SENSE | HDMI_PHY_HPD); hdmi_writeb(phy_int_mask, HDMI_PHY_MASK0); + + if (rx_powerdown) + mxc_hdmi_rx_powerdown(hdmi); } static irqreturn_t mxc_hdmi_hotplug(int irq, void *data) { struct mxc_hdmi *hdmi = data; + unsigned int ret; u8 val, intr_stat; - /* Capture status - used in det_worker ISR */ - intr_stat = hdmi_readb(HDMI_IH_PHY_STAT0); - if ((intr_stat & (HDMI_IH_PHY_RX_SENSE | HDMI_IH_PHY_STAT0_HPD)) == 0) - return IRQ_HANDLED; - - dev_dbg(&hdmi->pdev->dev, "Hotplug interrupt received\n"); - hdmi->latest_intr_stat = intr_stat; - - /* Unmask interrupts until handled */ - val = hdmi_readb(HDMI_PHY_MASK0); - val |= HDMI_PHY_RX_SENSE | HDMI_PHY_HPD; - hdmi_writeb(val, HDMI_PHY_MASK0); - - /* Clear Hotplug/RX Sense interrupts */ - hdmi_writeb(HDMI_IH_PHY_STAT0_HPD | - HDMI_IH_PHY_STAT0_TX_PHY_LOCK | - HDMI_IH_PHY_STAT0_RX_SENSE0 | - HDMI_IH_PHY_STAT0_RX_SENSE1 | - HDMI_IH_PHY_STAT0_RX_SENSE2 | - HDMI_IH_PHY_STAT0_RX_SENSE3, - HDMI_IH_PHY_STAT0); + /* + * We have to disable the irq, rather than just masking + * off the HDMI interrupts using HDMI registers. This is + * because the HDMI iahb clock is required to be on to + * access the HDMI registers, and we cannot enable it + * in an IST. This IRQ will be re-enabled in the + * interrupt handler workqueue function. + */ + ret = hdmi_irq_disable(irq); + if (ret == IRQ_DISABLE_FAIL) { + /* Capture status - used in det_worker ISR */ + intr_stat = hdmi_readb(HDMI_IH_PHY_STAT0); + if ((intr_stat & + (HDMI_IH_PHY_RX_SENSE | HDMI_IH_PHY_STAT0_HPD)) == 0) + return IRQ_HANDLED; + + dev_dbg(&hdmi->pdev->dev, "Hotplug interrupt received\n"); + hdmi->latest_intr_stat = intr_stat; + + /* Unmask interrupts until handled */ + val = hdmi_readb(HDMI_PHY_MASK0); + val |= HDMI_PHY_RX_SENSE | HDMI_PHY_HPD; + hdmi_writeb(val, HDMI_PHY_MASK0); + + /* Clear Hotplug/RX Sense interrupts */ + hdmi_writeb(HDMI_IH_PHY_STAT0_HPD | + HDMI_IH_PHY_STAT0_TX_PHY_LOCK | + HDMI_IH_PHY_STAT0_RX_SENSE0 | + HDMI_IH_PHY_STAT0_RX_SENSE1 | + HDMI_IH_PHY_STAT0_RX_SENSE2 | + HDMI_IH_PHY_STAT0_RX_SENSE3, + HDMI_IH_PHY_STAT0); + + hdmi->irq_enabled = true; + } else + hdmi->irq_enabled = false; schedule_delayed_work(&(hdmi->det_work), msecs_to_jiffies(20)); @@ -1580,20 +1641,36 @@ static int mxc_hdmi_disp_init(struct mxc_dispdrv_entry *disp) if (plat->init) plat->init(plat->ipu_id, plat->disp_id); - hdmi->hdmi_clk = clk_get(&hdmi->pdev->dev, "hdmi_isfr_clk"); - if (IS_ERR(hdmi->hdmi_clk)) { - ret = PTR_ERR(hdmi->hdmi_clk); + hdmi->hdmi_isfr_clk = clk_get(&hdmi->pdev->dev, "hdmi_isfr_clk"); + if (IS_ERR(hdmi->hdmi_isfr_clk)) { + ret = PTR_ERR(hdmi->hdmi_isfr_clk); + dev_err(&hdmi->pdev->dev, + "Unable to get HDMI clk: %d\n", ret); + goto egetclk1; + } + + ret = clk_enable(hdmi->hdmi_isfr_clk); + if (ret < 0) { + dev_err(&hdmi->pdev->dev, + "Cannot enable HDMI isfr clock: %d\n", ret); + goto erate1; + } + + hdmi->hdmi_iahb_clk = clk_get(&hdmi->pdev->dev, "hdmi_iahb_clk"); + if (IS_ERR(hdmi->hdmi_iahb_clk)) { + ret = PTR_ERR(hdmi->hdmi_iahb_clk); dev_err(&hdmi->pdev->dev, "Unable to get HDMI clk: %d\n", ret); - goto egetclk; + goto egetclk2; } - ret = clk_enable(hdmi->hdmi_clk); + ret = clk_enable(hdmi->hdmi_iahb_clk); if (ret < 0) { dev_err(&hdmi->pdev->dev, - "Cannot enable HDMI clock: %d\n", ret); - goto erate; + "Cannot enable HDMI iahb clock: %d\n", ret); + goto erate2; } + dev_dbg(&hdmi->pdev->dev, "Enabled HDMI clocks\n"); /* Product and revision IDs */ @@ -1667,6 +1744,9 @@ static int mxc_hdmi_disp_init(struct mxc_dispdrv_entry *disp) goto ereqirq; } + /* Initialize IRQ at HDMI core level */ + hdmi_irq_init(); + INIT_DELAYED_WORK(&(hdmi->det_work), det_worker); /* Configure registers related to HDMI interrupt @@ -1704,16 +1784,25 @@ static int mxc_hdmi_disp_init(struct mxc_dispdrv_entry *disp) mxc_hdmi_setup(hdmi); + /* Disable IAHB clock while waiting for hotplug interrupt. + * ISFR clock must remain enabled for hotplug to work. */ + clk_disable(hdmi->hdmi_iahb_clk); + return ret; efbclient: free_irq(irq, hdmi); efindmode: ereqirq: - clk_disable(hdmi->hdmi_clk); -erate: - clk_put(hdmi->hdmi_clk); -egetclk: + clk_disable(hdmi->hdmi_iahb_clk); +erate2: + clk_put(hdmi->hdmi_iahb_clk); +egetclk2: + clk_disable(hdmi->hdmi_isfr_clk); +erate1: + clk_put(hdmi->hdmi_isfr_clk); +egetclk1: + plat->put_pins(); egetpins: return ret; } @@ -1727,6 +1816,11 @@ static void mxc_hdmi_disp_deinit(struct mxc_dispdrv_entry *disp) mxc_hdmi_poweroff(hdmi); + clk_disable(hdmi->hdmi_isfr_clk); + clk_put(hdmi->hdmi_isfr_clk); + clk_disable(hdmi->hdmi_iahb_clk); + clk_put(hdmi->hdmi_iahb_clk); + /* Release HDMI pins */ if (plat->put_pins) plat->put_pins(); @@ -1797,8 +1891,6 @@ static int mxc_hdmi_remove(struct platform_device *pdev) /* No new work will be scheduled, wait for running ISR */ free_irq(irq, hdmi); - clk_disable(hdmi->hdmi_clk); - clk_put(hdmi->hdmi_clk); kfree(hdmi); return 0; diff --git a/include/linux/mfd/mxc-hdmi-core.h b/include/linux/mfd/mxc-hdmi-core.h index f5021c7acb44..d0712dbb8ad5 100644 --- a/include/linux/mfd/mxc-hdmi-core.h +++ b/include/linux/mfd/mxc-hdmi-core.h @@ -19,11 +19,17 @@ #ifndef __LINUX_MXC_HDMI_CORE_H_ #define __LINUX_MXC_HDMI_CORE_H_ +#define IRQ_DISABLE_SUCCEED 0 +#define IRQ_DISABLE_FAIL 1 + u8 hdmi_readb(unsigned int reg); void hdmi_writeb(u8 value, unsigned int reg); void hdmi_mask_writeb(u8 data, unsigned int addr, u8 shift, u8 mask); unsigned int hdmi_read4(unsigned int reg); void hdmi_write4(unsigned int value, unsigned int reg); +void hdmi_irq_init(void); +void hdmi_irq_enable(int irq); +unsigned int hdmi_irq_disable(int irq); extern int mxc_hdmi_pixel_clk; extern int mxc_hdmi_ratio; -- 2.39.5