2011-08-04 23:50:10 +10:00

1562 lines
36 KiB
C

/*
* drivers/video/tegra/dc/dc.c
*
* Copyright (C) 2010 Google, Inc.
* Author: Erik Gilling <konkers@android.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/workqueue.h>
#include <linux/ktime.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/switch.h>
#include <video/tegrafb.h>
#include <mach/clk.h>
#include <mach/dc.h>
#include <mach/fb.h>
#include <mach/mc.h>
#include <mach/nvhost.h>
#include "dc_reg.h"
#include "dc_priv.h"
static int no_vsync;
module_param_named(no_vsync, no_vsync, int, S_IRUGO | S_IWUSR);
static int use_dynamic_emc = 1;
module_param_named(use_dynamic_emc, use_dynamic_emc, int, S_IRUGO | S_IWUSR);
static int windows_idle_detection_time = 0;
module_param_named(windows_idle_detection_time, windows_idle_detection_time,
int, S_IRUGO | S_IWUSR);
struct tegra_dc *tegra_dcs[TEGRA_MAX_DC];
DEFINE_MUTEX(tegra_dc_lock);
static inline int tegra_dc_fmt_bpp(int fmt)
{
switch (fmt) {
case TEGRA_WIN_FMT_P1:
return 1;
case TEGRA_WIN_FMT_P2:
return 2;
case TEGRA_WIN_FMT_P4:
return 4;
case TEGRA_WIN_FMT_P8:
return 8;
case TEGRA_WIN_FMT_B4G4R4A4:
case TEGRA_WIN_FMT_B5G5R5A:
case TEGRA_WIN_FMT_B5G6R5:
case TEGRA_WIN_FMT_AB5G5R5:
return 16;
case TEGRA_WIN_FMT_B8G8R8A8:
case TEGRA_WIN_FMT_R8G8B8A8:
case TEGRA_WIN_FMT_B6x2G6x2R6x2A8:
case TEGRA_WIN_FMT_R6x2G6x2B6x2A8:
return 32;
/* for planar formats, size of the Y plane, 8bit */
case TEGRA_WIN_FMT_YCbCr420P:
case TEGRA_WIN_FMT_YUV420P:
case TEGRA_WIN_FMT_YCbCr422P:
case TEGRA_WIN_FMT_YUV422P:
return 8;
case TEGRA_WIN_FMT_YCbCr422:
case TEGRA_WIN_FMT_YUV422:
case TEGRA_WIN_FMT_YCbCr422R:
case TEGRA_WIN_FMT_YUV422R:
case TEGRA_WIN_FMT_YCbCr422RA:
case TEGRA_WIN_FMT_YUV422RA:
/* FIXME: need to know the bpp of these formats */
return 0;
}
return 0;
}
static inline bool tegra_dc_is_yuv_planar(int fmt)
{
switch (fmt) {
case TEGRA_WIN_FMT_YUV420P:
case TEGRA_WIN_FMT_YCbCr420P:
case TEGRA_WIN_FMT_YCbCr422P:
case TEGRA_WIN_FMT_YUV422P:
return true;
}
return false;
}
#define DUMP_REG(a) do { \
snprintf(buff, sizeof(buff), "%-32s\t%03x\t%08lx\n", \
#a, a, tegra_dc_readl(dc, a)); \
print(data, buff); \
} while (0)
static void _dump_regs(struct tegra_dc *dc, void *data,
void (* print)(void *data, const char *str))
{
int i;
char buff[256];
tegra_dc_io_start(dc);
clk_enable(dc->clk);
DUMP_REG(DC_CMD_DISPLAY_COMMAND_OPTION0);
DUMP_REG(DC_CMD_DISPLAY_COMMAND);
DUMP_REG(DC_CMD_SIGNAL_RAISE);
DUMP_REG(DC_CMD_INT_STATUS);
DUMP_REG(DC_CMD_INT_MASK);
DUMP_REG(DC_CMD_INT_ENABLE);
DUMP_REG(DC_CMD_INT_TYPE);
DUMP_REG(DC_CMD_INT_POLARITY);
DUMP_REG(DC_CMD_SIGNAL_RAISE1);
DUMP_REG(DC_CMD_SIGNAL_RAISE2);
DUMP_REG(DC_CMD_SIGNAL_RAISE3);
DUMP_REG(DC_CMD_STATE_ACCESS);
DUMP_REG(DC_CMD_STATE_CONTROL);
DUMP_REG(DC_CMD_DISPLAY_WINDOW_HEADER);
DUMP_REG(DC_CMD_REG_ACT_CONTROL);
DUMP_REG(DC_DISP_DISP_SIGNAL_OPTIONS0);
DUMP_REG(DC_DISP_DISP_SIGNAL_OPTIONS1);
DUMP_REG(DC_DISP_DISP_WIN_OPTIONS);
DUMP_REG(DC_DISP_MEM_HIGH_PRIORITY);
DUMP_REG(DC_DISP_MEM_HIGH_PRIORITY_TIMER);
DUMP_REG(DC_DISP_DISP_TIMING_OPTIONS);
DUMP_REG(DC_DISP_REF_TO_SYNC);
DUMP_REG(DC_DISP_SYNC_WIDTH);
DUMP_REG(DC_DISP_BACK_PORCH);
DUMP_REG(DC_DISP_DISP_ACTIVE);
DUMP_REG(DC_DISP_FRONT_PORCH);
DUMP_REG(DC_DISP_H_PULSE0_CONTROL);
DUMP_REG(DC_DISP_H_PULSE0_POSITION_A);
DUMP_REG(DC_DISP_H_PULSE0_POSITION_B);
DUMP_REG(DC_DISP_H_PULSE0_POSITION_C);
DUMP_REG(DC_DISP_H_PULSE0_POSITION_D);
DUMP_REG(DC_DISP_H_PULSE1_CONTROL);
DUMP_REG(DC_DISP_H_PULSE1_POSITION_A);
DUMP_REG(DC_DISP_H_PULSE1_POSITION_B);
DUMP_REG(DC_DISP_H_PULSE1_POSITION_C);
DUMP_REG(DC_DISP_H_PULSE1_POSITION_D);
DUMP_REG(DC_DISP_H_PULSE2_CONTROL);
DUMP_REG(DC_DISP_H_PULSE2_POSITION_A);
DUMP_REG(DC_DISP_H_PULSE2_POSITION_B);
DUMP_REG(DC_DISP_H_PULSE2_POSITION_C);
DUMP_REG(DC_DISP_H_PULSE2_POSITION_D);
DUMP_REG(DC_DISP_V_PULSE0_CONTROL);
DUMP_REG(DC_DISP_V_PULSE0_POSITION_A);
DUMP_REG(DC_DISP_V_PULSE0_POSITION_B);
DUMP_REG(DC_DISP_V_PULSE0_POSITION_C);
DUMP_REG(DC_DISP_V_PULSE1_CONTROL);
DUMP_REG(DC_DISP_V_PULSE1_POSITION_A);
DUMP_REG(DC_DISP_V_PULSE1_POSITION_B);
DUMP_REG(DC_DISP_V_PULSE1_POSITION_C);
DUMP_REG(DC_DISP_V_PULSE2_CONTROL);
DUMP_REG(DC_DISP_V_PULSE2_POSITION_A);
DUMP_REG(DC_DISP_V_PULSE3_CONTROL);
DUMP_REG(DC_DISP_V_PULSE3_POSITION_A);
DUMP_REG(DC_DISP_M0_CONTROL);
DUMP_REG(DC_DISP_M1_CONTROL);
DUMP_REG(DC_DISP_DI_CONTROL);
DUMP_REG(DC_DISP_PP_CONTROL);
DUMP_REG(DC_DISP_PP_SELECT_A);
DUMP_REG(DC_DISP_PP_SELECT_B);
DUMP_REG(DC_DISP_PP_SELECT_C);
DUMP_REG(DC_DISP_PP_SELECT_D);
DUMP_REG(DC_DISP_DISP_CLOCK_CONTROL);
DUMP_REG(DC_DISP_DISP_INTERFACE_CONTROL);
DUMP_REG(DC_DISP_DISP_COLOR_CONTROL);
DUMP_REG(DC_DISP_SHIFT_CLOCK_OPTIONS);
DUMP_REG(DC_DISP_DATA_ENABLE_OPTIONS);
DUMP_REG(DC_DISP_SERIAL_INTERFACE_OPTIONS);
DUMP_REG(DC_DISP_LCD_SPI_OPTIONS);
DUMP_REG(DC_DISP_BORDER_COLOR);
DUMP_REG(DC_DISP_COLOR_KEY0_LOWER);
DUMP_REG(DC_DISP_COLOR_KEY0_UPPER);
DUMP_REG(DC_DISP_COLOR_KEY1_LOWER);
DUMP_REG(DC_DISP_COLOR_KEY1_UPPER);
DUMP_REG(DC_DISP_CURSOR_FOREGROUND);
DUMP_REG(DC_DISP_CURSOR_BACKGROUND);
DUMP_REG(DC_DISP_CURSOR_START_ADDR);
DUMP_REG(DC_DISP_CURSOR_START_ADDR_NS);
DUMP_REG(DC_DISP_CURSOR_POSITION);
DUMP_REG(DC_DISP_CURSOR_POSITION_NS);
DUMP_REG(DC_DISP_INIT_SEQ_CONTROL);
DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_A);
DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_B);
DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_C);
DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_D);
DUMP_REG(DC_DISP_DC_MCCIF_FIFOCTRL);
DUMP_REG(DC_DISP_MCCIF_DISPLAY0A_HYST);
DUMP_REG(DC_DISP_MCCIF_DISPLAY0B_HYST);
DUMP_REG(DC_DISP_MCCIF_DISPLAY0C_HYST);
DUMP_REG(DC_DISP_MCCIF_DISPLAY1B_HYST);
DUMP_REG(DC_DISP_DAC_CRT_CTRL);
DUMP_REG(DC_DISP_DISP_MISC_CONTROL);
for (i = 0; i < 3; i++) {
print(data, "\n");
snprintf(buff, sizeof(buff), "WINDOW %c:\n", 'A' + i);
print(data, buff);
tegra_dc_writel(dc, WINDOW_A_SELECT << i,
DC_CMD_DISPLAY_WINDOW_HEADER);
DUMP_REG(DC_CMD_DISPLAY_WINDOW_HEADER);
DUMP_REG(DC_WIN_WIN_OPTIONS);
DUMP_REG(DC_WIN_BYTE_SWAP);
DUMP_REG(DC_WIN_BUFFER_CONTROL);
DUMP_REG(DC_WIN_COLOR_DEPTH);
DUMP_REG(DC_WIN_POSITION);
DUMP_REG(DC_WIN_SIZE);
DUMP_REG(DC_WIN_PRESCALED_SIZE);
DUMP_REG(DC_WIN_H_INITIAL_DDA);
DUMP_REG(DC_WIN_V_INITIAL_DDA);
DUMP_REG(DC_WIN_DDA_INCREMENT);
DUMP_REG(DC_WIN_LINE_STRIDE);
DUMP_REG(DC_WIN_BUF_STRIDE);
DUMP_REG(DC_WIN_UV_BUF_STRIDE);
DUMP_REG(DC_WIN_BLEND_NOKEY);
DUMP_REG(DC_WIN_BLEND_1WIN);
DUMP_REG(DC_WIN_BLEND_2WIN_X);
DUMP_REG(DC_WIN_BLEND_2WIN_Y);
DUMP_REG(DC_WIN_BLEND_3WIN_XY);
DUMP_REG(DC_WINBUF_START_ADDR);
DUMP_REG(DC_WINBUF_START_ADDR_U);
DUMP_REG(DC_WINBUF_START_ADDR_V);
DUMP_REG(DC_WINBUF_ADDR_H_OFFSET);
DUMP_REG(DC_WINBUF_ADDR_V_OFFSET);
DUMP_REG(DC_WINBUF_UFLOW_STATUS);
DUMP_REG(DC_WIN_CSC_YOF);
DUMP_REG(DC_WIN_CSC_KYRGB);
DUMP_REG(DC_WIN_CSC_KUR);
DUMP_REG(DC_WIN_CSC_KVR);
DUMP_REG(DC_WIN_CSC_KUG);
DUMP_REG(DC_WIN_CSC_KVG);
DUMP_REG(DC_WIN_CSC_KUB);
DUMP_REG(DC_WIN_CSC_KVB);
}
clk_disable(dc->clk);
tegra_dc_io_end(dc);
}
#undef DUMP_REG
#ifdef DEBUG
static void dump_regs_print(void *data, const char *str)
{
struct tegra_dc *dc = data;
dev_dbg(&dc->ndev->dev, "%s", str);
}
static void dump_regs(struct tegra_dc *dc)
{
_dump_regs(dc, dc, dump_regs_print);
}
#else
static void dump_regs(struct tegra_dc *dc) {}
#endif
#ifdef CONFIG_DEBUG_FS
static void dbg_regs_print(void *data, const char *str)
{
struct seq_file *s = data;
seq_printf(s, "%s", str);
}
#undef DUMP_REG
static int dbg_dc_show(struct seq_file *s, void *unused)
{
struct tegra_dc *dc = s->private;
_dump_regs(dc, s, dbg_regs_print);
return 0;
}
static int dbg_dc_open(struct inode *inode, struct file *file)
{
return single_open(file, dbg_dc_show, inode->i_private);
}
static const struct file_operations dbg_fops = {
.open = dbg_dc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static void tegra_dc_dbg_add(struct tegra_dc *dc)
{
char name[32];
snprintf(name, sizeof(name), "tegra_dc%d_regs", dc->ndev->id);
(void) debugfs_create_file(name, S_IRUGO, NULL, dc, &dbg_fops);
}
#else
static void tegra_dc_dbg_add(struct tegra_dc *dc) {}
#endif
static int tegra_dc_set(struct tegra_dc *dc, int index)
{
int ret = 0;
mutex_lock(&tegra_dc_lock);
if (index >= TEGRA_MAX_DC) {
ret = -EINVAL;
goto out;
}
if (dc != NULL && tegra_dcs[index] != NULL) {
ret = -EBUSY;
goto out;
}
tegra_dcs[index] = dc;
out:
mutex_unlock(&tegra_dc_lock);
return ret;
}
static unsigned int tegra_dc_has_multiple_dc(void)
{
unsigned int idx;
unsigned int cnt = 0;
struct tegra_dc *dc;
mutex_lock(&tegra_dc_lock);
for (idx = 0; idx < TEGRA_MAX_DC; idx++)
cnt += ((dc = tegra_dcs[idx]) != NULL && dc->enabled) ? 1 : 0;
mutex_unlock(&tegra_dc_lock);
return (cnt > 1);
}
struct tegra_dc *tegra_dc_get_dc(unsigned idx)
{
if (idx < TEGRA_MAX_DC)
return tegra_dcs[idx];
else
return NULL;
}
EXPORT_SYMBOL(tegra_dc_get_dc);
struct tegra_dc_win *tegra_dc_get_window(struct tegra_dc *dc, unsigned win)
{
if (win >= dc->n_windows)
return NULL;
return &dc->windows[win];
}
EXPORT_SYMBOL(tegra_dc_get_window);
static int get_topmost_window(u32 *depths, unsigned long *wins)
{
int idx, best = -1;
for_each_set_bit(idx, wins, DC_N_WINDOWS) {
if (best == -1 || depths[idx] < depths[best])
best = idx;
}
clear_bit(best, wins);
return best;
}
static u32 blend_topwin(u32 flags)
{
if (flags & TEGRA_WIN_FLAG_BLEND_COVERAGE)
return BLEND(NOKEY, ALPHA, 0xff, 0xff);
else if (flags & TEGRA_WIN_FLAG_BLEND_PREMULT)
return BLEND(NOKEY, PREMULT, 0xff, 0xff);
else
return BLEND(NOKEY, FIX, 0xff, 0xff);
}
static u32 blend_2win(int idx, unsigned long behind_mask, u32* flags, int xy)
{
int other;
for (other = 0; other < DC_N_WINDOWS; other++) {
if (other != idx && (xy-- == 0))
break;
}
if (BIT(other) & behind_mask)
return blend_topwin(flags[idx]);
else if (flags[other])
return BLEND(NOKEY, DEPENDANT, 0x00, 0x00);
else
return BLEND(NOKEY, FIX, 0x00, 0x00);
}
static u32 blend_3win(int idx, unsigned long behind_mask, u32* flags)
{
unsigned long infront_mask;
int first;
infront_mask = ~(behind_mask | BIT(idx));
infront_mask &= (BIT(DC_N_WINDOWS) - 1);
first = ffs(infront_mask) - 1;
if (!infront_mask)
return blend_topwin(flags[idx]);
else if (behind_mask && first != -1 && flags[first])
return BLEND(NOKEY, DEPENDANT, 0x00, 0x00);
else
return BLEND(NOKEY, FIX, 0x0, 0x0);
}
static void tegra_dc_set_blending(struct tegra_dc *dc, struct tegra_dc_blend *blend)
{
unsigned long mask = BIT(DC_N_WINDOWS) - 1;
while (mask) {
int idx = get_topmost_window(blend->z, &mask);
tegra_dc_writel(dc, WINDOW_A_SELECT << idx,
DC_CMD_DISPLAY_WINDOW_HEADER);
tegra_dc_writel(dc, BLEND(NOKEY, FIX, 0xff, 0xff),
DC_WIN_BLEND_NOKEY);
tegra_dc_writel(dc, BLEND(NOKEY, FIX, 0xff, 0xff),
DC_WIN_BLEND_1WIN);
tegra_dc_writel(dc, blend_2win(idx, mask, blend->flags, 0),
DC_WIN_BLEND_2WIN_X);
tegra_dc_writel(dc, blend_2win(idx, mask, blend->flags, 1),
DC_WIN_BLEND_2WIN_Y);
tegra_dc_writel(dc, blend_3win(idx, mask, blend->flags),
DC_WIN_BLEND_3WIN_XY);
}
}
static void tegra_dc_set_csc(struct tegra_dc *dc)
{
tegra_dc_writel(dc, 0x00f0, DC_WIN_CSC_YOF);
tegra_dc_writel(dc, 0x012a, DC_WIN_CSC_KYRGB);
tegra_dc_writel(dc, 0x0000, DC_WIN_CSC_KUR);
tegra_dc_writel(dc, 0x0198, DC_WIN_CSC_KVR);
tegra_dc_writel(dc, 0x039b, DC_WIN_CSC_KUG);
tegra_dc_writel(dc, 0x032f, DC_WIN_CSC_KVG);
tegra_dc_writel(dc, 0x0204, DC_WIN_CSC_KUB);
tegra_dc_writel(dc, 0x0000, DC_WIN_CSC_KVB);
}
static void tegra_dc_set_scaling_filter(struct tegra_dc *dc)
{
unsigned i;
unsigned v0 = 128;
unsigned v1 = 0;
/* linear horizontal and vertical filters */
for (i = 0; i < 16; i++) {
tegra_dc_writel(dc, (v1 << 16) | (v0 << 8),
DC_WIN_H_FILTER_P(i));
tegra_dc_writel(dc, v0,
DC_WIN_V_FILTER_P(i));
v0 -= 8;
v1 += 8;
}
}
static unsigned int tegra_dc_windows_is_overlapped(struct tegra_dc_win *a,
struct tegra_dc_win *b)
{
if (!WIN_IS_ENABLED(a) || !WIN_IS_ENABLED(b))
return 0;
return ((a->out_y + a->out_h > b->out_y) && (a->out_y <= b->out_y)) ||
((b->out_y + b->out_h > a->out_y) && (b->out_y <= a->out_y));
}
static unsigned int tegra_dc_find_max_bandwidth(struct tegra_dc_win *wins[],
unsigned int bw[], int n)
{
/* We have n windows and knows their geometries and bandwidthes. If any
* of them overlapped vertically, the overlapped area bandwidth get
* combined.
*
* This function will find the maximum bandwidth of overlapped area.
* If there is no windows overlapped, then return the maximum
* bandwidth of windows.
*/
/* We know win_2 is always overlapped with win_0 and win_1. */
if (tegra_dc_windows_is_overlapped(wins[0], wins[1]))
return bw[0] + bw[1] + bw[2];
else
return max(bw[0], bw[1]) + bw[2];
}
/* 8 bits per byte (1 << 3) */
#define BIT_TO_BYTE_SHIFT 3
#undef BIT_TO_BYTE_SHIFT
static void tegra_dc_change_emc(struct tegra_dc *dc)
{
if (dc->emc_clk_rate != dc->new_emc_clk_rate) {
dc->emc_clk_rate = dc->new_emc_clk_rate;
clk_set_rate(dc->emc_clk, dc->emc_clk_rate);
}
}
static void tegra_dc_reduce_emc_worker(struct work_struct *work)
{
struct tegra_dc *dc;
dc = container_of(to_delayed_work(work), struct tegra_dc,
reduce_emc_clk_work);
mutex_lock(&dc->lock);
if (!dc->enabled) {
mutex_unlock(&dc->lock);
return;
}
tegra_dc_change_emc(dc);
mutex_unlock(&dc->lock);
}
int tegra_dc_set_default_emc(struct tegra_dc *dc)
{
/*
* POST happens whenever this function is called, we first delete any
* reduce_emc_clk_work, then we always set the DC EMC clock to default
* value.
*/
cancel_delayed_work_sync(&dc->reduce_emc_clk_work);
if (NEED_UPDATE_EMC_ON_EVERY_FRAME)
return 0;
mutex_lock(&dc->lock);
if (!dc->enabled) {
mutex_unlock(&dc->lock);
return -EFAULT;
}
dc->new_emc_clk_rate = tegra_dc_get_default_emc_clk_rate(dc);
tegra_dc_change_emc(dc);
mutex_unlock(&dc->lock);
return 0;
}
/* does not support updating windows on multiple dcs in one call */
int tegra_dc_update_windows(struct tegra_dc_win *windows[], int n)
{
struct tegra_dc *dc;
unsigned long update_mask = GENERAL_ACT_REQ;
unsigned long val;
bool update_blend = false;
int i;
dc = windows[0]->dc;
mutex_lock(&dc->lock);
if (!dc->enabled) {
mutex_unlock(&dc->lock);
return -EFAULT;
}
if (no_vsync)
tegra_dc_writel(dc, WRITE_MUX_ACTIVE | READ_MUX_ACTIVE, DC_CMD_STATE_ACCESS);
else
tegra_dc_writel(dc, WRITE_MUX_ASSEMBLY | READ_MUX_ASSEMBLY, DC_CMD_STATE_ACCESS);
for (i = 0; i < n; i++) {
struct tegra_dc_win *win = windows[i];
unsigned h_dda;
unsigned v_dda;
unsigned h_offset;
unsigned v_offset;
bool invert_h = (win->flags & TEGRA_WIN_FLAG_INVERT_H) != 0;
bool invert_v = (win->flags & TEGRA_WIN_FLAG_INVERT_V) != 0;
bool yuvp = tegra_dc_is_yuv_planar(win->fmt);
if (win->z != dc->blend.z[win->idx]) {
dc->blend.z[win->idx] = win->z;
update_blend = true;
}
if ((win->flags & TEGRA_WIN_BLEND_FLAGS_MASK) !=
dc->blend.flags[win->idx]) {
dc->blend.flags[win->idx] =
win->flags & TEGRA_WIN_BLEND_FLAGS_MASK;
update_blend = true;
}
tegra_dc_writel(dc, WINDOW_A_SELECT << win->idx,
DC_CMD_DISPLAY_WINDOW_HEADER);
if (!no_vsync)
update_mask |= WIN_A_ACT_REQ << win->idx;
if (!WIN_IS_ENABLED(win)) {
tegra_dc_writel(dc, 0, DC_WIN_WIN_OPTIONS);
continue;
}
tegra_dc_writel(dc, win->fmt, DC_WIN_COLOR_DEPTH);
tegra_dc_writel(dc, 0, DC_WIN_BYTE_SWAP);
tegra_dc_writel(dc,
V_POSITION(win->out_y) | H_POSITION(win->out_x),
DC_WIN_POSITION);
tegra_dc_writel(dc,
V_SIZE(win->out_h) | H_SIZE(win->out_w),
DC_WIN_SIZE);
tegra_dc_writel(dc,
V_PRESCALED_SIZE(win->h) |
H_PRESCALED_SIZE(win->w * tegra_dc_fmt_bpp(win->fmt) / 8),
DC_WIN_PRESCALED_SIZE);
h_dda = ((win->w - 1) * 0x1000) / max_t(int, win->out_w - 1, 1);
v_dda = ((win->h - 1) * 0x1000) / max_t(int, win->out_h - 1, 1);
tegra_dc_writel(dc, V_DDA_INC(v_dda) | H_DDA_INC(h_dda),
DC_WIN_DDA_INCREMENT);
tegra_dc_writel(dc, 0, DC_WIN_H_INITIAL_DDA);
tegra_dc_writel(dc, 0, DC_WIN_V_INITIAL_DDA);
tegra_dc_writel(dc, 0, DC_WIN_BUF_STRIDE);
tegra_dc_writel(dc, 0, DC_WIN_UV_BUF_STRIDE);
tegra_dc_writel(dc, (unsigned long)win->phys_addr,
DC_WINBUF_START_ADDR);
if (!yuvp) {
tegra_dc_writel(dc, win->stride, DC_WIN_LINE_STRIDE);
} else {
tegra_dc_writel(dc,
(unsigned long)win->phys_addr +
(unsigned long)win->offset_u,
DC_WINBUF_START_ADDR_U);
tegra_dc_writel(dc,
(unsigned long)win->phys_addr +
(unsigned long)win->offset_v,
DC_WINBUF_START_ADDR_V);
tegra_dc_writel(dc,
LINE_STRIDE(win->stride) |
UV_LINE_STRIDE(win->stride_uv),
DC_WIN_LINE_STRIDE);
}
h_offset = win->x;
if (invert_h) {
h_offset += win->w - 1;
}
h_offset *= tegra_dc_fmt_bpp(win->fmt) / 8;
v_offset = win->y;
if (invert_v) {
v_offset += win->h - 1;
}
tegra_dc_writel(dc, h_offset, DC_WINBUF_ADDR_H_OFFSET);
tegra_dc_writel(dc, v_offset, DC_WINBUF_ADDR_V_OFFSET);
if (WIN_IS_TILED(win))
tegra_dc_writel(dc,
DC_WIN_BUFFER_ADDR_MODE_TILE |
DC_WIN_BUFFER_ADDR_MODE_TILE_UV,
DC_WIN_BUFFER_ADDR_MODE);
else
tegra_dc_writel(dc,
DC_WIN_BUFFER_ADDR_MODE_LINEAR |
DC_WIN_BUFFER_ADDR_MODE_LINEAR_UV,
DC_WIN_BUFFER_ADDR_MODE);
val = WIN_ENABLE;
if (yuvp)
val |= CSC_ENABLE;
else if (tegra_dc_fmt_bpp(win->fmt) < 24)
val |= COLOR_EXPAND;
if (WIN_USE_H_FILTER(win))
val |= H_FILTER_ENABLE;
if (WIN_USE_V_FILTER(win))
val |= V_FILTER_ENABLE;
if (invert_h)
val |= H_DIRECTION_DECREMENT;
if (invert_v)
val |= V_DIRECTION_DECREMENT;
tegra_dc_writel(dc, val, DC_WIN_WIN_OPTIONS);
win->dirty = no_vsync ? 0 : 1;
}
if (update_blend) {
tegra_dc_set_blending(dc, &dc->blend);
for (i = 0; i < DC_N_WINDOWS; i++) {
if (!no_vsync)
dc->windows[i].dirty = 1;
update_mask |= WIN_A_ACT_REQ << i;
}
}
tegra_dc_writel(dc, update_mask << 8, DC_CMD_STATE_CONTROL);
if (!no_vsync) {
val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE);
val |= FRAME_END_INT;
tegra_dc_writel(dc, val, DC_CMD_INT_ENABLE);
val = tegra_dc_readl(dc, DC_CMD_INT_MASK);
val |= FRAME_END_INT;
tegra_dc_writel(dc, val, DC_CMD_INT_MASK);
}
tegra_dc_writel(dc, update_mask, DC_CMD_STATE_CONTROL);
mutex_unlock(&dc->lock);
return 0;
}
EXPORT_SYMBOL(tegra_dc_update_windows);
u32 tegra_dc_get_syncpt_id(const struct tegra_dc *dc)
{
return dc->syncpt_id;
}
EXPORT_SYMBOL(tegra_dc_get_syncpt_id);
u32 tegra_dc_incr_syncpt_max(struct tegra_dc *dc)
{
u32 max;
mutex_lock(&dc->lock);
max = nvhost_syncpt_incr_max(&dc->ndev->host->syncpt, dc->syncpt_id, 1);
dc->syncpt_max = max;
mutex_unlock(&dc->lock);
return max;
}
void tegra_dc_incr_syncpt_min(struct tegra_dc *dc, u32 val)
{
mutex_lock(&dc->lock);
while (dc->syncpt_min < val) {
dc->syncpt_min++;
nvhost_syncpt_cpu_incr(&dc->ndev->host->syncpt, dc->syncpt_id);
}
mutex_unlock(&dc->lock);
}
static bool tegra_dc_windows_are_clean(struct tegra_dc_win *windows[],
int n)
{
int i;
for (i = 0; i < n; i++) {
if (windows[i]->dirty)
return false;
}
return true;
}
/* does not support syncing windows on multiple dcs in one call */
int tegra_dc_sync_windows(struct tegra_dc_win *windows[], int n)
{
if (n < 1 || n > DC_N_WINDOWS)
return -EINVAL;
if (!windows[0]->dc->enabled)
return -EFAULT;
return wait_event_interruptible_timeout(windows[0]->dc->wq,
tegra_dc_windows_are_clean(windows, n),
HZ);
}
EXPORT_SYMBOL(tegra_dc_sync_windows);
static unsigned long tegra_dc_pclk_round_rate(struct tegra_dc *dc, int pclk)
{
unsigned long rate;
unsigned long div;
rate = clk_get_rate(dc->clk);
div = DIV_ROUND_CLOSEST(rate * 2, pclk);
if (div < 2)
return 0;
return rate * 2 / div;
}
void tegra_dc_setup_clk(struct tegra_dc *dc, struct clk *clk)
{
int pclk;
if (dc->out->type == TEGRA_DC_OUT_HDMI) {
unsigned long rate;
struct clk *pll_d_out0_clk =
clk_get_sys(NULL, "pll_d_out0");
struct clk *pll_d_clk =
clk_get_sys(NULL, "pll_d");
if (dc->mode.pclk > 70000000)
rate = 594000000;
else
rate = 216000000;
if (rate != clk_get_rate(pll_d_clk))
clk_set_rate(pll_d_clk, rate);
if (clk_get_parent(clk) != pll_d_out0_clk)
clk_set_parent(clk, pll_d_out0_clk);
}
pclk = tegra_dc_pclk_round_rate(dc, dc->mode.pclk);
tegra_dvfs_set_rate(clk, pclk);
}
static int tegra_dc_program_mode(struct tegra_dc *dc, struct tegra_dc_mode *mode)
{
unsigned long val;
unsigned long rate;
unsigned long div;
unsigned long pclk;
tegra_dc_writel(dc, 0x0, DC_DISP_DISP_TIMING_OPTIONS);
tegra_dc_writel(dc, mode->h_ref_to_sync | (mode->v_ref_to_sync << 16),
DC_DISP_REF_TO_SYNC);
tegra_dc_writel(dc, mode->h_sync_width | (mode->v_sync_width << 16),
DC_DISP_SYNC_WIDTH);
tegra_dc_writel(dc, mode->h_back_porch | (mode->v_back_porch << 16),
DC_DISP_BACK_PORCH);
tegra_dc_writel(dc, mode->h_active | (mode->v_active << 16),
DC_DISP_DISP_ACTIVE);
tegra_dc_writel(dc, mode->h_front_porch | (mode->v_front_porch << 16),
DC_DISP_FRONT_PORCH);
tegra_dc_writel(dc, DE_SELECT_ACTIVE | DE_CONTROL_NORMAL,
DC_DISP_DATA_ENABLE_OPTIONS);
val = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_POLARITY1);
if (mode->flags & TEGRA_DC_MODE_FLAG_NEG_V_SYNC)
val |= PIN1_LVS_OUTPUT;
else
val &= ~PIN1_LVS_OUTPUT;
if (mode->flags & TEGRA_DC_MODE_FLAG_NEG_H_SYNC)
val |= PIN1_LHS_OUTPUT;
else
val &= ~PIN1_LHS_OUTPUT;
tegra_dc_writel(dc, val, DC_COM_PIN_OUTPUT_POLARITY1);
/* TODO: MIPI/CRT/HDMI clock cals */
val = DISP_DATA_FORMAT_DF1P1C;
if (dc->out->align == TEGRA_DC_ALIGN_MSB)
val |= DISP_DATA_ALIGNMENT_MSB;
else
val |= DISP_DATA_ALIGNMENT_LSB;
if (dc->out->order == TEGRA_DC_ORDER_RED_BLUE)
val |= DISP_DATA_ORDER_RED_BLUE;
else
val |= DISP_DATA_ORDER_BLUE_RED;
tegra_dc_writel(dc, val, DC_DISP_DISP_INTERFACE_CONTROL);
rate = clk_get_rate(dc->clk);
pclk = tegra_dc_pclk_round_rate(dc, mode->pclk);
if (pclk < (mode->pclk / 100 * 99) ||
pclk > (mode->pclk / 100 * 109)) {
dev_err(&dc->ndev->dev,
"can't divide %ld clock to %d -1/+9%% %ld %d %d\n",
rate, mode->pclk,
pclk, (mode->pclk / 100 * 99),
(mode->pclk / 100 * 109));
return -EINVAL;
}
div = (rate * 2 / pclk) - 2;
tegra_dc_writel(dc, 0x00010001,
DC_DISP_SHIFT_CLOCK_OPTIONS);
tegra_dc_writel(dc, PIXEL_CLK_DIVIDER_PCD1 | SHIFT_CLK_DIVIDER(div),
DC_DISP_DISP_CLOCK_CONTROL);
return 0;
}
int tegra_dc_set_mode(struct tegra_dc *dc, const struct tegra_dc_mode *mode)
{
memcpy(&dc->mode, mode, sizeof(dc->mode));
return 0;
}
EXPORT_SYMBOL(tegra_dc_set_mode);
static void tegra_dc_set_out(struct tegra_dc *dc, struct tegra_dc_out *out)
{
dc->out = out;
if (out->n_modes > 0)
tegra_dc_set_mode(dc, &dc->out->modes[0]);
switch (out->type) {
case TEGRA_DC_OUT_RGB:
dc->out_ops = &tegra_dc_rgb_ops;
break;
case TEGRA_DC_OUT_HDMI:
dc->out_ops = &tegra_dc_hdmi_ops;
break;
default:
dc->out_ops = NULL;
break;
}
if (dc->out_ops && dc->out_ops->init)
dc->out_ops->init(dc);
}
unsigned tegra_dc_get_out_height(struct tegra_dc *dc)
{
if (dc->out)
return dc->out->height;
else
return 0;
}
EXPORT_SYMBOL(tegra_dc_get_out_height);
unsigned tegra_dc_get_out_width(struct tegra_dc *dc)
{
if (dc->out)
return dc->out->width;
else
return 0;
}
EXPORT_SYMBOL(tegra_dc_get_out_width);
static irqreturn_t tegra_dc_irq(int irq, void *ptr)
{
struct tegra_dc *dc = ptr;
unsigned long status;
unsigned long val;
unsigned long underflow_mask;
int i;
status = tegra_dc_readl(dc, DC_CMD_INT_STATUS);
tegra_dc_writel(dc, status, DC_CMD_INT_STATUS);
if (status & FRAME_END_INT) {
int completed = 0;
int dirty = 0;
val = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
for (i = 0; i < DC_N_WINDOWS; i++) {
if (!(val & (WIN_A_UPDATE << i))) {
dc->windows[i].dirty = 0;
completed = 1;
} else {
dirty = 1;
}
}
if (!dirty) {
val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE);
val &= ~FRAME_END_INT;
tegra_dc_writel(dc, val, DC_CMD_INT_ENABLE);
}
if (completed)
wake_up(&dc->wq);
}
/*
* Overlays can get thier internal state corrupted during and underflow
* condition. The only way to fix this state is to reset the DC.
* if we get 4 consecutive frames with underflows, assume we're
* hosed and reset.
*/
underflow_mask = status & (WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT);
if (underflow_mask) {
val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE);
val |= V_BLANK_INT;
tegra_dc_writel(dc, val, DC_CMD_INT_ENABLE);
dc->underflow_mask |= underflow_mask;
}
if (status & V_BLANK_INT) {
int i;
for (i = 0; i< DC_N_WINDOWS; i++) {
if (dc->underflow_mask & (WIN_A_UF_INT <<i)) {
dc->windows[i].underflows++;
if (dc->windows[i].underflows > 4)
schedule_work(&dc->reset_work);
} else {
dc->windows[i].underflows = 0;
}
}
if (!dc->underflow_mask) {
val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE);
val &= ~V_BLANK_INT;
tegra_dc_writel(dc, val, DC_CMD_INT_ENABLE);
}
dc->underflow_mask = 0;
}
return IRQ_HANDLED;
}
static void tegra_dc_set_color_control(struct tegra_dc *dc)
{
u32 color_control;
switch (dc->out->depth) {
case 3:
color_control = BASE_COLOR_SIZE111;
break;
case 6:
color_control = BASE_COLOR_SIZE222;
break;
case 8:
color_control = BASE_COLOR_SIZE332;
break;
case 9:
color_control = BASE_COLOR_SIZE333;
break;
case 12:
color_control = BASE_COLOR_SIZE444;
break;
case 15:
color_control = BASE_COLOR_SIZE555;
break;
case 16:
color_control = BASE_COLOR_SIZE565;
break;
case 18:
color_control = BASE_COLOR_SIZE666;
break;
default:
color_control = BASE_COLOR_SIZE888;
break;
}
tegra_dc_writel(dc, color_control, DC_DISP_DISP_COLOR_CONTROL);
}
static void tegra_dc_init(struct tegra_dc *dc)
{
u32 disp_syncpt;
u32 vblank_syncpt;
int i;
tegra_dc_writel(dc, 0x00000100, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL);
if (dc->ndev->id == 0) {
disp_syncpt = NVSYNCPT_DISP0;
vblank_syncpt = NVSYNCPT_VBLANK0;
tegra_mc_set_priority(TEGRA_MC_CLIENT_DISPLAY0A,
TEGRA_MC_PRIO_MED);
tegra_mc_set_priority(TEGRA_MC_CLIENT_DISPLAY0B,
TEGRA_MC_PRIO_MED);
tegra_mc_set_priority(TEGRA_MC_CLIENT_DISPLAY0C,
TEGRA_MC_PRIO_MED);
tegra_mc_set_priority(TEGRA_MC_CLIENT_DISPLAY1B,
TEGRA_MC_PRIO_MED);
tegra_mc_set_priority(TEGRA_MC_CLIENT_DISPLAYHC,
TEGRA_MC_PRIO_HIGH);
} else if (dc->ndev->id == 1) {
disp_syncpt = NVSYNCPT_DISP1;
vblank_syncpt = NVSYNCPT_VBLANK1;
tegra_mc_set_priority(TEGRA_MC_CLIENT_DISPLAY0AB,
TEGRA_MC_PRIO_MED);
tegra_mc_set_priority(TEGRA_MC_CLIENT_DISPLAY0BB,
TEGRA_MC_PRIO_MED);
tegra_mc_set_priority(TEGRA_MC_CLIENT_DISPLAY0CB,
TEGRA_MC_PRIO_MED);
tegra_mc_set_priority(TEGRA_MC_CLIENT_DISPLAY1BB,
TEGRA_MC_PRIO_MED);
tegra_mc_set_priority(TEGRA_MC_CLIENT_DISPLAYHCB,
TEGRA_MC_PRIO_HIGH);
}
tegra_dc_writel(dc, 0x00000100 | vblank_syncpt, DC_CMD_CONT_SYNCPT_VSYNC);
tegra_dc_writel(dc, 0x00004700, DC_CMD_INT_TYPE);
tegra_dc_writel(dc, 0x0001c700, DC_CMD_INT_POLARITY);
tegra_dc_writel(dc, 0x00202020, DC_DISP_MEM_HIGH_PRIORITY);
tegra_dc_writel(dc, 0x00010101, DC_DISP_MEM_HIGH_PRIORITY_TIMER);
tegra_dc_writel(dc, (FRAME_END_INT |
V_BLANK_INT |
WIN_A_UF_INT |
WIN_B_UF_INT |
WIN_C_UF_INT), DC_CMD_INT_MASK);
tegra_dc_writel(dc, (WIN_A_UF_INT |
WIN_B_UF_INT |
WIN_C_UF_INT), DC_CMD_INT_ENABLE);
tegra_dc_writel(dc, 0x00000000, DC_DISP_BORDER_COLOR);
tegra_dc_set_color_control(dc);
for (i = 0; i < DC_N_WINDOWS; i++) {
tegra_dc_writel(dc, WINDOW_A_SELECT << i,
DC_CMD_DISPLAY_WINDOW_HEADER);
tegra_dc_set_csc(dc);
tegra_dc_set_scaling_filter(dc);
}
dc->syncpt_id = disp_syncpt;
dc->syncpt_min = dc->syncpt_max =
nvhost_syncpt_read(&dc->ndev->host->syncpt, disp_syncpt);
if (dc->mode.pclk)
tegra_dc_program_mode(dc, &dc->mode);
}
static bool _tegra_dc_enable(struct tegra_dc *dc)
{
if (dc->mode.pclk == 0)
return false;
tegra_dc_io_start(dc);
if (dc->out && dc->out->enable)
dc->out->enable();
tegra_dc_setup_clk(dc, dc->clk);
clk_enable(dc->clk);
clk_enable(dc->emc_clk);
tegra_periph_reset_deassert(dc->clk);
msleep(10);
enable_irq(dc->irq);
tegra_dc_init(dc);
if (dc->out_ops && dc->out_ops->enable)
dc->out_ops->enable(dc);
/* force a full blending update */
dc->blend.z[0] = -1;
return true;
}
void tegra_dc_enable(struct tegra_dc *dc)
{
mutex_lock(&dc->lock);
if (!dc->enabled)
dc->enabled = _tegra_dc_enable(dc);
mutex_unlock(&dc->lock);
}
static void _tegra_dc_disable(struct tegra_dc *dc)
{
disable_irq(dc->irq);
if (dc->out_ops && dc->out_ops->disable)
dc->out_ops->disable(dc);
clk_disable(dc->emc_clk);
clk_disable(dc->clk);
tegra_dvfs_set_rate(dc->clk, 0);
if (dc->out && dc->out->disable)
dc->out->disable();
/* flush any pending syncpt waits */
while (dc->syncpt_min < dc->syncpt_max) {
dc->syncpt_min++;
nvhost_syncpt_cpu_incr(&dc->ndev->host->syncpt, dc->syncpt_id);
}
tegra_dc_io_end(dc);
}
void tegra_dc_disable(struct tegra_dc *dc)
{
mutex_lock(&dc->lock);
if (dc->enabled) {
dc->enabled = false;
if (!dc->suspended)
_tegra_dc_disable(dc);
}
mutex_unlock(&dc->lock);
}
static void tegra_dc_reset_worker(struct work_struct *work)
{
struct tegra_dc *dc =
container_of(work, struct tegra_dc, reset_work);
dev_warn(&dc->ndev->dev, "overlay stuck in underflow state. resetting.\n");
mutex_lock(&dc->lock);
if (dc->enabled && !dc->suspended) {
_tegra_dc_disable(dc);
/* A necessary wait. */
msleep(100);
tegra_periph_reset_assert(dc->clk);
/* _tegra_dc_enable deasserts reset */
_tegra_dc_enable(dc);
}
mutex_unlock(&dc->lock);
}
static int tegra_dc_probe(struct nvhost_device *ndev)
{
struct tegra_dc *dc;
struct clk *clk;
struct clk *emc_clk;
struct resource *res;
struct resource *base_res;
struct resource *fb_mem = NULL;
int ret = 0;
void __iomem *base;
int irq;
int i;
unsigned long emc_clk_rate;
if (!ndev->dev.platform_data) {
dev_err(&ndev->dev, "no platform data\n");
return -ENOENT;
}
dc = kzalloc(sizeof(struct tegra_dc), GFP_KERNEL);
if (!dc) {
dev_err(&ndev->dev, "can't allocate memory for tegra_dc\n");
return -ENOMEM;
}
irq = nvhost_get_irq_byname(ndev, "irq");
if (irq <= 0) {
dev_err(&ndev->dev, "no irq\n");
ret = -ENOENT;
goto err_free;
}
res = nvhost_get_resource_byname(ndev, IORESOURCE_MEM, "regs");
if (!res) {
dev_err(&ndev->dev, "no mem resource\n");
ret = -ENOENT;
goto err_free;
}
base_res = request_mem_region(res->start, resource_size(res), ndev->name);
if (!base_res) {
dev_err(&ndev->dev, "request_mem_region failed\n");
ret = -EBUSY;
goto err_free;
}
base = ioremap(res->start, resource_size(res));
if (!base) {
dev_err(&ndev->dev, "registers can't be mapped\n");
ret = -EBUSY;
goto err_release_resource_reg;
}
fb_mem = nvhost_get_resource_byname(ndev, IORESOURCE_MEM, "fbmem");
clk = clk_get(&ndev->dev, NULL);
if (IS_ERR_OR_NULL(clk)) {
dev_err(&ndev->dev, "can't get clock\n");
ret = -ENOENT;
goto err_iounmap_reg;
}
emc_clk = clk_get(&ndev->dev, "emc");
if (IS_ERR_OR_NULL(emc_clk)) {
dev_err(&ndev->dev, "can't get emc clock\n");
ret = -ENOENT;
goto err_put_clk;
}
dc->clk = clk;
dc->emc_clk = emc_clk;
INIT_DELAYED_WORK(&dc->reduce_emc_clk_work, tegra_dc_reduce_emc_worker);
dc->base_res = base_res;
dc->base = base;
dc->irq = irq;
dc->ndev = ndev;
dc->pdata = ndev->dev.platform_data;
/*
* The emc is a shared clock, it will be set based on
* the requirements for each user on the bus.
*/
dc->emc_clk_rate = tegra_dc_get_default_emc_clk_rate(dc);
clk_set_rate(emc_clk, dc->emc_clk_rate);
if (dc->pdata->flags & TEGRA_DC_FLAG_ENABLED)
dc->enabled = true;
mutex_init(&dc->lock);
init_waitqueue_head(&dc->wq);
INIT_WORK(&dc->reset_work, tegra_dc_reset_worker);
dc->n_windows = DC_N_WINDOWS;
for (i = 0; i < dc->n_windows; i++) {
dc->windows[i].idx = i;
dc->windows[i].dc = dc;
}
if (request_irq(irq, tegra_dc_irq, IRQF_DISABLED,
dev_name(&ndev->dev), dc)) {
dev_err(&ndev->dev, "request_irq %d failed\n", irq);
ret = -EBUSY;
goto err_put_emc_clk;
}
/* hack to ballence enable_irq calls in _tegra_dc_enable() */
disable_irq(dc->irq);
ret = tegra_dc_set(dc, ndev->id);
if (ret < 0) {
dev_err(&ndev->dev, "can't add dc\n");
goto err_free_irq;
}
nvhost_set_drvdata(ndev, dc);
if (dc->pdata->default_out)
tegra_dc_set_out(dc, dc->pdata->default_out);
else
dev_err(&ndev->dev, "No default output specified. Leaving output disabled.\n");
if (dc->enabled)
_tegra_dc_enable(dc);
tegra_dc_dbg_add(dc);
dev_info(&ndev->dev, "probed\n");
if (dc->pdata->fb) {
if (dc->pdata->fb->bits_per_pixel == -1) {
unsigned long fmt;
tegra_dc_writel(dc,
WINDOW_A_SELECT << dc->pdata->fb->win,
DC_CMD_DISPLAY_WINDOW_HEADER);
fmt = tegra_dc_readl(dc, DC_WIN_COLOR_DEPTH);
dc->pdata->fb->bits_per_pixel =
tegra_dc_fmt_bpp(fmt);
}
dc->fb = tegra_fb_register(ndev, dc, dc->pdata->fb, fb_mem);
if (IS_ERR_OR_NULL(dc->fb))
dc->fb = NULL;
}
if (dc->out_ops && dc->out_ops->detect)
dc->out_ops->detect(dc);
return 0;
err_free_irq:
free_irq(irq, dc);
err_put_emc_clk:
clk_put(emc_clk);
err_put_clk:
clk_put(clk);
err_iounmap_reg:
iounmap(base);
if (fb_mem)
release_resource(fb_mem);
err_release_resource_reg:
release_resource(base_res);
err_free:
kfree(dc);
return ret;
}
static int tegra_dc_remove(struct nvhost_device *ndev)
{
struct tegra_dc *dc = nvhost_get_drvdata(ndev);
if (dc->fb) {
tegra_fb_unregister(dc->fb);
if (dc->fb_mem)
release_resource(dc->fb_mem);
}
if (dc->enabled)
_tegra_dc_disable(dc);
free_irq(dc->irq, dc);
clk_put(dc->emc_clk);
clk_put(dc->clk);
iounmap(dc->base);
if (dc->fb_mem)
release_resource(dc->base_res);
kfree(dc);
tegra_dc_set(NULL, ndev->id);
return 0;
}
#ifdef CONFIG_PM
static int tegra_dc_suspend(struct nvhost_device *ndev, pm_message_t state)
{
struct tegra_dc *dc = nvhost_get_drvdata(ndev);
dev_info(&ndev->dev, "suspend\n");
mutex_lock(&dc->lock);
if (dc->out_ops && dc->out_ops->suspend)
dc->out_ops->suspend(dc);
if (dc->enabled) {
_tegra_dc_disable(dc);
dc->suspended = true;
}
mutex_unlock(&dc->lock);
return 0;
}
static int tegra_dc_resume(struct nvhost_device *ndev)
{
struct tegra_dc *dc = nvhost_get_drvdata(ndev);
dev_info(&ndev->dev, "resume\n");
mutex_lock(&dc->lock);
dc->suspended = false;
if (dc->enabled)
_tegra_dc_enable(dc);
if (dc->out_ops && dc->out_ops->resume)
dc->out_ops->resume(dc);
mutex_unlock(&dc->lock);
return 0;
}
#endif
extern int suspend_set(const char *val, struct kernel_param *kp)
{
if (!strcmp(val, "dump"))
dump_regs(tegra_dcs[0]);
#ifdef CONFIG_PM
else if (!strcmp(val, "suspend"))
tegra_dc_suspend(tegra_dcs[0]->ndev, PMSG_SUSPEND);
else if (!strcmp(val, "resume"))
tegra_dc_resume(tegra_dcs[0]->ndev);
#endif
return 0;
}
extern int suspend_get(char *buffer, struct kernel_param *kp)
{
return 0;
}
int suspend;
module_param_call(suspend, suspend_set, suspend_get, &suspend, 0644);
struct nvhost_driver tegra_dc_driver = {
.driver = {
.name = "tegradc",
.owner = THIS_MODULE,
},
.probe = tegra_dc_probe,
.remove = tegra_dc_remove,
#ifdef CONFIG_PM
.suspend = tegra_dc_suspend,
.resume = tegra_dc_resume,
#endif
};
static int __init tegra_dc_module_init(void)
{
return nvhost_driver_register(&tegra_dc_driver);
}
static void __exit tegra_dc_module_exit(void)
{
nvhost_driver_unregister(&tegra_dc_driver);
}
module_exit(tegra_dc_module_exit);
module_init(tegra_dc_module_init);