// SPDX-License-Identifier: GPL-2.0 /* Phytium display drm driver * * Copyright (C) 2021-2023, Phytium Technology Co., Ltd. */ #include #include #include #include "phytium_display_drv.h" #include "phytium_dp.h" #include "phytium_reg.h" const char *const mem_state[PHYTIUM_MEM_STATE_TYPE_COUNT] = { "Memory_Vram_Total", "Memory_Vram_Alloc", "Memory_System_Carveout_Total", "Memory_System_Carveout_Alloc", "Memory_System_Alloc", }; static ssize_t phytium_dp_register_write(struct file *filp, const char __user *ubuf, size_t len, loff_t *ppos) { char tmp[16]; if (len >= sizeof(tmp)) return -EINVAL; memset(tmp, 0, sizeof(tmp)); if (copy_from_user(tmp, ubuf, len)) return -EFAULT; tmp[len] = '\0'; return len; } static int phytium_dp_register_show(struct seq_file *m, void *data) { struct drm_connector *connector = m->private; struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); struct drm_device *dev = phytium_dp->dev; struct phytium_display_private *priv = dev->dev_private; int port = phytium_dp->port; uint32_t group_offset = priv->dp_reg_base[port]; seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_M_VID, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_M_VID)); seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_N_VID, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_N_VID)); seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_TRANSFER_UNIT_SIZE, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_TRANSFER_UNIT_SIZE)); seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_DATA_COUNT, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_DATA_COUNT)); seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_MAIN_LINK_HTOTAL, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_HTOTAL)); seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_MAIN_LINK_HRES, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_HRES)); seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_MAIN_LINK_HSWIDTH, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_HSWIDTH)); seq_printf(m, "addr:h0x%08x h0x%08x\n", PHYTIUM_DP_MAIN_LINK_HSTART, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_HSTART)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_MAIN_LINK_VTOTAL, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_VTOTAL)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_MAIN_LINK_VRES, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_VRES)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_MAIN_LINK_VSWIDTH, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_VSWIDTH)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_MAIN_LINK_VSTART, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_VSTART)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_MAIN_LINK_POLARITY, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_POLARITY)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_MAIN_LINK_MISC0, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_MISC0)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_MAIN_LINK_MISC1, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_MAIN_LINK_MISC1)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_USER_SYNC_POLARITY, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_USER_SYNC_POLARITY)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_VIDEO_STREAM_ENABLE, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_VIDEO_STREAM_ENABLE)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SECONDARY_STREAM_ENABLE, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SECONDARY_STREAM_ENABLE)); seq_puts(m, "audio:\n"); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_INPUT_SELECT, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_INPUT_SELECT)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_DIRECT_CLKDIV, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_DIRECT_CLKDIV)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_CHANNEL_COUNT, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_CHANNEL_COUNT)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_CHANNEL_MAP, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_CHANNEL_MAP)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_DATA_WINDOW, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_DATA_WINDOW)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_CS_CATEGORY_CODE, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_CS_CATEGORY_CODE)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_MAUD, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_MAUD)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_NAUD, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_NAUD)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_CLOCK_MODE, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_CLOCK_MODE)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_CS_SOURCE_FORMAT, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_CS_SOURCE_FORMAT)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_CS_LENGTH_ORIG_FREQ, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_CS_LENGTH_ORIG_FREQ)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_CS_FREQ_CLOCK_ACCURACY, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_CS_FREQ_CLOCK_ACCURACY)); seq_printf(m, "addr:h'0x%08x h'0x%08x\n", PHYTIUM_DP_SEC_AUDIO_ENABLE, phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_AUDIO_ENABLE)); return 0; } static int phytium_dp_register_open(struct inode *inode, struct file *file) { return single_open(file, phytium_dp_register_show, inode->i_private); } static const struct file_operations phytium_dp_register_fops = { .owner = THIS_MODULE, .open = phytium_dp_register_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = phytium_dp_register_write, }; static ssize_t phytium_dp_trigger_train_fail_write(struct file *filp, const char __user *ubuf, size_t len, loff_t *ppos) { struct seq_file *m = filp->private_data; struct drm_connector *connector = m->private; struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); char tmp[16]; if (len >= sizeof(tmp)) return -EINVAL; memset(tmp, 0, sizeof(tmp)); if (copy_from_user(tmp, ubuf, len)) return -EFAULT; tmp[len] = '\0'; if (kstrtouint(tmp, 10, &phytium_dp->trigger_train_fail) != 0) return -EINVAL; return len; } static int phytium_dp_trigger_train_fail_show(struct seq_file *m, void *data) { struct drm_connector *connector = m->private; struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); seq_printf(m, "trigger_train_fail: %d\n", phytium_dp->trigger_train_fail); seq_printf(m, "train_retry_count: %d\n", phytium_dp->train_retry_count); return 0; } static int phytium_dp_trigger_train_fail_open(struct inode *inode, struct file *file) { return single_open(file, phytium_dp_trigger_train_fail_show, inode->i_private); } static const struct file_operations phytium_dp_trigger_train_fail_fops = { .owner = THIS_MODULE, .open = phytium_dp_trigger_train_fail_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = phytium_dp_trigger_train_fail_write, }; static int phytium_edp_backlight_show(struct seq_file *m, void *data) { struct drm_connector *connector = m->private; struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); if (!phytium_dp->is_edp) return -ENODEV; mutex_lock(&phytium_dp->panel.panel_lock); seq_printf(m, "backlight: %s\n", phytium_dp->panel.backlight_enabled?"enabled":"disabled"); mutex_unlock(&phytium_dp->panel.panel_lock); return 0; } static int phytium_edp_backlight_open(struct inode *inode, struct file *file) { return single_open(file, phytium_edp_backlight_show, inode->i_private); } static const struct file_operations phytium_edp_backlight_fops = { .owner = THIS_MODULE, .open = phytium_edp_backlight_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int phytium_edp_power_show(struct seq_file *m, void *data) { struct drm_connector *connector = m->private; struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); if (!phytium_dp->is_edp) return -ENODEV; mutex_lock(&phytium_dp->panel.panel_lock); seq_printf(m, "power: %s\n", phytium_dp->panel.power_enabled?"enabled":"disabled"); mutex_unlock(&phytium_dp->panel.panel_lock); return 0; } static int phytium_edp_power_open(struct inode *inode, struct file *file) { return single_open(file, phytium_edp_power_show, inode->i_private); } static const struct file_operations phytium_edp_power_fops = { .owner = THIS_MODULE, .open = phytium_edp_power_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; struct dpcd_block { /* DPCD dump start address. */ unsigned int offset; /* DPCD dump end address, inclusive. If unset, .size will be used. */ unsigned int end; /* DPCD dump size. Used if .end is unset. If unset, defaults to 1. */ size_t size; /* Only valid for eDP. */ bool edp; }; static const struct dpcd_block phytium_dpcd_debug[] = { { .offset = DP_DPCD_REV, .size = DP_RECEIVER_CAP_SIZE }, { .offset = DP_PSR_SUPPORT, .end = DP_PSR_CAPS }, { .offset = DP_DOWNSTREAM_PORT_0, .size = 16 }, { .offset = DP_LINK_BW_SET, .end = DP_EDP_CONFIGURATION_SET }, { .offset = DP_SINK_COUNT, .end = DP_ADJUST_REQUEST_LANE2_3 }, { .offset = DP_SET_POWER }, { .offset = DP_EDP_DPCD_REV }, { .offset = DP_EDP_GENERAL_CAP_1, .end = DP_EDP_GENERAL_CAP_3 }, { .offset = DP_EDP_DISPLAY_CONTROL_REGISTER, .end = DP_EDP_BACKLIGHT_FREQ_CAP_MAX_LSB }, { .offset = DP_EDP_DBC_MINIMUM_BRIGHTNESS_SET, .end = DP_EDP_DBC_MAXIMUM_BRIGHTNESS_SET }, { .offset = DP_DEVICE_SERVICE_IRQ_VECTOR, .size = 1 }, { .offset = DP_TEST_REQUEST, .end = DP_TEST_PATTERN }, }; static int phytium_dpcd_show(struct seq_file *m, void *data) { struct drm_connector *connector = m->private; struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); uint8_t buf[16], i; ssize_t err; if (connector->status != connector_status_connected) return -ENODEV; for (i = 0; i < ARRAY_SIZE(phytium_dpcd_debug); i++) { const struct dpcd_block *b = &phytium_dpcd_debug[i]; size_t size = b->end ? b->end - b->offset + 1 : (b->size ?: 1); if (WARN_ON(size > sizeof(buf))) continue; err = drm_dp_dpcd_read(&phytium_dp->aux, b->offset, buf, size); if (err <= 0) { DRM_ERROR("dpcd read (%zu bytes at %u) failed (%zd)\n", size, b->offset, err); continue; } seq_printf(m, "%04x: %*ph\n", b->offset, (int) size, buf); } return 0; } static int phytium_dpcd_open(struct inode *inode, struct file *file) { return single_open(file, phytium_dpcd_show, inode->i_private); } static const struct file_operations phytium_dpcd_fops = { .owner = THIS_MODULE, .open = phytium_dpcd_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static ssize_t phytium_dp_state_write(struct file *filp, const char __user *ubuf, size_t len, loff_t *ppos) { char tmp[16]; if (len >= sizeof(tmp)) return -EINVAL; memset(tmp, 0, sizeof(tmp)); if (copy_from_user(tmp, ubuf, len)) return -EFAULT; tmp[len] = '\0'; return len; } static int phytium_dp_state_show(struct seq_file *m, void *data) { struct drm_connector *connector = m->private; struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); seq_printf(m, "port number: %d\n", phytium_dp->port); seq_printf(m, "source_max_lane_count: %d\n", phytium_dp->source_max_lane_count); seq_printf(m, "max_source_rates: %d\n", phytium_dp->source_rates[phytium_dp->num_source_rates-1]); if (connector->status == connector_status_connected) { seq_printf(m, "sink_max_lane_count: %d\n", phytium_dp->sink_max_lane_count); seq_printf(m, "max_sink_rates: %d\n", phytium_dp->sink_rates[phytium_dp->num_sink_rates-1]); seq_printf(m, "link_rate: %d\n", phytium_dp->link_rate); seq_printf(m, "link_lane_count: %d\n", phytium_dp->link_lane_count); seq_printf(m, "train_set[0]: %d\n", phytium_dp->train_set[0]); seq_printf(m, "has_audio: %s\n", phytium_dp->has_audio?"yes":"no"); } return 0; } static int phytium_dp_state_open(struct inode *inode, struct file *file) { return single_open(file, phytium_dp_state_show, inode->i_private); } static const struct file_operations phytium_dp_state_fops = { .owner = THIS_MODULE, .open = phytium_dp_state_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = phytium_dp_state_write, }; static const struct phytium_debugfs_files { const char *name; const struct file_operations *fops; } phytium_debugfs_connector_files[] = { {"dp_state", &phytium_dp_state_fops}, {"dpcd", &phytium_dpcd_fops}, {"dp_register", &phytium_dp_register_fops}, {"dp_trigger_train_fail", &phytium_dp_trigger_train_fail_fops}, }; static const struct phytium_debugfs_files phytium_edp_debugfs_connector_files[] = { {"edp_power", &phytium_edp_power_fops}, {"edp_backlight", &phytium_edp_backlight_fops}, }; int phytium_debugfs_connector_add(struct drm_connector *connector) { struct dentry *root = connector->debugfs_entry; struct dentry *ent; int i; struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector); if (!root) return -ENODEV; for (i = 0; i < ARRAY_SIZE(phytium_debugfs_connector_files); i++) { ent = debugfs_create_file(phytium_debugfs_connector_files[i].name, 0644, root, connector, phytium_debugfs_connector_files[i].fops); if (!ent) return -ENOMEM; } if (phytium_dp->is_edp) for (i = 0; i < ARRAY_SIZE(phytium_edp_debugfs_connector_files); i++) { ent = debugfs_create_file(phytium_edp_debugfs_connector_files[i].name, 0644, root, connector, phytium_edp_debugfs_connector_files[i].fops); if (!ent) return -ENOMEM; } return 0; } static int phytium_mem_state_show(struct seq_file *m, void *data) { struct phytium_display_private *priv = m->private; uint8_t i; for (i = 0; i < ARRAY_SIZE(mem_state); i++) seq_printf(m, "%-34s %10lld\n", mem_state[i], priv->mem_state[i]); return 0; } static int phytium_mem_state_open(struct inode *inode, struct file *file) { return single_open(file, phytium_mem_state_show, inode->i_private); } static const struct file_operations phytium_mem_state_fops = { .owner = THIS_MODULE, .open = phytium_mem_state_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct phytium_debugfs_files phytium_debugfs_display_files[] = { {"mem_state", &phytium_mem_state_fops}, }; int phytium_debugfs_display_register(struct phytium_display_private *priv) { struct drm_minor *minor = priv->dev->primary; struct dentry *root = minor->debugfs_root; struct dentry *ent; if (!root) return -ENODEV; ent = debugfs_create_file(phytium_debugfs_display_files[0].name, 0644, root, priv, phytium_debugfs_display_files[0].fops); if (!ent) return -ENOMEM; return 0; }