/* * 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 on T2x/T3x, so need double BW if use V filter * (*) Tiling mode on T30 and DDR3 requires double BW * * return: * bandwidth in kBps */ static unsigned long tegra_dc_calc_win_bandwidth(struct tegra_dc *dc, struct tegra_dc_win *w) { unsigned long ret; int tiled_windows_bw_multiplier; unsigned long bpp; if (!WIN_IS_ENABLED(w)) return 0; if (dfixed_trunc(w->w) == 0 || dfixed_trunc(w->h) == 0 || w->out_w == 0 || w->out_h == 0) return 0; tiled_windows_bw_multiplier = tegra_mc_get_tiled_memory_bandwidth_multiplier(); /* all of tegra's YUV formats(420 and 422) fetch 2 bytes per pixel, * but the size reported by tegra_dc_fmt_bpp for the planar version * is of the luma plane's size only. */ bpp = tegra_dc_is_yuv_planar(w->fmt) ? 2 * tegra_dc_fmt_bpp(w->fmt) : tegra_dc_fmt_bpp(w->fmt); ret = dc->mode.pclk / 1000UL * bpp / 8 * #if defined(CONFIG_ARCH_TEGRA_2x_SOC) || defined(CONFIG_ARCH_TEGRA_3x_SOC) (win_use_v_filter(dc, w) ? 2 : 1) * #endif dfixed_trunc(w->w) / w->out_w * (WIN_IS_TILED(w) ? tiled_windows_bw_multiplier : 1); return ret; }
/* * 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 on T2x/T3x, so need double BW if use V filter * (*) Tiling mode on T30 and DDR3 requires double BW * * return: * bandwidth in kBps */ static unsigned long tegra_dc_calc_win_bandwidth(struct tegra_dc *dc, struct tegra_dc_win *w) { unsigned in_w; unsigned in_h; unsigned bpp; unsigned long f_w; unsigned long f_h; unsigned long bw; int tiled_windows_bw_multiplier; /* ignore windows that are off or invalid */ if (!WIN_IS_ENABLED(w) || dfixed_trunc(w->w) == 0 || dfixed_trunc(w->h) == 0 || w->out_w == 0 || w->out_h == 0) return 0; if (w->flags & TEGRA_WIN_FLAG_SCAN_COLUMN) { /* rotated : prescaled size is swapped */ in_w = dfixed_trunc(w->h); in_h = dfixed_trunc(w->w); } else { /* normal */ in_w = dfixed_trunc(w->w); in_h = dfixed_trunc(w->h); } tiled_windows_bw_multiplier = tegra_mc_get_tiled_memory_bandwidth_multiplier(); /* all of tegra's YUV formats(420 and 422) fetch 2 bytes per pixel, * but the size reported by tegra_dc_fmt_bpp for the planar version * is of the luma plane's size only. */ bpp = tegra_dc_is_yuv_planar(w->fmt) ? 2 * tegra_dc_fmt_bpp(w->fmt) : tegra_dc_fmt_bpp(w->fmt); bw = dc->mode.pclk / 1000UL * bpp / 8; if (WIN_IS_TILED(w)) bw *= tiled_windows_bw_multiplier; #if defined(CONFIG_ARCH_TEGRA_2x_SOC) || defined(CONFIG_ARCH_TEGRA_3x_SOC) if (win_use_v_filter(dc, w, false)) bw *= 2; #endif /* calculate H & V scaling factor. treat upscaling as 1.00 */ f_w = max(in_w * 100 / w->out_w, 100u); f_h = max(in_h * 100 / w->out_h, 100u); bw *= f_w; bw /= 100; if (win_use_v_filter(dc, w, w->flags & TEGRA_WIN_FLAG_SCAN_COLUMN)) { bw *= f_h; bw /= 100; } return 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 on T2x/T3x, so need double BW if use V filter * (*) Tiling mode on T30 and DDR3 requires double BW * * return: * bandwidth in kBps */ static unsigned long tegra_dc_calc_win_bandwidth(struct tegra_dc *dc, struct tegra_dc_win *w) { unsigned long ret; int tiled_windows_bw_multiplier; unsigned long bpp; unsigned in_w; if (!WIN_IS_ENABLED(w)) return 0; if (dfixed_trunc(w->w) == 0 || dfixed_trunc(w->h) == 0 || w->out_w == 0 || w->out_h == 0) return 0; if (w->flags & TEGRA_WIN_FLAG_SCAN_COLUMN) /* rotated: PRESCALE_SIZE swapped, but WIN_SIZE is unchanged */ in_w = dfixed_trunc(w->h); else in_w = dfixed_trunc(w->w); /* normal output, not rotated */ tiled_windows_bw_multiplier = tegra_mc_get_tiled_memory_bandwidth_multiplier(); /* all of tegra's YUV formats(420 and 422) fetch 2 bytes per pixel, * but the size reported by tegra_dc_fmt_bpp for the planar version * is of the luma plane's size only. */ bpp = tegra_dc_is_yuv_planar(w->fmt) ? 2 * tegra_dc_fmt_bpp(w->fmt) : tegra_dc_fmt_bpp(w->fmt); ret = dc->mode.pclk / 1000UL * bpp / 8 * #if defined(CONFIG_ARCH_TEGRA_2x_SOC) || defined(CONFIG_ARCH_TEGRA_3x_SOC) (win_use_v_filter(dc, w) ? 2 : 1) * #endif in_w / w->out_w * (WIN_IS_TILED(w) ? tiled_windows_bw_multiplier : 1); #ifdef CONFIG_ARCH_TEGRA_2x_SOC /* * Assuming 60% efficiency: i.e. if we calculate we need 70MBps, we * will request 117MBps from EMC. */ ret = ret + (17 * ret / 25); #endif return ret; }
/* * 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 * * return: * bandwidth in kBps */ static unsigned long tegra_dc_calc_win_bandwidth(struct tegra_dc *dc, struct tegra_dc_win *w) { unsigned long ret; int tiled_windows_bw_multiplier; unsigned long bpp; if (!WIN_IS_ENABLED(w)) return 0; if (dfixed_trunc(w->w) == 0 || dfixed_trunc(w->h) == 0 || w->out_w == 0 || w->out_h == 0) return 0; tiled_windows_bw_multiplier = tegra_mc_get_tiled_memory_bandwidth_multiplier(); /* all of tegra's YUV formats(420 and 422) fetch 2 bytes per pixel, * but the size reported by tegra_dc_fmt_bpp for the planar version * is of the luma plane's size only. */ bpp = tegra_dc_is_yuv_planar(w->fmt) ? 2 * tegra_dc_fmt_bpp(w->fmt) : tegra_dc_fmt_bpp(w->fmt); ret = dc->mode.pclk / 1000UL * bpp / 8 * ( win_use_v_filter(dc, w) ? 2 : 1) * dfixed_trunc(w->w) / w->out_w * (WIN_IS_TILED(w) ? tiled_windows_bw_multiplier : 1); #ifdef CONFIG_ARCH_TEGRA_2x_SOC /* * Assuming 60% efficiency: i.e. if we calculate we need 70MBps, we * will request 117MBps from EMC. */ ret = ret + (17 * ret / 25); #endif return ret; }
/* 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 win_options; bool update_blend = false; int i; dc = windows[0]->dc; if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) { /* Acquire one_shot_lock to avoid race condition between * cancellation of old delayed work and schedule of new * delayed work. */ mutex_lock(&dc->one_shot_lock); cancel_delayed_work_sync(&dc->one_shot_work); } mutex_lock(&dc->lock); if (!dc->enabled) { mutex_unlock(&dc->lock); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) mutex_unlock(&dc->one_shot_lock); return -EFAULT; } tegra_dc_hold_dc_out(dc); 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]; bool scan_column = 0; fixed20_12 h_offset, 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 yuv = tegra_dc_is_yuv(win->fmt); bool yuvp = tegra_dc_is_yuv_planar(win->fmt); unsigned Bpp = tegra_dc_fmt_bpp(win->fmt) / 8; /* Bytes per pixel of bandwidth, used for dda_inc calculation */ unsigned Bpp_bw = Bpp * (yuvp ? 2 : 1); const bool filter_h = win_use_h_filter(dc, win); const bool filter_v = win_use_v_filter(dc, win); #if defined(CONFIG_TEGRA_DC_SCAN_COLUMN) scan_column = (win->flags & TEGRA_WIN_FLAG_SCAN_COLUMN); #endif 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)) { /*dc->windows[i].dirty = 1; NV patch, but Now we do not use it */ tegra_dc_writel(dc, 0, DC_WIN_WIN_OPTIONS); continue; } tegra_dc_writel(dc, win->fmt & 0x1f, DC_WIN_COLOR_DEPTH); tegra_dc_writel(dc, win->fmt >> 6, 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); /* Check scan_column flag to set window size and scaling. */ win_options = WIN_ENABLE; if (scan_column) { win_options |= WIN_SCAN_COLUMN; win_options |= H_FILTER_ENABLE(filter_v); win_options |= V_FILTER_ENABLE(filter_h); } else { win_options |= H_FILTER_ENABLE(filter_h); win_options |= V_FILTER_ENABLE(filter_v); } /* Update scaling registers if window supports scaling. */ if (likely(tegra_dc_feature_has_scaling(dc, win->idx))) tegra_dc_update_scaling(dc, win, Bpp, Bpp_bw, scan_column); 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_u, DC_WINBUF_START_ADDR_U); tegra_dc_writel(dc, (unsigned long)win->phys_addr_v, DC_WINBUF_START_ADDR_V); tegra_dc_writel(dc, LINE_STRIDE(win->stride) | UV_LINE_STRIDE(win->stride_uv), DC_WIN_LINE_STRIDE); } if (invert_h) { h_offset.full = win->x.full + win->w.full; h_offset.full = dfixed_floor(h_offset) * Bpp; h_offset.full -= dfixed_const(1); } else { h_offset.full = dfixed_floor(win->x) * Bpp; } v_offset = win->y; if (invert_v) { v_offset.full += win->h.full - dfixed_const(1); } tegra_dc_writel(dc, dfixed_trunc(h_offset), DC_WINBUF_ADDR_H_OFFSET); tegra_dc_writel(dc, dfixed_trunc(v_offset), DC_WINBUF_ADDR_V_OFFSET); if (tegra_dc_feature_has_tiling(dc, win->idx)) { 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); } if (yuv) win_options |= CSC_ENABLE; else if (tegra_dc_fmt_bpp(win->fmt) < 24) win_options |= COLOR_EXPAND; #if defined(CONFIG_ARCH_TEGRA_3x_SOC) if (win->global_alpha == 255) { tegra_dc_writel(dc, 0, DC_WIN_GLOBAL_ALPHA); } else { tegra_dc_writel(dc, GLOBAL_ALPHA_ENABLE | win->global_alpha, DC_WIN_GLOBAL_ALPHA); win_options |= CP_ENABLE; } #endif if (win->ppflags & TEGRA_WIN_PPFLAG_CP_ENABLE) win_options |= CP_ENABLE; win_options |= H_DIRECTION_DECREMENT(invert_h); win_options |= V_DIRECTION_DECREMENT(invert_v); tegra_dc_writel(dc, win_options, DC_WIN_WIN_OPTIONS); win->dirty = no_vsync ? 0 : 1; dev_dbg(&dc->ndev->dev, "%s():idx=%d z=%d x=%d y=%d w=%d h=%d " "out_x=%u out_y=%u out_w=%u out_h=%u " "fmt=%d yuvp=%d Bpp=%u filter_h=%d filter_v=%d", __func__, win->idx, win->z, dfixed_trunc(win->x), dfixed_trunc(win->y), dfixed_trunc(win->w), dfixed_trunc(win->h), win->out_x, win->out_y, win->out_w, win->out_h, win->fmt, yuvp, Bpp, filter_h, filter_v); trace_printk("%s:win%u in:%ux%u out:%ux%u fmt=%d\n", dc->ndev->name, win->idx, dfixed_trunc(win->w), dfixed_trunc(win->h), win->out_w, win->out_h, win->fmt); } 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_set_dynamic_emc(windows, n); tegra_dc_writel(dc, update_mask << 8, DC_CMD_STATE_CONTROL); tegra_dc_writel(dc, FRAME_END_INT | V_BLANK_INT, DC_CMD_INT_STATUS); if (!no_vsync) { set_bit(V_BLANK_FLIP, &dc->vblank_ref_count); tegra_dc_unmask_interrupt(dc, FRAME_END_INT | V_BLANK_INT | ALL_UF_INT); } else { clear_bit(V_BLANK_FLIP, &dc->vblank_ref_count); tegra_dc_mask_interrupt(dc, FRAME_END_INT | V_BLANK_INT | ALL_UF_INT); } if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) { atomic_set(&update_frame,1); schedule_delayed_work(&dc->one_shot_work, msecs_to_jiffies(dc->one_shot_delay_ms)); } /* update EMC clock if calculated bandwidth has changed */ tegra_dc_program_bandwidth(dc, false); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) update_mask |= NC_HOST_TRIG; tegra_dc_writel(dc, update_mask, DC_CMD_STATE_CONTROL); trace_printk("%s:update_mask=%#lx\n", dc->ndev->name, update_mask); tegra_dc_release_dc_out(dc); mutex_unlock(&dc->lock); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) mutex_unlock(&dc->one_shot_lock); bool is_yuvp = 0; for (i = 0; i < n; i++) { struct tegra_dc_win *win = windows[i]; bool yuvp = tegra_dc_is_yuv_planar(win->fmt); is_yuvp |= yuvp; } if (dc->ndev->id == 0) { struct tegra_dc_out *out = dc->out; struct tegra_dsi_out *dsi = out->dsi; struct tegra_dsi_cmd *cur = NULL; int n = dsi->n_cabc_cmd; if (out && dsi && dc->out_ops && dc->out_ops->send_cmd) { if (is_yuvp && !dc->isyuv_lasttime) { printk(KERN_INFO "[DISP] YUV\r\n"); cur = dsi->dsi_cabc_still_mode; dc->isyuv_lasttime = is_yuvp; } else if (!is_yuvp && dc->isyuv_lasttime) { printk(KERN_INFO "[DISP] RGB\r\n"); cur = dsi->dsi_cabc_moving_mode; dc->isyuv_lasttime = is_yuvp; } if (cur) { dc->out_ops->send_cmd(dc, cur, n); } } } 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; if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) { /* Acquire one_shot_lock to avoid race condition between * cancellation of old delayed work and schedule of new * delayed work. */ mutex_lock(&dc->one_shot_lock); cancel_delayed_work_sync(&dc->one_shot_work); } mutex_lock(&dc->lock); if (!dc->enabled) { mutex_unlock(&dc->lock); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) mutex_unlock(&dc->one_shot_lock); return -EFAULT; } tegra_dc_hold_dc_out(dc); 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; fixed20_12 h_offset, 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 yuv = tegra_dc_is_yuv(win->fmt); bool yuvp = tegra_dc_is_yuv_planar(win->fmt); unsigned Bpp = tegra_dc_fmt_bpp(win->fmt) / 8; /* Bytes per pixel of bandwidth, used for dda_inc calculation */ unsigned Bpp_bw = Bpp * (yuvp ? 2 : 1); const bool filter_h = win_use_h_filter(dc, win); const bool filter_v = win_use_v_filter(dc, win); 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)) { dc->windows[i].dirty = 1; tegra_dc_writel(dc, 0, DC_WIN_WIN_OPTIONS); continue; } tegra_dc_writel(dc, win->fmt & 0x1f, DC_WIN_COLOR_DEPTH); tegra_dc_writel(dc, win->fmt >> 6, 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); if (tegra_dc_feature_has_scaling(dc, win->idx)) { tegra_dc_writel(dc, V_PRESCALED_SIZE(dfixed_trunc(win->h)) | H_PRESCALED_SIZE(dfixed_trunc(win->w) * Bpp), DC_WIN_PRESCALED_SIZE); h_dda = compute_dda_inc(win->w, win->out_w, false, Bpp_bw); v_dda = compute_dda_inc(win->h, win->out_h, true, Bpp_bw); tegra_dc_writel(dc, V_DDA_INC(v_dda) | H_DDA_INC(h_dda), DC_WIN_DDA_INCREMENT); h_dda = compute_initial_dda(win->x); v_dda = compute_initial_dda(win->y); tegra_dc_writel(dc, h_dda, DC_WIN_H_INITIAL_DDA); tegra_dc_writel(dc, v_dda, DC_WIN_V_INITIAL_DDA); } tegra_dc_writel(dc, 0, DC_WIN_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_u, DC_WINBUF_START_ADDR_U); tegra_dc_writel(dc, (unsigned long)win->phys_addr_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.full += win->w.full - dfixed_const(1); } v_offset = win->y; if (invert_v) { v_offset.full += win->h.full - dfixed_const(1); } tegra_dc_writel(dc, dfixed_trunc(h_offset) * Bpp, DC_WINBUF_ADDR_H_OFFSET); tegra_dc_writel(dc, dfixed_trunc(v_offset), DC_WINBUF_ADDR_V_OFFSET); if (tegra_dc_feature_has_tiling(dc, win->idx)) { 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 (yuv) val |= CSC_ENABLE; else if (tegra_dc_fmt_bpp(win->fmt) < 24) val |= COLOR_EXPAND; if (win->ppflags & TEGRA_WIN_PPFLAG_CP_ENABLE) val |= CP_ENABLE; if (filter_h) val |= H_FILTER_ENABLE; if (filter_v) 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); #ifdef CONFIG_ARCH_TEGRA_3x_SOC if (win->global_alpha == 255) tegra_dc_writel(dc, 0, DC_WIN_GLOBAL_ALPHA); else tegra_dc_writel(dc, GLOBAL_ALPHA_ENABLE | win->global_alpha, DC_WIN_GLOBAL_ALPHA); #endif win->dirty = no_vsync ? 0 : 1; dev_dbg(&dc->ndev->dev, "%s():idx=%d z=%d x=%d y=%d w=%d h=%d " "out_x=%u out_y=%u out_w=%u out_h=%u " "fmt=%d yuvp=%d Bpp=%u filter_h=%d filter_v=%d", __func__, win->idx, win->z, dfixed_trunc(win->x), dfixed_trunc(win->y), dfixed_trunc(win->w), dfixed_trunc(win->h), win->out_x, win->out_y, win->out_w, win->out_h, win->fmt, yuvp, Bpp, filter_h, filter_v); trace_printk("%s:win%u in:%ux%u out:%ux%u fmt=%d\n", dc->ndev->name, win->idx, dfixed_trunc(win->w), dfixed_trunc(win->h), win->out_w, win->out_h, win->fmt); } 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_set_dynamic_emc(windows, n); tegra_dc_writel(dc, update_mask << 8, DC_CMD_STATE_CONTROL); tegra_dc_writel(dc, FRAME_END_INT | V_BLANK_INT, DC_CMD_INT_STATUS); if (!no_vsync) { set_bit(V_BLANK_FLIP, &dc->vblank_ref_count); tegra_dc_unmask_interrupt(dc, FRAME_END_INT | V_BLANK_INT | ALL_UF_INT); } else { clear_bit(V_BLANK_FLIP, &dc->vblank_ref_count); tegra_dc_mask_interrupt(dc, V_BLANK_INT | ALL_UF_INT); if (!atomic_read(&frame_end_ref)) tegra_dc_mask_interrupt(dc, FRAME_END_INT); } if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) schedule_delayed_work(&dc->one_shot_work, msecs_to_jiffies(dc->one_shot_delay_ms)); /* update EMC clock if calculated bandwidth has changed */ tegra_dc_program_bandwidth(dc, false); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) update_mask |= NC_HOST_TRIG; tegra_dc_writel(dc, update_mask, DC_CMD_STATE_CONTROL); trace_printk("%s:update_mask=%#lx\n", dc->ndev->name, update_mask); tegra_dc_release_dc_out(dc); mutex_unlock(&dc->lock); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) mutex_unlock(&dc->one_shot_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; bool yuvp = tegra_dc_is_yuv_planar(win->fmt); unsigned Bpp = tegra_dc_fmt_bpp(win->fmt) / 8; static const struct { bool h; bool v; } can_filter[] = { /* Window A has no filtering */ { false, false }, /* Window B has both H and V filtering */ { true, true }, /* Window C has only H filtering */ { false, true }, }; const bool filter_h = can_filter[win->idx].h && (win->w.full != dfixed_const(win->out_w)); const bool filter_v = can_filter[win->idx].v && (win->h.full != dfixed_const(win->out_h)); 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->flags & TEGRA_WIN_FLAG_ENABLED)) { 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(dfixed_trunc(win->h)) | H_PRESCALED_SIZE(dfixed_trunc(win->w) * Bpp), DC_WIN_PRESCALED_SIZE); h_dda = compute_dda_inc(win->w, win->out_w, false, Bpp); v_dda = compute_dda_inc(win->h, win->out_h, true, Bpp); tegra_dc_writel(dc, V_DDA_INC(v_dda) | H_DDA_INC(h_dda), DC_WIN_DDA_INCREMENT); h_dda = compute_initial_dda(win->x); v_dda = compute_initial_dda(win->y); tegra_dc_writel(dc, h_dda, DC_WIN_H_INITIAL_DDA); tegra_dc_writel(dc, v_dda, DC_WIN_V_INITIAL_DDA); tegra_dc_writel(dc, 0, DC_WIN_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); } tegra_dc_writel(dc, dfixed_trunc(win->x) * Bpp, DC_WINBUF_ADDR_H_OFFSET); tegra_dc_writel(dc, dfixed_trunc(win->y), DC_WINBUF_ADDR_V_OFFSET); val = WIN_ENABLE; if (yuvp) val |= CSC_ENABLE; else if (tegra_dc_fmt_bpp(win->fmt) < 24) val |= COLOR_EXPAND; if (filter_h) val |= H_FILTER_ENABLE; if (filter_v) val |= V_FILTER_ENABLE; 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; }
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; 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. */ emc_clk_rate = dc->pdata->emc_clk_rate; clk_set_rate(emc_clk, emc_clk_rate ? emc_clk_rate : ULONG_MAX); 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_add(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"); dc->vblank_syncpt = (dc->ndev->id == 0) ? NVSYNCPT_VBLANK0 : NVSYNCPT_VBLANK1; dc->ext = tegra_dc_ext_register(ndev, dc); if (IS_ERR_OR_NULL(dc->ext)) { dev_warn(&ndev->dev, "Failed to enable Tegra DC extensions.\n"); dc->ext = NULL; } 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 && dc->out->hotplug_init) dc->out->hotplug_init(); if (dc->out_ops && dc->out_ops->detect) dc->out_ops->detect(dc); else dc->connected = true; 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; }
/* Does not support updating windows on multiple dcs in one call. * Requires a matching sync_windows to avoid leaking ref-count on clocks. */ 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 win_options; bool update_blend_par = false; bool update_blend_seq = false; int i; dc = windows[0]->dc; trace_update_windows(dc); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) { /* Acquire one_shot_lock to avoid race condition between * cancellation of old delayed work and schedule of new * delayed work. */ mutex_lock(&dc->one_shot_lock); cancel_delayed_work_sync(&dc->one_shot_work); } mutex_lock(&dc->lock); if (!dc->enabled) { mutex_unlock(&dc->lock); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) mutex_unlock(&dc->one_shot_lock); return -EFAULT; } tegra_dc_io_start(dc); tegra_dc_hold_dc_out(dc); 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]; struct tegra_dc_win *dc_win = tegra_dc_get_window(dc, win->idx); bool scan_column = 0; fixed20_12 h_offset, 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 yuv = tegra_dc_is_yuv(win->fmt); bool yuvp = tegra_dc_is_yuv_planar(win->fmt); unsigned Bpp = tegra_dc_fmt_bpp(win->fmt) / 8; /* Bytes per pixel of bandwidth, used for dda_inc calculation */ unsigned Bpp_bw = Bpp * (yuvp ? 2 : 1); const bool filter_h = win_use_h_filter(dc, win); const bool filter_v = win_use_v_filter(dc, win); #if defined(CONFIG_TEGRA_DC_SCAN_COLUMN) scan_column = (win->flags & TEGRA_WIN_FLAG_SCAN_COLUMN); #endif /* Update blender */ if ((win->z != dc->blend.z[win->idx]) || ((win->flags & TEGRA_WIN_BLEND_FLAGS_MASK) != dc->blend.flags[win->idx])) { dc->blend.z[win->idx] = win->z; dc->blend.flags[win->idx] = win->flags & TEGRA_WIN_BLEND_FLAGS_MASK; if (tegra_dc_feature_is_gen2_blender(dc, win->idx)) update_blend_seq = true; else update_blend_par = 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)) { dc_win->dirty = no_vsync ? 0 : 1; tegra_dc_writel(dc, 0, DC_WIN_WIN_OPTIONS); continue; } tegra_dc_writel(dc, win->fmt & 0x1f, DC_WIN_COLOR_DEPTH); tegra_dc_writel(dc, win->fmt >> 6, 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); /* Check scan_column flag to set window size and scaling. */ win_options = WIN_ENABLE; if (scan_column) { win_options |= WIN_SCAN_COLUMN; win_options |= H_FILTER_ENABLE(filter_v); win_options |= V_FILTER_ENABLE(filter_h); } else { win_options |= H_FILTER_ENABLE(filter_h); win_options |= V_FILTER_ENABLE(filter_v); } /* Update scaling registers if window supports scaling. */ if (likely(tegra_dc_feature_has_scaling(dc, win->idx))) tegra_dc_update_scaling(dc, win, Bpp, Bpp_bw, scan_column); #if defined(CONFIG_ARCH_TEGRA_2x_SOC) || defined(CONFIG_ARCH_TEGRA_3x_SOC) tegra_dc_writel(dc, 0, DC_WIN_BUF_STRIDE); tegra_dc_writel(dc, 0, DC_WIN_UV_BUF_STRIDE); #endif 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_u, DC_WINBUF_START_ADDR_U); tegra_dc_writel(dc, (unsigned long)win->phys_addr_v, DC_WINBUF_START_ADDR_V); tegra_dc_writel(dc, LINE_STRIDE(win->stride) | UV_LINE_STRIDE(win->stride_uv), DC_WIN_LINE_STRIDE); } if (invert_h) { h_offset.full = win->x.full + win->w.full; h_offset.full = dfixed_floor(h_offset) * Bpp; h_offset.full -= dfixed_const(1); } else { h_offset.full = dfixed_floor(win->x) * Bpp; } v_offset = win->y; if (invert_v) { v_offset.full += win->h.full - dfixed_const(1); } tegra_dc_writel(dc, dfixed_trunc(h_offset), DC_WINBUF_ADDR_H_OFFSET); tegra_dc_writel(dc, dfixed_trunc(v_offset), DC_WINBUF_ADDR_V_OFFSET); if (tegra_dc_feature_has_tiling(dc, win->idx)) { 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); } if (yuv) win_options |= CSC_ENABLE; else if (tegra_dc_fmt_bpp(win->fmt) < 24) win_options |= COLOR_EXPAND; #if defined(CONFIG_ARCH_TEGRA_3x_SOC) || defined(CONFIG_ARCH_TEGRA_11x_SOC) if (win->global_alpha == 255) { tegra_dc_writel(dc, 0, DC_WIN_GLOBAL_ALPHA); } else { tegra_dc_writel(dc, GLOBAL_ALPHA_ENABLE | win->global_alpha, DC_WIN_GLOBAL_ALPHA); win_options |= CP_ENABLE; } #endif if (win->ppflags & TEGRA_WIN_PPFLAG_CP_ENABLE) win_options |= CP_ENABLE; win_options |= H_DIRECTION_DECREMENT(invert_h); win_options |= V_DIRECTION_DECREMENT(invert_v); tegra_dc_writel(dc, win_options, DC_WIN_WIN_OPTIONS); dc_win->dirty = no_vsync ? 0 : 1; trace_window_update(dc, win); } if (update_blend_par || update_blend_seq) { if (update_blend_par) tegra_dc_blend_parallel(dc, &dc->blend); if (update_blend_seq) tegra_dc_blend_sequential(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_set_dynamic_emc(windows, n); tegra_dc_writel(dc, update_mask << 8, DC_CMD_STATE_CONTROL); tegra_dc_writel(dc, FRAME_END_INT | V_BLANK_INT, DC_CMD_INT_STATUS); if (!no_vsync) { set_bit(V_BLANK_FLIP, &dc->vblank_ref_count); tegra_dc_unmask_interrupt(dc, FRAME_END_INT | V_BLANK_INT | ALL_UF_INT); #if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) set_bit(V_PULSE2_FLIP, &dc->vpulse2_ref_count); tegra_dc_unmask_interrupt(dc, V_PULSE2_INT); #endif } else { clear_bit(V_BLANK_FLIP, &dc->vblank_ref_count); tegra_dc_mask_interrupt(dc, V_BLANK_INT | ALL_UF_INT); #if !defined(CONFIG_ARCH_TEGRA_2x_SOC) && !defined(CONFIG_ARCH_TEGRA_3x_SOC) clear_bit(V_PULSE2_FLIP, &dc->vpulse2_ref_count); tegra_dc_mask_interrupt(dc, V_PULSE2_INT); #endif if (!atomic_read(&frame_end_ref)) tegra_dc_mask_interrupt(dc, FRAME_END_INT); } if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) schedule_delayed_work(&dc->one_shot_work, msecs_to_jiffies(dc->one_shot_delay_ms)); /* update EMC clock if calculated bandwidth has changed */ tegra_dc_program_bandwidth(dc, false); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) update_mask |= NC_HOST_TRIG; tegra_dc_writel(dc, update_mask, DC_CMD_STATE_CONTROL); tegra_dc_release_dc_out(dc); /* tegra_dc_io_end() is called in tegra_dc_sync_windows() */ mutex_unlock(&dc->lock); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) mutex_unlock(&dc->one_shot_lock); return 0; }
static int tegra_dc_probe(struct nvhost_device *ndev, struct nvhost_device_id *id_table) { struct tegra_dc *dc; struct tegra_dc_mode *mode; 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; 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; dc->shift_clk_div = 1; /* Initialize one shot work delay, it will be assigned by dsi * according to refresh rate later. */ dc->one_shot_delay_ms = 40; 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 = 0; mutex_init(&dc->lock); mutex_init(&dc->one_shot_lock); init_completion(&dc->frame_end_complete); init_waitqueue_head(&dc->wq); init_waitqueue_head(&dc->timestamp_wq); #ifdef CONFIG_ARCH_TEGRA_2x_SOC INIT_WORK(&dc->reset_work, tegra_dc_reset_worker); #endif INIT_WORK(&dc->vblank_work, tegra_dc_vblank); dc->vblank_ref_count = 0; INIT_DELAYED_WORK(&dc->underflow_work, tegra_dc_underflow_worker); INIT_DELAYED_WORK(&dc->one_shot_work, tegra_dc_one_shot_worker); tegra_dc_init_lut_defaults(&dc->fb_lut); dc->n_windows = DC_N_WINDOWS; for (i = 0; i < dc->n_windows; i++) { struct tegra_dc_win *win = &dc->windows[i]; win->idx = i; win->dc = dc; tegra_dc_init_csc_defaults(&win->csc); tegra_dc_init_lut_defaults(&win->lut); } 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); #ifdef CONFIG_SWITCH dc->modeset_switch.name = dev_name(&ndev->dev); dc->modeset_switch.state = 0; dc->modeset_switch.print_state = switch_modeset_print_mode; switch_dev_register(&dc->modeset_switch); #endif tegra_dc_feature_register(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"); dc->vblank_syncpt = (dc->ndev->id == 0) ? NVSYNCPT_VBLANK0 : NVSYNCPT_VBLANK1; dc->ext = tegra_dc_ext_register(ndev, dc); if (IS_ERR_OR_NULL(dc->ext)) { dev_warn(&ndev->dev, "Failed to enable Tegra DC extensions.\n"); dc->ext = NULL; } /* interrupt handler must be registered before tegra_fb_register() */ if (request_irq(irq, tegra_dc_irq, 0, dev_name(&ndev->dev), dc)) { dev_err(&ndev->dev, "request_irq %d failed\n", irq); ret = -EBUSY; goto err_put_emc_clk; } disable_dc_irq(irq); mutex_lock(&dc->lock); if (dc->pdata->flags & TEGRA_DC_FLAG_ENABLED) dc->enabled = _tegra_dc_enable(dc); mutex_unlock(&dc->lock); tegra_dc_create_debugfs(dc); dev_info(&ndev->dev, "probed\n"); if (dc->pdata->fb) { if (dc->enabled && 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); } mode = tegra_dc_get_override_mode(dc); if (mode) { dc->pdata->fb->xres = mode->h_active; dc->pdata->fb->yres = mode->v_active; } 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 && dc->out->hotplug_init) dc->out->hotplug_init(); if (dc->out_ops && dc->out_ops->detect) dc->out_ops->detect(dc); else dc->connected = true; tegra_dc_create_sysfs(&dc->ndev->dev); 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; }