Commit 47ddb72f authored by Lukas Bulwahn's avatar Lukas Bulwahn Committed by Daniel Vetter

drm: zte: remove obsolete DRM Support for ZTE SoCs

Commit 89d4f98a ("ARM: remove zte zx platform") removes the config
ARCH_ZX. So, since then, the DRM Support for ZTE SoCs (config DRM_ZTE)
depends on this removed config ARCH_ZX and cannot be selected.

Fortunately, ./scripts/checkkconfigsymbols.py detects this and warns:

ARCH_ZX
Referencing files: drivers/gpu/drm/zte/Kconfig

So, remove this obsolete DRM support.
Signed-off-by: default avatarLukas Bulwahn <lukas.bulwahn@gmail.com>
Signed-off-by: default avatarDaniel Vetter <daniel.vetter@ffwll.ch>
Link: https://patchwork.freedesktop.org/patch/msgid/20210819112253.16484-5-lukas.bulwahn@gmail.com
parent 5e12f7ea
......@@ -353,8 +353,6 @@ source "drivers/gpu/drm/hisilicon/Kconfig"
source "drivers/gpu/drm/mediatek/Kconfig"
source "drivers/gpu/drm/zte/Kconfig"
source "drivers/gpu/drm/mxsfb/Kconfig"
source "drivers/gpu/drm/meson/Kconfig"
......
......@@ -113,7 +113,6 @@ obj-y += bridge/
obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/
obj-y += hisilicon/
obj-$(CONFIG_DRM_ZTE) += zte/
obj-$(CONFIG_DRM_MXSFB) += mxsfb/
obj-y += tiny/
obj-$(CONFIG_DRM_PL111) += pl111/
......
# SPDX-License-Identifier: GPL-2.0-only
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.
# 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
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2017 Sanechips Technology Co., Ltd.
* Copyright 2017 Linaro Ltd.
*/
#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__ */
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2016 Linaro Ltd.
* Copyright 2016 ZTE Corporation.
*/
#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_drv.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/drm_probe_helper.h>
#include <drm/drm_vblank.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,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
DEFINE_DRM_GEM_CMA_FOPS(zx_drm_fops);
static const struct drm_driver zx_drm_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
DRM_GEM_CMA_DRIVER_OPS,
.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;
}
drm_mode_config_reset(drm);
drm_kms_helper_poll_init(drm);
ret = drm_dev_register(drm, 0);
if (ret)
goto out_poll_fini;
drm_fbdev_generic_setup(drm, 32);
return 0;
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_put(drm);
return ret;
}
static void zx_drm_unbind(struct device *dev)
{
struct drm_device *drm = dev_get_drvdata(dev);
drm_dev_unregister(drm);
drm_kms_helper_poll_fini(drm);
drm_atomic_helper_shutdown(drm);
drm_mode_config_cleanup(drm);
component_unbind_all(dev, drm);
dev_set_drvdata(dev, NULL);
drm_dev_put(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);
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");
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2016 Linaro Ltd.
* Copyright 2016 ZTE Corporation.
*/
#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__ */
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2016 Linaro Ltd.
* Copyright 2016 ZTE Corporation.
*/
#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_edid.h>
#include <drm/drm_of.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_print.h>
#include <drm/drm_simple_kms_helper.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,
&hdmi->connector,
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(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 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_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
drm_encoder_helper_add(encoder, &zx_hdmi_encoder_helper_funcs);
hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
drm_connector_init_with_ddc(drm, &hdmi->connector,
&zx_hdmi_connector_funcs,
DRM_MODE_CONNECTOR_HDMIA,
&hdmi->ddc->adap);
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 = &params->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_mute(struct device *dev, void *data,
bool enable, int direction)
{
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,
.mute_stream = zx_hdmi_audio_mute,
.get_eld = zx_hdmi_audio_get_eld,
.no_capture_mute = 1,
};
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,
},
};
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2016 Linaro Ltd.
* Copyright 2016 ZTE Corporation.
*/
#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__ */
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2016 Linaro Ltd.
* Copyright 2016 ZTE Corporation.
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_plane_helper.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_atomic_state *state)
{
struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state,
plane);
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 || WARN_ON(!fb))
return 0;
crtc_state = drm_atomic_get_existing_crtc_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_atomic_state *state)
{
struct zx_plane *zplane = to_zx_plane(plane);
struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state,
plane);
struct drm_framebuffer *fb = new_state->fb;
struct drm_rect *src = &new_state->src;
struct drm_rect *dst = &new_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 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 */
paddr_reg = layer + VL_Y;
for (i = 0; i < fb->format->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 * fb->format->cpp[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_atomic_state *state)
{
struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state,
plane);
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_atomic_state *state)
{
struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state,
plane);
struct drm_framebuffer *fb = plane_state->fb;
struct drm_crtc *crtc = plane_state->crtc;
struct drm_crtc_state *crtc_state;
if (!crtc || WARN_ON(!fb))
return 0;
crtc_state = drm_atomic_get_existing_crtc_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_atomic_state *state)
{
struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state,
plane);
struct zx_plane *zplane = to_zx_plane(plane);
struct drm_framebuffer *fb = new_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 = new_state->src_x >> 16;
src_y = new_state->src_y >> 16;
src_w = new_state->src_w >> 16;
src_h = new_state->src_h >> 16;
dst_x = new_state->crtc_x;
dst_y = new_state->crtc_y;
dst_w = new_state->crtc_w;
dst_h = new_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 const struct drm_plane_funcs zx_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.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;
}
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2016 Linaro Ltd.
* Copyright 2016 ZTE Corporation.
*/
#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__ */
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2016 Linaro Ltd.
* Copyright 2016 ZTE Corporation.
*/
#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__ */
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2017 Linaro Ltd.
* Copyright 2017 ZTE Corporation.
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.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 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_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TVDAC);
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,
},
};
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2017 Linaro Ltd.
* Copyright 2017 ZTE Corporation.
*/
#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__ */
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2017 Sanechips Technology Co., Ltd.
* Copyright 2017 Linaro Ltd.
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.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 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_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_DAC);
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_with_ddc(drm, connector,
&zx_vga_connector_funcs,
DRM_MODE_CONNECTOR_VGA,
&vga->ddc->adap);
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,
},
};
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2017 Sanechips Technology Co., Ltd.
* Copyright 2017 Linaro Ltd.
*/
#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__ */
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2016 Linaro Ltd.
* Copyright 2016 ZTE Corporation.
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <video/videomode.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.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/drm_probe_helper.h>
#include <drm/drm_vblank.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_atomic_state *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_atomic_state *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_atomic_state *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,
},
};
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2016 Linaro Ltd.
* Copyright 2016 ZTE Corporation.
*/
#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__ */
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2016 Linaro Ltd.
* Copyright 2016 ZTE Corporation.
*/
#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__ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment