Index: drivers/media/video/samsung/tv20/s5p_tv_base.c |
diff --git a/drivers/media/video/samsung/tv20/s5p_tv_base.c b/drivers/media/video/samsung/tv20/s5p_tv_base.c |
new file mode 100644 |
index 0000000000000000000000000000000000000000..df1e9a0fc1ddb9ef0ae28940126c27e74f75705e |
--- /dev/null |
+++ b/drivers/media/video/samsung/tv20/s5p_tv_base.c |
@@ -0,0 +1,810 @@ |
+/* linux/drivers/media/video/samsung/tv20/s5p_tv_base.c |
+* |
+* Copyright (c) 2010 Samsung Electronics Co., Ltd. |
+* http://www.samsung.com/ |
+* |
+* S5PV210 - 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/init.h> |
+#include <linux/module.h> |
+#include <linux/kernel.h> |
+#include <linux/string.h> |
+#include <linux/fs.h> |
+#include <linux/init.h> |
+#include <linux/mm.h> |
+#include <linux/interrupt.h> |
+#include <linux/miscdevice.h> |
+#include <linux/platform_device.h> |
+#include <linux/workqueue.h> |
+#include <linux/wait.h> |
+#include <linux/ioctl.h> |
+#include <linux/device.h> |
+#include <linux/clk.h> |
+#include <linux/i2c.h> |
+#include <linux/delay.h> |
+#include <linux/irq.h> |
+ |
+#include <media/v4l2-common.h> |
+#include <media/v4l2-ioctl.h> |
+#include <mach/map.h> |
+ |
+#include <mach/gpio.h> |
+#include <plat/gpio-cfg.h> |
+ |
+#include <linux/io.h> |
+#include <linux/uaccess.h> |
+ |
+#ifdef CONFIG_S5PV210_PM |
+#include <mach/pd.h> |
+#endif |
+ |
+#include "s5p_tv.h" |
+ |
+#ifdef CONFIG_TVOUT_DBG |
+#define S5P_TV_BASE_DEBUG 1 |
+#endif |
+ |
+#ifdef S5P_TV_BASE_DEBUG |
+#define BASEPRINTK(fmt, args...) \ |
+ printk(KERN_INFO "[TVBASE] %s: " fmt, __func__ , ## args) |
+#else |
+#define BASEPRINTK(fmt, args...) |
+#endif |
+ |
+ |
+#define TVOUT_CLK_INIT(dev, clk, name) |
+ |
+#define TVOUT_IRQ_INIT(x, ret, dev, num, jump, ftn, m_name) \ |
+ do { \ |
+ x = platform_get_irq(dev, num); \ |
+ if (x < 0) { \ |
+ printk(KERN_ERR \ |
+ "failed to get %s irq resource\n", m_name); \ |
+ ret = -ENOENT; \ |
+ goto jump; \ |
+ } \ |
+ ret = request_irq(x, ftn, IRQF_DISABLED, dev->name, dev); \ |
+ if (ret != 0) { \ |
+ printk(KERN_ERR \ |
+ "failed to install %s irq (%d)\n", m_name, ret); \ |
+ goto jump; \ |
+ } \ |
+ } while (0) |
+ |
+ |
+static struct mutex *mutex_for_fo; |
+ |
+ |
+struct s5p_tv_status s5ptv_status; |
+struct s5p_tv_vo s5ptv_overlay[2]; |
+wait_queue_head_t s5ptv_wq; |
+ |
+#ifdef I2C_BASE |
+static struct mutex *mutex_for_i2c; |
+static struct work_struct ws_hpd; |
+spinlock_t slock_hpd; |
+ |
+static struct i2c_driver hdcp_i2c_driver; |
+static bool hdcp_i2c_drv_state; |
+ |
+const static u16 ignore[] = {I2C_CLIENT_END}; |
+const static u16 normal_addr[] = {(S5P_HDCP_I2C_ADDR >> 1), I2C_CLIENT_END}; |
+const static u16 *forces[] = {NULL}; |
+ |
+static struct i2c_client_address_data addr_data = { |
+ .normal_i2c = normal_addr, |
+ .probe = ignore, |
+ .ignore = ignore, |
+ .forces = forces, |
+}; |
+ |
+/* |
+ * i2c client drv. - register client drv |
+ */ |
+static int hdcp_i2c_attach(struct i2c_adapter *adap, int addr, int kind) |
+{ |
+ |
+ struct i2c_client *c; |
+ |
+ c = kzalloc(sizeof(*c), GFP_KERNEL); |
+ |
+ if (!c) |
+ return -ENOMEM; |
+ |
+ strcpy(c->name, "s5p_ddc_client"); |
+ |
+ c->addr = addr; |
+ |
+ c->adapter = adap; |
+ |
+ c->driver = &hdcp_i2c_driver; |
+ |
+ s5ptv_status.hdcp_i2c_client = c; |
+ |
+ dev_info(&adap->dev, "s5p_ddc_client attached " |
+ "into s5p_ddc_port successfully\n"); |
+ |
+ return i2c_attach_client(c); |
+} |
+ |
+static int hdcp_i2c_attach_adapter(struct i2c_adapter *adap) |
+{ |
+ int ret = 0; |
+ |
+ ret = i2c_probe(adap, &addr_data, hdcp_i2c_attach); |
+ |
+ if (ret) { |
+ dev_err(&adap->dev, |
+ "failed to attach s5p_hdcp_port driver\n"); |
+ ret = -ENODEV; |
+ } |
+ |
+ return ret; |
+} |
+ |
+static int hdcp_i2c_detach(struct i2c_client *client) |
+{ |
+ dev_info(&client->adapter->dev, "s5p_ddc_client detached " |
+ "from s5p_ddc_port successfully\n"); |
+ |
+ i2c_detach_client(client); |
+ |
+ return 0; |
+} |
+ |
+static struct i2c_driver hdcp_i2c_driver = { |
+ .driver = { |
+ .name = "s5p_ddc_port", |
+ }, |
+ .id = I2C_DRIVERID_S5P_HDCP, |
+ .attach_adapter = hdcp_i2c_attach_adapter, |
+ .detach_client = hdcp_i2c_detach, |
+}; |
+ |
+static void set_ddc_port(void) |
+{ |
+ mutex_lock(mutex_for_i2c); |
+ |
+ if (s5ptv_status.hpd_status) { |
+ |
+ if (!hdcp_i2c_drv_state) |
+ /* cable : plugged, drv : unregistered */ |
+ if (i2c_add_driver(&hdcp_i2c_driver)) |
+ printk(KERN_INFO "HDCP port add failed\n"); |
+ |
+ /* changed drv. status */ |
+ hdcp_i2c_drv_state = true; |
+ |
+ |
+ /* cable inserted -> removed */ |
+ tv_set_hpd_detection(true, s5ptv_status.hdcp_en, |
+ s5ptv_status.hdcp_i2c_client); |
+ |
+ } else { |
+ |
+ if (hdcp_i2c_drv_state) |
+ /* cable : unplugged, drv : registered */ |
+ i2c_del_driver(&hdcp_i2c_driver); |
+ |
+ /* changed drv. status */ |
+ hdcp_i2c_drv_state = false; |
+ |
+ /* cable removed -> inserted */ |
+ tv_set_hpd_detection(false, s5ptv_status.hdcp_en, |
+ s5ptv_status.hdcp_i2c_client); |
+ } |
+ |
+ mutex_unlock(mutex_for_i2c); |
+} |
+#endif |
+ |
+int tv_phy_power(bool on) |
+{ |
+ if (on) { |
+ /* on */ |
+ clk_enable(s5ptv_status.i2c_phy_clk); |
+ |
+ tv_hdmi_phy_power(true); |
+ |
+ } else { |
+ /* |
+ * for preventing hdmi hang up when restart |
+ * switch to internal clk - SCLK_DAC, SCLK_PIXEL |
+ */ |
+ tv_clk_change_internal(); |
+ |
+ tv_hdmi_phy_power(false); |
+ |
+ clk_disable(s5ptv_status.i2c_phy_clk); |
+ } |
+ |
+ return 0; |
+} |
+ |
+ |
+int s5p_tv_clk_gate(bool on) |
+{ |
+ if (on) { |
+#ifdef CONFIG_S5PV210_PM |
+ if (s5pv210_pd_enable("vp_pd") < 0) { |
+ printk(KERN_ERR "[Err]Power is not on for VP\n"); |
+ goto err_pm; |
+ } |
+#endif |
+ clk_enable(s5ptv_status.vp_clk); |
+#ifdef CONFIG_S5PV210_PM |
+ if (s5pv210_pd_enable("mixer_pd") < 0) { |
+ printk(KERN_ERR "[Err]Power is not on for mixer\n"); |
+ goto err_pm; |
+ } |
+#endif |
+ clk_enable(s5ptv_status.mixer_clk); |
+#ifdef CONFIG_S5PV210_PM |
+ if (s5pv210_pd_enable("tv_enc_pd") < 0) { |
+ printk(KERN_ERR "[Err]Power is not on for TV ENC\n"); |
+ goto err_pm; |
+ } |
+#endif |
+ clk_enable(s5ptv_status.tvenc_clk); |
+#ifdef CONFIG_S5PV210_PM |
+ if (s5pv210_pd_enable("hdmi_pd") < 0) { |
+ printk(KERN_ERR "[Err]Power is not on for HDMI\n"); |
+ goto err_pm; |
+ } |
+#endif |
+ clk_enable(s5ptv_status.hdmi_clk); |
+ |
+ } else { |
+ |
+ /* off */ |
+ clk_disable(s5ptv_status.vp_clk); |
+#ifdef CONFIG_S5PV210_PM |
+ if (s5pv210_pd_disable("vp_pd") < 0) { |
+ printk(KERN_ERR "[Err]Power is not off for VP\n"); |
+ goto err_pm; |
+ } |
+#endif |
+ clk_disable(s5ptv_status.mixer_clk); |
+#ifdef CONFIG_S5PV210_PM |
+ if (0 != s5pv210_pd_disable("mixer_pd")) { |
+ printk(KERN_ERR "[Err]Power is not off for mixer\n"); |
+ goto err_pm; |
+ } |
+#endif |
+ clk_disable(s5ptv_status.tvenc_clk); |
+#ifdef CONFIG_S5PV210_PM |
+ if (s5pv210_pd_disable("tv_enc_pd") < 0) { |
+ printk(KERN_ERR "[Err]Power is not off for TV ENC\n"); |
+ goto err_pm; |
+ } |
+#endif |
+ clk_disable(s5ptv_status.hdmi_clk); |
+#ifdef CONFIG_S5PV210_PM |
+ if (s5pv210_pd_disable("hdmi_pd") < 0) { |
+ printk(KERN_ERR "[Err]Power is not off for HDMI\n"); |
+ goto err_pm; |
+ } |
+#endif |
+ } |
+ |
+ return 0; |
+#ifdef CONFIG_S5PV210_PM |
+err_pm: |
+ return -1; |
+#endif |
+} |
+ |
+static int __devinit tv_clk_get(struct platform_device *pdev, |
+ struct s5p_tv_status *ctrl) |
+{ |
+ /* tvenc clk */ |
+ ctrl->tvenc_clk = clk_get(&pdev->dev, "tvenc"); |
+ |
+ if (IS_ERR(ctrl->tvenc_clk)) { |
+ printk(KERN_ERR "failed to find %s clock source\n", "tvenc"); |
+ return -ENOENT; |
+ } |
+ |
+ /* vp clk */ |
+ ctrl->vp_clk = clk_get(&pdev->dev, "vp"); |
+ |
+ if (IS_ERR(ctrl->vp_clk)) { |
+ printk(KERN_ERR "failed to find %s clock source\n", "vp"); |
+ return -ENOENT; |
+ } |
+ |
+ /* mixer clk */ |
+ ctrl->mixer_clk = clk_get(&pdev->dev, "mixer"); |
+ |
+ if (IS_ERR(ctrl->mixer_clk)) { |
+ printk(KERN_ERR "failed to find %s clock source\n", "mixer"); |
+ return -ENOENT; |
+ } |
+ |
+ /* hdmi clk */ |
+ ctrl->hdmi_clk = clk_get(&pdev->dev, "hdmi"); |
+ |
+ if (IS_ERR(ctrl->hdmi_clk)) { |
+ printk(KERN_ERR "failed to find %s clock source\n", "hdmi"); |
+ return -ENOENT; |
+ } |
+ |
+ /* i2c-hdmiphy clk */ |
+ ctrl->i2c_phy_clk = clk_get(&pdev->dev, "i2c-hdmiphy"); |
+ |
+ if (IS_ERR(ctrl->i2c_phy_clk)) { |
+ printk(KERN_ERR |
+ "failed to find %s clock source\n", "i2c-hdmiphy"); |
+ return -ENOENT; |
+ } |
+ |
+ return 0; |
+} |
+ |
+/* |
+ * ftn for irq |
+ */ |
+static irqreturn_t s5p_tvenc_irq(int irq, void *dev_id) |
+{ |
+ return IRQ_HANDLED; |
+} |
+ |
+static int s5p_tv_open(struct file *file) |
+{ |
+ /* |
+ * for first open |
+ * when boot up time this parameter is set. |
+ */ |
+ |
+ if (s5ptv_status.tvout_output_enable) |
+ tv_if_stop(); |
+ |
+ return 0; |
+} |
+ |
+static int s5p_tv_release(struct file *file) |
+{ |
+ s5ptv_status.hdcp_en = false; |
+ |
+ if (s5ptv_status.tvout_output_enable) |
+ tv_if_stop(); |
+ |
+ return 0; |
+} |
+ |
+static int s5p_tv_vid_open(struct file *file) |
+{ |
+ int ret = 0; |
+ |
+ mutex_lock(mutex_for_fo); |
+ |
+ if (s5ptv_status.vp_layer_enable) { |
+ printk(KERN_ERR "video layer. already used !!\n"); |
+ ret = -EBUSY; |
+ } |
+ |
+ mutex_unlock(mutex_for_fo); |
+ return ret; |
+} |
+ |
+static int s5p_tv_vid_release(struct file *file) |
+{ |
+ s5ptv_status.vp_layer_enable = false; |
+ |
+ tv_vlayer_stop(); |
+ |
+ return 0; |
+} |
+ |
+int s5ptvfb_alloc_framebuffer(void) |
+{ |
+ int ret; |
+ |
+ /* alloc for each framebuffer */ |
+ s5ptv_status.fb = framebuffer_alloc(sizeof(struct s5ptvfb_window), |
+ s5ptv_status.dev_fb); |
+ if (!s5ptv_status.fb) { |
+ dev_err(s5ptv_status.dev_fb, "not enough memory\n"); |
+ ret = -ENOMEM; |
+ goto err_alloc_fb; |
+ } |
+ |
+ /* Initializing framebuffer struct. */ |
+ memset(s5ptv_status.fb, 0, sizeof(struct s5ptvfb_window)); |
+ |
+ /* Changing frame number 0 into 5 for TV out */ |
+ ret = s5ptvfb_init_fbinfo(5); |
+ if (ret) { |
+ dev_err(s5ptv_status.dev_fb, |
+ "failed to allocate memory for fb for tv\n"); |
+ ret = -ENOMEM; |
+ goto err_alloc_fb; |
+ } |
+ |
+ return 0; |
+ |
+err_alloc_fb: |
+ if (s5ptv_status.fb) |
+ framebuffer_release(s5ptv_status.fb); |
+ |
+ kfree(s5ptv_status.fb); |
+ |
+ return ret; |
+} |
+ |
+int s5ptvfb_free_framebuffer(void) |
+{ |
+ if (s5ptv_status.fb) |
+ framebuffer_release(s5ptv_status.fb); |
+ |
+ return 0; |
+} |
+ |
+int s5ptvfb_register_framebuffer(void) |
+{ |
+ int ret; |
+ |
+ ret = register_framebuffer(s5ptv_status.fb); |
+ if (ret) { |
+ dev_err(s5ptv_status.dev_fb, "failed to register " |
+ "framebuffer device\n"); |
+ return -EINVAL; |
+ } |
+ return 0; |
+} |
+ |
+int s5ptvfb_unregister_framebuffer(void) |
+{ |
+ if (s5ptv_status.fb) |
+ unregister_framebuffer(s5ptv_status.fb); |
+ |
+ return 0; |
+} |
+ |
+/* |
+ * struct for video |
+ */ |
+static struct v4l2_file_operations s5p_tv_fops = { |
+ .owner = THIS_MODULE, |
+ .open = s5p_tv_open, |
+ .ioctl = s5p_tv_ioctl, |
+ .release = s5p_tv_release |
+}; |
+static struct v4l2_file_operations s5p_tv_vid_fops = { |
+ .owner = THIS_MODULE, |
+ .open = s5p_tv_vid_open, |
+ .ioctl = s5p_tv_vid_ioctl, |
+ .release = s5p_tv_vid_release |
+}; |
+ |
+ |
+void s5p_tv_vdev_release(struct video_device *vdev) |
+{ |
+ kfree(vdev); |
+} |
+ |
+struct video_device s5p_tvout[] = { |
+ [0] = { |
+ .name = "S5PV210 TVOUT ctrl", |
+ .fops = &s5p_tv_fops, |
+ .ioctl_ops = &s5p_tv_v4l2_ops, |
+ .release = s5p_tv_vdev_release, |
+ .minor = TVOUT_MINOR_TVOUT, |
+ .tvnorms = V4L2_STD_ALL_HD, |
+ }, |
+ [1] = { |
+ .name = "S5PV210 TVOUT for Video", |
+ .fops = &s5p_tv_vid_fops, |
+ .ioctl_ops = &s5p_tv_v4l2_vid_ops, |
+ .release = s5p_tv_vdev_release, |
+ .minor = TVOUT_MINOR_VID, |
+ .tvnorms = V4L2_STD_ALL_HD, |
+ }, |
+}; |
+ |
+void s5p_tv_kobject_uevent(struct work_struct *data) |
+{ |
+ int hpd_state = s5p_hpd_get_state(); |
+ |
+ if (hpd_state) { |
+ printk(KERN_ERR "Event] Send UEvent = %d\n", hpd_state); |
+ kobject_uevent(&(s5p_tvout[0].dev.kobj), KOBJ_ADD); |
+ kobject_uevent(&(s5p_tvout[1].dev.kobj), KOBJ_ADD); |
+ } else { |
+ printk(KERN_ERR "Event] Send UEvent = %d\n", hpd_state); |
+ kobject_uevent(&(s5p_tvout[0].dev.kobj), KOBJ_REMOVE); |
+ kobject_uevent(&(s5p_tvout[1].dev.kobj), KOBJ_REMOVE); |
+ } |
+ |
+} |
+ |
+ |
+ |
+#define S5P_TVMAX_CTRLS ARRAY_SIZE(s5p_tvout) |
+ |
+static int __devinit s5p_tv_probe(struct platform_device *pdev) |
+{ |
+ int irq_num; |
+ int ret; |
+ int i; |
+ |
+ s5ptv_status.dev_fb = &pdev->dev; |
+ |
+ tv_sdout_probe(pdev, 0); |
+ tv_vp_probe(pdev, 1); |
+ tv_mixer_probe(pdev, 2); |
+ |
+ tv_hdmi_probe(pdev, 3, 4); |
+ |
+ tv_hdcp_init(); |
+ |
+ tv_clk_get(pdev, &s5ptv_status); |
+ |
+#ifdef FIX_27M_UNSTABLE_ISSUE /* for smdkc100 pop */ |
+ writel(0x1, S5PC1XX_GPA0_BASE + 0x56c); |
+#endif |
+ |
+#ifdef I2C_BASE |
+ /* for dev_dbg err. */ |
+ spin_lock_init(&slock_hpd); |
+ |
+ /* for bh */ |
+ INIT_WORK(&ws_hpd, (void *)set_ddc_port); |
+#endif |
+ /* interrupt */ |
+ /* Changing interrupt mode into SHARED */ |
+ irq_num = platform_get_irq(pdev, 0); |
+ if (irq_num < 0) { |
+ printk(KERN_ERR "failed to get %s irq resource\n", "mixer"); |
+ ret = -ENOENT; |
+ goto out; |
+ } |
+ |
+ ret = request_irq(irq_num, tv_mixer_irq, |
+ IRQF_SHARED, pdev->name, pdev); |
+ if (ret != 0) { |
+ printk(KERN_ERR "failed to install %s irq (%d)\n", "mixer", |
+ ret); |
+ goto out; |
+ } |
+ |
+ TVOUT_IRQ_INIT(irq_num, ret, pdev, 1, out_hdmi_irq, |
+ tv_hdmi_irq , "hdmi"); |
+ TVOUT_IRQ_INIT(irq_num, ret, pdev, 2, out_tvenc_irq, |
+ s5p_tvenc_irq, "tvenc"); |
+ |
+ set_irq_type(IRQ_EINT5, IRQ_TYPE_LEVEL_LOW); |
+ |
+ /* v4l2 video device registration */ |
+ for (i = 0; i < S5P_TVMAX_CTRLS; i++) { |
+ s5ptv_status.video_dev[i] = &s5p_tvout[i]; |
+ |
+ if (video_register_device(s5ptv_status.video_dev[i], |
+ VFL_TYPE_GRABBER, s5p_tvout[i].minor) != 0) { |
+ |
+ dev_err(&pdev->dev, |
+ "Couldn't register tvout driver.\n"); |
+ return 0; |
+ } |
+ } |
+ |
+ /* for default start up */ |
+ tv_if_init_param(); |
+ |
+ s5ptv_status.tvout_param.disp_mode = TVOUT_720P_60; |
+ s5ptv_status.tvout_param.out_mode = TVOUT_OUTPUT_HDMI_RGB; |
+ |
+ mutex_init(&s5ptv_status.fb_lock); |
+ |
+ s5ptvfb_set_lcd_info(&s5ptv_status); |
+ |
+ /* prepare memory */ |
+ if (s5ptvfb_alloc_framebuffer()) |
+ goto err_alloc; |
+ |
+ if (s5ptvfb_register_framebuffer()) |
+ goto err_alloc; |
+ |
+ mutex_for_fo = |
+ kmalloc(sizeof(struct mutex), GFP_KERNEL); |
+ |
+ if (mutex_for_fo == NULL) { |
+ dev_err(&pdev->dev, |
+ "failed to create mutex handle\n"); |
+ goto out; |
+ } |
+ |
+#ifdef I2C_BASE |
+ mutex_for_i2c = |
+ kmalloc(sizeof(struct mutex), GFP_KERNEL); |
+ |
+ if (mutex_for_i2c == NULL) { |
+ dev_err(&pdev->dev, |
+ "failed to create mutex handle\n"); |
+ goto out; |
+ } |
+ mutex_init(mutex_for_i2c); |
+#endif |
+ mutex_init(mutex_for_fo); |
+ |
+ return 0; |
+ |
+err_alloc: |
+ |
+out_tvenc_irq: |
+ free_irq(IRQ_HDMI, pdev); |
+ |
+out_hdmi_irq: |
+ free_irq(IRQ_MIXER, pdev); |
+ |
+out: |
+ printk(KERN_ERR "not found (%d). \n", ret); |
+ |
+ return ret; |
+} |
+ |
+static int s5p_tv_remove(struct platform_device *pdev) |
+{ |
+ tv_hdmi_release(pdev); |
+ tv_sdout_release(pdev); |
+ tv_mixer_release(pdev); |
+ tv_vp_release(pdev); |
+ |
+#ifdef I2C_BASE |
+ i2c_del_driver(&hdcp_i2c_driver); |
+#endif |
+ clk_disable(s5ptv_status.tvenc_clk); |
+ clk_disable(s5ptv_status.vp_clk); |
+ clk_disable(s5ptv_status.mixer_clk); |
+ clk_disable(s5ptv_status.hdmi_clk); |
+ clk_disable(s5ptv_status.sclk_hdmi); |
+ clk_disable(s5ptv_status.sclk_mixer); |
+ clk_disable(s5ptv_status.sclk_tv); |
+ |
+ clk_put(s5ptv_status.tvenc_clk); |
+ clk_put(s5ptv_status.vp_clk); |
+ clk_put(s5ptv_status.mixer_clk); |
+ clk_put(s5ptv_status.hdmi_clk); |
+ clk_put(s5ptv_status.sclk_hdmi); |
+ clk_put(s5ptv_status.sclk_mixer); |
+ clk_put(s5ptv_status.sclk_tv); |
+ |
+ free_irq(IRQ_MIXER, pdev); |
+ free_irq(IRQ_HDMI, pdev); |
+ free_irq(IRQ_TVENC, pdev); |
+ free_irq(IRQ_EINT5, pdev); |
+ |
+ mutex_destroy(mutex_for_fo); |
+#ifdef I2C_BASE |
+ mutex_destroy(mutex_for_i2c); |
+#endif |
+ |
+ s5ptvfb_unregister_framebuffer(); |
+ s5ptvfb_free_framebuffer(); |
+ return 0; |
+} |
+ |
+ |
+#ifdef CONFIG_PM |
+int s5p_tv_suspend(struct platform_device *dev, pm_message_t state) |
+{ |
+ /* video layer stop */ |
+ if (s5ptv_status.vp_layer_enable) { |
+ tv_vlayer_stop(); |
+ s5ptv_status.vp_layer_enable = true; |
+ |
+ } |
+ |
+ /* grp0 layer stop */ |
+ if (s5ptv_status.grp_layer_enable[0]) { |
+ tv_grp_stop(VM_GPR0_LAYER); |
+ s5ptv_status.grp_layer_enable[VM_GPR0_LAYER] = true; |
+ } |
+ |
+ /* grp1 layer stop */ |
+ if (s5ptv_status.grp_layer_enable[1]) { |
+ tv_grp_stop(VM_GPR1_LAYER); |
+ s5ptv_status.grp_layer_enable[VM_GPR0_LAYER] = true; |
+ } |
+ |
+ /* tv off */ |
+ if (s5ptv_status.tvout_output_enable) { |
+ tv_if_stop(); |
+ s5ptv_status.tvout_output_enable = true; |
+ s5ptv_status.tvout_param_available = true; |
+ |
+ /* clk & power off */ |
+ if (s5p_tv_clk_gate(false) < 0) { |
+ printk(KERN_ERR "[Error]Cannot suspend\n"); |
+ return -1; |
+ } |
+ tv_phy_power(false); |
+ |
+ } |
+ |
+ return 0; |
+} |
+ |
+int s5p_tv_resume(struct platform_device *dev) |
+{ |
+ /* tv on */ |
+ if (s5ptv_status.tvout_output_enable) { |
+ |
+ /* clk & power on */ |
+ if (s5p_tv_clk_gate(true) < 0) { |
+ printk(KERN_ERR "[Error]Cannot resume\n"); |
+ return -1; |
+ } |
+ tv_phy_power(true); |
+ |
+ tv_if_start(); |
+ } |
+ |
+ /* video layer start */ |
+ if (s5ptv_status.vp_layer_enable) |
+ tv_vlayer_start(); |
+ |
+ /* grp0 layer start */ |
+ if (s5ptv_status.grp_layer_enable[0]) |
+ tv_grp_start(VM_GPR0_LAYER); |
+ |
+ /* grp1 layer start */ |
+ if (s5ptv_status.grp_layer_enable[1]) |
+ tv_grp_start(VM_GPR1_LAYER); |
+ |
+ s5ptvfb_display_on(&s5ptv_status); |
+ s5ptvfb_set_par(s5ptv_status.fb); |
+ |
+ return 0; |
+} |
+#else |
+#define s5p_tv_suspend NULL |
+#define s5p_tv_resume NULL |
+#endif |
+ |
+static struct platform_driver s5p_tv_driver = { |
+ .probe = s5p_tv_probe, |
+ .remove = s5p_tv_remove, |
+ .suspend = s5p_tv_suspend, |
+ .resume = s5p_tv_resume, |
+ .driver = { |
+ .name = "s5p-tvout", |
+ .owner = THIS_MODULE, |
+ }, |
+}; |
+ |
+static char banner[] __initdata = |
+ KERN_INFO "S5V210 TVOUT Driver, (c) 2010 Samsung Electronics\n"; |
+ |
+int __init s5p_tv_init(void) |
+{ |
+ int ret; |
+ |
+ printk(banner); |
+ |
+ ret = platform_driver_register(&s5p_tv_driver); |
+ |
+ if (ret) { |
+ printk(KERN_ERR "Platform Device Register Failed %d\n", ret); |
+ return -1; |
+ } |
+ |
+ return 0; |
+} |
+ |
+static void __exit s5p_tv_exit(void) |
+{ |
+ platform_driver_unregister(&s5p_tv_driver); |
+} |
+ |
+late_initcall(s5p_tv_init); |
+module_exit(s5p_tv_exit); |
+ |
+MODULE_AUTHOR("SangPil Moon"); |
+MODULE_DESCRIPTION("S5V210 TVOUT driver"); |
+MODULE_LICENSE("GPL"); |