This patch adds support HPD(Hot-Plug Detection) driver for S5P TVOUT driver. HPD driver generates event and send it to application when user connects/disconnects HDMI cable to/from source and sink device. [based on work originally written by Sangpil Moon <sangpil.moon@xxxxxxxxxxx>] Cc: Kukjin Kim <kgene.kim@xxxxxxxxxxx> Acked-by: KyungHwan Kim <kh.k.kim@xxxxxxxxxxx> Signed-off-by: Jiun Yu <jiun.yu@xxxxxxxxxxx> Signed-off-by: Abhilash Kesavan <a.kesavan@xxxxxxxxxxx> --- drivers/media/video/s5p-tvout/s5p_tvout_hpd.c | 405 +++++++++++++++++++++++++ 1 files changed, 405 insertions(+), 0 deletions(-) create mode 100644 drivers/media/video/s5p-tvout/s5p_tvout_hpd.c diff --git a/drivers/media/video/s5p-tvout/s5p_tvout_hpd.c b/drivers/media/video/s5p-tvout/s5p_tvout_hpd.c new file mode 100644 index 0000000..f307286 --- /dev/null +++ b/drivers/media/video/s5p-tvout/s5p_tvout_hpd.c @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * HPD(Hot-Plug Detection) Interface for Samsung S5P 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/miscdevice.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/poll.h> + +#include <plat/tvout.h> + +#include "hw_if/hw_if.h" +#include "s5p_tvout_common_lib.h" + +#undef tvout_dbg + +#ifdef CONFIG_HPD_DEBUG +#define tvout_dbg(fmt, ...) \ + printk(KERN_INFO "\t[HPD] %s(): " fmt, \ + __func__, ##__VA_ARGS__) +#else +#define tvout_dbg(fmt, ...) +#endif + +/* /dev/hpd (Major 10, Minor 243) */ +#define HPD_MINOR 243 + +#define HPD_LO 0 +#define HPD_HI 1 + +#define HDMI_ON 1 +#define HDMI_OFF 0 + +struct hpd_struct { + spinlock_t lock; + wait_queue_head_t waitq; + atomic_t state; + void (*int_src_hdmi_hpd)(void); + void (*int_src_ext_hpd)(void); + int (*read_gpio)(void); + int irq_n; +}; + +static struct hpd_struct hpd_struct; + +static int last_hpd_state; +atomic_t hdmi_status; +atomic_t poll_state; + +static struct kobject *hpd_tvout_kobj, *hpd_video_kobj; + +static void s5p_hpd_kobject_uevent(void) +{ + int hpd_state = atomic_read(&hpd_struct.state); + + if (hpd_state) { + tvout_err("Event] Send UEvent = %d\n", hpd_state); + kobject_uevent(hpd_tvout_kobj, KOBJ_ONLINE); + kobject_uevent(hpd_video_kobj, KOBJ_ONLINE); + } else { + tvout_err("Event] Send UEvent = %d\n", hpd_state); + kobject_uevent(hpd_tvout_kobj, KOBJ_OFFLINE); + kobject_uevent(hpd_video_kobj, KOBJ_OFFLINE); + } +} + +static DECLARE_WORK(hpd_work, (void *)s5p_hpd_kobject_uevent); + +void s5p_hpd_set_kobj(struct kobject *tvout_kobj, struct kobject *video_kobj) +{ + hpd_tvout_kobj = tvout_kobj; + hpd_video_kobj = video_kobj; +} + +static int s5p_hpd_open(struct inode *inode, struct file *file) +{ + atomic_set(&poll_state, 1); + + return 0; +} + +static int s5p_hpd_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t s5p_hpd_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + ssize_t retval; + + spin_lock_irq(&hpd_struct.lock); + + retval = put_user(atomic_read(&hpd_struct.state), + (unsigned int __user *) buffer); + + atomic_set(&poll_state, -1); + + spin_unlock_irq(&hpd_struct.lock); + + return retval; +} + +static unsigned int s5p_hpd_poll(struct file *file, poll_table *wait) +{ + poll_wait(file, &hpd_struct.waitq, wait); + + if (atomic_read(&poll_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 = { + .minor = HPD_MINOR, + .name = "HPD", + .fops = &hpd_fops, +}; + +int s5p_hpd_set_hdmiint(void) +{ + /* EINT -> HDMI */ + + set_irq_type(hpd_struct.irq_n, IRQ_TYPE_NONE); + + if (last_hpd_state) + s5p_hdmi_reg_intc_enable(HDMI_IRQ_HPD_UNPLUG, 0); + else + s5p_hdmi_reg_intc_enable(HDMI_IRQ_HPD_PLUG, 0); + + atomic_set(&hdmi_status, HDMI_ON); + + hpd_struct.int_src_hdmi_hpd(); + + s5p_hdmi_reg_hpd_gen(); + + if (s5p_hdmi_reg_get_hpd_status()) + s5p_hdmi_reg_intc_enable(HDMI_IRQ_HPD_UNPLUG, 1); + else + s5p_hdmi_reg_intc_enable(HDMI_IRQ_HPD_PLUG, 1); + + return 0; +} + +int s5p_hpd_set_eint(void) +{ + /* HDMI -> EINT */ + + atomic_set(&hdmi_status, HDMI_OFF); + + s5p_hdmi_reg_intc_clear_pending(HDMI_IRQ_HPD_PLUG); + s5p_hdmi_reg_intc_clear_pending(HDMI_IRQ_HPD_UNPLUG); + + s5p_hdmi_reg_intc_enable(HDMI_IRQ_HPD_PLUG, 0); + s5p_hdmi_reg_intc_enable(HDMI_IRQ_HPD_UNPLUG, 0); + + hpd_struct.int_src_ext_hpd(); + + return 0; +} + +static int s5p_hdp_irq_eint(int irq) +{ + if (hpd_struct.read_gpio()) { + set_irq_type(hpd_struct.irq_n, IRQ_TYPE_LEVEL_LOW); + + if (atomic_read(&hpd_struct.state) == HPD_HI) + return IRQ_HANDLED; + + atomic_set(&hpd_struct.state, HPD_HI); + atomic_set(&poll_state, 1); + + last_hpd_state = HPD_HI; + wake_up_interruptible(&hpd_struct.waitq); + } else { + set_irq_type(hpd_struct.irq_n, IRQ_TYPE_LEVEL_HIGH); + + if (atomic_read(&hpd_struct.state) == HPD_LO) + return IRQ_HANDLED; + + atomic_set(&hpd_struct.state, HPD_LO); + atomic_set(&poll_state, 1); + + last_hpd_state = HPD_LO; + wake_up_interruptible(&hpd_struct.waitq); + } + schedule_work(&hpd_work); + + tvout_dbg("%s\n", atomic_read(&hpd_struct.state) == HPD_HI ? + "HPD HI" : "HPD LO"); + + return IRQ_HANDLED; +} + +static int s5p_hpd_irq_hdmi(int irq) +{ + u8 flag; + int ret = IRQ_HANDLED; + + /* read flag register */ + flag = s5p_hdmi_reg_intc_status(); + + if (s5p_hdmi_reg_get_hpd_status()) + s5p_hdmi_reg_intc_clear_pending(HDMI_IRQ_HPD_PLUG); + else + s5p_hdmi_reg_intc_clear_pending(HDMI_IRQ_HPD_UNPLUG); + + s5p_hdmi_reg_intc_enable(HDMI_IRQ_HPD_UNPLUG, 0); + s5p_hdmi_reg_intc_enable(HDMI_IRQ_HPD_PLUG, 0); + + /* is this our interrupt? */ + if (!(flag & ((1 << HDMI_IRQ_HPD_PLUG) | (1 << HDMI_IRQ_HPD_UNPLUG)))) { + ret = IRQ_NONE; + + goto out; + } + + if (flag == ((1 << HDMI_IRQ_HPD_PLUG) | (1 << HDMI_IRQ_HPD_UNPLUG))) { + tvout_dbg("HPD_HI && HPD_LO\n"); + + if (last_hpd_state == HPD_HI && s5p_hdmi_reg_get_hpd_status()) + flag = 1 << HDMI_IRQ_HPD_UNPLUG; + else + flag = 1 << HDMI_IRQ_HPD_PLUG; + } + + if (flag & (1 << HDMI_IRQ_HPD_PLUG)) { + s5p_hdmi_reg_intc_enable(HDMI_IRQ_HPD_UNPLUG, 1); + + atomic_set(&hpd_struct.state, HPD_HI); + atomic_set(&poll_state, 1); + + last_hpd_state = HPD_HI; + wake_up_interruptible(&hpd_struct.waitq); + + s5p_hdcp_encrypt_stop(true); + + tvout_dbg("HPD_HI\n"); + + } else if (flag & (1 << HDMI_IRQ_HPD_UNPLUG)) { + s5p_hdcp_encrypt_stop(false); + + s5p_hdmi_reg_intc_enable(HDMI_IRQ_HPD_PLUG, 1); + + atomic_set(&hpd_struct.state, HPD_LO); + atomic_set(&poll_state, 1); + + last_hpd_state = HPD_LO; + wake_up_interruptible(&hpd_struct.waitq); + + tvout_dbg("HPD_LO\n"); + } + schedule_work(&hpd_work); + +out: + return IRQ_HANDLED; +} + +/* + * HPD interrupt handler + * + * Handles interrupt requests from HPD hardware. + * Handler changes value of internal variable and notifies waiting thread. + */ +static irqreturn_t s5p_hpd_irq_handler(int irq, void *dev_id) +{ + irqreturn_t ret = IRQ_HANDLED; + + /* check HDMI status */ + + if (atomic_read(&hdmi_status)) { + /* HDMI on */ + ret = s5p_hpd_irq_hdmi(irq); + tvout_dbg("HDMI HPD interrupt\n"); + } else { + /* HDMI off */ + ret = s5p_hdp_irq_eint(irq); + tvout_dbg("EINT HPD interrupt\n"); + } + + return ret; +} + +static int __init s5p_hpd_probe(struct platform_device *pdev) +{ + struct s5p_platform_hpd *pdata; + int ret; + + 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); + + atomic_set(&hdmi_status, HDMI_OFF); + + pdata = to_tvout_plat(&pdev->dev); + + if (pdata->int_src_hdmi_hpd) + hpd_struct.int_src_hdmi_hpd = (void (*)(void))pdata->int_src_hdmi_hpd; + + if (pdata->int_src_ext_hpd) + hpd_struct.int_src_ext_hpd = (void (*)(void))pdata->int_src_ext_hpd; + + if (pdata->read_gpio) + hpd_struct.read_gpio = (int (*)(void))pdata->read_gpio; + + hpd_struct.irq_n = platform_get_irq(pdev, 0); + + hpd_struct.int_src_ext_hpd(); + if (hpd_struct.read_gpio()) { + atomic_set(&hpd_struct.state, HPD_HI); + last_hpd_state = HPD_HI; + } else { + atomic_set(&hpd_struct.state, HPD_LO); + last_hpd_state = HPD_LO; + } + + set_irq_type(hpd_struct.irq_n, IRQ_TYPE_EDGE_BOTH); + + if (request_irq(hpd_struct.irq_n, (irq_handler_t)s5p_hpd_irq_handler, + IRQF_DISABLED, "hpd", (void *)(&pdev->dev))) { + printk(KERN_ERR "failed to install hpd irq\n"); + return -EBUSY; + } + + s5p_hdmi_reg_intc_set_isr(s5p_hpd_irq_handler, (u8)HDMI_IRQ_HPD_PLUG); + s5p_hdmi_reg_intc_set_isr(s5p_hpd_irq_handler, (u8)HDMI_IRQ_HPD_UNPLUG); + + return 0; +} + +static int s5p_hpd_remove(struct platform_device *pdev) +{ + return 0; +} + +#ifdef CONFIG_PM +static int s5p_hpd_suspend(struct platform_device *dev, pm_message_t state) +{ + hpd_struct.int_src_ext_hpd(); + return 0; +} + +static int s5p_hpd_resume(struct platform_device *dev) +{ + if (atomic_read(&hdmi_status) == HDMI_ON) + hpd_struct.int_src_hdmi_hpd(); + + 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-tvout-hpd", + .owner = THIS_MODULE, + }, +}; + +static int __init s5p_hpd_init(void) +{ + printk(KERN_INFO "S5P HPD for TVOUT Driver, Copyright (c) 2011 Samsung Electronics Co., LTD.\n"); + + return platform_driver_register(&s5p_hpd_driver); +} + +static void __exit s5p_hpd_exit(void) +{ + misc_deregister(&hpd_misc_device); +} +module_init(s5p_hpd_init); +module_exit(s5p_hpd_exit); + +MODULE_AUTHOR("Abhilash Kesavan <a.kesavan@xxxxxxxxxxx>"); +MODULE_DESCRIPTION("Samsung S5P HPD(Hot-Plug Detection) driver for TVOUT"); +MODULE_LICENSE("GPL"); -- 1.7.1 -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html