1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Analog TV Connector driver 4 * 5 * Copyright (C) 2013 Texas Instruments 6 * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> 7 */ 8 9 #include <linux/slab.h> 10 #include <linux/module.h> 11 #include <linux/platform_device.h> 12 #include <linux/of.h> 13 14 #include <video/omapfb_dss.h> 15 #include <video/omap-panel-data.h> 16 17 struct panel_drv_data { 18 struct omap_dss_device dssdev; 19 struct omap_dss_device *in; 20 21 struct device *dev; 22 23 struct omap_video_timings timings; 24 25 bool invert_polarity; 26 }; 27 28 static const struct omap_video_timings tvc_pal_timings = { 29 .x_res = 720, 30 .y_res = 574, 31 .pixelclock = 13500000, 32 .hsw = 64, 33 .hfp = 12, 34 .hbp = 68, 35 .vsw = 5, 36 .vfp = 5, 37 .vbp = 41, 38 39 .interlace = true, 40 }; 41 42 static const struct of_device_id tvc_of_match[]; 43 44 #define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev) 45 46 static int tvc_connect(struct omap_dss_device *dssdev) 47 { 48 struct panel_drv_data *ddata = to_panel_data(dssdev); 49 struct omap_dss_device *in = ddata->in; 50 51 dev_dbg(ddata->dev, "connect\n"); 52 53 if (omapdss_device_is_connected(dssdev)) 54 return 0; 55 56 return in->ops.atv->connect(in, dssdev); 57 } 58 59 static void tvc_disconnect(struct omap_dss_device *dssdev) 60 { 61 struct panel_drv_data *ddata = to_panel_data(dssdev); 62 struct omap_dss_device *in = ddata->in; 63 64 dev_dbg(ddata->dev, "disconnect\n"); 65 66 if (!omapdss_device_is_connected(dssdev)) 67 return; 68 69 in->ops.atv->disconnect(in, dssdev); 70 } 71 72 static int tvc_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 dev_dbg(ddata->dev, "enable\n"); 79 80 if (!omapdss_device_is_connected(dssdev)) 81 return -ENODEV; 82 83 if (omapdss_device_is_enabled(dssdev)) 84 return 0; 85 86 in->ops.atv->set_timings(in, &ddata->timings); 87 88 if (!ddata->dev->of_node) { 89 in->ops.atv->set_type(in, OMAP_DSS_VENC_TYPE_COMPOSITE); 90 91 in->ops.atv->invert_vid_out_polarity(in, 92 ddata->invert_polarity); 93 } 94 95 r = in->ops.atv->enable(in); 96 if (r) 97 return r; 98 99 dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; 100 101 return r; 102 } 103 104 static void tvc_disable(struct omap_dss_device *dssdev) 105 { 106 struct panel_drv_data *ddata = to_panel_data(dssdev); 107 struct omap_dss_device *in = ddata->in; 108 109 dev_dbg(ddata->dev, "disable\n"); 110 111 if (!omapdss_device_is_enabled(dssdev)) 112 return; 113 114 in->ops.atv->disable(in); 115 116 dssdev->state = OMAP_DSS_DISPLAY_DISABLED; 117 } 118 119 static void tvc_set_timings(struct omap_dss_device *dssdev, 120 struct omap_video_timings *timings) 121 { 122 struct panel_drv_data *ddata = to_panel_data(dssdev); 123 struct omap_dss_device *in = ddata->in; 124 125 ddata->timings = *timings; 126 dssdev->panel.timings = *timings; 127 128 in->ops.atv->set_timings(in, timings); 129 } 130 131 static void tvc_get_timings(struct omap_dss_device *dssdev, 132 struct omap_video_timings *timings) 133 { 134 struct panel_drv_data *ddata = to_panel_data(dssdev); 135 136 *timings = ddata->timings; 137 } 138 139 static int tvc_check_timings(struct omap_dss_device *dssdev, 140 struct omap_video_timings *timings) 141 { 142 struct panel_drv_data *ddata = to_panel_data(dssdev); 143 struct omap_dss_device *in = ddata->in; 144 145 return in->ops.atv->check_timings(in, timings); 146 } 147 148 static u32 tvc_get_wss(struct omap_dss_device *dssdev) 149 { 150 struct panel_drv_data *ddata = to_panel_data(dssdev); 151 struct omap_dss_device *in = ddata->in; 152 153 return in->ops.atv->get_wss(in); 154 } 155 156 static int tvc_set_wss(struct omap_dss_device *dssdev, u32 wss) 157 { 158 struct panel_drv_data *ddata = to_panel_data(dssdev); 159 struct omap_dss_device *in = ddata->in; 160 161 return in->ops.atv->set_wss(in, wss); 162 } 163 164 static struct omap_dss_driver tvc_driver = { 165 .connect = tvc_connect, 166 .disconnect = tvc_disconnect, 167 168 .enable = tvc_enable, 169 .disable = tvc_disable, 170 171 .set_timings = tvc_set_timings, 172 .get_timings = tvc_get_timings, 173 .check_timings = tvc_check_timings, 174 175 .get_resolution = omapdss_default_get_resolution, 176 177 .get_wss = tvc_get_wss, 178 .set_wss = tvc_set_wss, 179 }; 180 181 static int tvc_probe_pdata(struct platform_device *pdev) 182 { 183 struct panel_drv_data *ddata = platform_get_drvdata(pdev); 184 struct connector_atv_platform_data *pdata; 185 struct omap_dss_device *in, *dssdev; 186 187 pdata = dev_get_platdata(&pdev->dev); 188 189 in = omap_dss_find_output(pdata->source); 190 if (in == NULL) { 191 dev_err(&pdev->dev, "Failed to find video source\n"); 192 return -EPROBE_DEFER; 193 } 194 195 ddata->in = in; 196 197 ddata->invert_polarity = pdata->invert_polarity; 198 199 dssdev = &ddata->dssdev; 200 dssdev->name = pdata->name; 201 202 return 0; 203 } 204 205 static int tvc_probe_of(struct platform_device *pdev) 206 { 207 struct panel_drv_data *ddata = platform_get_drvdata(pdev); 208 struct device_node *node = pdev->dev.of_node; 209 struct omap_dss_device *in; 210 211 in = omapdss_of_find_source_for_first_ep(node); 212 if (IS_ERR(in)) { 213 dev_err(&pdev->dev, "failed to find video source\n"); 214 return PTR_ERR(in); 215 } 216 217 ddata->in = in; 218 219 return 0; 220 } 221 222 static int tvc_probe(struct platform_device *pdev) 223 { 224 struct panel_drv_data *ddata; 225 struct omap_dss_device *dssdev; 226 int r; 227 228 ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); 229 if (!ddata) 230 return -ENOMEM; 231 232 platform_set_drvdata(pdev, ddata); 233 ddata->dev = &pdev->dev; 234 235 if (dev_get_platdata(&pdev->dev)) { 236 r = tvc_probe_pdata(pdev); 237 if (r) 238 return r; 239 } else if (pdev->dev.of_node) { 240 r = tvc_probe_of(pdev); 241 if (r) 242 return r; 243 } else { 244 return -ENODEV; 245 } 246 247 ddata->timings = tvc_pal_timings; 248 249 dssdev = &ddata->dssdev; 250 dssdev->driver = &tvc_driver; 251 dssdev->dev = &pdev->dev; 252 dssdev->type = OMAP_DISPLAY_TYPE_VENC; 253 dssdev->owner = THIS_MODULE; 254 dssdev->panel.timings = tvc_pal_timings; 255 256 r = omapdss_register_display(dssdev); 257 if (r) { 258 dev_err(&pdev->dev, "Failed to register panel\n"); 259 goto err_reg; 260 } 261 262 return 0; 263 err_reg: 264 omap_dss_put_device(ddata->in); 265 return r; 266 } 267 268 static int __exit tvc_remove(struct platform_device *pdev) 269 { 270 struct panel_drv_data *ddata = platform_get_drvdata(pdev); 271 struct omap_dss_device *dssdev = &ddata->dssdev; 272 struct omap_dss_device *in = ddata->in; 273 274 omapdss_unregister_display(&ddata->dssdev); 275 276 tvc_disable(dssdev); 277 tvc_disconnect(dssdev); 278 279 omap_dss_put_device(in); 280 281 return 0; 282 } 283 284 static const struct of_device_id tvc_of_match[] = { 285 { .compatible = "omapdss,svideo-connector", }, 286 { .compatible = "omapdss,composite-video-connector", }, 287 {}, 288 }; 289 290 MODULE_DEVICE_TABLE(of, tvc_of_match); 291 292 static struct platform_driver tvc_connector_driver = { 293 .probe = tvc_probe, 294 .remove = __exit_p(tvc_remove), 295 .driver = { 296 .name = "connector-analog-tv", 297 .of_match_table = tvc_of_match, 298 .suppress_bind_attrs = true, 299 }, 300 }; 301 302 module_platform_driver(tvc_connector_driver); 303 304 MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); 305 MODULE_DESCRIPTION("Analog TV Connector driver"); 306 MODULE_LICENSE("GPL"); 307