/* * drivers/video/tegra/dc/nvsd.c * * Copyright (c) 2010-2014, NVIDIA CORPORATION, All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include "dc_reg.h" #include "dc_priv.h" #include "nvsd.h" /* Elements for sysfs access */ #define NVSD_ATTR(__name) static struct kobj_attribute nvsd_attr_##__name = \ __ATTR(__name, S_IRUGO|S_IWUSR, nvsd_settings_show, nvsd_settings_store) #define NVSD_ATTRS_ENTRY(__name) (&nvsd_attr_##__name.attr) #define IS_NVSD_ATTR(__name) (attr == &nvsd_attr_##__name) static ssize_t nvsd_settings_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); static ssize_t nvsd_settings_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count); static ssize_t nvsd_registers_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); NVSD_ATTR(enable); NVSD_ATTR(aggressiveness); NVSD_ATTR(phase_in_settings); NVSD_ATTR(phase_in_adjustments); NVSD_ATTR(bin_width); NVSD_ATTR(hw_update_delay); NVSD_ATTR(use_vid_luma); NVSD_ATTR(coeff); NVSD_ATTR(blp_time_constant); NVSD_ATTR(blp_step); NVSD_ATTR(fc_time_limit); NVSD_ATTR(fc_threshold); NVSD_ATTR(lut); NVSD_ATTR(bltf); #ifdef CONFIG_TEGRA_SD_GEN2 NVSD_ATTR(k_limit_enable); NVSD_ATTR(k_limit); NVSD_ATTR(sd_window_enable); NVSD_ATTR(sd_window); NVSD_ATTR(soft_clipping_enable); NVSD_ATTR(soft_clipping_threshold); NVSD_ATTR(soft_clipping_correction); NVSD_ATTR(smooth_k_enable); NVSD_ATTR(smooth_k_incr); NVSD_ATTR(use_vpulse2); #endif static struct kobj_attribute nvsd_attr_registers = __ATTR(registers, S_IRUGO, nvsd_registers_show, NULL); static struct attribute *nvsd_attrs[] = { NVSD_ATTRS_ENTRY(enable), NVSD_ATTRS_ENTRY(aggressiveness), NVSD_ATTRS_ENTRY(phase_in_settings), NVSD_ATTRS_ENTRY(phase_in_adjustments), NVSD_ATTRS_ENTRY(bin_width), NVSD_ATTRS_ENTRY(hw_update_delay), NVSD_ATTRS_ENTRY(use_vid_luma), NVSD_ATTRS_ENTRY(coeff), NVSD_ATTRS_ENTRY(blp_time_constant), NVSD_ATTRS_ENTRY(blp_step), NVSD_ATTRS_ENTRY(fc_time_limit), NVSD_ATTRS_ENTRY(fc_threshold), NVSD_ATTRS_ENTRY(lut), NVSD_ATTRS_ENTRY(bltf), NVSD_ATTRS_ENTRY(registers), #ifdef CONFIG_TEGRA_SD_GEN2 NVSD_ATTRS_ENTRY(k_limit_enable), NVSD_ATTRS_ENTRY(k_limit), NVSD_ATTRS_ENTRY(sd_window_enable), NVSD_ATTRS_ENTRY(sd_window), NVSD_ATTRS_ENTRY(soft_clipping_enable), NVSD_ATTRS_ENTRY(soft_clipping_threshold), NVSD_ATTRS_ENTRY(soft_clipping_correction), NVSD_ATTRS_ENTRY(smooth_k_enable), NVSD_ATTRS_ENTRY(smooth_k_incr), NVSD_ATTRS_ENTRY(use_vpulse2), #endif NULL, }; static struct attribute_group nvsd_attr_group = { .attrs = nvsd_attrs, }; static struct kobject *nvsd_kobj; /* shared brightness variable */ static atomic_t *_sd_brightness; /* shared boolean for manual K workaround */ static atomic_t man_k_until_blank = ATOMIC_INIT(0); static u16 smooth_k_frames_left; static u16 smooth_k_duration_frames; static u8 nvsd_get_bw_idx(struct tegra_dc_sd_settings *settings) { u8 bw; 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; } return bw >> 3; } static bool nvsd_phase_in_adjustments(struct tegra_dc *dc, struct tegra_dc_sd_settings *settings) { u8 step, cur_sd_brightness; u16 target_k, cur_k; u32 man_k, val; 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); 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_io_start(dc); tegra_dc_writel(dc, man_k, DC_DISP_SD_MAN_K_VALUES); tegra_dc_io_end(dc); /* Set manual brightness value */ atomic_set(_sd_brightness, cur_sd_brightness); } settings->phase_adj_step = step; return true; } else return false; } /* phase in the luts based on the current and max step */ static void nvsd_phase_in_luts(struct tegra_dc_sd_settings *settings, struct tegra_dc *dc) { u32 val; u8 bw_idx; int i; u16 phase_settings_step = settings->phase_settings_step; u16 num_phase_in_steps = settings->num_phase_in_steps; bw_idx = nvsd_get_bw_idx(settings); /* Phase in Final LUT */ for (i = 0; i < DC_DISP_SD_LUT_NUM; i++) { val = SD_LUT_R((settings->lut[bw_idx][i].r * phase_settings_step)/num_phase_in_steps) | SD_LUT_G((settings->lut[bw_idx][i].g * phase_settings_step)/num_phase_in_steps) | SD_LUT_B((settings->lut[bw_idx][i].b * phase_settings_step)/num_phase_in_steps); tegra_dc_writel(dc, val, DC_DISP_SD_LUT(i)); } /* Phase in Final BLTF */ for (i = 0; i < DC_DISP_SD_BL_TF_NUM; i++) { val = SD_BL_TF_POINT_0(255-((255-settings->bltf[bw_idx][i][0]) * phase_settings_step)/num_phase_in_steps) | SD_BL_TF_POINT_1(255-((255-settings->bltf[bw_idx][i][1]) * phase_settings_step)/num_phase_in_steps) | SD_BL_TF_POINT_2(255-((255-settings->bltf[bw_idx][i][2]) * phase_settings_step)/num_phase_in_steps) | SD_BL_TF_POINT_3(255-((255-settings->bltf[bw_idx][i][3]) * phase_settings_step)/num_phase_in_steps); tegra_dc_writel(dc, val, DC_DISP_SD_BL_TF(i)); } } /* 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 nvsd_update_enable(struct tegra_dc_sd_settings *settings, int enable_val) { if (enable_val != 1 && enable_val != 0) return false; if (!settings->cmd && settings->enable != enable_val) { settings->num_phase_in_steps = STEPS_PER_AGG_LVL*settings->aggressiveness; settings->phase_settings_step = enable_val ? 0 : settings->num_phase_in_steps; } if (settings->enable != enable_val || settings->cmd & DISABLE) { settings->cmd &= ~(ENABLE | DISABLE); if (!settings->enable && enable_val) settings->cmd |= PHASE_IN; settings->cmd |= enable_val ? ENABLE : DISABLE; return true; } return false; } static bool nvsd_update_agg(struct tegra_dc_sd_settings *settings, int agg_val) { int i; int pri_lvl = SD_AGG_PRI_LVL(agg_val); int agg_lvl = SD_GET_AGG(agg_val); struct tegra_dc_sd_agg_priorities *sd_agg_priorities = &settings->agg_priorities; if (agg_lvl > 5 || agg_lvl < 0) return false; else if (agg_lvl == 0 && pri_lvl == 0) return false; if (pri_lvl >= 0 && pri_lvl < 4) sd_agg_priorities->agg[pri_lvl] = agg_lvl; for (i = NUM_AGG_PRI_LVLS - 1; i >= 0; i--) { if (sd_agg_priorities->agg[i]) break; } sd_agg_priorities->pri_lvl = i; pri_lvl = i; agg_lvl = sd_agg_priorities->agg[i]; if (settings->phase_in_settings && settings->enable && settings->aggressiveness != agg_lvl) { settings->final_agg = agg_lvl; settings->cmd |= AGG_CHG; settings->cur_agg_step = 0; return true; } else if (settings->aggressiveness != agg_lvl) { settings->aggressiveness = agg_lvl; return true; } 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 = 0; u32 bw_idx = 0; /* TODO: check if HW says SD's available */ tegra_dc_io_start(dc); /* 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); tegra_dc_io_end(dc); 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); bw = SD_BIN_WIDTH(bw_idx); /* 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. On T114, we will use 1st table by default.*/ #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) { fixed20_12 smooth_k_incr; fixed20_12 num; /* 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); /* Convert 8.6 fixed-point to 20.12 fixed-point */ smooth_k_incr.full = val << 6; /* In the BL_TF LUT, raw K is specified in steps of 8 */ num.full = dfixed_const(8); num.full = dfixed_div(num, smooth_k_incr); num.full = dfixed_ceil(num); smooth_k_frames_left = dfixed_trunc(num); smooth_k_duration_frames = smooth_k_frames_left; dev_dbg(&dc->ndev->dev, " Smooth K duration (frames): %d\n", smooth_k_frames_left); } #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 above) */ val |= bw; #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); tegra_dc_io_end(dc); /* 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 int bl_tf[17] = { 57, 65, 73, 82, 92, 103, 114, 125, 138, 150, 164, 178, 193, 208, 224, 241, 255, }; static int nvsd_backlght_interplate(u32 in, u32 base) { int q, r; if (in <= base) return bl_tf[0]; if (in > 255) { WARN(1, "PRISM gain is out of range!\n"); return -1; } q = (in - base) / 8; r = (in - base) % 8; return (bl_tf[q] * (8 - r) + bl_tf[q + 1] * r) / 8; } /* Estimate the pixel gain of PRISM enhancement and soft-clipping algorithm*/ static u32 nvsd_softclip(fixed20_12 pixel, fixed20_12 k, fixed20_12 th) { fixed20_12 num, f; if (pixel.full >= th.full) { num.full = pixel.full - th.full; f.full = dfixed_const(1) - dfixed_div(num, th); } else { f.full = dfixed_const(1); } num.full = dfixed_mul(pixel, f); f.full = dfixed_mul(num, k); num.full = pixel.full + f.full; return min_t(u32, num.full, dfixed_const(255)); } 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; } static ssize_t nvsd_lut_show(struct tegra_dc_sd_settings *sd_settings, char *buf, ssize_t res) { u32 i; u32 j; for (i = 0; i < NUM_BIN_WIDTHS; i++) { res += snprintf(buf + res, PAGE_SIZE - res, "Bin Width: %d\n", 1 << i); for (j = 0; j < DC_DISP_SD_LUT_NUM; j++) { res += snprintf(buf + res, PAGE_SIZE - res, "%d: R: %3d / G: %3d / B: %3d\n", j, sd_settings->lut[i][j].r, sd_settings->lut[i][j].g, sd_settings->lut[i][j].b); } } return res; } static ssize_t nvsd_bltf_show(struct tegra_dc_sd_settings *sd_settings, char *buf, ssize_t res) { u32 i; u32 j; for (i = 0; i < NUM_BIN_WIDTHS; i++) { res += snprintf(buf + res, PAGE_SIZE - res, "Bin Width: %d\n", 1 << i); for (j = 0; j < DC_DISP_SD_BL_TF_NUM; j++) { res += snprintf(buf + res, PAGE_SIZE - res, "%d: 0: %3d / 1: %3d / 2: %3d / 3: %3d\n", j, sd_settings->bltf[i][j][0], sd_settings->bltf[i][j][1], sd_settings->bltf[i][j][2], sd_settings->bltf[i][j][3]); } } return res; } /* Sysfs accessors */ static ssize_t nvsd_settings_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct device *dev = container_of((kobj->parent), struct device, kobj); struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); struct tegra_dc_sd_settings *sd_settings = dc->out->sd_settings; ssize_t res = 0; if (sd_settings) { if (IS_NVSD_ATTR(enable)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->enable); else if (IS_NVSD_ATTR(aggressiveness)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->aggressiveness); else if (IS_NVSD_ATTR(phase_in_settings)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->phase_in_settings); else if (IS_NVSD_ATTR(phase_in_adjustments)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->phase_in_adjustments); else if (IS_NVSD_ATTR(bin_width)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->bin_width); else if (IS_NVSD_ATTR(hw_update_delay)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->hw_update_delay); else if (IS_NVSD_ATTR(use_vid_luma)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->use_vid_luma); else if (IS_NVSD_ATTR(coeff)) res = snprintf(buf, PAGE_SIZE, "R: %d / G: %d / B: %d\n", sd_settings->coeff.r, sd_settings->coeff.g, sd_settings->coeff.b); else if (IS_NVSD_ATTR(blp_time_constant)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->blp.time_constant); else if (IS_NVSD_ATTR(blp_step)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->blp.step); else if (IS_NVSD_ATTR(fc_time_limit)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->fc.time_limit); else if (IS_NVSD_ATTR(fc_threshold)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->fc.threshold); #ifdef CONFIG_TEGRA_SD_GEN2 else if (IS_NVSD_ATTR(k_limit_enable)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->k_limit_enable); else if (IS_NVSD_ATTR(k_limit)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->k_limit); else if (IS_NVSD_ATTR(sd_window_enable)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->sd_window_enable); else if (IS_NVSD_ATTR(sd_window)) res = snprintf(buf, PAGE_SIZE, "x: %d, y: %d, w: %d, h: %d\n", sd_settings->sd_window.h_position, sd_settings->sd_window.v_position, sd_settings->sd_window.h_size, sd_settings->sd_window.v_size); else if (IS_NVSD_ATTR(soft_clipping_enable)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->soft_clipping_enable); else if (IS_NVSD_ATTR(soft_clipping_threshold)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->soft_clipping_threshold); else if (IS_NVSD_ATTR(soft_clipping_correction)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->soft_clipping_correction); else if (IS_NVSD_ATTR(smooth_k_enable)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->smooth_k_enable); else if (IS_NVSD_ATTR(smooth_k_incr)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->smooth_k_incr); else if (IS_NVSD_ATTR(use_vpulse2)) res = snprintf(buf, PAGE_SIZE, "%d\n", sd_settings->use_vpulse2); #endif else if (IS_NVSD_ATTR(lut)) res = nvsd_lut_show(sd_settings, buf, res); else if (IS_NVSD_ATTR(bltf)) res = nvsd_bltf_show(sd_settings, buf, res); else res = -EINVAL; } else { /* This shouldn't be reachable. But just in case... */ res = -EINVAL; } return res; } #define nvsd_check_and_update(_min, _max, _varname) { \ int val = simple_strtol(buf, NULL, 10); \ if (val >= _min && val <= _max) { \ sd_settings->_varname = val; \ settings_updated = true; \ } } #define nvsd_get_multi(_ele, _num, _act, _min, _max) { \ char *b, *c, *orig_b; \ b = orig_b = kstrdup(buf, GFP_KERNEL); \ for (_act = 0; _act < _num; _act++) { \ if (!b) \ break; \ b = strim(b); \ c = strsep(&b, " "); \ if (!strlen(c)) \ break; \ _ele[_act] = simple_strtol(c, NULL, 10); \ if (_ele[_act] < _min || _ele[_act] > _max) \ break; \ } \ if (orig_b) \ kfree(orig_b); \ } static int nvsd_lut_store(struct tegra_dc_sd_settings *sd_settings, const char *buf) { int ele[3 * DC_DISP_SD_LUT_NUM * NUM_BIN_WIDTHS]; int i = 0; int j = 0; int num = 3 * DC_DISP_SD_LUT_NUM * NUM_BIN_WIDTHS; nvsd_get_multi(ele, num, i, 0, 255); if (i != num) return -EINVAL; for (i = 0; i < NUM_BIN_WIDTHS; i++) { for (j = 0; j < DC_DISP_SD_LUT_NUM; j++) { sd_settings->lut[i][j].r = ele[i * NUM_BIN_WIDTHS + j * 3 + 0]; sd_settings->lut[i][j].g = ele[i * NUM_BIN_WIDTHS + j * 3 + 1]; sd_settings->lut[i][j].b = ele[i * NUM_BIN_WIDTHS + j * 3 + 2]; } } return 0; } static int nvsd_bltf_store(struct tegra_dc_sd_settings *sd_settings, const char *buf) { int ele[4 * DC_DISP_SD_BL_TF_NUM * NUM_BIN_WIDTHS]; int i = 0, j = 0, num = ARRAY_SIZE(ele); nvsd_get_multi(ele, num, i, 0, 255); if (i != num) return -EINVAL; for (i = 0; i < NUM_BIN_WIDTHS; i++) { for (j = 0; j < DC_DISP_SD_BL_TF_NUM; j++) { size_t base = (i * NUM_BIN_WIDTHS * DC_DISP_SD_BL_TF_NUM) + (j * 4); sd_settings->bltf[i][j][0] = ele[base + 0]; sd_settings->bltf[i][j][1] = ele[base + 1]; sd_settings->bltf[i][j][2] = ele[base + 2]; sd_settings->bltf[i][j][3] = ele[base + 3]; } } return 0; } static ssize_t nvsd_settings_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct device *dev = container_of((kobj->parent), struct device, kobj); struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); struct tegra_dc_sd_settings *sd_settings = dc->out->sd_settings; ssize_t res = count; bool settings_updated = false; long int result; int err; if (sd_settings) { if (IS_NVSD_ATTR(enable)) { if (sd_settings->phase_in_settings) { err = strict_strtol(buf, 10, &result); if (err) return err; if (nvsd_update_enable(sd_settings, result)) nvsd_check_and_update(1, 1, enable); } else { nvsd_check_and_update(0, 1, enable); } } else if (IS_NVSD_ATTR(aggressiveness)) { err = strict_strtol(buf, 10, &result); if (err) return err; if (nvsd_update_agg(sd_settings, result) && !sd_settings->phase_in_settings) settings_updated = true; } else if (IS_NVSD_ATTR(phase_in_settings)) { nvsd_check_and_update(0, 1, phase_in_settings); } else if (IS_NVSD_ATTR(phase_in_adjustments)) { nvsd_check_and_update(0, 1, phase_in_adjustments); } else if (IS_NVSD_ATTR(bin_width)) { nvsd_check_and_update(-1, 8, bin_width); } else if (IS_NVSD_ATTR(hw_update_delay)) { nvsd_check_and_update(0, 2, hw_update_delay); } else if (IS_NVSD_ATTR(use_vid_luma)) { nvsd_check_and_update(0, 1, use_vid_luma); } else if (IS_NVSD_ATTR(coeff)) { int ele[3], i = 0, num = 3; nvsd_get_multi(ele, num, i, 0, 15); if (i == num) { sd_settings->coeff.r = ele[0]; sd_settings->coeff.g = ele[1]; sd_settings->coeff.b = ele[2]; settings_updated = true; } else { res = -EINVAL; } } else if (IS_NVSD_ATTR(blp_time_constant)) { nvsd_check_and_update(0, 1024, blp.time_constant); } else if (IS_NVSD_ATTR(blp_step)) { nvsd_check_and_update(0, 255, blp.step); } else if (IS_NVSD_ATTR(fc_time_limit)) { nvsd_check_and_update(0, 255, fc.time_limit); } else if (IS_NVSD_ATTR(fc_threshold)) { nvsd_check_and_update(0, 255, fc.threshold); #ifdef CONFIG_TEGRA_SD_GEN2 } else if (IS_NVSD_ATTR(k_limit_enable)) { nvsd_check_and_update(0, 1, k_limit_enable); } else if (IS_NVSD_ATTR(k_limit)) { nvsd_check_and_update(128, 255, k_limit); } else if (IS_NVSD_ATTR(sd_window_enable)) { nvsd_check_and_update(0, 1, sd_window_enable); } else if (IS_NVSD_ATTR(sd_window)) { int ele[4], i = 0, num = 4; nvsd_get_multi(ele, num, i, 0, LONG_MAX); if (i == num) { sd_settings->sd_window.h_position = ele[0]; sd_settings->sd_window.v_position = ele[1]; sd_settings->sd_window.h_size = ele[2]; sd_settings->sd_window.v_size = ele[3]; settings_updated = true; } else { res = -EINVAL; } } else if (IS_NVSD_ATTR(soft_clipping_enable)) { nvsd_check_and_update(0, 1, soft_clipping_enable); } else if (IS_NVSD_ATTR(soft_clipping_threshold)) { nvsd_check_and_update(0, 255, soft_clipping_threshold); } else if (IS_NVSD_ATTR(soft_clipping_correction)) { nvsd_check_and_update(0, 1, soft_clipping_correction); } else if (IS_NVSD_ATTR(smooth_k_enable)) { nvsd_check_and_update(0, 1, smooth_k_enable); } else if (IS_NVSD_ATTR(smooth_k_incr)) { nvsd_check_and_update(0, 16320, smooth_k_incr); } else if (IS_NVSD_ATTR(use_vpulse2)) { nvsd_check_and_update(0, 1, use_vpulse2); #endif } else if (IS_NVSD_ATTR(lut)) { if (nvsd_lut_store(sd_settings, buf)) res = -EINVAL; else settings_updated = true; } else if (IS_NVSD_ATTR(bltf)) { if (nvsd_bltf_store(sd_settings, buf)) res = -EINVAL; else settings_updated = true; } else { res = -EINVAL; } /* Re-init if our settings were updated. */ if (settings_updated) { mutex_lock(&dc->lock); if (!dc->enabled) { mutex_unlock(&dc->lock); return -ENODEV; } tegra_dc_get(dc); nvsd_init(dc, sd_settings); tegra_dc_put(dc); mutex_unlock(&dc->lock); /* Update backlight state IFF we're disabling! */ if (!sd_settings->enable && sd_settings->bl_device) { /* Do the actual brightness update outside of * the mutex */ struct backlight_device *bl = sd_settings->bl_device; if (bl) backlight_update_status(bl); } } } else { /* This shouldn't be reachable. But just in case... */ res = -EINVAL; } return res; } #define NVSD_PRINT_REG(__name) { \ u32 val = tegra_dc_readl(dc, __name); \ res += snprintf(buf + res, PAGE_SIZE - res, #__name ": 0x%08x\n", \ val); \ } #define NVSD_PRINT_REG_ARRAY(__name) { \ u32 val = 0, i = 0; \ res += snprintf(buf + res, PAGE_SIZE - res, #__name ":\n"); \ for (i = 0; i < __name##_NUM; i++) { \ val = tegra_dc_readl(dc, __name(i)); \ res += snprintf(buf + res, PAGE_SIZE - res, " %d: 0x%08x\n", \ i, val); \ } \ } static ssize_t nvsd_registers_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct device *dev = container_of((kobj->parent), struct device, kobj); struct platform_device *ndev = to_platform_device(dev); struct tegra_dc *dc = platform_get_drvdata(ndev); ssize_t res = 0; mutex_lock(&dc->lock); if (!dc->enabled) { mutex_unlock(&dc->lock); return -ENODEV; } tegra_dc_get(dc); mutex_unlock(&dc->lock); NVSD_PRINT_REG(DC_DISP_SD_CONTROL); NVSD_PRINT_REG(DC_DISP_SD_CSC_COEFF); NVSD_PRINT_REG_ARRAY(DC_DISP_SD_LUT); NVSD_PRINT_REG(DC_DISP_SD_FLICKER_CONTROL); NVSD_PRINT_REG(DC_DISP_SD_PIXEL_COUNT); NVSD_PRINT_REG_ARRAY(DC_DISP_SD_HISTOGRAM); NVSD_PRINT_REG(DC_DISP_SD_BL_PARAMETERS); NVSD_PRINT_REG_ARRAY(DC_DISP_SD_BL_TF); NVSD_PRINT_REG(DC_DISP_SD_BL_CONTROL); NVSD_PRINT_REG(DC_DISP_SD_HW_K_VALUES); NVSD_PRINT_REG(DC_DISP_SD_MAN_K_VALUES); #ifdef CONFIG_TEGRA_SD_GEN2 NVSD_PRINT_REG(DC_DISP_SD_K_LIMIT); NVSD_PRINT_REG(DC_DISP_SD_WINDOW_POSITION); NVSD_PRINT_REG(DC_DISP_SD_WINDOW_SIZE); NVSD_PRINT_REG(DC_DISP_SD_SOFT_CLIPPING); NVSD_PRINT_REG(DC_DISP_SD_SMOOTH_K); #endif tegra_dc_put(dc); mutex_unlock(&dc->lock); return res; } /* Sysfs initializer */ int nvsd_create_sysfs(struct device *dev) { int retval = 0; nvsd_kobj = kobject_create_and_add("smartdimmer", &dev->kobj); if (!nvsd_kobj) return -ENOMEM; retval = sysfs_create_group(nvsd_kobj, &nvsd_attr_group); if (retval) { kobject_put(nvsd_kobj); dev_err(dev, "%s: failed to create attributes\n", __func__); } return retval; } /* Sysfs destructor */ void nvsd_remove_sysfs(struct device *dev) { if (nvsd_kobj) { sysfs_remove_group(nvsd_kobj, &nvsd_attr_group); kobject_put(nvsd_kobj); } }