diff options
Diffstat (limited to 'drivers/video/tegra')
-rw-r--r-- | drivers/video/tegra/dc/dc.c | 227 | ||||
-rw-r--r-- | drivers/video/tegra/dc/dc_priv.h | 39 | ||||
-rw-r--r-- | drivers/video/tegra/dc/overlay.c | 8 | ||||
-rw-r--r-- | drivers/video/tegra/fb.c | 11 |
4 files changed, 273 insertions, 12 deletions
diff --git a/drivers/video/tegra/dc/dc.c b/drivers/video/tegra/dc/dc.c index b46f8b1eb47a..341e52b64e6c 100644 --- a/drivers/video/tegra/dc/dc.c +++ b/drivers/video/tegra/dc/dc.c @@ -33,6 +33,7 @@ #include <linux/debugfs.h> #include <linux/seq_file.h> #include <linux/backlight.h> +#include <video/tegrafb.h> #include <mach/clk.h> #include <mach/dc.h> @@ -49,6 +50,16 @@ 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); + +/* set default windows idle time as 2000ms for power saving purpose */ +static int windows_idle_detection_time = 2000; + +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); @@ -445,7 +456,7 @@ static inline void tegra_dc_create_debugfs(struct tegra_dc *dc) { }; static inline void __devexit tegra_dc_remove_debugfs(struct tegra_dc *dc) { }; #endif /* CONFIG_DEBUGFS */ -static int tegra_dc_add(struct tegra_dc *dc, int index) +static int tegra_dc_set(struct tegra_dc *dc, int index) { int ret = 0; @@ -455,7 +466,7 @@ static int tegra_dc_add(struct tegra_dc *dc, int index) goto out; } - if (tegra_dcs[index] != NULL) { + if (dc != NULL && tegra_dcs[index] != NULL) { ret = -EBUSY; goto out; } @@ -468,6 +479,20 @@ out: 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) @@ -592,6 +617,186 @@ static void tegra_dc_set_scaling_filter(struct tegra_dc *dc) } } +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 +/* + * Assuming 50% (X >> 1) efficiency: i.e. if we calculate we need 70MBps, we + * will request 140MBps from EMC. + */ +#define MEM_EFFICIENCY_SHIFT 1 +static unsigned long tegra_dc_get_emc_rate(struct tegra_dc_win *wins[], int n) +{ + int i; + unsigned int bw[TEGRA_FB_FLIP_N_WINDOWS]; + struct tegra_dc_win *w; + struct tegra_dc *dc; + unsigned int max; + unsigned int ret; + + dc = wins[0]->dc; + + if (tegra_dc_has_multiple_dc()) + return tegra_dc_get_default_emc_clk_rate(dc); + + BUG_ON(n > ARRAY_SIZE(bw)); + /* + * Calculate peak EMC bandwidth for each enabled window = + * pixel_clock * win_bpp * (use_v_filter ? 2 : 1)) * H_scale_factor * + * (windows_tiling ? 2 : 1) + * + * + * note: + * (*) We use 2 tap V filter, so need double BW if use V filter + * (*) Tiling mode on T30 and DDR3 requires double BW + */ + for (i = 0; w = wins[i], bw[i] = 0, i < n; i++) { + if (!WIN_IS_ENABLED(w)) + continue; + bw[i] = dc->mode.pclk * + (tegra_dc_fmt_bpp(w->fmt) >> BIT_TO_BYTE_SHIFT) * + (WIN_USE_V_FILTER(w) ? 2 : 1) / + w->out_w * w->w * + (WIN_IS_TILED(w) ? TILED_WINDOWS_BW_MULTIPLIER : 1); + } + + max = tegra_dc_find_max_bandwidth(wins, bw, n) << MEM_EFFICIENCY_SHIFT; + + ret = EMC_BW_TO_FREQ(max); + + /* + * If the calculated peak BW is bigger than board specified BW, then + * either the above calculation is wrong, or board specified BW is + * wrong. + */ + WARN_ON(ret > tegra_dc_get_default_emc_clk_rate(dc)); + + return ret; +} +#undef BIT_TO_BYTE_SHIFT +#undef MEM_EFFICIENCY_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_dynamic_emc(struct tegra_dc_win *windows[], int n) +{ + unsigned long new_rate; + struct tegra_dc *dc; + + if (!use_dynamic_emc) + return 0; + + dc = windows[0]->dc; + + mutex_lock(&dc->lock); + + if (!dc->enabled) { + mutex_unlock(&dc->lock); + return -EFAULT; + } + + /* calculate the new rate based on this POST */ + new_rate = tegra_dc_get_emc_rate(windows, n); + + dc->new_emc_clk_rate = new_rate; + + /* + * If we don't need set EMC immediately after a frame POST, we schedule + * a work_queue to reduce EMC in the future. This work_queue task will + * not be executed if the another POST comes before the idle time + * expired. + */ + if (NEED_UPDATE_EMC_ON_EVERY_FRAME) + tegra_dc_change_emc(dc); + else + schedule_delayed_work(&dc->reduce_emc_clk_work, + msecs_to_jiffies(windows_idle_detection_time)); + + mutex_unlock(&dc->lock); + + return 0; +} + +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) { @@ -642,7 +847,7 @@ int tegra_dc_update_windows(struct tegra_dc_win *windows[], int n) if (!no_vsync) update_mask |= WIN_A_ACT_REQ << win->idx; - if (!(win->flags & TEGRA_WIN_FLAG_ENABLED)) { + if (!WIN_IS_ENABLED(win)) { tegra_dc_writel(dc, 0, DC_WIN_WIN_OPTIONS); continue; } @@ -706,7 +911,7 @@ int tegra_dc_update_windows(struct tegra_dc_win *windows[], int n) tegra_dc_writel(dc, h_offset, DC_WINBUF_ADDR_H_OFFSET); tegra_dc_writel(dc, v_offset, DC_WINBUF_ADDR_V_OFFSET); - if (win->flags & TEGRA_WIN_FLAG_TILED) + if (WIN_IS_TILED(win)) tegra_dc_writel(dc, DC_WIN_BUFFER_ADDR_MODE_TILE | DC_WIN_BUFFER_ADDR_MODE_TILE_UV, @@ -723,9 +928,9 @@ int tegra_dc_update_windows(struct tegra_dc_win *windows[], int n) else if (tegra_dc_fmt_bpp(win->fmt) < 24) val |= COLOR_EXPAND; - if (win->w != win->out_w) + if (WIN_USE_H_FILTER(win)) val |= H_FILTER_ENABLE; - if (win->h != win->out_h) + if (WIN_USE_V_FILTER(win)) val |= V_FILTER_ENABLE; if (invert_h) @@ -1855,7 +2060,6 @@ static int tegra_dc_probe(struct nvhost_device *ndev) 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"); @@ -1914,6 +2118,8 @@ static int tegra_dc_probe(struct nvhost_device *ndev) 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; @@ -1924,8 +2130,8 @@ static int tegra_dc_probe(struct nvhost_device *ndev) * The emc is a shared clock, it will be set based on * the requirements for each user on the bus. */ - emc_clk_rate = dc->pdata->emc_clk_rate; - clk_set_rate(emc_clk, emc_clk_rate ? emc_clk_rate : ULONG_MAX); + 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; @@ -1954,7 +2160,7 @@ static int tegra_dc_probe(struct nvhost_device *ndev) /* hack to balance enable_irq calls in _tegra_dc_enable() */ disable_dc_irq(dc->irq); - ret = tegra_dc_add(dc, ndev->id); + ret = tegra_dc_set(dc, ndev->id); if (ret < 0) { dev_err(&ndev->dev, "can't add dc\n"); goto err_free_irq; @@ -2055,6 +2261,7 @@ static int tegra_dc_remove(struct nvhost_device *ndev) if (dc->fb_mem) release_resource(dc->base_res); kfree(dc); + tegra_dc_set(NULL, ndev->id); return 0; } diff --git a/drivers/video/tegra/dc/dc_priv.h b/drivers/video/tegra/dc/dc_priv.h index f6b560b98801..77ed9afe2661 100644 --- a/drivers/video/tegra/dc/dc_priv.h +++ b/drivers/video/tegra/dc/dc_priv.h @@ -26,6 +26,34 @@ #include "../host/dev.h" +#define WIN_IS_TILED(win) ((win)->flags & TEGRA_WIN_FLAG_TILED) +#define WIN_IS_ENABLED(win) ((win)->flags & TEGRA_WIN_FLAG_ENABLED) +#define WIN_USE_V_FILTER(win) ((win)->h != (win)->out_h) +#define WIN_USE_H_FILTER(win) ((win)->w != (win)->out_w) + +#define NEED_UPDATE_EMC_ON_EVERY_FRAME (windows_idle_detection_time == 0) + +/* DDR: 8 bytes transfer per clock */ +#define DDR_BW_TO_FREQ(bw) ((bw) / 8) + +#if defined(CONFIG_TEGRA_EMC_TO_DDR_CLOCK) +#define EMC_BW_TO_FREQ(bw) (DDR_BW_TO_FREQ(bw) * CONFIG_TEGRA_EMC_TO_DDR_CLOCK) +#else +#define EMC_BW_TO_FREQ(bw) (DDR_BW_TO_FREQ(bw) * 2) +#endif + +/* + * If using T30/DDR3, the 2nd 16 bytes part of DDR3 atom is 2nd line and is + * discarded in tiling mode. + */ +#if defined(CONFIG_ARCH_TEGRA_2x_SOC) +#define TILED_WINDOWS_BW_MULTIPLIER 1 +#elif defined(CONFIG_ARCH_TEGRA_3x_SOC) +#define TILED_WINDOWS_BW_MULTIPLIER 2 +#else +#warning "need to revisit memory tiling effects on DC" +#endif + struct tegra_dc; struct tegra_dc_blend { @@ -52,8 +80,6 @@ struct tegra_dc_out_ops { }; struct tegra_dc { - struct list_head list; - struct nvhost_device *ndev; struct tegra_dc_platform_data *pdata; @@ -63,6 +89,8 @@ struct tegra_dc { struct clk *clk; struct clk *emc_clk; + int emc_clk_rate; + int new_emc_clk_rate; bool enabled; bool suspended; @@ -92,6 +120,7 @@ struct tegra_dc { unsigned long underflow_mask; struct work_struct reset_work; + struct delayed_work reduce_emc_clk_work; struct completion vblank_complete; @@ -155,6 +184,12 @@ static inline void *tegra_dc_get_outdata(struct tegra_dc *dc) return dc->out_data; } +static inline unsigned long tegra_dc_get_default_emc_clk_rate( + struct tegra_dc *dc) +{ + return dc->pdata->emc_clk_rate ? dc->pdata->emc_clk_rate : ULONG_MAX; +} + void tegra_dc_setup_clk(struct tegra_dc *dc, struct clk *clk); extern struct tegra_dc_out_ops tegra_dc_rgb_ops; diff --git a/drivers/video/tegra/dc/overlay.c b/drivers/video/tegra/dc/overlay.c index 530944013f58..0272bd8e4107 100644 --- a/drivers/video/tegra/dc/overlay.c +++ b/drivers/video/tegra/dc/overlay.c @@ -310,9 +310,11 @@ static void tegra_overlay_flip_worker(struct work_struct *work) dcwins[i] = tegra_dc_get_window(overlay->dc, i); tegra_overlay_blend_reorder(&overlay->blend, dcwins); + tegra_dc_set_dynamic_emc(dcwins, DC_N_WINDOWS); tegra_dc_update_windows(dcwins, DC_N_WINDOWS); tegra_dc_sync_windows(dcwins, DC_N_WINDOWS); } else { + tegra_dc_set_dynamic_emc(wins, nr_win); tegra_dc_update_windows(wins, nr_win); /* TODO: implement swapinterval here */ tegra_dc_sync_windows(wins, nr_win); @@ -380,6 +382,12 @@ static int tegra_overlay_flip(struct tegra_overlay_info *overlay, queue_work(overlay->flip_wq, &data->work); + /* + * Before the queued flip_wq get scheduled, we set the EMC clock to the + * default value in order to do FLIP without glitch. + */ + tegra_dc_set_default_emc(overlay->dc); + args->post_syncpt_val = syncpt_max; args->post_syncpt_id = tegra_dc_get_syncpt_id(overlay->dc); mutex_unlock(&tegra_flip_lock); diff --git a/drivers/video/tegra/fb.c b/drivers/video/tegra/fb.c index 913feeb49796..2954661a4064 100644 --- a/drivers/video/tegra/fb.c +++ b/drivers/video/tegra/fb.c @@ -279,6 +279,8 @@ static int tegra_fb_pan_display(struct fb_var_screeninfo *var, tegra_fb->win->phys_addr = addr; /* TODO: update virt_addr */ + tegra_dc_set_default_emc(tegra_fb->win->dc); + tegra_dc_set_dynamic_emc(&tegra_fb->win, 1); tegra_dc_update_windows(&tegra_fb->win, 1); tegra_dc_sync_windows(&tegra_fb->win, 1); } @@ -489,6 +491,7 @@ static void tegra_fb_flip_worker(struct work_struct *work) #endif } + tegra_dc_set_dynamic_emc(wins, nr_win); tegra_dc_update_windows(wins, nr_win); /* TODO: implement swapinterval here */ tegra_dc_sync_windows(wins, nr_win); @@ -546,6 +549,12 @@ static int tegra_fb_flip(struct tegra_fb_info *tegra_fb, queue_work(tegra_fb->flip_wq, &data->work); + /* + * Before the queued flip_wq get scheduled, we set the EMC clock to the + * default value in order to do FLIP without glitch. + */ + tegra_dc_set_default_emc(tegra_fb->win->dc); + args->post_syncpt_val = syncpt_max; args->post_syncpt_id = tegra_dc_get_syncpt_id(tegra_fb->win->dc); @@ -840,6 +849,8 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, dev_info(&ndev->dev, "probed\n"); if (fb_data->flags & TEGRA_FB_FLIP_ON_PROBE) { + tegra_dc_set_default_emc(tegra_fb->win->dc); + tegra_dc_set_dynamic_emc(&tegra_fb->win, 1); tegra_dc_update_windows(&tegra_fb->win, 1); tegra_dc_sync_windows(&tegra_fb->win, 1); } |