Commit 2f5b4ef1 authored by Dave Airlie's avatar Dave Airlie

Merge tag 'drm/tegra/for-3.20-rc1' of git://anongit.freedesktop.org/tegra/linux into drm-next

drm/tegra: Changes for v3.20-rc1

The biggest part of these changes is the conversion to atomic mode-
setting. A lot of cleanup and demidlayering was required before the
conversion, with the result being a whole lot of changes.

Besides the atomic mode-setting support, the host1x bus now has the
proper infrastructure to support suspend/resume for child devices.

Finally, a couple of smaller cleanup patches round things off.

* tag 'drm/tegra/for-3.20-rc1' of git://anongit.freedesktop.org/tegra/linux: (54 commits)
  drm/tegra: Use correct relocation target offsets
  drm/tegra: Add minimal power management
  drm/tegra: dc: Unify enabling the display controller
  drm/tegra: Track tiling and format in plane state
  drm/tegra: Track active planes in CRTC state
  drm/tegra: Remove unused ->mode_fixup() callbacks
  drm/tegra: Atomic conversion, phase 3, step 3
  drm/tegra: Atomic conversion, phase 3, step 2
  drm/tegra: dc: Use atomic clock state in modeset
  drm/tegra: sor: Implement ->atomic_check()
  drm/tegra: hdmi: Implement ->atomic_check()
  drm/tegra: dsi: Implement ->atomic_check()
  drm/tegra: rgb: Implement ->atomic_check()
  drm/tegra: dc: Store clock setup in atomic state
  drm/tegra: Atomic conversion, phase 3, step 1
  drm/tegra: Atomic conversion, phase 2
  drm/tegra: Atomic conversion, phase 1
  drm/tegra: dc: Do not needlessly deassert reset
  drm/tegra: Output cleanup functions cannot fail
  drm/tegra: Remove remnants of the output midlayer
  ...
parents 1da30627 31f40f86
obj-y += drm/ vga/ # drm/tegra depends on host1x, so if both drivers are built-in care must be
# taken to initialize them in the correct order. Link order is the only way
# to ensure this currently.
obj-$(CONFIG_TEGRA_HOST1X) += host1x/ obj-$(CONFIG_TEGRA_HOST1X) += host1x/
obj-y += drm/ vga/
obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/ obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/
...@@ -297,7 +297,15 @@ mode_fixup(struct drm_atomic_state *state) ...@@ -297,7 +297,15 @@ mode_fixup(struct drm_atomic_state *state)
} }
} }
if (funcs->atomic_check) {
ret = funcs->atomic_check(encoder, crtc_state,
conn_state);
if (ret) {
DRM_DEBUG_KMS("[ENCODER:%d:%s] check failed\n",
encoder->base.id, encoder->name);
return ret;
}
} else {
ret = funcs->mode_fixup(encoder, &crtc_state->mode, ret = funcs->mode_fixup(encoder, &crtc_state->mode,
&crtc_state->adjusted_mode); &crtc_state->adjusted_mode);
if (!ret) { if (!ret) {
...@@ -306,6 +314,7 @@ mode_fixup(struct drm_atomic_state *state) ...@@ -306,6 +314,7 @@ mode_fixup(struct drm_atomic_state *state)
return -EINVAL; return -EINVAL;
} }
} }
}
for (i = 0; i < ncrtcs; i++) { for (i = 0; i < ncrtcs; i++) {
struct drm_crtc_helper_funcs *funcs; struct drm_crtc_helper_funcs *funcs;
...@@ -1108,11 +1117,18 @@ void drm_atomic_helper_commit_planes(struct drm_device *dev, ...@@ -1108,11 +1117,18 @@ void drm_atomic_helper_commit_planes(struct drm_device *dev,
funcs = plane->helper_private; funcs = plane->helper_private;
if (!funcs || !funcs->atomic_update) if (!funcs)
continue; continue;
old_plane_state = old_state->plane_states[i]; old_plane_state = old_state->plane_states[i];
/*
* Special-case disabling the plane if drivers support it.
*/
if (drm_atomic_plane_disabling(plane, old_plane_state) &&
funcs->atomic_disable)
funcs->atomic_disable(plane, old_plane_state);
else
funcs->atomic_update(plane, old_plane_state); funcs->atomic_update(plane, old_plane_state);
} }
......
...@@ -449,6 +449,14 @@ int drm_plane_helper_commit(struct drm_plane *plane, ...@@ -449,6 +449,14 @@ int drm_plane_helper_commit(struct drm_plane *plane,
crtc_funcs[i]->atomic_begin(crtc[i]); crtc_funcs[i]->atomic_begin(crtc[i]);
} }
/*
* Drivers may optionally implement the ->atomic_disable callback, so
* special-case that here.
*/
if (drm_atomic_plane_disabling(plane, plane_state) &&
plane_funcs->atomic_disable)
plane_funcs->atomic_disable(plane, plane_state);
else
plane_funcs->atomic_update(plane, plane_state); plane_funcs->atomic_update(plane, plane_state);
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
......
...@@ -18,9 +18,12 @@ ...@@ -18,9 +18,12 @@
#include "drm.h" #include "drm.h"
#include "gem.h" #include "gem.h"
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_plane_helper.h> #include <drm/drm_plane_helper.h>
struct tegra_dc_soc_info { struct tegra_dc_soc_info {
bool supports_border_color;
bool supports_interlacing; bool supports_interlacing;
bool supports_cursor; bool supports_cursor;
bool supports_block_linear; bool supports_block_linear;
...@@ -38,63 +41,122 @@ static inline struct tegra_plane *to_tegra_plane(struct drm_plane *plane) ...@@ -38,63 +41,122 @@ static inline struct tegra_plane *to_tegra_plane(struct drm_plane *plane)
return container_of(plane, struct tegra_plane, base); return container_of(plane, struct tegra_plane, base);
} }
static void tegra_dc_window_commit(struct tegra_dc *dc, unsigned int index) struct tegra_dc_state {
struct drm_crtc_state base;
struct clk *clk;
unsigned long pclk;
unsigned int div;
u32 planes;
};
static inline struct tegra_dc_state *to_dc_state(struct drm_crtc_state *state)
{ {
u32 value = WIN_A_ACT_REQ << index; if (state)
return container_of(state, struct tegra_dc_state, base);
tegra_dc_writel(dc, value << 8, DC_CMD_STATE_CONTROL); return NULL;
tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL);
} }
static void tegra_dc_cursor_commit(struct tegra_dc *dc) struct tegra_plane_state {
struct drm_plane_state base;
struct tegra_bo_tiling tiling;
u32 format;
u32 swap;
};
static inline struct tegra_plane_state *
to_tegra_plane_state(struct drm_plane_state *state)
{ {
tegra_dc_writel(dc, CURSOR_ACT_REQ << 8, DC_CMD_STATE_CONTROL); if (state)
tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL); return container_of(state, struct tegra_plane_state, base);
return NULL;
} }
static void tegra_dc_commit(struct tegra_dc *dc) /*
* Reads the active copy of a register. This takes the dc->lock spinlock to
* prevent races with the VBLANK processing which also needs access to the
* active copy of some registers.
*/
static u32 tegra_dc_readl_active(struct tegra_dc *dc, unsigned long offset)
{
unsigned long flags;
u32 value;
spin_lock_irqsave(&dc->lock, flags);
tegra_dc_writel(dc, READ_MUX, DC_CMD_STATE_ACCESS);
value = tegra_dc_readl(dc, offset);
tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS);
spin_unlock_irqrestore(&dc->lock, flags);
return value;
}
/*
* Double-buffered registers have two copies: ASSEMBLY and ACTIVE. When the
* *_ACT_REQ bits are set the ASSEMBLY copy is latched into the ACTIVE copy.
* Latching happens mmediately if the display controller is in STOP mode or
* on the next frame boundary otherwise.
*
* Triple-buffered registers have three copies: ASSEMBLY, ARM and ACTIVE. The
* ASSEMBLY copy is latched into the ARM copy immediately after *_UPDATE bits
* are written. When the *_ACT_REQ bits are written, the ARM copy is latched
* into the ACTIVE copy, either immediately if the display controller is in
* STOP mode, or at the next frame boundary otherwise.
*/
void tegra_dc_commit(struct tegra_dc *dc)
{ {
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, 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); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
} }
static unsigned int tegra_dc_format(uint32_t format, uint32_t *swap) static int tegra_dc_format(u32 fourcc, u32 *format, u32 *swap)
{ {
/* assume no swapping of fetched data */ /* assume no swapping of fetched data */
if (swap) if (swap)
*swap = BYTE_SWAP_NOSWAP; *swap = BYTE_SWAP_NOSWAP;
switch (format) { switch (fourcc) {
case DRM_FORMAT_XBGR8888: case DRM_FORMAT_XBGR8888:
return WIN_COLOR_DEPTH_R8G8B8A8; *format = WIN_COLOR_DEPTH_R8G8B8A8;
break;
case DRM_FORMAT_XRGB8888: case DRM_FORMAT_XRGB8888:
return WIN_COLOR_DEPTH_B8G8R8A8; *format = WIN_COLOR_DEPTH_B8G8R8A8;
break;
case DRM_FORMAT_RGB565: case DRM_FORMAT_RGB565:
return WIN_COLOR_DEPTH_B5G6R5; *format = WIN_COLOR_DEPTH_B5G6R5;
break;
case DRM_FORMAT_UYVY: case DRM_FORMAT_UYVY:
return WIN_COLOR_DEPTH_YCbCr422; *format = WIN_COLOR_DEPTH_YCbCr422;
break;
case DRM_FORMAT_YUYV: case DRM_FORMAT_YUYV:
if (swap) if (swap)
*swap = BYTE_SWAP_SWAP2; *swap = BYTE_SWAP_SWAP2;
return WIN_COLOR_DEPTH_YCbCr422; *format = WIN_COLOR_DEPTH_YCbCr422;
break;
case DRM_FORMAT_YUV420: case DRM_FORMAT_YUV420:
return WIN_COLOR_DEPTH_YCbCr420P; *format = WIN_COLOR_DEPTH_YCbCr420P;
break;
case DRM_FORMAT_YUV422: case DRM_FORMAT_YUV422:
return WIN_COLOR_DEPTH_YCbCr422P; *format = WIN_COLOR_DEPTH_YCbCr422P;
break;
default: default:
break; return -EINVAL;
} }
WARN(1, "unsupported pixel format %u, using default\n", format); return 0;
return WIN_COLOR_DEPTH_B8G8R8A8;
} }
static bool tegra_dc_format_is_yuv(unsigned int format, bool *planar) static bool tegra_dc_format_is_yuv(unsigned int format, bool *planar)
...@@ -121,6 +183,9 @@ static bool tegra_dc_format_is_yuv(unsigned int format, bool *planar) ...@@ -121,6 +183,9 @@ static bool tegra_dc_format_is_yuv(unsigned int format, bool *planar)
return true; return true;
} }
if (planar)
*planar = false;
return false; return false;
} }
...@@ -164,7 +229,7 @@ static inline u32 compute_initial_dda(unsigned int in) ...@@ -164,7 +229,7 @@ static inline u32 compute_initial_dda(unsigned int in)
return dfixed_frac(inf); return dfixed_frac(inf);
} }
static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, static void tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index,
const struct tegra_dc_window *window) const struct tegra_dc_window *window)
{ {
unsigned h_offset, v_offset, h_size, v_size, h_dda, v_dda, bpp; unsigned h_offset, v_offset, h_size, v_size, h_dda, v_dda, bpp;
...@@ -274,9 +339,11 @@ static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, ...@@ -274,9 +339,11 @@ static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index,
break; break;
case TEGRA_BO_TILING_MODE_BLOCK: case TEGRA_BO_TILING_MODE_BLOCK:
DRM_ERROR("hardware doesn't support block linear mode\n"); /*
spin_unlock_irqrestore(&dc->lock, flags); * No need to handle this here because ->atomic_check
return -EINVAL; * will already have filtered it out.
*/
break;
} }
tegra_dc_writel(dc, value, DC_WIN_BUFFER_ADDR_MODE); tegra_dc_writel(dc, value, DC_WIN_BUFFER_ADDR_MODE);
...@@ -332,109 +399,245 @@ static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, ...@@ -332,109 +399,245 @@ static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index,
break; break;
} }
tegra_dc_window_commit(dc, index);
spin_unlock_irqrestore(&dc->lock, flags); spin_unlock_irqrestore(&dc->lock, flags);
return 0;
} }
static int tegra_window_plane_disable(struct drm_plane *plane) static void tegra_plane_destroy(struct drm_plane *plane)
{ {
struct tegra_dc *dc = to_tegra_dc(plane->crtc);
struct tegra_plane *p = to_tegra_plane(plane); struct tegra_plane *p = to_tegra_plane(plane);
unsigned long flags;
u32 value;
if (!plane->crtc) drm_plane_cleanup(plane);
return 0; kfree(p);
}
spin_lock_irqsave(&dc->lock, flags); static const u32 tegra_primary_plane_formats[] = {
DRM_FORMAT_XBGR8888,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_RGB565,
};
value = WINDOW_A_SELECT << p->index; static void tegra_primary_plane_destroy(struct drm_plane *plane)
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER); {
tegra_plane_destroy(plane);
}
value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); static void tegra_plane_reset(struct drm_plane *plane)
value &= ~WIN_ENABLE; {
tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); struct tegra_plane_state *state;
tegra_dc_window_commit(dc, p->index); if (plane->state && plane->state->fb)
drm_framebuffer_unreference(plane->state->fb);
spin_unlock_irqrestore(&dc->lock, flags); kfree(plane->state);
plane->state = NULL;
return 0; state = kzalloc(sizeof(*state), GFP_KERNEL);
if (state) {
plane->state = &state->base;
plane->state->plane = plane;
}
} }
static void tegra_plane_destroy(struct drm_plane *plane) static struct drm_plane_state *tegra_plane_atomic_duplicate_state(struct drm_plane *plane)
{ {
struct tegra_plane *p = to_tegra_plane(plane); struct tegra_plane_state *state = to_tegra_plane_state(plane->state);
struct tegra_plane_state *copy;
drm_plane_cleanup(plane); copy = kmemdup(state, sizeof(*state), GFP_KERNEL);
kfree(p); if (!copy)
return NULL;
if (copy->base.fb)
drm_framebuffer_reference(copy->base.fb);
return &copy->base;
} }
static const u32 tegra_primary_plane_formats[] = { static void tegra_plane_atomic_destroy_state(struct drm_plane *plane,
DRM_FORMAT_XBGR8888, struct drm_plane_state *state)
DRM_FORMAT_XRGB8888, {
DRM_FORMAT_RGB565, if (state->fb)
drm_framebuffer_unreference(state->fb);
kfree(state);
}
static const struct drm_plane_funcs tegra_primary_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = tegra_primary_plane_destroy,
.reset = tegra_plane_reset,
.atomic_duplicate_state = tegra_plane_atomic_duplicate_state,
.atomic_destroy_state = tegra_plane_atomic_destroy_state,
}; };
static int tegra_primary_plane_update(struct drm_plane *plane, static int tegra_plane_prepare_fb(struct drm_plane *plane,
struct drm_crtc *crtc, struct drm_framebuffer *fb)
struct drm_framebuffer *fb, int crtc_x,
int crtc_y, unsigned int crtc_w,
unsigned int crtc_h, uint32_t src_x,
uint32_t src_y, uint32_t src_w,
uint32_t src_h)
{ {
struct tegra_bo *bo = tegra_fb_get_plane(fb, 0); return 0;
struct tegra_plane *p = to_tegra_plane(plane); }
struct tegra_dc *dc = to_tegra_dc(crtc);
struct tegra_dc_window window; static void tegra_plane_cleanup_fb(struct drm_plane *plane,
struct drm_framebuffer *fb)
{
}
static int tegra_plane_state_add(struct tegra_plane *plane,
struct drm_plane_state *state)
{
struct drm_crtc_state *crtc_state;
struct tegra_dc_state *tegra;
/* Propagate errors from allocation or locking failures. */
crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
tegra = to_dc_state(crtc_state);
tegra->planes |= WIN_A_ACT_REQ << plane->index;
return 0;
}
static int tegra_plane_atomic_check(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct tegra_plane_state *plane_state = to_tegra_plane_state(state);
struct tegra_bo_tiling *tiling = &plane_state->tiling;
struct tegra_plane *tegra = to_tegra_plane(plane);
struct tegra_dc *dc = to_tegra_dc(state->crtc);
int err; int err;
memset(&window, 0, sizeof(window)); /* no need for further checks if the plane is being disabled */
window.src.x = src_x >> 16; if (!state->crtc)
window.src.y = src_y >> 16; return 0;
window.src.w = src_w >> 16;
window.src.h = src_h >> 16;
window.dst.x = crtc_x;
window.dst.y = crtc_y;
window.dst.w = crtc_w;
window.dst.h = crtc_h;
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);
err = tegra_fb_get_tiling(fb, &window.tiling); err = tegra_dc_format(state->fb->pixel_format, &plane_state->format,
&plane_state->swap);
if (err < 0) if (err < 0)
return err; return err;
window.base[0] = bo->paddr + fb->offsets[0]; err = tegra_fb_get_tiling(state->fb, tiling);
window.stride[0] = fb->pitches[0]; if (err < 0)
return err;
err = tegra_dc_setup_window(dc, p->index, &window); if (tiling->mode == TEGRA_BO_TILING_MODE_BLOCK &&
!dc->soc->supports_block_linear) {
DRM_ERROR("hardware doesn't support block linear mode\n");
return -EINVAL;
}
/*
* Tegra doesn't support different strides for U and V planes so we
* error out if the user tries to display a framebuffer with such a
* configuration.
*/
if (drm_format_num_planes(state->fb->pixel_format) > 2) {
if (state->fb->pitches[2] != state->fb->pitches[1]) {
DRM_ERROR("unsupported UV-plane configuration\n");
return -EINVAL;
}
}
err = tegra_plane_state_add(tegra, state);
if (err < 0) if (err < 0)
return err; return err;
return 0; return 0;
} }
static void tegra_primary_plane_destroy(struct drm_plane *plane) static void tegra_plane_atomic_update(struct drm_plane *plane,
struct drm_plane_state *old_state)
{ {
tegra_window_plane_disable(plane); struct tegra_plane_state *state = to_tegra_plane_state(plane->state);
tegra_plane_destroy(plane); struct tegra_dc *dc = to_tegra_dc(plane->state->crtc);
struct drm_framebuffer *fb = plane->state->fb;
struct tegra_plane *p = to_tegra_plane(plane);
struct tegra_dc_window window;
unsigned int i;
/* rien ne va plus */
if (!plane->state->crtc || !plane->state->fb)
return;
memset(&window, 0, sizeof(window));
window.src.x = plane->state->src_x >> 16;
window.src.y = plane->state->src_y >> 16;
window.src.w = plane->state->src_w >> 16;
window.src.h = plane->state->src_h >> 16;
window.dst.x = plane->state->crtc_x;
window.dst.y = plane->state->crtc_y;
window.dst.w = plane->state->crtc_w;
window.dst.h = plane->state->crtc_h;
window.bits_per_pixel = fb->bits_per_pixel;
window.bottom_up = tegra_fb_is_bottom_up(fb);
/* copy from state */
window.tiling = state->tiling;
window.format = state->format;
window.swap = state->swap;
for (i = 0; i < drm_format_num_planes(fb->pixel_format); i++) {
struct tegra_bo *bo = tegra_fb_get_plane(fb, i);
window.base[i] = bo->paddr + fb->offsets[i];
window.stride[i] = fb->pitches[i];
}
tegra_dc_setup_window(dc, p->index, &window);
} }
static const struct drm_plane_funcs tegra_primary_plane_funcs = { static void tegra_plane_atomic_disable(struct drm_plane *plane,
.update_plane = tegra_primary_plane_update, struct drm_plane_state *old_state)
.disable_plane = tegra_window_plane_disable, {
.destroy = tegra_primary_plane_destroy, struct tegra_plane *p = to_tegra_plane(plane);
struct tegra_dc *dc;
unsigned long flags;
u32 value;
/* rien ne va plus */
if (!old_state || !old_state->crtc)
return;
dc = to_tegra_dc(old_state->crtc);
spin_lock_irqsave(&dc->lock, flags);
value = WINDOW_A_SELECT << p->index;
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER);
value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS);
value &= ~WIN_ENABLE;
tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS);
spin_unlock_irqrestore(&dc->lock, flags);
}
static const struct drm_plane_helper_funcs tegra_primary_plane_helper_funcs = {
.prepare_fb = tegra_plane_prepare_fb,
.cleanup_fb = tegra_plane_cleanup_fb,
.atomic_check = tegra_plane_atomic_check,
.atomic_update = tegra_plane_atomic_update,
.atomic_disable = tegra_plane_atomic_disable,
}; };
static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm, static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm,
struct tegra_dc *dc) struct tegra_dc *dc)
{ {
/*
* Ideally this would use drm_crtc_mask(), but that would require the
* CRTC to already be in the mode_config's list of CRTCs. However, it
* will only be added to that list in the drm_crtc_init_with_planes()
* (in tegra_dc_init()), which in turn requires registration of these
* planes. So we have ourselves a nice little chicken and egg problem
* here.
*
* We work around this by manually creating the mask from the number
* of CRTCs that have been registered, and should therefore always be
* the same as drm_crtc_index() after registration.
*/
unsigned long possible_crtcs = 1 << drm->mode_config.num_crtc;
struct tegra_plane *plane; struct tegra_plane *plane;
unsigned int num_formats; unsigned int num_formats;
const u32 *formats; const u32 *formats;
...@@ -447,7 +650,7 @@ static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm, ...@@ -447,7 +650,7 @@ static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm,
num_formats = ARRAY_SIZE(tegra_primary_plane_formats); num_formats = ARRAY_SIZE(tegra_primary_plane_formats);
formats = tegra_primary_plane_formats; formats = tegra_primary_plane_formats;
err = drm_universal_plane_init(drm, &plane->base, 1 << dc->pipe, err = drm_universal_plane_init(drm, &plane->base, possible_crtcs,
&tegra_primary_plane_funcs, formats, &tegra_primary_plane_funcs, formats,
num_formats, DRM_PLANE_TYPE_PRIMARY); num_formats, DRM_PLANE_TYPE_PRIMARY);
if (err < 0) { if (err < 0) {
...@@ -455,6 +658,8 @@ static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm, ...@@ -455,6 +658,8 @@ static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm,
return ERR_PTR(err); return ERR_PTR(err);
} }
drm_plane_helper_add(&plane->base, &tegra_primary_plane_helper_funcs);
return &plane->base; return &plane->base;
} }
...@@ -462,27 +667,49 @@ static const u32 tegra_cursor_plane_formats[] = { ...@@ -462,27 +667,49 @@ static const u32 tegra_cursor_plane_formats[] = {
DRM_FORMAT_RGBA8888, DRM_FORMAT_RGBA8888,
}; };
static int tegra_cursor_plane_update(struct drm_plane *plane, static int tegra_cursor_atomic_check(struct drm_plane *plane,
struct drm_crtc *crtc, struct drm_plane_state *state)
struct drm_framebuffer *fb, int crtc_x,
int crtc_y, unsigned int crtc_w,
unsigned int crtc_h, uint32_t src_x,
uint32_t src_y, uint32_t src_w,
uint32_t src_h)
{ {
struct tegra_bo *bo = tegra_fb_get_plane(fb, 0); struct tegra_plane *tegra = to_tegra_plane(plane);
struct tegra_dc *dc = to_tegra_dc(crtc); int err;
u32 value = CURSOR_CLIP_DISPLAY;
/* no need for further checks if the plane is being disabled */
if (!state->crtc)
return 0;
/* scaling not supported for cursor */ /* scaling not supported for cursor */
if ((src_w >> 16 != crtc_w) || (src_h >> 16 != crtc_h)) if ((state->src_w >> 16 != state->crtc_w) ||
(state->src_h >> 16 != state->crtc_h))
return -EINVAL; return -EINVAL;
/* only square cursors supported */ /* only square cursors supported */
if (src_w != src_h) if (state->src_w != state->src_h)
return -EINVAL; return -EINVAL;
switch (crtc_w) { if (state->crtc_w != 32 && state->crtc_w != 64 &&
state->crtc_w != 128 && state->crtc_w != 256)
return -EINVAL;
err = tegra_plane_state_add(tegra, state);
if (err < 0)
return err;
return 0;
}
static void tegra_cursor_atomic_update(struct drm_plane *plane,
struct drm_plane_state *old_state)
{
struct tegra_bo *bo = tegra_fb_get_plane(plane->state->fb, 0);
struct tegra_dc *dc = to_tegra_dc(plane->state->crtc);
struct drm_plane_state *state = plane->state;
u32 value = CURSOR_CLIP_DISPLAY;
/* rien ne va plus */
if (!plane->state->crtc || !plane->state->fb)
return;
switch (state->crtc_w) {
case 32: case 32:
value |= CURSOR_SIZE_32x32; value |= CURSOR_SIZE_32x32;
break; break;
...@@ -500,7 +727,9 @@ static int tegra_cursor_plane_update(struct drm_plane *plane, ...@@ -500,7 +727,9 @@ static int tegra_cursor_plane_update(struct drm_plane *plane,
break; break;
default: default:
return -EINVAL; WARN(1, "cursor size %ux%u not supported\n", state->crtc_w,
state->crtc_h);
return;
} }
value |= (bo->paddr >> 10) & 0x3fffff; value |= (bo->paddr >> 10) & 0x3fffff;
...@@ -526,38 +755,43 @@ static int tegra_cursor_plane_update(struct drm_plane *plane, ...@@ -526,38 +755,43 @@ static int tegra_cursor_plane_update(struct drm_plane *plane,
tegra_dc_writel(dc, value, DC_DISP_BLEND_CURSOR_CONTROL); tegra_dc_writel(dc, value, DC_DISP_BLEND_CURSOR_CONTROL);
/* position the cursor */ /* position the cursor */
value = (crtc_y & 0x3fff) << 16 | (crtc_x & 0x3fff); value = (state->crtc_y & 0x3fff) << 16 | (state->crtc_x & 0x3fff);
tegra_dc_writel(dc, value, DC_DISP_CURSOR_POSITION); tegra_dc_writel(dc, value, DC_DISP_CURSOR_POSITION);
/* apply changes */
tegra_dc_cursor_commit(dc);
tegra_dc_commit(dc);
return 0;
} }
static int tegra_cursor_plane_disable(struct drm_plane *plane) static void tegra_cursor_atomic_disable(struct drm_plane *plane,
struct drm_plane_state *old_state)
{ {
struct tegra_dc *dc = to_tegra_dc(plane->crtc); struct tegra_dc *dc;
u32 value; u32 value;
if (!plane->crtc) /* rien ne va plus */
return 0; if (!old_state || !old_state->crtc)
return;
dc = to_tegra_dc(old_state->crtc);
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
value &= ~CURSOR_ENABLE; value &= ~CURSOR_ENABLE;
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
tegra_dc_cursor_commit(dc);
tegra_dc_commit(dc);
return 0;
} }
static const struct drm_plane_funcs tegra_cursor_plane_funcs = { static const struct drm_plane_funcs tegra_cursor_plane_funcs = {
.update_plane = tegra_cursor_plane_update, .update_plane = drm_atomic_helper_update_plane,
.disable_plane = tegra_cursor_plane_disable, .disable_plane = drm_atomic_helper_disable_plane,
.destroy = tegra_plane_destroy, .destroy = tegra_plane_destroy,
.reset = tegra_plane_reset,
.atomic_duplicate_state = tegra_plane_atomic_duplicate_state,
.atomic_destroy_state = tegra_plane_atomic_destroy_state,
};
static const struct drm_plane_helper_funcs tegra_cursor_plane_helper_funcs = {
.prepare_fb = tegra_plane_prepare_fb,
.cleanup_fb = tegra_plane_cleanup_fb,
.atomic_check = tegra_cursor_atomic_check,
.atomic_update = tegra_cursor_atomic_update,
.atomic_disable = tegra_cursor_atomic_disable,
}; };
static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm, static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm,
...@@ -572,6 +806,13 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm, ...@@ -572,6 +806,13 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm,
if (!plane) if (!plane)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
/*
* We'll treat the cursor as an overlay plane with index 6 here so
* that the update and activation request bits in DC_CMD_STATE_CONTROL
* match up.
*/
plane->index = 6;
num_formats = ARRAY_SIZE(tegra_cursor_plane_formats); num_formats = ARRAY_SIZE(tegra_cursor_plane_formats);
formats = tegra_cursor_plane_formats; formats = tegra_cursor_plane_formats;
...@@ -583,71 +824,23 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm, ...@@ -583,71 +824,23 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm,
return ERR_PTR(err); return ERR_PTR(err);
} }
return &plane->base; drm_plane_helper_add(&plane->base, &tegra_cursor_plane_helper_funcs);
}
static int tegra_overlay_plane_update(struct drm_plane *plane,
struct drm_crtc *crtc,
struct drm_framebuffer *fb, int crtc_x,
int crtc_y, unsigned int crtc_w,
unsigned int crtc_h, uint32_t src_x,
uint32_t src_y, uint32_t src_w,
uint32_t src_h)
{
struct tegra_plane *p = to_tegra_plane(plane);
struct tegra_dc *dc = to_tegra_dc(crtc);
struct tegra_dc_window window;
unsigned int i;
int err;
memset(&window, 0, sizeof(window));
window.src.x = src_x >> 16;
window.src.y = src_y >> 16;
window.src.w = src_w >> 16;
window.src.h = src_h >> 16;
window.dst.x = crtc_x;
window.dst.y = crtc_y;
window.dst.w = crtc_w;
window.dst.h = crtc_h;
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);
err = tegra_fb_get_tiling(fb, &window.tiling);
if (err < 0)
return err;
for (i = 0; i < drm_format_num_planes(fb->pixel_format); i++) {
struct tegra_bo *bo = tegra_fb_get_plane(fb, i);
window.base[i] = bo->paddr + fb->offsets[i];
/*
* Tegra doesn't support different strides for U and V planes
* so we display a warning if the user tries to display a
* framebuffer with such a configuration.
*/
if (i >= 2) {
if (fb->pitches[i] != window.stride[1])
DRM_ERROR("unsupported UV-plane configuration\n");
} else {
window.stride[i] = fb->pitches[i];
}
}
return tegra_dc_setup_window(dc, p->index, &window); return &plane->base;
} }
static void tegra_overlay_plane_destroy(struct drm_plane *plane) static void tegra_overlay_plane_destroy(struct drm_plane *plane)
{ {
tegra_window_plane_disable(plane);
tegra_plane_destroy(plane); tegra_plane_destroy(plane);
} }
static const struct drm_plane_funcs tegra_overlay_plane_funcs = { static const struct drm_plane_funcs tegra_overlay_plane_funcs = {
.update_plane = tegra_overlay_plane_update, .update_plane = drm_atomic_helper_update_plane,
.disable_plane = tegra_window_plane_disable, .disable_plane = drm_atomic_helper_disable_plane,
.destroy = tegra_overlay_plane_destroy, .destroy = tegra_overlay_plane_destroy,
.reset = tegra_plane_reset,
.atomic_duplicate_state = tegra_plane_atomic_duplicate_state,
.atomic_destroy_state = tegra_plane_atomic_destroy_state,
}; };
static const uint32_t tegra_overlay_plane_formats[] = { static const uint32_t tegra_overlay_plane_formats[] = {
...@@ -660,6 +853,14 @@ static const uint32_t tegra_overlay_plane_formats[] = { ...@@ -660,6 +853,14 @@ static const uint32_t tegra_overlay_plane_formats[] = {
DRM_FORMAT_YUV422, DRM_FORMAT_YUV422,
}; };
static const struct drm_plane_helper_funcs tegra_overlay_plane_helper_funcs = {
.prepare_fb = tegra_plane_prepare_fb,
.cleanup_fb = tegra_plane_cleanup_fb,
.atomic_check = tegra_plane_atomic_check,
.atomic_update = tegra_plane_atomic_update,
.atomic_disable = tegra_plane_atomic_disable,
};
static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm, static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm,
struct tegra_dc *dc, struct tegra_dc *dc,
unsigned int index) unsigned int index)
...@@ -686,6 +887,8 @@ static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm, ...@@ -686,6 +887,8 @@ static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm,
return ERR_PTR(err); return ERR_PTR(err);
} }
drm_plane_helper_add(&plane->base, &tegra_overlay_plane_helper_funcs);
return &plane->base; return &plane->base;
} }
...@@ -703,99 +906,6 @@ static int tegra_dc_add_planes(struct drm_device *drm, struct tegra_dc *dc) ...@@ -703,99 +906,6 @@ static int tegra_dc_add_planes(struct drm_device *drm, struct tegra_dc *dc)
return 0; return 0;
} }
static int tegra_dc_set_base(struct tegra_dc *dc, int x, int y,
struct drm_framebuffer *fb)
{
struct tegra_bo *bo = tegra_fb_get_plane(fb, 0);
unsigned int h_offset = 0, v_offset = 0;
struct tegra_bo_tiling tiling;
unsigned long value, flags;
unsigned int format, swap;
int err;
err = tegra_fb_get_tiling(fb, &tiling);
if (err < 0)
return err;
spin_lock_irqsave(&dc->lock, flags);
tegra_dc_writel(dc, WINDOW_A_SELECT, DC_CMD_DISPLAY_WINDOW_HEADER);
value = fb->offsets[0] + y * fb->pitches[0] +
x * fb->bits_per_pixel / 8;
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 (dc->soc->supports_block_linear) {
unsigned long height = tiling.value;
switch (tiling.mode) {
case TEGRA_BO_TILING_MODE_PITCH:
value = DC_WINBUF_SURFACE_KIND_PITCH;
break;
case TEGRA_BO_TILING_MODE_TILED:
value = DC_WINBUF_SURFACE_KIND_TILED;
break;
case TEGRA_BO_TILING_MODE_BLOCK:
value = DC_WINBUF_SURFACE_KIND_BLOCK_HEIGHT(height) |
DC_WINBUF_SURFACE_KIND_BLOCK;
break;
}
tegra_dc_writel(dc, value, DC_WINBUF_SURFACE_KIND);
} else {
switch (tiling.mode) {
case TEGRA_BO_TILING_MODE_PITCH:
value = DC_WIN_BUFFER_ADDR_MODE_LINEAR_UV |
DC_WIN_BUFFER_ADDR_MODE_LINEAR;
break;
case TEGRA_BO_TILING_MODE_TILED:
value = DC_WIN_BUFFER_ADDR_MODE_TILE_UV |
DC_WIN_BUFFER_ADDR_MODE_TILE;
break;
case TEGRA_BO_TILING_MODE_BLOCK:
DRM_ERROR("hardware doesn't support block linear mode\n");
spin_unlock_irqrestore(&dc->lock, flags);
return -EINVAL;
}
tegra_dc_writel(dc, value, DC_WIN_BUFFER_ADDR_MODE);
}
/* 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 |= 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 &= ~V_DIRECTION;
tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS);
}
tegra_dc_writel(dc, h_offset, DC_WINBUF_ADDR_H_OFFSET);
tegra_dc_writel(dc, v_offset, DC_WINBUF_ADDR_V_OFFSET);
value = GENERAL_ACT_REQ | WIN_A_ACT_REQ;
tegra_dc_writel(dc, value << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL);
spin_unlock_irqrestore(&dc->lock, flags);
return 0;
}
void tegra_dc_enable_vblank(struct tegra_dc *dc) void tegra_dc_enable_vblank(struct tegra_dc *dc)
{ {
unsigned long value, flags; unsigned long value, flags;
...@@ -838,7 +948,7 @@ static void tegra_dc_finish_page_flip(struct tegra_dc *dc) ...@@ -838,7 +948,7 @@ static void tegra_dc_finish_page_flip(struct tegra_dc *dc)
bo = tegra_fb_get_plane(crtc->primary->fb, 0); bo = tegra_fb_get_plane(crtc->primary->fb, 0);
spin_lock_irqsave(&dc->lock, flags); spin_lock(&dc->lock);
/* check if new start address has been latched */ /* check if new start address has been latched */
tegra_dc_writel(dc, WINDOW_A_SELECT, DC_CMD_DISPLAY_WINDOW_HEADER); tegra_dc_writel(dc, WINDOW_A_SELECT, DC_CMD_DISPLAY_WINDOW_HEADER);
...@@ -846,7 +956,7 @@ static void tegra_dc_finish_page_flip(struct tegra_dc *dc) ...@@ -846,7 +956,7 @@ static void tegra_dc_finish_page_flip(struct tegra_dc *dc)
base = tegra_dc_readl(dc, DC_WINBUF_START_ADDR); base = tegra_dc_readl(dc, DC_WINBUF_START_ADDR);
tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS); tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS);
spin_unlock_irqrestore(&dc->lock, flags); spin_unlock(&dc->lock);
if (base == bo->paddr + crtc->primary->fb->offsets[0]) { if (base == bo->paddr + crtc->primary->fb->offsets[0]) {
drm_crtc_send_vblank_event(crtc, dc->event); drm_crtc_send_vblank_event(crtc, dc->event);
...@@ -874,64 +984,130 @@ void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file) ...@@ -874,64 +984,130 @@ void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file)
spin_unlock_irqrestore(&drm->event_lock, flags); spin_unlock_irqrestore(&drm->event_lock, flags);
} }
static int tegra_dc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, static void tegra_dc_destroy(struct drm_crtc *crtc)
struct drm_pending_vblank_event *event, uint32_t page_flip_flags)
{ {
unsigned int pipe = drm_crtc_index(crtc); drm_crtc_cleanup(crtc);
struct tegra_dc *dc = to_tegra_dc(crtc); }
if (dc->event)
return -EBUSY;
if (event) { static void tegra_crtc_reset(struct drm_crtc *crtc)
event->pipe = pipe; {
dc->event = event; struct tegra_dc_state *state;
drm_crtc_vblank_get(crtc);
}
tegra_dc_set_base(dc, 0, 0, fb); kfree(crtc->state);
crtc->primary->fb = fb; crtc->state = NULL;
return 0; state = kzalloc(sizeof(*state), GFP_KERNEL);
if (state)
crtc->state = &state->base;
} }
static void drm_crtc_clear(struct drm_crtc *crtc) static struct drm_crtc_state *
tegra_crtc_atomic_duplicate_state(struct drm_crtc *crtc)
{ {
memset(crtc, 0, sizeof(*crtc)); struct tegra_dc_state *state = to_dc_state(crtc->state);
struct tegra_dc_state *copy;
copy = kmemdup(state, sizeof(*state), GFP_KERNEL);
if (!copy)
return NULL;
copy->base.mode_changed = false;
copy->base.planes_changed = false;
copy->base.event = NULL;
return &copy->base;
} }
static void tegra_dc_destroy(struct drm_crtc *crtc) static void tegra_crtc_atomic_destroy_state(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{ {
drm_crtc_cleanup(crtc); kfree(state);
drm_crtc_clear(crtc);
} }
static const struct drm_crtc_funcs tegra_crtc_funcs = { static const struct drm_crtc_funcs tegra_crtc_funcs = {
.page_flip = tegra_dc_page_flip, .page_flip = drm_atomic_helper_page_flip,
.set_config = drm_crtc_helper_set_config, .set_config = drm_atomic_helper_set_config,
.destroy = tegra_dc_destroy, .destroy = tegra_dc_destroy,
.reset = tegra_crtc_reset,
.atomic_duplicate_state = tegra_crtc_atomic_duplicate_state,
.atomic_destroy_state = tegra_crtc_atomic_destroy_state,
}; };
static void tegra_dc_stop(struct tegra_dc *dc)
{
u32 value;
/* stop the display controller */
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
value &= ~DISP_CTRL_MODE_MASK;
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
tegra_dc_commit(dc);
}
static bool tegra_dc_idle(struct tegra_dc *dc)
{
u32 value;
value = tegra_dc_readl_active(dc, DC_CMD_DISPLAY_COMMAND);
return (value & DISP_CTRL_MODE_MASK) == 0;
}
static int tegra_dc_wait_idle(struct tegra_dc *dc, unsigned long timeout)
{
timeout = jiffies + msecs_to_jiffies(timeout);
while (time_before(jiffies, timeout)) {
if (tegra_dc_idle(dc))
return 0;
usleep_range(1000, 2000);
}
dev_dbg(dc->dev, "timeout waiting for DC to become idle\n");
return -ETIMEDOUT;
}
static void tegra_crtc_disable(struct drm_crtc *crtc) static void tegra_crtc_disable(struct drm_crtc *crtc)
{ {
struct tegra_dc *dc = to_tegra_dc(crtc); struct tegra_dc *dc = to_tegra_dc(crtc);
struct drm_device *drm = crtc->dev; u32 value;
struct drm_plane *plane;
drm_for_each_legacy_plane(plane, &drm->mode_config.plane_list) { if (!tegra_dc_idle(dc)) {
if (plane->crtc == crtc) { tegra_dc_stop(dc);
tegra_window_plane_disable(plane);
plane->crtc = NULL;
if (plane->fb) { /*
drm_framebuffer_unreference(plane->fb); * Ignore the return value, there isn't anything useful to do
plane->fb = NULL; * in case this fails.
} */
tegra_dc_wait_idle(dc, 100);
} }
/*
* This should really be part of the RGB encoder driver, but clearing
* these bits has the side-effect of stopping the display controller.
* When that happens no VBLANK interrupts will be raised. At the same
* time the encoder is disabled before the display controller, so the
* above code is always going to timeout waiting for the controller
* to go idle.
*
* Given the close coupling between the RGB encoder and the display
* controller doing it here is still kind of okay. None of the other
* encoder drivers require these bits to be cleared.
*
* XXX: Perhaps given that the display controller is switched off at
* this point anyway maybe clearing these bits isn't even useful for
* the RGB encoder?
*/
if (dc->rgb) {
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);
} }
drm_crtc_vblank_off(crtc); drm_crtc_vblank_off(crtc);
tegra_dc_commit(dc);
} }
static bool tegra_crtc_mode_fixup(struct drm_crtc *crtc, static bool tegra_crtc_mode_fixup(struct drm_crtc *crtc,
...@@ -971,33 +1147,15 @@ static int tegra_dc_set_timings(struct tegra_dc *dc, ...@@ -971,33 +1147,15 @@ static int tegra_dc_set_timings(struct tegra_dc *dc,
return 0; return 0;
} }
static int tegra_crtc_setup_clk(struct drm_crtc *crtc, int tegra_dc_setup_clock(struct tegra_dc *dc, struct clk *parent,
struct drm_display_mode *mode) unsigned long pclk, unsigned int div)
{ {
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; u32 value;
long err; int err;
list_for_each_entry(encoder, &crtc->dev->mode_config.encoder_list, head)
if (encoder->crtc == crtc) {
output = encoder_to_output(encoder);
break;
}
if (!output)
return -ENODEV;
/* err = clk_set_parent(dc->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 = tegra_output_setup_clock(output, dc->clk, pclk, &div);
if (err < 0) { if (err < 0) {
dev_err(dc->dev, "failed to setup clock: %ld\n", err); dev_err(dc->dev, "failed to set parent clock: %d\n", err);
return err; return err;
} }
...@@ -1009,26 +1167,69 @@ static int tegra_crtc_setup_clk(struct drm_crtc *crtc, ...@@ -1009,26 +1167,69 @@ static int tegra_crtc_setup_clk(struct drm_crtc *crtc,
return 0; return 0;
} }
static int tegra_crtc_mode_set(struct drm_crtc *crtc, int tegra_dc_state_setup_clock(struct tegra_dc *dc,
struct drm_display_mode *mode, struct drm_crtc_state *crtc_state,
struct drm_display_mode *adjusted, struct clk *clk, unsigned long pclk,
int x, int y, struct drm_framebuffer *old_fb) unsigned int div)
{
struct tegra_dc_state *state = to_dc_state(crtc_state);
state->clk = clk;
state->pclk = pclk;
state->div = div;
return 0;
}
static void tegra_dc_commit_state(struct tegra_dc *dc,
struct tegra_dc_state *state)
{ {
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;
u32 value; u32 value;
int err; int err;
err = tegra_crtc_setup_clk(crtc, mode); err = clk_set_parent(dc->clk, state->clk);
if (err) { if (err < 0)
dev_err(dc->dev, "failed to setup clock for CRTC: %d\n", err); dev_err(dc->dev, "failed to set parent clock: %d\n", err);
return err;
/*
* Outputs may not want to change the parent clock rate. This is only
* relevant to Tegra20 where only a single display PLL is available.
* Since that PLL would typically be used for HDMI, an internal LVDS
* panel would need to be driven by some other clock such as PLL_P
* which is shared with other peripherals. Changing the clock rate
* should therefore be avoided.
*/
if (state->pclk > 0) {
err = clk_set_rate(state->clk, state->pclk);
if (err < 0)
dev_err(dc->dev,
"failed to set clock rate to %lu Hz\n",
state->pclk);
} }
DRM_DEBUG_KMS("rate: %lu, div: %u\n", clk_get_rate(dc->clk),
state->div);
DRM_DEBUG_KMS("pclk: %lu\n", state->pclk);
value = SHIFT_CLK_DIVIDER(state->div) | PIXEL_CLK_DIVIDER_PCD1;
tegra_dc_writel(dc, value, DC_DISP_DISP_CLOCK_CONTROL);
}
static void tegra_crtc_mode_set_nofb(struct drm_crtc *crtc)
{
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
struct tegra_dc_state *state = to_dc_state(crtc->state);
struct tegra_dc *dc = to_tegra_dc(crtc);
u32 value;
tegra_dc_commit_state(dc, state);
/* program display mode */ /* program display mode */
tegra_dc_set_timings(dc, mode); tegra_dc_set_timings(dc, mode);
if (dc->soc->supports_border_color)
tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR);
/* interlacing isn't supported yet, so disable it */ /* interlacing isn't supported yet, so disable it */
if (dc->soc->supports_interlacing) { if (dc->soc->supports_interlacing) {
value = tegra_dc_readl(dc, DC_DISP_INTERLACE_CONTROL); value = tegra_dc_readl(dc, DC_DISP_INTERLACE_CONTROL);
...@@ -1036,35 +1237,17 @@ static int tegra_crtc_mode_set(struct drm_crtc *crtc, ...@@ -1036,35 +1237,17 @@ static int tegra_crtc_mode_set(struct drm_crtc *crtc,
tegra_dc_writel(dc, value, DC_DISP_INTERLACE_CONTROL); tegra_dc_writel(dc, value, DC_DISP_INTERLACE_CONTROL);
} }
/* setup window parameters */ value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
memset(&window, 0, sizeof(window)); value &= ~DISP_CTRL_MODE_MASK;
window.src.x = 0; value |= DISP_CTRL_MODE_C_DISPLAY;
window.src.y = 0; tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
window.src.w = mode->hdisplay;
window.src.h = mode->vdisplay;
window.dst.x = 0;
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.swap);
window.bits_per_pixel = crtc->primary->fb->bits_per_pixel;
window.stride[0] = crtc->primary->fb->pitches[0];
window.base[0] = bo->paddr;
err = tegra_dc_setup_window(dc, 0, &window);
if (err < 0)
dev_err(dc->dev, "failed to enable root plane\n");
return 0; 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);
static int tegra_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, tegra_dc_commit(dc);
struct drm_framebuffer *old_fb)
{
struct tegra_dc *dc = to_tegra_dc(crtc);
return tegra_dc_set_base(dc, x, y, crtc->primary->fb);
} }
static void tegra_crtc_prepare(struct drm_crtc *crtc) static void tegra_crtc_prepare(struct drm_crtc *crtc)
...@@ -1075,10 +1258,6 @@ static void tegra_crtc_prepare(struct drm_crtc *crtc) ...@@ -1075,10 +1258,6 @@ static void tegra_crtc_prepare(struct drm_crtc *crtc)
drm_crtc_vblank_off(crtc); drm_crtc_vblank_off(crtc);
/* hardware initialization */
reset_control_deassert(dc->rst);
usleep_range(10000, 20000);
if (dc->pipe) if (dc->pipe)
syncpt = SYNCPT_VBLANK1; syncpt = SYNCPT_VBLANK1;
else else
...@@ -1112,20 +1291,50 @@ static void tegra_crtc_prepare(struct drm_crtc *crtc) ...@@ -1112,20 +1291,50 @@ static void tegra_crtc_prepare(struct drm_crtc *crtc)
} }
static void tegra_crtc_commit(struct drm_crtc *crtc) static void tegra_crtc_commit(struct drm_crtc *crtc)
{
drm_crtc_vblank_on(crtc);
}
static int tegra_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
return 0;
}
static void tegra_crtc_atomic_begin(struct drm_crtc *crtc)
{ {
struct tegra_dc *dc = to_tegra_dc(crtc); struct tegra_dc *dc = to_tegra_dc(crtc);
drm_crtc_vblank_on(crtc); if (crtc->state->event) {
tegra_dc_commit(dc); crtc->state->event->pipe = drm_crtc_index(crtc);
WARN_ON(drm_crtc_vblank_get(crtc) != 0);
dc->event = crtc->state->event;
crtc->state->event = NULL;
}
}
static void tegra_crtc_atomic_flush(struct drm_crtc *crtc)
{
struct tegra_dc_state *state = to_dc_state(crtc->state);
struct tegra_dc *dc = to_tegra_dc(crtc);
tegra_dc_writel(dc, state->planes << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, state->planes, DC_CMD_STATE_CONTROL);
} }
static const struct drm_crtc_helper_funcs tegra_crtc_helper_funcs = { static const struct drm_crtc_helper_funcs tegra_crtc_helper_funcs = {
.disable = tegra_crtc_disable, .disable = tegra_crtc_disable,
.mode_fixup = tegra_crtc_mode_fixup, .mode_fixup = tegra_crtc_mode_fixup,
.mode_set = tegra_crtc_mode_set, .mode_set = drm_helper_crtc_mode_set,
.mode_set_base = tegra_crtc_mode_set_base, .mode_set_nofb = tegra_crtc_mode_set_nofb,
.mode_set_base = drm_helper_crtc_mode_set_base,
.prepare = tegra_crtc_prepare, .prepare = tegra_crtc_prepare,
.commit = tegra_crtc_commit, .commit = tegra_crtc_commit,
.atomic_check = tegra_crtc_atomic_check,
.atomic_begin = tegra_crtc_atomic_begin,
.atomic_flush = tegra_crtc_atomic_flush,
}; };
static irqreturn_t tegra_dc_irq(int irq, void *data) static irqreturn_t tegra_dc_irq(int irq, void *data)
...@@ -1571,6 +1780,7 @@ static const struct host1x_client_ops dc_client_ops = { ...@@ -1571,6 +1780,7 @@ static const struct host1x_client_ops dc_client_ops = {
}; };
static const struct tegra_dc_soc_info tegra20_dc_soc_info = { static const struct tegra_dc_soc_info tegra20_dc_soc_info = {
.supports_border_color = true,
.supports_interlacing = false, .supports_interlacing = false,
.supports_cursor = false, .supports_cursor = false,
.supports_block_linear = false, .supports_block_linear = false,
...@@ -1579,6 +1789,7 @@ static const struct tegra_dc_soc_info tegra20_dc_soc_info = { ...@@ -1579,6 +1789,7 @@ static const struct tegra_dc_soc_info tegra20_dc_soc_info = {
}; };
static const struct tegra_dc_soc_info tegra30_dc_soc_info = { static const struct tegra_dc_soc_info tegra30_dc_soc_info = {
.supports_border_color = true,
.supports_interlacing = false, .supports_interlacing = false,
.supports_cursor = false, .supports_cursor = false,
.supports_block_linear = false, .supports_block_linear = false,
...@@ -1587,6 +1798,7 @@ static const struct tegra_dc_soc_info tegra30_dc_soc_info = { ...@@ -1587,6 +1798,7 @@ static const struct tegra_dc_soc_info tegra30_dc_soc_info = {
}; };
static const struct tegra_dc_soc_info tegra114_dc_soc_info = { static const struct tegra_dc_soc_info tegra114_dc_soc_info = {
.supports_border_color = true,
.supports_interlacing = false, .supports_interlacing = false,
.supports_cursor = false, .supports_cursor = false,
.supports_block_linear = false, .supports_block_linear = false,
...@@ -1595,6 +1807,7 @@ static const struct tegra_dc_soc_info tegra114_dc_soc_info = { ...@@ -1595,6 +1807,7 @@ static const struct tegra_dc_soc_info tegra114_dc_soc_info = {
}; };
static const struct tegra_dc_soc_info tegra124_dc_soc_info = { static const struct tegra_dc_soc_info tegra124_dc_soc_info = {
.supports_border_color = false,
.supports_interlacing = true, .supports_interlacing = true,
.supports_cursor = true, .supports_cursor = true,
.supports_block_linear = true, .supports_block_linear = true,
......
...@@ -10,6 +10,9 @@ ...@@ -10,6 +10,9 @@
#include <linux/host1x.h> #include <linux/host1x.h>
#include <linux/iommu.h> #include <linux/iommu.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include "drm.h" #include "drm.h"
#include "gem.h" #include "gem.h"
...@@ -24,6 +27,92 @@ struct tegra_drm_file { ...@@ -24,6 +27,92 @@ struct tegra_drm_file {
struct list_head contexts; struct list_head contexts;
}; };
static void tegra_atomic_schedule(struct tegra_drm *tegra,
struct drm_atomic_state *state)
{
tegra->commit.state = state;
schedule_work(&tegra->commit.work);
}
static void tegra_atomic_complete(struct tegra_drm *tegra,
struct drm_atomic_state *state)
{
struct drm_device *drm = tegra->drm;
/*
* Everything below can be run asynchronously without the need to grab
* any modeset locks at all under one condition: It must be guaranteed
* that the asynchronous work has either been cancelled (if the driver
* supports it, which at least requires that the framebuffers get
* cleaned up with drm_atomic_helper_cleanup_planes()) or completed
* before the new state gets committed on the software side with
* drm_atomic_helper_swap_state().
*
* This scheme allows new atomic state updates to be prepared and
* checked in parallel to the asynchronous completion of the previous
* update. Which is important since compositors need to figure out the
* composition of the next frame right after having submitted the
* current layout.
*/
drm_atomic_helper_commit_pre_planes(drm, state);
drm_atomic_helper_commit_planes(drm, state);
drm_atomic_helper_commit_post_planes(drm, state);
drm_atomic_helper_wait_for_vblanks(drm, state);
drm_atomic_helper_cleanup_planes(drm, state);
drm_atomic_state_free(state);
}
static void tegra_atomic_work(struct work_struct *work)
{
struct tegra_drm *tegra = container_of(work, struct tegra_drm,
commit.work);
tegra_atomic_complete(tegra, tegra->commit.state);
}
static int tegra_atomic_commit(struct drm_device *drm,
struct drm_atomic_state *state, bool async)
{
struct tegra_drm *tegra = drm->dev_private;
int err;
err = drm_atomic_helper_prepare_planes(drm, state);
if (err)
return err;
/* serialize outstanding asynchronous commits */
mutex_lock(&tegra->commit.lock);
flush_work(&tegra->commit.work);
/*
* This is the point of no return - everything below never fails except
* when the hw goes bonghits. Which means we can commit the new state on
* the software side now.
*/
drm_atomic_helper_swap_state(drm, state);
if (async)
tegra_atomic_schedule(tegra, state);
else
tegra_atomic_complete(tegra, state);
mutex_unlock(&tegra->commit.lock);
return 0;
}
static const struct drm_mode_config_funcs tegra_drm_mode_funcs = {
.fb_create = tegra_fb_create,
#ifdef CONFIG_DRM_TEGRA_FBDEV
.output_poll_changed = tegra_fb_output_poll_changed,
#endif
.atomic_check = drm_atomic_helper_check,
.atomic_commit = tegra_atomic_commit,
};
static int tegra_drm_load(struct drm_device *drm, unsigned long flags) static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
{ {
struct host1x_device *device = to_host1x_device(drm->dev); struct host1x_device *device = to_host1x_device(drm->dev);
...@@ -36,8 +125,8 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) ...@@ -36,8 +125,8 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
if (iommu_present(&platform_bus_type)) { if (iommu_present(&platform_bus_type)) {
tegra->domain = iommu_domain_alloc(&platform_bus_type); tegra->domain = iommu_domain_alloc(&platform_bus_type);
if (IS_ERR(tegra->domain)) { if (!tegra->domain) {
err = PTR_ERR(tegra->domain); err = -ENOMEM;
goto free; goto free;
} }
...@@ -47,11 +136,23 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) ...@@ -47,11 +136,23 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
mutex_init(&tegra->clients_lock); mutex_init(&tegra->clients_lock);
INIT_LIST_HEAD(&tegra->clients); INIT_LIST_HEAD(&tegra->clients);
mutex_init(&tegra->commit.lock);
INIT_WORK(&tegra->commit.work, tegra_atomic_work);
drm->dev_private = tegra; drm->dev_private = tegra;
tegra->drm = drm; tegra->drm = drm;
drm_mode_config_init(drm); drm_mode_config_init(drm);
drm->mode_config.min_width = 0;
drm->mode_config.min_height = 0;
drm->mode_config.max_width = 4096;
drm->mode_config.max_height = 4096;
drm->mode_config.funcs = &tegra_drm_mode_funcs;
err = tegra_drm_fb_prepare(drm); err = tegra_drm_fb_prepare(drm);
if (err < 0) if (err < 0)
goto config; goto config;
...@@ -62,6 +163,8 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) ...@@ -62,6 +163,8 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
if (err < 0) if (err < 0)
goto fbdev; goto fbdev;
drm_mode_config_reset(drm);
/* /*
* We don't use the drm_irq_install() helpers provided by the DRM * We don't use the drm_irq_install() helpers provided by the DRM
* core, so we need to set this manually in order to allow the * core, so we need to set this manually in order to allow the
...@@ -106,8 +209,8 @@ static int tegra_drm_unload(struct drm_device *drm) ...@@ -106,8 +209,8 @@ static int tegra_drm_unload(struct drm_device *drm)
drm_kms_helper_poll_fini(drm); drm_kms_helper_poll_fini(drm);
tegra_drm_fb_exit(drm); tegra_drm_fb_exit(drm);
drm_vblank_cleanup(drm);
drm_mode_config_cleanup(drm); drm_mode_config_cleanup(drm);
drm_vblank_cleanup(drm);
err = host1x_device_exit(device); err = host1x_device_exit(device);
if (err < 0) if (err < 0)
...@@ -190,7 +293,7 @@ static int host1x_reloc_copy_from_user(struct host1x_reloc *dest, ...@@ -190,7 +293,7 @@ static int host1x_reloc_copy_from_user(struct host1x_reloc *dest,
if (err < 0) if (err < 0)
return err; return err;
err = get_user(dest->target.offset, &src->cmdbuf.offset); err = get_user(dest->target.offset, &src->target.offset);
if (err < 0) if (err < 0)
return err; return err;
...@@ -893,6 +996,30 @@ static int host1x_drm_remove(struct host1x_device *dev) ...@@ -893,6 +996,30 @@ static int host1x_drm_remove(struct host1x_device *dev)
return 0; return 0;
} }
#ifdef CONFIG_PM_SLEEP
static int host1x_drm_suspend(struct device *dev)
{
struct drm_device *drm = dev_get_drvdata(dev);
drm_kms_helper_poll_disable(drm);
return 0;
}
static int host1x_drm_resume(struct device *dev)
{
struct drm_device *drm = dev_get_drvdata(dev);
drm_kms_helper_poll_enable(drm);
return 0;
}
#endif
static const struct dev_pm_ops host1x_drm_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(host1x_drm_suspend, host1x_drm_resume)
};
static const struct of_device_id host1x_drm_subdevs[] = { static const struct of_device_id host1x_drm_subdevs[] = {
{ .compatible = "nvidia,tegra20-dc", }, { .compatible = "nvidia,tegra20-dc", },
{ .compatible = "nvidia,tegra20-hdmi", }, { .compatible = "nvidia,tegra20-hdmi", },
...@@ -912,7 +1039,10 @@ static const struct of_device_id host1x_drm_subdevs[] = { ...@@ -912,7 +1039,10 @@ static const struct of_device_id host1x_drm_subdevs[] = {
}; };
static struct host1x_driver host1x_drm_driver = { static struct host1x_driver host1x_drm_driver = {
.driver = {
.name = "drm", .name = "drm",
.pm = &host1x_drm_pm_ops,
},
.probe = host1x_drm_probe, .probe = host1x_drm_probe,
.remove = host1x_drm_remove, .remove = host1x_drm_remove,
.subdevs = host1x_drm_subdevs, .subdevs = host1x_drm_subdevs,
......
...@@ -50,6 +50,12 @@ struct tegra_drm { ...@@ -50,6 +50,12 @@ struct tegra_drm {
#endif #endif
unsigned int pitch_align; unsigned int pitch_align;
struct {
struct drm_atomic_state *state;
struct work_struct work;
struct mutex lock;
} commit;
}; };
struct tegra_drm_client; struct tegra_drm_client;
...@@ -164,45 +170,31 @@ struct tegra_dc_window { ...@@ -164,45 +170,31 @@ struct tegra_dc_window {
unsigned int h; unsigned int h;
} dst; } dst;
unsigned int bits_per_pixel; unsigned int bits_per_pixel;
unsigned int format;
unsigned int swap;
unsigned int stride[2]; unsigned int stride[2];
unsigned long base[3]; unsigned long base[3];
bool bottom_up; bool bottom_up;
struct tegra_bo_tiling tiling; struct tegra_bo_tiling tiling;
u32 format;
u32 swap;
}; };
/* from dc.c */ /* from dc.c */
void tegra_dc_enable_vblank(struct tegra_dc *dc); void tegra_dc_enable_vblank(struct tegra_dc *dc);
void tegra_dc_disable_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); void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file);
void tegra_dc_commit(struct tegra_dc *dc);
struct tegra_output_ops { int tegra_dc_setup_clock(struct tegra_dc *dc, struct clk *parent,
int (*enable)(struct tegra_output *output); unsigned long pclk, unsigned int div);
int (*disable)(struct tegra_output *output); int tegra_dc_state_setup_clock(struct tegra_dc *dc,
int (*setup_clock)(struct tegra_output *output, struct clk *clk, struct drm_crtc_state *crtc_state,
unsigned long pclk, unsigned int *div); struct clk *clk, unsigned long pclk,
int (*check_mode)(struct tegra_output *output, unsigned int div);
struct drm_display_mode *mode,
enum drm_mode_status *status);
enum drm_connector_status (*detect)(struct tegra_output *output);
};
enum tegra_output_type {
TEGRA_OUTPUT_RGB,
TEGRA_OUTPUT_HDMI,
TEGRA_OUTPUT_DSI,
TEGRA_OUTPUT_EDP,
};
struct tegra_output { struct tegra_output {
struct device_node *of_node; struct device_node *of_node;
struct device *dev; struct device *dev;
const struct tegra_output_ops *ops;
enum tegra_output_type type;
struct drm_panel *panel; struct drm_panel *panel;
struct i2c_adapter *ddc; struct i2c_adapter *ddc;
const struct edid *edid; const struct edid *edid;
...@@ -223,42 +215,6 @@ static inline struct tegra_output *connector_to_output(struct drm_connector *c) ...@@ -223,42 +215,6 @@ static inline struct tegra_output *connector_to_output(struct drm_connector *c)
return container_of(c, struct tegra_output, connector); return container_of(c, struct tegra_output, connector);
} }
static inline int tegra_output_enable(struct tegra_output *output)
{
if (output && output->ops && output->ops->enable)
return output->ops->enable(output);
return output ? -ENOSYS : -EINVAL;
}
static inline int tegra_output_disable(struct tegra_output *output)
{
if (output && output->ops && output->ops->disable)
return output->ops->disable(output);
return output ? -ENOSYS : -EINVAL;
}
static inline int tegra_output_setup_clock(struct tegra_output *output,
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, div);
return output ? -ENOSYS : -EINVAL;
}
static inline int tegra_output_check_mode(struct tegra_output *output,
struct drm_display_mode *mode,
enum drm_mode_status *status)
{
if (output && output->ops && output->ops->check_mode)
return output->ops->check_mode(output, mode, status);
return output ? -ENOSYS : -EINVAL;
}
/* from rgb.c */ /* from rgb.c */
int tegra_dc_rgb_probe(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_remove(struct tegra_dc *dc);
...@@ -267,9 +223,18 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc); ...@@ -267,9 +223,18 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc);
/* from output.c */ /* from output.c */
int tegra_output_probe(struct tegra_output *output); int tegra_output_probe(struct tegra_output *output);
int tegra_output_remove(struct tegra_output *output); void tegra_output_remove(struct tegra_output *output);
int tegra_output_init(struct drm_device *drm, struct tegra_output *output); int tegra_output_init(struct drm_device *drm, struct tegra_output *output);
int tegra_output_exit(struct tegra_output *output); void tegra_output_exit(struct tegra_output *output);
int tegra_output_connector_get_modes(struct drm_connector *connector);
struct drm_encoder *
tegra_output_connector_best_encoder(struct drm_connector *connector);
enum drm_connector_status
tegra_output_connector_detect(struct drm_connector *connector, bool force);
void tegra_output_connector_destroy(struct drm_connector *connector);
void tegra_output_encoder_destroy(struct drm_encoder *encoder);
/* from dpaux.c */ /* from dpaux.c */
struct tegra_dpaux; struct tegra_dpaux;
...@@ -291,12 +256,16 @@ struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer, ...@@ -291,12 +256,16 @@ struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer,
bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer); bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer);
int tegra_fb_get_tiling(struct drm_framebuffer *framebuffer, int tegra_fb_get_tiling(struct drm_framebuffer *framebuffer,
struct tegra_bo_tiling *tiling); struct tegra_bo_tiling *tiling);
struct drm_framebuffer *tegra_fb_create(struct drm_device *drm,
struct drm_file *file,
struct drm_mode_fb_cmd2 *cmd);
int tegra_drm_fb_prepare(struct drm_device *drm); int tegra_drm_fb_prepare(struct drm_device *drm);
void tegra_drm_fb_free(struct drm_device *drm); void tegra_drm_fb_free(struct drm_device *drm);
int tegra_drm_fb_init(struct drm_device *drm); int tegra_drm_fb_init(struct drm_device *drm);
void tegra_drm_fb_exit(struct drm_device *drm); void tegra_drm_fb_exit(struct drm_device *drm);
#ifdef CONFIG_DRM_TEGRA_FBDEV #ifdef CONFIG_DRM_TEGRA_FBDEV
void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev); void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev);
void tegra_fb_output_poll_changed(struct drm_device *drm);
#endif #endif
extern struct platform_driver tegra_dc_driver; extern struct platform_driver tegra_dc_driver;
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_mipi_dsi.h> #include <drm/drm_mipi_dsi.h>
#include <drm/drm_panel.h> #include <drm/drm_panel.h>
...@@ -27,6 +28,28 @@ ...@@ -27,6 +28,28 @@
#include "dsi.h" #include "dsi.h"
#include "mipi-phy.h" #include "mipi-phy.h"
struct tegra_dsi_state {
struct drm_connector_state base;
struct mipi_dphy_timing timing;
unsigned long period;
unsigned int vrefresh;
unsigned int lanes;
unsigned long pclk;
unsigned long bclk;
enum tegra_dsi_format format;
unsigned int mul;
unsigned int div;
};
static inline struct tegra_dsi_state *
to_dsi_state(struct drm_connector_state *state)
{
return container_of(state, struct tegra_dsi_state, base);
}
struct tegra_dsi { struct tegra_dsi {
struct host1x_client client; struct host1x_client client;
struct tegra_output output; struct tegra_output output;
...@@ -51,7 +74,6 @@ struct tegra_dsi { ...@@ -51,7 +74,6 @@ struct tegra_dsi {
struct mipi_dsi_host host; struct mipi_dsi_host host;
struct regulator *vdd; struct regulator *vdd;
bool enabled;
unsigned int video_fifo_depth; unsigned int video_fifo_depth;
unsigned int host_fifo_depth; unsigned int host_fifo_depth;
...@@ -77,13 +99,17 @@ static inline struct tegra_dsi *to_dsi(struct tegra_output *output) ...@@ -77,13 +99,17 @@ static inline struct tegra_dsi *to_dsi(struct tegra_output *output)
return container_of(output, struct tegra_dsi, output); return container_of(output, struct tegra_dsi, output);
} }
static inline unsigned long tegra_dsi_readl(struct tegra_dsi *dsi, static struct tegra_dsi_state *tegra_dsi_get_state(struct tegra_dsi *dsi)
unsigned long reg) {
return to_dsi_state(dsi->output.connector.state);
}
static inline u32 tegra_dsi_readl(struct tegra_dsi *dsi, unsigned long reg)
{ {
return readl(dsi->regs + (reg << 2)); return readl(dsi->regs + (reg << 2));
} }
static inline void tegra_dsi_writel(struct tegra_dsi *dsi, unsigned long value, static inline void tegra_dsi_writel(struct tegra_dsi *dsi, u32 value,
unsigned long reg) unsigned long reg)
{ {
writel(value, dsi->regs + (reg << 2)); writel(value, dsi->regs + (reg << 2));
...@@ -95,7 +121,7 @@ static int tegra_dsi_show_regs(struct seq_file *s, void *data) ...@@ -95,7 +121,7 @@ static int tegra_dsi_show_regs(struct seq_file *s, void *data)
struct tegra_dsi *dsi = node->info_ent->data; struct tegra_dsi *dsi = node->info_ent->data;
#define DUMP_REG(name) \ #define DUMP_REG(name) \
seq_printf(s, "%-32s %#05x %08lx\n", #name, name, \ seq_printf(s, "%-32s %#05x %08x\n", #name, name, \
tegra_dsi_readl(dsi, name)) tegra_dsi_readl(dsi, name))
DUMP_REG(DSI_INCR_SYNCPT); DUMP_REG(DSI_INCR_SYNCPT);
...@@ -230,7 +256,7 @@ static int tegra_dsi_debugfs_init(struct tegra_dsi *dsi, ...@@ -230,7 +256,7 @@ static int tegra_dsi_debugfs_init(struct tegra_dsi *dsi,
return err; return err;
} }
static int tegra_dsi_debugfs_exit(struct tegra_dsi *dsi) static void tegra_dsi_debugfs_exit(struct tegra_dsi *dsi)
{ {
drm_debugfs_remove_files(dsi->debugfs_files, ARRAY_SIZE(debugfs_files), drm_debugfs_remove_files(dsi->debugfs_files, ARRAY_SIZE(debugfs_files),
dsi->minor); dsi->minor);
...@@ -241,8 +267,6 @@ static int tegra_dsi_debugfs_exit(struct tegra_dsi *dsi) ...@@ -241,8 +267,6 @@ static int tegra_dsi_debugfs_exit(struct tegra_dsi *dsi)
debugfs_remove(dsi->debugfs); debugfs_remove(dsi->debugfs);
dsi->debugfs = NULL; dsi->debugfs = NULL;
return 0;
} }
#define PKT_ID0(id) ((((id) & 0x3f) << 3) | (1 << 9)) #define PKT_ID0(id) ((((id) & 0x3f) << 3) | (1 << 9))
...@@ -338,61 +362,36 @@ static const u32 pkt_seq_command_mode[NUM_PKT_SEQ] = { ...@@ -338,61 +362,36 @@ static const u32 pkt_seq_command_mode[NUM_PKT_SEQ] = {
[11] = 0, [11] = 0,
}; };
static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi) static void tegra_dsi_set_phy_timing(struct tegra_dsi *dsi,
unsigned long period,
const struct mipi_dphy_timing *timing)
{ {
struct mipi_dphy_timing timing; u32 value;
unsigned long value, period;
long rate;
int err;
rate = clk_get_rate(dsi->clk);
if (rate < 0)
return rate;
period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, rate * 2);
err = mipi_dphy_timing_get_default(&timing, period);
if (err < 0)
return err;
err = mipi_dphy_timing_validate(&timing, period);
if (err < 0) {
dev_err(dsi->dev, "failed to validate D-PHY timing: %d\n", err);
return err;
}
/*
* The D-PHY timing fields below are expressed in byte-clock cycles,
* so multiply the period by 8.
*/
period *= 8;
value = DSI_TIMING_FIELD(timing.hsexit, period, 1) << 24 | value = DSI_TIMING_FIELD(timing->hsexit, period, 1) << 24 |
DSI_TIMING_FIELD(timing.hstrail, period, 0) << 16 | DSI_TIMING_FIELD(timing->hstrail, period, 0) << 16 |
DSI_TIMING_FIELD(timing.hszero, period, 3) << 8 | DSI_TIMING_FIELD(timing->hszero, period, 3) << 8 |
DSI_TIMING_FIELD(timing.hsprepare, period, 1); DSI_TIMING_FIELD(timing->hsprepare, period, 1);
tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_0); tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_0);
value = DSI_TIMING_FIELD(timing.clktrail, period, 1) << 24 | value = DSI_TIMING_FIELD(timing->clktrail, period, 1) << 24 |
DSI_TIMING_FIELD(timing.clkpost, period, 1) << 16 | DSI_TIMING_FIELD(timing->clkpost, period, 1) << 16 |
DSI_TIMING_FIELD(timing.clkzero, period, 1) << 8 | DSI_TIMING_FIELD(timing->clkzero, period, 1) << 8 |
DSI_TIMING_FIELD(timing.lpx, period, 1); DSI_TIMING_FIELD(timing->lpx, period, 1);
tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_1); tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_1);
value = DSI_TIMING_FIELD(timing.clkprepare, period, 1) << 16 | value = DSI_TIMING_FIELD(timing->clkprepare, period, 1) << 16 |
DSI_TIMING_FIELD(timing.clkpre, period, 1) << 8 | DSI_TIMING_FIELD(timing->clkpre, period, 1) << 8 |
DSI_TIMING_FIELD(0xff * period, period, 0) << 0; DSI_TIMING_FIELD(0xff * period, period, 0) << 0;
tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_2); tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_2);
value = DSI_TIMING_FIELD(timing.taget, period, 1) << 16 | value = DSI_TIMING_FIELD(timing->taget, period, 1) << 16 |
DSI_TIMING_FIELD(timing.tasure, period, 1) << 8 | DSI_TIMING_FIELD(timing->tasure, period, 1) << 8 |
DSI_TIMING_FIELD(timing.tago, period, 1); DSI_TIMING_FIELD(timing->tago, period, 1);
tegra_dsi_writel(dsi, value, DSI_BTA_TIMING); tegra_dsi_writel(dsi, value, DSI_BTA_TIMING);
if (dsi->slave) if (dsi->slave)
return tegra_dsi_set_phy_timing(dsi->slave); tegra_dsi_set_phy_timing(dsi->slave, period, timing);
return 0;
} }
static int tegra_dsi_get_muldiv(enum mipi_dsi_pixel_format format, static int tegra_dsi_get_muldiv(enum mipi_dsi_pixel_format format,
...@@ -484,14 +483,22 @@ static unsigned int tegra_dsi_get_lanes(struct tegra_dsi *dsi) ...@@ -484,14 +483,22 @@ static unsigned int tegra_dsi_get_lanes(struct tegra_dsi *dsi)
return dsi->lanes; return dsi->lanes;
} }
static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, static void tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
const struct drm_display_mode *mode) const struct drm_display_mode *mode)
{ {
unsigned int hact, hsw, hbp, hfp, i, mul, div; unsigned int hact, hsw, hbp, hfp, i, mul, div;
enum tegra_dsi_format format; struct tegra_dsi_state *state;
const u32 *pkt_seq; const u32 *pkt_seq;
u32 value; u32 value;
int err;
/* XXX: pass in state into this function? */
if (dsi->master)
state = tegra_dsi_get_state(dsi->master);
else
state = tegra_dsi_get_state(dsi);
mul = state->mul;
div = state->div;
if (dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { if (dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
DRM_DEBUG_KMS("Non-burst video mode with sync pulses\n"); DRM_DEBUG_KMS("Non-burst video mode with sync pulses\n");
...@@ -504,15 +511,8 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, ...@@ -504,15 +511,8 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
pkt_seq = pkt_seq_command_mode; pkt_seq = pkt_seq_command_mode;
} }
err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); value = DSI_CONTROL_CHANNEL(0) |
if (err < 0) DSI_CONTROL_FORMAT(state->format) |
return err;
err = tegra_dsi_get_format(dsi->format, &format);
if (err < 0)
return err;
value = DSI_CONTROL_CHANNEL(0) | DSI_CONTROL_FORMAT(format) |
DSI_CONTROL_LANES(dsi->lanes - 1) | DSI_CONTROL_LANES(dsi->lanes - 1) |
DSI_CONTROL_SOURCE(pipe); DSI_CONTROL_SOURCE(pipe);
tegra_dsi_writel(dsi, value, DSI_CONTROL); tegra_dsi_writel(dsi, value, DSI_CONTROL);
...@@ -591,8 +591,8 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, ...@@ -591,8 +591,8 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
/* set SOL delay */ /* set SOL delay */
if (dsi->master || dsi->slave) { if (dsi->master || dsi->slave) {
unsigned int lanes = tegra_dsi_get_lanes(dsi);
unsigned long delay, bclk, bclk_ganged; unsigned long delay, bclk, bclk_ganged;
unsigned int lanes = state->lanes;
/* SOL to valid, valid to FIFO and FIFO write delay */ /* SOL to valid, valid to FIFO and FIFO write delay */
delay = 4 + 4 + 2; delay = 4 + 4 + 2;
...@@ -612,9 +612,7 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, ...@@ -612,9 +612,7 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
} }
if (dsi->slave) { if (dsi->slave) {
err = tegra_dsi_configure(dsi->slave, pipe, mode); tegra_dsi_configure(dsi->slave, pipe, mode);
if (err < 0)
return err;
/* /*
* TODO: Support modes other than symmetrical left-right * TODO: Support modes other than symmetrical left-right
...@@ -624,49 +622,6 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, ...@@ -624,49 +622,6 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
tegra_dsi_ganged_enable(dsi->slave, mode->hdisplay / 2, tegra_dsi_ganged_enable(dsi->slave, mode->hdisplay / 2,
mode->hdisplay / 2); mode->hdisplay / 2);
} }
return 0;
}
static int tegra_output_dsi_enable(struct tegra_output *output)
{
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
const struct drm_display_mode *mode = &dc->base.mode;
struct tegra_dsi *dsi = to_dsi(output);
u32 value;
int err;
if (dsi->enabled)
return 0;
err = tegra_dsi_configure(dsi, dc->pipe, mode);
if (err < 0)
return err;
/* enable display controller */
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
value |= DSI_ENABLE;
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
value &= ~DISP_CTRL_MODE_MASK;
value |= DISP_CTRL_MODE_C_DISPLAY;
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL);
value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE;
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
/* enable DSI controller */
tegra_dsi_enable(dsi);
dsi->enabled = true;
return 0;
} }
static int tegra_dsi_wait_idle(struct tegra_dsi *dsi, unsigned long timeout) static int tegra_dsi_wait_idle(struct tegra_dsi *dsi, unsigned long timeout)
...@@ -705,6 +660,29 @@ static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi) ...@@ -705,6 +660,29 @@ static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi)
tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL); tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL);
} }
static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk,
unsigned int vrefresh)
{
unsigned int timeout;
u32 value;
/* one frame high-speed transmission timeout */
timeout = (bclk / vrefresh) / 512;
value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout);
tegra_dsi_writel(dsi, value, DSI_TIMEOUT_0);
/* 2 ms peripheral timeout for panel */
timeout = 2 * bclk / 512 * 1000;
value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000);
tegra_dsi_writel(dsi, value, DSI_TIMEOUT_1);
value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0);
tegra_dsi_writel(dsi, value, DSI_TO_TALLY);
if (dsi->slave)
tegra_dsi_set_timeout(dsi->slave, bclk, vrefresh);
}
static void tegra_dsi_disable(struct tegra_dsi *dsi) static void tegra_dsi_disable(struct tegra_dsi *dsi)
{ {
u32 value; u32 value;
...@@ -724,15 +702,149 @@ static void tegra_dsi_disable(struct tegra_dsi *dsi) ...@@ -724,15 +702,149 @@ static void tegra_dsi_disable(struct tegra_dsi *dsi)
usleep_range(5000, 10000); usleep_range(5000, 10000);
} }
static int tegra_output_dsi_disable(struct tegra_output *output) static void tegra_dsi_soft_reset(struct tegra_dsi *dsi)
{
u32 value;
value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
value &= ~DSI_POWER_CONTROL_ENABLE;
tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
usleep_range(300, 1000);
value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
value |= DSI_POWER_CONTROL_ENABLE;
tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
usleep_range(300, 1000);
value = tegra_dsi_readl(dsi, DSI_TRIGGER);
if (value)
tegra_dsi_writel(dsi, 0, DSI_TRIGGER);
if (dsi->slave)
tegra_dsi_soft_reset(dsi->slave);
}
static void tegra_dsi_connector_dpms(struct drm_connector *connector, int mode)
{
}
static void tegra_dsi_connector_reset(struct drm_connector *connector)
{
struct tegra_dsi_state *state;
kfree(connector->state);
connector->state = NULL;
state = kzalloc(sizeof(*state), GFP_KERNEL);
if (state)
connector->state = &state->base;
}
static struct drm_connector_state *
tegra_dsi_connector_duplicate_state(struct drm_connector *connector)
{
struct tegra_dsi_state *state = to_dsi_state(connector->state);
struct tegra_dsi_state *copy;
copy = kmemdup(state, sizeof(*state), GFP_KERNEL);
if (!copy)
return NULL;
return &copy->base;
}
static const struct drm_connector_funcs tegra_dsi_connector_funcs = {
.dpms = tegra_dsi_connector_dpms,
.reset = tegra_dsi_connector_reset,
.detect = tegra_output_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = tegra_output_connector_destroy,
.atomic_duplicate_state = tegra_dsi_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static enum drm_mode_status
tegra_dsi_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
return MODE_OK;
}
static const struct drm_connector_helper_funcs tegra_dsi_connector_helper_funcs = {
.get_modes = tegra_output_connector_get_modes,
.mode_valid = tegra_dsi_connector_mode_valid,
.best_encoder = tegra_output_connector_best_encoder,
};
static const struct drm_encoder_funcs tegra_dsi_encoder_funcs = {
.destroy = tegra_output_encoder_destroy,
};
static void tegra_dsi_encoder_dpms(struct drm_encoder *encoder, int mode)
{
}
static void tegra_dsi_encoder_prepare(struct drm_encoder *encoder)
{
}
static void tegra_dsi_encoder_commit(struct drm_encoder *encoder)
{ {
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); }
static void tegra_dsi_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted)
{
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
struct tegra_dsi *dsi = to_dsi(output);
struct tegra_dsi_state *state;
u32 value;
state = tegra_dsi_get_state(dsi);
tegra_dsi_set_timeout(dsi, state->bclk, state->vrefresh);
/*
* The D-PHY timing fields are expressed in byte-clock cycles, so
* multiply the period by 8.
*/
tegra_dsi_set_phy_timing(dsi, state->period * 8, &state->timing);
if (output->panel)
drm_panel_prepare(output->panel);
tegra_dsi_configure(dsi, dc->pipe, mode);
/* enable display controller */
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
value |= DSI_ENABLE;
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
tegra_dc_commit(dc);
/* enable DSI controller */
tegra_dsi_enable(dsi);
if (output->panel)
drm_panel_enable(output->panel);
return;
}
static void tegra_dsi_encoder_disable(struct drm_encoder *encoder)
{
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
struct tegra_dsi *dsi = to_dsi(output); struct tegra_dsi *dsi = to_dsi(output);
unsigned long value; u32 value;
int err; int err;
if (!dsi->enabled) if (output->panel)
return 0; drm_panel_disable(output->panel);
tegra_dsi_video_disable(dsi); tegra_dsi_video_disable(dsi);
...@@ -741,85 +853,78 @@ static int tegra_output_dsi_disable(struct tegra_output *output) ...@@ -741,85 +853,78 @@ static int tegra_output_dsi_disable(struct tegra_output *output)
* sure it's only executed when the output is attached to one. * sure it's only executed when the output is attached to one.
*/ */
if (dc) { if (dc) {
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;
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
value &= ~DSI_ENABLE; value &= ~DSI_ENABLE;
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); tegra_dc_commit(dc);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
} }
err = tegra_dsi_wait_idle(dsi, 100); err = tegra_dsi_wait_idle(dsi, 100);
if (err < 0) if (err < 0)
dev_dbg(dsi->dev, "failed to idle DSI: %d\n", err); dev_dbg(dsi->dev, "failed to idle DSI: %d\n", err);
tegra_dsi_disable(dsi); tegra_dsi_soft_reset(dsi);
dsi->enabled = false;
return 0;
}
static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk,
unsigned int vrefresh)
{
unsigned int timeout;
u32 value;
/* one frame high-speed transmission timeout */
timeout = (bclk / vrefresh) / 512;
value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout);
tegra_dsi_writel(dsi, value, DSI_TIMEOUT_0);
/* 2 ms peripheral timeout for panel */ if (output->panel)
timeout = 2 * bclk / 512 * 1000; drm_panel_unprepare(output->panel);
value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000);
tegra_dsi_writel(dsi, value, DSI_TIMEOUT_1);
value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0); tegra_dsi_disable(dsi);
tegra_dsi_writel(dsi, value, DSI_TO_TALLY);
if (dsi->slave) return;
tegra_dsi_set_timeout(dsi->slave, bclk, vrefresh);
} }
static int tegra_output_dsi_setup_clock(struct tegra_output *output, static int
struct clk *clk, unsigned long pclk, tegra_dsi_encoder_atomic_check(struct drm_encoder *encoder,
unsigned int *divp) struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{ {
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct tegra_output *output = encoder_to_output(encoder);
struct drm_display_mode *mode = &dc->base.mode; struct tegra_dsi_state *state = to_dsi_state(conn_state);
struct tegra_dc *dc = to_tegra_dc(conn_state->crtc);
struct tegra_dsi *dsi = to_dsi(output); struct tegra_dsi *dsi = to_dsi(output);
unsigned int mul, div, vrefresh, lanes; unsigned int scdiv;
unsigned long bclk, plld; unsigned long plld;
int err; int err;
lanes = tegra_dsi_get_lanes(dsi); state->pclk = crtc_state->mode.clock * 1000;
err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); err = tegra_dsi_get_muldiv(dsi->format, &state->mul, &state->div);
if (err < 0) if (err < 0)
return err; return err;
DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", mul, div, lanes); state->lanes = tegra_dsi_get_lanes(dsi);
vrefresh = drm_mode_vrefresh(mode);
DRM_DEBUG_KMS("vrefresh: %u\n", vrefresh); err = tegra_dsi_get_format(dsi->format, &state->format);
if (err < 0)
return err;
state->vrefresh = drm_mode_vrefresh(&crtc_state->mode);
/* compute byte clock */ /* compute byte clock */
bclk = (pclk * mul) / (div * lanes); state->bclk = (state->pclk * state->mul) / (state->div * state->lanes);
DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", state->mul, state->div,
state->lanes);
DRM_DEBUG_KMS("format: %u, vrefresh: %u\n", state->format,
state->vrefresh);
DRM_DEBUG_KMS("bclk: %lu\n", state->bclk);
/* /*
* Compute bit clock and round up to the next MHz. * Compute bit clock and round up to the next MHz.
*/ */
plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC) * USEC_PER_SEC; plld = DIV_ROUND_UP(state->bclk * 8, USEC_PER_SEC) * USEC_PER_SEC;
state->period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, plld);
err = mipi_dphy_timing_get_default(&state->timing, state->period);
if (err < 0)
return err;
err = mipi_dphy_timing_validate(&state->timing, state->period);
if (err < 0) {
dev_err(dsi->dev, "failed to validate D-PHY timing: %d\n", err);
return err;
}
/* /*
* We divide the frequency by two here, but we make up for that by * We divide the frequency by two here, but we make up for that by
...@@ -828,19 +933,6 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, ...@@ -828,19 +933,6 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output,
*/ */
plld /= 2; plld /= 2;
err = clk_set_parent(clk, dsi->clk_parent);
if (err < 0) {
dev_err(dsi->dev, "failed to set parent clock: %d\n", err);
return err;
}
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",
plld);
return err;
}
/* /*
* Derive pixel clock from bit clock using the shift clock divider. * 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 * Note that this is only half of what we would expect, but we need
...@@ -851,44 +943,30 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, ...@@ -851,44 +943,30 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output,
* not working properly otherwise. Perhaps the PLLs cannot generate * not working properly otherwise. Perhaps the PLLs cannot generate
* frequencies sufficiently high. * frequencies sufficiently high.
*/ */
*divp = ((8 * mul) / (div * lanes)) - 2; scdiv = ((8 * state->mul) / (state->div * state->lanes)) - 2;
/* err = tegra_dc_state_setup_clock(dc, crtc_state, dsi->clk_parent,
* XXX: Move the below somewhere else so that we don't need to have plld, scdiv);
* access to the vrefresh in this function? if (err < 0) {
*/ dev_err(output->dev, "failed to setup CRTC state: %d\n", err);
tegra_dsi_set_timeout(dsi, bclk, vrefresh);
err = tegra_dsi_set_phy_timing(dsi);
if (err < 0)
return err; return err;
}
return 0; return err;
}
static int tegra_output_dsi_check_mode(struct tegra_output *output,
struct drm_display_mode *mode,
enum drm_mode_status *status)
{
/*
* FIXME: For now, always assume that the mode is okay.
*/
*status = MODE_OK;
return 0;
} }
static const struct tegra_output_ops dsi_ops = { static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = {
.enable = tegra_output_dsi_enable, .dpms = tegra_dsi_encoder_dpms,
.disable = tegra_output_dsi_disable, .prepare = tegra_dsi_encoder_prepare,
.setup_clock = tegra_output_dsi_setup_clock, .commit = tegra_dsi_encoder_commit,
.check_mode = tegra_output_dsi_check_mode, .mode_set = tegra_dsi_encoder_mode_set,
.disable = tegra_dsi_encoder_disable,
.atomic_check = tegra_dsi_encoder_atomic_check,
}; };
static int tegra_dsi_pad_enable(struct tegra_dsi *dsi) static int tegra_dsi_pad_enable(struct tegra_dsi *dsi)
{ {
unsigned long value; u32 value;
value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0); value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0);
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0); tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0);
...@@ -923,17 +1001,44 @@ static int tegra_dsi_init(struct host1x_client *client) ...@@ -923,17 +1001,44 @@ static int tegra_dsi_init(struct host1x_client *client)
struct tegra_dsi *dsi = host1x_client_to_dsi(client); struct tegra_dsi *dsi = host1x_client_to_dsi(client);
int err; int err;
reset_control_deassert(dsi->rst);
err = tegra_dsi_pad_calibrate(dsi);
if (err < 0) {
dev_err(dsi->dev, "MIPI calibration failed: %d\n", err);
goto reset;
}
/* Gangsters must not register their own outputs. */ /* Gangsters must not register their own outputs. */
if (!dsi->master) { if (!dsi->master) {
dsi->output.type = TEGRA_OUTPUT_DSI;
dsi->output.dev = client->dev; dsi->output.dev = client->dev;
dsi->output.ops = &dsi_ops;
drm_connector_init(drm, &dsi->output.connector,
&tegra_dsi_connector_funcs,
DRM_MODE_CONNECTOR_DSI);
drm_connector_helper_add(&dsi->output.connector,
&tegra_dsi_connector_helper_funcs);
dsi->output.connector.dpms = DRM_MODE_DPMS_OFF;
drm_encoder_init(drm, &dsi->output.encoder,
&tegra_dsi_encoder_funcs,
DRM_MODE_ENCODER_DSI);
drm_encoder_helper_add(&dsi->output.encoder,
&tegra_dsi_encoder_helper_funcs);
drm_mode_connector_attach_encoder(&dsi->output.connector,
&dsi->output.encoder);
drm_connector_register(&dsi->output.connector);
err = tegra_output_init(drm, &dsi->output); err = tegra_output_init(drm, &dsi->output);
if (err < 0) { if (err < 0) {
dev_err(client->dev, "output setup failed: %d\n", err); dev_err(client->dev,
return err; "failed to initialize output: %d\n",
err);
goto reset;
} }
dsi->output.encoder.possible_crtcs = 0x3;
} }
if (IS_ENABLED(CONFIG_DEBUG_FS)) { if (IS_ENABLED(CONFIG_DEBUG_FS)) {
...@@ -943,34 +1048,22 @@ static int tegra_dsi_init(struct host1x_client *client) ...@@ -943,34 +1048,22 @@ static int tegra_dsi_init(struct host1x_client *client)
} }
return 0; return 0;
reset:
reset_control_assert(dsi->rst);
return err;
} }
static int tegra_dsi_exit(struct host1x_client *client) static int tegra_dsi_exit(struct host1x_client *client)
{ {
struct tegra_dsi *dsi = host1x_client_to_dsi(client); struct tegra_dsi *dsi = host1x_client_to_dsi(client);
int err;
if (IS_ENABLED(CONFIG_DEBUG_FS)) { tegra_output_exit(&dsi->output);
err = tegra_dsi_debugfs_exit(dsi);
if (err < 0)
dev_err(dsi->dev, "debugfs cleanup failed: %d\n", err);
}
if (!dsi->master) { if (IS_ENABLED(CONFIG_DEBUG_FS))
err = tegra_output_disable(&dsi->output); tegra_dsi_debugfs_exit(dsi);
if (err < 0) {
dev_err(client->dev, "output failed to disable: %d\n",
err);
return err;
}
err = tegra_output_exit(&dsi->output); reset_control_assert(dsi->rst);
if (err < 0) {
dev_err(client->dev, "output cleanup failed: %d\n",
err);
return err;
}
}
return 0; return 0;
} }
...@@ -1398,13 +1491,6 @@ static int tegra_dsi_probe(struct platform_device *pdev) ...@@ -1398,13 +1491,6 @@ static int tegra_dsi_probe(struct platform_device *pdev)
if (IS_ERR(dsi->rst)) if (IS_ERR(dsi->rst))
return PTR_ERR(dsi->rst); return PTR_ERR(dsi->rst);
err = reset_control_deassert(dsi->rst);
if (err < 0) {
dev_err(&pdev->dev, "failed to bring DSI out of reset: %d\n",
err);
return err;
}
dsi->clk = devm_clk_get(&pdev->dev, NULL); dsi->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(dsi->clk)) { if (IS_ERR(dsi->clk)) {
dev_err(&pdev->dev, "cannot get DSI clock\n"); dev_err(&pdev->dev, "cannot get DSI clock\n");
...@@ -1470,12 +1556,6 @@ static int tegra_dsi_probe(struct platform_device *pdev) ...@@ -1470,12 +1556,6 @@ static int tegra_dsi_probe(struct platform_device *pdev)
goto disable_vdd; goto disable_vdd;
} }
err = tegra_dsi_pad_calibrate(dsi);
if (err < 0) {
dev_err(dsi->dev, "MIPI calibration failed: %d\n", err);
goto mipi_free;
}
dsi->host.ops = &tegra_dsi_host_ops; dsi->host.ops = &tegra_dsi_host_ops;
dsi->host.dev = &pdev->dev; dsi->host.dev = &pdev->dev;
...@@ -1527,6 +1607,8 @@ static int tegra_dsi_remove(struct platform_device *pdev) ...@@ -1527,6 +1607,8 @@ static int tegra_dsi_remove(struct platform_device *pdev)
return err; return err;
} }
tegra_output_remove(&dsi->output);
mipi_dsi_host_unregister(&dsi->host); mipi_dsi_host_unregister(&dsi->host);
tegra_mipi_free(dsi->mipi); tegra_mipi_free(dsi->mipi);
...@@ -1535,12 +1617,6 @@ static int tegra_dsi_remove(struct platform_device *pdev) ...@@ -1535,12 +1617,6 @@ static int tegra_dsi_remove(struct platform_device *pdev)
clk_disable_unprepare(dsi->clk); clk_disable_unprepare(dsi->clk);
reset_control_assert(dsi->rst); reset_control_assert(dsi->rst);
err = tegra_output_remove(&dsi->output);
if (err < 0) {
dev_err(&pdev->dev, "failed to remove output: %d\n", err);
return err;
}
return 0; return 0;
} }
......
...@@ -129,7 +129,7 @@ static struct tegra_fb *tegra_fb_alloc(struct drm_device *drm, ...@@ -129,7 +129,7 @@ static struct tegra_fb *tegra_fb_alloc(struct drm_device *drm,
return fb; return fb;
} }
static struct drm_framebuffer *tegra_fb_create(struct drm_device *drm, struct drm_framebuffer *tegra_fb_create(struct drm_device *drm,
struct drm_file *file, struct drm_file *file,
struct drm_mode_fb_cmd2 *cmd) struct drm_mode_fb_cmd2 *cmd)
{ {
...@@ -377,7 +377,7 @@ void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev) ...@@ -377,7 +377,7 @@ void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev)
drm_fb_helper_restore_fbdev_mode_unlocked(&fbdev->base); drm_fb_helper_restore_fbdev_mode_unlocked(&fbdev->base);
} }
static void tegra_fb_output_poll_changed(struct drm_device *drm) void tegra_fb_output_poll_changed(struct drm_device *drm)
{ {
struct tegra_drm *tegra = drm->dev_private; struct tegra_drm *tegra = drm->dev_private;
...@@ -386,28 +386,11 @@ static void tegra_fb_output_poll_changed(struct drm_device *drm) ...@@ -386,28 +386,11 @@ static void tegra_fb_output_poll_changed(struct drm_device *drm)
} }
#endif #endif
static const struct drm_mode_config_funcs tegra_drm_mode_funcs = {
.fb_create = tegra_fb_create,
#ifdef CONFIG_DRM_TEGRA_FBDEV
.output_poll_changed = tegra_fb_output_poll_changed,
#endif
};
int tegra_drm_fb_prepare(struct drm_device *drm) int tegra_drm_fb_prepare(struct drm_device *drm)
{ {
#ifdef CONFIG_DRM_TEGRA_FBDEV #ifdef CONFIG_DRM_TEGRA_FBDEV
struct tegra_drm *tegra = drm->dev_private; struct tegra_drm *tegra = drm->dev_private;
#endif
drm->mode_config.min_width = 0;
drm->mode_config.min_height = 0;
drm->mode_config.max_width = 4096;
drm->mode_config.max_height = 4096;
drm->mode_config.funcs = &tegra_drm_mode_funcs;
#ifdef CONFIG_DRM_TEGRA_FBDEV
tegra->fbdev = tegra_fbdev_create(drm); tegra->fbdev = tegra_fbdev_create(drm);
if (IS_ERR(tegra->fbdev)) if (IS_ERR(tegra->fbdev))
return PTR_ERR(tegra->fbdev); return PTR_ERR(tegra->fbdev);
......
...@@ -92,36 +92,6 @@ static const struct host1x_bo_ops tegra_bo_ops = { ...@@ -92,36 +92,6 @@ static const struct host1x_bo_ops tegra_bo_ops = {
.kunmap = tegra_bo_kunmap, .kunmap = tegra_bo_kunmap,
}; };
/*
* A generic iommu_map_sg() function is being reviewed and will hopefully be
* merged soon. At that point this function can be dropped in favour of the
* one provided by the IOMMU API.
*/
static ssize_t __iommu_map_sg(struct iommu_domain *domain, unsigned long iova,
struct scatterlist *sg, unsigned int nents,
int prot)
{
struct scatterlist *s;
size_t offset = 0;
unsigned int i;
int err;
for_each_sg(sg, s, nents, i) {
phys_addr_t phys = page_to_phys(sg_page(s));
size_t length = s->offset + s->length;
err = iommu_map(domain, iova + offset, phys, length, prot);
if (err < 0) {
iommu_unmap(domain, iova, offset);
return err;
}
offset += length;
}
return offset;
}
static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo) static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo)
{ {
int prot = IOMMU_READ | IOMMU_WRITE; int prot = IOMMU_READ | IOMMU_WRITE;
...@@ -144,7 +114,7 @@ static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo) ...@@ -144,7 +114,7 @@ static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo)
bo->paddr = bo->mm->start; bo->paddr = bo->mm->start;
err = __iommu_map_sg(tegra->domain, bo->paddr, bo->sgt->sgl, err = iommu_map_sg(tegra->domain, bo->paddr, bo->sgt->sgl,
bo->sgt->nents, prot); bo->sgt->nents, prot);
if (err < 0) { if (err < 0) {
dev_err(tegra->drm->dev, "failed to map buffer: %zd\n", err); dev_err(tegra->drm->dev, "failed to map buffer: %zd\n", err);
...@@ -244,10 +214,8 @@ static int tegra_bo_get_pages(struct drm_device *drm, struct tegra_bo *bo) ...@@ -244,10 +214,8 @@ static int tegra_bo_get_pages(struct drm_device *drm, struct tegra_bo *bo)
for_each_sg(sgt->sgl, s, sgt->nents, i) for_each_sg(sgt->sgl, s, sgt->nents, i)
sg_dma_address(s) = sg_phys(s); sg_dma_address(s) = sg_phys(s);
if (dma_map_sg(drm->dev, sgt->sgl, sgt->nents, DMA_TO_DEVICE) == 0) { if (dma_map_sg(drm->dev, sgt->sgl, sgt->nents, DMA_TO_DEVICE) == 0)
sgt = ERR_PTR(-ENOMEM);
goto release_sgt; goto release_sgt;
}
bo->sgt = sgt; bo->sgt = sgt;
...@@ -256,6 +224,7 @@ static int tegra_bo_get_pages(struct drm_device *drm, struct tegra_bo *bo) ...@@ -256,6 +224,7 @@ static int tegra_bo_get_pages(struct drm_device *drm, struct tegra_bo *bo)
release_sgt: release_sgt:
sg_free_table(sgt); sg_free_table(sgt);
kfree(sgt); kfree(sgt);
sgt = ERR_PTR(-ENOMEM);
put_pages: put_pages:
drm_gem_put_pages(&bo->gem, bo->pages, false, false); drm_gem_put_pages(&bo->gem, bo->pages, false, false);
return PTR_ERR(sgt); return PTR_ERR(sgt);
......
...@@ -9,10 +9,15 @@ ...@@ -9,10 +9,15 @@
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/debugfs.h> #include <linux/debugfs.h>
#include <linux/gpio.h>
#include <linux/hdmi.h> #include <linux/hdmi.h>
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <linux/reset.h> #include <linux/reset.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include "hdmi.h" #include "hdmi.h"
#include "drm.h" #include "drm.h"
#include "dc.h" #include "dc.h"
...@@ -31,7 +36,7 @@ struct tegra_hdmi_config { ...@@ -31,7 +36,7 @@ struct tegra_hdmi_config {
unsigned int num_tmds; unsigned int num_tmds;
unsigned long fuse_override_offset; unsigned long fuse_override_offset;
unsigned long fuse_override_value; u32 fuse_override_value;
bool has_sor_io_peak_current; bool has_sor_io_peak_current;
}; };
...@@ -40,7 +45,6 @@ struct tegra_hdmi { ...@@ -40,7 +45,6 @@ struct tegra_hdmi {
struct host1x_client client; struct host1x_client client;
struct tegra_output output; struct tegra_output output;
struct device *dev; struct device *dev;
bool enabled;
struct regulator *hdmi; struct regulator *hdmi;
struct regulator *pll; struct regulator *pll;
...@@ -85,16 +89,16 @@ enum { ...@@ -85,16 +89,16 @@ enum {
HDA, HDA,
}; };
static inline unsigned long tegra_hdmi_readl(struct tegra_hdmi *hdmi, static inline u32 tegra_hdmi_readl(struct tegra_hdmi *hdmi,
unsigned long reg) unsigned long offset)
{ {
return readl(hdmi->regs + (reg << 2)); return readl(hdmi->regs + (offset << 2));
} }
static inline void tegra_hdmi_writel(struct tegra_hdmi *hdmi, unsigned long val, static inline void tegra_hdmi_writel(struct tegra_hdmi *hdmi, u32 value,
unsigned long reg) unsigned long offset)
{ {
writel(val, hdmi->regs + (reg << 2)); writel(value, hdmi->regs + (offset << 2));
} }
struct tegra_hdmi_audio_config { struct tegra_hdmi_audio_config {
...@@ -455,8 +459,8 @@ static void tegra_hdmi_setup_audio_fs_tables(struct tegra_hdmi *hdmi) ...@@ -455,8 +459,8 @@ static void tegra_hdmi_setup_audio_fs_tables(struct tegra_hdmi *hdmi)
for (i = 0; i < ARRAY_SIZE(freqs); i++) { for (i = 0; i < ARRAY_SIZE(freqs); i++) {
unsigned int f = freqs[i]; unsigned int f = freqs[i];
unsigned int eight_half; unsigned int eight_half;
unsigned long value;
unsigned int delta; unsigned int delta;
u32 value;
if (f > 96000) if (f > 96000)
delta = 2; delta = 2;
...@@ -477,7 +481,7 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk) ...@@ -477,7 +481,7 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk)
struct device_node *node = hdmi->dev->of_node; struct device_node *node = hdmi->dev->of_node;
const struct tegra_hdmi_audio_config *config; const struct tegra_hdmi_audio_config *config;
unsigned int offset = 0; unsigned int offset = 0;
unsigned long value; u32 value;
switch (hdmi->audio_source) { switch (hdmi->audio_source) {
case HDA: case HDA:
...@@ -571,9 +575,9 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk) ...@@ -571,9 +575,9 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk)
return 0; return 0;
} }
static inline unsigned long tegra_hdmi_subpack(const u8 *ptr, size_t size) static inline u32 tegra_hdmi_subpack(const u8 *ptr, size_t size)
{ {
unsigned long value = 0; u32 value = 0;
size_t i; size_t i;
for (i = size; i > 0; i--) for (i = size; i > 0; i--)
...@@ -587,8 +591,8 @@ static void tegra_hdmi_write_infopack(struct tegra_hdmi *hdmi, const void *data, ...@@ -587,8 +591,8 @@ static void tegra_hdmi_write_infopack(struct tegra_hdmi *hdmi, const void *data,
{ {
const u8 *ptr = data; const u8 *ptr = data;
unsigned long offset; unsigned long offset;
unsigned long value;
size_t i, j; size_t i, j;
u32 value;
switch (ptr[0]) { switch (ptr[0]) {
case HDMI_INFOFRAME_TYPE_AVI: case HDMI_INFOFRAME_TYPE_AVI:
...@@ -707,9 +711,9 @@ static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi) ...@@ -707,9 +711,9 @@ static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi)
static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi) static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi)
{ {
struct hdmi_vendor_infoframe frame; struct hdmi_vendor_infoframe frame;
unsigned long value;
u8 buffer[10]; u8 buffer[10];
ssize_t err; ssize_t err;
u32 value;
if (!hdmi->stereo) { if (!hdmi->stereo) {
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
...@@ -738,7 +742,7 @@ static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi) ...@@ -738,7 +742,7 @@ static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi)
static void tegra_hdmi_setup_tmds(struct tegra_hdmi *hdmi, static void tegra_hdmi_setup_tmds(struct tegra_hdmi *hdmi,
const struct tmds_config *tmds) const struct tmds_config *tmds)
{ {
unsigned long value; u32 value;
tegra_hdmi_writel(hdmi, tmds->pll0, HDMI_NV_PDISP_SOR_PLL0); tegra_hdmi_writel(hdmi, tmds->pll0, HDMI_NV_PDISP_SOR_PLL0);
tegra_hdmi_writel(hdmi, tmds->pll1, HDMI_NV_PDISP_SOR_PLL1); tegra_hdmi_writel(hdmi, tmds->pll1, HDMI_NV_PDISP_SOR_PLL1);
...@@ -768,21 +772,78 @@ static bool tegra_output_is_hdmi(struct tegra_output *output) ...@@ -768,21 +772,78 @@ static bool tegra_output_is_hdmi(struct tegra_output *output)
return drm_detect_hdmi_monitor(edid); return drm_detect_hdmi_monitor(edid);
} }
static int tegra_output_hdmi_enable(struct tegra_output *output) static void tegra_hdmi_connector_dpms(struct drm_connector *connector,
int mode)
{
}
static const struct drm_connector_funcs tegra_hdmi_connector_funcs = {
.dpms = tegra_hdmi_connector_dpms,
.reset = drm_atomic_helper_connector_reset,
.detect = tegra_output_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = tegra_output_connector_destroy,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static enum drm_mode_status
tegra_hdmi_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct tegra_output *output = connector_to_output(connector);
struct tegra_hdmi *hdmi = to_hdmi(output);
unsigned long pclk = mode->clock * 1000;
enum drm_mode_status status = MODE_OK;
struct clk *parent;
long err;
parent = clk_get_parent(hdmi->clk_parent);
err = clk_round_rate(parent, pclk * 4);
if (err <= 0)
status = MODE_NOCLOCK;
return status;
}
static const struct drm_connector_helper_funcs
tegra_hdmi_connector_helper_funcs = {
.get_modes = tegra_output_connector_get_modes,
.mode_valid = tegra_hdmi_connector_mode_valid,
.best_encoder = tegra_output_connector_best_encoder,
};
static const struct drm_encoder_funcs tegra_hdmi_encoder_funcs = {
.destroy = tegra_output_encoder_destroy,
};
static void tegra_hdmi_encoder_dpms(struct drm_encoder *encoder, int mode)
{
}
static void tegra_hdmi_encoder_prepare(struct drm_encoder *encoder)
{
}
static void tegra_hdmi_encoder_commit(struct drm_encoder *encoder)
{
}
static void tegra_hdmi_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted)
{ {
unsigned int h_sync_width, h_front_porch, h_back_porch, i, rekey; unsigned int h_sync_width, h_front_porch, h_back_porch, i, rekey;
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct tegra_output *output = encoder_to_output(encoder);
struct drm_display_mode *mode = &dc->base.mode; struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
struct device_node *node = output->dev->of_node;
struct tegra_hdmi *hdmi = to_hdmi(output); struct tegra_hdmi *hdmi = to_hdmi(output);
struct device_node *node = hdmi->dev->of_node;
unsigned int pulse_start, div82, pclk; unsigned int pulse_start, div82, pclk;
unsigned long value;
int retries = 1000; int retries = 1000;
u32 value;
int err; int err;
if (hdmi->enabled)
return 0;
hdmi->dvi = !tegra_output_is_hdmi(output); hdmi->dvi = !tegra_output_is_hdmi(output);
pclk = mode->clock * 1000; pclk = mode->clock * 1000;
...@@ -790,32 +851,6 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) ...@@ -790,32 +851,6 @@ static int tegra_output_hdmi_enable(struct tegra_output *output)
h_back_porch = mode->htotal - mode->hsync_end; h_back_porch = mode->htotal - mode->hsync_end;
h_front_porch = mode->hsync_start - mode->hdisplay; h_front_porch = mode->hsync_start - mode->hdisplay;
err = regulator_enable(hdmi->pll);
if (err < 0) {
dev_err(hdmi->dev, "failed to enable PLL regulator: %d\n", err);
return err;
}
err = regulator_enable(hdmi->vdd);
if (err < 0) {
dev_err(hdmi->dev, "failed to enable VDD regulator: %d\n", err);
return err;
}
err = clk_set_rate(hdmi->clk, pclk);
if (err < 0)
return err;
err = clk_prepare_enable(hdmi->clk);
if (err < 0) {
dev_err(hdmi->dev, "failed to enable clock: %d\n", err);
return err;
}
reset_control_assert(hdmi->rst);
usleep_range(1000, 2000);
reset_control_deassert(hdmi->rst);
/* power up sequence */ /* power up sequence */
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_PLL0); value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_PLL0);
value &= ~SOR_PLL_PDBG; value &= ~SOR_PLL_PDBG;
...@@ -987,123 +1022,57 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) ...@@ -987,123 +1022,57 @@ static int tegra_output_hdmi_enable(struct tegra_output *output)
value |= HDMI_ENABLE; value |= HDMI_ENABLE;
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); tegra_dc_commit(dc);
value &= ~DISP_CTRL_MODE_MASK;
value |= DISP_CTRL_MODE_C_DISPLAY;
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL);
value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE;
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
/* TODO: add HDCP support */ /* TODO: add HDCP support */
hdmi->enabled = true;
return 0;
} }
static int tegra_output_hdmi_disable(struct tegra_output *output) static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder)
{ {
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
struct tegra_hdmi *hdmi = to_hdmi(output); u32 value;
unsigned long value;
if (!hdmi->enabled)
return 0;
/* /*
* The following accesses registers of the display controller, so make * The following accesses registers of the display controller, so make
* sure it's only executed when the output is attached to one. * sure it's only executed when the output is attached to one.
*/ */
if (dc) { 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;
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
value &= ~HDMI_ENABLE; value &= ~HDMI_ENABLE;
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); tegra_dc_commit(dc);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
} }
clk_disable_unprepare(hdmi->clk);
reset_control_assert(hdmi->rst);
regulator_disable(hdmi->vdd);
regulator_disable(hdmi->pll);
hdmi->enabled = false;
return 0;
} }
static int tegra_output_hdmi_setup_clock(struct tegra_output *output, static int
struct clk *clk, unsigned long pclk, tegra_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
unsigned int *div) struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{ {
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_dc *dc = to_tegra_dc(conn_state->crtc);
unsigned long pclk = crtc_state->mode.clock * 1000;
struct tegra_hdmi *hdmi = to_hdmi(output); struct tegra_hdmi *hdmi = to_hdmi(output);
int err; int err;
err = clk_set_parent(clk, hdmi->clk_parent); err = tegra_dc_state_setup_clock(dc, crtc_state, hdmi->clk_parent,
pclk, 0);
if (err < 0) { if (err < 0) {
dev_err(output->dev, "failed to set parent: %d\n", err); dev_err(output->dev, "failed to setup CRTC state: %d\n", err);
return err; return err;
} }
err = clk_set_rate(hdmi->clk_parent, pclk); return err;
if (err < 0)
dev_err(output->dev, "failed to set clock rate to %lu Hz\n",
pclk);
*div = 0;
return 0;
}
static int tegra_output_hdmi_check_mode(struct tegra_output *output,
struct drm_display_mode *mode,
enum drm_mode_status *status)
{
struct tegra_hdmi *hdmi = to_hdmi(output);
unsigned long pclk = mode->clock * 1000;
struct clk *parent;
long err;
parent = clk_get_parent(hdmi->clk_parent);
err = clk_round_rate(parent, pclk * 4);
if (err <= 0)
*status = MODE_NOCLOCK;
else
*status = MODE_OK;
return 0;
} }
static const struct tegra_output_ops hdmi_ops = { static const struct drm_encoder_helper_funcs tegra_hdmi_encoder_helper_funcs = {
.enable = tegra_output_hdmi_enable, .dpms = tegra_hdmi_encoder_dpms,
.disable = tegra_output_hdmi_disable, .prepare = tegra_hdmi_encoder_prepare,
.setup_clock = tegra_output_hdmi_setup_clock, .commit = tegra_hdmi_encoder_commit,
.check_mode = tegra_output_hdmi_check_mode, .mode_set = tegra_hdmi_encoder_mode_set,
.disable = tegra_hdmi_encoder_disable,
.atomic_check = tegra_hdmi_encoder_atomic_check,
}; };
static int tegra_hdmi_show_regs(struct seq_file *s, void *data) static int tegra_hdmi_show_regs(struct seq_file *s, void *data)
...@@ -1117,7 +1086,7 @@ static int tegra_hdmi_show_regs(struct seq_file *s, void *data) ...@@ -1117,7 +1086,7 @@ static int tegra_hdmi_show_regs(struct seq_file *s, void *data)
return err; return err;
#define DUMP_REG(name) \ #define DUMP_REG(name) \
seq_printf(s, "%-56s %#05x %08lx\n", #name, name, \ seq_printf(s, "%-56s %#05x %08x\n", #name, name, \
tegra_hdmi_readl(hdmi, name)) tegra_hdmi_readl(hdmi, name))
DUMP_REG(HDMI_CTXSW); DUMP_REG(HDMI_CTXSW);
...@@ -1330,7 +1299,7 @@ static int tegra_hdmi_debugfs_init(struct tegra_hdmi *hdmi, ...@@ -1330,7 +1299,7 @@ static int tegra_hdmi_debugfs_init(struct tegra_hdmi *hdmi,
return err; return err;
} }
static int tegra_hdmi_debugfs_exit(struct tegra_hdmi *hdmi) static void tegra_hdmi_debugfs_exit(struct tegra_hdmi *hdmi)
{ {
drm_debugfs_remove_files(hdmi->debugfs_files, ARRAY_SIZE(debugfs_files), drm_debugfs_remove_files(hdmi->debugfs_files, ARRAY_SIZE(debugfs_files),
hdmi->minor); hdmi->minor);
...@@ -1341,8 +1310,6 @@ static int tegra_hdmi_debugfs_exit(struct tegra_hdmi *hdmi) ...@@ -1341,8 +1310,6 @@ static int tegra_hdmi_debugfs_exit(struct tegra_hdmi *hdmi)
debugfs_remove(hdmi->debugfs); debugfs_remove(hdmi->debugfs);
hdmi->debugfs = NULL; hdmi->debugfs = NULL;
return 0;
} }
static int tegra_hdmi_init(struct host1x_client *client) static int tegra_hdmi_init(struct host1x_client *client)
...@@ -1351,16 +1318,32 @@ static int tegra_hdmi_init(struct host1x_client *client) ...@@ -1351,16 +1318,32 @@ static int tegra_hdmi_init(struct host1x_client *client)
struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client); struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client);
int err; int err;
hdmi->output.type = TEGRA_OUTPUT_HDMI;
hdmi->output.dev = client->dev; hdmi->output.dev = client->dev;
hdmi->output.ops = &hdmi_ops;
drm_connector_init(drm, &hdmi->output.connector,
&tegra_hdmi_connector_funcs,
DRM_MODE_CONNECTOR_HDMIA);
drm_connector_helper_add(&hdmi->output.connector,
&tegra_hdmi_connector_helper_funcs);
hdmi->output.connector.dpms = DRM_MODE_DPMS_OFF;
drm_encoder_init(drm, &hdmi->output.encoder, &tegra_hdmi_encoder_funcs,
DRM_MODE_ENCODER_TMDS);
drm_encoder_helper_add(&hdmi->output.encoder,
&tegra_hdmi_encoder_helper_funcs);
drm_mode_connector_attach_encoder(&hdmi->output.connector,
&hdmi->output.encoder);
drm_connector_register(&hdmi->output.connector);
err = tegra_output_init(drm, &hdmi->output); err = tegra_output_init(drm, &hdmi->output);
if (err < 0) { if (err < 0) {
dev_err(client->dev, "output setup failed: %d\n", err); dev_err(client->dev, "failed to initialize output: %d\n", err);
return err; return err;
} }
hdmi->output.encoder.possible_crtcs = 0x3;
if (IS_ENABLED(CONFIG_DEBUG_FS)) { if (IS_ENABLED(CONFIG_DEBUG_FS)) {
err = tegra_hdmi_debugfs_init(hdmi, drm->primary); err = tegra_hdmi_debugfs_init(hdmi, drm->primary);
if (err < 0) if (err < 0)
...@@ -1374,34 +1357,44 @@ static int tegra_hdmi_init(struct host1x_client *client) ...@@ -1374,34 +1357,44 @@ static int tegra_hdmi_init(struct host1x_client *client)
return err; return err;
} }
err = regulator_enable(hdmi->pll);
if (err < 0) {
dev_err(hdmi->dev, "failed to enable PLL regulator: %d\n", err);
return err;
}
err = regulator_enable(hdmi->vdd);
if (err < 0) {
dev_err(hdmi->dev, "failed to enable VDD regulator: %d\n", err);
return err;
}
err = clk_prepare_enable(hdmi->clk);
if (err < 0) {
dev_err(hdmi->dev, "failed to enable clock: %d\n", err);
return err;
}
reset_control_deassert(hdmi->rst);
return 0; return 0;
} }
static int tegra_hdmi_exit(struct host1x_client *client) static int tegra_hdmi_exit(struct host1x_client *client)
{ {
struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client); struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client);
int err;
regulator_disable(hdmi->hdmi); tegra_output_exit(&hdmi->output);
if (IS_ENABLED(CONFIG_DEBUG_FS)) { clk_disable_unprepare(hdmi->clk);
err = tegra_hdmi_debugfs_exit(hdmi); reset_control_assert(hdmi->rst);
if (err < 0)
dev_err(client->dev, "debugfs cleanup failed: %d\n",
err);
}
err = tegra_output_disable(&hdmi->output); regulator_disable(hdmi->vdd);
if (err < 0) { regulator_disable(hdmi->pll);
dev_err(client->dev, "output failed to disable: %d\n", err); regulator_disable(hdmi->hdmi);
return err;
}
err = tegra_output_exit(&hdmi->output); if (IS_ENABLED(CONFIG_DEBUG_FS))
if (err < 0) { tegra_hdmi_debugfs_exit(hdmi);
dev_err(client->dev, "output cleanup failed: %d\n", err);
return err;
}
return 0; return 0;
} }
...@@ -1559,11 +1552,7 @@ static int tegra_hdmi_remove(struct platform_device *pdev) ...@@ -1559,11 +1552,7 @@ static int tegra_hdmi_remove(struct platform_device *pdev)
return err; return err;
} }
err = tegra_output_remove(&hdmi->output); tegra_output_remove(&hdmi->output);
if (err < 0) {
dev_err(&pdev->dev, "failed to remove output: %d\n", err);
return err;
}
clk_disable_unprepare(hdmi->clk_parent); clk_disable_unprepare(hdmi->clk_parent);
clk_disable_unprepare(hdmi->clk); clk_disable_unprepare(hdmi->clk);
......
...@@ -12,9 +12,9 @@ ...@@ -12,9 +12,9 @@
#include "mipi-phy.h" #include "mipi-phy.h"
/* /*
* Default D-PHY timings based on MIPI D-PHY specification. Derived from * Default D-PHY timings based on MIPI D-PHY specification. Derived from the
* the valid ranges specified in Section 5.9 of the D-PHY specification * valid ranges specified in Section 6.9, Table 14, Page 40 of the D-PHY
* with minor adjustments. * specification (v1.2) with minor adjustments.
*/ */
int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing,
unsigned long period) unsigned long period)
...@@ -34,7 +34,20 @@ int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, ...@@ -34,7 +34,20 @@ int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing,
timing->hszero = 145 + 5 * period; timing->hszero = 145 + 5 * period;
timing->hssettle = 85 + 6 * period; timing->hssettle = 85 + 6 * period;
timing->hsskip = 40; timing->hsskip = 40;
timing->hstrail = max(8 * period, 60 + 4 * period);
/*
* The MIPI D-PHY specification (Section 6.9, v1.2, Table 14, Page 40)
* contains this formula as:
*
* T_HS-TRAIL = max(n * 8 * period, 60 + n * 4 * period)
*
* where n = 1 for forward-direction HS mode and n = 4 for reverse-
* direction HS mode. There's only one setting and this function does
* not parameterize on anything other that period, so this code will
* assumes that reverse-direction HS mode is supported and uses n = 4.
*/
timing->hstrail = max(4 * 8 * period, 60 + 4 * 4 * period);
timing->init = 100000; timing->init = 100000;
timing->lpx = 60; timing->lpx = 60;
timing->taget = 5 * timing->lpx; timing->taget = 5 * timing->lpx;
...@@ -46,8 +59,8 @@ int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, ...@@ -46,8 +59,8 @@ int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing,
} }
/* /*
* Validate D-PHY timing according to MIPI Alliance Specification for D-PHY, * Validate D-PHY timing according to MIPI D-PHY specification (v1.2, Section
* Section 5.9 "Global Operation Timing Parameters". * Section 6.9 "Global Operation Timing Parameters").
*/ */
int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing, int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing,
unsigned long period) unsigned long period)
......
...@@ -9,10 +9,11 @@ ...@@ -9,10 +9,11 @@
#include <linux/of_gpio.h> #include <linux/of_gpio.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_panel.h> #include <drm/drm_panel.h>
#include "drm.h" #include "drm.h"
static int tegra_connector_get_modes(struct drm_connector *connector) int tegra_output_connector_get_modes(struct drm_connector *connector)
{ {
struct tegra_output *output = connector_to_output(connector); struct tegra_output *output = connector_to_output(connector);
struct edid *edid = NULL; struct edid *edid = NULL;
...@@ -43,43 +44,20 @@ static int tegra_connector_get_modes(struct drm_connector *connector) ...@@ -43,43 +44,20 @@ static int tegra_connector_get_modes(struct drm_connector *connector)
return err; return err;
} }
static int tegra_connector_mode_valid(struct drm_connector *connector, struct drm_encoder *
struct drm_display_mode *mode) tegra_output_connector_best_encoder(struct drm_connector *connector)
{
struct tegra_output *output = connector_to_output(connector);
enum drm_mode_status status = MODE_OK;
int err;
err = tegra_output_check_mode(output, mode, &status);
if (err < 0)
return MODE_ERROR;
return status;
}
static struct drm_encoder *
tegra_connector_best_encoder(struct drm_connector *connector)
{ {
struct tegra_output *output = connector_to_output(connector); struct tegra_output *output = connector_to_output(connector);
return &output->encoder; return &output->encoder;
} }
static const struct drm_connector_helper_funcs connector_helper_funcs = { enum drm_connector_status
.get_modes = tegra_connector_get_modes, tegra_output_connector_detect(struct drm_connector *connector, bool force)
.mode_valid = tegra_connector_mode_valid,
.best_encoder = tegra_connector_best_encoder,
};
static enum drm_connector_status
tegra_connector_detect(struct drm_connector *connector, bool force)
{ {
struct tegra_output *output = connector_to_output(connector); struct tegra_output *output = connector_to_output(connector);
enum drm_connector_status status = connector_status_unknown; enum drm_connector_status status = connector_status_unknown;
if (output->ops->detect)
return output->ops->detect(output);
if (gpio_is_valid(output->hpd_gpio)) { if (gpio_is_valid(output->hpd_gpio)) {
if (gpio_get_value(output->hpd_gpio) == 0) if (gpio_get_value(output->hpd_gpio) == 0)
status = connector_status_disconnected; status = connector_status_disconnected;
...@@ -90,95 +68,22 @@ tegra_connector_detect(struct drm_connector *connector, bool force) ...@@ -90,95 +68,22 @@ tegra_connector_detect(struct drm_connector *connector, bool force)
status = connector_status_disconnected; status = connector_status_disconnected;
else else
status = connector_status_connected; status = connector_status_connected;
if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)
status = connector_status_connected;
} }
return status; return status;
} }
static void drm_connector_clear(struct drm_connector *connector) void tegra_output_connector_destroy(struct drm_connector *connector)
{
memset(connector, 0, sizeof(*connector));
}
static void tegra_connector_destroy(struct drm_connector *connector)
{ {
drm_connector_unregister(connector); drm_connector_unregister(connector);
drm_connector_cleanup(connector); drm_connector_cleanup(connector);
drm_connector_clear(connector);
} }
static const struct drm_connector_funcs connector_funcs = { void tegra_output_encoder_destroy(struct drm_encoder *encoder)
.dpms = drm_helper_connector_dpms,
.detect = tegra_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = tegra_connector_destroy,
};
static void drm_encoder_clear(struct drm_encoder *encoder)
{
memset(encoder, 0, sizeof(*encoder));
}
static void tegra_encoder_destroy(struct drm_encoder *encoder)
{ {
drm_encoder_cleanup(encoder); drm_encoder_cleanup(encoder);
drm_encoder_clear(encoder);
}
static const struct drm_encoder_funcs encoder_funcs = {
.destroy = tegra_encoder_destroy,
};
static void tegra_encoder_dpms(struct drm_encoder *encoder, int mode)
{
struct tegra_output *output = encoder_to_output(encoder);
struct drm_panel *panel = output->panel;
if (mode != DRM_MODE_DPMS_ON) {
drm_panel_disable(panel);
tegra_output_disable(output);
drm_panel_unprepare(panel);
} else {
drm_panel_prepare(panel);
tegra_output_enable(output);
drm_panel_enable(panel);
}
} }
static bool tegra_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted)
{
return true;
}
static void tegra_encoder_prepare(struct drm_encoder *encoder)
{
tegra_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
}
static void tegra_encoder_commit(struct drm_encoder *encoder)
{
tegra_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
}
static void tegra_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted)
{
}
static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
.dpms = tegra_encoder_dpms,
.mode_fixup = tegra_encoder_mode_fixup,
.prepare = tegra_encoder_prepare,
.commit = tegra_encoder_commit,
.mode_set = tegra_encoder_mode_set,
};
static irqreturn_t hpd_irq(int irq, void *data) static irqreturn_t hpd_irq(int irq, void *data)
{ {
struct tegra_output *output = data; struct tegra_output *output = data;
...@@ -268,7 +173,7 @@ int tegra_output_probe(struct tegra_output *output) ...@@ -268,7 +173,7 @@ int tegra_output_probe(struct tegra_output *output)
return 0; return 0;
} }
int tegra_output_remove(struct tegra_output *output) void tegra_output_remove(struct tegra_output *output)
{ {
if (gpio_is_valid(output->hpd_gpio)) { if (gpio_is_valid(output->hpd_gpio)) {
free_irq(output->hpd_irq, output); free_irq(output->hpd_irq, output);
...@@ -277,56 +182,17 @@ int tegra_output_remove(struct tegra_output *output) ...@@ -277,56 +182,17 @@ int tegra_output_remove(struct tegra_output *output)
if (output->ddc) if (output->ddc)
put_device(&output->ddc->dev); put_device(&output->ddc->dev);
return 0;
} }
int tegra_output_init(struct drm_device *drm, struct tegra_output *output) int tegra_output_init(struct drm_device *drm, struct tegra_output *output)
{ {
int connector, encoder; int err;
switch (output->type) {
case TEGRA_OUTPUT_RGB:
connector = DRM_MODE_CONNECTOR_LVDS;
encoder = DRM_MODE_ENCODER_LVDS;
break;
case TEGRA_OUTPUT_HDMI:
connector = DRM_MODE_CONNECTOR_HDMIA;
encoder = DRM_MODE_ENCODER_TMDS;
break;
case TEGRA_OUTPUT_DSI:
connector = DRM_MODE_CONNECTOR_DSI;
encoder = DRM_MODE_ENCODER_DSI;
break;
case TEGRA_OUTPUT_EDP:
connector = DRM_MODE_CONNECTOR_eDP;
encoder = DRM_MODE_ENCODER_TMDS;
break;
default:
connector = DRM_MODE_CONNECTOR_Unknown;
encoder = DRM_MODE_ENCODER_NONE;
break;
}
drm_connector_init(drm, &output->connector, &connector_funcs,
connector);
drm_connector_helper_add(&output->connector, &connector_helper_funcs);
output->connector.dpms = DRM_MODE_DPMS_OFF;
if (output->panel)
drm_panel_attach(output->panel, &output->connector);
drm_encoder_init(drm, &output->encoder, &encoder_funcs, encoder);
drm_encoder_helper_add(&output->encoder, &encoder_helper_funcs);
drm_mode_connector_attach_encoder(&output->connector, &output->encoder);
drm_connector_register(&output->connector);
output->encoder.possible_crtcs = 0x3; if (output->panel) {
err = drm_panel_attach(output->panel, &output->connector);
if (err < 0)
return err;
}
/* /*
* The connector is now registered and ready to receive hotplug events * The connector is now registered and ready to receive hotplug events
...@@ -338,7 +204,7 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output) ...@@ -338,7 +204,7 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output)
return 0; return 0;
} }
int tegra_output_exit(struct tegra_output *output) void tegra_output_exit(struct tegra_output *output)
{ {
/* /*
* The connector is going away, so the interrupt must be disabled to * The connector is going away, so the interrupt must be disabled to
...@@ -349,6 +215,4 @@ int tegra_output_exit(struct tegra_output *output) ...@@ -349,6 +215,4 @@ int tegra_output_exit(struct tegra_output *output)
if (output->panel) if (output->panel)
drm_panel_detach(output->panel); drm_panel_detach(output->panel);
return 0;
} }
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
#include <linux/clk.h> #include <linux/clk.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_panel.h>
#include "drm.h" #include "drm.h"
#include "dc.h" #include "dc.h"
...@@ -85,13 +88,65 @@ static void tegra_dc_write_regs(struct tegra_dc *dc, ...@@ -85,13 +88,65 @@ static void tegra_dc_write_regs(struct tegra_dc *dc,
tegra_dc_writel(dc, table[i].value, table[i].offset); tegra_dc_writel(dc, table[i].value, table[i].offset);
} }
static int tegra_output_rgb_enable(struct tegra_output *output) static void tegra_rgb_connector_dpms(struct drm_connector *connector,
int mode)
{
}
static const struct drm_connector_funcs tegra_rgb_connector_funcs = {
.dpms = tegra_rgb_connector_dpms,
.reset = drm_atomic_helper_connector_reset,
.detect = tegra_output_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = tegra_output_connector_destroy,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static enum drm_mode_status
tegra_rgb_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
/*
* FIXME: For now, always assume that the mode is okay. There are
* unresolved issues with clk_round_rate(), which doesn't always
* reliably report whether a frequency can be set or not.
*/
return MODE_OK;
}
static const struct drm_connector_helper_funcs tegra_rgb_connector_helper_funcs = {
.get_modes = tegra_output_connector_get_modes,
.mode_valid = tegra_rgb_connector_mode_valid,
.best_encoder = tegra_output_connector_best_encoder,
};
static const struct drm_encoder_funcs tegra_rgb_encoder_funcs = {
.destroy = tegra_output_encoder_destroy,
};
static void tegra_rgb_encoder_dpms(struct drm_encoder *encoder, int mode)
{
}
static void tegra_rgb_encoder_prepare(struct drm_encoder *encoder)
{ {
}
static void tegra_rgb_encoder_commit(struct drm_encoder *encoder)
{
}
static void tegra_rgb_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted)
{
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_rgb *rgb = to_rgb(output); struct tegra_rgb *rgb = to_rgb(output);
unsigned long value; u32 value;
if (rgb->enabled) if (output->panel)
return 0; drm_panel_prepare(output->panel);
tegra_dc_write_regs(rgb->dc, rgb_enable, ARRAY_SIZE(rgb_enable)); tegra_dc_write_regs(rgb->dc, rgb_enable, ARRAY_SIZE(rgb_enable));
...@@ -113,64 +168,39 @@ static int tegra_output_rgb_enable(struct tegra_output *output) ...@@ -113,64 +168,39 @@ static int tegra_output_rgb_enable(struct tegra_output *output)
value = SC0_H_QUALIFIER_NONE | SC1_H_QUALIFIER_NONE; value = SC0_H_QUALIFIER_NONE | SC1_H_QUALIFIER_NONE;
tegra_dc_writel(rgb->dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS); tegra_dc_writel(rgb->dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS);
value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_COMMAND); tegra_dc_commit(rgb->dc);
value &= ~DISP_CTRL_MODE_MASK;
value |= DISP_CTRL_MODE_C_DISPLAY;
tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_COMMAND);
value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_POWER_CONTROL);
value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE;
tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); if (output->panel)
tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); drm_panel_enable(output->panel);
rgb->enabled = true;
return 0;
} }
static int tegra_output_rgb_disable(struct tegra_output *output) static void tegra_rgb_encoder_disable(struct drm_encoder *encoder)
{ {
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_rgb *rgb = to_rgb(output); struct tegra_rgb *rgb = to_rgb(output);
unsigned long value;
if (!rgb->enabled)
return 0;
value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_POWER_CONTROL);
value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE);
tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_COMMAND);
value &= ~DISP_CTRL_MODE_MASK;
tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_COMMAND);
tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); if (output->panel)
tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); drm_panel_disable(output->panel);
tegra_dc_write_regs(rgb->dc, rgb_disable, ARRAY_SIZE(rgb_disable)); tegra_dc_write_regs(rgb->dc, rgb_disable, ARRAY_SIZE(rgb_disable));
tegra_dc_commit(rgb->dc);
rgb->enabled = false; if (output->panel)
drm_panel_unprepare(output->panel);
return 0;
} }
static int tegra_output_rgb_setup_clock(struct tegra_output *output, static int
struct clk *clk, unsigned long pclk, tegra_rgb_encoder_atomic_check(struct drm_encoder *encoder,
unsigned int *div) struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{ {
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_dc *dc = to_tegra_dc(conn_state->crtc);
unsigned long pclk = crtc_state->mode.clock * 1000;
struct tegra_rgb *rgb = to_rgb(output); struct tegra_rgb *rgb = to_rgb(output);
unsigned int div;
int err; 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;
}
/* /*
* We may not want to change the frequency of the parent clock, since * 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 * it may be a parent for other peripherals. This is due to the fact
...@@ -187,32 +217,26 @@ static int tegra_output_rgb_setup_clock(struct tegra_output *output, ...@@ -187,32 +217,26 @@ static int tegra_output_rgb_setup_clock(struct tegra_output *output,
* and hope that the desired frequency can be matched (or at least * and hope that the desired frequency can be matched (or at least
* matched sufficiently close that the panel will still work). * matched sufficiently close that the panel will still work).
*/ */
div = ((clk_get_rate(rgb->clk) * 2) / pclk) - 2;
pclk = 0;
*div = ((clk_get_rate(clk) * 2) / pclk) - 2; err = tegra_dc_state_setup_clock(dc, crtc_state, rgb->clk_parent,
pclk, div);
return 0; if (err < 0) {
} dev_err(output->dev, "failed to setup CRTC state: %d\n", err);
return err;
static int tegra_output_rgb_check_mode(struct tegra_output *output, }
struct drm_display_mode *mode,
enum drm_mode_status *status)
{
/*
* FIXME: For now, always assume that the mode is okay. There are
* unresolved issues with clk_round_rate(), which doesn't always
* reliably report whether a frequency can be set or not.
*/
*status = MODE_OK;
return 0; return err;
} }
static const struct tegra_output_ops rgb_ops = { static const struct drm_encoder_helper_funcs tegra_rgb_encoder_helper_funcs = {
.enable = tegra_output_rgb_enable, .dpms = tegra_rgb_encoder_dpms,
.disable = tegra_output_rgb_disable, .prepare = tegra_rgb_encoder_prepare,
.setup_clock = tegra_output_rgb_setup_clock, .commit = tegra_rgb_encoder_commit,
.check_mode = tegra_output_rgb_check_mode, .mode_set = tegra_rgb_encoder_mode_set,
.disable = tegra_rgb_encoder_disable,
.atomic_check = tegra_rgb_encoder_atomic_check,
}; };
int tegra_dc_rgb_probe(struct tegra_dc *dc) int tegra_dc_rgb_probe(struct tegra_dc *dc)
...@@ -262,64 +286,58 @@ int tegra_dc_rgb_probe(struct tegra_dc *dc) ...@@ -262,64 +286,58 @@ int tegra_dc_rgb_probe(struct tegra_dc *dc)
int tegra_dc_rgb_remove(struct tegra_dc *dc) int tegra_dc_rgb_remove(struct tegra_dc *dc)
{ {
int err;
if (!dc->rgb) if (!dc->rgb)
return 0; return 0;
err = tegra_output_remove(dc->rgb); tegra_output_remove(dc->rgb);
if (err < 0) dc->rgb = NULL;
return err;
return 0; return 0;
} }
int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc) int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc)
{ {
struct tegra_rgb *rgb = to_rgb(dc->rgb); struct tegra_output *output = dc->rgb;
int err; int err;
if (!dc->rgb) if (!dc->rgb)
return -ENODEV; return -ENODEV;
rgb->output.type = TEGRA_OUTPUT_RGB; drm_connector_init(drm, &output->connector, &tegra_rgb_connector_funcs,
rgb->output.ops = &rgb_ops; DRM_MODE_CONNECTOR_LVDS);
drm_connector_helper_add(&output->connector,
&tegra_rgb_connector_helper_funcs);
output->connector.dpms = DRM_MODE_DPMS_OFF;
drm_encoder_init(drm, &output->encoder, &tegra_rgb_encoder_funcs,
DRM_MODE_ENCODER_LVDS);
drm_encoder_helper_add(&output->encoder,
&tegra_rgb_encoder_helper_funcs);
drm_mode_connector_attach_encoder(&output->connector,
&output->encoder);
drm_connector_register(&output->connector);
err = tegra_output_init(dc->base.dev, &rgb->output); err = tegra_output_init(drm, output);
if (err < 0) { if (err < 0) {
dev_err(dc->dev, "output setup failed: %d\n", err); dev_err(output->dev, "failed to initialize output: %d\n", err);
return err; return err;
} }
/* /*
* By default, outputs can be associated with each display controller. * Other outputs can be attached to either display controller. The RGB
* RGB outputs are an exception, so we make sure they can be attached * outputs are an exception and work only with their parent display
* to only their parent display controller. * controller.
*/ */
rgb->output.encoder.possible_crtcs = drm_crtc_mask(&dc->base); output->encoder.possible_crtcs = drm_crtc_mask(&dc->base);
return 0; return 0;
} }
int tegra_dc_rgb_exit(struct tegra_dc *dc) int tegra_dc_rgb_exit(struct tegra_dc *dc)
{ {
if (dc->rgb) { if (dc->rgb)
int err; tegra_output_exit(dc->rgb);
err = tegra_output_disable(dc->rgb);
if (err < 0) {
dev_err(dc->dev, "output failed to disable: %d\n", err);
return err;
}
err = tegra_output_exit(dc->rgb);
if (err < 0) {
dev_err(dc->dev, "output cleanup failed: %d\n", err);
return err;
}
dc->rgb = NULL;
}
return 0; return 0;
} }
...@@ -8,13 +8,16 @@ ...@@ -8,13 +8,16 @@
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/debugfs.h> #include <linux/debugfs.h>
#include <linux/gpio.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/reset.h> #include <linux/reset.h>
#include <soc/tegra/pmc.h> #include <soc/tegra/pmc.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_dp_helper.h> #include <drm/drm_dp_helper.h>
#include <drm/drm_panel.h>
#include "dc.h" #include "dc.h"
#include "drm.h" #include "drm.h"
...@@ -258,18 +261,8 @@ static int tegra_sor_attach(struct tegra_sor *sor) ...@@ -258,18 +261,8 @@ static int tegra_sor_attach(struct tegra_sor *sor)
static int tegra_sor_wakeup(struct tegra_sor *sor) static int tegra_sor_wakeup(struct tegra_sor *sor)
{ {
struct tegra_dc *dc = to_tegra_dc(sor->output.encoder.crtc);
unsigned long value, timeout; unsigned long value, timeout;
/* enable display controller outputs */
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);
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
timeout = jiffies + msecs_to_jiffies(250); timeout = jiffies + msecs_to_jiffies(250);
/* wait for head to wake up */ /* wait for head to wake up */
...@@ -482,202 +475,512 @@ static int tegra_sor_calc_config(struct tegra_sor *sor, ...@@ -482,202 +475,512 @@ static int tegra_sor_calc_config(struct tegra_sor *sor,
return 0; return 0;
} }
static int tegra_output_sor_enable(struct tegra_output *output) static int tegra_sor_detach(struct tegra_sor *sor)
{ {
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); unsigned long value, timeout;
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 = 0;
mutex_lock(&sor->lock);
if (sor->enabled)
goto unlock;
err = clk_prepare_enable(sor->clk); /* switch to safe mode */
if (err < 0) value = tegra_sor_readl(sor, SOR_SUPER_STATE_1);
goto unlock; value &= ~SOR_SUPER_STATE_MODE_NORMAL;
tegra_sor_writel(sor, value, SOR_SUPER_STATE_1);
tegra_sor_super_update(sor);
reset_control_deassert(sor->rst); timeout = jiffies + msecs_to_jiffies(250);
/* FIXME: properly convert to struct drm_dp_aux */ while (time_before(jiffies, timeout)) {
aux = (struct drm_dp_aux *)sor->dpaux; value = tegra_sor_readl(sor, SOR_PWR);
if (value & SOR_PWR_MODE_SAFE)
break;
}
if (sor->dpaux) { if ((value & SOR_PWR_MODE_SAFE) == 0)
err = tegra_dpaux_enable(sor->dpaux); return -ETIMEDOUT;
if (err < 0)
dev_err(sor->dev, "failed to enable DP: %d\n", err);
err = drm_dp_link_probe(aux, &link); /* go to sleep */
if (err < 0) { value = tegra_sor_readl(sor, SOR_SUPER_STATE_1);
dev_err(sor->dev, "failed to probe eDP link: %d\n", value &= ~SOR_SUPER_STATE_HEAD_MODE_MASK;
err); tegra_sor_writel(sor, value, SOR_SUPER_STATE_1);
goto unlock; tegra_sor_super_update(sor);
}
}
err = clk_set_parent(sor->clk, sor->clk_safe); /* detach */
if (err < 0) value = tegra_sor_readl(sor, SOR_SUPER_STATE_1);
dev_err(sor->dev, "failed to set safe parent clock: %d\n", err); value &= ~SOR_SUPER_STATE_ATTACHED;
tegra_sor_writel(sor, value, SOR_SUPER_STATE_1);
tegra_sor_super_update(sor);
memset(&config, 0, sizeof(config)); timeout = jiffies + msecs_to_jiffies(250);
config.bits_per_pixel = output->connector.display_info.bpc * 3;
err = tegra_sor_calc_config(sor, mode, &config, &link); while (time_before(jiffies, timeout)) {
if (err < 0) value = tegra_sor_readl(sor, SOR_TEST);
dev_err(sor->dev, "failed to compute link configuration: %d\n", if ((value & SOR_TEST_ATTACHED) == 0)
err); break;
value = tegra_sor_readl(sor, SOR_CLK_CNTRL); usleep_range(25, 100);
value &= ~SOR_CLK_CNTRL_DP_CLK_SEL_MASK; }
value |= SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_DPCLK;
tegra_sor_writel(sor, value, SOR_CLK_CNTRL);
value = tegra_sor_readl(sor, SOR_PLL_2); if ((value & SOR_TEST_ATTACHED) != 0)
value &= ~SOR_PLL_2_BANDGAP_POWERDOWN; return -ETIMEDOUT;
tegra_sor_writel(sor, value, SOR_PLL_2);
usleep_range(20, 100);
value = tegra_sor_readl(sor, SOR_PLL_3); return 0;
value |= SOR_PLL_3_PLL_VDD_MODE_V3_3; }
tegra_sor_writel(sor, value, SOR_PLL_3);
value = SOR_PLL_0_ICHPMP(0xf) | SOR_PLL_0_VCOCAP_RST | static int tegra_sor_power_down(struct tegra_sor *sor)
SOR_PLL_0_PLLREG_LEVEL_V45 | SOR_PLL_0_RESISTOR_EXT; {
tegra_sor_writel(sor, value, SOR_PLL_0); unsigned long value, timeout;
int err;
value = tegra_sor_readl(sor, SOR_PLL_2); value = tegra_sor_readl(sor, SOR_PWR);
value |= SOR_PLL_2_SEQ_PLLCAPPD; value &= ~SOR_PWR_NORMAL_STATE_PU;
value &= ~SOR_PLL_2_SEQ_PLLCAPPD_ENFORCE; value |= SOR_PWR_TRIGGER;
value |= SOR_PLL_2_LVDS_ENABLE; tegra_sor_writel(sor, value, SOR_PWR);
tegra_sor_writel(sor, value, SOR_PLL_2);
value = SOR_PLL_1_TERM_COMPOUT | SOR_PLL_1_TMDS_TERM; timeout = jiffies + msecs_to_jiffies(250);
tegra_sor_writel(sor, value, SOR_PLL_1);
while (true) { while (time_before(jiffies, timeout)) {
value = tegra_sor_readl(sor, SOR_PLL_2); value = tegra_sor_readl(sor, SOR_PWR);
if ((value & SOR_PLL_2_SEQ_PLLCAPPD_ENFORCE) == 0) if ((value & SOR_PWR_TRIGGER) == 0)
break; return 0;
usleep_range(250, 1000); usleep_range(25, 100);
} }
value = tegra_sor_readl(sor, SOR_PLL_2); if ((value & SOR_PWR_TRIGGER) != 0)
value &= ~SOR_PLL_2_POWERDOWN_OVERRIDE; return -ETIMEDOUT;
value &= ~SOR_PLL_2_PORT_POWERDOWN;
tegra_sor_writel(sor, value, SOR_PLL_2);
/* err = clk_set_parent(sor->clk, sor->clk_safe);
* power up if (err < 0)
*/ dev_err(sor->dev, "failed to set safe parent clock: %d\n", err);
/* set safe link bandwidth (1.62 Gbps) */ value = tegra_sor_readl(sor, SOR_DP_PADCTL_0);
value = tegra_sor_readl(sor, SOR_CLK_CNTRL); value &= ~(SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_0 |
value &= ~SOR_CLK_CNTRL_DP_LINK_SPEED_MASK; SOR_DP_PADCTL_PD_TXD_1 | SOR_DP_PADCTL_PD_TXD_2);
value |= SOR_CLK_CNTRL_DP_LINK_SPEED_G1_62; tegra_sor_writel(sor, value, SOR_DP_PADCTL_0);
tegra_sor_writel(sor, value, SOR_CLK_CNTRL);
/* step 1 */ /* stop lane sequencer */
value = tegra_sor_readl(sor, SOR_PLL_2); value = SOR_LANE_SEQ_CTL_TRIGGER | SOR_LANE_SEQ_CTL_SEQUENCE_UP |
value |= SOR_PLL_2_SEQ_PLLCAPPD_ENFORCE | SOR_PLL_2_PORT_POWERDOWN | SOR_LANE_SEQ_CTL_POWER_STATE_DOWN;
SOR_PLL_2_BANDGAP_POWERDOWN; tegra_sor_writel(sor, value, SOR_LANE_SEQ_CTL);
tegra_sor_writel(sor, value, SOR_PLL_2);
value = tegra_sor_readl(sor, SOR_PLL_0); timeout = jiffies + msecs_to_jiffies(250);
value |= SOR_PLL_0_VCOPD | SOR_PLL_0_POWER_OFF;
tegra_sor_writel(sor, value, SOR_PLL_0);
value = tegra_sor_readl(sor, SOR_DP_PADCTL_0); while (time_before(jiffies, timeout)) {
value &= ~SOR_DP_PADCTL_PAD_CAL_PD; value = tegra_sor_readl(sor, SOR_LANE_SEQ_CTL);
tegra_sor_writel(sor, value, SOR_DP_PADCTL_0); if ((value & SOR_LANE_SEQ_CTL_TRIGGER) == 0)
break;
/* step 2 */ usleep_range(25, 100);
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);
goto unlock;
} }
usleep_range(5, 100); if ((value & SOR_LANE_SEQ_CTL_TRIGGER) != 0)
return -ETIMEDOUT;
/* step 3 */
value = tegra_sor_readl(sor, SOR_PLL_2); value = tegra_sor_readl(sor, SOR_PLL_2);
value &= ~SOR_PLL_2_BANDGAP_POWERDOWN; value |= SOR_PLL_2_PORT_POWERDOWN;
tegra_sor_writel(sor, value, SOR_PLL_2); tegra_sor_writel(sor, value, SOR_PLL_2);
usleep_range(20, 100); usleep_range(20, 100);
/* step 4 */
value = tegra_sor_readl(sor, SOR_PLL_0); value = tegra_sor_readl(sor, SOR_PLL_0);
value &= ~SOR_PLL_0_POWER_OFF; value |= SOR_PLL_0_POWER_OFF;
value &= ~SOR_PLL_0_VCOPD; value |= SOR_PLL_0_VCOPD;
tegra_sor_writel(sor, value, SOR_PLL_0); tegra_sor_writel(sor, value, SOR_PLL_0);
value = tegra_sor_readl(sor, SOR_PLL_2); value = tegra_sor_readl(sor, SOR_PLL_2);
value &= ~SOR_PLL_2_SEQ_PLLCAPPD_ENFORCE; value |= SOR_PLL_2_SEQ_PLLCAPPD;
value |= SOR_PLL_2_SEQ_PLLCAPPD_ENFORCE;
tegra_sor_writel(sor, value, SOR_PLL_2); tegra_sor_writel(sor, value, SOR_PLL_2);
usleep_range(200, 1000); usleep_range(20, 100);
/* step 5 */ return 0;
value = tegra_sor_readl(sor, SOR_PLL_2); }
value &= ~SOR_PLL_2_PORT_POWERDOWN;
tegra_sor_writel(sor, value, SOR_PLL_2);
/* switch to DP clock */ static int tegra_sor_crc_open(struct inode *inode, struct file *file)
err = clk_set_parent(sor->clk, sor->clk_dp); {
if (err < 0) file->private_data = inode->i_private;
dev_err(sor->dev, "failed to set DP parent clock: %d\n", err);
/* power DP lanes */ return 0;
value = tegra_sor_readl(sor, SOR_DP_PADCTL_0); }
if (link.num_lanes <= 2) static int tegra_sor_crc_release(struct inode *inode, struct file *file)
value &= ~(SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_2); {
else return 0;
value |= SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_2; }
if (link.num_lanes <= 1) static int tegra_sor_crc_wait(struct tegra_sor *sor, unsigned long timeout)
value &= ~SOR_DP_PADCTL_PD_TXD_1; {
else u32 value;
value |= SOR_DP_PADCTL_PD_TXD_1;
if (link.num_lanes == 0) timeout = jiffies + msecs_to_jiffies(timeout);
value &= ~SOR_DP_PADCTL_PD_TXD_0;
else
value |= SOR_DP_PADCTL_PD_TXD_0;
tegra_sor_writel(sor, value, SOR_DP_PADCTL_0); while (time_before(jiffies, timeout)) {
value = tegra_sor_readl(sor, SOR_CRC_A);
if (value & SOR_CRC_A_VALID)
return 0;
value = tegra_sor_readl(sor, SOR_DP_LINKCTL_0); usleep_range(100, 200);
value &= ~SOR_DP_LINKCTL_LANE_COUNT_MASK; }
value |= SOR_DP_LINKCTL_LANE_COUNT(link.num_lanes);
tegra_sor_writel(sor, value, SOR_DP_LINKCTL_0);
/* start lane sequencer */ return -ETIMEDOUT;
value = SOR_LANE_SEQ_CTL_TRIGGER | SOR_LANE_SEQ_CTL_SEQUENCE_DOWN | }
SOR_LANE_SEQ_CTL_POWER_STATE_UP;
tegra_sor_writel(sor, value, SOR_LANE_SEQ_CTL);
while (true) { static ssize_t tegra_sor_crc_read(struct file *file, char __user *buffer,
value = tegra_sor_readl(sor, SOR_LANE_SEQ_CTL); size_t size, loff_t *ppos)
if ((value & SOR_LANE_SEQ_CTL_TRIGGER) == 0) {
break; struct tegra_sor *sor = file->private_data;
ssize_t num, err;
char buf[10];
u32 value;
usleep_range(250, 1000); mutex_lock(&sor->lock);
}
/* set link bandwidth */ if (!sor->enabled) {
value = tegra_sor_readl(sor, SOR_CLK_CNTRL); err = -EAGAIN;
value &= ~SOR_CLK_CNTRL_DP_LINK_SPEED_MASK; goto unlock;
value |= drm_dp_link_rate_to_bw_code(link.rate) << 2; }
tegra_sor_writel(sor, value, SOR_CLK_CNTRL);
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 void tegra_sor_debugfs_exit(struct tegra_sor *sor)
{
debugfs_remove_recursive(sor->debugfs);
sor->debugfs = NULL;
}
static void tegra_sor_connector_dpms(struct drm_connector *connector, int mode)
{
}
static enum drm_connector_status
tegra_sor_connector_detect(struct drm_connector *connector, bool force)
{
struct tegra_output *output = connector_to_output(connector);
struct tegra_sor *sor = to_sor(output);
if (sor->dpaux)
return tegra_dpaux_detect(sor->dpaux);
return connector_status_unknown;
}
static const struct drm_connector_funcs tegra_sor_connector_funcs = {
.dpms = tegra_sor_connector_dpms,
.reset = drm_atomic_helper_connector_reset,
.detect = tegra_sor_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = tegra_output_connector_destroy,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static int tegra_sor_connector_get_modes(struct drm_connector *connector)
{
struct tegra_output *output = connector_to_output(connector);
struct tegra_sor *sor = to_sor(output);
int err;
if (sor->dpaux)
tegra_dpaux_enable(sor->dpaux);
err = tegra_output_connector_get_modes(connector);
if (sor->dpaux)
tegra_dpaux_disable(sor->dpaux);
return err;
}
static enum drm_mode_status
tegra_sor_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
return MODE_OK;
}
static const struct drm_connector_helper_funcs tegra_sor_connector_helper_funcs = {
.get_modes = tegra_sor_connector_get_modes,
.mode_valid = tegra_sor_connector_mode_valid,
.best_encoder = tegra_output_connector_best_encoder,
};
static const struct drm_encoder_funcs tegra_sor_encoder_funcs = {
.destroy = tegra_output_encoder_destroy,
};
static void tegra_sor_encoder_dpms(struct drm_encoder *encoder, int mode)
{
}
static void tegra_sor_encoder_prepare(struct drm_encoder *encoder)
{
}
static void tegra_sor_encoder_commit(struct drm_encoder *encoder)
{
}
static void tegra_sor_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted)
{
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
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 = 0;
mutex_lock(&sor->lock);
if (sor->enabled)
goto unlock;
err = clk_prepare_enable(sor->clk);
if (err < 0)
goto unlock;
reset_control_deassert(sor->rst);
if (output->panel)
drm_panel_prepare(output->panel);
/* 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);
goto unlock;
}
}
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 = output->connector.display_info.bpc * 3;
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;
tegra_sor_writel(sor, value, SOR_CLK_CNTRL);
value = tegra_sor_readl(sor, SOR_PLL_2);
value &= ~SOR_PLL_2_BANDGAP_POWERDOWN;
tegra_sor_writel(sor, value, SOR_PLL_2);
usleep_range(20, 100);
value = tegra_sor_readl(sor, SOR_PLL_3);
value |= SOR_PLL_3_PLL_VDD_MODE_V3_3;
tegra_sor_writel(sor, value, SOR_PLL_3);
value = SOR_PLL_0_ICHPMP(0xf) | SOR_PLL_0_VCOCAP_RST |
SOR_PLL_0_PLLREG_LEVEL_V45 | SOR_PLL_0_RESISTOR_EXT;
tegra_sor_writel(sor, value, SOR_PLL_0);
value = tegra_sor_readl(sor, SOR_PLL_2);
value |= SOR_PLL_2_SEQ_PLLCAPPD;
value &= ~SOR_PLL_2_SEQ_PLLCAPPD_ENFORCE;
value |= SOR_PLL_2_LVDS_ENABLE;
tegra_sor_writel(sor, value, SOR_PLL_2);
value = SOR_PLL_1_TERM_COMPOUT | SOR_PLL_1_TMDS_TERM;
tegra_sor_writel(sor, value, SOR_PLL_1);
while (true) {
value = tegra_sor_readl(sor, SOR_PLL_2);
if ((value & SOR_PLL_2_SEQ_PLLCAPPD_ENFORCE) == 0)
break;
usleep_range(250, 1000);
}
value = tegra_sor_readl(sor, SOR_PLL_2);
value &= ~SOR_PLL_2_POWERDOWN_OVERRIDE;
value &= ~SOR_PLL_2_PORT_POWERDOWN;
tegra_sor_writel(sor, value, SOR_PLL_2);
/*
* power up
*/
/* set safe link bandwidth (1.62 Gbps) */
value = tegra_sor_readl(sor, SOR_CLK_CNTRL);
value &= ~SOR_CLK_CNTRL_DP_LINK_SPEED_MASK;
value |= SOR_CLK_CNTRL_DP_LINK_SPEED_G1_62;
tegra_sor_writel(sor, value, SOR_CLK_CNTRL);
/* step 1 */
value = tegra_sor_readl(sor, SOR_PLL_2);
value |= SOR_PLL_2_SEQ_PLLCAPPD_ENFORCE | SOR_PLL_2_PORT_POWERDOWN |
SOR_PLL_2_BANDGAP_POWERDOWN;
tegra_sor_writel(sor, value, SOR_PLL_2);
value = tegra_sor_readl(sor, SOR_PLL_0);
value |= SOR_PLL_0_VCOPD | SOR_PLL_0_POWER_OFF;
tegra_sor_writel(sor, value, SOR_PLL_0);
value = tegra_sor_readl(sor, SOR_DP_PADCTL_0);
value &= ~SOR_DP_PADCTL_PAD_CAL_PD;
tegra_sor_writel(sor, value, SOR_DP_PADCTL_0);
/* step 2 */
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);
goto unlock;
}
usleep_range(5, 100);
/* step 3 */
value = tegra_sor_readl(sor, SOR_PLL_2);
value &= ~SOR_PLL_2_BANDGAP_POWERDOWN;
tegra_sor_writel(sor, value, SOR_PLL_2);
usleep_range(20, 100);
/* step 4 */
value = tegra_sor_readl(sor, SOR_PLL_0);
value &= ~SOR_PLL_0_POWER_OFF;
value &= ~SOR_PLL_0_VCOPD;
tegra_sor_writel(sor, value, SOR_PLL_0);
value = tegra_sor_readl(sor, SOR_PLL_2);
value &= ~SOR_PLL_2_SEQ_PLLCAPPD_ENFORCE;
tegra_sor_writel(sor, value, SOR_PLL_2);
usleep_range(200, 1000);
/* step 5 */
value = tegra_sor_readl(sor, SOR_PLL_2);
value &= ~SOR_PLL_2_PORT_POWERDOWN;
tegra_sor_writel(sor, value, SOR_PLL_2);
/* switch to DP clock */
err = clk_set_parent(sor->clk, sor->clk_dp);
if (err < 0)
dev_err(sor->dev, "failed to set DP parent clock: %d\n", err);
/* power DP lanes */
value = tegra_sor_readl(sor, SOR_DP_PADCTL_0);
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(link.num_lanes);
tegra_sor_writel(sor, value, SOR_DP_LINKCTL_0);
/* start lane sequencer */
value = SOR_LANE_SEQ_CTL_TRIGGER | SOR_LANE_SEQ_CTL_SEQUENCE_DOWN |
SOR_LANE_SEQ_CTL_POWER_STATE_UP;
tegra_sor_writel(sor, value, SOR_LANE_SEQ_CTL);
while (true) {
value = tegra_sor_readl(sor, SOR_LANE_SEQ_CTL);
if ((value & SOR_LANE_SEQ_CTL_TRIGGER) == 0)
break;
usleep_range(250, 1000);
}
/* set link bandwidth */
value = tegra_sor_readl(sor, SOR_CLK_CNTRL);
value &= ~SOR_CLK_CNTRL_DP_LINK_SPEED_MASK;
value |= drm_dp_link_rate_to_bw_code(link.rate) << 2;
tegra_sor_writel(sor, value, SOR_CLK_CNTRL);
/* set linkctl */ /* set linkctl */
value = tegra_sor_readl(sor, SOR_DP_LINKCTL_0); value = tegra_sor_readl(sor, SOR_DP_LINKCTL_0);
...@@ -800,18 +1103,6 @@ static int tegra_output_sor_enable(struct tegra_output *output) ...@@ -800,18 +1103,6 @@ static int tegra_output_sor_enable(struct tegra_output *output)
goto unlock; goto unlock;
} }
/* start display controller in continuous mode */
value = tegra_dc_readl(dc, DC_CMD_STATE_ACCESS);
value |= WRITE_MUX;
tegra_dc_writel(dc, value, DC_CMD_STATE_ACCESS);
tegra_dc_writel(dc, VSYNC_H_POSITION(1), DC_DISP_DISP_TIMING_OPTIONS);
tegra_dc_writel(dc, DISP_CTRL_MODE_C_DISPLAY, DC_CMD_DISPLAY_COMMAND);
value = tegra_dc_readl(dc, DC_CMD_STATE_ACCESS);
value &= ~WRITE_MUX;
tegra_dc_writel(dc, value, DC_CMD_STATE_ACCESS);
/* /*
* configure panel (24bpp, vsync-, hsync-, DP-A protocol, complete * configure panel (24bpp, vsync-, hsync-, DP-A protocol, complete
* raster, associate with display controller) * raster, associate with display controller)
...@@ -865,184 +1156,72 @@ static int tegra_output_sor_enable(struct tegra_output *output) ...@@ -865,184 +1156,72 @@ static int tegra_output_sor_enable(struct tegra_output *output)
vbe = vse + (mode->vsync_start - mode->vdisplay); vbe = vse + (mode->vsync_start - mode->vdisplay);
hbe = hse + (mode->hsync_start - mode->hdisplay); hbe = hse + (mode->hsync_start - mode->hdisplay);
value = ((vbe & 0x7fff) << 16) | (hbe & 0x7fff); value = ((vbe & 0x7fff) << 16) | (hbe & 0x7fff);
tegra_sor_writel(sor, value, SOR_HEAD_STATE_3(0)); tegra_sor_writel(sor, value, SOR_HEAD_STATE_3(0));
vbs = vbe + mode->vdisplay;
hbs = hbe + mode->hdisplay;
value = ((vbs & 0x7fff) << 16) | (hbs & 0x7fff);
tegra_sor_writel(sor, value, SOR_HEAD_STATE_4(0));
/* CSTM (LVDS, link A/B, upper) */
value = SOR_CSTM_LVDS | SOR_CSTM_LINK_ACT_A | SOR_CSTM_LINK_ACT_B |
SOR_CSTM_UPPER;
tegra_sor_writel(sor, value, SOR_CSTM);
/* PWM setup */
err = tegra_sor_setup_pwm(sor, 250);
if (err < 0) {
dev_err(sor->dev, "failed to setup PWM: %d\n", err);
goto unlock;
}
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
value |= SOR_ENABLE;
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
tegra_sor_update(sor);
err = tegra_sor_attach(sor);
if (err < 0) {
dev_err(sor->dev, "failed to attach SOR: %d\n", err);
goto unlock;
}
err = tegra_sor_wakeup(sor);
if (err < 0) {
dev_err(sor->dev, "failed to enable DC: %d\n", err);
goto unlock;
}
sor->enabled = true;
unlock:
mutex_unlock(&sor->lock);
return err;
}
static int tegra_sor_detach(struct tegra_sor *sor)
{
unsigned long value, timeout;
/* switch to safe mode */
value = tegra_sor_readl(sor, SOR_SUPER_STATE_1);
value &= ~SOR_SUPER_STATE_MODE_NORMAL;
tegra_sor_writel(sor, value, SOR_SUPER_STATE_1);
tegra_sor_super_update(sor);
timeout = jiffies + msecs_to_jiffies(250);
while (time_before(jiffies, timeout)) {
value = tegra_sor_readl(sor, SOR_PWR);
if (value & SOR_PWR_MODE_SAFE)
break;
}
if ((value & SOR_PWR_MODE_SAFE) == 0)
return -ETIMEDOUT;
/* go to sleep */
value = tegra_sor_readl(sor, SOR_SUPER_STATE_1);
value &= ~SOR_SUPER_STATE_HEAD_MODE_MASK;
tegra_sor_writel(sor, value, SOR_SUPER_STATE_1);
tegra_sor_super_update(sor);
/* detach */
value = tegra_sor_readl(sor, SOR_SUPER_STATE_1);
value &= ~SOR_SUPER_STATE_ATTACHED;
tegra_sor_writel(sor, value, SOR_SUPER_STATE_1);
tegra_sor_super_update(sor);
timeout = jiffies + msecs_to_jiffies(250);
while (time_before(jiffies, timeout)) {
value = tegra_sor_readl(sor, SOR_TEST);
if ((value & SOR_TEST_ATTACHED) == 0)
break;
usleep_range(25, 100);
}
if ((value & SOR_TEST_ATTACHED) != 0)
return -ETIMEDOUT;
return 0;
}
static int tegra_sor_power_down(struct tegra_sor *sor)
{
unsigned long value, timeout;
int err;
value = tegra_sor_readl(sor, SOR_PWR);
value &= ~SOR_PWR_NORMAL_STATE_PU;
value |= SOR_PWR_TRIGGER;
tegra_sor_writel(sor, value, SOR_PWR);
timeout = jiffies + msecs_to_jiffies(250);
while (time_before(jiffies, timeout)) {
value = tegra_sor_readl(sor, SOR_PWR);
if ((value & SOR_PWR_TRIGGER) == 0)
return 0;
usleep_range(25, 100);
}
if ((value & SOR_PWR_TRIGGER) != 0)
return -ETIMEDOUT;
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);
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);
tegra_sor_writel(sor, value, SOR_DP_PADCTL_0);
/* stop lane sequencer */ vbs = vbe + mode->vdisplay;
value = SOR_LANE_SEQ_CTL_TRIGGER | SOR_LANE_SEQ_CTL_SEQUENCE_UP | hbs = hbe + mode->hdisplay;
SOR_LANE_SEQ_CTL_POWER_STATE_DOWN;
tegra_sor_writel(sor, value, SOR_LANE_SEQ_CTL);
timeout = jiffies + msecs_to_jiffies(250); value = ((vbs & 0x7fff) << 16) | (hbs & 0x7fff);
tegra_sor_writel(sor, value, SOR_HEAD_STATE_4(0));
while (time_before(jiffies, timeout)) { /* CSTM (LVDS, link A/B, upper) */
value = tegra_sor_readl(sor, SOR_LANE_SEQ_CTL); value = SOR_CSTM_LVDS | SOR_CSTM_LINK_ACT_A | SOR_CSTM_LINK_ACT_B |
if ((value & SOR_LANE_SEQ_CTL_TRIGGER) == 0) SOR_CSTM_UPPER;
break; tegra_sor_writel(sor, value, SOR_CSTM);
usleep_range(25, 100); /* PWM setup */
err = tegra_sor_setup_pwm(sor, 250);
if (err < 0) {
dev_err(sor->dev, "failed to setup PWM: %d\n", err);
goto unlock;
} }
if ((value & SOR_LANE_SEQ_CTL_TRIGGER) != 0) tegra_sor_update(sor);
return -ETIMEDOUT;
value = tegra_sor_readl(sor, SOR_PLL_2); value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
value |= SOR_PLL_2_PORT_POWERDOWN; value |= SOR_ENABLE;
tegra_sor_writel(sor, value, SOR_PLL_2); tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
usleep_range(20, 100); tegra_dc_commit(dc);
value = tegra_sor_readl(sor, SOR_PLL_0); err = tegra_sor_attach(sor);
value |= SOR_PLL_0_POWER_OFF; if (err < 0) {
value |= SOR_PLL_0_VCOPD; dev_err(sor->dev, "failed to attach SOR: %d\n", err);
tegra_sor_writel(sor, value, SOR_PLL_0); goto unlock;
}
value = tegra_sor_readl(sor, SOR_PLL_2); err = tegra_sor_wakeup(sor);
value |= SOR_PLL_2_SEQ_PLLCAPPD; if (err < 0) {
value |= SOR_PLL_2_SEQ_PLLCAPPD_ENFORCE; dev_err(sor->dev, "failed to enable DC: %d\n", err);
tegra_sor_writel(sor, value, SOR_PLL_2); goto unlock;
}
usleep_range(20, 100); if (output->panel)
drm_panel_enable(output->panel);
return 0; sor->enabled = true;
unlock:
mutex_unlock(&sor->lock);
} }
static int tegra_output_sor_disable(struct tegra_output *output) static void tegra_sor_encoder_disable(struct drm_encoder *encoder)
{ {
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct tegra_output *output = encoder_to_output(encoder);
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
struct tegra_sor *sor = to_sor(output); struct tegra_sor *sor = to_sor(output);
unsigned long value; u32 value;
int err = 0; int err;
mutex_lock(&sor->lock); mutex_lock(&sor->lock);
if (!sor->enabled) if (!sor->enabled)
goto unlock; goto unlock;
if (output->panel)
drm_panel_disable(output->panel);
err = tegra_sor_detach(sor); err = tegra_sor_detach(sor);
if (err < 0) { if (err < 0) {
dev_err(sor->dev, "failed to detach SOR: %d\n", err); dev_err(sor->dev, "failed to detach SOR: %d\n", err);
...@@ -1057,31 +1236,11 @@ static int tegra_output_sor_disable(struct tegra_output *output) ...@@ -1057,31 +1236,11 @@ static int tegra_output_sor_disable(struct tegra_output *output)
* sure it's only executed when the output is attached to one. * sure it's only executed when the output is attached to one.
*/ */
if (dc) { if (dc) {
/*
* XXX: We can't do this here because it causes the SOR to go
* into an erroneous state and the output will look scrambled
* the next time it is enabled. Presumably this is because we
* should be doing this only on the next VBLANK. A possible
* solution would be to queue a "power-off" event to trigger
* this code to be run during the next VBLANK.
*/
/*
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;
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
value &= ~SOR_ENABLE; value &= ~SOR_ENABLE;
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); tegra_dc_commit(dc);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
} }
err = tegra_sor_power_down(sor); err = tegra_sor_power_down(sor);
...@@ -1104,187 +1263,48 @@ static int tegra_output_sor_disable(struct tegra_output *output) ...@@ -1104,187 +1263,48 @@ static int tegra_output_sor_disable(struct tegra_output *output)
goto unlock; goto unlock;
} }
reset_control_assert(sor->rst); if (output->panel)
drm_panel_unprepare(output->panel);
clk_disable_unprepare(sor->clk); clk_disable_unprepare(sor->clk);
reset_control_assert(sor->rst);
sor->enabled = false; sor->enabled = false;
unlock: unlock:
mutex_unlock(&sor->lock); mutex_unlock(&sor->lock);
return err;
} }
static int tegra_output_sor_setup_clock(struct tegra_output *output, static int
struct clk *clk, unsigned long pclk, tegra_sor_encoder_atomic_check(struct drm_encoder *encoder,
unsigned int *div) struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{ {
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_dc *dc = to_tegra_dc(conn_state->crtc);
unsigned long pclk = crtc_state->mode.clock * 1000;
struct tegra_sor *sor = to_sor(output); struct tegra_sor *sor = to_sor(output);
int err; int err;
err = clk_set_parent(clk, sor->clk_parent); err = tegra_dc_state_setup_clock(dc, crtc_state, sor->clk_parent,
if (err < 0) { pclk, 0);
dev_err(sor->dev, "failed to set parent clock: %d\n", err);
return err;
}
err = clk_set_rate(sor->clk_parent, pclk);
if (err < 0) { if (err < 0) {
dev_err(sor->dev, "failed to set clock rate to %lu Hz\n", pclk); dev_err(output->dev, "failed to setup CRTC state: %d\n", err);
return err; return err;
} }
*div = 0;
return 0;
}
static int tegra_output_sor_check_mode(struct tegra_output *output,
struct drm_display_mode *mode,
enum drm_mode_status *status)
{
/*
* FIXME: For now, always assume that the mode is okay.
*/
*status = MODE_OK;
return 0;
}
static enum drm_connector_status
tegra_output_sor_detect(struct tegra_output *output)
{
struct tegra_sor *sor = to_sor(output);
if (sor->dpaux)
return tegra_dpaux_detect(sor->dpaux);
return connector_status_unknown;
}
static const struct tegra_output_ops sor_ops = {
.enable = tegra_output_sor_enable,
.disable = tegra_output_sor_disable,
.setup_clock = tegra_output_sor_setup_clock,
.check_mode = tegra_output_sor_check_mode,
.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; 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 = { static const struct drm_encoder_helper_funcs tegra_sor_encoder_helper_funcs = {
.owner = THIS_MODULE, .dpms = tegra_sor_encoder_dpms,
.open = tegra_sor_crc_open, .prepare = tegra_sor_encoder_prepare,
.read = tegra_sor_crc_read, .commit = tegra_sor_encoder_commit,
.release = tegra_sor_crc_release, .mode_set = tegra_sor_encoder_mode_set,
.disable = tegra_sor_encoder_disable,
.atomic_check = tegra_sor_encoder_atomic_check,
}; };
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) static int tegra_sor_init(struct host1x_client *client)
{ {
struct drm_device *drm = dev_get_drvdata(client->parent); struct drm_device *drm = dev_get_drvdata(client->parent);
...@@ -1294,17 +1314,32 @@ static int tegra_sor_init(struct host1x_client *client) ...@@ -1294,17 +1314,32 @@ static int tegra_sor_init(struct host1x_client *client)
if (!sor->dpaux) if (!sor->dpaux)
return -ENODEV; return -ENODEV;
sor->output.type = TEGRA_OUTPUT_EDP;
sor->output.dev = sor->dev; sor->output.dev = sor->dev;
sor->output.ops = &sor_ops;
drm_connector_init(drm, &sor->output.connector,
&tegra_sor_connector_funcs,
DRM_MODE_CONNECTOR_eDP);
drm_connector_helper_add(&sor->output.connector,
&tegra_sor_connector_helper_funcs);
sor->output.connector.dpms = DRM_MODE_DPMS_OFF;
drm_encoder_init(drm, &sor->output.encoder, &tegra_sor_encoder_funcs,
DRM_MODE_ENCODER_TMDS);
drm_encoder_helper_add(&sor->output.encoder,
&tegra_sor_encoder_helper_funcs);
drm_mode_connector_attach_encoder(&sor->output.connector,
&sor->output.encoder);
drm_connector_register(&sor->output.connector);
err = tegra_output_init(drm, &sor->output); err = tegra_output_init(drm, &sor->output);
if (err < 0) { if (err < 0) {
dev_err(sor->dev, "output setup failed: %d\n", err); dev_err(client->dev, "failed to initialize output: %d\n", err);
return err; return err;
} }
sor->output.encoder.possible_crtcs = 0x3;
if (IS_ENABLED(CONFIG_DEBUG_FS)) { if (IS_ENABLED(CONFIG_DEBUG_FS)) {
err = tegra_sor_debugfs_init(sor, drm->primary); err = tegra_sor_debugfs_init(sor, drm->primary);
if (err < 0) if (err < 0)
...@@ -1319,6 +1354,20 @@ static int tegra_sor_init(struct host1x_client *client) ...@@ -1319,6 +1354,20 @@ static int tegra_sor_init(struct host1x_client *client)
} }
} }
err = clk_prepare_enable(sor->clk);
if (err < 0) {
dev_err(sor->dev, "failed to enable clock: %d\n", err);
return err;
}
err = clk_prepare_enable(sor->clk_safe);
if (err < 0)
return err;
err = clk_prepare_enable(sor->clk_dp);
if (err < 0)
return err;
return 0; return 0;
} }
...@@ -1327,11 +1376,7 @@ static int tegra_sor_exit(struct host1x_client *client) ...@@ -1327,11 +1376,7 @@ static int tegra_sor_exit(struct host1x_client *client)
struct tegra_sor *sor = host1x_client_to_sor(client); struct tegra_sor *sor = host1x_client_to_sor(client);
int err; int err;
err = tegra_output_disable(&sor->output); tegra_output_exit(&sor->output);
if (err < 0) {
dev_err(sor->dev, "output failed to disable: %d\n", err);
return err;
}
if (sor->dpaux) { if (sor->dpaux) {
err = tegra_dpaux_detach(sor->dpaux); err = tegra_dpaux_detach(sor->dpaux);
...@@ -1341,17 +1386,12 @@ static int tegra_sor_exit(struct host1x_client *client) ...@@ -1341,17 +1386,12 @@ static int tegra_sor_exit(struct host1x_client *client)
} }
} }
if (IS_ENABLED(CONFIG_DEBUG_FS)) { clk_disable_unprepare(sor->clk_safe);
err = tegra_sor_debugfs_exit(sor); clk_disable_unprepare(sor->clk_dp);
if (err < 0) clk_disable_unprepare(sor->clk);
dev_err(sor->dev, "debugfs cleanup failed: %d\n", err);
}
err = tegra_output_exit(&sor->output); if (IS_ENABLED(CONFIG_DEBUG_FS))
if (err < 0) { tegra_sor_debugfs_exit(sor);
dev_err(sor->dev, "output cleanup failed: %d\n", err);
return err;
}
return 0; return 0;
} }
...@@ -1404,26 +1444,14 @@ static int tegra_sor_probe(struct platform_device *pdev) ...@@ -1404,26 +1444,14 @@ static int tegra_sor_probe(struct platform_device *pdev)
if (IS_ERR(sor->clk_parent)) if (IS_ERR(sor->clk_parent))
return PTR_ERR(sor->clk_parent); return PTR_ERR(sor->clk_parent);
err = clk_prepare_enable(sor->clk_parent);
if (err < 0)
return err;
sor->clk_safe = devm_clk_get(&pdev->dev, "safe"); sor->clk_safe = devm_clk_get(&pdev->dev, "safe");
if (IS_ERR(sor->clk_safe)) if (IS_ERR(sor->clk_safe))
return PTR_ERR(sor->clk_safe); return PTR_ERR(sor->clk_safe);
err = clk_prepare_enable(sor->clk_safe);
if (err < 0)
return err;
sor->clk_dp = devm_clk_get(&pdev->dev, "dp"); sor->clk_dp = devm_clk_get(&pdev->dev, "dp");
if (IS_ERR(sor->clk_dp)) if (IS_ERR(sor->clk_dp))
return PTR_ERR(sor->clk_dp); return PTR_ERR(sor->clk_dp);
err = clk_prepare_enable(sor->clk_dp);
if (err < 0)
return err;
INIT_LIST_HEAD(&sor->client.list); INIT_LIST_HEAD(&sor->client.list);
sor->client.ops = &sor_client_ops; sor->client.ops = &sor_client_ops;
sor->client.dev = &pdev->dev; sor->client.dev = &pdev->dev;
...@@ -1454,10 +1482,7 @@ static int tegra_sor_remove(struct platform_device *pdev) ...@@ -1454,10 +1482,7 @@ static int tegra_sor_remove(struct platform_device *pdev)
return err; return err;
} }
clk_disable_unprepare(sor->clk_parent); tegra_output_remove(&sor->output);
clk_disable_unprepare(sor->clk_safe);
clk_disable_unprepare(sor->clk_dp);
clk_disable_unprepare(sor->clk);
return 0; return 0;
} }
......
...@@ -72,13 +72,14 @@ static void host1x_subdev_del(struct host1x_subdev *subdev) ...@@ -72,13 +72,14 @@ static void host1x_subdev_del(struct host1x_subdev *subdev)
/** /**
* host1x_device_parse_dt() - scan device tree and add matching subdevices * host1x_device_parse_dt() - scan device tree and add matching subdevices
*/ */
static int host1x_device_parse_dt(struct host1x_device *device) static int host1x_device_parse_dt(struct host1x_device *device,
struct host1x_driver *driver)
{ {
struct device_node *np; struct device_node *np;
int err; int err;
for_each_child_of_node(device->dev.parent->of_node, np) { for_each_child_of_node(device->dev.parent->of_node, np) {
if (of_match_node(device->driver->subdevs, np) && if (of_match_node(driver->subdevs, np) &&
of_device_is_available(np)) { of_device_is_available(np)) {
err = host1x_subdev_add(device, np); err = host1x_subdev_add(device, np);
if (err < 0) if (err < 0)
...@@ -109,14 +110,12 @@ static void host1x_subdev_register(struct host1x_device *device, ...@@ -109,14 +110,12 @@ static void host1x_subdev_register(struct host1x_device *device,
mutex_unlock(&device->clients_lock); mutex_unlock(&device->clients_lock);
mutex_unlock(&device->subdevs_lock); mutex_unlock(&device->subdevs_lock);
/*
* When all subdevices have been registered, the composite device is
* ready to be probed.
*/
if (list_empty(&device->subdevs)) { if (list_empty(&device->subdevs)) {
err = device->driver->probe(device); err = device_add(&device->dev);
if (err < 0) if (err < 0)
dev_err(&device->dev, "probe failed: %d\n", err); dev_err(&device->dev, "failed to add: %d\n", err);
else
device->registered = true;
} }
} }
...@@ -124,16 +123,16 @@ static void __host1x_subdev_unregister(struct host1x_device *device, ...@@ -124,16 +123,16 @@ static void __host1x_subdev_unregister(struct host1x_device *device,
struct host1x_subdev *subdev) struct host1x_subdev *subdev)
{ {
struct host1x_client *client = subdev->client; struct host1x_client *client = subdev->client;
int err;
/* /*
* If all subdevices have been activated, we're about to remove the * If all subdevices have been activated, we're about to remove the
* first active subdevice, so unload the driver first. * first active subdevice, so unload the driver first.
*/ */
if (list_empty(&device->subdevs)) { if (list_empty(&device->subdevs)) {
err = device->driver->remove(device); if (device->registered) {
if (err < 0) device->registered = false;
dev_err(&device->dev, "remove failed: %d\n", err); device_del(&device->dev);
}
} }
/* /*
...@@ -260,24 +259,113 @@ static int host1x_del_client(struct host1x *host1x, ...@@ -260,24 +259,113 @@ static int host1x_del_client(struct host1x *host1x,
return -ENODEV; return -ENODEV;
} }
static struct bus_type host1x_bus_type = { static int host1x_device_match(struct device *dev, struct device_driver *drv)
.name = "host1x", {
}; return strcmp(dev_name(dev), drv->name) == 0;
}
int host1x_bus_init(void) static int host1x_device_probe(struct device *dev)
{ {
return bus_register(&host1x_bus_type); struct host1x_driver *driver = to_host1x_driver(dev->driver);
struct host1x_device *device = to_host1x_device(dev);
if (driver->probe)
return driver->probe(device);
return 0;
} }
void host1x_bus_exit(void) static int host1x_device_remove(struct device *dev)
{ {
bus_unregister(&host1x_bus_type); struct host1x_driver *driver = to_host1x_driver(dev->driver);
struct host1x_device *device = to_host1x_device(dev);
if (driver->remove)
return driver->remove(device);
return 0;
}
static void host1x_device_shutdown(struct device *dev)
{
struct host1x_driver *driver = to_host1x_driver(dev->driver);
struct host1x_device *device = to_host1x_device(dev);
if (driver->shutdown)
driver->shutdown(device);
}
static const struct dev_pm_ops host1x_device_pm_ops = {
.suspend = pm_generic_suspend,
.resume = pm_generic_resume,
.freeze = pm_generic_freeze,
.thaw = pm_generic_thaw,
.poweroff = pm_generic_poweroff,
.restore = pm_generic_restore,
};
struct bus_type host1x_bus_type = {
.name = "host1x",
.match = host1x_device_match,
.probe = host1x_device_probe,
.remove = host1x_device_remove,
.shutdown = host1x_device_shutdown,
.pm = &host1x_device_pm_ops,
};
static void __host1x_device_del(struct host1x_device *device)
{
struct host1x_subdev *subdev, *sd;
struct host1x_client *client, *cl;
mutex_lock(&device->subdevs_lock);
/* unregister subdevices */
list_for_each_entry_safe(subdev, sd, &device->active, list) {
/*
* host1x_subdev_unregister() will remove the client from
* any lists, so we'll need to manually add it back to the
* list of idle clients.
*
* XXX: Alternatively, perhaps don't remove the client from
* any lists in host1x_subdev_unregister() and instead do
* that explicitly from host1x_unregister_client()?
*/
client = subdev->client;
__host1x_subdev_unregister(device, subdev);
/* add the client to the list of idle clients */
mutex_lock(&clients_lock);
list_add_tail(&client->list, &clients);
mutex_unlock(&clients_lock);
}
/* remove subdevices */
list_for_each_entry_safe(subdev, sd, &device->subdevs, list)
host1x_subdev_del(subdev);
mutex_unlock(&device->subdevs_lock);
/* move clients to idle list */
mutex_lock(&clients_lock);
mutex_lock(&device->clients_lock);
list_for_each_entry_safe(client, cl, &device->clients, list)
list_move_tail(&client->list, &clients);
mutex_unlock(&device->clients_lock);
mutex_unlock(&clients_lock);
/* finally remove the device */
list_del_init(&device->list);
} }
static void host1x_device_release(struct device *dev) static void host1x_device_release(struct device *dev)
{ {
struct host1x_device *device = to_host1x_device(dev); struct host1x_device *device = to_host1x_device(dev);
__host1x_device_del(device);
kfree(device); kfree(device);
} }
...@@ -293,6 +381,8 @@ static int host1x_device_add(struct host1x *host1x, ...@@ -293,6 +381,8 @@ static int host1x_device_add(struct host1x *host1x,
if (!device) if (!device)
return -ENOMEM; return -ENOMEM;
device_initialize(&device->dev);
mutex_init(&device->subdevs_lock); mutex_init(&device->subdevs_lock);
INIT_LIST_HEAD(&device->subdevs); INIT_LIST_HEAD(&device->subdevs);
INIT_LIST_HEAD(&device->active); INIT_LIST_HEAD(&device->active);
...@@ -303,24 +393,18 @@ static int host1x_device_add(struct host1x *host1x, ...@@ -303,24 +393,18 @@ static int host1x_device_add(struct host1x *host1x,
device->dev.coherent_dma_mask = host1x->dev->coherent_dma_mask; device->dev.coherent_dma_mask = host1x->dev->coherent_dma_mask;
device->dev.dma_mask = &device->dev.coherent_dma_mask; device->dev.dma_mask = &device->dev.coherent_dma_mask;
dev_set_name(&device->dev, "%s", driver->driver.name);
device->dev.release = host1x_device_release; device->dev.release = host1x_device_release;
dev_set_name(&device->dev, "%s", driver->name);
device->dev.bus = &host1x_bus_type; device->dev.bus = &host1x_bus_type;
device->dev.parent = host1x->dev; device->dev.parent = host1x->dev;
err = device_register(&device->dev); err = host1x_device_parse_dt(device, driver);
if (err < 0)
return err;
err = host1x_device_parse_dt(device);
if (err < 0) { if (err < 0) {
device_unregister(&device->dev); kfree(device);
return err; return err;
} }
mutex_lock(&host1x->devices_lock);
list_add_tail(&device->list, &host1x->devices); list_add_tail(&device->list, &host1x->devices);
mutex_unlock(&host1x->devices_lock);
mutex_lock(&clients_lock); mutex_lock(&clients_lock);
...@@ -347,51 +431,12 @@ static int host1x_device_add(struct host1x *host1x, ...@@ -347,51 +431,12 @@ static int host1x_device_add(struct host1x *host1x,
static void host1x_device_del(struct host1x *host1x, static void host1x_device_del(struct host1x *host1x,
struct host1x_device *device) struct host1x_device *device)
{ {
struct host1x_subdev *subdev, *sd; if (device->registered) {
struct host1x_client *client, *cl; device->registered = false;
device_del(&device->dev);
mutex_lock(&device->subdevs_lock);
/* unregister subdevices */
list_for_each_entry_safe(subdev, sd, &device->active, list) {
/*
* host1x_subdev_unregister() will remove the client from
* any lists, so we'll need to manually add it back to the
* list of idle clients.
*
* XXX: Alternatively, perhaps don't remove the client from
* any lists in host1x_subdev_unregister() and instead do
* that explicitly from host1x_unregister_client()?
*/
client = subdev->client;
__host1x_subdev_unregister(device, subdev);
/* add the client to the list of idle clients */
mutex_lock(&clients_lock);
list_add_tail(&client->list, &clients);
mutex_unlock(&clients_lock);
} }
/* remove subdevices */ put_device(&device->dev);
list_for_each_entry_safe(subdev, sd, &device->subdevs, list)
host1x_subdev_del(subdev);
mutex_unlock(&device->subdevs_lock);
/* move clients to idle list */
mutex_lock(&clients_lock);
mutex_lock(&device->clients_lock);
list_for_each_entry_safe(client, cl, &device->clients, list)
list_move_tail(&client->list, &clients);
mutex_unlock(&device->clients_lock);
mutex_unlock(&clients_lock);
/* finally remove the device */
list_del_init(&device->list);
device_unregister(&device->dev);
} }
static void host1x_attach_driver(struct host1x *host1x, static void host1x_attach_driver(struct host1x *host1x,
...@@ -409,11 +454,11 @@ static void host1x_attach_driver(struct host1x *host1x, ...@@ -409,11 +454,11 @@ static void host1x_attach_driver(struct host1x *host1x,
} }
} }
mutex_unlock(&host1x->devices_lock);
err = host1x_device_add(host1x, driver); err = host1x_device_add(host1x, driver);
if (err < 0) if (err < 0)
dev_err(host1x->dev, "failed to allocate device: %d\n", err); dev_err(host1x->dev, "failed to allocate device: %d\n", err);
mutex_unlock(&host1x->devices_lock);
} }
static void host1x_detach_driver(struct host1x *host1x, static void host1x_detach_driver(struct host1x *host1x,
...@@ -466,7 +511,8 @@ int host1x_unregister(struct host1x *host1x) ...@@ -466,7 +511,8 @@ int host1x_unregister(struct host1x *host1x)
return 0; return 0;
} }
int host1x_driver_register(struct host1x_driver *driver) int host1x_driver_register_full(struct host1x_driver *driver,
struct module *owner)
{ {
struct host1x *host1x; struct host1x *host1x;
...@@ -483,9 +529,12 @@ int host1x_driver_register(struct host1x_driver *driver) ...@@ -483,9 +529,12 @@ int host1x_driver_register(struct host1x_driver *driver)
mutex_unlock(&devices_lock); mutex_unlock(&devices_lock);
return 0; driver->driver.bus = &host1x_bus_type;
driver->driver.owner = owner;
return driver_register(&driver->driver);
} }
EXPORT_SYMBOL(host1x_driver_register); EXPORT_SYMBOL(host1x_driver_register_full);
void host1x_driver_unregister(struct host1x_driver *driver) void host1x_driver_unregister(struct host1x_driver *driver)
{ {
......
...@@ -18,10 +18,10 @@ ...@@ -18,10 +18,10 @@
#ifndef HOST1X_BUS_H #ifndef HOST1X_BUS_H
#define HOST1X_BUS_H #define HOST1X_BUS_H
struct bus_type;
struct host1x; struct host1x;
int host1x_bus_init(void); extern struct bus_type host1x_bus_type;
void host1x_bus_exit(void);
int host1x_register(struct host1x *host1x); int host1x_register(struct host1x *host1x);
int host1x_unregister(struct host1x *host1x); int host1x_unregister(struct host1x *host1x);
......
...@@ -216,7 +216,7 @@ static int __init tegra_host1x_init(void) ...@@ -216,7 +216,7 @@ static int __init tegra_host1x_init(void)
{ {
int err; int err;
err = host1x_bus_init(); err = bus_register(&host1x_bus_type);
if (err < 0) if (err < 0)
return err; return err;
...@@ -233,7 +233,7 @@ static int __init tegra_host1x_init(void) ...@@ -233,7 +233,7 @@ static int __init tegra_host1x_init(void)
unregister_host1x: unregister_host1x:
platform_driver_unregister(&tegra_host1x_driver); platform_driver_unregister(&tegra_host1x_driver);
unregister_bus: unregister_bus:
host1x_bus_exit(); bus_unregister(&host1x_bus_type);
return err; return err;
} }
module_init(tegra_host1x_init); module_init(tegra_host1x_init);
...@@ -242,7 +242,7 @@ static void __exit tegra_host1x_exit(void) ...@@ -242,7 +242,7 @@ static void __exit tegra_host1x_exit(void)
{ {
platform_driver_unregister(&tegra_mipi_driver); platform_driver_unregister(&tegra_mipi_driver);
platform_driver_unregister(&tegra_host1x_driver); platform_driver_unregister(&tegra_host1x_driver);
host1x_bus_exit(); bus_unregister(&host1x_bus_type);
} }
module_exit(tegra_host1x_exit); module_exit(tegra_host1x_exit);
......
...@@ -127,4 +127,41 @@ void drm_atomic_helper_connector_destroy_state(struct drm_connector *connector, ...@@ -127,4 +127,41 @@ void drm_atomic_helper_connector_destroy_state(struct drm_connector *connector,
#define drm_atomic_crtc_state_for_each_plane(plane, crtc_state) \ #define drm_atomic_crtc_state_for_each_plane(plane, crtc_state) \
drm_for_each_plane_mask(plane, (crtc_state)->state->dev, (crtc_state)->plane_mask) drm_for_each_plane_mask(plane, (crtc_state)->state->dev, (crtc_state)->plane_mask)
/*
* drm_atomic_plane_disabling - check whether a plane is being disabled
* @plane: plane object
* @old_state: previous atomic state
*
* Checks the atomic state of a plane to determine whether it's being disabled
* or not. This also WARNs if it detects an invalid state (both CRTC and FB
* need to either both be NULL or both be non-NULL).
*
* RETURNS:
* True if the plane is being disabled, false otherwise.
*/
static inline bool
drm_atomic_plane_disabling(struct drm_plane *plane,
struct drm_plane_state *old_state)
{
/*
* When disabling a plane, CRTC and FB should always be NULL together.
* Anything else should be considered a bug in the atomic core, so we
* gently warn about it.
*/
WARN_ON((plane->state->crtc == NULL && plane->state->fb != NULL) ||
(plane->state->crtc != NULL && plane->state->fb == NULL));
/*
* When using the transitional helpers, old_state may be NULL. If so,
* we know nothing about the current state and have to assume that it
* might be enabled.
*
* When using the atomic helpers, old_state won't be NULL. Therefore
* this check assumes that either the driver will have reconstructed
* the correct state in ->reset() or that the driver will have taken
* appropriate measures to disable all planes.
*/
return (!old_state || old_state->crtc) && !plane->state->crtc;
}
#endif /* DRM_ATOMIC_HELPER_H_ */ #endif /* DRM_ATOMIC_HELPER_H_ */
...@@ -115,6 +115,7 @@ struct drm_crtc_helper_funcs { ...@@ -115,6 +115,7 @@ struct drm_crtc_helper_funcs {
* @get_crtc: return CRTC that the encoder is currently attached to * @get_crtc: return CRTC that the encoder is currently attached to
* @detect: connection status detection * @detect: connection status detection
* @disable: disable encoder when not in use (overrides DPMS off) * @disable: disable encoder when not in use (overrides DPMS off)
* @atomic_check: check for validity of an atomic update
* *
* The helper operations are called by the mid-layer CRTC helper. * The helper operations are called by the mid-layer CRTC helper.
*/ */
...@@ -137,6 +138,11 @@ struct drm_encoder_helper_funcs { ...@@ -137,6 +138,11 @@ struct drm_encoder_helper_funcs {
struct drm_connector *connector); struct drm_connector *connector);
/* disable encoder when not in use - more explicit than dpms off */ /* disable encoder when not in use - more explicit than dpms off */
void (*disable)(struct drm_encoder *encoder); void (*disable)(struct drm_encoder *encoder);
/* atomic helpers */
int (*atomic_check)(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state);
}; };
/** /**
......
...@@ -52,7 +52,8 @@ extern int drm_crtc_init(struct drm_device *dev, ...@@ -52,7 +52,8 @@ extern int drm_crtc_init(struct drm_device *dev,
* @prepare_fb: prepare a framebuffer for use by the plane * @prepare_fb: prepare a framebuffer for use by the plane
* @cleanup_fb: cleanup a framebuffer when it's no longer used by the plane * @cleanup_fb: cleanup a framebuffer when it's no longer used by the plane
* @atomic_check: check that a given atomic state is valid and can be applied * @atomic_check: check that a given atomic state is valid and can be applied
* @atomic_update: apply an atomic state to the plane * @atomic_update: apply an atomic state to the plane (mandatory)
* @atomic_disable: disable the plane
* *
* The helper operations are called by the mid-layer CRTC helper. * The helper operations are called by the mid-layer CRTC helper.
*/ */
...@@ -66,6 +67,8 @@ struct drm_plane_helper_funcs { ...@@ -66,6 +67,8 @@ struct drm_plane_helper_funcs {
struct drm_plane_state *state); struct drm_plane_state *state);
void (*atomic_update)(struct drm_plane *plane, void (*atomic_update)(struct drm_plane *plane,
struct drm_plane_state *old_state); struct drm_plane_state *old_state);
void (*atomic_disable)(struct drm_plane *plane,
struct drm_plane_state *old_state);
}; };
static inline void drm_plane_helper_add(struct drm_plane *plane, static inline void drm_plane_helper_add(struct drm_plane *plane,
......
...@@ -250,17 +250,29 @@ void host1x_job_unpin(struct host1x_job *job); ...@@ -250,17 +250,29 @@ void host1x_job_unpin(struct host1x_job *job);
struct host1x_device; struct host1x_device;
struct host1x_driver { struct host1x_driver {
struct device_driver driver;
const struct of_device_id *subdevs; const struct of_device_id *subdevs;
struct list_head list; struct list_head list;
const char *name;
int (*probe)(struct host1x_device *device); int (*probe)(struct host1x_device *device);
int (*remove)(struct host1x_device *device); int (*remove)(struct host1x_device *device);
void (*shutdown)(struct host1x_device *device);
}; };
int host1x_driver_register(struct host1x_driver *driver); static inline struct host1x_driver *
to_host1x_driver(struct device_driver *driver)
{
return container_of(driver, struct host1x_driver, driver);
}
int host1x_driver_register_full(struct host1x_driver *driver,
struct module *owner);
void host1x_driver_unregister(struct host1x_driver *driver); void host1x_driver_unregister(struct host1x_driver *driver);
#define host1x_driver_register(driver) \
host1x_driver_register_full(driver, THIS_MODULE)
struct host1x_device { struct host1x_device {
struct host1x_driver *driver; struct host1x_driver *driver;
struct list_head list; struct list_head list;
...@@ -272,6 +284,8 @@ struct host1x_device { ...@@ -272,6 +284,8 @@ struct host1x_device {
struct mutex clients_lock; struct mutex clients_lock;
struct list_head clients; struct list_head clients;
bool registered;
}; };
static inline struct host1x_device *to_host1x_device(struct device *dev) static inline struct host1x_device *to_host1x_device(struct device *dev)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment