blob: 6faba46959c6e2ae0056ac9663ddc23265872ac8 [file] [log] [blame]
/*
* drivers/amlogic/media/vout/lcd/lcd_vout.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/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/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.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/clk.h>
#include <linux/of_device.h>
#include <linux/amlogic/iomap.h>
#ifdef CONFIG_OF
#include <linux/of.h>
#endif
#include <linux/amlogic/pm.h>
#include <linux/amlogic/cpu_version.h>
#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_notify.h>
#include <linux/amlogic/media/vout/lcd/lcd_unifykey.h>
#ifdef CONFIG_AMLOGIC_LCD_EXTERN
#include <linux/amlogic/media/vout/lcd/lcd_extern.h>
#endif
#include "lcd_reg.h"
#include "lcd_common.h"
#include "lcd_clk_config.h"
#define LCD_CDEV_NAME "lcd"
unsigned char lcd_debug_print_flag;
unsigned char lcd_resume_flag;
static struct aml_lcd_drv_s *lcd_driver;
struct mutex lcd_vout_mutex;
int lcd_vout_serve_bypass;
static char lcd_propname[20] = "null";
struct lcd_cdev_s {
dev_t devno;
struct cdev cdev;
struct device *dev;
};
static struct lcd_cdev_s *lcd_cdev;
static int lcd_vsync_cnt;
static int lcd_vsync_cnt_previous;
#define LCD_VSYNC_NONE_INTERVAL msecs_to_jiffies(500)
static struct timer_list lcd_vsync_none_timer;
/* *********************************************************
* lcd config define
* *********************************************************
*/
static struct ttl_config_s lcd_ttl_config = {
.clk_pol = 0,
.sync_valid = ((1 << 1) | (1 << 0)),
.swap_ctrl = ((0 << 1) | (0 << 0)),
};
static struct lvds_config_s lcd_lvds_config = {
.lvds_repack = 1,
.dual_port = 0,
.pn_swap = 0,
.port_swap = 0,
.lane_reverse = 0,
.port_sel = 0,
.phy_vswing = LVDS_PHY_VSWING_DFT,
.phy_preem = LVDS_PHY_PREEM_DFT,
.phy_clk_vswing = LVDS_PHY_CLK_VSWING_DFT,
.phy_clk_preem = LVDS_PHY_CLK_PREEM_DFT,
};
static struct vbyone_config_s lcd_vbyone_config = {
.lane_count = 8,
.region_num = 2,
.byte_mode = 4,
.color_fmt = 4,
.phy_div = 1,
.phy_vswing = VX1_PHY_VSWING_DFT,
.phy_preem = VX1_PHY_PREEM_DFT,
.intr_en = 1,
.vsync_intr_en = 1,
.ctrl_flag = 0,
.power_on_reset_delay = VX1_PWR_ON_RESET_DLY_DFT,
.hpd_data_delay = VX1_HPD_DATA_DELAY_DFT,
.cdr_training_hold = VX1_CDR_TRAINING_HOLD_DFT,
};
static struct mlvds_config_s lcd_mlvds_config = {
.channel_num = 6,
.channel_sel0 = 0x76543210,
.channel_sel1 = 0,
.clk_phase = 0,
.pn_swap = 0,
.bit_swap = 0,
.phy_vswing = 0,
.phy_preem = 0,
.pi_clk_sel = 0,
};
static struct p2p_config_s lcd_p2p_config = {
.p2p_type = P2P_MAX,
.lane_num = 12,
.channel_sel0 = 0x76543210,
.channel_sel1 = 0xba98,
.pn_swap = 0,
.bit_swap = 0,
.phy_vswing = 0,
.phy_preem = 0,
};
static unsigned char dsi_init_on_table[DSI_INIT_ON_MAX] = {0xff, 0xff};
static unsigned char dsi_init_off_table[DSI_INIT_OFF_MAX] = {0xff, 0xff};
static struct dsi_config_s lcd_mipi_config = {
.lane_num = 4,
.bit_rate_max = 550, /* MHz */
.factor_numerator = 0,
.factor_denominator = 100,
.operation_mode_init = 1, /* 0=video mode, 1=command mode */
.operation_mode_display = 0, /* 0=video mode, 1=command mode */
.video_mode_type = 2, /* 0=sync_pulse, 1=sync_event, 2=burst */
.clk_always_hs = 1, /* 0=disable, 1=enable */
.phy_switch = 0, /* 0=auto, 1=standard, 2=slow */
.dsi_init_on = &dsi_init_on_table[0],
.dsi_init_off = &dsi_init_off_table[0],
.extern_init = 0xff,
/* ext_index if needed, 0xff for invalid */
.check_en = 0,
.check_reg = 0,
.check_cnt = 0,
.check_state = 0,
.mode_update_flag = 0,
};
static struct lcd_power_ctrl_s lcd_power_config = {
.cpu_gpio = {
{.probe_flag = 0, .register_flag = 0,},
{.probe_flag = 0, .register_flag = 0,},
{.probe_flag = 0, .register_flag = 0,},
{.probe_flag = 0, .register_flag = 0,},
{.probe_flag = 0, .register_flag = 0,},
{.probe_flag = 0, .register_flag = 0,},
{.probe_flag = 0, .register_flag = 0,},
{.probe_flag = 0, .register_flag = 0,},
{.probe_flag = 0, .register_flag = 0,},
{.probe_flag = 0, .register_flag = 0,},
},
.power_on_step = {
{
.type = LCD_POWER_TYPE_MAX,
},
},
.power_off_step = {
{
.type = LCD_POWER_TYPE_MAX,
},
},
};
/* index 0: valid flag */
static unsigned int vlock_param[LCD_VLOCK_PARAM_NUM] = {0};
static struct lcd_config_s lcd_config_dft = {
.lcd_propname = lcd_propname,
.lcd_basic = {
.lcd_type = LCD_TYPE_MAX,
},
.lcd_timing = {
.lcd_clk = 0,
.clk_auto = 1,
.ss_level = 0,
.fr_adjust_type = 0,
.bit_rate = 0,
},
.optical_info = {
.hdr_support = 0,
.features = 0,
.primaries_r_x = 0,
.primaries_r_y = 0,
.primaries_g_x = 0,
.primaries_g_y = 0,
.primaries_b_x = 0,
.primaries_b_y = 0,
.white_point_x = 0,
.white_point_y = 0,
.luma_max = 0,
.luma_min = 0,
.luma_avg = 0,
},
.lcd_control = {
.ttl_config = &lcd_ttl_config,
.lvds_config = &lcd_lvds_config,
.vbyone_config = &lcd_vbyone_config,
.mlvds_config = &lcd_mlvds_config,
.p2p_config = &lcd_p2p_config,
.mipi_config = &lcd_mipi_config,
.vlock_param = vlock_param,
},
.lcd_power = &lcd_power_config,
.pinmux_flag = 0xff,
.change_flag = 0,
.retry_enable_flag = 0,
.retry_enable_cnt = 0,
.backlight_index = 0xff,
.extern_index = 0xff,
};
static struct vinfo_s lcd_vinfo = {
.name = "panel",
.mode = VMODE_LCD,
.viu_color_fmt = COLOR_FMT_RGB444,
.viu_mux = VIU_MUX_ENCL,
.vout_device = NULL,
};
struct aml_lcd_drv_s *aml_lcd_get_driver(void)
{
return lcd_driver;
}
EXPORT_SYMBOL(aml_lcd_get_driver);
/* ********************************************************* */
static void lcd_power_ctrl(int status)
{
struct lcd_power_ctrl_s *lcd_power = lcd_driver->lcd_config->lcd_power;
struct lcd_power_step_s *power_step;
#ifdef CONFIG_AMLOGIC_LCD_EXTERN
struct aml_lcd_extern_driver_s *ext_drv;
#endif
unsigned int i, index, wait, temp;
int value = -1;
int ret = 0;
LCDPR("%s: %d\n", __func__, status);
i = 0;
while (i < LCD_PWR_STEP_MAX) {
if (status)
power_step = &lcd_power->power_on_step[i];
else
power_step = &lcd_power->power_off_step[i];
if (power_step->type >= LCD_POWER_TYPE_MAX)
break;
if (lcd_debug_print_flag) {
LCDPR("power_ctrl: %d, step %d\n", status, i);
LCDPR("%s: type=%d, index=%d, value=%d, delay=%d\n",
__func__, power_step->type, power_step->index,
power_step->value, power_step->delay);
}
switch (power_step->type) {
case LCD_POWER_TYPE_CPU:
index = power_step->index;
lcd_cpu_gpio_set(index, power_step->value);
break;
case LCD_POWER_TYPE_PMU:
LCDPR("to do\n");
break;
case LCD_POWER_TYPE_SIGNAL:
if (status)
lcd_driver->driver_init();
else
lcd_driver->driver_disable();
break;
#ifdef CONFIG_AMLOGIC_LCD_EXTERN
case LCD_POWER_TYPE_EXTERN:
index = power_step->index;
ext_drv = aml_lcd_extern_get_driver(index);
if (ext_drv) {
if (status) {
if (ext_drv->power_on)
ext_drv->power_on();
else
LCDERR("no ext power on\n");
} else {
if (ext_drv->power_off)
ext_drv->power_off();
else
LCDERR("no ext power off\n");
}
}
break;
#endif
case LCD_POWER_TYPE_WAIT_GPIO:
index = power_step->index;
lcd_cpu_gpio_set(index, LCD_GPIO_INPUT);
LCDPR("lcd_power_type_wait_gpio wait\n");
for (wait = 0; wait < power_step->delay; wait++) {
value = lcd_cpu_gpio_get(index);
if (value == power_step->value) {
LCDPR("wait_gpio %d ok\n", value);
break;
}
mdelay(1);
}
if (wait == power_step->delay)
LCDERR("wait_gpio %d timeout!\n", value);
break;
case LCD_POWER_TYPE_CLK_SS:
temp = lcd_driver->lcd_config->lcd_timing.ss_level;
value = (power_step->value) & 0xff;
ret = lcd_set_ss(0xff,
(value >> LCD_CLK_SS_BIT_FREQ) & 0xf,
(value >> LCD_CLK_SS_BIT_MODE) & 0xf);
if (ret == 0) {
temp &= ~(0xff << 8);
temp |= (value << 8);
lcd_driver->lcd_config->lcd_timing.ss_level =
temp;
}
break;
default:
break;
}
if ((power_step->type != LCD_POWER_TYPE_WAIT_GPIO) &&
(power_step->delay > 0))
mdelay(power_step->delay);
i++;
}
if (lcd_debug_print_flag)
LCDPR("%s: %d finished\n", __func__, status);
}
static void lcd_power_encl_on(void)
{
mutex_lock(&lcd_vout_mutex);
lcd_driver->driver_init_pre();
lcd_driver->lcd_status |= LCD_STATUS_ENCL_ON;
/* vsync_none_timer conditional enabled to save cpu loading */
if (lcd_driver->viu_sel == LCD_VIU_SEL_NONE) {
if (lcd_driver->vsync_none_timer_flag == 0) {
lcd_vsync_none_timer.expires =
jiffies + LCD_VSYNC_NONE_INTERVAL;
add_timer(&lcd_vsync_none_timer);
lcd_driver->vsync_none_timer_flag = 1;
LCDPR("add lcd_vsync_none_timer handler\n");
}
} else {
if (lcd_driver->vsync_none_timer_flag) {
del_timer_sync(&lcd_vsync_none_timer);
lcd_driver->vsync_none_timer_flag = 0;
}
}
mutex_unlock(&lcd_vout_mutex);
}
static void lcd_power_encl_off(void)
{
mutex_lock(&lcd_vout_mutex);
lcd_driver->lcd_status &= ~LCD_STATUS_ENCL_ON;
lcd_driver->driver_disable_post();
if (lcd_driver->vsync_none_timer_flag) {
del_timer_sync(&lcd_vsync_none_timer);
lcd_driver->vsync_none_timer_flag = 0;
}
mutex_unlock(&lcd_vout_mutex);
}
static void lcd_power_if_on(void)
{
mutex_lock(&lcd_vout_mutex);
lcd_driver->power_ctrl(1);
lcd_driver->lcd_status |= LCD_STATUS_IF_ON;
lcd_driver->lcd_config->change_flag = 0;
mutex_unlock(&lcd_vout_mutex);
}
static void lcd_power_if_off(void)
{
mutex_lock(&lcd_vout_mutex);
lcd_driver->lcd_status &= ~LCD_STATUS_IF_ON;
lcd_driver->power_ctrl(0);
mutex_unlock(&lcd_vout_mutex);
}
static void lcd_power_screen_black(void)
{
mutex_lock(&lcd_vout_mutex);
lcd_driver->lcd_mute_flag = (unsigned char)(1 | LCD_MUTE_UPDATE);
LCDPR("set mute\n");
mutex_unlock(&lcd_vout_mutex);
}
static void lcd_power_screen_restore(void)
{
mutex_lock(&lcd_vout_mutex);
lcd_driver->lcd_mute_flag = (unsigned char)(0 | LCD_MUTE_UPDATE);
LCDPR("clear mute\n");
mutex_unlock(&lcd_vout_mutex);
}
static void lcd_module_reset(void)
{
mutex_lock(&lcd_vout_mutex);
lcd_driver->lcd_status &= ~LCD_STATUS_ON;
lcd_driver->power_ctrl(0);
msleep(500);
lcd_driver->driver_init_pre();
lcd_driver->power_ctrl(1);
lcd_driver->lcd_status |= LCD_STATUS_ON;
lcd_driver->lcd_config->change_flag = 0;
lcd_driver->lcd_mute_flag = (unsigned char)(0 | LCD_MUTE_UPDATE);
LCDPR("clear mute\n");
mutex_unlock(&lcd_vout_mutex);
}
static void lcd_resume_work(struct work_struct *p_work)
{
mutex_lock(&lcd_driver->power_mutex);
aml_lcd_notifier_call_chain(LCD_EVENT_POWER_ON, NULL);
lcd_if_enable_retry(lcd_driver->lcd_config);
LCDPR("%s finished\n", __func__);
mutex_unlock(&lcd_driver->power_mutex);
}
static struct delayed_work lcd_test_delayed_work;
static void lcd_auto_test_delayed(struct work_struct *work)
{
LCDPR("%s\n", __func__);
mutex_lock(&lcd_driver->power_mutex);
aml_lcd_notifier_call_chain(LCD_EVENT_POWER_ON, NULL);
mutex_unlock(&lcd_driver->power_mutex);
}
static void lcd_auto_test(unsigned char flag)
{
lcd_driver->lcd_test_state = flag;
if (lcd_driver->workqueue) {
queue_delayed_work(lcd_driver->workqueue,
&lcd_test_delayed_work,
msecs_to_jiffies(20000));
} else {
schedule_delayed_work(&lcd_test_delayed_work,
msecs_to_jiffies(20000));
}
}
static int lcd_vsync_print_cnt;
static inline void lcd_vsync_handler(void)
{
int flag;
#ifdef CONFIG_AMLOGIC_LCD_TABLET
struct lcd_config_s *pconf;
#endif
if (lcd_driver == NULL)
return;
pconf = lcd_driver->lcd_config;
#ifdef CONFIG_AMLOGIC_LCD_TABLET
if (pconf->lcd_control.mipi_config->dread) {
if (pconf->lcd_control.mipi_config->dread->flag) {
lcd_mipi_test_read(
pconf->lcd_control.mipi_config->dread);
pconf->lcd_control.mipi_config->dread->flag = 0;
}
}
#endif
if (lcd_driver->lcd_mute_flag & LCD_MUTE_UPDATE) {
flag = lcd_driver->lcd_mute_flag & 0x1;
if (flag) {
if (lcd_driver->lcd_mute_state == 0) {
lcd_driver->lcd_mute_state = 1;
lcd_driver->lcd_screen_black();
}
} else {
if (lcd_driver->lcd_mute_state) {
lcd_driver->lcd_mute_state = 0;
lcd_driver->lcd_screen_restore();
}
}
lcd_driver->lcd_mute_flag &= ~(LCD_MUTE_UPDATE);
}
if (lcd_driver->lcd_test_flag & LCD_TEST_UPDATE) {
flag = lcd_driver->lcd_test_flag & 0xf;
if (flag != lcd_driver->lcd_test_state) {
lcd_driver->lcd_test_state = (unsigned char)flag;
lcd_debug_test(flag);
}
lcd_driver->lcd_test_flag &= ~(LCD_TEST_UPDATE);
}
if (lcd_vsync_print_cnt++ >= LCD_DEBUG_VSYNC_INTERVAL) {
lcd_vsync_print_cnt = 0;
if (lcd_debug_print_flag & LCD_DEBUG_LEVEL_VSYNC) {
LCDPR("%s: viu_sel: %d\n",
__func__, lcd_driver->viu_sel);
}
}
}
static irqreturn_t lcd_vsync_isr(int irq, void *dev_id)
{
if (lcd_driver == NULL)
return IRQ_HANDLED;
if ((lcd_driver->lcd_status & LCD_STATUS_ENCL_ON) == 0)
return IRQ_HANDLED;
if (lcd_driver->viu_sel == 1) {
lcd_vsync_handler();
if (lcd_vsync_cnt++ >= 65536)
lcd_vsync_cnt = 0;
}
return IRQ_HANDLED;
}
static irqreturn_t lcd_vsync2_isr(int irq, void *dev_id)
{
if (lcd_driver == NULL)
return IRQ_HANDLED;
if ((lcd_driver->lcd_status & LCD_STATUS_ENCL_ON) == 0)
return IRQ_HANDLED;
if (lcd_driver->viu_sel == 2) {
lcd_vsync_handler();
if (lcd_vsync_cnt++ >= 65536)
lcd_vsync_cnt = 0;
}
return IRQ_HANDLED;
}
#define LCD_WAIT_VSYNC_TIMEOUT 50000
static void lcd_wait_vsync(void)
{
int line_cnt, line_cnt_previous;
int i = 0;
line_cnt = 0x1fff;
line_cnt_previous = lcd_vcbus_getb(ENCL_INFO_READ, 16, 13);
while (i++ < LCD_WAIT_VSYNC_TIMEOUT) {
line_cnt = lcd_vcbus_getb(ENCL_INFO_READ, 16, 13);
if (line_cnt < line_cnt_previous)
break;
line_cnt_previous = line_cnt;
udelay(2);
}
/*LCDPR("line_cnt=%d, line_cnt_previous=%d, i=%d\n",
* line_cnt, line_cnt_previous, i);
*/
}
static void lcd_vsync_none_timer_handler(unsigned long arg)
{
if (lcd_driver == NULL)
return;
if ((lcd_driver->lcd_status & LCD_STATUS_ENCL_ON) == 0)
goto lcd_vsync_none_timer_handler_end;
if (lcd_vsync_cnt == lcd_vsync_cnt_previous) {
lcd_wait_vsync();
lcd_vsync_handler();
}
lcd_vsync_cnt_previous = lcd_vsync_cnt;
lcd_vsync_none_timer_handler_end:
if (lcd_driver->vsync_none_timer_flag) {
lcd_vsync_none_timer.expires =
jiffies + LCD_VSYNC_NONE_INTERVAL;
add_timer(&lcd_vsync_none_timer);
}
}
/* ****************************************
* lcd notify
* ****************************************
*/
static int lcd_power_encl_on_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
if ((event & LCD_EVENT_ENCL_ON) == 0)
return NOTIFY_DONE;
if (lcd_debug_print_flag)
LCDPR("%s: 0x%lx\n", __func__, event);
if (lcd_driver->lcd_status & LCD_STATUS_ENCL_ON) {
LCDPR("lcd is already enabled\n");
return NOTIFY_OK;
}
lcd_power_encl_on();
return NOTIFY_OK;
}
static struct notifier_block lcd_power_encl_on_nb = {
.notifier_call = lcd_power_encl_on_notifier,
.priority = LCD_PRIORITY_POWER_ENCL_ON,
};
static int lcd_power_encl_off_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
if ((event & LCD_EVENT_ENCL_OFF) == 0)
return NOTIFY_DONE;
if (lcd_debug_print_flag)
LCDPR("%s: 0x%lx\n", __func__, event);
lcd_power_encl_off();
return NOTIFY_OK;
}
static struct notifier_block lcd_power_encl_off_nb = {
.notifier_call = lcd_power_encl_off_notifier,
.priority = LCD_PRIORITY_POWER_ENCL_OFF,
};
static int lcd_power_if_on_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
if ((event & LCD_EVENT_IF_ON) == 0)
return NOTIFY_DONE;
if (lcd_debug_print_flag)
LCDPR("%s: 0x%lx\n", __func__, event);
if (lcd_driver->lcd_status & LCD_STATUS_IF_ON) {
LCDPR("lcd interface is already enabled\n");
return NOTIFY_OK;
}
if (lcd_driver->lcd_status & LCD_STATUS_ENCL_ON) {
lcd_power_if_on();
} else {
LCDERR("%s: can't power on when controller is off\n",
__func__);
return NOTIFY_DONE;
}
return NOTIFY_OK;
}
static struct notifier_block lcd_power_if_on_nb = {
.notifier_call = lcd_power_if_on_notifier,
.priority = LCD_PRIORITY_POWER_IF_ON,
};
static int lcd_power_if_off_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
if ((event & LCD_EVENT_IF_OFF) == 0)
return NOTIFY_DONE;
if (lcd_debug_print_flag)
LCDPR("%s: 0x%lx\n", __func__, event);
lcd_power_if_off();
return NOTIFY_OK;
}
static struct notifier_block lcd_power_if_off_nb = {
.notifier_call = lcd_power_if_off_notifier,
.priority = LCD_PRIORITY_POWER_IF_OFF,
};
static int lcd_power_screen_black_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
if ((event & LCD_EVENT_SCREEN_BLACK) == 0)
return NOTIFY_DONE;
if (lcd_debug_print_flag)
LCDPR("%s: 0x%lx\n", __func__, event);
lcd_power_screen_black();
return NOTIFY_OK;
}
static struct notifier_block lcd_power_screen_black_nb = {
.notifier_call = lcd_power_screen_black_notifier,
.priority = LCD_PRIORITY_SCREEN_BLACK,
};
static int lcd_power_screen_restore_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
if ((event & LCD_EVENT_SCREEN_RESTORE) == 0)
return NOTIFY_DONE;
if (lcd_debug_print_flag)
LCDPR("%s: 0x%lx\n", __func__, event);
if (lcd_driver->lcd_mute_state == 0) {
LCDPR("lcd mute is already cleared\n");
return NOTIFY_OK;
}
lcd_power_screen_restore();
return NOTIFY_OK;
}
static struct notifier_block lcd_power_screen_restore_nb = {
.notifier_call = lcd_power_screen_restore_notifier,
.priority = LCD_PRIORITY_SCREEN_RESTORE,
};
static int lcd_bl_select_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
unsigned int *index;
if ((event & LCD_EVENT_BACKLIGHT_SEL) == 0)
return NOTIFY_DONE;
/* LCDPR("%s: 0x%lx\n", __func__, event); */
index = (unsigned int *)data;
*index = lcd_driver->lcd_config->backlight_index;
return NOTIFY_OK;
}
static struct notifier_block lcd_bl_select_nb = {
.notifier_call = lcd_bl_select_notifier,
};
static int lcd_extern_select_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
unsigned int *index;
struct lcd_config_s *pconf = lcd_driver->lcd_config;
if ((event & LCD_EVENT_EXTERN_SEL) == 0)
return NOTIFY_DONE;
/* LCDPR("%s: 0x%lx\n", __func__, event); */
index = (unsigned int *)data;
*index = pconf->extern_index;
if (pconf->lcd_basic.lcd_type == LCD_MIPI) {
if (*index == LCD_EXTERN_INDEX_INVALID)
*index = pconf->lcd_control.mipi_config->extern_init;
}
return NOTIFY_OK;
}
static struct notifier_block lcd_extern_select_nb = {
.notifier_call = lcd_extern_select_notifier,
};
static int lcd_vlock_param_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
unsigned int *param;
if ((event & LCD_EVENT_VLOCK_PARAM) == 0)
return NOTIFY_DONE;
/* LCDPR("%s: 0x%lx\n", __func__, event); */
param = (unsigned int *)data;
memcpy(param, vlock_param,
(LCD_VLOCK_PARAM_NUM * sizeof(unsigned int)));
return NOTIFY_OK;
}
static struct notifier_block lcd_vlock_param_nb = {
.notifier_call = lcd_vlock_param_notifier,
};
static int lcd_notifier_register(void)
{
int ret = 0;
ret = aml_lcd_notifier_register(&lcd_power_encl_on_nb);
if (ret)
LCDERR("register lcd_power_encl_on_nb failed\n");
ret = aml_lcd_notifier_register(&lcd_power_encl_off_nb);
if (ret)
LCDERR("register lcd_power_encl_off_nb failed\n");
ret = aml_lcd_notifier_register(&lcd_power_if_on_nb);
if (ret)
LCDERR("register lcd_power_if_on_nb failed\n");
ret = aml_lcd_notifier_register(&lcd_power_if_off_nb);
if (ret)
LCDERR("register lcd_power_if_off_nb failed\n");
ret = aml_lcd_notifier_register(&lcd_power_screen_black_nb);
if (ret)
LCDERR("register lcd_power_screen_black_nb failed\n");
ret = aml_lcd_notifier_register(&lcd_power_screen_restore_nb);
if (ret)
LCDERR("register lcd_power_screen_restore_nb failed\n");
ret = aml_lcd_notifier_register(&lcd_bl_select_nb);
if (ret)
LCDERR("register aml_bl_select_notifier failed\n");
ret = aml_lcd_notifier_register(&lcd_extern_select_nb);
if (ret)
LCDERR("register lcd_extern_select_nb failed\n");
ret = aml_lcd_notifier_register(&lcd_vlock_param_nb);
if (ret)
LCDERR("register lcd_vlock_param_nb failed\n");
return 0;
}
static void lcd_notifier_unregister(void)
{
aml_lcd_notifier_unregister(&lcd_power_screen_restore_nb);
aml_lcd_notifier_unregister(&lcd_power_screen_black_nb);
aml_lcd_notifier_unregister(&lcd_power_if_off_nb);
aml_lcd_notifier_unregister(&lcd_power_if_on_nb);
aml_lcd_notifier_unregister(&lcd_power_encl_off_nb);
aml_lcd_notifier_unregister(&lcd_power_encl_on_nb);
aml_lcd_notifier_unregister(&lcd_bl_select_nb);
aml_lcd_notifier_unregister(&lcd_extern_select_nb);
aml_lcd_notifier_unregister(&lcd_vlock_param_nb);
}
/* **************************************** */
/* ************************************************************* */
/* lcd ioctl */
/* ************************************************************* */
static int lcd_io_open(struct inode *inode, struct file *file)
{
struct lcd_cdev_s *lcd_cdev;
LCDPR("%s\n", __func__);
lcd_cdev = container_of(inode->i_cdev, struct lcd_cdev_s, cdev);
file->private_data = lcd_cdev;
return 0;
}
static int lcd_io_release(struct inode *inode, struct file *file)
{
LCDPR("%s\n", __func__);
file->private_data = NULL;
return 0;
}
static long lcd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
void __user *argp;
int mcd_nr;
struct lcd_optical_info_s *opt_info;
opt_info = &lcd_driver->lcd_config->optical_info;
mcd_nr = _IOC_NR(cmd);
LCDPR("%s: cmd_dir = 0x%x, cmd_nr = 0x%x\n",
__func__, _IOC_DIR(cmd), mcd_nr);
argp = (void __user *)arg;
switch (mcd_nr) {
case LCD_IOC_NR_GET_HDR_INFO:
if (copy_to_user(argp, opt_info,
sizeof(struct lcd_optical_info_s))) {
ret = -EFAULT;
}
break;
case LCD_IOC_NR_SET_HDR_INFO:
if (copy_from_user(opt_info, argp,
sizeof(struct lcd_optical_info_s))) {
ret = -EFAULT;
} else {
lcd_optical_vinfo_update();
if (lcd_debug_print_flag) {
LCDPR("set optical info:\n"
"hdr_support %d\n"
"features %d\n"
"primaries_r_x %d\n"
"primaries_r_y %d\n"
"primaries_g_x %d\n"
"primaries_g_y %d\n"
"primaries_b_x %d\n"
"primaries_b_y %d\n"
"white_point_x %d\n"
"white_point_y %d\n"
"luma_max %d\n"
"luma_min %d\n\n",
opt_info->hdr_support,
opt_info->features,
opt_info->primaries_r_x,
opt_info->primaries_r_y,
opt_info->primaries_g_x,
opt_info->primaries_g_y,
opt_info->primaries_b_x,
opt_info->primaries_b_y,
opt_info->white_point_x,
opt_info->white_point_y,
opt_info->luma_max,
opt_info->luma_min);
}
}
break;
default:
LCDERR("not support ioctl cmd_nr: 0x%x\n", mcd_nr);
ret = -EINVAL;
break;
}
return ret;
}
#ifdef CONFIG_COMPAT
static long lcd_compat_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
unsigned long ret;
arg = (unsigned long)compat_ptr(arg);
ret = lcd_ioctl(file, cmd, arg);
return ret;
}
#endif
static const struct file_operations lcd_fops = {
.owner = THIS_MODULE,
.open = lcd_io_open,
.release = lcd_io_release,
.unlocked_ioctl = lcd_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = lcd_compat_ioctl,
#endif
};
static int lcd_fops_create(void)
{
int ret = 0;
lcd_cdev = kmalloc(sizeof(struct lcd_cdev_s), GFP_KERNEL);
if (!lcd_cdev) {
LCDERR("%s: failed to allocate lcd_cdev\n", __func__);
return -1;
}
ret = alloc_chrdev_region(&lcd_cdev->devno, 0, 1, LCD_CDEV_NAME);
if (ret < 0) {
LCDERR("%s: failed to alloc devno\n", __func__);
kfree(lcd_cdev);
lcd_cdev = NULL;
return -1;
}
cdev_init(&lcd_cdev->cdev, &lcd_fops);
lcd_cdev->cdev.owner = THIS_MODULE;
ret = cdev_add(&lcd_cdev->cdev, lcd_cdev->devno, 1);
if (ret) {
LCDERR("%s: failed to add cdev\n", __func__);
unregister_chrdev_region(lcd_cdev->devno, 1);
kfree(lcd_cdev);
lcd_cdev = NULL;
return -1;
}
lcd_cdev->dev = device_create(lcd_driver->lcd_debug_class, NULL,
lcd_cdev->devno, NULL, LCD_CDEV_NAME);
if (IS_ERR(lcd_cdev->dev)) {
LCDERR("%s: failed to add device\n", __func__);
ret = PTR_ERR(lcd_cdev->dev);
cdev_del(&lcd_cdev->cdev);
unregister_chrdev_region(lcd_cdev->devno, 1);
kfree(lcd_cdev);
lcd_cdev = NULL;
return -1;
}
if (lcd_debug_print_flag)
LCDPR("%s OK\n", __func__);
return 0;
}
static void lcd_fops_remove(void)
{
cdev_del(&lcd_cdev->cdev);
unregister_chrdev_region(lcd_cdev->devno, 1);
kfree(lcd_cdev);
lcd_cdev = NULL;
}
/* ************************************************************* */
static int lcd_vsync_irq_init(void)
{
if (lcd_driver->res_vsync_irq) {
if (request_irq(lcd_driver->res_vsync_irq->start,
lcd_vsync_isr, IRQF_SHARED,
"lcd_vsync", (void *)"lcd_vsync")) {
LCDERR("can't request lcd_vsync_irq\n");
} else {
if (lcd_debug_print_flag)
LCDPR("request lcd_vsync_irq successful\n");
}
}
if (lcd_driver->res_vsync2_irq) {
if (request_irq(lcd_driver->res_vsync2_irq->start,
lcd_vsync2_isr, IRQF_SHARED,
"lcd_vsync2", (void *)"lcd_vsync2")) {
LCDERR("can't request lcd_vsync2_irq\n");
} else {
if (lcd_debug_print_flag)
LCDPR("request lcd_vsync2_irq successful\n");
}
}
/* add timer to monitor hpll frequency */
init_timer(&lcd_vsync_none_timer);
/* lcd_vsync_none_timer.data = NULL; */
lcd_vsync_none_timer.function = lcd_vsync_none_timer_handler;
lcd_vsync_none_timer.expires = jiffies + LCD_VSYNC_NONE_INTERVAL;
/*add_timer(&lcd_vsync_none_timer);*/
/*LCDPR("add lcd_vsync_none_timer handler\n"); */
return 0;
}
static void lcd_vsync_irq_remove(void)
{
if (lcd_driver->res_vsync_irq)
free_irq(lcd_driver->res_vsync_irq->start, (void *)"lcd_vsync");
if (lcd_driver->res_vsync2_irq) {
free_irq(lcd_driver->res_vsync2_irq->start,
(void *)"lcd_vsync");
}
if (lcd_driver->vsync_none_timer_flag) {
del_timer_sync(&lcd_vsync_none_timer);
lcd_driver->vsync_none_timer_flag = 0;
}
}
static void lcd_init_vout(void)
{
switch (lcd_driver->lcd_mode) {
#ifdef CONFIG_AMLOGIC_LCD_TV
case LCD_MODE_TV:
lcd_tv_vout_server_init();
break;
#endif
#ifdef CONFIG_AMLOGIC_LCD_TABLET
case LCD_MODE_TABLET:
lcd_tablet_vout_server_init();
break;
#endif
default:
LCDERR("invalid lcd mode: %d\n", lcd_driver->lcd_mode);
break;
}
}
static int lcd_mode_probe(struct device *dev)
{
switch (lcd_driver->lcd_mode) {
#ifdef CONFIG_AMLOGIC_LCD_TV
case LCD_MODE_TV:
lcd_tv_probe(dev);
break;
#endif
#ifdef CONFIG_AMLOGIC_LCD_TABLET
case LCD_MODE_TABLET:
lcd_tablet_probe(dev);
break;
#endif
default:
LCDERR("invalid lcd mode: %d\n", lcd_driver->lcd_mode);
break;
}
lcd_tcon_probe(lcd_driver);
if (lcd_driver->lcd_status & LCD_STATUS_ENCL_ON)
lcd_clk_gate_switch(1);
lcd_debug_probe();
lcd_fops_create();
lcd_notifier_register();
lcd_vsync_irq_init();
/* add notifier for video sync_duration info refresh */
vout_notifier_call_chain(VOUT_EVENT_MODE_CHANGE,
&lcd_driver->lcd_info->mode);
if (lcd_driver->lcd_auto_test)
lcd_auto_test(lcd_driver->lcd_auto_test);
return 0;
}
static int lcd_config_remove(struct device *dev)
{
lcd_notifier_unregister();
switch (lcd_driver->lcd_mode) {
#ifdef CONFIG_AMLOGIC_LCD_TV
case LCD_MODE_TV:
lcd_tv_vout_server_remove();
lcd_tv_remove(dev);
break;
#endif
#ifdef CONFIG_AMLOGIC_LCD_TABLET
case LCD_MODE_TABLET:
lcd_tablet_vout_server_remove();
lcd_tablet_remove(dev);
break;
#endif
default:
LCDPR("invalid lcd mode\n");
break;
}
lcd_clk_config_remove();
return 0;
}
static void lcd_config_probe_delayed(struct work_struct *work)
{
int key_init_flag = 0;
int i = 0;
int ret;
key_init_flag = key_unify_get_init_flag();
while (key_init_flag == 0) {
if (i++ >= LCD_UNIFYKEY_WAIT_TIMEOUT)
break;
msleep(LCD_UNIFYKEY_RETRY_INTERVAL);
key_init_flag = key_unify_get_init_flag();
}
LCDPR("key_init_flag=%d, i=%d\n", key_init_flag, i);
if (key_init_flag == 0) {
kfree(lcd_driver);
lcd_driver = NULL;
LCDERR("key is not ready, probe exit\n");
return;
}
ret = lcd_mode_probe(lcd_driver->dev);
if (ret) {
kfree(lcd_driver);
lcd_driver = NULL;
LCDERR("probe exit\n");
}
}
static void lcd_config_default(void)
{
struct lcd_config_s *pconf;
pconf = lcd_driver->lcd_config;
pconf->lcd_basic.h_active = lcd_vcbus_read(ENCL_VIDEO_HAVON_END)
- lcd_vcbus_read(ENCL_VIDEO_HAVON_BEGIN) + 1;
pconf->lcd_basic.v_active = lcd_vcbus_read(ENCL_VIDEO_VAVON_ELINE)
- lcd_vcbus_read(ENCL_VIDEO_VAVON_BLINE) + 1;
if (lcd_vcbus_read(ENCL_VIDEO_EN)) {
lcd_driver->lcd_status = LCD_STATUS_ON;
lcd_resume_flag = 1;
} else {
lcd_driver->lcd_status = 0;
lcd_resume_flag = 0;
}
LCDPR("status: %d\n", lcd_driver->lcd_status);
}
static int lcd_config_probe(struct platform_device *pdev)
{
const char *str;
unsigned int val;
int ret = 0;
if (lcd_driver->dev->of_node == NULL) {
LCDERR("dev of_node is null\n");
lcd_driver->lcd_mode = LCD_MODE_MAX;
return -1;
}
lcd_driver->res_vsync_irq = NULL;
lcd_driver->res_vsync2_irq = NULL;
lcd_driver->res_vx1_irq = NULL;
lcd_driver->res_tcon_irq = NULL;
/* lcd driver assign */
ret = of_property_read_string(lcd_driver->dev->of_node, "mode", &str);
if (ret) {
str = "none";
LCDERR("failed to get mode\n");
return -1;
}
lcd_driver->lcd_mode = lcd_mode_str_to_mode(str);
ret = of_property_read_u32(lcd_driver->dev->of_node,
"fr_auto_policy", &val);
if (ret) {
if (lcd_debug_print_flag)
LCDPR("failed to get fr_auto_policy\n");
lcd_driver->fr_auto_policy = 0;
} else {
lcd_driver->fr_auto_policy = (unsigned char)val;
}
ret = of_property_read_u32(lcd_driver->dev->of_node, "key_valid", &val);
if (ret) {
if (lcd_debug_print_flag)
LCDPR("failed to get key_valid\n");
lcd_driver->lcd_key_valid = 0;
} else {
lcd_driver->lcd_key_valid = (unsigned char)val;
}
LCDPR("detect mode: %s, fr_auto_policy: %d, key_valid: %d\n",
str, lcd_driver->fr_auto_policy, lcd_driver->lcd_key_valid);
ret = of_property_read_u32(lcd_driver->dev->of_node, "clk_path", &val);
if (ret) {
if (lcd_debug_print_flag)
LCDPR("failed to get clk_path\n");
lcd_driver->lcd_clk_path = 0;
} else {
lcd_driver->lcd_clk_path = (unsigned char)val;
LCDPR("detect lcd_clk_path: %d\n", lcd_driver->lcd_clk_path);
}
ret = of_property_read_u32(lcd_driver->dev->of_node, "auto_test", &val);
if (ret) {
if (lcd_debug_print_flag)
LCDPR("failed to get auto_test\n");
lcd_driver->lcd_auto_test = 0;
} else {
lcd_driver->lcd_auto_test = (unsigned char)val;
LCDPR("detect lcd_auto_test: %d\n", lcd_driver->lcd_auto_test);
}
lcd_driver->res_vsync_irq = platform_get_resource_byname(pdev,
IORESOURCE_IRQ, "vsync");
lcd_driver->res_vsync2_irq = platform_get_resource_byname(pdev,
IORESOURCE_IRQ, "vsync2");
lcd_driver->res_vx1_irq = platform_get_resource_byname(pdev,
IORESOURCE_IRQ, "vbyone");
lcd_driver->res_tcon_irq = platform_get_resource_byname(pdev,
IORESOURCE_IRQ, "tcon");
lcd_driver->lcd_info = &lcd_vinfo;
lcd_driver->lcd_config = &lcd_config_dft;
lcd_driver->lcd_test_state = 0;
lcd_driver->lcd_test_flag = 0;
lcd_driver->lcd_mute_state = 0;
lcd_driver->lcd_mute_flag = 0;
lcd_driver->lcd_resume_type = 1; /* default workqueue */
lcd_driver->viu_sel = LCD_VIU_SEL_NONE;
lcd_driver->vsync_none_timer_flag = 0;
lcd_driver->power_ctrl = lcd_power_ctrl;
lcd_driver->module_reset = lcd_module_reset;
lcd_clk_config_probe();
lcd_config_default();
lcd_init_vout();
if (lcd_driver->lcd_key_valid) {
if (lcd_driver->workqueue) {
queue_delayed_work(lcd_driver->workqueue,
&lcd_driver->lcd_probe_delayed_work,
msecs_to_jiffies(2000));
} else {
schedule_delayed_work(
&lcd_driver->lcd_probe_delayed_work,
msecs_to_jiffies(2000));
}
} else {
ret = lcd_mode_probe(lcd_driver->dev);
if (ret) {
kfree(lcd_driver);
lcd_driver = NULL;
LCDERR("probe exit\n");
}
}
return 0;
}
#ifdef CONFIG_OF
static struct lcd_data_s lcd_data_gxl = {
.chip_type = LCD_CHIP_GXL,
.chip_name = "gxl",
.reg_map_table = &lcd_reg_gxb[0],
};
static struct lcd_data_s lcd_data_gxm = {
.chip_type = LCD_CHIP_GXM,
.chip_name = "gxm",
.reg_map_table = &lcd_reg_gxb[0],
};
static struct lcd_data_s lcd_data_txl = {
.chip_type = LCD_CHIP_TXL,
.chip_name = "txl",
.reg_map_table = &lcd_reg_gxb[0],
};
static struct lcd_data_s lcd_data_txlx = {
.chip_type = LCD_CHIP_TXLX,
.chip_name = "txlx",
.reg_map_table = &lcd_reg_gxb[0],
};
static struct lcd_data_s lcd_data_axg = {
.chip_type = LCD_CHIP_AXG,
.chip_name = "axg",
.reg_map_table = &lcd_reg_axg[0],
};
static struct lcd_data_s lcd_data_g12a = {
.chip_type = LCD_CHIP_G12A,
.chip_name = "g12a",
.reg_map_table = &lcd_reg_axg[0],
};
static struct lcd_data_s lcd_data_g12b = {
.chip_type = LCD_CHIP_G12B,
.chip_name = "g12b",
.reg_map_table = &lcd_reg_axg[0],
};
static struct lcd_data_s lcd_data_tl1 = {
.chip_type = LCD_CHIP_TL1,
.chip_name = "tl1",
.reg_map_table = &lcd_reg_tl1[0],
};
static struct lcd_data_s lcd_data_sm1 = {
.chip_type = LCD_CHIP_SM1,
.chip_name = "sm1",
.reg_map_table = &lcd_reg_axg[0],
};
static const struct of_device_id lcd_dt_match_table[] = {
{
.compatible = "amlogic, lcd-gxl",
.data = &lcd_data_gxl,
},
{
.compatible = "amlogic, lcd-gxm",
.data = &lcd_data_gxm,
},
{
.compatible = "amlogic, lcd-txl",
.data = &lcd_data_txl,
},
{
.compatible = "amlogic, lcd-txlx",
.data = &lcd_data_txlx,
},
{
.compatible = "amlogic, lcd-axg",
.data = &lcd_data_axg,
},
{
.compatible = "amlogic, lcd-g12a",
.data = &lcd_data_g12a,
},
{
.compatible = "amlogic, lcd-g12b",
.data = &lcd_data_g12b,
},
{
.compatible = "amlogic, lcd-tl1",
.data = &lcd_data_tl1,
},
{
.compatible = "amlogic, lcd-sm1",
.data = &lcd_data_sm1,
},
{},
};
#endif
static int lcd_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
int ret = 0;
#ifdef LCD_DEBUG_INFO
lcd_debug_print_flag = 1;
#else
lcd_debug_print_flag = 0;
#endif
lcd_driver = kmalloc(sizeof(struct aml_lcd_drv_s), GFP_KERNEL);
if (!lcd_driver) {
LCDERR("%s: lcd driver no enough memory\n", __func__);
return -ENOMEM;
}
lcd_driver->dev = &pdev->dev;
match = of_match_device(lcd_dt_match_table, &pdev->dev);
if (match == NULL) {
LCDERR("%s: no match table\n", __func__);
return -1;
}
lcd_driver->data = (struct lcd_data_s *)match->data;
strcpy(lcd_driver->version, LCD_DRV_VERSION);
LCDPR("driver version: %s(%d-%s)\n",
lcd_driver->version,
lcd_driver->data->chip_type,
lcd_driver->data->chip_name);
mutex_init(&lcd_vout_mutex);
mutex_init(&lcd_driver->power_mutex);
lcd_vout_serve_bypass = 0;
/* init workqueue */
INIT_DELAYED_WORK(&lcd_driver->lcd_probe_delayed_work,
lcd_config_probe_delayed);
INIT_DELAYED_WORK(&lcd_test_delayed_work, lcd_auto_test_delayed);
lcd_driver->workqueue = create_singlethread_workqueue("lcd_work_queue");
if (lcd_driver->workqueue == NULL)
LCDERR("can't create lcd workqueue\n");
INIT_WORK(&(lcd_driver->lcd_resume_work), lcd_resume_work);
lcd_ioremap(pdev);
ret = lcd_config_probe(pdev);
LCDPR("%s %s\n", __func__, (ret ? "failed" : "ok"));
return 0;
}
static int lcd_remove(struct platform_device *pdev)
{
if (lcd_driver == NULL)
return 0;
cancel_delayed_work(&lcd_driver->lcd_probe_delayed_work);
cancel_work_sync(&(lcd_driver->lcd_resume_work));
if (lcd_driver->workqueue)
destroy_workqueue(lcd_driver->workqueue);
lcd_vsync_irq_remove();
lcd_fops_remove();
lcd_debug_remove();
lcd_config_remove(lcd_driver->dev);
kfree(lcd_driver);
lcd_driver = NULL;
LCDPR("%s\n", __func__);
return 0;
}
static int lcd_resume(struct platform_device *pdev)
{
if (lcd_debug_print_flag)
LCDPR("resume method: %d\n", get_resume_method());
if ((get_resume_method() == RTC_WAKEUP) ||
(get_resume_method() == AUTO_WAKEUP) ||
(get_resume_method() == UDEFINED_WAKEUP))
return 0;
if (lcd_driver == NULL)
return 0;
if ((lcd_driver->lcd_status & LCD_STATUS_VMODE_ACTIVE) == 0)
return 0;
if (lcd_driver->lcd_resume_type) {
lcd_resume_flag = 1;
if (lcd_driver->workqueue) {
queue_work(lcd_driver->workqueue,
&(lcd_driver->lcd_resume_work));
} else {
schedule_work(&(lcd_driver->lcd_resume_work));
}
} else {
mutex_lock(&lcd_driver->power_mutex);
LCDPR("directly lcd resume\n");
lcd_resume_flag = 1;
aml_lcd_notifier_call_chain(LCD_EVENT_POWER_ON, NULL);
lcd_if_enable_retry(lcd_driver->lcd_config);
LCDPR("%s finished\n", __func__);
mutex_unlock(&lcd_driver->power_mutex);
}
return 0;
}
static int lcd_suspend(struct platform_device *pdev, pm_message_t state)
{
if (lcd_driver == NULL)
return 0;
mutex_lock(&lcd_driver->power_mutex);
if (lcd_driver->lcd_status & LCD_STATUS_ENCL_ON) {
aml_lcd_notifier_call_chain(LCD_EVENT_POWER_OFF, NULL);
lcd_resume_flag = 0;
LCDPR("%s finished\n", __func__);
}
mutex_unlock(&lcd_driver->power_mutex);
return 0;
}
static void lcd_shutdown(struct platform_device *pdev)
{
if (lcd_debug_print_flag)
LCDPR("%s\n", __func__);
if (lcd_driver == NULL)
return;
if (lcd_driver->lcd_status & LCD_STATUS_ENCL_ON)
aml_lcd_notifier_call_chain(LCD_EVENT_POWER_OFF, NULL);
}
static struct platform_driver lcd_platform_driver = {
.probe = lcd_probe,
.remove = lcd_remove,
.suspend = lcd_suspend,
.resume = lcd_resume,
.shutdown = lcd_shutdown,
.driver = {
.name = "mesonlcd",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = of_match_ptr(lcd_dt_match_table),
#endif
},
};
static int __init lcd_init(void)
{
if (platform_driver_register(&lcd_platform_driver)) {
LCDERR("failed to register lcd driver module\n");
return -ENODEV;
}
return 0;
}
static void __exit lcd_exit(void)
{
platform_driver_unregister(&lcd_platform_driver);
}
subsys_initcall(lcd_init);
module_exit(lcd_exit);
static int __init lcd_panel_type_para_setup(char *str)
{
if (str != NULL)
sprintf(lcd_propname, "%s", str);
LCDPR("panel_type: %s\n", lcd_propname);
return 0;
}
__setup("panel_type=", lcd_panel_type_para_setup);
MODULE_DESCRIPTION("Meson LCD Panel Driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Amlogic, Inc.");