aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorDanny Nold <dannynold@freescale.com>2011-10-14 13:43:32 -0500
committerEric Miao <eric.miao@canonical.com>2011-11-10 07:38:47 +0800
commit24d8daa1432863515b9e6fb65a3298611bc7c61b (patch)
treeb2e153ffc7f556af00e73e517a49b5e16f03c4e7 /drivers
parent16b37759f818eb28fc3596242c622593faea6e7a (diff)
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 <dannynold@freescale.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/mfd/mxc-hdmi-core.c87
-rw-r--r--drivers/video/mxc_hdmi.c186
2 files changed, 225 insertions, 48 deletions
diff --git a/drivers/mfd/mxc-hdmi-core.c b/drivers/mfd/mxc-hdmi-core.c
index 0e59dafd0a3..3fe3d1e1f9f 100644
--- a/drivers/mfd/mxc-hdmi-core.c
+++ b/drivers/mfd/mxc-hdmi-core.c
@@ -25,11 +25,15 @@
#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 <mach/clock.h>
#include <mach/mxc_hdmi.h>
#include <linux/mfd/mxc-hdmi-core.h>
@@ -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 1c1009c027b..ca3f7fa4201 100644
--- a/drivers/video/mxc_hdmi.c
+++ b/drivers/video/mxc_hdmi.c
@@ -41,6 +41,7 @@
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/clk.h>
+#include <mach/clock.h>
#include <linux/uaccess.h>
#include <linux/cpufreq.h>
#include <linux/firmware.h>
@@ -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;