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