Message ID | 1484843100-16284-6-git-send-email-shawnguo@kernel.org |
---|---|
State | New |
Headers | show |
Series | Add TV Encoder support for ZTE DRM driver | expand |
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
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)