/* * DVI output support * * Copyright (C) 2011 Texas Instruments Inc * Author: Tomi Valkeinen <tomi.valkeinen@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/module.h> #include <linux/slab.h> #include <video/omapdss.h> #include <linux/i2c.h> #include <drm/drm_edid.h> #include <video/omap-panel-dvi.h> static const struct omap_video_timings panel_dvi_default_timings = { .x_res = 640, .y_res = 480, .pixel_clock = 23500, .hfp = 48, .hsw = 32, .hbp = 80, .vfp = 3, .vsw = 4, .vbp = 7, }; struct panel_drv_data { struct omap_dss_device *dssdev; struct mutex lock; }; static inline struct panel_dvi_platform_data *get_pdata(const struct omap_dss_device *dssdev) { return dssdev->data; } static int panel_dvi_power_on(struct omap_dss_device *dssdev) { struct panel_dvi_platform_data *pdata = get_pdata(dssdev); int r; if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) return 0; r = omapdss_dpi_display_enable(dssdev); if (r) goto err0; if (pdata->platform_enable) { r = pdata->platform_enable(dssdev); if (r) goto err1; } return 0; err1: omapdss_dpi_display_disable(dssdev); err0: return r; } static void panel_dvi_power_off(struct omap_dss_device *dssdev) { struct panel_dvi_platform_data *pdata = get_pdata(dssdev); if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) return; if (pdata->platform_disable) pdata->platform_disable(dssdev); omapdss_dpi_display_disable(dssdev); } static int panel_dvi_probe(struct omap_dss_device *dssdev) { struct panel_drv_data *ddata; ddata = kzalloc(sizeof(*ddata), GFP_KERNEL); if (!ddata) return -ENOMEM; dssdev->panel.timings = panel_dvi_default_timings; dssdev->panel.config = OMAP_DSS_LCD_TFT; ddata->dssdev = dssdev; mutex_init(&ddata->lock); dev_set_drvdata(&dssdev->dev, ddata); return 0; } static void __exit panel_dvi_remove(struct omap_dss_device *dssdev) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); mutex_lock(&ddata->lock); dev_set_drvdata(&dssdev->dev, NULL); mutex_unlock(&ddata->lock); kfree(ddata); } static int panel_dvi_enable(struct omap_dss_device *dssdev) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); int r; mutex_lock(&ddata->lock); r = panel_dvi_power_on(dssdev); if (r == 0) dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; mutex_unlock(&ddata->lock); return r; } static void panel_dvi_disable(struct omap_dss_device *dssdev) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); mutex_lock(&ddata->lock); panel_dvi_power_off(dssdev); dssdev->state = OMAP_DSS_DISPLAY_DISABLED; mutex_unlock(&ddata->lock); } static int panel_dvi_suspend(struct omap_dss_device *dssdev) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); mutex_lock(&ddata->lock); panel_dvi_power_off(dssdev); dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED; mutex_unlock(&ddata->lock); return 0; } static int panel_dvi_resume(struct omap_dss_device *dssdev) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); int r; mutex_lock(&ddata->lock); r = panel_dvi_power_on(dssdev); if (r == 0) dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; mutex_unlock(&ddata->lock); return r; } static void panel_dvi_set_timings(struct omap_dss_device *dssdev, struct omap_video_timings *timings) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); mutex_lock(&ddata->lock); dpi_set_timings(dssdev, timings); mutex_unlock(&ddata->lock); } static void panel_dvi_get_timings(struct omap_dss_device *dssdev, struct omap_video_timings *timings) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); mutex_lock(&ddata->lock); *timings = dssdev->panel.timings; mutex_unlock(&ddata->lock); } static int panel_dvi_check_timings(struct omap_dss_device *dssdev, struct omap_video_timings *timings) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); int r; mutex_lock(&ddata->lock); r = dpi_check_timings(dssdev, timings); mutex_unlock(&ddata->lock); return r; } static int panel_dvi_ddc_read(struct i2c_adapter *adapter, unsigned char *buf, u16 count, u8 offset) { int r, retries; for (retries = 3; retries > 0; retries--) { struct i2c_msg msgs[] = { { .addr = DDC_ADDR, .flags = 0, .len = 1, .buf = &offset, }, { .addr = DDC_ADDR, .flags = I2C_M_RD, .len = count, .buf = buf, } }; r = i2c_transfer(adapter, msgs, 2); if (r == 2) return 0; if (r != -EAGAIN) break; } return r < 0 ? r : -EIO; } static int panel_dvi_read_edid(struct omap_dss_device *dssdev, u8 *edid, int len) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); struct panel_dvi_platform_data *pdata = get_pdata(dssdev); struct i2c_adapter *adapter; int r, l, bytes_read; mutex_lock(&ddata->lock); if (pdata->i2c_bus_num == 0) { r = -ENODEV; goto err; } adapter = i2c_get_adapter(pdata->i2c_bus_num); if (!adapter) { dev_err(&dssdev->dev, "Failed to get I2C adapter, bus %d\n", pdata->i2c_bus_num); r = -EINVAL; goto err; } l = min(EDID_LENGTH, len); r = panel_dvi_ddc_read(adapter, edid, l, 0); if (r) goto err; bytes_read = l; /* if there are extensions, read second block */ if (len > EDID_LENGTH && edid[0x7e] > 0) { l = min(EDID_LENGTH, len - EDID_LENGTH); r = panel_dvi_ddc_read(adapter, edid + EDID_LENGTH, l, EDID_LENGTH); if (r) goto err; bytes_read += l; } mutex_unlock(&ddata->lock); return bytes_read; err: mutex_unlock(&ddata->lock); return r; } static bool panel_dvi_detect(struct omap_dss_device *dssdev) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); struct panel_dvi_platform_data *pdata = get_pdata(dssdev); struct i2c_adapter *adapter; unsigned char out; int r; mutex_lock(&ddata->lock); if (pdata->i2c_bus_num == 0) goto out; adapter = i2c_get_adapter(pdata->i2c_bus_num); if (!adapter) goto out; r = panel_dvi_ddc_read(adapter, &out, 1, 0); mutex_unlock(&ddata->lock); return r == 0; out: mutex_unlock(&ddata->lock); return true; } static struct omap_dss_driver panel_dvi_driver = { .probe = panel_dvi_probe, .remove = __exit_p(panel_dvi_remove), .enable = panel_dvi_enable, .disable = panel_dvi_disable, .suspend = panel_dvi_suspend, .resume = panel_dvi_resume, .set_timings = panel_dvi_set_timings, .get_timings = panel_dvi_get_timings, .check_timings = panel_dvi_check_timings, .read_edid = panel_dvi_read_edid, .detect = panel_dvi_detect, .driver = { .name = "dvi", .owner = THIS_MODULE, }, }; static int __init panel_dvi_init(void) { return omap_dss_register_driver(&panel_dvi_driver); } static void __exit panel_dvi_exit(void) { omap_dss_unregister_driver(&panel_dvi_driver); } module_init(panel_dvi_init); module_exit(panel_dvi_exit); MODULE_LICENSE("GPL");