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