342 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			342 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * Copyright (C) 2022 Rockchip Electronics Co., Ltd.
 | |
|  *
 | |
|  * Author: Andy Yan <Andy.yan@rock-chips.com>
 | |
|  */
 | |
| 
 | |
| #include <dt-bindings/mfd/rockchip-serdes.h>
 | |
| #include <linux/debugfs.h>
 | |
| 
 | |
| #include "hal/cru_api.h"
 | |
| #include "hal/pinctrl_api.h"
 | |
| #include "rkx110_x120.h"
 | |
| #include "rkx120_reg.h"
 | |
| #include "serdes_combphy.h"
 | |
| 
 | |
| static struct pattern_gen rkx120_pattern_gen[] = {
 | |
| 	{
 | |
| 		.name = "dsi",
 | |
| 		.base = RKX120_PATTERN_GEN_DSI_BASE,
 | |
| 		.link_src_reg = DES_GRF_SOC_CON2,
 | |
| 		.link_src_offset = 12,
 | |
| 		.type = RK_SERDES_DSI_TX0,
 | |
| 	}, {
 | |
| 		.name = "dual-lvds",
 | |
| 		.link_src_reg = DES_GRF_SOC_CON1,
 | |
| 		.link_src_offset = 14,
 | |
| 		.type = RK_SERDES_DUAL_LVDS_TX,
 | |
| 	}, {
 | |
| 		.name = "lvds0",
 | |
| 		.base = RKX120_PATTERN_GEN_LVDS0_BASE,
 | |
| 		.link_src_reg = DES_GRF_SOC_CON2,
 | |
| 		.link_src_offset = 13,
 | |
| 		.type = RK_SERDES_LVDS_TX0,
 | |
| 	}, {
 | |
| 		.name = "lvds1",
 | |
| 		.base = RKX120_PATTERN_GEN_LVDS1_BASE,
 | |
| 		.link_src_reg = DES_GRF_SOC_CON2,
 | |
| 		.link_src_offset = 14,
 | |
| 		.type = RK_SERDES_LVDS_TX1,
 | |
| 	},
 | |
| 	{ /* sentinel */ }
 | |
| };
 | |
| 
 | |
| static const struct rk_serdes_reg rkx120_regs[] = {
 | |
| 	{
 | |
| 		.name = "grf",
 | |
| 		.reg_base = RKX120_DES_GRF_BASE,
 | |
| 		.reg_len = 0x410,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "cru",
 | |
| 		.reg_base = RKX120_DES_CRU_BASE,
 | |
| 		.reg_len = 0xF04,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "dvp",
 | |
| 		.reg_base = RKX120_DVP_TX_BASE,
 | |
| 		.reg_len = 0x1d0,
 | |
| 	},
 | |
| 
 | |
| 	{
 | |
| 		.name = "grf_mipi0",
 | |
| 		.reg_base = RKX120_GRF_MIPI0_BASE,
 | |
| 		.reg_len = 0x600,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "grf_mipi1",
 | |
| 		.reg_base = RKX120_GRF_MIPI1_BASE,
 | |
| 		.reg_len = 0x600,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "mipi_lvds_phy0",
 | |
| 		.reg_base = RKX120_MIPI_LVDS_TX_PHY0_BASE,
 | |
| 		.reg_len = 0x70,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "mipi_lvds_phy1",
 | |
| 		.reg_base = RKX120_MIPI_LVDS_TX_PHY1_BASE,
 | |
| 		.reg_len = 0x70,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "dsihost",
 | |
| 		.reg_base = RKX120_DSI_TX_BASE,
 | |
| 		.reg_len = 0x190,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "gpio0",
 | |
| 		.reg_base = RKX120_GPIO0_TX_BASE,
 | |
| 		.reg_len = 0x80,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "gpio1",
 | |
| 		.reg_base = RKX120_GPIO1_TX_BASE,
 | |
| 		.reg_len = 0x80,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "csi_tx0",
 | |
| 		.reg_base = RKX120_CSI_TX0_BASE,
 | |
| 		.reg_len = 0x1D0,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "csi_tx1",
 | |
| 		.reg_base = RKX120_CSI_TX1_BASE,
 | |
| 		.reg_len = 0x1D0,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "rklink",
 | |
| 		.reg_base = RKX120_DES_RKLINK_BASE,
 | |
| 		.reg_len = 0xD4,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "pcs0",
 | |
| 		.reg_base = RKX120_DES_PCS0_BASE,
 | |
| 		.reg_len = 0x1c0,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "pcs1",
 | |
| 		.reg_base = RKX120_DES_PCS1_BASE,
 | |
| 		.reg_len = 0x1c0,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "pwm",
 | |
| 		.reg_base = RKX120_PWM_BASE,
 | |
| 		.reg_len = 0x100,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "pma0",
 | |
| 		.reg_base = RKX120_DES_PMA0_BASE,
 | |
| 		.reg_len = 0x100,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "pma1",
 | |
| 		.reg_base = RKX120_DES_PMA1_BASE,
 | |
| 		.reg_len = 0x100,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "dsi_pattern_gen",
 | |
| 		.reg_base = RKX120_PATTERN_GEN_DSI_BASE,
 | |
| 		.reg_len = 0x18,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "lvds0_pattern_gen",
 | |
| 		.reg_base = RKX120_PATTERN_GEN_LVDS0_BASE,
 | |
| 		.reg_len = 0x18,
 | |
| 	},
 | |
| 	{
 | |
| 		.name = "lvds1_pattern_gen",
 | |
| 		.reg_base = RKX120_PATTERN_GEN_LVDS1_BASE,
 | |
| 		.reg_len = 0x18,
 | |
| 	},
 | |
| 	{ /* sentinel */ }
 | |
| };
 | |
| 
 | |
| static int rkx120_reg_show(struct seq_file *s, void *v)
 | |
| {
 | |
| 	const struct rk_serdes_reg *regs = rkx120_regs;
 | |
| 	struct rk_serdes_chip *chip = s->private;
 | |
| 	struct rk_serdes *serdes = chip->serdes;
 | |
| 	struct i2c_client *client = chip->client;
 | |
| 	int i;
 | |
| 	u32 val = 0;
 | |
| 
 | |
| 	seq_printf(s, "rkx120_%s:\n", file_dentry(s->file)->d_iname);
 | |
| 
 | |
| 	while (regs->name) {
 | |
| 		if (!strcmp(regs->name, file_dentry(s->file)->d_iname))
 | |
| 			break;
 | |
| 		regs++;
 | |
| 	}
 | |
| 
 | |
| 	if (!regs->name)
 | |
| 		return -ENODEV;
 | |
| 
 | |
| 	for (i = 0; i <= regs->reg_len; i += 4) {
 | |
| 		serdes->i2c_read_reg(client, regs->reg_base + i, &val);
 | |
| 
 | |
| 		if (i % 16 == 0)
 | |
| 			seq_printf(s, "\n0x%04x:", i);
 | |
| 		seq_printf(s, " %08x", val);
 | |
| 	}
 | |
| 	seq_puts(s, "\n");
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static ssize_t rkx120_reg_write(struct file *file, const char __user *buf,
 | |
| 				size_t count, loff_t *ppos)
 | |
| {
 | |
| 	const struct rk_serdes_reg *regs = rkx120_regs;
 | |
| 	struct rk_serdes_chip *chip = file->f_path.dentry->d_inode->i_private;
 | |
| 	struct rk_serdes *serdes = chip->serdes;
 | |
| 	struct i2c_client *client = chip->client;
 | |
| 	u32 addr;
 | |
| 	u32 val;
 | |
| 	char kbuf[25];
 | |
| 	int ret;
 | |
| 
 | |
| 	if (count >= sizeof(kbuf))
 | |
| 		return -ENOSPC;
 | |
| 
 | |
| 	if (copy_from_user(kbuf, buf, count))
 | |
| 		return -EFAULT;
 | |
| 
 | |
| 	kbuf[count] = '\0';
 | |
| 
 | |
| 	ret = sscanf(kbuf, "%x%x", &addr, &val);
 | |
| 	if (ret != 2)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	while (regs->name) {
 | |
| 		if (!strcmp(regs->name, file_dentry(file)->d_iname))
 | |
| 			break;
 | |
| 		regs++;
 | |
| 	}
 | |
| 
 | |
| 	if (!regs->name)
 | |
| 		return -ENODEV;
 | |
| 
 | |
| 	addr += regs->reg_base;
 | |
| 
 | |
| 	serdes->i2c_write_reg(client, addr, val);
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static int rkx120_reg_open(struct inode *inode, struct file *file)
 | |
| {
 | |
| 	struct rk_serdes_chip *chip = inode->i_private;
 | |
| 
 | |
| 	return single_open(file, rkx120_reg_show, chip);
 | |
| }
 | |
| 
 | |
| static const struct file_operations rkx120_reg_fops = {
 | |
| 	.owner          = THIS_MODULE,
 | |
| 	.open           = rkx120_reg_open,
 | |
| 	.read           = seq_read,
 | |
| 	.write          = rkx120_reg_write,
 | |
| 	.llseek         = seq_lseek,
 | |
| 	.release        = single_release,
 | |
| };
 | |
| 
 | |
| void rkx120_debugfs_init(struct rk_serdes_chip *chip, struct dentry *dentry)
 | |
| {
 | |
| 	const struct rk_serdes_reg *regs = rkx120_regs;
 | |
| 	struct pattern_gen *pattern_gen;
 | |
| 	struct dentry *dir;
 | |
| 
 | |
| 	if (!IS_ENABLED(CONFIG_DEBUG_FS))
 | |
| 		return;
 | |
| 
 | |
| 	pattern_gen = devm_kmemdup(chip->serdes->dev, &rkx120_pattern_gen,
 | |
| 				   sizeof(rkx120_pattern_gen), GFP_KERNEL);
 | |
| 	if (!pattern_gen)
 | |
| 		return;
 | |
| 
 | |
| 	dir = debugfs_create_dir("registers", dentry);
 | |
| 	if (!IS_ERR(dir)) {
 | |
| 		while (regs->name) {
 | |
| 			debugfs_create_file(regs->name, 0600, dir, chip, &rkx120_reg_fops);
 | |
| 			regs++;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	dir = debugfs_create_dir("pattern_gen", dentry);
 | |
| 	if (!IS_ERR(dir)) {
 | |
| 		while (pattern_gen->name) {
 | |
| 			rkx110_x120_pattern_gen_debugfs_create_file(pattern_gen, chip, dir);
 | |
| 			pattern_gen++;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int rkx120_rgb_tx_iomux_cfg(struct rk_serdes *serdes, struct rk_serdes_route *route,
 | |
| 				   u8 remote_id)
 | |
| {
 | |
| 	struct i2c_client *client = serdes->chip[remote_id].client;
 | |
| 	uint32_t pins;
 | |
| 
 | |
| 	pins = RK_SERDES_GPIO_PIN_C0 | RK_SERDES_GPIO_PIN_C1 | RK_SERDES_GPIO_PIN_C2 |
 | |
| 	       RK_SERDES_GPIO_PIN_C3 | RK_SERDES_GPIO_PIN_C4 | RK_SERDES_GPIO_PIN_C5 |
 | |
| 	       RK_SERDES_GPIO_PIN_C6 | RK_SERDES_GPIO_PIN_C7;
 | |
| 	serdes->set_hwpin(serdes, client, PIN_RKX120, RK_SERDES_DES_GPIO_BANK0, pins,
 | |
| 			  RK_SERDES_PIN_CONFIG_MUX_FUNC1);
 | |
| 
 | |
| 	pins = RK_SERDES_GPIO_PIN_A0 | RK_SERDES_GPIO_PIN_A1 | RK_SERDES_GPIO_PIN_A2 |
 | |
| 	       RK_SERDES_GPIO_PIN_A3 | RK_SERDES_GPIO_PIN_A4 | RK_SERDES_GPIO_PIN_A5 |
 | |
| 	       RK_SERDES_GPIO_PIN_A6 | RK_SERDES_GPIO_PIN_A7 | RK_SERDES_GPIO_PIN_B0 |
 | |
| 	       RK_SERDES_GPIO_PIN_B1 | RK_SERDES_GPIO_PIN_B2 | RK_SERDES_GPIO_PIN_B3 |
 | |
| 	       RK_SERDES_GPIO_PIN_B4 | RK_SERDES_GPIO_PIN_B5 | RK_SERDES_GPIO_PIN_B6 |
 | |
| 	       RK_SERDES_GPIO_PIN_B7 | RK_SERDES_GPIO_PIN_C0 | RK_SERDES_GPIO_PIN_C1 |
 | |
| 	       RK_SERDES_GPIO_PIN_C2 | RK_SERDES_GPIO_PIN_C3;
 | |
| 	serdes->set_hwpin(serdes, client, PIN_RKX120, RK_SERDES_DES_GPIO_BANK1, pins,
 | |
| 			  RK_SERDES_PIN_CONFIG_MUX_FUNC1);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int rkx120_rgb_tx_enable(struct rk_serdes *serdes, struct rk_serdes_route *route,
 | |
| 			 u8 remote_id)
 | |
| {
 | |
| 	rkx120_rgb_tx_iomux_cfg(serdes, route, remote_id);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int rkx120_lvds_tx_enable(struct rk_serdes *serdes, struct rk_serdes_route *route, u8 remote_id,
 | |
| 			  u8 phy_id)
 | |
| {
 | |
| 	struct i2c_client *client = serdes->chip[remote_id].client;
 | |
| 	struct rk_serdes_panel *sd_panel = container_of(route, struct rk_serdes_panel, route);
 | |
| 	struct rkx120_combtxphy *combtxphy = &sd_panel->combtxphy;
 | |
| 
 | |
| 	if (phy_id) {
 | |
| 		serdes->i2c_write_reg(client, DES_GRF_SOC_CON7, 0xfff00630);
 | |
| 		serdes->i2c_write_reg(client, DES_GRF_SOC_CON2, 0x00200020);
 | |
| 	} else {
 | |
| 		serdes->i2c_write_reg(client, DES_GRF_SOC_CON6, 0x0fff0063);
 | |
| 		serdes->i2c_write_reg(client, DES_GRF_SOC_CON2, 0x00040004);
 | |
| 	}
 | |
| 
 | |
| 	rkx120_combtxphy_set_mode(combtxphy, COMBTX_PHY_MODE_VIDEO_LVDS);
 | |
| 	if (route->remote0_port0 & RK_SERDES_DUAL_LVDS_TX)
 | |
| 		rkx120_combtxphy_set_rate(combtxphy, route->vm.pixelclock * 7 / 2);
 | |
| 	else
 | |
| 		rkx120_combtxphy_set_rate(combtxphy, route->vm.pixelclock * 7);
 | |
| 	rkx120_combtxphy_power_on(serdes, combtxphy, remote_id, phy_id);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int rkx120_lvds_tx_disable(struct rk_serdes *serdes, struct rk_serdes_route *route, u8 remote_id,
 | |
| 			   u8 phy_id)
 | |
| {
 | |
| 	struct rk_serdes_panel *sd_panel = container_of(route, struct rk_serdes_panel, route);
 | |
| 	struct rkx120_combtxphy *combtxphy = &sd_panel->combtxphy;
 | |
| 
 | |
| 	rkx120_combtxphy_power_off(serdes, combtxphy, remote_id, phy_id);
 | |
| 
 | |
| 	return 0;
 | |
| }
 |