diff mbox

[5/5,media] s5p-tvout: Add HPD driver for S5P TVOUT

Message ID 1298620413-24182-6-git-send-email-a.kesavan@samsung.com (mailing list archive)
State RFC
Headers show

Commit Message

Abhilash Kesavan Feb. 25, 2011, 7:53 a.m. UTC
None
diff mbox

Patch

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@samsung.com>");
+MODULE_DESCRIPTION("Samsung S5P HPD(Hot-Plug Detection) driver for TVOUT");
+MODULE_LICENSE("GPL");