/* * TFP410 DPI-to-DVI chip * * 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 <linux/gpio.h> #include <drm/drm_edid.h> #include <video/omap-panel-data.h> static const struct omap_video_timings tfp410_default_timings = { .x_res = 640, .y_res = 480, .pixel_clock = 23500, .hfp = 48, .hsw = 32, .hbp = 80, .vfp = 3, .vsw = 4, .vbp = 7, .vsync_level = OMAPDSS_SIG_ACTIVE_HIGH, .hsync_level = OMAPDSS_SIG_ACTIVE_HIGH, .data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE, .de_level = OMAPDSS_SIG_ACTIVE_HIGH, .sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES, }; struct panel_drv_data { struct omap_dss_device *dssdev; struct mutex lock; int pd_gpio; struct i2c_adapter *i2c_adapter; }; static int tfp410_power_on(struct omap_dss_device *dssdev) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); int r; if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) return 0; omapdss_dpi_set_timings(dssdev, &dssdev->panel.timings); omapdss_dpi_set_data_lines(dssdev, dssdev->phy.dpi.data_lines); r = omapdss_dpi_display_enable(dssdev); if (r) goto err0; if (gpio_is_valid(ddata->pd_gpio)) gpio_set_value_cansleep(ddata->pd_gpio, 1); return 0; err0: return r; } static void tfp410_power_off(struct omap_dss_device *dssdev) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) return; if (gpio_is_valid(ddata->pd_gpio)) gpio_set_value_cansleep(ddata->pd_gpio, 0); omapdss_dpi_display_disable(dssdev); } static int tfp410_probe(struct omap_dss_device *dssdev) { struct panel_drv_data *ddata; int r; int i2c_bus_num; ddata = devm_kzalloc(&dssdev->dev, sizeof(*ddata), GFP_KERNEL); if (!ddata) return -ENOMEM; dssdev->panel.timings = tfp410_default_timings; ddata->dssdev = dssdev; mutex_init(&ddata->lock); if (dssdev->data) { struct tfp410_platform_data *pdata = dssdev->data; ddata->pd_gpio = pdata->power_down_gpio; i2c_bus_num = pdata->i2c_bus_num; } else { ddata->pd_gpio = -1; i2c_bus_num = -1; } if (gpio_is_valid(ddata->pd_gpio)) { r = devm_gpio_request_one(&dssdev->dev, ddata->pd_gpio, GPIOF_OUT_INIT_LOW, "tfp410 pd"); if (r) { dev_err(&dssdev->dev, "Failed to request PD GPIO %d\n", ddata->pd_gpio); return r; } } if (i2c_bus_num != -1) { struct i2c_adapter *adapter; adapter = i2c_get_adapter(i2c_bus_num); if (!adapter) { dev_err(&dssdev->dev, "Failed to get I2C adapter, bus %d\n", i2c_bus_num); return -EPROBE_DEFER; } ddata->i2c_adapter = adapter; } dev_set_drvdata(&dssdev->dev, ddata); return 0; } static void __exit tfp410_remove(struct omap_dss_device *dssdev) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); mutex_lock(&ddata->lock); if (ddata->i2c_adapter) i2c_put_adapter(ddata->i2c_adapter); dev_set_drvdata(&dssdev->dev, NULL); mutex_unlock(&ddata->lock); } static int tfp410_enable(struct omap_dss_device *dssdev) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); int r; mutex_lock(&ddata->lock); r = tfp410_power_on(dssdev); if (r == 0) dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; mutex_unlock(&ddata->lock); return r; } static void tfp410_disable(struct omap_dss_device *dssdev) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); mutex_lock(&ddata->lock); tfp410_power_off(dssdev); dssdev->state = OMAP_DSS_DISPLAY_DISABLED; mutex_unlock(&ddata->lock); } static void tfp410_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); omapdss_dpi_set_timings(dssdev, timings); dssdev->panel.timings = *timings; mutex_unlock(&ddata->lock); } static void tfp410_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 tfp410_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 tfp410_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 tfp410_read_edid(struct omap_dss_device *dssdev, u8 *edid, int len) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); int r, l, bytes_read; mutex_lock(&ddata->lock); if (!ddata->i2c_adapter) { r = -ENODEV; goto err; } l = min(EDID_LENGTH, len); r = tfp410_ddc_read(ddata->i2c_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 = tfp410_ddc_read(ddata->i2c_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 tfp410_detect(struct omap_dss_device *dssdev) { struct panel_drv_data *ddata = dev_get_drvdata(&dssdev->dev); unsigned char out; int r; mutex_lock(&ddata->lock); if (!ddata->i2c_adapter) goto out; r = tfp410_ddc_read(ddata->i2c_adapter, &out, 1, 0); mutex_unlock(&ddata->lock); return r == 0; out: mutex_unlock(&ddata->lock); return true; } static struct omap_dss_driver tfp410_driver = { .probe = tfp410_probe, .remove = __exit_p(tfp410_remove), .enable = tfp410_enable, .disable = tfp410_disable, .set_timings = tfp410_set_timings, .get_timings = tfp410_get_timings, .check_timings = tfp410_check_timings, .read_edid = tfp410_read_edid, .detect = tfp410_detect, .driver = { .name = "tfp410", .owner = THIS_MODULE, }, }; static int __init tfp410_init(void) { return omap_dss_register_driver(&tfp410_driver); } static void __exit tfp410_exit(void) { omap_dss_unregister_driver(&tfp410_driver); } module_init(tfp410_init); module_exit(tfp410_exit); MODULE_LICENSE("GPL");