diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/gpu/drm/zte | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/zte')
-rw-r--r-- | drivers/gpu/drm/zte/Kconfig | 9 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/Makefile | 10 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_common_regs.h | 31 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_drm_drv.c | 215 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_drm_drv.h | 38 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_hdmi.c | 763 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_hdmi_regs.h | 70 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_plane.c | 540 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_plane.h | 30 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_plane_regs.h | 124 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_tvenc.c | 406 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_tvenc_regs.h | 31 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_vga.c | 530 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_vga_regs.h | 36 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_vou.c | 922 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_vou.h | 68 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_vou_regs.h | 216 |
17 files changed, 4039 insertions, 0 deletions
diff --git a/drivers/gpu/drm/zte/Kconfig b/drivers/gpu/drm/zte/Kconfig new file mode 100644 index 000000000..75b70126d --- /dev/null +++ b/drivers/gpu/drm/zte/Kconfig @@ -0,0 +1,9 @@ +config DRM_ZTE + tristate "DRM Support for ZTE SoCs" + depends on DRM && ARCH_ZX + select DRM_KMS_CMA_HELPER + select DRM_KMS_HELPER + select SND_SOC_HDMI_CODEC if SND_SOC + select VIDEOMODE_HELPERS + help + Choose this option to enable DRM on ZTE ZX SoCs. diff --git a/drivers/gpu/drm/zte/Makefile b/drivers/gpu/drm/zte/Makefile new file mode 100644 index 000000000..b6d966d84 --- /dev/null +++ b/drivers/gpu/drm/zte/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +zxdrm-y := \ + zx_drm_drv.o \ + zx_hdmi.o \ + zx_plane.o \ + zx_tvenc.o \ + zx_vga.o \ + zx_vou.o + +obj-$(CONFIG_DRM_ZTE) += zxdrm.o diff --git a/drivers/gpu/drm/zte/zx_common_regs.h b/drivers/gpu/drm/zte/zx_common_regs.h new file mode 100644 index 000000000..2afd80664 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_common_regs.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 Sanechips Technology Co., Ltd. + * Copyright 2017 Linaro Ltd. + * + * 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_COMMON_REGS_H__ +#define __ZX_COMMON_REGS_H__ + +/* CSC registers */ +#define CSC_CTRL0 0x30 +#define CSC_COV_MODE_SHIFT 16 +#define CSC_COV_MODE_MASK (0xffff << CSC_COV_MODE_SHIFT) +#define CSC_BT601_IMAGE_RGB2YCBCR 0 +#define CSC_BT601_IMAGE_YCBCR2RGB 1 +#define CSC_BT601_VIDEO_RGB2YCBCR 2 +#define CSC_BT601_VIDEO_YCBCR2RGB 3 +#define CSC_BT709_IMAGE_RGB2YCBCR 4 +#define CSC_BT709_IMAGE_YCBCR2RGB 5 +#define CSC_BT709_VIDEO_RGB2YCBCR 6 +#define CSC_BT709_VIDEO_YCBCR2RGB 7 +#define CSC_BT2020_IMAGE_RGB2YCBCR 8 +#define CSC_BT2020_IMAGE_YCBCR2RGB 9 +#define CSC_BT2020_VIDEO_RGB2YCBCR 10 +#define CSC_BT2020_VIDEO_YCBCR2RGB 11 +#define CSC_WORK_ENABLE BIT(0) + +#endif /* __ZX_COMMON_REGS_H__ */ diff --git a/drivers/gpu/drm/zte/zx_drm_drv.c b/drivers/gpu/drm/zte/zx_drm_drv.c new file mode 100644 index 000000000..6f4205e80 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_drm_drv.c @@ -0,0 +1,215 @@ +/* + * Copyright 2016 Linaro Ltd. + * Copyright 2016 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/list.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/of_platform.h> +#include <linux/spinlock.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_of.h> +#include <drm/drmP.h> + +#include "zx_drm_drv.h" +#include "zx_vou.h" + +static const struct drm_mode_config_funcs zx_drm_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .output_poll_changed = drm_fb_helper_output_poll_changed, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +DEFINE_DRM_GEM_CMA_FOPS(zx_drm_fops); + +static struct drm_driver zx_drm_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | + DRIVER_ATOMIC, + .lastclose = drm_fb_helper_lastclose, + .gem_free_object_unlocked = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .dumb_create = drm_gem_cma_dumb_create, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, + .gem_prime_vmap = drm_gem_cma_prime_vmap, + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, + .gem_prime_mmap = drm_gem_cma_prime_mmap, + .fops = &zx_drm_fops, + .name = "zx-vou", + .desc = "ZTE VOU Controller DRM", + .date = "20160811", + .major = 1, + .minor = 0, +}; + +static int zx_drm_bind(struct device *dev) +{ + struct drm_device *drm; + int ret; + + drm = drm_dev_alloc(&zx_drm_driver, dev); + if (IS_ERR(drm)) + return PTR_ERR(drm); + + dev_set_drvdata(dev, drm); + + drm_mode_config_init(drm); + drm->mode_config.min_width = 16; + drm->mode_config.min_height = 16; + drm->mode_config.max_width = 4096; + drm->mode_config.max_height = 4096; + drm->mode_config.funcs = &zx_drm_mode_config_funcs; + + ret = component_bind_all(dev, drm); + if (ret) { + DRM_DEV_ERROR(dev, "failed to bind all components: %d\n", ret); + goto out_unregister; + } + + ret = drm_vblank_init(drm, drm->mode_config.num_crtc); + if (ret < 0) { + DRM_DEV_ERROR(dev, "failed to init vblank: %d\n", ret); + goto out_unbind; + } + + /* + * We will manage irq handler on our own. In this case, irq_enabled + * need to be true for using vblank core support. + */ + drm->irq_enabled = true; + + drm_mode_config_reset(drm); + drm_kms_helper_poll_init(drm); + + ret = drm_fb_cma_fbdev_init(drm, 32, 0); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init cma fbdev: %d\n", ret); + goto out_poll_fini; + } + + ret = drm_dev_register(drm, 0); + if (ret) + goto out_fbdev_fini; + + return 0; + +out_fbdev_fini: + drm_fb_cma_fbdev_fini(drm); +out_poll_fini: + drm_kms_helper_poll_fini(drm); + drm_mode_config_cleanup(drm); +out_unbind: + component_unbind_all(dev, drm); +out_unregister: + dev_set_drvdata(dev, NULL); + drm_dev_unref(drm); + return ret; +} + +static void zx_drm_unbind(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + drm_dev_unregister(drm); + drm_fb_cma_fbdev_fini(drm); + drm_kms_helper_poll_fini(drm); + drm_mode_config_cleanup(drm); + component_unbind_all(dev, drm); + dev_set_drvdata(dev, NULL); + drm_dev_unref(drm); +} + +static const struct component_master_ops zx_drm_master_ops = { + .bind = zx_drm_bind, + .unbind = zx_drm_unbind, +}; + +static int compare_of(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +static int zx_drm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *parent = dev->of_node; + struct device_node *child; + struct component_match *match = NULL; + int ret; + + ret = devm_of_platform_populate(dev); + if (ret) + return ret; + + for_each_available_child_of_node(parent, child) { + component_match_add(dev, &match, compare_of, child); + of_node_put(child); + } + + return component_master_add_with_match(dev, &zx_drm_master_ops, match); +} + +static int zx_drm_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &zx_drm_master_ops); + return 0; +} + +static const struct of_device_id zx_drm_of_match[] = { + { .compatible = "zte,zx296718-vou", }, + { /* end */ }, +}; +MODULE_DEVICE_TABLE(of, zx_drm_of_match); + +static struct platform_driver zx_drm_platform_driver = { + .probe = zx_drm_probe, + .remove = zx_drm_remove, + .driver = { + .name = "zx-drm", + .of_match_table = zx_drm_of_match, + }, +}; + +static struct platform_driver *drivers[] = { + &zx_crtc_driver, + &zx_hdmi_driver, + &zx_tvenc_driver, + &zx_vga_driver, + &zx_drm_platform_driver, +}; + +static int zx_drm_init(void) +{ + return platform_register_drivers(drivers, ARRAY_SIZE(drivers)); +} +module_init(zx_drm_init); + +static void zx_drm_exit(void) +{ + platform_unregister_drivers(drivers, ARRAY_SIZE(drivers)); +} +module_exit(zx_drm_exit); + +MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>"); +MODULE_DESCRIPTION("ZTE ZX VOU DRM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/zte/zx_drm_drv.h b/drivers/gpu/drm/zte/zx_drm_drv.h new file mode 100644 index 000000000..2a8cdc5f8 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_drm_drv.h @@ -0,0 +1,38 @@ +/* + * Copyright 2016 Linaro Ltd. + * Copyright 2016 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_DRM_DRV_H__ +#define __ZX_DRM_DRV_H__ + +extern struct platform_driver zx_crtc_driver; +extern struct platform_driver zx_hdmi_driver; +extern struct platform_driver zx_tvenc_driver; +extern struct platform_driver zx_vga_driver; + +static inline u32 zx_readl(void __iomem *reg) +{ + return readl_relaxed(reg); +} + +static inline void zx_writel(void __iomem *reg, u32 val) +{ + writel_relaxed(val, reg); +} + +static inline void zx_writel_mask(void __iomem *reg, u32 mask, u32 val) +{ + u32 tmp; + + tmp = zx_readl(reg); + tmp = (tmp & ~mask) | (val & mask); + zx_writel(reg, tmp); +} + +#endif /* __ZX_DRM_DRV_H__ */ diff --git a/drivers/gpu/drm/zte/zx_hdmi.c b/drivers/gpu/drm/zte/zx_hdmi.c new file mode 100644 index 000000000..78655269d --- /dev/null +++ b/drivers/gpu/drm/zte/zx_hdmi.c @@ -0,0 +1,763 @@ +/* + * Copyright 2016 Linaro Ltd. + * Copyright 2016 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/delay.h> +#include <linux/err.h> +#include <linux/hdmi.h> +#include <linux/irq.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_device.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_of.h> +#include <drm/drmP.h> + +#include <sound/hdmi-codec.h> + +#include "zx_hdmi_regs.h" +#include "zx_vou.h" + +#define ZX_HDMI_INFOFRAME_SIZE 31 +#define DDC_SEGMENT_ADDR 0x30 + +struct zx_hdmi_i2c { + struct i2c_adapter adap; + struct mutex lock; +}; + +struct zx_hdmi { + struct drm_connector connector; + struct drm_encoder encoder; + struct zx_hdmi_i2c *ddc; + struct device *dev; + struct drm_device *drm; + void __iomem *mmio; + struct clk *cec_clk; + struct clk *osc_clk; + struct clk *xclk; + bool sink_is_hdmi; + bool sink_has_audio; + struct platform_device *audio_pdev; +}; + +#define to_zx_hdmi(x) container_of(x, struct zx_hdmi, x) + +static inline u8 hdmi_readb(struct zx_hdmi *hdmi, u16 offset) +{ + return readl_relaxed(hdmi->mmio + offset * 4); +} + +static inline void hdmi_writeb(struct zx_hdmi *hdmi, u16 offset, u8 val) +{ + writel_relaxed(val, hdmi->mmio + offset * 4); +} + +static inline void hdmi_writeb_mask(struct zx_hdmi *hdmi, u16 offset, + u8 mask, u8 val) +{ + u8 tmp; + + tmp = hdmi_readb(hdmi, offset); + tmp = (tmp & ~mask) | (val & mask); + hdmi_writeb(hdmi, offset, tmp); +} + +static int zx_hdmi_infoframe_trans(struct zx_hdmi *hdmi, + union hdmi_infoframe *frame, u8 fsel) +{ + u8 buffer[ZX_HDMI_INFOFRAME_SIZE]; + int num; + int i; + + hdmi_writeb(hdmi, TPI_INFO_FSEL, fsel); + + num = hdmi_infoframe_pack(frame, buffer, ZX_HDMI_INFOFRAME_SIZE); + if (num < 0) { + DRM_DEV_ERROR(hdmi->dev, "failed to pack infoframe: %d\n", num); + return num; + } + + for (i = 0; i < num; i++) + hdmi_writeb(hdmi, TPI_INFO_B0 + i, buffer[i]); + + hdmi_writeb_mask(hdmi, TPI_INFO_EN, TPI_INFO_TRANS_RPT, + TPI_INFO_TRANS_RPT); + hdmi_writeb_mask(hdmi, TPI_INFO_EN, TPI_INFO_TRANS_EN, + TPI_INFO_TRANS_EN); + + return num; +} + +static int zx_hdmi_config_video_vsi(struct zx_hdmi *hdmi, + struct drm_display_mode *mode) +{ + union hdmi_infoframe frame; + int ret; + + ret = drm_hdmi_vendor_infoframe_from_display_mode(&frame.vendor.hdmi, + &hdmi->connector, + mode); + if (ret) { + DRM_DEV_ERROR(hdmi->dev, "failed to get vendor infoframe: %d\n", + ret); + return ret; + } + + return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_VSIF); +} + +static int zx_hdmi_config_video_avi(struct zx_hdmi *hdmi, + struct drm_display_mode *mode) +{ + union hdmi_infoframe frame; + int ret; + + ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, mode, false); + if (ret) { + DRM_DEV_ERROR(hdmi->dev, "failed to get avi infoframe: %d\n", + ret); + return ret; + } + + /* We always use YUV444 for HDMI output. */ + frame.avi.colorspace = HDMI_COLORSPACE_YUV444; + + return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_AVI); +} + +static void zx_hdmi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct zx_hdmi *hdmi = to_zx_hdmi(encoder); + + if (hdmi->sink_is_hdmi) { + zx_hdmi_config_video_avi(hdmi, mode); + zx_hdmi_config_video_vsi(hdmi, mode); + } +} + +static void zx_hdmi_phy_start(struct zx_hdmi *hdmi) +{ + /* Copy from ZTE BSP code */ + hdmi_writeb(hdmi, 0x222, 0x0); + hdmi_writeb(hdmi, 0x224, 0x4); + hdmi_writeb(hdmi, 0x909, 0x0); + hdmi_writeb(hdmi, 0x7b0, 0x90); + hdmi_writeb(hdmi, 0x7b1, 0x00); + hdmi_writeb(hdmi, 0x7b2, 0xa7); + hdmi_writeb(hdmi, 0x7b8, 0xaa); + hdmi_writeb(hdmi, 0x7b2, 0xa7); + hdmi_writeb(hdmi, 0x7b3, 0x0f); + hdmi_writeb(hdmi, 0x7b4, 0x0f); + hdmi_writeb(hdmi, 0x7b5, 0x55); + hdmi_writeb(hdmi, 0x7b7, 0x03); + hdmi_writeb(hdmi, 0x7b9, 0x12); + hdmi_writeb(hdmi, 0x7ba, 0x32); + hdmi_writeb(hdmi, 0x7bc, 0x68); + hdmi_writeb(hdmi, 0x7be, 0x40); + hdmi_writeb(hdmi, 0x7bf, 0x84); + hdmi_writeb(hdmi, 0x7c1, 0x0f); + hdmi_writeb(hdmi, 0x7c8, 0x02); + hdmi_writeb(hdmi, 0x7c9, 0x03); + hdmi_writeb(hdmi, 0x7ca, 0x40); + hdmi_writeb(hdmi, 0x7dc, 0x31); + hdmi_writeb(hdmi, 0x7e2, 0x04); + hdmi_writeb(hdmi, 0x7e0, 0x06); + hdmi_writeb(hdmi, 0x7cb, 0x68); + hdmi_writeb(hdmi, 0x7f9, 0x02); + hdmi_writeb(hdmi, 0x7b6, 0x02); + hdmi_writeb(hdmi, 0x7f3, 0x0); +} + +static void zx_hdmi_hw_enable(struct zx_hdmi *hdmi) +{ + /* Enable pclk */ + hdmi_writeb_mask(hdmi, CLKPWD, CLKPWD_PDIDCK, CLKPWD_PDIDCK); + + /* Enable HDMI for TX */ + hdmi_writeb_mask(hdmi, FUNC_SEL, FUNC_HDMI_EN, FUNC_HDMI_EN); + + /* Enable deep color packet */ + hdmi_writeb_mask(hdmi, P2T_CTRL, P2T_DC_PKT_EN, P2T_DC_PKT_EN); + + /* Enable HDMI/MHL mode for output */ + hdmi_writeb_mask(hdmi, TEST_TXCTRL, TEST_TXCTRL_HDMI_MODE, + TEST_TXCTRL_HDMI_MODE); + + /* Configure reg_qc_sel */ + hdmi_writeb(hdmi, HDMICTL4, 0x3); + + /* Enable interrupt */ + hdmi_writeb_mask(hdmi, INTR1_MASK, INTR1_MONITOR_DETECT, + INTR1_MONITOR_DETECT); + + /* Start up phy */ + zx_hdmi_phy_start(hdmi); +} + +static void zx_hdmi_hw_disable(struct zx_hdmi *hdmi) +{ + /* Disable interrupt */ + hdmi_writeb_mask(hdmi, INTR1_MASK, INTR1_MONITOR_DETECT, 0); + + /* Disable deep color packet */ + hdmi_writeb_mask(hdmi, P2T_CTRL, P2T_DC_PKT_EN, P2T_DC_PKT_EN); + + /* Disable HDMI for TX */ + hdmi_writeb_mask(hdmi, FUNC_SEL, FUNC_HDMI_EN, 0); + + /* Disable pclk */ + hdmi_writeb_mask(hdmi, CLKPWD, CLKPWD_PDIDCK, 0); +} + +static void zx_hdmi_encoder_enable(struct drm_encoder *encoder) +{ + struct zx_hdmi *hdmi = to_zx_hdmi(encoder); + + clk_prepare_enable(hdmi->cec_clk); + clk_prepare_enable(hdmi->osc_clk); + clk_prepare_enable(hdmi->xclk); + + zx_hdmi_hw_enable(hdmi); + + vou_inf_enable(VOU_HDMI, encoder->crtc); +} + +static void zx_hdmi_encoder_disable(struct drm_encoder *encoder) +{ + struct zx_hdmi *hdmi = to_zx_hdmi(encoder); + + vou_inf_disable(VOU_HDMI, encoder->crtc); + + zx_hdmi_hw_disable(hdmi); + + clk_disable_unprepare(hdmi->xclk); + clk_disable_unprepare(hdmi->osc_clk); + clk_disable_unprepare(hdmi->cec_clk); +} + +static const struct drm_encoder_helper_funcs zx_hdmi_encoder_helper_funcs = { + .enable = zx_hdmi_encoder_enable, + .disable = zx_hdmi_encoder_disable, + .mode_set = zx_hdmi_encoder_mode_set, +}; + +static const struct drm_encoder_funcs zx_hdmi_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static int zx_hdmi_connector_get_modes(struct drm_connector *connector) +{ + struct zx_hdmi *hdmi = to_zx_hdmi(connector); + struct edid *edid; + int ret; + + edid = drm_get_edid(connector, &hdmi->ddc->adap); + if (!edid) + return 0; + + hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid); + hdmi->sink_has_audio = drm_detect_monitor_audio(edid); + drm_connector_update_edid_property(connector, edid); + ret = drm_add_edid_modes(connector, edid); + kfree(edid); + + return ret; +} + +static enum drm_mode_status +zx_hdmi_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static struct drm_connector_helper_funcs zx_hdmi_connector_helper_funcs = { + .get_modes = zx_hdmi_connector_get_modes, + .mode_valid = zx_hdmi_connector_mode_valid, +}; + +static enum drm_connector_status +zx_hdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct zx_hdmi *hdmi = to_zx_hdmi(connector); + + return (hdmi_readb(hdmi, TPI_HPD_RSEN) & TPI_HPD_CONNECTION) ? + connector_status_connected : connector_status_disconnected; +} + +static const struct drm_connector_funcs zx_hdmi_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = zx_hdmi_connector_detect, + .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_hdmi_register(struct drm_device *drm, struct zx_hdmi *hdmi) +{ + struct drm_encoder *encoder = &hdmi->encoder; + + encoder->possible_crtcs = VOU_CRTC_MASK; + + drm_encoder_init(drm, encoder, &zx_hdmi_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + drm_encoder_helper_add(encoder, &zx_hdmi_encoder_helper_funcs); + + hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_init(drm, &hdmi->connector, &zx_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + drm_connector_helper_add(&hdmi->connector, + &zx_hdmi_connector_helper_funcs); + + drm_connector_attach_encoder(&hdmi->connector, encoder); + + return 0; +} + +static irqreturn_t zx_hdmi_irq_thread(int irq, void *dev_id) +{ + struct zx_hdmi *hdmi = dev_id; + + drm_helper_hpd_irq_event(hdmi->connector.dev); + + return IRQ_HANDLED; +} + +static irqreturn_t zx_hdmi_irq_handler(int irq, void *dev_id) +{ + struct zx_hdmi *hdmi = dev_id; + u8 lstat; + + lstat = hdmi_readb(hdmi, L1_INTR_STAT); + + /* Monitor detect/HPD interrupt */ + if (lstat & L1_INTR_STAT_INTR1) { + u8 stat; + + stat = hdmi_readb(hdmi, INTR1_STAT); + hdmi_writeb(hdmi, INTR1_STAT, stat); + + if (stat & INTR1_MONITOR_DETECT) + return IRQ_WAKE_THREAD; + } + + return IRQ_NONE; +} + +static int zx_hdmi_audio_startup(struct device *dev, void *data) +{ + struct zx_hdmi *hdmi = dev_get_drvdata(dev); + struct drm_encoder *encoder = &hdmi->encoder; + + vou_inf_hdmi_audio_sel(encoder->crtc, VOU_HDMI_AUD_SPDIF); + + return 0; +} + +static void zx_hdmi_audio_shutdown(struct device *dev, void *data) +{ + struct zx_hdmi *hdmi = dev_get_drvdata(dev); + + /* Disable audio input */ + hdmi_writeb_mask(hdmi, AUD_EN, AUD_IN_EN, 0); +} + +static inline int zx_hdmi_audio_get_n(unsigned int fs) +{ + unsigned int n; + + if (fs && (fs % 44100) == 0) + n = 6272 * (fs / 44100); + else + n = fs * 128 / 1000; + + return n; +} + +static int zx_hdmi_audio_hw_params(struct device *dev, + void *data, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct zx_hdmi *hdmi = dev_get_drvdata(dev); + struct hdmi_audio_infoframe *cea = ¶ms->cea; + union hdmi_infoframe frame; + int n; + + /* We only support spdif for now */ + if (daifmt->fmt != HDMI_SPDIF) { + DRM_DEV_ERROR(hdmi->dev, "invalid daifmt %d\n", daifmt->fmt); + return -EINVAL; + } + + switch (params->sample_width) { + case 16: + hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, SPDIF_SAMPLE_SIZE_MASK, + SPDIF_SAMPLE_SIZE_16BIT); + break; + case 20: + hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, SPDIF_SAMPLE_SIZE_MASK, + SPDIF_SAMPLE_SIZE_20BIT); + break; + case 24: + hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, SPDIF_SAMPLE_SIZE_MASK, + SPDIF_SAMPLE_SIZE_24BIT); + break; + default: + DRM_DEV_ERROR(hdmi->dev, "invalid sample width %d\n", + params->sample_width); + return -EINVAL; + } + + /* CTS is calculated by hardware, and we only need to take care of N */ + n = zx_hdmi_audio_get_n(params->sample_rate); + hdmi_writeb(hdmi, N_SVAL1, n & 0xff); + hdmi_writeb(hdmi, N_SVAL2, (n >> 8) & 0xff); + hdmi_writeb(hdmi, N_SVAL3, (n >> 16) & 0xf); + + /* Enable spdif mode */ + hdmi_writeb_mask(hdmi, AUD_MODE, SPDIF_EN, SPDIF_EN); + + /* Enable audio input */ + hdmi_writeb_mask(hdmi, AUD_EN, AUD_IN_EN, AUD_IN_EN); + + memcpy(&frame.audio, cea, sizeof(*cea)); + + return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_AUDIO); +} + +static int zx_hdmi_audio_digital_mute(struct device *dev, void *data, + bool enable) +{ + struct zx_hdmi *hdmi = dev_get_drvdata(dev); + + if (enable) + hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, TPI_AUD_MUTE, + TPI_AUD_MUTE); + else + hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, TPI_AUD_MUTE, 0); + + return 0; +} + +static int zx_hdmi_audio_get_eld(struct device *dev, void *data, + uint8_t *buf, size_t len) +{ + struct zx_hdmi *hdmi = dev_get_drvdata(dev); + struct drm_connector *connector = &hdmi->connector; + + memcpy(buf, connector->eld, min(sizeof(connector->eld), len)); + + return 0; +} + +static const struct hdmi_codec_ops zx_hdmi_codec_ops = { + .audio_startup = zx_hdmi_audio_startup, + .hw_params = zx_hdmi_audio_hw_params, + .audio_shutdown = zx_hdmi_audio_shutdown, + .digital_mute = zx_hdmi_audio_digital_mute, + .get_eld = zx_hdmi_audio_get_eld, +}; + +static struct hdmi_codec_pdata zx_hdmi_codec_pdata = { + .ops = &zx_hdmi_codec_ops, + .spdif = 1, +}; + +static int zx_hdmi_audio_register(struct zx_hdmi *hdmi) +{ + struct platform_device *pdev; + + pdev = platform_device_register_data(hdmi->dev, HDMI_CODEC_DRV_NAME, + PLATFORM_DEVID_AUTO, + &zx_hdmi_codec_pdata, + sizeof(zx_hdmi_codec_pdata)); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + hdmi->audio_pdev = pdev; + + return 0; +} + +static int zx_hdmi_i2c_read(struct zx_hdmi *hdmi, struct i2c_msg *msg) +{ + int len = msg->len; + u8 *buf = msg->buf; + int retry = 0; + int ret = 0; + + /* Bits [9:8] of bytes */ + hdmi_writeb(hdmi, ZX_DDC_DIN_CNT2, (len >> 8) & 0xff); + /* Bits [7:0] of bytes */ + hdmi_writeb(hdmi, ZX_DDC_DIN_CNT1, len & 0xff); + + /* Clear FIFO */ + hdmi_writeb_mask(hdmi, ZX_DDC_CMD, DDC_CMD_MASK, DDC_CMD_CLEAR_FIFO); + + /* Kick off the read */ + hdmi_writeb_mask(hdmi, ZX_DDC_CMD, DDC_CMD_MASK, + DDC_CMD_SEQUENTIAL_READ); + + while (len > 0) { + int cnt, i; + + /* FIFO needs some time to get ready */ + usleep_range(500, 1000); + + cnt = hdmi_readb(hdmi, ZX_DDC_DOUT_CNT) & DDC_DOUT_CNT_MASK; + if (cnt == 0) { + if (++retry > 5) { + DRM_DEV_ERROR(hdmi->dev, + "DDC FIFO read timed out!"); + return -ETIMEDOUT; + } + continue; + } + + for (i = 0; i < cnt; i++) + *buf++ = hdmi_readb(hdmi, ZX_DDC_DATA); + len -= cnt; + } + + return ret; +} + +static int zx_hdmi_i2c_write(struct zx_hdmi *hdmi, struct i2c_msg *msg) +{ + /* + * The DDC I2C adapter is only for reading EDID data, so we assume + * that the write to this adapter must be the EDID data offset. + */ + if ((msg->len != 1) || + ((msg->addr != DDC_ADDR) && (msg->addr != DDC_SEGMENT_ADDR))) + return -EINVAL; + + if (msg->addr == DDC_SEGMENT_ADDR) + hdmi_writeb(hdmi, ZX_DDC_SEGM, msg->addr << 1); + else if (msg->addr == DDC_ADDR) + hdmi_writeb(hdmi, ZX_DDC_ADDR, msg->addr << 1); + + hdmi_writeb(hdmi, ZX_DDC_OFFSET, msg->buf[0]); + + return 0; +} + +static int zx_hdmi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) +{ + struct zx_hdmi *hdmi = i2c_get_adapdata(adap); + struct zx_hdmi_i2c *ddc = hdmi->ddc; + int i, ret = 0; + + mutex_lock(&ddc->lock); + + /* Enable DDC master access */ + hdmi_writeb_mask(hdmi, TPI_DDC_MASTER_EN, HW_DDC_MASTER, HW_DDC_MASTER); + + for (i = 0; i < num; i++) { + DRM_DEV_DEBUG(hdmi->dev, + "xfer: num: %d/%d, len: %d, flags: %#x\n", + i + 1, num, msgs[i].len, msgs[i].flags); + + if (msgs[i].flags & I2C_M_RD) + ret = zx_hdmi_i2c_read(hdmi, &msgs[i]); + else + ret = zx_hdmi_i2c_write(hdmi, &msgs[i]); + + if (ret < 0) + break; + } + + if (!ret) + ret = num; + + /* Disable DDC master access */ + hdmi_writeb_mask(hdmi, TPI_DDC_MASTER_EN, HW_DDC_MASTER, 0); + + mutex_unlock(&ddc->lock); + + return ret; +} + +static u32 zx_hdmi_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm zx_hdmi_algorithm = { + .master_xfer = zx_hdmi_i2c_xfer, + .functionality = zx_hdmi_i2c_func, +}; + +static int zx_hdmi_ddc_register(struct zx_hdmi *hdmi) +{ + struct i2c_adapter *adap; + struct zx_hdmi_i2c *ddc; + int ret; + + ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL); + if (!ddc) + return -ENOMEM; + + hdmi->ddc = ddc; + mutex_init(&ddc->lock); + + adap = &ddc->adap; + adap->owner = THIS_MODULE; + adap->class = I2C_CLASS_DDC; + adap->dev.parent = hdmi->dev; + adap->algo = &zx_hdmi_algorithm; + snprintf(adap->name, sizeof(adap->name), "zx hdmi i2c"); + + ret = i2c_add_adapter(adap); + if (ret) { + DRM_DEV_ERROR(hdmi->dev, "failed to add I2C adapter: %d\n", + ret); + return ret; + } + + i2c_set_adapdata(adap, hdmi); + + return 0; +} + +static int zx_hdmi_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_hdmi *hdmi; + int irq; + int ret; + + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + + hdmi->dev = dev; + hdmi->drm = drm; + + dev_set_drvdata(dev, hdmi); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdmi->mmio = devm_ioremap_resource(dev, res); + if (IS_ERR(hdmi->mmio)) { + ret = PTR_ERR(hdmi->mmio); + DRM_DEV_ERROR(dev, "failed to remap hdmi region: %d\n", ret); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + hdmi->cec_clk = devm_clk_get(hdmi->dev, "osc_cec"); + if (IS_ERR(hdmi->cec_clk)) { + ret = PTR_ERR(hdmi->cec_clk); + DRM_DEV_ERROR(dev, "failed to get cec_clk: %d\n", ret); + return ret; + } + + hdmi->osc_clk = devm_clk_get(hdmi->dev, "osc_clk"); + if (IS_ERR(hdmi->osc_clk)) { + ret = PTR_ERR(hdmi->osc_clk); + DRM_DEV_ERROR(dev, "failed to get osc_clk: %d\n", ret); + return ret; + } + + hdmi->xclk = devm_clk_get(hdmi->dev, "xclk"); + if (IS_ERR(hdmi->xclk)) { + ret = PTR_ERR(hdmi->xclk); + DRM_DEV_ERROR(dev, "failed to get xclk: %d\n", ret); + return ret; + } + + ret = zx_hdmi_ddc_register(hdmi); + if (ret) { + DRM_DEV_ERROR(dev, "failed to register ddc: %d\n", ret); + return ret; + } + + ret = zx_hdmi_audio_register(hdmi); + if (ret) { + DRM_DEV_ERROR(dev, "failed to register audio: %d\n", ret); + return ret; + } + + ret = zx_hdmi_register(drm, hdmi); + if (ret) { + DRM_DEV_ERROR(dev, "failed to register hdmi: %d\n", ret); + return ret; + } + + ret = devm_request_threaded_irq(dev, irq, zx_hdmi_irq_handler, + zx_hdmi_irq_thread, IRQF_SHARED, + dev_name(dev), hdmi); + if (ret) { + DRM_DEV_ERROR(dev, "failed to request threaded irq: %d\n", ret); + return ret; + } + + return 0; +} + +static void zx_hdmi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct zx_hdmi *hdmi = dev_get_drvdata(dev); + + hdmi->connector.funcs->destroy(&hdmi->connector); + hdmi->encoder.funcs->destroy(&hdmi->encoder); + + if (hdmi->audio_pdev) + platform_device_unregister(hdmi->audio_pdev); +} + +static const struct component_ops zx_hdmi_component_ops = { + .bind = zx_hdmi_bind, + .unbind = zx_hdmi_unbind, +}; + +static int zx_hdmi_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &zx_hdmi_component_ops); +} + +static int zx_hdmi_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &zx_hdmi_component_ops); + return 0; +} + +static const struct of_device_id zx_hdmi_of_match[] = { + { .compatible = "zte,zx296718-hdmi", }, + { /* end */ }, +}; +MODULE_DEVICE_TABLE(of, zx_hdmi_of_match); + +struct platform_driver zx_hdmi_driver = { + .probe = zx_hdmi_probe, + .remove = zx_hdmi_remove, + .driver = { + .name = "zx-hdmi", + .of_match_table = zx_hdmi_of_match, + }, +}; diff --git a/drivers/gpu/drm/zte/zx_hdmi_regs.h b/drivers/gpu/drm/zte/zx_hdmi_regs.h new file mode 100644 index 000000000..c6d5d8211 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_hdmi_regs.h @@ -0,0 +1,70 @@ +/* + * Copyright 2016 Linaro Ltd. + * Copyright 2016 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_HDMI_REGS_H__ +#define __ZX_HDMI_REGS_H__ + +#define FUNC_SEL 0x000b +#define FUNC_HDMI_EN BIT(0) +#define CLKPWD 0x000d +#define CLKPWD_PDIDCK BIT(2) +#define P2T_CTRL 0x0066 +#define P2T_DC_PKT_EN BIT(7) +#define L1_INTR_STAT 0x007e +#define L1_INTR_STAT_INTR1 BIT(0) +#define INTR1_STAT 0x008f +#define INTR1_MASK 0x0095 +#define INTR1_MONITOR_DETECT (BIT(5) | BIT(6)) +#define ZX_DDC_ADDR 0x00ed +#define ZX_DDC_SEGM 0x00ee +#define ZX_DDC_OFFSET 0x00ef +#define ZX_DDC_DIN_CNT1 0x00f0 +#define ZX_DDC_DIN_CNT2 0x00f1 +#define ZX_DDC_CMD 0x00f3 +#define DDC_CMD_MASK 0xf +#define DDC_CMD_CLEAR_FIFO 0x9 +#define DDC_CMD_SEQUENTIAL_READ 0x2 +#define ZX_DDC_DATA 0x00f4 +#define ZX_DDC_DOUT_CNT 0x00f5 +#define DDC_DOUT_CNT_MASK 0x1f +#define TEST_TXCTRL 0x00f7 +#define TEST_TXCTRL_HDMI_MODE BIT(1) +#define HDMICTL4 0x0235 +#define TPI_HPD_RSEN 0x063b +#define TPI_HPD_CONNECTION (BIT(1) | BIT(2)) +#define TPI_INFO_FSEL 0x06bf +#define FSEL_AVI 0 +#define FSEL_GBD 1 +#define FSEL_AUDIO 2 +#define FSEL_SPD 3 +#define FSEL_MPEG 4 +#define FSEL_VSIF 5 +#define TPI_INFO_B0 0x06c0 +#define TPI_INFO_EN 0x06df +#define TPI_INFO_TRANS_EN BIT(7) +#define TPI_INFO_TRANS_RPT BIT(6) +#define TPI_DDC_MASTER_EN 0x06f8 +#define HW_DDC_MASTER BIT(7) +#define N_SVAL1 0xa03 +#define N_SVAL2 0xa04 +#define N_SVAL3 0xa05 +#define AUD_EN 0xa13 +#define AUD_IN_EN BIT(0) +#define AUD_MODE 0xa14 +#define SPDIF_EN BIT(1) +#define TPI_AUD_CONFIG 0xa62 +#define SPDIF_SAMPLE_SIZE_SHIFT 6 +#define SPDIF_SAMPLE_SIZE_MASK (0x3 << SPDIF_SAMPLE_SIZE_SHIFT) +#define SPDIF_SAMPLE_SIZE_16BIT (0x1 << SPDIF_SAMPLE_SIZE_SHIFT) +#define SPDIF_SAMPLE_SIZE_20BIT (0x2 << SPDIF_SAMPLE_SIZE_SHIFT) +#define SPDIF_SAMPLE_SIZE_24BIT (0x3 << SPDIF_SAMPLE_SIZE_SHIFT) +#define TPI_AUD_MUTE BIT(4) + +#endif /* __ZX_HDMI_REGS_H__ */ diff --git a/drivers/gpu/drm/zte/zx_plane.c b/drivers/gpu/drm/zte/zx_plane.c new file mode 100644 index 000000000..ae8c53b4b --- /dev/null +++ b/drivers/gpu/drm/zte/zx_plane.c @@ -0,0 +1,540 @@ +/* + * Copyright 2016 Linaro Ltd. + * Copyright 2016 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 <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_plane_helper.h> +#include <drm/drmP.h> + +#include "zx_common_regs.h" +#include "zx_drm_drv.h" +#include "zx_plane.h" +#include "zx_plane_regs.h" +#include "zx_vou.h" + +static const uint32_t gl_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_RGB565, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_ARGB4444, +}; + +static const uint32_t vl_formats[] = { + DRM_FORMAT_NV12, /* Semi-planar YUV420 */ + DRM_FORMAT_YUV420, /* Planar YUV420 */ + DRM_FORMAT_YUYV, /* Packed YUV422 */ + DRM_FORMAT_YVYU, + DRM_FORMAT_UYVY, + DRM_FORMAT_VYUY, + DRM_FORMAT_YUV444, /* YUV444 8bit */ + /* + * TODO: add formats below that HW supports: + * - YUV420 P010 + * - YUV420 Hantro + * - YUV444 10bit + */ +}; + +#define FRAC_16_16(mult, div) (((mult) << 16) / (div)) + +static int zx_vl_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *plane_state) +{ + struct drm_framebuffer *fb = plane_state->fb; + struct drm_crtc *crtc = plane_state->crtc; + struct drm_crtc_state *crtc_state; + int min_scale = FRAC_16_16(1, 8); + int max_scale = FRAC_16_16(8, 1); + + if (!crtc || !fb) + return 0; + + crtc_state = drm_atomic_get_existing_crtc_state(plane_state->state, + crtc); + if (WARN_ON(!crtc_state)) + return -EINVAL; + + /* nothing to check when disabling or disabled */ + if (!crtc_state->enable) + return 0; + + /* plane must be enabled */ + if (!plane_state->crtc) + return -EINVAL; + + return drm_atomic_helper_check_plane_state(plane_state, crtc_state, + min_scale, max_scale, + true, true); +} + +static int zx_vl_get_fmt(uint32_t format) +{ + switch (format) { + case DRM_FORMAT_NV12: + return VL_FMT_YUV420; + case DRM_FORMAT_YUV420: + return VL_YUV420_PLANAR | VL_FMT_YUV420; + case DRM_FORMAT_YUYV: + return VL_YUV422_YUYV | VL_FMT_YUV422; + case DRM_FORMAT_YVYU: + return VL_YUV422_YVYU | VL_FMT_YUV422; + case DRM_FORMAT_UYVY: + return VL_YUV422_UYVY | VL_FMT_YUV422; + case DRM_FORMAT_VYUY: + return VL_YUV422_VYUY | VL_FMT_YUV422; + case DRM_FORMAT_YUV444: + return VL_FMT_YUV444_8BIT; + default: + WARN_ONCE(1, "invalid pixel format %d\n", format); + return -EINVAL; + } +} + +static inline void zx_vl_set_update(struct zx_plane *zplane) +{ + void __iomem *layer = zplane->layer; + + zx_writel_mask(layer + VL_CTRL0, VL_UPDATE, VL_UPDATE); +} + +static inline void zx_vl_rsz_set_update(struct zx_plane *zplane) +{ + zx_writel(zplane->rsz + RSZ_VL_ENABLE_CFG, 1); +} + +static int zx_vl_rsz_get_fmt(uint32_t format) +{ + switch (format) { + case DRM_FORMAT_NV12: + case DRM_FORMAT_YUV420: + return RSZ_VL_FMT_YCBCR420; + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VYUY: + return RSZ_VL_FMT_YCBCR422; + case DRM_FORMAT_YUV444: + return RSZ_VL_FMT_YCBCR444; + default: + WARN_ONCE(1, "invalid pixel format %d\n", format); + return -EINVAL; + } +} + +static inline u32 rsz_step_value(u32 src, u32 dst) +{ + u32 val = 0; + + if (src == dst) + val = 0; + else if (src < dst) + val = RSZ_PARA_STEP((src << 16) / dst); + else if (src > dst) + val = RSZ_DATA_STEP(src / dst) | + RSZ_PARA_STEP(((src << 16) / dst) & 0xffff); + + return val; +} + +static void zx_vl_rsz_setup(struct zx_plane *zplane, uint32_t format, + u32 src_w, u32 src_h, u32 dst_w, u32 dst_h) +{ + void __iomem *rsz = zplane->rsz; + u32 src_chroma_w = src_w; + u32 src_chroma_h = src_h; + int fmt; + + /* Set up source and destination resolution */ + zx_writel(rsz + RSZ_SRC_CFG, RSZ_VER(src_h - 1) | RSZ_HOR(src_w - 1)); + zx_writel(rsz + RSZ_DEST_CFG, RSZ_VER(dst_h - 1) | RSZ_HOR(dst_w - 1)); + + /* Configure data format for VL RSZ */ + fmt = zx_vl_rsz_get_fmt(format); + if (fmt >= 0) + zx_writel_mask(rsz + RSZ_VL_CTRL_CFG, RSZ_VL_FMT_MASK, fmt); + + /* Calculate Chroma height and width */ + if (fmt == RSZ_VL_FMT_YCBCR420) { + src_chroma_w = src_w >> 1; + src_chroma_h = src_h >> 1; + } else if (fmt == RSZ_VL_FMT_YCBCR422) { + src_chroma_w = src_w >> 1; + } + + /* Set up Luma and Chroma step registers */ + zx_writel(rsz + RSZ_VL_LUMA_HOR, rsz_step_value(src_w, dst_w)); + zx_writel(rsz + RSZ_VL_LUMA_VER, rsz_step_value(src_h, dst_h)); + zx_writel(rsz + RSZ_VL_CHROMA_HOR, rsz_step_value(src_chroma_w, dst_w)); + zx_writel(rsz + RSZ_VL_CHROMA_VER, rsz_step_value(src_chroma_h, dst_h)); + + zx_vl_rsz_set_update(zplane); +} + +static void zx_vl_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct zx_plane *zplane = to_zx_plane(plane); + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + struct drm_rect *src = &state->src; + struct drm_rect *dst = &state->dst; + struct drm_gem_cma_object *cma_obj; + void __iomem *layer = zplane->layer; + void __iomem *hbsc = zplane->hbsc; + void __iomem *paddr_reg; + dma_addr_t paddr; + u32 src_x, src_y, src_w, src_h; + u32 dst_x, dst_y, dst_w, dst_h; + uint32_t format; + int fmt; + int num_planes; + int i; + + if (!fb) + return; + + format = fb->format->format; + + src_x = src->x1 >> 16; + src_y = src->y1 >> 16; + src_w = drm_rect_width(src) >> 16; + src_h = drm_rect_height(src) >> 16; + + dst_x = dst->x1; + dst_y = dst->y1; + dst_w = drm_rect_width(dst); + dst_h = drm_rect_height(dst); + + /* Set up data address registers for Y, Cb and Cr planes */ + num_planes = drm_format_num_planes(format); + paddr_reg = layer + VL_Y; + for (i = 0; i < num_planes; i++) { + cma_obj = drm_fb_cma_get_gem_obj(fb, i); + paddr = cma_obj->paddr + fb->offsets[i]; + paddr += src_y * fb->pitches[i]; + paddr += src_x * drm_format_plane_cpp(format, i); + zx_writel(paddr_reg, paddr); + paddr_reg += 4; + } + + /* Set up source height/width register */ + zx_writel(layer + VL_SRC_SIZE, GL_SRC_W(src_w) | GL_SRC_H(src_h)); + + /* Set up start position register */ + zx_writel(layer + VL_POS_START, GL_POS_X(dst_x) | GL_POS_Y(dst_y)); + + /* Set up end position register */ + zx_writel(layer + VL_POS_END, + GL_POS_X(dst_x + dst_w) | GL_POS_Y(dst_y + dst_h)); + + /* Strides of Cb and Cr planes should be identical */ + zx_writel(layer + VL_STRIDE, LUMA_STRIDE(fb->pitches[0]) | + CHROMA_STRIDE(fb->pitches[1])); + + /* Set up video layer data format */ + fmt = zx_vl_get_fmt(format); + if (fmt >= 0) + zx_writel(layer + VL_CTRL1, fmt); + + /* Always use scaler since it exists (set for not bypass) */ + zx_writel_mask(layer + VL_CTRL2, VL_SCALER_BYPASS_MODE, + VL_SCALER_BYPASS_MODE); + + zx_vl_rsz_setup(zplane, format, src_w, src_h, dst_w, dst_h); + + /* Enable HBSC block */ + zx_writel_mask(hbsc + HBSC_CTRL0, HBSC_CTRL_EN, HBSC_CTRL_EN); + + zx_vou_layer_enable(plane); + + zx_vl_set_update(zplane); +} + +static void zx_plane_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct zx_plane *zplane = to_zx_plane(plane); + void __iomem *hbsc = zplane->hbsc; + + zx_vou_layer_disable(plane, old_state); + + /* Disable HBSC block */ + zx_writel_mask(hbsc + HBSC_CTRL0, HBSC_CTRL_EN, 0); +} + +static const struct drm_plane_helper_funcs zx_vl_plane_helper_funcs = { + .atomic_check = zx_vl_plane_atomic_check, + .atomic_update = zx_vl_plane_atomic_update, + .atomic_disable = zx_plane_atomic_disable, +}; + +static int zx_gl_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *plane_state) +{ + struct drm_framebuffer *fb = plane_state->fb; + struct drm_crtc *crtc = plane_state->crtc; + struct drm_crtc_state *crtc_state; + + if (!crtc || !fb) + return 0; + + crtc_state = drm_atomic_get_existing_crtc_state(plane_state->state, + crtc); + if (WARN_ON(!crtc_state)) + return -EINVAL; + + /* nothing to check when disabling or disabled */ + if (!crtc_state->enable) + return 0; + + /* plane must be enabled */ + if (!plane_state->crtc) + return -EINVAL; + + return drm_atomic_helper_check_plane_state(plane_state, crtc_state, + DRM_PLANE_HELPER_NO_SCALING, + DRM_PLANE_HELPER_NO_SCALING, + false, true); +} + +static int zx_gl_get_fmt(uint32_t format) +{ + switch (format) { + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + return GL_FMT_ARGB8888; + case DRM_FORMAT_RGB888: + return GL_FMT_RGB888; + case DRM_FORMAT_RGB565: + return GL_FMT_RGB565; + case DRM_FORMAT_ARGB1555: + return GL_FMT_ARGB1555; + case DRM_FORMAT_ARGB4444: + return GL_FMT_ARGB4444; + default: + WARN_ONCE(1, "invalid pixel format %d\n", format); + return -EINVAL; + } +} + +static inline void zx_gl_set_update(struct zx_plane *zplane) +{ + void __iomem *layer = zplane->layer; + + zx_writel_mask(layer + GL_CTRL0, GL_UPDATE, GL_UPDATE); +} + +static inline void zx_gl_rsz_set_update(struct zx_plane *zplane) +{ + zx_writel(zplane->rsz + RSZ_ENABLE_CFG, 1); +} + +static void zx_gl_rsz_setup(struct zx_plane *zplane, u32 src_w, u32 src_h, + u32 dst_w, u32 dst_h) +{ + void __iomem *rsz = zplane->rsz; + + zx_writel(rsz + RSZ_SRC_CFG, RSZ_VER(src_h - 1) | RSZ_HOR(src_w - 1)); + zx_writel(rsz + RSZ_DEST_CFG, RSZ_VER(dst_h - 1) | RSZ_HOR(dst_w - 1)); + + zx_gl_rsz_set_update(zplane); +} + +static void zx_gl_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct zx_plane *zplane = to_zx_plane(plane); + struct drm_framebuffer *fb = plane->state->fb; + struct drm_gem_cma_object *cma_obj; + void __iomem *layer = zplane->layer; + void __iomem *csc = zplane->csc; + void __iomem *hbsc = zplane->hbsc; + u32 src_x, src_y, src_w, src_h; + u32 dst_x, dst_y, dst_w, dst_h; + unsigned int bpp; + uint32_t format; + dma_addr_t paddr; + u32 stride; + int fmt; + + if (!fb) + return; + + format = fb->format->format; + stride = fb->pitches[0]; + + src_x = plane->state->src_x >> 16; + src_y = plane->state->src_y >> 16; + src_w = plane->state->src_w >> 16; + src_h = plane->state->src_h >> 16; + + dst_x = plane->state->crtc_x; + dst_y = plane->state->crtc_y; + dst_w = plane->state->crtc_w; + dst_h = plane->state->crtc_h; + + bpp = fb->format->cpp[0]; + + cma_obj = drm_fb_cma_get_gem_obj(fb, 0); + paddr = cma_obj->paddr + fb->offsets[0]; + paddr += src_y * stride + src_x * bpp / 8; + zx_writel(layer + GL_ADDR, paddr); + + /* Set up source height/width register */ + zx_writel(layer + GL_SRC_SIZE, GL_SRC_W(src_w) | GL_SRC_H(src_h)); + + /* Set up start position register */ + zx_writel(layer + GL_POS_START, GL_POS_X(dst_x) | GL_POS_Y(dst_y)); + + /* Set up end position register */ + zx_writel(layer + GL_POS_END, + GL_POS_X(dst_x + dst_w) | GL_POS_Y(dst_y + dst_h)); + + /* Set up stride register */ + zx_writel(layer + GL_STRIDE, stride & 0xffff); + + /* Set up graphic layer data format */ + fmt = zx_gl_get_fmt(format); + if (fmt >= 0) + zx_writel_mask(layer + GL_CTRL1, GL_DATA_FMT_MASK, + fmt << GL_DATA_FMT_SHIFT); + + /* Initialize global alpha with a sane value */ + zx_writel_mask(layer + GL_CTRL2, GL_GLOBAL_ALPHA_MASK, + 0xff << GL_GLOBAL_ALPHA_SHIFT); + + /* Setup CSC for the GL */ + if (dst_h > 720) + zx_writel_mask(csc + CSC_CTRL0, CSC_COV_MODE_MASK, + CSC_BT709_IMAGE_RGB2YCBCR << CSC_COV_MODE_SHIFT); + else + zx_writel_mask(csc + CSC_CTRL0, CSC_COV_MODE_MASK, + CSC_BT601_IMAGE_RGB2YCBCR << CSC_COV_MODE_SHIFT); + zx_writel_mask(csc + CSC_CTRL0, CSC_WORK_ENABLE, CSC_WORK_ENABLE); + + /* Always use scaler since it exists (set for not bypass) */ + zx_writel_mask(layer + GL_CTRL3, GL_SCALER_BYPASS_MODE, + GL_SCALER_BYPASS_MODE); + + zx_gl_rsz_setup(zplane, src_w, src_h, dst_w, dst_h); + + /* Enable HBSC block */ + zx_writel_mask(hbsc + HBSC_CTRL0, HBSC_CTRL_EN, HBSC_CTRL_EN); + + zx_vou_layer_enable(plane); + + zx_gl_set_update(zplane); +} + +static const struct drm_plane_helper_funcs zx_gl_plane_helper_funcs = { + .atomic_check = zx_gl_plane_atomic_check, + .atomic_update = zx_gl_plane_atomic_update, + .atomic_disable = zx_plane_atomic_disable, +}; + +static void zx_plane_destroy(struct drm_plane *plane) +{ + drm_plane_helper_disable(plane, NULL); + drm_plane_cleanup(plane); +} + +static const struct drm_plane_funcs zx_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = zx_plane_destroy, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +void zx_plane_set_update(struct drm_plane *plane) +{ + struct zx_plane *zplane = to_zx_plane(plane); + + /* Do nothing if the plane is not enabled */ + if (!plane->state->crtc) + return; + + switch (plane->type) { + case DRM_PLANE_TYPE_PRIMARY: + zx_gl_rsz_set_update(zplane); + zx_gl_set_update(zplane); + break; + case DRM_PLANE_TYPE_OVERLAY: + zx_vl_rsz_set_update(zplane); + zx_vl_set_update(zplane); + break; + default: + WARN_ONCE(1, "unsupported plane type %d\n", plane->type); + } +} + +static void zx_plane_hbsc_init(struct zx_plane *zplane) +{ + void __iomem *hbsc = zplane->hbsc; + + /* + * Initialize HBSC block with a sane configuration per recommedation + * from ZTE BSP code. + */ + zx_writel(hbsc + HBSC_SATURATION, 0x200); + zx_writel(hbsc + HBSC_HUE, 0x0); + zx_writel(hbsc + HBSC_BRIGHT, 0x0); + zx_writel(hbsc + HBSC_CONTRAST, 0x200); + + zx_writel(hbsc + HBSC_THRESHOLD_COL1, (0x3ac << 16) | 0x40); + zx_writel(hbsc + HBSC_THRESHOLD_COL2, (0x3c0 << 16) | 0x40); + zx_writel(hbsc + HBSC_THRESHOLD_COL3, (0x3c0 << 16) | 0x40); +} + +int zx_plane_init(struct drm_device *drm, struct zx_plane *zplane, + enum drm_plane_type type) +{ + const struct drm_plane_helper_funcs *helper; + struct drm_plane *plane = &zplane->plane; + struct device *dev = zplane->dev; + const uint32_t *formats; + unsigned int format_count; + int ret; + + zx_plane_hbsc_init(zplane); + + switch (type) { + case DRM_PLANE_TYPE_PRIMARY: + helper = &zx_gl_plane_helper_funcs; + formats = gl_formats; + format_count = ARRAY_SIZE(gl_formats); + break; + case DRM_PLANE_TYPE_OVERLAY: + helper = &zx_vl_plane_helper_funcs; + formats = vl_formats; + format_count = ARRAY_SIZE(vl_formats); + break; + default: + return -ENODEV; + } + + ret = drm_universal_plane_init(drm, plane, VOU_CRTC_MASK, + &zx_plane_funcs, formats, format_count, + NULL, type, NULL); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init universal plane: %d\n", ret); + return ret; + } + + drm_plane_helper_add(plane, helper); + + return 0; +} diff --git a/drivers/gpu/drm/zte/zx_plane.h b/drivers/gpu/drm/zte/zx_plane.h new file mode 100644 index 000000000..933611ddf --- /dev/null +++ b/drivers/gpu/drm/zte/zx_plane.h @@ -0,0 +1,30 @@ +/* + * Copyright 2016 Linaro Ltd. + * Copyright 2016 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_PLANE_H__ +#define __ZX_PLANE_H__ + +struct zx_plane { + struct drm_plane plane; + struct device *dev; + void __iomem *layer; + void __iomem *csc; + void __iomem *hbsc; + void __iomem *rsz; + const struct vou_layer_bits *bits; +}; + +#define to_zx_plane(plane) container_of(plane, struct zx_plane, plane) + +int zx_plane_init(struct drm_device *drm, struct zx_plane *zplane, + enum drm_plane_type type); +void zx_plane_set_update(struct drm_plane *plane); + +#endif /* __ZX_PLANE_H__ */ diff --git a/drivers/gpu/drm/zte/zx_plane_regs.h b/drivers/gpu/drm/zte/zx_plane_regs.h new file mode 100644 index 000000000..9c655f59f --- /dev/null +++ b/drivers/gpu/drm/zte/zx_plane_regs.h @@ -0,0 +1,124 @@ +/* + * Copyright 2016 Linaro Ltd. + * Copyright 2016 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_PLANE_REGS_H__ +#define __ZX_PLANE_REGS_H__ + +/* GL registers */ +#define GL_CTRL0 0x00 +#define GL_UPDATE BIT(5) +#define GL_CTRL1 0x04 +#define GL_DATA_FMT_SHIFT 0 +#define GL_DATA_FMT_MASK (0xf << GL_DATA_FMT_SHIFT) +#define GL_FMT_ARGB8888 0 +#define GL_FMT_RGB888 1 +#define GL_FMT_RGB565 2 +#define GL_FMT_ARGB1555 3 +#define GL_FMT_ARGB4444 4 +#define GL_CTRL2 0x08 +#define GL_GLOBAL_ALPHA_SHIFT 8 +#define GL_GLOBAL_ALPHA_MASK (0xff << GL_GLOBAL_ALPHA_SHIFT) +#define GL_CTRL3 0x0c +#define GL_SCALER_BYPASS_MODE BIT(0) +#define GL_STRIDE 0x18 +#define GL_ADDR 0x1c +#define GL_SRC_SIZE 0x38 +#define GL_SRC_W_SHIFT 16 +#define GL_SRC_W_MASK (0x3fff << GL_SRC_W_SHIFT) +#define GL_SRC_H_SHIFT 0 +#define GL_SRC_H_MASK (0x3fff << GL_SRC_H_SHIFT) +#define GL_POS_START 0x9c +#define GL_POS_END 0xa0 +#define GL_POS_X_SHIFT 16 +#define GL_POS_X_MASK (0x1fff << GL_POS_X_SHIFT) +#define GL_POS_Y_SHIFT 0 +#define GL_POS_Y_MASK (0x1fff << GL_POS_Y_SHIFT) + +#define GL_SRC_W(x) (((x) << GL_SRC_W_SHIFT) & GL_SRC_W_MASK) +#define GL_SRC_H(x) (((x) << GL_SRC_H_SHIFT) & GL_SRC_H_MASK) +#define GL_POS_X(x) (((x) << GL_POS_X_SHIFT) & GL_POS_X_MASK) +#define GL_POS_Y(x) (((x) << GL_POS_Y_SHIFT) & GL_POS_Y_MASK) + +/* VL registers */ +#define VL_CTRL0 0x00 +#define VL_UPDATE BIT(3) +#define VL_CTRL1 0x04 +#define VL_YUV420_PLANAR BIT(5) +#define VL_YUV422_SHIFT 3 +#define VL_YUV422_YUYV (0 << VL_YUV422_SHIFT) +#define VL_YUV422_YVYU (1 << VL_YUV422_SHIFT) +#define VL_YUV422_UYVY (2 << VL_YUV422_SHIFT) +#define VL_YUV422_VYUY (3 << VL_YUV422_SHIFT) +#define VL_FMT_YUV420 0 +#define VL_FMT_YUV422 1 +#define VL_FMT_YUV420_P010 2 +#define VL_FMT_YUV420_HANTRO 3 +#define VL_FMT_YUV444_8BIT 4 +#define VL_FMT_YUV444_10BIT 5 +#define VL_CTRL2 0x08 +#define VL_SCALER_BYPASS_MODE BIT(0) +#define VL_STRIDE 0x0c +#define LUMA_STRIDE_SHIFT 16 +#define LUMA_STRIDE_MASK (0xffff << LUMA_STRIDE_SHIFT) +#define CHROMA_STRIDE_SHIFT 0 +#define CHROMA_STRIDE_MASK (0xffff << CHROMA_STRIDE_SHIFT) +#define VL_SRC_SIZE 0x10 +#define VL_Y 0x14 +#define VL_POS_START 0x30 +#define VL_POS_END 0x34 + +#define LUMA_STRIDE(x) (((x) << LUMA_STRIDE_SHIFT) & LUMA_STRIDE_MASK) +#define CHROMA_STRIDE(x) (((x) << CHROMA_STRIDE_SHIFT) & CHROMA_STRIDE_MASK) + +/* RSZ registers */ +#define RSZ_SRC_CFG 0x00 +#define RSZ_DEST_CFG 0x04 +#define RSZ_ENABLE_CFG 0x14 + +#define RSZ_VL_LUMA_HOR 0x08 +#define RSZ_VL_LUMA_VER 0x0c +#define RSZ_VL_CHROMA_HOR 0x10 +#define RSZ_VL_CHROMA_VER 0x14 +#define RSZ_VL_CTRL_CFG 0x18 +#define RSZ_VL_FMT_SHIFT 3 +#define RSZ_VL_FMT_MASK (0x3 << RSZ_VL_FMT_SHIFT) +#define RSZ_VL_FMT_YCBCR420 (0x0 << RSZ_VL_FMT_SHIFT) +#define RSZ_VL_FMT_YCBCR422 (0x1 << RSZ_VL_FMT_SHIFT) +#define RSZ_VL_FMT_YCBCR444 (0x2 << RSZ_VL_FMT_SHIFT) +#define RSZ_VL_ENABLE_CFG 0x1c + +#define RSZ_VER_SHIFT 16 +#define RSZ_VER_MASK (0xffff << RSZ_VER_SHIFT) +#define RSZ_HOR_SHIFT 0 +#define RSZ_HOR_MASK (0xffff << RSZ_HOR_SHIFT) + +#define RSZ_VER(x) (((x) << RSZ_VER_SHIFT) & RSZ_VER_MASK) +#define RSZ_HOR(x) (((x) << RSZ_HOR_SHIFT) & RSZ_HOR_MASK) + +#define RSZ_DATA_STEP_SHIFT 16 +#define RSZ_DATA_STEP_MASK (0xffff << RSZ_DATA_STEP_SHIFT) +#define RSZ_PARA_STEP_SHIFT 0 +#define RSZ_PARA_STEP_MASK (0xffff << RSZ_PARA_STEP_SHIFT) + +#define RSZ_DATA_STEP(x) (((x) << RSZ_DATA_STEP_SHIFT) & RSZ_DATA_STEP_MASK) +#define RSZ_PARA_STEP(x) (((x) << RSZ_PARA_STEP_SHIFT) & RSZ_PARA_STEP_MASK) + +/* HBSC registers */ +#define HBSC_SATURATION 0x00 +#define HBSC_HUE 0x04 +#define HBSC_BRIGHT 0x08 +#define HBSC_CONTRAST 0x0c +#define HBSC_THRESHOLD_COL1 0x10 +#define HBSC_THRESHOLD_COL2 0x14 +#define HBSC_THRESHOLD_COL3 0x18 +#define HBSC_CTRL0 0x28 +#define HBSC_CTRL_EN BIT(2) + +#endif /* __ZX_PLANE_REGS_H__ */ diff --git a/drivers/gpu/drm/zte/zx_tvenc.c b/drivers/gpu/drm/zte/zx_tvenc.c new file mode 100644 index 000000000..b73afb212 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_tvenc.c @@ -0,0 +1,406 @@ +/* + * 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 { + struct drm_display_mode mode; + 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; +}; + +/* + * The CRM cannot directly provide a suitable frequency, and we have to + * ask a multiplied rate from CRM and use the divider in VOU to get the + * desired one. + */ +#define TVENC_CLOCK_MULTIPLIER 4 + +static const struct zx_tvenc_mode tvenc_mode_pal = { + .mode = { + .clock = 13500 * TVENC_CLOCK_MULTIPLIER, + .hdisplay = 720, + .hsync_start = 720 + 12, + .hsync_end = 720 + 12 + 2, + .htotal = 720 + 12 + 2 + 130, + .vdisplay = 576, + .vsync_start = 576 + 2, + .vsync_end = 576 + 2 + 2, + .vtotal = 576 + 2 + 2 + 20, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE, + }, + .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, +}; + +static const struct zx_tvenc_mode tvenc_mode_ntsc = { + .mode = { + .clock = 13500 * TVENC_CLOCK_MULTIPLIER, + .hdisplay = 720, + .hsync_start = 720 + 16, + .hsync_end = 720 + 16 + 2, + .htotal = 720 + 16 + 2 + 120, + .vdisplay = 480, + .vsync_start = 480 + 3, + .vsync_end = 480 + 3 + 2, + .vtotal = 480 + 3 + 2 + 17, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE, + }, + .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 *tvenc_modes[] = { + &tvenc_mode_pal, + &tvenc_mode_ntsc, +}; + +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 (drm_mode_equal(mode, &zmode->mode)) + 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 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++) { + const struct zx_tvenc_mode *zmode = tvenc_modes[i]; + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &zmode->mode); + if (!mode) { + DRM_DEV_ERROR(dev, "failed to duplicate drm mode\n"); + continue; + } + + drm_mode_set_name(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) +{ + struct zx_tvenc *tvenc = to_zx_tvenc(connector); + const struct zx_tvenc_mode *zmode; + + zmode = zx_tvenc_find_zmode(mode); + if (!zmode) { + DRM_DEV_ERROR(tvenc->dev, "unsupported mode: %s\n", mode->name); + return MODE_NOMODE; + } + + 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 = { + .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_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) +{ + /* Nothing to do */ +} + +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 000000000..bd91f5dcc --- /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_vga.c b/drivers/gpu/drm/zte/zx_vga.c new file mode 100644 index 000000000..23d1ff435 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_vga.c @@ -0,0 +1,530 @@ +/* + * Copyright (C) 2017 Sanechips Technology Co., Ltd. + * Copyright 2017 Linaro Ltd. + * + * 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_vga_regs.h" +#include "zx_vou.h" + +struct zx_vga_pwrctrl { + struct regmap *regmap; + u32 reg; + u32 mask; +}; + +struct zx_vga_i2c { + struct i2c_adapter adap; + struct mutex lock; +}; + +struct zx_vga { + struct drm_connector connector; + struct drm_encoder encoder; + struct zx_vga_i2c *ddc; + struct device *dev; + void __iomem *mmio; + struct clk *i2c_wclk; + struct zx_vga_pwrctrl pwrctrl; + struct completion complete; + bool connected; +}; + +#define to_zx_vga(x) container_of(x, struct zx_vga, x) + +static void zx_vga_encoder_enable(struct drm_encoder *encoder) +{ + struct zx_vga *vga = to_zx_vga(encoder); + struct zx_vga_pwrctrl *pwrctrl = &vga->pwrctrl; + + /* Set bit to power up VGA DACs */ + regmap_update_bits(pwrctrl->regmap, pwrctrl->reg, pwrctrl->mask, + pwrctrl->mask); + + vou_inf_enable(VOU_VGA, encoder->crtc); +} + +static void zx_vga_encoder_disable(struct drm_encoder *encoder) +{ + struct zx_vga *vga = to_zx_vga(encoder); + struct zx_vga_pwrctrl *pwrctrl = &vga->pwrctrl; + + vou_inf_disable(VOU_VGA, encoder->crtc); + + /* Clear bit to power down VGA DACs */ + regmap_update_bits(pwrctrl->regmap, pwrctrl->reg, pwrctrl->mask, 0); +} + +static const struct drm_encoder_helper_funcs zx_vga_encoder_helper_funcs = { + .enable = zx_vga_encoder_enable, + .disable = zx_vga_encoder_disable, +}; + +static const struct drm_encoder_funcs zx_vga_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static int zx_vga_connector_get_modes(struct drm_connector *connector) +{ + struct zx_vga *vga = to_zx_vga(connector); + struct edid *edid; + int ret; + + /* + * Clear both detection bits to switch I2C bus from device + * detecting to EDID reading. + */ + zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL, 0); + + edid = drm_get_edid(connector, &vga->ddc->adap); + if (!edid) { + /* + * If EDID reading fails, we set the device state into + * disconnected. Locking is not required here, since the + * VGA_AUTO_DETECT_SEL register write in irq handler cannot + * be triggered when both detection bits are cleared as above. + */ + zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL, + VGA_DETECT_SEL_NO_DEVICE); + vga->connected = false; + return 0; + } + + /* + * As edid reading succeeds, device must be connected, so we set + * up detection bit for unplug interrupt here. + */ + zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL, VGA_DETECT_SEL_HAS_DEVICE); + + drm_connector_update_edid_property(connector, edid); + ret = drm_add_edid_modes(connector, edid); + kfree(edid); + + return ret; +} + +static enum drm_mode_status +zx_vga_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static struct drm_connector_helper_funcs zx_vga_connector_helper_funcs = { + .get_modes = zx_vga_connector_get_modes, + .mode_valid = zx_vga_connector_mode_valid, +}; + +static enum drm_connector_status +zx_vga_connector_detect(struct drm_connector *connector, bool force) +{ + struct zx_vga *vga = to_zx_vga(connector); + + return vga->connected ? connector_status_connected : + connector_status_disconnected; +} + +static const struct drm_connector_funcs zx_vga_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = zx_vga_connector_detect, + .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_vga_register(struct drm_device *drm, struct zx_vga *vga) +{ + struct drm_encoder *encoder = &vga->encoder; + struct drm_connector *connector = &vga->connector; + struct device *dev = vga->dev; + int ret; + + encoder->possible_crtcs = VOU_CRTC_MASK; + + ret = drm_encoder_init(drm, encoder, &zx_vga_encoder_funcs, + DRM_MODE_ENCODER_DAC, NULL); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init encoder: %d\n", ret); + return ret; + }; + + drm_encoder_helper_add(encoder, &zx_vga_encoder_helper_funcs); + + vga->connector.polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(drm, connector, &zx_vga_connector_funcs, + DRM_MODE_CONNECTOR_VGA); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init connector: %d\n", ret); + goto clean_encoder; + }; + + drm_connector_helper_add(connector, &zx_vga_connector_helper_funcs); + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) { + DRM_DEV_ERROR(dev, "failed to attach encoder: %d\n", ret); + goto clean_connector; + }; + + return 0; + +clean_connector: + drm_connector_cleanup(connector); +clean_encoder: + drm_encoder_cleanup(encoder); + return ret; +} + +static int zx_vga_pwrctrl_init(struct zx_vga *vga) +{ + struct zx_vga_pwrctrl *pwrctrl = &vga->pwrctrl; + struct device *dev = vga->dev; + struct of_phandle_args out_args; + struct regmap *regmap; + int ret; + + ret = of_parse_phandle_with_fixed_args(dev->of_node, + "zte,vga-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_vga_i2c_read(struct zx_vga *vga, struct i2c_msg *msg) +{ + int len = msg->len; + u8 *buf = msg->buf; + u32 offset = 0; + int i; + + reinit_completion(&vga->complete); + + /* Select combo write */ + zx_writel_mask(vga->mmio + VGA_CMD_CFG, VGA_CMD_COMBO, VGA_CMD_COMBO); + zx_writel_mask(vga->mmio + VGA_CMD_CFG, VGA_CMD_RW, 0); + + while (len > 0) { + u32 cnt; + + /* Clear RX FIFO */ + zx_writel_mask(vga->mmio + VGA_RXF_CTRL, VGA_RX_FIFO_CLEAR, + VGA_RX_FIFO_CLEAR); + + /* Data offset to read from */ + zx_writel(vga->mmio + VGA_SUB_ADDR, offset); + + /* Kick off the transfer */ + zx_writel_mask(vga->mmio + VGA_CMD_CFG, VGA_CMD_TRANS, + VGA_CMD_TRANS); + + if (!wait_for_completion_timeout(&vga->complete, + msecs_to_jiffies(1000))) { + DRM_DEV_ERROR(vga->dev, "transfer timeout\n"); + return -ETIMEDOUT; + } + + cnt = zx_readl(vga->mmio + VGA_RXF_STATUS); + cnt = (cnt & VGA_RXF_COUNT_MASK) >> VGA_RXF_COUNT_SHIFT; + /* FIFO status may report more data than we need to read */ + cnt = min_t(u32, len, cnt); + + for (i = 0; i < cnt; i++) + *buf++ = zx_readl(vga->mmio + VGA_DATA); + + len -= cnt; + offset += cnt; + } + + return 0; +} + +static int zx_vga_i2c_write(struct zx_vga *vga, struct i2c_msg *msg) +{ + /* + * The DDC I2C adapter is only for reading EDID data, so we assume + * that the write to this adapter must be the EDID data offset. + */ + if ((msg->len != 1) || ((msg->addr != DDC_ADDR))) + return -EINVAL; + + /* Hardware will take care of the slave address shifting */ + zx_writel(vga->mmio + VGA_DEVICE_ADDR, msg->addr); + + return 0; +} + +static int zx_vga_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) +{ + struct zx_vga *vga = i2c_get_adapdata(adap); + struct zx_vga_i2c *ddc = vga->ddc; + int ret = 0; + int i; + + mutex_lock(&ddc->lock); + + for (i = 0; i < num; i++) { + if (msgs[i].flags & I2C_M_RD) + ret = zx_vga_i2c_read(vga, &msgs[i]); + else + ret = zx_vga_i2c_write(vga, &msgs[i]); + + if (ret < 0) + break; + } + + if (!ret) + ret = num; + + mutex_unlock(&ddc->lock); + + return ret; +} + +static u32 zx_vga_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm zx_vga_algorithm = { + .master_xfer = zx_vga_i2c_xfer, + .functionality = zx_vga_i2c_func, +}; + +static int zx_vga_ddc_register(struct zx_vga *vga) +{ + struct device *dev = vga->dev; + struct i2c_adapter *adap; + struct zx_vga_i2c *ddc; + int ret; + + ddc = devm_kzalloc(dev, sizeof(*ddc), GFP_KERNEL); + if (!ddc) + return -ENOMEM; + + vga->ddc = ddc; + mutex_init(&ddc->lock); + + adap = &ddc->adap; + adap->owner = THIS_MODULE; + adap->class = I2C_CLASS_DDC; + adap->dev.parent = dev; + adap->algo = &zx_vga_algorithm; + snprintf(adap->name, sizeof(adap->name), "zx vga i2c"); + + ret = i2c_add_adapter(adap); + if (ret) { + DRM_DEV_ERROR(dev, "failed to add I2C adapter: %d\n", ret); + return ret; + } + + i2c_set_adapdata(adap, vga); + + return 0; +} + +static irqreturn_t zx_vga_irq_thread(int irq, void *dev_id) +{ + struct zx_vga *vga = dev_id; + + drm_helper_hpd_irq_event(vga->connector.dev); + + return IRQ_HANDLED; +} + +static irqreturn_t zx_vga_irq_handler(int irq, void *dev_id) +{ + struct zx_vga *vga = dev_id; + u32 status; + + status = zx_readl(vga->mmio + VGA_I2C_STATUS); + + /* Clear interrupt status */ + zx_writel_mask(vga->mmio + VGA_I2C_STATUS, VGA_CLEAR_IRQ, + VGA_CLEAR_IRQ); + + if (status & VGA_DEVICE_CONNECTED) { + /* + * Since VGA_DETECT_SEL bits need to be reset for switching DDC + * bus from device detection to EDID read, rather than setting + * up HAS_DEVICE bit here, we need to do that in .get_modes + * hook for unplug detecting after EDID read succeeds. + */ + vga->connected = true; + return IRQ_WAKE_THREAD; + } + + if (status & VGA_DEVICE_DISCONNECTED) { + zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL, + VGA_DETECT_SEL_NO_DEVICE); + vga->connected = false; + return IRQ_WAKE_THREAD; + } + + if (status & VGA_TRANS_DONE) { + complete(&vga->complete); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static void zx_vga_hw_init(struct zx_vga *vga) +{ + unsigned long ref = clk_get_rate(vga->i2c_wclk); + int div; + + /* + * Set up I2C fast speed divider per formula below to get 400kHz. + * scl = ref / ((div + 1) * 4) + */ + div = DIV_ROUND_UP(ref / 1000, 400 * 4) - 1; + zx_writel(vga->mmio + VGA_CLK_DIV_FS, div); + + /* Set up device detection */ + zx_writel(vga->mmio + VGA_AUTO_DETECT_PARA, 0x80); + zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL, VGA_DETECT_SEL_NO_DEVICE); + + /* + * We need to poke monitor via DDC bus to get connection irq + * start working. + */ + zx_writel(vga->mmio + VGA_DEVICE_ADDR, DDC_ADDR); + zx_writel_mask(vga->mmio + VGA_CMD_CFG, VGA_CMD_TRANS, VGA_CMD_TRANS); +} + +static int zx_vga_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_vga *vga; + int irq; + int ret; + + vga = devm_kzalloc(dev, sizeof(*vga), GFP_KERNEL); + if (!vga) + return -ENOMEM; + + vga->dev = dev; + dev_set_drvdata(dev, vga); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + vga->mmio = devm_ioremap_resource(dev, res); + if (IS_ERR(vga->mmio)) + return PTR_ERR(vga->mmio); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + vga->i2c_wclk = devm_clk_get(dev, "i2c_wclk"); + if (IS_ERR(vga->i2c_wclk)) { + ret = PTR_ERR(vga->i2c_wclk); + DRM_DEV_ERROR(dev, "failed to get i2c_wclk: %d\n", ret); + return ret; + } + + ret = zx_vga_pwrctrl_init(vga); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init power control: %d\n", ret); + return ret; + } + + ret = zx_vga_ddc_register(vga); + if (ret) { + DRM_DEV_ERROR(dev, "failed to register ddc: %d\n", ret); + return ret; + } + + ret = zx_vga_register(drm, vga); + if (ret) { + DRM_DEV_ERROR(dev, "failed to register vga: %d\n", ret); + return ret; + } + + init_completion(&vga->complete); + + ret = devm_request_threaded_irq(dev, irq, zx_vga_irq_handler, + zx_vga_irq_thread, IRQF_SHARED, + dev_name(dev), vga); + if (ret) { + DRM_DEV_ERROR(dev, "failed to request threaded irq: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(vga->i2c_wclk); + if (ret) + return ret; + + zx_vga_hw_init(vga); + + return 0; +} + +static void zx_vga_unbind(struct device *dev, struct device *master, + void *data) +{ + struct zx_vga *vga = dev_get_drvdata(dev); + + clk_disable_unprepare(vga->i2c_wclk); +} + +static const struct component_ops zx_vga_component_ops = { + .bind = zx_vga_bind, + .unbind = zx_vga_unbind, +}; + +static int zx_vga_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &zx_vga_component_ops); +} + +static int zx_vga_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &zx_vga_component_ops); + return 0; +} + +static const struct of_device_id zx_vga_of_match[] = { + { .compatible = "zte,zx296718-vga", }, + { /* end */ }, +}; +MODULE_DEVICE_TABLE(of, zx_vga_of_match); + +struct platform_driver zx_vga_driver = { + .probe = zx_vga_probe, + .remove = zx_vga_remove, + .driver = { + .name = "zx-vga", + .of_match_table = zx_vga_of_match, + }, +}; diff --git a/drivers/gpu/drm/zte/zx_vga_regs.h b/drivers/gpu/drm/zte/zx_vga_regs.h new file mode 100644 index 000000000..feaa345fe --- /dev/null +++ b/drivers/gpu/drm/zte/zx_vga_regs.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 Sanechips Technology Co., Ltd. + * Copyright 2017 Linaro Ltd. + * + * 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_VGA_REGS_H__ +#define __ZX_VGA_REGS_H__ + +#define VGA_CMD_CFG 0x04 +#define VGA_CMD_TRANS BIT(6) +#define VGA_CMD_COMBO BIT(5) +#define VGA_CMD_RW BIT(4) +#define VGA_SUB_ADDR 0x0c +#define VGA_DEVICE_ADDR 0x10 +#define VGA_CLK_DIV_FS 0x14 +#define VGA_RXF_CTRL 0x20 +#define VGA_RX_FIFO_CLEAR BIT(7) +#define VGA_DATA 0x24 +#define VGA_I2C_STATUS 0x28 +#define VGA_DEVICE_DISCONNECTED BIT(7) +#define VGA_DEVICE_CONNECTED BIT(6) +#define VGA_CLEAR_IRQ BIT(4) +#define VGA_TRANS_DONE BIT(0) +#define VGA_RXF_STATUS 0x30 +#define VGA_RXF_COUNT_SHIFT 2 +#define VGA_RXF_COUNT_MASK GENMASK(7, 2) +#define VGA_AUTO_DETECT_PARA 0x34 +#define VGA_AUTO_DETECT_SEL 0x38 +#define VGA_DETECT_SEL_HAS_DEVICE BIT(1) +#define VGA_DETECT_SEL_NO_DEVICE BIT(0) + +#endif /* __ZX_VGA_REGS_H__ */ diff --git a/drivers/gpu/drm/zte/zx_vou.c b/drivers/gpu/drm/zte/zx_vou.c new file mode 100644 index 000000000..442311d31 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_vou.c @@ -0,0 +1,922 @@ +/* + * Copyright 2016 Linaro Ltd. + * Copyright 2016 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/of_address.h> +#include <video/videomode.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_plane_helper.h> +#include <drm/drmP.h> + +#include "zx_common_regs.h" +#include "zx_drm_drv.h" +#include "zx_plane.h" +#include "zx_vou.h" +#include "zx_vou_regs.h" + +#define GL_NUM 2 +#define VL_NUM 3 + +enum vou_chn_type { + VOU_CHN_MAIN, + VOU_CHN_AUX, +}; + +struct zx_crtc_regs { + u32 fir_active; + u32 fir_htiming; + u32 fir_vtiming; + u32 sec_vtiming; + u32 timing_shift; + u32 timing_pi_shift; +}; + +static const struct zx_crtc_regs main_crtc_regs = { + .fir_active = FIR_MAIN_ACTIVE, + .fir_htiming = FIR_MAIN_H_TIMING, + .fir_vtiming = FIR_MAIN_V_TIMING, + .sec_vtiming = SEC_MAIN_V_TIMING, + .timing_shift = TIMING_MAIN_SHIFT, + .timing_pi_shift = TIMING_MAIN_PI_SHIFT, +}; + +static const struct zx_crtc_regs aux_crtc_regs = { + .fir_active = FIR_AUX_ACTIVE, + .fir_htiming = FIR_AUX_H_TIMING, + .fir_vtiming = FIR_AUX_V_TIMING, + .sec_vtiming = SEC_AUX_V_TIMING, + .timing_shift = TIMING_AUX_SHIFT, + .timing_pi_shift = TIMING_AUX_PI_SHIFT, +}; + +struct zx_crtc_bits { + u32 polarity_mask; + u32 polarity_shift; + u32 int_frame_mask; + u32 tc_enable; + u32 sec_vactive_shift; + u32 sec_vactive_mask; + u32 interlace_select; + u32 pi_enable; + u32 div_vga_shift; + u32 div_pic_shift; + u32 div_tvenc_shift; + u32 div_hdmi_pnx_shift; + u32 div_hdmi_shift; + u32 div_inf_shift; + u32 div_layer_shift; +}; + +static const struct zx_crtc_bits main_crtc_bits = { + .polarity_mask = MAIN_POL_MASK, + .polarity_shift = MAIN_POL_SHIFT, + .int_frame_mask = TIMING_INT_MAIN_FRAME, + .tc_enable = MAIN_TC_EN, + .sec_vactive_shift = SEC_VACT_MAIN_SHIFT, + .sec_vactive_mask = SEC_VACT_MAIN_MASK, + .interlace_select = MAIN_INTERLACE_SEL, + .pi_enable = MAIN_PI_EN, + .div_vga_shift = VGA_MAIN_DIV_SHIFT, + .div_pic_shift = PIC_MAIN_DIV_SHIFT, + .div_tvenc_shift = TVENC_MAIN_DIV_SHIFT, + .div_hdmi_pnx_shift = HDMI_MAIN_PNX_DIV_SHIFT, + .div_hdmi_shift = HDMI_MAIN_DIV_SHIFT, + .div_inf_shift = INF_MAIN_DIV_SHIFT, + .div_layer_shift = LAYER_MAIN_DIV_SHIFT, +}; + +static const struct zx_crtc_bits aux_crtc_bits = { + .polarity_mask = AUX_POL_MASK, + .polarity_shift = AUX_POL_SHIFT, + .int_frame_mask = TIMING_INT_AUX_FRAME, + .tc_enable = AUX_TC_EN, + .sec_vactive_shift = SEC_VACT_AUX_SHIFT, + .sec_vactive_mask = SEC_VACT_AUX_MASK, + .interlace_select = AUX_INTERLACE_SEL, + .pi_enable = AUX_PI_EN, + .div_vga_shift = VGA_AUX_DIV_SHIFT, + .div_pic_shift = PIC_AUX_DIV_SHIFT, + .div_tvenc_shift = TVENC_AUX_DIV_SHIFT, + .div_hdmi_pnx_shift = HDMI_AUX_PNX_DIV_SHIFT, + .div_hdmi_shift = HDMI_AUX_DIV_SHIFT, + .div_inf_shift = INF_AUX_DIV_SHIFT, + .div_layer_shift = LAYER_AUX_DIV_SHIFT, +}; + +struct zx_crtc { + struct drm_crtc crtc; + struct drm_plane *primary; + struct zx_vou_hw *vou; + void __iomem *chnreg; + void __iomem *chncsc; + void __iomem *dither; + const struct zx_crtc_regs *regs; + const struct zx_crtc_bits *bits; + enum vou_chn_type chn_type; + struct clk *pixclk; +}; + +#define to_zx_crtc(x) container_of(x, struct zx_crtc, crtc) + +struct vou_layer_bits { + u32 enable; + u32 chnsel; + u32 clksel; +}; + +static const struct vou_layer_bits zx_gl_bits[GL_NUM] = { + { + .enable = OSD_CTRL0_GL0_EN, + .chnsel = OSD_CTRL0_GL0_SEL, + .clksel = VOU_CLK_GL0_SEL, + }, { + .enable = OSD_CTRL0_GL1_EN, + .chnsel = OSD_CTRL0_GL1_SEL, + .clksel = VOU_CLK_GL1_SEL, + }, +}; + +static const struct vou_layer_bits zx_vl_bits[VL_NUM] = { + { + .enable = OSD_CTRL0_VL0_EN, + .chnsel = OSD_CTRL0_VL0_SEL, + .clksel = VOU_CLK_VL0_SEL, + }, { + .enable = OSD_CTRL0_VL1_EN, + .chnsel = OSD_CTRL0_VL1_SEL, + .clksel = VOU_CLK_VL1_SEL, + }, { + .enable = OSD_CTRL0_VL2_EN, + .chnsel = OSD_CTRL0_VL2_SEL, + .clksel = VOU_CLK_VL2_SEL, + }, +}; + +struct zx_vou_hw { + struct device *dev; + void __iomem *osd; + void __iomem *timing; + void __iomem *vouctl; + void __iomem *otfppu; + void __iomem *dtrc; + struct clk *axi_clk; + struct clk *ppu_clk; + struct clk *main_clk; + struct clk *aux_clk; + struct zx_crtc *main_crtc; + struct zx_crtc *aux_crtc; +}; + +enum vou_inf_data_sel { + VOU_YUV444 = 0, + VOU_RGB_101010 = 1, + VOU_RGB_888 = 2, + VOU_RGB_666 = 3, +}; + +struct vou_inf { + enum vou_inf_id id; + enum vou_inf_data_sel data_sel; + u32 clocks_en_bits; + u32 clocks_sel_bits; +}; + +static struct vou_inf vou_infs[] = { + [VOU_HDMI] = { + .data_sel = VOU_YUV444, + .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), + }, + [VOU_VGA] = { + .data_sel = VOU_RGB_888, + .clocks_en_bits = BIT(1), + .clocks_sel_bits = BIT(10), + }, +}; + +static inline struct zx_vou_hw *crtc_to_vou(struct drm_crtc *crtc) +{ + struct zx_crtc *zcrtc = to_zx_crtc(crtc); + + return zcrtc->vou; +} + +void vou_inf_hdmi_audio_sel(struct drm_crtc *crtc, + enum vou_inf_hdmi_audio aud) +{ + struct zx_crtc *zcrtc = to_zx_crtc(crtc); + struct zx_vou_hw *vou = zcrtc->vou; + + zx_writel_mask(vou->vouctl + VOU_INF_HDMI_CTRL, VOU_HDMI_AUD_MASK, aud); +} + +void vou_inf_enable(enum vou_inf_id id, struct drm_crtc *crtc) +{ + struct zx_crtc *zcrtc = to_zx_crtc(crtc); + struct zx_vou_hw *vou = zcrtc->vou; + struct vou_inf *inf = &vou_infs[id]; + void __iomem *dither = zcrtc->dither; + void __iomem *csc = zcrtc->chncsc; + bool is_main = zcrtc->chn_type == VOU_CHN_MAIN; + u32 data_sel_shift = id << 1; + + if (inf->data_sel != VOU_YUV444) { + /* Enable channel CSC for RGB output */ + zx_writel_mask(csc + CSC_CTRL0, CSC_COV_MODE_MASK, + CSC_BT709_IMAGE_YCBCR2RGB << CSC_COV_MODE_SHIFT); + zx_writel_mask(csc + CSC_CTRL0, CSC_WORK_ENABLE, + CSC_WORK_ENABLE); + + /* Bypass Dither block for RGB output */ + zx_writel_mask(dither + OSD_DITHER_CTRL0, DITHER_BYSPASS, + DITHER_BYSPASS); + } else { + zx_writel_mask(csc + CSC_CTRL0, CSC_WORK_ENABLE, 0); + zx_writel_mask(dither + OSD_DITHER_CTRL0, DITHER_BYSPASS, 0); + } + + /* Select data format */ + zx_writel_mask(vou->vouctl + VOU_INF_DATA_SEL, 0x3 << data_sel_shift, + inf->data_sel << data_sel_shift); + + /* Select channel */ + zx_writel_mask(vou->vouctl + VOU_INF_CH_SEL, 0x1 << id, + zcrtc->chn_type << id); + + /* Select interface clocks */ + zx_writel_mask(vou->vouctl + VOU_CLK_SEL, inf->clocks_sel_bits, + is_main ? 0 : inf->clocks_sel_bits); + + /* Enable interface clocks */ + zx_writel_mask(vou->vouctl + VOU_CLK_EN, inf->clocks_en_bits, + inf->clocks_en_bits); + + /* Enable the device */ + zx_writel_mask(vou->vouctl + VOU_INF_EN, 1 << id, 1 << id); +} + +void vou_inf_disable(enum vou_inf_id id, struct drm_crtc *crtc) +{ + struct zx_vou_hw *vou = crtc_to_vou(crtc); + struct vou_inf *inf = &vou_infs[id]; + + /* Disable the device */ + zx_writel_mask(vou->vouctl + VOU_INF_EN, 1 << id, 0); + + /* Disable interface clocks */ + zx_writel_mask(vou->vouctl + VOU_CLK_EN, inf->clocks_en_bits, 0); +} + +void zx_vou_config_dividers(struct drm_crtc *crtc, + struct vou_div_config *configs, int num) +{ + struct zx_crtc *zcrtc = to_zx_crtc(crtc); + struct zx_vou_hw *vou = zcrtc->vou; + const struct zx_crtc_bits *bits = zcrtc->bits; + int i; + + /* Clear update flag bit */ + zx_writel_mask(vou->vouctl + VOU_DIV_PARA, DIV_PARA_UPDATE, 0); + + for (i = 0; i < num; i++) { + struct vou_div_config *cfg = configs + i; + u32 reg, shift; + + switch (cfg->id) { + case VOU_DIV_VGA: + reg = VOU_CLK_SEL; + shift = bits->div_vga_shift; + break; + case VOU_DIV_PIC: + reg = VOU_CLK_SEL; + shift = bits->div_pic_shift; + break; + case VOU_DIV_TVENC: + reg = VOU_DIV_PARA; + shift = bits->div_tvenc_shift; + break; + case VOU_DIV_HDMI_PNX: + reg = VOU_DIV_PARA; + shift = bits->div_hdmi_pnx_shift; + break; + case VOU_DIV_HDMI: + reg = VOU_DIV_PARA; + shift = bits->div_hdmi_shift; + break; + case VOU_DIV_INF: + reg = VOU_DIV_PARA; + shift = bits->div_inf_shift; + break; + case VOU_DIV_LAYER: + reg = VOU_DIV_PARA; + shift = bits->div_layer_shift; + break; + default: + continue; + } + + /* Each divider occupies 3 bits */ + zx_writel_mask(vou->vouctl + reg, 0x7 << shift, + cfg->val << shift); + } + + /* Set update flag bit to get dividers effected */ + zx_writel_mask(vou->vouctl + VOU_DIV_PARA, DIV_PARA_UPDATE, + DIV_PARA_UPDATE); +} + +static inline void vou_chn_set_update(struct zx_crtc *zcrtc) +{ + zx_writel(zcrtc->chnreg + CHN_UPDATE, 1); +} + +static void zx_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE; + struct zx_crtc *zcrtc = to_zx_crtc(crtc); + struct zx_vou_hw *vou = zcrtc->vou; + const struct zx_crtc_regs *regs = zcrtc->regs; + const struct zx_crtc_bits *bits = zcrtc->bits; + struct videomode vm; + u32 scan_mask; + u32 pol = 0; + u32 val; + int ret; + + drm_display_mode_to_videomode(mode, &vm); + + /* Set up timing parameters */ + val = V_ACTIVE((interlaced ? vm.vactive / 2 : vm.vactive) - 1); + val |= H_ACTIVE(vm.hactive - 1); + zx_writel(vou->timing + regs->fir_active, val); + + val = SYNC_WIDE(vm.hsync_len - 1); + val |= BACK_PORCH(vm.hback_porch - 1); + val |= FRONT_PORCH(vm.hfront_porch - 1); + zx_writel(vou->timing + regs->fir_htiming, val); + + val = SYNC_WIDE(vm.vsync_len - 1); + val |= BACK_PORCH(vm.vback_porch - 1); + val |= FRONT_PORCH(vm.vfront_porch - 1); + zx_writel(vou->timing + regs->fir_vtiming, val); + + if (interlaced) { + u32 shift = bits->sec_vactive_shift; + u32 mask = bits->sec_vactive_mask; + + val = zx_readl(vou->timing + SEC_V_ACTIVE); + val &= ~mask; + val |= ((vm.vactive / 2 - 1) << shift) & mask; + zx_writel(vou->timing + SEC_V_ACTIVE, val); + + val = SYNC_WIDE(vm.vsync_len - 1); + /* + * The vback_porch for the second field needs to shift one on + * the value for the first field. + */ + val |= BACK_PORCH(vm.vback_porch); + val |= FRONT_PORCH(vm.vfront_porch - 1); + zx_writel(vou->timing + regs->sec_vtiming, val); + } + + /* Set up polarities */ + if (vm.flags & DISPLAY_FLAGS_VSYNC_LOW) + pol |= 1 << POL_VSYNC_SHIFT; + if (vm.flags & DISPLAY_FLAGS_HSYNC_LOW) + pol |= 1 << POL_HSYNC_SHIFT; + + zx_writel_mask(vou->timing + TIMING_CTRL, bits->polarity_mask, + pol << bits->polarity_shift); + + /* Setup SHIFT register by following what ZTE BSP does */ + val = H_SHIFT_VAL; + if (interlaced) + val |= V_SHIFT_VAL << 16; + zx_writel(vou->timing + regs->timing_shift, val); + zx_writel(vou->timing + regs->timing_pi_shift, H_PI_SHIFT_VAL); + + /* Progressive or interlace scan select */ + scan_mask = bits->interlace_select | bits->pi_enable; + zx_writel_mask(vou->timing + SCAN_CTRL, scan_mask, + interlaced ? scan_mask : 0); + + /* Enable TIMING_CTRL */ + zx_writel_mask(vou->timing + TIMING_TC_ENABLE, bits->tc_enable, + bits->tc_enable); + + /* Configure channel screen size */ + zx_writel_mask(zcrtc->chnreg + CHN_CTRL1, CHN_SCREEN_W_MASK, + vm.hactive << CHN_SCREEN_W_SHIFT); + zx_writel_mask(zcrtc->chnreg + CHN_CTRL1, CHN_SCREEN_H_MASK, + vm.vactive << CHN_SCREEN_H_SHIFT); + + /* Configure channel interlace buffer control */ + zx_writel_mask(zcrtc->chnreg + CHN_INTERLACE_BUF_CTRL, CHN_INTERLACE_EN, + interlaced ? CHN_INTERLACE_EN : 0); + + /* Update channel */ + vou_chn_set_update(zcrtc); + + /* Enable channel */ + zx_writel_mask(zcrtc->chnreg + CHN_CTRL0, CHN_ENABLE, CHN_ENABLE); + + drm_crtc_vblank_on(crtc); + + ret = clk_set_rate(zcrtc->pixclk, mode->clock * 1000); + if (ret) { + DRM_DEV_ERROR(vou->dev, "failed to set pixclk rate: %d\n", ret); + return; + } + + ret = clk_prepare_enable(zcrtc->pixclk); + if (ret) + DRM_DEV_ERROR(vou->dev, "failed to enable pixclk: %d\n", ret); +} + +static void zx_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct zx_crtc *zcrtc = to_zx_crtc(crtc); + const struct zx_crtc_bits *bits = zcrtc->bits; + struct zx_vou_hw *vou = zcrtc->vou; + + clk_disable_unprepare(zcrtc->pixclk); + + drm_crtc_vblank_off(crtc); + + /* Disable channel */ + zx_writel_mask(zcrtc->chnreg + CHN_CTRL0, CHN_ENABLE, 0); + + /* Disable TIMING_CTRL */ + zx_writel_mask(vou->timing + TIMING_TC_ENABLE, bits->tc_enable, 0); +} + +static void zx_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct drm_pending_vblank_event *event = crtc->state->event; + + if (!event) + return; + + crtc->state->event = NULL; + + spin_lock_irq(&crtc->dev->event_lock); + if (drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); +} + +static const struct drm_crtc_helper_funcs zx_crtc_helper_funcs = { + .atomic_flush = zx_crtc_atomic_flush, + .atomic_enable = zx_crtc_atomic_enable, + .atomic_disable = zx_crtc_atomic_disable, +}; + +static int zx_vou_enable_vblank(struct drm_crtc *crtc) +{ + struct zx_crtc *zcrtc = to_zx_crtc(crtc); + struct zx_vou_hw *vou = crtc_to_vou(crtc); + u32 int_frame_mask = zcrtc->bits->int_frame_mask; + + zx_writel_mask(vou->timing + TIMING_INT_CTRL, int_frame_mask, + int_frame_mask); + + return 0; +} + +static void zx_vou_disable_vblank(struct drm_crtc *crtc) +{ + struct zx_crtc *zcrtc = to_zx_crtc(crtc); + struct zx_vou_hw *vou = crtc_to_vou(crtc); + + zx_writel_mask(vou->timing + TIMING_INT_CTRL, + zcrtc->bits->int_frame_mask, 0); +} + +static const struct drm_crtc_funcs zx_crtc_funcs = { + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .enable_vblank = zx_vou_enable_vblank, + .disable_vblank = zx_vou_disable_vblank, +}; + +static int zx_crtc_init(struct drm_device *drm, struct zx_vou_hw *vou, + enum vou_chn_type chn_type) +{ + struct device *dev = vou->dev; + struct zx_plane *zplane; + struct zx_crtc *zcrtc; + int ret; + + zcrtc = devm_kzalloc(dev, sizeof(*zcrtc), GFP_KERNEL); + if (!zcrtc) + return -ENOMEM; + + zcrtc->vou = vou; + zcrtc->chn_type = chn_type; + + zplane = devm_kzalloc(dev, sizeof(*zplane), GFP_KERNEL); + if (!zplane) + return -ENOMEM; + + zplane->dev = dev; + + if (chn_type == VOU_CHN_MAIN) { + zplane->layer = vou->osd + MAIN_GL_OFFSET; + zplane->csc = vou->osd + MAIN_GL_CSC_OFFSET; + zplane->hbsc = vou->osd + MAIN_HBSC_OFFSET; + zplane->rsz = vou->otfppu + MAIN_RSZ_OFFSET; + zplane->bits = &zx_gl_bits[0]; + zcrtc->chnreg = vou->osd + OSD_MAIN_CHN; + zcrtc->chncsc = vou->osd + MAIN_CHN_CSC_OFFSET; + zcrtc->dither = vou->osd + MAIN_DITHER_OFFSET; + zcrtc->regs = &main_crtc_regs; + zcrtc->bits = &main_crtc_bits; + } else { + zplane->layer = vou->osd + AUX_GL_OFFSET; + zplane->csc = vou->osd + AUX_GL_CSC_OFFSET; + zplane->hbsc = vou->osd + AUX_HBSC_OFFSET; + zplane->rsz = vou->otfppu + AUX_RSZ_OFFSET; + zplane->bits = &zx_gl_bits[1]; + zcrtc->chnreg = vou->osd + OSD_AUX_CHN; + zcrtc->chncsc = vou->osd + AUX_CHN_CSC_OFFSET; + zcrtc->dither = vou->osd + AUX_DITHER_OFFSET; + zcrtc->regs = &aux_crtc_regs; + zcrtc->bits = &aux_crtc_bits; + } + + zcrtc->pixclk = devm_clk_get(dev, (chn_type == VOU_CHN_MAIN) ? + "main_wclk" : "aux_wclk"); + if (IS_ERR(zcrtc->pixclk)) { + ret = PTR_ERR(zcrtc->pixclk); + DRM_DEV_ERROR(dev, "failed to get pix clk: %d\n", ret); + return ret; + } + + ret = zx_plane_init(drm, zplane, DRM_PLANE_TYPE_PRIMARY); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init primary plane: %d\n", ret); + return ret; + } + + zcrtc->primary = &zplane->plane; + + ret = drm_crtc_init_with_planes(drm, &zcrtc->crtc, zcrtc->primary, NULL, + &zx_crtc_funcs, NULL); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init drm crtc: %d\n", ret); + return ret; + } + + drm_crtc_helper_add(&zcrtc->crtc, &zx_crtc_helper_funcs); + + if (chn_type == VOU_CHN_MAIN) + vou->main_crtc = zcrtc; + else + vou->aux_crtc = zcrtc; + + return 0; +} + +void zx_vou_layer_enable(struct drm_plane *plane) +{ + struct zx_crtc *zcrtc = to_zx_crtc(plane->state->crtc); + struct zx_vou_hw *vou = zcrtc->vou; + struct zx_plane *zplane = to_zx_plane(plane); + const struct vou_layer_bits *bits = zplane->bits; + + if (zcrtc->chn_type == VOU_CHN_MAIN) { + zx_writel_mask(vou->osd + OSD_CTRL0, bits->chnsel, 0); + zx_writel_mask(vou->vouctl + VOU_CLK_SEL, bits->clksel, 0); + } else { + zx_writel_mask(vou->osd + OSD_CTRL0, bits->chnsel, + bits->chnsel); + zx_writel_mask(vou->vouctl + VOU_CLK_SEL, bits->clksel, + bits->clksel); + } + + zx_writel_mask(vou->osd + OSD_CTRL0, bits->enable, bits->enable); +} + +void zx_vou_layer_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct zx_crtc *zcrtc = to_zx_crtc(old_state->crtc); + struct zx_vou_hw *vou = zcrtc->vou; + struct zx_plane *zplane = to_zx_plane(plane); + const struct vou_layer_bits *bits = zplane->bits; + + zx_writel_mask(vou->osd + OSD_CTRL0, bits->enable, 0); +} + +static void zx_overlay_init(struct drm_device *drm, struct zx_vou_hw *vou) +{ + struct device *dev = vou->dev; + struct zx_plane *zplane; + int i; + int ret; + + /* + * VL0 has some quirks on scaling support which need special handling. + * Let's leave it out for now. + */ + for (i = 1; i < VL_NUM; i++) { + zplane = devm_kzalloc(dev, sizeof(*zplane), GFP_KERNEL); + if (!zplane) { + DRM_DEV_ERROR(dev, "failed to allocate zplane %d\n", i); + return; + } + + zplane->layer = vou->osd + OSD_VL_OFFSET(i); + zplane->hbsc = vou->osd + HBSC_VL_OFFSET(i); + zplane->rsz = vou->otfppu + RSZ_VL_OFFSET(i); + zplane->bits = &zx_vl_bits[i]; + + ret = zx_plane_init(drm, zplane, DRM_PLANE_TYPE_OVERLAY); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init overlay %d\n", i); + continue; + } + } +} + +static inline void zx_osd_int_update(struct zx_crtc *zcrtc) +{ + struct drm_crtc *crtc = &zcrtc->crtc; + struct drm_plane *plane; + + vou_chn_set_update(zcrtc); + + drm_for_each_plane_mask(plane, crtc->dev, crtc->state->plane_mask) + zx_plane_set_update(plane); +} + +static irqreturn_t vou_irq_handler(int irq, void *dev_id) +{ + struct zx_vou_hw *vou = dev_id; + u32 state; + + /* Handle TIMING_CTRL frame interrupts */ + state = zx_readl(vou->timing + TIMING_INT_STATE); + zx_writel(vou->timing + TIMING_INT_STATE, state); + + if (state & TIMING_INT_MAIN_FRAME) + drm_crtc_handle_vblank(&vou->main_crtc->crtc); + + if (state & TIMING_INT_AUX_FRAME) + drm_crtc_handle_vblank(&vou->aux_crtc->crtc); + + /* Handle OSD interrupts */ + state = zx_readl(vou->osd + OSD_INT_STA); + zx_writel(vou->osd + OSD_INT_CLRSTA, state); + + if (state & OSD_INT_MAIN_UPT) + zx_osd_int_update(vou->main_crtc); + + if (state & OSD_INT_AUX_UPT) + zx_osd_int_update(vou->aux_crtc); + + if (state & OSD_INT_ERROR) + DRM_DEV_ERROR(vou->dev, "OSD ERROR: 0x%08x!\n", state); + + return IRQ_HANDLED; +} + +static void vou_dtrc_init(struct zx_vou_hw *vou) +{ + /* Clear bit for bypass by ID */ + zx_writel_mask(vou->dtrc + DTRC_DETILE_CTRL, + TILE2RASTESCAN_BYPASS_MODE, 0); + + /* Select ARIDR mode */ + zx_writel_mask(vou->dtrc + DTRC_DETILE_CTRL, DETILE_ARIDR_MODE_MASK, + DETILE_ARID_IN_ARIDR); + + /* Bypass decompression for both frames */ + zx_writel_mask(vou->dtrc + DTRC_F0_CTRL, DTRC_DECOMPRESS_BYPASS, + DTRC_DECOMPRESS_BYPASS); + zx_writel_mask(vou->dtrc + DTRC_F1_CTRL, DTRC_DECOMPRESS_BYPASS, + DTRC_DECOMPRESS_BYPASS); + + /* Set up ARID register */ + zx_writel(vou->dtrc + DTRC_ARID, DTRC_ARID3(0xf) | DTRC_ARID2(0xe) | + DTRC_ARID1(0xf) | DTRC_ARID0(0xe)); +} + +static void vou_hw_init(struct zx_vou_hw *vou) +{ + /* Release reset for all VOU modules */ + zx_writel(vou->vouctl + VOU_SOFT_RST, ~0); + + /* Enable all VOU module clocks */ + zx_writel(vou->vouctl + VOU_CLK_EN, ~0); + + /* Clear both OSD and TIMING_CTRL interrupt state */ + zx_writel(vou->osd + OSD_INT_CLRSTA, ~0); + zx_writel(vou->timing + TIMING_INT_STATE, ~0); + + /* Enable OSD and TIMING_CTRL interrrupts */ + zx_writel(vou->osd + OSD_INT_MSK, OSD_INT_ENABLE); + zx_writel(vou->timing + TIMING_INT_CTRL, TIMING_INT_ENABLE); + + /* Select GPC as input to gl/vl scaler as a sane default setting */ + zx_writel(vou->otfppu + OTFPPU_RSZ_DATA_SOURCE, 0x2a); + + /* + * Needs to reset channel and layer logic per frame when frame starts + * to get VOU work properly. + */ + zx_writel_mask(vou->osd + OSD_RST_CLR, RST_PER_FRAME, RST_PER_FRAME); + + vou_dtrc_init(vou); +} + +static int zx_crtc_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct zx_vou_hw *vou; + struct resource *res; + int irq; + int ret; + + vou = devm_kzalloc(dev, sizeof(*vou), GFP_KERNEL); + if (!vou) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "osd"); + vou->osd = devm_ioremap_resource(dev, res); + if (IS_ERR(vou->osd)) { + ret = PTR_ERR(vou->osd); + DRM_DEV_ERROR(dev, "failed to remap osd region: %d\n", ret); + return ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "timing_ctrl"); + vou->timing = devm_ioremap_resource(dev, res); + if (IS_ERR(vou->timing)) { + ret = PTR_ERR(vou->timing); + DRM_DEV_ERROR(dev, "failed to remap timing_ctrl region: %d\n", + ret); + return ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dtrc"); + vou->dtrc = devm_ioremap_resource(dev, res); + if (IS_ERR(vou->dtrc)) { + ret = PTR_ERR(vou->dtrc); + DRM_DEV_ERROR(dev, "failed to remap dtrc region: %d\n", ret); + return ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vou_ctrl"); + vou->vouctl = devm_ioremap_resource(dev, res); + if (IS_ERR(vou->vouctl)) { + ret = PTR_ERR(vou->vouctl); + DRM_DEV_ERROR(dev, "failed to remap vou_ctrl region: %d\n", + ret); + return ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otfppu"); + vou->otfppu = devm_ioremap_resource(dev, res); + if (IS_ERR(vou->otfppu)) { + ret = PTR_ERR(vou->otfppu); + DRM_DEV_ERROR(dev, "failed to remap otfppu region: %d\n", ret); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + vou->axi_clk = devm_clk_get(dev, "aclk"); + if (IS_ERR(vou->axi_clk)) { + ret = PTR_ERR(vou->axi_clk); + DRM_DEV_ERROR(dev, "failed to get axi_clk: %d\n", ret); + return ret; + } + + vou->ppu_clk = devm_clk_get(dev, "ppu_wclk"); + if (IS_ERR(vou->ppu_clk)) { + ret = PTR_ERR(vou->ppu_clk); + DRM_DEV_ERROR(dev, "failed to get ppu_clk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(vou->axi_clk); + if (ret) { + DRM_DEV_ERROR(dev, "failed to enable axi_clk: %d\n", ret); + return ret; + } + + clk_prepare_enable(vou->ppu_clk); + if (ret) { + DRM_DEV_ERROR(dev, "failed to enable ppu_clk: %d\n", ret); + goto disable_axi_clk; + } + + vou->dev = dev; + dev_set_drvdata(dev, vou); + + vou_hw_init(vou); + + ret = devm_request_irq(dev, irq, vou_irq_handler, 0, "zx_vou", vou); + if (ret < 0) { + DRM_DEV_ERROR(dev, "failed to request vou irq: %d\n", ret); + goto disable_ppu_clk; + } + + ret = zx_crtc_init(drm, vou, VOU_CHN_MAIN); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init main channel crtc: %d\n", + ret); + goto disable_ppu_clk; + } + + ret = zx_crtc_init(drm, vou, VOU_CHN_AUX); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init aux channel crtc: %d\n", + ret); + goto disable_ppu_clk; + } + + zx_overlay_init(drm, vou); + + return 0; + +disable_ppu_clk: + clk_disable_unprepare(vou->ppu_clk); +disable_axi_clk: + clk_disable_unprepare(vou->axi_clk); + return ret; +} + +static void zx_crtc_unbind(struct device *dev, struct device *master, + void *data) +{ + struct zx_vou_hw *vou = dev_get_drvdata(dev); + + clk_disable_unprepare(vou->axi_clk); + clk_disable_unprepare(vou->ppu_clk); +} + +static const struct component_ops zx_crtc_component_ops = { + .bind = zx_crtc_bind, + .unbind = zx_crtc_unbind, +}; + +static int zx_crtc_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &zx_crtc_component_ops); +} + +static int zx_crtc_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &zx_crtc_component_ops); + return 0; +} + +static const struct of_device_id zx_crtc_of_match[] = { + { .compatible = "zte,zx296718-dpc", }, + { /* end */ }, +}; +MODULE_DEVICE_TABLE(of, zx_crtc_of_match); + +struct platform_driver zx_crtc_driver = { + .probe = zx_crtc_probe, + .remove = zx_crtc_remove, + .driver = { + .name = "zx-crtc", + .of_match_table = zx_crtc_of_match, + }, +}; diff --git a/drivers/gpu/drm/zte/zx_vou.h b/drivers/gpu/drm/zte/zx_vou.h new file mode 100644 index 000000000..5b7f84fbb --- /dev/null +++ b/drivers/gpu/drm/zte/zx_vou.h @@ -0,0 +1,68 @@ +/* + * Copyright 2016 Linaro Ltd. + * Copyright 2016 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_VOU_H__ +#define __ZX_VOU_H__ + +#define VOU_CRTC_MASK 0x3 + +/* VOU output interfaces */ +enum vou_inf_id { + VOU_HDMI = 0, + VOU_RGB_LCD = 1, + VOU_TV_ENC = 2, + VOU_MIPI_DSI = 3, + VOU_LVDS = 4, + VOU_VGA = 5, +}; + +enum vou_inf_hdmi_audio { + VOU_HDMI_AUD_SPDIF = BIT(0), + VOU_HDMI_AUD_I2S = BIT(1), + VOU_HDMI_AUD_DSD = BIT(2), + VOU_HDMI_AUD_HBR = BIT(3), + VOU_HDMI_AUD_PARALLEL = BIT(4), +}; + +void vou_inf_hdmi_audio_sel(struct drm_crtc *crtc, + enum vou_inf_hdmi_audio aud); +void vou_inf_enable(enum vou_inf_id id, struct drm_crtc *crtc); +void vou_inf_disable(enum vou_inf_id id, struct drm_crtc *crtc); + +enum vou_div_id { + VOU_DIV_VGA, + VOU_DIV_PIC, + VOU_DIV_TVENC, + VOU_DIV_HDMI_PNX, + VOU_DIV_HDMI, + VOU_DIV_INF, + VOU_DIV_LAYER, +}; + +enum vou_div_val { + VOU_DIV_1 = 0, + VOU_DIV_2 = 1, + VOU_DIV_4 = 3, + VOU_DIV_8 = 7, +}; + +struct vou_div_config { + enum vou_div_id id; + enum vou_div_val val; +}; + +void zx_vou_config_dividers(struct drm_crtc *crtc, + struct vou_div_config *configs, int num); + +void zx_vou_layer_enable(struct drm_plane *plane); +void zx_vou_layer_disable(struct drm_plane *plane, + struct drm_plane_state *old_state); + +#endif /* __ZX_VOU_H__ */ diff --git a/drivers/gpu/drm/zte/zx_vou_regs.h b/drivers/gpu/drm/zte/zx_vou_regs.h new file mode 100644 index 000000000..5a218351b --- /dev/null +++ b/drivers/gpu/drm/zte/zx_vou_regs.h @@ -0,0 +1,216 @@ +/* + * Copyright 2016 Linaro Ltd. + * Copyright 2016 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_VOU_REGS_H__ +#define __ZX_VOU_REGS_H__ + +/* Sub-module offset */ +#define MAIN_GL_OFFSET 0x130 +#define MAIN_GL_CSC_OFFSET 0x580 +#define MAIN_CHN_CSC_OFFSET 0x6c0 +#define MAIN_HBSC_OFFSET 0x820 +#define MAIN_DITHER_OFFSET 0x960 +#define MAIN_RSZ_OFFSET 0x600 /* OTFPPU sub-module */ + +#define AUX_GL_OFFSET 0x200 +#define AUX_GL_CSC_OFFSET 0x5d0 +#define AUX_CHN_CSC_OFFSET 0x710 +#define AUX_HBSC_OFFSET 0x860 +#define AUX_DITHER_OFFSET 0x970 +#define AUX_RSZ_OFFSET 0x800 + +#define OSD_VL0_OFFSET 0x040 +#define OSD_VL_OFFSET(i) (OSD_VL0_OFFSET + 0x050 * (i)) + +#define HBSC_VL0_OFFSET 0x760 +#define HBSC_VL_OFFSET(i) (HBSC_VL0_OFFSET + 0x040 * (i)) + +#define RSZ_VL1_U0 0xa00 +#define RSZ_VL_OFFSET(i) (RSZ_VL1_U0 + 0x200 * (i)) + +/* OSD (GPC_GLOBAL) registers */ +#define OSD_INT_STA 0x04 +#define OSD_INT_CLRSTA 0x08 +#define OSD_INT_MSK 0x0c +#define OSD_INT_AUX_UPT BIT(14) +#define OSD_INT_MAIN_UPT BIT(13) +#define OSD_INT_GL1_LBW BIT(10) +#define OSD_INT_GL0_LBW BIT(9) +#define OSD_INT_VL2_LBW BIT(8) +#define OSD_INT_VL1_LBW BIT(7) +#define OSD_INT_VL0_LBW BIT(6) +#define OSD_INT_BUS_ERR BIT(3) +#define OSD_INT_CFG_ERR BIT(2) +#define OSD_INT_ERROR (\ + OSD_INT_GL1_LBW | OSD_INT_GL0_LBW | \ + OSD_INT_VL2_LBW | OSD_INT_VL1_LBW | OSD_INT_VL0_LBW | \ + OSD_INT_BUS_ERR | OSD_INT_CFG_ERR \ +) +#define OSD_INT_ENABLE (OSD_INT_ERROR | OSD_INT_AUX_UPT | OSD_INT_MAIN_UPT) +#define OSD_CTRL0 0x10 +#define OSD_CTRL0_VL0_EN BIT(13) +#define OSD_CTRL0_VL0_SEL BIT(12) +#define OSD_CTRL0_VL1_EN BIT(11) +#define OSD_CTRL0_VL1_SEL BIT(10) +#define OSD_CTRL0_VL2_EN BIT(9) +#define OSD_CTRL0_VL2_SEL BIT(8) +#define OSD_CTRL0_GL0_EN BIT(7) +#define OSD_CTRL0_GL0_SEL BIT(6) +#define OSD_CTRL0_GL1_EN BIT(5) +#define OSD_CTRL0_GL1_SEL BIT(4) +#define OSD_RST_CLR 0x1c +#define RST_PER_FRAME BIT(19) + +/* Main/Aux channel registers */ +#define OSD_MAIN_CHN 0x470 +#define OSD_AUX_CHN 0x4d0 +#define CHN_CTRL0 0x00 +#define CHN_ENABLE BIT(0) +#define CHN_CTRL1 0x04 +#define CHN_SCREEN_W_SHIFT 18 +#define CHN_SCREEN_W_MASK (0x1fff << CHN_SCREEN_W_SHIFT) +#define CHN_SCREEN_H_SHIFT 5 +#define CHN_SCREEN_H_MASK (0x1fff << CHN_SCREEN_H_SHIFT) +#define CHN_UPDATE 0x08 +#define CHN_INTERLACE_BUF_CTRL 0x24 +#define CHN_INTERLACE_EN BIT(2) + +/* Dither registers */ +#define OSD_DITHER_CTRL0 0x00 +#define DITHER_BYSPASS BIT(31) + +/* TIMING_CTRL registers */ +#define TIMING_TC_ENABLE 0x04 +#define AUX_TC_EN BIT(1) +#define MAIN_TC_EN BIT(0) +#define FIR_MAIN_ACTIVE 0x08 +#define FIR_AUX_ACTIVE 0x0c +#define V_ACTIVE_SHIFT 16 +#define V_ACTIVE_MASK (0xffff << V_ACTIVE_SHIFT) +#define H_ACTIVE_SHIFT 0 +#define H_ACTIVE_MASK (0xffff << H_ACTIVE_SHIFT) +#define FIR_MAIN_H_TIMING 0x10 +#define FIR_MAIN_V_TIMING 0x14 +#define FIR_AUX_H_TIMING 0x18 +#define FIR_AUX_V_TIMING 0x1c +#define SYNC_WIDE_SHIFT 22 +#define SYNC_WIDE_MASK (0x3ff << SYNC_WIDE_SHIFT) +#define BACK_PORCH_SHIFT 11 +#define BACK_PORCH_MASK (0x7ff << BACK_PORCH_SHIFT) +#define FRONT_PORCH_SHIFT 0 +#define FRONT_PORCH_MASK (0x7ff << FRONT_PORCH_SHIFT) +#define TIMING_CTRL 0x20 +#define AUX_POL_SHIFT 3 +#define AUX_POL_MASK (0x7 << AUX_POL_SHIFT) +#define MAIN_POL_SHIFT 0 +#define MAIN_POL_MASK (0x7 << MAIN_POL_SHIFT) +#define POL_DE_SHIFT 2 +#define POL_VSYNC_SHIFT 1 +#define POL_HSYNC_SHIFT 0 +#define TIMING_INT_CTRL 0x24 +#define TIMING_INT_STATE 0x28 +#define TIMING_INT_AUX_FRAME BIT(3) +#define TIMING_INT_MAIN_FRAME BIT(1) +#define TIMING_INT_AUX_FRAME_SEL_VSW (0x2 << 10) +#define TIMING_INT_MAIN_FRAME_SEL_VSW (0x2 << 6) +#define TIMING_INT_ENABLE (\ + TIMING_INT_MAIN_FRAME_SEL_VSW | TIMING_INT_AUX_FRAME_SEL_VSW | \ + TIMING_INT_MAIN_FRAME | TIMING_INT_AUX_FRAME \ +) +#define TIMING_MAIN_SHIFT 0x2c +#define TIMING_AUX_SHIFT 0x30 +#define H_SHIFT_VAL 0x0048 +#define V_SHIFT_VAL 0x0001 +#define SCAN_CTRL 0x34 +#define AUX_PI_EN BIT(19) +#define MAIN_PI_EN BIT(18) +#define AUX_INTERLACE_SEL BIT(1) +#define MAIN_INTERLACE_SEL BIT(0) +#define SEC_V_ACTIVE 0x38 +#define SEC_VACT_MAIN_SHIFT 0 +#define SEC_VACT_MAIN_MASK (0xffff << SEC_VACT_MAIN_SHIFT) +#define SEC_VACT_AUX_SHIFT 16 +#define SEC_VACT_AUX_MASK (0xffff << SEC_VACT_AUX_SHIFT) +#define SEC_MAIN_V_TIMING 0x3c +#define SEC_AUX_V_TIMING 0x40 +#define TIMING_MAIN_PI_SHIFT 0x68 +#define TIMING_AUX_PI_SHIFT 0x6c +#define H_PI_SHIFT_VAL 0x000f + +#define V_ACTIVE(x) (((x) << V_ACTIVE_SHIFT) & V_ACTIVE_MASK) +#define H_ACTIVE(x) (((x) << H_ACTIVE_SHIFT) & H_ACTIVE_MASK) + +#define SYNC_WIDE(x) (((x) << SYNC_WIDE_SHIFT) & SYNC_WIDE_MASK) +#define BACK_PORCH(x) (((x) << BACK_PORCH_SHIFT) & BACK_PORCH_MASK) +#define FRONT_PORCH(x) (((x) << FRONT_PORCH_SHIFT) & FRONT_PORCH_MASK) + +/* DTRC registers */ +#define DTRC_F0_CTRL 0x2c +#define DTRC_F1_CTRL 0x5c +#define DTRC_DECOMPRESS_BYPASS BIT(17) +#define DTRC_DETILE_CTRL 0x68 +#define TILE2RASTESCAN_BYPASS_MODE BIT(30) +#define DETILE_ARIDR_MODE_MASK (0x3 << 0) +#define DETILE_ARID_ALL 0 +#define DETILE_ARID_IN_ARIDR 1 +#define DETILE_ARID_BYP_BUT_ARIDR 2 +#define DETILE_ARID_IN_ARIDR2 3 +#define DTRC_ARID 0x6c +#define DTRC_ARID3_SHIFT 24 +#define DTRC_ARID3_MASK (0xff << DTRC_ARID3_SHIFT) +#define DTRC_ARID2_SHIFT 16 +#define DTRC_ARID2_MASK (0xff << DTRC_ARID2_SHIFT) +#define DTRC_ARID1_SHIFT 8 +#define DTRC_ARID1_MASK (0xff << DTRC_ARID1_SHIFT) +#define DTRC_ARID0_SHIFT 0 +#define DTRC_ARID0_MASK (0xff << DTRC_ARID0_SHIFT) +#define DTRC_DEC2DDR_ARID 0x70 + +#define DTRC_ARID3(x) (((x) << DTRC_ARID3_SHIFT) & DTRC_ARID3_MASK) +#define DTRC_ARID2(x) (((x) << DTRC_ARID2_SHIFT) & DTRC_ARID2_MASK) +#define DTRC_ARID1(x) (((x) << DTRC_ARID1_SHIFT) & DTRC_ARID1_MASK) +#define DTRC_ARID0(x) (((x) << DTRC_ARID0_SHIFT) & DTRC_ARID0_MASK) + +/* VOU_CTRL registers */ +#define VOU_INF_EN 0x00 +#define VOU_INF_CH_SEL 0x04 +#define VOU_INF_DATA_SEL 0x08 +#define VOU_SOFT_RST 0x14 +#define VOU_CLK_SEL 0x18 +#define VGA_AUX_DIV_SHIFT 29 +#define VGA_MAIN_DIV_SHIFT 26 +#define PIC_MAIN_DIV_SHIFT 23 +#define PIC_AUX_DIV_SHIFT 20 +#define VOU_CLK_VL2_SEL BIT(8) +#define VOU_CLK_VL1_SEL BIT(7) +#define VOU_CLK_VL0_SEL BIT(6) +#define VOU_CLK_GL1_SEL BIT(5) +#define VOU_CLK_GL0_SEL BIT(4) +#define VOU_DIV_PARA 0x1c +#define DIV_PARA_UPDATE BIT(31) +#define TVENC_AUX_DIV_SHIFT 28 +#define HDMI_AUX_PNX_DIV_SHIFT 25 +#define HDMI_MAIN_PNX_DIV_SHIFT 22 +#define HDMI_AUX_DIV_SHIFT 19 +#define HDMI_MAIN_DIV_SHIFT 16 +#define TVENC_MAIN_DIV_SHIFT 13 +#define INF_AUX_DIV_SHIFT 9 +#define INF_MAIN_DIV_SHIFT 6 +#define LAYER_AUX_DIV_SHIFT 3 +#define LAYER_MAIN_DIV_SHIFT 0 +#define VOU_CLK_REQEN 0x20 +#define VOU_CLK_EN 0x24 +#define VOU_INF_HDMI_CTRL 0x30 +#define VOU_HDMI_AUD_MASK 0x1f + +/* OTFPPU_CTRL registers */ +#define OTFPPU_RSZ_DATA_SOURCE 0x04 + +#endif /* __ZX_VOU_REGS_H__ */ |