| 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");
|
|
|