Index: drivers/media/video/samsung/tv20/cec.c |
diff --git a/drivers/media/video/samsung/tv20/cec.c b/drivers/media/video/samsung/tv20/cec.c |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7a13f2478586043b5dbab7270f3b773e871989ed |
--- /dev/null |
+++ b/drivers/media/video/samsung/tv20/cec.c |
@@ -0,0 +1,405 @@ |
+/* linux/drivers/media/video/samsung/tv20/cec.c |
+* |
+* Copyright (c) 2010 Samsung Electronics Co., Ltd. |
+* http://www.samsung.com/ |
+* |
+* S5PV210 - cec interface 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/io.h> |
+#include <asm/mach-types.h> |
+#include <linux/uaccess.h> |
+ |
+#include <mach/gpio.h> |
+#include <plat/gpio-cfg.h> |
+ |
+#include <mach/regs-hdmi.h> |
+ |
+#include "s5p_tv.h" |
+#include "cec.h" |
+ |
+#ifdef CECDEBUG |
+#define CECIFPRINTK(fmt, args...) \ |
+ printk(KERN_INFO "\t[CEC_IF] %s: " fmt, __func__ , ## args) |
+#else |
+#define CECIFPRINTK(fmt, args...) |
+#endif |
+ |
+static struct cec_rx_struct cec_rx_struct; |
+static struct cec_tx_struct cec_tx_struct; |
+ |
+static bool hdmi_on; |
+ |
+/** |
+ * Change CEC Tx state to state |
+ * @param state [in] new CEC Tx state. |
+ */ |
+void tv_cec_set_tx_state(enum cec_state state) |
+{ |
+ atomic_set(&cec_tx_struct.state, state); |
+} |
+ |
+/** |
+ * Change CEC Rx state to @c state. |
+ * @param state [in] new CEC Rx state. |
+ */ |
+void tv_cec_set_rx_state(enum cec_state state) |
+{ |
+ atomic_set(&cec_rx_struct.state, state); |
+} |
+ |
+ |
+int s5p_cec_open(struct inode *inode, struct file *file) |
+{ |
+ s5p_tv_clk_gate(true); |
+ |
+ hdmi_on = true; |
+ |
+ tv_cec_reset(); |
+ |
+ tv_cec_set_divider(); |
+ |
+ tv_cec_threshold(); |
+ |
+ tv_cec_unmask_tx_interrupts(); |
+ |
+ tv_cec_set_rx_state(STATE_RX); |
+ tv_cec_unmask_rx_interrupts(); |
+ tv_cec_enable_rx(); |
+ |
+ return 0; |
+} |
+ |
+int s5p_cec_release(struct inode *inode, struct file *file) |
+{ |
+ s5p_tv_clk_gate(false); |
+ |
+ hdmi_on = false; |
+ |
+ tv_cec_mask_tx_interrupts(); |
+ tv_cec_mask_rx_interrupts(); |
+ |
+ return 0; |
+} |
+ |
+ssize_t s5p_cec_read(struct file *file, char __user *buffer, |
+ size_t count, loff_t *ppos) |
+{ |
+ ssize_t retval; |
+ |
+ if (wait_event_interruptible(cec_rx_struct.waitq, |
+ atomic_read(&cec_rx_struct.state) == STATE_DONE)) { |
+ return -ERESTARTSYS; |
+ } |
+ |
+ spin_lock_irq(&cec_rx_struct.lock); |
+ |
+ if (cec_rx_struct.size > count) { |
+ spin_unlock_irq(&cec_rx_struct.lock); |
+ return -1; |
+ } |
+ |
+ if (copy_to_user(buffer, cec_rx_struct.buffer, cec_rx_struct.size)) { |
+ spin_unlock_irq(&cec_rx_struct.lock); |
+ printk(KERN_ERR " copy_to_user() failed!\n"); |
+ return -EFAULT; |
+ } |
+ |
+ retval = cec_rx_struct.size; |
+ |
+ tv_cec_set_rx_state(STATE_RX); |
+ spin_unlock_irq(&cec_rx_struct.lock); |
+ |
+ return retval; |
+} |
+ |
+ssize_t s5p_cec_write(struct file *file, const char __user *buffer, |
+ size_t count, loff_t *ppos) |
+{ |
+ char *data; |
+ |
+ /* check data size */ |
+ |
+ if (count > CEC_TX_BUFF_SIZE || count == 0) |
+ return -1; |
+ |
+ data = kmalloc(count, GFP_KERNEL); |
+ |
+ if (!data) { |
+ printk(KERN_ERR " kmalloc() failed!\n"); |
+ return -1; |
+ } |
+ |
+ if (copy_from_user(data, buffer, count)) { |
+ printk(KERN_ERR " copy_from_user() failed!\n"); |
+ kfree(data); |
+ return -EFAULT; |
+ } |
+ |
+ tv_cec_copy_packet(data, count); |
+ |
+ kfree(data); |
+ |
+ /* wait for interrupt */ |
+ if (wait_event_interruptible(cec_tx_struct.waitq, |
+ atomic_read(&cec_tx_struct.state) != STATE_TX)) { |
+ return -ERESTARTSYS; |
+ } |
+ |
+ if (atomic_read(&cec_tx_struct.state) == STATE_ERROR) |
+ return -1; |
+ |
+ |
+ return count; |
+} |
+ |
+int s5p_cec_ioctl(struct inode *inode, struct file *file, |
+ u32 cmd, unsigned long arg) |
+{ |
+ u32 laddr; |
+ |
+ switch (cmd) { |
+ |
+ case CEC_IOC_SETLADDR: |
+ CECIFPRINTK("ioctl(CEC_IOC_SETLADDR)\n"); |
+ |
+ if (get_user(laddr, (u32 __user *) arg)) |
+ return -EFAULT; |
+ |
+ CECIFPRINTK("logical address = 0x%02x\n", laddr); |
+ |
+ tv_cec_set_addr(laddr); |
+ |
+ break; |
+ |
+ default: |
+ return -EINVAL; |
+ } |
+ |
+ return 0; |
+} |
+ |
+u32 s5p_cec_poll(struct file *file, poll_table *wait) |
+{ |
+ poll_wait(file, &cec_rx_struct.waitq, wait); |
+ |
+ if (atomic_read(&cec_rx_struct.state) == STATE_DONE) |
+ return POLLIN | POLLRDNORM; |
+ |
+ return 0; |
+} |
+ |
+static const struct file_operations cec_fops = { |
+ .owner = THIS_MODULE, |
+ .open = s5p_cec_open, |
+ .release = s5p_cec_release, |
+ .read = s5p_cec_read, |
+ .write = s5p_cec_write, |
+ .ioctl = s5p_cec_ioctl, |
+ .poll = s5p_cec_poll, |
+}; |
+ |
+static struct miscdevice cec_misc_device = { |
+ .minor = CEC_MINOR, |
+ .name = "CEC", |
+ .fops = &cec_fops, |
+}; |
+ |
+ |
+/** |
+ * @brief CEC interrupt handler |
+ * |
+ * Handles interrupt requests from CEC hardware. \n |
+ * Action depends on current state of CEC hardware. |
+ */ |
+irqreturn_t s5p_cec_irq_handler(int irq, void *dev_id) |
+{ |
+/* u8 flag; */ |
+ u32 status = 0; |
+ |
+ status = tv_cec_get_status(); |
+ |
+ if (status & CEC_STATUS_TX_DONE) { |
+ if (status & CEC_STATUS_TX_ERROR) { |
+ CECIFPRINTK(" CEC_STATUS_TX_ERROR!\n"); |
+ tv_cec_set_tx_state(STATE_ERROR); |
+ } else { |
+ CECIFPRINTK(" CEC_STATUS_TX_DONE!\n"); |
+ tv_cec_set_tx_state(STATE_DONE); |
+ } |
+ |
+ tv_clr_pending_tx(); |
+ |
+ wake_up_interruptible(&cec_tx_struct.waitq); |
+ } |
+ |
+ if (status & CEC_STATUS_RX_DONE) { |
+ if (status & CEC_STATUS_RX_ERROR) { |
+ CECIFPRINTK(" CEC_STATUS_RX_ERROR!\n"); |
+ tv_cec_rx_reset(); |
+ |
+ } else { |
+ u32 size; |
+ |
+ CECIFPRINTK(" CEC_STATUS_RX_DONE!\n"); |
+ |
+ /* copy data from internal buffer */ |
+ size = status >> 24; |
+ |
+ spin_lock(&cec_rx_struct.lock); |
+ |
+ tv_cec_get_rx_buf(size, cec_rx_struct.buffer); |
+ |
+ cec_rx_struct.size = size; |
+ |
+ tv_cec_set_rx_state(STATE_DONE); |
+ |
+ spin_unlock(&cec_rx_struct.lock); |
+ |
+ tv_cec_enable_rx(); |
+ } |
+ |
+ /* clear interrupt pending bit */ |
+ tv_clr_pending_rx(); |
+ |
+ wake_up_interruptible(&cec_rx_struct.waitq); |
+ } |
+ |
+ return IRQ_HANDLED; |
+} |
+ |
+static int __init s5p_cec_probe(struct platform_device *pdev) |
+{ |
+ u8 *buffer; |
+ int irq_num; |
+ int ret; |
+ |
+ s3c_gpio_cfgpin(S5PV210_GPH1(4), S5P_GPH1_4_HDMI_CEC); |
+ s3c_gpio_setpull(S5PV210_GPH1(4), S3C_GPIO_PULL_NONE); |
+ |
+ /* get ioremap addr */ |
+ tv_cec_probe(pdev); |
+ |
+ if (misc_register(&cec_misc_device)) { |
+ printk(KERN_WARNING |
+ "Couldn't register device 10, %d.\n", CEC_MINOR); |
+ return -EBUSY; |
+ } |
+ |
+ irq_num = platform_get_irq(pdev, 0); |
+ if (irq_num < 0) { |
+ printk(KERN_ERR "failed to get %s irq resource\n", "cec"); |
+ ret = -ENOENT; |
+ return ret; |
+ } |
+ |
+ ret = request_irq(irq_num, s5p_cec_irq_handler, IRQF_DISABLED, |
+ pdev->name, &pdev->id); |
+ if (ret != 0) { |
+ printk(KERN_ERR "failed to install %s irq (%d)\n", "cec", ret); |
+ return ret; |
+ } |
+ |
+ init_waitqueue_head(&cec_rx_struct.waitq); |
+ spin_lock_init(&cec_rx_struct.lock); |
+ init_waitqueue_head(&cec_tx_struct.waitq); |
+ |
+ buffer = kmalloc(CEC_TX_BUFF_SIZE, GFP_KERNEL); |
+ |
+ if (!buffer) { |
+ printk(KERN_ERR " kmalloc() failed!\n"); |
+ misc_deregister(&cec_misc_device); |
+ return -EIO; |
+ } |
+ |
+ cec_rx_struct.buffer = buffer; |
+ |
+ cec_rx_struct.size = 0; |
+ |
+ return 0; |
+} |
+ |
+static int s5p_cec_remove(struct platform_device *pdev) |
+{ |
+ return 0; |
+} |
+ |
+#ifdef CONFIG_PM |
+ |
+int s5p_cec_suspend(struct platform_device *dev, pm_message_t state) |
+{ |
+ if (hdmi_on) |
+ s5p_tv_clk_gate(false); |
+ |
+ return 0; |
+} |
+ |
+int s5p_cec_resume(struct platform_device *dev) |
+{ |
+ if (hdmi_on) |
+ s5p_tv_clk_gate(true); |
+ |
+ return 0; |
+} |
+#else |
+#define s5p_cec_suspend NULL |
+#define s5p_cec_resume NULL |
+#endif |
+ |
+static struct platform_driver s5p_cec_driver = { |
+ .probe = s5p_cec_probe, |
+ .remove = s5p_cec_remove, |
+ .suspend = s5p_cec_suspend, |
+ .resume = s5p_cec_resume, |
+ .driver = { |
+ .name = "s5p-cec", |
+ .owner = THIS_MODULE, |
+ }, |
+}; |
+ |
+static char banner[] __initdata = |
+ "S5PV210 CEC Driver, (c) 2010 Samsung Electronics\n"; |
+ |
+int __init s5p_cec_init(void) |
+{ |
+ int ret; |
+ |
+ printk(banner); |
+ |
+ ret = platform_driver_register(&s5p_cec_driver); |
+ |
+ if (ret) { |
+ printk(KERN_ERR "Platform Device Register Failed %d\n", ret); |
+ return -1; |
+ } |
+ |
+ return 0; |
+} |
+ |
+static void __exit s5p_cec_exit(void) |
+{ |
+ kfree(cec_rx_struct.buffer); |
+ |
+ platform_driver_unregister(&s5p_cec_driver); |
+ |
+} |
+ |
+module_init(s5p_cec_init); |
+module_exit(s5p_cec_exit); |
+ |
+MODULE_AUTHOR("SangPil Moon"); |
+MODULE_DESCRIPTION("S5PV210 CEC driver"); |
+MODULE_LICENSE("GPL"); |