hub.c (33525B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Copyright (C) 2017 NVIDIA CORPORATION. All rights reserved. 4 */ 5 6#include <linux/clk.h> 7#include <linux/delay.h> 8#include <linux/host1x.h> 9#include <linux/module.h> 10#include <linux/of.h> 11#include <linux/of_device.h> 12#include <linux/of_graph.h> 13#include <linux/platform_device.h> 14#include <linux/pm_runtime.h> 15#include <linux/reset.h> 16 17#include <drm/drm_atomic.h> 18#include <drm/drm_atomic_helper.h> 19#include <drm/drm_fourcc.h> 20#include <drm/drm_probe_helper.h> 21 22#include "drm.h" 23#include "dc.h" 24#include "plane.h" 25 26#define NFB 24 27 28static const u32 tegra_shared_plane_formats[] = { 29 DRM_FORMAT_ARGB1555, 30 DRM_FORMAT_RGB565, 31 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, 50}; 51 52static const u64 tegra_shared_plane_modifiers[] = { 53 DRM_FORMAT_MOD_LINEAR, 54 DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(0), 55 DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(1), 56 DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(2), 57 DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(3), 58 DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(4), 59 DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(5), 60 /* 61 * The GPU sector layout is only supported on Tegra194, but these will 62 * be filtered out later on by ->format_mod_supported() on SoCs where 63 * it isn't supported. 64 */ 65 DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(0) | DRM_FORMAT_MOD_NVIDIA_SECTOR_LAYOUT, 66 DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(1) | DRM_FORMAT_MOD_NVIDIA_SECTOR_LAYOUT, 67 DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(2) | DRM_FORMAT_MOD_NVIDIA_SECTOR_LAYOUT, 68 DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(3) | DRM_FORMAT_MOD_NVIDIA_SECTOR_LAYOUT, 69 DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(4) | DRM_FORMAT_MOD_NVIDIA_SECTOR_LAYOUT, 70 DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(5) | DRM_FORMAT_MOD_NVIDIA_SECTOR_LAYOUT, 71 /* sentinel */ 72 DRM_FORMAT_MOD_INVALID 73}; 74 75static inline unsigned int tegra_plane_offset(struct tegra_plane *plane, 76 unsigned int offset) 77{ 78 if (offset >= 0x500 && offset <= 0x581) { 79 offset = 0x000 + (offset - 0x500); 80 return plane->offset + offset; 81 } 82 83 if (offset >= 0x700 && offset <= 0x73c) { 84 offset = 0x180 + (offset - 0x700); 85 return plane->offset + offset; 86 } 87 88 if (offset >= 0x800 && offset <= 0x83e) { 89 offset = 0x1c0 + (offset - 0x800); 90 return plane->offset + offset; 91 } 92 93 dev_WARN(plane->dc->dev, "invalid offset: %x\n", offset); 94 95 return plane->offset + offset; 96} 97 98static inline u32 tegra_plane_readl(struct tegra_plane *plane, 99 unsigned int offset) 100{ 101 return tegra_dc_readl(plane->dc, tegra_plane_offset(plane, offset)); 102} 103 104static inline void tegra_plane_writel(struct tegra_plane *plane, u32 value, 105 unsigned int offset) 106{ 107 tegra_dc_writel(plane->dc, value, tegra_plane_offset(plane, offset)); 108} 109 110static int tegra_windowgroup_enable(struct tegra_windowgroup *wgrp) 111{ 112 int err = 0; 113 114 mutex_lock(&wgrp->lock); 115 116 if (wgrp->usecount == 0) { 117 err = host1x_client_resume(wgrp->parent); 118 if (err < 0) { 119 dev_err(wgrp->parent->dev, "failed to resume: %d\n", err); 120 goto unlock; 121 } 122 123 reset_control_deassert(wgrp->rst); 124 } 125 126 wgrp->usecount++; 127 128unlock: 129 mutex_unlock(&wgrp->lock); 130 return err; 131} 132 133static void tegra_windowgroup_disable(struct tegra_windowgroup *wgrp) 134{ 135 int err; 136 137 mutex_lock(&wgrp->lock); 138 139 if (wgrp->usecount == 1) { 140 err = reset_control_assert(wgrp->rst); 141 if (err < 0) { 142 pr_err("failed to assert reset for window group %u\n", 143 wgrp->index); 144 } 145 146 host1x_client_suspend(wgrp->parent); 147 } 148 149 wgrp->usecount--; 150 mutex_unlock(&wgrp->lock); 151} 152 153int tegra_display_hub_prepare(struct tegra_display_hub *hub) 154{ 155 unsigned int i; 156 157 /* 158 * XXX Enabling/disabling windowgroups needs to happen when the owner 159 * display controller is disabled. There's currently no good point at 160 * which this could be executed, so unconditionally enable all window 161 * groups for now. 162 */ 163 for (i = 0; i < hub->soc->num_wgrps; i++) { 164 struct tegra_windowgroup *wgrp = &hub->wgrps[i]; 165 166 /* Skip orphaned window group whose parent DC is disabled */ 167 if (wgrp->parent) 168 tegra_windowgroup_enable(wgrp); 169 } 170 171 return 0; 172} 173 174void tegra_display_hub_cleanup(struct tegra_display_hub *hub) 175{ 176 unsigned int i; 177 178 /* 179 * XXX Remove this once window groups can be more fine-grainedly 180 * enabled and disabled. 181 */ 182 for (i = 0; i < hub->soc->num_wgrps; i++) { 183 struct tegra_windowgroup *wgrp = &hub->wgrps[i]; 184 185 /* Skip orphaned window group whose parent DC is disabled */ 186 if (wgrp->parent) 187 tegra_windowgroup_disable(wgrp); 188 } 189} 190 191static void tegra_shared_plane_update(struct tegra_plane *plane) 192{ 193 struct tegra_dc *dc = plane->dc; 194 unsigned long timeout; 195 u32 mask, value; 196 197 mask = COMMON_UPDATE | WIN_A_UPDATE << plane->base.index; 198 tegra_dc_writel(dc, mask, DC_CMD_STATE_CONTROL); 199 200 timeout = jiffies + msecs_to_jiffies(1000); 201 202 while (time_before(jiffies, timeout)) { 203 value = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); 204 if ((value & mask) == 0) 205 break; 206 207 usleep_range(100, 400); 208 } 209} 210 211static void tegra_shared_plane_activate(struct tegra_plane *plane) 212{ 213 struct tegra_dc *dc = plane->dc; 214 unsigned long timeout; 215 u32 mask, value; 216 217 mask = COMMON_ACTREQ | WIN_A_ACT_REQ << plane->base.index; 218 tegra_dc_writel(dc, mask, DC_CMD_STATE_CONTROL); 219 220 timeout = jiffies + msecs_to_jiffies(1000); 221 222 while (time_before(jiffies, timeout)) { 223 value = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); 224 if ((value & mask) == 0) 225 break; 226 227 usleep_range(100, 400); 228 } 229} 230 231static unsigned int 232tegra_shared_plane_get_owner(struct tegra_plane *plane, struct tegra_dc *dc) 233{ 234 unsigned int offset = 235 tegra_plane_offset(plane, DC_WIN_CORE_WINDOWGROUP_SET_CONTROL); 236 237 return tegra_dc_readl(dc, offset) & OWNER_MASK; 238} 239 240static bool tegra_dc_owns_shared_plane(struct tegra_dc *dc, 241 struct tegra_plane *plane) 242{ 243 struct device *dev = dc->dev; 244 245 if (tegra_shared_plane_get_owner(plane, dc) == dc->pipe) { 246 if (plane->dc == dc) 247 return true; 248 249 dev_WARN(dev, "head %u owns window %u but is not attached\n", 250 dc->pipe, plane->index); 251 } 252 253 return false; 254} 255 256static int tegra_shared_plane_set_owner(struct tegra_plane *plane, 257 struct tegra_dc *new) 258{ 259 unsigned int offset = 260 tegra_plane_offset(plane, DC_WIN_CORE_WINDOWGROUP_SET_CONTROL); 261 struct tegra_dc *old = plane->dc, *dc = new ? new : old; 262 struct device *dev = new ? new->dev : old->dev; 263 unsigned int owner, index = plane->index; 264 u32 value; 265 266 value = tegra_dc_readl(dc, offset); 267 owner = value & OWNER_MASK; 268 269 if (new && (owner != OWNER_MASK && owner != new->pipe)) { 270 dev_WARN(dev, "window %u owned by head %u\n", index, owner); 271 return -EBUSY; 272 } 273 274 /* 275 * This seems to happen whenever the head has been disabled with one 276 * or more windows being active. This is harmless because we'll just 277 * reassign the window to the new head anyway. 278 */ 279 if (old && owner == OWNER_MASK) 280 dev_dbg(dev, "window %u not owned by head %u but %u\n", index, 281 old->pipe, owner); 282 283 value &= ~OWNER_MASK; 284 285 if (new) 286 value |= OWNER(new->pipe); 287 else 288 value |= OWNER_MASK; 289 290 tegra_dc_writel(dc, value, offset); 291 292 plane->dc = new; 293 294 return 0; 295} 296 297static void tegra_shared_plane_setup_scaler(struct tegra_plane *plane) 298{ 299 static const unsigned int coeffs[192] = { 300 0x00000000, 0x3c70e400, 0x3bb037e4, 0x0c51cc9c, 301 0x00100001, 0x3bf0dbfa, 0x3d00f406, 0x3fe003ff, 302 0x00300002, 0x3b80cbf5, 0x3da1040d, 0x3fb003fe, 303 0x00400002, 0x3b20bff1, 0x3e511015, 0x3f9003fc, 304 0x00500002, 0x3ad0b3ed, 0x3f21201d, 0x3f5003fb, 305 0x00500003, 0x3aa0a3e9, 0x3ff13026, 0x3f2007f9, 306 0x00500403, 0x3a7097e6, 0x00e1402f, 0x3ee007f7, 307 0x00500403, 0x3a608be4, 0x01d14c38, 0x3ea00bf6, 308 0x00500403, 0x3a507fe2, 0x02e15c42, 0x3e500ff4, 309 0x00500402, 0x3a6073e1, 0x03f16c4d, 0x3e000ff2, 310 0x00400402, 0x3a706be0, 0x05117858, 0x3db013f0, 311 0x00300402, 0x3a905fe0, 0x06318863, 0x3d6017ee, 312 0x00300402, 0x3ab057e0, 0x0771986e, 0x3d001beb, 313 0x00200001, 0x3af04fe1, 0x08a1a47a, 0x3cb023e9, 314 0x00100001, 0x3b2047e2, 0x09e1b485, 0x3c6027e7, 315 0x00100000, 0x3b703fe2, 0x0b11c091, 0x3c002fe6, 316 0x3f203800, 0x0391103f, 0x3ff0a014, 0x0811606c, 317 0x3f2037ff, 0x0351083c, 0x03e11842, 0x3f203c00, 318 0x3f302fff, 0x03010439, 0x04311c45, 0x3f104401, 319 0x3f302fff, 0x02c0fc35, 0x04812448, 0x3f104802, 320 0x3f4027ff, 0x0270f832, 0x04c1284b, 0x3f205003, 321 0x3f4023ff, 0x0230f030, 0x0511304e, 0x3f205403, 322 0x3f601fff, 0x01f0e82d, 0x05613451, 0x3f205c04, 323 0x3f701bfe, 0x01b0e02a, 0x05a13c54, 0x3f306006, 324 0x3f7017fe, 0x0170d827, 0x05f14057, 0x3f406807, 325 0x3f8017ff, 0x0140d424, 0x0641445a, 0x3f406c08, 326 0x3fa013ff, 0x0100cc22, 0x0681485d, 0x3f507409, 327 0x3fa00fff, 0x00d0c41f, 0x06d14c60, 0x3f607c0b, 328 0x3fc00fff, 0x0090bc1c, 0x07115063, 0x3f80840c, 329 0x3fd00bff, 0x0070b41a, 0x07515465, 0x3f908c0e, 330 0x3fe007ff, 0x0040b018, 0x07915868, 0x3fb0900f, 331 0x3ff00400, 0x0010a816, 0x07d15c6a, 0x3fd09811, 332 0x00a04c0e, 0x0460f442, 0x0240a827, 0x05c15859, 333 0x0090440d, 0x0440f040, 0x0480fc43, 0x00b05010, 334 0x0080400c, 0x0410ec3e, 0x04910044, 0x00d05411, 335 0x0070380b, 0x03f0e83d, 0x04b10846, 0x00e05812, 336 0x0060340a, 0x03d0e43b, 0x04d10c48, 0x00f06013, 337 0x00503009, 0x03b0e039, 0x04e11449, 0x01106415, 338 0x00402c08, 0x0390d838, 0x05011c4b, 0x01206c16, 339 0x00302807, 0x0370d436, 0x0511204c, 0x01407018, 340 0x00302406, 0x0340d034, 0x0531244e, 0x01507419, 341 0x00202005, 0x0320cc32, 0x05412c50, 0x01707c1b, 342 0x00101c04, 0x0300c431, 0x05613451, 0x0180801d, 343 0x00101803, 0x02e0c02f, 0x05713853, 0x01a0881e, 344 0x00101002, 0x02b0bc2d, 0x05814054, 0x01c08c20, 345 0x00000c02, 0x02a0b82c, 0x05914455, 0x01e09421, 346 0x00000801, 0x0280b02a, 0x05a14c57, 0x02009c23, 347 0x00000400, 0x0260ac28, 0x05b15458, 0x0220a025, 348 }; 349 unsigned int ratio, row, column; 350 351 for (ratio = 0; ratio <= 2; ratio++) { 352 for (row = 0; row <= 15; row++) { 353 for (column = 0; column <= 3; column++) { 354 unsigned int index = (ratio << 6) + (row << 2) + column; 355 u32 value; 356 357 value = COEFF_INDEX(index) | COEFF_DATA(coeffs[index]); 358 tegra_plane_writel(plane, value, 359 DC_WIN_WINDOWGROUP_SET_INPUT_SCALER_COEFF); 360 } 361 } 362 } 363} 364 365static void tegra_dc_assign_shared_plane(struct tegra_dc *dc, 366 struct tegra_plane *plane) 367{ 368 u32 value; 369 int err; 370 371 if (!tegra_dc_owns_shared_plane(dc, plane)) { 372 err = tegra_shared_plane_set_owner(plane, dc); 373 if (err < 0) 374 return; 375 } 376 377 value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_LINEBUF_CONFIG); 378 value |= MODE_FOUR_LINES; 379 tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_LINEBUF_CONFIG); 380 381 value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_FETCH_METER); 382 value = SLOTS(1); 383 tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_FETCH_METER); 384 385 /* disable watermark */ 386 value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLA); 387 value &= ~LATENCY_CTL_MODE_ENABLE; 388 tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLA); 389 390 value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLB); 391 value |= WATERMARK_MASK; 392 tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLB); 393 394 /* pipe meter */ 395 value = tegra_plane_readl(plane, DC_WIN_CORE_PRECOMP_WGRP_PIPE_METER); 396 value = PIPE_METER_INT(0) | PIPE_METER_FRAC(0); 397 tegra_plane_writel(plane, value, DC_WIN_CORE_PRECOMP_WGRP_PIPE_METER); 398 399 /* mempool entries */ 400 value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_POOL_CONFIG); 401 value = MEMPOOL_ENTRIES(0x331); 402 tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_POOL_CONFIG); 403 404 value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_THREAD_GROUP); 405 value &= ~THREAD_NUM_MASK; 406 value |= THREAD_NUM(plane->base.index); 407 value |= THREAD_GROUP_ENABLE; 408 tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_THREAD_GROUP); 409 410 tegra_shared_plane_setup_scaler(plane); 411 412 tegra_shared_plane_update(plane); 413 tegra_shared_plane_activate(plane); 414} 415 416static void tegra_dc_remove_shared_plane(struct tegra_dc *dc, 417 struct tegra_plane *plane) 418{ 419 tegra_shared_plane_set_owner(plane, NULL); 420} 421 422static int tegra_shared_plane_atomic_check(struct drm_plane *plane, 423 struct drm_atomic_state *state) 424{ 425 struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, 426 plane); 427 struct tegra_plane_state *plane_state = to_tegra_plane_state(new_plane_state); 428 struct tegra_shared_plane *tegra = to_tegra_shared_plane(plane); 429 struct tegra_bo_tiling *tiling = &plane_state->tiling; 430 struct tegra_dc *dc = to_tegra_dc(new_plane_state->crtc); 431 int err; 432 433 /* no need for further checks if the plane is being disabled */ 434 if (!new_plane_state->crtc || !new_plane_state->fb) 435 return 0; 436 437 err = tegra_plane_format(new_plane_state->fb->format->format, 438 &plane_state->format, 439 &plane_state->swap); 440 if (err < 0) 441 return err; 442 443 err = tegra_fb_get_tiling(new_plane_state->fb, tiling); 444 if (err < 0) 445 return err; 446 447 if (tiling->mode == TEGRA_BO_TILING_MODE_BLOCK && 448 !dc->soc->supports_block_linear) { 449 DRM_ERROR("hardware doesn't support block linear mode\n"); 450 return -EINVAL; 451 } 452 453 if (tiling->sector_layout == TEGRA_BO_SECTOR_LAYOUT_GPU && 454 !dc->soc->supports_sector_layout) { 455 DRM_ERROR("hardware doesn't support GPU sector layout\n"); 456 return -EINVAL; 457 } 458 459 /* 460 * Tegra doesn't support different strides for U and V planes so we 461 * error out if the user tries to display a framebuffer with such a 462 * configuration. 463 */ 464 if (new_plane_state->fb->format->num_planes > 2) { 465 if (new_plane_state->fb->pitches[2] != new_plane_state->fb->pitches[1]) { 466 DRM_ERROR("unsupported UV-plane configuration\n"); 467 return -EINVAL; 468 } 469 } 470 471 /* XXX scaling is not yet supported, add a check here */ 472 473 err = tegra_plane_state_add(&tegra->base, new_plane_state); 474 if (err < 0) 475 return err; 476 477 return 0; 478} 479 480static void tegra_shared_plane_atomic_disable(struct drm_plane *plane, 481 struct drm_atomic_state *state) 482{ 483 struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, 484 plane); 485 struct tegra_plane *p = to_tegra_plane(plane); 486 struct tegra_dc *dc; 487 u32 value; 488 int err; 489 490 /* rien ne va plus */ 491 if (!old_state || !old_state->crtc) 492 return; 493 494 dc = to_tegra_dc(old_state->crtc); 495 496 err = host1x_client_resume(&dc->client); 497 if (err < 0) { 498 dev_err(dc->dev, "failed to resume: %d\n", err); 499 return; 500 } 501 502 /* 503 * XXX Legacy helpers seem to sometimes call ->atomic_disable() even 504 * on planes that are already disabled. Make sure we fallback to the 505 * head for this particular state instead of crashing. 506 */ 507 if (WARN_ON(p->dc == NULL)) 508 p->dc = dc; 509 510 value = tegra_plane_readl(p, DC_WIN_WIN_OPTIONS); 511 value &= ~WIN_ENABLE; 512 tegra_plane_writel(p, value, DC_WIN_WIN_OPTIONS); 513 514 tegra_dc_remove_shared_plane(dc, p); 515 516 host1x_client_suspend(&dc->client); 517} 518 519static inline u32 compute_phase_incr(fixed20_12 in, unsigned int out) 520{ 521 u64 tmp, tmp1, tmp2; 522 523 tmp = (u64)dfixed_trunc(in); 524 tmp2 = (u64)out; 525 tmp1 = (tmp << NFB) + (tmp2 >> 1); 526 do_div(tmp1, tmp2); 527 528 return lower_32_bits(tmp1); 529} 530 531static void tegra_shared_plane_atomic_update(struct drm_plane *plane, 532 struct drm_atomic_state *state) 533{ 534 struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, 535 plane); 536 struct tegra_plane_state *tegra_plane_state = to_tegra_plane_state(new_state); 537 struct tegra_dc *dc = to_tegra_dc(new_state->crtc); 538 unsigned int zpos = new_state->normalized_zpos; 539 struct drm_framebuffer *fb = new_state->fb; 540 struct tegra_plane *p = to_tegra_plane(plane); 541 u32 value, min_width, bypass = 0; 542 dma_addr_t base, addr_flag = 0; 543 unsigned int bpc, planes; 544 bool yuv; 545 int err; 546 547 /* rien ne va plus */ 548 if (!new_state->crtc || !new_state->fb) 549 return; 550 551 if (!new_state->visible) { 552 tegra_shared_plane_atomic_disable(plane, state); 553 return; 554 } 555 556 err = host1x_client_resume(&dc->client); 557 if (err < 0) { 558 dev_err(dc->dev, "failed to resume: %d\n", err); 559 return; 560 } 561 562 yuv = tegra_plane_format_is_yuv(tegra_plane_state->format, &planes, &bpc); 563 564 tegra_dc_assign_shared_plane(dc, p); 565 566 tegra_plane_writel(p, VCOUNTER, DC_WIN_CORE_ACT_CONTROL); 567 568 /* blending */ 569 value = BLEND_FACTOR_DST_ALPHA_ZERO | BLEND_FACTOR_SRC_ALPHA_K2 | 570 BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_SRC | 571 BLEND_FACTOR_SRC_COLOR_K1_TIMES_SRC; 572 tegra_plane_writel(p, value, DC_WIN_BLEND_MATCH_SELECT); 573 574 value = BLEND_FACTOR_DST_ALPHA_ZERO | BLEND_FACTOR_SRC_ALPHA_K2 | 575 BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_SRC | 576 BLEND_FACTOR_SRC_COLOR_K1_TIMES_SRC; 577 tegra_plane_writel(p, value, DC_WIN_BLEND_NOMATCH_SELECT); 578 579 value = K2(255) | K1(255) | WINDOW_LAYER_DEPTH(255 - zpos); 580 tegra_plane_writel(p, value, DC_WIN_BLEND_LAYER_CONTROL); 581 582 /* scaling */ 583 min_width = min(new_state->src_w >> 16, new_state->crtc_w); 584 585 value = tegra_plane_readl(p, DC_WINC_PRECOMP_WGRP_PIPE_CAPC); 586 587 if (min_width < MAX_PIXELS_5TAP444(value)) { 588 value = HORIZONTAL_TAPS_5 | VERTICAL_TAPS_5; 589 } else { 590 value = tegra_plane_readl(p, DC_WINC_PRECOMP_WGRP_PIPE_CAPE); 591 592 if (min_width < MAX_PIXELS_2TAP444(value)) 593 value = HORIZONTAL_TAPS_2 | VERTICAL_TAPS_2; 594 else 595 dev_err(dc->dev, "invalid minimum width: %u\n", min_width); 596 } 597 598 value = HORIZONTAL_TAPS_5 | VERTICAL_TAPS_5; 599 tegra_plane_writel(p, value, DC_WIN_WINDOWGROUP_SET_CONTROL_INPUT_SCALER); 600 601 if (new_state->src_w != new_state->crtc_w << 16) { 602 fixed20_12 width = dfixed_init(new_state->src_w >> 16); 603 u32 incr = compute_phase_incr(width, new_state->crtc_w) & ~0x1; 604 u32 init = (1 << (NFB - 1)) + (incr >> 1); 605 606 tegra_plane_writel(p, incr, DC_WIN_SET_INPUT_SCALER_HPHASE_INCR); 607 tegra_plane_writel(p, init, DC_WIN_SET_INPUT_SCALER_H_START_PHASE); 608 } else { 609 bypass |= INPUT_SCALER_HBYPASS; 610 } 611 612 if (new_state->src_h != new_state->crtc_h << 16) { 613 fixed20_12 height = dfixed_init(new_state->src_h >> 16); 614 u32 incr = compute_phase_incr(height, new_state->crtc_h) & ~0x1; 615 u32 init = (1 << (NFB - 1)) + (incr >> 1); 616 617 tegra_plane_writel(p, incr, DC_WIN_SET_INPUT_SCALER_VPHASE_INCR); 618 tegra_plane_writel(p, init, DC_WIN_SET_INPUT_SCALER_V_START_PHASE); 619 } else { 620 bypass |= INPUT_SCALER_VBYPASS; 621 } 622 623 tegra_plane_writel(p, bypass, DC_WIN_WINDOWGROUP_SET_INPUT_SCALER_USAGE); 624 625 /* disable compression */ 626 tegra_plane_writel(p, 0, DC_WINBUF_CDE_CONTROL); 627 628#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT 629 /* 630 * Physical address bit 39 in Tegra194 is used as a switch for special 631 * logic that swizzles the memory using either the legacy Tegra or the 632 * dGPU sector layout. 633 */ 634 if (tegra_plane_state->tiling.sector_layout == TEGRA_BO_SECTOR_LAYOUT_GPU) 635 addr_flag = BIT_ULL(39); 636#endif 637 638 base = tegra_plane_state->iova[0] + fb->offsets[0]; 639 base |= addr_flag; 640 641 tegra_plane_writel(p, tegra_plane_state->format, DC_WIN_COLOR_DEPTH); 642 tegra_plane_writel(p, 0, DC_WIN_PRECOMP_WGRP_PARAMS); 643 644 value = V_POSITION(new_state->crtc_y) | 645 H_POSITION(new_state->crtc_x); 646 tegra_plane_writel(p, value, DC_WIN_POSITION); 647 648 value = V_SIZE(new_state->crtc_h) | H_SIZE(new_state->crtc_w); 649 tegra_plane_writel(p, value, DC_WIN_SIZE); 650 651 value = WIN_ENABLE | COLOR_EXPAND; 652 tegra_plane_writel(p, value, DC_WIN_WIN_OPTIONS); 653 654 value = V_SIZE(new_state->src_h >> 16) | H_SIZE(new_state->src_w >> 16); 655 tegra_plane_writel(p, value, DC_WIN_CROPPED_SIZE); 656 657 tegra_plane_writel(p, upper_32_bits(base), DC_WINBUF_START_ADDR_HI); 658 tegra_plane_writel(p, lower_32_bits(base), DC_WINBUF_START_ADDR); 659 660 value = PITCH(fb->pitches[0]); 661 tegra_plane_writel(p, value, DC_WIN_PLANAR_STORAGE); 662 663 if (yuv && planes > 1) { 664 base = tegra_plane_state->iova[1] + fb->offsets[1]; 665 base |= addr_flag; 666 667 tegra_plane_writel(p, upper_32_bits(base), DC_WINBUF_START_ADDR_HI_U); 668 tegra_plane_writel(p, lower_32_bits(base), DC_WINBUF_START_ADDR_U); 669 670 if (planes > 2) { 671 base = tegra_plane_state->iova[2] + fb->offsets[2]; 672 base |= addr_flag; 673 674 tegra_plane_writel(p, upper_32_bits(base), DC_WINBUF_START_ADDR_HI_V); 675 tegra_plane_writel(p, lower_32_bits(base), DC_WINBUF_START_ADDR_V); 676 } 677 678 value = PITCH_U(fb->pitches[1]); 679 680 if (planes > 2) 681 value |= PITCH_V(fb->pitches[2]); 682 683 tegra_plane_writel(p, value, DC_WIN_PLANAR_STORAGE_UV); 684 } else { 685 tegra_plane_writel(p, 0, DC_WINBUF_START_ADDR_U); 686 tegra_plane_writel(p, 0, DC_WINBUF_START_ADDR_HI_U); 687 tegra_plane_writel(p, 0, DC_WINBUF_START_ADDR_V); 688 tegra_plane_writel(p, 0, DC_WINBUF_START_ADDR_HI_V); 689 tegra_plane_writel(p, 0, DC_WIN_PLANAR_STORAGE_UV); 690 } 691 692 value = CLAMP_BEFORE_BLEND | INPUT_RANGE_FULL; 693 694 if (yuv) { 695 if (bpc < 12) 696 value |= DEGAMMA_YUV8_10; 697 else 698 value |= DEGAMMA_YUV12; 699 700 /* XXX parameterize */ 701 value |= COLOR_SPACE_YUV_2020; 702 } else { 703 if (!tegra_plane_format_is_indexed(tegra_plane_state->format)) 704 value |= DEGAMMA_SRGB; 705 } 706 707 tegra_plane_writel(p, value, DC_WIN_SET_PARAMS); 708 709 value = OFFSET_X(new_state->src_y >> 16) | 710 OFFSET_Y(new_state->src_x >> 16); 711 tegra_plane_writel(p, value, DC_WINBUF_CROPPED_POINT); 712 713 if (dc->soc->supports_block_linear) { 714 unsigned long height = tegra_plane_state->tiling.value; 715 716 /* XXX */ 717 switch (tegra_plane_state->tiling.mode) { 718 case TEGRA_BO_TILING_MODE_PITCH: 719 value = DC_WINBUF_SURFACE_KIND_BLOCK_HEIGHT(0) | 720 DC_WINBUF_SURFACE_KIND_PITCH; 721 break; 722 723 /* XXX not supported on Tegra186 and later */ 724 case TEGRA_BO_TILING_MODE_TILED: 725 value = DC_WINBUF_SURFACE_KIND_TILED; 726 break; 727 728 case TEGRA_BO_TILING_MODE_BLOCK: 729 value = DC_WINBUF_SURFACE_KIND_BLOCK_HEIGHT(height) | 730 DC_WINBUF_SURFACE_KIND_BLOCK; 731 break; 732 } 733 734 tegra_plane_writel(p, value, DC_WINBUF_SURFACE_KIND); 735 } 736 737 /* disable gamut CSC */ 738 value = tegra_plane_readl(p, DC_WIN_WINDOW_SET_CONTROL); 739 value &= ~CONTROL_CSC_ENABLE; 740 tegra_plane_writel(p, value, DC_WIN_WINDOW_SET_CONTROL); 741 742 host1x_client_suspend(&dc->client); 743} 744 745static const struct drm_plane_helper_funcs tegra_shared_plane_helper_funcs = { 746 .prepare_fb = tegra_plane_prepare_fb, 747 .cleanup_fb = tegra_plane_cleanup_fb, 748 .atomic_check = tegra_shared_plane_atomic_check, 749 .atomic_update = tegra_shared_plane_atomic_update, 750 .atomic_disable = tegra_shared_plane_atomic_disable, 751}; 752 753struct drm_plane *tegra_shared_plane_create(struct drm_device *drm, 754 struct tegra_dc *dc, 755 unsigned int wgrp, 756 unsigned int index) 757{ 758 enum drm_plane_type type = DRM_PLANE_TYPE_OVERLAY; 759 struct tegra_drm *tegra = drm->dev_private; 760 struct tegra_display_hub *hub = tegra->hub; 761 struct tegra_shared_plane *plane; 762 unsigned int possible_crtcs; 763 unsigned int num_formats; 764 const u64 *modifiers; 765 struct drm_plane *p; 766 const u32 *formats; 767 int err; 768 769 plane = kzalloc(sizeof(*plane), GFP_KERNEL); 770 if (!plane) 771 return ERR_PTR(-ENOMEM); 772 773 plane->base.offset = 0x0a00 + 0x0300 * index; 774 plane->base.index = index; 775 776 plane->wgrp = &hub->wgrps[wgrp]; 777 plane->wgrp->parent = &dc->client; 778 779 p = &plane->base.base; 780 781 /* planes can be assigned to arbitrary CRTCs */ 782 possible_crtcs = BIT(tegra->num_crtcs) - 1; 783 784 num_formats = ARRAY_SIZE(tegra_shared_plane_formats); 785 formats = tegra_shared_plane_formats; 786 modifiers = tegra_shared_plane_modifiers; 787 788 err = drm_universal_plane_init(drm, p, possible_crtcs, 789 &tegra_plane_funcs, formats, 790 num_formats, modifiers, type, NULL); 791 if (err < 0) { 792 kfree(plane); 793 return ERR_PTR(err); 794 } 795 796 drm_plane_helper_add(p, &tegra_shared_plane_helper_funcs); 797 drm_plane_create_zpos_property(p, 0, 0, 255); 798 799 return p; 800} 801 802static struct drm_private_state * 803tegra_display_hub_duplicate_state(struct drm_private_obj *obj) 804{ 805 struct tegra_display_hub_state *state; 806 807 state = kmemdup(obj->state, sizeof(*state), GFP_KERNEL); 808 if (!state) 809 return NULL; 810 811 __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base); 812 813 return &state->base; 814} 815 816static void tegra_display_hub_destroy_state(struct drm_private_obj *obj, 817 struct drm_private_state *state) 818{ 819 struct tegra_display_hub_state *hub_state = 820 to_tegra_display_hub_state(state); 821 822 kfree(hub_state); 823} 824 825static const struct drm_private_state_funcs tegra_display_hub_state_funcs = { 826 .atomic_duplicate_state = tegra_display_hub_duplicate_state, 827 .atomic_destroy_state = tegra_display_hub_destroy_state, 828}; 829 830static struct tegra_display_hub_state * 831tegra_display_hub_get_state(struct tegra_display_hub *hub, 832 struct drm_atomic_state *state) 833{ 834 struct drm_private_state *priv; 835 836 priv = drm_atomic_get_private_obj_state(state, &hub->base); 837 if (IS_ERR(priv)) 838 return ERR_CAST(priv); 839 840 return to_tegra_display_hub_state(priv); 841} 842 843int tegra_display_hub_atomic_check(struct drm_device *drm, 844 struct drm_atomic_state *state) 845{ 846 struct tegra_drm *tegra = drm->dev_private; 847 struct tegra_display_hub_state *hub_state; 848 struct drm_crtc_state *old, *new; 849 struct drm_crtc *crtc; 850 unsigned int i; 851 852 if (!tegra->hub) 853 return 0; 854 855 hub_state = tegra_display_hub_get_state(tegra->hub, state); 856 if (IS_ERR(hub_state)) 857 return PTR_ERR(hub_state); 858 859 /* 860 * The display hub display clock needs to be fed by the display clock 861 * with the highest frequency to ensure proper functioning of all the 862 * displays. 863 * 864 * Note that this isn't used before Tegra186, but it doesn't hurt and 865 * conditionalizing it would make the code less clean. 866 */ 867 for_each_oldnew_crtc_in_state(state, crtc, old, new, i) { 868 struct tegra_dc_state *dc = to_dc_state(new); 869 870 if (new->active) { 871 if (!hub_state->clk || dc->pclk > hub_state->rate) { 872 hub_state->dc = to_tegra_dc(dc->base.crtc); 873 hub_state->clk = hub_state->dc->clk; 874 hub_state->rate = dc->pclk; 875 } 876 } 877 } 878 879 return 0; 880} 881 882static void tegra_display_hub_update(struct tegra_dc *dc) 883{ 884 u32 value; 885 int err; 886 887 err = host1x_client_resume(&dc->client); 888 if (err < 0) { 889 dev_err(dc->dev, "failed to resume: %d\n", err); 890 return; 891 } 892 893 value = tegra_dc_readl(dc, DC_CMD_IHUB_COMMON_MISC_CTL); 894 value &= ~LATENCY_EVENT; 895 tegra_dc_writel(dc, value, DC_CMD_IHUB_COMMON_MISC_CTL); 896 897 value = tegra_dc_readl(dc, DC_DISP_IHUB_COMMON_DISPLAY_FETCH_METER); 898 value = CURS_SLOTS(1) | WGRP_SLOTS(1); 899 tegra_dc_writel(dc, value, DC_DISP_IHUB_COMMON_DISPLAY_FETCH_METER); 900 901 tegra_dc_writel(dc, COMMON_UPDATE, DC_CMD_STATE_CONTROL); 902 tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); 903 tegra_dc_writel(dc, COMMON_ACTREQ, DC_CMD_STATE_CONTROL); 904 tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); 905 906 host1x_client_suspend(&dc->client); 907} 908 909void tegra_display_hub_atomic_commit(struct drm_device *drm, 910 struct drm_atomic_state *state) 911{ 912 struct tegra_drm *tegra = drm->dev_private; 913 struct tegra_display_hub *hub = tegra->hub; 914 struct tegra_display_hub_state *hub_state; 915 struct device *dev = hub->client.dev; 916 int err; 917 918 hub_state = to_tegra_display_hub_state(hub->base.state); 919 920 if (hub_state->clk) { 921 err = clk_set_rate(hub_state->clk, hub_state->rate); 922 if (err < 0) 923 dev_err(dev, "failed to set rate of %pC to %lu Hz\n", 924 hub_state->clk, hub_state->rate); 925 926 err = clk_set_parent(hub->clk_disp, hub_state->clk); 927 if (err < 0) 928 dev_err(dev, "failed to set parent of %pC to %pC: %d\n", 929 hub->clk_disp, hub_state->clk, err); 930 } 931 932 if (hub_state->dc) 933 tegra_display_hub_update(hub_state->dc); 934} 935 936static int tegra_display_hub_init(struct host1x_client *client) 937{ 938 struct tegra_display_hub *hub = to_tegra_display_hub(client); 939 struct drm_device *drm = dev_get_drvdata(client->host); 940 struct tegra_drm *tegra = drm->dev_private; 941 struct tegra_display_hub_state *state; 942 943 state = kzalloc(sizeof(*state), GFP_KERNEL); 944 if (!state) 945 return -ENOMEM; 946 947 drm_atomic_private_obj_init(drm, &hub->base, &state->base, 948 &tegra_display_hub_state_funcs); 949 950 tegra->hub = hub; 951 952 return 0; 953} 954 955static int tegra_display_hub_exit(struct host1x_client *client) 956{ 957 struct drm_device *drm = dev_get_drvdata(client->host); 958 struct tegra_drm *tegra = drm->dev_private; 959 960 drm_atomic_private_obj_fini(&tegra->hub->base); 961 tegra->hub = NULL; 962 963 return 0; 964} 965 966static int tegra_display_hub_runtime_suspend(struct host1x_client *client) 967{ 968 struct tegra_display_hub *hub = to_tegra_display_hub(client); 969 struct device *dev = client->dev; 970 unsigned int i = hub->num_heads; 971 int err; 972 973 err = reset_control_assert(hub->rst); 974 if (err < 0) 975 return err; 976 977 while (i--) 978 clk_disable_unprepare(hub->clk_heads[i]); 979 980 clk_disable_unprepare(hub->clk_hub); 981 clk_disable_unprepare(hub->clk_dsc); 982 clk_disable_unprepare(hub->clk_disp); 983 984 pm_runtime_put_sync(dev); 985 986 return 0; 987} 988 989static int tegra_display_hub_runtime_resume(struct host1x_client *client) 990{ 991 struct tegra_display_hub *hub = to_tegra_display_hub(client); 992 struct device *dev = client->dev; 993 unsigned int i; 994 int err; 995 996 err = pm_runtime_resume_and_get(dev); 997 if (err < 0) { 998 dev_err(dev, "failed to get runtime PM: %d\n", err); 999 return err; 1000 } 1001 1002 err = clk_prepare_enable(hub->clk_disp); 1003 if (err < 0) 1004 goto put_rpm; 1005 1006 err = clk_prepare_enable(hub->clk_dsc); 1007 if (err < 0) 1008 goto disable_disp; 1009 1010 err = clk_prepare_enable(hub->clk_hub); 1011 if (err < 0) 1012 goto disable_dsc; 1013 1014 for (i = 0; i < hub->num_heads; i++) { 1015 err = clk_prepare_enable(hub->clk_heads[i]); 1016 if (err < 0) 1017 goto disable_heads; 1018 } 1019 1020 err = reset_control_deassert(hub->rst); 1021 if (err < 0) 1022 goto disable_heads; 1023 1024 return 0; 1025 1026disable_heads: 1027 while (i--) 1028 clk_disable_unprepare(hub->clk_heads[i]); 1029 1030 clk_disable_unprepare(hub->clk_hub); 1031disable_dsc: 1032 clk_disable_unprepare(hub->clk_dsc); 1033disable_disp: 1034 clk_disable_unprepare(hub->clk_disp); 1035put_rpm: 1036 pm_runtime_put_sync(dev); 1037 return err; 1038} 1039 1040static const struct host1x_client_ops tegra_display_hub_ops = { 1041 .init = tegra_display_hub_init, 1042 .exit = tegra_display_hub_exit, 1043 .suspend = tegra_display_hub_runtime_suspend, 1044 .resume = tegra_display_hub_runtime_resume, 1045}; 1046 1047static int tegra_display_hub_probe(struct platform_device *pdev) 1048{ 1049 u64 dma_mask = dma_get_mask(pdev->dev.parent); 1050 struct device_node *child = NULL; 1051 struct tegra_display_hub *hub; 1052 struct clk *clk; 1053 unsigned int i; 1054 int err; 1055 1056 err = dma_coerce_mask_and_coherent(&pdev->dev, dma_mask); 1057 if (err < 0) { 1058 dev_err(&pdev->dev, "failed to set DMA mask: %d\n", err); 1059 return err; 1060 } 1061 1062 hub = devm_kzalloc(&pdev->dev, sizeof(*hub), GFP_KERNEL); 1063 if (!hub) 1064 return -ENOMEM; 1065 1066 hub->soc = of_device_get_match_data(&pdev->dev); 1067 1068 hub->clk_disp = devm_clk_get(&pdev->dev, "disp"); 1069 if (IS_ERR(hub->clk_disp)) { 1070 err = PTR_ERR(hub->clk_disp); 1071 return err; 1072 } 1073 1074 if (hub->soc->supports_dsc) { 1075 hub->clk_dsc = devm_clk_get(&pdev->dev, "dsc"); 1076 if (IS_ERR(hub->clk_dsc)) { 1077 err = PTR_ERR(hub->clk_dsc); 1078 return err; 1079 } 1080 } 1081 1082 hub->clk_hub = devm_clk_get(&pdev->dev, "hub"); 1083 if (IS_ERR(hub->clk_hub)) { 1084 err = PTR_ERR(hub->clk_hub); 1085 return err; 1086 } 1087 1088 hub->rst = devm_reset_control_get(&pdev->dev, "misc"); 1089 if (IS_ERR(hub->rst)) { 1090 err = PTR_ERR(hub->rst); 1091 return err; 1092 } 1093 1094 hub->wgrps = devm_kcalloc(&pdev->dev, hub->soc->num_wgrps, 1095 sizeof(*hub->wgrps), GFP_KERNEL); 1096 if (!hub->wgrps) 1097 return -ENOMEM; 1098 1099 for (i = 0; i < hub->soc->num_wgrps; i++) { 1100 struct tegra_windowgroup *wgrp = &hub->wgrps[i]; 1101 char id[8]; 1102 1103 snprintf(id, sizeof(id), "wgrp%u", i); 1104 mutex_init(&wgrp->lock); 1105 wgrp->usecount = 0; 1106 wgrp->index = i; 1107 1108 wgrp->rst = devm_reset_control_get(&pdev->dev, id); 1109 if (IS_ERR(wgrp->rst)) 1110 return PTR_ERR(wgrp->rst); 1111 1112 err = reset_control_assert(wgrp->rst); 1113 if (err < 0) 1114 return err; 1115 } 1116 1117 hub->num_heads = of_get_child_count(pdev->dev.of_node); 1118 1119 hub->clk_heads = devm_kcalloc(&pdev->dev, hub->num_heads, sizeof(clk), 1120 GFP_KERNEL); 1121 if (!hub->clk_heads) 1122 return -ENOMEM; 1123 1124 for (i = 0; i < hub->num_heads; i++) { 1125 child = of_get_next_child(pdev->dev.of_node, child); 1126 if (!child) { 1127 dev_err(&pdev->dev, "failed to find node for head %u\n", 1128 i); 1129 return -ENODEV; 1130 } 1131 1132 clk = devm_get_clk_from_child(&pdev->dev, child, "dc"); 1133 if (IS_ERR(clk)) { 1134 dev_err(&pdev->dev, "failed to get clock for head %u\n", 1135 i); 1136 of_node_put(child); 1137 return PTR_ERR(clk); 1138 } 1139 1140 hub->clk_heads[i] = clk; 1141 } 1142 1143 of_node_put(child); 1144 1145 /* XXX: enable clock across reset? */ 1146 err = reset_control_assert(hub->rst); 1147 if (err < 0) 1148 return err; 1149 1150 platform_set_drvdata(pdev, hub); 1151 pm_runtime_enable(&pdev->dev); 1152 1153 INIT_LIST_HEAD(&hub->client.list); 1154 hub->client.ops = &tegra_display_hub_ops; 1155 hub->client.dev = &pdev->dev; 1156 1157 err = host1x_client_register(&hub->client); 1158 if (err < 0) 1159 dev_err(&pdev->dev, "failed to register host1x client: %d\n", 1160 err); 1161 1162 err = devm_of_platform_populate(&pdev->dev); 1163 if (err < 0) 1164 goto unregister; 1165 1166 return err; 1167 1168unregister: 1169 host1x_client_unregister(&hub->client); 1170 pm_runtime_disable(&pdev->dev); 1171 return err; 1172} 1173 1174static int tegra_display_hub_remove(struct platform_device *pdev) 1175{ 1176 struct tegra_display_hub *hub = platform_get_drvdata(pdev); 1177 unsigned int i; 1178 int err; 1179 1180 err = host1x_client_unregister(&hub->client); 1181 if (err < 0) { 1182 dev_err(&pdev->dev, "failed to unregister host1x client: %d\n", 1183 err); 1184 } 1185 1186 for (i = 0; i < hub->soc->num_wgrps; i++) { 1187 struct tegra_windowgroup *wgrp = &hub->wgrps[i]; 1188 1189 mutex_destroy(&wgrp->lock); 1190 } 1191 1192 pm_runtime_disable(&pdev->dev); 1193 1194 return err; 1195} 1196 1197static const struct tegra_display_hub_soc tegra186_display_hub = { 1198 .num_wgrps = 6, 1199 .supports_dsc = true, 1200}; 1201 1202static const struct tegra_display_hub_soc tegra194_display_hub = { 1203 .num_wgrps = 6, 1204 .supports_dsc = false, 1205}; 1206 1207static const struct of_device_id tegra_display_hub_of_match[] = { 1208 { 1209 .compatible = "nvidia,tegra194-display", 1210 .data = &tegra194_display_hub 1211 }, { 1212 .compatible = "nvidia,tegra186-display", 1213 .data = &tegra186_display_hub 1214 }, { 1215 /* sentinel */ 1216 } 1217}; 1218MODULE_DEVICE_TABLE(of, tegra_display_hub_of_match); 1219 1220struct platform_driver tegra_display_hub_driver = { 1221 .driver = { 1222 .name = "tegra-display-hub", 1223 .of_match_table = tegra_display_hub_of_match, 1224 }, 1225 .probe = tegra_display_hub_probe, 1226 .remove = tegra_display_hub_remove, 1227};