blob: cccd44711d682cc18d4b9f82942041290f7a957f [file] [log] [blame]
Thierry Redingc4755fb2017-11-13 11:08:13 +01001/*
2 * Copyright (C) 2017 NVIDIA CORPORATION. All rights reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8
9#include <linux/clk.h>
10#include <linux/host1x.h>
11#include <linux/module.h>
12#include <linux/of.h>
13#include <linux/of_device.h>
14#include <linux/of_graph.h>
15#include <linux/platform_device.h>
16#include <linux/pm_runtime.h>
17#include <linux/reset.h>
18
19#include <drm/drmP.h>
20#include <drm/drm_atomic.h>
21#include <drm/drm_atomic_helper.h>
22#include <drm/drm_crtc_helper.h>
23
24#include "drm.h"
25#include "dc.h"
26#include "plane.h"
27
28static const u32 tegra_shared_plane_formats[] = {
Thierry Reding511c7022017-11-14 16:07:40 +010029 DRM_FORMAT_ARGB1555,
Thierry Redingc4755fb2017-11-13 11:08:13 +010030 DRM_FORMAT_RGB565,
Thierry Reding511c7022017-11-14 16:07:40 +010031 DRM_FORMAT_RGBA5551,
32 DRM_FORMAT_ARGB8888,
33 DRM_FORMAT_ABGR8888,
34 /* new on Tegra114 */
35 DRM_FORMAT_ABGR4444,
36 DRM_FORMAT_ABGR1555,
37 DRM_FORMAT_BGRA5551,
38 DRM_FORMAT_XRGB1555,
39 DRM_FORMAT_RGBX5551,
40 DRM_FORMAT_XBGR1555,
41 DRM_FORMAT_BGRX5551,
42 DRM_FORMAT_BGR565,
43 DRM_FORMAT_XRGB8888,
44 DRM_FORMAT_XBGR8888,
45 /* planar formats */
46 DRM_FORMAT_UYVY,
47 DRM_FORMAT_YUYV,
48 DRM_FORMAT_YUV420,
49 DRM_FORMAT_YUV422,
Thierry Redingc4755fb2017-11-13 11:08:13 +010050};
51
52static inline unsigned int tegra_plane_offset(struct tegra_shared_plane *plane,
53 unsigned int offset)
54{
55 struct tegra_plane *p = &plane->base;
56
57 if (offset >= 0x500 && offset <= 0x581) {
58 offset = 0x000 + (offset - 0x500);
59 return p->offset + offset;
60 }
61
62 if (offset >= 0x700 && offset <= 0x73c) {
63 offset = 0x180 + (offset - 0x700);
64 return p->offset + offset;
65 }
66
67 if (offset >= 0x800 && offset <= 0x83e) {
68 offset = 0x1c0 + (offset - 0x800);
69 return p->offset + offset;
70 }
71
72 dev_WARN(plane->dc->dev, "invalid offset: %x\n", offset);
73
74 return p->offset + offset;
75}
76
77static inline u32 tegra_plane_readl(struct tegra_shared_plane *plane,
78 unsigned int offset)
79{
80 return tegra_dc_readl(plane->dc, tegra_plane_offset(plane, offset));
81}
82
83static inline void tegra_plane_writel(struct tegra_shared_plane *plane,
84 u32 value, unsigned int offset)
85{
86 tegra_dc_writel(plane->dc, value, tegra_plane_offset(plane, offset));
87}
88
89static int tegra_windowgroup_enable(struct tegra_windowgroup *wgrp)
90{
91 mutex_lock(&wgrp->lock);
92
93 if (wgrp->usecount == 0) {
94 pm_runtime_get_sync(wgrp->parent);
95 reset_control_deassert(wgrp->rst);
96 }
97
98 wgrp->usecount++;
99 mutex_unlock(&wgrp->lock);
100
101 return 0;
102}
103
104static void tegra_windowgroup_disable(struct tegra_windowgroup *wgrp)
105{
106 int err;
107
108 mutex_lock(&wgrp->lock);
109
110 if (wgrp->usecount == 1) {
111 err = reset_control_assert(wgrp->rst);
112 if (err < 0) {
113 pr_err("failed to assert reset for window group %u\n",
114 wgrp->index);
115 }
116
117 pm_runtime_put(wgrp->parent);
118 }
119
120 wgrp->usecount--;
121 mutex_unlock(&wgrp->lock);
122}
123
124int tegra_display_hub_prepare(struct tegra_display_hub *hub)
125{
126 unsigned int i;
127
128 /*
129 * XXX Enabling/disabling windowgroups needs to happen when the owner
130 * display controller is disabled. There's currently no good point at
131 * which this could be executed, so unconditionally enable all window
132 * groups for now.
133 */
134 for (i = 0; i < hub->soc->num_wgrps; i++) {
135 struct tegra_windowgroup *wgrp = &hub->wgrps[i];
136
137 tegra_windowgroup_enable(wgrp);
138 }
139
140 return 0;
141}
142
143void tegra_display_hub_cleanup(struct tegra_display_hub *hub)
144{
145 unsigned int i;
146
147 /*
148 * XXX Remove this once window groups can be more fine-grainedly
149 * enabled and disabled.
150 */
151 for (i = 0; i < hub->soc->num_wgrps; i++) {
152 struct tegra_windowgroup *wgrp = &hub->wgrps[i];
153
154 tegra_windowgroup_disable(wgrp);
155 }
156}
157
158static void tegra_shared_plane_update(struct tegra_shared_plane *plane)
159{
160 struct tegra_dc *dc = plane->dc;
161 unsigned long timeout;
162 u32 mask, value;
163
164 mask = COMMON_UPDATE | WIN_A_UPDATE << plane->base.index;
165 tegra_dc_writel(dc, mask, DC_CMD_STATE_CONTROL);
166
167 timeout = jiffies + msecs_to_jiffies(1000);
168
169 while (time_before(jiffies, timeout)) {
170 value = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
171 if ((value & mask) == 0)
172 break;
173
174 usleep_range(100, 400);
175 }
176}
177
178static void tegra_shared_plane_activate(struct tegra_shared_plane *plane)
179{
180 struct tegra_dc *dc = plane->dc;
181 unsigned long timeout;
182 u32 mask, value;
183
184 mask = COMMON_ACTREQ | WIN_A_ACT_REQ << plane->base.index;
185 tegra_dc_writel(dc, mask, DC_CMD_STATE_CONTROL);
186
187 timeout = jiffies + msecs_to_jiffies(1000);
188
189 while (time_before(jiffies, timeout)) {
190 value = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
191 if ((value & mask) == 0)
192 break;
193
194 usleep_range(100, 400);
195 }
196}
197
198static unsigned int
199tegra_shared_plane_get_owner(struct tegra_shared_plane *plane,
200 struct tegra_dc *dc)
201{
202 unsigned int offset =
203 tegra_plane_offset(plane, DC_WIN_CORE_WINDOWGROUP_SET_CONTROL);
204
205 return tegra_dc_readl(dc, offset) & OWNER_MASK;
206}
207
208static bool tegra_dc_owns_shared_plane(struct tegra_dc *dc,
209 struct tegra_shared_plane *plane)
210{
211 struct device *dev = dc->dev;
212
213 if (tegra_shared_plane_get_owner(plane, dc) == dc->pipe) {
214 if (plane->dc == dc)
215 return true;
216
217 dev_WARN(dev, "head %u owns window %u but is not attached\n",
218 dc->pipe, plane->base.index);
219 }
220
221 return false;
222}
223
224static int tegra_shared_plane_set_owner(struct tegra_shared_plane *plane,
225 struct tegra_dc *new)
226{
227 unsigned int offset =
228 tegra_plane_offset(plane, DC_WIN_CORE_WINDOWGROUP_SET_CONTROL);
229 struct tegra_dc *old = plane->dc, *dc = new ? new : old;
230 struct device *dev = new ? new->dev : old->dev;
231 unsigned int owner, index = plane->base.index;
232 u32 value;
233
234 value = tegra_dc_readl(dc, offset);
235 owner = value & OWNER_MASK;
236
237 if (new && (owner != OWNER_MASK && owner != new->pipe)) {
238 dev_WARN(dev, "window %u owned by head %u\n", index, owner);
239 return -EBUSY;
240 }
241
242 /*
243 * This seems to happen whenever the head has been disabled with one
244 * or more windows being active. This is harmless because we'll just
245 * reassign the window to the new head anyway.
246 */
247 if (old && owner == OWNER_MASK)
248 dev_dbg(dev, "window %u not owned by head %u but %u\n", index,
249 old->pipe, owner);
250
251 value &= ~OWNER_MASK;
252
253 if (new)
254 value |= OWNER(new->pipe);
255 else
256 value |= OWNER_MASK;
257
258 tegra_dc_writel(dc, value, offset);
259
260 plane->dc = new;
261
262 return 0;
263}
264
265static void tegra_dc_assign_shared_plane(struct tegra_dc *dc,
266 struct tegra_shared_plane *plane)
267{
268 u32 value;
269 int err;
270
271 if (!tegra_dc_owns_shared_plane(dc, plane)) {
272 err = tegra_shared_plane_set_owner(plane, dc);
273 if (err < 0)
274 return;
275 }
276
277 value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_LINEBUF_CONFIG);
278 value |= MODE_FOUR_LINES;
279 tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_LINEBUF_CONFIG);
280
281 value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_FETCH_METER);
282 value = SLOTS(1);
283 tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_FETCH_METER);
284
285 /* disable watermark */
286 value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLA);
287 value &= ~LATENCY_CTL_MODE_ENABLE;
288 tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLA);
289
290 value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLB);
291 value |= WATERMARK_MASK;
292 tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLB);
293
294 /* pipe meter */
295 value = tegra_plane_readl(plane, DC_WIN_CORE_PRECOMP_WGRP_PIPE_METER);
296 value = PIPE_METER_INT(0) | PIPE_METER_FRAC(0);
297 tegra_plane_writel(plane, value, DC_WIN_CORE_PRECOMP_WGRP_PIPE_METER);
298
299 /* mempool entries */
300 value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_POOL_CONFIG);
301 value = MEMPOOL_ENTRIES(0x331);
302 tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_POOL_CONFIG);
303
304 value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_THREAD_GROUP);
305 value &= ~THREAD_NUM_MASK;
306 value |= THREAD_NUM(plane->base.index);
307 value |= THREAD_GROUP_ENABLE;
308 tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_THREAD_GROUP);
309
310 tegra_shared_plane_update(plane);
311 tegra_shared_plane_activate(plane);
312}
313
314static void tegra_dc_remove_shared_plane(struct tegra_dc *dc,
315 struct tegra_shared_plane *plane)
316{
317 tegra_shared_plane_set_owner(plane, NULL);
318}
319
320static int tegra_shared_plane_atomic_check(struct drm_plane *plane,
321 struct drm_plane_state *state)
322{
323 struct tegra_plane_state *plane_state = to_tegra_plane_state(state);
324 struct tegra_shared_plane *tegra = to_tegra_shared_plane(plane);
325 struct tegra_bo_tiling *tiling = &plane_state->tiling;
326 struct tegra_dc *dc = to_tegra_dc(state->crtc);
327 int err;
328
329 /* no need for further checks if the plane is being disabled */
330 if (!state->crtc || !state->fb)
331 return 0;
332
333 err = tegra_plane_format(state->fb->format->format,
334 &plane_state->format,
335 &plane_state->swap);
336 if (err < 0)
337 return err;
338
339 err = tegra_fb_get_tiling(state->fb, tiling);
340 if (err < 0)
341 return err;
342
343 if (tiling->mode == TEGRA_BO_TILING_MODE_BLOCK &&
344 !dc->soc->supports_block_linear) {
345 DRM_ERROR("hardware doesn't support block linear mode\n");
346 return -EINVAL;
347 }
348
349 /*
350 * Tegra doesn't support different strides for U and V planes so we
351 * error out if the user tries to display a framebuffer with such a
352 * configuration.
353 */
354 if (state->fb->format->num_planes > 2) {
355 if (state->fb->pitches[2] != state->fb->pitches[1]) {
356 DRM_ERROR("unsupported UV-plane configuration\n");
357 return -EINVAL;
358 }
359 }
360
361 /* XXX scaling is not yet supported, add a check here */
362
363 err = tegra_plane_state_add(&tegra->base, state);
364 if (err < 0)
365 return err;
366
367 return 0;
368}
369
370static void tegra_shared_plane_atomic_disable(struct drm_plane *plane,
371 struct drm_plane_state *old_state)
372{
373 struct tegra_shared_plane *p = to_tegra_shared_plane(plane);
374 struct tegra_dc *dc = to_tegra_dc(old_state->crtc);
375 u32 value;
376
377 /* rien ne va plus */
378 if (!old_state || !old_state->crtc)
379 return;
380
381 /*
382 * XXX Legacy helpers seem to sometimes call ->atomic_disable() even
383 * on planes that are already disabled. Make sure we fallback to the
384 * head for this particular state instead of crashing.
385 */
386 if (WARN_ON(p->dc == NULL))
387 p->dc = dc;
388
389 pm_runtime_get_sync(dc->dev);
390
391 value = tegra_plane_readl(p, DC_WIN_WIN_OPTIONS);
392 value &= ~WIN_ENABLE;
393 tegra_plane_writel(p, value, DC_WIN_WIN_OPTIONS);
394
395 tegra_dc_remove_shared_plane(dc, p);
396
397 pm_runtime_put(dc->dev);
398}
399
400static void tegra_shared_plane_atomic_update(struct drm_plane *plane,
401 struct drm_plane_state *old_state)
402{
403 struct tegra_plane_state *state = to_tegra_plane_state(plane->state);
404 struct tegra_shared_plane *p = to_tegra_shared_plane(plane);
405 struct tegra_dc *dc = to_tegra_dc(plane->state->crtc);
406 struct drm_framebuffer *fb = plane->state->fb;
407 struct tegra_bo *bo;
408 dma_addr_t base;
409 u32 value;
410
411 /* rien ne va plus */
412 if (!plane->state->crtc || !plane->state->fb)
413 return;
414
415 if (!plane->state->visible) {
416 tegra_shared_plane_atomic_disable(plane, old_state);
417 return;
418 }
419
420 pm_runtime_get_sync(dc->dev);
421
422 tegra_dc_assign_shared_plane(dc, p);
423
424 tegra_plane_writel(p, VCOUNTER, DC_WIN_CORE_ACT_CONTROL);
425
426 /* blending */
427 value = BLEND_FACTOR_DST_ALPHA_ZERO | BLEND_FACTOR_SRC_ALPHA_K2 |
428 BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_SRC |
429 BLEND_FACTOR_SRC_COLOR_K1_TIMES_SRC;
430 tegra_plane_writel(p, value, DC_WIN_BLEND_MATCH_SELECT);
431
432 value = BLEND_FACTOR_DST_ALPHA_ZERO | BLEND_FACTOR_SRC_ALPHA_K2 |
433 BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_SRC |
434 BLEND_FACTOR_SRC_COLOR_K1_TIMES_SRC;
435 tegra_plane_writel(p, value, DC_WIN_BLEND_NOMATCH_SELECT);
436
437 value = K2(255) | K1(255) | WINDOW_LAYER_DEPTH(p->base.depth);
438 tegra_plane_writel(p, value, DC_WIN_BLEND_LAYER_CONTROL);
439
440 /* bypass scaling */
441 value = HORIZONTAL_TAPS_5 | VERTICAL_TAPS_5;
442 tegra_plane_writel(p, value, DC_WIN_WINDOWGROUP_SET_CONTROL_INPUT_SCALER);
443
444 value = INPUT_SCALER_VBYPASS | INPUT_SCALER_HBYPASS;
445 tegra_plane_writel(p, value, DC_WIN_WINDOWGROUP_SET_INPUT_SCALER_USAGE);
446
447 /* disable compression */
448 tegra_plane_writel(p, 0, DC_WINBUF_CDE_CONTROL);
449
450 bo = tegra_fb_get_plane(fb, 0);
451 base = bo->paddr;
452
453 tegra_plane_writel(p, state->format, DC_WIN_COLOR_DEPTH);
454 tegra_plane_writel(p, 0, DC_WIN_PRECOMP_WGRP_PARAMS);
455
456 value = V_POSITION(plane->state->crtc_y) |
457 H_POSITION(plane->state->crtc_x);
458 tegra_plane_writel(p, value, DC_WIN_POSITION);
459
460 value = V_SIZE(plane->state->crtc_h) | H_SIZE(plane->state->crtc_w);
461 tegra_plane_writel(p, value, DC_WIN_SIZE);
462
463 value = WIN_ENABLE | COLOR_EXPAND;
464 tegra_plane_writel(p, value, DC_WIN_WIN_OPTIONS);
465
466 value = V_SIZE(plane->state->crtc_h) | H_SIZE(plane->state->crtc_w);
467 tegra_plane_writel(p, value, DC_WIN_CROPPED_SIZE);
468
469 tegra_plane_writel(p, upper_32_bits(base), DC_WINBUF_START_ADDR_HI);
470 tegra_plane_writel(p, lower_32_bits(base), DC_WINBUF_START_ADDR);
471
472 value = PITCH(fb->pitches[0]);
473 tegra_plane_writel(p, value, DC_WIN_PLANAR_STORAGE);
474
475 value = CLAMP_BEFORE_BLEND | DEGAMMA_SRGB | INPUT_RANGE_FULL;
476 tegra_plane_writel(p, value, DC_WIN_SET_PARAMS);
477
478 value = OFFSET_X(plane->state->src_y >> 16) |
479 OFFSET_Y(plane->state->src_x >> 16);
480 tegra_plane_writel(p, value, DC_WINBUF_CROPPED_POINT);
481
482 if (dc->soc->supports_block_linear) {
483 unsigned long height = state->tiling.value;
484
485 /* XXX */
486 switch (state->tiling.mode) {
487 case TEGRA_BO_TILING_MODE_PITCH:
488 value = DC_WINBUF_SURFACE_KIND_BLOCK_HEIGHT(0) |
489 DC_WINBUF_SURFACE_KIND_PITCH;
490 break;
491
492 /* XXX not supported on Tegra186 and later */
493 case TEGRA_BO_TILING_MODE_TILED:
494 value = DC_WINBUF_SURFACE_KIND_TILED;
495 break;
496
497 case TEGRA_BO_TILING_MODE_BLOCK:
498 value = DC_WINBUF_SURFACE_KIND_BLOCK_HEIGHT(height) |
499 DC_WINBUF_SURFACE_KIND_BLOCK;
500 break;
501 }
502
503 tegra_plane_writel(p, value, DC_WINBUF_SURFACE_KIND);
504 }
505
506 /* disable gamut CSC */
507 value = tegra_plane_readl(p, DC_WIN_WINDOW_SET_CONTROL);
508 value &= ~CONTROL_CSC_ENABLE;
509 tegra_plane_writel(p, value, DC_WIN_WINDOW_SET_CONTROL);
510
511 pm_runtime_put(dc->dev);
512}
513
514static const struct drm_plane_helper_funcs tegra_shared_plane_helper_funcs = {
515 .atomic_check = tegra_shared_plane_atomic_check,
516 .atomic_update = tegra_shared_plane_atomic_update,
517 .atomic_disable = tegra_shared_plane_atomic_disable,
518};
519
520struct drm_plane *tegra_shared_plane_create(struct drm_device *drm,
521 struct tegra_dc *dc,
522 unsigned int wgrp,
523 unsigned int index)
524{
525 enum drm_plane_type type = DRM_PLANE_TYPE_OVERLAY;
526 struct tegra_drm *tegra = drm->dev_private;
527 struct tegra_display_hub *hub = tegra->hub;
528 /* planes can be assigned to arbitrary CRTCs */
529 unsigned int possible_crtcs = 0x7;
530 struct tegra_shared_plane *plane;
531 unsigned int num_formats;
532 struct drm_plane *p;
533 const u32 *formats;
534 int err;
535
536 plane = kzalloc(sizeof(*plane), GFP_KERNEL);
537 if (!plane)
538 return ERR_PTR(-ENOMEM);
539
540 plane->base.offset = 0x0a00 + 0x0300 * index;
541 plane->base.index = index;
542 plane->base.depth = 0;
543
544 plane->wgrp = &hub->wgrps[wgrp];
545 plane->wgrp->parent = dc->dev;
546
547 p = &plane->base.base;
548
549 num_formats = ARRAY_SIZE(tegra_shared_plane_formats);
550 formats = tegra_shared_plane_formats;
551
552 err = drm_universal_plane_init(drm, p, possible_crtcs,
553 &tegra_plane_funcs, formats,
554 num_formats, NULL, type, NULL);
555 if (err < 0) {
556 kfree(plane);
557 return ERR_PTR(err);
558 }
559
560 drm_plane_helper_add(p, &tegra_shared_plane_helper_funcs);
561
562 return p;
563}
564
565static void tegra_display_hub_update(struct tegra_dc *dc)
566{
567 u32 value;
568
569 pm_runtime_get_sync(dc->dev);
570
571 value = tegra_dc_readl(dc, DC_CMD_IHUB_COMMON_MISC_CTL);
572 value &= ~LATENCY_EVENT;
573 tegra_dc_writel(dc, value, DC_CMD_IHUB_COMMON_MISC_CTL);
574
575 value = tegra_dc_readl(dc, DC_DISP_IHUB_COMMON_DISPLAY_FETCH_METER);
576 value = CURS_SLOTS(1) | WGRP_SLOTS(1);
577 tegra_dc_writel(dc, value, DC_DISP_IHUB_COMMON_DISPLAY_FETCH_METER);
578
579 tegra_dc_writel(dc, COMMON_UPDATE, DC_CMD_STATE_CONTROL);
580 tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
581 tegra_dc_writel(dc, COMMON_ACTREQ, DC_CMD_STATE_CONTROL);
582 tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
583
584 pm_runtime_put(dc->dev);
585}
586
587void tegra_display_hub_atomic_commit(struct drm_device *drm,
588 struct drm_atomic_state *state)
589{
590 struct tegra_atomic_state *s = to_tegra_atomic_state(state);
591 struct tegra_drm *tegra = drm->dev_private;
592 struct tegra_display_hub *hub = tegra->hub;
593 struct device *dev = hub->client.dev;
594 int err;
595
596 if (s->clk_disp) {
597 err = clk_set_rate(s->clk_disp, s->rate);
598 if (err < 0)
599 dev_err(dev, "failed to set rate of %pC to %lu Hz\n",
600 s->clk_disp, s->rate);
601
602 err = clk_set_parent(hub->clk_disp, s->clk_disp);
603 if (err < 0)
604 dev_err(dev, "failed to set parent of %pC to %pC: %d\n",
605 hub->clk_disp, s->clk_disp, err);
606 }
607
608 if (s->dc)
609 tegra_display_hub_update(s->dc);
610}
611
612static int tegra_display_hub_init(struct host1x_client *client)
613{
614 struct tegra_display_hub *hub = to_tegra_display_hub(client);
615 struct drm_device *drm = dev_get_drvdata(client->parent);
616 struct tegra_drm *tegra = drm->dev_private;
617
618 tegra->hub = hub;
619
620 return 0;
621}
622
623static int tegra_display_hub_exit(struct host1x_client *client)
624{
625 struct drm_device *drm = dev_get_drvdata(client->parent);
626 struct tegra_drm *tegra = drm->dev_private;
627
628 tegra->hub = NULL;
629
630 return 0;
631}
632
633static const struct host1x_client_ops tegra_display_hub_ops = {
634 .init = tegra_display_hub_init,
635 .exit = tegra_display_hub_exit,
636};
637
638static int tegra_display_hub_probe(struct platform_device *pdev)
639{
640 struct tegra_display_hub *hub;
641 unsigned int i;
642 int err;
643
644 hub = devm_kzalloc(&pdev->dev, sizeof(*hub), GFP_KERNEL);
645 if (!hub)
646 return -ENOMEM;
647
648 hub->soc = of_device_get_match_data(&pdev->dev);
649
650 hub->clk_disp = devm_clk_get(&pdev->dev, "disp");
651 if (IS_ERR(hub->clk_disp)) {
652 err = PTR_ERR(hub->clk_disp);
653 return err;
654 }
655
656 hub->clk_dsc = devm_clk_get(&pdev->dev, "dsc");
657 if (IS_ERR(hub->clk_dsc)) {
658 err = PTR_ERR(hub->clk_dsc);
659 return err;
660 }
661
662 hub->clk_hub = devm_clk_get(&pdev->dev, "hub");
663 if (IS_ERR(hub->clk_hub)) {
664 err = PTR_ERR(hub->clk_hub);
665 return err;
666 }
667
668 hub->rst = devm_reset_control_get(&pdev->dev, "misc");
669 if (IS_ERR(hub->rst)) {
670 err = PTR_ERR(hub->rst);
671 return err;
672 }
673
674 hub->wgrps = devm_kcalloc(&pdev->dev, hub->soc->num_wgrps,
675 sizeof(*hub->wgrps), GFP_KERNEL);
676 if (!hub->wgrps)
677 return -ENOMEM;
678
679 for (i = 0; i < hub->soc->num_wgrps; i++) {
680 struct tegra_windowgroup *wgrp = &hub->wgrps[i];
681 char id[8];
682
683 snprintf(id, sizeof(id), "wgrp%u", i);
684 mutex_init(&wgrp->lock);
685 wgrp->usecount = 0;
686 wgrp->index = i;
687
688 wgrp->rst = devm_reset_control_get(&pdev->dev, id);
689 if (IS_ERR(wgrp->rst))
690 return PTR_ERR(wgrp->rst);
691
692 err = reset_control_assert(wgrp->rst);
693 if (err < 0)
694 return err;
695 }
696
697 /* XXX: enable clock across reset? */
698 err = reset_control_assert(hub->rst);
699 if (err < 0)
700 return err;
701
702 platform_set_drvdata(pdev, hub);
703 pm_runtime_enable(&pdev->dev);
704
705 INIT_LIST_HEAD(&hub->client.list);
706 hub->client.ops = &tegra_display_hub_ops;
707 hub->client.dev = &pdev->dev;
708
709 err = host1x_client_register(&hub->client);
710 if (err < 0)
711 dev_err(&pdev->dev, "failed to register host1x client: %d\n",
712 err);
713
714 return err;
715}
716
717static int tegra_display_hub_remove(struct platform_device *pdev)
718{
719 struct tegra_display_hub *hub = platform_get_drvdata(pdev);
720 int err;
721
722 err = host1x_client_unregister(&hub->client);
723 if (err < 0) {
724 dev_err(&pdev->dev, "failed to unregister host1x client: %d\n",
725 err);
726 }
727
728 pm_runtime_disable(&pdev->dev);
729
730 return err;
731}
732
733static int tegra_display_hub_suspend(struct device *dev)
734{
735 struct tegra_display_hub *hub = dev_get_drvdata(dev);
736 int err;
737
738 err = reset_control_assert(hub->rst);
739 if (err < 0)
740 return err;
741
742 clk_disable_unprepare(hub->clk_hub);
743 clk_disable_unprepare(hub->clk_dsc);
744 clk_disable_unprepare(hub->clk_disp);
745
746 return 0;
747}
748
749static int tegra_display_hub_resume(struct device *dev)
750{
751 struct tegra_display_hub *hub = dev_get_drvdata(dev);
752 int err;
753
754 err = clk_prepare_enable(hub->clk_disp);
755 if (err < 0)
756 return err;
757
758 err = clk_prepare_enable(hub->clk_dsc);
759 if (err < 0)
760 goto disable_disp;
761
762 err = clk_prepare_enable(hub->clk_hub);
763 if (err < 0)
764 goto disable_dsc;
765
766 err = reset_control_deassert(hub->rst);
767 if (err < 0)
768 goto disable_hub;
769
770 return 0;
771
772disable_hub:
773 clk_disable_unprepare(hub->clk_hub);
774disable_dsc:
775 clk_disable_unprepare(hub->clk_dsc);
776disable_disp:
777 clk_disable_unprepare(hub->clk_disp);
778 return err;
779}
780
781static const struct dev_pm_ops tegra_display_hub_pm_ops = {
782 SET_RUNTIME_PM_OPS(tegra_display_hub_suspend,
783 tegra_display_hub_resume, NULL)
784};
785
786static const struct tegra_display_hub_soc tegra186_display_hub = {
787 .num_wgrps = 6,
788};
789
790static const struct of_device_id tegra_display_hub_of_match[] = {
791 {
792 .compatible = "nvidia,tegra186-display",
793 .data = &tegra186_display_hub
794 }, {
795 /* sentinel */
796 }
797};
798MODULE_DEVICE_TABLE(of, tegra_display_hub_of_match);
799
800struct platform_driver tegra_display_hub_driver = {
801 .driver = {
802 .name = "tegra-display-hub",
803 .of_match_table = tegra_display_hub_of_match,
804 .pm = &tegra_display_hub_pm_ops,
805 },
806 .probe = tegra_display_hub_probe,
807 .remove = tegra_display_hub_remove,
808};