diff options
-rw-r--r-- | Documentation/devicetree/bindings/display/zte,vou.txt | 84 | ||||
-rw-r--r-- | MAINTAINERS | 7 | ||||
-rw-r--r-- | drivers/gpu/drm/Kconfig | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/Kconfig | 8 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/Makefile | 7 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_drm_drv.c | 267 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_drm_drv.h | 36 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_hdmi.c | 624 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_hdmi_regs.h | 56 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_plane.c | 299 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_plane.h | 26 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_plane_regs.h | 91 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_vou.c | 661 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_vou.h | 46 | ||||
-rw-r--r-- | drivers/gpu/drm/zte/zx_vou_regs.h | 157 |
16 files changed, 2372 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/display/zte,vou.txt b/Documentation/devicetree/bindings/display/zte,vou.txt new file mode 100644 index 000000000000..740e5bd2e4f7 --- /dev/null +++ b/Documentation/devicetree/bindings/display/zte,vou.txt @@ -0,0 +1,84 @@ +ZTE VOU Display Controller + +This is a display controller found on ZTE ZX296718 SoC. It includes multiple +Graphic Layer (GL) and Video Layer (VL), two Mixers/Channels, and a few blocks +handling scaling, color space conversion etc. VOU also integrates the support +for typical output devices, like HDMI, TV Encoder, VGA, and RGB LCD. + +* Master VOU node + +It must be the parent node of all the sub-device nodes. + +Required properties: + - compatible: should be "zte,zx296718-vou" + - #address-cells: should be <1> + - #size-cells: should be <1> + - ranges: list of address translations between VOU and sub-devices + +* VOU DPC device + +Required properties: + - compatible: should be "zte,zx296718-dpc" + - reg: Physical base address and length of DPC register regions, one for each + entry in 'reg-names' + - reg-names: The names of register regions. The following regions are required: + "osd" + "timing_ctrl" + "dtrc" + "vou_ctrl" + "otfppu" + - interrupts: VOU DPC interrupt number to CPU + - clocks: A list of phandle + clock-specifier pairs, one for each entry + in 'clock-names' + - clock-names: A list of clock names. The following clocks are required: + "aclk" + "ppu_wclk" + "main_wclk" + "aux_wclk" + +* HDMI output device + +Required properties: + - compatible: should be "zte,zx296718-hdmi" + - reg: Physical base address and length of the HDMI device IO region + - interrupts : HDMI interrupt number to CPU + - clocks: A list of phandle + clock-specifier pairs, one for each entry + in 'clock-names' + - clock-names: A list of clock names. The following clocks are required: + "osc_cec" + "osc_clk" + "xclk" + +Example: + +vou: vou@1440000 { + compatible = "zte,zx296718-vou"; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 0x1440000 0x10000>; + + dpc: dpc@0 { + compatible = "zte,zx296718-dpc"; + reg = <0x0000 0x1000>, <0x1000 0x1000>, + <0x5000 0x1000>, <0x6000 0x1000>, + <0xa000 0x1000>; + reg-names = "osd", "timing_ctrl", + "dtrc", "vou_ctrl", + "otfppu"; + interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&topcrm VOU_ACLK>, <&topcrm VOU_PPU_WCLK>, + <&topcrm VOU_MAIN_WCLK>, <&topcrm VOU_AUX_WCLK>; + clock-names = "aclk", "ppu_wclk", + "main_wclk", "aux_wclk"; + }; + + hdmi: hdmi@c000 { + compatible = "zte,zx296718-hdmi"; + reg = <0xc000 0x4000>; + interrupts = <GIC_SPI 82 IRQ_TYPE_EDGE_RISING>; + clocks = <&topcrm HDMI_OSC_CEC>, + <&topcrm HDMI_OSC_CLK>, + <&topcrm HDMI_XCLK>; + clock-names = "osc_cec", "osc_clk", "xclk"; + }; +}; diff --git a/MAINTAINERS b/MAINTAINERS index 561d4174cdfe..02f20dba7055 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4298,6 +4298,13 @@ S: Maintained F: drivers/gpu/drm/tilcdc/ F: Documentation/devicetree/bindings/display/tilcdc/ +DRM DRIVERS FOR ZTE ZX +M: Shawn Guo <shawnguo@kernel.org> +L: dri-devel@lists.freedesktop.org +S: Maintained +F: drivers/gpu/drm/zte/ +F: Documentation/devicetree/bindings/display/zte,vou.txt + DSBR100 USB FM RADIO DRIVER M: Alexey Klimov <klimov.linux@gmail.com> L: linux-media@vger.kernel.org diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 2ac0a564af60..863cdcad9f90 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -237,6 +237,8 @@ source "drivers/gpu/drm/hisilicon/Kconfig" source "drivers/gpu/drm/mediatek/Kconfig" +source "drivers/gpu/drm/zte/Kconfig" + # Keep legacy drivers last menuconfig DRM_LEGACY diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index c1d0602fbe24..f217274754d4 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -87,3 +87,4 @@ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/ obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/ obj-$(CONFIG_DRM_ARCPGU)+= arc/ obj-y += hisilicon/ +obj-$(CONFIG_DRM_ZTE) += zte/ diff --git a/drivers/gpu/drm/zte/Kconfig b/drivers/gpu/drm/zte/Kconfig new file mode 100644 index 000000000000..4065b2840f1c --- /dev/null +++ b/drivers/gpu/drm/zte/Kconfig @@ -0,0 +1,8 @@ +config DRM_ZTE + tristate "DRM Support for ZTE SoCs" + depends on DRM && ARCH_ZX + select DRM_KMS_CMA_HELPER + select DRM_KMS_FB_HELPER + select DRM_KMS_HELPER + 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 000000000000..699180bfd57c --- /dev/null +++ b/drivers/gpu/drm/zte/Makefile @@ -0,0 +1,7 @@ +zxdrm-y := \ + zx_drm_drv.o \ + zx_hdmi.o \ + zx_plane.o \ + zx_vou.o + +obj-$(CONFIG_DRM_ZTE) += zxdrm.o diff --git a/drivers/gpu/drm/zte/zx_drm_drv.c b/drivers/gpu/drm/zte/zx_drm_drv.c new file mode 100644 index 000000000000..abc8099e6f53 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_drm_drv.c @@ -0,0 +1,267 @@ +/* + * 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_of.h> +#include <drm/drmP.h> + +#include "zx_drm_drv.h" +#include "zx_vou.h" + +struct zx_drm_private { + struct drm_fbdev_cma *fbdev; +}; + +static void zx_drm_fb_output_poll_changed(struct drm_device *drm) +{ + struct zx_drm_private *priv = drm->dev_private; + + drm_fbdev_cma_hotplug_event(priv->fbdev); +} + +static const struct drm_mode_config_funcs zx_drm_mode_config_funcs = { + .fb_create = drm_fb_cma_create, + .output_poll_changed = zx_drm_fb_output_poll_changed, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static void zx_drm_lastclose(struct drm_device *drm) +{ + struct zx_drm_private *priv = drm->dev_private; + + drm_fbdev_cma_restore_mode(priv->fbdev); +} + +static const struct file_operations zx_drm_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .poll = drm_poll, + .read = drm_read, + .llseek = noop_llseek, + .mmap = drm_gem_cma_mmap, +}; + +static struct drm_driver zx_drm_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | + DRIVER_ATOMIC, + .lastclose = zx_drm_lastclose, + .get_vblank_counter = drm_vblank_no_hw_counter, + .enable_vblank = zx_vou_enable_vblank, + .disable_vblank = zx_vou_disable_vblank, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .dumb_create = drm_gem_cma_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + .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; + struct zx_drm_private *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + drm = drm_dev_alloc(&zx_drm_driver, dev); + if (!drm) + return -ENOMEM; + + drm->dev_private = priv; + 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); + + priv->fbdev = drm_fbdev_cma_init(drm, 32, drm->mode_config.num_crtc, + drm->mode_config.num_connector); + if (IS_ERR(priv->fbdev)) { + ret = PTR_ERR(priv->fbdev); + DRM_DEV_ERROR(dev, "failed to init cma fbdev: %d\n", ret); + priv->fbdev = NULL; + goto out_poll_fini; + } + + ret = drm_dev_register(drm, 0); + if (ret) + goto out_fbdev_fini; + + return 0; + +out_fbdev_fini: + if (priv->fbdev) { + drm_fbdev_cma_fini(priv->fbdev); + priv->fbdev = NULL; + } +out_poll_fini: + drm_kms_helper_poll_fini(drm); + drm_mode_config_cleanup(drm); + drm_vblank_cleanup(drm); +out_unbind: + component_unbind_all(dev, drm); +out_unregister: + dev_set_drvdata(dev, NULL); + drm->dev_private = NULL; + drm_dev_unref(drm); + return ret; +} + +static void zx_drm_unbind(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + struct zx_drm_private *priv = drm->dev_private; + + drm_dev_unregister(drm); + if (priv->fbdev) { + drm_fbdev_cma_fini(priv->fbdev); + priv->fbdev = NULL; + } + drm_kms_helper_poll_fini(drm); + drm_mode_config_cleanup(drm); + drm_vblank_cleanup(drm); + component_unbind_all(dev, drm); + dev_set_drvdata(dev, NULL); + drm->dev_private = 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 = of_platform_populate(parent, NULL, NULL, 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_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 000000000000..e65cd18a6cba --- /dev/null +++ b/drivers/gpu/drm/zte/zx_drm_drv.h @@ -0,0 +1,36 @@ +/* + * 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; + +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 000000000000..6bf6c364811e --- /dev/null +++ b/drivers/gpu/drm/zte/zx_hdmi.c @@ -0,0 +1,624 @@ +/* + * 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 "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; + const struct vou_inf *inf; +}; + +#define to_zx_hdmi(x) container_of(x, struct zx_hdmi, x) + +static const struct vou_inf vou_inf_hdmi = { + .id = VOU_HDMI, + .data_sel = VOU_YUV444, + .clocks_en_bits = BIT(24) | BIT(18) | BIT(6), + .clocks_sel_bits = BIT(13) | BIT(2), +}; + +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, + 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); + 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(hdmi->inf, encoder->crtc); +} + +static void zx_hdmi_encoder_disable(struct drm_encoder *encoder) +{ + struct zx_hdmi *hdmi = to_zx_hdmi(encoder); + + vou_inf_disable(hdmi->inf, 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_mode_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 = { + .dpms = drm_atomic_helper_connector_dpms, + .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_mode_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_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; + hdmi->inf = &vou_inf_hdmi; + + 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_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); +} + +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 000000000000..de911f66b658 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_hdmi_regs.h @@ -0,0 +1,56 @@ +/* + * 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) + +#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 000000000000..546eb92a94e8 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_plane.c @@ -0,0 +1,299 @@ +/* + * 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_drm_drv.h" +#include "zx_plane.h" +#include "zx_plane_regs.h" +#include "zx_vou.h" + +struct zx_plane { + struct drm_plane plane; + void __iomem *layer; + void __iomem *csc; + void __iomem *hbsc; + void __iomem *rsz; +}; + +#define to_zx_plane(plane) container_of(plane, struct zx_plane, plane) + +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 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; + struct drm_rect clip; + + 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; + + clip.x1 = 0; + clip.y1 = 0; + clip.x2 = crtc_state->adjusted_mode.hdisplay; + clip.y2 = crtc_state->adjusted_mode.vdisplay; + + return drm_plane_helper_check_state(plane_state, &clip, + 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); +} + +void zx_plane_set_update(struct drm_plane *plane) +{ + struct zx_plane *zplane = to_zx_plane(plane); + + zx_gl_rsz_set_update(zplane); + zx_gl_set_update(zplane); +} + +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->pixel_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 = drm_format_plane_cpp(format, 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_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, +}; + +static void zx_plane_destroy(struct drm_plane *plane) +{ + drm_plane_helper_disable(plane); + 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, +}; + +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); +} + +struct drm_plane *zx_plane_init(struct drm_device *drm, struct device *dev, + struct zx_layer_data *data, + enum drm_plane_type type) +{ + const struct drm_plane_helper_funcs *helper; + struct zx_plane *zplane; + struct drm_plane *plane; + const uint32_t *formats; + unsigned int format_count; + int ret; + + zplane = devm_kzalloc(dev, sizeof(*zplane), GFP_KERNEL); + if (!zplane) + return ERR_PTR(-ENOMEM); + + plane = &zplane->plane; + + zplane->layer = data->layer; + zplane->hbsc = data->hbsc; + zplane->csc = data->csc; + zplane->rsz = data->rsz; + + 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: + /* TODO: add video layer (vl) support */ + break; + default: + return ERR_PTR(-ENODEV); + } + + ret = drm_universal_plane_init(drm, plane, VOU_CRTC_MASK, + &zx_plane_funcs, formats, format_count, + type, NULL); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init universal plane: %d\n", ret); + return ERR_PTR(ret); + } + + drm_plane_helper_add(plane, helper); + + return plane; +} diff --git a/drivers/gpu/drm/zte/zx_plane.h b/drivers/gpu/drm/zte/zx_plane.h new file mode 100644 index 000000000000..2b82cd558d9d --- /dev/null +++ b/drivers/gpu/drm/zte/zx_plane.h @@ -0,0 +1,26 @@ +/* + * 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_layer_data { + void __iomem *layer; + void __iomem *csc; + void __iomem *hbsc; + void __iomem *rsz; +}; + +struct drm_plane *zx_plane_init(struct drm_device *drm, struct device *dev, + struct zx_layer_data *data, + 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 000000000000..3dde6716a558 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_plane_regs.h @@ -0,0 +1,91 @@ +/* + * 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) + +/* 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) + +/* RSZ registers */ +#define RSZ_SRC_CFG 0x00 +#define RSZ_DEST_CFG 0x04 +#define RSZ_ENABLE_CFG 0x14 + +#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) + +/* 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_vou.c b/drivers/gpu/drm/zte/zx_vou.c new file mode 100644 index 000000000000..73fe15c17c32 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_vou.c @@ -0,0 +1,661 @@ +/* + * 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_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 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, + .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, + .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 gl_enable; +}; + +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, + .gl_enable = OSD_CTRL0_GL0_EN, +}; + +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, + .gl_enable = OSD_CTRL0_GL1_EN, +}; + +struct zx_crtc { + struct drm_crtc crtc; + struct drm_plane *primary; + struct zx_vou_hw *vou; + void __iomem *chnreg; + 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 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; +}; + +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_enable(const struct vou_inf *inf, struct drm_crtc *crtc) +{ + struct zx_crtc *zcrtc = to_zx_crtc(crtc); + struct zx_vou_hw *vou = zcrtc->vou; + bool is_main = zcrtc->chn_type == VOU_CHN_MAIN; + u32 data_sel_shift = inf->id << 1; + + /* 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 << inf->id, + zcrtc->chn_type << inf->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 << inf->id, 1 << inf->id); +} + +void vou_inf_disable(const struct vou_inf *inf, struct drm_crtc *crtc) +{ + struct zx_vou_hw *vou = crtc_to_vou(crtc); + + /* Disable the device */ + zx_writel_mask(vou->vouctl + VOU_INF_EN, 1 << inf->id, 0); + + /* Disable interface clocks */ + zx_writel_mask(vou->vouctl + VOU_CLK_EN, inf->clocks_en_bits, 0); +} + +static inline void vou_chn_set_update(struct zx_crtc *zcrtc) +{ + zx_writel(zcrtc->chnreg + CHN_UPDATE, 1); +} + +static void zx_crtc_enable(struct drm_crtc *crtc) +{ + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + 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 pol = 0; + u32 val; + int ret; + + drm_display_mode_to_videomode(mode, &vm); + + /* Set up timing parameters */ + val = V_ACTIVE(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); + + /* 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 */ + zx_writel(vou->timing + regs->timing_shift, H_SHIFT_VAL); + zx_writel(vou->timing + regs->timing_pi_shift, H_PI_SHIFT_VAL); + + /* 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); + + /* Update channel */ + vou_chn_set_update(zcrtc); + + /* Enable channel */ + zx_writel_mask(zcrtc->chnreg + CHN_CTRL0, CHN_ENABLE, CHN_ENABLE); + + /* Enable Graphic Layer */ + zx_writel_mask(vou->osd + OSD_CTRL0, bits->gl_enable, + bits->gl_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_disable(struct drm_crtc *crtc) +{ + 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 Graphic Layer */ + zx_writel_mask(vou->osd + OSD_CTRL0, bits->gl_enable, 0); + + /* 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 = { + .enable = zx_crtc_enable, + .disable = zx_crtc_disable, + .atomic_flush = zx_crtc_atomic_flush, +}; + +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, +}; + +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_layer_data data; + 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; + + if (chn_type == VOU_CHN_MAIN) { + data.layer = vou->osd + MAIN_GL_OFFSET; + data.csc = vou->osd + MAIN_CSC_OFFSET; + data.hbsc = vou->osd + MAIN_HBSC_OFFSET; + data.rsz = vou->otfppu + MAIN_RSZ_OFFSET; + zcrtc->chnreg = vou->osd + OSD_MAIN_CHN; + zcrtc->regs = &main_crtc_regs; + zcrtc->bits = &main_crtc_bits; + } else { + data.layer = vou->osd + AUX_GL_OFFSET; + data.csc = vou->osd + AUX_CSC_OFFSET; + data.hbsc = vou->osd + AUX_HBSC_OFFSET; + data.rsz = vou->otfppu + AUX_RSZ_OFFSET; + zcrtc->chnreg = vou->osd + OSD_AUX_CHN; + 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; + } + + zcrtc->primary = zx_plane_init(drm, dev, &data, DRM_PLANE_TYPE_PRIMARY); + if (IS_ERR(zcrtc->primary)) { + ret = PTR_ERR(zcrtc->primary); + DRM_DEV_ERROR(dev, "failed to init primary plane: %d\n", ret); + return ret; + } + + 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; +} + +static inline struct drm_crtc *zx_find_crtc(struct drm_device *drm, int pipe) +{ + struct drm_crtc *crtc; + + list_for_each_entry(crtc, &drm->mode_config.crtc_list, head) + if (crtc->index == pipe) + return crtc; + + return NULL; +} + +int zx_vou_enable_vblank(struct drm_device *drm, unsigned int pipe) +{ + struct drm_crtc *crtc; + struct zx_crtc *zcrtc; + struct zx_vou_hw *vou; + u32 int_frame_mask; + + crtc = zx_find_crtc(drm, pipe); + if (!crtc) + return 0; + + vou = crtc_to_vou(crtc); + zcrtc = to_zx_crtc(crtc); + int_frame_mask = zcrtc->bits->int_frame_mask; + + zx_writel_mask(vou->timing + TIMING_INT_CTRL, int_frame_mask, + int_frame_mask); + + return 0; +} + +void zx_vou_disable_vblank(struct drm_device *drm, unsigned int pipe) +{ + struct drm_crtc *crtc; + struct zx_crtc *zcrtc; + struct zx_vou_hw *vou; + + crtc = zx_find_crtc(drm, pipe); + if (!crtc) + return; + + vou = crtc_to_vou(crtc); + zcrtc = to_zx_crtc(crtc); + + zx_writel_mask(vou->timing + TIMING_INT_CTRL, + zcrtc->bits->int_frame_mask, 0); +} + +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) { + vou_chn_set_update(vou->main_crtc); + zx_plane_set_update(vou->main_crtc->primary); + } + + if (state & OSD_INT_AUX_UPT) { + vou_chn_set_update(vou->aux_crtc); + zx_plane_set_update(vou->aux_crtc->primary); + } + + 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) +{ + /* Set GL0 to main channel and GL1 to aux channel */ + zx_writel_mask(vou->osd + OSD_CTRL0, OSD_CTRL0_GL0_SEL, 0); + zx_writel_mask(vou->osd + OSD_CTRL0, OSD_CTRL0_GL1_SEL, + OSD_CTRL0_GL1_SEL); + + /* Release reset for all VOU modules */ + zx_writel(vou->vouctl + VOU_SOFT_RST, ~0); + + /* Select main clock for GL0 and aux clock for GL1 module */ + zx_writel_mask(vou->vouctl + VOU_CLK_SEL, VOU_CLK_GL0_SEL, 0); + zx_writel_mask(vou->vouctl + VOU_CLK_SEL, VOU_CLK_GL1_SEL, + VOU_CLK_GL1_SEL); + + /* Enable clock auto-gating for all VOU modules */ + zx_writel(vou->vouctl + VOU_CLK_REQEN, ~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; + } + + 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 000000000000..349e06cd86f4 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_vou.h @@ -0,0 +1,46 @@ +/* + * 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_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; +}; + +void vou_inf_enable(const struct vou_inf *inf, struct drm_crtc *crtc); +void vou_inf_disable(const struct vou_inf *inf, struct drm_crtc *crtc); + +int zx_vou_enable_vblank(struct drm_device *drm, unsigned int pipe); +void zx_vou_disable_vblank(struct drm_device *drm, unsigned int pipe); + +#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 000000000000..f44e7a4ae441 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_vou_regs.h @@ -0,0 +1,157 @@ +/* + * 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_CSC_OFFSET 0x580 +#define MAIN_HBSC_OFFSET 0x820 +#define MAIN_RSZ_OFFSET 0x600 /* OTFPPU sub-module */ + +#define AUX_GL_OFFSET 0x200 +#define AUX_CSC_OFFSET 0x5d0 +#define AUX_HBSC_OFFSET 0x860 +#define AUX_RSZ_OFFSET 0x800 + +/* 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_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 + +/* 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 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 VOU_CLK_GL1_SEL BIT(5) +#define VOU_CLK_GL0_SEL BIT(4) +#define VOU_CLK_REQEN 0x20 +#define VOU_CLK_EN 0x24 + +/* OTFPPU_CTRL registers */ +#define OTFPPU_RSZ_DATA_SOURCE 0x04 + +#endif /* __ZX_VOU_REGS_H__ */ |