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