diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/media/video/samsung/tvout/s5p_tvout.c | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2 |
samsung update 1
Diffstat (limited to 'drivers/media/video/samsung/tvout/s5p_tvout.c')
-rw-r--r-- | drivers/media/video/samsung/tvout/s5p_tvout.c | 666 |
1 files changed, 666 insertions, 0 deletions
diff --git a/drivers/media/video/samsung/tvout/s5p_tvout.c b/drivers/media/video/samsung/tvout/s5p_tvout.c new file mode 100644 index 0000000..7407670 --- /dev/null +++ b/drivers/media/video/samsung/tvout/s5p_tvout.c @@ -0,0 +1,666 @@ +/* linux/drivers/media/video/samsung/tvout/s5p_tvout.c + * + * Copyright (c) 2009 Samsung Electronics + * http://www.samsung.com/ + * + * Entry file for Samsung TVOut driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/mm.h> + +#if defined(CONFIG_S5P_SYSMMU_TV) +#include <plat/sysmmu.h> +#endif + +#if defined(CONFIG_S5P_MEM_CMA) +#include <linux/cma.h> +#elif defined(CONFIG_S5P_MEM_BOOTMEM) +#include <plat/media.h> +#include <mach/media.h> +#endif + +#include "s5p_tvout_common_lib.h" +#include "s5p_tvout_ctrl.h" +#include "s5p_tvout_fb.h" +#include "s5p_tvout_v4l2.h" + +#define TV_CLK_GET_WITH_ERR_CHECK(clk, pdev, clk_name) \ + do { \ + clk = clk_get(&pdev->dev, clk_name); \ + if (IS_ERR(clk)) { \ + printk(KERN_ERR \ + "failed to find clock %s\n", clk_name); \ + return -ENOENT; \ + } \ + } while (0); + +struct s5p_tvout_status s5ptv_status; +bool on_stop_process; +bool on_start_process; +struct s5p_tvout_vp_bufferinfo s5ptv_vp_buff; +#ifdef CONFIG_PM +static struct workqueue_struct *tvout_resume_wq; +struct work_struct tvout_resume_work; +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND +#include <linux/earlysuspend.h> +static struct early_suspend s5ptv_early_suspend; +static DEFINE_MUTEX(s5p_tvout_mutex); +unsigned int suspend_status; +static void s5p_tvout_early_suspend(struct early_suspend *h); +static void s5p_tvout_late_resume(struct early_suspend *h); +#endif +bool flag_after_resume; + +#ifdef CONFIG_TVOUT_DEBUG +int tvout_dbg_flag; +#endif + + +#ifdef CONFIG_HDMI_EARJACK_MUTE +bool hdmi_audio_ext; + +/* To provide an interface fo Audio path control */ +static ssize_t hdmi_set_audio_read(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int count = 0; + + printk(KERN_ERR "[HDMI]: AUDIO PATH\n"); + return count; +} + +static ssize_t hdmi_set_audio_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + char *after; + bool value = !strncmp(buf, "1", 1) ? true : false; + + printk(KERN_ERR "[HDMI] Change AUDIO PATH: %d\n", (int)value); + + if (value == hdmi_audio_ext) { + if (value) { + hdmi_audio_ext = 0; + s5p_hdmi_ctrl_set_audio(1); + } else { + hdmi_audio_ext = 1; + s5p_hdmi_ctrl_set_audio(0); + } + } + + return size; +} + +static DEVICE_ATTR(hdmi_audio_set_ext, 0660, + hdmi_set_audio_read, hdmi_set_audio_store); +#endif + +static int __devinit s5p_tvout_clk_get(struct platform_device *pdev, + struct s5p_tvout_status *ctrl) +{ + struct clk *ext_xtal_clk, *mout_vpll_src, *fout_vpll, *mout_vpll; + + TV_CLK_GET_WITH_ERR_CHECK(ctrl->i2c_phy_clk, pdev, "i2c-hdmiphy"); + + TV_CLK_GET_WITH_ERR_CHECK(ctrl->sclk_dac, pdev, "sclk_dac"); + TV_CLK_GET_WITH_ERR_CHECK(ctrl->sclk_hdmi, pdev, "sclk_hdmi"); + + TV_CLK_GET_WITH_ERR_CHECK(ctrl->sclk_pixel, pdev, "sclk_pixel"); + TV_CLK_GET_WITH_ERR_CHECK(ctrl->sclk_hdmiphy, pdev, "sclk_hdmiphy"); + + TV_CLK_GET_WITH_ERR_CHECK(ext_xtal_clk, pdev, "ext_xtal"); + TV_CLK_GET_WITH_ERR_CHECK(mout_vpll_src, pdev, "vpll_src"); + TV_CLK_GET_WITH_ERR_CHECK(fout_vpll, pdev, "fout_vpll"); + TV_CLK_GET_WITH_ERR_CHECK(mout_vpll, pdev, "sclk_vpll"); + +#ifdef CONFIG_VPLL_USE_FOR_TVENC + if (clk_set_rate(fout_vpll, 54000000)) { + tvout_err("%s rate change failed: %lu\n", fout_vpll->name, + 54000000); + return -1; + } + + if (clk_set_parent(mout_vpll_src, ext_xtal_clk)) { + tvout_err("unable to set parent %s of clock %s.\n", + ext_xtal_clk->name, mout_vpll_src->name); + return -1; + } + + if (clk_set_parent(mout_vpll, fout_vpll)) { + tvout_err("unable to set parent %s of clock %s.\n", + fout_vpll->name, mout_vpll->name); + return -1; + } + + /* sclk_dac's parent is fixed as mout_vpll */ + if (clk_set_parent(ctrl->sclk_dac, mout_vpll)) { + tvout_err("unable to set parent %s of clock %s.\n", + mout_vpll->name, ctrl->sclk_dac->name); + return -1; + } + + /* It'll be moved in the future */ + if (clk_enable(mout_vpll_src) < 0) + return -1; + + if (clk_enable(fout_vpll) < 0) + return -1; + + if (clk_enable(mout_vpll) < 0) + return -1; + + clk_put(ext_xtal_clk); + clk_put(mout_vpll_src); + clk_put(fout_vpll); + clk_put(mout_vpll); +#endif + + return 0; +} + +#ifdef CONFIG_TVOUT_DEBUG +void show_tvout_dbg_flag(void) +{ + pr_info("hw_if/hdmi.c %s\n", + ((tvout_dbg_flag >> DBG_FLAG_HDMI) & 0x1 ? "On" : "Off")); + pr_info("s5p_tvout_hpd.c %s\n", + ((tvout_dbg_flag >> DBG_FLAG_HPD) & 0x1 ? "On" : "Off")); + pr_info("s5p_tvout_common_lib.h %s\n", + ((tvout_dbg_flag >> DBG_FLAG_TVOUT) & 0x1 ? "On" : "Off")); + pr_info("hw_if/hdcp.c %s\n", + ((tvout_dbg_flag >> DBG_FLAG_HDCP) & 0x1 ? "On" : "Off")); +} + +void set_flag_value(int *flag, int pos, int value) +{ + if (value == 1) { + *flag |= (1 << pos); + } else { /* value is 0 */ + *flag &= ~(1 << pos); + } +} + +static ssize_t sysfs_dbg_msg_show(struct class *class, + struct class_attribute *attr, char *buf) +{ + pr_info("sysfs_dbg_msg_show\n"); + show_tvout_dbg_flag(); + return sprintf(buf, "hw_if/hdmi.c %s\n" + "s5p_tvout_hpd.c %s\n" + "s5p_tvout_common_lib.h %s\n" + "hw_if/hdcp.c %s\n", + ((tvout_dbg_flag >> DBG_FLAG_HDMI) & 0x1 ? "On" : "Off"), + ((tvout_dbg_flag >> DBG_FLAG_HPD) & 0x1 ? "On" : "Off"), + ((tvout_dbg_flag >> DBG_FLAG_TVOUT) & 0x1 ? "On" : "Off"), + ((tvout_dbg_flag >> DBG_FLAG_HDCP) & 0x1 ? "On" : "Off")); +} + +static ssize_t sysfs_dbg_msg_store(struct class *class, + struct class_attribute *attr, const char *buf, size_t size) +{ + enum tvout_dbg_flag_bit_num tvout_dbg_flag_bit; + int value; + int i; + char *dest[2]; + char *buffer = (char *)buf; + + pr_info("TVOUT Debug Message setting : "); + for (i = 0; i < 2; i++) + dest[i] = strsep(&buffer, ":"); + + if (strcmp(dest[0], "help") == 0) { + pr_info( + "bit3 : hw_if/hdmi.c\n" + "bit2 : s5p_tvout_hpd.c\n" + "bit1 : s5p_tvout_common_lib.h\n" + "bit0 : hw_if/hdcp.c\n" + "ex1) echo 1010 > dbg_msg\n" + " hw_if/hdmi.c On\n" + " s5p_tvout_hpd.c Off\n" + " s5p_tvout_common_lib.h On\n" + " hw_if/hdcp.c Off\n" + "ex2) echo hdcp:1 > dbg_msg\n" + " hw_if/hdcp.c On\n" + ); + return size; + } + + if (strcmp(dest[0], "hdcp") == 0) { + tvout_dbg_flag_bit = DBG_FLAG_HDCP; + } else if (strcmp(dest[0], "tvout") == 0) { + tvout_dbg_flag_bit = DBG_FLAG_TVOUT; + } else if (strcmp(dest[0], "hpd") == 0) { + tvout_dbg_flag_bit = DBG_FLAG_HPD; + } else if (strcmp(dest[0], "hdmi") == 0) { + tvout_dbg_flag_bit = DBG_FLAG_HDMI; + } else if (strlen(dest[0]) == 5) { + for (i = 0; i < 4; i++) { + value = dest[0][i] - '0'; + if (value < 0 || 2 < value) { + pr_info("error : setting value!\n"); + return size; + } + set_flag_value(&tvout_dbg_flag, 3-i, value); + } + show_tvout_dbg_flag(); + return size; + } else { + pr_info("Error : Debug Message Taget\n"); + return size; + } + + if (strcmp(dest[1], "1\n") == 0) { + value = 1; + } else if (strcmp(dest[1], "0\n") == 0) { + value = 0; + } else { + pr_info("Error : Setting value!\n"); + return size; + } + + set_flag_value(&tvout_dbg_flag, tvout_dbg_flag_bit, value); + show_tvout_dbg_flag(); + + return size; +} + +static CLASS_ATTR(dbg_msg, S_IRUGO | S_IWUSR, + sysfs_dbg_msg_show, sysfs_dbg_msg_store); +#endif + +static int __devinit s5p_tvout_probe(struct platform_device *pdev) +{ +#if defined(CONFIG_S5P_MEM_CMA) + struct cma_info mem_info; + int ret; +#elif defined(CONFIG_S5P_MEM_BOOTMEM) + int mdev_id; +#endif + unsigned int vp_buff_vir_addr; + unsigned int vp_buff_phy_addr = 0; + int i; + +#ifdef CONFIG_HDMI_EARJACK_MUTE + struct class *hdmi_audio_class; + struct device *hdmi_audio_dev; +#endif + +#ifdef CONFIG_TVOUT_DEBUG + struct class *sec_tvout; + tvout_dbg_flag = 1 << DBG_FLAG_HPD; +#endif + s5p_tvout_pm_runtime_enable(&pdev->dev); + +#if defined(CONFIG_S5P_SYSMMU_TV) && defined(CONFIG_VCM) + if (s5p_tvout_vcm_create_unified() < 0) + goto err; + + if (s5p_tvout_vcm_init() < 0) + goto err; +#elif defined(CONFIG_S5P_SYSMMU_TV) && defined(CONFIG_S5P_VMEM) + s5p_sysmmu_enable(&pdev->dev); + printk(KERN_WARNING "sysmmu on\n"); + s5p_sysmmu_set_tablebase_pgd(&pdev->dev, __pa(swapper_pg_dir)); +#endif + if (s5p_tvout_clk_get(pdev, &s5ptv_status) < 0) + goto err; + + if (s5p_vp_ctrl_constructor(pdev) < 0) + goto err; + + /* s5p_mixer_ctrl_constructor must be called + before s5p_tvif_ctrl_constructor */ + if (s5p_mixer_ctrl_constructor(pdev) < 0) + goto err_mixer; + + if (s5p_tvif_ctrl_constructor(pdev) < 0) + goto err_tvif; + + if (s5p_tvout_v4l2_constructor(pdev) < 0) + goto err_v4l2; + +#ifdef CONFIG_HAS_EARLYSUSPEND + spin_lock_init(&s5ptv_status.tvout_lock); + s5ptv_early_suspend.suspend = s5p_tvout_early_suspend; + s5ptv_early_suspend.resume = s5p_tvout_late_resume; + s5ptv_early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB - 4; + register_early_suspend(&s5ptv_early_suspend); + suspend_status = 0; +#endif + +#ifdef CONFIG_TV_FB +#ifndef CONFIG_USER_ALLOC_TVOUT + s5p_hdmi_phy_power(true); + if (s5p_tvif_ctrl_start(TVOUT_720P_60, TVOUT_HDMI) < 0) + goto err_tvif_start; +#endif + + /* prepare memory */ + if (s5p_tvout_fb_alloc_framebuffer(&pdev->dev)) + goto err_tvif_start; + + if (s5p_tvout_fb_register_framebuffer(&pdev->dev)) + goto err_tvif_start; +#endif + on_stop_process = false; + on_start_process = false; +#if !defined(CONFIG_CPU_EXYNOS4212) && !defined(CONFIG_CPU_EXYNOS4412) +#if defined(CONFIG_S5P_MEM_CMA) + /* CMA */ + ret = cma_info(&mem_info, &pdev->dev, 0); + tvout_dbg("[cma_info] start_addr : 0x%x, end_addr : 0x%x, " + "total_size : 0x%x, free_size : 0x%x\n", + mem_info.lower_bound, mem_info.upper_bound, + mem_info.total_size, mem_info.free_size); + if (ret) { + tvout_err("get cma info failed\n"); + goto err_tvif_start; + } + s5ptv_vp_buff.size = mem_info.total_size; + if (s5ptv_vp_buff.size < S5PTV_VP_BUFF_CNT * S5PTV_VP_BUFF_SIZE) { + tvout_err("insufficient vp buffer size\n"); + goto err_tvif_start; + } + vp_buff_phy_addr = (unsigned int)cma_alloc + (&pdev->dev, (char *)"tvout", (size_t) s5ptv_vp_buff.size, + (dma_addr_t) 0); + +#elif defined(CONFIG_S5P_MEM_BOOTMEM) + mdev_id = S5P_MDEV_TVOUT; + /* alloc from bank1 as default */ + vp_buff_phy_addr = s5p_get_media_memory_bank(mdev_id, 1); + s5ptv_vp_buff.size = s5p_get_media_memsize_bank(mdev_id, 1); + if (s5ptv_vp_buff.size < S5PTV_VP_BUFF_CNT * S5PTV_VP_BUFF_SIZE) { + tvout_err("insufficient vp buffer size\n"); + goto err_tvif_start; + } +#endif + + tvout_dbg("s5ptv_vp_buff.size = 0x%x\n", s5ptv_vp_buff.size); + tvout_dbg("s5ptv_vp_buff phy_base = 0x%x\n", vp_buff_phy_addr); + + vp_buff_vir_addr = (unsigned int)phys_to_virt(vp_buff_phy_addr); + tvout_dbg("s5ptv_vp_buff vir_base = 0x%x\n", vp_buff_vir_addr); + + if (!vp_buff_vir_addr) { + tvout_err("phys_to_virt failed\n"); + goto err_ioremap; + } + + for (i = 0; i < S5PTV_VP_BUFF_CNT; i++) { + s5ptv_vp_buff.vp_buffs[i].phy_base = + vp_buff_phy_addr + (i * S5PTV_VP_BUFF_SIZE); + s5ptv_vp_buff.vp_buffs[i].vir_base = + vp_buff_vir_addr + (i * S5PTV_VP_BUFF_SIZE); + } +#else + for (i = 0; i < S5PTV_VP_BUFF_CNT; i++) { + s5ptv_vp_buff.vp_buffs[i].phy_base = 0; + s5ptv_vp_buff.vp_buffs[i].vir_base = 0; + } +#endif + + for (i = 0; i < S5PTV_VP_BUFF_CNT - 1; i++) + s5ptv_vp_buff.copy_buff_idxs[i] = i; + + s5ptv_vp_buff.curr_copy_idx = 0; + s5ptv_vp_buff.vp_access_buff_idx = S5PTV_VP_BUFF_CNT - 1; + +#ifdef CONFIG_TVOUT_DEBUG + tvout_dbg("Create tvout class sysfile\n"); + + sec_tvout = class_create(THIS_MODULE, "tvout"); + if (IS_ERR(sec_tvout)) { + tvout_err("Failed to create class(sec_tvout)!\n"); + goto err_class; + } + + if (class_create_file(sec_tvout, &class_attr_dbg_msg) < 0) { + tvout_err("failed to add sysfs entries\n"); + goto err_sysfs; + } +#endif + + flag_after_resume = false; +#ifdef CONFIG_HDMI_EARJACK_MUTE + hdmi_audio_class = class_create(THIS_MODULE, "hdmi_audio"); + if (IS_ERR(hdmi_audio_class)) + pr_err("Failed to create class(hdmi_audio)!\n"); + hdmi_audio_dev = device_create(hdmi_audio_class, NULL, 0, NULL, + "hdmi_audio"); + if (IS_ERR(hdmi_audio_dev)) + pr_err("Failed to create device(hdmi_audio_dev)!\n"); + + if (device_create_file(hdmi_audio_dev, + &dev_attr_hdmi_audio_set_ext) < 0) + printk(KERN_ERR "Failed to create device file(%s)!\n", + dev_attr_hdmi_audio_set_ext.attr.name); + + hdmi_audio_ext = false; +#endif + + return 0; + +err_sysfs: + class_destroy(sec_tvout); +err_class: +err_ioremap: +#if defined(CONFIG_S5P_MEM_CMA) + cma_free(vp_buff_phy_addr); +#endif +err_tvif_start: + s5p_tvout_v4l2_destructor(); +err_v4l2: + s5p_tvif_ctrl_destructor(); +err_tvif: + s5p_mixer_ctrl_destructor(); +err_mixer: + s5p_vp_ctrl_destructor(); +err: + return -ENODEV; +} + +static int s5p_tvout_remove(struct platform_device *pdev) +{ +#if defined(CONFIG_S5P_SYSMMU_TV) && defined(CONFIG_S5P_VMEM) + s5p_sysmmu_off(&pdev->dev); + tvout_dbg("sysmmu off\n"); +#endif + s5p_vp_ctrl_destructor(); + s5p_tvif_ctrl_destructor(); + s5p_mixer_ctrl_destructor(); + + s5p_tvout_v4l2_destructor(); + + clk_disable(s5ptv_status.sclk_hdmi); + + clk_put(s5ptv_status.sclk_hdmi); + clk_put(s5ptv_status.sclk_dac); + clk_put(s5ptv_status.sclk_pixel); + clk_put(s5ptv_status.sclk_hdmiphy); + + s5p_tvout_pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +#ifdef CONFIG_HAS_EARLYSUSPEND +static void s5p_tvout_early_suspend(struct early_suspend *h) +{ + tvout_dbg("\n"); + mutex_lock(&s5p_tvout_mutex); + s5p_mixer_ctrl_set_vsync_interrupt(false); + s5p_vp_ctrl_suspend(); + s5p_mixer_ctrl_suspend(); + s5p_tvif_ctrl_suspend(); + suspend_status = 1; + tvout_dbg("suspend_status is true\n"); + mutex_unlock(&s5p_tvout_mutex); + + return; +} + +static void s5p_tvout_late_resume(struct early_suspend *h) +{ + tvout_dbg("\n"); + + mutex_lock(&s5p_tvout_mutex); + +#if defined(CONFIG_CPU_EXYNOS4212) || defined(CONFIG_CPU_EXYNOS4412) + if (flag_after_resume) { + queue_work_on(0, tvout_resume_wq, &tvout_resume_work); + flag_after_resume = false; + } +#endif + suspend_status = 0; + tvout_dbg("suspend_status is false\n"); + s5p_tvif_ctrl_resume(); + s5p_mixer_ctrl_resume(); + s5p_vp_ctrl_resume(); + s5p_mixer_ctrl_set_vsync_interrupt(s5p_mixer_ctrl_get_vsync_interrupt()); + mutex_unlock(&s5p_tvout_mutex); + + return; +} + +void s5p_tvout_mutex_lock() +{ + mutex_lock(&s5p_tvout_mutex); +} + +void s5p_tvout_mutex_unlock() +{ + mutex_unlock(&s5p_tvout_mutex); +} +#endif + +static void s5p_tvout_resume_work(void *arg) +{ +#ifdef CONFIG_HAS_EARLYSUSPEND + mutex_lock(&s5p_tvout_mutex); +#endif + s5p_hdmi_ctrl_phy_power_resume(); +#ifdef CONFIG_HAS_EARLYSUSPEND + mutex_unlock(&s5p_tvout_mutex); +#endif +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static int s5p_tvout_suspend(struct device *dev) +{ + tvout_dbg("\n"); + return 0; +} + +static int s5p_tvout_resume(struct device *dev) +{ + tvout_dbg("\n"); +#if defined(CONFIG_CPU_EXYNOS4212) || defined(CONFIG_CPU_EXYNOS4412) + flag_after_resume = true; +#else + queue_work_on(0, tvout_resume_wq, &tvout_resume_work); +#endif + return 0; +} +#else +static int s5p_tvout_suspend(struct device *dev) +{ + s5p_vp_ctrl_suspend(); + s5p_mixer_ctrl_suspend(); + s5p_tvif_ctrl_suspend(); + return 0; +} + +static int s5p_tvout_resume(struct device *dev) +{ + s5p_tvif_ctrl_resume(); + s5p_mixer_ctrl_resume(); + s5p_vp_ctrl_resume(); + return 0; +} +#endif +static int s5p_tvout_runtime_suspend(struct device *dev) +{ + tvout_dbg("\n"); + return 0; +} + +static int s5p_tvout_runtime_resume(struct device *dev) +{ + tvout_dbg("\n"); + return 0; +} +#else +#define s5p_tvout_suspend NULL +#define s5p_tvout_resume NULL +#define s5p_tvout_runtime_suspend NULL +#define s5p_tvout_runtime_resume NULL +#endif + +static const struct dev_pm_ops s5p_tvout_pm_ops = { + .suspend = s5p_tvout_suspend, + .resume = s5p_tvout_resume, + .runtime_suspend = s5p_tvout_runtime_suspend, + .runtime_resume = s5p_tvout_runtime_resume +}; + +static struct platform_driver s5p_tvout_driver = { + .probe = s5p_tvout_probe, + .remove = s5p_tvout_remove, + .driver = { + .name = "s5p-tvout", + .owner = THIS_MODULE, + .pm = &s5p_tvout_pm_ops}, +}; + +static char banner[] __initdata = + KERN_INFO "S5P TVOUT Driver v3.0 (c) 2010 Samsung Electronics\n"; + +static int __init s5p_tvout_init(void) +{ + int ret; + + printk(banner); + + ret = platform_driver_register(&s5p_tvout_driver); + + if (ret) { + printk(KERN_ERR "Platform Device Register Failed %d\n", ret); + + return -1; + } +#ifdef CONFIG_PM + tvout_resume_wq = create_freezable_workqueue("tvout resume work"); + if (!tvout_resume_wq) { + printk(KERN_ERR "Platform Device Register Failed %d\n", ret); + platform_driver_unregister(&s5p_tvout_driver); + return -1; + } + + INIT_WORK(&tvout_resume_work, (work_func_t) s5p_tvout_resume_work); +#endif + + return 0; +} + +static void __exit s5p_tvout_exit(void) +{ +#ifdef CONFIG_HAS_EARLYSUSPEND + mutex_destroy(&s5p_tvout_mutex); +#endif + platform_driver_unregister(&s5p_tvout_driver); +} + +late_initcall(s5p_tvout_init); +module_exit(s5p_tvout_exit); + +MODULE_AUTHOR("SangPil Moon"); +MODULE_DESCRIPTION("S5P TVOUT driver"); +MODULE_LICENSE("GPL"); |