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/mod_devicetable.h> 16 #include <linux/slab.h> 17 #include <linux/platform_device.h> 18 #include <linux/gpio/consumer.h> 19 20 #include <video/omapfb_dss.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 *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 gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 0, 242 GPIOD_OUT_LOW); 243 if (IS_ERR(gpio)) { 244 r = PTR_ERR(gpio); 245 goto err_gpio; 246 } 247 248 ddata->ct_cp_hpd_gpio = gpio; 249 250 gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 1, 251 GPIOD_OUT_LOW); 252 if (IS_ERR(gpio)) { 253 r = PTR_ERR(gpio); 254 goto err_gpio; 255 } 256 257 ddata->ls_oe_gpio = gpio; 258 259 gpio = devm_gpiod_get_index(&pdev->dev, NULL, 2, 260 GPIOD_IN); 261 if (IS_ERR(gpio)) { 262 r = PTR_ERR(gpio); 263 goto err_gpio; 264 } 265 266 ddata->hpd_gpio = gpio; 267 268 dssdev = &ddata->dssdev; 269 dssdev->ops.hdmi = &tpd_hdmi_ops; 270 dssdev->dev = &pdev->dev; 271 dssdev->type = OMAP_DISPLAY_TYPE_HDMI; 272 dssdev->output_type = OMAP_DISPLAY_TYPE_HDMI; 273 dssdev->owner = THIS_MODULE; 274 dssdev->port_num = 1; 275 276 r = omapdss_register_output(dssdev); 277 if (r) { 278 dev_err(&pdev->dev, "Failed to register output\n"); 279 goto err_reg; 280 } 281 282 return 0; 283 err_reg: 284 err_gpio: 285 omap_dss_put_device(ddata->in); 286 return r; 287 } 288 289 static int __exit tpd_remove(struct platform_device *pdev) 290 { 291 struct panel_drv_data *ddata = platform_get_drvdata(pdev); 292 struct omap_dss_device *dssdev = &ddata->dssdev; 293 struct omap_dss_device *in = ddata->in; 294 295 omapdss_unregister_output(&ddata->dssdev); 296 297 WARN_ON(omapdss_device_is_enabled(dssdev)); 298 if (omapdss_device_is_enabled(dssdev)) 299 tpd_disable(dssdev); 300 301 WARN_ON(omapdss_device_is_connected(dssdev)); 302 if (omapdss_device_is_connected(dssdev)) 303 tpd_disconnect(dssdev, dssdev->dst); 304 305 omap_dss_put_device(in); 306 307 return 0; 308 } 309 310 static const struct of_device_id tpd_of_match[] = { 311 { .compatible = "omapdss,ti,tpd12s015", }, 312 {}, 313 }; 314 315 MODULE_DEVICE_TABLE(of, tpd_of_match); 316 317 static struct platform_driver tpd_driver = { 318 .probe = tpd_probe, 319 .remove = __exit_p(tpd_remove), 320 .driver = { 321 .name = "tpd12s015", 322 .of_match_table = tpd_of_match, 323 .suppress_bind_attrs = true, 324 }, 325 }; 326 327 module_platform_driver(tpd_driver); 328 329 MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); 330 MODULE_DESCRIPTION("TPD12S015 driver"); 331 MODULE_LICENSE("GPL"); 332