]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/video/sh_mobile_hdmi.c
fbdev: sh_mobile_hdmi: implement locking
[mv-sheeva.git] / drivers / video / sh_mobile_hdmi.c
index 2fde08cc66bf524f4f72594ee64cf72192f10800..56e44fd0a3af5ed6154805aea6e6d040159eb714 100644 (file)
@@ -26,6 +26,8 @@
 #include <video/sh_mobile_hdmi.h>
 #include <video/sh_mobile_lcdc.h>
 
+#include "sh_mobile_lcdcfb.h"
+
 #define HDMI_SYSTEM_CTRL                       0x00 /* System control */
 #define HDMI_L_R_DATA_SWAP_CTRL_RPKT           0x01 /* L/R data swap control,
                                                        bits 19..16 of 20-bit N for Audio Clock Regeneration packet */
@@ -205,9 +207,11 @@ enum hotplug_state {
 struct sh_hdmi {
        void __iomem *base;
        enum hotplug_state hp_state;
+       bool preprogrammed_mode;        /* use a pre-programmed VIC or the external mode */
        struct clk *hdmi_clk;
        struct device *dev;
        struct fb_info *info;
+       struct mutex mutex;             /* Protect the info pointer */
        struct delayed_work edid_work;
        struct fb_var_screeninfo var;
 };
@@ -282,7 +286,10 @@ static void hdmi_external_video_param(struct sh_hdmi *hdmi)
 
        hdmi_write(hdmi, var->vsync_len, HDMI_EXTERNAL_V_DURATION);
 
-       /* Set bit 0 of HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS here for manual mode */
+       /* Set bit 0 of HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS here for external mode */
+       if (!hdmi->preprogrammed_mode)
+               hdmi_write(hdmi, sync | 1 | (voffset << 4),
+                          HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS);
 }
 
 /**
@@ -381,21 +388,61 @@ static void sh_hdmi_audio_config(struct sh_hdmi *hdmi)
 }
 
 /**
- * sh_hdmi_phy_config()
+ * sh_hdmi_phy_config() - configure the HDMI PHY for the used video mode
  */
 static void sh_hdmi_phy_config(struct sh_hdmi *hdmi)
 {
-       /* 720p, 8bit, 74.25MHz. Might need to be adjusted for other formats */
-       hdmi_write(hdmi, 0x19, HDMI_SLIPHDMIT_PARAM_SETTINGS_1);
-       hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_2);
-       hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_3);
-       /* PLLA_CONFIG[7:0]: VCO gain, VCO offset, LPF resistance[0] */
-       hdmi_write(hdmi, 0x44, HDMI_SLIPHDMIT_PARAM_SETTINGS_5);
-       hdmi_write(hdmi, 0x32, HDMI_SLIPHDMIT_PARAM_SETTINGS_6);
-       hdmi_write(hdmi, 0x4A, HDMI_SLIPHDMIT_PARAM_SETTINGS_7);
-       hdmi_write(hdmi, 0x0E, HDMI_SLIPHDMIT_PARAM_SETTINGS_8);
-       hdmi_write(hdmi, 0x25, HDMI_SLIPHDMIT_PARAM_SETTINGS_9);
-       hdmi_write(hdmi, 0x04, HDMI_SLIPHDMIT_PARAM_SETTINGS_10);
+       if (hdmi->var.yres > 480) {
+               /* 720p, 8bit, 74.25MHz. Might need to be adjusted for other formats */
+               /*
+                * [1:0]        Speed_A
+                * [3:2]        Speed_B
+                * [4]          PLLA_Bypass
+                * [6]          DRV_TEST_EN
+                * [7]          DRV_TEST_IN
+                */
+               hdmi_write(hdmi, 0x19, HDMI_SLIPHDMIT_PARAM_SETTINGS_1);
+               /* PLLB_CONFIG[17], PLLA_CONFIG[17] - not in PHY datasheet */
+               hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_2);
+               /*
+                * [2:0]        BGR_I_OFFSET
+                * [6:4]        BGR_V_OFFSET
+                */
+               hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_3);
+               /* PLLA_CONFIG[7:0]: VCO gain, VCO offset, LPF resistance[0] */
+               hdmi_write(hdmi, 0x44, HDMI_SLIPHDMIT_PARAM_SETTINGS_5);
+               /*
+                * PLLA_CONFIG[15:8]: regulator voltage[0], CP current,
+                * LPF capacitance, LPF resistance[1]
+                */
+               hdmi_write(hdmi, 0x32, HDMI_SLIPHDMIT_PARAM_SETTINGS_6);
+               /* PLLB_CONFIG[7:0]: LPF resistance[0], VCO offset, VCO gain */
+               hdmi_write(hdmi, 0x4A, HDMI_SLIPHDMIT_PARAM_SETTINGS_7);
+               /*
+                * PLLB_CONFIG[15:8]: regulator voltage[0], CP current,
+                * LPF capacitance, LPF resistance[1]
+                */
+               hdmi_write(hdmi, 0x0E, HDMI_SLIPHDMIT_PARAM_SETTINGS_8);
+               /* DRV_CONFIG, PE_CONFIG */
+               hdmi_write(hdmi, 0x25, HDMI_SLIPHDMIT_PARAM_SETTINGS_9);
+               /*
+                * [2:0]        AMON_SEL (4 == LPF voltage)
+                * [4]          PLLA_CONFIG[16]
+                * [5]          PLLB_CONFIG[16]
+                */
+               hdmi_write(hdmi, 0x04, HDMI_SLIPHDMIT_PARAM_SETTINGS_10);
+       } else {
+               /* for 480p8bit 27MHz */
+               hdmi_write(hdmi, 0x19, HDMI_SLIPHDMIT_PARAM_SETTINGS_1);
+               hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_2);
+               hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_3);
+               hdmi_write(hdmi, 0x44, HDMI_SLIPHDMIT_PARAM_SETTINGS_5);
+               hdmi_write(hdmi, 0x32, HDMI_SLIPHDMIT_PARAM_SETTINGS_6);
+               hdmi_write(hdmi, 0x48, HDMI_SLIPHDMIT_PARAM_SETTINGS_7);
+               hdmi_write(hdmi, 0x0F, HDMI_SLIPHDMIT_PARAM_SETTINGS_8);
+               hdmi_write(hdmi, 0x20, HDMI_SLIPHDMIT_PARAM_SETTINGS_9);
+               hdmi_write(hdmi, 0x04, HDMI_SLIPHDMIT_PARAM_SETTINGS_10);
+       }
 }
 
 /**
@@ -403,6 +450,8 @@ static void sh_hdmi_phy_config(struct sh_hdmi *hdmi)
  */
 static void sh_hdmi_avi_infoframe_setup(struct sh_hdmi *hdmi)
 {
+       u8 vic;
+
        /* AVI InfoFrame */
        hdmi_write(hdmi, 0x06, HDMI_CTRL_PKT_BUF_INDEX);
 
@@ -443,9 +492,15 @@ static void sh_hdmi_avi_infoframe_setup(struct sh_hdmi *hdmi)
 
        /*
         * VIC = 1280 x 720p: ignored if external config is used
-        * Send 2 for 720 x 480p, 16 for 1080p
+        * Send 2 for 720 x 480p, 16 for 1080p, ignored in external mode
         */
-       hdmi_write(hdmi, 4, HDMI_CTRL_PKT_BUF_ACCESS_PB4);
+       if (hdmi->var.yres == 1080 && hdmi->var.xres == 1920)
+               vic = 16;
+       else if (hdmi->var.yres == 480 && hdmi->var.xres == 720)
+               vic = 2;
+       else
+               vic = 4;
+       hdmi_write(hdmi, vic, HDMI_CTRL_PKT_BUF_ACCESS_PB4);
 
        /* PR = No Repetition */
        hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB5);
@@ -518,100 +573,6 @@ static void sh_hdmi_audio_infoframe_setup(struct sh_hdmi *hdmi)
        hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB10);
 }
 
-/**
- * sh_hdmi_gamut_metadata_setup() - Gamut Metadata Packet of CONTROL PACKET
- */
-static void sh_hdmi_gamut_metadata_setup(struct sh_hdmi *hdmi)
-{
-       int i;
-
-       /* Gamut Metadata Packet */
-       hdmi_write(hdmi, 0x04, HDMI_CTRL_PKT_BUF_INDEX);
-
-       /* Packet Type = 0x0A */
-       hdmi_write(hdmi, 0x0A, HDMI_CTRL_PKT_BUF_ACCESS_HB0);
-       /* Gamut Packet is not used, so default value */
-       hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB1);
-       /* Gamut Packet is not used, so default value */
-       hdmi_write(hdmi, 0x10, HDMI_CTRL_PKT_BUF_ACCESS_HB2);
-
-       /* GBD bytes 0 through 27 */
-       for (i = 0; i <= 27; i++)
-               /* HDMI_CTRL_PKT_BUF_ACCESS_PB0_63H - PB27_7EH */
-               hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0 + i);
-}
-
-/**
- * sh_hdmi_acp_setup() - Audio Content Protection Packet (ACP)
- */
-static void sh_hdmi_acp_setup(struct sh_hdmi *hdmi)
-{
-       int i;
-
-       /* Audio Content Protection Packet (ACP) */
-       hdmi_write(hdmi, 0x01, HDMI_CTRL_PKT_BUF_INDEX);
-
-       /* Packet Type = 0x04 */
-       hdmi_write(hdmi, 0x04, HDMI_CTRL_PKT_BUF_ACCESS_HB0);
-       /* ACP_Type */
-       hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB1);
-       /* Reserved (0) */
-       hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB2);
-
-       /* GBD bytes 0 through 27 */
-       for (i = 0; i <= 27; i++)
-               /* HDMI_CTRL_PKT_BUF_ACCESS_PB0 - PB27 */
-               hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0 + i);
-}
-
-/**
- * sh_hdmi_isrc1_setup() - ISRC1 Packet
- */
-static void sh_hdmi_isrc1_setup(struct sh_hdmi *hdmi)
-{
-       int i;
-
-       /* ISRC1 Packet */
-       hdmi_write(hdmi, 0x02, HDMI_CTRL_PKT_BUF_INDEX);
-
-       /* Packet Type = 0x05 */
-       hdmi_write(hdmi, 0x05, HDMI_CTRL_PKT_BUF_ACCESS_HB0);
-       /* ISRC_Cont, ISRC_Valid, Reserved (0), ISRC_Status */
-       hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB1);
-       /* Reserved (0) */
-       hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB2);
-
-       /* PB0 UPC_EAN_ISRC_0-15 */
-       /* Bytes PB16-PB27 shall be set to a value of 0. */
-       for (i = 0; i <= 27; i++)
-               /* HDMI_CTRL_PKT_BUF_ACCESS_PB0 - PB27 */
-               hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0 + i);
-}
-
-/**
- * sh_hdmi_isrc2_setup() - ISRC2 Packet
- */
-static void sh_hdmi_isrc2_setup(struct sh_hdmi *hdmi)
-{
-       int i;
-
-       /* ISRC2 Packet */
-       hdmi_write(hdmi, 0x03, HDMI_CTRL_PKT_BUF_INDEX);
-
-       /* HB0 Packet Type = 0x06 */
-       hdmi_write(hdmi, 0x06, HDMI_CTRL_PKT_BUF_ACCESS_HB0);
-       /* Reserved (0) */
-       hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB1);
-       /* Reserved (0) */
-       hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB2);
-
-       /* PB0 UPC_EAN_ISRC_16-31 */
-       /* Bytes PB16-PB27 shall be set to a value of 0. */
-       for (i = 0; i <= 27; i++)
-               /* HDMI_CTRL_PKT_BUF_ACCESS_PB0 - PB27 */
-               hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0 + i);
-}
-
 /**
  * sh_hdmi_configure() - Initialise HDMI for output
  */
@@ -632,18 +593,6 @@ static void sh_hdmi_configure(struct sh_hdmi *hdmi)
        /* Audio InfoFrame */
        sh_hdmi_audio_infoframe_setup(hdmi);
 
-       /* Gamut Metadata packet */
-       sh_hdmi_gamut_metadata_setup(hdmi);
-
-       /* Audio Content Protection (ACP) Packet */
-       sh_hdmi_acp_setup(hdmi);
-
-       /* ISRC1 Packet */
-       sh_hdmi_isrc1_setup(hdmi);
-
-       /* ISRC2 Packet */
-       sh_hdmi_isrc2_setup(hdmi);
-
        /*
         * Control packet auto send with VSYNC control: auto send
         * General control, Gamut metadata, ISRC, and ACP packets
@@ -663,10 +612,9 @@ static void sh_hdmi_configure(struct sh_hdmi *hdmi)
 
 static void sh_hdmi_read_edid(struct sh_hdmi *hdmi)
 {
-       struct fb_var_screeninfo *var = &hdmi->var;
-       struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data;
-       struct fb_videomode *lcd_cfg = &pdata->lcd_chan->lcd_cfg;
-       unsigned long height = var->height, width = var->width;
+       struct fb_var_screeninfo tmpvar;
+       /* TODO: When we are ready to use EDID, use this to fill &hdmi->var */
+       struct fb_var_screeninfo *var = &tmpvar;
        int i;
        u8 edid[128];
 
@@ -692,21 +640,12 @@ static void sh_hdmi_read_edid(struct sh_hdmi *hdmi)
                 var->upper_margin, var->yres, var->lower_margin, var->vsync_len,
                 PICOS2KHZ(var->pixclock));
 
-       /* FIXME: Use user-provided configuration instead of EDID */
-       var->width              = width;
-       var->xres               = lcd_cfg->xres;
-       var->xres_virtual       = lcd_cfg->xres;
-       var->left_margin        = lcd_cfg->left_margin;
-       var->right_margin       = lcd_cfg->right_margin;
-       var->hsync_len          = lcd_cfg->hsync_len;
-       var->height             = height;
-       var->yres               = lcd_cfg->yres;
-       var->yres_virtual       = lcd_cfg->yres * 2;
-       var->upper_margin       = lcd_cfg->upper_margin;
-       var->lower_margin       = lcd_cfg->lower_margin;
-       var->vsync_len          = lcd_cfg->vsync_len;
-       var->sync               = lcd_cfg->sync;
-       var->pixclock           = lcd_cfg->pixclock;
+       if ((hdmi->var.xres == 720 && hdmi->var.yres == 480) ||
+           (hdmi->var.xres == 1280 && hdmi->var.yres == 720) ||
+           (hdmi->var.xres == 1920 && hdmi->var.yres == 1080))
+               hdmi->preprogrammed_mode = true;
+       else
+               hdmi->preprogrammed_mode = false;
 
        hdmi_external_video_param(hdmi);
 }
@@ -784,23 +723,21 @@ static irqreturn_t sh_hdmi_hotplug(int irq, void *dev_id)
        return IRQ_HANDLED;
 }
 
+/* locking:    called with info->lock held, or before register_framebuffer() */
 static void hdmi_display_on(void *arg, struct fb_info *info)
 {
+       /*
+        * info is guaranteed to be valid, when we are called, because our
+        * FB_EVENT_FB_UNBIND notify is also called with info->lock held
+        */
        struct sh_hdmi *hdmi = arg;
        struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data;
 
-       if (info->var.xres != 1280 || info->var.yres != 720) {
-               dev_warn(info->device, "Unsupported framebuffer geometry %ux%u\n",
-                        info->var.xres, info->var.yres);
-               return;
-       }
-
        pr_debug("%s(%p): state %x\n", __func__, pdata->lcd_dev, info->state);
-       /*
-        * FIXME: not a good place to store fb_info. And we cannot nullify it
-        * even on monitor disconnect. What should the lifecycle be?
-        */
+
+       /* No need to lock */
        hdmi->info = info;
+
        switch (hdmi->hp_state) {
        case HDMI_HOTPLUG_EDID_DONE:
                /* PS mode d->e. All functions are active */
@@ -814,6 +751,7 @@ static void hdmi_display_on(void *arg, struct fb_info *info)
        }
 }
 
+/* locking: called with info->lock held */
 static void hdmi_display_off(void *arg)
 {
        struct sh_hdmi *hdmi = arg;
@@ -836,6 +774,8 @@ static void edid_work_fn(struct work_struct *work)
        if (!pdata->lcd_dev)
                return;
 
+       mutex_lock(&hdmi->mutex);
+
        if (hdmi->hp_state == HDMI_HOTPLUG_EDID_DONE) {
                pm_runtime_get_sync(hdmi->dev);
                /* A device has been plugged in */
@@ -846,21 +786,25 @@ static void edid_work_fn(struct work_struct *work)
                msleep(10);
 
                if (!hdmi->info)
-                       return;
+                       goto out;
 
                acquire_console_sem();
 
                /* HDMI plug in */
                hdmi->info->var = hdmi->var;
-               if (hdmi->info->state != FBINFO_STATE_RUNNING)
+               if (hdmi->info->state != FBINFO_STATE_RUNNING) {
                        fb_set_suspend(hdmi->info, 0);
-               else
-                       hdmi_display_on(hdmi, hdmi->info);
+               } else {
+                       if (lock_fb_info(hdmi->info)) {
+                               hdmi_display_on(hdmi, hdmi->info);
+                               unlock_fb_info(hdmi->info);
+                       }
+               }
 
                release_console_sem();
        } else {
                if (!hdmi->info)
-                       return;
+                       goto out;
 
                acquire_console_sem();
 
@@ -871,13 +815,61 @@ static void edid_work_fn(struct work_struct *work)
                pm_runtime_put(hdmi->dev);
        }
 
+out:
+       mutex_unlock(&hdmi->mutex);
+
        pr_debug("%s(%p): end\n", __func__, pdata->lcd_dev);
 }
 
+static int sh_hdmi_notify(struct notifier_block *nb,
+                         unsigned long action, void *data);
+
+static struct notifier_block sh_hdmi_notifier = {
+       .notifier_call = sh_hdmi_notify,
+};
+
+static int sh_hdmi_notify(struct notifier_block *nb,
+                         unsigned long action, void *data)
+{
+       struct fb_event *event = data;
+       struct fb_info *info = event->info;
+       struct sh_mobile_lcdc_chan *ch = info->par;
+       struct sh_mobile_lcdc_board_cfg *board_cfg = &ch->cfg.board_cfg;
+       struct sh_hdmi *hdmi = board_cfg->board_data;
+
+       if (nb != &sh_hdmi_notifier || !hdmi || hdmi->info != info)
+               return NOTIFY_DONE;
+
+       switch(action) {
+       case FB_EVENT_FB_REGISTERED:
+               /* Unneeded, activation taken care by hdmi_display_on() */
+               break;
+       case FB_EVENT_FB_UNREGISTERED:
+               /*
+                * We are called from unregister_framebuffer() with the
+                * info->lock held. This is bad for us, because we can race with
+                * the scheduled work, which has to call fb_set_suspend(), which
+                * takes info->lock internally, so, edid_work_fn() cannot take
+                * and hold info->lock for the whole function duration. Using an
+                * additional lock creates a classical AB-BA lock up. Therefore,
+                * we have to release the info->lock temporarily, synchronise
+                * with the work queue and re-acquire the info->lock.
+                */
+               unlock_fb_info(hdmi->info);
+               mutex_lock(&hdmi->mutex);
+               hdmi->info = NULL;
+               mutex_unlock(&hdmi->mutex);
+               lock_fb_info(hdmi->info);
+               return NOTIFY_OK;
+       }
+       return NOTIFY_DONE;
+}
+
 static int __init sh_hdmi_probe(struct platform_device *pdev)
 {
        struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data;
        struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       struct sh_mobile_lcdc_board_cfg *board_cfg;
        int irq = platform_get_irq(pdev, 0), ret;
        struct sh_hdmi *hdmi;
        long rate;
@@ -891,6 +883,7 @@ static int __init sh_hdmi_probe(struct platform_device *pdev)
                return -ENOMEM;
        }
 
+       mutex_init(&hdmi->mutex);
        hdmi->dev = &pdev->dev;
 
        hdmi->hdmi_clk = clk_get(&pdev->dev, "ick");
@@ -900,7 +893,8 @@ static int __init sh_hdmi_probe(struct platform_device *pdev)
                goto egetclk;
        }
 
-       rate = PICOS2KHZ(pdata->lcd_chan->lcd_cfg.pixclock) * 1000;
+       /* TODO: reconfigure the clock on monitor plug in */
+       rate = PICOS2KHZ(pdata->lcd_chan->lcd_cfg[0].pixclock) * 1000;
 
        rate = clk_round_rate(hdmi->hdmi_clk, rate);
        if (rate < 0) {
@@ -947,9 +941,11 @@ static int __init sh_hdmi_probe(struct platform_device *pdev)
 #endif
 
        /* Set up LCDC callbacks */
-       pdata->lcd_chan->board_cfg.board_data = hdmi;
-       pdata->lcd_chan->board_cfg.display_on = hdmi_display_on;
-       pdata->lcd_chan->board_cfg.display_off = hdmi_display_off;
+       board_cfg = &pdata->lcd_chan->board_cfg;
+       board_cfg->owner = THIS_MODULE;
+       board_cfg->board_data = hdmi;
+       board_cfg->display_on = hdmi_display_on;
+       board_cfg->display_off = hdmi_display_off;
 
        INIT_DELAYED_WORK(&hdmi->edid_work, edid_work_fn);
 
@@ -976,6 +972,7 @@ eclkenable:
 erate:
        clk_put(hdmi->hdmi_clk);
 egetclk:
+       mutex_destroy(&hdmi->mutex);
        kfree(hdmi);
 
        return ret;
@@ -986,19 +983,24 @@ static int __exit sh_hdmi_remove(struct platform_device *pdev)
        struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data;
        struct sh_hdmi *hdmi = platform_get_drvdata(pdev);
        struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       struct sh_mobile_lcdc_board_cfg *board_cfg = &pdata->lcd_chan->board_cfg;
        int irq = platform_get_irq(pdev, 0);
 
-       pdata->lcd_chan->board_cfg.display_on = NULL;
-       pdata->lcd_chan->board_cfg.display_off = NULL;
-       pdata->lcd_chan->board_cfg.board_data = NULL;
+       board_cfg->display_on = NULL;
+       board_cfg->display_off = NULL;
+       board_cfg->board_data = NULL;
+       board_cfg->owner = NULL;
 
+       /* No new work will be scheduled, wait for running ISR */
        free_irq(irq, hdmi);
-       pm_runtime_disable(&pdev->dev);
+       /* Wait for already scheduled work */
        cancel_delayed_work_sync(&hdmi->edid_work);
+       pm_runtime_disable(&pdev->dev);
        clk_disable(hdmi->hdmi_clk);
        clk_put(hdmi->hdmi_clk);
        iounmap(hdmi->base);
        release_mem_region(res->start, resource_size(res));
+       mutex_destroy(&hdmi->mutex);
        kfree(hdmi);
 
        return 0;