diff options
Diffstat (limited to 'drivers/video/omap2/dss/hdmi_panel.c')
-rw-r--r-- | drivers/video/omap2/dss/hdmi_panel.c | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/drivers/video/omap2/dss/hdmi_panel.c b/drivers/video/omap2/dss/hdmi_panel.c new file mode 100644 index 0000000..0fa5dea --- /dev/null +++ b/drivers/video/omap2/dss/hdmi_panel.c @@ -0,0 +1,339 @@ +/* + * hdmi_panel.c + * + * HDMI library support functions for TI OMAP4 processors. + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Mythri P k <mythripk@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <video/omapdss.h> +#include <linux/switch.h> + +#include "dss.h" + +#include <video/hdmi_ti_4xxx_ip.h> + +static struct { + struct mutex hdmi_lock; + struct switch_dev hpd_switch; +} hdmi; + +static ssize_t hdmi_deepcolor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int r; + r = omapdss_hdmi_get_deepcolor(); + return snprintf(buf, PAGE_SIZE, "%d\n", r); +} + +static ssize_t hdmi_deepcolor_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long deep_color; + int r = kstrtoul(buf, 0, &deep_color); + if (r || deep_color > 2) + return -EINVAL; + omapdss_hdmi_set_deepcolor(deep_color); + return size; +} + +static DEVICE_ATTR(deepcolor, S_IRUGO | S_IWUSR, hdmi_deepcolor_show, + hdmi_deepcolor_store); + +static int hdmi_panel_probe(struct omap_dss_device *dssdev) +{ + DSSDBG("ENTER hdmi_panel_probe\n"); + + dssdev->panel.config = OMAP_DSS_LCD_TFT | + OMAP_DSS_LCD_IVS | OMAP_DSS_LCD_IHS; + + /* + * Initialize the timings to 640 * 480 + * This is only for framebuffer update not for TV timing setting + * Setting TV timing will be done only on enable + */ + dssdev->panel.timings.x_res = 640; + dssdev->panel.timings.y_res = 480; + + /* sysfs entry to provide user space control to set deepcolor mode */ + if (device_create_file(&dssdev->dev, &dev_attr_deepcolor)) + DSSERR("failed to create sysfs file\n"); + + DSSDBG("hdmi_panel_probe x_res= %d y_res = %d\n", + dssdev->panel.timings.x_res, + dssdev->panel.timings.y_res); + return 0; +} + +static void hdmi_panel_remove(struct omap_dss_device *dssdev) +{ + device_remove_file(&dssdev->dev, &dev_attr_deepcolor); +} + +static int hdmi_panel_enable(struct omap_dss_device *dssdev) +{ + int r = 0; + DSSDBG("ENTER hdmi_panel_enable\n"); + + mutex_lock(&hdmi.hdmi_lock); + + if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) { + r = -EINVAL; + goto err; + } + + r = omapdss_hdmi_display_enable(dssdev); + if (r) { + DSSERR("failed to power on\n"); + goto err; + } + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; +err: + mutex_unlock(&hdmi.hdmi_lock); + + return r; +} + +static void hdmi_panel_disable(struct omap_dss_device *dssdev) +{ + mutex_lock(&hdmi.hdmi_lock); + + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) + omapdss_hdmi_display_disable(dssdev); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; + + mutex_unlock(&hdmi.hdmi_lock); +} + +static int hdmi_panel_suspend(struct omap_dss_device *dssdev) +{ + int r = 0; + + mutex_lock(&hdmi.hdmi_lock); + + if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) { + r = -EINVAL; + goto err; + } + + dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED; + + hdmi_panel_hpd_handler(0); + + omapdss_hdmi_display_disable(dssdev); +err: + mutex_unlock(&hdmi.hdmi_lock); + + return r; +} + +static int hdmi_panel_resume(struct omap_dss_device *dssdev) +{ + int r = 0; + + mutex_lock(&hdmi.hdmi_lock); + + if (dssdev->state != OMAP_DSS_DISPLAY_SUSPENDED) { + r = -EINVAL; + goto err; + } + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +err: + mutex_unlock(&hdmi.hdmi_lock); + + hdmi_panel_hpd_handler(hdmi_get_current_hpd()); + + return r; +} + +enum { + HPD_STATE_OFF, + HPD_STATE_START, + HPD_STATE_EDID_TRYLAST = HPD_STATE_START + 5, +}; + +static struct hpd_worker_data { + struct delayed_work dwork; + atomic_t state; +} hpd_work; +static struct workqueue_struct *my_workq; + +static void hdmi_hotplug_detect_worker(struct work_struct *work) +{ + struct hpd_worker_data *d = container_of(work, typeof(*d), dwork.work); + struct omap_dss_device *dssdev = NULL; + int state = atomic_read(&d->state); + + int match(struct omap_dss_device *dssdev, void *arg) + { + return sysfs_streq(dssdev->name , "hdmi"); + } + dssdev = omap_dss_find_device(NULL, match); + + pr_err("in hpd work %d, state=%d\n", state, dssdev->state); + if (dssdev == NULL) + return; + + mutex_lock(&hdmi.hdmi_lock); + if (state == HPD_STATE_OFF) { + switch_set_state(&hdmi.hpd_switch, 0); + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) { + mutex_unlock(&hdmi.hdmi_lock); + dssdev->driver->disable(dssdev); + mutex_lock(&hdmi.hdmi_lock); + } + goto done; + } else { + if (state == HPD_STATE_START) { + mutex_unlock(&hdmi.hdmi_lock); + dssdev->driver->enable(dssdev); + mutex_lock(&hdmi.hdmi_lock); + } else if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE || + hdmi.hpd_switch.state) { + /* powered down after enable - skip EDID read */ + goto done; + } else if (hdmi_read_edid(&dssdev->panel.timings)) { + /* get monspecs from edid */ + hdmi_get_monspecs(&dssdev->panel.monspecs); + pr_info("panel size %d by %d\n", + dssdev->panel.monspecs.max_x, + dssdev->panel.monspecs.max_y); + dssdev->panel.width_in_um = + dssdev->panel.monspecs.max_x * 10000; + dssdev->panel.height_in_um = + dssdev->panel.monspecs.max_y * 10000; + switch_set_state(&hdmi.hpd_switch, 1); + goto done; + } else if (state == HPD_STATE_EDID_TRYLAST){ + pr_info("Failed to read EDID after %d times. Giving up.", state - HPD_STATE_START); + goto done; + } + if (atomic_add_unless(&d->state, 1, HPD_STATE_OFF)) + queue_delayed_work(my_workq, &d->dwork, msecs_to_jiffies(60)); + } +done: + mutex_unlock(&hdmi.hdmi_lock); +} + +int hdmi_panel_hpd_handler(int hpd) +{ + __cancel_delayed_work(&hpd_work.dwork); + atomic_set(&hpd_work.state, hpd ? HPD_STATE_START : HPD_STATE_OFF); + queue_delayed_work(my_workq, &hpd_work.dwork, msecs_to_jiffies(hpd ? 40 : 30)); + return 0; +} + +static void hdmi_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + mutex_lock(&hdmi.hdmi_lock); + + *timings = dssdev->panel.timings; + + mutex_unlock(&hdmi.hdmi_lock); +} + +static void hdmi_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + DSSDBG("hdmi_set_timings\n"); + + mutex_lock(&hdmi.hdmi_lock); + + dssdev->panel.timings = *timings; + + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) + omapdss_hdmi_display_set_timing(dssdev); + + mutex_unlock(&hdmi.hdmi_lock); +} + +static int hdmi_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + int r = 0; + + DSSDBG("hdmi_check_timings\n"); + + mutex_lock(&hdmi.hdmi_lock); + + r = omapdss_hdmi_display_check_timing(dssdev, timings); + if (r) { + DSSERR("Timing cannot be applied\n"); + goto err; + } +err: + mutex_unlock(&hdmi.hdmi_lock); + return r; +} + +static int hdmi_get_modedb(struct omap_dss_device *dssdev, + struct fb_videomode *modedb, int modedb_len) +{ + struct fb_monspecs *specs = &dssdev->panel.monspecs; + if (specs->modedb_len < modedb_len) + modedb_len = specs->modedb_len; + memcpy(modedb, specs->modedb, sizeof(*modedb) * modedb_len); + return modedb_len; +} + +static struct omap_dss_driver hdmi_driver = { + .probe = hdmi_panel_probe, + .remove = hdmi_panel_remove, + .enable = hdmi_panel_enable, + .disable = hdmi_panel_disable, + .suspend = hdmi_panel_suspend, + .resume = hdmi_panel_resume, + .get_timings = hdmi_get_timings, + .set_timings = hdmi_set_timings, + .check_timings = hdmi_check_timings, + .get_modedb = hdmi_get_modedb, + .set_mode = omapdss_hdmi_display_set_mode, + .driver = { + .name = "hdmi_panel", + .owner = THIS_MODULE, + }, +}; + +int hdmi_panel_init(void) +{ + mutex_init(&hdmi.hdmi_lock); + hdmi.hpd_switch.name = "hdmi"; + switch_dev_register(&hdmi.hpd_switch); + + my_workq = create_singlethread_workqueue("hdmi_hotplug"); + INIT_DELAYED_WORK(&hpd_work.dwork, hdmi_hotplug_detect_worker); + omap_dss_register_driver(&hdmi_driver); + + return 0; +} + +void hdmi_panel_exit(void) +{ + destroy_workqueue(my_workq); + omap_dss_unregister_driver(&hdmi_driver); + + switch_dev_unregister(&hdmi.hpd_switch); +} |