From a2a86f43fbbd34d7f2ba5fee2db45b709d97e122 Mon Sep 17 00:00:00 2001 From: fanglifei Date: Tue, 21 May 2024 15:57:24 +0800 Subject: [PATCH 013/219] feat: Add VO support for linux 6.6 Changelogs: 1. Add support for DC8000 and virtual display --- arch/riscv/boot/dts/Makefile | 1 + arch/riscv/configs/win2030_defconfig | 4 +- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/eswin/Kconfig | 72 + drivers/gpu/drm/eswin/Makefile | 23 + drivers/gpu/drm/eswin/dw_hdmi.c | 4617 +++++++++++++++++ drivers/gpu/drm/eswin/dw_hdmi.h | 1408 +++++ drivers/gpu/drm/eswin/dw_hdmi_audio.h | 23 + drivers/gpu/drm/eswin/dw_hdmi_cec.c | 334 ++ drivers/gpu/drm/eswin/dw_hdmi_cec.h | 18 + drivers/gpu/drm/eswin/dw_hdmi_hdcp.c | 1055 ++++ drivers/gpu/drm/eswin/dw_hdmi_hdcp.h | 71 + drivers/gpu/drm/eswin/dw_hdmi_hdcp2.c | 775 +++ drivers/gpu/drm/eswin/dw_hdmi_i2s_audio.c | 237 + drivers/gpu/drm/eswin/es_crtc.c | 436 ++ drivers/gpu/drm/eswin/es_crtc.h | 71 + drivers/gpu/drm/eswin/es_dc.c | 1083 ++++ drivers/gpu/drm/eswin/es_dc.h | 48 + drivers/gpu/drm/eswin/es_dc_hw.c | 1614 ++++++ drivers/gpu/drm/eswin/es_dc_hw.h | 473 ++ drivers/gpu/drm/eswin/es_dc_mmu.c | 778 +++ drivers/gpu/drm/eswin/es_dc_mmu.h | 100 + drivers/gpu/drm/eswin/es_drm.h | 23 + drivers/gpu/drm/eswin/es_drv.c | 517 ++ drivers/gpu/drm/eswin/es_drv.h | 71 + drivers/gpu/drm/eswin/es_fb.c | 141 + drivers/gpu/drm/eswin/es_fb.h | 13 + drivers/gpu/drm/eswin/es_gem.c | 598 +++ drivers/gpu/drm/eswin/es_gem.h | 76 + drivers/gpu/drm/eswin/es_plane.c | 399 ++ drivers/gpu/drm/eswin/es_plane.h | 87 + drivers/gpu/drm/eswin/es_simple_enc.c | 266 + drivers/gpu/drm/eswin/es_simple_enc.h | 27 + drivers/gpu/drm/eswin/es_type.h | 58 + drivers/gpu/drm/eswin/es_virtual.c | 1310 +++++ drivers/gpu/drm/eswin/es_virtual.h | 34 + drivers/gpu/drm/eswin/eswin_dw_hdmi.c | 1129 ++++ .../gpu/drm/eswin/host_lib_driver_linux_if.h | 146 + 39 files changed, 18138 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/eswin/Kconfig create mode 100644 drivers/gpu/drm/eswin/Makefile create mode 100644 drivers/gpu/drm/eswin/dw_hdmi.c create mode 100644 drivers/gpu/drm/eswin/dw_hdmi.h create mode 100644 drivers/gpu/drm/eswin/dw_hdmi_audio.h create mode 100644 drivers/gpu/drm/eswin/dw_hdmi_cec.c create mode 100644 drivers/gpu/drm/eswin/dw_hdmi_cec.h create mode 100644 drivers/gpu/drm/eswin/dw_hdmi_hdcp.c create mode 100644 drivers/gpu/drm/eswin/dw_hdmi_hdcp.h create mode 100644 drivers/gpu/drm/eswin/dw_hdmi_hdcp2.c create mode 100644 drivers/gpu/drm/eswin/dw_hdmi_i2s_audio.c create mode 100644 drivers/gpu/drm/eswin/es_crtc.c create mode 100644 drivers/gpu/drm/eswin/es_crtc.h create mode 100644 drivers/gpu/drm/eswin/es_dc.c create mode 100644 drivers/gpu/drm/eswin/es_dc.h create mode 100644 drivers/gpu/drm/eswin/es_dc_hw.c create mode 100644 drivers/gpu/drm/eswin/es_dc_hw.h create mode 100644 drivers/gpu/drm/eswin/es_dc_mmu.c create mode 100644 drivers/gpu/drm/eswin/es_dc_mmu.h create mode 100644 drivers/gpu/drm/eswin/es_drm.h create mode 100644 drivers/gpu/drm/eswin/es_drv.c create mode 100644 drivers/gpu/drm/eswin/es_drv.h create mode 100644 drivers/gpu/drm/eswin/es_fb.c create mode 100644 drivers/gpu/drm/eswin/es_fb.h create mode 100644 drivers/gpu/drm/eswin/es_gem.c create mode 100644 drivers/gpu/drm/eswin/es_gem.h create mode 100644 drivers/gpu/drm/eswin/es_plane.c create mode 100644 drivers/gpu/drm/eswin/es_plane.h create mode 100644 drivers/gpu/drm/eswin/es_simple_enc.c create mode 100644 drivers/gpu/drm/eswin/es_simple_enc.h create mode 100644 drivers/gpu/drm/eswin/es_type.h create mode 100644 drivers/gpu/drm/eswin/es_virtual.c create mode 100644 drivers/gpu/drm/eswin/es_virtual.h create mode 100644 drivers/gpu/drm/eswin/eswin_dw_hdmi.c create mode 100644 drivers/gpu/drm/eswin/host_lib_driver_linux_if.h diff --git a/arch/riscv/boot/dts/Makefile b/arch/riscv/boot/dts/Makefile index f60a280abb15..5c3eda88e8af 100644 --- a/arch/riscv/boot/dts/Makefile +++ b/arch/riscv/boot/dts/Makefile @@ -6,5 +6,6 @@ subdir-y += renesas subdir-y += sifive subdir-y += starfive subdir-y += thead +subdir-y += eswin obj-$(CONFIG_BUILTIN_DTB) := $(addsuffix /, $(subdir-y)) diff --git a/arch/riscv/configs/win2030_defconfig b/arch/riscv/configs/win2030_defconfig index 6733030403b9..dab66c993601 100644 --- a/arch/riscv/configs/win2030_defconfig +++ b/arch/riscv/configs/win2030_defconfig @@ -167,6 +167,9 @@ CONFIG_DRM_NOUVEAU=y CONFIG_DRM_DISPLAY_CONNECTOR=y CONFIG_DRM_SIMPLE_BRIDGE=y CONFIG_DRM_TOSHIBA_TC358768=m +CONFIG_DRM_ESWIN=y +CONFIG_ESWIN_VIRTUAL_DISPLAY=y +CONFIG_ESWIN_MMU=y CONFIG_DRM_LEGACY=y CONFIG_FB=y CONFIG_SOUND=y @@ -277,7 +280,6 @@ CONFIG_CONSOLE_LOGLEVEL_DEFAULT=15 CONFIG_CONSOLE_LOGLEVEL_QUIET=15 CONFIG_MESSAGE_LOGLEVEL_DEFAULT=7 CONFIG_DYNAMIC_DEBUG=y -CONFIG_DEBUG_FS=y CONFIG_DEBUG_PAGEALLOC=y CONFIG_SCHED_STACK_END_CHECK=y CONFIG_DEBUG_VM=y diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index ec4abf9ff47b..fb30fa9500b3 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -388,6 +388,8 @@ source "drivers/gpu/drm/solomon/Kconfig" source "drivers/gpu/drm/sprd/Kconfig" +source "drivers/gpu/drm/eswin/Kconfig" + config DRM_HYPERV tristate "DRM Support for Hyper-V synthetic video device" depends on DRM && PCI && MMU && HYPERV diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 215e78e79125..a2b855de24bf 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -198,3 +198,4 @@ obj-$(CONFIG_DRM_HYPERV) += hyperv/ obj-y += solomon/ obj-$(CONFIG_DRM_SPRD) += sprd/ obj-$(CONFIG_DRM_LOONGSON) += loongson/ +obj-$(CONFIG_DRM_ESWIN) += eswin/ diff --git a/drivers/gpu/drm/eswin/Kconfig b/drivers/gpu/drm/eswin/Kconfig new file mode 100644 index 000000000000..6a7771e6ac29 --- /dev/null +++ b/drivers/gpu/drm/eswin/Kconfig @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: GPL-2.0 + +config DRM_ESWIN + tristate "DRM Support for Eswin" + depends on DRM + select DRM_KMS_HELPER + help + Choose this option if you have a Eswin soc chipset. + This driver provides Eswin kernel mode + setting and buffer management. It does not + provide 2D or 3D acceleration. + +config ESWIN_VIRTUAL_DISPLAY + bool "display content output to debugfs file" + depends on DRM_ESWIN + select DEBUG_FS + help + This is a debug feature which capture video content output + from display controller. Output path is debugfs/dri/connector/. + The content format is ARGB which Alpha is 0 for 8bits. + Disabled in default. + +config ESWIN_MMU + bool "MMU support for Eswin display controller" + depends on DRM_ESWIN + help + This is a memory management function which translate virtual address + to physical address. DPU MMU only do address translate, doesn't + support security and shareable. + +config ESWIN_DW_HDMI + bool "ESWIN specific extensions for Synopsys DW HDMI" + depends on DRM_ESWIN + select CEC_CORE if CEC_NOTIFIER + select DW_HDMI + help + This selects support for ESWIN SoC specific extensions + for the Synopsys DesignWare HDMI driver. If you want to + enable HDMI on win2030 based SoC, you should select + this option. + +config DW_HDMI_I2S_AUDIO + bool "Synopsys Designware I2S Audio interface" + depends on SND_SOC + depends on ESWIN_DW_HDMI + select SND_SOC_HDMI_CODEC + help + Support the I2S Audio interface which is part of the Synopsys + Designware HDMI block. + +config DW_HDMI_CEC + bool "Synopsis Designware CEC interface" + depends on ESWIN_DW_HDMI + select CEC_CORE + select CEC_NOTIFIER + help + Support the CEC interface which is part of the Synopsys + Designware HDMI block. + +config DW_HDMI_HDCP + bool "Synopsis Designware HDCP interface" + depends on ESWIN_DW_HDMI + help + Support the HDCP interface which is part of the Synopsys + Designware HDMI block. + +config DW_HDMI_HDCP2 + tristate "Synopsis Designware HDCP2 interface" + select DRM_ESWIN + help + Support the HDCP2 interface which is part of the Synopsys + Designware HDMI block. diff --git a/drivers/gpu/drm/eswin/Makefile b/drivers/gpu/drm/eswin/Makefile new file mode 100644 index 000000000000..e84e28f388d6 --- /dev/null +++ b/drivers/gpu/drm/eswin/Makefile @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0 + +es_drm-objs := es_dc_hw.o \ + es_dc.o \ + es_crtc.o \ + es_drv.o \ + es_fb.o \ + es_gem.o \ + es_plane.o \ + es_simple_enc.o + +es_drm-$(CONFIG_ESWIN_VIRTUAL_DISPLAY) += es_virtual.o +es_drm-$(CONFIG_ESWIN_MMU) += es_dc_mmu.o +es_drm-$(CONFIG_ESWIN_DW_HDMI) += eswin_dw_hdmi.o dw_hdmi.o + +es_drm-$(CONFIG_DW_HDMI_I2S_AUDIO) += dw_hdmi_i2s_audio.o +es_drm-$(CONFIG_DW_HDMI_CEC) += dw_hdmi_cec.o +es_drm-$(CONFIG_DW_HDMI_HDCP) += dw_hdmi_hdcp.o + +dw_hdcp2-$(CONFIG_DW_HDMI_HDCP2) += dw_hdmi_hdcp2.o + +obj-$(CONFIG_DRM_ESWIN) += es_drm.o +obj-$(CONFIG_DW_HDMI_HDCP2) += dw_hdcp2.o \ No newline at end of file diff --git a/drivers/gpu/drm/eswin/dw_hdmi.c b/drivers/gpu/drm/eswin/dw_hdmi.c new file mode 100644 index 000000000000..ae13cbcb6e29 --- /dev/null +++ b/drivers/gpu/drm/eswin/dw_hdmi.c @@ -0,0 +1,4617 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DesignWare High-Definition Multimedia Interface (HDMI) driver + * + * Copyright (C) 2013-2015 Mentor Graphics Inc. + * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. + * Copyright (C) 2010, Guennadi Liakhovetski + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dw_hdmi_audio.h" +#include "dw_hdmi_cec.h" +#include "dw_hdmi.h" +#include "dw_hdmi_hdcp.h" + +#define DDC_CI_ADDR 0x37 +#define DDC_SEGMENT_ADDR 0x30 + +#define HDMI_EDID_LEN 512 + +/* DW-HDMI Controller >= 0x200a are at least compliant with SCDC version 1 */ +#define SCDC_MIN_SOURCE_VERSION 0x1 + +#define HDMI14_MAX_TMDSCLK 340000000 + +static bool hpd_flag = false; + +enum hdmi_datamap { + RGB444_8B = 0x01, + RGB444_10B = 0x03, + RGB444_12B = 0x05, + RGB444_16B = 0x07, + YCbCr444_8B = 0x09, + YCbCr444_10B = 0x0B, + YCbCr444_12B = 0x0D, + YCbCr444_16B = 0x0F, + YCbCr422_8B = 0x16, + YCbCr422_10B = 0x14, + YCbCr422_12B = 0x12, +}; + +static const u16 csc_coeff_default[3][4] = { { 0x2000, 0x0000, 0x0000, 0x0000 }, + { 0x0000, 0x2000, 0x0000, 0x0000 }, + { 0x0000, 0x0000, 0x2000, + 0x0000 } }; + +static const u16 csc_coeff_rgb_out_eitu601[3][4] = { + { 0x2000, 0x6926, 0x74fd, 0x010e }, + { 0x2000, 0x2cdd, 0x0000, 0x7e9a }, + { 0x2000, 0x0000, 0x38b4, 0x7e3b } +}; + +static const u16 csc_coeff_rgb_out_eitu709[3][4] = { + { 0x2000, 0x7106, 0x7a02, 0x00a7 }, + { 0x2000, 0x3264, 0x0000, 0x7e6d }, + { 0x2000, 0x0000, 0x3b61, 0x7e25 } +}; + +static const u16 csc_coeff_rgb_in_eitu601[3][4] = { + { 0x2591, 0x1322, 0x074b, 0x0000 }, + { 0x6535, 0x2000, 0x7acc, 0x0200 }, + { 0x6acd, 0x7534, 0x2000, 0x0200 } +}; + +static const u16 csc_coeff_rgb_in_eitu709[3][4] = { + { 0x2dc5, 0x0d9b, 0x049e, 0x0000 }, + { 0x62f0, 0x2000, 0x7d11, 0x0200 }, + { 0x6756, 0x78ab, 0x2000, 0x0200 } +}; + +static const u16 csc_coeff_rgb_full_to_rgb_limited[3][4] = { + { 0x1b7c, 0x0000, 0x0000, 0x0020 }, + { 0x0000, 0x1b7c, 0x0000, 0x0020 }, + { 0x0000, 0x0000, 0x1b7c, 0x0020 } +}; + +static const u16 csc_coeff_rgb_to_yuv_8bit_eitu601[3][4] = { + { 0x2591, 0x1323, 0x074c, 0x0000 }, + { 0xe534, 0x2000, 0xfacc, 0x0200 }, + { 0xeacd, 0xf533, 0x2000, 0x0200 } +}; + +static const u16 csc_coeff_rgb_to_yuv_8bit_eitu709[3][4] = { + { 0x2dc6, 0x0d9b, 0x049f, 0x0000 }, + { 0xe2ef, 0x2000, 0xfd11, 0x0200 }, + { 0xe755, 0xf8ab, 0x2000, 0x0200 } +}; + +static const u16 csc_coeff_rgb_to_yuv_10bit_eitu601[3][4] = { + { 0x2591, 0x1323, 0x074c, 0x0000 }, + { 0xe534, 0x2000, 0xfacc, 0x0800 }, + { 0xeacd, 0xf533, 0x2000, 0x0800 } +}; + +static const u16 csc_coeff_rgb_to_yuv_10bit_eitu709[3][4] = { + { 0x2dc6, 0x0d9b, 0x049f, 0x0000 }, + { 0xe2ef, 0x2000, 0xfd11, 0x0800 }, + { 0xe755, 0xf8ab, 0x2000, 0x0800 } +}; + +struct hdmi_vmode { + bool mdataenablepolarity; + unsigned int previous_pixelclock; + unsigned int mpixelclock; + unsigned int mpixelrepetitioninput; + unsigned int mpixelrepetitionoutput; + unsigned int previous_tmdsclock; + unsigned int mtmdsclock; +}; + +struct hdmi_data_info { + unsigned int enc_in_bus_format; + unsigned int enc_out_bus_format; + unsigned int enc_in_encoding; + unsigned int enc_out_encoding; + unsigned int pix_repet_factor; + unsigned int hdcp_enable; + struct hdmi_vmode video_mode; + bool rgb_limited_range; +}; + +struct dw_hdmi_i2c { + struct i2c_adapter adap; + + struct mutex lock; /* used to serialize data transfers */ + struct completion cmp; + u8 stat; + + u8 slave_reg; + bool is_regaddr; + bool is_segment; + unsigned int scl_high_ns; + unsigned int scl_low_ns; +}; + +struct dw_hdmi_phy_data { + enum dw_hdmi_phy_type type; + const char *name; + unsigned int gen; + bool has_svsret; + int (*configure)(struct dw_hdmi *hdmi, + const struct dw_hdmi_plat_data *pdata, + unsigned long mpixelclock); +}; + +struct dw_hdmi { + struct drm_connector connector; + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + unsigned int version; + + struct platform_device *hdcp_dev; + struct platform_device *audio; + struct platform_device *cec; + struct device *dev; + struct clk *isfr_clk; + struct clk *hclk_vio; + struct clk *iahb_clk; + struct clk *cec_clk; + struct dw_hdmi_i2c *i2c; + + struct hdmi_data_info hdmi_data; + const struct dw_hdmi_plat_data *plat_data; + struct dw_hdcp *hdcp; + int vic; + + u8 edid[HDMI_EDID_LEN]; + + struct { + const struct dw_hdmi_phy_ops *ops; + const char *name; + void *data; + bool enabled; + } phy; + + struct drm_display_mode previous_mode; + + struct i2c_adapter *ddc; + void __iomem *regs; + bool sink_is_hdmi; + bool sink_has_audio; + bool hpd_state; + + struct pinctrl *pinctrl; + struct pinctrl_state *default_state; + struct pinctrl_state *unwedge_state; + + struct mutex mutex; /* for state below and previous_mode */ + enum drm_connector_force force; /* mutex-protected force state */ + struct drm_connector + *curr_conn; /* current connector (only valid when !disabled) */ + bool disabled; /* DRM has disabled our bridge */ + bool bridge_is_on; /* indicates the bridge is on */ + bool rxsense; /* rxsense state */ + u8 phy_mask; /* desired phy int mask settings */ + u8 mc_clkdis; /* clock disable register */ + + spinlock_t audio_lock; + struct mutex audio_mutex; + struct dentry *debugfs_dir; + unsigned int sample_rate; + unsigned int audio_cts; + unsigned int audio_n; + bool audio_enable; + + unsigned int reg_shift; + struct regmap *regm; + void (*enable_audio)(struct dw_hdmi *hdmi); + void (*disable_audio)(struct dw_hdmi *hdmi); + + struct mutex cec_notifier_mutex; + struct cec_notifier *cec_notifier; + + hdmi_codec_plugged_cb plugged_cb; + struct device *codec_dev; + enum drm_connector_status last_connector_result; + + int irq; + struct delayed_work work; + struct workqueue_struct *workqueue; + struct reset_control *rst_hdmi_prstn; + struct reset_control *rst_hdmi_phyrstn; + struct reset_control *rst_hdmi_rstn; + + u32 eswin_plat; +}; + +#define HDMI_IH_PHY_STAT0_RX_SENSE \ + (HDMI_IH_PHY_STAT0_RX_SENSE0 | HDMI_IH_PHY_STAT0_RX_SENSE1 | \ + HDMI_IH_PHY_STAT0_RX_SENSE2 | HDMI_IH_PHY_STAT0_RX_SENSE3) + +#define HDMI_PHY_RX_SENSE \ + (HDMI_PHY_RX_SENSE0 | HDMI_PHY_RX_SENSE1 | HDMI_PHY_RX_SENSE2 | \ + HDMI_PHY_RX_SENSE3) + +static struct edid *dw_hdmi_get_edid(struct dw_hdmi *hdmi, + struct drm_connector *connector); +static void hdmi_tx_hdcp_config(struct dw_hdmi *hdmi, + const struct drm_display_mode *mode); + +static inline void hdmi_writeb(struct dw_hdmi *hdmi, u8 val, int offset) +{ + int ret = regmap_write(hdmi->regm, offset << hdmi->reg_shift, val); + if (ret != 0) { + dev_err(hdmi->dev, + "hdmi reg write error, reg:0x%x, val:0x%x, ret:%d\n", + offset, val, ret); + } +} + +static inline u8 hdmi_readb(struct dw_hdmi *hdmi, int offset) +{ + unsigned int val = 0; + + int ret = regmap_read(hdmi->regm, offset << hdmi->reg_shift, &val); + if (ret != 0) { + dev_err(hdmi->dev, + "hdmi reg read error, reg:0x%x, val:0x%x, ret:%d\n", + offset, val, ret); + } + + return val; +} + +static void handle_plugged_change(struct dw_hdmi *hdmi, bool plugged) +{ + if (hdmi->plugged_cb && hdmi->codec_dev) + hdmi->plugged_cb(hdmi->codec_dev, plugged); +} + +int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + bool plugged; + + mutex_lock(&hdmi->mutex); + hdmi->plugged_cb = fn; + hdmi->codec_dev = codec_dev; + plugged = hdmi->last_connector_result == connector_status_connected; + handle_plugged_change(hdmi, plugged); + mutex_unlock(&hdmi->mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_plugged_cb); + +static void hdmi_modb(struct dw_hdmi *hdmi, u8 data, u8 mask, unsigned reg) +{ + regmap_update_bits(hdmi->regm, reg << hdmi->reg_shift, mask, data); +} + +static void hdmi_mask_writeb(struct dw_hdmi *hdmi, u8 data, unsigned int reg, + u8 shift, u8 mask) +{ + hdmi_modb(hdmi, data << shift, mask, reg); +} + +static void repo_hpd_event(struct work_struct *p_work) +{ + struct dw_hdmi *hdmi = container_of(p_work, struct dw_hdmi, work.work); + u8 phy_stat = hdmi_readb(hdmi, HDMI_PHY_STAT0); + + mutex_lock(&hdmi->mutex); + if (!(phy_stat & HDMI_PHY_RX_SENSE)) + hdmi->rxsense = false; + if (phy_stat & HDMI_PHY_HPD) + hdmi->rxsense = true; + mutex_unlock(&hdmi->mutex); + + if (hdmi->bridge.dev) { + bool change; + + change = drm_helper_hpd_irq_event(hdmi->bridge.dev); + if (change) { +#ifdef CONFIG_CEC_NOTIFIER + cec_notifier_repo_cec_hpd(hdmi->cec_notifier, + hdmi->hpd_state, ktime_get()); +#endif + if (hdmi->hpd_state) { +#ifdef CONFIG_CEC_NOTIFIER + struct edid *edid; + edid = dw_hdmi_get_edid(hdmi, &hdmi->connector); + if (!edid) + return; + drm_connector_update_edid_property( + &hdmi->connector, edid); + cec_notifier_set_phys_addr_from_edid( + hdmi->cec_notifier, edid); + kfree(edid); +#endif + } + } + } + + msleep(150); + if (hdmi->hpd_state) { + if (hdmi->hdcp && hdmi->hdcp->hdcp2 && + hdmi->hdcp->hdcp2->enable && + (tv_hdmi_hdcp2_support(hdmi)) == 1) { + hdmi->hdcp->hdcp2->start(); + } else { + hdmi_tx_hdcp_config(hdmi, &hdmi->previous_mode); + } + } +} + +static bool check_hdmi_irq(struct dw_hdmi *hdmi, int intr_stat, int phy_int_pol) +{ + int msecs; + + /* To determine whether interrupt type is HPD */ + if (!(intr_stat & HDMI_IH_PHY_STAT0_HPD)) + return false; + + if (phy_int_pol & HDMI_PHY_HPD) { + dev_info(hdmi->dev, "dw hdmi plug in\n"); + msecs = 100; + hdmi->hpd_state = true; + } else { + dev_info(hdmi->dev, "dw hdmi plug out\n"); + msecs = 1000; + hdmi->hpd_state = false; + if (hdmi->hdcp && hdmi->hdcp->hdcp2 && + hdmi->hdcp->hdcp2->enable) { + hdmi->hdcp->hdcp2->stop(); + } + if (hdmi->hdcp && hdmi->hdcp->hdcp_stop) { + hdmi->hdcp->hdcp_stop(hdmi->hdcp); + } + } + mod_delayed_work(hdmi->workqueue, &hdmi->work, msecs_to_jiffies(msecs)); + + return true; +} + +static void init_hpd_work(struct dw_hdmi *hdmi) +{ + hdmi->workqueue = create_workqueue("hpd_queue"); + INIT_DELAYED_WORK(&hdmi->work, repo_hpd_event); +} + +static void dw_hdmi_i2c_set_divs(struct dw_hdmi *hdmi) +{ + unsigned long clk_rate_khz; + unsigned long low_ns, high_ns; + unsigned long div_low, div_high; + + /* Standard-mode */ + if (hdmi->i2c->scl_high_ns < 4000) + high_ns = 4708; + else + high_ns = hdmi->i2c->scl_high_ns; + + if (hdmi->i2c->scl_low_ns < 4700) + low_ns = 4916; + else + low_ns = hdmi->i2c->scl_low_ns; + + /* Adjust to avoid overflow */ + clk_rate_khz = DIV_ROUND_UP(clk_get_rate(hdmi->isfr_clk), 1000); + + div_low = (clk_rate_khz * low_ns) / 1000000; + if ((clk_rate_khz * low_ns) % 1000000) + div_low++; + + div_high = (clk_rate_khz * high_ns) / 1000000; + if ((clk_rate_khz * high_ns) % 1000000) + div_high++; + + /* Maximum divider supported by hw is 0xffff */ + if (div_low > 0xffff) + div_low = 0xffff; + + if (div_high > 0xffff) + div_high = 0xffff; + + hdmi_writeb(hdmi, div_high & 0xff, HDMI_I2CM_SS_SCL_HCNT_0_ADDR); + hdmi_writeb(hdmi, (div_high >> 8) & 0xff, HDMI_I2CM_SS_SCL_HCNT_1_ADDR); + hdmi_writeb(hdmi, div_low & 0xff, HDMI_I2CM_SS_SCL_LCNT_0_ADDR); + hdmi_writeb(hdmi, (div_low >> 8) & 0xff, HDMI_I2CM_SS_SCL_LCNT_1_ADDR); + if (!hdmi->eswin_plat) { + hdmi_writeb(hdmi, 0x13, HDMI_I2CM_SS_SCL_HCNT_0_ADDR); + hdmi_writeb(hdmi, 0x00, HDMI_I2CM_SS_SCL_HCNT_1_ADDR); + hdmi_writeb(hdmi, 0x16, HDMI_I2CM_SS_SCL_LCNT_0_ADDR); + hdmi_writeb(hdmi, 0x00, HDMI_I2CM_SS_SCL_LCNT_1_ADDR); + } +} + +static void dw_hdmi_i2c_init(struct dw_hdmi *hdmi) +{ + hdmi_writeb(hdmi, HDMI_PHY_I2CM_INT_ADDR_DONE_POL, + HDMI_PHY_I2CM_INT_ADDR); + + hdmi_writeb(hdmi, + HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL | + HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL, + HDMI_PHY_I2CM_CTLINT_ADDR); + + /* Software reset */ + hdmi_writeb(hdmi, 0x00, HDMI_I2CM_SOFTRSTZ); + + /* Set Standard Mode speed (determined to be 100KHz on iMX6) */ + hdmi_writeb(hdmi, 0x00, HDMI_I2CM_DIV); + + /* Set done, not acknowledged and arbitration interrupt polarities */ + hdmi_writeb(hdmi, HDMI_I2CM_INT_DONE_POL, HDMI_I2CM_INT); + hdmi_writeb(hdmi, HDMI_I2CM_CTLINT_NAC_POL | HDMI_I2CM_CTLINT_ARB_POL, + HDMI_I2CM_CTLINT); + + /* Clear DONE and ERROR interrupts */ + hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE, + HDMI_IH_I2CM_STAT0); + + /* Mute DONE and ERROR interrupts */ + hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE, + HDMI_IH_MUTE_I2CM_STAT0); + + /* set SDA high level holding time */ + hdmi_writeb(hdmi, 0x48, HDMI_I2CM_SDA_HOLD); + if (!hdmi->eswin_plat) { + hdmi_writeb(hdmi, 0x0f, HDMI_I2CM_SDA_HOLD); + } + dw_hdmi_i2c_set_divs(hdmi); +} + +static bool dw_hdmi_i2c_unwedge(struct dw_hdmi *hdmi) +{ + /* If no unwedge state then give up */ + if (!hdmi->unwedge_state) + return false; + + dev_info(hdmi->dev, "Attempting to unwedge stuck i2c bus\n"); + + /* + * This is a huge hack to workaround a problem where the dw_hdmi i2c + * bus could sometimes get wedged. Once wedged there doesn't appear + * to be any way to unwedge it (including the HDMI_I2CM_SOFTRSTZ) + * other than pulsing the SDA line. + * + * We appear to be able to pulse the SDA line (in the eyes of dw_hdmi) + * by: + * 1. Remux the pin as a GPIO output, driven low. + * 2. Wait a little while. 1 ms seems to work, but we'll do 10. + * 3. Immediately jump to remux the pin as dw_hdmi i2c again. + * + * At the moment of remuxing, the line will still be low due to its + * recent stint as an output, but then it will be pulled high by the + * (presumed) external pullup. dw_hdmi seems to see this as a rising + * edge and that seems to get it out of its jam. + * + * This wedging was only ever seen on one TV, and only on one of + * its HDMI ports. It happened when the TV was powered on while the + * device was plugged in. A scope trace shows the TV bringing both SDA + * and SCL low, then bringing them both back up at roughly the same + * time. Presumably this confuses dw_hdmi because it saw activity but + * no real STOP (maybe it thinks there's another master on the bus?). + * Giving it a clean rising edge of SDA while SCL is already high + * presumably makes dw_hdmi see a STOP which seems to bring dw_hdmi out + * of its stupor. + * + * Note that after coming back alive, transfers seem to immediately + * resume, so if we unwedge due to a timeout we should wait a little + * longer for our transfer to finish, since it might have just started + * now. + */ + pinctrl_select_state(hdmi->pinctrl, hdmi->unwedge_state); + msleep(10); + pinctrl_select_state(hdmi->pinctrl, hdmi->default_state); + + return true; +} + +static int dw_hdmi_i2c_wait(struct dw_hdmi *hdmi) +{ + struct dw_hdmi_i2c *i2c = hdmi->i2c; + int stat; + + stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); + if (!stat) { + /* If we can't unwedge, return timeout */ + if (!dw_hdmi_i2c_unwedge(hdmi)) + return -EAGAIN; + + /* We tried to unwedge; give it another chance */ + stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); + if (!stat) + return -EAGAIN; + } + + /* Check for error condition on the bus */ + if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR) + return -EIO; + + return 0; +} + +static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi, unsigned char *buf, + unsigned int length) +{ + struct dw_hdmi_i2c *i2c = hdmi->i2c; + int ret; + + if (!i2c->is_regaddr) { + dev_dbg(hdmi->dev, "set read register address to 0\n"); + i2c->slave_reg = 0x00; + i2c->is_regaddr = true; + } + + while (length--) { + reinit_completion(&i2c->cmp); + + hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS); + if (i2c->is_segment) + hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ_EXT, + HDMI_I2CM_OPERATION); + else + hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ, + HDMI_I2CM_OPERATION); + + ret = dw_hdmi_i2c_wait(hdmi); + if (ret) + return ret; + + /* Check for error condition on the bus */ + if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR) + return -EIO; + + *buf++ = hdmi_readb(hdmi, HDMI_I2CM_DATAI); + } + i2c->is_segment = false; + + return 0; +} + +static int dw_hdmi_i2c_write(struct dw_hdmi *hdmi, unsigned char *buf, + unsigned int length) +{ + struct dw_hdmi_i2c *i2c = hdmi->i2c; + int ret; + + if (!i2c->is_regaddr) { + /* Use the first write byte as register address */ + i2c->slave_reg = buf[0]; + length--; + buf++; + i2c->is_regaddr = true; + } + + while (length--) { + reinit_completion(&i2c->cmp); + + hdmi_writeb(hdmi, *buf++, HDMI_I2CM_DATAO); + hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS); + hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_WRITE, + HDMI_I2CM_OPERATION); + + ret = dw_hdmi_i2c_wait(hdmi); + if (ret) + return ret; + + /* Check for error condition on the bus */ + if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR) + return -EIO; + } + + return 0; +} + +static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) +{ + struct dw_hdmi *hdmi = i2c_get_adapdata(adap); + struct dw_hdmi_i2c *i2c = hdmi->i2c; + u8 addr = msgs[0].addr; + int i, ret = 0; + + if (addr == DDC_CI_ADDR) + /* + * The internal I2C controller does not support the multi-byte + * read and write operations needed for DDC/CI. + * TOFIX: Blacklist the DDC/CI address until we filter out + * unsupported I2C operations. + */ + return -EOPNOTSUPP; + + dev_dbg(hdmi->dev, "xfer: num: %d, addr: %#x\n", num, addr); + + for (i = 0; i < num; i++) { + if (msgs[i].len == 0) { + dev_dbg(hdmi->dev, + "unsupported transfer %d/%d, no data\n", i + 1, + num); + return -EOPNOTSUPP; + } + } + + mutex_lock(&i2c->lock); + + /* Unmute DONE and ERROR interrupts */ + hdmi_writeb(hdmi, 0x00, HDMI_IH_MUTE_I2CM_STAT0); + + /* Set slave device address taken from the first I2C message */ + if (addr == DDC_SEGMENT_ADDR && msgs[0].len == 1) + addr = DDC_ADDR; + hdmi_writeb(hdmi, addr, HDMI_I2CM_SLAVE); + + /* Set slave device register address on transfer */ + i2c->is_regaddr = false; + + /* Set segment pointer for I2C extended read mode operation */ + i2c->is_segment = false; + + for (i = 0; i < num; i++) { + dev_dbg(hdmi->dev, "xfer: num: %d/%d, len: %d, flags: %#x\n", + i + 1, num, msgs[i].len, msgs[i].flags); + if (msgs[i].addr == DDC_SEGMENT_ADDR && msgs[i].len == 1) { + i2c->is_segment = true; + hdmi_writeb(hdmi, DDC_SEGMENT_ADDR, HDMI_I2CM_SEGADDR); + hdmi_writeb(hdmi, *msgs[i].buf, HDMI_I2CM_SEGPTR); + } else { + if (msgs[i].flags & I2C_M_RD) + ret = dw_hdmi_i2c_read(hdmi, msgs[i].buf, + msgs[i].len); + else + ret = dw_hdmi_i2c_write(hdmi, msgs[i].buf, + msgs[i].len); + } + if (ret < 0) { + dev_info(hdmi->dev, "i2c transfer fail\n"); + break; + } + } + + if (!ret) + ret = num; + + /* Mute DONE and ERROR interrupts */ + hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE, + HDMI_IH_MUTE_I2CM_STAT0); + + mutex_unlock(&i2c->lock); + + return ret; +} + +static u32 dw_hdmi_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm dw_hdmi_algorithm = { + .master_xfer = dw_hdmi_i2c_xfer, + .functionality = dw_hdmi_i2c_func, +}; + +u8 tv_hdmi_hdcp2_support(struct dw_hdmi *hdmi) +{ + int ret = 0; + int try_times = 10; + unsigned char start = 0x50; + u8 buf; + int i; + + struct i2c_msg msgs[] = { { + .addr = 0x3a, + .flags = 0, + .len = 1, + .buf = &start, + }, + { + .addr = 0x3a, + .flags = I2C_M_RD, + .len = 1, + .buf = &buf, + } }; + + for (i = 0; i < try_times; i++) { + //if (hdmi->bridge_is_on) + ret = dw_hdmi_i2c_xfer(hdmi->ddc, msgs, 2); + + printk("ret = %d; buf = %d\n", ret, buf); + if (ret == 2 && (buf & 0x4)) + return ((buf & 0x4) >> 2); + udelay(100); + } + + return -1; +} +EXPORT_SYMBOL_GPL(tv_hdmi_hdcp2_support); + +static struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi *hdmi) +{ + struct i2c_adapter *adap; + struct dw_hdmi_i2c *i2c; + int ret; + + i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return ERR_PTR(-ENOMEM); + + mutex_init(&i2c->lock); + init_completion(&i2c->cmp); + + adap = &i2c->adap; + adap->class = I2C_CLASS_DDC; + adap->owner = THIS_MODULE; + adap->dev.parent = hdmi->dev; + adap->dev.of_node = hdmi->dev->of_node; + adap->algo = &dw_hdmi_algorithm; + strlcpy(adap->name, "DesignWare HDMI", sizeof(adap->name)); + i2c_set_adapdata(adap, hdmi); + + ret = i2c_add_adapter(adap); + if (ret) { + dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name); + devm_kfree(hdmi->dev, i2c); + return ERR_PTR(ret); + } + + hdmi->i2c = i2c; + + dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name); + + return adap; +} + +static void hdmi_set_cts_n(struct dw_hdmi *hdmi, unsigned int cts, + unsigned int n) +{ + /* Must be set/cleared first */ + hdmi_modb(hdmi, 0, HDMI_AUD_CTS3_CTS_MANUAL, HDMI_AUD_CTS3); + + /* nshift factor = 0 */ + hdmi_modb(hdmi, 0, HDMI_AUD_CTS3_N_SHIFT_MASK, HDMI_AUD_CTS3); + + /* Use automatic CTS generation mode when CTS is not set */ + if (cts) + hdmi_writeb(hdmi, + ((cts >> 16) & HDMI_AUD_CTS3_AUDCTS19_16_MASK) | + HDMI_AUD_CTS3_CTS_MANUAL, + HDMI_AUD_CTS3); + else + hdmi_writeb(hdmi, 0, HDMI_AUD_CTS3); + hdmi_writeb(hdmi, (cts >> 8) & 0xff, HDMI_AUD_CTS2); + hdmi_writeb(hdmi, cts & 0xff, HDMI_AUD_CTS1); + + hdmi_writeb(hdmi, (n >> 16) & 0x0f, HDMI_AUD_N3); + hdmi_writeb(hdmi, (n >> 8) & 0xff, HDMI_AUD_N2); + hdmi_writeb(hdmi, n & 0xff, HDMI_AUD_N1); +} + +static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk) +{ + unsigned int n = (128 * freq) / 1000; + unsigned int mult = 1; + + while (freq > 48000) { + mult *= 2; + freq /= 2; + } + + switch (freq) { + case 32000: + if (pixel_clk == 25175000) + n = 4576; + else if (pixel_clk == 27027000) + n = 4096; + else if (pixel_clk == 74176000 || pixel_clk == 148352000) + n = 11648; + else + n = 4096; + n *= mult; + break; + + case 44100: + if (pixel_clk == 25175000) + n = 7007; + else if (pixel_clk == 74176000) + n = 17836; + else if (pixel_clk == 148352000) + n = 8918; + else + n = 6272; + n *= mult; + break; + + case 48000: + if (pixel_clk == 25175000) + n = 6864; + else if (pixel_clk == 27027000) + n = 6144; + else if (pixel_clk == 74176000) + n = 11648; + else if (pixel_clk == 148352000) + n = 5824; + else + n = 6144; + n *= mult; + break; + + default: + break; + } + + return n; +} + +/* + * When transmitting IEC60958 linear PCM audio, these registers allow to + * configure the channel status information of all the channel status + * bits in the IEC60958 frame. For the moment this configuration is only + * used when the I2S audio interface, General Purpose Audio (GPA), + * or AHB audio DMA (AHBAUDDMA) interface is active + * (for S/PDIF interface this information comes from the stream). + */ +void dw_hdmi_set_channel_status(struct dw_hdmi *hdmi, u8 *channel_status) +{ + /* + * Set channel status register for frequency and word length. + * Use default values for other registers. + */ + hdmi_writeb(hdmi, channel_status[3], HDMI_FC_AUDSCHNLS7); + hdmi_writeb(hdmi, channel_status[4], HDMI_FC_AUDSCHNLS8); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_channel_status); + +static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi, + unsigned long pixel_clk, + unsigned int sample_rate) +{ + unsigned long ftdms = pixel_clk; + unsigned int n, cts; + u8 config3; + u64 tmp; + + n = hdmi_compute_n(sample_rate, pixel_clk); + + config3 = hdmi_readb(hdmi, HDMI_CONFIG3_ID); + + /* Only compute CTS when using internal AHB audio */ + if (config3 & HDMI_CONFIG3_AHBAUDDMA) { + /* + * Compute the CTS value from the N value. Note that CTS and N + * can be up to 20 bits in total, so we need 64-bit math. Also + * note that our TDMS clock is not fully accurate; it is + * accurate to kHz. This can introduce an unnecessary remainder + * in the calculation below, so we don't try to warn about that. + */ + tmp = (u64)ftdms * n; + do_div(tmp, 128 * sample_rate); + cts = tmp; + + dev_dbg(hdmi->dev, + "%s: fs=%uHz ftdms=%lu.%03luMHz N=%d cts=%d\n", + __func__, sample_rate, ftdms / 1000000, + (ftdms / 1000) % 1000, n, cts); + } else { + cts = 0; + } + + spin_lock_irq(&hdmi->audio_lock); + hdmi->audio_n = n; + hdmi->audio_cts = cts; + hdmi_set_cts_n(hdmi, cts, hdmi->audio_enable ? n : 0); + spin_unlock_irq(&hdmi->audio_lock); +} + +static void hdmi_init_clk_regenerator(struct dw_hdmi *hdmi) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi_set_clk_regenerator(hdmi, 74250000, hdmi->sample_rate); + mutex_unlock(&hdmi->audio_mutex); +} + +static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi *hdmi) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock, + hdmi->sample_rate); + mutex_unlock(&hdmi->audio_mutex); +} + +void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi->sample_rate = rate; + hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock, + hdmi->sample_rate); + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_rate); + +void dw_hdmi_set_channel_count(struct dw_hdmi *hdmi, unsigned int cnt) +{ + u8 layout; + + mutex_lock(&hdmi->audio_mutex); + + /* + * For >2 channel PCM audio, we need to select layout 1 + * and set an appropriate channel map. + */ + if (cnt > 2) + layout = HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT1; + else + layout = HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT0; + + hdmi_modb(hdmi, layout, HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_MASK, + HDMI_FC_AUDSCONF); + + /* Set the audio infoframes channel count */ + hdmi_modb(hdmi, (cnt - 1) << HDMI_FC_AUDICONF0_CC_OFFSET, + HDMI_FC_AUDICONF0_CC_MASK, HDMI_FC_AUDICONF0); + + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_channel_count); + +void dw_hdmi_set_channel_allocation(struct dw_hdmi *hdmi, unsigned int ca) +{ + mutex_lock(&hdmi->audio_mutex); + + hdmi_writeb(hdmi, ca, HDMI_FC_AUDICONF2); + + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_channel_allocation); + +static void hdmi_enable_audio_clk(struct dw_hdmi *hdmi, bool enable) +{ + if (enable) + hdmi->mc_clkdis &= ~HDMI_MC_CLKDIS_AUDCLK_DISABLE; + else + hdmi->mc_clkdis |= HDMI_MC_CLKDIS_AUDCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); +} + +static u8 *hdmi_audio_get_eld(struct dw_hdmi *hdmi) +{ + if (!hdmi->curr_conn) + return NULL; + + return hdmi->curr_conn->eld; +} + +static void dw_hdmi_ahb_audio_enable(struct dw_hdmi *hdmi) +{ + hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); +} + +static void dw_hdmi_ahb_audio_disable(struct dw_hdmi *hdmi) +{ + hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0); +} + +static void dw_hdmi_i2s_audio_enable(struct dw_hdmi *hdmi) +{ + hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); + hdmi_enable_audio_clk(hdmi, true); +} + +static void dw_hdmi_i2s_audio_disable(struct dw_hdmi *hdmi) +{ + hdmi_enable_audio_clk(hdmi, false); +} + +void dw_hdmi_audio_enable(struct dw_hdmi *hdmi) +{ + unsigned long flags; + + spin_lock_irqsave(&hdmi->audio_lock, flags); + hdmi->audio_enable = true; + if (hdmi->enable_audio) + hdmi->enable_audio(hdmi); + spin_unlock_irqrestore(&hdmi->audio_lock, flags); +} +EXPORT_SYMBOL_GPL(dw_hdmi_audio_enable); + +void dw_hdmi_audio_disable(struct dw_hdmi *hdmi) +{ + unsigned long flags; + + spin_lock_irqsave(&hdmi->audio_lock, flags); + hdmi->audio_enable = false; + if (hdmi->disable_audio) + hdmi->disable_audio(hdmi); + spin_unlock_irqrestore(&hdmi->audio_lock, flags); +} +EXPORT_SYMBOL_GPL(dw_hdmi_audio_disable); + +static bool hdmi_bus_fmt_is_rgb(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_RGB161616_1X48: + return true; + + default: + return false; + } +} + +static bool hdmi_bus_fmt_is_yuv444(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_YUV16_1X48: + return true; + + default: + return false; + } +} + +static bool hdmi_bus_fmt_is_yuv422(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_UYVY12_1X24: + return true; + + default: + return false; + } +} + +static bool hdmi_bus_fmt_is_yuv420(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + return true; + + default: + return false; + } +} + +static int hdmi_bus_fmt_color_depth(unsigned int bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + return 8; + + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + return 10; + + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_UYVY12_1X24: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + return 12; + + case MEDIA_BUS_FMT_RGB161616_1X48: + case MEDIA_BUS_FMT_YUV16_1X48: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + return 16; + + default: + return 0; + } +} + +/* + * this submodule is responsible for the video data synchronization. + * for example, for RGB 4:4:4 input, the data map is defined as + * pin{47~40} <==> R[7:0] + * pin{31~24} <==> G[7:0] + * pin{15~8} <==> B[7:0] + */ +static void hdmi_video_sample(struct dw_hdmi *hdmi) +{ + int color_format = 0; + u8 val; + + switch (hdmi->hdmi_data.enc_in_bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + color_format = 0x01; + break; + case MEDIA_BUS_FMT_RGB101010_1X30: + color_format = 0x03; + break; + case MEDIA_BUS_FMT_RGB121212_1X36: + color_format = 0x05; + break; + case MEDIA_BUS_FMT_RGB161616_1X48: + color_format = 0x07; + break; + + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + color_format = 0x09; + break; + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + color_format = 0x0B; + break; + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + color_format = 0x0D; + break; + case MEDIA_BUS_FMT_YUV16_1X48: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + color_format = 0x0F; + break; + + case MEDIA_BUS_FMT_UYVY8_1X16: + color_format = 0x16; + break; + case MEDIA_BUS_FMT_UYVY10_1X20: + color_format = 0x14; + break; + case MEDIA_BUS_FMT_UYVY12_1X24: + color_format = 0x12; + break; + + default: + return; + } + + val = HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE | + ((color_format << HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET) & + HDMI_TX_INVID0_VIDEO_MAPPING_MASK); + hdmi_writeb(hdmi, val, HDMI_TX_INVID0); + + /* Enable TX stuffing: When DE is inactive, fix the output data to 0 */ + val = HDMI_TX_INSTUFFING_BDBDATA_STUFFING_ENABLE | + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_ENABLE | + HDMI_TX_INSTUFFING_GYDATA_STUFFING_ENABLE; + hdmi_writeb(hdmi, val, HDMI_TX_INSTUFFING); + hdmi_writeb(hdmi, 0x0, HDMI_TX_GYDATA0); + hdmi_writeb(hdmi, 0x0, HDMI_TX_GYDATA1); + hdmi_writeb(hdmi, 0x0, HDMI_TX_RCRDATA0); + hdmi_writeb(hdmi, 0x0, HDMI_TX_RCRDATA1); + hdmi_writeb(hdmi, 0x0, HDMI_TX_BCBDATA0); + hdmi_writeb(hdmi, 0x0, HDMI_TX_BCBDATA1); +} + +static int is_color_space_conversion(struct dw_hdmi *hdmi) +{ + struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data; + bool is_input_rgb, is_output_rgb; + + is_input_rgb = hdmi_bus_fmt_is_rgb(hdmi_data->enc_in_bus_format); + is_output_rgb = hdmi_bus_fmt_is_rgb(hdmi_data->enc_out_bus_format); + + return (is_input_rgb != is_output_rgb) || + (is_input_rgb && is_output_rgb && hdmi_data->rgb_limited_range); +} + +static int is_color_space_decimation(struct dw_hdmi *hdmi) +{ + if (!hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) + return 0; + + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_in_bus_format) || + hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_in_bus_format)) + return 1; + + return 0; +} + +static int is_color_space_interpolation(struct dw_hdmi *hdmi) +{ + if (!hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_in_bus_format)) + return 0; + + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format) || + hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) + return 1; + + return 0; +} + +static bool is_csc_needed(struct dw_hdmi *hdmi) +{ + return is_color_space_conversion(hdmi) || + is_color_space_decimation(hdmi) || + is_color_space_interpolation(hdmi); +} + +static void dw_hdmi_update_csc_coeffs(struct dw_hdmi *hdmi) +{ + const u16(*csc_coeff)[3][4] = &csc_coeff_default; + bool is_input_rgb, is_output_rgb, is_output_yuv; + unsigned i; + u32 csc_scale = 1; + unsigned int depth = + hdmi_bus_fmt_color_depth(hdmi->hdmi_data.enc_out_bus_format); + + is_input_rgb = hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_in_bus_format); + is_output_rgb = hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format); + is_output_yuv = + hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format) || + hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format); + + if (!is_input_rgb && is_output_rgb) { + if (hdmi->hdmi_data.enc_out_encoding == V4L2_YCBCR_ENC_601) + csc_coeff = &csc_coeff_rgb_out_eitu601; + else + csc_coeff = &csc_coeff_rgb_out_eitu709; + } else if (is_input_rgb && !is_output_rgb) { + if (hdmi->hdmi_data.enc_out_encoding == V4L2_YCBCR_ENC_601) { + if (is_output_yuv && depth == 8) + csc_coeff = &csc_coeff_rgb_to_yuv_8bit_eitu601; + else if (is_output_yuv && depth == 10) + csc_coeff = &csc_coeff_rgb_to_yuv_10bit_eitu601; + else + csc_coeff = &csc_coeff_rgb_in_eitu601; + } else { + if (is_output_yuv && depth == 8) + csc_coeff = &csc_coeff_rgb_to_yuv_8bit_eitu709; + else if (is_output_yuv && depth == 10) + csc_coeff = &csc_coeff_rgb_to_yuv_10bit_eitu709; + else + csc_coeff = &csc_coeff_rgb_in_eitu709; + } + csc_scale = 0; + } else if (is_input_rgb && is_output_rgb && + hdmi->hdmi_data.rgb_limited_range) { + csc_coeff = &csc_coeff_rgb_full_to_rgb_limited; + } + + /* The CSC registers are sequential, alternating MSB then LSB */ + for (i = 0; i < ARRAY_SIZE(csc_coeff_default[0]); i++) { + u16 coeff_a = (*csc_coeff)[0][i]; + u16 coeff_b = (*csc_coeff)[1][i]; + u16 coeff_c = (*csc_coeff)[2][i]; + + hdmi_writeb(hdmi, coeff_a & 0xff, HDMI_CSC_COEF_A1_LSB + i * 2); + hdmi_writeb(hdmi, coeff_a >> 8, HDMI_CSC_COEF_A1_MSB + i * 2); + hdmi_writeb(hdmi, coeff_b & 0xff, HDMI_CSC_COEF_B1_LSB + i * 2); + hdmi_writeb(hdmi, coeff_b >> 8, HDMI_CSC_COEF_B1_MSB + i * 2); + hdmi_writeb(hdmi, coeff_c & 0xff, HDMI_CSC_COEF_C1_LSB + i * 2); + hdmi_writeb(hdmi, coeff_c >> 8, HDMI_CSC_COEF_C1_MSB + i * 2); + } + + hdmi_modb(hdmi, csc_scale, HDMI_CSC_SCALE_CSCSCALE_MASK, + HDMI_CSC_SCALE); +} + +static void hdmi_video_csc(struct dw_hdmi *hdmi) +{ + int color_depth = 0; + int interpolation = HDMI_CSC_CFG_INTMODE_DISABLE; + int decimation = 0; + + /* YCC422 interpolation to 444 mode */ + if (is_color_space_interpolation(hdmi)) + interpolation = HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA1; + else if (is_color_space_decimation(hdmi)) + decimation = HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA3; + + switch (hdmi_bus_fmt_color_depth(hdmi->hdmi_data.enc_out_bus_format)) { + case 8: + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_24BPP; + break; + case 10: + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_30BPP; + break; + case 12: + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_36BPP; + break; + case 16: + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_48BPP; + break; + + default: + return; + } + + /* Configure the CSC registers */ + hdmi_writeb(hdmi, interpolation | decimation, HDMI_CSC_CFG); + hdmi_modb(hdmi, color_depth, HDMI_CSC_SCALE_CSC_COLORDE_PTH_MASK, + HDMI_CSC_SCALE); + + dw_hdmi_update_csc_coeffs(hdmi); +} + +/* + * HDMI video packetizer is used to packetize the data. + * for example, if input is YCC422 mode or repeater is used, + * data should be repacked this module can be bypassed. + */ +static void hdmi_video_packetize(struct dw_hdmi *hdmi) +{ + unsigned int color_depth = 0; + unsigned int remap_size = HDMI_VP_REMAP_YCC422_16bit; + unsigned int output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_PP; + struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data; + u8 val, vp_conf; + + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format) || + hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format) || + hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) { + switch (hdmi_bus_fmt_color_depth( + hdmi->hdmi_data.enc_out_bus_format)) { + case 8: + color_depth = 4; + output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS; + break; + case 10: + color_depth = 5; + break; + case 12: + color_depth = 6; + break; + case 16: + color_depth = 7; + break; + default: + output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS; + } + } else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) { + switch (hdmi_bus_fmt_color_depth( + hdmi->hdmi_data.enc_out_bus_format)) { + case 0: + case 8: + remap_size = HDMI_VP_REMAP_YCC422_16bit; + break; + case 10: + remap_size = HDMI_VP_REMAP_YCC422_20bit; + break; + case 12: + remap_size = HDMI_VP_REMAP_YCC422_24bit; + break; + + default: + return; + } + output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422; + } else { + return; + } + + /* set the packetizer registers */ + val = ((color_depth << HDMI_VP_PR_CD_COLOR_DEPTH_OFFSET) & + HDMI_VP_PR_CD_COLOR_DEPTH_MASK) | + ((hdmi_data->pix_repet_factor + << HDMI_VP_PR_CD_DESIRED_PR_FACTOR_OFFSET) & + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK); + hdmi_writeb(hdmi, val, HDMI_VP_PR_CD); + + hdmi_modb(hdmi, HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE, + HDMI_VP_STUFF_PR_STUFFING_MASK, HDMI_VP_STUFF); + + /* Data from pixel repeater block */ + if (hdmi_data->pix_repet_factor > 1) { + vp_conf = HDMI_VP_CONF_PR_EN_ENABLE | + HDMI_VP_CONF_BYPASS_SELECT_PIX_REPEATER; + } else { /* data from packetizer block */ + vp_conf = HDMI_VP_CONF_PR_EN_DISABLE | + HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER; + } + + hdmi_modb(hdmi, vp_conf, + HDMI_VP_CONF_PR_EN_MASK | HDMI_VP_CONF_BYPASS_SELECT_MASK, + HDMI_VP_CONF); + + hdmi_modb(hdmi, 1 << HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET, + HDMI_VP_STUFF_IDEFAULT_PHASE_MASK, HDMI_VP_STUFF); + + hdmi_writeb(hdmi, remap_size, HDMI_VP_REMAP); + + if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_PP) { + vp_conf = HDMI_VP_CONF_BYPASS_EN_DISABLE | + HDMI_VP_CONF_PP_EN_ENABLE | + HDMI_VP_CONF_YCC422_EN_DISABLE; + } else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422) { + vp_conf = HDMI_VP_CONF_BYPASS_EN_DISABLE | + HDMI_VP_CONF_PP_EN_DISABLE | + HDMI_VP_CONF_YCC422_EN_ENABLE; + } else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS) { + vp_conf = HDMI_VP_CONF_BYPASS_EN_ENABLE | + HDMI_VP_CONF_PP_EN_DISABLE | + HDMI_VP_CONF_YCC422_EN_DISABLE; + } else { + return; + } + + hdmi_modb(hdmi, vp_conf, + HDMI_VP_CONF_BYPASS_EN_MASK | HDMI_VP_CONF_PP_EN_ENMASK | + HDMI_VP_CONF_YCC422_EN_MASK, + HDMI_VP_CONF); + + hdmi_modb(hdmi, + HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE | + HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE, + HDMI_VP_STUFF_PP_STUFFING_MASK | + HDMI_VP_STUFF_YCC422_STUFFING_MASK, + HDMI_VP_STUFF); + + hdmi_modb(hdmi, output_select, HDMI_VP_CONF_OUTPUT_SELECTOR_MASK, + HDMI_VP_CONF); +} + +/* ----------------------------------------------------------------------------- + * Synopsys PHY Handling + */ + +static inline void hdmi_phy_test_clear(struct dw_hdmi *hdmi, unsigned char bit) +{ + hdmi_modb(hdmi, bit << HDMI_PHY_TST0_TSTCLR_OFFSET, + HDMI_PHY_TST0_TSTCLR_MASK, HDMI_PHY_TST0); +} + +static inline void hdmi_phy_test_enable(struct dw_hdmi *hdmi, unsigned char bit) +{ + hdmi_modb(hdmi, bit << HDMI_PHY_TST0_TSTEN_OFFSET, + HDMI_PHY_TST0_TSTEN_MASK, HDMI_PHY_TST0); +} + +static inline void hdmi_phy_test_clock(struct dw_hdmi *hdmi, unsigned char bit) +{ + hdmi_modb(hdmi, bit << HDMI_PHY_TST0_TSTCLK_OFFSET, + HDMI_PHY_TST0_TSTCLK_MASK, HDMI_PHY_TST0); +} + +static inline void hdmi_phy_test_din(struct dw_hdmi *hdmi, unsigned char bit) +{ + hdmi_writeb(hdmi, bit, HDMI_PHY_TST1); +} + +static inline void hdmi_phy_test_dout(struct dw_hdmi *hdmi, unsigned char bit) +{ + hdmi_writeb(hdmi, bit, HDMI_PHY_TST2); +} + +static bool hdmi_phy_wait_i2c_done(struct dw_hdmi *hdmi, int msec) +{ + u32 val; + + while ((val = hdmi_readb(hdmi, HDMI_IH_I2CMPHY_STAT0) & 0x3) == 0) { + if (msec-- == 0) + return false; + udelay(1000); + } + hdmi_writeb(hdmi, val, HDMI_IH_I2CMPHY_STAT0); + + return true; +} + +void dw_hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data, + unsigned char addr) +{ + hdmi_writeb(hdmi, 0xFF, HDMI_IH_I2CMPHY_STAT0); + hdmi_writeb(hdmi, addr, HDMI_PHY_I2CM_ADDRESS_ADDR); + hdmi_writeb(hdmi, (unsigned char)(data >> 8), + HDMI_PHY_I2CM_DATAO_1_ADDR); + hdmi_writeb(hdmi, (unsigned char)(data >> 0), + HDMI_PHY_I2CM_DATAO_0_ADDR); + hdmi_writeb(hdmi, HDMI_PHY_I2CM_OPERATION_ADDR_WRITE, + HDMI_PHY_I2CM_OPERATION_ADDR); + hdmi_phy_wait_i2c_done(hdmi, 1000); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_i2c_write); + +static int hdmi_phy_i2c_read(struct dw_hdmi *hdmi, unsigned char addr) +{ + int val; + + hdmi_writeb(hdmi, 0xFF, HDMI_IH_I2CMPHY_STAT0); + hdmi_writeb(hdmi, addr, HDMI_PHY_I2CM_ADDRESS_ADDR); + hdmi_writeb(hdmi, 0, HDMI_PHY_I2CM_DATAI_1_ADDR); + hdmi_writeb(hdmi, 0, HDMI_PHY_I2CM_DATAI_0_ADDR); + hdmi_writeb(hdmi, HDMI_PHY_I2CM_OPERATION_ADDR_READ, + HDMI_PHY_I2CM_OPERATION_ADDR); + hdmi_phy_wait_i2c_done(hdmi, 1000); + val = hdmi_readb(hdmi, HDMI_PHY_I2CM_DATAI_1_ADDR); + val = (val & 0xff) << 8; + val += hdmi_readb(hdmi, HDMI_PHY_I2CM_DATAI_0_ADDR) & 0xff; + return val; +} + +/* Filter out invalid setups to avoid configuring SCDC and scrambling */ +static bool dw_hdmi_support_scdc(struct dw_hdmi *hdmi, + const struct drm_display_info *display) +{ + /* Completely disable SCDC support for older controllers */ + if (hdmi->version < 0x200a) + return false; + + /* Disable if no DDC bus */ + if (!hdmi->ddc) + return false; + + /* Disable if SCDC is not supported, or if an HF-VSDB block is absent */ + if (!display->hdmi.scdc.supported || + !display->hdmi.scdc.scrambling.supported) + return false; + + /* + * Disable if display only support low TMDS rates and scrambling + * for low rates is not supported either + */ + if (!display->hdmi.scdc.scrambling.low_rates && + display->max_tmds_clock <= 340000) + return false; + + return true; +} + +/* + * HDMI2.0 Specifies the following procedure for High TMDS Bit Rates: + * - The Source shall suspend transmission of the TMDS clock and data + * - The Source shall write to the TMDS_Bit_Clock_Ratio bit to change it + * from a 0 to a 1 or from a 1 to a 0 + * - The Source shall allow a minimum of 1 ms and a maximum of 100 ms from + * the time the TMDS_Bit_Clock_Ratio bit is written until resuming + * transmission of TMDS clock and data + * + * To respect the 100ms maximum delay, the dw_hdmi_set_high_tmds_clock_ratio() + * helper should called right before enabling the TMDS Clock and Data in + * the PHY configuration callback. + */ +void dw_hdmi_set_high_tmds_clock_ratio(struct dw_hdmi *hdmi, + const struct drm_display_info *display) +{ + unsigned long mtmdsclock = hdmi->hdmi_data.video_mode.mtmdsclock; + + /* Control for TMDS Bit Period/TMDS Clock-Period Ratio */ + if (dw_hdmi_support_scdc(hdmi, display)) { + if (mtmdsclock > HDMI14_MAX_TMDSCLK) + drm_scdc_set_high_tmds_clock_ratio(hdmi->ddc, 1); + else + drm_scdc_set_high_tmds_clock_ratio(hdmi->ddc, 0); + } +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_high_tmds_clock_ratio); + +static void dw_hdmi_phy_enable_powerdown(struct dw_hdmi *hdmi, bool enable) +{ + hdmi_mask_writeb(hdmi, !enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_PDZ_OFFSET, HDMI_PHY_CONF0_PDZ_MASK); +} + +static void dw_hdmi_phy_enable_tmds(struct dw_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_ENTMDS_OFFSET, + HDMI_PHY_CONF0_ENTMDS_MASK); +} + +static void dw_hdmi_phy_enable_svsret(struct dw_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_SVSRET_OFFSET, + HDMI_PHY_CONF0_SVSRET_MASK); +} + +void dw_hdmi_phy_gen2_pddq(struct dw_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET, + HDMI_PHY_CONF0_GEN2_PDDQ_MASK); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen2_pddq); + +void dw_hdmi_phy_gen2_txpwron(struct dw_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET, + HDMI_PHY_CONF0_GEN2_TXPWRON_MASK); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen2_txpwron); + +static void dw_hdmi_phy_sel_data_en_pol(struct dw_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_SELDATAENPOL_OFFSET, + HDMI_PHY_CONF0_SELDATAENPOL_MASK); +} + +static void dw_hdmi_phy_sel_interface_control(struct dw_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_SELDIPIF_OFFSET, + HDMI_PHY_CONF0_SELDIPIF_MASK); +} + +void dw_hdmi_phy_reset(struct dw_hdmi *hdmi) +{ + /* PHY reset. The reset signal is active high on Gen2 PHYs. */ + hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ); + hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_reset); + +void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address) +{ + hdmi_phy_test_clear(hdmi, 1); + hdmi_writeb(hdmi, address, HDMI_PHY_I2CM_SLAVE_ADDR); + hdmi_phy_test_clear(hdmi, 0); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_i2c_set_addr); + +static void dw_hdmi_phy_power_off(struct dw_hdmi *hdmi) +{ + const struct dw_hdmi_phy_data *phy = hdmi->phy.data; + unsigned int i; + u16 val; + + if (phy->gen == 1) { + dw_hdmi_phy_enable_tmds(hdmi, 0); + dw_hdmi_phy_enable_powerdown(hdmi, true); + return; + } + + dw_hdmi_phy_gen2_txpwron(hdmi, 0); + + /* + * Wait for TX_PHY_LOCK to be deasserted to indicate that the PHY went + * to low power mode. + */ + for (i = 0; i < 5; ++i) { + val = hdmi_readb(hdmi, HDMI_PHY_STAT0); + if (!(val & HDMI_PHY_TX_PHY_LOCK)) + break; + + usleep_range(1000, 2000); + } + + if (val & HDMI_PHY_TX_PHY_LOCK) + dev_warn(hdmi->dev, "PHY failed to power down\n"); + else + dev_dbg(hdmi->dev, "PHY powered down in %u iterations\n", i); + + dw_hdmi_phy_gen2_pddq(hdmi, 1); +} + +static int dw_hdmi_phy_power_on(struct dw_hdmi *hdmi) +{ + const struct dw_hdmi_phy_data *phy = hdmi->phy.data; + unsigned int i; + u8 val; + + if (phy->gen == 1) { + dw_hdmi_phy_enable_powerdown(hdmi, false); + + /* Toggle TMDS enable. */ + dw_hdmi_phy_enable_tmds(hdmi, 0); + dw_hdmi_phy_enable_tmds(hdmi, 1); + return 0; + } + + dw_hdmi_phy_gen2_pddq(hdmi, 0); + dw_hdmi_phy_gen2_txpwron(hdmi, 1); + /* Wait for PHY PLL lock */ + for (i = 0; i < 5; ++i) { + val = hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK; + if (val) + break; + + usleep_range(1000, 2000); + } + + if (!val) { + dev_err(hdmi->dev, "PHY PLL failed to lock\n"); + return -ETIMEDOUT; + } + + dev_dbg(hdmi->dev, "PHY PLL locked %u iterations\n", i); + + return 0; +} + +/* + * PHY configuration function for the DWC HDMI 3D TX PHY. Based on the available + * information the DWC MHL PHY has the same register layout and is thus also + * supported by this function. + */ +static int +hdmi_phy_configure_dwc_hdmi_3d_tx(struct dw_hdmi *hdmi, + const struct dw_hdmi_plat_data *pdata, + unsigned long mpixelclock) +{ + const struct dw_hdmi_mpll_config *mpll_config = pdata->mpll_cfg; + const struct dw_hdmi_curr_ctrl *curr_ctrl = pdata->cur_ctr; + const struct dw_hdmi_phy_config *phy_config = pdata->phy_config; + + /* TOFIX Will need 420 specific PHY configuration tables */ + unsigned int depth = + hdmi_bus_fmt_color_depth(hdmi->hdmi_data.enc_out_bus_format); + + /* PLL/MPLL Cfg - always match on final entry */ + for (; mpll_config->mpixelclock != ~0UL; mpll_config++) + if (mpixelclock <= mpll_config->mpixelclock) + break; + + for (; curr_ctrl->mpixelclock != ~0UL; curr_ctrl++) + if (mpixelclock <= curr_ctrl->mpixelclock) + break; + + for (; phy_config->mpixelclock != ~0UL; phy_config++) + if (mpixelclock <= phy_config->mpixelclock) + break; + + if (mpll_config->mpixelclock == ~0UL || + curr_ctrl->mpixelclock == ~0UL || phy_config->mpixelclock == ~0UL) + return -EINVAL; + + if (!hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) + depth = fls(depth - 8); + else + depth = 0; + if (depth) + depth--; + + dw_hdmi_phy_i2c_write(hdmi, mpll_config->res[depth].cpce, + HDMI_3D_TX_PHY_CPCE_CTRL); + dw_hdmi_phy_i2c_write(hdmi, mpll_config->res[depth].gmp, + HDMI_3D_TX_PHY_DRVANACTRL); + dw_hdmi_phy_i2c_write(hdmi, curr_ctrl->curr[depth], + HDMI_3D_TX_PHY_CURRCTRL); + + dw_hdmi_phy_i2c_write(hdmi, 0, HDMI_3D_TX_PHY_PLLPHBYCTRL); + dw_hdmi_phy_i2c_write(hdmi, HDMI_3D_TX_PHY_MSM_CTRL_CKO_SEL_FB_CLK, + HDMI_3D_TX_PHY_MSM_CTRL); + + dw_hdmi_phy_i2c_write(hdmi, phy_config->term, HDMI_3D_TX_PHY_TXTERM); + dw_hdmi_phy_i2c_write(hdmi, phy_config->sym_ctr, + HDMI_3D_TX_PHY_CKSYMTXCTRL); + dw_hdmi_phy_i2c_write(hdmi, phy_config->vlev_ctr, + HDMI_3D_TX_PHY_VLEVCTRL); + + /* Override and disable clock termination. */ + dw_hdmi_phy_i2c_write(hdmi, HDMI_3D_TX_PHY_CKCALCTRL_OVERRIDE, + HDMI_3D_TX_PHY_CKCALCTRL); + + return 0; +} + +static int hdmi_phy_configure(struct dw_hdmi *hdmi, + const struct drm_display_info *display) +{ + const struct dw_hdmi_phy_data *phy = hdmi->phy.data; + const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; + unsigned long mpixelclock = hdmi->hdmi_data.video_mode.mpixelclock; + unsigned long mtmdsclock = hdmi->hdmi_data.video_mode.mtmdsclock; + int ret; + + dw_hdmi_phy_power_off(hdmi); + + dw_hdmi_set_high_tmds_clock_ratio(hdmi, display); + + /* Leave low power consumption mode by asserting SVSRET. */ + if (phy->has_svsret) + dw_hdmi_phy_enable_svsret(hdmi, 1); + + dw_hdmi_phy_reset(hdmi); + + hdmi_writeb(hdmi, HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST); + if (!hdmi->eswin_plat) { + dw_hdmi_phy_i2c_set_addr(hdmi, 0x54); + } else { + dw_hdmi_phy_i2c_set_addr(hdmi, + HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2); + } + + /* Write to the PHY as configured by the platform */ + if (pdata->configure_phy) + ret = pdata->configure_phy(hdmi, pdata->priv_data, mpixelclock); + else + ret = phy->configure(hdmi, pdata, mpixelclock); + if (ret) { + dev_err(hdmi->dev, "PHY configuration failed (clock %lu)\n", + mpixelclock); + return ret; + } + + /* Wait for resuming transmission of TMDS clock and data */ + if (mtmdsclock > HDMI14_MAX_TMDSCLK) + msleep(100); + + return dw_hdmi_phy_power_on(hdmi); +} + +static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, + const struct drm_display_info *display, + const struct drm_display_mode *mode) +{ + int i, ret; + + /* HDMI Phy spec says to do the phy initialization sequence twice */ + for (i = 0; i < 2; i++) { + dw_hdmi_phy_sel_data_en_pol(hdmi, 1); + dw_hdmi_phy_sel_interface_control(hdmi, 0); + + ret = hdmi_phy_configure(hdmi, display); + if (ret) + return ret; + } + + return 0; +} + +static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data) +{ + dw_hdmi_phy_power_off(hdmi); +} + +enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi, void *data) +{ + return hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD ? + connector_status_connected : + connector_status_disconnected; +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_read_hpd); + +void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data, bool force, + bool disabled, bool rxsense) +{ + u8 old_mask = hdmi->phy_mask; + + if (force || disabled || !rxsense) + hdmi->phy_mask |= HDMI_PHY_RX_SENSE; + else + hdmi->phy_mask &= ~HDMI_PHY_RX_SENSE; + + if (old_mask != hdmi->phy_mask) + hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_update_hpd); + +void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data) +{ + /* + * Configure the PHY RX SENSE and HPD interrupts polarities and clear + * any pending interrupt. + */ + hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE, HDMI_PHY_POL0); + hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE, + HDMI_IH_PHY_STAT0); + + /* Enable cable hot plug irq. */ + hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0); + + /* Clear and unmute interrupts. */ + hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE, + HDMI_IH_PHY_STAT0); + hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE), + HDMI_IH_MUTE_PHY_STAT0); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_setup_hpd); + +static const struct dw_hdmi_phy_ops dw_hdmi_synopsys_phy_ops = { + .init = dw_hdmi_phy_init, + .disable = dw_hdmi_phy_disable, + .read_hpd = dw_hdmi_phy_read_hpd, + .update_hpd = dw_hdmi_phy_update_hpd, + .setup_hpd = dw_hdmi_phy_setup_hpd, +}; + +/* ----------------------------------------------------------------------------- + * HDMI TX Setup + */ + +static void hdmi_tx_hdcp_config(struct dw_hdmi *hdmi, + const struct drm_display_mode *mode) +{ + u8 de, vsync_pol, hsync_pol, hdmi_dvi; + + /* Configure the video polarity */ + if (hdmi->hdmi_data.video_mode.mdataenablepolarity) + de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH; + else + de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_LOW; + + vsync_pol = mode->flags & DRM_MODE_FLAG_PVSYNC ? + HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_HIGH : + HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_LOW; + + hsync_pol = mode->flags & DRM_MODE_FLAG_PHSYNC ? + HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_HIGH : + HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_LOW; + + /* Config the display mode */ + hdmi_dvi = hdmi->sink_is_hdmi ? HDMI_A_HDCPCFG0_HDMIDVI_HDMI : + HDMI_A_HDCPCFG0_HDMIDVI_DVI; + + hdmi_modb(hdmi, hdmi_dvi, HDMI_A_HDCPCFG0_HDMIDVI_MASK, + HDMI_A_HDCPCFG0); + + /* disable rx detect */ + hdmi_modb(hdmi, HDMI_A_HDCPCFG0_RXDETECT_DISABLE, + HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0); + + hdmi_modb(hdmi, vsync_pol | hsync_pol | de, + HDMI_A_VIDPOLCFG_VSYNCPOL_MASK | + HDMI_A_VIDPOLCFG_HSYNCPOL_MASK | + HDMI_A_VIDPOLCFG_DATAENPOL_MASK, + HDMI_A_VIDPOLCFG); + + hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1); + + if (hdmi->hdcp && hdmi->hdcp->hdcp_start) { + hdmi->hdcp->hdcp_start(hdmi->hdcp); + } +} + +static void hdmi_config_AVI(struct dw_hdmi *hdmi, + const struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + struct hdmi_avi_infoframe frame; + u8 val; + + /* Initialise info frame from DRM mode */ + drm_hdmi_avi_infoframe_from_display_mode(&frame, connector, mode); + + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) { + drm_hdmi_avi_infoframe_quant_range( + &frame, connector, mode, + hdmi->hdmi_data.rgb_limited_range ? + HDMI_QUANTIZATION_RANGE_LIMITED : + HDMI_QUANTIZATION_RANGE_FULL); + } else { + frame.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT; + frame.ycc_quantization_range = + HDMI_YCC_QUANTIZATION_RANGE_LIMITED; + } + + if (hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) + frame.colorspace = HDMI_COLORSPACE_YUV444; + else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) + frame.colorspace = HDMI_COLORSPACE_YUV422; + else if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) + frame.colorspace = HDMI_COLORSPACE_YUV420; + else + frame.colorspace = HDMI_COLORSPACE_RGB; + + /* Set up colorimetry */ + if (!hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) { + switch (hdmi->hdmi_data.enc_out_encoding) { + case V4L2_YCBCR_ENC_601: + if (hdmi->hdmi_data.enc_in_encoding == + V4L2_YCBCR_ENC_XV601) + frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; + else + frame.colorimetry = HDMI_COLORIMETRY_ITU_601; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + break; + case V4L2_YCBCR_ENC_709: + if (hdmi->hdmi_data.enc_in_encoding == + V4L2_YCBCR_ENC_XV709) + frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; + else + frame.colorimetry = HDMI_COLORIMETRY_ITU_709; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_709; + break; + + case V4L2_YCBCR_ENC_BT2020: + if (hdmi->hdmi_data.enc_in_encoding == + V4L2_YCBCR_ENC_BT2020) + frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; + else + frame.colorimetry = HDMI_COLORIMETRY_ITU_709; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_BT2020; + break; + + default: /* Carries no data */ + frame.colorimetry = HDMI_COLORIMETRY_ITU_601; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + break; + } + } else { + frame.colorimetry = HDMI_COLORIMETRY_NONE; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + } + + frame.scan_mode = HDMI_SCAN_MODE_NONE; + + /* + * The Designware IP uses a different byte format from standard + * AVI info frames, though generally the bits are in the correct + * bytes. + */ + + /* + * AVI data byte 1 differences: Colorspace in bits 0,1 rather than 5,6, + * scan info in bits 4,5 rather than 0,1 and active aspect present in + * bit 6 rather than 4. + */ + val = (frame.scan_mode & 3) << 4 | (frame.colorspace & 3); + if (frame.active_aspect & 15) + val |= HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT; + if (frame.top_bar || frame.bottom_bar) + val |= HDMI_FC_AVICONF0_BAR_DATA_HORIZ_BAR; + if (frame.left_bar || frame.right_bar) + val |= HDMI_FC_AVICONF0_BAR_DATA_VERT_BAR; + hdmi_writeb(hdmi, val, HDMI_FC_AVICONF0); + + /* AVI data byte 2 differences: none */ + val = ((frame.colorimetry & 0x3) << 6) | + ((frame.picture_aspect & 0x3) << 4) | (frame.active_aspect & 0xf); + hdmi_writeb(hdmi, val, HDMI_FC_AVICONF1); + + /* AVI data byte 3 differences: none */ + val = ((frame.extended_colorimetry & 0x7) << 4) | + ((frame.quantization_range & 0x3) << 2) | (frame.nups & 0x3); + if (frame.itc) + val |= HDMI_FC_AVICONF2_IT_CONTENT_VALID; + hdmi_writeb(hdmi, val, HDMI_FC_AVICONF2); + + /* AVI data byte 4 differences: none */ + val = frame.video_code & 0x7f; + hdmi_writeb(hdmi, val, HDMI_FC_AVIVID); + + /* AVI Data Byte 5- set up input and output pixel repetition */ + val = (((hdmi->hdmi_data.video_mode.mpixelrepetitioninput + 1) + << HDMI_FC_PRCONF_INCOMING_PR_FACTOR_OFFSET) & + HDMI_FC_PRCONF_INCOMING_PR_FACTOR_MASK) | + ((hdmi->hdmi_data.video_mode.mpixelrepetitionoutput + << HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_OFFSET) & + HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_MASK); + hdmi_writeb(hdmi, val, HDMI_FC_PRCONF); + + /* + * AVI data byte 5 differences: content type in 0,1 rather than 4,5, + * ycc range in bits 2,3 rather than 6,7 + */ + val = ((frame.ycc_quantization_range & 0x3) << 2) | + (frame.content_type & 0x3); + hdmi_writeb(hdmi, val, HDMI_FC_AVICONF3); + + /* AVI Data Bytes 6-13 */ + hdmi_writeb(hdmi, frame.top_bar & 0xff, HDMI_FC_AVIETB0); + hdmi_writeb(hdmi, (frame.top_bar >> 8) & 0xff, HDMI_FC_AVIETB1); + hdmi_writeb(hdmi, frame.bottom_bar & 0xff, HDMI_FC_AVISBB0); + hdmi_writeb(hdmi, (frame.bottom_bar >> 8) & 0xff, HDMI_FC_AVISBB1); + hdmi_writeb(hdmi, frame.left_bar & 0xff, HDMI_FC_AVIELB0); + hdmi_writeb(hdmi, (frame.left_bar >> 8) & 0xff, HDMI_FC_AVIELB1); + hdmi_writeb(hdmi, frame.right_bar & 0xff, HDMI_FC_AVISRB0); + hdmi_writeb(hdmi, (frame.right_bar >> 8) & 0xff, HDMI_FC_AVISRB1); +} + +static void +hdmi_config_vendor_specific_infoframe(struct dw_hdmi *hdmi, + const struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + struct hdmi_vendor_infoframe frame; + u8 buffer[10]; + ssize_t err; + + err = drm_hdmi_vendor_infoframe_from_display_mode(&frame, connector, + mode); + if (err < 0) + /* + * Going into that statement does not means vendor infoframe + * fails. It just informed us that vendor infoframe is not + * needed for the selected mode. Only 4k or stereoscopic 3D + * mode requires vendor infoframe. So just simply return. + */ + return; + + err = hdmi_vendor_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(hdmi->dev, "Failed to pack vendor infoframe: %zd\n", + err); + return; + } + hdmi_mask_writeb(hdmi, 0, HDMI_FC_DATAUTO0, HDMI_FC_DATAUTO0_VSD_OFFSET, + HDMI_FC_DATAUTO0_VSD_MASK); + + /* Set the length of HDMI vendor specific InfoFrame payload */ + hdmi_writeb(hdmi, buffer[2], HDMI_FC_VSDSIZE); + + /* Set 24bit IEEE Registration Identifier */ + hdmi_writeb(hdmi, buffer[4], HDMI_FC_VSDIEEEID0); + hdmi_writeb(hdmi, buffer[5], HDMI_FC_VSDIEEEID1); + hdmi_writeb(hdmi, buffer[6], HDMI_FC_VSDIEEEID2); + + /* Set HDMI_Video_Format and HDMI_VIC/3D_Structure */ + hdmi_writeb(hdmi, buffer[7], HDMI_FC_VSDPAYLOAD0); + hdmi_writeb(hdmi, buffer[8], HDMI_FC_VSDPAYLOAD1); + + if (frame.s3d_struct >= HDMI_3D_STRUCTURE_SIDE_BY_SIDE_HALF) + hdmi_writeb(hdmi, buffer[9], HDMI_FC_VSDPAYLOAD2); + + /* Packet frame interpolation */ + hdmi_writeb(hdmi, 1, HDMI_FC_DATAUTO1); + + /* Auto packets per frame and line spacing */ + hdmi_writeb(hdmi, 0x11, HDMI_FC_DATAUTO2); + + /* Configures the Frame Composer On RDRB mode */ + hdmi_mask_writeb(hdmi, 1, HDMI_FC_DATAUTO0, HDMI_FC_DATAUTO0_VSD_OFFSET, + HDMI_FC_DATAUTO0_VSD_MASK); +} + +static void hdmi_config_drm_infoframe(struct dw_hdmi *hdmi, + const struct drm_connector *connector) +{ + const struct drm_connector_state *conn_state = connector->state; + struct hdmi_drm_infoframe frame; + u8 buffer[30]; + ssize_t err; + int i; + + if (!hdmi->plat_data->use_drm_infoframe) + return; + + hdmi_modb(hdmi, HDMI_FC_PACKET_TX_EN_DRM_DISABLE, + HDMI_FC_PACKET_TX_EN_DRM_MASK, HDMI_FC_PACKET_TX_EN); + + err = drm_hdmi_infoframe_set_hdr_metadata(&frame, conn_state); + if (err < 0) + return; + + err = hdmi_drm_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(hdmi->dev, "Failed to pack drm infoframe: %zd\n", err); + return; + } + + hdmi_writeb(hdmi, frame.version, HDMI_FC_DRM_HB0); + hdmi_writeb(hdmi, frame.length, HDMI_FC_DRM_HB1); + + for (i = 0; i < frame.length; i++) + hdmi_writeb(hdmi, buffer[4 + i], HDMI_FC_DRM_PB0 + i); + + hdmi_writeb(hdmi, 1, HDMI_FC_DRM_UP); + hdmi_modb(hdmi, HDMI_FC_PACKET_TX_EN_DRM_ENABLE, + HDMI_FC_PACKET_TX_EN_DRM_MASK, HDMI_FC_PACKET_TX_EN); +} + +static void hdmi_av_composer(struct dw_hdmi *hdmi, + const struct drm_display_info *display, + const struct drm_display_mode *mode) +{ + u8 inv_val, bytes; + const struct drm_hdmi_info *hdmi_info = &display->hdmi; + struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode; + int hblank, vblank, h_de_hs, v_de_vs, hsync_len, vsync_len; + unsigned int vdisplay, hdisplay; + + vmode->previous_pixelclock = vmode->mpixelclock; + vmode->mpixelclock = mode->clock * 1000; + if ((mode->flags & DRM_MODE_FLAG_3D_MASK) == + DRM_MODE_FLAG_3D_FRAME_PACKING) + vmode->mpixelclock *= 2; + + dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock); + + vmode->previous_tmdsclock = vmode->mtmdsclock; + vmode->mtmdsclock = vmode->mpixelclock; + + if (!hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) { + switch (hdmi_bus_fmt_color_depth( + hdmi->hdmi_data.enc_out_bus_format)) { + case 16: + vmode->mtmdsclock = vmode->mpixelclock * 2; + break; + case 12: + vmode->mtmdsclock = vmode->mpixelclock * 3 / 2; + break; + case 10: + vmode->mtmdsclock = vmode->mpixelclock * 5 / 4; + break; + } + } + + if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) + vmode->mtmdsclock /= 2; + + dev_dbg(hdmi->dev, "final tmdsclock = %d\n", vmode->mtmdsclock); + + /* Set up HDMI_FC_INVIDCONF */ + inv_val = (hdmi->hdmi_data.hdcp_enable || + (dw_hdmi_support_scdc(hdmi, display) && + (vmode->mtmdsclock > HDMI14_MAX_TMDSCLK || + hdmi_info->scdc.scrambling.low_rates)) ? + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE : + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE); + + inv_val |= mode->flags & DRM_MODE_FLAG_PVSYNC ? + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW; + + inv_val |= mode->flags & DRM_MODE_FLAG_PHSYNC ? + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW; + + inv_val |= (vmode->mdataenablepolarity ? + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_LOW); + + if (hdmi->vic == 39) + inv_val |= HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH; + else + inv_val |= + mode->flags & DRM_MODE_FLAG_INTERLACE ? + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH : + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW; + + inv_val |= mode->flags & DRM_MODE_FLAG_INTERLACE ? + HDMI_FC_INVIDCONF_IN_I_P_INTERLACED : + HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE; + + inv_val |= hdmi->sink_is_hdmi ? HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE : + HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE; + + hdmi_writeb(hdmi, inv_val, HDMI_FC_INVIDCONF); + + hdisplay = mode->hdisplay; + hblank = mode->htotal - mode->hdisplay; + h_de_hs = mode->hsync_start - mode->hdisplay; + hsync_len = mode->hsync_end - mode->hsync_start; + + /* + * When we're setting a YCbCr420 mode, we need + * to adjust the horizontal timing to suit. + */ + if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) { + hdisplay /= 2; + hblank /= 2; + h_de_hs /= 2; + hsync_len /= 2; + } + + vdisplay = mode->vdisplay; + vblank = mode->vtotal - mode->vdisplay; + v_de_vs = mode->vsync_start - mode->vdisplay; + vsync_len = mode->vsync_end - mode->vsync_start; + + /* + * When we're setting an interlaced mode, we need + * to adjust the vertical timing to suit. + */ + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + vdisplay /= 2; + vblank /= 2; + v_de_vs /= 2; + vsync_len /= 2; + } else if ((mode->flags & DRM_MODE_FLAG_3D_MASK) == + DRM_MODE_FLAG_3D_FRAME_PACKING) { + vdisplay += mode->vtotal; + } + + /* Scrambling Control */ + if (dw_hdmi_support_scdc(hdmi, display)) { + if (vmode->mtmdsclock > HDMI14_MAX_TMDSCLK || + hdmi_info->scdc.scrambling.low_rates) { + /* + * HDMI2.0 Specifies the following procedure: + * After the Source Device has determined that + * SCDC_Present is set (=1), the Source Device should + * write the accurate Version of the Source Device + * to the Source Version field in the SCDCS. + * Source Devices compliant shall set the + * Source Version = 1. + */ + drm_scdc_readb(hdmi->ddc, SCDC_SINK_VERSION, &bytes); + drm_scdc_writeb(hdmi->ddc, SCDC_SOURCE_VERSION, + min_t(u8, bytes, + SCDC_MIN_SOURCE_VERSION)); + + /* Enabled Scrambling in the Sink */ + drm_scdc_set_scrambling(hdmi->ddc, 1); + + /* + * To activate the scrambler feature, you must ensure + * that the quasi-static configuration bit + * fc_invidconf.HDCP_keepout is set at configuration + * time, before the required mc_swrstzreq.tmdsswrst_req + * reset request is issued. + */ + hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, + HDMI_MC_SWRSTZ); + hdmi_writeb(hdmi, 1, HDMI_FC_SCRAMBLER_CTRL); + } else { + hdmi_writeb(hdmi, 0, HDMI_FC_SCRAMBLER_CTRL); + hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, + HDMI_MC_SWRSTZ); + drm_scdc_set_scrambling(hdmi->ddc, 0); + } + } else { + hdmi_writeb(hdmi, 0, HDMI_FC_SCRAMBLER_CTRL); + } + + /* Set up horizontal active pixel width */ + hdmi_writeb(hdmi, hdisplay >> 8, HDMI_FC_INHACTV1); + hdmi_writeb(hdmi, hdisplay, HDMI_FC_INHACTV0); + + /* Set up vertical active lines */ + hdmi_writeb(hdmi, vdisplay >> 8, HDMI_FC_INVACTV1); + hdmi_writeb(hdmi, vdisplay, HDMI_FC_INVACTV0); + + /* Set up horizontal blanking pixel region width */ + hdmi_writeb(hdmi, hblank >> 8, HDMI_FC_INHBLANK1); + hdmi_writeb(hdmi, hblank, HDMI_FC_INHBLANK0); + + /* Set up vertical blanking pixel region width */ + hdmi_writeb(hdmi, vblank, HDMI_FC_INVBLANK); + + /* Set up HSYNC active edge delay width (in pixel clks) */ + hdmi_writeb(hdmi, h_de_hs >> 8, HDMI_FC_HSYNCINDELAY1); + hdmi_writeb(hdmi, h_de_hs, HDMI_FC_HSYNCINDELAY0); + + /* Set up VSYNC active edge delay (in lines) */ + hdmi_writeb(hdmi, v_de_vs, HDMI_FC_VSYNCINDELAY); + + /* Set up HSYNC active pulse width (in pixel clks) */ + hdmi_writeb(hdmi, hsync_len >> 8, HDMI_FC_HSYNCINWIDTH1); + hdmi_writeb(hdmi, hsync_len, HDMI_FC_HSYNCINWIDTH0); + + /* Set up VSYNC active edge delay (in lines) */ + hdmi_writeb(hdmi, vsync_len, HDMI_FC_VSYNCINWIDTH); +} + +/* HDMI Initialization Step B.4 */ +static void dw_hdmi_enable_video_path(struct dw_hdmi *hdmi) +{ + /* control period minimum duration */ + hdmi_writeb(hdmi, 12, HDMI_FC_CTRLDUR); + hdmi_writeb(hdmi, 32, HDMI_FC_EXCTRLDUR); + hdmi_writeb(hdmi, 1, HDMI_FC_EXCTRLSPAC); + + /* Set to fill TMDS data channels */ + hdmi_writeb(hdmi, 0x0B, HDMI_FC_CH0PREAM); + hdmi_writeb(hdmi, 0x16, HDMI_FC_CH1PREAM); + hdmi_writeb(hdmi, 0x21, HDMI_FC_CH2PREAM); + + /* Enable pixel clock and tmds data path */ + hdmi->mc_clkdis |= + HDMI_MC_CLKDIS_HDCPCLK_DISABLE | HDMI_MC_CLKDIS_CSCCLK_DISABLE | + HDMI_MC_CLKDIS_AUDCLK_DISABLE | HDMI_MC_CLKDIS_PREPCLK_DISABLE | + HDMI_MC_CLKDIS_TMDSCLK_DISABLE; + hdmi->mc_clkdis &= ~HDMI_MC_CLKDIS_PIXELCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + + hdmi->mc_clkdis &= ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + + /* Enable csc path */ + if (is_csc_needed(hdmi)) { + hdmi->mc_clkdis &= ~HDMI_MC_CLKDIS_CSCCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + + hdmi_writeb(hdmi, HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH, + HDMI_MC_FLOWCTRL); + } else { + hdmi->mc_clkdis |= HDMI_MC_CLKDIS_CSCCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + + hdmi_writeb(hdmi, HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS, + HDMI_MC_FLOWCTRL); + } +} + +/* Workaround to clear the overflow condition */ +static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi) +{ + unsigned int count; + unsigned int i; + u8 val; + + /* + * Under some circumstances the Frame Composer arithmetic unit can miss + * an FC register write due to being busy processing the previous one. + * The issue can be worked around by issuing a TMDS software reset and + * then write one of the FC registers several times. + * + * The number of iterations matters and depends on the HDMI TX revision + * (and possibly on the platform). So far i.MX6Q (v1.30a), i.MX6DL + * (v1.31a) and multiple Allwinner SoCs (v1.32a) have been identified + * as needing the workaround, with 4 iterations for v1.30a and 1 + * iteration for others. + * The Amlogic Meson GX SoCs (v2.01a) have been identified as needing + * the workaround with a single iteration. + * The ESWIN SOC WIN2030(v2.14a) have been identified as needing the + * workaround with a single iteration. + */ + + switch (hdmi->version) { + case 0x130a: + count = 4; + break; + case 0x131a: + case 0x132a: + case 0x200a: + case 0x201a: + case 0x211a: + case 0x212a: + case 0x214a: + count = 1; + break; + default: + return; + } + + /* TMDS software reset */ + hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, HDMI_MC_SWRSTZ); + + val = hdmi_readb(hdmi, HDMI_FC_INVIDCONF); + for (i = 0; i < count; i++) + hdmi_writeb(hdmi, val, HDMI_FC_INVIDCONF); + if (!hdmi->eswin_plat) { + val = hdmi_readb(hdmi, HDMI_FC_VSYNCINWIDTH); + for (i = 0; i < count; i++) + hdmi_writeb(hdmi, val, HDMI_FC_VSYNCINWIDTH); + } + /* Audio software reset */ + if (hdmi->sink_has_audio) { + val = hdmi_readb(hdmi, HDMI_AUD_CONF0); + val &= HDMI_AUD_CONF0_I2S_SELECT_MASK; + hdmi_modb(hdmi, ~val, HDMI_AUD_CONF0_I2S_SELECT_MASK, + HDMI_AUD_CONF0); + udelay(10); + hdmi_modb(hdmi, val | HDMI_AUD_CONF0_SW_RESET, + HDMI_AUD_CONF0_SW_RESET | + HDMI_AUD_CONF0_I2S_SELECT_MASK, + HDMI_AUD_CONF0); + } +} + +static void hdmi_disable_overflow_interrupts(struct dw_hdmi *hdmi) +{ + hdmi_writeb(hdmi, HDMI_IH_MUTE_FC_STAT2_OVERFLOW_MASK, + HDMI_IH_MUTE_FC_STAT2); +} + +static int dw_hdmi_setup(struct dw_hdmi *hdmi, + const struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + void *data = hdmi->plat_data->phy_data; + int ret; + + hdmi_disable_overflow_interrupts(hdmi); + + hdmi->vic = drm_match_cea_mode(mode); + + if (!hdmi->vic) { + dev_dbg(hdmi->dev, "Non-CEA mode used in HDMI\n"); + } else { + dev_dbg(hdmi->dev, "CEA mode used vic=%d\n", hdmi->vic); + } + + if (hdmi->plat_data->get_enc_out_encoding) + hdmi->hdmi_data.enc_out_encoding = + hdmi->plat_data->get_enc_out_encoding(data); + else if ((hdmi->vic == 6) || (hdmi->vic == 7) || (hdmi->vic == 21) || + (hdmi->vic == 22) || (hdmi->vic == 2) || (hdmi->vic == 3) || + (hdmi->vic == 17) || (hdmi->vic == 18)) + hdmi->hdmi_data.enc_out_encoding = V4L2_YCBCR_ENC_601; + else + hdmi->hdmi_data.enc_out_encoding = V4L2_YCBCR_ENC_709; + + if (mode->flags & DRM_MODE_FLAG_DBLCLK) { + hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 1; + hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 1; + } else { + hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0; + hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0; + } + + /* TOFIX: Get input format from plat data or fallback to RGB888 */ + if (hdmi->plat_data->get_input_bus_format) + hdmi->hdmi_data.enc_in_bus_format = + hdmi->plat_data->get_input_bus_format(data); + else if (hdmi->hdmi_data.enc_in_bus_format == MEDIA_BUS_FMT_FIXED) + hdmi->hdmi_data.enc_in_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + else + hdmi->hdmi_data.enc_in_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + /* TOFIX: Get input encoding from plat data or fallback to none */ + if (hdmi->plat_data->get_enc_in_encoding) + hdmi->hdmi_data.enc_in_encoding = + hdmi->plat_data->get_enc_in_encoding(data); + else + hdmi->hdmi_data.enc_in_encoding = V4L2_YCBCR_ENC_DEFAULT; + + /* TOFIX: Default to RGB888 output format */ + if (hdmi->plat_data->get_output_bus_format) + hdmi->hdmi_data.enc_out_bus_format = + hdmi->plat_data->get_output_bus_format(data); + else if (hdmi->hdmi_data.enc_out_bus_format == MEDIA_BUS_FMT_FIXED) + hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + else + hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + hdmi->hdmi_data.rgb_limited_range = + hdmi->sink_is_hdmi && drm_default_rgb_quant_range(mode) == + HDMI_QUANTIZATION_RANGE_LIMITED; + + hdmi->hdmi_data.pix_repet_factor = 0; + hdmi->hdmi_data.hdcp_enable = 0; + hdmi->hdmi_data.video_mode.mdataenablepolarity = true; + + /* HDMI Initialization Step B.1 */ + hdmi_av_composer(hdmi, &connector->display_info, mode); + /* HDMI Initializateion Step B.2 */ + ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, + &connector->display_info, + &hdmi->previous_mode); + if (ret) + return ret; + hdmi->phy.enabled = true; + /* HDMI Initialization Step B.3 */ + dw_hdmi_enable_video_path(hdmi); + + if (hdmi->sink_has_audio) { + dev_dbg(hdmi->dev, "sink has audio support\n"); + + /* HDMI Initialization Step E - Configure audio */ + hdmi_clk_regenerator_update_pixel_clock(hdmi); + hdmi_enable_audio_clk(hdmi, hdmi->audio_enable); + } + + /* not for DVI mode */ + if (hdmi->sink_is_hdmi) { + dev_dbg(hdmi->dev, "%s HDMI mode\n", __func__); + + /* HDMI Initialization Step F - Configure AVI InfoFrame */ + hdmi_config_AVI(hdmi, connector, mode); + hdmi_config_vendor_specific_infoframe(hdmi, connector, mode); + hdmi_config_drm_infoframe(hdmi, connector); + } else { + dev_dbg(hdmi->dev, "%s DVI mode\n", __func__); + } + hdmi_video_packetize(hdmi); + hdmi_video_csc(hdmi); + hdmi_video_sample(hdmi); + if (!hpd_flag) { + hdmi_tx_hdcp_config(hdmi, mode); + } + dw_hdmi_clear_overflow(hdmi); + + return 0; +} + +static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi) +{ + u8 ih_mute; + /* + * Boot up defaults are: + * HDMI_IH_MUTE = 0x03 (disabled) + * HDMI_IH_MUTE_* = 0x00 (enabled) + * + * Disable top level interrupt bits in HDMI block + */ + ih_mute = hdmi_readb(hdmi, HDMI_IH_MUTE) | + HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT | + HDMI_IH_MUTE_MUTE_ALL_INTERRUPT; + + hdmi_writeb(hdmi, ih_mute, HDMI_IH_MUTE); + + /* by default mask all interrupts */ + hdmi_writeb(hdmi, 0xff, HDMI_VP_MASK); + hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK0); + hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK1); + hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK2); + hdmi_writeb(hdmi, 0xff, HDMI_PHY_MASK0); + hdmi_writeb(hdmi, 0xff, HDMI_PHY_I2CM_INT_ADDR); + hdmi_writeb(hdmi, 0xff, HDMI_PHY_I2CM_CTLINT_ADDR); + hdmi_writeb(hdmi, 0xff, HDMI_AUD_INT); + hdmi_writeb(hdmi, 0xff, HDMI_AUD_SPDIFINT); + hdmi_writeb(hdmi, 0xff, HDMI_AUD_HBR_MASK); + hdmi_writeb(hdmi, 0xff, HDMI_GP_MASK); + hdmi_writeb(hdmi, 0xff, HDMI_A_APIINTMSK); + hdmi_writeb(hdmi, 0xff, HDMI_I2CM_INT); + hdmi_writeb(hdmi, 0xff, HDMI_I2CM_CTLINT); + + /* Disable interrupts in the IH_MUTE_* registers */ + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT1); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT2); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_AS_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_PHY_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_I2CM_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_CEC_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_VP_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_I2CMPHY_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0); + + /* Enable top level interrupt bits in HDMI block */ + ih_mute &= ~(HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT | + HDMI_IH_MUTE_MUTE_ALL_INTERRUPT); + hdmi_writeb(hdmi, ih_mute, HDMI_IH_MUTE); +} + +static void dw_hdmi_poweron(struct dw_hdmi *hdmi) +{ + hdmi->bridge_is_on = true; + /* + * The curr_conn field is guaranteed to be valid here, as this function + * is only be called when !hdmi->disabled. + */ + dev_dbg(hdmi->dev, "%s", __func__); + dw_hdmi_setup(hdmi, hdmi->curr_conn, &hdmi->previous_mode); +} + +static void dw_hdmi_poweroff(struct dw_hdmi *hdmi) +{ + dev_dbg(hdmi->dev, "%s", __func__); + if (hdmi->phy.enabled) { + hdmi->phy.ops->disable(hdmi, hdmi->phy.data); + hdmi->phy.enabled = false; + } + hdmi->bridge_is_on = false; +} + +static void dw_hdmi_update_power(struct dw_hdmi *hdmi) +{ + int force = hdmi->force; + + if (hdmi->disabled) { + force = DRM_FORCE_OFF; + } else if (force == DRM_FORCE_UNSPECIFIED) { + if (hdmi->rxsense) + force = DRM_FORCE_ON; + else + force = DRM_FORCE_OFF; + } + + if (force == DRM_FORCE_OFF) { + if (hdmi->bridge_is_on) + dw_hdmi_poweroff(hdmi); + } else { + if (!hdmi->bridge_is_on) { + struct edid *edid; + edid = dw_hdmi_get_edid(hdmi, &hdmi->connector); + dw_hdmi_poweron(hdmi); + } + } +} + +/* + * Adjust the detection of RXSENSE according to whether we have a forced + * connection mode enabled, or whether we have been disabled. There is + * no point processing RXSENSE interrupts if we have a forced connection + * state, or DRM has us disabled. + * + * We also disable rxsense interrupts when we think we're disconnected + * to avoid floating TDMS signals giving false rxsense interrupts. + * + * Note: we still need to listen for HPD interrupts even when DRM has us + * disabled so that we can detect a connect event. + */ +static void dw_hdmi_update_phy_mask(struct dw_hdmi *hdmi) +{ + if (hdmi->phy.ops->update_hpd) + hdmi->phy.ops->update_hpd(hdmi, hdmi->phy.data, hdmi->force, + hdmi->disabled, hdmi->rxsense); +} + +static enum drm_connector_status dw_hdmi_detect(struct dw_hdmi *hdmi) +{ + enum drm_connector_status result; + result = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + + mutex_lock(&hdmi->mutex); + if (result != hdmi->last_connector_result) { + dev_info(hdmi->dev, "read_hpd result: %d", result); + handle_plugged_change(hdmi, + result == connector_status_connected); + hdmi->last_connector_result = result; + } + mutex_unlock(&hdmi->mutex); + + return result; +} + +static struct edid *dw_hdmi_get_edid(struct dw_hdmi *hdmi, + struct drm_connector *connector) +{ + struct edid *edid; + + if (!hdmi->ddc) + return NULL; + + edid = drm_get_edid(connector, hdmi->ddc); + if (!edid) { + dev_err(hdmi->dev, "failed to get edid\n"); + return NULL; + } + + dev_info(hdmi->dev, "got edid: width[%d] x height[%d]\n", + edid->width_cm, edid->height_cm); + + hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid); + hdmi->sink_has_audio = drm_detect_monitor_audio(edid); + + return edid; +} + +/* ----------------------------------------------------------------------------- + * DRM Connector Operations + */ + +static enum drm_connector_status +dw_hdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct dw_hdmi *hdmi = + container_of(connector, struct dw_hdmi, connector); + return dw_hdmi_detect(hdmi); +} + +static int dw_hdmi_connector_get_modes(struct drm_connector *connector) +{ + struct dw_hdmi *hdmi = + container_of(connector, struct dw_hdmi, connector); + struct edid *edid; + int ret; + + edid = dw_hdmi_get_edid(hdmi, connector); + if (!edid) + return 0; + + drm_connector_update_edid_property(connector, edid); + cec_notifier_set_phys_addr_from_edid(hdmi->cec_notifier, edid); + ret = drm_add_edid_modes(connector, edid); + kfree(edid); + + return ret; +} + +static int dw_hdmi_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct drm_connector_state *old_state = + drm_atomic_get_old_connector_state(state, connector); + struct drm_connector_state *new_state = + drm_atomic_get_new_connector_state(state, connector); + struct drm_crtc *crtc = new_state->crtc; + struct drm_crtc_state *crtc_state; + + if (!crtc) + return 0; + + if (!drm_connector_atomic_hdr_metadata_equal(old_state, new_state)) { + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + crtc_state->mode_changed = true; + } + + return 0; +} + +static void dw_hdmi_connector_force(struct drm_connector *connector) +{ + struct dw_hdmi *hdmi = + container_of(connector, struct dw_hdmi, connector); + + mutex_lock(&hdmi->mutex); + hdmi->force = connector->force; + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); + mutex_unlock(&hdmi->mutex); +} + +static int dw_hdmi_atomic_connector_set_property( + struct drm_connector *connector, struct drm_connector_state *state, + struct drm_property *property, uint64_t val) +{ + struct dw_hdmi *hdmi = + container_of(connector, struct dw_hdmi, connector); + const struct dw_hdmi_property_ops *ops = hdmi->plat_data->property_ops; + + if (ops && ops->set_property) + return ops->set_property(connector, state, property, val, + hdmi->plat_data->phy_data); + else + return -EINVAL; +} + +static int +dw_hdmi_atomic_connector_get_property(struct drm_connector *connector, + const struct drm_connector_state *state, + struct drm_property *property, + uint64_t *val) +{ + struct dw_hdmi *hdmi = + container_of(connector, struct dw_hdmi, connector); + const struct dw_hdmi_property_ops *ops = hdmi->plat_data->property_ops; + + if (ops && ops->get_property) + return ops->get_property(connector, state, property, val, + hdmi->plat_data->phy_data); + else + return -EINVAL; +} + +static int dw_hdmi_connector_set_property(struct drm_connector *connector, + struct drm_property *property, + uint64_t val) +{ + return dw_hdmi_atomic_connector_set_property(connector, NULL, property, + val); +} + +static const struct drm_connector_funcs dw_hdmi_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = dw_hdmi_connector_detect, + .destroy = drm_connector_cleanup, + .force = dw_hdmi_connector_force, + .reset = drm_atomic_helper_connector_reset, + .set_property = dw_hdmi_connector_set_property, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .atomic_set_property = dw_hdmi_atomic_connector_set_property, + .atomic_get_property = dw_hdmi_atomic_connector_get_property, +}; + +static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = { + .get_modes = dw_hdmi_connector_get_modes, + .atomic_check = dw_hdmi_connector_atomic_check, +}; + +static void dw_hdmi_attatch_properties(struct dw_hdmi *hdmi) +{ + unsigned int color = MEDIA_BUS_FMT_RGB888_1X24; + int video_mapping, colorspace; + + enum drm_connector_status connect_status = + hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + const struct dw_hdmi_property_ops *ops = hdmi->plat_data->property_ops; + + if (connect_status == connector_status_connected) { + video_mapping = (hdmi_readb(hdmi, HDMI_TX_INVID0) & + HDMI_TX_INVID0_VIDEO_MAPPING_MASK); + colorspace = (hdmi_readb(hdmi, HDMI_FC_AVICONF0) & + HDMI_FC_AVICONF0_PIX_FMT_MASK); + switch (video_mapping) { + case 0x01: + color = MEDIA_BUS_FMT_RGB888_1X24; + break; + case 0x03: + color = MEDIA_BUS_FMT_RGB101010_1X30; + break; + case 0x09: + if (colorspace == HDMI_COLORSPACE_YUV420) + color = MEDIA_BUS_FMT_UYYVYY8_0_5X24; + else if (colorspace == HDMI_COLORSPACE_YUV422) + color = MEDIA_BUS_FMT_UYVY8_1X16; + else + color = MEDIA_BUS_FMT_YUV8_1X24; + break; + case 0x0b: + if (colorspace == HDMI_COLORSPACE_YUV420) + color = MEDIA_BUS_FMT_UYYVYY10_0_5X30; + else if (colorspace == HDMI_COLORSPACE_YUV422) + color = MEDIA_BUS_FMT_UYVY10_1X20; + else + color = MEDIA_BUS_FMT_YUV10_1X30; + break; + case 0x14: + color = MEDIA_BUS_FMT_UYVY10_1X20; + break; + case 0x16: + color = MEDIA_BUS_FMT_UYVY8_1X16; + break; + default: + color = MEDIA_BUS_FMT_RGB888_1X24; + dev_err(hdmi->dev, "unexpected mapping: 0x%x\n", + video_mapping); + } + + hdmi->hdmi_data.enc_in_bus_format = color; + hdmi->hdmi_data.enc_out_bus_format = color; + /* + * input format will be set as yuv444 when output + * format is yuv420 + */ + if (color == MEDIA_BUS_FMT_UYVY10_1X20) + hdmi->hdmi_data.enc_in_bus_format = + MEDIA_BUS_FMT_YUV10_1X30; + else if (color == MEDIA_BUS_FMT_UYVY8_1X16) + hdmi->hdmi_data.enc_in_bus_format = + MEDIA_BUS_FMT_YUV8_1X24; + } + + if (ops && ops->attatch_properties) + return ops->attatch_properties(&hdmi->connector, color, + hdmi->version, + hdmi->plat_data->phy_data); +} + +static void dw_hdmi_destroy_properties(struct dw_hdmi *hdmi) +{ + const struct dw_hdmi_property_ops *ops = hdmi->plat_data->property_ops; + + if (ops && ops->destroy_properties) + return ops->destroy_properties(&hdmi->connector, + hdmi->plat_data->phy_data); +} + +static int dw_hdmi_connector_create(struct dw_hdmi *hdmi) +{ + struct drm_connector *connector = &hdmi->connector; + struct cec_connector_info conn_info; + struct cec_notifier *notifier; + + if (hdmi->version >= 0x200a) + connector->ycbcr_420_allowed = + hdmi->plat_data->ycbcr_420_allowed; + else + connector->ycbcr_420_allowed = false; + + connector->interlace_allowed = 1; + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, &dw_hdmi_connector_helper_funcs); + + drm_connector_init_with_ddc(hdmi->bridge.dev, connector, + &dw_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA, hdmi->ddc); + + /* + * drm_connector_attach_max_bpc_property() requires the + * connector to have a state. + */ + drm_atomic_helper_connector_reset(connector); + + drm_connector_attach_max_bpc_property(connector, 8, 16); + + if (hdmi->version >= 0x200a && hdmi->plat_data->use_drm_infoframe) + drm_connector_attach_hdr_output_metadata_property(connector); + + drm_connector_attach_encoder(connector, hdmi->bridge.encoder); + + dw_hdmi_attatch_properties(hdmi); + + cec_fill_conn_info_from_drm(&conn_info, connector); + + notifier = cec_notifier_conn_register(hdmi->dev, NULL, &conn_info); + if (!notifier) + return -ENOMEM; + + mutex_lock(&hdmi->cec_notifier_mutex); + hdmi->cec_notifier = notifier; + mutex_unlock(&hdmi->cec_notifier_mutex); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * DRM Bridge Operations + */ + +/* + * Possible output formats : + * - MEDIA_BUS_FMT_UYYVYY16_0_5X48, + * - MEDIA_BUS_FMT_UYYVYY12_0_5X36, + * - MEDIA_BUS_FMT_UYYVYY10_0_5X30, + * - MEDIA_BUS_FMT_UYYVYY8_0_5X24, + * - MEDIA_BUS_FMT_YUV16_1X48, + * - MEDIA_BUS_FMT_RGB161616_1X48, + * - MEDIA_BUS_FMT_UYVY12_1X24, + * - MEDIA_BUS_FMT_YUV12_1X36, + * - MEDIA_BUS_FMT_RGB121212_1X36, + * - MEDIA_BUS_FMT_UYVY10_1X20, + * - MEDIA_BUS_FMT_YUV10_1X30, + * - MEDIA_BUS_FMT_RGB101010_1X30, + * - MEDIA_BUS_FMT_UYVY8_1X16, + * - MEDIA_BUS_FMT_YUV8_1X24, + * - MEDIA_BUS_FMT_RGB888_1X24, + */ + +/* Can return a maximum of 11 possible output formats for a mode/connector */ +#define MAX_OUTPUT_SEL_FORMATS 11 + +static u32 *dw_hdmi_bridge_atomic_get_output_bus_fmts( + struct drm_bridge *bridge, struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, unsigned int *num_output_fmts) +{ + struct drm_connector *conn = conn_state->connector; + struct drm_display_info *info = &conn->display_info; + struct drm_display_mode *mode = &crtc_state->mode; + u8 max_bpc = conn_state->max_requested_bpc; + bool is_hdmi2_sink = info->hdmi.scdc.supported || + (info->color_formats & DRM_COLOR_FORMAT_YCRCB420); + u32 *output_fmts; + unsigned int i = 0; + + *num_output_fmts = 0; + + output_fmts = kcalloc(MAX_OUTPUT_SEL_FORMATS, sizeof(*output_fmts), + GFP_KERNEL); + if (!output_fmts) + return NULL; + + /* If dw-hdmi is the only bridge, avoid negociating with ourselves */ + if (list_is_singular(&bridge->encoder->bridge_chain)) { + *num_output_fmts = 1; + output_fmts[0] = MEDIA_BUS_FMT_FIXED; + + return output_fmts; + } + + /* + * If the current mode enforces 4:2:0, force the output but format + * to 4:2:0 and do not add the YUV422/444/RGB formats + */ + if (conn->ycbcr_420_allowed && + (drm_mode_is_420_only(info, mode) || + (is_hdmi2_sink && drm_mode_is_420_also(info, mode)))) { + /* Order bus formats from 16bit to 8bit if supported */ + if (max_bpc >= 16 && info->bpc == 16 && + (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48)) + output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY16_0_5X48; + + if (max_bpc >= 12 && info->bpc >= 12 && + (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36)) + output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY12_0_5X36; + + if (max_bpc >= 10 && info->bpc >= 10 && + (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30)) + output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY10_0_5X30; + + /* Default 8bit fallback */ + output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY8_0_5X24; + + *num_output_fmts = i; + + return output_fmts; + } + + /* + * Order bus formats from 16bit to 8bit and from YUV422 to RGB + * if supported. In any case the default RGB888 format is added + */ + + if (max_bpc >= 16 && info->bpc == 16) { + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48; + + output_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48; + } + + if (max_bpc >= 12 && info->bpc >= 12) { + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + output_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + + output_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + } + + if (max_bpc >= 10 && info->bpc >= 10) { + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + output_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + + output_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + } + + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + output_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + + /* Default 8bit RGB fallback */ + output_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + + *num_output_fmts = i; + + return output_fmts; +} + +/* + * Possible input formats : + * - MEDIA_BUS_FMT_RGB888_1X24 + * - MEDIA_BUS_FMT_YUV8_1X24 + * - MEDIA_BUS_FMT_UYVY8_1X16 + * - MEDIA_BUS_FMT_UYYVYY8_0_5X24 + * - MEDIA_BUS_FMT_RGB101010_1X30 + * - MEDIA_BUS_FMT_YUV10_1X30 + * - MEDIA_BUS_FMT_UYVY10_1X20 + * - MEDIA_BUS_FMT_UYYVYY10_0_5X30 + * - MEDIA_BUS_FMT_RGB121212_1X36 + * - MEDIA_BUS_FMT_YUV12_1X36 + * - MEDIA_BUS_FMT_UYVY12_1X24 + * - MEDIA_BUS_FMT_UYYVYY12_0_5X36 + * - MEDIA_BUS_FMT_RGB161616_1X48 + * - MEDIA_BUS_FMT_YUV16_1X48 + * - MEDIA_BUS_FMT_UYYVYY16_0_5X48 + */ + +/* Can return a maximum of 3 possible input formats for an output format */ +#define MAX_INPUT_SEL_FORMATS 3 + +static u32 *dw_hdmi_bridge_atomic_get_input_bus_fmts( + struct drm_bridge *bridge, struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, u32 output_fmt, + unsigned int *num_input_fmts) +{ + u32 *input_fmts; + unsigned int i = 0; + + *num_input_fmts = 0; + + input_fmts = + kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts), GFP_KERNEL); + if (!input_fmts) + return NULL; + + switch (output_fmt) { + /* If MEDIA_BUS_FMT_FIXED is tested, return default bus format */ + case MEDIA_BUS_FMT_FIXED: + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + break; + /* 8bit */ + case MEDIA_BUS_FMT_RGB888_1X24: + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + break; + case MEDIA_BUS_FMT_YUV8_1X24: + input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + break; + + /* 10bit */ + case MEDIA_BUS_FMT_RGB101010_1X30: + input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + break; + case MEDIA_BUS_FMT_YUV10_1X30: + input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + break; + case MEDIA_BUS_FMT_UYVY10_1X20: + input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + break; + + /* 12bit */ + case MEDIA_BUS_FMT_RGB121212_1X36: + input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + break; + case MEDIA_BUS_FMT_YUV12_1X36: + input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + break; + case MEDIA_BUS_FMT_UYVY12_1X24: + input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + break; + + /* 16bit */ + case MEDIA_BUS_FMT_RGB161616_1X48: + input_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48; + input_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48; + break; + case MEDIA_BUS_FMT_YUV16_1X48: + input_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48; + input_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48; + break; + + /*YUV 4:2:0 */ + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + input_fmts[i++] = output_fmt; + break; + } + + *num_input_fmts = i; + + if (*num_input_fmts == 0) { + kfree(input_fmts); + input_fmts = NULL; + } + + return input_fmts; +} + +static int dw_hdmi_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + hdmi->hdmi_data.enc_out_bus_format = + bridge_state->output_bus_cfg.format; + + hdmi->hdmi_data.enc_in_bus_format = bridge_state->input_bus_cfg.format; + + dev_dbg(hdmi->dev, "input format 0x%04x, output format 0x%04x\n", + bridge_state->input_bus_cfg.format, + bridge_state->output_bus_cfg.format); + + return 0; +} + +static int dw_hdmi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return drm_bridge_attach(bridge->encoder, hdmi->next_bridge, + bridge, flags); + + return dw_hdmi_connector_create(hdmi); +} + +static void dw_hdmi_bridge_detach(struct drm_bridge *bridge) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + mutex_lock(&hdmi->cec_notifier_mutex); + cec_notifier_conn_unregister(hdmi->cec_notifier); + hdmi->cec_notifier = NULL; + mutex_unlock(&hdmi->cec_notifier_mutex); +} + +static enum drm_mode_status +dw_hdmi_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; + enum drm_mode_status mode_status = MODE_OK; + + /* We don't support double-clocked modes */ + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + return MODE_BAD; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_NO_INTERLACE; + + if (mode->clock != 594000 && mode->clock != 297000 && + mode->clock != 148500 && mode->clock != 108000 && + mode->clock != 74250 && mode->clock != 54000 && + mode->clock != 27000) { + return MODE_NOCLOCK; + } + + if (pdata->mode_valid) + mode_status = + pdata->mode_valid(hdmi, pdata->priv_data, info, mode); + + return mode_status; +} + +static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *orig_mode, + const struct drm_display_mode *mode) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + mutex_lock(&hdmi->mutex); + + /* Store the display mode for plugin/DKMS poweron events */ + memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode)); + + mutex_unlock(&hdmi->mutex); +} + +static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + mutex_lock(&hdmi->mutex); + hdmi->disabled = true; + hdmi->curr_conn = NULL; + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); + mutex_unlock(&hdmi->mutex); +} + +static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + struct drm_atomic_state *state = old_state->base.state; + struct drm_connector *connector; + + connector = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + + mutex_lock(&hdmi->mutex); + hdmi->disabled = false; + hdmi->curr_conn = connector; + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); + mutex_unlock(&hdmi->mutex); +} + +static enum drm_connector_status +dw_hdmi_bridge_detect(struct drm_bridge *bridge) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + return dw_hdmi_detect(hdmi); +} + +static struct edid *dw_hdmi_bridge_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + return dw_hdmi_get_edid(hdmi, connector); +} + +static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = { + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .attach = dw_hdmi_bridge_attach, + .detach = dw_hdmi_bridge_detach, + .atomic_check = dw_hdmi_bridge_atomic_check, + .atomic_get_output_bus_fmts = dw_hdmi_bridge_atomic_get_output_bus_fmts, + .atomic_get_input_bus_fmts = dw_hdmi_bridge_atomic_get_input_bus_fmts, + .atomic_enable = dw_hdmi_bridge_atomic_enable, + .atomic_disable = dw_hdmi_bridge_atomic_disable, + .mode_set = dw_hdmi_bridge_mode_set, + .mode_valid = dw_hdmi_bridge_mode_valid, + .detect = dw_hdmi_bridge_detect, + .get_edid = dw_hdmi_bridge_get_edid, +}; + +/* ----------------------------------------------------------------------------- + * IRQ Handling + */ + +static irqreturn_t dw_hdmi_i2c_irq(struct dw_hdmi *hdmi) +{ + struct dw_hdmi_i2c *i2c = hdmi->i2c; + unsigned int stat; + + stat = hdmi_readb(hdmi, HDMI_IH_I2CM_STAT0); + if (!stat) + return IRQ_NONE; + + hdmi_writeb(hdmi, stat, HDMI_IH_I2CM_STAT0); + + i2c->stat = stat; + + complete(&i2c->cmp); + + return IRQ_HANDLED; +} + +static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) +{ + struct dw_hdmi *hdmi = dev_id; + u8 intr_stat, hdcp_stat; + irqreturn_t ret = IRQ_NONE; + + if (hdmi->i2c) + ret = dw_hdmi_i2c_irq(hdmi); + if (ret == IRQ_HANDLED) + return ret; + + intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); + hdcp_stat = hdmi_readb(hdmi, HDMI_A_APIINTSTAT); + if (intr_stat | hdcp_stat) { + if (intr_stat) { + dev_info(hdmi->dev, "intr status %#x\n", intr_stat); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_PHY_STAT0); + } + if (hdcp_stat) { + dev_info(hdmi->dev, "HDCP status %#x\n", hdcp_stat); + hdmi_writeb(hdmi, 0xff, HDMI_A_APIINTMSK); + } + return IRQ_WAKE_THREAD; + } + + return ret; +} + +void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense) +{ + mutex_lock(&hdmi->mutex); + + if (!hdmi->force) { + /* + * If the RX sense status indicates we're disconnected, + * clear the software rxsense status. + */ + if (!rx_sense) + hdmi->rxsense = false; + + /* + * Only set the software rxsense status when both + * rxsense and hpd indicates we're connected. + * This avoids what seems to be bad behaviour in + * at least iMX6S versions of the phy. + */ + if (hpd) + hdmi->rxsense = true; + + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); + } + mutex_unlock(&hdmi->mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_setup_rx_sense); + +static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) +{ + struct dw_hdmi *hdmi = dev_id; + u8 intr_stat, phy_int_pol, phy_pol_mask, phy_stat, hdcp_stat; + + intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); + if (intr_stat) { + phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0); + phy_stat = hdmi_readb(hdmi, HDMI_PHY_STAT0); + dev_dbg(hdmi->dev, "phy_int_pol:0x%x, phy_stat:0x%x\n", + phy_int_pol, phy_stat); + + phy_pol_mask = 0; + if (intr_stat & HDMI_IH_PHY_STAT0_HPD) + phy_pol_mask |= HDMI_PHY_HPD; + if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE0) + phy_pol_mask |= HDMI_PHY_RX_SENSE0; + if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE1) + phy_pol_mask |= HDMI_PHY_RX_SENSE1; + if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE2) + phy_pol_mask |= HDMI_PHY_RX_SENSE2; + if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE3) + phy_pol_mask |= HDMI_PHY_RX_SENSE3; + + if (phy_pol_mask) + hdmi_modb(hdmi, ~phy_int_pol, phy_pol_mask, + HDMI_PHY_POL0); + if (phy_int_pol & HDMI_PHY_HPD) + hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_BUSCLEAR, + HDMI_I2CM_OPERATION); + + /* + * RX sense tells us whether the TDMS transmitters are detecting + * load - in other words, there's something listening on the + * other end of the link. Use this to decide whether we should + * power on the phy as HPD may be toggled by the sink to merely + * ask the source to re-read the EDID. + */ + if (intr_stat & + (HDMI_IH_PHY_STAT0_RX_SENSE | HDMI_IH_PHY_STAT0_HPD)) { + hpd_flag = true; + dw_hdmi_setup_rx_sense(hdmi, phy_stat & HDMI_PHY_HPD, + phy_stat & HDMI_PHY_RX_SENSE); + + if ((phy_stat & (HDMI_PHY_RX_SENSE | HDMI_PHY_HPD)) == + 0) { + mutex_lock(&hdmi->cec_notifier_mutex); + cec_notifier_phys_addr_invalidate( + hdmi->cec_notifier); + mutex_unlock(&hdmi->cec_notifier_mutex); + } + } + + check_hdmi_irq(hdmi, intr_stat, phy_int_pol); + hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0); + hdmi_writeb(hdmi, + ~(HDMI_IH_PHY_STAT0_HPD | + HDMI_IH_PHY_STAT0_RX_SENSE), + HDMI_IH_MUTE_PHY_STAT0); + } + + hpd_flag = false; + + hdcp_stat = hdmi_readb(hdmi, HDMI_A_APIINTSTAT); + if (hdcp_stat) { + if (hdmi->hdcp && hdmi->hdcp->hdcp_isr) + hdmi->hdcp->hdcp_isr(hdmi->hdcp, hdcp_stat); + hdmi_writeb(hdmi, hdcp_stat, HDMI_A_APIINTCLR); + hdmi_writeb(hdmi, 0x00, HDMI_A_APIINTMSK); + } + + return IRQ_HANDLED; +} + +static const struct dw_hdmi_phy_data dw_hdmi_phys[] = { + { + .type = DW_HDMI_PHY_DWC_HDMI_TX_PHY, + .name = "DWC HDMI TX PHY", + .gen = 1, + }, + { + .type = DW_HDMI_PHY_DWC_MHL_PHY_HEAC, + .name = "DWC MHL PHY + HEAC PHY", + .gen = 2, + .has_svsret = true, + .configure = hdmi_phy_configure_dwc_hdmi_3d_tx, + }, + { + .type = DW_HDMI_PHY_DWC_MHL_PHY, + .name = "DWC MHL PHY", + .gen = 2, + .has_svsret = true, + .configure = hdmi_phy_configure_dwc_hdmi_3d_tx, + }, + { + .type = DW_HDMI_PHY_DWC_HDMI_3D_TX_PHY_HEAC, + .name = "DWC HDMI 3D TX PHY + HEAC PHY", + .gen = 2, + .configure = hdmi_phy_configure_dwc_hdmi_3d_tx, + }, + { + .type = DW_HDMI_PHY_DWC_HDMI_3D_TX_PHY, + .name = "DWC HDMI 3D TX PHY", + .gen = 2, + .configure = hdmi_phy_configure_dwc_hdmi_3d_tx, + }, + { + .type = DW_HDMI_PHY_DWC_HDMI20_TX_PHY, + .name = "DWC HDMI 2.0 TX PHY", + .gen = 2, + .has_svsret = true, + .configure = hdmi_phy_configure_dwc_hdmi_3d_tx, + }, + { + .type = DW_HDMI_PHY_VENDOR_PHY, + .name = "Vendor PHY", + } +}; + +static int dw_hdmi_detect_phy(struct dw_hdmi *hdmi) +{ + unsigned int i; + u8 phy_type; + if (hdmi->eswin_plat) { + phy_type = hdmi->plat_data->phy_force_vendor ? + DW_HDMI_PHY_VENDOR_PHY : + hdmi_readb(hdmi, HDMI_CONFIG2_ID); + + if (phy_type == DW_HDMI_PHY_VENDOR_PHY) { + /* Vendor PHYs require support from the glue layer. */ + if (!hdmi->plat_data->phy_ops || + !hdmi->plat_data->phy_name) { + dev_err(hdmi->dev, + "Vendor HDMI PHY not supported by glue layer\n"); + return -ENODEV; + } + + hdmi->phy.ops = hdmi->plat_data->phy_ops; + hdmi->phy.data = hdmi->plat_data->phy_data; + hdmi->phy.name = hdmi->plat_data->phy_name; + return 0; + } + } else { + phy_type = 0xf3; + } + /* Synopsys PHYs are handled internally. */ + for (i = 0; i < ARRAY_SIZE(dw_hdmi_phys); ++i) { + if (dw_hdmi_phys[i].type == phy_type) { + hdmi->phy.ops = &dw_hdmi_synopsys_phy_ops; + hdmi->phy.name = dw_hdmi_phys[i].name; + hdmi->phy.data = (void *)&dw_hdmi_phys[i]; + + if (!dw_hdmi_phys[i].configure && + !hdmi->plat_data->configure_phy) { + dev_err(hdmi->dev, + "%s requires platform support\n", + hdmi->phy.name); + return -ENODEV; + } + + return 0; + } + } + + dev_err(hdmi->dev, "Unsupported HDMI PHY type (%02x)\n", phy_type); + return -ENODEV; +} + +static void dw_hdmi_cec_enable(struct dw_hdmi *hdmi) +{ + mutex_lock(&hdmi->mutex); + hdmi->mc_clkdis &= ~HDMI_MC_CLKDIS_CECCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + mutex_unlock(&hdmi->mutex); +} + +static void dw_hdmi_cec_disable(struct dw_hdmi *hdmi) +{ + mutex_lock(&hdmi->mutex); + hdmi->mc_clkdis |= HDMI_MC_CLKDIS_CECCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + mutex_unlock(&hdmi->mutex); +} + +static const struct dw_hdmi_cec_ops dw_hdmi_cec_ops = { + .write = hdmi_writeb, + .read = hdmi_readb, + .enable = dw_hdmi_cec_enable, + .disable = dw_hdmi_cec_disable, +}; + +static int dw_hdmi_status_show(struct seq_file *s, void *v) +{ + struct dw_hdmi *hdmi = s->private; + u32 val; + + seq_puts(s, "PHY: "); + if (!hdmi->phy.enabled) { + seq_puts(s, "disabled\n"); + return 0; + } + seq_puts(s, "enabled\t\t\tMode: "); + if (hdmi->sink_is_hdmi) + seq_puts(s, "HDMI\n"); + else + seq_puts(s, "DVI\n"); + if (hdmi->hdmi_data.video_mode.mtmdsclock > 340000000) + val = hdmi->hdmi_data.video_mode.mtmdsclock / 4; + else + val = hdmi->hdmi_data.video_mode.mtmdsclock; + seq_printf(s, "Pixel Clk: %uHz\t\tTMDS Clk: %uHz\n", + hdmi->hdmi_data.video_mode.mpixelclock, val); + seq_puts(s, "Color Format: "); + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) + seq_puts(s, "RGB"); + else if (hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) + seq_puts(s, "YUV444"); + else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) + seq_puts(s, "YUV422"); + else if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) + seq_puts(s, "YUV420"); + else + seq_puts(s, "UNKNOWN"); + val = hdmi_bus_fmt_color_depth(hdmi->hdmi_data.enc_out_bus_format); + seq_printf(s, "\t\tColor Depth: %d bit\n", val); + seq_puts(s, "Colorimetry: "); + switch (hdmi->hdmi_data.enc_out_encoding) { + case V4L2_YCBCR_ENC_601: + seq_puts(s, "ITU.BT601"); + break; + case V4L2_YCBCR_ENC_709: + seq_puts(s, "ITU.BT709"); + break; + case V4L2_YCBCR_ENC_BT2020: + seq_puts(s, "ITU.BT2020"); + break; + default: /* Carries no data */ + seq_puts(s, "ITU.BT601"); + break; + } + + seq_puts(s, "\t\tEOTF: "); + + if (hdmi->version < 0x211a) { + seq_puts(s, "Unsupported\n"); + return 0; + } + + val = hdmi_readb(hdmi, HDMI_FC_PACKET_TX_EN); + if (!(val & HDMI_FC_PACKET_TX_EN_DRM_MASK)) { + seq_puts(s, "Off\n"); + return 0; + } + + switch (hdmi_readb(hdmi, HDMI_FC_DRM_PB0)) { + case TRADITIONAL_GAMMA_SDR: + seq_puts(s, "SDR"); + break; + case TRADITIONAL_GAMMA_HDR: + seq_puts(s, "HDR"); + break; + case SMPTE_ST2084: + seq_puts(s, "ST2084"); + break; + case HLG: + seq_puts(s, "HLG"); + break; + default: + seq_puts(s, "Not Defined\n"); + return 0; + } + + val = hdmi_readb(hdmi, HDMI_FC_DRM_PB3) << 8; + val |= hdmi_readb(hdmi, HDMI_FC_DRM_PB2); + seq_printf(s, "\nx0: %d", val); + val = hdmi_readb(hdmi, HDMI_FC_DRM_PB5) << 8; + val |= hdmi_readb(hdmi, HDMI_FC_DRM_PB4); + seq_printf(s, "\t\t\t\ty0: %d\n", val); + val = hdmi_readb(hdmi, HDMI_FC_DRM_PB7) << 8; + val |= hdmi_readb(hdmi, HDMI_FC_DRM_PB6); + seq_printf(s, "x1: %d", val); + val = hdmi_readb(hdmi, HDMI_FC_DRM_PB9) << 8; + val |= hdmi_readb(hdmi, HDMI_FC_DRM_PB8); + seq_printf(s, "\t\t\t\ty1: %d\n", val); + val = hdmi_readb(hdmi, HDMI_FC_DRM_PB11) << 8; + val |= hdmi_readb(hdmi, HDMI_FC_DRM_PB10); + seq_printf(s, "x2: %d", val); + val = hdmi_readb(hdmi, HDMI_FC_DRM_PB13) << 8; + val |= hdmi_readb(hdmi, HDMI_FC_DRM_PB12); + seq_printf(s, "\t\t\t\ty2: %d\n", val); + val = hdmi_readb(hdmi, HDMI_FC_DRM_PB15) << 8; + val |= hdmi_readb(hdmi, HDMI_FC_DRM_PB14); + seq_printf(s, "white x: %d", val); + val = hdmi_readb(hdmi, HDMI_FC_DRM_PB17) << 8; + val |= hdmi_readb(hdmi, HDMI_FC_DRM_PB16); + seq_printf(s, "\t\t\twhite y: %d\n", val); + val = hdmi_readb(hdmi, HDMI_FC_DRM_PB19) << 8; + val |= hdmi_readb(hdmi, HDMI_FC_DRM_PB18); + seq_printf(s, "max lum: %d", val); + val = hdmi_readb(hdmi, HDMI_FC_DRM_PB21) << 8; + val |= hdmi_readb(hdmi, HDMI_FC_DRM_PB20); + seq_printf(s, "\t\t\tmin lum: %d\n", val); + val = hdmi_readb(hdmi, HDMI_FC_DRM_PB23) << 8; + val |= hdmi_readb(hdmi, HDMI_FC_DRM_PB22); + seq_printf(s, "max cll: %d", val); + val = hdmi_readb(hdmi, HDMI_FC_DRM_PB25) << 8; + val |= hdmi_readb(hdmi, HDMI_FC_DRM_PB24); + seq_printf(s, "\t\t\tmax fall: %d\n", val); + return 0; +} + +static int dw_hdmi_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, dw_hdmi_status_show, inode->i_private); +} + +static const struct file_operations dw_hdmi_status_fops = { + .owner = THIS_MODULE, + .open = dw_hdmi_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#include +#include +#include + +struct dw_hdmi_reg_table { + int reg_base; + int reg_end; +}; + +static const struct dw_hdmi_reg_table hdmi_reg_table[] = { + { HDMI_DESIGN_ID, HDMI_CONFIG3_ID }, + { HDMI_IH_FC_STAT0, HDMI_IH_MUTE }, + { HDMI_TX_INVID0, HDMI_TX_BCBDATA1 }, + { HDMI_VP_STATUS, HDMI_VP_POL }, + { HDMI_FC_INVIDCONF, HDMI_FC_DBGTMDS2 }, + { HDMI_PHY_CONF0, HDMI_PHY_POL0 }, + { HDMI_PHY_I2CM_SLAVE_ADDR, HDMI_PHY_I2CM_FS_SCL_LCNT_0_ADDR }, + { HDMI_AUD_CONF0, 0x3624 }, + { HDMI_MC_SFRDIV, HDMI_MC_HEACPHY_RST }, + { HDMI_CSC_CFG, HDMI_CSC_COEF_C4_LSB }, + { HDMI_A_HDCPCFG0, 0x52bb }, + { 0x7800, 0x7818 }, + { 0x7900, 0x790e }, + { HDMI_CEC_CTRL, HDMI_CEC_WKUPCTRL }, + { HDMI_I2CM_SLAVE, HDMI_I2CM_MAX_REG }, +}; + +static int dw_hdmi_ctrl_show(struct seq_file *s, void *v) +{ + struct dw_hdmi *hdmi = s->private; + u32 i = 0, j = 0, val = 0; + + seq_puts(s, "\n>>>hdmi_ctl reg "); + for (i = 0; i < 16; i++) + seq_printf(s, " %2x", i); + seq_puts(s, "\n---------------------------------------------------"); + + for (i = 0; i < ARRAY_SIZE(hdmi_reg_table); i++) { + for (j = hdmi_reg_table[i].reg_base; + j <= hdmi_reg_table[i].reg_end; j++) { + val = hdmi_readb(hdmi, j); + if ((j - hdmi_reg_table[i].reg_base) % 16 == 0) + seq_printf(s, "\n>>>hdmi_ctl %04x:", j); + seq_printf(s, " %02x", val); + } + } + seq_puts(s, "\n---------------------------------------------------\n"); + + return 0; +} + +static int dw_hdmi_ctrl_open(struct inode *inode, struct file *file) +{ + return single_open(file, dw_hdmi_ctrl_show, inode->i_private); +} + +static ssize_t dw_hdmi_ctrl_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct dw_hdmi *hdmi = ((struct seq_file *)file->private_data)->private; + u32 reg, val; + char kbuf[25]; + + if (copy_from_user(kbuf, buf, count)) + return -EFAULT; + if (sscanf(kbuf, "%x%x", ®, &val) == -1) + return -EFAULT; + if ((reg < 0) || (reg > HDMI_I2CM_SCDC_READ_UPDATE_ON)) { + dev_err(hdmi->dev, "it is no a hdmi register\n"); + return count; + } + dev_info(hdmi->dev, "/**********hdmi register config******/"); + dev_info(hdmi->dev, "\n reg=%x val=%x\n", reg, val); + hdmi_writeb(hdmi, val, reg); + return count; +} + +static const struct file_operations dw_hdmi_ctrl_fops = { + .owner = THIS_MODULE, + .open = dw_hdmi_ctrl_open, + .read = seq_read, + .write = dw_hdmi_ctrl_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static int dw_hdmi_phy_show(struct seq_file *s, void *v) +{ + struct dw_hdmi *hdmi = s->private; + u32 i, val; + + seq_puts(s, "\n>>>hdmi_phy reg\n"); + for (i = 0; i < 0x28; i++) { + val = hdmi_phy_i2c_read(hdmi, i); + seq_printf(s, "regs %02x val %04x\n", i, val); + } + return 0; +} + +static int dw_hdmi_phy_open(struct inode *inode, struct file *file) +{ + return single_open(file, dw_hdmi_phy_show, inode->i_private); +} + +static ssize_t dw_hdmi_phy_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct dw_hdmi *hdmi = ((struct seq_file *)file->private_data)->private; + u32 reg, val; + char kbuf[25]; + + if (copy_from_user(kbuf, buf, count)) + return -EFAULT; + if (sscanf(kbuf, "%x%x", ®, &val) == -1) + return -EFAULT; + if ((reg < 0) || (reg > 0x100)) { + dev_err(hdmi->dev, "it is not a hdmi phy register\n"); + return count; + } + dev_info(hdmi->dev, "/*******hdmi phy register config******/"); + dev_info(hdmi->dev, "\n reg=%x val=%x\n", reg, val); + dw_hdmi_phy_i2c_write(hdmi, val, reg); + return count; +} + +static const struct file_operations dw_hdmi_phy_fops = { + .owner = THIS_MODULE, + .open = dw_hdmi_phy_open, + .read = seq_read, + .write = dw_hdmi_phy_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static void dw_hdmi_register_debugfs(struct device *dev, struct dw_hdmi *hdmi) +{ + hdmi->debugfs_dir = debugfs_create_dir("dw-hdmi", NULL); + if (IS_ERR(hdmi->debugfs_dir)) { + dev_err(dev, "failed to create debugfs dir!\n"); + return; + } + + debugfs_create_file("status", 0400, hdmi->debugfs_dir, hdmi, + &dw_hdmi_status_fops); + + debugfs_create_file("ctrl", 0400, hdmi->debugfs_dir, hdmi, + &dw_hdmi_ctrl_fops); + + debugfs_create_file("phy", 0400, hdmi->debugfs_dir, hdmi, + &dw_hdmi_phy_fops); +} + +static void dw_hdmi_register_hdcp(struct device *dev, struct dw_hdmi *hdmi, + u32 val, bool hdcp1x_enable) +{ + struct dw_hdcp hdmi_hdcp = { + .hdmi = hdmi, + .write = hdmi_writeb, + .read = hdmi_readb, + .regs = hdmi->regs, + .reg_io_width = val, + .enable = hdcp1x_enable, + }; + struct platform_device_info hdcp_device_info = { + .parent = dev, + .id = PLATFORM_DEVID_AUTO, + .res = NULL, + .num_res = 0, + .name = DW_HDCP_DRIVER_NAME, + .data = &hdmi_hdcp, + .size_data = sizeof(hdmi_hdcp), + .dma_mask = DMA_BIT_MASK(32), + }; + + hdmi->hdcp_dev = platform_device_register_full(&hdcp_device_info); + if (IS_ERR(hdmi->hdcp_dev)) + dev_err(dev, "failed to register hdcp!\n"); + else + hdmi->hdcp = hdmi->hdcp_dev->dev.platform_data; +} + +static const struct regmap_config hdmi_regmap_8bit_config = { + .reg_bits = 32, + .val_bits = 8, + .reg_stride = 1, + .max_register = HDMI_I2CM_MAX_REG, +}; + +static const struct regmap_config hdmi_regmap_32bit_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = HDMI_I2CM_MAX_REG << 2, +}; + +static void dw_hdmi_reg_initial(struct dw_hdmi *hdmi) +{ + if (hdmi_readb(hdmi, HDMI_IH_MUTE)) { + initialize_hdmi_ih_mutes(hdmi); + hdmi_writeb(hdmi, HDMI_PHY_I2CM_INT_ADDR_DONE_POL, + HDMI_PHY_I2CM_INT_ADDR); + + hdmi_writeb(hdmi, + HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL | + HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL, + HDMI_PHY_I2CM_CTLINT_ADDR); + + hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE, + HDMI_PHY_POL0); + hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0); + hdmi_writeb(hdmi, + ~(HDMI_IH_PHY_STAT0_HPD | + HDMI_IH_PHY_STAT0_RX_SENSE), + HDMI_IH_MUTE_PHY_STAT0); + } +} + +static void dw_hdmi_init_hw(struct dw_hdmi *hdmi) +{ + if (!hdmi) { + printk("Hdmi has not been initialized\n"); + return; + } + + mutex_lock(&hdmi->mutex); + dw_hdmi_reg_initial(hdmi); + /* + * Reset HDMI DDC I2C master controller and mute I2CM interrupts. + * Even if we are using a separate i2c adapter doing this doesn't + * hurt. + */ + if (hdmi->i2c) + dw_hdmi_i2c_init(hdmi); + if (hdmi->irq) + enable_irq(hdmi->irq); + if (hdmi->phy.ops->setup_hpd) + hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data); + + /* + * HDMI status maybe incorrect in the following condition: + * HDMI plug in -> system sleep -> HDMI plug out -> system wake up. + * At this time, cat /sys/class/drm/card 0-HDMI-A-1/status is connected. + * There is no hpd interrupt, because HDMI is powerdown during suspend. + * So we need check the current HDMI status in this case. + */ + if (hdmi->connector.status == connector_status_connected) + if (hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data) == + connector_status_disconnected) { + hdmi->hpd_state = false; + mod_delayed_work(hdmi->workqueue, &hdmi->work, + msecs_to_jiffies(20)); + } + mutex_unlock(&hdmi->mutex); +} + +/* ----------------------------------------------------------------------------- + * Probe/remove API, used from platforms based on the DRM bridge API. + */ + +static int dw_hdmi_parse_dt(struct dw_hdmi *hdmi) +{ + struct device_node *endpoint; + struct device_node *remote; + + if (!hdmi->plat_data->output_port) + return 0; + + endpoint = of_graph_get_endpoint_by_regs( + hdmi->dev->of_node, hdmi->plat_data->output_port, -1); + if (!endpoint) { + /* + * On platforms whose bindings don't make the output port + * mandatory (such as Rockchip) the plat_data->output_port + * field isn't set, so it's safe to make this a fatal error. + */ + dev_err(hdmi->dev, "Missing endpoint in port@%u\n", + hdmi->plat_data->output_port); + return -ENODEV; + } + + remote = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + if (!remote) { + dev_err(hdmi->dev, "Endpoint in port@%u unconnected\n", + hdmi->plat_data->output_port); + return -ENODEV; + } + + if (!of_device_is_available(remote)) { + dev_err(hdmi->dev, "port@%u remote device is disabled\n", + hdmi->plat_data->output_port); + of_node_put(remote); + return -ENODEV; + } + + hdmi->next_bridge = of_drm_find_bridge(remote); + of_node_put(remote); + if (!hdmi->next_bridge) + return -EPROBE_DEFER; + + return 0; +} + +void dw_hdmi_enable_video(struct dw_hdmi *hdmi) +{ + dev_info(hdmi->dev, "%s", __func__); + hdmi_writeb(hdmi, HDMI_FC_GCP_CLEAR_AVMUTE, HDMI_FC_GCP); +} +void dw_hdmi_disable_video(struct dw_hdmi *hdmi) +{ + dev_info(hdmi->dev, "%s", __func__); + hdmi_writeb(hdmi, HDMI_FC_GCP_SET_AVMUTE, HDMI_FC_GCP); +} + +struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, + const struct dw_hdmi_plat_data *plat_data) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct platform_device_info pdevinfo; + struct device_node *ddc_node; + struct dw_hdmi_cec_data cec; + struct dw_hdmi *hdmi; + struct resource *iores = NULL; + int irq; + int ret; + u32 val = 1; + u8 prod_id0; + u8 prod_id1; + u8 config0; + u8 config3; + bool hdcp1x_enable = false; + + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return ERR_PTR(-ENOMEM); + + hdmi->plat_data = plat_data; + hdmi->dev = dev; + hdmi->sample_rate = 48000; + hdmi->disabled = true; + hdmi->rxsense = true; + hdmi->phy_mask = (u8) ~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE); + hdmi->mc_clkdis = 0x7f; + hdmi->last_connector_result = connector_status_disconnected; + + mutex_init(&hdmi->mutex); + mutex_init(&hdmi->audio_mutex); + mutex_init(&hdmi->cec_notifier_mutex); + spin_lock_init(&hdmi->audio_lock); + + ret = dw_hdmi_parse_dt(hdmi); + if (ret < 0) + return ERR_PTR(ret); + + ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0); + if (ddc_node) { + hdmi->ddc = of_get_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!hdmi->ddc) { + dev_dbg(hdmi->dev, "failed to read ddc node\n"); + return ERR_PTR(-EPROBE_DEFER); + } + + } else { + dev_dbg(hdmi->dev, "no ddc property found\n"); + } + + ret = device_property_read_u32(&pdev->dev, "eswin-plat", + &hdmi->eswin_plat); + if (0 != ret) { + dev_warn(&pdev->dev, "Failed to get eswin platform\n"); + hdmi->eswin_plat = 0; + } + dev_info(hdmi->dev, "eswin platform:%d\n", hdmi->eswin_plat); + + if (!plat_data->regm) { + const struct regmap_config *reg_config; + + of_property_read_u32(np, "reg-io-width", &val); + switch (val) { + case 4: + reg_config = &hdmi_regmap_32bit_config; + hdmi->reg_shift = 2; + break; + case 1: + reg_config = &hdmi_regmap_8bit_config; + break; + default: + dev_err(dev, "reg-io-width must be 1 or 4\n"); + return ERR_PTR(-EINVAL); + } + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdmi->regs = devm_ioremap_resource(dev, iores); + if (IS_ERR(hdmi->regs)) { + ret = PTR_ERR(hdmi->regs); + goto err_res; + } + + hdmi->regm = devm_regmap_init_mmio(dev, hdmi->regs, reg_config); + if (IS_ERR(hdmi->regm)) { + dev_err(dev, "Failed to configure regmap\n"); + ret = PTR_ERR(hdmi->regm); + goto err_res; + } + } else { + hdmi->regm = plat_data->regm; + } + + hdmi->isfr_clk = devm_clk_get(hdmi->dev, "isfr"); + if (IS_ERR(hdmi->isfr_clk)) { + ret = PTR_ERR(hdmi->isfr_clk); + dev_err(hdmi->dev, "Unable to get HDMI isfr clk: %d\n", ret); + goto err_res; + } + + ret = clk_prepare_enable(hdmi->isfr_clk); + if (ret) { + dev_err(hdmi->dev, "Cannot enable HDMI isfr clock: %d\n", ret); + goto err_res; + } + + hdmi->iahb_clk = devm_clk_get(hdmi->dev, "iahb"); + if (IS_ERR(hdmi->iahb_clk)) { + ret = PTR_ERR(hdmi->iahb_clk); + dev_err(hdmi->dev, "Unable to get HDMI iahb clk: %d\n", ret); + goto err_isfr; + } + + ret = clk_prepare_enable(hdmi->iahb_clk); + if (ret) { + dev_err(hdmi->dev, "Cannot enable HDMI iahb clock: %d\n", ret); + goto err_isfr; + } + + hdmi->cec_clk = devm_clk_get(hdmi->dev, "cec"); + if (PTR_ERR(hdmi->cec_clk) == -ENOENT) { + hdmi->cec_clk = NULL; + } else if (IS_ERR(hdmi->cec_clk)) { + ret = PTR_ERR(hdmi->cec_clk); + if (ret != -EPROBE_DEFER) + dev_err(hdmi->dev, "Cannot get HDMI cec clock: %d\n", + ret); + + hdmi->cec_clk = NULL; + goto err_iahb; + } else { + ret = clk_prepare_enable(hdmi->cec_clk); + if (ret) { + dev_err(hdmi->dev, "Cannot enable HDMI cec clock: %d\n", + ret); + goto err_iahb; + } + } + + /* hdmi prstn reset */ + hdmi->rst_hdmi_prstn = + devm_reset_control_get_optional(hdmi->dev, "prstn"); + if (IS_ERR_OR_NULL(hdmi->rst_hdmi_prstn)) { + dev_err(hdmi->dev, "Failed to get hdmi prstn reset handle\n"); + ret = -EFAULT; + goto err_iahb; + } + + /* hdmi phyctl reset */ + hdmi->rst_hdmi_phyrstn = + devm_reset_control_get_optional(hdmi->dev, "phyrstn"); + if (IS_ERR_OR_NULL(hdmi->rst_hdmi_phyrstn)) { + dev_err(hdmi->dev, "Failed to get hdmi phyrstn reset handle\n"); + ret = -EFAULT; + goto err_iahb; + } + + /* hdmi rstn reset */ + hdmi->rst_hdmi_rstn = + devm_reset_control_get_optional(hdmi->dev, "rstn"); + if (IS_ERR_OR_NULL(hdmi->rst_hdmi_rstn)) { + dev_err(hdmi->dev, "Failed to get hdmi rstn reset handle\n"); + ret = -EFAULT; + goto err_iahb; + } + + if (hdmi->rst_hdmi_prstn) { + ret = reset_control_reset(hdmi->rst_hdmi_prstn); + WARN_ON(0 != ret); + } + + if (hdmi->rst_hdmi_phyrstn) { + ret = reset_control_reset(hdmi->rst_hdmi_phyrstn); + WARN_ON(0 != ret); + } + + if (hdmi->rst_hdmi_rstn) { + ret = reset_control_reset(hdmi->rst_hdmi_rstn); + WARN_ON(0 != ret); + } + + /* Product and revision IDs */ + hdmi->version = (hdmi_readb(hdmi, HDMI_DESIGN_ID) << 8) | + (hdmi_readb(hdmi, HDMI_REVISION_ID) << 0); + prod_id0 = hdmi_readb(hdmi, HDMI_PRODUCT_ID0); + prod_id1 = hdmi_readb(hdmi, HDMI_PRODUCT_ID1); + + if (prod_id0 != HDMI_PRODUCT_ID0_HDMI_TX || + (prod_id1 & ~HDMI_PRODUCT_ID1_HDCP) != HDMI_PRODUCT_ID1_HDMI_TX) { + dev_err(dev, "Unsupported HDMI controller (%04x:%02x:%02x)\n", + hdmi->version, prod_id0, prod_id1); + ret = -ENODEV; + goto err_iahb; + } + + ret = dw_hdmi_detect_phy(hdmi); + if (ret < 0) + goto err_iahb; + + dev_info(dev, "Detected HDMI TX controller v%x.%03x %s HDCP (%s)\n", + hdmi->version >> 12, hdmi->version & 0xfff, + prod_id1 & HDMI_PRODUCT_ID1_HDCP ? "with" : "without", + hdmi->phy.name); + + init_hpd_work(hdmi); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto err_iahb; + } + + ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq, dw_hdmi_irq, + IRQF_SHARED, dev_name(dev), hdmi); + if (ret) + goto err_iahb; + + /* + * To prevent overflows in HDMI_IH_FC_STAT2, set the clk regenerator + * N and cts values before enabling phy + */ + hdmi_init_clk_regenerator(hdmi); + + /* If DDC bus is not specified, try to register HDMI I2C bus */ + if (!hdmi->ddc) { + /* Look for (optional) stuff related to unwedging */ + hdmi->pinctrl = devm_pinctrl_get(dev); + if (!IS_ERR(hdmi->pinctrl)) { + hdmi->unwedge_state = + pinctrl_lookup_state(hdmi->pinctrl, "unwedge"); + hdmi->default_state = + pinctrl_lookup_state(hdmi->pinctrl, "default"); + + if (IS_ERR(hdmi->default_state) || + IS_ERR(hdmi->unwedge_state)) { + if (!IS_ERR(hdmi->unwedge_state)) + dev_warn( + dev, + "Unwedge requires default pinctrl\n"); + hdmi->default_state = NULL; + hdmi->unwedge_state = NULL; + } + } + + hdmi->ddc = dw_hdmi_i2c_adapter(hdmi); + if (IS_ERR(hdmi->ddc)) + hdmi->ddc = NULL; + + /* + * Read high and low time from device tree. If not available use + * the default timing scl clock rate is about 99.6KHz. + */ + if (of_property_read_u32(np, "ddc-i2c-scl-high-time-ns", + &hdmi->i2c->scl_high_ns)) + hdmi->i2c->scl_high_ns = 4708; + if (of_property_read_u32(np, "ddc-i2c-scl-low-time-ns", + &hdmi->i2c->scl_low_ns)) + hdmi->i2c->scl_low_ns = 4916; + } + + dw_hdmi_init_hw(hdmi); + hdmi->bridge.driver_private = hdmi; + hdmi->bridge.funcs = &dw_hdmi_bridge_funcs; + hdmi->bridge.ops = + DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; + hdmi->bridge.interlace_allowed = true; +#ifdef CONFIG_OF + hdmi->bridge.of_node = pdev->dev.of_node; +#endif + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.parent = dev; + pdevinfo.id = PLATFORM_DEVID_AUTO; + + config0 = hdmi_readb(hdmi, HDMI_CONFIG0_ID); + config3 = hdmi_readb(hdmi, HDMI_CONFIG3_ID); + + if (iores && config3 & HDMI_CONFIG3_AHBAUDDMA) { + struct dw_hdmi_audio_data audio; + + audio.phys = iores->start; + audio.base = hdmi->regs; + audio.irq = irq; + audio.hdmi = hdmi; + audio.get_eld = hdmi_audio_get_eld; + hdmi->enable_audio = dw_hdmi_ahb_audio_enable; + hdmi->disable_audio = dw_hdmi_ahb_audio_disable; + + pdevinfo.name = "dw-hdmi-ahb-audio"; + pdevinfo.data = &audio; + pdevinfo.size_data = sizeof(audio); + pdevinfo.dma_mask = DMA_BIT_MASK(32); + hdmi->audio = platform_device_register_full(&pdevinfo); + } else if (config0 & HDMI_CONFIG0_I2S) { + struct dw_hdmi_i2s_audio_data audio; + + audio.hdmi = hdmi; + audio.get_eld = hdmi_audio_get_eld; + audio.write = hdmi_writeb; + audio.read = hdmi_readb; + hdmi->enable_audio = dw_hdmi_i2s_audio_enable; + hdmi->disable_audio = dw_hdmi_i2s_audio_disable; + + pdevinfo.name = "dw-hdmi-i2s-audio"; + pdevinfo.data = &audio; + pdevinfo.size_data = sizeof(audio); + pdevinfo.dma_mask = DMA_BIT_MASK(32); + hdmi->audio = platform_device_register_full(&pdevinfo); + } + + if (!plat_data->disable_cec && (config0 & HDMI_CONFIG0_CEC)) { + cec.hdmi = hdmi; + cec.ops = &dw_hdmi_cec_ops; + cec.irq = irq; + + pdevinfo.name = "dw-hdmi-cec"; + pdevinfo.data = &cec; + pdevinfo.size_data = sizeof(cec); + pdevinfo.dma_mask = 0; + + hdmi->cec = platform_device_register_full(&pdevinfo); + } + + drm_bridge_add(&hdmi->bridge); + + dw_hdmi_register_debugfs(dev, hdmi); + + if (of_property_read_bool(np, "hdcp1x-enable")) + hdcp1x_enable = true; + dw_hdmi_register_hdcp(dev, hdmi, val, hdcp1x_enable); + + return hdmi; + +err_iahb: + if (hdmi->i2c) + i2c_del_adapter(&hdmi->i2c->adap); + clk_disable_unprepare(hdmi->iahb_clk); + if (hdmi->cec_clk) + clk_disable_unprepare(hdmi->cec_clk); +err_isfr: + clk_disable_unprepare(hdmi->isfr_clk); +err_res: + i2c_put_adapter(hdmi->ddc); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(dw_hdmi_probe); + +void dw_hdmi_remove(struct dw_hdmi *hdmi) +{ + int ret; + + drm_bridge_remove(&hdmi->bridge); + + if (hdmi->irq) + disable_irq(hdmi->irq); + + cancel_delayed_work(&hdmi->work); + flush_workqueue(hdmi->workqueue); + destroy_workqueue(hdmi->workqueue); + + if (hdmi->audio && !IS_ERR(hdmi->audio)) + platform_device_unregister(hdmi->audio); + if (!IS_ERR(hdmi->cec)) + platform_device_unregister(hdmi->cec); + if (hdmi->hdcp_dev && !IS_ERR(hdmi->hdcp_dev)) + platform_device_unregister(hdmi->hdcp_dev); + + /* Disable all interrupts */ + hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); + + dw_hdmi_destroy_properties(hdmi); + hdmi->connector.funcs->destroy(&hdmi->connector); + + clk_disable_unprepare(hdmi->iahb_clk); + clk_disable_unprepare(hdmi->isfr_clk); + clk_disable_unprepare(hdmi->cec_clk); + + if (hdmi->i2c) + i2c_del_adapter(&hdmi->i2c->adap); + else + i2c_put_adapter(hdmi->ddc); + + debugfs_remove_recursive(hdmi->debugfs_dir); + + if (hdmi->rst_hdmi_prstn) { + ret = reset_control_assert(hdmi->rst_hdmi_prstn); + WARN_ON(0 != ret); + } + + if (hdmi->rst_hdmi_phyrstn) { + ret = reset_control_assert(hdmi->rst_hdmi_phyrstn); + WARN_ON(0 != ret); + } + + if (hdmi->rst_hdmi_rstn) { + ret = reset_control_assert(hdmi->rst_hdmi_rstn); + WARN_ON(0 != ret); + } +} +EXPORT_SYMBOL_GPL(dw_hdmi_remove); + +/* ----------------------------------------------------------------------------- + * Bind/unbind API, used from platforms based on the component framework. + */ +struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev, + struct drm_encoder *encoder, + const struct dw_hdmi_plat_data *plat_data) +{ + struct dw_hdmi *hdmi; + int ret; + + hdmi = dw_hdmi_probe(pdev, plat_data); + if (IS_ERR(hdmi)) + return hdmi; + + ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, 0); + if (ret) { + dw_hdmi_remove(hdmi); + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ERR_PTR(ret); + } + + return hdmi; +} +EXPORT_SYMBOL_GPL(dw_hdmi_bind); + +void dw_hdmi_unbind(struct dw_hdmi *hdmi) +{ + dw_hdmi_remove(hdmi); +} +EXPORT_SYMBOL_GPL(dw_hdmi_unbind); + +void dw_hdmi_suspend(struct dw_hdmi *hdmi) +{ + if (!hdmi) { + dev_warn(hdmi->dev, "HDMI has not been initialized\n"); + return; + } + + mutex_lock(&hdmi->mutex); + + /* + * When system shutdown, hdmi should be disabled. + * When system suspend, dw_hdmi_bridge_disable will disable hdmi first. + * To prevent duplicate operation, we should determine whether hdmi + * has been disabled. + */ + if (!hdmi->disabled) { + hdmi->disabled = true; + dw_hdmi_update_power(hdmi); + dw_hdmi_update_phy_mask(hdmi); + } + mutex_unlock(&hdmi->mutex); + + if (hdmi->irq) + disable_irq(hdmi->irq); + cancel_delayed_work(&hdmi->work); + flush_workqueue(hdmi->workqueue); + pinctrl_pm_select_sleep_state(hdmi->dev); +} +EXPORT_SYMBOL_GPL(dw_hdmi_suspend); + +void dw_hdmi_resume(struct dw_hdmi *hdmi) +{ + dw_hdmi_init_hw(hdmi); +} +EXPORT_SYMBOL_GPL(dw_hdmi_resume); + +MODULE_DESCRIPTION("DW HDMI transmitter driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:dw-hdmi"); diff --git a/drivers/gpu/drm/eswin/dw_hdmi.h b/drivers/gpu/drm/eswin/dw_hdmi.h new file mode 100644 index 000000000000..6281fd8bfc53 --- /dev/null +++ b/drivers/gpu/drm/eswin/dw_hdmi.h @@ -0,0 +1,1408 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. + */ + +#ifndef __DW_HDMI_H__ +#define __DW_HDMI_H__ + +#include +#include + +extern struct platform_driver dw_hdmi_eswin_pltfm_driver; +extern struct platform_driver snd_dw_hdmi_driver; +extern struct platform_driver dw_hdmi_cec_driver; +extern struct platform_driver dw_hdmi_hdcp_driver; +extern struct platform_driver dw_hdmi_hdcp2_driver; + +/* Identification Registers */ +#define HDMI_DESIGN_ID 0x0000 +#define HDMI_REVISION_ID 0x0001 +#define HDMI_PRODUCT_ID0 0x0002 +#define HDMI_PRODUCT_ID1 0x0003 +#define HDMI_CONFIG0_ID 0x0004 +#define HDMI_CONFIG1_ID 0x0005 +#define HDMI_CONFIG2_ID 0x0006 +#define HDMI_CONFIG3_ID 0x0007 + +/* Interrupt Registers */ +#define HDMI_IH_FC_STAT0 0x0100 +#define HDMI_IH_FC_STAT1 0x0101 +#define HDMI_IH_FC_STAT2 0x0102 +#define HDMI_IH_AS_STAT0 0x0103 +#define HDMI_IH_PHY_STAT0 0x0104 +#define HDMI_IH_I2CM_STAT0 0x0105 +#define HDMI_IH_CEC_STAT0 0x0106 +#define HDMI_IH_VP_STAT0 0x0107 +#define HDMI_IH_I2CMPHY_STAT0 0x0108 +#define HDMI_IH_AHBDMAAUD_STAT0 0x0109 + +#define HDMI_IH_MUTE_FC_STAT0 0x0180 +#define HDMI_IH_MUTE_FC_STAT1 0x0181 +#define HDMI_IH_MUTE_FC_STAT2 0x0182 +#define HDMI_IH_MUTE_AS_STAT0 0x0183 +#define HDMI_IH_MUTE_PHY_STAT0 0x0184 +#define HDMI_IH_MUTE_I2CM_STAT0 0x0185 +#define HDMI_IH_MUTE_CEC_STAT0 0x0186 +#define HDMI_IH_MUTE_VP_STAT0 0x0187 +#define HDMI_IH_MUTE_I2CMPHY_STAT0 0x0188 +#define HDMI_IH_MUTE_AHBDMAAUD_STAT0 0x0189 +#define HDMI_IH_MUTE 0x01FF + +/* Video Sample Registers */ +#define HDMI_TX_INVID0 0x0200 +#define HDMI_TX_INSTUFFING 0x0201 +#define HDMI_TX_GYDATA0 0x0202 +#define HDMI_TX_GYDATA1 0x0203 +#define HDMI_TX_RCRDATA0 0x0204 +#define HDMI_TX_RCRDATA1 0x0205 +#define HDMI_TX_BCBDATA0 0x0206 +#define HDMI_TX_BCBDATA1 0x0207 + +/* Video Packetizer Registers */ +#define HDMI_VP_STATUS 0x0800 +#define HDMI_VP_PR_CD 0x0801 +#define HDMI_VP_STUFF 0x0802 +#define HDMI_VP_REMAP 0x0803 +#define HDMI_VP_CONF 0x0804 +#define HDMI_VP_STAT 0x0805 +#define HDMI_VP_INT 0x0806 +#define HDMI_VP_MASK 0x0807 +#define HDMI_VP_POL 0x0808 + +/* Frame Composer Registers */ +#define HDMI_FC_INVIDCONF 0x1000 +#define HDMI_FC_INHACTV0 0x1001 +#define HDMI_FC_INHACTV1 0x1002 +#define HDMI_FC_INHBLANK0 0x1003 +#define HDMI_FC_INHBLANK1 0x1004 +#define HDMI_FC_INVACTV0 0x1005 +#define HDMI_FC_INVACTV1 0x1006 +#define HDMI_FC_INVBLANK 0x1007 +#define HDMI_FC_HSYNCINDELAY0 0x1008 +#define HDMI_FC_HSYNCINDELAY1 0x1009 +#define HDMI_FC_HSYNCINWIDTH0 0x100A +#define HDMI_FC_HSYNCINWIDTH1 0x100B +#define HDMI_FC_VSYNCINDELAY 0x100C +#define HDMI_FC_VSYNCINWIDTH 0x100D +#define HDMI_FC_INFREQ0 0x100E +#define HDMI_FC_INFREQ1 0x100F +#define HDMI_FC_INFREQ2 0x1010 +#define HDMI_FC_CTRLDUR 0x1011 +#define HDMI_FC_EXCTRLDUR 0x1012 +#define HDMI_FC_EXCTRLSPAC 0x1013 +#define HDMI_FC_CH0PREAM 0x1014 +#define HDMI_FC_CH1PREAM 0x1015 +#define HDMI_FC_CH2PREAM 0x1016 +#define HDMI_FC_AVICONF3 0x1017 +#define HDMI_FC_GCP 0x1018 +#define HDMI_FC_AVICONF0 0x1019 +#define HDMI_FC_AVICONF1 0x101A +#define HDMI_FC_AVICONF2 0x101B +#define HDMI_FC_AVIVID 0x101C +#define HDMI_FC_AVIETB0 0x101D +#define HDMI_FC_AVIETB1 0x101E +#define HDMI_FC_AVISBB0 0x101F +#define HDMI_FC_AVISBB1 0x1020 +#define HDMI_FC_AVIELB0 0x1021 +#define HDMI_FC_AVIELB1 0x1022 +#define HDMI_FC_AVISRB0 0x1023 +#define HDMI_FC_AVISRB1 0x1024 +#define HDMI_FC_AUDICONF0 0x1025 +#define HDMI_FC_AUDICONF1 0x1026 +#define HDMI_FC_AUDICONF2 0x1027 +#define HDMI_FC_AUDICONF3 0x1028 +#define HDMI_FC_VSDIEEEID0 0x1029 +#define HDMI_FC_VSDSIZE 0x102A +#define HDMI_FC_VSDIEEEID1 0x1030 +#define HDMI_FC_VSDIEEEID2 0x1031 +#define HDMI_FC_VSDPAYLOAD0 0x1032 +#define HDMI_FC_VSDPAYLOAD1 0x1033 +#define HDMI_FC_VSDPAYLOAD2 0x1034 +#define HDMI_FC_VSDPAYLOAD3 0x1035 +#define HDMI_FC_VSDPAYLOAD4 0x1036 +#define HDMI_FC_VSDPAYLOAD5 0x1037 +#define HDMI_FC_VSDPAYLOAD6 0x1038 +#define HDMI_FC_VSDPAYLOAD7 0x1039 +#define HDMI_FC_VSDPAYLOAD8 0x103A +#define HDMI_FC_VSDPAYLOAD9 0x103B +#define HDMI_FC_VSDPAYLOAD10 0x103C +#define HDMI_FC_VSDPAYLOAD11 0x103D +#define HDMI_FC_VSDPAYLOAD12 0x103E +#define HDMI_FC_VSDPAYLOAD13 0x103F +#define HDMI_FC_VSDPAYLOAD14 0x1040 +#define HDMI_FC_VSDPAYLOAD15 0x1041 +#define HDMI_FC_VSDPAYLOAD16 0x1042 +#define HDMI_FC_VSDPAYLOAD17 0x1043 +#define HDMI_FC_VSDPAYLOAD18 0x1044 +#define HDMI_FC_VSDPAYLOAD19 0x1045 +#define HDMI_FC_VSDPAYLOAD20 0x1046 +#define HDMI_FC_VSDPAYLOAD21 0x1047 +#define HDMI_FC_VSDPAYLOAD22 0x1048 +#define HDMI_FC_VSDPAYLOAD23 0x1049 +#define HDMI_FC_SPDVENDORNAME0 0x104A +#define HDMI_FC_SPDVENDORNAME1 0x104B +#define HDMI_FC_SPDVENDORNAME2 0x104C +#define HDMI_FC_SPDVENDORNAME3 0x104D +#define HDMI_FC_SPDVENDORNAME4 0x104E +#define HDMI_FC_SPDVENDORNAME5 0x104F +#define HDMI_FC_SPDVENDORNAME6 0x1050 +#define HDMI_FC_SPDVENDORNAME7 0x1051 +#define HDMI_FC_SDPPRODUCTNAME0 0x1052 +#define HDMI_FC_SDPPRODUCTNAME1 0x1053 +#define HDMI_FC_SDPPRODUCTNAME2 0x1054 +#define HDMI_FC_SDPPRODUCTNAME3 0x1055 +#define HDMI_FC_SDPPRODUCTNAME4 0x1056 +#define HDMI_FC_SDPPRODUCTNAME5 0x1057 +#define HDMI_FC_SDPPRODUCTNAME6 0x1058 +#define HDMI_FC_SDPPRODUCTNAME7 0x1059 +#define HDMI_FC_SDPPRODUCTNAME8 0x105A +#define HDMI_FC_SDPPRODUCTNAME9 0x105B +#define HDMI_FC_SDPPRODUCTNAME10 0x105C +#define HDMI_FC_SDPPRODUCTNAME11 0x105D +#define HDMI_FC_SDPPRODUCTNAME12 0x105E +#define HDMI_FC_SDPPRODUCTNAME13 0x105F +#define HDMI_FC_SDPPRODUCTNAME14 0x1060 +#define HDMI_FC_SPDPRODUCTNAME15 0x1061 +#define HDMI_FC_SPDDEVICEINF 0x1062 +#define HDMI_FC_AUDSCONF 0x1063 +#define HDMI_FC_AUDSSTAT 0x1064 +#define HDMI_FC_AUDSCHNLS7 0x106e +#define HDMI_FC_AUDSCHNLS8 0x106f +#define HDMI_FC_DATACH0FILL 0x1070 +#define HDMI_FC_DATACH1FILL 0x1071 +#define HDMI_FC_DATACH2FILL 0x1072 +#define HDMI_FC_CTRLQHIGH 0x1073 +#define HDMI_FC_CTRLQLOW 0x1074 +#define HDMI_FC_ACP0 0x1075 +#define HDMI_FC_ACP28 0x1076 +#define HDMI_FC_ACP27 0x1077 +#define HDMI_FC_ACP26 0x1078 +#define HDMI_FC_ACP25 0x1079 +#define HDMI_FC_ACP24 0x107A +#define HDMI_FC_ACP23 0x107B +#define HDMI_FC_ACP22 0x107C +#define HDMI_FC_ACP21 0x107D +#define HDMI_FC_ACP20 0x107E +#define HDMI_FC_ACP19 0x107F +#define HDMI_FC_ACP18 0x1080 +#define HDMI_FC_ACP17 0x1081 +#define HDMI_FC_ACP16 0x1082 +#define HDMI_FC_ACP15 0x1083 +#define HDMI_FC_ACP14 0x1084 +#define HDMI_FC_ACP13 0x1085 +#define HDMI_FC_ACP12 0x1086 +#define HDMI_FC_ACP11 0x1087 +#define HDMI_FC_ACP10 0x1088 +#define HDMI_FC_ACP9 0x1089 +#define HDMI_FC_ACP8 0x108A +#define HDMI_FC_ACP7 0x108B +#define HDMI_FC_ACP6 0x108C +#define HDMI_FC_ACP5 0x108D +#define HDMI_FC_ACP4 0x108E +#define HDMI_FC_ACP3 0x108F +#define HDMI_FC_ACP2 0x1090 +#define HDMI_FC_ACP1 0x1091 +#define HDMI_FC_ISCR1_0 0x1092 +#define HDMI_FC_ISCR1_16 0x1093 +#define HDMI_FC_ISCR1_15 0x1094 +#define HDMI_FC_ISCR1_14 0x1095 +#define HDMI_FC_ISCR1_13 0x1096 +#define HDMI_FC_ISCR1_12 0x1097 +#define HDMI_FC_ISCR1_11 0x1098 +#define HDMI_FC_ISCR1_10 0x1099 +#define HDMI_FC_ISCR1_9 0x109A +#define HDMI_FC_ISCR1_8 0x109B +#define HDMI_FC_ISCR1_7 0x109C +#define HDMI_FC_ISCR1_6 0x109D +#define HDMI_FC_ISCR1_5 0x109E +#define HDMI_FC_ISCR1_4 0x109F +#define HDMI_FC_ISCR1_3 0x10A0 +#define HDMI_FC_ISCR1_2 0x10A1 +#define HDMI_FC_ISCR1_1 0x10A2 +#define HDMI_FC_ISCR2_15 0x10A3 +#define HDMI_FC_ISCR2_14 0x10A4 +#define HDMI_FC_ISCR2_13 0x10A5 +#define HDMI_FC_ISCR2_12 0x10A6 +#define HDMI_FC_ISCR2_11 0x10A7 +#define HDMI_FC_ISCR2_10 0x10A8 +#define HDMI_FC_ISCR2_9 0x10A9 +#define HDMI_FC_ISCR2_8 0x10AA +#define HDMI_FC_ISCR2_7 0x10AB +#define HDMI_FC_ISCR2_6 0x10AC +#define HDMI_FC_ISCR2_5 0x10AD +#define HDMI_FC_ISCR2_4 0x10AE +#define HDMI_FC_ISCR2_3 0x10AF +#define HDMI_FC_ISCR2_2 0x10B0 +#define HDMI_FC_ISCR2_1 0x10B1 +#define HDMI_FC_ISCR2_0 0x10B2 +#define HDMI_FC_DATAUTO0 0x10B3 +#define HDMI_FC_DATAUTO1 0x10B4 +#define HDMI_FC_DATAUTO2 0x10B5 +#define HDMI_FC_DATMAN 0x10B6 +#define HDMI_FC_DATAUTO3 0x10B7 +#define HDMI_FC_RDRB0 0x10B8 +#define HDMI_FC_RDRB1 0x10B9 +#define HDMI_FC_RDRB2 0x10BA +#define HDMI_FC_RDRB3 0x10BB +#define HDMI_FC_RDRB4 0x10BC +#define HDMI_FC_RDRB5 0x10BD +#define HDMI_FC_RDRB6 0x10BE +#define HDMI_FC_RDRB7 0x10BF +#define HDMI_FC_STAT0 0x10D0 +#define HDMI_FC_INT0 0x10D1 +#define HDMI_FC_MASK0 0x10D2 +#define HDMI_FC_POL0 0x10D3 +#define HDMI_FC_STAT1 0x10D4 +#define HDMI_FC_INT1 0x10D5 +#define HDMI_FC_MASK1 0x10D6 +#define HDMI_FC_POL1 0x10D7 +#define HDMI_FC_STAT2 0x10D8 +#define HDMI_FC_INT2 0x10D9 +#define HDMI_FC_MASK2 0x10DA +#define HDMI_FC_POL2 0x10DB +#define HDMI_FC_PRCONF 0x10E0 +#define HDMI_FC_SCRAMBLER_CTRL 0x10E1 +#define HDMI_FC_PACKET_TX_EN 0x10E3 + +#define HDMI_FC_GMD_STAT 0x1100 +#define HDMI_FC_GMD_EN 0x1101 +#define HDMI_FC_GMD_UP 0x1102 +#define HDMI_FC_GMD_CONF 0x1103 +#define HDMI_FC_GMD_HB 0x1104 +#define HDMI_FC_GMD_PB0 0x1105 +#define HDMI_FC_GMD_PB1 0x1106 +#define HDMI_FC_GMD_PB2 0x1107 +#define HDMI_FC_GMD_PB3 0x1108 +#define HDMI_FC_GMD_PB4 0x1109 +#define HDMI_FC_GMD_PB5 0x110A +#define HDMI_FC_GMD_PB6 0x110B +#define HDMI_FC_GMD_PB7 0x110C +#define HDMI_FC_GMD_PB8 0x110D +#define HDMI_FC_GMD_PB9 0x110E +#define HDMI_FC_GMD_PB10 0x110F +#define HDMI_FC_GMD_PB11 0x1110 +#define HDMI_FC_GMD_PB12 0x1111 +#define HDMI_FC_GMD_PB13 0x1112 +#define HDMI_FC_GMD_PB14 0x1113 +#define HDMI_FC_GMD_PB15 0x1114 +#define HDMI_FC_GMD_PB16 0x1115 +#define HDMI_FC_GMD_PB17 0x1116 +#define HDMI_FC_GMD_PB18 0x1117 +#define HDMI_FC_GMD_PB19 0x1118 +#define HDMI_FC_GMD_PB20 0x1119 +#define HDMI_FC_GMD_PB21 0x111A +#define HDMI_FC_GMD_PB22 0x111B +#define HDMI_FC_GMD_PB23 0x111C +#define HDMI_FC_GMD_PB24 0x111D +#define HDMI_FC_GMD_PB25 0x111E +#define HDMI_FC_GMD_PB26 0x111F +#define HDMI_FC_GMD_PB27 0x1120 + +#define HDMI_FC_DRM_UP 0x1167 +#define HDMI_FC_DRM_HB0 0x1168 +#define HDMI_FC_DRM_HB1 0x1169 +#define HDMI_FC_DRM_PB0 0x116A +#define HDMI_FC_DRM_PB1 0x116B +#define HDMI_FC_DRM_PB2 0x116C +#define HDMI_FC_DRM_PB3 0x116D +#define HDMI_FC_DRM_PB4 0x116E +#define HDMI_FC_DRM_PB5 0x116F +#define HDMI_FC_DRM_PB6 0x1170 +#define HDMI_FC_DRM_PB7 0x1171 +#define HDMI_FC_DRM_PB8 0x1172 +#define HDMI_FC_DRM_PB9 0x1173 +#define HDMI_FC_DRM_PB10 0x1174 +#define HDMI_FC_DRM_PB11 0x1175 +#define HDMI_FC_DRM_PB12 0x1176 +#define HDMI_FC_DRM_PB13 0x1177 +#define HDMI_FC_DRM_PB14 0x1178 +#define HDMI_FC_DRM_PB15 0x1179 +#define HDMI_FC_DRM_PB16 0x117A +#define HDMI_FC_DRM_PB17 0x117B +#define HDMI_FC_DRM_PB18 0x117C +#define HDMI_FC_DRM_PB19 0x117D +#define HDMI_FC_DRM_PB20 0x117E +#define HDMI_FC_DRM_PB21 0x117F +#define HDMI_FC_DRM_PB22 0x1180 +#define HDMI_FC_DRM_PB23 0x1181 +#define HDMI_FC_DRM_PB24 0x1182 +#define HDMI_FC_DRM_PB25 0x1183 +#define HDMI_FC_DRM_PB26 0x1184 + +#define HDMI_FC_DBGFORCE 0x1200 +#define HDMI_FC_DBGAUD0CH0 0x1201 +#define HDMI_FC_DBGAUD1CH0 0x1202 +#define HDMI_FC_DBGAUD2CH0 0x1203 +#define HDMI_FC_DBGAUD0CH1 0x1204 +#define HDMI_FC_DBGAUD1CH1 0x1205 +#define HDMI_FC_DBGAUD2CH1 0x1206 +#define HDMI_FC_DBGAUD0CH2 0x1207 +#define HDMI_FC_DBGAUD1CH2 0x1208 +#define HDMI_FC_DBGAUD2CH2 0x1209 +#define HDMI_FC_DBGAUD0CH3 0x120A +#define HDMI_FC_DBGAUD1CH3 0x120B +#define HDMI_FC_DBGAUD2CH3 0x120C +#define HDMI_FC_DBGAUD0CH4 0x120D +#define HDMI_FC_DBGAUD1CH4 0x120E +#define HDMI_FC_DBGAUD2CH4 0x120F +#define HDMI_FC_DBGAUD0CH5 0x1210 +#define HDMI_FC_DBGAUD1CH5 0x1211 +#define HDMI_FC_DBGAUD2CH5 0x1212 +#define HDMI_FC_DBGAUD0CH6 0x1213 +#define HDMI_FC_DBGAUD1CH6 0x1214 +#define HDMI_FC_DBGAUD2CH6 0x1215 +#define HDMI_FC_DBGAUD0CH7 0x1216 +#define HDMI_FC_DBGAUD1CH7 0x1217 +#define HDMI_FC_DBGAUD2CH7 0x1218 +#define HDMI_FC_DBGTMDS0 0x1219 +#define HDMI_FC_DBGTMDS1 0x121A +#define HDMI_FC_DBGTMDS2 0x121B + +/* HDMI Source PHY Registers */ +#define HDMI_PHY_CONF0 0x3000 +#define HDMI_PHY_TST0 0x3001 +#define HDMI_PHY_TST1 0x3002 +#define HDMI_PHY_TST2 0x3003 +#define HDMI_PHY_STAT0 0x3004 +#define HDMI_PHY_INT0 0x3005 +#define HDMI_PHY_MASK0 0x3006 +#define HDMI_PHY_POL0 0x3007 + +/* HDMI Master PHY Registers */ +#define HDMI_PHY_I2CM_SLAVE_ADDR 0x3020 +#define HDMI_PHY_I2CM_ADDRESS_ADDR 0x3021 +#define HDMI_PHY_I2CM_DATAO_1_ADDR 0x3022 +#define HDMI_PHY_I2CM_DATAO_0_ADDR 0x3023 +#define HDMI_PHY_I2CM_DATAI_1_ADDR 0x3024 +#define HDMI_PHY_I2CM_DATAI_0_ADDR 0x3025 +#define HDMI_PHY_I2CM_OPERATION_ADDR 0x3026 +#define HDMI_PHY_I2CM_INT_ADDR 0x3027 +#define HDMI_PHY_I2CM_CTLINT_ADDR 0x3028 +#define HDMI_PHY_I2CM_DIV_ADDR 0x3029 +#define HDMI_PHY_I2CM_SOFTRSTZ_ADDR 0x302a +#define HDMI_PHY_I2CM_SS_SCL_HCNT_1_ADDR 0x302b +#define HDMI_PHY_I2CM_SS_SCL_HCNT_0_ADDR 0x302c +#define HDMI_PHY_I2CM_SS_SCL_LCNT_1_ADDR 0x302d +#define HDMI_PHY_I2CM_SS_SCL_LCNT_0_ADDR 0x302e +#define HDMI_PHY_I2CM_FS_SCL_HCNT_1_ADDR 0x302f +#define HDMI_PHY_I2CM_FS_SCL_HCNT_0_ADDR 0x3030 +#define HDMI_PHY_I2CM_FS_SCL_LCNT_1_ADDR 0x3031 +#define HDMI_PHY_I2CM_FS_SCL_LCNT_0_ADDR 0x3032 +#define HDMI_PHY_I2CM_SDA_HOLD 0x3033 +#define HDMI_JTAG_PHY_CONFIG 0x3034 + +/* Audio Sampler Registers */ +#define HDMI_AUD_CONF0 0x3100 +#define HDMI_AUD_CONF1 0x3101 +#define HDMI_AUD_INT 0x3102 +#define HDMI_AUD_CONF2 0x3103 +#define HDMI_AUD_INT1 0x3104 +#define HDMI_AUD_N1 0x3200 +#define HDMI_AUD_N2 0x3201 +#define HDMI_AUD_N3 0x3202 +#define HDMI_AUD_CTS1 0x3203 +#define HDMI_AUD_CTS2 0x3204 +#define HDMI_AUD_CTS3 0x3205 +#define HDMI_AUD_INPUTCLKFS 0x3206 +#define HDMI_AUD_SPDIFINT 0x3302 +#define HDMI_AUD_CONF0_HBR 0x3400 +#define HDMI_AUD_HBR_STATUS 0x3401 +#define HDMI_AUD_HBR_INT 0x3402 +#define HDMI_AUD_HBR_POL 0x3403 +#define HDMI_AUD_HBR_MASK 0x3404 + +/* + * Generic Parallel Audio Interface Registers + * Not used as GPAUD interface is not enabled in hw + */ +#define HDMI_GP_CONF0 0x3500 +#define HDMI_GP_CONF1 0x3501 +#define HDMI_GP_CONF2 0x3502 +#define HDMI_GP_MASK 0x3506 + +/* Audio DMA Registers */ +#define HDMI_AHB_DMA_CONF0 0x3600 +#define HDMI_AHB_DMA_START 0x3601 +#define HDMI_AHB_DMA_STOP 0x3602 +#define HDMI_AHB_DMA_THRSLD 0x3603 +#define HDMI_AHB_DMA_STRADDR0 0x3604 +#define HDMI_AHB_DMA_STRADDR1 0x3605 +#define HDMI_AHB_DMA_STRADDR2 0x3606 +#define HDMI_AHB_DMA_STRADDR3 0x3607 +#define HDMI_AHB_DMA_STPADDR0 0x3608 +#define HDMI_AHB_DMA_STPADDR1 0x3609 +#define HDMI_AHB_DMA_STPADDR2 0x360a +#define HDMI_AHB_DMA_STPADDR3 0x360b +#define HDMI_AHB_DMA_BSTADDR0 0x360c +#define HDMI_AHB_DMA_BSTADDR1 0x360d +#define HDMI_AHB_DMA_BSTADDR2 0x360e +#define HDMI_AHB_DMA_BSTADDR3 0x360f +#define HDMI_AHB_DMA_MBLENGTH0 0x3610 +#define HDMI_AHB_DMA_MBLENGTH1 0x3611 +#define HDMI_AHB_DMA_STAT 0x3612 +#define HDMI_AHB_DMA_INT 0x3613 +#define HDMI_AHB_DMA_MASK 0x3614 +#define HDMI_AHB_DMA_POL 0x3615 +#define HDMI_AHB_DMA_CONF1 0x3616 +#define HDMI_AHB_DMA_BUFFSTAT 0x3617 +#define HDMI_AHB_DMA_BUFFINT 0x3618 +#define HDMI_AHB_DMA_BUFFMASK 0x3619 +#define HDMI_AHB_DMA_BUFFPOL 0x361a + +/* Main Controller Registers */ +#define HDMI_MC_SFRDIV 0x4000 +#define HDMI_MC_CLKDIS 0x4001 +#define HDMI_MC_SWRSTZ 0x4002 +#define HDMI_MC_OPCTRL 0x4003 +#define HDMI_MC_FLOWCTRL 0x4004 +#define HDMI_MC_PHYRSTZ 0x4005 +#define HDMI_MC_LOCKONCLOCK 0x4006 +#define HDMI_MC_HEACPHY_RST 0x4007 + +/* Color Space Converter Registers */ +#define HDMI_CSC_CFG 0x4100 +#define HDMI_CSC_SCALE 0x4101 +#define HDMI_CSC_COEF_A1_MSB 0x4102 +#define HDMI_CSC_COEF_A1_LSB 0x4103 +#define HDMI_CSC_COEF_A2_MSB 0x4104 +#define HDMI_CSC_COEF_A2_LSB 0x4105 +#define HDMI_CSC_COEF_A3_MSB 0x4106 +#define HDMI_CSC_COEF_A3_LSB 0x4107 +#define HDMI_CSC_COEF_A4_MSB 0x4108 +#define HDMI_CSC_COEF_A4_LSB 0x4109 +#define HDMI_CSC_COEF_B1_MSB 0x410A +#define HDMI_CSC_COEF_B1_LSB 0x410B +#define HDMI_CSC_COEF_B2_MSB 0x410C +#define HDMI_CSC_COEF_B2_LSB 0x410D +#define HDMI_CSC_COEF_B3_MSB 0x410E +#define HDMI_CSC_COEF_B3_LSB 0x410F +#define HDMI_CSC_COEF_B4_MSB 0x4110 +#define HDMI_CSC_COEF_B4_LSB 0x4111 +#define HDMI_CSC_COEF_C1_MSB 0x4112 +#define HDMI_CSC_COEF_C1_LSB 0x4113 +#define HDMI_CSC_COEF_C2_MSB 0x4114 +#define HDMI_CSC_COEF_C2_LSB 0x4115 +#define HDMI_CSC_COEF_C3_MSB 0x4116 +#define HDMI_CSC_COEF_C3_LSB 0x4117 +#define HDMI_CSC_COEF_C4_MSB 0x4118 +#define HDMI_CSC_COEF_C4_LSB 0x4119 + +/* HDCP Encryption Engine Registers */ +#define HDMI_A_HDCPCFG0 0x5000 +#define HDMI_A_HDCPCFG1 0x5001 +#define HDMI_A_HDCPOBS0 0x5002 +#define HDMI_A_HDCPOBS1 0x5003 +#define HDMI_A_HDCPOBS2 0x5004 +#define HDMI_A_HDCPOBS3 0x5005 +#define HDMI_A_APIINTCLR 0x5006 +#define HDMI_A_APIINTSTAT 0x5007 +#define HDMI_A_APIINTMSK 0x5008 +#define HDMI_A_VIDPOLCFG 0x5009 +#define HDMI_A_OESSWCFG 0x500A +#define HDMI_A_TIMER1SETUP0 0x500B +#define HDMI_A_TIMER1SETUP1 0x500C +#define HDMI_A_TIMER2SETUP0 0x500D +#define HDMI_A_TIMER2SETUP1 0x500E +#define HDMI_A_100MSCFG 0x500F +#define HDMI_A_2SCFG0 0x5010 +#define HDMI_A_2SCFG1 0x5011 +#define HDMI_A_5SCFG0 0x5012 +#define HDMI_A_5SCFG1 0x5013 +#define HDMI_A_SRMVERLSB 0x5014 +#define HDMI_A_SRMVERMSB 0x5015 +#define HDMI_A_SRMCTRL 0x5016 +#define HDMI_A_SFRSETUP 0x5017 +#define HDMI_A_I2CHSETUP 0x5018 +#define HDMI_A_INTSETUP 0x5019 +#define HDMI_A_PRESETUP 0x501A +#define HDMI_A_SRM_BASE 0x5020 + +/* HDCP Registers */ +#define HDMI_HDCPREG_RMCTL 0x780e +#define HDMI_HDCPREG_RMSTS 0x780f +#define HDMI_HDCPREG_SEED0 0x7810 +#define HDMI_HDCPREG_SEED1 0x7811 +#define HDMI_HDCPREG_DPK0 0x7812 +#define HDMI_HDCPREG_DPK1 0x7813 +#define HDMI_HDCPREG_DPK2 0x7814 +#define HDMI_HDCPREG_DPK3 0x7815 +#define HDMI_HDCPREG_DPK4 0x7816 +#define HDMI_HDCPREG_DPK5 0x7817 +#define HDMI_HDCPREG_DPK6 0x7818 +#define HDMI_HDCP2REG_CTRL 0x7904 +#define HDMI_HDCP2REG_MASK 0x790c +#define HDMI_HDCP2REG_MUTE 0x7912 + +/* CEC Engine Registers */ +#define HDMI_CEC_CTRL 0x7D00 +#define HDMI_CEC_STAT 0x7D01 +#define HDMI_CEC_MASK 0x7D02 +#define HDMI_CEC_POLARITY 0x7D03 +#define HDMI_CEC_INT 0x7D04 +#define HDMI_CEC_ADDR_L 0x7D05 +#define HDMI_CEC_ADDR_H 0x7D06 +#define HDMI_CEC_TX_CNT 0x7D07 +#define HDMI_CEC_RX_CNT 0x7D08 +#define HDMI_CEC_TX_DATA0 0x7D10 +#define HDMI_CEC_TX_DATA1 0x7D11 +#define HDMI_CEC_TX_DATA2 0x7D12 +#define HDMI_CEC_TX_DATA3 0x7D13 +#define HDMI_CEC_TX_DATA4 0x7D14 +#define HDMI_CEC_TX_DATA5 0x7D15 +#define HDMI_CEC_TX_DATA6 0x7D16 +#define HDMI_CEC_TX_DATA7 0x7D17 +#define HDMI_CEC_TX_DATA8 0x7D18 +#define HDMI_CEC_TX_DATA9 0x7D19 +#define HDMI_CEC_TX_DATA10 0x7D1a +#define HDMI_CEC_TX_DATA11 0x7D1b +#define HDMI_CEC_TX_DATA12 0x7D1c +#define HDMI_CEC_TX_DATA13 0x7D1d +#define HDMI_CEC_TX_DATA14 0x7D1e +#define HDMI_CEC_TX_DATA15 0x7D1f +#define HDMI_CEC_RX_DATA0 0x7D20 +#define HDMI_CEC_RX_DATA1 0x7D21 +#define HDMI_CEC_RX_DATA2 0x7D22 +#define HDMI_CEC_RX_DATA3 0x7D23 +#define HDMI_CEC_RX_DATA4 0x7D24 +#define HDMI_CEC_RX_DATA5 0x7D25 +#define HDMI_CEC_RX_DATA6 0x7D26 +#define HDMI_CEC_RX_DATA7 0x7D27 +#define HDMI_CEC_RX_DATA8 0x7D28 +#define HDMI_CEC_RX_DATA9 0x7D29 +#define HDMI_CEC_RX_DATA10 0x7D2a +#define HDMI_CEC_RX_DATA11 0x7D2b +#define HDMI_CEC_RX_DATA12 0x7D2c +#define HDMI_CEC_RX_DATA13 0x7D2d +#define HDMI_CEC_RX_DATA14 0x7D2e +#define HDMI_CEC_RX_DATA15 0x7D2f +#define HDMI_CEC_LOCK 0x7D30 +#define HDMI_CEC_WKUPCTRL 0x7D31 + +/* I2C Master Registers (E-DDC) */ +#define HDMI_I2CM_SLAVE 0x7E00 +#define HDMI_I2CM_ADDRESS 0x7E01 +#define HDMI_I2CM_DATAO 0x7E02 +#define HDMI_I2CM_DATAI 0x7E03 +#define HDMI_I2CM_OPERATION 0x7E04 +#define HDMI_I2CM_INT 0x7E05 +#define HDMI_I2CM_CTLINT 0x7E06 +#define HDMI_I2CM_DIV 0x7E07 +#define HDMI_I2CM_SEGADDR 0x7E08 +#define HDMI_I2CM_SOFTRSTZ 0x7E09 +#define HDMI_I2CM_SEGPTR 0x7E0A +#define HDMI_I2CM_SS_SCL_HCNT_1_ADDR 0x7E0B +#define HDMI_I2CM_SS_SCL_HCNT_0_ADDR 0x7E0C +#define HDMI_I2CM_SS_SCL_LCNT_1_ADDR 0x7E0D +#define HDMI_I2CM_SS_SCL_LCNT_0_ADDR 0x7E0E +#define HDMI_I2CM_FS_SCL_HCNT_1_ADDR 0x7E0F +#define HDMI_I2CM_FS_SCL_HCNT_0_ADDR 0x7E10 +#define HDMI_I2CM_FS_SCL_LCNT_1_ADDR 0x7E11 +#define HDMI_I2CM_FS_SCL_LCNT_0_ADDR 0x7E12 +#define HDMI_I2CM_SDA_HOLD 0x7E13 +#define HDMI_I2CM_SCDC_READ_UPDATE_ON 0x7E14 + +#define HDMI_I2CM_MAX_REG 0x7E31 + +enum { + /* PRODUCT_ID0 field values */ + HDMI_PRODUCT_ID0_HDMI_TX = 0xa0, + + /* PRODUCT_ID1 field values */ + HDMI_PRODUCT_ID1_HDCP = 0xc0, + HDMI_PRODUCT_ID1_HDMI_RX = 0x02, + HDMI_PRODUCT_ID1_HDMI_TX = 0x01, + + /* CONFIG0_ID field values */ + HDMI_CONFIG0_I2S = 0x10, + HDMI_CONFIG0_CEC = 0x02, + + /* CONFIG1_ID field values */ + HDMI_CONFIG1_AHB = 0x01, + + /* CONFIG3_ID field values */ + HDMI_CONFIG3_AHBAUDDMA = 0x02, + HDMI_CONFIG3_GPAUD = 0x01, + + /* IH_FC_INT2 field values */ + HDMI_IH_FC_INT2_OVERFLOW_MASK = 0x03, + HDMI_IH_FC_INT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_IH_FC_INT2_HIGH_PRIORITY_OVERFLOW = 0x01, + + /* IH_FC_STAT2 field values */ + HDMI_IH_FC_STAT2_OVERFLOW_MASK = 0x03, + HDMI_IH_FC_STAT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_IH_FC_STAT2_HIGH_PRIORITY_OVERFLOW = 0x01, + + /* IH_PHY_STAT0 field values */ + HDMI_IH_PHY_STAT0_RX_SENSE3 = 0x20, + HDMI_IH_PHY_STAT0_RX_SENSE2 = 0x10, + HDMI_IH_PHY_STAT0_RX_SENSE1 = 0x8, + HDMI_IH_PHY_STAT0_RX_SENSE0 = 0x4, + HDMI_IH_PHY_STAT0_TX_PHY_LOCK = 0x2, + HDMI_IH_PHY_STAT0_HPD = 0x1, + + /* IH_I2CM_STAT0 and IH_MUTE_I2CM_STAT0 field values */ + HDMI_IH_I2CM_STAT0_DONE = 0x2, + HDMI_IH_I2CM_STAT0_ERROR = 0x1, + + /* IH_MUTE_I2CMPHY_STAT0 field values */ + HDMI_IH_MUTE_I2CMPHY_STAT0_I2CMPHYDONE = 0x2, + HDMI_IH_MUTE_I2CMPHY_STAT0_I2CMPHYERROR = 0x1, + + /* IH_AHBDMAAUD_STAT0 field values */ + HDMI_IH_AHBDMAAUD_STAT0_ERROR = 0x20, + HDMI_IH_AHBDMAAUD_STAT0_LOST = 0x10, + HDMI_IH_AHBDMAAUD_STAT0_RETRY = 0x08, + HDMI_IH_AHBDMAAUD_STAT0_DONE = 0x04, + HDMI_IH_AHBDMAAUD_STAT0_BUFFFULL = 0x02, + HDMI_IH_AHBDMAAUD_STAT0_BUFFEMPTY = 0x01, + + /* IH_MUTE_FC_STAT2 field values */ + HDMI_IH_MUTE_FC_STAT2_OVERFLOW_MASK = 0x03, + HDMI_IH_MUTE_FC_STAT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_IH_MUTE_FC_STAT2_HIGH_PRIORITY_OVERFLOW = 0x01, + + /* IH_MUTE_AHBDMAAUD_STAT0 field values */ + HDMI_IH_MUTE_AHBDMAAUD_STAT0_ERROR = 0x20, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_LOST = 0x10, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_RETRY = 0x08, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE = 0x04, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFFULL = 0x02, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFEMPTY = 0x01, + + /* IH_MUTE field values */ + HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT = 0x2, + HDMI_IH_MUTE_MUTE_ALL_INTERRUPT = 0x1, + + /* TX_INVID0 field values */ + HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_MASK = 0x80, + HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_ENABLE = 0x80, + HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE = 0x00, + HDMI_TX_INVID0_VIDEO_MAPPING_MASK = 0x1F, + HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET = 0, + + /* TX_INSTUFFING field values */ + HDMI_TX_INSTUFFING_BDBDATA_STUFFING_MASK = 0x4, + HDMI_TX_INSTUFFING_BDBDATA_STUFFING_ENABLE = 0x4, + HDMI_TX_INSTUFFING_BDBDATA_STUFFING_DISABLE = 0x0, + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_MASK = 0x2, + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_ENABLE = 0x2, + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_DISABLE = 0x0, + HDMI_TX_INSTUFFING_GYDATA_STUFFING_MASK = 0x1, + HDMI_TX_INSTUFFING_GYDATA_STUFFING_ENABLE = 0x1, + HDMI_TX_INSTUFFING_GYDATA_STUFFING_DISABLE = 0x0, + + /* VP_PR_CD field values */ + HDMI_VP_PR_CD_COLOR_DEPTH_MASK = 0xF0, + HDMI_VP_PR_CD_COLOR_DEPTH_OFFSET = 4, + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK = 0x0F, + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_OFFSET = 0, + + /* VP_STUFF field values */ + HDMI_VP_STUFF_IDEFAULT_PHASE_MASK = 0x20, + HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET = 5, + HDMI_VP_STUFF_IFIX_PP_TO_LAST_MASK = 0x10, + HDMI_VP_STUFF_IFIX_PP_TO_LAST_OFFSET = 4, + HDMI_VP_STUFF_ICX_GOTO_P0_ST_MASK = 0x8, + HDMI_VP_STUFF_ICX_GOTO_P0_ST_OFFSET = 3, + HDMI_VP_STUFF_YCC422_STUFFING_MASK = 0x4, + HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE = 0x4, + HDMI_VP_STUFF_YCC422_STUFFING_DIRECT_MODE = 0x0, + HDMI_VP_STUFF_PP_STUFFING_MASK = 0x2, + HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE = 0x2, + HDMI_VP_STUFF_PP_STUFFING_DIRECT_MODE = 0x0, + HDMI_VP_STUFF_PR_STUFFING_MASK = 0x1, + HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE = 0x1, + HDMI_VP_STUFF_PR_STUFFING_DIRECT_MODE = 0x0, + + /* VP_CONF field values */ + HDMI_VP_CONF_BYPASS_EN_MASK = 0x40, + HDMI_VP_CONF_BYPASS_EN_ENABLE = 0x40, + HDMI_VP_CONF_BYPASS_EN_DISABLE = 0x00, + HDMI_VP_CONF_PP_EN_ENMASK = 0x20, + HDMI_VP_CONF_PP_EN_ENABLE = 0x20, + HDMI_VP_CONF_PP_EN_DISABLE = 0x00, + HDMI_VP_CONF_PR_EN_MASK = 0x10, + HDMI_VP_CONF_PR_EN_ENABLE = 0x10, + HDMI_VP_CONF_PR_EN_DISABLE = 0x00, + HDMI_VP_CONF_YCC422_EN_MASK = 0x8, + HDMI_VP_CONF_YCC422_EN_ENABLE = 0x8, + HDMI_VP_CONF_YCC422_EN_DISABLE = 0x0, + HDMI_VP_CONF_BYPASS_SELECT_MASK = 0x4, + HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER = 0x4, + HDMI_VP_CONF_BYPASS_SELECT_PIX_REPEATER = 0x0, + HDMI_VP_CONF_OUTPUT_SELECTOR_MASK = 0x3, + HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS = 0x3, + HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422 = 0x1, + HDMI_VP_CONF_OUTPUT_SELECTOR_PP = 0x0, + + /* VP_REMAP field values */ + HDMI_VP_REMAP_MASK = 0x3, + HDMI_VP_REMAP_YCC422_24bit = 0x2, + HDMI_VP_REMAP_YCC422_20bit = 0x1, + HDMI_VP_REMAP_YCC422_16bit = 0x0, + + /* FC_INVIDCONF field values */ + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_MASK = 0x80, + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE = 0x80, + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE = 0x00, + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_MASK = 0x40, + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH = 0x40, + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW = 0x00, + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_MASK = 0x20, + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH = 0x20, + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW = 0x00, + HDMI_FC_INVIDCONF_DE_IN_POLARITY_MASK = 0x10, + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH = 0x10, + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_LOW = 0x00, + HDMI_FC_INVIDCONF_DVI_MODEZ_MASK = 0x8, + HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE = 0x8, + HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE = 0x0, + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_MASK = 0x2, + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH = 0x2, + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW = 0x0, + HDMI_FC_INVIDCONF_IN_I_P_MASK = 0x1, + HDMI_FC_INVIDCONF_IN_I_P_INTERLACED = 0x1, + HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE = 0x0, + + /* FC_AUDICONF0 field values */ + HDMI_FC_AUDICONF0_CC_OFFSET = 4, + HDMI_FC_AUDICONF0_CC_MASK = 0x70, + HDMI_FC_AUDICONF0_CT_OFFSET = 0, + HDMI_FC_AUDICONF0_CT_MASK = 0xF, + + /* FC_AUDICONF1 field values */ + HDMI_FC_AUDICONF1_SS_OFFSET = 4, + HDMI_FC_AUDICONF1_SS_MASK = 0x30, + HDMI_FC_AUDICONF1_SF_OFFSET = 0, + HDMI_FC_AUDICONF1_SF_MASK = 0x7, + + /* FC_AUDICONF3 field values */ + HDMI_FC_AUDICONF3_LFEPBL_OFFSET = 5, + HDMI_FC_AUDICONF3_LFEPBL_MASK = 0x60, + HDMI_FC_AUDICONF3_DM_INH_OFFSET = 4, + HDMI_FC_AUDICONF3_DM_INH_MASK = 0x10, + HDMI_FC_AUDICONF3_LSV_OFFSET = 0, + HDMI_FC_AUDICONF3_LSV_MASK = 0xF, + + /* FC_AUDSCHNLS0 field values */ + HDMI_FC_AUDSCHNLS0_CGMSA_OFFSET = 4, + HDMI_FC_AUDSCHNLS0_CGMSA_MASK = 0x30, + HDMI_FC_AUDSCHNLS0_COPYRIGHT_OFFSET = 0, + HDMI_FC_AUDSCHNLS0_COPYRIGHT_MASK = 0x01, + + /* FC_AUDSCHNLS3-6 field values */ + HDMI_FC_AUDSCHNLS3_OIEC_CH0_OFFSET = 0, + HDMI_FC_AUDSCHNLS3_OIEC_CH0_MASK = 0x0f, + HDMI_FC_AUDSCHNLS3_OIEC_CH1_OFFSET = 4, + HDMI_FC_AUDSCHNLS3_OIEC_CH1_MASK = 0xf0, + HDMI_FC_AUDSCHNLS4_OIEC_CH2_OFFSET = 0, + HDMI_FC_AUDSCHNLS4_OIEC_CH2_MASK = 0x0f, + HDMI_FC_AUDSCHNLS4_OIEC_CH3_OFFSET = 4, + HDMI_FC_AUDSCHNLS4_OIEC_CH3_MASK = 0xf0, + + HDMI_FC_AUDSCHNLS5_OIEC_CH0_OFFSET = 0, + HDMI_FC_AUDSCHNLS5_OIEC_CH0_MASK = 0x0f, + HDMI_FC_AUDSCHNLS5_OIEC_CH1_OFFSET = 4, + HDMI_FC_AUDSCHNLS5_OIEC_CH1_MASK = 0xf0, + HDMI_FC_AUDSCHNLS6_OIEC_CH2_OFFSET = 0, + HDMI_FC_AUDSCHNLS6_OIEC_CH2_MASK = 0x0f, + HDMI_FC_AUDSCHNLS6_OIEC_CH3_OFFSET = 4, + HDMI_FC_AUDSCHNLS6_OIEC_CH3_MASK = 0xf0, + + /* HDMI_FC_AUDSCHNLS7 field values */ + HDMI_FC_AUDSCHNLS7_ACCURACY_OFFSET = 4, + HDMI_FC_AUDSCHNLS7_ACCURACY_MASK = 0x30, + + /* HDMI_FC_AUDSCHNLS8 field values */ + HDMI_FC_AUDSCHNLS8_ORIGSAMPFREQ_MASK = 0xf0, + HDMI_FC_AUDSCHNLS8_ORIGSAMPFREQ_OFFSET = 4, + HDMI_FC_AUDSCHNLS8_WORDLEGNTH_MASK = 0x0f, + HDMI_FC_AUDSCHNLS8_WORDLEGNTH_OFFSET = 0, + + /* FC_AUDSCONF field values */ + HDMI_FC_AUDSCONF_AUD_PACKET_SAMPFIT_MASK = 0xF0, + HDMI_FC_AUDSCONF_AUD_PACKET_SAMPFIT_OFFSET = 4, + HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_MASK = 0x1, + HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_OFFSET = 0, + HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT1 = 0x1, + HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT0 = 0x0, + + /* FC_STAT2 field values */ + HDMI_FC_STAT2_OVERFLOW_MASK = 0x03, + HDMI_FC_STAT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_FC_STAT2_HIGH_PRIORITY_OVERFLOW = 0x01, + + /* FC_INT2 field values */ + HDMI_FC_INT2_OVERFLOW_MASK = 0x03, + HDMI_FC_INT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_FC_INT2_HIGH_PRIORITY_OVERFLOW = 0x01, + + /* FC_MASK2 field values */ + HDMI_FC_MASK2_OVERFLOW_MASK = 0x03, + HDMI_FC_MASK2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_FC_MASK2_HIGH_PRIORITY_OVERFLOW = 0x01, + + /* FC_PRCONF field values */ + HDMI_FC_PRCONF_INCOMING_PR_FACTOR_MASK = 0xF0, + HDMI_FC_PRCONF_INCOMING_PR_FACTOR_OFFSET = 4, + HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_MASK = 0x0F, + HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_OFFSET = 0, + + /* FC_PACKET_TX_EN field values */ + HDMI_FC_PACKET_TX_EN_DRM_MASK = 0x80, + HDMI_FC_PACKET_TX_EN_DRM_ENABLE = 0x80, + HDMI_FC_PACKET_TX_EN_DRM_DISABLE = 0x00, + + /* FC_AVICONF0-FC_AVICONF3 field values */ + HDMI_FC_AVICONF0_PIX_FMT_MASK = 0x03, + HDMI_FC_AVICONF0_PIX_FMT_RGB = 0x00, + HDMI_FC_AVICONF0_PIX_FMT_YCBCR422 = 0x01, + HDMI_FC_AVICONF0_PIX_FMT_YCBCR444 = 0x02, + HDMI_FC_AVICONF0_ACTIVE_FMT_MASK = 0x40, + HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT = 0x40, + HDMI_FC_AVICONF0_ACTIVE_FMT_NO_INFO = 0x00, + HDMI_FC_AVICONF0_BAR_DATA_MASK = 0x0C, + HDMI_FC_AVICONF0_BAR_DATA_NO_DATA = 0x00, + HDMI_FC_AVICONF0_BAR_DATA_VERT_BAR = 0x04, + HDMI_FC_AVICONF0_BAR_DATA_HORIZ_BAR = 0x08, + HDMI_FC_AVICONF0_BAR_DATA_VERT_HORIZ_BAR = 0x0C, + HDMI_FC_AVICONF0_SCAN_INFO_MASK = 0x30, + HDMI_FC_AVICONF0_SCAN_INFO_OVERSCAN = 0x10, + HDMI_FC_AVICONF0_SCAN_INFO_UNDERSCAN = 0x20, + HDMI_FC_AVICONF0_SCAN_INFO_NODATA = 0x00, + + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_MASK = 0x0F, + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_USE_CODED = 0x08, + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_4_3 = 0x09, + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_16_9 = 0x0A, + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_14_9 = 0x0B, + HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_MASK = 0x30, + HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_NO_DATA = 0x00, + HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_4_3 = 0x10, + HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_16_9 = 0x20, + HDMI_FC_AVICONF1_COLORIMETRY_MASK = 0xC0, + HDMI_FC_AVICONF1_COLORIMETRY_NO_DATA = 0x00, + HDMI_FC_AVICONF1_COLORIMETRY_SMPTE = 0x40, + HDMI_FC_AVICONF1_COLORIMETRY_ITUR = 0x80, + HDMI_FC_AVICONF1_COLORIMETRY_EXTENDED_INFO = 0xC0, + + HDMI_FC_AVICONF2_SCALING_MASK = 0x03, + HDMI_FC_AVICONF2_SCALING_NONE = 0x00, + HDMI_FC_AVICONF2_SCALING_HORIZ = 0x01, + HDMI_FC_AVICONF2_SCALING_VERT = 0x02, + HDMI_FC_AVICONF2_SCALING_HORIZ_VERT = 0x03, + HDMI_FC_AVICONF2_RGB_QUANT_MASK = 0x0C, + HDMI_FC_AVICONF2_RGB_QUANT_DEFAULT = 0x00, + HDMI_FC_AVICONF2_RGB_QUANT_LIMITED_RANGE = 0x04, + HDMI_FC_AVICONF2_RGB_QUANT_FULL_RANGE = 0x08, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_MASK = 0x70, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601 = 0x00, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC709 = 0x10, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_SYCC601 = 0x20, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_ADOBE_YCC601 = 0x30, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_ADOBE_RGB = 0x40, + HDMI_FC_AVICONF2_IT_CONTENT_MASK = 0x80, + HDMI_FC_AVICONF2_IT_CONTENT_NO_DATA = 0x00, + HDMI_FC_AVICONF2_IT_CONTENT_VALID = 0x80, + + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_MASK = 0x03, + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_GRAPHICS = 0x00, + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_PHOTO = 0x01, + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_CINEMA = 0x02, + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_GAME = 0x03, + HDMI_FC_AVICONF3_QUANT_RANGE_MASK = 0x0C, + HDMI_FC_AVICONF3_QUANT_RANGE_LIMITED = 0x00, + HDMI_FC_AVICONF3_QUANT_RANGE_FULL = 0x04, + + /* HDMI_FC_GCP */ + HDMI_FC_GCP_SET_AVMUTE = 0x2, + HDMI_FC_GCP_CLEAR_AVMUTE = 0x1, + + /* FC_DBGFORCE field values */ + HDMI_FC_DBGFORCE_FORCEAUDIO = 0x10, + HDMI_FC_DBGFORCE_FORCEVIDEO = 0x1, + + /* FC_DATAUTO0 field values */ + HDMI_FC_DATAUTO0_VSD_MASK = 0x08, + HDMI_FC_DATAUTO0_VSD_OFFSET = 3, + + /* PHY_CONF0 field values */ + HDMI_PHY_CONF0_PDZ_MASK = 0x80, + HDMI_PHY_CONF0_PDZ_OFFSET = 7, + HDMI_PHY_CONF0_ENTMDS_MASK = 0x40, + HDMI_PHY_CONF0_ENTMDS_OFFSET = 6, + HDMI_PHY_CONF0_SVSRET_MASK = 0x20, + HDMI_PHY_CONF0_SVSRET_OFFSET = 5, + HDMI_PHY_CONF0_GEN2_PDDQ_MASK = 0x10, + HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET = 4, + HDMI_PHY_CONF0_GEN2_TXPWRON_MASK = 0x8, + HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET = 3, + HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_MASK = 0x4, + HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_OFFSET = 2, + HDMI_PHY_CONF0_SELDATAENPOL_MASK = 0x2, + HDMI_PHY_CONF0_SELDATAENPOL_OFFSET = 1, + HDMI_PHY_CONF0_SELDIPIF_MASK = 0x1, + HDMI_PHY_CONF0_SELDIPIF_OFFSET = 0, + + /* PHY_TST0 field values */ + HDMI_PHY_TST0_TSTCLR_MASK = 0x20, + HDMI_PHY_TST0_TSTCLR_OFFSET = 5, + HDMI_PHY_TST0_TSTEN_MASK = 0x10, + HDMI_PHY_TST0_TSTEN_OFFSET = 4, + HDMI_PHY_TST0_TSTCLK_MASK = 0x1, + HDMI_PHY_TST0_TSTCLK_OFFSET = 0, + + /* PHY_STAT0 field values */ + HDMI_PHY_RX_SENSE3 = 0x80, + HDMI_PHY_RX_SENSE2 = 0x40, + HDMI_PHY_RX_SENSE1 = 0x20, + HDMI_PHY_RX_SENSE0 = 0x10, + HDMI_PHY_HPD = 0x02, + HDMI_PHY_TX_PHY_LOCK = 0x01, + + /* PHY_I2CM_SLAVE_ADDR field values */ + HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2 = 0x69, + HDMI_PHY_I2CM_SLAVE_ADDR_HEAC_PHY = 0x49, + + /* PHY_I2CM_OPERATION_ADDR field values */ + HDMI_PHY_I2CM_OPERATION_ADDR_WRITE = 0x10, + HDMI_PHY_I2CM_OPERATION_ADDR_READ = 0x1, + + /* HDMI_PHY_I2CM_INT_ADDR */ + HDMI_PHY_I2CM_INT_ADDR_DONE_POL = 0x08, + HDMI_PHY_I2CM_INT_ADDR_DONE_MASK = 0x04, + + /* HDMI_PHY_I2CM_CTLINT_ADDR */ + HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL = 0x80, + HDMI_PHY_I2CM_CTLINT_ADDR_NAC_MASK = 0x40, + HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL = 0x08, + HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_MASK = 0x04, + + /* AUD */ + HDMI_AUD_CONF0_I2S_SELECT_MASK = 0x20, + HDMI_AUD_CONF0_I2S_2CHANNEL_ENABLE = 0x21, + HDMI_AUD_CONF0_I2S_4CHANNEL_ENABLE = 0x23, + HDMI_AUD_CONF0_I2S_6CHANNEL_ENABLE = 0x27, + HDMI_AUD_CONF0_I2S_8CHANNEL_ENABLE = 0x2F, + HDMI_AUD_CONF0_I2S_ALL_ENABLE = 0x2F, + + /* AUD_CONF0 field values */ + HDMI_AUD_CONF0_SW_RESET = 0x80, + HDMI_AUD_CONF0_I2S_SELECT = 0x20, + HDMI_AUD_CONF0_I2S_EN3 = 0x08, + HDMI_AUD_CONF0_I2S_EN2 = 0x04, + HDMI_AUD_CONF0_I2S_EN1 = 0x02, + HDMI_AUD_CONF0_I2S_EN0 = 0x01, + + /* AUD_CONF1 field values */ + HDMI_AUD_CONF1_MODE_I2S = 0x00, + HDMI_AUD_CONF1_MODE_RIGHT_J = 0x20, + HDMI_AUD_CONF1_MODE_LEFT_J = 0x40, + HDMI_AUD_CONF1_MODE_BURST_1 = 0x60, + HDMI_AUD_CONF1_MODE_BURST_2 = 0x80, + HDMI_AUD_CONF1_WIDTH_16 = 0x10, + HDMI_AUD_CONF1_WIDTH_24 = 0x18, + + /* AUD_CTS3 field values */ + HDMI_AUD_CTS3_N_SHIFT_OFFSET = 5, + HDMI_AUD_CTS3_N_SHIFT_MASK = 0xe0, + HDMI_AUD_CTS3_N_SHIFT_1 = 0, + HDMI_AUD_CTS3_N_SHIFT_16 = 0x20, + HDMI_AUD_CTS3_N_SHIFT_32 = 0x40, + HDMI_AUD_CTS3_N_SHIFT_64 = 0x60, + HDMI_AUD_CTS3_N_SHIFT_128 = 0x80, + HDMI_AUD_CTS3_N_SHIFT_256 = 0xa0, + /* note that the CTS3 MANUAL bit has been removed + from our part. Can't set it, will read as 0. */ + HDMI_AUD_CTS3_CTS_MANUAL = 0x10, + HDMI_AUD_CTS3_AUDCTS19_16_MASK = 0x0f, + + /* HDMI_AUD_INPUTCLKFS field values */ + HDMI_AUD_INPUTCLKFS_128FS = 0, + HDMI_AUD_INPUTCLKFS_256FS = 1, + HDMI_AUD_INPUTCLKFS_512FS = 2, + HDMI_AUD_INPUTCLKFS_64FS = 4, + + /* AHB_DMA_CONF0 field values */ + HDMI_AHB_DMA_CONF0_SW_FIFO_RST_OFFSET = 7, + HDMI_AHB_DMA_CONF0_SW_FIFO_RST_MASK = 0x80, + HDMI_AHB_DMA_CONF0_HBR = 0x10, + HDMI_AHB_DMA_CONF0_EN_HLOCK_OFFSET = 3, + HDMI_AHB_DMA_CONF0_EN_HLOCK_MASK = 0x08, + HDMI_AHB_DMA_CONF0_INCR_TYPE_OFFSET = 1, + HDMI_AHB_DMA_CONF0_INCR_TYPE_MASK = 0x06, + HDMI_AHB_DMA_CONF0_INCR4 = 0x0, + HDMI_AHB_DMA_CONF0_INCR8 = 0x2, + HDMI_AHB_DMA_CONF0_INCR16 = 0x4, + HDMI_AHB_DMA_CONF0_BURST_MODE = 0x1, + + /* HDMI_AHB_DMA_START field values */ + HDMI_AHB_DMA_START_START_OFFSET = 0, + HDMI_AHB_DMA_START_START_MASK = 0x01, + + /* HDMI_AHB_DMA_STOP field values */ + HDMI_AHB_DMA_STOP_STOP_OFFSET = 0, + HDMI_AHB_DMA_STOP_STOP_MASK = 0x01, + + /* AHB_DMA_STAT, AHB_DMA_INT, AHB_DMA_MASK, AHB_DMA_POL field values */ + HDMI_AHB_DMA_DONE = 0x80, + HDMI_AHB_DMA_RETRY_SPLIT = 0x40, + HDMI_AHB_DMA_LOSTOWNERSHIP = 0x20, + HDMI_AHB_DMA_ERROR = 0x10, + HDMI_AHB_DMA_FIFO_THREMPTY = 0x04, + HDMI_AHB_DMA_FIFO_FULL = 0x02, + HDMI_AHB_DMA_FIFO_EMPTY = 0x01, + + /* AHB_DMA_BUFFSTAT, AHB_DMA_BUFFINT,AHB_DMA_BUFFMASK,AHB_DMA_BUFFPOL values */ + HDMI_AHB_DMA_BUFFSTAT_FULL = 0x02, + HDMI_AHB_DMA_BUFFSTAT_EMPTY = 0x01, + + /* MC_CLKDIS field values */ + HDMI_MC_CLKDIS_HDCPCLK_DISABLE = 0x40, + HDMI_MC_CLKDIS_CECCLK_DISABLE = 0x20, + HDMI_MC_CLKDIS_CSCCLK_DISABLE = 0x10, + HDMI_MC_CLKDIS_AUDCLK_DISABLE = 0x8, + HDMI_MC_CLKDIS_PREPCLK_DISABLE = 0x4, + HDMI_MC_CLKDIS_TMDSCLK_DISABLE = 0x2, + HDMI_MC_CLKDIS_PIXELCLK_DISABLE = 0x1, + HDMI_MC_CLKDIS_PIXELCLK_MASK = 0x1, + HDMI_MC_CLKDIS_PIXELCLK_ENABLE = 0, + + /* MC_SWRSTZ field values */ + HDMI_MC_SWRSTZ_I2SSWRST_REQ = 0x08, + HDMI_MC_SWRSTZ_TMDSSWRST_REQ = 0x02, + + /* MC_FLOWCTRL field values */ + HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_MASK = 0x1, + HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH = 0x1, + HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS = 0x0, + + /* MC_PHYRSTZ field values */ + HDMI_MC_PHYRSTZ_PHYRSTZ = 0x01, + + /* MC_HEACPHY_RST field values */ + HDMI_MC_HEACPHY_RST_ASSERT = 0x1, + HDMI_MC_HEACPHY_RST_DEASSERT = 0x0, + + /* CSC_CFG field values */ + HDMI_CSC_CFG_INTMODE_MASK = 0x30, + HDMI_CSC_CFG_INTMODE_OFFSET = 4, + HDMI_CSC_CFG_INTMODE_DISABLE = 0x00, + HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA1 = 0x10, + HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA2 = 0x20, + HDMI_CSC_CFG_DECMODE_MASK = 0x3, + HDMI_CSC_CFG_DECMODE_OFFSET = 0, + HDMI_CSC_CFG_DECMODE_DISABLE = 0x0, + HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA1 = 0x1, + HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA2 = 0x2, + HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA3 = 0x3, + + /* CSC_SCALE field values */ + HDMI_CSC_SCALE_CSC_COLORDE_PTH_MASK = 0xF0, + HDMI_CSC_SCALE_CSC_COLORDE_PTH_24BPP = 0x00, + HDMI_CSC_SCALE_CSC_COLORDE_PTH_30BPP = 0x50, + HDMI_CSC_SCALE_CSC_COLORDE_PTH_36BPP = 0x60, + HDMI_CSC_SCALE_CSC_COLORDE_PTH_48BPP = 0x70, + HDMI_CSC_SCALE_CSCSCALE_MASK = 0x03, + + /* A_HDCPCFG0 field values */ + HDMI_A_HDCPCFG0_ELVENA_MASK = 0x80, + HDMI_A_HDCPCFG0_ELVENA_ENABLE = 0x80, + HDMI_A_HDCPCFG0_ELVENA_DISABLE = 0x00, + HDMI_A_HDCPCFG0_I2CFASTMODE_MASK = 0x40, + HDMI_A_HDCPCFG0_I2CFASTMODE_ENABLE = 0x40, + HDMI_A_HDCPCFG0_I2CFASTMODE_DISABLE = 0x00, + HDMI_A_HDCPCFG0_BYPENCRYPTION_MASK = 0x20, + HDMI_A_HDCPCFG0_BYPENCRYPTION_ENABLE = 0x20, + HDMI_A_HDCPCFG0_BYPENCRYPTION_DISABLE = 0x00, + HDMI_A_HDCPCFG0_SYNCRICHECK_MASK = 0x10, + HDMI_A_HDCPCFG0_SYNCRICHECK_ENABLE = 0x10, + HDMI_A_HDCPCFG0_SYNCRICHECK_DISABLE = 0x00, + HDMI_A_HDCPCFG0_AVMUTE_MASK = 0x8, + HDMI_A_HDCPCFG0_AVMUTE_ENABLE = 0x8, + HDMI_A_HDCPCFG0_AVMUTE_DISABLE = 0x0, + HDMI_A_HDCPCFG0_RXDETECT_MASK = 0x4, + HDMI_A_HDCPCFG0_RXDETECT_ENABLE = 0x4, + HDMI_A_HDCPCFG0_RXDETECT_DISABLE = 0x0, + HDMI_A_HDCPCFG0_EN11FEATURE_MASK = 0x2, + HDMI_A_HDCPCFG0_EN11FEATURE_ENABLE = 0x2, + HDMI_A_HDCPCFG0_EN11FEATURE_DISABLE = 0x0, + HDMI_A_HDCPCFG0_HDMIDVI_MASK = 0x1, + HDMI_A_HDCPCFG0_HDMIDVI_HDMI = 0x1, + HDMI_A_HDCPCFG0_HDMIDVI_DVI = 0x0, + + /* A_HDCPCFG1 field values */ + HDMI_A_HDCPCFG1_DISSHA1CHECK_MASK = 0x8, + HDMI_A_HDCPCFG1_DISSHA1CHECK_DISABLE = 0x8, + HDMI_A_HDCPCFG1_DISSHA1CHECK_ENABLE = 0x0, + HDMI_A_HDCPCFG1_PH2UPSHFTENC_MASK = 0x4, + HDMI_A_HDCPCFG1_PH2UPSHFTENC_ENABLE = 0x4, + HDMI_A_HDCPCFG1_PH2UPSHFTENC_DISABLE = 0x0, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK = 0x2, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE = 0x2, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_ENABLE = 0x0, + HDMI_A_HDCPCFG1_SWRESET_MASK = 0x1, + HDMI_A_HDCPCFG1_SWRESET_ASSERT = 0x0, + + /* A_VIDPOLCFG field values */ + HDMI_A_VIDPOLCFG_UNENCRYPTCONF_MASK = 0x60, + HDMI_A_VIDPOLCFG_UNENCRYPTCONF_OFFSET = 5, + HDMI_A_VIDPOLCFG_DATAENPOL_MASK = 0x10, + HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH = 0x10, + HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_LOW = 0x0, + HDMI_A_VIDPOLCFG_VSYNCPOL_MASK = 0x8, + HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_HIGH = 0x8, + HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_LOW = 0x0, + HDMI_A_VIDPOLCFG_HSYNCPOL_MASK = 0x2, + HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_HIGH = 0x2, + HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_LOW = 0x0, + + /* I2CM_OPERATION field values */ + HDMI_I2CM_OPERATION_BUSCLEAR = 0x20, + HDMI_I2CM_OPERATION_WRITE = 0x10, + HDMI_I2CM_OPERATION_READ_EXT = 0x2, + HDMI_I2CM_OPERATION_READ = 0x1, + + /* I2CM_INT field values */ + HDMI_I2CM_INT_DONE_POL = 0x8, + HDMI_I2CM_INT_DONE_MASK = 0x4, + + /* I2CM_CTLINT field values */ + HDMI_I2CM_CTLINT_NAC_POL = 0x80, + HDMI_I2CM_CTLINT_NAC_MASK = 0x40, + HDMI_I2CM_CTLINT_ARB_POL = 0x8, + HDMI_I2CM_CTLINT_ARB_MASK = 0x4, +}; + +/* + * HDMI 3D TX PHY registers + */ +#define HDMI_3D_TX_PHY_PWRCTRL 0x00 +#define HDMI_3D_TX_PHY_SERDIVCTRL 0x01 +#define HDMI_3D_TX_PHY_SERCKCTRL 0x02 +#define HDMI_3D_TX_PHY_SERCKKILLCTRL 0x03 +#define HDMI_3D_TX_PHY_TXRESCTRL 0x04 +#define HDMI_3D_TX_PHY_CKCALCTRL 0x05 +#define HDMI_3D_TX_PHY_CPCE_CTRL 0x06 +#define HDMI_3D_TX_PHY_TXCLKMEASCTRL 0x07 +#define HDMI_3D_TX_PHY_TXMEASCTRL 0x08 +#define HDMI_3D_TX_PHY_CKSYMTXCTRL 0x09 +#define HDMI_3D_TX_PHY_CMPSEQCTRL 0x0a +#define HDMI_3D_TX_PHY_CMPPWRCTRL 0x0b +#define HDMI_3D_TX_PHY_CMPMODECTRL 0x0c +#define HDMI_3D_TX_PHY_MEASCTRL 0x0d +#define HDMI_3D_TX_PHY_VLEVCTRL 0x0e +#define HDMI_3D_TX_PHY_D2ACTRL 0x0f +#define HDMI_3D_TX_PHY_CURRCTRL 0x10 +#define HDMI_3D_TX_PHY_DRVANACTRL 0x11 +#define HDMI_3D_TX_PHY_PLLMEASCTRL 0x12 +#define HDMI_3D_TX_PHY_PLLPHBYCTRL 0x13 +#define HDMI_3D_TX_PHY_GRP_CTRL 0x14 +#define HDMI_3D_TX_PHY_GMPCTRL 0x15 +#define HDMI_3D_TX_PHY_MPLLMEASCTRL 0x16 +#define HDMI_3D_TX_PHY_MSM_CTRL 0x17 +#define HDMI_3D_TX_PHY_SCRPB_STATUS 0x18 +#define HDMI_3D_TX_PHY_TXTERM 0x19 +#define HDMI_3D_TX_PHY_PTRPT_ENBL 0x1a +#define HDMI_3D_TX_PHY_PATTERNGEN 0x1b +#define HDMI_3D_TX_PHY_SDCAP_MODE 0x1c +#define HDMI_3D_TX_PHY_SCOPEMODE 0x1d +#define HDMI_3D_TX_PHY_DIGTXMODE 0x1e +#define HDMI_3D_TX_PHY_STR_STATUS 0x1f +#define HDMI_3D_TX_PHY_SCOPECNT0 0x20 +#define HDMI_3D_TX_PHY_SCOPECNT1 0x21 +#define HDMI_3D_TX_PHY_SCOPECNT2 0x22 +#define HDMI_3D_TX_PHY_SCOPECNTCLK 0x23 +#define HDMI_3D_TX_PHY_SCOPESAMPLE 0x24 +#define HDMI_3D_TX_PHY_SCOPECNTMSB01 0x25 +#define HDMI_3D_TX_PHY_SCOPECNTMSB2CK 0x26 + +/* HDMI_3D_TX_PHY_CKCALCTRL values */ +#define HDMI_3D_TX_PHY_CKCALCTRL_OVERRIDE BIT(15) + +/* HDMI_3D_TX_PHY_MSM_CTRL values */ +#define HDMI_3D_TX_PHY_MSM_CTRL_MPLL_PH_SEL_CK BIT(13) +#define HDMI_3D_TX_PHY_MSM_CTRL_CKO_SEL_CLK_REF_MPLL (0 << 1) +#define HDMI_3D_TX_PHY_MSM_CTRL_CKO_SEL_OFF (1 << 1) +#define HDMI_3D_TX_PHY_MSM_CTRL_CKO_SEL_PCLK (2 << 1) +#define HDMI_3D_TX_PHY_MSM_CTRL_CKO_SEL_FB_CLK (3 << 1) +#define HDMI_3D_TX_PHY_MSM_CTRL_SCOPE_CK_SEL BIT(0) + +/* HDMI_3D_TX_PHY_PTRPT_ENBL values */ +#define HDMI_3D_TX_PHY_PTRPT_ENBL_OVERRIDE BIT(15) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_PG_SKIP_BIT2 BIT(8) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_PG_SKIP_BIT1 BIT(7) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_PG_SKIP_BIT0 BIT(6) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_CK_REF_ENB BIT(5) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_RCAL_ENB BIT(4) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_TX_CLK_ALIGN_ENB BIT(3) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_TX_READY BIT(2) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_CKO_WORD_ENB BIT(1) +#define HDMI_3D_TX_PHY_PTRPT_ENBL_REFCLK_ENB BIT(0) + +struct drm_display_info; +struct drm_display_mode; +struct drm_encoder; +struct dw_hdmi; +struct platform_device; + +enum { + DW_HDMI_RES_8, + DW_HDMI_RES_10, + DW_HDMI_RES_12, + DW_HDMI_RES_MAX, +}; + +struct dw_hdmi_audio_tmds_n { + unsigned long tmds; + unsigned int n_32k; + unsigned int n_44k1; + unsigned int n_48k; +}; + +enum dw_hdmi_phy_type { + DW_HDMI_PHY_DWC_HDMI_TX_PHY = 0x00, + DW_HDMI_PHY_DWC_MHL_PHY_HEAC = 0xb2, + DW_HDMI_PHY_DWC_MHL_PHY = 0xc2, + DW_HDMI_PHY_DWC_HDMI_3D_TX_PHY_HEAC = 0xe2, + DW_HDMI_PHY_DWC_HDMI_3D_TX_PHY = 0xf2, + DW_HDMI_PHY_DWC_HDMI20_TX_PHY = 0xf3, + DW_HDMI_PHY_VENDOR_PHY = 0xfe, +}; + +enum supported_eotf_type { + TRADITIONAL_GAMMA_SDR = 0, + TRADITIONAL_GAMMA_HDR, + SMPTE_ST2084, + HLG, + FUTURE_EOTF +}; + +struct dw_hdmi_mpll_config { + unsigned long mpixelclock; + struct { + u16 cpce; + u16 gmp; + } res[DW_HDMI_RES_MAX]; +}; + +struct dw_hdmi_curr_ctrl { + unsigned long mpixelclock; + u16 curr[DW_HDMI_RES_MAX]; +}; + +struct dw_hdmi_phy_config { + unsigned long mpixelclock; + u16 sym_ctr; /*clock symbol and transmitter control*/ + u16 term; /*transmission termination value*/ + u16 vlev_ctr; /* voltage level control */ +}; + +struct dw_hdmi_phy_ops { + int (*init)(struct dw_hdmi *hdmi, void *data, + const struct drm_display_info *display, + const struct drm_display_mode *mode); + void (*disable)(struct dw_hdmi *hdmi, void *data); + enum drm_connector_status (*read_hpd)(struct dw_hdmi *hdmi, void *data); + void (*update_hpd)(struct dw_hdmi *hdmi, void *data, bool force, + bool disabled, bool rxsense); + void (*setup_hpd)(struct dw_hdmi *hdmi, void *data); +}; + +struct dw_hdmi_property_ops { + void (*attatch_properties)(struct drm_connector *connector, + unsigned int color, int version, void *data); + void (*destroy_properties)(struct drm_connector *connector, void *data); + int (*set_property)(struct drm_connector *connector, + struct drm_connector_state *state, + struct drm_property *property, u64 val, void *data); + int (*get_property)(struct drm_connector *connector, + const struct drm_connector_state *state, + struct drm_property *property, u64 *val, + void *data); +}; + +struct dw_hdmi_plat_data { + struct regmap *regm; + unsigned long input_bus_encoding; + bool use_drm_infoframe; + bool ycbcr_420_allowed; + unsigned int output_port; + /* + ¦* Private data passed to all the .mode_valid() and .configure_phy() + ¦* callback functions. + ¦*/ + void *priv_data; + + /* Platform-specific mode validation (optional). */ + enum drm_mode_status (*mode_valid)(struct dw_hdmi *hdmi, void *data, + const struct drm_display_info *info, + const struct drm_display_mode *mode); + + /* Vendor PHY support */ + const struct dw_hdmi_phy_ops *phy_ops; + const char *phy_name; + void *phy_data; + unsigned int phy_force_vendor; + + /* Synopsys PHY support */ + const struct dw_hdmi_mpll_config *mpll_cfg; + const struct dw_hdmi_curr_ctrl *cur_ctr; + const struct dw_hdmi_phy_config *phy_config; + int (*configure_phy)(struct dw_hdmi *hdmi, void *data, + unsigned long mpixelclock); + unsigned long (*get_input_bus_format)(void *data); + unsigned long (*get_output_bus_format)(void *data); + unsigned long (*get_enc_in_encoding)(void *data); + unsigned long (*get_enc_out_encoding)(void *data); + + /* Vendor Property support */ + const struct dw_hdmi_property_ops *property_ops; + + unsigned int disable_cec : 1; +}; + +struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, + const struct dw_hdmi_plat_data *plat_data); +void dw_hdmi_remove(struct dw_hdmi *hdmi); +void dw_hdmi_unbind(struct dw_hdmi *hdmi); +struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev, + struct drm_encoder *encoder, + const struct dw_hdmi_plat_data *plat_data); + +void dw_hdmi_suspend(struct dw_hdmi *hdmi); +void dw_hdmi_resume(struct dw_hdmi *hdmi); + +void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense); + +int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn, + struct device *codec_dev); +void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate); +void dw_hdmi_set_channel_count(struct dw_hdmi *hdmi, unsigned int cnt); +void dw_hdmi_set_channel_status(struct dw_hdmi *hdmi, u8 *channel_status); +void dw_hdmi_set_channel_allocation(struct dw_hdmi *hdmi, unsigned int ca); +void dw_hdmi_audio_enable(struct dw_hdmi *hdmi); +void dw_hdmi_audio_disable(struct dw_hdmi *hdmi); +void dw_hdmi_set_high_tmds_clock_ratio(struct dw_hdmi *hdmi, + const struct drm_display_info *display); + +/* PHY configuration */ +void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address); +void dw_hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data, + unsigned char addr); + +void dw_hdmi_phy_gen2_pddq(struct dw_hdmi *hdmi, u8 enable); +void dw_hdmi_phy_gen2_txpwron(struct dw_hdmi *hdmi, u8 enable); +void dw_hdmi_phy_reset(struct dw_hdmi *hdmi); + +enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi, + void *data); +void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data, bool force, + bool disabled, bool rxsense); +void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data); + +void dw_hdmi_enable_video(struct dw_hdmi *hdmi); +void dw_hdmi_disable_video(struct dw_hdmi *hdmi); +#endif /* __DW_HDMI_H__ */ diff --git a/drivers/gpu/drm/eswin/dw_hdmi_audio.h b/drivers/gpu/drm/eswin/dw_hdmi_audio.h new file mode 100644 index 000000000000..f72d27208ebe --- /dev/null +++ b/drivers/gpu/drm/eswin/dw_hdmi_audio.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef DW_HDMI_AUDIO_H +#define DW_HDMI_AUDIO_H + +struct dw_hdmi; + +struct dw_hdmi_audio_data { + phys_addr_t phys; + void __iomem *base; + int irq; + struct dw_hdmi *hdmi; + u8 *(*get_eld)(struct dw_hdmi *hdmi); +}; + +struct dw_hdmi_i2s_audio_data { + struct dw_hdmi *hdmi; + + void (*write)(struct dw_hdmi *hdmi, u8 val, int offset); + u8 (*read)(struct dw_hdmi *hdmi, int offset); + u8 *(*get_eld)(struct dw_hdmi *hdmi); +}; + +#endif diff --git a/drivers/gpu/drm/eswin/dw_hdmi_cec.c b/drivers/gpu/drm/eswin/dw_hdmi_cec.c new file mode 100644 index 000000000000..42c57cf1dbc0 --- /dev/null +++ b/drivers/gpu/drm/eswin/dw_hdmi_cec.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Designware HDMI CEC driver + * + * Copyright (C) 2015-2017 Russell King. + */ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "dw_hdmi_cec.h" + +#define CEC_IO_ADDR 0x516000fc + +enum { + HDMI_IH_CEC_STAT0 = 0x0106, + HDMI_IH_MUTE_CEC_STAT0 = 0x0186, + + HDMI_CEC_CTRL = 0x7d00, + CEC_CTRL_START = BIT(0), + CEC_CTRL_FRAME_TYP = 3 << 1, + CEC_CTRL_RETRY = 0 << 1, + CEC_CTRL_NORMAL = 1 << 1, + CEC_CTRL_IMMED = 2 << 1, + + HDMI_CEC_STAT = 0x7d01, + CEC_STAT_DONE = BIT(0), + CEC_STAT_EOM = BIT(1), + CEC_STAT_NACK = BIT(2), + CEC_STAT_ARBLOST = BIT(3), + CEC_STAT_ERROR_INIT = BIT(4), + CEC_STAT_ERROR_FOLL = BIT(5), + CEC_STAT_WAKEUP = BIT(6), + + HDMI_CEC_MASK = 0x7d02, + HDMI_CEC_POLARITY = 0x7d03, + HDMI_CEC_INT = 0x7d04, + HDMI_CEC_ADDR_L = 0x7d05, + HDMI_CEC_ADDR_H = 0x7d06, + HDMI_CEC_TX_CNT = 0x7d07, + HDMI_CEC_RX_CNT = 0x7d08, + HDMI_CEC_TX_DATA0 = 0x7d10, + HDMI_CEC_RX_DATA0 = 0x7d20, + HDMI_CEC_LOCK = 0x7d30, + HDMI_CEC_WKUPCTRL = 0x7d31, +}; + +struct dw_hdmi_cec { + struct dw_hdmi *hdmi; + const struct dw_hdmi_cec_ops *ops; + u32 addresses; + struct cec_adapter *adap; + struct cec_msg rx_msg; + unsigned int tx_status; + bool tx_done; + bool rx_done; + struct cec_notifier *notify; + int irq; +}; + +static void dw_hdmi_write(struct dw_hdmi_cec *cec, u8 val, int offset) +{ + cec->ops->write(cec->hdmi, val, offset); +} + +static u8 dw_hdmi_read(struct dw_hdmi_cec *cec, int offset) +{ + return cec->ops->read(cec->hdmi, offset); +} + +static int dw_hdmi_cec_log_addr(struct cec_adapter *adap, u8 logical_addr) +{ + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); + + if (logical_addr == CEC_LOG_ADDR_INVALID) + cec->addresses = 0; + else + cec->addresses |= BIT(logical_addr) | BIT(15); + + dw_hdmi_write(cec, cec->addresses & 255, HDMI_CEC_ADDR_L); + dw_hdmi_write(cec, cec->addresses >> 8, HDMI_CEC_ADDR_H); + + return 0; +} + +static int dw_hdmi_cec_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); + unsigned int i, ctrl; + + switch (signal_free_time) { + case CEC_SIGNAL_FREE_TIME_RETRY: + ctrl = CEC_CTRL_RETRY; + break; + case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR: + default: + ctrl = CEC_CTRL_NORMAL; + break; + case CEC_SIGNAL_FREE_TIME_NEXT_XFER: + ctrl = CEC_CTRL_IMMED; + break; + } + + for (i = 0; i < msg->len; i++) + dw_hdmi_write(cec, msg->msg[i], HDMI_CEC_TX_DATA0 + i); + + dw_hdmi_write(cec, msg->len, HDMI_CEC_TX_CNT); + dw_hdmi_write(cec, ctrl | CEC_CTRL_START, HDMI_CEC_CTRL); + + return 0; +} + +static irqreturn_t dw_hdmi_cec_hardirq(int irq, void *data) +{ + struct cec_adapter *adap = data; + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); + unsigned int stat = dw_hdmi_read(cec, HDMI_IH_CEC_STAT0); + irqreturn_t ret = IRQ_HANDLED; + + if (stat == 0) + return IRQ_NONE; + + dw_hdmi_write(cec, stat, HDMI_IH_CEC_STAT0); + + if (stat & CEC_STAT_ERROR_INIT) { + cec->tx_status = CEC_TX_STATUS_ERROR; + cec->tx_done = true; + ret = IRQ_WAKE_THREAD; + } else if (stat & CEC_STAT_DONE) { + cec->tx_status = CEC_TX_STATUS_OK; + cec->tx_done = true; + ret = IRQ_WAKE_THREAD; + } else if (stat & CEC_STAT_NACK) { + cec->tx_status = CEC_TX_STATUS_NACK; + cec->tx_done = true; + ret = IRQ_WAKE_THREAD; + } + + if (stat & CEC_STAT_EOM) { + unsigned int len, i; + + len = dw_hdmi_read(cec, HDMI_CEC_RX_CNT); + if (len > sizeof(cec->rx_msg.msg)) + len = sizeof(cec->rx_msg.msg); + + for (i = 0; i < len; i++) + cec->rx_msg.msg[i] = + dw_hdmi_read(cec, HDMI_CEC_RX_DATA0 + i); + + dw_hdmi_write(cec, 0, HDMI_CEC_LOCK); + + cec->rx_msg.len = len; + smp_wmb(); + cec->rx_done = true; + + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +static irqreturn_t dw_hdmi_cec_thread(int irq, void *data) +{ + struct cec_adapter *adap = data; + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); + + if (cec->tx_done) { + cec->tx_done = false; + cec_transmit_attempt_done(adap, cec->tx_status); + } + if (cec->rx_done) { + cec->rx_done = false; + smp_rmb(); + cec_received_msg(adap, &cec->rx_msg); + } + return IRQ_HANDLED; +} + +static int dw_hdmi_cec_enable(struct cec_adapter *adap, bool enable) +{ + struct dw_hdmi_cec *cec = cec_get_drvdata(adap); + + if (!enable) { + dw_hdmi_write(cec, ~0, HDMI_CEC_MASK); + dw_hdmi_write(cec, ~0, HDMI_IH_MUTE_CEC_STAT0); + dw_hdmi_write(cec, 0, HDMI_CEC_POLARITY); + + cec->ops->disable(cec->hdmi); + } else { + unsigned int irqs; + + dw_hdmi_write(cec, 0, HDMI_CEC_CTRL); + dw_hdmi_write(cec, ~0, HDMI_IH_CEC_STAT0); + dw_hdmi_write(cec, 0, HDMI_CEC_LOCK); + + dw_hdmi_cec_log_addr(cec->adap, CEC_LOG_ADDR_INVALID); + + cec->ops->enable(cec->hdmi); + + irqs = CEC_STAT_ERROR_INIT | CEC_STAT_NACK | CEC_STAT_EOM | + CEC_STAT_DONE; + dw_hdmi_write(cec, irqs, HDMI_CEC_POLARITY); + dw_hdmi_write(cec, ~irqs, HDMI_CEC_MASK); + dw_hdmi_write(cec, ~irqs, HDMI_IH_MUTE_CEC_STAT0); + } + return 0; +} + +static const struct cec_adap_ops dw_hdmi_cec_ops = { + .adap_enable = dw_hdmi_cec_enable, + .adap_log_addr = dw_hdmi_cec_log_addr, + .adap_transmit = dw_hdmi_cec_transmit, +}; + +static void dw_hdmi_cec_del(void *data) +{ + struct dw_hdmi_cec *cec = data; + + cec_delete_adapter(cec->adap); +} + +static int dw_hdmi_cec_probe(struct platform_device *pdev) +{ + struct dw_hdmi_cec_data *data = dev_get_platdata(&pdev->dev); + struct dw_hdmi_cec *cec; + int ret; + void __iomem *cec_io_base; + int reg_val; + + if (!data) + return -ENXIO; + + /* + * Our device is just a convenience - we want to link to the real + * hardware device here, so that userspace can see the association + * between the HDMI hardware and its associated CEC chardev. + */ + cec = devm_kzalloc(&pdev->dev, sizeof(*cec), GFP_KERNEL); + if (!cec) + return -ENOMEM; + + cec->irq = data->irq; + cec->ops = data->ops; + cec->hdmi = data->hdmi; + + platform_set_drvdata(pdev, cec); + + dw_hdmi_write(cec, 0, HDMI_CEC_TX_CNT); + dw_hdmi_write(cec, ~0, HDMI_CEC_MASK); + dw_hdmi_write(cec, ~0, HDMI_IH_MUTE_CEC_STAT0); + dw_hdmi_write(cec, 0, HDMI_CEC_POLARITY); + + cec->adap = + cec_allocate_adapter(&dw_hdmi_cec_ops, cec, "dw_hdmi", + CEC_CAP_DEFAULTS | CEC_CAP_CONNECTOR_INFO, + CEC_MAX_LOG_ADDRS); + if (IS_ERR(cec->adap)) + return PTR_ERR(cec->adap); + + /* override the module pointer */ + cec->adap->owner = THIS_MODULE; + + ret = devm_add_action_or_reset(&pdev->dev, dw_hdmi_cec_del, cec); + if (ret) + return ret; + + ret = devm_request_threaded_irq(&pdev->dev, cec->irq, + dw_hdmi_cec_hardirq, dw_hdmi_cec_thread, + IRQF_SHARED, "dw-hdmi-cec", cec->adap); + if (ret < 0) + return ret; + + cec->notify = cec_notifier_cec_adap_register(pdev->dev.parent, NULL, + cec->adap); + if (!cec->notify) + return -ENOMEM; + + ret = cec_register_adapter(cec->adap, pdev->dev.parent); + if (ret < 0) { + cec_notifier_cec_adap_unregister(cec->notify, cec->adap); + return ret; + } + + /* + * CEC documentation says we must not call cec_delete_adapter + * after a successful call to cec_register_adapter(). + */ + devm_remove_action(&pdev->dev, dw_hdmi_cec_del, cec); + + cec_io_base = devm_ioremap(&pdev->dev, CEC_IO_ADDR, 4); + if (!cec_io_base) { + dev_err(&pdev->dev, "failed to remap cec io ctl\n"); + return -ENOMEM; + } + + /* set the cec io to output */ + reg_val = readl((char *)cec_io_base); + reg_val |= 0x1; + writel(reg_val, (char *)cec_io_base); + + return 0; +} + +static int dw_hdmi_cec_remove(struct platform_device *pdev) +{ + struct dw_hdmi_cec *cec = platform_get_drvdata(pdev); + + cec_notifier_cec_adap_unregister(cec->notify, cec->adap); + cec_unregister_adapter(cec->adap); + + return 0; +} + +struct platform_driver dw_hdmi_cec_driver = { + .probe = dw_hdmi_cec_probe, + .remove = dw_hdmi_cec_remove, + .driver = { + .name = "dw-hdmi-cec", + }, +}; +//module_platform_driver(dw_hdmi_cec_driver); + +MODULE_DESCRIPTION("Synopsys Designware HDMI CEC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS(PLATFORM_MODULE_PREFIX "dw-hdmi-cec"); diff --git a/drivers/gpu/drm/eswin/dw_hdmi_cec.h b/drivers/gpu/drm/eswin/dw_hdmi_cec.h new file mode 100644 index 000000000000..9d2547f1ea16 --- /dev/null +++ b/drivers/gpu/drm/eswin/dw_hdmi_cec.h @@ -0,0 +1,18 @@ +#ifndef DW_HDMI_CEC_H +#define DW_HDMI_CEC_H + +struct dw_hdmi; + +struct dw_hdmi_cec_ops { + void (*write)(struct dw_hdmi *hdmi, u8 val, int offset); + u8 (*read)(struct dw_hdmi *hdmi, int offset); + void (*enable)(struct dw_hdmi *hdmi); + void (*disable)(struct dw_hdmi *hdmi); +}; + +struct dw_hdmi_cec_data { + struct dw_hdmi *hdmi; + const struct dw_hdmi_cec_ops *ops; + int irq; +}; +#endif diff --git a/drivers/gpu/drm/eswin/dw_hdmi_hdcp.c b/drivers/gpu/drm/eswin/dw_hdmi_hdcp.c new file mode 100644 index 000000000000..1b4e7433b718 --- /dev/null +++ b/drivers/gpu/drm/eswin/dw_hdmi_hdcp.c @@ -0,0 +1,1055 @@ +/* + * Copyright (C) ESWIN Electronics Co.Ltd + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dw_hdmi.h" +#include "dw_hdmi_hdcp.h" + +#define HDCP_KEY_PATH "/usr/hdcp1.4_key/Tx_A2_TestDPK_encrypted" + +#define HDCP_KEY_SIZE 308 +#define HDCP_KEY_SEED_SIZE 2 + +#define KSV_LEN 5 +#define HEADER 10 +#define SHAMAX 20 + +#define MAX_DOWNSTREAM_DEVICE_NUM 5 +#define DPK_WR_OK_TIMEOUT_US 30000 +#define HDMI_HDCP1X_ID 5 + +/* HDCP DCP KEY & SEED */ +const u8 hdcp_const_data[320] = { + /* 0 1 2 3 4 5 6 */ + 0x00, + 0x00, + 0xf0, + 0xff, + 0xff, + 0x00, + 0x00, + 0x00, //KSV + 0x91, + 0x71, + 0x7, + 0x42, + 0x86, + 0xC1, + 0xD1, + 0x89, + 0x0E, + 0x2D, + 0xFF, + 0x92, + 0x95, + 0x28, + 0xF4, + 0x7D, + 0x7B, + 0x1F, + 0x2A, + 0xD9, + 0xBB, + 0xE4, + 0xFD, + 0x10, + 0x18, + 0xAA, + 0xFB, + 0x99, + 0x5A, + 0x83, + 0x97, + 0xD5, + 0xDA, + 0x85, + 0x2D, + 0x52, + 0x8B, + 0xB5, + 0xB2, + 0x49, + 0xDC, + 0x64, + 0xC6, + 0x62, + 0xF0, + 0xDB, + 0xAA, + 0x48, + 0x2E, + 0x84, + 0xAD, + 0x21, + 0xCD, + 0xB9, + 0xD6, + 0x47, + 0xC7, + 0xD7, + 0xD1, + 0x9F, + 0xD4, + 0xB1, + 0x29, + 0x4E, + 0x98, + 0xC6, + 0xAE, + 0xA4, + 0xF5, + 0xA6, + 0xFE, + 0x68, + 0x3D, + 0x43, + 0x97, + 0x7B, + 0x52, + 0xC7, + 0xA1, + 0x65, + 0x7B, + 0xF9, + 0x8C, + 0xCC, + 0x20, + 0x8C, + 0xCB, + 0x2F, + 0x7D, + 0xFA, + 0xC5, + 0x80, + 0xD8, + 0xDB, + 0x5A, + 0x72, + 0x2D, + 0xE1, + 0xA6, + 0x79, + 0xF4, + 0xAE, + 0x96, + 0x1D, + 0xE8, + 0x28, + 0x85, + 0x5F, + 0xBD, + 0x64, + 0xF8, + 0xBF, + 0x7A, + 0xE7, + 0xFF, + 0xBC, + 0x1F, + 0xC6, + 0x75, + 0x56, + 0xB9, + 0xF9, + 0x0F, + 0x36, + 0x29, + 0x5A, + 0x3B, + 0xF3, + 0x76, + 0x7B, + 0x8B, + 0xF8, + 0xFD, + 0x13, + 0x80, + 0x49, + 0xAB, + 0x5C, + 0x12, + 0x63, + 0xB9, + 0xE7, + 0x91, + 0x2A, + 0xBA, + 0x82, + 0xF3, + 0xCD, + 0xFA, + 0xFB, + 0x4E, + 0xA7, + 0xE1, + 0xBD, + 0x8B, + 0xC3, + 0x24, + 0xEC, + 0x31, + 0xBC, + 0x1, + 0xB1, + 0xCE, + 0x9A, + 0x4, + 0x9C, + 0x69, + 0x5D, + 0xBA, + 0x3C, + 0xF7, + 0x97, + 0x50, + 0x88, + 0xE2, + 0xA2, + 0xE1, + 0x3, + 0xDB, + 0x39, + 0xDD, + 0x93, + 0x0A, + 0x24, + 0x5C, + 0x6E, + 0x17, + 0xE9, + 0x1, + 0x4C, + 0x25, + 0xF5, + 0x9, + 0x24, + 0xC6, + 0x91, + 0xC6, + 0x6A, + 0x7A, + 0x40, + 0x89, + 0x62, + 0x7F, + 0xED, + 0x6B, + 0x8E, + 0x5F, + 0x79, + 0xAD, + 0xF2, + 0x50, + 0x59, + 0xC4, + 0x11, + 0x2E, + 0x1, + 0xC2, + 0xDC, + 0x8, + 0xCE, + 0xDC, + 0x51, + 0x14, + 0xF4, + 0x8C, + 0x3D, + 0x9E, + 0xB7, + 0x16, + 0xB3, + 0x9C, + 0xF3, + 0x55, + 0xC0, + 0xCE, + 0x74, + 0x5B, + 0x19, + 0x4E, + 0xF5, + 0x39, + 0x37, + 0xA6, + 0xEA, + 0xB5, + 0x20, + 0xBF, + 0xD7, + 0x79, + 0x24, + 0xE2, + 0x8D, + 0x13, + 0xBC, + 0x38, + 0x10, + 0x60, + 0x93, + 0xAE, + 0x70, + 0xA9, + 0x66, + 0x81, + 0xF3, + 0x19, + 0xEC, + 0x45, + 0xEC, + 0xE5, + 0x5, + 0x47, + 0xE4, + 0x67, + 0x65, + 0x4C, + 0x62, + 0x1, + 0x98, + 0xA3, + 0x52, + //SHA1 + 0x18, + 0xb4, + 0x70, + 0x59, + 0xfe, + 0x13, + 0x38, + 0xc4, + 0x15, + 0xae, + 0xf0, + 0x81, + 0xcb, + 0x96, + 0x27, + 0xe7, + 0xd9, + 0x7b, + 0xc5, + 0x27, + 0x20, //seed 0x2020 + 0x20, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +/* HDCP Registers */ +#define HDMI_A_KSVMEMCTRL 0x5016 +#define HDMI_HDCPREG_ANCONF 0x7805 +#define HDMI_HDCPREG_AN0 0x7806 +#define HDMI_HDCPREG_AN1 0x7807 +#define HDMI_HDCPREG_AN2 0x7808 +#define HDMI_HDCPREG_AN3 0x7809 +#define HDMI_HDCPREG_AN4 0x780a +#define HDMI_HDCPREG_AN5 0x780b +#define HDMI_HDCPREG_AN6 0x780c +#define HDMI_HDCPREG_AN7 0x780d +#define HDMI_HDCPREG_RMCTL 0x780e +#define HDMI_HDCPREG_RMSTS 0x780f +#define HDMI_HDCPREG_SEED0 0x7810 +#define HDMI_HDCPREG_SEED1 0x7811 +#define HDMI_HDCPREG_DPK0 0x7812 +#define HDMI_HDCPREG_DPK1 0x7813 +#define HDMI_HDCPREG_DPK2 0x7814 +#define HDMI_HDCPREG_DPK3 0x7815 +#define HDMI_HDCPREG_DPK4 0x7816 +#define HDMI_HDCPREG_DPK5 0x7817 +#define HDMI_HDCPREG_DPK6 0x7818 +#define HDMI_HDCP2REG_CTRL 0x7904 +#define HDMI_HDCP2REG_MASK 0x790c +#define HDMI_HDCP2REG_MUTE 0x7912 + +enum dw_hdmi_hdcp_state { + DW_HDCP_DISABLED, + DW_HDCP_AUTH_START, + DW_HDCP_AUTH_SUCCESS, + DW_HDCP_AUTH_FAIL, +}; + +enum { + DW_HDMI_HDCP_KSV_LEN = 8, + DW_HDMI_HDCP_SHA_LEN = 20, + DW_HDMI_HDCP_DPK_LEN = 280, + DW_HDMI_HDCP_KEY_LEN = 308, + DW_HDMI_HDCP_SEED_LEN = 2, +}; + +enum { + HDMI_MC_CLKDIS_HDCPCLK_MASK = 0x40, + HDMI_MC_CLKDIS_HDCPCLK_ENABLE = 0x00, + + HDMI_A_SRMCTRL_SHA1_FAIL_MASK = 0X08, + HDMI_A_SRMCTRL_SHA1_FAIL_DISABLE = 0X00, + HDMI_A_SRMCTRL_SHA1_FAIL_ENABLE = 0X08, + + HDMI_A_SRMCTRL_KSV_UPDATE_MASK = 0X04, + HDMI_A_SRMCTRL_KSV_UPDATE_DISABLE = 0X00, + HDMI_A_SRMCTRL_KSV_UPDATE_ENABLE = 0X04, + + HDMI_A_SRMCTRL_KSV_MEM_REQ_MASK = 0X01, + HDMI_A_SRMCTRL_KSV_MEM_REQ_DISABLE = 0X00, + HDMI_A_SRMCTRL_KSV_MEM_REQ_ENABLE = 0X01, + + HDMI_A_SRMCTRL_KSV_MEM_ACCESS_MASK = 0X02, + HDMI_A_SRMCTRL_KSV_MEM_ACCESS_DISABLE = 0X00, + HDMI_A_SRMCTRL_KSV_MEM_ACCESS_ENABLE = 0X02, + + HDMI_A_SRM_BASE_MAX_DEVS_EXCEEDED = 0x80, + HDMI_A_SRM_BASE_DEVICE_COUNT = 0x7f, + + HDMI_A_SRM_BASE_MAX_CASCADE_EXCEEDED = 0x08, + + HDMI_A_APIINTSTAT_KSVSHA1_CALC_INT = 0x02, + HDMI_A_APIINTSTAT_KSVSHA1_CALC_DONE_INT = 0x20, + /* HDCPREG_RMSTS field values */ + DPK_WR_OK_STS = 0x40, + + HDMI_A_HDCP22_MASK = 0x40, + + HDMI_HDCP2_OVR_EN_MASK = 0x02, + HDMI_HDCP2_OVR_ENABLE = 0x02, + HDMI_HDCP2_OVR_DISABLE = 0x00, + + HDMI_HDCP2_FORCE_MASK = 0x04, + HDMI_HDCP2_FORCE_ENABLE = 0x04, + HDMI_HDCP2_FORCE_DISABLE = 0x00, + HDMI_A_KSVMEMCTRL_KSV_SHA1_STATUS = 0x08, +}; + +static struct dw_hdcp *g_hdcp; +static int trytimes = 0; + +static void hdcp_modb(struct dw_hdcp *hdcp, u8 data, u8 mask, unsigned int reg) +{ + struct dw_hdmi *hdmi = hdcp->hdmi; + u8 val = hdcp->read(hdmi, reg) & ~mask; + + val |= data & mask; + hdcp->write(hdmi, val, reg); +} + +static int hdcp_load_keys_cb(struct dw_hdcp *hdcp) +{ + u32 size; + u8 hdcp_vendor_data[320]; + int i; +#if 0 + int j; + struct file *fp; + loff_t pos = 0; + ssize_t nread; +#endif + hdcp->keys = kmalloc(HDCP_KEY_SIZE, GFP_KERNEL); + if (!hdcp->keys) + return -ENOMEM; + + hdcp->seeds = kmalloc(HDCP_KEY_SEED_SIZE, GFP_KERNEL); + if (!hdcp->seeds) { + kfree(hdcp->keys); + return -ENOMEM; + } +#if 1 + size = eswin_vendor_read(HDMI_HDCP1X_ID, hdcp_vendor_data, 314); + + for (i = 0; i < sizeof(hdcp_vendor_data); i++) + hdcp_vendor_data[i] = hdcp_const_data[i]; + size = 320; + + if (size < (HDCP_KEY_SIZE + HDCP_KEY_SEED_SIZE)) { + dev_dbg(hdcp->dev, "HDCP: read size %d\n", size); + memset(hdcp->keys, 0, HDCP_KEY_SIZE); + memset(hdcp->seeds, 0, HDCP_KEY_SEED_SIZE); + } else { + memcpy(hdcp->keys, hdcp_vendor_data, HDCP_KEY_SIZE); + memcpy(hdcp->seeds, hdcp_vendor_data + HDCP_KEY_SIZE, + HDCP_KEY_SEED_SIZE); + } +#else + fp = filp_open(HDCP_KEY_PATH, O_RDONLY, 0644); + if (IS_ERR(fp)) { + printk("Error, Tx_A2_TestDPK_encrypted.txt doesn't exist.\n"); + return 0; + } + + nread = kernel_read(fp, hdcp_vendor_data, sizeof(hdcp_vendor_data), + &pos); + + if (nread != sizeof(hdcp_vendor_data)) { + printk("Error, failed to read %ld bytes to non volatile memory area,ret %ld\n", + sizeof(hdcp_vendor_data), nread); + return -EIO; + } + + memcpy(hdcp->keys, hdcp_vendor_data, HDCP_KEY_SIZE); + memcpy(hdcp->seeds, hdcp_vendor_data + HDCP_KEY_SIZE, + HDCP_KEY_SEED_SIZE); + + filp_close(fp, NULL); +#endif + + return 0; +} + +static int dw_hdmi_hdcp_load_key(struct dw_hdcp *hdcp) +{ + int i, j; + int ret, val; + void __iomem *reg_rmsts_addr; + struct hdcp_keys *hdcp_keys; + struct dw_hdmi *hdmi = hdcp->hdmi; + + if (!hdcp->keys) { + ret = hdcp_load_keys_cb(hdcp); + if (ret) + return ret; + } + hdcp_keys = hdcp->keys; + + if (hdcp->reg_io_width == 4) + reg_rmsts_addr = hdcp->regs + (HDMI_HDCPREG_RMSTS << 2); + else if (hdcp->reg_io_width == 1) + reg_rmsts_addr = hdcp->regs + HDMI_HDCPREG_RMSTS; + else + return -EPERM; + + /* Disable decryption logic */ + hdcp->write(hdmi, 0, HDMI_HDCPREG_RMCTL); + ret = readx_poll_timeout(readl, reg_rmsts_addr, val, + val & DPK_WR_OK_STS, 1000, + DPK_WR_OK_TIMEOUT_US); + if (ret) + return ret; + + hdcp->write(hdmi, 0, HDMI_HDCPREG_DPK6); + hdcp->write(hdmi, 0, HDMI_HDCPREG_DPK5); + + /* The useful data in ksv should be 5 byte */ + for (i = 4; i >= 0; i--) + hdcp->write(hdmi, hdcp_keys->KSV[i], HDMI_HDCPREG_DPK0 + i); + ret = readx_poll_timeout(readl, reg_rmsts_addr, val, + val & DPK_WR_OK_STS, 1000, + DPK_WR_OK_TIMEOUT_US); + + if (ret) + return ret; + + /* Enable decryption logic */ + if (hdcp->seeds) { + hdcp->write(hdmi, 1, HDMI_HDCPREG_RMCTL); + hdcp->write(hdmi, hdcp->seeds[0], HDMI_HDCPREG_SEED1); + hdcp->write(hdmi, hdcp->seeds[1], HDMI_HDCPREG_SEED0); + } else { + hdcp->write(hdmi, 0, HDMI_HDCPREG_RMCTL); + } + + /* Write encrypt device private key */ + for (i = 0; i < DW_HDMI_HDCP_DPK_LEN - 6; i += 7) { + for (j = 6; j >= 0; j--) + hdcp->write(hdmi, hdcp_keys->devicekey[i + j], + HDMI_HDCPREG_DPK0 + j); + ret = readx_poll_timeout(readl, reg_rmsts_addr, val, + val & DPK_WR_OK_STS, 1000, + DPK_WR_OK_TIMEOUT_US); + + if (ret) + return ret; + } + return 0; +} + +static int dw_hdmi_hdcp1x_start(struct dw_hdcp *hdcp) +{ + struct dw_hdmi *hdmi = hdcp->hdmi; + int i; + int val; + u8 An[8]; + + if (!hdcp->enable) + return -EPERM; + + if (hdcp->status == DW_HDCP_AUTH_START || + hdcp->status == DW_HDCP_AUTH_SUCCESS) + return 0; + + /* disable the pixel clock*/ + dev_dbg(hdcp->dev, "start hdcp with disable hdmi pixel clock\n"); + hdcp_modb(hdcp, HDMI_MC_CLKDIS_PIXELCLK_DISABLE, + HDMI_MC_CLKDIS_PIXELCLK_MASK, HDMI_MC_CLKDIS); + + /* Update An */ + get_random_bytes(&An, sizeof(An)); + for (i = 0; i < 8; i++) + hdcp->write(hdmi, An[i], HDMI_HDCPREG_AN0 + i); + + hdcp->write(hdmi, 0x01, HDMI_HDCPREG_ANCONF); + + if (!(hdcp->read(hdmi, HDMI_HDCPREG_RMSTS) & 0x3f)) + dw_hdmi_hdcp_load_key(hdcp); + + if (hdcp->hdcp2) { + for (i = 0; i < 100; i++) { + if (hdcp->hdcp2->wait_hdcp2_reset) { + msleep(80); + } else { + break; + } + } + printk("wait_hdcp2_reset i = %d\n", i); + } + val = hdcp->read(hdmi, HDMI_HDCP2REG_CTRL); + dev_dbg(hdcp->dev, "before set HDMI_HDCP2REG_CTRL val = %d\n", val); + + hdcp_modb(hdcp, HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE, + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_MASK, HDMI_FC_INVIDCONF); + + hdcp->remaining_times = hdcp->retry_times; + if (hdcp->read(hdmi, HDMI_CONFIG1_ID) & HDMI_A_HDCP22_MASK) { + hdcp_modb(hdcp, + HDMI_HDCP2_OVR_ENABLE | HDMI_HDCP2_FORCE_DISABLE, + HDMI_HDCP2_OVR_EN_MASK | HDMI_HDCP2_FORCE_MASK, + HDMI_HDCP2REG_CTRL); + hdcp->write(hdmi, 0xff, HDMI_HDCP2REG_MASK); + hdcp->write(hdmi, 0xff, HDMI_HDCP2REG_MUTE); + } + + hdcp->write(hdmi, 0x40, HDMI_A_OESSWCFG); + hdcp_modb(hdcp, + HDMI_A_HDCPCFG0_BYPENCRYPTION_DISABLE | + HDMI_A_HDCPCFG0_EN11FEATURE_DISABLE | + HDMI_A_HDCPCFG0_SYNCRICHECK_ENABLE, + HDMI_A_HDCPCFG0_BYPENCRYPTION_MASK | + HDMI_A_HDCPCFG0_EN11FEATURE_MASK | + HDMI_A_HDCPCFG0_SYNCRICHECK_MASK, + HDMI_A_HDCPCFG0); + + hdcp_modb(hdcp, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_ENABLE | + HDMI_A_HDCPCFG1_PH2UPSHFTENC_ENABLE, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK | + HDMI_A_HDCPCFG1_PH2UPSHFTENC_MASK, + HDMI_A_HDCPCFG1); + + /* Reset HDCP Engine */ + if (hdcp->read(hdmi, HDMI_MC_CLKDIS) & HDMI_MC_CLKDIS_HDCPCLK_MASK) { + hdcp_modb(hdcp, HDMI_A_HDCPCFG1_SWRESET_ASSERT, + HDMI_A_HDCPCFG1_SWRESET_MASK, HDMI_A_HDCPCFG1); + } + + hdcp->write(hdmi, 0x00, HDMI_A_APIINTMSK); + hdcp_modb(hdcp, HDMI_A_HDCPCFG0_RXDETECT_ENABLE, + HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0); + + /* + * XXX: to sleep 500ms here between output hdmi and enable hdcpclk, + * otherwise hdcp auth fail when Connect to repeater + */ + msleep(500); + hdcp_modb(hdcp, HDMI_MC_CLKDIS_HDCPCLK_ENABLE, + HDMI_MC_CLKDIS_HDCPCLK_MASK, HDMI_MC_CLKDIS); + + hdcp->status = DW_HDCP_AUTH_START; + dev_info(hdcp->dev, "%s success\n", __func__); + + /* enable the pixel clock*/ + dev_dbg(hdcp->dev, "start hdcp with enable hdmi pixel clock\n"); + hdcp_modb(hdcp, HDMI_MC_CLKDIS_PIXELCLK_ENABLE, + HDMI_MC_CLKDIS_PIXELCLK_MASK, HDMI_MC_CLKDIS); + + return 0; +} + +static int dw_hdmi_hdcp1x_stop(struct dw_hdcp *hdcp) +{ + struct dw_hdmi *hdmi = hdcp->hdmi; + u8 val; + bool phy_enable = false; + + if (!hdcp->enable) + return -EPERM; + + val = hdcp->read(hdmi, HDMI_PHY_CONF0); + if (val & HDMI_PHY_CONF0_GEN2_TXPWRON_MASK) { + phy_enable = true; + } + + dev_dbg(hdcp->dev, "dw_hdmi_hdcp1x_stop\n"); + if (phy_enable) { + dev_dbg(hdcp->dev, "stop hdcp with disable hdmi pixel clock\n"); + hdcp_modb(hdcp, HDMI_MC_CLKDIS_PIXELCLK_DISABLE, + HDMI_MC_CLKDIS_PIXELCLK_MASK, HDMI_MC_CLKDIS); + } + + hdcp_modb(hdcp, HDMI_MC_CLKDIS_HDCPCLK_DISABLE, + HDMI_MC_CLKDIS_HDCPCLK_MASK, HDMI_MC_CLKDIS); + hdcp->write(hdmi, 0xff, HDMI_A_APIINTMSK); + + hdcp_modb(hdcp, HDMI_A_HDCPCFG0_RXDETECT_DISABLE, + HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0); + + hdcp_modb(hdcp, + HDMI_A_SRMCTRL_SHA1_FAIL_DISABLE | + HDMI_A_SRMCTRL_KSV_UPDATE_DISABLE, + HDMI_A_SRMCTRL_SHA1_FAIL_MASK | + HDMI_A_SRMCTRL_KSV_UPDATE_MASK, + HDMI_A_SRMCTRL); + + hdcp->status = DW_HDCP_DISABLED; + + if (phy_enable) { + dev_dbg(hdcp->dev, "stop hdcp with enable hdmi pixel clock\n"); + hdcp_modb(hdcp, HDMI_MC_CLKDIS_PIXELCLK_ENABLE, + HDMI_MC_CLKDIS_PIXELCLK_MASK, HDMI_MC_CLKDIS); + } + + return 0; +} + +void dw_hdmi_hdcp2_init(struct dw_hdcp2 *hdcp2) +{ + if (g_hdcp) + g_hdcp->hdcp2 = hdcp2; +} +EXPORT_SYMBOL_GPL(dw_hdmi_hdcp2_init); + +void dw_hdmi_hdcp2_remove(void) +{ + printk("func: %s; line: %d\n", __func__, __LINE__); + if (g_hdcp->hdcp2) + g_hdcp->hdcp2->stop(); + g_hdcp->hdcp2 = NULL; +} +EXPORT_SYMBOL_GPL(dw_hdmi_hdcp2_remove); + +void dw_hdmi_hdcp2_start(int enable) +{ + int val; + + if (!(g_hdcp->hdcp2)) + return; + + dev_dbg(g_hdcp->dev, "%s enable = %d\n", __func__, enable); + if (enable == 0) { + hdcp_modb(g_hdcp, + HDMI_HDCP2_OVR_ENABLE | HDMI_HDCP2_FORCE_DISABLE, + HDMI_HDCP2_OVR_EN_MASK | HDMI_HDCP2_FORCE_MASK, + HDMI_HDCP2REG_CTRL); + hdcp_modb(g_hdcp, HDMI_MC_CLKDIS_HDCPCLK_DISABLE, + HDMI_MC_CLKDIS_HDCPCLK_MASK, HDMI_MC_CLKDIS); + } else if (enable == 1) { + hdcp_modb(g_hdcp, HDMI_MC_CLKDIS_HDCPCLK_ENABLE, + HDMI_MC_CLKDIS_HDCPCLK_MASK, HDMI_MC_CLKDIS); + hdcp_modb(g_hdcp, + HDMI_HDCP2_OVR_ENABLE | HDMI_HDCP2_FORCE_ENABLE, + HDMI_HDCP2_OVR_EN_MASK | HDMI_HDCP2_FORCE_MASK, + HDMI_HDCP2REG_CTRL); + hdcp_modb(g_hdcp, HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE, + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_MASK, + HDMI_FC_INVIDCONF); + } else if (enable == 2) { + val = g_hdcp->read(g_hdcp->hdmi, HDMI_PHY_STAT0); + if (val & HDMI_PHY_HPD) + dw_hdmi_hdcp1x_start(g_hdcp); + } else if (enable == 3) { + if (g_hdcp->hdcp2 && g_hdcp->hdcp2->enable && + (tv_hdmi_hdcp2_support(g_hdcp->hdmi) == 1)) { + if (g_hdcp->status != DW_HDCP_DISABLED) + dw_hdmi_hdcp1x_stop(g_hdcp); + g_hdcp->hdcp2->start(); + } + } +} +EXPORT_SYMBOL_GPL(dw_hdmi_hdcp2_start); + +static int dw_hdmi_hdcp_start(struct dw_hdcp *hdcp) +{ + if (hdcp->hdcp2 && hdcp->hdcp2->enable && + (tv_hdmi_hdcp2_support(hdcp->hdmi) == 1)) { + // hdcp->hdcp2->start(); + return 0; + } + return dw_hdmi_hdcp1x_start(hdcp); +} + +static int dw_hdmi_hdcp_stop(struct dw_hdcp *hdcp) +{ + if (hdcp->hdcp2 && hdcp->hdcp2->hot_plug) { + // g_hdcp->hdcp2->stop(); + printk("func: %s; line: %d\n", __func__, __LINE__); + } + + return dw_hdmi_hdcp1x_stop(hdcp); +} + +static void dw_hdmi_hdcp_isr(struct dw_hdcp *hdcp, int hdcp_int) +{ + struct dw_hdmi *hdmi = hdcp->hdmi; + int val; + + dev_info(hdcp->dev, "hdcp_int is 0x%02x\n", hdcp_int); + + if (hdcp_int & HDMI_A_APIINTSTAT_KSVSHA1_CALC_DONE_INT) { + dev_dbg(hdcp->dev, "hdcp sink is a repeater\n"); + val = hdcp->read(hdmi, HDMI_A_KSVMEMCTRL); + if (val | HDMI_A_KSVMEMCTRL_KSV_SHA1_STATUS) { + dev_dbg(hdcp->dev, + "hdcp verifivation failed, waiting hdmi controller re-authentication!\n"); + } else { + dev_dbg(hdcp->dev, "hdcp verifivation succeeded!\n"); + /* reset HDCP */ + hdcp_modb(hdcp, HDMI_A_HDCPCFG1_SWRESET_ASSERT, + HDMI_A_HDCPCFG1_SWRESET_MASK, + HDMI_A_HDCPCFG1); + } + } + + if (hdcp_int & 0x40) { + hdcp->status = DW_HDCP_AUTH_FAIL; + dev_info(hdcp->dev, "hdcp auth fail\n"); + if (hdcp->remaining_times > 1) + hdcp->remaining_times--; + else if (hdcp->remaining_times == 1) + hdcp_modb(hdcp, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, + HDMI_A_HDCPCFG1); + } else if (hdcp_int & 0x80) { + dev_info(hdcp->dev, "hdcp auth success\n"); + hdcp->status = DW_HDCP_AUTH_SUCCESS; + } else if (hdcp_int & 0x10) { + dev_info(hdcp->dev, "i2c nack\n"); + trytimes++; + if (trytimes == 20) { + trytimes = 0; + dw_hdmi_hdcp1x_stop(hdcp); + } + } +} + +static ssize_t hdcp_enable_read(struct device *device, + struct device_attribute *attr, char *buf) +{ + bool enable = 0; + struct dw_hdcp *hdcp = g_hdcp; + + if (hdcp) + enable = hdcp->enable; + + return snprintf(buf, PAGE_SIZE, "%d\n", enable); +} + +static ssize_t hdcp_enable_write(struct device *device, + struct device_attribute *attr, const char *buf, + size_t count) +{ + bool enable; + struct dw_hdcp *hdcp = g_hdcp; + + if (!hdcp) + return -EINVAL; + + if (kstrtobool(buf, &enable)) + return -EINVAL; + + if (hdcp->enable != enable) { + if (enable) { + hdcp->enable = enable; + if (hdcp->hdcp2 && hdcp->hdcp2->hot_plug) { + return count; + } + + if (hdcp->read(hdcp->hdmi, HDMI_PHY_STAT0) & + HDMI_PHY_HPD) { + dw_hdmi_hdcp1x_start(hdcp); + } + } else { + if (hdcp->status != DW_HDCP_DISABLED) { + dw_hdmi_hdcp1x_stop(hdcp); + } + hdcp->enable = enable; + } + } + + return count; +} + +static DEVICE_ATTR(enable, 0644, hdcp_enable_read, hdcp_enable_write); + +static ssize_t hdcp_trytimes_read(struct device *device, + struct device_attribute *attr, char *buf) +{ + int trytimes = 0; + struct dw_hdcp *hdcp = g_hdcp; + + if (hdcp) + trytimes = hdcp->retry_times; + + return snprintf(buf, PAGE_SIZE, "%d\n", trytimes); +} + +static ssize_t hdcp_trytimes_write(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int trytimes; + struct dw_hdcp *hdcp = g_hdcp; + + if (!hdcp) + return -EINVAL; + + if (kstrtoint(buf, 0, &trytimes)) + return -EINVAL; + + if (hdcp->retry_times != trytimes) { + hdcp->retry_times = trytimes; + hdcp->remaining_times = hdcp->retry_times; + } + + return count; +} + +static DEVICE_ATTR(trytimes, 0644, hdcp_trytimes_read, hdcp_trytimes_write); + +static ssize_t hdcp_status_read(struct device *device, + struct device_attribute *attr, char *buf) +{ + int status = DW_HDCP_DISABLED; + struct dw_hdcp *hdcp = g_hdcp; + + if (hdcp) + status = hdcp->status; + + if (status == DW_HDCP_DISABLED) + return snprintf(buf, PAGE_SIZE, "hdcp disable\n"); + else if (status == DW_HDCP_AUTH_START) + return snprintf(buf, PAGE_SIZE, "hdcp_auth_start\n"); + else if (status == DW_HDCP_AUTH_SUCCESS) + return snprintf(buf, PAGE_SIZE, "hdcp_auth_success\n"); + else if (status == DW_HDCP_AUTH_FAIL) + return snprintf(buf, PAGE_SIZE, "hdcp_auth_fail\n"); + else + return snprintf(buf, PAGE_SIZE, "unknown status\n"); +} + +static DEVICE_ATTR(status, 0444, hdcp_status_read, NULL); + +static int dw_hdmi_hdcp_probe(struct platform_device *pdev) +{ + int ret = 0; + struct dw_hdcp *hdcp = pdev->dev.platform_data; + + dev_info(&pdev->dev, "%s...\n", __func__); + g_hdcp = hdcp; + hdcp->mdev.minor = MISC_DYNAMIC_MINOR; + hdcp->mdev.name = "hdmi_hdcp1x"; + hdcp->mdev.mode = 0666; + + if (misc_register(&hdcp->mdev)) { + dev_err(&pdev->dev, "HDCP: Could not add character driver\n"); + return -EINVAL; + } + + ret = device_create_file(hdcp->mdev.this_device, &dev_attr_enable); + if (ret) { + dev_err(&pdev->dev, "HDCP: Could not add sys file enable\n"); + ret = -EINVAL; + goto error0; + } + + ret = device_create_file(hdcp->mdev.this_device, &dev_attr_trytimes); + if (ret) { + dev_err(&pdev->dev, "HDCP: Could not add sys file trytimes\n"); + ret = -EINVAL; + goto error1; + } + + ret = device_create_file(hdcp->mdev.this_device, &dev_attr_status); + if (ret) { + dev_err(&pdev->dev, "HDCP: Could not add sys file status\n"); + ret = -EINVAL; + goto error2; + } + + /* retry time if hdcp auth fail. unlimited time if set 0 */ + hdcp->retry_times = 0; + hdcp->dev = &pdev->dev; + hdcp->hdcp_start = dw_hdmi_hdcp_start; + hdcp->hdcp_stop = dw_hdmi_hdcp_stop; + hdcp->hdcp_isr = dw_hdmi_hdcp_isr; + +#ifdef CONFIG_DW_HDMI_HDCP1X_ENABLED + hdcp_enable_write(NULL, NULL, "1", 1); +#endif + + dev_dbg(hdcp->dev, "%s success\n", __func__); + return 0; + +error2: + device_remove_file(hdcp->mdev.this_device, &dev_attr_trytimes); +error1: + device_remove_file(hdcp->mdev.this_device, &dev_attr_enable); +error0: + misc_deregister(&hdcp->mdev); + return ret; +} + +static int dw_hdmi_hdcp_remove(struct platform_device *pdev) +{ + struct dw_hdcp *hdcp = pdev->dev.platform_data; + + device_remove_file(hdcp->mdev.this_device, &dev_attr_trytimes); + device_remove_file(hdcp->mdev.this_device, &dev_attr_enable); + device_remove_file(hdcp->mdev.this_device, &dev_attr_status); + misc_deregister(&hdcp->mdev); + + kfree(hdcp->keys); + kfree(hdcp->seeds); + + return 0; +} + +struct platform_driver dw_hdmi_hdcp_driver = { + .probe = dw_hdmi_hdcp_probe, + .remove = dw_hdmi_hdcp_remove, + .driver = { + .name = DW_HDCP_DRIVER_NAME, + }, +}; + +//module_platform_driver(dw_hdmi_hdcp_driver); +MODULE_DESCRIPTION("DW HDMI transmitter HDCP driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/eswin/dw_hdmi_hdcp.h b/drivers/gpu/drm/eswin/dw_hdmi_hdcp.h new file mode 100644 index 000000000000..849de9037652 --- /dev/null +++ b/drivers/gpu/drm/eswin/dw_hdmi_hdcp.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) ESWIN Electronics Co.Ltd + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef DW_HDMI_HDCP_H +#define DW_HDMI_HDCP_H + +#include + +#define DW_HDCP_DRIVER_NAME "dw-hdmi-hdcp" +#define HDCP_PRIVATE_KEY_SIZE 280 +#define HDCP_KEY_SHA_SIZE 20 + +struct hdcp_keys { + u8 KSV[8]; + u8 devicekey[HDCP_PRIVATE_KEY_SIZE]; + u8 sha1[HDCP_KEY_SHA_SIZE]; +}; + +struct dw_hdcp2 { + int enable; + void (*start)(void); + void (*stop)(void); + + struct device *dev; + int wait_hdcp2_reset; + int hot_plug; + struct miscdevice mdev; + int auth_sucess; +}; + +struct dw_hdcp { + bool enable; + int retry_times; + int remaining_times; + char *seeds; + int invalidkey; + char *invalidkeys; + int hdcp2_enable; + int status; + u32 reg_io_width; + + struct dw_hdcp2 *hdcp2; + struct miscdevice mdev; + struct hdcp_keys *keys; + struct device *dev; + struct dw_hdmi *hdmi; + void __iomem *regs; + + void (*write)(struct dw_hdmi *hdmi, u8 val, int offset); + u8 (*read)(struct dw_hdmi *hdmi, int offset); + int (*hdcp_start)(struct dw_hdcp *hdcp); + int (*hdcp_stop)(struct dw_hdcp *hdcp); + void (*hdcp_isr)(struct dw_hdcp *hdcp, int hdcp_int); +}; + +extern u8 tv_hdmi_hdcp2_support(struct dw_hdmi *hdmi); +extern void dw_hdmi_hdcp2_init(struct dw_hdcp2 *hdcp2); +extern void dw_hdmi_hdcp2_remove(void); +extern void dw_hdmi_hdcp2_start(int enable); +extern void get_random_bytes(void *buf, int nbytes); +#endif diff --git a/drivers/gpu/drm/eswin/dw_hdmi_hdcp2.c b/drivers/gpu/drm/eswin/dw_hdmi_hdcp2.c new file mode 100644 index 000000000000..dd553bd9b72e --- /dev/null +++ b/drivers/gpu/drm/eswin/dw_hdmi_hdcp2.c @@ -0,0 +1,775 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "host_lib_driver_linux_if.h" +#include "dw_hdmi_hdcp.h" + +/** + * \file + * \ingroup HL_Driver_Kernel + * \brief Sample Linux Host Library Driver + * \copydoc HL_Driver_Kernel + */ + +/** + * \defgroup HL_Driver_Linux Sample Linux Host Library Driver + * \ingroup HL_Driver + * \brief Sample code for the Linux Host Library Driver. + * The Linux Host Library Driver is composed of 2 parts: + * 1. A kernel driver. + * 2. A file access instance. + * + * The kernel driver is the kernel executable code enabling the firmware to execute. + * It provides the access to the hardware register to interact with the firmware. + * + * The file access instance initializes the #hl_driver_t structure for the + * host library access. The Host Library references the file access to request the + * kernel operations. + */ + +/** + * \defgroup HL_Driver_Kernel Sample Linux Kernel Host Library Driver + * \ingroup HL_Driver_Linux + * \brief Example code for the Linux Kernel Host Library Driver. + * + * The Sample Linux Kernel Driver operates on the linux kernel. + * To install (requires root access): + * \code + insmod bin/linux_hld_module.ko verbose=0 + * \endcode + * + * To remove (requires root access): + * \code + rmmod linux_hld_module + * \endcode + * + * Example Linux Host Library Code: + * \code + */ + +#define MAX_HL_DEVICES 16 + +static bool randomize_mem = false; +module_param(randomize_mem, bool, 0); +MODULE_PARM_DESC(noverify, "Wipe memory allocations on startup (for debug)"); + +static struct dw_hdcp2 *g_dw_hdcp2; + +static void dw_hdcp2_stop(void) +{ + printk("func: %s; line: %d\n", __func__, __LINE__); + g_dw_hdcp2->hot_plug = 0; + dw_hdmi_hdcp2_start(0); +} + +static void dw_hdcp2_start(void) +{ + printk("func: %s; line: %d\n", __func__, __LINE__); + dw_hdmi_hdcp2_start(1); + g_dw_hdcp2->hot_plug = 1; +} + +// +// HL Device +// +typedef struct { + int allocated, initialized; + int code_loaded; + + int code_is_phys_mem; + dma_addr_t code_base; + uint32_t code_size; + uint8_t *code; + int data_is_phys_mem; + dma_addr_t data_base; + uint32_t data_size; + uint8_t *data; + + struct resource *hpi_resource; + uint8_t __iomem *hpi; +} hl_device; + +static hl_device hl_devices[MAX_HL_DEVICES]; + +/* HL_DRV_IOC_MEMINFO implementation */ +static long get_meminfo(hl_device *hl_dev, void __user *arg) +{ + struct hl_drv_ioc_meminfo info; + + if ((hl_dev == 0) || (arg == 0) || (hl_dev->hpi_resource == 0)) { + return -EFAULT; + } + + info.hpi_base = hl_dev->hpi_resource->start; + info.code_base = hl_dev->code_base; + info.code_size = hl_dev->code_size; + info.data_base = hl_dev->data_base; + info.data_size = hl_dev->data_size; + + if (copy_to_user(arg, &info, sizeof info) != 0) { + return -EFAULT; + } + + return 0; +} + +/* HL_DRV_IOC_LOAD_CODE implementation */ +static long load_code(hl_device *hl_dev, struct hl_drv_ioc_code __user *arg) +{ + struct hl_drv_ioc_code head; + + if ((hl_dev == 0) || (arg == 0) || (hl_dev->code == 0) || + (hl_dev->data == 0)) { + return -EFAULT; + } + + if (copy_from_user(&head, arg, sizeof head) != 0) + return -EFAULT; + + if (head.len > hl_dev->code_size) + return -ENOSPC; + + if (hl_dev->code_loaded) + return -EBUSY; + + if (randomize_mem) { + prandom_bytes(hl_dev->code, hl_dev->code_size); + prandom_bytes(hl_dev->data, hl_dev->data_size); + } + + if (copy_from_user(hl_dev->code, &arg->data, head.len) != 0) + return -EFAULT; + + hl_dev->code_loaded = 1; + return 0; +} + +/* HL_DRV_IOC_WRITE_DATA implementation */ +static long write_data(hl_device *hl_dev, struct hl_drv_ioc_data __user *arg) +{ + struct hl_drv_ioc_data head; + + if ((hl_dev == 0) || (arg == 0) || (hl_dev->data == 0)) { + return -EFAULT; + } + + if (copy_from_user(&head, arg, sizeof head) != 0) + return -EFAULT; + + if (hl_dev->data_size < head.len) + return -ENOSPC; + if (hl_dev->data_size - head.len < head.offset) + return -ENOSPC; + + if (copy_from_user(hl_dev->data + head.offset, &arg->data, head.len) != + 0) + return -EFAULT; + + return 0; +} + +/* HL_DRV_IOC_READ_DATA implementation */ +static long read_data(hl_device *hl_dev, struct hl_drv_ioc_data __user *arg) +{ + struct hl_drv_ioc_data head; + + if ((hl_dev == 0) || (arg == 0) || (hl_dev->data == 0)) { + return -EFAULT; + } + + if (copy_from_user(&head, arg, sizeof head) != 0) + return -EFAULT; + + if (hl_dev->data_size < head.len) + return -ENOSPC; + if (hl_dev->data_size - head.len < head.offset) + return -ENOSPC; + + if (copy_to_user(&arg->data, hl_dev->data + head.offset, head.len) != 0) + return -EFAULT; + + return 0; +} + +/* HL_DRV_IOC_MEMSET_DATA implementation */ +static long set_data(hl_device *hl_dev, void __user *arg) +{ + union { + struct hl_drv_ioc_data data; + unsigned char buf[sizeof(struct hl_drv_ioc_data) + 1]; + } u; + + if ((hl_dev == 0) || (arg == 0) || (hl_dev->data == 0)) { + return -EFAULT; + } + + if (copy_from_user(&u.data, arg, sizeof u.buf) != 0) + return -EFAULT; + + if (hl_dev->data_size < u.data.len) + return -ENOSPC; + if (hl_dev->data_size - u.data.len < u.data.offset) + return -ENOSPC; + + memset(hl_dev->data + u.data.offset, u.data.data[0], u.data.len); + return 0; +} + +/* HL_DRV_IOC_READ_HPI implementation */ +static long hpi_read(hl_device *hl_dev, void __user *arg) +{ + struct hl_drv_ioc_hpi_reg reg; + + if ((hl_dev == 0) || (arg == 0) || (hl_dev->hpi_resource == 0)) { + return -EFAULT; + } + + if (copy_from_user(®, arg, sizeof reg) != 0) + return -EFAULT; + + if ((reg.offset & 3) || + reg.offset >= resource_size(hl_dev->hpi_resource)) + return -EINVAL; + + reg.value = ioread32(hl_dev->hpi + reg.offset); + + if (copy_to_user(arg, ®, sizeof reg) != 0) + return -EFAULT; + + return 0; +} + +/* HL_DRV_IOC_WRITE_HPI implementation */ +static long hpi_write(hl_device *hl_dev, void __user *arg) +{ + struct hl_drv_ioc_hpi_reg reg; + + if ((hl_dev == 0) || (arg == 0)) { + return -EFAULT; + } + + if (copy_from_user(®, arg, sizeof reg) != 0) + return -EFAULT; + + if ((reg.offset & 3) || + reg.offset >= resource_size(hl_dev->hpi_resource)) + return -EINVAL; + + iowrite32(reg.value, hl_dev->hpi + reg.offset); + +#ifdef TROOT_GRIFFIN + // If Kill command + // (HL_GET_CMD_EVENT(krequest.data) == TROOT_CMD_SYSTEM_ON_EXIT_REQ)) + // + if ((reg.offset == 0x38) && ((reg.value & 0x000000ff) == 0x08)) { + hl_dev->code_loaded = 0; + } +#endif + return 0; +} + +static hl_device *alloc_hl_dev_slot(const struct hl_drv_ioc_meminfo *info) +{ + int i; + + if (info == 0) { + return 0; + } + + /* Check if we have a matching device (same HPI base) */ + for (i = 0; i < MAX_HL_DEVICES; i++) { + hl_device *slot = &hl_devices[i]; + if (slot->allocated && + (info->hpi_base == slot->hpi_resource->start)) + return slot; + } + + /* Find unused slot */ + for (i = 0; i < MAX_HL_DEVICES; i++) { + hl_device *slot = &hl_devices[i]; + if (!slot->allocated) { + slot->allocated = 1; + return slot; + } + } + + return 0; +} + +static void free_dma_areas(hl_device *hl_dev) +{ + if (hl_dev == 0) { + return; + } + + if (!hl_dev->code_is_phys_mem && hl_dev->code) { + dma_free_coherent(0, hl_dev->code_size, hl_dev->code, + hl_dev->code_base); + hl_dev->code = 0; + } + + if (!hl_dev->data_is_phys_mem && hl_dev->data) { + dma_free_coherent(0, hl_dev->data_size, hl_dev->data, + hl_dev->data_base); + hl_dev->data = 0; + } +} + +static int alloc_dma_areas(hl_device *hl_dev, + const struct hl_drv_ioc_meminfo *info) +{ + if ((hl_dev == 0) || (info == 0)) { + return -EFAULT; + } + + hl_dev->code_size = info->code_size; + hl_dev->code_is_phys_mem = + (info->code_base != HL_DRIVER_ALLOCATE_DYNAMIC_MEM); + + if (hl_dev->code_is_phys_mem && (hl_dev->code == 0)) { + /* TODO: support highmem */ + hl_dev->code_base = info->code_base; + hl_dev->code = phys_to_virt(hl_dev->code_base); + } else { + dma_set_mask_and_coherent(g_dw_hdcp2->dev, DMA_BIT_MASK(32)); + hl_dev->code = + dma_alloc_coherent(g_dw_hdcp2->dev, hl_dev->code_size, + &hl_dev->code_base, GFP_KERNEL); + if (!hl_dev->code) { + return -ENOMEM; + } + } + + hl_dev->data_size = info->data_size; + hl_dev->data_is_phys_mem = + (info->data_base != HL_DRIVER_ALLOCATE_DYNAMIC_MEM); + + if (hl_dev->data_is_phys_mem && (hl_dev->data == 0)) { + hl_dev->data_base = info->data_base; + hl_dev->data = phys_to_virt(hl_dev->data_base); + } else { + hl_dev->data = + dma_alloc_coherent(g_dw_hdcp2->dev, hl_dev->data_size, + &hl_dev->data_base, GFP_KERNEL); + if (!hl_dev->data) { + free_dma_areas(hl_dev); + return -ENOMEM; + } + } + + return 0; +} + +/* HL_DRV_IOC_INIT implementation */ +static long init(struct file *f, void __user *arg) +{ + struct resource *hpi_mem; + struct hl_drv_ioc_meminfo info; + hl_device *hl_dev; + int rc; + + if ((f == 0) || (arg == 0)) { + return -EFAULT; + } + + if (copy_from_user(&info, arg, sizeof info) != 0) + return -EFAULT; + + hl_dev = alloc_hl_dev_slot(&info); + if (!hl_dev) + return -EMFILE; + + if (!hl_dev->initialized) { + rc = alloc_dma_areas(hl_dev, &info); + if (rc < 0) + goto err_free; + + hpi_mem = request_mem_region(info.hpi_base, 128, "hl_dev-hpi"); + if (!hpi_mem) { + rc = -EADDRNOTAVAIL; + goto err_free; + } + + hl_dev->hpi = ioremap(hpi_mem->start, resource_size(hpi_mem)); + if (!hl_dev->hpi) { + rc = -ENOMEM; + goto err_release_region; + } + hl_dev->hpi_resource = hpi_mem; + hl_dev->initialized = 1; + } + + f->private_data = hl_dev; + return 0; + +err_release_region: + release_resource(hpi_mem); +err_free: + free_dma_areas(hl_dev); + hl_dev->initialized = 0; + hl_dev->allocated = 0; + hl_dev->hpi_resource = 0; + hl_dev->hpi = 0; + + return rc; +} + +static void free_hl_dev_slot(hl_device *slot) +{ + if (slot == 0) { + return; + } + + if (!slot->allocated) + return; + + if (slot->initialized) { + if (slot->hpi) { + iounmap(slot->hpi); + slot->hpi = 0; + } + + if (slot->hpi_resource) { + release_mem_region(slot->hpi_resource->start, 128); + slot->hpi_resource = 0; + } + + free_dma_areas(slot); + } + + slot->initialized = 0; + slot->allocated = 0; +} + +static long hld_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ + hl_device *hl_dev; + void __user *data; + + if (f == 0) { + return -EFAULT; + } + + hl_dev = f->private_data; + data = (void __user *)arg; + + if (cmd == HL_DRV_IOC_INIT) { + return init(f, data); + } else if (!hl_dev) { + return -EAGAIN; + } + + switch (cmd) { + case HL_DRV_IOC_INIT: + return init(f, data); + case HL_DRV_IOC_MEMINFO: + return get_meminfo(hl_dev, data); + case HL_DRV_IOC_READ_HPI: + return hpi_read(hl_dev, data); + case HL_DRV_IOC_WRITE_HPI: + return hpi_write(hl_dev, data); + case HL_DRV_IOC_LOAD_CODE: + return load_code(hl_dev, data); + case HL_DRV_IOC_WRITE_DATA: + return write_data(hl_dev, data); + case HL_DRV_IOC_READ_DATA: + return read_data(hl_dev, data); + case HL_DRV_IOC_MEMSET_DATA: + return set_data(hl_dev, data); + + case DW_DRV_IOC_CONNECT_STATUS: + return g_dw_hdcp2->hot_plug; + case DW_DRV_IOC_CONNECT_SET: + printk("set hdcp2 reset one\n"); + g_dw_hdcp2->wait_hdcp2_reset = 1; + dw_hdmi_hdcp2_start(1); + return 0; + case DW_DRV_IOC_DISCONNECT_SET: + if (g_dw_hdcp2->wait_hdcp2_reset == 1) { + printk("set hdcp2 reset zero\n"); + g_dw_hdcp2->wait_hdcp2_reset = 0; + dw_hdmi_hdcp2_start(0); + } + if (g_dw_hdcp2->auth_sucess == 1) { + g_dw_hdcp2->auth_sucess = 0; + } + return 0; + case DW_DRV_IOC_AUTH_SUCCESS: + g_dw_hdcp2->auth_sucess = 1; + return 0; + case DW_DRV_IOC_AUTH_FAIL: + g_dw_hdcp2->auth_sucess = 0; + return 0; + case DW_DRV_IOC_NO_CAPACITY: + printk("set hdcp2 reset zero 3005\n"); + g_dw_hdcp2->hot_plug = 0; + g_dw_hdcp2->wait_hdcp2_reset = 0; + dw_hdmi_hdcp2_start(0); + dw_hdmi_hdcp2_start(2); + return 0; + } + + return -ENOTTY; +} + +static const struct file_operations hld_file_operations = { +#ifdef CONFIG_COMPAT + .compat_ioctl = hld_ioctl, +#else + .unlocked_ioctl = hld_ioctl, +#endif + .owner = THIS_MODULE, +}; + +static struct miscdevice hld_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "hl_dev", + .fops = &hld_file_operations, +}; + +static int hld_init(void) +{ + int i; + + printk("%s...\n", __func__); + for (i = 0; i < MAX_HL_DEVICES; i++) { + hl_devices[i].allocated = 0; + hl_devices[i].initialized = 0; + hl_devices[i].code_loaded = 0; + hl_devices[i].code = 0; + hl_devices[i].data = 0; + hl_devices[i].hpi_resource = 0; + hl_devices[i].hpi = 0; + } + return misc_register(&hld_device); +} + +static void hld_exit(void) +{ + int i; + + for (i = 0; i < MAX_HL_DEVICES; i++) { + free_hl_dev_slot(&hl_devices[i]); + } + + misc_deregister(&hld_device); +} + +static int dw_hdmi2_hdcp2_clk_enable(struct device *dev) +{ + struct clk *pclk; + //struct clk *aclk; + struct clk *hdcp2_clk_hdmi; + + pclk = devm_clk_get(dev, "pclk_hdcp2"); + if (IS_ERR(pclk)) { + pr_err("Unable to get hdcp2 pclk\n"); + return -1; + } + clk_prepare_enable(pclk); +#if 0 + aclk = devm_clk_get(dev, "aclk_hdcp2"); + if (IS_ERR(aclk)) { + pr_err("Unable to get hdcp2 aclk\n"); + return -1; + } + clk_prepare_enable(aclk); +#endif + hdcp2_clk_hdmi = devm_clk_get(dev, "hdcp2_clk_hdmi"); + if (IS_ERR(hdcp2_clk_hdmi)) { + pr_err("Unable to get hdcp2_clk_hdmi\n"); + return -1; + } + clk_prepare_enable(hdcp2_clk_hdmi); + + return 0; +} + +static ssize_t hdcp2_show_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", g_dw_hdcp2->enable); +} + +static ssize_t hdcp2_store_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int enable; + if (kstrtoint(buf, 0, &enable)) + return size; + + if (g_dw_hdcp2->enable != enable) { + g_dw_hdcp2->enable = enable; + if (enable) { + dw_hdmi_hdcp2_start(3); + } else { + if (g_dw_hdcp2->hot_plug) { + g_dw_hdcp2->stop(); + dw_hdmi_hdcp2_start(2); + } + } + } + return size; +} + +static ssize_t hdcp2_show_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (g_dw_hdcp2->enable != 1) { + return snprintf(buf, PAGE_SIZE, "%s\n", "no enable hdcp2"); + } else if (!g_dw_hdcp2->hot_plug) { + return snprintf(buf, PAGE_SIZE, "%s\n", "hdcp2 no auth"); + } else { + if (g_dw_hdcp2->auth_sucess) + return snprintf(buf, PAGE_SIZE, "%s\n", + "hdcp2 auth sucess"); + else + return snprintf(buf, PAGE_SIZE, "%s\n", + "no already auth sucess"); + } +} + +static DEVICE_ATTR(enable, 0644, hdcp2_show_enable, hdcp2_store_enable); +static DEVICE_ATTR(status, 0444, hdcp2_show_status, NULL); + +static int create_device_node(void) +{ + int ret; + + if (!g_dw_hdcp2) + return -1; + g_dw_hdcp2->mdev.minor = MISC_DYNAMIC_MINOR; + g_dw_hdcp2->mdev.name = "hdcp2_node"; + g_dw_hdcp2->mdev.mode = 0666; + if (misc_register(&(g_dw_hdcp2->mdev))) { + pr_err("HDCP2: Could not add character driver\n"); + return -1; + } + + ret = device_create_file(g_dw_hdcp2->mdev.this_device, + &dev_attr_enable); + if (ret) { + pr_err("HDCP: Could not add sys file enable\n"); + ret = -EINVAL; + goto error0; + } + + ret = device_create_file(g_dw_hdcp2->mdev.this_device, + &dev_attr_status); + if (ret) { + pr_err("HDCP: Could not add sys file status\n"); + ret = -EINVAL; + goto error1; + } + + return 0; + +error1: + device_remove_file(g_dw_hdcp2->mdev.this_device, &dev_attr_enable); +error0: + misc_deregister(&g_dw_hdcp2->mdev); + return ret; +} + +static void end_device_node(void) +{ + if (g_dw_hdcp2) + misc_deregister(&(g_dw_hdcp2->mdev)); +} + +static int eswin_hdmi_hdcp2_probe(struct platform_device *pdev) +{ + struct device *hdcp2_dev = &pdev->dev; + + printk("%s...\n", __func__); + g_dw_hdcp2 = kmalloc(sizeof(*g_dw_hdcp2), GFP_KERNEL); + if (!g_dw_hdcp2) { + printk("malloc g_dw_hdcp2 error\n"); + return -ENOMEM; + } + memset(g_dw_hdcp2, 0, sizeof(*g_dw_hdcp2)); + + g_dw_hdcp2->dev = hdcp2_dev; + g_dw_hdcp2->stop = dw_hdcp2_stop; + g_dw_hdcp2->start = dw_hdcp2_start; + hld_init(); + dw_hdmi2_hdcp2_clk_enable(hdcp2_dev); + dw_hdmi_hdcp2_init(g_dw_hdcp2); + dw_hdmi_hdcp2_start(3); + + create_device_node(); + return 0; +} + +static int eswin_hdmi_hdcp2_remove(struct platform_device *pdev) +{ + printk("%s...\n", __func__); + dw_hdmi_hdcp2_remove(); + end_device_node(); + hld_exit(); + kfree(g_dw_hdcp2); + g_dw_hdcp2 = NULL; + + return 0; +} + +static void eswin_hdmi_hdcp2_shutdown(struct platform_device *pdev) +{ + printk("%s...\n", __func__); +} + +#if defined(CONFIG_OF) +static const struct of_device_id dw_hdmi_hdcp2_dt_ids[] = { + { + .compatible = "eswin,dw-hdmi-hdcp2", + }, + {} +}; + +MODULE_DEVICE_TABLE(of, dw_hdmi_hdcp2_dt_ids); +#endif + +struct platform_driver dw_hdmi_hdcp2_driver = { + .probe = eswin_hdmi_hdcp2_probe, + .remove = eswin_hdmi_hdcp2_remove, + .shutdown = eswin_hdmi_hdcp2_shutdown, + .driver = { + .name = "dw-hdmi-hdcp2", + .owner = THIS_MODULE, +#if defined(CONFIG_OF) + .of_match_table = of_match_ptr(dw_hdmi_hdcp2_dt_ids), +#endif + }, +}; + +static int __init hdmi_hdcp2_init(void) +{ + printk("%s...\n", __func__); + return platform_driver_register(&dw_hdmi_hdcp2_driver); +} + +static void hdmi_hdcp2_exit(void) +{ + platform_driver_unregister(&dw_hdmi_hdcp2_driver); +} + +late_initcall_sync(hdmi_hdcp2_init); +module_exit(hdmi_hdcp2_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Synopsys, Inc."); +MODULE_DESCRIPTION("Linux Host Library Driver"); diff --git a/drivers/gpu/drm/eswin/dw_hdmi_i2s_audio.c b/drivers/gpu/drm/eswin/dw_hdmi_i2s_audio.c new file mode 100644 index 000000000000..a2f3099b6bd2 --- /dev/null +++ b/drivers/gpu/drm/eswin/dw_hdmi_i2s_audio.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dw-hdmi-i2s-audio.c + * + * Copyright (c) 2017 Renesas Solutions Corp. + * Kuninori Morimoto + */ + +#include +#include + +#include + +#include + +#include "dw_hdmi.h" +#include "dw_hdmi_audio.h" + +#define DRIVER_NAME "dw-hdmi-i2s-audio" + +static inline void hdmi_write(struct dw_hdmi_i2s_audio_data *audio, u8 val, + int offset) +{ + struct dw_hdmi *hdmi = audio->hdmi; + + audio->write(hdmi, val, offset); +} + +static inline u8 hdmi_read(struct dw_hdmi_i2s_audio_data *audio, int offset) +{ + struct dw_hdmi *hdmi = audio->hdmi; + + return audio->read(hdmi, offset); +} + +static int dw_hdmi_i2s_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct dw_hdmi_i2s_audio_data *audio = data; + struct dw_hdmi *hdmi = audio->hdmi; + u8 conf0 = 0; + u8 conf1 = 0; + u8 inputclkfs = 0; + + /* it cares I2S only */ + if (fmt->bit_clk_master | fmt->frame_clk_master) { + dev_err(dev, "unsupported clock settings\n"); + return -EINVAL; + } + + /* Reset the FIFOs before applying new params */ + hdmi_write(audio, HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0); + hdmi_write(audio, (u8)~HDMI_MC_SWRSTZ_I2SSWRST_REQ, HDMI_MC_SWRSTZ); + + inputclkfs = HDMI_AUD_INPUTCLKFS_64FS; + conf0 = (HDMI_AUD_CONF0_I2S_SELECT | HDMI_AUD_CONF0_I2S_EN0); + + /* Enable the required i2s lanes */ + switch (hparms->channels) { + case 7 ... 8: + conf0 |= HDMI_AUD_CONF0_I2S_EN3; + fallthrough; + case 5 ... 6: + conf0 |= HDMI_AUD_CONF0_I2S_EN2; + fallthrough; + case 3 ... 4: + conf0 |= HDMI_AUD_CONF0_I2S_EN1; + /* Fall-thru */ + } + + switch (hparms->sample_width) { + case 16: + conf1 = HDMI_AUD_CONF1_WIDTH_16; + break; + case 24: + case 32: + conf1 = HDMI_AUD_CONF1_WIDTH_24; + break; + } + + switch (fmt->fmt) { + case HDMI_I2S: + conf1 |= HDMI_AUD_CONF1_MODE_I2S; + break; + case HDMI_RIGHT_J: + conf1 |= HDMI_AUD_CONF1_MODE_RIGHT_J; + break; + case HDMI_LEFT_J: + conf1 |= HDMI_AUD_CONF1_MODE_LEFT_J; + break; + case HDMI_DSP_A: + conf1 |= HDMI_AUD_CONF1_MODE_BURST_1; + break; + case HDMI_DSP_B: + conf1 |= HDMI_AUD_CONF1_MODE_BURST_2; + break; + default: + dev_err(dev, "unsupported format\n"); + return -EINVAL; + } + + dw_hdmi_set_sample_rate(hdmi, hparms->sample_rate); + dw_hdmi_set_channel_status(hdmi, hparms->iec.status); + dw_hdmi_set_channel_count(hdmi, hparms->channels); + dw_hdmi_set_channel_allocation(hdmi, hparms->cea.channel_allocation); + + hdmi_write(audio, inputclkfs, HDMI_AUD_INPUTCLKFS); + hdmi_write(audio, conf0, HDMI_AUD_CONF0); + hdmi_write(audio, conf1, HDMI_AUD_CONF1); + + return 0; +} + +static int dw_hdmi_i2s_audio_startup(struct device *dev, void *data) +{ + struct dw_hdmi_i2s_audio_data *audio = data; + struct dw_hdmi *hdmi = audio->hdmi; + + dw_hdmi_audio_enable(hdmi); + + return 0; +} + +static void dw_hdmi_i2s_audio_shutdown(struct device *dev, void *data) +{ + struct dw_hdmi_i2s_audio_data *audio = data; + struct dw_hdmi *hdmi = audio->hdmi; + + dw_hdmi_audio_disable(hdmi); +} + +static int dw_hdmi_i2s_get_eld(struct device *dev, void *data, uint8_t *buf, + size_t len) +{ + struct dw_hdmi_i2s_audio_data *audio = data; + u8 *eld; + + eld = audio->get_eld(audio->hdmi); + if (eld) + memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len)); + else + /* Pass en empty ELD if connector not available */ + memset(buf, 0, len); + + return 0; +} + +static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component, + struct device_node *endpoint) +{ + struct of_endpoint of_ep; + int ret; + + ret = of_graph_parse_endpoint(endpoint, &of_ep); + if (ret < 0) + return ret; + + /* + * HDMI sound should be located as reg = <2> + * Then, it is sound port 0 + */ + if (of_ep.port == 2) + return 0; + + return -EINVAL; +} + +static int dw_hdmi_i2s_hook_plugged_cb(struct device *dev, void *data, + hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + struct dw_hdmi_i2s_audio_data *audio = data; + struct dw_hdmi *hdmi = audio->hdmi; + + return dw_hdmi_set_plugged_cb(hdmi, fn, codec_dev); +} + +static const struct hdmi_codec_ops dw_hdmi_i2s_ops = { + .hw_params = dw_hdmi_i2s_hw_params, + .audio_startup = dw_hdmi_i2s_audio_startup, + .audio_shutdown = dw_hdmi_i2s_audio_shutdown, + .get_eld = dw_hdmi_i2s_get_eld, + .get_dai_id = dw_hdmi_i2s_get_dai_id, + .hook_plugged_cb = dw_hdmi_i2s_hook_plugged_cb, +}; + +static int snd_dw_hdmi_probe(struct platform_device *pdev) +{ + struct dw_hdmi_i2s_audio_data *audio = pdev->dev.platform_data; + struct platform_device_info pdevinfo; + struct hdmi_codec_pdata pdata; + struct platform_device *platform; + + pdata.ops = &dw_hdmi_i2s_ops; + pdata.i2s = 1; + pdata.max_i2s_channels = 8; + pdata.data = audio; + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.parent = pdev->dev.parent; + pdevinfo.id = PLATFORM_DEVID_AUTO; + pdevinfo.name = HDMI_CODEC_DRV_NAME; + pdevinfo.data = &pdata; + pdevinfo.size_data = sizeof(pdata); + pdevinfo.dma_mask = DMA_BIT_MASK(32); + + platform = platform_device_register_full(&pdevinfo); + if (IS_ERR(platform)) + return PTR_ERR(platform); + + dev_set_drvdata(&pdev->dev, platform); + + return 0; +} + +static int snd_dw_hdmi_remove(struct platform_device *pdev) +{ + struct platform_device *platform = dev_get_drvdata(&pdev->dev); + + platform_device_unregister(platform); + + return 0; +} + +struct platform_driver snd_dw_hdmi_driver = { + .probe = snd_dw_hdmi_probe, + .remove = snd_dw_hdmi_remove, + .driver = { + .name = DRIVER_NAME, + }, +}; +//module_platform_driver(snd_dw_hdmi_driver); + +MODULE_DESCRIPTION("Synopsis Designware HDMI I2S ALSA SoC interface"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/eswin/es_crtc.c b/drivers/gpu/drm/eswin/es_crtc.c new file mode 100644 index 000000000000..96cbe4badf91 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_crtc.c @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#include +#include +#include + +#include +#include +#include + +#include "es_drm.h" +#include "es_crtc.h" + +void es_crtc_destroy(struct drm_crtc *crtc) +{ + struct es_crtc *es_crtc = to_es_crtc(crtc); + + drm_crtc_cleanup(crtc); + kfree(es_crtc); +} + +static void es_crtc_reset(struct drm_crtc *crtc) +{ + struct es_crtc_state *state; + + if (crtc->state) { + __drm_atomic_helper_crtc_destroy_state(crtc->state); + + state = to_es_crtc_state(crtc->state); + kfree(state); + crtc->state = NULL; + } + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state == NULL) + return; + + __drm_atomic_helper_crtc_reset(crtc, &state->base); + + state->sync_mode = ES_SINGLE_DC; + state->output_fmt = MEDIA_BUS_FMT_RBG888_1X24; + state->encoder_type = DRM_MODE_ENCODER_NONE; +#ifdef CONFIG_ESWIN_MMU + state->mmu_prefetch = ES_MMU_PREFETCH_DISABLE; +#endif +} + +static struct drm_crtc_state * +es_crtc_atomic_duplicate_state(struct drm_crtc *crtc) +{ + struct es_crtc_state *ori_state; + struct es_crtc_state *state; + + if (WARN_ON(!crtc->state)) + return NULL; + + ori_state = to_es_crtc_state(crtc->state); + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base); + + state->sync_mode = ori_state->sync_mode; + state->output_fmt = ori_state->output_fmt; + state->encoder_type = ori_state->encoder_type; + state->bpp = ori_state->bpp; + state->dither_enable = ori_state->dither_enable; + state->underflow = ori_state->underflow; + state->bg_color = ori_state->bg_color; +#ifdef CONFIG_ESWIN_MMU + state->mmu_prefetch = ori_state->mmu_prefetch; +#endif + + return &state->base; +} + +static void es_crtc_atomic_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + __drm_atomic_helper_crtc_destroy_state(state); + kfree(to_es_crtc_state(state)); +} + +static int es_crtc_atomic_set_property(struct drm_crtc *crtc, + struct drm_crtc_state *state, + struct drm_property *property, + uint64_t val) +{ + struct es_crtc *es_crtc = to_es_crtc(crtc); + struct es_crtc_state *es_crtc_state = to_es_crtc_state(state); + + if (property == es_crtc->sync_mode) + es_crtc_state->sync_mode = val; + else if (property == es_crtc->mmu_prefetch) + es_crtc_state->mmu_prefetch = val; + else if (property == es_crtc->dither) + es_crtc_state->dither_enable = val; + else if (property == es_crtc->bg_color) + es_crtc_state->bg_color = val; + else + return -EINVAL; + + return 0; +} + +static int es_crtc_atomic_get_property(struct drm_crtc *crtc, + const struct drm_crtc_state *state, + struct drm_property *property, + uint64_t *val) +{ + struct es_crtc *es_crtc = to_es_crtc(crtc); + const struct es_crtc_state *es_crtc_state = + container_of(state, const struct es_crtc_state, base); + + if (property == es_crtc->sync_mode) + *val = es_crtc_state->sync_mode; + else if (property == es_crtc->mmu_prefetch) + *val = es_crtc_state->mmu_prefetch; + else if (property == es_crtc->dither) + *val = es_crtc_state->dither_enable; + else if (property == es_crtc->bg_color) + *val = es_crtc_state->bg_color; + else + return -EINVAL; + + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static int es_crtc_debugfs_show(struct seq_file *s, void *data) +{ + struct drm_crtc *crtc = s->private; + struct es_crtc_state *crtc_state = to_es_crtc_state(crtc->state); + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + + seq_printf(s, "crtc[%u]: %s\n", crtc->base.id, crtc->name); + seq_printf(s, "\tactive = %d\n", crtc->state->active); + seq_printf(s, "\tsize = %dx%d\n", mode->hdisplay, mode->vdisplay); + seq_printf(s, "\tbpp = %u\n", crtc_state->bpp); + seq_printf(s, "\tunderflow = %d\n", crtc_state->underflow); + + return 0; +} + +static int es_crtc_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, es_crtc_debugfs_show, inode->i_private); +} + +static const struct file_operations es_crtc_debugfs_fops = { + .open = es_crtc_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int es_crtc_debugfs_init(struct drm_crtc *crtc) +{ + debugfs_create_file("status", 0444, crtc->debugfs_entry, crtc, + &es_crtc_debugfs_fops); + + return 0; +} +#else +static int es_crtc_debugfs_init(struct drm_crtc *crtc) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static int es_crtc_late_register(struct drm_crtc *crtc) +{ + return es_crtc_debugfs_init(crtc); +} + +static int es_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct es_crtc *es_crtc = to_es_crtc(crtc); + + es_crtc->funcs->enable_vblank(es_crtc->dev, true); + + return 0; +} + +static void es_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct es_crtc *es_crtc = to_es_crtc(crtc); + + es_crtc->funcs->enable_vblank(es_crtc->dev, false); +} + +static const struct drm_crtc_funcs es_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .destroy = es_crtc_destroy, + .page_flip = drm_atomic_helper_page_flip, + .reset = es_crtc_reset, + .atomic_duplicate_state = es_crtc_atomic_duplicate_state, + .atomic_destroy_state = es_crtc_atomic_destroy_state, + .atomic_set_property = es_crtc_atomic_set_property, + .atomic_get_property = es_crtc_atomic_get_property, + // .gamma_set = drm_atomic_helper_legacy_gamma_set, /* TODO: */ + .late_register = es_crtc_late_register, + .enable_vblank = es_crtc_enable_vblank, + .disable_vblank = es_crtc_disable_vblank, +}; + +static u8 cal_pixel_bits(u32 bus_format) +{ + u8 bpp; + + switch (bus_format) { + case MEDIA_BUS_FMT_RGB565_1X16: + case MEDIA_BUS_FMT_UYVY8_1X16: + bpp = 16; + break; + case MEDIA_BUS_FMT_RGB666_1X18: + case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: + bpp = 18; + break; + case MEDIA_BUS_FMT_UYVY10_1X20: + bpp = 20; + break; + case MEDIA_BUS_FMT_BGR888_1X24: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_YUV8_1X24: + bpp = 24; + break; + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + case MEDIA_BUS_FMT_YUV10_1X30: + bpp = 30; + break; + default: + bpp = 24; + break; + } + + return bpp; +} + +static bool es_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct es_crtc *es_crtc = to_es_crtc(crtc); + + return es_crtc->funcs->mode_fixup(es_crtc->dev, mode, adjusted_mode); +} + +static void es_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *old_state) +{ + struct es_crtc *es_crtc = to_es_crtc(crtc); + struct es_crtc_state *es_crtc_state = to_es_crtc_state(crtc->state); + + es_crtc_state->bpp = cal_pixel_bits(es_crtc_state->output_fmt); + + es_crtc->funcs->enable(es_crtc->dev, crtc); + + drm_crtc_vblank_on(crtc); +} + +static void es_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *old_state) +{ + struct es_crtc *es_crtc = to_es_crtc(crtc); + + drm_crtc_vblank_off(crtc); + + es_crtc->funcs->disable(es_crtc->dev); + + if (crtc->state->event && !crtc->state->active) { + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + spin_unlock_irq(&crtc->dev->event_lock); + + crtc->state->event = NULL; + } +} + +static void es_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_state *old_crtc_state) +{ + struct es_crtc *es_crtc = to_es_crtc(crtc); + struct device *dev = es_crtc->dev; + struct drm_property_blob *blob = crtc->state->gamma_lut; + struct drm_color_lut *lut; + + if (crtc->state->color_mgmt_changed) { + if ((blob) && (blob->length)) { + lut = blob->data; + es_crtc->funcs->set_gamma(dev, lut, + blob->length / sizeof(*lut)); + es_crtc->funcs->enable_gamma(dev, true); + } else { + es_crtc->funcs->enable_gamma(dev, false); + } + } +} + +static void es_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *old_crtc_state) +{ + struct es_crtc *es_crtc = to_es_crtc(crtc); + struct drm_pending_vblank_event *event = crtc->state->event; + + es_crtc->funcs->commit(es_crtc->dev); + + if (event) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_arm_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); + crtc->state->event = NULL; + } +} + +static const struct drm_crtc_helper_funcs es_crtc_helper_funcs = { + .mode_fixup = es_crtc_mode_fixup, + .atomic_enable = es_crtc_atomic_enable, + .atomic_disable = es_crtc_atomic_disable, + .atomic_begin = es_crtc_atomic_begin, + .atomic_flush = es_crtc_atomic_flush, +}; + +static const struct drm_prop_enum_list es_sync_mode_enum_list[] = { + { ES_SINGLE_DC, "single dc mode" }, + { ES_MULTI_DC_PRIMARY, "primary dc for multi dc mode" }, + { ES_MULTI_DC_SECONDARY, "secondary dc for multi dc mode" }, +}; + +#ifdef CONFIG_ESWIN_MMU +static const struct drm_prop_enum_list es_mmu_prefetch_enum_list[] = { + { ES_MMU_PREFETCH_DISABLE, "disable mmu prefetch" }, + { ES_MMU_PREFETCH_ENABLE, "enable mmu prefetch" }, +}; +#endif + +struct es_crtc *es_crtc_create(struct drm_device *drm_dev, + struct es_dc_info *info) +{ + struct es_crtc *crtc; + int ret; + + if (!info) + return NULL; + + crtc = kzalloc(sizeof(struct es_crtc), GFP_KERNEL); + if (!crtc) + return NULL; + + ret = drm_crtc_init_with_planes(drm_dev, &crtc->base, NULL, NULL, + &es_crtc_funcs, + info->name ? info->name : NULL); + if (ret) + goto err_free_crtc; + + drm_crtc_helper_add(&crtc->base, &es_crtc_helper_funcs); + + /* Set up the crtc properties */ + if (info->pipe_sync) { + crtc->sync_mode = drm_property_create_enum( + drm_dev, 0, "SYNC_MODE", es_sync_mode_enum_list, + ARRAY_SIZE(es_sync_mode_enum_list)); + + if (!crtc->sync_mode) + goto err_cleanup_crts; + + drm_object_attach_property(&crtc->base.base, crtc->sync_mode, + ES_SINGLE_DC); + } + + if (info->gamma_size) { + ret = drm_mode_crtc_set_gamma_size(&crtc->base, + info->gamma_size); + if (ret) + goto err_cleanup_crts; + + drm_crtc_enable_color_mgmt(&crtc->base, 0, false, + info->gamma_size); + } + + if (info->background) { + crtc->bg_color = drm_property_create_range( + drm_dev, 0, "BG_COLOR", 0, 0xffffffff); + + if (!crtc->bg_color) + goto err_cleanup_crts; + + drm_object_attach_property(&crtc->base.base, crtc->bg_color, 0); + } + + crtc->dither = drm_property_create_bool(drm_dev, 0, "DITHER_ENABLED"); + if (!crtc->dither) + goto err_cleanup_crts; + + drm_object_attach_property(&crtc->base.base, crtc->dither, 0); + +#ifdef CONFIG_ESWIN_MMU + if (info->mmu_prefetch) { + crtc->mmu_prefetch = drm_property_create_enum( + drm_dev, 0, "MMU_PREFETCH", es_mmu_prefetch_enum_list, + ARRAY_SIZE(es_mmu_prefetch_enum_list)); + if (!crtc->mmu_prefetch) + goto err_cleanup_crts; + + drm_object_attach_property(&crtc->base.base, crtc->mmu_prefetch, + ES_MMU_PREFETCH_DISABLE); + } +#endif + + crtc->max_bpc = info->max_bpc; + crtc->color_formats = info->color_formats; + return crtc; + +err_cleanup_crts: + drm_crtc_cleanup(&crtc->base); + +err_free_crtc: + kfree(crtc); + return NULL; +} + +void es_crtc_handle_vblank(struct drm_crtc *crtc, bool underflow) +{ + struct es_crtc_state *es_crtc_state = to_es_crtc_state(crtc->state); + + drm_crtc_handle_vblank(crtc); + + es_crtc_state->underflow = underflow; +} diff --git a/drivers/gpu/drm/eswin/es_crtc.h b/drivers/gpu/drm/eswin/es_crtc.h new file mode 100644 index 000000000000..5f7cf9009d4f --- /dev/null +++ b/drivers/gpu/drm/eswin/es_crtc.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#ifndef __ES_CRTC_H__ +#define __ES_CRTC_H__ + +#include +#include + +#include "es_type.h" + +struct es_crtc_funcs { + void (*enable)(struct device *dev, struct drm_crtc *crtc); + void (*disable)(struct device *dev); + bool (*mode_fixup)(struct device *dev, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); + void (*set_gamma)(struct device *dev, struct drm_color_lut *lut, + unsigned int size); + void (*enable_gamma)(struct device *dev, bool enable); + void (*enable_vblank)(struct device *dev, bool enable); + void (*commit)(struct device *dev); +}; + +struct es_crtc_state { + struct drm_crtc_state base; + + u32 sync_mode; + u32 output_fmt; + u32 bg_color; + u8 encoder_type; + u8 mmu_prefetch; + u8 bpp; + bool dither_enable; + bool underflow; +}; + +struct es_crtc { + struct drm_crtc base; + struct device *dev; + struct drm_pending_vblank_event *event; + unsigned int max_bpc; + unsigned int color_formats; /* supported color format */ + + struct drm_property *sync_mode; + struct drm_property *mmu_prefetch; + struct drm_property *dither; + struct drm_property *bg_color; + + const struct es_crtc_funcs *funcs; +}; + +void es_crtc_destroy(struct drm_crtc *crtc); + +struct es_crtc *es_crtc_create(struct drm_device *drm_dev, + struct es_dc_info *info); +void es_crtc_handle_vblank(struct drm_crtc *crtc, bool underflow); + +static inline struct es_crtc *to_es_crtc(struct drm_crtc *crtc) +{ + return container_of(crtc, struct es_crtc, base); +} + +static inline struct es_crtc_state * +to_es_crtc_state(struct drm_crtc_state *state) +{ + return container_of(state, struct es_crtc_state, base); +} +#endif /* __ES_CRTC_H__ */ diff --git a/drivers/gpu/drm/eswin/es_dc.c b/drivers/gpu/drm/eswin/es_dc.c new file mode 100644 index 000000000000..9e485f7edcc4 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_dc.c @@ -0,0 +1,1083 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "es_drm.h" +#include "es_type.h" +#include "es_dc_hw.h" +#include "es_dc.h" +#include "es_crtc.h" +#include "es_drv.h" + +#define VO_ACLK_HIGHEST 800000000 + +static inline void update_format(u32 format, struct dc_hw_fb *fb) +{ + u8 f; + + switch (format) { + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_BGRX4444: + f = FORMAT_X4R4G4B4; + break; + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_BGRA4444: + f = FORMAT_A4R4G4B4; + break; + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_BGRX5551: + f = FORMAT_X1R5G5B5; + break; + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_BGRA5551: + f = FORMAT_A1R5G5B5; + break; + case DRM_FORMAT_BGR565: + case DRM_FORMAT_RGB565: + f = FORMAT_R5G6B5; + break; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + f = FORMAT_X8R8G8B8; + break; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_BGRA8888: + f = FORMAT_A8R8G8B8; + break; + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + f = FORMAT_YUY2; + break; + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VYUY: + f = FORMAT_UYVY; + break; + case DRM_FORMAT_YVU420: + case DRM_FORMAT_YUV420: + f = FORMAT_YV12; + break; + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + f = FORMAT_NV12; + break; + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + f = FORMAT_NV16; + break; + case DRM_FORMAT_P010: + f = FORMAT_P010; + break; + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_BGRA1010102: + f = FORMAT_A2R10G10B10; + break; + default: + f = FORMAT_A8R8G8B8; + break; + } + fb->format = f; +} + +static inline void update_swizzle(u32 format, struct dc_hw_fb *fb) +{ + fb->swizzle = SWIZZLE_ARGB; + fb->uv_swizzle = 0; + + switch (format) { + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBA1010102: + fb->swizzle = SWIZZLE_RGBA; + break; + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_ABGR2101010: + fb->swizzle = SWIZZLE_ABGR; + break; + case DRM_FORMAT_BGRA4444: + case DRM_FORMAT_BGRX4444: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_BGRA5551: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_BGRA1010102: + fb->swizzle = SWIZZLE_BGRA; + break; + case DRM_FORMAT_YVYU: + case DRM_FORMAT_VYUY: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV61: + fb->uv_swizzle = 1; + break; + default: + break; + } +} + +static inline u8 to_es_rotation(unsigned int rotation) +{ + u8 rot; + + switch (rotation & DRM_MODE_REFLECT_MASK) { + case DRM_MODE_REFLECT_X: + rot = FLIP_X; + return rot; + case DRM_MODE_REFLECT_Y: + rot = FLIP_Y; + return rot; + case DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y: + rot = FLIP_XY; + return rot; + default: + break; + } + + switch (rotation & DRM_MODE_ROTATE_MASK) { + case DRM_MODE_ROTATE_0: + rot = ROT_0; + break; + case DRM_MODE_ROTATE_90: + rot = ROT_90; + break; + case DRM_MODE_ROTATE_180: + rot = ROT_180; + break; + case DRM_MODE_ROTATE_270: + rot = ROT_270; + break; + default: + rot = ROT_0; + break; + } + + return rot; +} + +static inline u8 to_es_yuv_color_space(u32 color_space) +{ + u8 cs; + + switch (color_space) { + case DRM_COLOR_YCBCR_BT601: + cs = COLOR_SPACE_601; + break; + case DRM_COLOR_YCBCR_BT709: + cs = COLOR_SPACE_709; + break; + case DRM_COLOR_YCBCR_BT2020: + cs = COLOR_SPACE_2020; + break; + default: + cs = COLOR_SPACE_601; + break; + } + + return cs; +} + +static inline u8 to_es_tile_mode(u64 modifier) +{ + // DRM_FORMAT_MOD_ES_NORM_MODE_MASK 0x1F + return (u8)(modifier & 0x1F); +} + +static void dc_deinit(struct device *dev) +{ + struct es_dc *dc = dev_get_drvdata(dev); + + dc_hw_enable_interrupt(&dc->hw, 0); + dc_hw_deinit(&dc->hw); + clk_disable_unprepare(dc->cfg_clk); + clk_disable_unprepare(dc->pix_clk); + clk_disable_unprepare(dc->axi_clk); +} + +static int dc_init(struct device *dev) +{ + struct es_dc *dc = dev_get_drvdata(dev); + int ret; + long rate; + + dc->first_frame = true; + + ret = clk_set_parent(dc->vo_mux, dc->spll0_fout1); + if (ret < 0) { + pr_err("DC: failed to set core clk parent: %d\n", ret); + return ret; + } + + rate = clk_round_rate(dc->axi_clk, VO_ACLK_HIGHEST); + if (rate > 0) { + ret = clk_set_rate(dc->axi_clk, rate); + if (ret) { + pr_err("DC: failed to set axi clk: %d\n", ret); + return ret; + } + } + + ret = clk_prepare_enable(dc->cfg_clk); + if (ret < 0) { + dev_err(dev, "failed to prepare/enable cfg_clk\n"); + return ret; + } + + ret = clk_prepare_enable(dc->pix_clk); + if (ret < 0) { + dev_err(dev, "failed to prepare/enable pix_clk\n"); + goto err_unprepare_cfg_clk; + } + + ret = clk_prepare_enable(dc->axi_clk); + if (ret < 0) { + dev_err(dev, "failed to prepare/enable axi_clk\n"); + goto err_unprepare_pix_clk; + } + + dc->pix_clk_rate = clk_get_rate(dc->pix_clk); + + ret = dc_hw_init(&dc->hw); + if (ret) { + dev_err(dev, "failed to init DC HW\n"); + goto err_unprepare_axi_clk; + } + + return 0; + +err_unprepare_axi_clk: + clk_disable_unprepare(dc->axi_clk); +err_unprepare_cfg_clk: + clk_disable_unprepare(dc->cfg_clk); +err_unprepare_pix_clk: + clk_disable_unprepare(dc->pix_clk); + return ret; +} + +static void es_dc_dump_enable(struct device *dev, dma_addr_t addr, + unsigned int pitch) +{ + struct es_dc *dc = dev_get_drvdata(dev); + + dc_hw_enable_dump(&dc->hw, addr, pitch); +} + +static void es_dc_dump_disable(struct device *dev) +{ + struct es_dc *dc = dev_get_drvdata(dev); + + dc_hw_disable_dump(&dc->hw); +} + +static void es_dc_enable(struct device *dev, struct drm_crtc *crtc) +{ + struct es_dc *dc = dev_get_drvdata(dev); + struct es_crtc_state *crtc_state = to_es_crtc_state(crtc->state); + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct dc_hw_display display; + + display.bus_format = crtc_state->output_fmt; + display.h_active = mode->hdisplay; + display.h_total = mode->htotal; + display.h_sync_start = mode->hsync_start; + display.h_sync_end = mode->hsync_end; + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + display.h_sync_polarity = true; + else + display.h_sync_polarity = false; + + display.v_active = mode->vdisplay; + display.v_total = mode->vtotal; + display.v_sync_start = mode->vsync_start; + display.v_sync_end = mode->vsync_end; + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + display.v_sync_polarity = true; + else + display.v_sync_polarity = false; + + display.sync_mode = crtc_state->sync_mode; + display.bg_color = crtc_state->bg_color; + display.dither_enable = crtc_state->dither_enable; + + display.enable = true; + + if (dc->pix_clk_rate != mode->clock) { + clk_set_rate(dc->pix_clk, mode->clock * 1000); + dc->pix_clk_rate = mode->clock; + } + + if (crtc_state->encoder_type == DRM_MODE_ENCODER_DSI || + crtc_state->encoder_type == DRM_MODE_ENCODER_VIRTUAL || + crtc_state->encoder_type == DRM_MODE_ENCODER_NONE) + dc_hw_set_out(&dc->hw, OUT_DPI); + else + dc_hw_set_out(&dc->hw, OUT_DP); + +#ifdef CONFIG_ESWIN_MMU + if (crtc_state->mmu_prefetch == ES_MMU_PREFETCH_ENABLE) + dc_hw_enable_mmu_prefetch(&dc->hw, true); + else + dc_hw_enable_mmu_prefetch(&dc->hw, false); +#endif + + dc_hw_setup_display(&dc->hw, &display); +} + +static void es_dc_disable(struct device *dev) +{ + struct es_dc *dc = dev_get_drvdata(dev); + struct dc_hw_display display; + + display.enable = false; + + dc_hw_setup_display(&dc->hw, &display); +} + +static bool es_dc_mode_fixup(struct device *dev, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct es_dc *dc = dev_get_drvdata(dev); + long clk_rate; + + if (dc->pix_clk) { + clk_rate = clk_round_rate(dc->pix_clk, + adjusted_mode->clock * 1000); + adjusted_mode->clock = DIV_ROUND_UP(clk_rate, 1000); + } + + return true; +} + +static void es_dc_set_gamma(struct device *dev, struct drm_color_lut *lut, + unsigned int size) +{ + struct es_dc *dc = dev_get_drvdata(dev); + u16 i, r, g, b; + u8 bits; + + if (size != dc->hw.info->gamma_size) { + dev_err(dev, "gamma size does not match!\n"); + return; + } + + bits = dc->hw.info->gamma_bits; + for (i = 0; i < size; i++) { + r = drm_color_lut_extract(lut[i].red, bits); + g = drm_color_lut_extract(lut[i].green, bits); + b = drm_color_lut_extract(lut[i].blue, bits); + dc_hw_update_gamma(&dc->hw, i, r, g, b); + } +} + +static void es_dc_enable_gamma(struct device *dev, bool enable) +{ + struct es_dc *dc = dev_get_drvdata(dev); + + dc_hw_enable_gamma(&dc->hw, enable); +} + +static void es_dc_enable_vblank(struct device *dev, bool enable) +{ + struct es_dc *dc = dev_get_drvdata(dev); + + dc_hw_enable_interrupt(&dc->hw, enable); +} + +static u32 calc_factor(u32 src, u32 dest) +{ + u32 factor = 1 << 16; + + if ((src > 1) && (dest > 1)) + factor = ((src - 1) << 16) / (dest - 1); + + return factor; +} + +static void update_scale(struct dc_hw_scale *scale, int width, int height, + int dst_w, int dst_h, unsigned int rotation, + struct dc_hw_roi *roi) +{ + int src_w, src_h, temp; + + scale->enable = false; + if (roi->enable) { + src_w = roi->width; + src_h = roi->height; + } else { + src_w = width; + src_h = height; + } + + if (drm_rotation_90_or_270(rotation)) { + temp = src_w; + src_w = src_h; + src_h = temp; + } + + if (src_w != dst_w) { + scale->scale_factor_x = calc_factor(src_w, dst_w); + scale->enable = true; + } else { + scale->scale_factor_x = 1 << 16; + } + if (src_h != dst_h) { + scale->scale_factor_y = calc_factor(src_h, dst_h); + scale->enable = true; + } else { + scale->scale_factor_y = 1 << 16; + } +} + +static void update_fb(struct es_plane *plane, struct dc_hw_fb *fb) +{ + struct drm_plane_state *state = plane->base.state; + struct drm_framebuffer *drm_fb = state->fb; + struct drm_rect *src = &state->src; + + fb->y_address = plane->dma_addr[0]; + fb->y_stride = drm_fb->pitches[0]; + if (drm_fb->format->format == DRM_FORMAT_YVU420) { + fb->u_address = plane->dma_addr[2]; + fb->v_address = plane->dma_addr[1]; + fb->u_stride = drm_fb->pitches[2]; + fb->v_stride = drm_fb->pitches[1]; + } else { + fb->u_address = plane->dma_addr[1]; + fb->v_address = plane->dma_addr[2]; + fb->u_stride = drm_fb->pitches[1]; + fb->v_stride = drm_fb->pitches[2]; + } + fb->width = drm_rect_width(src) >> 16; + fb->height = drm_rect_height(src) >> 16; + fb->tile_mode = to_es_tile_mode(drm_fb->modifier); + fb->rotation = to_es_rotation(state->rotation); + fb->yuv_color_space = to_es_yuv_color_space(state->color_encoding); + fb->enable = state->visible; + update_format(drm_fb->format->format, fb); + update_swizzle(drm_fb->format->format, fb); +} + +static void update_degamma(struct es_dc *dc, struct es_plane *plane, + struct es_plane_state *plane_state) +{ + dc_hw_update_degamma(&dc->hw, plane->id, plane_state->degamma); + plane_state->degamma_changed = false; +} + +void update_roi(struct es_dc *dc, enum dc_hw_plane_id id, + struct es_plane_state *plane_state, struct dc_hw_roi *roi, + struct dc_hw_fb *fb) +{ + struct drm_es_roi *data; + u16 src_w = fb->width; + u16 src_h = fb->height; + + if (plane_state->roi) { + data = (struct drm_es_roi *)plane_state->roi->data; + if (data->enable) { + roi->x = data->roi_x; + roi->y = data->roi_y; + roi->width = (data->roi_w + data->roi_x > src_w) ? + (src_w - data->roi_x) : + data->roi_w; + roi->height = (data->roi_h + data->roi_y > src_h) ? + (src_h - data->roi_y) : + data->roi_h; + roi->enable = true; + } else { + roi->enable = false; + roi->width = src_w; + roi->height = src_h; + } + + dc_hw_update_roi(&dc->hw, id, roi); + } else { + roi->enable = false; + } +} + +static void update_color_mgmt(struct es_dc *dc, u8 id, struct dc_hw_fb *fb, + struct es_plane_state *plane_state) +{ + struct drm_es_color_mgmt *data; + struct dc_hw_colorkey colorkey; + + if (plane_state->color_mgmt) { + data = plane_state->color_mgmt->data; + + fb->clear_enable = data->clear_enable; + fb->clear_value = data->clear_value; + + if (data->colorkey > data->colorkey_high) + data->colorkey = data->colorkey_high; + + colorkey.colorkey = data->colorkey; + colorkey.colorkey_high = data->colorkey_high; + colorkey.transparency = (data->transparency) ? + DC_TRANSPARENCY_KEY : + DC_TRANSPARENCY_OPAQUE; + dc_hw_update_colorkey(&dc->hw, id, &colorkey); + } +} + +static void update_primary_plane(struct es_dc *dc, struct es_plane *plane) +{ + struct dc_hw_fb fb = { 0 }; + struct dc_hw_scale scale; + struct drm_plane_state *state = plane->base.state; + struct es_plane_state *plane_state = to_es_plane_state(state); + struct drm_crtc *crtc = state->crtc; + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct dc_hw_roi roi; + + update_fb(plane, &fb); + + update_roi(dc, plane->id, plane_state, &roi, &fb); + + update_scale(&scale, fb.width, fb.height, mode->hdisplay, + mode->vdisplay, state->rotation, &roi); + + if (plane_state->degamma_changed) + update_degamma(dc, plane, plane_state); + + update_color_mgmt(dc, plane->id, &fb, plane_state); + + dc_hw_update_plane(&dc->hw, plane->id, &fb, &scale); +} + +static void update_overlay_plane(struct es_dc *dc, struct es_plane *plane) +{ + struct dc_hw_fb fb = { 0 }; + struct dc_hw_scale scale; + struct dc_hw_position pos; + struct dc_hw_blend blend; + struct drm_plane_state *state = plane->base.state; + struct es_plane_state *plane_state = to_es_plane_state(state); + struct drm_rect *dest = &state->dst; + struct dc_hw_roi roi; + + update_fb(plane, &fb); + update_roi(dc, plane->id, plane_state, &roi, &fb); + update_scale(&scale, fb.width, fb.height, drm_rect_width(dest), + drm_rect_height(dest), state->rotation, &roi); + + if (plane_state->degamma_changed) + update_degamma(dc, plane, plane_state); + + update_color_mgmt(dc, plane->id, &fb, plane_state); + + dc_hw_update_plane(&dc->hw, plane->id, &fb, &scale); + + pos.start_x = dest->x1; + pos.start_y = dest->y1; + pos.end_x = dest->x2; + pos.end_y = dest->y2; + dc_hw_set_position(&dc->hw, &pos); + + blend.alpha = (u8)(state->alpha >> 8); + blend.blend_mode = (u8)(state->pixel_blend_mode); + dc_hw_set_blend(&dc->hw, &blend); +} + +static void update_cursor_plane(struct es_dc *dc, struct es_plane *plane) +{ + struct drm_plane_state *state = plane->base.state; + struct drm_framebuffer *drm_fb = state->fb; + struct dc_hw_cursor cursor; + + cursor.address = plane->dma_addr[0]; + cursor.x = state->crtc_x; + cursor.y = state->crtc_y; + cursor.hot_x = drm_fb->hot_x; + cursor.hot_y = drm_fb->hot_y; + cursor.enable = true; + + dc_hw_update_cursor(&dc->hw, &cursor); +} + +static void es_dc_update_plane(struct device *dev, struct es_plane *plane) +{ + struct es_dc *dc = dev_get_drvdata(dev); + enum drm_plane_type type = plane->base.type; + + switch (type) { + case DRM_PLANE_TYPE_PRIMARY: + update_primary_plane(dc, plane); + break; + case DRM_PLANE_TYPE_OVERLAY: + update_overlay_plane(dc, plane); + break; + case DRM_PLANE_TYPE_CURSOR: + update_cursor_plane(dc, plane); + break; + default: + break; + } +} + +static void es_dc_disable_plane(struct device *dev, struct es_plane *plane) +{ + struct es_dc *dc = dev_get_drvdata(dev); + enum drm_plane_type type = plane->base.type; + struct dc_hw_fb fb = { 0 }; + struct dc_hw_cursor cursor = { 0 }; + + switch (type) { + case DRM_PLANE_TYPE_PRIMARY: + case DRM_PLANE_TYPE_OVERLAY: + fb.enable = false; + dc_hw_update_plane(&dc->hw, plane->id, &fb, NULL); + break; + case DRM_PLANE_TYPE_CURSOR: + cursor.enable = false; + dc_hw_update_cursor(&dc->hw, &cursor); + break; + default: + break; + } +} + +static bool es_dc_mod_supported(const struct es_plane_info *plane_info, + u64 modifier) +{ + const u64 *mods; + + if (plane_info->modifiers == NULL) + return false; + + for (mods = plane_info->modifiers; *mods != DRM_FORMAT_MOD_INVALID; + mods++) { + if (*mods == modifier) + return true; + } + + return false; +} + +static int es_dc_check_plane(struct device *dev, struct es_plane *plane, + struct drm_plane_state *state) +{ + struct es_dc *dc = dev_get_drvdata(dev); + struct drm_framebuffer *fb = state->fb; + const struct es_plane_info *plane_info; + struct drm_crtc *crtc = state->crtc; + struct drm_crtc_state *crtc_state; + + plane_info = &dc->hw.info->planes[plane->id]; + if (plane_info == NULL) + return -EINVAL; + + if (fb->width < plane_info->min_width || + fb->width > plane_info->max_width || + fb->height < plane_info->min_height || + fb->height > plane_info->max_height) + dev_err_once(dev, "buffer size may not support on plane%d.\n", + plane->id); + + if ((plane->base.type != DRM_PLANE_TYPE_CURSOR) && + (!es_dc_mod_supported(plane_info, fb->modifier))) { + dev_err(dev, "unsupported modifier on plane%d.\n", plane->id); + return -EINVAL; + } + + crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc); + if (IS_ERR(crtc_state)) + return -EINVAL; + + return drm_atomic_helper_check_plane_state(state, crtc_state, + plane_info->min_scale, + plane_info->max_scale, true, + true); +} + +static irqreturn_t dc_isr(int irq, void *data) +{ + struct es_dc *dc = data; + + dc_hw_get_interrupt(&dc->hw); + + if (!dc->dc_initialized) { + return IRQ_HANDLED; + } + + es_crtc_handle_vblank(&dc->crtc->base, dc_hw_check_underflow(&dc->hw)); + + return IRQ_HANDLED; +} + +static void es_dc_commit(struct device *dev) +{ + struct es_dc *dc = dev_get_drvdata(dev); + +#ifdef CONFIG_ESWIN_MMU + dc_mmu_flush(&dc->hw); +#endif + + if (!dc->first_frame) { + if (dc_hw_flip_in_progress(&dc->hw)) + udelay(100); + + dc_hw_enable_shadow_register(&dc->hw, false); + } + + dc_hw_commit(&dc->hw); + + if (dc->first_frame) + dc->first_frame = false; + + if (!dc->dc_initialized) + dc->dc_initialized = true; + + dc_hw_enable_shadow_register(&dc->hw, true); +} + +static const struct es_crtc_funcs dc_crtc_funcs = { + .enable = es_dc_enable, + .disable = es_dc_disable, + .mode_fixup = es_dc_mode_fixup, + .set_gamma = es_dc_set_gamma, + .enable_gamma = es_dc_enable_gamma, + .enable_vblank = es_dc_enable_vblank, + .commit = es_dc_commit, +}; + +static const struct es_plane_funcs dc_plane_funcs = { + .update = es_dc_update_plane, + .disable = es_dc_disable_plane, + .check = es_dc_check_plane, +}; + +static const struct es_dc_funcs dc_funcs = { + .dump_enable = es_dc_dump_enable, + .dump_disable = es_dc_dump_disable, +}; + +static int dc_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm_dev = data; +#ifdef CONFIG_ESWIN_MMU + struct es_drm_private *priv = drm_dev->dev_private; +#endif + struct es_dc *dc = dev_get_drvdata(dev); + struct device_node *port; + struct es_crtc *crtc; + struct es_dc_info *dc_info; + struct es_plane *plane; + struct drm_plane *drm_plane, *tmp; + struct es_plane_info *plane_info; + int i, ret; + + if (!drm_dev || !dc) { + dev_err(dev, "devices are not created.\n"); + return -ENODEV; + } + + ret = dc_init(dev); + if (ret < 0) { + dev_err(dev, "Failed to initialize DC hardware.\n"); + return ret; + } + +#ifdef CONFIG_ESWIN_MMU + ret = dc_mmu_construct(priv->dma_dev, &priv->mmu); + if (ret) { + dev_err(dev, "failed to construct DC MMU\n"); + goto err_clean_dc; + } + + ret = dc_hw_mmu_init(&dc->hw, priv->mmu); + if (ret) { + dev_err(dev, "failed to init DC MMU\n"); + goto err_clean_dc; + } +#endif + + ret = es_drm_iommu_attach_device(drm_dev, dev); + if (ret < 0) { + dev_err(dev, "Failed to attached iommu device.\n"); + goto err_clean_dc; + } + + port = of_get_child_by_name(dev->of_node, "port"); + if (!port) { + dev_err(dev, "no port node found\n"); + goto err_detach_dev; + } + of_node_put(port); + + dc_info = dc->hw.info; + crtc = es_crtc_create(drm_dev, dc_info); + if (!crtc) { + dev_err(dev, "Failed to create CRTC.\n"); + ret = -ENOMEM; + goto err_detach_dev; + } + + crtc->base.port = port; + crtc->dev = dev; + crtc->funcs = &dc_crtc_funcs; + + for (i = 0; i < dc_info->plane_num; i++) { + plane_info = (struct es_plane_info *)&dc_info->planes[i]; + + plane = es_plane_create(drm_dev, plane_info, + drm_crtc_mask(&crtc->base)); + if (!plane) + goto err_cleanup_planes; + + plane->funcs = &dc_plane_funcs; + + if (plane_info->type == DRM_PLANE_TYPE_PRIMARY) { + crtc->base.primary = &plane->base; + drm_dev->mode_config.min_width = plane_info->min_width; + drm_dev->mode_config.min_height = + plane_info->min_height; + drm_dev->mode_config.max_width = plane_info->max_width; + drm_dev->mode_config.max_height = + plane_info->max_height; + } + + if (plane_info->type == DRM_PLANE_TYPE_CURSOR) { + crtc->base.cursor = &plane->base; + drm_dev->mode_config.cursor_width = + plane_info->max_width; + drm_dev->mode_config.cursor_height = + plane_info->max_height; + } + } + + dc->crtc = crtc; + dc->funcs = &dc_funcs; + + es_drm_update_pitch_alignment(drm_dev, dc_info->pitch_alignment); + + return 0; + +err_cleanup_planes: + list_for_each_entry_safe (drm_plane, tmp, + &drm_dev->mode_config.plane_list, head) + if (drm_plane->possible_crtcs == drm_crtc_mask(&crtc->base)) + es_plane_destory(drm_plane); + + es_crtc_destroy(&crtc->base); +err_detach_dev: + es_drm_iommu_detach_device(drm_dev, dev); +err_clean_dc: + dc_deinit(dev); + return ret; +} + +static void dc_unbind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm_dev = data; + + dc_deinit(dev); + + es_drm_iommu_detach_device(drm_dev, dev); +} + +const struct component_ops dc_component_ops = { + .bind = dc_bind, + .unbind = dc_unbind, +}; + +static const struct of_device_id dc_driver_dt_match[] = { + { + .compatible = "eswin,dc", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, dc_driver_dt_match); + +static int dc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct es_dc *dc; + int irq, ret; + + dc = devm_kzalloc(dev, sizeof(*dc), GFP_KERNEL); + if (!dc) + return -ENOMEM; + + dc->hw.hi_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dc->hw.hi_base)) + return PTR_ERR(dc->hw.hi_base); + +#ifdef CONFIG_ESWIN_MMU + dc->hw.mmu_base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(dc->hw.mmu_base)) + return PTR_ERR(dc->hw.mmu_base); +#endif + + dc->hw.reg_base = devm_platform_ioremap_resource(pdev, 2); + if (IS_ERR(dc->hw.reg_base)) + return PTR_ERR(dc->hw.reg_base); + + irq = platform_get_irq(pdev, 0); + ret = devm_request_irq(dev, irq, dc_isr, 0, dev_name(dev), dc); + if (ret < 0) { + dev_err(dev, "Failed to install irq:%u.\n", irq); + return ret; + } + + dc->vo_mux = devm_clk_get(dev, "vo_mux"); + if (IS_ERR(dc->vo_mux)) { + ret = PTR_ERR(dc->vo_mux); + dev_err(dev, "failed to get vo_mux: %d\n", ret); + return ret; + } + + dc->spll0_fout1 = devm_clk_get_optional(dev, "spll0_fout1"); + if (IS_ERR(dc->spll0_fout1)) { + dev_err(dev, "failed to get spll0_fout1 source\n"); + return PTR_ERR(dc->spll0_fout1); + } + + dc->cfg_clk = devm_clk_get_optional(dev, "cfg_clk"); + if (IS_ERR(dc->cfg_clk)) { + dev_err(dev, "failed to get cfg_clk source\n"); + return PTR_ERR(dc->cfg_clk); + } + + dc->pix_clk = devm_clk_get_optional(dev, "pix_clk"); + if (IS_ERR(dc->pix_clk)) { + dev_err(dev, "failed to get pix_clk source\n"); + return PTR_ERR(dc->pix_clk); + } + + dc->axi_clk = devm_clk_get_optional(dev, "axi_clk"); + if (IS_ERR(dc->axi_clk)) { + dev_err(dev, "failed to get axi_clk source\n"); + return PTR_ERR(dc->axi_clk); + } + + dc->vo_arst = devm_reset_control_get_optional(dev, "vo_arst"); + if (IS_ERR_OR_NULL(dc->vo_arst)) { + dev_err(dev, "Failed to vo_arst handle\n"); + return PTR_ERR(dc->vo_arst); + } + + dc->vo_prst = devm_reset_control_get_optional(dev, "vo_prst"); + if (IS_ERR_OR_NULL(dc->vo_prst)) { + dev_err(dev, "Failed to vo_prst handle\n"); + return PTR_ERR(dc->vo_prst); + } + + dc->dc_arst = devm_reset_control_get_optional(dev, "dc_arst"); + if (IS_ERR_OR_NULL(dc->dc_arst)) { + dev_err(dev, "Failed to dc_arst handle\n"); + return PTR_ERR(dc->dc_arst); + } + + dc->dc_prst = devm_reset_control_get_optional(dev, "dc_prst"); + if (IS_ERR_OR_NULL(dc->dc_prst)) { + dev_err(dev, "Failed to dc_prst handle\n"); + return PTR_ERR(dc->dc_prst); + } + + /* reset dc first to ensure no data on axi bus */ + if (dc->dc_arst) { + ret = reset_control_reset(dc->dc_arst); + WARN_ON(0 != ret); + } + + if (dc->dc_prst) { + ret = reset_control_reset(dc->dc_prst); + WARN_ON(0 != ret); + } + + if (dc->vo_arst) { + ret = reset_control_reset(dc->vo_arst); + WARN_ON(0 != ret); + } + + if (dc->vo_prst) { + ret = reset_control_reset(dc->vo_prst); + WARN_ON(0 != ret); + } + + dev_set_drvdata(dev, dc); + + return component_add(dev, &dc_component_ops); +} + +static int dc_remove(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct es_dc *dc = dev_get_drvdata(dev); + + component_del(dev, &dc_component_ops); + + if (dc->dc_arst) { + ret = reset_control_assert(dc->dc_arst); + WARN_ON(0 != ret); + } + + if (dc->dc_prst) { + ret = reset_control_assert(dc->dc_prst); + WARN_ON(0 != ret); + } + + if (dc->vo_arst) { + ret = reset_control_assert(dc->vo_arst); + WARN_ON(0 != ret); + } + + if (dc->vo_prst) { + ret = reset_control_assert(dc->vo_prst); + WARN_ON(0 != ret); + } + + dev_set_drvdata(dev, NULL); + + return 0; +} + +struct platform_driver dc_platform_driver = { + .probe = dc_probe, + .remove = dc_remove, + .driver = { + .name = "es-dc", + .of_match_table = of_match_ptr(dc_driver_dt_match), + }, +}; + +MODULE_DESCRIPTION("Eswin DC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/eswin/es_dc.h b/drivers/gpu/drm/eswin/es_dc.h new file mode 100644 index 000000000000..3a20987aa799 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_dc.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#ifndef __ES_DC_H__ +#define __ES_DC_H__ + +#include +#include + +#include "es_plane.h" +#include "es_crtc.h" +#include "es_dc_hw.h" +#ifdef CONFIG_ESWIN_MMU +#include "es_dc_mmu.h" +#endif + +struct es_dc_funcs { + void (*dump_enable)(struct device *dev, dma_addr_t addr, + unsigned int pitch); + void (*dump_disable)(struct device *dev); +}; + +struct es_dc { + struct es_crtc *crtc; + struct dc_hw hw; + + struct clk *vo_mux; + struct clk *spll0_fout1; + struct clk *cfg_clk; + struct clk *pix_clk; + struct clk *axi_clk; + unsigned int pix_clk_rate; /* in KHz */ + + struct reset_control *vo_arst; + struct reset_control *vo_prst; + struct reset_control *dc_arst; + struct reset_control *dc_prst; + + bool first_frame; + bool dc_initialized; + + const struct es_dc_funcs *funcs; +}; + +extern struct platform_driver dc_platform_driver; +#endif /* __ES_DC_H__ */ diff --git a/drivers/gpu/drm/eswin/es_dc_hw.c b/drivers/gpu/drm/eswin/es_dc_hw.c new file mode 100644 index 000000000000..010de5b65250 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_dc_hw.c @@ -0,0 +1,1614 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "es_drm.h" +#include "es_type.h" +#include "es_dc_hw.h" + +static const u32 horKernel[] = { + 0x00000000, 0x20000000, 0x00002000, 0x00000000, 0x00000000, 0x00000000, + 0x23fd1c03, 0x00000000, 0x00000000, 0x00000000, 0x181f0000, 0x000027e1, + 0x00000000, 0x00000000, 0x00000000, 0x2b981468, 0x00000000, 0x00000000, + 0x00000000, 0x10f00000, 0x00002f10, 0x00000000, 0x00000000, 0x00000000, + 0x32390dc7, 0x00000000, 0x00000000, 0x00000000, 0x0af50000, 0x0000350b, + 0x00000000, 0x00000000, 0x00000000, 0x3781087f, 0x00000000, 0x00000000, + 0x00000000, 0x06660000, 0x0000399a, 0x00000000, 0x00000000, 0x00000000, + 0x3b5904a7, 0x00000000, 0x00000000, 0x00000000, 0x033c0000, 0x00003cc4, + 0x00000000, 0x00000000, 0x00000000, 0x3de1021f, 0x00000000, 0x00000000, + 0x00000000, 0x01470000, 0x00003eb9, 0x00000000, 0x00000000, 0x00000000, + 0x3f5300ad, 0x00000000, 0x00000000, 0x00000000, 0x00480000, 0x00003fb8, + 0x00000000, 0x00000000, 0x00000000, 0x3fef0011, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00004000, 0x00000000, 0x00000000, 0x00000000, + 0x20002000, 0x00000000, 0x00000000, 0x00000000, 0x1c030000, 0x000023fd, + 0x00000000, 0x00000000, 0x00000000, 0x27e1181f, 0x00000000, 0x00000000, + 0x00000000, 0x14680000, 0x00002b98, 0x00000000, 0x00000000, 0x00000000, + 0x2f1010f0, 0x00000000, 0x00000000, 0x00000000, 0x0dc70000, 0x00003239, + 0x00000000, 0x00000000, 0x00000000, 0x350b0af5, 0x00000000, 0x00000000, + 0x00000000, 0x087f0000, 0x00003781, 0x00000000, 0x00000000, 0x00000000, + 0x399a0666, 0x00000000, 0x00000000, 0x00000000, 0x04a70000, 0x00003b59, + 0x00000000, 0x00000000, 0x00000000, 0x3cc4033c, 0x00000000, 0x00000000, + 0x00000000, 0x021f0000, +}; +#define H_COEF_SIZE (sizeof(horKernel) / sizeof(u32)) + +static const u32 verKernel[] = { + 0x00000000, 0x20000000, 0x00002000, 0x00000000, 0x00000000, 0x00000000, + 0x23fd1c03, 0x00000000, 0x00000000, 0x00000000, 0x181f0000, 0x000027e1, + 0x00000000, 0x00000000, 0x00000000, 0x2b981468, 0x00000000, 0x00000000, + 0x00000000, 0x10f00000, 0x00002f10, 0x00000000, 0x00000000, 0x00000000, + 0x32390dc7, 0x00000000, 0x00000000, 0x00000000, 0x0af50000, 0x0000350b, + 0x00000000, 0x00000000, 0x00000000, 0x3781087f, 0x00000000, 0x00000000, + 0x00000000, 0x06660000, 0x0000399a, 0x00000000, 0x00000000, 0x00000000, + 0x3b5904a7, 0x00000000, 0x00000000, 0x00000000, 0x033c0000, 0x00003cc4, + 0x00000000, 0x00000000, 0x00000000, 0x3de1021f, 0x00000000, 0x00000000, + 0x00000000, 0x01470000, 0x00003eb9, 0x00000000, 0x00000000, 0x00000000, + 0x3f5300ad, 0x00000000, 0x00000000, 0x00000000, 0x00480000, 0x00003fb8, + 0x00000000, 0x00000000, 0x00000000, 0x3fef0011, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00004000, 0x00000000, 0xcdcd0000, 0xfdfdfdfd, + 0xabababab, 0xabababab, 0x00000000, 0x00000000, 0x5ff5f456, 0x000f5f58, + 0x02cc6c78, 0x02cc0c28, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, +}; +#define V_COEF_SIZE (sizeof(verKernel) / sizeof(u32)) + +/* + * RGB 709->2020 conversion parameters + */ +static u16 RGB2RGB[RGB_TO_RGB_TABLE_SIZE] = { 10279, 5395, 709, 1132, 15065, + 187, 269, 1442, 14674 }; + +/* + * YUV601 to RGB conversion parameters + * YUV2RGB[0] - [8] : C0 - C8; + * YUV2RGB[9] - [11]: D0 - D2; + * YUV2RGB[12] - [13]: Y clamp min & max calue; + * YUV2RGB[14] - [15]: UV clamp min & max calue; + */ +static s32 YUV601_2RGB[YUV_TO_RGB_TABLE_SIZE] = { + 1196, 0, 1640, 1196, -404, -836, 1196, 2076, + 0, -916224, 558336, -1202944, 64, 940, 64, 960 +}; + +/* + * YUV709 to RGB conversion parameters + * YUV2RGB[0] - [8] : C0 - C8; + * YUV2RGB[9] - [11]: D0 - D2; + * YUV2RGB[12] - [13]: Y clamp min & max calue; + * YUV2RGB[14] - [15]: UV clamp min & max calue; + */ +static s32 YUV709_2RGB[YUV_TO_RGB_TABLE_SIZE] = { + 1196, 0, 1844, 1196, -220, -548, 1196, 2172, + 0, -1020672, 316672, -1188608, 64, 940, 64, 960 +}; + +/* + * YUV2020 to RGB conversion parameters + * YUV2RGB[0] - [8] : C0 - C8; + * YUV2RGB[9] - [11]: D0 - D2; + * YUV2RGB[12] - [13]: Y clamp min & max calue; + * YUV2RGB[14] - [15]: UV clamp min & max calue; + */ +static s32 YUV2020_2RGB[YUV_TO_RGB_TABLE_SIZE] = { + 1196, 0, 1724, 1196, -192, -668, 1196, 2200, + 0, -959232, 363776, -1202944, 64, 940, 64, 960 +}; + +/* + * RGB to YUV2020 conversion parameters + * RGB2YUV[0] - [8] : C0 - C8; + * RGB2YUV[9] - [11]: D0 - D2; + */ +static s16 RGB2YUV[RGB_TO_YUV_TABLE_SIZE] = { 230, 594, 52, -125, -323, 448, + 448, -412, -36, 64, 512, 512 }; + +/* + * Degamma table for 709 color space data. + */ +static u16 DEGAMMA_709[DEGAMMA_SIZE] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0005, 0x0007, + 0x000a, 0x000d, 0x0011, 0x0015, 0x0019, 0x001e, 0x0024, 0x002a, 0x0030, + 0x0038, 0x003f, 0x0048, 0x0051, 0x005a, 0x0064, 0x006f, 0x007b, 0x0087, + 0x0094, 0x00a1, 0x00af, 0x00be, 0x00ce, 0x00de, 0x00ef, 0x0101, 0x0114, + 0x0127, 0x013b, 0x0150, 0x0166, 0x017c, 0x0193, 0x01ac, 0x01c4, 0x01de, + 0x01f9, 0x0214, 0x0230, 0x024d, 0x026b, 0x028a, 0x02aa, 0x02ca, 0x02ec, + 0x030e, 0x0331, 0x0355, 0x037a, 0x03a0, 0x03c7, 0x03ef, 0x0418, 0x0441, + 0x046c, 0x0498, 0x04c4, 0x04f2, 0x0520, 0x0550, 0x0581, 0x05b2, 0x05e5, + 0x0618, 0x064d, 0x0682, 0x06b9, 0x06f0, 0x0729, 0x0763, 0x079d, 0x07d9, + 0x0816, 0x0854, 0x0893, 0x08d3, 0x0914, 0x0956, 0x0999, 0x09dd, 0x0a23, + 0x0a69, 0x0ab1, 0x0afa, 0x0b44, 0x0b8f, 0x0bdb, 0x0c28, 0x0c76, 0x0cc6, + 0x0d17, 0x0d69, 0x0dbb, 0x0e10, 0x0e65, 0x0ebb, 0x0f13, 0x0f6c, 0x0fc6, + 0x1021, 0x107d, 0x10db, 0x113a, 0x119a, 0x11fb, 0x125d, 0x12c1, 0x1325, + 0x138c, 0x13f3, 0x145b, 0x14c5, 0x1530, 0x159c, 0x160a, 0x1678, 0x16e8, + 0x175a, 0x17cc, 0x1840, 0x18b5, 0x192b, 0x19a3, 0x1a1c, 0x1a96, 0x1b11, + 0x1b8e, 0x1c0c, 0x1c8c, 0x1d0c, 0x1d8e, 0x1e12, 0x1e96, 0x1f1c, 0x1fa3, + 0x202c, 0x20b6, 0x2141, 0x21ce, 0x225c, 0x22eb, 0x237c, 0x240e, 0x24a1, + 0x2536, 0x25cc, 0x2664, 0x26fc, 0x2797, 0x2832, 0x28cf, 0x296e, 0x2a0e, + 0x2aaf, 0x2b51, 0x2bf5, 0x2c9b, 0x2d41, 0x2dea, 0x2e93, 0x2f3e, 0x2feb, + 0x3099, 0x3148, 0x31f9, 0x32ab, 0x335f, 0x3414, 0x34ca, 0x3582, 0x363c, + 0x36f7, 0x37b3, 0x3871, 0x3930, 0x39f1, 0x3ab3, 0x3b77, 0x3c3c, 0x3d02, + 0x3dcb, 0x3e94, 0x3f5f, 0x402c, 0x40fa, 0x41ca, 0x429b, 0x436d, 0x4442, + 0x4517, 0x45ee, 0x46c7, 0x47a1, 0x487d, 0x495a, 0x4a39, 0x4b19, 0x4bfb, + 0x4cde, 0x4dc3, 0x4eaa, 0x4f92, 0x507c, 0x5167, 0x5253, 0x5342, 0x5431, + 0x5523, 0x5616, 0x570a, 0x5800, 0x58f8, 0x59f1, 0x5aec, 0x5be9, 0x5ce7, + 0x5de6, 0x5ee7, 0x5fea, 0x60ef, 0x61f5, 0x62fc, 0x6406, 0x6510, 0x661d, + 0x672b, 0x683b, 0x694c, 0x6a5f, 0x6b73, 0x6c8a, 0x6da2, 0x6ebb, 0x6fd6, + 0x70f3, 0x7211, 0x7331, 0x7453, 0x7576, 0x769b, 0x77c2, 0x78ea, 0x7a14, + 0x7b40, 0x7c6d, 0x7d9c, 0x7ecd, 0x3f65, 0x3f8c, 0x3fb2, 0x3fd8 +}; + +/* + * Degamma table for 2020 color space data. + */ +static u16 DEGAMMA_2020[DEGAMMA_SIZE] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0002, 0x0002, 0x0002, + 0x0002, 0x0002, 0x0003, 0x0003, 0x0003, 0x0003, 0x0004, 0x0004, 0x0004, + 0x0005, 0x0005, 0x0006, 0x0006, 0x0006, 0x0007, 0x0007, 0x0008, 0x0008, + 0x0009, 0x000a, 0x000a, 0x000b, 0x000c, 0x000c, 0x000d, 0x000e, 0x000f, + 0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0016, 0x0017, 0x0018, + 0x0019, 0x001b, 0x001c, 0x001e, 0x001f, 0x0021, 0x0022, 0x0024, 0x0026, + 0x0028, 0x002a, 0x002c, 0x002e, 0x0030, 0x0033, 0x0035, 0x0038, 0x003a, + 0x003d, 0x0040, 0x0043, 0x0046, 0x0049, 0x004d, 0x0050, 0x0054, 0x0057, + 0x005b, 0x005f, 0x0064, 0x0068, 0x006d, 0x0071, 0x0076, 0x007c, 0x0081, + 0x0086, 0x008c, 0x0092, 0x0098, 0x009f, 0x00a5, 0x00ac, 0x00b4, 0x00bb, + 0x00c3, 0x00cb, 0x00d3, 0x00dc, 0x00e5, 0x00ee, 0x00f8, 0x0102, 0x010c, + 0x0117, 0x0123, 0x012e, 0x013a, 0x0147, 0x0154, 0x0161, 0x016f, 0x017e, + 0x018d, 0x019c, 0x01ac, 0x01bd, 0x01ce, 0x01e0, 0x01f3, 0x0206, 0x021a, + 0x022f, 0x0244, 0x025a, 0x0272, 0x0289, 0x02a2, 0x02bc, 0x02d6, 0x02f2, + 0x030f, 0x032c, 0x034b, 0x036b, 0x038b, 0x03ae, 0x03d1, 0x03f5, 0x041b, + 0x0443, 0x046b, 0x0495, 0x04c1, 0x04ee, 0x051d, 0x054e, 0x0580, 0x05b4, + 0x05ea, 0x0622, 0x065c, 0x0698, 0x06d6, 0x0717, 0x075a, 0x079f, 0x07e7, + 0x0831, 0x087e, 0x08cd, 0x0920, 0x0976, 0x09ce, 0x0a2a, 0x0a89, 0x0aec, + 0x0b52, 0x0bbc, 0x0c2a, 0x0c9b, 0x0d11, 0x0d8b, 0x0e0a, 0x0e8d, 0x0f15, + 0x0fa1, 0x1033, 0x10ca, 0x1167, 0x120a, 0x12b2, 0x1360, 0x1415, 0x14d1, + 0x1593, 0x165d, 0x172e, 0x1806, 0x18e7, 0x19d0, 0x1ac1, 0x1bbb, 0x1cbf, + 0x1dcc, 0x1ee3, 0x2005, 0x2131, 0x2268, 0x23ab, 0x24fa, 0x2656, 0x27be, + 0x2934, 0x2ab8, 0x2c4a, 0x2dec, 0x2f9d, 0x315f, 0x3332, 0x3516, 0x370d, + 0x3916, 0x3b34, 0x3d66, 0x3fad, 0x420b, 0x4480, 0x470d, 0x49b3, 0x4c73, + 0x4f4e, 0x5246, 0x555a, 0x588e, 0x5be1, 0x5f55, 0x62eb, 0x66a6, 0x6a86, + 0x6e8c, 0x72bb, 0x7714, 0x7b99, 0x3dcb, 0x3e60, 0x3ef5, 0x3f8c +}; + +/* one is for primary plane and the other is for all overlay planes */ +static const struct dc_hw_plane_reg dc_plane_reg[] = { + { + .y_address = DC_FRAMEBUFFER_ADDRESS, + .u_address = DC_FRAMEBUFFER_U_ADDRESS, + .v_address = DC_FRAMEBUFFER_V_ADDRESS, + .y_stride = DC_FRAMEBUFFER_STRIDE, + .u_stride = DC_FRAMEBUFFER_U_STRIDE, + .v_stride = DC_FRAMEBUFFER_V_STRIDE, + .size = DC_FRAMEBUFFER_SIZE, + .scale_factor_x = DC_FRAMEBUFFER_SCALE_FACTOR_X, + .scale_factor_y = DC_FRAMEBUFFER_SCALE_FACTOR_Y, + .h_filter_coef_index = DC_FRAMEBUFFER_H_FILTER_COEF_INDEX, + .h_filter_coef_data = DC_FRAMEBUFFER_H_FILTER_COEF_DATA, + .v_filter_coef_index = DC_FRAMEBUFFER_V_FILTER_COEF_INDEX, + .v_filter_coef_data = DC_FRAMEBUFFER_V_FILTER_COEF_DATA, + .init_offset = DC_FRAMEBUFFER_INIT_OFFSET, + .color_key = DC_FRAMEBUFFER_COLOR_KEY, + .color_key_high = DC_FRAMEBUFFER_COLOR_KEY_HIGH, + .clear_value = DC_FRAMEBUFFER_CLEAR_VALUE, + .color_table_index = DC_FRAMEBUFFER_COLOR_TABLE_INDEX, + .color_table_data = DC_FRAMEBUFFER_COLOR_TABLE_DATA, + .scale_config = DC_FRAMEBUFFER_SCALE_CONFIG, + .degamma_index = DC_FRAMEBUFFER_DEGAMMA_INDEX, + .degamma_data = DC_FRAMEBUFFER_DEGAMMA_DATA, + .degamma_ex_data = DC_FRAMEBUFFER_DEGAMMA_EX_DATA, + .roi_origin = DC_FRAMEBUFFER_ROI_ORIGIN, + .roi_size = DC_FRAMEBUFFER_ROI_SIZE, + .YUVToRGBCoef0 = DC_FRAMEBUFFER_YUVTORGB_COEF0, + .YUVToRGBCoef1 = DC_FRAMEBUFFER_YUVTORGB_COEF1, + .YUVToRGBCoef2 = DC_FRAMEBUFFER_YUVTORGB_COEF2, + .YUVToRGBCoef3 = DC_FRAMEBUFFER_YUVTORGB_COEF3, + .YUVToRGBCoef4 = DC_FRAMEBUFFER_YUVTORGB_COEF4, + .YUVToRGBCoefD0 = DC_FRAMEBUFFER_YUVTORGB_COEFD0, + .YUVToRGBCoefD1 = DC_FRAMEBUFFER_YUVTORGB_COEFD1, + .YUVToRGBCoefD2 = DC_FRAMEBUFFER_YUVTORGB_COEFD2, + .YClampBound = DC_FRAMEBUFFER_Y_CLAMP_BOUND, + .UVClampBound = DC_FRAMEBUFFER_UV_CLAMP_BOUND, + .RGBToRGBCoef0 = DC_FRAMEBUFFER_RGBTORGB_COEF0, + .RGBToRGBCoef1 = DC_FRAMEBUFFER_RGBTORGB_COEF1, + .RGBToRGBCoef2 = DC_FRAMEBUFFER_RGBTORGB_COEF2, + .RGBToRGBCoef3 = DC_FRAMEBUFFER_RGBTORGB_COEF3, + .RGBToRGBCoef4 = DC_FRAMEBUFFER_RGBTORGB_COEF4, + }, + { + .y_address = DC_OVERLAY_ADDRESS, + .u_address = DC_OVERLAY_U_ADDRESS, + .v_address = DC_OVERLAY_V_ADDRESS, + .y_stride = DC_OVERLAY_STRIDE, + .u_stride = DC_OVERLAY_U_STRIDE, + .v_stride = DC_OVERLAY_V_STRIDE, + .size = DC_OVERLAY_SIZE, + .scale_factor_x = DC_OVERLAY_SCALE_FACTOR_X, + .scale_factor_y = DC_OVERLAY_SCALE_FACTOR_Y, + .h_filter_coef_index = DC_OVERLAY_H_FILTER_COEF_INDEX, + .h_filter_coef_data = DC_OVERLAY_H_FILTER_COEF_DATA, + .v_filter_coef_index = DC_OVERLAY_V_FILTER_COEF_INDEX, + .v_filter_coef_data = DC_OVERLAY_V_FILTER_COEF_DATA, + .init_offset = DC_OVERLAY_INIT_OFFSET, + .color_key = DC_OVERLAY_COLOR_KEY, + .color_key_high = DC_OVERLAY_COLOR_KEY_HIGH, + .clear_value = DC_OVERLAY_CLEAR_VALUE, + .color_table_index = DC_OVERLAY_COLOR_TABLE_INDEX, + .color_table_data = DC_OVERLAY_COLOR_TABLE_DATA, + .scale_config = DC_OVERLAY_SCALE_CONFIG, + .degamma_index = DC_OVERLAY_DEGAMMA_INDEX, + .degamma_data = DC_OVERLAY_DEGAMMA_DATA, + .degamma_ex_data = DC_OVERLAY_DEGAMMA_EX_DATA, + .roi_origin = DC_OVERLAY_ROI_ORIGIN, + .roi_size = DC_OVERLAY_ROI_SIZE, + .YUVToRGBCoef0 = DC_OVERLAY_YUVTORGB_COEF0, + .YUVToRGBCoef1 = DC_OVERLAY_YUVTORGB_COEF1, + .YUVToRGBCoef2 = DC_OVERLAY_YUVTORGB_COEF2, + .YUVToRGBCoef3 = DC_OVERLAY_YUVTORGB_COEF3, + .YUVToRGBCoef4 = DC_OVERLAY_YUVTORGB_COEF4, + .YUVToRGBCoefD0 = DC_OVERLAY_YUVTORGB_COEFD0, + .YUVToRGBCoefD1 = DC_OVERLAY_YUVTORGB_COEFD1, + .YUVToRGBCoefD2 = DC_OVERLAY_YUVTORGB_COEFD2, + .YClampBound = DC_OVERLAY_Y_CLAMP_BOUND, + .UVClampBound = DC_OVERLAY_UV_CLAMP_BOUND, + .RGBToRGBCoef0 = DC_OVERLAY_RGBTORGB_COEF0, + .RGBToRGBCoef1 = DC_OVERLAY_RGBTORGB_COEF1, + .RGBToRGBCoef2 = DC_OVERLAY_RGBTORGB_COEF2, + .RGBToRGBCoef3 = DC_OVERLAY_RGBTORGB_COEF3, + .RGBToRGBCoef4 = DC_OVERLAY_RGBTORGB_COEF4, + }, +}; + +static const u32 read_reg[] = { + DC_FRAMEBUFFER_CONFIG, + DC_OVERLAY_CONFIG, + DC_OVERLAY_SCALE_CONFIG, +}; +#define READ_REG_COUNT ARRAY_SIZE(read_reg) + +static const u32 primary_overlay_formats[] = { + DRM_FORMAT_XRGB4444, DRM_FORMAT_ARGB4444, DRM_FORMAT_XRGB1555, + DRM_FORMAT_ARGB1555, DRM_FORMAT_RGB565, DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, DRM_FORMAT_ARGB2101010, DRM_FORMAT_RGBA4444, + DRM_FORMAT_RGBA5551, DRM_FORMAT_RGBA8888, DRM_FORMAT_RGBA1010102, + DRM_FORMAT_ABGR4444, DRM_FORMAT_ABGR1555, DRM_FORMAT_BGR565, + DRM_FORMAT_ABGR8888, DRM_FORMAT_ABGR2101010, DRM_FORMAT_BGRA4444, + DRM_FORMAT_BGRA5551, DRM_FORMAT_BGRA8888, DRM_FORMAT_BGRA1010102, + DRM_FORMAT_XBGR4444, DRM_FORMAT_RGBX4444, DRM_FORMAT_BGRX4444, + DRM_FORMAT_XBGR1555, DRM_FORMAT_RGBX5551, DRM_FORMAT_BGRX5551, + DRM_FORMAT_XBGR8888, DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888, + DRM_FORMAT_YUYV, DRM_FORMAT_YVYU, DRM_FORMAT_UYVY, + DRM_FORMAT_VYUY, DRM_FORMAT_NV12, DRM_FORMAT_NV21, + DRM_FORMAT_NV16, DRM_FORMAT_NV61, DRM_FORMAT_P010, + /* TODO. */ +}; + +static const u32 cursor_formats[] = { DRM_FORMAT_ARGB8888 }; + +static const u64 cursor_dumy_modifiers[] = { DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID }; + +static const u64 format_modifiers[] = { DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID }; + +#define FRAC_16_16(mult, div) (((mult) << 16) / (div)) + +static const struct es_plane_info dc_hw_planes[][PLANE_NUM] = { + { + /* DC_REV_5551 */ + { + .name = "Primary", + .id = PRIMARY_PLANE, + .type = DRM_PLANE_TYPE_PRIMARY, + .num_formats = ARRAY_SIZE(primary_overlay_formats), + .formats = primary_overlay_formats, + .modifiers = format_modifiers, + .min_width = 0, + .min_height = 0, + .max_width = 4096, + .max_height = 4096, + .rotation = DRM_MODE_ROTATE_0, + .color_encoding = BIT(DRM_COLOR_YCBCR_BT601) | + BIT(DRM_COLOR_YCBCR_BT709) | + BIT(DRM_COLOR_YCBCR_BT2020), + .degamma_size = 0, + .min_scale = FRAC_16_16(1, 3), + .max_scale = FRAC_16_16(10, 1), + }, + { + .name = "Cursor", + .id = CURSOR_PLANE, + .type = DRM_PLANE_TYPE_CURSOR, + .num_formats = ARRAY_SIZE(cursor_formats), + .formats = cursor_formats, + .modifiers = NULL, + .min_width = 32, + .min_height = 32, + .max_width = 32, + .max_height = 32, + .rotation = DRM_MODE_ROTATE_0, + .degamma_size = 0, + .min_scale = DRM_PLANE_NO_SCALING, + .max_scale = DRM_PLANE_NO_SCALING, + }, + { + .name = "Overlay", + .id = OVERLAY_PLANE, + .type = DRM_PLANE_TYPE_OVERLAY, + .num_formats = ARRAY_SIZE(primary_overlay_formats), + .formats = primary_overlay_formats, + .modifiers = format_modifiers, + .min_width = 0, + .min_height = 0, + .max_width = 4096, + .max_height = 4096, + .rotation = DRM_MODE_ROTATE_0, + .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI) | + BIT(DRM_MODE_BLEND_COVERAGE), + .color_encoding = BIT(DRM_COLOR_YCBCR_BT601) | + BIT(DRM_COLOR_YCBCR_BT709) | + BIT(DRM_COLOR_YCBCR_BT2020), + .degamma_size = 0, + .min_scale = FRAC_16_16(1, 3), + .max_scale = FRAC_16_16(10, 1), + }, + }, + { + /* DC_REV_5551_306 */ + { + .name = "Primary", + .id = PRIMARY_PLANE, + .type = DRM_PLANE_TYPE_PRIMARY, + .num_formats = ARRAY_SIZE(primary_overlay_formats), + .formats = primary_overlay_formats, + .modifiers = format_modifiers, + .min_width = 0, + .min_height = 0, + .max_width = 4096, + .max_height = 4096, + .rotation = DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_90 | + DRM_MODE_ROTATE_180 | DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y, + .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) | + BIT(DRM_COLOR_YCBCR_BT2020), + .degamma_size = 0, + .min_scale = FRAC_16_16(1, 3), + .max_scale = FRAC_16_16(10, 1), + }, + { + .name = "Cursor", + .id = CURSOR_PLANE, + .type = DRM_PLANE_TYPE_CURSOR, + .num_formats = ARRAY_SIZE(cursor_formats), + .formats = cursor_formats, + .modifiers = NULL, + .min_width = 32, + .min_height = 32, + .max_width = 32, + .max_height = 32, + .rotation = 0, + .color_encoding = 0, + .degamma_size = 0, + .min_scale = DRM_PLANE_NO_SCALING, + .max_scale = DRM_PLANE_NO_SCALING, + }, + { + .name = "Overlay", + .id = OVERLAY_PLANE, + .type = DRM_PLANE_TYPE_OVERLAY, + .num_formats = ARRAY_SIZE(primary_overlay_formats), + .formats = primary_overlay_formats, + .modifiers = format_modifiers, + .min_width = 0, + .min_height = 0, + .max_width = 4096, + .max_height = 4096, + .rotation = DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_90 | + DRM_MODE_ROTATE_180 | DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y, + .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI) | + BIT(DRM_MODE_BLEND_COVERAGE), + .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) | + BIT(DRM_COLOR_YCBCR_BT2020), + .degamma_size = 0, + .min_scale = FRAC_16_16(1, 3), + .max_scale = FRAC_16_16(10, 1), + }, + }, + { + /* DC_REV_5701_303 */ + { + .name = "Primary", + .id = PRIMARY_PLANE, + .type = DRM_PLANE_TYPE_PRIMARY, + .num_formats = ARRAY_SIZE(primary_overlay_formats), + .formats = primary_overlay_formats, + .modifiers = format_modifiers, + .min_width = 0, + .min_height = 0, + .max_width = 4096, + .max_height = 4096, + .rotation = DRM_MODE_ROTATE_0, + .color_encoding = BIT(DRM_COLOR_YCBCR_BT601) | + BIT(DRM_COLOR_YCBCR_BT709) | + BIT(DRM_COLOR_YCBCR_BT2020), + .degamma_size = DEGAMMA_SIZE, + .min_scale = FRAC_16_16(1, 3), + .max_scale = FRAC_16_16(10, 1), + }, + { + .name = "Cursor", + .id = CURSOR_PLANE, + .type = DRM_PLANE_TYPE_CURSOR, + .num_formats = ARRAY_SIZE(cursor_formats), + .formats = cursor_formats, + .modifiers = NULL, + .min_width = 32, + .min_height = 32, + .max_width = 256, + .max_height = 256, + .rotation = DRM_MODE_ROTATE_0, + .degamma_size = 0, + .min_scale = DRM_PLANE_NO_SCALING, + .max_scale = DRM_PLANE_NO_SCALING, + }, + { + .name = "Overlay", + .id = OVERLAY_PLANE, + .type = DRM_PLANE_TYPE_OVERLAY, + .num_formats = ARRAY_SIZE(primary_overlay_formats), + .formats = primary_overlay_formats, + .modifiers = format_modifiers, + .min_width = 0, + .min_height = 0, + .max_width = 4096, + .max_height = 4096, + .rotation = DRM_MODE_ROTATE_0, + .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI) | + BIT(DRM_MODE_BLEND_COVERAGE), + .color_encoding = BIT(DRM_COLOR_YCBCR_BT601) | + BIT(DRM_COLOR_YCBCR_BT709) | + BIT(DRM_COLOR_YCBCR_BT2020), + .degamma_size = DEGAMMA_SIZE, + .min_scale = FRAC_16_16(1, 3), + .max_scale = FRAC_16_16(10, 1), + }, + }, + { + /* DC_REV_5701_309 */ + { + .name = "Primary", + .id = PRIMARY_PLANE, + .type = DRM_PLANE_TYPE_PRIMARY, + .num_formats = ARRAY_SIZE(primary_overlay_formats), + .formats = primary_overlay_formats, + .modifiers = format_modifiers, + .min_width = 0, + .min_height = 0, + .max_width = 4096, + .max_height = 4096, + .rotation = DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180, + .color_encoding = BIT(DRM_COLOR_YCBCR_BT601) | + BIT(DRM_COLOR_YCBCR_BT709) | + BIT(DRM_COLOR_YCBCR_BT2020), + .degamma_size = DEGAMMA_SIZE, + .min_scale = FRAC_16_16(1, 32), + .max_scale = FRAC_16_16(2, 1), + .roi = true, + .color_mgmt = true, + .background = true, + }, + { + .name = "Cursor", + .id = CURSOR_PLANE, + .type = DRM_PLANE_TYPE_CURSOR, + .num_formats = ARRAY_SIZE(cursor_formats), + .formats = cursor_formats, + .modifiers = cursor_dumy_modifiers, + .min_width = 32, + .min_height = 32, + .max_width = 256, + .max_height = 256, + .rotation = DRM_MODE_ROTATE_0, + .degamma_size = 0, + .min_scale = DRM_PLANE_NO_SCALING, + .max_scale = DRM_PLANE_NO_SCALING, + }, + { + .name = "Overlay", + .id = OVERLAY_PLANE, + .type = DRM_PLANE_TYPE_OVERLAY, + .num_formats = ARRAY_SIZE(primary_overlay_formats), + .formats = primary_overlay_formats, + .modifiers = format_modifiers, + .min_width = 0, + .min_height = 0, + .max_width = 4096, + .max_height = 4096, + .rotation = DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180, + .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI) | + BIT(DRM_MODE_BLEND_COVERAGE), + .color_encoding = BIT(DRM_COLOR_YCBCR_BT601) | + BIT(DRM_COLOR_YCBCR_BT709) | + BIT(DRM_COLOR_YCBCR_BT2020), + .degamma_size = DEGAMMA_SIZE, + .min_scale = FRAC_16_16(1, 32), + .max_scale = FRAC_16_16(2, 1), + .roi = true, + .color_mgmt = true, + }, + }, +}; + +static const struct es_dc_info dc_info[] = { + { + /* DC_REV_5551 */ + .name = "DisplayControl", + .plane_num = PLANE_NUM, + .planes = dc_hw_planes[DC_REV_5551], + .max_bpc = 10, + .color_formats = DRM_COLOR_FORMAT_RGB444, + .gamma_size = GAMMA_SIZE, + .gamma_bits = 10, + .pitch_alignment = 128, + .pipe_sync = false, + .mmu_prefetch = false, + }, + { + /* DC_REV_5551_306 */ + .name = "DisplayControl", + .plane_num = PLANE_NUM, + .planes = dc_hw_planes[DC_REV_5551_306], + .max_bpc = 10, + .color_formats = DRM_COLOR_FORMAT_RGB444, + .gamma_size = GAMMA_SIZE, + .gamma_bits = 10, + .pitch_alignment = 128, + .pipe_sync = false, + .mmu_prefetch = false, + }, + { + /* DC_REV_5701_303 */ + .name = "DisplayControl", + .plane_num = PLANE_NUM, + .planes = dc_hw_planes[DC_REV_5701_303], + .max_bpc = 10, + .color_formats = + DRM_COLOR_FORMAT_RGB444 | DRM_COLOR_FORMAT_YCBCR444 | + DRM_COLOR_FORMAT_YCBCR422 | DRM_COLOR_FORMAT_YCBCR420, + .gamma_size = GAMMA_EX_SIZE, + .gamma_bits = 12, + .pitch_alignment = 128, + .pipe_sync = false, + .mmu_prefetch = false, + }, + { + /* DC_REV_5701_309 */ + .name = "DisplayControl", + .plane_num = PLANE_NUM, + .planes = dc_hw_planes[DC_REV_5701_309], + .max_bpc = 10, + .color_formats = + DRM_COLOR_FORMAT_RGB444 | DRM_COLOR_FORMAT_YCBCR444 | + DRM_COLOR_FORMAT_YCBCR422 | DRM_COLOR_FORMAT_YCBCR420, + .gamma_size = GAMMA_EX_SIZE, + .gamma_bits = 12, + .pitch_alignment = 128, + .pipe_sync = true, + .mmu_prefetch = true, + .background = true, + }, +}; + +static const struct dc_hw_funcs hw_func[]; + +static inline u32 hi_write(struct dc_hw *hw, u32 reg, u32 val) +{ + writel(val, hw->hi_base + reg); + + return 0; +} + +static inline u32 hi_read(struct dc_hw *hw, u32 reg) +{ + u32 value; + + value = readl(hw->hi_base + reg); + return value; +} + +static inline int dc_get_read_index(struct dc_hw *hw, u32 reg) +{ + int i; + + for (i = 0; i < READ_REG_COUNT; i++) { + if (hw->read_block[i].reg == reg) + return i; + } + return i; +} + +static inline void dc_write(struct dc_hw *hw, u32 reg, u32 value) +{ + writel(value, hw->reg_base + reg - DC_REG_BASE); + + if (hw->read_block) { + int i = dc_get_read_index(hw, reg); + + if (i < READ_REG_COUNT) + hw->read_block[i].value = value; + } +} + +static inline u32 dc_read(struct dc_hw *hw, u32 reg) +{ + u32 value = readl(hw->reg_base + reg - DC_REG_BASE); + + if (hw->read_block) { + int i = dc_get_read_index(hw, reg); + + if (i < READ_REG_COUNT) { + value &= ~BIT(0); + value |= hw->read_block[i].value & BIT(0); + } + } + + return value; +} + +static inline void dc_set_clear(struct dc_hw *hw, u32 reg, u32 set, u32 clear) +{ + u32 value = dc_read(hw, reg); + + value &= ~clear; + value |= set; + dc_write(hw, reg, value); +} + +static void load_default_filter(struct dc_hw *hw, + const struct dc_hw_plane_reg *reg) +{ + u8 i; + + dc_write(hw, reg->scale_config, 0x33); + dc_write(hw, reg->init_offset, 0x80008000); + dc_write(hw, reg->h_filter_coef_index, 0x00); + for (i = 0; i < H_COEF_SIZE; i++) + dc_write(hw, reg->h_filter_coef_data, horKernel[i]); + + dc_write(hw, reg->v_filter_coef_index, 0x00); + for (i = 0; i < V_COEF_SIZE; i++) + dc_write(hw, reg->v_filter_coef_data, verKernel[i]); +} + +static void load_rgb_to_rgb(struct dc_hw *hw, const struct dc_hw_plane_reg *reg, + u16 *table) +{ + dc_write(hw, reg->RGBToRGBCoef0, table[0] | (table[1] << 16)); + dc_write(hw, reg->RGBToRGBCoef1, table[2] | (table[3] << 16)); + dc_write(hw, reg->RGBToRGBCoef2, table[4] | (table[5] << 16)); + dc_write(hw, reg->RGBToRGBCoef3, table[6] | (table[7] << 16)); + dc_write(hw, reg->RGBToRGBCoef4, table[8]); +} + +static void load_yuv_to_rgb(struct dc_hw *hw, const struct dc_hw_plane_reg *reg, + s32 *table) +{ + dc_write(hw, reg->YUVToRGBCoef0, + (0xFFFF & table[0]) | (table[1] << 16)); + dc_write(hw, reg->YUVToRGBCoef1, + (0xFFFF & table[2]) | (table[3] << 16)); + dc_write(hw, reg->YUVToRGBCoef2, + (0xFFFF & table[4]) | (table[5] << 16)); + dc_write(hw, reg->YUVToRGBCoef3, + (0xFFFF & table[6]) | (table[7] << 16)); + dc_write(hw, reg->YUVToRGBCoef4, table[8]); + dc_write(hw, reg->YUVToRGBCoefD0, table[9]); + dc_write(hw, reg->YUVToRGBCoefD1, table[10]); + dc_write(hw, reg->YUVToRGBCoefD2, table[11]); + dc_write(hw, reg->YClampBound, table[12] | (table[13] << 16)); + dc_write(hw, reg->UVClampBound, table[14] | (table[15] << 16)); +} + +static void load_rgb_to_yuv(struct dc_hw *hw, s16 *table) +{ + dc_write(hw, DC_DISPLAY_RGBTOYUV_COEF0, table[0] | (table[1] << 16)); + dc_write(hw, DC_DISPLAY_RGBTOYUV_COEF1, table[2] | (table[3] << 16)); + dc_write(hw, DC_DISPLAY_RGBTOYUV_COEF2, table[4] | (table[5] << 16)); + dc_write(hw, DC_DISPLAY_RGBTOYUV_COEF3, table[6] | (table[7] << 16)); + dc_write(hw, DC_DISPLAY_RGBTOYUV_COEF4, table[8]); + dc_write(hw, DC_DISPLAY_RGBTOYUV_COEFD0, table[9]); + dc_write(hw, DC_DISPLAY_RGBTOYUV_COEFD1, table[10]); + dc_write(hw, DC_DISPLAY_RGBTOYUV_COEFD2, table[11]); +} + +static bool is_rgb(enum dc_hw_color_format format) +{ + switch (format) { + case FORMAT_X4R4G4B4: + case FORMAT_A4R4G4B4: + case FORMAT_X1R5G5B5: + case FORMAT_A1R5G5B5: + case FORMAT_R5G6B5: + case FORMAT_X8R8G8B8: + case FORMAT_A8R8G8B8: + case FORMAT_A2R10G10B10: + return true; + default: + return false; + } +} + +static void load_degamma_table(struct dc_hw *hw, + const struct dc_hw_plane_reg *reg, u16 *table) +{ + u16 i; + u32 value; + + for (i = 0; i < DEGAMMA_SIZE; i++) { + dc_write(hw, reg->degamma_index, i); + value = table[i] | (table[i] << 16); + dc_write(hw, reg->degamma_data, value); + dc_write(hw, reg->degamma_ex_data, table[i]); + } +} + +int dc_hw_init(struct dc_hw *hw) +{ + u8 i; + u32 revision = hi_read(hw, DC_HW_REVISION); + u32 cid = hi_read(hw, DC_HW_CHIP_CID); + + switch (revision) { + case 0x5551: + if (cid < 0x306) + hw->rev = DC_REV_5551; + else + hw->rev = DC_REV_5551_306; + break; + case 0x5701: + if (cid < 0x309) + hw->rev = DC_REV_5701_303; + else + hw->rev = DC_REV_5701_309; + break; + default: + return -ENXIO; + } + + if (hw->rev == DC_REV_5551) { + hw->read_block = kzalloc( + sizeof(struct dc_hw_read) * READ_REG_COUNT, GFP_KERNEL); + if (!hw->read_block) + return -ENOMEM; + for (i = 0; i < READ_REG_COUNT; i++) + hw->read_block[i].reg = read_reg[i]; + } + + /* + * Do soft reset before configuring DC + * Sleep 50ms to ensure no data transfer on AXI bus, + * because dc may be initialized in uboot + */ + { + u32 val; + + val = dc_read(hw, DC_FRAMEBUFFER_CONFIG); + val &= ~BIT(0); + val |= BIT(3); + dc_write(hw, DC_FRAMEBUFFER_CONFIG, val); + mdelay(50); + hi_write(hw, 0x0, 0x1000); + mdelay(10); + } + + hw->info = (struct es_dc_info *)&dc_info[hw->rev]; + hw->func = (struct dc_hw_funcs *)&hw_func[hw->rev]; + + for (i = 0; i < ARRAY_SIZE(dc_plane_reg); i++) { + load_default_filter(hw, &dc_plane_reg[i]); + if ((hw->rev == DC_REV_5701_303) || + (hw->rev == DC_REV_5701_309)) + load_rgb_to_rgb(hw, &dc_plane_reg[i], RGB2RGB); + } + + if (hw->rev == DC_REV_5701_303) + load_rgb_to_yuv(hw, RGB2YUV); + + dc_write(hw, DC_CURSOR_BACKGROUND, 0x00FFFFFF); + dc_write(hw, DC_CURSOR_FOREGROUND, 0x00AAAAAA); + dc_write(hw, DC_DISPLAY_PANEL_CONFIG, 0x111); + + return 0; +} + +void dc_hw_deinit(struct dc_hw *hw) +{ + if (hw->read_block) + kfree(hw->read_block); +} + +void dc_hw_update_plane(struct dc_hw *hw, enum dc_hw_plane_id id, + struct dc_hw_fb *fb, struct dc_hw_scale *scale) +{ + struct dc_hw_plane *plane = NULL; + + if (id == PRIMARY_PLANE) + plane = &hw->primary; + else if (id == OVERLAY_PLANE) + plane = &hw->overlay.plane; + + if (plane) { + if (fb) { + if (fb->enable == false) + plane->fb.enable = false; + else + memcpy(&plane->fb, fb, + sizeof(*fb) - sizeof(fb->dirty)); + plane->fb.dirty = true; + } + if (scale) { + memcpy(&plane->scale, scale, + sizeof(*scale) - sizeof(scale->dirty)); + plane->scale.dirty = true; + } + } +} + +void dc_hw_update_degamma(struct dc_hw *hw, enum dc_hw_plane_id id, u32 mode) +{ + struct dc_hw_plane *plane = NULL; + int i = (int)id; + + if (id == PRIMARY_PLANE) + plane = &hw->primary; + else if (id == OVERLAY_PLANE) + plane = &hw->overlay.plane; + + if (plane) { + if (hw->info->planes[i].degamma_size) { + plane->degamma.mode = mode; + plane->degamma.dirty = true; + } else { + plane->degamma.dirty = false; + } + } +} + +void dc_hw_set_position(struct dc_hw *hw, struct dc_hw_position *pos) +{ + memcpy(&hw->overlay.pos, pos, sizeof(*pos) - sizeof(pos->dirty)); + hw->overlay.pos.dirty = true; +} + +void dc_hw_set_blend(struct dc_hw *hw, struct dc_hw_blend *blend) +{ + memcpy(&hw->overlay.blend, blend, + sizeof(*blend) - sizeof(blend->dirty)); + hw->overlay.blend.dirty = true; +} + +void dc_hw_update_cursor(struct dc_hw *hw, struct dc_hw_cursor *cursor) +{ + memcpy(&hw->cursor, cursor, sizeof(*cursor) - sizeof(cursor->dirty)); + hw->cursor.dirty = true; +} + +void dc_hw_update_gamma(struct dc_hw *hw, u16 index, u16 r, u16 g, u16 b) +{ + if (index >= hw->info->gamma_size) + return; + + hw->gamma.gamma[index][0] = r; + hw->gamma.gamma[index][1] = g; + hw->gamma.gamma[index][2] = b; + hw->gamma.dirty = true; +} + +void dc_hw_enable_gamma(struct dc_hw *hw, bool enable) +{ + hw->gamma.enable = enable; + hw->gamma.dirty = true; +} + +void dc_hw_enable_dump(struct dc_hw *hw, u32 addr, u32 pitch) +{ + dc_write(hw, 0x14F0, addr); + dc_write(hw, 0x14E8, addr); + dc_write(hw, 0x1500, pitch); + dc_write(hw, 0x14F8, 0x30000); +} + +void dc_hw_disable_dump(struct dc_hw *hw) +{ + dc_write(hw, 0x14F8, 0x00); +} + +void dc_hw_setup_display(struct dc_hw *hw, struct dc_hw_display *display) +{ + memcpy(&hw->display, display, sizeof(*display)); + + hw->func->display(hw, display); +} + +void dc_hw_enable_interrupt(struct dc_hw *hw, bool enable) +{ + dc_write(hw, DC_DISPLAY_INT_ENABLE, enable); +} + +u32 dc_hw_get_interrupt(struct dc_hw *hw) +{ + return dc_read(hw, DC_DISPLAY_INT); +} + +bool dc_hw_flip_in_progress(struct dc_hw *hw) +{ + return dc_read(hw, DC_FRAMEBUFFER_CONFIG) & BIT(6); +} + +bool dc_hw_check_underflow(struct dc_hw *hw) +{ + return dc_read(hw, DC_FRAMEBUFFER_CONFIG) & BIT(5); +} + +void dc_hw_enable_shadow_register(struct dc_hw *hw, bool enable) +{ + if (enable) + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG, 0, BIT(3)); + else + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG, BIT(3), 0); +} + +void dc_hw_set_out(struct dc_hw *hw, enum dc_hw_out out) +{ + if (out <= OUT_DP) + hw->out = out; +} + +static void gamma_commit(struct dc_hw *hw) +{ + u16 i; + u32 value; + + if (hw->gamma.dirty) { + if (hw->gamma.enable) { + dc_write(hw, DC_DISPLAY_GAMMA_INDEX, 0x00); + for (i = 0; i < GAMMA_SIZE; i++) { + value = hw->gamma.gamma[i][2] | + (hw->gamma.gamma[i][1] << 10) | + (hw->gamma.gamma[i][0] << 20); + dc_write(hw, DC_DISPLAY_GAMMA_DATA, value); + } + + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG, BIT(2), 0); + } else { + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG, 0, BIT(2)); + } + hw->gamma.dirty = false; + } +} + +static void gamma_ex_commit(struct dc_hw *hw) +{ + u16 i; + u32 value; + + if (hw->gamma.dirty) { + if (hw->gamma.enable) { + dc_write(hw, DC_DISPLAY_GAMMA_EX_INDEX, 0x00); + for (i = 0; i < GAMMA_EX_SIZE; i++) { + value = hw->gamma.gamma[i][2] | + (hw->gamma.gamma[i][1] << 16); + dc_write(hw, DC_DISPLAY_GAMMA_EX_DATA, value); + dc_write(hw, DC_DISPLAY_GAMMA_EX_ONE_DATA, + hw->gamma.gamma[i][0]); + } + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG, BIT(2), 0); + } else { + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG, 0, BIT(2)); + } + hw->gamma.dirty = false; + } +} + +static void plane_commit(struct dc_hw *hw) +{ + struct dc_hw_plane *plane; + const struct dc_hw_plane_reg *reg; + u16 i; + + for (i = 0; i < PLANE_NUM; i++) { + if (i == PRIMARY_PLANE) { + plane = &hw->primary; + reg = &dc_plane_reg[0]; + } else if (i == OVERLAY_PLANE) { + plane = &hw->overlay.plane; + reg = &dc_plane_reg[1]; + } else { + continue; + } + + if (plane->fb.dirty) { + if (plane->fb.enable) { + // sifive_l2_flush64_range(plane->fb.y_address, plane->fb.width * plane->fb.height * 4); + + dc_write(hw, reg->y_address, + plane->fb.y_address); + dc_write(hw, reg->u_address, + plane->fb.u_address); + dc_write(hw, reg->v_address, + plane->fb.v_address); + dc_write(hw, reg->y_stride, plane->fb.y_stride); + dc_write(hw, reg->u_stride, plane->fb.u_stride); + dc_write(hw, reg->v_stride, plane->fb.v_stride); + dc_write(hw, reg->size, + plane->fb.width | + (plane->fb.height << 15)); + if (plane->fb.clear_enable) + dc_write(hw, reg->clear_value, + plane->fb.clear_value); + } else { + dc_hw_enable_shadow_register(hw, true); + if (i == PRIMARY_PLANE) { + u32 reg_clr = BIT(24), reg_set = 0; + dc_set_clear(hw, DC_OVERLAY_CONFIG, + reg_set, reg_clr); + + reg_clr = (0x1F << 26), + reg_set = BIT(8) | (05 << 26); + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG, + reg_set, reg_clr); + } + mdelay(50); + dc_hw_enable_shadow_register(hw, false); + } + + if (i == PRIMARY_PLANE) { + u32 reg_clr = 0, reg_set = 0; + + reg_clr = (0x1F << 26) | BIT(25) | + (0x03 << 23) | (0x1F << 17) | + (0x07 << 14) | (0x07 << 11) | BIT(0); + reg_set = (plane->fb.format << 26) | + (plane->fb.uv_swizzle << 25) | + (plane->fb.swizzle << 23) | + (plane->fb.tile_mode << 17) | + (plane->fb.yuv_color_space << 14) | + (plane->fb.rotation << 11) | + (plane->fb.clear_enable << 8) | + plane->fb.enable; + if (plane->fb.enable) { + reg_clr |= BIT(8); + reg_set |= BIT(4); + } + + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG, reg_set, + reg_clr); + } else + dc_set_clear( + hw, DC_OVERLAY_CONFIG, + (plane->fb.format << 16) | + (plane->fb.uv_swizzle << 15) | + (plane->fb.swizzle << 13) | + (plane->fb.tile_mode << 8) | + (plane->fb.yuv_color_space + << 5) | + (plane->fb.rotation << 2) | + (plane->fb.enable << 24) | + (plane->fb.clear_enable << 25), + (0x1F << 16) | BIT(15) | (0x03 << 13) | + (0x1F << 8) | (0x07 << 5) | + (0x07 << 2) | BIT(24)); + plane->fb.dirty = false; + } + + if (plane->scale.dirty) { + if (plane->scale.enable) { + dc_write(hw, reg->scale_factor_x, + plane->scale.scale_factor_x); + dc_write(hw, reg->scale_factor_y, + plane->scale.scale_factor_y); + if (i == PRIMARY_PLANE) + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG, + BIT(22), 0); + else + dc_set_clear(hw, + DC_OVERLAY_SCALE_CONFIG, + BIT(8), 0); + + } else { + if (i == PRIMARY_PLANE) + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG, + 0, BIT(22)); + else + dc_set_clear(hw, + DC_OVERLAY_SCALE_CONFIG, 0, + BIT(8)); + } + plane->scale.dirty = false; + } + + if (plane->colorkey.dirty) { + dc_write(hw, reg->color_key, plane->colorkey.colorkey); + dc_write(hw, reg->color_key_high, + plane->colorkey.colorkey_high); + + if (i == PRIMARY_PLANE) + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG, + plane->colorkey.transparency << 9, + 0x03 << 9); + else + dc_set_clear(hw, DC_OVERLAY_CONFIG, + plane->colorkey.transparency, + 0x03); + + plane->colorkey.dirty = false; + } + + if (plane->roi.dirty) { + if (plane->roi.enable) { + dc_write(hw, reg->roi_origin, + plane->roi.x | (plane->roi.y << 16)); + dc_write(hw, reg->roi_size, + plane->roi.width | + (plane->roi.height << 16)); + if (i == PRIMARY_PLANE) + dc_set_clear(hw, + DC_FRAMEBUFFER_CONFIG_EX, + BIT(0), 0); + } else { + // if (i == PRIMARY_PLANE) + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG_EX, 0, + BIT(0)); + } + plane->roi.dirty = false; + } + } +} + +static void plane_ex_commit(struct dc_hw *hw) +{ + struct dc_hw_plane *plane; + const struct dc_hw_plane_reg *reg; + u16 i; + + for (i = 0; i < PLANE_NUM; i++) { + if (i == PRIMARY_PLANE) { + plane = &hw->primary; + reg = &dc_plane_reg[0]; + } else if (i == OVERLAY_PLANE) { + plane = &hw->overlay.plane; + reg = &dc_plane_reg[1]; + } else { + continue; + } + + if (plane->fb.dirty) { + if (is_rgb(plane->fb.format)) { + if (i == PRIMARY_PLANE) + dc_set_clear(hw, + DC_FRAMEBUFFER_CONFIG_EX, + BIT(6), BIT(8)); + else + dc_set_clear(hw, DC_OVERLAY_CONFIG, + BIT(29), BIT(30)); + } else { + if (i == PRIMARY_PLANE) + dc_set_clear(hw, + DC_FRAMEBUFFER_CONFIG_EX, + BIT(8), BIT(6)); + else + dc_set_clear(hw, DC_OVERLAY_CONFIG, + BIT(30), BIT(29)); + switch (plane->fb.yuv_color_space) { + case COLOR_SPACE_601: + load_yuv_to_rgb(hw, reg, YUV601_2RGB); + break; + case COLOR_SPACE_709: + load_yuv_to_rgb(hw, reg, YUV709_2RGB); + break; + case COLOR_SPACE_2020: + load_yuv_to_rgb(hw, reg, YUV2020_2RGB); + break; + default: + break; + } + } + } + if (plane->degamma.dirty) { + switch (plane->degamma.mode) { + case ES_DEGAMMA_DISABLE: + if (i == PRIMARY_PLANE) + dc_set_clear(hw, + DC_FRAMEBUFFER_CONFIG_EX, + 0, BIT(5)); + else + dc_set_clear(hw, DC_OVERLAY_CONFIG, 0, + BIT(28)); + break; + case ES_DEGAMMA_BT709: + load_degamma_table(hw, reg, DEGAMMA_709); + if (i == PRIMARY_PLANE) + dc_set_clear(hw, + DC_FRAMEBUFFER_CONFIG_EX, + BIT(5), 0); + else + dc_set_clear(hw, DC_OVERLAY_CONFIG, + BIT(28), 0); + break; + case ES_DEGAMMA_BT2020: + load_degamma_table(hw, reg, DEGAMMA_2020); + if (i == PRIMARY_PLANE) + dc_set_clear(hw, + DC_FRAMEBUFFER_CONFIG_EX, + BIT(5), 0); + else + dc_set_clear(hw, DC_OVERLAY_CONFIG, + BIT(28), 0); + break; + default: + break; + } + plane->degamma.dirty = false; + } + } + plane_commit(hw); +} + +static void setup_display(struct dc_hw *hw, struct dc_hw_display *display) +{ + u32 dpi_cfg; + + if (hw->display.enable) { + switch (display->bus_format) { + case MEDIA_BUS_FMT_RGB565_1X16: + dpi_cfg = 0; + break; + case MEDIA_BUS_FMT_RGB666_1X18: + dpi_cfg = 3; + break; + case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: + dpi_cfg = 4; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + dpi_cfg = 5; + break; + case MEDIA_BUS_FMT_RGB101010_1X30: + dpi_cfg = 6; + break; + default: + dpi_cfg = 5; + break; + } + dc_write(hw, DC_DISPLAY_DPI_CONFIG, dpi_cfg); + + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG, 0, BIT(4)); + + dc_write(hw, DC_DISPLAY_H, + hw->display.h_active | (hw->display.h_total << 16)); + dc_write(hw, DC_DISPLAY_H_SYNC, + hw->display.h_sync_start | + (hw->display.h_sync_end << 15) | + (hw->display.h_sync_polarity ? 0 : BIT(31)) | + BIT(30)); + dc_write(hw, DC_DISPLAY_V, + hw->display.v_active | (hw->display.v_total << 16)); + dc_write(hw, DC_DISPLAY_V_SYNC, + hw->display.v_sync_start | + (hw->display.v_sync_end << 15) | + (hw->display.v_sync_polarity ? 0 : BIT(31)) | + BIT(30)); + + if (hw->info->pipe_sync) { + switch (display->sync_mode) { + case ES_SINGLE_DC: + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG_EX, 0, + BIT(3) | BIT(4)); + break; + case ES_MULTI_DC_PRIMARY: + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG_EX, + BIT(3) | BIT(4), 0); + break; + case ES_MULTI_DC_SECONDARY: + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG_EX, + BIT(3), BIT(4)); + break; + default: + break; + } + } + + if (hw->display.dither_enable) { + dc_write(hw, DC_DISPLAY_DITHER_TABLE_LOW, + DC_DISPLAY_DITHERTABLE_LOW); + dc_write(hw, DC_DISPLAY_DITHER_TABLE_HIGH, + DC_DISPLAY_DITHERTABLE_HIGH); + dc_write(hw, DC_DISPLAY_DITHER_CONFIG, BIT(31)); + } else { + dc_write(hw, DC_DISPLAY_DITHER_CONFIG, 0); + } + + if (hw->info->background) + dc_write(hw, DC_FRAMEBUFFER_BG_COLOR, + hw->display.bg_color); + + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG, BIT(4), 0); + } else { + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG, 0, BIT(4) | BIT(0)); + } +} + +static void setup_display_ex(struct dc_hw *hw, struct dc_hw_display *display) +{ + u32 dp_cfg; + bool is_yuv = false; + + if (hw->display.enable && hw->out == OUT_DP) { + switch (display->bus_format) { + case MEDIA_BUS_FMT_RGB565_1X16: + dp_cfg = 0; + break; + case MEDIA_BUS_FMT_RGB666_1X18: + dp_cfg = 1; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + dp_cfg = 2; + break; + case MEDIA_BUS_FMT_RGB101010_1X30: + dp_cfg = 3; + break; + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + dp_cfg = 0; + is_yuv = true; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + dp_cfg = 2 << 4; + is_yuv = true; + break; + case MEDIA_BUS_FMT_YUV8_1X24: + dp_cfg = 4 << 4; + is_yuv = true; + break; + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + dp_cfg = 6 << 4; + is_yuv = true; + break; + case MEDIA_BUS_FMT_UYVY10_1X20: + dp_cfg = 8 << 4; + is_yuv = true; + break; + case MEDIA_BUS_FMT_YUV10_1X30: + dp_cfg = 10 << 4; + is_yuv = true; + break; + default: + dp_cfg = 2; + break; + } + if (is_yuv) + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG_EX, BIT(7), 0); + else + dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG_EX, 0, BIT(7)); + dc_write(hw, DC_DISPLAY_DP_CONFIG, dp_cfg | BIT(3)); + } + + if (hw->out == OUT_DPI) + dc_set_clear(hw, DC_DISPLAY_DP_CONFIG, 0, BIT(3)); + + setup_display(hw, display); +} + +static const struct dc_hw_funcs hw_func[] = { + { + .gamma = &gamma_commit, + .plane = &plane_commit, + .display = setup_display, + }, + { + .gamma = &gamma_commit, + .plane = &plane_commit, + .display = setup_display, + }, + { + .gamma = &gamma_ex_commit, + .plane = &plane_ex_commit, + .display = setup_display_ex, + }, + { + .gamma = &gamma_ex_commit, + .plane = &plane_ex_commit, + .display = setup_display_ex, + }, +}; + +void dc_hw_commit(struct dc_hw *hw) +{ + hw->func->gamma(hw); + hw->func->plane(hw); + + if (hw->overlay.pos.dirty) { + dc_write(hw, DC_OVERLAY_TOP_LEFT, + hw->overlay.pos.start_x | + (hw->overlay.pos.start_y << 15)); + dc_write(hw, DC_OVERLAY_BOTTOM_RIGHT, + hw->overlay.pos.end_x | (hw->overlay.pos.end_y << 15)); + hw->overlay.pos.dirty = false; + } + + if (hw->overlay.blend.dirty) { + dc_write(hw, DC_OVERLAY_SRC_GLOBAL_COLOR, + hw->overlay.blend.alpha << 24); + dc_write(hw, DC_OVERLAY_DST_GLOBAL_COLOR, + hw->overlay.blend.alpha << 24); + switch (hw->overlay.blend.blend_mode) { + case BLEND_PREMULTI: + dc_write(hw, DC_OVERLAY_BLEND_CONFIG, 0x3450); + break; + case BLEND_COVERAGE: + dc_write(hw, DC_OVERLAY_BLEND_CONFIG, 0x3950); + break; + case BLEND_PIXEL_NONE: + dc_write(hw, DC_OVERLAY_BLEND_CONFIG, 0x3548); + break; + default: + break; + } + hw->overlay.blend.dirty = false; + } + + if (hw->cursor.dirty) { + if (hw->cursor.enable) { + dc_write(hw, DC_CURSOR_ADDRESS, hw->cursor.address); + dc_write(hw, DC_CURSOR_LOCATION, + hw->cursor.x | (hw->cursor.y << 16)); + dc_write(hw, DC_CURSOR_CONFIG, + (hw->cursor.hot_x << 16) | + (hw->cursor.hot_y << 8) | 0x02); + } else { + dc_write(hw, DC_CURSOR_CONFIG, 0x00); + } + hw->cursor.dirty = false; + } +} + +#ifdef CONFIG_ESWIN_MMU +static u32 mmu_read(struct dc_hw *hw, u32 reg) +{ + return readl(hw->mmu_base + reg - MMU_REG_BASE); +} + +static void mmu_write(struct dc_hw *hw, u32 reg, u32 value) +{ + writel(value, hw->mmu_base + reg - MMU_REG_BASE); +} + +static void mmu_set_clear(struct dc_hw *hw, u32 reg, u32 set, u32 clear) +{ + u32 value = mmu_read(hw, reg); + + value &= ~clear; + value |= set; + mmu_write(hw, reg, value); +} + +int dc_hw_mmu_init(struct dc_hw *hw, dc_mmu_pt mmu) +{ + u32 mtlb = 0, ext_mtlb = 0; + u32 safe_addr = 0, ext_safe_addr = 0; + u32 config = 0; + + mtlb = (u32)(mmu->mtlb_physical & 0xFFFFFFFF); + ext_mtlb = (u32)(mmu->mtlb_physical >> 32); + + /* more than 40bit physical address */ + if (ext_mtlb & 0xFFFFFF00) { + pr_err("Mtlb address out of range.\n"); + return -EFAULT; + } + + config = (ext_mtlb << 20) | (mtlb >> 12); + if (mmu->mode == MMU_MODE_1K) + mmu_set_clear(hw, MMU_REG_CONTEXT_PD, (config << 4) | BIT(0), + (0xFFFFFFF << 4) | (0x03)); + else + mmu_set_clear(hw, MMU_REG_CONTEXT_PD, (config << 4), + (0xFFFFFFF << 4) | (0x03)); + + safe_addr = (u32)(mmu->safe_page_physical & 0xFFFFFFFF); + ext_safe_addr = (u32)(mmu->safe_page_physical >> 32); + + if ((safe_addr & 0x3F) || (ext_safe_addr & 0xFFFFFF00)) { + pr_err("Invalid safe_address.\n"); + return -EFAULT; + } + + mmu_write(hw, MMU_REG_TABLE_ARRAY_SIZE, 1); + mmu_write(hw, MMU_REG_SAFE_SECURE, safe_addr); + mmu_write(hw, MMU_REG_SAFE_NON_SECURE, safe_addr); + + mmu_set_clear(hw, MMU_REG_SAFE_EXT_ADDRESS, + (ext_safe_addr << 16) | ext_safe_addr, + BIT(31) | (0xFF << 16) | BIT(15) | 0xFF); + + mmu_write(hw, MMU_REG_CONTROL, BIT(5) | BIT(0)); + + mmu_write(hw, DEC_REG_CONTROL, DEC_REG_CONTROL_VALUE); + + return 0; +} + +void dc_hw_enable_mmu_prefetch(struct dc_hw *hw, bool enable) +{ + if (enable) + dc_write(hw, DC_MMU_PREFETCH, BIT(0)); + else + dc_write(hw, DC_MMU_PREFETCH, 0); +} + +void dc_mmu_flush(struct dc_hw *hw) +{ + u32 config, read; + + read = mmu_read(hw, MMU_REG_CONFIG); + config = read | BIT(4); + + mmu_write(hw, MMU_REG_CONFIG, config); + mmu_write(hw, MMU_REG_CONFIG, read); +} +#endif + +void dc_hw_update_roi(struct dc_hw *hw, enum dc_hw_plane_id id, + struct dc_hw_roi *roi) +{ + struct dc_hw_plane *plane = NULL; + + if (id == PRIMARY_PLANE) + plane = &hw->primary; + else if (id == OVERLAY_PLANE) + plane = &hw->overlay.plane; + + if (plane) { + memcpy(&plane->roi, roi, sizeof(*roi) - sizeof(roi->dirty)); + plane->roi.dirty = true; + } +} + +void dc_hw_update_colorkey(struct dc_hw *hw, enum dc_hw_plane_id id, + struct dc_hw_colorkey *colorkey) +{ + struct dc_hw_plane *plane = NULL; + + if (id == PRIMARY_PLANE) + plane = &hw->primary; + else if (id == OVERLAY_PLANE) + plane = &hw->overlay.plane; + + if (plane) { + memcpy(&plane->colorkey, colorkey, + sizeof(*colorkey) - sizeof(colorkey->dirty)); + plane->colorkey.dirty = true; + } +} diff --git a/drivers/gpu/drm/eswin/es_dc_hw.h b/drivers/gpu/drm/eswin/es_dc_hw.h new file mode 100644 index 000000000000..885d225c9203 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_dc_hw.h @@ -0,0 +1,473 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#ifndef __ES_DC_HW_H__ +#define __ES_DC_HW_H__ + +#ifdef CONFIG_ESWIN_MMU +#include "es_dc_mmu.h" +#endif + +#define DC_HW_REVISION 0x24 +#define DC_HW_CHIP_CID 0x30 + +#define DC_REG_BASE 0x1400 + +#define DC_FRAMEBUFFER_CONFIG 0x1518 +#define DC_FRAMEBUFFER_CONFIG_EX 0x1CC0 +#define DC_FRAMEBUFFER_SCALE_CONFIG 0x1520 +#define DC_FRAMEBUFFER_ADDRESS 0x1400 +#define DC_FRAMEBUFFER_U_ADDRESS 0x1530 +#define DC_FRAMEBUFFER_V_ADDRESS 0x1538 +#define DC_FRAMEBUFFER_STRIDE 0x1408 +#define DC_FRAMEBUFFER_U_STRIDE 0x1800 +#define DC_FRAMEBUFFER_V_STRIDE 0x1808 +#define DC_FRAMEBUFFER_SIZE 0x1810 +#define DC_FRAMEBUFFER_SCALE_FACTOR_X 0x1828 +#define DC_FRAMEBUFFER_SCALE_FACTOR_Y 0x1830 +#define DC_FRAMEBUFFER_H_FILTER_COEF_INDEX 0x1838 +#define DC_FRAMEBUFFER_H_FILTER_COEF_DATA 0x1A00 +#define DC_FRAMEBUFFER_V_FILTER_COEF_INDEX 0x1A08 +#define DC_FRAMEBUFFER_V_FILTER_COEF_DATA 0x1A10 +#define DC_FRAMEBUFFER_INIT_OFFSET 0x1A20 +#define DC_FRAMEBUFFER_COLOR_KEY 0x1508 +#define DC_FRAMEBUFFER_COLOR_KEY_HIGH 0x1510 +#define DC_FRAMEBUFFER_CLEAR_VALUE 0x1A18 +#define DC_FRAMEBUFFER_COLOR_TABLE_INDEX 0x1818 +#define DC_FRAMEBUFFER_COLOR_TABLE_DATA 0x1820 +#define DC_FRAMEBUFFER_BG_COLOR 0x1528 +#define DC_FRAMEBUFFER_ROI_ORIGIN 0x1CB0 +#define DC_FRAMEBUFFER_ROI_SIZE 0x1CB8 +#define DC_FRAMEBUFFER_DEGAMMA_INDEX 0x1D88 +#define DC_FRAMEBUFFER_DEGAMMA_DATA 0x1D90 +#define DC_FRAMEBUFFER_DEGAMMA_EX_DATA 0x1D98 +#define DC_FRAMEBUFFER_YUVTORGB_COEF0 0x1DA0 +#define DC_FRAMEBUFFER_YUVTORGB_COEF1 0x1DA8 +#define DC_FRAMEBUFFER_YUVTORGB_COEF2 0x1DB0 +#define DC_FRAMEBUFFER_YUVTORGB_COEF3 0x1DB8 +#define DC_FRAMEBUFFER_YUVTORGB_COEF4 0x1E00 +#define DC_FRAMEBUFFER_YUVTORGB_COEFD0 0x1E08 +#define DC_FRAMEBUFFER_YUVTORGB_COEFD1 0x1E10 +#define DC_FRAMEBUFFER_YUVTORGB_COEFD2 0x1E18 +#define DC_FRAMEBUFFER_Y_CLAMP_BOUND 0x1E88 +#define DC_FRAMEBUFFER_UV_CLAMP_BOUND 0x1E90 +#define DC_FRAMEBUFFER_RGBTORGB_COEF0 0x1E20 +#define DC_FRAMEBUFFER_RGBTORGB_COEF1 0x1E28 +#define DC_FRAMEBUFFER_RGBTORGB_COEF2 0x1E30 +#define DC_FRAMEBUFFER_RGBTORGB_COEF3 0x1E38 +#define DC_FRAMEBUFFER_RGBTORGB_COEF4 0x1E40 + +#define DC_OVERLAY_CONFIG 0x1540 +#define DC_OVERLAY_SCALE_CONFIG 0x1C00 +#define DC_OVERLAY_BLEND_CONFIG 0x1580 +#define DC_OVERLAY_TOP_LEFT 0x1640 +#define DC_OVERLAY_BOTTOM_RIGHT 0x1680 +#define DC_OVERLAY_ADDRESS 0x15C0 +#define DC_OVERLAY_U_ADDRESS 0x1840 +#define DC_OVERLAY_V_ADDRESS 0x1880 +#define DC_OVERLAY_STRIDE 0x1600 +#define DC_OVERLAY_U_STRIDE 0x18C0 +#define DC_OVERLAY_V_STRIDE 0x1900 +#define DC_OVERLAY_SIZE 0x17C0 +#define DC_OVERLAY_SCALE_FACTOR_X 0x1A40 +#define DC_OVERLAY_SCALE_FACTOR_Y 0x1A80 +#define DC_OVERLAY_H_FILTER_COEF_INDEX 0x1AC0 +#define DC_OVERLAY_H_FILTER_COEF_DATA 0x1B00 +#define DC_OVERLAY_V_FILTER_COEF_INDEX 0x1B40 +#define DC_OVERLAY_V_FILTER_COEF_DATA 0x1B80 +#define DC_OVERLAY_INIT_OFFSET 0x1BC0 +#define DC_OVERLAY_COLOR_KEY 0x1740 +#define DC_OVERLAY_COLOR_KEY_HIGH 0x1780 +#define DC_OVERLAY_CLEAR_VALUE 0x1940 +#define DC_OVERLAY_COLOR_TABLE_INDEX 0x1980 +#define DC_OVERLAY_COLOR_TABLE_DATA 0x19C0 +#define DC_OVERLAY_SRC_GLOBAL_COLOR 0x16C0 +#define DC_OVERLAY_DST_GLOBAL_COLOR 0x1700 +#define DC_OVERLAY_ROI_ORIGIN 0x1D00 +#define DC_OVERLAY_ROI_SIZE 0x1D40 +#define DC_OVERLAY_DEGAMMA_INDEX 0x2200 +#define DC_OVERLAY_DEGAMMA_DATA 0x2240 +#define DC_OVERLAY_DEGAMMA_EX_DATA 0x2280 +#define DC_OVERLAY_YUVTORGB_COEF0 0x1EC0 +#define DC_OVERLAY_YUVTORGB_COEF1 0x1F00 +#define DC_OVERLAY_YUVTORGB_COEF2 0x1F40 +#define DC_OVERLAY_YUVTORGB_COEF3 0x1F80 +#define DC_OVERLAY_YUVTORGB_COEF4 0x1FC0 +#define DC_OVERLAY_YUVTORGB_COEFD0 0x2000 +#define DC_OVERLAY_YUVTORGB_COEFD1 0x2040 +#define DC_OVERLAY_YUVTORGB_COEFD2 0x2080 +#define DC_OVERLAY_Y_CLAMP_BOUND 0x22C0 +#define DC_OVERLAY_UV_CLAMP_BOUND 0x2300 +#define DC_OVERLAY_RGBTORGB_COEF0 0x20C0 +#define DC_OVERLAY_RGBTORGB_COEF1 0x2100 +#define DC_OVERLAY_RGBTORGB_COEF2 0x2140 +#define DC_OVERLAY_RGBTORGB_COEF3 0x2180 +#define DC_OVERLAY_RGBTORGB_COEF4 0x21C0 + +#define DC_CURSOR_CONFIG 0x1468 +#define DC_CURSOR_ADDRESS 0x146C +#define DC_CURSOR_LOCATION 0x1470 +#define DC_CURSOR_BACKGROUND 0x1474 +#define DC_CURSOR_FOREGROUND 0x1478 +#define DC_CURSOR_CLK_GATING 0x1484 + +#define DC_DISPLAY_DITHER_CONFIG 0x1410 +#define DC_DISPLAY_PANEL_CONFIG 0x1418 +#define DC_DISPLAY_DITHER_TABLE_LOW 0x1420 +#define DC_DISPLAY_DITHER_TABLE_HIGH 0x1428 +#define DC_DISPLAY_H 0x1430 +#define DC_DISPLAY_H_SYNC 0x1438 +#define DC_DISPLAY_V 0x1440 +#define DC_DISPLAY_V_SYNC 0x1448 +#define DC_DISPLAY_CURRENT_LOCATION 0x1450 +#define DC_DISPLAY_GAMMA_INDEX 0x1458 +#define DC_DISPLAY_GAMMA_DATA 0x1460 +#define DC_DISPLAY_INT 0x147C +#define DC_DISPLAY_INT_ENABLE 0x1480 +#define DC_DISPLAY_DBI_CONFIG 0x1488 +#define DC_DISPLAY_GENERAL_CONFIG 0x14B0 +#define DC_DISPLAY_DPI_CONFIG 0x14B8 +#define DC_DISPLAY_DEBUG_COUNTER_SELECT 0x14D0 +#define DC_DISPLAY_DEBUG_COUNTER_VALUE 0x14D8 +#define DC_DISPLAY_DP_CONFIG 0x1CD0 +#define DC_DISPLAY_GAMMA_EX_INDEX 0x1CF0 +#define DC_DISPLAY_GAMMA_EX_DATA 0x1CF8 +#define DC_DISPLAY_GAMMA_EX_ONE_DATA 0x1D80 +#define DC_DISPLAY_RGBTOYUV_COEF0 0x1E48 +#define DC_DISPLAY_RGBTOYUV_COEF1 0x1E50 +#define DC_DISPLAY_RGBTOYUV_COEF2 0x1E58 +#define DC_DISPLAY_RGBTOYUV_COEF3 0x1E60 +#define DC_DISPLAY_RGBTOYUV_COEF4 0x1E68 +#define DC_DISPLAY_RGBTOYUV_COEFD0 0x1E70 +#define DC_DISPLAY_RGBTOYUV_COEFD1 0x1E78 +#define DC_DISPLAY_RGBTOYUV_COEFD2 0x1E80 + +#define DC_CLK_GATTING 0x1A28 + +#define DC_TRANSPARENCY_OPAQUE 0x00 +#define DC_TRANSPARENCY_KEY 0x02 +#define DC_DISPLAY_DITHERTABLE_LOW 0x7B48F3C0 +#define DC_DISPLAY_DITHERTABLE_HIGH 0x596AD1E2 + +#define GAMMA_SIZE 256 +#define GAMMA_EX_SIZE 300 +#define DEGAMMA_SIZE 260 + +#define RGB_TO_RGB_TABLE_SIZE 9 +#define YUV_TO_RGB_TABLE_SIZE 16 +#define RGB_TO_YUV_TABLE_SIZE 12 + +#ifdef CONFIG_ESWIN_MMU +#define DC_MMU_PREFETCH 0x1E98 + +#define MMU_REG_BASE 0x0180 + +#define MMU_REG_CONFIG 0x0184 +#define MMU_REG_CONTROL 0x0388 +#define MMU_REG_TABLE_ARRAY_SIZE 0x0394 +#define MMU_REG_SAFE_NON_SECURE 0x0398 +#define MMU_REG_SAFE_SECURE 0x039C +#define MMU_REG_SAFE_EXT_ADDRESS 0x03A0 +#define MMU_REG_CONTEXT_PD 0x03B4 + +#define DEC_REG_CONTROL 0x0800 +#define DEC_REG_CONTROL_VALUE 0x02010188 +#endif + +enum dc_hw_chip_rev { + DC_REV_5551, + DC_REV_5551_306, + DC_REV_5701_303, + DC_REV_5701_309, +}; + +enum dc_hw_plane_id { PRIMARY_PLANE, CURSOR_PLANE, OVERLAY_PLANE, PLANE_NUM }; + +enum dc_hw_color_format { + FORMAT_X4R4G4B4, + FORMAT_A4R4G4B4, + FORMAT_X1R5G5B5, + FORMAT_A1R5G5B5, + FORMAT_R5G6B5, + FORMAT_X8R8G8B8, + FORMAT_A8R8G8B8, + FORMAT_YUY2, + FORMAT_UYVY, + FORMAT_INDEX8, + FORMAT_MONOCHROME, + FORMAT_YV12 = 0xf, + FORMAT_A8, + FORMAT_NV12, + FORMAT_NV16, + FORMAT_RG16, + FORMAT_R8, + FORMAT_NV12_10BIT, + FORMAT_A2R10G10B10, + FORMAT_NV16_10BIT, + FORMAT_INDEX1, + FORMAT_INDEX2, + FORMAT_INDEX4, + FORMAT_P010, +}; + +enum dc_hw_yuv_color_space { + COLOR_SPACE_601 = 0, + COLOR_SPACE_709 = 1, + COLOR_SPACE_2020 = 3, +}; + +enum dc_hw_rotation { + ROT_0 = 0, + ROT_90 = 4, + ROT_180 = 5, + ROT_270 = 6, + FLIP_X = 1, + FLIP_Y = 2, + FLIP_XY = 3, +}; + +enum dc_hw_swizzle { + SWIZZLE_ARGB = 0, + SWIZZLE_RGBA, + SWIZZLE_ABGR, + SWIZZLE_BGRA, +}; + +enum dc_hw_out { + OUT_DPI, + OUT_DP, +}; + +enum dc_hw_blend_mode { + /* out.rgb = plane_alpha * fg.rgb + + * (1 - (plane_alpha * fg.alpha)) * bg.rgb + */ + BLEND_PREMULTI, + /* out.rgb = plane_alpha * fg.alpha * fg.rgb + + * (1 - (plane_alpha * fg.alpha)) * bg.rgb + */ + BLEND_COVERAGE, + /* out.rgb = plane_alpha * fg.rgb + + * (1 - plane_alpha) * bg.rgb + */ + BLEND_PIXEL_NONE, +}; + +struct dc_hw_plane_reg { + u32 y_address; + u32 u_address; + u32 v_address; + u32 y_stride; + u32 u_stride; + u32 v_stride; + u32 size; + u32 scale_factor_x; + u32 scale_factor_y; + u32 h_filter_coef_index; + u32 h_filter_coef_data; + u32 v_filter_coef_index; + u32 v_filter_coef_data; + u32 init_offset; + u32 color_key; + u32 color_key_high; + u32 clear_value; + u32 color_table_index; + u32 color_table_data; + u32 scale_config; + u32 degamma_index; + u32 degamma_data; + u32 degamma_ex_data; + u32 roi_origin; + u32 roi_size; + u32 YUVToRGBCoef0; + u32 YUVToRGBCoef1; + u32 YUVToRGBCoef2; + u32 YUVToRGBCoef3; + u32 YUVToRGBCoef4; + u32 YUVToRGBCoefD0; + u32 YUVToRGBCoefD1; + u32 YUVToRGBCoefD2; + u32 YClampBound; + u32 UVClampBound; + u32 RGBToRGBCoef0; + u32 RGBToRGBCoef1; + u32 RGBToRGBCoef2; + u32 RGBToRGBCoef3; + u32 RGBToRGBCoef4; +}; + +struct dc_hw_fb { + u32 y_address; + u32 u_address; + u32 v_address; + u32 clear_value; + u16 y_stride; + u16 u_stride; + u16 v_stride; + u16 width; + u16 height; + u8 format; + u8 tile_mode; + u8 rotation; + u8 yuv_color_space; + u8 swizzle; + u8 uv_swizzle; + bool clear_enable; + bool enable; + bool dirty; +}; + +struct dc_hw_scale { + u32 scale_factor_x; + u32 scale_factor_y; + bool enable; + bool dirty; +}; + +struct dc_hw_position { + u16 start_x; + u16 start_y; + u16 end_x; + u16 end_y; + bool dirty; +}; + +struct dc_hw_blend { + u8 alpha; + u8 blend_mode; + bool dirty; +}; + +struct dc_hw_colorkey { + u32 colorkey; + u32 colorkey_high; + u8 transparency; + bool dirty; +}; + +struct dc_hw_roi { + u16 x; + u16 y; + u16 width; + u16 height; + bool enable; + bool dirty; +}; + +struct dc_hw_cursor { + u32 address; + u16 x; + u16 y; + u16 hot_x; + u16 hot_y; + bool enable; + bool dirty; +}; + +struct dc_hw_display { + u32 bus_format; + u16 h_active; + u16 h_total; + u16 h_sync_start; + u16 h_sync_end; + u16 v_active; + u16 v_total; + u16 v_sync_start; + u16 v_sync_end; + u16 sync_mode; + u32 bg_color; + bool h_sync_polarity; + bool v_sync_polarity; + bool enable; + bool dither_enable; +}; + +struct dc_hw_gamma { + u16 gamma[GAMMA_EX_SIZE][3]; + bool enable; + bool dirty; +}; + +struct dc_hw_degamma { + u16 degamma[DEGAMMA_SIZE][3]; + u32 mode; + bool dirty; +}; + +struct dc_hw_plane { + struct dc_hw_fb fb; + struct dc_hw_scale scale; + struct dc_hw_roi roi; + struct dc_hw_colorkey colorkey; + struct dc_hw_degamma degamma; +}; + +struct dc_hw_overlay { + struct dc_hw_plane plane; + struct dc_hw_position pos; + struct dc_hw_blend blend; +}; + +struct dc_hw_read { + u32 reg; + u32 value; +}; + +struct dc_hw; +struct dc_hw_funcs { + void (*gamma)(struct dc_hw *hw); + void (*plane)(struct dc_hw *hw); + void (*display)(struct dc_hw *hw, struct dc_hw_display *display); +}; + +struct dc_hw { + enum dc_hw_chip_rev rev; + enum dc_hw_out out; + void *hi_base; + void *reg_base; +#ifdef CONFIG_ESWIN_MMU + void *mmu_base; +#endif + struct dc_hw_read *read_block; + struct dc_hw_display display; + struct dc_hw_gamma gamma; + struct dc_hw_plane primary; + struct dc_hw_overlay overlay; + struct dc_hw_cursor cursor; + struct dc_hw_funcs *func; + struct es_dc_info *info; +}; + +int dc_hw_init(struct dc_hw *hw); +void dc_hw_deinit(struct dc_hw *hw); +void dc_hw_update_plane(struct dc_hw *hw, enum dc_hw_plane_id id, + struct dc_hw_fb *fb, struct dc_hw_scale *scale); +void dc_hw_update_degamma(struct dc_hw *hw, enum dc_hw_plane_id id, u32 mode); +void dc_hw_set_position(struct dc_hw *hw, struct dc_hw_position *pos); +void dc_hw_set_blend(struct dc_hw *hw, struct dc_hw_blend *blend); +void dc_hw_update_cursor(struct dc_hw *hw, struct dc_hw_cursor *cursor); +void dc_hw_update_gamma(struct dc_hw *hw, u16 index, u16 r, u16 g, u16 b); +void dc_hw_enable_gamma(struct dc_hw *hw, bool enable); +void dc_hw_enable_dump(struct dc_hw *hw, u32 addr, u32 pitch); +void dc_hw_disable_dump(struct dc_hw *hw); +void dc_hw_setup_display(struct dc_hw *hw, struct dc_hw_display *display); +void dc_hw_enable_interrupt(struct dc_hw *hw, bool enable); +u32 dc_hw_get_interrupt(struct dc_hw *hw); +bool dc_hw_flip_in_progress(struct dc_hw *hw); +bool dc_hw_check_underflow(struct dc_hw *hw); +void dc_hw_enable_shadow_register(struct dc_hw *hw, bool enable); +void dc_hw_set_out(struct dc_hw *hw, enum dc_hw_out out); +void dc_hw_commit(struct dc_hw *hw); +#ifdef CONFIG_ESWIN_MMU +int dc_hw_mmu_init(struct dc_hw *hw, dc_mmu_pt mmu); +void dc_hw_enable_mmu_prefetch(struct dc_hw *hw, bool enable); +void dc_mmu_flush(struct dc_hw *hw); +#endif +void dc_hw_update_roi(struct dc_hw *hw, enum dc_hw_plane_id id, + struct dc_hw_roi *roi); +void dc_hw_update_colorkey(struct dc_hw *hw, enum dc_hw_plane_id id, + struct dc_hw_colorkey *colorkey); + +#endif /* __ES_DC_HW_H__ */ diff --git a/drivers/gpu/drm/eswin/es_dc_mmu.c b/drivers/gpu/drm/eswin/es_dc_mmu.c new file mode 100644 index 000000000000..80cbeb421740 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_dc_mmu.c @@ -0,0 +1,778 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "es_dc_mmu.h" + +static bool mmu_construct = false; + +int _allocate_memory(u32 bytes, void **memory) +{ + void *mem = NULL; + + if (bytes == 0 || memory == NULL) { + pr_err("%s has invalid arguments.\n", __FUNCTION__); + return -EINVAL; + } + + if (bytes > PAGE_SIZE) { + mem = vmalloc(bytes); + } else { + mem = kmalloc(bytes, GFP_KERNEL); + } + + if (!mem) { + pr_err("%s out of memory.\n", __FUNCTION__); + return -ENOMEM; + } + + memset((u8 *)mem, 0, bytes); + *memory = mem; + + return 0; +} + +static int _create_mutex(void **mutex) +{ + int ret = 0; + + if (mutex == NULL) { + return -EINVAL; + } + + ret = _allocate_memory(sizeof(struct mutex), mutex); + if (ret) + return ret; + + mutex_init(*(struct mutex **)mutex); + + return 0; +} + +static int _acquire_mutex(void *mutex, u32 timeout) +{ + if (mutex == NULL) { + pr_err("%s has invalid argument.\n", __FUNCTION__); + return -EINVAL; + } + + if (timeout == DC_INFINITE) { + mutex_lock(mutex); + return 0; + } + + for (;;) { + /* Try to acquire the mutex. */ + if (mutex_trylock(mutex)) { + /* Success. */ + return 0; + } + + if (timeout-- == 0) { + break; + } + + /* Wait for 1 millisecond. */ + udelay(1000); + } + + return -ETIMEDOUT; +} + +static int _release_mutex(void *mutex) +{ + if (mutex == NULL) { + pr_err("%s has invalid argument.\n", __FUNCTION__); + return -EINVAL; + } + + mutex_unlock(mutex); + + return 0; +} + +static u32 _mtlb_offset(u32 address) +{ + return (address & MMU_MTLB_MASK) >> MMU_MTLB_SHIFT; +} + +static u32 _stlb_offset(u32 address) +{ + return (address & MMU_STLB_4K_MASK) >> MMU_STLB_4K_SHIFT; +} + +static u32 _address_to_index(dc_mmu_pt mmu, u32 address) +{ + return _mtlb_offset(address) * MMU_STLB_4K_ENTRY_NUM + + _stlb_offset(address); +} + +static u32 _set_page(u32 page_address, u32 page_address_ext, bool writable) +{ + u32 entry = page_address + /* AddressExt */ + | (page_address_ext << 4) + /* Ignore exception */ + | (0 << 1) + /* Present */ + | (1 << 0); + + if (writable) { + /* writable */ + entry |= (1 << 2); + } + + return entry; +} + +static void _write_page_entry(u32 *page_entry, u32 entry_value) +{ + *page_entry = entry_value; +} + +static u32 _read_page_entry(u32 *page_entry) +{ + return *page_entry; +} + +int _allocate_stlb(dc_mmu_stlb_pt *stlb) +{ + dc_mmu_stlb_pt stlb_t = NULL; + void *mem = NULL; + + mem = kzalloc(sizeof(dc_mmu_stlb), GFP_KERNEL); + if (!mem) + return -ENOMEM; + + stlb_t = (dc_mmu_stlb_pt)mem; + + stlb_t->size = MMU_STLB_4K_SIZE; + + *stlb = stlb_t; + + return 0; +} + +int _allocate_all_stlb(struct device *dev, dc_mmu_stlb_pt *stlb) +{ + dc_mmu_stlb_pt stlb_t = NULL; + void *mem = NULL; + void *cookie = NULL; + dma_addr_t dma_addr; + size_t size; + + mem = kzalloc(sizeof(dc_mmu_stlb), GFP_KERNEL); + if (!mem) + return -ENOMEM; + + stlb_t = (dc_mmu_stlb_pt)mem; + + stlb_t->size = MMU_STLB_4K_SIZE * MMU_MTLB_ENTRY_NUM; + size = PAGE_ALIGN(stlb_t->size); + + cookie = dma_alloc_wc(dev, size, &dma_addr, GFP_KERNEL); + if (!cookie) { + dev_err(dev, "Failed to alloc stlb buffer.\n"); + return -ENOMEM; + } + + stlb_t->logical = cookie; + stlb_t->physBase = (u64)dma_addr; + memset(stlb_t->logical, 0, size); + + *stlb = stlb_t; + + return 0; +} + +int _setup_process_address_space(struct device *dev, dc_mmu_pt mmu) +{ + u32 *map = NULL; + u32 free, i; + u32 dynamic_mapping_entries, address; + dc_mmu_stlb_pt all_stlb; + int ret = 0; + + dynamic_mapping_entries = MMU_MTLB_ENTRY_NUM; + mmu->dynamic_mapping_start = 0; + mmu->page_table_size = dynamic_mapping_entries * MMU_STLB_4K_SIZE; + + mmu->page_table_entries = mmu->page_table_size / sizeof(u32); + + ret = _allocate_memory(mmu->page_table_size, + (void **)&mmu->map_logical); + if (ret) { + pr_err("Failed to alloc mmu map buffer.\n"); + return ret; + ; + } + + ret = _allocate_memory(mmu->page_table_size, + (void **)&mmu->page_entry_info); + if (ret) { + pr_err("Failed to alloc entry info buffer.\n"); + return ret; + ; + } + + map = mmu->map_logical; + + /* Initialize free area */ + free = mmu->page_table_entries; + _write_page_entry(map, (free << 8) | DC_MMU_FREE); + _write_page_entry(map + 1, ~0U); + + mmu->heap_list = 0; + mmu->free_nodes = false; + + ret = _allocate_all_stlb(dev, &all_stlb); + if (ret) + return ret; + + BUG_ON((all_stlb->physBase & (0xffUL << 32)) != + (mmu->mtlb_physical & (0xffUL << 32))); + + mmu->stlb_physical = all_stlb->physBase; + mmu->stlb_bytes = all_stlb->size; + + for (i = 0; i < dynamic_mapping_entries; i++) { + dc_mmu_stlb_pt stlb; + dc_mmu_stlb_pt *stlbs = (dc_mmu_stlb_pt *)mmu->stlbs; + + ret = _allocate_stlb(&stlb); + if (ret) + return ret; + + stlb->physBase = all_stlb->physBase + i * MMU_STLB_4K_SIZE; + stlb->logical = + all_stlb->logical + i * MMU_STLB_4K_SIZE / sizeof(u32); + + stlbs[i] = stlb; + } + + address = (u32)all_stlb->physBase; + + ret = _acquire_mutex(mmu->page_table_mutex, DC_INFINITE); + if (ret) + return ret; + + for (i = mmu->dynamic_mapping_start; + i < mmu->dynamic_mapping_start + dynamic_mapping_entries; i++) { + u32 mtlb_entry; + + mtlb_entry = address | MMU_MTLB_4K_PAGE | MMU_MTLB_PRESENT; + + address += MMU_STLB_4K_SIZE; + + /* Insert Slave TLB address to Master TLB entry.*/ + _write_page_entry(mmu->mtlb_logical + i, mtlb_entry); + } + + kfree(all_stlb); + _release_mutex(mmu->page_table_mutex); + + return 0; +} + +/* MMU Construct */ +int dc_mmu_construct(struct device *dev, dc_mmu_pt *mmu) +{ + dc_mmu_pt mmu_t = NULL; + void *mem = NULL; + void *cookie = NULL, *cookie_safe = NULL; + dma_addr_t dma_addr, dma_addr_safe; + u32 size = 0; + int ret = 0; + + if (mmu_construct) + return 0; + + mem = kzalloc(sizeof(dc_mmu), GFP_KERNEL); + if (!mem) + return -ENOMEM; + + mmu_t = (dc_mmu_pt)mem; + mmu_t->mtlb_bytes = MMU_MTLB_SIZE; + size = PAGE_ALIGN(mmu_t->mtlb_bytes); + if (size == PAGE_SIZE) { + size = PAGE_SIZE * 2; + } + + /* Allocate MTLB */ + cookie = dma_alloc_wc(dev, size, &dma_addr, GFP_KERNEL); + if (!cookie) { + dev_err(dev, "Failed to alloc mtlb buffer.\n"); + return -ENOMEM; + } + + mmu_t->mtlb_logical = cookie; + mmu_t->mtlb_physical = (u64)dma_addr; + memset(mmu_t->mtlb_logical, 0, size); + + size = MMU_MTLB_ENTRY_NUM * sizeof(dc_mmu_stlb_pt); + + ret = _allocate_memory(size, &mmu_t->stlbs); + if (ret) + return ret; + + ret = _create_mutex(&mmu_t->page_table_mutex); + if (ret) + return ret; + + mmu_t->mode = MMU_MODE_1K; + + ret = _setup_process_address_space(dev, mmu_t); + if (ret) + return ret; + + /* Allocate safe page */ + cookie_safe = dma_alloc_wc(dev, 4096, &dma_addr_safe, GFP_KERNEL); + if (!cookie_safe) { + dev_err(dev, "Failed to alloc safe page.\n"); + return -ENOMEM; + } + + mmu_t->safe_page_logical = cookie_safe; + mmu_t->safe_page_physical = (u64)dma_addr_safe; + memset(mmu_t->safe_page_logical, 0, size); + + *mmu = mmu_t; + mmu_construct = true; + + return 0; +} + +int dc_mmu_get_page_entry(dc_mmu_pt mmu, u32 address, u32 **page_table) +{ + dc_mmu_stlb_pt stlb; + dc_mmu_stlb_pt *stlbs = (dc_mmu_stlb_pt *)mmu->stlbs; + u32 mtlb_offset = _mtlb_offset(address); + u32 stlb_offset = _stlb_offset(address); + + stlb = stlbs[mtlb_offset - mmu->dynamic_mapping_start]; + if (stlb == NULL) { + pr_err("BUG: invalid stlb, mmu=%p stlbs=%p mtlb_offset=0x%x %s(%d)\n", + mmu, stlbs, mtlb_offset, __FUNCTION__, __LINE__); + return -ENXIO; + } + + *page_table = &stlb->logical[stlb_offset]; + + return 0; +} + +int _link(dc_mmu_pt mmu, u32 index, u32 node) +{ + if (index >= mmu->page_table_entries) { + mmu->heap_list = node; + } else { + u32 *map = mmu->map_logical; + + switch (DC_ENTRY_TYPE(_read_page_entry(&map[index]))) { + case DC_MMU_SINGLE: + /* Previous is a single node, link to it*/ + _write_page_entry(&map[index], + (node << 8) | DC_MMU_SINGLE); + break; + case DC_MMU_FREE: + /* Link to FREE TYPE node */ + _write_page_entry(&map[index + 1], node); + break; + default: + pr_err("MMU table corrupted at index %u!", index); + return -EINVAL; + } + } + + return 0; +} + +int _add_free(dc_mmu_pt mmu, u32 index, u32 node, u32 count) +{ + u32 *map = mmu->map_logical; + + if (count == 1) { + /* Initialize a single page node */ + _write_page_entry(map + node, DC_SINGLE_PAGE_NODE_INITIALIZE | + DC_MMU_SINGLE); + } else { + /* Initialize the FREE node*/ + _write_page_entry(map + node, (count << 8) | DC_MMU_FREE); + _write_page_entry(map + node + 1, ~0U); + } + + return _link(mmu, index, node); +} + +/* Collect free nodes */ +int _collect(dc_mmu_pt mmu) +{ + u32 *map = mmu->map_logical; + u32 count = 0, start = 0, i = 0; + u32 previous = ~0U; + int ret = 0; + + mmu->heap_list = ~0U; + mmu->free_nodes = false; + + /* Walk the entire page table */ + for (i = 0; i < mmu->page_table_entries; i++) { + switch (DC_ENTRY_TYPE(_read_page_entry(&map[i]))) { + case DC_MMU_SINGLE: + if (count++ == 0) { + /* Set new start node */ + start = i; + } + break; + case DC_MMU_FREE: + if (count == 0) { + /* Set new start node */ + start = i; + } + + count += _read_page_entry(&map[i]) >> 8; + /* Advance the index of the page table */ + i += (_read_page_entry(&map[i]) >> 8) - 1; + break; + case DC_MMU_USED: + /* Meet used node, start to collect */ + if (count > 0) { + /* Add free node to list*/ + ret = _add_free(mmu, previous, start, count); + if (ret) + return ret; + /* Reset previous unused node index */ + previous = start; + count = 0; + } + break; + default: + pr_err("MMU page table corrupted at index %u!", i); + return -EINVAL; + } + } + + /* If left node is an open node. */ + if (count > 0) { + ret = _add_free(mmu, previous, start, count); + if (ret) + return ret; + } + + return 0; +} + +int _fill_page_table(u32 *page_table, u32 page_count, u32 entry_value) +{ + u32 i; + + for (i = 0; i < page_count; i++) { + _write_page_entry(page_table + i, entry_value); + } + + return 0; +} + +int dc_mmu_allocate_pages(dc_mmu_pt mmu, u32 page_count, u32 *address) +{ + bool got = false, acquired = false; + u32 *map; + u32 index = 0, vaddr, left; + u32 previous = ~0U; + u32 mtlb_offset, stlb_offset; + int ret = 0; + + if (page_count == 0 || page_count > mmu->page_table_entries) { + pr_err("%s has invalid arguments.\n", __FUNCTION__); + return -EINVAL; + } + + _acquire_mutex(mmu->page_table_mutex, DC_INFINITE); + acquired = true; + + for (map = mmu->map_logical; !got;) { + for (index = mmu->heap_list; + !got && (index < mmu->page_table_entries);) { + switch (DC_ENTRY_TYPE(_read_page_entry(&map[index]))) { + case DC_MMU_SINGLE: + if ((index != 0) && (page_count == 1)) { + got = true; + } else { + /* Move to next node */ + previous = index; + index = _read_page_entry(&map[index]) >> + 8; + } + break; + case DC_MMU_FREE: + if ((index == 0) && + (page_count < + ((_read_page_entry(&map[index]) >> 8)))) { + got = true; + } else if ((index != 0) && + (page_count <= + ((_read_page_entry(&map[index]) >> + 8)))) { + got = true; + } else { + /* Move to next node */ + previous = index; + index = _read_page_entry( + &map[index + 1]); + } + break; + default: + /* Only link SINGLE and FREE node */ + pr_err("MMU table corrupted at index %u!", + index); + ret = -EINVAL; + goto OnError; + } + } + + /* If out of index */ + if (index >= mmu->page_table_entries) { + if (mmu->free_nodes) { + /* Collect the free node */ + ret = _collect(mmu); + if (ret) + goto OnError; + previous = ~0U; + } else { + ret = -ENODATA; + goto OnError; + } + } + } + + switch (DC_ENTRY_TYPE(_read_page_entry(&map[index]))) { + case DC_MMU_SINGLE: + /* Unlink single node from node list */ + ret = _link(mmu, previous, _read_page_entry(&map[index]) >> 8); + if (ret) + goto OnError; + break; + + case DC_MMU_FREE: + left = (_read_page_entry(&map[index]) >> 8) - page_count; + switch (left) { + case 0: + /* Unlink the entire FREE type node */ + ret = _link(mmu, previous, + _read_page_entry(&map[index + 1])); + if (ret) + goto OnError; + break; + case 1: + /* Keep the map[index] as a single node, + * mark the left as used + */ + _write_page_entry(&map[index], + (_read_page_entry(&map[index + 1]) + << 8) | DC_MMU_SINGLE); + index++; + break; + default: + /* FREE type node left */ + _write_page_entry(&map[index], + (left << 8) | DC_MMU_FREE); + index += left; + if (index + page_count > mmu->page_table_entries) { + pr_err("Error: index %d , left %d\n", index, + left); + ret = -EINVAL; + goto OnError; + } + + break; + } + break; + default: + /* Only link SINGLE and FREE node */ + pr_err("MMU table corrupted at index %u!", index); + ret = -EINVAL; + goto OnError; + } + + if (index + page_count > mmu->page_table_entries) { + pr_err("Fatal ERROR: This may caused by free pages more than allocated pages\n"); + _write_page_entry(mmu->map_logical, + (mmu->page_table_entries << 8) | DC_MMU_FREE); + _write_page_entry(mmu->map_logical + 1, ~0U); + previous = ~0U; + mmu->heap_list = 0; + memset(mmu->page_entry_info, 0, + mmu->page_table_entries * sizeof(u32)); + + ret = -EINVAL; + goto OnError; + } + + /* Mark node as used */ + ret = _fill_page_table(&map[index], page_count, DC_MMU_USED); + if (ret) + goto OnError; + + _release_mutex(mmu->page_table_mutex); + + mtlb_offset = + index / MMU_STLB_4K_ENTRY_NUM + mmu->dynamic_mapping_start; + stlb_offset = index % MMU_STLB_4K_ENTRY_NUM; + + vaddr = (mtlb_offset << MMU_MTLB_SHIFT) | + (stlb_offset << MMU_STLB_4K_SHIFT); + + if (vaddr == 0) { + pr_err("Error occured on alloc vaddr\n"); + ret = -ENODATA; + goto OnError; + } + + mmu->page_entry_info[index] = (index << 12) | (page_count & 0xfff); + + if (address != NULL) { + *address = vaddr; + } + + return 0; + +OnError: + if (acquired) { + _release_mutex(mmu->page_table_mutex); + } + + return ret; +} + +int dc_mmu_free_pages(dc_mmu_pt mmu, u32 address, u32 page_count) +{ + u32 *node, index; + + if (page_count == 0) + return -EINVAL; + + index = _address_to_index(mmu, address); + node = mmu->map_logical + _address_to_index(mmu, address); + + _acquire_mutex(mmu->page_table_mutex, DC_INFINITE); + + if (((mmu->page_entry_info[index] & 0xfff) != (page_count & 0xfff)) || + ((mmu->page_entry_info[index] >> 12) != index)) { + pr_err("index %d, page_count %d: free pages not equal to alloc\n", + index, page_count); + _release_mutex(mmu->page_table_mutex); + return -EINVAL; + } else { + mmu->page_entry_info[index] = 0; + } + + if (page_count == 1) { + /* Mark the Single page node free */ + _write_page_entry(node, DC_SINGLE_PAGE_NODE_INITIALIZE | + DC_MMU_SINGLE); + } else { + /* Mark the FREE type node free */ + _write_page_entry(node, (page_count << 8) | DC_MMU_FREE); + _write_page_entry(node + 1, ~0U); + } + + mmu->free_nodes = true; + + _release_mutex(mmu->page_table_mutex); + + return 0; +} + +int dc_mmu_set_page(dc_mmu_pt mmu, u64 page_address, u32 *page_entry) +{ + u32 address_ext; + u32 address; + + if (page_entry == NULL || (page_address & 0xFFF)) { + return -EINVAL; + } + + /* [31:0]. */ + address = (u32)(page_address & 0xFFFFFFFF); + /* [39:32]. */ + address_ext = (u32)((page_address >> 32) & 0xFF); + + _write_page_entry(page_entry, _set_page(address, address_ext, true)); + + return 0; +} + +int dc_mmu_map_memory(dc_mmu_pt mmu, u64 physical, u32 page_count, u32 *address, + bool continuous) +{ + u32 virutal_address, i = 0; + bool allocated = false; + int ret = 0; + + ret = dc_mmu_allocate_pages(mmu, page_count, &virutal_address); + if (ret) + goto OnError; + + if (continuous) + *address = virutal_address | (physical & MMU_PAGE_4K_MASK); + else + *address = virutal_address; + + allocated = true; + + /* Fill in page table */ + for (i = 0; i < page_count; i++) { + u64 page_phy; + u32 *page_entry; + struct page **pages; + + if (continuous == true) { + page_phy = physical + i * MMU_PAGE_4K_SIZE; + } else { + pages = (struct page **)physical; + page_phy = page_to_phys(pages[i]); + } + + ret = dc_mmu_get_page_entry(mmu, virutal_address, &page_entry); + if (ret) + goto OnError; + + /* Write the page address to the page entry */ + ret = dc_mmu_set_page(mmu, page_phy, page_entry); + if (ret) + goto OnError; + + /* Get next page */ + virutal_address += MMU_PAGE_4K_SIZE; + } + + wmb(); + + return 0; + +OnError: + if (allocated) + dc_mmu_free_pages(mmu, virutal_address, page_count); + pr_info("%s fail!\n", __FUNCTION__); + + return ret; +} + +int dc_mmu_unmap_memory(dc_mmu_pt mmu, u32 gpu_address, u32 page_count) +{ + return dc_mmu_free_pages(mmu, gpu_address, page_count); +} diff --git a/drivers/gpu/drm/eswin/es_dc_mmu.h b/drivers/gpu/drm/eswin/es_dc_mmu.h new file mode 100644 index 000000000000..ee049e3f40ec --- /dev/null +++ b/drivers/gpu/drm/eswin/es_dc_mmu.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#ifndef _ES_DC_MMU_H_ +#define _ES_DC_MMU_H_ + +#include "es_type.h" + +#define DC_INFINITE ((u32)(~0U)) + +#define DC_ENTRY_TYPE(x) (x & 0xF0) +#define DC_SINGLE_PAGE_NODE_INITIALIZE (~((1U << 8) - 1)) + +#define DC_INVALID_PHYSICAL_ADDRESS ~0ULL +#define DC_INVALID_ADDRESS ~0U + +/* 1k mode */ +#define MMU_MTLB_SHIFT 24 +#define MMU_STLB_4K_SHIFT 12 + +#define MMU_MTLB_BITS (32 - MMU_MTLB_SHIFT) +#define MMU_PAGE_4K_BITS MMU_STLB_4K_SHIFT +#define MMU_STLB_4K_BITS (32 - MMU_MTLB_BITS - MMU_PAGE_4K_BITS) + +#define MMU_MTLB_ENTRY_NUM (1 << MMU_MTLB_BITS) +#define MMU_MTLB_SIZE (MMU_MTLB_ENTRY_NUM << 2) +#define MMU_STLB_4K_ENTRY_NUM (1 << MMU_STLB_4K_BITS) +#define MMU_STLB_4K_SIZE (MMU_STLB_4K_ENTRY_NUM << 2) +#define MMU_PAGE_4K_SIZE (1 << MMU_STLB_4K_SHIFT) + +#define MMU_MTLB_MASK (~((1U << MMU_MTLB_SHIFT) - 1)) +#define MMU_STLB_4K_MASK ((~0U << MMU_STLB_4K_SHIFT) ^ MMU_MTLB_MASK) +#define MMU_PAGE_4K_MASK (MMU_PAGE_4K_SIZE - 1) + +/* page offset definitions. */ +#define MMU_OFFSET_4K_BITS (32 - MMU_MTLB_BITS - MMU_STLB_4K_BITS) +#define MMU_OFFSET_4K_MASK ((1U << MMU_OFFSET_4K_BITS) - 1) + +#define MMU_MTLB_PRESENT 0x00000001 +#define MMU_MTLB_EXCEPTION 0x00000002 +#define MMU_MTLB_4K_PAGE 0x00000000 + +typedef enum _dc_mmu_type { + DC_MMU_USED = (0 << 4), + DC_MMU_SINGLE = (1 << 4), + DC_MMU_FREE = (2 << 4), +} dc_mmu_type; + +typedef enum _dc_mmu_mode { + MMU_MODE_1K, + MMU_MODE_4K, +} dc_mmu_mode; + +typedef struct _dc_mmu_stlb { + u32 *logical; + void *physical; + u32 size; + u64 physBase; + u32 pageCount; +} dc_mmu_stlb, *dc_mmu_stlb_pt; + +typedef struct _dc_mmu { + u32 mtlb_bytes; + u64 mtlb_physical; + u32 *mtlb_logical; + + void *safe_page_logical; + u64 safe_page_physical; + + u32 dynamic_mapping_start; + + void *stlbs; + + // u64 stlb_physicals[MMU_MTLB_ENTRY_NUM]; + u32 stlb_bytes; + u64 stlb_physical; + + u32 page_table_entries; + u32 page_table_size; + u32 heap_list; + + u32 *map_logical; + u32 *page_entry_info; + bool free_nodes; + + void *page_table_mutex; + + dc_mmu_mode mode; + + void *static_stlb; +} dc_mmu, *dc_mmu_pt; + +int dc_mmu_construct(struct device *dev, dc_mmu_pt *mmu); +int dc_mmu_map_memory(dc_mmu_pt mmu, u64 physical, u32 page_count, u32 *address, + bool continuous); +int dc_mmu_unmap_memory(dc_mmu_pt mmu, u32 gpu_address, u32 page_count); + +#endif /* _ES_DC_MMU_H_ */ diff --git a/drivers/gpu/drm/eswin/es_drm.h b/drivers/gpu/drm/eswin/es_drm.h new file mode 100644 index 000000000000..73817dcf024b --- /dev/null +++ b/drivers/gpu/drm/eswin/es_drm.h @@ -0,0 +1,23 @@ +#ifndef __ES_DRM_H__ +#define __ES_DRM_H__ + +#include + +enum drm_es_degamma_mode { + ES_DEGAMMA_DISABLE = 0, + ES_DEGAMMA_BT709 = 1, + ES_DEGAMMA_BT2020 = 2, +}; + +enum drm_es_sync_dc_mode { + ES_SINGLE_DC = 0, + ES_MULTI_DC_PRIMARY = 1, + ES_MULTI_DC_SECONDARY = 2, +}; + +enum drm_es_mmu_prefetch_mode { + ES_MMU_PREFETCH_DISABLE = 0, + ES_MMU_PREFETCH_ENABLE = 1, +}; + +#endif /* __ES_DRM_H__ */ \ No newline at end of file diff --git a/drivers/gpu/drm/eswin/es_drv.c b/drivers/gpu/drm/eswin/es_drv.c new file mode 100644 index 000000000000..1d15deda7c7b --- /dev/null +++ b/drivers/gpu/drm/eswin/es_drv.c @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "es_drv.h" +#include "es_fb.h" +#include "es_gem.h" +#include "es_plane.h" +#include "es_crtc.h" +#include "es_simple_enc.h" +#include "es_dc.h" +#include "es_virtual.h" +#ifdef CONFIG_ESWIN_DW_HDMI +#include "dw_hdmi.h" +#endif + +#define DRV_NAME "es_drm" +#define DRV_DESC "Eswin DRM driver" +#define DRV_DATE "20191101" +#define DRV_MAJOR 1 +#define DRV_MINOR 0 + +static bool has_iommu = true; +static struct platform_driver es_drm_platform_driver; + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, + .compat_ioctl = drm_compat_ioctl, + .poll = drm_poll, + .read = drm_read, + .mmap = es_gem_mmap, +}; + +#ifdef CONFIG_DEBUG_FS +static int es_debugfs_planes_show(struct seq_file *s, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *)s->private; + struct drm_device *dev = node->minor->dev; + struct drm_plane *plane = NULL; + + list_for_each_entry (plane, &dev->mode_config.plane_list, head) { + struct drm_plane_state *state = plane->state; + struct es_plane_state *plane_state = to_es_plane_state(state); + + seq_printf(s, "plane[%u]: %s\n", plane->base.id, plane->name); + seq_printf(s, "\tcrtc = %s\n", + state->crtc ? state->crtc->name : "(null)"); + seq_printf(s, "\tcrtc id = %u\n", + state->crtc ? state->crtc->base.id : 0); + seq_printf(s, "\tcrtc-pos = " DRM_RECT_FMT "\n", + DRM_RECT_ARG(&plane_state->status.dest)); + seq_printf(s, "\tsrc-pos = " DRM_RECT_FP_FMT "\n", + DRM_RECT_FP_ARG(&plane_state->status.src)); + seq_printf(s, "\tformat = %p4cc\n", + state->fb ? &state->fb->format->format : NULL); + seq_printf(s, "\trotation = 0x%x\n", state->rotation); + seq_printf(s, "\ttiling = %u\n", plane_state->status.tile_mode); + + seq_puts(s, "\n"); + } + + return 0; +} + +static struct drm_info_list es_debugfs_list[] = { + { "planes", es_debugfs_planes_show, 0, NULL }, +}; + +static void es_debugfs_init(struct drm_minor *minor) +{ + drm_debugfs_create_files(es_debugfs_list, ARRAY_SIZE(es_debugfs_list), + minor->debugfs_root, minor); +} +#endif + +static struct drm_driver es_drm_driver = { + .driver_features = + DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_GEM | DRIVER_SYNCOBJ, + .lastclose = drm_fb_helper_lastclose, + .gem_prime_import = es_gem_prime_import, + .gem_prime_import_sg_table = es_gem_prime_import_sg_table, + .dumb_create = es_gem_dumb_create, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = es_debugfs_init, +#endif + .fops = &fops, + .name = DRV_NAME, + .desc = DRV_DESC, + .date = DRV_DATE, + .major = DRV_MAJOR, + .minor = DRV_MINOR, +}; + +int es_drm_iommu_attach_device(struct drm_device *drm_dev, struct device *dev) +{ + struct es_drm_private *priv = drm_dev->dev_private; + int ret; + + if (!has_iommu) + return 0; + + if (!priv->domain) { + priv->domain = iommu_get_domain_for_dev(dev); + if (IS_ERR(priv->domain)) + return PTR_ERR(priv->domain); + priv->dma_dev = dev; + } + + ret = iommu_attach_device(priv->domain, dev); + if (ret) { + DRM_DEV_ERROR(dev, "Failed to attach iommu device\n"); + return ret; + } + + return 0; +} + +void es_drm_iommu_detach_device(struct drm_device *drm_dev, struct device *dev) +{ + struct es_drm_private *priv = drm_dev->dev_private; + + if (!has_iommu) + return; + + iommu_detach_device(priv->domain, dev); + + if (priv->dma_dev == dev) + priv->dma_dev = drm_dev->dev; +} + +void es_drm_update_pitch_alignment(struct drm_device *drm_dev, + unsigned int alignment) +{ + struct es_drm_private *priv = drm_dev->dev_private; + + if (alignment > priv->pitch_alignment) + priv->pitch_alignment = alignment; +} + +#ifdef CONFIG_ESWIN_DW_HDMI +static int es_drm_create_properties(struct drm_device *dev) +{ + struct drm_property *prop; + struct es_drm_private *private = dev->dev_private; + + prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC, "EOTF", 0, + 5); + if (!prop) + return -ENOMEM; + private->eotf_prop = prop; + + prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC, + "COLOR_SPACE", 0, 12); + if (!prop) + return -ENOMEM; + private->color_space_prop = prop; + + prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC, + "GLOBAL_ALPHA", 0, 255); + if (!prop) + return -ENOMEM; + private->global_alpha_prop = prop; + + prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC, + "BLEND_MODE", 0, 1); + if (!prop) + return -ENOMEM; + private->blend_mode_prop = prop; + + prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC, + "ALPHA_SCALE", 0, 1); + if (!prop) + return -ENOMEM; + private->alpha_scale_prop = prop; + + prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC, + "ASYNC_COMMIT", 0, 1); + if (!prop) + return -ENOMEM; + private->async_commit_prop = prop; + + prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC, "SHARE_ID", + 0, UINT_MAX); + if (!prop) + return -ENOMEM; + private->share_id_prop = prop; + + prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC, + "CONNECTOR_ID", 0, 0xf); + if (!prop) + return -ENOMEM; + private->connector_id_prop = prop; + + return drm_mode_create_tv_properties(dev, 0, NULL); +} +#endif + +static int es_drm_bind(struct device *dev) +{ + struct drm_device *drm_dev; + struct es_drm_private *priv; + int ret; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + static u64 dma_mask = DMA_BIT_MASK(40); +#else + static u64 dma_mask = DMA_40BIT_MASK; +#endif + + drm_dev = drm_dev_alloc(&es_drm_driver, dev); + if (IS_ERR(drm_dev)) + return PTR_ERR(drm_dev); + + dev_set_drvdata(dev, drm_dev); + + priv = devm_kzalloc(drm_dev->dev, sizeof(struct es_drm_private), + GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + goto err_put_dev; + } + + priv->pitch_alignment = 64; + priv->dma_dev = drm_dev->dev; + priv->dma_dev->coherent_dma_mask = dma_mask; + dma_set_mask_and_coherent(priv->dma_dev, DMA_BIT_MASK(40)); + + drm_dev->dev_private = priv; + + drm_mode_config_init(drm_dev); + +#ifdef CONFIG_ESWIN_DW_HDMI + es_drm_create_properties(drm_dev); +#endif + + /* Now try and bind all our sub-components */ + ret = component_bind_all(dev, drm_dev); + if (ret) + goto err_mode; + + es_mode_config_init(drm_dev); + + ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc); + if (ret) + goto err_bind; + + drm_mode_config_reset(drm_dev); + + drm_kms_helper_poll_init(drm_dev); + + ret = drm_dev_register(drm_dev, 0); + if (ret) + goto err_helper; + + return 0; + +err_helper: + drm_kms_helper_poll_fini(drm_dev); +err_bind: + component_unbind_all(drm_dev->dev, drm_dev); +err_mode: + drm_mode_config_cleanup(drm_dev); + if (priv->domain) + iommu_domain_free(priv->domain); +err_put_dev: + drm_dev->dev_private = NULL; + dev_set_drvdata(dev, NULL); + drm_dev_put(drm_dev); + return ret; +} + +static void es_drm_unbind(struct device *dev) +{ + struct drm_device *drm_dev = dev_get_drvdata(dev); + struct es_drm_private *priv = drm_dev->dev_private; + + drm_dev_unregister(drm_dev); + drm_kms_helper_poll_fini(drm_dev); + drm_atomic_helper_shutdown(drm_dev); + component_unbind_all(drm_dev->dev, drm_dev); + drm_mode_config_cleanup(drm_dev); + + if (priv->domain) { + iommu_domain_free(priv->domain); + priv->domain = NULL; + } + + drm_dev->dev_private = NULL; + dev_set_drvdata(dev, NULL); + drm_dev_put(drm_dev); +} + +static const struct component_master_ops es_drm_ops = { + .bind = es_drm_bind, + .unbind = es_drm_unbind, +}; + +static struct platform_driver *drm_sub_drivers[] = { + /* put display control driver at start */ + &dc_platform_driver, + +/* connector */ + +/* bridge */ +#if 1 +#ifdef CONFIG_ESWIN_DW_HDMI + &dw_hdmi_eswin_pltfm_driver, +#endif +#ifdef CONFIG_DW_HDMI_I2S_AUDIO + &snd_dw_hdmi_driver, +#endif + +#ifdef CONFIG_DW_HDMI_CEC + &dw_hdmi_cec_driver, +#endif + +#ifdef CONFIG_DW_HDMI_HDCP + &dw_hdmi_hdcp_driver, +#endif +#endif + +#ifdef CONFIG_ESWIN_VIRTUAL_DISPLAY + &virtual_display_platform_driver, +#endif +}; +#define NUM_DRM_DRIVERS \ + (sizeof(drm_sub_drivers) / sizeof(struct platform_driver *)) + +static int compare_dev(struct device *dev, void *data) +{ + return dev == (struct device *)data; +} + +static struct component_match *es_drm_match_add(struct device *dev) +{ + struct component_match *match = NULL; + int i; + + for (i = 0; i < NUM_DRM_DRIVERS; ++i) { + struct platform_driver *drv = drm_sub_drivers[i]; + struct device *p = NULL, *d; + + while ((d = platform_find_device_by_driver(p, &drv->driver))) { + put_device(p); + + component_match_add(dev, &match, compare_dev, d); + p = d; + } + put_device(p); + } + + return match ?: ERR_PTR(-ENODEV); +} + +static int es_drm_platform_of_probe(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct device_node *port; + bool found = false; + int i; + + if (!np) + return -ENODEV; + + for (i = 0;; i++) { + struct device_node *iommu; + + port = of_parse_phandle(np, "ports", i); + if (!port) + break; + + if (!of_device_is_available(port->parent)) { + of_node_put(port); + continue; + } + + iommu = of_parse_phandle(port->parent, "iommus", 0); + + /* + * if there is a crtc not support iommu, force set all + * crtc use non-iommu buffer. + */ + if (!iommu || !of_device_is_available(iommu->parent)) + has_iommu = false; + + found = true; + + of_node_put(iommu); + of_node_put(port); + } + + if (i == 0) { + DRM_DEV_ERROR(dev, "missing 'ports' property\n"); + return -ENODEV; + } + + if (!found) { + DRM_DEV_ERROR(dev, "No available DC found.\n"); + return -ENODEV; + } + + return 0; +} + +static int es_drm_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct component_match *match; + int ret; + + ret = es_drm_platform_of_probe(dev); + if (ret) + return ret; + + match = es_drm_match_add(dev); + if (IS_ERR(match)) + return PTR_ERR(match); + + return component_master_add_with_match(dev, &es_drm_ops, match); +} + +static int es_drm_platform_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &es_drm_ops); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int es_drm_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + return drm_mode_config_helper_suspend(drm); +} + +static int es_drm_resume(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + return drm_mode_config_helper_resume(drm); +} +#endif + +static SIMPLE_DEV_PM_OPS(es_drm_pm_ops, es_drm_suspend, es_drm_resume); + +static const struct of_device_id es_drm_dt_ids[] = { + + { + .compatible = "eswin,display-subsystem", + }, + + { /* sentinel */ }, + +}; + +MODULE_DEVICE_TABLE(of, es_drm_dt_ids); + +static struct platform_driver es_drm_platform_driver = { + .probe = es_drm_platform_probe, + .remove = es_drm_platform_remove, + + .driver = { + .name = DRV_NAME, + .of_match_table = es_drm_dt_ids, + .pm = &es_drm_pm_ops, + }, +}; + +static int __init es_drm_init(void) +{ + int ret; + + ret = platform_register_drivers(drm_sub_drivers, NUM_DRM_DRIVERS); + if (ret) + return ret; + + ret = platform_driver_register(&es_drm_platform_driver); + if (ret) + platform_unregister_drivers(drm_sub_drivers, NUM_DRM_DRIVERS); + + return ret; +} + +static void __exit es_drm_fini(void) +{ + platform_driver_unregister(&es_drm_platform_driver); + platform_unregister_drivers(drm_sub_drivers, NUM_DRM_DRIVERS); +} + +module_init(es_drm_init); +module_exit(es_drm_fini); + +MODULE_DESCRIPTION("Eswin DRM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/eswin/es_drv.h b/drivers/gpu/drm/eswin/es_drv.h new file mode 100644 index 000000000000..85465f559de0 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_drv.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#ifndef __ES_DRV_H__ +#define __ES_DRV_H__ + +#include +#include + +#include +#include + +#include "es_plane.h" +#ifdef CONFIG_ESWIN_MMU +#include "es_dc_mmu.h" +#endif + +/* + * + * @dma_dev: device for DMA API. + * - use the first attached device if support iommu + else use drm device (only contiguous buffer support) + * @domain: iommu domain for DRM. + * - all DC IOMMU share same domain to reduce mapping + * @pitch_alignment: buffer pitch alignment required by sub-devices. + * + */ +struct es_drm_private { + struct device *dma_dev; + struct iommu_domain *domain; +#ifdef CONFIG_ESWIN_DW_HDMI + struct drm_property *connector_id_prop; + struct drm_property *eotf_prop; + struct drm_property *color_space_prop; + struct drm_property *global_alpha_prop; + struct drm_property *blend_mode_prop; + struct drm_property *alpha_scale_prop; + struct drm_property *async_commit_prop; + struct drm_property *share_id_prop; +#endif + +#ifdef CONFIG_ESWIN_MMU + dc_mmu *mmu; +#endif + + unsigned int pitch_alignment; +}; + +int es_drm_iommu_attach_device(struct drm_device *drm_dev, struct device *dev); + +void es_drm_iommu_detach_device(struct drm_device *drm_dev, struct device *dev); + +void es_drm_update_pitch_alignment(struct drm_device *drm_dev, + unsigned int alignment); + +static inline struct device *to_dma_dev(struct drm_device *dev) +{ + struct es_drm_private *priv = dev->dev_private; + + return priv->dma_dev; +} + +static inline bool is_iommu_enabled(struct drm_device *dev) +{ + struct es_drm_private *priv = dev->dev_private; + + return priv->domain != NULL ? true : false; +} +#endif /* __ES_DRV_H__ */ diff --git a/drivers/gpu/drm/eswin/es_fb.c b/drivers/gpu/drm/eswin/es_fb.c new file mode 100644 index 000000000000..9254b784003d --- /dev/null +++ b/drivers/gpu/drm/eswin/es_fb.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "es_fb.h" +#include "es_gem.h" + +static struct drm_framebuffer_funcs es_fb_funcs = { + .create_handle = drm_gem_fb_create_handle, + .destroy = drm_gem_fb_destroy, + .dirty = drm_atomic_helper_dirtyfb, +}; + +static struct drm_framebuffer * +es_fb_alloc(struct drm_device *dev, const struct drm_mode_fb_cmd2 *mode_cmd, + struct es_gem_object **obj, unsigned int num_planes) +{ + struct drm_framebuffer *fb; + int ret, i; + + fb = kzalloc(sizeof(*fb), GFP_KERNEL); + if (!fb) + return ERR_PTR(-ENOMEM); + + drm_helper_mode_fill_fb_struct(dev, fb, mode_cmd); + + for (i = 0; i < num_planes; i++) + fb->obj[i] = &obj[i]->base; + + ret = drm_framebuffer_init(dev, fb, &es_fb_funcs); + if (ret) { + dev_err(dev->dev, "Failed to initialize framebuffer: %d\n", + ret); + kfree(fb); + return ERR_PTR(ret); + } + + return fb; +} + +static struct drm_framebuffer * +es_fb_create(struct drm_device *dev, struct drm_file *file_priv, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_framebuffer *fb; + const struct drm_format_info *info; + struct es_gem_object *objs[MAX_NUM_PLANES]; + struct drm_gem_object *obj; + unsigned int height, size; + unsigned char i, num_planes; + int ret = 0; + + info = drm_format_info(mode_cmd->pixel_format); + if (!info) + return ERR_PTR(-EINVAL); + + num_planes = info->num_planes; + if (num_planes > MAX_NUM_PLANES) + return ERR_PTR(-EINVAL); + + for (i = 0; i < num_planes; i++) { + obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[i]); + if (!obj) { + dev_err(dev->dev, "Failed to lookup GEM object.\n"); + ret = -ENXIO; + goto err; + } + + height = + drm_format_info_plane_height(info, mode_cmd->height, i); + + size = height * mode_cmd->pitches[i] + mode_cmd->offsets[i]; + + if (obj->size < size) { + drm_gem_object_put(obj); + ret = -EINVAL; + goto err; + } + + objs[i] = to_es_gem_object(obj); + } + + fb = es_fb_alloc(dev, mode_cmd, objs, i); + if (IS_ERR(fb)) { + ret = PTR_ERR(fb); + goto err; + } + + return fb; + +err: + for (; i > 0; i--) { + drm_gem_object_put(&objs[i - 1]->base); + } + + return ERR_PTR(ret); +} + +struct es_gem_object *es_fb_get_gem_obj(struct drm_framebuffer *fb, + unsigned char plane) +{ + if (plane > MAX_NUM_PLANES) + return NULL; + + return to_es_gem_object(fb->obj[plane]); +} + +static const struct drm_mode_config_funcs es_mode_config_funcs = { + .fb_create = es_fb_create, + .output_poll_changed = drm_fb_helper_output_poll_changed, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static struct drm_mode_config_helper_funcs es_mode_config_helpers = { + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, +}; + +void es_mode_config_init(struct drm_device *dev) +{ + if (dev->mode_config.max_width == 0 || + dev->mode_config.max_height == 0) { + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.max_width = 4096; + dev->mode_config.max_height = 4096; + } + dev->mode_config.funcs = &es_mode_config_funcs; + dev->mode_config.helper_private = &es_mode_config_helpers; +} diff --git a/drivers/gpu/drm/eswin/es_fb.h b/drivers/gpu/drm/eswin/es_fb.h new file mode 100644 index 000000000000..6cd8b31684aa --- /dev/null +++ b/drivers/gpu/drm/eswin/es_fb.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#ifndef __ES_FB_H__ +#define __ES_FB_H__ + +struct es_gem_object *es_fb_get_gem_obj(struct drm_framebuffer *fb, + unsigned char plane); + +void es_mode_config_init(struct drm_device *dev); +#endif /* __ES_FB_H__ */ diff --git a/drivers/gpu/drm/eswin/es_gem.c b/drivers/gpu/drm/eswin/es_gem.c new file mode 100644 index 000000000000..4898c86d5d87 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_gem.c @@ -0,0 +1,598 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#include +#include + +#include "es_drv.h" +#include "es_gem.h" + +MODULE_IMPORT_NS(DMA_BUF); + +static const struct drm_gem_object_funcs es_gem_default_funcs; + +static void nonseq_free(struct page **pages, unsigned int nr_page) +{ + u32 i; + + if (!pages) + return; + + for (i = 0; i < nr_page; i++) + __free_page(pages[i]); +} + +#ifdef CONFIG_ESWIN_MMU +static int get_pages(unsigned int nr_page, struct es_gem_object *es_obj) +{ + struct page *pages; + u32 i, num_page, page_count = 0; + int order = 0; + gfp_t gfp = GFP_KERNEL; + + if (!es_obj->pages) + return -EINVAL; + + gfp &= ~__GFP_HIGHMEM; + gfp |= __GFP_DMA32; + + num_page = nr_page; + + do { + pages = NULL; + order = get_order(num_page * PAGE_SIZE); + num_page = 1 << order; + + if ((num_page + page_count > nr_page) || (order >= MAX_ORDER)) { + num_page = num_page >> 1; + continue; + } + + pages = alloc_pages(gfp, order); + if (!pages) { + if (num_page == 1) { + nonseq_free(es_obj->pages, page_count); + return -ENOMEM; + } + + num_page = num_page >> 1; + } else { + for (i = 0; i < num_page; i++) { + es_obj->pages[page_count + i] = &pages[i]; + SetPageReserved(es_obj->pages[page_count + i]); + } + + page_count += num_page; + num_page = nr_page - page_count; + } + + } while (page_count < nr_page); + + es_obj->get_pages = true; + + return 0; +} +#endif + +static void put_pages(unsigned int nr_page, struct es_gem_object *es_obj) +{ + u32 i; + + for (i = 0; i < nr_page; i++) + ClearPageReserved(es_obj->pages[i]); + + nonseq_free(es_obj->pages, nr_page); + + return; +} + +static int es_gem_alloc_buf(struct es_gem_object *es_obj) +{ + struct drm_device *dev = es_obj->base.dev; + unsigned int nr_pages; + struct sg_table sgt; + int ret = -ENOMEM; +#ifdef CONFIG_ESWIN_MMU + struct es_drm_private *priv = dev->dev_private; +#endif + + if (es_obj->dma_addr) { + DRM_DEV_DEBUG_KMS(dev->dev, "already allocated.\n"); + return 0; + } + + es_obj->dma_attrs = DMA_ATTR_WRITE_COMBINE | DMA_ATTR_NO_KERNEL_MAPPING; + + if (!is_iommu_enabled(dev)) + es_obj->dma_attrs |= DMA_ATTR_FORCE_CONTIGUOUS; + + nr_pages = es_obj->size >> PAGE_SHIFT; + + es_obj->pages = kvmalloc_array(nr_pages, sizeof(struct page *), + GFP_KERNEL | __GFP_ZERO); + if (!es_obj->pages) { + DRM_DEV_ERROR(dev->dev, "failed to allocate pages.\n"); + return -ENOMEM; + } + + es_obj->cookie = dma_alloc_attrs(to_dma_dev(dev), es_obj->size, + &es_obj->dma_addr, GFP_KERNEL, + es_obj->dma_attrs); + if (!es_obj->cookie) { +#ifdef CONFIG_ESWIN_MMU + ret = get_pages(nr_pages, es_obj); + if (ret) { + DRM_DEV_ERROR(dev->dev, "fail to allocate buffer.\n"); + goto err_free; + } +#else + DRM_DEV_ERROR(dev->dev, "failed to allocate buffer.\n"); + goto err_free; +#endif + } + +#ifdef CONFIG_ESWIN_MMU + /* MMU map*/ + if (!priv->mmu) { + DRM_DEV_ERROR(dev->dev, "invalid mmu.\n"); + ret = -EINVAL; + goto err_mem_free; + } + + if (!es_obj->get_pages) + ret = dc_mmu_map_memory(priv->mmu, (u64)es_obj->dma_addr, + nr_pages, &es_obj->iova, true); + else + ret = dc_mmu_map_memory(priv->mmu, (u64)es_obj->pages, nr_pages, + &es_obj->iova, false); + + if (ret) { + DRM_DEV_ERROR(dev->dev, "failed to do mmu map.\n"); + goto err_mem_free; + } +#else + es_obj->iova = es_obj->dma_addr; +#endif + + if (!es_obj->get_pages) { + ret = dma_get_sgtable_attrs(to_dma_dev(dev), &sgt, + es_obj->cookie, es_obj->dma_addr, + es_obj->size, es_obj->dma_attrs); + if (ret < 0) { + DRM_DEV_ERROR(dev->dev, "failed to get sgtable.\n"); + goto err_mem_free; + } + + if (drm_prime_sg_to_page_array(&sgt, es_obj->pages, nr_pages)) { + DRM_DEV_ERROR(dev->dev, "invalid sgtable.\n"); + ret = -EINVAL; + goto err_sgt_free; + } + + sg_free_table(&sgt); + } + + return 0; + +err_sgt_free: + sg_free_table(&sgt); +err_mem_free: + if (!es_obj->get_pages) + dma_free_attrs(to_dma_dev(dev), es_obj->size, es_obj->cookie, + es_obj->dma_addr, es_obj->dma_attrs); + else + put_pages(nr_pages, es_obj); +err_free: + kvfree(es_obj->pages); + + return ret; +} + +static void es_gem_free_buf(struct es_gem_object *es_obj) +{ + struct drm_device *dev = es_obj->base.dev; +#ifdef CONFIG_ESWIN_MMU + struct es_drm_private *priv = dev->dev_private; + unsigned int nr_pages; +#endif + + if ((!es_obj->get_pages) && (!es_obj->dma_addr)) { + DRM_DEV_DEBUG_KMS(dev->dev, "dma_addr is invalid.\n"); + return; + } + +#ifdef CONFIG_ESWIN_MMU + if (!priv->mmu) { + DRM_DEV_ERROR(dev->dev, "invalid mmu.\n"); + return; + } + + if (!es_obj->sgt) { // dumb buffer release + nr_pages = es_obj->size >> PAGE_SHIFT; + if (es_obj->iova) { + dc_mmu_unmap_memory(priv->mmu, es_obj->iova, nr_pages); + } + } else { // prime buffer release + if (es_obj->iova_list) { + if (es_obj->iova_list->iova) { + dc_mmu_unmap_memory( + priv->mmu, es_obj->iova_list->iova, + es_obj->iova_list->nr_pages); + kfree(es_obj->iova_list); + } + } + } +#endif + + if (!es_obj->get_pages) { + dma_free_attrs(to_dma_dev(dev), es_obj->size, es_obj->cookie, + (dma_addr_t)es_obj->dma_addr, es_obj->dma_attrs); + } else { + if (!es_obj->dma_addr) { + DRM_DEV_ERROR(dev->dev, "No dma addr allocated, no need to free\n"); + return; + } + put_pages(es_obj->size >> PAGE_SHIFT, es_obj); + } + + kvfree(es_obj->pages); +} + +static void es_gem_free_object(struct drm_gem_object *obj) +{ + struct es_gem_object *es_obj = to_es_gem_object(obj); + +#ifdef CONFIG_ESWIN_MMU + if (es_obj) + es_gem_free_buf(es_obj); +#endif + if (obj->import_attach) { + drm_prime_gem_destroy(obj, es_obj->sgt); + } + + drm_gem_object_release(obj); + + kfree(es_obj); +} + +static struct es_gem_object *es_gem_alloc_object(struct drm_device *dev, + size_t size) +{ + struct es_gem_object *es_obj; + struct drm_gem_object *obj; + int ret; + + es_obj = kzalloc(sizeof(*es_obj), GFP_KERNEL); + if (!es_obj) + return ERR_PTR(-ENOMEM); + + es_obj->size = size; + obj = &es_obj->base; + + ret = drm_gem_object_init(dev, obj, size); + if (ret) + goto err_free; + + es_obj->base.funcs = &es_gem_default_funcs; + + ret = drm_gem_create_mmap_offset(obj); + if (ret) { + drm_gem_object_release(obj); + goto err_free; + } + + return es_obj; + +err_free: + kfree(es_obj); + return ERR_PTR(ret); +} + +struct es_gem_object *es_gem_create_object(struct drm_device *dev, size_t size) +{ + struct es_gem_object *es_obj; + int ret; + + size = PAGE_ALIGN(size); + + es_obj = es_gem_alloc_object(dev, size); + if (IS_ERR(es_obj)) + return es_obj; + + ret = es_gem_alloc_buf(es_obj); + if (ret) { + drm_gem_object_release(&es_obj->base); + kfree(es_obj); + return ERR_PTR(ret); + } + + return es_obj; +} + +static struct es_gem_object *es_gem_create_with_handle(struct drm_device *dev, + struct drm_file *file, + size_t size, + unsigned int *handle) +{ + struct es_gem_object *es_obj; + struct drm_gem_object *obj; + int ret; + + es_obj = es_gem_create_object(dev, size); + if (IS_ERR(es_obj)) + return es_obj; + + obj = &es_obj->base; + + ret = drm_gem_handle_create(file, obj, handle); + drm_gem_object_put(obj); + if (ret) { + pr_err("Drm GEM handle create failed\n"); + return ERR_PTR(ret); + } + + return es_obj; +} + +static int es_gem_mmap_obj(struct drm_gem_object *obj, + struct vm_area_struct *vma) +{ + struct es_gem_object *es_obj = to_es_gem_object(obj); + struct drm_device *drm_dev = es_obj->base.dev; + unsigned long vm_size; + int ret = 0; + + vm_size = vma->vm_end - vma->vm_start; + if (vm_size > es_obj->size) + return -EINVAL; + + vma->vm_pgoff = 0; + + if (!es_obj->get_pages) { + vm_flags_clear(vma, VM_PFNMAP); + + ret = dma_mmap_attrs(to_dma_dev(drm_dev), vma, es_obj->cookie, + es_obj->dma_addr, es_obj->size, + es_obj->dma_attrs); + } else { + u32 i, nr_pages, pfn = 0U; + unsigned long start; + + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + vm_flags_set(vma, VM_IO | VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP); + start = vma->vm_start; + vm_size = PAGE_ALIGN(vm_size); + nr_pages = vm_size >> PAGE_SHIFT; + + for (i = 0; i < nr_pages; i++) { + pfn = page_to_pfn(es_obj->pages[i]); + + ret = remap_pfn_range(vma, start, pfn, PAGE_SIZE, + vma->vm_page_prot); + if (ret < 0) + break; + + start += PAGE_SIZE; + } + } + + if (ret) + drm_gem_vm_close(vma); + + return ret; +} + +struct sg_table *es_gem_prime_get_sg_table(struct drm_gem_object *obj) +{ + struct es_gem_object *es_obj = to_es_gem_object(obj); + + return drm_prime_pages_to_sg(obj->dev, es_obj->pages, + es_obj->size >> PAGE_SHIFT); +} + +static int es_gem_prime_vmap(struct drm_gem_object *obj, + struct iosys_map *map) +{ + return 0; +} + +static void es_gem_prime_vunmap(struct drm_gem_object *obj, + struct iosys_map *map) +{ + /* Nothing to do */ +} + +static const struct vm_operations_struct es_vm_ops = { + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + +static const struct drm_gem_object_funcs es_gem_default_funcs = { + .free = es_gem_free_object, + .get_sg_table = es_gem_prime_get_sg_table, + .vmap = es_gem_prime_vmap, + .vunmap = es_gem_prime_vunmap, + .vm_ops = &es_vm_ops, +}; + +int es_gem_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + struct es_drm_private *priv = dev->dev_private; + struct es_gem_object *es_obj; + unsigned int pitch = args->width * DIV_ROUND_UP(args->bpp, 8); + + args->pitch = ALIGN(pitch, priv->pitch_alignment); + args->size = PAGE_ALIGN(args->pitch * args->height); + + es_obj = + es_gem_create_with_handle(dev, file, args->size, &args->handle); + return PTR_ERR_OR_ZERO(es_obj); +} + +struct drm_gem_object *es_gem_prime_import(struct drm_device *dev, + struct dma_buf *dma_buf) +{ + return drm_gem_prime_import_dev(dev, dma_buf, to_dma_dev(dev)); +} + +struct drm_gem_object * +es_gem_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, + struct sg_table *sgt) +{ + struct es_gem_object *es_obj; + int npages; + int ret; + struct scatterlist *s = NULL; + u32 i = 0; + dma_addr_t expected; + size_t size = attach->dmabuf->size; +#ifdef CONFIG_ESWIN_MMU + u32 iova, j; + struct scatterlist **splist; + struct es_drm_private *priv = dev->dev_private; + + if (!priv->mmu) { + DRM_ERROR("invalid mmu.\n"); + ret = -EINVAL; + return ERR_PTR(ret); + } +#endif + + size = PAGE_ALIGN(size); + + es_obj = es_gem_alloc_object(dev, size); + if (IS_ERR(es_obj)) + return ERR_CAST(es_obj); + + npages = es_obj->size >> PAGE_SHIFT; + es_obj->pages = + kvmalloc_array(npages, sizeof(struct page *), GFP_KERNEL); + if (!es_obj->pages) { + ret = -ENOMEM; + goto err_gemalloc; + } + + ret = drm_prime_sg_to_page_array(sgt, es_obj->pages, npages); + if (ret) + goto err_free_page; + + expected = sg_dma_address(sgt->sgl); +#ifdef CONFIG_ESWIN_MMU + splist = (struct scatterlist **)kzalloc(sizeof(s) * sgt->nents, + GFP_KERNEL); + if (!splist) { + DRM_ERROR("Allocate splist failed"); + ret = -ENOMEM; + goto err_free_page; + } + + es_obj->iova_list = + (iova_info_t *)kzalloc(sizeof(iova_info_t), GFP_KERNEL); + if (!es_obj->iova_list) { + DRM_ERROR("Allocate splist failed"); + ret = -ENOMEM; + goto err_sp; + } + + for_each_sg (sgt->sgl, s, sgt->nents, i) { + splist[i] = s; + } + i = 0; + es_obj->nr_iova = sgt->nents; + + for (j = sgt->nents; j > 0; j--) { + s = splist[j - 1]; +#else + for_each_sg (sgt->sgl, s, sgt->nents, i) { +#endif + if (sg_dma_address(s) != expected) { +#ifndef CONFIG_ESWIN_MMU + DRM_ERROR("sg_table is not contiguous"); + ret = -EINVAL; + goto err; +#endif + } + + if (sg_dma_len(s) & (PAGE_SIZE - 1)) { + ret = -EINVAL; + goto err; + } + +#ifdef CONFIG_ESWIN_MMU + iova = 0; + + if (j == 1) { + ret = dc_mmu_map_memory(priv->mmu, (u64)es_obj->pages, + npages, &iova, false); + if (ret) { + DRM_ERROR("failed to do mmu map.\n"); + goto err; + } + es_obj->iova_list->iova = iova; + es_obj->iova_list->nr_pages = npages; + } + + if (i == 0) + es_obj->iova = iova; +#else + if (i == 0) + es_obj->iova = sg_dma_address(s); +#endif + + expected = sg_dma_address(s) + sg_dma_len(s); + } + + es_obj->dma_addr = sg_dma_address(sgt->sgl); + + es_obj->sgt = sgt; +#ifdef CONFIG_ESWIN_MMU + kfree(splist); +#endif + + return &es_obj->base; + +#ifdef CONFIG_ESWIN_MMU +err: + kfree(es_obj->iova_list); +err_sp: + kfree(splist); +#endif +err_free_page: + kvfree(es_obj->pages); +err_gemalloc: + es_gem_free_object(&es_obj->base); + + return ERR_PTR(ret); +} + +int es_gem_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma) +{ + int ret = 0; + + ret = drm_gem_mmap_obj(obj, obj->size, vma); + if (ret < 0) + return ret; + + return es_gem_mmap_obj(obj, vma); +} + +int es_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct drm_gem_object *obj; + int ret; + + ret = drm_gem_mmap(filp, vma); + if (ret) + return ret; + + obj = vma->vm_private_data; + + if (obj->import_attach) + return dma_buf_mmap(obj->dma_buf, vma, 0); + + return es_gem_mmap_obj(obj, vma); +} diff --git a/drivers/gpu/drm/eswin/es_gem.h b/drivers/gpu/drm/eswin/es_gem.h new file mode 100644 index 000000000000..b14b32ea5cb4 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_gem.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#ifndef __ES_GEM_H__ +#define __ES_GEM_H__ + +#include + +#include + +#include "es_drv.h" + +#ifdef CONFIG_ESWIN_MMU +typedef struct _iova_info { + u32 iova; + u32 nr_pages; +} iova_info_t; +#endif + +/* + * + * @base: drm gem object. + * @size: size requested from user + * @cookie: cookie returned by dma_alloc_attrs + * - not kernel virtual address with DMA_ATTR_NO_KERNEL_MAPPING + * @dma_addr: bus address(accessed by dma) to allocated memory region. + * - this address could be physical address without IOMMU and + * device address with IOMMU. + * @dma_attrs: attribute for DMA API + * @get_pages: flag for manually applying for non-contiguous memory. + * @pages: Array of backing pages. + * @sgt: Imported sg_table. + * + */ +struct es_gem_object { + struct drm_gem_object base; + size_t size; + void *cookie; + dma_addr_t dma_addr; + u32 iova; + unsigned long dma_attrs; + bool get_pages; + struct page **pages; + struct sg_table *sgt; +#ifdef CONFIG_ESWIN_MMU + iova_info_t *iova_list; + u32 nr_iova; +#endif +}; + +static inline struct es_gem_object *to_es_gem_object(struct drm_gem_object *obj) +{ + return container_of(obj, struct es_gem_object, base); +} + +struct es_gem_object *es_gem_create_object(struct drm_device *dev, size_t size); + +int es_gem_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma); + +int es_gem_dumb_create(struct drm_file *file_priv, struct drm_device *drm, + struct drm_mode_create_dumb *args); + +int es_gem_mmap(struct file *filp, struct vm_area_struct *vma); + +struct sg_table *es_gem_prime_get_sg_table(struct drm_gem_object *obj); + +struct drm_gem_object *es_gem_prime_import(struct drm_device *dev, + struct dma_buf *dma_buf); +struct drm_gem_object * +es_gem_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, + struct sg_table *sgt); + +#endif /* __ES_GEM_H__ */ diff --git a/drivers/gpu/drm/eswin/es_plane.c b/drivers/gpu/drm/eswin/es_plane.c new file mode 100644 index 000000000000..68e14d921de5 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_plane.c @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#include +#include +#include +#include +#include + +#include "es_drm.h" +#include "es_type.h" +#include "es_crtc.h" +#include "es_plane.h" +#include "es_gem.h" +#include "es_fb.h" + +void es_plane_destory(struct drm_plane *plane) +{ + struct es_plane *es_plane = to_es_plane(plane); + + drm_plane_cleanup(plane); + kfree(es_plane); +} + +static void es_plane_reset(struct drm_plane *plane) +{ + struct es_plane_state *state; + + if (plane->state) { + __drm_atomic_helper_plane_destroy_state(plane->state); + + state = to_es_plane_state(plane->state); + kfree(state); + plane->state = NULL; + } + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state == NULL) + return; + + __drm_atomic_helper_plane_reset(plane, &state->base); + + state->degamma = ES_DEGAMMA_DISABLE; + state->degamma_changed = false; + memset(&state->status, 0, sizeof(state->status)); +} + +static void _es_plane_duplicate_blob(struct es_plane_state *state, + struct es_plane_state *ori_state) +{ + state->roi = ori_state->roi; + state->color_mgmt = ori_state->color_mgmt; + if (state->roi) + drm_property_blob_get(state->roi); + if (state->color_mgmt) + drm_property_blob_get(state->color_mgmt); +} + +static struct drm_plane_state * +es_plane_atomic_duplicate_state(struct drm_plane *plane) +{ + struct es_plane_state *ori_state; + struct es_plane_state *state; + + if (WARN_ON(!plane->state)) + return NULL; + + ori_state = to_es_plane_state(plane->state); + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, &state->base); + + state->degamma = ori_state->degamma; + state->degamma_changed = ori_state->degamma_changed; + + _es_plane_duplicate_blob(state, ori_state); + memcpy(&state->status, &ori_state->status, sizeof(ori_state->status)); + + return &state->base; +} + +static void es_plane_atomic_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct es_plane_state *es_plane_state = to_es_plane_state(state); + + __drm_atomic_helper_plane_destroy_state(state); + drm_property_blob_put(es_plane_state->roi); + drm_property_blob_put(es_plane_state->color_mgmt); + kfree(es_plane_state); +} + +static int _es_plane_set_property_blob_from_id(struct drm_device *dev, + struct drm_property_blob **blob, + u64 blob_id, + size_t expected_size) +{ + struct drm_property_blob *new_blob = NULL; + + if (blob_id) { + new_blob = drm_property_lookup_blob(dev, blob_id); + if (!new_blob) { + return -EINVAL; + } + + if (new_blob->length != expected_size) { + drm_property_blob_put(new_blob); + return -EINVAL; + } + } + drm_property_replace_blob(blob, new_blob); + drm_property_blob_put(new_blob); + return 0; +} + +static int es_plane_atomic_set_property(struct drm_plane *plane, + struct drm_plane_state *state, + struct drm_property *property, + uint64_t val) +{ + struct drm_device *dev = plane->dev; + struct es_plane *es_plane = to_es_plane(plane); + struct es_plane_state *es_plane_state = to_es_plane_state(state); + int ret = 0; + + if (property == es_plane->degamma_mode) { + if (es_plane_state->degamma != val) { + es_plane_state->degamma = val; + es_plane_state->degamma_changed = true; + } else { + es_plane_state->degamma_changed = false; + } + } else if (property == es_plane->roi_prop) { + ret = _es_plane_set_property_blob_from_id( + dev, &es_plane_state->roi, val, + sizeof(struct drm_es_roi)); + return ret; + } else if (property == es_plane->color_mgmt_prop) { + ret = _es_plane_set_property_blob_from_id( + dev, &es_plane_state->color_mgmt, val, + sizeof(struct drm_es_color_mgmt)); + return ret; + } else { + return -EINVAL; + } + + return 0; +} + +static int es_plane_atomic_get_property(struct drm_plane *plane, + const struct drm_plane_state *state, + struct drm_property *property, + uint64_t *val) +{ + struct es_plane *es_plane = to_es_plane(plane); + const struct es_plane_state *es_plane_state = + container_of(state, const struct es_plane_state, base); + + if (property == es_plane->degamma_mode) + *val = es_plane_state->degamma; + else if (property == es_plane->roi_prop) + *val = (es_plane_state->roi) ? es_plane_state->roi->base.id : 0; + else if (property == es_plane->color_mgmt_prop) + *val = (es_plane_state->color_mgmt) ? + es_plane_state->color_mgmt->base.id : + 0; + else + return -EINVAL; + + return 0; +} + +static bool es_format_mod_supported(struct drm_plane *plane, u32 format, + u64 modifier) +{ + int i; + + /* We always have to allow these modifiers: + * 1. Core DRM checks for LINEAR support if userspace does not provide + modifiers. + * 2. Not passing any modifiers is the same as explicitly passing + INVALID. + */ + if (modifier == DRM_FORMAT_MOD_LINEAR) + return true; + + /* Check that the modifier is on the list of the plane's supported + modifiers. */ + for (i = 0; i < plane->modifier_count; i++) { + if (modifier == plane->modifiers[i]) + break; + } + + if (i == plane->modifier_count) + return false; + + return true; +} + +const struct drm_plane_funcs es_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = es_plane_destory, + .reset = es_plane_reset, + .atomic_duplicate_state = es_plane_atomic_duplicate_state, + .atomic_destroy_state = es_plane_atomic_destroy_state, + .atomic_set_property = es_plane_atomic_set_property, + .atomic_get_property = es_plane_atomic_get_property, + .format_mod_supported = es_format_mod_supported, +}; + +static unsigned char es_get_plane_number(struct drm_framebuffer *fb) +{ + const struct drm_format_info *info; + + if (!fb) + return 0; + + info = drm_format_info(fb->format->format); + if (!info || info->num_planes > MAX_NUM_PLANES) + return 0; + + return info->num_planes; +} + +static int es_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state = + drm_atomic_get_new_plane_state(state, plane); + struct es_plane *es_plane = to_es_plane(plane); + struct drm_framebuffer *fb = new_plane_state->fb; + struct drm_crtc *crtc = new_plane_state->crtc; + struct es_crtc *es_crtc = to_es_crtc(crtc); + + if (!crtc || !fb) + return 0; + + return es_plane->funcs->check(es_crtc->dev, es_plane, new_plane_state); +} + +static void es_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *old_state) +{ + unsigned char i, num_planes; + struct drm_framebuffer *fb; + struct es_plane *es_plane = to_es_plane(plane); + struct drm_plane_state *state = plane->state; + struct es_crtc *es_crtc = to_es_crtc(state->crtc); + struct es_plane_state *plane_state = to_es_plane_state(state); + // struct drm_format_name_buf *name = &plane_state->status.format_name; + + if (!state->fb || !state->crtc) + return; + + fb = state->fb; + + num_planes = es_get_plane_number(fb); + + for (i = 0; i < num_planes; i++) { + struct es_gem_object *es_obj; + + es_obj = es_fb_get_gem_obj(fb, i); + es_plane->dma_addr[i] = es_obj->iova + fb->offsets[i]; + } + + plane_state->status.tile_mode = 0; /* to be updated */ + plane_state->status.src = drm_plane_state_src(state); + plane_state->status.dest = drm_plane_state_dest(state); + // drm_get_format_name(fb->format->format, name); + + es_plane->funcs->update(es_crtc->dev, es_plane); +} + +static void es_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *old_plane_state = + drm_atomic_get_old_plane_state(state, plane); + struct es_plane *es_plane = to_es_plane(plane); + struct es_crtc *es_crtc = to_es_crtc(old_plane_state->crtc); + + if (!old_plane_state->crtc) + return; + + if (es_plane->funcs && es_plane->funcs->disable) + es_plane->funcs->disable(es_crtc->dev, es_plane); +} + +const struct drm_plane_helper_funcs es_plane_helper_funcs = { + .atomic_check = es_plane_atomic_check, + .atomic_update = es_plane_atomic_update, + .atomic_disable = es_plane_atomic_disable, +}; + +static const struct drm_prop_enum_list es_degamma_mode_enum_list[] = { + { ES_DEGAMMA_DISABLE, "disabled" }, + { ES_DEGAMMA_BT709, "preset degamma for BT709" }, + { ES_DEGAMMA_BT2020, "preset degamma for BT2020" }, +}; + +struct es_plane *es_plane_create(struct drm_device *drm_dev, + struct es_plane_info *info, + unsigned int possible_crtcs) +{ + struct es_plane *plane; + int ret; + + if (!info) + return NULL; + + plane = kzalloc(sizeof(struct es_plane), GFP_KERNEL); + if (!plane) + return NULL; + + plane->id = info->id; + + ret = drm_universal_plane_init(drm_dev, &plane->base, possible_crtcs, + &es_plane_funcs, info->formats, + info->num_formats, info->modifiers, + info->type, + info->name ? info->name : NULL); + if (ret) + goto err_free_plane; + + drm_plane_helper_add(&plane->base, &es_plane_helper_funcs); + + /* Set up the plane properties */ + if (info->degamma_size) { + plane->degamma_mode = drm_property_create_enum( + drm_dev, 0, "DEGAMMA_MODE", es_degamma_mode_enum_list, + ARRAY_SIZE(es_degamma_mode_enum_list)); + + if (!plane->degamma_mode) + goto error_cleanup_plane; + + drm_object_attach_property(&plane->base.base, + plane->degamma_mode, + ES_DEGAMMA_DISABLE); + } + + if (info->rotation) { + ret = drm_plane_create_rotation_property( + &plane->base, DRM_MODE_ROTATE_0, info->rotation); + if (ret) + goto error_cleanup_plane; + } + + if (info->blend_mode) { + ret = drm_plane_create_blend_mode_property(&plane->base, + info->blend_mode); + if (ret) + goto error_cleanup_plane; + ret = drm_plane_create_alpha_property(&plane->base); + if (ret) + goto error_cleanup_plane; + } + + if (info->color_encoding) { + ret = drm_plane_create_color_properties( + &plane->base, info->color_encoding, + BIT(DRM_COLOR_YCBCR_LIMITED_RANGE), + DRM_COLOR_YCBCR_BT709, DRM_COLOR_YCBCR_LIMITED_RANGE); + if (ret) + goto error_cleanup_plane; + } + + if (info->roi) { + plane->roi_prop = drm_property_create( + drm_dev, DRM_MODE_PROP_BLOB, "ROI", 0); + if (!plane->roi_prop) + goto error_cleanup_plane; + + drm_object_attach_property(&plane->base.base, plane->roi_prop, + 0); + } + + if (info->color_mgmt) { + plane->color_mgmt_prop = drm_property_create( + drm_dev, DRM_MODE_PROP_BLOB, "COLOR_CONFIG", 0); + if (!plane->color_mgmt_prop) + return NULL; + + drm_object_attach_property(&plane->base.base, + plane->color_mgmt_prop, 0); + } + + return plane; + +error_cleanup_plane: + drm_plane_cleanup(&plane->base); +err_free_plane: + kfree(plane); + return NULL; +} diff --git a/drivers/gpu/drm/eswin/es_plane.h b/drivers/gpu/drm/eswin/es_plane.h new file mode 100644 index 000000000000..cc41d0741361 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_plane.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#ifndef __ES_PLANE_H__ +#define __ES_PLANE_H__ + +#include +#include + +#include "es_type.h" +#include "es_fb.h" + +#define MAX_NUM_PLANES 3 /* colour format plane */ + +struct es_plane; + +struct es_plane_funcs { + void (*update)(struct device *dev, struct es_plane *plane); + void (*disable)(struct device *dev, struct es_plane *plane); + int (*check)(struct device *dev, struct es_plane *plane, + struct drm_plane_state *state); +}; + +struct drm_es_roi { + __u16 enable; + __u16 roi_x; + __u16 roi_y; + __u16 roi_w; + __u16 roi_h; +}; + +struct drm_es_color_mgmt { + __u32 colorkey; + __u32 colorkey_high; + __u32 clear_value; + __u16 clear_enable; + __u16 transparency; +}; + +struct es_plane_status { + u32 tile_mode; + struct drm_rect src; + struct drm_rect dest; + // struct drm_format_name_buf format_name; +}; + +struct es_plane_state { + struct drm_plane_state base; + struct es_plane_status status; /* for debugfs */ + struct drm_property_blob *roi; + struct drm_property_blob *color_mgmt; + + u32 degamma; + bool degamma_changed; +}; + +struct es_plane { + struct drm_plane base; + u8 id; + dma_addr_t dma_addr[MAX_NUM_PLANES]; + + struct drm_property *degamma_mode; + struct drm_property *roi_prop; + struct drm_property *color_mgmt_prop; + + const struct es_plane_funcs *funcs; +}; + +void es_plane_destory(struct drm_plane *plane); + +struct es_plane *es_plane_create(struct drm_device *drm_dev, + struct es_plane_info *info, + unsigned int possible_crtcs); + +static inline struct es_plane *to_es_plane(struct drm_plane *plane) +{ + return container_of(plane, struct es_plane, base); +} + +static inline struct es_plane_state * +to_es_plane_state(struct drm_plane_state *state) +{ + return container_of(state, struct es_plane_state, base); +} +#endif /* __ES_PLANE_H__ */ diff --git a/drivers/gpu/drm/eswin/es_simple_enc.c b/drivers/gpu/drm/eswin/es_simple_enc.c new file mode 100644 index 000000000000..fbc725fc64ea --- /dev/null +++ b/drivers/gpu/drm/eswin/es_simple_enc.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "es_crtc.h" +#include "es_simple_enc.h" + +static const struct simple_encoder_priv hdmi_priv = { + .encoder_type = DRM_MODE_ENCODER_TMDS +}; + +static const struct simple_encoder_priv dsi_priv = { + .encoder_type = DRM_MODE_ENCODER_DSI +}; + +static const struct drm_encoder_funcs encoder_funcs = { + .destroy = drm_encoder_cleanup +}; + +static inline struct simple_encoder *to_simple_encoder(struct drm_encoder *enc) +{ + return container_of(enc, struct simple_encoder, encoder); +} + +static int encoder_parse_dt(struct device *dev) +{ + struct simple_encoder *simple = dev_get_drvdata(dev); + int ret = 0; + int cnt, i; + u32 *vals; + u32 *masks; + + simple->dss_regmap = syscon_regmap_lookup_by_phandle( + dev->of_node, "eswin,dss-syscon"); + + if (IS_ERR(simple->dss_regmap)) { + if (PTR_ERR(simple->dss_regmap) != -ENODEV) { + dev_err(dev, "failed to get dss-syscon\n"); + ret = PTR_ERR(simple->dss_regmap); + goto err; + } + simple->dss_regmap = NULL; + goto err; + } + + cnt = of_property_count_elems_of_size(dev->of_node, "eswin,mux-mask", + 4); + if (!cnt) { + ret = cnt; + goto err; + } + + simple->dss_regdatas = devm_kzalloc( + dev, sizeof(*simple->dss_regdatas) * cnt, GFP_KERNEL); + + masks = kcalloc(cnt, sizeof(*masks), GFP_KERNEL); + if (!masks) { + ret = -ENOMEM; + goto err; + } + + vals = kcalloc(cnt, sizeof(*vals), GFP_KERNEL); + if (!vals) { + ret = -ENOMEM; + goto err_free_masks; + } + + ret = of_property_read_u32_array(dev->of_node, "eswin,mux-mask", masks, + cnt); + if (ret) + goto err_free_vals; + + ret = of_property_read_u32_array(dev->of_node, "eswin,mux-val", vals, + cnt); + if (ret) + goto err_free_vals; + + for (i = 0; i < cnt; i++) { + simple->dss_regdatas[i].mask = masks[i]; + simple->dss_regdatas[i].value = vals[i]; + } + +err_free_vals: + kfree(vals); +err_free_masks: + kfree(masks); +err: + return ret; +} + +void encoder_atomic_enable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + struct simple_encoder *simple = to_simple_encoder(encoder); + struct dss_data *data = simple->dss_regdatas; + int crtc_id; + + if (!simple->dss_regmap) + return; + + crtc_id = drm_of_encoder_active_endpoint_id(simple->dev->of_node, + encoder); + + regmap_update_bits(simple->dss_regmap, 0, data[crtc_id].mask, + data[crtc_id].value); +} + +int encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct es_crtc_state *es_crtc_state = to_es_crtc_state(crtc_state); + struct drm_connector *connector = conn_state->connector; + + es_crtc_state->encoder_type = encoder->encoder_type; + + if (connector->display_info.num_bus_formats) + es_crtc_state->output_fmt = + connector->display_info.bus_formats[0]; + else + es_crtc_state->output_fmt = MEDIA_BUS_FMT_RGB888_1X24; + + switch (es_crtc_state->output_fmt) { + case MEDIA_BUS_FMT_RGB565_1X16: + case MEDIA_BUS_FMT_RGB666_1X18: + case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_YUV10_1X30: + break; + default: + es_crtc_state->output_fmt = MEDIA_BUS_FMT_RGB888_1X24; + break; + } + + return 0; +} + +static const struct drm_encoder_helper_funcs encoder_helper_funcs = { + .atomic_enable = encoder_atomic_enable, + .atomic_check = encoder_atomic_check, +}; + +static int encoder_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm_dev = data; + struct simple_encoder *simple = dev_get_drvdata(dev); + struct drm_encoder *encoder; + struct drm_bridge *bridge; + int ret; + + encoder = &simple->encoder; + + /* Encoder. */ + ret = drm_encoder_init(drm_dev, encoder, &encoder_funcs, + simple->priv->encoder_type, NULL); + if (ret) + return ret; + + drm_encoder_helper_add(encoder, &encoder_helper_funcs); + + encoder->possible_crtcs = + drm_of_find_possible_crtcs(drm_dev, dev->of_node); + + /* output port is port1*/ + ret = drm_of_find_panel_or_bridge(dev->of_node, 1, -1, NULL, &bridge); + if (ret) + goto err; + + ret = drm_bridge_attach(encoder, bridge, NULL, 0); + if (ret) + goto err; + + return 0; +err: + drm_encoder_cleanup(encoder); + + return ret; +} + +static void encoder_unbind(struct device *dev, struct device *master, + void *data) +{ + struct simple_encoder *simple = dev_get_drvdata(dev); + + drm_encoder_cleanup(&simple->encoder); +} + +static const struct component_ops encoder_component_ops = { + .bind = encoder_bind, + .unbind = encoder_unbind, +}; + +static const struct of_device_id simple_encoder_dt_match[] = { + { .compatible = "eswin,hdmi-encoder", .data = &hdmi_priv }, + { .compatible = "eswin,dp-encoder", .data = &hdmi_priv }, + { .compatible = "eswin,dsi-encoder", .data = &dsi_priv }, + {}, +}; +MODULE_DEVICE_TABLE(of, simple_encoder_dt_match); + +static int encoder_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct simple_encoder *simple; + int ret; + + simple = devm_kzalloc(dev, sizeof(*simple), GFP_KERNEL); + if (!simple) + return -ENOMEM; + + simple->priv = of_device_get_match_data(dev); + + simple->dev = dev; + + dev_set_drvdata(dev, simple); + + ret = encoder_parse_dt(dev); + if (ret) + return ret; + + return component_add(dev, &encoder_component_ops); +} + +static int encoder_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + component_del(dev, &encoder_component_ops); + + dev_set_drvdata(dev, NULL); + + return 0; +} + +struct platform_driver simple_encoder_driver = { + .probe = encoder_probe, + .remove = encoder_remove, + .driver = { + .name = "es-simple-encoder", + .of_match_table = of_match_ptr(simple_encoder_dt_match), + }, +}; + +MODULE_DESCRIPTION("Simple Encoder Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/eswin/es_simple_enc.h b/drivers/gpu/drm/eswin/es_simple_enc.h new file mode 100644 index 000000000000..bc461e1231e3 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_simple_enc.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#ifndef __ES_SIMPLE_ENC_H_ +#define __ES_SIMPLE_ENC_H_ + +struct simple_encoder_priv { + unsigned char encoder_type; +}; + +struct dss_data { + u32 mask; + u32 value; +}; + +struct simple_encoder { + struct drm_encoder encoder; + struct device *dev; + const struct simple_encoder_priv *priv; + struct regmap *dss_regmap; + struct dss_data *dss_regdatas; +}; + +extern struct platform_driver simple_encoder_driver; +#endif /* __ES_SIMPLE_ENC_H_ */ diff --git a/drivers/gpu/drm/eswin/es_type.h b/drivers/gpu/drm/eswin/es_type.h new file mode 100644 index 000000000000..aca0a83b0017 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_type.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#ifndef __ES_TYPE_H__ +#define __ES_TYPE_H__ + +#include +#include + +struct es_plane_info { + const char *name; + u8 id; + enum drm_plane_type type; + unsigned int num_formats; + const u32 *formats; + const u64 *modifiers; + unsigned int min_width; + unsigned int min_height; + unsigned int max_width; + unsigned int max_height; + unsigned int rotation; + unsigned int blend_mode; + unsigned int color_encoding; + + /* 0 means no de-gamma LUT */ + unsigned int degamma_size; + + int min_scale; /* 16.16 fixed point */ + int max_scale; /* 16.16 fixed point */ + bool roi; + bool color_mgmt; + bool background; +}; + +struct es_dc_info { + const char *name; + + /* planes */ + unsigned char plane_num; + const struct es_plane_info *planes; + + unsigned int max_bpc; + unsigned int color_formats; + + /* 0 means no gamma LUT */ + u16 gamma_size; + u8 gamma_bits; + + u16 pitch_alignment; + + bool pipe_sync; + bool mmu_prefetch; + bool background; +}; + +#endif /* __ES_TYPE_H__ */ diff --git a/drivers/gpu/drm/eswin/es_virtual.c b/drivers/gpu/drm/eswin/es_virtual.c new file mode 100644 index 000000000000..1d35ada1c0c0 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_virtual.c @@ -0,0 +1,1310 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "es_virtual.h" +#include "es_dc.h" +#include "es_gem.h" + +static unsigned char __get_bpp(struct es_virtual_display *vd) +{ + if (vd->bus_format == MEDIA_BUS_FMT_RGB101010_1X30) + return 10; + return 8; +} + +static void vd_dump_destroy(struct es_virtual_display *vd) +{ + struct drm_device *drm_dev = vd->encoder.dev; + + if (vd->dump_blob.data) { + vunmap(vd->dump_blob.data); + vd->dump_blob.data = NULL; + } + vd->dump_blob.size = 0; + + debugfs_remove(vd->dump_debugfs); + vd->dump_debugfs = NULL; + + if (vd->dump_obj) { + mutex_lock(&drm_dev->struct_mutex); + drm_gem_object_put(&vd->dump_obj->base); + mutex_unlock(&drm_dev->struct_mutex); + vd->dump_obj = NULL; + } +} + +static void vd_dump_create(struct es_virtual_display *vd, + struct drm_display_mode *mode) +{ + struct drm_device *drm_dev = vd->encoder.dev; + struct es_dc *dc = dev_get_drvdata(vd->dc); + struct es_gem_object *obj; + unsigned int pitch, size; + void *kvaddr; + char *name; + + if (!dc->funcs) + return; + + vd_dump_destroy(vd); + + /* dump in 4bytes XRGB format */ + pitch = mode->hdisplay * 4; + pitch = ALIGN(pitch, dc->hw.info->pitch_alignment); + size = PAGE_ALIGN(pitch * mode->vdisplay); + + obj = es_gem_create_object(drm_dev, size); + if (IS_ERR(obj)) { + return; + } + + vd->dump_obj = obj; + vd->pitch = pitch; + + kvaddr = vmap(obj->pages, obj->size >> PAGE_SHIFT, VM_MAP, + pgprot_writecombine(PAGE_KERNEL)); + if (!kvaddr) + goto err; + + vd->dump_blob.data = kvaddr; + vd->dump_blob.size = obj->size; + + name = kasprintf(GFP_KERNEL, "%dx%d-XRGB-%d.raw", mode->hdisplay, + mode->vdisplay, __get_bpp(vd)); + if (!name) + goto err; + + vd->dump_debugfs = debugfs_create_blob( + name, 0444, vd->connector.debugfs_entry, &vd->dump_blob); + kfree(name); + + return; + +err: + vd_dump_destroy(vd); +} + +static void vd_encoder_destroy(struct drm_encoder *encoder) +{ + struct es_virtual_display *vd; + + drm_encoder_cleanup(encoder); + vd = to_virtual_display_with_encoder(encoder); + vd_dump_destroy(vd); +} + +static const struct drm_encoder_funcs vd_encoder_funcs = { + .destroy = vd_encoder_destroy +}; + +static void vd_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct es_virtual_display *vd; + + vd = to_virtual_display_with_encoder(encoder); + vd_dump_create(vd, adjusted_mode); +} + +static void vd_encoder_disable(struct drm_encoder *encoder) +{ + struct es_virtual_display *vd; + struct es_dc *dc; + + vd = to_virtual_display_with_encoder(encoder); + dc = dev_get_drvdata(vd->dc); + if (dc->funcs && dc->funcs->dump_disable) + dc->funcs->dump_disable(vd->dc); +} + +static void vd_encoder_enable(struct drm_encoder *encoder) +{ + struct es_virtual_display *vd; + struct es_dc *dc; + + vd = to_virtual_display_with_encoder(encoder); + dc = dev_get_drvdata(vd->dc); + if (dc->funcs && dc->funcs->dump_enable && vd->dump_obj) + dc->funcs->dump_enable(vd->dc, vd->dump_obj->iova, vd->pitch); +} + +int vd_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct es_crtc_state *es_crtc_state = to_es_crtc_state(crtc_state); + + es_crtc_state->encoder_type = encoder->encoder_type; + + return 0; +} + +static const struct drm_encoder_helper_funcs vd_encoder_helper_funcs = { + .mode_set = vd_mode_set, + .enable = vd_encoder_enable, + .disable = vd_encoder_disable, + .atomic_check = vd_encoder_atomic_check, +}; + +static const struct drm_display_mode edid_cea_modes_1[] = { + /* 1 - 640x480@60Hz 4:3 */ + { + DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 25175, 640, 656, 752, + 800, 0, 480, 490, 492, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 2 - 720x480@60Hz 4:3 */ + { + DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, 798, + 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 3 - 720x480@60Hz 16:9 */ + { + DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, 798, + 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 4 - 1280x720@60Hz 16:9 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390, + 1430, 1650, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 5 - 1920x1080i@60Hz 16:9 */ + { + DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1094, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | + DRM_MODE_FLAG_INTERLACE), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 6 - 720(1440)x480i@60Hz 4:3 */ + { + DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 13500, 720, 739, 801, + 858, 0, 480, 488, 494, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 7 - 720(1440)x480i@60Hz 16:9 */ + { + DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 13500, 720, 739, 801, + 858, 0, 480, 488, 494, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 8 - 720(1440)x240@60Hz 4:3 */ + { + DRM_MODE("720x240", DRM_MODE_TYPE_DRIVER, 13500, 720, 739, 801, + 858, 0, 240, 244, 247, 262, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 9 - 720(1440)x240@60Hz 16:9 */ + { + DRM_MODE("720x240", DRM_MODE_TYPE_DRIVER, 13500, 720, 739, 801, + 858, 0, 240, 244, 247, 262, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 10 - 2880x480i@60Hz 4:3 */ + { + DRM_MODE("2880x480i", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2956, + 3204, 3432, 0, 480, 488, 494, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 11 - 2880x480i@60Hz 16:9 */ + { + DRM_MODE("2880x480i", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2956, + 3204, 3432, 0, 480, 488, 494, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 12 - 2880x240@60Hz 4:3 */ + { + DRM_MODE("2880x240", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2956, + 3204, 3432, 0, 240, 244, 247, 262, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 13 - 2880x240@60Hz 16:9 */ + { + DRM_MODE("2880x240", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2956, + 3204, 3432, 0, 240, 244, 247, 262, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 14 - 1440x480@60Hz 4:3 */ + { + DRM_MODE("1440x480", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1472, + 1596, 1716, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 15 - 1440x480@60Hz 16:9 */ + { + DRM_MODE("1440x480", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1472, + 1596, 1716, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 16 - 1920x1080@60Hz 16:9 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 17 - 720x576@50Hz 4:3 */ + { + DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 27000, 720, 732, 796, + 864, 0, 576, 581, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 18 - 720x576@50Hz 16:9 */ + { + DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 27000, 720, 732, 796, + 864, 0, 576, 581, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 19 - 1280x720@50Hz 16:9 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1720, + 1760, 1980, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 20 - 1920x1080i@50Hz 16:9 */ + { + DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448, + 2492, 2640, 0, 1080, 1084, 1094, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | + DRM_MODE_FLAG_INTERLACE), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 21 - 720(1440)x576i@50Hz 4:3 */ + { + DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 13500, 720, 732, 795, + 864, 0, 576, 580, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 22 - 720(1440)x576i@50Hz 16:9 */ + { + DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 13500, 720, 732, 795, + 864, 0, 576, 580, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 23 - 720(1440)x288@50Hz 4:3 */ + { + DRM_MODE("720x288", DRM_MODE_TYPE_DRIVER, 13500, 720, 732, 795, + 864, 0, 288, 290, 293, 312, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 24 - 720(1440)x288@50Hz 16:9 */ + { + DRM_MODE("720x288", DRM_MODE_TYPE_DRIVER, 13500, 720, 732, 795, + 864, 0, 288, 290, 293, 312, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 25 - 2880x576i@50Hz 4:3 */ + { + DRM_MODE("2880x576i", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2928, + 3180, 3456, 0, 576, 580, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 26 - 2880x576i@50Hz 16:9 */ + { + DRM_MODE("2880x576i", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2928, + 3180, 3456, 0, 576, 580, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 27 - 2880x288@50Hz 4:3 */ + { + DRM_MODE("2880x288", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2928, + 3180, 3456, 0, 288, 290, 293, 312, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 28 - 2880x288@50Hz 16:9 */ + { + DRM_MODE("2880x288", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2928, + 3180, 3456, 0, 288, 290, 293, 312, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 29 - 1440x576@50Hz 4:3 */ + { + DRM_MODE("1440x576", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1464, + 1592, 1728, 0, 576, 581, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 30 - 1440x576@50Hz 16:9 */ + { + DRM_MODE("1440x576", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1464, + 1592, 1728, 0, 576, 581, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 31 - 1920x1080@50Hz 16:9 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2448, + 2492, 2640, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 32 - 1920x1080@24Hz 16:9 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2558, + 2602, 2750, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 33 - 1920x1080@25Hz 16:9 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448, + 2492, 2640, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 34 - 1920x1080@30Hz 16:9 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 35 - 2880x480@60Hz 4:3 */ + { + DRM_MODE("2880x480", DRM_MODE_TYPE_DRIVER, 108000, 2880, 2944, + 3192, 3432, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 36 - 2880x480@60Hz 16:9 */ + { + DRM_MODE("2880x480", DRM_MODE_TYPE_DRIVER, 108000, 2880, 2944, + 3192, 3432, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 37 - 2880x576@50Hz 4:3 */ + { + DRM_MODE("2880x576", DRM_MODE_TYPE_DRIVER, 108000, 2880, 2928, + 3184, 3456, 0, 576, 581, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 38 - 2880x576@50Hz 16:9 */ + { + DRM_MODE("2880x576", DRM_MODE_TYPE_DRIVER, 108000, 2880, 2928, + 3184, 3456, 0, 576, 581, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 39 - 1920x1080i@50Hz 16:9 */ + { + DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 72000, 1920, 1952, + 2120, 2304, 0, 1080, 1126, 1136, 1250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 40 - 1920x1080i@100Hz 16:9 */ + { + DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2448, + 2492, 2640, 0, 1080, 1084, 1094, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | + DRM_MODE_FLAG_INTERLACE), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 41 - 1280x720@100Hz 16:9 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 148500, 1280, 1720, + 1760, 1980, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 42 - 720x576@100Hz 4:3 */ + { + DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 54000, 720, 732, 796, + 864, 0, 576, 581, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 43 - 720x576@100Hz 16:9 */ + { + DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 54000, 720, 732, 796, + 864, 0, 576, 581, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 44 - 720(1440)x576i@100Hz 4:3 */ + { + DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 27000, 720, 732, 795, + 864, 0, 576, 580, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 45 - 720(1440)x576i@100Hz 16:9 */ + { + DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 27000, 720, 732, 795, + 864, 0, 576, 580, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 46 - 1920x1080i@120Hz 16:9 */ + { + DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1094, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | + DRM_MODE_FLAG_INTERLACE), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 47 - 1280x720@120Hz 16:9 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 148500, 1280, 1390, + 1430, 1650, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 48 - 720x480@120Hz 4:3 */ + { + DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 54000, 720, 736, 798, + 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 49 - 720x480@120Hz 16:9 */ + { + DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 54000, 720, 736, 798, + 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 50 - 720(1440)x480i@120Hz 4:3 */ + { + DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 27000, 720, 739, 801, + 858, 0, 480, 488, 494, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 51 - 720(1440)x480i@120Hz 16:9 */ + { + DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 27000, 720, 739, 801, + 858, 0, 480, 488, 494, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 52 - 720x576@200Hz 4:3 */ + { + DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 108000, 720, 732, 796, + 864, 0, 576, 581, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 53 - 720x576@200Hz 16:9 */ + { + DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 108000, 720, 732, 796, + 864, 0, 576, 581, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 54 - 720(1440)x576i@200Hz 4:3 */ + { + DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 54000, 720, 732, 795, + 864, 0, 576, 580, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 55 - 720(1440)x576i@200Hz 16:9 */ + { + DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 54000, 720, 732, 795, + 864, 0, 576, 580, 586, 625, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 56 - 720x480@240Hz 4:3 */ + { + DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 108000, 720, 736, 798, + 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 57 - 720x480@240Hz 16:9 */ + { + DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 108000, 720, 736, 798, + 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 58 - 720(1440)x480i@240Hz 4:3 */ + { + DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 54000, 720, 739, 801, + 858, 0, 480, 488, 494, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, + }, + /* 59 - 720(1440)x480i@240Hz 16:9 */ + { + DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 54000, 720, 739, 801, + 858, 0, 480, 488, 494, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | + DRM_MODE_FLAG_INTERLACE | + DRM_MODE_FLAG_DBLCLK), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 60 - 1280x720@24Hz 16:9 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 59400, 1280, 3040, + 3080, 3300, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 61 - 1280x720@25Hz 16:9 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 3700, + 3740, 3960, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 62 - 1280x720@30Hz 16:9 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 3040, + 3080, 3300, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 63 - 1920x1080@120Hz 16:9 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 297000, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 64 - 1920x1080@100Hz 16:9 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 297000, 1920, 2448, + 2492, 2640, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 65 - 1280x720@24Hz 64:27 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 59400, 1280, 3040, + 3080, 3300, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 66 - 1280x720@25Hz 64:27 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 3700, + 3740, 3960, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 67 - 1280x720@30Hz 64:27 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 3040, + 3080, 3300, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 68 - 1280x720@50Hz 64:27 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1720, + 1760, 1980, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 69 - 1280x720@60Hz 64:27 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390, + 1430, 1650, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 70 - 1280x720@100Hz 64:27 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 148500, 1280, 1720, + 1760, 1980, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 71 - 1280x720@120Hz 64:27 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 148500, 1280, 1390, + 1430, 1650, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 72 - 1920x1080@24Hz 64:27 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2558, + 2602, 2750, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 73 - 1920x1080@25Hz 64:27 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448, + 2492, 2640, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 74 - 1920x1080@30Hz 64:27 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 75 - 1920x1080@50Hz 64:27 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2448, + 2492, 2640, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 76 - 1920x1080@60Hz 64:27 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 77 - 1920x1080@100Hz 64:27 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 297000, 1920, 2448, + 2492, 2640, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 78 - 1920x1080@120Hz 64:27 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 297000, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 79 - 1680x720@24Hz 64:27 */ + { + DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 59400, 1680, 3040, + 3080, 3300, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 80 - 1680x720@25Hz 64:27 */ + { + DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 59400, 1680, 2908, + 2948, 3168, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 81 - 1680x720@30Hz 64:27 */ + { + DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 59400, 1680, 2380, + 2420, 2640, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 82 - 1680x720@50Hz 64:27 */ + { + DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 82500, 1680, 1940, + 1980, 2200, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 83 - 1680x720@60Hz 64:27 */ + { + DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 99000, 1680, 1940, + 1980, 2200, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 84 - 1680x720@100Hz 64:27 */ + { + DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 165000, 1680, 1740, + 1780, 2000, 0, 720, 725, 730, 825, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 85 - 1680x720@120Hz 64:27 */ + { + DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 198000, 1680, 1740, + 1780, 2000, 0, 720, 725, 730, 825, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 86 - 2560x1080@24Hz 64:27 */ + { + DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 99000, 2560, 3558, + 3602, 3750, 0, 1080, 1084, 1089, 1100, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 87 - 2560x1080@25Hz 64:27 */ + { + DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 90000, 2560, 3008, + 3052, 3200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 88 - 2560x1080@30Hz 64:27 */ + { + DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 118800, 2560, 3328, + 3372, 3520, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 89 - 2560x1080@50Hz 64:27 */ + { + DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 185625, 2560, 3108, + 3152, 3300, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 90 - 2560x1080@60Hz 64:27 */ + { + DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 198000, 2560, 2808, + 2852, 3000, 0, 1080, 1084, 1089, 1100, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 91 - 2560x1080@100Hz 64:27 */ + { + DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 371250, 2560, 2778, + 2822, 2970, 0, 1080, 1084, 1089, 1250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 92 - 2560x1080@120Hz 64:27 */ + { + DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 495000, 2560, 3108, + 3152, 3300, 0, 1080, 1084, 1089, 1250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 93 - 3840x2160@24Hz 16:9 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 5116, + 5204, 5500, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 94 - 3840x2160@25Hz 16:9 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 4896, + 4984, 5280, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 95 - 3840x2160@30Hz 16:9 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 4016, + 4104, 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 96 - 3840x2160@50Hz 16:9 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4896, + 4984, 5280, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 97 - 3840x2160@60Hz 16:9 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4016, + 4104, 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 98 - 4096x2160@24Hz 256:135 */ + { + DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 297000, 4096, 5116, + 5204, 5500, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135, + }, + /* 99 - 4096x2160@25Hz 256:135 */ + { + DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 297000, 4096, 5064, + 5152, 5280, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135, + }, + /* 100 - 4096x2160@30Hz 256:135 */ + { + DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 297000, 4096, 4184, + 4272, 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135, + }, + /* 101 - 4096x2160@50Hz 256:135 */ + { + DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 594000, 4096, 5064, + 5152, 5280, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135, + }, + /* 102 - 4096x2160@60Hz 256:135 */ + { + DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 594000, 4096, 4184, + 4272, 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135, + }, + /* 103 - 3840x2160@24Hz 64:27 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 5116, + 5204, 5500, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 104 - 3840x2160@25Hz 64:27 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 4896, + 4984, 5280, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 105 - 3840x2160@30Hz 64:27 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 4016, + 4104, 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 106 - 3840x2160@50Hz 64:27 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4896, + 4984, 5280, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 107 - 3840x2160@60Hz 64:27 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4016, + 4104, 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 108 - 1280x720@48Hz 16:9 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 90000, 1280, 2240, + 2280, 2500, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 109 - 1280x720@48Hz 64:27 */ + { + DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 90000, 1280, 2240, + 2280, 2500, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 110 - 1680x720@48Hz 64:27 */ + { + DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 99000, 1680, 2490, + 2530, 2750, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 111 - 1920x1080@48Hz 16:9 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2558, + 2602, 2750, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 112 - 1920x1080@48Hz 64:27 */ + { + DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2558, + 2602, 2750, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 113 - 2560x1080@48Hz 64:27 */ + { + DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 198000, 2560, 3558, + 3602, 3750, 0, 1080, 1084, 1089, 1100, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 114 - 3840x2160@48Hz 16:9 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 5116, + 5204, 5500, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 115 - 4096x2160@48Hz 256:135 */ + { + DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 594000, 4096, 5116, + 5204, 5500, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135, + }, + /* 116 - 3840x2160@48Hz 64:27 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 5116, + 5204, 5500, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 117 - 3840x2160@100Hz 16:9 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 1188000, 3840, 4896, + 4984, 5280, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 118 - 3840x2160@120Hz 16:9 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 1188000, 3840, 4016, + 4104, 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, + }, + /* 119 - 3840x2160@100Hz 64:27 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 1188000, 3840, 4896, + 4984, 5280, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 120 - 3840x2160@120Hz 64:27 */ + { + DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 1188000, 3840, 4016, + 4104, 4400, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 121 - 5120x2160@24Hz 64:27 */ + { + DRM_MODE("5120x2160", DRM_MODE_TYPE_DRIVER, 396000, 5120, 7116, + 7204, 7500, 0, 2160, 2168, 2178, 2200, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 122 - 5120x2160@25Hz 64:27 */ + { + DRM_MODE("5120x2160", DRM_MODE_TYPE_DRIVER, 396000, 5120, 6816, + 6904, 7200, 0, 2160, 2168, 2178, 2200, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 123 - 5120x2160@30Hz 64:27 */ + { + DRM_MODE("5120x2160", DRM_MODE_TYPE_DRIVER, 396000, 5120, 5784, + 5872, 6000, 0, 2160, 2168, 2178, 2200, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 124 - 5120x2160@48Hz 64:27 */ + { + DRM_MODE("5120x2160", DRM_MODE_TYPE_DRIVER, 742500, 5120, 5866, + 5954, 6250, 0, 2160, 2168, 2178, 2475, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 125 - 5120x2160@50Hz 64:27 */ + { + DRM_MODE("5120x2160", DRM_MODE_TYPE_DRIVER, 742500, 5120, 6216, + 6304, 6600, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 126 - 5120x2160@60Hz 64:27 */ + { + DRM_MODE("5120x2160", DRM_MODE_TYPE_DRIVER, 742500, 5120, 5284, + 5372, 5500, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, + /* 127 - 5120x2160@100Hz 64:27 */ + { + DRM_MODE("5120x2160", DRM_MODE_TYPE_DRIVER, 1485000, 5120, 6216, + 6304, 6600, 0, 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, + }, +}; + +static int vd_get_modes(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_display_mode *mode = NULL; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(edid_cea_modes_1); i++) { + if (edid_cea_modes_1[i].clock == 594000 || + edid_cea_modes_1[i].clock == 297000 || + edid_cea_modes_1[i].clock == 148500 || + edid_cea_modes_1[i].clock == 108000 || + edid_cea_modes_1[i].clock == 74250 || + edid_cea_modes_1[i].clock == 54000 || + edid_cea_modes_1[i].clock == 27000) { + mode = drm_mode_duplicate(dev, &edid_cea_modes_1[i]); + drm_mode_probed_add(connector, mode); + } + } + + return 0; +} + +static struct drm_encoder *vd_best_encoder(struct drm_connector *connector) +{ + struct es_virtual_display *vd; + + vd = to_virtual_display_with_connector(connector); + return &vd->encoder; +} + +static enum drm_mode_status vd_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + if (mode->clock != 594000 && mode->clock != 297000 && + mode->clock != 148500 && mode->clock != 108000 && + mode->clock != 74250 && mode->clock != 54000 && + mode->clock != 27000) { + return MODE_NOCLOCK; + } + + return MODE_OK; +} + +static const struct drm_connector_helper_funcs vd_connector_helper_funcs = { + .get_modes = vd_get_modes, + .mode_valid = vd_mode_valid, + .best_encoder = vd_best_encoder, +}; + +static void vd_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static enum drm_connector_status +vd_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const struct drm_connector_funcs vd_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = vd_connector_destroy, + .detect = vd_connector_detect, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .reset = drm_atomic_helper_connector_reset, +}; + +static int vd_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm_dev = data; + struct es_virtual_display *vd = dev_get_drvdata(dev); + struct drm_encoder *encoder; + struct drm_connector *connector; + struct device_node *ep, *np; + struct platform_device *pdev; + int ret; + + /* Encoder */ + encoder = &vd->encoder; + ret = drm_encoder_init(drm_dev, encoder, &vd_encoder_funcs, + DRM_MODE_ENCODER_VIRTUAL, NULL); + if (ret) { + return ret; + } + + encoder->encoder_type = DRM_MODE_ENCODER_VIRTUAL; + drm_encoder_helper_add(encoder, &vd_encoder_helper_funcs); + + encoder->possible_crtcs = + drm_of_find_possible_crtcs(drm_dev, dev->of_node); + + /* Connector */ + connector = &vd->connector; + ret = drm_connector_init(drm_dev, connector, &vd_connector_funcs, + DRM_MODE_CONNECTOR_VIRTUAL); + if (ret) + goto connector_init_err; + drm_connector_helper_add(connector, &vd_connector_helper_funcs); + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + connector->dpms = DRM_MODE_DPMS_OFF; + connector->polled = + DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; + ret = drm_connector_register(connector); + if (ret) + goto connector_reg_err; + + drm_display_info_set_bus_formats(&connector->display_info, + &vd->bus_format, 1); + + /* attach */ + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + goto attach_err; + + ep = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1); + if (!ep) { + ret = -EINVAL; + goto attach_err; + } + + np = of_graph_get_remote_port_parent(ep); + of_node_put(ep); + if (!np) { + ret = -EINVAL; + goto attach_err; + } + + pdev = of_find_device_by_node(np); + of_node_put(np); + if (!pdev) { + ret = -EPROBE_DEFER; + goto attach_err; + } + get_device(&pdev->dev); + vd->dc = &pdev->dev; + + return 0; + +attach_err: + drm_connector_unregister(connector); +connector_reg_err: + drm_connector_cleanup(connector); +connector_init_err: + drm_encoder_cleanup(encoder); + return ret; +} + +static void vd_unbind(struct device *dev, struct device *master, void *data) +{ + struct es_virtual_display *vd = dev_get_drvdata(dev); + + drm_connector_unregister(&vd->connector); + drm_connector_cleanup(&vd->connector); + drm_encoder_cleanup(&vd->encoder); + if (vd->dump_obj) { + drm_gem_object_put(&vd->dump_obj->base); + vd->dump_obj = NULL; + } +} + +const struct component_ops vd_component_ops = { + .bind = vd_bind, + .unbind = vd_unbind, +}; + +static const struct of_device_id vd_driver_dt_match[] = { + { + .compatible = "eswin,virtual_display", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, vd_driver_dt_match); + +static int vd_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct es_virtual_display *vd; + unsigned char bpp; + + vd = devm_kzalloc(dev, sizeof(*vd), GFP_KERNEL); + if (!vd) + return -ENOMEM; + + vd->bus_format = MEDIA_BUS_FMT_RGB101010_1X30; + of_property_read_u8(dev->of_node, "bpp", &bpp); + if (bpp == 8) + vd->bus_format = MEDIA_BUS_FMT_RBG888_1X24; + + dev_set_drvdata(dev, vd); + + return component_add(dev, &vd_component_ops); +} + +static int vd_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + component_del(dev, &vd_component_ops); + + dev_set_drvdata(dev, NULL); + + return 0; +} + +struct platform_driver virtual_display_platform_driver = { + .probe = vd_probe, + .remove = vd_remove, + .driver = { + .name = "es-virtual-display", + .of_match_table = of_match_ptr(vd_driver_dt_match), + }, +}; + +MODULE_DESCRIPTION("Eswin Virtual Display Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/eswin/es_virtual.h b/drivers/gpu/drm/eswin/es_virtual.h new file mode 100644 index 000000000000..16371232d605 --- /dev/null +++ b/drivers/gpu/drm/eswin/es_virtual.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Eswin Holdings Co., Ltd. + */ + +#ifndef __ES_VIRTUAL_H_ +#define __ES_VIRTUAL_H_ + +struct es_virtual_display { + struct drm_encoder encoder; + struct drm_connector connector; + struct device *dc; + u32 bus_format; + + struct dentry *dump_debugfs; + struct debugfs_blob_wrapper dump_blob; + struct es_gem_object *dump_obj; + unsigned int pitch; +}; + +static inline struct es_virtual_display * +to_virtual_display_with_connector(struct drm_connector *connector) +{ + return container_of(connector, struct es_virtual_display, connector); +} + +static inline struct es_virtual_display * +to_virtual_display_with_encoder(struct drm_encoder *encoder) +{ + return container_of(encoder, struct es_virtual_display, encoder); +} + +extern struct platform_driver virtual_display_platform_driver; +#endif /* __ES_VIRTUAL_H_ */ diff --git a/drivers/gpu/drm/eswin/eswin_dw_hdmi.c b/drivers/gpu/drm/eswin/eswin_dw_hdmi.c new file mode 100644 index 000000000000..330837047580 --- /dev/null +++ b/drivers/gpu/drm/eswin/eswin_dw_hdmi.c @@ -0,0 +1,1129 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2021, ESWIN Electronics Co., Ltd + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "dw_hdmi.h" +#include "es_drv.h" +#include "es_crtc.h" + +#define HIWORD_UPDATE(val, mask) (val | (mask) << 16) +#define ESWIN_HDMI_COLORIMETRY_BT2020 \ + (HDMI_COLORIMETRY_EXTENDED + HDMI_EXTENDED_COLORIMETRY_BT2020) + +/* HDMI output pixel format */ +enum drm_hdmi_output_type { + DRM_HDMI_OUTPUT_DEFAULT_RGB, /* default RGB */ + DRM_HDMI_OUTPUT_YCBCR444, /* YCBCR 444 */ + DRM_HDMI_OUTPUT_YCBCR422, /* YCBCR 422 */ + DRM_HDMI_OUTPUT_YCBCR420, /* YCBCR 420 */ + DRM_HDMI_OUTPUT_YCBCR_HQ, /* Highest subsampled YUV */ + DRM_HDMI_OUTPUT_YCBCR_LQ, /* Lowest subsampled YUV */ + DRM_HDMI_OUTPUT_INVALID, /* Guess what ? */ +}; + +enum dw_hdmi_eswin_color_depth { + ESWIN_HDMI_DEPTH_8, + ESWIN_HDMI_DEPTH_10, + ESWIN_HDMI_DEPTH_12, + ESWIN_HDMI_DEPTH_16, + ESWIN_HDMI_DEPTH_420_10, + ESWIN_HDMI_DEPTH_420_12, + ESWIN_HDMI_DEPTH_420_16 +}; + +struct eswin_hdmi { + struct device *dev; + struct regmap *regmap; + struct drm_encoder encoder; + struct clk *vpll_clk; + struct clk *hclk_vio; + struct clk *dclk; + struct dw_hdmi *hdmi; + struct phy *phy; + u8 id; + unsigned long bus_format; + unsigned long output_bus_format; + unsigned long enc_out_encoding; + + struct drm_property *color_depth_property; + struct drm_property *output_format_property; + struct drm_property *colorimetry_property; + struct drm_property *video_enable_property; + + struct drm_property *color_depth_capacity; + struct drm_property *output_format_capacity; + struct drm_property *is_hdmi_capacity; + struct drm_property *width_heigth_capacity; + struct drm_property *quant_range_select_capacity; + struct drm_property *max_tmds_clock_capacity; + + unsigned int colordepth; + unsigned int colorimetry; + unsigned int phy_bus_width; + unsigned int hdmi_quant_range; + enum drm_hdmi_output_type hdmi_output; + bool video_enable; +}; + +#define to_eswin_hdmi(x) container_of(x, struct eswin_hdmi, x) + +static const struct dw_hdmi_mpll_config eswin_mpll_cfg[] = { + { + 27000000, + { + { 0x0003, 0x0628 }, + { 0x1003, 0x0632 }, + { 0x2003, 0x023c }, + }, + }, + { + 54000000, + { + { 0x0002, 0x0614 }, + { 0x1002, 0x0619 }, + { 0x2002, 0x021e }, + }, + }, + { + 74250000, + { + { 0x0002, 0x0214 }, + { 0x1009, 0x0619 }, + { 0x2001, 0x060f }, + }, + }, + { + 108000000, + { + { 0x0001, 0x060a }, + { 0x1009, 0x0619 }, + { 0x2001, 0x020f }, + }, + }, + { + 148500000, + { + { 0x0001, 0x020a }, + { 0x1018, 0x0619 }, + { 0x2008, 0x060f }, + }, + }, + { + 297000000, + { + { 0x0000, 0x0205 }, + { 0x1658, 0x0219 }, + { 0x2648, 0x020f }, + }, + }, + { + 594000000, + { + { 0x0640, 0x0005 }, + { 0x1658, 0x0019 }, + { 0x2648, 0x000f }, + }, + }, + { + ~0UL, + { + { 0x0000, 0x0000 }, + { 0x0000, 0x0000 }, + { 0x0000, 0x0000 }, + }, + } +}; + +static const struct dw_hdmi_curr_ctrl eswin_cur_ctr[] = { + /* pixelclk bpp8 bpp10 bpp12 */ + { + 27000000, + { 0x0283, 0x0281, 0x02c2 }, + }, + { + 54000000, + { 0x1183, 0x1203, 0x1202 }, + }, + { + 74250000, + { 0x1142, 0x2203, 0x2141 }, + }, + { + 108000000, + { 0x20c0, 0x2203, 0x2100 }, + }, + { + 148500000, + { 0x2080, 0x3203, 0x3141 }, + }, + { + 297000000, + { 0x3041, 0x3182, 0x3100 }, + }, + { + 594000000, + { 0x3080, 0x31c0, 0x3100 }, + }, + { + ~0UL, + { 0x0000, 0x0000, 0x0000 }, + } +}; + +static struct dw_hdmi_phy_config eswin_phy_config[] = { + /*pixelclk symbol term vlev*/ + { 165000000, 0x8088, 0x0007, 0x0180 }, + { 297000000, 0x80c8, 0x0004, 0x0180 }, + { 594000000, 0x80f8, 0x0000, 0x0180 }, + { ~0UL, 0x0000, 0x0000, 0x0000 } +}; + +static int eswin_hdmi_update_phy_table(struct eswin_hdmi *hdmi, u32 *config, + int phy_table_size) +{ + int i; + + if (phy_table_size > ARRAY_SIZE(eswin_phy_config)) { + dev_err(hdmi->dev, "phy table array number is out of range\n"); + return -E2BIG; + } + + for (i = 0; i < phy_table_size; i++) { + if (config[i * 4] != 0) + eswin_phy_config[i].mpixelclock = (u64)config[i * 4]; + else + eswin_phy_config[i].mpixelclock = ~0UL; + eswin_phy_config[i].sym_ctr = (u16)config[i * 4 + 1]; + eswin_phy_config[i].term = (u16)config[i * 4 + 2]; + eswin_phy_config[i].vlev_ctr = (u16)config[i * 4 + 3]; + } + + return 0; +} + +static int eswin_hdmi_parse_dt(struct eswin_hdmi *hdmi) +{ + struct device_node *np = hdmi->dev->of_node; + int ret, val, phy_table_size; + u32 *phy_config; + + hdmi->vpll_clk = devm_clk_get(hdmi->dev, "vpll"); + if (PTR_ERR(hdmi->vpll_clk) == -ENOENT) { + hdmi->vpll_clk = NULL; + } else if (PTR_ERR(hdmi->vpll_clk) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (IS_ERR(hdmi->vpll_clk)) { + DRM_DEV_ERROR(hdmi->dev, "failed to get vpll clock\n"); + return PTR_ERR(hdmi->vpll_clk); + } + + hdmi->hclk_vio = devm_clk_get(hdmi->dev, "hclk_vio"); + if (PTR_ERR(hdmi->hclk_vio) == -ENOENT) { + hdmi->hclk_vio = NULL; + } else if (PTR_ERR(hdmi->hclk_vio) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (IS_ERR(hdmi->hclk_vio)) { + dev_err(hdmi->dev, "failed to get hclk_vio clock\n"); + return PTR_ERR(hdmi->hclk_vio); + } + hdmi->dclk = devm_clk_get(hdmi->dev, "dclk"); + if (PTR_ERR(hdmi->dclk) == -ENOENT) { + hdmi->dclk = NULL; + } else if (PTR_ERR(hdmi->dclk) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (IS_ERR(hdmi->dclk)) { + dev_err(hdmi->dev, "failed to get dclk\n"); + return PTR_ERR(hdmi->dclk); + } + + ret = clk_prepare_enable(hdmi->vpll_clk); + if (ret) { + dev_err(hdmi->dev, "Failed to enable HDMI vpll: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(hdmi->hclk_vio); + if (ret) { + dev_err(hdmi->dev, "Failed to eanble HDMI hclk_vio: %d\n", ret); + return ret; + } + + if (of_get_property(np, "eswin,phy-table", &val)) { + phy_config = kmalloc(val, GFP_KERNEL); + if (!phy_config) { + /* use default table when kmalloc failed. */ + dev_err(hdmi->dev, "kmalloc phy table failed\n"); + + return -ENOMEM; + } + phy_table_size = val / 16; + of_property_read_u32_array(np, "eswin,phy-table", phy_config, + val / sizeof(u32)); + ret = eswin_hdmi_update_phy_table(hdmi, phy_config, + phy_table_size); + if (ret) { + kfree(phy_config); + return ret; + } + kfree(phy_config); + } else { + dev_dbg(hdmi->dev, "use default hdmi phy table\n"); + } + + return 0; +} + +static enum drm_mode_status +dw_hdmi_eswin_mode_valid(struct dw_hdmi *hdmi, void *data, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + const struct dw_hdmi_mpll_config *mpll_cfg = eswin_mpll_cfg; + int pclk = mode->clock * 1000; + bool valid = false; + int i; + + for (i = 0; mpll_cfg[i].mpixelclock != (~0UL); i++) { + if (pclk == mpll_cfg[i].mpixelclock) { + valid = true; + break; + } + } + + return (valid) ? MODE_OK : MODE_BAD; +} + +static void dw_hdmi_eswin_encoder_disable(struct drm_encoder *encoder) +{ + struct eswin_hdmi *hdmi = to_eswin_hdmi(encoder); + + /* + * when plug out hdmi it will be switch cvbs and then phy bus width + * must be set as 8 + */ + if (hdmi->phy) + phy_set_bus_width(hdmi->phy, 8); + clk_disable_unprepare(hdmi->dclk); +} + +static bool +dw_hdmi_eswin_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + return true; +} + +static void dw_hdmi_eswin_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct eswin_hdmi *hdmi = to_eswin_hdmi(encoder); + + clk_set_rate(hdmi->vpll_clk, adj_mode->clock * 1000); +} + +static void dw_hdmi_eswin_encoder_enable(struct drm_encoder *encoder) +{ + struct eswin_hdmi *hdmi = to_eswin_hdmi(encoder); + struct drm_crtc *crtc = encoder->crtc; + + if (WARN_ON(!crtc || !crtc->state)) + return; + + if (hdmi->phy) + phy_set_bus_width(hdmi->phy, hdmi->phy_bus_width); + + clk_set_rate(hdmi->vpll_clk, + crtc->state->adjusted_mode.crtc_clock * 1000); + + clk_set_rate(hdmi->dclk, crtc->state->adjusted_mode.crtc_clock * 1000); + clk_prepare_enable(hdmi->dclk); + + DRM_DEV_DEBUG(hdmi->dev, "dc output to hdmi\n"); +} + +static void dw_hdmi_eswin_select_output(struct drm_connector_state *conn_state, + struct drm_crtc_state *crtc_state, + struct eswin_hdmi *hdmi, + unsigned int *color_format, + unsigned int *color_depth, + unsigned long *enc_out_encoding, + unsigned int *eotf) +{ + struct drm_display_info *info = &conn_state->connector->display_info; + struct drm_display_mode *mode = &crtc_state->mode; + struct hdr_output_metadata *hdr_metadata; + u32 vic = drm_match_cea_mode(mode); + unsigned long tmdsclock, pixclock = mode->crtc_clock; + bool support_dc = false; + int max_tmds_clock = info->max_tmds_clock; + int output_eotf; + + *color_format = DRM_HDMI_OUTPUT_DEFAULT_RGB; + + switch (hdmi->hdmi_output) { + case DRM_HDMI_OUTPUT_YCBCR_HQ: + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + *color_format = DRM_HDMI_OUTPUT_YCBCR444; + else if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + *color_format = DRM_HDMI_OUTPUT_YCBCR422; + else if (conn_state->connector->ycbcr_420_allowed && + drm_mode_is_420(info, mode)) + *color_format = DRM_HDMI_OUTPUT_YCBCR420; + break; + case DRM_HDMI_OUTPUT_YCBCR_LQ: + if (conn_state->connector->ycbcr_420_allowed && + drm_mode_is_420(info, mode)) + *color_format = DRM_HDMI_OUTPUT_YCBCR420; + else if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + *color_format = DRM_HDMI_OUTPUT_YCBCR422; + else if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + *color_format = DRM_HDMI_OUTPUT_YCBCR444; + break; + case DRM_HDMI_OUTPUT_YCBCR420: + if (conn_state->connector->ycbcr_420_allowed && + drm_mode_is_420(info, mode)) + *color_format = DRM_HDMI_OUTPUT_YCBCR420; + break; + case DRM_HDMI_OUTPUT_YCBCR422: + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + *color_format = DRM_HDMI_OUTPUT_YCBCR422; + break; + case DRM_HDMI_OUTPUT_YCBCR444: + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + *color_format = DRM_HDMI_OUTPUT_YCBCR444; + break; + case DRM_HDMI_OUTPUT_DEFAULT_RGB: + default: + break; + } + + if (*color_format == DRM_HDMI_OUTPUT_DEFAULT_RGB && + info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_30) + support_dc = true; + if (*color_format == DRM_HDMI_OUTPUT_YCBCR444 && + info->edid_hdmi_dc_modes & + (DRM_EDID_HDMI_DC_Y444 | DRM_EDID_HDMI_DC_30)) + support_dc = true; + if (*color_format == DRM_HDMI_OUTPUT_YCBCR422) + support_dc = true; + if (*color_format == DRM_HDMI_OUTPUT_YCBCR420 && + info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30) + support_dc = true; + + if (hdmi->colordepth > 8 && support_dc) + *color_depth = 10; + else + *color_depth = 8; + + *eotf = TRADITIONAL_GAMMA_SDR; + if (conn_state->hdr_output_metadata) { + hdr_metadata = (struct hdr_output_metadata *) + conn_state->hdr_output_metadata->data; + output_eotf = hdr_metadata->hdmi_metadata_type1.eotf; + if (output_eotf > TRADITIONAL_GAMMA_HDR && + output_eotf < FUTURE_EOTF) + *eotf = output_eotf; + } + + if ((*eotf > TRADITIONAL_GAMMA_HDR && + conn_state->connector->hdr_sink_metadata.hdmi_type1.eotf & + BIT(*eotf)) || + (hdmi->colorimetry == ESWIN_HDMI_COLORIMETRY_BT2020)) + *enc_out_encoding = V4L2_YCBCR_ENC_BT2020; + else if ((vic == 6) || (vic == 7) || (vic == 21) || (vic == 22) || + (vic == 2) || (vic == 3) || (vic == 17) || (vic == 18)) + *enc_out_encoding = V4L2_YCBCR_ENC_601; + else + *enc_out_encoding = V4L2_YCBCR_ENC_709; + + if (*enc_out_encoding == V4L2_YCBCR_ENC_BT2020) { + /* BT2020 require color depth at lest 10bit */ + *color_depth = 10; + /* We prefer use YCbCr422 to send 10bit */ + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + *color_format = DRM_HDMI_OUTPUT_YCBCR422; + } + + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + pixclock *= 2; + if ((mode->flags & DRM_MODE_FLAG_3D_MASK) == + DRM_MODE_FLAG_3D_FRAME_PACKING) + pixclock *= 2; + + if (*color_format == DRM_HDMI_OUTPUT_YCBCR422 || *color_depth == 8) + tmdsclock = pixclock; + else + tmdsclock = pixclock * (*color_depth) / 8; + + if (*color_format == DRM_HDMI_OUTPUT_YCBCR420) + tmdsclock /= 2; + + /* XXX: max_tmds_clock of some sink is 0, we think it is 340MHz. */ + if (!max_tmds_clock) + max_tmds_clock = 340000; + + max_tmds_clock = min(max_tmds_clock, 594000); + + if (tmdsclock > max_tmds_clock) { + if (max_tmds_clock >= 594000) { + *color_depth = 8; + } else if (max_tmds_clock > 340000) { + if (drm_mode_is_420(info, mode) || tmdsclock >= 594000) + *color_format = DRM_HDMI_OUTPUT_YCBCR420; + } else { + *color_depth = 8; + if (drm_mode_is_420(info, mode) || tmdsclock >= 594000) + *color_format = DRM_HDMI_OUTPUT_YCBCR420; + } + } +} + +static int +dw_hdmi_eswin_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct es_crtc_state *s = to_es_crtc_state(crtc_state); + struct eswin_hdmi *hdmi = to_eswin_hdmi(encoder); + unsigned int colordepth, colorformat, bus_width, eotf; + + dw_hdmi_eswin_select_output(conn_state, crtc_state, hdmi, &colorformat, + &colordepth, &hdmi->enc_out_encoding, + &eotf); + if (colordepth > 8) + hdmi->bus_format = MEDIA_BUS_FMT_RGB101010_1X30; + else + hdmi->bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + /* DC does not support YUV output */ + s->output_fmt = hdmi->bus_format; + + if (colorformat == DRM_HDMI_OUTPUT_YCBCR420) { + if (colordepth > 8) + hdmi->output_bus_format = MEDIA_BUS_FMT_UYYVYY10_0_5X30; + else + hdmi->output_bus_format = MEDIA_BUS_FMT_UYYVYY8_0_5X24; + + bus_width = colordepth / 2; + } else { + if ((colordepth > 8) && + (colorformat != DRM_HDMI_OUTPUT_YCBCR422)) { + if (colorformat != DRM_HDMI_OUTPUT_DEFAULT_RGB) + hdmi->output_bus_format = + MEDIA_BUS_FMT_YUV10_1X30; + else + hdmi->output_bus_format = + MEDIA_BUS_FMT_RGB101010_1X30; + } else { + if (colorformat != DRM_HDMI_OUTPUT_DEFAULT_RGB) + hdmi->output_bus_format = + MEDIA_BUS_FMT_YUV8_1X24; + else + hdmi->output_bus_format = + MEDIA_BUS_FMT_RGB888_1X24; + } + if (colorformat == DRM_HDMI_OUTPUT_YCBCR422) { + bus_width = 8; + if (colordepth > 8) + hdmi->output_bus_format = + MEDIA_BUS_FMT_UYVY10_1X20; + else + hdmi->output_bus_format = + MEDIA_BUS_FMT_UYVY8_1X16; + } else { + bus_width = colordepth; + } + } + + hdmi->phy_bus_width = bus_width; + if (hdmi->phy) + phy_set_bus_width(hdmi->phy, bus_width); + + s->encoder_type = DRM_MODE_ENCODER_TMDS; + + return 0; +} + +static unsigned long dw_hdmi_eswin_get_input_bus_format(void *data) +{ + struct eswin_hdmi *hdmi = (struct eswin_hdmi *)data; + + return hdmi->bus_format; +} + +static unsigned long dw_hdmi_eswin_get_output_bus_format(void *data) +{ + struct eswin_hdmi *hdmi = (struct eswin_hdmi *)data; + + return hdmi->output_bus_format; +} + +static unsigned long dw_hdmi_eswin_get_enc_in_encoding(void *data) +{ + struct eswin_hdmi *hdmi = (struct eswin_hdmi *)data; + + return hdmi->enc_out_encoding; +} + +static unsigned long dw_hdmi_eswin_get_enc_out_encoding(void *data) +{ + struct eswin_hdmi *hdmi = (struct eswin_hdmi *)data; + + return hdmi->enc_out_encoding; +} + +static const struct drm_prop_enum_list color_depth_enum_list[] = { + { 0, "Automatic" }, /* Same as 24bit */ + { 8, "24bit" }, + { 10, "30bit" }, +}; + +static const struct drm_prop_enum_list drm_hdmi_output_enum_list[] = { + { DRM_HDMI_OUTPUT_DEFAULT_RGB, "output_rgb" }, + { DRM_HDMI_OUTPUT_YCBCR444, "output_ycbcr444" }, + { DRM_HDMI_OUTPUT_YCBCR422, "output_ycbcr422" }, + { DRM_HDMI_OUTPUT_YCBCR420, "output_ycbcr420" }, + { DRM_HDMI_OUTPUT_YCBCR_HQ, "output_ycbcr_high_subsampling" }, + { DRM_HDMI_OUTPUT_YCBCR_LQ, "output_ycbcr_low_subsampling" }, + { DRM_HDMI_OUTPUT_INVALID, "invalid_output" }, +}; + +static const struct drm_prop_enum_list colorimetry_enum_list[] = { + { HDMI_COLORIMETRY_NONE, "None" }, + { ESWIN_HDMI_COLORIMETRY_BT2020, "ITU_2020" }, +}; + +static const struct drm_prop_enum_list quant_range_enum_list[] = { + { HDMI_QUANTIZATION_RANGE_DEFAULT, "default" }, + { HDMI_QUANTIZATION_RANGE_LIMITED, "limit" }, + { HDMI_QUANTIZATION_RANGE_FULL, "full" }, +}; + +static const struct drm_prop_enum_list color_depth_capacity_list[] = { + { BIT(ESWIN_HDMI_DEPTH_8), "8bit" }, + { BIT(ESWIN_HDMI_DEPTH_10), "10bit" }, + { BIT(ESWIN_HDMI_DEPTH_12), "12bit" }, + { BIT(ESWIN_HDMI_DEPTH_16), "16bit" }, + { BIT(ESWIN_HDMI_DEPTH_420_10), "yuv420_10bit" }, + { BIT(ESWIN_HDMI_DEPTH_420_12), "yuv420_12bit" }, + { BIT(ESWIN_HDMI_DEPTH_420_16), "yuv420_16bit" }, +}; + +static const struct drm_prop_enum_list output_format_capacity_list[] = { + { BIT(DRM_HDMI_OUTPUT_DEFAULT_RGB), "rgb" }, + { BIT(DRM_HDMI_OUTPUT_YCBCR444), "yuv444" }, + { BIT(DRM_HDMI_OUTPUT_YCBCR422), "yuv422" }, + { BIT(DRM_HDMI_OUTPUT_YCBCR420), "yuv420" }, + { BIT(DRM_HDMI_OUTPUT_YCBCR_HQ), "yuv_hq" }, + { BIT(DRM_HDMI_OUTPUT_YCBCR_LQ), "yuv_lq" }, +}; + +static void dw_hdmi_eswin_attatch_properties(struct drm_connector *connector, + unsigned int color, int version, + void *data) +{ + struct eswin_hdmi *hdmi = (struct eswin_hdmi *)data; + struct drm_property *prop; +#ifdef CONFIG_ESWIN_DW_HDMI + struct es_drm_private *private = connector->dev->dev_private; +#endif + switch (color) { + case MEDIA_BUS_FMT_RGB101010_1X30: + hdmi->hdmi_output = DRM_HDMI_OUTPUT_DEFAULT_RGB; + hdmi->colordepth = 10; + break; + case MEDIA_BUS_FMT_YUV8_1X24: + hdmi->hdmi_output = DRM_HDMI_OUTPUT_YCBCR444; + hdmi->colordepth = 8; + break; + case MEDIA_BUS_FMT_YUV10_1X30: + hdmi->hdmi_output = DRM_HDMI_OUTPUT_YCBCR444; + hdmi->colordepth = 10; + break; + case MEDIA_BUS_FMT_UYVY10_1X20: + hdmi->hdmi_output = DRM_HDMI_OUTPUT_YCBCR422; + hdmi->colordepth = 10; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + hdmi->hdmi_output = DRM_HDMI_OUTPUT_YCBCR422; + hdmi->colordepth = 8; + break; + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + hdmi->hdmi_output = DRM_HDMI_OUTPUT_YCBCR420; + hdmi->colordepth = 8; + break; + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + hdmi->hdmi_output = DRM_HDMI_OUTPUT_YCBCR420; + hdmi->colordepth = 10; + break; + default: + hdmi->hdmi_output = DRM_HDMI_OUTPUT_DEFAULT_RGB; + hdmi->colordepth = 8; + } + + if (!hdmi->color_depth_property) { + prop = drm_property_create_enum( + connector->dev, 0, "hdmi_output_color_depth", + color_depth_enum_list, + ARRAY_SIZE(color_depth_enum_list)); + if (prop) { + hdmi->color_depth_property = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + } + + prop = drm_property_create_enum(connector->dev, 0, "hdmi_output_format", + drm_hdmi_output_enum_list, + ARRAY_SIZE(drm_hdmi_output_enum_list)); + if (prop) { + hdmi->output_format_property = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = drm_property_create_enum(connector->dev, 0, + "hdmi_output_colorimetry", + colorimetry_enum_list, + ARRAY_SIZE(colorimetry_enum_list)); + if (prop) { + hdmi->colorimetry_property = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = drm_property_create_bool(connector->dev, 0, "video_enable"); + if (prop) { + hdmi->video_enable_property = prop; + drm_object_attach_property(&connector->base, prop, 1); + hdmi->video_enable = true; + } + + prop = drm_property_create_enum(connector->dev, 0, + "hdmi_color_depth_capacity", + color_depth_capacity_list, + ARRAY_SIZE(color_depth_capacity_list)); + if (prop) { + hdmi->color_depth_capacity = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = drm_property_create_enum( + connector->dev, 0, "hdmi_output_format_capacity", + output_format_capacity_list, + ARRAY_SIZE(output_format_capacity_list)); + if (prop) { + hdmi->output_format_capacity = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = drm_property_create_bool(connector->dev, 0, "is_hdmi_capacity"); + if (prop) { + hdmi->is_hdmi_capacity = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = drm_property_create_range( + connector->dev, 0, "hdmi_width_height_mm_capacity", 0, 0xff); + if (prop) { + hdmi->width_heigth_capacity = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = drm_property_create_bool(connector->dev, 0, + "hdmi_quant_range_sel_capacity"); + if (prop) { + hdmi->quant_range_select_capacity = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = drm_property_create_range( + connector->dev, 0, "hdmi_max_tmds_clock_capacity", 0, 340000); + if (prop) { + hdmi->max_tmds_clock_capacity = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + prop = connector->dev->mode_config.hdr_output_metadata_property; + if (version >= 0x211a) + drm_object_attach_property(&connector->base, prop, 0); + +#ifdef CONFIG_ESWIN_DW_HDMI + drm_object_attach_property(&connector->base, private->connector_id_prop, + 0); +#endif +} + +static void dw_hdmi_eswin_destroy_properties(struct drm_connector *connector, + void *data) +{ + struct eswin_hdmi *hdmi = (struct eswin_hdmi *)data; + + if (hdmi->color_depth_property) { + drm_property_destroy(connector->dev, + hdmi->color_depth_property); + hdmi->color_depth_property = NULL; + } + + if (hdmi->output_format_property) { + drm_property_destroy(connector->dev, + hdmi->output_format_property); + hdmi->output_format_property = NULL; + } + + if (hdmi->colorimetry_property) { + drm_property_destroy(connector->dev, + hdmi->colorimetry_property); + hdmi->colorimetry_property = NULL; + } + + if (hdmi->video_enable_property) { + drm_property_destroy(connector->dev, + hdmi->video_enable_property); + hdmi->video_enable_property = NULL; + } + + if (hdmi->color_depth_capacity) { + drm_property_destroy(connector->dev, + hdmi->color_depth_capacity); + hdmi->color_depth_capacity = NULL; + } + + if (hdmi->output_format_capacity) { + drm_property_destroy(connector->dev, + hdmi->output_format_capacity); + hdmi->output_format_capacity = NULL; + } + + if (hdmi->is_hdmi_capacity) { + drm_property_destroy(connector->dev, hdmi->is_hdmi_capacity); + hdmi->is_hdmi_capacity = NULL; + } + + if (hdmi->width_heigth_capacity) { + drm_property_destroy(connector->dev, + hdmi->width_heigth_capacity); + hdmi->width_heigth_capacity = NULL; + } + + if (hdmi->quant_range_select_capacity) { + drm_property_destroy(connector->dev, + hdmi->quant_range_select_capacity); + hdmi->quant_range_select_capacity = NULL; + } + + if (hdmi->max_tmds_clock_capacity) { + drm_property_destroy(connector->dev, + hdmi->max_tmds_clock_capacity); + hdmi->max_tmds_clock_capacity = NULL; + } +} + +static int dw_hdmi_eswin_set_property(struct drm_connector *connector, + struct drm_connector_state *state, + struct drm_property *property, + uint64_t val, void *data) +{ + struct eswin_hdmi *hdmi = (struct eswin_hdmi *)data; + + if (property == hdmi->color_depth_property) { + hdmi->colordepth = val; + } else if (property == hdmi->output_format_property) { + hdmi->hdmi_output = val; + } else if (property == hdmi->colorimetry_property) { + hdmi->colorimetry = val; + } else if (property == hdmi->video_enable_property) { + if (hdmi->video_enable != val) { + if (val == true) { + dw_hdmi_enable_video(hdmi->hdmi); + } else { + dw_hdmi_disable_video(hdmi->hdmi); + } + hdmi->video_enable = val; + } + } else { + DRM_ERROR("don't support set %s property\n", property->name); + return 0; + } + return 0; +} + +static int dw_hdmi_eswin_get_property(struct drm_connector *connector, + const struct drm_connector_state *state, + struct drm_property *property, + uint64_t *val, void *data) +{ + struct eswin_hdmi *hdmi = (struct eswin_hdmi *)data; + struct drm_display_info *info = &connector->display_info; + struct drm_mode_config *config = &connector->dev->mode_config; +#ifdef CONFIG_ESWIN_DW_HDMI + struct es_drm_private *private = connector->dev->dev_private; +#endif + if (property == hdmi->color_depth_property) { + *val = hdmi->colordepth; + } else if (property == hdmi->output_format_property) { + *val = hdmi->hdmi_output; + } else if (property == hdmi->color_depth_capacity) { + *val = BIT(ESWIN_HDMI_DEPTH_8); + if (info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_30) + *val |= BIT(ESWIN_HDMI_DEPTH_10); + if (info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_36) + *val |= BIT(ESWIN_HDMI_DEPTH_12); + if (info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_48) + *val |= BIT(ESWIN_HDMI_DEPTH_16); + if (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30) + *val |= BIT(ESWIN_HDMI_DEPTH_420_10); + if (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36) + *val |= BIT(ESWIN_HDMI_DEPTH_420_12); + if (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48) + *val |= BIT(ESWIN_HDMI_DEPTH_420_16); + } else if (property == hdmi->output_format_capacity) { + *val = BIT(DRM_HDMI_OUTPUT_DEFAULT_RGB); + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + *val |= BIT(DRM_HDMI_OUTPUT_YCBCR444); + if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + *val |= BIT(DRM_HDMI_OUTPUT_YCBCR422); + if (connector->ycbcr_420_allowed && + info->color_formats & DRM_COLOR_FORMAT_YCRCB420) + *val |= BIT(DRM_HDMI_OUTPUT_YCBCR420); + } else if (property == config->hdr_output_metadata_property) { + *val = state->hdr_output_metadata ? + state->hdr_output_metadata->base.id : + 0; + } else if (property == hdmi->colorimetry_property) { + *val = hdmi->colorimetry; + } +#ifdef CONFIG_ESWIN_DW_HDMI + else if (property == private->connector_id_prop) { + *val = hdmi->id; + } +#endif + else if (property == hdmi->is_hdmi_capacity) { + *val = info->is_hdmi; + } else if (property == hdmi->quant_range_select_capacity) { + *val = info->rgb_quant_range_selectable; + } else if (property == hdmi->width_heigth_capacity) { + property->values[0] = info->width_mm; + property->values[1] = info->height_mm; + *val = 0; + } else if (property == hdmi->max_tmds_clock_capacity) { + *val = info->max_tmds_clock; + } else if (property == hdmi->video_enable_property) { + *val = hdmi->video_enable; + } else { + DRM_ERROR("failed to get eswin hdmi connector %s property\n", + property->name); + return -EINVAL; + } + return 0; +} + +static const struct dw_hdmi_property_ops dw_hdmi_eswin_property_ops = { + .attatch_properties = dw_hdmi_eswin_attatch_properties, + .destroy_properties = dw_hdmi_eswin_destroy_properties, + .set_property = dw_hdmi_eswin_set_property, + .get_property = dw_hdmi_eswin_get_property, +}; + +static const struct drm_encoder_helper_funcs + dw_hdmi_eswin_encoder_helper_funcs = { + .mode_fixup = dw_hdmi_eswin_encoder_mode_fixup, + .mode_set = dw_hdmi_eswin_encoder_mode_set, + .enable = dw_hdmi_eswin_encoder_enable, + .disable = dw_hdmi_eswin_encoder_disable, + .atomic_check = dw_hdmi_eswin_encoder_atomic_check, + }; + +static const struct dw_hdmi_plat_data win2030_hdmi_drv_data = { + .mode_valid = dw_hdmi_eswin_mode_valid, + .mpll_cfg = eswin_mpll_cfg, + .cur_ctr = eswin_cur_ctr, + .phy_config = eswin_phy_config, + .use_drm_infoframe = true, + .ycbcr_420_allowed = false, +}; + +static const struct of_device_id dw_hdmi_eswin_dt_ids[] = { + { .compatible = "eswin,eswin-dw-hdmi", .data = &win2030_hdmi_drv_data }, + {}, +}; +MODULE_DEVICE_TABLE(of, dw_hdmi_eswin_dt_ids); + +static int dw_hdmi_eswin_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dw_hdmi_plat_data *plat_data; + const struct of_device_id *match; + struct drm_device *drm = data; + struct drm_encoder *encoder; + struct eswin_hdmi *hdmi; + int ret; + + if (!pdev->dev.of_node) + return -ENODEV; + + hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + + match = of_match_node(dw_hdmi_eswin_dt_ids, pdev->dev.of_node); + plat_data = devm_kmemdup(&pdev->dev, match->data, sizeof(*plat_data), + GFP_KERNEL); + if (!plat_data) + return -ENOMEM; + + hdmi->dev = &pdev->dev; + + plat_data->phy_data = hdmi; + encoder = &hdmi->encoder; + + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + /* + * If we failed to find the CRTC(s) which this encoder is + * supposed to be connected to, it's because the CRTC has + * not been registered yet. Defer probing, and hope that + * the required CRTC is added later. + */ + if (encoder->possible_crtcs == 0) + return -EPROBE_DEFER; + + ret = eswin_hdmi_parse_dt(hdmi); + if (ret) { + DRM_DEV_ERROR(hdmi->dev, "Unable to parse OF data\n"); + return ret; + } + + ret = clk_prepare_enable(hdmi->vpll_clk); + if (ret) { + DRM_DEV_ERROR(hdmi->dev, "Failed to enable HDMI vpll: %d\n", + ret); + return ret; + } + + plat_data->phy_data = hdmi; + plat_data->get_input_bus_format = dw_hdmi_eswin_get_input_bus_format; + plat_data->get_output_bus_format = dw_hdmi_eswin_get_output_bus_format; + plat_data->get_enc_in_encoding = dw_hdmi_eswin_get_enc_in_encoding; + plat_data->get_enc_out_encoding = dw_hdmi_eswin_get_enc_out_encoding; + plat_data->property_ops = &dw_hdmi_eswin_property_ops; + + hdmi->phy = devm_phy_optional_get(dev, "hdmi"); + if (IS_ERR(hdmi->phy)) { + ret = PTR_ERR(hdmi->phy); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(hdmi->dev, "failed to get phy\n"); + return ret; + } + + drm_encoder_helper_add(encoder, &dw_hdmi_eswin_encoder_helper_funcs); + drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + + platform_set_drvdata(pdev, hdmi); + + hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data); + + /* + * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(), + * which would have called the encoder cleanup. Do it manually. + */ + if (IS_ERR(hdmi->hdmi)) { + ret = PTR_ERR(hdmi->hdmi); + drm_encoder_cleanup(encoder); + clk_disable_unprepare(hdmi->vpll_clk); + } + + return ret; +} + +static void dw_hdmi_eswin_unbind(struct device *dev, struct device *master, + void *data) +{ + struct eswin_hdmi *hdmi = dev_get_drvdata(dev); + + dw_hdmi_unbind(hdmi->hdmi); + clk_disable_unprepare(hdmi->vpll_clk); +} + +static const struct component_ops dw_hdmi_eswin_ops = { + .bind = dw_hdmi_eswin_bind, + .unbind = dw_hdmi_eswin_unbind, +}; + +static int dw_hdmi_eswin_probe(struct platform_device *pdev) +{ + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + return component_add(&pdev->dev, &dw_hdmi_eswin_ops); +} + +static void dw_hdmi_eswin_shutdown(struct platform_device *pdev) +{ + struct eswin_hdmi *hdmi = dev_get_drvdata(&pdev->dev); + + dw_hdmi_suspend(hdmi->hdmi); + pm_runtime_put_sync(&pdev->dev); +} + +static int dw_hdmi_eswin_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dw_hdmi_eswin_ops); + pm_runtime_disable(&pdev->dev); + return 0; +} + +static int __maybe_unused dw_hdmi_eswin_suspend(struct device *dev) +{ + struct eswin_hdmi *hdmi = dev_get_drvdata(dev); + + dw_hdmi_suspend(hdmi->hdmi); + pm_runtime_put_sync(dev); + + return 0; +} + +static int __maybe_unused dw_hdmi_eswin_resume(struct device *dev) +{ + struct eswin_hdmi *hdmi = dev_get_drvdata(dev); + + dw_hdmi_resume(hdmi->hdmi); + + return 0; +} + +static const struct dev_pm_ops dw_hdmi_eswin_pm = { SET_SYSTEM_SLEEP_PM_OPS( + dw_hdmi_eswin_suspend, dw_hdmi_eswin_resume) }; + +struct platform_driver dw_hdmi_eswin_pltfm_driver = { + .probe = dw_hdmi_eswin_probe, + .remove = dw_hdmi_eswin_remove, + .shutdown = dw_hdmi_eswin_shutdown, + .driver = { + .name = "dw-hdmi-eswin", + .pm = &dw_hdmi_eswin_pm, + .of_match_table = dw_hdmi_eswin_dt_ids, + }, +}; + +//module_platform_driver(dw_hdmi_eswin_pltfm_driver); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/eswin/host_lib_driver_linux_if.h b/drivers/gpu/drm/eswin/host_lib_driver_linux_if.h new file mode 100644 index 000000000000..0cde34421994 --- /dev/null +++ b/drivers/gpu/drm/eswin/host_lib_driver_linux_if.h @@ -0,0 +1,146 @@ +#ifndef _HOST_LIB_DRIVER_LINUX_IF_H_ +#define _HOST_LIB_DRIVER_LINUX_IF_H_ + +#include +#include + +#define HL_DRIVER_ALLOCATE_DYNAMIC_MEM 0xffffffff +// hl_drv_ioctl numbers +enum { + HL_DRV_NR_MIN = 0x10, + HL_DRV_NR_INIT, + HL_DRV_NR_MEMINFO, + HL_DRV_NR_LOAD_CODE, + HL_DRV_NR_READ_DATA, + HL_DRV_NR_WRITE_DATA, + HL_DRV_NR_MEMSET_DATA, + HL_DRV_NR_READ_HPI, + HL_DRV_NR_WRITE_HPI, + + DW_DRV_NR_CONNECT_STATUS, + DW_DRV_NR_CONNECT_SET, + DW_DRV_NR_DISCONNECT_SET, + DW_DRV_NR_AUTH_SUCCESS, + DW_DRV_NR_AUTH_FAIL, + DW_DRV_NR_NO_CAPACITY, + + HL_DRV_NR_MAX +}; + +/* + * HL_DRV_IOC_INIT: associate file descriptor with the indicated memory. This + * must be called before any other hl_drv_ioctl on the file descriptor. + * + * - hpi_base = base address of HPI registers. + * - code_base = base address of firmware memory (0 to allocate internally) + * - data_base = base address of data memory (0 to allocate internally) + * - code_len, data_len = length of firmware and data memory, respectively. + */ +#define HL_DRV_IOC_INIT _IOW('H', HL_DRV_NR_INIT, struct hl_drv_ioc_meminfo) + +/* + * HL_DRV_IOC_MEMINFO: retrieve memory information from file descriptor. + * + * Fills out the meminfo struct, returning the values passed to HL_DRV_IOC_INIT + * except that the actual base addresses of internal allocations (if any) are + * reported. + */ +#define HL_DRV_IOC_MEMINFO \ + _IOR('H', HL_DRV_NR_MEMINFO, struct hl_drv_ioc_meminfo) + +struct hl_drv_ioc_meminfo { + __u32 hpi_base; + __u32 code_base; + __u32 code_size; + __u32 data_base; + __u32 data_size; +}; + +/* + * HL_DRV_IOC_LOAD_CODE: write the provided buffer to the firmware memory. + * + * - len = number of bytes in data buffer + * - data = data to write to firmware memory. + * + * This can only be done once (successfully). Subsequent attempts will + * return -EBUSY. + */ +#define HL_DRV_IOC_LOAD_CODE \ + _IOW('H', HL_DRV_NR_LOAD_CODE, struct hl_drv_ioc_code) + +struct hl_drv_ioc_code { + __u32 len; + __u8 data[]; +}; + +/* + * HL_DRV_IOC_READ_DATA: copy from data memory. + * HL_DRV_IOC_WRITE_DATA: copy to data memory. + * + * - offset = start copying at this byte offset into the data memory. + * - len = number of bytes to copy. + * - data = for write, buffer containing data to copy. + * for read, buffer to which read data will be written. + * + */ +#define HL_DRV_IOC_READ_DATA \ + _IOWR('H', HL_DRV_NR_READ_DATA, struct hl_drv_ioc_data) +#define HL_DRV_IOC_WRITE_DATA \ + _IOW('H', HL_DRV_NR_WRITE_DATA, struct hl_drv_ioc_data) + +/* + * HL_DRV_IOC_MEMSET_DATA: initialize data memory. + * + * - offset = start initializatoin at this byte offset into the data memory. + * - len = number of bytes to set. + * - data[0] = byte value to write to all indicated memory locations. + */ +#define HL_DRV_IOC_MEMSET_DATA \ + _IOW('H', HL_DRV_NR_MEMSET_DATA, struct hl_drv_ioc_data) + +struct hl_drv_ioc_data { + __u32 offset; + __u32 len; + __u8 data[]; +}; + +/* + * HL_DRV_IOC_READ_HPI: read HPI register. + * HL_DRV_IOC_WRITE_HPI: write HPI register. + * + * - offset = byte offset of HPI register to access. + * - value = for write, value to write. + * for read, location to which result is stored. + */ +#define HL_DRV_IOC_READ_HPI \ + _IOWR('H', HL_DRV_NR_READ_HPI, struct hl_drv_ioc_hpi_reg) +#define HL_DRV_IOC_WRITE_HPI \ + _IOW('H', HL_DRV_NR_WRITE_HPI, struct hl_drv_ioc_hpi_reg) + +struct hl_drv_ioc_hpi_reg { + __u32 offset; + __u32 value; +}; + +#define DW_DRV_IOC_CONNECT_STATUS \ + _IOR('H', DW_DRV_NR_CONNECT_STATUS, struct hl_drv_ioc_info) +#define DW_DRV_IOC_CONNECT_SET \ + _IOW('H', DW_DRV_NR_CONNECT_SET, struct hl_drv_ioc_info) +#define DW_DRV_IOC_DISCONNECT_SET \ + _IOW('H', DW_DRV_NR_DISCONNECT_SET, struct hl_drv_ioc_info) +#define DW_DRV_IOC_AUTH_SUCCESS \ + _IOW('H', DW_DRV_NR_AUTH_SUCCESS, struct hl_drv_ioc_info) +#define DW_DRV_IOC_AUTH_FAIL \ + _IOW('H', DW_DRV_NR_AUTH_FAIL, struct hl_drv_ioc_info) +#define DW_DRV_IOC_NO_CAPACITY \ + _IOW('H', DW_DRV_NR_NO_CAPACITY, struct hl_drv_ioc_info) + +struct hl_drv_ioc_info { + union { + __u32 connect; + __u32 auth; + __u32 capacity; + }; +}; + +#endif // _HOST_LIB_DRIVER_LINUX_IF_H_ -- 2.47.0