/* * linux/drivers/video/fbdev/exynos/panel/dimming.c * * Samsung AID Dimming Driver. * * Copyright (c) 2016 Samsung Electronics * Gwanghui Lee * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include "panel_drv.h" #include "panel_debug.h" #include "dimming.h" #include "dimming_gamma.h" static int NR_LUMINANCE; static int NR_TP; static int TP_VT = 0; static int TP_V0 = -1; static int TP_V255 = -1; static int TARGET_LUMINANCE; static enum gamma_degree TARGET_G_CURVE_DEGREE; static int *calc_gray_order; static int *calc_vout_order; const char *tag_name[] = { "[DIM_GLOBAL_INFO_START]", "[DIM_GLOBAL_INFO_END]", "[TP_LUT_INFO_START]", "[TP_LUT_INFO_END]", "[TP_LUT_START]", "[TP_LUT_END]", "[DIM_LUT_INFO_START]", "[DIM_LUT_INFO_END]", "[DIM_LUT_START]", "[DIM_LUT_END]", "[MTP_OFFSET_START]", "[MTP_OFFSET_END]", "[TP_VOLT_START]", "[TP_VOLT_END]", "[GRAY_SCALE_VOLT_START]", "[GRAY_SCALE_VOLT_END]", "[GAMMA_CENTER_START]", "[GAMMA_CENTER_END]", "[MTP_OFFSET_TEST_RANGE_START]", "[MTP_OFFSET_TEST_RANGE_END]", }; const char *global_dim_info_name[] = { "NR_TP", "NR_LUMINANCE", "VREGOUT", "VREF", "GAMMA", "VT_VOLTAGE", }; const char *tp_lut_field_name[] = { "TP_NONE", "TP_LEVEL", "TP_NAME", "TP_VOLT_SRC", "TP_GAMMA_CENTER", "TP_GAMMA_CALC", }; const char *dim_lut_field_name[] = { "DIM_NONE" , "DIM_LUMINANCE", "DIM_AOR", "DIM_BASE_LUMINANCE", "DIM_GAMMA", "DIM_GRAY_SCALE_OFFSET", "DIM_COLOR_SHIFT_OFFSET", }; const char *color_name[] = { "RED", "GREEN", "BLUE", }; const char *volt_src_name[] = { "VREG_OUT", "V0_OUT", "VT_OUT", }; struct gamma_curve gamma_curve_lut[] = { #ifndef GENERATE_GAMMA_CURVE { 0, NULL }, { 160, gamma_curve_1p60 }, { 165, gamma_curve_1p65 }, { 170, gamma_curve_1p70 }, { 175, gamma_curve_1p75 }, { 180, gamma_curve_1p80 }, { 185, gamma_curve_1p85 }, { 190, gamma_curve_1p90 }, { 195, gamma_curve_1p95 }, { 200, gamma_curve_2p00 }, { 205, gamma_curve_2p05 }, { 210, gamma_curve_2p10 }, { 212, gamma_curve_2p12 }, { 213, gamma_curve_2p13 }, { 215, gamma_curve_2p15 }, { 220, gamma_curve_2p20 }, { 225, gamma_curve_2p25 }, #else { 0, NULL }, { 160, NULL }, { 165, NULL }, { 170, NULL }, { 175, NULL }, { 180, NULL }, { 185, NULL }, { 190, NULL }, { 195, NULL }, { 200, NULL }, { 205, NULL }, { 210, NULL }, { 212, NULL }, { 213, NULL }, { 215, NULL }, { 220, NULL }, { 225, NULL }, #endif /* GENERATE_GAMMA_CURVE */ }; s64 disp_pow(s64 num, u32 digits) { s64 res = num; u32 i; if (digits == 0) return 1; for (i = 1; i < digits; i++) res *= num; return res; } EXPORT_SYMBOL(disp_pow); s64 disp_round(s64 num, u32 digits) { int sign = (num < 0 ? -1 : 1); u64 tnum; tnum = num * sign; tnum *= disp_pow(10, digits + 1); tnum += 5; do_div(tnum, (u32)disp_pow(10, digits + 1)); return sign * (s64)tnum; } static s64 scale_down_round(s64 num, u32 digits) { int sign = (num < 0 ? -1 : 1); u64 tnum, rem; tnum = num * sign; rem = do_div(tnum, (1U << BIT_SHIFT)); rem *= disp_pow(10, digits + 1); rem >>= BIT_SHIFT; rem += 5; do_div(rem, (u32)disp_pow(10, digits + 1)); return sign * (s64)(tnum + rem); } static s64 scale_down_rem(s64 num, u32 digits) { int sign = (num < 0 ? -1 : 1); u64 tnum, rem; tnum = num * sign; rem = do_div(tnum, (1U << BIT_SHIFT)); rem *= disp_pow(10, digits + 1); rem >>= BIT_SHIFT; rem += 5; do_div(rem, 10); /* round up and minus in remainder */ if (rem >= (u64)disp_pow(10, digits)) rem -= (u64)disp_pow(10, digits); return sign * rem; } #if BIT_SHIFT > 26 static int msb64(s64 num) { int i; for (i = 63; i >= 0; i--) if (num & (1ULL << i)) return i; return 0; } #endif s64 disp_div64(s64 num, s64 den) { #if BIT_SHIFT > 26 if (num > ((1LL << (63 - BIT_SHIFT)) - 1)) { int bit_shift = 62 - msb64(num); panel_err("out of range num %lld\n", num); return ((num << bit_shift) / den) >> bit_shift; } #endif int sign_num = (num < 0 ? -1 : 1); int sign_den = (den < 0 ? -1 : 1); int sign = sign_num * sign_den; u64 tnum, tden; if (unlikely(den == 0)) { panel_err("den should not be zero (%llu %llu)\n", num, den); return 0; } tnum = num * sign_num; tden = den * sign_den; if (tden >= (1ULL << 31)) panel_err("out of range denominator %llu\n", tden); do_div(tnum, (u32)tden); return sign * tnum; } EXPORT_SYMBOL(disp_div64); #ifdef DIMMING_CALC_PRECISE s64 disp_div64_round(s64 num, s64 den, u32 digits) { int sign_num = (num < 0 ? -1 : 1); int sign_den = (den < 0 ? -1 : 1); int sign = sign_num * sign_den; u64 tnum, tden, rem; if (unlikely(den == 0)) { panel_err("den should not be zero (%llu %llu %u)\n", num, den, digits); return 0; } tnum = num * sign_num; tden = den * sign_den; rem = do_div(tnum, (u32)tden); rem *= disp_pow(10, digits + 2); do_div(rem, (u32)tden); /* * floating point should not be used. * Some variables are inaccurate in most case (e.g. vregout...) * To compensate it, round_up function use (+ 6) instead of (+ 5). */ rem += 5; do_div(rem, (u32)disp_pow(10, digits + 2)); return sign * (tnum + rem); } #endif /* DIMMING_CALC_PRECISE */ /* * generate_order() is an helper function can make an order * that mixed an initial order value and range based order values. * @s : initial value * @from - starting of the range * @to - end of the range including 'to'. * @return - an allocated array will be set as like { s, [from, to] }. */ static int *generate_order(int s, int from, int to) { int i, len = abs(from - to) + 2; int sign = ((to - from) < 0) ? -1 : 1; int *t_order = (int *)kmalloc(len * sizeof(int), GFP_KERNEL); if (!t_order) { panel_err("failed to allocate memory\n"); return NULL; } t_order[0] = s; t_order[1] = from; for (i = 2; i < len; i++) t_order[i] = t_order[i - 1] + sign; return t_order; } static int find_name_in_table(const char **table, int sz_table, char *s) { int i; for (i = 0; i < sz_table; i++) if (!strncmp(s, table[i], 128)) return i; return -EINVAL; } int find_tag_name(char *s) { return find_name_in_table(tag_name, ARRAY_SIZE(tag_name), s); } int find_global_dim_info(char *s) { return find_name_in_table(global_dim_info_name, ARRAY_SIZE(global_dim_info_name), s); } int find_voltage_source(char *s) { return find_name_in_table(volt_src_name, ARRAY_SIZE(volt_src_name), s); } int find_tp_lut_field(char *s) { return find_name_in_table(tp_lut_field_name, ARRAY_SIZE(tp_lut_field_name), s); } int find_dim_lut_field(char *s) { return find_name_in_table(dim_lut_field_name, ARRAY_SIZE(dim_lut_field_name), s); } #ifdef GENERATE_GAMMA_CURVE static void generate_gamma_curve(int gamma, int *gamma_curve) { int i; double gamma_f = (double)gamma / 100; panel_info("generate %d gamma\n", gamma); for (i = 0; i < GRAY_SCALE_MAX; i++) { gamma_curve[i] = (int)(pow(((double)i / 255), gamma_f) * (double)(1 << BIT_SHIFT) + 0.5f); panel_info("%d ", gamma_curve[i]); if (!((i + 1) % 8)) panel_info("\n"); } } void remove_gamma_curve(void) { size_t index; for (index = 0; index < ARRAY_SIZE(gamma_curve_lut); index++) if (gamma_curve_lut[index].gamma_curve_table) { free(gamma_curve_lut[index].gamma_curve_table); gamma_curve_lut[index].gamma_curve_table = NULL; } } #endif /* GENERATE_GAMMA_CURVE */ int find_gamma_curve(int gamma) { size_t index; #ifdef GENERATE_GAMMA_CURVE int *gamma_curve_table; #endif for (index = 0; index < ARRAY_SIZE(gamma_curve_lut); index++) if (gamma_curve_lut[index].gamma == gamma) break; if (index == ARRAY_SIZE(gamma_curve_lut)) { panel_err("gamma %d curve not found\n", gamma); return -1; } #ifdef GENERATE_GAMMA_CURVE if (!gamma_curve_lut[index].gamma_curve_table) { panel_info("generate gamma curve %d\n", gamma); gamma_curve_table = kmalloc(GRAY_SCALE_MAX * sizeof(int), GFP_KERNEL); generate_gamma_curve(gamma, gamma_curve_table); gamma_curve_lut[index].gamma_curve_table = gamma_curve_table; } #endif return (int)index; } enum gamma_degree gamma_value_to_degree(int gamma) { int g_curve_index; g_curve_index = find_gamma_curve(gamma); if (g_curve_index < 0) { panel_err("gamma %d not exist\n", gamma); return GAMMA_NONE; } return (enum gamma_degree)g_curve_index; } /* * @ g_curve : gamma curve * @ idx : gray scale level 0 ~ 255. * @ return ((idx/255)^g_curve)*(2^22) */ int gamma_curve_value(enum gamma_degree g_curve, int idx) { if (g_curve == GAMMA_NONE || idx >= GRAY_SCALE_MAX) return -1; #ifdef GENERATE_GAMMA_CURVE if (!gamma_curve_lut[(int)g_curve].gamma_curve_table) { int *gamma_curve_table = (int *)kmalloc(GRAY_SCALE_MAX * sizeof(int), GFP_KERNEL); int gamma = gamma_curve_lut[(int)g_curve].gamma; panel_info("generate gamma curve %d\n", gamma); generate_gamma_curve(gamma, gamma_curve_table); gamma_curve_lut[(int)g_curve].gamma_curve_table = gamma_curve_table; } #endif return gamma_curve_lut[(int)g_curve].gamma_curve_table[idx]; } bool gamma_in_range(int gamma) { return !(gamma < gamma_curve_lut[GAMMA_MIN].gamma || gamma > gamma_curve_lut[GAMMA_MAX - 1].gamma); } void print_mtp_offset(struct dimming_info *dim_info) { int i; for (i = 0; i < dim_info->nr_tp; i++) panel_info("%-7s %-4d %-4d %-4d\n", dim_info->tp[i].name, dim_info->tp[i].offset[RED], dim_info->tp[i].offset[GREEN], dim_info->tp[i].offset[BLUE]); } void print_center_gamma(struct dimming_info *dim_info) { int i; panel_info("[CENTER_GAMMA]\n"); for (i = 0; i < dim_info->nr_tp; i++) panel_info("%-7s %-4X %-4X %-4X\n", dim_info->tp[i].name, dim_info->tp[i].center[RED], dim_info->tp[i].center[GREEN], dim_info->tp[i].center[BLUE]); } void print_hbm_gamma_tbl(struct dimming_info *dim_info) { int i; if (unlikely(!dim_info->hbm_gamma_tbl)) { panel_warn("hbm_gamma_tbl not exist\n"); return; } if (unlikely(!dim_info->tp)) { panel_warn("tp not exist\n"); return; } for (i = 0; i < dim_info->nr_tp; i++) panel_info("%-7s %-4d %-4d %-4d\n", dim_info->tp[i].name, dim_info->hbm_gamma_tbl[i][RED], dim_info->hbm_gamma_tbl[i][GREEN], dim_info->hbm_gamma_tbl[i][BLUE]); } void print_tpout_center(struct dimming_info *dim_info) { int i, len, ilum, nr_luminance, nr_tp; char buf[MAX_PRINT_BUF_SIZE]; struct dimming_lut *lut; if (unlikely(!dim_info)) { panel_err("invalid dim_info\n"); return; } lut = dim_info->dim_lut; nr_tp = dim_info->nr_tp; nr_luminance = dim_info->nr_luminance; if (unlikely(!lut)) { panel_err("invalid dim_lut\n"); return; } if ((nr_tp * 15UL) >= ARRAY_SIZE(buf)) { panel_err("exceed linebuf size (%lu)\n", nr_tp * 15UL); return; } for (ilum = 0; ilum < nr_luminance; ilum++) { len = snprintf(buf, MAX_PRINT_BUF_SIZE, "%-3d ", lut[ilum].luminance); #ifdef DEBUG_DIMMING_RESULT_ASCENDING for (i = 1; i < nr_tp; i++) { len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%-3d %-3d %-3d ", lut[ilum].tpout[i].center[RED], lut[ilum].tpout[i].center[GREEN], lut[ilum].tpout[i].center[BLUE]); } len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%-3d %-3d %-3d ", lut[ilum].tpout[TP_VT].center[RED], lut[ilum].tpout[TP_VT].center[GREEN], lut[ilum].tpout[TP_VT].center[BLUE]); #else for (i = nr_tp - 1; i >= 0; i--) { len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%-3d %-3d %-3d ", lut[ilum].tpout[i].center[RED], lut[ilum].tpout[i].center[GREEN], lut[ilum].tpout[i].center[BLUE]); } #endif panel_info("%s\n", buf); } } void print_tpout_center_debug(struct dimming_info *dim_info) { int i, ilum, len, nr_tp, nr_luminance; char buf[MAX_PRINT_BUF_SIZE]; struct dimming_lut *lut; if (unlikely(!dim_info)) { panel_err("invalid dim_info\n"); return; } lut = dim_info->dim_lut; nr_tp = dim_info->nr_tp; nr_luminance = dim_info->nr_luminance; if (unlikely(!lut)) { panel_err("invalid dim_lut\n"); return; } if ((nr_tp * 15UL) >= ARRAY_SIZE(buf)) { panel_err("exceed linebuf size (%lu)\n", nr_tp * 15UL); return; } for (ilum = 0; ilum < nr_luminance; ilum++) { len = snprintf(buf, MAX_PRINT_BUF_SIZE, "%-3d ", lut[ilum].luminance); for (i = nr_tp - 1; i >= 0; i--) { len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%-3d %-3d %-3d ", lut[ilum].tpout[i].center_debug[RED], lut[ilum].tpout[i].center_debug[GREEN], lut[ilum].tpout[i].center_debug[BLUE]); } panel_info("%s\n", buf); } } void print_tp_vout(struct dimming_info *dim_info) { int i, nr_tp; struct tp *tp; if (unlikely(!dim_info)) { panel_err("invalid dim_info\n"); return; } tp = dim_info->tp; nr_tp = dim_info->nr_tp; if (unlikely(!tp)) { panel_err("invalid tp\n"); return; } for (i = 0; i < nr_tp; i++) panel_info("%1lld.%04lld %1lld.%04lld %1lld.%04lld\n", scale_down_round(tp[i].vout[RED], 4), scale_down_rem(tp[i].vout[RED], 4), scale_down_round(tp[i].vout[GREEN], 4), scale_down_rem(tp[i].vout[GREEN], 4), scale_down_round(tp[i].vout[BLUE], 4), scale_down_rem(tp[i].vout[BLUE], 4)); } void print_gray_scale_vout(struct gray_scale *gray_scale_lut) { int i; for (i = 0; i < GRAY_SCALE_MAX; i++) { panel_info("%1lld.%04lld %1lld.%04lld %1lld.%04lld\n", scale_down_round(gray_scale_lut[i].vout[RED], 4), scale_down_rem(gray_scale_lut[i].vout[RED], 4), scale_down_round(gray_scale_lut[i].vout[GREEN], 4), scale_down_rem(gray_scale_lut[i].vout[GREEN], 4), scale_down_round(gray_scale_lut[i].vout[BLUE], 4), scale_down_rem(gray_scale_lut[i].vout[BLUE], 4)); } } static u64 mtp_offset_to_vregout(struct dimming_info *dim_info, int v, enum color c) { s64 num, den, vreg, vsrc, res = 0; struct tp *tp = dim_info->tp; u32 *vt_voltage = dim_info->vt_voltage; u32 *v0_voltage = dim_info->v0_voltage; s64 VREGOUT = dim_info->vregout; s64 VREF = dim_info->vref; if (!tp || v < 0 || v >= NR_TP) { panel_err("invalid tp %d\n", v); return -EINVAL; } den = (s64)tp[v].denominator; if (tp[v].volt_src == VREG_OUT || tp[v].volt_src == V0_OUT) vsrc = VREGOUT; else if (tp[v].volt_src == VT_OUT) vsrc = tp[TP_VT].vout[c]; else { vsrc = tp[TP_VT].vout[c]; panel_warn("unknown tp[%d].volt_src %d\n", v, tp[v].volt_src); } if (v == TP_VT) { s32 offset = tp[v].center[c] + tp[v].offset[c]; if ((offset > 0xF) || (offset < 0x0)) { panel_warn("ivt (%d) out of range(0x0 ~ 0xF) replace to %s\n", offset, offset > 0xF ? "0xF" : "0x0" ); offset = offset > 0xF ? 0xF : 0x0; } vreg = vsrc - VREF; num = vreg * (s64)vt_voltage[offset]; res = vsrc - disp_div64(num, den); } else if (v == TP_V0) { s32 offset = tp[v].center[c] + tp[v].offset[c]; if ((offset > 0xF) || (offset < 0x0)) { panel_warn("ivt (%d) out of range(0x0 ~ 0xF) replace to %s\n", offset, offset > 0xF ? "0xF" : "0x0" ); offset = offset > 0xF ? 0xF : 0x0; } vreg = vsrc - VREF; num = vreg * (s64)v0_voltage[offset]; res = vsrc - disp_div64(num, den); } else if (v == TP_V255) { vreg = vsrc - VREF; num = vreg * ((s64)tp[v].numerator + tp[v].center[c] + tp[v].offset[c]); res = vsrc - disp_div64(num, den); } else { vreg = vsrc - tp[v + 1].vout[c]; num = vreg * ((s64)tp[v].numerator + tp[v].center[c] + tp[v].offset[c]); res = vsrc - disp_div64(num, den); } #ifdef DEBUG_DIMMING panel_info("%s %s vreg %lld, vsrc %lld, num %lld, den %lld, res %lld\n", tp[(int)v].name, color_name[(int)c], vreg, vsrc, num, den, res); #endif if (res < 0) panel_err("res %lld should not be minus value\n", res); return res; } static s32 mtp_vregout_to_offset(struct dimming_info *dim_info, int v, enum color c, int luminance_index) { s64 num, den, res, vsrc; u32 upper; s32(*rgb_offset)[MAX_COLOR]; s64 VREGOUT = dim_info->vregout; s64 VREF = dim_info->vref; struct dimming_lut *dim_lut = dim_info->dim_lut; struct tp *tp = dim_info->tp; struct dimming_tp_output *tpout; if (!tp || v < 0 || v >= NR_TP) { panel_err("invalid tp %d\n", v); return -EINVAL; } if (luminance_index < 0 || luminance_index >= NR_LUMINANCE) { panel_err("out of range luminance index (%d)\n", luminance_index); return 0; } if (tp[v].bits > 0) upper = (1 << tp[v].bits) - 1; else upper = ((v == TP_V255) ? 511: 255); rgb_offset = dim_lut[luminance_index].rgb_color_offset; tpout = dim_lut[luminance_index].tpout; if (tp[v].volt_src == VREG_OUT || tp[v].volt_src == V0_OUT) vsrc = VREGOUT; else if (tp[v].volt_src == VT_OUT) vsrc = tpout[TP_VT].vout[c]; else { vsrc = tpout[TP_VT].vout[c]; panel_warn("unknown tp[%d].volt_src %d\n", v, tp[v].volt_src); } if (v == TP_V255) { num = (vsrc - tpout[v].vout[c]) * (s64)tp[v].denominator; den = vsrc - VREF; } else { num = (vsrc - tpout[v].vout[c]) * (s64)tp[v].denominator; den = vsrc - tpout[v + 1].vout[c]; } #ifdef DIMMING_CALC_PRECISE /* To get precise value, use disp_div64_round() function */ num -= den * (tp[v].numerator); num = disp_round(num, 7) - den * (s64)tp[v].offset[c]; res = disp_div64_round(num, den, 2); panel_dbg("lum %d, %s %s num %lld, den %lld, res %lld\n", dim_lut[luminance_index].luminance, tp[v].name, color_name[c], num, den, res); #else num -= den * ((s64)tp[v].numerator + (s64)tp[v].offset[c]); res = disp_div64(num, den); #endif //if (in_range(v, dim_lut_info->rgb_color_offset_range)) res += rgb_offset[v][c]; #ifdef DEBUG_DIMMING panel_info("luminance %3d %5s %5s num %lld\t den %lld\t rgb_offset %d\t res %lld\n", dim_lut[luminance_index].luminance, tp[v].name, color_name[c], num, den, rgb_offset[v][c], res); #endif if (res < 0) res = 0; if (res > upper) res = upper; return (s32)res; } /* * ########### BASE LUMINANCE GRAY SCALE TABLE FUNCTIONS ########### */ /* * sams as excel macro function min_diff_gray(). */ static int find_gray_scale_gamma_value(struct gray_scale *gray_scale_lut, u64 gamma_value) { int l = 0, m, r = GRAY_SCALE_MAX - 1; s64 delta_l, delta_r; if (unlikely(!gray_scale_lut)) { panel_err("gray_scale_lut not exist\n"); return -EINVAL; } if ((gray_scale_lut[l].gamma_value > gamma_value) || (gray_scale_lut[r].gamma_value < gamma_value)) { panel_err("out of range([l:%d, r:%d], [%lld, %lld]) candela(%lld)\n", l, r, gray_scale_lut[l].gamma_value, gray_scale_lut[r].gamma_value, gamma_value); return -EINVAL; } while (l <= r) { m = l + (r - l) / 2; if (gray_scale_lut[m].gamma_value == gamma_value) return m; if (gray_scale_lut[m].gamma_value < gamma_value) l = m + 1; else r = m - 1; } delta_l = gamma_value - gray_scale_lut[r].gamma_value; delta_r = gray_scale_lut[l].gamma_value - gamma_value; return delta_l <= delta_r ? r : l; } s64 interpolation_round(s64 from, s64 to, int cur_step, int total_step) { s64 num, den, sign = 1LL; if (cur_step == total_step || from == to) return to; if (from > to) sign = -1LL; num = abs(to - from) * (s64)cur_step + (total_step / 2); den = (s64)total_step; num = from + sign * disp_div64(num, den); return num; } s64 disp_interpolation64(s64 from, s64 to, int cur_step, int total_step) { s64 num, den; if (cur_step == total_step || from == to) return to; num = (to - from) * (s64)cur_step; den = (s64)total_step; num = from + disp_div64(num, den); return num; } EXPORT_SYMBOL(disp_interpolation64); int gamma_table_add_offset(s32 (*src)[MAX_COLOR], s32 (*ofs)[MAX_COLOR], s32 (*out)[MAX_COLOR], struct tp *tp, int nr_tp) { int v, c, upper, res; if (unlikely(!tp || nr_tp == 0 || !src|| !ofs || !out)) { panel_err("invalid parameter (tp %d, nr_tp %d, src %d, ofs %d, out %d)\n", !!tp, nr_tp, !!src, !!ofs, !!out); return -EINVAL; } for (v = 0; v < nr_tp; v++) { upper = (1 << tp[v].bits) - 1; for_each_color(c) { res = src[v][c] + ofs[v][c]; if (res < 0) res = 0; if (res > upper) res = upper; out[v][c] = res; #ifdef DEBUG_DIMMING panel_info("src %d, ofs %d, out %d\n", src[v][c], ofs[v][c], out[v][c]); #endif } } return 0; } int gamma_table_interpolation(s32 (*from)[MAX_COLOR], s32 (*to)[MAX_COLOR], s32 (*out)[MAX_COLOR], int nr_tp, int cur_step, int total_step) { int i, c; if (unlikely(!from || !to || !out)) { panel_err("invalid parameter (from %p, to %p, out %p)\n", from, to, out); return -EINVAL; } if (unlikely(nr_tp < 0 || cur_step > total_step)) { panel_err("out of range (nr_tp %d, cur_step %d, total_step %d)\n", nr_tp, cur_step, total_step); return -EINVAL; } for (i = 0; i < nr_tp; i++) { for_each_color(c) { out[i][c] = disp_interpolation64(from[i][c], to[i][c], cur_step, total_step); #ifdef DEBUG_DIMMING panel_info("from %d, to %d, out %d, cur_step %d, total_step %d\n", from[i][c], to[i][c], out[i][c], cur_step, total_step); #endif } } return 0; } int gamma_table_interpolation_round(s32 (*from)[MAX_COLOR], s32 (*to)[MAX_COLOR], s32 (*out)[MAX_COLOR], int nr_tp, int cur_step, int total_step) { int i, c; if (unlikely(!from || !to || !out)) { panel_err("invalid parameter (from %p, to %p, out %p)\n", from, to, out); return -EINVAL; } if (unlikely(nr_tp < 0 || cur_step > total_step)) { panel_err("out of range (nr_tp %d, cur_step %d, total_step %d)\n", nr_tp, cur_step, total_step); return -EINVAL; } for (i = 0; i < nr_tp; i++) { for_each_color(c) { out[i][c] = interpolation_round(from[i][c], to[i][c], cur_step, total_step); #ifdef DEBUG_DIMMING panel_info("from %d, to %d, out %d, cur_step %d, total_step %d\n", from[i][c], to[i][c], out[i][c], cur_step, total_step); #endif } } return 0; } EXPORT_SYMBOL(gamma_table_interpolation_round); static int generate_gray_scale(struct dimming_info *dim_info) { int i, c, iv = 0, iv_upper = 0, iv_lower = 0, ret = 0, cur_step, total_step = 0; s64 vout, v_upper, v_lower; struct gray_scale *gray_scale_lut = dim_info->gray_scale_lut; struct tp *tp = dim_info->tp; int nr_tp = dim_info->nr_tp; for (i = 0, iv = (TP_V0 > 0 ? TP_V0 : TP_VT); iv < nr_tp && i < GRAY_SCALE_MAX; i++) { if (i == tp[iv].level) { iv_upper = tp[((iv == nr_tp - 1) ? iv : iv + 1)].level; iv_lower = tp[iv].level; total_step = iv_upper - iv_lower; iv++; continue; } cur_step = i - iv_lower; for_each_color(c) { v_upper = (s64)gray_scale_lut[iv_upper].vout[c]; v_lower = (s64)gray_scale_lut[iv_lower].vout[c]; vout = disp_interpolation64(v_lower, v_upper, cur_step, total_step); #ifdef DEBUG_DIMMING panel_info("lower %3d, upper %3d, " "cur_step %3d, total_step %3d, vout[%d]\t " "from %2lld.%04lld, vout %2lld.%04lld, to %2lld.%04lld\n", iv_lower, iv_upper, cur_step, total_step, c, scale_down_round(v_lower, 4), scale_down_rem(v_lower, 4), scale_down_round(vout, 4), scale_down_rem(vout, 4), scale_down_round(v_upper, 4), scale_down_rem(v_upper, 4)); #endif if (vout < 0) { panel_warn("from %2lld.%4lld, to %2lld.%4lld (idx %-3d %-3d), cur_step %-3d, total_step %-3d\n", scale_down_round(v_lower, 4), scale_down_rem(v_lower, 4), scale_down_round(v_upper, 4), scale_down_rem(v_upper, 4), iv_lower, iv_upper, cur_step, total_step); ret = -EINVAL; } gray_scale_lut[i].vout[c] = vout; } } for (i = 0; i < GRAY_SCALE_MAX; i++) { /* base luminance gamma curve value */ gray_scale_lut[i].gamma_value = TARGET_LUMINANCE * (u64)gamma_curve_value(TARGET_G_CURVE_DEGREE, i); #ifdef DEBUG_DIMMING panel_info("%-4d gamma %3lld.%02lld\n", i, scale_down_round(gray_scale_lut[i].gamma_value, 2), scale_down_rem(gray_scale_lut[i].gamma_value, 2)); #endif } return ret; } /* * ########### GAMMA CORRECTION TABLE FUNCTIONS ########### */ int find_luminance(struct dimming_info *dim_info, u32 luminance) { int index, nr_luminance; struct dimming_lut *dim_lut; if (unlikely(!dim_info)) { panel_err("invalid dim_info\n"); return -1; } dim_lut = dim_info->dim_lut; nr_luminance = dim_info->nr_luminance; if (unlikely(!dim_lut)) { panel_err("invalid dim_lut\n"); return -1; } for (index = 0; index < nr_luminance; index++) if (dim_lut[index].luminance == luminance) return index; return -1; } int get_luminance(struct dimming_info *dim_info, u32 index) { int nr_luminance; struct dimming_lut *dim_lut; if (unlikely(!dim_info)) { panel_err("invalid dim_info\n"); return 0; } dim_lut = dim_info->dim_lut; nr_luminance = dim_info->nr_luminance; if (unlikely(!dim_lut)) { panel_err("invalid dim_lut\n"); return 0; } if (index >= nr_luminance) { panel_warn("exceed max %d, index %d\n", nr_luminance - 1, index); return dim_lut[nr_luminance - 1].luminance; } return dim_lut[index].luminance; } static void copy_tpout_center(struct dimming_info *dim_info, u32 luminance, u8 *output, void (*copy)(u8 *output, u32 value, u32 index, u32 color)) { struct dimming_lut *dim_lut = dim_info->dim_lut; struct tp *tp = dim_info->tp; int ilum = find_luminance(dim_info, luminance); int i, c, value; if (unlikely(!tp || !dim_lut)) { panel_err("invalid tp or dim_lut\n"); return; } if (unlikely(ilum < 0)) { panel_err("luminance(%d) not found\n", luminance); return; } for (i = TP_V255; i >= TP_VT; i--) { for_each_color(c) { value = dim_lut[ilum].tpout[i].center[c]; copy(output, value, i, c); } } } static void copy_tpout_hbm_center(struct dimming_info *dim_info, u32 luminance, u8 *output, void (*copy)(u8 *output, u32 value, u32 index, u32 color)) { struct dimming_lut *dim_lut = dim_info->dim_lut; struct tp *tp = dim_info->tp; int i, c, value, ilum; int nr_tp = dim_info->nr_tp; s32 (*target_gamma_tbl)[MAX_COLOR]; s32 (*output_gamma_tbl)[MAX_COLOR]; if (unlikely(!tp || !dim_lut)) { panel_err("invalid tp or dim_lut\n"); return; } target_gamma_tbl = (s32 (*)[MAX_COLOR])kzalloc(sizeof(s32) * nr_tp * MAX_COLOR, GFP_KERNEL); if (unlikely(!target_gamma_tbl)) { panel_err("failed to allocate target_gamma_tbl\n"); goto err; } output_gamma_tbl = (s32 (*)[MAX_COLOR])kzalloc(sizeof(s32) * nr_tp * MAX_COLOR, GFP_KERNEL); if (unlikely(!output_gamma_tbl)) { panel_err("failed to allocate output_gamma_tbl\n"); goto err_alloc; } /* make target gamma table */ ilum = find_luminance(dim_info, dim_info->target_luminance); if (unlikely(ilum < 0)) { panel_err("target_luminance(%d) not found\n", dim_info->target_luminance); goto err_find; } for (i = 0; i < nr_tp; i++) for_each_color(c) target_gamma_tbl[i][c] = dim_lut[ilum].tpout[i].center[c]; gamma_table_interpolation(target_gamma_tbl, dim_info->hbm_gamma_tbl, output_gamma_tbl, dim_info->nr_tp, luminance - dim_info->target_luminance, dim_info->hbm_luminance - dim_info->target_luminance); for (i = TP_V255; i >= TP_VT; i--) { for_each_color(c) { value = output_gamma_tbl[i][c]; copy(output, value, i, c); } } err_find: kfree(output_gamma_tbl); err_alloc: kfree(target_gamma_tbl); err: return; } void get_dimming_gamma(struct dimming_info *dim_info, u32 luminance, u8 *output, void (*copy)(u8 *output, u32 value, u32 index, u32 color)) { if (unlikely(!dim_info || !output)) { panel_err("invalid dim_info or output\n"); return; } if (luminance <= dim_info->target_luminance) copy_tpout_center(dim_info, luminance, output, copy); else if (dim_info->hbm_luminance && luminance <= dim_info->hbm_luminance) copy_tpout_hbm_center(dim_info, luminance, output, copy); else panel_warn("out of range (hbm_luminance %d, luminance %d)\n", dim_info->hbm_luminance, luminance); return; } EXPORT_SYMBOL(get_dimming_gamma); void print_dimming_tp_output(struct dimming_lut *dim_lut, struct tp *tp, int size) { int i, v; struct dimming_tp_output *tpout; u32 luminance; for (i = 0; i < size; i++) { tpout = dim_lut[i].tpout; luminance = dim_lut[i].luminance; for (v = 0; v < NR_TP; v++) { panel_info("luminance %3d %5s " "L %3lld.%05lld\t M_GRAY %3u " "R %3lld.%05lld\t G %3lld.%05lld\t B %3lld.%05lld\t " "%3X %3X %3X\n", luminance, tp[v].name, scale_down_round(tpout[v].L, 5), scale_down_rem(tpout[v].L, 5), tpout[v].M_GRAY, scale_down_round(tpout[v].vout[RED], 5), scale_down_rem(tpout[v].vout[RED], 5), scale_down_round(tpout[v].vout[GREEN], 5), scale_down_rem(tpout[v].vout[GREEN], 5), scale_down_round(tpout[v].vout[BLUE], 5), scale_down_rem(tpout[v].vout[BLUE], 5), tpout[v].center[RED], tpout[v].center[GREEN], tpout[v].center[BLUE]); } } panel_info("\n"); } static int generate_gamma_table(struct dimming_info *dim_info) { int i, v, c, ilum; enum gamma_degree g_curve_degree; struct dimming_tp_output *tpout; struct dimming_lut *dim_lut = dim_info->dim_lut; struct gray_scale *gray_scale_lut = dim_info->gray_scale_lut; struct tp *tp = dim_info->tp; int NR_TP = dim_info->nr_tp; int TARGET_LUMINANCE = dim_info->target_luminance; enum gamma_degree TARGET_G_CURVE_DEGREE = dim_info->target_g_curve_degree; u32 luminance; if (unlikely(!calc_gray_order)) { panel_err("calc_gray_order not exist!!\n"); return -EINVAL; } if (unlikely(!calc_vout_order)) { panel_err("calc_vout_order not exist!!\n"); return -EINVAL; } for (ilum = 0; ilum < NR_LUMINANCE; ilum++) { luminance = dim_lut[ilum].luminance; tpout = dim_lut[ilum].tpout; for (i = 0; i < NR_TP; i++) { v = calc_gray_order[i]; g_curve_degree = dim_lut[ilum].g_curve_degree; /* TODO : need to optimize */ if (v == TP_V255) tpout[v].L = (dim_lut[ilum].base_luminance) ? ((u64)dim_lut[ilum].base_luminance << BIT_SHIFT) : (u64)TARGET_LUMINANCE * (u64)gamma_curve_value(TARGET_G_CURVE_DEGREE, dim_lut[ilum].base_gray); else if (v == TP_VT) tpout[v].L = (luminance < 251) ? 0 : ((tpout[TP_V255].L * (u64)gamma_curve_value(g_curve_degree, tp[v].level)) >> BIT_SHIFT); else tpout[v].L = (tpout[TP_V255].L * (u64)gamma_curve_value(g_curve_degree, tp[v].level)) >> BIT_SHIFT; /* * In two special case, M_GRAY should be same with tp value * 1. tp is VT (v == TP_VT) * 2. tp's value is 1 (tp[v].level == 1) */ tpout[v].M_GRAY = (tp[v].level <= 1) ? tp[v].level : find_gray_scale_gamma_value(gray_scale_lut, tpout[v].L) + dim_lut[ilum].gray_scale_offset[v]; if (tpout[v].M_GRAY > 255) tpout[v].M_GRAY = 255; for_each_color(c) { dim_lut[ilum].tpout[v].vout[c] = (v == TP_VT) ? tp[v].vout[c] : gray_scale_lut[tpout[v].M_GRAY].vout[c]; } } for (i = 0; i < NR_TP; i++) { v = calc_vout_order[i]; for_each_color(c) { dim_lut[ilum].tpout[v].center[c] = (v == TP_VT || v == TP_V0) ? tp[v].center[c] : mtp_vregout_to_offset(dim_info, v, (enum color)c, ilum); } } #ifdef DEBUG_DIMMING print_dimming_tp_output(&dim_lut[ilum], tp, 1); #endif } return 0; } int alloc_dimming_info(struct dimming_info *dim_info, int nr_tp, int nr_luminance) { int i; dim_info->nr_tp = nr_tp; dim_info->nr_luminance = nr_luminance; dim_info->tp = kzalloc(sizeof(struct tp) * nr_tp, GFP_KERNEL); /* allocation of struct dimming_lut lut and members */ dim_info->dim_lut = (struct dimming_lut *)kzalloc(sizeof(struct dimming_lut) * nr_luminance, GFP_KERNEL); for (i = 0; i < nr_luminance; i++) { dim_info->dim_lut[i].tpout = kzalloc(sizeof(struct dimming_tp_output) * nr_tp, GFP_KERNEL); dim_info->dim_lut[i].gray_scale_offset = (s32 *)kzalloc(sizeof(s32) * nr_tp, GFP_KERNEL); dim_info->dim_lut[i].rgb_color_offset = (s32 (*)[MAX_COLOR])kzalloc(sizeof(s32) * nr_tp * MAX_COLOR, GFP_KERNEL); } return 0; } int free_dimming_info(struct dimming_info *dim_info) { int i; for (i = 0; i < dim_info->nr_luminance; i++) { kfree(dim_info->dim_lut[i].tpout); kfree(dim_info->dim_lut[i].gray_scale_offset); kfree(dim_info->dim_lut[i].rgb_color_offset); } kfree(dim_info->dim_lut); kfree(dim_info->tp); dim_info->nr_luminance = 0; dim_info->nr_tp = 0; return 0; } int find_tp_index(struct tp *tp, int nr_tp, char *name) { int i; for (i = 0; i < nr_tp; i++) if (!strcmp(tp[i].name, name)) return i; return -1; } void prepare_dim_info(struct dimming_info *dim_info) { #ifdef DEBUG_DIMMING int i, len; char buf[MAX_PRINT_BUF_SIZE]; #endif struct tp *tp = dim_info->tp; NR_TP = dim_info->nr_tp; NR_LUMINANCE = dim_info->nr_luminance; TARGET_LUMINANCE = dim_info->target_luminance; TARGET_G_CURVE_DEGREE = dim_info->target_g_curve_degree; TP_VT = find_tp_index(tp, dim_info->nr_tp, "VT"); TP_V0 = find_tp_index(tp, dim_info->nr_tp, "V0"); TP_V255 = dim_info->nr_tp - 1; #ifdef DEBUG_DIMMING panel_info("NR_TP %d\n", NR_TP); #endif if (!calc_gray_order) { calc_gray_order = generate_order(NR_TP - 1, 0, NR_TP - 2); if (unlikely(!calc_gray_order)) { panel_err("calc_gray_order not exist!!\n"); return; } #ifdef DEBUG_DIMMING len = snprintf(buf, MAX_PRINT_BUF_SIZE, "[calc_gray_order]\n"); for (i = 0; i < NR_TP; i++) len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "[%d]%s ", i, ((calc_gray_order[i] >= 0 && calc_gray_order[i] < NR_TP) ? tp[calc_gray_order[i]].name : "TP_ERR")); panel_info("%s\n", buf); #endif } if (!calc_vout_order) { calc_vout_order = generate_order(0, NR_TP - 1, 1); if (unlikely(!calc_vout_order)) { panel_err("calc_vout_order not exist!!\n"); return; } #ifdef DEBUG_DIMMING len = snprintf(buf, MAX_PRINT_BUF_SIZE, "[calc_vout_order]\n"); for (i = 0; i < NR_TP; i++) len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "[%d]%s ", i, ((calc_vout_order[i] >= 0 && calc_vout_order[i] < NR_TP) ? tp[calc_vout_order[i]].name : "TP_ERR")); panel_info("%s\n", buf); #endif } } void print_tp_lut(struct dimming_info *dim_info) { int i, len, col, ncol, *fields; char buf[MAX_PRINT_BUF_SIZE]; ncol = dim_info->tp_lut_info.ncol; fields = dim_info->tp_lut_info.fields; for (i = 0; i < dim_info->nr_tp; i++) { for (len = 0, col = 0; col < ncol; col++) { switch (fields[col]) { case TP_LUT_LEVEL: len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%-3d ", dim_info->tp[i].level); break; case TP_LUT_VOLT_SRC: len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%-10s ", volt_src_name[dim_info->tp[i].volt_src]); break; case TP_LUT_GAMMA_CENTER: len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%-3X %-3X %-3X ", dim_info->tp[i].center[RED], dim_info->tp[i].center[GREEN], dim_info->tp[i].center[BLUE]); break; case TP_LUT_GAMMA_CALC: len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%-3d %-3d", dim_info->tp[i].numerator, dim_info->tp[i].denominator); break; default: break; } } panel_info("%s\n", buf); } } void print_dim_lut(struct dimming_info *dim_info) { int ilum, i, c, len, col, ncol, *fields; char buf[MAX_PRINT_BUF_SIZE]; struct dimming_lut_info *dim_lut_info; dim_lut_info = &dim_info->dim_lut_info; ncol = dim_info->dim_lut_info.ncol; fields = dim_info->dim_lut_info.fields; for (ilum = 0; ilum < dim_info->nr_luminance; ilum++) { struct dimming_lut *arr = &dim_info->dim_lut[ilum]; for (len = 0, col = 0; col < ncol; col++) { switch (fields[col]) { case DIM_LUT_LUMINANCE: len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%-3d ", arr->luminance); break; case DIM_LUT_AOR: len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%04X ", arr->aor); break; case DIM_LUT_BASE_LUMINANCE: len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%3d ", arr->base_luminance); break; case DIM_LUT_GAMMA: len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%3d ", gamma_curve_lut[arr->g_curve_degree].gamma); break; case DIM_LUT_GRAY_SCALE_OFFSET: for_each_range(i, dim_lut_info->gray_scale_offset_range) len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%3d ", arr->gray_scale_offset[i]); len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "\t "); break; case DIM_LUT_COLOR_SHIFT_OFFSET: for_each_range(i, dim_lut_info->rgb_color_offset_range) for_each_color(c) len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%3d ", arr->rgb_color_offset[i][c]); break; default: break; } } panel_info("%s\n", buf); } } void print_dimming_info(struct dimming_info *dim_info, int tag) { static char buf[MAX_PRINT_BUF_SIZE]; size_t i; int len, col, ncol, *fields; struct dimming_lut_info *dim_lut_info = &dim_info->dim_lut_info; panel_info("%s\n", tag_name[tag]); switch (tag) { case TAG_DIM_GLOBAL_INFO_START: panel_info("%-15s %u\n", global_dim_info_name[GLOBAL_DIM_INFO_NR_TP], dim_info->nr_tp); panel_info("%-15s %u\n", global_dim_info_name[GLOBAL_DIM_INFO_NR_LUMINANCE], dim_info->nr_luminance); panel_info("%-15s %lld %d\n", global_dim_info_name[GLOBAL_DIM_INFO_VREGOUT], dim_info->vregout, DIMMING_BITSHIFT); panel_info("%-15s %lld %d\n", global_dim_info_name[GLOBAL_DIM_INFO_VREF], dim_info->vref, DIMMING_BITSHIFT); panel_info("%-15s %d\n", global_dim_info_name[GLOBAL_DIM_INFO_GAMMA], gamma_curve_lut[(int)dim_info->target_g_curve_degree].gamma); len = snprintf(buf, MAX_PRINT_BUF_SIZE, "%-15s ", global_dim_info_name[GLOBAL_DIM_INFO_VT_VOLTAGE]); for (i = 0; i < ARRAY_SIZE(dim_info->vt_voltage); i++) len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%d ", dim_info->vt_voltage[i]); panel_info("%s\n", buf); break; case TAG_TP_LUT_INFO_START: ncol = dim_info->tp_lut_info.ncol; fields = dim_info->tp_lut_info.fields; for (len = 0, col = 0; col < ncol; col++) len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%s ", tp_lut_field_name[fields[col]]); panel_info("%s\n", buf); break; case TAG_TP_LUT_START: print_tp_lut(dim_info); break; case TAG_DIM_LUT_INFO_START: ncol = dim_lut_info->ncol; fields = dim_lut_info->fields; for (len = 0, col = 0; col < ncol; col++) { len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%s ", dim_lut_field_name[fields[col]]); if (fields[col] == DIM_LUT_GRAY_SCALE_OFFSET) { len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%s %s ", dim_info->tp[dim_lut_info->gray_scale_offset_range.from].name, dim_info->tp[dim_lut_info->gray_scale_offset_range.to - dim_lut_info->gray_scale_offset_range.step].name); } else if (fields[col] == DIM_LUT_COLOR_SHIFT_OFFSET) { len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "%s %s ", dim_info->tp[dim_lut_info->rgb_color_offset_range.from].name, dim_info->tp[dim_lut_info->rgb_color_offset_range.to - dim_lut_info->rgb_color_offset_range.step].name); } } panel_info("%s\n", buf); break; case TAG_DIM_LUT_START: print_dim_lut(dim_info); break; case TAG_MTP_OFFSET_START: print_mtp_offset(dim_info); break; case TAG_GAMMA_CENTER_START: print_tpout_center(dim_info); break; case TAG_TP_VOLT_START: print_tp_vout(dim_info); break; case TAG_GRAY_SCALE_VOLT_START: print_gray_scale_vout(dim_info->gray_scale_lut); break; } panel_info("%s\n\n", tag_name[tag + 1]); } void print_input_data(struct dimming_info *dim_info) { print_dimming_info(dim_info, TAG_DIM_GLOBAL_INFO_START); print_dimming_info(dim_info, TAG_TP_LUT_INFO_START); print_dimming_info(dim_info, TAG_TP_LUT_START); print_dimming_info(dim_info, TAG_DIM_LUT_INFO_START); print_dimming_info(dim_info, TAG_DIM_LUT_START); #if 0 print_dimming_info(dim_info, TAG_MTP_OFFSET_START); #endif } void print_tbl(struct dimming_info *dim_info, s32 (*tbl)[MAX_COLOR]) { int i, len; char buf[MAX_PRINT_BUF_SIZE]; for (i = 0, len = 0; i < dim_info->nr_tp; i++) len += snprintf(buf + len, max(MAX_PRINT_BUF_SIZE - len, 0), "0x%03X 0x%03X 0x%03X\n", tbl[i][RED], tbl[i][GREEN], tbl[i][RED]); panel_info("%s\n", buf); } int init_dimming_info(struct dimming_info *dim_info, struct dimming_init_info *src) { int ilum; memcpy(dim_info->vt_voltage, src->vt_voltage, sizeof(src->vt_voltage)); memcpy(dim_info->v0_voltage, src->v0_voltage, sizeof(src->v0_voltage)); dim_info->vregout = src->vregout; dim_info->vref = src->vref; dim_info->nr_tp = src->nr_tp; dim_info->tp = src->tp; dim_info->nr_luminance = src->nr_luminance; dim_info->dim_lut = src->dim_lut; /* allocation of struct dimming_lut lut and members */ for (ilum = 0; ilum < dim_info->nr_luminance; ilum++) { dim_info->dim_lut[ilum].tpout = (struct dimming_tp_output *) kzalloc(sizeof(struct dimming_tp_output) * dim_info->nr_tp, GFP_KERNEL); } dim_info->target_luminance = src->target_luminance; dim_info->target_g_curve_degree = gamma_value_to_degree(src->target_gamma); if (unlikely(dim_info->target_g_curve_degree == GAMMA_NONE)) { panel_err("invalid target gamma degree %d\n", dim_info->target_g_curve_degree); return -EINVAL; } return 0; } EXPORT_SYMBOL(init_dimming_info); int init_dimming_mtp(struct dimming_info *dim_info, s32 (*mtp)[MAX_COLOR]) { int i, c; print_center_gamma(dim_info); for (i = 0; i < dim_info->nr_tp; i++) for_each_color(c) dim_info->tp[i].offset[c] = mtp[i][c]; panel_info("init mtp offset\n"); print_mtp_offset(dim_info); return 0; } EXPORT_SYMBOL(init_dimming_mtp); int init_dimming_hbm_info(struct dimming_info *dim_info, s32 (*hbm_gamma_tbl)[MAX_COLOR], u32 hbm_luminance) { int i, c, nr_tp; if (unlikely(!dim_info || !hbm_gamma_tbl)) { panel_err("invalid parameter\n"); return -EINVAL; } if (unlikely(hbm_luminance < dim_info->target_luminance)) { panel_err("out of range (hbm_luminance %d < target_luminance %d)\n", hbm_luminance, dim_info->target_luminance); return -EINVAL; } nr_tp = dim_info->nr_tp; if (!dim_info->hbm_gamma_tbl) dim_info->hbm_gamma_tbl = (s32 (*)[MAX_COLOR])kzalloc(sizeof(s32) * nr_tp * MAX_COLOR, GFP_KERNEL); for (i = 0; i < dim_info->nr_tp; i++) for_each_color(c) dim_info->hbm_gamma_tbl[i][c] = hbm_gamma_tbl[i][c]; dim_info->hbm_luminance = hbm_luminance; #ifdef DEBUG_DIMMING panel_info("init hbm gamma table (hbm_luminance %d)\n", hbm_luminance); print_hbm_gamma_tbl(dim_info); #endif return 0; } int process_dimming(struct dimming_info *dim_info) { int i, c, v; struct tp *tp; s64 VREGOUT; s64 VREF; #ifdef DEBUG_EXCUTION_TIME mytime_t ts, te; gettime(ts); #endif if (unlikely(!dim_info)) { panel_err("invalid dim_info\n"); return -EINVAL; } VREGOUT = dim_info->vregout; VREF = dim_info->vref; prepare_dim_info(dim_info); #ifdef DEBUG_DIMMING print_dimming_info(dim_info, TAG_MTP_OFFSET_START); #endif tp = dim_info->tp; if (unlikely(!tp)) { panel_err("tuning point not prepared\n"); return -EINVAL; } /* * Generate RGB voltage output table of TP using MTP(TP_VT ~ TP_V255). */ for (i = 0; i < dim_info->nr_tp; i++) { v = calc_vout_order[i]; for_each_color(c) { tp[v].vout[c] = mtp_offset_to_vregout(dim_info, v, (enum color)c); if (tp[v].vout[c] < 0) { panel_warn("invalid vout[%s][%s] %lld\n", tp[v].name, color_name[c], tp[v].vout[c]); } dim_info->gray_scale_lut[tp[v].level].vout[c] = (v == TP_VT) ? (u64)VREGOUT : (u64)tp[v].vout[c]; } } #ifdef DEBUG_DIMMING print_dimming_info(dim_info, TAG_TP_VOLT_START); #endif /* * Fill in the all RGB voltate output of gray scale by interpolation. */ generate_gray_scale(dim_info); #ifdef DEBUG_DIMMING print_dimming_info(dim_info, TAG_GRAY_SCALE_VOLT_START); #endif /* * Generate mtp offset of given luminances. */ generate_gamma_table(dim_info); #if 0 print_dimming_tp_output(dim_lut, dim_info->tp, dim_info->nr_luminance); print_dimming_info(dim_info, TAG_MTP_OFFSET_START); #endif #ifdef DEBUG_DIMMING_RESULT print_dimming_info(dim_info, TAG_GAMMA_CENTER_START); #endif #ifdef DEBUG_EXCUTION_TIME gettime(te); panel_info("elapsed time %u us\n", difftime(ts, te)); #endif /* TODO : free allocated heap */ return 0; } EXPORT_SYMBOL(process_dimming);