diff options
author | Dave Airlie <airlied@redhat.com> | 2018-01-12 11:46:19 +1000 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2018-01-12 11:46:19 +1000 |
commit | 9be712ef4612268c28b9f1e2d850d3ceab06ef66 (patch) | |
tree | af5831ce1017c604da8f96dc6a321df4ff6a7adb /drivers/gpu/drm/tegra | |
parent | 323b20c4af6e315e665b0ea85035fea7d738e0cc (diff) | |
parent | 8f62142e490d761ceb92b55a7c05bb79294d6c6c (diff) | |
download | linux-9be712ef4612268c28b9f1e2d850d3ceab06ef66.tar.bz2 |
Merge tag 'drm/tegra/for-4.16-rc1-fixes' of git://anongit.freedesktop.org/tegra/linux into drm-next
drm/tegra: Changes for v4.16-rc1
The bulk of these changes are preparation work and addition of support
for Tegra186. Currently only HDMI output (the primary output on Jetson
TX2) is supported, but the hardware is also capable of doing DSI and
DisplayPort.
Tegra DRM now also uses the atomic commit helpers instead of the open-
coded variant that was only doing half its job. As a bit of a byproduct
of the Tegra186 support the driver also gained HDMI 2.0 as well as zpos
property support.
Along the way there are also a few patches to clean up a few things and
fix minor issues.
* tag 'drm/tegra/for-4.16-rc1-fixes' of git://anongit.freedesktop.org/tegra/linux: (51 commits)
drm/tegra: dc: Properly cleanup overlay planes
drm/tegra: dc: Fix possible_crtcs mask for planes
drm/tegra: dc: Restore YUV overlay support
drm/tegra: dc: Implement legacy blending
drm/tegra: Correct timeout in tegra_syncpt_wait
drm/tegra: gem: Correct iommu_map_sg() error checking
drm/tegra: dc: Link DC1 to DC0 on Tegra20
drm/tegra: Fix non-debugfs builds
drm/tegra: dpaux: Keep reset defaults for hybrid pad parameters
drm/tegra: Mark Tegra186 display hub PM functions __maybe_unused
drm/tegra: Use IOMMU groups
gpu: host1x: Use IOMMU groups
drm/tegra: Implement zpos property
drm/tegra: dc: Remove redundant spinlock
drm/tegra: dc: Use direct offset to plane registers
drm/tegra: dc: Support more formats
drm/tegra: fb: Force alpha formats
drm/tegra: dpaux: Add Tegra186 support
drm/tegra: dpaux: Implement runtime PM
drm/tegra: sor: Support HDMI 2.0 modes
...
Diffstat (limited to 'drivers/gpu/drm/tegra')
-rw-r--r-- | drivers/gpu/drm/tegra/Makefile | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dc.c | 1914 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dc.h | 289 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dpaux.c | 120 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/drm.c | 164 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/drm.h | 26 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dsi.c | 228 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/fb.c | 36 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/gem.c | 15 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/hdmi.c | 504 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/hub.c | 806 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/hub.h | 81 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/output.c | 24 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/plane.c | 383 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/plane.h | 70 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/sor.c | 1119 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/sor.h | 16 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/vic.c | 20 |
18 files changed, 4019 insertions, 1798 deletions
diff --git a/drivers/gpu/drm/tegra/Makefile b/drivers/gpu/drm/tegra/Makefile index 46d65d39214d..2e0d6213f6bc 100644 --- a/drivers/gpu/drm/tegra/Makefile +++ b/drivers/gpu/drm/tegra/Makefile @@ -5,6 +5,8 @@ tegra-drm-y := \ drm.o \ gem.o \ fb.o \ + hub.o \ + plane.o \ dc.o \ output.o \ rgb.o \ diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index fc70351b9017..b8403ed48285 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -19,82 +19,79 @@ #include "dc.h" #include "drm.h" #include "gem.h" +#include "hub.h" +#include "plane.h" #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_plane_helper.h> -struct tegra_plane { - struct drm_plane base; - unsigned int index; -}; - -static inline struct tegra_plane *to_tegra_plane(struct drm_plane *plane) +static void tegra_dc_stats_reset(struct tegra_dc_stats *stats) { - return container_of(plane, struct tegra_plane, base); + stats->frames = 0; + stats->vblank = 0; + stats->underflow = 0; + stats->overflow = 0; } -struct tegra_dc_state { - struct drm_crtc_state base; +/* Reads the active copy of a register. */ +static u32 tegra_dc_readl_active(struct tegra_dc *dc, unsigned long offset) +{ + u32 value; - struct clk *clk; - unsigned long pclk; - unsigned int div; + tegra_dc_writel(dc, READ_MUX, DC_CMD_STATE_ACCESS); + value = tegra_dc_readl(dc, offset); + tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS); - u32 planes; -}; + return value; +} -static inline struct tegra_dc_state *to_dc_state(struct drm_crtc_state *state) +static inline unsigned int tegra_plane_offset(struct tegra_plane *plane, + unsigned int offset) { - if (state) - return container_of(state, struct tegra_dc_state, base); - - return NULL; -} + if (offset >= 0x500 && offset <= 0x638) { + offset = 0x000 + (offset - 0x500); + return plane->offset + offset; + } -struct tegra_plane_state { - struct drm_plane_state base; + if (offset >= 0x700 && offset <= 0x719) { + offset = 0x180 + (offset - 0x700); + return plane->offset + offset; + } - struct tegra_bo_tiling tiling; - u32 format; - u32 swap; -}; + if (offset >= 0x800 && offset <= 0x839) { + offset = 0x1c0 + (offset - 0x800); + return plane->offset + offset; + } -static inline struct tegra_plane_state * -to_tegra_plane_state(struct drm_plane_state *state) -{ - if (state) - return container_of(state, struct tegra_plane_state, base); + dev_WARN(plane->dc->dev, "invalid offset: %x\n", offset); - return NULL; + return plane->offset + offset; } -static void tegra_dc_stats_reset(struct tegra_dc_stats *stats) +static inline u32 tegra_plane_readl(struct tegra_plane *plane, + unsigned int offset) { - stats->frames = 0; - stats->vblank = 0; - stats->underflow = 0; - stats->overflow = 0; + return tegra_dc_readl(plane->dc, tegra_plane_offset(plane, offset)); } -/* - * Reads the active copy of a register. This takes the dc->lock spinlock to - * prevent races with the VBLANK processing which also needs access to the - * active copy of some registers. - */ -static u32 tegra_dc_readl_active(struct tegra_dc *dc, unsigned long offset) +static inline void tegra_plane_writel(struct tegra_plane *plane, u32 value, + unsigned int offset) { - unsigned long flags; - u32 value; + tegra_dc_writel(plane->dc, value, tegra_plane_offset(plane, offset)); +} - spin_lock_irqsave(&dc->lock, flags); +bool tegra_dc_has_output(struct tegra_dc *dc, struct device *dev) +{ + struct device_node *np = dc->dev->of_node; + struct of_phandle_iterator it; + int err; - tegra_dc_writel(dc, READ_MUX, DC_CMD_STATE_ACCESS); - value = tegra_dc_readl(dc, offset); - tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS); + of_for_each_phandle(&it, err, np, "nvidia,outputs", NULL, 0) + if (it.node == dev->of_node) + return true; - spin_unlock_irqrestore(&dc->lock, flags); - return value; + return false; } /* @@ -115,81 +112,6 @@ void tegra_dc_commit(struct tegra_dc *dc) tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); } -static int tegra_dc_format(u32 fourcc, u32 *format, u32 *swap) -{ - /* assume no swapping of fetched data */ - if (swap) - *swap = BYTE_SWAP_NOSWAP; - - switch (fourcc) { - case DRM_FORMAT_XBGR8888: - *format = WIN_COLOR_DEPTH_R8G8B8A8; - break; - - case DRM_FORMAT_XRGB8888: - *format = WIN_COLOR_DEPTH_B8G8R8A8; - break; - - case DRM_FORMAT_RGB565: - *format = WIN_COLOR_DEPTH_B5G6R5; - break; - - case DRM_FORMAT_UYVY: - *format = WIN_COLOR_DEPTH_YCbCr422; - break; - - case DRM_FORMAT_YUYV: - if (swap) - *swap = BYTE_SWAP_SWAP2; - - *format = WIN_COLOR_DEPTH_YCbCr422; - break; - - case DRM_FORMAT_YUV420: - *format = WIN_COLOR_DEPTH_YCbCr420P; - break; - - case DRM_FORMAT_YUV422: - *format = WIN_COLOR_DEPTH_YCbCr422P; - break; - - default: - return -EINVAL; - } - - return 0; -} - -static bool tegra_dc_format_is_yuv(unsigned int format, bool *planar) -{ - switch (format) { - case WIN_COLOR_DEPTH_YCbCr422: - case WIN_COLOR_DEPTH_YUV422: - if (planar) - *planar = false; - - return true; - - case WIN_COLOR_DEPTH_YCbCr420P: - case WIN_COLOR_DEPTH_YUV420P: - case WIN_COLOR_DEPTH_YCbCr422P: - case WIN_COLOR_DEPTH_YUV422P: - case WIN_COLOR_DEPTH_YCbCr422R: - case WIN_COLOR_DEPTH_YUV422R: - case WIN_COLOR_DEPTH_YCbCr422RA: - case WIN_COLOR_DEPTH_YUV422RA: - if (planar) - *planar = true; - - return true; - } - - if (planar) - *planar = false; - - return false; -} - static inline u32 compute_dda_inc(unsigned int in, unsigned int out, bool v, unsigned int bpp) { @@ -230,36 +152,104 @@ static inline u32 compute_initial_dda(unsigned int in) return dfixed_frac(inf); } -static void tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, +static void tegra_plane_setup_blending_legacy(struct tegra_plane *plane) +{ + u32 background[3] = { + BLEND_WEIGHT1(0) | BLEND_WEIGHT0(0) | BLEND_COLOR_KEY_NONE, + BLEND_WEIGHT1(0) | BLEND_WEIGHT0(0) | BLEND_COLOR_KEY_NONE, + BLEND_WEIGHT1(0) | BLEND_WEIGHT0(0) | BLEND_COLOR_KEY_NONE, + }; + u32 foreground = BLEND_WEIGHT1(255) | BLEND_WEIGHT0(255) | + BLEND_COLOR_KEY_NONE; + u32 blendnokey = BLEND_WEIGHT1(255) | BLEND_WEIGHT0(255); + struct tegra_plane_state *state; + unsigned int i; + + state = to_tegra_plane_state(plane->base.state); + + /* alpha contribution is 1 minus sum of overlapping windows */ + for (i = 0; i < 3; i++) { + if (state->dependent[i]) + background[i] |= BLEND_CONTROL_DEPENDENT; + } + + /* enable alpha blending if pixel format has an alpha component */ + if (!state->opaque) + foreground |= BLEND_CONTROL_ALPHA; + + /* + * Disable blending and assume Window A is the bottom-most window, + * Window C is the top-most window and Window B is in the middle. + */ + tegra_plane_writel(plane, blendnokey, DC_WIN_BLEND_NOKEY); + tegra_plane_writel(plane, foreground, DC_WIN_BLEND_1WIN); + + switch (plane->index) { + case 0: + tegra_plane_writel(plane, background[0], DC_WIN_BLEND_2WIN_X); + tegra_plane_writel(plane, background[1], DC_WIN_BLEND_2WIN_Y); + tegra_plane_writel(plane, background[2], DC_WIN_BLEND_3WIN_XY); + break; + + case 1: + tegra_plane_writel(plane, foreground, DC_WIN_BLEND_2WIN_X); + tegra_plane_writel(plane, background[1], DC_WIN_BLEND_2WIN_Y); + tegra_plane_writel(plane, background[2], DC_WIN_BLEND_3WIN_XY); + break; + + case 2: + tegra_plane_writel(plane, foreground, DC_WIN_BLEND_2WIN_X); + tegra_plane_writel(plane, foreground, DC_WIN_BLEND_2WIN_Y); + tegra_plane_writel(plane, foreground, DC_WIN_BLEND_3WIN_XY); + break; + } +} + +static void tegra_plane_setup_blending(struct tegra_plane *plane, + const struct tegra_dc_window *window) +{ + u32 value; + + value = BLEND_FACTOR_DST_ALPHA_ZERO | BLEND_FACTOR_SRC_ALPHA_K2 | + BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_SRC | + BLEND_FACTOR_SRC_COLOR_K1_TIMES_SRC; + tegra_plane_writel(plane, value, DC_WIN_BLEND_MATCH_SELECT); + + value = BLEND_FACTOR_DST_ALPHA_ZERO | BLEND_FACTOR_SRC_ALPHA_K2 | + BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_SRC | + BLEND_FACTOR_SRC_COLOR_K1_TIMES_SRC; + tegra_plane_writel(plane, value, DC_WIN_BLEND_NOMATCH_SELECT); + + value = K2(255) | K1(255) | WINDOW_LAYER_DEPTH(255 - window->zpos); + tegra_plane_writel(plane, value, DC_WIN_BLEND_LAYER_CONTROL); +} + +static void tegra_dc_setup_window(struct tegra_plane *plane, const struct tegra_dc_window *window) { unsigned h_offset, v_offset, h_size, v_size, h_dda, v_dda, bpp; - unsigned long value, flags; + struct tegra_dc *dc = plane->dc; bool yuv, planar; + u32 value; /* * For YUV planar modes, the number of bytes per pixel takes into * account only the luma component and therefore is 1. */ - yuv = tegra_dc_format_is_yuv(window->format, &planar); + yuv = tegra_plane_format_is_yuv(window->format, &planar); if (!yuv) bpp = window->bits_per_pixel / 8; else bpp = planar ? 1 : 2; - spin_lock_irqsave(&dc->lock, flags); - - value = WINDOW_A_SELECT << index; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER); - - tegra_dc_writel(dc, window->format, DC_WIN_COLOR_DEPTH); - tegra_dc_writel(dc, window->swap, DC_WIN_BYTE_SWAP); + tegra_plane_writel(plane, window->format, DC_WIN_COLOR_DEPTH); + tegra_plane_writel(plane, window->swap, DC_WIN_BYTE_SWAP); value = V_POSITION(window->dst.y) | H_POSITION(window->dst.x); - tegra_dc_writel(dc, value, DC_WIN_POSITION); + tegra_plane_writel(plane, value, DC_WIN_POSITION); value = V_SIZE(window->dst.h) | H_SIZE(window->dst.w); - tegra_dc_writel(dc, value, DC_WIN_SIZE); + tegra_plane_writel(plane, value, DC_WIN_SIZE); h_offset = window->src.x * bpp; v_offset = window->src.y; @@ -267,7 +257,7 @@ static void tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, v_size = window->src.h; value = V_PRESCALED_SIZE(v_size) | H_PRESCALED_SIZE(h_size); - tegra_dc_writel(dc, value, DC_WIN_PRESCALED_SIZE); + tegra_plane_writel(plane, value, DC_WIN_PRESCALED_SIZE); /* * For DDA computations the number of bytes per pixel for YUV planar @@ -280,33 +270,33 @@ static void tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, v_dda = compute_dda_inc(window->src.h, window->dst.h, true, bpp); value = V_DDA_INC(v_dda) | H_DDA_INC(h_dda); - tegra_dc_writel(dc, value, DC_WIN_DDA_INC); + tegra_plane_writel(plane, value, DC_WIN_DDA_INC); h_dda = compute_initial_dda(window->src.x); v_dda = compute_initial_dda(window->src.y); - tegra_dc_writel(dc, h_dda, DC_WIN_H_INITIAL_DDA); - tegra_dc_writel(dc, v_dda, DC_WIN_V_INITIAL_DDA); + tegra_plane_writel(plane, h_dda, DC_WIN_H_INITIAL_DDA); + tegra_plane_writel(plane, v_dda, DC_WIN_V_INITIAL_DDA); - tegra_dc_writel(dc, 0, DC_WIN_UV_BUF_STRIDE); - tegra_dc_writel(dc, 0, DC_WIN_BUF_STRIDE); + tegra_plane_writel(plane, 0, DC_WIN_UV_BUF_STRIDE); + tegra_plane_writel(plane, 0, DC_WIN_BUF_STRIDE); - tegra_dc_writel(dc, window->base[0], DC_WINBUF_START_ADDR); + tegra_plane_writel(plane, window->base[0], DC_WINBUF_START_ADDR); if (yuv && planar) { - tegra_dc_writel(dc, window->base[1], DC_WINBUF_START_ADDR_U); - tegra_dc_writel(dc, window->base[2], DC_WINBUF_START_ADDR_V); + tegra_plane_writel(plane, window->base[1], DC_WINBUF_START_ADDR_U); + tegra_plane_writel(plane, window->base[2], DC_WINBUF_START_ADDR_V); value = window->stride[1] << 16 | window->stride[0]; - tegra_dc_writel(dc, value, DC_WIN_LINE_STRIDE); + tegra_plane_writel(plane, value, DC_WIN_LINE_STRIDE); } else { - tegra_dc_writel(dc, window->stride[0], DC_WIN_LINE_STRIDE); + tegra_plane_writel(plane, window->stride[0], DC_WIN_LINE_STRIDE); } if (window->bottom_up) v_offset += window->src.h - 1; - tegra_dc_writel(dc, h_offset, DC_WINBUF_ADDR_H_OFFSET); - tegra_dc_writel(dc, v_offset, DC_WINBUF_ADDR_V_OFFSET); + tegra_plane_writel(plane, h_offset, DC_WINBUF_ADDR_H_OFFSET); + tegra_plane_writel(plane, v_offset, DC_WINBUF_ADDR_V_OFFSET); if (dc->soc->supports_block_linear) { unsigned long height = window->tiling.value; @@ -326,7 +316,7 @@ static void tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, break; } - tegra_dc_writel(dc, value, DC_WINBUF_SURFACE_KIND); + tegra_plane_writel(plane, value, DC_WINBUF_SURFACE_KIND); } else { switch (window->tiling.mode) { case TEGRA_BO_TILING_MODE_PITCH: @@ -347,21 +337,21 @@ static void tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, break; } - tegra_dc_writel(dc, value, DC_WIN_BUFFER_ADDR_MODE); + tegra_plane_writel(plane, value, DC_WIN_BUFFER_ADDR_MODE); } value = WIN_ENABLE; if (yuv) { /* setup default colorspace conversion coefficients */ - tegra_dc_writel(dc, 0x00f0, DC_WIN_CSC_YOF); - tegra_dc_writel(dc, 0x012a, DC_WIN_CSC_KYRGB); - tegra_dc_writel(dc, 0x0000, DC_WIN_CSC_KUR); - tegra_dc_writel(dc, 0x0198, DC_WIN_CSC_KVR); - tegra_dc_writel(dc, 0x039b, DC_WIN_CSC_KUG); - tegra_dc_writel(dc, 0x032f, DC_WIN_CSC_KVG); - tegra_dc_writel(dc, 0x0204, DC_WIN_CSC_KUB); - tegra_dc_writel(dc, 0x0000, DC_WIN_CSC_KVB); + tegra_plane_writel(plane, 0x00f0, DC_WIN_CSC_YOF); + tegra_plane_writel(plane, 0x012a, DC_WIN_CSC_KYRGB); + tegra_plane_writel(plane, 0x0000, DC_WIN_CSC_KUR); + tegra_plane_writel(plane, 0x0198, DC_WIN_CSC_KVR); + tegra_plane_writel(plane, 0x039b, DC_WIN_CSC_KUG); + tegra_plane_writel(plane, 0x032f, DC_WIN_CSC_KVG); + tegra_plane_writel(plane, 0x0204, DC_WIN_CSC_KUB); + tegra_plane_writel(plane, 0x0000, DC_WIN_CSC_KVB); value |= CSC_ENABLE; } else if (window->bits_per_pixel < 24) { @@ -371,137 +361,74 @@ static void tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, if (window->bottom_up) value |= V_DIRECTION; - tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); - - /* - * Disable blending and assume Window A is the bottom-most window, - * Window C is the top-most window and Window B is in the middle. - */ - tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_NOKEY); - tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_1WIN); - - switch (index) { - case 0: - tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_2WIN_X); - tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_2WIN_Y); - tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_3WIN_XY); - break; - - case 1: - tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_2WIN_X); - tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_2WIN_Y); - tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_3WIN_XY); - break; - - case 2: - tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_2WIN_X); - tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_2WIN_Y); - tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_3WIN_XY); - break; - } - - spin_unlock_irqrestore(&dc->lock, flags); -} + tegra_plane_writel(plane, value, DC_WIN_WIN_OPTIONS); -static void tegra_plane_destroy(struct drm_plane *plane) -{ - struct tegra_plane *p = to_tegra_plane(plane); - - drm_plane_cleanup(plane); - kfree(p); + if (dc->soc->supports_blending) + tegra_plane_setup_blending(plane, window); + else + tegra_plane_setup_blending_legacy(plane); } -static const u32 tegra_primary_plane_formats[] = { +static const u32 tegra20_primary_formats[] = { + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_ARGB8888, + /* non-native formats */ + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGBX5551, DRM_FORMAT_XBGR8888, DRM_FORMAT_XRGB8888, - DRM_FORMAT_RGB565, }; -static void tegra_primary_plane_destroy(struct drm_plane *plane) -{ - tegra_plane_destroy(plane); -} - -static void tegra_plane_reset(struct drm_plane *plane) -{ - struct tegra_plane_state *state; - - if (plane->state) - __drm_atomic_helper_plane_destroy_state(plane->state); - - kfree(plane->state); - plane->state = NULL; - - state = kzalloc(sizeof(*state), GFP_KERNEL); - if (state) { - plane->state = &state->base; - plane->state->plane = plane; - } -} - -static struct drm_plane_state *tegra_plane_atomic_duplicate_state(struct drm_plane *plane) -{ - struct tegra_plane_state *state = to_tegra_plane_state(plane->state); - struct tegra_plane_state *copy; - - copy = kmalloc(sizeof(*copy), GFP_KERNEL); - if (!copy) - return NULL; - - __drm_atomic_helper_plane_duplicate_state(plane, ©->base); - copy->tiling = state->tiling; - copy->format = state->format; - copy->swap = state->swap; - - return ©->base; -} - -static void tegra_plane_atomic_destroy_state(struct drm_plane *plane, - struct drm_plane_state *state) -{ - __drm_atomic_helper_plane_destroy_state(state); - kfree(state); -} - -static const struct drm_plane_funcs tegra_primary_plane_funcs = { - .update_plane = drm_atomic_helper_update_plane, - .disable_plane = drm_atomic_helper_disable_plane, - .destroy = tegra_primary_plane_destroy, - .reset = tegra_plane_reset, - .atomic_duplicate_state = tegra_plane_atomic_duplicate_state, - .atomic_destroy_state = tegra_plane_atomic_destroy_state, +static const u32 tegra114_primary_formats[] = { + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_ARGB8888, + /* new on Tegra114 */ + DRM_FORMAT_ABGR4444, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_BGR565, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, }; -static int tegra_plane_state_add(struct tegra_plane *plane, - struct drm_plane_state *state) -{ - struct drm_crtc_state *crtc_state; - struct tegra_dc_state *tegra; - struct drm_rect clip; - int err; - - /* Propagate errors from allocation or locking failures. */ - crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc); - if (IS_ERR(crtc_state)) - return PTR_ERR(crtc_state); - - clip.x1 = 0; - clip.y1 = 0; - clip.x2 = crtc_state->mode.hdisplay; - clip.y2 = crtc_state->mode.vdisplay; - - /* Check plane state for visibility and calculate clipping bounds */ - err = drm_atomic_helper_check_plane_state(state, crtc_state, &clip, - 0, INT_MAX, true, true); - if (err < 0) - return err; - - tegra = to_dc_state(crtc_state); - - tegra->planes |= WIN_A_ACT_REQ << plane->index; - - return 0; -} +static const u32 tegra124_primary_formats[] = { + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_ARGB8888, + /* new on Tegra114 */ + DRM_FORMAT_ABGR4444, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_BGR565, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + /* new on Tegra124 */ + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, +}; static int tegra_plane_atomic_check(struct drm_plane *plane, struct drm_plane_state *state) @@ -510,17 +437,40 @@ static int tegra_plane_atomic_check(struct drm_plane *plane, struct tegra_bo_tiling *tiling = &plane_state->tiling; struct tegra_plane *tegra = to_tegra_plane(plane); struct tegra_dc *dc = to_tegra_dc(state->crtc); + unsigned int format; int err; /* no need for further checks if the plane is being disabled */ if (!state->crtc) return 0; - err = tegra_dc_format(state->fb->format->format, &plane_state->format, - &plane_state->swap); + err = tegra_plane_format(state->fb->format->format, &format, + &plane_state->swap); if (err < 0) return err; + /* + * Tegra20 and Tegra30 are special cases here because they support + * only variants of specific formats with an alpha component, but not + * the corresponding opaque formats. However, the opaque formats can + * be emulated by disabling alpha blending for the plane. + */ + if (!dc->soc->supports_blending) { + if (!tegra_plane_format_has_alpha(format)) { + err = tegra_plane_format_get_alpha(format, &format); + if (err < 0) + return err; + + plane_state->opaque = true; + } else { + plane_state->opaque = false; + } + + tegra_plane_check_dependent(tegra, plane_state); + } + + plane_state->format = format; + err = tegra_fb_get_tiling(state->fb, tiling); if (err < 0) return err; @@ -553,32 +503,22 @@ static int tegra_plane_atomic_check(struct drm_plane *plane, static void tegra_plane_atomic_disable(struct drm_plane *plane, struct drm_plane_state *old_state) { - struct tegra_dc *dc = to_tegra_dc(old_state->crtc); struct tegra_plane *p = to_tegra_plane(plane); - unsigned long flags; u32 value; /* rien ne va plus */ if (!old_state || !old_state->crtc) return; - spin_lock_irqsave(&dc->lock, flags); - - value = WINDOW_A_SELECT << p->index; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER); - - value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); + value = tegra_plane_readl(p, DC_WIN_WIN_OPTIONS); value &= ~WIN_ENABLE; - tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); - - spin_unlock_irqrestore(&dc->lock, flags); + tegra_plane_writel(p, value, DC_WIN_WIN_OPTIONS); } static void tegra_plane_atomic_update(struct drm_plane *plane, struct drm_plane_state *old_state) { struct tegra_plane_state *state = to_tegra_plane_state(plane->state); - struct tegra_dc *dc = to_tegra_dc(plane->state->crtc); struct drm_framebuffer *fb = plane->state->fb; struct tegra_plane *p = to_tegra_plane(plane); struct tegra_dc_window window; @@ -604,6 +544,7 @@ static void tegra_plane_atomic_update(struct drm_plane *plane, window.bottom_up = tegra_fb_is_bottom_up(fb); /* copy from state */ + window.zpos = plane->state->normalized_zpos; window.tiling = state->tiling; window.format = state->format; window.swap = state->swap; @@ -622,7 +563,7 @@ static void tegra_plane_atomic_update(struct drm_plane *plane, window.stride[i] = fb->pitches[i]; } - tegra_dc_setup_window(dc, p->index, &window); + tegra_dc_setup_window(p, &window); } static const struct drm_plane_helper_funcs tegra_plane_helper_funcs = { @@ -631,8 +572,7 @@ static const struct drm_plane_helper_funcs tegra_plane_helper_funcs = { .atomic_update = tegra_plane_atomic_update, }; -static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm, - struct tegra_dc *dc) +static unsigned long tegra_plane_get_possible_crtcs(struct drm_device *drm) { /* * Ideally this would use drm_crtc_mask(), but that would require the @@ -646,7 +586,14 @@ static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm, * of CRTCs that have been registered, and should therefore always be * the same as drm_crtc_index() after registration. */ - unsigned long possible_crtcs = 1 << drm->mode_config.num_crtc; + return 1 << drm->mode_config.num_crtc; +} + +static struct drm_plane *tegra_primary_plane_create(struct drm_device *drm, + struct tegra_dc *dc) +{ + unsigned long possible_crtcs = tegra_plane_get_possible_crtcs(drm); + enum drm_plane_type type = DRM_PLANE_TYPE_PRIMARY; struct tegra_plane *plane; unsigned int num_formats; const u32 *formats; @@ -656,13 +603,17 @@ static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm, if (!plane) return ERR_PTR(-ENOMEM); - num_formats = ARRAY_SIZE(tegra_primary_plane_formats); - formats = tegra_primary_plane_formats; + /* Always use window A as primary window */ + plane->offset = 0xa00; + plane->index = 0; + plane->dc = dc; + + num_formats = dc->soc->num_primary_formats; + formats = dc->soc->primary_formats; err = drm_universal_plane_init(drm, &plane->base, possible_crtcs, - &tegra_primary_plane_funcs, formats, - num_formats, NULL, - DRM_PLANE_TYPE_PRIMARY, NULL); + &tegra_plane_funcs, formats, + num_formats, NULL, type, NULL); if (err < 0) { kfree(plane); return ERR_PTR(err); @@ -670,6 +621,9 @@ static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm, drm_plane_helper_add(&plane->base, &tegra_plane_helper_funcs); + if (dc->soc->supports_blending) + drm_plane_create_zpos_property(&plane->base, 0, 0, 255); + return &plane->base; } @@ -786,15 +740,6 @@ static void tegra_cursor_atomic_disable(struct drm_plane *plane, tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); } -static const struct drm_plane_funcs tegra_cursor_plane_funcs = { - .update_plane = drm_atomic_helper_update_plane, - .disable_plane = drm_atomic_helper_disable_plane, - .destroy = tegra_plane_destroy, - .reset = tegra_plane_reset, - .atomic_duplicate_state = tegra_plane_atomic_duplicate_state, - .atomic_destroy_state = tegra_plane_atomic_destroy_state, -}; - static const struct drm_plane_helper_funcs tegra_cursor_plane_helper_funcs = { .atomic_check = tegra_cursor_atomic_check, .atomic_update = tegra_cursor_atomic_update, @@ -804,6 +749,7 @@ static const struct drm_plane_helper_funcs tegra_cursor_plane_helper_funcs = { static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm, struct tegra_dc *dc) { + unsigned long possible_crtcs = tegra_plane_get_possible_crtcs(drm); struct tegra_plane *plane; unsigned int num_formats; const u32 *formats; @@ -821,12 +767,13 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm, * need to special-casing the cursor plane. */ plane->index = 6; + plane->dc = dc; num_formats = ARRAY_SIZE(tegra_cursor_plane_formats); formats = tegra_cursor_plane_formats; - err = drm_universal_plane_init(drm, &plane->base, 1 << dc->pipe, - &tegra_cursor_plane_funcs, formats, + err = drm_universal_plane_init(drm, &plane->base, possible_crtcs, + &tegra_plane_funcs, formats, num_formats, NULL, DRM_PLANE_TYPE_CURSOR, NULL); if (err < 0) { @@ -839,24 +786,76 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm, return &plane->base; } -static void tegra_overlay_plane_destroy(struct drm_plane *plane) -{ - tegra_plane_destroy(plane); -} - -static const struct drm_plane_funcs tegra_overlay_plane_funcs = { - .update_plane = drm_atomic_helper_update_plane, - .disable_plane = drm_atomic_helper_disable_plane, - .destroy = tegra_overlay_plane_destroy, - .reset = tegra_plane_reset, - .atomic_duplicate_state = tegra_plane_atomic_duplicate_state, - .atomic_destroy_state = tegra_plane_atomic_destroy_state, +static const u32 tegra20_overlay_formats[] = { + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_ARGB8888, + /* non-native formats */ + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_XRGB8888, + /* planar formats */ + DRM_FORMAT_UYVY, + DRM_FORMAT_YUYV, + DRM_FORMAT_YUV420, + DRM_FORMAT_YUV422, }; -static const uint32_t tegra_overlay_plane_formats[] = { - DRM_FORMAT_XBGR8888, +static const u32 tegra114_overlay_formats[] = { + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_ARGB8888, + /* new on Tegra114 */ + DRM_FORMAT_ABGR4444, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_BGR565, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_RGBA8888, DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + /* planar formats */ + DRM_FORMAT_UYVY, + DRM_FORMAT_YUYV, + DRM_FORMAT_YUV420, + DRM_FORMAT_YUV422, +}; + +static const u32 tegra124_overlay_formats[] = { + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ARGB1555, DRM_FORMAT_RGB565, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_ARGB8888, + /* new on Tegra114 */ + DRM_FORMAT_ABGR4444, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_BGR565, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + /* new on Tegra124 */ + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + /* planar formats */ DRM_FORMAT_UYVY, DRM_FORMAT_YUYV, DRM_FORMAT_YUV420, @@ -867,6 +866,7 @@ static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm, struct tegra_dc *dc, unsigned int index) { + unsigned long possible_crtcs = tegra_plane_get_possible_crtcs(drm); struct tegra_plane *plane; unsigned int num_formats; const u32 *formats; @@ -876,13 +876,15 @@ static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm, if (!plane) return ERR_PTR(-ENOMEM); + plane->offset = 0xa00 + 0x200 * index; plane->index = index; + plane->dc = dc; - num_formats = ARRAY_SIZE(tegra_overlay_plane_formats); - formats = tegra_overlay_plane_formats; + num_formats = dc->soc->num_overlay_formats; + formats = dc->soc->overlay_formats; - err = drm_universal_plane_init(drm, &plane->base, 1 << dc->pipe, - &tegra_overlay_plane_funcs, formats, + err = drm_universal_plane_init(drm, &plane->base, possible_crtcs, + &tegra_plane_funcs, formats, num_formats, NULL, DRM_PLANE_TYPE_OVERLAY, NULL); if (err < 0) { @@ -892,97 +894,71 @@ static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm, drm_plane_helper_add(&plane->base, &tegra_plane_helper_funcs); + if (dc->soc->supports_blending) + drm_plane_create_zpos_property(&plane->base, 0, 0, 255); + return &plane->base; } -static int tegra_dc_add_planes(struct drm_device *drm, struct tegra_dc *dc) +static struct drm_plane *tegra_dc_add_shared_planes(struct drm_device *drm, + struct tegra_dc *dc) { - struct drm_plane *plane; - unsigned int i; - - for (i = 0; i < 2; i++) { - plane = tegra_dc_overlay_plane_create(drm, dc, 1 + i); - if (IS_ERR(plane)) - return PTR_ERR(plane); + struct drm_plane *plane, *primary = NULL; + unsigned int i, j; + + for (i = 0; i < dc->soc->num_wgrps; i++) { + const struct tegra_windowgroup_soc *wgrp = &dc->soc->wgrps[i]; + + if (wgrp->dc == dc->pipe) { + for (j = 0; j < wgrp->num_windows; j++) { + unsigned int index = wgrp->windows[j]; + + plane = tegra_shared_plane_create(drm, dc, + wgrp->index, + index); + if (IS_ERR(plane)) + return plane; + + /* + * Choose the first shared plane owned by this + * head as the primary plane. + */ + if (!primary) { + plane->type = DRM_PLANE_TYPE_PRIMARY; + primary = plane; + } + } + } } - return 0; -} - -static u32 tegra_dc_get_vblank_counter(struct drm_crtc *crtc) -{ - struct tegra_dc *dc = to_tegra_dc(crtc); - - if (dc->syncpt) - return host1x_syncpt_read(dc->syncpt); - - /* fallback to software emulated VBLANK counter */ - return drm_crtc_vblank_count(&dc->base); + return primary; } -static int tegra_dc_enable_vblank(struct drm_crtc *crtc) +static struct drm_plane *tegra_dc_add_planes(struct drm_device *drm, + struct tegra_dc *dc) { - struct tegra_dc *dc = to_tegra_dc(crtc); - unsigned long value, flags; - - spin_lock_irqsave(&dc->lock, flags); - - value = tegra_dc_readl(dc, DC_CMD_INT_MASK); - value |= VBLANK_INT; - tegra_dc_writel(dc, value, DC_CMD_INT_MASK); - - spin_unlock_irqrestore(&dc->lock, flags); - - return 0; -} - -static void tegra_dc_disable_vblank(struct drm_crtc *crtc) -{ - struct tegra_dc *dc = to_tegra_dc(crtc); - unsigned long value, flags; - - spin_lock_irqsave(&dc->lock, flags); - - value = tegra_dc_readl(dc, DC_CMD_INT_MASK); - value &= ~VBLANK_INT; - tegra_dc_writel(dc, value, DC_CMD_INT_MASK); - - spin_unlock_irqrestore(&dc->lock, flags); -} - -static void tegra_dc_finish_page_flip(struct tegra_dc *dc) -{ - struct drm_device *drm = dc->base.dev; - struct drm_crtc *crtc = &dc->base; - unsigned long flags, base; - struct tegra_bo *bo; - - spin_lock_irqsave(&drm->event_lock, flags); - - if (!dc->event) { - spin_unlock_irqrestore(&drm->event_lock, flags); - return; - } - - bo = tegra_fb_get_plane(crtc->primary->fb, 0); + struct drm_plane *planes[2], *primary; + unsigned int i; + int err; - spin_lock(&dc->lock); + primary = tegra_primary_plane_create(drm, dc); + if (IS_ERR(primary)) + return primary; - /* check if new start address has been latched */ - tegra_dc_writel(dc, WINDOW_A_SELECT, DC_CMD_DISPLAY_WINDOW_HEADER); - tegra_dc_writel(dc, READ_MUX, DC_CMD_STATE_ACCESS); - base = tegra_dc_readl(dc, DC_WINBUF_START_ADDR); - tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS); + for (i = 0; i < 2; i++) { + planes[i] = tegra_dc_overlay_plane_create(drm, dc, 1 + i); + if (IS_ERR(planes[i])) { + err = PTR_ERR(planes[i]); - spin_unlock(&dc->lock); + while (i--) + tegra_plane_funcs.destroy(planes[i]); - if (base == bo->paddr + crtc->primary->fb->offsets[0]) { - drm_crtc_send_vblank_event(crtc, dc->event); - drm_crtc_vblank_put(crtc); - dc->event = NULL; + tegra_plane_funcs.destroy(primary); + return ERR_PTR(err); + } } - spin_unlock_irqrestore(&drm->event_lock, flags); + return primary; } static void tegra_dc_destroy(struct drm_crtc *crtc) @@ -1035,6 +1011,379 @@ static void tegra_crtc_atomic_destroy_state(struct drm_crtc *crtc, kfree(state); } +#define DEBUGFS_REG32(_name) { .name = #_name, .offset = _name } + +static const struct debugfs_reg32 tegra_dc_regs[] = { + DEBUGFS_REG32(DC_CMD_GENERAL_INCR_SYNCPT), + DEBUGFS_REG32(DC_CMD_GENERAL_INCR_SYNCPT_CNTRL), + DEBUGFS_REG32(DC_CMD_GENERAL_INCR_SYNCPT_ERROR), + DEBUGFS_REG32(DC_CMD_WIN_A_INCR_SYNCPT), + DEBUGFS_REG32(DC_CMD_WIN_A_INCR_SYNCPT_CNTRL), + DEBUGFS_REG32(DC_CMD_WIN_A_INCR_SYNCPT_ERROR), + DEBUGFS_REG32(DC_CMD_WIN_B_INCR_SYNCPT), + DEBUGFS_REG32(DC_CMD_WIN_B_INCR_SYNCPT_CNTRL), + DEBUGFS_REG32(DC_CMD_WIN_B_INCR_SYNCPT_ERROR), + DEBUGFS_REG32(DC_CMD_WIN_C_INCR_SYNCPT), + DEBUGFS_REG32(DC_CMD_WIN_C_INCR_SYNCPT_CNTRL), + DEBUGFS_REG32(DC_CMD_WIN_C_INCR_SYNCPT_ERROR), + DEBUGFS_REG32(DC_CMD_CONT_SYNCPT_VSYNC), + DEBUGFS_REG32(DC_CMD_DISPLAY_COMMAND_OPTION0), + DEBUGFS_REG32(DC_CMD_DISPLAY_COMMAND), + DEBUGFS_REG32(DC_CMD_SIGNAL_RAISE), + DEBUGFS_REG32(DC_CMD_DISPLAY_POWER_CONTROL), + DEBUGFS_REG32(DC_CMD_INT_STATUS), + DEBUGFS_REG32(DC_CMD_INT_MASK), + DEBUGFS_REG32(DC_CMD_INT_ENABLE), + DEBUGFS_REG32(DC_CMD_INT_TYPE), + DEBUGFS_REG32(DC_CMD_INT_POLARITY), + DEBUGFS_REG32(DC_CMD_SIGNAL_RAISE1), + DEBUGFS_REG32(DC_CMD_SIGNAL_RAISE2), + DEBUGFS_REG32(DC_CMD_SIGNAL_RAISE3), + DEBUGFS_REG32(DC_CMD_STATE_ACCESS), + DEBUGFS_REG32(DC_CMD_STATE_CONTROL), + DEBUGFS_REG32(DC_CMD_DISPLAY_WINDOW_HEADER), + DEBUGFS_REG32(DC_CMD_REG_ACT_CONTROL), + DEBUGFS_REG32(DC_COM_CRC_CONTROL), + DEBUGFS_REG32(DC_COM_CRC_CHECKSUM), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_ENABLE(0)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_ENABLE(1)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_ENABLE(2)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_ENABLE(3)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_POLARITY(0)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_POLARITY(1)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_POLARITY(2)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_POLARITY(3)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_DATA(0)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_DATA(1)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_DATA(2)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_DATA(3)), + DEBUGFS_REG32(DC_COM_PIN_INPUT_ENABLE(0)), + DEBUGFS_REG32(DC_COM_PIN_INPUT_ENABLE(1)), + DEBUGFS_REG32(DC_COM_PIN_INPUT_ENABLE(2)), + DEBUGFS_REG32(DC_COM_PIN_INPUT_ENABLE(3)), + DEBUGFS_REG32(DC_COM_PIN_INPUT_DATA(0)), + DEBUGFS_REG32(DC_COM_PIN_INPUT_DATA(1)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(0)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(1)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(2)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(3)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(4)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(5)), + DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(6)), + DEBUGFS_REG32(DC_COM_PIN_MISC_CONTROL), + DEBUGFS_REG32(DC_COM_PIN_PM0_CONTROL), + DEBUGFS_REG32(DC_COM_PIN_PM0_DUTY_CYCLE), + DEBUGFS_REG32(DC_COM_PIN_PM1_CONTROL), + DEBUGFS_REG32(DC_COM_PIN_PM1_DUTY_CYCLE), + DEBUGFS_REG32(DC_COM_SPI_CONTROL), + DEBUGFS_REG32(DC_COM_SPI_START_BYTE), + DEBUGFS_REG32(DC_COM_HSPI_WRITE_DATA_AB), + DEBUGFS_REG32(DC_COM_HSPI_WRITE_DATA_CD), + DEBUGFS_REG32(DC_COM_HSPI_CS_DC), + DEBUGFS_REG32(DC_COM_SCRATCH_REGISTER_A), + DEBUGFS_REG32(DC_COM_SCRATCH_REGISTER_B), + DEBUGFS_REG32(DC_COM_GPIO_CTRL), + DEBUGFS_REG32(DC_COM_GPIO_DEBOUNCE_COUNTER), + DEBUGFS_REG32(DC_COM_CRC_CHECKSUM_LATCHED), + DEBUGFS_REG32(DC_DISP_DISP_SIGNAL_OPTIONS0), + DEBUGFS_REG32(DC_DISP_DISP_SIGNAL_OPTIONS1), + DEBUGFS_REG32(DC_DISP_DISP_WIN_OPTIONS), + DEBUGFS_REG32(DC_DISP_DISP_MEM_HIGH_PRIORITY), + DEBUGFS_REG32(DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER), + DEBUGFS_REG32(DC_DISP_DISP_TIMING_OPTIONS), + DEBUGFS_REG32(DC_DISP_REF_TO_SYNC), + DEBUGFS_REG32(DC_DISP_SYNC_WIDTH), + DEBUGFS_REG32(DC_DISP_BACK_PORCH), + DEBUGFS_REG32(DC_DISP_ACTIVE), + DEBUGFS_REG32(DC_DISP_FRONT_PORCH), + DEBUGFS_REG32(DC_DISP_H_PULSE0_CONTROL), + DEBUGFS_REG32(DC_DISP_H_PULSE0_POSITION_A), + DEBUGFS_REG32(DC_DISP_H_PULSE0_POSITION_B), + DEBUGFS_REG32(DC_DISP_H_PULSE0_POSITION_C), + DEBUGFS_REG32(DC_DISP_H_PULSE0_POSITION_D), + DEBUGFS_REG32(DC_DISP_H_PULSE1_CONTROL), + DEBUGFS_REG32(DC_DISP_H_PULSE1_POSITION_A), + DEBUGFS_REG32(DC_DISP_H_PULSE1_POSITION_B), + DEBUGFS_REG32(DC_DISP_H_PULSE1_POSITION_C), + DEBUGFS_REG32(DC_DISP_H_PULSE1_POSITION_D), + DEBUGFS_REG32(DC_DISP_H_PULSE2_CONTROL), + DEBUGFS_REG32(DC_DISP_H_PULSE2_POSITION_A), + DEBUGFS_REG32(DC_DISP_H_PULSE2_POSITION_B), + DEBUGFS_REG32(DC_DISP_H_PULSE2_POSITION_C), + DEBUGFS_REG32(DC_DISP_H_PULSE2_POSITION_D), + DEBUGFS_REG32(DC_DISP_V_PULSE0_CONTROL), + DEBUGFS_REG32(DC_DISP_V_PULSE0_POSITION_A), + DEBUGFS_REG32(DC_DISP_V_PULSE0_POSITION_B), + DEBUGFS_REG32(DC_DISP_V_PULSE0_POSITION_C), + DEBUGFS_REG32(DC_DISP_V_PULSE1_CONTROL), + DEBUGFS_REG32(DC_DISP_V_PULSE1_POSITION_A), + DEBUGFS_REG32(DC_DISP_V_PULSE1_POSITION_B), + DEBUGFS_REG32(DC_DISP_V_PULSE1_POSITION_C), + DEBUGFS_REG32(DC_DISP_V_PULSE2_CONTROL), + DEBUGFS_REG32(DC_DISP_V_PULSE2_POSITION_A), + DEBUGFS_REG32(DC_DISP_V_PULSE3_CONTROL), + DEBUGFS_REG32(DC_DISP_V_PULSE3_POSITION_A), + DEBUGFS_REG32(DC_DISP_M0_CONTROL), + DEBUGFS_REG32(DC_DISP_M1_CONTROL), + DEBUGFS_REG32(DC_DISP_DI_CONTROL), + DEBUGFS_REG32(DC_DISP_PP_CONTROL), + DEBUGFS_REG32(DC_DISP_PP_SELECT_A), + DEBUGFS_REG32(DC_DISP_PP_SELECT_B), + DEBUGFS_REG32(DC_DISP_PP_SELECT_C), + DEBUGFS_REG32(DC_DISP_PP_SELECT_D), + DEBUGFS_REG32(DC_DISP_DISP_CLOCK_CONTROL), + DEBUGFS_REG32(DC_DISP_DISP_INTERFACE_CONTROL), + DEBUGFS_REG32(DC_DISP_DISP_COLOR_CONTROL), + DEBUGFS_REG32(DC_DISP_SHIFT_CLOCK_OPTIONS), + DEBUGFS_REG32(DC_DISP_DATA_ENABLE_OPTIONS), + DEBUGFS_REG32(DC_DISP_SERIAL_INTERFACE_OPTIONS), + DEBUGFS_REG32(DC_DISP_LCD_SPI_OPTIONS), + DEBUGFS_REG32(DC_DISP_BORDER_COLOR), + DEBUGFS_REG32(DC_DISP_COLOR_KEY0_LOWER), + DEBUGFS_REG32(DC_DISP_COLOR_KEY0_UPPER), + DEBUGFS_REG32(DC_DISP_COLOR_KEY1_LOWER), + DEBUGFS_REG32(DC_DISP_COLOR_KEY1_UPPER), + DEBUGFS_REG32(DC_DISP_CURSOR_FOREGROUND), + DEBUGFS_REG32(DC_DISP_CURSOR_BACKGROUND), + DEBUGFS_REG32(DC_DISP_CURSOR_START_ADDR), + DEBUGFS_REG32(DC_DISP_CURSOR_START_ADDR_NS), + DEBUGFS_REG32(DC_DISP_CURSOR_POSITION), + DEBUGFS_REG32(DC_DISP_CURSOR_POSITION_NS), + DEBUGFS_REG32(DC_DISP_INIT_SEQ_CONTROL), + DEBUGFS_REG32(DC_DISP_SPI_INIT_SEQ_DATA_A), + DEBUGFS_REG32(DC_DISP_SPI_INIT_SEQ_DATA_B), + DEBUGFS_REG32(DC_DISP_SPI_INIT_SEQ_DATA_C), + DEBUGFS_REG32(DC_DISP_SPI_INIT_SEQ_DATA_D), + DEBUGFS_REG32(DC_DISP_DC_MCCIF_FIFOCTRL), + DEBUGFS_REG32(DC_DISP_MCCIF_DISPLAY0A_HYST), + DEBUGFS_REG32(DC_DISP_MCCIF_DISPLAY0B_HYST), + DEBUGFS_REG32(DC_DISP_MCCIF_DISPLAY1A_HYST), + DEBUGFS_REG32(DC_DISP_MCCIF_DISPLAY1B_HYST), + DEBUGFS_REG32(DC_DISP_DAC_CRT_CTRL), + DEBUGFS_REG32(DC_DISP_DISP_MISC_CONTROL), + DEBUGFS_REG32(DC_DISP_SD_CONTROL), + DEBUGFS_REG32(DC_DISP_SD_CSC_COEFF), + DEBUGFS_REG32(DC_DISP_SD_LUT(0)), + DEBUGFS_REG32(DC_DISP_SD_LUT(1)), + DEBUGFS_REG32(DC_DISP_SD_LUT(2)), + DEBUGFS_REG32(DC_DISP_SD_LUT(3)), + DEBUGFS_REG32(DC_DISP_SD_LUT(4)), + DEBUGFS_REG32(DC_DISP_SD_LUT(5)), + DEBUGFS_REG32(DC_DISP_SD_LUT(6)), + DEBUGFS_REG32(DC_DISP_SD_LUT(7)), + DEBUGFS_REG32(DC_DISP_SD_LUT(8)), + DEBUGFS_REG32(DC_DISP_SD_FLICKER_CONTROL), + DEBUGFS_REG32(DC_DISP_DC_PIXEL_COUNT), + DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(0)), + DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(1)), + DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(2)), + DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(3)), + DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(4)), + DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(5)), + DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(6)), + DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(7)), + DEBUGFS_REG32(DC_DISP_SD_BL_TF(0)), + DEBUGFS_REG32(DC_DISP_SD_BL_TF(1)), + DEBUGFS_REG32(DC_DISP_SD_BL_TF(2)), + DEBUGFS_REG32(DC_DISP_SD_BL_TF(3)), + DEBUGFS_REG32(DC_DISP_SD_BL_CONTROL), + DEBUGFS_REG32(DC_DISP_SD_HW_K_VALUES), + DEBUGFS_REG32(DC_DISP_SD_MAN_K_VALUES), + DEBUGFS_REG32(DC_DISP_CURSOR_START_ADDR_HI), + DEBUGFS_REG32(DC_DISP_BLEND_CURSOR_CONTROL), + DEBUGFS_REG32(DC_WIN_WIN_OPTIONS), + DEBUGFS_REG32(DC_WIN_BYTE_SWAP), + DEBUGFS_REG32(DC_WIN_BUFFER_CONTROL), + DEBUGFS_REG32(DC_WIN_COLOR_DEPTH), + DEBUGFS_REG32(DC_WIN_POSITION), + DEBUGFS_REG32(DC_WIN_SIZE), + DEBUGFS_REG32(DC_WIN_PRESCALED_SIZE), + DEBUGFS_REG32(DC_WIN_H_INITIAL_DDA), + DEBUGFS_REG32(DC_WIN_V_INITIAL_DDA), + DEBUGFS_REG32(DC_WIN_DDA_INC), + DEBUGFS_REG32(DC_WIN_LINE_STRIDE), + DEBUGFS_REG32(DC_WIN_BUF_STRIDE), + DEBUGFS_REG32(DC_WIN_UV_BUF_STRIDE), + DEBUGFS_REG32(DC_WIN_BUFFER_ADDR_MODE), + DEBUGFS_REG32(DC_WIN_DV_CONTROL), + DEBUGFS_REG32(DC_WIN_BLEND_NOKEY), + DEBUGFS_REG32(DC_WIN_BLEND_1WIN), + DEBUGFS_REG32(DC_WIN_BLEND_2WIN_X), + DEBUGFS_REG32(DC_WIN_BLEND_2WIN_Y), + DEBUGFS_REG32(DC_WIN_BLEND_3WIN_XY), + DEBUGFS_REG32(DC_WIN_HP_FETCH_CONTROL), + DEBUGFS_REG32(DC_WINBUF_START_ADDR), + DEBUGFS_REG32(DC_WINBUF_START_ADDR_NS), + DEBUGFS_REG32(DC_WINBUF_START_ADDR_U), + DEBUGFS_REG32(DC_WINBUF_START_ADDR_U_NS), + DEBUGFS_REG32(DC_WINBUF_START_ADDR_V), + DEBUGFS_REG32(DC_WINBUF_START_ADDR_V_NS), + DEBUGFS_REG32(DC_WINBUF_ADDR_H_OFFSET), + DEBUGFS_REG32(DC_WINBUF_ADDR_H_OFFSET_NS), + DEBUGFS_REG32(DC_WINBUF_ADDR_V_OFFSET), + DEBUGFS_REG32(DC_WINBUF_ADDR_V_OFFSET_NS), + DEBUGFS_REG32(DC_WINBUF_UFLOW_STATUS), + DEBUGFS_REG32(DC_WINBUF_AD_UFLOW_STATUS), + DEBUGFS_REG32(DC_WINBUF_BD_UFLOW_STATUS), + DEBUGFS_REG32(DC_WINBUF_CD_UFLOW_STATUS), +}; + +static int tegra_dc_show_regs(struct seq_file *s, void *data) +{ + struct drm_info_node *node = s->private; + struct tegra_dc *dc = node->info_ent->data; + unsigned int i; + int err = 0; + + drm_modeset_lock(&dc->base.mutex, NULL); + + if (!dc->base.state->active) { + err = -EBUSY; + goto unlock; + } + + for (i = 0; i < ARRAY_SIZE(tegra_dc_regs); i++) { + unsigned int offset = tegra_dc_regs[i].offset; + + seq_printf(s, "%-40s %#05x %08x\n", tegra_dc_regs[i].name, + offset, tegra_dc_readl(dc, offset)); + } + +unlock: + drm_modeset_unlock(&dc->base.mutex); + return err; +} + +static int tegra_dc_show_crc(struct seq_file *s, void *data) +{ + struct drm_info_node *node = s->private; + struct tegra_dc *dc = node->info_ent->data; + int err = 0; + u32 value; + + drm_modeset_lock(&dc->base.mutex, NULL); + + if (!dc->base.state->active) { + err = -EBUSY; + goto unlock; + } + + value = DC_COM_CRC_CONTROL_ACTIVE_DATA | DC_COM_CRC_CONTROL_ENABLE; + tegra_dc_writel(dc, value, DC_COM_CRC_CONTROL); + tegra_dc_commit(dc); + + drm_crtc_wait_one_vblank(&dc->base); + drm_crtc_wait_one_vblank(&dc->base); + + value = tegra_dc_readl(dc, DC_COM_CRC_CHECKSUM); + seq_printf(s, "%08x\n", value); + + tegra_dc_writel(dc, 0, DC_COM_CRC_CONTROL); + +unlock: + drm_modeset_unlock(&dc->base.mutex); + return err; +} + +static int tegra_dc_show_stats(struct seq_file *s, void *data) +{ + struct drm_info_node *node = s->private; + struct tegra_dc *dc = node->info_ent->data; + + seq_printf(s, "frames: %lu\n", dc->stats.frames); + seq_printf(s, "vblank: %lu\n", dc->stats.vblank); + seq_printf(s, "underflow: %lu\n", dc->stats.underflow); + seq_printf(s, "overflow: %lu\n", dc->stats.overflow); + + return 0; +} + +static struct drm_info_list debugfs_files[] = { + { "regs", tegra_dc_show_regs, 0, NULL }, + { "crc", tegra_dc_show_crc, 0, NULL }, + { "stats", tegra_dc_show_stats, 0, NULL }, +}; + +static int tegra_dc_late_register(struct drm_crtc *crtc) +{ + unsigned int i, count = ARRAY_SIZE(debugfs_files); + struct drm_minor *minor = crtc->dev->primary; + struct dentry *root; + struct tegra_dc *dc = to_tegra_dc(crtc); + int err; + +#ifdef CONFIG_DEBUG_FS + root = crtc->debugfs_entry; +#else + root = NULL; +#endif + + dc->debugfs_files = kmemdup(debugfs_files, sizeof(debugfs_files), + GFP_KERNEL); + if (!dc->debugfs_files) + return -ENOMEM; + + for (i = 0; i < count; i++) + dc->debugfs_files[i].data = dc; + + err = drm_debugfs_create_files(dc->debugfs_files, count, root, minor); + if (err < 0) + goto free; + + return 0; + +free: + kfree(dc->debugfs_files); + dc->debugfs_files = NULL; + + return err; +} + +static void tegra_dc_early_unregister(struct drm_crtc *crtc) +{ + unsigned int count = ARRAY_SIZE(debugfs_files); + struct drm_minor *minor = crtc->dev->primary; + struct tegra_dc *dc = to_tegra_dc(crtc); + + drm_debugfs_remove_files(dc->debugfs_files, count, minor); + kfree(dc->debugfs_files); + dc->debugfs_files = NULL; +} + +static u32 tegra_dc_get_vblank_counter(struct drm_crtc *crtc) +{ + struct tegra_dc *dc = to_tegra_dc(crtc); + + /* XXX vblank syncpoints don't work with nvdisplay yet */ + if (dc->syncpt && !dc->soc->has_nvdisplay) + return host1x_syncpt_read(dc->syncpt); + + /* fallback to software emulated VBLANK counter */ + return drm_crtc_vblank_count(&dc->base); +} + +static int tegra_dc_enable_vblank(struct drm_crtc *crtc) +{ + struct tegra_dc *dc = to_tegra_dc(crtc); + u32 value; + + value = tegra_dc_readl(dc, DC_CMD_INT_MASK); + value |= VBLANK_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_MASK); + + return 0; +} + +static void tegra_dc_disable_vblank(struct drm_crtc *crtc) +{ + struct tegra_dc *dc = to_tegra_dc(crtc); + u32 value; + + value = tegra_dc_readl(dc, DC_CMD_INT_MASK); + value &= ~VBLANK_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_MASK); +} + static const struct drm_crtc_funcs tegra_crtc_funcs = { .page_flip = drm_atomic_helper_page_flip, .set_config = drm_atomic_helper_set_config, @@ -1042,6 +1391,8 @@ static const struct drm_crtc_funcs tegra_crtc_funcs = { .reset = tegra_crtc_reset, .atomic_duplicate_state = tegra_crtc_atomic_duplicate_state, .atomic_destroy_state = tegra_crtc_atomic_destroy_state, + .late_register = tegra_dc_late_register, + .early_unregister = tegra_dc_early_unregister, .get_vblank_counter = tegra_dc_get_vblank_counter, .enable_vblank = tegra_dc_enable_vblank, .disable_vblank = tegra_dc_disable_vblank, @@ -1054,10 +1405,12 @@ static int tegra_dc_set_timings(struct tegra_dc *dc, unsigned int v_ref_to_sync = 1; unsigned long value; - tegra_dc_writel(dc, 0x0, DC_DISP_DISP_TIMING_OPTIONS); + if (!dc->soc->has_nvdisplay) { + tegra_dc_writel(dc, 0x0, DC_DISP_DISP_TIMING_OPTIONS); - value = (v_ref_to_sync << 16) | h_ref_to_sync; - tegra_dc_writel(dc, value, DC_DISP_REF_TO_SYNC); + value = (v_ref_to_sync << 16) | h_ref_to_sync; + tegra_dc_writel(dc, value, DC_DISP_REF_TO_SYNC); + } value = ((mode->vsync_end - mode->vsync_start) << 16) | ((mode->hsync_end - mode->hsync_start) << 0); @@ -1136,8 +1489,10 @@ static void tegra_dc_commit_state(struct tegra_dc *dc, state->div); DRM_DEBUG_KMS("pclk: %lu\n", state->pclk); - value = SHIFT_CLK_DIVIDER(state->div) | PIXEL_CLK_DIVIDER_PCD1; - tegra_dc_writel(dc, value, DC_DISP_DISP_CLOCK_CONTROL); + if (!dc->soc->has_nvdisplay) { + value = SHIFT_CLK_DIVIDER(state->div) | PIXEL_CLK_DIVIDER_PCD1; + tegra_dc_writel(dc, value, DC_DISP_DISP_CLOCK_CONTROL); + } err = clk_set_rate(dc->clk, state->pclk); if (err < 0) @@ -1223,6 +1578,15 @@ static void tegra_crtc_atomic_disable(struct drm_crtc *crtc, tegra_dc_stats_reset(&dc->stats); drm_crtc_vblank_off(crtc); + spin_lock_irq(&crtc->dev->event_lock); + + if (crtc->state->event) { + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + + spin_unlock_irq(&crtc->dev->event_lock); + pm_runtime_put_sync(dc->dev); } @@ -1238,41 +1602,70 @@ static void tegra_crtc_atomic_enable(struct drm_crtc *crtc, /* initialize display controller */ if (dc->syncpt) { - u32 syncpt = host1x_syncpt_id(dc->syncpt); + u32 syncpt = host1x_syncpt_id(dc->syncpt), enable; + + if (dc->soc->has_nvdisplay) + enable = 1 << 31; + else + enable = 1 << 8; value = SYNCPT_CNTRL_NO_STALL; tegra_dc_writel(dc, value, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL); - value = SYNCPT_VSYNC_ENABLE | syncpt; + value = enable | syncpt; tegra_dc_writel(dc, value, DC_CMD_CONT_SYNCPT_VSYNC); } - value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | - WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; - tegra_dc_writel(dc, value, DC_CMD_INT_TYPE); - - value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | - WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; - tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY); + if (dc->soc->has_nvdisplay) { + value = DSC_TO_UF_INT | DSC_BBUF_UF_INT | DSC_RBUF_UF_INT | + DSC_OBUF_UF_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_TYPE); - /* initialize timer */ - value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) | - WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20); - tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY); + value = DSC_TO_UF_INT | DSC_BBUF_UF_INT | DSC_RBUF_UF_INT | + DSC_OBUF_UF_INT | SD3_BUCKET_WALK_DONE_INT | + HEAD_UF_INT | MSF_INT | REG_TMOUT_INT | + REGION_CRC_INT | V_PULSE2_INT | V_PULSE3_INT | + VBLANK_INT | FRAME_END_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY); - value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(1) | - WINDOW_B_THRESHOLD(1) | WINDOW_C_THRESHOLD(1); - tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER); + value = SD3_BUCKET_WALK_DONE_INT | HEAD_UF_INT | VBLANK_INT | + FRAME_END_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE); - value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | - WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; - tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE); + value = HEAD_UF_INT | REG_TMOUT_INT | FRAME_END_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_MASK); - value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | - WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; - tegra_dc_writel(dc, value, DC_CMD_INT_MASK); + tegra_dc_writel(dc, READ_MUX, DC_CMD_STATE_ACCESS); + } else { + value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | + WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_TYPE); + + value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | + WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY); + + /* initialize timer */ + value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) | + WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20); + tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY); + + value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(1) | + WINDOW_B_THRESHOLD(1) | WINDOW_C_THRESHOLD(1); + tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER); + + value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | + WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE); + + value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | + WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_MASK); + } - if (dc->soc->supports_border_color) + if (dc->soc->supports_background_color) + tegra_dc_writel(dc, 0, DC_DISP_BLEND_BACKGROUND_COLOR); + else tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR); /* apply PLL and pixel clock changes */ @@ -1293,10 +1686,18 @@ static void tegra_crtc_atomic_enable(struct drm_crtc *crtc, value |= DISP_CTRL_MODE_C_DISPLAY; tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); - value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | - PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + if (!dc->soc->has_nvdisplay) { + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); + value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | + PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + } + + /* enable underflow reporting and display red for missing pixels */ + if (dc->soc->has_nvdisplay) { + value = UNDERFLOW_MODE_RED | UNDERFLOW_REPORT_ENABLE; + tegra_dc_writel(dc, value, DC_COM_RG_UNDERFLOW); + } tegra_dc_commit(dc); @@ -1306,20 +1707,43 @@ static void tegra_crtc_atomic_enable(struct drm_crtc *crtc, static int tegra_crtc_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *state) { + struct tegra_atomic_state *s = to_tegra_atomic_state(state->state); + struct tegra_dc_state *tegra = to_dc_state(state); + + /* + * The display hub display clock needs to be fed by the display clock + * with the highest frequency to ensure proper functioning of all the + * displays. + * + * Note that this isn't used before Tegra186, but it doesn't hurt and + * conditionalizing it would make the code less clean. + */ + if (state->active) { + if (!s->clk_disp || tegra->pclk > s->rate) { + s->dc = to_tegra_dc(crtc); + s->clk_disp = s->dc->clk; + s->rate = tegra->pclk; + } + } + return 0; } static void tegra_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_crtc_state *old_crtc_state) { - struct tegra_dc *dc = to_tegra_dc(crtc); + unsigned long flags; if (crtc->state->event) { - crtc->state->event->pipe = drm_crtc_index(crtc); + spin_lock_irqsave(&crtc->dev->event_lock, flags); - WARN_ON(drm_crtc_vblank_get(crtc) != 0); + if (drm_crtc_vblank_get(crtc) != 0) + drm_crtc_send_vblank_event(crtc, crtc->state->event); + else + drm_crtc_arm_vblank_event(crtc, crtc->state->event); + + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); - dc->event = crtc->state->event; crtc->state->event = NULL; } } @@ -1329,9 +1753,15 @@ static void tegra_crtc_atomic_flush(struct drm_crtc *crtc, { struct tegra_dc_state *state = to_dc_state(crtc->state); struct tegra_dc *dc = to_tegra_dc(crtc); + u32 value; - tegra_dc_writel(dc, state->planes << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, state->planes, DC_CMD_STATE_CONTROL); + value = state->planes << 8 | GENERAL_UPDATE; + tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); + value = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); + + value = state->planes | GENERAL_ACT_REQ; + tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); + value = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); } static const struct drm_crtc_helper_funcs tegra_crtc_helper_funcs = { @@ -1362,7 +1792,6 @@ static irqreturn_t tegra_dc_irq(int irq, void *data) dev_dbg(dc->dev, "%s(): vertical blank\n", __func__); */ drm_crtc_handle_vblank(&dc->base); - tegra_dc_finish_page_flip(dc); dc->stats.vblank++; } @@ -1380,357 +1809,18 @@ static irqreturn_t tegra_dc_irq(int irq, void *data) dc->stats.overflow++; } - return IRQ_HANDLED; -} - -static int tegra_dc_show_regs(struct seq_file *s, void *data) -{ - struct drm_info_node *node = s->private; - struct tegra_dc *dc = node->info_ent->data; - int err = 0; - - drm_modeset_lock(&dc->base.mutex, NULL); - - if (!dc->base.state->active) { - err = -EBUSY; - goto unlock; - } - -#define DUMP_REG(name) \ - seq_printf(s, "%-40s %#05x %08x\n", #name, name, \ - tegra_dc_readl(dc, name)) - - DUMP_REG(DC_CMD_GENERAL_INCR_SYNCPT); - DUMP_REG(DC_CMD_GENERAL_INCR_SYNCPT_CNTRL); - DUMP_REG(DC_CMD_GENERAL_INCR_SYNCPT_ERROR); - DUMP_REG(DC_CMD_WIN_A_INCR_SYNCPT); - DUMP_REG(DC_CMD_WIN_A_INCR_SYNCPT_CNTRL); - DUMP_REG(DC_CMD_WIN_A_INCR_SYNCPT_ERROR); - DUMP_REG(DC_CMD_WIN_B_INCR_SYNCPT); - DUMP_REG(DC_CMD_WIN_B_INCR_SYNCPT_CNTRL); - DUMP_REG(DC_CMD_WIN_B_INCR_SYNCPT_ERROR); - DUMP_REG(DC_CMD_WIN_C_INCR_SYNCPT); - DUMP_REG(DC_CMD_WIN_C_INCR_SYNCPT_CNTRL); - DUMP_REG(DC_CMD_WIN_C_INCR_SYNCPT_ERROR); - DUMP_REG(DC_CMD_CONT_SYNCPT_VSYNC); - DUMP_REG(DC_CMD_DISPLAY_COMMAND_OPTION0); - DUMP_REG(DC_CMD_DISPLAY_COMMAND); - DUMP_REG(DC_CMD_SIGNAL_RAISE); - DUMP_REG(DC_CMD_DISPLAY_POWER_CONTROL); - DUMP_REG(DC_CMD_INT_STATUS); - DUMP_REG(DC_CMD_INT_MASK); - DUMP_REG(DC_CMD_INT_ENABLE); - DUMP_REG(DC_CMD_INT_TYPE); - DUMP_REG(DC_CMD_INT_POLARITY); - DUMP_REG(DC_CMD_SIGNAL_RAISE1); - DUMP_REG(DC_CMD_SIGNAL_RAISE2); - DUMP_REG(DC_CMD_SIGNAL_RAISE3); - DUMP_REG(DC_CMD_STATE_ACCESS); - DUMP_REG(DC_CMD_STATE_CONTROL); - DUMP_REG(DC_CMD_DISPLAY_WINDOW_HEADER); - DUMP_REG(DC_CMD_REG_ACT_CONTROL); - DUMP_REG(DC_COM_CRC_CONTROL); - DUMP_REG(DC_COM_CRC_CHECKSUM); - DUMP_REG(DC_COM_PIN_OUTPUT_ENABLE(0)); - DUMP_REG(DC_COM_PIN_OUTPUT_ENABLE(1)); - DUMP_REG(DC_COM_PIN_OUTPUT_ENABLE(2)); - DUMP_REG(DC_COM_PIN_OUTPUT_ENABLE(3)); - DUMP_REG(DC_COM_PIN_OUTPUT_POLARITY(0)); - DUMP_REG(DC_COM_PIN_OUTPUT_POLARITY(1)); - DUMP_REG(DC_COM_PIN_OUTPUT_POLARITY(2)); - DUMP_REG(DC_COM_PIN_OUTPUT_POLARITY(3)); - DUMP_REG(DC_COM_PIN_OUTPUT_DATA(0)); - DUMP_REG(DC_COM_PIN_OUTPUT_DATA(1)); - DUMP_REG(DC_COM_PIN_OUTPUT_DATA(2)); - DUMP_REG(DC_COM_PIN_OUTPUT_DATA(3)); - DUMP_REG(DC_COM_PIN_INPUT_ENABLE(0)); - DUMP_REG(DC_COM_PIN_INPUT_ENABLE(1)); - DUMP_REG(DC_COM_PIN_INPUT_ENABLE(2)); - DUMP_REG(DC_COM_PIN_INPUT_ENABLE(3)); - DUMP_REG(DC_COM_PIN_INPUT_DATA(0)); - DUMP_REG(DC_COM_PIN_INPUT_DATA(1)); - DUMP_REG(DC_COM_PIN_OUTPUT_SELECT(0)); - DUMP_REG(DC_COM_PIN_OUTPUT_SELECT(1)); - DUMP_REG(DC_COM_PIN_OUTPUT_SELECT(2)); - DUMP_REG(DC_COM_PIN_OUTPUT_SELECT(3)); - DUMP_REG(DC_COM_PIN_OUTPUT_SELECT(4)); - DUMP_REG(DC_COM_PIN_OUTPUT_SELECT(5)); - DUMP_REG(DC_COM_PIN_OUTPUT_SELECT(6)); - DUMP_REG(DC_COM_PIN_MISC_CONTROL); - DUMP_REG(DC_COM_PIN_PM0_CONTROL); - DUMP_REG(DC_COM_PIN_PM0_DUTY_CYCLE); - DUMP_REG(DC_COM_PIN_PM1_CONTROL); - DUMP_REG(DC_COM_PIN_PM1_DUTY_CYCLE); - DUMP_REG(DC_COM_SPI_CONTROL); - DUMP_REG(DC_COM_SPI_START_BYTE); - DUMP_REG(DC_COM_HSPI_WRITE_DATA_AB); - DUMP_REG(DC_COM_HSPI_WRITE_DATA_CD); - DUMP_REG(DC_COM_HSPI_CS_DC); - DUMP_REG(DC_COM_SCRATCH_REGISTER_A); - DUMP_REG(DC_COM_SCRATCH_REGISTER_B); - DUMP_REG(DC_COM_GPIO_CTRL); - DUMP_REG(DC_COM_GPIO_DEBOUNCE_COUNTER); - DUMP_REG(DC_COM_CRC_CHECKSUM_LATCHED); - DUMP_REG(DC_DISP_DISP_SIGNAL_OPTIONS0); - DUMP_REG(DC_DISP_DISP_SIGNAL_OPTIONS1); - DUMP_REG(DC_DISP_DISP_WIN_OPTIONS); - DUMP_REG(DC_DISP_DISP_MEM_HIGH_PRIORITY); - DUMP_REG(DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER); - DUMP_REG(DC_DISP_DISP_TIMING_OPTIONS); - DUMP_REG(DC_DISP_REF_TO_SYNC); - DUMP_REG(DC_DISP_SYNC_WIDTH); - DUMP_REG(DC_DISP_BACK_PORCH); - DUMP_REG(DC_DISP_ACTIVE); - DUMP_REG(DC_DISP_FRONT_PORCH); - DUMP_REG(DC_DISP_H_PULSE0_CONTROL); - DUMP_REG(DC_DISP_H_PULSE0_POSITION_A); - DUMP_REG(DC_DISP_H_PULSE0_POSITION_B); - DUMP_REG(DC_DISP_H_PULSE0_POSITION_C); - DUMP_REG(DC_DISP_H_PULSE0_POSITION_D); - DUMP_REG(DC_DISP_H_PULSE1_CONTROL); - DUMP_REG(DC_DISP_H_PULSE1_POSITION_A); - DUMP_REG(DC_DISP_H_PULSE1_POSITION_B); - DUMP_REG(DC_DISP_H_PULSE1_POSITION_C); - DUMP_REG(DC_DISP_H_PULSE1_POSITION_D); - DUMP_REG(DC_DISP_H_PULSE2_CONTROL); - DUMP_REG(DC_DISP_H_PULSE2_POSITION_A); - DUMP_REG(DC_DISP_H_PULSE2_POSITION_B); - DUMP_REG(DC_DISP_H_PULSE2_POSITION_C); - DUMP_REG(DC_DISP_H_PULSE2_POSITION_D); - DUMP_REG(DC_DISP_V_PULSE0_CONTROL); - DUMP_REG(DC_DISP_V_PULSE0_POSITION_A); - DUMP_REG(DC_DISP_V_PULSE0_POSITION_B); - DUMP_REG(DC_DISP_V_PULSE0_POSITION_C); - DUMP_REG(DC_DISP_V_PULSE1_CONTROL); - DUMP_REG(DC_DISP_V_PULSE1_POSITION_A); - DUMP_REG(DC_DISP_V_PULSE1_POSITION_B); - DUMP_REG(DC_DISP_V_PULSE1_POSITION_C); - DUMP_REG(DC_DISP_V_PULSE2_CONTROL); - DUMP_REG(DC_DISP_V_PULSE2_POSITION_A); - DUMP_REG(DC_DISP_V_PULSE3_CONTROL); - DUMP_REG(DC_DISP_V_PULSE3_POSITION_A); - DUMP_REG(DC_DISP_M0_CONTROL); - DUMP_REG(DC_DISP_M1_CONTROL); - DUMP_REG(DC_DISP_DI_CONTROL); - DUMP_REG(DC_DISP_PP_CONTROL); - DUMP_REG(DC_DISP_PP_SELECT_A); - DUMP_REG(DC_DISP_PP_SELECT_B); - DUMP_REG(DC_DISP_PP_SELECT_C); - DUMP_REG(DC_DISP_PP_SELECT_D); - DUMP_REG(DC_DISP_DISP_CLOCK_CONTROL); - DUMP_REG(DC_DISP_DISP_INTERFACE_CONTROL); - DUMP_REG(DC_DISP_DISP_COLOR_CONTROL); - DUMP_REG(DC_DISP_SHIFT_CLOCK_OPTIONS); - DUMP_REG(DC_DISP_DATA_ENABLE_OPTIONS); - DUMP_REG(DC_DISP_SERIAL_INTERFACE_OPTIONS); - DUMP_REG(DC_DISP_LCD_SPI_OPTIONS); - DUMP_REG(DC_DISP_BORDER_COLOR); - DUMP_REG(DC_DISP_COLOR_KEY0_LOWER); - DUMP_REG(DC_DISP_COLOR_KEY0_UPPER); - DUMP_REG(DC_DISP_COLOR_KEY1_LOWER); - DUMP_REG(DC_DISP_COLOR_KEY1_UPPER); - DUMP_REG(DC_DISP_CURSOR_FOREGROUND); - DUMP_REG(DC_DISP_CURSOR_BACKGROUND); - DUMP_REG(DC_DISP_CURSOR_START_ADDR); - DUMP_REG(DC_DISP_CURSOR_START_ADDR_NS); - DUMP_REG(DC_DISP_CURSOR_POSITION); - DUMP_REG(DC_DISP_CURSOR_POSITION_NS); - DUMP_REG(DC_DISP_INIT_SEQ_CONTROL); - DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_A); - DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_B); - DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_C); - DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_D); - DUMP_REG(DC_DISP_DC_MCCIF_FIFOCTRL); - DUMP_REG(DC_DISP_MCCIF_DISPLAY0A_HYST); - DUMP_REG(DC_DISP_MCCIF_DISPLAY0B_HYST); - DUMP_REG(DC_DISP_MCCIF_DISPLAY1A_HYST); - DUMP_REG(DC_DISP_MCCIF_DISPLAY1B_HYST); - DUMP_REG(DC_DISP_DAC_CRT_CTRL); - DUMP_REG(DC_DISP_DISP_MISC_CONTROL); - DUMP_REG(DC_DISP_SD_CONTROL); - DUMP_REG(DC_DISP_SD_CSC_COEFF); - DUMP_REG(DC_DISP_SD_LUT(0)); - DUMP_REG(DC_DISP_SD_LUT(1)); - DUMP_REG(DC_DISP_SD_LUT(2)); - DUMP_REG(DC_DISP_SD_LUT(3)); - DUMP_REG(DC_DISP_SD_LUT(4)); - DUMP_REG(DC_DISP_SD_LUT(5)); - DUMP_REG(DC_DISP_SD_LUT(6)); - DUMP_REG(DC_DISP_SD_LUT(7)); - DUMP_REG(DC_DISP_SD_LUT(8)); - DUMP_REG(DC_DISP_SD_FLICKER_CONTROL); - DUMP_REG(DC_DISP_DC_PIXEL_COUNT); - DUMP_REG(DC_DISP_SD_HISTOGRAM(0)); - DUMP_REG(DC_DISP_SD_HISTOGRAM(1)); - DUMP_REG(DC_DISP_SD_HISTOGRAM(2)); - DUMP_REG(DC_DISP_SD_HISTOGRAM(3)); - DUMP_REG(DC_DISP_SD_HISTOGRAM(4)); - DUMP_REG(DC_DISP_SD_HISTOGRAM(5)); - DUMP_REG(DC_DISP_SD_HISTOGRAM(6)); - DUMP_REG(DC_DISP_SD_HISTOGRAM(7)); - DUMP_REG(DC_DISP_SD_BL_TF(0)); - DUMP_REG(DC_DISP_SD_BL_TF(1)); - DUMP_REG(DC_DISP_SD_BL_TF(2)); - DUMP_REG(DC_DISP_SD_BL_TF(3)); - DUMP_REG(DC_DISP_SD_BL_CONTROL); - DUMP_REG(DC_DISP_SD_HW_K_VALUES); - DUMP_REG(DC_DISP_SD_MAN_K_VALUES); - DUMP_REG(DC_DISP_CURSOR_START_ADDR_HI); - DUMP_REG(DC_DISP_BLEND_CURSOR_CONTROL); - DUMP_REG(DC_WIN_WIN_OPTIONS); - DUMP_REG(DC_WIN_BYTE_SWAP); - DUMP_REG(DC_WIN_BUFFER_CONTROL); - DUMP_REG(DC_WIN_COLOR_DEPTH); - DUMP_REG(DC_WIN_POSITION); - DUMP_REG(DC_WIN_SIZE); - DUMP_REG(DC_WIN_PRESCALED_SIZE); - DUMP_REG(DC_WIN_H_INITIAL_DDA); - DUMP_REG(DC_WIN_V_INITIAL_DDA); - DUMP_REG(DC_WIN_DDA_INC); - DUMP_REG(DC_WIN_LINE_STRIDE); - DUMP_REG(DC_WIN_BUF_STRIDE); - DUMP_REG(DC_WIN_UV_BUF_STRIDE); - DUMP_REG(DC_WIN_BUFFER_ADDR_MODE); - DUMP_REG(DC_WIN_DV_CONTROL); - DUMP_REG(DC_WIN_BLEND_NOKEY); - DUMP_REG(DC_WIN_BLEND_1WIN); - DUMP_REG(DC_WIN_BLEND_2WIN_X); - DUMP_REG(DC_WIN_BLEND_2WIN_Y); - DUMP_REG(DC_WIN_BLEND_3WIN_XY); - DUMP_REG(DC_WIN_HP_FETCH_CONTROL); - DUMP_REG(DC_WINBUF_START_ADDR); - DUMP_REG(DC_WINBUF_START_ADDR_NS); - DUMP_REG(DC_WINBUF_START_ADDR_U); - DUMP_REG(DC_WINBUF_START_ADDR_U_NS); - DUMP_REG(DC_WINBUF_START_ADDR_V); - DUMP_REG(DC_WINBUF_START_ADDR_V_NS); - DUMP_REG(DC_WINBUF_ADDR_H_OFFSET); - DUMP_REG(DC_WINBUF_ADDR_H_OFFSET_NS); - DUMP_REG(DC_WINBUF_ADDR_V_OFFSET); - DUMP_REG(DC_WINBUF_ADDR_V_OFFSET_NS); - DUMP_REG(DC_WINBUF_UFLOW_STATUS); - DUMP_REG(DC_WINBUF_AD_UFLOW_STATUS); - DUMP_REG(DC_WINBUF_BD_UFLOW_STATUS); - DUMP_REG(DC_WINBUF_CD_UFLOW_STATUS); - -#undef DUMP_REG - -unlock: - drm_modeset_unlock(&dc->base.mutex); - return err; -} - -static int tegra_dc_show_crc(struct seq_file *s, void *data) -{ - struct drm_info_node *node = s->private; - struct tegra_dc *dc = node->info_ent->data; - int err = 0; - u32 value; - - drm_modeset_lock(&dc->base.mutex, NULL); - - if (!dc->base.state->active) { - err = -EBUSY; - goto unlock; - } - - value = DC_COM_CRC_CONTROL_ACTIVE_DATA | DC_COM_CRC_CONTROL_ENABLE; - tegra_dc_writel(dc, value, DC_COM_CRC_CONTROL); - tegra_dc_commit(dc); - - drm_crtc_wait_one_vblank(&dc->base); - drm_crtc_wait_one_vblank(&dc->base); - - value = tegra_dc_readl(dc, DC_COM_CRC_CHECKSUM); - seq_printf(s, "%08x\n", value); - - tegra_dc_writel(dc, 0, DC_COM_CRC_CONTROL); - -unlock: - drm_modeset_unlock(&dc->base.mutex); - return err; -} - -static int tegra_dc_show_stats(struct seq_file *s, void *data) -{ - struct drm_info_node *node = s->private; - struct tegra_dc *dc = node->info_ent->data; - - seq_printf(s, "frames: %lu\n", dc->stats.frames); - seq_printf(s, "vblank: %lu\n", dc->stats.vblank); - seq_printf(s, "underflow: %lu\n", dc->stats.underflow); - seq_printf(s, "overflow: %lu\n", dc->stats.overflow); - - return 0; -} - -static struct drm_info_list debugfs_files[] = { - { "regs", tegra_dc_show_regs, 0, NULL }, - { "crc", tegra_dc_show_crc, 0, NULL }, - { "stats", tegra_dc_show_stats, 0, NULL }, -}; - -static int tegra_dc_debugfs_init(struct tegra_dc *dc, struct drm_minor *minor) -{ - unsigned int i; - char *name; - int err; - - name = kasprintf(GFP_KERNEL, "dc.%d", dc->pipe); - dc->debugfs = debugfs_create_dir(name, minor->debugfs_root); - kfree(name); - - if (!dc->debugfs) - return -ENOMEM; - - dc->debugfs_files = kmemdup(debugfs_files, sizeof(debugfs_files), - GFP_KERNEL); - if (!dc->debugfs_files) { - err = -ENOMEM; - goto remove; + if (status & HEAD_UF_INT) { + dev_dbg_ratelimited(dc->dev, "%s(): head underflow\n", __func__); + dc->stats.underflow++; } - for (i = 0; i < ARRAY_SIZE(debugfs_files); i++) - dc->debugfs_files[i].data = dc; - - err = drm_debugfs_create_files(dc->debugfs_files, - ARRAY_SIZE(debugfs_files), - dc->debugfs, minor); - if (err < 0) - goto free; - - dc->minor = minor; - - return 0; - -free: - kfree(dc->debugfs_files); - dc->debugfs_files = NULL; -remove: - debugfs_remove(dc->debugfs); - dc->debugfs = NULL; - - return err; -} - -static int tegra_dc_debugfs_exit(struct tegra_dc *dc) -{ - drm_debugfs_remove_files(dc->debugfs_files, ARRAY_SIZE(debugfs_files), - dc->minor); - dc->minor = NULL; - - kfree(dc->debugfs_files); - dc->debugfs_files = NULL; - - debugfs_remove(dc->debugfs); - dc->debugfs = NULL; - - return 0; + return IRQ_HANDLED; } static int tegra_dc_init(struct host1x_client *client) { struct drm_device *drm = dev_get_drvdata(client->parent); + struct iommu_group *group = iommu_group_get(client->dev); unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED; struct tegra_dc *dc = host1x_client_to_dc(client); struct tegra_drm *tegra = drm->dev_private; @@ -1742,18 +1832,27 @@ static int tegra_dc_init(struct host1x_client *client) if (!dc->syncpt) dev_warn(dc->dev, "failed to allocate syncpoint\n"); - if (tegra->domain) { - err = iommu_attach_device(tegra->domain, dc->dev); - if (err < 0) { - dev_err(dc->dev, "failed to attach to domain: %d\n", - err); - return err; + if (group && tegra->domain) { + if (group != tegra->group) { + err = iommu_attach_group(tegra->domain, group); + if (err < 0) { + dev_err(dc->dev, + "failed to attach to domain: %d\n", + err); + return err; + } + + tegra->group = group; } dc->domain = tegra->domain; } - primary = tegra_dc_primary_plane_create(drm, dc); + if (dc->soc->wgrps) + primary = tegra_dc_add_shared_planes(drm, dc); + else + primary = tegra_dc_add_planes(drm, dc); + if (IS_ERR(primary)) { err = PTR_ERR(primary); goto cleanup; @@ -1787,16 +1886,6 @@ static int tegra_dc_init(struct host1x_client *client) goto cleanup; } - err = tegra_dc_add_planes(drm, dc); - if (err < 0) - goto cleanup; - - if (IS_ENABLED(CONFIG_DEBUG_FS)) { - err = tegra_dc_debugfs_init(dc, drm->primary); - if (err < 0) - dev_err(dc->dev, "debugfs setup failed: %d\n", err); - } - err = devm_request_irq(dc->dev, dc->irq, tegra_dc_irq, 0, dev_name(dc->dev), dc); if (err < 0) { @@ -1808,14 +1897,14 @@ static int tegra_dc_init(struct host1x_client *client) return 0; cleanup: - if (cursor) + if (!IS_ERR_OR_NULL(cursor)) drm_plane_cleanup(cursor); - if (primary) + if (!IS_ERR(primary)) drm_plane_cleanup(primary); - if (tegra->domain) { - iommu_detach_device(tegra->domain, dc->dev); + if (group && tegra->domain) { + iommu_detach_group(tegra->domain, group); dc->domain = NULL; } @@ -1824,25 +1913,20 @@ cleanup: static int tegra_dc_exit(struct host1x_client *client) { + struct iommu_group *group = iommu_group_get(client->dev); struct tegra_dc *dc = host1x_client_to_dc(client); int err; devm_free_irq(dc->dev, dc->irq, dc); - if (IS_ENABLED(CONFIG_DEBUG_FS)) { - err = tegra_dc_debugfs_exit(dc); - if (err < 0) - dev_err(dc->dev, "debugfs cleanup failed: %d\n", err); - } - err = tegra_dc_rgb_exit(dc); if (err) { dev_err(dc->dev, "failed to shutdown RGB output: %d\n", err); return err; } - if (dc->domain) { - iommu_detach_device(dc->domain, dc->dev); + if (group && dc->domain) { + iommu_detach_group(dc->domain, group); dc->domain = NULL; } @@ -1857,57 +1941,138 @@ static const struct host1x_client_ops dc_client_ops = { }; static const struct tegra_dc_soc_info tegra20_dc_soc_info = { - .supports_border_color = true, + .supports_background_color = false, .supports_interlacing = false, .supports_cursor = false, .supports_block_linear = false, + .supports_blending = false, .pitch_align = 8, .has_powergate = false, - .broken_reset = true, + .coupled_pm = true, + .has_nvdisplay = false, + .num_primary_formats = ARRAY_SIZE(tegra20_primary_formats), + .primary_formats = tegra20_primary_formats, + .num_overlay_formats = ARRAY_SIZE(tegra20_overlay_formats), + .overlay_formats = tegra20_overlay_formats, }; static const struct tegra_dc_soc_info tegra30_dc_soc_info = { - .supports_border_color = true, + .supports_background_color = false, .supports_interlacing = false, .supports_cursor = false, .supports_block_linear = false, + .supports_blending = false, .pitch_align = 8, .has_powergate = false, - .broken_reset = false, + .coupled_pm = false, + .has_nvdisplay = false, + .num_primary_formats = ARRAY_SIZE(tegra20_primary_formats), + .primary_formats = tegra20_primary_formats, + .num_overlay_formats = ARRAY_SIZE(tegra20_overlay_formats), + .overlay_formats = tegra20_overlay_formats, }; static const struct tegra_dc_soc_info tegra114_dc_soc_info = { - .supports_border_color = true, + .supports_background_color = false, .supports_interlacing = false, .supports_cursor = false, .supports_block_linear = false, + .supports_blending = false, .pitch_align = 64, .has_powergate = true, - .broken_reset = false, + .coupled_pm = false, + .has_nvdisplay = false, + .num_primary_formats = ARRAY_SIZE(tegra114_primary_formats), + .primary_formats = tegra114_primary_formats, + .num_overlay_formats = ARRAY_SIZE(tegra114_overlay_formats), + .overlay_formats = tegra114_overlay_formats, }; static const struct tegra_dc_soc_info tegra124_dc_soc_info = { - .supports_border_color = false, + .supports_background_color = true, .supports_interlacing = true, .supports_cursor = true, .supports_block_linear = true, + .supports_blending = true, .pitch_align = 64, .has_powergate = true, - .broken_reset = false, + .coupled_pm = false, + .has_nvdisplay = false, + .num_primary_formats = ARRAY_SIZE(tegra124_primary_formats), + .primary_formats = tegra114_primary_formats, + .num_overlay_formats = ARRAY_SIZE(tegra124_overlay_formats), + .overlay_formats = tegra114_overlay_formats, }; static const struct tegra_dc_soc_info tegra210_dc_soc_info = { - .supports_border_color = false, + .supports_background_color = true, .supports_interlacing = true, .supports_cursor = true, .supports_block_linear = true, + .supports_blending = true, .pitch_align = 64, .has_powergate = true, - .broken_reset = false, + .coupled_pm = false, + .has_nvdisplay = false, + .num_primary_formats = ARRAY_SIZE(tegra114_primary_formats), + .primary_formats = tegra114_primary_formats, + .num_overlay_formats = ARRAY_SIZE(tegra114_overlay_formats), + .overlay_formats = tegra114_overlay_formats, +}; + +static const struct tegra_windowgroup_soc tegra186_dc_wgrps[] = { + { + .index = 0, + .dc = 0, + .windows = (const unsigned int[]) { 0 }, + .num_windows = 1, + }, { + .index = 1, + .dc = 1, + .windows = (const unsigned int[]) { 1 }, + .num_windows = 1, + }, { + .index = 2, + .dc = 1, + .windows = (const unsigned int[]) { 2 }, + .num_windows = 1, + }, { + .index = 3, + .dc = 2, + .windows = (const unsigned int[]) { 3 }, + .num_windows = 1, + }, { + .index = 4, + .dc = 2, + .windows = (const unsigned int[]) { 4 }, + .num_windows = 1, + }, { + .index = 5, + .dc = 2, + .windows = (const unsigned int[]) { 5 }, + .num_windows = 1, + }, +}; + +static const struct tegra_dc_soc_info tegra186_dc_soc_info = { + .supports_background_color = true, + .supports_interlacing = true, + .supports_cursor = true, + .supports_block_linear = true, + .supports_blending = true, + .pitch_align = 64, + .has_powergate = false, + .coupled_pm = false, + .has_nvdisplay = true, + .wgrps = tegra186_dc_wgrps, + .num_wgrps = ARRAY_SIZE(tegra186_dc_wgrps), }; static const struct of_device_id tegra_dc_of_match[] = { { + .compatible = "nvidia,tegra186-dc", + .data = &tegra186_dc_soc_info, + }, { .compatible = "nvidia,tegra210-dc", .data = &tegra210_dc_soc_info, }, { @@ -1965,6 +2130,43 @@ static int tegra_dc_parse_dt(struct tegra_dc *dc) return 0; } +static int tegra_dc_match_by_pipe(struct device *dev, void *data) +{ + struct tegra_dc *dc = dev_get_drvdata(dev); + unsigned int pipe = (unsigned long)data; + + return dc->pipe == pipe; +} + +static int tegra_dc_couple(struct tegra_dc *dc) +{ + /* + * On Tegra20, DC1 requires DC0 to be taken out of reset in order to + * be enabled, otherwise CPU hangs on writing to CMD_DISPLAY_COMMAND / + * POWER_CONTROL registers during CRTC enabling. + */ + if (dc->soc->coupled_pm && dc->pipe == 1) { + u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE; + struct device_link *link; + struct device *partner; + + partner = driver_find_device(dc->dev->driver, NULL, 0, + tegra_dc_match_by_pipe); + if (!partner) + return -EPROBE_DEFER; + + link = device_link_add(dc->dev, partner, flags); + if (!link) { + dev_err(dc->dev, "failed to link controllers\n"); + return -EINVAL; + } + + dev_dbg(dc->dev, "coupled to %s\n", dev_name(partner)); + } + + return 0; +} + static int tegra_dc_probe(struct platform_device *pdev) { struct resource *regs; @@ -1977,7 +2179,6 @@ static int tegra_dc_probe(struct platform_device *pdev) dc->soc = of_device_get_match_data(&pdev->dev); - spin_lock_init(&dc->lock); INIT_LIST_HEAD(&dc->list); dc->dev = &pdev->dev; @@ -1985,6 +2186,10 @@ static int tegra_dc_probe(struct platform_device *pdev) if (err < 0) return err; + err = tegra_dc_couple(dc); + if (err < 0) + return err; + dc->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(dc->clk)) { dev_err(&pdev->dev, "failed to get clock\n"); @@ -1998,21 +2203,19 @@ static int tegra_dc_probe(struct platform_device *pdev) } /* assert reset and disable clock */ - if (!dc->soc->broken_reset) { - err = clk_prepare_enable(dc->clk); - if (err < 0) - return err; + err = clk_prepare_enable(dc->clk); + if (err < 0) + return err; - usleep_range(2000, 4000); + usleep_range(2000, 4000); - err = reset_control_assert(dc->rst); - if (err < 0) - return err; + err = reset_control_assert(dc->rst); + if (err < 0) + return err; - usleep_range(2000, 4000); + usleep_range(2000, 4000); - clk_disable_unprepare(dc->clk); - } + clk_disable_unprepare(dc->clk); if (dc->soc->has_powergate) { if (dc->pipe == 0) @@ -2086,12 +2289,10 @@ static int tegra_dc_suspend(struct device *dev) struct tegra_dc *dc = dev_get_drvdata(dev); int err; - if (!dc->soc->broken_reset) { - err = reset_control_assert(dc->rst); - if (err < 0) { - dev_err(dev, "failed to assert reset: %d\n", err); - return err; - } + err = reset_control_assert(dc->rst); + if (err < 0) { + dev_err(dev, "failed to assert reset: %d\n", err); + return err; } if (dc->soc->has_powergate) @@ -2121,13 +2322,10 @@ static int tegra_dc_resume(struct device *dev) return err; } - if (!dc->soc->broken_reset) { - err = reset_control_deassert(dc->rst); - if (err < 0) { - dev_err(dev, - "failed to deassert reset: %d\n", err); - return err; - } + err = reset_control_deassert(dc->rst); + if (err < 0) { + dev_err(dev, "failed to deassert reset: %d\n", err); + return err; } } diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h index cb100b6e3282..096a81ad6d8d 100644 --- a/drivers/gpu/drm/tegra/dc.h +++ b/drivers/gpu/drm/tegra/dc.h @@ -18,6 +18,24 @@ struct tegra_output; +struct tegra_dc_state { + struct drm_crtc_state base; + + struct clk *clk; + unsigned long pclk; + unsigned int div; + + u32 planes; +}; + +static inline struct tegra_dc_state *to_dc_state(struct drm_crtc_state *state) +{ + if (state) + return container_of(state, struct tegra_dc_state, base); + + return NULL; +} + struct tegra_dc_stats { unsigned long frames; unsigned long vblank; @@ -25,21 +43,35 @@ struct tegra_dc_stats { unsigned long overflow; }; +struct tegra_windowgroup_soc { + unsigned int index; + unsigned int dc; + const unsigned int *windows; + unsigned int num_windows; +}; + struct tegra_dc_soc_info { - bool supports_border_color; + bool supports_background_color; bool supports_interlacing; bool supports_cursor; bool supports_block_linear; + bool supports_blending; unsigned int pitch_align; bool has_powergate; - bool broken_reset; + bool coupled_pm; + bool has_nvdisplay; + const struct tegra_windowgroup_soc *wgrps; + unsigned int num_wgrps; + const u32 *primary_formats; + unsigned int num_primary_formats; + const u32 *overlay_formats; + unsigned int num_overlay_formats; }; struct tegra_dc { struct host1x_client client; struct host1x_syncpt *syncpt; struct device *dev; - spinlock_t lock; struct drm_crtc base; unsigned int powergate; @@ -56,11 +88,6 @@ struct tegra_dc { struct list_head list; struct drm_info_list *debugfs_files; - struct drm_minor *minor; - struct dentry *debugfs; - - /* page-flip handling */ - struct drm_pending_vblank_event *event; const struct tegra_dc_soc_info *soc; @@ -110,6 +137,7 @@ struct tegra_dc_window { unsigned int bits_per_pixel; unsigned int stride[2]; unsigned long base[3]; + unsigned int zpos; bool bottom_up; struct tegra_bo_tiling tiling; @@ -118,6 +146,7 @@ struct tegra_dc_window { }; /* from dc.c */ +bool tegra_dc_has_output(struct tegra_dc *dc, struct device *dev); void tegra_dc_commit(struct tegra_dc *dc); int tegra_dc_state_setup_clock(struct tegra_dc *dc, struct drm_crtc_state *crtc_state, @@ -167,15 +196,26 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc); #define DC_CMD_INT_ENABLE 0x039 #define DC_CMD_INT_TYPE 0x03a #define DC_CMD_INT_POLARITY 0x03b -#define CTXSW_INT (1 << 0) -#define FRAME_END_INT (1 << 1) -#define VBLANK_INT (1 << 2) -#define WIN_A_UF_INT (1 << 8) -#define WIN_B_UF_INT (1 << 9) -#define WIN_C_UF_INT (1 << 10) -#define WIN_A_OF_INT (1 << 14) -#define WIN_B_OF_INT (1 << 15) -#define WIN_C_OF_INT (1 << 16) +#define CTXSW_INT (1 << 0) +#define FRAME_END_INT (1 << 1) +#define VBLANK_INT (1 << 2) +#define V_PULSE3_INT (1 << 4) +#define V_PULSE2_INT (1 << 5) +#define REGION_CRC_INT (1 << 6) +#define REG_TMOUT_INT (1 << 7) +#define WIN_A_UF_INT (1 << 8) +#define WIN_B_UF_INT (1 << 9) +#define WIN_C_UF_INT (1 << 10) +#define MSF_INT (1 << 12) +#define WIN_A_OF_INT (1 << 14) +#define WIN_B_OF_INT (1 << 15) +#define WIN_C_OF_INT (1 << 16) +#define HEAD_UF_INT (1 << 23) +#define SD3_BUCKET_WALK_DONE_INT (1 << 24) +#define DSC_OBUF_UF_INT (1 << 26) +#define DSC_RBUF_UF_INT (1 << 27) +#define DSC_BBUF_UF_INT (1 << 28) +#define DSC_TO_UF_INT (1 << 29) #define DC_CMD_SIGNAL_RAISE1 0x03c #define DC_CMD_SIGNAL_RAISE2 0x03d @@ -196,6 +236,8 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc); #define WIN_B_UPDATE (1 << 10) #define WIN_C_UPDATE (1 << 11) #define CURSOR_UPDATE (1 << 15) +#define COMMON_ACTREQ (1 << 16) +#define COMMON_UPDATE (1 << 17) #define NC_HOST_TRIG (1 << 24) #define DC_CMD_DISPLAY_WINDOW_HEADER 0x042 @@ -238,6 +280,10 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc); #define DC_COM_GPIO_DEBOUNCE_COUNTER 0x328 #define DC_COM_CRC_CHECKSUM_LATCHED 0x329 +#define DC_COM_RG_UNDERFLOW 0x365 +#define UNDERFLOW_MODE_RED (1 << 8) +#define UNDERFLOW_REPORT_ENABLE (1 << 0) + #define DC_DISP_DISP_SIGNAL_OPTIONS0 0x400 #define H_PULSE0_ENABLE (1 << 8) #define H_PULSE1_ENABLE (1 << 10) @@ -249,10 +295,10 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc); #define HDMI_ENABLE (1 << 30) #define DSI_ENABLE (1 << 29) #define SOR1_TIMING_CYA (1 << 27) -#define SOR1_ENABLE (1 << 26) -#define SOR_ENABLE (1 << 25) #define CURSOR_ENABLE (1 << 16) +#define SOR_ENABLE(x) (1 << (25 + (x))) + #define DC_DISP_DISP_MEM_HIGH_PRIORITY 0x403 #define CURSOR_THRESHOLD(x) (((x) & 0x03) << 24) #define WINDOW_A_THRESHOLD(x) (((x) & 0x7f) << 16) @@ -360,29 +406,33 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc); #define DISP_ORDER_BLUE_RED (1 << 9) #define DC_DISP_DISP_COLOR_CONTROL 0x430 -#define BASE_COLOR_SIZE666 (0 << 0) -#define BASE_COLOR_SIZE111 (1 << 0) -#define BASE_COLOR_SIZE222 (2 << 0) -#define BASE_COLOR_SIZE333 (3 << 0) -#define BASE_COLOR_SIZE444 (4 << 0) -#define BASE_COLOR_SIZE555 (5 << 0) -#define BASE_COLOR_SIZE565 (6 << 0) -#define BASE_COLOR_SIZE332 (7 << 0) -#define BASE_COLOR_SIZE888 (8 << 0) +#define BASE_COLOR_SIZE666 ( 0 << 0) +#define BASE_COLOR_SIZE111 ( 1 << 0) +#define BASE_COLOR_SIZE222 ( 2 << 0) +#define BASE_COLOR_SIZE333 ( 3 << 0) +#define BASE_COLOR_SIZE444 ( 4 << 0) +#define BASE_COLOR_SIZE555 ( 5 << 0) +#define BASE_COLOR_SIZE565 ( 6 << 0) +#define BASE_COLOR_SIZE332 ( 7 << 0) +#define BASE_COLOR_SIZE888 ( 8 << 0) +#define BASE_COLOR_SIZE101010 (10 << 0) +#define BASE_COLOR_SIZE121212 (12 << 0) #define DITHER_CONTROL_MASK (3 << 8) #define DITHER_CONTROL_DISABLE (0 << 8) #define DITHER_CONTROL_ORDERED (2 << 8) #define DITHER_CONTROL_ERRDIFF (3 << 8) #define BASE_COLOR_SIZE_MASK (0xf << 0) -#define BASE_COLOR_SIZE_666 (0 << 0) -#define BASE_COLOR_SIZE_111 (1 << 0) -#define BASE_COLOR_SIZE_222 (2 << 0) -#define BASE_COLOR_SIZE_333 (3 << 0) -#define BASE_COLOR_SIZE_444 (4 << 0) -#define BASE_COLOR_SIZE_555 (5 << 0) -#define BASE_COLOR_SIZE_565 (6 << 0) -#define BASE_COLOR_SIZE_332 (7 << 0) -#define BASE_COLOR_SIZE_888 (8 << 0) +#define BASE_COLOR_SIZE_666 ( 0 << 0) +#define BASE_COLOR_SIZE_111 ( 1 << 0) +#define BASE_COLOR_SIZE_222 ( 2 << 0) +#define BASE_COLOR_SIZE_333 ( 3 << 0) +#define BASE_COLOR_SIZE_444 ( 4 << 0) +#define BASE_COLOR_SIZE_555 ( 5 << 0) +#define BASE_COLOR_SIZE_565 ( 6 << 0) +#define BASE_COLOR_SIZE_332 ( 7 << 0) +#define BASE_COLOR_SIZE_888 ( 8 << 0) +#define BASE_COLOR_SIZE_101010 ( 10 << 0) +#define BASE_COLOR_SIZE_121212 ( 12 << 0) #define DC_DISP_SHIFT_CLOCK_OPTIONS 0x431 #define SC1_H_QUALIFIER_NONE (1 << 16) @@ -449,6 +499,12 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc); #define DC_DISP_SD_HW_K_VALUES 0x4dd #define DC_DISP_SD_MAN_K_VALUES 0x4de +#define DC_DISP_BLEND_BACKGROUND_COLOR 0x4e4 +#define BACKGROUND_COLOR_ALPHA(x) (((x) & 0xff) << 24) +#define BACKGROUND_COLOR_BLUE(x) (((x) & 0xff) << 16) +#define BACKGROUND_COLOR_GREEN(x) (((x) & 0xff) << 8) +#define BACKGROUND_COLOR_RED(x) (((x) & 0xff) << 0) + #define DC_DISP_INTERLACE_CONTROL 0x4e5 #define INTERLACE_STATUS (1 << 2) #define INTERLACE_START (1 << 1) @@ -467,6 +523,35 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc); #define CURSOR_SRC_BLEND_MASK (3 << 8) #define CURSOR_ALPHA 0xff +#define DC_WIN_CORE_ACT_CONTROL 0x50e +#define VCOUNTER (0 << 0) +#define HCOUNTER (1 << 0) + +#define DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLA 0x543 +#define LATENCY_CTL_MODE_ENABLE (1 << 2) + +#define DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLB 0x544 +#define WATERMARK_MASK 0x1fffffff + +#define DC_WIN_CORE_PRECOMP_WGRP_PIPE_METER 0x560 +#define PIPE_METER_INT(x) (((x) & 0xff) << 8) +#define PIPE_METER_FRAC(x) (((x) & 0xff) << 0) + +#define DC_WIN_CORE_IHUB_WGRP_POOL_CONFIG 0x561 +#define MEMPOOL_ENTRIES(x) (((x) & 0xffff) << 0) + +#define DC_WIN_CORE_IHUB_WGRP_FETCH_METER 0x562 +#define SLOTS(x) (((x) & 0xff) << 0) + +#define DC_WIN_CORE_IHUB_LINEBUF_CONFIG 0x563 +#define MODE_TWO_LINES (0 << 14) +#define MODE_FOUR_LINES (1 << 14) + +#define DC_WIN_CORE_IHUB_THREAD_GROUP 0x568 +#define THREAD_NUM_MASK (0x1f << 1) +#define THREAD_NUM(x) (((x) & 0x1f) << 1) +#define THREAD_GROUP_ENABLE (1 << 0) + #define DC_WIN_CSC_YOF 0x611 #define DC_WIN_CSC_KYRGB 0x612 #define DC_WIN_CSC_KUR 0x613 @@ -502,9 +587,9 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc); #define WIN_COLOR_DEPTH_P4 2 #define WIN_COLOR_DEPTH_P8 3 #define WIN_COLOR_DEPTH_B4G4R4A4 4 -#define WIN_COLOR_DEPTH_B5G5R5A 5 +#define WIN_COLOR_DEPTH_B5G5R5A1 5 #define WIN_COLOR_DEPTH_B5G6R5 6 -#define WIN_COLOR_DEPTH_AB5G5R5 7 +#define WIN_COLOR_DEPTH_A1B5G5R5 7 #define WIN_COLOR_DEPTH_B8G8R8A8 12 #define WIN_COLOR_DEPTH_R8G8B8A8 13 #define WIN_COLOR_DEPTH_B6x2G6x2R6x2A8 14 @@ -519,18 +604,32 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc); #define WIN_COLOR_DEPTH_YUV422R 23 #define WIN_COLOR_DEPTH_YCbCr422RA 24 #define WIN_COLOR_DEPTH_YUV422RA 25 +#define WIN_COLOR_DEPTH_R4G4B4A4 27 +#define WIN_COLOR_DEPTH_R5G5B5A 28 +#define WIN_COLOR_DEPTH_AR5G5B5 29 +#define WIN_COLOR_DEPTH_B5G5R5X1 30 +#define WIN_COLOR_DEPTH_X1B5G5R5 31 +#define WIN_COLOR_DEPTH_R5G5B5X1 32 +#define WIN_COLOR_DEPTH_X1R5G5B5 33 +#define WIN_COLOR_DEPTH_R5G6B5 34 +#define WIN_COLOR_DEPTH_A8R8G8B8 35 +#define WIN_COLOR_DEPTH_A8B8G8R8 36 +#define WIN_COLOR_DEPTH_B8G8R8X8 37 +#define WIN_COLOR_DEPTH_R8G8B8X8 38 +#define WIN_COLOR_DEPTH_X8B8G8R8 65 +#define WIN_COLOR_DEPTH_X8R8G8B8 66 #define DC_WIN_POSITION 0x704 -#define H_POSITION(x) (((x) & 0x1fff) << 0) -#define V_POSITION(x) (((x) & 0x1fff) << 16) +#define H_POSITION(x) (((x) & 0x1fff) << 0) /* XXX 0x7fff on Tegra186 */ +#define V_POSITION(x) (((x) & 0x1fff) << 16) /* XXX 0x7fff on Tegra186 */ #define DC_WIN_SIZE 0x705 -#define H_SIZE(x) (((x) & 0x1fff) << 0) -#define V_SIZE(x) (((x) & 0x1fff) << 16) +#define H_SIZE(x) (((x) & 0x1fff) << 0) /* XXX 0x7fff on Tegra186 */ +#define V_SIZE(x) (((x) & 0x1fff) << 16) /* XXX 0x7fff on Tegra186 */ #define DC_WIN_PRESCALED_SIZE 0x706 #define H_PRESCALED_SIZE(x) (((x) & 0x7fff) << 0) -#define V_PRESCALED_SIZE(x) (((x) & 0x1fff) << 16) +#define V_PRESCALED_SIZE(x) (((x) & 0x1fff) << 16) /* XXX 0x7fff on Tegra186 */ #define DC_WIN_H_INITIAL_DDA 0x707 #define DC_WIN_V_INITIAL_DDA 0x708 @@ -546,11 +645,24 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc); #define DC_WIN_BUFFER_ADDR_MODE_TILE (1 << 0) #define DC_WIN_BUFFER_ADDR_MODE_LINEAR_UV (0 << 16) #define DC_WIN_BUFFER_ADDR_MODE_TILE_UV (1 << 16) + #define DC_WIN_DV_CONTROL 0x70e #define DC_WIN_BLEND_NOKEY 0x70f +#define BLEND_WEIGHT1(x) (((x) & 0xff) << 16) +#define BLEND_WEIGHT0(x) (((x) & 0xff) << 8) + #define DC_WIN_BLEND_1WIN 0x710 +#define BLEND_CONTROL_FIX (0 << 2) +#define BLEND_CONTROL_ALPHA (1 << 2) +#define BLEND_COLOR_KEY_NONE (0 << 0) +#define BLEND_COLOR_KEY_0 (1 << 0) +#define BLEND_COLOR_KEY_1 (2 << 0) +#define BLEND_COLOR_KEY_BOTH (3 << 0) + #define DC_WIN_BLEND_2WIN_X 0x711 +#define BLEND_CONTROL_DEPENDENT (2 << 2) + #define DC_WIN_BLEND_2WIN_Y 0x712 #define DC_WIN_BLEND_3WIN_XY 0x713 @@ -575,8 +687,97 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc); #define DC_WINBUF_SURFACE_KIND_BLOCK (2 << 0) #define DC_WINBUF_SURFACE_KIND_BLOCK_HEIGHT(x) (((x) & 0x7) << 4) +#define DC_WINBUF_START_ADDR_HI 0x80d + +#define DC_WINBUF_CDE_CONTROL 0x82f +#define ENABLE_SURFACE (1 << 0) + #define DC_WINBUF_AD_UFLOW_STATUS 0xbca #define DC_WINBUF_BD_UFLOW_STATUS 0xdca #define DC_WINBUF_CD_UFLOW_STATUS 0xfca +/* Tegra186 and later */ +#define DC_DISP_CORE_SOR_SET_CONTROL(x) (0x403 + (x)) +#define PROTOCOL_MASK (0xf << 8) +#define PROTOCOL_SINGLE_TMDS_A (0x1 << 8) + +#define DC_WIN_CORE_WINDOWGROUP_SET_CONTROL 0x702 +#define OWNER_MASK (0xf << 0) +#define OWNER(x) (((x) & 0xf) << 0) + +#define DC_WIN_CROPPED_SIZE 0x706 + +#define DC_WIN_PLANAR_STORAGE 0x709 +#define PITCH(x) (((x) >> 6) & 0x1fff) + +#define DC_WIN_SET_PARAMS 0x70d +#define CLAMP_BEFORE_BLEND (1 << 15) +#define DEGAMMA_NONE (0 << 13) +#define DEGAMMA_SRGB (1 << 13) +#define DEGAMMA_YUV8_10 (2 << 13) +#define DEGAMMA_YUV12 (3 << 13) +#define INPUT_RANGE_BYPASS (0 << 10) +#define INPUT_RANGE_LIMITED (1 << 10) +#define INPUT_RANGE_FULL (2 << 10) +#define COLOR_SPACE_RGB (0 << 8) +#define COLOR_SPACE_YUV_601 (1 << 8) +#define COLOR_SPACE_YUV_709 (2 << 8) +#define COLOR_SPACE_YUV_2020 (3 << 8) + +#define DC_WIN_WINDOWGROUP_SET_CONTROL_INPUT_SCALER 0x70e +#define HORIZONTAL_TAPS_2 (1 << 3) +#define HORIZONTAL_TAPS_5 (4 << 3) +#define VERTICAL_TAPS_2 (1 << 0) +#define VERTICAL_TAPS_5 (4 << 0) + +#define DC_WIN_WINDOWGROUP_SET_INPUT_SCALER_USAGE 0x711 +#define INPUT_SCALER_USE422 (1 << 2) +#define INPUT_SCALER_VBYPASS (1 << 1) +#define INPUT_SCALER_HBYPASS (1 << 0) + +#define DC_WIN_BLEND_LAYER_CONTROL 0x716 +#define COLOR_KEY_NONE (0 << 25) +#define COLOR_KEY_SRC (1 << 25) +#define COLOR_KEY_DST (2 << 25) +#define BLEND_BYPASS (1 << 24) +#define K2(x) (((x) & 0xff) << 16) +#define K1(x) (((x) & 0xff) << 8) +#define WINDOW_LAYER_DEPTH(x) (((x) & 0xff) << 0) + +#define DC_WIN_BLEND_MATCH_SELECT 0x717 +#define BLEND_FACTOR_DST_ALPHA_ZERO (0 << 12) +#define BLEND_FACTOR_DST_ALPHA_ONE (1 << 12) +#define BLEND_FACTOR_DST_ALPHA_NEG_K1_TIMES_SRC (2 << 12) +#define BLEND_FACTOR_DST_ALPHA_K2 (3 << 12) +#define BLEND_FACTOR_SRC_ALPHA_ZERO (0 << 8) +#define BLEND_FACTOR_SRC_ALPHA_K1 (1 << 8) +#define BLEND_FACTOR_SRC_ALPHA_K2 (2 << 8) +#define BLEND_FACTOR_SRC_ALPHA_NEG_K1_TIMES_DST (3 << 8) +#define BLEND_FACTOR_DST_COLOR_ZERO (0 << 4) +#define BLEND_FACTOR_DST_COLOR_ONE (1 << 4) +#define BLEND_FACTOR_DST_COLOR_K1 (2 << 4) +#define BLEND_FACTOR_DST_COLOR_K2 (3 << 4) +#define BLEND_FACTOR_DST_COLOR_K1_TIMES_DST (4 << 4) +#define BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_DST (5 << 4) +#define BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_SRC (6 << 4) +#define BLEND_FACTOR_DST_COLOR_NEG_K1 (7 << 4) +#define BLEND_FACTOR_SRC_COLOR_ZERO (0 << 0) +#define BLEND_FACTOR_SRC_COLOR_ONE (1 << 0) +#define BLEND_FACTOR_SRC_COLOR_K1 (2 << 0) +#define BLEND_FACTOR_SRC_COLOR_K1_TIMES_DST (3 << 0) +#define BLEND_FACTOR_SRC_COLOR_NEG_K1_TIMES_DST (4 << 0) +#define BLEND_FACTOR_SRC_COLOR_K1_TIMES_SRC (5 << 0) + +#define DC_WIN_BLEND_NOMATCH_SELECT 0x718 + +#define DC_WIN_PRECOMP_WGRP_PARAMS 0x724 +#define SWAP_UV (1 << 0) + +#define DC_WIN_WINDOW_SET_CONTROL 0x730 +#define CONTROL_CSC_ENABLE (1 << 5) + +#define DC_WINBUF_CROPPED_POINT 0x806 +#define OFFSET_Y(x) (((x) & 0xffff) << 16) +#define OFFSET_X(x) (((x) & 0xffff) << 0) + #endif /* TEGRA_DC_H */ diff --git a/drivers/gpu/drm/tegra/dpaux.c b/drivers/gpu/drm/tegra/dpaux.c index e4da041ba89b..d84e81ff36ad 100644 --- a/drivers/gpu/drm/tegra/dpaux.c +++ b/drivers/gpu/drm/tegra/dpaux.c @@ -15,6 +15,7 @@ #include <linux/pinctrl/pinconf-generic.h> #include <linux/pinctrl/pinctrl.h> #include <linux/pinctrl/pinmux.h> +#include <linux/pm_runtime.h> #include <linux/platform_device.h> #include <linux/reset.h> #include <linux/regulator/consumer.h> @@ -321,6 +322,9 @@ static int tegra_dpaux_pad_config(struct tegra_dpaux *dpaux, unsigned function) case DPAUX_PADCTL_FUNC_I2C: value = DPAUX_HYBRID_PADCTL_I2C_SDA_INPUT_RCV | DPAUX_HYBRID_PADCTL_I2C_SCL_INPUT_RCV | + DPAUX_HYBRID_PADCTL_AUX_CMH(2) | + DPAUX_HYBRID_PADCTL_AUX_DRVZ(4) | + DPAUX_HYBRID_PADCTL_AUX_DRVI(0x18) | DPAUX_HYBRID_PADCTL_MODE_I2C; break; @@ -467,52 +471,37 @@ static int tegra_dpaux_probe(struct platform_device *pdev) return PTR_ERR(dpaux->clk); } - err = clk_prepare_enable(dpaux->clk); - if (err < 0) { - dev_err(&pdev->dev, "failed to enable module clock: %d\n", - err); - return err; - } - - if (dpaux->rst) - reset_control_deassert(dpaux->rst); - dpaux->clk_parent = devm_clk_get(&pdev->dev, "parent"); if (IS_ERR(dpaux->clk_parent)) { dev_err(&pdev->dev, "failed to get parent clock: %ld\n", PTR_ERR(dpaux->clk_parent)); - err = PTR_ERR(dpaux->clk_parent); - goto assert_reset; - } - - err = clk_prepare_enable(dpaux->clk_parent); - if (err < 0) { - dev_err(&pdev->dev, "failed to enable parent clock: %d\n", - err); - goto assert_reset; + return PTR_ERR(dpaux->clk_parent); } err = clk_set_rate(dpaux->clk_parent, 270000000); if (err < 0) { dev_err(&pdev->dev, "failed to set clock to 270 MHz: %d\n", err); - goto disable_parent_clk; + return err; } dpaux->vdd = devm_regulator_get(&pdev->dev, "vdd"); if (IS_ERR(dpaux->vdd)) { dev_err(&pdev->dev, "failed to get VDD supply: %ld\n", PTR_ERR(dpaux->vdd)); - err = PTR_ERR(dpaux->vdd); - goto disable_parent_clk; + return PTR_ERR(dpaux->vdd); } + platform_set_drvdata(pdev, dpaux); + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + err = devm_request_irq(dpaux->dev, dpaux->irq, tegra_dpaux_irq, 0, dev_name(dpaux->dev), dpaux); if (err < 0) { dev_err(dpaux->dev, "failed to request IRQ#%u: %d\n", dpaux->irq, err); - goto disable_parent_clk; + return err; } disable_irq(dpaux->irq); @@ -522,7 +511,7 @@ static int tegra_dpaux_probe(struct platform_device *pdev) err = drm_dp_aux_register(&dpaux->aux); if (err < 0) - goto disable_parent_clk; + return err; /* * Assume that by default the DPAUX/I2C pads will be used for HDMI, @@ -560,47 +549,97 @@ static int tegra_dpaux_probe(struct platform_device *pdev) list_add_tail(&dpaux->list, &dpaux_list); mutex_unlock(&dpaux_lock); - platform_set_drvdata(pdev, dpaux); - return 0; - -disable_parent_clk: - clk_disable_unprepare(dpaux->clk_parent); -assert_reset: - if (dpaux->rst) - reset_control_assert(dpaux->rst); - - clk_disable_unprepare(dpaux->clk); - - return err; } static int tegra_dpaux_remove(struct platform_device *pdev) { struct tegra_dpaux *dpaux = platform_get_drvdata(pdev); + cancel_work_sync(&dpaux->work); + /* make sure pads are powered down when not in use */ tegra_dpaux_pad_power_down(dpaux); + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + drm_dp_aux_unregister(&dpaux->aux); mutex_lock(&dpaux_lock); list_del(&dpaux->list); mutex_unlock(&dpaux_lock); - cancel_work_sync(&dpaux->work); + return 0; +} - clk_disable_unprepare(dpaux->clk_parent); +#ifdef CONFIG_PM +static int tegra_dpaux_suspend(struct device *dev) +{ + struct tegra_dpaux *dpaux = dev_get_drvdata(dev); + int err = 0; + + if (dpaux->rst) { + err = reset_control_assert(dpaux->rst); + if (err < 0) { + dev_err(dev, "failed to assert reset: %d\n", err); + return err; + } + } - if (dpaux->rst) - reset_control_assert(dpaux->rst); + usleep_range(1000, 2000); + clk_disable_unprepare(dpaux->clk_parent); clk_disable_unprepare(dpaux->clk); + return err; +} + +static int tegra_dpaux_resume(struct device *dev) +{ + struct tegra_dpaux *dpaux = dev_get_drvdata(dev); + int err; + + err = clk_prepare_enable(dpaux->clk); + if (err < 0) { + dev_err(dev, "failed to enable clock: %d\n", err); + return err; + } + + err = clk_prepare_enable(dpaux->clk_parent); + if (err < 0) { + dev_err(dev, "failed to enable parent clock: %d\n", err); + goto disable_clk; + } + + usleep_range(1000, 2000); + + if (dpaux->rst) { + err = reset_control_deassert(dpaux->rst); + if (err < 0) { + dev_err(dev, "failed to deassert reset: %d\n", err); + goto disable_parent; + } + + usleep_range(1000, 2000); + } + return 0; + +disable_parent: + clk_disable_unprepare(dpaux->clk_parent); +disable_clk: + clk_disable_unprepare(dpaux->clk); + return err; } +#endif + +static const struct dev_pm_ops tegra_dpaux_pm_ops = { + SET_RUNTIME_PM_OPS(tegra_dpaux_suspend, tegra_dpaux_resume, NULL) +}; static const struct of_device_id tegra_dpaux_of_match[] = { + { .compatible = "nvidia,tegra186-dpaux", }, { .compatible = "nvidia,tegra210-dpaux", }, { .compatible = "nvidia,tegra124-dpaux", }, { }, @@ -611,6 +650,7 @@ struct platform_driver tegra_dpaux_driver = { .driver = { .name = "tegra-dpaux", .of_match_table = tegra_dpaux_of_match, + .pm = &tegra_dpaux_pm_ops, }, .probe = tegra_dpaux_probe, .remove = tegra_dpaux_remove, diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index f157bc675269..d50bddb2e447 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -33,97 +33,91 @@ struct tegra_drm_file { struct mutex lock; }; -static void tegra_atomic_schedule(struct tegra_drm *tegra, - struct drm_atomic_state *state) +static int tegra_atomic_check(struct drm_device *drm, + struct drm_atomic_state *state) { - tegra->commit.state = state; - schedule_work(&tegra->commit.work); -} + int err; -static void tegra_atomic_complete(struct tegra_drm *tegra, - struct drm_atomic_state *state) -{ - struct drm_device *drm = tegra->drm; + err = drm_atomic_helper_check_modeset(drm, state); + if (err < 0) + return err; - /* - * Everything below can be run asynchronously without the need to grab - * any modeset locks at all under one condition: It must be guaranteed - * that the asynchronous work has either been cancelled (if the driver - * supports it, which at least requires that the framebuffers get - * cleaned up with drm_atomic_helper_cleanup_planes()) or completed - * before the new state gets committed on the software side with - * drm_atomic_helper_swap_state(). - * - * This scheme allows new atomic state updates to be prepared and - * checked in parallel to the asynchronous completion of the previous - * update. Which is important since compositors need to figure out the - * composition of the next frame right after having submitted the - * current layout. - */ + err = drm_atomic_normalize_zpos(drm, state); + if (err < 0) + return err; - drm_atomic_helper_commit_modeset_disables(drm, state); - drm_atomic_helper_commit_modeset_enables(drm, state); - drm_atomic_helper_commit_planes(drm, state, - DRM_PLANE_COMMIT_ACTIVE_ONLY); + err = drm_atomic_helper_check_planes(drm, state); + if (err < 0) + return err; - drm_atomic_helper_wait_for_vblanks(drm, state); + if (state->legacy_cursor_update) + state->async_update = !drm_atomic_helper_async_check(drm, state); - drm_atomic_helper_cleanup_planes(drm, state); - drm_atomic_state_put(state); + return 0; } -static void tegra_atomic_work(struct work_struct *work) +static struct drm_atomic_state * +tegra_atomic_state_alloc(struct drm_device *drm) { - struct tegra_drm *tegra = container_of(work, struct tegra_drm, - commit.work); + struct tegra_atomic_state *state = kzalloc(sizeof(*state), GFP_KERNEL); + + if (!state || drm_atomic_state_init(drm, &state->base) < 0) { + kfree(state); + return NULL; + } - tegra_atomic_complete(tegra, tegra->commit.state); + return &state->base; } -static int tegra_atomic_commit(struct drm_device *drm, - struct drm_atomic_state *state, bool nonblock) +static void tegra_atomic_state_clear(struct drm_atomic_state *state) { - struct tegra_drm *tegra = drm->dev_private; - int err; - - err = drm_atomic_helper_prepare_planes(drm, state); - if (err) - return err; - - /* serialize outstanding nonblocking commits */ - mutex_lock(&tegra->commit.lock); - flush_work(&tegra->commit.work); - - /* - * This is the point of no return - everything below never fails except - * when the hw goes bonghits. Which means we can commit the new state on - * the software side now. - */ - - err = drm_atomic_helper_swap_state(state, true); - if (err) { - mutex_unlock(&tegra->commit.lock); - drm_atomic_helper_cleanup_planes(drm, state); - return err; - } + struct tegra_atomic_state *tegra = to_tegra_atomic_state(state); - drm_atomic_state_get(state); - if (nonblock) - tegra_atomic_schedule(tegra, state); - else - tegra_atomic_complete(tegra, state); + drm_atomic_state_default_clear(state); + tegra->clk_disp = NULL; + tegra->dc = NULL; + tegra->rate = 0; +} - mutex_unlock(&tegra->commit.lock); - return 0; +static void tegra_atomic_state_free(struct drm_atomic_state *state) +{ + drm_atomic_state_default_release(state); + kfree(state); } -static const struct drm_mode_config_funcs tegra_drm_mode_funcs = { +static const struct drm_mode_config_funcs tegra_drm_mode_config_funcs = { .fb_create = tegra_fb_create, #ifdef CONFIG_DRM_FBDEV_EMULATION .output_poll_changed = drm_fb_helper_output_poll_changed, #endif - .atomic_check = drm_atomic_helper_check, - .atomic_commit = tegra_atomic_commit, + .atomic_check = tegra_atomic_check, + .atomic_commit = drm_atomic_helper_commit, + .atomic_state_alloc = tegra_atomic_state_alloc, + .atomic_state_clear = tegra_atomic_state_clear, + .atomic_state_free = tegra_atomic_state_free, +}; + +static void tegra_atomic_commit_tail(struct drm_atomic_state *old_state) +{ + struct drm_device *drm = old_state->dev; + struct tegra_drm *tegra = drm->dev_private; + + if (tegra->hub) { + drm_atomic_helper_commit_modeset_disables(drm, old_state); + tegra_display_hub_atomic_commit(drm, old_state); + drm_atomic_helper_commit_planes(drm, old_state, 0); + drm_atomic_helper_commit_modeset_enables(drm, old_state); + drm_atomic_helper_commit_hw_done(old_state); + drm_atomic_helper_wait_for_vblanks(drm, old_state); + drm_atomic_helper_cleanup_planes(drm, old_state); + } else { + drm_atomic_helper_commit_tail_rpm(old_state); + } +} + +static const struct drm_mode_config_helper_funcs +tegra_drm_mode_config_helpers = { + .atomic_commit_tail = tegra_atomic_commit_tail, }; static int tegra_drm_load(struct drm_device *drm, unsigned long flags) @@ -172,9 +166,6 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) mutex_init(&tegra->clients_lock); INIT_LIST_HEAD(&tegra->clients); - mutex_init(&tegra->commit.lock); - INIT_WORK(&tegra->commit.work, tegra_atomic_work); - drm->dev_private = tegra; tegra->drm = drm; @@ -188,7 +179,8 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) drm->mode_config.allow_fb_modifiers = true; - drm->mode_config.funcs = &tegra_drm_mode_funcs; + drm->mode_config.funcs = &tegra_drm_mode_config_funcs; + drm->mode_config.helper_private = &tegra_drm_mode_config_helpers; err = tegra_drm_fb_prepare(drm); if (err < 0) @@ -200,6 +192,12 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) if (err < 0) goto fbdev; + if (tegra->hub) { + err = tegra_display_hub_prepare(tegra->hub); + if (err < 0) + goto device; + } + /* * We don't use the drm_irq_install() helpers provided by the DRM * core, so we need to set this manually in order to allow the @@ -212,16 +210,19 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) err = drm_vblank_init(drm, drm->mode_config.num_crtc); if (err < 0) - goto device; + goto hub; drm_mode_config_reset(drm); err = tegra_drm_fb_init(drm); if (err < 0) - goto device; + goto hub; return 0; +hub: + if (tegra->hub) + tegra_display_hub_cleanup(tegra->hub); device: host1x_device_exit(device); fbdev: @@ -651,7 +652,8 @@ static int tegra_syncpt_wait(struct drm_device *drm, void *data, if (!sp) return -EINVAL; - return host1x_syncpt_wait(sp, args->thresh, args->timeout, + return host1x_syncpt_wait(sp, args->thresh, + msecs_to_jiffies(args->timeout), &args->value); } @@ -1139,8 +1141,7 @@ int tegra_drm_unregister_client(struct tegra_drm *tegra, return 0; } -void *tegra_drm_alloc(struct tegra_drm *tegra, size_t size, - dma_addr_t *dma) +void *tegra_drm_alloc(struct tegra_drm *tegra, size_t size, dma_addr_t *dma) { struct iova *alloc; void *virt; @@ -1308,6 +1309,10 @@ static const struct of_device_id host1x_drm_subdevs[] = { { .compatible = "nvidia,tegra210-sor", }, { .compatible = "nvidia,tegra210-sor1", }, { .compatible = "nvidia,tegra210-vic", }, + { .compatible = "nvidia,tegra186-display", }, + { .compatible = "nvidia,tegra186-dc", }, + { .compatible = "nvidia,tegra186-sor", }, + { .compatible = "nvidia,tegra186-sor1", }, { .compatible = "nvidia,tegra186-vic", }, { /* sentinel */ } }; @@ -1323,6 +1328,7 @@ static struct host1x_driver host1x_drm_driver = { }; static struct platform_driver * const drivers[] = { + &tegra_display_hub_driver, &tegra_dc_driver, &tegra_hdmi_driver, &tegra_dsi_driver, diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index 0009f6ea21b6..73b661ce7086 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -16,6 +16,7 @@ #include <linux/of_gpio.h> #include <drm/drmP.h> +#include <drm/drm_atomic.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> #include <drm/drm_encoder.h> @@ -23,6 +24,7 @@ #include <drm/drm_fixed.h> #include "gem.h" +#include "hub.h" #include "trace.h" struct reset_control; @@ -40,10 +42,25 @@ struct tegra_fbdev { }; #endif +struct tegra_atomic_state { + struct drm_atomic_state base; + + struct clk *clk_disp; + struct tegra_dc *dc; + unsigned long rate; +}; + +static inline struct tegra_atomic_state * +to_tegra_atomic_state(struct drm_atomic_state *state) +{ + return container_of(state, struct tegra_atomic_state, base); +} + struct tegra_drm { struct drm_device *drm; struct iommu_domain *domain; + struct iommu_group *group; struct mutex mm_lock; struct drm_mm mm; @@ -62,11 +79,7 @@ struct tegra_drm { unsigned int pitch_align; - struct { - struct drm_atomic_state *state; - struct work_struct work; - struct mutex lock; - } commit; + struct tegra_display_hub *hub; struct drm_atomic_state *state; }; @@ -152,6 +165,8 @@ int tegra_output_probe(struct tegra_output *output); void tegra_output_remove(struct tegra_output *output); int tegra_output_init(struct drm_device *drm, struct tegra_output *output); void tegra_output_exit(struct tegra_output *output); +void tegra_output_find_possible_crtcs(struct tegra_output *output, + struct drm_device *drm); int tegra_output_connector_get_modes(struct drm_connector *connector); enum drm_connector_status @@ -189,6 +204,7 @@ void tegra_drm_fb_exit(struct drm_device *drm); void tegra_drm_fb_suspend(struct drm_device *drm); void tegra_drm_fb_resume(struct drm_device *drm); +extern struct platform_driver tegra_display_hub_driver; extern struct platform_driver tegra_dc_driver; extern struct platform_driver tegra_hdmi_driver; extern struct platform_driver tegra_dsi_driver; diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 046649ec9441..4d2ed966f9e3 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -65,8 +65,6 @@ struct tegra_dsi { struct clk *clk; struct drm_info_list *debugfs_files; - struct drm_minor *minor; - struct dentry *debugfs; unsigned long flags; enum mipi_dsi_pixel_format format; @@ -122,12 +120,89 @@ static inline void tegra_dsi_writel(struct tegra_dsi *dsi, u32 value, writel(value, dsi->regs + (offset << 2)); } +#define DEBUGFS_REG32(_name) { .name = #_name, .offset = _name } + +static const struct debugfs_reg32 tegra_dsi_regs[] = { + DEBUGFS_REG32(DSI_INCR_SYNCPT), + DEBUGFS_REG32(DSI_INCR_SYNCPT_CONTROL), + DEBUGFS_REG32(DSI_INCR_SYNCPT_ERROR), + DEBUGFS_REG32(DSI_CTXSW), + DEBUGFS_REG32(DSI_RD_DATA), + DEBUGFS_REG32(DSI_WR_DATA), + DEBUGFS_REG32(DSI_POWER_CONTROL), + DEBUGFS_REG32(DSI_INT_ENABLE), + DEBUGFS_REG32(DSI_INT_STATUS), + DEBUGFS_REG32(DSI_INT_MASK), + DEBUGFS_REG32(DSI_HOST_CONTROL), + DEBUGFS_REG32(DSI_CONTROL), + DEBUGFS_REG32(DSI_SOL_DELAY), + DEBUGFS_REG32(DSI_MAX_THRESHOLD), + DEBUGFS_REG32(DSI_TRIGGER), + DEBUGFS_REG32(DSI_TX_CRC), + DEBUGFS_REG32(DSI_STATUS), + DEBUGFS_REG32(DSI_INIT_SEQ_CONTROL), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_0), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_1), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_2), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_3), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_4), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_5), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_6), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_7), + DEBUGFS_REG32(DSI_PKT_SEQ_0_LO), + DEBUGFS_REG32(DSI_PKT_SEQ_0_HI), + DEBUGFS_REG32(DSI_PKT_SEQ_1_LO), + DEBUGFS_REG32(DSI_PKT_SEQ_1_HI), + DEBUGFS_REG32(DSI_PKT_SEQ_2_LO), + DEBUGFS_REG32(DSI_PKT_SEQ_2_HI), + DEBUGFS_REG32(DSI_PKT_SEQ_3_LO), + DEBUGFS_REG32(DSI_PKT_SEQ_3_HI), + DEBUGFS_REG32(DSI_PKT_SEQ_4_LO), + DEBUGFS_REG32(DSI_PKT_SEQ_4_HI), + DEBUGFS_REG32(DSI_PKT_SEQ_5_LO), + DEBUGFS_REG32(DSI_PKT_SEQ_5_HI), + DEBUGFS_REG32(DSI_DCS_CMDS), + DEBUGFS_REG32(DSI_PKT_LEN_0_1), + DEBUGFS_REG32(DSI_PKT_LEN_2_3), + DEBUGFS_REG32(DSI_PKT_LEN_4_5), + DEBUGFS_REG32(DSI_PKT_LEN_6_7), + DEBUGFS_REG32(DSI_PHY_TIMING_0), + DEBUGFS_REG32(DSI_PHY_TIMING_1), + DEBUGFS_REG32(DSI_PHY_TIMING_2), + DEBUGFS_REG32(DSI_BTA_TIMING), + DEBUGFS_REG32(DSI_TIMEOUT_0), + DEBUGFS_REG32(DSI_TIMEOUT_1), + DEBUGFS_REG32(DSI_TO_TALLY), + DEBUGFS_REG32(DSI_PAD_CONTROL_0), + DEBUGFS_REG32(DSI_PAD_CONTROL_CD), + DEBUGFS_REG32(DSI_PAD_CD_STATUS), + DEBUGFS_REG32(DSI_VIDEO_MODE_CONTROL), + DEBUGFS_REG32(DSI_PAD_CONTROL_1), + DEBUGFS_REG32(DSI_PAD_CONTROL_2), + DEBUGFS_REG32(DSI_PAD_CONTROL_3), + DEBUGFS_REG32(DSI_PAD_CONTROL_4), + DEBUGFS_REG32(DSI_GANGED_MODE_CONTROL), + DEBUGFS_REG32(DSI_GANGED_MODE_START), + DEBUGFS_REG32(DSI_GANGED_MODE_SIZE), + DEBUGFS_REG32(DSI_RAW_DATA_BYTE_COUNT), + DEBUGFS_REG32(DSI_ULTRA_LOW_POWER_CONTROL), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_8), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_9), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_10), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_11), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_12), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_13), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_14), + DEBUGFS_REG32(DSI_INIT_SEQ_DATA_15), +}; + static int tegra_dsi_show_regs(struct seq_file *s, void *data) { struct drm_info_node *node = s->private; struct tegra_dsi *dsi = node->info_ent->data; struct drm_crtc *crtc = dsi->output.encoder.crtc; struct drm_device *drm = node->minor->dev; + unsigned int i; int err = 0; drm_modeset_lock_all(drm); @@ -137,93 +212,12 @@ static int tegra_dsi_show_regs(struct seq_file *s, void *data) goto unlock; } -#define DUMP_REG(name) \ - seq_printf(s, "%-32s %#05x %08x\n", #name, name, \ - tegra_dsi_readl(dsi, name)) - - DUMP_REG(DSI_INCR_SYNCPT); - DUMP_REG(DSI_INCR_SYNCPT_CONTROL); - DUMP_REG(DSI_INCR_SYNCPT_ERROR); - DUMP_REG(DSI_CTXSW); - DUMP_REG(DSI_RD_DATA); - DUMP_REG(DSI_WR_DATA); - DUMP_REG(DSI_POWER_CONTROL); - DUMP_REG(DSI_INT_ENABLE); - DUMP_REG(DSI_INT_STATUS); - DUMP_REG(DSI_INT_MASK); - DUMP_REG(DSI_HOST_CONTROL); - DUMP_REG(DSI_CONTROL); - DUMP_REG(DSI_SOL_DELAY); - DUMP_REG(DSI_MAX_THRESHOLD); - DUMP_REG(DSI_TRIGGER); - DUMP_REG(DSI_TX_CRC); - DUMP_REG(DSI_STATUS); - - DUMP_REG(DSI_INIT_SEQ_CONTROL); - DUMP_REG(DSI_INIT_SEQ_DATA_0); - DUMP_REG(DSI_INIT_SEQ_DATA_1); - DUMP_REG(DSI_INIT_SEQ_DATA_2); - DUMP_REG(DSI_INIT_SEQ_DATA_3); - DUMP_REG(DSI_INIT_SEQ_DATA_4); - DUMP_REG(DSI_INIT_SEQ_DATA_5); - DUMP_REG(DSI_INIT_SEQ_DATA_6); - DUMP_REG(DSI_INIT_SEQ_DATA_7); - - DUMP_REG(DSI_PKT_SEQ_0_LO); - DUMP_REG(DSI_PKT_SEQ_0_HI); - DUMP_REG(DSI_PKT_SEQ_1_LO); - DUMP_REG(DSI_PKT_SEQ_1_HI); - DUMP_REG(DSI_PKT_SEQ_2_LO); - DUMP_REG(DSI_PKT_SEQ_2_HI); - DUMP_REG(DSI_PKT_SEQ_3_LO); - DUMP_REG(DSI_PKT_SEQ_3_HI); - DUMP_REG(DSI_PKT_SEQ_4_LO); - DUMP_REG(DSI_PKT_SEQ_4_HI); - DUMP_REG(DSI_PKT_SEQ_5_LO); - DUMP_REG(DSI_PKT_SEQ_5_HI); - - DUMP_REG(DSI_DCS_CMDS); - - DUMP_REG(DSI_PKT_LEN_0_1); - DUMP_REG(DSI_PKT_LEN_2_3); - DUMP_REG(DSI_PKT_LEN_4_5); - DUMP_REG(DSI_PKT_LEN_6_7); - - DUMP_REG(DSI_PHY_TIMING_0); - DUMP_REG(DSI_PHY_TIMING_1); - DUMP_REG(DSI_PHY_TIMING_2); - DUMP_REG(DSI_BTA_TIMING); - - DUMP_REG(DSI_TIMEOUT_0); - DUMP_REG(DSI_TIMEOUT_1); - DUMP_REG(DSI_TO_TALLY); - - DUMP_REG(DSI_PAD_CONTROL_0); - DUMP_REG(DSI_PAD_CONTROL_CD); - DUMP_REG(DSI_PAD_CD_STATUS); - DUMP_REG(DSI_VIDEO_MODE_CONTROL); - DUMP_REG(DSI_PAD_CONTROL_1); - DUMP_REG(DSI_PAD_CONTROL_2); - DUMP_REG(DSI_PAD_CONTROL_3); - DUMP_REG(DSI_PAD_CONTROL_4); - - DUMP_REG(DSI_GANGED_MODE_CONTROL); - DUMP_REG(DSI_GANGED_MODE_START); - DUMP_REG(DSI_GANGED_MODE_SIZE); - - DUMP_REG(DSI_RAW_DATA_BYTE_COUNT); - DUMP_REG(DSI_ULTRA_LOW_POWER_CONTROL); - - DUMP_REG(DSI_INIT_SEQ_DATA_8); - DUMP_REG(DSI_INIT_SEQ_DATA_9); - DUMP_REG(DSI_INIT_SEQ_DATA_10); - DUMP_REG(DSI_INIT_SEQ_DATA_11); - DUMP_REG(DSI_INIT_SEQ_DATA_12); - DUMP_REG(DSI_INIT_SEQ_DATA_13); - DUMP_REG(DSI_INIT_SEQ_DATA_14); - DUMP_REG(DSI_INIT_SEQ_DATA_15); - -#undef DUMP_REG + for (i = 0; i < ARRAY_SIZE(tegra_dsi_regs); i++) { + unsigned int offset = tegra_dsi_regs[i].offset; + + seq_printf(s, "%-32s %#05x %08x\n", tegra_dsi_regs[i].name, + offset, tegra_dsi_readl(dsi, offset)); + } unlock: drm_modeset_unlock_all(drm); @@ -234,58 +228,46 @@ static struct drm_info_list debugfs_files[] = { { "regs", tegra_dsi_show_regs, 0, NULL }, }; -static int tegra_dsi_debugfs_init(struct tegra_dsi *dsi, - struct drm_minor *minor) +static int tegra_dsi_late_register(struct drm_connector *connector) { - const char *name = dev_name(dsi->dev); - unsigned int i; + struct tegra_output *output = connector_to_output(connector); + unsigned int i, count = ARRAY_SIZE(debugfs_files); + struct drm_minor *minor = connector->dev->primary; + struct dentry *root = connector->debugfs_entry; + struct tegra_dsi *dsi = to_dsi(output); int err; - dsi->debugfs = debugfs_create_dir(name, minor->debugfs_root); - if (!dsi->debugfs) - return -ENOMEM; - dsi->debugfs_files = kmemdup(debugfs_files, sizeof(debugfs_files), GFP_KERNEL); - if (!dsi->debugfs_files) { - err = -ENOMEM; - goto remove; - } + if (!dsi->debugfs_files) + return -ENOMEM; - for (i = 0; i < ARRAY_SIZE(debugfs_files); i++) + for (i = 0; i < count; i++) dsi->debugfs_files[i].data = dsi; - err = drm_debugfs_create_files(dsi->debugfs_files, - ARRAY_SIZE(debugfs_files), - dsi->debugfs, minor); + err = drm_debugfs_create_files(dsi->debugfs_files, count, root, minor); if (err < 0) goto free; - dsi->minor = minor; - return 0; free: kfree(dsi->debugfs_files); dsi->debugfs_files = NULL; -remove: - debugfs_remove(dsi->debugfs); - dsi->debugfs = NULL; return err; } -static void tegra_dsi_debugfs_exit(struct tegra_dsi *dsi) +static void tegra_dsi_early_unregister(struct drm_connector *connector) { - drm_debugfs_remove_files(dsi->debugfs_files, ARRAY_SIZE(debugfs_files), - dsi->minor); - dsi->minor = NULL; + struct tegra_output *output = connector_to_output(connector); + unsigned int count = ARRAY_SIZE(debugfs_files); + struct tegra_dsi *dsi = to_dsi(output); + drm_debugfs_remove_files(dsi->debugfs_files, count, + connector->dev->primary); kfree(dsi->debugfs_files); dsi->debugfs_files = NULL; - - debugfs_remove(dsi->debugfs); - dsi->debugfs = NULL; } #define PKT_ID0(id) ((((id) & 0x3f) << 3) | (1 << 9)) @@ -827,6 +809,8 @@ static const struct drm_connector_funcs tegra_dsi_connector_funcs = { .destroy = tegra_output_connector_destroy, .atomic_duplicate_state = tegra_dsi_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .late_register = tegra_dsi_late_register, + .early_unregister = tegra_dsi_early_unregister, }; static enum drm_mode_status @@ -1080,12 +1064,6 @@ static int tegra_dsi_init(struct host1x_client *client) dsi->output.encoder.possible_crtcs = 0x3; } - if (IS_ENABLED(CONFIG_DEBUG_FS)) { - err = tegra_dsi_debugfs_init(dsi, drm->primary); - if (err < 0) - dev_err(dsi->dev, "debugfs setup failed: %d\n", err); - } - return 0; } @@ -1094,10 +1072,6 @@ static int tegra_dsi_exit(struct host1x_client *client) struct tegra_dsi *dsi = host1x_client_to_dsi(client); tegra_output_exit(&dsi->output); - - if (IS_ENABLED(CONFIG_DEBUG_FS)) - tegra_dsi_debugfs_exit(dsi); - regulator_disable(dsi->vdd); return 0; diff --git a/drivers/gpu/drm/tegra/fb.c b/drivers/gpu/drm/tegra/fb.c index 8dfe3c6c217e..001cb77e2f59 100644 --- a/drivers/gpu/drm/tegra/fb.c +++ b/drivers/gpu/drm/tegra/fb.c @@ -54,17 +54,40 @@ int tegra_fb_get_tiling(struct drm_framebuffer *framebuffer, struct tegra_fb *fb = to_tegra_fb(framebuffer); uint64_t modifier = fb->base.modifier; - switch (fourcc_mod_tegra_mod(modifier)) { - case NV_FORMAT_MOD_TEGRA_TILED: + switch (modifier) { + case DRM_FORMAT_MOD_NVIDIA_TEGRA_TILED: tiling->mode = TEGRA_BO_TILING_MODE_TILED; tiling->value = 0; break; - case NV_FORMAT_MOD_TEGRA_16BX2_BLOCK(0): + case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(0): tiling->mode = TEGRA_BO_TILING_MODE_BLOCK; - tiling->value = fourcc_mod_tegra_param(modifier); - if (tiling->value > 5) - return -EINVAL; + tiling->value = 0; + break; + + case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(1): + tiling->mode = TEGRA_BO_TILING_MODE_BLOCK; + tiling->value = 1; + break; + + case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(2): + tiling->mode = TEGRA_BO_TILING_MODE_BLOCK; + tiling->value = 2; + break; + + case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(3): + tiling->mode = TEGRA_BO_TILING_MODE_BLOCK; + tiling->value = 3; + break; + + case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(4): + tiling->mode = TEGRA_BO_TILING_MODE_BLOCK; + tiling->value = 4; + break; + + case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(5): + tiling->mode = TEGRA_BO_TILING_MODE_BLOCK; + tiling->value = 5; break; default: @@ -230,6 +253,7 @@ static int tegra_fbdev_probe(struct drm_fb_helper *helper, cmd.height = sizes->surface_height; cmd.pitches[0] = round_up(sizes->surface_width * bytes_per_pixel, tegra->pitch_align); + cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index ab1e53d434e8..49b9bf28f872 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -114,7 +114,7 @@ static const struct host1x_bo_ops tegra_bo_ops = { static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo) { int prot = IOMMU_READ | IOMMU_WRITE; - ssize_t err; + int err; if (bo->mm) return -EBUSY; @@ -128,22 +128,21 @@ static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo) err = drm_mm_insert_node_generic(&tegra->mm, bo->mm, bo->gem.size, PAGE_SIZE, 0, 0); if (err < 0) { - dev_err(tegra->drm->dev, "out of I/O virtual memory: %zd\n", + dev_err(tegra->drm->dev, "out of I/O virtual memory: %d\n", err); goto unlock; } bo->paddr = bo->mm->start; - err = iommu_map_sg(tegra->domain, bo->paddr, bo->sgt->sgl, - bo->sgt->nents, prot); - if (err < 0) { - dev_err(tegra->drm->dev, "failed to map buffer: %zd\n", err); + bo->size = iommu_map_sg(tegra->domain, bo->paddr, bo->sgt->sgl, + bo->sgt->nents, prot); + if (!bo->size) { + dev_err(tegra->drm->dev, "failed to map buffer\n"); + err = -ENOMEM; goto remove; } - bo->size = err; - mutex_unlock(&tegra->mm_lock); return 0; diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 6434b3d3d1ba..784739a9f497 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -79,8 +79,6 @@ struct tegra_hdmi { bool dvi; struct drm_info_list *debugfs_files; - struct drm_minor *minor; - struct dentry *debugfs; }; static inline struct tegra_hdmi * @@ -910,6 +908,249 @@ tegra_hdmi_connector_detect(struct drm_connector *connector, bool force) return status; } +#define DEBUGFS_REG32(_name) { .name = #_name, .offset = _name } + +static const struct debugfs_reg32 tegra_hdmi_regs[] = { + DEBUGFS_REG32(HDMI_CTXSW), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_STATE0), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_STATE1), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_STATE2), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_AN_MSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_AN_LSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_CN_MSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_CN_LSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_AKSV_MSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_AKSV_LSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_BKSV_MSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_BKSV_LSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_CKSV_MSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_CKSV_LSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_DKSV_MSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_DKSV_LSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_CTRL), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_CMODE), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_MPRIME_MSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_MPRIME_LSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_SPRIME_MSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB2), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB1), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_RI), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_CS_MSB), + DEBUGFS_REG32(HDMI_NV_PDISP_RG_HDCP_CS_LSB), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AUDIO_EMU0), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AUDIO_EMU_RDATA0), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AUDIO_EMU1), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AUDIO_EMU2), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_STATUS), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_HEADER), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_LOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_HIGH), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_STATUS), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_HEADER), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_LOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_HIGH), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_LOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_HIGH), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_GENERIC_CTRL), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_GENERIC_STATUS), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_GENERIC_HEADER), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK0_LOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK0_HIGH), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK1_LOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK1_HIGH), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK2_LOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK2_HIGH), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK3_LOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK3_HIGH), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_CTRL), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_0320_SUBPACK_LOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_0320_SUBPACK_HIGH), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_0882_SUBPACK_LOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_0882_SUBPACK_HIGH), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_1764_SUBPACK_LOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_1764_SUBPACK_HIGH), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_0480_SUBPACK_LOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_0480_SUBPACK_HIGH), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_0960_SUBPACK_LOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_0960_SUBPACK_HIGH), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_1920_SUBPACK_LOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_ACR_1920_SUBPACK_HIGH), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_CTRL), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_VSYNC_KEEPOUT), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_VSYNC_WINDOW), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_GCP_CTRL), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_GCP_STATUS), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_GCP_SUBPACK), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_CHANNEL_STATUS1), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_CHANNEL_STATUS2), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_EMU0), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_EMU1), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_EMU1_RDATA), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_SPARE), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_SPDIF_CHN_STATUS1), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_SPDIF_CHN_STATUS2), + DEBUGFS_REG32(HDMI_NV_PDISP_HDMI_HDCPRIF_ROM_CTRL), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_CAP), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_PWR), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_TEST), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_PLL0), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_PLL1), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_PLL2), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_CSTM), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_LVDS), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_CRCA), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_CRCB), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_BLANK), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_CTL), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(0)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(1)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(2)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(3)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(4)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(5)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(6)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(7)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(8)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(9)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(10)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(11)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(12)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(13)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(14)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_SEQ_INST(15)), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_VCRCA0), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_VCRCA1), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_CCRCA0), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_CCRCA1), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_EDATAA0), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_EDATAA1), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_COUNTA0), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_COUNTA1), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_DEBUGA0), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_DEBUGA1), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_TRIG), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_MSCHECK), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT), + DEBUGFS_REG32(HDMI_NV_PDISP_AUDIO_DEBUG0), + DEBUGFS_REG32(HDMI_NV_PDISP_AUDIO_DEBUG1), + DEBUGFS_REG32(HDMI_NV_PDISP_AUDIO_DEBUG2), + DEBUGFS_REG32(HDMI_NV_PDISP_AUDIO_FS(0)), + DEBUGFS_REG32(HDMI_NV_PDISP_AUDIO_FS(1)), + DEBUGFS_REG32(HDMI_NV_PDISP_AUDIO_FS(2)), + DEBUGFS_REG32(HDMI_NV_PDISP_AUDIO_FS(3)), + DEBUGFS_REG32(HDMI_NV_PDISP_AUDIO_FS(4)), + DEBUGFS_REG32(HDMI_NV_PDISP_AUDIO_FS(5)), + DEBUGFS_REG32(HDMI_NV_PDISP_AUDIO_FS(6)), + DEBUGFS_REG32(HDMI_NV_PDISP_AUDIO_PULSE_WIDTH), + DEBUGFS_REG32(HDMI_NV_PDISP_AUDIO_THRESHOLD), + DEBUGFS_REG32(HDMI_NV_PDISP_AUDIO_CNTRL0), + DEBUGFS_REG32(HDMI_NV_PDISP_AUDIO_N), + DEBUGFS_REG32(HDMI_NV_PDISP_HDCPRIF_ROM_TIMING), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_REFCLK), + DEBUGFS_REG32(HDMI_NV_PDISP_CRC_CONTROL), + DEBUGFS_REG32(HDMI_NV_PDISP_INPUT_CONTROL), + DEBUGFS_REG32(HDMI_NV_PDISP_SCRATCH), + DEBUGFS_REG32(HDMI_NV_PDISP_PE_CURRENT), + DEBUGFS_REG32(HDMI_NV_PDISP_KEY_CTRL), + DEBUGFS_REG32(HDMI_NV_PDISP_KEY_DEBUG0), + DEBUGFS_REG32(HDMI_NV_PDISP_KEY_DEBUG1), + DEBUGFS_REG32(HDMI_NV_PDISP_KEY_DEBUG2), + DEBUGFS_REG32(HDMI_NV_PDISP_KEY_HDCP_KEY_0), + DEBUGFS_REG32(HDMI_NV_PDISP_KEY_HDCP_KEY_1), + DEBUGFS_REG32(HDMI_NV_PDISP_KEY_HDCP_KEY_2), + DEBUGFS_REG32(HDMI_NV_PDISP_KEY_HDCP_KEY_3), + DEBUGFS_REG32(HDMI_NV_PDISP_KEY_HDCP_KEY_TRIG), + DEBUGFS_REG32(HDMI_NV_PDISP_KEY_SKEY_INDEX), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_AUDIO_CNTRL0), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_AUDIO_SPARE0), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH1), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE), + DEBUGFS_REG32(HDMI_NV_PDISP_INT_STATUS), + DEBUGFS_REG32(HDMI_NV_PDISP_INT_MASK), + DEBUGFS_REG32(HDMI_NV_PDISP_INT_ENABLE), + DEBUGFS_REG32(HDMI_NV_PDISP_SOR_IO_PEAK_CURRENT), +}; + +static int tegra_hdmi_show_regs(struct seq_file *s, void *data) +{ + struct drm_info_node *node = s->private; + struct tegra_hdmi *hdmi = node->info_ent->data; + struct drm_crtc *crtc = hdmi->output.encoder.crtc; + struct drm_device *drm = node->minor->dev; + unsigned int i; + int err = 0; + + drm_modeset_lock_all(drm); + + if (!crtc || !crtc->state->active) { + err = -EBUSY; + goto unlock; + } + + for (i = 0; i < ARRAY_SIZE(tegra_hdmi_regs); i++) { + unsigned int offset = tegra_hdmi_regs[i].offset; + + seq_printf(s, "%-56s %#05x %08x\n", tegra_hdmi_regs[i].name, + offset, tegra_hdmi_readl(hdmi, offset)); + } + +unlock: + drm_modeset_unlock_all(drm); + return err; +} + +static struct drm_info_list debugfs_files[] = { + { "regs", tegra_hdmi_show_regs, 0, NULL }, +}; + +static int tegra_hdmi_late_register(struct drm_connector *connector) +{ + struct tegra_output *output = connector_to_output(connector); + unsigned int i, count = ARRAY_SIZE(debugfs_files); + struct drm_minor *minor = connector->dev->primary; + struct dentry *root = connector->debugfs_entry; + struct tegra_hdmi *hdmi = to_hdmi(output); + int err; + + hdmi->debugfs_files = kmemdup(debugfs_files, sizeof(debugfs_files), + GFP_KERNEL); + if (!hdmi->debugfs_files) + return -ENOMEM; + + for (i = 0; i < count; i++) + hdmi->debugfs_files[i].data = hdmi; + + err = drm_debugfs_create_files(hdmi->debugfs_files, count, root, minor); + if (err < 0) + goto free; + + return 0; + +free: + kfree(hdmi->debugfs_files); + hdmi->debugfs_files = NULL; + + return err; +} + +static void tegra_hdmi_early_unregister(struct drm_connector *connector) +{ + struct tegra_output *output = connector_to_output(connector); + struct drm_minor *minor = connector->dev->primary; + unsigned int count = ARRAY_SIZE(debugfs_files); + struct tegra_hdmi *hdmi = to_hdmi(output); + + drm_debugfs_remove_files(hdmi->debugfs_files, count, minor); + kfree(hdmi->debugfs_files); + hdmi->debugfs_files = NULL; +} + static const struct drm_connector_funcs tegra_hdmi_connector_funcs = { .reset = drm_atomic_helper_connector_reset, .detect = tegra_hdmi_connector_detect, @@ -917,6 +1158,8 @@ static const struct drm_connector_funcs tegra_hdmi_connector_funcs = { .destroy = tegra_output_connector_destroy, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .late_register = tegra_hdmi_late_register, + .early_unregister = tegra_hdmi_early_unregister, }; static enum drm_mode_status @@ -1225,254 +1468,6 @@ static const struct drm_encoder_helper_funcs tegra_hdmi_encoder_helper_funcs = { .atomic_check = tegra_hdmi_encoder_atomic_check, }; -static int tegra_hdmi_show_regs(struct seq_file *s, void *data) -{ - struct drm_info_node *node = s->private; - struct tegra_hdmi *hdmi = node->info_ent->data; - struct drm_crtc *crtc = hdmi->output.encoder.crtc; - struct drm_device *drm = node->minor->dev; - int err = 0; - - drm_modeset_lock_all(drm); - - if (!crtc || !crtc->state->active) { - err = -EBUSY; - goto unlock; - } - -#define DUMP_REG(name) \ - seq_printf(s, "%-56s %#05x %08x\n", #name, name, \ - tegra_hdmi_readl(hdmi, name)) - - DUMP_REG(HDMI_CTXSW); - DUMP_REG(HDMI_NV_PDISP_SOR_STATE0); - DUMP_REG(HDMI_NV_PDISP_SOR_STATE1); - DUMP_REG(HDMI_NV_PDISP_SOR_STATE2); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_AN_MSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_AN_LSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CN_MSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CN_LSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_AKSV_MSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_AKSV_LSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_BKSV_MSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_BKSV_LSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CKSV_MSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CKSV_LSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_DKSV_MSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_DKSV_LSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CTRL); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CMODE); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_MPRIME_MSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_MPRIME_LSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_SPRIME_MSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB2); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB1); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_RI); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CS_MSB); - DUMP_REG(HDMI_NV_PDISP_RG_HDCP_CS_LSB); - DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_EMU0); - DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_EMU_RDATA0); - DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_EMU1); - DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_EMU2); - DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL); - DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_STATUS); - DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_HEADER); - DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_LOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_HIGH); - DUMP_REG(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL); - DUMP_REG(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_STATUS); - DUMP_REG(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_HEADER); - DUMP_REG(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_LOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_HIGH); - DUMP_REG(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_LOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_HIGH); - DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_CTRL); - DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_STATUS); - DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_HEADER); - DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK0_LOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK0_HIGH); - DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK1_LOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK1_HIGH); - DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK2_LOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK2_HIGH); - DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK3_LOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK3_HIGH); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_CTRL); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0320_SUBPACK_LOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0320_SUBPACK_HIGH); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0882_SUBPACK_LOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0882_SUBPACK_HIGH); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_1764_SUBPACK_LOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_1764_SUBPACK_HIGH); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0480_SUBPACK_LOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0480_SUBPACK_HIGH); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0960_SUBPACK_LOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_0960_SUBPACK_HIGH); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_1920_SUBPACK_LOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_ACR_1920_SUBPACK_HIGH); - DUMP_REG(HDMI_NV_PDISP_HDMI_CTRL); - DUMP_REG(HDMI_NV_PDISP_HDMI_VSYNC_KEEPOUT); - DUMP_REG(HDMI_NV_PDISP_HDMI_VSYNC_WINDOW); - DUMP_REG(HDMI_NV_PDISP_HDMI_GCP_CTRL); - DUMP_REG(HDMI_NV_PDISP_HDMI_GCP_STATUS); - DUMP_REG(HDMI_NV_PDISP_HDMI_GCP_SUBPACK); - DUMP_REG(HDMI_NV_PDISP_HDMI_CHANNEL_STATUS1); - DUMP_REG(HDMI_NV_PDISP_HDMI_CHANNEL_STATUS2); - DUMP_REG(HDMI_NV_PDISP_HDMI_EMU0); - DUMP_REG(HDMI_NV_PDISP_HDMI_EMU1); - DUMP_REG(HDMI_NV_PDISP_HDMI_EMU1_RDATA); - DUMP_REG(HDMI_NV_PDISP_HDMI_SPARE); - DUMP_REG(HDMI_NV_PDISP_HDMI_SPDIF_CHN_STATUS1); - DUMP_REG(HDMI_NV_PDISP_HDMI_SPDIF_CHN_STATUS2); - DUMP_REG(HDMI_NV_PDISP_HDMI_HDCPRIF_ROM_CTRL); - DUMP_REG(HDMI_NV_PDISP_SOR_CAP); - DUMP_REG(HDMI_NV_PDISP_SOR_PWR); - DUMP_REG(HDMI_NV_PDISP_SOR_TEST); - DUMP_REG(HDMI_NV_PDISP_SOR_PLL0); - DUMP_REG(HDMI_NV_PDISP_SOR_PLL1); - DUMP_REG(HDMI_NV_PDISP_SOR_PLL2); - DUMP_REG(HDMI_NV_PDISP_SOR_CSTM); - DUMP_REG(HDMI_NV_PDISP_SOR_LVDS); - DUMP_REG(HDMI_NV_PDISP_SOR_CRCA); - DUMP_REG(HDMI_NV_PDISP_SOR_CRCB); - DUMP_REG(HDMI_NV_PDISP_SOR_BLANK); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_CTL); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(0)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(1)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(2)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(3)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(4)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(5)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(6)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(7)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(8)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(9)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(10)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(11)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(12)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(13)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(14)); - DUMP_REG(HDMI_NV_PDISP_SOR_SEQ_INST(15)); - DUMP_REG(HDMI_NV_PDISP_SOR_VCRCA0); - DUMP_REG(HDMI_NV_PDISP_SOR_VCRCA1); - DUMP_REG(HDMI_NV_PDISP_SOR_CCRCA0); - DUMP_REG(HDMI_NV_PDISP_SOR_CCRCA1); - DUMP_REG(HDMI_NV_PDISP_SOR_EDATAA0); - DUMP_REG(HDMI_NV_PDISP_SOR_EDATAA1); - DUMP_REG(HDMI_NV_PDISP_SOR_COUNTA0); - DUMP_REG(HDMI_NV_PDISP_SOR_COUNTA1); - DUMP_REG(HDMI_NV_PDISP_SOR_DEBUGA0); - DUMP_REG(HDMI_NV_PDISP_SOR_DEBUGA1); - DUMP_REG(HDMI_NV_PDISP_SOR_TRIG); - DUMP_REG(HDMI_NV_PDISP_SOR_MSCHECK); - DUMP_REG(HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT); - DUMP_REG(HDMI_NV_PDISP_AUDIO_DEBUG0); - DUMP_REG(HDMI_NV_PDISP_AUDIO_DEBUG1); - DUMP_REG(HDMI_NV_PDISP_AUDIO_DEBUG2); - DUMP_REG(HDMI_NV_PDISP_AUDIO_FS(0)); - DUMP_REG(HDMI_NV_PDISP_AUDIO_FS(1)); - DUMP_REG(HDMI_NV_PDISP_AUDIO_FS(2)); - DUMP_REG(HDMI_NV_PDISP_AUDIO_FS(3)); - DUMP_REG(HDMI_NV_PDISP_AUDIO_FS(4)); - DUMP_REG(HDMI_NV_PDISP_AUDIO_FS(5)); - DUMP_REG(HDMI_NV_PDISP_AUDIO_FS(6)); - DUMP_REG(HDMI_NV_PDISP_AUDIO_PULSE_WIDTH); - DUMP_REG(HDMI_NV_PDISP_AUDIO_THRESHOLD); - DUMP_REG(HDMI_NV_PDISP_AUDIO_CNTRL0); - DUMP_REG(HDMI_NV_PDISP_AUDIO_N); - DUMP_REG(HDMI_NV_PDISP_HDCPRIF_ROM_TIMING); - DUMP_REG(HDMI_NV_PDISP_SOR_REFCLK); - DUMP_REG(HDMI_NV_PDISP_CRC_CONTROL); - DUMP_REG(HDMI_NV_PDISP_INPUT_CONTROL); - DUMP_REG(HDMI_NV_PDISP_SCRATCH); - DUMP_REG(HDMI_NV_PDISP_PE_CURRENT); - DUMP_REG(HDMI_NV_PDISP_KEY_CTRL); - DUMP_REG(HDMI_NV_PDISP_KEY_DEBUG0); - DUMP_REG(HDMI_NV_PDISP_KEY_DEBUG1); - DUMP_REG(HDMI_NV_PDISP_KEY_DEBUG2); - DUMP_REG(HDMI_NV_PDISP_KEY_HDCP_KEY_0); - DUMP_REG(HDMI_NV_PDISP_KEY_HDCP_KEY_1); - DUMP_REG(HDMI_NV_PDISP_KEY_HDCP_KEY_2); - DUMP_REG(HDMI_NV_PDISP_KEY_HDCP_KEY_3); - DUMP_REG(HDMI_NV_PDISP_KEY_HDCP_KEY_TRIG); - DUMP_REG(HDMI_NV_PDISP_KEY_SKEY_INDEX); - DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_CNTRL0); - DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_SPARE0); - DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0); - DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH1); - DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR); - DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE); - DUMP_REG(HDMI_NV_PDISP_INT_STATUS); - DUMP_REG(HDMI_NV_PDISP_INT_MASK); - DUMP_REG(HDMI_NV_PDISP_INT_ENABLE); - DUMP_REG(HDMI_NV_PDISP_SOR_IO_PEAK_CURRENT); - -#undef DUMP_REG - -unlock: - drm_modeset_unlock_all(drm); - return err; -} - -static struct drm_info_list debugfs_files[] = { - { "regs", tegra_hdmi_show_regs, 0, NULL }, -}; - -static int tegra_hdmi_debugfs_init(struct tegra_hdmi *hdmi, - struct drm_minor *minor) -{ - unsigned int i; - int err; - - hdmi->debugfs = debugfs_create_dir("hdmi", minor->debugfs_root); - if (!hdmi->debugfs) - return -ENOMEM; - - hdmi->debugfs_files = kmemdup(debugfs_files, sizeof(debugfs_files), - GFP_KERNEL); - if (!hdmi->debugfs_files) { - err = -ENOMEM; - goto remove; - } - - for (i = 0; i < ARRAY_SIZE(debugfs_files); i++) - hdmi->debugfs_files[i].data = hdmi; - - err = drm_debugfs_create_files(hdmi->debugfs_files, - ARRAY_SIZE(debugfs_files), - hdmi->debugfs, minor); - if (err < 0) - goto free; - - hdmi->minor = minor; - - return 0; - -free: - kfree(hdmi->debugfs_files); - hdmi->debugfs_files = NULL; -remove: - debugfs_remove(hdmi->debugfs); - hdmi->debugfs = NULL; - - return err; -} - -static void tegra_hdmi_debugfs_exit(struct tegra_hdmi *hdmi) -{ - drm_debugfs_remove_files(hdmi->debugfs_files, ARRAY_SIZE(debugfs_files), - hdmi->minor); - hdmi->minor = NULL; - - kfree(hdmi->debugfs_files); - hdmi->debugfs_files = NULL; - - debugfs_remove(hdmi->debugfs); - hdmi->debugfs = NULL; -} - static int tegra_hdmi_init(struct host1x_client *client) { struct drm_device *drm = dev_get_drvdata(client->parent); @@ -1505,12 +1500,6 @@ static int tegra_hdmi_init(struct host1x_client *client) hdmi->output.encoder.possible_crtcs = 0x3; - if (IS_ENABLED(CONFIG_DEBUG_FS)) { - err = tegra_hdmi_debugfs_init(hdmi, drm->primary); - if (err < 0) - dev_err(client->dev, "debugfs setup failed: %d\n", err); - } - err = regulator_enable(hdmi->hdmi); if (err < 0) { dev_err(client->dev, "failed to enable HDMI regulator: %d\n", @@ -1543,9 +1532,6 @@ static int tegra_hdmi_exit(struct host1x_client *client) regulator_disable(hdmi->pll); regulator_disable(hdmi->hdmi); - if (IS_ENABLED(CONFIG_DEBUG_FS)) - tegra_hdmi_debugfs_exit(hdmi); - return 0; } diff --git a/drivers/gpu/drm/tegra/hub.c b/drivers/gpu/drm/tegra/hub.c new file mode 100644 index 000000000000..e10a47d57313 --- /dev/null +++ b/drivers/gpu/drm/tegra/hub.c @@ -0,0 +1,806 @@ +/* + * Copyright (C) 2017 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/host1x.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> + +#include "drm.h" +#include "dc.h" +#include "plane.h" + +static const u32 tegra_shared_plane_formats[] = { + DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + /* new on Tegra114 */ + DRM_FORMAT_ABGR4444, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_BGR565, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + /* planar formats */ + DRM_FORMAT_UYVY, + DRM_FORMAT_YUYV, + DRM_FORMAT_YUV420, + DRM_FORMAT_YUV422, +}; + +static inline unsigned int tegra_plane_offset(struct tegra_plane *plane, + unsigned int offset) +{ + if (offset >= 0x500 && offset <= 0x581) { + offset = 0x000 + (offset - 0x500); + return plane->offset + offset; + } + + if (offset >= 0x700 && offset <= 0x73c) { + offset = 0x180 + (offset - 0x700); + return plane->offset + offset; + } + + if (offset >= 0x800 && offset <= 0x83e) { + offset = 0x1c0 + (offset - 0x800); + return plane->offset + offset; + } + + dev_WARN(plane->dc->dev, "invalid offset: %x\n", offset); + + return plane->offset + offset; +} + +static inline u32 tegra_plane_readl(struct tegra_plane *plane, + unsigned int offset) +{ + return tegra_dc_readl(plane->dc, tegra_plane_offset(plane, offset)); +} + +static inline void tegra_plane_writel(struct tegra_plane *plane, u32 value, + unsigned int offset) +{ + tegra_dc_writel(plane->dc, value, tegra_plane_offset(plane, offset)); +} + +static int tegra_windowgroup_enable(struct tegra_windowgroup *wgrp) +{ + mutex_lock(&wgrp->lock); + + if (wgrp->usecount == 0) { + pm_runtime_get_sync(wgrp->parent); + reset_control_deassert(wgrp->rst); + } + + wgrp->usecount++; + mutex_unlock(&wgrp->lock); + + return 0; +} + +static void tegra_windowgroup_disable(struct tegra_windowgroup *wgrp) +{ + int err; + + mutex_lock(&wgrp->lock); + + if (wgrp->usecount == 1) { + err = reset_control_assert(wgrp->rst); + if (err < 0) { + pr_err("failed to assert reset for window group %u\n", + wgrp->index); + } + + pm_runtime_put(wgrp->parent); + } + + wgrp->usecount--; + mutex_unlock(&wgrp->lock); +} + +int tegra_display_hub_prepare(struct tegra_display_hub *hub) +{ + unsigned int i; + + /* + * XXX Enabling/disabling windowgroups needs to happen when the owner + * display controller is disabled. There's currently no good point at + * which this could be executed, so unconditionally enable all window + * groups for now. + */ + for (i = 0; i < hub->soc->num_wgrps; i++) { + struct tegra_windowgroup *wgrp = &hub->wgrps[i]; + + tegra_windowgroup_enable(wgrp); + } + + return 0; +} + +void tegra_display_hub_cleanup(struct tegra_display_hub *hub) +{ + unsigned int i; + + /* + * XXX Remove this once window groups can be more fine-grainedly + * enabled and disabled. + */ + for (i = 0; i < hub->soc->num_wgrps; i++) { + struct tegra_windowgroup *wgrp = &hub->wgrps[i]; + + tegra_windowgroup_disable(wgrp); + } +} + +static void tegra_shared_plane_update(struct tegra_plane *plane) +{ + struct tegra_dc *dc = plane->dc; + unsigned long timeout; + u32 mask, value; + + mask = COMMON_UPDATE | WIN_A_UPDATE << plane->base.index; + tegra_dc_writel(dc, mask, DC_CMD_STATE_CONTROL); + + timeout = jiffies + msecs_to_jiffies(1000); + + while (time_before(jiffies, timeout)) { + value = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); + if ((value & mask) == 0) + break; + + usleep_range(100, 400); + } +} + +static void tegra_shared_plane_activate(struct tegra_plane *plane) +{ + struct tegra_dc *dc = plane->dc; + unsigned long timeout; + u32 mask, value; + + mask = COMMON_ACTREQ | WIN_A_ACT_REQ << plane->base.index; + tegra_dc_writel(dc, mask, DC_CMD_STATE_CONTROL); + + timeout = jiffies + msecs_to_jiffies(1000); + + while (time_before(jiffies, timeout)) { + value = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); + if ((value & mask) == 0) + break; + + usleep_range(100, 400); + } +} + +static unsigned int +tegra_shared_plane_get_owner(struct tegra_plane *plane, struct tegra_dc *dc) +{ + unsigned int offset = + tegra_plane_offset(plane, DC_WIN_CORE_WINDOWGROUP_SET_CONTROL); + + return tegra_dc_readl(dc, offset) & OWNER_MASK; +} + +static bool tegra_dc_owns_shared_plane(struct tegra_dc *dc, + struct tegra_plane *plane) +{ + struct device *dev = dc->dev; + + if (tegra_shared_plane_get_owner(plane, dc) == dc->pipe) { + if (plane->dc == dc) + return true; + + dev_WARN(dev, "head %u owns window %u but is not attached\n", + dc->pipe, plane->index); + } + + return false; +} + +static int tegra_shared_plane_set_owner(struct tegra_plane *plane, + struct tegra_dc *new) +{ + unsigned int offset = + tegra_plane_offset(plane, DC_WIN_CORE_WINDOWGROUP_SET_CONTROL); + struct tegra_dc *old = plane->dc, *dc = new ? new : old; + struct device *dev = new ? new->dev : old->dev; + unsigned int owner, index = plane->index; + u32 value; + + value = tegra_dc_readl(dc, offset); + owner = value & OWNER_MASK; + + if (new && (owner != OWNER_MASK && owner != new->pipe)) { + dev_WARN(dev, "window %u owned by head %u\n", index, owner); + return -EBUSY; + } + + /* + * This seems to happen whenever the head has been disabled with one + * or more windows being active. This is harmless because we'll just + * reassign the window to the new head anyway. + */ + if (old && owner == OWNER_MASK) + dev_dbg(dev, "window %u not owned by head %u but %u\n", index, + old->pipe, owner); + + value &= ~OWNER_MASK; + + if (new) + value |= OWNER(new->pipe); + else + value |= OWNER_MASK; + + tegra_dc_writel(dc, value, offset); + + plane->dc = new; + + return 0; +} + +static void tegra_dc_assign_shared_plane(struct tegra_dc *dc, + struct tegra_plane *plane) +{ + u32 value; + int err; + + if (!tegra_dc_owns_shared_plane(dc, plane)) { + err = tegra_shared_plane_set_owner(plane, dc); + if (err < 0) + return; + } + + value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_LINEBUF_CONFIG); + value |= MODE_FOUR_LINES; + tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_LINEBUF_CONFIG); + + value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_FETCH_METER); + value = SLOTS(1); + tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_FETCH_METER); + + /* disable watermark */ + value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLA); + value &= ~LATENCY_CTL_MODE_ENABLE; + tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLA); + + value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLB); + value |= WATERMARK_MASK; + tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLB); + + /* pipe meter */ + value = tegra_plane_readl(plane, DC_WIN_CORE_PRECOMP_WGRP_PIPE_METER); + value = PIPE_METER_INT(0) | PIPE_METER_FRAC(0); + tegra_plane_writel(plane, value, DC_WIN_CORE_PRECOMP_WGRP_PIPE_METER); + + /* mempool entries */ + value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_POOL_CONFIG); + value = MEMPOOL_ENTRIES(0x331); + tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_POOL_CONFIG); + + value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_THREAD_GROUP); + value &= ~THREAD_NUM_MASK; + value |= THREAD_NUM(plane->base.index); + value |= THREAD_GROUP_ENABLE; + tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_THREAD_GROUP); + + tegra_shared_plane_update(plane); + tegra_shared_plane_activate(plane); +} + +static void tegra_dc_remove_shared_plane(struct tegra_dc *dc, + struct tegra_plane *plane) +{ + tegra_shared_plane_set_owner(plane, NULL); +} + +static int tegra_shared_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct tegra_plane_state *plane_state = to_tegra_plane_state(state); + struct tegra_shared_plane *tegra = to_tegra_shared_plane(plane); + struct tegra_bo_tiling *tiling = &plane_state->tiling; + struct tegra_dc *dc = to_tegra_dc(state->crtc); + int err; + + /* no need for further checks if the plane is being disabled */ + if (!state->crtc || !state->fb) + return 0; + + err = tegra_plane_format(state->fb->format->format, + &plane_state->format, + &plane_state->swap); + if (err < 0) + return err; + + err = tegra_fb_get_tiling(state->fb, tiling); + if (err < 0) + return err; + + if (tiling->mode == TEGRA_BO_TILING_MODE_BLOCK && + !dc->soc->supports_block_linear) { + DRM_ERROR("hardware doesn't support block linear mode\n"); + return -EINVAL; + } + + /* + * Tegra doesn't support different strides for U and V planes so we + * error out if the user tries to display a framebuffer with such a + * configuration. + */ + if (state->fb->format->num_planes > 2) { + if (state->fb->pitches[2] != state->fb->pitches[1]) { + DRM_ERROR("unsupported UV-plane configuration\n"); + return -EINVAL; + } + } + + /* XXX scaling is not yet supported, add a check here */ + + err = tegra_plane_state_add(&tegra->base, state); + if (err < 0) + return err; + + return 0; +} + +static void tegra_shared_plane_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct tegra_dc *dc = to_tegra_dc(old_state->crtc); + struct tegra_plane *p = to_tegra_plane(plane); + u32 value; + + /* rien ne va plus */ + if (!old_state || !old_state->crtc) + return; + + /* + * XXX Legacy helpers seem to sometimes call ->atomic_disable() even + * on planes that are already disabled. Make sure we fallback to the + * head for this particular state instead of crashing. + */ + if (WARN_ON(p->dc == NULL)) + p->dc = dc; + + pm_runtime_get_sync(dc->dev); + + value = tegra_plane_readl(p, DC_WIN_WIN_OPTIONS); + value &= ~WIN_ENABLE; + tegra_plane_writel(p, value, DC_WIN_WIN_OPTIONS); + + tegra_dc_remove_shared_plane(dc, p); + + pm_runtime_put(dc->dev); +} + +static void tegra_shared_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct tegra_plane_state *state = to_tegra_plane_state(plane->state); + struct tegra_dc *dc = to_tegra_dc(plane->state->crtc); + unsigned int zpos = plane->state->normalized_zpos; + struct drm_framebuffer *fb = plane->state->fb; + struct tegra_plane *p = to_tegra_plane(plane); + struct tegra_bo *bo; + dma_addr_t base; + u32 value; + + /* rien ne va plus */ + if (!plane->state->crtc || !plane->state->fb) + return; + + if (!plane->state->visible) { + tegra_shared_plane_atomic_disable(plane, old_state); + return; + } + + pm_runtime_get_sync(dc->dev); + + tegra_dc_assign_shared_plane(dc, p); + + tegra_plane_writel(p, VCOUNTER, DC_WIN_CORE_ACT_CONTROL); + + /* blending */ + value = BLEND_FACTOR_DST_ALPHA_ZERO | BLEND_FACTOR_SRC_ALPHA_K2 | + BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_SRC | + BLEND_FACTOR_SRC_COLOR_K1_TIMES_SRC; + tegra_plane_writel(p, value, DC_WIN_BLEND_MATCH_SELECT); + + value = BLEND_FACTOR_DST_ALPHA_ZERO | BLEND_FACTOR_SRC_ALPHA_K2 | + BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_SRC | + BLEND_FACTOR_SRC_COLOR_K1_TIMES_SRC; + tegra_plane_writel(p, value, DC_WIN_BLEND_NOMATCH_SELECT); + + value = K2(255) | K1(255) | WINDOW_LAYER_DEPTH(255 - zpos); + tegra_plane_writel(p, value, DC_WIN_BLEND_LAYER_CONTROL); + + /* bypass scaling */ + value = HORIZONTAL_TAPS_5 | VERTICAL_TAPS_5; + tegra_plane_writel(p, value, DC_WIN_WINDOWGROUP_SET_CONTROL_INPUT_SCALER); + + value = INPUT_SCALER_VBYPASS | INPUT_SCALER_HBYPASS; + tegra_plane_writel(p, value, DC_WIN_WINDOWGROUP_SET_INPUT_SCALER_USAGE); + + /* disable compression */ + tegra_plane_writel(p, 0, DC_WINBUF_CDE_CONTROL); + + bo = tegra_fb_get_plane(fb, 0); + base = bo->paddr; + + tegra_plane_writel(p, state->format, DC_WIN_COLOR_DEPTH); + tegra_plane_writel(p, 0, DC_WIN_PRECOMP_WGRP_PARAMS); + + value = V_POSITION(plane->state->crtc_y) | + H_POSITION(plane->state->crtc_x); + tegra_plane_writel(p, value, DC_WIN_POSITION); + + value = V_SIZE(plane->state->crtc_h) | H_SIZE(plane->state->crtc_w); + tegra_plane_writel(p, value, DC_WIN_SIZE); + + value = WIN_ENABLE | COLOR_EXPAND; + tegra_plane_writel(p, value, DC_WIN_WIN_OPTIONS); + + value = V_SIZE(plane->state->crtc_h) | H_SIZE(plane->state->crtc_w); + tegra_plane_writel(p, value, DC_WIN_CROPPED_SIZE); + + tegra_plane_writel(p, upper_32_bits(base), DC_WINBUF_START_ADDR_HI); + tegra_plane_writel(p, lower_32_bits(base), DC_WINBUF_START_ADDR); + + value = PITCH(fb->pitches[0]); + tegra_plane_writel(p, value, DC_WIN_PLANAR_STORAGE); + + value = CLAMP_BEFORE_BLEND | DEGAMMA_SRGB | INPUT_RANGE_FULL; + tegra_plane_writel(p, value, DC_WIN_SET_PARAMS); + + value = OFFSET_X(plane->state->src_y >> 16) | + OFFSET_Y(plane->state->src_x >> 16); + tegra_plane_writel(p, value, DC_WINBUF_CROPPED_POINT); + + if (dc->soc->supports_block_linear) { + unsigned long height = state->tiling.value; + + /* XXX */ + switch (state->tiling.mode) { + case TEGRA_BO_TILING_MODE_PITCH: + value = DC_WINBUF_SURFACE_KIND_BLOCK_HEIGHT(0) | + DC_WINBUF_SURFACE_KIND_PITCH; + break; + + /* XXX not supported on Tegra186 and later */ + case TEGRA_BO_TILING_MODE_TILED: + value = DC_WINBUF_SURFACE_KIND_TILED; + break; + + case TEGRA_BO_TILING_MODE_BLOCK: + value = DC_WINBUF_SURFACE_KIND_BLOCK_HEIGHT(height) | + DC_WINBUF_SURFACE_KIND_BLOCK; + break; + } + + tegra_plane_writel(p, value, DC_WINBUF_SURFACE_KIND); + } + + /* disable gamut CSC */ + value = tegra_plane_readl(p, DC_WIN_WINDOW_SET_CONTROL); + value &= ~CONTROL_CSC_ENABLE; + tegra_plane_writel(p, value, DC_WIN_WINDOW_SET_CONTROL); + + pm_runtime_put(dc->dev); +} + +static const struct drm_plane_helper_funcs tegra_shared_plane_helper_funcs = { + .atomic_check = tegra_shared_plane_atomic_check, + .atomic_update = tegra_shared_plane_atomic_update, + .atomic_disable = tegra_shared_plane_atomic_disable, +}; + +struct drm_plane *tegra_shared_plane_create(struct drm_device *drm, + struct tegra_dc *dc, + unsigned int wgrp, + unsigned int index) +{ + enum drm_plane_type type = DRM_PLANE_TYPE_OVERLAY; + struct tegra_drm *tegra = drm->dev_private; + struct tegra_display_hub *hub = tegra->hub; + /* planes can be assigned to arbitrary CRTCs */ + unsigned int possible_crtcs = 0x7; + struct tegra_shared_plane *plane; + unsigned int num_formats; + struct drm_plane *p; + const u32 *formats; + int err; + + plane = kzalloc(sizeof(*plane), GFP_KERNEL); + if (!plane) + return ERR_PTR(-ENOMEM); + + plane->base.offset = 0x0a00 + 0x0300 * index; + plane->base.index = index; + + plane->wgrp = &hub->wgrps[wgrp]; + plane->wgrp->parent = dc->dev; + + p = &plane->base.base; + + num_formats = ARRAY_SIZE(tegra_shared_plane_formats); + formats = tegra_shared_plane_formats; + + err = drm_universal_plane_init(drm, p, possible_crtcs, + &tegra_plane_funcs, formats, + num_formats, NULL, type, NULL); + if (err < 0) { + kfree(plane); + return ERR_PTR(err); + } + + drm_plane_helper_add(p, &tegra_shared_plane_helper_funcs); + drm_plane_create_zpos_property(p, 0, 0, 255); + + return p; +} + +static void tegra_display_hub_update(struct tegra_dc *dc) +{ + u32 value; + + pm_runtime_get_sync(dc->dev); + + value = tegra_dc_readl(dc, DC_CMD_IHUB_COMMON_MISC_CTL); + value &= ~LATENCY_EVENT; + tegra_dc_writel(dc, value, DC_CMD_IHUB_COMMON_MISC_CTL); + + value = tegra_dc_readl(dc, DC_DISP_IHUB_COMMON_DISPLAY_FETCH_METER); + value = CURS_SLOTS(1) | WGRP_SLOTS(1); + tegra_dc_writel(dc, value, DC_DISP_IHUB_COMMON_DISPLAY_FETCH_METER); + + tegra_dc_writel(dc, COMMON_UPDATE, DC_CMD_STATE_CONTROL); + tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, COMMON_ACTREQ, DC_CMD_STATE_CONTROL); + tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); + + pm_runtime_put(dc->dev); +} + +void tegra_display_hub_atomic_commit(struct drm_device *drm, + struct drm_atomic_state *state) +{ + struct tegra_atomic_state *s = to_tegra_atomic_state(state); + struct tegra_drm *tegra = drm->dev_private; + struct tegra_display_hub *hub = tegra->hub; + struct device *dev = hub->client.dev; + int err; + + if (s->clk_disp) { + err = clk_set_rate(s->clk_disp, s->rate); + if (err < 0) + dev_err(dev, "failed to set rate of %pC to %lu Hz\n", + s->clk_disp, s->rate); + + err = clk_set_parent(hub->clk_disp, s->clk_disp); + if (err < 0) + dev_err(dev, "failed to set parent of %pC to %pC: %d\n", + hub->clk_disp, s->clk_disp, err); + } + + if (s->dc) + tegra_display_hub_update(s->dc); +} + +static int tegra_display_hub_init(struct host1x_client *client) +{ + struct tegra_display_hub *hub = to_tegra_display_hub(client); + struct drm_device *drm = dev_get_drvdata(client->parent); + struct tegra_drm *tegra = drm->dev_private; + + tegra->hub = hub; + + return 0; +} + +static int tegra_display_hub_exit(struct host1x_client *client) +{ + struct drm_device *drm = dev_get_drvdata(client->parent); + struct tegra_drm *tegra = drm->dev_private; + + tegra->hub = NULL; + + return 0; +} + +static const struct host1x_client_ops tegra_display_hub_ops = { + .init = tegra_display_hub_init, + .exit = tegra_display_hub_exit, +}; + +static int tegra_display_hub_probe(struct platform_device *pdev) +{ + struct tegra_display_hub *hub; + unsigned int i; + int err; + + hub = devm_kzalloc(&pdev->dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + hub->soc = of_device_get_match_data(&pdev->dev); + + hub->clk_disp = devm_clk_get(&pdev->dev, "disp"); + if (IS_ERR(hub->clk_disp)) { + err = PTR_ERR(hub->clk_disp); + return err; + } + + hub->clk_dsc = devm_clk_get(&pdev->dev, "dsc"); + if (IS_ERR(hub->clk_dsc)) { + err = PTR_ERR(hub->clk_dsc); + return err; + } + + hub->clk_hub = devm_clk_get(&pdev->dev, "hub"); + if (IS_ERR(hub->clk_hub)) { + err = PTR_ERR(hub->clk_hub); + return err; + } + + hub->rst = devm_reset_control_get(&pdev->dev, "misc"); + if (IS_ERR(hub->rst)) { + err = PTR_ERR(hub->rst); + return err; + } + + hub->wgrps = devm_kcalloc(&pdev->dev, hub->soc->num_wgrps, + sizeof(*hub->wgrps), GFP_KERNEL); + if (!hub->wgrps) + return -ENOMEM; + + for (i = 0; i < hub->soc->num_wgrps; i++) { + struct tegra_windowgroup *wgrp = &hub->wgrps[i]; + char id[8]; + + snprintf(id, sizeof(id), "wgrp%u", i); + mutex_init(&wgrp->lock); + wgrp->usecount = 0; + wgrp->index = i; + + wgrp->rst = devm_reset_control_get(&pdev->dev, id); + if (IS_ERR(wgrp->rst)) + return PTR_ERR(wgrp->rst); + + err = reset_control_assert(wgrp->rst); + if (err < 0) + return err; + } + + /* XXX: enable clock across reset? */ + err = reset_control_assert(hub->rst); + if (err < 0) + return err; + + platform_set_drvdata(pdev, hub); + pm_runtime_enable(&pdev->dev); + + INIT_LIST_HEAD(&hub->client.list); + hub->client.ops = &tegra_display_hub_ops; + hub->client.dev = &pdev->dev; + + err = host1x_client_register(&hub->client); + if (err < 0) + dev_err(&pdev->dev, "failed to register host1x client: %d\n", + err); + + return err; +} + +static int tegra_display_hub_remove(struct platform_device *pdev) +{ + struct tegra_display_hub *hub = platform_get_drvdata(pdev); + int err; + + err = host1x_client_unregister(&hub->client); + if (err < 0) { + dev_err(&pdev->dev, "failed to unregister host1x client: %d\n", + err); + } + + pm_runtime_disable(&pdev->dev); + + return err; +} + +static int __maybe_unused tegra_display_hub_suspend(struct device *dev) +{ + struct tegra_display_hub *hub = dev_get_drvdata(dev); + int err; + + err = reset_control_assert(hub->rst); + if (err < 0) + return err; + + clk_disable_unprepare(hub->clk_hub); + clk_disable_unprepare(hub->clk_dsc); + clk_disable_unprepare(hub->clk_disp); + + return 0; +} + +static int __maybe_unused tegra_display_hub_resume(struct device *dev) +{ + struct tegra_display_hub *hub = dev_get_drvdata(dev); + int err; + + err = clk_prepare_enable(hub->clk_disp); + if (err < 0) + return err; + + err = clk_prepare_enable(hub->clk_dsc); + if (err < 0) + goto disable_disp; + + err = clk_prepare_enable(hub->clk_hub); + if (err < 0) + goto disable_dsc; + + err = reset_control_deassert(hub->rst); + if (err < 0) + goto disable_hub; + + return 0; + +disable_hub: + clk_disable_unprepare(hub->clk_hub); +disable_dsc: + clk_disable_unprepare(hub->clk_dsc); +disable_disp: + clk_disable_unprepare(hub->clk_disp); + return err; +} + +static const struct dev_pm_ops tegra_display_hub_pm_ops = { + SET_RUNTIME_PM_OPS(tegra_display_hub_suspend, + tegra_display_hub_resume, NULL) +}; + +static const struct tegra_display_hub_soc tegra186_display_hub = { + .num_wgrps = 6, +}; + +static const struct of_device_id tegra_display_hub_of_match[] = { + { + .compatible = "nvidia,tegra186-display", + .data = &tegra186_display_hub + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, tegra_display_hub_of_match); + +struct platform_driver tegra_display_hub_driver = { + .driver = { + .name = "tegra-display-hub", + .of_match_table = tegra_display_hub_of_match, + .pm = &tegra_display_hub_pm_ops, + }, + .probe = tegra_display_hub_probe, + .remove = tegra_display_hub_remove, +}; diff --git a/drivers/gpu/drm/tegra/hub.h b/drivers/gpu/drm/tegra/hub.h new file mode 100644 index 000000000000..890a47cd05c3 --- /dev/null +++ b/drivers/gpu/drm/tegra/hub.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef TEGRA_HUB_H +#define TEGRA_HUB_H 1 + +#include <drm/drmP.h> +#include <drm/drm_plane.h> + +#include "plane.h" + +struct tegra_dc; + +struct tegra_windowgroup { + unsigned int usecount; + struct mutex lock; + + unsigned int index; + struct device *parent; + struct reset_control *rst; +}; + +struct tegra_shared_plane { + struct tegra_plane base; + struct tegra_windowgroup *wgrp; +}; + +static inline struct tegra_shared_plane * +to_tegra_shared_plane(struct drm_plane *plane) +{ + return container_of(plane, struct tegra_shared_plane, base.base); +} + +struct tegra_display_hub_soc { + unsigned int num_wgrps; +}; + +struct tegra_display_hub { + struct host1x_client client; + struct clk *clk_disp; + struct clk *clk_dsc; + struct clk *clk_hub; + struct reset_control *rst; + + const struct tegra_display_hub_soc *soc; + struct tegra_windowgroup *wgrps; +}; + +static inline struct tegra_display_hub * +to_tegra_display_hub(struct host1x_client *client) +{ + return container_of(client, struct tegra_display_hub, client); +} + +struct tegra_dc; +struct tegra_plane; + +int tegra_display_hub_prepare(struct tegra_display_hub *hub); +void tegra_display_hub_cleanup(struct tegra_display_hub *hub); + +struct drm_plane *tegra_shared_plane_create(struct drm_device *drm, + struct tegra_dc *dc, + unsigned int wgrp, + unsigned int index); + +void tegra_display_hub_atomic_commit(struct drm_device *drm, + struct drm_atomic_state *state); + +#define DC_CMD_IHUB_COMMON_MISC_CTL 0x068 +#define LATENCY_EVENT (1 << 3) + +#define DC_DISP_IHUB_COMMON_DISPLAY_FETCH_METER 0x451 +#define CURS_SLOTS(x) (((x) & 0xff) << 8) +#define WGRP_SLOTS(x) (((x) & 0xff) << 0) + +#endif /* TEGRA_HUB_H */ diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 24f8a3b712b4..ffe34bd0bb9d 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -9,7 +9,9 @@ #include <drm/drm_atomic_helper.h> #include <drm/drm_panel.h> + #include "drm.h" +#include "dc.h" #include <media/cec-notifier.h> @@ -218,3 +220,25 @@ void tegra_output_exit(struct tegra_output *output) if (output->panel) drm_panel_detach(output->panel); } + +void tegra_output_find_possible_crtcs(struct tegra_output *output, + struct drm_device *drm) +{ + struct device *dev = output->dev; + struct drm_crtc *crtc; + unsigned int mask = 0; + + drm_for_each_crtc(crtc, drm) { + struct tegra_dc *dc = to_tegra_dc(crtc); + + if (tegra_dc_has_output(dc, dev)) + mask |= drm_crtc_mask(crtc); + } + + if (mask == 0) { + dev_warn(dev, "missing output definition for heads in DT\n"); + mask = 0x3; + } + + output->encoder.possible_crtcs = mask; +} diff --git a/drivers/gpu/drm/tegra/plane.c b/drivers/gpu/drm/tegra/plane.c new file mode 100644 index 000000000000..36a06a993698 --- /dev/null +++ b/drivers/gpu/drm/tegra/plane.c @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2017 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_plane_helper.h> + +#include "dc.h" +#include "plane.h" + +static void tegra_plane_destroy(struct drm_plane *plane) +{ + struct tegra_plane *p = to_tegra_plane(plane); + + drm_plane_cleanup(plane); + kfree(p); +} + +static void tegra_plane_reset(struct drm_plane *plane) +{ + struct tegra_plane_state *state; + + if (plane->state) + __drm_atomic_helper_plane_destroy_state(plane->state); + + kfree(plane->state); + plane->state = NULL; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) { + plane->state = &state->base; + plane->state->plane = plane; + } +} + +static struct drm_plane_state * +tegra_plane_atomic_duplicate_state(struct drm_plane *plane) +{ + struct tegra_plane_state *state = to_tegra_plane_state(plane->state); + struct tegra_plane_state *copy; + unsigned int i; + + copy = kmalloc(sizeof(*copy), GFP_KERNEL); + if (!copy) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, ©->base); + copy->tiling = state->tiling; + copy->format = state->format; + copy->swap = state->swap; + copy->opaque = state->opaque; + + for (i = 0; i < 3; i++) + copy->dependent[i] = state->dependent[i]; + + return ©->base; +} + +static void tegra_plane_atomic_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + __drm_atomic_helper_plane_destroy_state(state); + kfree(state); +} + +const struct drm_plane_funcs tegra_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = tegra_plane_destroy, + .reset = tegra_plane_reset, + .atomic_duplicate_state = tegra_plane_atomic_duplicate_state, + .atomic_destroy_state = tegra_plane_atomic_destroy_state, +}; + +int tegra_plane_state_add(struct tegra_plane *plane, + struct drm_plane_state *state) +{ + struct drm_crtc_state *crtc_state; + struct tegra_dc_state *tegra; + struct drm_rect clip; + int err; + + /* Propagate errors from allocation or locking failures. */ + crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + clip.x1 = 0; + clip.y1 = 0; + clip.x2 = crtc_state->mode.hdisplay; + clip.y2 = crtc_state->mode.vdisplay; + + /* Check plane state for visibility and calculate clipping bounds */ + err = drm_atomic_helper_check_plane_state(state, crtc_state, &clip, + 0, INT_MAX, true, true); + if (err < 0) + return err; + + tegra = to_dc_state(crtc_state); + + tegra->planes |= WIN_A_ACT_REQ << plane->index; + + return 0; +} + +int tegra_plane_format(u32 fourcc, u32 *format, u32 *swap) +{ + /* assume no swapping of fetched data */ + if (swap) + *swap = BYTE_SWAP_NOSWAP; + + switch (fourcc) { + case DRM_FORMAT_ARGB4444: + *format = WIN_COLOR_DEPTH_B4G4R4A4; + break; + + case DRM_FORMAT_ARGB1555: + *format = WIN_COLOR_DEPTH_B5G5R5A1; + break; + + case DRM_FORMAT_RGB565: + *format = WIN_COLOR_DEPTH_B5G6R5; + break; + + case DRM_FORMAT_RGBA5551: + *format = WIN_COLOR_DEPTH_A1B5G5R5; + break; + + case DRM_FORMAT_ARGB8888: + *format = WIN_COLOR_DEPTH_B8G8R8A8; + break; + + case DRM_FORMAT_ABGR8888: + *format = WIN_COLOR_DEPTH_R8G8B8A8; + break; + + case DRM_FORMAT_ABGR4444: + *format = WIN_COLOR_DEPTH_R4G4B4A4; + break; + + case DRM_FORMAT_ABGR1555: + *format = WIN_COLOR_DEPTH_R5G5B5A; + break; + + case DRM_FORMAT_BGRA5551: + *format = WIN_COLOR_DEPTH_AR5G5B5; + break; + + case DRM_FORMAT_XRGB1555: + *format = WIN_COLOR_DEPTH_B5G5R5X1; + break; + + case DRM_FORMAT_RGBX5551: + *format = WIN_COLOR_DEPTH_X1B5G5R5; + break; + + case DRM_FORMAT_XBGR1555: + *format = WIN_COLOR_DEPTH_R5G5B5X1; + break; + + case DRM_FORMAT_BGRX5551: + *format = WIN_COLOR_DEPTH_X1R5G5B5; + break; + + case DRM_FORMAT_BGR565: + *format = WIN_COLOR_DEPTH_R5G6B5; + break; + + case DRM_FORMAT_BGRA8888: + *format = WIN_COLOR_DEPTH_A8R8G8B8; + break; + + case DRM_FORMAT_RGBA8888: + *format = WIN_COLOR_DEPTH_A8B8G8R8; + break; + + case DRM_FORMAT_XRGB8888: + *format = WIN_COLOR_DEPTH_B8G8R8X8; + break; + + case DRM_FORMAT_XBGR8888: + *format = WIN_COLOR_DEPTH_R8G8B8X8; + break; + + case DRM_FORMAT_UYVY: + *format = WIN_COLOR_DEPTH_YCbCr422; + break; + + case DRM_FORMAT_YUYV: + if (!swap) + return -EINVAL; + + *format = WIN_COLOR_DEPTH_YCbCr422; + *swap = BYTE_SWAP_SWAP2; + break; + + case DRM_FORMAT_YUV420: + *format = WIN_COLOR_DEPTH_YCbCr420P; + break; + + case DRM_FORMAT_YUV422: + *format = WIN_COLOR_DEPTH_YCbCr422P; + break; + + default: + return -EINVAL; + } + + return 0; +} + +bool tegra_plane_format_is_yuv(unsigned int format, bool *planar) +{ + switch (format) { + case WIN_COLOR_DEPTH_YCbCr422: + case WIN_COLOR_DEPTH_YUV422: + if (planar) + *planar = false; + + return true; + + case WIN_COLOR_DEPTH_YCbCr420P: + case WIN_COLOR_DEPTH_YUV420P: + case WIN_COLOR_DEPTH_YCbCr422P: + case WIN_COLOR_DEPTH_YUV422P: + case WIN_COLOR_DEPTH_YCbCr422R: + case WIN_COLOR_DEPTH_YUV422R: + case WIN_COLOR_DEPTH_YCbCr422RA: + case WIN_COLOR_DEPTH_YUV422RA: + if (planar) + *planar = true; + + return true; + } + + if (planar) + *planar = false; + + return false; +} + +static bool __drm_format_has_alpha(u32 format) +{ + switch (format) { + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_ARGB8888: + return true; + } + + return false; +} + +/* + * This is applicable to Tegra20 and Tegra30 only where the opaque formats can + * be emulated using the alpha formats and alpha blending disabled. + */ +bool tegra_plane_format_has_alpha(unsigned int format) +{ + switch (format) { + case WIN_COLOR_DEPTH_B5G5R5A1: + case WIN_COLOR_DEPTH_A1B5G5R5: + case WIN_COLOR_DEPTH_R8G8B8A8: + case WIN_COLOR_DEPTH_B8G8R8A8: + return true; + } + + return false; +} + +int tegra_plane_format_get_alpha(unsigned int opaque, unsigned int *alpha) +{ + if (tegra_plane_format_is_yuv(opaque, NULL)) { + *alpha = opaque; + return 0; + } + + switch (opaque) { + case WIN_COLOR_DEPTH_B5G5R5X1: + *alpha = WIN_COLOR_DEPTH_B5G5R5A1; + return 0; + + case WIN_COLOR_DEPTH_X1B5G5R5: + *alpha = WIN_COLOR_DEPTH_A1B5G5R5; + return 0; + + case WIN_COLOR_DEPTH_R8G8B8X8: + *alpha = WIN_COLOR_DEPTH_R8G8B8A8; + return 0; + + case WIN_COLOR_DEPTH_B8G8R8X8: + *alpha = WIN_COLOR_DEPTH_B8G8R8A8; + return 0; + } + + return -EINVAL; +} + +unsigned int tegra_plane_get_overlap_index(struct tegra_plane *plane, + struct tegra_plane *other) +{ + unsigned int index = 0, i; + + WARN_ON(plane == other); + + for (i = 0; i < 3; i++) { + if (i == plane->index) + continue; + + if (i == other->index) + break; + + index++; + } + + return index; +} + +void tegra_plane_check_dependent(struct tegra_plane *tegra, + struct tegra_plane_state *state) +{ + struct drm_plane_state *old, *new; + struct drm_plane *plane; + unsigned int zpos[2]; + unsigned int i; + + for (i = 0; i < 3; i++) + state->dependent[i] = false; + + for (i = 0; i < 2; i++) + zpos[i] = 0; + + for_each_oldnew_plane_in_state(state->base.state, plane, old, new, i) { + struct tegra_plane *p = to_tegra_plane(plane); + unsigned index; + + /* skip this plane and planes on different CRTCs */ + if (p == tegra || new->crtc != state->base.crtc) + continue; + + index = tegra_plane_get_overlap_index(tegra, p); + + /* + * If any of the other planes is on top of this plane and uses + * a format with an alpha component, mark this plane as being + * dependent, meaning it's alpha value will be 1 minus the sum + * of alpha components of the overlapping planes. + */ + if (p->index > tegra->index) { + if (__drm_format_has_alpha(new->fb->format->format)) + state->dependent[index] = true; + + /* keep track of the Z position */ + zpos[index] = p->index; + } + } + + /* + * The region where three windows overlap is the intersection of the + * two regions where two windows overlap. It contributes to the area + * if any of the windows on top of it have an alpha component. + */ + for (i = 0; i < 2; i++) + state->dependent[2] = state->dependent[2] || + state->dependent[i]; + + /* + * However, if any of the windows on top of this window is opaque, it + * will completely conceal this window within that area, so avoid the + * window from contributing to the area. + */ + for (i = 0; i < 2; i++) { + if (zpos[i] > tegra->index) + state->dependent[2] = state->dependent[2] && + state->dependent[i]; + } +} diff --git a/drivers/gpu/drm/tegra/plane.h b/drivers/gpu/drm/tegra/plane.h new file mode 100644 index 000000000000..6938719e7e5d --- /dev/null +++ b/drivers/gpu/drm/tegra/plane.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef TEGRA_PLANE_H +#define TEGRA_PLANE_H 1 + +#include <drm/drm_plane.h> + +struct tegra_bo; +struct tegra_dc; + +struct tegra_plane { + struct drm_plane base; + struct tegra_dc *dc; + unsigned int offset; + unsigned int index; +}; + +struct tegra_cursor { + struct tegra_plane base; + + struct tegra_bo *bo; + unsigned int width; + unsigned int height; +}; + +static inline struct tegra_plane *to_tegra_plane(struct drm_plane *plane) +{ + return container_of(plane, struct tegra_plane, base); +} + +struct tegra_plane_state { + struct drm_plane_state base; + + struct tegra_bo_tiling tiling; + u32 format; + u32 swap; + + /* used for legacy blending support only */ + bool opaque; + bool dependent[3]; +}; + +static inline struct tegra_plane_state * +to_tegra_plane_state(struct drm_plane_state *state) +{ + if (state) + return container_of(state, struct tegra_plane_state, base); + + return NULL; +} + +extern const struct drm_plane_funcs tegra_plane_funcs; + +int tegra_plane_state_add(struct tegra_plane *plane, + struct drm_plane_state *state); + +int tegra_plane_format(u32 fourcc, u32 *format, u32 *swap); +bool tegra_plane_format_is_yuv(unsigned int format, bool *planar); +bool tegra_plane_format_has_alpha(unsigned int format); +int tegra_plane_format_get_alpha(unsigned int opaque, unsigned int *alpha); +void tegra_plane_check_dependent(struct tegra_plane *tegra, + struct tegra_plane_state *state); + +#endif /* TEGRA_PLANE_H */ diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index b0a1dedac802..4be9edf9c6fe 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -22,23 +22,37 @@ #include <drm/drm_atomic_helper.h> #include <drm/drm_dp_helper.h> #include <drm/drm_panel.h> +#include <drm/drm_scdc_helper.h> #include "dc.h" #include "drm.h" #include "sor.h" #include "trace.h" +/* + * XXX Remove this after the commit adding it to soc/tegra/pmc.h has been + * merged. Having this around after the commit is merged should be safe since + * the preprocessor will effectively replace all occurrences and therefore no + * duplicate will be defined. + */ +#define TEGRA_IO_PAD_HDMI_DP0 26 + #define SOR_REKEY 0x38 struct tegra_sor_hdmi_settings { unsigned long frequency; u8 vcocap; + u8 filter; u8 ichpmp; u8 loadadj; - u8 termadj; - u8 tx_pu; - u8 bg_vref; + u8 tmds_termadj; + u8 tx_pu_value; + u8 bg_temp_coef; + u8 bg_vref_level; + u8 avdd10_level; + u8 avdd14_level; + u8 sparepll; u8 drive_current[4]; u8 preemphasis[4]; @@ -49,51 +63,76 @@ static const struct tegra_sor_hdmi_settings tegra210_sor_hdmi_defaults[] = { { .frequency = 54000000, .vcocap = 0x0, + .filter = 0x0, .ichpmp = 0x1, .loadadj = 0x3, - .termadj = 0x9, - .tx_pu = 0x10, - .bg_vref = 0x8, + .tmds_termadj = 0x9, + .tx_pu_value = 0x10, + .bg_temp_coef = 0x3, + .bg_vref_level = 0x8, + .avdd10_level = 0x4, + .avdd14_level = 0x4, + .sparepll = 0x0, .drive_current = { 0x33, 0x3a, 0x3a, 0x3a }, .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, }, { .frequency = 75000000, .vcocap = 0x3, + .filter = 0x0, .ichpmp = 0x1, .loadadj = 0x3, - .termadj = 0x9, - .tx_pu = 0x40, - .bg_vref = 0x8, + .tmds_termadj = 0x9, + .tx_pu_value = 0x40, + .bg_temp_coef = 0x3, + .bg_vref_level = 0x8, + .avdd10_level = 0x4, + .avdd14_level = 0x4, + .sparepll = 0x0, .drive_current = { 0x33, 0x3a, 0x3a, 0x3a }, .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, }, { .frequency = 150000000, .vcocap = 0x3, + .filter = 0x0, .ichpmp = 0x1, .loadadj = 0x3, - .termadj = 0x9, - .tx_pu = 0x66, - .bg_vref = 0x8, + .tmds_termadj = 0x9, + .tx_pu_value = 0x66, + .bg_temp_coef = 0x3, + .bg_vref_level = 0x8, + .avdd10_level = 0x4, + .avdd14_level = 0x4, + .sparepll = 0x0, .drive_current = { 0x33, 0x3a, 0x3a, 0x3a }, .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, }, { .frequency = 300000000, .vcocap = 0x3, + .filter = 0x0, .ichpmp = 0x1, .loadadj = 0x3, - .termadj = 0x9, - .tx_pu = 0x66, - .bg_vref = 0xa, + .tmds_termadj = 0x9, + .tx_pu_value = 0x66, + .bg_temp_coef = 0x3, + .bg_vref_level = 0xa, + .avdd10_level = 0x4, + .avdd14_level = 0x4, + .sparepll = 0x0, .drive_current = { 0x33, 0x3f, 0x3f, 0x3f }, .preemphasis = { 0x00, 0x17, 0x17, 0x17 }, }, { .frequency = 600000000, .vcocap = 0x3, + .filter = 0x0, .ichpmp = 0x1, .loadadj = 0x3, - .termadj = 0x9, - .tx_pu = 0x66, - .bg_vref = 0x8, + .tmds_termadj = 0x9, + .tx_pu_value = 0x66, + .bg_temp_coef = 0x3, + .bg_vref_level = 0x8, + .avdd10_level = 0x4, + .avdd14_level = 0x4, + .sparepll = 0x0, .drive_current = { 0x33, 0x3f, 0x3f, 0x3f }, .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, }, @@ -103,53 +142,170 @@ static const struct tegra_sor_hdmi_settings tegra210_sor_hdmi_defaults[] = { { .frequency = 75000000, .vcocap = 0x3, + .filter = 0x0, .ichpmp = 0x1, .loadadj = 0x3, - .termadj = 0x9, - .tx_pu = 0x40, - .bg_vref = 0x8, + .tmds_termadj = 0x9, + .tx_pu_value = 0x40, + .bg_temp_coef = 0x3, + .bg_vref_level = 0x8, + .avdd10_level = 0x4, + .avdd14_level = 0x4, + .sparepll = 0x0, .drive_current = { 0x29, 0x29, 0x29, 0x29 }, .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, }, { .frequency = 150000000, .vcocap = 0x3, + .filter = 0x0, .ichpmp = 0x1, .loadadj = 0x3, - .termadj = 0x9, - .tx_pu = 0x66, - .bg_vref = 0x8, + .tmds_termadj = 0x9, + .tx_pu_value = 0x66, + .bg_temp_coef = 0x3, + .bg_vref_level = 0x8, + .avdd10_level = 0x4, + .avdd14_level = 0x4, + .sparepll = 0x0, .drive_current = { 0x30, 0x37, 0x37, 0x37 }, .preemphasis = { 0x01, 0x02, 0x02, 0x02 }, }, { .frequency = 300000000, .vcocap = 0x3, + .filter = 0x0, .ichpmp = 0x6, .loadadj = 0x3, - .termadj = 0x9, - .tx_pu = 0x66, - .bg_vref = 0xf, + .tmds_termadj = 0x9, + .tx_pu_value = 0x66, + .bg_temp_coef = 0x3, + .bg_vref_level = 0xf, + .avdd10_level = 0x4, + .avdd14_level = 0x4, + .sparepll = 0x0, .drive_current = { 0x30, 0x37, 0x37, 0x37 }, .preemphasis = { 0x10, 0x3e, 0x3e, 0x3e }, }, { .frequency = 600000000, .vcocap = 0x3, + .filter = 0x0, .ichpmp = 0xa, .loadadj = 0x3, - .termadj = 0xb, - .tx_pu = 0x66, - .bg_vref = 0xe, + .tmds_termadj = 0xb, + .tx_pu_value = 0x66, + .bg_temp_coef = 0x3, + .bg_vref_level = 0xe, + .avdd10_level = 0x4, + .avdd14_level = 0x4, + .sparepll = 0x0, .drive_current = { 0x35, 0x3e, 0x3e, 0x3e }, .preemphasis = { 0x02, 0x3f, 0x3f, 0x3f }, }, }; #endif +static const struct tegra_sor_hdmi_settings tegra186_sor_hdmi_defaults[] = { + { + .frequency = 54000000, + .vcocap = 0, + .filter = 5, + .ichpmp = 5, + .loadadj = 3, + .tmds_termadj = 0xf, + .tx_pu_value = 0, + .bg_temp_coef = 3, + .bg_vref_level = 8, + .avdd10_level = 4, + .avdd14_level = 4, + .sparepll = 0x54, + .drive_current = { 0x3a, 0x3a, 0x3a, 0x33 }, + .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, + }, { + .frequency = 75000000, + .vcocap = 1, + .filter = 5, + .ichpmp = 5, + .loadadj = 3, + .tmds_termadj = 0xf, + .tx_pu_value = 0, + .bg_temp_coef = 3, + .bg_vref_level = 8, + .avdd10_level = 4, + .avdd14_level = 4, + .sparepll = 0x44, + .drive_current = { 0x3a, 0x3a, 0x3a, 0x33 }, + .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, + }, { + .frequency = 150000000, + .vcocap = 3, + .filter = 5, + .ichpmp = 5, + .loadadj = 3, + .tmds_termadj = 15, + .tx_pu_value = 0x66 /* 0 */, + .bg_temp_coef = 3, + .bg_vref_level = 8, + .avdd10_level = 4, + .avdd14_level = 4, + .sparepll = 0x00, /* 0x34 */ + .drive_current = { 0x3a, 0x3a, 0x3a, 0x37 }, + .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, + }, { + .frequency = 300000000, + .vcocap = 3, + .filter = 5, + .ichpmp = 5, + .loadadj = 3, + .tmds_termadj = 15, + .tx_pu_value = 64, + .bg_temp_coef = 3, + .bg_vref_level = 8, + .avdd10_level = 4, + .avdd14_level = 4, + .sparepll = 0x34, + .drive_current = { 0x3d, 0x3d, 0x3d, 0x33 }, + .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, + }, { + .frequency = 600000000, + .vcocap = 3, + .filter = 5, + .ichpmp = 5, + .loadadj = 3, + .tmds_termadj = 12, + .tx_pu_value = 96, + .bg_temp_coef = 3, + .bg_vref_level = 8, + .avdd10_level = 4, + .avdd14_level = 4, + .sparepll = 0x34, + .drive_current = { 0x3d, 0x3d, 0x3d, 0x33 }, + .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, + } +}; + +struct tegra_sor_regs { + unsigned int head_state0; + unsigned int head_state1; + unsigned int head_state2; + unsigned int head_state3; + unsigned int head_state4; + unsigned int head_state5; + unsigned int pll0; + unsigned int pll1; + unsigned int pll2; + unsigned int pll3; + unsigned int dp_padctl0; + unsigned int dp_padctl2; +}; + struct tegra_sor_soc { bool supports_edp; bool supports_lvds; bool supports_hdmi; bool supports_dp; + const struct tegra_sor_regs *regs; + bool has_nvdisplay; + const struct tegra_sor_hdmi_settings *settings; unsigned int num_settings; @@ -171,6 +327,7 @@ struct tegra_sor { const struct tegra_sor_soc *soc; void __iomem *regs; + unsigned int index; struct reset_control *rst; struct clk *clk_parent; @@ -183,10 +340,9 @@ struct tegra_sor { struct drm_dp_aux *aux; struct drm_info_list *debugfs_files; - struct drm_minor *minor; - struct dentry *debugfs; const struct tegra_sor_ops *ops; + enum tegra_io_pad pad; /* for HDMI 2.0 */ struct tegra_sor_hdmi_settings *settings; @@ -195,11 +351,16 @@ struct tegra_sor { struct regulator *avdd_io_supply; struct regulator *vdd_pll_supply; struct regulator *hdmi_supply; + + struct delayed_work scdc; + bool scdc_enabled; }; struct tegra_sor_state { struct drm_connector_state base; + unsigned int link_speed; + unsigned long pclk; unsigned int bpc; }; @@ -389,23 +550,23 @@ static int tegra_sor_dp_train_fast(struct tegra_sor *sor, /* disable LVDS mode */ tegra_sor_writel(sor, 0, SOR_LVDS); - value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value = tegra_sor_readl(sor, sor->soc->regs->dp_padctl0); value |= SOR_DP_PADCTL_TX_PU_ENABLE; value &= ~SOR_DP_PADCTL_TX_PU_MASK; value |= SOR_DP_PADCTL_TX_PU(2); /* XXX: don't hardcode? */ - tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + tegra_sor_writel(sor, value, sor->soc->regs->dp_padctl0); - value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value = tegra_sor_readl(sor, sor->soc->regs->dp_padctl0); value |= SOR_DP_PADCTL_CM_TXD_3 | SOR_DP_PADCTL_CM_TXD_2 | SOR_DP_PADCTL_CM_TXD_1 | SOR_DP_PADCTL_CM_TXD_0; - tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + tegra_sor_writel(sor, value, sor->soc->regs->dp_padctl0); usleep_range(10, 100); - value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value = tegra_sor_readl(sor, sor->soc->regs->dp_padctl0); value &= ~(SOR_DP_PADCTL_CM_TXD_3 | SOR_DP_PADCTL_CM_TXD_2 | SOR_DP_PADCTL_CM_TXD_1 | SOR_DP_PADCTL_CM_TXD_0); - tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + tegra_sor_writel(sor, value, sor->soc->regs->dp_padctl0); err = drm_dp_aux_prepare(sor->aux, DP_SET_ANSI_8B10B); if (err < 0) @@ -465,47 +626,6 @@ static int tegra_sor_dp_train_fast(struct tegra_sor *sor, return 0; } -static void tegra_sor_dp_term_calibrate(struct tegra_sor *sor) -{ - u32 mask = 0x08, adj = 0, value; - - /* enable pad calibration logic */ - value = tegra_sor_readl(sor, SOR_DP_PADCTL0); - value &= ~SOR_DP_PADCTL_PAD_CAL_PD; - tegra_sor_writel(sor, value, SOR_DP_PADCTL0); - - value = tegra_sor_readl(sor, SOR_PLL1); - value |= SOR_PLL1_TMDS_TERM; - tegra_sor_writel(sor, value, SOR_PLL1); - - while (mask) { - adj |= mask; - - value = tegra_sor_readl(sor, SOR_PLL1); - value &= ~SOR_PLL1_TMDS_TERMADJ_MASK; - value |= SOR_PLL1_TMDS_TERMADJ(adj); - tegra_sor_writel(sor, value, SOR_PLL1); - - usleep_range(100, 200); - - value = tegra_sor_readl(sor, SOR_PLL1); - if (value & SOR_PLL1_TERM_COMPOUT) - adj &= ~mask; - - mask >>= 1; - } - - value = tegra_sor_readl(sor, SOR_PLL1); - value &= ~SOR_PLL1_TMDS_TERMADJ_MASK; - value |= SOR_PLL1_TMDS_TERMADJ(adj); - tegra_sor_writel(sor, value, SOR_PLL1); - - /* disable pad calibration logic */ - value = tegra_sor_readl(sor, SOR_DP_PADCTL0); - value |= SOR_DP_PADCTL_PAD_CAL_PD; - tegra_sor_writel(sor, value, SOR_DP_PADCTL0); -} - static void tegra_sor_super_update(struct tegra_sor *sor) { tegra_sor_writel(sor, 0, SOR_SUPER_STATE0); @@ -897,31 +1017,31 @@ static void tegra_sor_mode_set(struct tegra_sor *sor, */ value = ((mode->vtotal & 0x7fff) << 16) | (mode->htotal & 0x7fff); - tegra_sor_writel(sor, value, SOR_HEAD_STATE1(dc->pipe)); + tegra_sor_writel(sor, value, sor->soc->regs->head_state1 + dc->pipe); /* sync end = sync width - 1 */ vse = mode->vsync_end - mode->vsync_start - 1; hse = mode->hsync_end - mode->hsync_start - 1; value = ((vse & 0x7fff) << 16) | (hse & 0x7fff); - tegra_sor_writel(sor, value, SOR_HEAD_STATE2(dc->pipe)); + tegra_sor_writel(sor, value, sor->soc->regs->head_state2 + dc->pipe); /* blank end = sync end + back porch */ vbe = vse + (mode->vtotal - mode->vsync_end); hbe = hse + (mode->htotal - mode->hsync_end); value = ((vbe & 0x7fff) << 16) | (hbe & 0x7fff); - tegra_sor_writel(sor, value, SOR_HEAD_STATE3(dc->pipe)); + tegra_sor_writel(sor, value, sor->soc->regs->head_state3 + dc->pipe); /* blank start = blank end + active */ vbs = vbe + mode->vdisplay; hbs = hbe + mode->hdisplay; value = ((vbs & 0x7fff) << 16) | (hbs & 0x7fff); - tegra_sor_writel(sor, value, SOR_HEAD_STATE4(dc->pipe)); + tegra_sor_writel(sor, value, sor->soc->regs->head_state4 + dc->pipe); /* XXX interlacing support */ - tegra_sor_writel(sor, 0x001, SOR_HEAD_STATE5(dc->pipe)); + tegra_sor_writel(sor, 0x001, sor->soc->regs->head_state5 + dc->pipe); } static int tegra_sor_detach(struct tegra_sor *sor) @@ -1003,10 +1123,10 @@ static int tegra_sor_power_down(struct tegra_sor *sor) return err; } - value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value = tegra_sor_readl(sor, sor->soc->regs->dp_padctl0); value &= ~(SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_0 | SOR_DP_PADCTL_PD_TXD_1 | SOR_DP_PADCTL_PD_TXD_2); - tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + tegra_sor_writel(sor, value, sor->soc->regs->dp_padctl0); /* stop lane sequencer */ value = SOR_LANE_SEQ_CTL_TRIGGER | SOR_LANE_SEQ_CTL_SEQUENCE_UP | @@ -1026,20 +1146,20 @@ static int tegra_sor_power_down(struct tegra_sor *sor) if ((value & SOR_LANE_SEQ_CTL_TRIGGER) != 0) return -ETIMEDOUT; - value = tegra_sor_readl(sor, SOR_PLL2); + value = tegra_sor_readl(sor, sor->soc->regs->pll2); value |= SOR_PLL2_PORT_POWERDOWN; - tegra_sor_writel(sor, value, SOR_PLL2); + tegra_sor_writel(sor, value, sor->soc->regs->pll2); usleep_range(20, 100); - value = tegra_sor_readl(sor, SOR_PLL0); + value = tegra_sor_readl(sor, sor->soc->regs->pll0); value |= SOR_PLL0_VCOPD | SOR_PLL0_PWR; - tegra_sor_writel(sor, value, SOR_PLL0); + tegra_sor_writel(sor, value, sor->soc->regs->pll0); - value = tegra_sor_readl(sor, SOR_PLL2); + value = tegra_sor_readl(sor, sor->soc->regs->pll2); value |= SOR_PLL2_SEQ_PLLCAPPD; value |= SOR_PLL2_SEQ_PLLCAPPD_ENFORCE; - tegra_sor_writel(sor, value, SOR_PLL2); + tegra_sor_writel(sor, value, sor->soc->regs->pll2); usleep_range(20, 100); @@ -1105,12 +1225,133 @@ unlock: return err; } +#define DEBUGFS_REG32(_name) { .name = #_name, .offset = _name } + +static const struct debugfs_reg32 tegra_sor_regs[] = { + DEBUGFS_REG32(SOR_CTXSW), + DEBUGFS_REG32(SOR_SUPER_STATE0), + DEBUGFS_REG32(SOR_SUPER_STATE1), + DEBUGFS_REG32(SOR_STATE0), + DEBUGFS_REG32(SOR_STATE1), + DEBUGFS_REG32(SOR_HEAD_STATE0(0)), + DEBUGFS_REG32(SOR_HEAD_STATE0(1)), + DEBUGFS_REG32(SOR_HEAD_STATE1(0)), + DEBUGFS_REG32(SOR_HEAD_STATE1(1)), + DEBUGFS_REG32(SOR_HEAD_STATE2(0)), + DEBUGFS_REG32(SOR_HEAD_STATE2(1)), + DEBUGFS_REG32(SOR_HEAD_STATE3(0)), + DEBUGFS_REG32(SOR_HEAD_STATE3(1)), + DEBUGFS_REG32(SOR_HEAD_STATE4(0)), + DEBUGFS_REG32(SOR_HEAD_STATE4(1)), + DEBUGFS_REG32(SOR_HEAD_STATE5(0)), + DEBUGFS_REG32(SOR_HEAD_STATE5(1)), + DEBUGFS_REG32(SOR_CRC_CNTRL), + DEBUGFS_REG32(SOR_DP_DEBUG_MVID), + DEBUGFS_REG32(SOR_CLK_CNTRL), + DEBUGFS_REG32(SOR_CAP), + DEBUGFS_REG32(SOR_PWR), + DEBUGFS_REG32(SOR_TEST), + DEBUGFS_REG32(SOR_PLL0), + DEBUGFS_REG32(SOR_PLL1), + DEBUGFS_REG32(SOR_PLL2), + DEBUGFS_REG32(SOR_PLL3), + DEBUGFS_REG32(SOR_CSTM), + DEBUGFS_REG32(SOR_LVDS), + DEBUGFS_REG32(SOR_CRCA), + DEBUGFS_REG32(SOR_CRCB), + DEBUGFS_REG32(SOR_BLANK), + DEBUGFS_REG32(SOR_SEQ_CTL), + DEBUGFS_REG32(SOR_LANE_SEQ_CTL), + DEBUGFS_REG32(SOR_SEQ_INST(0)), + DEBUGFS_REG32(SOR_SEQ_INST(1)), + DEBUGFS_REG32(SOR_SEQ_INST(2)), + DEBUGFS_REG32(SOR_SEQ_INST(3)), + DEBUGFS_REG32(SOR_SEQ_INST(4)), + DEBUGFS_REG32(SOR_SEQ_INST(5)), + DEBUGFS_REG32(SOR_SEQ_INST(6)), + DEBUGFS_REG32(SOR_SEQ_INST(7)), + DEBUGFS_REG32(SOR_SEQ_INST(8)), + DEBUGFS_REG32(SOR_SEQ_INST(9)), + DEBUGFS_REG32(SOR_SEQ_INST(10)), + DEBUGFS_REG32(SOR_SEQ_INST(11)), + DEBUGFS_REG32(SOR_SEQ_INST(12)), + DEBUGFS_REG32(SOR_SEQ_INST(13)), + DEBUGFS_REG32(SOR_SEQ_INST(14)), + DEBUGFS_REG32(SOR_SEQ_INST(15)), + DEBUGFS_REG32(SOR_PWM_DIV), + DEBUGFS_REG32(SOR_PWM_CTL), + DEBUGFS_REG32(SOR_VCRC_A0), + DEBUGFS_REG32(SOR_VCRC_A1), + DEBUGFS_REG32(SOR_VCRC_B0), + DEBUGFS_REG32(SOR_VCRC_B1), + DEBUGFS_REG32(SOR_CCRC_A0), + DEBUGFS_REG32(SOR_CCRC_A1), + DEBUGFS_REG32(SOR_CCRC_B0), + DEBUGFS_REG32(SOR_CCRC_B1), + DEBUGFS_REG32(SOR_EDATA_A0), + DEBUGFS_REG32(SOR_EDATA_A1), + DEBUGFS_REG32(SOR_EDATA_B0), + DEBUGFS_REG32(SOR_EDATA_B1), + DEBUGFS_REG32(SOR_COUNT_A0), + DEBUGFS_REG32(SOR_COUNT_A1), + DEBUGFS_REG32(SOR_COUNT_B0), + DEBUGFS_REG32(SOR_COUNT_B1), + DEBUGFS_REG32(SOR_DEBUG_A0), + DEBUGFS_REG32(SOR_DEBUG_A1), + DEBUGFS_REG32(SOR_DEBUG_B0), + DEBUGFS_REG32(SOR_DEBUG_B1), + DEBUGFS_REG32(SOR_TRIG), + DEBUGFS_REG32(SOR_MSCHECK), + DEBUGFS_REG32(SOR_XBAR_CTRL), + DEBUGFS_REG32(SOR_XBAR_POL), + DEBUGFS_REG32(SOR_DP_LINKCTL0), + DEBUGFS_REG32(SOR_DP_LINKCTL1), + DEBUGFS_REG32(SOR_LANE_DRIVE_CURRENT0), + DEBUGFS_REG32(SOR_LANE_DRIVE_CURRENT1), + DEBUGFS_REG32(SOR_LANE4_DRIVE_CURRENT0), + DEBUGFS_REG32(SOR_LANE4_DRIVE_CURRENT1), + DEBUGFS_REG32(SOR_LANE_PREEMPHASIS0), + DEBUGFS_REG32(SOR_LANE_PREEMPHASIS1), + DEBUGFS_REG32(SOR_LANE4_PREEMPHASIS0), + DEBUGFS_REG32(SOR_LANE4_PREEMPHASIS1), + DEBUGFS_REG32(SOR_LANE_POSTCURSOR0), + DEBUGFS_REG32(SOR_LANE_POSTCURSOR1), + DEBUGFS_REG32(SOR_DP_CONFIG0), + DEBUGFS_REG32(SOR_DP_CONFIG1), + DEBUGFS_REG32(SOR_DP_MN0), + DEBUGFS_REG32(SOR_DP_MN1), + DEBUGFS_REG32(SOR_DP_PADCTL0), + DEBUGFS_REG32(SOR_DP_PADCTL1), + DEBUGFS_REG32(SOR_DP_PADCTL2), + DEBUGFS_REG32(SOR_DP_DEBUG0), + DEBUGFS_REG32(SOR_DP_DEBUG1), + DEBUGFS_REG32(SOR_DP_SPARE0), + DEBUGFS_REG32(SOR_DP_SPARE1), + DEBUGFS_REG32(SOR_DP_AUDIO_CTRL), + DEBUGFS_REG32(SOR_DP_AUDIO_HBLANK_SYMBOLS), + DEBUGFS_REG32(SOR_DP_AUDIO_VBLANK_SYMBOLS), + DEBUGFS_REG32(SOR_DP_GENERIC_INFOFRAME_HEADER), + DEBUGFS_REG32(SOR_DP_GENERIC_INFOFRAME_SUBPACK0), + DEBUGFS_REG32(SOR_DP_GENERIC_INFOFRAME_SUBPACK1), + DEBUGFS_REG32(SOR_DP_GENERIC_INFOFRAME_SUBPACK2), + DEBUGFS_REG32(SOR_DP_GENERIC_INFOFRAME_SUBPACK3), + DEBUGFS_REG32(SOR_DP_GENERIC_INFOFRAME_SUBPACK4), + DEBUGFS_REG32(SOR_DP_GENERIC_INFOFRAME_SUBPACK5), + DEBUGFS_REG32(SOR_DP_GENERIC_INFOFRAME_SUBPACK6), + DEBUGFS_REG32(SOR_DP_TPG), + DEBUGFS_REG32(SOR_DP_TPG_CONFIG), + DEBUGFS_REG32(SOR_DP_LQ_CSTM0), + DEBUGFS_REG32(SOR_DP_LQ_CSTM1), + DEBUGFS_REG32(SOR_DP_LQ_CSTM2), +}; + static int tegra_sor_show_regs(struct seq_file *s, void *data) { struct drm_info_node *node = s->private; struct tegra_sor *sor = node->info_ent->data; struct drm_crtc *crtc = sor->output.encoder.crtc; struct drm_device *drm = node->minor->dev; + unsigned int i; int err = 0; drm_modeset_lock_all(drm); @@ -1120,126 +1361,12 @@ static int tegra_sor_show_regs(struct seq_file *s, void *data) goto unlock; } -#define DUMP_REG(name) \ - seq_printf(s, "%-38s %#05x %08x\n", #name, name, \ - tegra_sor_readl(sor, name)) - - DUMP_REG(SOR_CTXSW); - DUMP_REG(SOR_SUPER_STATE0); - DUMP_REG(SOR_SUPER_STATE1); - DUMP_REG(SOR_STATE0); - DUMP_REG(SOR_STATE1); - DUMP_REG(SOR_HEAD_STATE0(0)); - DUMP_REG(SOR_HEAD_STATE0(1)); - DUMP_REG(SOR_HEAD_STATE1(0)); - DUMP_REG(SOR_HEAD_STATE1(1)); - DUMP_REG(SOR_HEAD_STATE2(0)); - DUMP_REG(SOR_HEAD_STATE2(1)); - DUMP_REG(SOR_HEAD_STATE3(0)); - DUMP_REG(SOR_HEAD_STATE3(1)); - DUMP_REG(SOR_HEAD_STATE4(0)); - DUMP_REG(SOR_HEAD_STATE4(1)); - DUMP_REG(SOR_HEAD_STATE5(0)); - DUMP_REG(SOR_HEAD_STATE5(1)); - DUMP_REG(SOR_CRC_CNTRL); - DUMP_REG(SOR_DP_DEBUG_MVID); - DUMP_REG(SOR_CLK_CNTRL); - DUMP_REG(SOR_CAP); - DUMP_REG(SOR_PWR); - DUMP_REG(SOR_TEST); - DUMP_REG(SOR_PLL0); - DUMP_REG(SOR_PLL1); - DUMP_REG(SOR_PLL2); - DUMP_REG(SOR_PLL3); - DUMP_REG(SOR_CSTM); - DUMP_REG(SOR_LVDS); - DUMP_REG(SOR_CRCA); - DUMP_REG(SOR_CRCB); - DUMP_REG(SOR_BLANK); - DUMP_REG(SOR_SEQ_CTL); - DUMP_REG(SOR_LANE_SEQ_CTL); - DUMP_REG(SOR_SEQ_INST(0)); - DUMP_REG(SOR_SEQ_INST(1)); - DUMP_REG(SOR_SEQ_INST(2)); - DUMP_REG(SOR_SEQ_INST(3)); - DUMP_REG(SOR_SEQ_INST(4)); - DUMP_REG(SOR_SEQ_INST(5)); - DUMP_REG(SOR_SEQ_INST(6)); - DUMP_REG(SOR_SEQ_INST(7)); - DUMP_REG(SOR_SEQ_INST(8)); - DUMP_REG(SOR_SEQ_INST(9)); - DUMP_REG(SOR_SEQ_INST(10)); - DUMP_REG(SOR_SEQ_INST(11)); - DUMP_REG(SOR_SEQ_INST(12)); - DUMP_REG(SOR_SEQ_INST(13)); - DUMP_REG(SOR_SEQ_INST(14)); - DUMP_REG(SOR_SEQ_INST(15)); - DUMP_REG(SOR_PWM_DIV); - DUMP_REG(SOR_PWM_CTL); - DUMP_REG(SOR_VCRC_A0); - DUMP_REG(SOR_VCRC_A1); - DUMP_REG(SOR_VCRC_B0); - DUMP_REG(SOR_VCRC_B1); - DUMP_REG(SOR_CCRC_A0); - DUMP_REG(SOR_CCRC_A1); - DUMP_REG(SOR_CCRC_B0); - DUMP_REG(SOR_CCRC_B1); - DUMP_REG(SOR_EDATA_A0); - DUMP_REG(SOR_EDATA_A1); - DUMP_REG(SOR_EDATA_B0); - DUMP_REG(SOR_EDATA_B1); - DUMP_REG(SOR_COUNT_A0); - DUMP_REG(SOR_COUNT_A1); - DUMP_REG(SOR_COUNT_B0); - DUMP_REG(SOR_COUNT_B1); - DUMP_REG(SOR_DEBUG_A0); - DUMP_REG(SOR_DEBUG_A1); - DUMP_REG(SOR_DEBUG_B0); - DUMP_REG(SOR_DEBUG_B1); - DUMP_REG(SOR_TRIG); - DUMP_REG(SOR_MSCHECK); - DUMP_REG(SOR_XBAR_CTRL); - DUMP_REG(SOR_XBAR_POL); - DUMP_REG(SOR_DP_LINKCTL0); - DUMP_REG(SOR_DP_LINKCTL1); - DUMP_REG(SOR_LANE_DRIVE_CURRENT0); - DUMP_REG(SOR_LANE_DRIVE_CURRENT1); - DUMP_REG(SOR_LANE4_DRIVE_CURRENT0); - DUMP_REG(SOR_LANE4_DRIVE_CURRENT1); - DUMP_REG(SOR_LANE_PREEMPHASIS0); - DUMP_REG(SOR_LANE_PREEMPHASIS1); - DUMP_REG(SOR_LANE4_PREEMPHASIS0); - DUMP_REG(SOR_LANE4_PREEMPHASIS1); - DUMP_REG(SOR_LANE_POSTCURSOR0); - DUMP_REG(SOR_LANE_POSTCURSOR1); - DUMP_REG(SOR_DP_CONFIG0); - DUMP_REG(SOR_DP_CONFIG1); - DUMP_REG(SOR_DP_MN0); - DUMP_REG(SOR_DP_MN1); - DUMP_REG(SOR_DP_PADCTL0); - DUMP_REG(SOR_DP_PADCTL1); - DUMP_REG(SOR_DP_DEBUG0); - DUMP_REG(SOR_DP_DEBUG1); - DUMP_REG(SOR_DP_SPARE0); - DUMP_REG(SOR_DP_SPARE1); - DUMP_REG(SOR_DP_AUDIO_CTRL); - DUMP_REG(SOR_DP_AUDIO_HBLANK_SYMBOLS); - DUMP_REG(SOR_DP_AUDIO_VBLANK_SYMBOLS); - DUMP_REG(SOR_DP_GENERIC_INFOFRAME_HEADER); - DUMP_REG(SOR_DP_GENERIC_INFOFRAME_SUBPACK0); - DUMP_REG(SOR_DP_GENERIC_INFOFRAME_SUBPACK1); - DUMP_REG(SOR_DP_GENERIC_INFOFRAME_SUBPACK2); - DUMP_REG(SOR_DP_GENERIC_INFOFRAME_SUBPACK3); - DUMP_REG(SOR_DP_GENERIC_INFOFRAME_SUBPACK4); - DUMP_REG(SOR_DP_GENERIC_INFOFRAME_SUBPACK5); - DUMP_REG(SOR_DP_GENERIC_INFOFRAME_SUBPACK6); - DUMP_REG(SOR_DP_TPG); - DUMP_REG(SOR_DP_TPG_CONFIG); - DUMP_REG(SOR_DP_LQ_CSTM0); - DUMP_REG(SOR_DP_LQ_CSTM1); - DUMP_REG(SOR_DP_LQ_CSTM2); - -#undef DUMP_REG + for (i = 0; i < ARRAY_SIZE(tegra_sor_regs); i++) { + unsigned int offset = tegra_sor_regs[i].offset; + + seq_printf(s, "%-38s %#05x %08x\n", tegra_sor_regs[i].name, + offset, tegra_sor_readl(sor, offset)); + } unlock: drm_modeset_unlock_all(drm); @@ -1251,57 +1378,46 @@ static const struct drm_info_list debugfs_files[] = { { "regs", tegra_sor_show_regs, 0, NULL }, }; -static int tegra_sor_debugfs_init(struct tegra_sor *sor, - struct drm_minor *minor) +static int tegra_sor_late_register(struct drm_connector *connector) { - const char *name = sor->soc->supports_dp ? "sor1" : "sor"; - unsigned int i; + struct tegra_output *output = connector_to_output(connector); + unsigned int i, count = ARRAY_SIZE(debugfs_files); + struct drm_minor *minor = connector->dev->primary; + struct dentry *root = connector->debugfs_entry; + struct tegra_sor *sor = to_sor(output); int err; - sor->debugfs = debugfs_create_dir(name, minor->debugfs_root); - if (!sor->debugfs) - return -ENOMEM; - sor->debugfs_files = kmemdup(debugfs_files, sizeof(debugfs_files), GFP_KERNEL); - if (!sor->debugfs_files) { - err = -ENOMEM; - goto remove; - } + if (!sor->debugfs_files) + return -ENOMEM; - for (i = 0; i < ARRAY_SIZE(debugfs_files); i++) + for (i = 0; i < count; i++) sor->debugfs_files[i].data = sor; - err = drm_debugfs_create_files(sor->debugfs_files, - ARRAY_SIZE(debugfs_files), - sor->debugfs, minor); + err = drm_debugfs_create_files(sor->debugfs_files, count, root, minor); if (err < 0) goto free; - sor->minor = minor; - return 0; free: kfree(sor->debugfs_files); sor->debugfs_files = NULL; -remove: - debugfs_remove_recursive(sor->debugfs); - sor->debugfs = NULL; + return err; } -static void tegra_sor_debugfs_exit(struct tegra_sor *sor) +static void tegra_sor_early_unregister(struct drm_connector *connector) { - drm_debugfs_remove_files(sor->debugfs_files, ARRAY_SIZE(debugfs_files), - sor->minor); - sor->minor = NULL; + struct tegra_output *output = connector_to_output(connector); + unsigned int count = ARRAY_SIZE(debugfs_files); + struct tegra_sor *sor = to_sor(output); + drm_debugfs_remove_files(sor->debugfs_files, count, + connector->dev->primary); kfree(sor->debugfs_files); sor->debugfs_files = NULL; - - debugfs_remove_recursive(sor->debugfs); - sor->debugfs = NULL; } static void tegra_sor_connector_reset(struct drm_connector *connector) @@ -1354,6 +1470,8 @@ static const struct drm_connector_funcs tegra_sor_connector_funcs = { .destroy = tegra_output_connector_destroy, .atomic_duplicate_state = tegra_sor_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .late_register = tegra_sor_late_register, + .early_unregister = tegra_sor_early_unregister, }; static int tegra_sor_connector_get_modes(struct drm_connector *connector) @@ -1377,10 +1495,6 @@ static enum drm_mode_status tegra_sor_connector_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode) { - /* HDMI 2.0 modes are not yet supported */ - if (mode->clock > 340000) - return MODE_NOCLOCK; - return MODE_OK; } @@ -1417,7 +1531,7 @@ static void tegra_sor_edp_disable(struct drm_encoder *encoder) */ if (dc) { value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); - value &= ~SOR_ENABLE; + value &= ~SOR_ENABLE(0); tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); tegra_dc_commit(dc); @@ -1433,9 +1547,9 @@ static void tegra_sor_edp_disable(struct drm_encoder *encoder) dev_err(sor->dev, "failed to disable DP: %d\n", err); } - err = tegra_io_rail_power_off(TEGRA_IO_RAIL_LVDS); + err = tegra_io_pad_power_disable(sor->pad); if (err < 0) - dev_err(sor->dev, "failed to power off I/O rail: %d\n", err); + dev_err(sor->dev, "failed to power off I/O pad: %d\n", err); if (output->panel) drm_panel_unprepare(output->panel); @@ -1533,40 +1647,40 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) value |= SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_DPCLK; tegra_sor_writel(sor, value, SOR_CLK_CNTRL); - value = tegra_sor_readl(sor, SOR_PLL2); + value = tegra_sor_readl(sor, sor->soc->regs->pll2); value &= ~SOR_PLL2_BANDGAP_POWERDOWN; - tegra_sor_writel(sor, value, SOR_PLL2); + tegra_sor_writel(sor, value, sor->soc->regs->pll2); usleep_range(20, 100); - value = tegra_sor_readl(sor, SOR_PLL3); + value = tegra_sor_readl(sor, sor->soc->regs->pll3); value |= SOR_PLL3_PLL_VDD_MODE_3V3; - tegra_sor_writel(sor, value, SOR_PLL3); + tegra_sor_writel(sor, value, sor->soc->regs->pll3); value = SOR_PLL0_ICHPMP(0xf) | SOR_PLL0_VCOCAP_RST | SOR_PLL0_PLLREG_LEVEL_V45 | SOR_PLL0_RESISTOR_EXT; - tegra_sor_writel(sor, value, SOR_PLL0); + tegra_sor_writel(sor, value, sor->soc->regs->pll0); - value = tegra_sor_readl(sor, SOR_PLL2); + value = tegra_sor_readl(sor, sor->soc->regs->pll2); value |= SOR_PLL2_SEQ_PLLCAPPD; value &= ~SOR_PLL2_SEQ_PLLCAPPD_ENFORCE; value |= SOR_PLL2_LVDS_ENABLE; - tegra_sor_writel(sor, value, SOR_PLL2); + tegra_sor_writel(sor, value, sor->soc->regs->pll2); value = SOR_PLL1_TERM_COMPOUT | SOR_PLL1_TMDS_TERM; - tegra_sor_writel(sor, value, SOR_PLL1); + tegra_sor_writel(sor, value, sor->soc->regs->pll1); while (true) { - value = tegra_sor_readl(sor, SOR_PLL2); + value = tegra_sor_readl(sor, sor->soc->regs->pll2); if ((value & SOR_PLL2_SEQ_PLLCAPPD_ENFORCE) == 0) break; usleep_range(250, 1000); } - value = tegra_sor_readl(sor, SOR_PLL2); + value = tegra_sor_readl(sor, sor->soc->regs->pll2); value &= ~SOR_PLL2_POWERDOWN_OVERRIDE; value &= ~SOR_PLL2_PORT_POWERDOWN; - tegra_sor_writel(sor, value, SOR_PLL2); + tegra_sor_writel(sor, value, sor->soc->regs->pll2); /* * power up @@ -1579,49 +1693,49 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) tegra_sor_writel(sor, value, SOR_CLK_CNTRL); /* step 1 */ - value = tegra_sor_readl(sor, SOR_PLL2); + value = tegra_sor_readl(sor, sor->soc->regs->pll2); value |= SOR_PLL2_SEQ_PLLCAPPD_ENFORCE | SOR_PLL2_PORT_POWERDOWN | SOR_PLL2_BANDGAP_POWERDOWN; - tegra_sor_writel(sor, value, SOR_PLL2); + tegra_sor_writel(sor, value, sor->soc->regs->pll2); - value = tegra_sor_readl(sor, SOR_PLL0); + value = tegra_sor_readl(sor, sor->soc->regs->pll0); value |= SOR_PLL0_VCOPD | SOR_PLL0_PWR; - tegra_sor_writel(sor, value, SOR_PLL0); + tegra_sor_writel(sor, value, sor->soc->regs->pll0); - value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value = tegra_sor_readl(sor, sor->soc->regs->dp_padctl0); value &= ~SOR_DP_PADCTL_PAD_CAL_PD; - tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + tegra_sor_writel(sor, value, sor->soc->regs->dp_padctl0); /* step 2 */ - err = tegra_io_rail_power_on(TEGRA_IO_RAIL_LVDS); + err = tegra_io_pad_power_enable(sor->pad); if (err < 0) - dev_err(sor->dev, "failed to power on I/O rail: %d\n", err); + dev_err(sor->dev, "failed to power on I/O pad: %d\n", err); usleep_range(5, 100); /* step 3 */ - value = tegra_sor_readl(sor, SOR_PLL2); + value = tegra_sor_readl(sor, sor->soc->regs->pll2); value &= ~SOR_PLL2_BANDGAP_POWERDOWN; - tegra_sor_writel(sor, value, SOR_PLL2); + tegra_sor_writel(sor, value, sor->soc->regs->pll2); usleep_range(20, 100); /* step 4 */ - value = tegra_sor_readl(sor, SOR_PLL0); + value = tegra_sor_readl(sor, sor->soc->regs->pll0); value &= ~SOR_PLL0_VCOPD; value &= ~SOR_PLL0_PWR; - tegra_sor_writel(sor, value, SOR_PLL0); + tegra_sor_writel(sor, value, sor->soc->regs->pll0); - value = tegra_sor_readl(sor, SOR_PLL2); + value = tegra_sor_readl(sor, sor->soc->regs->pll2); value &= ~SOR_PLL2_SEQ_PLLCAPPD_ENFORCE; - tegra_sor_writel(sor, value, SOR_PLL2); + tegra_sor_writel(sor, value, sor->soc->regs->pll2); usleep_range(200, 1000); /* step 5 */ - value = tegra_sor_readl(sor, SOR_PLL2); + value = tegra_sor_readl(sor, sor->soc->regs->pll2); value &= ~SOR_PLL2_PORT_POWERDOWN; - tegra_sor_writel(sor, value, SOR_PLL2); + tegra_sor_writel(sor, value, sor->soc->regs->pll2); /* XXX not in TRM */ for (value = 0, i = 0; i < 5; i++) @@ -1637,7 +1751,7 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) dev_err(sor->dev, "failed to set parent clock: %d\n", err); /* power DP lanes */ - value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value = tegra_sor_readl(sor, sor->soc->regs->dp_padctl0); if (link.num_lanes <= 2) value &= ~(SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_2); @@ -1654,7 +1768,7 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) else value |= SOR_DP_PADCTL_PD_TXD_0; - tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + tegra_sor_writel(sor, value, sor->soc->regs->dp_padctl0); value = tegra_sor_readl(sor, SOR_DP_LINKCTL0); value &= ~SOR_DP_LINKCTL_LANE_COUNT_MASK; @@ -1698,9 +1812,9 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) tegra_sor_writel(sor, value, SOR_DP_TPG); /* enable pad calibration logic */ - value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value = tegra_sor_readl(sor, sor->soc->regs->dp_padctl0); value |= SOR_DP_PADCTL_PAD_CAL_PD; - tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + tegra_sor_writel(sor, value, sor->soc->regs->dp_padctl0); err = drm_dp_link_probe(sor->aux, &link); if (err < 0) @@ -1773,7 +1887,7 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) tegra_sor_update(sor); value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); - value |= SOR_ENABLE; + value |= SOR_ENABLE(0); tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); tegra_dc_commit(dc); @@ -1805,6 +1919,18 @@ tegra_sor_encoder_atomic_check(struct drm_encoder *encoder, info = &output->connector.display_info; + /* + * For HBR2 modes, the SOR brick needs to use the x20 multiplier, so + * the pixel clock must be corrected accordingly. + */ + if (pclk >= 340000000) { + state->link_speed = 20; + state->pclk = pclk / 2; + } else { + state->link_speed = 10; + state->pclk = pclk; + } + err = tegra_dc_state_setup_clock(dc, crtc_state, sor->clk_parent, pclk, 0); if (err < 0) { @@ -1955,6 +2081,81 @@ tegra_sor_hdmi_find_settings(struct tegra_sor *sor, unsigned long frequency) return NULL; } +static void tegra_sor_hdmi_disable_scrambling(struct tegra_sor *sor) +{ + u32 value; + + value = tegra_sor_readl(sor, SOR_HDMI2_CTRL); + value &= ~SOR_HDMI2_CTRL_CLOCK_MODE_DIV_BY_4; + value &= ~SOR_HDMI2_CTRL_SCRAMBLE; + tegra_sor_writel(sor, value, SOR_HDMI2_CTRL); +} + +static void tegra_sor_hdmi_scdc_disable(struct tegra_sor *sor) +{ + struct i2c_adapter *ddc = sor->output.ddc; + + drm_scdc_set_high_tmds_clock_ratio(ddc, false); + drm_scdc_set_scrambling(ddc, false); + + tegra_sor_hdmi_disable_scrambling(sor); +} + +static void tegra_sor_hdmi_scdc_stop(struct tegra_sor *sor) +{ + if (sor->scdc_enabled) { + cancel_delayed_work_sync(&sor->scdc); + tegra_sor_hdmi_scdc_disable(sor); + } +} + +static void tegra_sor_hdmi_enable_scrambling(struct tegra_sor *sor) +{ + u32 value; + + value = tegra_sor_readl(sor, SOR_HDMI2_CTRL); + value |= SOR_HDMI2_CTRL_CLOCK_MODE_DIV_BY_4; + value |= SOR_HDMI2_CTRL_SCRAMBLE; + tegra_sor_writel(sor, value, SOR_HDMI2_CTRL); +} + +static void tegra_sor_hdmi_scdc_enable(struct tegra_sor *sor) +{ + struct i2c_adapter *ddc = sor->output.ddc; + + drm_scdc_set_high_tmds_clock_ratio(ddc, true); + drm_scdc_set_scrambling(ddc, true); + + tegra_sor_hdmi_enable_scrambling(sor); +} + +static void tegra_sor_hdmi_scdc_work(struct work_struct *work) +{ + struct tegra_sor *sor = container_of(work, struct tegra_sor, scdc.work); + struct i2c_adapter *ddc = sor->output.ddc; + + if (!drm_scdc_get_scrambling_status(ddc)) { + DRM_DEBUG_KMS("SCDC not scrambled\n"); + tegra_sor_hdmi_scdc_enable(sor); + } + + schedule_delayed_work(&sor->scdc, msecs_to_jiffies(5000)); +} + +static void tegra_sor_hdmi_scdc_start(struct tegra_sor *sor) +{ + struct drm_scdc *scdc = &sor->output.connector.display_info.hdmi.scdc; + struct drm_display_mode *mode; + + mode = &sor->output.encoder.crtc->state->adjusted_mode; + + if (mode->clock >= 340000 && scdc->supported) { + schedule_delayed_work(&sor->scdc, msecs_to_jiffies(5000)); + tegra_sor_hdmi_scdc_enable(sor); + sor->scdc_enabled = true; + } +} + static void tegra_sor_hdmi_disable(struct drm_encoder *encoder) { struct tegra_output *output = encoder_to_output(encoder); @@ -1963,6 +2164,8 @@ static void tegra_sor_hdmi_disable(struct drm_encoder *encoder) u32 value; int err; + tegra_sor_hdmi_scdc_stop(sor); + err = tegra_sor_detach(sor); if (err < 0) dev_err(sor->dev, "failed to detach SOR: %d\n", err); @@ -1972,8 +2175,12 @@ static void tegra_sor_hdmi_disable(struct drm_encoder *encoder) /* disable display to SOR clock */ value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); - value &= ~SOR1_TIMING_CYA; - value &= ~SOR1_ENABLE; + + if (!sor->soc->has_nvdisplay) + value &= ~(SOR1_TIMING_CYA | SOR_ENABLE(1)); + else + value &= ~SOR_ENABLE(sor->index); + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); tegra_dc_commit(dc); @@ -1982,9 +2189,9 @@ static void tegra_sor_hdmi_disable(struct drm_encoder *encoder) if (err < 0) dev_err(sor->dev, "failed to power down SOR: %d\n", err); - err = tegra_io_rail_power_off(TEGRA_IO_RAIL_HDMI); + err = tegra_io_pad_power_disable(sor->pad); if (err < 0) - dev_err(sor->dev, "failed to power off HDMI rail: %d\n", err); + dev_err(sor->dev, "failed to power off I/O pad: %d\n", err); pm_runtime_put(sor->dev); } @@ -1998,12 +2205,14 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) struct tegra_sor *sor = to_sor(output); struct tegra_sor_state *state; struct drm_display_mode *mode; + unsigned long rate, pclk; unsigned int div, i; u32 value; int err; state = to_sor_state(output->connector.state); mode = &encoder->crtc->state->adjusted_mode; + pclk = mode->clock * 1000; pm_runtime_get_sync(sor->dev); @@ -2016,44 +2225,44 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) div = clk_get_rate(sor->clk) / 1000000 * 4; - err = tegra_io_rail_power_on(TEGRA_IO_RAIL_HDMI); + err = tegra_io_pad_power_enable(sor->pad); if (err < 0) - dev_err(sor->dev, "failed to power on HDMI rail: %d\n", err); + dev_err(sor->dev, "failed to power on I/O pad: %d\n", err); usleep_range(20, 100); - value = tegra_sor_readl(sor, SOR_PLL2); + value = tegra_sor_readl(sor, sor->soc->regs->pll2); value &= ~SOR_PLL2_BANDGAP_POWERDOWN; - tegra_sor_writel(sor, value, SOR_PLL2); + tegra_sor_writel(sor, value, sor->soc->regs->pll2); usleep_range(20, 100); - value = tegra_sor_readl(sor, SOR_PLL3); + value = tegra_sor_readl(sor, sor->soc->regs->pll3); value &= ~SOR_PLL3_PLL_VDD_MODE_3V3; - tegra_sor_writel(sor, value, SOR_PLL3); + tegra_sor_writel(sor, value, sor->soc->regs->pll3); - value = tegra_sor_readl(sor, SOR_PLL0); + value = tegra_sor_readl(sor, sor->soc->regs->pll0); value &= ~SOR_PLL0_VCOPD; value &= ~SOR_PLL0_PWR; - tegra_sor_writel(sor, value, SOR_PLL0); + tegra_sor_writel(sor, value, sor->soc->regs->pll0); - value = tegra_sor_readl(sor, SOR_PLL2); + value = tegra_sor_readl(sor, sor->soc->regs->pll2); value &= ~SOR_PLL2_SEQ_PLLCAPPD_ENFORCE; - tegra_sor_writel(sor, value, SOR_PLL2); + tegra_sor_writel(sor, value, sor->soc->regs->pll2); usleep_range(200, 400); - value = tegra_sor_readl(sor, SOR_PLL2); + value = tegra_sor_readl(sor, sor->soc->regs->pll2); value &= ~SOR_PLL2_POWERDOWN_OVERRIDE; value &= ~SOR_PLL2_PORT_POWERDOWN; - tegra_sor_writel(sor, value, SOR_PLL2); + tegra_sor_writel(sor, value, sor->soc->regs->pll2); usleep_range(20, 100); - value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value = tegra_sor_readl(sor, sor->soc->regs->dp_padctl0); value |= SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_0 | SOR_DP_PADCTL_PD_TXD_1 | SOR_DP_PADCTL_PD_TXD_2; - tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + tegra_sor_writel(sor, value, sor->soc->regs->dp_padctl0); while (true) { value = tegra_sor_readl(sor, SOR_LANE_SEQ_CTL); @@ -2079,18 +2288,30 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) value &= ~SOR_CLK_CNTRL_DP_LINK_SPEED_MASK; value &= ~SOR_CLK_CNTRL_DP_CLK_SEL_MASK; - if (mode->clock < 340000) + if (mode->clock < 340000) { + DRM_DEBUG_KMS("setting 2.7 GHz link speed\n"); value |= SOR_CLK_CNTRL_DP_LINK_SPEED_G2_70; - else + } else { + DRM_DEBUG_KMS("setting 5.4 GHz link speed\n"); value |= SOR_CLK_CNTRL_DP_LINK_SPEED_G5_40; + } value |= SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_PCLK; tegra_sor_writel(sor, value, SOR_CLK_CNTRL); + /* SOR pad PLL stabilization time */ + usleep_range(250, 1000); + + value = tegra_sor_readl(sor, SOR_DP_LINKCTL0); + value &= ~SOR_DP_LINKCTL_LANE_COUNT_MASK; + value |= SOR_DP_LINKCTL_LANE_COUNT(4); + tegra_sor_writel(sor, value, SOR_DP_LINKCTL0); + value = tegra_sor_readl(sor, SOR_DP_SPARE0); - value |= SOR_DP_SPARE_DISP_VIDEO_PREAMBLE; + value &= ~SOR_DP_SPARE_DISP_VIDEO_PREAMBLE; value &= ~SOR_DP_SPARE_PANEL_INTERNAL; - value |= SOR_DP_SPARE_SEQ_ENABLE; + value &= ~SOR_DP_SPARE_SEQ_ENABLE; + value &= ~SOR_DP_SPARE_MACRO_SOR_CLK; tegra_sor_writel(sor, value, SOR_DP_SPARE0); value = SOR_SEQ_CTL_PU_PC(0) | SOR_SEQ_CTL_PU_PC_ALT(0) | @@ -2102,9 +2323,11 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) tegra_sor_writel(sor, value, SOR_SEQ_INST(0)); tegra_sor_writel(sor, value, SOR_SEQ_INST(8)); - /* program the reference clock */ - value = SOR_REFCLK_DIV_INT(div) | SOR_REFCLK_DIV_FRAC(div); - tegra_sor_writel(sor, value, SOR_REFCLK); + if (!sor->soc->has_nvdisplay) { + /* program the reference clock */ + value = SOR_REFCLK_DIV_INT(div) | SOR_REFCLK_DIV_FRAC(div); + tegra_sor_writel(sor, value, SOR_REFCLK); + } /* XXX not in TRM */ for (value = 0, i = 0; i < 5; i++) @@ -2127,13 +2350,25 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) return; } - value = SOR_INPUT_CONTROL_HDMI_SRC_SELECT(dc->pipe); + /* adjust clock rate for HDMI 2.0 modes */ + rate = clk_get_rate(sor->clk_parent); + + if (mode->clock >= 340000) + rate /= 2; - /* XXX is this the proper check? */ - if (mode->clock < 75000) - value |= SOR_INPUT_CONTROL_ARM_VIDEO_RANGE_LIMITED; + DRM_DEBUG_KMS("setting clock to %lu Hz, mode: %lu Hz\n", rate, pclk); - tegra_sor_writel(sor, value, SOR_INPUT_CONTROL); + clk_set_rate(sor->clk, rate); + + if (!sor->soc->has_nvdisplay) { + value = SOR_INPUT_CONTROL_HDMI_SRC_SELECT(dc->pipe); + + /* XXX is this the proper check? */ + if (mode->clock < 75000) + value |= SOR_INPUT_CONTROL_ARM_VIDEO_RANGE_LIMITED; + + tegra_sor_writel(sor, value, SOR_INPUT_CONTROL); + } max_ac = ((mode->htotal - mode->hdisplay) - SOR_REKEY - 18) / 32; @@ -2141,20 +2376,23 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) SOR_HDMI_CTRL_AUDIO_LAYOUT | SOR_HDMI_CTRL_REKEY(SOR_REKEY); tegra_sor_writel(sor, value, SOR_HDMI_CTRL); - /* H_PULSE2 setup */ - pulse_start = h_ref_to_sync + (mode->hsync_end - mode->hsync_start) + - (mode->htotal - mode->hsync_end) - 10; + if (!dc->soc->has_nvdisplay) { + /* H_PULSE2 setup */ + pulse_start = h_ref_to_sync + + (mode->hsync_end - mode->hsync_start) + + (mode->htotal - mode->hsync_end) - 10; - value = PULSE_LAST_END_A | PULSE_QUAL_VACTIVE | - PULSE_POLARITY_HIGH | PULSE_MODE_NORMAL; - tegra_dc_writel(dc, value, DC_DISP_H_PULSE2_CONTROL); + value = PULSE_LAST_END_A | PULSE_QUAL_VACTIVE | + PULSE_POLARITY_HIGH | PULSE_MODE_NORMAL; + tegra_dc_writel(dc, value, DC_DISP_H_PULSE2_CONTROL); - value = PULSE_END(pulse_start + 8) | PULSE_START(pulse_start); - tegra_dc_writel(dc, value, DC_DISP_H_PULSE2_POSITION_A); + value = PULSE_END(pulse_start + 8) | PULSE_START(pulse_start); + tegra_dc_writel(dc, value, DC_DISP_H_PULSE2_POSITION_A); - value = tegra_dc_readl(dc, DC_DISP_DISP_SIGNAL_OPTIONS0); - value |= H_PULSE2_ENABLE; - tegra_dc_writel(dc, value, DC_DISP_DISP_SIGNAL_OPTIONS0); + value = tegra_dc_readl(dc, DC_DISP_DISP_SIGNAL_OPTIONS0); + value |= H_PULSE2_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_SIGNAL_OPTIONS0); + } /* infoframe setup */ err = tegra_sor_hdmi_setup_avi_infoframe(sor, mode); @@ -2171,9 +2409,9 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) tegra_sor_writel(sor, value, SOR_STATE1); /* power up pad calibration */ - value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value = tegra_sor_readl(sor, sor->soc->regs->dp_padctl0); value &= ~SOR_DP_PADCTL_PAD_CAL_PD; - tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + tegra_sor_writel(sor, value, sor->soc->regs->dp_padctl0); /* production settings */ settings = tegra_sor_hdmi_find_settings(sor, mode->clock * 1000); @@ -2183,51 +2421,68 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) return; } - value = tegra_sor_readl(sor, SOR_PLL0); + value = tegra_sor_readl(sor, sor->soc->regs->pll0); value &= ~SOR_PLL0_ICHPMP_MASK; + value &= ~SOR_PLL0_FILTER_MASK; value &= ~SOR_PLL0_VCOCAP_MASK; value |= SOR_PLL0_ICHPMP(settings->ichpmp); + value |= SOR_PLL0_FILTER(settings->filter); value |= SOR_PLL0_VCOCAP(settings->vcocap); - tegra_sor_writel(sor, value, SOR_PLL0); - - tegra_sor_dp_term_calibrate(sor); + tegra_sor_writel(sor, value, sor->soc->regs->pll0); - value = tegra_sor_readl(sor, SOR_PLL1); + /* XXX not in TRM */ + value = tegra_sor_readl(sor, sor->soc->regs->pll1); value &= ~SOR_PLL1_LOADADJ_MASK; + value &= ~SOR_PLL1_TMDS_TERMADJ_MASK; value |= SOR_PLL1_LOADADJ(settings->loadadj); - tegra_sor_writel(sor, value, SOR_PLL1); + value |= SOR_PLL1_TMDS_TERMADJ(settings->tmds_termadj); + value |= SOR_PLL1_TMDS_TERM; + tegra_sor_writel(sor, value, sor->soc->regs->pll1); - value = tegra_sor_readl(sor, SOR_PLL3); + value = tegra_sor_readl(sor, sor->soc->regs->pll3); + value &= ~SOR_PLL3_BG_TEMP_COEF_MASK; value &= ~SOR_PLL3_BG_VREF_LEVEL_MASK; - value |= SOR_PLL3_BG_VREF_LEVEL(settings->bg_vref); - tegra_sor_writel(sor, value, SOR_PLL3); - - value = settings->drive_current[0] << 24 | - settings->drive_current[1] << 16 | - settings->drive_current[2] << 8 | - settings->drive_current[3] << 0; + value &= ~SOR_PLL3_AVDD10_LEVEL_MASK; + value &= ~SOR_PLL3_AVDD14_LEVEL_MASK; + value |= SOR_PLL3_BG_TEMP_COEF(settings->bg_temp_coef); + value |= SOR_PLL3_BG_VREF_LEVEL(settings->bg_vref_level); + value |= SOR_PLL3_AVDD10_LEVEL(settings->avdd10_level); + value |= SOR_PLL3_AVDD14_LEVEL(settings->avdd14_level); + tegra_sor_writel(sor, value, sor->soc->regs->pll3); + + value = settings->drive_current[3] << 24 | + settings->drive_current[2] << 16 | + settings->drive_current[1] << 8 | + settings->drive_current[0] << 0; tegra_sor_writel(sor, value, SOR_LANE_DRIVE_CURRENT0); - value = settings->preemphasis[0] << 24 | - settings->preemphasis[1] << 16 | - settings->preemphasis[2] << 8 | - settings->preemphasis[3] << 0; + value = settings->preemphasis[3] << 24 | + settings->preemphasis[2] << 16 | + settings->preemphasis[1] << 8 | + settings->preemphasis[0] << 0; tegra_sor_writel(sor, value, SOR_LANE_PREEMPHASIS0); - value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value = tegra_sor_readl(sor, sor->soc->regs->dp_padctl0); value &= ~SOR_DP_PADCTL_TX_PU_MASK; value |= SOR_DP_PADCTL_TX_PU_ENABLE; - value |= SOR_DP_PADCTL_TX_PU(settings->tx_pu); - tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + value |= SOR_DP_PADCTL_TX_PU(settings->tx_pu_value); + tegra_sor_writel(sor, value, sor->soc->regs->dp_padctl0); + + value = tegra_sor_readl(sor, sor->soc->regs->dp_padctl2); + value &= ~SOR_DP_PADCTL_SPAREPLL_MASK; + value |= SOR_DP_PADCTL_SPAREPLL(settings->sparepll); + tegra_sor_writel(sor, value, sor->soc->regs->dp_padctl2); /* power down pad calibration */ - value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value = tegra_sor_readl(sor, sor->soc->regs->dp_padctl0); value |= SOR_DP_PADCTL_PAD_CAL_PD; - tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + tegra_sor_writel(sor, value, sor->soc->regs->dp_padctl0); - /* miscellaneous display controller settings */ - value = VSYNC_H_POSITION(1); - tegra_dc_writel(dc, value, DC_DISP_DISP_TIMING_OPTIONS); + if (!dc->soc->has_nvdisplay) { + /* miscellaneous display controller settings */ + value = VSYNC_H_POSITION(1); + tegra_dc_writel(dc, value, DC_DISP_DISP_TIMING_OPTIONS); + } value = tegra_dc_readl(dc, DC_DISP_DISP_COLOR_CONTROL); value &= ~DITHER_CONTROL_MASK; @@ -2242,6 +2497,14 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) value |= BASE_COLOR_SIZE_888; break; + case 10: + value |= BASE_COLOR_SIZE_101010; + break; + + case 12: + value |= BASE_COLOR_SIZE_121212; + break; + default: WARN(1, "%u bits-per-color not supported\n", state->bpc); value |= BASE_COLOR_SIZE_888; @@ -2250,40 +2513,65 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) tegra_dc_writel(dc, value, DC_DISP_DISP_COLOR_CONTROL); + /* XXX set display head owner */ + value = tegra_sor_readl(sor, SOR_STATE1); + value &= ~SOR_STATE_ASY_OWNER_MASK; + value |= SOR_STATE_ASY_OWNER(1 + dc->pipe); + tegra_sor_writel(sor, value, SOR_STATE1); + err = tegra_sor_power_up(sor, 250); if (err < 0) dev_err(sor->dev, "failed to power up SOR: %d\n", err); /* configure dynamic range of output */ - value = tegra_sor_readl(sor, SOR_HEAD_STATE0(dc->pipe)); + value = tegra_sor_readl(sor, sor->soc->regs->head_state0 + dc->pipe); value &= ~SOR_HEAD_STATE_RANGECOMPRESS_MASK; value &= ~SOR_HEAD_STATE_DYNRANGE_MASK; - tegra_sor_writel(sor, value, SOR_HEAD_STATE0(dc->pipe)); + tegra_sor_writel(sor, value, sor->soc->regs->head_state0 + dc->pipe); /* configure colorspace */ - value = tegra_sor_readl(sor, SOR_HEAD_STATE0(dc->pipe)); + value = tegra_sor_readl(sor, sor->soc->regs->head_state0 + dc->pipe); value &= ~SOR_HEAD_STATE_COLORSPACE_MASK; value |= SOR_HEAD_STATE_COLORSPACE_RGB; - tegra_sor_writel(sor, value, SOR_HEAD_STATE0(dc->pipe)); + tegra_sor_writel(sor, value, sor->soc->regs->head_state0 + dc->pipe); tegra_sor_mode_set(sor, mode, state); tegra_sor_update(sor); + /* program preamble timing in SOR (XXX) */ + value = tegra_sor_readl(sor, SOR_DP_SPARE0); + value &= ~SOR_DP_SPARE_DISP_VIDEO_PREAMBLE; + tegra_sor_writel(sor, value, SOR_DP_SPARE0); + err = tegra_sor_attach(sor); if (err < 0) dev_err(sor->dev, "failed to attach SOR: %d\n", err); /* enable display to SOR clock and generate HDMI preamble */ value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); - value |= SOR1_ENABLE | SOR1_TIMING_CYA; + + if (!sor->soc->has_nvdisplay) + value |= SOR_ENABLE(1) | SOR1_TIMING_CYA; + else + value |= SOR_ENABLE(sor->index); + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); + if (dc->soc->has_nvdisplay) { + value = tegra_dc_readl(dc, DC_DISP_CORE_SOR_SET_CONTROL(sor->index)); + value &= ~PROTOCOL_MASK; + value |= PROTOCOL_SINGLE_TMDS_A; + tegra_dc_writel(dc, value, DC_DISP_CORE_SOR_SET_CONTROL(sor->index)); + } + tegra_dc_commit(dc); err = tegra_sor_wakeup(sor); if (err < 0) dev_err(sor->dev, "failed to wakeup SOR: %d\n", err); + + tegra_sor_hdmi_scdc_start(sor); } static const struct drm_encoder_helper_funcs tegra_sor_hdmi_helpers = { @@ -2344,13 +2632,7 @@ static int tegra_sor_init(struct host1x_client *client) return err; } - sor->output.encoder.possible_crtcs = 0x3; - - if (IS_ENABLED(CONFIG_DEBUG_FS)) { - err = tegra_sor_debugfs_init(sor, drm->primary); - if (err < 0) - dev_err(sor->dev, "debugfs setup failed: %d\n", err); - } + tegra_output_find_possible_crtcs(&sor->output, drm); if (sor->aux) { err = drm_dp_aux_attach(sor->aux, &sor->output); @@ -2420,9 +2702,6 @@ static int tegra_sor_exit(struct host1x_client *client) clk_disable_unprepare(sor->clk_dp); clk_disable_unprepare(sor->clk); - if (IS_ENABLED(CONFIG_DEBUG_FS)) - tegra_sor_debugfs_exit(sor); - return 0; } @@ -2480,6 +2759,8 @@ static int tegra_sor_hdmi_probe(struct tegra_sor *sor) return err; } + INIT_DELAYED_WORK(&sor->scdc, tegra_sor_hdmi_scdc_work); + return 0; } @@ -2502,19 +2783,53 @@ static const u8 tegra124_sor_xbar_cfg[5] = { 0, 1, 2, 3, 4 }; +static const struct tegra_sor_regs tegra124_sor_regs = { + .head_state0 = 0x05, + .head_state1 = 0x07, + .head_state2 = 0x09, + .head_state3 = 0x0b, + .head_state4 = 0x0d, + .head_state5 = 0x0f, + .pll0 = 0x17, + .pll1 = 0x18, + .pll2 = 0x19, + .pll3 = 0x1a, + .dp_padctl0 = 0x5c, + .dp_padctl2 = 0x73, +}; + static const struct tegra_sor_soc tegra124_sor = { .supports_edp = true, .supports_lvds = true, .supports_hdmi = false, .supports_dp = false, + .regs = &tegra124_sor_regs, + .has_nvdisplay = false, .xbar_cfg = tegra124_sor_xbar_cfg, }; +static const struct tegra_sor_regs tegra210_sor_regs = { + .head_state0 = 0x05, + .head_state1 = 0x07, + .head_state2 = 0x09, + .head_state3 = 0x0b, + .head_state4 = 0x0d, + .head_state5 = 0x0f, + .pll0 = 0x17, + .pll1 = 0x18, + .pll2 = 0x19, + .pll3 = 0x1a, + .dp_padctl0 = 0x5c, + .dp_padctl2 = 0x73, +}; + static const struct tegra_sor_soc tegra210_sor = { .supports_edp = true, .supports_lvds = false, .supports_hdmi = false, .supports_dp = false, + .regs = &tegra210_sor_regs, + .has_nvdisplay = false, .xbar_cfg = tegra124_sor_xbar_cfg, }; @@ -2528,13 +2843,60 @@ static const struct tegra_sor_soc tegra210_sor1 = { .supports_hdmi = true, .supports_dp = true, + .regs = &tegra210_sor_regs, + .has_nvdisplay = false, + .num_settings = ARRAY_SIZE(tegra210_sor_hdmi_defaults), .settings = tegra210_sor_hdmi_defaults, .xbar_cfg = tegra210_sor_xbar_cfg, }; +static const struct tegra_sor_regs tegra186_sor_regs = { + .head_state0 = 0x151, + .head_state1 = 0x154, + .head_state2 = 0x157, + .head_state3 = 0x15a, + .head_state4 = 0x15d, + .head_state5 = 0x160, + .pll0 = 0x163, + .pll1 = 0x164, + .pll2 = 0x165, + .pll3 = 0x166, + .dp_padctl0 = 0x168, + .dp_padctl2 = 0x16a, +}; + +static const struct tegra_sor_soc tegra186_sor = { + .supports_edp = false, + .supports_lvds = false, + .supports_hdmi = false, + .supports_dp = true, + + .regs = &tegra186_sor_regs, + .has_nvdisplay = true, + + .xbar_cfg = tegra124_sor_xbar_cfg, +}; + +static const struct tegra_sor_soc tegra186_sor1 = { + .supports_edp = false, + .supports_lvds = false, + .supports_hdmi = true, + .supports_dp = true, + + .regs = &tegra186_sor_regs, + .has_nvdisplay = true, + + .num_settings = ARRAY_SIZE(tegra186_sor_hdmi_defaults), + .settings = tegra186_sor_hdmi_defaults, + + .xbar_cfg = tegra124_sor_xbar_cfg, +}; + static const struct of_device_id tegra_sor_of_match[] = { + { .compatible = "nvidia,tegra186-sor1", .data = &tegra186_sor1 }, + { .compatible = "nvidia,tegra186-sor", .data = &tegra186_sor }, { .compatible = "nvidia,tegra210-sor1", .data = &tegra210_sor1 }, { .compatible = "nvidia,tegra210-sor", .data = &tegra210_sor }, { .compatible = "nvidia,tegra124-sor", .data = &tegra124_sor }, @@ -2542,6 +2904,29 @@ static const struct of_device_id tegra_sor_of_match[] = { }; MODULE_DEVICE_TABLE(of, tegra_sor_of_match); +static int tegra_sor_parse_dt(struct tegra_sor *sor) +{ + struct device_node *np = sor->dev->of_node; + u32 value; + int err; + + if (sor->soc->has_nvdisplay) { + err = of_property_read_u32(np, "nvidia,interface", &value); + if (err < 0) + return err; + + sor->index = value; + + /* + * override the default that we already set for Tegra210 and + * earlier + */ + sor->pad = TEGRA_IO_PAD_HDMI_DP0 + sor->index; + } + + return 0; +} + static int tegra_sor_probe(struct platform_device *pdev) { struct device_node *np; @@ -2577,6 +2962,7 @@ static int tegra_sor_probe(struct platform_device *pdev) if (!sor->aux) { if (sor->soc->supports_hdmi) { sor->ops = &tegra_sor_hdmi_ops; + sor->pad = TEGRA_IO_PAD_HDMI; } else if (sor->soc->supports_lvds) { dev_err(&pdev->dev, "LVDS not supported yet\n"); return -ENODEV; @@ -2587,6 +2973,7 @@ static int tegra_sor_probe(struct platform_device *pdev) } else { if (sor->soc->supports_edp) { sor->ops = &tegra_sor_edp_ops; + sor->pad = TEGRA_IO_PAD_LVDS; } else if (sor->soc->supports_dp) { dev_err(&pdev->dev, "DisplayPort not supported yet\n"); return -ENODEV; @@ -2596,6 +2983,10 @@ static int tegra_sor_probe(struct platform_device *pdev) } } + err = tegra_sor_parse_dt(sor); + if (err < 0) + return err; + err = tegra_output_probe(&sor->output); if (err < 0) { dev_err(&pdev->dev, "failed to probe output: %d\n", err); @@ -2656,6 +3047,8 @@ static int tegra_sor_probe(struct platform_device *pdev) name, err); goto remove; } + } else { + sor->clk_out = sor->clk; } sor->clk_parent = devm_clk_get(&pdev->dev, "parent"); diff --git a/drivers/gpu/drm/tegra/sor.h b/drivers/gpu/drm/tegra/sor.h index 865c73b48968..fb0854d92a27 100644 --- a/drivers/gpu/drm/tegra/sor.h +++ b/drivers/gpu/drm/tegra/sor.h @@ -89,6 +89,8 @@ #define SOR_PLL0 0x17 #define SOR_PLL0_ICHPMP_MASK (0xf << 24) #define SOR_PLL0_ICHPMP(x) (((x) & 0xf) << 24) +#define SOR_PLL0_FILTER_MASK (0xf << 16) +#define SOR_PLL0_FILTER(x) (((x) & 0xf) << 16) #define SOR_PLL0_VCOCAP_MASK (0xf << 8) #define SOR_PLL0_VCOCAP(x) (((x) & 0xf) << 8) #define SOR_PLL0_VCOCAP_RST SOR_PLL0_VCOCAP(3) @@ -122,10 +124,16 @@ #define SOR_PLL2_SEQ_PLL_PULLDOWN (1 << 16) #define SOR_PLL3 0x1a +#define SOR_PLL3_BG_TEMP_COEF_MASK (0xf << 28) +#define SOR_PLL3_BG_TEMP_COEF(x) (((x) & 0xf) << 28) #define SOR_PLL3_BG_VREF_LEVEL_MASK (0xf << 24) #define SOR_PLL3_BG_VREF_LEVEL(x) (((x) & 0xf) << 24) #define SOR_PLL3_PLL_VDD_MODE_1V8 (0 << 13) #define SOR_PLL3_PLL_VDD_MODE_3V3 (1 << 13) +#define SOR_PLL3_AVDD10_LEVEL_MASK (0xf << 8) +#define SOR_PLL3_AVDD10_LEVEL(x) (((x) & 0xf) << 8) +#define SOR_PLL3_AVDD14_LEVEL_MASK (0xf << 4) +#define SOR_PLL3_AVDD14_LEVEL(x) (((x) & 0xf) << 4) #define SOR_CSTM 0x1b #define SOR_CSTM_ROTCLK_MASK (0xf << 24) @@ -334,6 +342,10 @@ #define SOR_DP_LQ_CSTM1 0x70 #define SOR_DP_LQ_CSTM2 0x71 +#define SOR_DP_PADCTL2 0x73 +#define SOR_DP_PADCTL_SPAREPLL_MASK (0xff << 24) +#define SOR_DP_PADCTL_SPAREPLL(x) (((x) & 0xff) << 24) + #define SOR_HDMI_AUDIO_INFOFRAME_CTRL 0x9a #define SOR_HDMI_AUDIO_INFOFRAME_STATUS 0x9b #define SOR_HDMI_AUDIO_INFOFRAME_HEADER 0x9c @@ -370,4 +382,8 @@ #define SOR_HDMI_VSI_INFOFRAME_STATUS 0x124 #define SOR_HDMI_VSI_INFOFRAME_HEADER 0x125 +#define SOR_HDMI2_CTRL 0x13e +#define SOR_HDMI2_CTRL_CLOCK_MODE_DIV_BY_4 (1 << 1) +#define SOR_HDMI2_CTRL_SCRAMBLE (1 << 0) + #endif diff --git a/drivers/gpu/drm/tegra/vic.c b/drivers/gpu/drm/tegra/vic.c index 18024183aa2b..f5794dd49f3b 100644 --- a/drivers/gpu/drm/tegra/vic.c +++ b/drivers/gpu/drm/tegra/vic.c @@ -115,7 +115,7 @@ static int vic_boot(struct vic *vic) } static void *vic_falcon_alloc(struct falcon *falcon, size_t size, - dma_addr_t *iova) + dma_addr_t *iova) { struct tegra_drm *tegra = falcon->data; @@ -138,13 +138,14 @@ static const struct falcon_ops vic_falcon_ops = { static int vic_init(struct host1x_client *client) { struct tegra_drm_client *drm = host1x_to_drm_client(client); + struct iommu_group *group = iommu_group_get(client->dev); struct drm_device *dev = dev_get_drvdata(client->parent); struct tegra_drm *tegra = dev->dev_private; struct vic *vic = to_vic(drm); int err; - if (tegra->domain) { - err = iommu_attach_device(tegra->domain, vic->dev); + if (group && tegra->domain) { + err = iommu_attach_group(tegra->domain, group); if (err < 0) { dev_err(vic->dev, "failed to attach to domain: %d\n", err); @@ -158,13 +159,13 @@ static int vic_init(struct host1x_client *client) vic->falcon.data = tegra; err = falcon_load_firmware(&vic->falcon); if (err < 0) - goto detach_device; + goto detach; } vic->channel = host1x_channel_request(client->dev); if (!vic->channel) { err = -ENOMEM; - goto detach_device; + goto detach; } client->syncpts[0] = host1x_syncpt_request(client, 0); @@ -183,9 +184,9 @@ free_syncpt: host1x_syncpt_free(client->syncpts[0]); free_channel: host1x_channel_put(vic->channel); -detach_device: - if (tegra->domain) - iommu_detach_device(tegra->domain, vic->dev); +detach: + if (group && tegra->domain) + iommu_detach_group(tegra->domain, group); return err; } @@ -193,6 +194,7 @@ detach_device: static int vic_exit(struct host1x_client *client) { struct tegra_drm_client *drm = host1x_to_drm_client(client); + struct iommu_group *group = iommu_group_get(client->dev); struct drm_device *dev = dev_get_drvdata(client->parent); struct tegra_drm *tegra = dev->dev_private; struct vic *vic = to_vic(drm); @@ -206,7 +208,7 @@ static int vic_exit(struct host1x_client *client) host1x_channel_put(vic->channel); if (vic->domain) { - iommu_detach_device(vic->domain, vic->dev); + iommu_detach_group(vic->domain, group); vic->domain = NULL; } |