--- /dev/null
+/*
+ * Copyright (C) 2012 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*!
+ * @file mxc_hdmi-cec.c
+ *
+ * @brief HDMI CEC system initialization and file operation implementation
+ *
+ * @ingroup HDMI
+ */
+
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/fs.h> /* for struct file_operations */
+#include <linux/stat.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/wait.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <linux/fsl_devices.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/workqueue.h>
+#include <asm/sizes.h>
+
+#include <linux/console.h>
+#include <linux/types.h>
+#include <linux/mfd/mxc-hdmi-core.h>
+
+#include <mach/mxc_edid.h>
+#include <mach/mxc_hdmi.h>
+
+#include "mxc_hdmi-cec.h"
+
+
+#define MAX_MESSAGE_LEN 17
+
+#define MESSAGE_TYPE_RECEIVE_SUCCESS 1
+#define MESSAGE_TYPE_NOACK 2
+#define MESSAGE_TYPE_DISCONNECTED 3
+#define MESSAGE_TYPE_CONNECTED 4
+#define MESSAGE_TYPE_SEND_SUCCESS 5
+
+
+struct hdmi_cec_priv {
+ int receive_error;
+ int send_error;
+ u8 Logical_address;
+ bool cec_state;
+ u8 last_msg[MAX_MESSAGE_LEN];
+ u8 msg_len;
+ u8 latest_cec_stat;
+ u32 cec_irq;
+ spinlock_t irq_lock;
+ struct delayed_work hdmi_cec_work;
+ struct mutex lock;
+};
+
+struct hdmi_cec_event {
+ int event_type;
+ int msg_len;
+ u8 msg[MAX_MESSAGE_LEN];
+ struct list_head list;
+};
+
+
+static LIST_HEAD(head);
+
+static int hdmi_cec_major;
+static struct class *hdmi_cec_class;
+static struct hdmi_cec_priv hdmi_cec_data;
+static u8 open_count;
+
+static wait_queue_head_t hdmi_cec_queue;
+static irqreturn_t mxc_hdmi_cec_isr(int irq, void *data)
+{
+ struct hdmi_cec_priv *hdmi_cec = data;
+ u8 cec_stat = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hdmi_cec->irq_lock, flags);
+
+ hdmi_writeb(0x7f, HDMI_IH_MUTE_CEC_STAT0);
+
+ cec_stat = hdmi_readb(HDMI_IH_CEC_STAT0);
+ hdmi_writeb(cec_stat, HDMI_IH_CEC_STAT0);
+ if ((cec_stat & (HDMI_IH_CEC_STAT0_ERROR_INIT | \
+ HDMI_IH_CEC_STAT0_NACK | HDMI_IH_CEC_STAT0_EOM | \
+ HDMI_IH_CEC_STAT0_DONE)) == 0) {
+ spin_unlock_irqrestore(&hdmi_cec->irq_lock, flags);
+ return IRQ_HANDLED;
+ }
+ pr_debug("HDMI CEC interrupt received\n");
+ hdmi_cec->latest_cec_stat = cec_stat;
+
+ schedule_delayed_work(&(hdmi_cec->hdmi_cec_work), msecs_to_jiffies(20));
+
+ spin_unlock_irqrestore(&hdmi_cec->irq_lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+void mxc_hdmi_cec_handle(u16 cec_stat)
+{
+ u8 val = 0, i = 0;
+ struct hdmi_cec_event *event = NULL;
+ /*The current transmission is successful (for initiator only).*/
+ if (!open_count)
+ return;
+
+ if (cec_stat & HDMI_IH_CEC_STAT0_DONE) {
+ event = vmalloc(sizeof(struct hdmi_cec_event));
+ if (NULL == event) {
+ pr_err("%s:Don't get memory!\n", __func__);
+ return;
+ }
+ memset(event, 0, sizeof(struct hdmi_cec_event));
+ event->event_type = MESSAGE_TYPE_SEND_SUCCESS;
+ mutex_lock(&hdmi_cec_data.lock);
+ list_add_tail(&event->list, &head);
+ mutex_unlock(&hdmi_cec_data.lock);
+ wake_up(&hdmi_cec_queue);
+ }
+ /*EOM is detected so that the received data is ready in the receiver data buffer*/
+ else if (cec_stat & HDMI_IH_CEC_STAT0_EOM) {
+ hdmi_writeb(0x02, HDMI_IH_CEC_STAT0);
+ event = vmalloc(sizeof(struct hdmi_cec_event));
+ if (NULL == event) {
+ pr_err("%s:Don't get memory!\n", __func__);
+ return;
+ }
+ memset(event, 0, sizeof(struct hdmi_cec_event));
+ event->msg_len = hdmi_readb(HDMI_CEC_RX_CNT);
+ if (!event->msg_len) {
+ pr_err("%s: Invalid CEC message length!\n", __func__);
+ return;
+ }
+ event->event_type = MESSAGE_TYPE_RECEIVE_SUCCESS;
+ for (i = 0; i < event->msg_len; i++)
+ event->msg[i] = hdmi_readb(HDMI_CEC_RX_DATA0+i);
+ hdmi_writeb(0x0, HDMI_CEC_LOCK);
+ mutex_lock(&hdmi_cec_data.lock);
+ list_add_tail(&event->list, &head);
+ mutex_unlock(&hdmi_cec_data.lock);
+ wake_up(&hdmi_cec_queue);
+ }
+ /*An error is detected on cec line (for initiator only). */
+ else if (cec_stat & HDMI_IH_CEC_STAT0_ERROR_INIT) {
+ mutex_lock(&hdmi_cec_data.lock);
+ hdmi_cec_data.send_error++;
+ if (hdmi_cec_data.send_error > 5) {
+ pr_err("%s:Re-transmission is attempted more than 5 times!\n", __func__);
+ hdmi_cec_data.send_error = 0;
+ mutex_unlock(&hdmi_cec_data.lock);
+ return;
+ }
+ for (i = 0; i < hdmi_cec_data.msg_len; i++)
+ hdmi_writeb(hdmi_cec_data.last_msg[i], HDMI_CEC_TX_DATA0+i);
+ hdmi_writeb(hdmi_cec_data.msg_len, HDMI_CEC_TX_CNT);
+ val = hdmi_readb(HDMI_CEC_CTRL);
+ val |= 0x01;
+ hdmi_writeb(val, HDMI_CEC_CTRL);
+ mutex_unlock(&hdmi_cec_data.lock);
+ }
+ /*A frame is not acknowledged in a directly addressed message. Or a frame is negatively acknowledged in
+ a broadcast message (for initiator only).*/
+ else if (cec_stat & HDMI_IH_CEC_STAT0_NACK) {
+ event = vmalloc(sizeof(struct hdmi_cec_event));
+ if (NULL == event) {
+ pr_err("%s:Don't get memory!\n", __func__);
+ return;
+ }
+ memset(event, 0, sizeof(struct hdmi_cec_event));
+ event->event_type = MESSAGE_TYPE_NOACK;
+ mutex_lock(&hdmi_cec_data.lock);
+ list_add_tail(&event->list, &head);
+ mutex_unlock(&hdmi_cec_data.lock);
+ wake_up(&hdmi_cec_queue);
+ }
+ /*An error is notified by a follower. Abnormal logic data bit error (for follower).*/
+ else if (cec_stat & HDMI_IH_CEC_STAT0_ERROR_FOLL)
+ hdmi_cec_data.receive_error++;
+ /*HDMI cable connected*/
+ else if (cec_stat & 0x80) {
+ event = vmalloc(sizeof(struct hdmi_cec_event));
+ if (NULL == event) {
+ pr_err("%s:Don't get memory!\n", __func__);
+ return;
+ }
+ memset(event, 0, sizeof(struct hdmi_cec_event));
+ event->event_type = MESSAGE_TYPE_CONNECTED;
+ mutex_lock(&hdmi_cec_data.lock);
+ list_add_tail(&event->list, &head);
+ mutex_unlock(&hdmi_cec_data.lock);
+ wake_up(&hdmi_cec_queue);
+ }
+ /*HDMI cable disconnected*/
+ else if (cec_stat & 0x100) {
+ event = vmalloc(sizeof(struct hdmi_cec_event));
+ if (NULL == event) {
+ pr_err("%s:Don't get memory!\n", __func__);
+ return;
+ }
+ memset(event, 0, sizeof(struct hdmi_cec_event));
+ event->event_type = MESSAGE_TYPE_DISCONNECTED;
+ mutex_lock(&hdmi_cec_data.lock);
+ list_add_tail(&event->list, &head);
+ mutex_unlock(&hdmi_cec_data.lock);
+ wake_up(&hdmi_cec_queue);
+ }
+ return;
+}
+EXPORT_SYMBOL(mxc_hdmi_cec_handle);
+static void mxc_hdmi_cec_worker(struct work_struct *work)
+{
+ u8 val;
+ mxc_hdmi_cec_handle(hdmi_cec_data.latest_cec_stat);
+ val = HDMI_IH_CEC_STAT0_WAKEUP | HDMI_IH_CEC_STAT0_ERROR_FOLL | HDMI_IH_CEC_STAT0_ARB_LOST;
+ hdmi_writeb(val, HDMI_IH_MUTE_CEC_STAT0);
+}
+
+/*!
+ * @brief open function for vpu file operation
+ *
+ * @return 0 on success or negative error code on error
+ */
+static int hdmi_cec_open(struct inode *inode, struct file *filp)
+{
+ mutex_lock(&hdmi_cec_data.lock);
+ if (open_count) {
+ mutex_unlock(&hdmi_cec_data.lock);
+ return -EBUSY;
+ }
+ open_count = 1;
+ filp->private_data = (void *)(&hdmi_cec_data);
+ hdmi_cec_data.Logical_address = 15;
+ hdmi_cec_data.cec_state = false;
+ mutex_unlock(&hdmi_cec_data.lock);
+ return 0;
+}
+static ssize_t hdmi_cec_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ struct hdmi_cec_event *event = NULL;
+ pr_debug("function : %s\n", __func__);
+ if (!open_count)
+ return -ENODEV;
+ mutex_lock(&hdmi_cec_data.lock);
+ if (false == hdmi_cec_data.cec_state) {
+ mutex_unlock(&hdmi_cec_data.lock);
+ return -EACCES;
+ }
+ mutex_unlock(&hdmi_cec_data.lock);
+ /* delete from list */
+ mutex_lock(&hdmi_cec_data.lock);
+ if (list_empty(&head)) {
+ mutex_unlock(&hdmi_cec_data.lock);
+ return -EACCES;
+ }
+ event = list_first_entry(&head, struct hdmi_cec_event, list);
+ list_del(&event->list);
+ mutex_unlock(&hdmi_cec_data.lock);
+ if (copy_to_user(buf,
+ event,
+ sizeof(struct hdmi_cec_event) - sizeof(struct list_head))) {
+ vfree(event);
+ return -EFAULT;
+ }
+ vfree(event);
+ return sizeof(struct hdmi_cec_event);
+}
+static ssize_t hdmi_cec_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int ret = 0 , i = 0;
+ u8 msg[MAX_MESSAGE_LEN];
+ u8 msg_len = 0, val = 0;
+ pr_debug("function : %s\n", __func__);
+ if (!open_count)
+ return -ENODEV;
+ mutex_lock(&hdmi_cec_data.lock);
+ if (false == hdmi_cec_data.cec_state) {
+ mutex_unlock(&hdmi_cec_data.lock);
+ return -EACCES;
+ }
+ mutex_unlock(&hdmi_cec_data.lock);
+ if (count > MAX_MESSAGE_LEN)
+ return -EINVAL;
+ mutex_lock(&hdmi_cec_data.lock);
+ hdmi_cec_data.send_error = 0;
+ memset(&msg, 0, MAX_MESSAGE_LEN);
+ ret = copy_from_user(&msg, buf, count);
+ if (ret) {
+ ret = -EACCES;
+ goto end;
+ }
+ msg_len = count;
+ hdmi_writeb(msg_len, HDMI_CEC_TX_CNT);
+ for (i = 0; i < msg_len; i++)
+ hdmi_writeb(msg[i], HDMI_CEC_TX_DATA0+i);
+ val = hdmi_readb(HDMI_CEC_CTRL);
+ val |= 0x01;
+ hdmi_writeb(val, HDMI_CEC_CTRL);
+ memcpy(hdmi_cec_data.last_msg, msg, msg_len);
+ hdmi_cec_data.msg_len = msg_len;
+ i = 0;
+ val = hdmi_readb(HDMI_CEC_CTRL);
+ while ((val & 0x01) == 0x1) {
+ msleep(50);
+ i++;
+ if (i > 3) {
+ ret = -EIO;
+ goto end;
+ }
+ val = hdmi_readb(HDMI_CEC_CTRL);
+ }
+end:
+ mutex_unlock(&hdmi_cec_data.lock);
+ return ret;
+}
+
+
+/*!
+ * @brief IO ctrl function for vpu file operation
+ * @param cmd IO ctrl command
+ * @return 0 on success or negative error code on error
+ */
+static long hdmi_cec_ioctl(struct file *filp, u_int cmd,
+ u_long arg)
+{
+ int ret = 0, status = 0;
+ u8 val = 0, msg = 0;
+ struct mxc_edid_cfg hdmi_edid_cfg;
+ pr_debug("function : %s\n", __func__);
+ if (!open_count)
+ return -ENODEV;
+ switch (cmd) {
+ case HDMICEC_IOC_SETLOGICALADDRESS:
+ mutex_lock(&hdmi_cec_data.lock);
+ if (false == hdmi_cec_data.cec_state) {
+ mutex_unlock(&hdmi_cec_data.lock);
+ return -EACCES;
+ }
+ hdmi_cec_data.Logical_address = (u8)arg;
+ if (hdmi_cec_data.Logical_address <= 7) {
+ val = 1 << hdmi_cec_data.Logical_address;
+ hdmi_writeb(val, HDMI_CEC_ADDR_L);
+ hdmi_writeb(0, HDMI_CEC_ADDR_H);
+ } else if (hdmi_cec_data.Logical_address > 7 && hdmi_cec_data.Logical_address <= 15) {
+ val = 1 << (hdmi_cec_data.Logical_address - 8);
+ hdmi_writeb(val, HDMI_CEC_ADDR_H);
+ hdmi_writeb(0, HDMI_CEC_ADDR_L);
+ } else
+ ret = -EINVAL;
+ /*Send Polling message with same source and destination address*/
+ if (0 == ret && 15 != hdmi_cec_data.Logical_address) {
+ msg = (hdmi_cec_data.Logical_address << 4)|hdmi_cec_data.Logical_address;
+ hdmi_writeb(1, HDMI_CEC_TX_CNT);
+ hdmi_writeb(msg, HDMI_CEC_TX_DATA0);
+ val = hdmi_readb(HDMI_CEC_CTRL);
+ val |= 0x01;
+ hdmi_writeb(val, HDMI_CEC_CTRL);
+ }
+ mutex_unlock(&hdmi_cec_data.lock);
+ break;
+ case HDMICEC_IOC_STARTDEVICE:
+ val = hdmi_readb(HDMI_MC_CLKDIS);
+ val &= ~HDMI_MC_CLKDIS_CECCLK_DISABLE;
+ hdmi_writeb(val, HDMI_MC_CLKDIS);
+ hdmi_writeb(0x02, HDMI_CEC_CTRL);
+ val = HDMI_IH_CEC_STAT0_WAKEUP | HDMI_IH_CEC_STAT0_ERROR_FOLL | HDMI_IH_CEC_STAT0_ARB_LOST;
+ hdmi_writeb(val, HDMI_CEC_MASK);
+ hdmi_writeb(val, HDMI_IH_MUTE_CEC_STAT0);
+ val = HDMI_IH_CEC_STAT0_ERROR_INIT | HDMI_IH_CEC_STAT0_NACK | HDMI_IH_CEC_STAT0_EOM | HDMI_IH_CEC_STAT0_DONE;
+ hdmi_writeb(val, HDMI_CEC_POLARITY);
+ mutex_lock(&hdmi_cec_data.lock);
+ hdmi_cec_data.cec_state = true;
+ mutex_unlock(&hdmi_cec_data.lock);
+ break;
+ case HDMICEC_IOC_STOPDEVICE:
+ hdmi_writeb(0x10, HDMI_CEC_CTRL);
+ val = HDMI_IH_CEC_STAT0_WAKEUP | HDMI_IH_CEC_STAT0_ERROR_FOLL | HDMI_IH_CEC_STAT0_ERROR_INIT | HDMI_IH_CEC_STAT0_ARB_LOST | \
+ HDMI_IH_CEC_STAT0_NACK | HDMI_IH_CEC_STAT0_EOM | HDMI_IH_CEC_STAT0_DONE;
+ hdmi_writeb(val, HDMI_CEC_MASK);
+ hdmi_writeb(val, HDMI_IH_MUTE_CEC_STAT0);
+ hdmi_writeb(0x0, HDMI_CEC_POLARITY);
+ val = hdmi_readb(HDMI_MC_CLKDIS);
+ val |= HDMI_MC_CLKDIS_CECCLK_DISABLE;
+ hdmi_writeb(val, HDMI_MC_CLKDIS);
+ mutex_lock(&hdmi_cec_data.lock);
+ hdmi_cec_data.cec_state = false;
+ mutex_unlock(&hdmi_cec_data.lock);
+ break;
+ case HDMICEC_IOC_GETPHYADDRESS:
+ hdmi_get_edid_cfg(&hdmi_edid_cfg);
+ status = copy_to_user((void __user *)arg,
+ &hdmi_edid_cfg.physical_address,
+ 4*sizeof(u8));
+ if (status)
+ ret = -EFAULT;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+/*!
+ * @brief Release function for vpu file operation
+ * @return 0 on success or negative error code on error
+ */
+static int hdmi_cec_release(struct inode *inode, struct file *filp)
+{
+ mutex_lock(&hdmi_cec_data.lock);
+ if (open_count) {
+ open_count = 0;
+ hdmi_cec_data.cec_state = false;
+ hdmi_cec_data.Logical_address = 15;
+ }
+ mutex_unlock(&hdmi_cec_data.lock);
+
+ return 0;
+}
+
+static unsigned int hdmi_cec_poll(struct file *file, poll_table *wait)
+{
+ unsigned int mask = 0;
+
+ pr_debug("function : %s\n", __func__);
+
+ if (!open_count)
+ return -ENODEV;
+ if (false == hdmi_cec_data.cec_state)
+ return -EACCES;
+
+ poll_wait(file, &hdmi_cec_queue, wait);
+
+ if (!list_empty(&head))
+ mask |= (POLLIN | POLLRDNORM);
+ return mask;
+}
+
+
+const struct file_operations hdmi_cec_fops = {
+ .owner = THIS_MODULE,
+ .read = hdmi_cec_read,
+ .write = hdmi_cec_write,
+ .open = hdmi_cec_open,
+ .unlocked_ioctl = hdmi_cec_ioctl,
+ .release = hdmi_cec_release,
+ .poll = hdmi_cec_poll,
+};
+
+static int hdmi_cec_dev_probe(struct platform_device *pdev)
+{
+ int err = 0;
+ struct device *temp_class;
+ struct resource *res;
+
+ hdmi_cec_major = register_chrdev(hdmi_cec_major, "mxc_hdmi_cec", &hdmi_cec_fops);
+ if (hdmi_cec_major < 0) {
+ pr_err("hdmi_cec: unable to get a major for HDMI CEC\n");
+ err = -EBUSY;
+ goto out;
+ }
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (unlikely(res == NULL)) {
+ pr_err("hdmi_cec:No HDMI irq line provided\n");
+ goto err_out_chrdev;
+ }
+ spin_lock_init(&hdmi_cec_data.irq_lock);
+ hdmi_cec_data.cec_irq = res->start;
+
+ err = request_irq(hdmi_cec_data.cec_irq, mxc_hdmi_cec_isr, IRQF_SHARED,
+ "mxc_hdmi_cec", &hdmi_cec_data);
+ if (err < 0) {
+ pr_err("hdmi_cec:Unable to request irq: %d\n", err);
+ goto err_out_chrdev;
+ }
+
+ hdmi_cec_class = class_create(THIS_MODULE, "mxc_hdmi_cec");
+ if (IS_ERR(hdmi_cec_class)) {
+ err = PTR_ERR(hdmi_cec_class);
+ goto err_out_chrdev;
+ }
+
+ temp_class = device_create(hdmi_cec_class, NULL, MKDEV(hdmi_cec_major, 0),
+ NULL, "mxc_hdmi_cec");
+ if (IS_ERR(temp_class)) {
+ err = PTR_ERR(temp_class);
+ goto err_out_class;
+ }
+
+ mutex_init(&hdmi_cec_data.lock);
+ hdmi_cec_data.Logical_address = 15;
+ platform_set_drvdata(pdev, &hdmi_cec_data);
+ INIT_DELAYED_WORK(&hdmi_cec_data.hdmi_cec_work, mxc_hdmi_cec_worker);
+ printk(KERN_INFO "HDMI CEC initialized\n");
+ goto out;
+
+err_out_class:
+ device_destroy(hdmi_cec_class, MKDEV(hdmi_cec_major, 0));
+ class_destroy(hdmi_cec_class);
+err_out_chrdev:
+ unregister_chrdev(hdmi_cec_major, "mxc_hdmi_cec");
+out:
+ return err;
+}
+
+static int hdmi_cec_dev_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int hdmi_cec_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ return 0;
+}
+
+static int hdmi_cec_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+#else
+#define hdmi_cec_suspend NULL
+#define hdmi_cec_resume NULL
+#endif /* !CONFIG_PM */
+
+/*! Driver definition
+ *
+ */
+static struct platform_driver mxc_hdmi_cec_driver = {
+ .driver = {
+ .name = "mxc_hdmi_cec",
+ },
+ .probe = hdmi_cec_dev_probe,
+ .remove = hdmi_cec_dev_remove,
+ .suspend = hdmi_cec_suspend,
+ .resume = hdmi_cec_resume,
+};
+
+static int __init hdmi_cec_init(void)
+{
+ int ret = platform_driver_register(&mxc_hdmi_cec_driver);
+
+ init_waitqueue_head(&hdmi_cec_queue);
+ INIT_LIST_HEAD(&head);
+ return ret;
+}
+
+static void __exit hdmi_cec_exit(void)
+{
+ if (hdmi_cec_major > 0) {
+ device_destroy(hdmi_cec_class, MKDEV(hdmi_cec_major, 0));
+ class_destroy(hdmi_cec_class);
+ unregister_chrdev(hdmi_cec_major, "mxc_vpu");
+ hdmi_cec_major = 0;
+ }
+
+ platform_driver_unregister(&mxc_hdmi_cec_driver);
+ return;
+}
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Linux HDMI CEC driver for Freescale i.MX/MXC");
+MODULE_LICENSE("GPL");
+
+module_init(hdmi_cec_init);
+module_exit(hdmi_cec_exit);