368 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			368 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * Maxim Quad GMSL Deserializer Test Pattern Driver
 | |
|  *
 | |
|  * Copyright (C) 2023 Rockchip Electronics Co., Ltd.
 | |
|  *
 | |
|  * Author: Cai Wenzhong <cwz@rock-chips.com>
 | |
|  *
 | |
|  */
 | |
| #include "maxim4c_api.h"
 | |
| 
 | |
| #define PATTERN_WIDTH		1920
 | |
| #define PATTERN_HEIGHT		1080
 | |
| 
 | |
| /* pattern generator: 0 or 1 */
 | |
| enum {
 | |
| 	PATTERN_GENERATOR_0 = 0,
 | |
| 	PATTERN_GENERATOR_1,
 | |
| };
 | |
| 
 | |
| /* pattern mode: checkerboard or gradient */
 | |
| enum {
 | |
| 	PATTERN_CHECKERBOARD = 0,
 | |
| 	PATTERN_GRADIENT,
 | |
| };
 | |
| 
 | |
| /* pattern pclk: 25M or 75M 0r 150M or 375M */
 | |
| enum {
 | |
| 	PATTERN_PCLK_25M = 0,
 | |
| 	PATTERN_PCLK_75M,
 | |
| 	PATTERN_PCLK_150M,
 | |
| 	PATTERN_PCLK_375M,
 | |
| };
 | |
| 
 | |
| static const struct maxim4c_mode maxim4c_pattern_mode = {
 | |
| 	.width = PATTERN_WIDTH,
 | |
| 	.height = PATTERN_HEIGHT,
 | |
| 	.max_fps = {
 | |
| 		.numerator = 10000,
 | |
| 		.denominator = 300000,
 | |
| 	},
 | |
| 	.link_freq_idx = 15,
 | |
| 	.bus_fmt = MEDIA_BUS_FMT_RGB888_1X24,
 | |
| 	.bpp = 24,
 | |
| #if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE
 | |
| 	.vc[PAD0] = 0,
 | |
| #else
 | |
| 	.vc[PAD0] = V4L2_MBUS_CSI2_CHANNEL_0,
 | |
| #endif
 | |
| 	/* crop rect */
 | |
| 	.crop_rect = {
 | |
| 		.left = 0,
 | |
| 		.width = PATTERN_WIDTH,
 | |
| 		.top = 0,
 | |
| 		.height = PATTERN_HEIGHT,
 | |
| 	},
 | |
| };
 | |
| 
 | |
| /* VPG0 or VPG1 register */
 | |
| #define VPGx_REG(x, reg)	((reg) + 0x30 * (x))
 | |
| 
 | |
| int maxim4c_pattern_enable(maxim4c_t *maxim4c, bool enable)
 | |
| {
 | |
| 	struct i2c_client *client = maxim4c->client;
 | |
| 	struct device *dev = &client->dev;
 | |
| 	struct maxim4c_pattern *pattern = &maxim4c->pattern;
 | |
| 	u32 vpgx;
 | |
| 	u32 pattern_mode;
 | |
| 	u8 reg_mask = 0, reg_val = 0;
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	dev_info(dev, "video pattern: enable = %d\n", enable);
 | |
| 
 | |
| 	vpgx = pattern->pattern_generator;
 | |
| 	pattern_mode = pattern->pattern_mode;
 | |
| 
 | |
| 	reg_mask = BIT(5) | BIT(4);
 | |
| 	if (pattern_mode == PATTERN_CHECKERBOARD) {
 | |
| 		/* Generate checkerboard pattern. */
 | |
| 		reg_val = enable ? BIT(4) : 0;
 | |
| 	} else {
 | |
| 		/* Generate gradient pattern. */
 | |
| 		reg_val = enable ? BIT(5) : 0;
 | |
| 	}
 | |
| 	ret = maxim4c_i2c_update_reg(client,
 | |
| 			VPGx_REG(vpgx, 0x1051), reg_mask, reg_val);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| EXPORT_SYMBOL(maxim4c_pattern_enable);
 | |
| 
 | |
| static int maxim4c_pattern_previnit(maxim4c_t *maxim4c)
 | |
| {
 | |
| 	struct i2c_client *client = maxim4c->client;
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	// All links disable at beginning.
 | |
| 	ret = maxim4c_i2c_write_reg(client, 0x0006, 0xF0);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	// video pipe disable.
 | |
| 	ret = maxim4c_i2c_write_reg(client, 0x00F4, 0x00);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	// MIPI CSI output disable.
 | |
| 	ret = maxim4c_i2c_write_reg(client, 0x040B, 0x00);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	// MIPI TXPHY standby
 | |
| 	ret = maxim4c_i2c_update_reg(client, 0x08A2, 0xF0, 0x00);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int maxim4c_pattern_config(maxim4c_t *maxim4c)
 | |
| {
 | |
| 	const u32 h_active = PATTERN_WIDTH;
 | |
| 	const u32 h_fp = 88;
 | |
| 	const u32 h_sw = 44;
 | |
| 	const u32 h_bp = 148;
 | |
| 	const u32 h_tot = h_active + h_fp + h_sw + h_bp;
 | |
| 
 | |
| 	const u32 v_active = PATTERN_HEIGHT;
 | |
| 	const u32 v_fp = 4;
 | |
| 	const u32 v_sw = 5;
 | |
| 	const u32 v_bp = 36;
 | |
| 	const u32 v_tot = v_active + v_fp + v_sw + v_bp;
 | |
| 
 | |
| 	struct i2c_client *client = maxim4c->client;
 | |
| 	struct maxim4c_pattern *pattern = &maxim4c->pattern;
 | |
| 	u32 vpgx;
 | |
| 	u32 pattern_mode;
 | |
| 	u32 pattern_pclk;
 | |
| 	u16 reg_addr = 0;
 | |
| 	u8 reg_mask = 0, reg_val = 0;
 | |
| 	int ret = 0, i = 0;
 | |
| 
 | |
| 	vpgx = pattern->pattern_generator;
 | |
| 	pattern_mode = pattern->pattern_mode;
 | |
| 	pattern_pclk = pattern->pattern_pclk;
 | |
| 
 | |
| 	// PATGEN_MODE = 0, Pattern generator disabled
 | |
| 	//	use video from the serializer input
 | |
| 	ret |= maxim4c_i2c_update_reg(client,
 | |
| 			VPGx_REG(vpgx, 0x1051), BIT(5) | BIT(4), 0x00);
 | |
| 
 | |
| 	/* Pattern PCLK:
 | |
| 	 *	0b00 - 25MHz
 | |
| 	 *	0b01 - 75MHz
 | |
| 	 *	0b1x - (PATGEN_CLK_SRC: 0 - 150MHz, 1 - 375MHz).
 | |
| 	 */
 | |
| 	pattern_pclk = (pattern_pclk & 0x03);
 | |
| 	ret |= maxim4c_i2c_write_reg(client, 0x0009, pattern_pclk);
 | |
| 	if (pattern_pclk >= PATTERN_PCLK_150M) {
 | |
| 		reg_mask = BIT(7);
 | |
| 		if (pattern_pclk == PATTERN_PCLK_375M)
 | |
| 			reg_val = BIT(7);
 | |
| 		else
 | |
| 			reg_val = 0;
 | |
| 
 | |
| 		if (vpgx == PATTERN_GENERATOR_0) {
 | |
| 			for (i = 0; i < 4; i++) {
 | |
| 				reg_addr = 0x01DC + i * 0x20;
 | |
| 				ret |= maxim4c_i2c_update_reg(client,
 | |
| 						reg_addr, reg_mask, reg_val);
 | |
| 			}
 | |
| 		} else {
 | |
| 			for (i = 0; i < 4; i++) {
 | |
| 				reg_addr = 0x025C + i * 0x20;
 | |
| 				ret |= maxim4c_i2c_update_reg(client,
 | |
| 						reg_addr, reg_mask, reg_val);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* Configure Video Timing Generator for 1920x1080 @ 30 fps. */
 | |
| 	// VS_DLY = 0
 | |
| 	ret |= maxim4c_i2c_write(client,
 | |
| 			VPGx_REG(vpgx, 0x1052), MAXIM4C_I2C_REG_ADDR_16BITS,
 | |
| 			MAXIM4C_I2C_REG_VALUE_24BITS, 0x000000);
 | |
| 	// VS_HIGH = Vsw * Htot
 | |
| 	ret |= maxim4c_i2c_write(client,
 | |
| 			VPGx_REG(vpgx, 0x1055), MAXIM4C_I2C_REG_ADDR_16BITS,
 | |
| 			MAXIM4C_I2C_REG_VALUE_24BITS, v_sw * h_tot);
 | |
| 	// VS_LOW = (Vactive + Vfp + Vbp) * Htot
 | |
| 	ret |= maxim4c_i2c_write(client,
 | |
| 			VPGx_REG(vpgx, 0x1058), MAXIM4C_I2C_REG_ADDR_16BITS,
 | |
| 			MAXIM4C_I2C_REG_VALUE_24BITS, (v_active + v_fp + v_bp) * h_tot);
 | |
| 	// V2H = VS_DLY
 | |
| 	ret |= maxim4c_i2c_write(client,
 | |
| 			VPGx_REG(vpgx, 0x105b), MAXIM4C_I2C_REG_ADDR_16BITS,
 | |
| 			MAXIM4C_I2C_REG_VALUE_24BITS, 0x000000);
 | |
| 	// HS_HIGH = Hsw
 | |
| 	ret |= maxim4c_i2c_write(client,
 | |
| 			VPGx_REG(vpgx, 0x105e), MAXIM4C_I2C_REG_ADDR_16BITS,
 | |
| 			MAXIM4C_I2C_REG_VALUE_16BITS, h_sw);
 | |
| 	// HS_LOW = Hactive + Hfp + Hbp
 | |
| 	ret |= maxim4c_i2c_write(client,
 | |
| 			VPGx_REG(vpgx, 0x1060), MAXIM4C_I2C_REG_ADDR_16BITS,
 | |
| 			MAXIM4C_I2C_REG_VALUE_16BITS, h_active + h_fp + h_bp);
 | |
| 	// HS_CNT = Vtot
 | |
| 	ret |= maxim4c_i2c_write(client,
 | |
| 			VPGx_REG(vpgx, 0x1062), MAXIM4C_I2C_REG_ADDR_16BITS,
 | |
| 			MAXIM4C_I2C_REG_VALUE_16BITS, v_tot);
 | |
| 	// V2D = VS_DLY + Htot * (Vsw + Vbp) + (Hsw + Hbp)
 | |
| 	ret |= maxim4c_i2c_write(client,
 | |
| 			VPGx_REG(vpgx, 0x1064),	MAXIM4C_I2C_REG_ADDR_16BITS,
 | |
| 			MAXIM4C_I2C_REG_VALUE_24BITS, h_tot * (v_sw + v_bp) + (h_sw + h_bp));
 | |
| 	// DE_HIGH = Hactive
 | |
| 	ret |= maxim4c_i2c_write(client,
 | |
| 			VPGx_REG(vpgx, 0x1067),	MAXIM4C_I2C_REG_ADDR_16BITS,
 | |
| 			MAXIM4C_I2C_REG_VALUE_16BITS, h_active);
 | |
| 	// DE_LOW = Hfp + Hsw + Hbp
 | |
| 	ret |= maxim4c_i2c_write(client,
 | |
| 			VPGx_REG(vpgx, 0x1069),	MAXIM4C_I2C_REG_ADDR_16BITS,
 | |
| 			MAXIM4C_I2C_REG_VALUE_16BITS, h_fp + h_sw + h_bp);
 | |
| 	// DE_CNT = Vactive
 | |
| 	ret |= maxim4c_i2c_write(client,
 | |
| 			VPGx_REG(vpgx, 0x106b),	MAXIM4C_I2C_REG_ADDR_16BITS,
 | |
| 			MAXIM4C_I2C_REG_VALUE_16BITS, v_active);
 | |
| 
 | |
| 	/* Generate VS, HS and DE in free-running mode, Invert HS and VS. */
 | |
| 	ret |= maxim4c_i2c_write_reg(client, VPGx_REG(vpgx, 0x1050), 0xfb);
 | |
| 
 | |
| 	/* Configure Video Pattern Generator. */
 | |
| 	if (pattern_mode == PATTERN_CHECKERBOARD) {
 | |
| 		/* Set checkerboard pattern size. */
 | |
| 		ret |= maxim4c_i2c_write(client,
 | |
| 			VPGx_REG(vpgx, 0x1074),	MAXIM4C_I2C_REG_ADDR_16BITS,
 | |
| 			MAXIM4C_I2C_REG_VALUE_24BITS, 0x3c3c3c);
 | |
| 
 | |
| 		/* Set checkerboard pattern colors. */
 | |
| 		ret |= maxim4c_i2c_write(client,
 | |
| 			VPGx_REG(vpgx, 0x106e), MAXIM4C_I2C_REG_ADDR_16BITS,
 | |
| 			MAXIM4C_I2C_REG_VALUE_24BITS, 0xfecc00);
 | |
| 		ret |= maxim4c_i2c_write(client,
 | |
| 			VPGx_REG(vpgx, 0x1071), MAXIM4C_I2C_REG_ADDR_16BITS,
 | |
| 			MAXIM4C_I2C_REG_VALUE_24BITS, 0x006aa7);
 | |
| 	} else {
 | |
| 		/* Set gradient increment. */
 | |
| 		ret |= maxim4c_i2c_write_reg(client, VPGx_REG(vpgx, 0x106d), 0x10);
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| int maxim4c_pattern_support_mode_init(maxim4c_t *maxim4c)
 | |
| {
 | |
| 	struct device *dev = &maxim4c->client->dev;
 | |
| 	struct maxim4c_mode *supported_mode = NULL;
 | |
| 
 | |
| 	dev_info(dev, "=== maxim4c pattern support mode init ===\n");
 | |
| 
 | |
| 	maxim4c->cfg_modes_num = 1;
 | |
| 	maxim4c->cur_mode = &maxim4c->supported_mode;
 | |
| 	supported_mode = &maxim4c->supported_mode;
 | |
| 
 | |
| 	// init using def mode
 | |
| 	memcpy(supported_mode, &maxim4c_pattern_mode, sizeof(struct maxim4c_mode));
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL(maxim4c_pattern_support_mode_init);
 | |
| 
 | |
| int maxim4c_pattern_data_init(maxim4c_t *maxim4c)
 | |
| {
 | |
| 	struct device *dev = &maxim4c->client->dev;
 | |
| 	struct device_node *node = NULL;
 | |
| 	struct maxim4c_mode *supported_mode = NULL;
 | |
| 	struct maxim4c_pattern *pattern = NULL;
 | |
| 	maxim4c_mipi_txphy_t *mipi_txphy = &maxim4c->mipi_txphy;
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	// maxim serdes local
 | |
| 	node = of_get_child_by_name(dev->of_node, "serdes-local-device");
 | |
| 	if (IS_ERR_OR_NULL(node)) {
 | |
| 		dev_err(dev, "%pOF has no child node: serdes-local-device\n",
 | |
| 				dev->of_node);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	if (!of_device_is_available(node)) {
 | |
| 		dev_info(dev, "%pOF is disabled\n", node);
 | |
| 
 | |
| 		of_node_put(node);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	maxim4c_mipi_txphy_data_init(maxim4c);
 | |
| 
 | |
| 	/* mipi txphy parse dt */
 | |
| 	ret = maxim4c_mipi_txphy_parse_dt(maxim4c, node);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "%s: txphy parse dt error\n", __func__);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	// pattern need enable force_clock_out_en
 | |
| 	dev_info(dev, "Pattern mode force_clock_out_en default enable\n");
 | |
| 	mipi_txphy->force_clock_out_en = 1;
 | |
| 
 | |
| 	// pattern generator and mode init
 | |
| 	pattern = &maxim4c->pattern;
 | |
| 	pattern->pattern_generator = PATTERN_GENERATOR_0;
 | |
| 	pattern->pattern_mode = PATTERN_CHECKERBOARD;
 | |
| 	pattern->pattern_pclk = PATTERN_PCLK_75M;
 | |
| 
 | |
| 	supported_mode = &maxim4c->supported_mode;
 | |
| 	switch (pattern->pattern_pclk) {
 | |
| 	case PATTERN_PCLK_25M:
 | |
| 		supported_mode->max_fps.denominator = 100000;
 | |
| 		break;
 | |
| 	case PATTERN_PCLK_75M:
 | |
| 		supported_mode->max_fps.denominator = 300000;
 | |
| 		break;
 | |
| 	case PATTERN_PCLK_150M:
 | |
| 		supported_mode->max_fps.denominator = 600000;
 | |
| 		if (supported_mode->link_freq_idx < 12)
 | |
| 			dev_warn(dev, "link_freq_idx = %d is too low\n",
 | |
| 					supported_mode->link_freq_idx);
 | |
| 		break;
 | |
| 	case PATTERN_PCLK_375M:
 | |
| 		supported_mode->max_fps.denominator = 1500000;
 | |
| 		if (supported_mode->link_freq_idx < 22)
 | |
| 			dev_warn(dev, "link_freq_idx = %d is too low\n",
 | |
| 					supported_mode->link_freq_idx);
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	dev_info(dev, "video pattern: generator = %d, mode = %d, pclk = %d\n",
 | |
| 		pattern->pattern_generator, pattern->pattern_mode, pattern->pattern_pclk);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL(maxim4c_pattern_data_init);
 | |
| 
 | |
| int maxim4c_pattern_hw_init(maxim4c_t *maxim4c)
 | |
| {
 | |
| 	struct device *dev = &maxim4c->client->dev;
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	ret = maxim4c_pattern_previnit(maxim4c);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "%s: pattern previnit error\n", __func__);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = maxim4c_mipi_txphy_hw_init(maxim4c);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "%s: txphy hw init error\n", __func__);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = maxim4c_pattern_config(maxim4c);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "%s: pattern config error\n", __func__);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL(maxim4c_pattern_hw_init);
 |