343 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Generic DVI Connector driver4 *5 * Copyright (C) 2013 Texas Instruments6 * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>7 */8 9#include <linux/i2c.h>10#include <linux/module.h>11#include <linux/platform_device.h>12#include <linux/slab.h>13 14#include <drm/drm_edid.h>15 16#include <video/omapfb_dss.h>17 18static const struct omap_video_timings dvic_default_timings = {19 .x_res = 640,20 .y_res = 480,21 22 .pixelclock = 23500000,23 24 .hfp = 48,25 .hsw = 32,26 .hbp = 80,27 28 .vfp = 3,29 .vsw = 4,30 .vbp = 7,31 32 .vsync_level = OMAPDSS_SIG_ACTIVE_HIGH,33 .hsync_level = OMAPDSS_SIG_ACTIVE_HIGH,34 .data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE,35 .de_level = OMAPDSS_SIG_ACTIVE_HIGH,36 .sync_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE,37};38 39struct panel_drv_data {40 struct omap_dss_device dssdev;41 struct omap_dss_device *in;42 43 struct omap_video_timings timings;44 45 struct i2c_adapter *i2c_adapter;46};47 48#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)49 50static int dvic_connect(struct omap_dss_device *dssdev)51{52 struct panel_drv_data *ddata = to_panel_data(dssdev);53 struct omap_dss_device *in = ddata->in;54 55 if (omapdss_device_is_connected(dssdev))56 return 0;57 58 return in->ops.dvi->connect(in, dssdev);59}60 61static void dvic_disconnect(struct omap_dss_device *dssdev)62{63 struct panel_drv_data *ddata = to_panel_data(dssdev);64 struct omap_dss_device *in = ddata->in;65 66 if (!omapdss_device_is_connected(dssdev))67 return;68 69 in->ops.dvi->disconnect(in, dssdev);70}71 72static int dvic_enable(struct omap_dss_device *dssdev)73{74 struct panel_drv_data *ddata = to_panel_data(dssdev);75 struct omap_dss_device *in = ddata->in;76 int r;77 78 if (!omapdss_device_is_connected(dssdev))79 return -ENODEV;80 81 if (omapdss_device_is_enabled(dssdev))82 return 0;83 84 in->ops.dvi->set_timings(in, &ddata->timings);85 86 r = in->ops.dvi->enable(in);87 if (r)88 return r;89 90 dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;91 92 return 0;93}94 95static void dvic_disable(struct omap_dss_device *dssdev)96{97 struct panel_drv_data *ddata = to_panel_data(dssdev);98 struct omap_dss_device *in = ddata->in;99 100 if (!omapdss_device_is_enabled(dssdev))101 return;102 103 in->ops.dvi->disable(in);104 105 dssdev->state = OMAP_DSS_DISPLAY_DISABLED;106}107 108static void dvic_set_timings(struct omap_dss_device *dssdev,109 struct omap_video_timings *timings)110{111 struct panel_drv_data *ddata = to_panel_data(dssdev);112 struct omap_dss_device *in = ddata->in;113 114 ddata->timings = *timings;115 dssdev->panel.timings = *timings;116 117 in->ops.dvi->set_timings(in, timings);118}119 120static void dvic_get_timings(struct omap_dss_device *dssdev,121 struct omap_video_timings *timings)122{123 struct panel_drv_data *ddata = to_panel_data(dssdev);124 125 *timings = ddata->timings;126}127 128static int dvic_check_timings(struct omap_dss_device *dssdev,129 struct omap_video_timings *timings)130{131 struct panel_drv_data *ddata = to_panel_data(dssdev);132 struct omap_dss_device *in = ddata->in;133 134 return in->ops.dvi->check_timings(in, timings);135}136 137static int dvic_ddc_read(struct i2c_adapter *adapter,138 unsigned char *buf, u16 count, u8 offset)139{140 int r, retries;141 142 for (retries = 3; retries > 0; retries--) {143 struct i2c_msg msgs[] = {144 {145 .addr = DDC_ADDR,146 .flags = 0,147 .len = 1,148 .buf = &offset,149 }, {150 .addr = DDC_ADDR,151 .flags = I2C_M_RD,152 .len = count,153 .buf = buf,154 }155 };156 157 r = i2c_transfer(adapter, msgs, 2);158 if (r == 2)159 return 0;160 161 if (r != -EAGAIN)162 break;163 }164 165 return r < 0 ? r : -EIO;166}167 168static int dvic_read_edid(struct omap_dss_device *dssdev,169 u8 *edid, int len)170{171 struct panel_drv_data *ddata = to_panel_data(dssdev);172 int r, l, bytes_read;173 174 if (!ddata->i2c_adapter)175 return -ENODEV;176 177 l = min(EDID_LENGTH, len);178 r = dvic_ddc_read(ddata->i2c_adapter, edid, l, 0);179 if (r)180 return r;181 182 bytes_read = l;183 184 /* if there are extensions, read second block */185 if (len > EDID_LENGTH && edid[0x7e] > 0) {186 l = min(EDID_LENGTH, len - EDID_LENGTH);187 188 r = dvic_ddc_read(ddata->i2c_adapter, edid + EDID_LENGTH,189 l, EDID_LENGTH);190 if (r)191 return r;192 193 bytes_read += l;194 }195 196 return bytes_read;197}198 199static bool dvic_detect(struct omap_dss_device *dssdev)200{201 struct panel_drv_data *ddata = to_panel_data(dssdev);202 unsigned char out;203 int r;204 205 if (!ddata->i2c_adapter)206 return true;207 208 r = dvic_ddc_read(ddata->i2c_adapter, &out, 1, 0);209 210 return r == 0;211}212 213static struct omap_dss_driver dvic_driver = {214 .connect = dvic_connect,215 .disconnect = dvic_disconnect,216 217 .enable = dvic_enable,218 .disable = dvic_disable,219 220 .set_timings = dvic_set_timings,221 .get_timings = dvic_get_timings,222 .check_timings = dvic_check_timings,223 224 .get_resolution = omapdss_default_get_resolution,225 226 .read_edid = dvic_read_edid,227 .detect = dvic_detect,228};229 230static int dvic_probe_of(struct platform_device *pdev)231{232 struct panel_drv_data *ddata = platform_get_drvdata(pdev);233 struct device_node *node = pdev->dev.of_node;234 struct omap_dss_device *in;235 struct device_node *adapter_node;236 struct i2c_adapter *adapter;237 238 in = omapdss_of_find_source_for_first_ep(node);239 if (IS_ERR(in)) {240 dev_err(&pdev->dev, "failed to find video source\n");241 return PTR_ERR(in);242 }243 244 ddata->in = in;245 246 adapter_node = of_parse_phandle(node, "ddc-i2c-bus", 0);247 if (adapter_node) {248 adapter = of_get_i2c_adapter_by_node(adapter_node);249 of_node_put(adapter_node);250 if (adapter == NULL) {251 dev_err(&pdev->dev, "failed to parse ddc-i2c-bus\n");252 omap_dss_put_device(ddata->in);253 return -EPROBE_DEFER;254 }255 256 ddata->i2c_adapter = adapter;257 }258 259 return 0;260}261 262static int dvic_probe(struct platform_device *pdev)263{264 struct panel_drv_data *ddata;265 struct omap_dss_device *dssdev;266 int r;267 268 if (!pdev->dev.of_node)269 return -ENODEV;270 271 ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);272 if (!ddata)273 return -ENOMEM;274 275 platform_set_drvdata(pdev, ddata);276 277 r = dvic_probe_of(pdev);278 if (r)279 return r;280 281 ddata->timings = dvic_default_timings;282 283 dssdev = &ddata->dssdev;284 dssdev->driver = &dvic_driver;285 dssdev->dev = &pdev->dev;286 dssdev->type = OMAP_DISPLAY_TYPE_DVI;287 dssdev->owner = THIS_MODULE;288 dssdev->panel.timings = dvic_default_timings;289 290 r = omapdss_register_display(dssdev);291 if (r) {292 dev_err(&pdev->dev, "Failed to register panel\n");293 goto err_reg;294 }295 296 return 0;297 298err_reg:299 omap_dss_put_device(ddata->in);300 301 i2c_put_adapter(ddata->i2c_adapter);302 303 return r;304}305 306static void dvic_remove(struct platform_device *pdev)307{308 struct panel_drv_data *ddata = platform_get_drvdata(pdev);309 struct omap_dss_device *dssdev = &ddata->dssdev;310 struct omap_dss_device *in = ddata->in;311 312 omapdss_unregister_display(&ddata->dssdev);313 314 dvic_disable(dssdev);315 dvic_disconnect(dssdev);316 317 omap_dss_put_device(in);318 319 i2c_put_adapter(ddata->i2c_adapter);320}321 322static const struct of_device_id dvic_of_match[] = {323 { .compatible = "omapdss,dvi-connector", },324 {},325};326 327MODULE_DEVICE_TABLE(of, dvic_of_match);328 329static struct platform_driver dvi_connector_driver = {330 .probe = dvic_probe,331 .remove = dvic_remove,332 .driver = {333 .name = "connector-dvi",334 .of_match_table = dvic_of_match,335 },336};337 338module_platform_driver(dvi_connector_driver);339 340MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");341MODULE_DESCRIPTION("Generic DVI Connector driver");342MODULE_LICENSE("GPL");343