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