diff options
Diffstat (limited to 'drivers/gpu/drm/sun4i/sun4i_frontend.c')
| -rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_frontend.c | 354 | 
1 files changed, 323 insertions, 31 deletions
| diff --git a/drivers/gpu/drm/sun4i/sun4i_frontend.c b/drivers/gpu/drm/sun4i/sun4i_frontend.c index 1a7ebc45747e..346c8071bd38 100644 --- a/drivers/gpu/drm/sun4i/sun4i_frontend.c +++ b/drivers/gpu/drm/sun4i/sun4i_frontend.c @@ -10,6 +10,7 @@  #include <linux/clk.h>  #include <linux/component.h>  #include <linux/module.h> +#include <linux/of_device.h>  #include <linux/platform_device.h>  #include <linux/pm_runtime.h>  #include <linux/regmap.h> @@ -48,10 +49,38 @@ static const u32 sun4i_frontend_horz_coef[64] = {  	0x03ff0000, 0x0000fd41, 0x01ff0000, 0x0000fe42,  }; +/* + * These coefficients are taken from the A33 BSP from Allwinner. + * + * The first three values of each row are coded as 13-bit signed fixed-point + * numbers, with 10 bits for the fractional part. The fourth value is a + * constant coded as a 14-bit signed fixed-point number with 4 bits for the + * fractional part. + * + * The values in table order give the following colorspace translation: + * G = 1.164 * Y - 0.391 * U - 0.813 * V + 135 + * R = 1.164 * Y + 1.596 * V - 222 + * B = 1.164 * Y + 2.018 * U + 276 + * + * This seems to be a conversion from Y[16:235] UV[16:240] to RGB[0:255], + * following the BT601 spec. + */ +const u32 sunxi_bt601_yuv2rgb_coef[12] = { +	0x000004a7, 0x00001e6f, 0x00001cbf, 0x00000877, +	0x000004a7, 0x00000000, 0x00000662, 0x00003211, +	0x000004a7, 0x00000812, 0x00000000, 0x00002eb1, +}; +EXPORT_SYMBOL(sunxi_bt601_yuv2rgb_coef); +  static void sun4i_frontend_scaler_init(struct sun4i_frontend *frontend)  {  	int i; +	if (frontend->data->has_coef_access_ctrl) +		regmap_write_bits(frontend->regs, SUN4I_FRONTEND_FRM_CTRL_REG, +				  SUN4I_FRONTEND_FRM_CTRL_COEF_ACCESS_CTRL, +				  SUN4I_FRONTEND_FRM_CTRL_COEF_ACCESS_CTRL); +  	for (i = 0; i < 32; i++) {  		regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_HORZCOEF0_REG(i),  			     sun4i_frontend_horz_coef[2 * i]); @@ -67,9 +96,11 @@ static void sun4i_frontend_scaler_init(struct sun4i_frontend *frontend)  			     sun4i_frontend_vert_coef[i]);  	} -	regmap_update_bits(frontend->regs, SUN4I_FRONTEND_FRM_CTRL_REG, -			   SUN4I_FRONTEND_FRM_CTRL_COEF_ACCESS_CTRL, -			   SUN4I_FRONTEND_FRM_CTRL_COEF_ACCESS_CTRL); +	if (frontend->data->has_coef_rdy) +		regmap_write_bits(frontend->regs, +				  SUN4I_FRONTEND_FRM_CTRL_REG, +				  SUN4I_FRONTEND_FRM_CTRL_COEF_RDY, +				  SUN4I_FRONTEND_FRM_CTRL_COEF_RDY);  }  int sun4i_frontend_init(struct sun4i_frontend *frontend) @@ -84,59 +115,228 @@ void sun4i_frontend_exit(struct sun4i_frontend *frontend)  }  EXPORT_SYMBOL(sun4i_frontend_exit); +static bool sun4i_frontend_format_chroma_requires_swap(uint32_t fmt) +{ +	switch (fmt) { +	case DRM_FORMAT_YVU411: +	case DRM_FORMAT_YVU420: +	case DRM_FORMAT_YVU422: +	case DRM_FORMAT_YVU444: +		return true; + +	default: +		return false; +	} +} + +static bool sun4i_frontend_format_supports_tiling(uint32_t fmt) +{ +	switch (fmt) { +	case DRM_FORMAT_NV12: +	case DRM_FORMAT_NV16: +	case DRM_FORMAT_NV21: +	case DRM_FORMAT_NV61: +	case DRM_FORMAT_YUV411: +	case DRM_FORMAT_YUV420: +	case DRM_FORMAT_YUV422: +	case DRM_FORMAT_YVU420: +	case DRM_FORMAT_YVU422: +	case DRM_FORMAT_YVU411: +		return true; + +	default: +		return false; +	} +} +  void sun4i_frontend_update_buffer(struct sun4i_frontend *frontend,  				  struct drm_plane *plane)  {  	struct drm_plane_state *state = plane->state;  	struct drm_framebuffer *fb = state->fb; +	unsigned int strides[3] = {}; +  	dma_addr_t paddr; +	bool swap; + +	if (fb->modifier == DRM_FORMAT_MOD_ALLWINNER_TILED) { +		unsigned int width = state->src_w >> 16; +		unsigned int offset; + +		strides[0] = SUN4I_FRONTEND_LINESTRD_TILED(fb->pitches[0]); + +		/* +		 * The X1 offset is the offset to the bottom-right point in the +		 * end tile, which is the final pixel (at offset width - 1) +		 * within the end tile (with a 32-byte mask). +		 */ +		offset = (width - 1) & (32 - 1); + +		regmap_write(frontend->regs, SUN4I_FRONTEND_TB_OFF0_REG, +			     SUN4I_FRONTEND_TB_OFF_X1(offset)); + +		if (fb->format->num_planes > 1) { +			strides[1] = +				SUN4I_FRONTEND_LINESTRD_TILED(fb->pitches[1]); + +			regmap_write(frontend->regs, SUN4I_FRONTEND_TB_OFF1_REG, +				     SUN4I_FRONTEND_TB_OFF_X1(offset)); +		} + +		if (fb->format->num_planes > 2) { +			strides[2] = +				SUN4I_FRONTEND_LINESTRD_TILED(fb->pitches[2]); + +			regmap_write(frontend->regs, SUN4I_FRONTEND_TB_OFF2_REG, +				     SUN4I_FRONTEND_TB_OFF_X1(offset)); +		} +	} else { +		strides[0] = fb->pitches[0]; + +		if (fb->format->num_planes > 1) +			strides[1] = fb->pitches[1]; + +		if (fb->format->num_planes > 2) +			strides[2] = fb->pitches[2]; +	}  	/* Set the line width */  	DRM_DEBUG_DRIVER("Frontend stride: %d bytes\n", fb->pitches[0]);  	regmap_write(frontend->regs, SUN4I_FRONTEND_LINESTRD0_REG, -		     fb->pitches[0]); +		     strides[0]); + +	if (fb->format->num_planes > 1) +		regmap_write(frontend->regs, SUN4I_FRONTEND_LINESTRD1_REG, +			     strides[1]); + +	if (fb->format->num_planes > 2) +		regmap_write(frontend->regs, SUN4I_FRONTEND_LINESTRD2_REG, +			     strides[2]); + +	/* Some planar formats require chroma channel swapping by hand. */ +	swap = sun4i_frontend_format_chroma_requires_swap(fb->format->format);  	/* Set the physical address of the buffer in memory */  	paddr = drm_fb_cma_get_gem_addr(fb, state, 0);  	paddr -= PHYS_OFFSET; -	DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &paddr); +	DRM_DEBUG_DRIVER("Setting buffer #0 address to %pad\n", &paddr);  	regmap_write(frontend->regs, SUN4I_FRONTEND_BUF_ADDR0_REG, paddr); + +	if (fb->format->num_planes > 1) { +		paddr = drm_fb_cma_get_gem_addr(fb, state, swap ? 2 : 1); +		paddr -= PHYS_OFFSET; +		DRM_DEBUG_DRIVER("Setting buffer #1 address to %pad\n", &paddr); +		regmap_write(frontend->regs, SUN4I_FRONTEND_BUF_ADDR1_REG, +			     paddr); +	} + +	if (fb->format->num_planes > 2) { +		paddr = drm_fb_cma_get_gem_addr(fb, state, swap ? 1 : 2); +		paddr -= PHYS_OFFSET; +		DRM_DEBUG_DRIVER("Setting buffer #2 address to %pad\n", &paddr); +		regmap_write(frontend->regs, SUN4I_FRONTEND_BUF_ADDR2_REG, +			     paddr); +	}  }  EXPORT_SYMBOL(sun4i_frontend_update_buffer); -static int sun4i_frontend_drm_format_to_input_fmt(uint32_t fmt, u32 *val) +static int +sun4i_frontend_drm_format_to_input_fmt(const struct drm_format_info *format, +				       u32 *val)  { -	switch (fmt) { -	case DRM_FORMAT_XRGB8888: +	if (!format->is_yuv)  		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_RGB; -		return 0; - -	default: +	else if (drm_format_info_is_yuv_sampling_411(format)) +		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_YUV411; +	else if (drm_format_info_is_yuv_sampling_420(format)) +		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_YUV420; +	else if (drm_format_info_is_yuv_sampling_422(format)) +		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_YUV422; +	else if (drm_format_info_is_yuv_sampling_444(format)) +		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_YUV444; +	else  		return -EINVAL; -	} + +	return 0;  } -static int sun4i_frontend_drm_format_to_input_mode(uint32_t fmt, u32 *val) +static int +sun4i_frontend_drm_format_to_input_mode(const struct drm_format_info *format, +					uint64_t modifier, u32 *val)  { -	if (drm_format_num_planes(fmt) == 1) +	bool tiled = (modifier == DRM_FORMAT_MOD_ALLWINNER_TILED); + +	switch (format->num_planes) { +	case 1:  		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_PACKED; -	else -		return -EINVAL; +		return 0; -	return 0; +	case 2: +		*val = tiled ? SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_MB32_SEMIPLANAR +			     : SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_SEMIPLANAR; +		return 0; + +	case 3: +		*val = tiled ? SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_MB32_PLANAR +			     : SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_PLANAR; +		return 0; + +	default: +		return -EINVAL; +	}  } -static int sun4i_frontend_drm_format_to_input_sequence(uint32_t fmt, u32 *val) +static int +sun4i_frontend_drm_format_to_input_sequence(const struct drm_format_info *format, +					    u32 *val)  { -	switch (fmt) { +	/* Planar formats have an explicit input sequence. */ +	if (drm_format_info_is_yuv_planar(format)) { +		*val = 0; +		return 0; +	} + +	switch (format->format) {  	case DRM_FORMAT_BGRX8888:  		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_BGRX;  		return 0; +	case DRM_FORMAT_NV12: +		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_UV; +		return 0; + +	case DRM_FORMAT_NV16: +		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_UV; +		return 0; + +	case DRM_FORMAT_NV21: +		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_VU; +		return 0; + +	case DRM_FORMAT_NV61: +		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_VU; +		return 0; + +	case DRM_FORMAT_UYVY: +		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_UYVY; +		return 0; + +	case DRM_FORMAT_VYUY: +		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_VYUY; +		return 0; +  	case DRM_FORMAT_XRGB8888:  		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_XRGB;  		return 0; +	case DRM_FORMAT_YUYV: +		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_YUYV; +		return 0; + +	case DRM_FORMAT_YVYU: +		*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_YVYU; +		return 0; +  	default:  		return -EINVAL;  	} @@ -160,14 +360,32 @@ static int sun4i_frontend_drm_format_to_output_fmt(uint32_t fmt, u32 *val)  static const uint32_t sun4i_frontend_formats[] = {  	DRM_FORMAT_BGRX8888, +	DRM_FORMAT_NV12, +	DRM_FORMAT_NV16, +	DRM_FORMAT_NV21, +	DRM_FORMAT_NV61, +	DRM_FORMAT_UYVY, +	DRM_FORMAT_VYUY,  	DRM_FORMAT_XRGB8888, +	DRM_FORMAT_YUV411, +	DRM_FORMAT_YUV420, +	DRM_FORMAT_YUV422, +	DRM_FORMAT_YUV444, +	DRM_FORMAT_YUYV, +	DRM_FORMAT_YVU411, +	DRM_FORMAT_YVU420, +	DRM_FORMAT_YVU422, +	DRM_FORMAT_YVU444, +	DRM_FORMAT_YVYU,  };  bool sun4i_frontend_format_is_supported(uint32_t fmt, uint64_t modifier)  {  	unsigned int i; -	if (modifier != DRM_FORMAT_MOD_LINEAR) +	if (modifier == DRM_FORMAT_MOD_ALLWINNER_TILED) +		return sun4i_frontend_format_supports_tiling(fmt); +	else if (modifier != DRM_FORMAT_MOD_LINEAR)  		return false;  	for (i = 0; i < ARRAY_SIZE(sun4i_frontend_formats); i++) @@ -183,9 +401,12 @@ int sun4i_frontend_update_formats(struct sun4i_frontend *frontend,  {  	struct drm_plane_state *state = plane->state;  	struct drm_framebuffer *fb = state->fb; -	uint32_t format = fb->format->format; +	const struct drm_format_info *format = fb->format; +	uint64_t modifier = fb->modifier;  	u32 out_fmt_val;  	u32 in_fmt_val, in_mod_val, in_ps_val; +	unsigned int i; +	u32 bypass;  	int ret;  	ret = sun4i_frontend_drm_format_to_input_fmt(format, &in_fmt_val); @@ -194,7 +415,8 @@ int sun4i_frontend_update_formats(struct sun4i_frontend *frontend,  		return ret;  	} -	ret = sun4i_frontend_drm_format_to_input_mode(format, &in_mod_val); +	ret = sun4i_frontend_drm_format_to_input_mode(format, modifier, +						      &in_mod_val);  	if (ret) {  		DRM_DEBUG_DRIVER("Invalid input mode\n");  		return ret; @@ -216,16 +438,39 @@ int sun4i_frontend_update_formats(struct sun4i_frontend *frontend,  	 * I have no idea what this does exactly, but it seems to be  	 * related to the scaler FIR filter phase parameters.  	 */ -	regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_HORZPHASE_REG, 0x400); -	regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_HORZPHASE_REG, 0x400); -	regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTPHASE0_REG, 0x400); -	regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTPHASE0_REG, 0x400); -	regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTPHASE1_REG, 0x400); -	regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTPHASE1_REG, 0x400); +	regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_HORZPHASE_REG, +		     frontend->data->ch_phase[0].horzphase); +	regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_HORZPHASE_REG, +		     frontend->data->ch_phase[1].horzphase); +	regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTPHASE0_REG, +		     frontend->data->ch_phase[0].vertphase[0]); +	regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTPHASE0_REG, +		     frontend->data->ch_phase[1].vertphase[0]); +	regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTPHASE1_REG, +		     frontend->data->ch_phase[0].vertphase[1]); +	regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTPHASE1_REG, +		     frontend->data->ch_phase[1].vertphase[1]); + +	/* +	 * Checking the input format is sufficient since we currently only +	 * support RGB output formats to the backend. If YUV output formats +	 * ever get supported, an YUV input and output would require bypassing +	 * the CSC engine too. +	 */ +	if (format->is_yuv) { +		/* Setup the CSC engine for YUV to RGB conversion. */ +		bypass = 0; + +		for (i = 0; i < ARRAY_SIZE(sunxi_bt601_yuv2rgb_coef); i++) +			regmap_write(frontend->regs, +				     SUN4I_FRONTEND_CSC_COEF_REG(i), +				     sunxi_bt601_yuv2rgb_coef[i]); +	} else { +		bypass = SUN4I_FRONTEND_BYPASS_CSC_EN; +	}  	regmap_update_bits(frontend->regs, SUN4I_FRONTEND_BYPASS_REG, -			   SUN4I_FRONTEND_BYPASS_CSC_EN, -			   SUN4I_FRONTEND_BYPASS_CSC_EN); +			   SUN4I_FRONTEND_BYPASS_CSC_EN, bypass);  	regmap_write(frontend->regs, SUN4I_FRONTEND_INPUT_FMT_REG,  		     in_mod_val | in_fmt_val | in_ps_val); @@ -321,6 +566,10 @@ static int sun4i_frontend_bind(struct device *dev, struct device *master,  	frontend->dev = dev;  	frontend->node = dev->of_node; +	frontend->data = of_device_get_match_data(dev); +	if (!frontend->data) +		return -ENODEV; +  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  	regs = devm_ioremap_resource(dev, res);  	if (IS_ERR(regs)) @@ -433,8 +682,51 @@ static const struct dev_pm_ops sun4i_frontend_pm_ops = {  	.runtime_suspend	= sun4i_frontend_runtime_suspend,  }; +static const struct sun4i_frontend_data sun4i_a10_frontend = { +	.ch_phase		= { +		{ +			.horzphase = 0, +			.vertphase = { 0, 0 }, +		}, +		{ +			.horzphase = 0xfc000, +			.vertphase = { 0xfc000, 0xfc000 }, +		}, +	}, +	.has_coef_rdy		= true, +}; + +static const struct sun4i_frontend_data sun8i_a33_frontend = { +	.ch_phase		= { +		{ +			.horzphase = 0x400, +			.vertphase = { 0x400, 0x400 }, +		}, +		{ +			.horzphase = 0x400, +			.vertphase = { 0x400, 0x400 }, +		}, +	}, +	.has_coef_access_ctrl	= true, +}; +  const struct of_device_id sun4i_frontend_of_table[] = { -	{ .compatible = "allwinner,sun8i-a33-display-frontend" }, +	{ +		.compatible = "allwinner,sun4i-a10-display-frontend", +		.data = &sun4i_a10_frontend +	}, +	{ +		.compatible = "allwinner,sun7i-a20-display-frontend", +		.data = &sun4i_a10_frontend +	}, +	{ +		.compatible = "allwinner,sun8i-a23-display-frontend", +		.data = &sun8i_a33_frontend +	}, +	{ +		.compatible = "allwinner,sun8i-a33-display-frontend", +		.data = &sun8i_a33_frontend +	},  	{ }  };  EXPORT_SYMBOL(sun4i_frontend_of_table); | 
