diff options
author | Stephen Rothwell <sfr@canb.auug.org.au> | 2014-05-16 12:58:17 +1000 |
---|---|---|
committer | Stephen Rothwell <sfr@canb.auug.org.au> | 2014-05-16 12:58:17 +1000 |
commit | 6d1404aafcd709e648054ccc4b689a9e86e05640 (patch) | |
tree | 767247b17f189d11ed20d851f88e1811b0b03aaf /drivers | |
parent | 633388c7b579684d0d567e6d095ef4bd26e2210e (diff) | |
parent | f00e104dc1650e4493780b0e6f55fcbd85e2e406 (diff) |
Merge remote-tracking branch 'drm-tegra/drm/tegra/for-next'
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/gpu/drm/tegra/dc.c | 536 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dc.h | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/drm.c | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/drm.h | 54 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dsi.c | 244 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dsi.h | 10 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/gem.c | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/hdmi.c | 196 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/hdmi.h | 5 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/rgb.c | 31 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/sor.c | 192 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/sor.h | 4 |
12 files changed, 807 insertions, 472 deletions
diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index edb871d7d395..859e424e15e5 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -29,6 +29,254 @@ static inline struct tegra_plane *to_tegra_plane(struct drm_plane *plane) return container_of(plane, struct tegra_plane, base); } +static unsigned int tegra_dc_format(uint32_t format, uint32_t *swap) +{ + /* assume no swapping of fetched data */ + if (swap) + *swap = BYTE_SWAP_NOSWAP; + + switch (format) { + case DRM_FORMAT_XBGR8888: + return WIN_COLOR_DEPTH_R8G8B8A8; + + case DRM_FORMAT_XRGB8888: + return WIN_COLOR_DEPTH_B8G8R8A8; + + case DRM_FORMAT_RGB565: + return WIN_COLOR_DEPTH_B5G6R5; + + case DRM_FORMAT_UYVY: + return WIN_COLOR_DEPTH_YCbCr422; + + case DRM_FORMAT_YUYV: + if (swap) + *swap = BYTE_SWAP_SWAP2; + + return WIN_COLOR_DEPTH_YCbCr422; + + case DRM_FORMAT_YUV420: + return WIN_COLOR_DEPTH_YCbCr420P; + + case DRM_FORMAT_YUV422: + return WIN_COLOR_DEPTH_YCbCr422P; + + default: + break; + } + + WARN(1, "unsupported pixel format %u, using default\n", format); + return WIN_COLOR_DEPTH_B8G8R8A8; +} + +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; + } + + return false; +} + +static inline u32 compute_dda_inc(unsigned int in, unsigned int out, bool v, + unsigned int bpp) +{ + fixed20_12 outf = dfixed_init(out); + fixed20_12 inf = dfixed_init(in); + u32 dda_inc; + int max; + + if (v) + max = 15; + else { + switch (bpp) { + case 2: + max = 8; + break; + + default: + WARN_ON_ONCE(1); + /* fallthrough */ + case 4: + max = 4; + break; + } + } + + outf.full = max_t(u32, outf.full - dfixed_const(1), dfixed_const(1)); + inf.full -= dfixed_const(1); + + dda_inc = dfixed_div(inf, outf); + dda_inc = min_t(u32, dda_inc, dfixed_const(max)); + + return dda_inc; +} + +static inline u32 compute_initial_dda(unsigned int in) +{ + fixed20_12 inf = dfixed_init(in); + return dfixed_frac(inf); +} + +static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, + const struct tegra_dc_window *window) +{ + unsigned h_offset, v_offset, h_size, v_size, h_dda, v_dda, bpp; + unsigned long value; + bool yuv, planar; + + /* + * 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); + if (!yuv) + bpp = window->bits_per_pixel / 8; + else + bpp = planar ? 1 : 2; + + 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); + + value = V_POSITION(window->dst.y) | H_POSITION(window->dst.x); + tegra_dc_writel(dc, value, DC_WIN_POSITION); + + value = V_SIZE(window->dst.h) | H_SIZE(window->dst.w); + tegra_dc_writel(dc, value, DC_WIN_SIZE); + + h_offset = window->src.x * bpp; + v_offset = window->src.y; + h_size = window->src.w * bpp; + 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); + + /* + * For DDA computations the number of bytes per pixel for YUV planar + * modes needs to take into account all Y, U and V components. + */ + if (yuv && planar) + bpp = 2; + + h_dda = compute_dda_inc(window->src.w, window->dst.w, false, bpp); + 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); + + 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_dc_writel(dc, 0, DC_WIN_UV_BUF_STRIDE); + tegra_dc_writel(dc, 0, DC_WIN_BUF_STRIDE); + + tegra_dc_writel(dc, 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); + value = window->stride[1] << 16 | window->stride[0]; + tegra_dc_writel(dc, value, DC_WIN_LINE_STRIDE); + } else { + tegra_dc_writel(dc, 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); + + if (window->tiled) { + value = DC_WIN_BUFFER_ADDR_MODE_TILE_UV | + DC_WIN_BUFFER_ADDR_MODE_TILE; + } else { + value = DC_WIN_BUFFER_ADDR_MODE_LINEAR_UV | + DC_WIN_BUFFER_ADDR_MODE_LINEAR; + } + + tegra_dc_writel(dc, 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); + + value |= CSC_ENABLE; + } else if (window->bits_per_pixel < 24) { + value |= COLOR_EXPAND; + } + + 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; + } + + tegra_dc_writel(dc, WIN_A_UPDATE << index, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, WIN_A_ACT_REQ << index, DC_CMD_STATE_CONTROL); + + return 0; +} + static int tegra_plane_update(struct drm_plane *plane, struct drm_crtc *crtc, struct drm_framebuffer *fb, int crtc_x, int crtc_y, unsigned int crtc_w, @@ -49,7 +297,7 @@ static int tegra_plane_update(struct drm_plane *plane, struct drm_crtc *crtc, window.dst.y = crtc_y; window.dst.w = crtc_w; window.dst.h = crtc_h; - window.format = tegra_dc_format(fb->pixel_format); + window.format = tegra_dc_format(fb->pixel_format, &window.swap); window.bits_per_pixel = fb->bits_per_pixel; window.bottom_up = tegra_fb_is_bottom_up(fb); window.tiled = tegra_fb_is_tiled(fb); @@ -117,6 +365,7 @@ static const uint32_t plane_formats[] = { DRM_FORMAT_XRGB8888, DRM_FORMAT_RGB565, DRM_FORMAT_UYVY, + DRM_FORMAT_YUYV, DRM_FORMAT_YUV420, DRM_FORMAT_YUV422, }; @@ -150,9 +399,9 @@ static int tegra_dc_add_planes(struct drm_device *drm, struct tegra_dc *dc) static int tegra_dc_set_base(struct tegra_dc *dc, int x, int y, struct drm_framebuffer *fb) { - unsigned int format = tegra_dc_format(fb->pixel_format); struct tegra_bo *bo = tegra_fb_get_plane(fb, 0); unsigned int h_offset = 0, v_offset = 0; + unsigned int format, swap; unsigned long value; tegra_dc_writel(dc, WINDOW_A_SELECT, DC_CMD_DISPLAY_WINDOW_HEADER); @@ -162,7 +411,10 @@ static int tegra_dc_set_base(struct tegra_dc *dc, int x, int y, tegra_dc_writel(dc, bo->paddr + value, DC_WINBUF_START_ADDR); tegra_dc_writel(dc, fb->pitches[0], DC_WIN_LINE_STRIDE); + + format = tegra_dc_format(fb->pixel_format, &swap); tegra_dc_writel(dc, format, DC_WIN_COLOR_DEPTH); + tegra_dc_writel(dc, swap, DC_WIN_BYTE_SWAP); if (tegra_fb_is_tiled(fb)) { value = DC_WIN_BUFFER_ADDR_MODE_TILE_UV | @@ -177,13 +429,13 @@ static int tegra_dc_set_base(struct tegra_dc *dc, int x, int y, /* make sure bottom-up buffers are properly displayed */ if (tegra_fb_is_bottom_up(fb)) { value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); - value |= INVERT_V; + value |= V_DIRECTION; tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); v_offset += fb->height - 1; } else { value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); - value &= ~INVERT_V; + value &= ~V_DIRECTION; tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); } @@ -334,52 +586,11 @@ static bool tegra_crtc_mode_fixup(struct drm_crtc *crtc, return true; } -static inline u32 compute_dda_inc(unsigned int in, unsigned int out, bool v, - unsigned int bpp) -{ - fixed20_12 outf = dfixed_init(out); - fixed20_12 inf = dfixed_init(in); - u32 dda_inc; - int max; - - if (v) - max = 15; - else { - switch (bpp) { - case 2: - max = 8; - break; - - default: - WARN_ON_ONCE(1); - /* fallthrough */ - case 4: - max = 4; - break; - } - } - - outf.full = max_t(u32, outf.full - dfixed_const(1), dfixed_const(1)); - inf.full -= dfixed_const(1); - - dda_inc = dfixed_div(inf, outf); - dda_inc = min_t(u32, dda_inc, dfixed_const(max)); - - return dda_inc; -} - -static inline u32 compute_initial_dda(unsigned int in) -{ - fixed20_12 inf = dfixed_init(in); - return dfixed_frac(inf); -} - static int tegra_dc_set_timings(struct tegra_dc *dc, struct drm_display_mode *mode) { - /* TODO: For HDMI compliance, h & v ref_to_sync should be set to 1 */ - unsigned int h_ref_to_sync = 0; - unsigned int v_ref_to_sync = 0; + unsigned int h_ref_to_sync = 1; + unsigned int v_ref_to_sync = 1; unsigned long value; tegra_dc_writel(dc, 0x0, DC_DISP_DISP_TIMING_OPTIONS); @@ -406,13 +617,14 @@ static int tegra_dc_set_timings(struct tegra_dc *dc, } static int tegra_crtc_setup_clk(struct drm_crtc *crtc, - struct drm_display_mode *mode, - unsigned long *div) + struct drm_display_mode *mode) { - unsigned long pclk = mode->clock * 1000, rate; + unsigned long pclk = mode->clock * 1000; struct tegra_dc *dc = to_tegra_dc(crtc); struct tegra_output *output = NULL; struct drm_encoder *encoder; + unsigned int div; + u32 value; long err; list_for_each_entry(encoder, &crtc->dev->mode_config.encoder_list, head) @@ -425,221 +637,23 @@ static int tegra_crtc_setup_clk(struct drm_crtc *crtc, return -ENODEV; /* - * This assumes that the display controller will divide its parent - * clock by 2 to generate the pixel clock. + * This assumes that the parent clock is pll_d_out0 or pll_d2_out + * respectively, each of which divides the base pll_d by 2. */ - err = tegra_output_setup_clock(output, dc->clk, pclk * 2); + err = tegra_output_setup_clock(output, dc->clk, pclk, &div); if (err < 0) { dev_err(dc->dev, "failed to setup clock: %ld\n", err); return err; } - rate = clk_get_rate(dc->clk); - *div = (rate * 2 / pclk) - 2; - - DRM_DEBUG_KMS("rate: %lu, div: %lu\n", rate, *div); - - 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; - } - - return false; -} - -int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, - const struct tegra_dc_window *window) -{ - unsigned h_offset, v_offset, h_size, v_size, h_dda, v_dda, bpp; - unsigned long value; - bool yuv, planar; - - /* - * 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); - if (!yuv) - bpp = window->bits_per_pixel / 8; - else - bpp = planar ? 1 : 2; - - 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, 0, DC_WIN_BYTE_SWAP); - - value = V_POSITION(window->dst.y) | H_POSITION(window->dst.x); - tegra_dc_writel(dc, value, DC_WIN_POSITION); - - value = V_SIZE(window->dst.h) | H_SIZE(window->dst.w); - tegra_dc_writel(dc, value, DC_WIN_SIZE); - - h_offset = window->src.x * bpp; - v_offset = window->src.y; - h_size = window->src.w * bpp; - 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); + DRM_DEBUG_KMS("rate: %lu, div: %u\n", clk_get_rate(dc->clk), div); - /* - * For DDA computations the number of bytes per pixel for YUV planar - * modes needs to take into account all Y, U and V components. - */ - if (yuv && planar) - bpp = 2; - - h_dda = compute_dda_inc(window->src.w, window->dst.w, false, bpp); - 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); - - 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_dc_writel(dc, 0, DC_WIN_UV_BUF_STRIDE); - tegra_dc_writel(dc, 0, DC_WIN_BUF_STRIDE); - - tegra_dc_writel(dc, 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); - value = window->stride[1] << 16 | window->stride[0]; - tegra_dc_writel(dc, value, DC_WIN_LINE_STRIDE); - } else { - tegra_dc_writel(dc, 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); - - if (window->tiled) { - value = DC_WIN_BUFFER_ADDR_MODE_TILE_UV | - DC_WIN_BUFFER_ADDR_MODE_TILE; - } else { - value = DC_WIN_BUFFER_ADDR_MODE_LINEAR_UV | - DC_WIN_BUFFER_ADDR_MODE_LINEAR; - } - - tegra_dc_writel(dc, 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); - - value |= CSC_ENABLE; - } else if (window->bits_per_pixel < 24) { - value |= COLOR_EXPAND; - } - - if (window->bottom_up) - value |= INVERT_V; - - 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; - } - - tegra_dc_writel(dc, WIN_A_UPDATE << index, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, WIN_A_ACT_REQ << index, DC_CMD_STATE_CONTROL); + value = SHIFT_CLK_DIVIDER(div) | PIXEL_CLK_DIVIDER_PCD1; + tegra_dc_writel(dc, value, DC_DISP_DISP_CLOCK_CONTROL); return 0; } -unsigned int tegra_dc_format(uint32_t format) -{ - switch (format) { - case DRM_FORMAT_XBGR8888: - return WIN_COLOR_DEPTH_R8G8B8A8; - - case DRM_FORMAT_XRGB8888: - return WIN_COLOR_DEPTH_B8G8R8A8; - - case DRM_FORMAT_RGB565: - return WIN_COLOR_DEPTH_B5G6R5; - - case DRM_FORMAT_UYVY: - return WIN_COLOR_DEPTH_YCbCr422; - - case DRM_FORMAT_YUV420: - return WIN_COLOR_DEPTH_YCbCr420P; - - case DRM_FORMAT_YUV422: - return WIN_COLOR_DEPTH_YCbCr422P; - - default: - break; - } - - WARN(1, "unsupported pixel format %u, using default\n", format); - return WIN_COLOR_DEPTH_B8G8R8A8; -} - static int tegra_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, struct drm_display_mode *adjusted, @@ -648,12 +662,12 @@ static int tegra_crtc_mode_set(struct drm_crtc *crtc, struct tegra_bo *bo = tegra_fb_get_plane(crtc->primary->fb, 0); struct tegra_dc *dc = to_tegra_dc(crtc); struct tegra_dc_window window; - unsigned long div, value; + u32 value; int err; drm_vblank_pre_modeset(crtc->dev, dc->pipe); - err = tegra_crtc_setup_clk(crtc, mode, &div); + err = tegra_crtc_setup_clk(crtc, mode); if (err) { dev_err(dc->dev, "failed to setup clock for CRTC: %d\n", err); return err; @@ -669,9 +683,6 @@ static int tegra_crtc_mode_set(struct drm_crtc *crtc, tegra_dc_writel(dc, value, DC_DISP_INTERLACE_CONTROL); } - value = SHIFT_CLK_DIVIDER(div) | PIXEL_CLK_DIVIDER_PCD1; - tegra_dc_writel(dc, value, DC_DISP_DISP_CLOCK_CONTROL); - /* setup window parameters */ memset(&window, 0, sizeof(window)); window.src.x = 0; @@ -682,7 +693,8 @@ static int tegra_crtc_mode_set(struct drm_crtc *crtc, window.dst.y = 0; window.dst.w = mode->hdisplay; window.dst.h = mode->vdisplay; - window.format = tegra_dc_format(crtc->primary->fb->pixel_format); + window.format = tegra_dc_format(crtc->primary->fb->pixel_format, + &window.swap); window.bits_per_pixel = crtc->primary->fb->bits_per_pixel; window.stride[0] = crtc->primary->fb->pitches[0]; window.base[0] = bo->paddr; @@ -728,10 +740,6 @@ static void tegra_crtc_prepare(struct drm_crtc *crtc) WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY); - 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); - /* initialize timer */ value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) | WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20); diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h index c94101494826..44e31aefe899 100644 --- a/drivers/gpu/drm/tegra/dc.h +++ b/drivers/gpu/drm/tegra/dc.h @@ -312,7 +312,8 @@ #define DC_WIN_CSC_KVB 0x618 #define DC_WIN_WIN_OPTIONS 0x700 -#define INVERT_V (1 << 2) +#define H_DIRECTION (1 << 0) +#define V_DIRECTION (1 << 2) #define COLOR_EXPAND (1 << 6) #define CSC_ENABLE (1 << 18) #define WIN_ENABLE (1 << 30) diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 6f5b6e2f552e..09ee77923d67 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -666,6 +666,7 @@ static const struct of_device_id host1x_drm_subdevs[] = { { .compatible = "nvidia,tegra114-gr3d", }, { .compatible = "nvidia,tegra124-dc", }, { .compatible = "nvidia,tegra124-sor", }, + { .compatible = "nvidia,tegra124-hdmi", }, { /* sentinel */ } }; diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index 126332c3ecbb..784fd5c77441 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -80,13 +80,13 @@ host1x_to_drm_client(struct host1x_client *client) return container_of(client, struct tegra_drm_client, base); } -extern int tegra_drm_register_client(struct tegra_drm *tegra, - struct tegra_drm_client *client); -extern int tegra_drm_unregister_client(struct tegra_drm *tegra, - struct tegra_drm_client *client); +int tegra_drm_register_client(struct tegra_drm *tegra, + struct tegra_drm_client *client); +int tegra_drm_unregister_client(struct tegra_drm *tegra, + struct tegra_drm_client *client); -extern int tegra_drm_init(struct tegra_drm *tegra, struct drm_device *drm); -extern int tegra_drm_exit(struct tegra_drm *tegra); +int tegra_drm_init(struct tegra_drm *tegra, struct drm_device *drm); +int tegra_drm_exit(struct tegra_drm *tegra); struct tegra_dc_soc_info; struct tegra_output; @@ -156,6 +156,7 @@ struct tegra_dc_window { } dst; unsigned int bits_per_pixel; unsigned int format; + unsigned int swap; unsigned int stride[2]; unsigned long base[3]; bool bottom_up; @@ -163,19 +164,15 @@ struct tegra_dc_window { }; /* from dc.c */ -extern unsigned int tegra_dc_format(uint32_t format); -extern int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, - const struct tegra_dc_window *window); -extern void tegra_dc_enable_vblank(struct tegra_dc *dc); -extern void tegra_dc_disable_vblank(struct tegra_dc *dc); -extern void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, - struct drm_file *file); +void tegra_dc_enable_vblank(struct tegra_dc *dc); +void tegra_dc_disable_vblank(struct tegra_dc *dc); +void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file); struct tegra_output_ops { int (*enable)(struct tegra_output *output); int (*disable)(struct tegra_output *output); int (*setup_clock)(struct tegra_output *output, struct clk *clk, - unsigned long pclk); + unsigned long pclk, unsigned int *div); int (*check_mode)(struct tegra_output *output, struct drm_display_mode *mode, enum drm_mode_status *status); @@ -233,10 +230,11 @@ static inline int tegra_output_disable(struct tegra_output *output) } static inline int tegra_output_setup_clock(struct tegra_output *output, - struct clk *clk, unsigned long pclk) + struct clk *clk, unsigned long pclk, + unsigned int *div) { if (output && output->ops && output->ops->setup_clock) - return output->ops->setup_clock(output, clk, pclk); + return output->ops->setup_clock(output, clk, pclk, div); return output ? -ENOSYS : -EINVAL; } @@ -256,22 +254,20 @@ int drm_host1x_init(struct drm_driver *driver, struct host1x_device *device); void drm_host1x_exit(struct drm_driver *driver, struct host1x_device *device); /* from rgb.c */ -extern int tegra_dc_rgb_probe(struct tegra_dc *dc); -extern int tegra_dc_rgb_remove(struct tegra_dc *dc); -extern int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc); -extern int tegra_dc_rgb_exit(struct tegra_dc *dc); +int tegra_dc_rgb_probe(struct tegra_dc *dc); +int tegra_dc_rgb_remove(struct tegra_dc *dc); +int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc); +int tegra_dc_rgb_exit(struct tegra_dc *dc); /* from output.c */ -extern int tegra_output_probe(struct tegra_output *output); -extern int tegra_output_remove(struct tegra_output *output); -extern int tegra_output_init(struct drm_device *drm, struct tegra_output *output); -extern int tegra_output_exit(struct tegra_output *output); +int tegra_output_probe(struct tegra_output *output); +int tegra_output_remove(struct tegra_output *output); +int tegra_output_init(struct drm_device *drm, struct tegra_output *output); +int tegra_output_exit(struct tegra_output *output); /* from dpaux.c */ - struct tegra_dpaux; struct drm_dp_link; -struct drm_dp_aux; struct tegra_dpaux *tegra_dpaux_find_by_of_node(struct device_node *np); enum drm_connector_status tegra_dpaux_detect(struct tegra_dpaux *dpaux); @@ -288,10 +284,10 @@ struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer, unsigned int index); bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer); bool tegra_fb_is_tiled(struct drm_framebuffer *framebuffer); -extern int tegra_drm_fb_init(struct drm_device *drm); -extern void tegra_drm_fb_exit(struct drm_device *drm); +int tegra_drm_fb_init(struct drm_device *drm); +void tegra_drm_fb_exit(struct drm_device *drm); #ifdef CONFIG_DRM_TEGRA_FBDEV -extern void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev); +void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev); #endif extern struct platform_driver tegra_dc_driver; diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 0e599f0417c0..3838575f71c6 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -14,6 +14,8 @@ #include <linux/platform_device.h> #include <linux/reset.h> +#include <linux/regulator/consumer.h> + #include <drm/drm_mipi_dsi.h> #include <drm/drm_panel.h> @@ -43,11 +45,15 @@ struct tegra_dsi { struct drm_minor *minor; struct dentry *debugfs; + unsigned long flags; enum mipi_dsi_pixel_format format; unsigned int lanes; struct tegra_mipi_device *mipi; struct mipi_dsi_host host; + + struct regulator *vdd; + bool enabled; }; static inline struct tegra_dsi * @@ -244,8 +250,10 @@ static int tegra_dsi_debugfs_exit(struct tegra_dsi *dsi) #define PKT_LP (1 << 30) #define NUM_PKT_SEQ 12 -/* non-burst mode with sync-end */ -static const u32 pkt_seq_vnb_syne[NUM_PKT_SEQ] = { +/* + * non-burst mode with sync pulses + */ +static const u32 pkt_seq_video_non_burst_sync_pulses[NUM_PKT_SEQ] = { [ 0] = PKT_ID0(MIPI_DSI_V_SYNC_START) | PKT_LEN0(0) | PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | @@ -280,6 +288,36 @@ static const u32 pkt_seq_vnb_syne[NUM_PKT_SEQ] = { PKT_ID2(MIPI_DSI_BLANKING_PACKET) | PKT_LEN2(4), }; +/* + * non-burst mode with sync events + */ +static const u32 pkt_seq_video_non_burst_sync_events[NUM_PKT_SEQ] = { + [ 0] = PKT_ID0(MIPI_DSI_V_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 1] = 0, + [ 2] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 3] = 0, + [ 4] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 5] = 0, + [ 6] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(2) | + PKT_ID2(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN2(3), + [ 7] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(4), + [ 8] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 9] = 0, + [10] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(2) | + PKT_ID2(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN2(3), + [11] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(4), +}; + static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi) { struct mipi_dphy_timing timing; @@ -361,28 +399,70 @@ static int tegra_dsi_get_muldiv(enum mipi_dsi_pixel_format format, return 0; } +static int tegra_dsi_get_format(enum mipi_dsi_pixel_format format, + enum tegra_dsi_format *fmt) +{ + switch (format) { + case MIPI_DSI_FMT_RGB888: + *fmt = TEGRA_DSI_FORMAT_24P; + break; + + case MIPI_DSI_FMT_RGB666: + *fmt = TEGRA_DSI_FORMAT_18NP; + break; + + case MIPI_DSI_FMT_RGB666_PACKED: + *fmt = TEGRA_DSI_FORMAT_18P; + break; + + case MIPI_DSI_FMT_RGB565: + *fmt = TEGRA_DSI_FORMAT_16P; + break; + + default: + return -EINVAL; + } + + return 0; +} + static int tegra_output_dsi_enable(struct tegra_output *output) { struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct drm_display_mode *mode = &dc->base.mode; unsigned int hact, hsw, hbp, hfp, i, mul, div; struct tegra_dsi *dsi = to_dsi(output); - /* FIXME: don't hardcode this */ - const u32 *pkt_seq = pkt_seq_vnb_syne; + enum tegra_dsi_format format; unsigned long value; + const u32 *pkt_seq; int err; + if (dsi->enabled) + return 0; + + if (dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { + DRM_DEBUG_KMS("Non-burst video mode with sync pulses\n"); + pkt_seq = pkt_seq_video_non_burst_sync_pulses; + } else { + DRM_DEBUG_KMS("Non-burst video mode with sync events\n"); + pkt_seq = pkt_seq_video_non_burst_sync_events; + } + err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); if (err < 0) return err; + err = tegra_dsi_get_format(dsi->format, &format); + if (err < 0) + return err; + err = clk_enable(dsi->clk); if (err < 0) return err; reset_control_deassert(dsi->rst); - value = DSI_CONTROL_CHANNEL(0) | DSI_CONTROL_FORMAT(dsi->format) | + value = DSI_CONTROL_CHANNEL(0) | DSI_CONTROL_FORMAT(format) | DSI_CONTROL_LANES(dsi->lanes - 1) | DSI_CONTROL_SOURCE(dc->pipe); tegra_dsi_writel(dsi, value, DSI_CONTROL); @@ -454,6 +534,8 @@ static int tegra_output_dsi_enable(struct tegra_output *output) value |= DSI_POWER_CONTROL_ENABLE; tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + dsi->enabled = true; + return 0; } @@ -463,9 +545,12 @@ static int tegra_output_dsi_disable(struct tegra_output *output) struct tegra_dsi *dsi = to_dsi(output); unsigned long value; + if (!dsi->enabled) + return 0; + /* disable DSI controller */ value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); - value &= DSI_POWER_CONTROL_ENABLE; + value &= ~DSI_POWER_CONTROL_ENABLE; tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); /* @@ -492,30 +577,44 @@ static int tegra_output_dsi_disable(struct tegra_output *output) clk_disable(dsi->clk); + dsi->enabled = false; + return 0; } static int tegra_output_dsi_setup_clock(struct tegra_output *output, - struct clk *clk, unsigned long pclk) + struct clk *clk, unsigned long pclk, + unsigned int *divp) { struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct drm_display_mode *mode = &dc->base.mode; unsigned int timeout, mul, div, vrefresh; struct tegra_dsi *dsi = to_dsi(output); unsigned long bclk, plld, value; - struct clk *base; int err; err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); if (err < 0) return err; + DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", mul, div, dsi->lanes); vrefresh = drm_mode_vrefresh(mode); + DRM_DEBUG_KMS("vrefresh: %u\n", vrefresh); - pclk = mode->htotal * mode->vtotal * vrefresh; + /* compute byte clock */ bclk = (pclk * mul) / (div * dsi->lanes); - plld = DIV_ROUND_UP(bclk * 8, 1000000); - pclk = (plld * 1000000) / 2; + + /* + * Compute bit clock and round up to the next MHz. + */ + plld = DIV_ROUND_UP(bclk * 8, 1000000) * 1000000; + + /* + * We divide the frequency by two here, but we make up for that by + * setting the shift clock divider (further below) to half of the + * correct value. + */ + plld /= 2; err = clk_set_parent(clk, dsi->clk_parent); if (err < 0) { @@ -523,20 +622,26 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, return err; } - base = clk_get_parent(dsi->clk_parent); - - /* - * This assumes that the parent clock is pll_d_out0 or pll_d2_out - * respectively, each of which divides the base pll_d by 2. - */ - err = clk_set_rate(base, pclk * 2); + err = clk_set_rate(dsi->clk_parent, plld); if (err < 0) { dev_err(dsi->dev, "failed to set base clock rate to %lu Hz\n", - pclk * 2); + plld); return err; } /* + * Derive pixel clock from bit clock using the shift clock divider. + * Note that this is only half of what we would expect, but we need + * that to make up for the fact that we divided the bit clock by a + * factor of two above. + * + * It's not clear exactly why this is necessary, but the display is + * not working properly otherwise. Perhaps the PLLs cannot generate + * frequencies sufficiently high. + */ + *divp = ((8 * mul) / (div * dsi->lanes)) - 2; + + /* * XXX: Move the below somewhere else so that we don't need to have * access to the vrefresh in this function? */ @@ -612,7 +717,6 @@ static int tegra_dsi_init(struct host1x_client *client) { struct tegra_drm *tegra = dev_get_drvdata(client->parent); struct tegra_dsi *dsi = host1x_client_to_dsi(client); - unsigned long value, i; int err; dsi->output.type = TEGRA_OUTPUT_DSI; @@ -631,40 +735,12 @@ static int tegra_dsi_init(struct host1x_client *client) dev_err(dsi->dev, "debugfs setup failed: %d\n", err); } - /* - * enable high-speed mode, checksum generation, ECC generation and - * disable raw mode - */ - value = tegra_dsi_readl(dsi, DSI_HOST_CONTROL); - value |= DSI_HOST_CONTROL_ECC | DSI_HOST_CONTROL_CS | - DSI_HOST_CONTROL_HS; - value &= ~DSI_HOST_CONTROL_RAW; - tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); - - tegra_dsi_writel(dsi, 0, DSI_SOL_DELAY); - tegra_dsi_writel(dsi, 0, DSI_MAX_THRESHOLD); - - tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_CONTROL); - - for (i = 0; i < 8; i++) { - tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_DATA_0 + i); - tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_DATA_8 + i); - } - - for (i = 0; i < 12; i++) - tegra_dsi_writel(dsi, 0, DSI_PKT_SEQ_0_LO + i); - - tegra_dsi_writel(dsi, 0, DSI_DCS_CMDS); - err = tegra_dsi_pad_calibrate(dsi); if (err < 0) { dev_err(dsi->dev, "MIPI calibration failed: %d\n", err); return err; } - tegra_dsi_writel(dsi, DSI_POWER_CONTROL_ENABLE, DSI_POWER_CONTROL); - usleep_range(300, 1000); - return 0; } @@ -715,66 +791,13 @@ static int tegra_dsi_setup_clocks(struct tegra_dsi *dsi) return 0; } -static void tegra_dsi_initialize(struct tegra_dsi *dsi) -{ - unsigned int i; - - tegra_dsi_writel(dsi, 0, DSI_POWER_CONTROL); - - tegra_dsi_writel(dsi, 0, DSI_INT_ENABLE); - tegra_dsi_writel(dsi, 0, DSI_INT_STATUS); - tegra_dsi_writel(dsi, 0, DSI_INT_MASK); - - tegra_dsi_writel(dsi, 0, DSI_HOST_CONTROL); - tegra_dsi_writel(dsi, 0, DSI_CONTROL); - - tegra_dsi_writel(dsi, 0, DSI_SOL_DELAY); - tegra_dsi_writel(dsi, 0, DSI_MAX_THRESHOLD); - - tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_CONTROL); - - for (i = 0; i < 8; i++) { - tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_DATA_0 + i); - tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_DATA_8 + i); - } - - for (i = 0; i < 12; i++) - tegra_dsi_writel(dsi, 0, DSI_PKT_SEQ_0_LO + i); - - tegra_dsi_writel(dsi, 0, DSI_DCS_CMDS); - - for (i = 0; i < 4; i++) - tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_0_1 + i); - - tegra_dsi_writel(dsi, 0x00000000, DSI_PHY_TIMING_0); - tegra_dsi_writel(dsi, 0x00000000, DSI_PHY_TIMING_1); - tegra_dsi_writel(dsi, 0x000000ff, DSI_PHY_TIMING_2); - tegra_dsi_writel(dsi, 0x00000000, DSI_BTA_TIMING); - - tegra_dsi_writel(dsi, 0, DSI_TIMEOUT_0); - tegra_dsi_writel(dsi, 0, DSI_TIMEOUT_1); - tegra_dsi_writel(dsi, 0, DSI_TO_TALLY); - - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0); - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_CD); - tegra_dsi_writel(dsi, 0, DSI_PAD_CD_STATUS); - tegra_dsi_writel(dsi, 0, DSI_VIDEO_MODE_CONTROL); - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1); - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2); - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3); - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4); - - tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL); - tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_START); - tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_SIZE); -} - static int tegra_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) { struct tegra_dsi *dsi = host_to_tegra(host); struct tegra_output *output = &dsi->output; + dsi->flags = device->mode_flags; dsi->format = device->format; dsi->lanes = device->lanes; @@ -829,6 +852,7 @@ static int tegra_dsi_probe(struct platform_device *pdev) * attaches to the DSI host, the parameters will be taken from * the attached device. */ + dsi->flags = MIPI_DSI_MODE_VIDEO; dsi->format = MIPI_DSI_FMT_RGB888; dsi->lanes = 4; @@ -872,6 +896,18 @@ static int tegra_dsi_probe(struct platform_device *pdev) return err; } + dsi->vdd = devm_regulator_get(&pdev->dev, "avdd-dsi-csi"); + if (IS_ERR(dsi->vdd)) { + dev_err(&pdev->dev, "cannot get VDD supply\n"); + return PTR_ERR(dsi->vdd); + } + + err = regulator_enable(dsi->vdd); + if (err < 0) { + dev_err(&pdev->dev, "cannot enable VDD supply\n"); + return err; + } + err = tegra_dsi_setup_clocks(dsi); if (err < 0) { dev_err(&pdev->dev, "cannot setup clocks\n"); @@ -883,8 +919,6 @@ static int tegra_dsi_probe(struct platform_device *pdev) if (IS_ERR(dsi->regs)) return PTR_ERR(dsi->regs); - tegra_dsi_initialize(dsi); - dsi->mipi = tegra_mipi_request(&pdev->dev); if (IS_ERR(dsi->mipi)) return PTR_ERR(dsi->mipi); @@ -929,9 +963,11 @@ static int tegra_dsi_remove(struct platform_device *pdev) mipi_dsi_host_unregister(&dsi->host); tegra_mipi_free(dsi->mipi); + regulator_disable(dsi->vdd); clk_disable_unprepare(dsi->clk_parent); clk_disable_unprepare(dsi->clk_lp); clk_disable_unprepare(dsi->clk); + reset_control_assert(dsi->rst); err = tegra_output_remove(&dsi->output); if (err < 0) { diff --git a/drivers/gpu/drm/tegra/dsi.h b/drivers/gpu/drm/tegra/dsi.h index 1db5cc24ea91..5ce610d08d77 100644 --- a/drivers/gpu/drm/tegra/dsi.h +++ b/drivers/gpu/drm/tegra/dsi.h @@ -117,4 +117,14 @@ #define DSI_INIT_SEQ_DATA_14 0x5e #define DSI_INIT_SEQ_DATA_15 0x5f +/* + * pixel format as used in the DSI_CONTROL_FORMAT field + */ +enum tegra_dsi_format { + TEGRA_DSI_FORMAT_16P, + TEGRA_DSI_FORMAT_18NP, + TEGRA_DSI_FORMAT_18P, + TEGRA_DSI_FORMAT_24P, +}; + #endif diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index bcf9895cef9f..aa85b7b26f10 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -169,7 +169,8 @@ err: return ERR_PTR(ret); } -struct tegra_bo *tegra_bo_import(struct drm_device *drm, struct dma_buf *buf) +static struct tegra_bo *tegra_bo_import(struct drm_device *drm, + struct dma_buf *buf) { struct dma_buf_attachment *attach; struct tegra_bo *bo; diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 6928015d11a4..fec1a63d32c9 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -42,8 +42,9 @@ struct tegra_hdmi { struct device *dev; bool enabled; - struct regulator *vdd; + struct regulator *hdmi; struct regulator *pll; + struct regulator *vdd; void __iomem *regs; unsigned int irq; @@ -317,6 +318,85 @@ static const struct tmds_config tegra114_tmds_config[] = { }, }; +static const struct tmds_config tegra124_tmds_config[] = { + { /* 480p/576p / 25.2MHz/27MHz modes */ + .pclk = 27000000, + .pll0 = SOR_PLL_ICHPMP(1) | SOR_PLL_BG_V17_S(3) | + SOR_PLL_VCOCAP(0) | SOR_PLL_RESISTORSEL, + .pll1 = SOR_PLL_LOADADJ(3) | SOR_PLL_TMDS_TERMADJ(0), + .pe_current = PE_CURRENT0(PE_CURRENT_0_mA_T114) | + PE_CURRENT1(PE_CURRENT_0_mA_T114) | + PE_CURRENT2(PE_CURRENT_0_mA_T114) | + PE_CURRENT3(PE_CURRENT_0_mA_T114), + .drive_current = + DRIVE_CURRENT_LANE0_T114(DRIVE_CURRENT_10_400_mA_T114) | + DRIVE_CURRENT_LANE1_T114(DRIVE_CURRENT_10_400_mA_T114) | + DRIVE_CURRENT_LANE2_T114(DRIVE_CURRENT_10_400_mA_T114) | + DRIVE_CURRENT_LANE3_T114(DRIVE_CURRENT_10_400_mA_T114), + .peak_current = PEAK_CURRENT_LANE0(PEAK_CURRENT_0_000_mA) | + PEAK_CURRENT_LANE1(PEAK_CURRENT_0_000_mA) | + PEAK_CURRENT_LANE2(PEAK_CURRENT_0_000_mA) | + PEAK_CURRENT_LANE3(PEAK_CURRENT_0_000_mA), + }, { /* 720p / 74.25MHz modes */ + .pclk = 74250000, + .pll0 = SOR_PLL_ICHPMP(1) | SOR_PLL_BG_V17_S(3) | + SOR_PLL_VCOCAP(1) | SOR_PLL_RESISTORSEL, + .pll1 = SOR_PLL_PE_EN | SOR_PLL_LOADADJ(3) | + SOR_PLL_TMDS_TERMADJ(0), + .pe_current = PE_CURRENT0(PE_CURRENT_15_mA_T114) | + PE_CURRENT1(PE_CURRENT_15_mA_T114) | + PE_CURRENT2(PE_CURRENT_15_mA_T114) | + PE_CURRENT3(PE_CURRENT_15_mA_T114), + .drive_current = + DRIVE_CURRENT_LANE0_T114(DRIVE_CURRENT_10_400_mA_T114) | + DRIVE_CURRENT_LANE1_T114(DRIVE_CURRENT_10_400_mA_T114) | + DRIVE_CURRENT_LANE2_T114(DRIVE_CURRENT_10_400_mA_T114) | + DRIVE_CURRENT_LANE3_T114(DRIVE_CURRENT_10_400_mA_T114), + .peak_current = PEAK_CURRENT_LANE0(PEAK_CURRENT_0_000_mA) | + PEAK_CURRENT_LANE1(PEAK_CURRENT_0_000_mA) | + PEAK_CURRENT_LANE2(PEAK_CURRENT_0_000_mA) | + PEAK_CURRENT_LANE3(PEAK_CURRENT_0_000_mA), + }, { /* 1080p / 148.5MHz modes */ + .pclk = 148500000, + .pll0 = SOR_PLL_ICHPMP(1) | SOR_PLL_BG_V17_S(3) | + SOR_PLL_VCOCAP(3) | SOR_PLL_RESISTORSEL, + .pll1 = SOR_PLL_PE_EN | SOR_PLL_LOADADJ(3) | + SOR_PLL_TMDS_TERMADJ(0), + .pe_current = PE_CURRENT0(PE_CURRENT_10_mA_T114) | + PE_CURRENT1(PE_CURRENT_10_mA_T114) | + PE_CURRENT2(PE_CURRENT_10_mA_T114) | + PE_CURRENT3(PE_CURRENT_10_mA_T114), + .drive_current = + DRIVE_CURRENT_LANE0_T114(DRIVE_CURRENT_12_400_mA_T114) | + DRIVE_CURRENT_LANE1_T114(DRIVE_CURRENT_12_400_mA_T114) | + DRIVE_CURRENT_LANE2_T114(DRIVE_CURRENT_12_400_mA_T114) | + DRIVE_CURRENT_LANE3_T114(DRIVE_CURRENT_12_400_mA_T114), + .peak_current = PEAK_CURRENT_LANE0(PEAK_CURRENT_0_000_mA) | + PEAK_CURRENT_LANE1(PEAK_CURRENT_0_000_mA) | + PEAK_CURRENT_LANE2(PEAK_CURRENT_0_000_mA) | + PEAK_CURRENT_LANE3(PEAK_CURRENT_0_000_mA), + }, { /* 225/297MHz modes */ + .pclk = UINT_MAX, + .pll0 = SOR_PLL_ICHPMP(1) | SOR_PLL_BG_V17_S(3) | + SOR_PLL_VCOCAP(0xf) | SOR_PLL_RESISTORSEL, + .pll1 = SOR_PLL_LOADADJ(3) | SOR_PLL_TMDS_TERMADJ(7) + | SOR_PLL_TMDS_TERM_ENABLE, + .pe_current = PE_CURRENT0(PE_CURRENT_0_mA_T114) | + PE_CURRENT1(PE_CURRENT_0_mA_T114) | + PE_CURRENT2(PE_CURRENT_0_mA_T114) | + PE_CURRENT3(PE_CURRENT_0_mA_T114), + .drive_current = + DRIVE_CURRENT_LANE0_T114(DRIVE_CURRENT_25_200_mA_T114) | + DRIVE_CURRENT_LANE1_T114(DRIVE_CURRENT_25_200_mA_T114) | + DRIVE_CURRENT_LANE2_T114(DRIVE_CURRENT_25_200_mA_T114) | + DRIVE_CURRENT_LANE3_T114(DRIVE_CURRENT_19_200_mA_T114), + .peak_current = PEAK_CURRENT_LANE0(PEAK_CURRENT_3_000_mA) | + PEAK_CURRENT_LANE1(PEAK_CURRENT_3_000_mA) | + PEAK_CURRENT_LANE2(PEAK_CURRENT_3_000_mA) | + PEAK_CURRENT_LANE3(PEAK_CURRENT_0_800_mA), + }, +}; + static const struct tegra_hdmi_audio_config * tegra_hdmi_get_audio_config(unsigned int audio_freq, unsigned int pclk) { @@ -716,13 +796,9 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) return err; } - /* - * This assumes that the display controller will divide its parent - * clock by 2 to generate the pixel clock. - */ - err = tegra_output_setup_clock(output, hdmi->clk, pclk * 2); + err = regulator_enable(hdmi->vdd); if (err < 0) { - dev_err(hdmi->dev, "failed to setup clock: %d\n", err); + dev_err(hdmi->dev, "failed to enable VDD regulator: %d\n", err); return err; } @@ -730,7 +806,7 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) if (err < 0) return err; - err = clk_enable(hdmi->clk); + err = clk_prepare_enable(hdmi->clk); if (err < 0) { dev_err(hdmi->dev, "failed to enable clock: %d\n", err); return err; @@ -740,6 +816,17 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) usleep_range(1000, 2000); reset_control_deassert(hdmi->rst); + /* power up sequence */ + value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_PLL0); + value &= ~SOR_PLL_PDBG; + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_PLL0); + + usleep_range(10, 20); + + value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_PLL0); + value &= ~SOR_PLL_PWR; + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_PLL0); + tegra_dc_writel(dc, VSYNC_H_POSITION(1), DC_DISP_DISP_TIMING_OPTIONS); tegra_dc_writel(dc, DITHER_CONTROL_DISABLE | BASE_COLOR_SIZE888, @@ -838,9 +925,13 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_SEQ_INST(0)); tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_SEQ_INST(8)); - value = 0x1c800; + value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_CSTM); value &= ~SOR_CSTM_ROTCLK(~0); value |= SOR_CSTM_ROTCLK(2); + value |= SOR_CSTM_PLLDIV; + value &= ~SOR_CSTM_LVDS_ENABLE; + value &= ~SOR_CSTM_MODE_MASK; + value |= SOR_CSTM_MODE_TMDS; tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_CSTM); /* start SOR */ @@ -930,10 +1021,18 @@ static int tegra_output_hdmi_disable(struct tegra_output *output) * sure it's only executed when the output is attached to one. */ if (dc) { + /* + * XXX: We can't do this here because it causes HDMI to go + * into an erroneous state with the result that HDMI won't + * properly work once disabled. See also a similar symptom + * for the SOR output. + */ + /* 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); + */ value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); value &= ~DISP_CTRL_MODE_MASK; @@ -947,8 +1046,9 @@ static int tegra_output_hdmi_disable(struct tegra_output *output) tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); } + clk_disable_unprepare(hdmi->clk); reset_control_assert(hdmi->rst); - clk_disable(hdmi->clk); + regulator_disable(hdmi->vdd); regulator_disable(hdmi->pll); hdmi->enabled = false; @@ -957,10 +1057,10 @@ static int tegra_output_hdmi_disable(struct tegra_output *output) } static int tegra_output_hdmi_setup_clock(struct tegra_output *output, - struct clk *clk, unsigned long pclk) + struct clk *clk, unsigned long pclk, + unsigned int *div) { struct tegra_hdmi *hdmi = to_hdmi(output); - struct clk *base; int err; err = clk_set_parent(clk, hdmi->clk_parent); @@ -969,17 +1069,12 @@ static int tegra_output_hdmi_setup_clock(struct tegra_output *output, return err; } - base = clk_get_parent(hdmi->clk_parent); - - /* - * This assumes that the parent clock is pll_d_out0 or pll_d2_out - * respectively, each of which divides the base pll_d by 2. - */ - err = clk_set_rate(base, pclk * 2); + err = clk_set_rate(hdmi->clk_parent, pclk); if (err < 0) - dev_err(output->dev, - "failed to set base clock rate to %lu Hz\n", - pclk * 2); + dev_err(output->dev, "failed to set clock rate to %lu Hz\n", + pclk); + + *div = 0; return 0; } @@ -1017,7 +1112,7 @@ static int tegra_hdmi_show_regs(struct seq_file *s, void *data) struct tegra_hdmi *hdmi = node->info_ent->data; int err; - err = clk_enable(hdmi->clk); + err = clk_prepare_enable(hdmi->clk); if (err) return err; @@ -1186,7 +1281,7 @@ static int tegra_hdmi_show_regs(struct seq_file *s, void *data) #undef DUMP_REG - clk_disable(hdmi->clk); + clk_disable_unprepare(hdmi->clk); return 0; } @@ -1256,13 +1351,6 @@ static int tegra_hdmi_init(struct host1x_client *client) struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client); int err; - err = regulator_enable(hdmi->vdd); - if (err < 0) { - dev_err(client->dev, "failed to enable VDD regulator: %d\n", - err); - return err; - } - hdmi->output.type = TEGRA_OUTPUT_HDMI; hdmi->output.dev = client->dev; hdmi->output.ops = &hdmi_ops; @@ -1279,6 +1367,13 @@ static int tegra_hdmi_init(struct host1x_client *client) 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", + err); + return err; + } + return 0; } @@ -1287,6 +1382,8 @@ static int tegra_hdmi_exit(struct host1x_client *client) struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client); int err; + regulator_disable(hdmi->hdmi); + if (IS_ENABLED(CONFIG_DEBUG_FS)) { err = tegra_hdmi_debugfs_exit(hdmi); if (err < 0) @@ -1306,8 +1403,6 @@ static int tegra_hdmi_exit(struct host1x_client *client) return err; } - regulator_disable(hdmi->vdd); - return 0; } @@ -1340,7 +1435,16 @@ static const struct tegra_hdmi_config tegra114_hdmi_config = { .has_sor_io_peak_current = true, }; +static const struct tegra_hdmi_config tegra124_hdmi_config = { + .tmds = tegra124_tmds_config, + .num_tmds = ARRAY_SIZE(tegra124_tmds_config), + .fuse_override_offset = HDMI_NV_PDISP_SOR_PAD_CTLS0, + .fuse_override_value = 1 << 31, + .has_sor_io_peak_current = true, +}; + static const struct of_device_id tegra_hdmi_of_match[] = { + { .compatible = "nvidia,tegra124-hdmi", .data = &tegra124_hdmi_config }, { .compatible = "nvidia,tegra114-hdmi", .data = &tegra114_hdmi_config }, { .compatible = "nvidia,tegra30-hdmi", .data = &tegra30_hdmi_config }, { .compatible = "nvidia,tegra20-hdmi", .data = &tegra20_hdmi_config }, @@ -1381,28 +1485,20 @@ static int tegra_hdmi_probe(struct platform_device *pdev) return PTR_ERR(hdmi->rst); } - err = clk_prepare(hdmi->clk); - if (err < 0) - return err; - hdmi->clk_parent = devm_clk_get(&pdev->dev, "parent"); if (IS_ERR(hdmi->clk_parent)) return PTR_ERR(hdmi->clk_parent); - err = clk_prepare(hdmi->clk_parent); - if (err < 0) - return err; - err = clk_set_parent(hdmi->clk, hdmi->clk_parent); if (err < 0) { dev_err(&pdev->dev, "failed to setup clocks: %d\n", err); return err; } - hdmi->vdd = devm_regulator_get(&pdev->dev, "vdd"); - if (IS_ERR(hdmi->vdd)) { - dev_err(&pdev->dev, "failed to get VDD regulator\n"); - return PTR_ERR(hdmi->vdd); + hdmi->hdmi = devm_regulator_get(&pdev->dev, "hdmi"); + if (IS_ERR(hdmi->hdmi)) { + dev_err(&pdev->dev, "failed to get HDMI regulator\n"); + return PTR_ERR(hdmi->hdmi); } hdmi->pll = devm_regulator_get(&pdev->dev, "pll"); @@ -1411,6 +1507,12 @@ static int tegra_hdmi_probe(struct platform_device *pdev) return PTR_ERR(hdmi->pll); } + hdmi->vdd = devm_regulator_get(&pdev->dev, "vdd"); + if (IS_ERR(hdmi->vdd)) { + dev_err(&pdev->dev, "failed to get VDD regulator\n"); + return PTR_ERR(hdmi->vdd); + } + hdmi->output.dev = &pdev->dev; err = tegra_output_probe(&hdmi->output); @@ -1462,8 +1564,8 @@ static int tegra_hdmi_remove(struct platform_device *pdev) return err; } - clk_unprepare(hdmi->clk_parent); - clk_unprepare(hdmi->clk); + clk_disable_unprepare(hdmi->clk_parent); + clk_disable_unprepare(hdmi->clk); return 0; } diff --git a/drivers/gpu/drm/tegra/hdmi.h b/drivers/gpu/drm/tegra/hdmi.h index 0aebc485f7fa..919a19df4e1b 100644 --- a/drivers/gpu/drm/tegra/hdmi.h +++ b/drivers/gpu/drm/tegra/hdmi.h @@ -190,6 +190,11 @@ #define HDMI_NV_PDISP_SOR_CSTM 0x5a #define SOR_CSTM_ROTCLK(x) (((x) & 0xf) << 24) +#define SOR_CSTM_PLLDIV (1 << 21) +#define SOR_CSTM_LVDS_ENABLE (1 << 16) +#define SOR_CSTM_MODE_LVDS (0 << 12) +#define SOR_CSTM_MODE_TMDS (1 << 12) +#define SOR_CSTM_MODE_MASK (3 << 12) #define HDMI_NV_PDISP_SOR_LVDS 0x5b #define HDMI_NV_PDISP_SOR_CRCA 0x5c diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c index 0266fb40479e..d6af9be48f42 100644 --- a/drivers/gpu/drm/tegra/rgb.c +++ b/drivers/gpu/drm/tegra/rgb.c @@ -159,11 +159,38 @@ static int tegra_output_rgb_disable(struct tegra_output *output) } static int tegra_output_rgb_setup_clock(struct tegra_output *output, - struct clk *clk, unsigned long pclk) + struct clk *clk, unsigned long pclk, + unsigned int *div) { struct tegra_rgb *rgb = to_rgb(output); + int err; + + err = clk_set_parent(clk, rgb->clk_parent); + if (err < 0) { + dev_err(output->dev, "failed to set parent: %d\n", err); + return err; + } - return clk_set_parent(clk, rgb->clk_parent); + /* + * We may not want to change the frequency of the parent clock, since + * it may be a parent for other peripherals. This is due to the fact + * that on Tegra20 there's only a single clock dedicated to display + * (pll_d_out0), whereas later generations have a second one that can + * be used to independently drive a second output (pll_d2_out0). + * + * As a way to support multiple outputs on Tegra20 as well, pll_p is + * typically used as the parent clock for the display controllers. + * But this comes at a cost: pll_p is the parent of several other + * peripherals, so its frequency shouldn't change out of the blue. + * + * The best we can do at this point is to use the shift clock divider + * and hope that the desired frequency can be matched (or at least + * matched sufficiently close that the panel will still work). + */ + + *div = ((clk_get_rate(clk) * 2) / pclk) - 2; + + return 0; } static int tegra_output_rgb_check_mode(struct tegra_output *output, diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 49ef5729f435..7d66f6e53919 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -7,6 +7,7 @@ */ #include <linux/clk.h> +#include <linux/debugfs.h> #include <linux/io.h> #include <linux/platform_device.h> #include <linux/reset.h> @@ -33,7 +34,10 @@ struct tegra_sor { struct tegra_dpaux *dpaux; + struct mutex lock; bool enabled; + + struct dentry *debugfs; }; static inline struct tegra_sor * @@ -296,14 +300,16 @@ static int tegra_output_sor_enable(struct tegra_output *output) unsigned int vbe, vse, hbe, hse, vbs, hbs, i; struct tegra_sor *sor = to_sor(output); unsigned long value; - int err; + int err = 0; + + mutex_lock(&sor->lock); if (sor->enabled) - return 0; + goto unlock; err = clk_prepare_enable(sor->clk); if (err < 0) - return err; + goto unlock; reset_control_deassert(sor->rst); @@ -385,7 +391,7 @@ static int tegra_output_sor_enable(struct tegra_output *output) err = tegra_io_rail_power_on(TEGRA_IO_RAIL_LVDS); if (err < 0) { dev_err(sor->dev, "failed to power on I/O rail: %d\n", err); - return err; + goto unlock; } usleep_range(5, 100); @@ -509,21 +515,21 @@ static int tegra_output_sor_enable(struct tegra_output *output) if (err < 0) { dev_err(sor->dev, "failed to probe eDP link: %d\n", err); - return err; + goto unlock; } err = drm_dp_link_power_up(aux, &link); if (err < 0) { dev_err(sor->dev, "failed to power up eDP link: %d\n", err); - return err; + goto unlock; } err = drm_dp_link_configure(aux, &link); if (err < 0) { dev_err(sor->dev, "failed to configure eDP link: %d\n", err); - return err; + goto unlock; } rate = drm_dp_link_rate_to_bw_code(link.rate); @@ -558,7 +564,7 @@ static int tegra_output_sor_enable(struct tegra_output *output) if (err < 0) { dev_err(sor->dev, "DP fast link training failed: %d\n", err); - return err; + goto unlock; } dev_dbg(sor->dev, "fast link training succeeded\n"); @@ -567,7 +573,7 @@ static int tegra_output_sor_enable(struct tegra_output *output) err = tegra_sor_power_up(sor, 250); if (err < 0) { dev_err(sor->dev, "failed to power up SOR: %d\n", err); - return err; + goto unlock; } /* start display controller in continuous mode */ @@ -632,7 +638,7 @@ static int tegra_output_sor_enable(struct tegra_output *output) err = tegra_sor_setup_pwm(sor, 250); if (err < 0) { dev_err(sor->dev, "failed to setup PWM: %d\n", err); - return err; + goto unlock; } value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); @@ -644,18 +650,20 @@ static int tegra_output_sor_enable(struct tegra_output *output) err = tegra_sor_attach(sor); if (err < 0) { dev_err(sor->dev, "failed to attach SOR: %d\n", err); - return err; + goto unlock; } err = tegra_sor_wakeup(sor); if (err < 0) { dev_err(sor->dev, "failed to enable DC: %d\n", err); - return err; + goto unlock; } sor->enabled = true; - return 0; +unlock: + mutex_unlock(&sor->lock); + return err; } static int tegra_sor_detach(struct tegra_sor *sor) @@ -783,15 +791,17 @@ static int tegra_output_sor_disable(struct tegra_output *output) struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct tegra_sor *sor = to_sor(output); unsigned long value; - int err; + int err = 0; + + mutex_lock(&sor->lock); if (!sor->enabled) - return 0; + goto unlock; err = tegra_sor_detach(sor); if (err < 0) { dev_err(sor->dev, "failed to detach SOR: %d\n", err); - return err; + goto unlock; } tegra_sor_writel(sor, 0, SOR_STATE_1); @@ -832,21 +842,21 @@ static int tegra_output_sor_disable(struct tegra_output *output) err = tegra_sor_power_down(sor); if (err < 0) { dev_err(sor->dev, "failed to power down SOR: %d\n", err); - return err; + goto unlock; } if (sor->dpaux) { err = tegra_dpaux_disable(sor->dpaux); if (err < 0) { dev_err(sor->dev, "failed to disable DP: %d\n", err); - return err; + goto unlock; } } err = tegra_io_rail_power_off(TEGRA_IO_RAIL_LVDS); if (err < 0) { dev_err(sor->dev, "failed to power off I/O rail: %d\n", err); - return err; + goto unlock; } reset_control_assert(sor->rst); @@ -854,17 +864,20 @@ static int tegra_output_sor_disable(struct tegra_output *output) sor->enabled = false; - return 0; +unlock: + mutex_unlock(&sor->lock); + return err; } static int tegra_output_sor_setup_clock(struct tegra_output *output, - struct clk *clk, unsigned long pclk) + struct clk *clk, unsigned long pclk, + unsigned int *div) { struct tegra_sor *sor = to_sor(output); int err; /* round to next MHz */ - pclk = DIV_ROUND_UP(pclk / 2, 1000000) * 1000000; + pclk = DIV_ROUND_UP(pclk, 1000000) * 1000000; err = clk_set_parent(clk, sor->clk_parent); if (err < 0) { @@ -874,11 +887,12 @@ static int tegra_output_sor_setup_clock(struct tegra_output *output, err = clk_set_rate(sor->clk_parent, pclk); if (err < 0) { - dev_err(sor->dev, "failed to set base clock rate to %lu Hz\n", - pclk * 2); + dev_err(sor->dev, "failed to set clock rate to %lu Hz\n", pclk); return err; } + *div = 0; + return 0; } @@ -914,6 +928,120 @@ static const struct tegra_output_ops sor_ops = { .detect = tegra_output_sor_detect, }; +static int tegra_sor_crc_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +static int tegra_sor_crc_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static int tegra_sor_crc_wait(struct tegra_sor *sor, unsigned long timeout) +{ + u32 value; + + timeout = jiffies + msecs_to_jiffies(timeout); + + while (time_before(jiffies, timeout)) { + value = tegra_sor_readl(sor, SOR_CRC_A); + if (value & SOR_CRC_A_VALID) + return 0; + + usleep_range(100, 200); + } + + return -ETIMEDOUT; +} + +static ssize_t tegra_sor_crc_read(struct file *file, char __user *buffer, + size_t size, loff_t *ppos) +{ + struct tegra_sor *sor = file->private_data; + ssize_t num, err; + char buf[10]; + u32 value; + + mutex_lock(&sor->lock); + + if (!sor->enabled) { + err = -EAGAIN; + goto unlock; + } + + value = tegra_sor_readl(sor, SOR_STATE_1); + value &= ~SOR_STATE_ASY_CRC_MODE_MASK; + tegra_sor_writel(sor, value, SOR_STATE_1); + + value = tegra_sor_readl(sor, SOR_CRC_CNTRL); + value |= SOR_CRC_CNTRL_ENABLE; + tegra_sor_writel(sor, value, SOR_CRC_CNTRL); + + value = tegra_sor_readl(sor, SOR_TEST); + value &= ~SOR_TEST_CRC_POST_SERIALIZE; + tegra_sor_writel(sor, value, SOR_TEST); + + err = tegra_sor_crc_wait(sor, 100); + if (err < 0) + goto unlock; + + tegra_sor_writel(sor, SOR_CRC_A_RESET, SOR_CRC_A); + value = tegra_sor_readl(sor, SOR_CRC_B); + + num = scnprintf(buf, sizeof(buf), "%08x\n", value); + + err = simple_read_from_buffer(buffer, size, ppos, buf, num); + +unlock: + mutex_unlock(&sor->lock); + return err; +} + +static const struct file_operations tegra_sor_crc_fops = { + .owner = THIS_MODULE, + .open = tegra_sor_crc_open, + .read = tegra_sor_crc_read, + .release = tegra_sor_crc_release, +}; + +static int tegra_sor_debugfs_init(struct tegra_sor *sor, struct dentry *root) +{ + struct dentry *entry; + int err = 0; + + sor->debugfs = debugfs_create_dir("sor", root); + if (!sor->debugfs) + return -ENOMEM; + + entry = debugfs_create_file("crc", 0644, sor->debugfs, sor, + &tegra_sor_crc_fops); + if (!entry) { + dev_err(sor->dev, + "cannot create /sys/kernel/debug/dri/%s/sor/crc\n", + root->d_name.name); + err = -ENOMEM; + goto remove; + } + + return err; + +remove: + debugfs_remove(sor->debugfs); + sor->debugfs = NULL; + return err; +} + +static int tegra_sor_debugfs_exit(struct tegra_sor *sor) +{ + debugfs_remove(sor->debugfs); + sor->debugfs = NULL; + + return 0; +} + static int tegra_sor_init(struct host1x_client *client) { struct tegra_drm *tegra = dev_get_drvdata(client->parent); @@ -934,6 +1062,14 @@ static int tegra_sor_init(struct host1x_client *client) return err; } + if (IS_ENABLED(CONFIG_DEBUG_FS)) { + struct dentry *root = tegra->drm->primary->debugfs_root; + + err = tegra_sor_debugfs_init(sor, root); + if (err < 0) + dev_err(sor->dev, "debugfs setup failed: %d\n", err); + } + if (sor->dpaux) { err = tegra_dpaux_attach(sor->dpaux, &sor->output); if (err < 0) { @@ -964,6 +1100,12 @@ static int tegra_sor_exit(struct host1x_client *client) } } + if (IS_ENABLED(CONFIG_DEBUG_FS)) { + err = tegra_sor_debugfs_exit(sor); + if (err < 0) + dev_err(sor->dev, "debugfs cleanup failed: %d\n", err); + } + err = tegra_output_exit(&sor->output); if (err < 0) { dev_err(sor->dev, "output cleanup failed: %d\n", err); @@ -1045,6 +1187,8 @@ static int tegra_sor_probe(struct platform_device *pdev) sor->client.ops = &sor_client_ops; sor->client.dev = &pdev->dev; + mutex_init(&sor->lock); + err = host1x_client_register(&sor->client); if (err < 0) { dev_err(&pdev->dev, "failed to register host1x client: %d\n", diff --git a/drivers/gpu/drm/tegra/sor.h b/drivers/gpu/drm/tegra/sor.h index f4156d54cd05..a5f8853fedb5 100644 --- a/drivers/gpu/drm/tegra/sor.h +++ b/drivers/gpu/drm/tegra/sor.h @@ -47,6 +47,7 @@ #define SOR_HEAD_STATE_4(x) (0x0d + (x)) #define SOR_HEAD_STATE_5(x) (0x0f + (x)) #define SOR_CRC_CNTRL 0x11 +#define SOR_CRC_CNTRL_ENABLE (1 << 0) #define SOR_DP_DEBUG_MVID 0x12 #define SOR_CLK_CNTRL 0x13 @@ -69,6 +70,7 @@ #define SOR_PWR_NORMAL_STATE_PU (1 << 0) #define SOR_TEST 0x16 +#define SOR_TEST_CRC_POST_SERIALIZE (1 << 23) #define SOR_TEST_ATTACHED (1 << 10) #define SOR_TEST_HEAD_MODE_MASK (3 << 8) #define SOR_TEST_HEAD_MODE_AWAKE (2 << 8) @@ -115,6 +117,8 @@ #define SOR_LVDS 0x1c #define SOR_CRC_A 0x1d +#define SOR_CRC_A_VALID (1 << 0) +#define SOR_CRC_A_RESET (1 << 0) #define SOR_CRC_B 0x1e #define SOR_BLANK 0x1f #define SOR_SEQ_CTL 0x20 |