| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * 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> |
| #include <linux/compat.h> |
| |
| /* Amlogic Headers */ |
| #include <linux/amlogic/media/registers/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 |
| #include <linux/amlogic/gki_module.h> |
| |
| #ifdef CONFIG_AML_VOUT_CC_BYPASS |
| /* interrupt source */ |
| #define INT_VIU_VSYNC 35 |
| #endif |
| |
| const char *cvbs_mode_t[] = { |
| "480cvbs", |
| "576cvbs", |
| "pal_m", |
| "pal_n", |
| "ntsc_m", |
| NULL |
| }; |
| |
| static struct vinfo_s cvbs_info[] = { |
| { /* MODE_480CVBS*/ |
| .name = "480cvbs", |
| .mode = VMODE_CVBS, |
| .frac = 0, |
| .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, |
| .frac = 0, |
| .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, |
| .frac = 0, |
| .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, |
| .frac = 0, |
| .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, |
| }, |
| { /* MODE_NTSC_M */ |
| .name = "ntsc_m", |
| .mode = VMODE_CVBS, |
| .frac = 0, |
| .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, |
| }, |
| }; |
| |
| #ifdef CONFIG_AML_VOUT_CC_BYPASS |
| static struct cc_ring_mgr_s cc_ringbuf; |
| static spinlock_t tvout_clk_lock; |
| static unsigned int vsync_empty_flag; |
| static unsigned int vsync_empty_flag_evn; |
| static unsigned int vsync_empty_flag_odd; |
| #endif |
| |
| /*bit[0]: 0=vid_pll, 1=gp0_pll*/ |
| /*bit[1]: 0=vid2_clk, 1=vid1_clk*/ |
| unsigned int cvbs_clk_path; |
| |
| static struct cvbs_drv_s *cvbs_drv; |
| static enum cvbs_mode_e local_cvbs_mode; |
| static DEFINE_MUTEX(setmode_mutex); |
| |
| static int cvbs_vdac_power_level; |
| static ssize_t aml_CVBS_attr_vdac_power_show(struct class *class, |
| struct class_attribute *attr, |
| char *buf); |
| static ssize_t aml_CVBS_attr_vdac_power_store(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t count); |
| struct class_attribute class_CVBS_attr_vdac_power_level = |
| __ATTR(vdac_power_level, 0644, aml_CVBS_attr_vdac_power_show, |
| aml_CVBS_attr_vdac_power_store); |
| |
| static ssize_t aml_CVBS_attr_debug_show(struct class *class, |
| struct class_attribute *attr, |
| char *buf); |
| static ssize_t aml_CVBS_attr_debug_store(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t count); |
| struct class_attribute class_CVBS_attr_debug = |
| __ATTR(debug, 0644, aml_CVBS_attr_debug_show, |
| aml_CVBS_attr_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_bist_test(unsigned int bist, void *data); |
| |
| /* static int get_cpu_minor(void) |
| * { |
| * return get_meson_cpu_version(MESON_CPU_VERSION_LVL_MINOR); |
| * } |
| */ |
| |
| int cvbs_cpu_type(void) |
| { |
| if (!cvbs_drv) { |
| cvbs_log_err("error: %s: no cvbs drv\n", __func__); |
| return -1; |
| } |
| if (!cvbs_drv->cvbs_data) { |
| cvbs_log_err("error: %s: no cvbs data\n", __func__); |
| return -1; |
| } |
| return cvbs_drv->cvbs_data->cpu_id; |
| } |
| EXPORT_SYMBOL(cvbs_cpu_type); |
| |
| struct meson_cvbsout_data *get_cvbs_data(void) |
| { |
| if (!cvbs_drv) { |
| cvbs_log_err("error: %s: no cvbs drv\n", __func__); |
| return NULL; |
| } |
| if (!cvbs_drv->cvbs_data) { |
| cvbs_log_err("error: %s: no cvbs data\n", __func__); |
| return NULL; |
| } |
| return cvbs_drv->cvbs_data; |
| } |
| |
| static void cvbs_vdac_output(unsigned int open) |
| { |
| if (open == 0) { /* close */ |
| cvbs_drv->flag &= ~CVBS_FLAG_EN_VDAC; |
| vdac_enable(0, VDAC_MODULE_CVBS_OUT); |
| } else if (open == 1) { /* open */ |
| vdac_vref_adj(cvbs_drv->cvbs_data->vdac_vref_adj); |
| vdac_enable(1, VDAC_MODULE_CVBS_OUT); |
| cvbs_drv->flag |= CVBS_FLAG_EN_VDAC; |
| } |
| cvbs_log_info("%s: %d\n", __func__, open); |
| } |
| |
| #ifdef CONFIG_CVBS_PERFORMANCE_COMPATIBILITY_SUPPORT |
| static void cvbs_performance_enhancement(enum cvbs_mode_e mode) |
| { |
| struct performance_config_s *perfconf = NULL; |
| const struct reg_s *s = NULL; |
| int i = 0; |
| |
| switch (mode) { |
| case MODE_576CVBS: |
| perfconf = &cvbs_drv->perf_conf_pal; |
| break; |
| case MODE_480CVBS: |
| case MODE_NTSC_M: |
| perfconf = &cvbs_drv->perf_conf_ntsc; |
| break; |
| default: |
| break; |
| } |
| if (!perfconf) |
| return; |
| |
| if (!perfconf->reg_table) { |
| cvbs_log_info("no performance table\n"); |
| return; |
| } |
| |
| i = 0; |
| s = perfconf->reg_table; |
| while (i < perfconf->reg_cnt) { |
| cvbs_out_reg_write(s->reg, s->val); |
| cvbs_log_info("%s: vcbus reg 0x%04x = 0x%08x\n", |
| __func__, 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 = cvbs_regs_tab[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 (!cvbs_drv->vinfo) |
| return; |
| if (status) { |
| #ifdef CONFIG_AMLOGIC_VPU |
| vpu_dev_clk_request(cvbs_drv->cvbs_vpu_dev, cvbs_drv->vinfo->video_clk); |
| vpu_dev_mem_power_on(cvbs_drv->cvbs_vpu_dev); |
| #endif |
| } else { |
| #ifdef CONFIG_AMLOGIC_VPU |
| vpu_dev_mem_power_down(cvbs_drv->cvbs_vpu_dev); |
| vpu_dev_clk_release(cvbs_drv->cvbs_vpu_dev); |
| #endif |
| } |
| } |
| |
| static void cvbs_out_clk_gate_ctrl(int status) |
| { |
| if (!cvbs_drv->vinfo) |
| return; |
| |
| if (status) { |
| if (cvbs_drv->clk_gate_state) { |
| cvbs_log_info("clk_gate is already on\n"); |
| return; |
| } |
| |
| if (IS_ERR(cvbs_drv->venci_top_gate)) |
| cvbs_log_err("error: %s: venci_top_gate\n", __func__); |
| else |
| clk_prepare_enable(cvbs_drv->venci_top_gate); |
| |
| if (IS_ERR(cvbs_drv->venci_0_gate)) |
| cvbs_log_err("error: %s: venci_0_gate\n", __func__); |
| else |
| clk_prepare_enable(cvbs_drv->venci_0_gate); |
| |
| if (IS_ERR(cvbs_drv->venci_1_gate)) |
| cvbs_log_err("error: %s: venci_1_gate\n", __func__); |
| else |
| clk_prepare_enable(cvbs_drv->venci_1_gate); |
| |
| if (IS_ERR(cvbs_drv->vdac_clk_gate)) |
| cvbs_log_err("error: %s: vdac_clk_gate\n", __func__); |
| else |
| clk_prepare_enable(cvbs_drv->vdac_clk_gate); |
| |
| #ifdef CONFIG_AMLOGIC_VPU |
| vpu_dev_clk_gate_on(cvbs_drv->cvbs_vpu_dev); |
| #endif |
| |
| cvbs_drv->clk_gate_state = 1; |
| } else { |
| if (cvbs_drv->clk_gate_state == 0) { |
| cvbs_log_info("clk_gate is already off\n"); |
| return; |
| } |
| |
| #ifdef CONFIG_AMLOGIC_VPU |
| vpu_dev_clk_gate_off(cvbs_drv->cvbs_vpu_dev); |
| #endif |
| if (IS_ERR(cvbs_drv->vdac_clk_gate)) |
| cvbs_log_err("error: %s: vdac_clk_gate\n", __func__); |
| else |
| clk_disable_unprepare(cvbs_drv->vdac_clk_gate); |
| |
| if (IS_ERR(cvbs_drv->venci_0_gate)) |
| cvbs_log_err("error: %s: venci_0_gate\n", __func__); |
| else |
| clk_disable_unprepare(cvbs_drv->venci_0_gate); |
| |
| if (IS_ERR(cvbs_drv->venci_1_gate)) |
| cvbs_log_err("error: %s: venci_1_gate\n", __func__); |
| else |
| clk_disable_unprepare(cvbs_drv->venci_1_gate); |
| |
| if (IS_ERR(cvbs_drv->venci_top_gate)) |
| cvbs_log_err("error: %s: venci_top_gate\n", __func__); |
| else |
| clk_disable_unprepare(cvbs_drv->venci_top_gate); |
| |
| cvbs_drv->clk_gate_state = 0; |
| } |
| } |
| |
| static void cvbs_vdac_dwork(struct work_struct *work) |
| { |
| if ((cvbs_drv->flag & CVBS_FLAG_EN_ENCI) == 0) |
| return; |
| cvbs_vdac_output(1); |
| } |
| |
| static 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; |
| case MODE_NTSC_M: |
| cvbs_log_info("SET cvbs mode: ntsc_m\n"); |
| break; |
| default: |
| cvbs_log_err("%s:invalid cvbs mode", __func__); |
| break; |
| } |
| |
| if (local_cvbs_mode >= MODE_MAX) { |
| cvbs_log_err("%s:mode error.return", __func__); |
| return -1; |
| } |
| mutex_lock(&setmode_mutex); |
| |
| cvbs_out_vpu_power_ctrl(1); |
| cvbs_out_clk_gate_ctrl(1); |
| |
| cvbs_vdac_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 |
| cvbs_drv->flag |= CVBS_FLAG_EN_ENCI; |
| schedule_delayed_work(&cvbs_drv->vdac_dwork, msecs_to_jiffies(1000)); |
| mutex_unlock(&setmode_mutex); |
| return 0; |
| } |
| |
| static int cvbs_open(struct inode *inode, struct file *file) |
| { |
| struct cvbs_drv_s *cdrv; |
| /* Get the per-device structure that contains this cdev */ |
| cdrv = container_of(&inode->i_cdev, struct cvbs_drv_s, cdev); |
| file->private_data = cdrv; |
| 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; |
| #ifdef CONFIG_AML_VOUT_CC_BYPASS |
| unsigned int CC_2byte_data = 0; |
| unsigned long flags = 0; |
| void __user *argp = (void __user *)arg; |
| #endif |
| 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) { |
| #ifdef CONFIG_AML_VOUT_CC_BYPASS |
| case VOUT_IOC_CC_OPEN: |
| spin_lock_irqsave(&tvout_clk_lock, flags); |
| memset(&cc_ringbuf, 0, sizeof(struct cc_ring_mgr_s)); |
| spin_unlock_irqrestore(&tvout_clk_lock, flags); |
| cvbs_out_reg_setb(ENCI_VBI_SETTING, 0x3, 0, 2); |
| break; |
| case VOUT_IOC_CC_CLOSE: |
| spin_lock_irqsave(&tvout_clk_lock, flags); |
| memset(&cc_ringbuf, 0, sizeof(struct cc_ring_mgr_s)); |
| spin_unlock_irqrestore(&tvout_clk_lock, flags); |
| cvbs_out_reg_setb(ENCI_VBI_SETTING, 0x0, 0, 2); |
| break; |
| case VOUT_IOC_CC_DATA: { |
| struct vout_cc_parm_s parm = {0}; |
| |
| spin_lock_irqsave(&tvout_clk_lock, flags); |
| if (copy_from_user(&parm, argp, |
| sizeof(struct vout_cc_parm_s))) { |
| cvbs_log_err("VOUT_IOC_CC_DATAinvalid parameter\n"); |
| ret = -EFAULT; |
| spin_unlock_irqrestore(&tvout_clk_lock, flags); |
| break; |
| } |
| |
| if (parm.type != 0) { |
| spin_unlock_irqrestore(&tvout_clk_lock, flags); |
| 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 ((cc_ringbuf.wp + 1) % MAX_RING_BUFF_LEN != cc_ringbuf.rp) { |
| cc_ringbuf.cc_data[cc_ringbuf.wp].type = parm.type; |
| cc_ringbuf.cc_data[cc_ringbuf.wp].data = CC_2byte_data; |
| cc_ringbuf.wp = (cc_ringbuf.wp + 1) % |
| MAX_RING_BUFF_LEN; |
| /* vout_log_info("CCringbuf Write :0x%x wp:%d\n", */ |
| /* CC_2byte_data, cc_ringbuf.wp); */ |
| } else { |
| cvbs_log_err("CCringbuf is FULL!! can't write.\n"); |
| } |
| |
| spin_unlock_irqrestore(&tvout_clk_lock, flags); |
| break; |
| } |
| #endif |
| default: |
| ret = -ENOIOCTLCMD; |
| cvbs_log_err("%s %d is not supported command\n", |
| __func__, cmd); |
| break; |
| } |
| cvbs_log_info("%s..out.ret=0x%lx\n", __func__, 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, |
| }; |
| |
| 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); |
| cvbs_drv->vinfo = vinfo; |
| } else { |
| local_cvbs_mode = MODE_MAX; |
| } |
| return vinfo; |
| } |
| EXPORT_SYMBOL(get_valid_vinfo); |
| |
| static struct vinfo_s *cvbs_get_current_info(void *data) |
| { |
| return cvbs_drv->vinfo; |
| } |
| |
| enum cvbs_mode_e get_local_cvbs_mode(void) |
| { |
| return local_cvbs_mode; |
| } |
| EXPORT_SYMBOL(get_local_cvbs_mode); |
| |
| int cvbs_set_current_vmode(enum vmode_e mode, void *data) |
| { |
| 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; |
| } |
| |
| cvbs_log_info("mode is %d,sync_duration_den=%d,sync_duration_num=%d\n", |
| tvmode, cvbs_drv->vinfo->sync_duration_den, |
| cvbs_drv->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_vdac_output(1); |
| cvbs_drv->flag = (CVBS_FLAG_EN_ENCI | CVBS_FLAG_EN_VDAC); |
| cvbs_log_info("already display in uboot\n"); |
| return 0; |
| } |
| cvbs_out_setmode(); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(cvbs_set_current_vmode); |
| |
| static enum vmode_e cvbs_validate_vmode(char *mode, unsigned int frac, |
| void *data) |
| { |
| const struct vinfo_s *info = get_valid_vinfo(mode); |
| |
| if (frac) |
| return VMODE_MAX; |
| |
| if (info) |
| return VMODE_CVBS; |
| return VMODE_MAX; |
| } |
| |
| static int cvbs_check_same_vmodeattr(char *mode, void *data) |
| { |
| return 1; |
| } |
| |
| static int cvbs_vmode_is_supported(enum vmode_e mode, void *data) |
| { |
| if ((mode & VMODE_MODE_BIT_MASK) == VMODE_CVBS) |
| return true; |
| return false; |
| } |
| |
| static int cvbs_module_disable(enum vmode_e cur_vmod, void *data) |
| { |
| cvbs_drv->flag &= ~CVBS_FLAG_EN_ENCI; |
| |
| cvbs_vdac_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, void *data) |
| { |
| cvbs_vout_state |= (1 << index); |
| return 0; |
| } |
| |
| static int cvbs_vout_clr_state(int index, void *data) |
| { |
| cvbs_vout_state &= ~(1 << index); |
| return 0; |
| } |
| |
| static int cvbs_vout_get_state(void *data) |
| { |
| return cvbs_vout_state; |
| } |
| |
| static int cvbs_vout_get_disp_cap(char *buf, void *data) |
| { |
| int ret = 0, i; |
| |
| for (i = 0; cvbs_mode_t[i]; i++) |
| ret += snprintf(buf + ret, PAGE_SIZE, "%s\n", cvbs_mode_t[i]); |
| return ret; |
| } |
| |
| #ifdef CONFIG_PM |
| static int cvbs_suspend(void *data) |
| { |
| /* TODO */ |
| /* video_dac_disable(); */ |
| cvbs_drv->flag &= ~CVBS_FLAG_EN_ENCI; |
| cvbs_vdac_output(0); |
| return 0; |
| } |
| |
| static int cvbs_resume(void *data) |
| { |
| /* TODO */ |
| /* video_dac_enable(0xff); */ |
| cvbs_set_current_vmode(cvbs_drv->vinfo->mode, NULL); |
| 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, |
| .check_same_vmodeattr = cvbs_check_same_vmodeattr, |
| .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, |
| .get_disp_cap = cvbs_vout_get_disp_cap, |
| .set_vframe_rate_hint = NULL, |
| .get_vframe_rate_hint = NULL, |
| .set_bist = cvbs_bist_test, |
| #ifdef CONFIG_PM |
| .vout_suspend = cvbs_suspend, |
| .vout_resume = cvbs_resume, |
| #endif |
| }, |
| .data = NULL, |
| }; |
| |
| #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, |
| .get_disp_cap = cvbs_vout_get_disp_cap, |
| .set_vframe_rate_hint = NULL, |
| .get_vframe_rate_hint = NULL, |
| .set_bist = cvbs_bist_test, |
| #ifdef CONFIG_PM |
| .vout_suspend = cvbs_suspend, |
| .vout_resume = cvbs_resume, |
| #endif |
| }, |
| .data = NULL, |
| }; |
| #endif |
| |
| static void cvbs_init_vout(void) |
| { |
| if (!cvbs_drv->vinfo) |
| cvbs_drv->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 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, void *data) |
| { |
| 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; |
| } |
| } |
| |
| static ssize_t aml_CVBS_attr_vdac_power_show(struct class *class, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 40, "%s\n", "vdac_power_level"); |
| } |
| |
| static void vdac_power_level_store(const char *para) |
| { |
| unsigned long level = 0; |
| int ret = 0; |
| |
| ret = kstrtoul(para, 10, (unsigned long *)&level); |
| cvbs_vdac_power_level = level; |
| } |
| |
| static ssize_t aml_CVBS_attr_vdac_power_store(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| vdac_power_level_store(buf); |
| |
| return strnlen(buf, count); |
| } |
| |
| static void dump_clk_registers(void) |
| { |
| struct meson_cvbsout_data *cvbs_data; |
| unsigned int vdac_reg0, vdac_reg1; |
| /* hiu 10c8 ~ 10cd */ |
| |
| cvbs_data = get_cvbs_data(); |
| if (!cvbs_data) |
| return; |
| /* hiu 104a, 104b*/ |
| pr_info("----ana----\n"); |
| pr_info("vid_pll_div[0x%x]:0x%x", cvbs_data->reg_vid_pll_clk_div, |
| cvbs_out_vid_pll_read(cvbs_data->reg_vid_pll_clk_div)); |
| pr_info("----hiu----\n"); |
| pr_info("vid_clk_div[0x%x]:0x%x", cvbs_data->reg_vid_clk_div, |
| cvbs_out_hiu_read(cvbs_data->reg_vid_clk_div)); |
| pr_info("vid_clk_ctrl[0x%x]:0x%x", cvbs_data->reg_vid_clk_ctrl, |
| cvbs_out_hiu_read(cvbs_data->reg_vid_clk_ctrl)); |
| pr_info("vid2_clk_div[0x%x]:0x%x", cvbs_data->reg_vid2_clk_div, |
| cvbs_out_hiu_read(cvbs_data->reg_vid2_clk_div)); |
| pr_info("vid2_clk_ctrl[0x%x]:0x%x", cvbs_data->reg_vid2_clk_ctrl, |
| cvbs_out_hiu_read(cvbs_data->reg_vid2_clk_ctrl)); |
| pr_info("vid_clk_ctrl2[0x%x]:0x%x", cvbs_data->reg_vid_clk_ctrl2, |
| cvbs_out_hiu_read(cvbs_data->reg_vid_clk_ctrl2)); |
| pr_info("----hiu----\n"); |
| |
| pr_info("------------------------\n"); |
| vdac_reg0 = vdac_get_reg_addr(0); |
| vdac_reg1 = vdac_get_reg_addr(1); |
| if (vdac_reg0 < 0x1000 && vdac_reg1 < 0x1000) { |
| pr_info("hiu [0x%x] = 0x%x\n" |
| "hiu [0x%x] = 0x%x\n", |
| vdac_reg0, cvbs_out_ana_read(vdac_reg0), |
| vdac_reg1, cvbs_out_ana_read(vdac_reg1)); |
| } |
| pr_info("------------------------\n"); |
| } |
| |
| 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 vdac_reg0, vdac_reg1; |
| 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])); |
| } |
| pr_info("------------------------\n"); |
| vdac_reg0 = vdac_get_reg_addr(0); |
| vdac_reg1 = vdac_get_reg_addr(1); |
| if (vdac_reg0 < 0x1000 && vdac_reg1 < 0x1000) { |
| pr_info("vdac cntl0 [0x%x] = 0x%x\n" |
| "vdac cntl1 [0x%x] = 0x%x\n", |
| vdac_reg0, cvbs_out_ana_read(vdac_reg0), |
| vdac_reg1, cvbs_out_ana_read(vdac_reg1)); |
| } |
| pr_info("------------------------\n"); |
| } |
| |
| static void cvbs_performance_config_dump(void) |
| { |
| struct performance_config_s *perfconf = NULL; |
| const struct reg_s *s = NULL; |
| int i = 0; |
| |
| perfconf = &cvbs_drv->perf_conf_pal; |
| if (!perfconf->reg_table) { |
| pr_info("no performance_pal table!\n"); |
| } else { |
| pr_info("------------------------\n"); |
| pr_info("performance_pal config:\n"); |
| s = perfconf->reg_table; |
| while (i < perfconf->reg_cnt) { |
| if (s->reg > 0x1000) |
| pr_info("0x%04x = 0x%x\n", s->reg, s->val); |
| else |
| pr_info("0x%02x = 0x%x\n", s->reg, s->val); |
| s++; |
| i++; |
| } |
| pr_info("------------------------\n"); |
| } |
| |
| i = 0; |
| perfconf = &cvbs_drv->perf_conf_ntsc; |
| if (!perfconf->reg_table) { |
| pr_info("no performance_ntsc table!\n"); |
| } else { |
| pr_info("------------------------\n"); |
| pr_info("performance_ntsc config:\n"); |
| s = perfconf->reg_table; |
| while (i < perfconf->reg_cnt) { |
| if (s->reg > 0x1000) |
| pr_info("0x%04x = 0x%x\n", s->reg, s->val); |
| else |
| pr_info("0x%02x = 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_VDAC, |
| |
| CMD_HELP, |
| |
| CMD_SVA_VALUE, |
| |
| 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;\ |
| } \ |
| else if (!strcmp(a, "a")) {\ |
| str_type = "ana";\ |
| func_read = cvbs_out_ana_read;\ |
| func_write = cvbs_out_ana_write;\ |
| func_getb = cvbs_out_ana_getb;\ |
| func_setb = cvbs_out_ana_setb;\ |
| } \ |
| } |
| |
| static ssize_t aml_CVBS_attr_debug_show(struct class *class, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, 40, "%s\n", "debug"); |
| } |
| |
| static void cvbs_debug_store(const 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) |
| 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"))) || |
| (!strncmp(argv[0], "clkpath", strlen("clkpath")))) { |
| cmd = CMD_VP_SET_PLLPATH; |
| } else if (!strncmp(argv[0], "vdac", strlen("vdac"))) { |
| cmd = CMD_VDAC; |
| } else if (!strncmp(argv[0], "cvbs_sva", strlen("cvbs_sva"))) { |
| pr_info("config ccitt033 SVA standard test value\n"); |
| cmd = CMD_SVA_VALUE; |
| } else if (!strncmp(argv[0], "help", strlen("help"))) { |
| cmd = CMD_HELP; |
| } else if (!strncmp(argv[0], "cvbs_ver", strlen("cvbs_ver"))) { |
| pr_info("cvbsout version : %s\n", CVBSOUT_VER); |
| goto DEBUG_END; |
| } else { |
| pr_info("[%s] invalid cmd = %s!\n", __func__, argv[0]); |
| goto DEBUG_END; |
| } |
| |
| switch (cmd) { |
| case CMD_REG_READ: |
| if (argc != 3) { |
| pr_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) |
| goto DEBUG_END; |
| ret = kstrtoul(argv[2], 16, &addr); |
| |
| pr_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) { |
| pr_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) |
| goto DEBUG_END; |
| ret = kstrtoul(argv[2], 16, &addr); |
| ret = kstrtoul(argv[3], 10, &start); |
| ret = kstrtoul(argv[4], 10, &length); |
| |
| if (length == 1) |
| pr_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 |
| pr_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) { |
| pr_info("[%s] cmd_reg_dump format: dump a/h/v start_dec end_dec\n", |
| __func__); |
| goto DEBUG_END; |
| } |
| |
| func_type_map(argv[1]); |
| if (!func_read) |
| goto DEBUG_END; |
| ret = kstrtoul(argv[2], 16, &start); |
| ret = kstrtoul(argv[3], 16, &end); |
| |
| for (i = start; i <= end; i++) |
| pr_info("%s[0x%x] = 0x%x\n", |
| str_type, i, func_read(i)); |
| |
| break; |
| |
| case CMD_REG_WRITE: |
| if (argc != 4) { |
| pr_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) |
| goto DEBUG_END; |
| ret = kstrtoul(argv[1], 16, &value); |
| ret = kstrtoul(argv[3], 16, &addr); |
| |
| func_write(addr, value); |
| pr_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) { |
| pr_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) |
| 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); |
| pr_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 */ |
| pr_info("cvbs: debug_store: clk_msr todo!\n"); |
| break; |
| |
| case CMD_BIST: |
| if (argc != 2) { |
| pr_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) { |
| pr_info("cvbs: invalid bist\n"); |
| goto DEBUG_END; |
| } |
| cvbs_bist_test(bist, NULL); |
| |
| break; |
| |
| case CMD_VP_SET: |
| if (cvbs_drv->vinfo->mode != VMODE_CVBS) { |
| pr_info("NOT VMODE_CVBS,Return\n"); |
| return; |
| } |
| cvbs_performance_enhancement(local_cvbs_mode); |
| break; |
| |
| case CMD_VP_GET: |
| cvbs_performance_regs_dump(); |
| break; |
| |
| case CMD_VP_CONFIG_DUMP: |
| pr_info("performance config in dts:\n"); |
| cvbs_performance_config_dump(); |
| break; |
| case CMD_VP_SET_PLLPATH: |
| if (cvbs_cpu_type() < CVBS_CPU_TYPE_G12A) { |
| pr_info("ERR:Only after g12a/b chip supported\n"); |
| break; |
| } |
| if (argc != 2) { |
| pr_info("[%s] set clkpath 0x0~0x3\n", __func__); |
| goto DEBUG_END; |
| } |
| ret = kstrtoul(argv[1], 10, &value); |
| if (value > 0x3) { |
| pr_info("invalid clkpath, only support 0x0~0x3\n"); |
| } else { |
| cvbs_clk_path = value; |
| pr_info("set clkpath 0x%x\n", cvbs_clk_path); |
| } |
| pr_info("bit[0]: 0=vid_pll, 1=gp0_pll\n"); |
| pr_info("bit[1]: 0=vid2_clk, 1=vid1_clk\n"); |
| |
| break; |
| case CMD_VDAC: |
| if (argc != 2) { |
| pr_info("[%s] vdac state: %d\n", |
| __func__, |
| (cvbs_drv->flag & CVBS_FLAG_EN_VDAC) ? 1 : 0); |
| goto DEBUG_END; |
| } |
| ret = kstrtoul(argv[1], 10, &value); |
| if (value) |
| cvbs_vdac_output(1); |
| else |
| cvbs_vdac_output(0); |
| |
| break; |
| case CMD_SVA_VALUE: { |
| struct performance_config_s *perfconf = NULL; |
| const struct reg_s *s = NULL; |
| int i = 0; |
| |
| perfconf = &cvbs_drv->perf_conf_pal_sva; |
| if (!perfconf) |
| return; |
| |
| if (!perfconf->reg_table) { |
| cvbs_log_info("no performance table\n"); |
| return; |
| } |
| |
| i = 0; |
| s = perfconf->reg_table; |
| while (i < perfconf->reg_cnt) { |
| cvbs_out_reg_write(s->reg, s->val); |
| cvbs_log_info("%s: vcbus reg 0x%04x = 0x%08x\n", |
| __func__, s->reg, s->val); |
| s++; |
| i++; |
| } |
| cvbs_log_info("%s\n", __func__); |
| break; |
| } |
| case CMD_HELP: |
| pr_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" |
| "\tclkpath 0/1/2/3\n" |
| "\tvdac 0/1\n" |
| "\tcvbs_ver\n"); |
| break; |
| } |
| |
| DEBUG_END: |
| kfree(p); |
| } |
| |
| static ssize_t aml_CVBS_attr_debug_store(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| cvbs_debug_store(buf); |
| |
| return strnlen(buf, count); |
| } |
| |
| 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 cvbs_drv_s *cdrv) |
| { |
| /* create base class for display */ |
| int i; |
| int ret = 0; |
| |
| cdrv->base_class = class_create(THIS_MODULE, CVBS_CLASS_NAME); |
| if (IS_ERR(cdrv->base_class)) { |
| ret = PTR_ERR(cdrv->base_class); |
| goto fail_create_class; |
| } |
| /* create class attr */ |
| for (i = 0; i < ARRAY_SIZE(cvbs_attr); i++) { |
| if (class_create_file(cdrv->base_class, cvbs_attr[i]) < 0) |
| goto fail_class_create_file; |
| } |
| /*cdev_init(cdrv->cdev, &am_cvbs_fops);*/ |
| cdrv->cdev = cdev_alloc(); |
| cdrv->cdev->ops = &am_cvbs_fops; |
| cdrv->cdev->owner = THIS_MODULE; |
| ret = cdev_add(cdrv->cdev, cdrv->devno, 1); |
| if (ret) |
| goto fail_add_cdev; |
| cdrv->dev = device_create(cdrv->base_class, NULL, cdrv->devno, |
| NULL, CVBS_NAME); |
| if (IS_ERR(cdrv->dev)) { |
| ret = PTR_ERR(cdrv->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(cdrv->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(cdrv->base_class, cvbs_attr[i]); |
| class_destroy(cdrv->base_class); |
| fail_create_class: |
| cvbs_log_info("[cvbs.] : cvbs class create error.\n"); |
| unregister_chrdev_region(cdrv->devno, 1); |
| return ret; |
| } |
| |
| /* **************************************************** */ |
| static char *cvbsout_performance_str[] = { |
| "performance", /* SVA standard value */ |
| "performance_pal", /* CTCC standard value */ |
| "performance_ntsc", |
| }; |
| |
| static void cvbsout_get_config(struct device *dev) |
| { |
| int ret = 0; |
| unsigned int val, cnt, i, j; |
| struct reg_s *s = NULL; |
| const char *str; |
| |
| /*clk path*/ |
| /*bit[0]: 0=vid_pll, 1=gp0_pll*/ |
| /*bit[1]: 0=vid2_clk, 1=vid_clk*/ |
| ret = of_property_read_u32(dev->of_node, "clk_path", &val); |
| if (!ret) { |
| if (val > 0x3) { |
| cvbs_log_err("error: invalid clk_path\n"); |
| } else { |
| cvbs_clk_path = val; |
| cvbs_log_info("clk path:0x%x\n", cvbs_clk_path); |
| } |
| } |
| |
| /* performance: PAL SVA */ |
| cvbs_drv->perf_conf_pal_sva.reg_cnt = 0; |
| cvbs_drv->perf_conf_pal_sva.reg_table = NULL; |
| str = cvbsout_performance_str[0]; |
| cnt = 0; |
| while (cnt < CVBS_PERFORMANCE_CNT_MAX) { |
| j = 2 * cnt; |
| ret = of_property_read_u32_index(dev->of_node, str, j, &val); |
| if (ret) { |
| cnt = 0; |
| break; |
| } |
| if (val == MREG_END_MARKER) /* ending */ |
| break; |
| cnt++; |
| } |
| if (cnt >= CVBS_PERFORMANCE_CNT_MAX) |
| cnt = 0; |
| if (cnt > 0) { |
| cvbs_log_info("find performance_pal config\n"); |
| cvbs_drv->perf_conf_pal_sva.reg_table = |
| kcalloc(cnt, sizeof(struct reg_s), GFP_KERNEL); |
| if (!cvbs_drv->perf_conf_pal_sva.reg_table) { |
| cvbs_log_err("error: failed to alloc %s table\n", str); |
| cnt = 0; |
| } |
| cvbs_drv->perf_conf_pal_sva.reg_cnt = cnt; |
| |
| i = 0; |
| s = cvbs_drv->perf_conf_pal_sva.reg_table; |
| while (i < cvbs_drv->perf_conf_pal_sva.reg_cnt) { |
| j = 2 * i; |
| ret = of_property_read_u32_index(dev->of_node, |
| str, j, &val); |
| s->reg = val; |
| j = 2 * i + 1; |
| ret = of_property_read_u32_index(dev->of_node, |
| str, j, &val); |
| s->val = val; |
| /*pr_info("%p: 0x%04x = 0x%x\n", s, s->reg, s->val);*/ |
| |
| s++; |
| i++; |
| } |
| } |
| |
| /* performance: PAL CTCC */ |
| cvbs_drv->perf_conf_pal.reg_cnt = 0; |
| cvbs_drv->perf_conf_pal.reg_table = NULL; |
| str = cvbsout_performance_str[1]; |
| cnt = 0; |
| while (cnt < CVBS_PERFORMANCE_CNT_MAX) { |
| j = 2 * cnt; |
| ret = of_property_read_u32_index(dev->of_node, str, j, &val); |
| if (ret) { |
| cnt = 0; |
| break; |
| } |
| if (val == MREG_END_MARKER) /* ending */ |
| break; |
| cnt++; |
| } |
| if (cnt >= CVBS_PERFORMANCE_CNT_MAX) |
| cnt = 0; |
| if (cnt > 0) { |
| cvbs_log_info("find performance_pal config\n"); |
| cvbs_drv->perf_conf_pal.reg_table = |
| kcalloc(cnt, sizeof(struct reg_s), GFP_KERNEL); |
| if (!cvbs_drv->perf_conf_pal.reg_table) { |
| cvbs_log_err("error: failed to alloc %s table\n", str); |
| cnt = 0; |
| } |
| cvbs_drv->perf_conf_pal.reg_cnt = cnt; |
| |
| i = 0; |
| s = cvbs_drv->perf_conf_pal.reg_table; |
| while (i < cvbs_drv->perf_conf_pal.reg_cnt) { |
| j = 2 * i; |
| ret = of_property_read_u32_index(dev->of_node, |
| str, j, &val); |
| s->reg = val; |
| j = 2 * i + 1; |
| ret = of_property_read_u32_index(dev->of_node, |
| str, j, &val); |
| s->val = val; |
| /*pr_info("%p: 0x%04x = 0x%x\n", s, s->reg, s->val);*/ |
| |
| s++; |
| i++; |
| } |
| } |
| |
| /* performance: NTSC */ |
| cvbs_drv->perf_conf_ntsc.reg_cnt = 0; |
| cvbs_drv->perf_conf_ntsc.reg_table = NULL; |
| cnt = 0; |
| str = cvbsout_performance_str[2]; |
| while (cnt < CVBS_PERFORMANCE_CNT_MAX) { |
| j = 2 * cnt; |
| ret = of_property_read_u32_index(dev->of_node, str, j, &val); |
| if (ret) { |
| cnt = 0; |
| break; |
| } |
| if (val == MREG_END_MARKER) /* ending */ |
| break; |
| cnt++; |
| } |
| if (cnt >= CVBS_PERFORMANCE_CNT_MAX) |
| cnt = 0; |
| if (cnt > 0) { |
| cvbs_log_info("find performance_ntsc config\n"); |
| cvbs_drv->perf_conf_ntsc.reg_table = |
| kcalloc(cnt, sizeof(struct reg_s), GFP_KERNEL); |
| if (!cvbs_drv->perf_conf_ntsc.reg_table) { |
| cvbs_log_err("error: failed to alloc %s table\n", str); |
| cnt = 0; |
| } |
| cvbs_drv->perf_conf_ntsc.reg_cnt = cnt; |
| |
| i = 0; |
| s = cvbs_drv->perf_conf_ntsc.reg_table; |
| while (i < cvbs_drv->perf_conf_ntsc.reg_cnt) { |
| j = 2 * i; |
| ret = of_property_read_u32_index(dev->of_node, |
| str, j, &val); |
| s->reg = val; |
| j = 2 * i + 1; |
| ret = of_property_read_u32_index(dev->of_node, |
| str, j, &val); |
| s->val = val; |
| /*pr_info("%p: 0x%04x = 0x%x\n", s, s->reg, s->val);*/ |
| |
| s++; |
| i++; |
| } |
| } |
| } |
| |
| static void cvbsout_clktree_probe(struct device *dev) |
| { |
| cvbs_drv->clk_gate_state = 0; |
| cvbs_drv->venci_top_gate = devm_clk_get(dev, "venci_top_gate"); |
| if (IS_ERR(cvbs_drv->venci_top_gate)) |
| cvbs_log_err("error: %s: clk venci_top_gate\n", __func__); |
| |
| cvbs_drv->venci_0_gate = devm_clk_get(dev, "venci_0_gate"); |
| if (IS_ERR(cvbs_drv->venci_0_gate)) |
| cvbs_log_err("error: %s: clk venci_0_gate\n", __func__); |
| |
| cvbs_drv->venci_1_gate = devm_clk_get(dev, "venci_1_gate"); |
| if (IS_ERR(cvbs_drv->venci_1_gate)) |
| cvbs_log_err("error: %s: clk venci_1_gate\n", __func__); |
| |
| cvbs_drv->vdac_clk_gate = devm_clk_get(dev, "vdac_clk_gate"); |
| if (IS_ERR(cvbs_drv->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(cvbs_drv->venci_top_gate)) |
| devm_clk_put(dev, cvbs_drv->venci_top_gate); |
| if (!IS_ERR(cvbs_drv->venci_0_gate)) |
| devm_clk_put(dev, cvbs_drv->venci_0_gate); |
| if (!IS_ERR(cvbs_drv->venci_1_gate)) |
| devm_clk_put(dev, cvbs_drv->venci_1_gate); |
| if (!IS_ERR(cvbs_drv->vdac_clk_gate)) |
| devm_clk_put(dev, cvbs_drv->vdac_clk_gate); |
| } |
| |
| #ifdef CONFIG_OF |
| struct meson_cvbsout_data meson_g12a_cvbsout_data = { |
| .cpu_id = CVBS_CPU_TYPE_G12A, |
| .name = "meson-g12a-cvbsout", |
| |
| .vdac_vref_adj = 0x10, |
| .vdac_gsw = 0x0, |
| |
| .reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV, |
| .reg_vid_clk_div = HHI_VID_CLK_DIV, |
| .reg_vid_clk_ctrl = HHI_VID_CLK_CNTL, |
| .reg_vid2_clk_div = HHI_VIID_CLK_DIV, |
| .reg_vid2_clk_ctrl = HHI_VIID_CLK_CNTL, |
| .reg_vid_clk_ctrl2 = HHI_VID_CLK_CNTL2, |
| }; |
| |
| struct meson_cvbsout_data meson_g12b_cvbsout_data = { |
| .cpu_id = CVBS_CPU_TYPE_G12B, |
| .name = "meson-g12b-cvbsout", |
| |
| .vdac_vref_adj = 0xf, |
| .vdac_gsw = 0x0, |
| |
| .reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV, |
| .reg_vid_clk_div = HHI_VID_CLK_DIV, |
| .reg_vid_clk_ctrl = HHI_VID_CLK_CNTL, |
| .reg_vid2_clk_div = HHI_VIID_CLK_DIV, |
| .reg_vid2_clk_ctrl = HHI_VIID_CLK_CNTL, |
| .reg_vid_clk_ctrl2 = HHI_VID_CLK_CNTL2, |
| }; |
| |
| #ifndef CONFIG_AMLOGIC_REMOVE_OLD |
| struct meson_cvbsout_data meson_tl1_cvbsout_data = { |
| .cpu_id = CVBS_CPU_TYPE_TL1, |
| .name = "meson-tl1-cvbsout", |
| |
| .vdac_vref_adj = 0x10, |
| .vdac_gsw = 0x0, |
| |
| .reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV, |
| .reg_vid_clk_div = HHI_VID_CLK_DIV, |
| .reg_vid_clk_ctrl = HHI_VID_CLK_CNTL, |
| .reg_vid2_clk_div = HHI_VIID_CLK_DIV, |
| .reg_vid2_clk_ctrl = HHI_VIID_CLK_CNTL, |
| .reg_vid_clk_ctrl2 = HHI_VID_CLK_CNTL2, |
| }; |
| #endif |
| |
| struct meson_cvbsout_data meson_sm1_cvbsout_data = { |
| .cpu_id = CVBS_CPU_TYPE_SM1, |
| .name = "meson-sm1-cvbsout", |
| |
| .vdac_vref_adj = 0xf, |
| .vdac_gsw = 0x0, |
| |
| .reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV, |
| .reg_vid_clk_div = HHI_VID_CLK_DIV, |
| .reg_vid_clk_ctrl = HHI_VID_CLK_CNTL, |
| .reg_vid2_clk_div = HHI_VIID_CLK_DIV, |
| .reg_vid2_clk_ctrl = HHI_VIID_CLK_CNTL, |
| .reg_vid_clk_ctrl2 = HHI_VID_CLK_CNTL2, |
| }; |
| |
| struct meson_cvbsout_data meson_tm2_cvbsout_data = { |
| .cpu_id = CVBS_CPU_TYPE_TM2, |
| .name = "meson-tm2-cvbsout", |
| |
| .vdac_vref_adj = 0x10, |
| .vdac_gsw = 0x0, |
| |
| .reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV, |
| .reg_vid_clk_div = HHI_VID_CLK_DIV, |
| .reg_vid_clk_ctrl = HHI_VID_CLK_CNTL, |
| .reg_vid2_clk_div = HHI_VIID_CLK_DIV, |
| .reg_vid2_clk_ctrl = HHI_VIID_CLK_CNTL, |
| .reg_vid_clk_ctrl2 = HHI_VID_CLK_CNTL2, |
| }; |
| |
| struct meson_cvbsout_data meson_sc2_cvbsout_data = { |
| .cpu_id = CVBS_CPU_TYPE_SC2, |
| .name = "meson-sc2-cvbsout", |
| |
| .vdac_vref_adj = 0x10, |
| .vdac_gsw = 0x0, |
| |
| .reg_vid_pll_clk_div = CLKCTRL_VID_PLL_CLK_DIV, |
| .reg_vid_clk_div = CLKCTRL_VID_CLK_DIV, |
| .reg_vid_clk_ctrl = CLKCTRL_VID_CLK_CTRL, |
| .reg_vid2_clk_div = CLKCTRL_VIID_CLK_DIV, |
| .reg_vid2_clk_ctrl = CLKCTRL_VIID_CLK_CTRL, |
| .reg_vid_clk_ctrl2 = CLKCTRL_VID_CLK_CTRL2, |
| }; |
| |
| struct meson_cvbsout_data meson_t5_cvbsout_data = { |
| .cpu_id = CVBS_CPU_TYPE_T5, |
| .name = "meson-t5-cvbsout", |
| |
| .vdac_vref_adj = 0x10, |
| .vdac_gsw = 0x5c, |
| |
| .reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV, |
| .reg_vid_clk_div = HHI_VID_CLK_DIV, |
| .reg_vid_clk_ctrl = HHI_VID_CLK_CNTL, |
| .reg_vid2_clk_div = HHI_VIID_CLK_DIV, |
| .reg_vid2_clk_ctrl = HHI_VIID_CLK_CNTL, |
| .reg_vid_clk_ctrl2 = HHI_VID_CLK_CNTL2, |
| }; |
| |
| struct meson_cvbsout_data meson_t5d_cvbsout_data = { |
| .cpu_id = CVBS_CPU_TYPE_T5D, |
| .name = "meson-t5d-cvbsout", |
| |
| .vdac_vref_adj = 0x10, |
| .vdac_gsw = 0x5c, |
| |
| .reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV, |
| .reg_vid_clk_div = HHI_VID_CLK_DIV, |
| .reg_vid_clk_ctrl = HHI_VID_CLK_CNTL, |
| .reg_vid2_clk_div = HHI_VIID_CLK_DIV, |
| .reg_vid2_clk_ctrl = HHI_VIID_CLK_CNTL, |
| .reg_vid_clk_ctrl2 = HHI_VID_CLK_CNTL2, |
| }; |
| |
| struct meson_cvbsout_data meson_s4_cvbsout_data = { |
| .cpu_id = CVBS_CPU_TYPE_S4, |
| .name = "meson-s4-cvbsout", |
| |
| .vdac_vref_adj = 0x10, |
| .vdac_gsw = 0x5c, |
| |
| .reg_vid_pll_clk_div = CLKCTRL_VID_PLL_CLK_DIV, |
| .reg_vid_clk_div = CLKCTRL_VID_CLK_DIV, |
| .reg_vid_clk_ctrl = CLKCTRL_VID_CLK_CTRL, |
| .reg_vid2_clk_div = CLKCTRL_VIID_CLK_DIV, |
| .reg_vid2_clk_ctrl = CLKCTRL_VIID_CLK_CTRL, |
| .reg_vid_clk_ctrl2 = CLKCTRL_VID_CLK_CTRL2, |
| }; |
| |
| struct meson_cvbsout_data meson_t3_cvbsout_data = { |
| .cpu_id = CVBS_CPU_TYPE_T3, |
| .name = "meson-t3-cvbsout", |
| |
| .vdac_vref_adj = 0x10, |
| .vdac_gsw = 0x5c, |
| |
| .reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV_T3, |
| .reg_vid_clk_div = CLKCTRL_VID_CLK_DIV, |
| .reg_vid_clk_ctrl = CLKCTRL_VID_CLK_CTRL, |
| .reg_vid2_clk_div = CLKCTRL_VIID_CLK_DIV, |
| .reg_vid2_clk_ctrl = CLKCTRL_VIID_CLK_CTRL, |
| .reg_vid_clk_ctrl2 = CLKCTRL_VID_CLK_CTRL2, |
| }; |
| |
| struct meson_cvbsout_data meson_s4d_cvbsout_data = { |
| .cpu_id = CVBS_CPU_TYPE_S4D, |
| .name = "meson-s4d-cvbsout", |
| |
| .vdac_vref_adj = 0x10, |
| .vdac_gsw = 0x5c, |
| |
| .reg_vid_pll_clk_div = CLKCTRL_VID_PLL_CLK_DIV, |
| .reg_vid_clk_div = CLKCTRL_VID_CLK_DIV, |
| .reg_vid_clk_ctrl = CLKCTRL_VID_CLK_CTRL, |
| .reg_vid2_clk_div = CLKCTRL_VIID_CLK_DIV, |
| .reg_vid2_clk_ctrl = CLKCTRL_VIID_CLK_CTRL, |
| .reg_vid_clk_ctrl2 = CLKCTRL_VID_CLK_CTRL2, |
| }; |
| |
| struct meson_cvbsout_data meson_t5w_cvbsout_data = { |
| .cpu_id = CVBS_CPU_TYPE_T5W, |
| .name = "meson-t5w-cvbsout", |
| |
| .vdac_vref_adj = 0x10, |
| .vdac_gsw = 0x5c, |
| |
| .reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV, |
| .reg_vid_clk_div = HHI_VID_CLK0_DIV, |
| .reg_vid_clk_ctrl = HHI_VID_CLK0_CTRL, |
| .reg_vid2_clk_div = HHI_VIID_CLK0_DIV, |
| .reg_vid2_clk_ctrl = HHI_VIID_CLK0_CTRL, |
| .reg_vid_clk_ctrl2 = HHI_VID_CLK0_CTRL2, |
| }; |
| |
| static const struct of_device_id meson_cvbsout_dt_match[] = { |
| { |
| .compatible = "amlogic, cvbsout-g12a", |
| .data = &meson_g12a_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-g12b", |
| .data = &meson_g12b_cvbsout_data, |
| }, |
| #ifndef CONFIG_AMLOGIC_REMOVE_OLD |
| { |
| .compatible = "amlogic, cvbsout-tl1", |
| .data = &meson_tl1_cvbsout_data, |
| }, |
| #endif |
| { |
| .compatible = "amlogic, cvbsout-sm1", |
| .data = &meson_sm1_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-tm2", |
| .data = &meson_tm2_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-sc2", |
| .data = &meson_sc2_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-t5", |
| .data = &meson_t5_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-t5d", |
| .data = &meson_t5d_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-s4", |
| .data = &meson_s4_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-t3", |
| .data = &meson_t3_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-s4d", |
| .data = &meson_s4d_cvbsout_data, |
| }, { |
| .compatible = "amlogic, cvbsout-t5w", |
| .data = &meson_t5w_cvbsout_data, |
| }, |
| {} |
| }; |
| #endif |
| |
| #ifdef CONFIG_AML_VOUT_CC_BYPASS |
| static irqreturn_t tvout_vsync_isr(int irq, void *dev_id) |
| { |
| unsigned int CC_2byte_data; |
| unsigned long flags = 0; |
| struct vout_cc_parm_s parm = {0}; |
| |
| spin_lock_irqsave(&tvout_clk_lock, flags); |
| if (cc_ringbuf.rp != cc_ringbuf.wp) { |
| parm.type = cc_ringbuf.cc_data[cc_ringbuf.rp].type; |
| CC_2byte_data = cc_ringbuf.cc_data[cc_ringbuf.rp].data; |
| vsync_empty_flag = 0; |
| vsync_empty_flag_evn = 0; |
| vsync_empty_flag_odd = 0; |
| } else { |
| if (vsync_empty_flag == 0) { |
| if ((cvbs_out_reg_read(ENCI_INFO_READ) & |
| 0x20000000) == 0x0) { |
| cvbs_out_reg_write(ENCI_VBI_CCDT_EVN, 0x8080); |
| vsync_empty_flag_evn = 1; |
| } else { |
| cvbs_out_reg_write(ENCI_VBI_CCDT_ODD, 0x8080); |
| vsync_empty_flag_odd = 1; |
| } |
| vsync_empty_flag = vsync_empty_flag_evn & |
| vsync_empty_flag_odd; |
| } |
| spin_unlock_irqrestore(&tvout_clk_lock, flags); |
| return IRQ_HANDLED; |
| } |
| |
| if (parm.type == 0) { |
| if ((((CC_2byte_data >> 8) & 0x7f) >= 0x1) && |
| (((CC_2byte_data >> 8) & 0x7f) < 0x10)) { |
| if ((cvbs_out_reg_read(ENCI_INFO_READ) & |
| 0x20000000) != 0x0) { |
| cvbs_out_reg_write(ENCI_VBI_CCDT_ODD, |
| CC_2byte_data); |
| if (((cvbs_out_reg_read(ENCI_INFO_READ) >> 16) & |
| 0xff) <= 0x15) |
| cc_ringbuf.rp = (cc_ringbuf.rp + |
| 1) % MAX_RING_BUFF_LEN; |
| } else { |
| cvbs_out_reg_write(ENCI_VBI_CCDT_ODD, 0x8080); |
| } |
| } else { |
| if ((cvbs_out_reg_read(ENCI_INFO_READ) & |
| 0x20000000) == 0x0){ |
| cvbs_out_reg_write(ENCI_VBI_CCDT_EVN, |
| CC_2byte_data); |
| if (((cvbs_out_reg_read(ENCI_INFO_READ) >> |
| 16) & 0xff) <= 0x15) |
| cc_ringbuf.rp = (cc_ringbuf.rp + |
| 1) % MAX_RING_BUFF_LEN; |
| } else { |
| cvbs_out_reg_write(ENCI_VBI_CCDT_EVN, 0x8080); |
| } |
| } |
| } else { |
| cvbs_log_err("vsync_isr.type:%d Unknown\n", parm.type); |
| } |
| spin_unlock_irqrestore(&tvout_clk_lock, flags); |
| return IRQ_HANDLED; |
| } |
| #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; |
| |
| #ifdef CONFIG_AML_VOUT_CC_BYPASS |
| memset(&cc_ringbuf, 0, sizeof(struct cc_ring_mgr_s)); |
| cc_ringbuf.max_len = MAX_RING_BUFF_LEN; |
| spin_lock_init(&tvout_clk_lock); |
| #endif |
| |
| cvbs_drv = kzalloc(sizeof(*cvbs_drv), GFP_KERNEL); |
| if (!cvbs_drv) |
| return -ENOMEM; |
| |
| match = of_match_device(meson_cvbsout_dt_match, &pdev->dev); |
| if (!match) { |
| cvbs_log_err("%s, no matched table\n", __func__); |
| goto cvbsout_probe_err; |
| } |
| cvbs_drv->cvbs_data = (struct meson_cvbsout_data *)match->data; |
| cvbs_log_info("%s, cpu_id:%d,name:%s\n", __func__, |
| cvbs_drv->cvbs_data->cpu_id, cvbs_drv->cvbs_data->name); |
| |
| if (cvbs_drv->cvbs_data->cpu_id != CVBS_CPU_TYPE_SC2 && |
| cvbs_drv->cvbs_data->cpu_id >= CVBS_CPU_TYPE_S4) |
| cvbsout_clktree_probe(&pdev->dev); |
| |
| cvbsout_get_config(&pdev->dev); |
| cvbs_init_vout(); |
| |
| #ifdef CONFIG_AMLOGIC_VPU |
| /*vpu gate register for cvbs*/ |
| cvbs_drv->cvbs_vpu_dev = vpu_dev_register(VPU_VENCI, CVBS_NAME); |
| #endif |
| |
| ret = alloc_chrdev_region(&cvbs_drv->devno, 0, 1, CVBS_NAME); |
| if (ret < 0) { |
| cvbs_log_err("alloc_chrdev_region error\n"); |
| goto cvbsout_probe_err; |
| } |
| cvbs_log_err("chrdev devno %d for disp\n", cvbs_drv->devno); |
| ret = create_cvbs_attr(cvbs_drv); |
| if (ret < 0) { |
| cvbs_log_err("create_cvbs_attr error\n"); |
| goto cvbsout_probe_err; |
| } |
| |
| #ifdef CONFIG_AML_VOUT_CC_BYPASS |
| if (request_irq(INT_VIU_VSYNC, &tvout_vsync_isr, |
| IRQF_SHARED, "tvout_vsync", (void *)"tvout_vsync")) { |
| cvbs_log_err("can't request vsync_irq for tvout\n"); |
| } else { |
| cvbs_log_info("request tvout vsync_irq successful\n"); |
| } |
| #endif |
| |
| INIT_DELAYED_WORK(&cvbs_drv->vdac_dwork, cvbs_vdac_dwork); |
| cvbs_log_info("%s OK\n", __func__); |
| return 0; |
| |
| cvbsout_probe_err: |
| kfree(cvbs_drv); |
| cvbs_drv = NULL; |
| return -1; |
| } |
| |
| static int cvbsout_remove(struct platform_device *pdev) |
| { |
| int i; |
| |
| #ifdef CONFIG_AML_VOUT_CC_BYPASS |
| free_irq(INT_VIU_VSYNC, (void *)"tvout_vsync"); |
| #endif |
| cvbsout_clktree_remove(&pdev->dev); |
| |
| if (cvbs_drv->base_class) { |
| for (i = 0; i < ARRAY_SIZE(cvbs_attr); i++) |
| class_remove_file(cvbs_drv->base_class, cvbs_attr[i]); |
| class_destroy(cvbs_drv->base_class); |
| } |
| if (cvbs_drv) { |
| cdev_del(cvbs_drv->cdev); |
| #ifdef CONFIG_AMLOGIC_VPU |
| /*vpu gate unregister for cvbs*/ |
| vpu_dev_unregister(cvbs_drv->cvbs_vpu_dev); |
| #endif |
| kfree(cvbs_drv); |
| } |
| 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) |
| { |
| if ((cvbs_drv->flag & CVBS_FLAG_EN_ENCI) == 0) |
| return; |
| |
| if (cvbs_drv->flag & CVBS_FLAG_EN_VDAC) |
| cvbs_vdac_output(0); |
| |
| cvbs_drv->flag &= ~CVBS_FLAG_EN_ENCI; |
| cvbs_out_reg_write(ENCI_VIDEO_EN, 0); |
| cvbs_out_disable_clk(); |
| |
| cvbs_out_vpu_power_ctrl(0); |
| cvbs_out_clk_gate_ctrl(0); |
| cvbs_log_info("%s\n", __func__); |
| } |
| |
| 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 |
| }, |
| }; |
| |
| 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; |
| } |
| #ifdef CONFIG_AML_VOUT_CC_BYPASS |
| memset(&cc_ringbuf, 0, sizeof(struct cc_ring_mgr_s)); |
| cc_ringbuf.max_len = MAX_RING_BUFF_LEN; |
| #endif |
| return 0; |
| } |
| |
| __exit void cvbs_exit_module(void) |
| { |
| /* cvbs_log_info("%s module exit\n", __func__); */ |
| platform_driver_unregister(&cvbsout_driver); |
| } |
| |
| //MODULE_AUTHOR("Platform-BJ <platform.bj@amlogic.com>"); |
| //MODULE_DESCRIPTION("TV Output Module"); |
| //MODULE_LICENSE("GPL"); |
| |