// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) Samsung Electronics Co., Ltd. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include "panel_kunit.h" #include "panel_drv.h" #include "mdnie.h" #include "panel_debug.h" #ifdef CONFIG_EXYNOS_DECON_LCD_COPR #include "copr.h" #endif #ifdef CONFIG_DISPLAY_USE_INFO #include "dpui.h" #endif #ifdef MDNIE_SELF_TEST int g_coord_x = MIN_WCRD_X; int g_coord_y = MIN_WCRD_Y; #endif static const char * const mdnie_maptbl_name[] = { [MDNIE_UI_MAPTBL] = "ui", [MDNIE_VIDEO_MAPTBL] = "video", [MDNIE_CAMERA_MAPTBL] = "camera", [MDNIE_GALLERY_MAPTBL] = "gallery", [MDNIE_VT_MAPTBL] = "vt", [MDNIE_BROWSER_MAPTBL] = "browser", [MDNIE_EBOOK_MAPTBL] = "ebook", [MDNIE_EMAIL_MAPTBL] = "email", [MDNIE_GAME_LOW_MAPTBL] = "game_low", [MDNIE_GAME_MID_MAPTBL] = "game_mid", [MDNIE_GAME_HIGH_MAPTBL] = "game_high", [MDNIE_VIDEO_ENHANCER_MAPTBL] = "video_enhancer", [MDNIE_VIDEO_ENHANCER_THIRD_MAPTBL] = "video_enhancer_third", [MDNIE_HMD_8_MAPTBL] = "hmd_8", [MDNIE_HMD_16_MAPTBL] = "hmd_16", #if defined(CONFIG_TDMB) [MDNIE_DMB_MAPTBL] = "dmb", #endif /* ACCESSIBILITY */ [MDNIE_NEGATIVE_MAPTBL] = "negative", [MDNIE_COLOR_BLIND_MAPTBL] = "color_blind", [MDNIE_SCREEN_CURTAIN_MAPTBL] = "screen_curtain", [MDNIE_GRAYSCALE_MAPTBL] = "grayscale", [MDNIE_GRAYSCALE_NEGATIVE_MAPTBL] = "grayscale_negative", [MDNIE_COLOR_BLIND_HBM_MAPTBL] = "color_blind_hbm", /* BYPASS */ [MDNIE_BYPASS_MAPTBL] = "bypass", /* HBM */ [MDNIE_HBM_MAPTBL] = "hbm", /* HMD */ [MDNIE_HMD_MAPTBL] = "hmd", /* HDR */ [MDNIE_HDR_MAPTBL] = "hdr", /* NIGHT */ [MDNIE_NIGHT_MAPTBL] = "night", /* LIGHT_NOTIFICATION */ [MDNIE_LIGHT_NOTIFICATION_MAPTBL] = "light_notification", /* COLOR_LENS */ [MDNIE_COLOR_LENS_MAPTBL] = "color_lens", }; static const char * const scr_white_mode_name[] = { [SCR_WHITE_MODE_NONE] = "none", [SCR_WHITE_MODE_COLOR_COORDINATE] = "coordinate", [SCR_WHITE_MODE_ADJUST_LDU] = "adjust-ldu", [SCR_WHITE_MODE_SENSOR_RGB] = "sensor-rgb", }; static const char * const mdnie_mode_name[] = { [MDNIE_OFF_MODE] = "off", [MDNIE_BYPASS_MODE] = "bypass", [MDNIE_LIGHT_NOTIFICATION_MODE] = "light_notification", [MDNIE_ACCESSIBILITY_MODE] = "accessibility", [MDNIE_COLOR_LENS_MODE] = "color_lens", [MDNIE_HDR_MODE] = "hdr", [MDNIE_HMD_MODE] = "hmd", [MDNIE_NIGHT_MODE] = "night", [MDNIE_HBM_MODE] = "hbm", [MDNIE_DMB_MODE] = "dmb", [MDNIE_SCENARIO_MODE] = "scenario", }; static const char * const scenario_mode_name[] = { [DYNAMIC] = "dynamic", [STANDARD] = "standard", [NATURAL] = "natural", [MOVIE] = "movie", [AUTO] = "auto", }; static const char * const scenario_name[] = { [UI_MODE] = "ui", [VIDEO_NORMAL_MODE] = "video_normal", [CAMERA_MODE] = "camera", [NAVI_MODE] = "navi", [GALLERY_MODE] = "gallery", [VT_MODE] = "vt", [BROWSER_MODE] = "browser", [EBOOK_MODE] = "ebook", [EMAIL_MODE] = "email", [GAME_LOW_MODE] = "game_low", [GAME_MID_MODE] = "game_mid", [GAME_HIGH_MODE] = "game_high", [VIDEO_ENHANCER] = "video_enhancer", [VIDEO_ENHANCER_THIRD] = "video_enhancer_third", [HMD_8_MODE] = "hmd_8", [HMD_16_MODE] = "hmd_16", [DMB_NORMAL_MODE] = "dmb_normal", }; static const char * const accessibility_name[] = { [ACCESSIBILITY_OFF] = "off", [NEGATIVE] = "negative", [COLOR_BLIND] = "color_blind", [SCREEN_CURTAIN] = "screen_curtain", [GRAYSCALE] = "grayscale", [GRAYSCALE_NEGATIVE] = "grayscale_negative", [COLOR_BLIND_HBM] = "color_blind_hbm", }; int mdnie_current_state(struct mdnie_info *mdnie) { struct panel_device *panel = container_of(mdnie, struct panel_device, mdnie); int mdnie_mode; if (IS_BYPASS_MODE(mdnie)) mdnie_mode = MDNIE_BYPASS_MODE; else if (IS_LIGHT_NOTIFICATION_MODE(mdnie)) mdnie_mode = MDNIE_LIGHT_NOTIFICATION_MODE; else if (IS_ACCESSIBILITY_MODE(mdnie)) mdnie_mode = MDNIE_ACCESSIBILITY_MODE; else if (IS_COLOR_LENS_MODE(mdnie)) mdnie_mode = MDNIE_COLOR_LENS_MODE; else if (IS_HMD_MODE(mdnie)) mdnie_mode = MDNIE_HMD_MODE; else if (IS_NIGHT_MODE(mdnie)) mdnie_mode = MDNIE_NIGHT_MODE; else if (IS_HBM_MODE(mdnie)) mdnie_mode = MDNIE_HBM_MODE; else if (IS_HDR_MODE(mdnie)) mdnie_mode = MDNIE_HDR_MODE; #if defined(CONFIG_TDMB) else if (IS_DMB_MODE(mdnie)) mdnie_mode = MDNIE_DMB_MODE; #endif else if (IS_SCENARIO_MODE(mdnie)) mdnie_mode = MDNIE_SCENARIO_MODE; else mdnie_mode = MDNIE_OFF_MODE; if (panel->state.cur_state == PANEL_STATE_ALPM && ((mdnie_mode == MDNIE_ACCESSIBILITY_MODE && (mdnie->props.accessibility == NEGATIVE || mdnie->props.accessibility == GRAYSCALE_NEGATIVE)) || (mdnie_mode == MDNIE_SCENARIO_MODE && !IS_LDU_MODE(mdnie)) || mdnie_mode == MDNIE_COLOR_LENS_MODE || mdnie_mode == MDNIE_DMB_MODE || mdnie_mode == MDNIE_HDR_MODE || mdnie_mode == MDNIE_LIGHT_NOTIFICATION_MODE || mdnie_mode == MDNIE_HMD_MODE)) { panel_dbg("block mdnie (%s->%s) in doze mode\n", mdnie_mode_name[mdnie_mode], mdnie_mode_name[MDNIE_BYPASS_MODE]); mdnie_mode = MDNIE_BYPASS_MODE; } return mdnie_mode; } __mockable int mdnie_get_maptbl_index(struct mdnie_info *mdnie) { int index; int mdnie_mode = mdnie_current_state(mdnie); switch (mdnie_mode) { case MDNIE_BYPASS_MODE: index = MDNIE_BYPASS_MAPTBL; break; case MDNIE_LIGHT_NOTIFICATION_MODE: index = MDNIE_LIGHT_NOTIFICATION_MAPTBL; break; case MDNIE_ACCESSIBILITY_MODE: index = MAPTBL_IDX_ACCESSIBILITY(mdnie->props.accessibility); break; case MDNIE_COLOR_LENS_MODE: index = MDNIE_COLOR_LENS_MAPTBL; break; case MDNIE_HDR_MODE: index = MDNIE_HDR_MAPTBL; break; case MDNIE_HMD_MODE: index = MDNIE_HMD_MAPTBL; break; case MDNIE_NIGHT_MODE: index = MDNIE_NIGHT_MAPTBL; break; case MDNIE_HBM_MODE: index = MDNIE_HBM_MAPTBL; break; #if defined(CONFIG_TDMB) case MDNIE_DMB_MODE: index = MDNIE_DMB_MAPTBL; break; #endif case MDNIE_SCENARIO_MODE: index = MAPTBL_IDX_SCENARIO(mdnie->props.scenario); break; default: index = -EINVAL; panel_err("unknown mdnie\n"); break; } if (index >= 0 && index < MAX_MDNIE_MAPTBL) { panel_dbg("mdnie %s(%d), maptbl %s(%d) found\n", mdnie_mode_name[mdnie_mode], mdnie_mode, mdnie_maptbl_name[index], index); } else { panel_err("mdnie %s(%d), maptbl not found!! (%d)\n", mdnie_mode_name[mdnie_mode], mdnie_mode, index); index = MDNIE_UI_MAPTBL; } return index; } EXPORT_SYMBOL(mdnie_get_maptbl_index); struct maptbl *mdnie_find_maptbl(struct mdnie_info *mdnie) { int index = mdnie_get_maptbl_index(mdnie); if (unlikely(index < 0 || index >= MAX_MDNIE_MAPTBL)) { panel_err("failed to find maptbl %d\n", index); return NULL; } return &mdnie->maptbl[index]; } EXPORT_SYMBOL(mdnie_find_maptbl); struct maptbl *mdnie_find_etc_maptbl(struct mdnie_info *mdnie, int index) { if (unlikely(index < 0 || index >= mdnie->nr_etc_maptbl)) { panel_err("failed to find maptbl %d\n", index); return NULL; } return &mdnie->etc_maptbl[index]; } EXPORT_SYMBOL(mdnie_find_etc_maptbl); struct maptbl *mdnie_find_scr_white_maptbl(struct mdnie_info *mdnie, int index) { if (unlikely(index < 0 || index >= mdnie->nr_scr_white_maptbl)) { panel_err("failed to find maptbl %d\n", index); return NULL; } return &mdnie->scr_white_maptbl[index]; } __visible_for_testing int mdnie_get_coordinate(struct mdnie_info *mdnie, int *x, int *y) { struct panel_device *panel = container_of(mdnie, struct panel_device, mdnie); struct panel_info *panel_data = &panel->panel_data; u8 coordinate[PANEL_COORD_LEN] = { 0, }; int ret; if (!mdnie || !x || !y) { panel_err("invalid parameter\n"); return -EINVAL; } ret = resource_copy_by_name(panel_data, coordinate, "coordinate"); if (ret < 0) { panel_err("failed to copy 'coordinate' resource\n"); return -EINVAL; } *x = (coordinate[0] << 8) | coordinate[1]; *y = (coordinate[2] << 8) | coordinate[3]; #ifdef MDNIE_SELF_TEST *x = g_coord_x; *y = g_coord_y; #endif if (*x < MIN_WCRD_X || *x > MAX_WCRD_X || *y < MIN_WCRD_Y || *y > MAX_WCRD_Y) panel_warn("need to check coord_x:%d coord_y:%d)\n", *x, *y); return 0; } static bool mdnie_coordinate_changed(struct mdnie_info *mdnie) { int x, y; mdnie_get_coordinate(mdnie, &x, &y); return (mdnie->props.wcrd_x != x || mdnie->props.wcrd_y != y); } static int mdnie_coordinate_area(struct mdnie_info *mdnie, int x, int y) { s64 result[MAX_CAL_LINE]; int i, area; for (i = 0; i < MAX_CAL_LINE; i++) result[i] = COLOR_OFFSET_FUNC(x, y, mdnie->props.line[i].num, mdnie->props.line[i].den, mdnie->props.line[i].con); area = RECOGNIZE_REGION(result[H_LINE], result[V_LINE]); panel_dbg("coord %d %d area Q%d, result %lld %lld\n", x, y, area + 1, result[H_LINE], result[V_LINE]); return area; } static int mdnie_coordinate_tune_x(struct mdnie_info *mdnie, int x, int y) { int res, area; area = mdnie_coordinate_area(mdnie, x, y); res = ((mdnie->props.coef[area].a * x) + (mdnie->props.coef[area].b * y) + (((mdnie->props.coef[area].c * x + (1L << 9)) >> 10) * y) + (mdnie->props.coef[area].d * 10000) + (1L << 9)) >> 10; return max(min(res, 1024), 0); } static int mdnie_coordinate_tune_y(struct mdnie_info *mdnie, int x, int y) { int res, area; area = mdnie_coordinate_area(mdnie, x, y); res = ((mdnie->props.coef[area].e * x) + (mdnie->props.coef[area].f * y) + (((mdnie->props.coef[area].g * x + (1L << 9)) >> 10) * y) + (mdnie->props.coef[area].h * 10000) + (1L << 9)) >> 10; return max(min(res, 1024), 0); } static void mdnie_coordinate_tune_rgb(struct mdnie_info *mdnie, int x, int y, u8 (*tune_rgb)[MAX_COLOR]) { static int pt[MAX_QUAD][MAX_RGB_PT] = { { CCRD_PT_5, CCRD_PT_2, CCRD_PT_6, CCRD_PT_3 }, /* QUAD_1 */ { CCRD_PT_5, CCRD_PT_2, CCRD_PT_4, CCRD_PT_1 }, /* QUAD_2 */ { CCRD_PT_5, CCRD_PT_8, CCRD_PT_4, CCRD_PT_7 }, /* QUAD_3 */ { CCRD_PT_5, CCRD_PT_8, CCRD_PT_6, CCRD_PT_9 }, /* QUAD_4 */ }; int i, c, type, area, tune_x, tune_y, res[MAX_COLOR][MAX_RGB_PT]; s64 result[MAX_CAL_LINE]; area = mdnie_coordinate_area(mdnie, x, y); if (((x - mdnie->props.cal_x_center) * (x - mdnie->props.cal_x_center) + (y - mdnie->props.cal_y_center) * (y - mdnie->props.cal_y_center)) <= mdnie->props.cal_boundary_center) { tune_x = 0; tune_y = 0; } else { tune_x = mdnie_coordinate_tune_x(mdnie, x, y); tune_y = mdnie_coordinate_tune_y(mdnie, x, y); } for (i = 0; i < MAX_CAL_LINE; i++) result[i] = COLOR_OFFSET_FUNC(x, y, mdnie->props.line[i].num, mdnie->props.line[i].den, mdnie->props.line[i].con); for (type = 0; type < MAX_WCRD_TYPE; type++) { for (c = 0; c < MAX_COLOR; c++) { for (i = 0; i < MAX_RGB_PT; i++) res[c][i] = mdnie->props.vtx[type][pt[area][i]][c]; tune_rgb[type][c] = ((((res[c][RGB_00] * (1024 - tune_x) + (res[c][RGB_10] * tune_x)) * (1024 - tune_y)) + ((res[c][RGB_01] * (1024 - tune_x) + (res[c][RGB_11] * tune_x)) * tune_y)) + (1L << 19)) >> 20; } } panel_info("coord (x:%4d y:%4d Q%d compV:%8lld compH:%8lld)\t" "tune_coord (%4d %4d) tune_rgb[ADT] (%3d %3d %3d) tune_rgb[D65] (%3d %3d %3d)\n", x, y, area + 1, result[H_LINE], result[V_LINE], tune_x, tune_y, tune_rgb[WCRD_TYPE_ADAPTIVE][0], tune_rgb[WCRD_TYPE_ADAPTIVE][1], tune_rgb[WCRD_TYPE_ADAPTIVE][2], tune_rgb[WCRD_TYPE_D65][0], tune_rgb[WCRD_TYPE_D65][1], tune_rgb[WCRD_TYPE_D65][2]); } __visible_for_testing int mdnie_init_coordinate_tune(struct mdnie_info *mdnie) { int x, y; if (!mdnie) return -EINVAL; mdnie_get_coordinate(mdnie, &x, &y); mdnie->props.wcrd_x = x; mdnie->props.wcrd_y = y; mdnie_coordinate_tune_rgb(mdnie, x, y, mdnie->props.coord_wrgb); return 0; } static int panel_set_mdnie(struct panel_device *panel) { int ret; struct mdnie_info *mdnie = &panel->mdnie; int mdnie_mode = mdnie_current_state(mdnie); if (panel == NULL) { panel_err("panel is null\n"); return -EINVAL; } if (!IS_PANEL_ACTIVE(panel)) return -EAGAIN; #ifdef CONFIG_SUPPORT_AFC panel_info("do mdnie-seq (mode:%s, afc:%s)\n", mdnie_mode_name[mdnie_mode], !mdnie->props.afc_on ? "off" : "on"); #else panel_info("do mdnie-seq (mode:%s)\n", mdnie_mode_name[mdnie_mode]); #endif ret = 0; mutex_lock(&panel->op_lock); ret = panel_do_seqtbl(panel, &mdnie->seqtbl[MDNIE_SET_SEQ]); if (unlikely(ret < 0)) panel_err("failed to write mdnie seqtbl\n"); #ifdef CONFIG_SUPPORT_AFC ret = panel_do_seqtbl(panel, !mdnie->props.afc_on ? &mdnie->seqtbl[MDNIE_AFC_OFF_SEQ] : &mdnie->seqtbl[MDNIE_AFC_ON_SEQ]); if (unlikely(ret < 0)) panel_err("failed to write afc seqtbl\n"); #endif mutex_unlock(&panel->op_lock); return ret; } static void mdnie_maptbl_init(struct mdnie_info *mdnie, int index) { int i; if (((index + 1) * mdnie->nr_reg) > mdnie->nr_maptbl) { panel_err("out of range index %d\n", index); return; } for (i = 0; i < mdnie->nr_reg; i++) maptbl_init(&mdnie->maptbl[index * mdnie->nr_reg + i]); } static void scr_white_maptbl_init(struct mdnie_info *mdnie, int index) { if ((index + 1) > mdnie->nr_scr_white_maptbl) { panel_err("out of range index %d\n", index); return; } maptbl_init(&mdnie->scr_white_maptbl[index]); } static void scr_white_maptbls_init(struct mdnie_info *mdnie) { int i; for (i = 0; i < mdnie->nr_scr_white_maptbl; i++) maptbl_init(&mdnie->scr_white_maptbl[i]); } static void mdnie_update_scr_white_mode(struct mdnie_info *mdnie) { int mdnie_mode = mdnie_current_state(mdnie); if (mdnie_mode == MDNIE_SCENARIO_MODE) { if ((IS_LDU_MODE(mdnie)) && (mdnie->props.scenario != EBOOK_MODE)) { mdnie->props.scr_white_mode = SCR_WHITE_MODE_ADJUST_LDU; } else if (mdnie->props.update_sensorRGB && mdnie->props.mode == AUTO && (mdnie->props.scenario == BROWSER_MODE || mdnie->props.scenario == EBOOK_MODE)) { mdnie->props.scr_white_mode = SCR_WHITE_MODE_SENSOR_RGB; mdnie->props.update_sensorRGB = false; } else if (mdnie->props.scenario <= SCENARIO_MAX && mdnie->props.scenario != EBOOK_MODE) { mdnie->props.scr_white_mode = SCR_WHITE_MODE_COLOR_COORDINATE; } else { mdnie->props.scr_white_mode = SCR_WHITE_MODE_NONE; } } else if (mdnie_mode == MDNIE_HBM_MODE) { mdnie->props.scr_white_mode = SCR_WHITE_MODE_COLOR_COORDINATE; } else { mdnie->props.scr_white_mode = SCR_WHITE_MODE_NONE; } panel_dbg("scr_white_mode %s\n", scr_white_mode_name[mdnie->props.scr_white_mode]); } int mdnie_set_def_wrgb(struct mdnie_info *mdnie, unsigned char r, unsigned char g, unsigned char b) { if (!mdnie) return -EINVAL; mdnie->props.def_wrgb[RED] = r; mdnie->props.def_wrgb[GREEN] = g; mdnie->props.def_wrgb[BLUE] = b; panel_dbg("def_wrgb: %d(%02X) %d(%02X) %d(%02X)\n", mdnie->props.def_wrgb[RED], mdnie->props.def_wrgb[RED], mdnie->props.def_wrgb[GREEN], mdnie->props.def_wrgb[GREEN], mdnie->props.def_wrgb[BLUE], mdnie->props.def_wrgb[BLUE]); return 0; } EXPORT_SYMBOL(mdnie_set_def_wrgb); int mdnie_set_cur_wrgb(struct mdnie_info *mdnie, unsigned char r, unsigned char g, unsigned char b) { if (!mdnie) return -EINVAL; mdnie->props.cur_wrgb[RED] = r; mdnie->props.cur_wrgb[GREEN] = g; mdnie->props.cur_wrgb[BLUE] = b; panel_dbg("cur_wrgb: %d(%02X) %d(%02X) %d(%02X)\n", mdnie->props.cur_wrgb[RED], mdnie->props.cur_wrgb[RED], mdnie->props.cur_wrgb[GREEN], mdnie->props.cur_wrgb[GREEN], mdnie->props.cur_wrgb[BLUE], mdnie->props.cur_wrgb[BLUE]); return 0; } EXPORT_SYMBOL(mdnie_set_cur_wrgb); int mdnie_cur_wrgb_to_byte_array(struct mdnie_info *mdnie, unsigned char *dst, unsigned int stride) { if (!mdnie || !dst || !stride) return -EINVAL; copy_to_sliced_byte_array(dst, mdnie->props.cur_wrgb, 0, MAX_COLOR * stride, stride); return 0; } EXPORT_SYMBOL(mdnie_cur_wrgb_to_byte_array); int mdnie_update_wrgb(struct mdnie_info *mdnie, unsigned char r, unsigned char g, unsigned char b) { unsigned char src[MAX_COLOR] = { r, g, b }; unsigned char dst[MAX_COLOR] = { 0, }; int i, value; if (!mdnie) return -EINVAL; if (mdnie->props.scr_white_mode < 0 || mdnie->props.scr_white_mode >= MAX_SCR_WHITE_MODE) { panel_warn("out of range %d\n", mdnie->props.scr_white_mode); return -EINVAL; } if (mdnie->props.scr_white_mode == SCR_WHITE_MODE_COLOR_COORDINATE) { mdnie_set_def_wrgb(mdnie, r, g, b); for_each_color(i) { value = (int)mdnie->props.def_wrgb[i] + (int)((mdnie->props.mode == AUTO) ? mdnie->props.def_wrgb_ofs[i] : 0); dst[i] = min(max(value, 0), 255); } mdnie_set_cur_wrgb(mdnie, dst[RED], dst[GREEN], dst[BLUE]); } else if (mdnie->props.scr_white_mode == SCR_WHITE_MODE_ADJUST_LDU) { for_each_color(i) { value = (int)src[i] + (int)(((mdnie->props.mode == AUTO) && (mdnie->props.scenario != EBOOK_MODE)) ? mdnie->props.def_wrgb_ofs[i] : 0); dst[i] = min(max(value, 0), 255); } mdnie_set_cur_wrgb(mdnie, dst[RED], dst[GREEN], dst[BLUE]); } else if (mdnie->props.scr_white_mode == SCR_WHITE_MODE_SENSOR_RGB) { mdnie_set_cur_wrgb(mdnie, r, g, b); } else { panel_warn("wrgb is not updated in scr_white_mode(%d)\n", mdnie->props.scr_white_mode); } return 0; } EXPORT_SYMBOL(mdnie_update_wrgb); int panel_mdnie_update(struct panel_device *panel) { int ret; struct mdnie_info *mdnie = &panel->mdnie; mutex_lock(&mdnie->lock); if (!IS_MDNIE_ENABLED(mdnie)) { panel_info("mdnie is off state\n"); mutex_unlock(&mdnie->lock); return -EINVAL; } if (mdnie_coordinate_changed(mdnie)) { mdnie_init_coordinate_tune(mdnie); scr_white_maptbls_init(mdnie); } mdnie_update_scr_white_mode(mdnie); ret = panel_set_mdnie(panel); if (ret < 0 && ret != -EAGAIN) { panel_err("failed to set mdnie %d\n", ret); mutex_unlock(&mdnie->lock); return ret; } mutex_unlock(&mdnie->lock); #ifdef CONFIG_EXYNOS_DECON_LCD_COPR copr_update_start(&panel->copr, 3); #endif return 0; } static int mdnie_update(struct mdnie_info *mdnie) { int ret; struct panel_device *panel = container_of(mdnie, struct panel_device, mdnie); panel_wake_lock(panel, WAKE_TIMEOUT_MSEC); ret = panel_mdnie_update(panel); panel_wake_unlock(panel); return ret; } #ifdef MDNIE_SELF_TEST static void mdnie_coordinate_tune_test(struct mdnie_info *mdnie) { int i, x, y; u8 tune_rgb[MAX_WCRD_TYPE][MAX_COLOR]; int input[27][2] = { { 2936, 3144 }, { 2987, 3203 }, { 3032, 3265 }, { 2954, 3108 }, { 2991, 3158 }, { 3041, 3210 }, { 2967, 3058 }, { 3004, 3091 }, { 3063, 3156 }, { 2985, 3206 }, { 3032, 3238 }, { 2955, 3094 }, { 2997, 3151 }, { 3045, 3191 }, { 2971, 3047 }, { 3020, 3100 }, { 3066, 3144 }, { 2983, 3131 }, { 2987, 3170 }, { 2975, 3159 }, { 3021, 3112 }, { 2982, 3122 }, { 2987, 3130 }, { 2930, 3260 }, { 2930, 3050 }, { 3060, 3050 }, { 3060, 3260 }, }; u8 output[27][MAX_COLOR] = { { 0xFF, 0xFA, 0xF9 }, { 0xFE, 0xFA, 0xFE }, { 0xF8, 0xF6, 0xFF }, { 0xFF, 0xFD, 0xFB }, { 0xFF, 0xFE, 0xFF }, { 0xF9, 0xFA, 0xFF }, { 0xFC, 0xFF, 0xF9 }, { 0xFB, 0xFF, 0xFB }, { 0xF9, 0xFF, 0xFF }, { 0xFE, 0xFA, 0xFE }, { 0xF9, 0xF8, 0xFF }, { 0xFE, 0xFE, 0xFA }, { 0xFE, 0xFF, 0xFF }, { 0xF8, 0xFC, 0xFF }, { 0xFA, 0xFF, 0xF8 }, { 0xF9, 0xFF, 0xFC }, { 0xF8, 0xFF, 0xFF }, { 0xFE, 0xFF, 0xFD }, { 0xFF, 0xFD, 0xFF }, { 0xFF, 0xFD, 0xFD }, { 0xF9, 0xFF, 0xFC }, { 0xFE, 0xFF, 0xFD }, { 0xFE, 0xFF, 0xFD }, { 0xFF, 0xFF, 0xFF }, { 0xFF, 0xFF, 0xFF }, { 0xFF, 0xFF, 0xFF }, { 0xFF, 0xFF, 0xFF }, }; for (x = MIN_WCRD_X; x <= MAX_WCRD_X; x += 10) for (y = MIN_WCRD_Y; y <= MAX_WCRD_Y; y += 10) mdnie_coordinate_tune_rgb(mdnie, x, y, tune_rgb); for (i = 0; i < 27; i++) { g_coord_x = input[i][0]; g_coord_y = input[i][1]; mdnie_update(mdnie); panel_info("compare %02X %02X %02X : %02X %02X %02X (%s)\n", output[i][0], output[i][1], output[i][2], mdnie->props.coord_wrgb[WCRD_TYPE_ADAPTIVE][0], mdnie->props.coord_wrgb[WCRD_TYPE_ADAPTIVE][1], mdnie->props.coord_wrgb[WCRD_TYPE_ADAPTIVE][2], output[i][0] == mdnie->props.coord_wrgb[WCRD_TYPE_ADAPTIVE][0] && output[i][1] == mdnie->props.coord_wrgb[WCRD_TYPE_ADAPTIVE][1] && output[i][2] == mdnie->props.coord_wrgb[WCRD_TYPE_ADAPTIVE][2] ? "SUCCESS" : "FAILED"); } } #endif static ssize_t mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d\n", mdnie->props.mode); } static ssize_t mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mdnie_info *mdnie = dev_get_drvdata(dev); unsigned int value = 0; int ret; ret = kstrtouint(buf, 0, &value); if (ret < 0) return ret; if (value >= MODE_MAX) { panel_err("invalid value=%d\n", value); return -EINVAL; } panel_info("value=%d\n", value); mutex_lock(&mdnie->lock); mdnie->props.mode = value; mutex_unlock(&mdnie->lock); mdnie_update(mdnie); return count; } static ssize_t scenario_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d\n", mdnie->props.scenario); } static ssize_t scenario_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mdnie_info *mdnie = dev_get_drvdata(dev); unsigned int value; int ret; ret = kstrtouint(buf, 0, &value); if (ret < 0) return ret; if (!SCENARIO_IS_VALID(value)) { panel_err("invalid scenario %d\n", value); return -EINVAL; } panel_info("value=%d\n", value); mutex_lock(&mdnie->lock); mdnie->props.scenario = value; mutex_unlock(&mdnie->lock); mdnie_update(mdnie); return count; } static ssize_t accessibility_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d\n", mdnie->props.accessibility); } static ssize_t accessibility_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mdnie_info *mdnie = dev_get_drvdata(dev); unsigned int s[12] = {0, }; int i, value = 0, ret; ret = sscanf(buf, "%d %x %x %x %x %x %x %x %x %x %x %x %x", &value, &s[0], &s[1], &s[2], &s[3], &s[4], &s[5], &s[6], &s[7], &s[8], &s[9], &s[10], &s[11]); if (ret <= 0 || ret > ARRAY_SIZE(s) + 1 || ((ret - 1) > (MAX_MDNIE_SCR_LEN / 2))) { panel_err("invalid size %d\n", ret); return -EINVAL; } if (value < 0 || value >= ACCESSIBILITY_MAX) { panel_err("unknown accessibility %d\n", value); return -EINVAL; } panel_info("value: %d, cnt: %d\n", value, ret); mutex_lock(&mdnie->lock); mdnie->props.accessibility = value; if (ret > 1 && (value == COLOR_BLIND || value == COLOR_BLIND_HBM)) { for (i = 0; i < ret - 1; i++) { mdnie->props.scr[i * 2 + 0] = GET_LSB_8BIT(s[i]); mdnie->props.scr[i * 2 + 1] = GET_MSB_8BIT(s[i]); } mdnie->props.sz_scr = (ret - 1) * 2; mdnie_maptbl_init(mdnie, MAPTBL_IDX_ACCESSIBILITY(mdnie->props.accessibility)); } mutex_unlock(&mdnie->lock); panel_info("%s\n", buf); mdnie_update(mdnie); return count; } static ssize_t bypass_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d\n", mdnie->props.bypass); } static ssize_t bypass_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mdnie_info *mdnie = dev_get_drvdata(dev); unsigned int value; int ret; ret = kstrtouint(buf, 0, &value); if (ret < 0) return ret; if (value >= BYPASS_MAX) { panel_err("invalid value=%d\n", value); return -EINVAL; } panel_info("value=%d\n", value); value = (value) ? BYPASS_ON : BYPASS_OFF; mutex_lock(&mdnie->lock); mdnie->props.bypass = value; mutex_unlock(&mdnie->lock); mdnie_update(mdnie); return count; } static ssize_t lux_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d\n", mdnie->props.hbm); } static ssize_t lux_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mdnie_info *mdnie = dev_get_drvdata(dev); int ret, value; int hbm_ce_lux = (mdnie->props.hbm_ce_lux > 0) ? mdnie->props.hbm_ce_lux : 40000; ret = kstrtoint(buf, 0, &value); if (ret < 0) return ret; mutex_lock(&mdnie->lock); mdnie->props.hbm = (value < hbm_ce_lux) ? 0 : 1; mutex_unlock(&mdnie->lock); panel_info("hbm:%d (lux:%d hbm_ce_lux:%d)\n", mdnie->props.hbm, value, hbm_ce_lux); mdnie_update(mdnie); return count; } /* Temporary solution: Do not use this sysfs as official purpose */ static ssize_t mdnie_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); int maptbl_index = mdnie_get_maptbl_index(mdnie); int mdnie_mode = mdnie_current_state(mdnie); unsigned int i, len = 0; if (!IS_MDNIE_ENABLED(mdnie)) { panel_err("mdnie state is off\n"); return -EINVAL; } len += snprintf(buf + len, PAGE_SIZE - len, "mdnie %s-mode, maptbl %s(%d)\n", mdnie_mode_name[mdnie_mode], (maptbl_index < 0) ? "invalid" : mdnie_maptbl_name[maptbl_index], maptbl_index); len += snprintf(buf + len, PAGE_SIZE - len, "accessibility %s(%d), hdr %d, hmd %d, hbm %d\n", accessibility_name[mdnie->props.accessibility], mdnie->props.accessibility, mdnie->props.hdr, mdnie->props.hmd, mdnie->props.hbm); len += snprintf(buf + len, PAGE_SIZE - len, "scenario %s(%d), mode %s(%d)\n", scenario_name[mdnie->props.scenario], mdnie->props.scenario, scenario_mode_name[mdnie->props.mode], mdnie->props.mode); len += snprintf(buf + len, PAGE_SIZE - len, "scr_white_mode %s\n", scr_white_mode_name[mdnie->props.scr_white_mode]); len += snprintf(buf + len, PAGE_SIZE - len, "mdnie_ldu %d, coord x %d, y %d area Q%d\n", mdnie->props.ldu, mdnie->props.wcrd_x, mdnie->props.wcrd_y, mdnie_coordinate_area(mdnie, mdnie->props.wcrd_x, mdnie->props.wcrd_y) + 1); len += snprintf(buf + len, PAGE_SIZE - len, "coord_wrgb[adpt] r:%d(%02X) g:%d(%02X) b:%d(%02X)\n", mdnie->props.coord_wrgb[WCRD_TYPE_ADAPTIVE][0], mdnie->props.coord_wrgb[WCRD_TYPE_ADAPTIVE][0], mdnie->props.coord_wrgb[WCRD_TYPE_ADAPTIVE][1], mdnie->props.coord_wrgb[WCRD_TYPE_ADAPTIVE][1], mdnie->props.coord_wrgb[WCRD_TYPE_ADAPTIVE][2], mdnie->props.coord_wrgb[WCRD_TYPE_ADAPTIVE][2]); len += snprintf(buf + len, PAGE_SIZE - len, "coord_wrgb[d65] r:%d(%02X) g:%d(%02X) b:%d(%02X)\n", mdnie->props.coord_wrgb[WCRD_TYPE_D65][0], mdnie->props.coord_wrgb[WCRD_TYPE_D65][0], mdnie->props.coord_wrgb[WCRD_TYPE_D65][1], mdnie->props.coord_wrgb[WCRD_TYPE_D65][1], mdnie->props.coord_wrgb[WCRD_TYPE_D65][2], mdnie->props.coord_wrgb[WCRD_TYPE_D65][2]); len += snprintf(buf + len, PAGE_SIZE - len, "cur_wrgb r:%d(%02X) g:%d(%02X) b:%d(%02X)\n", mdnie->props.cur_wrgb[0], mdnie->props.cur_wrgb[0], mdnie->props.cur_wrgb[1], mdnie->props.cur_wrgb[1], mdnie->props.cur_wrgb[2], mdnie->props.cur_wrgb[2]); len += snprintf(buf + len, PAGE_SIZE - len, "ssr_wrgb r:%d(%02X) g:%d(%02X) b:%d(%02X)\n", mdnie->props.ssr_wrgb[0], mdnie->props.ssr_wrgb[0], mdnie->props.ssr_wrgb[1], mdnie->props.ssr_wrgb[1], mdnie->props.ssr_wrgb[2], mdnie->props.ssr_wrgb[2]); len += snprintf(buf + len, PAGE_SIZE - len, "def_wrgb r:%d(%02X) g:%d(%02X) b:%d(%02X) offset r:%d g:%d b:%d\n", mdnie->props.def_wrgb[0], mdnie->props.def_wrgb[0], mdnie->props.def_wrgb[1], mdnie->props.def_wrgb[1], mdnie->props.def_wrgb[2], mdnie->props.def_wrgb[2], mdnie->props.def_wrgb_ofs[0], mdnie->props.def_wrgb_ofs[1], mdnie->props.def_wrgb_ofs[2]); len += snprintf(buf + len, PAGE_SIZE - len, "scr : "); if (mdnie->props.sz_scr) { for (i = 0; i < mdnie->props.sz_scr; i++) len += snprintf(buf + len, PAGE_SIZE - len, "%02x ", mdnie->props.scr[i]); } else { len += snprintf(buf + len, PAGE_SIZE - len, "none"); } len += snprintf(buf + len, PAGE_SIZE - len, "\n"); len += snprintf(buf + len, PAGE_SIZE - len, "night_mode %s, level %d\n", mdnie->props.night ? "on" : "off", mdnie->props.night_level); #ifdef MDNIE_SELF_TEST mdnie_coordinate_tune_test(mdnie); #endif return len; } static ssize_t sensorRGB_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d %d %d\n", mdnie->props.cur_wrgb[0], mdnie->props.cur_wrgb[1], mdnie->props.cur_wrgb[2]); } static ssize_t sensorRGB_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mdnie_info *mdnie = dev_get_drvdata(dev); unsigned int white_red, white_green, white_blue; int mdnie_mode = mdnie_current_state(mdnie), ret; ret = sscanf(buf, "%u %u %u", &white_red, &white_green, &white_blue); if (ret != 3) return -EINVAL; panel_info("white_r %u, white_g %u, white_b %u\n", white_red, white_green, white_blue); if (mdnie_mode == MDNIE_SCENARIO_MODE && mdnie->props.mode == AUTO && (mdnie->props.scenario == BROWSER_MODE || mdnie->props.scenario == EBOOK_MODE)) { mutex_lock(&mdnie->lock); mdnie->props.ssr_wrgb[0] = white_red; mdnie->props.ssr_wrgb[1] = white_green; mdnie->props.ssr_wrgb[2] = white_blue; mdnie->props.update_sensorRGB = true; scr_white_maptbl_init(mdnie, MDNIE_SENSOR_RGB_MAPTBL); mutex_unlock(&mdnie->lock); mdnie_update(mdnie); } return count; } static ssize_t whiteRGB_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d %d %d\n", mdnie->props.def_wrgb_ofs[0], mdnie->props.def_wrgb_ofs[1], mdnie->props.def_wrgb_ofs[2]); } static ssize_t whiteRGB_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mdnie_info *mdnie = dev_get_drvdata(dev); int wr_offset, wg_offset, wb_offset; int ret; ret = sscanf(buf, "%d %d %d", &wr_offset, &wg_offset, &wb_offset); if (ret != 3) return -EINVAL; if (!IS_VALID_WRGB_OFS(wr_offset) || !IS_VALID_WRGB_OFS(wg_offset) || !IS_VALID_WRGB_OFS(wb_offset)) { panel_err("invalid offset %d %d %d\n", wr_offset, wg_offset, wb_offset); return -EINVAL; } panel_info("wr_offset %d, wg_offset %d, wb_offset %d\n", wr_offset, wg_offset, wb_offset); mutex_lock(&mdnie->lock); mdnie->props.def_wrgb_ofs[0] = wr_offset; mdnie->props.def_wrgb_ofs[1] = wg_offset; mdnie->props.def_wrgb_ofs[2] = wb_offset; mutex_unlock(&mdnie->lock); mdnie_update(mdnie); return count; } static ssize_t night_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d %d\n", mdnie->props.night, mdnie->props.night_level); } static ssize_t night_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mdnie_info *mdnie = dev_get_drvdata(dev); int enable, level, ret; ret = sscanf(buf, "%d %d", &enable, &level); if (ret != 2) return -EINVAL; if (level < 0 || level >= mdnie->props.num_night_level) return -EINVAL; panel_info("night_mode %s level %d\n", enable ? "on" : "off", level); mutex_lock(&mdnie->lock); mdnie->props.night = !!enable; mdnie->props.night_level = level; if (enable) { /* MDNIE_NIGHT_MAPTBL update using MDNIE_ETC_NIGHT_MAPTBL */ mdnie_maptbl_init(mdnie, MDNIE_NIGHT_MAPTBL); } mutex_unlock(&mdnie->lock); mdnie_update(mdnie); return count; } static ssize_t color_lens_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d %d %d\n", mdnie->props.color_lens, mdnie->props.color_lens_color, mdnie->props.color_lens_level); } static ssize_t color_lens_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mdnie_info *mdnie = dev_get_drvdata(dev); int enable, level, color, ret; ret = sscanf(buf, "%d %d %d", &enable, &color, &level); if (ret != 3) return -EINVAL; if (color < 0 || color >= COLOR_LENS_COLOR_MAX) return -EINVAL; if (level < 0 || level >= COLOR_LENS_LEVEL_MAX) return -EINVAL; panel_info("color_lens_mode %s color %d level %d\n", enable ? "on" : "off", color, level); mutex_lock(&mdnie->lock); mdnie->props.color_lens = !!enable; mdnie->props.color_lens_color = color; mdnie->props.color_lens_level = level; if (enable) { /* MDNIE_COLOR_LENS_MAPTBL update using MDNIE_ETC_COLOR_LENS_MAPTBL */ mdnie_maptbl_init(mdnie, MDNIE_COLOR_LENS_MAPTBL); } mutex_unlock(&mdnie->lock); mdnie_update(mdnie); return count; } static ssize_t hdr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d\n", mdnie->props.hdr); } static ssize_t hdr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mdnie_info *mdnie = dev_get_drvdata(dev); unsigned int value; int ret; ret = kstrtouint(buf, 0, &value); if (ret < 0) return ret; if (value >= HDR_MAX) { panel_err("invalid value=%d\n", value); return -EINVAL; } panel_info("value=%d\n", value); mutex_lock(&mdnie->lock); mdnie->props.hdr = value; mutex_unlock(&mdnie->lock); mdnie_update(mdnie); return count; } static ssize_t light_notification_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d\n", mdnie->props.light_notification); } static ssize_t light_notification_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mdnie_info *mdnie = dev_get_drvdata(dev); unsigned int value; int ret; ret = kstrtouint(buf, 0, &value); if (ret < 0) return ret; if (value >= LIGHT_NOTIFICATION_MAX) return -EINVAL; panel_info("value=%d\n", value); mutex_lock(&mdnie->lock); mdnie->props.light_notification = value; mutex_unlock(&mdnie->lock); mdnie_update(mdnie); return count; } static ssize_t mdnie_ldu_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d %d %d\n", mdnie->props.cur_wrgb[0], mdnie->props.cur_wrgb[1], mdnie->props.cur_wrgb[2]); } static ssize_t mdnie_ldu_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mdnie_info *mdnie = dev_get_drvdata(dev); int value, ret; ret = kstrtoint(buf, 10, &value); if (ret < 0) return ret; if (value < 0 || value >= MAX_LDU_MODE) { panel_err("out of range %d\n", value); return -EINVAL; } panel_info("value=%d\n", value); mutex_lock(&mdnie->lock); mdnie->props.ldu = value; mutex_unlock(&mdnie->lock); mdnie_update(mdnie); return count; } #ifdef CONFIG_SUPPORT_HMD static ssize_t hmt_color_temperature_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "hmd_mode: %d\n", mdnie->props.hmd); } static ssize_t hmt_color_temperature_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mdnie_info *mdnie = dev_get_drvdata(dev); unsigned int value; int ret; ret = kstrtouint(buf, 0, &value); if (ret < 0) return ret; if (value >= HMD_MDNIE_MAX) return -EINVAL; if (value == mdnie->props.hmd) return count; panel_info("value=%d\n", value); mutex_lock(&mdnie->lock); mdnie->props.hmd = value; mutex_unlock(&mdnie->lock); mdnie_update(mdnie); return count; } #endif #ifdef CONFIG_SUPPORT_AFC static ssize_t afc_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdnie_info *mdnie = dev_get_drvdata(dev); int len = 0; size_t i; len += snprintf(buf + len, PAGE_SIZE - len, "%d", mdnie->props.afc_on); for (i = 0; i < ARRAY_SIZE(mdnie->props.afc_roi); i++) len += snprintf(buf + len, PAGE_SIZE - len, " %d", mdnie->props.afc_roi[i]); len += snprintf(buf + len, PAGE_SIZE - len, "\n"); return len; } static ssize_t afc_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct mdnie_info *mdnie = dev_get_drvdata(dev); int s[12] = {0, }; int value = 0, ret; size_t i; ret = sscanf(buf, "%i %i %i %i %i %i %i %i %i %i %i %i %i", &value, &s[0], &s[1], &s[2], &s[3], &s[4], &s[5], &s[6], &s[7], &s[8], &s[9], &s[10], &s[11]); if ((ret != 1 && ret != ARRAY_SIZE(s) + 1) || ARRAY_SIZE(s) != MAX_AFC_ROI_LEN) { panel_err("invalid size %d\n", ret); return -EINVAL; } panel_info("value=%d, cnt=%d\n", value, ret); mutex_lock(&mdnie->lock); mdnie->props.afc_on = !!value; for (i = 0; i < ARRAY_SIZE(mdnie->props.afc_roi); i++) mdnie->props.afc_roi[i] = s[i] & 0xFF; mutex_unlock(&mdnie->lock); panel_info("%s\n", buf); mdnie_update(mdnie); return count; } #endif struct device_attribute mdnie_dev_attrs[] = { __PANEL_ATTR_RW(mode, 0664), __PANEL_ATTR_RW(scenario, 0664), __PANEL_ATTR_RW(accessibility, 0664), __PANEL_ATTR_RW(bypass, 0664), __PANEL_ATTR_RW(lux, 0000), __PANEL_ATTR_RO(mdnie, 0444), __PANEL_ATTR_RW(sensorRGB, 0664), __PANEL_ATTR_RW(whiteRGB, 0664), __PANEL_ATTR_RW(night_mode, 0664), __PANEL_ATTR_RW(color_lens, 0664), __PANEL_ATTR_RW(hdr, 0664), __PANEL_ATTR_RW(light_notification, 0664), __PANEL_ATTR_RW(mdnie_ldu, 0664), #ifdef CONFIG_SUPPORT_HMD __PANEL_ATTR_RW(hmt_color_temperature, 0664), #endif #ifdef CONFIG_SUPPORT_AFC __PANEL_ATTR_RW(afc, 0664), #endif }; int mdnie_enable(struct mdnie_info *mdnie) { struct panel_device *panel = container_of(mdnie, struct panel_device, mdnie); int ret; if (IS_MDNIE_ENABLED(mdnie)) { panel_info("mdnie already enabled\n"); panel_mdnie_update(panel); return 0; } mutex_lock(&mdnie->lock); mdnie->props.enable = 1; mdnie->props.light_notification = LIGHT_NOTIFICATION_OFF; if (IS_HBM_MODE(mdnie)) mdnie->props.trans_mode = TRANS_ON; mutex_unlock(&mdnie->lock); ret = panel_mdnie_update(panel); if (ret < 0) mdnie->props.enable = 0; mutex_lock(&mdnie->lock); mdnie->props.trans_mode = TRANS_ON; mutex_unlock(&mdnie->lock); panel_info("done %u\n", mdnie->props.enable); return 0; } int mdnie_disable(struct mdnie_info *mdnie) { if (!IS_MDNIE_ENABLED(mdnie)) { panel_info("mdnie already disabled\n"); return 0; } mutex_lock(&mdnie->lock); mdnie->props.enable = 0; mdnie->props.trans_mode = TRANS_OFF; mdnie->props.update_sensorRGB = false; mutex_unlock(&mdnie->lock); panel_info("done\n"); return 0; } static int fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data) { struct mdnie_info *mdnie; struct fb_event *evdata = data; int fb_blank; switch (event) { case FB_EVENT_BLANK: break; default: return 0; } mdnie = container_of(self, struct mdnie_info, fb_notif); fb_blank = *(int *)evdata->data; panel_dbg("%d\n", fb_blank); if (evdata->info->node != 0) return 0; if (fb_blank == FB_BLANK_UNBLANK) { mutex_lock(&mdnie->lock); mdnie->props.light_notification = LIGHT_NOTIFICATION_OFF; if (IS_HBM_MODE(mdnie)) mdnie->props.trans_mode = TRANS_ON; mutex_unlock(&mdnie->lock); mdnie_update(mdnie); } return 0; } static int mdnie_register_fb(struct mdnie_info *mdnie) { memset(&mdnie->fb_notif, 0, sizeof(mdnie->fb_notif)); mdnie->fb_notif.notifier_call = fb_notifier_callback; fb_register_client(&mdnie->fb_notif); return 0; } static int mdnie_unregister_fb(struct mdnie_info *mdnie) { fb_unregister_client(&mdnie->fb_notif); mdnie->fb_notif.notifier_call = NULL; return 0; } #ifdef CONFIG_DISPLAY_USE_INFO static int dpui_notifier_callback(struct notifier_block *self, unsigned long event, void *data) { struct mdnie_info *mdnie; struct panel_device *panel; struct panel_info *panel_data; char tbuf[MAX_DPUI_VAL_LEN]; u8 coordinate[PANEL_COORD_LEN] = { 0, }; int size; mdnie = container_of(self, struct mdnie_info, dpui_notif); panel = container_of(mdnie, struct panel_device, mdnie); panel_data = &panel->panel_data; mutex_lock(&mdnie->lock); resource_copy_by_name(panel_data, coordinate, "coordinate"); size = snprintf(tbuf, MAX_DPUI_VAL_LEN, "%d", (coordinate[0] << 8) | coordinate[1]); set_dpui_field(DPUI_KEY_WCRD_X, tbuf, size); size = snprintf(tbuf, MAX_DPUI_VAL_LEN, "%d", (coordinate[2] << 8) | coordinate[3]); set_dpui_field(DPUI_KEY_WCRD_Y, tbuf, size); size = snprintf(tbuf, MAX_DPUI_VAL_LEN, "%d", mdnie->props.def_wrgb_ofs[0]); set_dpui_field(DPUI_KEY_WOFS_R, tbuf, size); size = snprintf(tbuf, MAX_DPUI_VAL_LEN, "%d", mdnie->props.def_wrgb_ofs[1]); set_dpui_field(DPUI_KEY_WOFS_G, tbuf, size); size = snprintf(tbuf, MAX_DPUI_VAL_LEN, "%d", mdnie->props.def_wrgb_ofs[2]); set_dpui_field(DPUI_KEY_WOFS_B, tbuf, size); #if 0 /* disable for GKI build */ mdnie_get_efs(MDNIE_WOFS_ORG_PATH, def_wrgb_ofs_org); size = snprintf(tbuf, MAX_DPUI_VAL_LEN, "%d", def_wrgb_ofs_org[0]); set_dpui_field(DPUI_KEY_WOFS_R_ORG, tbuf, size); size = snprintf(tbuf, MAX_DPUI_VAL_LEN, "%d", def_wrgb_ofs_org[1]); set_dpui_field(DPUI_KEY_WOFS_G_ORG, tbuf, size); size = snprintf(tbuf, MAX_DPUI_VAL_LEN, "%d", def_wrgb_ofs_org[2]); set_dpui_field(DPUI_KEY_WOFS_B_ORG, tbuf, size); #endif mutex_unlock(&mdnie->lock); return 0; } static int mdnie_register_dpui(struct mdnie_info *mdnie) { memset(&mdnie->dpui_notif, 0, sizeof(mdnie->dpui_notif)); mdnie->dpui_notif.notifier_call = dpui_notifier_callback; return dpui_logging_register(&mdnie->dpui_notif, DPUI_TYPE_PANEL); } static int mdnie_unregister_dpui(struct mdnie_info *mdnie) { dpui_logging_unregister(&mdnie->dpui_notif); mdnie->dpui_notif.notifier_call = NULL; return 0; } #endif /* CONFIG_DISPLAY_USE_INFO */ __visible_for_testing int mdnie_init_property(struct mdnie_info *mdnie, struct mdnie_tune *mdnie_tune) { if (!mdnie || !mdnie_tune) return -EINVAL; mdnie->props.enable = 0; mdnie->props.scenario = UI_MODE; mdnie->props.mode = AUTO; mdnie->props.tuning = 0; mdnie->props.bypass = BYPASS_OFF; mdnie->props.hdr = HDR_OFF; mdnie->props.light_notification = LIGHT_NOTIFICATION_OFF; mdnie->props.hmd = HMD_MDNIE_OFF; mdnie->props.night = NIGHT_MODE_OFF; mdnie->props.night_level = NIGHT_LEVEL_6500K; mdnie->props.color_lens = COLOR_LENS_OFF; mdnie->props.color_lens_color = COLOR_LENS_COLOR_BLUE; mdnie->props.color_lens_level = COLOR_LENS_LEVEL_20P; mdnie->props.accessibility = ACCESSIBILITY_OFF; mdnie->props.ldu = LDU_MODE_OFF; mdnie->props.scr_white_mode = SCR_WHITE_MODE_NONE; mdnie->props.trans_mode = TRANS_ON; mdnie->props.update_sensorRGB = false; mdnie->props.sz_scr = 0; /* initialization by mdnie_tune */ mdnie->props.num_ldu_mode = mdnie_tune->num_ldu_mode; mdnie->props.num_night_level = mdnie_tune->num_night_level; mdnie->props.num_color_lens_color = mdnie_tune->num_color_lens_color; mdnie->props.num_color_lens_level = mdnie_tune->num_color_lens_level; memcpy(mdnie->props.line, mdnie_tune->line, sizeof(mdnie->props.line)); memcpy(mdnie->props.coef, mdnie_tune->coef, sizeof(mdnie->props.coef)); memcpy(mdnie->props.vtx, mdnie_tune->vtx, sizeof(mdnie->props.vtx)); mdnie->props.cal_x_center = mdnie_tune->cal_x_center; mdnie->props.cal_y_center = mdnie_tune->cal_y_center; mdnie->props.cal_boundary_center = mdnie_tune->cal_boundary_center; mdnie->props.hbm_ce_lux = mdnie_tune->hbm_ce_lux; return 0; } __visible_for_testing int mdnie_init_tables(struct mdnie_info *mdnie, struct mdnie_tune *mdnie_tune) { if (!mdnie || !mdnie_tune) return -EINVAL; if (mdnie_tune->nr_maptbl % MAX_MDNIE_MAPTBL) { panel_err("invalid size of maptbl %d\n", mdnie_tune->nr_maptbl); return -EINVAL; } mdnie->seqtbl = mdnie_tune->seqtbl; mdnie->nr_seqtbl = mdnie_tune->nr_seqtbl; mdnie->etc_maptbl = mdnie_tune->etc_maptbl; mdnie->nr_etc_maptbl = mdnie_tune->nr_etc_maptbl; mdnie->maptbl = mdnie_tune->maptbl; mdnie->nr_maptbl = mdnie_tune->nr_maptbl; mdnie->scr_white_maptbl = mdnie_tune->scr_white_maptbl; mdnie->nr_scr_white_maptbl = mdnie_tune->nr_scr_white_maptbl; #ifdef CONFIG_SUPPORT_AFC mdnie->afc_maptbl = mdnie_tune->afc_maptbl; mdnie->nr_afc_maptbl = mdnie_tune->nr_afc_maptbl; #endif mdnie->nr_reg = mdnie->nr_maptbl / MAX_MDNIE_MAPTBL; return 0; } __visible_for_testing int mdnie_init_maptbls(struct mdnie_info *mdnie) { int i; if (!mdnie) return -EINVAL; for (i = 0; i < mdnie->nr_etc_maptbl; i++) { mdnie->etc_maptbl[i].pdata = mdnie; maptbl_init(&mdnie->etc_maptbl[i]); } for (i = 0; i < mdnie->nr_maptbl; i++) { mdnie->maptbl[i].pdata = mdnie; maptbl_init(&mdnie->maptbl[i]); } for (i = 0; i < mdnie->nr_scr_white_maptbl; i++) { mdnie->scr_white_maptbl[i].pdata = mdnie; maptbl_init(&mdnie->scr_white_maptbl[i]); } #ifdef CONFIG_SUPPORT_AFC for (i = 0; i < mdnie->nr_afc_maptbl; i++) { mdnie->afc_maptbl[i].pdata = mdnie; maptbl_init(&mdnie->afc_maptbl[i]); } #endif return 0; } __visible_for_testing int mdnie_set_name(struct mdnie_info *mdnie, unsigned int id) { if (!mdnie) return -EINVAL; if (id == 0) snprintf(mdnie->name, MAX_MDNIE_DEV_NAME_SIZE, "%s", MDNIE_DEV_NAME); else snprintf(mdnie->name, MAX_MDNIE_DEV_NAME_SIZE, "%s-%d", MDNIE_DEV_NAME, id); return 0; } __visible_for_testing const char *mdnie_get_name(struct mdnie_info *mdnie) { return mdnie ? mdnie->name : NULL; } __visible_for_testing int mdnie_create_class(struct mdnie_info *mdnie) { if (!mdnie) return -EINVAL; mdnie->class = class_create(THIS_MODULE, mdnie_get_name(mdnie)); if (IS_ERR_OR_NULL(mdnie->class)) { panel_err("failed to create mdnie class\n"); return -EINVAL; } return 0; } __visible_for_testing int mdnie_destroy_class(struct mdnie_info *mdnie) { if (!mdnie || !mdnie->class) return -EINVAL; class_destroy(mdnie->class); mdnie->class = NULL; return 0; } __visible_for_testing int mdnie_create_device(struct mdnie_info *mdnie) { if (!mdnie) return -EINVAL; if (!to_panel_device(mdnie)->lcd_dev) { panel_err("lcd_device is null\n"); return -EINVAL; } mdnie->dev = device_create(mdnie->class, to_panel_device(mdnie)->lcd_dev, 0, &mdnie, "%s", mdnie_get_name(mdnie)); if (IS_ERR_OR_NULL(mdnie->dev)) { panel_err("failed to create mdnie device\n"); return -EINVAL; } dev_set_drvdata(mdnie->dev, mdnie); return 0; } __visible_for_testing int mdnie_destroy_device(struct mdnie_info *mdnie) { if (!mdnie) return -EINVAL; if (!mdnie->dev) { panel_err("mdnie device is null\n"); return -ENODEV; } device_unregister(mdnie->dev); mdnie->dev = NULL; return 0; } __visible_for_testing int mdnie_create_device_files(struct mdnie_info *mdnie) { int i, ret; if (!mdnie || !mdnie->dev) return -EINVAL; for (i = 0; i < ARRAY_SIZE(mdnie_dev_attrs); i++) { ret = device_create_file(mdnie->dev, &mdnie_dev_attrs[i]); if (ret < 0) { panel_err("failed to add %s sysfs entries, %d\n", mdnie_dev_attrs[i].attr.name, ret); return ret; } } return 0; } __visible_for_testing int mdnie_remove_device_files(struct mdnie_info *mdnie) { int i; if (!mdnie || !mdnie->dev) return -EINVAL; for (i = 0; i < ARRAY_SIZE(mdnie_dev_attrs); i++) device_remove_file(mdnie->dev, &mdnie_dev_attrs[i]); return 0; } __visible_for_testing int mdnie_create_class_and_device(struct mdnie_info *mdnie) { int ret; if (!mdnie) return -EINVAL; ret = mdnie_create_class(mdnie); if (ret < 0) return ret; ret = mdnie_create_device(mdnie); if (ret < 0) goto error1; ret = mdnie_create_device_files(mdnie); if (ret < 0) goto error2; return 0; error2: mdnie_destroy_device(mdnie); error1: mdnie_destroy_class(mdnie); return ret; } __visible_for_testing int mdnie_remove_class_and_device(struct mdnie_info *mdnie) { int ret; ret = mdnie_remove_device_files(mdnie); if (ret < 0) return -EINVAL; ret = mdnie_destroy_device(mdnie); if (ret < 0) return -EINVAL; ret = mdnie_destroy_class(mdnie); if (ret < 0) return -EINVAL; return 0; } int mdnie_init(struct mdnie_info *mdnie) { int ret; if (!mdnie) return -EINVAL; mutex_init(&mdnie->lock); ret = mdnie_set_name(mdnie, to_panel_device(mdnie)->id); if (ret < 0) return -EINVAL; ret = mdnie_create_class_and_device(mdnie); if (ret < 0) return -EINVAL; panel_info("mdnie init success\n"); return 0; } int mdnie_exit(struct mdnie_info *mdnie) { if (!mdnie) return -EINVAL; mutex_lock(&mdnie->lock); mdnie_remove_class_and_device(mdnie); memset(mdnie->name, 0, sizeof(mdnie->name)); mutex_unlock(&mdnie->lock); return 0; } int mdnie_probe(struct mdnie_info *mdnie, struct mdnie_tune *mdnie_tune) { int ret; if (unlikely(!mdnie || !mdnie_tune)) { panel_err("invalid argument\n"); return -ENODEV; } mutex_lock(&mdnie->lock); ret = mdnie_init_property(mdnie, mdnie_tune); if (ret < 0) goto err; ret = mdnie_init_tables(mdnie, mdnie_tune); if (ret < 0) goto err; ret = mdnie_init_coordinate_tune(mdnie); if (ret < 0) goto err; ret = mdnie_init_maptbls(mdnie); if (ret < 0) goto err; ret = mdnie_register_fb(mdnie); if (ret < 0) goto err; #ifdef CONFIG_DISPLAY_USE_INFO ret = mdnie_register_dpui(mdnie); if (ret < 0) goto err; #endif mutex_unlock(&mdnie->lock); ret = mdnie_enable(mdnie); if (ret < 0) { panel_err("failed to enable mdnie\n"); return ret; } panel_info("mdnie probe success\n"); return 0; err: mutex_unlock(&mdnie->lock); panel_err("failed to probe mdnie\n"); return ret; } int mdnie_remove(struct mdnie_info *mdnie) { if (!mdnie) return -EINVAL; mdnie_disable(mdnie); mutex_lock(&mdnie->lock); #ifdef CONFIG_DISPLAY_USE_INFO mdnie_unregister_dpui(mdnie); #endif mdnie_unregister_fb(mdnie); mutex_unlock(&mdnie->lock); return 0; }