linux/drivers/gpu/drm/tegra/output.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (C) 2012 Avionic Design GmbH
   4 * Copyright (C) 2012 NVIDIA CORPORATION.  All rights reserved.
   5 */
   6
   7#include <drm/drm_atomic_helper.h>
   8#include <drm/drm_of.h>
   9#include <drm/drm_panel.h>
  10#include <drm/drm_simple_kms_helper.h>
  11
  12#include "drm.h"
  13#include "dc.h"
  14
  15#include <media/cec-notifier.h>
  16
  17int tegra_output_connector_get_modes(struct drm_connector *connector)
  18{
  19        struct tegra_output *output = connector_to_output(connector);
  20        struct edid *edid = NULL;
  21        int err = 0;
  22
  23        /*
  24         * If the panel provides one or more modes, use them exclusively and
  25         * ignore any other means of obtaining a mode.
  26         */
  27        if (output->panel) {
  28                err = drm_panel_get_modes(output->panel, connector);
  29                if (err > 0)
  30                        return err;
  31        }
  32
  33        if (output->edid)
  34                edid = kmemdup(output->edid, sizeof(*edid), GFP_KERNEL);
  35        else if (output->ddc)
  36                edid = drm_get_edid(connector, output->ddc);
  37
  38        cec_notifier_set_phys_addr_from_edid(output->cec, edid);
  39        drm_connector_update_edid_property(connector, edid);
  40
  41        if (edid) {
  42                err = drm_add_edid_modes(connector, edid);
  43                kfree(edid);
  44        }
  45
  46        return err;
  47}
  48
  49enum drm_connector_status
  50tegra_output_connector_detect(struct drm_connector *connector, bool force)
  51{
  52        struct tegra_output *output = connector_to_output(connector);
  53        enum drm_connector_status status = connector_status_unknown;
  54
  55        if (output->hpd_gpio) {
  56                if (gpiod_get_value(output->hpd_gpio) == 0)
  57                        status = connector_status_disconnected;
  58                else
  59                        status = connector_status_connected;
  60        } else {
  61                if (!output->panel)
  62                        status = connector_status_disconnected;
  63                else
  64                        status = connector_status_connected;
  65        }
  66
  67        if (status != connector_status_connected)
  68                cec_notifier_phys_addr_invalidate(output->cec);
  69
  70        return status;
  71}
  72
  73void tegra_output_connector_destroy(struct drm_connector *connector)
  74{
  75        struct tegra_output *output = connector_to_output(connector);
  76
  77        if (output->cec)
  78                cec_notifier_conn_unregister(output->cec);
  79
  80        drm_connector_unregister(connector);
  81        drm_connector_cleanup(connector);
  82}
  83
  84static irqreturn_t hpd_irq(int irq, void *data)
  85{
  86        struct tegra_output *output = data;
  87
  88        if (output->connector.dev)
  89                drm_helper_hpd_irq_event(output->connector.dev);
  90
  91        return IRQ_HANDLED;
  92}
  93
  94int tegra_output_probe(struct tegra_output *output)
  95{
  96        struct device_node *ddc, *panel;
  97        unsigned long flags;
  98        int err, size;
  99
 100        if (!output->of_node)
 101                output->of_node = output->dev->of_node;
 102
 103        err = drm_of_find_panel_or_bridge(output->of_node, -1, -1,
 104                                          &output->panel, &output->bridge);
 105        if (err && err != -ENODEV)
 106                return err;
 107
 108        panel = of_parse_phandle(output->of_node, "nvidia,panel", 0);
 109        if (panel) {
 110                /*
 111                 * Don't mix nvidia,panel phandle with the graph in a
 112                 * device-tree.
 113                 */
 114                WARN_ON(output->panel || output->bridge);
 115
 116                output->panel = of_drm_find_panel(panel);
 117                of_node_put(panel);
 118
 119                if (IS_ERR(output->panel))
 120                        return PTR_ERR(output->panel);
 121        }
 122
 123        output->edid = of_get_property(output->of_node, "nvidia,edid", &size);
 124
 125        ddc = of_parse_phandle(output->of_node, "nvidia,ddc-i2c-bus", 0);
 126        if (ddc) {
 127                output->ddc = of_get_i2c_adapter_by_node(ddc);
 128                of_node_put(ddc);
 129
 130                if (!output->ddc) {
 131                        err = -EPROBE_DEFER;
 132                        return err;
 133                }
 134        }
 135
 136        output->hpd_gpio = devm_gpiod_get_from_of_node(output->dev,
 137                                                       output->of_node,
 138                                                       "nvidia,hpd-gpio", 0,
 139                                                       GPIOD_IN,
 140                                                       "HDMI hotplug detect");
 141        if (IS_ERR(output->hpd_gpio)) {
 142                if (PTR_ERR(output->hpd_gpio) != -ENOENT)
 143                        return PTR_ERR(output->hpd_gpio);
 144
 145                output->hpd_gpio = NULL;
 146        }
 147
 148        if (output->hpd_gpio) {
 149                err = gpiod_to_irq(output->hpd_gpio);
 150                if (err < 0) {
 151                        dev_err(output->dev, "gpiod_to_irq(): %d\n", err);
 152                        return err;
 153                }
 154
 155                output->hpd_irq = err;
 156
 157                flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
 158                        IRQF_ONESHOT;
 159
 160                err = request_threaded_irq(output->hpd_irq, NULL, hpd_irq,
 161                                           flags, "hpd", output);
 162                if (err < 0) {
 163                        dev_err(output->dev, "failed to request IRQ#%u: %d\n",
 164                                output->hpd_irq, err);
 165                        return err;
 166                }
 167
 168                output->connector.polled = DRM_CONNECTOR_POLL_HPD;
 169
 170                /*
 171                 * Disable the interrupt until the connector has been
 172                 * initialized to avoid a race in the hotplug interrupt
 173                 * handler.
 174                 */
 175                disable_irq(output->hpd_irq);
 176        }
 177
 178        return 0;
 179}
 180
 181void tegra_output_remove(struct tegra_output *output)
 182{
 183        if (output->hpd_gpio)
 184                free_irq(output->hpd_irq, output);
 185
 186        if (output->ddc)
 187                i2c_put_adapter(output->ddc);
 188}
 189
 190int tegra_output_init(struct drm_device *drm, struct tegra_output *output)
 191{
 192        int connector_type;
 193
 194        /*
 195         * The connector is now registered and ready to receive hotplug events
 196         * so the hotplug interrupt can be enabled.
 197         */
 198        if (output->hpd_gpio)
 199                enable_irq(output->hpd_irq);
 200
 201        connector_type = output->connector.connector_type;
 202        /*
 203         * Create a CEC notifier for HDMI connector.
 204         */
 205        if (connector_type == DRM_MODE_CONNECTOR_HDMIA ||
 206            connector_type == DRM_MODE_CONNECTOR_HDMIB) {
 207                struct cec_connector_info conn_info;
 208
 209                cec_fill_conn_info_from_drm(&conn_info, &output->connector);
 210                output->cec = cec_notifier_conn_register(output->dev, NULL,
 211                                                         &conn_info);
 212                if (!output->cec)
 213                        return -ENOMEM;
 214        }
 215
 216        return 0;
 217}
 218
 219void tegra_output_exit(struct tegra_output *output)
 220{
 221        /*
 222         * The connector is going away, so the interrupt must be disabled to
 223         * prevent the hotplug interrupt handler from potentially crashing.
 224         */
 225        if (output->hpd_gpio)
 226                disable_irq(output->hpd_irq);
 227}
 228
 229void tegra_output_find_possible_crtcs(struct tegra_output *output,
 230                                      struct drm_device *drm)
 231{
 232        struct device *dev = output->dev;
 233        struct drm_crtc *crtc;
 234        unsigned int mask = 0;
 235
 236        drm_for_each_crtc(crtc, drm) {
 237                struct tegra_dc *dc = to_tegra_dc(crtc);
 238
 239                if (tegra_dc_has_output(dc, dev))
 240                        mask |= drm_crtc_mask(crtc);
 241        }
 242
 243        if (mask == 0) {
 244                dev_warn(dev, "missing output definition for heads in DT\n");
 245                mask = 0x3;
 246        }
 247
 248        output->encoder.possible_crtcs = mask;
 249}
 250
 251int tegra_output_suspend(struct tegra_output *output)
 252{
 253        if (output->hpd_irq)
 254                disable_irq(output->hpd_irq);
 255
 256        return 0;
 257}
 258
 259int tegra_output_resume(struct tegra_output *output)
 260{
 261        if (output->hpd_irq)
 262                enable_irq(output->hpd_irq);
 263
 264        return 0;
 265}
 266