[5/5] drm: zte: add tvenc driver support
diff mbox

Message ID 1484843100-16284-6-git-send-email-shawnguo@kernel.org
State New
Headers show

Commit Message

Shawn Guo Jan. 19, 2017, 4:25 p.m. UTC
From: Shawn Guo <shawn.guo@linaro.org>

It adds the TV Encoder driver to support video output in PAL and NTSC
format.  The driver uses syscon/regmap interface to configure register
bit sitting in SYSCTRL module for DAC power control.

Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
---
 drivers/gpu/drm/zte/Makefile        |   1 +
 drivers/gpu/drm/zte/zx_drm_drv.c    |   1 +
 drivers/gpu/drm/zte/zx_drm_drv.h    |   1 +
 drivers/gpu/drm/zte/zx_tvenc.c      | 416 ++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/zte/zx_tvenc_regs.h |  31 +++
 drivers/gpu/drm/zte/zx_vou.c        |   5 +
 6 files changed, 455 insertions(+)
 create mode 100644 drivers/gpu/drm/zte/zx_tvenc.c
 create mode 100644 drivers/gpu/drm/zte/zx_tvenc_regs.h

Comments

Sean Paul Jan. 23, 2017, 4:10 p.m. UTC | #1
On Fri, Jan 20, 2017 at 12:25:00AM +0800, Shawn Guo wrote:
> From: Shawn Guo <shawn.guo@linaro.org>
> 
> It adds the TV Encoder driver to support video output in PAL and NTSC
> format.  The driver uses syscon/regmap interface to configure register
> bit sitting in SYSCTRL module for DAC power control.
> 
> Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
> ---
>  drivers/gpu/drm/zte/Makefile        |   1 +
>  drivers/gpu/drm/zte/zx_drm_drv.c    |   1 +
>  drivers/gpu/drm/zte/zx_drm_drv.h    |   1 +
>  drivers/gpu/drm/zte/zx_tvenc.c      | 416 ++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/zte/zx_tvenc_regs.h |  31 +++
>  drivers/gpu/drm/zte/zx_vou.c        |   5 +
>  6 files changed, 455 insertions(+)
>  create mode 100644 drivers/gpu/drm/zte/zx_tvenc.c
>  create mode 100644 drivers/gpu/drm/zte/zx_tvenc_regs.h
> 
> diff --git a/drivers/gpu/drm/zte/Makefile b/drivers/gpu/drm/zte/Makefile
> index 699180bfd57c..01352b56c418 100644
> --- a/drivers/gpu/drm/zte/Makefile
> +++ b/drivers/gpu/drm/zte/Makefile
> @@ -2,6 +2,7 @@ zxdrm-y := \
>  	zx_drm_drv.o \
>  	zx_hdmi.o \
>  	zx_plane.o \
> +	zx_tvenc.o \
>  	zx_vou.o
>  
>  obj-$(CONFIG_DRM_ZTE) += zxdrm.o
> diff --git a/drivers/gpu/drm/zte/zx_drm_drv.c b/drivers/gpu/drm/zte/zx_drm_drv.c
> index 3e76f72c92ff..13081fed902d 100644
> --- a/drivers/gpu/drm/zte/zx_drm_drv.c
> +++ b/drivers/gpu/drm/zte/zx_drm_drv.c
> @@ -247,6 +247,7 @@ static int zx_drm_remove(struct platform_device *pdev)
>  static struct platform_driver *drivers[] = {
>  	&zx_crtc_driver,
>  	&zx_hdmi_driver,
> +	&zx_tvenc_driver,
>  	&zx_drm_platform_driver,
>  };
>  
> diff --git a/drivers/gpu/drm/zte/zx_drm_drv.h b/drivers/gpu/drm/zte/zx_drm_drv.h
> index e65cd18a6cba..5ca035b079c7 100644
> --- a/drivers/gpu/drm/zte/zx_drm_drv.h
> +++ b/drivers/gpu/drm/zte/zx_drm_drv.h
> @@ -13,6 +13,7 @@
>  
>  extern struct platform_driver zx_crtc_driver;
>  extern struct platform_driver zx_hdmi_driver;
> +extern struct platform_driver zx_tvenc_driver;
>  
>  static inline u32 zx_readl(void __iomem *reg)
>  {
> diff --git a/drivers/gpu/drm/zte/zx_tvenc.c b/drivers/gpu/drm/zte/zx_tvenc.c
> new file mode 100644
> index 000000000000..5a6cff1ff8a8
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_tvenc.c
> @@ -0,0 +1,416 @@
> +/*
> + * Copyright 2017 Linaro Ltd.
> + * Copyright 2017 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/regmap.h>
> +
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drmP.h>
> +
> +#include "zx_drm_drv.h"
> +#include "zx_tvenc_regs.h"
> +#include "zx_vou.h"
> +
> +struct zx_tvenc_pwrctrl {
> +	struct regmap *regmap;
> +	u32 reg;
> +	u32 mask;
> +};
> +
> +struct zx_tvenc {
> +	struct drm_connector connector;
> +	struct drm_encoder encoder;
> +	struct device *dev;
> +	void __iomem *mmio;
> +	const struct vou_inf *inf;
> +	struct zx_tvenc_pwrctrl pwrctrl;
> +};
> +
> +#define to_zx_tvenc(x) container_of(x, struct zx_tvenc, x)
> +
> +struct zx_tvenc_mode {
> +	char *name;
> +	u32 hdisplay;
> +	u32 vdisplay;
> +	u32 hfp;
> +	u32 hbp;
> +	u32 hsw;
> +	u32 vfp;
> +	u32 vbp;
> +	u32 vsw;

Let's not duplicate these fields and do conversions. I think you should embed a
drm_display_mode struct here instead and just return it in get_modes. Check out
panel_desc in drivers/gpu/drm/panel/panel-simple.c, I think you can do something
similar here.

> +	u32 video_info;
> +	u32 video_res;
> +	u32 field1_param;
> +	u32 field2_param;
> +	u32 burst_line_odd1;
> +	u32 burst_line_even1;
> +	u32 burst_line_odd2;
> +	u32 burst_line_even2;
> +	u32 line_timing_param;
> +	u32 weight_value;
> +	u32 blank_black_level;
> +	u32 burst_level;
> +	u32 control_param;
> +	u32 sub_carrier_phase1;
> +	u32 phase_line_incr_cvbs;
> +};
> +
> +static const struct zx_tvenc_mode tvenc_modes[] = {
> +	{
> +		.name = "PAL",
> +		.hdisplay = 720,
> +		.vdisplay = 576,
> +		.hfp = 12,
> +		.hbp = 130,
> +		.hsw = 2,
> +		.vfp = 2,
> +		.vbp = 20,
> +		.vsw = 2,
> +		.video_info = 0x00040040,
> +		.video_res = 0x05a9c760,
> +		.field1_param = 0x0004d416,
> +		.field2_param = 0x0009b94f,
> +		.burst_line_odd1 = 0x0004d406,
> +		.burst_line_even1 = 0x0009b53e,
> +		.burst_line_odd2 = 0x0004d805,
> +		.burst_line_even2 = 0x0009b93f,
> +		.line_timing_param = 0x06a96fdf,
> +		.weight_value = 0x00c188a0,
> +		.blank_black_level = 0x0000fcfc,
> +		.burst_level = 0x00001595,
> +		.control_param = 0x00000001,
> +		.sub_carrier_phase1 = 0x1504c566,
> +		.phase_line_incr_cvbs = 0xc068db8c,

I don't suppose you can derive these magic values from the mode?

> +	}, {
> +		.name = "NTSC",
> +		.hdisplay = 720,
> +		.vdisplay = 480,
> +		.hfp = 16,
> +		.hbp = 120,
> +		.hsw = 2,
> +		.vfp = 3,
> +		.vbp = 17,
> +		.vsw = 2,
> +		.video_info = 0x00040080,
> +		.video_res = 0x05a8375a,
> +		.field1_param = 0x00041817,
> +		.field2_param = 0x0008351e,
> +		.burst_line_odd1 = 0x00041006,
> +		.burst_line_even1 = 0x0008290d,
> +		.burst_line_odd2 = 0x00000000,
> +		.burst_line_even2 = 0x00000000,
> +		.line_timing_param = 0x06a8ef9e,
> +		.weight_value = 0x00b68197,
> +		.blank_black_level = 0x0000f0f0,
> +		.burst_level = 0x0000009c,
> +		.control_param = 0x00000001,
> +		.sub_carrier_phase1 = 0x10f83e10,
> +		.phase_line_incr_cvbs = 0x80000000,
> +	},
> +};
> +
> +static const struct zx_tvenc_mode *
> +zx_tvenc_find_zmode(struct drm_display_mode *mode)
> +{
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(tvenc_modes); i++) {
> +		const struct zx_tvenc_mode *zmode = &tvenc_modes[i];
> +
> +		if (!strcmp(mode->name, zmode->name))

drm_mode_equal() is your friend

> +			return zmode;
> +	}
> +
> +	return NULL;
> +}
> +
> +static void zx_tvenc_encoder_mode_set(struct drm_encoder *encoder,
> +				      struct drm_display_mode *mode,
> +				      struct drm_display_mode *adj_mode)
> +{
> +	struct zx_tvenc *tvenc = to_zx_tvenc(encoder);
> +	const struct zx_tvenc_mode *zmode;
> +	struct vou_div_config configs[] = {
> +		{ VOU_DIV_INF,   VOU_DIV_4 },
> +		{ VOU_DIV_TVENC, VOU_DIV_1 },
> +		{ VOU_DIV_LAYER, VOU_DIV_2 },
> +	};
> +
> +	zx_vou_config_dividers(encoder->crtc, configs, ARRAY_SIZE(configs));
> +
> +	zmode = zx_tvenc_find_zmode(mode);
> +	if (!zmode) {
> +		DRM_DEV_ERROR(tvenc->dev, "failed to find zmode\n");
> +		return;
> +	}
> +
> +	zx_writel(tvenc->mmio + VENC_VIDEO_INFO, zmode->video_info);
> +	zx_writel(tvenc->mmio + VENC_VIDEO_RES, zmode->video_res);
> +	zx_writel(tvenc->mmio + VENC_FIELD1_PARAM, zmode->field1_param);
> +	zx_writel(tvenc->mmio + VENC_FIELD2_PARAM, zmode->field2_param);
> +	zx_writel(tvenc->mmio + VENC_LINE_O_1, zmode->burst_line_odd1);
> +	zx_writel(tvenc->mmio + VENC_LINE_E_1, zmode->burst_line_even1);
> +	zx_writel(tvenc->mmio + VENC_LINE_O_2, zmode->burst_line_odd2);
> +	zx_writel(tvenc->mmio + VENC_LINE_E_2, zmode->burst_line_even2);
> +	zx_writel(tvenc->mmio + VENC_LINE_TIMING_PARAM,
> +		  zmode->line_timing_param);
> +	zx_writel(tvenc->mmio + VENC_WEIGHT_VALUE, zmode->weight_value);
> +	zx_writel(tvenc->mmio + VENC_BLANK_BLACK_LEVEL,
> +		  zmode->blank_black_level);
> +	zx_writel(tvenc->mmio + VENC_BURST_LEVEL, zmode->burst_level);
> +	zx_writel(tvenc->mmio + VENC_CONTROL_PARAM, zmode->control_param);
> +	zx_writel(tvenc->mmio + VENC_SUB_CARRIER_PHASE1,
> +		  zmode->sub_carrier_phase1);
> +	zx_writel(tvenc->mmio + VENC_PHASE_LINE_INCR_CVBS,
> +		  zmode->phase_line_incr_cvbs);
> +}
> +
> +static void zx_tvenc_encoder_enable(struct drm_encoder *encoder)
> +{
> +	struct zx_tvenc *tvenc = to_zx_tvenc(encoder);
> +	struct zx_tvenc_pwrctrl *pwrctrl = &tvenc->pwrctrl;
> +
> +	/* Set bit to power up TVENC DAC */
> +	regmap_update_bits(pwrctrl->regmap, pwrctrl->reg, pwrctrl->mask,
> +			   pwrctrl->mask);
> +
> +	vou_inf_enable(VOU_TV_ENC, encoder->crtc);
> +
> +	zx_writel(tvenc->mmio + VENC_ENABLE, 1);
> +}
> +
> +static void zx_tvenc_encoder_disable(struct drm_encoder *encoder)
> +{
> +	struct zx_tvenc *tvenc = to_zx_tvenc(encoder);
> +	struct zx_tvenc_pwrctrl *pwrctrl = &tvenc->pwrctrl;
> +
> +	zx_writel(tvenc->mmio + VENC_ENABLE, 0);
> +
> +	vou_inf_disable(VOU_TV_ENC, encoder->crtc);
> +
> +	/* Clear bit to power down TVENC DAC */
> +	regmap_update_bits(pwrctrl->regmap, pwrctrl->reg, pwrctrl->mask, 0);
> +}
> +
> +static const struct drm_encoder_helper_funcs zx_tvenc_encoder_helper_funcs = {
> +	.enable	= zx_tvenc_encoder_enable,
> +	.disable = zx_tvenc_encoder_disable,
> +	.mode_set = zx_tvenc_encoder_mode_set,
> +};
> +
> +static const struct drm_encoder_funcs zx_tvenc_encoder_funcs = {
> +	.destroy = drm_encoder_cleanup,
> +};
> +
> +static void zx_tvenc_mode_to_drm_mode(const struct zx_tvenc_mode *zmode,
> +				      struct drm_display_mode *mode)
> +{
> +	strcpy(mode->name, zmode->name);

Consider drm_mode_set_name() instead of rolling your own names

> +
> +	mode->type = DRM_MODE_TYPE_DRIVER;
> +	mode->flags = DRM_MODE_FLAG_INTERLACE;
> +	mode->flags |= DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC;
> +
> +	/*
> +	 * The CRM cannot directly provide such a low rate, and we have to
> +	 * ask a multiflied rate from CRM and use the divider in VOU to get

s/multified/multiplied/

> +	 * the disired one.

s/disired/desired/

> +	 */
> +	mode->clock = 13500 * 4;

I don't think this should be hardcoded at runtime, stick it in the modes above

> +
> +	mode->hdisplay = zmode->hdisplay;
> +	mode->hsync_start = mode->hdisplay + zmode->hfp;
> +	mode->hsync_end = mode->hsync_start + zmode->hsw;
> +	mode->htotal = mode->hsync_end + zmode->hbp;
> +
> +	mode->vdisplay = zmode->vdisplay;
> +	mode->vsync_start = mode->vdisplay + zmode->vfp;
> +	mode->vsync_end = mode->vsync_start + zmode->vsw;
> +	mode->vtotal = mode->vsync_end + zmode->vbp;
> +}
> +
> +static int zx_tvenc_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct zx_tvenc *tvenc = to_zx_tvenc(connector);
> +	struct device *dev = tvenc->dev;
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(tvenc_modes); i++) {
> +		struct drm_display_mode *mode;
> +		const struct zx_tvenc_mode *zmode = &tvenc_modes[i];
> +
> +		mode = drm_mode_create(connector->dev);
> +		if (!mode) {
> +			DRM_DEV_ERROR(dev, "failed to create drm mode\n");
> +			return 0;
> +		}
> +
> +		zx_tvenc_mode_to_drm_mode(zmode, mode);
> +		drm_mode_probed_add(connector, mode);
> +	}
> +
> +	return i;
> +}
> +
> +static enum drm_mode_status
> +zx_tvenc_connector_mode_valid(struct drm_connector *connector,
> +			      struct drm_display_mode *mode)
> +{

You probably want to do a sanity check to ensure the mode is in your list above
(and not user-submitted).

> +	return MODE_OK;
> +}
> +
> +static struct drm_connector_helper_funcs zx_tvenc_connector_helper_funcs = {
> +	.get_modes = zx_tvenc_connector_get_modes,
> +	.mode_valid = zx_tvenc_connector_mode_valid,
> +};
> +
> +static const struct drm_connector_funcs zx_tvenc_connector_funcs = {
> +	.dpms = drm_atomic_helper_connector_dpms,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.destroy = drm_connector_cleanup,
> +	.reset = drm_atomic_helper_connector_reset,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static int zx_tvenc_register(struct drm_device *drm, struct zx_tvenc *tvenc)
> +{
> +	struct drm_encoder *encoder = &tvenc->encoder;
> +	struct drm_connector *connector = &tvenc->connector;
> +
> +	/*
> +	 * The tvenc is designed to use aux channel, as there is a deflicker
> +	 * block for the channel.
> +	 */
> +	encoder->possible_crtcs = BIT(1);
> +
> +	drm_encoder_init(drm, encoder, &zx_tvenc_encoder_funcs,
> +			 DRM_MODE_ENCODER_TVDAC, NULL);
> +	drm_encoder_helper_add(encoder, &zx_tvenc_encoder_helper_funcs);
> +
> +	connector->interlace_allowed = true;
> +
> +	drm_connector_init(drm, connector, &zx_tvenc_connector_funcs,
> +			   DRM_MODE_CONNECTOR_Composite);
> +	drm_connector_helper_add(connector, &zx_tvenc_connector_helper_funcs);
> +
> +	drm_mode_connector_attach_encoder(connector, encoder);
> +
> +	return 0;
> +}
> +
> +static int zx_tvenc_pwrctrl_init(struct zx_tvenc *tvenc)
> +{
> +	struct zx_tvenc_pwrctrl *pwrctrl = &tvenc->pwrctrl;
> +	struct device *dev = tvenc->dev;
> +	struct of_phandle_args out_args;
> +	struct regmap *regmap;
> +	int ret;
> +
> +	ret = of_parse_phandle_with_fixed_args(dev->of_node,
> +				"zte,tvenc-power-control", 2, 0, &out_args);
> +	if (ret)
> +		return ret;
> +
> +	regmap = syscon_node_to_regmap(out_args.np);
> +	if (IS_ERR(regmap)) {
> +		ret = PTR_ERR(regmap);
> +		goto out;
> +	}
> +
> +	pwrctrl->regmap = regmap;
> +	pwrctrl->reg = out_args.args[0];
> +	pwrctrl->mask = out_args.args[1];
> +
> +out:
> +	of_node_put(out_args.np);
> +	return ret;
> +}
> +
> +static int zx_tvenc_bind(struct device *dev, struct device *master, void *data)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct drm_device *drm = data;
> +	struct resource *res;
> +	struct zx_tvenc *tvenc;
> +	int ret;
> +
> +	tvenc = devm_kzalloc(dev, sizeof(*tvenc), GFP_KERNEL);
> +	if (!tvenc)
> +		return -ENOMEM;
> +
> +	tvenc->dev = dev;
> +	dev_set_drvdata(dev, tvenc);
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	tvenc->mmio = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(tvenc->mmio)) {
> +		ret = PTR_ERR(tvenc->mmio);
> +		DRM_DEV_ERROR(dev, "failed to remap tvenc region: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = zx_tvenc_pwrctrl_init(tvenc);
> +	if (ret) {
> +		DRM_DEV_ERROR(dev, "failed to init power control: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = zx_tvenc_register(drm, tvenc);
> +	if (ret) {
> +		DRM_DEV_ERROR(dev, "failed to register tvenc: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void zx_tvenc_unbind(struct device *dev, struct device *master,
> +			    void *data)
> +{
> +	struct zx_tvenc *tvenc = dev_get_drvdata(dev);
> +
> +	tvenc->connector.funcs->destroy(&tvenc->connector);
> +	tvenc->encoder.funcs->destroy(&tvenc->encoder);

I think drm_mode_config_cleanup() should take care of calling these

> +}
> +
> +static const struct component_ops zx_tvenc_component_ops = {
> +	.bind = zx_tvenc_bind,
> +	.unbind = zx_tvenc_unbind,
> +};
> +
> +static int zx_tvenc_probe(struct platform_device *pdev)
> +{
> +	return component_add(&pdev->dev, &zx_tvenc_component_ops);
> +}
> +
> +static int zx_tvenc_remove(struct platform_device *pdev)
> +{
> +	component_del(&pdev->dev, &zx_tvenc_component_ops);
> +	return 0;
> +}
> +
> +static const struct of_device_id zx_tvenc_of_match[] = {
> +	{ .compatible = "zte,zx296718-tvenc", },
> +	{ /* end */ },
> +};
> +MODULE_DEVICE_TABLE(of, zx_tvenc_of_match);
> +
> +struct platform_driver zx_tvenc_driver = {
> +	.probe = zx_tvenc_probe,
> +	.remove = zx_tvenc_remove,
> +	.driver	= {
> +		.name = "zx-tvenc",
> +		.of_match_table	= zx_tvenc_of_match,
> +	},
> +};
> diff --git a/drivers/gpu/drm/zte/zx_tvenc_regs.h b/drivers/gpu/drm/zte/zx_tvenc_regs.h
> new file mode 100644
> index 000000000000..bd91f5dcc1f3
> --- /dev/null
> +++ b/drivers/gpu/drm/zte/zx_tvenc_regs.h
> @@ -0,0 +1,31 @@
> +/*
> + * Copyright 2017 Linaro Ltd.
> + * Copyright 2017 ZTE Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#ifndef __ZX_TVENC_REGS_H__
> +#define __ZX_TVENC_REGS_H__
> +
> +#define VENC_VIDEO_INFO			0x04
> +#define VENC_VIDEO_RES			0x08
> +#define VENC_FIELD1_PARAM		0x10
> +#define VENC_FIELD2_PARAM		0x14
> +#define VENC_LINE_O_1			0x18
> +#define VENC_LINE_E_1			0x1c
> +#define VENC_LINE_O_2			0x20
> +#define VENC_LINE_E_2			0x24
> +#define VENC_LINE_TIMING_PARAM		0x28
> +#define VENC_WEIGHT_VALUE		0x2c
> +#define VENC_BLANK_BLACK_LEVEL		0x30
> +#define VENC_BURST_LEVEL		0x34
> +#define VENC_CONTROL_PARAM		0x3c
> +#define VENC_SUB_CARRIER_PHASE1		0x40
> +#define VENC_PHASE_LINE_INCR_CVBS	0x48
> +#define VENC_ENABLE			0xa8
> +
> +#endif /* __ZX_TVENC_REGS_H__ */
> diff --git a/drivers/gpu/drm/zte/zx_vou.c b/drivers/gpu/drm/zte/zx_vou.c
> index 98f0f51f9748..61d4ff709d83 100644
> --- a/drivers/gpu/drm/zte/zx_vou.c
> +++ b/drivers/gpu/drm/zte/zx_vou.c
> @@ -192,6 +192,11 @@ struct vou_inf {
>  		.clocks_en_bits = BIT(24) | BIT(18) | BIT(6),
>  		.clocks_sel_bits = BIT(13) | BIT(2),
>  	},
> +	[VOU_TV_ENC] = {
> +		.data_sel = VOU_YUV444,
> +		.clocks_en_bits = BIT(15),
> +		.clocks_sel_bits = BIT(11) | BIT(0),
> +	},
>  };
>  
>  static inline struct zx_vou_hw *crtc_to_vou(struct drm_crtc *crtc)
> -- 
> 1.9.1
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
Shawn Guo Jan. 26, 2017, 3:06 p.m. UTC | #2
On Mon, Jan 23, 2017 at 11:10:34AM -0500, Sean Paul wrote:
> > +static const struct zx_tvenc_mode tvenc_modes[] = {
> > +	{
> > +		.name = "PAL",
> > +		.hdisplay = 720,
> > +		.vdisplay = 576,
> > +		.hfp = 12,
> > +		.hbp = 130,
> > +		.hsw = 2,
> > +		.vfp = 2,
> > +		.vbp = 20,
> > +		.vsw = 2,
> > +		.video_info = 0x00040040,
> > +		.video_res = 0x05a9c760,
> > +		.field1_param = 0x0004d416,
> > +		.field2_param = 0x0009b94f,
> > +		.burst_line_odd1 = 0x0004d406,
> > +		.burst_line_even1 = 0x0009b53e,
> > +		.burst_line_odd2 = 0x0004d805,
> > +		.burst_line_even2 = 0x0009b93f,
> > +		.line_timing_param = 0x06a96fdf,
> > +		.weight_value = 0x00c188a0,
> > +		.blank_black_level = 0x0000fcfc,
> > +		.burst_level = 0x00001595,
> > +		.control_param = 0x00000001,
> > +		.sub_carrier_phase1 = 0x1504c566,
> > +		.phase_line_incr_cvbs = 0xc068db8c,
> 
> I don't suppose you can derive these magic values from the mode?

No, we have no way to derive these register values from the mode
parameters.  The registers contain a lot of hardware specific
configurations which need to be set up differently per PAL or NTSC.

All other comments will be addressed in v2.  Thanks for the review
effort.

Shawn

Patch
diff mbox

diff --git a/drivers/gpu/drm/zte/Makefile b/drivers/gpu/drm/zte/Makefile
index 699180bfd57c..01352b56c418 100644
--- a/drivers/gpu/drm/zte/Makefile
+++ b/drivers/gpu/drm/zte/Makefile
@@ -2,6 +2,7 @@  zxdrm-y := \
 	zx_drm_drv.o \
 	zx_hdmi.o \
 	zx_plane.o \
+	zx_tvenc.o \
 	zx_vou.o
 
 obj-$(CONFIG_DRM_ZTE) += zxdrm.o
diff --git a/drivers/gpu/drm/zte/zx_drm_drv.c b/drivers/gpu/drm/zte/zx_drm_drv.c
index 3e76f72c92ff..13081fed902d 100644
--- a/drivers/gpu/drm/zte/zx_drm_drv.c
+++ b/drivers/gpu/drm/zte/zx_drm_drv.c
@@ -247,6 +247,7 @@  static int zx_drm_remove(struct platform_device *pdev)
 static struct platform_driver *drivers[] = {
 	&zx_crtc_driver,
 	&zx_hdmi_driver,
+	&zx_tvenc_driver,
 	&zx_drm_platform_driver,
 };
 
diff --git a/drivers/gpu/drm/zte/zx_drm_drv.h b/drivers/gpu/drm/zte/zx_drm_drv.h
index e65cd18a6cba..5ca035b079c7 100644
--- a/drivers/gpu/drm/zte/zx_drm_drv.h
+++ b/drivers/gpu/drm/zte/zx_drm_drv.h
@@ -13,6 +13,7 @@ 
 
 extern struct platform_driver zx_crtc_driver;
 extern struct platform_driver zx_hdmi_driver;
+extern struct platform_driver zx_tvenc_driver;
 
 static inline u32 zx_readl(void __iomem *reg)
 {
diff --git a/drivers/gpu/drm/zte/zx_tvenc.c b/drivers/gpu/drm/zte/zx_tvenc.c
new file mode 100644
index 000000000000..5a6cff1ff8a8
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_tvenc.c
@@ -0,0 +1,416 @@ 
+/*
+ * Copyright 2017 Linaro Ltd.
+ * Copyright 2017 ZTE Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drmP.h>
+
+#include "zx_drm_drv.h"
+#include "zx_tvenc_regs.h"
+#include "zx_vou.h"
+
+struct zx_tvenc_pwrctrl {
+	struct regmap *regmap;
+	u32 reg;
+	u32 mask;
+};
+
+struct zx_tvenc {
+	struct drm_connector connector;
+	struct drm_encoder encoder;
+	struct device *dev;
+	void __iomem *mmio;
+	const struct vou_inf *inf;
+	struct zx_tvenc_pwrctrl pwrctrl;
+};
+
+#define to_zx_tvenc(x) container_of(x, struct zx_tvenc, x)
+
+struct zx_tvenc_mode {
+	char *name;
+	u32 hdisplay;
+	u32 vdisplay;
+	u32 hfp;
+	u32 hbp;
+	u32 hsw;
+	u32 vfp;
+	u32 vbp;
+	u32 vsw;
+	u32 video_info;
+	u32 video_res;
+	u32 field1_param;
+	u32 field2_param;
+	u32 burst_line_odd1;
+	u32 burst_line_even1;
+	u32 burst_line_odd2;
+	u32 burst_line_even2;
+	u32 line_timing_param;
+	u32 weight_value;
+	u32 blank_black_level;
+	u32 burst_level;
+	u32 control_param;
+	u32 sub_carrier_phase1;
+	u32 phase_line_incr_cvbs;
+};
+
+static const struct zx_tvenc_mode tvenc_modes[] = {
+	{
+		.name = "PAL",
+		.hdisplay = 720,
+		.vdisplay = 576,
+		.hfp = 12,
+		.hbp = 130,
+		.hsw = 2,
+		.vfp = 2,
+		.vbp = 20,
+		.vsw = 2,
+		.video_info = 0x00040040,
+		.video_res = 0x05a9c760,
+		.field1_param = 0x0004d416,
+		.field2_param = 0x0009b94f,
+		.burst_line_odd1 = 0x0004d406,
+		.burst_line_even1 = 0x0009b53e,
+		.burst_line_odd2 = 0x0004d805,
+		.burst_line_even2 = 0x0009b93f,
+		.line_timing_param = 0x06a96fdf,
+		.weight_value = 0x00c188a0,
+		.blank_black_level = 0x0000fcfc,
+		.burst_level = 0x00001595,
+		.control_param = 0x00000001,
+		.sub_carrier_phase1 = 0x1504c566,
+		.phase_line_incr_cvbs = 0xc068db8c,
+	}, {
+		.name = "NTSC",
+		.hdisplay = 720,
+		.vdisplay = 480,
+		.hfp = 16,
+		.hbp = 120,
+		.hsw = 2,
+		.vfp = 3,
+		.vbp = 17,
+		.vsw = 2,
+		.video_info = 0x00040080,
+		.video_res = 0x05a8375a,
+		.field1_param = 0x00041817,
+		.field2_param = 0x0008351e,
+		.burst_line_odd1 = 0x00041006,
+		.burst_line_even1 = 0x0008290d,
+		.burst_line_odd2 = 0x00000000,
+		.burst_line_even2 = 0x00000000,
+		.line_timing_param = 0x06a8ef9e,
+		.weight_value = 0x00b68197,
+		.blank_black_level = 0x0000f0f0,
+		.burst_level = 0x0000009c,
+		.control_param = 0x00000001,
+		.sub_carrier_phase1 = 0x10f83e10,
+		.phase_line_incr_cvbs = 0x80000000,
+	},
+};
+
+static const struct zx_tvenc_mode *
+zx_tvenc_find_zmode(struct drm_display_mode *mode)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(tvenc_modes); i++) {
+		const struct zx_tvenc_mode *zmode = &tvenc_modes[i];
+
+		if (!strcmp(mode->name, zmode->name))
+			return zmode;
+	}
+
+	return NULL;
+}
+
+static void zx_tvenc_encoder_mode_set(struct drm_encoder *encoder,
+				      struct drm_display_mode *mode,
+				      struct drm_display_mode *adj_mode)
+{
+	struct zx_tvenc *tvenc = to_zx_tvenc(encoder);
+	const struct zx_tvenc_mode *zmode;
+	struct vou_div_config configs[] = {
+		{ VOU_DIV_INF,   VOU_DIV_4 },
+		{ VOU_DIV_TVENC, VOU_DIV_1 },
+		{ VOU_DIV_LAYER, VOU_DIV_2 },
+	};
+
+	zx_vou_config_dividers(encoder->crtc, configs, ARRAY_SIZE(configs));
+
+	zmode = zx_tvenc_find_zmode(mode);
+	if (!zmode) {
+		DRM_DEV_ERROR(tvenc->dev, "failed to find zmode\n");
+		return;
+	}
+
+	zx_writel(tvenc->mmio + VENC_VIDEO_INFO, zmode->video_info);
+	zx_writel(tvenc->mmio + VENC_VIDEO_RES, zmode->video_res);
+	zx_writel(tvenc->mmio + VENC_FIELD1_PARAM, zmode->field1_param);
+	zx_writel(tvenc->mmio + VENC_FIELD2_PARAM, zmode->field2_param);
+	zx_writel(tvenc->mmio + VENC_LINE_O_1, zmode->burst_line_odd1);
+	zx_writel(tvenc->mmio + VENC_LINE_E_1, zmode->burst_line_even1);
+	zx_writel(tvenc->mmio + VENC_LINE_O_2, zmode->burst_line_odd2);
+	zx_writel(tvenc->mmio + VENC_LINE_E_2, zmode->burst_line_even2);
+	zx_writel(tvenc->mmio + VENC_LINE_TIMING_PARAM,
+		  zmode->line_timing_param);
+	zx_writel(tvenc->mmio + VENC_WEIGHT_VALUE, zmode->weight_value);
+	zx_writel(tvenc->mmio + VENC_BLANK_BLACK_LEVEL,
+		  zmode->blank_black_level);
+	zx_writel(tvenc->mmio + VENC_BURST_LEVEL, zmode->burst_level);
+	zx_writel(tvenc->mmio + VENC_CONTROL_PARAM, zmode->control_param);
+	zx_writel(tvenc->mmio + VENC_SUB_CARRIER_PHASE1,
+		  zmode->sub_carrier_phase1);
+	zx_writel(tvenc->mmio + VENC_PHASE_LINE_INCR_CVBS,
+		  zmode->phase_line_incr_cvbs);
+}
+
+static void zx_tvenc_encoder_enable(struct drm_encoder *encoder)
+{
+	struct zx_tvenc *tvenc = to_zx_tvenc(encoder);
+	struct zx_tvenc_pwrctrl *pwrctrl = &tvenc->pwrctrl;
+
+	/* Set bit to power up TVENC DAC */
+	regmap_update_bits(pwrctrl->regmap, pwrctrl->reg, pwrctrl->mask,
+			   pwrctrl->mask);
+
+	vou_inf_enable(VOU_TV_ENC, encoder->crtc);
+
+	zx_writel(tvenc->mmio + VENC_ENABLE, 1);
+}
+
+static void zx_tvenc_encoder_disable(struct drm_encoder *encoder)
+{
+	struct zx_tvenc *tvenc = to_zx_tvenc(encoder);
+	struct zx_tvenc_pwrctrl *pwrctrl = &tvenc->pwrctrl;
+
+	zx_writel(tvenc->mmio + VENC_ENABLE, 0);
+
+	vou_inf_disable(VOU_TV_ENC, encoder->crtc);
+
+	/* Clear bit to power down TVENC DAC */
+	regmap_update_bits(pwrctrl->regmap, pwrctrl->reg, pwrctrl->mask, 0);
+}
+
+static const struct drm_encoder_helper_funcs zx_tvenc_encoder_helper_funcs = {
+	.enable	= zx_tvenc_encoder_enable,
+	.disable = zx_tvenc_encoder_disable,
+	.mode_set = zx_tvenc_encoder_mode_set,
+};
+
+static const struct drm_encoder_funcs zx_tvenc_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static void zx_tvenc_mode_to_drm_mode(const struct zx_tvenc_mode *zmode,
+				      struct drm_display_mode *mode)
+{
+	strcpy(mode->name, zmode->name);
+
+	mode->type = DRM_MODE_TYPE_DRIVER;
+	mode->flags = DRM_MODE_FLAG_INTERLACE;
+	mode->flags |= DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC;
+
+	/*
+	 * The CRM cannot directly provide such a low rate, and we have to
+	 * ask a multiflied rate from CRM and use the divider in VOU to get
+	 * the disired one.
+	 */
+	mode->clock = 13500 * 4;
+
+	mode->hdisplay = zmode->hdisplay;
+	mode->hsync_start = mode->hdisplay + zmode->hfp;
+	mode->hsync_end = mode->hsync_start + zmode->hsw;
+	mode->htotal = mode->hsync_end + zmode->hbp;
+
+	mode->vdisplay = zmode->vdisplay;
+	mode->vsync_start = mode->vdisplay + zmode->vfp;
+	mode->vsync_end = mode->vsync_start + zmode->vsw;
+	mode->vtotal = mode->vsync_end + zmode->vbp;
+}
+
+static int zx_tvenc_connector_get_modes(struct drm_connector *connector)
+{
+	struct zx_tvenc *tvenc = to_zx_tvenc(connector);
+	struct device *dev = tvenc->dev;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(tvenc_modes); i++) {
+		struct drm_display_mode *mode;
+		const struct zx_tvenc_mode *zmode = &tvenc_modes[i];
+
+		mode = drm_mode_create(connector->dev);
+		if (!mode) {
+			DRM_DEV_ERROR(dev, "failed to create drm mode\n");
+			return 0;
+		}
+
+		zx_tvenc_mode_to_drm_mode(zmode, mode);
+		drm_mode_probed_add(connector, mode);
+	}
+
+	return i;
+}
+
+static enum drm_mode_status
+zx_tvenc_connector_mode_valid(struct drm_connector *connector,
+			      struct drm_display_mode *mode)
+{
+	return MODE_OK;
+}
+
+static struct drm_connector_helper_funcs zx_tvenc_connector_helper_funcs = {
+	.get_modes = zx_tvenc_connector_get_modes,
+	.mode_valid = zx_tvenc_connector_mode_valid,
+};
+
+static const struct drm_connector_funcs zx_tvenc_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = drm_connector_cleanup,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int zx_tvenc_register(struct drm_device *drm, struct zx_tvenc *tvenc)
+{
+	struct drm_encoder *encoder = &tvenc->encoder;
+	struct drm_connector *connector = &tvenc->connector;
+
+	/*
+	 * The tvenc is designed to use aux channel, as there is a deflicker
+	 * block for the channel.
+	 */
+	encoder->possible_crtcs = BIT(1);
+
+	drm_encoder_init(drm, encoder, &zx_tvenc_encoder_funcs,
+			 DRM_MODE_ENCODER_TVDAC, NULL);
+	drm_encoder_helper_add(encoder, &zx_tvenc_encoder_helper_funcs);
+
+	connector->interlace_allowed = true;
+
+	drm_connector_init(drm, connector, &zx_tvenc_connector_funcs,
+			   DRM_MODE_CONNECTOR_Composite);
+	drm_connector_helper_add(connector, &zx_tvenc_connector_helper_funcs);
+
+	drm_mode_connector_attach_encoder(connector, encoder);
+
+	return 0;
+}
+
+static int zx_tvenc_pwrctrl_init(struct zx_tvenc *tvenc)
+{
+	struct zx_tvenc_pwrctrl *pwrctrl = &tvenc->pwrctrl;
+	struct device *dev = tvenc->dev;
+	struct of_phandle_args out_args;
+	struct regmap *regmap;
+	int ret;
+
+	ret = of_parse_phandle_with_fixed_args(dev->of_node,
+				"zte,tvenc-power-control", 2, 0, &out_args);
+	if (ret)
+		return ret;
+
+	regmap = syscon_node_to_regmap(out_args.np);
+	if (IS_ERR(regmap)) {
+		ret = PTR_ERR(regmap);
+		goto out;
+	}
+
+	pwrctrl->regmap = regmap;
+	pwrctrl->reg = out_args.args[0];
+	pwrctrl->mask = out_args.args[1];
+
+out:
+	of_node_put(out_args.np);
+	return ret;
+}
+
+static int zx_tvenc_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct drm_device *drm = data;
+	struct resource *res;
+	struct zx_tvenc *tvenc;
+	int ret;
+
+	tvenc = devm_kzalloc(dev, sizeof(*tvenc), GFP_KERNEL);
+	if (!tvenc)
+		return -ENOMEM;
+
+	tvenc->dev = dev;
+	dev_set_drvdata(dev, tvenc);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	tvenc->mmio = devm_ioremap_resource(dev, res);
+	if (IS_ERR(tvenc->mmio)) {
+		ret = PTR_ERR(tvenc->mmio);
+		DRM_DEV_ERROR(dev, "failed to remap tvenc region: %d\n", ret);
+		return ret;
+	}
+
+	ret = zx_tvenc_pwrctrl_init(tvenc);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "failed to init power control: %d\n", ret);
+		return ret;
+	}
+
+	ret = zx_tvenc_register(drm, tvenc);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "failed to register tvenc: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void zx_tvenc_unbind(struct device *dev, struct device *master,
+			    void *data)
+{
+	struct zx_tvenc *tvenc = dev_get_drvdata(dev);
+
+	tvenc->connector.funcs->destroy(&tvenc->connector);
+	tvenc->encoder.funcs->destroy(&tvenc->encoder);
+}
+
+static const struct component_ops zx_tvenc_component_ops = {
+	.bind = zx_tvenc_bind,
+	.unbind = zx_tvenc_unbind,
+};
+
+static int zx_tvenc_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &zx_tvenc_component_ops);
+}
+
+static int zx_tvenc_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &zx_tvenc_component_ops);
+	return 0;
+}
+
+static const struct of_device_id zx_tvenc_of_match[] = {
+	{ .compatible = "zte,zx296718-tvenc", },
+	{ /* end */ },
+};
+MODULE_DEVICE_TABLE(of, zx_tvenc_of_match);
+
+struct platform_driver zx_tvenc_driver = {
+	.probe = zx_tvenc_probe,
+	.remove = zx_tvenc_remove,
+	.driver	= {
+		.name = "zx-tvenc",
+		.of_match_table	= zx_tvenc_of_match,
+	},
+};
diff --git a/drivers/gpu/drm/zte/zx_tvenc_regs.h b/drivers/gpu/drm/zte/zx_tvenc_regs.h
new file mode 100644
index 000000000000..bd91f5dcc1f3
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_tvenc_regs.h
@@ -0,0 +1,31 @@ 
+/*
+ * Copyright 2017 Linaro Ltd.
+ * Copyright 2017 ZTE Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __ZX_TVENC_REGS_H__
+#define __ZX_TVENC_REGS_H__
+
+#define VENC_VIDEO_INFO			0x04
+#define VENC_VIDEO_RES			0x08
+#define VENC_FIELD1_PARAM		0x10
+#define VENC_FIELD2_PARAM		0x14
+#define VENC_LINE_O_1			0x18
+#define VENC_LINE_E_1			0x1c
+#define VENC_LINE_O_2			0x20
+#define VENC_LINE_E_2			0x24
+#define VENC_LINE_TIMING_PARAM		0x28
+#define VENC_WEIGHT_VALUE		0x2c
+#define VENC_BLANK_BLACK_LEVEL		0x30
+#define VENC_BURST_LEVEL		0x34
+#define VENC_CONTROL_PARAM		0x3c
+#define VENC_SUB_CARRIER_PHASE1		0x40
+#define VENC_PHASE_LINE_INCR_CVBS	0x48
+#define VENC_ENABLE			0xa8
+
+#endif /* __ZX_TVENC_REGS_H__ */
diff --git a/drivers/gpu/drm/zte/zx_vou.c b/drivers/gpu/drm/zte/zx_vou.c
index 98f0f51f9748..61d4ff709d83 100644
--- a/drivers/gpu/drm/zte/zx_vou.c
+++ b/drivers/gpu/drm/zte/zx_vou.c
@@ -192,6 +192,11 @@  struct vou_inf {
 		.clocks_en_bits = BIT(24) | BIT(18) | BIT(6),
 		.clocks_sel_bits = BIT(13) | BIT(2),
 	},
+	[VOU_TV_ENC] = {
+		.data_sel = VOU_YUV444,
+		.clocks_en_bits = BIT(15),
+		.clocks_sel_bits = BIT(11) | BIT(0),
+	},
 };
 
 static inline struct zx_vou_hw *crtc_to_vou(struct drm_crtc *crtc)