xref: /linux/drivers/gpu/drm/tegra/output.c (revision 3a39d672e7f48b8d6b91a09afa4b55352773b4b5)
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 
tegra_output_connector_get_modes(struct drm_connector * connector)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
tegra_output_connector_detect(struct drm_connector * connector,bool force)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 
tegra_output_connector_destroy(struct drm_connector * connector)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 
hpd_irq(int irq,void * data)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 
tegra_output_probe(struct tegra_output * output)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 
tegra_output_remove(struct tegra_output * output)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 
tegra_output_init(struct drm_device * drm,struct tegra_output * output)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 
tegra_output_exit(struct tegra_output * output)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 
tegra_output_find_possible_crtcs(struct tegra_output * output,struct drm_device * drm)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 
tegra_output_suspend(struct tegra_output * output)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 
tegra_output_resume(struct tegra_output * output)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 }
283