/*
* Copyright (C) 2012 Avionic Design GmbH
* Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <mach/clk.h>
#include "drm.h"
#include "dc.h"
struct tegra_dc_window {
fixed20_12 x;
fixed20_12 y;
fixed20_12 w;
fixed20_12 h;
unsigned int outx;
unsigned int outy;
unsigned int outw;
unsigned int outh;
unsigned int stride;
unsigned int fmt;
};
static const struct drm_crtc_funcs tegra_crtc_funcs = {
.set_config = drm_crtc_helper_set_config,
.destroy = drm_crtc_cleanup,
};
static void tegra_crtc_dpms(struct drm_crtc *crtc, int mode)
{
}
static bool tegra_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted)
{
return true;
}
static inline u32 compute_dda_inc(fixed20_12 inf, unsigned int out, bool v,
unsigned int bpp)
{
fixed20_12 outf = dfixed_init(out);
u32 dda_inc;
int max;
if (v)
max = 15;
else {
switch (bpp) {
case 2:
max = 8;
break;
default:
WARN_ON_ONCE(1);
/* fallthrough */
case 4:
max = 4;
break;
}
}
outf.full = max_t(u32, outf.full - dfixed_const(1), dfixed_const(1));
inf.full -= dfixed_const(1);
dda_inc = dfixed_div(inf, outf);
dda_inc = min_t(u32, dda_inc, dfixed_const(max));
return dda_inc;
}
static inline u32 compute_initial_dda(fixed20_12 in)
{
return dfixed_frac(in);
}
static int tegra_dc_set_timings(struct tegra_dc *dc,
struct drm_display_mode *mode)
{
/* TODO: For HDMI compliance, h & v ref_to_sync should be set to 1 */
unsigned int h_ref_to_sync = 0;
unsigned int v_ref_to_sync = 0;
unsigned long value;
tegra_dc_writel(dc, 0x0, DC_DISP_DISP_TIMING_OPTIONS);
value = (v_ref_to_sync << 16) | h_ref_to_sync;
tegra_dc_writel(dc, value, DC_DISP_REF_TO_SYNC);
value = ((mode->vsync_end - mode->vsync_start) << 16) |
((mode->hsync_end - mode->hsync_start) << 0);
tegra_dc_writel(dc, value, DC_DISP_SYNC_WIDTH);
value = ((mode->vtotal - mode->vsync_end) << 16) |
((mode->htotal - mode->hsync_end) << 0);
tegra_dc_writel(dc, value, DC_DISP_BACK_PORCH);
value = ((mode->vsync_start - mode->vdisplay) << 16) |
((mode->hsync_start - mode->hdisplay) << 0);
tegra_dc_writel(dc, value, DC_DISP_FRONT_PORCH);
value = (mode->vdisplay << 16) | mode->hdisplay;
tegra_dc_writel(dc, value, DC_DISP_ACTIVE);
return 0;
}
static int tegra_crtc_setup_clk(struct drm_crtc *crtc,
struct drm_display_mode *mode,
unsigned long *div)
{
unsigned long pclk = mode->clock * 1000, rate;
struct tegra_dc *dc = to_tegra_dc(crtc);
struct tegra_output *output = NULL;
struct drm_encoder *encoder;
long 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;
/*
* This assumes that the display controller will divide its parent
* clock by 2 to generate the pixel clock.
*/
err = tegra_output_setup_clock(output, dc->clk, pclk * 2);
if (err < 0) {
dev_err(dc->dev, "failed to setup clock: %ld\n", err);
return err;
}
rate = clk_get_rate(dc->clk);
*div = (rate * 2 / pclk) - 2;
DRM_DEBUG_KMS("rate: %lu, div: %lu\n", rate, *div);
return 0;
}
static int tegra_crtc_mode_set(struct drm_crtc *crtc,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted,
int x, int y, struct drm_framebuffer *old_fb)
{
struct tegra_framebuffer *fb = to_tegra_fb(crtc->fb);
struct tegra_dc *dc = to_tegra_dc(crtc);
unsigned int h_dda, v_dda, bpp;
struct tegra_dc_window win;
unsigned long div, value;
int err;
err = tegra_crtc_setup_clk(crtc, mode, &div);
if (err) {
dev_err(d