diff options
Diffstat (limited to 'drivers/gpu/drm/tegra')
-rw-r--r-- | drivers/gpu/drm/tegra/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/bus.c | 75 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dc.c | 657 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dc.h | 33 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dpaux.c | 35 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/drm.c | 36 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/drm.h | 58 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dsi.c | 250 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dsi.h | 10 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/fb.c | 7 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/gem.c | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/gr2d.c | 8 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/gr3d.c | 8 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/hdmi.c | 202 | ||||
-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 | 478 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/sor.h | 4 |
18 files changed, 1277 insertions, 624 deletions
diff --git a/drivers/gpu/drm/tegra/Makefile b/drivers/gpu/drm/tegra/Makefile index d43f21bb4596..2c66a8db9da4 100644 --- a/drivers/gpu/drm/tegra/Makefile +++ b/drivers/gpu/drm/tegra/Makefile @@ -1,7 +1,6 @@ ccflags-$(CONFIG_DRM_TEGRA_DEBUG) += -DDEBUG tegra-drm-y := \ - bus.o \ drm.o \ gem.o \ fb.o \ diff --git a/drivers/gpu/drm/tegra/bus.c b/drivers/gpu/drm/tegra/bus.c deleted file mode 100644 index 71cef5c13dc8..000000000000 --- a/drivers/gpu/drm/tegra/bus.c +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2013 NVIDIA Corporation - * - * 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.h" - -static int drm_host1x_set_busid(struct drm_device *dev, - struct drm_master *master) -{ - const char *device = dev_name(dev->dev); - const char *driver = dev->driver->name; - const char *bus = dev->dev->bus->name; - int length; - - master->unique_len = strlen(bus) + 1 + strlen(device); - master->unique_size = master->unique_len; - - master->unique = kmalloc(master->unique_len + 1, GFP_KERNEL); - if (!master->unique) - return -ENOMEM; - - snprintf(master->unique, master->unique_len + 1, "%s:%s", bus, device); - - length = strlen(driver) + 1 + master->unique_len; - - dev->devname = kmalloc(length + 1, GFP_KERNEL); - if (!dev->devname) - return -ENOMEM; - - snprintf(dev->devname, length + 1, "%s@%s", driver, master->unique); - - return 0; -} - -static struct drm_bus drm_host1x_bus = { - .bus_type = DRIVER_BUS_HOST1X, - .set_busid = drm_host1x_set_busid, -}; - -int drm_host1x_init(struct drm_driver *driver, struct host1x_device *device) -{ - struct drm_device *drm; - int ret; - - driver->bus = &drm_host1x_bus; - - drm = drm_dev_alloc(driver, &device->dev); - if (!drm) - return -ENOMEM; - - ret = drm_dev_register(drm, 0); - if (ret) - goto err_free; - - DRM_INFO("Initialized %s %d.%d.%d %s on minor %d\n", driver->name, - driver->major, driver->minor, driver->patchlevel, - driver->date, drm->primary->index); - - return 0; - -err_free: - drm_dev_unref(drm); - return ret; -} - -void drm_host1x_exit(struct drm_driver *driver, struct host1x_device *device) -{ - struct tegra_drm *tegra = dev_get_drvdata(&device->dev); - - drm_put_dev(tegra->drm); -} diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index edb871d7d395..ef40381f3909 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -17,6 +17,7 @@ struct tegra_dc_soc_info { bool supports_interlacing; + bool supports_cursor; }; struct tegra_plane { @@ -29,6 +30,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 +298,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 +366,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 +400,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 +412,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 +430,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); } @@ -225,6 +478,109 @@ void tegra_dc_disable_vblank(struct tegra_dc *dc) spin_unlock_irqrestore(&dc->lock, flags); } +static int tegra_dc_cursor_set2(struct drm_crtc *crtc, struct drm_file *file, + uint32_t handle, uint32_t width, + uint32_t height, int32_t hot_x, int32_t hot_y) +{ + unsigned long value = CURSOR_CLIP_DISPLAY; + struct tegra_dc *dc = to_tegra_dc(crtc); + struct drm_gem_object *gem; + struct tegra_bo *bo = NULL; + + if (!dc->soc->supports_cursor) + return -ENXIO; + + if (width != height) + return -EINVAL; + + switch (width) { + case 32: + value |= CURSOR_SIZE_32x32; + break; + + case 64: + value |= CURSOR_SIZE_64x64; + break; + + case 128: + value |= CURSOR_SIZE_128x128; + + case 256: + value |= CURSOR_SIZE_256x256; + break; + + default: + return -EINVAL; + } + + if (handle) { + gem = drm_gem_object_lookup(crtc->dev, file, handle); + if (!gem) + return -ENOENT; + + bo = to_tegra_bo(gem); + } + + if (bo) { + unsigned long addr = (bo->paddr & 0xfffffc00) >> 10; +#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT + unsigned long high = (bo->paddr & 0xfffffffc) >> 32; +#endif + + tegra_dc_writel(dc, value | addr, DC_DISP_CURSOR_START_ADDR); + +#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT + tegra_dc_writel(dc, high, DC_DISP_CURSOR_START_ADDR_HI); +#endif + + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); + value |= CURSOR_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); + + value = tegra_dc_readl(dc, DC_DISP_BLEND_CURSOR_CONTROL); + value &= ~CURSOR_DST_BLEND_MASK; + value &= ~CURSOR_SRC_BLEND_MASK; + value |= CURSOR_MODE_NORMAL; + value |= CURSOR_DST_BLEND_NEG_K1_TIMES_SRC; + value |= CURSOR_SRC_BLEND_K1_TIMES_SRC; + value |= CURSOR_ALPHA; + tegra_dc_writel(dc, value, DC_DISP_BLEND_CURSOR_CONTROL); + } else { + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); + value &= ~CURSOR_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); + } + + tegra_dc_writel(dc, CURSOR_ACT_REQ << 8, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL); + + tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + + return 0; +} + +static int tegra_dc_cursor_move(struct drm_crtc *crtc, int x, int y) +{ + struct tegra_dc *dc = to_tegra_dc(crtc); + unsigned long value; + + if (!dc->soc->supports_cursor) + return -ENXIO; + + value = ((y & 0x3fff) << 16) | (x & 0x3fff); + tegra_dc_writel(dc, value, DC_DISP_CURSOR_POSITION); + + tegra_dc_writel(dc, CURSOR_ACT_REQ << 8, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL); + + /* XXX: only required on generations earlier than Tegra124? */ + tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + + return 0; +} + static void tegra_dc_finish_page_flip(struct tegra_dc *dc) { struct drm_device *drm = dc->base.dev; @@ -301,6 +657,8 @@ static void tegra_dc_destroy(struct drm_crtc *crtc) } static const struct drm_crtc_funcs tegra_crtc_funcs = { + .cursor_set2 = tegra_dc_cursor_set2, + .cursor_move = tegra_dc_cursor_move, .page_flip = tegra_dc_page_flip, .set_config = drm_crtc_helper_set_config, .destroy = tegra_dc_destroy, @@ -334,52 +692,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 +723,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 +743,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); - - /* - * 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; + DRM_DEBUG_KMS("rate: %lu, div: %u\n", clk_get_rate(dc->clk), div); - 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 +768,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 +789,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 +799,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 +846,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); @@ -991,6 +1105,8 @@ static int tegra_dc_show_regs(struct seq_file *s, void *data) 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); @@ -1096,26 +1212,26 @@ static int tegra_dc_debugfs_exit(struct tegra_dc *dc) static int tegra_dc_init(struct host1x_client *client) { - struct tegra_drm *tegra = dev_get_drvdata(client->parent); + struct drm_device *drm = dev_get_drvdata(client->parent); struct tegra_dc *dc = host1x_client_to_dc(client); int err; - drm_crtc_init(tegra->drm, &dc->base, &tegra_crtc_funcs); + drm_crtc_init(drm, &dc->base, &tegra_crtc_funcs); drm_mode_crtc_set_gamma_size(&dc->base, 256); drm_crtc_helper_add(&dc->base, &tegra_crtc_helper_funcs); - err = tegra_dc_rgb_init(tegra->drm, dc); + err = tegra_dc_rgb_init(drm, dc); if (err < 0 && err != -ENODEV) { dev_err(dc->dev, "failed to initialize RGB output: %d\n", err); return err; } - err = tegra_dc_add_planes(tegra->drm, dc); + err = tegra_dc_add_planes(drm, dc); if (err < 0) return err; if (IS_ENABLED(CONFIG_DEBUG_FS)) { - err = tegra_dc_debugfs_init(dc, tegra->drm->primary); + err = tegra_dc_debugfs_init(dc, drm->primary); if (err < 0) dev_err(dc->dev, "debugfs setup failed: %d\n", err); } @@ -1160,14 +1276,17 @@ static const struct host1x_client_ops dc_client_ops = { static const struct tegra_dc_soc_info tegra20_dc_soc_info = { .supports_interlacing = false, + .supports_cursor = false, }; static const struct tegra_dc_soc_info tegra30_dc_soc_info = { .supports_interlacing = false, + .supports_cursor = false, }; static const struct tegra_dc_soc_info tegra124_dc_soc_info = { .supports_interlacing = true, + .supports_cursor = true, }; static const struct of_device_id tegra_dc_of_match[] = { diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h index c94101494826..78c5feff95d2 100644 --- a/drivers/gpu/drm/tegra/dc.h +++ b/drivers/gpu/drm/tegra/dc.h @@ -67,10 +67,12 @@ #define WIN_A_ACT_REQ (1 << 1) #define WIN_B_ACT_REQ (1 << 2) #define WIN_C_ACT_REQ (1 << 3) +#define CURSOR_ACT_REQ (1 << 7) #define GENERAL_UPDATE (1 << 8) #define WIN_A_UPDATE (1 << 9) #define WIN_B_UPDATE (1 << 10) #define WIN_C_UPDATE (1 << 11) +#define CURSOR_UPDATE (1 << 15) #define NC_HOST_TRIG (1 << 24) #define DC_CMD_DISPLAY_WINDOW_HEADER 0x042 @@ -116,9 +118,10 @@ #define DC_DISP_DISP_SIGNAL_OPTIONS1 0x401 #define DC_DISP_DISP_WIN_OPTIONS 0x402 -#define HDMI_ENABLE (1 << 30) -#define DSI_ENABLE (1 << 29) -#define SOR_ENABLE (1 << 25) +#define HDMI_ENABLE (1 << 30) +#define DSI_ENABLE (1 << 29) +#define SOR_ENABLE (1 << 25) +#define CURSOR_ENABLE (1 << 16) #define DC_DISP_DISP_MEM_HIGH_PRIORITY 0x403 #define CURSOR_THRESHOLD(x) (((x) & 0x03) << 24) @@ -266,6 +269,14 @@ #define DC_DISP_CURSOR_BACKGROUND 0x43d #define DC_DISP_CURSOR_START_ADDR 0x43e +#define CURSOR_CLIP_DISPLAY (0 << 28) +#define CURSOR_CLIP_WIN_A (1 << 28) +#define CURSOR_CLIP_WIN_B (2 << 28) +#define CURSOR_CLIP_WIN_C (3 << 28) +#define CURSOR_SIZE_32x32 (0 << 24) +#define CURSOR_SIZE_64x64 (1 << 24) +#define CURSOR_SIZE_128x128 (2 << 24) +#define CURSOR_SIZE_256x256 (3 << 24) #define DC_DISP_CURSOR_START_ADDR_NS 0x43f #define DC_DISP_CURSOR_POSITION 0x440 @@ -302,6 +313,19 @@ #define INTERLACE_START (1 << 1) #define INTERLACE_ENABLE (1 << 0) +#define DC_DISP_CURSOR_START_ADDR_HI 0x4ec +#define DC_DISP_BLEND_CURSOR_CONTROL 0x4f1 +#define CURSOR_MODE_LEGACY (0 << 24) +#define CURSOR_MODE_NORMAL (1 << 24) +#define CURSOR_DST_BLEND_ZERO (0 << 16) +#define CURSOR_DST_BLEND_K1 (1 << 16) +#define CURSOR_DST_BLEND_NEG_K1_TIMES_SRC (2 << 16) +#define CURSOR_DST_BLEND_MASK (3 << 16) +#define CURSOR_SRC_BLEND_K1 (0 << 8) +#define CURSOR_SRC_BLEND_K1_TIMES_SRC (1 << 8) +#define CURSOR_SRC_BLEND_MASK (3 << 8) +#define CURSOR_ALPHA 0xff + #define DC_WIN_CSC_YOF 0x611 #define DC_WIN_CSC_KYRGB 0x612 #define DC_WIN_CSC_KUR 0x613 @@ -312,7 +336,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/dpaux.c b/drivers/gpu/drm/tegra/dpaux.c index 005c19bd92df..3f132e356e9c 100644 --- a/drivers/gpu/drm/tegra/dpaux.c +++ b/drivers/gpu/drm/tegra/dpaux.c @@ -15,6 +15,7 @@ #include <linux/platform_device.h> #include <linux/reset.h> #include <linux/regulator/consumer.h> +#include <linux/workqueue.h> #include <drm/drm_dp_helper.h> #include <drm/drm_panel.h> @@ -41,6 +42,7 @@ struct tegra_dpaux { struct regulator *vdd; struct completion complete; + struct work_struct work; struct list_head list; }; @@ -49,6 +51,11 @@ static inline struct tegra_dpaux *to_dpaux(struct drm_dp_aux *aux) return container_of(aux, struct tegra_dpaux, aux); } +static inline struct tegra_dpaux *work_to_dpaux(struct work_struct *work) +{ + return container_of(work, struct tegra_dpaux, work); +} + static inline unsigned long tegra_dpaux_readl(struct tegra_dpaux *dpaux, unsigned long offset) { @@ -231,6 +238,14 @@ static ssize_t tegra_dpaux_transfer(struct drm_dp_aux *aux, return ret; } +static void tegra_dpaux_hotplug(struct work_struct *work) +{ + struct tegra_dpaux *dpaux = work_to_dpaux(work); + + if (dpaux->output) + drm_helper_hpd_irq_event(dpaux->output->connector.dev); +} + static irqreturn_t tegra_dpaux_irq(int irq, void *data) { struct tegra_dpaux *dpaux = data; @@ -241,16 +256,8 @@ static irqreturn_t tegra_dpaux_irq(int irq, void *data) value = tegra_dpaux_readl(dpaux, DPAUX_INTR_AUX); tegra_dpaux_writel(dpaux, value, DPAUX_INTR_AUX); - if (value & DPAUX_INTR_PLUG_EVENT) { - if (dpaux->output) { - drm_helper_hpd_irq_event(dpaux->output->connector.dev); - } - } - - if (value & DPAUX_INTR_UNPLUG_EVENT) { - if (dpaux->output) - drm_helper_hpd_irq_event(dpaux->output->connector.dev); - } + if (value & (DPAUX_INTR_PLUG_EVENT | DPAUX_INTR_UNPLUG_EVENT)) + schedule_work(&dpaux->work); if (value & DPAUX_INTR_IRQ_EVENT) { /* TODO: handle this */ @@ -273,6 +280,7 @@ static int tegra_dpaux_probe(struct platform_device *pdev) if (!dpaux) return -ENOMEM; + INIT_WORK(&dpaux->work, tegra_dpaux_hotplug); init_completion(&dpaux->complete); INIT_LIST_HEAD(&dpaux->list); dpaux->dev = &pdev->dev; @@ -332,7 +340,7 @@ static int tegra_dpaux_probe(struct platform_device *pdev) dpaux->aux.transfer = tegra_dpaux_transfer; dpaux->aux.dev = &pdev->dev; - err = drm_dp_aux_register_i2c_bus(&dpaux->aux); + err = drm_dp_aux_register(&dpaux->aux); if (err < 0) return err; @@ -355,12 +363,14 @@ static int tegra_dpaux_remove(struct platform_device *pdev) { struct tegra_dpaux *dpaux = platform_get_drvdata(pdev); - drm_dp_aux_unregister_i2c_bus(&dpaux->aux); + drm_dp_aux_unregister(&dpaux->aux); mutex_lock(&dpaux_lock); list_del(&dpaux->list); mutex_unlock(&dpaux_lock); + cancel_work_sync(&dpaux->work); + clk_disable_unprepare(dpaux->clk_parent); reset_control_assert(dpaux->rst); clk_disable_unprepare(dpaux->clk); @@ -404,6 +414,7 @@ int tegra_dpaux_attach(struct tegra_dpaux *dpaux, struct tegra_output *output) unsigned long timeout; int err; + output->connector.polled = DRM_CONNECTOR_POLL_HPD; dpaux->output = output; err = regulator_enable(dpaux->vdd); diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 6f5b6e2f552e..3396f9f6a9f7 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -33,7 +33,6 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) if (!tegra) return -ENOMEM; - dev_set_drvdata(drm->dev, tegra); mutex_init(&tegra->clients_lock); INIT_LIST_HEAD(&tegra->clients); drm->dev_private = tegra; @@ -640,14 +639,40 @@ int tegra_drm_unregister_client(struct tegra_drm *tegra, return 0; } -static int host1x_drm_probe(struct host1x_device *device) +static int host1x_drm_probe(struct host1x_device *dev) { - return drm_host1x_init(&tegra_drm_driver, device); + struct drm_driver *driver = &tegra_drm_driver; + struct drm_device *drm; + int err; + + drm = drm_dev_alloc(driver, &dev->dev); + if (!drm) + return -ENOMEM; + + drm_dev_set_unique(drm, dev_name(&dev->dev)); + dev_set_drvdata(&dev->dev, drm); + + err = drm_dev_register(drm, 0); + if (err < 0) + goto unref; + + DRM_INFO("Initialized %s %d.%d.%d %s on minor %d\n", driver->name, + driver->major, driver->minor, driver->patchlevel, + driver->date, drm->primary->index); + + return 0; + +unref: + drm_dev_unref(drm); + return err; } -static int host1x_drm_remove(struct host1x_device *device) +static int host1x_drm_remove(struct host1x_device *dev) { - drm_host1x_exit(&tegra_drm_driver, device); + struct drm_device *drm = dev_get_drvdata(&dev->dev); + + drm_dev_unregister(drm); + drm_dev_unref(drm); return 0; } @@ -666,6 +691,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..6b8fe9d86ed4 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; } @@ -251,27 +249,21 @@ static inline int tegra_output_check_mode(struct tegra_output *output, return output ? -ENOSYS : -EINVAL; } -/* from bus.c */ -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 +280,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..bd56f2affa78 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? */ @@ -610,61 +715,32 @@ static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi) static int tegra_dsi_init(struct host1x_client *client) { - struct tegra_drm *tegra = dev_get_drvdata(client->parent); + struct drm_device *drm = 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; dsi->output.dev = client->dev; dsi->output.ops = &dsi_ops; - err = tegra_output_init(tegra->drm, &dsi->output); + err = tegra_output_init(drm, &dsi->output); if (err < 0) { dev_err(client->dev, "output setup failed: %d\n", err); return err; } if (IS_ENABLED(CONFIG_DEBUG_FS)) { - err = tegra_dsi_debugfs_init(dsi, tegra->drm->primary); + err = tegra_dsi_debugfs_init(dsi, drm->primary); if (err < 0) 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/fb.c b/drivers/gpu/drm/tegra/fb.c index f7fca09d4921..9798a7080322 100644 --- a/drivers/gpu/drm/tegra/fb.c +++ b/drivers/gpu/drm/tegra/fb.c @@ -346,11 +346,8 @@ static void tegra_fbdev_free(struct tegra_fbdev *fbdev) void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev) { - if (fbdev) { - drm_modeset_lock_all(fbdev->base.dev); - drm_fb_helper_restore_fbdev_mode(&fbdev->base); - drm_modeset_unlock_all(fbdev->base.dev); - } + if (fbdev) + drm_fb_helper_restore_fbdev_mode_unlocked(&fbdev->base); } static void tegra_fb_output_poll_changed(struct drm_device *drm) 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/gr2d.c b/drivers/gpu/drm/tegra/gr2d.c index 2c7ca748edf5..7c53941f2a9e 100644 --- a/drivers/gpu/drm/tegra/gr2d.c +++ b/drivers/gpu/drm/tegra/gr2d.c @@ -28,7 +28,7 @@ static inline struct gr2d *to_gr2d(struct tegra_drm_client *client) static int gr2d_init(struct host1x_client *client) { struct tegra_drm_client *drm = host1x_to_drm_client(client); - struct tegra_drm *tegra = dev_get_drvdata(client->parent); + struct drm_device *dev = dev_get_drvdata(client->parent); unsigned long flags = HOST1X_SYNCPT_HAS_BASE; struct gr2d *gr2d = to_gr2d(drm); @@ -42,17 +42,17 @@ static int gr2d_init(struct host1x_client *client) return -ENOMEM; } - return tegra_drm_register_client(tegra, drm); + return tegra_drm_register_client(dev->dev_private, drm); } static int gr2d_exit(struct host1x_client *client) { struct tegra_drm_client *drm = host1x_to_drm_client(client); - struct tegra_drm *tegra = dev_get_drvdata(client->parent); + struct drm_device *dev = dev_get_drvdata(client->parent); struct gr2d *gr2d = to_gr2d(drm); int err; - err = tegra_drm_unregister_client(tegra, drm); + err = tegra_drm_unregister_client(dev->dev_private, drm); if (err < 0) return err; diff --git a/drivers/gpu/drm/tegra/gr3d.c b/drivers/gpu/drm/tegra/gr3d.c index 0cbb24b1ae04..30f5ba9bd6d0 100644 --- a/drivers/gpu/drm/tegra/gr3d.c +++ b/drivers/gpu/drm/tegra/gr3d.c @@ -37,7 +37,7 @@ static inline struct gr3d *to_gr3d(struct tegra_drm_client *client) static int gr3d_init(struct host1x_client *client) { struct tegra_drm_client *drm = host1x_to_drm_client(client); - struct tegra_drm *tegra = dev_get_drvdata(client->parent); + struct drm_device *dev = dev_get_drvdata(client->parent); unsigned long flags = HOST1X_SYNCPT_HAS_BASE; struct gr3d *gr3d = to_gr3d(drm); @@ -51,17 +51,17 @@ static int gr3d_init(struct host1x_client *client) return -ENOMEM; } - return tegra_drm_register_client(tegra, drm); + return tegra_drm_register_client(dev->dev_private, drm); } static int gr3d_exit(struct host1x_client *client) { struct tegra_drm_client *drm = host1x_to_drm_client(client); - struct tegra_drm *tegra = dev_get_drvdata(client->parent); + struct drm_device *dev = dev_get_drvdata(client->parent); struct gr3d *gr3d = to_gr3d(drm); int err; - err = tegra_drm_unregister_client(tegra, drm); + err = tegra_drm_unregister_client(dev->dev_private, drm); if (err < 0) return err; diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 6928015d11a4..ba067bb767e3 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; } @@ -1252,33 +1347,33 @@ static int tegra_hdmi_debugfs_exit(struct tegra_hdmi *hdmi) static int tegra_hdmi_init(struct host1x_client *client) { - struct tegra_drm *tegra = dev_get_drvdata(client->parent); + struct drm_device *drm = dev_get_drvdata(client->parent); 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; - err = tegra_output_init(tegra->drm, &hdmi->output); + err = tegra_output_init(drm, &hdmi->output); if (err < 0) { dev_err(client->dev, "output setup failed: %d\n", err); return err; } if (IS_ENABLED(CONFIG_DEBUG_FS)) { - err = tegra_hdmi_debugfs_init(hdmi, tegra->drm->primary); + 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", + 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..27c979b50111 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,23 @@ struct tegra_sor { struct tegra_dpaux *dpaux; + struct mutex lock; bool enabled; + + struct dentry *debugfs; +}; + +struct tegra_sor_config { + u32 bits_per_pixel; + + u32 active_polarity; + u32 active_count; + u32 tu_size; + u32 active_frac; + u32 watermark; + + u32 hblank_symbols; + u32 vblank_symbols; }; static inline struct tegra_sor * @@ -289,34 +306,232 @@ static int tegra_sor_power_up(struct tegra_sor *sor, unsigned long timeout) return -ETIMEDOUT; } +struct tegra_sor_params { + /* number of link clocks per line */ + unsigned int num_clocks; + /* ratio between input and output */ + u64 ratio; + /* precision factor */ + u64 precision; + + unsigned int active_polarity; + unsigned int active_count; + unsigned int active_frac; + unsigned int tu_size; + unsigned int error; +}; + +static int tegra_sor_compute_params(struct tegra_sor *sor, + struct tegra_sor_params *params, + unsigned int tu_size) +{ + u64 active_sym, active_count, frac, approx; + u32 active_polarity, active_frac = 0; + const u64 f = params->precision; + s64 error; + + active_sym = params->ratio * tu_size; + active_count = div_u64(active_sym, f) * f; + frac = active_sym - active_count; + + /* fraction < 0.5 */ + if (frac >= (f / 2)) { + active_polarity = 1; + frac = f - frac; + } else { + active_polarity = 0; + } + + if (frac != 0) { + frac = div_u64(f * f, frac); /* 1/fraction */ + if (frac <= (15 * f)) { + active_frac = div_u64(frac, f); + + /* round up */ + if (active_polarity) + active_frac++; + } else { + active_frac = active_polarity ? 1 : 15; + } + } + + if (active_frac == 1) + active_polarity = 0; + + if (active_polarity == 1) { + if (active_frac) { + approx = active_count + (active_frac * (f - 1)) * f; + approx = div_u64(approx, active_frac * f); + } else { + approx = active_count + f; + } + } else { + if (active_frac) + approx = active_count + div_u64(f, active_frac); + else + approx = active_count; + } + + error = div_s64(active_sym - approx, tu_size); + error *= params->num_clocks; + + if (error <= 0 && abs64(error) < params->error) { + params->active_count = div_u64(active_count, f); + params->active_polarity = active_polarity; + params->active_frac = active_frac; + params->error = abs64(error); + params->tu_size = tu_size; + + if (error == 0) + return true; + } + + return false; +} + +static int tegra_sor_calc_config(struct tegra_sor *sor, + struct drm_display_mode *mode, + struct tegra_sor_config *config, + struct drm_dp_link *link) +{ + const u64 f = 100000, link_rate = link->rate * 1000; + const u64 pclk = mode->clock * 1000; + u64 input, output, watermark, num; + struct tegra_sor_params params; + u32 num_syms_per_line; + unsigned int i; + + if (!link_rate || !link->num_lanes || !pclk || !config->bits_per_pixel) + return -EINVAL; + + output = link_rate * 8 * link->num_lanes; + input = pclk * config->bits_per_pixel; + + if (input >= output) + return -ERANGE; + + memset(¶ms, 0, sizeof(params)); + params.ratio = div64_u64(input * f, output); + params.num_clocks = div_u64(link_rate * mode->hdisplay, pclk); + params.precision = f; + params.error = 64 * f; + params.tu_size = 64; + + for (i = params.tu_size; i >= 32; i--) + if (tegra_sor_compute_params(sor, ¶ms, i)) + break; + + if (params.active_frac == 0) { + config->active_polarity = 0; + config->active_count = params.active_count; + + if (!params.active_polarity) + config->active_count--; + + config->tu_size = params.tu_size; + config->active_frac = 1; + } else { + config->active_polarity = params.active_polarity; + config->active_count = params.active_count; + config->active_frac = params.active_frac; + config->tu_size = params.tu_size; + } + + dev_dbg(sor->dev, + "polarity: %d active count: %d tu size: %d active frac: %d\n", + config->active_polarity, config->active_count, + config->tu_size, config->active_frac); + + watermark = params.ratio * config->tu_size * (f - params.ratio); + watermark = div_u64(watermark, f); + + watermark = div_u64(watermark + params.error, f); + config->watermark = watermark + (config->bits_per_pixel / 8) + 2; + num_syms_per_line = (mode->hdisplay * config->bits_per_pixel) * + (link->num_lanes * 8); + + if (config->watermark > 30) { + config->watermark = 30; + dev_err(sor->dev, + "unable to compute TU size, forcing watermark to %u\n", + config->watermark); + } else if (config->watermark > num_syms_per_line) { + config->watermark = num_syms_per_line; + dev_err(sor->dev, "watermark too high, forcing to %u\n", + config->watermark); + } + + /* compute the number of symbols per horizontal blanking interval */ + num = ((mode->htotal - mode->hdisplay) - 7) * link_rate; + config->hblank_symbols = div_u64(num, pclk); + + if (link->capabilities & DP_LINK_CAP_ENHANCED_FRAMING) + config->hblank_symbols -= 3; + + config->hblank_symbols -= 12 / link->num_lanes; + + /* compute the number of symbols per vertical blanking interval */ + num = (mode->hdisplay - 25) * link_rate; + config->vblank_symbols = div_u64(num, pclk); + config->vblank_symbols -= 36 / link->num_lanes + 4; + + dev_dbg(sor->dev, "blank symbols: H:%u V:%u\n", config->hblank_symbols, + config->vblank_symbols); + + return 0; +} + static int tegra_output_sor_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 vbe, vse, hbe, hse, vbs, hbs, i; struct tegra_sor *sor = to_sor(output); + struct tegra_sor_config config; + struct drm_dp_link link; + struct drm_dp_aux *aux; 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); + /* FIXME: properly convert to struct drm_dp_aux */ + aux = (struct drm_dp_aux *)sor->dpaux; + if (sor->dpaux) { err = tegra_dpaux_enable(sor->dpaux); if (err < 0) dev_err(sor->dev, "failed to enable DP: %d\n", err); + + err = drm_dp_link_probe(aux, &link); + if (err < 0) { + dev_err(sor->dev, "failed to probe eDP link: %d\n", + err); + return err; + } } err = clk_set_parent(sor->clk, sor->clk_safe); if (err < 0) dev_err(sor->dev, "failed to set safe parent clock: %d\n", err); + memset(&config, 0, sizeof(config)); + config.bits_per_pixel = 24; /* XXX: don't hardcode? */ + + err = tegra_sor_calc_config(sor, mode, &config, &link); + if (err < 0) + dev_err(sor->dev, "failed to compute link configuration: %d\n", + err); + value = tegra_sor_readl(sor, SOR_CLK_CNTRL); value &= ~SOR_CLK_CNTRL_DP_CLK_SEL_MASK; value |= SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_DPCLK; @@ -385,7 +600,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); @@ -419,15 +634,29 @@ static int tegra_output_sor_enable(struct tegra_output *output) if (err < 0) dev_err(sor->dev, "failed to set DP parent clock: %d\n", err); - /* power dplanes (XXX parameterize based on link?) */ + /* power DP lanes */ value = tegra_sor_readl(sor, SOR_DP_PADCTL_0); - 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; + + if (link.num_lanes <= 2) + value &= ~(SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_2); + else + value |= SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_2; + + if (link.num_lanes <= 1) + value &= ~SOR_DP_PADCTL_PD_TXD_1; + else + value |= SOR_DP_PADCTL_PD_TXD_1; + + if (link.num_lanes == 0) + value &= ~SOR_DP_PADCTL_PD_TXD_0; + else + value |= SOR_DP_PADCTL_PD_TXD_0; + tegra_sor_writel(sor, value, SOR_DP_PADCTL_0); value = tegra_sor_readl(sor, SOR_DP_LINKCTL_0); value &= ~SOR_DP_LINKCTL_LANE_COUNT_MASK; - value |= SOR_DP_LINKCTL_LANE_COUNT(4); + value |= SOR_DP_LINKCTL_LANE_COUNT(link.num_lanes); tegra_sor_writel(sor, value, SOR_DP_LINKCTL_0); /* start lane sequencer */ @@ -443,10 +672,10 @@ static int tegra_output_sor_enable(struct tegra_output *output) usleep_range(250, 1000); } - /* set link bandwidth (2.7 GHz, XXX: parameterize based on link?) */ + /* set link bandwidth */ value = tegra_sor_readl(sor, SOR_CLK_CNTRL); value &= ~SOR_CLK_CNTRL_DP_LINK_SPEED_MASK; - value |= SOR_CLK_CNTRL_DP_LINK_SPEED_G2_70; + value |= drm_dp_link_rate_to_bw_code(link.rate) << 2; tegra_sor_writel(sor, value, SOR_CLK_CNTRL); /* set linkctl */ @@ -454,7 +683,7 @@ static int tegra_output_sor_enable(struct tegra_output *output) value |= SOR_DP_LINKCTL_ENABLE; value &= ~SOR_DP_LINKCTL_TU_SIZE_MASK; - value |= SOR_DP_LINKCTL_TU_SIZE(59); /* XXX: don't hardcode? */ + value |= SOR_DP_LINKCTL_TU_SIZE(config.tu_size); value |= SOR_DP_LINKCTL_ENHANCED_FRAME; tegra_sor_writel(sor, value, SOR_DP_LINKCTL_0); @@ -470,28 +699,31 @@ static int tegra_output_sor_enable(struct tegra_output *output) value = tegra_sor_readl(sor, SOR_DP_CONFIG_0); value &= ~SOR_DP_CONFIG_WATERMARK_MASK; - value |= SOR_DP_CONFIG_WATERMARK(14); /* XXX: don't hardcode? */ + value |= SOR_DP_CONFIG_WATERMARK(config.watermark); value &= ~SOR_DP_CONFIG_ACTIVE_SYM_COUNT_MASK; - value |= SOR_DP_CONFIG_ACTIVE_SYM_COUNT(47); /* XXX: don't hardcode? */ + value |= SOR_DP_CONFIG_ACTIVE_SYM_COUNT(config.active_count); value &= ~SOR_DP_CONFIG_ACTIVE_SYM_FRAC_MASK; - value |= SOR_DP_CONFIG_ACTIVE_SYM_FRAC(9); /* XXX: don't hardcode? */ + value |= SOR_DP_CONFIG_ACTIVE_SYM_FRAC(config.active_frac); - value &= ~SOR_DP_CONFIG_ACTIVE_SYM_POLARITY; /* XXX: don't hardcode? */ + if (config.active_polarity) + value |= SOR_DP_CONFIG_ACTIVE_SYM_POLARITY; + else + value &= ~SOR_DP_CONFIG_ACTIVE_SYM_POLARITY; value |= SOR_DP_CONFIG_ACTIVE_SYM_ENABLE; - value |= SOR_DP_CONFIG_DISPARITY_NEGATIVE; /* XXX: don't hardcode? */ + value |= SOR_DP_CONFIG_DISPARITY_NEGATIVE; tegra_sor_writel(sor, value, SOR_DP_CONFIG_0); value = tegra_sor_readl(sor, SOR_DP_AUDIO_HBLANK_SYMBOLS); value &= ~SOR_DP_AUDIO_HBLANK_SYMBOLS_MASK; - value |= 137; /* XXX: don't hardcode? */ + value |= config.hblank_symbols & 0xffff; tegra_sor_writel(sor, value, SOR_DP_AUDIO_HBLANK_SYMBOLS); value = tegra_sor_readl(sor, SOR_DP_AUDIO_VBLANK_SYMBOLS); value &= ~SOR_DP_AUDIO_VBLANK_SYMBOLS_MASK; - value |= 2368; /* XXX: don't hardcode? */ + value |= config.vblank_symbols & 0xffff; tegra_sor_writel(sor, value, SOR_DP_AUDIO_VBLANK_SYMBOLS); /* enable pad calibration logic */ @@ -500,30 +732,27 @@ static int tegra_output_sor_enable(struct tegra_output *output) tegra_sor_writel(sor, value, SOR_DP_PADCTL_0); if (sor->dpaux) { - /* FIXME: properly convert to struct drm_dp_aux */ - struct drm_dp_aux *aux = (struct drm_dp_aux *)sor->dpaux; - struct drm_dp_link link; u8 rate, lanes; err = drm_dp_link_probe(aux, &link); 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 +787,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 +796,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 */ @@ -586,12 +815,26 @@ static int tegra_output_sor_enable(struct tegra_output *output) * configure panel (24bpp, vsync-, hsync-, DP-A protocol, complete * raster, associate with display controller) */ - value = SOR_STATE_ASY_PIXELDEPTH_BPP_24_444 | - SOR_STATE_ASY_VSYNCPOL | + value = SOR_STATE_ASY_VSYNCPOL | SOR_STATE_ASY_HSYNCPOL | SOR_STATE_ASY_PROTOCOL_DP_A | SOR_STATE_ASY_CRC_MODE_COMPLETE | SOR_STATE_ASY_OWNER(dc->pipe + 1); + + switch (config.bits_per_pixel) { + case 24: + value |= SOR_STATE_ASY_PIXELDEPTH_BPP_24_444; + break; + + case 18: + value |= SOR_STATE_ASY_PIXELDEPTH_BPP_18_444; + break; + + default: + BUG(); + break; + } + tegra_sor_writel(sor, value, SOR_STATE_1); /* @@ -620,11 +863,8 @@ static int tegra_output_sor_enable(struct tegra_output *output) value = ((vbs & 0x7fff) << 16) | (hbs & 0x7fff); tegra_sor_writel(sor, value, SOR_HEAD_STATE_4(0)); - /* XXX interlaced mode */ - tegra_sor_writel(sor, 0x00000001, SOR_HEAD_STATE_5(0)); - /* CSTM (LVDS, link A/B, upper) */ - value = SOR_CSTM_LVDS | SOR_CSTM_LINK_ACT_B | SOR_CSTM_LINK_ACT_B | + value = SOR_CSTM_LVDS | SOR_CSTM_LINK_ACT_A | SOR_CSTM_LINK_ACT_B | SOR_CSTM_UPPER; tegra_sor_writel(sor, value, SOR_CSTM); @@ -632,7 +872,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 +884,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) @@ -740,7 +982,7 @@ static int tegra_sor_power_down(struct tegra_sor *sor) tegra_sor_writel(sor, value, SOR_DP_PADCTL_0); /* stop lane sequencer */ - value = SOR_LANE_SEQ_CTL_TRIGGER | SOR_LANE_SEQ_CTL_SEQUENCE_DOWN | + value = SOR_LANE_SEQ_CTL_TRIGGER | SOR_LANE_SEQ_CTL_SEQUENCE_UP | SOR_LANE_SEQ_CTL_POWER_STATE_DOWN; tegra_sor_writel(sor, value, SOR_LANE_SEQ_CTL); @@ -783,15 +1025,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 +1076,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,18 +1098,18 @@ 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; - err = clk_set_parent(clk, sor->clk_parent); if (err < 0) { dev_err(sor->dev, "failed to set parent clock: %d\n", err); @@ -874,11 +1118,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,9 +1159,124 @@ 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 drm_minor *minor) +{ + struct dentry *entry; + int err = 0; + + sor->debugfs = debugfs_create_dir("sor", minor->debugfs_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", + minor->debugfs_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_recursive(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); + struct drm_device *drm = dev_get_drvdata(client->parent); struct tegra_sor *sor = host1x_client_to_sor(client); int err; @@ -928,12 +1288,18 @@ static int tegra_sor_init(struct host1x_client *client) sor->output.dev = sor->dev; sor->output.ops = &sor_ops; - err = tegra_output_init(tegra->drm, &sor->output); + err = tegra_output_init(drm, &sor->output); if (err < 0) { dev_err(sor->dev, "output setup failed: %d\n", err); return err; } + 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); + } + if (sor->dpaux) { err = tegra_dpaux_attach(sor->dpaux, &sor->output); if (err < 0) { @@ -964,6 +1330,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 +1417,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 |