blob: c30b1ccac9e4e8859fc891a53beb14125c6befb8 [file] [log] [blame]
/*
* drivers/amlogic/media/vout/vout_serve/vout_serve.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.
*
*/
/* Linux Headers */
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/ctype.h>
#include <linux/of.h>
#include <linux/major.h>
#include <linux/uaccess.h>
#include <linux/extcon.h>
#include <linux/cdev.h>
/* Amlogic Headers */
#include <linux/amlogic/media/vout/vout_notify.h>
/* Local Headers */
#include "vout_func.h"
#ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND
#include <linux/amlogic/pm.h>
static struct early_suspend early_suspend;
static int early_suspend_flag;
#endif
#ifdef CONFIG_SCREEN_ON_EARLY
static int early_resume_flag;
#endif
#define VOUT_CDEV_NAME "display"
#define VOUT_CLASS_NAME "display"
#define MAX_NUMBER_PARA 10
#define VMODE_NAME_LEN_MAX 64
static struct class *vout_class;
static DEFINE_MUTEX(vout_serve_mutex);
static char vout_mode_uboot[VMODE_NAME_LEN_MAX] __nosavedata;
static char vout_mode[VMODE_NAME_LEN_MAX] __nosavedata;
static char local_name[VMODE_NAME_LEN_MAX] = {0};
static u32 vout_init_vmode = VMODE_INIT_NULL;
static int uboot_display;
static unsigned int bist_mode;
static char vout_axis[64] __nosavedata;
static char hdmimode[VMODE_NAME_LEN_MAX] = {
'i', 'n', 'v', 'a', 'l', 'i', 'd', '\0'
};
static char cvbsmode[VMODE_NAME_LEN_MAX] = {
'i', 'n', 'v', 'a', 'l', 'i', 'd', '\0'
};
static enum vmode_e last_vmode = VMODE_MAX;
static int tvout_monitor_flag = 1;
static unsigned int tvout_monitor_timeout_cnt = 20;
static struct delayed_work tvout_mode_work;
static struct extcon_dev *vout_excton_setmode;
static const unsigned int vout_cable[] = {
EXTCON_TYPE_DISP,
EXTCON_NONE,
};
static struct vout_cdev_s *vout_cdev;
/* **********************************************************
* null display support
* **********************************************************
*/
static int nulldisp_index = VMODE_NULL_DISP_MAX;
static struct vinfo_s nulldisp_vinfo[] = {
{
.name = "null",
.mode = VMODE_NULL,
.width = 1920,
.height = 1080,
.field_height = 1080,
.aspect_ratio_num = 16,
.aspect_ratio_den = 9,
.sync_duration_num = 60,
.sync_duration_den = 1,
.video_clk = 148500000,
.htotal = 2200,
.vtotal = 1125,
.fr_adj_type = VOUT_FR_ADJ_NONE,
.viu_color_fmt = COLOR_FMT_RGB444,
.viu_mux = VIU_MUX_MAX,
.vout_device = NULL,
},
{
.name = "invalid",
.mode = VMODE_INVALID,
.width = 1920,
.height = 1080,
.field_height = 1080,
.aspect_ratio_num = 16,
.aspect_ratio_den = 9,
.sync_duration_num = 60,
.sync_duration_den = 1,
.video_clk = 148500000,
.htotal = 2200,
.vtotal = 1125,
.fr_adj_type = VOUT_FR_ADJ_NONE,
.viu_color_fmt = COLOR_FMT_RGB444,
.viu_mux = VIU_MUX_MAX,
.vout_device = NULL,
},
{
.name = "dummy_panel",
.mode = VMODE_DUMMY_LCD,
.width = 1920,
.height = 1080,
.field_height = 1080,
.aspect_ratio_num = 16,
.aspect_ratio_den = 9,
.sync_duration_num = 60,
.sync_duration_den = 1,
.video_clk = 148500000,
.htotal = 2200,
.vtotal = 1125,
.fr_adj_type = VOUT_FR_ADJ_NONE,
.viu_color_fmt = COLOR_FMT_RGB444,
.viu_mux = VIU_MUX_MAX,
.vout_device = NULL,
},
};
static struct vinfo_s *nulldisp_get_current_info(void)
{
if (nulldisp_index >= ARRAY_SIZE(nulldisp_vinfo))
return NULL;
return &nulldisp_vinfo[nulldisp_index];
}
static int nulldisp_set_current_vmode(enum vmode_e mode)
{
return 0;
}
static enum vmode_e nulldisp_validate_vmode(char *name)
{
enum vmode_e vmode = VMODE_MAX;
int i;
for (i = 0; i < ARRAY_SIZE(nulldisp_vinfo); i++) {
if (strcmp(nulldisp_vinfo[i].name, name) == 0) {
vmode = nulldisp_vinfo[i].mode;
nulldisp_index = i;
break;
}
}
return vmode;
}
static int nulldisp_vmode_is_supported(enum vmode_e mode)
{
int i;
for (i = 0; i < ARRAY_SIZE(nulldisp_vinfo); i++) {
if (nulldisp_vinfo[i].mode == (mode & VMODE_MODE_BIT_MASK))
return true;
}
return false;
}
static int nulldisp_disable(enum vmode_e cur_vmod)
{
return 0;
}
static int nulldisp_vout_state;
static int nulldisp_vout_set_state(int bit)
{
nulldisp_vout_state |= (1 << bit);
return 0;
}
static int nulldisp_vout_clr_state(int bit)
{
nulldisp_vout_state &= ~(1 << bit);
return 0;
}
static int nulldisp_vout_get_state(void)
{
return nulldisp_vout_state;
}
static struct vout_server_s nulldisp_vout_server = {
.name = "nulldisp_vout_server",
.op = {
.get_vinfo = nulldisp_get_current_info,
.set_vmode = nulldisp_set_current_vmode,
.validate_vmode = nulldisp_validate_vmode,
.vmode_is_supported = nulldisp_vmode_is_supported,
.disable = nulldisp_disable,
.set_state = nulldisp_vout_set_state,
.clr_state = nulldisp_vout_clr_state,
.get_state = nulldisp_vout_get_state,
.set_bist = NULL,
},
};
/* ********************************************************** */
char *get_vout_mode_internal(void)
{
return vout_mode;
}
EXPORT_SYMBOL(get_vout_mode_internal);
char *get_vout_mode_uboot(void)
{
return vout_mode_uboot;
}
EXPORT_SYMBOL(get_vout_mode_uboot);
int set_vout_mode(char *name)
{
enum vmode_e mode;
int ret = 0;
vout_trim_string(name);
VOUTPR("vmode set to %s\n", name);
if (strcmp(name, local_name) == 0) {
VOUTPR("don't set the same mode as current, exit\n");
return -1;
}
mode = validate_vmode(name);
if (mode == VMODE_MAX) {
VOUTERR("no matched vout mode, exit\n");
return -1;
}
memset(local_name, 0, sizeof(local_name));
snprintf(local_name, VMODE_NAME_LEN_MAX, "%s", name);
extcon_set_state_sync(vout_excton_setmode, EXTCON_TYPE_DISP, 1);
vout_notifier_call_chain(VOUT_EVENT_MODE_CHANGE_PRE, &mode);
ret = set_current_vmode(mode);
if (ret)
VOUTERR("new mode %s set error\n", name);
else {
snprintf(vout_mode, VMODE_NAME_LEN_MAX, "%s", name);
VOUTPR("new mode %s set ok\n", vout_mode);
}
vout_notifier_call_chain(VOUT_EVENT_MODE_CHANGE, &mode);
extcon_set_state_sync(vout_excton_setmode, EXTCON_TYPE_DISP, 0);
return ret;
}
static int set_vout_init_mode(void)
{
enum vmode_e vmode;
char init_mode_str[VMODE_NAME_LEN_MAX];
int ret = 0;
snprintf(init_mode_str, VMODE_NAME_LEN_MAX, "%s", vout_mode_uboot);
vout_init_vmode = validate_vmode(vout_mode_uboot);
if (vout_init_vmode >= VMODE_MAX) {
VOUTERR("no matched vout_init mode %s, force to invalid\n",
vout_mode_uboot);
nulldisp_index = 1;
vout_init_vmode = nulldisp_vinfo[nulldisp_index].mode;
snprintf(init_mode_str, VMODE_NAME_LEN_MAX, "%s",
nulldisp_vinfo[nulldisp_index].name);
}
last_vmode = vout_init_vmode;
if (uboot_display)
vmode = vout_init_vmode | VMODE_INIT_BIT_MASK;
else
vmode = vout_init_vmode;
memset(local_name, 0, sizeof(local_name));
snprintf(local_name, VMODE_NAME_LEN_MAX, "%s", init_mode_str);
ret = set_current_vmode(vmode);
if (ret)
VOUTERR("init mode %s set error\n", init_mode_str);
else {
snprintf(vout_mode, VMODE_NAME_LEN_MAX, "%s", init_mode_str);
VOUTPR("init mode %s set ok\n", vout_mode);
}
return ret;
}
static int parse_para(const char *para, int para_num, int *result)
{
char *token = NULL;
char *params, *params_base;
int *out = result;
int len = 0, count = 0;
int res = 0;
int ret = 0;
if (!para)
return 0;
params = kstrdup(para, GFP_KERNEL);
params_base = params;
token = params;
len = strlen(token);
do {
token = strsep(&params, " ");
while (token && (isspace(*token)
|| !isgraph(*token)) && len) {
token++;
len--;
}
if ((!token) || (*token == '\n') || (len == 0))
break;
ret = kstrtoint(token, 0, &res);
if (ret < 0)
break;
len = strlen(token);
*out++ = res;
count++;
} while ((token) && (count < para_num) && (len > 0));
kfree(params_base);
return count;
}
#define OSD_COUNT 2
static void set_vout_axis(char *para)
{
static struct disp_rect_s disp_rect[OSD_COUNT];
/* char count = OSD_COUNT * 4; */
int *pt;
int parsed[MAX_NUMBER_PARA] = {};
/* parse window para */
if (parse_para(para, 8, parsed) >= 4) {
pt = &disp_rect[0].x;
memcpy(pt, &parsed[0], sizeof(struct disp_rect_s));
pt = &disp_rect[1].x;
memcpy(pt, &parsed[4], sizeof(struct disp_rect_s));
}
/* if ((count >= 4) && (count < 8))
* disp_rect[1] = disp_rect[0];
*/
VOUTPR("osd0=> x:%d,y:%d,w:%d,h:%d\n"
"osd1=> x:%d,y:%d,w:%d,h:%d\n",
disp_rect[0].x, disp_rect[0].y,
disp_rect[0].w, disp_rect[0].h,
disp_rect[1].x, disp_rect[1].y,
disp_rect[1].w, disp_rect[1].h);
vout_notifier_call_chain(VOUT_EVENT_OSD_DISP_AXIS, &disp_rect[0]);
}
static ssize_t vout_mode_show(struct class *class,
struct class_attribute *attr, char *buf)
{
int ret = 0;
ret = snprintf(buf, VMODE_NAME_LEN_MAX, "%s\n", vout_mode);
return ret;
}
static ssize_t vout_mode_store(struct class *class,
struct class_attribute *attr, const char *buf, size_t count)
{
char mode[VMODE_NAME_LEN_MAX];
mutex_lock(&vout_serve_mutex);
tvout_monitor_flag = 0;
snprintf(mode, VMODE_NAME_LEN_MAX, "%s", buf);
set_vout_mode(mode);
mutex_unlock(&vout_serve_mutex);
return count;
}
static ssize_t vout_dummy_store(struct class *class,
struct class_attribute *attr, const char *buf, size_t count)
{
unsigned int tmp[4], sync_duration;
enum vmode_e mode;
int ret;
mutex_lock(&vout_serve_mutex);
mode = VMODE_DUMMY_LCD;
ret = sscanf(buf, "%d %d %d %d", &tmp[0], &tmp[1], &tmp[2], &tmp[3]);
if (ret == 2) {
vout_notifier_call_chain(VOUT_EVENT_MODE_CHANGE_PRE, &mode);
nulldisp_vinfo[2].width = tmp[0];
nulldisp_vinfo[2].height = tmp[1];
nulldisp_vinfo[2].field_height = tmp[1];
VOUTPR("set dummy size: %d x %d\n", tmp[0], tmp[1]);
vout_notifier_call_chain(VOUT_EVENT_MODE_CHANGE, &mode);
} else if (ret == 4) {
vout_notifier_call_chain(VOUT_EVENT_MODE_CHANGE_PRE, &mode);
nulldisp_vinfo[2].width = tmp[0];
nulldisp_vinfo[2].height = tmp[1];
nulldisp_vinfo[2].field_height = tmp[1];
nulldisp_vinfo[2].sync_duration_num = tmp[2];
nulldisp_vinfo[2].sync_duration_den = tmp[3];
sync_duration = (tmp[2] * 100) / tmp[3];
VOUTPR("set dummy size: %d x %d, frame_rate: %d.%02dHz\n",
tmp[0], tmp[1],
(sync_duration / 100), (sync_duration % 100));
vout_notifier_call_chain(VOUT_EVENT_MODE_CHANGE, &mode);
} else {
VOUTERR("invalid data\n");
}
mutex_unlock(&vout_serve_mutex);
return count;
}
static ssize_t vout_axis_show(struct class *class,
struct class_attribute *attr, char *buf)
{
int ret = 0;
ret = snprintf(buf, 64, "%s\n", vout_axis);
return ret;
}
static ssize_t vout_axis_store(struct class *class,
struct class_attribute *attr, const char *buf, size_t count)
{
mutex_lock(&vout_serve_mutex);
snprintf(vout_axis, 64, "%s", buf);
set_vout_axis(vout_axis);
mutex_unlock(&vout_serve_mutex);
return count;
}
static ssize_t vout_fr_policy_show(struct class *class,
struct class_attribute *attr, char *buf)
{
int policy;
int ret = 0;
policy = get_vframe_rate_policy();
ret = sprintf(buf, "%d\n", policy);
return ret;
}
static ssize_t vout_fr_policy_store(struct class *class,
struct class_attribute *attr, const char *buf, size_t count)
{
int policy;
int ret = 0;
mutex_lock(&vout_serve_mutex);
ret = kstrtoint(buf, 10, &policy);
if (ret) {
pr_info("%s: invalid data\n", __func__);
mutex_unlock(&vout_serve_mutex);
return -EINVAL;
}
ret = set_vframe_rate_policy(policy);
if (ret)
pr_info("%s: %d failed\n", __func__, policy);
mutex_unlock(&vout_serve_mutex);
return count;
}
static ssize_t vout_bist_show(struct class *class,
struct class_attribute *attr, char *buf)
{
int ret = 0;
ret = sprintf(buf, "%d\n", bist_mode);
return ret;
}
static ssize_t vout_bist_store(struct class *class,
struct class_attribute *attr, const char *buf, size_t count)
{
int ret = 0;
mutex_lock(&vout_serve_mutex);
ret = kstrtouint(buf, 10, &bist_mode);
if (ret) {
pr_info("%s: invalid data\n", __func__);
mutex_unlock(&vout_serve_mutex);
return -EINVAL;
}
set_vout_bist(bist_mode);
mutex_unlock(&vout_serve_mutex);
return count;
}
static ssize_t vout_vinfo_show(struct class *class,
struct class_attribute *attr, char *buf)
{
const struct vinfo_s *info = NULL;
ssize_t len = 0;
unsigned int i, j;
info = get_current_vinfo();
if (info == NULL)
return sprintf(buf, "current vinfo is null\n");
len = sprintf(buf, "current vinfo:\n"
" name: %s\n"
" mode: %d\n"
" width: %d\n"
" height: %d\n"
" field_height: %d\n"
" aspect_ratio_num: %d\n"
" aspect_ratio_den: %d\n"
" sync_duration_num: %d\n"
" sync_duration_den: %d\n"
" screen_real_width: %d\n"
" screen_real_height: %d\n"
" htotal: %d\n"
" vtotal: %d\n"
" fr_adj_type: %d\n"
" video_clk: %d\n"
" viu_color_fmt: %d\n"
" viu_mux: %d\n"
" 3d_info: %d\n\n",
info->name, info->mode,
info->width, info->height, info->field_height,
info->aspect_ratio_num, info->aspect_ratio_den,
info->sync_duration_num, info->sync_duration_den,
info->screen_real_width, info->screen_real_height,
info->htotal, info->vtotal, info->fr_adj_type,
info->video_clk, info->viu_color_fmt, info->viu_mux,
info->info_3d);
len += sprintf(buf+len, "master_display_info:\n"
" present_flag %d\n"
" features 0x%x\n"
" primaries 0x%x, 0x%x\n"
" 0x%x, 0x%x\n"
" 0x%x, 0x%x\n"
" white_point 0x%x, 0x%x\n"
" luminance %d, %d\n\n",
info->master_display_info.present_flag,
info->master_display_info.features,
info->master_display_info.primaries[0][0],
info->master_display_info.primaries[0][1],
info->master_display_info.primaries[1][0],
info->master_display_info.primaries[1][1],
info->master_display_info.primaries[2][0],
info->master_display_info.primaries[2][1],
info->master_display_info.white_point[0],
info->master_display_info.white_point[1],
info->master_display_info.luminance[0],
info->master_display_info.luminance[1]);
len += sprintf(buf+len, "hdr_static_info:\n"
" hdr_support %d\n"
" lumi_max %d\n"
" lumi_avg %d\n"
" lumi_min %d\n",
info->hdr_info.hdr_support,
info->hdr_info.lumi_max,
info->hdr_info.lumi_avg,
info->hdr_info.lumi_min);
len += sprintf(buf+len, "hdr_dynamic_info:");
for (i = 0; i < 4; i++) {
len += sprintf(buf+len,
"\n metadata_version: %x\n"
" support_flags: %x\n",
info->hdr_info.dynamic_info[i].type,
info->hdr_info.dynamic_info[i].support_flags);
len += sprintf(buf+len, " optional_fields: ");
for (j = 0; j <
info->hdr_info.dynamic_info[i].of_len; j++) {
len += sprintf(buf+len, " %x",
info->hdr_info.dynamic_info[i].
optional_fields[j]);
}
}
len += sprintf(buf+len, "\n");
len += sprintf(buf+len, "hdr10+:\n");
len += sprintf(buf+len, " ieeeoui: %x\n",
info->hdr_info.hdr10plus_info.ieeeoui);
len += sprintf(buf+len, " application_version: %x\n",
info->hdr_info.hdr10plus_info.application_version);
return len;
}
static struct class_attribute vout_class_attrs[] = {
__ATTR(mode, 0644, vout_mode_show, vout_mode_store),
__ATTR(dummy, 0644, NULL, vout_dummy_store),
__ATTR(axis, 0644, vout_axis_show, vout_axis_store),
__ATTR(fr_policy, 0644,
vout_fr_policy_show, vout_fr_policy_store),
__ATTR(bist, 0644, vout_bist_show, vout_bist_store),
__ATTR(vinfo, 0444, vout_vinfo_show, NULL),
};
static int vout_attr_create(void)
{
int i;
int ret = 0;
/* create vout class */
vout_class = class_create(THIS_MODULE, VOUT_CLASS_NAME);
if (IS_ERR(vout_class)) {
VOUTERR("create vout class fail\n");
return -1;
}
/* create vout class attr files */
for (i = 0; i < ARRAY_SIZE(vout_class_attrs); i++) {
if (class_create_file(vout_class, &vout_class_attrs[i])) {
VOUTERR("create vout attribute %s fail\n",
vout_class_attrs[i].attr.name);
}
}
VOUTPR("create vout attribute OK\n");
return ret;
}
static int vout_attr_remove(void)
{
int i;
if (vout_class == NULL)
return 0;
for (i = 0; i < ARRAY_SIZE(vout_class_attrs); i++)
class_remove_file(vout_class, &vout_class_attrs[i]);
class_destroy(vout_class);
vout_class = NULL;
return 0;
}
/* ************************************************************* */
/* vout ioctl */
/* ************************************************************* */
static int vout_io_open(struct inode *inode, struct file *file)
{
struct vout_cdev_s *vcdev;
VOUTPR("%s\n", __func__);
vcdev = container_of(inode->i_cdev, struct vout_cdev_s, cdev);
file->private_data = vcdev;
return 0;
}
static int vout_io_release(struct inode *inode, struct file *file)
{
VOUTPR("%s\n", __func__);
file->private_data = NULL;
return 0;
}
static long vout_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
void __user *argp;
int mcd_nr;
struct vinfo_s *info = NULL;
struct vinfo_base_s baseinfo;
mcd_nr = _IOC_NR(cmd);
VOUTPR("%s: cmd_dir = 0x%x, cmd_nr = 0x%x\n",
__func__, _IOC_DIR(cmd), mcd_nr);
argp = (void __user *)arg;
switch (mcd_nr) {
case VOUT_IOC_NR_GET_VINFO:
info = get_current_vinfo();
if (info == NULL)
ret = -EFAULT;
else if (info->mode == VMODE_INIT_NULL)
ret = -EFAULT;
else {
baseinfo.mode = info->mode;
baseinfo.width = info->width;
baseinfo.height = info->height;
baseinfo.field_height = info->field_height;
baseinfo.aspect_ratio_num = info->aspect_ratio_num;
baseinfo.aspect_ratio_den = info->aspect_ratio_den;
baseinfo.sync_duration_num = info->sync_duration_num;
baseinfo.sync_duration_den = info->sync_duration_den;
baseinfo.screen_real_width = info->screen_real_width;
baseinfo.screen_real_height = info->screen_real_height;
baseinfo.video_clk = info->video_clk;
baseinfo.viu_color_fmt = info->viu_color_fmt;
if (copy_to_user(argp, &baseinfo,
sizeof(struct vinfo_base_s)))
ret = -EFAULT;
}
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
#ifdef CONFIG_COMPAT
static long vout_compat_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
unsigned long ret;
arg = (unsigned long)compat_ptr(arg);
ret = vout_ioctl(file, cmd, arg);
return ret;
}
#endif
static const struct file_operations vout_fops = {
.owner = THIS_MODULE,
.open = vout_io_open,
.release = vout_io_release,
.unlocked_ioctl = vout_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = vout_compat_ioctl,
#endif
};
static int vout_fops_create(void)
{
int ret = 0;
vout_cdev = kmalloc(sizeof(struct vout_cdev_s), GFP_KERNEL);
if (!vout_cdev) {
VOUTERR("failed to allocate vout_cdev\n");
return -1;
}
ret = alloc_chrdev_region(&vout_cdev->devno, 0, 1, VOUT_CDEV_NAME);
if (ret < 0) {
VOUTERR("failed to alloc vout devno\n");
goto vout_fops_err1;
}
cdev_init(&vout_cdev->cdev, &vout_fops);
vout_cdev->cdev.owner = THIS_MODULE;
ret = cdev_add(&vout_cdev->cdev, vout_cdev->devno, 1);
if (ret) {
VOUTERR("failed to add vout cdev\n");
goto vout_fops_err2;
}
vout_cdev->dev = device_create(vout_class, NULL, vout_cdev->devno,
NULL, VOUT_CDEV_NAME);
if (IS_ERR(vout_cdev->dev)) {
ret = PTR_ERR(vout_cdev->dev);
VOUTERR("failed to create vout device: %d\n", ret);
goto vout_fops_err3;
}
VOUTPR("%s OK\n", __func__);
return 0;
vout_fops_err3:
cdev_del(&vout_cdev->cdev);
vout_fops_err2:
unregister_chrdev_region(vout_cdev->devno, 1);
vout_fops_err1:
kfree(vout_cdev);
vout_cdev = NULL;
return -1;
}
static void vout_fops_remove(void)
{
cdev_del(&vout_cdev->cdev);
unregister_chrdev_region(vout_cdev->devno, 1);
kfree(vout_cdev);
vout_cdev = NULL;
}
/* ************************************************************* */
#ifdef CONFIG_PM
static int aml_vout_suspend(struct platform_device *pdev, pm_message_t state)
{
#ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND
if (early_suspend_flag)
return 0;
#endif
vout_suspend();
return 0;
}
static int aml_vout_resume(struct platform_device *pdev)
{
#ifdef CONFIG_SCREEN_ON_EARLY
if (early_resume_flag) {
early_resume_flag = 0;
return 0;
}
#endif
#ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND
if (early_suspend_flag)
return 0;
#endif
vout_resume();
return 0;
}
#endif
#ifdef CONFIG_HIBERNATION
static int aml_vout_freeze(struct device *dev)
{
return 0;
}
static int aml_vout_thaw(struct device *dev)
{
return 0;
}
static int aml_vout_restore(struct device *dev)
{
enum vmode_e mode;
mode = get_current_vmode();
vout_notifier_call_chain(VOUT_EVENT_MODE_CHANGE, &mode);
return 0;
}
static int aml_vout_pm_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
return aml_vout_suspend(pdev, PMSG_SUSPEND);
}
static int aml_vout_pm_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
return aml_vout_resume(pdev);
}
#endif
#ifdef CONFIG_SCREEN_ON_EARLY
void resume_vout_early(void)
{
#ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND
early_suspend_flag = 0;
early_resume_flag = 1;
vout_resume();
#endif
}
EXPORT_SYMBOL(resume_vout_early);
#endif
#ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND
static void aml_vout_early_suspend(struct early_suspend *h)
{
if (early_suspend_flag)
return;
vout_suspend();
early_suspend_flag = 1;
}
static void aml_vout_late_resume(struct early_suspend *h)
{
if (!early_suspend_flag)
return;
early_suspend_flag = 0;
vout_resume();
}
#endif
/* ***************************************************** */
/* hdmi/cvbs output mode monitor */
/* ***************************************************** */
static int refresh_tvout_mode(void)
{
enum vmode_e cur_vmode = VMODE_MAX;
char cur_mode_str[VMODE_NAME_LEN_MAX];
int hpd_state = 0;
if (tvout_monitor_flag == 0)
return 0;
hpd_state = vout_get_hpd_state();
if (hpd_state) {
cur_vmode = validate_vmode(hdmimode);
snprintf(cur_mode_str, VMODE_NAME_LEN_MAX, "%s", hdmimode);
} else {
cur_vmode = validate_vmode(cvbsmode);
snprintf(cur_mode_str, VMODE_NAME_LEN_MAX, "%s", cvbsmode);
}
if (cur_vmode >= VMODE_MAX) {
VOUTERR("%s: no matched cur_mode: %s, force to invalid\n",
__func__, cur_mode_str);
nulldisp_index = 1;
cur_vmode = nulldisp_vinfo[nulldisp_index].mode;
snprintf(cur_mode_str, VMODE_NAME_LEN_MAX, "%s",
nulldisp_vinfo[nulldisp_index].name);
}
/* not box platform */
if ((cur_vmode != VMODE_HDMI) &&
(cur_vmode != VMODE_CVBS) &&
(cur_vmode != VMODE_NULL) &&
(cur_vmode != VMODE_INVALID))
return -1;
if (cur_vmode != last_vmode) {
VOUTPR("%s: mode chang to %s\n", __func__, cur_mode_str);
set_vout_mode(cur_mode_str);
last_vmode = cur_vmode;
}
return 0;
}
static void aml_tvout_mode_work(struct work_struct *work)
{
if (tvout_monitor_timeout_cnt-- == 0) {
tvout_monitor_flag = 0;
VOUTPR("%s: monitor_timeout\n", __func__);
return;
}
mutex_lock(&vout_serve_mutex);
refresh_tvout_mode();
mutex_unlock(&vout_serve_mutex);
if (tvout_monitor_flag)
schedule_delayed_work(&tvout_mode_work, 1*HZ/2);
else
VOUTPR("%s: monitor stop\n", __func__);
}
static void aml_tvout_mode_monitor(void)
{
if ((vout_init_vmode != VMODE_HDMI) &&
(vout_init_vmode != VMODE_CVBS) &&
(vout_init_vmode != VMODE_NULL) &&
(vout_init_vmode != VMODE_INVALID))
return;
VOUTPR("%s\n", __func__);
last_vmode = vout_init_vmode;
tvout_monitor_flag = 1;
INIT_DELAYED_WORK(&tvout_mode_work, aml_tvout_mode_work);
mutex_lock(&vout_serve_mutex);
refresh_tvout_mode();
mutex_unlock(&vout_serve_mutex);
schedule_delayed_work(&tvout_mode_work, 1*HZ/2);
}
static void aml_vout_extcon_register(struct platform_device *pdev)
{
struct extcon_dev *edev;
int ret;
/*set display mode*/
edev = extcon_dev_allocate(vout_cable);
if (IS_ERR(edev)) {
VOUTERR("failed to allocate vout extcon setmode\n");
return;
}
edev->dev.parent = &pdev->dev;
edev->name = "vout_excton_setmode";
dev_set_name(&edev->dev, "setmode");
ret = extcon_dev_register(edev);
if (ret) {
VOUTERR("failed to register vout extcon setmode\n");
return;
}
vout_excton_setmode = edev;
}
static void aml_vout_extcon_free(void)
{
extcon_dev_free(vout_excton_setmode);
vout_excton_setmode = NULL;
}
/*****************************************************************
**
** vout driver interface
**
******************************************************************/
static int aml_vout_probe(struct platform_device *pdev)
{
int ret = -1;
#ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND
early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN;
early_suspend.suspend = aml_vout_early_suspend;
early_suspend.resume = aml_vout_late_resume;
register_early_suspend(&early_suspend);
#endif
vout_class = NULL;
ret = vout_attr_create();
ret = vout_fops_create();
vout_register_server(&nulldisp_vout_server);
set_vout_init_mode();
aml_vout_extcon_register(pdev);
aml_tvout_mode_monitor();
VOUTPR("%s OK\n", __func__);
return ret;
}
static int aml_vout_remove(struct platform_device *pdev)
{
#ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND
unregister_early_suspend(&early_suspend);
#endif
aml_vout_extcon_free();
vout_attr_remove();
vout_fops_remove();
vout_unregister_server(&nulldisp_vout_server);
return 0;
}
static void aml_vout_shutdown(struct platform_device *pdev)
{
VOUTPR("%s\n", __func__);
vout_shutdown();
}
static const struct of_device_id aml_vout_dt_match[] = {
{ .compatible = "amlogic, vout",},
{ },
};
#ifdef CONFIG_HIBERNATION
const struct dev_pm_ops vout_pm = {
.freeze = aml_vout_freeze,
.thaw = aml_vout_thaw,
.restore = aml_vout_restore,
.suspend = aml_vout_pm_suspend,
.resume = aml_vout_pm_resume,
};
#endif
static struct platform_driver vout_driver = {
.probe = aml_vout_probe,
.remove = aml_vout_remove,
.shutdown = aml_vout_shutdown,
#ifdef CONFIG_PM
.suspend = aml_vout_suspend,
.resume = aml_vout_resume,
#endif
.driver = {
.name = "vout",
.of_match_table = aml_vout_dt_match,
#ifdef CONFIG_HIBERNATION
.pm = &vout_pm,
#endif
},
};
static int __init vout_init_module(void)
{
int ret = 0;
if (platform_driver_register(&vout_driver)) {
VOUTERR("failed to register VOUT driver\n");
ret = -ENODEV;
}
return ret;
}
static __exit void vout_exit_module(void)
{
platform_driver_unregister(&vout_driver);
}
subsys_initcall(vout_init_module);
module_exit(vout_exit_module);
static int str2lower(char *str)
{
while (*str != '\0') {
*str = tolower(*str);
str++;
}
return 0;
}
static void vout_init_mode_parse(char *str)
{
/* detect vout mode */
if (strlen(str) <= 1) {
VOUTERR("%s: %s\n", __func__, str);
return;
}
/* detect uboot display */
if (strncmp(str, "en", 2) == 0) { /* enable */
uboot_display = 1;
VOUTPR("%s: %d\n", str, uboot_display);
return;
}
if (strncmp(str, "dis", 3) == 0) { /* disable */
uboot_display = 0;
VOUTPR("%s: %d\n", str, uboot_display);
return;
}
/*
* just save the vmode_name,
* convert to vmode when vout sever registered
*/
snprintf(vout_mode_uboot, VMODE_NAME_LEN_MAX, "%s", str);
vout_trim_string(vout_mode_uboot);
VOUTPR("%s\n", str);
}
static int __init get_vout_init_mode(char *str)
{
char *ptr = str;
char sep[2];
char *option;
int count = 3;
char find = 0;
/* init void vout_mode_uboot name */
memset(vout_mode_uboot, 0, sizeof(vout_mode_uboot));
if (str == NULL)
return -EINVAL;
do {
if (!isalpha(*ptr) && !isdigit(*ptr)) {
find = 1;
break;
}
} while (*++ptr != '\0');
if (!find)
return -EINVAL;
sep[0] = *ptr;
sep[1] = '\0';
while ((count--) && (option = strsep(&str, sep))) {
/* VOUTPR("%s\n", option); */
str2lower(option);
vout_init_mode_parse(option);
}
return 0;
}
__setup("vout=", get_vout_init_mode);
static int __init get_hdmi_mode(char *str)
{
snprintf(hdmimode, VMODE_NAME_LEN_MAX, "%s", str);
VOUTPR("get hdmimode: %s\n", hdmimode);
return 0;
}
__setup("hdmimode=", get_hdmi_mode);
static int __init get_cvbs_mode(char *str)
{
snprintf(cvbsmode, VMODE_NAME_LEN_MAX, "%s", str);
VOUTPR("get cvbsmode: %s\n", cvbsmode);
return 0;
}
__setup("cvbsmode=", get_cvbs_mode);
MODULE_AUTHOR("Platform-BJ <platform.bj@amlogic.com>");
MODULE_DESCRIPTION("VOUT Server Module");
MODULE_LICENSE("GPL");