blob: 5274329f34286931fc5e9418be7470e15b9c5ce7 [file] [log] [blame]
/*
* drivers/video/tegra/dc/nvsd.c
*
* Copyright (c) 2010-2012, 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 <linux/kernel.h>
#include <mach/dc.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/backlight.h>
#include <linux/stat.h>
#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(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(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 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) {
/* 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 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;
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 = SD_HW_K_R(val) << 2;
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)>>3;
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;
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) {
return nvsd_phase_in_adjustments(dc, settings);
} else if (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);
return true;
}
} 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 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(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(0, 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(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;
clk_prepare_enable(dc->clk);
tegra_dc_io_start(dc);
mutex_lock(&dc->lock);
if (!dc->enabled) {
mutex_unlock(&dc->lock);
return -ENODEV;
}
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_io_end(dc);
clk_disable_unprepare(dc->clk);
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);
}
}