1 /* 2 * TPD12S015 HDMI ESD protection & level shifter chip 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/completion.h> 13 #include <linux/delay.h> 14 #include <linux/module.h> 15 #include <linux/slab.h> 16 #include <linux/platform_device.h> 17 #include <linux/gpio/consumer.h> 18 19 #include <video/omapdss.h> 20 #include <video/omap-panel-data.h> 21 22 struct panel_drv_data { 23 struct omap_dss_device dssdev; 24 struct omap_dss_device *in; 25 26 struct gpio_desc *ct_cp_hpd_gpio; 27 struct gpio_desc *ls_oe_gpio; 28 struct gpio_desc *hpd_gpio; 29 30 struct omap_video_timings timings; 31 }; 32 33 #define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev) 34 35 static int tpd_connect(struct omap_dss_device *dssdev, 36 struct omap_dss_device *dst) 37 { 38 struct panel_drv_data *ddata = to_panel_data(dssdev); 39 struct omap_dss_device *in = ddata->in; 40 int r; 41 42 r = in->ops.hdmi->connect(in, dssdev); 43 if (r) 44 return r; 45 46 dst->src = dssdev; 47 dssdev->dst = dst; 48 49 if (ddata->ct_cp_hpd_gpio) { 50 gpiod_set_value_cansleep(ddata->ct_cp_hpd_gpio, 1); 51 /* DC-DC converter needs at max 300us to get to 90% of 5V */ 52 udelay(300); 53 } 54 55 return 0; 56 } 57 58 static void tpd_disconnect(struct omap_dss_device *dssdev, 59 struct omap_dss_device *dst) 60 { 61 struct panel_drv_data *ddata = to_panel_data(dssdev); 62 struct omap_dss_device *in = ddata->in; 63 64 WARN_ON(dst != dssdev->dst); 65 66 if (dst != dssdev->dst) 67 return; 68 69 gpiod_set_value_cansleep(ddata->ct_cp_hpd_gpio, 0); 70 71 dst->src = NULL; 72 dssdev->dst = NULL; 73 74 in->ops.hdmi->disconnect(in, &ddata->dssdev); 75 } 76 77 static int tpd_enable(struct omap_dss_device *dssdev) 78 { 79 struct panel_drv_data *ddata = to_panel_data(dssdev); 80 struct omap_dss_device *in = ddata->in; 81 int r; 82 83 if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) 84 return 0; 85 86 in->ops.hdmi->set_timings(in, &ddata->timings); 87 88 r = in->ops.hdmi->enable(in); 89 if (r) 90 return r; 91 92 dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; 93 94 return r; 95 } 96 97 static void tpd_disable(struct omap_dss_device *dssdev) 98 { 99 struct panel_drv_data *ddata = to_panel_data(dssdev); 100 struct omap_dss_device *in = ddata->in; 101 102 if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) 103 return; 104 105 in->ops.hdmi->disable(in); 106 107 dssdev->state = OMAP_DSS_DISPLAY_DISABLED; 108 } 109 110 static void tpd_set_timings(struct omap_dss_device *dssdev, 111 struct omap_video_timings *timings) 112 { 113 struct panel_drv_data *ddata = to_panel_data(dssdev); 114 struct omap_dss_device *in = ddata->in; 115 116 ddata->timings = *timings; 117 dssdev->panel.timings = *timings; 118 119 in->ops.hdmi->set_timings(in, timings); 120 } 121 122 static void tpd_get_timings(struct omap_dss_device *dssdev, 123 struct omap_video_timings *timings) 124 { 125 struct panel_drv_data *ddata = to_panel_data(dssdev); 126 127 *timings = ddata->timings; 128 } 129 130 static int tpd_check_timings(struct omap_dss_device *dssdev, 131 struct omap_video_timings *timings) 132 { 133 struct panel_drv_data *ddata = to_panel_data(dssdev); 134 struct omap_dss_device *in = ddata->in; 135 int r; 136 137 r = in->ops.hdmi->check_timings(in, timings); 138 139 return r; 140 } 141 142 static int tpd_read_edid(struct omap_dss_device *dssdev, 143 u8 *edid, int len) 144 { 145 struct panel_drv_data *ddata = to_panel_data(dssdev); 146 struct omap_dss_device *in = ddata->in; 147 int r; 148 149 if (!gpiod_get_value_cansleep(ddata->hpd_gpio)) 150 return -ENODEV; 151 152 gpiod_set_value_cansleep(ddata->ls_oe_gpio, 1); 153 154 r = in->ops.hdmi->read_edid(in, edid, len); 155 156 gpiod_set_value_cansleep(ddata->ls_oe_gpio, 0); 157 158 return r; 159 } 160 161 static bool tpd_detect(struct omap_dss_device *dssdev) 162 { 163 struct panel_drv_data *ddata = to_panel_data(dssdev); 164 165 return gpiod_get_value_cansleep(ddata->hpd_gpio); 166 } 167 168 static int tpd_set_infoframe(struct omap_dss_device *dssdev, 169 const struct hdmi_avi_infoframe *avi) 170 { 171 struct panel_drv_data *ddata = to_panel_data(dssdev); 172 struct omap_dss_device *in = ddata->in; 173 174 return in->ops.hdmi->set_infoframe(in, avi); 175 } 176 177 static int tpd_set_hdmi_mode(struct omap_dss_device *dssdev, 178 bool hdmi_mode) 179 { 180 struct panel_drv_data *ddata = to_panel_data(dssdev); 181 struct omap_dss_device *in = ddata->in; 182 183 return in->ops.hdmi->set_hdmi_mode(in, hdmi_mode); 184 } 185 186 static const struct omapdss_hdmi_ops tpd_hdmi_ops = { 187 .connect = tpd_connect, 188 .disconnect = tpd_disconnect, 189 190 .enable = tpd_enable, 191 .disable = tpd_disable, 192 193 .check_timings = tpd_check_timings, 194 .set_timings = tpd_set_timings, 195 .get_timings = tpd_get_timings, 196 197 .read_edid = tpd_read_edid, 198 .detect = tpd_detect, 199 .set_infoframe = tpd_set_infoframe, 200 .set_hdmi_mode = tpd_set_hdmi_mode, 201 }; 202 203 static int tpd_probe_of(struct platform_device *pdev) 204 { 205 struct panel_drv_data *ddata = platform_get_drvdata(pdev); 206 struct device_node *node = pdev->dev.of_node; 207 struct omap_dss_device *in; 208 209 in = omapdss_of_find_source_for_first_ep(node); 210 if (IS_ERR(in)) { 211 dev_err(&pdev->dev, "failed to find video source\n"); 212 return PTR_ERR(in); 213 } 214 215 ddata->in = in; 216 217 return 0; 218 } 219 220 static int tpd_probe(struct platform_device *pdev) 221 { 222 struct omap_dss_device *in, *dssdev; 223 struct panel_drv_data *ddata; 224 int r; 225 struct gpio_desc *gpio; 226 227 ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); 228 if (!ddata) 229 return -ENOMEM; 230 231 platform_set_drvdata(pdev, ddata); 232 233 if (pdev->dev.of_node) { 234 r = tpd_probe_of(pdev); 235 if (r) 236 return r; 237 } else { 238 return -ENODEV; 239 } 240 241 242 gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 0, 243 GPIOD_OUT_LOW); 244 if (IS_ERR(gpio)) 245 goto err_gpio; 246 247 ddata->ct_cp_hpd_gpio = gpio; 248 249 gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 1, 250 GPIOD_OUT_LOW); 251 if (IS_ERR(gpio)) 252 goto err_gpio; 253 254 ddata->ls_oe_gpio = gpio; 255 256 gpio = devm_gpiod_get_index(&pdev->dev, NULL, 2, 257 GPIOD_IN); 258 if (IS_ERR(gpio)) 259 goto err_gpio; 260 261 ddata->hpd_gpio = gpio; 262 263 dssdev = &ddata->dssdev; 264 dssdev->ops.hdmi = &tpd_hdmi_ops; 265 dssdev->dev = &pdev->dev; 266 dssdev->type = OMAP_DISPLAY_TYPE_HDMI; 267 dssdev->output_type = OMAP_DISPLAY_TYPE_HDMI; 268 dssdev->owner = THIS_MODULE; 269 dssdev->port_num = 1; 270 271 in = ddata->in; 272 273 r = omapdss_register_output(dssdev); 274 if (r) { 275 dev_err(&pdev->dev, "Failed to register output\n"); 276 goto err_reg; 277 } 278 279 return 0; 280 err_reg: 281 err_gpio: 282 omap_dss_put_device(ddata->in); 283 return r; 284 } 285 286 static int __exit tpd_remove(struct platform_device *pdev) 287 { 288 struct panel_drv_data *ddata = platform_get_drvdata(pdev); 289 struct omap_dss_device *dssdev = &ddata->dssdev; 290 struct omap_dss_device *in = ddata->in; 291 292 omapdss_unregister_output(&ddata->dssdev); 293 294 WARN_ON(omapdss_device_is_enabled(dssdev)); 295 if (omapdss_device_is_enabled(dssdev)) 296 tpd_disable(dssdev); 297 298 WARN_ON(omapdss_device_is_connected(dssdev)); 299 if (omapdss_device_is_connected(dssdev)) 300 tpd_disconnect(dssdev, dssdev->dst); 301 302 omap_dss_put_device(in); 303 304 return 0; 305 } 306 307 static const struct of_device_id tpd_of_match[] = { 308 { .compatible = "omapdss,ti,tpd12s015", }, 309 {}, 310 }; 311 312 MODULE_DEVICE_TABLE(of, tpd_of_match); 313 314 static struct platform_driver tpd_driver = { 315 .probe = tpd_probe, 316 .remove = __exit_p(tpd_remove), 317 .driver = { 318 .name = "tpd12s015", 319 .of_match_table = tpd_of_match, 320 .suppress_bind_attrs = true, 321 }, 322 }; 323 324 module_platform_driver(tpd_driver); 325 326 MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); 327 MODULE_DESCRIPTION("TPD12S015 driver"); 328 MODULE_LICENSE("GPL"); 329