diff options
Diffstat (limited to 'drivers/gpu/drm/tilcdc/tilcdc_tfp410.c')
-rw-r--r-- | drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c new file mode 100644 index 000000000000..58d487ba2414 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | |||
@@ -0,0 +1,419 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2012 Texas Instruments | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include <linux/i2c.h> | ||
19 | #include <linux/of_i2c.h> | ||
20 | #include <linux/gpio.h> | ||
21 | #include <linux/of_gpio.h> | ||
22 | #include <linux/pinctrl/pinmux.h> | ||
23 | #include <linux/pinctrl/consumer.h> | ||
24 | |||
25 | #include "tilcdc_drv.h" | ||
26 | |||
27 | struct tfp410_module { | ||
28 | struct tilcdc_module base; | ||
29 | struct i2c_adapter *i2c; | ||
30 | int gpio; | ||
31 | }; | ||
32 | #define to_tfp410_module(x) container_of(x, struct tfp410_module, base) | ||
33 | |||
34 | |||
35 | static const struct tilcdc_panel_info dvi_info = { | ||
36 | .ac_bias = 255, | ||
37 | .ac_bias_intrpt = 0, | ||
38 | .dma_burst_sz = 16, | ||
39 | .bpp = 16, | ||
40 | .fdd = 0x80, | ||
41 | .tft_alt_mode = 0, | ||
42 | .sync_edge = 0, | ||
43 | .sync_ctrl = 1, | ||
44 | .raster_order = 0, | ||
45 | }; | ||
46 | |||
47 | /* | ||
48 | * Encoder: | ||
49 | */ | ||
50 | |||
51 | struct tfp410_encoder { | ||
52 | struct drm_encoder base; | ||
53 | struct tfp410_module *mod; | ||
54 | int dpms; | ||
55 | }; | ||
56 | #define to_tfp410_encoder(x) container_of(x, struct tfp410_encoder, base) | ||
57 | |||
58 | |||
59 | static void tfp410_encoder_destroy(struct drm_encoder *encoder) | ||
60 | { | ||
61 | struct tfp410_encoder *tfp410_encoder = to_tfp410_encoder(encoder); | ||
62 | drm_encoder_cleanup(encoder); | ||
63 | kfree(tfp410_encoder); | ||
64 | } | ||
65 | |||
66 | static void tfp410_encoder_dpms(struct drm_encoder *encoder, int mode) | ||
67 | { | ||
68 | struct tfp410_encoder *tfp410_encoder = to_tfp410_encoder(encoder); | ||
69 | |||
70 | if (tfp410_encoder->dpms == mode) | ||
71 | return; | ||
72 | |||
73 | if (mode == DRM_MODE_DPMS_ON) { | ||
74 | DBG("Power on"); | ||
75 | gpio_direction_output(tfp410_encoder->mod->gpio, 1); | ||
76 | } else { | ||
77 | DBG("Power off"); | ||
78 | gpio_direction_output(tfp410_encoder->mod->gpio, 0); | ||
79 | } | ||
80 | |||
81 | tfp410_encoder->dpms = mode; | ||
82 | } | ||
83 | |||
84 | static bool tfp410_encoder_mode_fixup(struct drm_encoder *encoder, | ||
85 | const struct drm_display_mode *mode, | ||
86 | struct drm_display_mode *adjusted_mode) | ||
87 | { | ||
88 | /* nothing needed */ | ||
89 | return true; | ||
90 | } | ||
91 | |||
92 | static void tfp410_encoder_prepare(struct drm_encoder *encoder) | ||
93 | { | ||
94 | tfp410_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); | ||
95 | tilcdc_crtc_set_panel_info(encoder->crtc, &dvi_info); | ||
96 | } | ||
97 | |||
98 | static void tfp410_encoder_commit(struct drm_encoder *encoder) | ||
99 | { | ||
100 | tfp410_encoder_dpms(encoder, DRM_MODE_DPMS_ON); | ||
101 | } | ||
102 | |||
103 | static void tfp410_encoder_mode_set(struct drm_encoder *encoder, | ||
104 | struct drm_display_mode *mode, | ||
105 | struct drm_display_mode *adjusted_mode) | ||
106 | { | ||
107 | /* nothing needed */ | ||
108 | } | ||
109 | |||
110 | static const struct drm_encoder_funcs tfp410_encoder_funcs = { | ||
111 | .destroy = tfp410_encoder_destroy, | ||
112 | }; | ||
113 | |||
114 | static const struct drm_encoder_helper_funcs tfp410_encoder_helper_funcs = { | ||
115 | .dpms = tfp410_encoder_dpms, | ||
116 | .mode_fixup = tfp410_encoder_mode_fixup, | ||
117 | .prepare = tfp410_encoder_prepare, | ||
118 | .commit = tfp410_encoder_commit, | ||
119 | .mode_set = tfp410_encoder_mode_set, | ||
120 | }; | ||
121 | |||
122 | static struct drm_encoder *tfp410_encoder_create(struct drm_device *dev, | ||
123 | struct tfp410_module *mod) | ||
124 | { | ||
125 | struct tfp410_encoder *tfp410_encoder; | ||
126 | struct drm_encoder *encoder; | ||
127 | int ret; | ||
128 | |||
129 | tfp410_encoder = kzalloc(sizeof(*tfp410_encoder), GFP_KERNEL); | ||
130 | if (!tfp410_encoder) { | ||
131 | dev_err(dev->dev, "allocation failed\n"); | ||
132 | return NULL; | ||
133 | } | ||
134 | |||
135 | tfp410_encoder->dpms = DRM_MODE_DPMS_OFF; | ||
136 | tfp410_encoder->mod = mod; | ||
137 | |||
138 | encoder = &tfp410_encoder->base; | ||
139 | encoder->possible_crtcs = 1; | ||
140 | |||
141 | ret = drm_encoder_init(dev, encoder, &tfp410_encoder_funcs, | ||
142 | DRM_MODE_ENCODER_TMDS); | ||
143 | if (ret < 0) | ||
144 | goto fail; | ||
145 | |||
146 | drm_encoder_helper_add(encoder, &tfp410_encoder_helper_funcs); | ||
147 | |||
148 | return encoder; | ||
149 | |||
150 | fail: | ||
151 | tfp410_encoder_destroy(encoder); | ||
152 | return NULL; | ||
153 | } | ||
154 | |||
155 | /* | ||
156 | * Connector: | ||
157 | */ | ||
158 | |||
159 | struct tfp410_connector { | ||
160 | struct drm_connector base; | ||
161 | |||
162 | struct drm_encoder *encoder; /* our connected encoder */ | ||
163 | struct tfp410_module *mod; | ||
164 | }; | ||
165 | #define to_tfp410_connector(x) container_of(x, struct tfp410_connector, base) | ||
166 | |||
167 | |||
168 | static void tfp410_connector_destroy(struct drm_connector *connector) | ||
169 | { | ||
170 | struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector); | ||
171 | drm_connector_cleanup(connector); | ||
172 | kfree(tfp410_connector); | ||
173 | } | ||
174 | |||
175 | static enum drm_connector_status tfp410_connector_detect( | ||
176 | struct drm_connector *connector, | ||
177 | bool force) | ||
178 | { | ||
179 | struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector); | ||
180 | |||
181 | if (drm_probe_ddc(tfp410_connector->mod->i2c)) | ||
182 | return connector_status_connected; | ||
183 | |||
184 | return connector_status_unknown; | ||
185 | } | ||
186 | |||
187 | static int tfp410_connector_get_modes(struct drm_connector *connector) | ||
188 | { | ||
189 | struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector); | ||
190 | struct edid *edid; | ||
191 | int ret = 0; | ||
192 | |||
193 | edid = drm_get_edid(connector, tfp410_connector->mod->i2c); | ||
194 | |||
195 | drm_mode_connector_update_edid_property(connector, edid); | ||
196 | |||
197 | if (edid) { | ||
198 | ret = drm_add_edid_modes(connector, edid); | ||
199 | kfree(edid); | ||
200 | } | ||
201 | |||
202 | return ret; | ||
203 | } | ||
204 | |||
205 | static int tfp410_connector_mode_valid(struct drm_connector *connector, | ||
206 | struct drm_display_mode *mode) | ||
207 | { | ||
208 | struct tilcdc_drm_private *priv = connector->dev->dev_private; | ||
209 | /* our only constraints are what the crtc can generate: */ | ||
210 | return tilcdc_crtc_mode_valid(priv->crtc, mode); | ||
211 | } | ||
212 | |||
213 | static struct drm_encoder *tfp410_connector_best_encoder( | ||
214 | struct drm_connector *connector) | ||
215 | { | ||
216 | struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector); | ||
217 | return tfp410_connector->encoder; | ||
218 | } | ||
219 | |||
220 | static const struct drm_connector_funcs tfp410_connector_funcs = { | ||
221 | .destroy = tfp410_connector_destroy, | ||
222 | .dpms = drm_helper_connector_dpms, | ||
223 | .detect = tfp410_connector_detect, | ||
224 | .fill_modes = drm_helper_probe_single_connector_modes, | ||
225 | }; | ||
226 | |||
227 | static const struct drm_connector_helper_funcs tfp410_connector_helper_funcs = { | ||
228 | .get_modes = tfp410_connector_get_modes, | ||
229 | .mode_valid = tfp410_connector_mode_valid, | ||
230 | .best_encoder = tfp410_connector_best_encoder, | ||
231 | }; | ||
232 | |||
233 | static struct drm_connector *tfp410_connector_create(struct drm_device *dev, | ||
234 | struct tfp410_module *mod, struct drm_encoder *encoder) | ||
235 | { | ||
236 | struct tfp410_connector *tfp410_connector; | ||
237 | struct drm_connector *connector; | ||
238 | int ret; | ||
239 | |||
240 | tfp410_connector = kzalloc(sizeof(*tfp410_connector), GFP_KERNEL); | ||
241 | if (!tfp410_connector) { | ||
242 | dev_err(dev->dev, "allocation failed\n"); | ||
243 | return NULL; | ||
244 | } | ||
245 | |||
246 | tfp410_connector->encoder = encoder; | ||
247 | tfp410_connector->mod = mod; | ||
248 | |||
249 | connector = &tfp410_connector->base; | ||
250 | |||
251 | drm_connector_init(dev, connector, &tfp410_connector_funcs, | ||
252 | DRM_MODE_CONNECTOR_DVID); | ||
253 | drm_connector_helper_add(connector, &tfp410_connector_helper_funcs); | ||
254 | |||
255 | connector->polled = DRM_CONNECTOR_POLL_CONNECT | | ||
256 | DRM_CONNECTOR_POLL_DISCONNECT; | ||
257 | |||
258 | connector->interlace_allowed = 0; | ||
259 | connector->doublescan_allowed = 0; | ||
260 | |||
261 | ret = drm_mode_connector_attach_encoder(connector, encoder); | ||
262 | if (ret) | ||
263 | goto fail; | ||
264 | |||
265 | drm_sysfs_connector_add(connector); | ||
266 | |||
267 | return connector; | ||
268 | |||
269 | fail: | ||
270 | tfp410_connector_destroy(connector); | ||
271 | return NULL; | ||
272 | } | ||
273 | |||
274 | /* | ||
275 | * Module: | ||
276 | */ | ||
277 | |||
278 | static int tfp410_modeset_init(struct tilcdc_module *mod, struct drm_device *dev) | ||
279 | { | ||
280 | struct tfp410_module *tfp410_mod = to_tfp410_module(mod); | ||
281 | struct tilcdc_drm_private *priv = dev->dev_private; | ||
282 | struct drm_encoder *encoder; | ||
283 | struct drm_connector *connector; | ||
284 | |||
285 | encoder = tfp410_encoder_create(dev, tfp410_mod); | ||
286 | if (!encoder) | ||
287 | return -ENOMEM; | ||
288 | |||
289 | connector = tfp410_connector_create(dev, tfp410_mod, encoder); | ||
290 | if (!connector) | ||
291 | return -ENOMEM; | ||
292 | |||
293 | priv->encoders[priv->num_encoders++] = encoder; | ||
294 | priv->connectors[priv->num_connectors++] = connector; | ||
295 | |||
296 | return 0; | ||
297 | } | ||
298 | |||
299 | static void tfp410_destroy(struct tilcdc_module *mod) | ||
300 | { | ||
301 | struct tfp410_module *tfp410_mod = to_tfp410_module(mod); | ||
302 | |||
303 | if (tfp410_mod->i2c) | ||
304 | i2c_put_adapter(tfp410_mod->i2c); | ||
305 | |||
306 | if (!IS_ERR_VALUE(tfp410_mod->gpio)) | ||
307 | gpio_free(tfp410_mod->gpio); | ||
308 | |||
309 | tilcdc_module_cleanup(mod); | ||
310 | kfree(tfp410_mod); | ||
311 | } | ||
312 | |||
313 | static const struct tilcdc_module_ops tfp410_module_ops = { | ||
314 | .modeset_init = tfp410_modeset_init, | ||
315 | .destroy = tfp410_destroy, | ||
316 | }; | ||
317 | |||
318 | /* | ||
319 | * Device: | ||
320 | */ | ||
321 | |||
322 | static struct of_device_id tfp410_of_match[]; | ||
323 | |||
324 | static int tfp410_probe(struct platform_device *pdev) | ||
325 | { | ||
326 | struct device_node *node = pdev->dev.of_node; | ||
327 | struct device_node *i2c_node; | ||
328 | struct tfp410_module *tfp410_mod; | ||
329 | struct tilcdc_module *mod; | ||
330 | struct pinctrl *pinctrl; | ||
331 | uint32_t i2c_phandle; | ||
332 | int ret = -EINVAL; | ||
333 | |||
334 | /* bail out early if no DT data: */ | ||
335 | if (!node) { | ||
336 | dev_err(&pdev->dev, "device-tree data is missing\n"); | ||
337 | return -ENXIO; | ||
338 | } | ||
339 | |||
340 | tfp410_mod = kzalloc(sizeof(*tfp410_mod), GFP_KERNEL); | ||
341 | if (!tfp410_mod) | ||
342 | return -ENOMEM; | ||
343 | |||
344 | mod = &tfp410_mod->base; | ||
345 | |||
346 | tilcdc_module_init(mod, "tfp410", &tfp410_module_ops); | ||
347 | |||
348 | pinctrl = devm_pinctrl_get_select_default(&pdev->dev); | ||
349 | if (IS_ERR(pinctrl)) | ||
350 | dev_warn(&pdev->dev, "pins are not configured\n"); | ||
351 | |||
352 | if (of_property_read_u32(node, "i2c", &i2c_phandle)) { | ||
353 | dev_err(&pdev->dev, "could not get i2c bus phandle\n"); | ||
354 | goto fail; | ||
355 | } | ||
356 | |||
357 | i2c_node = of_find_node_by_phandle(i2c_phandle); | ||
358 | if (!i2c_node) { | ||
359 | dev_err(&pdev->dev, "could not get i2c bus node\n"); | ||
360 | goto fail; | ||
361 | } | ||
362 | |||
363 | tfp410_mod->i2c = of_find_i2c_adapter_by_node(i2c_node); | ||
364 | if (!tfp410_mod->i2c) { | ||
365 | dev_err(&pdev->dev, "could not get i2c\n"); | ||
366 | goto fail; | ||
367 | } | ||
368 | |||
369 | of_node_put(i2c_node); | ||
370 | |||
371 | tfp410_mod->gpio = of_get_named_gpio_flags(node, "powerdn-gpio", | ||
372 | 0, NULL); | ||
373 | if (IS_ERR_VALUE(tfp410_mod->gpio)) { | ||
374 | dev_warn(&pdev->dev, "No power down GPIO\n"); | ||
375 | } else { | ||
376 | ret = gpio_request(tfp410_mod->gpio, "DVI_PDn"); | ||
377 | if (ret) { | ||
378 | dev_err(&pdev->dev, "could not get DVI_PDn gpio\n"); | ||
379 | goto fail; | ||
380 | } | ||
381 | } | ||
382 | |||
383 | return 0; | ||
384 | |||
385 | fail: | ||
386 | tfp410_destroy(mod); | ||
387 | return ret; | ||
388 | } | ||
389 | |||
390 | static int tfp410_remove(struct platform_device *pdev) | ||
391 | { | ||
392 | return 0; | ||
393 | } | ||
394 | |||
395 | static struct of_device_id tfp410_of_match[] = { | ||
396 | { .compatible = "ti,tilcdc,tfp410", }, | ||
397 | { }, | ||
398 | }; | ||
399 | MODULE_DEVICE_TABLE(of, tfp410_of_match); | ||
400 | |||
401 | struct platform_driver tfp410_driver = { | ||
402 | .probe = tfp410_probe, | ||
403 | .remove = tfp410_remove, | ||
404 | .driver = { | ||
405 | .owner = THIS_MODULE, | ||
406 | .name = "tfp410", | ||
407 | .of_match_table = tfp410_of_match, | ||
408 | }, | ||
409 | }; | ||
410 | |||
411 | int __init tilcdc_tfp410_init(void) | ||
412 | { | ||
413 | return platform_driver_register(&tfp410_driver); | ||
414 | } | ||
415 | |||
416 | void __exit tilcdc_tfp410_fini(void) | ||
417 | { | ||
418 | platform_driver_unregister(&tfp410_driver); | ||
419 | } | ||