RFC: abstract common HDMI code

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi everyone,

    I am writing another HDMI driver for our new SOC and find that
sh_mobile_hdmi.c implemented some logic that can be reused by my
driver. I do not want to duplicated similar code and enlarge kernel
code size with it. So my plan is to abstract some common code to an
independent file to ease other developers. If you guys think it is
good to have a such common layer, I will put more effort on this and
contribute to the community. Also any help and co-work is warmly
welcomed!

    The attachment is my every preliminary code that only support
video resolution change and hot-plug on my platform with my private
code base. Sorry for the lot of style issues and small bugs for my
hurry debug. In my code, video resolution interface is still based on
original /sys/class/graphics/fb1/mode and more HDMI specific item is
added in /sys/class/hdmi/, such as video ratio. EDID part is not
ported yet and use predefined mode list in the first step. ASoC can be
added later just as sh_mobile_hdmi.c does, CEC and DHCP are the same.

    Thanks for any comments!

Jun


diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c
new file mode 100644
index 0000000..c845dc7
--- /dev/null
+++ b/drivers/video/hdmi.c
@@ -0,0 +1,675 @@
+/*
+ * HDMI Control Abstraction
+ *
+ * This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fb.h>
+#include <linux/freezer.h>
+#include <linux/hdmi.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/kthread.h>
+#include <linux/lcd.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define HDMI_DEBUG
+static int hdmi_core_debug = 5;
+#define hdmi_dprintk(level, fmt, arg...)	if (hdmi_core_debug >= level) \
+	printk(KERN_DEBUG "%s: " fmt , __func__, ## arg)
+
+/* Define the max number of events to buffer */
+#define MAX_HDMI_EVENT_SIZE      16
+
+static int hdmidevno;
+
+/* ==================== info frame utility ===================== */
+
+int hdmi_vender_info_frame (struct hdmi_dev *hd, char buf[])
+{
+	buf[0] = 0x84;
+	buf[1] = 0x1;
+	buf[2] = 0xa;
+	buf[3] = 0x70;
+	buf[4] = 0x1;
+}
+EXPORT_SYMBOL_GPL(hdmi_vender_info_frame);
+
+int hdmi_avi_info_frame (struct hdmi_dev *hd, char buf[])
+{
+	int count;
+	char check_sum;
+
+	hdmi_dprintk(3, "pack avi infoframe\n");
+	/* packet header for AVI Packet in pkt1 */
+	/* Fix me. we should set bits according to format */
+	buf[0] = 0x82; /* InfoFrame Type, AVI frame */
+	buf[1] = 0x2;  /* Version = 02 */
+	buf[2] = 0xd;  /* Length = 13 */
+	buf[3] = 0x0;  /* checksum */
+	buf[4] = 0x0; /* Fix to RGB format currently */
+	/*
+	buf[4] = 0x10; // RGB, Active Format(r0,r1,r2,r3) valid, no bar, no scan
+	buf[4] = 0x30; // HDMI_YUV422, Active Format(r0,r1,r2,r3) valid, no
bar, no scan
+	buf[4] = 0x50; // HDMI_YUV444, Active Format(r0,r1,r2,r3) valid, no
bar, no scan
+	*/
+
+	buf[5] = 0xa8;  /* ITU-R 709, 16:9 */
+	buf[6] = 0x20;
+	/* buf[6] = 0x0;   // No IT content, xvYCC601, no scaling */
+	buf[7] = hd->mode_id;
+	/* limited YCC range, graphics, no repeat  Pixel repitition factor */
+	buf[8] = hd->pixel_rept;
+
+	buf[9] = 0x0;
+	buf[10] = 0x0;
+	buf[11] = 0x0;
+	buf[12] = 0x0;
+	buf[13] = 0x0;
+	check_sum = 0;
+    for (count = 0; count < 14; count++)
+	    check_sum += buf[count];
+    buf[3] = 0x100 - check_sum;
+    return 0;
+}
+EXPORT_SYMBOL_GPL(hdmi_avi_info_frame);
+
+int hdmi_get_freq (struct hdmi_dev *hd, u32 *need_freq)
+{
+	u64 div_result;
+	int freq;
+
+	/* Calc divider according to pclk in pico second */
+	div_result = 1000000000000ll;
+	do_div(div_result, cea_modes[hd->mode_id].pixclock);
+	freq = (u32)div_result;
+
+	hdmi_dprintk(3, "get base pclk %d\n", freq);
+
+	if (FB_VMODE_INTERLACED & cea_modes[hd->mode_id].vmode)
+		freq >>= 1;
+
+	if (36 == hd->fb_info->var.bits_per_pixel)
+		freq += freq >> 1;
+	else if (30 == hd->fb_info->var.bits_per_pixel)
+		freq += freq >> 2;
+
+	/* We only support repeat=2/4 currently. Add more later */
+	if (!(hd->pixel_rept == 0 || hd->pixel_rept == 1
+				|| hd->pixel_rept == 3)) {
+		hdmi_dprintk(3, "Not support pix repeat %d\n", hd->pixel_rept);
+		return -EINVAL;
+	}
+
+	if (1 == hd->pixel_rept)
+	{
+		if (!(FB_VMODE_INTERLACED & cea_modes[hd->mode_id].vmode))
+			freq <<= 1;
+	} else if (3 == hd->pixel_rept) {
+		if (!(FB_VMODE_INTERLACED & cea_modes[hd->mode_id].vmode))
+			freq <<= 2;
+		else
+			freq <<= 1;
+	}
+
+	hdmi_dprintk(3, "get hdmi pclk %d\n", freq);
+	*need_freq = freq;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(hdmi_get_freq);
+
+/* ==================== video part ===================== */
+int hdmi_lcd_check_fb(struct lcd_device * ld, struct fb_info *fi)
+{
+	struct hdmi_dev *hd = lcd_get_data(ld);
+
+	hdmi_dprintk(3, "hdmi_lcd_check_fb..\n");
+	return 1;
+}
+
+/* We'd better update mode list in hot plug operation.
+   LCD framework does not implement get_mode yet */
+static int hdmi_lcd_get_mode(struct lcd_device *ld, struct fb_videomode *mode)
+{
+	struct hdmi_dev *hd = lcd_get_data(ld);
+
+	/* TODO: return parsed mode list */
+	return 0;
+}
+
+static int hdmi_lcd_set_mode(struct lcd_device *ld, struct fb_videomode *mode)
+{
+	//struct hdmi_lcd_data *data = lcd_get_data(lcd);
+	struct hdmi_dev *hd = lcd_get_data(ld);
+	cea_mode_id mode_id;
+
+	hdmi_dprintk(3, "hdmi_lcd_set_mode dev\n");
+	mode_id = fb_find_cea_mode_index(mode);
+	if (!mode_id) {
+		hdmi_dprintk(1, "unknown video mode %d\n", mode_id);
+		return -EINVAL;
+	}
+	/* If high level on detection signal, no connection */
+	if(! hd->ops->get_hpd(hd))
+		return 0;
+
+	hd->ops->set_mode(hd, mode_id);
+
+	if (hd->ops && hd->ops->set_enable)
+		hd->ops->set_enable(hd, 1);
+}
+
+static struct lcd_ops hdmi_lcd_ops = {
+	.get_mode = hdmi_lcd_get_mode,
+	.set_mode = hdmi_lcd_set_mode,
+	.check_fb = hdmi_lcd_check_fb,
+};
+
+static int hdmi_register_lcd(struct hdmi_dev *dev)
+{
+	int ret = 0;
+
+	hdmi_dprintk(3, "registering lcd device...\n");
+	/* FIX ME! How to hook expected fb if more than one fb exist? */
+	dev->lcd = lcd_device_register("hdmi-lcd", dev->dev, dev,
+			&hdmi_lcd_ops);
+
+	if (IS_ERR(dev->lcd)) {
+		ret = PTR_ERR(dev->lcd);
+		dev->lcd = NULL;
+		return ret;
+	}
+	hdmi_dprintk(3, "registering lcd device ok\n");
+	return 0;
+}
+
+/* ==================== audio part ===================== */
+/* ==================== cec part ===================== */
+/* ==================== edid part ===================== */
+
+/* ==================== hot plug part ===================== */
+static int hdmi_hotplug(struct hdmi_dev *hd)
+{
+	int ret;
+
+	msleep(100);
+	ret = hd->ops->get_hpd(hd);
+	if(!ret) {
+		hdmi_dprintk(3, "uplugged. dev %p\n", hd);
+		ret = hd->ops->set_enable(hd, 0);
+		lock_fb_info(hd->fb_info);
+		/* We should clear mode list here */
+		/* ... */
+		unlock_fb_info(hd->fb_info);
+		return 0;
+	}
+
+	ret = hd->ops->init(hd);
+	if (ret) {
+		hdmi_dprintk(1, "init fail\n");
+	}
+
+	if(hd->fb_info && hd->fb_info->mode) {
+		lock_fb_info(hd->fb_info);
+		/* Fix me! We should get EDID and parse mode list here */
+		/* But now we use fix mode list first before EDID util is ready */
+		/* ... */
+		unlock_fb_info(hd->fb_info);
+
+		ret = hdmi_lcd_set_mode(hd->lcd, hd->fb_info->mode);
+		if (ret) {
+			hdmi_dprintk(1, "fail to set video mode\n");
+		}
+	} else {
+		hdmi_dprintk(3, "no avaliable video mode\n");
+	}
+
+	if (hd->enable)
+		ret = hd->ops->set_enable(hd, 1);
+	return ret;
+}
+
+/* Handling hot plug HDCP and CEC */
+static int hdmi_event_thread(void *data)
+{
+	struct hdmi_dev *dev = (struct hdmi_dev *)data;
+	struct hdmi_event *evt;
+
+	while (!kthread_should_stop()) {
+
+		spin_lock_irq(&dev->lock);
+		if (list_empty(&dev->hdmi_evt_list)) {
+			set_current_state(TASK_INTERRUPTIBLE);
+
+			if (kthread_should_stop())
+				set_current_state(TASK_RUNNING);
+
+			spin_unlock_irq(&dev->lock);
+			schedule();
+			continue;
+		}
+
+		evt = list_entry(dev->hdmi_evt_list.next, typeof(*evt), list);
+		list_del(&evt->list);
+		spin_unlock_irq(&dev->lock);
+		hdmi_dprintk(1, "Receive evt type %d\n", evt->type);
+
+		mutex_lock(&dev->hdmi_lock);
+		switch (evt->type) {
+			/* trigger gpio irq */
+			case HDMI_EV_PLUG:
+				hdmi_hotplug(dev);
+				break;
+			/* trigger from TV/CEC */
+			case HDMI_EV_CEC:
+				break;
+			default:
+				hdmi_dprintk(1, "Invalid event type %d\n", evt->type);
+				break;
+		}
+		mutex_unlock(&dev->hdmi_lock);
+	}
+
+	return 0;
+}
+
+/**
+ * hdmi_event_signal() - schedules the hdmi event thread
+ * @dev:	the struct hdmi_dev device
+ *
+ * This routine will tell hdmi-core to start decoding stored ir data.
+ */
+void hdmi_event_signal(struct hdmi_dev *dev, enum hdmi_event_type type)
+{
+	unsigned long flags;
+	struct hdmi_event *evt;
+
+	hdmi_dprintk(1, "hdmi event %d stored, dev %p\n", type, dev);
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if(HDMI_EV_PLUG == type) {
+		list_for_each_entry(evt, &dev->hdmi_evt_list, list) {
+			if (HDMI_EV_PLUG == evt->type) {
+				hdmi_dprintk(1, "pending hotplug event, ignore new ones\n");
+				spin_unlock_irqrestore(&dev->lock, flags);
+				return;
+			}
+		}
+	}
+	evt = list_entry(dev->free_evt_list.next, struct hdmi_event, list);
+	evt->type = type;
+	list_move_tail(&evt->list, &dev->hdmi_evt_list);
+	wake_up_process(dev->thread);
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+EXPORT_SYMBOL_GPL(hdmi_event_signal);
+
+/* ==================== sys fs part ===================== */
+#ifdef HDMI_DEBUG
+static ssize_t hdmi_show_reg(struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	int rc;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->get_reg)
+		rc = sprintf(buf, "%d\n", hd->ops->get_reg(hd));
+	else
+		rc = -ENXIO;
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_store_reg(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int rc = -ENXIO;
+	char *endp;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+	int power = simple_strtoul(buf, &endp, 0);
+	size_t size = endp - buf;
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->set_reg) {
+		hd->ops->set_reg(hd, power, power);
+		rc = count;
+	}
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_show_reg_addr(struct device *dev, struct
device_attribute *attr,
+		char *buf)
+{
+	int rc;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->get_reg)
+		rc = sprintf(buf, "%d\n", hd->ops->get_reg(hd));
+	else
+		rc = -ENXIO;
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_store_reg_addr(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int rc = -ENXIO;
+	char *endp;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+	int power = simple_strtoul(buf, &endp, 0);
+	size_t size = endp - buf;
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->set_reg) {
+		hd->ops->set_reg(hd, power, power);
+		rc = count;
+	}
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+#endif
+
+static ssize_t hdmi_show_ratio(struct device *dev, struct
device_attribute *attr,
+		char *buf)
+{
+	int rc;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ratio == RATIO_16V9)
+		rc = sprintf(buf, "%s\n", RATIO_16V9_STR);
+	else if (hd->ratio == RATIO_4V3)
+		rc = sprintf(buf, "%s\n", RATIO_4V3_STR);
+	else
+		rc = -ENXIO;
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_store_ratio(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int rc = -ENXIO;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+
+	mutex_lock(&hd->update_lock);
+	if (sysfs_streq(buf, RATIO_16V9_STR)) {
+		hd->ratio = RATIO_16V9;
+	} else if (sysfs_streq(buf, RATIO_4V3_STR)) {
+		hd->ratio = RATIO_4V3;
+	}
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_show_power(struct device *dev, struct
device_attribute *attr,
+		char *buf)
+{
+	int rc;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->get_power) {
+		hd->ops->get_power(hd);
+		rc = sprintf(buf, "%d\n", hd->ops->get_power(hd));
+	}
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_store_power(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int rc = -ENXIO;
+	char *endp;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+	int power = simple_strtoul(buf, &endp, 0);
+	size_t size = endp - buf;
+
+	if (isspace(*endp))
+		size++;
+	if (size != count)
+		return -EINVAL;
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->set_power) {
+		hdmi_dprintk(3, "hdmi: set power to %d\n", power);
+		hd->ops->set_power(hd, power);
+		rc = count;
+	}
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_show_enable(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	int rc = -ENXIO;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->get_enable)
+		rc = sprintf(buf, "%d\n", hd->ops->get_enable(hd));
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_store_enable(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int rc = -ENXIO;
+	char *endp;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+	int enable = simple_strtoul(buf, &endp, 0);
+	size_t size = endp - buf;
+
+	if (isspace(*endp))
+		size++;
+	if (size != count)
+		return -EINVAL;
+
+	mutex_lock(&hd->update_lock);
+	hd->enable = enable;
+	if (hd->ops && hd->ops->set_enable) {
+		hdmi_dprintk(3, "hdmi: set enable to %d\n", enable);
+		hd->ops->set_enable(hd, enable);
+		rc = count;
+	}
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_show_hotplug(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int rc = -ENXIO;
+	char *endp;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->get_hpd) {
+		rc = sprintf(buf, "%d\n", ! hd->ops->get_hpd(hd));
+	}
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static struct class *hdmi_class;
+
+static void hdmi_dev_release(struct device *dev)
+{
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+	kfree(hd);
+}
+
+static struct device_attribute hdmi_dev_attributes[] = {
+	__ATTR(power, 0644, hdmi_show_power, hdmi_store_power),
+	__ATTR(ratio, 0644, hdmi_show_ratio, hdmi_store_ratio),
+#ifdef HDMI_DEBUG
+	__ATTR(reg_val, 0644, hdmi_show_reg, hdmi_store_reg),
+	__ATTR(reg_addr, 0644, hdmi_show_reg_addr, hdmi_store_reg_addr),
+#endif
+	__ATTR(enable, 0644, hdmi_show_enable, hdmi_store_enable),
+	__ATTR(hotplug, 0644, hdmi_show_hotplug, NULL),
+	__ATTR_NULL,
+};
+
+/**
+ * hdmi_dev_register - register a new object of hdmi_dev class.
+ * @name: the name of the new object(must be the same as the name of the
+ *   respective framebuffer device).
+ * @devdata: an optional pointer to be stored in the device. The
+ *   methods may retrieve it by using hdmi_get_data(hd).
+ * @ops: the hdmi operations structure.
+ *
+ * Creates and registers a new hdmi device. Returns either an ERR_PTR()
+ * or a pointer to the newly allocated device.
+ */
+int hdmi_dev_register(struct hdmi_dev *hd, struct device *parent,
+		void *devdata, struct hdmi_ops *ops, int fb_index)
+{
+	struct hdmi_event *evt_list;
+	int i;
+	int rc;
+
+	hd->dev = device_create(hdmi_class, parent,
+			MKDEV(HDMI_MAJOR, hdmidevno), hd, "hdmi%d", hdmidevno);
+	if (IS_ERR(hd->dev)) {
+		/* Not fatal */
+		hdmi_dprintk(1, "Unable to create device for hdmi %d; errno = %ld\n",
+			hdmidevno, PTR_ERR(hd->dev));
+		hd->dev = NULL;
+	}
+
+	spin_lock_init(&hd->lock);
+	mutex_init(&hd->hdmi_lock);
+	mutex_init(&hd->update_lock);
+
+	INIT_LIST_HEAD(&hd->hdmi_evt_list);
+	INIT_LIST_HEAD(&hd->free_evt_list);
+	hd->evt_ptr = evt_list =
+		kzalloc(MAX_HDMI_EVENT_SIZE * sizeof(struct hdmi_event), GFP_KERNEL);
+	for (i = 0; i < MAX_HDMI_EVENT_SIZE; i++) {
+		list_add(&evt_list->list, &hd->free_evt_list);
+		evt_list ++;
+	}
+
+	rc = hdmi_register_lcd(hd);
+	if (rc) {
+		device_unregister(hd->dev);
+		hdmi_dprintk(1, "Unable to create lcd device for hdmi\n");
+		goto dev_out;
+	}
+
+	hd->ops = ops;
+	hd->fb_info = registered_fb[fb_index];
+	if (! hd->fb_info) {
+		hdmi_dprintk(1, "Invalid associated fb\n");
+		goto dev_out;
+	}
+
+	/* Register CEC input device here */
+	/* Register audio Asoc device here */
+	/* Init hot plug uevent here */
+	/* Init HDCP here */
+
+	hd->thread = kthread_run(hdmi_event_thread, hd,
+				       "hdmi%d", hdmidevno++);
+
+	if (IS_ERR(hd->thread)) {
+		rc = PTR_ERR(hd->thread);
+		hdmi_dprintk(1, "Unable to create internal thread for hdmi\n");
+		goto lcd_out;
+	}
+
+	return 0;
+
+	kthread_stop(hd->thread);
+lcd_out:
+	lcd_device_unregister(hd->lcd);
+dev_out:
+	device_unregister(hd->dev);
+	hdmi_dprintk(1, "Fail to register hdmi dev\n");
+	return rc;
+}
+EXPORT_SYMBOL(hdmi_dev_register);
+
+/**
+ * hdmi_dev_unregister - unregisters a object of hdmi_dev class.
+ * @hd: the hdmi device object to be unregistered and freed.
+ *
+ * Unregisters a previously registered via hdmi_dev_register object.
+ */
+void hdmi_dev_unregister(struct hdmi_dev *hd)
+{
+	if (!hd)
+		return;
+
+	mutex_lock(&hd->hdmi_lock);
+	hd->ops = NULL;
+	mutex_unlock(&hd->hdmi_lock);
+	lcd_device_unregister(hd->lcd);
+
+	kthread_stop(hd->thread);
+	device_unregister(hd->dev);
+}
+EXPORT_SYMBOL(hdmi_dev_unregister);
+
+static void __exit hdmi_class_exit(void)
+{
+	class_destroy(hdmi_class);
+}
+
+static int __init hdmi_class_init(void)
+{
+	hdmi_dprintk(1, "create hdmi class...\n");
+	hdmi_class = class_create(THIS_MODULE, "hdmi");
+	if (IS_ERR(hdmi_class)) {
+		hdmi_dprintk(1, "Unable to create hdmi class; errno = %ld\n",
+				PTR_ERR(hdmi_class));
+		return PTR_ERR(hdmi_class);
+	}
+
+	hdmi_class->dev_attrs = hdmi_dev_attributes;
+	hdmi_dprintk(1, "create hdmi class ok\n");
+	return 0;
+}
+
+subsys_initcall(hdmi_class_init);
+module_exit(hdmi_class_exit);
+
+module_param_named(debug, hdmi_core_debug, int, 0644);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/hdmi-mmp.c b/drivers/video/hdmi-mmp.c
new file mode 100644
index 0000000..f71f9fa
--- /dev/null
+++ b/drivers/video/hdmi-mmp.c
@@ -0,0 +1,445 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fb.h>
+#include <linux/freezer.h>
+#include <linux/gpio.h>
+#include <linux/hdmi.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/kthread.h>
+#include <linux/lcd.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <mach/mmp_hdmi.h>
+#include "hdmi-mmp.h"
+
+struct hdmi_mmp_dev {
+	struct hdmi_dev hdmi_dev;
+	struct clk *clk;
+	void *base;
+	void *sspa1_reg_base;
+	unsigned int gpio;
+	unsigned int irq;
+};
+
+static void hdmi_direct_write(void *base, unsigned addr, unsigned data)
+{
+	__raw_writel(data, base + BASE_OFFSET + addr);
+}
+
+static unsigned hdmi_direct_read(void *base, unsigned addr)
+{
+	return __raw_readl(base + BASE_OFFSET + addr);
+}
+
+static unsigned hdmi_read(void *base, unsigned addr)
+{
+	__raw_writel(addr | 1 << 30, base + BASE_OFFSET + HDMI_ADDR);
+	return __raw_readl((base + BASE_OFFSET + HDMI_DATA)) & 0xff;
+}
+
+static void hdmi_write(void *base, unsigned addr, unsigned data)
+{
+	__raw_writel(data & 0xff, base + BASE_OFFSET + HDMI_DATA);
+	__raw_writel(addr | 1 << 31, base + BASE_OFFSET + HDMI_ADDR);
+}
+
+static void hdmi_write_multi(struct hdmi_mmp_dev *md, u32 addr, u8 *buf,
+		u32 length)
+{
+	u32 i;
+	for (i = 0; i < length; i++)
+		hdmi_write(md->base, addr + i, *(buf + i));
+}
+
+static int hdmi_mmp_init_controller(struct hdmi_dev *dev)
+{
+	struct hdmi_mmp_dev *md = (struct hdmi_mmp_dev *)dev;
+	u32 tmp;
+
+	printk(KERN_DEBUG "Init mmp hdmi ctroller...\n");
+	hdmi_direct_write(md->base, HDMI_PHY_CFG_2, 1<<27);
+
+	hdmi_direct_write(md->base, HDMI_PHY_CFG_0, 0X00249b6d);
+	hdmi_direct_write(md->base, HDMI_PHY_CFG_1, 0X0492eeee);
+	/* hdmi_direct_write(md->base, HDMI_PHY_CFG_2, 0X00003c30); */
+	hdmi_direct_write(md->base, HDMI_PHY_CFG_2, 0X00003c10);
+	tmp = hdmi_direct_read(md->base, HDMI_PHY_CFG_3);
+	hdmi_direct_write(md->base, HDMI_PHY_CFG_3, (tmp | 0X0000001));
+	msleep(50);
+	hdmi_direct_write(md->base, HDMI_PHY_CFG_3, (tmp & (~0X0000001)));
+	/* temporatyly set the aduio cfg */
+	/* hdmi_direct_write(md->base, HDMI_AUDIO_CFG, 0x869); */
+
+	hdmi_write(md->base, HDTX_HST_PKT_CTRL0, 0);
+	hdmi_write(md->base, HDTX_HST_PKT_CTRL1, 0);
+
+	return 0;
+}
+
+static int hdmi_mmp_set_freq(struct hdmi_mmp_dev *md)
+{
+	int ret = 0;
+
+	/* Fix the pll2refdiv to 1(+2), to get 8.66MHz ref clk
+	 * Stable val recomended between 8-12MHz. To get the reqd
+	 * freq val, just program the fbdiv
+	 * freq takes effect during a fc req
+	 */
+	u32 residual;
+	volatile unsigned int temp = 0;
+	u32 freq;
+	unsigned int postdiv;
+	unsigned int freq_offset_inner;
+	unsigned int hdmi_pll_fbdiv;
+	unsigned int hdmi_pll_refdiv;
+	unsigned int kvco;
+	unsigned int freq_offset_inner_16;
+	unsigned int VPLL_CALCLK_DIV = 1;
+	unsigned int VDDM = 1;
+	unsigned int VDDL = 0x9;
+	unsigned int ICP = 0x9;
+	unsigned int VREG_IVREF = 2;
+	unsigned int INTPI = 5;
+	unsigned int VTH_VPLL_CA = 2;
+	int count = 0x10000;
+
+	ret = hdmi_get_freq ((struct hdmi_dev *)md, &freq);
+	if (ret)
+		return ret;
+
+	freq /= 1000;
+	/* round to freq = N*100k Hz */
+	residual = freq % 100;
+	freq = residual? (freq + 100 - residual) : freq;
+	freq /= 1000;
+
+	switch (freq) {
+	case 25:
+		postdiv = 0x3;
+		freq_offset_inner = 0x1C47;
+		freq_offset_inner_16 = 1;
+		hdmi_pll_fbdiv = 39;
+		hdmi_pll_refdiv = 0;
+		kvco = 0x4;
+		break;
+	case 27:
+		postdiv = 0x3;
+		freq_offset_inner = 0x2d03;
+		freq_offset_inner_16 = 1;
+		hdmi_pll_fbdiv = 42;
+		hdmi_pll_refdiv = 0;
+		kvco = 0x4;
+		break;
+	case 54:
+		postdiv = 0x2;
+		freq_offset_inner = 0x2d03;
+		freq_offset_inner_16 = 1;
+		hdmi_pll_fbdiv = 42;
+		hdmi_pll_refdiv = 0;
+		kvco = 0x4;
+		break;
+	case 74:
+		postdiv = 0x2;
+		freq_offset_inner = 0x084B;
+		freq_offset_inner_16 = 0;
+		hdmi_pll_fbdiv = 57;
+		hdmi_pll_refdiv = 0;
+		kvco = 0x4;
+		break;
+	case 108:
+		postdiv = 0x1;
+		freq_offset_inner = 0x2d03;
+		freq_offset_inner_16 = 1;
+		hdmi_pll_fbdiv = 42;
+		hdmi_pll_refdiv = 0;
+		kvco = 0x4;
+		break;
+	case 148:
+		postdiv = 0x1;
+		freq_offset_inner = 0x084B;
+		freq_offset_inner_16 = 0;
+		hdmi_pll_fbdiv = 57;
+		hdmi_pll_refdiv = 0;
+		kvco = 0x4;
+		break;
+	default:
+		printk(KERN_ERR "not supported freq %d\n", freq);
+		return -EINVAL;
+	}
+
+	hdmi_direct_write(md->base, HDMI_CLOCK_CFG, 1|1<<2|1<<4|4<<5|5<<9|5<<13);
+	//hdmi_direct_write(md->base, HDMI_CLOCK_CFG, 1|1<<2|4<<5|5<<9|5<<13);
+
+	/* power up the pll */
+	temp |= HDMI_PLL_CFG_3_HDMI_PLL_ON;
+	hdmi_direct_write(md->base, HDMI_PLL_CFG_3, temp);
+
+	temp = ((INTPI << HDMI_PLL_CFG_0_INTPI_BASE)	|
+			(HDMI_PLL_CFG_0_CLK_DET_EN)	|
+			(VDDM << HDMI_PLL_CFG_0_VDDM_BASE)	|
+			(VDDL << HDMI_PLL_CFG_0_VDDL_BASE)	|
+			(ICP << HDMI_PLL_CFG_0_ICP_BASE)	|
+			(kvco << HDMI_PLL_CFG_0_KVCO_BASE)	|
+			(VREG_IVREF << HDMI_PLL_CFG_0_VREG_IVREF_BASE)	|
+			(VPLL_CALCLK_DIV << HDMI_PLL_CFG_0_VPLL_CALCLK_DIV_BASE) |
+			(postdiv << HDMI_PLL_CFG_0_POSTDIV_BASE)	|
+			(ICP  << HDMI_PLL_CFG_0_ICP_BASE));
+	hdmi_direct_write(md->base, HDMI_PLL_CFG_0, temp);
+
+	temp = ((HDMI_PLL_CFG_1_MODE) |
+			(HDMI_PLL_CFG_1_EN_PANNEL)|
+			(HDMI_PLL_CFG_1_EN_HDMI)|
+			(VTH_VPLL_CA << HDMI_PLL_CFG_1_VTH_VPLL_CA_BASE)|
+			(freq_offset_inner << HDMI_PLL_CFG_1_FREQ_OFFSET_INNER_BASE)|
+			(freq_offset_inner_16 << 20));
+	hdmi_direct_write(md->base, HDMI_PLL_CFG_1, temp);
+
+	temp = 0;
+	hdmi_direct_write(md->base, HDMI_PLL_CFG_2, temp);
+
+	/* Power Down the pll and set the divider ratios*/
+	temp = (hdmi_pll_fbdiv << HDMI_PLL_CFG_3_HDMI_PLL_FBDIV_BASE)|
+		(hdmi_pll_refdiv << HDMI_PLL_CFG_3_HDMI_PLL_REFDIV_BASE);
+	hdmi_direct_write(md->base, HDMI_PLL_CFG_3, temp);
+
+	/* power up the pll */
+	temp |= HDMI_PLL_CFG_3_HDMI_PLL_ON;
+	hdmi_direct_write(md->base, HDMI_PLL_CFG_3, temp);
+
+	/*wait 10us : just an estimate*/
+	udelay(100);
+
+	/* release the pll out of reset*/
+	temp |= HDMI_PLL_CFG_3_HDMI_PLL_RSTB;
+	hdmi_direct_write(md->base, HDMI_PLL_CFG_3, temp);
+
+	/* PLL_LOCK should be 1, */
+	temp = hdmi_direct_read(md->base, HDMI_PLL_CFG_3);
+	temp &= 0x400000;
+	while ( !temp )	{
+		temp = hdmi_direct_read(md->base, HDMI_PLL_CFG_3);
+		temp &= 0x400000;
+		udelay(10);
+		count--;
+		if (count <= 0) {
+			printk(KERN_ERR "%s PLL lock error, PLL_CFG_3 %x\n",
+				__func__, temp);
+			return -EIO;
+		}
+	}
+	/* pll stablize in 10ms */
+	msleep(10);
+	return ret;
+}
+
+static irqreturn_t hdmi_mmp_hpd(int irq, void *data)
+{
+	printk(KERN_INFO "gpio hpd detected\n");
+
+	hdmi_event_signal((struct hdmi_dev *)data, HDMI_EV_PLUG);
+	return IRQ_HANDLED;
+}
+
+static int hdmi_mmp_get_hpd_status (struct hdmi_dev *dev)
+{
+	struct hdmi_mmp_dev *mh = (struct hdmi_mmp_dev *)dev;
+
+	return ! gpio_get_value(mh->gpio);
+}
+
+static int hdmi_send_packet (struct hdmi_mmp_dev *md, char *buf, int idx)
+{
+	u32 v;
+
+	hdmi_write_multi (md, HDTX_PKT0_BYTE0 + 0x20 * idx, buf, 32);
+
+	v = hdmi_read(md->base, HDTX_HST_PKT_CTRL0);
+	hdmi_write(md->base, HDTX_HST_PKT_CTRL0, v | (1 << idx));
+	/* HW bug */
+	v &= ~(1 << idx);
+	hdmi_write(md->base, HDTX_HST_PKT_CTRL0, v);
+	v = hdmi_read(md->base, HDTX_HST_PKT_CTRL1);
+	hdmi_write(md->base, HDTX_HST_PKT_CTRL1, v | (1 << idx));
+}
+
+static int hdmi_mmp_get_reg (struct hdmi_dev *dev)
+{
+	struct hdmi_mmp_dev *md = (struct hdmi_mmp_dev *)dev;
+	int i;
+
+	printk("************direct register*******************\n");
+	for (i = 0x8; i <= 0x30; i += 4)
+		printk("direct offset 0x%x is 0x%x\n", i, hdmi_direct_read(md->base, i));
+	printk("************indirect register*******************\n");
+	for (i = 0; i < 0x13e; i++)
+		printk("offset 0x%x is 0x%x\n", i, hdmi_read(md->base, i));
+}
+
+static int hdmi_mmp_set_reg (struct hdmi_dev *dev)
+{
+	struct hdmi_mmp_dev *md = (struct hdmi_mmp_dev *)dev;
+
+	return 0;
+}
+
+static int hdmi_mmp_set_mode (struct hdmi_dev *dev, cea_mode_id id)
+{
+	char *buf;
+	u32 v;
+	struct hdmi_mmp_dev *md = (struct hdmi_mmp_dev *)dev;
+
+	dev->mode_id = id;
+	hdmi_mmp_set_freq (md);
+	buf = kzalloc (32, GFP_KERNEL);
+
+	/* get proper avi packet */
+	hdmi_avi_info_frame (dev, buf);
+	/* write avi packet */
+	hdmi_send_packet (dev, buf, 0);
+	memset (buf, 0, 32);
+	hdmi_vender_info_frame (dev, buf);
+	hdmi_send_packet (dev, buf, 1);
+
+	v = hdmi_read(md->base, HDTX_HDMI_CTRL);
+	v &= ~0x78;
+	hdmi_write(md->base, HDTX_HDMI_CTRL, v | 0x1 | (dev->pixel_rept));
+	/* auto detect lcd timing */
+	hdmi_write(md->base, HDTX_VIDEO_CTRL, 0x58);
+	return 0;
+}
+
+static int hdmi_mmp_enable(struct hdmi_dev *dev, int enable)
+{
+	struct hdmi_mmp_dev *md = (struct hdmi_mmp_dev *)dev;
+	u32 v;
+
+	v = hdmi_direct_read(md->base, HDMI_CLOCK_CFG);
+	v = enable ? (v | (1<<4)) : (v & ~(1<<4));
+	hdmi_direct_write(md->base, HDMI_CLOCK_CFG, v);
+
+	/* magic register */
+	hdmi_write(md->base, HDTX_TDATA3_1, 0x83);
+	hdmi_direct_write(md->base, HDMI_PHY_CFG_2, 0x3c10);
+	//hdmi_direct_write(md->base,0x1c, 0xaa95);
+	return 0;
+}
+
+static struct hdmi_ops mmp_hdmi_ops = {
+	.init = hdmi_mmp_init_controller,
+	.get_hpd = hdmi_mmp_get_hpd_status,
+	.set_mode = hdmi_mmp_set_mode,
+	.set_enable = hdmi_mmp_enable,
+	.get_reg = hdmi_mmp_get_reg,
+	.set_reg = hdmi_mmp_set_reg,
+};
+
+static int __devinit hdmi_mmp_probe(struct platform_device *pdev)
+{
+	struct hdmi_mmp_mach_info *pdata;
+	struct hdmi_mmp_dev *mdev;
+	struct resource *res;
+	struct clk *clk;
+	int ret = -ENOMEM;
+
+	clk = clk_get(NULL, "HDMICLK");
+	if (IS_ERR(clk)) {
+		dev_err(&pdev->dev, "unable to get HDMICLK\n");
+		return PTR_ERR(clk);
+	}
+	clk_enable(clk);
+
+	mdev = kzalloc(sizeof(struct hdmi_mmp_dev), GFP_KERNEL);
+	if (!mdev)
+		goto out_mdev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	pdata = pdev->dev.platform_data;
+	if (res == NULL) {
+		printk(KERN_ERR "hdmi_probe: no memory resources given\n");
+		goto out_mdev;
+	}
+
+	mdev->base = ioremap(res->start, res->end - res->start + 1);
+	if (mdev->base == NULL) {
+		printk(KERN_ERR "%s: can't remap resgister area\n", __func__);
+		goto out_mdev;
+	}
+	mdev->gpio = pdata->gpio;
+	mdev->clk = clk;
+
+	ret = gpio_request(pdata->gpio, pdev->name);
+	if (ret < 0)
+		goto out_mdev;
+
+	ret = gpio_direction_input(pdata->gpio);
+	if (ret < 0)
+		goto out_free;
+
+	ret = request_irq(gpio_to_irq(pdata->gpio),
+			hdmi_mmp_hpd, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+			"HDMI HPD irq", mdev);
+	if(ret < 0)
+		goto free_gpio;
+
+	platform_set_drvdata(pdev, pdata);
+
+	ret = hdmi_dev_register((struct hdmi_dev*)mdev, &pdev->dev,
+		mdev, &mmp_hdmi_ops, pdata->fb_index);
+	if (ret) {
+		printk(KERN_ERR "mmp hdmi_dev_register fail\n");
+		goto irq_free;
+	}
+	return 0;
+
+irq_free:
+	free_irq(gpio_to_irq(pdata->gpio), NULL);
+free_gpio:
+	gpio_free(pdata->gpio);
+out_free:
+out_mdev:
+	kfree(mdev);
+	clk_disable(clk);
+	clk_put(clk);
+	return ret;
+}
+
+static struct platform_driver hdmi_mmp_driver = {
+	.driver         = {
+		.name   = "mmp-hdmi",
+		.owner  = THIS_MODULE,
+	},
+	.probe          = hdmi_mmp_probe,
+	//.remove         = __devexit_p(hdmi_mmp_remove),
+};
+
+static int __devinit hdmi_mmp_init(void)
+{
+	return platform_driver_register(&hdmi_mmp_driver);
+}
+
+module_init(hdmi_mmp_init);
+
+MODULE_AUTHOR("Jun Nie <njun@xxxxxxxxxxx>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("HDMI driver for Marvell MMP");
+MODULE_ALIAS("hdmi-mmp");
diff --git a/drivers/video/modedb.c b/drivers/video/modedb.c
index cb175fe..93f5b6e 100644
--- a/drivers/video/modedb.c
+++ b/drivers/video/modedb.c
@@ -292,62 +292,63 @@ static const struct fb_videomode modedb[] = {
 };

 #ifdef CONFIG_FB_MODE_HELPERS
-const struct fb_videomode cea_modes[64] = {
+const struct fb_videomode cea_modes[65] = {
 	/* #1: 640x480p@59.94/60Hz */
 	[1] = {
 		NULL, 60, 640, 480, 39722, 48, 16, 33, 10, 96, 2, 0,
-		FB_VMODE_NONINTERLACED, 0,
+		FB_VMODE_NONINTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #3: 720x480p@59.94/60Hz */
 	[3] = {
 		NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0,
-		FB_VMODE_NONINTERLACED, 0,
+		FB_VMODE_NONINTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #5: 1920x1080i@59.94/60Hz */
 	[5] = {
 		NULL, 60, 1920, 1080, 13763, 148, 88, 15, 2, 44, 5,
 		FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
-		FB_VMODE_INTERLACED, 0,
+		FB_VMODE_INTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #7: 720(1440)x480iH@59.94/60Hz */
 	[7] = {
 		NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0,
-		FB_VMODE_INTERLACED, 0,
+		FB_VMODE_INTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #9: 720(1440)x240pH@59.94/60Hz */
 	[9] = {
 		NULL, 60, 1440, 240, 18554, 114, 38, 16, 4, 124, 3, 0,
-		FB_VMODE_NONINTERLACED, 0,
+		FB_VMODE_NONINTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #18: 720x576pH@50Hz */
 	[18] = {
 		NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0,
-		FB_VMODE_NONINTERLACED, 0,
+		FB_VMODE_NONINTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #19: 1280x720p@50Hz */
 	[19] = {
 		NULL, 50, 1280, 720, 13468, 220, 440, 20, 5, 40, 5,
 		FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
-		FB_VMODE_NONINTERLACED, 0,
+		FB_VMODE_NONINTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #20: 1920x1080i@50Hz */
 	[20] = {
 		NULL, 50, 1920, 1080, 13480, 148, 528, 15, 5, 528, 5,
 		FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
-		FB_VMODE_INTERLACED, 0,
+		FB_VMODE_INTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #32: 1920x1080p@23.98/24Hz */
 	[32] = {
 		NULL, 24, 1920, 1080, 13468, 148, 638, 36, 4, 44, 5,
 		FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
-		FB_VMODE_NONINTERLACED, 0,
+		FB_VMODE_NONINTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #35: (2880)x480p4x@59.94/60Hz */
 	[35] = {
 		NULL, 60, 2880, 480, 9250, 240, 64, 30, 9, 248, 6, 0,
-		FB_VMODE_NONINTERLACED, 0,
+		FB_VMODE_NONINTERLACED, FB_MODE_IS_CEA,
 	},
 };
+EXPORT_SYMBOL(cea_modes);

 const struct fb_videomode vesa_modes[] = {
 	/* 0 640x350-85 VESA */
@@ -1119,6 +1120,24 @@ finished:
 }
 EXPORT_SYMBOL(fb_find_best_display);

+/**
+ * fb_find_cea_mode_index - find a cea video mode index
+ *
+ * RETURNS:
+ * cea mode index, 0 if none found
+ */
+const int fb_find_cea_mode_index(struct fb_videomode *m)
+{
+	int idx;
+
+	for (idx = 1; idx <= 64; idx ++) {
+		if (fb_mode_is_equal(&cea_modes[idx], m))
+			return idx;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(fb_find_cea_mode_index);
+
 EXPORT_SYMBOL(fb_videomode_to_var);
 EXPORT_SYMBOL(fb_var_to_videomode);
 EXPORT_SYMBOL(fb_mode_is_equal);
diff --git a/include/linux/fb.h b/include/linux/fb.h
index 6a82748..14672f8 100644
--- a/include/linux/fb.h
+++ b/include/linux/fb.h
@@ -1082,10 +1082,24 @@ extern void fb_bl_default_curve(struct fb_info
*fb_info, u8 off, u8 min, u8 max)
 #define FB_MODE_IS_DETAILED	1
 #define FB_MODE_IS_STANDARD	2
 #define FB_MODE_IS_VESA		4
+#define FB_MODE_IS_CEA		5
 #define FB_MODE_IS_CALCULATED	8
 #define FB_MODE_IS_FIRST	16
 #define FB_MODE_IS_FROM_VAR     32

+typedef enum {
+	CEA_640x480p60 = 1,
+	CEA_720x480p60 = 3,
+	CEA_1920x1080i60 = 5,
+	CEA_720x480i60 = 7,
+	CEA_720x240p60 = 9,
+	CEA_720x576p50 = 18,
+	CEA_1280x720p50 = 19,
+	CEA_1920x1080i50 = 20,
+	CEA_1920x1080p24 = 32,
+	CEA_2880x480p60 =35,
+} cea_mode_id;
+
 extern int fbmon_dpms(const struct fb_info *fb_info);
 extern int fb_get_mode(int flags, u32 val, struct fb_var_screeninfo *var,
 		       struct fb_info *info);
@@ -1124,6 +1138,7 @@ extern void fb_videomode_to_modelist(const
struct fb_videomode *modedb, int num,
 				     struct list_head *head);
 extern const struct fb_videomode *fb_find_best_display(const struct
fb_monspecs *specs,
 						       struct list_head *head);
+extern const int fb_find_cea_mode_index(struct fb_videomode *m);

 /* drivers/video/fbcmap.c */
 extern int fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp);
@@ -1155,7 +1170,7 @@ struct fb_videomode {

 extern const char *fb_mode_option;
 extern const struct fb_videomode vesa_modes[];
-extern const struct fb_videomode cea_modes[64];
+extern const struct fb_videomode cea_modes[65];

 struct fb_modelist {
 	struct list_head list;
diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h
new file mode 100644
index 0000000..d8a3da9
--- /dev/null
+++ b/include/linux/hdmi.h
@@ -0,0 +1,164 @@
+/*
+ * HDMI Control Abstraction
+ *
+ */
+
+#ifndef _LINUX_HDMI_H
+#define _LINUX_HDMI_H
+
+#include <linux/device.h>
+#include <linux/mutex.h>
+//#include <linux/notifier.h>
+#include <linux/fb.h>
+#include <linux/lcd.h>
+
+/* Notes on locking:
+ *
+ * hdmi_dev->ops_lock is an internal backlight lock protecting the ops
+ * field and no code outside the core should need to touch it.
+ *
+ * Access to set_power() is serialised by the update_lock mutex since
+ * most drivers seem to need this and historically get it wrong.
+ *
+ * Most drivers don't need locking on their get_power() method.
+ * If yours does, you need to implement it in the driver. You can use the
+ * update_lock mutex if appropriate.
+ *
+ * Any other use of the locks below is probably wrong.
+ */
+
+struct hdmi_dev;
+struct fb_info;
+
+struct hdmi_properties {
+	/* source or sink */
+	u16 type;
+};
+
+struct hdmi_ops {
+	int (*init)(struct hdmi_dev *);
+	int (*get_hpd)(struct hdmi_dev *);
+	int (*enable)(struct hdmi_dev *, int);
+	/* Get the LCD panel power status (0: full on, 1..3: controller
+	   power on, flat panel power off, 4: full off), see FB_BLANK_XXX */
+	int (*get_power)(struct hdmi_dev *);
+	/* Enable or disable power to the LCD (0: on; 4: off, see FB_BLANK_XXX) */
+	int (*set_power)(struct hdmi_dev *, int power);
+	/* For debug */
+	int (*get_reg)(struct hdmi_dev *);
+	int (*set_reg)(struct hdmi_dev *, int offset, int v);
+	/* Get the current audio_cfg setting (0-max_audio_cfg) */
+	int (*get_audio_cfg)(struct hdmi_dev *);
+	/* Set LCD panel audio_cfg */
+        int (*set_audio_cfg)(struct hdmi_dev *, int audio_cfg);
+	int (*get_mode)(struct hdmi_dev *, struct fb_videomode *);
+	/* Set LCD panel mode (resolutions ...) */
+	int (*set_mode)(struct hdmi_dev *, cea_mode_id);
+	int (*get_enable)(struct hdmi_dev *);
+	int (*set_enable)(struct hdmi_dev *, int enable);
+	/* Check if given framebuffer device is the one LCD is bound to;
+	   return 0 if not, !=0 if it is. If NULL, hdmi always matches the fb. */
+	int (*check_mode)(struct hdmi_dev *, struct fb_videomode *);
+};
+
+enum hdmi_event_type {
+	/* trigger gpio irq */
+	HDMI_EV_PLUG,
+	HDMI_EV_UNPLUG,
+	/* trigger in gpio irq or from fb change */
+	HDMI_EV_VIDEO,
+	/* trigger in gpio irq or from Asoc change */
+	HDMI_EV_AUDIO,
+	/* trigger from CEC/TV */
+	HDMI_EV_CEC,
+};
+
+struct hdmi_event {
+	enum hdmi_event_type type;
+	struct list_head list;
+};
+
+#define RATIO_16V9_STR "16:9"
+#define RATIO_4V3_STR "4:3"
+#define RATIO_16V9 (0)
+#define RATIO_4V3 (1)
+
+struct hdmi_dev {
+	struct device		*dev;
+	struct hdmi_properties	props;
+	/* This protects the 'ops' field. If 'ops' is NULL, the driver that
+	   registered this device has been unloaded, and if class_get_devdata()
+	   points to something in the body of that driver, it is also invalid. */
+	struct mutex		hdmi_lock;
+	/* Serialise access to set_power method */
+	struct mutex		update_lock;
+	spinlock_t              lock;           /* lock for claim and bus ops */
+	struct list_head	hdmi_evt_list;
+	struct list_head	free_evt_list;
+	struct task_struct	*thread;
+	struct hdmi_event	*evt_ptr;
+	/* If this is NULL, the backing module is unloaded */
+	struct hdmi_ops *ops;
+	/* The framebuffer notifier block */
+	//struct notifier_block fb_notif;
+	struct lcd_device	*lcd;
+	struct fb_info		*fb_info;
+	cea_mode_id		mode_id;
+	//u32			deep_color;
+	u32			enable;
+	u32			pixel_rept;
+	u32			ratio;
+};
+
+struct hdmi_platform_data {
+	/* reset hdmi panel device. */
+	int (*reset)(struct hdmi_dev *ld);
+	/* on or off to hdmi panel. if 'enable' is 0 then
+	   hdmi power off and 1, hdmi power on. */
+	int (*power_on)(struct hdmi_dev *ld, int enable);
+
+	/* it indicates whether hdmi panel was enabled
+	   from bootloader or not. */
+	int hdmi_enabled;
+	/* video, audio, cec, hdcp */
+	unsigned int features;
+	/* it means delay for stable time when it becomes low to high
+	   or high to low that is dependent on whether reset gpio is
+	   low active or high active. */
+	unsigned int reset_delay;
+	/* stable time needing to become hdmi power on. */
+	unsigned int power_on_delay;
+	/* stable time needing to become hdmi power off. */
+	unsigned int power_off_delay;
+
+	/* it could be used for any purpose. */
+	void *pdata;
+};
+
+static inline void hdmi_set_power(struct hdmi_dev *ld, int power)
+{
+	mutex_lock(&ld->update_lock);
+	if (ld->ops && ld->ops->set_power)
+		ld->ops->set_power(ld, power);
+	mutex_unlock(&ld->update_lock);
+}
+
+extern int hdmi_dev_register(struct hdmi_dev *hdmi_dev,
+	struct device *parent, void *devdata, struct hdmi_ops *ops,
+	int fb_index);
+extern void hdmi_dev_unregister(struct hdmi_dev *ld);
+
+#define to_hdmi_dev(obj) container_of(obj, struct hdmi_dev, dev)
+
+static inline void * hdmi_get_data(struct hdmi_dev *ld_dev)
+{
+	return dev_get_drvdata(ld_dev->dev);
+}
+
+
+#endif
+
+int hdmi_get_freq (struct hdmi_dev *hd, u32 *need_freq);
+int hdmi_vender_info_frame (struct hdmi_dev *hd, char buf[]);
+int hdmi_avi_info_frame (struct hdmi_dev *hd, char buf[]);
+void hdmi_event_signal(struct hdmi_dev *dev, enum hdmi_event_type type);
diff --git a/include/linux/lcd.h b/include/linux/lcd.h
index 8877123..b09e0f9 100644
--- a/include/linux/lcd.h
+++ b/include/linux/lcd.h
@@ -48,6 +48,8 @@ struct lcd_ops {
         int (*set_contrast)(struct lcd_device *, int contrast);
 	/* Set LCD panel mode (resolutions ...) */
 	int (*set_mode)(struct lcd_device *, struct fb_videomode *);
+	/* Get LCD panel mode (resolutions ...) */
+	int (*get_mode)(struct lcd_device *, struct fb_videomode *);
 	/* Check if given framebuffer device is the one LCD is bound to;
 	   return 0 if not, !=0 if it is. If NULL, lcd always matches the fb. */
 	int (*check_fb)(struct lcd_device *, struct fb_info *);
diff --git a/include/linux/major.h b/include/linux/major.h
index 6a8ca98..e6e46e2 100644
--- a/include/linux/major.h
+++ b/include/linux/major.h
@@ -170,6 +170,7 @@
 #define IBM_FS3270_MAJOR	228

 #define VIOTAPE_MAJOR		230
+#define HDMI_MAJOR		231

 #define BLOCK_EXT_MAJOR		259
 #define SCSI_OSD_MAJOR		260	/* open-osd's OSD scsi device */
diff --git a/drivers/video/hdmi-mmp.c b/drivers/video/hdmi-mmp.c
new file mode 100644
index 0000000..f71f9fa
--- /dev/null
+++ b/drivers/video/hdmi-mmp.c
@@ -0,0 +1,445 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fb.h>
+#include <linux/freezer.h>
+#include <linux/gpio.h>
+#include <linux/hdmi.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/kthread.h>
+#include <linux/lcd.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <mach/mmp_hdmi.h>
+#include "hdmi-mmp.h"
+
+struct hdmi_mmp_dev {
+	struct hdmi_dev hdmi_dev;
+	struct clk *clk;
+	void *base;
+	void *sspa1_reg_base;
+	unsigned int gpio;
+	unsigned int irq;
+};
+
+static void hdmi_direct_write(void *base, unsigned addr, unsigned data)
+{
+	__raw_writel(data, base + BASE_OFFSET + addr);
+}
+
+static unsigned hdmi_direct_read(void *base, unsigned addr)
+{
+	return __raw_readl(base + BASE_OFFSET + addr);
+}
+
+static unsigned hdmi_read(void *base, unsigned addr)
+{
+	__raw_writel(addr | 1 << 30, base + BASE_OFFSET + HDMI_ADDR);
+	return __raw_readl((base + BASE_OFFSET + HDMI_DATA)) & 0xff;
+}
+
+static void hdmi_write(void *base, unsigned addr, unsigned data)
+{
+	__raw_writel(data & 0xff, base + BASE_OFFSET + HDMI_DATA);
+	__raw_writel(addr | 1 << 31, base + BASE_OFFSET + HDMI_ADDR);
+}
+
+static void hdmi_write_multi(struct hdmi_mmp_dev *md, u32 addr, u8 *buf,
+		u32 length)
+{
+	u32 i;
+	for (i = 0; i < length; i++)
+		hdmi_write(md->base, addr + i, *(buf + i));
+}
+
+static int hdmi_mmp_init_controller(struct hdmi_dev *dev)
+{
+	struct hdmi_mmp_dev *md = (struct hdmi_mmp_dev *)dev;
+	u32 tmp;
+
+	printk(KERN_DEBUG "Init mmp hdmi ctroller...\n");
+	hdmi_direct_write(md->base, HDMI_PHY_CFG_2, 1<<27);
+
+	hdmi_direct_write(md->base, HDMI_PHY_CFG_0, 0X00249b6d);
+	hdmi_direct_write(md->base, HDMI_PHY_CFG_1, 0X0492eeee);
+	/* hdmi_direct_write(md->base, HDMI_PHY_CFG_2, 0X00003c30); */
+	hdmi_direct_write(md->base, HDMI_PHY_CFG_2, 0X00003c10);
+	tmp = hdmi_direct_read(md->base, HDMI_PHY_CFG_3);
+	hdmi_direct_write(md->base, HDMI_PHY_CFG_3, (tmp | 0X0000001));
+	msleep(50);
+	hdmi_direct_write(md->base, HDMI_PHY_CFG_3, (tmp & (~0X0000001)));
+	/* temporatyly set the aduio cfg */
+	/* hdmi_direct_write(md->base, HDMI_AUDIO_CFG, 0x869); */
+
+	hdmi_write(md->base, HDTX_HST_PKT_CTRL0, 0);
+	hdmi_write(md->base, HDTX_HST_PKT_CTRL1, 0);
+
+	return 0;
+}
+
+static int hdmi_mmp_set_freq(struct hdmi_mmp_dev *md)
+{
+	int ret = 0;
+
+	/* Fix the pll2refdiv to 1(+2), to get 8.66MHz ref clk
+	 * Stable val recomended between 8-12MHz. To get the reqd
+	 * freq val, just program the fbdiv
+	 * freq takes effect during a fc req
+	 */
+	u32 residual;
+	volatile unsigned int temp = 0;
+	u32 freq;
+	unsigned int postdiv;
+	unsigned int freq_offset_inner;
+	unsigned int hdmi_pll_fbdiv;
+	unsigned int hdmi_pll_refdiv;
+	unsigned int kvco;
+	unsigned int freq_offset_inner_16;
+	unsigned int VPLL_CALCLK_DIV = 1;
+	unsigned int VDDM = 1;
+	unsigned int VDDL = 0x9;
+	unsigned int ICP = 0x9;
+	unsigned int VREG_IVREF = 2;
+	unsigned int INTPI = 5;
+	unsigned int VTH_VPLL_CA = 2;
+	int count = 0x10000;
+
+	ret = hdmi_get_freq ((struct hdmi_dev *)md, &freq);
+	if (ret)
+		return ret;
+
+	freq /= 1000;
+	/* round to freq = N*100k Hz */
+	residual = freq % 100;
+	freq = residual? (freq + 100 - residual) : freq;
+	freq /= 1000;
+
+	switch (freq) {
+	case 25:
+		postdiv = 0x3;
+		freq_offset_inner = 0x1C47;
+		freq_offset_inner_16 = 1;
+		hdmi_pll_fbdiv = 39;
+		hdmi_pll_refdiv = 0;
+		kvco = 0x4;
+		break;
+	case 27:
+		postdiv = 0x3;
+		freq_offset_inner = 0x2d03;
+		freq_offset_inner_16 = 1;
+		hdmi_pll_fbdiv = 42;
+		hdmi_pll_refdiv = 0;
+		kvco = 0x4;
+		break;
+	case 54:
+		postdiv = 0x2;
+		freq_offset_inner = 0x2d03;
+		freq_offset_inner_16 = 1;
+		hdmi_pll_fbdiv = 42;
+		hdmi_pll_refdiv = 0;
+		kvco = 0x4;
+		break;
+	case 74:
+		postdiv = 0x2;
+		freq_offset_inner = 0x084B;
+		freq_offset_inner_16 = 0;
+		hdmi_pll_fbdiv = 57;
+		hdmi_pll_refdiv = 0;
+		kvco = 0x4;
+		break;
+	case 108:
+		postdiv = 0x1;
+		freq_offset_inner = 0x2d03;
+		freq_offset_inner_16 = 1;
+		hdmi_pll_fbdiv = 42;
+		hdmi_pll_refdiv = 0;
+		kvco = 0x4;
+		break;
+	case 148:
+		postdiv = 0x1;
+		freq_offset_inner = 0x084B;
+		freq_offset_inner_16 = 0;
+		hdmi_pll_fbdiv = 57;
+		hdmi_pll_refdiv = 0;
+		kvco = 0x4;
+		break;
+	default:
+		printk(KERN_ERR "not supported freq %d\n", freq);
+		return -EINVAL;
+	}
+
+	hdmi_direct_write(md->base, HDMI_CLOCK_CFG, 1|1<<2|1<<4|4<<5|5<<9|5<<13);
+	//hdmi_direct_write(md->base, HDMI_CLOCK_CFG, 1|1<<2|4<<5|5<<9|5<<13);
+
+	/* power up the pll */
+	temp |= HDMI_PLL_CFG_3_HDMI_PLL_ON;
+	hdmi_direct_write(md->base, HDMI_PLL_CFG_3, temp);
+
+	temp = ((INTPI << HDMI_PLL_CFG_0_INTPI_BASE)	|
+			(HDMI_PLL_CFG_0_CLK_DET_EN)	|
+			(VDDM << HDMI_PLL_CFG_0_VDDM_BASE)	|
+			(VDDL << HDMI_PLL_CFG_0_VDDL_BASE)	|
+			(ICP << HDMI_PLL_CFG_0_ICP_BASE)	|
+			(kvco << HDMI_PLL_CFG_0_KVCO_BASE)	|
+			(VREG_IVREF << HDMI_PLL_CFG_0_VREG_IVREF_BASE)	|
+			(VPLL_CALCLK_DIV << HDMI_PLL_CFG_0_VPLL_CALCLK_DIV_BASE) |
+			(postdiv << HDMI_PLL_CFG_0_POSTDIV_BASE)	|
+			(ICP  << HDMI_PLL_CFG_0_ICP_BASE));
+	hdmi_direct_write(md->base, HDMI_PLL_CFG_0, temp);
+
+	temp = ((HDMI_PLL_CFG_1_MODE) |
+			(HDMI_PLL_CFG_1_EN_PANNEL)|
+			(HDMI_PLL_CFG_1_EN_HDMI)|
+			(VTH_VPLL_CA << HDMI_PLL_CFG_1_VTH_VPLL_CA_BASE)|
+			(freq_offset_inner << HDMI_PLL_CFG_1_FREQ_OFFSET_INNER_BASE)|
+			(freq_offset_inner_16 << 20));
+	hdmi_direct_write(md->base, HDMI_PLL_CFG_1, temp);
+
+	temp = 0;
+	hdmi_direct_write(md->base, HDMI_PLL_CFG_2, temp);
+
+	/* Power Down the pll and set the divider ratios*/
+	temp = (hdmi_pll_fbdiv << HDMI_PLL_CFG_3_HDMI_PLL_FBDIV_BASE)|
+		(hdmi_pll_refdiv << HDMI_PLL_CFG_3_HDMI_PLL_REFDIV_BASE);
+	hdmi_direct_write(md->base, HDMI_PLL_CFG_3, temp);
+
+	/* power up the pll */
+	temp |= HDMI_PLL_CFG_3_HDMI_PLL_ON;
+	hdmi_direct_write(md->base, HDMI_PLL_CFG_3, temp);
+
+	/*wait 10us : just an estimate*/
+	udelay(100);
+
+	/* release the pll out of reset*/
+	temp |= HDMI_PLL_CFG_3_HDMI_PLL_RSTB;
+	hdmi_direct_write(md->base, HDMI_PLL_CFG_3, temp);
+
+	/* PLL_LOCK should be 1, */
+	temp = hdmi_direct_read(md->base, HDMI_PLL_CFG_3);
+	temp &= 0x400000;
+	while ( !temp )	{
+		temp = hdmi_direct_read(md->base, HDMI_PLL_CFG_3);
+		temp &= 0x400000;
+		udelay(10);
+		count--;
+		if (count <= 0) {
+			printk(KERN_ERR "%s PLL lock error, PLL_CFG_3 %x\n",
+				__func__, temp);
+			return -EIO;
+		}
+	}
+	/* pll stablize in 10ms */
+	msleep(10);
+	return ret;
+}
+
+static irqreturn_t hdmi_mmp_hpd(int irq, void *data)
+{
+	printk(KERN_INFO "gpio hpd detected\n");
+
+	hdmi_event_signal((struct hdmi_dev *)data, HDMI_EV_PLUG);
+	return IRQ_HANDLED;
+}
+
+static int hdmi_mmp_get_hpd_status (struct hdmi_dev *dev)
+{
+	struct hdmi_mmp_dev *mh = (struct hdmi_mmp_dev *)dev;
+
+	return ! gpio_get_value(mh->gpio);
+}
+
+static int hdmi_send_packet (struct hdmi_mmp_dev *md, char *buf, int idx)
+{
+	u32 v;
+
+	hdmi_write_multi (md, HDTX_PKT0_BYTE0 + 0x20 * idx, buf, 32);
+
+	v = hdmi_read(md->base, HDTX_HST_PKT_CTRL0);
+	hdmi_write(md->base, HDTX_HST_PKT_CTRL0, v | (1 << idx));
+	/* HW bug */
+	v &= ~(1 << idx);
+	hdmi_write(md->base, HDTX_HST_PKT_CTRL0, v);
+	v = hdmi_read(md->base, HDTX_HST_PKT_CTRL1);
+	hdmi_write(md->base, HDTX_HST_PKT_CTRL1, v | (1 << idx));
+}
+
+static int hdmi_mmp_get_reg (struct hdmi_dev *dev)
+{
+	struct hdmi_mmp_dev *md = (struct hdmi_mmp_dev *)dev;
+	int i;
+
+	printk("************direct register*******************\n");
+	for (i = 0x8; i <= 0x30; i += 4)
+		printk("direct offset 0x%x is 0x%x\n", i, hdmi_direct_read(md->base, i));
+	printk("************indirect register*******************\n");
+	for (i = 0; i < 0x13e; i++)
+		printk("offset 0x%x is 0x%x\n", i, hdmi_read(md->base, i));
+}
+
+static int hdmi_mmp_set_reg (struct hdmi_dev *dev)
+{
+	struct hdmi_mmp_dev *md = (struct hdmi_mmp_dev *)dev;
+
+	return 0;
+}
+
+static int hdmi_mmp_set_mode (struct hdmi_dev *dev, cea_mode_id id)
+{
+	char *buf;
+	u32 v;
+	struct hdmi_mmp_dev *md = (struct hdmi_mmp_dev *)dev;
+
+	dev->mode_id = id;
+	hdmi_mmp_set_freq (md);
+	buf = kzalloc (32, GFP_KERNEL);
+
+	/* get proper avi packet */
+	hdmi_avi_info_frame (dev, buf);
+	/* write avi packet */
+	hdmi_send_packet (dev, buf, 0);
+	memset (buf, 0, 32);
+	hdmi_vender_info_frame (dev, buf);
+	hdmi_send_packet (dev, buf, 1);
+
+	v = hdmi_read(md->base, HDTX_HDMI_CTRL);
+	v &= ~0x78;
+	hdmi_write(md->base, HDTX_HDMI_CTRL, v | 0x1 | (dev->pixel_rept));
+	/* auto detect lcd timing */
+	hdmi_write(md->base, HDTX_VIDEO_CTRL, 0x58);
+	return 0;
+}
+
+static int hdmi_mmp_enable(struct hdmi_dev *dev, int enable)
+{
+	struct hdmi_mmp_dev *md = (struct hdmi_mmp_dev *)dev;
+	u32 v;
+
+	v = hdmi_direct_read(md->base, HDMI_CLOCK_CFG);
+	v = enable ? (v | (1<<4)) : (v & ~(1<<4));
+	hdmi_direct_write(md->base, HDMI_CLOCK_CFG, v);
+
+	/* magic register */
+	hdmi_write(md->base, HDTX_TDATA3_1, 0x83);
+	hdmi_direct_write(md->base, HDMI_PHY_CFG_2, 0x3c10);
+	//hdmi_direct_write(md->base,0x1c, 0xaa95);
+	return 0;
+}
+
+static struct hdmi_ops mmp_hdmi_ops = {
+	.init = hdmi_mmp_init_controller,
+	.get_hpd = hdmi_mmp_get_hpd_status,
+	.set_mode = hdmi_mmp_set_mode,
+	.set_enable = hdmi_mmp_enable,
+	.get_reg = hdmi_mmp_get_reg,
+	.set_reg = hdmi_mmp_set_reg,
+};
+
+static int __devinit hdmi_mmp_probe(struct platform_device *pdev)
+{
+	struct hdmi_mmp_mach_info *pdata;
+	struct hdmi_mmp_dev *mdev;
+	struct resource *res;
+	struct clk *clk;
+	int ret = -ENOMEM;
+
+	clk = clk_get(NULL, "HDMICLK");
+	if (IS_ERR(clk)) {
+		dev_err(&pdev->dev, "unable to get HDMICLK\n");
+		return PTR_ERR(clk);
+	}
+	clk_enable(clk);
+
+	mdev = kzalloc(sizeof(struct hdmi_mmp_dev), GFP_KERNEL);
+	if (!mdev)
+		goto out_mdev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	pdata = pdev->dev.platform_data;
+	if (res == NULL) {
+		printk(KERN_ERR "hdmi_probe: no memory resources given\n");
+		goto out_mdev;
+	}
+
+	mdev->base = ioremap(res->start, res->end - res->start + 1);
+	if (mdev->base == NULL) {
+		printk(KERN_ERR "%s: can't remap resgister area\n", __func__);
+		goto out_mdev;
+	}
+	mdev->gpio = pdata->gpio;
+	mdev->clk = clk;
+
+	ret = gpio_request(pdata->gpio, pdev->name);
+	if (ret < 0)
+		goto out_mdev;
+
+	ret = gpio_direction_input(pdata->gpio);
+	if (ret < 0)
+		goto out_free;
+
+	ret = request_irq(gpio_to_irq(pdata->gpio),
+			hdmi_mmp_hpd, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+			"HDMI HPD irq", mdev);
+	if(ret < 0)
+		goto free_gpio;
+
+	platform_set_drvdata(pdev, pdata);
+
+	ret = hdmi_dev_register((struct hdmi_dev*)mdev, &pdev->dev,
+		mdev, &mmp_hdmi_ops, pdata->fb_index);
+	if (ret) {
+		printk(KERN_ERR "mmp hdmi_dev_register fail\n");
+		goto irq_free;
+	}
+	return 0;
+
+irq_free:
+	free_irq(gpio_to_irq(pdata->gpio), NULL);
+free_gpio:
+	gpio_free(pdata->gpio);
+out_free:
+out_mdev:
+	kfree(mdev);
+	clk_disable(clk);
+	clk_put(clk);
+	return ret;
+}
+
+static struct platform_driver hdmi_mmp_driver = {
+	.driver         = {
+		.name   = "mmp-hdmi",
+		.owner  = THIS_MODULE,
+	},
+	.probe          = hdmi_mmp_probe,
+	//.remove         = __devexit_p(hdmi_mmp_remove),
+};
+
+static int __devinit hdmi_mmp_init(void)
+{
+	return platform_driver_register(&hdmi_mmp_driver);
+}
+
+module_init(hdmi_mmp_init);
+
+MODULE_AUTHOR("Jun Nie <njun@xxxxxxxxxxx>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("HDMI driver for Marvell MMP");
+MODULE_ALIAS("hdmi-mmp");
diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c
new file mode 100644
index 0000000..c845dc7
--- /dev/null
+++ b/drivers/video/hdmi.c
@@ -0,0 +1,675 @@
+/*
+ * HDMI Control Abstraction
+ *
+ * This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fb.h>
+#include <linux/freezer.h>
+#include <linux/hdmi.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/kthread.h>
+#include <linux/lcd.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define HDMI_DEBUG
+static int hdmi_core_debug = 5;
+#define hdmi_dprintk(level, fmt, arg...)	if (hdmi_core_debug >= level) \
+	printk(KERN_DEBUG "%s: " fmt , __func__, ## arg)
+
+/* Define the max number of events to buffer */
+#define MAX_HDMI_EVENT_SIZE      16
+
+static int hdmidevno;
+
+/* ==================== info frame utility ===================== */
+
+int hdmi_vender_info_frame (struct hdmi_dev *hd, char buf[])
+{
+	buf[0] = 0x84;
+	buf[1] = 0x1;
+	buf[2] = 0xa;
+	buf[3] = 0x70;
+	buf[4] = 0x1;
+}
+EXPORT_SYMBOL_GPL(hdmi_vender_info_frame);
+
+int hdmi_avi_info_frame (struct hdmi_dev *hd, char buf[])
+{
+	int count;
+	char check_sum;
+
+	hdmi_dprintk(3, "pack avi infoframe\n");
+	/* packet header for AVI Packet in pkt1 */
+	/* Fix me. we should set bits according to format */
+	buf[0] = 0x82; /* InfoFrame Type, AVI frame */
+	buf[1] = 0x2;  /* Version = 02 */
+	buf[2] = 0xd;  /* Length = 13 */
+	buf[3] = 0x0;  /* checksum */
+	buf[4] = 0x0; /* Fix to RGB format currently */
+	/*
+	buf[4] = 0x10; // RGB, Active Format(r0,r1,r2,r3) valid, no bar, no scan
+	buf[4] = 0x30; // HDMI_YUV422, Active Format(r0,r1,r2,r3) valid, no bar, no scan
+	buf[4] = 0x50; // HDMI_YUV444, Active Format(r0,r1,r2,r3) valid, no bar, no scan
+	*/
+
+	buf[5] = 0xa8;  /* ITU-R 709, 16:9 */
+	buf[6] = 0x20;
+	/* buf[6] = 0x0;   // No IT content, xvYCC601, no scaling */
+	buf[7] = hd->mode_id;
+	/* limited YCC range, graphics, no repeat  Pixel repitition factor */
+	buf[8] = hd->pixel_rept;
+
+	buf[9] = 0x0;
+	buf[10] = 0x0;
+	buf[11] = 0x0;
+	buf[12] = 0x0;
+	buf[13] = 0x0;
+	check_sum = 0;
+    for (count = 0; count < 14; count++)
+	    check_sum += buf[count];
+    buf[3] = 0x100 - check_sum;
+    return 0;
+}
+EXPORT_SYMBOL_GPL(hdmi_avi_info_frame);
+
+int hdmi_get_freq (struct hdmi_dev *hd, u32 *need_freq)
+{
+	u64 div_result;
+	int freq;
+
+	/* Calc divider according to pclk in pico second */
+	div_result = 1000000000000ll;
+	do_div(div_result, cea_modes[hd->mode_id].pixclock);
+	freq = (u32)div_result;
+
+	hdmi_dprintk(3, "get base pclk %d\n", freq);
+
+	if (FB_VMODE_INTERLACED & cea_modes[hd->mode_id].vmode)
+		freq >>= 1;
+
+	if (36 == hd->fb_info->var.bits_per_pixel)
+		freq += freq >> 1;
+	else if (30 == hd->fb_info->var.bits_per_pixel)
+		freq += freq >> 2;
+
+	/* We only support repeat=2/4 currently. Add more later */
+	if (!(hd->pixel_rept == 0 || hd->pixel_rept == 1
+				|| hd->pixel_rept == 3)) {
+		hdmi_dprintk(3, "Not support pix repeat %d\n", hd->pixel_rept);
+		return -EINVAL;
+	}
+
+	if (1 == hd->pixel_rept)
+	{
+		if (!(FB_VMODE_INTERLACED & cea_modes[hd->mode_id].vmode))
+			freq <<= 1;
+	} else if (3 == hd->pixel_rept) {
+		if (!(FB_VMODE_INTERLACED & cea_modes[hd->mode_id].vmode))
+			freq <<= 2;
+		else
+			freq <<= 1;
+	}
+
+	hdmi_dprintk(3, "get hdmi pclk %d\n", freq);
+	*need_freq = freq;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(hdmi_get_freq);
+
+/* ==================== video part ===================== */
+int hdmi_lcd_check_fb(struct lcd_device * ld, struct fb_info *fi)
+{
+	struct hdmi_dev *hd = lcd_get_data(ld);
+
+	hdmi_dprintk(3, "hdmi_lcd_check_fb..\n");
+	return 1;
+}
+
+/* We'd better update mode list in hot plug operation.
+   LCD framework does not implement get_mode yet */
+static int hdmi_lcd_get_mode(struct lcd_device *ld, struct fb_videomode *mode)
+{
+	struct hdmi_dev *hd = lcd_get_data(ld);
+
+	/* TODO: return parsed mode list */
+	return 0;
+}
+
+static int hdmi_lcd_set_mode(struct lcd_device *ld, struct fb_videomode *mode)
+{
+	//struct hdmi_lcd_data *data = lcd_get_data(lcd);
+	struct hdmi_dev *hd = lcd_get_data(ld);
+	cea_mode_id mode_id;
+
+	hdmi_dprintk(3, "hdmi_lcd_set_mode dev\n");
+	mode_id = fb_find_cea_mode_index(mode);
+	if (!mode_id) {
+		hdmi_dprintk(1, "unknown video mode %d\n", mode_id);
+		return -EINVAL;
+	}
+	/* If high level on detection signal, no connection */
+	if(! hd->ops->get_hpd(hd))
+		return 0;
+
+	hd->ops->set_mode(hd, mode_id);
+
+	if (hd->ops && hd->ops->set_enable)
+		hd->ops->set_enable(hd, 1);
+}
+
+static struct lcd_ops hdmi_lcd_ops = {
+	.get_mode = hdmi_lcd_get_mode,
+	.set_mode = hdmi_lcd_set_mode,
+	.check_fb = hdmi_lcd_check_fb,
+};
+
+static int hdmi_register_lcd(struct hdmi_dev *dev)
+{
+	int ret = 0;
+
+	hdmi_dprintk(3, "registering lcd device...\n");
+	/* FIX ME! How to hook expected fb if more than one fb exist? */
+	dev->lcd = lcd_device_register("hdmi-lcd", dev->dev, dev,
+			&hdmi_lcd_ops);
+
+	if (IS_ERR(dev->lcd)) {
+		ret = PTR_ERR(dev->lcd);
+		dev->lcd = NULL;
+		return ret;
+	}
+	hdmi_dprintk(3, "registering lcd device ok\n");
+	return 0;
+}
+
+/* ==================== audio part ===================== */
+/* ==================== cec part ===================== */
+/* ==================== edid part ===================== */
+
+/* ==================== hot plug part ===================== */
+static int hdmi_hotplug(struct hdmi_dev *hd)
+{
+	int ret;
+
+	msleep(100);
+	ret = hd->ops->get_hpd(hd);
+	if(!ret) {
+		hdmi_dprintk(3, "uplugged. dev %p\n", hd);
+		ret = hd->ops->set_enable(hd, 0);
+		lock_fb_info(hd->fb_info);
+		/* We should clear mode list here */
+		/* ... */
+		unlock_fb_info(hd->fb_info);
+		return 0;
+	}
+
+	ret = hd->ops->init(hd);
+	if (ret) {
+		hdmi_dprintk(1, "init fail\n");
+	}
+
+	if(hd->fb_info && hd->fb_info->mode) {
+		lock_fb_info(hd->fb_info);
+		/* Fix me! We should get EDID and parse mode list here */
+		/* But now we use fix mode list first before EDID util is ready */
+		/* ... */
+		unlock_fb_info(hd->fb_info);
+
+		ret = hdmi_lcd_set_mode(hd->lcd, hd->fb_info->mode);
+		if (ret) {
+			hdmi_dprintk(1, "fail to set video mode\n");
+		}
+	} else {
+		hdmi_dprintk(3, "no avaliable video mode\n");
+	}
+
+	if (hd->enable)
+		ret = hd->ops->set_enable(hd, 1);
+	return ret;
+}
+
+/* Handling hot plug HDCP and CEC */
+static int hdmi_event_thread(void *data)
+{
+	struct hdmi_dev *dev = (struct hdmi_dev *)data;
+	struct hdmi_event *evt;
+
+	while (!kthread_should_stop()) {
+
+		spin_lock_irq(&dev->lock);
+		if (list_empty(&dev->hdmi_evt_list)) {
+			set_current_state(TASK_INTERRUPTIBLE);
+
+			if (kthread_should_stop())
+				set_current_state(TASK_RUNNING);
+
+			spin_unlock_irq(&dev->lock);
+			schedule();
+			continue;
+		}
+
+		evt = list_entry(dev->hdmi_evt_list.next, typeof(*evt), list);
+		list_del(&evt->list);
+		spin_unlock_irq(&dev->lock);
+		hdmi_dprintk(1, "Receive evt type %d\n", evt->type);
+
+		mutex_lock(&dev->hdmi_lock);
+		switch (evt->type) {
+			/* trigger gpio irq */
+			case HDMI_EV_PLUG:
+				hdmi_hotplug(dev);
+				break;
+			/* trigger from TV/CEC */
+			case HDMI_EV_CEC:
+				break;
+			default:
+				hdmi_dprintk(1, "Invalid event type %d\n", evt->type);
+				break;
+		}
+		mutex_unlock(&dev->hdmi_lock);
+	}
+
+	return 0;
+}
+
+/**
+ * hdmi_event_signal() - schedules the hdmi event thread
+ * @dev:	the struct hdmi_dev device
+ *
+ * This routine will tell hdmi-core to start decoding stored ir data.
+ */
+void hdmi_event_signal(struct hdmi_dev *dev, enum hdmi_event_type type)
+{
+	unsigned long flags;
+	struct hdmi_event *evt;
+
+	hdmi_dprintk(1, "hdmi event %d stored, dev %p\n", type, dev);
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if(HDMI_EV_PLUG == type) {
+		list_for_each_entry(evt, &dev->hdmi_evt_list, list) {
+			if (HDMI_EV_PLUG == evt->type) {
+				hdmi_dprintk(1, "pending hotplug event, ignore new ones\n");
+				spin_unlock_irqrestore(&dev->lock, flags);
+				return;
+			}
+		}
+	}
+	evt = list_entry(dev->free_evt_list.next, struct hdmi_event, list);
+	evt->type = type;
+	list_move_tail(&evt->list, &dev->hdmi_evt_list);
+	wake_up_process(dev->thread);
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+EXPORT_SYMBOL_GPL(hdmi_event_signal);
+
+/* ==================== sys fs part ===================== */
+#ifdef HDMI_DEBUG
+static ssize_t hdmi_show_reg(struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	int rc;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->get_reg)
+		rc = sprintf(buf, "%d\n", hd->ops->get_reg(hd));
+	else
+		rc = -ENXIO;
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_store_reg(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int rc = -ENXIO;
+	char *endp;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+	int power = simple_strtoul(buf, &endp, 0);
+	size_t size = endp - buf;
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->set_reg) {
+		hd->ops->set_reg(hd, power, power);
+		rc = count;
+	}
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_show_reg_addr(struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	int rc;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->get_reg)
+		rc = sprintf(buf, "%d\n", hd->ops->get_reg(hd));
+	else
+		rc = -ENXIO;
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_store_reg_addr(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int rc = -ENXIO;
+	char *endp;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+	int power = simple_strtoul(buf, &endp, 0);
+	size_t size = endp - buf;
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->set_reg) {
+		hd->ops->set_reg(hd, power, power);
+		rc = count;
+	}
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+#endif
+
+static ssize_t hdmi_show_ratio(struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	int rc;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ratio == RATIO_16V9)
+		rc = sprintf(buf, "%s\n", RATIO_16V9_STR);
+	else if (hd->ratio == RATIO_4V3)
+		rc = sprintf(buf, "%s\n", RATIO_4V3_STR);
+	else
+		rc = -ENXIO;
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_store_ratio(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int rc = -ENXIO;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+
+	mutex_lock(&hd->update_lock);
+	if (sysfs_streq(buf, RATIO_16V9_STR)) {
+		hd->ratio = RATIO_16V9;
+	} else if (sysfs_streq(buf, RATIO_4V3_STR)) {
+		hd->ratio = RATIO_4V3;
+	}
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_show_power(struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	int rc;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->get_power) {
+		hd->ops->get_power(hd);
+		rc = sprintf(buf, "%d\n", hd->ops->get_power(hd));
+	}
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_store_power(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int rc = -ENXIO;
+	char *endp;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+	int power = simple_strtoul(buf, &endp, 0);
+	size_t size = endp - buf;
+
+	if (isspace(*endp))
+		size++;
+	if (size != count)
+		return -EINVAL;
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->set_power) {
+		hdmi_dprintk(3, "hdmi: set power to %d\n", power);
+		hd->ops->set_power(hd, power);
+		rc = count;
+	}
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_show_enable(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	int rc = -ENXIO;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->get_enable)
+		rc = sprintf(buf, "%d\n", hd->ops->get_enable(hd));
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_store_enable(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int rc = -ENXIO;
+	char *endp;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+	int enable = simple_strtoul(buf, &endp, 0);
+	size_t size = endp - buf;
+
+	if (isspace(*endp))
+		size++;
+	if (size != count)
+		return -EINVAL;
+
+	mutex_lock(&hd->update_lock);
+	hd->enable = enable;
+	if (hd->ops && hd->ops->set_enable) {
+		hdmi_dprintk(3, "hdmi: set enable to %d\n", enable);
+		hd->ops->set_enable(hd, enable);
+		rc = count;
+	}
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static ssize_t hdmi_show_hotplug(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int rc = -ENXIO;
+	char *endp;
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+
+	mutex_lock(&hd->update_lock);
+	if (hd->ops && hd->ops->get_hpd) {
+		rc = sprintf(buf, "%d\n", ! hd->ops->get_hpd(hd));
+	}
+	mutex_unlock(&hd->update_lock);
+
+	return rc;
+}
+
+static struct class *hdmi_class;
+
+static void hdmi_dev_release(struct device *dev)
+{
+	struct hdmi_dev *hd = dev_get_drvdata(dev);
+	kfree(hd);
+}
+
+static struct device_attribute hdmi_dev_attributes[] = {
+	__ATTR(power, 0644, hdmi_show_power, hdmi_store_power),
+	__ATTR(ratio, 0644, hdmi_show_ratio, hdmi_store_ratio),
+#ifdef HDMI_DEBUG
+	__ATTR(reg_val, 0644, hdmi_show_reg, hdmi_store_reg),
+	__ATTR(reg_addr, 0644, hdmi_show_reg_addr, hdmi_store_reg_addr),
+#endif
+	__ATTR(enable, 0644, hdmi_show_enable, hdmi_store_enable),
+	__ATTR(hotplug, 0644, hdmi_show_hotplug, NULL),
+	__ATTR_NULL,
+};
+
+/**
+ * hdmi_dev_register - register a new object of hdmi_dev class.
+ * @name: the name of the new object(must be the same as the name of the
+ *   respective framebuffer device).
+ * @devdata: an optional pointer to be stored in the device. The
+ *   methods may retrieve it by using hdmi_get_data(hd).
+ * @ops: the hdmi operations structure.
+ *
+ * Creates and registers a new hdmi device. Returns either an ERR_PTR()
+ * or a pointer to the newly allocated device.
+ */
+int hdmi_dev_register(struct hdmi_dev *hd, struct device *parent,
+		void *devdata, struct hdmi_ops *ops, int fb_index)
+{
+	struct hdmi_event *evt_list;
+	int i;
+	int rc;
+
+	hd->dev = device_create(hdmi_class, parent,
+			MKDEV(HDMI_MAJOR, hdmidevno), hd, "hdmi%d", hdmidevno);
+	if (IS_ERR(hd->dev)) {
+		/* Not fatal */
+		hdmi_dprintk(1, "Unable to create device for hdmi %d; errno = %ld\n",
+			hdmidevno, PTR_ERR(hd->dev));
+		hd->dev = NULL;
+	}
+
+	spin_lock_init(&hd->lock);
+	mutex_init(&hd->hdmi_lock);
+	mutex_init(&hd->update_lock);
+
+	INIT_LIST_HEAD(&hd->hdmi_evt_list);
+	INIT_LIST_HEAD(&hd->free_evt_list);
+	hd->evt_ptr = evt_list =
+		kzalloc(MAX_HDMI_EVENT_SIZE * sizeof(struct hdmi_event), GFP_KERNEL);
+	for (i = 0; i < MAX_HDMI_EVENT_SIZE; i++) {
+		list_add(&evt_list->list, &hd->free_evt_list);
+		evt_list ++;
+	}
+
+	rc = hdmi_register_lcd(hd);
+	if (rc) {
+		device_unregister(hd->dev);
+		hdmi_dprintk(1, "Unable to create lcd device for hdmi\n");
+		goto dev_out;
+	}
+
+	hd->ops = ops;
+	hd->fb_info = registered_fb[fb_index];
+	if (! hd->fb_info) {
+		hdmi_dprintk(1, "Invalid associated fb\n");
+		goto dev_out;
+	}
+
+	/* Register CEC input device here */
+	/* Register audio Asoc device here */
+	/* Init hot plug uevent here */
+	/* Init HDCP here */
+
+	hd->thread = kthread_run(hdmi_event_thread, hd,
+				       "hdmi%d", hdmidevno++);
+
+	if (IS_ERR(hd->thread)) {
+		rc = PTR_ERR(hd->thread);
+		hdmi_dprintk(1, "Unable to create internal thread for hdmi\n");
+		goto lcd_out;
+	}
+
+	return 0;
+
+	kthread_stop(hd->thread);
+lcd_out:
+	lcd_device_unregister(hd->lcd);
+dev_out:
+	device_unregister(hd->dev);
+	hdmi_dprintk(1, "Fail to register hdmi dev\n");
+	return rc;
+}
+EXPORT_SYMBOL(hdmi_dev_register);
+
+/**
+ * hdmi_dev_unregister - unregisters a object of hdmi_dev class.
+ * @hd: the hdmi device object to be unregistered and freed.
+ *
+ * Unregisters a previously registered via hdmi_dev_register object.
+ */
+void hdmi_dev_unregister(struct hdmi_dev *hd)
+{
+	if (!hd)
+		return;
+
+	mutex_lock(&hd->hdmi_lock);
+	hd->ops = NULL;
+	mutex_unlock(&hd->hdmi_lock);
+	lcd_device_unregister(hd->lcd);
+
+	kthread_stop(hd->thread);
+	device_unregister(hd->dev);
+}
+EXPORT_SYMBOL(hdmi_dev_unregister);
+
+static void __exit hdmi_class_exit(void)
+{
+	class_destroy(hdmi_class);
+}
+
+static int __init hdmi_class_init(void)
+{
+	hdmi_dprintk(1, "create hdmi class...\n");
+	hdmi_class = class_create(THIS_MODULE, "hdmi");
+	if (IS_ERR(hdmi_class)) {
+		hdmi_dprintk(1, "Unable to create hdmi class; errno = %ld\n",
+				PTR_ERR(hdmi_class));
+		return PTR_ERR(hdmi_class);
+	}
+
+	hdmi_class->dev_attrs = hdmi_dev_attributes;
+	hdmi_dprintk(1, "create hdmi class ok\n");
+	return 0;
+}
+
+subsys_initcall(hdmi_class_init);
+module_exit(hdmi_class_exit);
+
+module_param_named(debug, hdmi_core_debug, int, 0644);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/modedb.c b/drivers/video/modedb.c
index cb175fe..93f5b6e 100644
--- a/drivers/video/modedb.c
+++ b/drivers/video/modedb.c
@@ -292,62 +292,63 @@ static const struct fb_videomode modedb[] = {
 };
 
 #ifdef CONFIG_FB_MODE_HELPERS
-const struct fb_videomode cea_modes[64] = {
+const struct fb_videomode cea_modes[65] = {
 	/* #1: 640x480p@59.94/60Hz */
 	[1] = {
 		NULL, 60, 640, 480, 39722, 48, 16, 33, 10, 96, 2, 0,
-		FB_VMODE_NONINTERLACED, 0,
+		FB_VMODE_NONINTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #3: 720x480p@59.94/60Hz */
 	[3] = {
 		NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0,
-		FB_VMODE_NONINTERLACED, 0,
+		FB_VMODE_NONINTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #5: 1920x1080i@59.94/60Hz */
 	[5] = {
 		NULL, 60, 1920, 1080, 13763, 148, 88, 15, 2, 44, 5,
 		FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
-		FB_VMODE_INTERLACED, 0,
+		FB_VMODE_INTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #7: 720(1440)x480iH@59.94/60Hz */
 	[7] = {
 		NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0,
-		FB_VMODE_INTERLACED, 0,
+		FB_VMODE_INTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #9: 720(1440)x240pH@59.94/60Hz */
 	[9] = {
 		NULL, 60, 1440, 240, 18554, 114, 38, 16, 4, 124, 3, 0,
-		FB_VMODE_NONINTERLACED, 0,
+		FB_VMODE_NONINTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #18: 720x576pH@50Hz */
 	[18] = {
 		NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0,
-		FB_VMODE_NONINTERLACED, 0,
+		FB_VMODE_NONINTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #19: 1280x720p@50Hz */
 	[19] = {
 		NULL, 50, 1280, 720, 13468, 220, 440, 20, 5, 40, 5,
 		FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
-		FB_VMODE_NONINTERLACED, 0,
+		FB_VMODE_NONINTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #20: 1920x1080i@50Hz */
 	[20] = {
 		NULL, 50, 1920, 1080, 13480, 148, 528, 15, 5, 528, 5,
 		FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
-		FB_VMODE_INTERLACED, 0,
+		FB_VMODE_INTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #32: 1920x1080p@23.98/24Hz */
 	[32] = {
 		NULL, 24, 1920, 1080, 13468, 148, 638, 36, 4, 44, 5,
 		FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
-		FB_VMODE_NONINTERLACED, 0,
+		FB_VMODE_NONINTERLACED, FB_MODE_IS_CEA,
 	},
 	/* #35: (2880)x480p4x@59.94/60Hz */
 	[35] = {
 		NULL, 60, 2880, 480, 9250, 240, 64, 30, 9, 248, 6, 0,
-		FB_VMODE_NONINTERLACED, 0,
+		FB_VMODE_NONINTERLACED, FB_MODE_IS_CEA,
 	},
 };
+EXPORT_SYMBOL(cea_modes);
 
 const struct fb_videomode vesa_modes[] = {
 	/* 0 640x350-85 VESA */
@@ -1119,6 +1120,24 @@ finished:
 }
 EXPORT_SYMBOL(fb_find_best_display);
 
+/**
+ * fb_find_cea_mode_index - find a cea video mode index
+ *
+ * RETURNS:
+ * cea mode index, 0 if none found
+ */
+const int fb_find_cea_mode_index(struct fb_videomode *m)
+{
+	int idx;
+
+	for (idx = 1; idx <= 64; idx ++) {
+		if (fb_mode_is_equal(&cea_modes[idx], m))
+			return idx;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(fb_find_cea_mode_index);
+
 EXPORT_SYMBOL(fb_videomode_to_var);
 EXPORT_SYMBOL(fb_var_to_videomode);
 EXPORT_SYMBOL(fb_mode_is_equal);
diff --git a/include/linux/fb.h b/include/linux/fb.h
index 6a82748..14672f8 100644
--- a/include/linux/fb.h
+++ b/include/linux/fb.h
@@ -1082,10 +1082,24 @@ extern void fb_bl_default_curve(struct fb_info *fb_info, u8 off, u8 min, u8 max)
 #define FB_MODE_IS_DETAILED	1
 #define FB_MODE_IS_STANDARD	2
 #define FB_MODE_IS_VESA		4
+#define FB_MODE_IS_CEA		5
 #define FB_MODE_IS_CALCULATED	8
 #define FB_MODE_IS_FIRST	16
 #define FB_MODE_IS_FROM_VAR     32
 
+typedef enum {
+	CEA_640x480p60 = 1,
+	CEA_720x480p60 = 3,
+	CEA_1920x1080i60 = 5,
+	CEA_720x480i60 = 7,
+	CEA_720x240p60 = 9,
+	CEA_720x576p50 = 18,
+	CEA_1280x720p50 = 19,
+	CEA_1920x1080i50 = 20,
+	CEA_1920x1080p24 = 32,
+	CEA_2880x480p60 =35,
+} cea_mode_id;
+
 extern int fbmon_dpms(const struct fb_info *fb_info);
 extern int fb_get_mode(int flags, u32 val, struct fb_var_screeninfo *var,
 		       struct fb_info *info);
@@ -1124,6 +1138,7 @@ extern void fb_videomode_to_modelist(const struct fb_videomode *modedb, int num,
 				     struct list_head *head);
 extern const struct fb_videomode *fb_find_best_display(const struct fb_monspecs *specs,
 						       struct list_head *head);
+extern const int fb_find_cea_mode_index(struct fb_videomode *m);
 
 /* drivers/video/fbcmap.c */
 extern int fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp);
@@ -1155,7 +1170,7 @@ struct fb_videomode {
 
 extern const char *fb_mode_option;
 extern const struct fb_videomode vesa_modes[];
-extern const struct fb_videomode cea_modes[64];
+extern const struct fb_videomode cea_modes[65];
 
 struct fb_modelist {
 	struct list_head list;
diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h
new file mode 100644
index 0000000..d8a3da9
--- /dev/null
+++ b/include/linux/hdmi.h
@@ -0,0 +1,164 @@
+/*
+ * HDMI Control Abstraction
+ *
+ */
+
+#ifndef _LINUX_HDMI_H
+#define _LINUX_HDMI_H
+
+#include <linux/device.h>
+#include <linux/mutex.h>
+//#include <linux/notifier.h>
+#include <linux/fb.h>
+#include <linux/lcd.h>
+
+/* Notes on locking:
+ *
+ * hdmi_dev->ops_lock is an internal backlight lock protecting the ops
+ * field and no code outside the core should need to touch it.
+ *
+ * Access to set_power() is serialised by the update_lock mutex since
+ * most drivers seem to need this and historically get it wrong.
+ *
+ * Most drivers don't need locking on their get_power() method.
+ * If yours does, you need to implement it in the driver. You can use the
+ * update_lock mutex if appropriate.
+ *
+ * Any other use of the locks below is probably wrong.
+ */
+
+struct hdmi_dev;
+struct fb_info;
+
+struct hdmi_properties {
+	/* source or sink */
+	u16 type;
+};
+
+struct hdmi_ops {
+	int (*init)(struct hdmi_dev *);
+	int (*get_hpd)(struct hdmi_dev *);
+	int (*enable)(struct hdmi_dev *, int);
+	/* Get the LCD panel power status (0: full on, 1..3: controller
+	   power on, flat panel power off, 4: full off), see FB_BLANK_XXX */
+	int (*get_power)(struct hdmi_dev *);
+	/* Enable or disable power to the LCD (0: on; 4: off, see FB_BLANK_XXX) */
+	int (*set_power)(struct hdmi_dev *, int power);
+	/* For debug */
+	int (*get_reg)(struct hdmi_dev *);
+	int (*set_reg)(struct hdmi_dev *, int offset, int v);
+	/* Get the current audio_cfg setting (0-max_audio_cfg) */
+	int (*get_audio_cfg)(struct hdmi_dev *);
+	/* Set LCD panel audio_cfg */
+        int (*set_audio_cfg)(struct hdmi_dev *, int audio_cfg);
+	int (*get_mode)(struct hdmi_dev *, struct fb_videomode *);
+	/* Set LCD panel mode (resolutions ...) */
+	int (*set_mode)(struct hdmi_dev *, cea_mode_id);
+	int (*get_enable)(struct hdmi_dev *);
+	int (*set_enable)(struct hdmi_dev *, int enable);
+	/* Check if given framebuffer device is the one LCD is bound to;
+	   return 0 if not, !=0 if it is. If NULL, hdmi always matches the fb. */
+	int (*check_mode)(struct hdmi_dev *, struct fb_videomode *);
+};
+
+enum hdmi_event_type {
+	/* trigger gpio irq */
+	HDMI_EV_PLUG,
+	HDMI_EV_UNPLUG,
+	/* trigger in gpio irq or from fb change */
+	HDMI_EV_VIDEO,
+	/* trigger in gpio irq or from Asoc change */
+	HDMI_EV_AUDIO,
+	/* trigger from CEC/TV */
+	HDMI_EV_CEC,
+};
+
+struct hdmi_event {
+	enum hdmi_event_type type;
+	struct list_head list;
+};
+
+#define RATIO_16V9_STR "16:9"
+#define RATIO_4V3_STR "4:3"
+#define RATIO_16V9 (0)
+#define RATIO_4V3 (1)
+
+struct hdmi_dev {
+	struct device		*dev;
+	struct hdmi_properties	props;
+	/* This protects the 'ops' field. If 'ops' is NULL, the driver that
+	   registered this device has been unloaded, and if class_get_devdata()
+	   points to something in the body of that driver, it is also invalid. */
+	struct mutex		hdmi_lock;
+	/* Serialise access to set_power method */
+	struct mutex		update_lock;
+	spinlock_t              lock;           /* lock for claim and bus ops */
+	struct list_head	hdmi_evt_list;
+	struct list_head	free_evt_list;
+	struct task_struct	*thread;
+	struct hdmi_event	*evt_ptr;
+	/* If this is NULL, the backing module is unloaded */
+	struct hdmi_ops *ops;
+	/* The framebuffer notifier block */
+	//struct notifier_block fb_notif;
+	struct lcd_device	*lcd;
+	struct fb_info		*fb_info;
+	cea_mode_id		mode_id;
+	//u32			deep_color;
+	u32			enable;
+	u32			pixel_rept;
+	u32			ratio;
+};
+
+struct hdmi_platform_data {
+	/* reset hdmi panel device. */
+	int (*reset)(struct hdmi_dev *ld);
+	/* on or off to hdmi panel. if 'enable' is 0 then
+	   hdmi power off and 1, hdmi power on. */
+	int (*power_on)(struct hdmi_dev *ld, int enable);
+
+	/* it indicates whether hdmi panel was enabled
+	   from bootloader or not. */
+	int hdmi_enabled;
+	/* video, audio, cec, hdcp */
+	unsigned int features;
+	/* it means delay for stable time when it becomes low to high
+	   or high to low that is dependent on whether reset gpio is
+	   low active or high active. */
+	unsigned int reset_delay;
+	/* stable time needing to become hdmi power on. */
+	unsigned int power_on_delay;
+	/* stable time needing to become hdmi power off. */
+	unsigned int power_off_delay;
+
+	/* it could be used for any purpose. */
+	void *pdata;
+};
+
+static inline void hdmi_set_power(struct hdmi_dev *ld, int power)
+{
+	mutex_lock(&ld->update_lock);
+	if (ld->ops && ld->ops->set_power)
+		ld->ops->set_power(ld, power);
+	mutex_unlock(&ld->update_lock);
+}
+
+extern int hdmi_dev_register(struct hdmi_dev *hdmi_dev,
+	struct device *parent, void *devdata, struct hdmi_ops *ops,
+	int fb_index);
+extern void hdmi_dev_unregister(struct hdmi_dev *ld);
+
+#define to_hdmi_dev(obj) container_of(obj, struct hdmi_dev, dev)
+
+static inline void * hdmi_get_data(struct hdmi_dev *ld_dev)
+{
+	return dev_get_drvdata(ld_dev->dev);
+}
+
+
+#endif
+
+int hdmi_get_freq (struct hdmi_dev *hd, u32 *need_freq);
+int hdmi_vender_info_frame (struct hdmi_dev *hd, char buf[]);
+int hdmi_avi_info_frame (struct hdmi_dev *hd, char buf[]);
+void hdmi_event_signal(struct hdmi_dev *dev, enum hdmi_event_type type);
diff --git a/include/linux/lcd.h b/include/linux/lcd.h
index 8877123..b09e0f9 100644
--- a/include/linux/lcd.h
+++ b/include/linux/lcd.h
@@ -48,6 +48,8 @@ struct lcd_ops {
         int (*set_contrast)(struct lcd_device *, int contrast);
 	/* Set LCD panel mode (resolutions ...) */
 	int (*set_mode)(struct lcd_device *, struct fb_videomode *);
+	/* Get LCD panel mode (resolutions ...) */
+	int (*get_mode)(struct lcd_device *, struct fb_videomode *);
 	/* Check if given framebuffer device is the one LCD is bound to;
 	   return 0 if not, !=0 if it is. If NULL, lcd always matches the fb. */
 	int (*check_fb)(struct lcd_device *, struct fb_info *);
diff --git a/include/linux/major.h b/include/linux/major.h
index 6a8ca98..e6e46e2 100644
--- a/include/linux/major.h
+++ b/include/linux/major.h
@@ -170,6 +170,7 @@
 #define IBM_FS3270_MAJOR	228
 
 #define VIOTAPE_MAJOR		230
+#define HDMI_MAJOR		231 
 
 #define BLOCK_EXT_MAJOR		259
 #define SCSI_OSD_MAJOR		260	/* open-osd's OSD scsi device */

[Index of Archives]     [Video for Linux]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Tourism]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux