From c66cf4b6622e577535ffc0392fa51a59e7fe2af7 Mon Sep 17 00:00:00 2001 From: Zheng Yang Date: Wed, 25 Apr 2012 18:20:43 +0800 Subject: [PATCH] rk30 hdmi: 1. When hdmi was disabled in early_suspend, sometimes it will be enabld agian by user control. So the hdmi irq will be enabled twice in early_resume, this action will caurse kernel crash. To fix this bug, we need to distinguish suspend mode and user control mode, and add mutex to protect enable/disable hdmi irq. 2. Use spin_lock_irqsave/spin_unlock_irqrestore replace spin_lock/spin_unlock. --- drivers/video/rockchip/hdmi/rk30_hdmi.c | 23 +++++++++++------ drivers/video/rockchip/hdmi/rk30_hdmi.h | 4 +++ drivers/video/rockchip/hdmi/rk30_hdmi_hw.c | 19 +++++++------- drivers/video/rockchip/hdmi/rk30_hdmi_sysfs.c | 25 ++++++++++++++++--- drivers/video/rockchip/hdmi/rk30_hdmi_task.c | 21 ++++++++++++---- 5 files changed, 67 insertions(+), 25 deletions(-) diff --git a/drivers/video/rockchip/hdmi/rk30_hdmi.c b/drivers/video/rockchip/hdmi/rk30_hdmi.c index 6fc842c9be81..c0038bf0274c 100755 --- a/drivers/video/rockchip/hdmi/rk30_hdmi.c +++ b/drivers/video/rockchip/hdmi/rk30_hdmi.c @@ -30,12 +30,16 @@ extern void hdmi_register_display_sysfs(struct hdmi *hdmi, struct device *parent static void hdmi_early_suspend(struct early_suspend *h) { hdmi_dbg(hdmi->dev, "hdmi enter early suspend pwr %d state %d\n", hdmi->pwr_mode, hdmi->state); + flush_delayed_work(&hdmi->delay_work); + mutex_lock(&hdmi->enable_mutex); + hdmi->suspend = 1; + if(!hdmi->enable) { + mutex_unlock(&hdmi->enable_mutex); + return; + } disable_irq(hdmi->irq); - hdmi->enable = 0; + mutex_unlock(&hdmi->enable_mutex); hdmi->command = HDMI_CONFIG_ENABLE; - /* wait for hdmi configuration finish */ - while(hdmi->wait) - msleep(10); init_completion(&hdmi->complete); hdmi->wait = 1; queue_delayed_work(hdmi->workqueue, &hdmi->delay_work, 0); @@ -48,8 +52,12 @@ static void hdmi_early_suspend(struct early_suspend *h) static void hdmi_early_resume(struct early_suspend *h) { hdmi_dbg(hdmi->dev, "hdmi exit early resume\n"); - hdmi->enable = 1; - enable_irq(hdmi->irq); + mutex_lock(&hdmi->enable_mutex); + hdmi->suspend = 0; + if(hdmi->enable) { + enable_irq(hdmi->irq); + } + mutex_unlock(&hdmi->enable_mutex); return; } #endif @@ -151,7 +159,8 @@ static int __devinit rk30_hdmi_probe (struct platform_device *pdev) hdmi_register_display_sysfs(hdmi, hdmi->dev); spin_lock_init(&hdmi->irq_lock); - + mutex_init(&hdmi->enable_mutex); + /* get the IRQ */ hdmi->irq = platform_get_irq(pdev, 0); if(hdmi->irq <= 0) { diff --git a/drivers/video/rockchip/hdmi/rk30_hdmi.h b/drivers/video/rockchip/hdmi/rk30_hdmi.h index d39c4b0db8f2..caf8b495f0fe 100755 --- a/drivers/video/rockchip/hdmi/rk30_hdmi.h +++ b/drivers/video/rockchip/hdmi/rk30_hdmi.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -48,10 +49,13 @@ struct hdmi { struct delayed_work delay_work; spinlock_t irq_lock; + struct mutex enable_mutex; int wait; struct completion complete; + #ifdef CONFIG_HAS_EARLYSUSPEND + int suspend; struct early_suspend early_suspend; #endif diff --git a/drivers/video/rockchip/hdmi/rk30_hdmi_hw.c b/drivers/video/rockchip/hdmi/rk30_hdmi_hw.c index 6ce3f62f82d7..f3c2a3482af0 100755 --- a/drivers/video/rockchip/hdmi/rk30_hdmi_hw.c +++ b/drivers/video/rockchip/hdmi/rk30_hdmi_hw.c @@ -63,11 +63,12 @@ int rk30_hdmi_read_edid(int block, unsigned char *buff) { int value, ret = -1, ddc_bus_freq = 0; char interrupt = 0, trytime = 2; + unsigned long flags; hdmi_dbg(hdmi->dev, "[%s] block %d\n", __FUNCTION__, block); - spin_lock(&hdmi->irq_lock); + spin_lock_irqsave(&hdmi->irq_lock, flags); edid_result = 0; - spin_unlock(&hdmi->irq_lock); + spin_unlock_irqrestore(&hdmi->irq_lock, flags); //Before Phy parameter was set, DDC_CLK is equal to PLLA freq which is 24MHz. //Set DDC I2C CLK which devided from DDC_CLK to 100KHz. ddc_bus_freq = (24000000/HDMI_EDID_DDC_CLK)/4; @@ -85,10 +86,9 @@ int rk30_hdmi_read_edid(int block, unsigned char *buff) value = 100; while(value--) { - spin_lock(&hdmi->irq_lock); + spin_lock_irqsave(&hdmi->irq_lock, flags); interrupt = edid_result; - spin_unlock(&hdmi->irq_lock); -// hdmi_dbg(hdmi->dev, "[%s] interrupt %02x value %d\n", __FUNCTION__, interrupt, value); + spin_unlock_irqrestore(&hdmi->irq_lock, flags); if(interrupt & (m_INT_EDID_ERR | m_INT_EDID_READY)) break; msleep(10); @@ -452,13 +452,12 @@ irqreturn_t hdmi_irq(int irq, void *priv) { char interrupt1 = 0, interrupt2 = 0, interrupt3 = 0, interrupt4 = 0; - spin_lock(&hdmi->irq_lock); if(hdmi->pwr_mode == PWR_SAVE_MODE_A) { - hdmi_dbg(hdmi->dev, "hdmi irq wake up\n"); HDMIWrReg(SYS_CTRL, 0x20); hdmi->pwr_mode = PWR_SAVE_MODE_B; + hdmi_dbg(hdmi->dev, "hdmi irq wake up\n"); // HDMI was inserted when system is sleeping, irq was triggered only once // when wake up. So we need to check hotplug status. if(HDMIRdReg(HPD_MENS_STA) & (m_HOTPLUG_STATUS | m_MSEN_STATUS)) { @@ -486,15 +485,17 @@ irqreturn_t hdmi_irq(int irq, void *priv) interrupt1 &= ~(m_INT_HOTPLUG | m_INT_MSENS); queue_delayed_work(hdmi->workqueue, &hdmi->delay_work, msecs_to_jiffies(10)); } - else if(interrupt1 & (m_INT_EDID_READY | m_INT_EDID_ERR)) + else if(interrupt1 & (m_INT_EDID_READY | m_INT_EDID_ERR)) { + spin_lock(&hdmi->irq_lock); edid_result = interrupt1; + spin_unlock(&hdmi->irq_lock); + } else if(hdmi->state == HDMI_SLEEP) { hdmi_dbg(hdmi->dev, "hdmi return to sleep mode\n"); HDMIWrReg(SYS_CTRL, 0x10); hdmi->pwr_mode = PWR_SAVE_MODE_A; } } - spin_unlock(&hdmi->irq_lock); return IRQ_HANDLED; } diff --git a/drivers/video/rockchip/hdmi/rk30_hdmi_sysfs.c b/drivers/video/rockchip/hdmi/rk30_hdmi_sysfs.c index cd206320086a..873562687d09 100755 --- a/drivers/video/rockchip/hdmi/rk30_hdmi_sysfs.c +++ b/drivers/video/rockchip/hdmi/rk30_hdmi_sysfs.c @@ -7,24 +7,41 @@ static int hdmi_get_enable(struct rk_display_device *device) { struct hdmi *hdmi = device->priv_data; - - return hdmi->enable; + int enable; + + mutex_lock(&hdmi->enable_mutex); + enable = hdmi->enable; + mutex_unlock(&hdmi->enable_mutex); + + return enable; } static int hdmi_set_enable(struct rk_display_device *device, int enable) { struct hdmi *hdmi = device->priv_data; - if(hdmi->enable == enable) + mutex_lock(&hdmi->enable_mutex); + if(hdmi->enable == enable) { + mutex_unlock(&hdmi->enable_mutex); return 0; + } hdmi->enable = enable; + + if(hdmi->suspend ) { + mutex_unlock(&hdmi->enable_mutex); + return 0; + } + if(enable == 0) { disable_irq(hdmi->irq); + mutex_unlock(&hdmi->enable_mutex); hdmi->command = HDMI_CONFIG_ENABLE; queue_delayed_work(hdmi->workqueue, &hdmi->delay_work, 0); } - else + else { enable_irq(hdmi->irq); + mutex_unlock(&hdmi->enable_mutex); + } return 0; } diff --git a/drivers/video/rockchip/hdmi/rk30_hdmi_task.c b/drivers/video/rockchip/hdmi/rk30_hdmi_task.c index 63cec9929241..72728864831c 100755 --- a/drivers/video/rockchip/hdmi/rk30_hdmi_task.c +++ b/drivers/video/rockchip/hdmi/rk30_hdmi_task.c @@ -82,12 +82,14 @@ void hdmi_sys_remove(void) static void hdmi_sys_sleep(void) { + mutex_lock(&hdmi->enable_mutex); if(hdmi->enable) - disable_irq(hdmi->irq); + disable_irq(hdmi->irq); hdmi->state = HDMI_SLEEP; rk30_hdmi_removed(); if(hdmi->enable) enable_irq(hdmi->irq); + mutex_unlock(&hdmi->enable_mutex); } static int hdmi_process_command(void) @@ -102,15 +104,17 @@ static int hdmi_process_command(void) { case HDMI_CONFIG_ENABLE: /* disable HDMI */ - if(!hdmi->enable) + mutex_lock(&hdmi->enable_mutex); + if(!hdmi->enable || hdmi->suspend) { - if(hdmi->hotplug) + if(hdmi->hotplug == HDMI_HPD_ACTIVED) hdmi_sys_remove(); hdmi->state = HDMI_SLEEP; hdmi->hotplug = HDMI_HPD_REMOVED; rk30_hdmi_removed(); state = HDMI_SLEEP; } + mutex_unlock(&hdmi->enable_mutex); if(hdmi->wait == 1) { complete(&hdmi->complete); hdmi->wait = 0; @@ -147,16 +151,21 @@ static int hdmi_process_command(void) return state; } +static DEFINE_MUTEX(work_mutex); + void hdmi_work(struct work_struct *work) { int hotplug, state_last; int rc = HDMI_ERROR_SUCESS, trytimes = 0; + + mutex_lock(&work_mutex); /* Process hdmi command */ hdmi->state = hdmi_process_command(); - if(!hdmi->enable) + if(!hdmi->enable || hdmi->suspend) { + mutex_unlock(&work_mutex); return; - + } hotplug = rk30_hdmi_detect_hotplug(); hdmi_dbg(hdmi->dev, "[%s] hotplug %02x curvalue %d\n", __FUNCTION__, hotplug, hdmi->hotplug); @@ -180,6 +189,7 @@ void hdmi_work(struct work_struct *work) hdmi->wait = 0; } kobject_uevent_env(&hdmi->dev->kobj, KOBJ_REMOVE, envp); + mutex_unlock(&work_mutex); return; } else if(hotplug == HDMI_HPD_REMOVED) { @@ -262,4 +272,5 @@ void hdmi_work(struct work_struct *work) } } hdmi_dbg(hdmi->dev, "[%s] done\n", __FUNCTION__); + mutex_unlock(&work_mutex); } \ No newline at end of file -- 2.34.1