void tegra_dc_trigger_windows(struct tegra_dc *dc) { u32 val, i; u32 completed = 0; u32 dirty = 0; val = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); for (i = 0; i < DC_N_WINDOWS; i++) { #ifdef CONFIG_TEGRA_SIMULATION_PLATFORM /* FIXME: this is not needed when the simulator clears WIN_x_UPDATE bits as in HW */ dc->windows[i].dirty = 0; completed = 1; #else if (!(val & (WIN_A_ACT_REQ << i))) { dc->windows[i].dirty = 0; completed = 1; } else { dirty = 1; } #endif } if (!dirty) { if (!(dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) && !atomic_read(&frame_end_ref)) tegra_dc_mask_interrupt(dc, FRAME_END_INT); } if (completed) wake_up(&dc->wq); }
static unsigned int tegra_shared_plane_get_owner(struct tegra_plane *plane, struct tegra_dc *dc) { unsigned int offset = tegra_plane_offset(plane, DC_WIN_CORE_WINDOWGROUP_SET_CONTROL); return tegra_dc_readl(dc, offset) & OWNER_MASK; }
int tegra_dc_ext_set_cursor(struct tegra_dc_ext_user *user, struct tegra_dc_ext_cursor *args) { struct tegra_dc_ext *ext = user->ext; struct tegra_dc *dc = ext->dc; u32 val; bool enable; int ret; mutex_lock(&ext->cursor.lock); if (ext->cursor.user != user) { ret = -EACCES; goto unlock; } if (!ext->enabled) { ret = -ENXIO; goto unlock; } enable = !!(args->flags & TEGRA_DC_EXT_CURSOR_FLAGS_VISIBLE); mutex_lock(&dc->lock); tegra_dc_io_start(dc); tegra_dc_hold_dc_out(dc); val = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); if (!!(val & CURSOR_ENABLE) != enable) { val &= ~CURSOR_ENABLE; if (enable) val |= CURSOR_ENABLE; tegra_dc_writel(dc, val, DC_DISP_DISP_WIN_OPTIONS); } tegra_dc_writel(dc, CURSOR_POSITION(args->x, args->y), DC_DISP_CURSOR_POSITION); tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); /* TODO: need to sync here? hopefully can avoid this, but need to * figure out interaction w/ rest of GENERAL_ACT_REQ */ tegra_dc_release_dc_out(dc); tegra_dc_io_end(dc); mutex_unlock(&dc->lock); mutex_unlock(&ext->cursor.lock); return 0; unlock: mutex_unlock(&ext->cursor.lock); return ret; }
/* handle the commands that may be invoked for phase_in_settings */ static void nvsd_cmd_handler(struct tegra_dc_sd_settings *settings, struct tegra_dc *dc) { u32 val; u8 bw_idx, bw; if (settings->cmd & ENABLE) { settings->phase_settings_step++; if (settings->phase_settings_step >= settings->num_phase_in_steps) settings->cmd &= ~ENABLE; nvsd_phase_in_luts(settings, dc); } if (settings->cmd & DISABLE) { settings->phase_settings_step--; nvsd_phase_in_luts(settings, dc); if (settings->phase_settings_step == 0) { /* finish up aggressiveness phase in */ if (settings->cmd & AGG_CHG) settings->aggressiveness = settings->final_agg; settings->cmd = NO_CMD; settings->enable = 0; nvsd_init(dc, settings); } } if (settings->cmd & AGG_CHG) { if (settings->aggressiveness == settings->final_agg) settings->cmd &= ~AGG_CHG; if ((settings->cur_agg_step++ & (STEPS_PER_AGG_CHG - 1)) == 0) { settings->final_agg > settings->aggressiveness ? settings->aggressiveness++ : settings->aggressiveness--; /* Update aggressiveness value in HW */ val = tegra_dc_readl(dc, DC_DISP_SD_CONTROL); val &= ~SD_AGGRESSIVENESS(0x7); val |= SD_AGGRESSIVENESS(settings->aggressiveness); /* Adjust bin_width for automatic setting */ if (settings->bin_width == -1) { bw_idx = nvsd_get_bw_idx(settings); bw = bw_idx << 3; val &= ~SD_BIN_WIDTH_MASK; val |= bw; } tegra_dc_writel(dc, val, DC_DISP_SD_CONTROL); nvsd_phase_in_luts(settings, dc); } } }
static bool tegra_dc_windows_are_dirty(struct tegra_dc *dc) { #ifndef CONFIG_TEGRA_SIMULATION_PLATFORM u32 val; val = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); if (val & (WIN_A_ACT_REQ | WIN_B_ACT_REQ | WIN_C_ACT_REQ)) return true; #endif return false; }
/* get the stride size of a window. * return: stride size in bytes for window win. or 0 if unavailble. */ int tegra_dc_get_stride(struct tegra_dc *dc, unsigned win) { u32 stride; if (!dc->enabled) return 0; BUG_ON(win > DC_N_WINDOWS); tegra_dc_writel(dc, WINDOW_A_SELECT << win, DC_CMD_DISPLAY_WINDOW_HEADER); stride = tegra_dc_readl(dc, DC_WIN_LINE_STRIDE); return GET_LINE_STRIDE(stride); }
/* Periodic update */ bool nvsd_update_brightness(struct tegra_dc *dc) { u32 val = 0; int cur_sd_brightness; struct tegra_dc_sd_settings *settings = dc->out->sd_settings; if (sd_brightness) { if (atomic_read(&man_k_until_blank) && !settings->phase_in_adjustments) { val = tegra_dc_readl(dc, DC_DISP_SD_CONTROL); val &= ~SD_CORRECTION_MODE_MAN; tegra_dc_writel(dc, val, DC_DISP_SD_CONTROL); atomic_set(&man_k_until_blank, 0); } if (settings->cmd) nvsd_cmd_handler(settings, dc); /* nvsd_cmd_handler may turn off didim */ if (!settings->enable) return true; cur_sd_brightness = atomic_read(sd_brightness); /* read brightness value */ val = tegra_dc_readl(dc, DC_DISP_SD_BL_CONTROL); val = SD_BLC_BRIGHTNESS(val); if (settings->phase_in_adjustments) { return nvsd_phase_in_adjustments(dc, settings); } else if (val != (u32)cur_sd_brightness) { /* set brightness value and note the update */ atomic_set(sd_brightness, (int)val); return true; } } /* No update needed. */ return false; }
static void set_cursor_image_hw(struct tegra_dc *dc, struct tegra_dc_ext_cursor_image *args, dma_addr_t phys_addr) { unsigned long val; int clip_win; tegra_dc_writel(dc, CURSOR_COLOR(args->foreground.r, args->foreground.g, args->foreground.b), DC_DISP_CURSOR_FOREGROUND); tegra_dc_writel(dc, CURSOR_COLOR(args->background.r, args->background.g, args->background.b), DC_DISP_CURSOR_BACKGROUND); BUG_ON(phys_addr & ~CURSOR_START_ADDR_MASK); switch (TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE(args->flags)) { case TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_64x64: val = CURSOR_SIZE_64; break; case TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_128x128: val = CURSOR_SIZE_128; break; case TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_256x256: val = CURSOR_SIZE_256; break; default: val = 0; } /* Get the cursor clip window number */ clip_win = CURSOR_CLIP_GET_WINDOW(tegra_dc_readl(dc, DC_DISP_CURSOR_START_ADDR)); val |= clip_win; tegra_dc_writel(dc, val | CURSOR_START_ADDR(((unsigned long) phys_addr)), DC_DISP_CURSOR_START_ADDR); if (args->flags & TEGRA_DC_EXT_CURSOR_FLAGS_RGBA_NORMAL) tegra_dc_writel(dc, CURSOR_MODE_SELECT(1), DC_DISP_BLEND_CURSOR_CONTROL); else tegra_dc_writel(dc, CURSOR_MODE_SELECT(0), DC_DISP_BLEND_CURSOR_CONTROL); }
void tegra_dc_set_lut(struct tegra_dc *dc, struct tegra_dc_win *win) { unsigned long val = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); tegra_dc_loop_lut(dc, win, tegra_dc_set_lut_setreg_lambda); if (win->ppflags & TEGRA_WIN_PPFLAG_CP_ENABLE) val |= CP_ENABLE; else val &= ~CP_ENABLE; tegra_dc_writel(dc, val, DC_WIN_WIN_OPTIONS); }
void tegra_dc_stats_enable(struct tegra_dc *dc, bool enable) { #if 0 /* underflow interrupt is already enabled by dc reset worker */ u32 val; if (dc->enabled) { val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE); if (enable) val |= (WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT); else val &= ~(WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT); tegra_dc_writel(dc, val, DC_CMD_INT_ENABLE); } #endif }
bool tegra_dc_stats_get(struct tegra_dc *dc) { #if 0 /* right now it is always enabled */ u32 val; bool res; if (dc->enabled) { val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE); res = !!(val & (WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT)); } else { res = false; } return res; #endif return true; }
u32 tegra_dc_read_checksum_latched(struct tegra_dc *dc) { int crc = 0; if (!dc) { dev_err(&dc->ndev->dev, "Failed to get dc.\n"); goto crc_error; } /* TODO: Replace mdelay with code to sync VBlANK, since * DC_COM_CRC_CHECKSUM_LATCHED is available after VBLANK */ mdelay(TEGRA_CRC_LATCHED_DELAY); crc = tegra_dc_readl(dc, DC_COM_CRC_CHECKSUM_LATCHED); crc_error: return crc; }
static void tegra_shared_plane_activate(struct tegra_plane *plane) { struct tegra_dc *dc = plane->dc; unsigned long timeout; u32 mask, value; mask = COMMON_ACTREQ | WIN_A_ACT_REQ << plane->base.index; tegra_dc_writel(dc, mask, DC_CMD_STATE_CONTROL); timeout = jiffies + msecs_to_jiffies(1000); while (time_before(jiffies, timeout)) { value = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); if ((value & mask) == 0) break; usleep_range(100, 400); } }
int tegra_dc_ext_cursor_clip(struct tegra_dc_ext_user *user, int *args) { struct tegra_dc_ext *ext = user->ext; struct tegra_dc *dc = ext->dc; int ret; unsigned long reg_val; mutex_lock(&ext->cursor.lock); if (ext->cursor.user != user) { ret = -EACCES; goto unlock; } if (!ext->enabled) { ret = -ENXIO; goto unlock; } mutex_lock(&dc->lock); tegra_dc_io_start(dc); tegra_dc_hold_dc_out(dc); reg_val = tegra_dc_readl(dc, DC_DISP_CURSOR_START_ADDR); reg_val &= ~CURSOR_CLIP_SHIFT_BITS(3); /* Clear out the old value */ tegra_dc_writel(dc, reg_val | CURSOR_CLIP_SHIFT_BITS(*args), DC_DISP_CURSOR_START_ADDR); tegra_dc_release_dc_out(dc); tegra_dc_io_end(dc); mutex_unlock(&dc->lock); mutex_unlock(&ext->cursor.lock); return 0; unlock: mutex_unlock(&ext->cursor.lock); return ret; }
/* Functional initialization */ void nvsd_init(struct tegra_dc *dc, struct tegra_dc_sd_settings *settings) { u32 i = 0; u32 val = 0; u32 bw_idx = 0; /* TODO: check if HW says SD's available */ /* If SD's not present or disabled, clear the register and return. */ if (!settings || settings->enable == 0) { /* clear the brightness val, too. */ if (sd_brightness) atomic_set(sd_brightness, 255); sd_brightness = NULL; if (settings) settings->phase_settings_step = 0; tegra_dc_writel(dc, 0, DC_DISP_SD_CONTROL); return; } dev_dbg(&dc->ndev->dev, "NVSD Init:\n"); /* init agg_priorities */ if (!settings->agg_priorities.agg[0]) settings->agg_priorities.agg[0] = settings->aggressiveness; /* WAR: Settings will not be valid until the next flip. * Thus, set manual K to either HW's current value (if * we're already enabled) or a non-effective value (if * we're about to enable). */ val = tegra_dc_readl(dc, DC_DISP_SD_CONTROL); if (val & SD_ENABLE_NORMAL) i = tegra_dc_readl(dc, DC_DISP_SD_HW_K_VALUES); else i = 0; /* 0 values for RGB = 1.0, i.e. non-affected */ tegra_dc_writel(dc, i, DC_DISP_SD_MAN_K_VALUES); /* Enable manual correction mode here so that changing the * settings won't immediately impact display dehavior. */ val |= SD_CORRECTION_MODE_MAN; tegra_dc_writel(dc, val, DC_DISP_SD_CONTROL); bw_idx = nvsd_get_bw_idx(settings); /* Write LUT */ if (!settings->cmd) { dev_dbg(&dc->ndev->dev, " LUT:\n"); for (i = 0; i < DC_DISP_SD_LUT_NUM; i++) { val = SD_LUT_R(settings->lut[bw_idx][i].r) | SD_LUT_G(settings->lut[bw_idx][i].g) | SD_LUT_B(settings->lut[bw_idx][i].b); tegra_dc_writel(dc, val, DC_DISP_SD_LUT(i)); dev_dbg(&dc->ndev->dev, " %d: 0x%08x\n", i, val); } } /* Write BL TF */ if (!settings->cmd) { dev_dbg(&dc->ndev->dev, " BL_TF:\n"); for (i = 0; i < DC_DISP_SD_BL_TF_NUM; i++) { val = SD_BL_TF_POINT_0(settings->bltf[bw_idx][i][0]) | SD_BL_TF_POINT_1(settings->bltf[bw_idx][i][1]) | SD_BL_TF_POINT_2(settings->bltf[bw_idx][i][2]) | SD_BL_TF_POINT_3(settings->bltf[bw_idx][i][3]); tegra_dc_writel(dc, val, DC_DISP_SD_BL_TF(i)); dev_dbg(&dc->ndev->dev, " %d: 0x%08x\n", i, val); } } else if ((settings->cmd & PHASE_IN)) { settings->cmd &= ~PHASE_IN; /* Write NO_OP values for BLTF */ for (i = 0; i < DC_DISP_SD_BL_TF_NUM; i++) { val = SD_BL_TF_POINT_0(0xFF) | SD_BL_TF_POINT_1(0xFF) | SD_BL_TF_POINT_2(0xFF) | SD_BL_TF_POINT_3(0xFF); tegra_dc_writel(dc, val, DC_DISP_SD_BL_TF(i)); dev_dbg(&dc->ndev->dev, " %d: 0x%08x\n", i, val); } } /* Set step correctly on init */ if (!settings->cmd && settings->phase_in_settings) { settings->num_phase_in_steps = STEPS_PER_AGG_LVL * settings->aggressiveness; settings->phase_settings_step = settings->enable ? settings->num_phase_in_steps : 0; } /* Write Coeff */ val = SD_CSC_COEFF_R(settings->coeff.r) | SD_CSC_COEFF_G(settings->coeff.g) | SD_CSC_COEFF_B(settings->coeff.b); tegra_dc_writel(dc, val, DC_DISP_SD_CSC_COEFF); dev_dbg(&dc->ndev->dev, " COEFF: 0x%08x\n", val); /* Write BL Params */ val = SD_BLP_TIME_CONSTANT(settings->blp.time_constant) | SD_BLP_STEP(settings->blp.step); tegra_dc_writel(dc, val, DC_DISP_SD_BL_PARAMETERS); dev_dbg(&dc->ndev->dev, " BLP: 0x%08x\n", val); /* Write Auto/Manual PWM */ val = (settings->use_auto_pwm) ? SD_BLC_MODE_AUTO : SD_BLC_MODE_MAN; tegra_dc_writel(dc, val, DC_DISP_SD_BL_CONTROL); dev_dbg(&dc->ndev->dev, " BL_CONTROL: 0x%08x\n", val); /* Write Flicker Control */ val = SD_FC_TIME_LIMIT(settings->fc.time_limit) | SD_FC_THRESHOLD(settings->fc.threshold); tegra_dc_writel(dc, val, DC_DISP_SD_FLICKER_CONTROL); dev_dbg(&dc->ndev->dev, " FLICKER_CONTROL: 0x%08x\n", val); /* Manage SD Control */ val = 0; /* Stay in manual correction mode until the next flip. */ val |= SD_CORRECTION_MODE_MAN; /* Enable / One-Shot */ val |= (settings->enable == 2) ? (SD_ENABLE_ONESHOT | SD_ONESHOT_ENABLE) : SD_ENABLE_NORMAL; /* HW Update Delay */ val |= SD_HW_UPDATE_DLY(settings->hw_update_delay); /* Video Luma */ val |= (settings->use_vid_luma) ? SD_USE_VID_LUMA : 0; /* Aggressiveness */ val |= SD_AGGRESSIVENESS(settings->aggressiveness); /* Bin Width (value derived from bw_idx) */ val |= bw_idx << 3; /* Finally, Write SD Control */ tegra_dc_writel(dc, val, DC_DISP_SD_CONTROL); dev_dbg(&dc->ndev->dev, " SD_CONTROL: 0x%08x\n", val); /* set the brightness pointer */ sd_brightness = settings->sd_brightness; /* note that we're in manual K until the next flip */ atomic_set(&man_k_until_blank, 1); }
/* return an arbitrarily large number if count overflow occurs. * make it a nice base-10 number to show up in stats output */ static u64 tegra_dc_underflow_count(struct tegra_dc *dc, unsigned reg) { unsigned count = tegra_dc_readl(dc, reg); tegra_dc_writel(dc, 0, reg); return ((count & 0x80000000) == 0) ? count : 10000000000ll; }
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; }
static bool nvsd_phase_in_adjustments(struct tegra_dc *dc, struct tegra_dc_sd_settings *settings) { u8 step, cur_sd_brightness; int commanded; u16 target_k, cur_k; u32 man_k, val; struct platform_device *pdev; struct backlight_device *bl; bool below_min_brightness = false; cur_sd_brightness = atomic_read(sd_brightness); target_k = tegra_dc_readl(dc, DC_DISP_SD_HW_K_VALUES); target_k = SD_HW_K_R(target_k); cur_k = tegra_dc_readl(dc, DC_DISP_SD_MAN_K_VALUES); cur_k = SD_HW_K_R(cur_k); /* read brightness value */ val = tegra_dc_readl(dc, DC_DISP_SD_BL_CONTROL); val = SD_BLC_BRIGHTNESS(val); if (settings->panel_min_brightness) { pdev = settings->bl_device; bl = platform_get_drvdata(pdev); commanded = (cur_sd_brightness * bl->props.brightness) / 255; /* Need to reduce how aggressive we are */ if (commanded < settings->panel_min_brightness) { if (cur_k || cur_sd_brightness != 255) below_min_brightness = true; else return false; } /* Return so we don't modify in the opposite direction */ if (commanded == settings->panel_min_brightness && target_k > cur_k) return false; } /* Correct until brightness is high enough */ if (below_min_brightness) { if (cur_sd_brightness != 255) cur_sd_brightness++; if (cur_k) cur_k -= K_STEP; man_k = SD_MAN_K_R(cur_k) | SD_MAN_K_G(cur_k) | SD_MAN_K_B(cur_k); tegra_dc_writel(dc, man_k, DC_DISP_SD_MAN_K_VALUES); atomic_set(sd_brightness, cur_sd_brightness); return true; } step = settings->phase_adj_step; if (cur_sd_brightness != val || target_k != cur_k) { if (!step) step = ADJ_PHASE_STEP; /* Phase in Backlight and Pixel K every ADJ_PHASE_STEP frames*/ if ((step-- & ADJ_PHASE_STEP) == ADJ_PHASE_STEP) { if (val != cur_sd_brightness) { val > cur_sd_brightness ? (cur_sd_brightness++) : (cur_sd_brightness--); } if (target_k != cur_k) { if (target_k > cur_k) cur_k += K_STEP; else cur_k -= K_STEP; } /* Set manual k value */ man_k = SD_MAN_K_R(cur_k) | SD_MAN_K_G(cur_k) | SD_MAN_K_B(cur_k); tegra_dc_writel(dc, man_k, DC_DISP_SD_MAN_K_VALUES); /* Set manual brightness value */ atomic_set(sd_brightness, cur_sd_brightness); } settings->phase_adj_step = step; return true; } else return false; }
/* Functional initialization */ void nvsd_init(struct tegra_dc *dc, struct tegra_dc_sd_settings *settings) { u32 i = 0; u32 val = 0; u32 bw_idx = 0; /* TODO: check if HW says SD's available */ /* If SD's not present or disabled, clear the register and return. */ if (!settings || settings->enable == 0) { /* clear the brightness val, too. */ if (sd_brightness) atomic_set(sd_brightness, 255); sd_brightness = NULL; if (settings) settings->phase_settings_step = 0; tegra_dc_writel(dc, 0, DC_DISP_SD_CONTROL); return; } dev_dbg(&dc->ndev->dev, "NVSD Init:\n"); /* init agg_priorities */ if (!settings->agg_priorities.agg[0]) settings->agg_priorities.agg[0] = settings->aggressiveness; /* WAR: Settings will not be valid until the next flip. * Thus, set manual K to either HW's current value (if * we're already enabled) or a non-effective value (if * we're about to enable). */ val = tegra_dc_readl(dc, DC_DISP_SD_CONTROL); if (val & SD_ENABLE_NORMAL) if (settings->phase_in_adjustments) i = tegra_dc_readl(dc, DC_DISP_SD_MAN_K_VALUES); else i = tegra_dc_readl(dc, DC_DISP_SD_HW_K_VALUES); else i = 0; /* 0 values for RGB = 1.0, i.e. non-affected */ tegra_dc_writel(dc, i, DC_DISP_SD_MAN_K_VALUES); /* Enable manual correction mode here so that changing the * settings won't immediately impact display dehavior. */ val |= SD_CORRECTION_MODE_MAN; tegra_dc_writel(dc, val, DC_DISP_SD_CONTROL); bw_idx = nvsd_get_bw_idx(settings); /* Values of SD LUT & BL TF are different according to bin_width on T30 * due to HW bug. Therefore we use bin_width to select the correct table * on T30. */ #ifdef CONFIG_TEGRA_SD_GEN2 bw_idx = 0; #endif /* Write LUT */ if (!settings->cmd) { dev_dbg(&dc->ndev->dev, " LUT:\n"); for (i = 0; i < DC_DISP_SD_LUT_NUM; i++) { val = SD_LUT_R(settings->lut[bw_idx][i].r) | SD_LUT_G(settings->lut[bw_idx][i].g) | SD_LUT_B(settings->lut[bw_idx][i].b); tegra_dc_writel(dc, val, DC_DISP_SD_LUT(i)); dev_dbg(&dc->ndev->dev, " %d: 0x%08x\n", i, val); } } /* Write BL TF */ if (!settings->cmd) { dev_dbg(&dc->ndev->dev, " BL_TF:\n"); for (i = 0; i < DC_DISP_SD_BL_TF_NUM; i++) { val = SD_BL_TF_POINT_0(settings->bltf[bw_idx][i][0]) | SD_BL_TF_POINT_1(settings->bltf[bw_idx][i][1]) | SD_BL_TF_POINT_2(settings->bltf[bw_idx][i][2]) | SD_BL_TF_POINT_3(settings->bltf[bw_idx][i][3]); tegra_dc_writel(dc, val, DC_DISP_SD_BL_TF(i)); dev_dbg(&dc->ndev->dev, " %d: 0x%08x\n", i, val); } } else if ((settings->cmd & PHASE_IN)) { settings->cmd &= ~PHASE_IN; /* Write NO_OP values for BLTF */ for (i = 0; i < DC_DISP_SD_BL_TF_NUM; i++) { val = SD_BL_TF_POINT_0(0xFF) | SD_BL_TF_POINT_1(0xFF) | SD_BL_TF_POINT_2(0xFF) | SD_BL_TF_POINT_3(0xFF); tegra_dc_writel(dc, val, DC_DISP_SD_BL_TF(i)); dev_dbg(&dc->ndev->dev, " %d: 0x%08x\n", i, val); } } /* Set step correctly on init */ if (!settings->cmd && settings->phase_in_settings) { settings->num_phase_in_steps = STEPS_PER_AGG_LVL * settings->aggressiveness; settings->phase_settings_step = settings->enable ? settings->num_phase_in_steps : 0; } /* Write Coeff */ val = SD_CSC_COEFF_R(settings->coeff.r) | SD_CSC_COEFF_G(settings->coeff.g) | SD_CSC_COEFF_B(settings->coeff.b); tegra_dc_writel(dc, val, DC_DISP_SD_CSC_COEFF); dev_dbg(&dc->ndev->dev, " COEFF: 0x%08x\n", val); /* Write BL Params */ val = SD_BLP_TIME_CONSTANT(settings->blp.time_constant) | SD_BLP_STEP(settings->blp.step); tegra_dc_writel(dc, val, DC_DISP_SD_BL_PARAMETERS); dev_dbg(&dc->ndev->dev, " BLP: 0x%08x\n", val); /* Write Auto/Manual PWM */ val = (settings->use_auto_pwm) ? SD_BLC_MODE_AUTO : SD_BLC_MODE_MAN; tegra_dc_writel(dc, val, DC_DISP_SD_BL_CONTROL); dev_dbg(&dc->ndev->dev, " BL_CONTROL: 0x%08x\n", val); /* Write Flicker Control */ val = SD_FC_TIME_LIMIT(settings->fc.time_limit) | SD_FC_THRESHOLD(settings->fc.threshold); tegra_dc_writel(dc, val, DC_DISP_SD_FLICKER_CONTROL); dev_dbg(&dc->ndev->dev, " FLICKER_CONTROL: 0x%08x\n", val); #ifdef CONFIG_TEGRA_SD_GEN2 /* Write K limit */ if (settings->k_limit_enable) { val = settings->k_limit; if (val < 128) val = 128; else if (val > 255) val = 255; val = SD_K_LIMIT(val); tegra_dc_writel(dc, val, DC_DISP_SD_K_LIMIT); dev_dbg(&dc->ndev->dev, " K_LIMIT: 0x%08x\n", val); } if (settings->sd_window_enable) { /* Write sd window */ val = SD_WIN_H_POSITION(settings->sd_window.h_position) | SD_WIN_V_POSITION(settings->sd_window.v_position); tegra_dc_writel(dc, val, DC_DISP_SD_WINDOW_POSITION); dev_dbg(&dc->ndev->dev, " SD_WINDOW_POSITION: 0x%08x\n", val); val = SD_WIN_H_POSITION(settings->sd_window.h_size) | SD_WIN_V_POSITION(settings->sd_window.v_size); tegra_dc_writel(dc, val, DC_DISP_SD_WINDOW_SIZE); dev_dbg(&dc->ndev->dev, " SD_WINDOW_SIZE: 0x%08x\n", val); } if (settings->soft_clipping_enable) { /* Write soft clipping */ val = (64 * 1024) / (256 - settings->soft_clipping_threshold); val = SD_SOFT_CLIPPING_RECIP(val) | SD_SOFT_CLIPPING_THRESHOLD(settings->soft_clipping_threshold); tegra_dc_writel(dc, val, DC_DISP_SD_SOFT_CLIPPING); dev_dbg(&dc->ndev->dev, " SOFT_CLIPPING: 0x%08x\n", val); } if (settings->smooth_k_enable) { /* Write K incr value */ val = SD_SMOOTH_K_INCR(settings->smooth_k_incr); tegra_dc_writel(dc, val, DC_DISP_SD_SMOOTH_K); dev_dbg(&dc->ndev->dev, " SMOOTH_K: 0x%08x\n", val); } #endif /* Manage SD Control */ val = 0; /* Stay in manual correction mode until the next flip. */ val |= SD_CORRECTION_MODE_MAN; /* Enable / One-Shot */ val |= (settings->enable == 2) ? (SD_ENABLE_ONESHOT | SD_ONESHOT_ENABLE) : SD_ENABLE_NORMAL; /* HW Update Delay */ val |= SD_HW_UPDATE_DLY(settings->hw_update_delay); /* Video Luma */ val |= (settings->use_vid_luma) ? SD_USE_VID_LUMA : 0; /* Aggressiveness */ val |= SD_AGGRESSIVENESS(settings->aggressiveness); /* Bin Width (value derived from bw_idx) */ val |= bw_idx << 3; #ifdef CONFIG_TEGRA_SD_GEN2 /* K limit enable */ val |= (settings->k_limit_enable) ? SD_K_LIMIT_ENABLE : 0; /* Programmable sd window enable */ val |= (settings->sd_window_enable) ? SD_WINDOW_ENABLE : 0; /* Soft clipping enable */ val |= (settings->soft_clipping_enable) ? SD_SOFT_CLIPPING_ENABLE : 0; /* Smooth K enable */ val |= (settings->smooth_k_enable) ? SD_SMOOTH_K_ENABLE : 0; /* SD proc control */ val |= (settings->use_vpulse2) ? SD_VPULSE2 : SD_VSYNC; #endif /* Finally, Write SD Control */ tegra_dc_writel(dc, val, DC_DISP_SD_CONTROL); dev_dbg(&dc->ndev->dev, " SD_CONTROL: 0x%08x\n", val); /* set the brightness pointer */ sd_brightness = settings->sd_brightness; /* note that we're in manual K until the next flip */ atomic_set(&man_k_until_blank, 1); }
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_underflow_handler(struct tegra_dc *dc) { u32 val; int i; dc->stats.underflows++; if (dc->underflow_mask & WIN_A_UF_INT) { dc->stats.underflows_a += tegra_dc_underflow_count(dc, DC_WINBUF_AD_UFLOW_STATUS); trace_printk("%s:Window A Underflow\n", dc->ndev->name); } if (dc->underflow_mask & WIN_B_UF_INT) { dc->stats.underflows_b += tegra_dc_underflow_count(dc, DC_WINBUF_BD_UFLOW_STATUS); trace_printk("%s:Window B Underflow\n", dc->ndev->name); } if (dc->underflow_mask & WIN_C_UF_INT) { dc->stats.underflows_c += tegra_dc_underflow_count(dc, DC_WINBUF_CD_UFLOW_STATUS); trace_printk("%s:Window C Underflow\n", dc->ndev->name); } /* Check for any underflow reset conditions */ for (i = 0; i < DC_N_WINDOWS; i++) { if (dc->underflow_mask & (WIN_A_UF_INT << i)) { dc->windows[i].underflows++; #ifdef CONFIG_ARCH_TEGRA_2x_SOC if (dc->windows[i].underflows > 4) { schedule_work(&dc->reset_work); /* reset counter */ dc->windows[i].underflows = 0; trace_printk("%s:Reset work scheduled for " "window %c\n", dc->ndev->name, (65 + i)); } #endif #ifdef CONFIG_ARCH_TEGRA_3x_SOC if (dc->windows[i].underflows > 4) { trace_printk("%s:window %c in underflow state." " enable UF_LINE_FLUSH to clear up\n", dc->ndev->name, (65 + i)); tegra_dc_writel(dc, UF_LINE_FLUSH, DC_DISP_DISP_MISC_CONTROL); tegra_dc_writel(dc, GENERAL_UPDATE, DC_CMD_STATE_CONTROL); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); tegra_dc_writel(dc, 0, DC_DISP_DISP_MISC_CONTROL); tegra_dc_writel(dc, GENERAL_UPDATE, DC_CMD_STATE_CONTROL); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); } #endif } else { dc->windows[i].underflows = 0; } } /* Clear the underflow mask now that we've checked it. */ tegra_dc_writel(dc, dc->underflow_mask, DC_CMD_INT_STATUS); dc->underflow_mask = 0; val = tegra_dc_readl(dc, DC_CMD_INT_MASK); tegra_dc_writel(dc, val | ALL_UF_INT, DC_CMD_INT_MASK); print_underflow_info(dc); }
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; }
/* 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; }
static int nvsd_set_brightness(struct tegra_dc *dc) { u32 bin_width; int i, j; int val; int pix; int bin_idx; int incr; int base; u32 histo[32]; u32 histo_total = 0; /* count of pixels */ fixed20_12 nonhisto_gain; /* gain of pixels not in histogram */ fixed20_12 est_achieved_gain; /* final gain of pixels */ fixed20_12 histo_gain = dfixed_init(0); /* gain of pixels */ fixed20_12 k, threshold; /* k is the fractional part of HW_K */ fixed20_12 den, num, out; fixed20_12 pix_avg, pix_avg_softclip; /* Collet the inputs of the algorithm */ for (i = 0; i < DC_DISP_SD_HISTOGRAM_NUM; i++) { val = tegra_dc_readl(dc, DC_DISP_SD_HISTOGRAM(i)); for (j = 0; j < 4; j++) histo[i * 4 + j] = SD_HISTOGRAM_BIN(val, (j * 8)); } val = tegra_dc_readl(dc, DC_DISP_SD_HW_K_VALUES); k.full = dfixed_const(SD_HW_K_R(val)); den.full = dfixed_const(1024); k.full = dfixed_div(k, den); val = tegra_dc_readl(dc, DC_DISP_SD_SOFT_CLIPPING); threshold.full = dfixed_const(SD_SOFT_CLIPPING_THRESHOLD(val)); val = tegra_dc_readl(dc, DC_DISP_SD_CONTROL); bin_width = SD_BIN_WIDTH_VAL(val); incr = 1 << bin_width; base = 256 - 32 * incr; for (pix = base, bin_idx = 0; pix < 256; pix += incr, bin_idx++) { num.full = dfixed_const(pix + pix + incr); den.full = dfixed_const(2); pix_avg.full = dfixed_div(num, den); pix_avg_softclip.full = nvsd_softclip(pix_avg, k, threshold); num.full = dfixed_const(histo[bin_idx]); den.full = dfixed_const(256); out.full = dfixed_div(num, den); num.full = dfixed_mul(out, pix_avg_softclip); out.full = dfixed_div(num, pix_avg); histo_gain.full += out.full; histo_total += histo[bin_idx]; } out.full = dfixed_const(256 - histo_total); den.full = dfixed_const(1) + k.full; num.full = dfixed_mul(out, den); den.full = dfixed_const(256); nonhisto_gain.full = dfixed_div(num, den); den.full = nonhisto_gain.full + histo_gain.full; num.full = dfixed_const(1); out.full = dfixed_div(num, den); num.full = dfixed_const(255); est_achieved_gain.full = dfixed_mul(num, out); val = dfixed_trunc(est_achieved_gain); return nvsd_backlght_interplate(val, 128); }
/* Periodic update */ bool nvsd_update_brightness(struct tegra_dc *dc) { u32 val = 0; int cur_sd_brightness; int sw_sd_brightness; struct tegra_dc_sd_settings *settings = dc->out->sd_settings; bool nvsd_updated = false; if (_sd_brightness) { if (atomic_read(&man_k_until_blank) && !settings->phase_in_adjustments) { val = tegra_dc_readl(dc, DC_DISP_SD_CONTROL); val &= ~SD_CORRECTION_MODE_MAN; tegra_dc_writel(dc, val, DC_DISP_SD_CONTROL); atomic_set(&man_k_until_blank, 0); } if (settings->cmd) nvsd_cmd_handler(settings, dc); /* nvsd_cmd_handler may turn off didim */ if (!settings->enable) return true; cur_sd_brightness = atomic_read(_sd_brightness); /* read brightness value */ val = tegra_dc_readl(dc, DC_DISP_SD_BL_CONTROL); val = SD_BLC_BRIGHTNESS(val); /* PRISM is updated by hw or sw algorithm. Brightness is * compensated according to histogram for soft-clipping * if hw output is used to update brightness. */ if (settings->phase_in_adjustments) { nvsd_updated = nvsd_phase_in_adjustments(dc, settings); } else if (settings->soft_clipping_enable && settings->soft_clipping_correction) { sw_sd_brightness = nvsd_set_brightness(dc); if (sw_sd_brightness != cur_sd_brightness) { atomic_set(_sd_brightness, sw_sd_brightness); nvsd_updated = true; } } else if (val != (u32)cur_sd_brightness) { /* set brightness value and note the update */ atomic_set(_sd_brightness, (int)val); nvsd_updated = true; } if (nvsd_updated) { smooth_k_frames_left = smooth_k_duration_frames; return true; } if (settings->smooth_k_enable) { if (smooth_k_frames_left--) return true; else smooth_k_frames_left = smooth_k_duration_frames; } } /* No update needed. */ return false; }
/* 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); val = tegra_dc_readl(dc, DC_CMD_INT_MASK); val &= ~(FRAME_END_INT | V_BLANK_INT | ALL_UF_INT); tegra_dc_writel(dc, val, DC_CMD_INT_MASK); 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); 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); /* clean & enable DC interrupts */ 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); } 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; }
/* Functional initialization */ void nvsd_init(struct tegra_dc *dc, struct tegra_dc_sd_settings *settings) { u32 i = 0; u32 val = 0; u32 bw_idx = 0; u32 bw = 0; /* TODO: check if HW says SD's available */ /* If SD's not present or disabled, clear the register and return. */ if (!settings || settings->enable == 0) { /* clear the brightness val, too. */ if (sd_brightness) atomic_set(sd_brightness, 255); sd_brightness = NULL; tegra_dc_writel(dc, 0, DC_DISP_SD_CONTROL); return; } dev_dbg(&dc->ndev->dev, "NVSD Init:\n"); /* WAR: Settings will not be valid until the next flip. * Thus, set manual K to either HW's current value (if * we're already enabled) or a non-effective value (if * we're about to enable). */ val = tegra_dc_readl(dc, DC_DISP_SD_CONTROL); if (val & SD_ENABLE_NORMAL) i = tegra_dc_readl(dc, DC_DISP_SD_HW_K_VALUES); else i = 0; /* 0 values for RGB = 1.0, i.e. non-affected */ tegra_dc_writel(dc, i, DC_DISP_SD_MAN_K_VALUES); /* Enable manual correction mode here so that changing the * settings won't immediately impact display dehavior. */ val |= SD_CORRECTION_MODE_MAN; tegra_dc_writel(dc, val, DC_DISP_SD_CONTROL); switch (settings->bin_width) { default: case -1: /* A -1 bin-width indicates 'automatic' * based upon aggressiveness. */ settings->bin_width = -1; switch (settings->aggressiveness) { default: case 0: case 1: bw = SD_BIN_WIDTH_ONE; break; case 2: case 3: case 4: bw = SD_BIN_WIDTH_TWO; break; case 5: bw = SD_BIN_WIDTH_FOUR; break; } break; case 1: bw = SD_BIN_WIDTH_ONE; break; case 2: bw = SD_BIN_WIDTH_TWO; break; case 4: bw = SD_BIN_WIDTH_FOUR; break; case 8: bw = SD_BIN_WIDTH_EIGHT; break; } bw_idx = bw >> 3; /* Write LUT */ dev_dbg(&dc->ndev->dev, " LUT:\n"); for (i = 0; i < DC_DISP_SD_LUT_NUM; i++) { val = SD_LUT_R(settings->lut[bw_idx][i].r) | SD_LUT_G(settings->lut[bw_idx][i].g) | SD_LUT_B(settings->lut[bw_idx][i].b); tegra_dc_writel(dc, val, DC_DISP_SD_LUT(i)); dev_dbg(&dc->ndev->dev, " %d: 0x%08x\n", i, val); } /* Write BL TF */ dev_dbg(&dc->ndev->dev, " BL_TF:\n"); for (i = 0; i < DC_DISP_SD_BL_TF_NUM; i++) { val = SD_BL_TF_POINT_0(settings->bltf[bw_idx][i][0]) | SD_BL_TF_POINT_1(settings->bltf[bw_idx][i][1]) | SD_BL_TF_POINT_2(settings->bltf[bw_idx][i][2]) | SD_BL_TF_POINT_3(settings->bltf[bw_idx][i][3]); tegra_dc_writel(dc, val, DC_DISP_SD_BL_TF(i)); dev_dbg(&dc->ndev->dev, " %d: 0x%08x\n", i, val); } /* Write Coeff */ val = SD_CSC_COEFF_R(settings->coeff.r) | SD_CSC_COEFF_G(settings->coeff.g) | SD_CSC_COEFF_B(settings->coeff.b); tegra_dc_writel(dc, val, DC_DISP_SD_CSC_COEFF); dev_dbg(&dc->ndev->dev, " COEFF: 0x%08x\n", val); /* Write BL Params */ val = SD_BLP_TIME_CONSTANT(settings->blp.time_constant) | SD_BLP_STEP(settings->blp.step); tegra_dc_writel(dc, val, DC_DISP_SD_BL_PARAMETERS); dev_dbg(&dc->ndev->dev, " BLP: 0x%08x\n", val); /* Write Auto/Manual PWM */ val = (settings->use_auto_pwm) ? SD_BLC_MODE_AUTO : SD_BLC_MODE_MAN; tegra_dc_writel(dc, val, DC_DISP_SD_BL_CONTROL); dev_dbg(&dc->ndev->dev, " BL_CONTROL: 0x%08x\n", val); /* Write Flicker Control */ val = SD_FC_TIME_LIMIT(settings->fc.time_limit) | SD_FC_THRESHOLD(settings->fc.threshold); tegra_dc_writel(dc, val, DC_DISP_SD_FLICKER_CONTROL); dev_dbg(&dc->ndev->dev, " FLICKER_CONTROL: 0x%08x\n", val); /* Manage SD Control */ val = 0; /* Stay in manual correction mode until the next flip. */ val |= SD_CORRECTION_MODE_MAN; /* Enable / One-Shot */ val |= (settings->enable == 2) ? (SD_ENABLE_ONESHOT | SD_ONESHOT_ENABLE) : SD_ENABLE_NORMAL; /* HW Update Delay */ val |= SD_HW_UPDATE_DLY(settings->hw_update_delay); /* Video Luma */ val |= (settings->use_vid_luma) ? SD_USE_VID_LUMA : 0; /* Aggressiveness */ val |= SD_AGGRESSIVENESS(settings->aggressiveness); /* Bin Width (value derived above) */ val |= bw; /* Finally, Write SD Control */ tegra_dc_writel(dc, val, DC_DISP_SD_CONTROL); dev_dbg(&dc->ndev->dev, " SD_CONTROL: 0x%08x\n", val); /* set the brightness pointer */ sd_brightness = settings->sd_brightness; /* note that we're in manual K until the next flip */ atomic_set(&man_k_until_blank, 1); }
static inline u32 tegra_plane_readl(struct tegra_plane *plane, unsigned int offset) { return tegra_dc_readl(plane->dc, tegra_plane_offset(plane, offset)); }
int esc_mods_tegra_dc_setup_sd(struct file *fp, struct MODS_TEGRA_DC_SETUP_SD *args) { int i; struct tegra_dc *dc = tegra_dc_get_dc(args->head); struct tegra_dc_sd_settings *sd_settings = dc->out->sd_settings; #if defined(CONFIG_ARCH_TEGRA_12x_SOC) u32 val; #endif u32 bw_idx; LOG_ENT(); BUG_ON(args->head > TEGRA_MAX_DC); sd_settings->enable = args->enable ? 1 : 0; sd_settings->use_auto_pwm = false; sd_settings->hw_update_delay = 0; sd_settings->aggressiveness = args->aggressiveness; sd_settings->bin_width = (1 << args->bin_width_log2); sd_settings->phase_in_settings = 0; sd_settings->phase_in_adjustments = 0; sd_settings->cmd = 0; sd_settings->final_agg = args->aggressiveness; sd_settings->cur_agg_step = 0; sd_settings->phase_settings_step = 0; sd_settings->phase_adj_step = 0; sd_settings->num_phase_in_steps = 0; sd_settings->agg_priorities.agg[0] = args->aggressiveness; sd_settings->use_vid_luma = args->use_vid_luma; sd_settings->coeff.r = args->csc_r; sd_settings->coeff.g = args->csc_g; sd_settings->coeff.b = args->csc_b; sd_settings->k_limit_enable = (args->klimit != 0); sd_settings->k_limit = args->klimit; sd_settings->sd_window_enable = true; sd_settings->sd_window.h_position = args->win_x; sd_settings->sd_window.v_position = args->win_y; sd_settings->sd_window.h_size = args->win_w; sd_settings->sd_window.v_size = args->win_h; sd_settings->soft_clipping_enable = true; sd_settings->soft_clipping_threshold = args->soft_clipping_threshold; sd_settings->smooth_k_enable = (args->smooth_k_inc != 0); sd_settings->smooth_k_incr = args->smooth_k_inc; sd_settings->sd_proc_control = false; sd_settings->soft_clipping_correction = false; sd_settings->use_vpulse2 = false; sd_settings->fc.time_limit = 0; sd_settings->fc.threshold = 0; sd_settings->blp.time_constant = 1024; sd_settings->blp.step = 0; #ifdef CONFIG_TEGRA_SD_GEN2 bw_idx = 0; #else bw_idx = args->bin_width_log2; #endif for (i = 0; i < MODS_TEGRA_DC_SETUP_BLTF_SIZE; i++) { sd_settings->bltf[bw_idx][i/4][i%4] = args->bltf[i]; } for (i = 0; i < MODS_TEGRA_DC_SETUP_SD_LUT_SIZE; i++) { sd_settings->lut[bw_idx][i].r = args->lut[i] & 0xff; sd_settings->lut[bw_idx][i].g = (args->lut[i] >> 8) & 0xff; sd_settings->lut[bw_idx][i].b = (args->lut[i] >> 16) & 0xff; } #if defined(CONFIG_TEGRA_NVSD) nvsd_init(dc, sd_settings); #endif #if defined(CONFIG_ARCH_TEGRA_12x_SOC) tegra_dc_io_start(dc); val = tegra_dc_readl(dc, DC_DISP_SD_CONTROL); val &= ~SD_KINIT_BIAS(0); val &= ~SD_CORRECTION_MODE_MAN; tegra_dc_writel(dc, val | SD_KINIT_BIAS(args->k_init_bias), DC_DISP_SD_CONTROL); tegra_dc_io_end(dc); #endif if (dc->enabled) { mutex_lock(&dc->lock); tegra_dc_get(dc); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); tegra_dc_put(dc); mutex_unlock(&dc->lock); } LOG_EXT(); return 0; }