blob: 16af7a316e09ae0fab1ddf1360de4e0fc7533dca [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
*
* Copyright (C) 2019 Amlogic, Inc. All rights reserved.
*
*/
#include <linux/init.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/of.h>
#include <linux/reset.h>
#include <linux/clk.h>
#ifdef CONFIG_AMLOGIC_VPU
#include <linux/amlogic/media/vpu/vpu.h>
#endif
#include <linux/amlogic/media/vout/vinfo.h>
#include <linux/amlogic/media/vout/vout_notify.h>
#include <linux/amlogic/media/vout/lcd/lcd_vout.h>
#include <linux/amlogic/media/vout/lcd/lcd_unifykey.h>
#include <linux/amlogic/media/vout/lcd/lcd_notify.h>
#include "lcd_tablet.h"
#include "../lcd_reg.h"
#include "../lcd_common.h"
#include "mipi_dsi_util.h"
/* **************************************************
* vout server api
* **************************************************
*/
static struct vinfo_s *lcd_get_current_info(void *data)
{
struct aml_lcd_drv_s *pdrv = (struct aml_lcd_drv_s *)data;
if (!pdrv)
return NULL;
return &pdrv->vinfo;
}
static int lcd_check_same_vmodeattr(char *mode, void *data)
{
return 1;
}
static int lcd_vmode_is_supported(enum vmode_e mode, void *data)
{
mode &= VMODE_MODE_BIT_MASK;
if (lcd_debug_print_flag & LCD_DBG_PR_NORMAL)
LCDPR("%s vmode = %d\n", __func__, mode);
if (mode == VMODE_LCD)
return true;
return false;
}
static enum vmode_e lcd_validate_vmode(char *mode, unsigned int frac,
void *data)
{
struct aml_lcd_drv_s *pdrv = (struct aml_lcd_drv_s *)data;
if (!pdrv || !mode)
return VMODE_MAX;
if (frac)
return VMODE_MAX;
if (lcd_debug_print_flag & LCD_DBG_PR_NORMAL) {
LCDPR("[%d]: %s: display_mode=%s, drv_mode=%s\n",
pdrv->index, __func__, mode, pdrv->vinfo.name);
}
if ((strcmp(mode, pdrv->vinfo.name)) == 0)
return VMODE_LCD;
return VMODE_MAX;
}
static int lcd_set_current_vmode(enum vmode_e mode, void *data)
{
struct aml_lcd_drv_s *pdrv = (struct aml_lcd_drv_s *)data;
int ret = 0;
if (!pdrv)
return -1;
mutex_lock(&lcd_power_mutex);
pdrv->vrr_dev = kzalloc(sizeof(*pdrv->vrr_dev), GFP_KERNEL);
if (pdrv->vrr_dev) {
sprintf(pdrv->vrr_dev->name, "lcd%d_dev", pdrv->index);
pdrv->vrr_dev->output_src = VRR_OUTPUT_ENCL;
if (pdrv->config.timing.fr_adjust_type == 2) /* vtotal adj */
pdrv->vrr_dev->enable = 1;
else
pdrv->vrr_dev->enable = 0;
pdrv->vrr_dev->vline_max = pdrv->config.basic.v_period_max;
pdrv->vrr_dev->vline_min = pdrv->config.basic.v_period_min;
pdrv->vrr_dev->vfreq_max = pdrv->config.basic.frame_rate_max;
pdrv->vrr_dev->vfreq_min = pdrv->config.basic.frame_rate_min;
aml_vrr_register_device(pdrv->vrr_dev, pdrv->index);
}
if (lcd_debug_print_flag & LCD_DBG_PR_NORMAL) {
LCDPR("[%d]: %s: drv_mode=%s\n",
pdrv->index, __func__, pdrv->vinfo.name);
}
if (VMODE_LCD == (mode & VMODE_MODE_BIT_MASK)) {
if (mode & VMODE_INIT_BIT_MASK) {
lcd_clk_gate_switch(pdrv, 1);
} else {
aml_lcd_notifier_call_chain(LCD_EVENT_POWER_ON, (void *)pdrv);
lcd_if_enable_retry(pdrv);
}
} else {
ret = -EINVAL;
}
pdrv->status |= LCD_STATUS_VMODE_ACTIVE;
mutex_unlock(&lcd_power_mutex);
return ret;
}
static int lcd_vout_disable(enum vmode_e cur_vmod, void *data)
{
struct aml_lcd_drv_s *pdrv = (struct aml_lcd_drv_s *)data;
if (!pdrv)
return -1;
mutex_lock(&lcd_power_mutex);
if (pdrv->vrr_dev) {
aml_vrr_unregister_device(pdrv->index);
kfree(pdrv->vrr_dev);
pdrv->vrr_dev = NULL;
}
pdrv->status &= ~LCD_STATUS_VMODE_ACTIVE;
aml_lcd_notifier_call_chain(LCD_EVENT_POWER_OFF, (void *)pdrv);
LCDPR("%s finished\n", __func__);
mutex_unlock(&lcd_power_mutex);
return 0;
}
static int lcd_vout_set_state(int index, void *data)
{
struct aml_lcd_drv_s *pdrv = (struct aml_lcd_drv_s *)data;
if (!pdrv)
return -1;
pdrv->vout_state |= (1 << index);
pdrv->viu_sel = index;
return 0;
}
static int lcd_vout_clr_state(int index, void *data)
{
struct aml_lcd_drv_s *pdrv = (struct aml_lcd_drv_s *)data;
if (!pdrv)
return -1;
pdrv->vout_state &= ~(1 << index);
if (pdrv->viu_sel == index)
pdrv->viu_sel = LCD_VIU_SEL_NONE;
return 0;
}
static int lcd_vout_get_state(void *data)
{
struct aml_lcd_drv_s *pdrv = (struct aml_lcd_drv_s *)data;
return pdrv->vout_state;
}
static int lcd_vout_get_disp_cap(char *buf, void *data)
{
int ret = 0;
ret += sprintf(buf + ret, "%s\n", "panel");
return ret;
}
struct lcd_vframe_match_s {
int frame_rate; /* *100 */
unsigned int duration_num;
unsigned int duration_den;
unsigned int frac;
};
static struct lcd_vframe_match_s lcd_vframe_match_table_1[] = {
{5000, 50, 1, 0},
{6000, 60, 1, 0},
{5994, 5994, 100, 1}
};
static struct lcd_vframe_match_s lcd_vframe_match_table_2[] = {
{5000, 50, 1, 0},
{6000, 60, 1, 0},
{4800, 48, 1, 0},
{5994, 5994, 100, 1}
};
static int lcd_framerate_automation_set_mode(struct aml_lcd_drv_s *pdrv)
{
if (!pdrv)
return -1;
LCDPR("%s\n", __func__);
/* update lcd config sync_duration, for calculate */
pdrv->config.timing.sync_duration_num = pdrv->vinfo.sync_duration_num;
pdrv->config.timing.sync_duration_den = pdrv->vinfo.sync_duration_den;
/* update clk & timing config */
lcd_tablet_config_update(pdrv);
/* update interface timing if needed, current no need */
#ifdef CONFIG_AMLOGIC_VPU
vpu_dev_clk_request(pdrv->lcd_vpu_dev, pdrv->config.timing.lcd_clk);
#endif
/* change clk parameter */
lcd_clk_change(pdrv);
lcd_tablet_config_post_update(pdrv);
lcd_venc_change(pdrv);
lcd_vout_notify_mode_change(pdrv);
return 0;
}
static int lcd_set_vframe_rate_hint(int duration, void *data)
{
struct aml_lcd_drv_s *pdrv = (struct aml_lcd_drv_s *)data;
struct vinfo_s *info;
unsigned int frame_rate = 6000;
unsigned int duration_num = 60, duration_den = 1, frac = 0;
struct lcd_vframe_match_s *vtable = lcd_vframe_match_table_1;
int i, n, find = 0;
if (!pdrv)
return -1;
if (pdrv->probe_done == 0)
return -1;
if ((pdrv->status & LCD_STATUS_ENCL_ON) == 0) {
LCDPR("%s: lcd is disabled, exit\n", __func__);
return -1;
}
if (lcd_fr_is_fixed(pdrv)) {
LCDPR("%s: fixed timing, exit\n", __func__);
return -1;
}
if (lcd_debug_print_flag & LCD_DBG_PR_NORMAL)
LCDPR("fr_auto_policy = %d\n", pdrv->fr_auto_policy);
info = &pdrv->vinfo;
switch (pdrv->fr_auto_policy) {
case 1:
vtable = lcd_vframe_match_table_1;
n = ARRAY_SIZE(lcd_vframe_match_table_1);
break;
case 2:
vtable = lcd_vframe_match_table_2;
n = ARRAY_SIZE(lcd_vframe_match_table_2);
break;
default:
LCDPR("%s: fr_auto_policy = %d, disabled\n",
__func__, pdrv->fr_auto_policy);
return 0;
}
if (duration == 0) { /* end hint */
LCDPR("%s: return mode = %s, policy = %d\n", __func__,
info->name, pdrv->fr_auto_policy);
if (pdrv->fr_mode == 0) {
LCDPR("%s: fr_mode is invalid, exit\n", __func__);
return 0;
}
pdrv->fr_duration = 0;
/* update vinfo */
info->sync_duration_num = pdrv->std_duration.duration_num;
info->sync_duration_den = pdrv->std_duration.duration_den;
info->frac = 0;
pdrv->fr_mode = 0;
} else {
for (i = 0; i < n; i++) {
if (duration == vtable[i].frame_rate) {
frame_rate = vtable[i].frame_rate;
duration_num = vtable[i].duration_num;
duration_den = vtable[i].duration_den;
frac = vtable[i].frac;
find = 1;
}
}
if (find == 0) {
LCDERR("%s: can't support duration %d\n, exit\n",
__func__, duration);
return -1;
}
LCDPR("%s: policy = %d, duration = %d, frame_rate = %d\n",
__func__, pdrv->fr_auto_policy,
duration, frame_rate);
pdrv->fr_duration = duration;
/* if the sync_duration is same as current */
if (duration_num == info->sync_duration_num &&
duration_den == info->sync_duration_den) {
LCDPR("%s: sync_duration is the same, exit\n",
__func__);
return 0;
}
/* update vinfo */
info->sync_duration_num = duration_num;
info->sync_duration_den = duration_den;
info->frac = frac;
pdrv->fr_mode = duration;
}
lcd_framerate_automation_set_mode(pdrv);
return 0;
}
static int lcd_get_vframe_rate_hint(void *data)
{
struct aml_lcd_drv_s *pdrv = (struct aml_lcd_drv_s *)data;
if (!pdrv)
return 0;
return pdrv->fr_mode;
}
static void lcd_vout_debug_test(unsigned int num, void *data)
{
struct aml_lcd_drv_s *pdrv = (struct aml_lcd_drv_s *)data;
if (!pdrv)
return;
lcd_debug_test(pdrv, num);
}
static int lcd_suspend(void *data)
{
struct aml_lcd_drv_s *pdrv = (struct aml_lcd_drv_s *)data;
if (!pdrv)
return -1;
mutex_lock(&lcd_power_mutex);
pdrv->resume_flag &= ~LCD_RESUME_ENABLE;
aml_lcd_notifier_call_chain(LCD_EVENT_DISABLE, (void *)pdrv);
LCDPR("[%d]: early_suspend finished\n", pdrv->index);
mutex_unlock(&lcd_power_mutex);
return 0;
}
static int lcd_resume(void *data)
{
struct aml_lcd_drv_s *pdrv = (struct aml_lcd_drv_s *)data;
if (!pdrv)
return -1;
if ((pdrv->status & LCD_STATUS_VMODE_ACTIVE) == 0)
return 0;
if (pdrv->resume_flag & LCD_RESUME_ENABLE)
return 0;
if (pdrv->resume_type) {
lcd_queue_work(&pdrv->late_resume_work);
} else {
mutex_lock(&lcd_power_mutex);
LCDPR("[%d]: directly lcd late_resume\n", pdrv->index);
pdrv->resume_flag |= LCD_RESUME_ENABLE;
aml_lcd_notifier_call_chain(LCD_EVENT_ENABLE, (void *)pdrv);
lcd_if_enable_retry(pdrv);
LCDPR("[%d]: late_resume finished\n", pdrv->index);
mutex_unlock(&lcd_power_mutex);
}
return 0;
}
static void lcd_tablet_vinfo_update(struct aml_lcd_drv_s *pdrv)
{
struct lcd_config_s *pconf;
if (!pdrv)
return;
pconf = &pdrv->config;
/* store standard duration */
pdrv->std_duration.duration_num = pconf->timing.sync_duration_num;
pdrv->std_duration.duration_den = pconf->timing.sync_duration_den;
pdrv->vinfo.width = pconf->basic.h_active;
pdrv->vinfo.height = pconf->basic.v_active;
pdrv->vinfo.field_height = pconf->basic.v_active;
pdrv->vinfo.aspect_ratio_num = pconf->basic.screen_width;
pdrv->vinfo.aspect_ratio_den = pconf->basic.screen_height;
pdrv->vinfo.screen_real_width = pconf->basic.screen_width;
pdrv->vinfo.screen_real_height = pconf->basic.screen_height;
pdrv->vinfo.sync_duration_num = pconf->timing.sync_duration_num;
pdrv->vinfo.sync_duration_den = pconf->timing.sync_duration_den;
pdrv->vinfo.video_clk = pconf->timing.lcd_clk;
pdrv->vinfo.htotal = pconf->basic.h_period;
pdrv->vinfo.vtotal = pconf->basic.v_period;
switch (pconf->timing.fr_adjust_type) {
case 0:
pdrv->vinfo.fr_adj_type = VOUT_FR_ADJ_CLK;
break;
case 1:
pdrv->vinfo.fr_adj_type = VOUT_FR_ADJ_HTOTAL;
break;
case 2:
pdrv->vinfo.fr_adj_type = VOUT_FR_ADJ_VTOTAL;
break;
case 3:
pdrv->vinfo.fr_adj_type = VOUT_FR_ADJ_COMBO;
break;
case 4:
pdrv->vinfo.fr_adj_type = VOUT_FR_ADJ_HDMI;
break;
case 5:
pdrv->vinfo.fr_adj_type = VOUT_FR_ADJ_FREERUN;
break;
default:
pdrv->vinfo.fr_adj_type = VOUT_FR_ADJ_NONE;
break;
}
lcd_optical_vinfo_update(pdrv);
}
static void lcd_tablet_vinfo_update_default(struct aml_lcd_drv_s *pdrv)
{
struct lcd_config_s *pconf = &pdrv->config;
memset(pdrv->output_name, 0, sizeof(pdrv->output_name));
if (pdrv->index == 0) {
snprintf(pdrv->output_name, sizeof(pdrv->output_name), "panel");
} else {
snprintf(pdrv->output_name, sizeof(pdrv->output_name),
"panel%d", pdrv->index);
}
pdrv->vinfo.name = pdrv->output_name;
pdrv->vinfo.mode = VMODE_LCD;
pdrv->vinfo.width = pconf->basic.h_active;
pdrv->vinfo.height = pconf->basic.v_active;
pdrv->vinfo.field_height = pconf->basic.v_active;
pdrv->vinfo.aspect_ratio_num = pconf->basic.h_active;
pdrv->vinfo.aspect_ratio_den = pconf->basic.v_active;
pdrv->vinfo.screen_real_width = pconf->basic.h_active;
pdrv->vinfo.screen_real_height = pconf->basic.v_active;
pdrv->vinfo.sync_duration_num = 60;
pdrv->vinfo.sync_duration_den = 1;
pdrv->vinfo.video_clk = 0;
pdrv->vinfo.htotal = pconf->basic.h_period;
pdrv->vinfo.vtotal = pconf->basic.v_period;
pdrv->vinfo.fr_adj_type = VOUT_FR_ADJ_NONE;
}
void lcd_tablet_vout_server_init(struct aml_lcd_drv_s *pdrv)
{
struct vout_server_s *vserver;
int i;
lcd_tablet_vinfo_update_default(pdrv);
for (i = 0; i < 3; i++) {
vserver = kzalloc(sizeof(*vserver), GFP_KERNEL);
if (!vserver)
return;
vserver->name = kzalloc(32, GFP_KERNEL);
if (!vserver->name) {
kfree(vserver);
return;
}
pdrv->vout_server[i] = vserver;
sprintf(vserver->name, "lcd%d_vout%d_server", pdrv->index, i);
vserver->op.get_vinfo = lcd_get_current_info;
vserver->op.set_vmode = lcd_set_current_vmode;
vserver->op.validate_vmode = lcd_validate_vmode;
vserver->op.check_same_vmodeattr = lcd_check_same_vmodeattr;
vserver->op.vmode_is_supported = lcd_vmode_is_supported;
vserver->op.disable = lcd_vout_disable;
vserver->op.set_state = lcd_vout_set_state;
vserver->op.clr_state = lcd_vout_clr_state;
vserver->op.get_state = lcd_vout_get_state;
vserver->op.get_disp_cap = lcd_vout_get_disp_cap;
vserver->op.set_vframe_rate_hint = lcd_set_vframe_rate_hint;
vserver->op.get_vframe_rate_hint = lcd_get_vframe_rate_hint;
vserver->op.set_bist = lcd_vout_debug_test;
vserver->op.vout_suspend = lcd_suspend;
vserver->op.vout_resume = lcd_resume;
vserver->data = (void *)pdrv;
}
vout_register_server(pdrv->vout_server[0]);
#ifdef CONFIG_AMLOGIC_VOUT2_SERVE
vout2_register_server(pdrv->vout_server[1]);
#endif
#ifdef CONFIG_AMLOGIC_VOUT3_SERVE
vout3_register_server(pdrv->vout_server[2]);
#endif
}
void lcd_tablet_vout_server_remove(struct aml_lcd_drv_s *pdrv)
{
vout_unregister_server(pdrv->vout_server[0]);
#ifdef CONFIG_AMLOGIC_VOUT2_SERVE
vout2_unregister_server(pdrv->vout_server[1]);
#endif
#ifdef CONFIG_AMLOGIC_VOUT3_SERVE
vout3_unregister_server(pdrv->vout_server[2]);
#endif
}
static void lcd_config_init(struct aml_lcd_drv_s *pdrv)
{
lcd_basic_timing_range_update(pdrv);
lcd_timing_init_config(pdrv);
lcd_tablet_vinfo_update(pdrv);
lcd_tablet_config_update(pdrv);
lcd_clk_generate_parameter(pdrv);
lcd_clk_ss_config_update(pdrv);
lcd_tablet_config_post_update(pdrv);
}
/* **************************************************
* lcd notify
* **************************************************
*/
/* sync_duration is real_value * 100 */
static void lcd_frame_rate_adjust(struct aml_lcd_drv_s *pdrv, int duration)
{
LCDPR("%s: sync_duration=%d\n", __func__, duration);
lcd_vout_notify_mode_change_pre(pdrv);
/* update vinfo */
pdrv->vinfo.sync_duration_num = duration;
pdrv->vinfo.sync_duration_den = 100;
/* update interface timing */
lcd_tablet_config_update(pdrv);
#ifdef CONFIG_AMLOGIC_VPU
vpu_dev_clk_request(pdrv->lcd_vpu_dev, pdrv->config.timing.lcd_clk);
#endif
/* change clk parameter */
lcd_clk_change(pdrv);
lcd_tablet_config_post_update(pdrv);
lcd_venc_change(pdrv);
lcd_vout_notify_mode_change(pdrv);
}
/* **************************************************
* lcd tablet
* **************************************************
*/
int lcd_mode_tablet_init(struct aml_lcd_drv_s *pdrv)
{
if (!pdrv)
return -1;
lcd_config_init(pdrv);
pdrv->driver_init_pre = lcd_tablet_driver_init_pre;
pdrv->driver_disable_post = lcd_tablet_driver_disable_post;
pdrv->driver_init = lcd_tablet_driver_init;
pdrv->driver_disable = lcd_tablet_driver_disable;
pdrv->fr_adjust = lcd_frame_rate_adjust;
return 0;
}
int lcd_mode_tablet_remove(struct aml_lcd_drv_s *pdrv)
{
return 0;
}