// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020, Rockchip Electronics Co., Ltd. */ #include #include #include #include #include #include #include #include #include #include #include #include "rockchip_dmc_timing.h" /* * DMCDBG share memory request 4KB for delivery parameter */ #define DMCDBG_PAGE_NUMS (1) #define DMCDBG_SHARE_MEM_SIZE ((DMCDBG_PAGE_NUMS) * 4096) #define PROC_DMCDBG_DIR_NAME "dmcdbg" #define PROC_DMCDBG_DRAM_INFO "dmcinfo" #define PROC_DMCDBG_POWERSAVE "powersave" #define PROC_DMCDBG_DRVODT "drvodt" #define PROC_DMCDBG_DESKEW "deskew" #define PROC_DMCDBG_REGS_INFO "regsinfo" #define DDRDBG_FUNC_GET_VERSION (0x01) #define DDRDBG_FUNC_GET_SUPPORTED (0x02) #define DDRDBG_FUNC_GET_DRAM_INFO (0x03) #define DDRDBG_FUNC_GET_DESKEW_INFO (0x04) #define DDRDBG_FUNC_UPDATE_DESKEW (0x05) #define DDRDBG_FUNC_DATA_TRAINING (0x06) #define DDRDBG_FUNC_UPDATE_DESKEW_TR (0x07) #define DDRDBG_FUNC_GET_POWERSAVE_INFO (0x08) #define DDRDBG_FUNC_UPDATE_POWERSAVE (0x09) #define DDRDBG_FUNC_GET_DRVODT_INFO (0x0a) #define DDRDBG_FUNC_UPDATE_DRVODT (0x0b) #define DDRDBG_FUNC_GET_REGISTERS_INFO (0x0c) #define DRV_ODT_UNKNOWN (0xffff) #define DRV_ODT_UNSUSPEND_FIX (0x0) #define DRV_ODT_SUSPEND_FIX (0x1) #define REGS_NAME_LEN_MAX (20) #define SKEW_GROUP_NUM_MAX (6) #define SKEW_TIMING_NUM_MAX (50) struct rockchip_dmcdbg { struct device *dev; }; struct proc_dir_entry *proc_dmcdbg_dir; struct dram_cap_info { unsigned int rank; unsigned int col; unsigned int bank; unsigned int buswidth; unsigned int die_buswidth; unsigned int row_3_4; unsigned int cs0_row; unsigned int cs1_row; unsigned int cs0_high16bit_row; unsigned int cs1_high16bit_row; unsigned int bankgroup; unsigned int size; }; struct dram_info { unsigned int version; char dramtype[10]; unsigned int dramfreq; unsigned int channel_num; struct dram_cap_info ch[2]; }; static const char * const power_save_msg[] = { "auto power down enable", "auto power down idle cycle", "auto self refresh enable", "auto self refresh idle cycle", "self refresh with clock gate idle cycle", "self refresh and power down lite idle cycle", "standby idle cycle", }; struct power_save_info { unsigned int pd_en; unsigned int pd_idle; unsigned int sr_en; unsigned int sr_idle; unsigned int sr_mc_gate_idle; unsigned int srpd_lite_idle; unsigned int standby_idle; }; static const char * const drv_odt_msg[] = { "dram side drv pull-up", "dram side drv pull-down", "dram side dq odt pull-up", "dram side dq odt pull-down", "dram side ca odt pull-up", "dram side ca odt pull-down", "soc side ca drv pull-up", "soc side ca drv pull-down", "soc side ck drv pull-up", "soc side ck drv pull-down", "soc side cs drv pull-up", "soc side cs drv pull-down", "soc side dq drv pull-up", "soc side dq drv pull-down", "soc side odt pull-up", "soc side odt pull-down", "phy vref inner", "phy vref out", }; struct drv_odt { unsigned int value; unsigned int ohm; unsigned int flag; }; struct drv_odt_vref { unsigned int value; unsigned int percen; unsigned int flag; }; struct drv_odt_info { struct drv_odt dram_drv_up; struct drv_odt dram_drv_down; struct drv_odt dram_dq_odt_up; struct drv_odt dram_dq_odt_down; struct drv_odt dram_ca_odt_up; struct drv_odt dram_ca_odt_down; struct drv_odt phy_ca_drv_up; struct drv_odt phy_ca_drv_down; struct drv_odt phy_ck_drv_up; struct drv_odt phy_ck_drv_down; struct drv_odt phy_cs_drv_up; struct drv_odt phy_cs_drv_down; struct drv_odt phy_dq_drv_up; struct drv_odt phy_dq_drv_down; struct drv_odt phy_odt_up; struct drv_odt phy_odt_down; struct drv_odt_vref phy_vref_inner; struct drv_odt_vref phy_vref_out; }; struct dmc_registers { char regs_name[REGS_NAME_LEN_MAX]; unsigned int regs_addr; }; struct registers_info { unsigned int regs_num; struct dmc_registers regs[]; }; struct skew_group { unsigned int skew_num; unsigned int *p_skew_info; char *p_skew_timing[SKEW_TIMING_NUM_MAX]; char *note; }; struct rockchip_dmcdbg_data { unsigned int inited_flag; void __iomem *share_memory; unsigned int skew_group_num; struct skew_group skew_group[SKEW_GROUP_NUM_MAX]; }; static struct rockchip_dmcdbg_data dmcdbg_data; struct skew_info_rv1126 { unsigned int ca_skew[32]; unsigned int cs0_a_skew[44]; unsigned int cs0_b_skew[44]; unsigned int cs1_a_skew[44]; unsigned int cs1_b_skew[44]; }; static int dmcinfo_proc_show(struct seq_file *m, void *v) { struct arm_smccc_res res; struct dram_info *p_dram_info; struct file *fp = NULL; char cur_freq[20] = {0}; char governor[20] = {0}; loff_t pos; u32 i; res = sip_smc_dram(SHARE_PAGE_TYPE_DDRDBG, DDRDBG_FUNC_GET_DRAM_INFO, ROCKCHIP_SIP_CONFIG_DRAM_DEBUG); if (res.a0) { seq_printf(m, "rockchip_sip_config_dram_debug error:%lx\n", res.a0); return -ENOMEM; } if (!dmcdbg_data.inited_flag) { seq_puts(m, "dmcdbg_data no int\n"); return -EPERM; } p_dram_info = (struct dram_info *)dmcdbg_data.share_memory; /* dram type information */ seq_printf(m, "DramType: %s\n" , p_dram_info->dramtype ); /* dram capacity information */ seq_printf(m, "\n" "DramCapacity:\n" ); for (i = 0; i < p_dram_info->channel_num; i++) { if (p_dram_info->channel_num == 2) seq_printf(m, "Channel [%d]:\n" , i ); seq_printf(m, "CS Count: %d\n" "Bus Width: %d bit\n" "Column: %d\n" "Bank: %d\n" "CS0_Row: %d\n" "CS1_Row: %d\n" "DieBusWidth: %d bit\n" "TotalSize: %d MB\n" , p_dram_info->ch[i].rank, p_dram_info->ch[i].buswidth, p_dram_info->ch[i].col, p_dram_info->ch[i].bank, p_dram_info->ch[i].cs0_row, p_dram_info->ch[i].cs1_row, p_dram_info->ch[i].die_buswidth, p_dram_info->ch[i].size ); } /* check devfreq/dmc device */ fp = filp_open("/sys/class/devfreq/dmc/cur_freq", O_RDONLY, 0); if (IS_ERR(fp)) { seq_printf(m, "\n" "devfreq/dmc: Disable\n" "DramFreq: %d\n" , p_dram_info->dramfreq ); } else { pos = 0; kernel_read(fp, cur_freq, sizeof(cur_freq), &pos); filp_close(fp, NULL); fp = filp_open("/sys/class/devfreq/dmc/governor", O_RDONLY, 0); if (IS_ERR(fp)) { fp = NULL; } else { pos = 0; kernel_read(fp, governor, sizeof(governor), &pos); filp_close(fp, NULL); } seq_printf(m, "\n" "devfreq/dmc: Enable\n" "governor: %s\n" "cur_freq: %s\n" , governor, cur_freq ); seq_printf(m, "NOTE:\n" "more information about dmc can get from /sys/class/devfreq/dmc.\n" ); } return 0; } static int dmcinfo_proc_open(struct inode *inode, struct file *file) { return single_open(file, dmcinfo_proc_show, NULL); } static const struct file_operations dmcinfo_proc_fops = { .open = dmcinfo_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int proc_dmcinfo_init(void) { /* create dmcinfo file */ proc_create(PROC_DMCDBG_DRAM_INFO, 0644, proc_dmcdbg_dir, &dmcinfo_proc_fops); return 0; } static int powersave_proc_show(struct seq_file *m, void *v) { struct arm_smccc_res res; struct power_save_info *p_power; unsigned int *p_uint; unsigned int i = 0; /* get low power information */ res = sip_smc_dram(SHARE_PAGE_TYPE_DDRDBG, DDRDBG_FUNC_GET_POWERSAVE_INFO, ROCKCHIP_SIP_CONFIG_DRAM_DEBUG); if (res.a0) { seq_printf(m, "rockchip_sip_config_dram_debug error:%lx\n", res.a0); return -ENOMEM; } if (!dmcdbg_data.inited_flag) { seq_puts(m, "dmcdbg_data no int\n"); return -EPERM; } p_power = (struct power_save_info *)dmcdbg_data.share_memory; seq_printf(m, "low power information:\n" "\n" "[number]name: value\n" ); p_uint = (unsigned int *)p_power; for (i = 0; i < ARRAY_SIZE(power_save_msg); i++) seq_printf(m, "[%d]%s: %d\n" , i, power_save_msg[i], *(p_uint + i) ); seq_printf(m, "\n" "power save setting:\n" "echo number=value > /proc/dmcdbg/powersave\n" "eg: set auto power down enable to 1\n" " echo 0=1 > /proc/dmcdbg/powersave\n" "\n" "Support for setting multiple parameters at the same time.\n" "echo number=value,number=value,... > /proc/dmcdbg/powersave\n" "eg:\n" " echo 0=1,1=32 > /proc/dmcdbg/powersave\n" ); return 0; } static int powersave_proc_open(struct inode *inode, struct file *file) { return single_open(file, powersave_proc_show, NULL); } static ssize_t powersave_proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { struct arm_smccc_res res; struct power_save_info *p_power; unsigned int *p_uint; char *buf, *cookie_pot, *p_char; int ret = 0; u32 loop, i, offset, value; long long_val; /* get buffer data */ buf = vzalloc(count); cookie_pot = buf; if (!cookie_pot) return -ENOMEM; if (copy_from_user(cookie_pot, buffer, count)) { ret = -EFAULT; goto err; } /* get power save setting information */ res = sip_smc_dram(SHARE_PAGE_TYPE_DDRDBG, DDRDBG_FUNC_GET_POWERSAVE_INFO, ROCKCHIP_SIP_CONFIG_DRAM_DEBUG); if (res.a0) { pr_err("rockchip_sip_config_dram_debug error:%lx\n", res.a0); ret = -ENOMEM; goto err; } if (!dmcdbg_data.inited_flag) { pr_err("dmcdbg_data no int\n"); ret = -EPERM; goto err; } p_power = (struct power_save_info *)dmcdbg_data.share_memory; loop = 0; for (i = 0; i < count; i++) { if (*(cookie_pot + i) == '=') loop++; } p_uint = (unsigned int *)p_power; for (i = 0; i < loop; i++) { p_char = strsep(&cookie_pot, "="); ret = kstrtol(p_char, 10, &long_val); if (ret) goto err; offset = long_val; if (i == (loop - 1)) p_char = strsep(&cookie_pot, "\0"); else p_char = strsep(&cookie_pot, ","); ret = kstrtol(p_char, 10, &long_val); if (ret) goto err; value = long_val; if (offset >= ARRAY_SIZE(power_save_msg)) { ret = -EINVAL; goto err; } offset = array_index_nospec(offset, ARRAY_SIZE(power_save_msg)); *(p_uint + offset) = value; } /* update power save setting */ res = sip_smc_dram(SHARE_PAGE_TYPE_DDRDBG, DDRDBG_FUNC_UPDATE_POWERSAVE, ROCKCHIP_SIP_CONFIG_DRAM_DEBUG); if (res.a0) { pr_err("rockchip_sip_config_dram_debug error:%lx\n", res.a0); ret = -ENOMEM; goto err; } ret = count; err: vfree(buf); return ret; } static const struct file_operations powersave_proc_fops = { .open = powersave_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = powersave_proc_write, }; static int proc_powersave_init(void) { /* create dmcinfo file */ proc_create(PROC_DMCDBG_POWERSAVE, 0644, proc_dmcdbg_dir, &powersave_proc_fops); return 0; } static int drvodt_proc_show(struct seq_file *m, void *v) { struct arm_smccc_res res; struct drv_odt_info *p_drvodt; unsigned int *p_uint; unsigned int i; /* get drive strength and odt information */ res = sip_smc_dram(SHARE_PAGE_TYPE_DDRDBG, DDRDBG_FUNC_GET_DRVODT_INFO, ROCKCHIP_SIP_CONFIG_DRAM_DEBUG); if (res.a0) { seq_printf(m, "rockchip_sip_config_dram_debug error:%lx\n", res.a0); return -ENOMEM; } if (!dmcdbg_data.inited_flag) { seq_puts(m, "dmcdbg_data no int\n"); return -EPERM; } p_drvodt = (struct drv_odt_info *)dmcdbg_data.share_memory; seq_printf(m, "drv and odt information:\n" "\n" "[number]name: value (ohm)\n" ); p_uint = (unsigned int *)p_drvodt; for (i = 0; i < ARRAY_SIZE(drv_odt_msg); i++) { if (*(p_uint + (i * 3)) == DRV_ODT_UNKNOWN) seq_printf(m, "[%2d]%s: NULL (unknown) %c\n" , i, drv_odt_msg[i], (*(p_uint + (i * 3) + 2) == DRV_ODT_SUSPEND_FIX) ? '\0' : '*' ); else if (*(p_uint + (i * 3) + 1) == DRV_ODT_UNKNOWN) seq_printf(m, "[%2d]%s: %d (unknown) %c\n" , i, drv_odt_msg[i], *(p_uint + (i * 3)), (*(p_uint + (i * 3) + 2) == DRV_ODT_SUSPEND_FIX) ? '\0' : '*' ); else if (i < (ARRAY_SIZE(drv_odt_msg) - 2)) seq_printf(m, "[%2d]%s: %d (%d ohm) %c\n" , i, drv_odt_msg[i], *(p_uint + (i * 3)), *(p_uint + (i * 3) + 1), (*(p_uint + (i * 3) + 2) == DRV_ODT_SUSPEND_FIX) ? '\0' : '*' ); else seq_printf(m, "[%2d]%s: %d (%d %%) %c\n" , i, drv_odt_msg[i], *(p_uint + (i * 3)), *(p_uint + (i * 3) + 1), (*(p_uint + (i * 3) + 2) == DRV_ODT_SUSPEND_FIX) ? '\0' : '*' ); } seq_printf(m, "\n" "drvodt setting:\n" "echo number=value > /proc/dmcdbg/drvodt\n" "eg: set soc side ca drv up to 20\n" " echo 6=20 > /proc/dmcdbg/drvodt\n" "\n" "Support for setting multiple parameters at the same time.\n" "echo number=value,number=value,... > /proc/dmcdbg/drvodt\n" "eg: set soc side ca drv up and down to 20\n" " echo 6=20,7=20 > /proc/dmcdbg/drvodt\n" "Note: Please update both up and down at the same time.\n" " (*) mean unsupported setting value\n" ); return 0; } static int drvodt_proc_open(struct inode *inode, struct file *file) { return single_open(file, drvodt_proc_show, NULL); } static ssize_t drvodt_proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { struct arm_smccc_res res; struct drv_odt_info *p_drvodt; unsigned int *p_uint; char *buf, *cookie_pot, *p_char; int ret = 0; u32 loop, i, offset, value; long long_val; /* get buffer data */ buf = vzalloc(count); cookie_pot = buf; if (!cookie_pot) return -ENOMEM; if (copy_from_user(cookie_pot, buffer, count)) { ret = -EFAULT; goto err; } /* get drv and odt setting */ res = sip_smc_dram(SHARE_PAGE_TYPE_DDRDBG, DDRDBG_FUNC_GET_DRVODT_INFO, ROCKCHIP_SIP_CONFIG_DRAM_DEBUG); if (res.a0) { pr_err("rockchip_sip_config_dram_debug error:%lx\n", res.a0); ret = -ENOMEM; goto err; } if (!dmcdbg_data.inited_flag) { pr_err("dmcdbg_data no int\n"); ret = -EPERM; goto err; } p_drvodt = (struct drv_odt_info *)dmcdbg_data.share_memory; loop = 0; for (i = 0; i < count; i++) { if (*(cookie_pot + i) == '=') loop++; } p_uint = (unsigned int *)p_drvodt; for (i = 0; i < loop; i++) { p_char = strsep(&cookie_pot, "="); ret = kstrtol(p_char, 10, &long_val); if (ret) goto err; offset = long_val; if (i == (loop - 1)) p_char = strsep(&cookie_pot, "\0"); else p_char = strsep(&cookie_pot, ","); ret = kstrtol(p_char, 10, &long_val); if (ret) goto err; value = long_val; if (offset >= ARRAY_SIZE(drv_odt_msg)) { ret = -EINVAL; goto err; } offset *= 3; offset = array_index_nospec(offset, ARRAY_SIZE(drv_odt_msg) * 3); *(p_uint + offset) = value; } /* update power save setting */ res = sip_smc_dram(SHARE_PAGE_TYPE_DDRDBG, DDRDBG_FUNC_UPDATE_DRVODT, ROCKCHIP_SIP_CONFIG_DRAM_DEBUG); if (res.a0) { pr_err("rockchip_sip_config_dram_debug error:%lx\n", res.a0); ret = -ENOMEM; goto err; } ret = count; err: vfree(buf); return ret; } static const struct file_operations drvodt_proc_fops = { .open = drvodt_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = drvodt_proc_write, }; static int proc_drvodt_init(void) { /* create dmcinfo file */ proc_create(PROC_DMCDBG_DRVODT, 0644, proc_dmcdbg_dir, &drvodt_proc_fops); return 0; } static int skew_proc_show(struct seq_file *m, void *v) { struct arm_smccc_res res; unsigned int *p_uint; u32 group, i; /* get deskew information */ res = sip_smc_dram(SHARE_PAGE_TYPE_DDRDBG, DDRDBG_FUNC_GET_DESKEW_INFO, ROCKCHIP_SIP_CONFIG_DRAM_DEBUG); if (res.a0) { seq_printf(m, "rockchip_sip_config_dram_debug error:%lx\n", res.a0); return -ENOMEM; } if (!dmcdbg_data.inited_flag) { seq_puts(m, "dmcdbg_data no int\n"); return -EPERM; } seq_printf(m, "de-skew information:\n" "\n" "[group_number]name: value\n" ); for (group = 0; group < dmcdbg_data.skew_group_num; group++) { if (dmcdbg_data.skew_group[group].note != NULL) seq_printf(m, "%s\n" , dmcdbg_data.skew_group[group].note ); p_uint = (unsigned int *)dmcdbg_data.skew_group[group].p_skew_info; for (i = 0; i < dmcdbg_data.skew_group[group].skew_num; i++) seq_printf(m, "[%c%d_%d]%s: %d\n" , (i < 10) ? ' ' : '\0', group, i, dmcdbg_data.skew_group[group].p_skew_timing[i], *(p_uint + i) ); } seq_printf(m, "\n" "de-skew setting:\n" "echo group_number=value > /proc/dmcdbg/deskew\n" "eg: set a1_ddr3a14_de-skew to 8\n" " echo 0_1=8 > /proc/dmcdbg/deskew\n" "\n" "Support for setting multiple parameters simultaneously.\n" "echo group_number=value,group_number=value,... > /proc/dmcdbg/deskew\n" "eg:\n" " echo 0_1=8,1_2=8 > /proc/dmcdbg/deskew\n" ); return 0; } static int skew_proc_open(struct inode *inode, struct file *file) { return single_open(file, skew_proc_show, NULL); } static ssize_t skew_proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { struct arm_smccc_res res; unsigned int *p_uint; char *buf, *cookie_pot, *p_char; int ret = 0; u32 loop, i, offset_max, group, offset, value; long long_val; /* get buffer data */ buf = vzalloc(count); cookie_pot = buf; if (!cookie_pot) return -ENOMEM; if (copy_from_user(cookie_pot, buffer, count)) { ret = -EFAULT; goto err; } /* get skew setting */ res = sip_smc_dram(SHARE_PAGE_TYPE_DDRDBG, DDRDBG_FUNC_GET_DESKEW_INFO, ROCKCHIP_SIP_CONFIG_DRAM_DEBUG); if (res.a0) { pr_err("rockchip_sip_config_dram_debug error:%lx\n", res.a0); ret = -ENOMEM; goto err; } if (!dmcdbg_data.inited_flag) { pr_err("dmcdbg_data no int\n"); ret = -EPERM; goto err; } loop = 0; for (i = 0; i < count; i++) { if (*(cookie_pot + i) == '=') loop++; } for (i = 0; i < loop; i++) { p_char = strsep(&cookie_pot, "_"); ret = kstrtol(p_char, 10, &long_val); if (ret) goto err; group = long_val; p_char = strsep(&cookie_pot, "="); ret = kstrtol(p_char, 10, &long_val); if (ret) goto err; offset = long_val; if (i == (loop - 1)) p_char = strsep(&cookie_pot, "\0"); else p_char = strsep(&cookie_pot, ","); ret = kstrtol(p_char, 10, &long_val); if (ret) goto err; value = long_val; if (group >= dmcdbg_data.skew_group_num) { ret = -EINVAL; goto err; } group = array_index_nospec(group, dmcdbg_data.skew_group_num); p_uint = (unsigned int *)dmcdbg_data.skew_group[group].p_skew_info; offset_max = dmcdbg_data.skew_group[group].skew_num; if (offset >= offset_max) { ret = -EINVAL; goto err; } offset = array_index_nospec(offset, offset_max); *(p_uint + offset) = value; } /* update power save setting */ res = sip_smc_dram(SHARE_PAGE_TYPE_DDRDBG, DDRDBG_FUNC_UPDATE_DESKEW, ROCKCHIP_SIP_CONFIG_DRAM_DEBUG); if (res.a0) { pr_err("rockchip_sip_config_dram_debug error:%lx\n", res.a0); ret = -ENOMEM; goto err; } ret = count; err: vfree(buf); return ret; } static const struct file_operations skew_proc_fops = { .open = skew_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = skew_proc_write, }; static int proc_skew_init(void) { /* create dmcinfo file */ proc_create(PROC_DMCDBG_DESKEW, 0644, proc_dmcdbg_dir, &skew_proc_fops); return 0; } static int regsinfo_proc_show(struct seq_file *m, void *v) { struct arm_smccc_res res; struct registers_info *p_regsinfo; u32 i; res = sip_smc_dram(SHARE_PAGE_TYPE_DDRDBG, DDRDBG_FUNC_GET_REGISTERS_INFO, ROCKCHIP_SIP_CONFIG_DRAM_DEBUG); if (res.a0) { seq_printf(m, "rockchip_sip_config_dram_debug error:%lx\n", res.a0); return -ENOMEM; } if (!dmcdbg_data.inited_flag) { seq_puts(m, "dmcdbg_data no int\n"); return -EPERM; } p_regsinfo = (struct registers_info *)dmcdbg_data.share_memory; seq_printf(m, "registers base address information:\n" "\n" ); for (i = 0; i < p_regsinfo->regs_num; i++) { seq_printf(m, "%s=0x%x\n" , p_regsinfo->regs[i].regs_name, p_regsinfo->regs[i].regs_addr ); } return 0; } static int regsinfo_proc_open(struct inode *inode, struct file *file) { return single_open(file, regsinfo_proc_show, NULL); } static const struct file_operations regsinfo_proc_fops = { .open = regsinfo_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int proc_regsinfo_init(void) { /* create dmcinfo file */ proc_create(PROC_DMCDBG_REGS_INFO, 0644, proc_dmcdbg_dir, ®sinfo_proc_fops); return 0; } static void rv1126_get_skew_parameter(void) { struct skew_info_rv1126 *p_skew; u32 i; /* get skew parameters */ p_skew = (struct skew_info_rv1126 *)dmcdbg_data.share_memory; dmcdbg_data.skew_group_num = 5; /* ca_skew parameters */ dmcdbg_data.skew_group[0].p_skew_info = (unsigned int *)p_skew->ca_skew; dmcdbg_data.skew_group[0].skew_num = ARRAY_SIZE(rv1126_dts_ca_timing); for (i = 0; i < dmcdbg_data.skew_group[0].skew_num; i++) dmcdbg_data.skew_group[0].p_skew_timing[i] = (char *)rv1126_dts_ca_timing[i]; dmcdbg_data.skew_group[0].note = "(ca_skew: ddr4(pad_name)_ddr3_lpddr3_lpddr4_de-skew)"; /* cs0_a_skew parameters */ dmcdbg_data.skew_group[1].p_skew_info = (unsigned int *)p_skew->cs0_a_skew; dmcdbg_data.skew_group[1].skew_num = ARRAY_SIZE(rv1126_dts_cs0_a_timing); for (i = 0; i < dmcdbg_data.skew_group[1].skew_num; i++) dmcdbg_data.skew_group[1].p_skew_timing[i] = (char *)rv1126_dts_cs0_a_timing[i]; dmcdbg_data.skew_group[1].note = "(cs0_a_skew)"; /* cs0_b_skew parameters */ dmcdbg_data.skew_group[2].p_skew_info = (unsigned int *)p_skew->cs0_b_skew; dmcdbg_data.skew_group[2].skew_num = ARRAY_SIZE(rv1126_dts_cs0_b_timing); for (i = 0; i < dmcdbg_data.skew_group[2].skew_num; i++) dmcdbg_data.skew_group[2].p_skew_timing[i] = (char *)rv1126_dts_cs0_b_timing[i]; dmcdbg_data.skew_group[2].note = "(cs0_b_skew)"; /* cs1_a_skew parameters */ dmcdbg_data.skew_group[3].p_skew_info = (unsigned int *)p_skew->cs1_a_skew; dmcdbg_data.skew_group[3].skew_num = ARRAY_SIZE(rv1126_dts_cs1_a_timing); for (i = 0; i < dmcdbg_data.skew_group[3].skew_num; i++) dmcdbg_data.skew_group[3].p_skew_timing[i] = (char *)rv1126_dts_cs1_a_timing[i]; dmcdbg_data.skew_group[3].note = "(cs1_a_skew)"; /* cs1_b_skew parameters */ dmcdbg_data.skew_group[4].p_skew_info = (unsigned int *)p_skew->cs1_b_skew; dmcdbg_data.skew_group[4].skew_num = ARRAY_SIZE(rv1126_dts_cs1_b_timing); for (i = 0; i < dmcdbg_data.skew_group[3].skew_num; i++) dmcdbg_data.skew_group[4].p_skew_timing[i] = (char *)rv1126_dts_cs1_b_timing[i]; dmcdbg_data.skew_group[4].note = "(cs1_b_skew)"; } static __maybe_unused int rv1126_dmcdbg_init(struct platform_device *pdev, struct rockchip_dmcdbg *dmcdbg) { struct arm_smccc_res res; /* check ddr_debug_func version */ res = sip_smc_dram(0, DDRDBG_FUNC_GET_VERSION, ROCKCHIP_SIP_CONFIG_DRAM_DEBUG); dev_notice(&pdev->dev, "current ATF ddr_debug_func version 0x%lx.\n", res.a1); /* * [15:8] major version, [7:0] minor version * major version must match both kernel dmcdbg and ATF ddr_debug_func. */ if (res.a0 || res.a1 < 0x101 || ((res.a1 & 0xff00) != 0x100)) { dev_err(&pdev->dev, "version invalid,need update,the major version unmatch!\n"); return -ENXIO; } /* request share memory for pass parameter */ res = sip_smc_request_share_mem(DMCDBG_PAGE_NUMS, SHARE_PAGE_TYPE_DDRDBG); if (res.a0 != 0) { dev_err(&pdev->dev, "request share mem error\n"); return -ENOMEM; } dmcdbg_data.share_memory = (void __iomem *)res.a1; dmcdbg_data.inited_flag = 1; rv1126_get_skew_parameter(); /* create parent dir in /proc */ proc_dmcdbg_dir = proc_mkdir(PROC_DMCDBG_DIR_NAME, NULL); if (!proc_dmcdbg_dir) { dev_err(&pdev->dev, "create proc dir error!"); return -ENOENT; } proc_dmcinfo_init(); proc_powersave_init(); proc_drvodt_init(); proc_skew_init(); proc_regsinfo_init(); return 0; } static const struct of_device_id rockchip_dmcdbg_of_match[] = { { .compatible = "rockchip,rv1126-dmcdbg", .data = rv1126_dmcdbg_init}, { }, }; MODULE_DEVICE_TABLE(of, rockchip_dmcdbg_of_match); static int rockchip_dmcdbg_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rockchip_dmcdbg *data; const struct of_device_id *match; int (*init)(struct platform_device *pdev, struct rockchip_dmcdbg *data); int ret = 0; data = devm_kzalloc(dev, sizeof(struct rockchip_dmcdbg), GFP_KERNEL); if (!data) return -ENOMEM; data->dev = dev; /* match soc chip init */ match = of_match_node(rockchip_dmcdbg_of_match, pdev->dev.of_node); if (match) { init = match->data; if (init) { if (init(pdev, data)) return -EINVAL; } } return ret; } static struct platform_driver rockchip_dmcdbg_driver = { .probe = rockchip_dmcdbg_probe, .driver = { .name = "rockchip,dmcdbg", .of_match_table = rockchip_dmcdbg_of_match, }, }; module_platform_driver(rockchip_dmcdbg_driver); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("YouMin Chen "); MODULE_DESCRIPTION("rockchip dmc debug driver with devfreq framework");