Commit | Line | Data |
---|---|---|
d8f4a9ed TR |
1 | /* |
2 | * Copyright (C) 2012 Avionic Design GmbH | |
3 | * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved. | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | */ | |
9 | ||
d8f4a9ed | 10 | #include <linux/of_gpio.h> |
d8f4a9ed | 11 | |
4aa3df71 | 12 | #include <drm/drm_atomic_helper.h> |
9be7d864 | 13 | #include <drm/drm_panel.h> |
d8f4a9ed TR |
14 | #include "drm.h" |
15 | ||
132085d8 | 16 | int tegra_output_connector_get_modes(struct drm_connector *connector) |
d8f4a9ed TR |
17 | { |
18 | struct tegra_output *output = connector_to_output(connector); | |
19 | struct edid *edid = NULL; | |
20 | int err = 0; | |
21 | ||
acde5413 TR |
22 | /* |
23 | * If the panel provides one or more modes, use them exclusively and | |
24 | * ignore any other means of obtaining a mode. | |
25 | */ | |
9be7d864 TR |
26 | if (output->panel) { |
27 | err = output->panel->funcs->get_modes(output->panel); | |
28 | if (err > 0) | |
29 | return err; | |
30 | } | |
31 | ||
d8f4a9ed TR |
32 | if (output->edid) |
33 | edid = kmemdup(output->edid, sizeof(*edid), GFP_KERNEL); | |
34 | else if (output->ddc) | |
35 | edid = drm_get_edid(connector, output->ddc); | |
36 | ||
37 | drm_mode_connector_update_edid_property(connector, edid); | |
38 | ||
39 | if (edid) { | |
40 | err = drm_add_edid_modes(connector, edid); | |
41 | kfree(edid); | |
42 | } | |
43 | ||
44 | return err; | |
45 | } | |
46 | ||
132085d8 TR |
47 | struct drm_encoder * |
48 | tegra_output_connector_best_encoder(struct drm_connector *connector) | |
d8f4a9ed TR |
49 | { |
50 | struct tegra_output *output = connector_to_output(connector); | |
51 | ||
52 | return &output->encoder; | |
53 | } | |
54 | ||
132085d8 TR |
55 | enum drm_connector_status |
56 | tegra_output_connector_detect(struct drm_connector *connector, bool force) | |
d8f4a9ed TR |
57 | { |
58 | struct tegra_output *output = connector_to_output(connector); | |
59 | enum drm_connector_status status = connector_status_unknown; | |
60 | ||
61 | if (gpio_is_valid(output->hpd_gpio)) { | |
62 | if (gpio_get_value(output->hpd_gpio) == 0) | |
63 | status = connector_status_disconnected; | |
64 | else | |
65 | status = connector_status_connected; | |
66 | } else { | |
9be7d864 TR |
67 | if (!output->panel) |
68 | status = connector_status_disconnected; | |
69 | else | |
70 | status = connector_status_connected; | |
d8f4a9ed TR |
71 | } |
72 | ||
73 | return status; | |
74 | } | |
75 | ||
132085d8 | 76 | void tegra_output_connector_destroy(struct drm_connector *connector) |
d8f4a9ed | 77 | { |
34ea3d38 | 78 | drm_connector_unregister(connector); |
d8f4a9ed TR |
79 | drm_connector_cleanup(connector); |
80 | } | |
81 | ||
132085d8 | 82 | void tegra_output_encoder_destroy(struct drm_encoder *encoder) |
d8f4a9ed TR |
83 | { |
84 | drm_encoder_cleanup(encoder); | |
85 | } | |
86 | ||
d8f4a9ed TR |
87 | static irqreturn_t hpd_irq(int irq, void *data) |
88 | { | |
89 | struct tegra_output *output = data; | |
90 | ||
8fc8f7da TR |
91 | if (output->connector.dev) |
92 | drm_helper_hpd_irq_event(output->connector.dev); | |
d8f4a9ed TR |
93 | |
94 | return IRQ_HANDLED; | |
95 | } | |
96 | ||
59d29c0e | 97 | int tegra_output_probe(struct tegra_output *output) |
d8f4a9ed | 98 | { |
9be7d864 | 99 | struct device_node *ddc, *panel; |
d8f4a9ed | 100 | enum of_gpio_flags flags; |
7236aa03 | 101 | int err, size; |
d8f4a9ed TR |
102 | |
103 | if (!output->of_node) | |
104 | output->of_node = output->dev->of_node; | |
105 | ||
9be7d864 TR |
106 | panel = of_parse_phandle(output->of_node, "nvidia,panel", 0); |
107 | if (panel) { | |
108 | output->panel = of_drm_find_panel(panel); | |
109 | if (!output->panel) | |
110 | return -EPROBE_DEFER; | |
111 | ||
112 | of_node_put(panel); | |
113 | } | |
114 | ||
d8f4a9ed TR |
115 | output->edid = of_get_property(output->of_node, "nvidia,edid", &size); |
116 | ||
117 | ddc = of_parse_phandle(output->of_node, "nvidia,ddc-i2c-bus", 0); | |
118 | if (ddc) { | |
119 | output->ddc = of_find_i2c_adapter_by_node(ddc); | |
120 | if (!output->ddc) { | |
121 | err = -EPROBE_DEFER; | |
122 | of_node_put(ddc); | |
123 | return err; | |
124 | } | |
125 | ||
126 | of_node_put(ddc); | |
127 | } | |
128 | ||
d8f4a9ed TR |
129 | output->hpd_gpio = of_get_named_gpio_flags(output->of_node, |
130 | "nvidia,hpd-gpio", 0, | |
131 | &flags); | |
d8f4a9ed TR |
132 | if (gpio_is_valid(output->hpd_gpio)) { |
133 | unsigned long flags; | |
134 | ||
135 | err = gpio_request_one(output->hpd_gpio, GPIOF_DIR_IN, | |
136 | "HDMI hotplug detect"); | |
137 | if (err < 0) { | |
138 | dev_err(output->dev, "gpio_request_one(): %d\n", err); | |
139 | return err; | |
140 | } | |
141 | ||
142 | err = gpio_to_irq(output->hpd_gpio); | |
143 | if (err < 0) { | |
144 | dev_err(output->dev, "gpio_to_irq(): %d\n", err); | |
59d29c0e TR |
145 | gpio_free(output->hpd_gpio); |
146 | return err; | |
d8f4a9ed TR |
147 | } |
148 | ||
149 | output->hpd_irq = err; | |
150 | ||
151 | flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | | |
152 | IRQF_ONESHOT; | |
153 | ||
154 | err = request_threaded_irq(output->hpd_irq, NULL, hpd_irq, | |
155 | flags, "hpd", output); | |
156 | if (err < 0) { | |
157 | dev_err(output->dev, "failed to request IRQ#%u: %d\n", | |
158 | output->hpd_irq, err); | |
59d29c0e TR |
159 | gpio_free(output->hpd_gpio); |
160 | return err; | |
d8f4a9ed TR |
161 | } |
162 | ||
163 | output->connector.polled = DRM_CONNECTOR_POLL_HPD; | |
8fc8f7da TR |
164 | |
165 | /* | |
166 | * Disable the interrupt until the connector has been | |
167 | * initialized to avoid a race in the hotplug interrupt | |
168 | * handler. | |
169 | */ | |
170 | disable_irq(output->hpd_irq); | |
d8f4a9ed TR |
171 | } |
172 | ||
59d29c0e TR |
173 | return 0; |
174 | } | |
175 | ||
328ec69e | 176 | void tegra_output_remove(struct tegra_output *output) |
59d29c0e TR |
177 | { |
178 | if (gpio_is_valid(output->hpd_gpio)) { | |
179 | free_irq(output->hpd_irq, output); | |
180 | gpio_free(output->hpd_gpio); | |
181 | } | |
182 | ||
183 | if (output->ddc) | |
184 | put_device(&output->ddc->dev); | |
59d29c0e TR |
185 | } |
186 | ||
187 | int tegra_output_init(struct drm_device *drm, struct tegra_output *output) | |
188 | { | |
ea130b24 | 189 | int err; |
d8f4a9ed | 190 | |
ea130b24 TR |
191 | if (output->panel) { |
192 | err = drm_panel_attach(output->panel, &output->connector); | |
193 | if (err < 0) | |
194 | return err; | |
195 | } | |
d8f4a9ed | 196 | |
8fc8f7da TR |
197 | /* |
198 | * The connector is now registered and ready to receive hotplug events | |
199 | * so the hotplug interrupt can be enabled. | |
200 | */ | |
201 | if (gpio_is_valid(output->hpd_gpio)) | |
202 | enable_irq(output->hpd_irq); | |
203 | ||
d8f4a9ed | 204 | return 0; |
d8f4a9ed TR |
205 | } |
206 | ||
328ec69e | 207 | void tegra_output_exit(struct tegra_output *output) |
d8f4a9ed | 208 | { |
8fc8f7da TR |
209 | /* |
210 | * The connector is going away, so the interrupt must be disabled to | |
211 | * prevent the hotplug interrupt handler from potentially crashing. | |
212 | */ | |
213 | if (gpio_is_valid(output->hpd_gpio)) | |
214 | disable_irq(output->hpd_irq); | |
215 | ||
9aaa0ceb TR |
216 | if (output->panel) |
217 | drm_panel_detach(output->panel); | |
d8f4a9ed | 218 | } |