linux/drivers/gpu/drm/bridge/display-connector.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
   4 */
   5
   6#include <linux/gpio/consumer.h>
   7#include <linux/i2c.h>
   8#include <linux/interrupt.h>
   9#include <linux/module.h>
  10#include <linux/mutex.h>
  11#include <linux/of.h>
  12#include <linux/of_device.h>
  13#include <linux/platform_device.h>
  14#include <linux/regulator/consumer.h>
  15
  16#include <drm/drm_bridge.h>
  17#include <drm/drm_edid.h>
  18
  19struct display_connector {
  20        struct drm_bridge       bridge;
  21
  22        struct gpio_desc        *hpd_gpio;
  23        int                     hpd_irq;
  24
  25        struct regulator        *dp_pwr;
  26};
  27
  28static inline struct display_connector *
  29to_display_connector(struct drm_bridge *bridge)
  30{
  31        return container_of(bridge, struct display_connector, bridge);
  32}
  33
  34static int display_connector_attach(struct drm_bridge *bridge,
  35                                    enum drm_bridge_attach_flags flags)
  36{
  37        return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL;
  38}
  39
  40static enum drm_connector_status
  41display_connector_detect(struct drm_bridge *bridge)
  42{
  43        struct display_connector *conn = to_display_connector(bridge);
  44
  45        if (conn->hpd_gpio) {
  46                if (gpiod_get_value_cansleep(conn->hpd_gpio))
  47                        return connector_status_connected;
  48                else
  49                        return connector_status_disconnected;
  50        }
  51
  52        if (conn->bridge.ddc && drm_probe_ddc(conn->bridge.ddc))
  53                return connector_status_connected;
  54
  55        switch (conn->bridge.type) {
  56        case DRM_MODE_CONNECTOR_DVIA:
  57        case DRM_MODE_CONNECTOR_DVID:
  58        case DRM_MODE_CONNECTOR_DVII:
  59        case DRM_MODE_CONNECTOR_HDMIA:
  60        case DRM_MODE_CONNECTOR_HDMIB:
  61                /*
  62                 * For DVI and HDMI connectors a DDC probe failure indicates
  63                 * that no cable is connected.
  64                 */
  65                return connector_status_disconnected;
  66
  67        case DRM_MODE_CONNECTOR_Composite:
  68        case DRM_MODE_CONNECTOR_SVIDEO:
  69        case DRM_MODE_CONNECTOR_VGA:
  70        default:
  71                /*
  72                 * Composite and S-Video connectors have no other detection
  73                 * mean than the HPD GPIO. For VGA connectors, even if we have
  74                 * an I2C bus, we can't assume that the cable is disconnected
  75                 * if drm_probe_ddc fails, as some cables don't wire the DDC
  76                 * pins.
  77                 */
  78                return connector_status_unknown;
  79        }
  80}
  81
  82static struct edid *display_connector_get_edid(struct drm_bridge *bridge,
  83                                               struct drm_connector *connector)
  84{
  85        struct display_connector *conn = to_display_connector(bridge);
  86
  87        return drm_get_edid(connector, conn->bridge.ddc);
  88}
  89
  90static const struct drm_bridge_funcs display_connector_bridge_funcs = {
  91        .attach = display_connector_attach,
  92        .detect = display_connector_detect,
  93        .get_edid = display_connector_get_edid,
  94};
  95
  96static irqreturn_t display_connector_hpd_irq(int irq, void *arg)
  97{
  98        struct display_connector *conn = arg;
  99        struct drm_bridge *bridge = &conn->bridge;
 100
 101        drm_bridge_hpd_notify(bridge, display_connector_detect(bridge));
 102
 103        return IRQ_HANDLED;
 104}
 105
 106static int display_connector_probe(struct platform_device *pdev)
 107{
 108        struct display_connector *conn;
 109        unsigned int type;
 110        const char *label;
 111        int ret;
 112
 113        conn = devm_kzalloc(&pdev->dev, sizeof(*conn), GFP_KERNEL);
 114        if (!conn)
 115                return -ENOMEM;
 116
 117        platform_set_drvdata(pdev, conn);
 118
 119        type = (uintptr_t)of_device_get_match_data(&pdev->dev);
 120
 121        /* Get the exact connector type. */
 122        switch (type) {
 123        case DRM_MODE_CONNECTOR_DVII: {
 124                bool analog, digital;
 125
 126                analog = of_property_read_bool(pdev->dev.of_node, "analog");
 127                digital = of_property_read_bool(pdev->dev.of_node, "digital");
 128                if (analog && !digital) {
 129                        conn->bridge.type = DRM_MODE_CONNECTOR_DVIA;
 130                } else if (!analog && digital) {
 131                        conn->bridge.type = DRM_MODE_CONNECTOR_DVID;
 132                } else if (analog && digital) {
 133                        conn->bridge.type = DRM_MODE_CONNECTOR_DVII;
 134                } else {
 135                        dev_err(&pdev->dev, "DVI connector with no type\n");
 136                        return -EINVAL;
 137                }
 138                break;
 139        }
 140
 141        case DRM_MODE_CONNECTOR_HDMIA: {
 142                const char *hdmi_type;
 143
 144                ret = of_property_read_string(pdev->dev.of_node, "type",
 145                                              &hdmi_type);
 146                if (ret < 0) {
 147                        dev_err(&pdev->dev, "HDMI connector with no type\n");
 148                        return -EINVAL;
 149                }
 150
 151                if (!strcmp(hdmi_type, "a") || !strcmp(hdmi_type, "c") ||
 152                    !strcmp(hdmi_type, "d") || !strcmp(hdmi_type, "e")) {
 153                        conn->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
 154                } else if (!strcmp(hdmi_type, "b")) {
 155                        conn->bridge.type = DRM_MODE_CONNECTOR_HDMIB;
 156                } else {
 157                        dev_err(&pdev->dev,
 158                                "Unsupported HDMI connector type '%s'\n",
 159                                hdmi_type);
 160                        return -EINVAL;
 161                }
 162
 163                break;
 164        }
 165
 166        default:
 167                conn->bridge.type = type;
 168                break;
 169        }
 170
 171        /* All the supported connector types support interlaced modes. */
 172        conn->bridge.interlace_allowed = true;
 173
 174        /* Get the optional connector label. */
 175        of_property_read_string(pdev->dev.of_node, "label", &label);
 176
 177        /*
 178         * Get the HPD GPIO for DVI, HDMI and DP connectors. If the GPIO can provide
 179         * edge interrupts, register an interrupt handler.
 180         */
 181        if (type == DRM_MODE_CONNECTOR_DVII ||
 182            type == DRM_MODE_CONNECTOR_HDMIA ||
 183            type == DRM_MODE_CONNECTOR_DisplayPort) {
 184                conn->hpd_gpio = devm_gpiod_get_optional(&pdev->dev, "hpd",
 185                                                         GPIOD_IN);
 186                if (IS_ERR(conn->hpd_gpio)) {
 187                        if (PTR_ERR(conn->hpd_gpio) != -EPROBE_DEFER)
 188                                dev_err(&pdev->dev,
 189                                        "Unable to retrieve HPD GPIO\n");
 190                        return PTR_ERR(conn->hpd_gpio);
 191                }
 192
 193                conn->hpd_irq = gpiod_to_irq(conn->hpd_gpio);
 194        } else {
 195                conn->hpd_irq = -EINVAL;
 196        }
 197
 198        if (conn->hpd_irq >= 0) {
 199                ret = devm_request_threaded_irq(&pdev->dev, conn->hpd_irq,
 200                                                NULL, display_connector_hpd_irq,
 201                                                IRQF_TRIGGER_RISING |
 202                                                IRQF_TRIGGER_FALLING |
 203                                                IRQF_ONESHOT,
 204                                                "HPD", conn);
 205                if (ret) {
 206                        dev_info(&pdev->dev,
 207                                 "Failed to request HPD edge interrupt, falling back to polling\n");
 208                        conn->hpd_irq = -EINVAL;
 209                }
 210        }
 211
 212        /* Retrieve the DDC I2C adapter for DVI, HDMI and VGA connectors. */
 213        if (type == DRM_MODE_CONNECTOR_DVII ||
 214            type == DRM_MODE_CONNECTOR_HDMIA ||
 215            type == DRM_MODE_CONNECTOR_VGA) {
 216                struct device_node *phandle;
 217
 218                phandle = of_parse_phandle(pdev->dev.of_node, "ddc-i2c-bus", 0);
 219                if (phandle) {
 220                        conn->bridge.ddc = of_get_i2c_adapter_by_node(phandle);
 221                        of_node_put(phandle);
 222                        if (!conn->bridge.ddc)
 223                                return -EPROBE_DEFER;
 224                } else {
 225                        dev_dbg(&pdev->dev,
 226                                "No I2C bus specified, disabling EDID readout\n");
 227                }
 228        }
 229
 230        /* Get the DP PWR for DP connector. */
 231        if (type == DRM_MODE_CONNECTOR_DisplayPort) {
 232                int ret;
 233
 234                conn->dp_pwr = devm_regulator_get_optional(&pdev->dev, "dp-pwr");
 235
 236                if (IS_ERR(conn->dp_pwr)) {
 237                        ret = PTR_ERR(conn->dp_pwr);
 238
 239                        switch (ret) {
 240                        case -ENODEV:
 241                                conn->dp_pwr = NULL;
 242                                break;
 243
 244                        case -EPROBE_DEFER:
 245                                return -EPROBE_DEFER;
 246
 247                        default:
 248                                dev_err(&pdev->dev, "failed to get DP PWR regulator: %d\n", ret);
 249                                return ret;
 250                        }
 251                }
 252
 253                if (conn->dp_pwr) {
 254                        ret = regulator_enable(conn->dp_pwr);
 255                        if (ret) {
 256                                dev_err(&pdev->dev, "failed to enable DP PWR regulator: %d\n", ret);
 257                                return ret;
 258                        }
 259                }
 260        }
 261
 262        conn->bridge.funcs = &display_connector_bridge_funcs;
 263        conn->bridge.of_node = pdev->dev.of_node;
 264
 265        if (conn->bridge.ddc)
 266                conn->bridge.ops |= DRM_BRIDGE_OP_EDID
 267                                 |  DRM_BRIDGE_OP_DETECT;
 268        if (conn->hpd_gpio)
 269                conn->bridge.ops |= DRM_BRIDGE_OP_DETECT;
 270        if (conn->hpd_irq >= 0)
 271                conn->bridge.ops |= DRM_BRIDGE_OP_HPD;
 272
 273        dev_dbg(&pdev->dev,
 274                "Found %s display connector '%s' %s DDC bus and %s HPD GPIO (ops 0x%x)\n",
 275                drm_get_connector_type_name(conn->bridge.type),
 276                label ? label : "<unlabelled>",
 277                conn->bridge.ddc ? "with" : "without",
 278                conn->hpd_gpio ? "with" : "without",
 279                conn->bridge.ops);
 280
 281        drm_bridge_add(&conn->bridge);
 282
 283        return 0;
 284}
 285
 286static int display_connector_remove(struct platform_device *pdev)
 287{
 288        struct display_connector *conn = platform_get_drvdata(pdev);
 289
 290        if (conn->dp_pwr)
 291                regulator_disable(conn->dp_pwr);
 292
 293        drm_bridge_remove(&conn->bridge);
 294
 295        if (!IS_ERR(conn->bridge.ddc))
 296                i2c_put_adapter(conn->bridge.ddc);
 297
 298        return 0;
 299}
 300
 301static const struct of_device_id display_connector_match[] = {
 302        {
 303                .compatible = "composite-video-connector",
 304                .data = (void *)DRM_MODE_CONNECTOR_Composite,
 305        }, {
 306                .compatible = "dvi-connector",
 307                .data = (void *)DRM_MODE_CONNECTOR_DVII,
 308        }, {
 309                .compatible = "hdmi-connector",
 310                .data = (void *)DRM_MODE_CONNECTOR_HDMIA,
 311        }, {
 312                .compatible = "svideo-connector",
 313                .data = (void *)DRM_MODE_CONNECTOR_SVIDEO,
 314        }, {
 315                .compatible = "vga-connector",
 316                .data = (void *)DRM_MODE_CONNECTOR_VGA,
 317        }, {
 318                .compatible = "dp-connector",
 319                .data = (void *)DRM_MODE_CONNECTOR_DisplayPort,
 320        },
 321        {},
 322};
 323MODULE_DEVICE_TABLE(of, display_connector_match);
 324
 325static struct platform_driver display_connector_driver = {
 326        .probe  = display_connector_probe,
 327        .remove = display_connector_remove,
 328        .driver         = {
 329                .name           = "display-connector",
 330                .of_match_table = display_connector_match,
 331        },
 332};
 333module_platform_driver(display_connector_driver);
 334
 335MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
 336MODULE_DESCRIPTION("Display connector driver");
 337MODULE_LICENSE("GPL");
 338