| /* |
| * drivers/amlogic/media/vout/vout_serve/vout2_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> |
| #include <linux/clk.h> |
| #include <linux/clk-provider.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 |
| |
| #define VOUT_CDEV_NAME "display2" |
| #define VOUT_CLASS_NAME "display2" |
| #define MAX_NUMBER_PARA 10 |
| |
| #define VMODE_NAME_LEN_MAX 64 |
| static struct class *vout2_class; |
| static DEFINE_MUTEX(vout2_serve_mutex); |
| static char vout2_mode[VMODE_NAME_LEN_MAX]; |
| static char local_name[VMODE_NAME_LEN_MAX] = {0}; |
| static unsigned int bist_mode2; |
| |
| static char vout2_axis[64]; |
| |
| static struct extcon_dev *vout2_excton_setmode; |
| static const unsigned int vout2_cable[] = { |
| EXTCON_TYPE_DISP, |
| EXTCON_NONE, |
| }; |
| |
| static struct vout_cdev_s *vout2_cdev; |
| static struct clk *vpu_clkc; |
| static unsigned char vpu_clkc_state; |
| |
| /* ********************************************************** |
| * null display support |
| * ********************************************************** |
| */ |
| 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, |
| }; |
| |
| static struct vinfo_s *nulldisp_get_current_info(void) |
| { |
| return &nulldisp_vinfo; |
| } |
| |
| static int nulldisp_set_current_vmode(enum vmode_e mode) |
| { |
| return 0; |
| } |
| |
| static enum vmode_e nulldisp_validate_vmode(char *name) |
| { |
| if (strncmp(nulldisp_vinfo.name, name, |
| strlen(nulldisp_vinfo.name)) == 0) { |
| return nulldisp_vinfo.mode; |
| } |
| |
| return VMODE_MAX; |
| } |
| |
| static int nulldisp_vmode_is_supported(enum vmode_e mode) |
| { |
| if (nulldisp_vinfo.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_vout2_server = { |
| .name = "nulldisp_vout2_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_vout2_mode_internal(void) |
| { |
| return vout2_mode; |
| } |
| EXPORT_SYMBOL(get_vout2_mode_internal); |
| |
| static int set_vout2_mode(char *name) |
| { |
| enum vmode_e mode; |
| int ret = 0; |
| |
| vout_trim_string(name); |
| VOUTPR("vout2: vmode set to %s\n", name); |
| |
| if (strcmp(name, local_name) == 0) { |
| VOUTPR("vout2: don't set the same mode as current\n"); |
| return -1; |
| } |
| |
| mode = validate_vmode2(name); |
| if (mode == VMODE_MAX) { |
| VOUTERR("vout2: no matched vout2 mode\n"); |
| return -1; |
| } |
| memset(local_name, 0, sizeof(local_name)); |
| snprintf(local_name, VMODE_NAME_LEN_MAX, "%s", name); |
| |
| extcon_set_state_sync(vout2_excton_setmode, EXTCON_TYPE_DISP, 1); |
| |
| vout2_notifier_call_chain(VOUT_EVENT_MODE_CHANGE_PRE, &mode); |
| ret = set_current_vmode2(mode); |
| if (ret) { |
| VOUTERR("vout2: new mode %s set error\n", name); |
| } else { |
| snprintf(vout2_mode, VMODE_NAME_LEN_MAX, "%s", name); |
| VOUTPR("vout2: new mode %s set ok\n", vout2_mode); |
| } |
| vout2_notifier_call_chain(VOUT_EVENT_MODE_CHANGE, &mode); |
| |
| extcon_set_state_sync(vout2_excton_setmode, EXTCON_TYPE_DISP, 0); |
| |
| return ret; |
| } |
| |
| static int set_vout2_init_mode(void) |
| { |
| enum vmode_e vmode = nulldisp_vinfo.mode; |
| int ret = 0; |
| |
| ret = set_current_vmode2(vmode); |
| if (ret) { |
| VOUTERR("vout2: init mode null set error\n"); |
| } else { |
| snprintf(local_name, VMODE_NAME_LEN_MAX, nulldisp_vinfo.name); |
| snprintf(vout2_mode, VMODE_NAME_LEN_MAX, nulldisp_vinfo.name); |
| VOUTPR("vout2: init mode %s set ok\n", vout2_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(¶ms, " "); |
| 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_vout2_axis(char *para) |
| { |
| static struct disp_rect_s disp_rect[OSD_COUNT]; |
| /* char count = OSD_COUNT * 4; */ |
| int *pt = &disp_rect[0].x; |
| 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("vout2: 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); |
| vout2_notifier_call_chain(VOUT_EVENT_OSD_DISP_AXIS, &disp_rect[0]); |
| } |
| |
| static ssize_t vout2_mode_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| |
| ret = snprintf(buf, VMODE_NAME_LEN_MAX, "%s\n", vout2_mode); |
| |
| return ret; |
| } |
| |
| static ssize_t vout2_mode_store(struct class *class, |
| struct class_attribute *attr, const char *buf, size_t count) |
| { |
| char mode[64]; |
| |
| mutex_lock(&vout2_serve_mutex); |
| snprintf(mode, VMODE_NAME_LEN_MAX, "%s", buf); |
| set_vout2_mode(mode); |
| mutex_unlock(&vout2_serve_mutex); |
| return count; |
| } |
| |
| static ssize_t vout2_axis_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| |
| ret = snprintf(buf, 64, "%s\n", vout2_axis); |
| return ret; |
| } |
| |
| static ssize_t vout2_axis_store(struct class *class, |
| struct class_attribute *attr, const char *buf, size_t count) |
| { |
| mutex_lock(&vout2_serve_mutex); |
| snprintf(vout2_axis, 64, "%s", buf); |
| set_vout2_axis(vout2_axis); |
| mutex_unlock(&vout2_serve_mutex); |
| return count; |
| } |
| |
| static ssize_t vout2_fr_policy_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int policy; |
| int ret = 0; |
| |
| policy = get_vframe2_rate_policy(); |
| ret = sprintf(buf, "%d\n", policy); |
| |
| return ret; |
| } |
| |
| static ssize_t vout2_fr_policy_store(struct class *class, |
| struct class_attribute *attr, const char *buf, size_t count) |
| { |
| int policy; |
| int ret = 0; |
| |
| mutex_lock(&vout2_serve_mutex); |
| ret = kstrtoint(buf, 10, &policy); |
| if (ret) { |
| pr_info("%s: invalid data\n", __func__); |
| mutex_unlock(&vout2_serve_mutex); |
| return -EINVAL; |
| } |
| ret = set_vframe2_rate_policy(policy); |
| if (ret) |
| pr_info("%s: %d failed\n", __func__, policy); |
| mutex_unlock(&vout2_serve_mutex); |
| |
| return count; |
| } |
| |
| static ssize_t vout2_bist_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| int ret = 0; |
| |
| ret = sprintf(buf, "%d\n", bist_mode2); |
| |
| return ret; |
| } |
| |
| static ssize_t vout2_bist_store(struct class *class, |
| struct class_attribute *attr, const char *buf, size_t count) |
| { |
| int ret = 0; |
| |
| mutex_lock(&vout2_serve_mutex); |
| |
| ret = kstrtouint(buf, 10, &bist_mode2); |
| if (ret) { |
| pr_info("%s: invalid data\n", __func__); |
| mutex_unlock(&vout2_serve_mutex); |
| return -EINVAL; |
| } |
| set_vout2_bist(bist_mode2); |
| |
| mutex_unlock(&vout2_serve_mutex); |
| |
| return count; |
| } |
| |
| static ssize_t vout2_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_vinfo2(); |
| if (info == NULL) |
| return sprintf(buf, "current vinfo2 is null\n"); |
| |
| len = sprintf(buf, "current vinfo2:\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" |
| " video_clk: %d\n" |
| " viu_color_fmt: %d\n" |
| " viu_mux: %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->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++) { |
| 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 vout2_class_attrs[] = { |
| __ATTR(mode, 0644, vout2_mode_show, vout2_mode_store), |
| __ATTR(axis, 0644, vout2_axis_show, vout2_axis_store), |
| __ATTR(fr_policy, 0644, |
| vout2_fr_policy_show, vout2_fr_policy_store), |
| __ATTR(bist, 0644, vout2_bist_show, vout2_bist_store), |
| __ATTR(vinfo, 0444, vout2_vinfo_show, NULL), |
| }; |
| |
| static int vout2_attr_create(void) |
| { |
| int i; |
| int ret = 0; |
| |
| /* create vout class */ |
| vout2_class = class_create(THIS_MODULE, VOUT_CLASS_NAME); |
| if (IS_ERR(vout2_class)) { |
| VOUTERR("vout2: create vout2 class fail\n"); |
| return -1; |
| } |
| |
| /* create vout class attr files */ |
| for (i = 0; i < ARRAY_SIZE(vout2_class_attrs); i++) { |
| if (class_create_file(vout2_class, &vout2_class_attrs[i])) { |
| VOUTERR("vout2: create vout2 attribute %s fail\n", |
| vout2_class_attrs[i].attr.name); |
| } |
| } |
| |
| VOUTPR("vout2: create vout2 attribute OK\n"); |
| |
| return ret; |
| } |
| |
| static int vout2_attr_remove(void) |
| { |
| int i; |
| |
| if (vout2_class == NULL) |
| return 0; |
| |
| for (i = 0; i < ARRAY_SIZE(vout2_class_attrs); i++) |
| class_remove_file(vout2_class, &vout2_class_attrs[i]); |
| |
| class_destroy(vout2_class); |
| vout2_class = NULL; |
| |
| return 0; |
| } |
| |
| /* ************************************************************* */ |
| /* vout2 ioctl */ |
| /* ************************************************************* */ |
| static int vout2_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 vout2_io_release(struct inode *inode, struct file *file) |
| { |
| VOUTPR("%s\n", __func__); |
| file->private_data = NULL; |
| return 0; |
| } |
| |
| static long vout2_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_vinfo2(); |
| 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 vout2_compat_ioctl(struct file *file, unsigned int cmd, |
| unsigned long arg) |
| { |
| unsigned long ret; |
| |
| arg = (unsigned long)compat_ptr(arg); |
| ret = vout2_ioctl(file, cmd, arg); |
| return ret; |
| } |
| #endif |
| |
| static const struct file_operations vout2_fops = { |
| .owner = THIS_MODULE, |
| .open = vout2_io_open, |
| .release = vout2_io_release, |
| .unlocked_ioctl = vout2_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = vout2_compat_ioctl, |
| #endif |
| }; |
| |
| static int vout2_fops_create(void) |
| { |
| int ret = 0; |
| |
| vout2_cdev = kmalloc(sizeof(struct vout_cdev_s), GFP_KERNEL); |
| if (!vout2_cdev) { |
| VOUTERR("vout2: failed to allocate vout2_cdev\n"); |
| return -1; |
| } |
| |
| ret = alloc_chrdev_region(&vout2_cdev->devno, 0, 1, VOUT_CDEV_NAME); |
| if (ret < 0) { |
| VOUTERR("vout2: failed to alloc vout2 devno\n"); |
| goto vout2_fops_err1; |
| } |
| |
| cdev_init(&vout2_cdev->cdev, &vout2_fops); |
| vout2_cdev->cdev.owner = THIS_MODULE; |
| ret = cdev_add(&vout2_cdev->cdev, vout2_cdev->devno, 1); |
| if (ret) { |
| VOUTERR("vout2: failed to add vout2 cdev\n"); |
| goto vout2_fops_err2; |
| } |
| |
| vout2_cdev->dev = device_create(vout2_class, NULL, vout2_cdev->devno, |
| NULL, VOUT_CDEV_NAME); |
| if (IS_ERR(vout2_cdev->dev)) { |
| ret = PTR_ERR(vout2_cdev->dev); |
| VOUTERR("vout2: failed to create vout2 device: %d\n", ret); |
| goto vout2_fops_err3; |
| } |
| |
| VOUTPR("vout2: %s OK\n", __func__); |
| return 0; |
| |
| vout2_fops_err3: |
| cdev_del(&vout2_cdev->cdev); |
| vout2_fops_err2: |
| unregister_chrdev_region(vout2_cdev->devno, 1); |
| vout2_fops_err1: |
| kfree(vout2_cdev); |
| vout2_cdev = NULL; |
| return -1; |
| } |
| |
| static void vout2_fops_remove(void) |
| { |
| cdev_del(&vout2_cdev->cdev); |
| unregister_chrdev_region(vout2_cdev->devno, 1); |
| kfree(vout2_cdev); |
| vout2_cdev = NULL; |
| } |
| /* ************************************************************* */ |
| |
| #ifdef CONFIG_PM |
| static int aml_vout2_suspend(struct platform_device *pdev, pm_message_t state) |
| { |
| #ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND |
| |
| if (early_suspend_flag) |
| return 0; |
| |
| #endif |
| vout2_suspend(); |
| return 0; |
| } |
| |
| static int aml_vout2_resume(struct platform_device *pdev) |
| { |
| #ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND |
| |
| if (early_suspend_flag) |
| return 0; |
| |
| #endif |
| vout2_resume(); |
| return 0; |
| } |
| #endif |
| |
| #ifdef CONFIG_HIBERNATION |
| static int aml_vout2_freeze(struct device *dev) |
| { |
| return 0; |
| } |
| |
| static int aml_vout2_thaw(struct device *dev) |
| { |
| return 0; |
| } |
| |
| static int aml_vout2_restore(struct device *dev) |
| { |
| enum vmode_e mode; |
| |
| mode = get_current_vmode2(); |
| vout2_notifier_call_chain(VOUT_EVENT_MODE_CHANGE, &mode); |
| |
| return 0; |
| } |
| static int aml_vout2_pm_suspend(struct device *dev) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| |
| return aml_vout2_suspend(pdev, PMSG_SUSPEND); |
| } |
| static int aml_vout2_pm_resume(struct device *dev) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| |
| return aml_vout2_resume(pdev); |
| } |
| #endif |
| |
| #ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND |
| static void aml_vout2_early_suspend(struct early_suspend *h) |
| { |
| if (early_suspend_flag) |
| return; |
| |
| vout2_suspend(); |
| early_suspend_flag = 1; |
| } |
| |
| static void aml_vout2_late_resume(struct early_suspend *h) |
| { |
| if (!early_suspend_flag) |
| return; |
| |
| early_suspend_flag = 0; |
| vout2_resume(); |
| } |
| #endif |
| |
| static void aml_vout2_extcon_register(struct platform_device *pdev) |
| { |
| struct extcon_dev *edev; |
| int ret; |
| |
| /*set display mode*/ |
| edev = extcon_dev_allocate(vout2_cable); |
| if (IS_ERR(edev)) { |
| VOUTERR("failed to allocate vout2 extcon setmode\n"); |
| return; |
| } |
| |
| edev->dev.parent = &pdev->dev; |
| edev->name = "vout2_excton_setmode"; |
| dev_set_name(&edev->dev, "setmode2"); |
| ret = extcon_dev_register(edev); |
| if (ret) { |
| VOUTERR("failed to register vout2 extcon setmode\n"); |
| return; |
| } |
| vout2_excton_setmode = edev; |
| } |
| |
| static void aml_vout2_extcon_free(void) |
| { |
| extcon_dev_free(vout2_excton_setmode); |
| vout2_excton_setmode = NULL; |
| } |
| |
| /***************************************************************** |
| ** |
| ** vout driver interface |
| ** |
| ******************************************************************/ |
| static int vout2_clk_on_notifier(struct notifier_block *nb, |
| unsigned long event, void *data) |
| { |
| int *vmod; |
| |
| if ((event & VOUT_EVENT_MODE_CHANGE_PRE) == 0) |
| return NOTIFY_DONE; |
| |
| if (data == NULL) { |
| VOUTERR("%s: data is NULL\n", __func__); |
| return NOTIFY_DONE; |
| } |
| vmod = (int *)data; |
| if (*vmod < VMODE_NULL) { |
| if (IS_ERR_OR_NULL(vpu_clkc)) |
| VOUTERR("vout2: vpu_clkc\n"); |
| else { |
| if (vpu_clkc_state == 0) { |
| VOUTPR("vout2: enable vpu_clkc\n"); |
| clk_prepare_enable(vpu_clkc); |
| vpu_clkc_state = 1; |
| } |
| } |
| } |
| |
| return NOTIFY_OK; |
| } |
| |
| static struct notifier_block vout2_clk_on_nb = { |
| .notifier_call = vout2_clk_on_notifier, |
| }; |
| |
| static int vout2_clk_off_notifier(struct notifier_block *nb, |
| unsigned long event, void *data) |
| { |
| int *vmod; |
| |
| if ((event & VOUT_EVENT_MODE_CHANGE) == 0) |
| return NOTIFY_DONE; |
| |
| if (data == NULL) { |
| VOUTERR("%s: data is NULL\n", __func__); |
| return NOTIFY_DONE; |
| } |
| vmod = (int *)data; |
| if (*vmod >= VMODE_NULL) { |
| if (IS_ERR_OR_NULL(vpu_clkc)) |
| VOUTERR("vout2: vpu_clkc\n"); |
| else { |
| if (vpu_clkc_state) { |
| VOUTPR("vout2: disable vpu_clkc\n"); |
| clk_disable_unprepare(vpu_clkc); |
| vpu_clkc_state = 0; |
| } |
| } |
| } |
| |
| return NOTIFY_OK; |
| } |
| |
| static struct notifier_block vout2_clk_off_nb = { |
| .notifier_call = vout2_clk_off_notifier, |
| }; |
| |
| static int vout2_notifier_register(void) |
| { |
| int ret = 0; |
| |
| ret = vout2_register_client(&vout2_clk_on_nb); |
| if (ret) |
| VOUTERR("register vout2_clk_on_nb failed\n"); |
| ret = vout2_register_client(&vout2_clk_off_nb); |
| if (ret) |
| VOUTERR("register vout2_clk_off_nb failed\n"); |
| |
| return 0; |
| } |
| |
| static void vout2_notifier_unregister(void) |
| { |
| vout2_unregister_client(&vout2_clk_on_nb); |
| vout2_unregister_client(&vout2_clk_off_nb); |
| } |
| |
| static void vout2_clktree_init(struct device *dev) |
| { |
| struct clk *vpu_clkc0; |
| |
| vpu_clkc = NULL; |
| vpu_clkc_state = 0; |
| |
| /* init & enable vpu_clk */ |
| vpu_clkc0 = devm_clk_get(dev, "vpu_clkc0"); |
| vpu_clkc = devm_clk_get(dev, "vpu_clkc"); |
| if ((IS_ERR_OR_NULL(vpu_clkc0)) || |
| (IS_ERR_OR_NULL(vpu_clkc))) { |
| VOUTERR("vout2: %s: vpu_clkc\n", __func__); |
| } else { |
| clk_set_rate(vpu_clkc0, 200000000); |
| clk_set_parent(vpu_clkc, vpu_clkc0); |
| } |
| |
| VOUTPR("vout2: clktree_init\n"); |
| } |
| |
| static int aml_vout2_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_vout2_early_suspend; |
| early_suspend.resume = aml_vout2_late_resume; |
| register_early_suspend(&early_suspend); |
| #endif |
| |
| vout2_class = NULL; |
| ret = vout2_attr_create(); |
| ret = vout2_fops_create(); |
| |
| vout2_register_server(&nulldisp_vout2_server); |
| set_vout2_init_mode(); |
| vout2_clktree_init(&pdev->dev); |
| |
| aml_vout2_extcon_register(pdev); |
| |
| vout2_notifier_register(); |
| |
| VOUTPR("vout2: %s OK\n", __func__); |
| return ret; |
| } |
| |
| static int aml_vout2_remove(struct platform_device *pdev) |
| { |
| #ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND |
| unregister_early_suspend(&early_suspend); |
| #endif |
| vout2_notifier_unregister(); |
| |
| aml_vout2_extcon_free(); |
| vout2_attr_remove(); |
| vout2_fops_remove(); |
| vout2_unregister_server(&nulldisp_vout2_server); |
| |
| return 0; |
| } |
| |
| static void aml_vout2_shutdown(struct platform_device *pdev) |
| { |
| VOUTPR("vout2: %s\n", __func__); |
| vout2_shutdown(); |
| } |
| |
| static const struct of_device_id aml_vout2_dt_match[] = { |
| { .compatible = "amlogic, vout2",}, |
| { }, |
| }; |
| |
| #ifdef CONFIG_HIBERNATION |
| const struct dev_pm_ops vout2_pm = { |
| .freeze = aml_vout2_freeze, |
| .thaw = aml_vout2_thaw, |
| .restore = aml_vout2_restore, |
| .suspend = aml_vout2_pm_suspend, |
| .resume = aml_vout2_pm_resume, |
| }; |
| #endif |
| |
| static struct platform_driver vout2_driver = { |
| .probe = aml_vout2_probe, |
| .remove = aml_vout2_remove, |
| .shutdown = aml_vout2_shutdown, |
| #ifdef CONFIG_PM |
| .suspend = aml_vout2_suspend, |
| .resume = aml_vout2_resume, |
| #endif |
| .driver = { |
| .name = "vout2", |
| .of_match_table = aml_vout2_dt_match, |
| #ifdef CONFIG_HIBERNATION |
| .pm = &vout2_pm, |
| #endif |
| }, |
| }; |
| |
| static int __init vout2_init_module(void) |
| { |
| int ret = 0; |
| |
| if (platform_driver_register(&vout2_driver)) { |
| VOUTERR("vout2: failed to register VOUT2 driver\n"); |
| ret = -ENODEV; |
| } |
| |
| return ret; |
| } |
| |
| static __exit void vout2_exit_module(void) |
| { |
| platform_driver_unregister(&vout2_driver); |
| } |
| |
| module_init(vout2_init_module); |
| module_exit(vout2_exit_module); |
| |
| MODULE_AUTHOR("Platform-BJ <platform.bj@amlogic.com>"); |
| MODULE_DESCRIPTION("VOUT2 Server Module"); |
| MODULE_LICENSE("GPL"); |