| /* |
| * drivers/amlogic/media/vout/cvbs/cvbs_out.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/module.h> |
| #include <linux/kernel.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/of_device.h> |
| #include <linux/fs.h> |
| #include <linux/slab.h> |
| #include <linux/interrupt.h> |
| #include <linux/ctype.h> |
| /*#include <linux/major.h>*/ |
| #include <linux/cdev.h> |
| #include <linux/delay.h> |
| #include <linux/jiffies.h> |
| #include <linux/workqueue.h> |
| #include <linux/uaccess.h> |
| #include <linux/clk.h> |
| |
| /* Amlogic Headers */ |
| #include <linux/amlogic/cpu_version.h> |
| #include <linux/amlogic/iomap.h> |
| #include <linux/amlogic/media/vout/vout_notify.h> |
| #include <linux/amlogic/media/vout/vinfo.h> |
| #include <linux/amlogic/media/vout/vdac_dev.h> |
| #ifdef CONFIG_AMLOGIC_VPU |
| #include <linux/amlogic/media/vpu/vpu.h> |
| #endif |
| |
| /* Local Headers */ |
| #include "cvbsregs.h" |
| #include "cvbs_out.h" |
| #include "enc_clk_config.h" |
| #include "cvbs_out_reg.h" |
| #include "cvbs_log.h" |
| #ifdef CONFIG_AMLOGIC_WSS |
| #include "wss.h" |
| #endif |
| |
| static struct vinfo_s cvbs_info[] = { |
| { /* MODE_480CVBS*/ |
| .name = "480cvbs", |
| .mode = VMODE_CVBS, |
| .width = 720, |
| .height = 480, |
| .field_height = 240, |
| .aspect_ratio_num = 4, |
| .aspect_ratio_den = 3, |
| .sync_duration_num = 60, |
| .sync_duration_den = 1, |
| .video_clk = 27000000, |
| .htotal = 1716, |
| .vtotal = 525, |
| .fr_adj_type = VOUT_FR_ADJ_NONE, |
| .viu_color_fmt = COLOR_FMT_YUV444, |
| .viu_mux = VIU_MUX_ENCI, |
| .vout_device = NULL, |
| }, |
| { /* MODE_576CVBS */ |
| .name = "576cvbs", |
| .mode = VMODE_CVBS, |
| .width = 720, |
| .height = 576, |
| .field_height = 288, |
| .aspect_ratio_num = 4, |
| .aspect_ratio_den = 3, |
| .sync_duration_num = 50, |
| .sync_duration_den = 1, |
| .video_clk = 27000000, |
| .htotal = 1728, |
| .vtotal = 625, |
| .fr_adj_type = VOUT_FR_ADJ_NONE, |
| .viu_color_fmt = COLOR_FMT_YUV444, |
| .viu_mux = VIU_MUX_ENCI, |
| .vout_device = NULL, |
| }, |
| { /* MODE_PAL_M */ |
| .name = "pal_m", |
| .mode = VMODE_CVBS, |
| .width = 720, |
| .height = 480, |
| .field_height = 240, |
| .aspect_ratio_num = 4, |
| .aspect_ratio_den = 3, |
| .sync_duration_num = 60, |
| .sync_duration_den = 1, |
| .video_clk = 27000000, |
| .htotal = 1716, |
| .vtotal = 525, |
| .viu_color_fmt = COLOR_FMT_YUV444, |
| .viu_mux = VIU_MUX_ENCI, |
| .vout_device = NULL, |
| }, |
| { /* MODE_PAL_N */ |
| .name = "pal_n", |
| .mode = VMODE_CVBS, |
| .width = 720, |
| .height = 576, |
| .field_height = 288, |
| .aspect_ratio_num = 4, |
| .aspect_ratio_den = 3, |
| .sync_duration_num = 50, |
| .sync_duration_den = 1, |
| .video_clk = 27000000, |
| .htotal = 1728, |
| .vtotal = 625, |
| .viu_color_fmt = COLOR_FMT_YUV444, |
| .viu_mux = VIU_MUX_ENCI, |
| .vout_device = NULL, |
| }, |
| }; |
| |
| /*bit[0]: 0=vid_pll, 1=gp0_pll*/ |
| /*bit[1]: 0=vid2_clk, 1=vid1_clk*/ |
| unsigned int cvbs_clk_path; |
| |
| static struct disp_module_info_s disp_module_info; |
| static struct disp_module_info_s *info; |
| static enum cvbs_mode_e local_cvbs_mode; |
| static unsigned int vdac_cfg_valid; |
| static unsigned int vdac_cfg_value; |
| static DEFINE_MUTEX(setmode_mutex); |
| static DEFINE_MUTEX(CC_mutex); |
| |
| static int cvbs_vdac_power_level; |
| static void vdac_power_level_store(char *para); |
| SET_CVBS_CLASS_ATTR(vdac_power_level, vdac_power_level_store); |
| |
| static void cvbs_debug_store(char *para); |
| SET_CVBS_CLASS_ATTR(debug, cvbs_debug_store); |
| |
| #ifdef CONFIG_AMLOGIC_WSS |
| struct class_attribute class_CVBS_attr_wss = __ATTR(wss, 0644, |
| aml_CVBS_attr_wss_show, aml_CVBS_attr_wss_store); |
| #endif /*CONFIG_AMLOGIC_WSS*/ |
| |
| static void cvbs_config_vdac(unsigned int flag, unsigned int cfg); |
| static void cvbs_cntl_output(unsigned int open); |
| static void cvbs_performance_config(unsigned int index); |
| #ifdef CONFIG_CVBS_PERFORMANCE_COMPATIBILITY_SUPPORT |
| static void cvbs_performance_enhancement(enum cvbs_mode_e mode); |
| #endif |
| |
| #if 0 |
| static int get_vdac_power_level(void) |
| { |
| return cvbs_vdac_power_level; |
| } |
| |
| static int check_cpu_type(unsigned int cpu_type) |
| { |
| int ret = 0; |
| |
| ret = (get_meson_cpu_version(MESON_CPU_VERSION_LVL_MAJOR) == cpu_type); |
| |
| return ret; |
| } |
| #endif |
| |
| /* static int get_cpu_minor(void) |
| * { |
| * return get_meson_cpu_version(MESON_CPU_VERSION_LVL_MINOR); |
| * } |
| */ |
| |
| int cvbs_cpu_type(void) |
| { |
| return info->cvbs_data->cpu_id; |
| } |
| EXPORT_SYMBOL(cvbs_cpu_type); |
| |
| static unsigned int cvbs_get_trimming_version(unsigned int flag) |
| { |
| unsigned int version = 0xff; |
| |
| if ((flag & 0xf0) == 0xa0) |
| version = 5; |
| else if ((flag & 0xf0) == 0x40) |
| version = 2; |
| else if ((flag & 0xc0) == 0x80) |
| version = 1; |
| else if ((flag & 0xc0) == 0x00) |
| version = 0; |
| return version; |
| } |
| |
| static void cvbs_config_vdac(unsigned int flag, unsigned int cfg) |
| { |
| unsigned char version = 0; |
| |
| vdac_cfg_value = cfg & 0x7; |
| version = cvbs_get_trimming_version(flag); |
| /* flag 1/0 for validity of vdac config */ |
| if ((version == 1) || (version == 2) || (version == 5)) |
| vdac_cfg_valid = 1; |
| else |
| vdac_cfg_valid = 0; |
| cvbs_log_info("cvbs trimming.%d.v%d: 0x%x, 0x%x\n", |
| vdac_cfg_valid, version, flag, cfg); |
| } |
| |
| static void cvbs_cntl_output(unsigned int open) |
| { |
| unsigned int cntl0 = 0, cntl1 = 0; |
| |
| if (open == 0) { /* close */ |
| cntl0 = 0; |
| cntl1 = 8; |
| vdac_set_ctrl0_ctrl1(cntl0, cntl1); |
| |
| /* must enable adc bandgap, the adc ref signal for demod */ |
| vdac_enable(0, 0x8); |
| } else if (open == 1) { /* open */ |
| |
| cntl0 = info->cvbs_data->cntl0_val; |
| cntl1 = (vdac_cfg_valid == 0) ? 0 : vdac_cfg_value; |
| cvbs_log_info("vdac open.%d = 0x%x, 0x%x\n", |
| vdac_cfg_valid, cntl0, cntl1); |
| vdac_set_ctrl0_ctrl1(cntl0, cntl1); |
| |
| /*vdac ctrl for cvbsout/rf signal,adc bandgap*/ |
| vdac_enable(1, 0x8); |
| } |
| } |
| |
| /* 0xff for none config from uboot */ |
| static unsigned int cvbs_performance_index = 0xff; |
| static void cvbs_performance_config(unsigned int index) |
| { |
| cvbs_performance_index = index; |
| } |
| |
| #ifdef CONFIG_CVBS_PERFORMANCE_COMPATIBILITY_SUPPORT |
| static void cvbs_performance_enhancement(enum cvbs_mode_e mode) |
| { |
| const struct reg_s *s = NULL; |
| int i = 0; |
| |
| if (mode != MODE_576CVBS) |
| return; |
| |
| s = info->cvbs_conf.performance_reg_table; |
| if (s == NULL) { |
| cvbs_log_err("error: can't find performance table!\n"); |
| return; |
| } |
| |
| while (i < info->cvbs_conf.performance_reg_cnt) { |
| cvbs_out_reg_write(s->reg, s->val); |
| s++; |
| i++; |
| } |
| cvbs_log_info("%s\n", __func__); |
| } |
| #endif/* end of CVBS_PERFORMANCE_COMPATIBILITY_SUPPORT */ |
| |
| static int cvbs_out_set_venc(enum cvbs_mode_e mode) |
| { |
| const struct reg_s *s; |
| int ret; |
| |
| /* setup encoding regs */ |
| s = cvbsregsTab[mode].enc_reg_setting; |
| if (!s) { |
| cvbs_log_info("display mode %d get enc set NULL\n", mode); |
| ret = -1; |
| } else { |
| while (s->reg != MREG_END_MARKER) { |
| cvbs_out_reg_write(s->reg, s->val); |
| s++; |
| } |
| ret = 0; |
| } |
| /* cvbs_log_info("%s[%d]\n", __func__, __LINE__); */ |
| return ret; |
| } |
| |
| static void cvbs_out_disable_clk(void) |
| { |
| disable_vmode_clk(); |
| } |
| |
| static void cvbs_out_vpu_power_ctrl(int status) |
| { |
| if (info->vinfo == NULL) |
| return; |
| if (status) { |
| #ifdef CONFIG_AMLOGIC_VPU |
| request_vpu_clk_vmod(info->vinfo->video_clk, VPU_VENCI); |
| switch_vpu_mem_pd_vmod(VPU_VENCI, VPU_MEM_POWER_ON); |
| #endif |
| } else { |
| #ifdef CONFIG_AMLOGIC_VPU |
| switch_vpu_mem_pd_vmod(VPU_VENCI, VPU_MEM_POWER_DOWN); |
| release_vpu_clk_vmod(VPU_VENCI); |
| #endif |
| } |
| } |
| |
| static void cvbs_out_clk_gate_ctrl(int status) |
| { |
| if (info->vinfo == NULL) |
| return; |
| |
| if (status) { |
| if (info->clk_gate_state) { |
| cvbs_log_info("clk_gate is already on\n"); |
| return; |
| } |
| |
| if (IS_ERR(info->venci_top_gate)) |
| cvbs_log_err("error: %s: venci_top_gate\n", __func__); |
| else |
| clk_prepare_enable(info->venci_top_gate); |
| |
| if (IS_ERR(info->venci_0_gate)) |
| cvbs_log_err("error: %s: venci_0_gate\n", __func__); |
| else |
| clk_prepare_enable(info->venci_0_gate); |
| |
| if (IS_ERR(info->venci_1_gate)) |
| cvbs_log_err("error: %s: venci_1_gate\n", __func__); |
| else |
| clk_prepare_enable(info->venci_1_gate); |
| |
| if (IS_ERR(info->vdac_clk_gate)) |
| cvbs_log_err("error: %s: vdac_clk_gate\n", __func__); |
| else |
| clk_prepare_enable(info->vdac_clk_gate); |
| |
| #ifdef CONFIG_AMLOGIC_VPU |
| switch_vpu_clk_gate_vmod(VPU_VENCI, VPU_CLK_GATE_ON); |
| #endif |
| |
| info->clk_gate_state = 1; |
| } else { |
| if (info->clk_gate_state == 0) { |
| cvbs_log_info("clk_gate is already off\n"); |
| return; |
| } |
| |
| #ifdef CONFIG_AMLOGIC_VPU |
| switch_vpu_clk_gate_vmod(VPU_VENCI, VPU_CLK_GATE_OFF); |
| #endif |
| if (IS_ERR(info->vdac_clk_gate)) |
| cvbs_log_err("error: %s: vdac_clk_gate\n", __func__); |
| else |
| clk_disable_unprepare(info->vdac_clk_gate); |
| |
| if (IS_ERR(info->venci_0_gate)) |
| cvbs_log_err("error: %s: venci_0_gate\n", __func__); |
| else |
| clk_disable_unprepare(info->venci_0_gate); |
| |
| if (IS_ERR(info->venci_1_gate)) |
| cvbs_log_err("error: %s: venci_1_gate\n", __func__); |
| else |
| clk_disable_unprepare(info->venci_1_gate); |
| |
| if (IS_ERR(info->venci_top_gate)) |
| cvbs_log_err("error: %s: venci_top_gate\n", __func__); |
| else |
| clk_disable_unprepare(info->venci_top_gate); |
| |
| info->clk_gate_state = 0; |
| } |
| } |
| |
| static void cvbs_dv_dwork(struct work_struct *work) |
| { |
| if (!info->dwork_flag) |
| return; |
| cvbs_cntl_output(1); |
| } |
| |
| int cvbs_out_setmode(void) |
| { |
| int ret; |
| |
| switch (local_cvbs_mode) { |
| case MODE_480CVBS: |
| cvbs_log_info("SET cvbs mode: 480cvbs\n"); |
| break; |
| case MODE_576CVBS: |
| cvbs_log_info("SET cvbs mode: 576cvbs\n"); |
| break; |
| case MODE_PAL_M: |
| cvbs_log_info("SET cvbs mode: pal_m\n"); |
| break; |
| case MODE_PAL_N: |
| cvbs_log_info("SET cvbs mode: pal_n\n"); |
| break; |
| default: |
| cvbs_log_err("cvbs_out_setmode:invalid cvbs mode"); |
| break; |
| } |
| |
| if (local_cvbs_mode >= MODE_MAX) { |
| cvbs_log_err("cvbs_out_setmode:mode error.return"); |
| return -1; |
| } |
| mutex_lock(&setmode_mutex); |
| |
| cvbs_out_vpu_power_ctrl(1); |
| cvbs_out_clk_gate_ctrl(1); |
| |
| cvbs_cntl_output(0); |
| cvbs_out_reg_write(VENC_VDAC_SETTING, 0xff); |
| /* Before setting clk for CVBS, disable ENCI to avoid hungup */ |
| cvbs_out_reg_write(ENCI_VIDEO_EN, 0); |
| set_vmode_clk(); |
| ret = cvbs_out_set_venc(local_cvbs_mode); |
| if (ret) { |
| mutex_lock(&setmode_mutex); |
| return -1; |
| } |
| #ifdef CONFIG_CVBS_PERFORMANCE_COMPATIBILITY_SUPPORT |
| cvbs_performance_enhancement(local_cvbs_mode); |
| #endif |
| info->dwork_flag = 1; |
| schedule_delayed_work(&info->dv_dwork, msecs_to_jiffies(1000)); |
| mutex_unlock(&setmode_mutex); |
| return 0; |
| } |
| |
| static int cvbs_open(struct inode *inode, struct file *file) |
| { |
| struct disp_module_info_s *dinfo; |
| /* Get the per-device structure that contains this cdev */ |
| dinfo = container_of(&inode->i_cdev, struct disp_module_info_s, cdev); |
| file->private_data = dinfo; |
| return 0; |
| } |
| |
| static int cvbs_release(struct inode *inode, struct file *file) |
| { |
| file->private_data = NULL; |
| return 0; |
| } |
| |
| static long cvbs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
| { |
| long ret = 0; |
| unsigned int CC_2byte_data = 0; |
| void __user *argp = (void __user *)arg; |
| |
| cvbs_log_info("[cvbs..] %s: cmd_nr = 0x%x\n", |
| __func__, _IOC_NR(cmd)); |
| if (_IOC_TYPE(cmd) != _TM_V) { |
| cvbs_log_err("%s invalid command: %u\n", __func__, cmd); |
| return -ENOTTY; |
| } |
| switch (cmd) { |
| case VOUT_IOC_CC_OPEN: |
| cvbs_out_reg_setb(ENCI_VBI_SETTING, 0x3, 0, 2); |
| break; |
| case VOUT_IOC_CC_CLOSE: |
| cvbs_out_reg_setb(ENCI_VBI_SETTING, 0x0, 0, 2); |
| break; |
| case VOUT_IOC_CC_DATA: { |
| struct vout_CCparm_s parm = {0}; |
| |
| mutex_lock(&CC_mutex); |
| if (copy_from_user(&parm, argp, |
| sizeof(struct vout_CCparm_s))) { |
| cvbs_log_err("VOUT_IOC_CC_DATAinvalid parameter\n"); |
| ret = -EFAULT; |
| mutex_unlock(&CC_mutex); |
| break; |
| } |
| /*cc standerd:nondisplay control byte + display control byte*/ |
| /*our chip high-low 16bits is opposite*/ |
| CC_2byte_data = parm.data2 << 8 | parm.data1; |
| if (parm.type == 0) |
| cvbs_out_reg_write(ENCI_VBI_CCDT_EVN, CC_2byte_data); |
| else if (parm.type == 1) |
| cvbs_out_reg_write(ENCI_VBI_CCDT_ODD, CC_2byte_data); |
| else |
| cvbs_log_err("CC type:%d,Unknown.\n", parm.type); |
| cvbs_log_info("VOUT_IOC_CC_DATA..type:%d,0x%x\n", |
| parm.type, CC_2byte_data); |
| mutex_unlock(&CC_mutex); |
| break; |
| } |
| default: |
| ret = -ENOIOCTLCMD; |
| cvbs_log_err("%s %d is not supported command\n", |
| __func__, cmd); |
| break; |
| } |
| cvbs_log_info("cvbs_ioctl..out.ret=0x%lx\n", ret); |
| return ret; |
| } |
| |
| #ifdef CONFIG_COMPAT |
| static long cvbs_compat_ioctl(struct file *file, unsigned int cmd, |
| unsigned long arg) |
| { |
| unsigned long ret; |
| |
| arg = (unsigned long)compat_ptr(arg); |
| ret = cvbs_ioctl(file, cmd, arg); |
| return ret; |
| } |
| #endif |
| |
| static const struct file_operations am_cvbs_fops = { |
| .open = cvbs_open, |
| .read = NULL,/* am_cvbs_read, */ |
| .write = NULL, |
| .unlocked_ioctl = cvbs_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = cvbs_compat_ioctl, |
| #endif |
| .release = cvbs_release, |
| .poll = NULL, |
| }; |
| |
| static const struct vinfo_s *get_valid_vinfo(char *mode) |
| { |
| struct vinfo_s *vinfo = NULL; |
| int i, count = ARRAY_SIZE(cvbs_info); |
| int mode_name_len = 0; |
| |
| /*cvbs_log_info("get_valid_vinfo..out.mode:%s\n", mode);*/ |
| for (i = 0; i < count; i++) { |
| if (strncmp(cvbs_info[i].name, mode, |
| strlen(cvbs_info[i].name)) == 0) { |
| if (strlen(cvbs_info[i].name) > mode_name_len) { |
| vinfo = &cvbs_info[i]; |
| mode_name_len = strlen(cvbs_info[i].name); |
| local_cvbs_mode = i; |
| break; |
| } |
| } |
| } |
| if (vinfo) |
| strncpy(vinfo->ext_name, mode, |
| (strlen(mode) < 32) ? strlen(mode) : 31); |
| else |
| local_cvbs_mode = MODE_MAX; |
| return vinfo; |
| } |
| |
| static struct vinfo_s *cvbs_get_current_info(void) |
| { |
| return info->vinfo; |
| } |
| |
| enum cvbs_mode_e get_local_cvbs_mode(void) |
| { |
| return local_cvbs_mode; |
| } |
| EXPORT_SYMBOL(get_local_cvbs_mode); |
| |
| static int cvbs_set_current_vmode(enum vmode_e mode) |
| { |
| enum vmode_e tvmode; |
| |
| tvmode = mode & VMODE_MODE_BIT_MASK; |
| if (tvmode != VMODE_CVBS) |
| return -EINVAL; |
| if (local_cvbs_mode == MODE_MAX) { |
| cvbs_log_info("local_cvbs_mode err:%d!\n", local_cvbs_mode); |
| return 1; |
| } |
| info->vinfo = &cvbs_info[local_cvbs_mode]; |
| |
| cvbs_log_info("mode is %d,sync_duration_den=%d,sync_duration_num=%d\n", |
| tvmode, info->vinfo->sync_duration_den, |
| info->vinfo->sync_duration_num); |
| |
| /*set limit range for enci*/ |
| amvecm_clip_range_limit(1); |
| if (mode & VMODE_INIT_BIT_MASK) { |
| cvbs_out_vpu_power_ctrl(1); |
| cvbs_out_clk_gate_ctrl(1); |
| cvbs_cntl_output(1); |
| cvbs_log_info("already display in uboot\n"); |
| return 0; |
| } |
| cvbs_out_setmode(); |
| |
| return 0; |
| } |
| |
| static enum vmode_e cvbs_validate_vmode(char *mode) |
| { |
| const struct vinfo_s *info = get_valid_vinfo(mode); |
| |
| if (info) |
| return VMODE_CVBS; |
| return VMODE_MAX; |
| } |
| static int cvbs_vmode_is_supported(enum vmode_e mode) |
| { |
| mode &= VMODE_MODE_BIT_MASK; |
| if (mode == VMODE_CVBS) |
| return true; |
| return false; |
| } |
| static int cvbs_module_disable(enum vmode_e cur_vmod) |
| { |
| info->dwork_flag = 0; |
| cvbs_cntl_output(0); |
| |
| /*restore full range for encp/encl*/ |
| amvecm_clip_range_limit(0); |
| |
| cvbs_out_vpu_power_ctrl(0); |
| cvbs_out_clk_gate_ctrl(0); |
| |
| return 0; |
| } |
| |
| static int cvbs_vout_state; |
| static int cvbs_vout_set_state(int index) |
| { |
| cvbs_vout_state |= (1 << index); |
| return 0; |
| } |
| |
| static int cvbs_vout_clr_state(int index) |
| { |
| cvbs_vout_state &= ~(1 << index); |
| return 0; |
| } |
| |
| static int cvbs_vout_get_state(void) |
| { |
| return cvbs_vout_state; |
| } |
| |
| static char *cvbs_out_bist_str[] = { |
| "OFF", /* 0 */ |
| "Color Bar", /* 1 */ |
| "Thin Line", /* 2 */ |
| "Dot Grid", /* 3 */ |
| "White", |
| "Red", |
| "Green", |
| "Blue", |
| "Black", |
| }; |
| |
| static void cvbs_bist_test(unsigned int bist) |
| { |
| switch (bist) { |
| case 1: |
| case 2: |
| case 3: |
| cvbs_out_reg_write(ENCI_TST_CLRBAR_STRT, 0x112); |
| cvbs_out_reg_write(ENCI_TST_CLRBAR_WIDTH, 0xb4); |
| cvbs_out_reg_write(ENCI_TST_MDSEL, bist); |
| cvbs_out_reg_write(ENCI_TST_Y, 0x200); |
| cvbs_out_reg_write(ENCI_TST_CB, 0x200); |
| cvbs_out_reg_write(ENCI_TST_CR, 0x200); |
| cvbs_out_reg_write(ENCI_TST_EN, 1); |
| pr_info("show bist pattern %d: %s\n", |
| bist, cvbs_out_bist_str[bist]); |
| break; |
| case 4: |
| cvbs_out_reg_write(ENCI_TST_CLRBAR_STRT, 0x112); |
| cvbs_out_reg_write(ENCI_TST_CLRBAR_WIDTH, 0xb4); |
| cvbs_out_reg_write(ENCI_TST_MDSEL, 0); |
| cvbs_out_reg_write(ENCI_TST_Y, 0x3ff); |
| cvbs_out_reg_write(ENCI_TST_CB, 0x200); |
| cvbs_out_reg_write(ENCI_TST_CR, 0x200); |
| cvbs_out_reg_write(ENCI_TST_EN, 1); |
| pr_info("show bist pattern %d: %s\n", |
| bist, cvbs_out_bist_str[bist]); |
| break; |
| case 5: |
| cvbs_out_reg_write(ENCI_TST_CLRBAR_STRT, 0x112); |
| cvbs_out_reg_write(ENCI_TST_CLRBAR_WIDTH, 0xb4); |
| cvbs_out_reg_write(ENCI_TST_MDSEL, 0); |
| cvbs_out_reg_write(ENCI_TST_Y, 0x200); |
| cvbs_out_reg_write(ENCI_TST_CB, 0x0); |
| cvbs_out_reg_write(ENCI_TST_CR, 0x3ff); |
| cvbs_out_reg_write(ENCI_TST_EN, 1); |
| pr_info("show bist pattern %d: %s\n", |
| bist, cvbs_out_bist_str[bist]); |
| break; |
| case 6: |
| cvbs_out_reg_write(ENCI_TST_CLRBAR_STRT, 0x112); |
| cvbs_out_reg_write(ENCI_TST_CLRBAR_WIDTH, 0xb4); |
| cvbs_out_reg_write(ENCI_TST_MDSEL, 0); |
| cvbs_out_reg_write(ENCI_TST_Y, 0x200); |
| cvbs_out_reg_write(ENCI_TST_CB, 0x0); |
| cvbs_out_reg_write(ENCI_TST_CR, 0x0); |
| cvbs_out_reg_write(ENCI_TST_EN, 1); |
| pr_info("show bist pattern %d: %s\n", |
| bist, cvbs_out_bist_str[bist]); |
| break; |
| case 7: |
| cvbs_out_reg_write(ENCI_TST_CLRBAR_STRT, 0x112); |
| cvbs_out_reg_write(ENCI_TST_CLRBAR_WIDTH, 0xb4); |
| cvbs_out_reg_write(ENCI_TST_MDSEL, 0); |
| cvbs_out_reg_write(ENCI_TST_Y, 0x200); |
| cvbs_out_reg_write(ENCI_TST_CB, 0x3ff); |
| cvbs_out_reg_write(ENCI_TST_CR, 0x0); |
| cvbs_out_reg_write(ENCI_TST_EN, 1); |
| pr_info("show bist pattern %d: %s\n", |
| bist, cvbs_out_bist_str[bist]); |
| break; |
| case 8: |
| cvbs_out_reg_write(ENCI_TST_CLRBAR_STRT, 0x112); |
| cvbs_out_reg_write(ENCI_TST_CLRBAR_WIDTH, 0xb4); |
| cvbs_out_reg_write(ENCI_TST_MDSEL, 0); |
| cvbs_out_reg_write(ENCI_TST_Y, 0x0); |
| cvbs_out_reg_write(ENCI_TST_CB, 0x200); |
| cvbs_out_reg_write(ENCI_TST_CR, 0x200); |
| cvbs_out_reg_write(ENCI_TST_EN, 1); |
| pr_info("show bist pattern %d: %s\n", |
| bist, cvbs_out_bist_str[bist]); |
| break; |
| case 0: |
| default: |
| cvbs_out_reg_write(ENCI_TST_MDSEL, 1); |
| cvbs_out_reg_write(ENCI_TST_Y, 0x200); |
| cvbs_out_reg_write(ENCI_TST_CB, 0x200); |
| cvbs_out_reg_write(ENCI_TST_CR, 0x200); |
| cvbs_out_reg_write(ENCI_TST_EN, 0); |
| pr_info("show bist pattern %d: %s\n", |
| bist, cvbs_out_bist_str[0]); |
| break; |
| } |
| } |
| |
| #ifdef CONFIG_PM |
| static int cvbs_suspend(void) |
| { |
| /* TODO */ |
| /* video_dac_disable(); */ |
| info->dwork_flag = 0; |
| cvbs_cntl_output(0); |
| return 0; |
| } |
| |
| static int cvbs_resume(void) |
| { |
| /* TODO */ |
| /* video_dac_enable(0xff); */ |
| cvbs_set_current_vmode(info->vinfo->mode); |
| return 0; |
| } |
| #endif |
| |
| static struct vout_server_s cvbs_vout_server = { |
| .name = "cvbs_vout_server", |
| .op = { |
| .get_vinfo = cvbs_get_current_info, |
| .set_vmode = cvbs_set_current_vmode, |
| .validate_vmode = cvbs_validate_vmode, |
| .vmode_is_supported = cvbs_vmode_is_supported, |
| .disable = cvbs_module_disable, |
| .set_state = cvbs_vout_set_state, |
| .clr_state = cvbs_vout_clr_state, |
| .get_state = cvbs_vout_get_state, |
| .set_vframe_rate_hint = NULL, |
| .set_vframe_rate_end_hint = NULL, |
| .set_vframe_rate_policy = NULL, |
| .get_vframe_rate_policy = NULL, |
| .set_bist = cvbs_bist_test, |
| #ifdef CONFIG_PM |
| .vout_suspend = cvbs_suspend, |
| .vout_resume = cvbs_resume, |
| #endif |
| }, |
| }; |
| |
| #ifdef CONFIG_AMLOGIC_VOUT2_SERVE |
| static struct vout_server_s cvbs_vout2_server = { |
| .name = "cvbs_vout2_server", |
| .op = { |
| .get_vinfo = cvbs_get_current_info, |
| .set_vmode = cvbs_set_current_vmode, |
| .validate_vmode = cvbs_validate_vmode, |
| .vmode_is_supported = cvbs_vmode_is_supported, |
| .disable = cvbs_module_disable, |
| .set_state = cvbs_vout_set_state, |
| .clr_state = cvbs_vout_clr_state, |
| .get_state = cvbs_vout_get_state, |
| .set_vframe_rate_hint = NULL, |
| .set_vframe_rate_end_hint = NULL, |
| .set_vframe_rate_policy = NULL, |
| .get_vframe_rate_policy = NULL, |
| .set_bist = cvbs_bist_test, |
| #ifdef CONFIG_PM |
| .vout_suspend = cvbs_suspend, |
| .vout_resume = cvbs_resume, |
| #endif |
| }, |
| }; |
| #endif |
| |
| static void cvbs_init_vout(void) |
| { |
| if (info->vinfo == NULL) |
| info->vinfo = &cvbs_info[MODE_480CVBS]; |
| |
| if (vout_register_server(&cvbs_vout_server)) |
| cvbs_log_err("register cvbs module server fail\n"); |
| else |
| cvbs_log_info("register cvbs module server ok\n"); |
| #ifdef CONFIG_AMLOGIC_VOUT2_SERVE |
| if (vout2_register_server(&cvbs_vout2_server)) |
| cvbs_log_err("register cvbs module vout2 server fail\n"); |
| else |
| cvbs_log_info("register cvbs module vout2 server ok\n"); |
| #endif |
| } |
| |
| static void vdac_power_level_store(char *para) |
| { |
| unsigned long level = 0; |
| int ret = 0; |
| |
| ret = kstrtoul(para, 10, (unsigned long *)&level); |
| cvbs_vdac_power_level = level; |
| } |
| |
| static void dump_clk_registers(void) |
| { |
| unsigned int clk_regs[] = { |
| /* hiu 10c8 ~ 10cd */ |
| HHI_HDMI_PLL_CNTL, |
| HHI_HDMI_PLL_CNTL2, |
| HHI_HDMI_PLL_CNTL3, |
| HHI_HDMI_PLL_CNTL4, |
| HHI_HDMI_PLL_CNTL5, |
| HHI_HDMI_PLL_CNTL6, |
| |
| /* hiu 1068 */ |
| HHI_VID_PLL_CLK_DIV, |
| |
| /* hiu 104a, 104b*/ |
| HHI_VIID_CLK_DIV, |
| HHI_VIID_CLK_CNTL, |
| |
| /* hiu 1059, 105f */ |
| HHI_VID_CLK_DIV, |
| HHI_VID_CLK_CNTL, |
| }; |
| unsigned int i, max; |
| |
| max = sizeof(clk_regs)/sizeof(unsigned int); |
| pr_info("\n total %d registers of clock path for hdmi pll:\n", max); |
| for (i = 0; i < max; i++) { |
| pr_info("hiu [0x%x] = 0x%x\n", clk_regs[i], |
| cvbs_out_hiu_read(clk_regs[i])); |
| } |
| } |
| |
| static void cvbs_performance_regs_dump(void) |
| { |
| unsigned int performance_regs_enci[] = { |
| VENC_VDAC_DAC0_GAINCTRL, |
| ENCI_SYNC_ADJ, |
| ENCI_VIDEO_BRIGHT, |
| ENCI_VIDEO_CONT, |
| ENCI_VIDEO_SAT, |
| ENCI_VIDEO_HUE, |
| ENCI_YC_DELAY, |
| VENC_VDAC_DAC0_FILT_CTRL0, |
| VENC_VDAC_DAC0_FILT_CTRL1 |
| }; |
| unsigned int performance_regs_vdac[] = { |
| HHI_VDAC_CNTL0, |
| HHI_VDAC_CNTL1 |
| }; |
| unsigned int performance_regs_vdac_g12a[] = { |
| HHI_VDAC_CNTL0_G12A, |
| HHI_VDAC_CNTL1_G12A |
| }; |
| int i, size; |
| |
| size = sizeof(performance_regs_enci)/sizeof(unsigned int); |
| pr_info("------------------------\n"); |
| for (i = 0; i < size; i++) { |
| pr_info("vcbus [0x%x] = 0x%x\n", performance_regs_enci[i], |
| cvbs_out_reg_read(performance_regs_enci[i])); |
| } |
| if (cvbs_cpu_type() >= CVBS_CPU_TYPE_G12A) |
| size = sizeof(performance_regs_vdac_g12a)/sizeof(unsigned int); |
| else |
| size = sizeof(performance_regs_vdac)/sizeof(unsigned int); |
| pr_info("------------------------\n"); |
| for (i = 0; i < size; i++) { |
| if (cvbs_cpu_type() >= CVBS_CPU_TYPE_G12A) |
| pr_info("hiu [0x%x] = 0x%x\n", |
| performance_regs_vdac_g12a[i], |
| cvbs_out_hiu_read(performance_regs_vdac_g12a[i])); |
| else |
| pr_info("hiu [0x%x] = 0x%x\n", performance_regs_vdac[i], |
| cvbs_out_hiu_read(performance_regs_vdac[i])); |
| } |
| pr_info("------------------------\n"); |
| } |
| |
| static void cvbs_performance_config_dump(void) |
| { |
| const struct reg_s *s = NULL; |
| int i = 0; |
| |
| s = info->cvbs_conf.performance_reg_table; |
| if (s == NULL) { |
| pr_info("can't find performance table!\n"); |
| return; |
| } |
| |
| pr_info("------------------------\n"); |
| while (i < info->cvbs_conf.performance_reg_cnt) { |
| pr_info("0x%04x = 0x%x\n", s->reg, s->val); |
| s++; |
| i++; |
| } |
| pr_info("------------------------\n"); |
| } |
| |
| enum { |
| CMD_REG_READ, |
| CMD_REG_READ_BITS, |
| CMD_REG_DUMP, |
| CMD_REG_WRITE, |
| CMD_REG_WRITE_BITS, |
| |
| CMD_CLK_DUMP, |
| CMD_CLK_MSR, |
| |
| CMD_BIST, |
| |
| /* config a set of performance parameters: |
| *0 for sarft, |
| *1 for telecom, |
| *2 for chinamobile |
| */ |
| CMD_VP_SET, |
| |
| /* get the current perfomance config */ |
| CMD_VP_GET, |
| |
| /* dump the perfomance config in dts */ |
| CMD_VP_CONFIG_DUMP, |
| /*set pll path: 3:vid pll 2:gp0 pll path2*/ |
| /*1:gp0 pll path1*/ |
| CMD_VP_SET_PLLPATH, |
| |
| CMD_HELP, |
| |
| CMD_MAX |
| } debug_cmd_t; |
| |
| #define func_type_map(a) {\ |
| if (!strcmp(a, "h")) {\ |
| str_type = "hiu";\ |
| func_read = cvbs_out_hiu_read;\ |
| func_write = cvbs_out_hiu_write;\ |
| func_getb = cvbs_out_hiu_getb;\ |
| func_setb = cvbs_out_hiu_setb;\ |
| } \ |
| else if (!strcmp(a, "v")) {\ |
| str_type = "vcbus";\ |
| func_read = cvbs_out_reg_read;\ |
| func_write = cvbs_out_reg_write;\ |
| func_getb = cvbs_out_reg_getb;\ |
| func_setb = cvbs_out_reg_setb;\ |
| } \ |
| } |
| |
| static void cvbs_debug_store(char *buf) |
| { |
| unsigned int ret = 0; |
| unsigned long addr, start, end, value, length, old; |
| unsigned int argc, bist; |
| char *p = NULL, *para = NULL, |
| *argv[6] = {NULL, NULL, NULL, NULL, NULL, NULL}; |
| char *str_type = NULL; |
| unsigned int i, cmd; |
| unsigned int (*func_read)(unsigned int) = NULL; |
| void (*func_write)(unsigned int, unsigned int) = NULL; |
| unsigned int (*func_getb)(unsigned int, |
| unsigned int, unsigned int) = NULL; |
| void (*func_setb)(unsigned int, unsigned int, |
| unsigned int, unsigned int) = NULL; |
| |
| p = kstrdup(buf, GFP_KERNEL); |
| for (argc = 0; argc < 6; argc++) { |
| para = strsep(&p, " "); |
| if (para == NULL) |
| break; |
| argv[argc] = para; |
| } |
| |
| if (!strcmp(argv[0], "r")) |
| cmd = CMD_REG_READ; |
| else if (!strcmp(argv[0], "rb")) |
| cmd = CMD_REG_READ_BITS; |
| else if (!strcmp(argv[0], "dump")) |
| cmd = CMD_REG_DUMP; |
| else if (!strcmp(argv[0], "w")) |
| cmd = CMD_REG_WRITE; |
| else if (!strcmp(argv[0], "wb")) |
| cmd = CMD_REG_WRITE_BITS; |
| else if (!strncmp(argv[0], "clkdump", strlen("clkdump"))) |
| cmd = CMD_CLK_DUMP; |
| else if (!strncmp(argv[0], "clkmsr", strlen("clkmsr"))) |
| cmd = CMD_CLK_MSR; |
| else if (!strncmp(argv[0], "bist", strlen("bist"))) |
| cmd = CMD_BIST; |
| else if (!strncmp(argv[0], "vpset", strlen("vpset"))) |
| cmd = CMD_VP_SET; |
| else if (!strncmp(argv[0], "vpget", strlen("vpget"))) |
| cmd = CMD_VP_GET; |
| else if (!strncmp(argv[0], "vpconf", strlen("vpconf"))) |
| cmd = CMD_VP_CONFIG_DUMP; |
| else if (!strncmp(argv[0], "set_clkpath", strlen("set_clkpath"))) |
| cmd = CMD_VP_SET_PLLPATH; |
| else if (!strncmp(argv[0], "help", strlen("help"))) |
| cmd = CMD_HELP; |
| else if (!strncmp(argv[0], "cvbs_ver", strlen("cvbs_ver"))) { |
| print_info("cvbsout version : %s\n", CVBSOUT_VER); |
| goto DEBUG_END; |
| } else { |
| print_info("[%s] invalid cmd = %s!\n", __func__, argv[0]); |
| goto DEBUG_END; |
| } |
| |
| switch (cmd) { |
| case CMD_REG_READ: |
| if (argc != 3) { |
| print_info("[%s] cmd_reg_read format: r c/h/v address_hex\n", |
| __func__); |
| goto DEBUG_END; |
| } |
| |
| func_type_map(argv[1]); |
| if (func_read == NULL) |
| goto DEBUG_END; |
| ret = kstrtoul(argv[2], 16, &addr); |
| |
| print_info("read %s[0x%x] = 0x%x\n", |
| str_type, (unsigned int)addr, func_read(addr)); |
| |
| break; |
| |
| case CMD_REG_READ_BITS: |
| if (argc != 5) { |
| print_info("[%s] cmd_reg_read_bits format:\n" |
| "\trb c/h/v address_hex start_dec length_dec\n", |
| __func__); |
| goto DEBUG_END; |
| } |
| |
| func_type_map(argv[1]); |
| if (func_read == NULL) |
| goto DEBUG_END; |
| ret = kstrtoul(argv[2], 16, &addr); |
| ret = kstrtoul(argv[3], 10, &start); |
| ret = kstrtoul(argv[4], 10, &length); |
| |
| if (length == 1) |
| print_info("read_bits %s[0x%x] = 0x%x, bit[%d] = 0x%x\n", |
| str_type, (unsigned int)addr, |
| func_read(addr), (unsigned int)start, |
| func_getb(addr, start, length)); |
| else |
| print_info("read_bits %s[0x%x] = 0x%x, bit[%d-%d] = 0x%x\n", |
| str_type, (unsigned int)addr, |
| func_read(addr), |
| (unsigned int)start+(unsigned int)length-1, |
| (unsigned int)start, |
| func_getb(addr, start, length)); |
| break; |
| |
| case CMD_REG_DUMP: |
| if (argc != 4) { |
| print_info("[%s] cmd_reg_dump format: dump c/h/v start_dec end_dec\n", |
| __func__); |
| goto DEBUG_END; |
| } |
| |
| func_type_map(argv[1]); |
| if (func_read == NULL) |
| goto DEBUG_END; |
| ret = kstrtoul(argv[2], 16, &start); |
| ret = kstrtoul(argv[3], 16, &end); |
| |
| for (i = start; i <= end; i++) |
| print_info("%s[0x%x] = 0x%x\n", |
| str_type, i, func_read(i)); |
| |
| break; |
| |
| case CMD_REG_WRITE: |
| if (argc != 4) { |
| print_info("[%s] cmd_reg_write format: w value_hex c/h/v address_hex\n", |
| __func__); |
| goto DEBUG_END; |
| } |
| |
| func_type_map(argv[2]); |
| if (func_write == NULL) |
| goto DEBUG_END; |
| ret = kstrtoul(argv[1], 16, &value); |
| ret = kstrtoul(argv[3], 16, &addr); |
| |
| func_write(addr, value); |
| print_info("write %s[0x%x] = 0x%x\n", str_type, |
| (unsigned int)addr, (unsigned int)value); |
| break; |
| |
| case CMD_REG_WRITE_BITS: |
| if (argc != 6) { |
| print_info("[%s] cmd_reg_wrute_bits format:\n" |
| "\twb value_hex c/h/v address_hex start_dec length_dec\n", |
| __func__); |
| goto DEBUG_END; |
| } |
| |
| func_type_map(argv[2]); |
| if (func_read == NULL) |
| goto DEBUG_END; |
| ret = kstrtoul(argv[1], 16, &value); |
| ret = kstrtoul(argv[3], 16, &addr); |
| ret = kstrtoul(argv[4], 10, &start); |
| ret = kstrtoul(argv[5], 10, &length); |
| |
| old = func_read(addr); |
| func_setb(addr, value, start, length); |
| print_info("write_bits %s[0x%x] old = 0x%x, new = 0x%x\n", |
| str_type, (unsigned int)addr, |
| (unsigned int)old, func_read(addr)); |
| break; |
| |
| case CMD_CLK_DUMP: |
| dump_clk_registers(); |
| break; |
| |
| case CMD_CLK_MSR: |
| /* todo */ |
| print_info("cvbs: debug_store: clk_msr todo!\n"); |
| break; |
| |
| case CMD_BIST: |
| if (argc != 2) { |
| print_info("[%s] cmd_bist format:\n" |
| "\tbist 1/2/3/4/5/6/7/8/0\n", __func__); |
| goto DEBUG_END; |
| } |
| ret = kstrtouint(argv[1], 10, &bist); |
| if (ret) { |
| print_info("cvbs: invalid bist\n"); |
| goto DEBUG_END; |
| } |
| cvbs_bist_test(bist); |
| |
| break; |
| |
| case CMD_VP_SET: |
| if (argc != 2) { |
| print_info("[%s] cmd_vp_set format:\n" |
| "\tvpset 0/1/2\n", __func__); |
| goto DEBUG_END; |
| } |
| if (info->vinfo->mode != VMODE_CVBS) { |
| print_info("NOT VMODE_CVBS,Return\n"); |
| return; |
| } |
| ret = kstrtoul(argv[1], 16, &value); |
| cvbs_performance_index = (value > 2) ? 0 : value; |
| cvbs_performance_enhancement(local_cvbs_mode); |
| cvbs_performance_regs_dump(); |
| break; |
| |
| case CMD_VP_GET: |
| print_info("current performance index: %d\n", |
| cvbs_performance_index); |
| cvbs_performance_regs_dump(); |
| break; |
| |
| case CMD_VP_CONFIG_DUMP: |
| print_info("performance config in dts:\n"); |
| cvbs_performance_config_dump(); |
| break; |
| case CMD_VP_SET_PLLPATH: |
| if (cvbs_cpu_type() < CVBS_CPU_TYPE_G12A) { |
| print_info("ERR:Only after g12a/b chip supported\n"); |
| break; |
| } |
| if (argc != 2) { |
| print_info("[%s] set_clkpath 0/1/2/3\n", |
| __func__); |
| goto DEBUG_END; |
| } |
| ret = kstrtoul(argv[1], 10, &value); |
| if (value == 1 || value == 2 || |
| value == 3 || value == 0) { |
| cvbs_clk_path = value; |
| print_info("path 0:vid_pll vid2_clk\n"); |
| print_info("path 1:gp0_pll vid2_clk\n"); |
| print_info("path 2:vid_pll vid1_clk\n"); |
| print_info("path 3:gp0_pll vid1_clk\n"); |
| print_info("you select path %d\n", cvbs_clk_path); |
| } else { |
| print_info("invalid value, only 0/1/2/3\n"); |
| print_info("bit[0]: 0=vid_pll, 1=gp0_pll\n"); |
| print_info("bit[1]: 0=vid2_clk, 1=vid1_clk\n"); |
| } |
| |
| break; |
| case CMD_HELP: |
| print_info("command format:\n" |
| "\tr c/h/v address_hex\n" |
| "\trb c/h/v address_hex start_dec length_dec\n" |
| "\tdump c/h/v start_dec end_dec\n" |
| "\tw value_hex c/h/v address_hex\n" |
| "\twb value_hex c/h/v address_hex start_dec length_dec\n" |
| "\tbist 0/1/2/3/off\n" |
| "\tclkdump\n" |
| "\tset_clkpath 0/1/2/3\n" |
| "\tcvbs_ver\n"); |
| break; |
| } |
| |
| DEBUG_END: |
| kfree(p); |
| } |
| |
| static struct class_attribute *cvbs_attr[] = { |
| &class_CVBS_attr_vdac_power_level, |
| &class_CVBS_attr_debug, |
| #ifdef CONFIG_AMLOGIC_WSS |
| &class_CVBS_attr_wss, |
| #endif |
| }; |
| |
| static int create_cvbs_attr(struct disp_module_info_s *info) |
| { |
| /* create base class for display */ |
| int i; |
| int ret = 0; |
| |
| info->base_class = class_create(THIS_MODULE, CVBS_CLASS_NAME); |
| if (IS_ERR(info->base_class)) { |
| ret = PTR_ERR(info->base_class); |
| goto fail_create_class; |
| } |
| /* create class attr */ |
| for (i = 0; i < ARRAY_SIZE(cvbs_attr); i++) { |
| if (class_create_file(info->base_class, cvbs_attr[i]) < 0) |
| goto fail_class_create_file; |
| } |
| /*cdev_init(info->cdev, &am_cvbs_fops);*/ |
| info->cdev = cdev_alloc(); |
| info->cdev->ops = &am_cvbs_fops; |
| info->cdev->owner = THIS_MODULE; |
| ret = cdev_add(info->cdev, info->devno, 1); |
| if (ret) |
| goto fail_add_cdev; |
| info->dev = device_create(info->base_class, NULL, info->devno, |
| NULL, CVBS_NAME); |
| if (IS_ERR(info->dev)) { |
| ret = PTR_ERR(info->dev); |
| goto fail_create_device; |
| } else { |
| cvbs_log_info("create cdev %s\n", CVBS_NAME); |
| } |
| return 0; |
| |
| fail_create_device: |
| cvbs_log_info("[cvbs.] : cvbs device create error.\n"); |
| cdev_del(info->cdev); |
| fail_add_cdev: |
| cvbs_log_info("[cvbs.] : cvbs add device error.\n"); |
| fail_class_create_file: |
| cvbs_log_info("[cvbs.] : cvbs class create file error.\n"); |
| for (i = 0; i < ARRAY_SIZE(cvbs_attr); i++) |
| class_remove_file(info->base_class, cvbs_attr[i]); |
| class_destroy(info->base_class); |
| fail_create_class: |
| cvbs_log_info("[cvbs.] : cvbs class create error.\n"); |
| unregister_chrdev_region(info->devno, 1); |
| return ret; |
| } |
| /* **************************************************** */ |
| |
| static void cvbsout_get_config(struct device *dev) |
| { |
| int ret = 0; |
| unsigned int val, cnt, i, j; |
| struct reg_s *s = NULL; |
| |
| /* performance */ |
| info->cvbs_conf.performance_reg_cnt = 0; |
| info->cvbs_conf.performance_reg_table = NULL; |
| cnt = 0; |
| while (cnt < CVBS_PERFORMANCE_CNT_MAX) { |
| j = 2 * cnt; |
| ret = of_property_read_u32_index(dev->of_node, "performance", |
| j, &val); |
| if (ret) { |
| cvbs_log_err("error: failed to get performance\n"); |
| cnt = 0; |
| break; |
| } |
| if (val == MREG_END_MARKER) /* ending */ |
| break; |
| cnt++; |
| } |
| |
| if (cnt > 0) { |
| info->cvbs_conf.performance_reg_table = |
| kzalloc(sizeof(struct reg_s) * cnt, GFP_KERNEL); |
| if (info->cvbs_conf.performance_reg_table == NULL) { |
| cvbs_log_err( |
| "error: failed to alloc performance table\n"); |
| cnt = 0; |
| } |
| info->cvbs_conf.performance_reg_cnt = cnt; |
| |
| i = 0; |
| s = info->cvbs_conf.performance_reg_table; |
| while (i < info->cvbs_conf.performance_reg_cnt) { |
| j = 2 * i; |
| ret = of_property_read_u32_index(dev->of_node, |
| "performance", j, &val); |
| s->reg = val; |
| j = 2 * i + 1; |
| ret = of_property_read_u32_index(dev->of_node, |
| "performance", j, &val); |
| s->val = val; |
| /* pr_info("%p: 0x%04x = 0x%x\n", s, s->reg, s->val); */ |
| |
| s++; |
| i++; |
| } |
| } |
| |
| /*clk path*/ |
| /*0:vid_pll vid2_clk*/ |
| /*1:gp0_pll vid2_clk*/ |
| /*2:vid_pll vid1_clk*/ |
| /*3:gp0_pll vid1_clk*/ |
| ret = of_property_read_u32(dev->of_node, "clk_path", &val); |
| if (ret) |
| cvbs_log_info("clk_path config null\n"); |
| else if (val > 3) |
| cvbs_log_err("error: invalid clk_path\n"); |
| else { |
| cvbs_clk_path = val; |
| cvbs_log_info("clk path:%d\n", cvbs_clk_path); |
| } |
| |
| /* vdac config */ |
| ret = of_property_read_u32(dev->of_node, "vdac_config", &val); |
| if (ret) |
| cvbs_log_err("error: failed to get vdac_config\n"); |
| else |
| cvbs_config_vdac((val & 0xff00) >> 8, val & 0xff); |
| |
| } |
| |
| static void cvbsout_clktree_probe(struct device *dev) |
| { |
| info->clk_gate_state = 0; |
| info->venci_top_gate = devm_clk_get(dev, "venci_top_gate"); |
| if (IS_ERR(info->venci_top_gate)) |
| cvbs_log_err("error: %s: clk venci_top_gate\n", __func__); |
| |
| info->venci_0_gate = devm_clk_get(dev, "venci_0_gate"); |
| if (IS_ERR(info->venci_0_gate)) |
| cvbs_log_err("error: %s: clk venci_0_gate\n", __func__); |
| |
| info->venci_1_gate = devm_clk_get(dev, "venci_1_gate"); |
| if (IS_ERR(info->venci_1_gate)) |
| cvbs_log_err("error: %s: clk venci_1_gate\n", __func__); |
| |
| info->vdac_clk_gate = devm_clk_get(dev, "vdac_clk_gate"); |
| if (IS_ERR(info->vdac_clk_gate)) |
| cvbs_log_err("error: %s: clk vdac_clk_gate\n", __func__); |
| } |
| |
| static void cvbsout_clktree_remove(struct device *dev) |
| { |
| if (!IS_ERR(info->venci_top_gate)) |
| devm_clk_put(dev, info->venci_top_gate); |
| if (!IS_ERR(info->venci_0_gate)) |
| devm_clk_put(dev, info->venci_0_gate); |
| if (!IS_ERR(info->venci_1_gate)) |
| devm_clk_put(dev, info->venci_1_gate); |
| if (!IS_ERR(info->vdac_clk_gate)) |
| devm_clk_put(dev, info->vdac_clk_gate); |
| } |
| |
| #ifdef CONFIG_OF |
| struct meson_cvbsout_data meson_gxl_cvbsout_data = { |
| .cntl0_val = 0xb0001, |
| .cpu_id = CVBS_CPU_TYPE_GXL, |
| .name = "meson-gxl-cvbsout", |
| }; |
| |
| struct meson_cvbsout_data meson_gxm_cvbsout_data = { |
| .cntl0_val = 0xb0001, |
| .cpu_id = CVBS_CPU_TYPE_GXM, |
| .name = "meson-gxm-cvbsout", |
| }; |
| |
| struct meson_cvbsout_data meson_txlx_cvbsout_data = { |
| .cntl0_val = 0x620001, |
| .cpu_id = CVBS_CPU_TYPE_TXLX, |
| .name = "meson-txlx-cvbsout", |
| }; |
| |
| struct meson_cvbsout_data meson_g12a_cvbsout_data = { |
| .cntl0_val = 0x906001, |
| .cpu_id = CVBS_CPU_TYPE_G12A, |
| .name = "meson-g12a-cvbsout", |
| }; |
| |
| struct meson_cvbsout_data meson_g12b_cvbsout_data = { |
| .cntl0_val = 0x8f6001, |
| .cpu_id = CVBS_CPU_TYPE_G12B, |
| .name = "meson-g12b-cvbsout", |
| }; |
| |
| struct meson_cvbsout_data meson_tl1_cvbsout_data = { |
| .cntl0_val = 0x906001, |
| .cpu_id = CVBS_CPU_TYPE_TL1, |
| .name = "meson-tl1-cvbsout", |
| }; |
| |
| struct meson_cvbsout_data meson_sm1_cvbsout_data = { |
| .cntl0_val = 0x8f6001, |
| .cpu_id = CVBS_CPU_TYPE_SM1, |
| .name = "meson-sm1-cvbsout", |
| }; |
| |
| static const struct of_device_id meson_cvbsout_dt_match[] = { |
| { |
| .compatible = "amlogic, cvbsout-gxl", |
| .data = &meson_gxl_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-gxm", |
| .data = &meson_gxm_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-txlx", |
| .data = &meson_txlx_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-g12a", |
| .data = &meson_g12a_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-g12b", |
| .data = &meson_g12b_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-tl1", |
| .data = &meson_tl1_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-sm1", |
| .data = &meson_sm1_cvbsout_data, |
| }, |
| {}, |
| }; |
| #endif |
| |
| static int cvbsout_probe(struct platform_device *pdev) |
| { |
| int ret; |
| const struct of_device_id *match; |
| |
| cvbs_clk_path = 0; |
| |
| local_cvbs_mode = MODE_MAX; |
| info = &disp_module_info; |
| match = of_match_device(meson_cvbsout_dt_match, &pdev->dev); |
| if (match == NULL) { |
| cvbs_log_err("%s,no matched table\n", __func__); |
| return -1; |
| } |
| info->cvbs_data = (struct meson_cvbsout_data *)match->data; |
| cvbs_log_info("%s, cpu_id:%d,name:%s\n", __func__, |
| info->cvbs_data->cpu_id, info->cvbs_data->name); |
| |
| cvbsout_get_config(&pdev->dev); |
| cvbsout_clktree_probe(&pdev->dev); |
| cvbs_init_vout(); |
| |
| ret = alloc_chrdev_region(&info->devno, 0, 1, CVBS_NAME); |
| if (ret < 0) { |
| cvbs_log_err("alloc_chrdev_region error\n"); |
| return ret; |
| } |
| cvbs_log_err("chrdev devno %d for disp\n", info->devno); |
| ret = create_cvbs_attr(info); |
| if (ret < 0) { |
| cvbs_log_err("create_cvbs_attr error\n"); |
| return -1; |
| } |
| INIT_DELAYED_WORK(&info->dv_dwork, cvbs_dv_dwork); |
| cvbs_log_info("%s OK\n", __func__); |
| return 0; |
| } |
| |
| static int cvbsout_remove(struct platform_device *pdev) |
| { |
| int i; |
| |
| cvbsout_clktree_remove(&pdev->dev); |
| |
| if (info->base_class) { |
| for (i = 0; i < ARRAY_SIZE(cvbs_attr); i++) |
| class_remove_file(info->base_class, cvbs_attr[i]); |
| class_destroy(info->base_class); |
| } |
| if (info) { |
| cdev_del(info->cdev); |
| kfree(info); |
| } |
| vout_unregister_server(&cvbs_vout_server); |
| #ifdef CONFIG_AMLOGIC_VOUT2_SERVE |
| vout2_unregister_server(&cvbs_vout2_server); |
| #endif |
| cvbs_log_info("%s\n", __func__); |
| return 0; |
| } |
| |
| static void cvbsout_shutdown(struct platform_device *pdev) |
| { |
| info->dwork_flag = 0; |
| cvbs_out_reg_write(ENCI_VIDEO_EN, 0); |
| cvbs_out_disable_clk(); |
| |
| cvbs_out_vpu_power_ctrl(0); |
| cvbs_out_clk_gate_ctrl(0); |
| } |
| |
| static struct platform_driver cvbsout_driver = { |
| .probe = cvbsout_probe, |
| .remove = cvbsout_remove, |
| .shutdown = cvbsout_shutdown, |
| .driver = { |
| .name = "cvbsout", |
| .owner = THIS_MODULE, |
| #ifdef CONFIG_OF |
| .of_match_table = meson_cvbsout_dt_match, |
| #endif |
| }, |
| }; |
| |
| static int __init cvbs_init_module(void) |
| { |
| /* cvbs_log_info("%s module init\n", __func__); */ |
| if (platform_driver_register(&cvbsout_driver)) { |
| cvbs_log_err("%s failed to register module\n", __func__); |
| return -ENODEV; |
| } |
| return 0; |
| } |
| |
| static __exit void cvbs_exit_module(void) |
| { |
| /* cvbs_log_info("%s module exit\n", __func__); */ |
| platform_driver_unregister(&cvbsout_driver); |
| } |
| |
| static int __init vdac_config_bootargs_setup(char *line) |
| { |
| unsigned long cfg = 0x0; |
| int ret = 0; |
| |
| cvbs_log_info("cvbs trimming line = %s\n", line); |
| ret = kstrtoul(line, 16, (unsigned long *)&cfg); |
| cvbs_config_vdac((cfg & 0xff00) >> 8, cfg & 0xff); |
| return 1; |
| } |
| |
| __setup("vdaccfg=", vdac_config_bootargs_setup); |
| |
| static int __init cvbs_performance_setup(char *line) |
| { |
| unsigned long cfg = 0x0; |
| int ret = 0; |
| |
| cvbs_log_info("cvbs performance line = %s\n", line); |
| ret = kstrtoul(line, 10, (unsigned long *)&cfg); |
| cvbs_performance_config(cfg); |
| return 0; |
| } |
| __setup("cvbsdrv=", cvbs_performance_setup); |
| |
| arch_initcall(cvbs_init_module); |
| module_exit(cvbs_exit_module); |
| |
| MODULE_AUTHOR("Platform-BJ <platform.bj@amlogic.com>"); |
| MODULE_DESCRIPTION("TV Output Module"); |
| MODULE_LICENSE("GPL"); |