connector-analog-tv.c (6667B)
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 17struct 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 28static 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 42static const struct of_device_id tvc_of_match[]; 43 44#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev) 45 46static 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 59static 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 72static 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 104static 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 119static 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 131static 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 139static 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 148static 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 156static 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 164static 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 181static 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 205static 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 222static 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; 263err_reg: 264 omap_dss_put_device(ddata->in); 265 return r; 266} 267 268static 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 284static const struct of_device_id tvc_of_match[] = { 285 { .compatible = "omapdss,svideo-connector", }, 286 { .compatible = "omapdss,composite-video-connector", }, 287 {}, 288}; 289 290MODULE_DEVICE_TABLE(of, tvc_of_match); 291 292static 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 302module_platform_driver(tvc_connector_driver); 303 304MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); 305MODULE_DESCRIPTION("Analog TV Connector driver"); 306MODULE_LICENSE("GPL");