| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #define pr_fmt(fmt) "vout: " fmt |
| |
| /* 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/cdev.h> |
| #include <linux/clk.h> |
| #include <linux/clk-provider.h> |
| #include <linux/compat.h> |
| |
| /* Amlogic Headers */ |
| #include <linux/amlogic/media/vout/vout_notify.h> |
| |
| /* Local Headers */ |
| #include "vout_func.h" |
| |
| #include <linux/amlogic/gki_module.h> |
| |
| #ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND |
| #include <linux/amlogic/pm.h> |
| static struct early_suspend early_suspend; |
| static int early_suspend_flag; |
| #endif |
| |
| #define VOUT_CDEV_NAME "display3" |
| #define VOUT_CLASS_NAME "display3" |
| #define MAX_NUMBER_PARA 10 |
| |
| #define VMODE_NAME_LEN_MAX 64 |
| static struct class *vout3_class; |
| static DEFINE_MUTEX(vout3_serve_mutex); |
| static char vout3_mode[VMODE_NAME_LEN_MAX]; |
| static char local_name[VMODE_NAME_LEN_MAX] = {0}; |
| static char vout3_mode_uboot[VMODE_NAME_LEN_MAX] = "null"; |
| static unsigned int vout3_init_vmode = VMODE_INIT_NULL; |
| static int uboot_display; |
| static unsigned int bist_mode3; |
| |
| static struct vout_cdev_s *vout3_cdev; |
| static struct device *vout3_dev; |
| |
| /* ********************************************************** |
| * null display support |
| * ********************************************************** |
| */ |
| static int nulldisp_index = VMODE_NULL_DISP_MAX; |
| static struct vinfo_s nulldisp_vinfo[] = { |
| { |
| .name = "null", |
| .mode = VMODE_NULL, |
| .frac = 0, |
| .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 = ((3 << 4) | VIU_MUX_MAX), |
| .vout_device = NULL, |
| }, |
| { |
| .name = "invalid", |
| .mode = VMODE_INVALID, |
| .frac = 0, |
| .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 = ((3 << 4) | VIU_MUX_MAX), |
| .vout_device = NULL, |
| } |
| }; |
| |
| static struct vinfo_s *nulldisp_get_current_info(void *data) |
| { |
| if (nulldisp_index >= ARRAY_SIZE(nulldisp_vinfo)) |
| return NULL; |
| |
| return &nulldisp_vinfo[nulldisp_index]; |
| } |
| |
| static int nulldisp_set_current_vmode(enum vmode_e mode, void *data) |
| { |
| return 0; |
| } |
| |
| static enum vmode_e nulldisp_validate_vmode(char *name, unsigned int frac, |
| void *data) |
| { |
| enum vmode_e vmode = VMODE_MAX; |
| int i; |
| |
| if (frac) |
| return VMODE_MAX; |
| |
| 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, void *data) |
| { |
| 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, void *data) |
| { |
| return 0; |
| } |
| |
| static int nulldisp_vout_state; |
| static int nulldisp_vout_set_state(int bit, void *data) |
| { |
| nulldisp_vout_state |= (1 << bit); |
| return 0; |
| } |
| |
| static int nulldisp_vout_clr_state(int bit, void *data) |
| { |
| nulldisp_vout_state &= ~(1 << bit); |
| return 0; |
| } |
| |
| static int nulldisp_vout_get_state(void *data) |
| { |
| return nulldisp_vout_state; |
| } |
| |
| static struct vout_server_s nulldisp_vout3_server = { |
| .name = "nulldisp_vout3_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, |
| }, |
| .data = NULL, |
| }; |
| |
| /* ********************************************************** */ |
| |
| char *get_vout3_mode_internal(void) |
| { |
| return vout3_mode; |
| } |
| EXPORT_SYMBOL(get_vout3_mode_internal); |
| |
| #define MAX_UEVENT_LEN 64 |
| static int vout3_set_uevent(unsigned int vout_event, int val) |
| { |
| char env[MAX_UEVENT_LEN]; |
| char *envp[2]; |
| int ret; |
| |
| if (vout_event != VOUT_EVENT_MODE_CHANGE) |
| return 0; |
| |
| if (!vout3_dev) { |
| VOUTERR("no vout3_dev\n"); |
| return -1; |
| } |
| |
| memset(env, 0, sizeof(env)); |
| envp[0] = env; |
| envp[1] = NULL; |
| snprintf(env, MAX_UEVENT_LEN, "vout3_setmode=%d", val); |
| |
| ret = kobject_uevent_env(&vout3_dev->kobj, KOBJ_CHANGE, envp); |
| |
| return ret; |
| } |
| |
| static int set_vout3_mode(char *name) |
| { |
| enum vmode_e mode; |
| unsigned int frac; |
| int ret = 0; |
| |
| vout_trim_string(name); |
| VOUTPR("vout3: vmode set to %s\n", name); |
| |
| if ((strcmp(name, vout3_mode) == 0) && |
| (vout3_check_same_vmodeattr(name))) { |
| VOUTPR("vout3: don't set the same mode as current\n"); |
| return -1; |
| } |
| |
| memset(local_name, 0, sizeof(local_name)); |
| snprintf(local_name, VMODE_NAME_LEN_MAX, "%s", name); |
| frac = vout_parse_vout_name(local_name); |
| |
| mode = validate_vmode3(local_name, frac); |
| if (mode == VMODE_MAX) { |
| VOUTERR("vout3: no matched vout3 mode\n"); |
| return -1; |
| } |
| |
| vout3_set_uevent(VOUT_EVENT_MODE_CHANGE, 1); |
| |
| vout3_notifier_call_chain(VOUT_EVENT_MODE_CHANGE_PRE, &mode); |
| ret = set_current_vmode3(mode); |
| if (ret) { |
| VOUTERR("vout3: new mode %s set error\n", name); |
| } else { |
| memset(vout3_mode, 0, sizeof(vout3_mode)); |
| snprintf(vout3_mode, VMODE_NAME_LEN_MAX, "%s", name); |
| VOUTPR("vout3: new mode %s set ok\n", name); |
| } |
| vout3_notifier_call_chain(VOUT_EVENT_MODE_CHANGE, &mode); |
| |
| vout3_set_uevent(VOUT_EVENT_MODE_CHANGE, 0); |
| |
| return ret; |
| } |
| |
| static int set_vout3_init_mode(void) |
| { |
| enum vmode_e vmode; |
| unsigned int frac; |
| int ret = 0; |
| |
| memset(local_name, 0, sizeof(local_name)); |
| snprintf(local_name, VMODE_NAME_LEN_MAX, "%s", vout3_mode_uboot); |
| frac = vout_parse_vout_name(local_name); |
| |
| vout3_init_vmode = validate_vmode3(local_name, frac); |
| if (vout3_init_vmode >= VMODE_MAX) { |
| VOUTERR("vout3: no matched vout3_init mode %s, force to invalid\n", |
| vout3_mode_uboot); |
| nulldisp_index = 1; |
| vout3_init_vmode = nulldisp_vinfo[nulldisp_index].mode; |
| snprintf(local_name, VMODE_NAME_LEN_MAX, "%s", |
| nulldisp_vinfo[nulldisp_index].name); |
| } else { /* recover vout_mode_uboot */ |
| snprintf(local_name, VMODE_NAME_LEN_MAX, "%s", |
| vout3_mode_uboot); |
| } |
| if (uboot_display) |
| vmode = vout3_init_vmode | VMODE_INIT_BIT_MASK; |
| else |
| vmode = vout3_init_vmode; |
| |
| ret = set_current_vmode3(vmode); |
| if (ret) { |
| VOUTERR("vout3: init mode %s set error\n", local_name); |
| } else { |
| memset(vout3_mode, 0, sizeof(vout3_mode)); |
| snprintf(vout3_mode, VMODE_NAME_LEN_MAX, local_name); |
| VOUTPR("vout3: init mode %s set ok\n", local_name); |
| } |
| |
| return ret; |
| } |
| |
| static ssize_t vout3_mode_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| |
| ret = snprintf(buf, VMODE_NAME_LEN_MAX, "%s\n", vout3_mode); |
| |
| return ret; |
| } |
| |
| static ssize_t vout3_mode_store(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| char mode[64]; |
| |
| mutex_lock(&vout3_serve_mutex); |
| snprintf(mode, VMODE_NAME_LEN_MAX, "%s", buf); |
| set_vout3_mode(mode); |
| mutex_unlock(&vout3_serve_mutex); |
| return count; |
| } |
| |
| static ssize_t vout3_fr_policy_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int policy; |
| int ret = 0; |
| |
| policy = get_vframe3_rate_policy(); |
| ret = sprintf(buf, "%d\n", policy); |
| |
| return ret; |
| } |
| |
| static ssize_t vout3_fr_policy_store(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| int policy; |
| int ret = 0; |
| |
| mutex_lock(&vout3_serve_mutex); |
| ret = kstrtoint(buf, 10, &policy); |
| if (ret) { |
| pr_info("%s: invalid data\n", __func__); |
| mutex_unlock(&vout3_serve_mutex); |
| return -EINVAL; |
| } |
| ret = set_vframe3_rate_policy(policy); |
| if (ret) |
| pr_info("%s: %d failed\n", __func__, policy); |
| mutex_unlock(&vout3_serve_mutex); |
| |
| return count; |
| } |
| |
| static ssize_t vout3_fr_hint_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int fr_hint; |
| int ret = 0; |
| |
| fr_hint = get_vframe3_rate_hint(); |
| ret = sprintf(buf, "%d\n", fr_hint); |
| |
| return ret; |
| } |
| |
| static ssize_t vout3_fr_hint_store(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| int fr_hint; |
| int ret = 0; |
| |
| mutex_lock(&vout3_serve_mutex); |
| ret = kstrtoint(buf, 10, &fr_hint); |
| if (ret) { |
| mutex_unlock(&vout3_serve_mutex); |
| return -EINVAL; |
| } |
| set_vframe3_rate_hint(fr_hint); |
| mutex_unlock(&vout3_serve_mutex); |
| |
| return count; |
| } |
| |
| static ssize_t vout3_bist_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| |
| ret = sprintf(buf, "%d\n", bist_mode3); |
| |
| return ret; |
| } |
| |
| static ssize_t vout3_bist_store(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| int ret = 0; |
| |
| mutex_lock(&vout3_serve_mutex); |
| |
| ret = kstrtouint(buf, 10, &bist_mode3); |
| if (ret) { |
| pr_info("%s: invalid data\n", __func__); |
| mutex_unlock(&vout3_serve_mutex); |
| return -EINVAL; |
| } |
| set_vout3_bist(bist_mode3); |
| |
| mutex_unlock(&vout3_serve_mutex); |
| |
| return count; |
| } |
| |
| static ssize_t vout3_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; |
| unsigned char val; |
| |
| info = get_current_vinfo3(); |
| if (!info) |
| return sprintf(buf, "current vinfo3 is null\n"); |
| |
| len = sprintf(buf, "current vinfo3:\n" |
| " name: %s\n" |
| " mode: %d\n" |
| " frac: %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" |
| " video_clk: %d\n" |
| " viu_color_fmt: %d\n" |
| " viu_mux: 0x%x\n\n", |
| info->name, info->mode, info->frac, |
| 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->video_clk, info->viu_color_fmt, info->viu_mux); |
| 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++) { |
| val = info->hdr_info.dynamic_info[i].optional_fields[j]; |
| len += sprintf(buf + len, " %x", val); |
| } |
| } |
| 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 ssize_t vout3_cap_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int ret; |
| |
| ret = get_vout3_disp_cap(buf); |
| if (!ret) |
| return sprintf(buf, "null\n"); |
| |
| return ret; |
| } |
| |
| static struct class_attribute vout3_class_attrs[] = { |
| __ATTR(mode, 0644, vout3_mode_show, vout3_mode_store), |
| __ATTR(fr_policy, 0644, |
| vout3_fr_policy_show, vout3_fr_policy_store), |
| __ATTR(fr_hint, 0644, vout3_fr_hint_show, vout3_fr_hint_store), |
| __ATTR(bist, 0644, vout3_bist_show, vout3_bist_store), |
| __ATTR(vinfo, 0444, vout3_vinfo_show, NULL), |
| __ATTR(cap, 0644, vout3_cap_show, NULL) |
| }; |
| |
| static int vout3_attr_create(void) |
| { |
| int i; |
| int ret = 0; |
| |
| /* create vout class */ |
| vout3_class = class_create(THIS_MODULE, VOUT_CLASS_NAME); |
| if (IS_ERR(vout3_class)) { |
| VOUTERR("vout3: create vout3 class fail\n"); |
| return -1; |
| } |
| |
| /* create vout class attr files */ |
| for (i = 0; i < ARRAY_SIZE(vout3_class_attrs); i++) { |
| if (class_create_file(vout3_class, &vout3_class_attrs[i])) { |
| VOUTERR("vout3: create vout3 attribute %s fail\n", |
| vout3_class_attrs[i].attr.name); |
| } |
| } |
| |
| /*VOUTPR("vout3: create vout3 attribute OK\n");*/ |
| |
| return ret; |
| } |
| |
| static int vout3_attr_remove(void) |
| { |
| int i; |
| |
| if (!vout3_class) |
| return 0; |
| |
| for (i = 0; i < ARRAY_SIZE(vout3_class_attrs); i++) |
| class_remove_file(vout3_class, &vout3_class_attrs[i]); |
| |
| class_destroy(vout3_class); |
| vout3_class = NULL; |
| |
| return 0; |
| } |
| |
| /* ************************************************************* */ |
| /* vout3 ioctl */ |
| /* ************************************************************* */ |
| static int vout3_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 vout3_io_release(struct inode *inode, struct file *file) |
| { |
| /*VOUTPR("%s\n", __func__);*/ |
| file->private_data = NULL; |
| return 0; |
| } |
| |
| static long vout3_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_vinfo3(); |
| if (!info) { |
| 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 vout3_compat_ioctl(struct file *file, unsigned int cmd, |
| unsigned long arg) |
| { |
| unsigned long ret; |
| |
| arg = (unsigned long)compat_ptr(arg); |
| ret = vout3_ioctl(file, cmd, arg); |
| return ret; |
| } |
| #endif |
| |
| static const struct file_operations vout3_fops = { |
| .owner = THIS_MODULE, |
| .open = vout3_io_open, |
| .release = vout3_io_release, |
| .unlocked_ioctl = vout3_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = vout3_compat_ioctl, |
| #endif |
| }; |
| |
| static int vout3_fops_create(void) |
| { |
| int ret = 0; |
| |
| vout3_cdev = kmalloc(sizeof(*vout3_cdev), GFP_KERNEL); |
| if (!vout3_cdev) { |
| VOUTERR("vout3: failed to allocate vout3_cdev\n"); |
| return -1; |
| } |
| |
| ret = alloc_chrdev_region(&vout3_cdev->devno, 0, 1, VOUT_CDEV_NAME); |
| if (ret < 0) { |
| VOUTERR("vout3: failed to alloc vout3 devno\n"); |
| goto vout3_fops_err1; |
| } |
| |
| cdev_init(&vout3_cdev->cdev, &vout3_fops); |
| vout3_cdev->cdev.owner = THIS_MODULE; |
| ret = cdev_add(&vout3_cdev->cdev, vout3_cdev->devno, 1); |
| if (ret) { |
| VOUTERR("vout3: failed to add vout3 cdev\n"); |
| goto vout3_fops_err2; |
| } |
| |
| vout3_cdev->dev = device_create(vout3_class, NULL, vout3_cdev->devno, |
| NULL, VOUT_CDEV_NAME); |
| if (IS_ERR(vout3_cdev->dev)) { |
| ret = PTR_ERR(vout3_cdev->dev); |
| VOUTERR("vout3: failed to create vout3 device: %d\n", ret); |
| goto vout3_fops_err3; |
| } |
| |
| /*VOUTPR("vout3: %s OK\n", __func__);*/ |
| return 0; |
| |
| vout3_fops_err3: |
| cdev_del(&vout3_cdev->cdev); |
| vout3_fops_err2: |
| unregister_chrdev_region(vout3_cdev->devno, 1); |
| vout3_fops_err1: |
| kfree(vout3_cdev); |
| vout3_cdev = NULL; |
| return -1; |
| } |
| |
| static void vout3_fops_remove(void) |
| { |
| cdev_del(&vout3_cdev->cdev); |
| unregister_chrdev_region(vout3_cdev->devno, 1); |
| kfree(vout3_cdev); |
| vout3_cdev = NULL; |
| } |
| |
| /* ************************************************************* */ |
| |
| #ifdef CONFIG_PM |
| static int aml_vout3_suspend(struct platform_device *pdev, pm_message_t state) |
| { |
| #ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND |
| |
| if (early_suspend_flag) |
| return 0; |
| |
| #endif |
| vout3_suspend(); |
| return 0; |
| } |
| |
| static int aml_vout3_resume(struct platform_device *pdev) |
| { |
| #ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND |
| |
| if (early_suspend_flag) |
| return 0; |
| |
| #endif |
| vout3_resume(); |
| return 0; |
| } |
| #endif |
| |
| #ifdef CONFIG_HIBERNATION |
| static int aml_vout3_freeze(struct device *dev) |
| { |
| return 0; |
| } |
| |
| static int aml_vout3_thaw(struct device *dev) |
| { |
| return 0; |
| } |
| |
| static int aml_vout3_restore(struct device *dev) |
| { |
| enum vmode_e mode; |
| |
| mode = get_current_vmode3(); |
| vout3_notifier_call_chain(VOUT_EVENT_MODE_CHANGE, &mode); |
| |
| return 0; |
| } |
| |
| static int aml_vout3_pm_suspend(struct device *dev) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| |
| return aml_vout3_suspend(pdev, PMSG_SUSPEND); |
| } |
| |
| static int aml_vout3_pm_resume(struct device *dev) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| |
| return aml_vout3_resume(pdev); |
| } |
| #endif |
| |
| #ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND |
| static void aml_vout3_early_suspend(struct early_suspend *h) |
| { |
| if (early_suspend_flag) |
| return; |
| |
| vout3_suspend(); |
| early_suspend_flag = 1; |
| } |
| |
| static void aml_vout3_late_resume(struct early_suspend *h) |
| { |
| if (!early_suspend_flag) |
| return; |
| |
| early_suspend_flag = 0; |
| vout3_resume(); |
| } |
| #endif |
| |
| /***************************************************************** |
| ** |
| ** vout driver interface |
| ** |
| ******************************************************************/ |
| static void aml_vout3_get_dt_info(struct platform_device *pdev) |
| { |
| int ret; |
| unsigned int para[2]; |
| |
| ret = of_property_read_u32(pdev->dev.of_node, "fr_policy", ¶[0]); |
| if (!ret) { |
| ret = set_vframe3_rate_policy(para[0]); |
| if (ret) |
| VOUTERR("vout3: init fr_policy %d failed\n", para[0]); |
| else |
| VOUTPR("vout3: fr_policy:%d\n", para[0]); |
| } |
| } |
| |
| static int aml_vout3_probe(struct platform_device *pdev) |
| { |
| int ret = -1; |
| |
| vout3_dev = &pdev->dev; |
| |
| aml_vout3_get_dt_info(pdev); |
| |
| #ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND |
| early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN; |
| early_suspend.suspend = aml_vout3_early_suspend; |
| early_suspend.resume = aml_vout3_late_resume; |
| register_early_suspend(&early_suspend); |
| #endif |
| |
| vout3_class = NULL; |
| ret = vout3_attr_create(); |
| ret = vout3_fops_create(); |
| |
| vout3_register_server(&nulldisp_vout3_server); |
| |
| set_vout3_init_mode(); |
| |
| VOUTPR("vout3: %s OK\n", __func__); |
| return ret; |
| } |
| |
| static int aml_vout3_remove(struct platform_device *pdev) |
| { |
| #ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND |
| unregister_early_suspend(&early_suspend); |
| #endif |
| vout3_attr_remove(); |
| vout3_fops_remove(); |
| vout3_unregister_server(&nulldisp_vout3_server); |
| |
| return 0; |
| } |
| |
| static void aml_vout3_shutdown(struct platform_device *pdev) |
| { |
| VOUTPR("vout3: %s\n", __func__); |
| vout3_shutdown(); |
| } |
| |
| static const struct of_device_id aml_vout3_dt_match[] = { |
| { .compatible = "amlogic, vout3",}, |
| { }, |
| }; |
| |
| #ifdef CONFIG_HIBERNATION |
| const struct dev_pm_ops vout3_pm = { |
| .freeze = aml_vout3_freeze, |
| .thaw = aml_vout3_thaw, |
| .restore = aml_vout3_restore, |
| .suspend = aml_vout3_pm_suspend, |
| .resume = aml_vout3_pm_resume, |
| }; |
| #endif |
| |
| static struct platform_driver vout3_driver = { |
| .probe = aml_vout3_probe, |
| .remove = aml_vout3_remove, |
| .shutdown = aml_vout3_shutdown, |
| #ifdef CONFIG_PM |
| .suspend = aml_vout3_suspend, |
| .resume = aml_vout3_resume, |
| #endif |
| .driver = { |
| .name = "vout3", |
| .of_match_table = aml_vout3_dt_match, |
| #ifdef CONFIG_HIBERNATION |
| .pm = &vout3_pm, |
| #endif |
| }, |
| }; |
| |
| int __init vout3_init_module(void) |
| { |
| return platform_driver_register(&vout3_driver); |
| } |
| |
| __exit void vout3_exit_module(void) |
| { |
| platform_driver_unregister(&vout3_driver); |
| } |
| |
| static int str2lower(char *str) |
| { |
| while (*str != '\0') { |
| *str = tolower(*str); |
| str++; |
| } |
| return 0; |
| } |
| |
| static void vout3_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("vout3: %s: %d\n", str, uboot_display); |
| return; |
| } |
| if (strncmp(str, "dis", 3) == 0) { /* disable */ |
| uboot_display = 0; |
| VOUTPR("vout3: %s: %d\n", str, uboot_display); |
| return; |
| } |
| if (strncmp(str, "frac", 4) == 0) { /* frac */ |
| if ((strlen(vout3_mode_uboot) + strlen(str)) |
| < VMODE_NAME_LEN_MAX) |
| strcat(vout3_mode_uboot, str); |
| else |
| VOUTERR("%s: str len out of support\n", __func__); |
| VOUTPR("%s\n", str); |
| return; |
| } |
| |
| /* |
| * just save the vmode_name, |
| * convert to vmode when vout sever registered |
| */ |
| snprintf(vout3_mode_uboot, VMODE_NAME_LEN_MAX, "%s", str); |
| vout_trim_string(vout3_mode_uboot); |
| VOUTPR("vout3: %s\n", vout3_mode_uboot); |
| } |
| |
| static int get_vout3_init_mode(char *str) |
| { |
| char *ptr = str; |
| char sep[2]; |
| char *option; |
| int count = 3; |
| char find = 0; |
| |
| if (!str) |
| return -EINVAL; |
| |
| do { |
| if (!isalpha(*ptr) && !isdigit(*ptr) && |
| (*ptr != '_') && (*ptr != '-')) { |
| find = 1; |
| break; |
| } |
| } while (*++ptr != '\0'); |
| if (!find) |
| return -EINVAL; |
| |
| sep[0] = *ptr; |
| sep[1] = '\0'; |
| while ((count--) && (option = strsep(&str, sep))) { |
| /* VOUTPR("vout3:%s\n", option); */ |
| str2lower(option); |
| vout3_init_mode_parse(option); |
| } |
| |
| return 0; |
| } |
| __setup("vout3=", get_vout3_init_mode); |
| |
| //MODULE_AUTHOR("Platform-BJ <platform.bj@amlogic.com>"); |
| //MODULE_DESCRIPTION("vout3 Server Module"); |
| //MODULE_LICENSE("GPL"); |