| Index: drivers/media/video/samsung/tv20/hpd.c
|
| diff --git a/drivers/media/video/samsung/tv20/hpd.c b/drivers/media/video/samsung/tv20/hpd.c
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..11c8bf50c080bff58f8031905a674dd7508c14b5
|
| --- /dev/null
|
| +++ b/drivers/media/video/samsung/tv20/hpd.c
|
| @@ -0,0 +1,357 @@
|
| +/* linux/drivers/media/video/samsung/tv20/hpd.c
|
| +*
|
| +* Copyright (c) 2010 Samsung Electronics Co., Ltd.
|
| +* http://www.samsung.com/
|
| +*
|
| +* S5PV210 - hpd interface ftn 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/module.h>
|
| +#include <linux/kernel.h>
|
| +#include <linux/interrupt.h>
|
| +#include <linux/fs.h>
|
| +#include <linux/miscdevice.h>
|
| +#include <linux/errno.h>
|
| +#include <linux/wait.h>
|
| +#include <linux/poll.h>
|
| +#include <linux/irq.h>
|
| +#include <linux/kobject.h>
|
| +#include <linux/workqueue.h>
|
| +
|
| +#include <linux/io.h>
|
| +
|
| +#include <mach/gpio.h>
|
| +#include <plat/gpio-cfg.h>
|
| +
|
| +#include <mach/regs-hdmi.h>
|
| +
|
| +#include "s5p_tv.h"
|
| +#include "hpd.h"
|
| +
|
| +/* #define HPDDEBUG */
|
| +
|
| +#define USEEXTINT
|
| +
|
| +#ifdef HPDDEBUG
|
| +#define HPDIFPRINTK(fmt, args...) \
|
| + printk(KERN_INFO "[HPD_IF] %s: " fmt, __func__ , ## args)
|
| +#else
|
| +#define HPDIFPRINTK(fmt, args...)
|
| +#endif
|
| +
|
| +static struct hpd_struct hpd_struct;
|
| +
|
| +#ifndef USEEXTINT
|
| +static int last_hpd_state;
|
| +#endif
|
| +
|
| +#ifdef CONFIG_PM
|
| + #ifndef USEEXTINT
|
| + static bool hdmi_on;
|
| + #endif
|
| +#endif
|
| +
|
| +static DECLARE_WORK(hpd_work, s5p_tv_kobject_uevent);
|
| +
|
| +int s5p_hpd_get_state(void)
|
| +{
|
| + return atomic_read(&hpd_struct.state);
|
| +}
|
| +
|
| +
|
| +int s5p_hpd_open(struct inode *inode, struct file *file)
|
| +{
|
| +#ifdef USEEXTINT
|
| +#else
|
| + /* adjust the duration of HPD detection */
|
| + s5p_tv_clk_gate(true);
|
| + hdmi_on = true;
|
| +
|
| + s5p_hdmi_hpd_gen();
|
| +
|
| + s3c_gpio_cfgpin(S5PV210_GPH1(5), S5P_GPH1_5_HDMI_HPD);
|
| + s3c_gpio_setpull(S5PV210_GPH1(5), S3C_GPIO_PULL_UP);
|
| +
|
| + s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_PLUG);
|
| + s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_UNPLUG);
|
| +#endif
|
| +
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +int s5p_hpd_release(struct inode *inode, struct file *file)
|
| +{
|
| + /* disable HPD interrupts */
|
| +#ifdef USEEXTINT
|
| +#else
|
| + s5p_tv_clk_gate(false);
|
| + hdmi_on = false;
|
| +
|
| + s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_PLUG);
|
| + s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_UNPLUG);
|
| +#endif
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +ssize_t s5p_hpd_read(struct file *file, char __user *buffer,
|
| + size_t count, loff_t *ppos)
|
| +{
|
| + ssize_t retval;
|
| +
|
| + if (wait_event_interruptible(hpd_struct.waitq,
|
| + atomic_read(&hpd_struct.state) != -1))
|
| + return -ERESTARTSYS;
|
| +
|
| + spin_lock_irq(&hpd_struct.lock);
|
| +
|
| + retval = put_user(atomic_read(&hpd_struct.state),
|
| + (unsigned int __user *) buffer);
|
| +
|
| + atomic_set(&hpd_struct.state, -1);
|
| +
|
| + spin_unlock_irq(&hpd_struct.lock);
|
| +
|
| + return retval;
|
| +}
|
| +
|
| +unsigned int s5p_hpd_poll(struct file *file, poll_table *wait)
|
| +{
|
| + poll_wait(file, &hpd_struct.waitq, wait);
|
| +
|
| + if (atomic_read(&hpd_struct.state) != -1)
|
| + return POLLIN | POLLRDNORM;
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +static const struct file_operations hpd_fops = {
|
| + .owner = THIS_MODULE,
|
| + .open = s5p_hpd_open,
|
| + .release = s5p_hpd_release,
|
| + .read = s5p_hpd_read,
|
| + .poll = s5p_hpd_poll,
|
| +};
|
| +
|
| +static struct miscdevice hpd_misc_device = {
|
| + HPD_MINOR,
|
| + "HPD",
|
| + &hpd_fops,
|
| +};
|
| +
|
| +/*
|
| + * HPD interrupt handler
|
| + *
|
| + * Handles interrupt requests from HPD hardware.
|
| + * Handler changes value of internal variable and notifies waiting thread.
|
| + */
|
| +irqreturn_t s5p_hpd_irq_handler(int irq, void *dev_id)
|
| +{
|
| +#ifdef USEEXTINT
|
| + spin_lock_irq(&hpd_struct.lock);
|
| +
|
| + if (gpio_get_value(S5PV210_GPH1(5))) {
|
| + if (atomic_read(&hpd_struct.state) == HPD_HI)
|
| + goto out;
|
| + atomic_set(&hpd_struct.state, HPD_HI);
|
| + } else {
|
| + if (atomic_read(&hpd_struct.state) == HPD_LO)
|
| + goto out;
|
| + atomic_set(&hpd_struct.state, HPD_LO);
|
| + }
|
| +
|
| + if (atomic_read(&hpd_struct.state))
|
| + set_irq_type(IRQ_EINT13, IRQ_TYPE_LEVEL_LOW);
|
| + else
|
| + set_irq_type(IRQ_EINT13, IRQ_TYPE_LEVEL_HIGH);
|
| +
|
| + /* Send UEvent for HPD - added by jyg1004 */
|
| + schedule_work(&hpd_work);
|
| +
|
| + spin_unlock_irq(&hpd_struct.lock);
|
| +
|
| + HPDIFPRINTK("hpd_status = %d\n", atomic_read(&hpd_struct.state));
|
| +
|
| + return IRQ_HANDLED;
|
| +out:
|
| + spin_unlock_irq(&hpd_struct.lock);
|
| +
|
| + return IRQ_HANDLED;
|
| +#else
|
| + u8 flag;
|
| + int ret = IRQ_HANDLED;
|
| +
|
| + /* read flag register */
|
| + flag = s5p_hdmi_get_interrupts();
|
| +
|
| + /* is this our interrupt? */
|
| +
|
| + if (!(flag & (1 << HDMI_IRQ_HPD_PLUG | 1 << HDMI_IRQ_HPD_UNPLUG))) {
|
| + ret = IRQ_NONE;
|
| + goto out;
|
| + }
|
| +
|
| + /* workaround: ignore HPD IRQ caused by reseting HDCP engine */
|
| + if (s5p_hdmi_get_swhpd_status()) {
|
| + s5p_hdmi_swhpd_disable();
|
| + /* clear pending bit */
|
| + s5p_hdmi_clear_pending(HDMI_IRQ_HPD_UNPLUG);
|
| + s5p_hdmi_clear_pending(HDMI_IRQ_HPD_PLUG);
|
| + ret = IRQ_HANDLED;
|
| + goto out;
|
| + }
|
| +
|
| + if (flag == (1 << HDMI_IRQ_HPD_PLUG | 1 << HDMI_IRQ_HPD_UNPLUG)) {
|
| + HPDIFPRINTK("HPD_HI && HPD_LO\n");
|
| +
|
| + if (last_hpd_state == HPD_HI && s5p_hdmi_get_hpd_status())
|
| + /*if ( last_hpd_state == HPD_HI ) */
|
| + flag = 1 << HDMI_IRQ_HPD_UNPLUG;
|
| + else
|
| + flag = 1 << HDMI_IRQ_HPD_PLUG;
|
| + }
|
| +
|
| + if (flag & (1 << HDMI_IRQ_HPD_PLUG)) {
|
| + HPDIFPRINTK("HPD_HI\n");
|
| + /* clear pending bit */
|
| + s5p_hdmi_clear_pending(HDMI_IRQ_HPD_PLUG);
|
| + s5p_hdmi_clear_pending(HDMI_IRQ_HPD_UNPLUG);
|
| + atomic_set(&hpd_struct.state, HPD_HI);
|
| + /* workaround: enable HDMI_IRQ_HPD_UNPLUG interrupt */
|
| + s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_PLUG);
|
| + s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_UNPLUG);
|
| + last_hpd_state = HPD_HI;
|
| + wake_up_interruptible(&hpd_struct.waitq);
|
| + } else if (flag & (1 << HDMI_IRQ_HPD_UNPLUG)) {
|
| + HPDIFPRINTK("HPD_LO\n");
|
| + /* clear pending bit */
|
| + s5p_hdmi_clear_pending(HDMI_IRQ_HPD_PLUG);
|
| + s5p_hdmi_clear_pending(HDMI_IRQ_HPD_UNPLUG);
|
| + atomic_set(&hpd_struct.state, HPD_LO);
|
| + /* workaround: disable HDMI_IRQ_HPD_UNPLUG interrupt */
|
| + last_hpd_state = HPD_LO;
|
| + s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_UNPLUG);
|
| + s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_PLUG);
|
| + wake_up_interruptible(&hpd_struct.waitq);
|
| + }
|
| +
|
| +out:
|
| + return IRQ_HANDLED;
|
| +
|
| +#endif
|
| +}
|
| +
|
| +static int __init s5p_hpd_probe(struct platform_device *pdev)
|
| +{
|
| + if (misc_register(&hpd_misc_device)) {
|
| + printk(KERN_WARNING
|
| + "Couldn't register device 10, %d.\n", HPD_MINOR);
|
| + return -EBUSY;
|
| + }
|
| +
|
| + init_waitqueue_head(&hpd_struct.waitq);
|
| +
|
| + spin_lock_init(&hpd_struct.lock);
|
| +
|
| + atomic_set(&hpd_struct.state, -1);
|
| +
|
| +#ifdef USEEXTINT
|
| + s3c_gpio_cfgpin(S5PV210_GPH1(5), S5P_GPH1_5_EXT_INT31_5);
|
| + s3c_gpio_setpull(S5PV210_GPH1(5), S3C_GPIO_PULL_UP);
|
| +
|
| + if (gpio_get_value(S5PV210_GPH1(5)))
|
| + atomic_set(&hpd_struct.state, HPD_HI);
|
| + else
|
| + atomic_set(&hpd_struct.state, HPD_LO);
|
| +
|
| + set_irq_type(IRQ_EINT13, IRQ_TYPE_EDGE_BOTH);
|
| +
|
| + if (request_irq(IRQ_EINT13, s5p_hpd_irq_handler, IRQF_DISABLED,
|
| + "hpd", s5p_hpd_irq_handler)) {
|
| + printk(KERN_ERR "failed to install %s irq\n", "hpd");
|
| + misc_deregister(&hpd_misc_device);
|
| + return -EIO;
|
| + }
|
| +#else
|
| + /* must be checked */
|
| + s5p_hdmi_register_isr(s5p_hpd_irq_handler, (u8)HDMI_IRQ_HPD_PLUG);
|
| + s5p_hdmi_register_isr(s5p_hpd_irq_handler, (u8)HDMI_IRQ_HPD_UNPLUG);
|
| +#endif
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +static int s5p_hpd_remove(struct platform_device *pdev)
|
| +{
|
| + return 0;
|
| +}
|
| +
|
| +
|
| +#ifdef CONFIG_PM
|
| +
|
| +int s5p_hpd_suspend(struct platform_device *dev, pm_message_t state)
|
| +{
|
| +#ifndef USEEXTINT
|
| + if (hdmi_on)
|
| + s5p_tv_clk_gate(false);
|
| +#endif
|
| + return 0;
|
| +}
|
| +
|
| +int s5p_hpd_resume(struct platform_device *dev)
|
| +{
|
| +#ifndef USEEXTINT
|
| + if (hdmi_on)
|
| + s5p_tv_clk_gate(true);
|
| +#endif
|
| + return 0;
|
| +}
|
| +#else
|
| +#define s5p_hpd_suspend NULL
|
| +#define s5p_hpd_resume NULL
|
| +#endif
|
| +
|
| +static struct platform_driver s5p_hpd_driver = {
|
| + .probe = s5p_hpd_probe,
|
| + .remove = s5p_hpd_remove,
|
| + .suspend = s5p_hpd_suspend,
|
| + .resume = s5p_hpd_resume,
|
| + .driver = {
|
| + .name = "s5p-hpd",
|
| + .owner = THIS_MODULE,
|
| + },
|
| +};
|
| +
|
| +static char banner[] __initdata =
|
| + "S5PV210 HPD Driver, (c) 2010 Samsung Electronics\n";
|
| +
|
| +int __init s5p_hpd_init(void)
|
| +{
|
| + int ret;
|
| +
|
| + printk(banner);
|
| +
|
| + ret = platform_driver_register(&s5p_hpd_driver);
|
| +
|
| + if (ret) {
|
| + printk(KERN_ERR "Platform Device Register Failed %d\n", ret);
|
| + return -1;
|
| + }
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +static void __exit s5p_hpd_exit(void)
|
| +{
|
| + misc_deregister(&hpd_misc_device);
|
| +}
|
| +
|
| +module_init(s5p_hpd_init);
|
| +module_exit(s5p_hpd_exit);
|
| +
|
| +
|
|
|