kernel_samsung_a53x/drivers/gpu/drm/samsung/panel/panel_drv.c
2024-06-15 16:02:09 -03:00

5631 lines
No EOL
137 KiB
C
Executable file

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) Samsung Electronics Co., Ltd.
* Author: Minwoo Kim <minwoo7945.kim@samsung.com>
*
* 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 <linux/err.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqreturn.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_reserved_mem.h>
#include <linux/slab.h>
#include <linux/dma-buf.h>
#include <linux/vmalloc.h>
#include <linux/workqueue.h>
#include <linux/debugfs.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/ctype.h>
#include <video/mipi_display.h>
#include "panel_kunit.h"
#include "kernel/irq/internals.h"
#include "panel_modes.h"
#include "panel.h"
#include "panel_bl.h"
#include "panel_vrr.h"
#include "panel_drv.h"
#include "panel_debug.h"
#include "panel_drv_ioctl.h"
#include "panel_gpio.h"
#include "panel_regulator.h"
#include "panel_obj.h"
#include "dpui.h"
#ifdef CONFIG_EXYNOS_DECON_MDNIE_LITE
#include "mdnie.h"
#endif
#ifdef CONFIG_EXYNOS_DECON_LCD_SPI
#include "spi.h"
#endif
#ifdef CONFIG_SUPPORT_DDI_FLASH
#include "panel_poc.h"
#endif
#ifdef CONFIG_EXTEND_LIVE_CLOCK
#include "./aod/aod_drv.h"
#endif
#ifdef CONFIG_SUPPORT_MAFPC
#include "./mafpc/mafpc_drv.h"
#endif
#ifdef CONFIG_SUPPORT_POC_SPI
#include "panel_spi.h"
#endif
#if defined(CONFIG_TDMB_NOTIFIER)
#include <linux/tdmb_notifier.h>
#endif
#if IS_ENABLED(CONFIG_SEC_ABC)
#include <linux/sti/abc_common.h>
#endif
#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER)
#include <drivers/input/sec_input/sec_input.h>
#endif
#if defined(CONFIG_PANEL_FREQ_HOP)
#include "panel_freq_hop.h"
#endif
__visible_for_testing struct class *lcd_class;
static char *panel_state_names[] = {
"OFF", /* POWER OFF */
"ON", /* POWER ON */
"NORMAL", /* SLEEP OUT */
"LPM", /* LPM */
};
/* panel workqueue */
static char *panel_work_names[] = {
[PANEL_WORK_DISP_DET] = "disp-det",
[PANEL_WORK_PCD] = "pcd",
[PANEL_WORK_ERR_FG] = "err-fg",
[PANEL_WORK_CONN_DET] = "conn-det",
#ifdef CONFIG_SUPPORT_DIM_FLASH
[PANEL_WORK_DIM_FLASH] = "dim-flash",
#endif
[PANEL_WORK_CHECK_CONDITION] = "panel-condition-check",
[PANEL_WORK_UPDATE] = "panel-update",
#ifdef CONFIG_EVASION_DISP_DET
[PANEL_WORK_EVASION_DISP_DET] = "evasion-disp-det",
#endif
};
static void disp_det_handler(struct work_struct *data);
static void conn_det_handler(struct work_struct *data);
static void err_fg_handler(struct work_struct *data);
static void panel_condition_handler(struct work_struct *work);
#ifdef CONFIG_SUPPORT_DIM_FLASH
static void dim_flash_handler(struct work_struct *work);
#endif
static void panel_update_handler(struct work_struct *work);
#ifdef CONFIG_EVASION_DISP_DET
static void evasion_disp_det_handler(struct work_struct *work);
#endif
static void pcd_handler(struct work_struct *data);
int panel_disp_det_state(struct panel_device *panel);
int panel_conn_det_state(struct panel_device *panel);
int panel_pcd_state(struct panel_device *panel);
int panel_drv_set_regulators(struct panel_device *panel);
static panel_wq_handler panel_wq_handlers[] = {
[PANEL_WORK_DISP_DET] = disp_det_handler,
[PANEL_WORK_PCD] = pcd_handler,
[PANEL_WORK_ERR_FG] = err_fg_handler,
[PANEL_WORK_CONN_DET] = conn_det_handler,
#ifdef CONFIG_SUPPORT_DIM_FLASH
[PANEL_WORK_DIM_FLASH] = dim_flash_handler,
#endif
[PANEL_WORK_CHECK_CONDITION] = panel_condition_handler,
[PANEL_WORK_UPDATE] = panel_update_handler,
#ifdef CONFIG_EVASION_DISP_DET
[PANEL_WORK_EVASION_DISP_DET] = evasion_disp_det_handler,
#endif
};
static char *panel_thread_names[PANEL_THREAD_MAX] = {
#ifdef CONFIG_PANEL_VRR_BRIDGE
[PANEL_THREAD_VRR_BRIDGE] = "panel-vrr-bridge",
#endif
};
static panel_thread_fn panel_thread_fns[PANEL_THREAD_MAX] = {
#ifdef CONFIG_PANEL_VRR_BRIDGE
[PANEL_THREAD_VRR_BRIDGE] = panel_vrr_bridge_thread,
#endif
};
/* panel gpio */
static char *panel_gpio_names[PANEL_GPIO_MAX] = {
[PANEL_GPIO_RESET] = PANEL_GPIO_NAME_RESET,
[PANEL_GPIO_DISP_DET] = PANEL_GPIO_NAME_DISP_DET,
[PANEL_GPIO_PCD] = PANEL_GPIO_NAME_PCD,
[PANEL_GPIO_ERR_FG] = PANEL_GPIO_NAME_ERR_FG,
[PANEL_GPIO_CONN_DET] = PANEL_GPIO_NAME_CONN_DET,
[PANEL_GPIO_DISP_TE] = PANEL_GPIO_NAME_DISP_TE,
};
static int boot_panel_id;
#if defined(CONFIG_UML)
/* suppress normal log because of exception with too much log in UML */
int panel_log_level = 3;
#else
int panel_log_level = 6;
#endif
EXPORT_SYMBOL(panel_log_level);
module_param(panel_log_level, int, 0600);
int panel_cmd_log;
EXPORT_SYMBOL(panel_cmd_log);
#ifdef CONFIG_SUPPORT_PANEL_SWAP
int panel_reprobe(struct panel_device *panel);
#endif
inline char *get_panel_state_names(enum panel_active_state idx)
{
if (idx < MAX_PANEL_STATE)
return panel_state_names[idx];
return NULL;
}
static struct panel_device *panel_get_panel_device(void)
{
struct panel_device *panel;
struct platform_device *pdev;
struct device_node *np;
np = of_find_compatible_node(NULL, NULL, "samsung,panel-drv");
if (!np) {
panel_err("compatible(\"samsung,panel-drv\") node not found\n");
return NULL;
}
pdev = of_find_device_by_node(np);
of_node_put(np);
if (!pdev) {
panel_err("mcd-panel device not found\n");
return NULL;
}
panel = (struct panel_device *)platform_get_drvdata(pdev);
if (!panel) {
panel_err("failed to get panel_device\n");
return NULL;
}
return panel;
}
int panel_find_max_brightness_from_cpi(struct common_panel_info *info)
{
struct panel_dt_lut *lut_info = NULL;
int max_brightness = 0;
struct panel_device *panel;
panel = panel_get_panel_device();
if (!panel)
return -EINVAL;
if (!info)
return -EINVAL;
lut_info = find_panel_lut(panel, boot_panel_id);
if (IS_ERR_OR_NULL(lut_info)) {
panel_err("failed to find panel lookup table\n");
return -EINVAL;
}
if (strncmp(lut_info->name, info->name, 128))
return 0;
if (!info->panel_dim_info[PANEL_BL_SUBDEV_TYPE_DISP])
return 0;
max_brightness = max_brt_tbl(info->panel_dim_info[PANEL_BL_SUBDEV_TYPE_DISP]->brt_tbl);
if (max_brightness < 0) {
panel_err("failed to get max brightness\n");
return 0;
}
if (!panel->panel_bl.bd) {
panel_err("backlight device is null\n");
return 0;
}
panel->panel_bl.bd->props.max_brightness = max_brightness;
panel_info("%s: max brightness=%d\n", lut_info->name,
panel->panel_bl.bd->props.max_brightness);
return 0;
}
__visible_for_testing int panel_snprintf_bypass(struct panel_device *panel, char *buf, size_t size)
{
if (!panel || !buf || !size)
return 0;
return snprintf(buf, size, "bypass:%s",
panel_bypass_is_on(panel) ? "on" : "off");
}
__visible_for_testing int panel_get_bypass_reason(struct panel_device *panel)
{
if (!panel)
return -EINVAL;
/* bypass:off state */
if (!panel_bypass_is_on(panel))
return BYPASS_REASON_NONE;
if (panel_conn_det_state(panel) == PANEL_STATE_NOK)
return BYPASS_REASON_DISPLAY_CONNECTOR_IS_DISCONNECTED;
if (panel_disp_det_state(panel) == PANEL_STATE_NOK)
return BYPASS_REASON_AVDD_SWIRE_IS_LOW_AFTER_SLPOUT;
if (panel_pcd_state(panel) == PANEL_STATE_NOK)
return BYPASS_REASON_PCD_DETECTED;
if (get_panel_id(panel) == 0)
return BYPASS_REASON_PANEL_ID_READ_FAILURE;
return BYPASS_REASON_NONE;
}
__visible_for_testing const char * const panel_get_bypass_reason_str(struct panel_device *panel)
{
static const char * const str_bypass_reason[MAX_BYPASS_REASON] = {
[BYPASS_REASON_NONE] = "none",
/* the connection status is checked by 'con-det' pin */
[BYPASS_REASON_DISPLAY_CONNECTOR_IS_DISCONNECTED] = "display connector is disconnected",
/* AVDD_SWIRE(or EL_ON1) status is checked by 'disp-det' pin */
[BYPASS_REASON_AVDD_SWIRE_IS_LOW_AFTER_SLPOUT] = "AVDD_SWIRE is off after SLPOUT",
/* panel id read failure by control interface(MIPI, SPI, ...) */
[BYPASS_REASON_PANEL_ID_READ_FAILURE] = "panel id read failure",
[BYPASS_REASON_PCD_DETECTED] = "panel crack detected",
};
int reason;
reason = panel_get_bypass_reason(panel);
if (reason < 0)
return NULL;
return str_bypass_reason[reason];
}
__visible_for_testing int panel_snprintf_bypass_reason(struct panel_device *panel, char *buf, size_t size)
{
struct panel_state *state;
int len = 0;
if (!panel || !buf || !size)
return 0;
state = &panel->state;
len = panel_snprintf_bypass(panel, buf, size);
len += snprintf(buf + len, size - len, "(reason:%s)", panel_get_bypass_reason_str(panel));
return len;
}
__visible_for_testing void panel_print_bypass_reason(struct panel_device *panel)
{
char buf[256] = { 0, };
if (!panel)
return;
panel_snprintf_bypass_reason(panel, buf, sizeof(buf));
panel_info("%s\n", buf);
}
#if 0
static int __init get_boot_panel_id(char *arg)
{
get_option(&arg, &boot_panel_id);
panel_info("boot_panel_id : 0x%x\n", boot_panel_id);
return 0;
}
early_param("lcdtype", get_boot_panel_id);
#else
module_param(boot_panel_id, int, S_IRUGO);
#endif
/**
* get_lcd info - get lcd global information.
* @arg: key string of lcd information
*
* if get lcd info successfully, return 0 or positive value.
* if not, return -EINVAL.
*/
int get_lcd_info(char *arg)
{
if (!arg) {
panel_err("invalid arg\n");
return -EINVAL;
}
if (!strncmp(arg, "connected", 9))
return (boot_panel_id < 0) ? 0 : 1;
else if (!strncmp(arg, "id", 2))
return (boot_panel_id < 0) ? 0 : boot_panel_id;
else if (!strncmp(arg, "window_color", 12))
return (boot_panel_id < 0) ? 0 : ((boot_panel_id & 0x0F0000) >> 16);
else
return -EINVAL;
}
EXPORT_SYMBOL(get_lcd_info);
int panel_initialize_regulator(struct panel_device *panel)
{
struct panel_regulator *regulator;
int ret, result = 0;
if (!panel)
return -EINVAL;
list_for_each_entry(regulator, &panel->regulator_list, head) {
ret = panel_regulator_helper_init(regulator);
if (ret < 0) {
panel_err("%s(%s) init failed\n", regulator->node_name, regulator->name);
result = ret;
}
}
return result;
}
struct panel_gpio *panel_gpio_list_find_by_name(struct panel_device *panel, const char *name)
{
struct panel_gpio *gpio;
if (!panel || !name)
return NULL;
list_for_each_entry(gpio, &panel->gpio_list, head) {
if (!strcmp(gpio->name, name))
return gpio;
}
return NULL;
}
__visible_for_testing struct panel_gpio *panel_get_gpio(struct panel_device *panel, enum panel_gpio_lists panel_gpio_id)
{
struct panel_gpio *gp;
if (!panel)
return NULL;
if (panel_gpio_id >= PANEL_GPIO_MAX)
return NULL;
gp = panel_gpio_list_find_by_name(panel, panel_gpio_names[panel_gpio_id]);
if (!gp)
return NULL;
if (!panel_gpio_helper_is_valid(gp))
return NULL;
return gp;
}
__visible_for_testing int panel_get_gpio_value(struct panel_device *panel,
enum panel_gpio_lists panel_gpio_id)
{
return panel_gpio_helper_get_value(panel_get_gpio(panel, panel_gpio_id));
}
__visible_for_testing int panel_set_gpio_value(struct panel_device *panel,
enum panel_gpio_lists panel_gpio_id, int value)
{
return panel_gpio_helper_set_value(panel_get_gpio(panel, panel_gpio_id), value);
}
__visible_for_testing const char *panel_get_gpio_name(struct panel_device *panel,
enum panel_gpio_lists panel_gpio_id)
{
return panel_gpio_helper_get_name(panel_get_gpio(panel, panel_gpio_id));
}
__visible_for_testing bool panel_is_gpio_irq_valid(struct panel_device *panel, enum panel_gpio_lists panel_gpio_id)
{
return panel_gpio_helper_is_irq_valid(panel_get_gpio(panel, panel_gpio_id));
}
inline bool panel_is_gpio_valid(struct panel_device *panel, enum panel_gpio_lists panel_gpio_id)
{
return panel_get_gpio(panel, panel_gpio_id) ? true : false;
}
__visible_for_testing int panel_get_gpio_state(struct panel_device *panel, enum panel_gpio_lists panel_gpio_id)
{
int state;
state = panel_gpio_helper_get_state(panel_get_gpio(panel, panel_gpio_id));
if (state < 0)
return state;
return (state == PANEL_GPIO_NORMAL_STATE) ? PANEL_STATE_OK : PANEL_STATE_NOK;
}
bool panel_is_gpio_irq_enabled(struct panel_device *panel,
enum panel_gpio_lists panel_gpio_id)
{
if (!panel_is_gpio_valid(panel, panel_gpio_id))
return false;
return panel_gpio_helper_is_irq_enabled(panel_get_gpio(panel, panel_gpio_id));
}
int panel_enable_gpio_irq(struct panel_device *panel,
enum panel_gpio_lists panel_gpio_id)
{
struct panel_gpio *gpio;
int ret;
if (!panel_is_gpio_valid(panel, panel_gpio_id))
return -EINVAL;
gpio = panel_get_gpio(panel, panel_gpio_id);
if (!gpio)
return -EINVAL;
/* clear pending bit */
ret = panel_gpio_helper_clear_irq_pending_bit(gpio);
if (ret < 0)
return -EINVAL;
return panel_gpio_helper_enable_irq(gpio);
}
int panel_disable_gpio_irq(struct panel_device *panel,
enum panel_gpio_lists panel_gpio_id)
{
if (!panel_is_gpio_valid(panel, panel_gpio_id))
return -EINVAL;
return panel_gpio_helper_disable_irq(panel_get_gpio(panel, panel_gpio_id));
}
int panel_poll_gpio(struct panel_device *panel,
enum panel_gpio_lists panel_gpio_id, bool expect_level,
unsigned long sleep_us, unsigned long timeout_us)
{
ktime_t timeout = ktime_add_us(ktime_get(), timeout_us);
int level;
if (!panel_is_gpio_valid(panel, panel_gpio_id)) {
panel_err("invalid gpio(%s)\n", panel_gpio_names[panel_gpio_id]);
return -EINVAL;
}
for (;;) {
level = panel_get_gpio_value(panel, panel_gpio_id);
if (level == expect_level)
break;
if (ktime_after(ktime_get(), timeout))
break;
if (sleep_us)
usleep_range((sleep_us >> 2) + 1, sleep_us);
}
return (level == expect_level) ? 0 : -ETIMEDOUT;
}
int panel_enable_disp_det_irq(struct panel_device *panel)
{
#ifndef CONFIG_EVASION_DISP_DET
int ret;
#endif
if (!panel)
return -EINVAL;
#ifdef CONFIG_EVASION_DISP_DET
cancel_delayed_work_sync(&panel->work[PANEL_WORK_EVASION_DISP_DET].dwork);
queue_delayed_work(panel->work[PANEL_WORK_EVASION_DISP_DET].wq,
&panel->work[PANEL_WORK_EVASION_DISP_DET].dwork, msecs_to_jiffies(EVASION_DISP_DET_DELAY_MSEC));
panel_info("disp_det_irq queued\n");
#else
ret = panel_enable_gpio_irq(panel, PANEL_GPIO_DISP_DET);
if (ret < 0)
panel_warn("do not support irq\n");
#endif
return 0;
}
int panel_disable_disp_det_irq(struct panel_device *panel)
{
int ret;
if (!panel)
return -EINVAL;
#ifdef CONFIG_EVASION_DISP_DET
cancel_delayed_work_sync(&panel->work[PANEL_WORK_EVASION_DISP_DET].dwork);
mutex_lock(&panel->work[PANEL_WORK_EVASION_DISP_DET].lock);
#endif
ret = panel_disable_gpio_irq(panel, PANEL_GPIO_DISP_DET);
if (ret < 0)
panel_warn("do not support irq\n");
#ifdef CONFIG_EVASION_DISP_DET
mutex_unlock(&panel->work[PANEL_WORK_EVASION_DISP_DET].lock);
#endif
panel_info("disp_det_irq cleared\n");
return 0;
}
int panel_enable_pcd_irq(struct panel_device *panel)
{
int ret;
if (!panel)
return -EINVAL;
ret = panel_enable_gpio_irq(panel, PANEL_GPIO_PCD);
if (ret < 0)
panel_warn("do not support irq\n");
return 0;
}
int panel_disable_pcd_irq(struct panel_device *panel)
{
int ret;
if (!panel)
return -EINVAL;
ret = panel_disable_gpio_irq(panel, PANEL_GPIO_PCD);
if (ret < 0)
panel_warn("do not support irq\n");
panel_info("pcd_irq cleared\n");
return 0;
}
struct panel_power_ctrl *panel_power_control_find_sequence(struct panel_device *panel,
const char *dev_name, const char *name)
{
struct panel_power_ctrl *pctrl;
if (!panel || !dev_name || !name)
return ERR_PTR(-EINVAL);
panel_dbg("find: %s, %s\n", dev_name, name);
list_for_each_entry(pctrl, &panel->power_ctrl_list, head) {
if (!pctrl->dev_name) {
panel_err("invalid pctrl->dev_name, skip to check\n");
continue;
}
if (!pctrl->name) {
panel_err("invalid pctrl->name, skip to check\n");
continue;
}
if (!strcmp(pctrl->dev_name, dev_name) && !strcmp(pctrl->name, name))
return pctrl;
}
return ERR_PTR(-ENODATA);
}
bool panel_power_control_exists(struct panel_device *panel, const char *name)
{
struct panel_power_ctrl *pctrl;
if (!panel || !name) {
panel_err("invalid arg");
return false;
}
pctrl = panel_power_control_find_sequence(panel, panel->of_node_name, name);
if (IS_ERR_OR_NULL(pctrl)) {
if (PTR_ERR(pctrl) == -ENODEV)
panel_dbg("not found %s\n", name);
else
panel_err("error occurred when find %s, %ld\n", name, PTR_ERR(pctrl));
return false;
}
return true;
}
int panel_power_control_execute(struct panel_device *panel, const char *name)
{
struct panel_power_ctrl *pctrl;
if (!panel || !name) {
panel_err("invalid arg");
return -EINVAL;
}
pctrl = panel_power_control_find_sequence(panel, panel->of_node_name, name);
if (IS_ERR_OR_NULL(pctrl)) {
if (PTR_ERR(pctrl) == -ENODATA) {
panel_dbg("%s not found\n", name);
return -ENODATA;
}
panel_err("error occurred when find %s, %ld\n", name, PTR_ERR(pctrl));
return PTR_ERR(pctrl);
}
return panel_power_ctrl_helper_execute(pctrl);
}
int panel_disp_det_state(struct panel_device *panel)
{
int state;
state = panel_get_gpio_state(panel, PANEL_GPIO_DISP_DET);
if (state >= 0)
panel_dbg("disp_det:%s\n", state ? "EL-OFF" : "EL-ON");
return state;
}
#ifdef CONFIG_SUPPORT_ERRFG_RECOVERY
int panel_err_fg_state(struct panel_device *panel)
{
int state;
state = panel_get_gpio_state(panel, PANEL_GPIO_ERR_FG);
if (state >= 0)
panel_info("err_fg:%s\n", state ? "ERR_FG OK" : "ERR_FG NOK");
return state;
}
#endif
int panel_pcd_state(struct panel_device *panel)
{
int state;
state = panel_get_gpio_state(panel, PANEL_GPIO_PCD);
if (state >= 0)
panel_dbg("pcd:%s\n", state ? "OK" : "NOK");
return state;
}
int panel_conn_det_state(struct panel_device *panel)
{
int state;
state = panel_get_gpio_state(panel, PANEL_GPIO_CONN_DET);
if (state >= 0)
panel_dbg("conn_det:%s\n", state ? "connected" : "disconnected");
return state;
}
bool panel_disconnected(struct panel_device *panel)
{
int state;
state = panel_conn_det_state(panel);
if (state < 0)
return false;
return !state;
}
static void panel_set_cur_state(struct panel_device *panel, enum panel_active_state state)
{
panel->state.cur_state = state;
}
#ifdef CONFIG_PANEL_NOTIFY
static inline void panel_send_ubconn_notify(u32 state)
{
struct panel_ub_con_event_data data;
data.state = state;
panel_notifier_call_chain(PANEL_EVENT_UB_CON_CHANGED, &data);
panel_info("call EVENT_UB_CON notifier %d\n", data.state);
}
void panel_send_screen_mode_notify(int display_idx, u32 mode)
{
struct panel_screen_mode_data data;
data.display_idx = display_idx;
data.mode = mode;
panel_notifier_call_chain(PANEL_EVENT_SCREEN_MODE_CHANGED, &data);
panel_info("call EVENT_SCREEN_MODE_CHANGED notifier %d %d\n", data.display_idx, data.mode);
}
#endif
#ifdef CONFIG_SUPPORT_MAFPC
static int cmd_v4l2_mafpc_dev(struct panel_device *panel, int cmd, void *param)
{
int ret = 0;
if (panel->mafpc_sd) {
ret = v4l2_subdev_call(panel->mafpc_sd, core, ioctl, cmd, param);
if (ret)
panel_err("failed to v4l2 subdev call\n");
}
return ret;
}
int panel_get_v4l2_abc_dev(struct panel_device *panel, struct mafpc_info *info)
{
int ret;
struct platform_device *pdev;
struct device_node *np;
struct mafpc_device *mafpc;
if ((!panel) || (!info)) {
panel_err("MCD:ABC: null\n");
return -EINVAL;
}
np = of_find_compatible_node(NULL, NULL, "samsung,panel-mafpc");
if (!np) {
panel_err("compatible(\"samsung,panel-mafpc\") node not found\n");
return -ENOENT;
}
pdev = of_find_device_by_node(np);
of_node_put(np);
if (!pdev) {
panel_err("mafpc device not found\n");
return -ENODEV;
}
mafpc = (struct mafpc_device *)platform_get_drvdata(pdev);
if (!mafpc) {
panel_err("failed to get mafpc device\n");
return -ENODEV;
}
panel->mafpc_sd = &mafpc->sd;
mafpc->panel = panel;
ret = cmd_v4l2_mafpc_dev(panel, V4L2_IOCTL_PROBE_ABC, (void *)info);
if (unlikely(ret < 0)) {
panel_err("failed to v4l2 mafpc probe\n");
return -ENODEV;
}
return 0;
}
#endif
static void panel_init_clock_info(struct panel_device *panel)
{
struct panel_properties *props;
struct ddi_properties *ddi_props;
if (!panel)
return;
props = &panel->panel_data.props;
ddi_props = &panel->panel_data.ddi_props;
props->dsi_freq = ddi_props->dft_dsi_freq;
props->osc_freq = ddi_props->dft_osc_freq;
}
int __set_panel_power(struct panel_device *panel, int power)
{
int ret = 0;
if (panel->state.power == power) {
panel_warn("same status.. skip..\n");
return 0;
}
if (power == PANEL_POWER_ON) {
ret = panel_power_control_execute(panel, "panel_power_on");
if (ret < 0)
panel_warn("failed to execute panel_power_on, ret:%d\n", ret);
} else {
ret = panel_power_control_execute(panel, "panel_power_off");
if (ret < 0)
panel_warn("failed to execute panel_power_off, ret:%d\n", ret);
}
panel_info("power(%s)\n", power == PANEL_POWER_ON ? "on" : "off");
panel->state.power = power;
return 0;
}
static int __panel_seq_display_on(struct panel_device *panel)
{
int ret;
panel_info("PANEL_DISPLAY_ON_SEQ\n");
ret = panel_do_seqtbl_by_index(panel, PANEL_DISPLAY_ON_SEQ);
if (unlikely(ret < 0)) {
panel_err("failed to seqtbl(PANEL_DISPLAY_ON_SEQ)\n");
return ret;
}
return 0;
}
static int __panel_seq_display_off(struct panel_device *panel)
{
int ret;
ret = panel_do_seqtbl_by_index(panel, PANEL_DISPLAY_OFF_SEQ);
if (unlikely(ret < 0)) {
panel_err("failed to seqtbl(PANEL_DISPLAY_OFF_SEQ)\n");
return ret;
}
return 0;
}
static int __panel_seq_res_init(struct panel_device *panel)
{
int ret;
ret = panel_do_seqtbl_by_index(panel, PANEL_RES_INIT_SEQ);
if (unlikely(ret < 0)) {
panel_err("failed to seqtbl(PANEL_RES_INIT_SEQ)\n");
return ret;
}
#ifdef CONFIG_SUPPORT_GM2_FLASH
ret = panel_do_seqtbl_by_index(panel, PANEL_GM2_FLASH_RES_INIT_SEQ);
if (unlikely(ret < 0)) {
panel_err("failed to seqtbl(PANEL_GM2_FLASH_RES_INIT_SEQ)\n");
return ret;
}
#endif
return 0;
}
static int __panel_seq_boot(struct panel_device *panel)
{
int ret;
if (!check_seqtbl_exist(&panel->panel_data, PANEL_BOOT_SEQ))
return 0;
ret = panel_do_seqtbl_by_index(panel, PANEL_BOOT_SEQ);
if (unlikely(ret < 0)) {
panel_err("failed to seqtbl(PANEL_BOOT_SEQ)\n");
return ret;
}
return 0;
}
#ifdef CONFIG_SUPPORT_DIM_FLASH
static int __panel_seq_dim_flash_res_init(struct panel_device *panel)
{
int ret;
ret = panel_do_seqtbl_by_index(panel, PANEL_DIM_FLASH_RES_INIT_SEQ);
if (unlikely(ret < 0)) {
panel_err("failed to seqtbl(PANEL_DIM_FLASH_RES_INIT_SEQ)\n");
return ret;
}
return 0;
}
#endif
static int __panel_seq_init(struct panel_device *panel)
{
int ret = 0;
int retry = 20;
s64 time_diff;
ktime_t timestamp = ktime_get();
struct panel_bl_device *panel_bl = &panel->panel_bl;
#if !defined(CONFIG_UML)
if (panel_disp_det_state(panel) == PANEL_STATE_OK) {
panel_warn("panel already initialized\n");
return 0;
}
#endif
mutex_lock(&panel_bl->lock);
mutex_lock(&panel->op_lock);
ret = panel_power_control_execute(panel, "panel_power_init");
if (ret < 0 && ret != -ENODATA)
panel_err("failed to panel_power_init\n");
mutex_unlock(&panel->op_lock);
mutex_unlock(&panel_bl->lock);
#ifdef CONFIG_SUPPORT_PANEL_SWAP
if (panel->panel_data.ddi_props.init_seq_by_lpdt)
panel_dsi_set_lpdt(panel, true);
ret = panel_reprobe(panel);
if (ret < 0) {
panel_err("failed to panel_reprobe\n");
return ret;
}
if (panel->panel_data.ddi_props.init_seq_by_lpdt)
panel_dsi_set_lpdt(panel, false);
#endif
mutex_lock(&panel_bl->lock);
mutex_lock(&panel->op_lock);
panel_bl_set_subdev(panel_bl, PANEL_BL_SUBDEV_TYPE_DISP);
if (panel->panel_data.ddi_props.init_seq_by_lpdt)
panel_dsi_set_lpdt(panel, true);
ret = panel_ddi_init(panel);
if (ret < 0 && ret != -ENOENT) {
panel_err("failed to panel_ddi_init\n");
}
ret = panel_do_seqtbl_by_index_nolock(panel, PANEL_INIT_SEQ);
if (panel->panel_data.ddi_props.init_seq_by_lpdt)
panel_dsi_set_lpdt(panel, false);
if (unlikely(ret < 0)) {
panel_err("failed to write init seqtbl\n");
goto err_init_seq;
}
#ifdef CONFIG_SUPPORT_MAFPC
cmd_v4l2_mafpc_dev(panel, V4L2_IOCTL_MAFPC_PANEL_INIT, NULL);
#endif
time_diff = ktime_to_us(ktime_sub(ktime_get(), timestamp));
panel_info("Time for Panel Init : %llu\n", time_diff);
mutex_unlock(&panel->op_lock);
check_disp_det:
if (panel_disp_det_state(panel) == PANEL_STATE_NOK) {
usleep_range(1000, 1000 + 10);
if (--retry >= 0)
goto check_disp_det;
panel_err("check disp det .. not ok\n");
mutex_unlock(&panel_bl->lock);
return -EAGAIN;
}
retry = 20;
check_pcd:
if (panel_pcd_state(panel) == PANEL_STATE_NOK) {
usleep_range(1000, 1000 + 10);
if (--retry >= 0)
goto check_pcd;
panel_dsi_set_bypass(panel, true); /* ignore TE checking & frame update request*/
panel_err("check pcd .. not ok\n");
mutex_unlock(&panel_bl->lock);
return -EAGAIN;
}
time_diff = ktime_to_us(ktime_sub(ktime_get(), timestamp));
panel_info("check disp det .. success %llu\n", time_diff);
ret = panel_power_control_execute(panel, "panel_fd_enable");
if (ret < 0 && ret != -ENODATA)
panel_err("failed to panel_fd_enable\n");
panel_enable_disp_det_irq(panel);
panel_enable_pcd_irq(panel);
#ifdef CONFIG_EXTEND_LIVE_CLOCK
ret = panel_aod_init_panel(panel, INIT_WITH_LOCK);
if (ret)
panel_err("failed to aod init_panel\n");
#endif
panel_bl_set_saved_flag(panel_bl, false);
mutex_unlock(&panel_bl->lock);
return 0;
err_init_seq:
mutex_unlock(&panel->op_lock);
mutex_unlock(&panel_bl->lock);
return -EAGAIN;
}
static int __panel_seq_exit(struct panel_device *panel)
{
int ret;
struct panel_bl_device *panel_bl = &panel->panel_bl;
ret = panel_disable_disp_det_irq(panel);
if (ret < 0)
panel_err("failed to disable disp_det irq\n");
ret = panel_disable_pcd_irq(panel);
if (ret < 0)
panel_err("failed to disable pcd irq\n");
mutex_lock(&panel_bl->lock);
mutex_lock(&panel->op_lock);
panel_bl_set_subdev(panel_bl, PANEL_BL_SUBDEV_TYPE_DISP);
ret = panel_do_seqtbl_by_index_nolock(panel, PANEL_EXIT_SEQ);
if (unlikely(ret < 0))
panel_err("failed to write exit seqtbl\n");
#ifdef CONFIG_SUPPORT_MAFPC
cmd_v4l2_mafpc_dev(panel, V4L2_IOCTL_MAFPC_PANEL_EXIT, NULL);
#endif
mutex_unlock(&panel->op_lock);
mutex_unlock(&panel_bl->lock);
return ret;
}
#ifdef CONFIG_SUPPORT_HMD
static int __panel_seq_hmd_on(struct panel_device *panel)
{
int ret = 0;
struct panel_state *state;
if (!panel) {
panel_err("pane is null\n");
return 0;
}
state = &panel->state;
panel_info("hmd was on, setting hmd on seq\n");
ret = panel_do_seqtbl_by_index(panel, PANEL_HMD_ON_SEQ);
if (ret)
panel_err("failed to set hmd on seq\n");
return ret;
}
static int panel_set_hmd_on(struct panel_device *panel)
{
int ret = 0;
struct panel_state *state;
if (!panel) {
panel_err("pane is null\n");
return 0;
}
state = &panel->state;
if (state->hmd_on != PANEL_HMD_ON)
return ret;
panel_info("hmd was on, setting hmd on seq\n");
ret = __panel_seq_hmd_on(panel);
if (ret)
panel_err("failed to set hmd on seq\n");
ret = panel_bl_set_brightness(&panel->panel_bl,
PANEL_BL_SUBDEV_TYPE_HMD, SEND_CMD);
if (ret)
panel_err("fail to set hmd brightness\n");
return ret;
}
#endif
#ifdef CONFIG_MCD_PANEL_LPM
static int __panel_seq_exit_alpm(struct panel_device *panel)
{
int ret = 0;
struct panel_bl_device *panel_bl = &panel->panel_bl;
panel_info("was called\n");
ret = panel_power_control_execute(panel, "panel_power_exit_alpm_pre");
if (ret < 0 && ret != -ENODATA)
panel_err("failed to panel_power_exit_alpm_pre\n");
#ifdef CONFIG_EXTEND_LIVE_CLOCK
ret = panel_aod_exit_from_lpm(panel);
if (ret)
panel_err("failed to exit_lpm ops\n");
#endif
mutex_lock(&panel_bl->lock);
mutex_lock(&panel->op_lock);
ret = panel_disable_disp_det_irq(panel);
if (ret < 0)
panel_err("failed to disable disp_det irq\n");
ret = panel_power_control_execute(panel, "panel_power_exit_alpm");
if (ret < 0 && ret != -ENODATA)
panel_err("failed to panel_power_exit_alpm\n");
ret = panel_do_seqtbl_by_index_nolock(panel, PANEL_ALPM_EXIT_SEQ);
if (ret)
panel_err("failed to alpm-exit\n");
panel->panel_data.props.lpm_brightness = -1;
panel_bl_set_subdev(panel_bl, PANEL_BL_SUBDEV_TYPE_DISP);
if (panel->panel_data.props.panel_partial_disp != -1)
panel->panel_data.props.panel_partial_disp = 0;
/* PANEL_ALPM_EXIT_AFTER_SEQ temporary added */
if (check_seqtbl_exist(&panel->panel_data, PANEL_ALPM_EXIT_AFTER_SEQ)) {
panel_bl_set_brightness(&panel->panel_bl, PANEL_BL_SUBDEV_TYPE_DISP, SKIP_CMD);
ret = panel_do_seqtbl_by_index_nolock(panel, PANEL_ALPM_EXIT_AFTER_SEQ);
if (ret)
panel_err("failed to alpm-exit-after seq\n");
}
mutex_unlock(&panel->op_lock);
mutex_unlock(&panel_bl->lock);
/* if PANEL_ALPM_EXIT_AFTER_SEQ is not exists, update brightness by panel_drv. */
if (!check_seqtbl_exist(&panel->panel_data, PANEL_ALPM_EXIT_AFTER_SEQ))
panel_update_brightness(panel);
panel_enable_disp_det_irq(panel);
return ret;
}
#ifdef CONFIG_MCD_PANEL_FACTORY
inline int panel_seq_exit_alpm(struct panel_device *panel)
{
return __panel_seq_exit_alpm(panel);
}
#endif
/* delay to prevent current leackage when alpm */
/* according to ha6 opmanual, the dealy value is 126msec */
static void __delay_normal_alpm(struct panel_device *panel)
{
u32 gap;
u32 delay = 0;
struct seqinfo *seqtbl;
struct delayinfo *delaycmd;
if (!check_seqtbl_exist(&panel->panel_data, PANEL_ALPM_DELAY_SEQ))
goto exit_delay;
seqtbl = find_index_seqtbl(&panel->panel_data, PANEL_ALPM_DELAY_SEQ);
if (unlikely(!seqtbl))
goto exit_delay;
delaycmd = (struct delayinfo *)seqtbl->cmdtbl[0];
if (delaycmd->type != CMD_TYPE_DELAY) {
panel_err("can't find value\n");
goto exit_delay;
}
if (ktime_after(ktime_get(), panel->ktime_panel_on)) {
gap = ktime_to_us(ktime_sub(ktime_get(), panel->ktime_panel_on));
if (gap > delaycmd->usec)
goto exit_delay;
delay = delaycmd->usec - gap;
usleep_range(delay, delay + 10);
}
panel_info("total elapsed time : %d\n",
(int)ktime_to_us(ktime_sub(ktime_get(), panel->ktime_panel_on)));
exit_delay:
return;
}
static int __panel_seq_set_alpm(struct panel_device *panel)
{
int ret;
struct panel_bl_device *panel_bl = &panel->panel_bl;
panel_info("%s was called\n", __func__);
__delay_normal_alpm(panel);
mutex_lock(&panel_bl->lock);
mutex_lock(&panel->op_lock);
ret = panel_disable_disp_det_irq(panel);
if (ret < 0)
panel_err("failed to disable disp_det irq\n");
if (check_seqtbl_exist(&panel->panel_data, PANEL_ALPM_INIT_SEQ)) {
ret = panel_do_seqtbl_by_index_nolock(panel, PANEL_ALPM_INIT_SEQ);
if (ret)
panel_err("failed to alpm-init\n");
}
ret = panel_power_control_execute(panel, "panel_power_enter_alpm");
if (ret < 0 && ret != -ENODATA)
panel_err("failed to panel_power_enter_alpm\n");
#ifdef CONFIG_SUPPORT_AOD_BL
panel_bl_set_subdev(panel_bl, PANEL_BL_SUBDEV_TYPE_AOD);
#endif
ret = panel_do_seqtbl_by_index_nolock(panel, PANEL_ALPM_SET_BL_SEQ);
if (ret)
panel_err("failed to alpm-set-bl\n");
mutex_unlock(&panel->op_lock);
mutex_unlock(&panel_bl->lock);
#ifdef CONFIG_EXTEND_LIVE_CLOCK
ret = panel_aod_enter_to_lpm(panel);
if (ret) {
panel_err("failed to enter to lpm\n");
return ret;
}
#endif
panel_enable_disp_det_irq(panel);
return 0;
}
#ifdef CONFIG_MCD_PANEL_FACTORY
inline int panel_seq_set_alpm(struct panel_device *panel)
{
return __panel_seq_set_alpm(panel);
}
#endif
#endif
static int __panel_seq_dump(struct panel_device *panel)
{
int ret;
ret = panel_do_seqtbl_by_index(panel, PANEL_DUMP_SEQ);
if (unlikely(ret < 0))
panel_err("failed to write dump seqtbl\n");
return ret;
}
static int panel_debug_dump(struct panel_device *panel)
{
int ret;
if (unlikely(!panel)) {
panel_err("panel is null\n");
goto dump_exit;
}
if (!IS_PANEL_ACTIVE(panel)) {
panel_info("Current state:%d\n", panel->state.cur_state);
goto dump_exit;
}
ret = __panel_seq_dump(panel);
if (ret) {
panel_err("failed to dump\n");
return ret;
}
panel_info("disp_det_state:%s\n",
panel_disp_det_state(panel) == PANEL_STATE_OK ? "OK" : "NOK");
dump_exit:
return 0;
}
#ifdef CONFIG_SUPPORT_DDI_CMDLOG
int panel_seq_cmdlog_dump(struct panel_device *panel)
{
int ret;
ret = panel_do_seqtbl_by_index_nolock(panel, PANEL_CMDLOG_DUMP_SEQ);
if (unlikely(ret < 0))
panel_err("failed to write cmdlog dump seqtbl\n");
return ret;
}
#endif
int panel_display_on(struct panel_device *panel)
{
int ret = 0;
struct panel_state *state;
struct panel_bl_device *panel_bl;
if (!panel)
return -EINVAL;
state = &panel->state;
panel_bl = &panel->panel_bl;
if (panel_bypass_is_on(panel)) {
panel_print_bypass_reason(panel);
goto do_exit;
}
if (state->cur_state == PANEL_STATE_OFF ||
state->cur_state == PANEL_STATE_ON) {
panel_warn("invalid state\n");
goto do_exit;
}
#ifdef CONFIG_EXYNOS_DECON_MDNIE_LITE
mdnie_enable(&panel->mdnie);
#endif
#ifdef CONFIG_EXTEND_LIVE_CLOCK
// Transmit Black Frame
if (panel->state.cur_state == PANEL_STATE_ALPM) {
ret = panel_aod_black_grid_on(panel);
if (ret)
panel_info("PANEL_ERR:failed to black grid on\n");
}
#endif
ret = __panel_seq_display_on(panel);
if (ret) {
panel_err("failed to display on\n");
return ret;
}
state->disp_on = PANEL_DISPLAY_ON;
#ifdef CONFIG_EXTEND_LIVE_CLOCK
if (panel->state.cur_state == PANEL_STATE_ALPM) {
usleep_range(33400, 33500);
ret = panel_aod_black_grid_off(panel);
if (ret)
panel_info("PANEL_ERR:failed to black grid on\n");
}
#endif
#ifdef CONFIG_EXYNOS_DECON_LCD_COPR
copr_enable(&panel->copr);
#endif
/*
* update brightness if saved brightness exists.
* W/A for 'set_brightness' is called when init-seq is running but
* panel state is 'PANEL_STATE_ON'.
*/
if (panel_bl_get_saved_flag(panel_bl))
panel_update_brightness(panel);
return 0;
do_exit:
return ret;
}
__visible_for_testing int panel_display_off(struct panel_device *panel)
{
int ret = 0;
struct panel_state *state;
if (!panel)
return -EINVAL;
state = &panel->state;
if (panel_bypass_is_on(panel)) {
panel_print_bypass_reason(panel);
goto do_exit;
}
if (state->cur_state == PANEL_STATE_OFF ||
state->cur_state == PANEL_STATE_ON) {
panel_warn("invalid state\n");
goto do_exit;
}
ret = __panel_seq_display_off(panel);
if (unlikely(ret < 0))
panel_err("failed to write init seqtbl\n");
state->disp_on = PANEL_DISPLAY_OFF;
return 0;
do_exit:
return ret;
}
static struct common_panel_info *panel_detect(struct panel_device *panel)
{
u8 id[3];
u32 panel_id;
int ret = 0;
struct common_panel_info *info;
struct panel_info *panel_data;
bool detect = true;
if (panel == NULL) {
panel_err("panel is null\n");
return NULL;
}
panel_data = &panel->panel_data;
memset(id, 0, sizeof(id));
#if !defined(CONFIG_SUPPORT_PANEL_SWAP) || IS_ENABLED(CONFIG_UNSUPPORT_INIT_READ)
panel_info("use BL panel id : 0x%06x\n", boot_panel_id);
id[0] = (boot_panel_id >> 16) & 0xFF;
id[1] = (boot_panel_id >> 8) & 0xFF;
id[2] = boot_panel_id & 0xFF;
ret = 0;
detect = true;
#else
#if IS_ENABLED(CONFIG_PANEL_ID_READ_BY_LPDT)
panel_dsi_set_lpdt(panel, true);
#endif
ret = read_panel_id(panel, id);
if (unlikely(ret < 0)) {
panel_err("failed to read id(ret %d)\n", ret);
detect = false;
}
#if IS_ENABLED(CONFIG_PANEL_ID_READ_BY_LPDT)
panel_dsi_set_lpdt(panel, false);
#endif
#endif
panel_id = (id[0] << 16) | (id[1] << 8) | id[2];
memcpy(panel_data->id, id, sizeof(id));
panel_info("panel id : 0x%06x\n", panel_id);
#ifdef CONFIG_SUPPORT_PANEL_SWAP
if ((boot_panel_id >= 0) && (detect == true)) {
boot_panel_id = (id[0] << 16) | (id[1] << 8) | id[2];
panel_info("boot_panel_id : 0x%06x\n", boot_panel_id);
}
#endif
info = find_panel(panel, panel_id);
if (unlikely(!info)) {
panel_err("panel not found (id 0x%06X)\n", panel_id);
return NULL;
}
return info;
}
static int panel_alloc_command_buffer(struct panel_device *panel)
{
int fifo_size;
fifo_size = get_panel_adapter_fifo_size(panel);
if (fifo_size <= 0) {
panel_err("invalid fifo_size(%d)\n", fifo_size);
return -EINVAL;
}
if (fifo_size > SZ_8K) {
panel_warn("fifo_size(%d) is too big\n", fifo_size);
fifo_size = SZ_8K;
}
panel_info("fifo_size %d\n", fifo_size);
panel->cmdbuf = kmalloc(fifo_size, GFP_KERNEL);
if (!panel->cmdbuf)
return -ENOMEM;
return 0;
}
static int panel_prepare(struct panel_device *panel, struct common_panel_info *info)
{
int i;
struct panel_info *panel_data;
if (!panel || !info)
return -EINVAL;
panel_data = &panel->panel_data;
mutex_lock(&panel->op_lock);
if (!panel->cmdbuf) {
int ret;
ret = panel_alloc_command_buffer(panel);
if (ret < 0) {
panel_err("failed to alloc command buffer\n");
mutex_unlock(&panel->op_lock);
return ret;
}
}
panel_data->props.panel_partial_disp =
(info->ddi_props.support_partial_disp) ? 0 : -1;
/* maptbl */
panel_data->maptbl = info->maptbl;
panel_data->nr_maptbl = info->nr_maptbl;
for (i = 0; i < panel_data->nr_maptbl; i++)
panel_data->maptbl[i].pdata = panel;
/* sequence table */
panel_data->seqtbl = info->seqtbl;
panel_data->nr_seqtbl = info->nr_seqtbl;
/* read information table */
panel_data->rditbl = info->rditbl;
panel_data->nr_rditbl = info->nr_rditbl;
/* resource table */
panel_data->restbl = info->restbl;
panel_data->nr_restbl = info->nr_restbl;
for (i = 0; i < panel_data->nr_restbl; i++)
panel_data->restbl[i].state = RES_UNINITIALIZED;
/* dump information */
panel_data->dumpinfo = info->dumpinfo;
panel_data->nr_dumpinfo = info->nr_dumpinfo;
/* dimming information */
for (i = 0; i < MAX_PANEL_BL_SUBDEV; i++)
panel_data->panel_dim_info[i] = info->panel_dim_info[i];
/* ddi properties */
memcpy(&panel_data->ddi_props, &info->ddi_props, sizeof(panel_data->ddi_props));
/* ddi operations */
memcpy(&panel_data->ddi_ops, &info->ddi_ops, sizeof(panel_data->ddi_ops));
/* multi-resolution */
memcpy(&panel_data->mres, &info->mres, sizeof(panel_data->mres));
/* variable-refresh-rate */
panel_data->vrrtbl = info->vrrtbl;
panel_data->nr_vrrtbl = info->nr_vrrtbl;
/* display-mode */
#if defined(CONFIG_PANEL_DISPLAY_MODE)
panel_data->common_panel_modes = info->common_panel_modes;
#endif
/* backlight IC table */
#ifdef CONFIG_MCD_PANEL_BLIC
panel_data->blic_data_tbl = info->blic_data_tbl;
panel_data->nr_blic_data_tbl = info->nr_blic_data_tbl;
#endif
#ifdef CONFIG_MCD_PANEL_RCD
panel_data->rcd_data = info->rcd_data;
#endif
/* panel vendor name */
memcpy(panel_data->vendor, info->vendor,
sizeof(panel_data->vendor));
mutex_unlock(&panel->op_lock);
return 0;
}
static int panel_resource_init(struct panel_device *panel)
{
if (!panel)
return -EINVAL;
__panel_seq_res_init(panel);
return 0;
}
static int panel_boot_on(struct panel_device *panel)
{
if (!panel)
return -EINVAL;
__panel_seq_boot(panel);
return 0;
}
#ifdef CONFIG_SUPPORT_DIM_FLASH
static int panel_dim_flash_resource_init(struct panel_device *panel)
{
return __panel_seq_dim_flash_res_init(panel);
}
#endif
static int panel_maptbl_init(struct panel_device *panel)
{
int i, ret;
struct panel_info *panel_data = &panel->panel_data;
mutex_lock(&panel->op_lock);
for (i = 0; i < panel_data->nr_maptbl; i++) {
ret = maptbl_init(&panel_data->maptbl[i]);
if (ret == -ENODATA)
panel_dbg("empty maptbl(idx:%d)\n", i);
else if (ret < 0)
panel_err("maptbl[%d] init failed\n", i);
}
mutex_unlock(&panel->op_lock);
return 0;
}
/*
* panel_mtp_gamma_check - call gamma_check function defined in ddi.
* Do not use op_lock in the function defined in ddi. A deadlock may occur.
*/
int panel_mtp_gamma_check(struct panel_device *panel)
{
struct ddi_ops *ops = &panel->panel_data.ddi_ops;
if (!ops->mtp_gamma_check) {
panel_warn("not supported");
return -1;
}
return ops->mtp_gamma_check(panel, NULL, 0);
}
/*
* panel_flash_checksum_calc - call gamma_flash_checksum function defined in ddi.
* Do not use op_lock in the function defined in ddi. A deadlock may occur.
*/
int panel_flash_checksum_calc(struct panel_device *panel)
{
struct ddi_ops *ops = &panel->panel_data.ddi_ops;
if (!ops->gamma_flash_checksum) {
panel_warn("not supported");
return 0;
}
return ops->gamma_flash_checksum(panel, &panel->flash_checksum_result, sizeof(panel->flash_checksum_result));
}
#ifdef CONFIG_SUPPORT_SSR_TEST
/*
* panel_ssr_test - call ssr test function defined in ddi.
* Do not use op_lock in the function defined in ddi. A deadlock may occur.
*/
int panel_ssr_test(struct panel_device *panel)
{
struct ddi_ops *ops = &panel->panel_data.ddi_ops;
if (!ops->ssr_test) {
panel_warn("not supported");
return -ENOENT;
}
return ops->ssr_test(panel, NULL, 0);
}
#endif
#ifdef CONFIG_SUPPORT_ECC_TEST
/*
* panel_ecc_test - call ssr test function defined in ddi.
* Do not use op_lock in the function defined in ddi. A deadlock may occur.
*/
int panel_ecc_test(struct panel_device *panel)
{
struct ddi_ops *ops = &panel->panel_data.ddi_ops;
if (!ops->ecc_test) {
panel_warn("not supported");
return -ENOENT;
}
return ops->ecc_test(panel, NULL, 0);
}
#endif
/*
* panel_decoder_test - call decoder test function defined in ddi.
* Do not use op_lock in the function defined in ddi. A deadlock may occur.
*/
int panel_decoder_test(struct panel_device *panel, u8 *buf, int len)
{
struct ddi_ops *ops = &panel->panel_data.ddi_ops;
if (!ops->decoder_test) {
panel_warn("not supported");
return -ENOENT;
}
return ops->decoder_test(panel, buf, len);
}
bool check_panel_decoder_test_exists(struct panel_device *panel)
{
struct ddi_ops *ops = &panel->panel_data.ddi_ops;
if (!ops->decoder_test) {
panel_warn("not supported");
return false;
}
return true;
}
#ifdef CONFIG_SUPPORT_PANEL_VCOM_TRIM_TEST
/*
* panel_vcom_trim_test - call vcom_trim test function defined in ddi.
* Do not use op_lock in the function defined in ddi. A deadlock may occur.
*/
int panel_vcom_trim_test(struct panel_device *panel, u8 *buf, int len)
{
struct ddi_ops *ops = &panel->panel_data.ddi_ops;
if (!ops->vcom_trim_test)
return -ENOENT;
return ops->vcom_trim_test(panel, buf, len);
}
#endif
int panel_ddi_init(struct panel_device *panel)
{
struct ddi_ops *ops = &panel->panel_data.ddi_ops;
if (!ops->ddi_init) {
panel_dbg("not supported");
return -ENOENT;
}
return ops->ddi_init(panel, NULL, 0);
}
#ifdef CONFIG_SUPPORT_DIM_FLASH
int panel_update_dim_type(struct panel_device *panel, u32 dim_type)
{
struct dim_flash_result *result;
u8 mtp_reg[64];
int sz_mtp_reg = 0;
int state, state_all = 0;
int index, result_idx = 0;
int ret;
if (dim_type == DIM_TYPE_DIM_FLASH) {
if (!panel->dim_flash_result) {
panel_err("dim buffer not found\n");
return -ENOMEM;
}
memset(panel->dim_flash_result, 0,
sizeof(struct dim_flash_result) * panel->max_nr_dim_flash_result);
for (index = 0; index < panel->max_nr_dim_flash_result; index++) {
if (get_poc_partition_size(&panel->poc_dev,
POC_DIM_PARTITION + index) == 0) {
continue;
}
result = &panel->dim_flash_result[result_idx++];
state = 0;
do {
/* DIM */
ret = set_panel_poc(&panel->poc_dev, POC_OP_DIM_READ, &index);
if (unlikely(ret < 0)) {
panel_err("failed to read gamma flash(ret %d)\n", ret);
state = GAMMA_FLASH_ERROR_READ_FAIL;
break;
}
#if !defined(CONFIG_SEC_PANEL_DIM_FLASH_NO_VALIDATION)
ret = check_poc_partition_exists(&panel->poc_dev,
POC_DIM_PARTITION + index);
if (unlikely(ret < 0)) {
panel_err("failed to check dim_flash exist\n");
state = GAMMA_FLASH_ERROR_READ_FAIL;
break;
}
if (ret == PARTITION_WRITE_CHECK_NOK) {
panel_err("dim partition not exist(%d)\n", ret);
state = GAMMA_FLASH_ERROR_NOT_EXIST;
break;
}
#endif
ret = get_poc_partition_chksum(&panel->poc_dev,
POC_DIM_PARTITION + index,
&result->dim_chksum_ok,
&result->dim_chksum_by_calc,
&result->dim_chksum_by_read);
#if !defined(CONFIG_SEC_PANEL_DIM_FLASH_NO_VALIDATION)
if (unlikely(ret < 0)) {
panel_err("failed to get chksum(ret %d)\n", ret);
state = GAMMA_FLASH_ERROR_READ_FAIL;
break;
}
if (result->dim_chksum_by_calc !=
result->dim_chksum_by_read) {
panel_err("dim flash checksum(%04X,%04X) mismatch\n",
result->dim_chksum_by_calc,
result->dim_chksum_by_read);
state = GAMMA_FLASH_ERROR_CHECKSUM_MISMATCH;
break;
}
#endif
/* MTP */
ret = set_panel_poc(&panel->poc_dev, POC_OP_MTP_READ, &index);
if (unlikely(ret)) {
panel_err("failed to read mtp flash(ret %d)\n", ret);
state = GAMMA_FLASH_ERROR_READ_FAIL;
break;
}
ret = get_poc_partition_chksum(&panel->poc_dev,
POC_MTP_PARTITION + index,
&result->mtp_chksum_ok,
&result->mtp_chksum_by_calc,
&result->mtp_chksum_by_read);
#if !defined(CONFIG_SEC_PANEL_DIM_FLASH_NO_VALIDATION)
if (unlikely(ret < 0)) {
panel_err("failed to get chksum(ret %d)\n", ret);
state = GAMMA_FLASH_ERROR_READ_FAIL;
break;
}
if (result->mtp_chksum_by_calc != result->mtp_chksum_by_read) {
panel_err("mtp flash checksum(%04X,%04X) mismatch\n",
result->mtp_chksum_by_calc, result->mtp_chksum_by_read);
state = GAMMA_FLASH_ERROR_MTP_OFFSET;
break;
}
#endif
ret = get_resource_size_by_name(&panel->panel_data, "mtp");
if (unlikely(ret < 0)) {
panel_err("failed to get resource mtp size (ret %d)\n", ret);
state = GAMMA_FLASH_ERROR_READ_FAIL;
break;
}
sz_mtp_reg = ret;
ret = resource_copy_by_name(&panel->panel_data, mtp_reg, "mtp");
if (unlikely(ret < 0)) {
panel_err("failed to copy resource mtp (ret %d)\n", ret);
state = GAMMA_FLASH_ERROR_READ_FAIL;
break;
}
#if !defined(CONFIG_SEC_PANEL_DIM_FLASH_NO_VALIDATION)
if (cmp_poc_partition_data(&panel->poc_dev,
POC_MTP_PARTITION + index, 0, mtp_reg, sz_mtp_reg)) {
panel_err("mismatch mtp(ret %d)\n", ret);
state = GAMMA_FLASH_ERROR_MTP_OFFSET;
break;
}
#endif
result->mtp_chksum_by_reg = calc_checksum_16bit(mtp_reg, sz_mtp_reg);
} while (0);
if (state_all == 0)
state_all = state;
if (state == 0)
result->state = GAMMA_FLASH_SUCCESS;
else
result->state = state;
}
panel->nr_dim_flash_result = result_idx;
if (state_all != 0)
return state_all;
/* update dimming flash, mtp, hbm_gamma resources */
ret = panel_dim_flash_resource_init(panel);
if (unlikely(ret)) {
panel_err("failed to resource init\n");
state_all = GAMMA_FLASH_ERROR_READ_FAIL;
}
}
mutex_lock(&panel->op_lock);
panel->panel_data.props.cur_dim_type = dim_type;
mutex_unlock(&panel->op_lock);
ret = panel_maptbl_init(panel);
if (unlikely(ret)) {
panel_err("failed to resource init\n");
state_all = -ENODEV;
}
return state_all;
}
#endif
int panel_reprobe(struct panel_device *panel)
{
struct common_panel_info *info;
int ret;
info = panel_detect(panel);
if (unlikely(!info)) {
panel_err("panel detection failed\n");
return -ENODEV;
}
ret = panel_prepare(panel, info);
if (unlikely(ret)) {
panel_err("failed to panel_prepare\n");
return ret;
}
ret = panel_resource_init(panel);
if (unlikely(ret)) {
panel_err("failed to resource init\n");
return ret;
}
#ifdef CONFIG_SUPPORT_DDI_FLASH
ret = panel_poc_probe(panel, info->poc_data);
if (unlikely(ret)) {
panel_err("failed to probe poc driver\n");
return -ENODEV;
}
#endif /* CONFIG_SUPPORT_DDI_FLASH */
ret = panel_maptbl_init(panel);
if (unlikely(ret)) {
panel_err("failed to maptbl init\n");
return -ENODEV;
}
ret = panel_bl_probe(&panel->panel_bl);
if (unlikely(ret)) {
panel_err("failed to probe backlight driver\n");
return -ENODEV;
}
return 0;
}
#ifdef CONFIG_SUPPORT_DIM_FLASH
static void dim_flash_handler(struct work_struct *work)
{
struct panel_work *w = container_of(to_delayed_work(work),
struct panel_work, dwork);
struct panel_device *panel =
container_of(w, struct panel_device, work[PANEL_WORK_DIM_FLASH]);
int ret;
mutex_lock(&panel->panel_bl.lock);
if (atomic_read(&w->running) >= 2) {
panel_info("already running\n");
mutex_unlock(&panel->panel_bl.lock);
return;
}
atomic_set(&w->running, 2);
panel_info("+\n");
ret = panel_update_dim_type(panel, DIM_TYPE_DIM_FLASH);
if (ret < 0) {
panel_err("failed to update dim_flash %d\n", ret);
w->ret = ret;
} else {
panel_info("update dim_flash done %d\n", ret);
w->ret = ret;
}
panel_info("-\n");
atomic_set(&w->running, 0);
mutex_unlock(&panel->panel_bl.lock);
panel_update_brightness(panel);
}
#endif
void clear_check_wq_var(struct panel_condition_check *pcc)
{
pcc->check_state = NO_CHECK_STATE;
pcc->is_panel_check = false;
pcc->frame_cnt = 0;
}
bool show_copr_value(struct panel_device *panel, int frame_cnt)
{
bool retVal = false;
#ifdef CONFIG_EXYNOS_DECON_LCD_COPR
int ret = 0;
struct copr_info *copr = &panel->copr;
char write_buf[200] = {0, };
int c = 0, i = 0, len = 0;
if (copr_is_enabled(copr)) {
ret = copr_get_value(copr);
if (ret < 0) {
panel_err("failed to get copr\n");
return retVal;
}
len += snprintf(write_buf + len, sizeof(write_buf) - len, "cur:%d avg:%d ",
copr->props.cur_copr, copr->props.avg_copr);
if (copr->props.nr_roi > 0) {
len += snprintf(write_buf + len, sizeof(write_buf) - len, "roi:");
for (i = 0; i < copr->props.nr_roi; i++) {
for (c = 0; c < 3; c++) {
if (sizeof(write_buf) - len > 0) {
len += snprintf(write_buf + len, sizeof(write_buf) - len, "%d%s",
copr->props.copr_roi_r[i][c],
((i == copr->props.nr_roi - 1) && c == 2) ? "\n" : " ");
}
}
}
} else {
len += snprintf(write_buf + len, sizeof(write_buf) - len, "%s", "\n");
}
panel_info("copr(frame_cnt=%d) -> %s", frame_cnt, write_buf);
if (copr->props.cur_copr > 0) /* not black */
retVal = true;
} else {
panel_info("copr do not support\n");
}
#else
panel_info("copr feature is disabled\n");
#endif
return retVal;
}
static void panel_condition_handler(struct work_struct *work)
{
int ret = 0;
struct panel_work *w = container_of(to_delayed_work(work),
struct panel_work, dwork);
struct panel_device *panel =
container_of(w, struct panel_device, work[PANEL_WORK_CHECK_CONDITION]);
struct panel_condition_check *condition = &panel->condition_check;
if (atomic_read(&w->running)) {
panel_info("already running\n");
return;
}
panel_wake_lock(panel, WAKE_TIMEOUT_MSEC);
atomic_set(&w->running, 1);
mutex_lock(&w->lock);
panel_info("%s\n", condition->str_state[condition->check_state]);
switch (condition->check_state) {
case PRINT_NORMAL_PANEL_INFO:
// print rddpm
ret = panel_do_seqtbl_by_index(panel, PANEL_CHECK_CONDITION_SEQ);
if (unlikely(ret < 0))
panel_err("failed to write panel check\n");
if (show_copr_value(panel, condition->frame_cnt))
clear_check_wq_var(condition);
else
condition->check_state = CHECK_NORMAL_PANEL_INFO;
break;
case PRINT_DOZE_PANEL_INFO:
ret = panel_do_seqtbl_by_index(panel, PANEL_CHECK_CONDITION_SEQ);
if (unlikely(ret < 0))
panel_err("failed to write panel check\n");
clear_check_wq_var(condition);
break;
case CHECK_NORMAL_PANEL_INFO:
if (show_copr_value(panel, condition->frame_cnt))
clear_check_wq_var(condition);
break;
default:
panel_info("state %d\n", condition->check_state);
clear_check_wq_var(condition);
break;
}
mutex_unlock(&w->lock);
atomic_set(&w->running, 0);
panel_wake_unlock(panel);
}
int init_check_wq(struct panel_condition_check *condition)
{
clear_check_wq_var(condition);
strcpy(condition->str_state[NO_CHECK_STATE], STR_NO_CHECK);
strcpy(condition->str_state[PRINT_NORMAL_PANEL_INFO], STR_NOMARL_ON);
strcpy(condition->str_state[CHECK_NORMAL_PANEL_INFO], STR_NOMARL_100FRAME);
strcpy(condition->str_state[PRINT_DOZE_PANEL_INFO], STR_AOD_ON);
return 0;
}
void panel_check_ready(struct panel_device *panel)
{
struct panel_condition_check *pcc = &panel->condition_check;
struct panel_work *pw = &panel->work[PANEL_WORK_CHECK_CONDITION];
mutex_lock(&pw->lock);
pcc->is_panel_check = true;
if (panel->state.cur_state == PANEL_STATE_NORMAL)
pcc->check_state = PRINT_NORMAL_PANEL_INFO;
if (panel->state.cur_state == PANEL_STATE_ALPM)
pcc->check_state = PRINT_DOZE_PANEL_INFO;
mutex_unlock(&pw->lock);
}
static void panel_check_start(struct panel_device *panel)
{
struct panel_condition_check *pcc = &panel->condition_check;
struct panel_work *pw = &panel->work[PANEL_WORK_CHECK_CONDITION];
mutex_lock(&pw->lock);
if (pcc->frame_cnt < 100) {
pcc->frame_cnt++;
switch (pcc->check_state) {
case PRINT_NORMAL_PANEL_INFO:
case PRINT_DOZE_PANEL_INFO:
if (pcc->frame_cnt == 1)
queue_delayed_work(pw->wq, &pw->dwork, msecs_to_jiffies(0));
break;
case CHECK_NORMAL_PANEL_INFO:
if (pcc->frame_cnt % 10 == 0)
queue_delayed_work(pw->wq, &pw->dwork, msecs_to_jiffies(0));
break;
case NO_CHECK_STATE:
// do nothing
break;
default:
panel_warn("state is invalid %d %d %d\n",
pcc->is_panel_check, pcc->frame_cnt, pcc->check_state);
clear_check_wq_var(pcc);
break;
}
} else {
if (pcc->check_state == CHECK_NORMAL_PANEL_INFO)
panel_warn("screen is black in first 100 frame %d %d %d\n",
pcc->is_panel_check, pcc->frame_cnt, pcc->check_state);
else
panel_warn("state is invalid %d %d %d\n",
pcc->is_panel_check, pcc->frame_cnt, pcc->check_state);
clear_check_wq_var(pcc);
}
mutex_unlock(&pw->lock);
}
static void panel_update_handler(struct work_struct *work)
{
struct panel_work *w = container_of(to_delayed_work(work),
struct panel_work, dwork);
struct panel_device *panel =
container_of(w, struct panel_device, work[PANEL_WORK_UPDATE]);
struct panel_bl_device *panel_bl = &panel->panel_bl;
struct panel_properties *props = &panel->panel_data.props;
bool vrr_updated = false;
int ret = 0;
mutex_lock(&w->lock);
mutex_lock(&panel_bl->lock);
ret = update_vrr_lfd(&props->vrr_lfd_info);
if (panel_bl->bd && ret == VRR_LFD_UPDATED) {
props->vrr_updated = true;
vrr_updated = true;
}
mutex_unlock(&panel_bl->lock);
if (vrr_updated) {
ret = panel_update_brightness(panel);
if (ret < 0)
panel_err("failed to update vrr & brightness\n");
}
mutex_unlock(&w->lock);
}
#ifdef CONFIG_EVASION_DISP_DET
static void evasion_disp_det_handler(struct work_struct *work)
{
int ret;
struct panel_work *w = container_of(to_delayed_work(work),
struct panel_work, dwork);
struct panel_device *panel =
container_of(w, struct panel_device, work[PANEL_WORK_EVASION_DISP_DET]);
if (panel->state.cur_state == PANEL_STATE_OFF) {
panel_warn("panel is off state\n");
return;
}
mutex_lock(&w->lock);
ret = panel_enable_gpio_irq(panel, PANEL_GPIO_DISP_DET);
if (ret < 0)
panel_warn("do not support irq\n");
mutex_unlock(&w->lock);
panel_info("disp_det_irq enabled\n");
}
#endif
static int panel_init_property(struct panel_device *panel)
{
struct panel_info *panel_data;
int i;
if (!panel)
return -EINVAL;
panel_data = &panel->panel_data;
mutex_lock(&panel->op_lock);
panel_data->props.temperature = NORMAL_TEMPERATURE;
panel_data->props.alpm_mode = ALPM_OFF;
panel_data->props.cur_alpm_mode = ALPM_OFF;
panel_data->props.lpm_opr = 250; /* default LPM OPR 2.5 */
panel_data->props.cur_lpm_opr = 250; /* default LPM OPR 2.5 */
panel_data->props.panel_partial_disp = 0;
panel_data->props.dia_mode = 1;
panel_data->props.irc_mode = 0;
memset(panel_data->props.mcd_rs_range, -1,
sizeof(panel_data->props.mcd_rs_range));
#ifdef CONFIG_SUPPORT_GRAM_CHECKSUM
panel_data->props.gct_on = GRAM_TEST_OFF;
panel_data->props.gct_vddm = VDDM_ORIG;
panel_data->props.gct_pattern = GCT_PATTERN_NONE;
#endif
#ifdef CONFIG_SUPPORT_DYNAMIC_HLPM
panel_data->props.dynamic_hlpm = 0;
#endif
#ifdef CONFIG_SUPPORT_TDMB_TUNE
panel_data->props.tdmb_on = false;
panel_data->props.cur_tdmb_on = false;
#endif
#ifdef CONFIG_SUPPORT_DIM_FLASH
panel_data->props.cur_dim_type = DIM_TYPE_AID_DIMMING;
#endif
for (i = 0; i < MAX_CMD_LEVEL; i++)
panel_data->props.key[i] = 0;
mutex_unlock(&panel->op_lock);
mutex_lock(&panel->panel_bl.lock);
panel_data->props.adaptive_control = 1;
#ifdef CONFIG_SUPPORT_XTALK_MODE
panel_data->props.xtalk_mode = XTALK_OFF;
#endif
panel_data->props.poc_onoff = POC_ONOFF_ON;
mutex_unlock(&panel->panel_bl.lock);
panel_data->props.mres_mode = 0;
panel_data->props.old_mres_mode = 0;
panel_data->props.mres_updated = false;
panel_data->props.ub_con_cnt = 0;
panel_data->props.conn_det_enable = 0;
/* variable refresh rate */
panel_data->props.vrr_fps = 60;
panel_data->props.vrr_mode = VRR_NORMAL_MODE;
panel_data->props.vrr_idx = 0;
panel_data->props.vrr_origin_fps = 60;
panel_data->props.vrr_origin_mode = VRR_NORMAL_MODE;
panel_data->props.vrr_origin_idx = 0;
#ifdef CONFIG_PANEL_VRR_BRIDGE
mutex_lock(&panel->io_lock);
panel_data->props.vrr_bridge_enable = true;
mutex_unlock(&panel->io_lock);
#endif
#if defined(CONFIG_SUPPORT_FAST_DISCHARGE)
panel_data->props.enable_fd = 1;
#endif
#ifdef CONFIG_MCD_PANEL_FACTORY
/*
* set vrr_lfd min/max high frequency to
* disable lfd in factory binary as default.
*/
for (i = 0; i < MAX_VRR_LFD_SCOPE; i++)
panel_data->props.vrr_lfd_info.req[VRR_LFD_CLIENT_FAC][i].fix = VRR_LFD_FREQ_HIGH;
queue_delayed_work(panel->work[PANEL_WORK_UPDATE].wq,
&panel->work[PANEL_WORK_UPDATE].dwork, msecs_to_jiffies(0));
#endif
return 0;
}
__visible_for_testing int panel_create_lcd_class(void)
{
if (lcd_class) {
panel_warn("lcd_class already exist\n");
return 0;
}
lcd_class = class_create(THIS_MODULE, "lcd");
if (IS_ERR_OR_NULL(lcd_class)) {
panel_err("failed to create lcd class\n");
return -EINVAL;
}
return 0;
}
__visible_for_testing int panel_destroy_lcd_class(void)
{
if (!lcd_class)
return -EINVAL;
class_destroy(lcd_class);
lcd_class = NULL;
return 0;
}
__visible_for_testing int panel_create_lcd_device(struct panel_device *panel, unsigned int id)
{
char name[MAX_PANEL_DEV_NAME_SIZE];
if (!lcd_class || !panel)
return -EINVAL;
if (id == 0)
snprintf(name, MAX_PANEL_DEV_NAME_SIZE,
"%s", PANEL_DEV_NAME);
else
snprintf(name, MAX_PANEL_DEV_NAME_SIZE,
"%s-%d", PANEL_DEV_NAME, id);
panel->lcd_dev = device_create(lcd_class,
panel->dev, 0, panel, "%s", name);
if (IS_ERR_OR_NULL(panel->lcd_dev)) {
panel_err("failed to create lcd device\n");
return PTR_ERR(panel->lcd_dev);
}
return 0;
}
__visible_for_testing int panel_destroy_lcd_device(struct panel_device *panel)
{
if (!panel || !panel->lcd_dev)
return -EINVAL;
device_unregister(panel->lcd_dev);
panel->lcd_dev = NULL;
return 0;
}
int panel_probe(struct panel_device *panel)
{
int ret = 0;
struct panel_info *panel_data;
struct common_panel_info *info;
panel_info("+\n");
if (panel == NULL) {
panel_err("panel is null\n");
return -EINVAL;
}
panel_data = &panel->panel_data;
ret = panel_initialize_regulator(panel);
if (ret < 0)
panel_warn("error occurred during initialize regulator\n");
panel_drv_set_regulators(panel);
info = panel_detect(panel);
if (unlikely(!info)) {
panel_err("panel detection failed\n");
return -ENODEV;
}
#ifdef CONFIG_EXYNOS_DECON_LCD_SPI
/*
* TODO : move to parse dt function
* 1. get panel_spi device node.
* 2. get spi_device of node
*/
panel->spi = of_find_panel_spi_by_node(NULL);
if (!panel->spi)
panel_warn("panel spi device unsupported\n");
#endif
ret = panel_init_property(panel);
if (ret < 0) {
panel_err("failed to initialize property\n");
return -EINVAL;
}
ret = panel_prepare(panel, info);
if (unlikely(ret < 0)) {
panel_err("failed to prepare common panel driver\n");
return -ENODEV;
}
#ifdef CONFIG_SUPPORT_POC_SPI
ret = panel_spi_drv_probe(panel, info->spi_data_tbl, info->nr_spi_data_tbl);
if (unlikely(ret))
panel_err("failed to probe panel spi driver\n");
#endif
#ifdef CONFIG_SUPPORT_DDI_FLASH
ret = panel_poc_probe(panel, info->poc_data);
if (unlikely(ret))
panel_err("failed to probe poc driver\n");
#endif /* CONFIG_SUPPORT_DDI_FLASH */
ret = panel_resource_init(panel);
if (unlikely(ret)) {
panel_err("failed to resource init\n");
return -ENODEV;
}
ret = panel_maptbl_init(panel);
if (unlikely(ret)) {
panel_err("failed to resource init\n");
return -ENODEV;
}
ret = panel_bl_probe(&panel->panel_bl);
if (unlikely(ret)) {
panel_err("failed to probe backlight driver\n");
return -ENODEV;
}
ret = panel_boot_on(panel);
if (unlikely(ret)) {
panel_err("failed to panel boot on seq\n");
return -ENODEV;
}
#ifdef CONFIG_EXYNOS_DECON_MDNIE_LITE
ret = mdnie_probe(&panel->mdnie, info->mdnie_tune);
if (unlikely(ret)) {
panel_err("failed to probe mdnie driver\n");
return -ENODEV;
}
#endif
#ifdef CONFIG_EXYNOS_DECON_LCD_COPR
ret = copr_probe(panel, info->copr_data);
if (unlikely(ret)) {
panel_err("failed to probe copr driver\n");
BUG();
return -ENODEV;
}
#endif
#ifdef CONFIG_EXTEND_LIVE_CLOCK
ret = aod_drv_probe(panel, info->aod_tune);
if (unlikely(ret)) {
panel_err("failed to probe aod driver\n");
BUG();
return -ENODEV;
}
#endif
#ifdef CONFIG_SUPPORT_MAFPC
ret = panel_get_v4l2_abc_dev(panel, info->mafpc_info);
if (unlikely(ret < 0)) {
panel_err("failed to probe mafpc driver\n");
return -ENODEV;
}
#endif
#if defined(CONFIG_PANEL_FREQ_HOP)
ret = panel_freq_hop_probe(panel,
info->freq_hop_elems, info->nr_freq_hop_elems);
if (ret)
panel_err("failed to register dynamic mipi module\n");
#endif
panel_init_clock_info(panel);
#ifdef CONFIG_SUPPORT_DIM_FLASH
mutex_lock(&panel->panel_bl.lock);
mutex_lock(&panel->op_lock);
for (i = 0; i < MAX_PANEL_BL_SUBDEV; i++) {
if (panel_data->panel_dim_info[i] == NULL)
continue;
if (panel_data->panel_dim_info[i]->dim_flash_on) {
panel_data->props.dim_flash_on = true;
panel_info("dim_flash : on\n");
break;
}
}
mutex_unlock(&panel->op_lock);
mutex_unlock(&panel->panel_bl.lock);
if (panel_data->props.dim_flash_on)
queue_delayed_work(panel->work[PANEL_WORK_DIM_FLASH].wq,
&panel->work[PANEL_WORK_DIM_FLASH].dwork,
msecs_to_jiffies(500));
#endif /* CONFIG_SUPPORT_DIM_FLASH */
init_check_wq(&panel->condition_check);
#if defined(CONFIG_PANEL_DISPLAY_MODE)
mutex_lock(&panel->op_lock);
ret = panel_display_mode_get_native_mode(panel);
if (ret >= 0) {
panel_data->props.panel_mode = ret;
#if defined(CONFIG_PANEL_VRR_BRIDGE)
panel->panel_data.props.target_panel_mode = ret;
#endif
panel_info("apply default panel_mode %d\n", ret);
} else {
panel_err("failed to update panel_mode\n");
}
mutex_unlock(&panel->op_lock);
panel_update_brightness_cmd_skip(panel);
ret = panel_update_display_mode(panel);
if (ret < 0)
panel_err("failed to panel_update_display_mode\n");
#endif
panel_info("-\n");
return 0;
}
int panel_remove(struct panel_device *panel)
{
int ret;
#ifdef CONFIG_SUPPORT_DIM_FLASH
/* TODO: dim flash need to flush before sleep in */
if (panel->panel_data.props.dim_flash_on)
flush_delayed_work(&panel->work[PANEL_WORK_DIM_FLASH].dwork);
#endif
#if defined(CONFIG_PANEL_FREQ_HOP)
/* TODO: remove dynamic_freq */
#endif
#ifdef CONFIG_EXTEND_LIVE_CLOCK
ret = aod_drv_remove(panel);
if (ret < 0) {
panel_err("failed to remove aod driver\n");
return ret;
}
#endif
#ifdef CONFIG_EXYNOS_DECON_LCD_COPR
ret = copr_remove(panel);
if (ret < 0) {
panel_err("failed to remove copr driver\n");
return ret;
}
#endif
#ifdef CONFIG_EXYNOS_DECON_MDNIE_LITE
ret = mdnie_remove(&panel->mdnie);
if (ret < 0) {
panel_err("failed to remove mdnie driver\n");
return ret;
}
#endif
ret = panel_bl_remove(&panel->panel_bl);
if (ret < 0) {
panel_err("failed to remove panel-bl driver\n");
return ret;
}
#ifdef CONFIG_SUPPORT_DDI_FLASH
ret = panel_poc_remove(panel);
if (ret < 0) {
panel_err("failed to remove poc driver\n");
return ret;
}
#endif /* CONFIG_SUPPORT_DDI_FLASH */
#ifdef CONFIG_SUPPORT_POC_SPI
ret = panel_spi_drv_remove(panel);
if (ret < 0) {
panel_err("failed to remove panel spi driver\n");
return ret;
}
#endif
panel->funcs = NULL;
return 0;
}
__visible_for_testing int panel_sleep_in(struct panel_device *panel)
{
int ret = 0;
struct panel_state *state;
enum panel_active_state prev_state;
ktime_t start;
if (!panel)
return -EINVAL;
if (panel_bypass_is_on(panel)) {
panel_print_bypass_reason(panel);
goto do_exit;
}
state = &panel->state;
prev_state = state->cur_state;
PRINT_PANEL_STATE_BEGIN(prev_state, PANEL_STATE_ON, start);
switch (state->cur_state) {
case PANEL_STATE_ON:
panel_warn("panel already %s state\n", panel_state_names[state->cur_state]);
goto do_exit;
case PANEL_STATE_NORMAL:
case PANEL_STATE_ALPM:
#ifdef CONFIG_EXYNOS_DECON_LCD_COPR
copr_disable(&panel->copr);
#endif
#ifdef CONFIG_EXYNOS_DECON_MDNIE_LITE
mdnie_disable(&panel->mdnie);
#endif
ret = panel_display_off(panel);
if (unlikely(ret < 0))
panel_err("failed to write display_off seqtbl\n");
ret = __panel_seq_exit(panel);
if (unlikely(ret < 0))
panel_err("failed to write exit seqtbl\n");
break;
default:
panel_err("invalid state(%d)\n", state->cur_state);
goto do_exit;
}
panel_set_cur_state(panel, PANEL_STATE_ON);
PRINT_PANEL_STATE_END(prev_state, state->cur_state, start);
return 0;
do_exit:
return ret;
}
__visible_for_testing int panel_power_on(struct panel_device *panel)
{
int ret = 0;
struct panel_state *state;
enum panel_active_state prev_state;
ktime_t start;
if (!panel)
return -EINVAL;
panel->state.connected = panel_conn_det_state(panel);
if (panel->state.connected < 0) {
panel_dbg("conn-det unsupported\n");
} else if (panel->state.connected == PANEL_STATE_NOK) {
panel_warn("panel disconnected\n");
panel_dsi_set_bypass(panel, true);
if (!panel_bypass_is_on(panel))
panel_set_bypass(panel, PANEL_BYPASS_ON);
} else {
panel_dbg("panel connected\n");
panel_dsi_set_bypass(panel, false);
if (panel_bypass_is_on(panel))
panel_set_bypass(panel, PANEL_BYPASS_OFF);
}
if (panel_bypass_is_on(panel)) {
panel_print_bypass_reason(panel);
ret = -ENODEV;
goto do_exit;
}
state = &panel->state;
prev_state = state->cur_state;
PRINT_PANEL_STATE_BEGIN(prev_state, PANEL_STATE_ON, start);
if (state->cur_state == PANEL_STATE_OFF) {
ret = __set_panel_power(panel, PANEL_POWER_ON);
if (ret) {
panel_err("failed to panel power on\n");
goto do_exit;
}
panel_set_cur_state(panel, PANEL_STATE_ON);
}
#ifdef CONFIG_PANEL_NOTIFY
panel_send_ubconn_notify(panel->state.connected == PANEL_STATE_OK ?
PANEL_EVENT_UB_CON_CONNECTED : PANEL_EVENT_UB_CON_DISCONNECTED);
#endif
PRINT_PANEL_STATE_END(prev_state, state->cur_state, start);
return 0;
do_exit:
#ifdef CONFIG_PANEL_NOTIFY
panel_send_ubconn_notify(PANEL_EVENT_UB_CON_DISCONNECTED);
#endif
return ret;
}
__visible_for_testing int panel_power_off(struct panel_device *panel)
{
int ret = 0;
struct panel_state *state;
enum panel_active_state prev_state;
ktime_t start;
if (!panel)
return -EINVAL;
if (panel_bypass_is_on(panel)) {
panel_print_bypass_reason(panel);
return -ENODEV;
}
state = &panel->state;
prev_state = state->cur_state;
PRINT_PANEL_STATE_BEGIN(prev_state, PANEL_STATE_OFF, start);
switch (state->cur_state) {
case PANEL_STATE_OFF:
panel_info("panel already %s state\n", panel_state_names[state->cur_state]);
goto do_exit;
case PANEL_STATE_ALPM:
case PANEL_STATE_NORMAL:
panel_sleep_in(panel);
case PANEL_STATE_ON:
ret = __set_panel_power(panel, PANEL_POWER_OFF);
if (ret) {
panel_err("failed to panel power off\n");
goto do_exit;
}
break;
default:
panel_err("invalid state(%d)\n", state->cur_state);
goto do_exit;
}
ret = panel_power_control_execute(panel, "panel_fd_disable");
if (ret < 0 && ret != -ENODATA)
panel_err("failed to panel_fd_disable\n");
panel_set_cur_state(panel, PANEL_STATE_OFF);
#ifdef CONFIG_EXTEND_LIVE_CLOCK
ret = panel_aod_power_off(panel);
if (ret)
panel_err("failed to aod power off\n");
#endif
PRINT_PANEL_STATE_END(prev_state, state->cur_state, start);
return 0;
do_exit:
return ret;
}
__visible_for_testing int panel_reset_lp11(struct panel_device *panel)
{
int ret = 0;
struct panel_state *state;
enum panel_active_state prev_state;
struct panel_bl_device *panel_bl;
if (!panel)
return -EINVAL;
panel_bl = &panel->panel_bl;
if (panel_bypass_is_on(panel)) {
panel_print_bypass_reason(panel);
ret = -ENODEV;
goto do_exit;
}
state = &panel->state;
prev_state = state->cur_state;
if (state->cur_state == PANEL_POWER_ON) {
mutex_lock(&panel_bl->lock);
mutex_lock(&panel->op_lock);
ret = panel_power_control_execute(panel, "panel_reset_lp11");
if (ret < 0 && ret != -ENODATA)
panel_err("failed to panel_reset_lp11\n");
mutex_unlock(&panel->op_lock);
mutex_unlock(&panel_bl->lock);
}
return 0;
do_exit:
return ret;
}
__visible_for_testing int panel_sleep_out(struct panel_device *panel)
{
int ret = 0;
static int retry = 3;
struct panel_state *state;
enum panel_active_state prev_state;
ktime_t start;
int pcd_state;
if (!panel)
return -EINVAL;
panel->state.connected = panel_conn_det_state(panel);
if (panel->state.connected < 0) {
panel_dbg("conn-det unsupported\n");
} else if (panel->state.connected == PANEL_STATE_NOK) {
panel_warn("panel disconnected\n");
panel_set_bypass(panel, PANEL_BYPASS_ON);
panel_print_bypass_reason(panel);
#if defined(CONFIG_SUPPORT_PANEL_SWAP)
return -ENODEV;
#endif
} else {
panel_info("panel connected\n");
panel_dsi_set_bypass(panel, PANEL_BYPASS_OFF);
panel_set_bypass(panel, PANEL_BYPASS_OFF);
}
state = &panel->state;
prev_state = state->cur_state;
PRINT_PANEL_STATE_BEGIN(prev_state, PANEL_STATE_NORMAL, start);
switch (state->cur_state) {
case PANEL_STATE_NORMAL:
panel_warn("panel already %s state\n",
panel_state_names[state->cur_state]);
goto do_exit;
case PANEL_STATE_ALPM:
#ifdef CONFIG_MCD_PANEL_LPM
ret = __panel_seq_exit_alpm(panel);
if (ret) {
panel_err("failed to panel exit alpm\n");
goto do_exit;
}
#endif
break;
case PANEL_STATE_OFF:
ret = panel_power_on(panel);
if (ret) {
panel_err("failed to power on\n");
goto do_exit;
}
case PANEL_STATE_ON:
ret = __panel_seq_init(panel);
if (ret) {
if (--retry >= 0 && ret == -EAGAIN) {
panel_dsi_set_bypass(panel, true);
panel_power_off(panel);
msleep(100);
goto do_exit;
}
pcd_state = panel_pcd_state(panel);
if (pcd_state == PANEL_STATE_NOK) {
/*
* When panel sleep out failed 3 times continuously,
* If PCD is NOT OK, Phone Should run as well. (concept).
* Do not BUG(); Just leave bypass flag to keep going.
*/
panel_dsi_set_bypass(panel, true); /* ignore TE checking & frame update request*/
panel_err("failed to panel init seq. but PCD is NOT OK. keep going.\n");
/* Below irq is not enabled in "__panel_seq_init", so turn them on here*/
panel_enable_disp_det_irq(panel);
panel_enable_pcd_irq(panel);
} else {
panel_err("failed to panel init seq\n");
BUG();
}
}
retry = 3;
break;
default:
panel_err("invalid state(%d)\n", state->cur_state);
goto do_exit;
}
panel_set_cur_state(panel, PANEL_STATE_NORMAL);
panel->ktime_panel_on = ktime_get();
mutex_lock(&panel->work[PANEL_WORK_CHECK_CONDITION].lock);
clear_check_wq_var(&panel->condition_check);
mutex_unlock(&panel->work[PANEL_WORK_CHECK_CONDITION].lock);
#ifdef CONFIG_PANEL_VRR_BRIDGE
if (prev_state == PANEL_STATE_ALPM) {
mutex_lock(&panel->op_lock);
panel->panel_data.props.panel_mode =
panel->panel_data.props.target_panel_mode;
mutex_unlock(&panel->op_lock);
ret = panel_update_display_mode(panel);
if (ret < 0)
panel_err("failed to panel_update_display_mode\n");
}
#endif
#ifdef CONFIG_SUPPORT_HMD
ret = panel_set_hmd_on(panel);
if (ret)
panel_err("failed to set hmd on seq\n");
#endif
PRINT_PANEL_STATE_END(prev_state, state->cur_state, start);
return 0;
do_exit:
return ret;
}
#ifdef CONFIG_MCD_PANEL_LPM
__visible_for_testing int panel_doze(struct panel_device *panel)
{
int ret = 0;
struct panel_state *state;
enum panel_active_state prev_state;
ktime_t start;
if (!panel)
return -EINVAL;
if (panel_bypass_is_on(panel)) {
panel_print_bypass_reason(panel);
goto do_exit;
}
state = &panel->state;
prev_state = state->cur_state;
PRINT_PANEL_STATE_BEGIN(prev_state, PANEL_STATE_ALPM, start);
switch (state->cur_state) {
case PANEL_STATE_ALPM:
panel_warn("panel already %s state\n",
panel_state_names[state->cur_state]);
goto do_exit;
case PANEL_POWER_ON:
case PANEL_POWER_OFF:
ret = panel_sleep_out(panel);
if (ret) {
panel_err("failed to set normal\n");
goto do_exit;
}
case PANEL_STATE_NORMAL:
ret = __panel_seq_set_alpm(panel);
if (ret)
panel_err("failed to write alpm\n");
panel_set_cur_state(panel, PANEL_STATE_ALPM);
#ifdef CONFIG_EXYNOS_DECON_MDNIE_LITE
panel_mdnie_update(panel);
#endif
break;
default:
break;
}
mutex_lock(&panel->work[PANEL_WORK_CHECK_CONDITION].lock);
clear_check_wq_var(&panel->condition_check);
mutex_unlock(&panel->work[PANEL_WORK_CHECK_CONDITION].lock);
PRINT_PANEL_STATE_END(prev_state, state->cur_state, start);
do_exit:
return ret;
}
#endif /* CONFIG_MCD_PANEL_LPM */
static int panel_register_cb(struct panel_device *panel, int cb_id, void *arg)
{
struct disp_cb_info *cb_info = arg;
if (!panel || !cb_info)
return -EINVAL;
if (cb_id >= MAX_PANEL_CB) {
panel_err("out of range (cb_id:%d)\n", cb_id);
return -EINVAL;
}
memcpy(&panel->cb_info[cb_id], cb_info, sizeof(*cb_info));
return 0;
}
int panel_vrr_cb(struct panel_device *panel)
{
struct disp_cb_info *vrr_cb_info = &panel->cb_info[PANEL_CB_VRR];
#ifdef CONFIG_PANEL_NOTIFY
struct panel_dms_data dms_data;
static struct panel_dms_data old_dms_data;
#endif
struct panel_properties *props;
struct panel_vrr *vrr;
int ret = 0;
props = &panel->panel_data.props;
vrr = get_panel_vrr(panel);
if (vrr == NULL)
return -EINVAL;
if (vrr_cb_info->cb) {
ret = vrr_cb_info->cb((void *)vrr_cb_info->data, (void *)vrr);
if (ret < 0)
panel_err("failed to vrr callback\n");
}
#ifdef CONFIG_PANEL_NOTIFY
dms_data.fps = vrr->fps /
max(TE_SKIP_TO_DIV(vrr->te_sw_skip_count,
vrr->te_hw_skip_count), MIN_VRR_DIV_COUNT);
dms_data.lfd_min_freq = props->vrr_lfd_info.status[VRR_LFD_SCOPE_NORMAL].lfd_min_freq;
dms_data.lfd_max_freq = props->vrr_lfd_info.status[VRR_LFD_SCOPE_NORMAL].lfd_max_freq;
/* notify clients that vrr has changed */
if (old_dms_data.fps != dms_data.fps) {
panel_notifier_call_chain(PANEL_EVENT_VRR_CHANGED, &dms_data);
panel_info("PANEL_EVENT_VRR_CHANGED fps:%d lfd_freq:%d~%dHz\n",
dms_data.fps, dms_data.lfd_min_freq, dms_data.lfd_max_freq);
}
/* notify clients that fps or lfd has changed */
if (memcmp(&old_dms_data, &dms_data, sizeof(struct panel_dms_data))) {
panel_notifier_call_chain(PANEL_EVENT_LFD_CHANGED, &dms_data);
panel_info("PANEL_EVENT_LFD_CHANGED fps:%d lfd_freq:%d~%dHz\n",
dms_data.fps, dms_data.lfd_min_freq, dms_data.lfd_max_freq);
}
memcpy(&old_dms_data, &dms_data, sizeof(struct panel_dms_data));
#endif
return ret;
}
#ifdef CONFIG_MCD_PANEL_RCD
int panel_get_rcd_info(struct panel_device *panel, void *arg)
{
struct panel_rcd_data **rcd_info = arg;
if (!panel || !rcd_info) {
panel_err("invalid argument\n");
return -EINVAL;
}
if (!panel->panel_data.rcd_data) {
panel_info("rcd_data is empty\n");
return -ENODATA;
}
*rcd_info = panel->panel_data.rcd_data;
return 0;
}
#endif
#if defined(CONFIG_PANEL_DISPLAY_MODE)
int panel_get_display_mode(struct panel_device *panel, void *arg)
{
struct panel_display_modes **panel_modes = arg;
if (!panel || !panel_modes) {
panel_err("invalid argument\n");
return -EINVAL;
}
if (!panel->panel_modes) {
panel_err("panel_modes not prepared\n");
return -EINVAL;
}
*panel_modes = panel->panel_modes;
return 0;
}
int panel_display_mode_cb(struct panel_device *panel)
{
struct disp_cb_info *cb_info = &panel->cb_info[PANEL_CB_DISPLAY_MODE];
struct common_panel_display_modes *common_panel_modes =
panel->panel_data.common_panel_modes;
struct panel_properties *props = &panel->panel_data.props;
int ret = 0, panel_mode = props->panel_mode;
if (!panel_display_mode_is_supported(panel)) {
panel_err("panel_display_mode not supported\n");
return -EINVAL;
}
if (panel_mode < 0 ||
panel_mode >= common_panel_modes->num_modes) {
panel_err("invalid panel_mode %d\n", panel_mode);
return -EINVAL;
}
if (cb_info->cb) {
ret = cb_info->cb(cb_info->data,
common_panel_modes->modes[panel_mode]);
if (ret)
panel_err("failed to display_mode callback\n");
}
panel_vrr_cb(panel);
return ret;
}
static bool check_display_mode_cond(struct panel_device *panel)
{
struct panel_properties *props = &panel->panel_data.props;
#if defined(CONFIG_MCD_PANEL_FACTORY)
if (props->alpm_mode != ALPM_OFF) {
panel_warn("could not change display mode in lpm(%d) state\n",
props->alpm_mode);
return false;
}
#endif
if (props->mcd_on == true) {
panel_warn("could not change display mode in mcd(%d) state\n",
props->mcd_on);
return false;
}
return true;
}
bool panel_display_mode_is_supported(struct panel_device *panel)
{
struct common_panel_display_modes *common_panel_modes =
panel->panel_data.common_panel_modes;
if (!common_panel_modes)
return false;
if (common_panel_modes->num_modes == 0)
return false;
if (!check_seqtbl_exist(&panel->panel_data,
PANEL_DISPLAY_MODE_SEQ))
return false;
return true;
}
static struct common_panel_display_mode *
panel_find_common_panel_display_mode(struct panel_device *panel,
struct panel_display_mode *pdm)
{
struct common_panel_display_modes *common_panel_modes =
panel->panel_data.common_panel_modes;
int i;
if (!common_panel_modes) {
panel_err("common_panel_modes not prepared\n");
return NULL;
}
for (i = 0; i < common_panel_modes->num_modes; i++) {
if (common_panel_modes->modes[i] == NULL)
continue;
if (!strncmp(common_panel_modes->modes[i]->name,
pdm->name, sizeof(pdm->name))) {
panel_dbg("pdm:%s cpdm:%d:%s\n",
pdm->name, i, common_panel_modes->modes[i]->name);
return common_panel_modes->modes[i];
}
}
return NULL;
}
int find_panel_mode_by_common_panel_display_mode(struct panel_device *panel,
struct common_panel_display_mode *cpdm)
{
struct common_panel_display_modes *common_panel_modes =
panel->panel_data.common_panel_modes;
int i;
for (i = 0; i < common_panel_modes->num_modes; i++)
if (cpdm == common_panel_modes->modes[i])
break;
if (i == common_panel_modes->num_modes)
return -EINVAL;
return i;
}
int panel_display_mode_find_panel_mode(struct panel_device *panel,
struct panel_display_mode *pdm)
{
struct common_panel_display_mode *cpdm;
if (!panel_display_mode_is_supported(panel)) {
panel_err("panel_display_mode not supported\n");
return -EINVAL;
}
cpdm = panel_find_common_panel_display_mode(panel, pdm);
if (!cpdm) {
panel_err("panel_display_mode(%s) not found\n", pdm->name);
return -EINVAL;
}
panel_dbg("%s:%s\n", pdm->name, cpdm->name);
return find_panel_mode_by_common_panel_display_mode(panel, cpdm);
}
int panel_display_mode_get_native_mode(struct panel_device *panel)
{
struct panel_display_modes *panel_modes =
panel->panel_modes;
struct panel_display_mode *pdm;
int panel_mode;
if (!panel_display_mode_is_supported(panel)) {
panel_err("panel_display_mode not supported\n");
return -EINVAL;
}
if (!panel_modes) {
panel_err("panel_modes is null\n");
return -EINVAL;
}
if (panel_modes->num_modes <= 0) {
panel_err("panel_modes->num_modes is 0\n");
return -EINVAL;
}
if (panel_modes->native_mode >=
panel_modes->num_modes) {
panel_err("panel_modes->native_mode(%d) is out of range\n",
panel_modes->native_mode);
return -EINVAL;
}
pdm = panel_modes->modes[panel_modes->native_mode];
panel_mode =
panel_display_mode_find_panel_mode(panel, pdm);
if (panel_mode < 0) {
panel_err("could not find panel_display_mode(%s)\n", pdm->name);
return -EINVAL;
}
return panel_mode;
}
int panel_display_mode_get_mres_mode(struct panel_device *panel, int panel_mode)
{
struct common_panel_display_modes *common_panel_modes =
panel->panel_data.common_panel_modes;
struct panel_mres *mres = &panel->panel_data.mres;
struct panel_resol *resol;
int i;
if (!panel_display_mode_is_supported(panel)) {
panel_err("panel_display_mode not supported\n");
return -EINVAL;
}
if (panel_mode < 0 ||
panel_mode >= common_panel_modes->num_modes) {
panel_err("invalid panel_mode %d\n", panel_mode);
return -EINVAL;
}
resol = common_panel_modes->modes[panel_mode]->resol;
for (i = 0; i < mres->nr_resol; i++)
if (resol == &mres->resol[i])
break;
return i;
}
bool panel_display_mode_is_mres_mode_changed(struct panel_device *panel, int panel_mode)
{
struct panel_properties *props =
&panel->panel_data.props;
int mres_mode;
mres_mode = panel_display_mode_get_mres_mode(panel, panel_mode);
if (mres_mode < 0)
return mres_mode;
return (props->mres_mode != mres_mode);
}
int panel_display_mode_get_vrr_idx(struct panel_device *panel, int panel_mode)
{
struct common_panel_display_modes *common_panel_modes =
panel->panel_data.common_panel_modes;
struct panel_vrr *vrr, **vrrtbl = panel->panel_data.vrrtbl;
int i, num_vrrs = panel->panel_data.nr_vrrtbl;
if (!panel_display_mode_is_supported(panel)) {
panel_err("panel_display_mode not supported\n");
return -EINVAL;
}
if (panel_mode < 0 ||
panel_mode >= common_panel_modes->num_modes) {
panel_err("invalid panel_mode %d\n", panel_mode);
return -EINVAL;
}
vrr = common_panel_modes->modes[panel_mode]->vrr;
if (vrr == NULL) {
panel_err("vrr is null of panel_mode(%d)\n", panel_mode);
return -EINVAL;
}
for (i = 0; i < num_vrrs; i++)
if (vrr == vrrtbl[i])
break;
if (i == num_vrrs) {
panel_warn("panel_mode(%d) vrr(%d%s) not found\n",
panel_mode, vrr->fps, REFRESH_MODE_STR(vrr->mode));
return -EINVAL;
}
return i;
}
static int panel_update_display_mode_props(struct panel_device *panel, int panel_mode)
{
struct panel_properties *props = &panel->panel_data.props;
struct panel_mres *mres = &panel->panel_data.mres;
struct panel_vrr *vrr;
struct panel_resol *resol;
int mres_mode, vrr_idx;
props->panel_mode = panel_mode;
mres_mode = panel_display_mode_get_mres_mode(panel, panel_mode);
if (mres_mode < 0) {
panel_err("failed to get mres_mode from panel_mode(%d)\n", panel_mode);
return -EINVAL;
}
if (mres_mode >= mres->nr_resol) {
panel_err("out of range mres_mode(%d)\n", mres_mode);
return -EINVAL;
}
resol = &mres->resol[mres_mode];
if (props->mres_mode == mres_mode) {
panel_dbg("same resolution(%d:%dx%d)\n",
mres_mode, resol->w, resol->h);
props->mres_updated = false;
} else {
props->old_mres_mode = props->mres_mode;
props->mres_mode = mres_mode;
props->mres_updated = true;
}
vrr_idx = panel_display_mode_get_vrr_idx(panel, panel_mode);
if (vrr_idx < 0) {
panel_err("failed to get vrr_idx from panel_mode(%d)\n",
panel_mode);
return -EINVAL;
}
if (vrr_idx >= panel->panel_data.nr_vrrtbl) {
panel_err("out of range vrr_idx(%d)\n", vrr_idx);
return -EINVAL;
}
vrr = panel->panel_data.vrrtbl[vrr_idx];
/* keep origin data */
props->vrr_origin_fps = props->vrr_fps;
props->vrr_origin_mode = props->vrr_mode;
props->vrr_origin_idx = props->vrr_idx;
/* update vrr data */
props->vrr_fps = vrr->fps;
props->vrr_mode = vrr->mode;
props->vrr_idx = vrr_idx;
panel_info("updated mres(%d:%dx%d) vrr(%d:%d%s,te:%dHz)\n",
props->mres_mode, resol->w, resol->h,
props->vrr_idx, props->vrr_fps,
REFRESH_MODE_STR(props->vrr_mode),
props->vrr_fps / (vrr->te_hw_skip_count + 1));
return 0;
}
static int panel_seq_display_mode(struct panel_device *panel)
{
int ret;
if (unlikely(!panel)) {
panel_err("panel_device is null!!\n");
return -EINVAL;
}
if (!panel_display_mode_is_supported(panel)) {
panel_err("panel_display_mode not supported\n");
return -EINVAL;
}
if (!check_display_mode_cond(panel))
return -EINVAL;
ret = panel_do_seqtbl_by_index_nolock(panel,
PANEL_DISPLAY_MODE_SEQ);
if (unlikely(ret < 0)) {
panel_err("failed to do display-mode-seq\n");
return ret;
}
return 0;
}
int panel_set_display_mode_nolock(struct panel_device *panel, int panel_mode)
{
struct common_panel_display_modes *common_panel_modes;
struct panel_properties *props;
int ret = 0;
if (unlikely(!panel)) {
panel_err("panel_device is null!!\n");
return -EINVAL;
}
props = &panel->panel_data.props;
if (!panel_display_mode_is_supported(panel)) {
panel_err("panel_display_mode not supported\n");
return -EINVAL;
}
common_panel_modes = panel->panel_data.common_panel_modes;
if (panel_mode < 0 ||
panel_mode >= common_panel_modes->num_modes) {
panel_err("invalid panel_mode %d\n", panel_mode);
return -EINVAL;
}
/*
* apply panel device dependent display mode
*/
ret = panel_update_display_mode_props(panel, panel_mode);
if (ret < 0) {
panel_err("failed to update display mode properties\n");
return ret;
}
ret = panel_seq_display_mode(panel);
if (unlikely(ret < 0)) {
panel_err("failed to panel_seq_display_mode\n");
return ret;
}
props->vrr_origin_fps = props->vrr_fps;
props->vrr_origin_mode = props->vrr_mode;
props->vrr_origin_idx = props->vrr_idx;
return ret;
}
#ifdef CONFIG_PANEL_VRR_BRIDGE
static int panel_run_vrr_bridge_thread(struct panel_device *panel)
{
wake_up_interruptible_all(&panel->thread[PANEL_THREAD_VRR_BRIDGE].wait);
return 0;
}
#endif
static int panel_set_display_mode(struct panel_device *panel, void *arg)
{
int ret = 0, panel_mode;
struct panel_display_mode *pdm = arg;
struct panel_properties *props =
&panel->panel_data.props;
if (unlikely(!pdm)) {
panel_err("panel_display_mode is null\n");
return -EINVAL;
}
mutex_lock(&panel->op_lock);
panel_mode =
panel_display_mode_find_panel_mode(panel, pdm);
if (panel_mode < 0) {
panel_err("could not find panel_display_mode(%s)\n", pdm->name);
ret = -EINVAL;
goto out;
}
if (props->panel_mode == panel_mode) {
panel_info("same panel_mode(%d)\n", panel_mode);
goto out;
}
if (panel->state.cur_state != PANEL_STATE_NORMAL &&
panel->state.cur_state != PANEL_STATE_ALPM) {
ret = panel_update_display_mode_props(panel, panel_mode);
if (ret < 0) {
panel_err("failed to update display mode properties\n");
goto out;
}
props->vrr_origin_fps = props->vrr_fps;
props->vrr_origin_mode = props->vrr_mode;
props->vrr_origin_idx = props->vrr_idx;
goto out;
}
#ifdef CONFIG_PANEL_VRR_BRIDGE
props->target_panel_mode = panel_mode;
if (panel_vrr_bridge_changeable(panel) &&
!panel_display_mode_is_mres_mode_changed(panel, panel_mode)) {
/* run vrr-bridge thread */
ret = panel_run_vrr_bridge_thread(panel);
if (ret < 0)
panel_err("failed to run vrr-bridge thread\n");
goto out;
}
#endif
ret = panel_set_display_mode_nolock(panel, panel_mode);
if (ret < 0)
panel_err("failed to panel_set_display_mode_nolock\n");
out:
mutex_unlock(&panel->op_lock);
/* callback to notify current display mode */
if (!ret)
panel_display_mode_cb(panel);
return ret;
}
/**
* panel_update_display_mode - update display mode
* @panel: panel device
*
* execute DISPLAY_MODE seq with current display mode.
*/
int panel_update_display_mode(struct panel_device *panel)
{
int ret;
struct panel_properties *props =
&panel->panel_data.props;
mutex_lock(&panel->op_lock);
ret = panel_set_display_mode_nolock(panel, props->panel_mode);
if (ret < 0)
panel_err("failed to panel_set_display_mode_nolock\n");
mutex_unlock(&panel->op_lock);
/* callback to notify current display mode */
panel_display_mode_cb(panel);
return ret;
}
#endif /* CONFIG_PANEL_DISPLAY_MODE */
#ifdef CONFIG_SUPPORT_DSU
static int panel_set_mres(struct panel_device *panel, void *arg)
{
int ret = 0;
int mres_idx;
struct panel_properties *props;
struct panel_mres *mres;
if (unlikely(!panel)) {
panel_err("panel is null\n");
return -EINVAL;
}
props = &panel->panel_data.props;
mres = &panel->panel_data.mres;
mres_idx = *(int *)arg;
if (mres->nr_resol == 0 || mres->resol == NULL) {
panel_err("multi-resolution unsupported!!\n");
return -EINVAL;
}
if (mres_idx >= mres->nr_resol) {
panel_err("invalid mres idx:%d, number:%d\n",
mres_idx, mres->nr_resol);
return -EINVAL;
}
props->old_mres_mode = props->mres_mode;
props->mres_mode = mres_idx;
props->mres_updated = true;
ret = panel_do_seqtbl_by_index(panel, PANEL_DSU_SEQ);
if (unlikely(ret < 0)) {
panel_err("failed to write dsu seqtbl\n");
goto do_exit;
}
props->xres = mres->resol[mres_idx].w;
props->yres = mres->resol[mres_idx].h;
return 0;
do_exit:
return ret;
}
#endif /* CONFIG_SUPPORT_DSU */
static inline int panel_set_ffc_seq(struct panel_device *panel, u32 dsi_freq)
{
int ret;
struct panel_properties *props = &panel->panel_data.props;
u32 origin = props->dsi_freq;
if ((props->dsi_freq == 0) ||
(props->dsi_freq == dsi_freq))
return 0;
panel_info("panel update ffc frequency %d -> %dkhz\n", origin, dsi_freq);
props->dsi_freq = dsi_freq;
ret = panel_do_seqtbl_by_index(panel, PANEL_FFC_SEQ);
if (unlikely(ret < 0)) {
panel_err("failed to write ffc seqtbl\n");
props->dsi_freq = origin;
return ret;
}
return 0;
}
static inline int panel_set_osc_seq(struct panel_device *panel, u32 osc_freq)
{
struct panel_properties *props = &panel->panel_data.props;
if ((props->osc_freq == 0) ||
(props->osc_freq == osc_freq))
return 0;
panel_info("panel update osc frequency %dkhz -> %d\n", props->osc_freq, osc_freq);
props->osc_freq = osc_freq;
return 0;
}
static int panel_request_set_clock(struct panel_device *panel, void *arg)
{
int ret = 0;
struct panel_clock_info *info = arg;
switch (info->clock_id) {
case CLOCK_ID_DSI:
ret = panel_set_ffc_seq(panel, info->clock_rate);
break;
case CLOCK_ID_OSC:
ret = panel_set_osc_seq(panel, info->clock_rate);
break;
default:
panel_err("Invalid clock id: %d\n", info->clock_id);
ret = -EINVAL;
}
return ret;
}
static int panel_get_ddi_props(struct panel_device *panel, void *arg)
{
if (!panel || !arg)
return -EINVAL;
memcpy(arg, &panel->panel_data.ddi_props, sizeof(struct ddi_properties));
return 0;
}
int panel_ioctl_attach_adapter(struct panel_device *panel, void *arg)
{
if (!panel || !arg) {
panel_err("invalid argument\n");
return -EINVAL;
}
memcpy(&panel->adapter, arg, sizeof(panel->adapter));
return panel_parse_lcd_info(panel);
}
int panel_ioctl_get_panel_state(struct panel_device *panel, void *arg)
{
struct panel_state **state = arg;
if (!panel || !state)
return -EINVAL;
/* TODO: extract function to update connected state */
panel->state.connected = panel_conn_det_state(panel);
//Todo..
panel_info("connected : %d\n", panel->state.connected);
panel->state.connected = 1;
*state = &panel->state;
return 0;
}
int panel_register_error_cb(struct panel_device *panel, void *arg)
{
struct disp_error_cb_info *error_cb_info = arg;
if (!panel || !error_cb_info)
return -EINVAL;
panel->error_cb_info.error_cb = error_cb_info->error_cb;
panel->error_cb_info.powerdown_cb = error_cb_info->powerdown_cb;
panel->error_cb_info.data = error_cb_info->data;
return 0;
}
#ifdef CONFIG_SUPPORT_ERRFG_RECOVERY
static int panel_check_cb(void *data)
{
struct panel_device *panel = data;
int status = DISP_CHECK_STATUS_OK;
if (panel_conn_det_state(panel) == PANEL_STATE_NOK)
status |= DISP_CHECK_STATUS_NODEV;
if (panel_disp_det_state(panel) == PANEL_STATE_NOK)
status |= DISP_CHECK_STATUS_ELOFF;
#ifdef CONFIG_SUPPORT_ERRFG_RECOVERY
if (panel_err_fg_state(panel) == PANEL_STATE_NOK
&& panel->panel_data.ddi_props.err_fg_powerdown)
status |= DISP_CHECK_STATUS_NODEV;
#endif
return status;
}
static int panel_error_cb(struct panel_device *panel)
{
struct disp_error_cb_info *error_cb_info = &panel->error_cb_info;
struct disp_check_cb_info panel_check_info = {
.check_cb = (disp_check_cb *)panel_check_cb,
.data = panel,
};
int ret = 0;
if (error_cb_info->error_cb) {
ret = error_cb_info->error_cb((void *)(error_cb_info->data),
&panel_check_info);
if (ret)
panel_err("failed to recovery panel\n");
}
return ret;
}
#endif
#ifdef CONFIG_SUPPORT_ERRFG_RECOVERY
static int panel_powerdown_cb(struct panel_device *panel)
{
struct disp_error_cb_info *error_cb_info = &panel->error_cb_info;
struct disp_check_cb_info panel_check_info = {
.check_cb = (disp_check_cb *)panel_check_cb,
.data = panel,
};
int ret = 0;
if (error_cb_info->powerdown_cb) {
ret = error_cb_info->powerdown_cb((void *)(error_cb_info->data),
&panel_check_info);
if (ret)
panel_err("failed to powerdown panel\n");
}
return ret;
}
#endif
#ifdef CONFIG_SUPPORT_MASK_LAYER
static int panel_set_mask_layer(struct panel_device *panel, void *arg)
{
int ret = 0;
struct panel_bl_device *panel_bl = &panel->panel_bl;
struct mask_layer_data *req_data = (struct mask_layer_data *)arg;
if (!panel->lcd_dev) {
panel_err("lcd device not exist\n");
return -EINVAL;
}
if (req_data->req_mask_layer == MASK_LAYER_ON) {
if (req_data->trigger_time == MASK_LAYER_TRIGGER_BEFORE) {
/*
* W/A - During smooth dimming transition,
* Smooth dimming transition should stop here.
*/
/* 0. STOP SMOOTH DIMMING */
mutex_lock(&panel_bl->lock);
if (check_seqtbl_exist(&panel->panel_data, PANEL_MASK_LAYER_STOP_DIMMING_SEQ)) {
panel_bl->props.smooth_transition = SMOOTH_TRANS_OFF;
panel_do_seqtbl_by_index(panel, PANEL_MASK_LAYER_STOP_DIMMING_SEQ);
}
/* 1. REQ ON + FRAME START BEFORE */
if (check_seqtbl_exist(&panel->panel_data, PANEL_MASK_LAYER_BEFORE_SEQ))
panel_do_seqtbl_by_index(panel, PANEL_MASK_LAYER_BEFORE_SEQ);
panel_bl->props.mask_layer_br_hook = MASK_LAYER_HOOK_ON;
panel_bl->props.smooth_transition = SMOOTH_TRANS_OFF;
panel_bl->props.acl_opr = ACL_OPR_OFF;
panel_bl->props.acl_pwrsave = ACL_PWRSAVE_OFF;
if (check_seqtbl_exist(&panel->panel_data, PANEL_MASK_LAYER_ENTER_BR_SEQ)
&&(panel->state.cur_state != PANEL_STATE_ALPM)) {
panel_bl->props.brightness = panel_bl->props.mask_layer_br_target;
panel_info("mask_layer_br enter (%d)->(%d)\n",
panel_bl->bd->props.brightness, panel_bl->props.mask_layer_br_target);
panel_do_seqtbl_by_index(panel, PANEL_MASK_LAYER_ENTER_BR_SEQ);
mutex_unlock(&panel_bl->lock);
} else {
mutex_unlock(&panel_bl->lock);
panel_update_brightness(panel);
}
if (check_seqtbl_exist(&panel->panel_data, PANEL_MASK_LAYER_AFTER_SEQ))
panel_do_seqtbl_by_index(panel, PANEL_MASK_LAYER_AFTER_SEQ);
} else if (req_data->trigger_time == MASK_LAYER_TRIGGER_AFTER) {
/* 2. REQ ON + FRAME START AFTER */
panel_bl->props.mask_layer_br_actual = panel_bl->props.mask_layer_br_target;
sysfs_notify(&panel->lcd_dev->kobj, NULL, "actual_mask_brightness");
}
} else if (req_data->req_mask_layer == MASK_LAYER_OFF) {
if (req_data->trigger_time == MASK_LAYER_TRIGGER_BEFORE) {
/* 3. REQ OFF + FRAME START BEFORE */
mutex_lock(&panel_bl->lock);
if (check_seqtbl_exist(&panel->panel_data, PANEL_MASK_LAYER_BEFORE_SEQ))
panel_do_seqtbl_by_index(panel, PANEL_MASK_LAYER_BEFORE_SEQ);
panel_bl->props.mask_layer_br_hook = MASK_LAYER_HOOK_OFF;
if (check_seqtbl_exist(&panel->panel_data, PANEL_MASK_LAYER_EXIT_BR_SEQ)
&& (panel->state.cur_state != PANEL_STATE_ALPM)) {
panel_info("mask_layer_br exit (%d)->(%d)\n",
panel_bl->props.mask_layer_br_target, panel_bl->bd->props.brightness);
panel_bl->props.brightness = panel_bl->bd->props.brightness;
panel_bl->subdev[PANEL_BL_SUBDEV_TYPE_DISP].brightness = panel_bl->props.brightness;
#ifdef CONFIG_SUPPORT_AOD_BL
panel_bl->subdev[PANEL_BL_SUBDEV_TYPE_AOD].brightness = panel_bl->props.brightness;
#endif
panel_do_seqtbl_by_index(panel, PANEL_MASK_LAYER_EXIT_BR_SEQ);
mutex_unlock(&panel_bl->lock);
} else {
mutex_unlock(&panel_bl->lock);
panel_update_brightness(panel);
}
if (check_seqtbl_exist(&panel->panel_data, PANEL_MASK_LAYER_AFTER_SEQ))
panel_do_seqtbl_by_index(panel, PANEL_MASK_LAYER_AFTER_SEQ);
} else if (req_data->trigger_time == MASK_LAYER_TRIGGER_AFTER) {
/* 4. REQ OFF + FRAME START AFTER */
/* HOLD TE <-> DDI VSYNC Duration */
if (check_seqtbl_exist(&panel->panel_data, PANEL_MASK_LAYER_EXIT_AFTER_SEQ))
panel_do_seqtbl_by_index(panel, PANEL_MASK_LAYER_EXIT_AFTER_SEQ);
panel_bl->props.smooth_transition = SMOOTH_TRANS_ON;
panel_bl->props.mask_layer_br_actual = 0;
sysfs_notify(&panel->lcd_dev->kobj, NULL, "actual_mask_brightness");
}
}
return ret;
}
#endif
#ifdef CONFIG_SUPPORT_MAFPC
static int panel_notify_frame_done_mafpc(struct panel_device *panel)
{
int ret = 0;
ret = cmd_v4l2_mafpc_dev(panel, V4L2_IOCL_MAFPC_FRAME_DONE, panel);
return ret;
}
#endif
int panel_ioctl_event_frame_done(struct panel_device *panel, void *arg)
{
int ret = 0;
#ifdef CONFIG_SUPPORT_DSU
static int mres_updated_frame_cnt;
#endif
if (panel->state.cur_state != PANEL_STATE_NORMAL &&
panel->state.cur_state != PANEL_STATE_ALPM) {
panel_warn("FRAME_DONE (panel_state:%s, disp_on:%s)\n",
panel_state_names[panel->state.cur_state],
panel->state.disp_on ? "on" : "off");
return 0;
}
if (unlikely(panel->state.disp_on == PANEL_DISPLAY_OFF)) {
panel_info("FRAME_DONE (panel_state:%s, display on)\n",
panel_state_names[panel->state.cur_state]);
ret = panel_display_on(panel);
panel_check_ready(panel);
}
#ifdef CONFIG_EXYNOS_DECON_LCD_COPR
copr_update_start(&panel->copr, 3);
#endif
#ifdef CONFIG_SUPPORT_DSU
if (panel->panel_data.props.mres_updated &&
(++mres_updated_frame_cnt > 1)) {
panel->panel_data.props.mres_updated = false;
mres_updated_frame_cnt = 0;
}
#endif
if (panel->condition_check.is_panel_check)
panel_check_start(panel);
#ifdef CONFIG_SUPPORT_MAFPC
panel_notify_frame_done_mafpc(panel);
#endif
return ret;
}
int panel_ioctl_event_vsync(struct panel_device *panel, void *arg)
{
return 0;
}
int panel_drv_attach_adapter_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, attach_adapter, arg);
}
int panel_drv_get_panel_state_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, get_panel_state, arg);
}
int panel_drv_panel_probe_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, probe);
}
int panel_drv_set_power_ioctl(struct panel_device *panel, void *arg)
{
if (!panel || !arg)
return -EINVAL;
return (*(int *)arg == 0) ?
call_panel_drv_func(panel, power_off) :
call_panel_drv_func(panel, power_on);
}
int panel_drv_sleep_in_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, sleep_in);
}
int panel_drv_sleep_out_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, sleep_out);
}
int panel_drv_disp_on_ioctl(struct panel_device *panel, void *arg)
{
if (!panel || !arg)
return -EINVAL;
return (*(int *)arg == 0) ?
call_panel_drv_func(panel, display_off) :
call_panel_drv_func(panel, display_on);
}
int panel_drv_panel_dump_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, debug_dump);
}
int panel_drv_evt_frame_done_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, frame_done, arg);
}
int panel_drv_evt_vsync_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, vsync, arg);
}
#ifdef CONFIG_MCD_PANEL_LPM
int panel_drv_doze_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, doze);
}
int panel_drv_doze_suspend_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, doze);
}
#endif
int panel_drv_set_mres_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, set_mres, arg);
}
int panel_drv_get_mres_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, get_mres, arg);
}
#if defined(CONFIG_PANEL_DISPLAY_MODE)
int panel_drv_get_display_mode_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, get_display_mode, arg);
}
int panel_drv_set_display_mode_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, set_display_mode, arg);
}
int panel_drv_reg_display_mode_cb_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, register_cb, PANEL_CB_DISPLAY_MODE, arg);
}
#endif
int panel_drv_reg_reset_cb_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, register_error_cb, arg);
}
int panel_drv_reg_vrr_cb_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, register_cb, PANEL_CB_VRR, arg);
}
#ifdef CONFIG_SUPPORT_MASK_LAYER
int panel_drv_set_mask_layer_ioctl(struct panel_device *panel, void *arg)
{
return call_panel_drv_func(panel, set_mask_layer, arg);
}
#endif
int panel_drv_set_gpios(struct panel_device *panel)
{
int rst_val = -1, det_val = -1;
if (panel == NULL) {
panel_err("panel is null\n");
return -EINVAL;
}
/* FIX ME : check if we can delete the rst pin condition */
if (panel_is_gpio_valid(panel, PANEL_GPIO_RESET)) {
rst_val = panel_get_gpio_value(panel, PANEL_GPIO_RESET);
} else if (find_panel_regulator_by_node_name(panel, PANEL_REGULATOR_NAME_RESET)) {
rst_val = (boot_panel_id >= 0) ? 1 : 0;
panel_info("Fixed reg(%s) is exist.\n", PANEL_REGULATOR_NAME_RESET);
} else {
panel_err("gpio(%s) not exist\n",
panel_get_gpio_name(panel, PANEL_GPIO_RESET));
return -EINVAL;
}
if (panel_is_gpio_valid(panel, PANEL_GPIO_DISP_DET))
det_val = panel_get_gpio_value(panel, PANEL_GPIO_DISP_DET);
else
panel_err("gpio(%s) not exist\n",
panel_get_gpio_name(panel, PANEL_GPIO_DISP_DET));
/*
* panel state is decided by rst, conn_det and disp_det pin
*
* @rst_val
* 0 : need to init panel in kernel
* 1 : already initialized in bootloader
*
* @conn_det
* < 0 : unsupported
* = 0 : panel connector is disconnected
* = 1 : panel connector is connected
*
* @det_val
* < 0 : unsupported
* 0 : panel is "sleep in" state
* 1 : panel is "sleep out" state
*/
panel->state.init_at = (rst_val == 1) ?
PANEL_INIT_BOOT : PANEL_INIT_KERNEL;
panel->state.connected = panel_conn_det_state(panel);
/* bypass : decide to use or ignore panel */
if ((panel->state.init_at == PANEL_INIT_BOOT) &&
(panel->state.connected != 0) && (det_val != 0)) {
/*
* connect panel condition
* conn_det is normal(not zero)
* disp_det is nomal(1) or unsupported(< 0)
* init panel in bootloader(rst == 1)
*/
panel_set_bypass(panel, PANEL_BYPASS_OFF);
panel_set_cur_state(panel, PANEL_STATE_NORMAL);
panel->state.power = PANEL_POWER_ON;
panel->state.disp_on = PANEL_DISPLAY_ON;
panel_set_gpio_value(panel, PANEL_GPIO_RESET, 1);
} else {
panel_set_bypass(panel, PANEL_BYPASS_ON);
panel_set_cur_state(panel, PANEL_STATE_OFF);
panel->state.power = PANEL_POWER_OFF;
panel->state.disp_on = PANEL_DISPLAY_OFF;
panel_set_gpio_value(panel, PANEL_GPIO_RESET, 0);
}
#ifdef CONFIG_PANEL_NOTIFY
panel_send_ubconn_notify(panel->state.connected == PANEL_STATE_OK ?
PANEL_EVENT_UB_CON_CONNECTED : PANEL_EVENT_UB_CON_DISCONNECTED);
#endif
panel_info("rst:%d, disp_det:%d (init_at:%s, ub_con:%d(%s) panel:(%s))\n",
rst_val, det_val, (panel->state.init_at ? "BL" : "KERNEL"),
panel->state.connected,
(panel->state.connected < 0 ? "UNSUPPORTED" :
(panel->state.connected == true ? "CONNECTED" : "DISCONNECTED")),
(!panel_bypass_is_on(panel) ? "USE" : "NO USE"));
return 0;
}
int panel_drv_set_regulators(struct panel_device *panel)
{
int ret = 0;
if (panel->state.init_at == PANEL_INIT_BOOT) {
ret = panel_power_control_execute(panel, "panel_boot_on");
if (ret < 0 && ret != -ENODATA)
panel_err("failed to execute panel_boot_on\n");
if (panel_bypass_is_on(panel)) {
ret = panel_power_control_execute(panel, "panel_power_off");
if (ret < 0 && ret != -ENODATA)
panel_err("failed to execute panel_power_off\n");
}
}
return ret;
}
#if !defined(CONFIG_UML)
static int panel_parse_pinctrl(struct panel_device *panel)
{
int ret = 0;
if (panel == NULL) {
panel_err("panel is null\n");
return -EINVAL;
}
panel->pinctrl = devm_pinctrl_get(panel->dev);
if (IS_ERR(panel->pinctrl)) {
panel_err("failed to get device's pinctrl\n");
goto exit_parse_pinctrl;
}
panel->default_gpio_pinctrl = pinctrl_lookup_state(panel->pinctrl, "default");
if (IS_ERR(panel->default_gpio_pinctrl)) {
panel_err("can't get default pinctrl setting\n");
goto exit_parse_pinctrl;
}
if (pinctrl_select_state(panel->pinctrl, panel->default_gpio_pinctrl)) {
panel_err("%s failed to set default pinctrl\n", __func__);
goto exit_parse_pinctrl;
}
panel_info("pinctrl setting done\n");
exit_parse_pinctrl:
return ret;
}
#endif
struct panel_gpio *panel_gpio_list_add(struct panel_device *panel, struct panel_gpio *_gpio)
{
struct panel_gpio *gpio;
if (!panel || !_gpio) {
panel_err("invalid args\n");
return NULL;
}
gpio = kzalloc(sizeof(struct panel_gpio), GFP_KERNEL);
if (!gpio)
return NULL;
memcpy(gpio, _gpio, sizeof(struct panel_gpio));
list_add(&gpio->head, &panel->gpio_list);
return gpio;
}
static int panel_parse_gpio(struct panel_device *panel)
{
struct device *dev = panel->dev;
struct device_node *gpios_np, *np;
struct panel_gpio gpio;
struct panel_gpio *p_gpio;
gpios_np = of_get_child_by_name(dev->of_node, "gpios");
if (!gpios_np) {
panel_err("'gpios' node not found\n");
return -EINVAL;
}
for_each_child_of_node(gpios_np, np) {
memset(&gpio, 0, sizeof(gpio));
if (of_get_panel_gpio(np, &gpio)) {
panel_err("failed to get gpio %s\n", np->name);
break;
}
p_gpio = panel_gpio_list_add(panel, &gpio);
if (!p_gpio) {
panel_err("failed to add gpio list %s\n", gpio.name);
break;
}
}
of_node_put(gpios_np);
return 0;
}
__visible_for_testing struct panel_regulator *panel_regulator_list_add(struct panel_device *panel,
struct panel_regulator *_reg)
{
struct panel_regulator *reg;
if (!panel || !_reg) {
panel_err("invalid args\n");
return ERR_PTR(-EINVAL);
}
reg = kzalloc(sizeof(struct panel_regulator), GFP_KERNEL);
if (!reg)
return ERR_PTR(-ENOMEM);
memcpy(reg, _reg, sizeof(struct panel_regulator));
list_add(&reg->head, &panel->regulator_list);
return reg;
}
static int panel_parse_regulator(struct panel_device *panel)
{
struct device *dev = panel->dev;
struct panel_regulator regulator;
struct device_node *regulators_np, *np;
regulators_np = of_get_child_by_name(dev->of_node, "regulators");
if (!regulators_np) {
panel_err("'regulators' node not found\n");
return -EINVAL;
}
for_each_child_of_node(regulators_np, np) {
memset(&regulator, 0, sizeof(regulator));
if (of_get_panel_regulator(np, &regulator) < 0) {
panel_err("failed to get regulator %s\n", np->name);
of_node_put(regulators_np);
return -EINVAL;
}
if (IS_ERR_OR_NULL(panel_regulator_list_add(panel, &regulator))) {
panel_err("failed to add regulator list %s\n", regulator.name);
break;
}
panel_info("found regulator name:%s type:%d\n", regulator.name, regulator.type);
}
of_node_put(regulators_np);
panel_dbg("done\n");
return 0;
}
struct panel_regulator *find_panel_regulator_by_node_name(struct panel_device *panel, const char *node_name)
{
struct panel_regulator *regulator;
if (!panel || !node_name)
return NULL;
list_for_each_entry(regulator, &panel->regulator_list, head) {
if (!strcmp(regulator->node_name, node_name))
return regulator;
}
return NULL;
}
static int panel_parse_power_ctrl(struct panel_device *panel)
{
struct device_node *power_np, *seq_np;
struct property *pp;
struct panel_power_ctrl *p_seq;
int ret = 0;
panel_info("++\n");
power_np = panel->power_ctrl_node;
if (!power_np) {
panel_err("'power_ctrl' node not found\n");
ret = -EINVAL;
goto exit;
}
seq_np = of_get_child_by_name(power_np, "sequences");
if (!seq_np) {
panel_err("'sequences' node not found\n");
ret = -EINVAL;
goto exit_power;
}
for_each_property_of_node(seq_np, pp) {
if (!strcmp(pp->name, "name") || !strcmp(pp->name, "phandle"))
continue;
p_seq = kzalloc(sizeof(struct panel_power_ctrl), GFP_KERNEL);
if (!p_seq) {
ret = -ENOMEM;
goto exit_power;
}
p_seq->dev_name = panel->of_node_name;
p_seq->name = pp->name;
ret = of_get_panel_power_ctrl(panel, seq_np, pp->name, p_seq);
if (ret < 0) {
panel_err("failed to get power_ctrl %s\n", pp->name);
break;
}
list_add_tail(&p_seq->head, &panel->power_ctrl_list);
panel_info("power_ctrl '%s' initialized\n", p_seq->name);
}
of_node_put(seq_np);
exit_power:
of_node_put(power_np);
exit:
panel_info("--\n");
return ret;
}
static irqreturn_t panel_work_isr(int irq, void *dev_id)
{
struct panel_work *w = (struct panel_work *)dev_id;
queue_delayed_work(w->wq, &w->dwork, msecs_to_jiffies(0));
return IRQ_HANDLED;
}
static struct panel_work *panel_find_work(struct panel_device *panel, const char *name)
{
int i;
if (!name)
return NULL;
for (i = 0; i < PANEL_WORK_MAX; i++) {
if (!strncmp(panel_work_names[i],
name, MAX_PANEL_WORK_NAME_SIZE))
break;
}
if (i == PANEL_WORK_MAX)
return NULL;
return &panel->work[i];
}
static int panel_devm_request_irq(struct panel_device *panel,
enum panel_gpio_lists panel_gpio_id)
{
struct panel_gpio *gpio;
struct panel_work *work;
if (!panel_is_gpio_irq_valid(panel, panel_gpio_id))
return -EINVAL;
gpio = panel_get_gpio(panel, panel_gpio_id);
if (!gpio)
return -EINVAL;
work = panel_find_work(panel, gpio->name);
if (!work) {
panel_err("failed to find work(%s)\n", gpio->name);
return -EINVAL;
}
return panel_gpio_helper_devm_request_irq(gpio,
panel->dev, panel_work_isr, work->name, work);
}
int panel_register_isr(struct panel_device *panel)
{
int i = 0, ret = 0;
if (panel == NULL) {
panel_err("panel is NULL\n");
return -EINVAL;
}
if (panel->dev == NULL) {
panel_err("panel->dev is NULL\n");
return -ENODEV;
}
if (panel_bypass_is_on(panel)) {
panel_print_bypass_reason(panel);
return 0;
}
for (i = 0; i < PANEL_GPIO_MAX; i++) {
if (!panel_is_gpio_irq_valid(panel, i))
continue;
if (!panel_find_work(panel, panel_gpio_names[i]))
continue;
ret = panel_devm_request_irq(panel, i);
if (ret < 0) {
panel_err("failed to register irq(%s)\n",
panel_gpio_names[i]);
break;
}
}
if (ret < 0)
return ret;
return 0;
}
int panel_parse_lcd_info(struct panel_device *panel)
{
struct device_node *node;
struct device *dev;
int ret = 0;
if (!panel || !(panel->dev))
return -EINVAL;
dev = panel->dev;
panel_info("PANEL_INFO:panel id : %x\n", boot_panel_id);
node = find_panel_power_ctrl_node(panel, boot_panel_id);
if (!node) {
panel_err("panel not found (boot_panel_id 0x%08X)\n", boot_panel_id);
node = of_parse_phandle(dev->of_node, "panel-power-ctrl", 0);
if (!node) {
panel_err("failed to get phandle of panel-power-ctrl\n");
return -EINVAL;
}
}
panel->power_ctrl_node = node;
ret = panel_parse_power_ctrl(panel);
if (ret < 0) {
panel_err("panel-%d:failed to parse power_ctrl\n", panel->id);
return ret;
}
#if defined(CONFIG_PANEL_DISPLAY_MODE)
node = find_panel_modes_node(panel, boot_panel_id);
if (node != NULL) {
panel->panel_modes =
of_get_panel_display_modes(node);
if (panel->panel_modes == NULL)
panel_warn("failed to get panel_modes\n");
else
panel_info("get panel display modes(%d)\n",
panel->panel_modes->num_modes);
} else {
panel_warn("panel modes not found (boot_panel_id 0x%08X)\n", boot_panel_id);
}
#endif
node = find_panel_ddi_node(panel, boot_panel_id);
if (!node) {
panel_err("panel not found (boot_panel_id 0x%08X)\n", boot_panel_id);
node = of_parse_phandle(dev->of_node, "ddi-info", 0);
if (!node) {
panel_err("failed to get phandle of ddi-info\n");
return -EINVAL;
}
}
panel->ap_vendor_setting_node = node;
panel->panel_data.dqe_suffix =
get_panel_lut_dqe_suffix(panel, boot_panel_id);
#if defined(CONFIG_PANEL_FREQ_HOP)
panel->freq_hop_node =
get_panel_lut_freq_hop_node(panel, boot_panel_id);
#endif
panel_parse_ap_vendor_node(panel, node);
return 0;
}
static int panel_parse_panel_lookup(struct panel_device *panel)
{
struct device *dev = panel->dev;
struct panel_dt_lut *lut;
struct panel_id_mask *id_mask;
struct device_node *lookup_np, *panel_np, *node;
struct property *pp;
int ret, i, sz;
int tmparr[32];
lookup_np = of_get_child_by_name(dev->of_node, "panel-lut");
if (unlikely(!lookup_np)) {
panel_warn("No DT node for panel-lut\n");
return -EINVAL;
}
for_each_property_of_node(lookup_np, pp) {
if (!strcmp(pp->name, "name") || !strcmp(pp->name, "phandle"))
continue;
lut = kzalloc(sizeof(struct panel_dt_lut), GFP_KERNEL);
if (!lut)
return -ENOMEM;
INIT_LIST_HEAD(&lut->id_mask_list);
panel_np = of_parse_phandle(lookup_np, pp->name, 0);
if (!panel_np) {
panel_err("failed to get phandle of panel %s\n", pp->name);
return -EINVAL;
}
lut->name = panel_np->name;
node = of_parse_phandle(panel_np, "ddi", 0);
if (!node) {
panel_err("failed to get phandle of ddi\n");
return -EINVAL;
}
lut->ap_vendor_setting_node = node;
of_node_put(node);
node = of_parse_phandle(panel_np, "display-mode", 0);
if (!node) {
panel_err("failed to get phandle of display-mode\n");
return -EINVAL;
}
lut->panel_modes_node = node;
of_node_put(node);
node = of_parse_phandle(panel_np, "power-ctrl", 0);
if (!node) {
panel_err("failed to get phandle of power-ctrl\n");
return -EINVAL;
}
lut->power_ctrl_node= node;
of_node_put(node);
ret = of_property_read_string(panel_np, "dqe-suffix", &lut->dqe_suffix);
if (ret != 0 || lut->dqe_suffix == NULL) {
panel_warn("dqe-suffix is empty\n");
} else {
panel_info("found dqe-suffix:%s\n", lut->dqe_suffix);
}
#if defined(CONFIG_PANEL_FREQ_HOP)
node = of_parse_phandle(panel_np, DT_NAME_FREQ_TABLE, 0);
if (!node) {
panel_err("failed to get phandle of %s\n", DT_NAME_FREQ_TABLE);
return -EINVAL;
}
lut->freq_hop_node = node;
of_node_put(node);
#endif
sz = of_property_count_u32_elems(panel_np, "id-mask");
if (sz <= 0) {
panel_err("failed to get count of id-mask property\n");
return -EINVAL;
}
if (sz % 2 > 0) {
panel_err("id-mask value must be pair (sz:%d)\n", sz);
return -EINVAL;
}
if (sz > ARRAY_SIZE(tmparr)) {
panel_warn("id mask size exceeded (sz:%d arr-size:%ld)\n", sz, ARRAY_SIZE(tmparr));
sz = ARRAY_SIZE(tmparr);
}
memset(tmparr, 0, ARRAY_SIZE(tmparr) * sizeof(int));
ret = of_property_read_u32_array(panel_np, "id-mask", tmparr, sz);
if (ret < 0) {
panel_err("failed to get id-mask values\n");
return -EINVAL;
}
for (i = 0; i < sz / 2; i++) {
id_mask = kzalloc(sizeof(struct panel_id_mask), GFP_KERNEL);
if (!id_mask)
return -ENOMEM;
id_mask->id = tmparr[i * 2];
id_mask->mask = tmparr[i * 2 + 1];
list_add_tail(&id_mask->head, &lut->id_mask_list);
}
list_add_tail(&lut->head, &panel->panel_lut_list);
panel_info("%s name:\"%s\"\n", pp->name, lut->name);
print_panel_lut(lut);
of_node_put(panel_np);
}
of_node_put(lookup_np);
return 0;
}
int panel_parse_dt(struct panel_device *panel)
{
struct device *dev;
int ret = 0;
if (panel == NULL)
return -EINVAL;
dev = panel->dev;
if (dev == NULL)
return -ENODEV;
if (IS_ERR_OR_NULL(dev->of_node)) {
panel_err("failed to get dt info\n");
return -EINVAL;
}
panel->of_node_name = dev->of_node->name;
if (of_property_read_u32(dev->of_node, "panel,id", &panel->id))
panel_err("Invalid panel's id : %d\n", panel->id);
panel_dbg("panel-id: %d\n", panel->id);
#if !defined(CONFIG_UML)
ret = panel_parse_pinctrl(panel);
if (ret < 0) {
panel_err("panel-%d:failed to parse pinctrl\n", panel->id);
return ret;
}
#endif
ret = panel_parse_gpio(panel);
if (ret < 0) {
panel_err("panel-%d:failed to parse gpio\n", panel->id);
return ret;
}
ret = panel_parse_regulator(panel);
if (ret < 0) {
panel_err("panel-%d:failed to parse regulator\n", panel->id);
return ret;
}
ret = panel_parse_panel_lookup(panel);
if (ret < 0) {
panel_err("panel-%d:failed to parse panel lookup\n", panel->id);
return ret;
}
return ret;
}
static void disp_det_handler(struct work_struct *work)
{
int con_det_state;
int ret, disp_det_state;
struct panel_work *w = container_of(to_delayed_work(work),
struct panel_work, dwork);
struct panel_device *panel =
container_of(w, struct panel_device, work[PANEL_WORK_DISP_DET]);
struct panel_state *state = &panel->state;
ret = panel_disable_disp_det_irq(panel);
if (ret < 0)
panel_err("failed to disable disp_det irq\n");
panel_err("disp_det is abnormal state\n");
con_det_state = panel_conn_det_state(panel);
disp_det_state = panel_disp_det_state(panel);
panel_info("1'st disp_det_state: %s, con_det_state: %s, reset: %d\n",
disp_det_state == PANEL_STATE_OK ? "OK" : "NOK",
con_det_state == PANEL_STATE_OK ? "OK" : "NOK",
panel_get_gpio_value(panel, PANEL_GPIO_RESET));
/* delay for disp_det deboundce */
usleep_range(10000, 11000);
con_det_state = panel_conn_det_state(panel);
disp_det_state = panel_disp_det_state(panel);
panel_info("2'nd disp_det_state: %s, con_det_state: %s, reset: %d\n",
disp_det_state == PANEL_STATE_OK ? "OK" : "NOK",
con_det_state == PANEL_STATE_OK ? "OK" : "NOK",
panel_get_gpio_value(panel, PANEL_GPIO_RESET));
if (disp_det_state != PANEL_STATE_NOK)
goto exit;
if (con_det_state == PANEL_STATE_NOK) {
panel_dsi_set_bypass(panel, true);
panel->state.connected = PANEL_STATE_NOK;
__set_panel_power(panel, PANEL_POWER_OFF);
panel_set_bypass(panel, PANEL_BYPASS_ON);
panel_set_cur_state(panel, PANEL_STATE_OFF);
state->disp_on = PANEL_DISPLAY_OFF;
usleep_range(300000, 301000);
return;
}
if (state->cur_state == PANEL_STATE_OFF) {
panel_err("panel is off state\n");
return;
}
return;
exit:
panel_enable_disp_det_irq(panel);
panel_enable_pcd_irq(panel);
}
void panel_send_ubconn_uevent(struct panel_device *panel)
{
char *uevent_conn_str[3] = {
"CONNECTOR_NAME=UB_CONNECT",
"CONNECTOR_TYPE=HIGH_LEVEL",
NULL,
};
if (!panel->lcd_dev)
return;
kobject_uevent_env(&panel->lcd_dev->kobj, KOBJ_CHANGE, uevent_conn_str);
panel_info("%s, %s\n", uevent_conn_str[0], uevent_conn_str[1]);
}
#if IS_ENABLED(CONFIG_SEC_ABC)
void panel_send_ubconn_to_abc(struct panel_device *panel)
{
#if IS_ENABLED(CONFIG_SEC_FACTORY)
sec_abc_send_event("MODULE=ub_main@INFO=ub_disconnected");
#else
sec_abc_send_event("MODULE=ub_main@WARN=ub_disconnected");
#endif
panel_info("done\n");
}
#endif
void conn_det_handler(struct work_struct *data)
{
struct panel_work *w = container_of(to_delayed_work(data),
struct panel_work, dwork);
struct panel_device *panel =
container_of(w, struct panel_device, work[PANEL_WORK_CONN_DET]);
bool is_disconnected;
is_disconnected = panel_disconnected(panel);
panel_info("%s state:%d cnt:%d\n", __func__,
is_disconnected, panel->panel_data.props.ub_con_cnt);
#ifdef CONFIG_PANEL_NOTIFY
panel_send_ubconn_notify((is_disconnected ?
PANEL_EVENT_UB_CON_DISCONNECTED : PANEL_EVENT_UB_CON_CONNECTED));
#endif
if (!is_disconnected)
return;
if (panel->panel_data.props.conn_det_enable) {
panel_send_ubconn_uevent(panel);
#if IS_ENABLED(CONFIG_SEC_ABC)
panel_send_ubconn_to_abc(panel);
#endif
}
panel->panel_data.props.ub_con_cnt++;
/* OCTA: power off is handled in disp_det handler */
/* TFT(if doesn't have disp_det): power off here */
if (!panel_is_gpio_valid(panel, PANEL_GPIO_DISP_DET)) {
panel_dsi_set_bypass(panel, true);
panel->state.connected = PANEL_STATE_NOK;
/* if panel_bypass is set, then power off will be returned */
/* turn off panel power here first */
__set_panel_power(panel, PANEL_POWER_OFF);
panel_set_bypass(panel, PANEL_BYPASS_ON);
panel_set_cur_state(panel, PANEL_STATE_OFF);
#ifdef CONFIG_MCD_PANEL_FACTORY
panel_emergency_off(panel);
#endif
usleep_range(300000, 301000);
}
}
void pcd_handler(struct work_struct *data)
{
struct panel_work *w = container_of(to_delayed_work(data),
struct panel_work, dwork);
struct panel_device *panel =
container_of(w, struct panel_device, work[PANEL_WORK_PCD]);
int pcd_state;
pcd_state = panel_pcd_state(panel);
if (pcd_state == PANEL_STATE_NOK)
panel_dsi_set_bypass(panel, true); /* ignore TE checking & frame update request*/
panel_info("state:%d\n", pcd_state);
}
void err_fg_handler(struct work_struct *data)
{
#ifdef CONFIG_SUPPORT_ERRFG_RECOVERY
int ret, err_fg_state;
bool err_fg_recovery = false, err_fg_powerdown = false;
struct panel_work *w = container_of(to_delayed_work(data),
struct panel_work, dwork);
struct panel_device *panel =
container_of(w, struct panel_device, work[PANEL_WORK_ERR_FG]);
struct panel_state *state = &panel->state;
err_fg_recovery = panel->panel_data.ddi_props.err_fg_recovery;
err_fg_powerdown = panel->panel_data.ddi_props.err_fg_powerdown;
err_fg_state = panel_err_fg_state(panel);
panel_info("err_fg_state:%s recover:%s powerdown: %s\n",
err_fg_state == PANEL_STATE_OK ? "OK" : "NOK",
err_fg_recovery ? "true" : "false",
err_fg_powerdown ? "true" : "false");
if (!(err_fg_recovery || err_fg_powerdown))
return;
switch (state->cur_state) {
case PANEL_STATE_ALPM:
case PANEL_STATE_NORMAL:
if (err_fg_state == PANEL_STATE_NOK) {
ret = panel_disable_gpio_irq(panel, PANEL_GPIO_ERR_FG);
if (ret < 0)
panel_warn("do not support irq\n");
/* delay for disp_det deboundce */
usleep_range(10000, 11000);
if (err_fg_powerdown) {
panel_err("powerdown: err_fg is abnormal state\n");
#ifdef CONFIG_PANEL_NOTIFY
panel_send_ubconn_notify(PANEL_EVENT_UB_CON_DISCONNECTED);
#endif
ret = panel_powerdown_cb(panel);
if (ret)
panel_err("failed to powerdown_cb\n");
#ifdef CONFIG_PANEL_NOTIFY
panel_send_ubconn_notify(PANEL_EVENT_UB_CON_CONNECTED);
#endif
} else if (err_fg_recovery) {
panel_err("recovery: err_fg is abnormal state\n");
ret = panel_error_cb(panel);
if (ret)
panel_err("failed to recover_cb\n");
}
ret = panel_enable_gpio_irq(panel, PANEL_GPIO_ERR_FG);
if (ret < 0)
panel_warn("do not support irq\n");
}
break;
default:
break;
}
#endif
panel_info("done\n");
}
static int panel_fb_notifier(struct notifier_block *self, unsigned long event, void *data)
{
#if 0
int *blank = NULL;
struct panel_device *panel;
struct fb_event *fb_event = data;
switch (event) {
case FB_EARLY_EVENT_BLANK:
case FB_EVENT_BLANK:
break;
case FB_EVENT_FB_REGISTERED:
panel_dbg("FB Registeted\n");
return 0;
default:
return 0;
}
panel = container_of(self, struct panel_device, fb_notif);
blank = fb_event->data;
if (!blank || !panel) {
panel_err("blank is null\n");
return 0;
}
switch (*blank) {
case FB_BLANK_POWERDOWN:
case FB_BLANK_NORMAL:
if (event == FB_EARLY_EVENT_BLANK)
panel_dbg("EARLY BLANK POWER DOWN\n");
else
panel_dbg("BLANK POWER DOWN\n");
break;
case FB_BLANK_UNBLANK:
if (event == FB_EARLY_EVENT_BLANK)
panel_dbg("EARLY UNBLANK\n");
else
panel_dbg("UNBLANK\n");
break;
}
#endif
return 0;
}
#ifdef CONFIG_DISPLAY_USE_INFO
unsigned int g_rddpm = 0xFF;
unsigned int g_rddsm = 0xFF;
unsigned int get_panel_bigdata(void)
{
unsigned int val = 0;
val = (g_rddsm << 8) | g_rddpm;
return val;
}
static int panel_dpui_notifier_callback(struct notifier_block *self,
unsigned long event, void *data)
{
struct panel_info *panel_data;
struct panel_device *panel;
struct common_panel_info *info;
int panel_id;
struct dpui_info *dpui = data;
char tbuf[MAX_DPUI_VAL_LEN];
u8 panel_datetime[7] = { 0, };
u8 panel_coord[4] = { 0, };
int i, site, rework, poc;
u8 cell_id[16], octa_id[PANEL_OCTA_ID_LEN] = { 0, };
bool cell_id_exist = true;
int size;
if (dpui == NULL) {
panel_err("dpui is null\n");
return 0;
}
panel = container_of(self, struct panel_device, panel_dpui_notif);
panel_data = &panel->panel_data;
panel_id = panel_data->id[0] << 16 | panel_data->id[1] << 8 | panel_data->id[2];
info = find_panel(panel, panel_id);
if (unlikely(!info)) {
panel_err("panel not found\n");
return -ENODEV;
}
resource_copy_by_name(panel_data, panel_datetime, "date");
size = snprintf(tbuf, MAX_DPUI_VAL_LEN, "%04d%02d%02d %02d%02d%02d",
((panel_datetime[0] & 0xF0) >> 4) + 2011, panel_datetime[0] & 0xF, panel_datetime[1] & 0x1F,
panel_datetime[2] & 0x1F, panel_datetime[3] & 0x3F, panel_datetime[4] & 0x3F);
set_dpui_field(DPUI_KEY_MAID_DATE, tbuf, size);
size = snprintf(tbuf, MAX_DPUI_VAL_LEN, "%d", panel_data->id[0]);
set_dpui_field(DPUI_KEY_LCDID1, tbuf, size);
size = snprintf(tbuf, MAX_DPUI_VAL_LEN, "%d", panel_data->id[1]);
set_dpui_field(DPUI_KEY_LCDID2, tbuf, size);
size = snprintf(tbuf, MAX_DPUI_VAL_LEN, "%d", panel_data->id[2]);
set_dpui_field(DPUI_KEY_LCDID3, tbuf, size);
resource_copy_by_name(panel_data, panel_coord, "coordinate");
size = snprintf(tbuf, MAX_DPUI_VAL_LEN, "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
panel_datetime[0], panel_datetime[1], panel_datetime[2], panel_datetime[3],
panel_datetime[4], panel_datetime[5], panel_datetime[6],
panel_coord[0], panel_coord[1], panel_coord[2], panel_coord[3]);
set_dpui_field(DPUI_KEY_CELLID, tbuf, size);
/* OCTAID */
resource_copy_by_name(panel_data, octa_id, "octa_id");
site = (octa_id[0] >> 4) & 0x0F;
rework = octa_id[0] & 0x0F;
poc = octa_id[1] & 0x0F;
for (i = 0; i < 16; i++) {
cell_id[i] = isalnum(octa_id[i + 4]) ? octa_id[i + 4] : '\0';
if (cell_id[i] == '\0') {
cell_id_exist = false;
break;
}
}
size = snprintf(tbuf, MAX_DPUI_VAL_LEN, "%d%d%d%02x%02x",
site, rework, poc, octa_id[2], octa_id[3]);
if (cell_id_exist) {
for (i = 0; i < 16; i++)
size += snprintf(tbuf + size, MAX_DPUI_VAL_LEN - size, "%c", cell_id[i]);
}
set_dpui_field(DPUI_KEY_OCTAID, tbuf, size);
#ifdef CONFIG_SUPPORT_DIM_FLASH
size = snprintf(tbuf, MAX_DPUI_VAL_LEN,
"%d", panel->work[PANEL_WORK_DIM_FLASH].ret);
set_dpui_field(DPUI_KEY_PNGFLS, tbuf, size);
#endif
inc_dpui_u32_field(DPUI_KEY_UB_CON, panel->panel_data.props.ub_con_cnt);
panel->panel_data.props.ub_con_cnt = 0;
return 0;
}
#endif /* CONFIG_DISPLAY_USE_INFO */
#ifdef CONFIG_SUPPORT_TDMB_TUNE
static int panel_tdmb_notifier_callback(struct notifier_block *nb,
unsigned long action, void *data)
{
struct panel_info *panel_data;
struct panel_device *panel;
struct tdmb_notifier_struct *value = data;
int ret;
panel = container_of(nb, struct panel_device, tdmb_notif);
panel_data = &panel->panel_data;
mutex_lock(&panel->io_lock);
mutex_lock(&panel->op_lock);
switch (value->event) {
case TDMB_NOTIFY_EVENT_TUNNER:
panel_data->props.tdmb_on = value->tdmb_status.pwr;
if (!IS_PANEL_ACTIVE(panel)) {
panel_info("keep tdmb state (%s) and affect later\n",
panel_data->props.tdmb_on ? "on" : "off");
break;
}
panel_info("tdmb state (%s)\n",
panel_data->props.tdmb_on ? "on" : "off");
ret = panel_do_seqtbl_by_index_nolock(panel, PANEL_TDMB_TUNE_SEQ);
if (unlikely(ret < 0))
panel_err("failed to write tdmb-tune seqtbl\n");
panel_data->props.cur_tdmb_on = panel_data->props.tdmb_on;
break;
default:
break;
}
mutex_unlock(&panel->op_lock);
mutex_unlock(&panel->io_lock);
return 0;
}
#endif
#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER)
static int panel_input_notifier_callback(struct notifier_block *nb,
unsigned long data, void *v)
{
struct panel_device *panel =
container_of(nb, struct panel_device, input_notif);
struct panel_info *panel_data = &panel->panel_data;
switch (data) {
case NOTIFIER_LCD_VRR_LFD_LOCK_REQUEST: /* set LFD min lock */
case NOTIFIER_LCD_VRR_LFD_LOCK_RELEASE: /* unset LFD min lock */
/* TODO : input scalability need to be passed by dtsi or panel config */
if (panel_vrr_lfd_is_supported(panel)) {
panel_data->props.vrr_lfd_info.req[VRR_LFD_CLIENT_INPUT][VRR_LFD_SCOPE_NORMAL].scalability =
(data == NOTIFIER_LCD_VRR_LFD_LOCK_REQUEST) ?
VRR_LFD_SCALABILITY_2 : VRR_LFD_SCALABILITY_NONE;
queue_delayed_work(panel->work[PANEL_WORK_UPDATE].wq,
&panel->work[PANEL_WORK_UPDATE].dwork, msecs_to_jiffies(0));
}
break;
}
return 0;
}
#endif
#if defined(CONFIG_SUPPORT_FAST_DISCHARGE)
int panel_fast_discharge_set(struct panel_device *panel)
{
int ret = 0;
mutex_lock(&panel->io_lock);
mutex_lock(&panel->op_lock);
ret = panel_do_seqtbl_by_index_nolock(panel, PANEL_FD_SEQ);
if (unlikely(ret < 0))
panel_err("failed to write fast discharge seqtbl\n");
mutex_unlock(&panel->op_lock);
mutex_unlock(&panel->io_lock);
return ret;
}
#endif
static int panel_init_work(struct panel_work *w,
char *name, panel_wq_handler handler)
{
if (w == NULL || name == NULL || handler == NULL) {
panel_err("invalid parameter\n");
return -EINVAL;
}
mutex_init(&w->lock);
strncpy(w->name, name, MAX_PANEL_WORK_NAME_SIZE - 1);
INIT_DELAYED_WORK(&w->dwork, handler);
w->wq = create_singlethread_workqueue(name);
if (w->wq == NULL) {
panel_err("failed to create %s workqueue\n", name);
return -ENOMEM;
}
atomic_set(&w->running, 0);
panel_info("%s:done\n", name);
return 0;
}
static int panel_drv_init_work(struct panel_device *panel)
{
int i, ret;
char name[MAX_PANEL_WORK_NAME_SIZE];
for (i = 0; i < PANEL_WORK_MAX; i++) {
if (!panel_wq_handlers[i])
continue;
snprintf(name, MAX_PANEL_WORK_NAME_SIZE, "panel%d:%s",
panel->id, panel_work_names[i]);
ret = panel_init_work(&panel->work[i],
name, panel_wq_handlers[i]);
if (ret < 0) {
panel_err("failed to initialize panel_work(%s)\n",
panel_work_names[i]);
return ret;
}
}
return 0;
}
static int panel_create_thread(struct panel_device *panel)
{
size_t i;
if (unlikely(!panel)) {
panel_warn("panel is null\n");
return 0;
}
for (i = 0; i < ARRAY_SIZE(panel->thread); i++) {
if (panel_thread_fns[i] == NULL)
continue;
panel_info("%s-%d:init\n",
panel_thread_names[i], panel->id);
panel->thread[i].should_stop = false;
init_waitqueue_head(&panel->thread[i].wait);
panel->thread[i].thread =
kthread_run(panel_thread_fns[i], panel,
"%s-%d", panel_thread_names[i], panel->id);
if (IS_ERR_OR_NULL(panel->thread[i].thread)) {
panel_err("failed to run %s-%d thread\n",
panel_thread_names[i], panel->id);
panel->thread[i].thread = NULL;
return PTR_ERR(panel->thread[i].thread);
}
panel_info("%s-%d:done\n",
panel_thread_names[i], panel->id);
}
return 0;
}
static int panel_destroy_thread(struct panel_device *panel)
{
size_t i;
if (unlikely(!panel)) {
panel_warn("panel is null\n");
return 0;
}
for (i = 0; i < ARRAY_SIZE(panel->thread); i++) {
if (panel_thread_fns[i] == NULL)
continue;
if (IS_ERR_OR_NULL(panel->thread[i].thread))
continue;
panel->thread[i].should_stop = true;
/* wake up waitqueue to stop */
wake_up_interruptible_all(&panel->thread[i].wait);
/* kthread_should_stop() == true */
kthread_stop(panel->thread[i].thread);
}
return 0;
}
struct panel_device *panel_device_create(void)
{
struct panel_device *panel;
panel = kzalloc(sizeof(struct panel_device), GFP_KERNEL);
if (!panel)
return NULL;
return panel;
}
EXPORT_SYMBOL(panel_device_create);
void panel_device_destroy(struct panel_device *panel)
{
kfree(panel);
}
EXPORT_SYMBOL(panel_device_destroy);
int panel_device_register_notifiers(struct panel_device *panel)
{
int ret;
if (!panel)
return -EINVAL;
panel->fb_notif.notifier_call = panel_fb_notifier;
ret = fb_register_client(&panel->fb_notif);
if (ret < 0) {
panel_err("failed to register fb notifier callback\n");
return ret;
}
#ifdef CONFIG_DISPLAY_USE_INFO
panel->panel_dpui_notif.notifier_call = panel_dpui_notifier_callback;
ret = dpui_logging_register(&panel->panel_dpui_notif, DPUI_TYPE_PANEL);
if (ret < 0) {
panel_err("failed to register dpui notifier callback\n");
return ret;
}
#endif
#ifdef CONFIG_SUPPORT_TDMB_TUNE
ret = tdmb_notifier_register(&panel->tdmb_notif,
panel_tdmb_notifier_callback, TDMB_NOTIFY_DEV_LCD);
if (ret < 0) {
panel_err("failed to register tdmb notifier callback\n");
return ret;
}
#endif
#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER)
sec_input_register_notify(&panel->input_notif,
panel_input_notifier_callback, 3);
#endif
return 0;
}
struct panel_drv_funcs panel_drv_funcs = {
.register_error_cb = panel_register_error_cb,
.register_cb = panel_register_cb,
.get_panel_state = panel_ioctl_get_panel_state,
.attach_adapter = panel_ioctl_attach_adapter,
.probe = panel_probe,
.sleep_in = panel_sleep_in,
.sleep_out = panel_sleep_out,
.display_on = panel_display_on,
.display_off = panel_display_off,
.power_on = panel_power_on,
.power_off = panel_power_off,
.debug_dump = panel_debug_dump,
#ifdef CONFIG_MCD_PANEL_LPM
.doze = panel_doze,
.doze_suspend = panel_doze,
#endif
#ifdef CONFIG_SUPPORT_DSU
.set_mres = panel_set_mres,
#endif
.get_mres = NULL,
.set_display_mode = panel_set_display_mode,
.get_display_mode = panel_get_display_mode,
.reset_lp11 = panel_reset_lp11,
.frame_done = panel_ioctl_event_frame_done,
.vsync = panel_ioctl_event_vsync,
#ifdef CONFIG_SUPPORT_MASK_LAYER
.set_mask_layer = panel_set_mask_layer,
#endif
.req_set_clock = panel_request_set_clock,
.get_ddi_props = panel_get_ddi_props,
.get_rcd_info = panel_get_rcd_info,
};
int panel_device_init(struct panel_device *panel)
{
int ret;
if (!panel)
return -EINVAL;
mutex_init(&panel->cmdq.lock);
panel->cmdq.top = -1;
panel->state.init_at = PANEL_INIT_BOOT;
panel_set_bypass(panel, PANEL_BYPASS_OFF);
panel->state.connected = true;
panel_set_cur_state(panel, PANEL_STATE_OFF);
panel->state.power = PANEL_POWER_OFF;
panel->state.disp_on = PANEL_DISPLAY_OFF;
panel->ktime_panel_on = ktime_get();
#ifdef CONFIG_SUPPORT_HMD
panel->state.hmd_on = PANEL_HMD_OFF;
#endif
mutex_init(&panel->op_lock);
mutex_init(&panel->data_lock);
mutex_init(&panel->io_lock);
#ifdef CONFIG_EXYNOS_DECON_LCD_COPR
mutex_init(&panel->copr.lock);
#endif
panel->funcs = &panel_drv_funcs;
INIT_LIST_HEAD(&panel->gpio_list);
INIT_LIST_HEAD(&panel->regulator_list);
INIT_LIST_HEAD(&panel->power_ctrl_list);
INIT_LIST_HEAD(&panel->panel_lut_list);
#ifdef CONFIG_MCD_PANEL_I2C
ret = panel_i2c_drv_probe(panel);
if (ret < 0) {
panel_err("panel-%d:failed to parse i2c\n", panel->id);
return ret;
}
#endif
#ifdef CONFIG_MCD_PANEL_BLIC
ret = panel_blic_probe(panel);
if (ret < 0) {
panel_err("panel-%d:failed to parse blic\n", panel->id);
return ret;
}
#endif
ret = panel_obj_init(&panel->properties);
if (ret < 0) {
panel_err("panel-%d:failed to init panel properties\n", panel->id);
return ret;
}
panel_parse_dt(panel);
ret = panel_create_lcd_device(panel, panel->id);
if (ret < 0) {
panel_err("failed to create lcd device\n");
return -EINVAL;
}
panel_drv_set_gpios(panel);
panel_drv_init_work(panel);
panel_create_thread(panel);
//remove debugfs
#if 1
#ifdef CONFIG_PANEL_DEBUG
panel_create_debugfs(panel);
#endif
#endif
/* sub-modules init */
ret = panel_bl_init(&panel->panel_bl);
if (ret < 0) {
panel_err("failed to init panel_bl\n");
return -ENODEV;
}
#ifdef CONFIG_EXYNOS_DECON_MDNIE_LITE
ret = mdnie_init(&panel->mdnie);
if (ret < 0) {
panel_err("failed to init mdnie\n");
return ret;
}
#endif
ret = panel_device_register_notifiers(panel);
if (ret < 0) {
panel_err("failed to register notifiers\n");
return ret;
}
ret = panel_register_isr(panel);
if (ret < 0) {
panel_err("failed to register isr\n");
return ret;
}
#ifdef CONFIG_EXYNOS_DECON_LCD_SYSFS
ret = panel_sysfs_probe(panel);
if (ret < 0) {
panel_err("failed to init sysfs\n");
return -ENODEV;
}
#endif
panel_info("done\n");
return 0;
}
EXPORT_SYMBOL(panel_device_init);
int panel_device_exit(struct panel_device *panel)
{
int ret;
if (!panel)
return -EINVAL;
#ifdef CONFIG_EXYNOS_DECON_LCD_SYSFS
ret = panel_sysfs_remove(panel);
if (ret < 0) {
panel_err("failed to remove panel-sysfs driver\n");
return ret;
}
#endif
#ifdef CONFIG_EXYNOS_DECON_MDNIE_LITE
ret = mdnie_exit(&panel->mdnie);
if (ret < 0) {
panel_err("failed to exit mdnie driver\n");
return ret;
}
#endif
ret = panel_destroy_lcd_device(panel);
if (ret < 0) {
panel_err("failed to destroy lcd device\n");
return ret;
}
ret = panel_remove(panel);
if (ret < 0) {
panel_err("failed to remove panel\n");
return ret;
}
/* TODO: unregister isr */
// if (panel->max_nr_dim_flash_result > 0)
// kfree(panel->dim_flash_result);
panel_destroy_thread(panel);
#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER)
sec_input_unregister_notify(&panel->input_notif);
#endif
#ifdef CONFIG_DISPLAY_USE_INFO
dpui_logging_unregister(&panel->panel_dpui_notif);
#endif
fb_unregister_client(&panel->fb_notif);
#if 0
#ifdef CONFIG_PANEL_DEBUG
panel_destroy_debugfs(panel);
#endif
#endif
kfree(panel->cmdbuf);
return 0;
}
EXPORT_SYMBOL(panel_device_exit);
static int panel_drv_probe(struct platform_device *pdev)
{
struct panel_device *panel;
int ret;
panel_info("...");
if (!pdev)
return -EINVAL;
panel = panel_device_create();
if (!panel)
return -ENOMEM;
panel->id = -1;
panel->dev = &pdev->dev;
platform_set_drvdata(pdev, panel);
ret = panel_device_init(panel);
if (ret < 0) {
panel_err("failed to initialize panel device\n");
goto err;
}
panel_info("done");
return 0;
err:
panel_device_destroy(panel);
return ret;
}
static int panel_drv_remove(struct platform_device *pdev)
{
struct panel_device *panel;
panel = platform_get_drvdata(pdev);
panel_device_destroy(panel);
return 0;
}
static const struct of_device_id panel_drv_of_match_table[] = {
{ .compatible = "samsung,panel-drv", },
{ },
};
MODULE_DEVICE_TABLE(of, panel_drv_of_match_table);
struct platform_driver panel_driver = {
.probe = panel_drv_probe,
.remove = panel_drv_remove,
.driver = {
.name = PANEL_DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(panel_drv_of_match_table),
}
};
#ifdef CONFIG_SUPPORT_MAFPC
extern struct platform_driver mafpc_driver;
#endif
static int __init panel_drv_init(void)
{
int ret;
panel_info("++\n");
ret = panel_create_lcd_class();
if (ret < 0) {
panel_err("panel_create_lcd_class returned %d\n", ret);
return -EINVAL;
}
#ifdef CONFIG_SUPPORT_MAFPC
ret = platform_driver_register(&mafpc_driver);
if (ret) {
panel_err("failed to register mafpc driver\n");
return ret;
}
#endif
ret = platform_driver_register(&panel_driver);
if (ret) {
panel_err("failed to register panel driver\n");
return ret;
}
panel_info("--\n");
return ret;
}
static void __exit panel_drv_exit(void)
{
platform_driver_unregister(&panel_driver);
#ifdef CONFIG_SUPPORT_MAFPC
platform_driver_unregister(&mafpc_driver);
#endif
panel_destroy_lcd_class();
}
#ifdef CONFIG_EXYNOS_DPU30_DUAL
device_initcall_sync(panel_drv_init);
#else
module_init(panel_drv_init);
#endif
module_exit(panel_drv_exit);
MODULE_SOFTDEP("pre: s2dos05-regulator i2c-exynos5");
MODULE_DESCRIPTION("Samsung's Panel Driver");
MODULE_AUTHOR("<minwoo7945.kim@samsung.com>");
MODULE_LICENSE("GPL");