diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/gpu/drm/mediatek | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/mediatek')
33 files changed, 14291 insertions, 0 deletions
diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig new file mode 100644 index 000000000..369e495d0 --- /dev/null +++ b/drivers/gpu/drm/mediatek/Kconfig @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_MEDIATEK + tristate "DRM Support for Mediatek SoCs" + depends on DRM + depends on ARCH_MEDIATEK || (ARM && COMPILE_TEST) + depends on COMMON_CLK + depends on HAVE_ARM_SMCCC + depends on OF + depends on MTK_MMSYS + select DRM_GEM_DMA_HELPER + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL + select MEMORY + select MTK_SMI + select PHY_MTK_MIPI_DSI + select VIDEOMODE_HELPERS + help + Choose this option if you have a Mediatek SoCs. + The module will be called mediatek-drm + This driver provides kernel mode setting and + buffer management to userspace. + +config DRM_MEDIATEK_DP + tristate "DRM DPTX Support for MediaTek SoCs" + depends on DRM_MEDIATEK + select PHY_MTK_DP + select DRM_DISPLAY_HELPER + select DRM_DISPLAY_DP_HELPER + help + DRM/KMS Display Port driver for MediaTek SoCs. + +config DRM_MEDIATEK_HDMI + tristate "DRM HDMI Support for Mediatek SoCs" + depends on DRM_MEDIATEK + select SND_SOC_HDMI_CODEC if SND_SOC + select PHY_MTK_HDMI + help + DRM/KMS HDMI driver for Mediatek SoCs diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile new file mode 100644 index 000000000..3517d1c65 --- /dev/null +++ b/drivers/gpu/drm/mediatek/Makefile @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0 + +mediatek-drm-y := mtk_disp_aal.o \ + mtk_disp_ccorr.o \ + mtk_disp_color.o \ + mtk_disp_gamma.o \ + mtk_disp_merge.o \ + mtk_disp_ovl.o \ + mtk_disp_rdma.o \ + mtk_drm_crtc.o \ + mtk_drm_ddp_comp.o \ + mtk_drm_drv.o \ + mtk_drm_gem.o \ + mtk_drm_plane.o \ + mtk_dsi.o \ + mtk_dpi.o \ + mtk_mdp_rdma.o + +obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o + +mediatek-drm-hdmi-objs := mtk_cec.o \ + mtk_hdmi.o \ + mtk_hdmi_ddc.o + +obj-$(CONFIG_DRM_MEDIATEK_HDMI) += mediatek-drm-hdmi.o + +obj-$(CONFIG_DRM_MEDIATEK_DP) += mtk_dp.o diff --git a/drivers/gpu/drm/mediatek/mtk_cec.c b/drivers/gpu/drm/mediatek/mtk_cec.c new file mode 100644 index 000000000..cdfa64891 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_cec.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu <jie.qiu@mediatek.com> + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> + +#include "mtk_cec.h" + +#define TR_CONFIG 0x00 +#define CLEAR_CEC_IRQ BIT(15) + +#define CEC_CKGEN 0x04 +#define CEC_32K_PDN BIT(19) +#define PDN BIT(16) + +#define RX_EVENT 0x54 +#define HDMI_PORD BIT(25) +#define HDMI_HTPLG BIT(24) +#define HDMI_PORD_INT_EN BIT(9) +#define HDMI_HTPLG_INT_EN BIT(8) + +#define RX_GEN_WD 0x58 +#define HDMI_PORD_INT_32K_STATUS BIT(26) +#define RX_RISC_INT_32K_STATUS BIT(25) +#define HDMI_HTPLG_INT_32K_STATUS BIT(24) +#define HDMI_PORD_INT_32K_CLR BIT(18) +#define RX_INT_32K_CLR BIT(17) +#define HDMI_HTPLG_INT_32K_CLR BIT(16) +#define HDMI_PORD_INT_32K_STA_MASK BIT(10) +#define RX_RISC_INT_32K_STA_MASK BIT(9) +#define HDMI_HTPLG_INT_32K_STA_MASK BIT(8) +#define HDMI_PORD_INT_32K_EN BIT(2) +#define RX_INT_32K_EN BIT(1) +#define HDMI_HTPLG_INT_32K_EN BIT(0) + +#define NORMAL_INT_CTRL 0x5C +#define HDMI_HTPLG_INT_STA BIT(0) +#define HDMI_PORD_INT_STA BIT(1) +#define HDMI_HTPLG_INT_CLR BIT(16) +#define HDMI_PORD_INT_CLR BIT(17) +#define HDMI_FULL_INT_CLR BIT(20) + +struct mtk_cec { + void __iomem *regs; + struct clk *clk; + int irq; + bool hpd; + void (*hpd_event)(bool hpd, struct device *dev); + struct device *hdmi_dev; + spinlock_t lock; +}; + +static void mtk_cec_clear_bits(struct mtk_cec *cec, unsigned int offset, + unsigned int bits) +{ + void __iomem *reg = cec->regs + offset; + u32 tmp; + + tmp = readl(reg); + tmp &= ~bits; + writel(tmp, reg); +} + +static void mtk_cec_set_bits(struct mtk_cec *cec, unsigned int offset, + unsigned int bits) +{ + void __iomem *reg = cec->regs + offset; + u32 tmp; + + tmp = readl(reg); + tmp |= bits; + writel(tmp, reg); +} + +static void mtk_cec_mask(struct mtk_cec *cec, unsigned int offset, + unsigned int val, unsigned int mask) +{ + u32 tmp = readl(cec->regs + offset) & ~mask; + + tmp |= val & mask; + writel(tmp, cec->regs + offset); +} + +void mtk_cec_set_hpd_event(struct device *dev, + void (*hpd_event)(bool hpd, struct device *dev), + struct device *hdmi_dev) +{ + struct mtk_cec *cec = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&cec->lock, flags); + cec->hdmi_dev = hdmi_dev; + cec->hpd_event = hpd_event; + spin_unlock_irqrestore(&cec->lock, flags); +} + +bool mtk_cec_hpd_high(struct device *dev) +{ + struct mtk_cec *cec = dev_get_drvdata(dev); + unsigned int status; + + status = readl(cec->regs + RX_EVENT); + + return (status & (HDMI_PORD | HDMI_HTPLG)) == (HDMI_PORD | HDMI_HTPLG); +} + +static void mtk_cec_htplg_irq_init(struct mtk_cec *cec) +{ + mtk_cec_mask(cec, CEC_CKGEN, 0 | CEC_32K_PDN, PDN | CEC_32K_PDN); + mtk_cec_set_bits(cec, RX_GEN_WD, HDMI_PORD_INT_32K_CLR | + RX_INT_32K_CLR | HDMI_HTPLG_INT_32K_CLR); + mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_PORD_INT_32K_CLR | RX_INT_32K_CLR | + HDMI_HTPLG_INT_32K_CLR | HDMI_PORD_INT_32K_EN | + RX_INT_32K_EN | HDMI_HTPLG_INT_32K_EN); +} + +static void mtk_cec_htplg_irq_enable(struct mtk_cec *cec) +{ + mtk_cec_set_bits(cec, RX_EVENT, HDMI_PORD_INT_EN | HDMI_HTPLG_INT_EN); +} + +static void mtk_cec_htplg_irq_disable(struct mtk_cec *cec) +{ + mtk_cec_clear_bits(cec, RX_EVENT, HDMI_PORD_INT_EN | HDMI_HTPLG_INT_EN); +} + +static void mtk_cec_clear_htplg_irq(struct mtk_cec *cec) +{ + mtk_cec_set_bits(cec, TR_CONFIG, CLEAR_CEC_IRQ); + mtk_cec_set_bits(cec, NORMAL_INT_CTRL, HDMI_HTPLG_INT_CLR | + HDMI_PORD_INT_CLR | HDMI_FULL_INT_CLR); + mtk_cec_set_bits(cec, RX_GEN_WD, HDMI_PORD_INT_32K_CLR | + RX_INT_32K_CLR | HDMI_HTPLG_INT_32K_CLR); + usleep_range(5, 10); + mtk_cec_clear_bits(cec, NORMAL_INT_CTRL, HDMI_HTPLG_INT_CLR | + HDMI_PORD_INT_CLR | HDMI_FULL_INT_CLR); + mtk_cec_clear_bits(cec, TR_CONFIG, CLEAR_CEC_IRQ); + mtk_cec_clear_bits(cec, RX_GEN_WD, HDMI_PORD_INT_32K_CLR | + RX_INT_32K_CLR | HDMI_HTPLG_INT_32K_CLR); +} + +static void mtk_cec_hpd_event(struct mtk_cec *cec, bool hpd) +{ + void (*hpd_event)(bool hpd, struct device *dev); + struct device *hdmi_dev; + unsigned long flags; + + spin_lock_irqsave(&cec->lock, flags); + hpd_event = cec->hpd_event; + hdmi_dev = cec->hdmi_dev; + spin_unlock_irqrestore(&cec->lock, flags); + + if (hpd_event) + hpd_event(hpd, hdmi_dev); +} + +static irqreturn_t mtk_cec_htplg_isr_thread(int irq, void *arg) +{ + struct device *dev = arg; + struct mtk_cec *cec = dev_get_drvdata(dev); + bool hpd; + + mtk_cec_clear_htplg_irq(cec); + hpd = mtk_cec_hpd_high(dev); + + if (cec->hpd != hpd) { + dev_dbg(dev, "hotplug event! cur hpd = %d, hpd = %d\n", + cec->hpd, hpd); + cec->hpd = hpd; + mtk_cec_hpd_event(cec, hpd); + } + return IRQ_HANDLED; +} + +static int mtk_cec_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_cec *cec; + struct resource *res; + int ret; + + cec = devm_kzalloc(dev, sizeof(*cec), GFP_KERNEL); + if (!cec) + return -ENOMEM; + + platform_set_drvdata(pdev, cec); + spin_lock_init(&cec->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + cec->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(cec->regs)) { + ret = PTR_ERR(cec->regs); + dev_err(dev, "Failed to ioremap cec: %d\n", ret); + return ret; + } + + cec->clk = devm_clk_get(dev, NULL); + if (IS_ERR(cec->clk)) { + ret = PTR_ERR(cec->clk); + dev_err(dev, "Failed to get cec clock: %d\n", ret); + return ret; + } + + cec->irq = platform_get_irq(pdev, 0); + if (cec->irq < 0) + return cec->irq; + + ret = devm_request_threaded_irq(dev, cec->irq, NULL, + mtk_cec_htplg_isr_thread, + IRQF_SHARED | IRQF_TRIGGER_LOW | + IRQF_ONESHOT, "hdmi hpd", dev); + if (ret) { + dev_err(dev, "Failed to register cec irq: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(cec->clk); + if (ret) { + dev_err(dev, "Failed to enable cec clock: %d\n", ret); + return ret; + } + + mtk_cec_htplg_irq_init(cec); + mtk_cec_htplg_irq_enable(cec); + + return 0; +} + +static int mtk_cec_remove(struct platform_device *pdev) +{ + struct mtk_cec *cec = platform_get_drvdata(pdev); + + mtk_cec_htplg_irq_disable(cec); + clk_disable_unprepare(cec->clk); + return 0; +} + +static const struct of_device_id mtk_cec_of_ids[] = { + { .compatible = "mediatek,mt8173-cec", }, + {} +}; +MODULE_DEVICE_TABLE(of, mtk_cec_of_ids); + +struct platform_driver mtk_cec_driver = { + .probe = mtk_cec_probe, + .remove = mtk_cec_remove, + .driver = { + .name = "mediatek-cec", + .of_match_table = mtk_cec_of_ids, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_cec.h b/drivers/gpu/drm/mediatek/mtk_cec.h new file mode 100644 index 000000000..c6412dddb --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_cec.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu <jie.qiu@mediatek.com> + */ +#ifndef _MTK_CEC_H +#define _MTK_CEC_H + +#include <linux/types.h> + +struct device; + +void mtk_cec_set_hpd_event(struct device *dev, + void (*hotplug_event)(bool hpd, struct device *dev), + struct device *hdmi_dev); +bool mtk_cec_hpd_high(struct device *dev); + +#endif /* _MTK_CEC_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_disp_aal.c b/drivers/gpu/drm/mediatek/mtk_disp_aal.c new file mode 100644 index 000000000..0f9d7efb6 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_disp_aal.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/soc/mediatek/mtk-cmdq.h> + +#include "mtk_disp_drv.h" +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp_comp.h" + +#define DISP_AAL_EN 0x0000 +#define AAL_EN BIT(0) +#define DISP_AAL_SIZE 0x0030 +#define DISP_AAL_OUTPUT_SIZE 0x04d8 + + +struct mtk_disp_aal_data { + bool has_gamma; +}; + +/** + * struct mtk_disp_aal - DISP_AAL driver structure + * @ddp_comp - structure containing type enum and hardware resources + * @crtc - associated crtc to report irq events to + */ +struct mtk_disp_aal { + struct clk *clk; + void __iomem *regs; + struct cmdq_client_reg cmdq_reg; + const struct mtk_disp_aal_data *data; +}; + +int mtk_aal_clk_enable(struct device *dev) +{ + struct mtk_disp_aal *aal = dev_get_drvdata(dev); + + return clk_prepare_enable(aal->clk); +} + +void mtk_aal_clk_disable(struct device *dev) +{ + struct mtk_disp_aal *aal = dev_get_drvdata(dev); + + clk_disable_unprepare(aal->clk); +} + +void mtk_aal_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_aal *aal = dev_get_drvdata(dev); + + mtk_ddp_write(cmdq_pkt, w << 16 | h, &aal->cmdq_reg, aal->regs, DISP_AAL_SIZE); + mtk_ddp_write(cmdq_pkt, w << 16 | h, &aal->cmdq_reg, aal->regs, DISP_AAL_OUTPUT_SIZE); +} + +void mtk_aal_gamma_set(struct device *dev, struct drm_crtc_state *state) +{ + struct mtk_disp_aal *aal = dev_get_drvdata(dev); + + if (aal->data && aal->data->has_gamma) + mtk_gamma_set_common(aal->regs, state, false); +} + +void mtk_aal_start(struct device *dev) +{ + struct mtk_disp_aal *aal = dev_get_drvdata(dev); + + writel(AAL_EN, aal->regs + DISP_AAL_EN); +} + +void mtk_aal_stop(struct device *dev) +{ + struct mtk_disp_aal *aal = dev_get_drvdata(dev); + + writel_relaxed(0x0, aal->regs + DISP_AAL_EN); +} + +static int mtk_disp_aal_bind(struct device *dev, struct device *master, + void *data) +{ + return 0; +} + +static void mtk_disp_aal_unbind(struct device *dev, struct device *master, + void *data) +{ +} + +static const struct component_ops mtk_disp_aal_component_ops = { + .bind = mtk_disp_aal_bind, + .unbind = mtk_disp_aal_unbind, +}; + +static int mtk_disp_aal_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_disp_aal *priv; + struct resource *res; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "failed to get aal clk\n"); + return PTR_ERR(priv->clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->regs)) { + dev_err(dev, "failed to ioremap aal\n"); + return PTR_ERR(priv->regs); + } + +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + ret = cmdq_dev_get_client_reg(dev, &priv->cmdq_reg, 0); + if (ret) + dev_dbg(dev, "get mediatek,gce-client-reg fail!\n"); +#endif + + priv->data = of_device_get_match_data(dev); + platform_set_drvdata(pdev, priv); + + ret = component_add(dev, &mtk_disp_aal_component_ops); + if (ret) + dev_err(dev, "Failed to add component: %d\n", ret); + + return ret; +} + +static int mtk_disp_aal_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &mtk_disp_aal_component_ops); + + return 0; +} + +static const struct mtk_disp_aal_data mt8173_aal_driver_data = { + .has_gamma = true, +}; + +static const struct of_device_id mtk_disp_aal_driver_dt_match[] = { + { .compatible = "mediatek,mt8173-disp-aal", + .data = &mt8173_aal_driver_data}, + { .compatible = "mediatek,mt8183-disp-aal"}, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_disp_aal_driver_dt_match); + +struct platform_driver mtk_disp_aal_driver = { + .probe = mtk_disp_aal_probe, + .remove = mtk_disp_aal_remove, + .driver = { + .name = "mediatek-disp-aal", + .owner = THIS_MODULE, + .of_match_table = mtk_disp_aal_driver_dt_match, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_disp_ccorr.c b/drivers/gpu/drm/mediatek/mtk_disp_ccorr.c new file mode 100644 index 000000000..3a53ebc4e --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_disp_ccorr.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/soc/mediatek/mtk-cmdq.h> + +#include "mtk_disp_drv.h" +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp_comp.h" + +#define DISP_CCORR_EN 0x0000 +#define CCORR_EN BIT(0) +#define DISP_CCORR_CFG 0x0020 +#define CCORR_RELAY_MODE BIT(0) +#define CCORR_ENGINE_EN BIT(1) +#define CCORR_GAMMA_OFF BIT(2) +#define CCORR_WGAMUT_SRC_CLIP BIT(3) +#define DISP_CCORR_SIZE 0x0030 +#define DISP_CCORR_COEF_0 0x0080 +#define DISP_CCORR_COEF_1 0x0084 +#define DISP_CCORR_COEF_2 0x0088 +#define DISP_CCORR_COEF_3 0x008C +#define DISP_CCORR_COEF_4 0x0090 + +struct mtk_disp_ccorr_data { + u32 matrix_bits; +}; + +/** + * struct mtk_disp_ccorr - DISP_CCORR driver structure + * @ddp_comp - structure containing type enum and hardware resources + * @crtc - associated crtc to report irq events to + */ +struct mtk_disp_ccorr { + struct clk *clk; + void __iomem *regs; + struct cmdq_client_reg cmdq_reg; + const struct mtk_disp_ccorr_data *data; +}; + +int mtk_ccorr_clk_enable(struct device *dev) +{ + struct mtk_disp_ccorr *ccorr = dev_get_drvdata(dev); + + return clk_prepare_enable(ccorr->clk); +} + +void mtk_ccorr_clk_disable(struct device *dev) +{ + struct mtk_disp_ccorr *ccorr = dev_get_drvdata(dev); + + clk_disable_unprepare(ccorr->clk); +} + +void mtk_ccorr_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_ccorr *ccorr = dev_get_drvdata(dev); + + mtk_ddp_write(cmdq_pkt, w << 16 | h, &ccorr->cmdq_reg, ccorr->regs, + DISP_CCORR_SIZE); + mtk_ddp_write(cmdq_pkt, CCORR_ENGINE_EN, &ccorr->cmdq_reg, ccorr->regs, + DISP_CCORR_CFG); +} + +void mtk_ccorr_start(struct device *dev) +{ + struct mtk_disp_ccorr *ccorr = dev_get_drvdata(dev); + + writel(CCORR_EN, ccorr->regs + DISP_CCORR_EN); +} + +void mtk_ccorr_stop(struct device *dev) +{ + struct mtk_disp_ccorr *ccorr = dev_get_drvdata(dev); + + writel_relaxed(0x0, ccorr->regs + DISP_CCORR_EN); +} + +/* Converts a DRM S31.32 value to the HW S1.n format. */ +static u16 mtk_ctm_s31_32_to_s1_n(u64 in, u32 n) +{ + u16 r; + + /* Sign bit. */ + r = in & BIT_ULL(63) ? BIT(n + 1) : 0; + + if ((in & GENMASK_ULL(62, 33)) > 0) { + /* identity value 0x100000000 -> 0x400(mt8183), */ + /* identity value 0x100000000 -> 0x800(mt8192), */ + /* if bigger this, set it to max 0x7ff. */ + r |= GENMASK(n, 0); + } else { + /* take the n+1 most important bits. */ + r |= (in >> (32 - n)) & GENMASK(n, 0); + } + + return r; +} + +void mtk_ccorr_ctm_set(struct device *dev, struct drm_crtc_state *state) +{ + struct mtk_disp_ccorr *ccorr = dev_get_drvdata(dev); + struct drm_property_blob *blob = state->ctm; + struct drm_color_ctm *ctm; + const u64 *input; + uint16_t coeffs[9] = { 0 }; + int i; + struct cmdq_pkt *cmdq_pkt = NULL; + u32 matrix_bits = ccorr->data->matrix_bits; + + if (!blob) + return; + + ctm = (struct drm_color_ctm *)blob->data; + input = ctm->matrix; + + for (i = 0; i < ARRAY_SIZE(coeffs); i++) + coeffs[i] = mtk_ctm_s31_32_to_s1_n(input[i], matrix_bits); + + mtk_ddp_write(cmdq_pkt, coeffs[0] << 16 | coeffs[1], + &ccorr->cmdq_reg, ccorr->regs, DISP_CCORR_COEF_0); + mtk_ddp_write(cmdq_pkt, coeffs[2] << 16 | coeffs[3], + &ccorr->cmdq_reg, ccorr->regs, DISP_CCORR_COEF_1); + mtk_ddp_write(cmdq_pkt, coeffs[4] << 16 | coeffs[5], + &ccorr->cmdq_reg, ccorr->regs, DISP_CCORR_COEF_2); + mtk_ddp_write(cmdq_pkt, coeffs[6] << 16 | coeffs[7], + &ccorr->cmdq_reg, ccorr->regs, DISP_CCORR_COEF_3); + mtk_ddp_write(cmdq_pkt, coeffs[8] << 16, + &ccorr->cmdq_reg, ccorr->regs, DISP_CCORR_COEF_4); +} + +static int mtk_disp_ccorr_bind(struct device *dev, struct device *master, + void *data) +{ + return 0; +} + +static void mtk_disp_ccorr_unbind(struct device *dev, struct device *master, + void *data) +{ +} + +static const struct component_ops mtk_disp_ccorr_component_ops = { + .bind = mtk_disp_ccorr_bind, + .unbind = mtk_disp_ccorr_unbind, +}; + +static int mtk_disp_ccorr_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_disp_ccorr *priv; + struct resource *res; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "failed to get ccorr clk\n"); + return PTR_ERR(priv->clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->regs)) { + dev_err(dev, "failed to ioremap ccorr\n"); + return PTR_ERR(priv->regs); + } + +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + ret = cmdq_dev_get_client_reg(dev, &priv->cmdq_reg, 0); + if (ret) + dev_dbg(dev, "get mediatek,gce-client-reg fail!\n"); +#endif + + priv->data = of_device_get_match_data(dev); + platform_set_drvdata(pdev, priv); + + ret = component_add(dev, &mtk_disp_ccorr_component_ops); + if (ret) + dev_err(dev, "Failed to add component: %d\n", ret); + + return ret; +} + +static int mtk_disp_ccorr_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &mtk_disp_ccorr_component_ops); + + return 0; +} + +static const struct mtk_disp_ccorr_data mt8183_ccorr_driver_data = { + .matrix_bits = 10, +}; + +static const struct mtk_disp_ccorr_data mt8192_ccorr_driver_data = { + .matrix_bits = 11, +}; + +static const struct of_device_id mtk_disp_ccorr_driver_dt_match[] = { + { .compatible = "mediatek,mt8183-disp-ccorr", + .data = &mt8183_ccorr_driver_data}, + { .compatible = "mediatek,mt8192-disp-ccorr", + .data = &mt8192_ccorr_driver_data}, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_disp_ccorr_driver_dt_match); + +struct platform_driver mtk_disp_ccorr_driver = { + .probe = mtk_disp_ccorr_probe, + .remove = mtk_disp_ccorr_remove, + .driver = { + .name = "mediatek-disp-ccorr", + .owner = THIS_MODULE, + .of_match_table = mtk_disp_ccorr_driver_dt_match, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_disp_color.c b/drivers/gpu/drm/mediatek/mtk_disp_color.c new file mode 100644 index 000000000..473f5bb5c --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_disp_color.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017 MediaTek Inc. + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/soc/mediatek/mtk-cmdq.h> + +#include "mtk_disp_drv.h" +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp_comp.h" + +#define DISP_COLOR_CFG_MAIN 0x0400 +#define DISP_COLOR_START_MT2701 0x0f00 +#define DISP_COLOR_START_MT8167 0x0400 +#define DISP_COLOR_START_MT8173 0x0c00 +#define DISP_COLOR_START(comp) ((comp)->data->color_offset) +#define DISP_COLOR_WIDTH(comp) (DISP_COLOR_START(comp) + 0x50) +#define DISP_COLOR_HEIGHT(comp) (DISP_COLOR_START(comp) + 0x54) + +#define COLOR_BYPASS_ALL BIT(7) +#define COLOR_SEQ_SEL BIT(13) + +struct mtk_disp_color_data { + unsigned int color_offset; +}; + +/* + * struct mtk_disp_color - DISP_COLOR driver structure + * @crtc: associated crtc to report irq events to + * @data: platform colour driver data + */ +struct mtk_disp_color { + struct drm_crtc *crtc; + struct clk *clk; + void __iomem *regs; + struct cmdq_client_reg cmdq_reg; + const struct mtk_disp_color_data *data; +}; + +int mtk_color_clk_enable(struct device *dev) +{ + struct mtk_disp_color *color = dev_get_drvdata(dev); + + return clk_prepare_enable(color->clk); +} + +void mtk_color_clk_disable(struct device *dev) +{ + struct mtk_disp_color *color = dev_get_drvdata(dev); + + clk_disable_unprepare(color->clk); +} + +void mtk_color_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_color *color = dev_get_drvdata(dev); + + mtk_ddp_write(cmdq_pkt, w, &color->cmdq_reg, color->regs, DISP_COLOR_WIDTH(color)); + mtk_ddp_write(cmdq_pkt, h, &color->cmdq_reg, color->regs, DISP_COLOR_HEIGHT(color)); +} + +void mtk_color_start(struct device *dev) +{ + struct mtk_disp_color *color = dev_get_drvdata(dev); + + writel(COLOR_BYPASS_ALL | COLOR_SEQ_SEL, + color->regs + DISP_COLOR_CFG_MAIN); + writel(0x1, color->regs + DISP_COLOR_START(color)); +} + +static int mtk_disp_color_bind(struct device *dev, struct device *master, + void *data) +{ + return 0; +} + +static void mtk_disp_color_unbind(struct device *dev, struct device *master, + void *data) +{ +} + +static const struct component_ops mtk_disp_color_component_ops = { + .bind = mtk_disp_color_bind, + .unbind = mtk_disp_color_unbind, +}; + +static int mtk_disp_color_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_disp_color *priv; + struct resource *res; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "failed to get color clk\n"); + return PTR_ERR(priv->clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->regs)) { + dev_err(dev, "failed to ioremap color\n"); + return PTR_ERR(priv->regs); + } +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + ret = cmdq_dev_get_client_reg(dev, &priv->cmdq_reg, 0); + if (ret) + dev_dbg(dev, "get mediatek,gce-client-reg fail!\n"); +#endif + + priv->data = of_device_get_match_data(dev); + platform_set_drvdata(pdev, priv); + + ret = component_add(dev, &mtk_disp_color_component_ops); + if (ret) + dev_err(dev, "Failed to add component: %d\n", ret); + + return ret; +} + +static int mtk_disp_color_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &mtk_disp_color_component_ops); + + return 0; +} + +static const struct mtk_disp_color_data mt2701_color_driver_data = { + .color_offset = DISP_COLOR_START_MT2701, +}; + +static const struct mtk_disp_color_data mt8167_color_driver_data = { + .color_offset = DISP_COLOR_START_MT8167, +}; + +static const struct mtk_disp_color_data mt8173_color_driver_data = { + .color_offset = DISP_COLOR_START_MT8173, +}; + +static const struct of_device_id mtk_disp_color_driver_dt_match[] = { + { .compatible = "mediatek,mt2701-disp-color", + .data = &mt2701_color_driver_data}, + { .compatible = "mediatek,mt8167-disp-color", + .data = &mt8167_color_driver_data}, + { .compatible = "mediatek,mt8173-disp-color", + .data = &mt8173_color_driver_data}, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_disp_color_driver_dt_match); + +struct platform_driver mtk_disp_color_driver = { + .probe = mtk_disp_color_probe, + .remove = mtk_disp_color_remove, + .driver = { + .name = "mediatek-disp-color", + .owner = THIS_MODULE, + .of_match_table = mtk_disp_color_driver_dt_match, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_disp_drv.h b/drivers/gpu/drm/mediatek/mtk_disp_drv.h new file mode 100644 index 000000000..33e61a136 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_disp_drv.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020 MediaTek Inc. + */ + +#ifndef _MTK_DISP_DRV_H_ +#define _MTK_DISP_DRV_H_ + +#include <linux/soc/mediatek/mtk-cmdq.h> +#include "mtk_drm_plane.h" +#include "mtk_mdp_rdma.h" + +int mtk_aal_clk_enable(struct device *dev); +void mtk_aal_clk_disable(struct device *dev); +void mtk_aal_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt); +void mtk_aal_gamma_set(struct device *dev, struct drm_crtc_state *state); +void mtk_aal_start(struct device *dev); +void mtk_aal_stop(struct device *dev); + +void mtk_ccorr_ctm_set(struct device *dev, struct drm_crtc_state *state); +int mtk_ccorr_clk_enable(struct device *dev); +void mtk_ccorr_clk_disable(struct device *dev); +void mtk_ccorr_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt); +void mtk_ccorr_start(struct device *dev); +void mtk_ccorr_stop(struct device *dev); + +void mtk_color_bypass_shadow(struct device *dev); +int mtk_color_clk_enable(struct device *dev); +void mtk_color_clk_disable(struct device *dev); +void mtk_color_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt); +void mtk_color_start(struct device *dev); + +void mtk_dither_set_common(void __iomem *regs, struct cmdq_client_reg *cmdq_reg, + unsigned int bpc, unsigned int cfg, + unsigned int dither_en, struct cmdq_pkt *cmdq_pkt); + +void mtk_dpi_start(struct device *dev); +void mtk_dpi_stop(struct device *dev); + +void mtk_dsi_ddp_start(struct device *dev); +void mtk_dsi_ddp_stop(struct device *dev); + +int mtk_gamma_clk_enable(struct device *dev); +void mtk_gamma_clk_disable(struct device *dev); +void mtk_gamma_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt); +void mtk_gamma_set(struct device *dev, struct drm_crtc_state *state); +void mtk_gamma_set_common(void __iomem *regs, struct drm_crtc_state *state, bool lut_diff); +void mtk_gamma_start(struct device *dev); +void mtk_gamma_stop(struct device *dev); + +int mtk_merge_clk_enable(struct device *dev); +void mtk_merge_clk_disable(struct device *dev); +void mtk_merge_config(struct device *dev, unsigned int width, + unsigned int height, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt); +void mtk_merge_start(struct device *dev); +void mtk_merge_stop(struct device *dev); +void mtk_merge_advance_config(struct device *dev, unsigned int l_w, unsigned int r_w, + unsigned int h, unsigned int vrefresh, unsigned int bpc, + struct cmdq_pkt *cmdq_pkt); +void mtk_merge_start_cmdq(struct device *dev, struct cmdq_pkt *cmdq_pkt); +void mtk_merge_stop_cmdq(struct device *dev, struct cmdq_pkt *cmdq_pkt); + +void mtk_ovl_bgclr_in_on(struct device *dev); +void mtk_ovl_bgclr_in_off(struct device *dev); +void mtk_ovl_bypass_shadow(struct device *dev); +int mtk_ovl_clk_enable(struct device *dev); +void mtk_ovl_clk_disable(struct device *dev); +void mtk_ovl_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt); +int mtk_ovl_layer_check(struct device *dev, unsigned int idx, + struct mtk_plane_state *mtk_state); +void mtk_ovl_layer_config(struct device *dev, unsigned int idx, + struct mtk_plane_state *state, + struct cmdq_pkt *cmdq_pkt); +unsigned int mtk_ovl_layer_nr(struct device *dev); +void mtk_ovl_layer_on(struct device *dev, unsigned int idx, + struct cmdq_pkt *cmdq_pkt); +void mtk_ovl_layer_off(struct device *dev, unsigned int idx, + struct cmdq_pkt *cmdq_pkt); +void mtk_ovl_start(struct device *dev); +void mtk_ovl_stop(struct device *dev); +unsigned int mtk_ovl_supported_rotations(struct device *dev); +void mtk_ovl_register_vblank_cb(struct device *dev, + void (*vblank_cb)(void *), + void *vblank_cb_data); +void mtk_ovl_unregister_vblank_cb(struct device *dev); +void mtk_ovl_enable_vblank(struct device *dev); +void mtk_ovl_disable_vblank(struct device *dev); + +void mtk_rdma_bypass_shadow(struct device *dev); +int mtk_rdma_clk_enable(struct device *dev); +void mtk_rdma_clk_disable(struct device *dev); +void mtk_rdma_config(struct device *dev, unsigned int width, + unsigned int height, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt); +unsigned int mtk_rdma_layer_nr(struct device *dev); +void mtk_rdma_layer_config(struct device *dev, unsigned int idx, + struct mtk_plane_state *state, + struct cmdq_pkt *cmdq_pkt); +void mtk_rdma_start(struct device *dev); +void mtk_rdma_stop(struct device *dev); +void mtk_rdma_register_vblank_cb(struct device *dev, + void (*vblank_cb)(void *), + void *vblank_cb_data); +void mtk_rdma_unregister_vblank_cb(struct device *dev); +void mtk_rdma_enable_vblank(struct device *dev); +void mtk_rdma_disable_vblank(struct device *dev); + +int mtk_mdp_rdma_clk_enable(struct device *dev); +void mtk_mdp_rdma_clk_disable(struct device *dev); +void mtk_mdp_rdma_start(struct device *dev, struct cmdq_pkt *cmdq_pkt); +void mtk_mdp_rdma_stop(struct device *dev, struct cmdq_pkt *cmdq_pkt); +void mtk_mdp_rdma_config(struct device *dev, struct mtk_mdp_rdma_cfg *cfg, + struct cmdq_pkt *cmdq_pkt); +#endif diff --git a/drivers/gpu/drm/mediatek/mtk_disp_gamma.c b/drivers/gpu/drm/mediatek/mtk_disp_gamma.c new file mode 100644 index 000000000..bbd558a03 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_disp_gamma.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/soc/mediatek/mtk-cmdq.h> + +#include "mtk_disp_drv.h" +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp_comp.h" + +#define DISP_GAMMA_EN 0x0000 +#define GAMMA_EN BIT(0) +#define DISP_GAMMA_CFG 0x0020 +#define GAMMA_LUT_EN BIT(1) +#define GAMMA_DITHERING BIT(2) +#define DISP_GAMMA_SIZE 0x0030 +#define DISP_GAMMA_LUT 0x0700 + +#define LUT_10BIT_MASK 0x03ff + +struct mtk_disp_gamma_data { + bool has_dither; + bool lut_diff; +}; + +/* + * struct mtk_disp_gamma - DISP_GAMMA driver structure + */ +struct mtk_disp_gamma { + struct clk *clk; + void __iomem *regs; + struct cmdq_client_reg cmdq_reg; + const struct mtk_disp_gamma_data *data; +}; + +int mtk_gamma_clk_enable(struct device *dev) +{ + struct mtk_disp_gamma *gamma = dev_get_drvdata(dev); + + return clk_prepare_enable(gamma->clk); +} + +void mtk_gamma_clk_disable(struct device *dev) +{ + struct mtk_disp_gamma *gamma = dev_get_drvdata(dev); + + clk_disable_unprepare(gamma->clk); +} + +void mtk_gamma_set_common(void __iomem *regs, struct drm_crtc_state *state, bool lut_diff) +{ + unsigned int i, reg; + struct drm_color_lut *lut; + void __iomem *lut_base; + u32 word; + u32 diff[3] = {0}; + + if (state->gamma_lut) { + reg = readl(regs + DISP_GAMMA_CFG); + reg = reg | GAMMA_LUT_EN; + writel(reg, regs + DISP_GAMMA_CFG); + lut_base = regs + DISP_GAMMA_LUT; + lut = (struct drm_color_lut *)state->gamma_lut->data; + for (i = 0; i < MTK_LUT_SIZE; i++) { + + if (!lut_diff || (i % 2 == 0)) { + word = (((lut[i].red >> 6) & LUT_10BIT_MASK) << 20) + + (((lut[i].green >> 6) & LUT_10BIT_MASK) << 10) + + ((lut[i].blue >> 6) & LUT_10BIT_MASK); + } else { + diff[0] = (lut[i].red >> 6) - (lut[i - 1].red >> 6); + diff[1] = (lut[i].green >> 6) - (lut[i - 1].green >> 6); + diff[2] = (lut[i].blue >> 6) - (lut[i - 1].blue >> 6); + + word = ((diff[0] & LUT_10BIT_MASK) << 20) + + ((diff[1] & LUT_10BIT_MASK) << 10) + + (diff[2] & LUT_10BIT_MASK); + } + writel(word, (lut_base + i * 4)); + } + } +} + +void mtk_gamma_set(struct device *dev, struct drm_crtc_state *state) +{ + struct mtk_disp_gamma *gamma = dev_get_drvdata(dev); + bool lut_diff = false; + + if (gamma->data) + lut_diff = gamma->data->lut_diff; + + mtk_gamma_set_common(gamma->regs, state, lut_diff); +} + +void mtk_gamma_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_gamma *gamma = dev_get_drvdata(dev); + + mtk_ddp_write(cmdq_pkt, h << 16 | w, &gamma->cmdq_reg, gamma->regs, + DISP_GAMMA_SIZE); + if (gamma->data && gamma->data->has_dither) + mtk_dither_set_common(gamma->regs, &gamma->cmdq_reg, bpc, + DISP_GAMMA_CFG, GAMMA_DITHERING, cmdq_pkt); +} + +void mtk_gamma_start(struct device *dev) +{ + struct mtk_disp_gamma *gamma = dev_get_drvdata(dev); + + writel(GAMMA_EN, gamma->regs + DISP_GAMMA_EN); +} + +void mtk_gamma_stop(struct device *dev) +{ + struct mtk_disp_gamma *gamma = dev_get_drvdata(dev); + + writel_relaxed(0x0, gamma->regs + DISP_GAMMA_EN); +} + +static int mtk_disp_gamma_bind(struct device *dev, struct device *master, + void *data) +{ + return 0; +} + +static void mtk_disp_gamma_unbind(struct device *dev, struct device *master, + void *data) +{ +} + +static const struct component_ops mtk_disp_gamma_component_ops = { + .bind = mtk_disp_gamma_bind, + .unbind = mtk_disp_gamma_unbind, +}; + +static int mtk_disp_gamma_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_disp_gamma *priv; + struct resource *res; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "failed to get gamma clk\n"); + return PTR_ERR(priv->clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->regs)) { + dev_err(dev, "failed to ioremap gamma\n"); + return PTR_ERR(priv->regs); + } + +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + ret = cmdq_dev_get_client_reg(dev, &priv->cmdq_reg, 0); + if (ret) + dev_dbg(dev, "get mediatek,gce-client-reg fail!\n"); +#endif + + priv->data = of_device_get_match_data(dev); + platform_set_drvdata(pdev, priv); + + ret = component_add(dev, &mtk_disp_gamma_component_ops); + if (ret) + dev_err(dev, "Failed to add component: %d\n", ret); + + return ret; +} + +static int mtk_disp_gamma_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &mtk_disp_gamma_component_ops); + + return 0; +} + +static const struct mtk_disp_gamma_data mt8173_gamma_driver_data = { + .has_dither = true, +}; + +static const struct mtk_disp_gamma_data mt8183_gamma_driver_data = { + .lut_diff = true, +}; + +static const struct of_device_id mtk_disp_gamma_driver_dt_match[] = { + { .compatible = "mediatek,mt8173-disp-gamma", + .data = &mt8173_gamma_driver_data}, + { .compatible = "mediatek,mt8183-disp-gamma", + .data = &mt8183_gamma_driver_data}, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_disp_gamma_driver_dt_match); + +struct platform_driver mtk_disp_gamma_driver = { + .probe = mtk_disp_gamma_probe, + .remove = mtk_disp_gamma_remove, + .driver = { + .name = "mediatek-disp-gamma", + .owner = THIS_MODULE, + .of_match_table = mtk_disp_gamma_driver_dt_match, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_disp_merge.c b/drivers/gpu/drm/mediatek/mtk_disp_merge.c new file mode 100644 index 000000000..211140e87 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_disp_merge.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include <linux/soc/mediatek/mtk-cmdq.h> + +#include "mtk_drm_ddp_comp.h" +#include "mtk_drm_drv.h" +#include "mtk_disp_drv.h" + +#define DISP_REG_MERGE_CTRL 0x000 +#define MERGE_EN 1 +#define DISP_REG_MERGE_CFG_0 0x010 +#define DISP_REG_MERGE_CFG_1 0x014 +#define DISP_REG_MERGE_CFG_4 0x020 +#define DISP_REG_MERGE_CFG_10 0x038 +/* no swap */ +#define SWAP_MODE 0 +#define FLD_SWAP_MODE GENMASK(4, 0) +#define DISP_REG_MERGE_CFG_12 0x040 +#define CFG_10_10_1PI_2PO_BUF_MODE 6 +#define CFG_10_10_2PI_2PO_BUF_MODE 8 +#define CFG_11_10_1PI_2PO_MERGE 18 +#define FLD_CFG_MERGE_MODE GENMASK(4, 0) +#define DISP_REG_MERGE_CFG_24 0x070 +#define DISP_REG_MERGE_CFG_25 0x074 +#define DISP_REG_MERGE_CFG_26 0x078 +#define DISP_REG_MERGE_CFG_27 0x07c +#define DISP_REG_MERGE_CFG_36 0x0a0 +#define ULTRA_EN BIT(0) +#define PREULTRA_EN BIT(4) +#define DISP_REG_MERGE_CFG_37 0x0a4 +/* 0: Off, 1: SRAM0, 2: SRAM1, 3: SRAM0 + SRAM1 */ +#define BUFFER_MODE 3 +#define FLD_BUFFER_MODE GENMASK(1, 0) +/* + * For the ultra and preultra settings, 6us ~ 9us is experience value + * and the maximum frequency of mmsys clock is 594MHz. + */ +#define DISP_REG_MERGE_CFG_40 0x0b0 +/* 6 us, 594M pixel/sec */ +#define ULTRA_TH_LOW (6 * 594) +/* 8 us, 594M pixel/sec */ +#define ULTRA_TH_HIGH (8 * 594) +#define FLD_ULTRA_TH_LOW GENMASK(15, 0) +#define FLD_ULTRA_TH_HIGH GENMASK(31, 16) +#define DISP_REG_MERGE_CFG_41 0x0b4 +/* 8 us, 594M pixel/sec */ +#define PREULTRA_TH_LOW (8 * 594) +/* 9 us, 594M pixel/sec */ +#define PREULTRA_TH_HIGH (9 * 594) +#define FLD_PREULTRA_TH_LOW GENMASK(15, 0) +#define FLD_PREULTRA_TH_HIGH GENMASK(31, 16) + +#define DISP_REG_MERGE_MUTE_0 0xf00 + +struct mtk_disp_merge { + void __iomem *regs; + struct clk *clk; + struct clk *async_clk; + struct cmdq_client_reg cmdq_reg; + bool fifo_en; + bool mute_support; + struct reset_control *reset_ctl; +}; + +void mtk_merge_start(struct device *dev) +{ + mtk_merge_start_cmdq(dev, NULL); +} + +void mtk_merge_stop(struct device *dev) +{ + mtk_merge_stop_cmdq(dev, NULL); +} + +void mtk_merge_start_cmdq(struct device *dev, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_merge *priv = dev_get_drvdata(dev); + + if (priv->mute_support) + mtk_ddp_write(cmdq_pkt, 0x0, &priv->cmdq_reg, priv->regs, + DISP_REG_MERGE_MUTE_0); + + mtk_ddp_write(cmdq_pkt, 1, &priv->cmdq_reg, priv->regs, + DISP_REG_MERGE_CTRL); +} + +void mtk_merge_stop_cmdq(struct device *dev, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_merge *priv = dev_get_drvdata(dev); + + if (priv->mute_support) + mtk_ddp_write(cmdq_pkt, 0x1, &priv->cmdq_reg, priv->regs, + DISP_REG_MERGE_MUTE_0); + + mtk_ddp_write(cmdq_pkt, 0, &priv->cmdq_reg, priv->regs, + DISP_REG_MERGE_CTRL); + + if (!cmdq_pkt && priv->async_clk) + reset_control_reset(priv->reset_ctl); +} + +static void mtk_merge_fifo_setting(struct mtk_disp_merge *priv, + struct cmdq_pkt *cmdq_pkt) +{ + mtk_ddp_write(cmdq_pkt, ULTRA_EN | PREULTRA_EN, + &priv->cmdq_reg, priv->regs, DISP_REG_MERGE_CFG_36); + + mtk_ddp_write_mask(cmdq_pkt, BUFFER_MODE, + &priv->cmdq_reg, priv->regs, DISP_REG_MERGE_CFG_37, + FLD_BUFFER_MODE); + + mtk_ddp_write_mask(cmdq_pkt, ULTRA_TH_LOW | ULTRA_TH_HIGH << 16, + &priv->cmdq_reg, priv->regs, DISP_REG_MERGE_CFG_40, + FLD_ULTRA_TH_LOW | FLD_ULTRA_TH_HIGH); + + mtk_ddp_write_mask(cmdq_pkt, PREULTRA_TH_LOW | PREULTRA_TH_HIGH << 16, + &priv->cmdq_reg, priv->regs, DISP_REG_MERGE_CFG_41, + FLD_PREULTRA_TH_LOW | FLD_PREULTRA_TH_HIGH); +} + +void mtk_merge_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + mtk_merge_advance_config(dev, w, 0, h, vrefresh, bpc, cmdq_pkt); +} + +void mtk_merge_advance_config(struct device *dev, unsigned int l_w, unsigned int r_w, + unsigned int h, unsigned int vrefresh, unsigned int bpc, + struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_merge *priv = dev_get_drvdata(dev); + unsigned int mode = CFG_10_10_1PI_2PO_BUF_MODE; + + if (!h || !l_w) { + dev_err(dev, "%s: input width(%d) or height(%d) is invalid\n", __func__, l_w, h); + return; + } + + if (priv->fifo_en) { + mtk_merge_fifo_setting(priv, cmdq_pkt); + mode = CFG_10_10_2PI_2PO_BUF_MODE; + } + + if (r_w) + mode = CFG_11_10_1PI_2PO_MERGE; + + mtk_ddp_write(cmdq_pkt, h << 16 | l_w, &priv->cmdq_reg, priv->regs, + DISP_REG_MERGE_CFG_0); + mtk_ddp_write(cmdq_pkt, h << 16 | r_w, &priv->cmdq_reg, priv->regs, + DISP_REG_MERGE_CFG_1); + mtk_ddp_write(cmdq_pkt, h << 16 | (l_w + r_w), &priv->cmdq_reg, priv->regs, + DISP_REG_MERGE_CFG_4); + /* + * DISP_REG_MERGE_CFG_24 is merge SRAM0 w/h + * DISP_REG_MERGE_CFG_25 is merge SRAM1 w/h. + * If r_w > 0, the merge is in merge mode (input0 and input1 merge together), + * the input0 goes to SRAM0, and input1 goes to SRAM1. + * If r_w = 0, the merge is in buffer mode, the input goes through SRAM0 and + * then to SRAM1. Both SRAM0 and SRAM1 are set to the same size. + */ + mtk_ddp_write(cmdq_pkt, h << 16 | l_w, &priv->cmdq_reg, priv->regs, + DISP_REG_MERGE_CFG_24); + if (r_w) + mtk_ddp_write(cmdq_pkt, h << 16 | r_w, &priv->cmdq_reg, priv->regs, + DISP_REG_MERGE_CFG_25); + else + mtk_ddp_write(cmdq_pkt, h << 16 | l_w, &priv->cmdq_reg, priv->regs, + DISP_REG_MERGE_CFG_25); + + /* + * DISP_REG_MERGE_CFG_26 and DISP_REG_MERGE_CFG_27 is only used in LR merge. + * Only take effect when the merge is setting to merge mode. + */ + mtk_ddp_write(cmdq_pkt, h << 16 | l_w, &priv->cmdq_reg, priv->regs, + DISP_REG_MERGE_CFG_26); + mtk_ddp_write(cmdq_pkt, h << 16 | r_w, &priv->cmdq_reg, priv->regs, + DISP_REG_MERGE_CFG_27); + + mtk_ddp_write_mask(cmdq_pkt, SWAP_MODE, &priv->cmdq_reg, priv->regs, + DISP_REG_MERGE_CFG_10, FLD_SWAP_MODE); + mtk_ddp_write_mask(cmdq_pkt, mode, &priv->cmdq_reg, priv->regs, + DISP_REG_MERGE_CFG_12, FLD_CFG_MERGE_MODE); +} + +int mtk_merge_clk_enable(struct device *dev) +{ + int ret = 0; + struct mtk_disp_merge *priv = dev_get_drvdata(dev); + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(dev, "merge clk prepare enable failed\n"); + return ret; + } + + ret = clk_prepare_enable(priv->async_clk); + if (ret) { + /* should clean up the state of priv->clk */ + clk_disable_unprepare(priv->clk); + + dev_err(dev, "async clk prepare enable failed\n"); + return ret; + } + + return ret; +} + +void mtk_merge_clk_disable(struct device *dev) +{ + struct mtk_disp_merge *priv = dev_get_drvdata(dev); + + clk_disable_unprepare(priv->async_clk); + clk_disable_unprepare(priv->clk); +} + +static int mtk_disp_merge_bind(struct device *dev, struct device *master, + void *data) +{ + return 0; +} + +static void mtk_disp_merge_unbind(struct device *dev, struct device *master, + void *data) +{ +} + +static const struct component_ops mtk_disp_merge_component_ops = { + .bind = mtk_disp_merge_bind, + .unbind = mtk_disp_merge_unbind, +}; + +static int mtk_disp_merge_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct mtk_disp_merge *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->regs)) { + dev_err(dev, "failed to ioremap merge\n"); + return PTR_ERR(priv->regs); + } + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "failed to get merge clk\n"); + return PTR_ERR(priv->clk); + } + + priv->async_clk = devm_clk_get_optional(dev, "merge_async"); + if (IS_ERR(priv->async_clk)) { + dev_err(dev, "failed to get merge async clock\n"); + return PTR_ERR(priv->async_clk); + } + + if (priv->async_clk) { + priv->reset_ctl = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(priv->reset_ctl)) + return PTR_ERR(priv->reset_ctl); + } + +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + ret = cmdq_dev_get_client_reg(dev, &priv->cmdq_reg, 0); + if (ret) + dev_dbg(dev, "get mediatek,gce-client-reg fail!\n"); +#endif + + priv->fifo_en = of_property_read_bool(dev->of_node, + "mediatek,merge-fifo-en"); + + priv->mute_support = of_property_read_bool(dev->of_node, + "mediatek,merge-mute"); + platform_set_drvdata(pdev, priv); + + ret = component_add(dev, &mtk_disp_merge_component_ops); + if (ret != 0) + dev_err(dev, "Failed to add component: %d\n", ret); + + return ret; +} + +static int mtk_disp_merge_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &mtk_disp_merge_component_ops); + + return 0; +} + +static const struct of_device_id mtk_disp_merge_driver_dt_match[] = { + { .compatible = "mediatek,mt8195-disp-merge", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, mtk_disp_merge_driver_dt_match); + +struct platform_driver mtk_disp_merge_driver = { + .probe = mtk_disp_merge_probe, + .remove = mtk_disp_merge_remove, + .driver = { + .name = "mediatek-disp-merge", + .owner = THIS_MODULE, + .of_match_table = mtk_disp_merge_driver_dt_match, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_disp_ovl.c b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c new file mode 100644 index 000000000..002b0f6ca --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 MediaTek Inc. + */ + +#include <drm/drm_blend.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/soc/mediatek/mtk-cmdq.h> + +#include "mtk_disp_drv.h" +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp_comp.h" + +#define DISP_REG_OVL_INTEN 0x0004 +#define OVL_FME_CPL_INT BIT(1) +#define DISP_REG_OVL_INTSTA 0x0008 +#define DISP_REG_OVL_EN 0x000c +#define DISP_REG_OVL_RST 0x0014 +#define DISP_REG_OVL_ROI_SIZE 0x0020 +#define DISP_REG_OVL_DATAPATH_CON 0x0024 +#define OVL_LAYER_SMI_ID_EN BIT(0) +#define OVL_BGCLR_SEL_IN BIT(2) +#define DISP_REG_OVL_ROI_BGCLR 0x0028 +#define DISP_REG_OVL_SRC_CON 0x002c +#define DISP_REG_OVL_CON(n) (0x0030 + 0x20 * (n)) +#define DISP_REG_OVL_SRC_SIZE(n) (0x0038 + 0x20 * (n)) +#define DISP_REG_OVL_OFFSET(n) (0x003c + 0x20 * (n)) +#define DISP_REG_OVL_PITCH(n) (0x0044 + 0x20 * (n)) +#define DISP_REG_OVL_RDMA_CTRL(n) (0x00c0 + 0x20 * (n)) +#define DISP_REG_OVL_RDMA_GMC(n) (0x00c8 + 0x20 * (n)) +#define DISP_REG_OVL_ADDR_MT2701 0x0040 +#define DISP_REG_OVL_ADDR_MT8173 0x0f40 +#define DISP_REG_OVL_ADDR(ovl, n) ((ovl)->data->addr + 0x20 * (n)) + +#define GMC_THRESHOLD_BITS 16 +#define GMC_THRESHOLD_HIGH ((1 << GMC_THRESHOLD_BITS) / 4) +#define GMC_THRESHOLD_LOW ((1 << GMC_THRESHOLD_BITS) / 8) + +#define OVL_CON_BYTE_SWAP BIT(24) +#define OVL_CON_MTX_YUV_TO_RGB (6 << 16) +#define OVL_CON_CLRFMT_RGB (1 << 12) +#define OVL_CON_CLRFMT_RGBA8888 (2 << 12) +#define OVL_CON_CLRFMT_ARGB8888 (3 << 12) +#define OVL_CON_CLRFMT_UYVY (4 << 12) +#define OVL_CON_CLRFMT_YUYV (5 << 12) +#define OVL_CON_CLRFMT_RGB565(ovl) ((ovl)->data->fmt_rgb565_is_0 ? \ + 0 : OVL_CON_CLRFMT_RGB) +#define OVL_CON_CLRFMT_RGB888(ovl) ((ovl)->data->fmt_rgb565_is_0 ? \ + OVL_CON_CLRFMT_RGB : 0) +#define OVL_CON_AEN BIT(8) +#define OVL_CON_ALPHA 0xff +#define OVL_CON_VIRT_FLIP BIT(9) +#define OVL_CON_HORZ_FLIP BIT(10) + +struct mtk_disp_ovl_data { + unsigned int addr; + unsigned int gmc_bits; + unsigned int layer_nr; + bool fmt_rgb565_is_0; + bool smi_id_en; +}; + +/* + * struct mtk_disp_ovl - DISP_OVL driver structure + * @crtc: associated crtc to report vblank events to + * @data: platform data + */ +struct mtk_disp_ovl { + struct drm_crtc *crtc; + struct clk *clk; + void __iomem *regs; + struct cmdq_client_reg cmdq_reg; + const struct mtk_disp_ovl_data *data; + void (*vblank_cb)(void *data); + void *vblank_cb_data; +}; + +static irqreturn_t mtk_disp_ovl_irq_handler(int irq, void *dev_id) +{ + struct mtk_disp_ovl *priv = dev_id; + + /* Clear frame completion interrupt */ + writel(0x0, priv->regs + DISP_REG_OVL_INTSTA); + + if (!priv->vblank_cb) + return IRQ_NONE; + + priv->vblank_cb(priv->vblank_cb_data); + + return IRQ_HANDLED; +} + +void mtk_ovl_register_vblank_cb(struct device *dev, + void (*vblank_cb)(void *), + void *vblank_cb_data) +{ + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + + ovl->vblank_cb = vblank_cb; + ovl->vblank_cb_data = vblank_cb_data; +} + +void mtk_ovl_unregister_vblank_cb(struct device *dev) +{ + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + + ovl->vblank_cb = NULL; + ovl->vblank_cb_data = NULL; +} + +void mtk_ovl_enable_vblank(struct device *dev) +{ + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + + writel(0x0, ovl->regs + DISP_REG_OVL_INTSTA); + writel_relaxed(OVL_FME_CPL_INT, ovl->regs + DISP_REG_OVL_INTEN); +} + +void mtk_ovl_disable_vblank(struct device *dev) +{ + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + + writel_relaxed(0x0, ovl->regs + DISP_REG_OVL_INTEN); +} + +int mtk_ovl_clk_enable(struct device *dev) +{ + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + + return clk_prepare_enable(ovl->clk); +} + +void mtk_ovl_clk_disable(struct device *dev) +{ + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + + clk_disable_unprepare(ovl->clk); +} + +void mtk_ovl_start(struct device *dev) +{ + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + + if (ovl->data->smi_id_en) { + unsigned int reg; + + reg = readl(ovl->regs + DISP_REG_OVL_DATAPATH_CON); + reg = reg | OVL_LAYER_SMI_ID_EN; + writel_relaxed(reg, ovl->regs + DISP_REG_OVL_DATAPATH_CON); + } + writel_relaxed(0x1, ovl->regs + DISP_REG_OVL_EN); +} + +void mtk_ovl_stop(struct device *dev) +{ + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + + writel_relaxed(0x0, ovl->regs + DISP_REG_OVL_EN); + if (ovl->data->smi_id_en) { + unsigned int reg; + + reg = readl(ovl->regs + DISP_REG_OVL_DATAPATH_CON); + reg = reg & ~OVL_LAYER_SMI_ID_EN; + writel_relaxed(reg, ovl->regs + DISP_REG_OVL_DATAPATH_CON); + } + +} + +void mtk_ovl_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + + if (w != 0 && h != 0) + mtk_ddp_write_relaxed(cmdq_pkt, h << 16 | w, &ovl->cmdq_reg, ovl->regs, + DISP_REG_OVL_ROI_SIZE); + mtk_ddp_write_relaxed(cmdq_pkt, 0x0, &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_ROI_BGCLR); + + mtk_ddp_write(cmdq_pkt, 0x1, &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_RST); + mtk_ddp_write(cmdq_pkt, 0x0, &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_RST); +} + +unsigned int mtk_ovl_layer_nr(struct device *dev) +{ + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + + return ovl->data->layer_nr; +} + +unsigned int mtk_ovl_supported_rotations(struct device *dev) +{ + return DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180 | + DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y; +} + +int mtk_ovl_layer_check(struct device *dev, unsigned int idx, + struct mtk_plane_state *mtk_state) +{ + struct drm_plane_state *state = &mtk_state->base; + unsigned int rotation = 0; + + rotation = drm_rotation_simplify(state->rotation, + DRM_MODE_ROTATE_0 | + DRM_MODE_REFLECT_X | + DRM_MODE_REFLECT_Y); + rotation &= ~DRM_MODE_ROTATE_0; + + /* We can only do reflection, not rotation */ + if ((rotation & DRM_MODE_ROTATE_MASK) != 0) + return -EINVAL; + + /* + * TODO: Rotating/reflecting YUV buffers is not supported at this time. + * Only RGB[AX] variants are supported. + */ + if (state->fb->format->is_yuv && rotation != 0) + return -EINVAL; + + state->rotation = rotation; + + return 0; +} + +void mtk_ovl_layer_on(struct device *dev, unsigned int idx, + struct cmdq_pkt *cmdq_pkt) +{ + unsigned int gmc_thrshd_l; + unsigned int gmc_thrshd_h; + unsigned int gmc_value; + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + + mtk_ddp_write(cmdq_pkt, 0x1, &ovl->cmdq_reg, ovl->regs, + DISP_REG_OVL_RDMA_CTRL(idx)); + gmc_thrshd_l = GMC_THRESHOLD_LOW >> + (GMC_THRESHOLD_BITS - ovl->data->gmc_bits); + gmc_thrshd_h = GMC_THRESHOLD_HIGH >> + (GMC_THRESHOLD_BITS - ovl->data->gmc_bits); + if (ovl->data->gmc_bits == 10) + gmc_value = gmc_thrshd_h | gmc_thrshd_h << 16; + else + gmc_value = gmc_thrshd_l | gmc_thrshd_l << 8 | + gmc_thrshd_h << 16 | gmc_thrshd_h << 24; + mtk_ddp_write(cmdq_pkt, gmc_value, + &ovl->cmdq_reg, ovl->regs, DISP_REG_OVL_RDMA_GMC(idx)); + mtk_ddp_write_mask(cmdq_pkt, BIT(idx), &ovl->cmdq_reg, ovl->regs, + DISP_REG_OVL_SRC_CON, BIT(idx)); +} + +void mtk_ovl_layer_off(struct device *dev, unsigned int idx, + struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + + mtk_ddp_write_mask(cmdq_pkt, 0, &ovl->cmdq_reg, ovl->regs, + DISP_REG_OVL_SRC_CON, BIT(idx)); + mtk_ddp_write(cmdq_pkt, 0, &ovl->cmdq_reg, ovl->regs, + DISP_REG_OVL_RDMA_CTRL(idx)); +} + +static unsigned int ovl_fmt_convert(struct mtk_disp_ovl *ovl, unsigned int fmt) +{ + /* The return value in switch "MEM_MODE_INPUT_FORMAT_XXX" + * is defined in mediatek HW data sheet. + * The alphabet order in XXX is no relation to data + * arrangement in memory. + */ + switch (fmt) { + default: + case DRM_FORMAT_RGB565: + return OVL_CON_CLRFMT_RGB565(ovl); + case DRM_FORMAT_BGR565: + return OVL_CON_CLRFMT_RGB565(ovl) | OVL_CON_BYTE_SWAP; + case DRM_FORMAT_RGB888: + return OVL_CON_CLRFMT_RGB888(ovl); + case DRM_FORMAT_BGR888: + return OVL_CON_CLRFMT_RGB888(ovl) | OVL_CON_BYTE_SWAP; + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_RGBA8888: + return OVL_CON_CLRFMT_ARGB8888; + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_BGRA8888: + return OVL_CON_CLRFMT_ARGB8888 | OVL_CON_BYTE_SWAP; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + return OVL_CON_CLRFMT_RGBA8888; + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return OVL_CON_CLRFMT_RGBA8888 | OVL_CON_BYTE_SWAP; + case DRM_FORMAT_UYVY: + return OVL_CON_CLRFMT_UYVY | OVL_CON_MTX_YUV_TO_RGB; + case DRM_FORMAT_YUYV: + return OVL_CON_CLRFMT_YUYV | OVL_CON_MTX_YUV_TO_RGB; + } +} + +void mtk_ovl_layer_config(struct device *dev, unsigned int idx, + struct mtk_plane_state *state, + struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + struct mtk_plane_pending_state *pending = &state->pending; + unsigned int addr = pending->addr; + unsigned int pitch = pending->pitch & 0xffff; + unsigned int fmt = pending->format; + unsigned int offset = (pending->y << 16) | pending->x; + unsigned int src_size = (pending->height << 16) | pending->width; + unsigned int con; + + if (!pending->enable) { + mtk_ovl_layer_off(dev, idx, cmdq_pkt); + return; + } + + con = ovl_fmt_convert(ovl, fmt); + if (state->base.fb && state->base.fb->format->has_alpha) + con |= OVL_CON_AEN | OVL_CON_ALPHA; + + if (pending->rotation & DRM_MODE_REFLECT_Y) { + con |= OVL_CON_VIRT_FLIP; + addr += (pending->height - 1) * pending->pitch; + } + + if (pending->rotation & DRM_MODE_REFLECT_X) { + con |= OVL_CON_HORZ_FLIP; + addr += pending->pitch - 1; + } + + mtk_ddp_write_relaxed(cmdq_pkt, con, &ovl->cmdq_reg, ovl->regs, + DISP_REG_OVL_CON(idx)); + mtk_ddp_write_relaxed(cmdq_pkt, pitch, &ovl->cmdq_reg, ovl->regs, + DISP_REG_OVL_PITCH(idx)); + mtk_ddp_write_relaxed(cmdq_pkt, src_size, &ovl->cmdq_reg, ovl->regs, + DISP_REG_OVL_SRC_SIZE(idx)); + mtk_ddp_write_relaxed(cmdq_pkt, offset, &ovl->cmdq_reg, ovl->regs, + DISP_REG_OVL_OFFSET(idx)); + mtk_ddp_write_relaxed(cmdq_pkt, addr, &ovl->cmdq_reg, ovl->regs, + DISP_REG_OVL_ADDR(ovl, idx)); + + mtk_ovl_layer_on(dev, idx, cmdq_pkt); +} + +void mtk_ovl_bgclr_in_on(struct device *dev) +{ + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + unsigned int reg; + + reg = readl(ovl->regs + DISP_REG_OVL_DATAPATH_CON); + reg = reg | OVL_BGCLR_SEL_IN; + writel(reg, ovl->regs + DISP_REG_OVL_DATAPATH_CON); +} + +void mtk_ovl_bgclr_in_off(struct device *dev) +{ + struct mtk_disp_ovl *ovl = dev_get_drvdata(dev); + unsigned int reg; + + reg = readl(ovl->regs + DISP_REG_OVL_DATAPATH_CON); + reg = reg & ~OVL_BGCLR_SEL_IN; + writel(reg, ovl->regs + DISP_REG_OVL_DATAPATH_CON); +} + +static int mtk_disp_ovl_bind(struct device *dev, struct device *master, + void *data) +{ + return 0; +} + +static void mtk_disp_ovl_unbind(struct device *dev, struct device *master, + void *data) +{ +} + +static const struct component_ops mtk_disp_ovl_component_ops = { + .bind = mtk_disp_ovl_bind, + .unbind = mtk_disp_ovl_unbind, +}; + +static int mtk_disp_ovl_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_disp_ovl *priv; + struct resource *res; + int irq; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "failed to get ovl clk\n"); + return PTR_ERR(priv->clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->regs)) { + dev_err(dev, "failed to ioremap ovl\n"); + return PTR_ERR(priv->regs); + } +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + ret = cmdq_dev_get_client_reg(dev, &priv->cmdq_reg, 0); + if (ret) + dev_dbg(dev, "get mediatek,gce-client-reg fail!\n"); +#endif + + priv->data = of_device_get_match_data(dev); + platform_set_drvdata(pdev, priv); + + ret = devm_request_irq(dev, irq, mtk_disp_ovl_irq_handler, + IRQF_TRIGGER_NONE, dev_name(dev), priv); + if (ret < 0) { + dev_err(dev, "Failed to request irq %d: %d\n", irq, ret); + return ret; + } + + pm_runtime_enable(dev); + + ret = component_add(dev, &mtk_disp_ovl_component_ops); + if (ret) { + pm_runtime_disable(dev); + dev_err(dev, "Failed to add component: %d\n", ret); + } + + return ret; +} + +static int mtk_disp_ovl_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &mtk_disp_ovl_component_ops); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct mtk_disp_ovl_data mt2701_ovl_driver_data = { + .addr = DISP_REG_OVL_ADDR_MT2701, + .gmc_bits = 8, + .layer_nr = 4, + .fmt_rgb565_is_0 = false, +}; + +static const struct mtk_disp_ovl_data mt8173_ovl_driver_data = { + .addr = DISP_REG_OVL_ADDR_MT8173, + .gmc_bits = 8, + .layer_nr = 4, + .fmt_rgb565_is_0 = true, +}; + +static const struct mtk_disp_ovl_data mt8183_ovl_driver_data = { + .addr = DISP_REG_OVL_ADDR_MT8173, + .gmc_bits = 10, + .layer_nr = 4, + .fmt_rgb565_is_0 = true, +}; + +static const struct mtk_disp_ovl_data mt8183_ovl_2l_driver_data = { + .addr = DISP_REG_OVL_ADDR_MT8173, + .gmc_bits = 10, + .layer_nr = 2, + .fmt_rgb565_is_0 = true, +}; + +static const struct mtk_disp_ovl_data mt8192_ovl_driver_data = { + .addr = DISP_REG_OVL_ADDR_MT8173, + .gmc_bits = 10, + .layer_nr = 4, + .fmt_rgb565_is_0 = true, + .smi_id_en = true, +}; + +static const struct mtk_disp_ovl_data mt8192_ovl_2l_driver_data = { + .addr = DISP_REG_OVL_ADDR_MT8173, + .gmc_bits = 10, + .layer_nr = 2, + .fmt_rgb565_is_0 = true, + .smi_id_en = true, +}; + +static const struct of_device_id mtk_disp_ovl_driver_dt_match[] = { + { .compatible = "mediatek,mt2701-disp-ovl", + .data = &mt2701_ovl_driver_data}, + { .compatible = "mediatek,mt8173-disp-ovl", + .data = &mt8173_ovl_driver_data}, + { .compatible = "mediatek,mt8183-disp-ovl", + .data = &mt8183_ovl_driver_data}, + { .compatible = "mediatek,mt8183-disp-ovl-2l", + .data = &mt8183_ovl_2l_driver_data}, + { .compatible = "mediatek,mt8192-disp-ovl", + .data = &mt8192_ovl_driver_data}, + { .compatible = "mediatek,mt8192-disp-ovl-2l", + .data = &mt8192_ovl_2l_driver_data}, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_disp_ovl_driver_dt_match); + +struct platform_driver mtk_disp_ovl_driver = { + .probe = mtk_disp_ovl_probe, + .remove = mtk_disp_ovl_remove, + .driver = { + .name = "mediatek-disp-ovl", + .owner = THIS_MODULE, + .of_match_table = mtk_disp_ovl_driver_dt_match, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_disp_rdma.c b/drivers/gpu/drm/mediatek/mtk_disp_rdma.c new file mode 100644 index 000000000..0ec2e4049 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_disp_rdma.c @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 MediaTek Inc. + */ + +#include <drm/drm_fourcc.h> + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/soc/mediatek/mtk-cmdq.h> + +#include "mtk_disp_drv.h" +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp_comp.h" + +#define DISP_REG_RDMA_INT_ENABLE 0x0000 +#define DISP_REG_RDMA_INT_STATUS 0x0004 +#define RDMA_TARGET_LINE_INT BIT(5) +#define RDMA_FIFO_UNDERFLOW_INT BIT(4) +#define RDMA_EOF_ABNORMAL_INT BIT(3) +#define RDMA_FRAME_END_INT BIT(2) +#define RDMA_FRAME_START_INT BIT(1) +#define RDMA_REG_UPDATE_INT BIT(0) +#define DISP_REG_RDMA_GLOBAL_CON 0x0010 +#define RDMA_ENGINE_EN BIT(0) +#define RDMA_MODE_MEMORY BIT(1) +#define DISP_REG_RDMA_SIZE_CON_0 0x0014 +#define RDMA_MATRIX_ENABLE BIT(17) +#define RDMA_MATRIX_INT_MTX_SEL GENMASK(23, 20) +#define RDMA_MATRIX_INT_MTX_BT601_to_RGB (6 << 20) +#define DISP_REG_RDMA_SIZE_CON_1 0x0018 +#define DISP_REG_RDMA_TARGET_LINE 0x001c +#define DISP_RDMA_MEM_CON 0x0024 +#define MEM_MODE_INPUT_FORMAT_RGB565 (0x000 << 4) +#define MEM_MODE_INPUT_FORMAT_RGB888 (0x001 << 4) +#define MEM_MODE_INPUT_FORMAT_RGBA8888 (0x002 << 4) +#define MEM_MODE_INPUT_FORMAT_ARGB8888 (0x003 << 4) +#define MEM_MODE_INPUT_FORMAT_UYVY (0x004 << 4) +#define MEM_MODE_INPUT_FORMAT_YUYV (0x005 << 4) +#define MEM_MODE_INPUT_SWAP BIT(8) +#define DISP_RDMA_MEM_SRC_PITCH 0x002c +#define DISP_RDMA_MEM_GMC_SETTING_0 0x0030 +#define DISP_REG_RDMA_FIFO_CON 0x0040 +#define RDMA_FIFO_UNDERFLOW_EN BIT(31) +#define RDMA_FIFO_PSEUDO_SIZE(bytes) (((bytes) / 16) << 16) +#define RDMA_OUTPUT_VALID_FIFO_THRESHOLD(bytes) ((bytes) / 16) +#define RDMA_FIFO_SIZE(rdma) ((rdma)->data->fifo_size) +#define DISP_RDMA_MEM_START_ADDR 0x0f00 + +#define RDMA_MEM_GMC 0x40402020 + +struct mtk_disp_rdma_data { + unsigned int fifo_size; +}; + +/* + * struct mtk_disp_rdma - DISP_RDMA driver structure + * @data: local driver data + */ +struct mtk_disp_rdma { + struct clk *clk; + void __iomem *regs; + struct cmdq_client_reg cmdq_reg; + const struct mtk_disp_rdma_data *data; + void (*vblank_cb)(void *data); + void *vblank_cb_data; + u32 fifo_size; +}; + +static irqreturn_t mtk_disp_rdma_irq_handler(int irq, void *dev_id) +{ + struct mtk_disp_rdma *priv = dev_id; + + /* Clear frame completion interrupt */ + writel(0x0, priv->regs + DISP_REG_RDMA_INT_STATUS); + + if (!priv->vblank_cb) + return IRQ_NONE; + + priv->vblank_cb(priv->vblank_cb_data); + + return IRQ_HANDLED; +} + +static void rdma_update_bits(struct device *dev, unsigned int reg, + unsigned int mask, unsigned int val) +{ + struct mtk_disp_rdma *rdma = dev_get_drvdata(dev); + unsigned int tmp = readl(rdma->regs + reg); + + tmp = (tmp & ~mask) | (val & mask); + writel(tmp, rdma->regs + reg); +} + +void mtk_rdma_register_vblank_cb(struct device *dev, + void (*vblank_cb)(void *), + void *vblank_cb_data) +{ + struct mtk_disp_rdma *rdma = dev_get_drvdata(dev); + + rdma->vblank_cb = vblank_cb; + rdma->vblank_cb_data = vblank_cb_data; +} + +void mtk_rdma_unregister_vblank_cb(struct device *dev) +{ + struct mtk_disp_rdma *rdma = dev_get_drvdata(dev); + + rdma->vblank_cb = NULL; + rdma->vblank_cb_data = NULL; +} + +void mtk_rdma_enable_vblank(struct device *dev) +{ + rdma_update_bits(dev, DISP_REG_RDMA_INT_ENABLE, RDMA_FRAME_END_INT, + RDMA_FRAME_END_INT); +} + +void mtk_rdma_disable_vblank(struct device *dev) +{ + rdma_update_bits(dev, DISP_REG_RDMA_INT_ENABLE, RDMA_FRAME_END_INT, 0); +} + +int mtk_rdma_clk_enable(struct device *dev) +{ + struct mtk_disp_rdma *rdma = dev_get_drvdata(dev); + + return clk_prepare_enable(rdma->clk); +} + +void mtk_rdma_clk_disable(struct device *dev) +{ + struct mtk_disp_rdma *rdma = dev_get_drvdata(dev); + + clk_disable_unprepare(rdma->clk); +} + +void mtk_rdma_start(struct device *dev) +{ + rdma_update_bits(dev, DISP_REG_RDMA_GLOBAL_CON, RDMA_ENGINE_EN, + RDMA_ENGINE_EN); +} + +void mtk_rdma_stop(struct device *dev) +{ + rdma_update_bits(dev, DISP_REG_RDMA_GLOBAL_CON, RDMA_ENGINE_EN, 0); +} + +void mtk_rdma_config(struct device *dev, unsigned int width, + unsigned int height, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + unsigned int threshold; + unsigned int reg; + struct mtk_disp_rdma *rdma = dev_get_drvdata(dev); + u32 rdma_fifo_size; + + mtk_ddp_write_mask(cmdq_pkt, width, &rdma->cmdq_reg, rdma->regs, + DISP_REG_RDMA_SIZE_CON_0, 0xfff); + mtk_ddp_write_mask(cmdq_pkt, height, &rdma->cmdq_reg, rdma->regs, + DISP_REG_RDMA_SIZE_CON_1, 0xfffff); + + if (rdma->fifo_size) + rdma_fifo_size = rdma->fifo_size; + else + rdma_fifo_size = RDMA_FIFO_SIZE(rdma); + + /* + * Enable FIFO underflow since DSI and DPI can't be blocked. + * Keep the FIFO pseudo size reset default of 8 KiB. Set the + * output threshold to 70% of max fifo size to make sure the + * threhold will not overflow + */ + threshold = rdma_fifo_size * 7 / 10; + reg = RDMA_FIFO_UNDERFLOW_EN | + RDMA_FIFO_PSEUDO_SIZE(rdma_fifo_size) | + RDMA_OUTPUT_VALID_FIFO_THRESHOLD(threshold); + mtk_ddp_write(cmdq_pkt, reg, &rdma->cmdq_reg, rdma->regs, DISP_REG_RDMA_FIFO_CON); +} + +static unsigned int rdma_fmt_convert(struct mtk_disp_rdma *rdma, + unsigned int fmt) +{ + /* The return value in switch "MEM_MODE_INPUT_FORMAT_XXX" + * is defined in mediatek HW data sheet. + * The alphabet order in XXX is no relation to data + * arrangement in memory. + */ + switch (fmt) { + default: + case DRM_FORMAT_RGB565: + return MEM_MODE_INPUT_FORMAT_RGB565; + case DRM_FORMAT_BGR565: + return MEM_MODE_INPUT_FORMAT_RGB565 | MEM_MODE_INPUT_SWAP; + case DRM_FORMAT_RGB888: + return MEM_MODE_INPUT_FORMAT_RGB888; + case DRM_FORMAT_BGR888: + return MEM_MODE_INPUT_FORMAT_RGB888 | MEM_MODE_INPUT_SWAP; + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_RGBA8888: + return MEM_MODE_INPUT_FORMAT_ARGB8888; + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_BGRA8888: + return MEM_MODE_INPUT_FORMAT_ARGB8888 | MEM_MODE_INPUT_SWAP; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + return MEM_MODE_INPUT_FORMAT_RGBA8888; + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return MEM_MODE_INPUT_FORMAT_RGBA8888 | MEM_MODE_INPUT_SWAP; + case DRM_FORMAT_UYVY: + return MEM_MODE_INPUT_FORMAT_UYVY; + case DRM_FORMAT_YUYV: + return MEM_MODE_INPUT_FORMAT_YUYV; + } +} + +unsigned int mtk_rdma_layer_nr(struct device *dev) +{ + return 1; +} + +void mtk_rdma_layer_config(struct device *dev, unsigned int idx, + struct mtk_plane_state *state, + struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_rdma *rdma = dev_get_drvdata(dev); + struct mtk_plane_pending_state *pending = &state->pending; + unsigned int addr = pending->addr; + unsigned int pitch = pending->pitch & 0xffff; + unsigned int fmt = pending->format; + unsigned int con; + + con = rdma_fmt_convert(rdma, fmt); + mtk_ddp_write_relaxed(cmdq_pkt, con, &rdma->cmdq_reg, rdma->regs, DISP_RDMA_MEM_CON); + + if (fmt == DRM_FORMAT_UYVY || fmt == DRM_FORMAT_YUYV) { + mtk_ddp_write_mask(cmdq_pkt, RDMA_MATRIX_ENABLE, &rdma->cmdq_reg, rdma->regs, + DISP_REG_RDMA_SIZE_CON_0, + RDMA_MATRIX_ENABLE); + mtk_ddp_write_mask(cmdq_pkt, RDMA_MATRIX_INT_MTX_BT601_to_RGB, + &rdma->cmdq_reg, rdma->regs, DISP_REG_RDMA_SIZE_CON_0, + RDMA_MATRIX_INT_MTX_SEL); + } else { + mtk_ddp_write_mask(cmdq_pkt, 0, &rdma->cmdq_reg, rdma->regs, + DISP_REG_RDMA_SIZE_CON_0, + RDMA_MATRIX_ENABLE); + } + mtk_ddp_write_relaxed(cmdq_pkt, addr, &rdma->cmdq_reg, rdma->regs, + DISP_RDMA_MEM_START_ADDR); + mtk_ddp_write_relaxed(cmdq_pkt, pitch, &rdma->cmdq_reg, rdma->regs, + DISP_RDMA_MEM_SRC_PITCH); + mtk_ddp_write(cmdq_pkt, RDMA_MEM_GMC, &rdma->cmdq_reg, rdma->regs, + DISP_RDMA_MEM_GMC_SETTING_0); + mtk_ddp_write_mask(cmdq_pkt, RDMA_MODE_MEMORY, &rdma->cmdq_reg, rdma->regs, + DISP_REG_RDMA_GLOBAL_CON, RDMA_MODE_MEMORY); + +} + +static int mtk_disp_rdma_bind(struct device *dev, struct device *master, + void *data) +{ + return 0; + +} + +static void mtk_disp_rdma_unbind(struct device *dev, struct device *master, + void *data) +{ +} + +static const struct component_ops mtk_disp_rdma_component_ops = { + .bind = mtk_disp_rdma_bind, + .unbind = mtk_disp_rdma_unbind, +}; + +static int mtk_disp_rdma_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_disp_rdma *priv; + struct resource *res; + int irq; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "failed to get rdma clk\n"); + return PTR_ERR(priv->clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->regs)) { + dev_err(dev, "failed to ioremap rdma\n"); + return PTR_ERR(priv->regs); + } +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + ret = cmdq_dev_get_client_reg(dev, &priv->cmdq_reg, 0); + if (ret) + dev_dbg(dev, "get mediatek,gce-client-reg fail!\n"); +#endif + + if (of_find_property(dev->of_node, "mediatek,rdma-fifo-size", &ret)) { + ret = of_property_read_u32(dev->of_node, + "mediatek,rdma-fifo-size", + &priv->fifo_size); + if (ret) { + dev_err(dev, "Failed to get rdma fifo size\n"); + return ret; + } + } + + /* Disable and clear pending interrupts */ + writel(0x0, priv->regs + DISP_REG_RDMA_INT_ENABLE); + writel(0x0, priv->regs + DISP_REG_RDMA_INT_STATUS); + + ret = devm_request_irq(dev, irq, mtk_disp_rdma_irq_handler, + IRQF_TRIGGER_NONE, dev_name(dev), priv); + if (ret < 0) { + dev_err(dev, "Failed to request irq %d: %d\n", irq, ret); + return ret; + } + + priv->data = of_device_get_match_data(dev); + + platform_set_drvdata(pdev, priv); + + pm_runtime_enable(dev); + + ret = component_add(dev, &mtk_disp_rdma_component_ops); + if (ret) { + pm_runtime_disable(dev); + dev_err(dev, "Failed to add component: %d\n", ret); + } + + return ret; +} + +static int mtk_disp_rdma_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &mtk_disp_rdma_component_ops); + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct mtk_disp_rdma_data mt2701_rdma_driver_data = { + .fifo_size = SZ_4K, +}; + +static const struct mtk_disp_rdma_data mt8173_rdma_driver_data = { + .fifo_size = SZ_8K, +}; + +static const struct mtk_disp_rdma_data mt8183_rdma_driver_data = { + .fifo_size = 5 * SZ_1K, +}; + +static const struct mtk_disp_rdma_data mt8195_rdma_driver_data = { + .fifo_size = 1920, +}; + +static const struct of_device_id mtk_disp_rdma_driver_dt_match[] = { + { .compatible = "mediatek,mt2701-disp-rdma", + .data = &mt2701_rdma_driver_data}, + { .compatible = "mediatek,mt8173-disp-rdma", + .data = &mt8173_rdma_driver_data}, + { .compatible = "mediatek,mt8183-disp-rdma", + .data = &mt8183_rdma_driver_data}, + { .compatible = "mediatek,mt8195-disp-rdma", + .data = &mt8195_rdma_driver_data}, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_disp_rdma_driver_dt_match); + +struct platform_driver mtk_disp_rdma_driver = { + .probe = mtk_disp_rdma_probe, + .remove = mtk_disp_rdma_remove, + .driver = { + .name = "mediatek-disp-rdma", + .owner = THIS_MODULE, + .of_match_table = mtk_disp_rdma_driver_dt_match, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_dp.c b/drivers/gpu/drm/mediatek/mtk_dp.c new file mode 100644 index 000000000..519e23a2a --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dp.c @@ -0,0 +1,2672 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019-2022 MediaTek Inc. + * Copyright (c) 2022 BayLibre + */ + +#include <drm/display/drm_dp.h> +#include <drm/display/drm_dp_helper.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_edid.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <linux/arm-smccc.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/media-bus-format.h> +#include <linux/nvmem-consumer.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/soc/mediatek/mtk_sip_svc.h> +#include <sound/hdmi-codec.h> +#include <video/videomode.h> + +#include "mtk_dp_reg.h" + +#define MTK_DP_SIP_CONTROL_AARCH32 MTK_SIP_SMC_CMD(0x523) +#define MTK_DP_SIP_ATF_EDP_VIDEO_UNMUTE (BIT(0) | BIT(5)) +#define MTK_DP_SIP_ATF_VIDEO_UNMUTE BIT(5) + +#define MTK_DP_THREAD_CABLE_STATE_CHG BIT(0) +#define MTK_DP_THREAD_HPD_EVENT BIT(1) + +#define MTK_DP_4P1T 4 +#define MTK_DP_HDE 2 +#define MTK_DP_PIX_PER_ADDR 2 +#define MTK_DP_AUX_WAIT_REPLY_COUNT 20 +#define MTK_DP_TBC_BUF_READ_START_ADDR 0x8 +#define MTK_DP_TRAIN_VOLTAGE_LEVEL_RETRY 5 +#define MTK_DP_TRAIN_DOWNSCALE_RETRY 10 +#define MTK_DP_VERSION 0x11 +#define MTK_DP_SDP_AUI 0x4 + +enum { + MTK_DP_CAL_GLB_BIAS_TRIM = 0, + MTK_DP_CAL_CLKTX_IMPSE, + MTK_DP_CAL_LN_TX_IMPSEL_PMOS_0, + MTK_DP_CAL_LN_TX_IMPSEL_PMOS_1, + MTK_DP_CAL_LN_TX_IMPSEL_PMOS_2, + MTK_DP_CAL_LN_TX_IMPSEL_PMOS_3, + MTK_DP_CAL_LN_TX_IMPSEL_NMOS_0, + MTK_DP_CAL_LN_TX_IMPSEL_NMOS_1, + MTK_DP_CAL_LN_TX_IMPSEL_NMOS_2, + MTK_DP_CAL_LN_TX_IMPSEL_NMOS_3, + MTK_DP_CAL_MAX, +}; + +struct mtk_dp_train_info { + bool sink_ssc; + bool cable_plugged_in; + /* link_rate is in multiple of 0.27Gbps */ + int link_rate; + int lane_count; + unsigned int channel_eq_pattern; +}; + +struct mtk_dp_audio_cfg { + bool detect_monitor; + int sad_count; + int sample_rate; + int word_length_bits; + int channels; +}; + +struct mtk_dp_info { + enum dp_pixelformat format; + struct videomode vm; + struct mtk_dp_audio_cfg audio_cur_cfg; +}; + +struct mtk_dp_efuse_fmt { + unsigned short idx; + unsigned short shift; + unsigned short mask; + unsigned short min_val; + unsigned short max_val; + unsigned short default_val; +}; + +struct mtk_dp { + bool enabled; + bool need_debounce; + u8 max_lanes; + u8 max_linkrate; + u8 rx_cap[DP_RECEIVER_CAP_SIZE]; + u32 cal_data[MTK_DP_CAL_MAX]; + u32 irq_thread_handle; + /* irq_thread_lock is used to protect irq_thread_handle */ + spinlock_t irq_thread_lock; + + struct device *dev; + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + struct drm_connector *conn; + struct drm_device *drm_dev; + struct drm_dp_aux aux; + + const struct mtk_dp_data *data; + struct mtk_dp_info info; + struct mtk_dp_train_info train_info; + + struct platform_device *phy_dev; + struct phy *phy; + struct regmap *regs; + struct timer_list debounce_timer; + + /* For audio */ + bool audio_enable; + hdmi_codec_plugged_cb plugged_cb; + struct platform_device *audio_pdev; + + struct device *codec_dev; + /* protect the plugged_cb as it's used in both bridge ops and audio */ + struct mutex update_plugged_status_lock; +}; + +struct mtk_dp_data { + int bridge_type; + unsigned int smc_cmd; + const struct mtk_dp_efuse_fmt *efuse_fmt; + bool audio_supported; +}; + +static const struct mtk_dp_efuse_fmt mt8195_edp_efuse_fmt[MTK_DP_CAL_MAX] = { + [MTK_DP_CAL_GLB_BIAS_TRIM] = { + .idx = 3, + .shift = 27, + .mask = 0x1f, + .min_val = 1, + .max_val = 0x1e, + .default_val = 0xf, + }, + [MTK_DP_CAL_CLKTX_IMPSE] = { + .idx = 0, + .shift = 9, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_PMOS_0] = { + .idx = 2, + .shift = 28, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_PMOS_1] = { + .idx = 2, + .shift = 20, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_PMOS_2] = { + .idx = 2, + .shift = 12, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_PMOS_3] = { + .idx = 2, + .shift = 4, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_NMOS_0] = { + .idx = 2, + .shift = 24, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_NMOS_1] = { + .idx = 2, + .shift = 16, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_NMOS_2] = { + .idx = 2, + .shift = 8, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_NMOS_3] = { + .idx = 2, + .shift = 0, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, +}; + +static const struct mtk_dp_efuse_fmt mt8195_dp_efuse_fmt[MTK_DP_CAL_MAX] = { + [MTK_DP_CAL_GLB_BIAS_TRIM] = { + .idx = 0, + .shift = 27, + .mask = 0x1f, + .min_val = 1, + .max_val = 0x1e, + .default_val = 0xf, + }, + [MTK_DP_CAL_CLKTX_IMPSE] = { + .idx = 0, + .shift = 13, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_PMOS_0] = { + .idx = 1, + .shift = 28, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_PMOS_1] = { + .idx = 1, + .shift = 20, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_PMOS_2] = { + .idx = 1, + .shift = 12, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_PMOS_3] = { + .idx = 1, + .shift = 4, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_NMOS_0] = { + .idx = 1, + .shift = 24, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_NMOS_1] = { + .idx = 1, + .shift = 16, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_NMOS_2] = { + .idx = 1, + .shift = 8, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, + [MTK_DP_CAL_LN_TX_IMPSEL_NMOS_3] = { + .idx = 1, + .shift = 0, + .mask = 0xf, + .min_val = 1, + .max_val = 0xe, + .default_val = 0x8, + }, +}; + +static struct regmap_config mtk_dp_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = SEC_OFFSET + 0x90, + .name = "mtk-dp-registers", +}; + +static struct mtk_dp *mtk_dp_from_bridge(struct drm_bridge *b) +{ + return container_of(b, struct mtk_dp, bridge); +} + +static u32 mtk_dp_read(struct mtk_dp *mtk_dp, u32 offset) +{ + u32 read_val; + int ret; + + ret = regmap_read(mtk_dp->regs, offset, &read_val); + if (ret) { + dev_err(mtk_dp->dev, "Failed to read register 0x%x: %d\n", + offset, ret); + return 0; + } + + return read_val; +} + +static int mtk_dp_write(struct mtk_dp *mtk_dp, u32 offset, u32 val) +{ + int ret = regmap_write(mtk_dp->regs, offset, val); + + if (ret) + dev_err(mtk_dp->dev, + "Failed to write register 0x%x with value 0x%x\n", + offset, val); + return ret; +} + +static int mtk_dp_update_bits(struct mtk_dp *mtk_dp, u32 offset, + u32 val, u32 mask) +{ + int ret = regmap_update_bits(mtk_dp->regs, offset, mask, val); + + if (ret) + dev_err(mtk_dp->dev, + "Failed to update register 0x%x with value 0x%x, mask 0x%x\n", + offset, val, mask); + return ret; +} + +static void mtk_dp_bulk_16bit_write(struct mtk_dp *mtk_dp, u32 offset, u8 *buf, + size_t length) +{ + int i; + + /* 2 bytes per register */ + for (i = 0; i < length; i += 2) { + u32 val = buf[i] | (i + 1 < length ? buf[i + 1] << 8 : 0); + + if (mtk_dp_write(mtk_dp, offset + i * 2, val)) + return; + } +} + +static void mtk_dp_msa_bypass_enable(struct mtk_dp *mtk_dp, bool enable) +{ + u32 mask = HTOTAL_SEL_DP_ENC0_P0 | VTOTAL_SEL_DP_ENC0_P0 | + HSTART_SEL_DP_ENC0_P0 | VSTART_SEL_DP_ENC0_P0 | + HWIDTH_SEL_DP_ENC0_P0 | VHEIGHT_SEL_DP_ENC0_P0 | + HSP_SEL_DP_ENC0_P0 | HSW_SEL_DP_ENC0_P0 | + VSP_SEL_DP_ENC0_P0 | VSW_SEL_DP_ENC0_P0; + + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3030, enable ? 0 : mask, mask); +} + +static void mtk_dp_set_msa(struct mtk_dp *mtk_dp) +{ + struct drm_display_mode mode; + struct videomode *vm = &mtk_dp->info.vm; + + drm_display_mode_from_videomode(vm, &mode); + + /* horizontal */ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3010, + mode.htotal, HTOTAL_SW_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3018, + vm->hsync_len + vm->hback_porch, + HSTART_SW_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3028, + vm->hsync_len, HSW_SW_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3028, + 0, HSP_SW_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3020, + vm->hactive, HWIDTH_SW_DP_ENC0_P0_MASK); + + /* vertical */ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3014, + mode.vtotal, VTOTAL_SW_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_301C, + vm->vsync_len + vm->vback_porch, + VSTART_SW_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_302C, + vm->vsync_len, VSW_SW_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_302C, + 0, VSP_SW_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3024, + vm->vactive, VHEIGHT_SW_DP_ENC0_P0_MASK); + + /* horizontal */ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3064, + vm->hactive, HDE_NUM_LAST_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3154, + mode.htotal, PGEN_HTOTAL_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3158, + vm->hfront_porch, + PGEN_HSYNC_RISING_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_315C, + vm->hsync_len, + PGEN_HSYNC_PULSE_WIDTH_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3160, + vm->hback_porch + vm->hsync_len, + PGEN_HFDE_START_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3164, + vm->hactive, + PGEN_HFDE_ACTIVE_WIDTH_DP_ENC0_P0_MASK); + + /* vertical */ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3168, + mode.vtotal, + PGEN_VTOTAL_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_316C, + vm->vfront_porch, + PGEN_VSYNC_RISING_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3170, + vm->vsync_len, + PGEN_VSYNC_PULSE_WIDTH_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3174, + vm->vback_porch + vm->vsync_len, + PGEN_VFDE_START_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3178, + vm->vactive, + PGEN_VFDE_ACTIVE_WIDTH_DP_ENC0_P0_MASK); +} + +static int mtk_dp_set_color_format(struct mtk_dp *mtk_dp, + enum dp_pixelformat color_format) +{ + u32 val; + + /* update MISC0 */ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3034, + color_format << DP_TEST_COLOR_FORMAT_SHIFT, + DP_TEST_COLOR_FORMAT_MASK); + + switch (color_format) { + case DP_PIXELFORMAT_YUV422: + val = PIXEL_ENCODE_FORMAT_DP_ENC0_P0_YCBCR422; + break; + case DP_PIXELFORMAT_RGB: + val = PIXEL_ENCODE_FORMAT_DP_ENC0_P0_RGB; + break; + default: + drm_warn(mtk_dp->drm_dev, "Unsupported color format: %d\n", + color_format); + return -EINVAL; + } + + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_303C, + val, PIXEL_ENCODE_FORMAT_DP_ENC0_P0_MASK); + return 0; +} + +static void mtk_dp_set_color_depth(struct mtk_dp *mtk_dp) +{ + /* Only support 8 bits currently */ + /* Update MISC0 */ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3034, + DP_MSA_MISC_8_BPC, DP_TEST_BIT_DEPTH_MASK); + + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_303C, + VIDEO_COLOR_DEPTH_DP_ENC0_P0_8BIT, + VIDEO_COLOR_DEPTH_DP_ENC0_P0_MASK); +} + +static void mtk_dp_config_mn_mode(struct mtk_dp *mtk_dp) +{ + /* 0: hw mode, 1: sw mode */ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3004, + 0, VIDEO_M_CODE_SEL_DP_ENC0_P0_MASK); +} + +static void mtk_dp_set_sram_read_start(struct mtk_dp *mtk_dp, u32 val) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_303C, + val, SRAM_START_READ_THRD_DP_ENC0_P0_MASK); +} + +static void mtk_dp_setup_encoder(struct mtk_dp *mtk_dp) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_303C, + VIDEO_MN_GEN_EN_DP_ENC0_P0, + VIDEO_MN_GEN_EN_DP_ENC0_P0); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3040, + SDP_DOWN_CNT_DP_ENC0_P0_VAL, + SDP_DOWN_CNT_INIT_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC1_P0_3364, + SDP_DOWN_CNT_IN_HBLANK_DP_ENC1_P0_VAL, + SDP_DOWN_CNT_INIT_IN_HBLANK_DP_ENC1_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC1_P0_3300, + VIDEO_AFIFO_RDY_SEL_DP_ENC1_P0_VAL << 8, + VIDEO_AFIFO_RDY_SEL_DP_ENC1_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC1_P0_3364, + FIFO_READ_START_POINT_DP_ENC1_P0_VAL << 12, + FIFO_READ_START_POINT_DP_ENC1_P0_MASK); + mtk_dp_write(mtk_dp, MTK_DP_ENC1_P0_3368, DP_ENC1_P0_3368_VAL); +} + +static void mtk_dp_pg_enable(struct mtk_dp *mtk_dp, bool enable) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3038, + enable ? VIDEO_SOURCE_SEL_DP_ENC0_P0_MASK : 0, + VIDEO_SOURCE_SEL_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_31B0, + PGEN_PATTERN_SEL_VAL << 4, PGEN_PATTERN_SEL_MASK); +} + +static void mtk_dp_audio_setup_channels(struct mtk_dp *mtk_dp, + struct mtk_dp_audio_cfg *cfg) +{ + u32 channel_enable_bits; + + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC1_P0_3324, + AUDIO_SOURCE_MUX_DP_ENC1_P0_DPRX, + AUDIO_SOURCE_MUX_DP_ENC1_P0_MASK); + + /* audio channel count change reset */ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC1_P0_33F4, + DP_ENC_DUMMY_RW_1, DP_ENC_DUMMY_RW_1); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC1_P0_3304, + AU_PRTY_REGEN_DP_ENC1_P0_MASK | + AU_CH_STS_REGEN_DP_ENC1_P0_MASK | + AUDIO_SAMPLE_PRSENT_REGEN_DP_ENC1_P0_MASK, + AU_PRTY_REGEN_DP_ENC1_P0_MASK | + AU_CH_STS_REGEN_DP_ENC1_P0_MASK | + AUDIO_SAMPLE_PRSENT_REGEN_DP_ENC1_P0_MASK); + + switch (cfg->channels) { + case 2: + channel_enable_bits = AUDIO_2CH_SEL_DP_ENC0_P0_MASK | + AUDIO_2CH_EN_DP_ENC0_P0_MASK; + break; + case 8: + default: + channel_enable_bits = AUDIO_8CH_SEL_DP_ENC0_P0_MASK | + AUDIO_8CH_EN_DP_ENC0_P0_MASK; + break; + } + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3088, + channel_enable_bits | AU_EN_DP_ENC0_P0, + AUDIO_2CH_SEL_DP_ENC0_P0_MASK | + AUDIO_2CH_EN_DP_ENC0_P0_MASK | + AUDIO_8CH_SEL_DP_ENC0_P0_MASK | + AUDIO_8CH_EN_DP_ENC0_P0_MASK | + AU_EN_DP_ENC0_P0); + + /* audio channel count change reset */ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC1_P0_33F4, 0, DP_ENC_DUMMY_RW_1); + + /* enable audio reset */ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC1_P0_33F4, + DP_ENC_DUMMY_RW_1_AUDIO_RST_EN, + DP_ENC_DUMMY_RW_1_AUDIO_RST_EN); +} + +static void mtk_dp_audio_channel_status_set(struct mtk_dp *mtk_dp, + struct mtk_dp_audio_cfg *cfg) +{ + struct snd_aes_iec958 iec = { 0 }; + + switch (cfg->sample_rate) { + case 32000: + iec.status[3] = IEC958_AES3_CON_FS_32000; + break; + case 44100: + iec.status[3] = IEC958_AES3_CON_FS_44100; + break; + case 48000: + iec.status[3] = IEC958_AES3_CON_FS_48000; + break; + case 88200: + iec.status[3] = IEC958_AES3_CON_FS_88200; + break; + case 96000: + iec.status[3] = IEC958_AES3_CON_FS_96000; + break; + case 192000: + iec.status[3] = IEC958_AES3_CON_FS_192000; + break; + default: + iec.status[3] = IEC958_AES3_CON_FS_NOTID; + break; + } + + switch (cfg->word_length_bits) { + case 16: + iec.status[4] = IEC958_AES4_CON_WORDLEN_20_16; + break; + case 20: + iec.status[4] = IEC958_AES4_CON_WORDLEN_20_16 | + IEC958_AES4_CON_MAX_WORDLEN_24; + break; + case 24: + iec.status[4] = IEC958_AES4_CON_WORDLEN_24_20 | + IEC958_AES4_CON_MAX_WORDLEN_24; + break; + default: + iec.status[4] = IEC958_AES4_CON_WORDLEN_NOTID; + } + + /* IEC 60958 consumer channel status bits */ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_308C, + 0, CH_STATUS_0_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3090, + iec.status[3] << 8, CH_STATUS_1_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3094, + iec.status[4], CH_STATUS_2_DP_ENC0_P0_MASK); +} + +static void mtk_dp_audio_sdp_asp_set_channels(struct mtk_dp *mtk_dp, + int channels) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_312C, + (min(8, channels) - 1) << 8, + ASP_HB2_DP_ENC0_P0_MASK | ASP_HB3_DP_ENC0_P0_MASK); +} + +static void mtk_dp_audio_set_divider(struct mtk_dp *mtk_dp) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_30BC, + AUDIO_M_CODE_MULT_DIV_SEL_DP_ENC0_P0_DIV_2, + AUDIO_M_CODE_MULT_DIV_SEL_DP_ENC0_P0_MASK); +} + +static void mtk_dp_sdp_trigger_aui(struct mtk_dp *mtk_dp) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC1_P0_3280, + MTK_DP_SDP_AUI, SDP_PACKET_TYPE_DP_ENC1_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC1_P0_3280, + SDP_PACKET_W_DP_ENC1_P0, SDP_PACKET_W_DP_ENC1_P0); +} + +static void mtk_dp_sdp_set_data(struct mtk_dp *mtk_dp, u8 *data_bytes) +{ + mtk_dp_bulk_16bit_write(mtk_dp, MTK_DP_ENC1_P0_3200, + data_bytes, 0x10); +} + +static void mtk_dp_sdp_set_header_aui(struct mtk_dp *mtk_dp, + struct dp_sdp_header *header) +{ + u32 db_addr = MTK_DP_ENC0_P0_30D8 + (MTK_DP_SDP_AUI - 1) * 8; + + mtk_dp_bulk_16bit_write(mtk_dp, db_addr, (u8 *)header, 4); +} + +static void mtk_dp_disable_sdp_aui(struct mtk_dp *mtk_dp) +{ + /* Disable periodic send */ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_30A8 & 0xfffc, 0, + 0xff << ((MTK_DP_ENC0_P0_30A8 & 3) * 8)); +} + +static void mtk_dp_setup_sdp_aui(struct mtk_dp *mtk_dp, + struct dp_sdp *sdp) +{ + u32 shift; + + mtk_dp_sdp_set_data(mtk_dp, sdp->db); + mtk_dp_sdp_set_header_aui(mtk_dp, &sdp->sdp_header); + mtk_dp_disable_sdp_aui(mtk_dp); + + shift = (MTK_DP_ENC0_P0_30A8 & 3) * 8; + + mtk_dp_sdp_trigger_aui(mtk_dp); + /* Enable periodic sending */ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_30A8 & 0xfffc, + 0x05 << shift, 0xff << shift); +} + +static void mtk_dp_aux_irq_clear(struct mtk_dp *mtk_dp) +{ + mtk_dp_write(mtk_dp, MTK_DP_AUX_P0_3640, DP_AUX_P0_3640_VAL); +} + +static void mtk_dp_aux_set_cmd(struct mtk_dp *mtk_dp, u8 cmd, u32 addr) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_3644, + cmd, MCU_REQUEST_COMMAND_AUX_TX_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_3648, + addr, MCU_REQUEST_ADDRESS_LSB_AUX_TX_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_364C, + addr >> 16, MCU_REQUEST_ADDRESS_MSB_AUX_TX_P0_MASK); +} + +static void mtk_dp_aux_clear_fifo(struct mtk_dp *mtk_dp) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_3650, + MCU_ACK_TRAN_COMPLETE_AUX_TX_P0, + MCU_ACK_TRAN_COMPLETE_AUX_TX_P0 | + PHY_FIFO_RST_AUX_TX_P0_MASK | + MCU_REQ_DATA_NUM_AUX_TX_P0_MASK); +} + +static void mtk_dp_aux_request_ready(struct mtk_dp *mtk_dp) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_3630, + AUX_TX_REQUEST_READY_AUX_TX_P0, + AUX_TX_REQUEST_READY_AUX_TX_P0); +} + +static void mtk_dp_aux_fill_write_fifo(struct mtk_dp *mtk_dp, u8 *buf, + size_t length) +{ + mtk_dp_bulk_16bit_write(mtk_dp, MTK_DP_AUX_P0_3708, buf, length); +} + +static void mtk_dp_aux_read_rx_fifo(struct mtk_dp *mtk_dp, u8 *buf, + size_t length, int read_delay) +{ + int read_pos; + + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_3620, + 0, AUX_RD_MODE_AUX_TX_P0_MASK); + + for (read_pos = 0; read_pos < length; read_pos++) { + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_3620, + AUX_RX_FIFO_READ_PULSE_TX_P0, + AUX_RX_FIFO_READ_PULSE_TX_P0); + + /* Hardware needs time to update the data */ + usleep_range(read_delay, read_delay * 2); + buf[read_pos] = (u8)(mtk_dp_read(mtk_dp, MTK_DP_AUX_P0_3620) & + AUX_RX_FIFO_READ_DATA_AUX_TX_P0_MASK); + } +} + +static void mtk_dp_aux_set_length(struct mtk_dp *mtk_dp, size_t length) +{ + if (length > 0) { + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_3650, + (length - 1) << 12, + MCU_REQ_DATA_NUM_AUX_TX_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_362C, + 0, + AUX_NO_LENGTH_AUX_TX_P0 | + AUX_TX_AUXTX_OV_EN_AUX_TX_P0_MASK | + AUX_RESERVED_RW_0_AUX_TX_P0_MASK); + } else { + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_362C, + AUX_NO_LENGTH_AUX_TX_P0, + AUX_NO_LENGTH_AUX_TX_P0 | + AUX_TX_AUXTX_OV_EN_AUX_TX_P0_MASK | + AUX_RESERVED_RW_0_AUX_TX_P0_MASK); + } +} + +static int mtk_dp_aux_wait_for_completion(struct mtk_dp *mtk_dp, bool is_read) +{ + int wait_reply = MTK_DP_AUX_WAIT_REPLY_COUNT; + + while (--wait_reply) { + u32 aux_irq_status; + + if (is_read) { + u32 fifo_status = mtk_dp_read(mtk_dp, MTK_DP_AUX_P0_3618); + + if (fifo_status & + (AUX_RX_FIFO_WRITE_POINTER_AUX_TX_P0_MASK | + AUX_RX_FIFO_FULL_AUX_TX_P0_MASK)) { + return 0; + } + } + + aux_irq_status = mtk_dp_read(mtk_dp, MTK_DP_AUX_P0_3640); + if (aux_irq_status & AUX_RX_AUX_RECV_COMPLETE_IRQ_AUX_TX_P0) + return 0; + + if (aux_irq_status & AUX_400US_TIMEOUT_IRQ_AUX_TX_P0) + return -ETIMEDOUT; + + /* Give the hardware a chance to reach completion before retrying */ + usleep_range(100, 500); + } + + return -ETIMEDOUT; +} + +static int mtk_dp_aux_do_transfer(struct mtk_dp *mtk_dp, bool is_read, u8 cmd, + u32 addr, u8 *buf, size_t length, u8 *reply_cmd) +{ + int ret; + + if (is_read && (length > DP_AUX_MAX_PAYLOAD_BYTES || + (cmd == DP_AUX_NATIVE_READ && !length))) + return -EINVAL; + + if (!is_read) + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_3704, + AUX_TX_FIFO_NEW_MODE_EN_AUX_TX_P0, + AUX_TX_FIFO_NEW_MODE_EN_AUX_TX_P0); + + /* We need to clear fifo and irq before sending commands to the sink device. */ + mtk_dp_aux_clear_fifo(mtk_dp); + mtk_dp_aux_irq_clear(mtk_dp); + + mtk_dp_aux_set_cmd(mtk_dp, cmd, addr); + mtk_dp_aux_set_length(mtk_dp, length); + + if (!is_read) { + if (length) + mtk_dp_aux_fill_write_fifo(mtk_dp, buf, length); + + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_3704, + AUX_TX_FIFO_WDATA_NEW_MODE_T_AUX_TX_P0_MASK, + AUX_TX_FIFO_WDATA_NEW_MODE_T_AUX_TX_P0_MASK); + } + + mtk_dp_aux_request_ready(mtk_dp); + + /* Wait for feedback from sink device. */ + ret = mtk_dp_aux_wait_for_completion(mtk_dp, is_read); + + *reply_cmd = mtk_dp_read(mtk_dp, MTK_DP_AUX_P0_3624) & + AUX_RX_REPLY_COMMAND_AUX_TX_P0_MASK; + + if (ret) { + u32 phy_status = mtk_dp_read(mtk_dp, MTK_DP_AUX_P0_3628) & + AUX_RX_PHY_STATE_AUX_TX_P0_MASK; + if (phy_status != AUX_RX_PHY_STATE_AUX_TX_P0_RX_IDLE) { + dev_err(mtk_dp->dev, + "AUX Rx Aux hang, need SW reset\n"); + return -EIO; + } + + return -ETIMEDOUT; + } + + if (!length) { + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_362C, + 0, + AUX_NO_LENGTH_AUX_TX_P0 | + AUX_TX_AUXTX_OV_EN_AUX_TX_P0_MASK | + AUX_RESERVED_RW_0_AUX_TX_P0_MASK); + } else if (is_read) { + int read_delay; + + if (cmd == (DP_AUX_I2C_READ | DP_AUX_I2C_MOT) || + cmd == DP_AUX_I2C_READ) + read_delay = 500; + else + read_delay = 100; + + mtk_dp_aux_read_rx_fifo(mtk_dp, buf, length, read_delay); + } + + return 0; +} + +static void mtk_dp_set_swing_pre_emphasis(struct mtk_dp *mtk_dp, int lane_num, + int swing_val, int preemphasis) +{ + u32 lane_shift = lane_num * DP_TX1_VOLT_SWING_SHIFT; + + dev_dbg(mtk_dp->dev, + "link training: swing_val = 0x%x, pre-emphasis = 0x%x\n", + swing_val, preemphasis); + + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_SWING_EMP, + swing_val << (DP_TX0_VOLT_SWING_SHIFT + lane_shift), + DP_TX0_VOLT_SWING_MASK << lane_shift); + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_SWING_EMP, + preemphasis << (DP_TX0_PRE_EMPH_SHIFT + lane_shift), + DP_TX0_PRE_EMPH_MASK << lane_shift); +} + +static void mtk_dp_reset_swing_pre_emphasis(struct mtk_dp *mtk_dp) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_SWING_EMP, + 0, + DP_TX0_VOLT_SWING_MASK | + DP_TX1_VOLT_SWING_MASK | + DP_TX2_VOLT_SWING_MASK | + DP_TX3_VOLT_SWING_MASK | + DP_TX0_PRE_EMPH_MASK | + DP_TX1_PRE_EMPH_MASK | + DP_TX2_PRE_EMPH_MASK | + DP_TX3_PRE_EMPH_MASK); +} + +static u32 mtk_dp_swirq_get_clear(struct mtk_dp *mtk_dp) +{ + u32 irq_status = mtk_dp_read(mtk_dp, MTK_DP_TRANS_P0_35D0) & + SW_IRQ_FINAL_STATUS_DP_TRANS_P0_MASK; + + if (irq_status) { + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_35C8, + irq_status, SW_IRQ_CLR_DP_TRANS_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_35C8, + 0, SW_IRQ_CLR_DP_TRANS_P0_MASK); + } + + return irq_status; +} + +static u32 mtk_dp_hwirq_get_clear(struct mtk_dp *mtk_dp) +{ + u32 irq_status = (mtk_dp_read(mtk_dp, MTK_DP_TRANS_P0_3418) & + IRQ_STATUS_DP_TRANS_P0_MASK) >> 12; + + if (irq_status) { + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_3418, + irq_status, IRQ_CLR_DP_TRANS_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_3418, + 0, IRQ_CLR_DP_TRANS_P0_MASK); + } + + return irq_status; +} + +static void mtk_dp_hwirq_enable(struct mtk_dp *mtk_dp, bool enable) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_3418, + enable ? 0 : + IRQ_MASK_DP_TRANS_P0_DISC_IRQ | + IRQ_MASK_DP_TRANS_P0_CONN_IRQ | + IRQ_MASK_DP_TRANS_P0_INT_IRQ, + IRQ_MASK_DP_TRANS_P0_MASK); +} + +static void mtk_dp_initialize_settings(struct mtk_dp *mtk_dp) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_342C, + XTAL_FREQ_DP_TRANS_P0_DEFAULT, + XTAL_FREQ_DP_TRANS_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_3540, + FEC_CLOCK_EN_MODE_DP_TRANS_P0, + FEC_CLOCK_EN_MODE_DP_TRANS_P0); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_31EC, + AUDIO_CH_SRC_SEL_DP_ENC0_P0, + AUDIO_CH_SRC_SEL_DP_ENC0_P0); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_304C, + 0, SDP_VSYNC_RISING_MASK_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_IRQ_MASK, + IRQ_MASK_AUX_TOP_IRQ, IRQ_MASK_AUX_TOP_IRQ); +} + +static void mtk_dp_initialize_hpd_detect_settings(struct mtk_dp *mtk_dp) +{ + u32 val; + /* Debounce threshold */ + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_3410, + 8, HPD_DEB_THD_DP_TRANS_P0_MASK); + + val = (HPD_INT_THD_DP_TRANS_P0_LOWER_500US | + HPD_INT_THD_DP_TRANS_P0_UPPER_1100US) << 4; + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_3410, + val, HPD_INT_THD_DP_TRANS_P0_MASK); + + /* + * Connect threshold 1.5ms + 5 x 0.1ms = 2ms + * Disconnect threshold 1.5ms + 5 x 0.1ms = 2ms + */ + val = (5 << 8) | (5 << 12); + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_3410, + val, + HPD_DISC_THD_DP_TRANS_P0_MASK | + HPD_CONN_THD_DP_TRANS_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_3430, + HPD_INT_THD_ECO_DP_TRANS_P0_HIGH_BOUND_EXT, + HPD_INT_THD_ECO_DP_TRANS_P0_MASK); +} + +static void mtk_dp_initialize_aux_settings(struct mtk_dp *mtk_dp) +{ + /* modify timeout threshold = 0x1595 */ + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_360C, + AUX_TIMEOUT_THR_AUX_TX_P0_VAL, + AUX_TIMEOUT_THR_AUX_TX_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_3658, + 0, AUX_TX_OV_EN_AUX_TX_P0_MASK); + /* 25 for 26M */ + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_3634, + AUX_TX_OVER_SAMPLE_RATE_FOR_26M << 8, + AUX_TX_OVER_SAMPLE_RATE_AUX_TX_P0_MASK); + /* 13 for 26M */ + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_3614, + AUX_RX_UI_CNT_THR_AUX_FOR_26M, + AUX_RX_UI_CNT_THR_AUX_TX_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_37C8, + MTK_ATOP_EN_AUX_TX_P0, + MTK_ATOP_EN_AUX_TX_P0); +} + +static void mtk_dp_initialize_digital_settings(struct mtk_dp *mtk_dp) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_304C, + 0, VBID_VIDEO_MUTE_DP_ENC0_P0_MASK); + + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC1_P0_3368, + BS2BS_MODE_DP_ENC1_P0_VAL << 12, + BS2BS_MODE_DP_ENC1_P0_MASK); + + /* dp tx encoder reset all sw */ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3004, + DP_TX_ENCODER_4P_RESET_SW_DP_ENC0_P0, + DP_TX_ENCODER_4P_RESET_SW_DP_ENC0_P0); + + /* Wait for sw reset to complete */ + usleep_range(1000, 5000); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3004, + 0, DP_TX_ENCODER_4P_RESET_SW_DP_ENC0_P0); +} + +static void mtk_dp_digital_sw_reset(struct mtk_dp *mtk_dp) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_340C, + DP_TX_TRANSMITTER_4P_RESET_SW_DP_TRANS_P0, + DP_TX_TRANSMITTER_4P_RESET_SW_DP_TRANS_P0); + + /* Wait for sw reset to complete */ + usleep_range(1000, 5000); + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_340C, + 0, DP_TX_TRANSMITTER_4P_RESET_SW_DP_TRANS_P0); +} + +static void mtk_dp_set_lanes(struct mtk_dp *mtk_dp, int lanes) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_35F0, + lanes == 0 ? 0 : DP_TRANS_DUMMY_RW_0, + DP_TRANS_DUMMY_RW_0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3000, + lanes, LANE_NUM_DP_ENC0_P0_MASK); + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_34A4, + lanes << 2, LANE_NUM_DP_TRANS_P0_MASK); +} + +static void mtk_dp_get_calibration_data(struct mtk_dp *mtk_dp) +{ + const struct mtk_dp_efuse_fmt *fmt; + struct device *dev = mtk_dp->dev; + struct nvmem_cell *cell; + u32 *cal_data = mtk_dp->cal_data; + u32 *buf; + int i; + size_t len; + + cell = nvmem_cell_get(dev, "dp_calibration_data"); + if (IS_ERR(cell)) { + dev_warn(dev, "Failed to get nvmem cell dp_calibration_data\n"); + goto use_default_val; + } + + buf = (u32 *)nvmem_cell_read(cell, &len); + nvmem_cell_put(cell); + + if (IS_ERR(buf) || ((len / sizeof(u32)) != 4)) { + dev_warn(dev, "Failed to read nvmem_cell_read\n"); + + if (!IS_ERR(buf)) + kfree(buf); + + goto use_default_val; + } + + for (i = 0; i < MTK_DP_CAL_MAX; i++) { + fmt = &mtk_dp->data->efuse_fmt[i]; + cal_data[i] = (buf[fmt->idx] >> fmt->shift) & fmt->mask; + + if (cal_data[i] < fmt->min_val || cal_data[i] > fmt->max_val) { + dev_warn(mtk_dp->dev, "Invalid efuse data, idx = %d\n", i); + kfree(buf); + goto use_default_val; + } + } + kfree(buf); + + return; + +use_default_val: + dev_warn(mtk_dp->dev, "Use default calibration data\n"); + for (i = 0; i < MTK_DP_CAL_MAX; i++) + cal_data[i] = mtk_dp->data->efuse_fmt[i].default_val; +} + +static void mtk_dp_set_calibration_data(struct mtk_dp *mtk_dp) +{ + u32 *cal_data = mtk_dp->cal_data; + + mtk_dp_update_bits(mtk_dp, DP_PHY_GLB_DPAUX_TX, + cal_data[MTK_DP_CAL_CLKTX_IMPSE] << 20, + RG_CKM_PT0_CKTX_IMPSEL); + mtk_dp_update_bits(mtk_dp, DP_PHY_GLB_BIAS_GEN_00, + cal_data[MTK_DP_CAL_GLB_BIAS_TRIM] << 16, + RG_XTP_GLB_BIAS_INTR_CTRL); + mtk_dp_update_bits(mtk_dp, DP_PHY_LANE_TX_0, + cal_data[MTK_DP_CAL_LN_TX_IMPSEL_PMOS_0] << 12, + RG_XTP_LN0_TX_IMPSEL_PMOS); + mtk_dp_update_bits(mtk_dp, DP_PHY_LANE_TX_0, + cal_data[MTK_DP_CAL_LN_TX_IMPSEL_NMOS_0] << 16, + RG_XTP_LN0_TX_IMPSEL_NMOS); + mtk_dp_update_bits(mtk_dp, DP_PHY_LANE_TX_1, + cal_data[MTK_DP_CAL_LN_TX_IMPSEL_PMOS_1] << 12, + RG_XTP_LN1_TX_IMPSEL_PMOS); + mtk_dp_update_bits(mtk_dp, DP_PHY_LANE_TX_1, + cal_data[MTK_DP_CAL_LN_TX_IMPSEL_NMOS_1] << 16, + RG_XTP_LN1_TX_IMPSEL_NMOS); + mtk_dp_update_bits(mtk_dp, DP_PHY_LANE_TX_2, + cal_data[MTK_DP_CAL_LN_TX_IMPSEL_PMOS_2] << 12, + RG_XTP_LN2_TX_IMPSEL_PMOS); + mtk_dp_update_bits(mtk_dp, DP_PHY_LANE_TX_2, + cal_data[MTK_DP_CAL_LN_TX_IMPSEL_NMOS_2] << 16, + RG_XTP_LN2_TX_IMPSEL_NMOS); + mtk_dp_update_bits(mtk_dp, DP_PHY_LANE_TX_3, + cal_data[MTK_DP_CAL_LN_TX_IMPSEL_PMOS_3] << 12, + RG_XTP_LN3_TX_IMPSEL_PMOS); + mtk_dp_update_bits(mtk_dp, DP_PHY_LANE_TX_3, + cal_data[MTK_DP_CAL_LN_TX_IMPSEL_NMOS_3] << 16, + RG_XTP_LN3_TX_IMPSEL_NMOS); +} + +static int mtk_dp_phy_configure(struct mtk_dp *mtk_dp, + u32 link_rate, int lane_count) +{ + int ret; + union phy_configure_opts phy_opts = { + .dp = { + .link_rate = drm_dp_bw_code_to_link_rate(link_rate) / 100, + .set_rate = 1, + .lanes = lane_count, + .set_lanes = 1, + .ssc = mtk_dp->train_info.sink_ssc, + } + }; + + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_PWR_STATE, DP_PWR_STATE_BANDGAP, + DP_PWR_STATE_MASK); + + ret = phy_configure(mtk_dp->phy, &phy_opts); + if (ret) + return ret; + + mtk_dp_set_calibration_data(mtk_dp); + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_PWR_STATE, + DP_PWR_STATE_BANDGAP_TPLL_LANE, DP_PWR_STATE_MASK); + + return 0; +} + +static void mtk_dp_set_idle_pattern(struct mtk_dp *mtk_dp, bool enable) +{ + u32 val = POST_MISC_DATA_LANE0_OV_DP_TRANS_P0_MASK | + POST_MISC_DATA_LANE1_OV_DP_TRANS_P0_MASK | + POST_MISC_DATA_LANE2_OV_DP_TRANS_P0_MASK | + POST_MISC_DATA_LANE3_OV_DP_TRANS_P0_MASK; + + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_3580, + enable ? val : 0, val); +} + +static void mtk_dp_train_set_pattern(struct mtk_dp *mtk_dp, int pattern) +{ + /* TPS1 */ + if (pattern == 1) + mtk_dp_set_idle_pattern(mtk_dp, false); + + mtk_dp_update_bits(mtk_dp, + MTK_DP_TRANS_P0_3400, + pattern ? BIT(pattern - 1) << 12 : 0, + PATTERN1_EN_DP_TRANS_P0_MASK | + PATTERN2_EN_DP_TRANS_P0_MASK | + PATTERN3_EN_DP_TRANS_P0_MASK | + PATTERN4_EN_DP_TRANS_P0_MASK); +} + +static void mtk_dp_set_enhanced_frame_mode(struct mtk_dp *mtk_dp) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3000, + ENHANCED_FRAME_EN_DP_ENC0_P0, + ENHANCED_FRAME_EN_DP_ENC0_P0); +} + +static void mtk_dp_training_set_scramble(struct mtk_dp *mtk_dp, bool enable) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_TRANS_P0_3404, + enable ? DP_SCR_EN_DP_TRANS_P0_MASK : 0, + DP_SCR_EN_DP_TRANS_P0_MASK); +} + +static void mtk_dp_video_mute(struct mtk_dp *mtk_dp, bool enable) +{ + struct arm_smccc_res res; + u32 val = VIDEO_MUTE_SEL_DP_ENC0_P0 | + (enable ? VIDEO_MUTE_SW_DP_ENC0_P0 : 0); + + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3000, + val, + VIDEO_MUTE_SEL_DP_ENC0_P0 | + VIDEO_MUTE_SW_DP_ENC0_P0); + + arm_smccc_smc(MTK_DP_SIP_CONTROL_AARCH32, + mtk_dp->data->smc_cmd, enable, + 0, 0, 0, 0, 0, &res); + + dev_dbg(mtk_dp->dev, "smc cmd: 0x%x, p1: %s, ret: 0x%lx-0x%lx\n", + mtk_dp->data->smc_cmd, enable ? "enable" : "disable", res.a0, res.a1); +} + +static void mtk_dp_audio_mute(struct mtk_dp *mtk_dp, bool mute) +{ + u32 val[3]; + + if (mute) { + val[0] = VBID_AUDIO_MUTE_FLAG_SW_DP_ENC0_P0 | + VBID_AUDIO_MUTE_FLAG_SEL_DP_ENC0_P0; + val[1] = 0; + val[2] = 0; + } else { + val[0] = 0; + val[1] = AU_EN_DP_ENC0_P0; + /* Send one every two frames */ + val[2] = 0x0F; + } + + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3030, + val[0], + VBID_AUDIO_MUTE_FLAG_SW_DP_ENC0_P0 | + VBID_AUDIO_MUTE_FLAG_SEL_DP_ENC0_P0); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3088, + val[1], AU_EN_DP_ENC0_P0); + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_30A4, + val[2], AU_TS_CFG_DP_ENC0_P0_MASK); +} + +static void mtk_dp_power_enable(struct mtk_dp *mtk_dp) +{ + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_RESET_AND_PROBE, + 0, SW_RST_B_PHYD); + + /* Wait for power enable */ + usleep_range(10, 200); + + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_RESET_AND_PROBE, + SW_RST_B_PHYD, SW_RST_B_PHYD); + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_PWR_STATE, + DP_PWR_STATE_BANDGAP_TPLL, DP_PWR_STATE_MASK); + mtk_dp_write(mtk_dp, MTK_DP_1040, + RG_DPAUX_RX_VALID_DEGLITCH_EN | RG_XTP_GLB_CKDET_EN | + RG_DPAUX_RX_EN); + mtk_dp_update_bits(mtk_dp, MTK_DP_0034, 0, DA_CKM_CKTX0_EN_FORCE_EN); +} + +static void mtk_dp_power_disable(struct mtk_dp *mtk_dp) +{ + mtk_dp_write(mtk_dp, MTK_DP_TOP_PWR_STATE, 0); + + mtk_dp_update_bits(mtk_dp, MTK_DP_0034, + DA_CKM_CKTX0_EN_FORCE_EN, DA_CKM_CKTX0_EN_FORCE_EN); + + /* Disable RX */ + mtk_dp_write(mtk_dp, MTK_DP_1040, 0); + mtk_dp_write(mtk_dp, MTK_DP_TOP_MEM_PD, + 0x550 | FUSE_SEL | MEM_ISO_EN); +} + +static void mtk_dp_initialize_priv_data(struct mtk_dp *mtk_dp) +{ + mtk_dp->train_info.link_rate = DP_LINK_BW_5_4; + mtk_dp->train_info.lane_count = mtk_dp->max_lanes; + mtk_dp->train_info.cable_plugged_in = false; + + mtk_dp->info.format = DP_PIXELFORMAT_RGB; + memset(&mtk_dp->info.vm, 0, sizeof(struct videomode)); + mtk_dp->audio_enable = false; +} + +static void mtk_dp_sdp_set_down_cnt_init(struct mtk_dp *mtk_dp, + u32 sram_read_start) +{ + u32 sdp_down_cnt_init = 0; + struct drm_display_mode mode; + struct videomode *vm = &mtk_dp->info.vm; + + drm_display_mode_from_videomode(vm, &mode); + + if (mode.clock > 0) + sdp_down_cnt_init = sram_read_start * + mtk_dp->train_info.link_rate * 2700 * 8 / + (mode.clock * 4); + + switch (mtk_dp->train_info.lane_count) { + case 1: + sdp_down_cnt_init = max_t(u32, sdp_down_cnt_init, 0x1A); + break; + case 2: + /* case for LowResolution && High Audio Sample Rate */ + sdp_down_cnt_init = max_t(u32, sdp_down_cnt_init, 0x10); + sdp_down_cnt_init += mode.vtotal <= 525 ? 4 : 0; + break; + case 4: + default: + sdp_down_cnt_init = max_t(u32, sdp_down_cnt_init, 6); + break; + } + + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3040, + sdp_down_cnt_init, + SDP_DOWN_CNT_INIT_DP_ENC0_P0_MASK); +} + +static void mtk_dp_sdp_set_down_cnt_init_in_hblank(struct mtk_dp *mtk_dp) +{ + int pix_clk_mhz; + u32 dc_offset; + u32 spd_down_cnt_init = 0; + struct drm_display_mode mode; + struct videomode *vm = &mtk_dp->info.vm; + + drm_display_mode_from_videomode(vm, &mode); + + pix_clk_mhz = mtk_dp->info.format == DP_PIXELFORMAT_YUV420 ? + mode.clock / 2000 : mode.clock / 1000; + + switch (mtk_dp->train_info.lane_count) { + case 1: + spd_down_cnt_init = 0x20; + break; + case 2: + dc_offset = (mode.vtotal <= 525) ? 0x14 : 0x00; + spd_down_cnt_init = 0x18 + dc_offset; + break; + case 4: + default: + dc_offset = (mode.vtotal <= 525) ? 0x08 : 0x00; + if (pix_clk_mhz > mtk_dp->train_info.link_rate * 27) + spd_down_cnt_init = 0x8; + else + spd_down_cnt_init = 0x10 + dc_offset; + break; + } + + mtk_dp_update_bits(mtk_dp, MTK_DP_ENC1_P0_3364, spd_down_cnt_init, + SDP_DOWN_CNT_INIT_IN_HBLANK_DP_ENC1_P0_MASK); +} + +static void mtk_dp_setup_tu(struct mtk_dp *mtk_dp) +{ + u32 sram_read_start = min_t(u32, MTK_DP_TBC_BUF_READ_START_ADDR, + mtk_dp->info.vm.hactive / + mtk_dp->train_info.lane_count / + MTK_DP_4P1T / MTK_DP_HDE / + MTK_DP_PIX_PER_ADDR); + mtk_dp_set_sram_read_start(mtk_dp, sram_read_start); + mtk_dp_setup_encoder(mtk_dp); + mtk_dp_sdp_set_down_cnt_init_in_hblank(mtk_dp); + mtk_dp_sdp_set_down_cnt_init(mtk_dp, sram_read_start); +} + +static void mtk_dp_set_tx_out(struct mtk_dp *mtk_dp) +{ + mtk_dp_setup_tu(mtk_dp); +} + +static void mtk_dp_train_update_swing_pre(struct mtk_dp *mtk_dp, int lanes, + u8 dpcd_adjust_req[2]) +{ + int lane; + + for (lane = 0; lane < lanes; ++lane) { + u8 val; + u8 swing; + u8 preemphasis; + int index = lane / 2; + int shift = lane % 2 ? DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT : 0; + + swing = (dpcd_adjust_req[index] >> shift) & + DP_ADJUST_VOLTAGE_SWING_LANE0_MASK; + preemphasis = ((dpcd_adjust_req[index] >> shift) & + DP_ADJUST_PRE_EMPHASIS_LANE0_MASK) >> + DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT; + val = swing << DP_TRAIN_VOLTAGE_SWING_SHIFT | + preemphasis << DP_TRAIN_PRE_EMPHASIS_SHIFT; + + if (swing == DP_TRAIN_VOLTAGE_SWING_LEVEL_3) + val |= DP_TRAIN_MAX_SWING_REACHED; + if (preemphasis == 3) + val |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + + mtk_dp_set_swing_pre_emphasis(mtk_dp, lane, swing, preemphasis); + drm_dp_dpcd_writeb(&mtk_dp->aux, DP_TRAINING_LANE0_SET + lane, + val); + } +} + +static void mtk_dp_pattern(struct mtk_dp *mtk_dp, bool is_tps1) +{ + int pattern; + unsigned int aux_offset; + + if (is_tps1) { + pattern = 1; + aux_offset = DP_LINK_SCRAMBLING_DISABLE | DP_TRAINING_PATTERN_1; + } else { + aux_offset = mtk_dp->train_info.channel_eq_pattern; + + switch (mtk_dp->train_info.channel_eq_pattern) { + case DP_TRAINING_PATTERN_4: + pattern = 4; + break; + case DP_TRAINING_PATTERN_3: + pattern = 3; + aux_offset |= DP_LINK_SCRAMBLING_DISABLE; + break; + case DP_TRAINING_PATTERN_2: + default: + pattern = 2; + aux_offset |= DP_LINK_SCRAMBLING_DISABLE; + break; + } + } + + mtk_dp_train_set_pattern(mtk_dp, pattern); + drm_dp_dpcd_writeb(&mtk_dp->aux, DP_TRAINING_PATTERN_SET, aux_offset); +} + +static int mtk_dp_train_setting(struct mtk_dp *mtk_dp, u8 target_link_rate, + u8 target_lane_count) +{ + int ret; + + drm_dp_dpcd_writeb(&mtk_dp->aux, DP_LINK_BW_SET, target_link_rate); + drm_dp_dpcd_writeb(&mtk_dp->aux, DP_LANE_COUNT_SET, + target_lane_count | DP_LANE_COUNT_ENHANCED_FRAME_EN); + + if (mtk_dp->train_info.sink_ssc) + drm_dp_dpcd_writeb(&mtk_dp->aux, DP_DOWNSPREAD_CTRL, + DP_SPREAD_AMP_0_5); + + mtk_dp_set_lanes(mtk_dp, target_lane_count / 2); + ret = mtk_dp_phy_configure(mtk_dp, target_link_rate, target_lane_count); + if (ret) + return ret; + + dev_dbg(mtk_dp->dev, + "Link train target_link_rate = 0x%x, target_lane_count = 0x%x\n", + target_link_rate, target_lane_count); + + return 0; +} + +static int mtk_dp_train_cr(struct mtk_dp *mtk_dp, u8 target_lane_count) +{ + u8 lane_adjust[2] = {}; + u8 link_status[DP_LINK_STATUS_SIZE] = {}; + u8 prev_lane_adjust = 0xff; + int train_retries = 0; + int voltage_retries = 0; + + mtk_dp_pattern(mtk_dp, true); + + /* In DP spec 1.4, the retry count of CR is defined as 10. */ + do { + train_retries++; + if (!mtk_dp->train_info.cable_plugged_in) { + mtk_dp_train_set_pattern(mtk_dp, 0); + return -ENODEV; + } + + drm_dp_dpcd_read(&mtk_dp->aux, DP_ADJUST_REQUEST_LANE0_1, + lane_adjust, sizeof(lane_adjust)); + mtk_dp_train_update_swing_pre(mtk_dp, target_lane_count, + lane_adjust); + + drm_dp_link_train_clock_recovery_delay(&mtk_dp->aux, + mtk_dp->rx_cap); + + /* check link status from sink device */ + drm_dp_dpcd_read_link_status(&mtk_dp->aux, link_status); + if (drm_dp_clock_recovery_ok(link_status, + target_lane_count)) { + dev_dbg(mtk_dp->dev, "Link train CR pass\n"); + return 0; + } + + /* + * In DP spec 1.4, if current voltage level is the same + * with previous voltage level, we need to retry 5 times. + */ + if (prev_lane_adjust == link_status[4]) { + voltage_retries++; + /* + * Condition of CR fail: + * 1. Failed to pass CR using the same voltage + * level over five times. + * 2. Failed to pass CR when the current voltage + * level is the same with previous voltage + * level and reach max voltage level (3). + */ + if (voltage_retries > MTK_DP_TRAIN_VOLTAGE_LEVEL_RETRY || + (prev_lane_adjust & DP_ADJUST_VOLTAGE_SWING_LANE0_MASK) == 3) { + dev_dbg(mtk_dp->dev, "Link train CR fail\n"); + break; + } + } else { + /* + * If the voltage level is changed, we need to + * re-calculate this retry count. + */ + voltage_retries = 0; + } + prev_lane_adjust = link_status[4]; + } while (train_retries < MTK_DP_TRAIN_DOWNSCALE_RETRY); + + /* Failed to train CR, and disable pattern. */ + drm_dp_dpcd_writeb(&mtk_dp->aux, DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_DISABLE); + mtk_dp_train_set_pattern(mtk_dp, 0); + + return -ETIMEDOUT; +} + +static int mtk_dp_train_eq(struct mtk_dp *mtk_dp, u8 target_lane_count) +{ + u8 lane_adjust[2] = {}; + u8 link_status[DP_LINK_STATUS_SIZE] = {}; + int train_retries = 0; + + mtk_dp_pattern(mtk_dp, false); + + do { + train_retries++; + if (!mtk_dp->train_info.cable_plugged_in) { + mtk_dp_train_set_pattern(mtk_dp, 0); + return -ENODEV; + } + + drm_dp_dpcd_read(&mtk_dp->aux, DP_ADJUST_REQUEST_LANE0_1, + lane_adjust, sizeof(lane_adjust)); + mtk_dp_train_update_swing_pre(mtk_dp, target_lane_count, + lane_adjust); + + drm_dp_link_train_channel_eq_delay(&mtk_dp->aux, + mtk_dp->rx_cap); + + /* check link status from sink device */ + drm_dp_dpcd_read_link_status(&mtk_dp->aux, link_status); + if (drm_dp_channel_eq_ok(link_status, target_lane_count)) { + dev_dbg(mtk_dp->dev, "Link train EQ pass\n"); + + /* Training done, and disable pattern. */ + drm_dp_dpcd_writeb(&mtk_dp->aux, DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_DISABLE); + mtk_dp_train_set_pattern(mtk_dp, 0); + return 0; + } + dev_dbg(mtk_dp->dev, "Link train EQ fail\n"); + } while (train_retries < MTK_DP_TRAIN_DOWNSCALE_RETRY); + + /* Failed to train EQ, and disable pattern. */ + drm_dp_dpcd_writeb(&mtk_dp->aux, DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_DISABLE); + mtk_dp_train_set_pattern(mtk_dp, 0); + + return -ETIMEDOUT; +} + +static int mtk_dp_parse_capabilities(struct mtk_dp *mtk_dp) +{ + u8 val; + ssize_t ret; + + ret = drm_dp_read_dpcd_caps(&mtk_dp->aux, mtk_dp->rx_cap); + if (ret < 0) + return ret; + + if (drm_dp_tps4_supported(mtk_dp->rx_cap)) + mtk_dp->train_info.channel_eq_pattern = DP_TRAINING_PATTERN_4; + else if (drm_dp_tps3_supported(mtk_dp->rx_cap)) + mtk_dp->train_info.channel_eq_pattern = DP_TRAINING_PATTERN_3; + else + mtk_dp->train_info.channel_eq_pattern = DP_TRAINING_PATTERN_2; + + mtk_dp->train_info.sink_ssc = drm_dp_max_downspread(mtk_dp->rx_cap); + + ret = drm_dp_dpcd_readb(&mtk_dp->aux, DP_MSTM_CAP, &val); + if (ret < 1) { + drm_err(mtk_dp->drm_dev, "Read mstm cap failed\n"); + return ret == 0 ? -EIO : ret; + } + + if (val & DP_MST_CAP) { + /* Clear DP_DEVICE_SERVICE_IRQ_VECTOR_ESI0 */ + ret = drm_dp_dpcd_readb(&mtk_dp->aux, + DP_DEVICE_SERVICE_IRQ_VECTOR_ESI0, + &val); + if (ret < 1) { + drm_err(mtk_dp->drm_dev, "Read irq vector failed\n"); + return ret == 0 ? -EIO : ret; + } + + if (val) { + ret = drm_dp_dpcd_writeb(&mtk_dp->aux, + DP_DEVICE_SERVICE_IRQ_VECTOR_ESI0, + val); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static bool mtk_dp_edid_parse_audio_capabilities(struct mtk_dp *mtk_dp, + struct mtk_dp_audio_cfg *cfg) +{ + if (!mtk_dp->data->audio_supported) + return false; + + if (mtk_dp->info.audio_cur_cfg.sad_count <= 0) { + drm_info(mtk_dp->drm_dev, "The SADs is NULL\n"); + return false; + } + + return true; +} + +static void mtk_dp_train_change_mode(struct mtk_dp *mtk_dp) +{ + phy_reset(mtk_dp->phy); + mtk_dp_reset_swing_pre_emphasis(mtk_dp); +} + +static int mtk_dp_training(struct mtk_dp *mtk_dp) +{ + int ret; + u8 lane_count, link_rate, train_limit, max_link_rate; + + link_rate = min_t(u8, mtk_dp->max_linkrate, + mtk_dp->rx_cap[DP_MAX_LINK_RATE]); + max_link_rate = link_rate; + lane_count = min_t(u8, mtk_dp->max_lanes, + drm_dp_max_lane_count(mtk_dp->rx_cap)); + + /* + * TPS are generated by the hardware pattern generator. From the + * hardware setting we need to disable this scramble setting before + * use the TPS pattern generator. + */ + mtk_dp_training_set_scramble(mtk_dp, false); + + for (train_limit = 6; train_limit > 0; train_limit--) { + mtk_dp_train_change_mode(mtk_dp); + + ret = mtk_dp_train_setting(mtk_dp, link_rate, lane_count); + if (ret) + return ret; + + ret = mtk_dp_train_cr(mtk_dp, lane_count); + if (ret == -ENODEV) { + return ret; + } else if (ret) { + /* reduce link rate */ + switch (link_rate) { + case DP_LINK_BW_1_62: + lane_count = lane_count / 2; + link_rate = max_link_rate; + if (lane_count == 0) + return -EIO; + break; + case DP_LINK_BW_2_7: + link_rate = DP_LINK_BW_1_62; + break; + case DP_LINK_BW_5_4: + link_rate = DP_LINK_BW_2_7; + break; + case DP_LINK_BW_8_1: + link_rate = DP_LINK_BW_5_4; + break; + default: + return -EINVAL; + }; + continue; + } + + ret = mtk_dp_train_eq(mtk_dp, lane_count); + if (ret == -ENODEV) { + return ret; + } else if (ret) { + /* reduce lane count */ + if (lane_count == 0) + return -EIO; + lane_count /= 2; + continue; + } + + /* if we can run to this, training is done. */ + break; + } + + if (train_limit == 0) + return -ETIMEDOUT; + + mtk_dp->train_info.link_rate = link_rate; + mtk_dp->train_info.lane_count = lane_count; + + /* + * After training done, we need to output normal stream instead of TPS, + * so we need to enable scramble. + */ + mtk_dp_training_set_scramble(mtk_dp, true); + mtk_dp_set_enhanced_frame_mode(mtk_dp); + + return 0; +} + +static void mtk_dp_video_enable(struct mtk_dp *mtk_dp, bool enable) +{ + /* the mute sequence is different between enable and disable */ + if (enable) { + mtk_dp_msa_bypass_enable(mtk_dp, false); + mtk_dp_pg_enable(mtk_dp, false); + mtk_dp_set_tx_out(mtk_dp); + mtk_dp_video_mute(mtk_dp, false); + } else { + mtk_dp_video_mute(mtk_dp, true); + mtk_dp_pg_enable(mtk_dp, true); + mtk_dp_msa_bypass_enable(mtk_dp, true); + } +} + +static void mtk_dp_audio_sdp_setup(struct mtk_dp *mtk_dp, + struct mtk_dp_audio_cfg *cfg) +{ + struct dp_sdp sdp; + struct hdmi_audio_infoframe frame; + + hdmi_audio_infoframe_init(&frame); + frame.coding_type = HDMI_AUDIO_CODING_TYPE_PCM; + frame.channels = cfg->channels; + frame.sample_frequency = cfg->sample_rate; + + switch (cfg->word_length_bits) { + case 16: + frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_16; + break; + case 20: + frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_20; + break; + case 24: + default: + frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_24; + break; + } + + hdmi_audio_infoframe_pack_for_dp(&frame, &sdp, MTK_DP_VERSION); + + mtk_dp_audio_sdp_asp_set_channels(mtk_dp, cfg->channels); + mtk_dp_setup_sdp_aui(mtk_dp, &sdp); +} + +static void mtk_dp_audio_setup(struct mtk_dp *mtk_dp, + struct mtk_dp_audio_cfg *cfg) +{ + mtk_dp_audio_sdp_setup(mtk_dp, cfg); + mtk_dp_audio_channel_status_set(mtk_dp, cfg); + + mtk_dp_audio_setup_channels(mtk_dp, cfg); + mtk_dp_audio_set_divider(mtk_dp); +} + +static int mtk_dp_video_config(struct mtk_dp *mtk_dp) +{ + mtk_dp_config_mn_mode(mtk_dp); + mtk_dp_set_msa(mtk_dp); + mtk_dp_set_color_depth(mtk_dp); + return mtk_dp_set_color_format(mtk_dp, mtk_dp->info.format); +} + +static void mtk_dp_init_port(struct mtk_dp *mtk_dp) +{ + mtk_dp_set_idle_pattern(mtk_dp, true); + mtk_dp_initialize_priv_data(mtk_dp); + + mtk_dp_initialize_settings(mtk_dp); + mtk_dp_initialize_aux_settings(mtk_dp); + mtk_dp_initialize_digital_settings(mtk_dp); + + mtk_dp_update_bits(mtk_dp, MTK_DP_AUX_P0_3690, + RX_REPLY_COMPLETE_MODE_AUX_TX_P0, + RX_REPLY_COMPLETE_MODE_AUX_TX_P0); + mtk_dp_initialize_hpd_detect_settings(mtk_dp); + + mtk_dp_digital_sw_reset(mtk_dp); +} + +static irqreturn_t mtk_dp_hpd_event_thread(int hpd, void *dev) +{ + struct mtk_dp *mtk_dp = dev; + unsigned long flags; + u32 status; + + if (mtk_dp->need_debounce && mtk_dp->train_info.cable_plugged_in) + msleep(100); + + spin_lock_irqsave(&mtk_dp->irq_thread_lock, flags); + status = mtk_dp->irq_thread_handle; + mtk_dp->irq_thread_handle = 0; + spin_unlock_irqrestore(&mtk_dp->irq_thread_lock, flags); + + if (status & MTK_DP_THREAD_CABLE_STATE_CHG) { + if (mtk_dp->bridge.dev) + drm_helper_hpd_irq_event(mtk_dp->bridge.dev); + + if (!mtk_dp->train_info.cable_plugged_in) { + mtk_dp_disable_sdp_aui(mtk_dp); + memset(&mtk_dp->info.audio_cur_cfg, 0, + sizeof(mtk_dp->info.audio_cur_cfg)); + + mtk_dp->need_debounce = false; + mod_timer(&mtk_dp->debounce_timer, + jiffies + msecs_to_jiffies(100) - 1); + } + } + + if (status & MTK_DP_THREAD_HPD_EVENT) + dev_dbg(mtk_dp->dev, "Receive IRQ from sink devices\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t mtk_dp_hpd_event(int hpd, void *dev) +{ + struct mtk_dp *mtk_dp = dev; + bool cable_sta_chg = false; + unsigned long flags; + u32 irq_status = mtk_dp_swirq_get_clear(mtk_dp) | + mtk_dp_hwirq_get_clear(mtk_dp); + + if (!irq_status) + return IRQ_HANDLED; + + spin_lock_irqsave(&mtk_dp->irq_thread_lock, flags); + + if (irq_status & MTK_DP_HPD_INTERRUPT) + mtk_dp->irq_thread_handle |= MTK_DP_THREAD_HPD_EVENT; + + /* Cable state is changed. */ + if (irq_status != MTK_DP_HPD_INTERRUPT) { + mtk_dp->irq_thread_handle |= MTK_DP_THREAD_CABLE_STATE_CHG; + cable_sta_chg = true; + } + + spin_unlock_irqrestore(&mtk_dp->irq_thread_lock, flags); + + if (cable_sta_chg) { + if (!!(mtk_dp_read(mtk_dp, MTK_DP_TRANS_P0_3414) & + HPD_DB_DP_TRANS_P0_MASK)) + mtk_dp->train_info.cable_plugged_in = true; + else + mtk_dp->train_info.cable_plugged_in = false; + } + + return IRQ_WAKE_THREAD; +} + +static int mtk_dp_dt_parse(struct mtk_dp *mtk_dp, + struct platform_device *pdev) +{ + struct device_node *endpoint; + struct device *dev = &pdev->dev; + int ret; + void __iomem *base; + u32 linkrate; + int len; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + mtk_dp->regs = devm_regmap_init_mmio(dev, base, &mtk_dp_regmap_config); + if (IS_ERR(mtk_dp->regs)) + return PTR_ERR(mtk_dp->regs); + + endpoint = of_graph_get_endpoint_by_regs(pdev->dev.of_node, 1, -1); + len = of_property_count_elems_of_size(endpoint, + "data-lanes", sizeof(u32)); + if (len < 0 || len > 4 || len == 3) { + dev_err(dev, "invalid data lane size: %d\n", len); + return -EINVAL; + } + + mtk_dp->max_lanes = len; + + ret = device_property_read_u32(dev, "max-linkrate-mhz", &linkrate); + if (ret) { + dev_err(dev, "failed to read max linkrate: %d\n", ret); + return ret; + } + + mtk_dp->max_linkrate = drm_dp_link_rate_to_bw_code(linkrate * 100); + + return 0; +} + +static void mtk_dp_update_plugged_status(struct mtk_dp *mtk_dp) +{ + mutex_lock(&mtk_dp->update_plugged_status_lock); + if (mtk_dp->plugged_cb && mtk_dp->codec_dev) + mtk_dp->plugged_cb(mtk_dp->codec_dev, + mtk_dp->enabled & + mtk_dp->info.audio_cur_cfg.detect_monitor); + mutex_unlock(&mtk_dp->update_plugged_status_lock); +} + +static enum drm_connector_status mtk_dp_bdg_detect(struct drm_bridge *bridge) +{ + struct mtk_dp *mtk_dp = mtk_dp_from_bridge(bridge); + enum drm_connector_status ret = connector_status_disconnected; + bool enabled = mtk_dp->enabled; + u8 sink_count = 0; + + if (!mtk_dp->train_info.cable_plugged_in) + return ret; + + if (!enabled) { + /* power on aux */ + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_PWR_STATE, + DP_PWR_STATE_BANDGAP_TPLL_LANE, + DP_PWR_STATE_MASK); + + /* power on panel */ + drm_dp_dpcd_writeb(&mtk_dp->aux, DP_SET_POWER, DP_SET_POWER_D0); + usleep_range(2000, 5000); + } + /* + * Some dongles still source HPD when they do not connect to any + * sink device. To avoid this, we need to read the sink count + * to make sure we do connect to sink devices. After this detect + * function, we just need to check the HPD connection to check + * whether we connect to a sink device. + */ + drm_dp_dpcd_readb(&mtk_dp->aux, DP_SINK_COUNT, &sink_count); + if (DP_GET_SINK_COUNT(sink_count)) + ret = connector_status_connected; + + if (!enabled) { + /* power off panel */ + drm_dp_dpcd_writeb(&mtk_dp->aux, DP_SET_POWER, DP_SET_POWER_D3); + usleep_range(2000, 3000); + + /* power off aux */ + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_PWR_STATE, + DP_PWR_STATE_BANDGAP_TPLL, + DP_PWR_STATE_MASK); + } + + return ret; +} + +static struct edid *mtk_dp_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct mtk_dp *mtk_dp = mtk_dp_from_bridge(bridge); + bool enabled = mtk_dp->enabled; + struct edid *new_edid = NULL; + struct mtk_dp_audio_cfg *audio_caps = &mtk_dp->info.audio_cur_cfg; + + if (!enabled) { + drm_bridge_chain_pre_enable(bridge); + + /* power on aux */ + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_PWR_STATE, + DP_PWR_STATE_BANDGAP_TPLL_LANE, + DP_PWR_STATE_MASK); + + /* power on panel */ + drm_dp_dpcd_writeb(&mtk_dp->aux, DP_SET_POWER, DP_SET_POWER_D0); + usleep_range(2000, 5000); + } + + new_edid = drm_get_edid(connector, &mtk_dp->aux.ddc); + + /* + * Parse capability here to let atomic_get_input_bus_fmts and + * mode_valid use the capability to calculate sink bitrates. + */ + if (mtk_dp_parse_capabilities(mtk_dp)) { + drm_err(mtk_dp->drm_dev, "Can't parse capabilities\n"); + kfree(new_edid); + new_edid = NULL; + } + + if (new_edid) { + struct cea_sad *sads; + + audio_caps->sad_count = drm_edid_to_sad(new_edid, &sads); + kfree(sads); + + audio_caps->detect_monitor = drm_detect_monitor_audio(new_edid); + } + + if (!enabled) { + /* power off panel */ + drm_dp_dpcd_writeb(&mtk_dp->aux, DP_SET_POWER, DP_SET_POWER_D3); + usleep_range(2000, 3000); + + /* power off aux */ + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_PWR_STATE, + DP_PWR_STATE_BANDGAP_TPLL, + DP_PWR_STATE_MASK); + + drm_bridge_chain_post_disable(bridge); + } + + return new_edid; +} + +static ssize_t mtk_dp_aux_transfer(struct drm_dp_aux *mtk_aux, + struct drm_dp_aux_msg *msg) +{ + struct mtk_dp *mtk_dp; + bool is_read; + u8 request; + size_t accessed_bytes = 0; + int ret; + + mtk_dp = container_of(mtk_aux, struct mtk_dp, aux); + + if (!mtk_dp->train_info.cable_plugged_in) { + ret = -EAGAIN; + goto err; + } + + switch (msg->request) { + case DP_AUX_I2C_MOT: + case DP_AUX_I2C_WRITE: + case DP_AUX_NATIVE_WRITE: + case DP_AUX_I2C_WRITE_STATUS_UPDATE: + case DP_AUX_I2C_WRITE_STATUS_UPDATE | DP_AUX_I2C_MOT: + request = msg->request & ~DP_AUX_I2C_WRITE_STATUS_UPDATE; + is_read = false; + break; + case DP_AUX_I2C_READ: + case DP_AUX_NATIVE_READ: + case DP_AUX_I2C_READ | DP_AUX_I2C_MOT: + request = msg->request; + is_read = true; + break; + default: + dev_err(mtk_dp->dev, "invalid aux cmd = %d\n", + msg->request); + ret = -EINVAL; + goto err; + } + + do { + size_t to_access = min_t(size_t, DP_AUX_MAX_PAYLOAD_BYTES, + msg->size - accessed_bytes); + + ret = mtk_dp_aux_do_transfer(mtk_dp, is_read, request, + msg->address + accessed_bytes, + msg->buffer + accessed_bytes, + to_access, &msg->reply); + + if (ret) { + dev_info(mtk_dp->dev, + "Failed to do AUX transfer: %d\n", ret); + goto err; + } + accessed_bytes += to_access; + } while (accessed_bytes < msg->size); + + return msg->size; +err: + msg->reply = DP_AUX_NATIVE_REPLY_NACK | DP_AUX_I2C_REPLY_NACK; + return ret; +} + +static int mtk_dp_poweron(struct mtk_dp *mtk_dp) +{ + int ret; + + ret = phy_init(mtk_dp->phy); + if (ret) { + dev_err(mtk_dp->dev, "Failed to initialize phy: %d\n", ret); + return ret; + } + + mtk_dp_init_port(mtk_dp); + mtk_dp_power_enable(mtk_dp); + + return 0; +} + +static void mtk_dp_poweroff(struct mtk_dp *mtk_dp) +{ + mtk_dp_power_disable(mtk_dp); + phy_exit(mtk_dp->phy); +} + +static int mtk_dp_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct mtk_dp *mtk_dp = mtk_dp_from_bridge(bridge); + int ret; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + dev_err(mtk_dp->dev, "Driver does not provide a connector!"); + return -EINVAL; + } + + mtk_dp->aux.drm_dev = bridge->dev; + ret = drm_dp_aux_register(&mtk_dp->aux); + if (ret) { + dev_err(mtk_dp->dev, + "failed to register DP AUX channel: %d\n", ret); + return ret; + } + + ret = mtk_dp_poweron(mtk_dp); + if (ret) + goto err_aux_register; + + if (mtk_dp->next_bridge) { + ret = drm_bridge_attach(bridge->encoder, mtk_dp->next_bridge, + &mtk_dp->bridge, flags); + if (ret) { + drm_warn(mtk_dp->drm_dev, + "Failed to attach external bridge: %d\n", ret); + goto err_bridge_attach; + } + } + + mtk_dp->drm_dev = bridge->dev; + + mtk_dp_hwirq_enable(mtk_dp, true); + + return 0; + +err_bridge_attach: + mtk_dp_poweroff(mtk_dp); +err_aux_register: + drm_dp_aux_unregister(&mtk_dp->aux); + return ret; +} + +static void mtk_dp_bridge_detach(struct drm_bridge *bridge) +{ + struct mtk_dp *mtk_dp = mtk_dp_from_bridge(bridge); + + mtk_dp_hwirq_enable(mtk_dp, false); + mtk_dp->drm_dev = NULL; + mtk_dp_poweroff(mtk_dp); + drm_dp_aux_unregister(&mtk_dp->aux); +} + +static void mtk_dp_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) +{ + struct mtk_dp *mtk_dp = mtk_dp_from_bridge(bridge); + int ret; + + mtk_dp->conn = drm_atomic_get_new_connector_for_encoder(old_state->base.state, + bridge->encoder); + if (!mtk_dp->conn) { + drm_err(mtk_dp->drm_dev, + "Can't enable bridge as connector is missing\n"); + return; + } + + /* power on aux */ + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_PWR_STATE, + DP_PWR_STATE_BANDGAP_TPLL_LANE, + DP_PWR_STATE_MASK); + + if (mtk_dp->train_info.cable_plugged_in) { + drm_dp_dpcd_writeb(&mtk_dp->aux, DP_SET_POWER, DP_SET_POWER_D0); + usleep_range(2000, 5000); + } + + /* Training */ + ret = mtk_dp_training(mtk_dp); + if (ret) { + drm_err(mtk_dp->drm_dev, "Training failed, %d\n", ret); + goto power_off_aux; + } + + ret = mtk_dp_video_config(mtk_dp); + if (ret) + goto power_off_aux; + + mtk_dp_video_enable(mtk_dp, true); + + mtk_dp->audio_enable = + mtk_dp_edid_parse_audio_capabilities(mtk_dp, + &mtk_dp->info.audio_cur_cfg); + if (mtk_dp->audio_enable) { + mtk_dp_audio_setup(mtk_dp, &mtk_dp->info.audio_cur_cfg); + mtk_dp_audio_mute(mtk_dp, false); + } else { + memset(&mtk_dp->info.audio_cur_cfg, 0, + sizeof(mtk_dp->info.audio_cur_cfg)); + } + + mtk_dp->enabled = true; + mtk_dp_update_plugged_status(mtk_dp); + + return; +power_off_aux: + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_PWR_STATE, + DP_PWR_STATE_BANDGAP_TPLL, + DP_PWR_STATE_MASK); +} + +static void mtk_dp_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) +{ + struct mtk_dp *mtk_dp = mtk_dp_from_bridge(bridge); + + mtk_dp->enabled = false; + mtk_dp_update_plugged_status(mtk_dp); + mtk_dp_video_enable(mtk_dp, false); + mtk_dp_audio_mute(mtk_dp, true); + + if (mtk_dp->train_info.cable_plugged_in) { + drm_dp_dpcd_writeb(&mtk_dp->aux, DP_SET_POWER, DP_SET_POWER_D3); + usleep_range(2000, 3000); + } + + /* power off aux */ + mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_PWR_STATE, + DP_PWR_STATE_BANDGAP_TPLL, + DP_PWR_STATE_MASK); + + /* Ensure the sink is muted */ + msleep(20); +} + +static enum drm_mode_status +mtk_dp_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct mtk_dp *mtk_dp = mtk_dp_from_bridge(bridge); + u32 bpp = info->color_formats & DRM_COLOR_FORMAT_YCBCR422 ? 16 : 24; + u32 rate = min_t(u32, drm_dp_max_link_rate(mtk_dp->rx_cap) * + drm_dp_max_lane_count(mtk_dp->rx_cap), + drm_dp_bw_code_to_link_rate(mtk_dp->max_linkrate) * + mtk_dp->max_lanes); + + if (rate < mode->clock * bpp / 8) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static u32 *mtk_dp_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) +{ + u32 *output_fmts; + + *num_output_fmts = 0; + output_fmts = kmalloc(sizeof(*output_fmts), GFP_KERNEL); + if (!output_fmts) + return NULL; + *num_output_fmts = 1; + output_fmts[0] = MEDIA_BUS_FMT_FIXED; + return output_fmts; +} + +static const u32 mt8195_input_fmts[] = { + MEDIA_BUS_FMT_RGB888_1X24, + MEDIA_BUS_FMT_YUV8_1X24, + MEDIA_BUS_FMT_YUYV8_1X16, +}; + +static u32 *mtk_dp_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; + struct mtk_dp *mtk_dp = mtk_dp_from_bridge(bridge); + struct drm_display_mode *mode = &crtc_state->adjusted_mode; + struct drm_display_info *display_info = + &conn_state->connector->display_info; + u32 rate = min_t(u32, drm_dp_max_link_rate(mtk_dp->rx_cap) * + drm_dp_max_lane_count(mtk_dp->rx_cap), + drm_dp_bw_code_to_link_rate(mtk_dp->max_linkrate) * + mtk_dp->max_lanes); + + *num_input_fmts = 0; + + /* + * If the linkrate is smaller than datarate of RGB888, larger than + * datarate of YUV422 and sink device supports YUV422, we output YUV422 + * format. Use this condition, we can support more resolution. + */ + if ((rate < (mode->clock * 24 / 8)) && + (rate > (mode->clock * 16 / 8)) && + (display_info->color_formats & DRM_COLOR_FORMAT_YCBCR422)) { + input_fmts = kcalloc(1, sizeof(*input_fmts), GFP_KERNEL); + if (!input_fmts) + return NULL; + *num_input_fmts = 1; + input_fmts[0] = MEDIA_BUS_FMT_YUYV8_1X16; + } else { + input_fmts = kcalloc(ARRAY_SIZE(mt8195_input_fmts), + sizeof(*input_fmts), + GFP_KERNEL); + if (!input_fmts) + return NULL; + + *num_input_fmts = ARRAY_SIZE(mt8195_input_fmts); + memcpy(input_fmts, mt8195_input_fmts, sizeof(mt8195_input_fmts)); + } + + return input_fmts; +} + +static int mtk_dp_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 mtk_dp *mtk_dp = mtk_dp_from_bridge(bridge); + struct drm_crtc *crtc = conn_state->crtc; + unsigned int input_bus_format; + + input_bus_format = bridge_state->input_bus_cfg.format; + + dev_dbg(mtk_dp->dev, "input format 0x%04x, output format 0x%04x\n", + bridge_state->input_bus_cfg.format, + bridge_state->output_bus_cfg.format); + + if (input_bus_format == MEDIA_BUS_FMT_YUYV8_1X16) + mtk_dp->info.format = DP_PIXELFORMAT_YUV422; + else + mtk_dp->info.format = DP_PIXELFORMAT_RGB; + + if (!crtc) { + drm_err(mtk_dp->drm_dev, + "Can't enable bridge as connector state doesn't have a crtc\n"); + return -EINVAL; + } + + drm_display_mode_to_videomode(&crtc_state->adjusted_mode, &mtk_dp->info.vm); + + return 0; +} + +static const struct drm_bridge_funcs mtk_dp_bridge_funcs = { + .atomic_check = mtk_dp_bridge_atomic_check, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_get_output_bus_fmts = mtk_dp_bridge_atomic_get_output_bus_fmts, + .atomic_get_input_bus_fmts = mtk_dp_bridge_atomic_get_input_bus_fmts, + .atomic_reset = drm_atomic_helper_bridge_reset, + .attach = mtk_dp_bridge_attach, + .detach = mtk_dp_bridge_detach, + .atomic_enable = mtk_dp_bridge_atomic_enable, + .atomic_disable = mtk_dp_bridge_atomic_disable, + .mode_valid = mtk_dp_bridge_mode_valid, + .get_edid = mtk_dp_get_edid, + .detect = mtk_dp_bdg_detect, +}; + +static void mtk_dp_debounce_timer(struct timer_list *t) +{ + struct mtk_dp *mtk_dp = from_timer(mtk_dp, t, debounce_timer); + + mtk_dp->need_debounce = true; +} + +/* + * HDMI audio codec callbacks + */ +static int mtk_dp_audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct mtk_dp *mtk_dp = dev_get_drvdata(dev); + + if (!mtk_dp->enabled) { + dev_err(mtk_dp->dev, "%s, DP is not ready!\n", __func__); + return -ENODEV; + } + + mtk_dp->info.audio_cur_cfg.channels = params->cea.channels; + mtk_dp->info.audio_cur_cfg.sample_rate = params->sample_rate; + + mtk_dp_audio_setup(mtk_dp, &mtk_dp->info.audio_cur_cfg); + + return 0; +} + +static int mtk_dp_audio_startup(struct device *dev, void *data) +{ + struct mtk_dp *mtk_dp = dev_get_drvdata(dev); + + mtk_dp_audio_mute(mtk_dp, false); + + return 0; +} + +static void mtk_dp_audio_shutdown(struct device *dev, void *data) +{ + struct mtk_dp *mtk_dp = dev_get_drvdata(dev); + + mtk_dp_audio_mute(mtk_dp, true); +} + +static int mtk_dp_audio_get_eld(struct device *dev, void *data, uint8_t *buf, + size_t len) +{ + struct mtk_dp *mtk_dp = dev_get_drvdata(dev); + + if (mtk_dp->enabled) + memcpy(buf, mtk_dp->conn->eld, len); + else + memset(buf, 0, len); + + return 0; +} + +static int mtk_dp_audio_hook_plugged_cb(struct device *dev, void *data, + hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + struct mtk_dp *mtk_dp = data; + + mutex_lock(&mtk_dp->update_plugged_status_lock); + mtk_dp->plugged_cb = fn; + mtk_dp->codec_dev = codec_dev; + mutex_unlock(&mtk_dp->update_plugged_status_lock); + + mtk_dp_update_plugged_status(mtk_dp); + + return 0; +} + +static const struct hdmi_codec_ops mtk_dp_audio_codec_ops = { + .hw_params = mtk_dp_audio_hw_params, + .audio_startup = mtk_dp_audio_startup, + .audio_shutdown = mtk_dp_audio_shutdown, + .get_eld = mtk_dp_audio_get_eld, + .hook_plugged_cb = mtk_dp_audio_hook_plugged_cb, + .no_capture_mute = 1, +}; + +static int mtk_dp_register_audio_driver(struct device *dev) +{ + struct mtk_dp *mtk_dp = dev_get_drvdata(dev); + struct hdmi_codec_pdata codec_data = { + .ops = &mtk_dp_audio_codec_ops, + .max_i2s_channels = 8, + .i2s = 1, + .data = mtk_dp, + }; + + mtk_dp->audio_pdev = platform_device_register_data(dev, + HDMI_CODEC_DRV_NAME, + PLATFORM_DEVID_AUTO, + &codec_data, + sizeof(codec_data)); + return PTR_ERR_OR_ZERO(mtk_dp->audio_pdev); +} + +static int mtk_dp_probe(struct platform_device *pdev) +{ + struct mtk_dp *mtk_dp; + struct device *dev = &pdev->dev; + int ret, irq_num; + + mtk_dp = devm_kzalloc(dev, sizeof(*mtk_dp), GFP_KERNEL); + if (!mtk_dp) + return -ENOMEM; + + mtk_dp->dev = dev; + mtk_dp->data = (struct mtk_dp_data *)of_device_get_match_data(dev); + + irq_num = platform_get_irq(pdev, 0); + if (irq_num < 0) + return dev_err_probe(dev, irq_num, + "failed to request dp irq resource\n"); + + mtk_dp->next_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0); + if (IS_ERR(mtk_dp->next_bridge) && + PTR_ERR(mtk_dp->next_bridge) == -ENODEV) + mtk_dp->next_bridge = NULL; + else if (IS_ERR(mtk_dp->next_bridge)) + return dev_err_probe(dev, PTR_ERR(mtk_dp->next_bridge), + "Failed to get bridge\n"); + + ret = mtk_dp_dt_parse(mtk_dp, pdev); + if (ret) + return dev_err_probe(dev, ret, "Failed to parse dt\n"); + + drm_dp_aux_init(&mtk_dp->aux); + mtk_dp->aux.name = "aux_mtk_dp"; + mtk_dp->aux.transfer = mtk_dp_aux_transfer; + + spin_lock_init(&mtk_dp->irq_thread_lock); + + ret = devm_request_threaded_irq(dev, irq_num, mtk_dp_hpd_event, + mtk_dp_hpd_event_thread, + IRQ_TYPE_LEVEL_HIGH, dev_name(dev), + mtk_dp); + if (ret) + return dev_err_probe(dev, ret, + "failed to request mediatek dptx irq\n"); + + mutex_init(&mtk_dp->update_plugged_status_lock); + + platform_set_drvdata(pdev, mtk_dp); + + if (mtk_dp->data->audio_supported) { + ret = mtk_dp_register_audio_driver(dev); + if (ret) { + dev_err(dev, "Failed to register audio driver: %d\n", + ret); + return ret; + } + } + + mtk_dp->phy_dev = platform_device_register_data(dev, "mediatek-dp-phy", + PLATFORM_DEVID_AUTO, + &mtk_dp->regs, + sizeof(struct regmap *)); + if (IS_ERR(mtk_dp->phy_dev)) + return dev_err_probe(dev, PTR_ERR(mtk_dp->phy_dev), + "Failed to create device mediatek-dp-phy\n"); + + mtk_dp_get_calibration_data(mtk_dp); + + mtk_dp->phy = devm_phy_get(&mtk_dp->phy_dev->dev, "dp"); + + if (IS_ERR(mtk_dp->phy)) { + platform_device_unregister(mtk_dp->phy_dev); + return dev_err_probe(dev, PTR_ERR(mtk_dp->phy), + "Failed to get phy\n"); + } + + mtk_dp->bridge.funcs = &mtk_dp_bridge_funcs; + mtk_dp->bridge.of_node = dev->of_node; + + mtk_dp->bridge.ops = + DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; + mtk_dp->bridge.type = mtk_dp->data->bridge_type; + + drm_bridge_add(&mtk_dp->bridge); + + mtk_dp->need_debounce = true; + timer_setup(&mtk_dp->debounce_timer, mtk_dp_debounce_timer, 0); + + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + return 0; +} + +static int mtk_dp_remove(struct platform_device *pdev) +{ + struct mtk_dp *mtk_dp = platform_get_drvdata(pdev); + + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + del_timer_sync(&mtk_dp->debounce_timer); + drm_bridge_remove(&mtk_dp->bridge); + platform_device_unregister(mtk_dp->phy_dev); + if (mtk_dp->audio_pdev) + platform_device_unregister(mtk_dp->audio_pdev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mtk_dp_suspend(struct device *dev) +{ + struct mtk_dp *mtk_dp = dev_get_drvdata(dev); + + mtk_dp_power_disable(mtk_dp); + mtk_dp_hwirq_enable(mtk_dp, false); + pm_runtime_put_sync(dev); + + return 0; +} + +static int mtk_dp_resume(struct device *dev) +{ + struct mtk_dp *mtk_dp = dev_get_drvdata(dev); + + pm_runtime_get_sync(dev); + mtk_dp_init_port(mtk_dp); + mtk_dp_hwirq_enable(mtk_dp, true); + mtk_dp_power_enable(mtk_dp); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(mtk_dp_pm_ops, mtk_dp_suspend, mtk_dp_resume); + +static const struct mtk_dp_data mt8195_edp_data = { + .bridge_type = DRM_MODE_CONNECTOR_eDP, + .smc_cmd = MTK_DP_SIP_ATF_EDP_VIDEO_UNMUTE, + .efuse_fmt = mt8195_edp_efuse_fmt, + .audio_supported = false, +}; + +static const struct mtk_dp_data mt8195_dp_data = { + .bridge_type = DRM_MODE_CONNECTOR_DisplayPort, + .smc_cmd = MTK_DP_SIP_ATF_VIDEO_UNMUTE, + .efuse_fmt = mt8195_dp_efuse_fmt, + .audio_supported = true, +}; + +static const struct of_device_id mtk_dp_of_match[] = { + { + .compatible = "mediatek,mt8195-edp-tx", + .data = &mt8195_edp_data, + }, + { + .compatible = "mediatek,mt8195-dp-tx", + .data = &mt8195_dp_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_dp_of_match); + +static struct platform_driver mtk_dp_driver = { + .probe = mtk_dp_probe, + .remove = mtk_dp_remove, + .driver = { + .name = "mediatek-drm-dp", + .of_match_table = mtk_dp_of_match, + .pm = &mtk_dp_pm_ops, + }, +}; + +module_platform_driver(mtk_dp_driver); + +MODULE_AUTHOR("Jitao Shi <jitao.shi@mediatek.com>"); +MODULE_AUTHOR("Markus Schneider-Pargmann <msp@baylibre.com>"); +MODULE_AUTHOR("Bo-Chen Chen <rex-bc.chen@mediatek.com>"); +MODULE_DESCRIPTION("MediaTek DisplayPort Driver"); +MODULE_LICENSE("GPL"); +MODULE_SOFTDEP("pre: phy_mtk_dp"); diff --git a/drivers/gpu/drm/mediatek/mtk_dp_reg.h b/drivers/gpu/drm/mediatek/mtk_dp_reg.h new file mode 100644 index 000000000..84e38cef0 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dp_reg.h @@ -0,0 +1,350 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019-2022 MediaTek Inc. + * Copyright (c) 2022 BayLibre + */ +#ifndef _MTK_DP_REG_H_ +#define _MTK_DP_REG_H_ + +#define SEC_OFFSET 0x4000 + +#define MTK_DP_HPD_DISCONNECT BIT(1) +#define MTK_DP_HPD_CONNECT BIT(2) +#define MTK_DP_HPD_INTERRUPT BIT(3) + +/* offset: 0x0 */ +#define DP_PHY_GLB_BIAS_GEN_00 0x0 +#define RG_XTP_GLB_BIAS_INTR_CTRL GENMASK(20, 16) +#define DP_PHY_GLB_DPAUX_TX 0x8 +#define RG_CKM_PT0_CKTX_IMPSEL GENMASK(23, 20) +#define MTK_DP_0034 0x34 +#define DA_XTP_GLB_CKDET_EN_FORCE_VAL BIT(15) +#define DA_XTP_GLB_CKDET_EN_FORCE_EN BIT(14) +#define DA_CKM_INTCKTX_EN_FORCE_VAL BIT(13) +#define DA_CKM_INTCKTX_EN_FORCE_EN BIT(12) +#define DA_CKM_CKTX0_EN_FORCE_VAL BIT(11) +#define DA_CKM_CKTX0_EN_FORCE_EN BIT(10) +#define DA_CKM_XTAL_CK_FORCE_VAL BIT(9) +#define DA_CKM_XTAL_CK_FORCE_EN BIT(8) +#define DA_CKM_BIAS_LPF_EN_FORCE_VAL BIT(7) +#define DA_CKM_BIAS_LPF_EN_FORCE_EN BIT(6) +#define DA_CKM_BIAS_EN_FORCE_VAL BIT(5) +#define DA_CKM_BIAS_EN_FORCE_EN BIT(4) +#define DA_XTP_GLB_AVD10_ON_FORCE_VAL BIT(3) +#define DA_XTP_GLB_AVD10_ON_FORCE BIT(2) +#define DA_XTP_GLB_LDO_EN_FORCE_VAL BIT(1) +#define DA_XTP_GLB_LDO_EN_FORCE_EN BIT(0) +#define DP_PHY_LANE_TX_0 0x104 +#define RG_XTP_LN0_TX_IMPSEL_PMOS GENMASK(15, 12) +#define RG_XTP_LN0_TX_IMPSEL_NMOS GENMASK(19, 16) +#define DP_PHY_LANE_TX_1 0x204 +#define RG_XTP_LN1_TX_IMPSEL_PMOS GENMASK(15, 12) +#define RG_XTP_LN1_TX_IMPSEL_NMOS GENMASK(19, 16) +#define DP_PHY_LANE_TX_2 0x304 +#define RG_XTP_LN2_TX_IMPSEL_PMOS GENMASK(15, 12) +#define RG_XTP_LN2_TX_IMPSEL_NMOS GENMASK(19, 16) +#define DP_PHY_LANE_TX_3 0x404 +#define RG_XTP_LN3_TX_IMPSEL_PMOS GENMASK(15, 12) +#define RG_XTP_LN3_TX_IMPSEL_NMOS GENMASK(19, 16) +#define MTK_DP_1040 0x1040 +#define RG_DPAUX_RX_VALID_DEGLITCH_EN BIT(2) +#define RG_XTP_GLB_CKDET_EN BIT(1) +#define RG_DPAUX_RX_EN BIT(0) + +/* offset: TOP_OFFSET (0x2000) */ +#define MTK_DP_TOP_PWR_STATE 0x2000 +#define DP_PWR_STATE_MASK GENMASK(1, 0) +#define DP_PWR_STATE_BANDGAP BIT(0) +#define DP_PWR_STATE_BANDGAP_TPLL BIT(1) +#define DP_PWR_STATE_BANDGAP_TPLL_LANE GENMASK(1, 0) +#define MTK_DP_TOP_SWING_EMP 0x2004 +#define DP_TX0_VOLT_SWING_MASK GENMASK(1, 0) +#define DP_TX0_VOLT_SWING_SHIFT 0 +#define DP_TX0_PRE_EMPH_MASK GENMASK(3, 2) +#define DP_TX0_PRE_EMPH_SHIFT 2 +#define DP_TX1_VOLT_SWING_MASK GENMASK(9, 8) +#define DP_TX1_VOLT_SWING_SHIFT 8 +#define DP_TX1_PRE_EMPH_MASK GENMASK(11, 10) +#define DP_TX2_VOLT_SWING_MASK GENMASK(17, 16) +#define DP_TX2_PRE_EMPH_MASK GENMASK(19, 18) +#define DP_TX3_VOLT_SWING_MASK GENMASK(25, 24) +#define DP_TX3_PRE_EMPH_MASK GENMASK(27, 26) +#define MTK_DP_TOP_RESET_AND_PROBE 0x2020 +#define SW_RST_B_PHYD BIT(4) +#define MTK_DP_TOP_IRQ_MASK 0x202c +#define IRQ_MASK_AUX_TOP_IRQ BIT(2) +#define MTK_DP_TOP_MEM_PD 0x2038 +#define MEM_ISO_EN BIT(0) +#define FUSE_SEL BIT(2) + +/* offset: ENC0_OFFSET (0x3000) */ +#define MTK_DP_ENC0_P0_3000 0x3000 +#define LANE_NUM_DP_ENC0_P0_MASK GENMASK(1, 0) +#define VIDEO_MUTE_SW_DP_ENC0_P0 BIT(2) +#define VIDEO_MUTE_SEL_DP_ENC0_P0 BIT(3) +#define ENHANCED_FRAME_EN_DP_ENC0_P0 BIT(4) +#define MTK_DP_ENC0_P0_3004 0x3004 +#define VIDEO_M_CODE_SEL_DP_ENC0_P0_MASK BIT(8) +#define DP_TX_ENCODER_4P_RESET_SW_DP_ENC0_P0 BIT(9) +#define MTK_DP_ENC0_P0_3010 0x3010 +#define HTOTAL_SW_DP_ENC0_P0_MASK GENMASK(15, 0) +#define MTK_DP_ENC0_P0_3014 0x3014 +#define VTOTAL_SW_DP_ENC0_P0_MASK GENMASK(15, 0) +#define MTK_DP_ENC0_P0_3018 0x3018 +#define HSTART_SW_DP_ENC0_P0_MASK GENMASK(15, 0) +#define MTK_DP_ENC0_P0_301C 0x301c +#define VSTART_SW_DP_ENC0_P0_MASK GENMASK(15, 0) +#define MTK_DP_ENC0_P0_3020 0x3020 +#define HWIDTH_SW_DP_ENC0_P0_MASK GENMASK(15, 0) +#define MTK_DP_ENC0_P0_3024 0x3024 +#define VHEIGHT_SW_DP_ENC0_P0_MASK GENMASK(15, 0) +#define MTK_DP_ENC0_P0_3028 0x3028 +#define HSW_SW_DP_ENC0_P0_MASK GENMASK(14, 0) +#define HSP_SW_DP_ENC0_P0_MASK BIT(15) +#define MTK_DP_ENC0_P0_302C 0x302c +#define VSW_SW_DP_ENC0_P0_MASK GENMASK(14, 0) +#define VSP_SW_DP_ENC0_P0_MASK BIT(15) +#define MTK_DP_ENC0_P0_3030 0x3030 +#define HTOTAL_SEL_DP_ENC0_P0 BIT(0) +#define VTOTAL_SEL_DP_ENC0_P0 BIT(1) +#define HSTART_SEL_DP_ENC0_P0 BIT(2) +#define VSTART_SEL_DP_ENC0_P0 BIT(3) +#define HWIDTH_SEL_DP_ENC0_P0 BIT(4) +#define VHEIGHT_SEL_DP_ENC0_P0 BIT(5) +#define HSP_SEL_DP_ENC0_P0 BIT(6) +#define HSW_SEL_DP_ENC0_P0 BIT(7) +#define VSP_SEL_DP_ENC0_P0 BIT(8) +#define VSW_SEL_DP_ENC0_P0 BIT(9) +#define VBID_AUDIO_MUTE_FLAG_SW_DP_ENC0_P0 BIT(11) +#define VBID_AUDIO_MUTE_FLAG_SEL_DP_ENC0_P0 BIT(12) +#define MTK_DP_ENC0_P0_3034 0x3034 +#define MTK_DP_ENC0_P0_3038 0x3038 +#define VIDEO_SOURCE_SEL_DP_ENC0_P0_MASK BIT(11) +#define MTK_DP_ENC0_P0_303C 0x303c +#define SRAM_START_READ_THRD_DP_ENC0_P0_MASK GENMASK(5, 0) +#define VIDEO_COLOR_DEPTH_DP_ENC0_P0_MASK GENMASK(10, 8) +#define VIDEO_COLOR_DEPTH_DP_ENC0_P0_16BIT (0 << 8) +#define VIDEO_COLOR_DEPTH_DP_ENC0_P0_12BIT (1 << 8) +#define VIDEO_COLOR_DEPTH_DP_ENC0_P0_10BIT (2 << 8) +#define VIDEO_COLOR_DEPTH_DP_ENC0_P0_8BIT (3 << 8) +#define VIDEO_COLOR_DEPTH_DP_ENC0_P0_6BIT (4 << 8) +#define PIXEL_ENCODE_FORMAT_DP_ENC0_P0_MASK GENMASK(14, 12) +#define PIXEL_ENCODE_FORMAT_DP_ENC0_P0_RGB (0 << 12) +#define PIXEL_ENCODE_FORMAT_DP_ENC0_P0_YCBCR422 (1 << 12) +#define PIXEL_ENCODE_FORMAT_DP_ENC0_P0_YCBCR420 (2 << 12) +#define VIDEO_MN_GEN_EN_DP_ENC0_P0 BIT(15) +#define MTK_DP_ENC0_P0_3040 0x3040 +#define SDP_DOWN_CNT_DP_ENC0_P0_VAL 0x20 +#define SDP_DOWN_CNT_INIT_DP_ENC0_P0_MASK GENMASK(11, 0) +#define MTK_DP_ENC0_P0_304C 0x304c +#define VBID_VIDEO_MUTE_DP_ENC0_P0_MASK BIT(2) +#define SDP_VSYNC_RISING_MASK_DP_ENC0_P0_MASK BIT(8) +#define MTK_DP_ENC0_P0_3064 0x3064 +#define HDE_NUM_LAST_DP_ENC0_P0_MASK GENMASK(15, 0) +#define MTK_DP_ENC0_P0_3088 0x3088 +#define AU_EN_DP_ENC0_P0 BIT(6) +#define AUDIO_8CH_EN_DP_ENC0_P0_MASK BIT(7) +#define AUDIO_8CH_SEL_DP_ENC0_P0_MASK BIT(8) +#define AUDIO_2CH_EN_DP_ENC0_P0_MASK BIT(14) +#define AUDIO_2CH_SEL_DP_ENC0_P0_MASK BIT(15) +#define MTK_DP_ENC0_P0_308C 0x308c +#define CH_STATUS_0_DP_ENC0_P0_MASK GENMASK(15, 0) +#define MTK_DP_ENC0_P0_3090 0x3090 +#define CH_STATUS_1_DP_ENC0_P0_MASK GENMASK(15, 0) +#define MTK_DP_ENC0_P0_3094 0x3094 +#define CH_STATUS_2_DP_ENC0_P0_MASK GENMASK(7, 0) +#define MTK_DP_ENC0_P0_30A4 0x30a4 +#define AU_TS_CFG_DP_ENC0_P0_MASK GENMASK(7, 0) +#define MTK_DP_ENC0_P0_30A8 0x30a8 +#define MTK_DP_ENC0_P0_30BC 0x30bc +#define ISRC_CONT_DP_ENC0_P0 BIT(0) +#define AUDIO_M_CODE_MULT_DIV_SEL_DP_ENC0_P0_MASK GENMASK(10, 8) +#define AUDIO_M_CODE_MULT_DIV_SEL_DP_ENC0_P0_MUL_2 (1 << 8) +#define AUDIO_M_CODE_MULT_DIV_SEL_DP_ENC0_P0_MUL_4 (2 << 8) +#define AUDIO_M_CODE_MULT_DIV_SEL_DP_ENC0_P0_MUL_8 (3 << 8) +#define AUDIO_M_CODE_MULT_DIV_SEL_DP_ENC0_P0_DIV_2 (5 << 8) +#define AUDIO_M_CODE_MULT_DIV_SEL_DP_ENC0_P0_DIV_4 (6 << 8) +#define AUDIO_M_CODE_MULT_DIV_SEL_DP_ENC0_P0_DIV_8 (7 << 8) +#define MTK_DP_ENC0_P0_30D8 0x30d8 +#define MTK_DP_ENC0_P0_312C 0x312c +#define ASP_HB2_DP_ENC0_P0_MASK GENMASK(7, 0) +#define ASP_HB3_DP_ENC0_P0_MASK GENMASK(15, 8) +#define MTK_DP_ENC0_P0_3154 0x3154 +#define PGEN_HTOTAL_DP_ENC0_P0_MASK GENMASK(13, 0) +#define MTK_DP_ENC0_P0_3158 0x3158 +#define PGEN_HSYNC_RISING_DP_ENC0_P0_MASK GENMASK(13, 0) +#define MTK_DP_ENC0_P0_315C 0x315c +#define PGEN_HSYNC_PULSE_WIDTH_DP_ENC0_P0_MASK GENMASK(13, 0) +#define MTK_DP_ENC0_P0_3160 0x3160 +#define PGEN_HFDE_START_DP_ENC0_P0_MASK GENMASK(13, 0) +#define MTK_DP_ENC0_P0_3164 0x3164 +#define PGEN_HFDE_ACTIVE_WIDTH_DP_ENC0_P0_MASK GENMASK(13, 0) +#define MTK_DP_ENC0_P0_3168 0x3168 +#define PGEN_VTOTAL_DP_ENC0_P0_MASK GENMASK(12, 0) +#define MTK_DP_ENC0_P0_316C 0x316c +#define PGEN_VSYNC_RISING_DP_ENC0_P0_MASK GENMASK(12, 0) +#define MTK_DP_ENC0_P0_3170 0x3170 +#define PGEN_VSYNC_PULSE_WIDTH_DP_ENC0_P0_MASK GENMASK(12, 0) +#define MTK_DP_ENC0_P0_3174 0x3174 +#define PGEN_VFDE_START_DP_ENC0_P0_MASK GENMASK(12, 0) +#define MTK_DP_ENC0_P0_3178 0x3178 +#define PGEN_VFDE_ACTIVE_WIDTH_DP_ENC0_P0_MASK GENMASK(12, 0) +#define MTK_DP_ENC0_P0_31B0 0x31b0 +#define PGEN_PATTERN_SEL_VAL 4 +#define PGEN_PATTERN_SEL_MASK GENMASK(6, 4) +#define MTK_DP_ENC0_P0_31EC 0x31ec +#define AUDIO_CH_SRC_SEL_DP_ENC0_P0 BIT(4) +#define ISRC1_HB3_DP_ENC0_P0_MASK GENMASK(15, 8) + +/* offset: ENC1_OFFSET (0x3200) */ +#define MTK_DP_ENC1_P0_3200 0x3200 +#define MTK_DP_ENC1_P0_3280 0x3280 +#define SDP_PACKET_TYPE_DP_ENC1_P0_MASK GENMASK(4, 0) +#define SDP_PACKET_W_DP_ENC1_P0 BIT(5) +#define SDP_PACKET_W_DP_ENC1_P0_MASK BIT(5) +#define MTK_DP_ENC1_P0_3300 0x3300 +#define VIDEO_AFIFO_RDY_SEL_DP_ENC1_P0_VAL 2 +#define VIDEO_AFIFO_RDY_SEL_DP_ENC1_P0_MASK GENMASK(9, 8) +#define MTK_DP_ENC1_P0_3304 0x3304 +#define AU_PRTY_REGEN_DP_ENC1_P0_MASK BIT(8) +#define AU_CH_STS_REGEN_DP_ENC1_P0_MASK BIT(9) +#define AUDIO_SAMPLE_PRSENT_REGEN_DP_ENC1_P0_MASK BIT(12) +#define MTK_DP_ENC1_P0_3324 0x3324 +#define AUDIO_SOURCE_MUX_DP_ENC1_P0_MASK GENMASK(9, 8) +#define AUDIO_SOURCE_MUX_DP_ENC1_P0_DPRX 0 +#define MTK_DP_ENC1_P0_3364 0x3364 +#define SDP_DOWN_CNT_IN_HBLANK_DP_ENC1_P0_VAL 0x20 +#define SDP_DOWN_CNT_INIT_IN_HBLANK_DP_ENC1_P0_MASK GENMASK(11, 0) +#define FIFO_READ_START_POINT_DP_ENC1_P0_VAL 4 +#define FIFO_READ_START_POINT_DP_ENC1_P0_MASK GENMASK(15, 12) +#define MTK_DP_ENC1_P0_3368 0x3368 +#define VIDEO_SRAM_FIFO_CNT_RESET_SEL_DP_ENC1_P0 BIT(0) +#define VIDEO_STABLE_CNT_THRD_DP_ENC1_P0 BIT(4) +#define SDP_DP13_EN_DP_ENC1_P0 BIT(8) +#define BS2BS_MODE_DP_ENC1_P0 BIT(12) +#define BS2BS_MODE_DP_ENC1_P0_MASK GENMASK(13, 12) +#define BS2BS_MODE_DP_ENC1_P0_VAL 1 +#define DP_ENC1_P0_3368_VAL (VIDEO_SRAM_FIFO_CNT_RESET_SEL_DP_ENC1_P0 | \ + VIDEO_STABLE_CNT_THRD_DP_ENC1_P0 | \ + SDP_DP13_EN_DP_ENC1_P0 | \ + BS2BS_MODE_DP_ENC1_P0) +#define MTK_DP_ENC1_P0_33F4 0x33f4 +#define DP_ENC_DUMMY_RW_1_AUDIO_RST_EN BIT(0) +#define DP_ENC_DUMMY_RW_1 BIT(9) + +/* offset: TRANS_OFFSET (0x3400) */ +#define MTK_DP_TRANS_P0_3400 0x3400 +#define PATTERN1_EN_DP_TRANS_P0_MASK BIT(12) +#define PATTERN2_EN_DP_TRANS_P0_MASK BIT(13) +#define PATTERN3_EN_DP_TRANS_P0_MASK BIT(14) +#define PATTERN4_EN_DP_TRANS_P0_MASK BIT(15) +#define MTK_DP_TRANS_P0_3404 0x3404 +#define DP_SCR_EN_DP_TRANS_P0_MASK BIT(0) +#define MTK_DP_TRANS_P0_340C 0x340c +#define DP_TX_TRANSMITTER_4P_RESET_SW_DP_TRANS_P0 BIT(13) +#define MTK_DP_TRANS_P0_3410 0x3410 +#define HPD_DEB_THD_DP_TRANS_P0_MASK GENMASK(3, 0) +#define HPD_INT_THD_DP_TRANS_P0_MASK GENMASK(7, 4) +#define HPD_INT_THD_DP_TRANS_P0_LOWER_500US (2 << 4) +#define HPD_INT_THD_DP_TRANS_P0_UPPER_1100US (2 << 6) +#define HPD_DISC_THD_DP_TRANS_P0_MASK GENMASK(11, 8) +#define HPD_CONN_THD_DP_TRANS_P0_MASK GENMASK(15, 12) +#define MTK_DP_TRANS_P0_3414 0x3414 +#define HPD_DB_DP_TRANS_P0_MASK BIT(2) +#define MTK_DP_TRANS_P0_3418 0x3418 +#define IRQ_CLR_DP_TRANS_P0_MASK GENMASK(3, 0) +#define IRQ_MASK_DP_TRANS_P0_MASK GENMASK(7, 4) +#define IRQ_MASK_DP_TRANS_P0_DISC_IRQ (BIT(1) << 4) +#define IRQ_MASK_DP_TRANS_P0_CONN_IRQ (BIT(2) << 4) +#define IRQ_MASK_DP_TRANS_P0_INT_IRQ (BIT(3) << 4) +#define IRQ_STATUS_DP_TRANS_P0_MASK GENMASK(15, 12) +#define MTK_DP_TRANS_P0_342C 0x342c +#define XTAL_FREQ_DP_TRANS_P0_DEFAULT (BIT(0) | BIT(3) | BIT(5) | BIT(6)) +#define XTAL_FREQ_DP_TRANS_P0_MASK GENMASK(7, 0) +#define MTK_DP_TRANS_P0_3430 0x3430 +#define HPD_INT_THD_ECO_DP_TRANS_P0_MASK GENMASK(1, 0) +#define HPD_INT_THD_ECO_DP_TRANS_P0_HIGH_BOUND_EXT BIT(1) +#define MTK_DP_TRANS_P0_34A4 0x34a4 +#define LANE_NUM_DP_TRANS_P0_MASK GENMASK(3, 2) +#define MTK_DP_TRANS_P0_3540 0x3540 +#define FEC_EN_DP_TRANS_P0_MASK BIT(0) +#define FEC_CLOCK_EN_MODE_DP_TRANS_P0 BIT(3) +#define MTK_DP_TRANS_P0_3580 0x3580 +#define POST_MISC_DATA_LANE0_OV_DP_TRANS_P0_MASK BIT(8) +#define POST_MISC_DATA_LANE1_OV_DP_TRANS_P0_MASK BIT(9) +#define POST_MISC_DATA_LANE2_OV_DP_TRANS_P0_MASK BIT(10) +#define POST_MISC_DATA_LANE3_OV_DP_TRANS_P0_MASK BIT(11) +#define MTK_DP_TRANS_P0_35C8 0x35c8 +#define SW_IRQ_CLR_DP_TRANS_P0_MASK GENMASK(15, 0) +#define SW_IRQ_STATUS_DP_TRANS_P0_MASK GENMASK(15, 0) +#define MTK_DP_TRANS_P0_35D0 0x35d0 +#define SW_IRQ_FINAL_STATUS_DP_TRANS_P0_MASK GENMASK(15, 0) +#define MTK_DP_TRANS_P0_35F0 0x35f0 +#define DP_TRANS_DUMMY_RW_0 BIT(3) +#define DP_TRANS_DUMMY_RW_0_MASK GENMASK(3, 2) + +/* offset: AUX_OFFSET (0x3600) */ +#define MTK_DP_AUX_P0_360C 0x360c +#define AUX_TIMEOUT_THR_AUX_TX_P0_MASK GENMASK(12, 0) +#define AUX_TIMEOUT_THR_AUX_TX_P0_VAL 0x1595 +#define MTK_DP_AUX_P0_3614 0x3614 +#define AUX_RX_UI_CNT_THR_AUX_TX_P0_MASK GENMASK(6, 0) +#define AUX_RX_UI_CNT_THR_AUX_FOR_26M 13 +#define MTK_DP_AUX_P0_3618 0x3618 +#define AUX_RX_FIFO_FULL_AUX_TX_P0_MASK BIT(9) +#define AUX_RX_FIFO_WRITE_POINTER_AUX_TX_P0_MASK GENMASK(3, 0) +#define MTK_DP_AUX_P0_3620 0x3620 +#define AUX_RD_MODE_AUX_TX_P0_MASK BIT(9) +#define AUX_RX_FIFO_READ_PULSE_TX_P0 BIT(8) +#define AUX_RX_FIFO_READ_DATA_AUX_TX_P0_MASK GENMASK(7, 0) +#define MTK_DP_AUX_P0_3624 0x3624 +#define AUX_RX_REPLY_COMMAND_AUX_TX_P0_MASK GENMASK(3, 0) +#define MTK_DP_AUX_P0_3628 0x3628 +#define AUX_RX_PHY_STATE_AUX_TX_P0_MASK GENMASK(9, 0) +#define AUX_RX_PHY_STATE_AUX_TX_P0_RX_IDLE BIT(0) +#define MTK_DP_AUX_P0_362C 0x362c +#define AUX_NO_LENGTH_AUX_TX_P0 BIT(0) +#define AUX_TX_AUXTX_OV_EN_AUX_TX_P0_MASK BIT(1) +#define AUX_RESERVED_RW_0_AUX_TX_P0_MASK GENMASK(15, 2) +#define MTK_DP_AUX_P0_3630 0x3630 +#define AUX_TX_REQUEST_READY_AUX_TX_P0 BIT(3) +#define MTK_DP_AUX_P0_3634 0x3634 +#define AUX_TX_OVER_SAMPLE_RATE_AUX_TX_P0_MASK GENMASK(15, 8) +#define AUX_TX_OVER_SAMPLE_RATE_FOR_26M 25 +#define MTK_DP_AUX_P0_3640 0x3640 +#define AUX_RX_AUX_RECV_COMPLETE_IRQ_AUX_TX_P0 BIT(6) +#define AUX_RX_EDID_RECV_COMPLETE_IRQ_AUX_TX_P0 BIT(5) +#define AUX_RX_MCCS_RECV_COMPLETE_IRQ_AUX_TX_P0 BIT(4) +#define AUX_RX_CMD_RECV_IRQ_AUX_TX_P0 BIT(3) +#define AUX_RX_ADDR_RECV_IRQ_AUX_TX_P0 BIT(2) +#define AUX_RX_DATA_RECV_IRQ_AUX_TX_P0 BIT(1) +#define AUX_400US_TIMEOUT_IRQ_AUX_TX_P0 BIT(0) +#define DP_AUX_P0_3640_VAL (AUX_400US_TIMEOUT_IRQ_AUX_TX_P0 | \ + AUX_RX_DATA_RECV_IRQ_AUX_TX_P0 | \ + AUX_RX_ADDR_RECV_IRQ_AUX_TX_P0 | \ + AUX_RX_CMD_RECV_IRQ_AUX_TX_P0 | \ + AUX_RX_MCCS_RECV_COMPLETE_IRQ_AUX_TX_P0 | \ + AUX_RX_EDID_RECV_COMPLETE_IRQ_AUX_TX_P0 | \ + AUX_RX_AUX_RECV_COMPLETE_IRQ_AUX_TX_P0) +#define MTK_DP_AUX_P0_3644 0x3644 +#define MCU_REQUEST_COMMAND_AUX_TX_P0_MASK GENMASK(3, 0) +#define MTK_DP_AUX_P0_3648 0x3648 +#define MCU_REQUEST_ADDRESS_LSB_AUX_TX_P0_MASK GENMASK(15, 0) +#define MTK_DP_AUX_P0_364C 0x364c +#define MCU_REQUEST_ADDRESS_MSB_AUX_TX_P0_MASK GENMASK(3, 0) +#define MTK_DP_AUX_P0_3650 0x3650 +#define MCU_REQ_DATA_NUM_AUX_TX_P0_MASK GENMASK(15, 12) +#define PHY_FIFO_RST_AUX_TX_P0_MASK BIT(9) +#define MCU_ACK_TRAN_COMPLETE_AUX_TX_P0 BIT(8) +#define MTK_DP_AUX_P0_3658 0x3658 +#define AUX_TX_OV_EN_AUX_TX_P0_MASK BIT(0) +#define MTK_DP_AUX_P0_3690 0x3690 +#define RX_REPLY_COMPLETE_MODE_AUX_TX_P0 BIT(8) +#define MTK_DP_AUX_P0_3704 0x3704 +#define AUX_TX_FIFO_WDATA_NEW_MODE_T_AUX_TX_P0_MASK BIT(1) +#define AUX_TX_FIFO_NEW_MODE_EN_AUX_TX_P0 BIT(2) +#define MTK_DP_AUX_P0_3708 0x3708 +#define MTK_DP_AUX_P0_37C8 0x37c8 +#define MTK_ATOP_EN_AUX_TX_P0 BIT(0) + +#endif /*_MTK_DP_REG_H_*/ diff --git a/drivers/gpu/drm/mediatek/mtk_dpi.c b/drivers/gpu/drm/mediatek/mtk_dpi.c new file mode 100644 index 000000000..1f5d39a40 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dpi.c @@ -0,0 +1,1099 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu <jie.qiu@mediatek.com> + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/media-bus-format.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/types.h> + +#include <video/videomode.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_bridge_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_edid.h> +#include <drm/drm_of.h> +#include <drm/drm_simple_kms_helper.h> + +#include "mtk_disp_drv.h" +#include "mtk_dpi_regs.h" +#include "mtk_drm_ddp_comp.h" + +enum mtk_dpi_out_bit_num { + MTK_DPI_OUT_BIT_NUM_8BITS, + MTK_DPI_OUT_BIT_NUM_10BITS, + MTK_DPI_OUT_BIT_NUM_12BITS, + MTK_DPI_OUT_BIT_NUM_16BITS +}; + +enum mtk_dpi_out_yc_map { + MTK_DPI_OUT_YC_MAP_RGB, + MTK_DPI_OUT_YC_MAP_CYCY, + MTK_DPI_OUT_YC_MAP_YCYC, + MTK_DPI_OUT_YC_MAP_CY, + MTK_DPI_OUT_YC_MAP_YC +}; + +enum mtk_dpi_out_channel_swap { + MTK_DPI_OUT_CHANNEL_SWAP_RGB, + MTK_DPI_OUT_CHANNEL_SWAP_GBR, + MTK_DPI_OUT_CHANNEL_SWAP_BRG, + MTK_DPI_OUT_CHANNEL_SWAP_RBG, + MTK_DPI_OUT_CHANNEL_SWAP_GRB, + MTK_DPI_OUT_CHANNEL_SWAP_BGR +}; + +enum mtk_dpi_out_color_format { + MTK_DPI_COLOR_FORMAT_RGB, + MTK_DPI_COLOR_FORMAT_YCBCR_422 +}; + +struct mtk_dpi { + struct drm_encoder encoder; + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + struct drm_connector *connector; + void __iomem *regs; + struct device *dev; + struct clk *engine_clk; + struct clk *pixel_clk; + struct clk *tvd_clk; + int irq; + struct drm_display_mode mode; + const struct mtk_dpi_conf *conf; + enum mtk_dpi_out_color_format color_format; + enum mtk_dpi_out_yc_map yc_map; + enum mtk_dpi_out_bit_num bit_num; + enum mtk_dpi_out_channel_swap channel_swap; + struct pinctrl *pinctrl; + struct pinctrl_state *pins_gpio; + struct pinctrl_state *pins_dpi; + u32 output_fmt; + int refcount; +}; + +static inline struct mtk_dpi *bridge_to_dpi(struct drm_bridge *b) +{ + return container_of(b, struct mtk_dpi, bridge); +} + +enum mtk_dpi_polarity { + MTK_DPI_POLARITY_RISING, + MTK_DPI_POLARITY_FALLING, +}; + +struct mtk_dpi_polarities { + enum mtk_dpi_polarity de_pol; + enum mtk_dpi_polarity ck_pol; + enum mtk_dpi_polarity hsync_pol; + enum mtk_dpi_polarity vsync_pol; +}; + +struct mtk_dpi_sync_param { + u32 sync_width; + u32 front_porch; + u32 back_porch; + bool shift_half_line; +}; + +struct mtk_dpi_yc_limit { + u16 y_top; + u16 y_bottom; + u16 c_top; + u16 c_bottom; +}; + +/** + * struct mtk_dpi_conf - Configuration of mediatek dpi. + * @cal_factor: Callback function to calculate factor value. + * @reg_h_fre_con: Register address of frequency control. + * @max_clock_khz: Max clock frequency supported for this SoCs in khz units. + * @edge_sel_en: Enable of edge selection. + * @output_fmts: Array of supported output formats. + * @num_output_fmts: Quantity of supported output formats. + * @is_ck_de_pol: Support CK/DE polarity. + * @swap_input_support: Support input swap function. + * @support_direct_pin: IP supports direct connection to dpi panels. + * @input_2pixel: Input pixel of dp_intf is 2 pixel per round, so enable this + * config to enable this feature. + * @dimension_mask: Mask used for HWIDTH, HPORCH, VSYNC_WIDTH and VSYNC_PORCH + * (no shift). + * @hvsize_mask: Mask of HSIZE and VSIZE mask (no shift). + * @channel_swap_shift: Shift value of channel swap. + * @yuv422_en_bit: Enable bit of yuv422. + * @csc_enable_bit: Enable bit of CSC. + * @pixels_per_iter: Quantity of transferred pixels per iteration. + */ +struct mtk_dpi_conf { + unsigned int (*cal_factor)(int clock); + u32 reg_h_fre_con; + u32 max_clock_khz; + bool edge_sel_en; + const u32 *output_fmts; + u32 num_output_fmts; + bool is_ck_de_pol; + bool swap_input_support; + bool support_direct_pin; + bool input_2pixel; + u32 dimension_mask; + u32 hvsize_mask; + u32 channel_swap_shift; + u32 yuv422_en_bit; + u32 csc_enable_bit; + u32 pixels_per_iter; +}; + +static void mtk_dpi_mask(struct mtk_dpi *dpi, u32 offset, u32 val, u32 mask) +{ + u32 tmp = readl(dpi->regs + offset) & ~mask; + + tmp |= (val & mask); + writel(tmp, dpi->regs + offset); +} + +static void mtk_dpi_sw_reset(struct mtk_dpi *dpi, bool reset) +{ + mtk_dpi_mask(dpi, DPI_RET, reset ? RST : 0, RST); +} + +static void mtk_dpi_enable(struct mtk_dpi *dpi) +{ + mtk_dpi_mask(dpi, DPI_EN, EN, EN); +} + +static void mtk_dpi_disable(struct mtk_dpi *dpi) +{ + mtk_dpi_mask(dpi, DPI_EN, 0, EN); +} + +static void mtk_dpi_config_hsync(struct mtk_dpi *dpi, + struct mtk_dpi_sync_param *sync) +{ + mtk_dpi_mask(dpi, DPI_TGEN_HWIDTH, sync->sync_width << HPW, + dpi->conf->dimension_mask << HPW); + mtk_dpi_mask(dpi, DPI_TGEN_HPORCH, sync->back_porch << HBP, + dpi->conf->dimension_mask << HBP); + mtk_dpi_mask(dpi, DPI_TGEN_HPORCH, sync->front_porch << HFP, + dpi->conf->dimension_mask << HFP); +} + +static void mtk_dpi_config_vsync(struct mtk_dpi *dpi, + struct mtk_dpi_sync_param *sync, + u32 width_addr, u32 porch_addr) +{ + mtk_dpi_mask(dpi, width_addr, + sync->shift_half_line << VSYNC_HALF_LINE_SHIFT, + VSYNC_HALF_LINE_MASK); + mtk_dpi_mask(dpi, width_addr, + sync->sync_width << VSYNC_WIDTH_SHIFT, + dpi->conf->dimension_mask << VSYNC_WIDTH_SHIFT); + mtk_dpi_mask(dpi, porch_addr, + sync->back_porch << VSYNC_BACK_PORCH_SHIFT, + dpi->conf->dimension_mask << VSYNC_BACK_PORCH_SHIFT); + mtk_dpi_mask(dpi, porch_addr, + sync->front_porch << VSYNC_FRONT_PORCH_SHIFT, + dpi->conf->dimension_mask << VSYNC_FRONT_PORCH_SHIFT); +} + +static void mtk_dpi_config_vsync_lodd(struct mtk_dpi *dpi, + struct mtk_dpi_sync_param *sync) +{ + mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH, DPI_TGEN_VPORCH); +} + +static void mtk_dpi_config_vsync_leven(struct mtk_dpi *dpi, + struct mtk_dpi_sync_param *sync) +{ + mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_LEVEN, + DPI_TGEN_VPORCH_LEVEN); +} + +static void mtk_dpi_config_vsync_rodd(struct mtk_dpi *dpi, + struct mtk_dpi_sync_param *sync) +{ + mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_RODD, + DPI_TGEN_VPORCH_RODD); +} + +static void mtk_dpi_config_vsync_reven(struct mtk_dpi *dpi, + struct mtk_dpi_sync_param *sync) +{ + mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_REVEN, + DPI_TGEN_VPORCH_REVEN); +} + +static void mtk_dpi_config_pol(struct mtk_dpi *dpi, + struct mtk_dpi_polarities *dpi_pol) +{ + unsigned int pol; + unsigned int mask; + + mask = HSYNC_POL | VSYNC_POL; + pol = (dpi_pol->hsync_pol == MTK_DPI_POLARITY_RISING ? 0 : HSYNC_POL) | + (dpi_pol->vsync_pol == MTK_DPI_POLARITY_RISING ? 0 : VSYNC_POL); + if (dpi->conf->is_ck_de_pol) { + mask |= CK_POL | DE_POL; + pol |= (dpi_pol->ck_pol == MTK_DPI_POLARITY_RISING ? + 0 : CK_POL) | + (dpi_pol->de_pol == MTK_DPI_POLARITY_RISING ? + 0 : DE_POL); + } + + mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, pol, mask); +} + +static void mtk_dpi_config_3d(struct mtk_dpi *dpi, bool en_3d) +{ + mtk_dpi_mask(dpi, DPI_CON, en_3d ? TDFP_EN : 0, TDFP_EN); +} + +static void mtk_dpi_config_interface(struct mtk_dpi *dpi, bool inter) +{ + mtk_dpi_mask(dpi, DPI_CON, inter ? INTL_EN : 0, INTL_EN); +} + +static void mtk_dpi_config_fb_size(struct mtk_dpi *dpi, u32 width, u32 height) +{ + mtk_dpi_mask(dpi, DPI_SIZE, width << HSIZE, + dpi->conf->hvsize_mask << HSIZE); + mtk_dpi_mask(dpi, DPI_SIZE, height << VSIZE, + dpi->conf->hvsize_mask << VSIZE); +} + +static void mtk_dpi_config_channel_limit(struct mtk_dpi *dpi) +{ + struct mtk_dpi_yc_limit limit; + + if (drm_default_rgb_quant_range(&dpi->mode) == + HDMI_QUANTIZATION_RANGE_LIMITED) { + limit.y_bottom = 0x10; + limit.y_top = 0xfe0; + limit.c_bottom = 0x10; + limit.c_top = 0xfe0; + } else { + limit.y_bottom = 0; + limit.y_top = 0xfff; + limit.c_bottom = 0; + limit.c_top = 0xfff; + } + + mtk_dpi_mask(dpi, DPI_Y_LIMIT, limit.y_bottom << Y_LIMINT_BOT, + Y_LIMINT_BOT_MASK); + mtk_dpi_mask(dpi, DPI_Y_LIMIT, limit.y_top << Y_LIMINT_TOP, + Y_LIMINT_TOP_MASK); + mtk_dpi_mask(dpi, DPI_C_LIMIT, limit.c_bottom << C_LIMIT_BOT, + C_LIMIT_BOT_MASK); + mtk_dpi_mask(dpi, DPI_C_LIMIT, limit.c_top << C_LIMIT_TOP, + C_LIMIT_TOP_MASK); +} + +static void mtk_dpi_config_bit_num(struct mtk_dpi *dpi, + enum mtk_dpi_out_bit_num num) +{ + u32 val; + + switch (num) { + case MTK_DPI_OUT_BIT_NUM_8BITS: + val = OUT_BIT_8; + break; + case MTK_DPI_OUT_BIT_NUM_10BITS: + val = OUT_BIT_10; + break; + case MTK_DPI_OUT_BIT_NUM_12BITS: + val = OUT_BIT_12; + break; + case MTK_DPI_OUT_BIT_NUM_16BITS: + val = OUT_BIT_16; + break; + default: + val = OUT_BIT_8; + break; + } + mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, val << OUT_BIT, + OUT_BIT_MASK); +} + +static void mtk_dpi_config_yc_map(struct mtk_dpi *dpi, + enum mtk_dpi_out_yc_map map) +{ + u32 val; + + switch (map) { + case MTK_DPI_OUT_YC_MAP_RGB: + val = YC_MAP_RGB; + break; + case MTK_DPI_OUT_YC_MAP_CYCY: + val = YC_MAP_CYCY; + break; + case MTK_DPI_OUT_YC_MAP_YCYC: + val = YC_MAP_YCYC; + break; + case MTK_DPI_OUT_YC_MAP_CY: + val = YC_MAP_CY; + break; + case MTK_DPI_OUT_YC_MAP_YC: + val = YC_MAP_YC; + break; + default: + val = YC_MAP_RGB; + break; + } + + mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, val << YC_MAP, YC_MAP_MASK); +} + +static void mtk_dpi_config_channel_swap(struct mtk_dpi *dpi, + enum mtk_dpi_out_channel_swap swap) +{ + u32 val; + + switch (swap) { + case MTK_DPI_OUT_CHANNEL_SWAP_RGB: + val = SWAP_RGB; + break; + case MTK_DPI_OUT_CHANNEL_SWAP_GBR: + val = SWAP_GBR; + break; + case MTK_DPI_OUT_CHANNEL_SWAP_BRG: + val = SWAP_BRG; + break; + case MTK_DPI_OUT_CHANNEL_SWAP_RBG: + val = SWAP_RBG; + break; + case MTK_DPI_OUT_CHANNEL_SWAP_GRB: + val = SWAP_GRB; + break; + case MTK_DPI_OUT_CHANNEL_SWAP_BGR: + val = SWAP_BGR; + break; + default: + val = SWAP_RGB; + break; + } + + mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, + val << dpi->conf->channel_swap_shift, + CH_SWAP_MASK << dpi->conf->channel_swap_shift); +} + +static void mtk_dpi_config_yuv422_enable(struct mtk_dpi *dpi, bool enable) +{ + mtk_dpi_mask(dpi, DPI_CON, enable ? dpi->conf->yuv422_en_bit : 0, + dpi->conf->yuv422_en_bit); +} + +static void mtk_dpi_config_csc_enable(struct mtk_dpi *dpi, bool enable) +{ + mtk_dpi_mask(dpi, DPI_CON, enable ? dpi->conf->csc_enable_bit : 0, + dpi->conf->csc_enable_bit); +} + +static void mtk_dpi_config_swap_input(struct mtk_dpi *dpi, bool enable) +{ + mtk_dpi_mask(dpi, DPI_CON, enable ? IN_RB_SWAP : 0, IN_RB_SWAP); +} + +static void mtk_dpi_config_2n_h_fre(struct mtk_dpi *dpi) +{ + mtk_dpi_mask(dpi, dpi->conf->reg_h_fre_con, H_FRE_2N, H_FRE_2N); +} + +static void mtk_dpi_config_disable_edge(struct mtk_dpi *dpi) +{ + if (dpi->conf->edge_sel_en) + mtk_dpi_mask(dpi, dpi->conf->reg_h_fre_con, 0, EDGE_SEL_EN); +} + +static void mtk_dpi_config_color_format(struct mtk_dpi *dpi, + enum mtk_dpi_out_color_format format) +{ + mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_RGB); + + if (format == MTK_DPI_COLOR_FORMAT_YCBCR_422) { + mtk_dpi_config_yuv422_enable(dpi, true); + mtk_dpi_config_csc_enable(dpi, true); + + /* + * If height is smaller than 720, we need to use RGB_TO_BT601 + * to transfer to yuv422. Otherwise, we use RGB_TO_JPEG. + */ + mtk_dpi_mask(dpi, DPI_MATRIX_SET, dpi->mode.hdisplay <= 720 ? + MATRIX_SEL_RGB_TO_BT601 : MATRIX_SEL_RGB_TO_JPEG, + INT_MATRIX_SEL_MASK); + } else { + mtk_dpi_config_yuv422_enable(dpi, false); + mtk_dpi_config_csc_enable(dpi, false); + if (dpi->conf->swap_input_support) + mtk_dpi_config_swap_input(dpi, false); + } +} + +static void mtk_dpi_dual_edge(struct mtk_dpi *dpi) +{ + if ((dpi->output_fmt == MEDIA_BUS_FMT_RGB888_2X12_LE) || + (dpi->output_fmt == MEDIA_BUS_FMT_RGB888_2X12_BE)) { + mtk_dpi_mask(dpi, DPI_DDR_SETTING, DDR_EN | DDR_4PHASE, + DDR_EN | DDR_4PHASE); + mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, + dpi->output_fmt == MEDIA_BUS_FMT_RGB888_2X12_LE ? + EDGE_SEL : 0, EDGE_SEL); + } else { + mtk_dpi_mask(dpi, DPI_DDR_SETTING, DDR_EN | DDR_4PHASE, 0); + } +} + +static void mtk_dpi_power_off(struct mtk_dpi *dpi) +{ + if (WARN_ON(dpi->refcount == 0)) + return; + + if (--dpi->refcount != 0) + return; + + mtk_dpi_disable(dpi); + clk_disable_unprepare(dpi->pixel_clk); + clk_disable_unprepare(dpi->engine_clk); +} + +static int mtk_dpi_power_on(struct mtk_dpi *dpi) +{ + int ret; + + if (++dpi->refcount != 1) + return 0; + + ret = clk_prepare_enable(dpi->engine_clk); + if (ret) { + dev_err(dpi->dev, "Failed to enable engine clock: %d\n", ret); + goto err_refcount; + } + + ret = clk_prepare_enable(dpi->pixel_clk); + if (ret) { + dev_err(dpi->dev, "Failed to enable pixel clock: %d\n", ret); + goto err_pixel; + } + + return 0; + +err_pixel: + clk_disable_unprepare(dpi->engine_clk); +err_refcount: + dpi->refcount--; + return ret; +} + +static int mtk_dpi_set_display_mode(struct mtk_dpi *dpi, + struct drm_display_mode *mode) +{ + struct mtk_dpi_polarities dpi_pol; + struct mtk_dpi_sync_param hsync; + struct mtk_dpi_sync_param vsync_lodd = { 0 }; + struct mtk_dpi_sync_param vsync_leven = { 0 }; + struct mtk_dpi_sync_param vsync_rodd = { 0 }; + struct mtk_dpi_sync_param vsync_reven = { 0 }; + struct videomode vm = { 0 }; + unsigned long pll_rate; + unsigned int factor; + + /* let pll_rate can fix the valid range of tvdpll (1G~2GHz) */ + factor = dpi->conf->cal_factor(mode->clock); + drm_display_mode_to_videomode(mode, &vm); + pll_rate = vm.pixelclock * factor; + + dev_dbg(dpi->dev, "Want PLL %lu Hz, pixel clock %lu Hz\n", + pll_rate, vm.pixelclock); + + clk_set_rate(dpi->tvd_clk, pll_rate); + pll_rate = clk_get_rate(dpi->tvd_clk); + + /* + * Depending on the IP version, we may output a different amount of + * pixels for each iteration: divide the clock by this number and + * adjust the display porches accordingly. + */ + vm.pixelclock = pll_rate / factor; + vm.pixelclock /= dpi->conf->pixels_per_iter; + + if ((dpi->output_fmt == MEDIA_BUS_FMT_RGB888_2X12_LE) || + (dpi->output_fmt == MEDIA_BUS_FMT_RGB888_2X12_BE)) + clk_set_rate(dpi->pixel_clk, vm.pixelclock * 2); + else + clk_set_rate(dpi->pixel_clk, vm.pixelclock); + + + vm.pixelclock = clk_get_rate(dpi->pixel_clk); + + dev_dbg(dpi->dev, "Got PLL %lu Hz, pixel clock %lu Hz\n", + pll_rate, vm.pixelclock); + + dpi_pol.ck_pol = MTK_DPI_POLARITY_FALLING; + dpi_pol.de_pol = MTK_DPI_POLARITY_RISING; + dpi_pol.hsync_pol = vm.flags & DISPLAY_FLAGS_HSYNC_HIGH ? + MTK_DPI_POLARITY_FALLING : MTK_DPI_POLARITY_RISING; + dpi_pol.vsync_pol = vm.flags & DISPLAY_FLAGS_VSYNC_HIGH ? + MTK_DPI_POLARITY_FALLING : MTK_DPI_POLARITY_RISING; + + /* + * Depending on the IP version, we may output a different amount of + * pixels for each iteration: divide the clock by this number and + * adjust the display porches accordingly. + */ + hsync.sync_width = vm.hsync_len / dpi->conf->pixels_per_iter; + hsync.back_porch = vm.hback_porch / dpi->conf->pixels_per_iter; + hsync.front_porch = vm.hfront_porch / dpi->conf->pixels_per_iter; + + hsync.shift_half_line = false; + vsync_lodd.sync_width = vm.vsync_len; + vsync_lodd.back_porch = vm.vback_porch; + vsync_lodd.front_porch = vm.vfront_porch; + vsync_lodd.shift_half_line = false; + + if (vm.flags & DISPLAY_FLAGS_INTERLACED && + mode->flags & DRM_MODE_FLAG_3D_MASK) { + vsync_leven = vsync_lodd; + vsync_rodd = vsync_lodd; + vsync_reven = vsync_lodd; + vsync_leven.shift_half_line = true; + vsync_reven.shift_half_line = true; + } else if (vm.flags & DISPLAY_FLAGS_INTERLACED && + !(mode->flags & DRM_MODE_FLAG_3D_MASK)) { + vsync_leven = vsync_lodd; + vsync_leven.shift_half_line = true; + } else if (!(vm.flags & DISPLAY_FLAGS_INTERLACED) && + mode->flags & DRM_MODE_FLAG_3D_MASK) { + vsync_rodd = vsync_lodd; + } + mtk_dpi_sw_reset(dpi, true); + mtk_dpi_config_pol(dpi, &dpi_pol); + + mtk_dpi_config_hsync(dpi, &hsync); + mtk_dpi_config_vsync_lodd(dpi, &vsync_lodd); + mtk_dpi_config_vsync_rodd(dpi, &vsync_rodd); + mtk_dpi_config_vsync_leven(dpi, &vsync_leven); + mtk_dpi_config_vsync_reven(dpi, &vsync_reven); + + mtk_dpi_config_3d(dpi, !!(mode->flags & DRM_MODE_FLAG_3D_MASK)); + mtk_dpi_config_interface(dpi, !!(vm.flags & + DISPLAY_FLAGS_INTERLACED)); + if (vm.flags & DISPLAY_FLAGS_INTERLACED) + mtk_dpi_config_fb_size(dpi, vm.hactive, vm.vactive >> 1); + else + mtk_dpi_config_fb_size(dpi, vm.hactive, vm.vactive); + + mtk_dpi_config_channel_limit(dpi); + mtk_dpi_config_bit_num(dpi, dpi->bit_num); + mtk_dpi_config_channel_swap(dpi, dpi->channel_swap); + mtk_dpi_config_color_format(dpi, dpi->color_format); + if (dpi->conf->support_direct_pin) { + mtk_dpi_config_yc_map(dpi, dpi->yc_map); + mtk_dpi_config_2n_h_fre(dpi); + mtk_dpi_dual_edge(dpi); + mtk_dpi_config_disable_edge(dpi); + } + if (dpi->conf->input_2pixel) { + mtk_dpi_mask(dpi, DPI_CON, DPINTF_INPUT_2P_EN, + DPINTF_INPUT_2P_EN); + } + mtk_dpi_sw_reset(dpi, false); + + return 0; +} + +static u32 *mtk_dpi_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 mtk_dpi *dpi = bridge_to_dpi(bridge); + u32 *output_fmts; + + *num_output_fmts = 0; + + if (!dpi->conf->output_fmts) { + dev_err(dpi->dev, "output_fmts should not be null\n"); + return NULL; + } + + output_fmts = kcalloc(dpi->conf->num_output_fmts, sizeof(*output_fmts), + GFP_KERNEL); + if (!output_fmts) + return NULL; + + *num_output_fmts = dpi->conf->num_output_fmts; + + memcpy(output_fmts, dpi->conf->output_fmts, + sizeof(*output_fmts) * dpi->conf->num_output_fmts); + + return output_fmts; +} + +static u32 *mtk_dpi_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; + + *num_input_fmts = 0; + + input_fmts = kcalloc(1, sizeof(*input_fmts), + GFP_KERNEL); + if (!input_fmts) + return NULL; + + *num_input_fmts = 1; + input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; + + return input_fmts; +} + +static int mtk_dpi_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 mtk_dpi *dpi = bridge_to_dpi(bridge); + unsigned int out_bus_format; + + out_bus_format = bridge_state->output_bus_cfg.format; + + if (out_bus_format == MEDIA_BUS_FMT_FIXED) + if (dpi->conf->num_output_fmts) + out_bus_format = dpi->conf->output_fmts[0]; + + dev_dbg(dpi->dev, "input format 0x%04x, output format 0x%04x\n", + bridge_state->input_bus_cfg.format, + bridge_state->output_bus_cfg.format); + + dpi->output_fmt = out_bus_format; + dpi->bit_num = MTK_DPI_OUT_BIT_NUM_8BITS; + dpi->channel_swap = MTK_DPI_OUT_CHANNEL_SWAP_RGB; + dpi->yc_map = MTK_DPI_OUT_YC_MAP_RGB; + if (out_bus_format == MEDIA_BUS_FMT_YUYV8_1X16) + dpi->color_format = MTK_DPI_COLOR_FORMAT_YCBCR_422; + else + dpi->color_format = MTK_DPI_COLOR_FORMAT_RGB; + + return 0; +} + +static int mtk_dpi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct mtk_dpi *dpi = bridge_to_dpi(bridge); + + return drm_bridge_attach(bridge->encoder, dpi->next_bridge, + &dpi->bridge, flags); +} + +static void mtk_dpi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct mtk_dpi *dpi = bridge_to_dpi(bridge); + + drm_mode_copy(&dpi->mode, adjusted_mode); +} + +static void mtk_dpi_bridge_disable(struct drm_bridge *bridge) +{ + struct mtk_dpi *dpi = bridge_to_dpi(bridge); + + mtk_dpi_power_off(dpi); + + if (dpi->pinctrl && dpi->pins_gpio) + pinctrl_select_state(dpi->pinctrl, dpi->pins_gpio); +} + +static void mtk_dpi_bridge_enable(struct drm_bridge *bridge) +{ + struct mtk_dpi *dpi = bridge_to_dpi(bridge); + + if (dpi->pinctrl && dpi->pins_dpi) + pinctrl_select_state(dpi->pinctrl, dpi->pins_dpi); + + mtk_dpi_power_on(dpi); + mtk_dpi_set_display_mode(dpi, &dpi->mode); + mtk_dpi_enable(dpi); +} + +static enum drm_mode_status +mtk_dpi_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct mtk_dpi *dpi = bridge_to_dpi(bridge); + + if (mode->clock > dpi->conf->max_clock_khz) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static const struct drm_bridge_funcs mtk_dpi_bridge_funcs = { + .attach = mtk_dpi_bridge_attach, + .mode_set = mtk_dpi_bridge_mode_set, + .mode_valid = mtk_dpi_bridge_mode_valid, + .disable = mtk_dpi_bridge_disable, + .enable = mtk_dpi_bridge_enable, + .atomic_check = mtk_dpi_bridge_atomic_check, + .atomic_get_output_bus_fmts = mtk_dpi_bridge_atomic_get_output_bus_fmts, + .atomic_get_input_bus_fmts = mtk_dpi_bridge_atomic_get_input_bus_fmts, + .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, +}; + +void mtk_dpi_start(struct device *dev) +{ + struct mtk_dpi *dpi = dev_get_drvdata(dev); + + mtk_dpi_power_on(dpi); +} + +void mtk_dpi_stop(struct device *dev) +{ + struct mtk_dpi *dpi = dev_get_drvdata(dev); + + mtk_dpi_power_off(dpi); +} + +static int mtk_dpi_bind(struct device *dev, struct device *master, void *data) +{ + struct mtk_dpi *dpi = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + int ret; + + ret = drm_simple_encoder_init(drm_dev, &dpi->encoder, + DRM_MODE_ENCODER_TMDS); + if (ret) { + dev_err(dev, "Failed to initialize decoder: %d\n", ret); + return ret; + } + + dpi->encoder.possible_crtcs = mtk_drm_find_possible_crtc_by_comp(drm_dev, dpi->dev); + + ret = drm_bridge_attach(&dpi->encoder, &dpi->bridge, NULL, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + goto err_cleanup; + + dpi->connector = drm_bridge_connector_init(drm_dev, &dpi->encoder); + if (IS_ERR(dpi->connector)) { + dev_err(dev, "Unable to create bridge connector\n"); + ret = PTR_ERR(dpi->connector); + goto err_cleanup; + } + drm_connector_attach_encoder(dpi->connector, &dpi->encoder); + + return 0; + +err_cleanup: + drm_encoder_cleanup(&dpi->encoder); + return ret; +} + +static void mtk_dpi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct mtk_dpi *dpi = dev_get_drvdata(dev); + + drm_encoder_cleanup(&dpi->encoder); +} + +static const struct component_ops mtk_dpi_component_ops = { + .bind = mtk_dpi_bind, + .unbind = mtk_dpi_unbind, +}; + +static unsigned int mt8173_calculate_factor(int clock) +{ + if (clock <= 27000) + return 3 << 4; + else if (clock <= 84000) + return 3 << 3; + else if (clock <= 167000) + return 3 << 2; + else + return 3 << 1; +} + +static unsigned int mt2701_calculate_factor(int clock) +{ + if (clock <= 64000) + return 4; + else if (clock <= 128000) + return 2; + else + return 1; +} + +static unsigned int mt8183_calculate_factor(int clock) +{ + if (clock <= 27000) + return 8; + else if (clock <= 167000) + return 4; + else + return 2; +} + +static unsigned int mt8195_dpintf_calculate_factor(int clock) +{ + if (clock < 70000) + return 4; + else if (clock < 200000) + return 2; + else + return 1; +} + +static const u32 mt8173_output_fmts[] = { + MEDIA_BUS_FMT_RGB888_1X24, +}; + +static const u32 mt8183_output_fmts[] = { + MEDIA_BUS_FMT_RGB888_2X12_LE, + MEDIA_BUS_FMT_RGB888_2X12_BE, +}; + +static const u32 mt8195_output_fmts[] = { + MEDIA_BUS_FMT_RGB888_1X24, + MEDIA_BUS_FMT_YUYV8_1X16, +}; + +static const struct mtk_dpi_conf mt8173_conf = { + .cal_factor = mt8173_calculate_factor, + .reg_h_fre_con = 0xe0, + .max_clock_khz = 300000, + .output_fmts = mt8173_output_fmts, + .num_output_fmts = ARRAY_SIZE(mt8173_output_fmts), + .pixels_per_iter = 1, + .is_ck_de_pol = true, + .swap_input_support = true, + .support_direct_pin = true, + .dimension_mask = HPW_MASK, + .hvsize_mask = HSIZE_MASK, + .channel_swap_shift = CH_SWAP, + .yuv422_en_bit = YUV422_EN, + .csc_enable_bit = CSC_ENABLE, +}; + +static const struct mtk_dpi_conf mt2701_conf = { + .cal_factor = mt2701_calculate_factor, + .reg_h_fre_con = 0xb0, + .edge_sel_en = true, + .max_clock_khz = 150000, + .output_fmts = mt8173_output_fmts, + .num_output_fmts = ARRAY_SIZE(mt8173_output_fmts), + .pixels_per_iter = 1, + .is_ck_de_pol = true, + .swap_input_support = true, + .support_direct_pin = true, + .dimension_mask = HPW_MASK, + .hvsize_mask = HSIZE_MASK, + .channel_swap_shift = CH_SWAP, + .yuv422_en_bit = YUV422_EN, + .csc_enable_bit = CSC_ENABLE, +}; + +static const struct mtk_dpi_conf mt8183_conf = { + .cal_factor = mt8183_calculate_factor, + .reg_h_fre_con = 0xe0, + .max_clock_khz = 100000, + .output_fmts = mt8183_output_fmts, + .num_output_fmts = ARRAY_SIZE(mt8183_output_fmts), + .pixels_per_iter = 1, + .is_ck_de_pol = true, + .swap_input_support = true, + .support_direct_pin = true, + .dimension_mask = HPW_MASK, + .hvsize_mask = HSIZE_MASK, + .channel_swap_shift = CH_SWAP, + .yuv422_en_bit = YUV422_EN, + .csc_enable_bit = CSC_ENABLE, +}; + +static const struct mtk_dpi_conf mt8192_conf = { + .cal_factor = mt8183_calculate_factor, + .reg_h_fre_con = 0xe0, + .max_clock_khz = 150000, + .output_fmts = mt8183_output_fmts, + .num_output_fmts = ARRAY_SIZE(mt8183_output_fmts), + .pixels_per_iter = 1, + .is_ck_de_pol = true, + .swap_input_support = true, + .support_direct_pin = true, + .dimension_mask = HPW_MASK, + .hvsize_mask = HSIZE_MASK, + .channel_swap_shift = CH_SWAP, + .yuv422_en_bit = YUV422_EN, + .csc_enable_bit = CSC_ENABLE, +}; + +static const struct mtk_dpi_conf mt8195_dpintf_conf = { + .cal_factor = mt8195_dpintf_calculate_factor, + .max_clock_khz = 600000, + .output_fmts = mt8195_output_fmts, + .num_output_fmts = ARRAY_SIZE(mt8195_output_fmts), + .pixels_per_iter = 4, + .input_2pixel = true, + .dimension_mask = DPINTF_HPW_MASK, + .hvsize_mask = DPINTF_HSIZE_MASK, + .channel_swap_shift = DPINTF_CH_SWAP, + .yuv422_en_bit = DPINTF_YUV422_EN, + .csc_enable_bit = DPINTF_CSC_ENABLE, +}; + +static int mtk_dpi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_dpi *dpi; + struct resource *mem; + int ret; + + dpi = devm_kzalloc(dev, sizeof(*dpi), GFP_KERNEL); + if (!dpi) + return -ENOMEM; + + dpi->dev = dev; + dpi->conf = (struct mtk_dpi_conf *)of_device_get_match_data(dev); + dpi->output_fmt = MEDIA_BUS_FMT_RGB888_1X24; + + dpi->pinctrl = devm_pinctrl_get(&pdev->dev); + if (IS_ERR(dpi->pinctrl)) { + dpi->pinctrl = NULL; + dev_dbg(&pdev->dev, "Cannot find pinctrl!\n"); + } + if (dpi->pinctrl) { + dpi->pins_gpio = pinctrl_lookup_state(dpi->pinctrl, "sleep"); + if (IS_ERR(dpi->pins_gpio)) { + dpi->pins_gpio = NULL; + dev_dbg(&pdev->dev, "Cannot find pinctrl idle!\n"); + } + if (dpi->pins_gpio) + pinctrl_select_state(dpi->pinctrl, dpi->pins_gpio); + + dpi->pins_dpi = pinctrl_lookup_state(dpi->pinctrl, "default"); + if (IS_ERR(dpi->pins_dpi)) { + dpi->pins_dpi = NULL; + dev_dbg(&pdev->dev, "Cannot find pinctrl active!\n"); + } + } + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dpi->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(dpi->regs)) { + ret = PTR_ERR(dpi->regs); + dev_err(dev, "Failed to ioremap mem resource: %d\n", ret); + return ret; + } + + dpi->engine_clk = devm_clk_get(dev, "engine"); + if (IS_ERR(dpi->engine_clk)) { + ret = PTR_ERR(dpi->engine_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get engine clock: %d\n", ret); + + return ret; + } + + dpi->pixel_clk = devm_clk_get(dev, "pixel"); + if (IS_ERR(dpi->pixel_clk)) { + ret = PTR_ERR(dpi->pixel_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get pixel clock: %d\n", ret); + + return ret; + } + + dpi->tvd_clk = devm_clk_get(dev, "pll"); + if (IS_ERR(dpi->tvd_clk)) { + ret = PTR_ERR(dpi->tvd_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get tvdpll clock: %d\n", ret); + + return ret; + } + + dpi->irq = platform_get_irq(pdev, 0); + if (dpi->irq <= 0) + return -EINVAL; + + ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0, + NULL, &dpi->next_bridge); + if (ret) + return ret; + + dev_info(dev, "Found bridge node: %pOF\n", dpi->next_bridge->of_node); + + platform_set_drvdata(pdev, dpi); + + dpi->bridge.funcs = &mtk_dpi_bridge_funcs; + dpi->bridge.of_node = dev->of_node; + dpi->bridge.type = DRM_MODE_CONNECTOR_DPI; + + drm_bridge_add(&dpi->bridge); + + ret = component_add(dev, &mtk_dpi_component_ops); + if (ret) { + drm_bridge_remove(&dpi->bridge); + dev_err(dev, "Failed to add component: %d\n", ret); + return ret; + } + + return 0; +} + +static int mtk_dpi_remove(struct platform_device *pdev) +{ + struct mtk_dpi *dpi = platform_get_drvdata(pdev); + + component_del(&pdev->dev, &mtk_dpi_component_ops); + drm_bridge_remove(&dpi->bridge); + + return 0; +} + +static const struct of_device_id mtk_dpi_of_ids[] = { + { .compatible = "mediatek,mt2701-dpi", + .data = &mt2701_conf, + }, + { .compatible = "mediatek,mt8173-dpi", + .data = &mt8173_conf, + }, + { .compatible = "mediatek,mt8183-dpi", + .data = &mt8183_conf, + }, + { .compatible = "mediatek,mt8192-dpi", + .data = &mt8192_conf, + }, + { .compatible = "mediatek,mt8195-dp-intf", + .data = &mt8195_dpintf_conf, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, mtk_dpi_of_ids); + +struct platform_driver mtk_dpi_driver = { + .probe = mtk_dpi_probe, + .remove = mtk_dpi_remove, + .driver = { + .name = "mediatek-dpi", + .of_match_table = mtk_dpi_of_ids, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_dpi_regs.h b/drivers/gpu/drm/mediatek/mtk_dpi_regs.h new file mode 100644 index 000000000..62bd4931b --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dpi_regs.h @@ -0,0 +1,238 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu <jie.qiu@mediatek.com> + */ +#ifndef __MTK_DPI_REGS_H +#define __MTK_DPI_REGS_H + +#define DPI_EN 0x00 +#define EN BIT(0) + +#define DPI_RET 0x04 +#define RST BIT(0) + +#define DPI_INTEN 0x08 +#define INT_VSYNC_EN BIT(0) +#define INT_VDE_EN BIT(1) +#define INT_UNDERFLOW_EN BIT(2) + +#define DPI_INTSTA 0x0C +#define INT_VSYNC_STA BIT(0) +#define INT_VDE_STA BIT(1) +#define INT_UNDERFLOW_STA BIT(2) + +#define DPI_CON 0x10 +#define BG_ENABLE BIT(0) +#define IN_RB_SWAP BIT(1) +#define INTL_EN BIT(2) +#define TDFP_EN BIT(3) +#define CLPF_EN BIT(4) +#define YUV422_EN BIT(5) +#define CSC_ENABLE BIT(6) +#define R601_SEL BIT(7) +#define EMBSYNC_EN BIT(8) +#define VS_LODD_EN BIT(16) +#define VS_LEVEN_EN BIT(17) +#define VS_RODD_EN BIT(18) +#define VS_REVEN BIT(19) +#define FAKE_DE_LODD BIT(20) +#define FAKE_DE_LEVEN BIT(21) +#define FAKE_DE_RODD BIT(22) +#define FAKE_DE_REVEN BIT(23) +#define DPINTF_YUV422_EN BIT(24) +#define DPINTF_CSC_ENABLE BIT(26) +#define DPINTF_INPUT_2P_EN BIT(29) + +#define DPI_OUTPUT_SETTING 0x14 +#define CH_SWAP 0 +#define DPINTF_CH_SWAP 1 +#define CH_SWAP_MASK (0x7 << 0) +#define SWAP_RGB 0x00 +#define SWAP_GBR 0x01 +#define SWAP_BRG 0x02 +#define SWAP_RBG 0x03 +#define SWAP_GRB 0x04 +#define SWAP_BGR 0x05 +#define BIT_SWAP BIT(3) +#define B_MASK BIT(4) +#define G_MASK BIT(5) +#define R_MASK BIT(6) +#define DE_MASK BIT(8) +#define HS_MASK BIT(9) +#define VS_MASK BIT(10) +#define DE_POL BIT(12) +#define HSYNC_POL BIT(13) +#define VSYNC_POL BIT(14) +#define CK_POL BIT(15) +#define OEN_OFF BIT(16) +#define EDGE_SEL BIT(17) +#define OUT_BIT 18 +#define OUT_BIT_MASK (0x3 << 18) +#define OUT_BIT_8 0x00 +#define OUT_BIT_10 0x01 +#define OUT_BIT_12 0x02 +#define OUT_BIT_16 0x03 +#define YC_MAP 20 +#define YC_MAP_MASK (0x7 << 20) +#define YC_MAP_RGB 0x00 +#define YC_MAP_CYCY 0x04 +#define YC_MAP_YCYC 0x05 +#define YC_MAP_CY 0x06 +#define YC_MAP_YC 0x07 + +#define DPI_SIZE 0x18 +#define HSIZE 0 +#define HSIZE_MASK (0x1FFF << 0) +#define DPINTF_HSIZE_MASK (0xFFFF << 0) +#define VSIZE 16 +#define VSIZE_MASK (0x1FFF << 16) +#define DPINTF_VSIZE_MASK (0xFFFF << 16) + +#define DPI_DDR_SETTING 0x1C +#define DDR_EN BIT(0) +#define DDDR_SEL BIT(1) +#define DDR_4PHASE BIT(2) +#define DDR_WIDTH (0x3 << 4) +#define DDR_PAD_MODE (0x1 << 8) + +#define DPI_TGEN_HWIDTH 0x20 +#define HPW 0 +#define HPW_MASK (0xFFF << 0) +#define DPINTF_HPW_MASK (0xFFFF << 0) + +#define DPI_TGEN_HPORCH 0x24 +#define HBP 0 +#define HBP_MASK (0xFFF << 0) +#define DPINTF_HBP_MASK (0xFFFF << 0) +#define HFP 16 +#define HFP_MASK (0xFFF << 16) +#define DPINTF_HFP_MASK (0xFFFF << 16) + +#define DPI_TGEN_VWIDTH 0x28 +#define DPI_TGEN_VPORCH 0x2C + +#define VSYNC_WIDTH_SHIFT 0 +#define VSYNC_WIDTH_MASK (0xFFF << 0) +#define DPINTF_VSYNC_WIDTH_MASK (0xFFFF << 0) +#define VSYNC_HALF_LINE_SHIFT 16 +#define VSYNC_HALF_LINE_MASK BIT(16) +#define VSYNC_BACK_PORCH_SHIFT 0 +#define VSYNC_BACK_PORCH_MASK (0xFFF << 0) +#define DPINTF_VSYNC_BACK_PORCH_MASK (0xFFFF << 0) +#define VSYNC_FRONT_PORCH_SHIFT 16 +#define VSYNC_FRONT_PORCH_MASK (0xFFF << 16) +#define DPINTF_VSYNC_FRONT_PORCH_MASK (0xFFFF << 16) + +#define DPI_BG_HCNTL 0x30 +#define BG_RIGHT (0x1FFF << 0) +#define BG_LEFT (0x1FFF << 16) + +#define DPI_BG_VCNTL 0x34 +#define BG_BOT (0x1FFF << 0) +#define BG_TOP (0x1FFF << 16) + +#define DPI_BG_COLOR 0x38 +#define BG_B (0xF << 0) +#define BG_G (0xF << 8) +#define BG_R (0xF << 16) + +#define DPI_FIFO_CTL 0x3C +#define FIFO_VALID_SET (0x1F << 0) +#define FIFO_RST_SEL (0x1 << 8) + +#define DPI_STATUS 0x40 +#define VCOUNTER (0x1FFF << 0) +#define DPI_BUSY BIT(16) +#define OUTEN BIT(17) +#define FIELD BIT(20) +#define TDLR BIT(21) + +#define DPI_TMODE 0x44 +#define DPI_OEN_ON BIT(0) + +#define DPI_CHECKSUM 0x48 +#define DPI_CHECKSUM_MASK (0xFFFFFF << 0) +#define DPI_CHECKSUM_READY BIT(30) +#define DPI_CHECKSUM_EN BIT(31) + +#define DPI_DUMMY 0x50 +#define DPI_DUMMY_MASK (0xFFFFFFFF << 0) + +#define DPI_TGEN_VWIDTH_LEVEN 0x68 +#define DPI_TGEN_VPORCH_LEVEN 0x6C +#define DPI_TGEN_VWIDTH_RODD 0x70 +#define DPI_TGEN_VPORCH_RODD 0x74 +#define DPI_TGEN_VWIDTH_REVEN 0x78 +#define DPI_TGEN_VPORCH_REVEN 0x7C + +#define DPI_ESAV_VTIMING_LODD 0x80 +#define ESAV_VOFST_LODD (0xFFF << 0) +#define ESAV_VWID_LODD (0xFFF << 16) + +#define DPI_ESAV_VTIMING_LEVEN 0x84 +#define ESAV_VOFST_LEVEN (0xFFF << 0) +#define ESAV_VWID_LEVEN (0xFFF << 16) + +#define DPI_ESAV_VTIMING_RODD 0x88 +#define ESAV_VOFST_RODD (0xFFF << 0) +#define ESAV_VWID_RODD (0xFFF << 16) + +#define DPI_ESAV_VTIMING_REVEN 0x8C +#define ESAV_VOFST_REVEN (0xFFF << 0) +#define ESAV_VWID_REVEN (0xFFF << 16) + +#define DPI_ESAV_FTIMING 0x90 +#define ESAV_FOFST_ODD (0xFFF << 0) +#define ESAV_FOFST_EVEN (0xFFF << 16) + +#define DPI_CLPF_SETTING 0x94 +#define CLPF_TYPE (0x3 << 0) +#define ROUND_EN BIT(4) + +#define DPI_Y_LIMIT 0x98 +#define Y_LIMINT_BOT 0 +#define Y_LIMINT_BOT_MASK (0xFFF << 0) +#define Y_LIMINT_TOP 16 +#define Y_LIMINT_TOP_MASK (0xFFF << 16) + +#define DPI_C_LIMIT 0x9C +#define C_LIMIT_BOT 0 +#define C_LIMIT_BOT_MASK (0xFFF << 0) +#define C_LIMIT_TOP 16 +#define C_LIMIT_TOP_MASK (0xFFF << 16) + +#define DPI_YUV422_SETTING 0xA0 +#define UV_SWAP BIT(0) +#define CR_DELSEL BIT(4) +#define CB_DELSEL BIT(5) +#define Y_DELSEL BIT(6) +#define DE_DELSEL BIT(7) + +#define DPI_EMBSYNC_SETTING 0xA4 +#define EMBSYNC_R_CR_EN BIT(0) +#define EMPSYNC_G_Y_EN BIT(1) +#define EMPSYNC_B_CB_EN BIT(2) +#define ESAV_F_INV BIT(4) +#define ESAV_V_INV BIT(5) +#define ESAV_H_INV BIT(6) +#define ESAV_CODE_MAN BIT(8) +#define VS_OUT_SEL (0x7 << 12) + +#define DPI_ESAV_CODE_SET0 0xA8 +#define ESAV_CODE0 (0xFFF << 0) +#define ESAV_CODE1 (0xFFF << 16) + +#define DPI_ESAV_CODE_SET1 0xAC +#define ESAV_CODE2 (0xFFF << 0) +#define ESAV_CODE3_MSB BIT(16) + +#define EDGE_SEL_EN BIT(5) +#define H_FRE_2N BIT(25) + +#define DPI_MATRIX_SET 0xB4 +#define INT_MATRIX_SEL_MASK GENMASK(4, 0) +#define MATRIX_SEL_RGB_TO_JPEG 0 +#define MATRIX_SEL_RGB_TO_BT601 2 + +#endif /* __MTK_DPI_REGS_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_crtc.c b/drivers/gpu/drm/mediatek/mtk_drm_crtc.c new file mode 100644 index 000000000..558000db4 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.c @@ -0,0 +1,1014 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 MediaTek Inc. + */ + +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/mailbox_controller.h> +#include <linux/pm_runtime.h> +#include <linux/soc/mediatek/mtk-cmdq.h> +#include <linux/soc/mediatek/mtk-mmsys.h> +#include <linux/soc/mediatek/mtk-mutex.h> + +#include <asm/barrier.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "mtk_drm_drv.h" +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp_comp.h" +#include "mtk_drm_gem.h" +#include "mtk_drm_plane.h" + +/* + * struct mtk_drm_crtc - MediaTek specific crtc structure. + * @base: crtc object. + * @enabled: records whether crtc_enable succeeded + * @planes: array of 4 drm_plane structures, one for each overlay plane + * @pending_planes: whether any plane has pending changes to be applied + * @mmsys_dev: pointer to the mmsys device for configuration registers + * @mutex: handle to one of the ten disp_mutex streams + * @ddp_comp_nr: number of components in ddp_comp + * @ddp_comp: array of pointers the mtk_ddp_comp structures used by this crtc + * + * TODO: Needs update: this header is missing a bunch of member descriptions. + */ +struct mtk_drm_crtc { + struct drm_crtc base; + bool enabled; + + bool pending_needs_vblank; + struct drm_pending_vblank_event *event; + + struct drm_plane *planes; + unsigned int layer_nr; + bool pending_planes; + bool pending_async_planes; + +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + struct cmdq_client cmdq_client; + struct cmdq_pkt cmdq_handle; + u32 cmdq_event; + u32 cmdq_vblank_cnt; + wait_queue_head_t cb_blocking_queue; +#endif + + struct device *mmsys_dev; + struct mtk_mutex *mutex; + unsigned int ddp_comp_nr; + struct mtk_ddp_comp **ddp_comp; + + /* lock for display hardware access */ + struct mutex hw_lock; + bool config_updating; +}; + +struct mtk_crtc_state { + struct drm_crtc_state base; + + bool pending_config; + unsigned int pending_width; + unsigned int pending_height; + unsigned int pending_vrefresh; +}; + +static inline struct mtk_drm_crtc *to_mtk_crtc(struct drm_crtc *c) +{ + return container_of(c, struct mtk_drm_crtc, base); +} + +static inline struct mtk_crtc_state *to_mtk_crtc_state(struct drm_crtc_state *s) +{ + return container_of(s, struct mtk_crtc_state, base); +} + +static void mtk_drm_crtc_finish_page_flip(struct mtk_drm_crtc *mtk_crtc) +{ + struct drm_crtc *crtc = &mtk_crtc->base; + unsigned long flags; + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + drm_crtc_send_vblank_event(crtc, mtk_crtc->event); + drm_crtc_vblank_put(crtc); + mtk_crtc->event = NULL; + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); +} + +static void mtk_drm_finish_page_flip(struct mtk_drm_crtc *mtk_crtc) +{ + drm_crtc_handle_vblank(&mtk_crtc->base); + if (!mtk_crtc->config_updating && mtk_crtc->pending_needs_vblank) { + mtk_drm_crtc_finish_page_flip(mtk_crtc); + mtk_crtc->pending_needs_vblank = false; + } +} + +#if IS_REACHABLE(CONFIG_MTK_CMDQ) +static int mtk_drm_cmdq_pkt_create(struct cmdq_client *client, struct cmdq_pkt *pkt, + size_t size) +{ + struct device *dev; + dma_addr_t dma_addr; + + pkt->va_base = kzalloc(size, GFP_KERNEL); + if (!pkt->va_base) + return -ENOMEM; + + pkt->buf_size = size; + pkt->cl = (void *)client; + + dev = client->chan->mbox->dev; + dma_addr = dma_map_single(dev, pkt->va_base, pkt->buf_size, + DMA_TO_DEVICE); + if (dma_mapping_error(dev, dma_addr)) { + dev_err(dev, "dma map failed, size=%u\n", (u32)(u64)size); + kfree(pkt->va_base); + return -ENOMEM; + } + + pkt->pa_base = dma_addr; + + return 0; +} + +static void mtk_drm_cmdq_pkt_destroy(struct cmdq_pkt *pkt) +{ + struct cmdq_client *client = (struct cmdq_client *)pkt->cl; + + dma_unmap_single(client->chan->mbox->dev, pkt->pa_base, pkt->buf_size, + DMA_TO_DEVICE); + kfree(pkt->va_base); +} +#endif + +static void mtk_drm_crtc_destroy(struct drm_crtc *crtc) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + int i; + + mtk_mutex_put(mtk_crtc->mutex); +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + mtk_drm_cmdq_pkt_destroy(&mtk_crtc->cmdq_handle); + + if (mtk_crtc->cmdq_client.chan) { + mbox_free_channel(mtk_crtc->cmdq_client.chan); + mtk_crtc->cmdq_client.chan = NULL; + } +#endif + + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) { + struct mtk_ddp_comp *comp; + + comp = mtk_crtc->ddp_comp[i]; + mtk_ddp_comp_unregister_vblank_cb(comp); + } + + drm_crtc_cleanup(crtc); +} + +static void mtk_drm_crtc_reset(struct drm_crtc *crtc) +{ + struct mtk_crtc_state *state; + + if (crtc->state) + __drm_atomic_helper_crtc_destroy_state(crtc->state); + + kfree(to_mtk_crtc_state(crtc->state)); + crtc->state = NULL; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) + __drm_atomic_helper_crtc_reset(crtc, &state->base); +} + +static struct drm_crtc_state *mtk_drm_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct mtk_crtc_state *state; + + state = kmalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base); + + WARN_ON(state->base.crtc != crtc); + state->base.crtc = crtc; + state->pending_config = false; + + return &state->base; +} + +static void mtk_drm_crtc_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + __drm_atomic_helper_crtc_destroy_state(state); + kfree(to_mtk_crtc_state(state)); +} + +static bool mtk_drm_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* Nothing to do here, but this callback is mandatory. */ + return true; +} + +static void mtk_drm_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state); + + state->pending_width = crtc->mode.hdisplay; + state->pending_height = crtc->mode.vdisplay; + state->pending_vrefresh = drm_mode_vrefresh(&crtc->mode); + wmb(); /* Make sure the above parameters are set before update */ + state->pending_config = true; +} + +static int mtk_crtc_ddp_clk_enable(struct mtk_drm_crtc *mtk_crtc) +{ + int ret; + int i; + + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) { + ret = mtk_ddp_comp_clk_enable(mtk_crtc->ddp_comp[i]); + if (ret) { + DRM_ERROR("Failed to enable clock %d: %d\n", i, ret); + goto err; + } + } + + return 0; +err: + while (--i >= 0) + mtk_ddp_comp_clk_disable(mtk_crtc->ddp_comp[i]); + return ret; +} + +static void mtk_crtc_ddp_clk_disable(struct mtk_drm_crtc *mtk_crtc) +{ + int i; + + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) + mtk_ddp_comp_clk_disable(mtk_crtc->ddp_comp[i]); +} + +static +struct mtk_ddp_comp *mtk_drm_ddp_comp_for_plane(struct drm_crtc *crtc, + struct drm_plane *plane, + unsigned int *local_layer) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + struct mtk_ddp_comp *comp; + int i, count = 0; + unsigned int local_index = plane - mtk_crtc->planes; + + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) { + comp = mtk_crtc->ddp_comp[i]; + if (local_index < (count + mtk_ddp_comp_layer_nr(comp))) { + *local_layer = local_index - count; + return comp; + } + count += mtk_ddp_comp_layer_nr(comp); + } + + WARN(1, "Failed to find component for plane %d\n", plane->index); + return NULL; +} + +#if IS_REACHABLE(CONFIG_MTK_CMDQ) +static void ddp_cmdq_cb(struct mbox_client *cl, void *mssg) +{ + struct cmdq_cb_data *data = mssg; + struct cmdq_client *cmdq_cl = container_of(cl, struct cmdq_client, client); + struct mtk_drm_crtc *mtk_crtc = container_of(cmdq_cl, struct mtk_drm_crtc, cmdq_client); + struct mtk_crtc_state *state; + unsigned int i; + + if (data->sta < 0) + return; + + state = to_mtk_crtc_state(mtk_crtc->base.state); + + state->pending_config = false; + + if (mtk_crtc->pending_planes) { + for (i = 0; i < mtk_crtc->layer_nr; i++) { + struct drm_plane *plane = &mtk_crtc->planes[i]; + struct mtk_plane_state *plane_state; + + plane_state = to_mtk_plane_state(plane->state); + + plane_state->pending.config = false; + } + mtk_crtc->pending_planes = false; + } + + if (mtk_crtc->pending_async_planes) { + for (i = 0; i < mtk_crtc->layer_nr; i++) { + struct drm_plane *plane = &mtk_crtc->planes[i]; + struct mtk_plane_state *plane_state; + + plane_state = to_mtk_plane_state(plane->state); + + plane_state->pending.async_config = false; + } + mtk_crtc->pending_async_planes = false; + } + + mtk_crtc->cmdq_vblank_cnt = 0; + wake_up(&mtk_crtc->cb_blocking_queue); +} +#endif + +static int mtk_crtc_ddp_hw_init(struct mtk_drm_crtc *mtk_crtc) +{ + struct drm_crtc *crtc = &mtk_crtc->base; + struct drm_connector *connector; + struct drm_encoder *encoder; + struct drm_connector_list_iter conn_iter; + unsigned int width, height, vrefresh, bpc = MTK_MAX_BPC; + int ret; + int i; + + if (WARN_ON(!crtc->state)) + return -EINVAL; + + width = crtc->state->adjusted_mode.hdisplay; + height = crtc->state->adjusted_mode.vdisplay; + vrefresh = drm_mode_vrefresh(&crtc->state->adjusted_mode); + + drm_for_each_encoder(encoder, crtc->dev) { + if (encoder->crtc != crtc) + continue; + + drm_connector_list_iter_begin(crtc->dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + if (connector->encoder != encoder) + continue; + if (connector->display_info.bpc != 0 && + bpc > connector->display_info.bpc) + bpc = connector->display_info.bpc; + } + drm_connector_list_iter_end(&conn_iter); + } + + ret = pm_runtime_resume_and_get(crtc->dev->dev); + if (ret < 0) { + DRM_ERROR("Failed to enable power domain: %d\n", ret); + return ret; + } + + ret = mtk_mutex_prepare(mtk_crtc->mutex); + if (ret < 0) { + DRM_ERROR("Failed to enable mutex clock: %d\n", ret); + goto err_pm_runtime_put; + } + + ret = mtk_crtc_ddp_clk_enable(mtk_crtc); + if (ret < 0) { + DRM_ERROR("Failed to enable component clocks: %d\n", ret); + goto err_mutex_unprepare; + } + + for (i = 0; i < mtk_crtc->ddp_comp_nr - 1; i++) { + mtk_mmsys_ddp_connect(mtk_crtc->mmsys_dev, + mtk_crtc->ddp_comp[i]->id, + mtk_crtc->ddp_comp[i + 1]->id); + mtk_mutex_add_comp(mtk_crtc->mutex, + mtk_crtc->ddp_comp[i]->id); + } + mtk_mutex_add_comp(mtk_crtc->mutex, mtk_crtc->ddp_comp[i]->id); + mtk_mutex_enable(mtk_crtc->mutex); + + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) { + struct mtk_ddp_comp *comp = mtk_crtc->ddp_comp[i]; + + if (i == 1) + mtk_ddp_comp_bgclr_in_on(comp); + + mtk_ddp_comp_config(comp, width, height, vrefresh, bpc, NULL); + mtk_ddp_comp_start(comp); + } + + /* Initially configure all planes */ + for (i = 0; i < mtk_crtc->layer_nr; i++) { + struct drm_plane *plane = &mtk_crtc->planes[i]; + struct mtk_plane_state *plane_state; + struct mtk_ddp_comp *comp; + unsigned int local_layer; + + plane_state = to_mtk_plane_state(plane->state); + + /* should not enable layer before crtc enabled */ + plane_state->pending.enable = false; + comp = mtk_drm_ddp_comp_for_plane(crtc, plane, &local_layer); + if (comp) + mtk_ddp_comp_layer_config(comp, local_layer, + plane_state, NULL); + } + + return 0; + +err_mutex_unprepare: + mtk_mutex_unprepare(mtk_crtc->mutex); +err_pm_runtime_put: + pm_runtime_put(crtc->dev->dev); + return ret; +} + +static void mtk_crtc_ddp_hw_fini(struct mtk_drm_crtc *mtk_crtc) +{ + struct drm_device *drm = mtk_crtc->base.dev; + struct drm_crtc *crtc = &mtk_crtc->base; + int i; + + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) { + mtk_ddp_comp_stop(mtk_crtc->ddp_comp[i]); + if (i == 1) + mtk_ddp_comp_bgclr_in_off(mtk_crtc->ddp_comp[i]); + } + + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) + mtk_mutex_remove_comp(mtk_crtc->mutex, + mtk_crtc->ddp_comp[i]->id); + mtk_mutex_disable(mtk_crtc->mutex); + for (i = 0; i < mtk_crtc->ddp_comp_nr - 1; i++) { + mtk_mmsys_ddp_disconnect(mtk_crtc->mmsys_dev, + mtk_crtc->ddp_comp[i]->id, + mtk_crtc->ddp_comp[i + 1]->id); + mtk_mutex_remove_comp(mtk_crtc->mutex, + mtk_crtc->ddp_comp[i]->id); + } + mtk_mutex_remove_comp(mtk_crtc->mutex, mtk_crtc->ddp_comp[i]->id); + mtk_crtc_ddp_clk_disable(mtk_crtc); + mtk_mutex_unprepare(mtk_crtc->mutex); + + pm_runtime_put(drm->dev); + + if (crtc->state->event && !crtc->state->active) { + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + spin_unlock_irq(&crtc->dev->event_lock); + } +} + +static void mtk_crtc_ddp_config(struct drm_crtc *crtc, + struct cmdq_pkt *cmdq_handle) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + struct mtk_crtc_state *state = to_mtk_crtc_state(mtk_crtc->base.state); + struct mtk_ddp_comp *comp = mtk_crtc->ddp_comp[0]; + unsigned int i; + unsigned int local_layer; + + /* + * TODO: instead of updating the registers here, we should prepare + * working registers in atomic_commit and let the hardware command + * queue update module registers on vblank. + */ + if (state->pending_config) { + mtk_ddp_comp_config(comp, state->pending_width, + state->pending_height, + state->pending_vrefresh, 0, + cmdq_handle); + + if (!cmdq_handle) + state->pending_config = false; + } + + if (mtk_crtc->pending_planes) { + for (i = 0; i < mtk_crtc->layer_nr; i++) { + struct drm_plane *plane = &mtk_crtc->planes[i]; + struct mtk_plane_state *plane_state; + + plane_state = to_mtk_plane_state(plane->state); + + if (!plane_state->pending.config) + continue; + + comp = mtk_drm_ddp_comp_for_plane(crtc, plane, + &local_layer); + + if (comp) + mtk_ddp_comp_layer_config(comp, local_layer, + plane_state, + cmdq_handle); + if (!cmdq_handle) + plane_state->pending.config = false; + } + + if (!cmdq_handle) + mtk_crtc->pending_planes = false; + } + + if (mtk_crtc->pending_async_planes) { + for (i = 0; i < mtk_crtc->layer_nr; i++) { + struct drm_plane *plane = &mtk_crtc->planes[i]; + struct mtk_plane_state *plane_state; + + plane_state = to_mtk_plane_state(plane->state); + + if (!plane_state->pending.async_config) + continue; + + comp = mtk_drm_ddp_comp_for_plane(crtc, plane, + &local_layer); + + if (comp) + mtk_ddp_comp_layer_config(comp, local_layer, + plane_state, + cmdq_handle); + if (!cmdq_handle) + plane_state->pending.async_config = false; + } + + if (!cmdq_handle) + mtk_crtc->pending_async_planes = false; + } +} + +static void mtk_drm_crtc_update_config(struct mtk_drm_crtc *mtk_crtc, + bool needs_vblank) +{ +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + struct cmdq_pkt *cmdq_handle = &mtk_crtc->cmdq_handle; +#endif + struct drm_crtc *crtc = &mtk_crtc->base; + struct mtk_drm_private *priv = crtc->dev->dev_private; + unsigned int pending_planes = 0, pending_async_planes = 0; + int i; + + mutex_lock(&mtk_crtc->hw_lock); + mtk_crtc->config_updating = true; + if (needs_vblank) + mtk_crtc->pending_needs_vblank = true; + + for (i = 0; i < mtk_crtc->layer_nr; i++) { + struct drm_plane *plane = &mtk_crtc->planes[i]; + struct mtk_plane_state *plane_state; + + plane_state = to_mtk_plane_state(plane->state); + if (plane_state->pending.dirty) { + plane_state->pending.config = true; + plane_state->pending.dirty = false; + pending_planes |= BIT(i); + } else if (plane_state->pending.async_dirty) { + plane_state->pending.async_config = true; + plane_state->pending.async_dirty = false; + pending_async_planes |= BIT(i); + } + } + if (pending_planes) + mtk_crtc->pending_planes = true; + if (pending_async_planes) + mtk_crtc->pending_async_planes = true; + + if (priv->data->shadow_register) { + mtk_mutex_acquire(mtk_crtc->mutex); + mtk_crtc_ddp_config(crtc, NULL); + mtk_mutex_release(mtk_crtc->mutex); + } +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + if (mtk_crtc->cmdq_client.chan) { + mbox_flush(mtk_crtc->cmdq_client.chan, 2000); + cmdq_handle->cmd_buf_size = 0; + cmdq_pkt_clear_event(cmdq_handle, mtk_crtc->cmdq_event); + cmdq_pkt_wfe(cmdq_handle, mtk_crtc->cmdq_event, false); + mtk_crtc_ddp_config(crtc, cmdq_handle); + cmdq_pkt_finalize(cmdq_handle); + dma_sync_single_for_device(mtk_crtc->cmdq_client.chan->mbox->dev, + cmdq_handle->pa_base, + cmdq_handle->cmd_buf_size, + DMA_TO_DEVICE); + /* + * CMDQ command should execute in next 3 vblank. + * One vblank interrupt before send message (occasionally) + * and one vblank interrupt after cmdq done, + * so it's timeout after 3 vblank interrupt. + * If it fail to execute in next 3 vblank, timeout happen. + */ + mtk_crtc->cmdq_vblank_cnt = 3; + + mbox_send_message(mtk_crtc->cmdq_client.chan, cmdq_handle); + mbox_client_txdone(mtk_crtc->cmdq_client.chan, 0); + } +#endif + mtk_crtc->config_updating = false; + mutex_unlock(&mtk_crtc->hw_lock); +} + +static void mtk_crtc_ddp_irq(void *data) +{ + struct drm_crtc *crtc = data; + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + struct mtk_drm_private *priv = crtc->dev->dev_private; + +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + if (!priv->data->shadow_register && !mtk_crtc->cmdq_client.chan) + mtk_crtc_ddp_config(crtc, NULL); + else if (mtk_crtc->cmdq_vblank_cnt > 0 && --mtk_crtc->cmdq_vblank_cnt == 0) + DRM_ERROR("mtk_crtc %d CMDQ execute command timeout!\n", + drm_crtc_index(&mtk_crtc->base)); +#else + if (!priv->data->shadow_register) + mtk_crtc_ddp_config(crtc, NULL); +#endif + mtk_drm_finish_page_flip(mtk_crtc); +} + +static int mtk_drm_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + struct mtk_ddp_comp *comp = mtk_crtc->ddp_comp[0]; + + mtk_ddp_comp_enable_vblank(comp); + + return 0; +} + +static void mtk_drm_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + struct mtk_ddp_comp *comp = mtk_crtc->ddp_comp[0]; + + mtk_ddp_comp_disable_vblank(comp); +} + +int mtk_drm_crtc_plane_check(struct drm_crtc *crtc, struct drm_plane *plane, + struct mtk_plane_state *state) +{ + unsigned int local_layer; + struct mtk_ddp_comp *comp; + + comp = mtk_drm_ddp_comp_for_plane(crtc, plane, &local_layer); + if (comp) + return mtk_ddp_comp_layer_check(comp, local_layer, state); + return 0; +} + +void mtk_drm_crtc_async_update(struct drm_crtc *crtc, struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + + if (!mtk_crtc->enabled) + return; + + mtk_drm_crtc_update_config(mtk_crtc, false); +} + +static void mtk_drm_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + struct mtk_ddp_comp *comp = mtk_crtc->ddp_comp[0]; + int ret; + + DRM_DEBUG_DRIVER("%s %d\n", __func__, crtc->base.id); + + ret = pm_runtime_resume_and_get(comp->dev); + if (ret < 0) { + DRM_DEV_ERROR(comp->dev, "Failed to enable power domain: %d\n", ret); + return; + } + + ret = mtk_crtc_ddp_hw_init(mtk_crtc); + if (ret) { + pm_runtime_put(comp->dev); + return; + } + + drm_crtc_vblank_on(crtc); + mtk_crtc->enabled = true; +} + +static void mtk_drm_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + struct mtk_ddp_comp *comp = mtk_crtc->ddp_comp[0]; + int i, ret; + + DRM_DEBUG_DRIVER("%s %d\n", __func__, crtc->base.id); + if (!mtk_crtc->enabled) + return; + + /* Set all pending plane state to disabled */ + for (i = 0; i < mtk_crtc->layer_nr; i++) { + struct drm_plane *plane = &mtk_crtc->planes[i]; + struct mtk_plane_state *plane_state; + + plane_state = to_mtk_plane_state(plane->state); + plane_state->pending.enable = false; + plane_state->pending.config = true; + } + mtk_crtc->pending_planes = true; + + mtk_drm_crtc_update_config(mtk_crtc, false); +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + /* Wait for planes to be disabled by cmdq */ + if (mtk_crtc->cmdq_client.chan) + wait_event_timeout(mtk_crtc->cb_blocking_queue, + mtk_crtc->cmdq_vblank_cnt == 0, + msecs_to_jiffies(500)); +#endif + /* Wait for planes to be disabled */ + drm_crtc_wait_one_vblank(crtc); + + drm_crtc_vblank_off(crtc); + mtk_crtc_ddp_hw_fini(mtk_crtc); + ret = pm_runtime_put(comp->dev); + if (ret < 0) + DRM_DEV_ERROR(comp->dev, "Failed to disable power domain: %d\n", ret); + + mtk_crtc->enabled = false; +} + +static void mtk_drm_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); + struct mtk_crtc_state *mtk_crtc_state = to_mtk_crtc_state(crtc_state); + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + unsigned long flags; + + if (mtk_crtc->event && mtk_crtc_state->base.event) + DRM_ERROR("new event while there is still a pending event\n"); + + if (mtk_crtc_state->base.event) { + mtk_crtc_state->base.event->pipe = drm_crtc_index(crtc); + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + mtk_crtc->event = mtk_crtc_state->base.event; + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + + mtk_crtc_state->base.event = NULL; + } +} + +static void mtk_drm_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + int i; + + if (crtc->state->color_mgmt_changed) + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) { + mtk_ddp_gamma_set(mtk_crtc->ddp_comp[i], crtc->state); + mtk_ddp_ctm_set(mtk_crtc->ddp_comp[i], crtc->state); + } + mtk_drm_crtc_update_config(mtk_crtc, !!mtk_crtc->event); +} + +static const struct drm_crtc_funcs mtk_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .destroy = mtk_drm_crtc_destroy, + .reset = mtk_drm_crtc_reset, + .atomic_duplicate_state = mtk_drm_crtc_duplicate_state, + .atomic_destroy_state = mtk_drm_crtc_destroy_state, + .enable_vblank = mtk_drm_crtc_enable_vblank, + .disable_vblank = mtk_drm_crtc_disable_vblank, +}; + +static const struct drm_crtc_helper_funcs mtk_crtc_helper_funcs = { + .mode_fixup = mtk_drm_crtc_mode_fixup, + .mode_set_nofb = mtk_drm_crtc_mode_set_nofb, + .atomic_begin = mtk_drm_crtc_atomic_begin, + .atomic_flush = mtk_drm_crtc_atomic_flush, + .atomic_enable = mtk_drm_crtc_atomic_enable, + .atomic_disable = mtk_drm_crtc_atomic_disable, +}; + +static int mtk_drm_crtc_init(struct drm_device *drm, + struct mtk_drm_crtc *mtk_crtc, + unsigned int pipe) +{ + struct drm_plane *primary = NULL; + struct drm_plane *cursor = NULL; + int i, ret; + + for (i = 0; i < mtk_crtc->layer_nr; i++) { + if (mtk_crtc->planes[i].type == DRM_PLANE_TYPE_PRIMARY) + primary = &mtk_crtc->planes[i]; + else if (mtk_crtc->planes[i].type == DRM_PLANE_TYPE_CURSOR) + cursor = &mtk_crtc->planes[i]; + } + + ret = drm_crtc_init_with_planes(drm, &mtk_crtc->base, primary, cursor, + &mtk_crtc_funcs, NULL); + if (ret) + goto err_cleanup_crtc; + + drm_crtc_helper_add(&mtk_crtc->base, &mtk_crtc_helper_funcs); + + return 0; + +err_cleanup_crtc: + drm_crtc_cleanup(&mtk_crtc->base); + return ret; +} + +static int mtk_drm_crtc_num_comp_planes(struct mtk_drm_crtc *mtk_crtc, + int comp_idx) +{ + struct mtk_ddp_comp *comp; + + if (comp_idx > 1) + return 0; + + comp = mtk_crtc->ddp_comp[comp_idx]; + if (!comp->funcs) + return 0; + + if (comp_idx == 1 && !comp->funcs->bgclr_in_on) + return 0; + + return mtk_ddp_comp_layer_nr(comp); +} + +static inline +enum drm_plane_type mtk_drm_crtc_plane_type(unsigned int plane_idx, + unsigned int num_planes) +{ + if (plane_idx == 0) + return DRM_PLANE_TYPE_PRIMARY; + else if (plane_idx == (num_planes - 1)) + return DRM_PLANE_TYPE_CURSOR; + else + return DRM_PLANE_TYPE_OVERLAY; + +} + +static int mtk_drm_crtc_init_comp_planes(struct drm_device *drm_dev, + struct mtk_drm_crtc *mtk_crtc, + int comp_idx, int pipe) +{ + int num_planes = mtk_drm_crtc_num_comp_planes(mtk_crtc, comp_idx); + struct mtk_ddp_comp *comp = mtk_crtc->ddp_comp[comp_idx]; + int i, ret; + + for (i = 0; i < num_planes; i++) { + ret = mtk_plane_init(drm_dev, + &mtk_crtc->planes[mtk_crtc->layer_nr], + BIT(pipe), + mtk_drm_crtc_plane_type(mtk_crtc->layer_nr, + num_planes), + mtk_ddp_comp_supported_rotations(comp)); + if (ret) + return ret; + + mtk_crtc->layer_nr++; + } + return 0; +} + +int mtk_drm_crtc_create(struct drm_device *drm_dev, + const enum mtk_ddp_comp_id *path, unsigned int path_len) +{ + struct mtk_drm_private *priv = drm_dev->dev_private; + struct device *dev = drm_dev->dev; + struct mtk_drm_crtc *mtk_crtc; + unsigned int num_comp_planes = 0; + int pipe = priv->num_pipes; + int ret; + int i; + bool has_ctm = false; + uint gamma_lut_size = 0; + + if (!path) + return 0; + + for (i = 0; i < path_len; i++) { + enum mtk_ddp_comp_id comp_id = path[i]; + struct device_node *node; + struct mtk_ddp_comp *comp; + + node = priv->comp_node[comp_id]; + comp = &priv->ddp_comp[comp_id]; + + if (!node) { + dev_info(dev, + "Not creating crtc %d because component %d is disabled or missing\n", + pipe, comp_id); + return 0; + } + + if (!comp->dev) { + dev_err(dev, "Component %pOF not initialized\n", node); + return -ENODEV; + } + } + + mtk_crtc = devm_kzalloc(dev, sizeof(*mtk_crtc), GFP_KERNEL); + if (!mtk_crtc) + return -ENOMEM; + + mtk_crtc->mmsys_dev = priv->mmsys_dev; + mtk_crtc->ddp_comp_nr = path_len; + mtk_crtc->ddp_comp = devm_kmalloc_array(dev, mtk_crtc->ddp_comp_nr, + sizeof(*mtk_crtc->ddp_comp), + GFP_KERNEL); + if (!mtk_crtc->ddp_comp) + return -ENOMEM; + + mtk_crtc->mutex = mtk_mutex_get(priv->mutex_dev); + if (IS_ERR(mtk_crtc->mutex)) { + ret = PTR_ERR(mtk_crtc->mutex); + dev_err(dev, "Failed to get mutex: %d\n", ret); + return ret; + } + + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) { + enum mtk_ddp_comp_id comp_id = path[i]; + struct mtk_ddp_comp *comp; + + comp = &priv->ddp_comp[comp_id]; + mtk_crtc->ddp_comp[i] = comp; + + if (comp->funcs) { + if (comp->funcs->gamma_set) + gamma_lut_size = MTK_LUT_SIZE; + + if (comp->funcs->ctm_set) + has_ctm = true; + } + + mtk_ddp_comp_register_vblank_cb(comp, mtk_crtc_ddp_irq, + &mtk_crtc->base); + } + + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) + num_comp_planes += mtk_drm_crtc_num_comp_planes(mtk_crtc, i); + + mtk_crtc->planes = devm_kcalloc(dev, num_comp_planes, + sizeof(struct drm_plane), GFP_KERNEL); + if (!mtk_crtc->planes) + return -ENOMEM; + + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) { + ret = mtk_drm_crtc_init_comp_planes(drm_dev, mtk_crtc, i, + pipe); + if (ret) + return ret; + } + + ret = mtk_drm_crtc_init(drm_dev, mtk_crtc, pipe); + if (ret < 0) + return ret; + + if (gamma_lut_size) + drm_mode_crtc_set_gamma_size(&mtk_crtc->base, gamma_lut_size); + drm_crtc_enable_color_mgmt(&mtk_crtc->base, 0, has_ctm, gamma_lut_size); + priv->num_pipes++; + mutex_init(&mtk_crtc->hw_lock); + +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + mtk_crtc->cmdq_client.client.dev = mtk_crtc->mmsys_dev; + mtk_crtc->cmdq_client.client.tx_block = false; + mtk_crtc->cmdq_client.client.knows_txdone = true; + mtk_crtc->cmdq_client.client.rx_callback = ddp_cmdq_cb; + mtk_crtc->cmdq_client.chan = + mbox_request_channel(&mtk_crtc->cmdq_client.client, + drm_crtc_index(&mtk_crtc->base)); + if (IS_ERR(mtk_crtc->cmdq_client.chan)) { + dev_dbg(dev, "mtk_crtc %d failed to create mailbox client, writing register by CPU now\n", + drm_crtc_index(&mtk_crtc->base)); + mtk_crtc->cmdq_client.chan = NULL; + } + + if (mtk_crtc->cmdq_client.chan) { + ret = of_property_read_u32_index(priv->mutex_node, + "mediatek,gce-events", + drm_crtc_index(&mtk_crtc->base), + &mtk_crtc->cmdq_event); + if (ret) { + dev_dbg(dev, "mtk_crtc %d failed to get mediatek,gce-events property\n", + drm_crtc_index(&mtk_crtc->base)); + mbox_free_channel(mtk_crtc->cmdq_client.chan); + mtk_crtc->cmdq_client.chan = NULL; + } else { + ret = mtk_drm_cmdq_pkt_create(&mtk_crtc->cmdq_client, + &mtk_crtc->cmdq_handle, + PAGE_SIZE); + if (ret) { + dev_dbg(dev, "mtk_crtc %d failed to create cmdq packet\n", + drm_crtc_index(&mtk_crtc->base)); + mbox_free_channel(mtk_crtc->cmdq_client.chan); + mtk_crtc->cmdq_client.chan = NULL; + } + } + + /* for sending blocking cmd in crtc disable */ + init_waitqueue_head(&mtk_crtc->cb_blocking_queue); + } +#endif + return 0; +} diff --git a/drivers/gpu/drm/mediatek/mtk_drm_crtc.h b/drivers/gpu/drm/mediatek/mtk_drm_crtc.h new file mode 100644 index 000000000..cb9a36c48 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015 MediaTek Inc. + */ + +#ifndef MTK_DRM_CRTC_H +#define MTK_DRM_CRTC_H + +#include <drm/drm_crtc.h> +#include "mtk_drm_ddp_comp.h" +#include "mtk_drm_plane.h" + +#define MTK_LUT_SIZE 512 +#define MTK_MAX_BPC 10 +#define MTK_MIN_BPC 3 + +void mtk_drm_crtc_commit(struct drm_crtc *crtc); +int mtk_drm_crtc_create(struct drm_device *drm_dev, + const enum mtk_ddp_comp_id *path, + unsigned int path_len); +int mtk_drm_crtc_plane_check(struct drm_crtc *crtc, struct drm_plane *plane, + struct mtk_plane_state *state); +void mtk_drm_crtc_async_update(struct drm_crtc *crtc, struct drm_plane *plane, + struct drm_atomic_state *plane_state); + +#endif /* MTK_DRM_CRTC_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c new file mode 100644 index 000000000..6b6d5335c --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c @@ -0,0 +1,576 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 MediaTek Inc. + * Authors: + * YT Shen <yt.shen@mediatek.com> + * CK Hu <ck.hu@mediatek.com> + */ + +#include <linux/clk.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/soc/mediatek/mtk-cmdq.h> +#include <drm/drm_print.h> + +#include "mtk_disp_drv.h" +#include "mtk_drm_drv.h" +#include "mtk_drm_plane.h" +#include "mtk_drm_ddp_comp.h" +#include "mtk_drm_crtc.h" + + +#define DISP_REG_DITHER_EN 0x0000 +#define DITHER_EN BIT(0) +#define DISP_REG_DITHER_CFG 0x0020 +#define DITHER_RELAY_MODE BIT(0) +#define DITHER_ENGINE_EN BIT(1) +#define DISP_DITHERING BIT(2) +#define DISP_REG_DITHER_SIZE 0x0030 +#define DISP_REG_DITHER_5 0x0114 +#define DISP_REG_DITHER_7 0x011c +#define DISP_REG_DITHER_15 0x013c +#define DITHER_LSB_ERR_SHIFT_R(x) (((x) & 0x7) << 28) +#define DITHER_ADD_LSHIFT_R(x) (((x) & 0x7) << 20) +#define DITHER_NEW_BIT_MODE BIT(0) +#define DISP_REG_DITHER_16 0x0140 +#define DITHER_LSB_ERR_SHIFT_B(x) (((x) & 0x7) << 28) +#define DITHER_ADD_LSHIFT_B(x) (((x) & 0x7) << 20) +#define DITHER_LSB_ERR_SHIFT_G(x) (((x) & 0x7) << 12) +#define DITHER_ADD_LSHIFT_G(x) (((x) & 0x7) << 4) + +#define DISP_REG_DSC_CON 0x0000 +#define DSC_EN BIT(0) +#define DSC_DUAL_INOUT BIT(2) +#define DSC_BYPASS BIT(4) +#define DSC_UFOE_SEL BIT(16) + +#define DISP_REG_OD_EN 0x0000 +#define DISP_REG_OD_CFG 0x0020 +#define OD_RELAYMODE BIT(0) +#define DISP_REG_OD_SIZE 0x0030 + +#define DISP_REG_POSTMASK_EN 0x0000 +#define POSTMASK_EN BIT(0) +#define DISP_REG_POSTMASK_CFG 0x0020 +#define POSTMASK_RELAY_MODE BIT(0) +#define DISP_REG_POSTMASK_SIZE 0x0030 + +#define DISP_REG_UFO_START 0x0000 +#define UFO_BYPASS BIT(2) + +struct mtk_ddp_comp_dev { + struct clk *clk; + void __iomem *regs; + struct cmdq_client_reg cmdq_reg; +}; + +void mtk_ddp_write(struct cmdq_pkt *cmdq_pkt, unsigned int value, + struct cmdq_client_reg *cmdq_reg, void __iomem *regs, + unsigned int offset) +{ +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + if (cmdq_pkt) + cmdq_pkt_write(cmdq_pkt, cmdq_reg->subsys, + cmdq_reg->offset + offset, value); + else +#endif + writel(value, regs + offset); +} + +void mtk_ddp_write_relaxed(struct cmdq_pkt *cmdq_pkt, unsigned int value, + struct cmdq_client_reg *cmdq_reg, void __iomem *regs, + unsigned int offset) +{ +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + if (cmdq_pkt) + cmdq_pkt_write(cmdq_pkt, cmdq_reg->subsys, + cmdq_reg->offset + offset, value); + else +#endif + writel_relaxed(value, regs + offset); +} + +void mtk_ddp_write_mask(struct cmdq_pkt *cmdq_pkt, unsigned int value, + struct cmdq_client_reg *cmdq_reg, void __iomem *regs, + unsigned int offset, unsigned int mask) +{ +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + if (cmdq_pkt) { + cmdq_pkt_write_mask(cmdq_pkt, cmdq_reg->subsys, + cmdq_reg->offset + offset, value, mask); + } else { +#endif + u32 tmp = readl(regs + offset); + + tmp = (tmp & ~mask) | (value & mask); + writel(tmp, regs + offset); +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + } +#endif +} + +static int mtk_ddp_clk_enable(struct device *dev) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + return clk_prepare_enable(priv->clk); +} + +static void mtk_ddp_clk_disable(struct device *dev) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + clk_disable_unprepare(priv->clk); +} + +void mtk_dither_set_common(void __iomem *regs, struct cmdq_client_reg *cmdq_reg, + unsigned int bpc, unsigned int cfg, + unsigned int dither_en, struct cmdq_pkt *cmdq_pkt) +{ + /* If bpc equal to 0, the dithering function didn't be enabled */ + if (bpc == 0) + return; + + if (bpc >= MTK_MIN_BPC) { + mtk_ddp_write(cmdq_pkt, 0, cmdq_reg, regs, DISP_REG_DITHER_5); + mtk_ddp_write(cmdq_pkt, 0, cmdq_reg, regs, DISP_REG_DITHER_7); + mtk_ddp_write(cmdq_pkt, + DITHER_LSB_ERR_SHIFT_R(MTK_MAX_BPC - bpc) | + DITHER_ADD_LSHIFT_R(MTK_MAX_BPC - bpc) | + DITHER_NEW_BIT_MODE, + cmdq_reg, regs, DISP_REG_DITHER_15); + mtk_ddp_write(cmdq_pkt, + DITHER_LSB_ERR_SHIFT_B(MTK_MAX_BPC - bpc) | + DITHER_ADD_LSHIFT_B(MTK_MAX_BPC - bpc) | + DITHER_LSB_ERR_SHIFT_G(MTK_MAX_BPC - bpc) | + DITHER_ADD_LSHIFT_G(MTK_MAX_BPC - bpc), + cmdq_reg, regs, DISP_REG_DITHER_16); + mtk_ddp_write(cmdq_pkt, dither_en, cmdq_reg, regs, cfg); + } +} + +static void mtk_dither_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + mtk_ddp_write(cmdq_pkt, w << 16 | h, &priv->cmdq_reg, priv->regs, DISP_REG_DITHER_SIZE); + mtk_ddp_write(cmdq_pkt, DITHER_RELAY_MODE, &priv->cmdq_reg, priv->regs, + DISP_REG_DITHER_CFG); + mtk_dither_set_common(priv->regs, &priv->cmdq_reg, bpc, DISP_REG_DITHER_CFG, + DITHER_ENGINE_EN, cmdq_pkt); +} + +static void mtk_dither_start(struct device *dev) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + writel(DITHER_EN, priv->regs + DISP_REG_DITHER_EN); +} + +static void mtk_dither_stop(struct device *dev) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + writel_relaxed(0x0, priv->regs + DISP_REG_DITHER_EN); +} + +static void mtk_dither_set(struct device *dev, unsigned int bpc, + unsigned int cfg, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + mtk_dither_set_common(priv->regs, &priv->cmdq_reg, bpc, cfg, + DISP_DITHERING, cmdq_pkt); +} + +static void mtk_dsc_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + /* dsc bypass mode */ + mtk_ddp_write_mask(cmdq_pkt, DSC_BYPASS, &priv->cmdq_reg, priv->regs, + DISP_REG_DSC_CON, DSC_BYPASS); + mtk_ddp_write_mask(cmdq_pkt, DSC_UFOE_SEL, &priv->cmdq_reg, priv->regs, + DISP_REG_DSC_CON, DSC_UFOE_SEL); + mtk_ddp_write_mask(cmdq_pkt, DSC_DUAL_INOUT, &priv->cmdq_reg, priv->regs, + DISP_REG_DSC_CON, DSC_DUAL_INOUT); +} + +static void mtk_dsc_start(struct device *dev) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + /* write with mask to reserve the value set in mtk_dsc_config */ + mtk_ddp_write_mask(NULL, DSC_EN, &priv->cmdq_reg, priv->regs, DISP_REG_DSC_CON, DSC_EN); +} + +static void mtk_dsc_stop(struct device *dev) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + writel_relaxed(0x0, priv->regs + DISP_REG_DSC_CON); +} + +static void mtk_od_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + mtk_ddp_write(cmdq_pkt, w << 16 | h, &priv->cmdq_reg, priv->regs, DISP_REG_OD_SIZE); + mtk_ddp_write(cmdq_pkt, OD_RELAYMODE, &priv->cmdq_reg, priv->regs, DISP_REG_OD_CFG); + mtk_dither_set(dev, bpc, DISP_REG_OD_CFG, cmdq_pkt); +} + +static void mtk_od_start(struct device *dev) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + writel(1, priv->regs + DISP_REG_OD_EN); +} + +static void mtk_postmask_config(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + mtk_ddp_write(cmdq_pkt, w << 16 | h, &priv->cmdq_reg, priv->regs, + DISP_REG_POSTMASK_SIZE); + mtk_ddp_write(cmdq_pkt, POSTMASK_RELAY_MODE, &priv->cmdq_reg, + priv->regs, DISP_REG_POSTMASK_CFG); +} + +static void mtk_postmask_start(struct device *dev) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + writel(POSTMASK_EN, priv->regs + DISP_REG_POSTMASK_EN); +} + +static void mtk_postmask_stop(struct device *dev) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + writel_relaxed(0x0, priv->regs + DISP_REG_POSTMASK_EN); +} + +static void mtk_ufoe_start(struct device *dev) +{ + struct mtk_ddp_comp_dev *priv = dev_get_drvdata(dev); + + writel(UFO_BYPASS, priv->regs + DISP_REG_UFO_START); +} + +static const struct mtk_ddp_comp_funcs ddp_aal = { + .clk_enable = mtk_aal_clk_enable, + .clk_disable = mtk_aal_clk_disable, + .gamma_set = mtk_aal_gamma_set, + .config = mtk_aal_config, + .start = mtk_aal_start, + .stop = mtk_aal_stop, +}; + +static const struct mtk_ddp_comp_funcs ddp_ccorr = { + .clk_enable = mtk_ccorr_clk_enable, + .clk_disable = mtk_ccorr_clk_disable, + .config = mtk_ccorr_config, + .start = mtk_ccorr_start, + .stop = mtk_ccorr_stop, + .ctm_set = mtk_ccorr_ctm_set, +}; + +static const struct mtk_ddp_comp_funcs ddp_color = { + .clk_enable = mtk_color_clk_enable, + .clk_disable = mtk_color_clk_disable, + .config = mtk_color_config, + .start = mtk_color_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_dither = { + .clk_enable = mtk_ddp_clk_enable, + .clk_disable = mtk_ddp_clk_disable, + .config = mtk_dither_config, + .start = mtk_dither_start, + .stop = mtk_dither_stop, +}; + +static const struct mtk_ddp_comp_funcs ddp_dpi = { + .start = mtk_dpi_start, + .stop = mtk_dpi_stop, +}; + +static const struct mtk_ddp_comp_funcs ddp_dsc = { + .clk_enable = mtk_ddp_clk_enable, + .clk_disable = mtk_ddp_clk_disable, + .config = mtk_dsc_config, + .start = mtk_dsc_start, + .stop = mtk_dsc_stop, +}; + +static const struct mtk_ddp_comp_funcs ddp_dsi = { + .start = mtk_dsi_ddp_start, + .stop = mtk_dsi_ddp_stop, +}; + +static const struct mtk_ddp_comp_funcs ddp_gamma = { + .clk_enable = mtk_gamma_clk_enable, + .clk_disable = mtk_gamma_clk_disable, + .gamma_set = mtk_gamma_set, + .config = mtk_gamma_config, + .start = mtk_gamma_start, + .stop = mtk_gamma_stop, +}; + +static const struct mtk_ddp_comp_funcs ddp_merge = { + .clk_enable = mtk_merge_clk_enable, + .clk_disable = mtk_merge_clk_disable, + .start = mtk_merge_start, + .stop = mtk_merge_stop, + .config = mtk_merge_config, +}; + +static const struct mtk_ddp_comp_funcs ddp_od = { + .clk_enable = mtk_ddp_clk_enable, + .clk_disable = mtk_ddp_clk_disable, + .config = mtk_od_config, + .start = mtk_od_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_ovl = { + .clk_enable = mtk_ovl_clk_enable, + .clk_disable = mtk_ovl_clk_disable, + .config = mtk_ovl_config, + .start = mtk_ovl_start, + .stop = mtk_ovl_stop, + .register_vblank_cb = mtk_ovl_register_vblank_cb, + .unregister_vblank_cb = mtk_ovl_unregister_vblank_cb, + .enable_vblank = mtk_ovl_enable_vblank, + .disable_vblank = mtk_ovl_disable_vblank, + .supported_rotations = mtk_ovl_supported_rotations, + .layer_nr = mtk_ovl_layer_nr, + .layer_check = mtk_ovl_layer_check, + .layer_config = mtk_ovl_layer_config, + .bgclr_in_on = mtk_ovl_bgclr_in_on, + .bgclr_in_off = mtk_ovl_bgclr_in_off, +}; + +static const struct mtk_ddp_comp_funcs ddp_postmask = { + .clk_enable = mtk_ddp_clk_enable, + .clk_disable = mtk_ddp_clk_disable, + .config = mtk_postmask_config, + .start = mtk_postmask_start, + .stop = mtk_postmask_stop, +}; + +static const struct mtk_ddp_comp_funcs ddp_rdma = { + .clk_enable = mtk_rdma_clk_enable, + .clk_disable = mtk_rdma_clk_disable, + .config = mtk_rdma_config, + .start = mtk_rdma_start, + .stop = mtk_rdma_stop, + .register_vblank_cb = mtk_rdma_register_vblank_cb, + .unregister_vblank_cb = mtk_rdma_unregister_vblank_cb, + .enable_vblank = mtk_rdma_enable_vblank, + .disable_vblank = mtk_rdma_disable_vblank, + .layer_nr = mtk_rdma_layer_nr, + .layer_config = mtk_rdma_layer_config, +}; + +static const struct mtk_ddp_comp_funcs ddp_ufoe = { + .clk_enable = mtk_ddp_clk_enable, + .clk_disable = mtk_ddp_clk_disable, + .start = mtk_ufoe_start, +}; + +static const char * const mtk_ddp_comp_stem[MTK_DDP_COMP_TYPE_MAX] = { + [MTK_DISP_AAL] = "aal", + [MTK_DISP_BLS] = "bls", + [MTK_DISP_CCORR] = "ccorr", + [MTK_DISP_COLOR] = "color", + [MTK_DISP_DITHER] = "dither", + [MTK_DISP_DSC] = "dsc", + [MTK_DISP_GAMMA] = "gamma", + [MTK_DISP_MERGE] = "merge", + [MTK_DISP_MUTEX] = "mutex", + [MTK_DISP_OD] = "od", + [MTK_DISP_OVL] = "ovl", + [MTK_DISP_OVL_2L] = "ovl-2l", + [MTK_DISP_POSTMASK] = "postmask", + [MTK_DISP_PWM] = "pwm", + [MTK_DISP_RDMA] = "rdma", + [MTK_DISP_UFOE] = "ufoe", + [MTK_DISP_WDMA] = "wdma", + [MTK_DP_INTF] = "dp-intf", + [MTK_DPI] = "dpi", + [MTK_DSI] = "dsi", +}; + +struct mtk_ddp_comp_match { + enum mtk_ddp_comp_type type; + int alias_id; + const struct mtk_ddp_comp_funcs *funcs; +}; + +static const struct mtk_ddp_comp_match mtk_ddp_matches[DDP_COMPONENT_ID_MAX] = { + [DDP_COMPONENT_AAL0] = { MTK_DISP_AAL, 0, &ddp_aal }, + [DDP_COMPONENT_AAL1] = { MTK_DISP_AAL, 1, &ddp_aal }, + [DDP_COMPONENT_BLS] = { MTK_DISP_BLS, 0, NULL }, + [DDP_COMPONENT_CCORR] = { MTK_DISP_CCORR, 0, &ddp_ccorr }, + [DDP_COMPONENT_COLOR0] = { MTK_DISP_COLOR, 0, &ddp_color }, + [DDP_COMPONENT_COLOR1] = { MTK_DISP_COLOR, 1, &ddp_color }, + [DDP_COMPONENT_DITHER0] = { MTK_DISP_DITHER, 0, &ddp_dither }, + [DDP_COMPONENT_DP_INTF0] = { MTK_DP_INTF, 0, &ddp_dpi }, + [DDP_COMPONENT_DP_INTF1] = { MTK_DP_INTF, 1, &ddp_dpi }, + [DDP_COMPONENT_DPI0] = { MTK_DPI, 0, &ddp_dpi }, + [DDP_COMPONENT_DPI1] = { MTK_DPI, 1, &ddp_dpi }, + [DDP_COMPONENT_DSC0] = { MTK_DISP_DSC, 0, &ddp_dsc }, + [DDP_COMPONENT_DSC1] = { MTK_DISP_DSC, 1, &ddp_dsc }, + [DDP_COMPONENT_DSI0] = { MTK_DSI, 0, &ddp_dsi }, + [DDP_COMPONENT_DSI1] = { MTK_DSI, 1, &ddp_dsi }, + [DDP_COMPONENT_DSI2] = { MTK_DSI, 2, &ddp_dsi }, + [DDP_COMPONENT_DSI3] = { MTK_DSI, 3, &ddp_dsi }, + [DDP_COMPONENT_GAMMA] = { MTK_DISP_GAMMA, 0, &ddp_gamma }, + [DDP_COMPONENT_MERGE0] = { MTK_DISP_MERGE, 0, &ddp_merge }, + [DDP_COMPONENT_MERGE1] = { MTK_DISP_MERGE, 1, &ddp_merge }, + [DDP_COMPONENT_MERGE2] = { MTK_DISP_MERGE, 2, &ddp_merge }, + [DDP_COMPONENT_MERGE3] = { MTK_DISP_MERGE, 3, &ddp_merge }, + [DDP_COMPONENT_MERGE4] = { MTK_DISP_MERGE, 4, &ddp_merge }, + [DDP_COMPONENT_MERGE5] = { MTK_DISP_MERGE, 5, &ddp_merge }, + [DDP_COMPONENT_OD0] = { MTK_DISP_OD, 0, &ddp_od }, + [DDP_COMPONENT_OD1] = { MTK_DISP_OD, 1, &ddp_od }, + [DDP_COMPONENT_OVL0] = { MTK_DISP_OVL, 0, &ddp_ovl }, + [DDP_COMPONENT_OVL1] = { MTK_DISP_OVL, 1, &ddp_ovl }, + [DDP_COMPONENT_OVL_2L0] = { MTK_DISP_OVL_2L, 0, &ddp_ovl }, + [DDP_COMPONENT_OVL_2L1] = { MTK_DISP_OVL_2L, 1, &ddp_ovl }, + [DDP_COMPONENT_OVL_2L2] = { MTK_DISP_OVL_2L, 2, &ddp_ovl }, + [DDP_COMPONENT_POSTMASK0] = { MTK_DISP_POSTMASK, 0, &ddp_postmask }, + [DDP_COMPONENT_PWM0] = { MTK_DISP_PWM, 0, NULL }, + [DDP_COMPONENT_PWM1] = { MTK_DISP_PWM, 1, NULL }, + [DDP_COMPONENT_PWM2] = { MTK_DISP_PWM, 2, NULL }, + [DDP_COMPONENT_RDMA0] = { MTK_DISP_RDMA, 0, &ddp_rdma }, + [DDP_COMPONENT_RDMA1] = { MTK_DISP_RDMA, 1, &ddp_rdma }, + [DDP_COMPONENT_RDMA2] = { MTK_DISP_RDMA, 2, &ddp_rdma }, + [DDP_COMPONENT_RDMA4] = { MTK_DISP_RDMA, 4, &ddp_rdma }, + [DDP_COMPONENT_UFOE] = { MTK_DISP_UFOE, 0, &ddp_ufoe }, + [DDP_COMPONENT_WDMA0] = { MTK_DISP_WDMA, 0, NULL }, + [DDP_COMPONENT_WDMA1] = { MTK_DISP_WDMA, 1, NULL }, +}; + +static bool mtk_drm_find_comp_in_ddp(struct device *dev, + const enum mtk_ddp_comp_id *path, + unsigned int path_len, + struct mtk_ddp_comp *ddp_comp) +{ + unsigned int i; + + if (path == NULL) + return false; + + for (i = 0U; i < path_len; i++) + if (dev == ddp_comp[path[i]].dev) + return true; + + return false; +} + +int mtk_ddp_comp_get_id(struct device_node *node, + enum mtk_ddp_comp_type comp_type) +{ + int id = of_alias_get_id(node, mtk_ddp_comp_stem[comp_type]); + int i; + + for (i = 0; i < ARRAY_SIZE(mtk_ddp_matches); i++) { + if (comp_type == mtk_ddp_matches[i].type && + (id < 0 || id == mtk_ddp_matches[i].alias_id)) + return i; + } + + return -EINVAL; +} + +unsigned int mtk_drm_find_possible_crtc_by_comp(struct drm_device *drm, + struct device *dev) +{ + struct mtk_drm_private *private = drm->dev_private; + unsigned int ret = 0; + + if (mtk_drm_find_comp_in_ddp(dev, private->data->main_path, private->data->main_len, + private->ddp_comp)) + ret = BIT(0); + else if (mtk_drm_find_comp_in_ddp(dev, private->data->ext_path, + private->data->ext_len, private->ddp_comp)) + ret = BIT(1); + else if (mtk_drm_find_comp_in_ddp(dev, private->data->third_path, + private->data->third_len, private->ddp_comp)) + ret = BIT(2); + else + DRM_INFO("Failed to find comp in ddp table\n"); + + return ret; +} + +int mtk_ddp_comp_init(struct device_node *node, struct mtk_ddp_comp *comp, + enum mtk_ddp_comp_id comp_id) +{ + struct platform_device *comp_pdev; + enum mtk_ddp_comp_type type; + struct mtk_ddp_comp_dev *priv; +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + int ret; +#endif + + if (comp_id < 0 || comp_id >= DDP_COMPONENT_ID_MAX) + return -EINVAL; + + type = mtk_ddp_matches[comp_id].type; + + comp->id = comp_id; + comp->funcs = mtk_ddp_matches[comp_id].funcs; + comp_pdev = of_find_device_by_node(node); + if (!comp_pdev) { + DRM_INFO("Waiting for device %s\n", node->full_name); + return -EPROBE_DEFER; + } + comp->dev = &comp_pdev->dev; + + if (type == MTK_DISP_AAL || + type == MTK_DISP_BLS || + type == MTK_DISP_CCORR || + type == MTK_DISP_COLOR || + type == MTK_DISP_GAMMA || + type == MTK_DISP_MERGE || + type == MTK_DISP_OVL || + type == MTK_DISP_OVL_2L || + type == MTK_DISP_PWM || + type == MTK_DISP_RDMA || + type == MTK_DPI || + type == MTK_DP_INTF || + type == MTK_DSI) + return 0; + + priv = devm_kzalloc(comp->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regs = of_iomap(node, 0); + priv->clk = of_clk_get(node, 0); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + ret = cmdq_dev_get_client_reg(comp->dev, &priv->cmdq_reg, 0); + if (ret) + dev_dbg(comp->dev, "get mediatek,gce-client-reg fail!\n"); +#endif + + platform_set_drvdata(comp_pdev, priv); + + return 0; +} diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h new file mode 100644 index 000000000..2d0052c23 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h @@ -0,0 +1,222 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015 MediaTek Inc. + */ + +#ifndef MTK_DRM_DDP_COMP_H +#define MTK_DRM_DDP_COMP_H + +#include <linux/io.h> +#include <linux/soc/mediatek/mtk-cmdq.h> +#include <linux/soc/mediatek/mtk-mmsys.h> + +struct device; +struct device_node; +struct drm_crtc; +struct drm_device; +struct mtk_plane_state; +struct drm_crtc_state; + +enum mtk_ddp_comp_type { + MTK_DISP_AAL, + MTK_DISP_BLS, + MTK_DISP_CCORR, + MTK_DISP_COLOR, + MTK_DISP_DITHER, + MTK_DISP_DSC, + MTK_DISP_GAMMA, + MTK_DISP_MERGE, + MTK_DISP_MUTEX, + MTK_DISP_OD, + MTK_DISP_OVL, + MTK_DISP_OVL_2L, + MTK_DISP_POSTMASK, + MTK_DISP_PWM, + MTK_DISP_RDMA, + MTK_DISP_UFOE, + MTK_DISP_WDMA, + MTK_DPI, + MTK_DP_INTF, + MTK_DSI, + MTK_DDP_COMP_TYPE_MAX, +}; + +struct mtk_ddp_comp; +struct cmdq_pkt; +struct mtk_ddp_comp_funcs { + int (*clk_enable)(struct device *dev); + void (*clk_disable)(struct device *dev); + void (*config)(struct device *dev, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt); + void (*start)(struct device *dev); + void (*stop)(struct device *dev); + void (*register_vblank_cb)(struct device *dev, + void (*vblank_cb)(void *), + void *vblank_cb_data); + void (*unregister_vblank_cb)(struct device *dev); + void (*enable_vblank)(struct device *dev); + void (*disable_vblank)(struct device *dev); + unsigned int (*supported_rotations)(struct device *dev); + unsigned int (*layer_nr)(struct device *dev); + int (*layer_check)(struct device *dev, + unsigned int idx, + struct mtk_plane_state *state); + void (*layer_config)(struct device *dev, unsigned int idx, + struct mtk_plane_state *state, + struct cmdq_pkt *cmdq_pkt); + void (*gamma_set)(struct device *dev, + struct drm_crtc_state *state); + void (*bgclr_in_on)(struct device *dev); + void (*bgclr_in_off)(struct device *dev); + void (*ctm_set)(struct device *dev, + struct drm_crtc_state *state); +}; + +struct mtk_ddp_comp { + struct device *dev; + int irq; + enum mtk_ddp_comp_id id; + const struct mtk_ddp_comp_funcs *funcs; +}; + +static inline int mtk_ddp_comp_clk_enable(struct mtk_ddp_comp *comp) +{ + if (comp->funcs && comp->funcs->clk_enable) + return comp->funcs->clk_enable(comp->dev); + + return 0; +} + +static inline void mtk_ddp_comp_clk_disable(struct mtk_ddp_comp *comp) +{ + if (comp->funcs && comp->funcs->clk_disable) + comp->funcs->clk_disable(comp->dev); +} + +static inline void mtk_ddp_comp_config(struct mtk_ddp_comp *comp, + unsigned int w, unsigned int h, + unsigned int vrefresh, unsigned int bpc, + struct cmdq_pkt *cmdq_pkt) +{ + if (comp->funcs && comp->funcs->config) + comp->funcs->config(comp->dev, w, h, vrefresh, bpc, cmdq_pkt); +} + +static inline void mtk_ddp_comp_start(struct mtk_ddp_comp *comp) +{ + if (comp->funcs && comp->funcs->start) + comp->funcs->start(comp->dev); +} + +static inline void mtk_ddp_comp_stop(struct mtk_ddp_comp *comp) +{ + if (comp->funcs && comp->funcs->stop) + comp->funcs->stop(comp->dev); +} + +static inline void mtk_ddp_comp_register_vblank_cb(struct mtk_ddp_comp *comp, + void (*vblank_cb)(void *), + void *vblank_cb_data) +{ + if (comp->funcs && comp->funcs->register_vblank_cb) + comp->funcs->register_vblank_cb(comp->dev, vblank_cb, + vblank_cb_data); +} + +static inline void mtk_ddp_comp_unregister_vblank_cb(struct mtk_ddp_comp *comp) +{ + if (comp->funcs && comp->funcs->unregister_vblank_cb) + comp->funcs->unregister_vblank_cb(comp->dev); +} + +static inline void mtk_ddp_comp_enable_vblank(struct mtk_ddp_comp *comp) +{ + if (comp->funcs && comp->funcs->enable_vblank) + comp->funcs->enable_vblank(comp->dev); +} + +static inline void mtk_ddp_comp_disable_vblank(struct mtk_ddp_comp *comp) +{ + if (comp->funcs && comp->funcs->disable_vblank) + comp->funcs->disable_vblank(comp->dev); +} + +static inline +unsigned int mtk_ddp_comp_supported_rotations(struct mtk_ddp_comp *comp) +{ + if (comp->funcs && comp->funcs->supported_rotations) + return comp->funcs->supported_rotations(comp->dev); + + return 0; +} + +static inline unsigned int mtk_ddp_comp_layer_nr(struct mtk_ddp_comp *comp) +{ + if (comp->funcs && comp->funcs->layer_nr) + return comp->funcs->layer_nr(comp->dev); + + return 0; +} + +static inline int mtk_ddp_comp_layer_check(struct mtk_ddp_comp *comp, + unsigned int idx, + struct mtk_plane_state *state) +{ + if (comp->funcs && comp->funcs->layer_check) + return comp->funcs->layer_check(comp->dev, idx, state); + return 0; +} + +static inline void mtk_ddp_comp_layer_config(struct mtk_ddp_comp *comp, + unsigned int idx, + struct mtk_plane_state *state, + struct cmdq_pkt *cmdq_pkt) +{ + if (comp->funcs && comp->funcs->layer_config) + comp->funcs->layer_config(comp->dev, idx, state, cmdq_pkt); +} + +static inline void mtk_ddp_gamma_set(struct mtk_ddp_comp *comp, + struct drm_crtc_state *state) +{ + if (comp->funcs && comp->funcs->gamma_set) + comp->funcs->gamma_set(comp->dev, state); +} + +static inline void mtk_ddp_comp_bgclr_in_on(struct mtk_ddp_comp *comp) +{ + if (comp->funcs && comp->funcs->bgclr_in_on) + comp->funcs->bgclr_in_on(comp->dev); +} + +static inline void mtk_ddp_comp_bgclr_in_off(struct mtk_ddp_comp *comp) +{ + if (comp->funcs && comp->funcs->bgclr_in_off) + comp->funcs->bgclr_in_off(comp->dev); +} + +static inline void mtk_ddp_ctm_set(struct mtk_ddp_comp *comp, + struct drm_crtc_state *state) +{ + if (comp->funcs && comp->funcs->ctm_set) + comp->funcs->ctm_set(comp->dev, state); +} + +int mtk_ddp_comp_get_id(struct device_node *node, + enum mtk_ddp_comp_type comp_type); +unsigned int mtk_drm_find_possible_crtc_by_comp(struct drm_device *drm, + struct device *dev); +int mtk_ddp_comp_init(struct device_node *comp_node, struct mtk_ddp_comp *comp, + enum mtk_ddp_comp_id comp_id); +enum mtk_ddp_comp_type mtk_ddp_comp_get_type(enum mtk_ddp_comp_id comp_id); +void mtk_ddp_write(struct cmdq_pkt *cmdq_pkt, unsigned int value, + struct cmdq_client_reg *cmdq_reg, void __iomem *regs, + unsigned int offset); +void mtk_ddp_write_relaxed(struct cmdq_pkt *cmdq_pkt, unsigned int value, + struct cmdq_client_reg *cmdq_reg, void __iomem *regs, + unsigned int offset); +void mtk_ddp_write_mask(struct cmdq_pkt *cmdq_pkt, unsigned int value, + struct cmdq_client_reg *cmdq_reg, void __iomem *regs, + unsigned int offset, unsigned int mask); +#endif /* MTK_DRM_DDP_COMP_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c new file mode 100644 index 000000000..25639fbfd --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -0,0 +1,897 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: YT SHEN <yt.shen@mediatek.com> + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/component.h> +#include <linux/iommu.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <linux/dma-mapping.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp_comp.h" +#include "mtk_drm_drv.h" +#include "mtk_drm_gem.h" + +#define DRIVER_NAME "mediatek" +#define DRIVER_DESC "Mediatek SoC DRM" +#define DRIVER_DATE "20150513" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +static const struct drm_mode_config_helper_funcs mtk_drm_mode_config_helpers = { + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, +}; + +static struct drm_framebuffer * +mtk_drm_mode_fb_create(struct drm_device *dev, + struct drm_file *file, + const struct drm_mode_fb_cmd2 *cmd) +{ + const struct drm_format_info *info = drm_get_format_info(dev, cmd); + + if (info->num_planes != 1) + return ERR_PTR(-EINVAL); + + return drm_gem_fb_create(dev, file, cmd); +} + +static const struct drm_mode_config_funcs mtk_drm_mode_config_funcs = { + .fb_create = mtk_drm_mode_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static const enum mtk_ddp_comp_id mt2701_mtk_ddp_main[] = { + DDP_COMPONENT_OVL0, + DDP_COMPONENT_RDMA0, + DDP_COMPONENT_COLOR0, + DDP_COMPONENT_BLS, + DDP_COMPONENT_DSI0, +}; + +static const enum mtk_ddp_comp_id mt2701_mtk_ddp_ext[] = { + DDP_COMPONENT_RDMA1, + DDP_COMPONENT_DPI0, +}; + +static const enum mtk_ddp_comp_id mt7623_mtk_ddp_main[] = { + DDP_COMPONENT_OVL0, + DDP_COMPONENT_RDMA0, + DDP_COMPONENT_COLOR0, + DDP_COMPONENT_BLS, + DDP_COMPONENT_DPI0, +}; + +static const enum mtk_ddp_comp_id mt7623_mtk_ddp_ext[] = { + DDP_COMPONENT_RDMA1, + DDP_COMPONENT_DSI0, +}; + +static const enum mtk_ddp_comp_id mt2712_mtk_ddp_main[] = { + DDP_COMPONENT_OVL0, + DDP_COMPONENT_COLOR0, + DDP_COMPONENT_AAL0, + DDP_COMPONENT_OD0, + DDP_COMPONENT_RDMA0, + DDP_COMPONENT_DPI0, + DDP_COMPONENT_PWM0, +}; + +static const enum mtk_ddp_comp_id mt2712_mtk_ddp_ext[] = { + DDP_COMPONENT_OVL1, + DDP_COMPONENT_COLOR1, + DDP_COMPONENT_AAL1, + DDP_COMPONENT_OD1, + DDP_COMPONENT_RDMA1, + DDP_COMPONENT_DPI1, + DDP_COMPONENT_PWM1, +}; + +static const enum mtk_ddp_comp_id mt2712_mtk_ddp_third[] = { + DDP_COMPONENT_RDMA2, + DDP_COMPONENT_DSI3, + DDP_COMPONENT_PWM2, +}; + +static enum mtk_ddp_comp_id mt8167_mtk_ddp_main[] = { + DDP_COMPONENT_OVL0, + DDP_COMPONENT_COLOR0, + DDP_COMPONENT_CCORR, + DDP_COMPONENT_AAL0, + DDP_COMPONENT_GAMMA, + DDP_COMPONENT_DITHER0, + DDP_COMPONENT_RDMA0, + DDP_COMPONENT_DSI0, +}; + +static const enum mtk_ddp_comp_id mt8173_mtk_ddp_main[] = { + DDP_COMPONENT_OVL0, + DDP_COMPONENT_COLOR0, + DDP_COMPONENT_AAL0, + DDP_COMPONENT_OD0, + DDP_COMPONENT_RDMA0, + DDP_COMPONENT_UFOE, + DDP_COMPONENT_DSI0, + DDP_COMPONENT_PWM0, +}; + +static const enum mtk_ddp_comp_id mt8173_mtk_ddp_ext[] = { + DDP_COMPONENT_OVL1, + DDP_COMPONENT_COLOR1, + DDP_COMPONENT_GAMMA, + DDP_COMPONENT_RDMA1, + DDP_COMPONENT_DPI0, +}; + +static const enum mtk_ddp_comp_id mt8183_mtk_ddp_main[] = { + DDP_COMPONENT_OVL0, + DDP_COMPONENT_OVL_2L0, + DDP_COMPONENT_RDMA0, + DDP_COMPONENT_COLOR0, + DDP_COMPONENT_CCORR, + DDP_COMPONENT_AAL0, + DDP_COMPONENT_GAMMA, + DDP_COMPONENT_DITHER0, + DDP_COMPONENT_DSI0, +}; + +static const enum mtk_ddp_comp_id mt8183_mtk_ddp_ext[] = { + DDP_COMPONENT_OVL_2L1, + DDP_COMPONENT_RDMA1, + DDP_COMPONENT_DPI0, +}; + +static const enum mtk_ddp_comp_id mt8186_mtk_ddp_main[] = { + DDP_COMPONENT_OVL0, + DDP_COMPONENT_RDMA0, + DDP_COMPONENT_COLOR0, + DDP_COMPONENT_CCORR, + DDP_COMPONENT_AAL0, + DDP_COMPONENT_GAMMA, + DDP_COMPONENT_POSTMASK0, + DDP_COMPONENT_DITHER0, + DDP_COMPONENT_DSI0, +}; + +static const enum mtk_ddp_comp_id mt8186_mtk_ddp_ext[] = { + DDP_COMPONENT_OVL_2L0, + DDP_COMPONENT_RDMA1, + DDP_COMPONENT_DPI0, +}; + +static const enum mtk_ddp_comp_id mt8192_mtk_ddp_main[] = { + DDP_COMPONENT_OVL0, + DDP_COMPONENT_OVL_2L0, + DDP_COMPONENT_RDMA0, + DDP_COMPONENT_COLOR0, + DDP_COMPONENT_CCORR, + DDP_COMPONENT_AAL0, + DDP_COMPONENT_GAMMA, + DDP_COMPONENT_POSTMASK0, + DDP_COMPONENT_DITHER0, + DDP_COMPONENT_DSI0, +}; + +static const enum mtk_ddp_comp_id mt8192_mtk_ddp_ext[] = { + DDP_COMPONENT_OVL_2L2, + DDP_COMPONENT_RDMA4, + DDP_COMPONENT_DPI0, +}; + +static const enum mtk_ddp_comp_id mt8195_mtk_ddp_main[] = { + DDP_COMPONENT_OVL0, + DDP_COMPONENT_RDMA0, + DDP_COMPONENT_COLOR0, + DDP_COMPONENT_CCORR, + DDP_COMPONENT_AAL0, + DDP_COMPONENT_GAMMA, + DDP_COMPONENT_DITHER0, + DDP_COMPONENT_DSC0, + DDP_COMPONENT_MERGE0, + DDP_COMPONENT_DP_INTF0, +}; + +static const struct mtk_mmsys_driver_data mt2701_mmsys_driver_data = { + .main_path = mt2701_mtk_ddp_main, + .main_len = ARRAY_SIZE(mt2701_mtk_ddp_main), + .ext_path = mt2701_mtk_ddp_ext, + .ext_len = ARRAY_SIZE(mt2701_mtk_ddp_ext), + .shadow_register = true, +}; + +static const struct mtk_mmsys_match_data mt2701_mmsys_match_data = { + .num_drv_data = 1, + .drv_data = { + &mt2701_mmsys_driver_data, + }, +}; + +static const struct mtk_mmsys_driver_data mt7623_mmsys_driver_data = { + .main_path = mt7623_mtk_ddp_main, + .main_len = ARRAY_SIZE(mt7623_mtk_ddp_main), + .ext_path = mt7623_mtk_ddp_ext, + .ext_len = ARRAY_SIZE(mt7623_mtk_ddp_ext), + .shadow_register = true, +}; + +static const struct mtk_mmsys_match_data mt7623_mmsys_match_data = { + .num_drv_data = 1, + .drv_data = { + &mt7623_mmsys_driver_data, + }, +}; + +static const struct mtk_mmsys_driver_data mt2712_mmsys_driver_data = { + .main_path = mt2712_mtk_ddp_main, + .main_len = ARRAY_SIZE(mt2712_mtk_ddp_main), + .ext_path = mt2712_mtk_ddp_ext, + .ext_len = ARRAY_SIZE(mt2712_mtk_ddp_ext), + .third_path = mt2712_mtk_ddp_third, + .third_len = ARRAY_SIZE(mt2712_mtk_ddp_third), +}; + +static const struct mtk_mmsys_match_data mt2712_mmsys_match_data = { + .num_drv_data = 1, + .drv_data = { + &mt2712_mmsys_driver_data, + }, +}; + +static const struct mtk_mmsys_driver_data mt8167_mmsys_driver_data = { + .main_path = mt8167_mtk_ddp_main, + .main_len = ARRAY_SIZE(mt8167_mtk_ddp_main), +}; + +static const struct mtk_mmsys_match_data mt8167_mmsys_match_data = { + .num_drv_data = 1, + .drv_data = { + &mt8167_mmsys_driver_data, + }, +}; + +static const struct mtk_mmsys_driver_data mt8173_mmsys_driver_data = { + .main_path = mt8173_mtk_ddp_main, + .main_len = ARRAY_SIZE(mt8173_mtk_ddp_main), + .ext_path = mt8173_mtk_ddp_ext, + .ext_len = ARRAY_SIZE(mt8173_mtk_ddp_ext), +}; + +static const struct mtk_mmsys_match_data mt8173_mmsys_match_data = { + .num_drv_data = 1, + .drv_data = { + &mt8173_mmsys_driver_data, + }, +}; + +static const struct mtk_mmsys_driver_data mt8183_mmsys_driver_data = { + .main_path = mt8183_mtk_ddp_main, + .main_len = ARRAY_SIZE(mt8183_mtk_ddp_main), + .ext_path = mt8183_mtk_ddp_ext, + .ext_len = ARRAY_SIZE(mt8183_mtk_ddp_ext), +}; + +static const struct mtk_mmsys_match_data mt8183_mmsys_match_data = { + .num_drv_data = 1, + .drv_data = { + &mt8183_mmsys_driver_data, + }, +}; + +static const struct mtk_mmsys_driver_data mt8186_mmsys_driver_data = { + .main_path = mt8186_mtk_ddp_main, + .main_len = ARRAY_SIZE(mt8186_mtk_ddp_main), + .ext_path = mt8186_mtk_ddp_ext, + .ext_len = ARRAY_SIZE(mt8186_mtk_ddp_ext), +}; + +static const struct mtk_mmsys_match_data mt8186_mmsys_match_data = { + .num_drv_data = 1, + .drv_data = { + &mt8186_mmsys_driver_data, + }, +}; + +static const struct mtk_mmsys_driver_data mt8192_mmsys_driver_data = { + .main_path = mt8192_mtk_ddp_main, + .main_len = ARRAY_SIZE(mt8192_mtk_ddp_main), + .ext_path = mt8192_mtk_ddp_ext, + .ext_len = ARRAY_SIZE(mt8192_mtk_ddp_ext), +}; + +static const struct mtk_mmsys_match_data mt8192_mmsys_match_data = { + .num_drv_data = 1, + .drv_data = { + &mt8192_mmsys_driver_data, + }, +}; + +static const struct mtk_mmsys_driver_data mt8195_vdosys0_driver_data = { + .io_start = 0x1c01a000, + .main_path = mt8195_mtk_ddp_main, + .main_len = ARRAY_SIZE(mt8195_mtk_ddp_main), +}; + +static const struct mtk_mmsys_driver_data mt8195_vdosys1_driver_data = { + .io_start = 0x1c100000, +}; + +static const struct mtk_mmsys_match_data mt8195_mmsys_match_data = { + .num_drv_data = 1, + .drv_data = { + &mt8195_vdosys0_driver_data, + &mt8195_vdosys1_driver_data, + }, +}; + +static int mtk_drm_kms_init(struct drm_device *drm) +{ + struct mtk_drm_private *private = drm->dev_private; + struct platform_device *pdev; + struct device_node *np; + struct device *dma_dev; + int ret; + + if (drm_firmware_drivers_only()) + return -ENODEV; + + if (!iommu_present(&platform_bus_type)) + return -EPROBE_DEFER; + + pdev = of_find_device_by_node(private->mutex_node); + if (!pdev) { + dev_err(drm->dev, "Waiting for disp-mutex device %pOF\n", + private->mutex_node); + of_node_put(private->mutex_node); + return -EPROBE_DEFER; + } + private->mutex_dev = &pdev->dev; + + ret = drmm_mode_config_init(drm); + if (ret) + goto put_mutex_dev; + + drm->mode_config.min_width = 64; + drm->mode_config.min_height = 64; + + /* + * set max width and height as default value(4096x4096). + * this value would be used to check framebuffer size limitation + * at drm_mode_addfb(). + */ + drm->mode_config.max_width = 4096; + drm->mode_config.max_height = 4096; + drm->mode_config.funcs = &mtk_drm_mode_config_funcs; + drm->mode_config.helper_private = &mtk_drm_mode_config_helpers; + + ret = component_bind_all(drm->dev, drm); + if (ret) + goto put_mutex_dev; + + /* + * We currently support two fixed data streams, each optional, + * and each statically assigned to a crtc: + * OVL0 -> COLOR0 -> AAL -> OD -> RDMA0 -> UFOE -> DSI0 ... + */ + ret = mtk_drm_crtc_create(drm, private->data->main_path, + private->data->main_len); + if (ret < 0) + goto err_component_unbind; + /* ... and OVL1 -> COLOR1 -> GAMMA -> RDMA1 -> DPI0. */ + ret = mtk_drm_crtc_create(drm, private->data->ext_path, + private->data->ext_len); + if (ret < 0) + goto err_component_unbind; + + ret = mtk_drm_crtc_create(drm, private->data->third_path, + private->data->third_len); + if (ret < 0) + goto err_component_unbind; + + /* Use OVL device for all DMA memory allocations */ + np = private->comp_node[private->data->main_path[0]] ?: + private->comp_node[private->data->ext_path[0]]; + pdev = of_find_device_by_node(np); + if (!pdev) { + ret = -ENODEV; + dev_err(drm->dev, "Need at least one OVL device\n"); + goto err_component_unbind; + } + + dma_dev = &pdev->dev; + private->dma_dev = dma_dev; + + /* + * Configure the DMA segment size to make sure we get contiguous IOVA + * when importing PRIME buffers. + */ + ret = dma_set_max_seg_size(dma_dev, UINT_MAX); + if (ret) { + dev_err(dma_dev, "Failed to set DMA segment size\n"); + goto err_component_unbind; + } + + ret = drm_vblank_init(drm, MAX_CRTC); + if (ret < 0) + goto err_component_unbind; + + drm_kms_helper_poll_init(drm); + drm_mode_config_reset(drm); + + return 0; + +err_component_unbind: + component_unbind_all(drm->dev, drm); +put_mutex_dev: + put_device(private->mutex_dev); + return ret; +} + +static void mtk_drm_kms_deinit(struct drm_device *drm) +{ + drm_kms_helper_poll_fini(drm); + drm_atomic_helper_shutdown(drm); + + component_unbind_all(drm->dev, drm); +} + +DEFINE_DRM_GEM_FOPS(mtk_drm_fops); + +/* + * We need to override this because the device used to import the memory is + * not dev->dev, as drm_gem_prime_import() expects. + */ +static struct drm_gem_object *mtk_drm_gem_prime_import(struct drm_device *dev, + struct dma_buf *dma_buf) +{ + struct mtk_drm_private *private = dev->dev_private; + + return drm_gem_prime_import_dev(dev, dma_buf, private->dma_dev); +} + +static const struct drm_driver mtk_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + + .dumb_create = mtk_drm_gem_dumb_create, + + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_import = mtk_drm_gem_prime_import, + .gem_prime_import_sg_table = mtk_gem_prime_import_sg_table, + .gem_prime_mmap = drm_gem_prime_mmap, + .fops = &mtk_drm_fops, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, +}; + +static int mtk_drm_bind(struct device *dev) +{ + struct mtk_drm_private *private = dev_get_drvdata(dev); + struct drm_device *drm; + int ret; + + drm = drm_dev_alloc(&mtk_drm_driver, dev); + if (IS_ERR(drm)) + return PTR_ERR(drm); + + drm->dev_private = private; + private->drm = drm; + + ret = mtk_drm_kms_init(drm); + if (ret < 0) + goto err_free; + + ret = drm_dev_register(drm, 0); + if (ret < 0) + goto err_deinit; + + drm_fbdev_generic_setup(drm, 32); + + return 0; + +err_deinit: + mtk_drm_kms_deinit(drm); +err_free: + private->drm = NULL; + drm_dev_put(drm); + return ret; +} + +static void mtk_drm_unbind(struct device *dev) +{ + struct mtk_drm_private *private = dev_get_drvdata(dev); + + drm_dev_unregister(private->drm); + mtk_drm_kms_deinit(private->drm); + drm_dev_put(private->drm); + private->num_pipes = 0; + private->drm = NULL; +} + +static const struct component_master_ops mtk_drm_ops = { + .bind = mtk_drm_bind, + .unbind = mtk_drm_unbind, +}; + +static const struct of_device_id mtk_ddp_comp_dt_ids[] = { + { .compatible = "mediatek,mt8167-disp-aal", + .data = (void *)MTK_DISP_AAL}, + { .compatible = "mediatek,mt8173-disp-aal", + .data = (void *)MTK_DISP_AAL}, + { .compatible = "mediatek,mt8183-disp-aal", + .data = (void *)MTK_DISP_AAL}, + { .compatible = "mediatek,mt8192-disp-aal", + .data = (void *)MTK_DISP_AAL}, + { .compatible = "mediatek,mt8167-disp-ccorr", + .data = (void *)MTK_DISP_CCORR }, + { .compatible = "mediatek,mt8183-disp-ccorr", + .data = (void *)MTK_DISP_CCORR }, + { .compatible = "mediatek,mt8192-disp-ccorr", + .data = (void *)MTK_DISP_CCORR }, + { .compatible = "mediatek,mt2701-disp-color", + .data = (void *)MTK_DISP_COLOR }, + { .compatible = "mediatek,mt8167-disp-color", + .data = (void *)MTK_DISP_COLOR }, + { .compatible = "mediatek,mt8173-disp-color", + .data = (void *)MTK_DISP_COLOR }, + { .compatible = "mediatek,mt8167-disp-dither", + .data = (void *)MTK_DISP_DITHER }, + { .compatible = "mediatek,mt8183-disp-dither", + .data = (void *)MTK_DISP_DITHER }, + { .compatible = "mediatek,mt8195-disp-dsc", + .data = (void *)MTK_DISP_DSC }, + { .compatible = "mediatek,mt8167-disp-gamma", + .data = (void *)MTK_DISP_GAMMA, }, + { .compatible = "mediatek,mt8173-disp-gamma", + .data = (void *)MTK_DISP_GAMMA, }, + { .compatible = "mediatek,mt8183-disp-gamma", + .data = (void *)MTK_DISP_GAMMA, }, + { .compatible = "mediatek,mt8195-disp-merge", + .data = (void *)MTK_DISP_MERGE }, + { .compatible = "mediatek,mt2701-disp-mutex", + .data = (void *)MTK_DISP_MUTEX }, + { .compatible = "mediatek,mt2712-disp-mutex", + .data = (void *)MTK_DISP_MUTEX }, + { .compatible = "mediatek,mt8167-disp-mutex", + .data = (void *)MTK_DISP_MUTEX }, + { .compatible = "mediatek,mt8173-disp-mutex", + .data = (void *)MTK_DISP_MUTEX }, + { .compatible = "mediatek,mt8183-disp-mutex", + .data = (void *)MTK_DISP_MUTEX }, + { .compatible = "mediatek,mt8186-disp-mutex", + .data = (void *)MTK_DISP_MUTEX }, + { .compatible = "mediatek,mt8192-disp-mutex", + .data = (void *)MTK_DISP_MUTEX }, + { .compatible = "mediatek,mt8195-disp-mutex", + .data = (void *)MTK_DISP_MUTEX }, + { .compatible = "mediatek,mt8173-disp-od", + .data = (void *)MTK_DISP_OD }, + { .compatible = "mediatek,mt2701-disp-ovl", + .data = (void *)MTK_DISP_OVL }, + { .compatible = "mediatek,mt8167-disp-ovl", + .data = (void *)MTK_DISP_OVL }, + { .compatible = "mediatek,mt8173-disp-ovl", + .data = (void *)MTK_DISP_OVL }, + { .compatible = "mediatek,mt8183-disp-ovl", + .data = (void *)MTK_DISP_OVL }, + { .compatible = "mediatek,mt8192-disp-ovl", + .data = (void *)MTK_DISP_OVL }, + { .compatible = "mediatek,mt8183-disp-ovl-2l", + .data = (void *)MTK_DISP_OVL_2L }, + { .compatible = "mediatek,mt8192-disp-ovl-2l", + .data = (void *)MTK_DISP_OVL_2L }, + { .compatible = "mediatek,mt8192-disp-postmask", + .data = (void *)MTK_DISP_POSTMASK }, + { .compatible = "mediatek,mt2701-disp-pwm", + .data = (void *)MTK_DISP_BLS }, + { .compatible = "mediatek,mt8167-disp-pwm", + .data = (void *)MTK_DISP_PWM }, + { .compatible = "mediatek,mt8173-disp-pwm", + .data = (void *)MTK_DISP_PWM }, + { .compatible = "mediatek,mt2701-disp-rdma", + .data = (void *)MTK_DISP_RDMA }, + { .compatible = "mediatek,mt8167-disp-rdma", + .data = (void *)MTK_DISP_RDMA }, + { .compatible = "mediatek,mt8173-disp-rdma", + .data = (void *)MTK_DISP_RDMA }, + { .compatible = "mediatek,mt8183-disp-rdma", + .data = (void *)MTK_DISP_RDMA }, + { .compatible = "mediatek,mt8195-disp-rdma", + .data = (void *)MTK_DISP_RDMA }, + { .compatible = "mediatek,mt8173-disp-ufoe", + .data = (void *)MTK_DISP_UFOE }, + { .compatible = "mediatek,mt8173-disp-wdma", + .data = (void *)MTK_DISP_WDMA }, + { .compatible = "mediatek,mt2701-dpi", + .data = (void *)MTK_DPI }, + { .compatible = "mediatek,mt8167-dsi", + .data = (void *)MTK_DSI }, + { .compatible = "mediatek,mt8173-dpi", + .data = (void *)MTK_DPI }, + { .compatible = "mediatek,mt8183-dpi", + .data = (void *)MTK_DPI }, + { .compatible = "mediatek,mt8192-dpi", + .data = (void *)MTK_DPI }, + { .compatible = "mediatek,mt8195-dp-intf", + .data = (void *)MTK_DP_INTF }, + { .compatible = "mediatek,mt2701-dsi", + .data = (void *)MTK_DSI }, + { .compatible = "mediatek,mt8173-dsi", + .data = (void *)MTK_DSI }, + { .compatible = "mediatek,mt8183-dsi", + .data = (void *)MTK_DSI }, + { .compatible = "mediatek,mt8186-dsi", + .data = (void *)MTK_DSI }, + { } +}; + +static const struct of_device_id mtk_drm_of_ids[] = { + { .compatible = "mediatek,mt2701-mmsys", + .data = &mt2701_mmsys_match_data}, + { .compatible = "mediatek,mt7623-mmsys", + .data = &mt7623_mmsys_match_data}, + { .compatible = "mediatek,mt2712-mmsys", + .data = &mt2712_mmsys_match_data}, + { .compatible = "mediatek,mt8167-mmsys", + .data = &mt8167_mmsys_match_data}, + { .compatible = "mediatek,mt8173-mmsys", + .data = &mt8173_mmsys_match_data}, + { .compatible = "mediatek,mt8183-mmsys", + .data = &mt8183_mmsys_match_data}, + { .compatible = "mediatek,mt8186-mmsys", + .data = &mt8186_mmsys_match_data}, + { .compatible = "mediatek,mt8192-mmsys", + .data = &mt8192_mmsys_match_data}, + { .compatible = "mediatek,mt8195-mmsys", + .data = &mt8195_mmsys_match_data}, + { } +}; +MODULE_DEVICE_TABLE(of, mtk_drm_of_ids); + +static int mtk_drm_find_match_data(struct device *dev, + const struct mtk_mmsys_match_data *match_data) +{ + int i; + struct platform_device *pdev = of_find_device_by_node(dev->parent->of_node); + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "failed to get parent resource\n"); + return -EINVAL; + } + + for (i = 0; i < match_data->num_drv_data; i++) + if (match_data->drv_data[i]->io_start == res->start) + return i; + + return -EINVAL; +} + +static int mtk_drm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *phandle = dev->parent->of_node; + const struct of_device_id *of_id; + const struct mtk_mmsys_match_data *match_data; + struct mtk_drm_private *private; + struct device_node *node; + struct component_match *match = NULL; + int ret; + int i; + + private = devm_kzalloc(dev, sizeof(*private), GFP_KERNEL); + if (!private) + return -ENOMEM; + + private->mmsys_dev = dev->parent; + if (!private->mmsys_dev) { + dev_err(dev, "Failed to get MMSYS device\n"); + return -ENODEV; + } + + of_id = of_match_node(mtk_drm_of_ids, phandle); + if (!of_id) + return -ENODEV; + + match_data = of_id->data; + if (match_data->num_drv_data > 1) { + /* This SoC has multiple mmsys channels */ + ret = mtk_drm_find_match_data(dev, match_data); + if (ret < 0) { + dev_err(dev, "Couldn't get match driver data\n"); + return ret; + } + private->data = match_data->drv_data[ret]; + } else { + dev_dbg(dev, "Using single mmsys channel\n"); + private->data = match_data->drv_data[0]; + } + + /* Iterate over sibling DISP function blocks */ + for_each_child_of_node(phandle->parent, node) { + const struct of_device_id *of_id; + enum mtk_ddp_comp_type comp_type; + int comp_id; + + of_id = of_match_node(mtk_ddp_comp_dt_ids, node); + if (!of_id) + continue; + + if (!of_device_is_available(node)) { + dev_dbg(dev, "Skipping disabled component %pOF\n", + node); + continue; + } + + comp_type = (enum mtk_ddp_comp_type)of_id->data; + + if (comp_type == MTK_DISP_MUTEX) { + private->mutex_node = of_node_get(node); + continue; + } + + comp_id = mtk_ddp_comp_get_id(node, comp_type); + if (comp_id < 0) { + dev_warn(dev, "Skipping unknown component %pOF\n", + node); + continue; + } + + private->comp_node[comp_id] = of_node_get(node); + + /* + * Currently only the AAL, CCORR, COLOR, GAMMA, MERGE, OVL, RDMA, DSI, and DPI + * blocks have separate component platform drivers and initialize their own + * DDP component structure. The others are initialized here. + */ + if (comp_type == MTK_DISP_AAL || + comp_type == MTK_DISP_CCORR || + comp_type == MTK_DISP_COLOR || + comp_type == MTK_DISP_GAMMA || + comp_type == MTK_DISP_MERGE || + comp_type == MTK_DISP_OVL || + comp_type == MTK_DISP_OVL_2L || + comp_type == MTK_DISP_RDMA || + comp_type == MTK_DP_INTF || + comp_type == MTK_DPI || + comp_type == MTK_DSI) { + dev_info(dev, "Adding component match for %pOF\n", + node); + drm_of_component_match_add(dev, &match, component_compare_of, + node); + } + + ret = mtk_ddp_comp_init(node, &private->ddp_comp[comp_id], comp_id); + if (ret) { + of_node_put(node); + goto err_node; + } + } + + if (!private->mutex_node) { + dev_err(dev, "Failed to find disp-mutex node\n"); + ret = -ENODEV; + goto err_node; + } + + pm_runtime_enable(dev); + + platform_set_drvdata(pdev, private); + + ret = component_master_add_with_match(dev, &mtk_drm_ops, match); + if (ret) + goto err_pm; + + return 0; + +err_pm: + pm_runtime_disable(dev); +err_node: + of_node_put(private->mutex_node); + for (i = 0; i < DDP_COMPONENT_ID_MAX; i++) + of_node_put(private->comp_node[i]); + return ret; +} + +static int mtk_drm_remove(struct platform_device *pdev) +{ + struct mtk_drm_private *private = platform_get_drvdata(pdev); + int i; + + component_master_del(&pdev->dev, &mtk_drm_ops); + pm_runtime_disable(&pdev->dev); + of_node_put(private->mutex_node); + for (i = 0; i < DDP_COMPONENT_ID_MAX; i++) + of_node_put(private->comp_node[i]); + + return 0; +} + +static int mtk_drm_sys_prepare(struct device *dev) +{ + struct mtk_drm_private *private = dev_get_drvdata(dev); + struct drm_device *drm = private->drm; + + return drm_mode_config_helper_suspend(drm); +} + +static void mtk_drm_sys_complete(struct device *dev) +{ + struct mtk_drm_private *private = dev_get_drvdata(dev); + struct drm_device *drm = private->drm; + int ret; + + ret = drm_mode_config_helper_resume(drm); + if (ret) + dev_err(dev, "Failed to resume\n"); +} + +static const struct dev_pm_ops mtk_drm_pm_ops = { + .prepare = mtk_drm_sys_prepare, + .complete = mtk_drm_sys_complete, +}; + +static struct platform_driver mtk_drm_platform_driver = { + .probe = mtk_drm_probe, + .remove = mtk_drm_remove, + .driver = { + .name = "mediatek-drm", + .pm = &mtk_drm_pm_ops, + }, +}; + +static struct platform_driver * const mtk_drm_drivers[] = { + &mtk_disp_aal_driver, + &mtk_disp_ccorr_driver, + &mtk_disp_color_driver, + &mtk_disp_gamma_driver, + &mtk_disp_merge_driver, + &mtk_disp_ovl_driver, + &mtk_disp_rdma_driver, + &mtk_dpi_driver, + &mtk_drm_platform_driver, + &mtk_dsi_driver, + &mtk_mdp_rdma_driver, +}; + +static int __init mtk_drm_init(void) +{ + return platform_register_drivers(mtk_drm_drivers, + ARRAY_SIZE(mtk_drm_drivers)); +} + +static void __exit mtk_drm_exit(void) +{ + platform_unregister_drivers(mtk_drm_drivers, + ARRAY_SIZE(mtk_drm_drivers)); +} + +module_init(mtk_drm_init); +module_exit(mtk_drm_exit); + +MODULE_AUTHOR("YT SHEN <yt.shen@mediatek.com>"); +MODULE_DESCRIPTION("Mediatek SoC DRM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.h b/drivers/gpu/drm/mediatek/mtk_drm_drv.h new file mode 100644 index 000000000..7b37b5cf9 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015 MediaTek Inc. + */ + +#ifndef MTK_DRM_DRV_H +#define MTK_DRM_DRV_H + +#include <linux/io.h> +#include "mtk_drm_ddp_comp.h" + +#define MAX_CRTC 3 +#define MAX_CONNECTOR 2 + +struct device; +struct device_node; +struct drm_crtc; +struct drm_device; +struct drm_fb_helper; +struct drm_property; +struct regmap; + +struct mtk_mmsys_driver_data { + const resource_size_t io_start; + const enum mtk_ddp_comp_id *main_path; + unsigned int main_len; + const enum mtk_ddp_comp_id *ext_path; + unsigned int ext_len; + const enum mtk_ddp_comp_id *third_path; + unsigned int third_len; + + bool shadow_register; +}; + +struct mtk_mmsys_match_data { + unsigned short num_drv_data; + const struct mtk_mmsys_driver_data *drv_data[]; +}; + +struct mtk_drm_private { + struct drm_device *drm; + struct device *dma_dev; + + unsigned int num_pipes; + + struct device_node *mutex_node; + struct device *mutex_dev; + struct device *mmsys_dev; + struct device_node *comp_node[DDP_COMPONENT_ID_MAX]; + struct mtk_ddp_comp ddp_comp[DDP_COMPONENT_ID_MAX]; + const struct mtk_mmsys_driver_data *data; + struct drm_atomic_state *suspend_state; +}; + +extern struct platform_driver mtk_disp_aal_driver; +extern struct platform_driver mtk_disp_ccorr_driver; +extern struct platform_driver mtk_disp_color_driver; +extern struct platform_driver mtk_disp_gamma_driver; +extern struct platform_driver mtk_disp_merge_driver; +extern struct platform_driver mtk_disp_ovl_driver; +extern struct platform_driver mtk_disp_rdma_driver; +extern struct platform_driver mtk_dpi_driver; +extern struct platform_driver mtk_dsi_driver; +extern struct platform_driver mtk_mdp_rdma_driver; + +#endif /* MTK_DRM_DRV_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_gem.c b/drivers/gpu/drm/mediatek/mtk_drm_gem.c new file mode 100644 index 000000000..fb4f0e336 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_gem.c @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 MediaTek Inc. + */ + +#include <linux/dma-buf.h> + +#include <drm/drm.h> +#include <drm/drm_device.h> +#include <drm/drm_gem.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_prime.h> + +#include "mtk_drm_drv.h" +#include "mtk_drm_gem.h" + +static int mtk_drm_gem_object_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma); + +static const struct drm_gem_object_funcs mtk_drm_gem_object_funcs = { + .free = mtk_drm_gem_free_object, + .get_sg_table = mtk_gem_prime_get_sg_table, + .vmap = mtk_drm_gem_prime_vmap, + .vunmap = mtk_drm_gem_prime_vunmap, + .mmap = mtk_drm_gem_object_mmap, + .vm_ops = &drm_gem_dma_vm_ops, +}; + +static struct mtk_drm_gem_obj *mtk_drm_gem_init(struct drm_device *dev, + unsigned long size) +{ + struct mtk_drm_gem_obj *mtk_gem_obj; + int ret; + + size = round_up(size, PAGE_SIZE); + + mtk_gem_obj = kzalloc(sizeof(*mtk_gem_obj), GFP_KERNEL); + if (!mtk_gem_obj) + return ERR_PTR(-ENOMEM); + + mtk_gem_obj->base.funcs = &mtk_drm_gem_object_funcs; + + ret = drm_gem_object_init(dev, &mtk_gem_obj->base, size); + if (ret < 0) { + DRM_ERROR("failed to initialize gem object\n"); + kfree(mtk_gem_obj); + return ERR_PTR(ret); + } + + return mtk_gem_obj; +} + +struct mtk_drm_gem_obj *mtk_drm_gem_create(struct drm_device *dev, + size_t size, bool alloc_kmap) +{ + struct mtk_drm_private *priv = dev->dev_private; + struct mtk_drm_gem_obj *mtk_gem; + struct drm_gem_object *obj; + int ret; + + mtk_gem = mtk_drm_gem_init(dev, size); + if (IS_ERR(mtk_gem)) + return ERR_CAST(mtk_gem); + + obj = &mtk_gem->base; + + mtk_gem->dma_attrs = DMA_ATTR_WRITE_COMBINE; + + if (!alloc_kmap) + mtk_gem->dma_attrs |= DMA_ATTR_NO_KERNEL_MAPPING; + + mtk_gem->cookie = dma_alloc_attrs(priv->dma_dev, obj->size, + &mtk_gem->dma_addr, GFP_KERNEL, + mtk_gem->dma_attrs); + if (!mtk_gem->cookie) { + DRM_ERROR("failed to allocate %zx byte dma buffer", obj->size); + ret = -ENOMEM; + goto err_gem_free; + } + + if (alloc_kmap) + mtk_gem->kvaddr = mtk_gem->cookie; + + DRM_DEBUG_DRIVER("cookie = %p dma_addr = %pad size = %zu\n", + mtk_gem->cookie, &mtk_gem->dma_addr, + size); + + return mtk_gem; + +err_gem_free: + drm_gem_object_release(obj); + kfree(mtk_gem); + return ERR_PTR(ret); +} + +void mtk_drm_gem_free_object(struct drm_gem_object *obj) +{ + struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj); + struct mtk_drm_private *priv = obj->dev->dev_private; + + if (mtk_gem->sg) + drm_prime_gem_destroy(obj, mtk_gem->sg); + else + dma_free_attrs(priv->dma_dev, obj->size, mtk_gem->cookie, + mtk_gem->dma_addr, mtk_gem->dma_attrs); + + /* release file pointer to gem object. */ + drm_gem_object_release(obj); + + kfree(mtk_gem); +} + +int mtk_drm_gem_dumb_create(struct drm_file *file_priv, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + struct mtk_drm_gem_obj *mtk_gem; + int ret; + + args->pitch = DIV_ROUND_UP(args->width * args->bpp, 8); + args->size = args->pitch * args->height; + + mtk_gem = mtk_drm_gem_create(dev, args->size, false); + if (IS_ERR(mtk_gem)) + return PTR_ERR(mtk_gem); + + /* + * allocate a id of idr table where the obj is registered + * and handle has the id what user can see. + */ + ret = drm_gem_handle_create(file_priv, &mtk_gem->base, &args->handle); + if (ret) + goto err_handle_create; + + /* drop reference from allocate - handle holds it now. */ + drm_gem_object_put(&mtk_gem->base); + + return 0; + +err_handle_create: + mtk_drm_gem_free_object(&mtk_gem->base); + return ret; +} + +static int mtk_drm_gem_object_mmap(struct drm_gem_object *obj, + struct vm_area_struct *vma) + +{ + int ret; + struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj); + struct mtk_drm_private *priv = obj->dev->dev_private; + + /* + * Set vm_pgoff (used as a fake buffer offset by DRM) to 0 and map the + * whole buffer from the start. + */ + vma->vm_pgoff = 0; + + /* + * dma_alloc_attrs() allocated a struct page table for mtk_gem, so clear + * VM_PFNMAP flag that was set by drm_gem_mmap_obj()/drm_gem_mmap(). + */ + vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP; + vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); + vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot); + + ret = dma_mmap_attrs(priv->dma_dev, vma, mtk_gem->cookie, + mtk_gem->dma_addr, obj->size, mtk_gem->dma_attrs); + + return ret; +} + +/* + * Allocate a sg_table for this GEM object. + * Note: Both the table's contents, and the sg_table itself must be freed by + * the caller. + * Returns a pointer to the newly allocated sg_table, or an ERR_PTR() error. + */ +struct sg_table *mtk_gem_prime_get_sg_table(struct drm_gem_object *obj) +{ + struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj); + struct mtk_drm_private *priv = obj->dev->dev_private; + struct sg_table *sgt; + int ret; + + sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); + if (!sgt) + return ERR_PTR(-ENOMEM); + + ret = dma_get_sgtable_attrs(priv->dma_dev, sgt, mtk_gem->cookie, + mtk_gem->dma_addr, obj->size, + mtk_gem->dma_attrs); + if (ret) { + DRM_ERROR("failed to allocate sgt, %d\n", ret); + kfree(sgt); + return ERR_PTR(ret); + } + + return sgt; +} + +struct drm_gem_object *mtk_gem_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, struct sg_table *sg) +{ + struct mtk_drm_gem_obj *mtk_gem; + + /* check if the entries in the sg_table are contiguous */ + if (drm_prime_get_contiguous_size(sg) < attach->dmabuf->size) { + DRM_ERROR("sg_table is not contiguous"); + return ERR_PTR(-EINVAL); + } + + mtk_gem = mtk_drm_gem_init(dev, attach->dmabuf->size); + if (IS_ERR(mtk_gem)) + return ERR_CAST(mtk_gem); + + mtk_gem->dma_addr = sg_dma_address(sg->sgl); + mtk_gem->sg = sg; + + return &mtk_gem->base; +} + +int mtk_drm_gem_prime_vmap(struct drm_gem_object *obj, struct iosys_map *map) +{ + struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj); + struct sg_table *sgt = NULL; + unsigned int npages; + + if (mtk_gem->kvaddr) + goto out; + + sgt = mtk_gem_prime_get_sg_table(obj); + if (IS_ERR(sgt)) + return PTR_ERR(sgt); + + npages = obj->size >> PAGE_SHIFT; + mtk_gem->pages = kcalloc(npages, sizeof(*mtk_gem->pages), GFP_KERNEL); + if (!mtk_gem->pages) { + sg_free_table(sgt); + kfree(sgt); + return -ENOMEM; + } + + drm_prime_sg_to_page_array(sgt, mtk_gem->pages, npages); + + mtk_gem->kvaddr = vmap(mtk_gem->pages, npages, VM_MAP, + pgprot_writecombine(PAGE_KERNEL)); + if (!mtk_gem->kvaddr) { + sg_free_table(sgt); + kfree(sgt); + kfree(mtk_gem->pages); + return -ENOMEM; + } + sg_free_table(sgt); + kfree(sgt); + +out: + iosys_map_set_vaddr(map, mtk_gem->kvaddr); + + return 0; +} + +void mtk_drm_gem_prime_vunmap(struct drm_gem_object *obj, + struct iosys_map *map) +{ + struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj); + void *vaddr = map->vaddr; + + if (!mtk_gem->pages) + return; + + vunmap(vaddr); + mtk_gem->kvaddr = NULL; + kfree(mtk_gem->pages); +} diff --git a/drivers/gpu/drm/mediatek/mtk_drm_gem.h b/drivers/gpu/drm/mediatek/mtk_drm_gem.h new file mode 100644 index 000000000..78f23b07a --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_gem.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015 MediaTek Inc. + */ + +#ifndef _MTK_DRM_GEM_H_ +#define _MTK_DRM_GEM_H_ + +#include <drm/drm_gem.h> + +/* + * mtk drm buffer structure. + * + * @base: a gem object. + * - a new handle to this gem object would be created + * by drm_gem_handle_create(). + * @cookie: the return value of dma_alloc_attrs(), keep it for dma_free_attrs() + * @kvaddr: kernel virtual address of gem buffer. + * @dma_addr: dma address of gem buffer. + * @dma_attrs: dma attributes of gem buffer. + * + * P.S. this object would be transferred to user as kms_bo.handle so + * user can access the buffer through kms_bo.handle. + */ +struct mtk_drm_gem_obj { + struct drm_gem_object base; + void *cookie; + void *kvaddr; + dma_addr_t dma_addr; + unsigned long dma_attrs; + struct sg_table *sg; + struct page **pages; +}; + +#define to_mtk_gem_obj(x) container_of(x, struct mtk_drm_gem_obj, base) + +void mtk_drm_gem_free_object(struct drm_gem_object *gem); +struct mtk_drm_gem_obj *mtk_drm_gem_create(struct drm_device *dev, size_t size, + bool alloc_kmap); +int mtk_drm_gem_dumb_create(struct drm_file *file_priv, struct drm_device *dev, + struct drm_mode_create_dumb *args); +struct sg_table *mtk_gem_prime_get_sg_table(struct drm_gem_object *obj); +struct drm_gem_object *mtk_gem_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, struct sg_table *sg); +int mtk_drm_gem_prime_vmap(struct drm_gem_object *obj, struct iosys_map *map); +void mtk_drm_gem_prime_vunmap(struct drm_gem_object *obj, + struct iosys_map *map); + +#endif diff --git a/drivers/gpu/drm/mediatek/mtk_drm_plane.c b/drivers/gpu/drm/mediatek/mtk_drm_plane.c new file mode 100644 index 000000000..c4a0203d1 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.c @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: CK Hu <ck.hu@mediatek.com> + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_atomic_uapi.h> +#include <drm/drm_blend.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> + +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp_comp.h" +#include "mtk_drm_drv.h" +#include "mtk_drm_gem.h" +#include "mtk_drm_plane.h" + +static const u32 formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB565, + DRM_FORMAT_UYVY, + DRM_FORMAT_YUYV, +}; + +static void mtk_plane_reset(struct drm_plane *plane) +{ + struct mtk_plane_state *state; + + if (plane->state) { + __drm_atomic_helper_plane_destroy_state(plane->state); + + state = to_mtk_plane_state(plane->state); + memset(state, 0, sizeof(*state)); + } else { + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return; + } + + __drm_atomic_helper_plane_reset(plane, &state->base); + + state->base.plane = plane; + state->pending.format = DRM_FORMAT_RGB565; +} + +static struct drm_plane_state *mtk_plane_duplicate_state(struct drm_plane *plane) +{ + struct mtk_plane_state *old_state = to_mtk_plane_state(plane->state); + struct mtk_plane_state *state; + + state = kmalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, &state->base); + + WARN_ON(state->base.plane != plane); + + state->pending = old_state->pending; + + return &state->base; +} + +static void mtk_drm_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + __drm_atomic_helper_plane_destroy_state(state); + kfree(to_mtk_plane_state(state)); +} + +static int mtk_plane_atomic_async_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 drm_crtc_state *crtc_state; + int ret; + + if (plane != new_plane_state->crtc->cursor) + return -EINVAL; + + if (!plane->state) + return -EINVAL; + + if (!plane->state->fb) + return -EINVAL; + + ret = mtk_drm_crtc_plane_check(new_plane_state->crtc, plane, + to_mtk_plane_state(new_plane_state)); + if (ret) + return ret; + + if (state) + crtc_state = drm_atomic_get_existing_crtc_state(state, + new_plane_state->crtc); + else /* Special case for asynchronous cursor updates. */ + crtc_state = new_plane_state->crtc->state; + + return drm_atomic_helper_check_plane_state(plane->state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + true, true); +} + +static void mtk_plane_update_new_state(struct drm_plane_state *new_state, + struct mtk_plane_state *mtk_plane_state) +{ + struct drm_framebuffer *fb = new_state->fb; + struct drm_gem_object *gem; + struct mtk_drm_gem_obj *mtk_gem; + unsigned int pitch, format; + dma_addr_t addr; + + gem = fb->obj[0]; + mtk_gem = to_mtk_gem_obj(gem); + addr = mtk_gem->dma_addr; + pitch = fb->pitches[0]; + format = fb->format->format; + + addr += (new_state->src.x1 >> 16) * fb->format->cpp[0]; + addr += (new_state->src.y1 >> 16) * pitch; + + mtk_plane_state->pending.enable = true; + mtk_plane_state->pending.pitch = pitch; + mtk_plane_state->pending.format = format; + mtk_plane_state->pending.addr = addr; + mtk_plane_state->pending.x = new_state->dst.x1; + mtk_plane_state->pending.y = new_state->dst.y1; + mtk_plane_state->pending.width = drm_rect_width(&new_state->dst); + mtk_plane_state->pending.height = drm_rect_height(&new_state->dst); + mtk_plane_state->pending.rotation = new_state->rotation; + mtk_plane_state->pending.color_encoding = new_state->color_encoding; +} + +static void mtk_plane_atomic_async_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, + plane); + struct mtk_plane_state *new_plane_state = to_mtk_plane_state(plane->state); + + plane->state->crtc_x = new_state->crtc_x; + plane->state->crtc_y = new_state->crtc_y; + plane->state->crtc_h = new_state->crtc_h; + plane->state->crtc_w = new_state->crtc_w; + plane->state->src_x = new_state->src_x; + plane->state->src_y = new_state->src_y; + plane->state->src_h = new_state->src_h; + plane->state->src_w = new_state->src_w; + + mtk_plane_update_new_state(new_state, new_plane_state); + swap(plane->state->fb, new_state->fb); + wmb(); /* Make sure the above parameters are set before update */ + new_plane_state->pending.async_dirty = true; + mtk_drm_crtc_async_update(new_state->crtc, plane, state); +} + +static const struct drm_plane_funcs mtk_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = mtk_plane_reset, + .atomic_duplicate_state = mtk_plane_duplicate_state, + .atomic_destroy_state = mtk_drm_plane_destroy_state, +}; + +static int mtk_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 drm_framebuffer *fb = new_plane_state->fb; + struct drm_crtc_state *crtc_state; + int ret; + + if (!fb) + return 0; + + if (WARN_ON(!new_plane_state->crtc)) + return 0; + + ret = mtk_drm_crtc_plane_check(new_plane_state->crtc, plane, + to_mtk_plane_state(new_plane_state)); + if (ret) + return ret; + + crtc_state = drm_atomic_get_crtc_state(state, + new_plane_state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + return drm_atomic_helper_check_plane_state(new_plane_state, + crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + true, true); +} + +static void mtk_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, + plane); + struct mtk_plane_state *mtk_plane_state = to_mtk_plane_state(new_state); + mtk_plane_state->pending.enable = false; + wmb(); /* Make sure the above parameter is set before update */ + mtk_plane_state->pending.dirty = true; +} + +static void mtk_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, + plane); + struct mtk_plane_state *mtk_plane_state = to_mtk_plane_state(new_state); + + if (!new_state->crtc || WARN_ON(!new_state->fb)) + return; + + if (!new_state->visible) { + mtk_plane_atomic_disable(plane, state); + return; + } + + mtk_plane_update_new_state(new_state, mtk_plane_state); + wmb(); /* Make sure the above parameters are set before update */ + mtk_plane_state->pending.dirty = true; +} + +static const struct drm_plane_helper_funcs mtk_plane_helper_funcs = { + .atomic_check = mtk_plane_atomic_check, + .atomic_update = mtk_plane_atomic_update, + .atomic_disable = mtk_plane_atomic_disable, + .atomic_async_update = mtk_plane_atomic_async_update, + .atomic_async_check = mtk_plane_atomic_async_check, +}; + +int mtk_plane_init(struct drm_device *dev, struct drm_plane *plane, + unsigned long possible_crtcs, enum drm_plane_type type, + unsigned int supported_rotations) +{ + int err; + + err = drm_universal_plane_init(dev, plane, possible_crtcs, + &mtk_plane_funcs, formats, + ARRAY_SIZE(formats), NULL, type, NULL); + if (err) { + DRM_ERROR("failed to initialize plane\n"); + return err; + } + + if (supported_rotations & ~DRM_MODE_ROTATE_0) { + err = drm_plane_create_rotation_property(plane, + DRM_MODE_ROTATE_0, + supported_rotations); + if (err) + DRM_INFO("Create rotation property failed\n"); + } + + drm_plane_helper_add(plane, &mtk_plane_helper_funcs); + + return 0; +} diff --git a/drivers/gpu/drm/mediatek/mtk_drm_plane.h b/drivers/gpu/drm/mediatek/mtk_drm_plane.h new file mode 100644 index 000000000..2d5ec66e3 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: CK Hu <ck.hu@mediatek.com> + */ + +#ifndef _MTK_DRM_PLANE_H_ +#define _MTK_DRM_PLANE_H_ + +#include <drm/drm_crtc.h> +#include <linux/types.h> + +struct mtk_plane_pending_state { + bool config; + bool enable; + dma_addr_t addr; + unsigned int pitch; + unsigned int format; + unsigned int x; + unsigned int y; + unsigned int width; + unsigned int height; + unsigned int rotation; + bool dirty; + bool async_dirty; + bool async_config; + enum drm_color_encoding color_encoding; +}; + +struct mtk_plane_state { + struct drm_plane_state base; + struct mtk_plane_pending_state pending; +}; + +static inline struct mtk_plane_state * +to_mtk_plane_state(struct drm_plane_state *state) +{ + return container_of(state, struct mtk_plane_state, base); +} + +int mtk_plane_init(struct drm_device *dev, struct drm_plane *plane, + unsigned long possible_crtcs, enum drm_plane_type type, + unsigned int supported_rotations); + +#endif diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c new file mode 100644 index 000000000..3e74c7c1b --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c @@ -0,0 +1,1230 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 MediaTek Inc. + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/iopoll.h> +#include <linux/irq.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <video/mipi_display.h> +#include <video/videomode.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_bridge_connector.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> + +#include "mtk_disp_drv.h" +#include "mtk_drm_ddp_comp.h" + +#define DSI_START 0x00 + +#define DSI_INTEN 0x08 + +#define DSI_INTSTA 0x0c +#define LPRX_RD_RDY_INT_FLAG BIT(0) +#define CMD_DONE_INT_FLAG BIT(1) +#define TE_RDY_INT_FLAG BIT(2) +#define VM_DONE_INT_FLAG BIT(3) +#define EXT_TE_RDY_INT_FLAG BIT(4) +#define DSI_BUSY BIT(31) + +#define DSI_CON_CTRL 0x10 +#define DSI_RESET BIT(0) +#define DSI_EN BIT(1) +#define DPHY_RESET BIT(2) + +#define DSI_MODE_CTRL 0x14 +#define MODE (3) +#define CMD_MODE 0 +#define SYNC_PULSE_MODE 1 +#define SYNC_EVENT_MODE 2 +#define BURST_MODE 3 +#define FRM_MODE BIT(16) +#define MIX_MODE BIT(17) + +#define DSI_TXRX_CTRL 0x18 +#define VC_NUM BIT(1) +#define LANE_NUM (0xf << 2) +#define DIS_EOT BIT(6) +#define NULL_EN BIT(7) +#define TE_FREERUN BIT(8) +#define EXT_TE_EN BIT(9) +#define EXT_TE_EDGE BIT(10) +#define MAX_RTN_SIZE (0xf << 12) +#define HSTX_CKLP_EN BIT(16) + +#define DSI_PSCTRL 0x1c +#define DSI_PS_WC 0x3fff +#define DSI_PS_SEL (3 << 16) +#define PACKED_PS_16BIT_RGB565 (0 << 16) +#define LOOSELY_PS_18BIT_RGB666 (1 << 16) +#define PACKED_PS_18BIT_RGB666 (2 << 16) +#define PACKED_PS_24BIT_RGB888 (3 << 16) + +#define DSI_VSA_NL 0x20 +#define DSI_VBP_NL 0x24 +#define DSI_VFP_NL 0x28 +#define DSI_VACT_NL 0x2C +#define DSI_SIZE_CON 0x38 +#define DSI_HSA_WC 0x50 +#define DSI_HBP_WC 0x54 +#define DSI_HFP_WC 0x58 + +#define DSI_CMDQ_SIZE 0x60 +#define CMDQ_SIZE 0x3f + +#define DSI_HSTX_CKL_WC 0x64 + +#define DSI_RX_DATA0 0x74 +#define DSI_RX_DATA1 0x78 +#define DSI_RX_DATA2 0x7c +#define DSI_RX_DATA3 0x80 + +#define DSI_RACK 0x84 +#define RACK BIT(0) + +#define DSI_PHY_LCCON 0x104 +#define LC_HS_TX_EN BIT(0) +#define LC_ULPM_EN BIT(1) +#define LC_WAKEUP_EN BIT(2) + +#define DSI_PHY_LD0CON 0x108 +#define LD0_HS_TX_EN BIT(0) +#define LD0_ULPM_EN BIT(1) +#define LD0_WAKEUP_EN BIT(2) + +#define DSI_PHY_TIMECON0 0x110 +#define LPX (0xff << 0) +#define HS_PREP (0xff << 8) +#define HS_ZERO (0xff << 16) +#define HS_TRAIL (0xff << 24) + +#define DSI_PHY_TIMECON1 0x114 +#define TA_GO (0xff << 0) +#define TA_SURE (0xff << 8) +#define TA_GET (0xff << 16) +#define DA_HS_EXIT (0xff << 24) + +#define DSI_PHY_TIMECON2 0x118 +#define CONT_DET (0xff << 0) +#define CLK_ZERO (0xff << 16) +#define CLK_TRAIL (0xff << 24) + +#define DSI_PHY_TIMECON3 0x11c +#define CLK_HS_PREP (0xff << 0) +#define CLK_HS_POST (0xff << 8) +#define CLK_HS_EXIT (0xff << 16) + +#define DSI_VM_CMD_CON 0x130 +#define VM_CMD_EN BIT(0) +#define TS_VFP_EN BIT(5) + +#define DSI_SHADOW_DEBUG 0x190U +#define FORCE_COMMIT BIT(0) +#define BYPASS_SHADOW BIT(1) + +#define CONFIG (0xff << 0) +#define SHORT_PACKET 0 +#define LONG_PACKET 2 +#define BTA BIT(2) +#define DATA_ID (0xff << 8) +#define DATA_0 (0xff << 16) +#define DATA_1 (0xff << 24) + +#define NS_TO_CYCLE(n, c) ((n) / (c) + (((n) % (c)) ? 1 : 0)) + +#define MTK_DSI_HOST_IS_READ(type) \ + ((type == MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM) || \ + (type == MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM) || \ + (type == MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM) || \ + (type == MIPI_DSI_DCS_READ)) + +struct mtk_phy_timing { + u32 lpx; + u32 da_hs_prepare; + u32 da_hs_zero; + u32 da_hs_trail; + + u32 ta_go; + u32 ta_sure; + u32 ta_get; + u32 da_hs_exit; + + u32 clk_hs_zero; + u32 clk_hs_trail; + + u32 clk_hs_prepare; + u32 clk_hs_post; + u32 clk_hs_exit; +}; + +struct phy; + +struct mtk_dsi_driver_data { + const u32 reg_cmdq_off; + bool has_shadow_ctl; + bool has_size_ctl; +}; + +struct mtk_dsi { + struct device *dev; + struct mipi_dsi_host host; + struct drm_encoder encoder; + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + struct drm_connector *connector; + struct phy *phy; + + void __iomem *regs; + + struct clk *engine_clk; + struct clk *digital_clk; + struct clk *hs_clk; + + u32 data_rate; + + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + unsigned int lanes; + struct videomode vm; + struct mtk_phy_timing phy_timing; + int refcount; + bool enabled; + bool lanes_ready; + u32 irq_data; + wait_queue_head_t irq_wait_queue; + const struct mtk_dsi_driver_data *driver_data; +}; + +static inline struct mtk_dsi *bridge_to_dsi(struct drm_bridge *b) +{ + return container_of(b, struct mtk_dsi, bridge); +} + +static inline struct mtk_dsi *host_to_dsi(struct mipi_dsi_host *h) +{ + return container_of(h, struct mtk_dsi, host); +} + +static void mtk_dsi_mask(struct mtk_dsi *dsi, u32 offset, u32 mask, u32 data) +{ + u32 temp = readl(dsi->regs + offset); + + writel((temp & ~mask) | (data & mask), dsi->regs + offset); +} + +static void mtk_dsi_phy_timconfig(struct mtk_dsi *dsi) +{ + u32 timcon0, timcon1, timcon2, timcon3; + u32 data_rate_mhz = DIV_ROUND_UP(dsi->data_rate, 1000000); + struct mtk_phy_timing *timing = &dsi->phy_timing; + + timing->lpx = (60 * data_rate_mhz / (8 * 1000)) + 1; + timing->da_hs_prepare = (80 * data_rate_mhz + 4 * 1000) / 8000; + timing->da_hs_zero = (170 * data_rate_mhz + 10 * 1000) / 8000 + 1 - + timing->da_hs_prepare; + timing->da_hs_trail = timing->da_hs_prepare + 1; + + timing->ta_go = 4 * timing->lpx - 2; + timing->ta_sure = timing->lpx + 2; + timing->ta_get = 4 * timing->lpx; + timing->da_hs_exit = 2 * timing->lpx + 1; + + timing->clk_hs_prepare = 70 * data_rate_mhz / (8 * 1000); + timing->clk_hs_post = timing->clk_hs_prepare + 8; + timing->clk_hs_trail = timing->clk_hs_prepare; + timing->clk_hs_zero = timing->clk_hs_trail * 4; + timing->clk_hs_exit = 2 * timing->clk_hs_trail; + + timcon0 = timing->lpx | timing->da_hs_prepare << 8 | + timing->da_hs_zero << 16 | timing->da_hs_trail << 24; + timcon1 = timing->ta_go | timing->ta_sure << 8 | + timing->ta_get << 16 | timing->da_hs_exit << 24; + timcon2 = 1 << 8 | timing->clk_hs_zero << 16 | + timing->clk_hs_trail << 24; + timcon3 = timing->clk_hs_prepare | timing->clk_hs_post << 8 | + timing->clk_hs_exit << 16; + + writel(timcon0, dsi->regs + DSI_PHY_TIMECON0); + writel(timcon1, dsi->regs + DSI_PHY_TIMECON1); + writel(timcon2, dsi->regs + DSI_PHY_TIMECON2); + writel(timcon3, dsi->regs + DSI_PHY_TIMECON3); +} + +static void mtk_dsi_enable(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_EN, DSI_EN); +} + +static void mtk_dsi_disable(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_EN, 0); +} + +static void mtk_dsi_reset_engine(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_RESET, DSI_RESET); + mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_RESET, 0); +} + +static void mtk_dsi_reset_dphy(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_CON_CTRL, DPHY_RESET, DPHY_RESET); + mtk_dsi_mask(dsi, DSI_CON_CTRL, DPHY_RESET, 0); +} + +static void mtk_dsi_clk_ulp_mode_enter(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, 0); + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_ULPM_EN, 0); +} + +static void mtk_dsi_clk_ulp_mode_leave(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_ULPM_EN, 0); + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_WAKEUP_EN, LC_WAKEUP_EN); + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_WAKEUP_EN, 0); +} + +static void mtk_dsi_lane0_ulp_mode_enter(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_HS_TX_EN, 0); + mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_ULPM_EN, 0); +} + +static void mtk_dsi_lane0_ulp_mode_leave(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_ULPM_EN, 0); + mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_WAKEUP_EN, LD0_WAKEUP_EN); + mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_WAKEUP_EN, 0); +} + +static bool mtk_dsi_clk_hs_state(struct mtk_dsi *dsi) +{ + return readl(dsi->regs + DSI_PHY_LCCON) & LC_HS_TX_EN; +} + +static void mtk_dsi_clk_hs_mode(struct mtk_dsi *dsi, bool enter) +{ + if (enter && !mtk_dsi_clk_hs_state(dsi)) + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, LC_HS_TX_EN); + else if (!enter && mtk_dsi_clk_hs_state(dsi)) + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, 0); +} + +static void mtk_dsi_set_mode(struct mtk_dsi *dsi) +{ + u32 vid_mode = CMD_MODE; + + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) { + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) + vid_mode = BURST_MODE; + else if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + vid_mode = SYNC_PULSE_MODE; + else + vid_mode = SYNC_EVENT_MODE; + } + + writel(vid_mode, dsi->regs + DSI_MODE_CTRL); +} + +static void mtk_dsi_set_vm_cmd(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_VM_CMD_CON, VM_CMD_EN, VM_CMD_EN); + mtk_dsi_mask(dsi, DSI_VM_CMD_CON, TS_VFP_EN, TS_VFP_EN); +} + +static void mtk_dsi_ps_control_vact(struct mtk_dsi *dsi) +{ + struct videomode *vm = &dsi->vm; + u32 dsi_buf_bpp, ps_wc; + u32 ps_bpp_mode; + + if (dsi->format == MIPI_DSI_FMT_RGB565) + dsi_buf_bpp = 2; + else + dsi_buf_bpp = 3; + + ps_wc = vm->hactive * dsi_buf_bpp; + ps_bpp_mode = ps_wc; + + switch (dsi->format) { + case MIPI_DSI_FMT_RGB888: + ps_bpp_mode |= PACKED_PS_24BIT_RGB888; + break; + case MIPI_DSI_FMT_RGB666: + ps_bpp_mode |= PACKED_PS_18BIT_RGB666; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + ps_bpp_mode |= LOOSELY_PS_18BIT_RGB666; + break; + case MIPI_DSI_FMT_RGB565: + ps_bpp_mode |= PACKED_PS_16BIT_RGB565; + break; + } + + writel(vm->vactive, dsi->regs + DSI_VACT_NL); + writel(ps_bpp_mode, dsi->regs + DSI_PSCTRL); + writel(ps_wc, dsi->regs + DSI_HSTX_CKL_WC); +} + +static void mtk_dsi_rxtx_control(struct mtk_dsi *dsi) +{ + u32 tmp_reg; + + switch (dsi->lanes) { + case 1: + tmp_reg = 1 << 2; + break; + case 2: + tmp_reg = 3 << 2; + break; + case 3: + tmp_reg = 7 << 2; + break; + case 4: + tmp_reg = 0xf << 2; + break; + default: + tmp_reg = 0xf << 2; + break; + } + + if (dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) + tmp_reg |= HSTX_CKLP_EN; + + if (dsi->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET) + tmp_reg |= DIS_EOT; + + writel(tmp_reg, dsi->regs + DSI_TXRX_CTRL); +} + +static void mtk_dsi_ps_control(struct mtk_dsi *dsi) +{ + u32 dsi_tmp_buf_bpp; + u32 tmp_reg; + + switch (dsi->format) { + case MIPI_DSI_FMT_RGB888: + tmp_reg = PACKED_PS_24BIT_RGB888; + dsi_tmp_buf_bpp = 3; + break; + case MIPI_DSI_FMT_RGB666: + tmp_reg = LOOSELY_PS_18BIT_RGB666; + dsi_tmp_buf_bpp = 3; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + tmp_reg = PACKED_PS_18BIT_RGB666; + dsi_tmp_buf_bpp = 3; + break; + case MIPI_DSI_FMT_RGB565: + tmp_reg = PACKED_PS_16BIT_RGB565; + dsi_tmp_buf_bpp = 2; + break; + default: + tmp_reg = PACKED_PS_24BIT_RGB888; + dsi_tmp_buf_bpp = 3; + break; + } + + tmp_reg += dsi->vm.hactive * dsi_tmp_buf_bpp & DSI_PS_WC; + writel(tmp_reg, dsi->regs + DSI_PSCTRL); +} + +static void mtk_dsi_config_vdo_timing(struct mtk_dsi *dsi) +{ + u32 horizontal_sync_active_byte; + u32 horizontal_backporch_byte; + u32 horizontal_frontporch_byte; + u32 horizontal_front_back_byte; + u32 data_phy_cycles_byte; + u32 dsi_tmp_buf_bpp, data_phy_cycles; + u32 delta; + struct mtk_phy_timing *timing = &dsi->phy_timing; + + struct videomode *vm = &dsi->vm; + + if (dsi->format == MIPI_DSI_FMT_RGB565) + dsi_tmp_buf_bpp = 2; + else + dsi_tmp_buf_bpp = 3; + + writel(vm->vsync_len, dsi->regs + DSI_VSA_NL); + writel(vm->vback_porch, dsi->regs + DSI_VBP_NL); + writel(vm->vfront_porch, dsi->regs + DSI_VFP_NL); + writel(vm->vactive, dsi->regs + DSI_VACT_NL); + + if (dsi->driver_data->has_size_ctl) + writel(vm->vactive << 16 | vm->hactive, + dsi->regs + DSI_SIZE_CON); + + horizontal_sync_active_byte = (vm->hsync_len * dsi_tmp_buf_bpp - 10); + + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + horizontal_backporch_byte = vm->hback_porch * dsi_tmp_buf_bpp - 10; + else + horizontal_backporch_byte = (vm->hback_porch + vm->hsync_len) * + dsi_tmp_buf_bpp - 10; + + data_phy_cycles = timing->lpx + timing->da_hs_prepare + + timing->da_hs_zero + timing->da_hs_exit + 3; + + delta = dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST ? 18 : 12; + delta += dsi->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET ? 0 : 2; + + horizontal_frontporch_byte = vm->hfront_porch * dsi_tmp_buf_bpp; + horizontal_front_back_byte = horizontal_frontporch_byte + horizontal_backporch_byte; + data_phy_cycles_byte = data_phy_cycles * dsi->lanes + delta; + + if (horizontal_front_back_byte > data_phy_cycles_byte) { + horizontal_frontporch_byte -= data_phy_cycles_byte * + horizontal_frontporch_byte / + horizontal_front_back_byte; + + horizontal_backporch_byte -= data_phy_cycles_byte * + horizontal_backporch_byte / + horizontal_front_back_byte; + } else { + DRM_WARN("HFP + HBP less than d-phy, FPS will under 60Hz\n"); + } + + if ((dsi->mode_flags & MIPI_DSI_HS_PKT_END_ALIGNED) && + (dsi->lanes == 4)) { + horizontal_sync_active_byte = + roundup(horizontal_sync_active_byte, dsi->lanes) - 2; + horizontal_frontporch_byte = + roundup(horizontal_frontporch_byte, dsi->lanes) - 2; + horizontal_backporch_byte = + roundup(horizontal_backporch_byte, dsi->lanes) - 2; + horizontal_backporch_byte -= + (vm->hactive * dsi_tmp_buf_bpp + 2) % dsi->lanes; + } + + writel(horizontal_sync_active_byte, dsi->regs + DSI_HSA_WC); + writel(horizontal_backporch_byte, dsi->regs + DSI_HBP_WC); + writel(horizontal_frontporch_byte, dsi->regs + DSI_HFP_WC); + + mtk_dsi_ps_control(dsi); +} + +static void mtk_dsi_start(struct mtk_dsi *dsi) +{ + writel(0, dsi->regs + DSI_START); + writel(1, dsi->regs + DSI_START); +} + +static void mtk_dsi_stop(struct mtk_dsi *dsi) +{ + writel(0, dsi->regs + DSI_START); +} + +static void mtk_dsi_set_cmd_mode(struct mtk_dsi *dsi) +{ + writel(CMD_MODE, dsi->regs + DSI_MODE_CTRL); +} + +static void mtk_dsi_set_interrupt_enable(struct mtk_dsi *dsi) +{ + u32 inten = LPRX_RD_RDY_INT_FLAG | CMD_DONE_INT_FLAG | VM_DONE_INT_FLAG; + + writel(inten, dsi->regs + DSI_INTEN); +} + +static void mtk_dsi_irq_data_set(struct mtk_dsi *dsi, u32 irq_bit) +{ + dsi->irq_data |= irq_bit; +} + +static void mtk_dsi_irq_data_clear(struct mtk_dsi *dsi, u32 irq_bit) +{ + dsi->irq_data &= ~irq_bit; +} + +static s32 mtk_dsi_wait_for_irq_done(struct mtk_dsi *dsi, u32 irq_flag, + unsigned int timeout) +{ + s32 ret = 0; + unsigned long jiffies = msecs_to_jiffies(timeout); + + ret = wait_event_interruptible_timeout(dsi->irq_wait_queue, + dsi->irq_data & irq_flag, + jiffies); + if (ret == 0) { + DRM_WARN("Wait DSI IRQ(0x%08x) Timeout\n", irq_flag); + + mtk_dsi_enable(dsi); + mtk_dsi_reset_engine(dsi); + } + + return ret; +} + +static irqreturn_t mtk_dsi_irq(int irq, void *dev_id) +{ + struct mtk_dsi *dsi = dev_id; + u32 status, tmp; + u32 flag = LPRX_RD_RDY_INT_FLAG | CMD_DONE_INT_FLAG | VM_DONE_INT_FLAG; + + status = readl(dsi->regs + DSI_INTSTA) & flag; + + if (status) { + do { + mtk_dsi_mask(dsi, DSI_RACK, RACK, RACK); + tmp = readl(dsi->regs + DSI_INTSTA); + } while (tmp & DSI_BUSY); + + mtk_dsi_mask(dsi, DSI_INTSTA, status, 0); + mtk_dsi_irq_data_set(dsi, status); + wake_up_interruptible(&dsi->irq_wait_queue); + } + + return IRQ_HANDLED; +} + +static s32 mtk_dsi_switch_to_cmd_mode(struct mtk_dsi *dsi, u8 irq_flag, u32 t) +{ + mtk_dsi_irq_data_clear(dsi, irq_flag); + mtk_dsi_set_cmd_mode(dsi); + + if (!mtk_dsi_wait_for_irq_done(dsi, irq_flag, t)) { + DRM_ERROR("failed to switch cmd mode\n"); + return -ETIME; + } else { + return 0; + } +} + +static int mtk_dsi_poweron(struct mtk_dsi *dsi) +{ + struct device *dev = dsi->host.dev; + int ret; + u32 bit_per_pixel; + + if (++dsi->refcount != 1) + return 0; + + switch (dsi->format) { + case MIPI_DSI_FMT_RGB565: + bit_per_pixel = 16; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + bit_per_pixel = 18; + break; + case MIPI_DSI_FMT_RGB666: + case MIPI_DSI_FMT_RGB888: + default: + bit_per_pixel = 24; + break; + } + + dsi->data_rate = DIV_ROUND_UP_ULL(dsi->vm.pixelclock * bit_per_pixel, + dsi->lanes); + + ret = clk_set_rate(dsi->hs_clk, dsi->data_rate); + if (ret < 0) { + dev_err(dev, "Failed to set data rate: %d\n", ret); + goto err_refcount; + } + + phy_power_on(dsi->phy); + + ret = clk_prepare_enable(dsi->engine_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable engine clock: %d\n", ret); + goto err_phy_power_off; + } + + ret = clk_prepare_enable(dsi->digital_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable digital clock: %d\n", ret); + goto err_disable_engine_clk; + } + + mtk_dsi_enable(dsi); + + if (dsi->driver_data->has_shadow_ctl) + writel(FORCE_COMMIT | BYPASS_SHADOW, + dsi->regs + DSI_SHADOW_DEBUG); + + mtk_dsi_reset_engine(dsi); + mtk_dsi_phy_timconfig(dsi); + + mtk_dsi_ps_control_vact(dsi); + mtk_dsi_set_vm_cmd(dsi); + mtk_dsi_config_vdo_timing(dsi); + mtk_dsi_set_interrupt_enable(dsi); + + return 0; +err_disable_engine_clk: + clk_disable_unprepare(dsi->engine_clk); +err_phy_power_off: + phy_power_off(dsi->phy); +err_refcount: + dsi->refcount--; + return ret; +} + +static void mtk_dsi_poweroff(struct mtk_dsi *dsi) +{ + if (WARN_ON(dsi->refcount == 0)) + return; + + if (--dsi->refcount != 0) + return; + + /* + * mtk_dsi_stop() and mtk_dsi_start() is asymmetric, since + * mtk_dsi_stop() should be called after mtk_drm_crtc_atomic_disable(), + * which needs irq for vblank, and mtk_dsi_stop() will disable irq. + * mtk_dsi_start() needs to be called in mtk_output_dsi_enable(), + * after dsi is fully set. + */ + mtk_dsi_stop(dsi); + + mtk_dsi_switch_to_cmd_mode(dsi, VM_DONE_INT_FLAG, 500); + mtk_dsi_reset_engine(dsi); + mtk_dsi_lane0_ulp_mode_enter(dsi); + mtk_dsi_clk_ulp_mode_enter(dsi); + /* set the lane number as 0 to pull down mipi */ + writel(0, dsi->regs + DSI_TXRX_CTRL); + + mtk_dsi_disable(dsi); + + clk_disable_unprepare(dsi->engine_clk); + clk_disable_unprepare(dsi->digital_clk); + + phy_power_off(dsi->phy); + + dsi->lanes_ready = false; +} + +static void mtk_dsi_lane_ready(struct mtk_dsi *dsi) +{ + if (!dsi->lanes_ready) { + dsi->lanes_ready = true; + mtk_dsi_rxtx_control(dsi); + usleep_range(30, 100); + mtk_dsi_reset_dphy(dsi); + mtk_dsi_clk_ulp_mode_leave(dsi); + mtk_dsi_lane0_ulp_mode_leave(dsi); + mtk_dsi_clk_hs_mode(dsi, 0); + usleep_range(1000, 3000); + /* The reaction time after pulling up the mipi signal for dsi_rx */ + } +} + +static void mtk_output_dsi_enable(struct mtk_dsi *dsi) +{ + if (dsi->enabled) + return; + + mtk_dsi_lane_ready(dsi); + mtk_dsi_set_mode(dsi); + mtk_dsi_clk_hs_mode(dsi, 1); + + mtk_dsi_start(dsi); + + dsi->enabled = true; +} + +static void mtk_output_dsi_disable(struct mtk_dsi *dsi) +{ + if (!dsi->enabled) + return; + + dsi->enabled = false; +} + +static int mtk_dsi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct mtk_dsi *dsi = bridge_to_dsi(bridge); + + /* Attach the panel or bridge to the dsi bridge */ + return drm_bridge_attach(bridge->encoder, dsi->next_bridge, + &dsi->bridge, flags); +} + +static void mtk_dsi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted) +{ + struct mtk_dsi *dsi = bridge_to_dsi(bridge); + + drm_display_mode_to_videomode(adjusted, &dsi->vm); +} + +static void mtk_dsi_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct mtk_dsi *dsi = bridge_to_dsi(bridge); + + mtk_output_dsi_disable(dsi); +} + +static void mtk_dsi_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct mtk_dsi *dsi = bridge_to_dsi(bridge); + + if (dsi->refcount == 0) + return; + + mtk_output_dsi_enable(dsi); +} + +static void mtk_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct mtk_dsi *dsi = bridge_to_dsi(bridge); + int ret; + + ret = mtk_dsi_poweron(dsi); + if (ret < 0) + DRM_ERROR("failed to power on dsi\n"); +} + +static void mtk_dsi_bridge_atomic_post_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct mtk_dsi *dsi = bridge_to_dsi(bridge); + + mtk_dsi_poweroff(dsi); +} + +static const struct drm_bridge_funcs mtk_dsi_bridge_funcs = { + .attach = mtk_dsi_bridge_attach, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_disable = mtk_dsi_bridge_atomic_disable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_enable = mtk_dsi_bridge_atomic_enable, + .atomic_pre_enable = mtk_dsi_bridge_atomic_pre_enable, + .atomic_post_disable = mtk_dsi_bridge_atomic_post_disable, + .atomic_reset = drm_atomic_helper_bridge_reset, + .mode_set = mtk_dsi_bridge_mode_set, +}; + +void mtk_dsi_ddp_start(struct device *dev) +{ + struct mtk_dsi *dsi = dev_get_drvdata(dev); + + mtk_dsi_poweron(dsi); +} + +void mtk_dsi_ddp_stop(struct device *dev) +{ + struct mtk_dsi *dsi = dev_get_drvdata(dev); + + mtk_dsi_poweroff(dsi); +} + +static int mtk_dsi_encoder_init(struct drm_device *drm, struct mtk_dsi *dsi) +{ + int ret; + + ret = drm_simple_encoder_init(drm, &dsi->encoder, + DRM_MODE_ENCODER_DSI); + if (ret) { + DRM_ERROR("Failed to encoder init to drm\n"); + return ret; + } + + dsi->encoder.possible_crtcs = mtk_drm_find_possible_crtc_by_comp(drm, dsi->host.dev); + + ret = drm_bridge_attach(&dsi->encoder, &dsi->bridge, NULL, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + goto err_cleanup_encoder; + + dsi->connector = drm_bridge_connector_init(drm, &dsi->encoder); + if (IS_ERR(dsi->connector)) { + DRM_ERROR("Unable to create bridge connector\n"); + ret = PTR_ERR(dsi->connector); + goto err_cleanup_encoder; + } + drm_connector_attach_encoder(dsi->connector, &dsi->encoder); + + return 0; + +err_cleanup_encoder: + drm_encoder_cleanup(&dsi->encoder); + return ret; +} + +static int mtk_dsi_bind(struct device *dev, struct device *master, void *data) +{ + int ret; + struct drm_device *drm = data; + struct mtk_dsi *dsi = dev_get_drvdata(dev); + + ret = mtk_dsi_encoder_init(drm, dsi); + if (ret) + return ret; + + return device_reset_optional(dev); +} + +static void mtk_dsi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct mtk_dsi *dsi = dev_get_drvdata(dev); + + drm_encoder_cleanup(&dsi->encoder); +} + +static const struct component_ops mtk_dsi_component_ops = { + .bind = mtk_dsi_bind, + .unbind = mtk_dsi_unbind, +}; + +static int mtk_dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct mtk_dsi *dsi = host_to_dsi(host); + struct device *dev = host->dev; + int ret; + + dsi->lanes = device->lanes; + dsi->format = device->format; + dsi->mode_flags = device->mode_flags; + dsi->next_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0); + if (IS_ERR(dsi->next_bridge)) + return PTR_ERR(dsi->next_bridge); + + drm_bridge_add(&dsi->bridge); + + ret = component_add(host->dev, &mtk_dsi_component_ops); + if (ret) { + DRM_ERROR("failed to add dsi_host component: %d\n", ret); + drm_bridge_remove(&dsi->bridge); + return ret; + } + + return 0; +} + +static int mtk_dsi_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct mtk_dsi *dsi = host_to_dsi(host); + + component_del(host->dev, &mtk_dsi_component_ops); + drm_bridge_remove(&dsi->bridge); + return 0; +} + +static void mtk_dsi_wait_for_idle(struct mtk_dsi *dsi) +{ + int ret; + u32 val; + + ret = readl_poll_timeout(dsi->regs + DSI_INTSTA, val, !(val & DSI_BUSY), + 4, 2000000); + if (ret) { + DRM_WARN("polling dsi wait not busy timeout!\n"); + + mtk_dsi_enable(dsi); + mtk_dsi_reset_engine(dsi); + } +} + +static u32 mtk_dsi_recv_cnt(u8 type, u8 *read_data) +{ + switch (type) { + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE: + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + return 1; + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE: + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + return 2; + case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE: + case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE: + return read_data[1] + read_data[2] * 16; + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: + DRM_INFO("type is 0x02, try again\n"); + break; + default: + DRM_INFO("type(0x%x) not recognized\n", type); + break; + } + + return 0; +} + +static void mtk_dsi_cmdq(struct mtk_dsi *dsi, const struct mipi_dsi_msg *msg) +{ + const char *tx_buf = msg->tx_buf; + u8 config, cmdq_size, cmdq_off, type = msg->type; + u32 reg_val, cmdq_mask, i; + u32 reg_cmdq_off = dsi->driver_data->reg_cmdq_off; + + if (MTK_DSI_HOST_IS_READ(type)) + config = BTA; + else + config = (msg->tx_len > 2) ? LONG_PACKET : SHORT_PACKET; + + if (msg->tx_len > 2) { + cmdq_size = 1 + (msg->tx_len + 3) / 4; + cmdq_off = 4; + cmdq_mask = CONFIG | DATA_ID | DATA_0 | DATA_1; + reg_val = (msg->tx_len << 16) | (type << 8) | config; + } else { + cmdq_size = 1; + cmdq_off = 2; + cmdq_mask = CONFIG | DATA_ID; + reg_val = (type << 8) | config; + } + + for (i = 0; i < msg->tx_len; i++) + mtk_dsi_mask(dsi, (reg_cmdq_off + cmdq_off + i) & (~0x3U), + (0xffUL << (((i + cmdq_off) & 3U) * 8U)), + tx_buf[i] << (((i + cmdq_off) & 3U) * 8U)); + + mtk_dsi_mask(dsi, reg_cmdq_off, cmdq_mask, reg_val); + mtk_dsi_mask(dsi, DSI_CMDQ_SIZE, CMDQ_SIZE, cmdq_size); +} + +static ssize_t mtk_dsi_host_send_cmd(struct mtk_dsi *dsi, + const struct mipi_dsi_msg *msg, u8 flag) +{ + mtk_dsi_wait_for_idle(dsi); + mtk_dsi_irq_data_clear(dsi, flag); + mtk_dsi_cmdq(dsi, msg); + mtk_dsi_start(dsi); + + if (!mtk_dsi_wait_for_irq_done(dsi, flag, 2000)) + return -ETIME; + else + return 0; +} + +static ssize_t mtk_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct mtk_dsi *dsi = host_to_dsi(host); + u32 recv_cnt, i; + u8 read_data[16]; + void *src_addr; + u8 irq_flag = CMD_DONE_INT_FLAG; + u32 dsi_mode; + int ret; + + dsi_mode = readl(dsi->regs + DSI_MODE_CTRL); + if (dsi_mode & MODE) { + mtk_dsi_stop(dsi); + ret = mtk_dsi_switch_to_cmd_mode(dsi, VM_DONE_INT_FLAG, 500); + if (ret) + goto restore_dsi_mode; + } + + if (MTK_DSI_HOST_IS_READ(msg->type)) + irq_flag |= LPRX_RD_RDY_INT_FLAG; + + mtk_dsi_lane_ready(dsi); + + ret = mtk_dsi_host_send_cmd(dsi, msg, irq_flag); + if (ret) + goto restore_dsi_mode; + + if (!MTK_DSI_HOST_IS_READ(msg->type)) { + recv_cnt = 0; + goto restore_dsi_mode; + } + + if (!msg->rx_buf) { + DRM_ERROR("dsi receive buffer size may be NULL\n"); + ret = -EINVAL; + goto restore_dsi_mode; + } + + for (i = 0; i < 16; i++) + *(read_data + i) = readb(dsi->regs + DSI_RX_DATA0 + i); + + recv_cnt = mtk_dsi_recv_cnt(read_data[0], read_data); + + if (recv_cnt > 2) + src_addr = &read_data[4]; + else + src_addr = &read_data[1]; + + if (recv_cnt > 10) + recv_cnt = 10; + + if (recv_cnt > msg->rx_len) + recv_cnt = msg->rx_len; + + if (recv_cnt) + memcpy(msg->rx_buf, src_addr, recv_cnt); + + DRM_INFO("dsi get %d byte data from the panel address(0x%x)\n", + recv_cnt, *((u8 *)(msg->tx_buf))); + +restore_dsi_mode: + if (dsi_mode & MODE) { + mtk_dsi_set_mode(dsi); + mtk_dsi_start(dsi); + } + + return ret < 0 ? ret : recv_cnt; +} + +static const struct mipi_dsi_host_ops mtk_dsi_ops = { + .attach = mtk_dsi_host_attach, + .detach = mtk_dsi_host_detach, + .transfer = mtk_dsi_host_transfer, +}; + +static int mtk_dsi_probe(struct platform_device *pdev) +{ + struct mtk_dsi *dsi; + struct device *dev = &pdev->dev; + struct resource *regs; + int irq_num; + int ret; + + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; + + dsi->host.ops = &mtk_dsi_ops; + dsi->host.dev = dev; + ret = mipi_dsi_host_register(&dsi->host); + if (ret < 0) { + dev_err(dev, "failed to register DSI host: %d\n", ret); + return ret; + } + + dsi->driver_data = of_device_get_match_data(dev); + + dsi->engine_clk = devm_clk_get(dev, "engine"); + if (IS_ERR(dsi->engine_clk)) { + ret = PTR_ERR(dsi->engine_clk); + + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get engine clock: %d\n", ret); + goto err_unregister_host; + } + + dsi->digital_clk = devm_clk_get(dev, "digital"); + if (IS_ERR(dsi->digital_clk)) { + ret = PTR_ERR(dsi->digital_clk); + + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get digital clock: %d\n", ret); + goto err_unregister_host; + } + + dsi->hs_clk = devm_clk_get(dev, "hs"); + if (IS_ERR(dsi->hs_clk)) { + ret = PTR_ERR(dsi->hs_clk); + dev_err(dev, "Failed to get hs clock: %d\n", ret); + goto err_unregister_host; + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dsi->regs = devm_ioremap_resource(dev, regs); + if (IS_ERR(dsi->regs)) { + ret = PTR_ERR(dsi->regs); + dev_err(dev, "Failed to ioremap memory: %d\n", ret); + goto err_unregister_host; + } + + dsi->phy = devm_phy_get(dev, "dphy"); + if (IS_ERR(dsi->phy)) { + ret = PTR_ERR(dsi->phy); + dev_err(dev, "Failed to get MIPI-DPHY: %d\n", ret); + goto err_unregister_host; + } + + irq_num = platform_get_irq(pdev, 0); + if (irq_num < 0) { + ret = irq_num; + goto err_unregister_host; + } + + ret = devm_request_irq(&pdev->dev, irq_num, mtk_dsi_irq, + IRQF_TRIGGER_NONE, dev_name(&pdev->dev), dsi); + if (ret) { + dev_err(&pdev->dev, "failed to request mediatek dsi irq\n"); + goto err_unregister_host; + } + + init_waitqueue_head(&dsi->irq_wait_queue); + + platform_set_drvdata(pdev, dsi); + + dsi->bridge.funcs = &mtk_dsi_bridge_funcs; + dsi->bridge.of_node = dev->of_node; + dsi->bridge.type = DRM_MODE_CONNECTOR_DSI; + + return 0; + +err_unregister_host: + mipi_dsi_host_unregister(&dsi->host); + return ret; +} + +static int mtk_dsi_remove(struct platform_device *pdev) +{ + struct mtk_dsi *dsi = platform_get_drvdata(pdev); + + mtk_output_dsi_disable(dsi); + mipi_dsi_host_unregister(&dsi->host); + + return 0; +} + +static const struct mtk_dsi_driver_data mt8173_dsi_driver_data = { + .reg_cmdq_off = 0x200, +}; + +static const struct mtk_dsi_driver_data mt2701_dsi_driver_data = { + .reg_cmdq_off = 0x180, +}; + +static const struct mtk_dsi_driver_data mt8183_dsi_driver_data = { + .reg_cmdq_off = 0x200, + .has_shadow_ctl = true, + .has_size_ctl = true, +}; + +static const struct mtk_dsi_driver_data mt8186_dsi_driver_data = { + .reg_cmdq_off = 0xd00, + .has_shadow_ctl = true, + .has_size_ctl = true, +}; + +static const struct of_device_id mtk_dsi_of_match[] = { + { .compatible = "mediatek,mt2701-dsi", + .data = &mt2701_dsi_driver_data }, + { .compatible = "mediatek,mt8173-dsi", + .data = &mt8173_dsi_driver_data }, + { .compatible = "mediatek,mt8183-dsi", + .data = &mt8183_dsi_driver_data }, + { .compatible = "mediatek,mt8186-dsi", + .data = &mt8186_dsi_driver_data }, + { }, +}; +MODULE_DEVICE_TABLE(of, mtk_dsi_of_match); + +struct platform_driver mtk_dsi_driver = { + .probe = mtk_dsi_probe, + .remove = mtk_dsi_remove, + .driver = { + .name = "mtk-dsi", + .of_match_table = mtk_dsi_of_match, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.c b/drivers/gpu/drm/mediatek/mtk_hdmi.c new file mode 100644 index 000000000..6e8f99554 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi.c @@ -0,0 +1,1840 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu <jie.qiu@mediatek.com> + */ + +#include <linux/arm-smccc.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/hdmi.h> +#include <linux/i2c.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_platform.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include <sound/hdmi-codec.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_edid.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "mtk_cec.h" +#include "mtk_hdmi.h" +#include "mtk_hdmi_regs.h" + +#define NCTS_BYTES 7 + +enum mtk_hdmi_clk_id { + MTK_HDMI_CLK_HDMI_PIXEL, + MTK_HDMI_CLK_HDMI_PLL, + MTK_HDMI_CLK_AUD_BCLK, + MTK_HDMI_CLK_AUD_SPDIF, + MTK_HDMI_CLK_COUNT +}; + +enum hdmi_aud_input_type { + HDMI_AUD_INPUT_I2S = 0, + HDMI_AUD_INPUT_SPDIF, +}; + +enum hdmi_aud_i2s_fmt { + HDMI_I2S_MODE_RJT_24BIT = 0, + HDMI_I2S_MODE_RJT_16BIT, + HDMI_I2S_MODE_LJT_24BIT, + HDMI_I2S_MODE_LJT_16BIT, + HDMI_I2S_MODE_I2S_24BIT, + HDMI_I2S_MODE_I2S_16BIT +}; + +enum hdmi_aud_mclk { + HDMI_AUD_MCLK_128FS, + HDMI_AUD_MCLK_192FS, + HDMI_AUD_MCLK_256FS, + HDMI_AUD_MCLK_384FS, + HDMI_AUD_MCLK_512FS, + HDMI_AUD_MCLK_768FS, + HDMI_AUD_MCLK_1152FS, +}; + +enum hdmi_aud_channel_type { + HDMI_AUD_CHAN_TYPE_1_0 = 0, + HDMI_AUD_CHAN_TYPE_1_1, + HDMI_AUD_CHAN_TYPE_2_0, + HDMI_AUD_CHAN_TYPE_2_1, + HDMI_AUD_CHAN_TYPE_3_0, + HDMI_AUD_CHAN_TYPE_3_1, + HDMI_AUD_CHAN_TYPE_4_0, + HDMI_AUD_CHAN_TYPE_4_1, + HDMI_AUD_CHAN_TYPE_5_0, + HDMI_AUD_CHAN_TYPE_5_1, + HDMI_AUD_CHAN_TYPE_6_0, + HDMI_AUD_CHAN_TYPE_6_1, + HDMI_AUD_CHAN_TYPE_7_0, + HDMI_AUD_CHAN_TYPE_7_1, + HDMI_AUD_CHAN_TYPE_3_0_LRS, + HDMI_AUD_CHAN_TYPE_3_1_LRS, + HDMI_AUD_CHAN_TYPE_4_0_CLRS, + HDMI_AUD_CHAN_TYPE_4_1_CLRS, + HDMI_AUD_CHAN_TYPE_6_1_CS, + HDMI_AUD_CHAN_TYPE_6_1_CH, + HDMI_AUD_CHAN_TYPE_6_1_OH, + HDMI_AUD_CHAN_TYPE_6_1_CHR, + HDMI_AUD_CHAN_TYPE_7_1_LH_RH, + HDMI_AUD_CHAN_TYPE_7_1_LSR_RSR, + HDMI_AUD_CHAN_TYPE_7_1_LC_RC, + HDMI_AUD_CHAN_TYPE_7_1_LW_RW, + HDMI_AUD_CHAN_TYPE_7_1_LSD_RSD, + HDMI_AUD_CHAN_TYPE_7_1_LSS_RSS, + HDMI_AUD_CHAN_TYPE_7_1_LHS_RHS, + HDMI_AUD_CHAN_TYPE_7_1_CS_CH, + HDMI_AUD_CHAN_TYPE_7_1_CS_OH, + HDMI_AUD_CHAN_TYPE_7_1_CS_CHR, + HDMI_AUD_CHAN_TYPE_7_1_CH_OH, + HDMI_AUD_CHAN_TYPE_7_1_CH_CHR, + HDMI_AUD_CHAN_TYPE_7_1_OH_CHR, + HDMI_AUD_CHAN_TYPE_7_1_LSS_RSS_LSR_RSR, + HDMI_AUD_CHAN_TYPE_6_0_CS, + HDMI_AUD_CHAN_TYPE_6_0_CH, + HDMI_AUD_CHAN_TYPE_6_0_OH, + HDMI_AUD_CHAN_TYPE_6_0_CHR, + HDMI_AUD_CHAN_TYPE_7_0_LH_RH, + HDMI_AUD_CHAN_TYPE_7_0_LSR_RSR, + HDMI_AUD_CHAN_TYPE_7_0_LC_RC, + HDMI_AUD_CHAN_TYPE_7_0_LW_RW, + HDMI_AUD_CHAN_TYPE_7_0_LSD_RSD, + HDMI_AUD_CHAN_TYPE_7_0_LSS_RSS, + HDMI_AUD_CHAN_TYPE_7_0_LHS_RHS, + HDMI_AUD_CHAN_TYPE_7_0_CS_CH, + HDMI_AUD_CHAN_TYPE_7_0_CS_OH, + HDMI_AUD_CHAN_TYPE_7_0_CS_CHR, + HDMI_AUD_CHAN_TYPE_7_0_CH_OH, + HDMI_AUD_CHAN_TYPE_7_0_CH_CHR, + HDMI_AUD_CHAN_TYPE_7_0_OH_CHR, + HDMI_AUD_CHAN_TYPE_7_0_LSS_RSS_LSR_RSR, + HDMI_AUD_CHAN_TYPE_8_0_LH_RH_CS, + HDMI_AUD_CHAN_TYPE_UNKNOWN = 0xFF +}; + +enum hdmi_aud_channel_swap_type { + HDMI_AUD_SWAP_LR, + HDMI_AUD_SWAP_LFE_CC, + HDMI_AUD_SWAP_LSRS, + HDMI_AUD_SWAP_RLS_RRS, + HDMI_AUD_SWAP_LR_STATUS, +}; + +struct hdmi_audio_param { + enum hdmi_audio_coding_type aud_codec; + enum hdmi_audio_sample_size aud_sampe_size; + enum hdmi_aud_input_type aud_input_type; + enum hdmi_aud_i2s_fmt aud_i2s_fmt; + enum hdmi_aud_mclk aud_mclk; + enum hdmi_aud_channel_type aud_input_chan_type; + struct hdmi_codec_params codec_params; +}; + +struct mtk_hdmi_conf { + bool tz_disabled; + bool cea_modes_only; + unsigned long max_mode_clock; +}; + +struct mtk_hdmi { + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + struct drm_connector *curr_conn;/* current connector (only valid when 'enabled') */ + struct device *dev; + const struct mtk_hdmi_conf *conf; + struct phy *phy; + struct device *cec_dev; + struct i2c_adapter *ddc_adpt; + struct clk *clk[MTK_HDMI_CLK_COUNT]; + struct drm_display_mode mode; + bool dvi_mode; + u32 min_clock; + u32 max_clock; + u32 max_hdisplay; + u32 max_vdisplay; + u32 ibias; + u32 ibias_up; + struct regmap *sys_regmap; + unsigned int sys_offset; + void __iomem *regs; + enum hdmi_colorspace csp; + struct hdmi_audio_param aud_param; + bool audio_enable; + bool powered; + bool enabled; + hdmi_codec_plugged_cb plugged_cb; + struct device *codec_dev; + struct mutex update_plugged_status_lock; +}; + +static inline struct mtk_hdmi *hdmi_ctx_from_bridge(struct drm_bridge *b) +{ + return container_of(b, struct mtk_hdmi, bridge); +} + +static u32 mtk_hdmi_read(struct mtk_hdmi *hdmi, u32 offset) +{ + return readl(hdmi->regs + offset); +} + +static void mtk_hdmi_write(struct mtk_hdmi *hdmi, u32 offset, u32 val) +{ + writel(val, hdmi->regs + offset); +} + +static void mtk_hdmi_clear_bits(struct mtk_hdmi *hdmi, u32 offset, u32 bits) +{ + void __iomem *reg = hdmi->regs + offset; + u32 tmp; + + tmp = readl(reg); + tmp &= ~bits; + writel(tmp, reg); +} + +static void mtk_hdmi_set_bits(struct mtk_hdmi *hdmi, u32 offset, u32 bits) +{ + void __iomem *reg = hdmi->regs + offset; + u32 tmp; + + tmp = readl(reg); + tmp |= bits; + writel(tmp, reg); +} + +static void mtk_hdmi_mask(struct mtk_hdmi *hdmi, u32 offset, u32 val, u32 mask) +{ + void __iomem *reg = hdmi->regs + offset; + u32 tmp; + + tmp = readl(reg); + tmp = (tmp & ~mask) | (val & mask); + writel(tmp, reg); +} + +static void mtk_hdmi_hw_vid_black(struct mtk_hdmi *hdmi, bool black) +{ + mtk_hdmi_mask(hdmi, VIDEO_CFG_4, black ? GEN_RGB : NORMAL_PATH, + VIDEO_SOURCE_SEL); +} + +static void mtk_hdmi_hw_make_reg_writable(struct mtk_hdmi *hdmi, bool enable) +{ + struct arm_smccc_res res; + + /* + * MT8173 HDMI hardware has an output control bit to enable/disable HDMI + * output. This bit can only be controlled in ARM supervisor mode. + * The ARM trusted firmware provides an API for the HDMI driver to set + * this control bit to enable HDMI output in supervisor mode. + */ + if (hdmi->conf && hdmi->conf->tz_disabled) + regmap_update_bits(hdmi->sys_regmap, + hdmi->sys_offset + HDMI_SYS_CFG20, + 0x80008005, enable ? 0x80000005 : 0x8000); + else + arm_smccc_smc(MTK_SIP_SET_AUTHORIZED_SECURE_REG, 0x14000904, + 0x80000000, 0, 0, 0, 0, 0, &res); + + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20, + HDMI_PCLK_FREE_RUN, enable ? HDMI_PCLK_FREE_RUN : 0); + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C, + HDMI_ON | ANLG_ON, enable ? (HDMI_ON | ANLG_ON) : 0); +} + +static void mtk_hdmi_hw_1p4_version_enable(struct mtk_hdmi *hdmi, bool enable) +{ + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20, + HDMI2P0_EN, enable ? 0 : HDMI2P0_EN); +} + +static void mtk_hdmi_hw_aud_mute(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_set_bits(hdmi, GRL_AUDIO_CFG, AUDIO_ZERO); +} + +static void mtk_hdmi_hw_aud_unmute(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_clear_bits(hdmi, GRL_AUDIO_CFG, AUDIO_ZERO); +} + +static void mtk_hdmi_hw_reset(struct mtk_hdmi *hdmi) +{ + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C, + HDMI_RST, HDMI_RST); + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C, + HDMI_RST, 0); + mtk_hdmi_clear_bits(hdmi, GRL_CFG3, CFG3_CONTROL_PACKET_DELAY); + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C, + ANLG_ON, ANLG_ON); +} + +static void mtk_hdmi_hw_enable_notice(struct mtk_hdmi *hdmi, bool enable_notice) +{ + mtk_hdmi_mask(hdmi, GRL_CFG2, enable_notice ? CFG2_NOTICE_EN : 0, + CFG2_NOTICE_EN); +} + +static void mtk_hdmi_hw_write_int_mask(struct mtk_hdmi *hdmi, u32 int_mask) +{ + mtk_hdmi_write(hdmi, GRL_INT_MASK, int_mask); +} + +static void mtk_hdmi_hw_enable_dvi_mode(struct mtk_hdmi *hdmi, bool enable) +{ + mtk_hdmi_mask(hdmi, GRL_CFG1, enable ? CFG1_DVI : 0, CFG1_DVI); +} + +static void mtk_hdmi_hw_send_info_frame(struct mtk_hdmi *hdmi, u8 *buffer, + u8 len) +{ + u32 ctrl_reg = GRL_CTRL; + int i; + u8 *frame_data; + enum hdmi_infoframe_type frame_type; + u8 frame_ver; + u8 frame_len; + u8 checksum; + int ctrl_frame_en = 0; + + frame_type = *buffer++; + frame_ver = *buffer++; + frame_len = *buffer++; + checksum = *buffer++; + frame_data = buffer; + + dev_dbg(hdmi->dev, + "frame_type:0x%x,frame_ver:0x%x,frame_len:0x%x,checksum:0x%x\n", + frame_type, frame_ver, frame_len, checksum); + + switch (frame_type) { + case HDMI_INFOFRAME_TYPE_AVI: + ctrl_frame_en = CTRL_AVI_EN; + ctrl_reg = GRL_CTRL; + break; + case HDMI_INFOFRAME_TYPE_SPD: + ctrl_frame_en = CTRL_SPD_EN; + ctrl_reg = GRL_CTRL; + break; + case HDMI_INFOFRAME_TYPE_AUDIO: + ctrl_frame_en = CTRL_AUDIO_EN; + ctrl_reg = GRL_CTRL; + break; + case HDMI_INFOFRAME_TYPE_VENDOR: + ctrl_frame_en = VS_EN; + ctrl_reg = GRL_ACP_ISRC_CTRL; + break; + default: + dev_err(hdmi->dev, "Unknown infoframe type %d\n", frame_type); + return; + } + mtk_hdmi_clear_bits(hdmi, ctrl_reg, ctrl_frame_en); + mtk_hdmi_write(hdmi, GRL_INFOFRM_TYPE, frame_type); + mtk_hdmi_write(hdmi, GRL_INFOFRM_VER, frame_ver); + mtk_hdmi_write(hdmi, GRL_INFOFRM_LNG, frame_len); + + mtk_hdmi_write(hdmi, GRL_IFM_PORT, checksum); + for (i = 0; i < frame_len; i++) + mtk_hdmi_write(hdmi, GRL_IFM_PORT, frame_data[i]); + + mtk_hdmi_set_bits(hdmi, ctrl_reg, ctrl_frame_en); +} + +static void mtk_hdmi_hw_send_aud_packet(struct mtk_hdmi *hdmi, bool enable) +{ + mtk_hdmi_mask(hdmi, GRL_SHIFT_R2, enable ? 0 : AUDIO_PACKET_OFF, + AUDIO_PACKET_OFF); +} + +static void mtk_hdmi_hw_config_sys(struct mtk_hdmi *hdmi) +{ + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20, + HDMI_OUT_FIFO_EN | MHL_MODE_ON, 0); + usleep_range(2000, 4000); + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20, + HDMI_OUT_FIFO_EN | MHL_MODE_ON, HDMI_OUT_FIFO_EN); +} + +static void mtk_hdmi_hw_set_deep_color_mode(struct mtk_hdmi *hdmi) +{ + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20, + DEEP_COLOR_MODE_MASK | DEEP_COLOR_EN, + COLOR_8BIT_MODE); +} + +static void mtk_hdmi_hw_send_av_mute(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_clear_bits(hdmi, GRL_CFG4, CTRL_AVMUTE); + usleep_range(2000, 4000); + mtk_hdmi_set_bits(hdmi, GRL_CFG4, CTRL_AVMUTE); +} + +static void mtk_hdmi_hw_send_av_unmute(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_mask(hdmi, GRL_CFG4, CFG4_AV_UNMUTE_EN, + CFG4_AV_UNMUTE_EN | CFG4_AV_UNMUTE_SET); + usleep_range(2000, 4000); + mtk_hdmi_mask(hdmi, GRL_CFG4, CFG4_AV_UNMUTE_SET, + CFG4_AV_UNMUTE_EN | CFG4_AV_UNMUTE_SET); +} + +static void mtk_hdmi_hw_ncts_enable(struct mtk_hdmi *hdmi, bool on) +{ + mtk_hdmi_mask(hdmi, GRL_CTS_CTRL, on ? 0 : CTS_CTRL_SOFT, + CTS_CTRL_SOFT); +} + +static void mtk_hdmi_hw_ncts_auto_write_enable(struct mtk_hdmi *hdmi, + bool enable) +{ + mtk_hdmi_mask(hdmi, GRL_CTS_CTRL, enable ? NCTS_WRI_ANYTIME : 0, + NCTS_WRI_ANYTIME); +} + +static void mtk_hdmi_hw_msic_setting(struct mtk_hdmi *hdmi, + struct drm_display_mode *mode) +{ + mtk_hdmi_clear_bits(hdmi, GRL_CFG4, CFG4_MHL_MODE); + + if (mode->flags & DRM_MODE_FLAG_INTERLACE && + mode->clock == 74250 && + mode->vdisplay == 1080) + mtk_hdmi_clear_bits(hdmi, GRL_CFG2, CFG2_MHL_DE_SEL); + else + mtk_hdmi_set_bits(hdmi, GRL_CFG2, CFG2_MHL_DE_SEL); +} + +static void mtk_hdmi_hw_aud_set_channel_swap(struct mtk_hdmi *hdmi, + enum hdmi_aud_channel_swap_type swap) +{ + u8 swap_bit; + + switch (swap) { + case HDMI_AUD_SWAP_LR: + swap_bit = LR_SWAP; + break; + case HDMI_AUD_SWAP_LFE_CC: + swap_bit = LFE_CC_SWAP; + break; + case HDMI_AUD_SWAP_LSRS: + swap_bit = LSRS_SWAP; + break; + case HDMI_AUD_SWAP_RLS_RRS: + swap_bit = RLS_RRS_SWAP; + break; + case HDMI_AUD_SWAP_LR_STATUS: + swap_bit = LR_STATUS_SWAP; + break; + default: + swap_bit = LFE_CC_SWAP; + break; + } + mtk_hdmi_mask(hdmi, GRL_CH_SWAP, swap_bit, 0xff); +} + +static void mtk_hdmi_hw_aud_set_bit_num(struct mtk_hdmi *hdmi, + enum hdmi_audio_sample_size bit_num) +{ + u32 val; + + switch (bit_num) { + case HDMI_AUDIO_SAMPLE_SIZE_16: + val = AOUT_16BIT; + break; + case HDMI_AUDIO_SAMPLE_SIZE_20: + val = AOUT_20BIT; + break; + case HDMI_AUDIO_SAMPLE_SIZE_24: + case HDMI_AUDIO_SAMPLE_SIZE_STREAM: + val = AOUT_24BIT; + break; + } + + mtk_hdmi_mask(hdmi, GRL_AOUT_CFG, val, AOUT_BNUM_SEL_MASK); +} + +static void mtk_hdmi_hw_aud_set_i2s_fmt(struct mtk_hdmi *hdmi, + enum hdmi_aud_i2s_fmt i2s_fmt) +{ + u32 val; + + val = mtk_hdmi_read(hdmi, GRL_CFG0); + val &= ~(CFG0_W_LENGTH_MASK | CFG0_I2S_MODE_MASK); + + switch (i2s_fmt) { + case HDMI_I2S_MODE_RJT_24BIT: + val |= CFG0_I2S_MODE_RTJ | CFG0_W_LENGTH_24BIT; + break; + case HDMI_I2S_MODE_RJT_16BIT: + val |= CFG0_I2S_MODE_RTJ | CFG0_W_LENGTH_16BIT; + break; + case HDMI_I2S_MODE_LJT_24BIT: + default: + val |= CFG0_I2S_MODE_LTJ | CFG0_W_LENGTH_24BIT; + break; + case HDMI_I2S_MODE_LJT_16BIT: + val |= CFG0_I2S_MODE_LTJ | CFG0_W_LENGTH_16BIT; + break; + case HDMI_I2S_MODE_I2S_24BIT: + val |= CFG0_I2S_MODE_I2S | CFG0_W_LENGTH_24BIT; + break; + case HDMI_I2S_MODE_I2S_16BIT: + val |= CFG0_I2S_MODE_I2S | CFG0_W_LENGTH_16BIT; + break; + } + mtk_hdmi_write(hdmi, GRL_CFG0, val); +} + +static void mtk_hdmi_hw_audio_config(struct mtk_hdmi *hdmi, bool dst) +{ + const u8 mask = HIGH_BIT_RATE | DST_NORMAL_DOUBLE | SACD_DST | DSD_SEL; + u8 val; + + /* Disable high bitrate, set DST packet normal/double */ + mtk_hdmi_clear_bits(hdmi, GRL_AOUT_CFG, HIGH_BIT_RATE_PACKET_ALIGN); + + if (dst) + val = DST_NORMAL_DOUBLE | SACD_DST; + else + val = 0; + + mtk_hdmi_mask(hdmi, GRL_AUDIO_CFG, val, mask); +} + +static void mtk_hdmi_hw_aud_set_i2s_chan_num(struct mtk_hdmi *hdmi, + enum hdmi_aud_channel_type channel_type, + u8 channel_count) +{ + unsigned int ch_switch; + u8 i2s_uv; + + ch_switch = CH_SWITCH(7, 7) | CH_SWITCH(6, 6) | + CH_SWITCH(5, 5) | CH_SWITCH(4, 4) | + CH_SWITCH(3, 3) | CH_SWITCH(1, 2) | + CH_SWITCH(2, 1) | CH_SWITCH(0, 0); + + if (channel_count == 2) { + i2s_uv = I2S_UV_CH_EN(0); + } else if (channel_count == 3 || channel_count == 4) { + if (channel_count == 4 && + (channel_type == HDMI_AUD_CHAN_TYPE_3_0_LRS || + channel_type == HDMI_AUD_CHAN_TYPE_4_0)) + i2s_uv = I2S_UV_CH_EN(2) | I2S_UV_CH_EN(0); + else + i2s_uv = I2S_UV_CH_EN(3) | I2S_UV_CH_EN(2); + } else if (channel_count == 6 || channel_count == 5) { + if (channel_count == 6 && + channel_type != HDMI_AUD_CHAN_TYPE_5_1 && + channel_type != HDMI_AUD_CHAN_TYPE_4_1_CLRS) { + i2s_uv = I2S_UV_CH_EN(3) | I2S_UV_CH_EN(2) | + I2S_UV_CH_EN(1) | I2S_UV_CH_EN(0); + } else { + i2s_uv = I2S_UV_CH_EN(2) | I2S_UV_CH_EN(1) | + I2S_UV_CH_EN(0); + } + } else if (channel_count == 8 || channel_count == 7) { + i2s_uv = I2S_UV_CH_EN(3) | I2S_UV_CH_EN(2) | + I2S_UV_CH_EN(1) | I2S_UV_CH_EN(0); + } else { + i2s_uv = I2S_UV_CH_EN(0); + } + + mtk_hdmi_write(hdmi, GRL_CH_SW0, ch_switch & 0xff); + mtk_hdmi_write(hdmi, GRL_CH_SW1, (ch_switch >> 8) & 0xff); + mtk_hdmi_write(hdmi, GRL_CH_SW2, (ch_switch >> 16) & 0xff); + mtk_hdmi_write(hdmi, GRL_I2S_UV, i2s_uv); +} + +static void mtk_hdmi_hw_aud_set_input_type(struct mtk_hdmi *hdmi, + enum hdmi_aud_input_type input_type) +{ + u32 val; + + val = mtk_hdmi_read(hdmi, GRL_CFG1); + if (input_type == HDMI_AUD_INPUT_I2S && + (val & CFG1_SPDIF) == CFG1_SPDIF) { + val &= ~CFG1_SPDIF; + } else if (input_type == HDMI_AUD_INPUT_SPDIF && + (val & CFG1_SPDIF) == 0) { + val |= CFG1_SPDIF; + } + mtk_hdmi_write(hdmi, GRL_CFG1, val); +} + +static void mtk_hdmi_hw_aud_set_channel_status(struct mtk_hdmi *hdmi, + u8 *channel_status) +{ + int i; + + for (i = 0; i < 5; i++) { + mtk_hdmi_write(hdmi, GRL_I2S_C_STA0 + i * 4, channel_status[i]); + mtk_hdmi_write(hdmi, GRL_L_STATUS_0 + i * 4, channel_status[i]); + mtk_hdmi_write(hdmi, GRL_R_STATUS_0 + i * 4, channel_status[i]); + } + for (; i < 24; i++) { + mtk_hdmi_write(hdmi, GRL_L_STATUS_0 + i * 4, 0); + mtk_hdmi_write(hdmi, GRL_R_STATUS_0 + i * 4, 0); + } +} + +static void mtk_hdmi_hw_aud_src_reenable(struct mtk_hdmi *hdmi) +{ + u32 val; + + val = mtk_hdmi_read(hdmi, GRL_MIX_CTRL); + if (val & MIX_CTRL_SRC_EN) { + val &= ~MIX_CTRL_SRC_EN; + mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val); + usleep_range(255, 512); + val |= MIX_CTRL_SRC_EN; + mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val); + } +} + +static void mtk_hdmi_hw_aud_src_disable(struct mtk_hdmi *hdmi) +{ + u32 val; + + val = mtk_hdmi_read(hdmi, GRL_MIX_CTRL); + val &= ~MIX_CTRL_SRC_EN; + mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val); + mtk_hdmi_write(hdmi, GRL_SHIFT_L1, 0x00); +} + +static void mtk_hdmi_hw_aud_set_mclk(struct mtk_hdmi *hdmi, + enum hdmi_aud_mclk mclk) +{ + u32 val; + + val = mtk_hdmi_read(hdmi, GRL_CFG5); + val &= CFG5_CD_RATIO_MASK; + + switch (mclk) { + case HDMI_AUD_MCLK_128FS: + val |= CFG5_FS128; + break; + case HDMI_AUD_MCLK_256FS: + val |= CFG5_FS256; + break; + case HDMI_AUD_MCLK_384FS: + val |= CFG5_FS384; + break; + case HDMI_AUD_MCLK_512FS: + val |= CFG5_FS512; + break; + case HDMI_AUD_MCLK_768FS: + val |= CFG5_FS768; + break; + default: + val |= CFG5_FS256; + break; + } + mtk_hdmi_write(hdmi, GRL_CFG5, val); +} + +struct hdmi_acr_n { + unsigned int clock; + unsigned int n[3]; +}; + +/* Recommended N values from HDMI specification, tables 7-1 to 7-3 */ +static const struct hdmi_acr_n hdmi_rec_n_table[] = { + /* Clock, N: 32kHz 44.1kHz 48kHz */ + { 25175, { 4576, 7007, 6864 } }, + { 74176, { 11648, 17836, 11648 } }, + { 148352, { 11648, 8918, 5824 } }, + { 296703, { 5824, 4459, 5824 } }, + { 297000, { 3072, 4704, 5120 } }, + { 0, { 4096, 6272, 6144 } }, /* all other TMDS clocks */ +}; + +/** + * hdmi_recommended_n() - Return N value recommended by HDMI specification + * @freq: audio sample rate in Hz + * @clock: rounded TMDS clock in kHz + */ +static unsigned int hdmi_recommended_n(unsigned int freq, unsigned int clock) +{ + const struct hdmi_acr_n *recommended; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(hdmi_rec_n_table) - 1; i++) { + if (clock == hdmi_rec_n_table[i].clock) + break; + } + recommended = hdmi_rec_n_table + i; + + switch (freq) { + case 32000: + return recommended->n[0]; + case 44100: + return recommended->n[1]; + case 48000: + return recommended->n[2]; + case 88200: + return recommended->n[1] * 2; + case 96000: + return recommended->n[2] * 2; + case 176400: + return recommended->n[1] * 4; + case 192000: + return recommended->n[2] * 4; + default: + return (128 * freq) / 1000; + } +} + +static unsigned int hdmi_mode_clock_to_hz(unsigned int clock) +{ + switch (clock) { + case 25175: + return 25174825; /* 25.2/1.001 MHz */ + case 74176: + return 74175824; /* 74.25/1.001 MHz */ + case 148352: + return 148351648; /* 148.5/1.001 MHz */ + case 296703: + return 296703297; /* 297/1.001 MHz */ + default: + return clock * 1000; + } +} + +static unsigned int hdmi_expected_cts(unsigned int audio_sample_rate, + unsigned int tmds_clock, unsigned int n) +{ + return DIV_ROUND_CLOSEST_ULL((u64)hdmi_mode_clock_to_hz(tmds_clock) * n, + 128 * audio_sample_rate); +} + +static void do_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi, unsigned int n, + unsigned int cts) +{ + unsigned char val[NCTS_BYTES]; + int i; + + mtk_hdmi_write(hdmi, GRL_NCTS, 0); + mtk_hdmi_write(hdmi, GRL_NCTS, 0); + mtk_hdmi_write(hdmi, GRL_NCTS, 0); + memset(val, 0, sizeof(val)); + + val[0] = (cts >> 24) & 0xff; + val[1] = (cts >> 16) & 0xff; + val[2] = (cts >> 8) & 0xff; + val[3] = cts & 0xff; + + val[4] = (n >> 16) & 0xff; + val[5] = (n >> 8) & 0xff; + val[6] = n & 0xff; + + for (i = 0; i < NCTS_BYTES; i++) + mtk_hdmi_write(hdmi, GRL_NCTS, val[i]); +} + +static void mtk_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi, + unsigned int sample_rate, + unsigned int clock) +{ + unsigned int n, cts; + + n = hdmi_recommended_n(sample_rate, clock); + cts = hdmi_expected_cts(sample_rate, clock, n); + + dev_dbg(hdmi->dev, "%s: sample_rate=%u, clock=%d, cts=%u, n=%u\n", + __func__, sample_rate, clock, n, cts); + + mtk_hdmi_mask(hdmi, DUMMY_304, AUDIO_I2S_NCTS_SEL_64, + AUDIO_I2S_NCTS_SEL); + do_hdmi_hw_aud_set_ncts(hdmi, n, cts); +} + +static u8 mtk_hdmi_aud_get_chnl_count(enum hdmi_aud_channel_type channel_type) +{ + switch (channel_type) { + case HDMI_AUD_CHAN_TYPE_1_0: + case HDMI_AUD_CHAN_TYPE_1_1: + case HDMI_AUD_CHAN_TYPE_2_0: + return 2; + case HDMI_AUD_CHAN_TYPE_2_1: + case HDMI_AUD_CHAN_TYPE_3_0: + return 3; + case HDMI_AUD_CHAN_TYPE_3_1: + case HDMI_AUD_CHAN_TYPE_4_0: + case HDMI_AUD_CHAN_TYPE_3_0_LRS: + return 4; + case HDMI_AUD_CHAN_TYPE_4_1: + case HDMI_AUD_CHAN_TYPE_5_0: + case HDMI_AUD_CHAN_TYPE_3_1_LRS: + case HDMI_AUD_CHAN_TYPE_4_0_CLRS: + return 5; + case HDMI_AUD_CHAN_TYPE_5_1: + case HDMI_AUD_CHAN_TYPE_6_0: + case HDMI_AUD_CHAN_TYPE_4_1_CLRS: + case HDMI_AUD_CHAN_TYPE_6_0_CS: + case HDMI_AUD_CHAN_TYPE_6_0_CH: + case HDMI_AUD_CHAN_TYPE_6_0_OH: + case HDMI_AUD_CHAN_TYPE_6_0_CHR: + return 6; + case HDMI_AUD_CHAN_TYPE_6_1: + case HDMI_AUD_CHAN_TYPE_6_1_CS: + case HDMI_AUD_CHAN_TYPE_6_1_CH: + case HDMI_AUD_CHAN_TYPE_6_1_OH: + case HDMI_AUD_CHAN_TYPE_6_1_CHR: + case HDMI_AUD_CHAN_TYPE_7_0: + case HDMI_AUD_CHAN_TYPE_7_0_LH_RH: + case HDMI_AUD_CHAN_TYPE_7_0_LSR_RSR: + case HDMI_AUD_CHAN_TYPE_7_0_LC_RC: + case HDMI_AUD_CHAN_TYPE_7_0_LW_RW: + case HDMI_AUD_CHAN_TYPE_7_0_LSD_RSD: + case HDMI_AUD_CHAN_TYPE_7_0_LSS_RSS: + case HDMI_AUD_CHAN_TYPE_7_0_LHS_RHS: + case HDMI_AUD_CHAN_TYPE_7_0_CS_CH: + case HDMI_AUD_CHAN_TYPE_7_0_CS_OH: + case HDMI_AUD_CHAN_TYPE_7_0_CS_CHR: + case HDMI_AUD_CHAN_TYPE_7_0_CH_OH: + case HDMI_AUD_CHAN_TYPE_7_0_CH_CHR: + case HDMI_AUD_CHAN_TYPE_7_0_OH_CHR: + case HDMI_AUD_CHAN_TYPE_7_0_LSS_RSS_LSR_RSR: + case HDMI_AUD_CHAN_TYPE_8_0_LH_RH_CS: + return 7; + case HDMI_AUD_CHAN_TYPE_7_1: + case HDMI_AUD_CHAN_TYPE_7_1_LH_RH: + case HDMI_AUD_CHAN_TYPE_7_1_LSR_RSR: + case HDMI_AUD_CHAN_TYPE_7_1_LC_RC: + case HDMI_AUD_CHAN_TYPE_7_1_LW_RW: + case HDMI_AUD_CHAN_TYPE_7_1_LSD_RSD: + case HDMI_AUD_CHAN_TYPE_7_1_LSS_RSS: + case HDMI_AUD_CHAN_TYPE_7_1_LHS_RHS: + case HDMI_AUD_CHAN_TYPE_7_1_CS_CH: + case HDMI_AUD_CHAN_TYPE_7_1_CS_OH: + case HDMI_AUD_CHAN_TYPE_7_1_CS_CHR: + case HDMI_AUD_CHAN_TYPE_7_1_CH_OH: + case HDMI_AUD_CHAN_TYPE_7_1_CH_CHR: + case HDMI_AUD_CHAN_TYPE_7_1_OH_CHR: + case HDMI_AUD_CHAN_TYPE_7_1_LSS_RSS_LSR_RSR: + return 8; + default: + return 2; + } +} + +static int mtk_hdmi_video_change_vpll(struct mtk_hdmi *hdmi, u32 clock) +{ + unsigned long rate; + int ret; + + /* The DPI driver already should have set TVDPLL to the correct rate */ + ret = clk_set_rate(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL], clock); + if (ret) { + dev_err(hdmi->dev, "Failed to set PLL to %u Hz: %d\n", clock, + ret); + return ret; + } + + rate = clk_get_rate(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]); + + if (DIV_ROUND_CLOSEST(rate, 1000) != DIV_ROUND_CLOSEST(clock, 1000)) + dev_warn(hdmi->dev, "Want PLL %u Hz, got %lu Hz\n", clock, + rate); + else + dev_dbg(hdmi->dev, "Want PLL %u Hz, got %lu Hz\n", clock, rate); + + mtk_hdmi_hw_config_sys(hdmi); + mtk_hdmi_hw_set_deep_color_mode(hdmi); + return 0; +} + +static void mtk_hdmi_video_set_display_mode(struct mtk_hdmi *hdmi, + struct drm_display_mode *mode) +{ + mtk_hdmi_hw_reset(hdmi); + mtk_hdmi_hw_enable_notice(hdmi, true); + mtk_hdmi_hw_write_int_mask(hdmi, 0xff); + mtk_hdmi_hw_enable_dvi_mode(hdmi, hdmi->dvi_mode); + mtk_hdmi_hw_ncts_auto_write_enable(hdmi, true); + + mtk_hdmi_hw_msic_setting(hdmi, mode); +} + + +static void mtk_hdmi_aud_set_input(struct mtk_hdmi *hdmi) +{ + enum hdmi_aud_channel_type chan_type; + u8 chan_count; + bool dst; + + mtk_hdmi_hw_aud_set_channel_swap(hdmi, HDMI_AUD_SWAP_LFE_CC); + mtk_hdmi_set_bits(hdmi, GRL_MIX_CTRL, MIX_CTRL_FLAT); + + if (hdmi->aud_param.aud_input_type == HDMI_AUD_INPUT_SPDIF && + hdmi->aud_param.aud_codec == HDMI_AUDIO_CODING_TYPE_DST) { + mtk_hdmi_hw_aud_set_bit_num(hdmi, HDMI_AUDIO_SAMPLE_SIZE_24); + } else if (hdmi->aud_param.aud_i2s_fmt == HDMI_I2S_MODE_LJT_24BIT) { + hdmi->aud_param.aud_i2s_fmt = HDMI_I2S_MODE_LJT_16BIT; + } + + mtk_hdmi_hw_aud_set_i2s_fmt(hdmi, hdmi->aud_param.aud_i2s_fmt); + mtk_hdmi_hw_aud_set_bit_num(hdmi, HDMI_AUDIO_SAMPLE_SIZE_24); + + dst = ((hdmi->aud_param.aud_input_type == HDMI_AUD_INPUT_SPDIF) && + (hdmi->aud_param.aud_codec == HDMI_AUDIO_CODING_TYPE_DST)); + mtk_hdmi_hw_audio_config(hdmi, dst); + + if (hdmi->aud_param.aud_input_type == HDMI_AUD_INPUT_SPDIF) + chan_type = HDMI_AUD_CHAN_TYPE_2_0; + else + chan_type = hdmi->aud_param.aud_input_chan_type; + chan_count = mtk_hdmi_aud_get_chnl_count(chan_type); + mtk_hdmi_hw_aud_set_i2s_chan_num(hdmi, chan_type, chan_count); + mtk_hdmi_hw_aud_set_input_type(hdmi, hdmi->aud_param.aud_input_type); +} + +static int mtk_hdmi_aud_set_src(struct mtk_hdmi *hdmi, + struct drm_display_mode *display_mode) +{ + unsigned int sample_rate = hdmi->aud_param.codec_params.sample_rate; + + mtk_hdmi_hw_ncts_enable(hdmi, false); + mtk_hdmi_hw_aud_src_disable(hdmi); + mtk_hdmi_clear_bits(hdmi, GRL_CFG2, CFG2_ACLK_INV); + + if (hdmi->aud_param.aud_input_type == HDMI_AUD_INPUT_I2S) { + switch (sample_rate) { + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + break; + default: + return -EINVAL; + } + mtk_hdmi_hw_aud_set_mclk(hdmi, hdmi->aud_param.aud_mclk); + } else { + switch (sample_rate) { + case 32000: + case 44100: + case 48000: + break; + default: + return -EINVAL; + } + mtk_hdmi_hw_aud_set_mclk(hdmi, HDMI_AUD_MCLK_128FS); + } + + mtk_hdmi_hw_aud_set_ncts(hdmi, sample_rate, display_mode->clock); + + mtk_hdmi_hw_aud_src_reenable(hdmi); + return 0; +} + +static int mtk_hdmi_aud_output_config(struct mtk_hdmi *hdmi, + struct drm_display_mode *display_mode) +{ + mtk_hdmi_hw_aud_mute(hdmi); + mtk_hdmi_hw_send_aud_packet(hdmi, false); + + mtk_hdmi_aud_set_input(hdmi); + mtk_hdmi_aud_set_src(hdmi, display_mode); + mtk_hdmi_hw_aud_set_channel_status(hdmi, + hdmi->aud_param.codec_params.iec.status); + + usleep_range(50, 100); + + mtk_hdmi_hw_ncts_enable(hdmi, true); + mtk_hdmi_hw_send_aud_packet(hdmi, true); + mtk_hdmi_hw_aud_unmute(hdmi); + return 0; +} + +static int mtk_hdmi_setup_avi_infoframe(struct mtk_hdmi *hdmi, + struct drm_display_mode *mode) +{ + struct hdmi_avi_infoframe frame; + u8 buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE]; + ssize_t err; + + err = drm_hdmi_avi_infoframe_from_display_mode(&frame, + hdmi->curr_conn, mode); + if (err < 0) { + dev_err(hdmi->dev, + "Failed to get AVI infoframe from mode: %zd\n", err); + return err; + } + + err = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(hdmi->dev, "Failed to pack AVI infoframe: %zd\n", err); + return err; + } + + mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer)); + return 0; +} + +static int mtk_hdmi_setup_spd_infoframe(struct mtk_hdmi *hdmi, + const char *vendor, + const char *product) +{ + struct hdmi_spd_infoframe frame; + u8 buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_SPD_INFOFRAME_SIZE]; + ssize_t err; + + err = hdmi_spd_infoframe_init(&frame, vendor, product); + if (err < 0) { + dev_err(hdmi->dev, "Failed to initialize SPD infoframe: %zd\n", + err); + return err; + } + + err = hdmi_spd_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(hdmi->dev, "Failed to pack SDP infoframe: %zd\n", err); + return err; + } + + mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer)); + return 0; +} + +static int mtk_hdmi_setup_audio_infoframe(struct mtk_hdmi *hdmi) +{ + struct hdmi_audio_infoframe frame; + u8 buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AUDIO_INFOFRAME_SIZE]; + ssize_t err; + + err = hdmi_audio_infoframe_init(&frame); + if (err < 0) { + dev_err(hdmi->dev, "Failed to setup audio infoframe: %zd\n", + err); + return err; + } + + frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; + frame.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM; + frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM; + frame.channels = mtk_hdmi_aud_get_chnl_count( + hdmi->aud_param.aud_input_chan_type); + + err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(hdmi->dev, "Failed to pack audio infoframe: %zd\n", + err); + return err; + } + + mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer)); + return 0; +} + +static int mtk_hdmi_setup_vendor_specific_infoframe(struct mtk_hdmi *hdmi, + 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, + hdmi->curr_conn, mode); + if (err) { + dev_err(hdmi->dev, + "Failed to get vendor infoframe from mode: %zd\n", err); + return err; + } + + 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 err; + } + + mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer)); + return 0; +} + +static int mtk_hdmi_output_init(struct mtk_hdmi *hdmi) +{ + struct hdmi_audio_param *aud_param = &hdmi->aud_param; + + hdmi->csp = HDMI_COLORSPACE_RGB; + aud_param->aud_codec = HDMI_AUDIO_CODING_TYPE_PCM; + aud_param->aud_sampe_size = HDMI_AUDIO_SAMPLE_SIZE_16; + aud_param->aud_input_type = HDMI_AUD_INPUT_I2S; + aud_param->aud_i2s_fmt = HDMI_I2S_MODE_I2S_24BIT; + aud_param->aud_mclk = HDMI_AUD_MCLK_128FS; + aud_param->aud_input_chan_type = HDMI_AUD_CHAN_TYPE_2_0; + + return 0; +} + +static void mtk_hdmi_audio_enable(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_hw_send_aud_packet(hdmi, true); + hdmi->audio_enable = true; +} + +static void mtk_hdmi_audio_disable(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_hw_send_aud_packet(hdmi, false); + hdmi->audio_enable = false; +} + +static int mtk_hdmi_audio_set_param(struct mtk_hdmi *hdmi, + struct hdmi_audio_param *param) +{ + if (!hdmi->audio_enable) { + dev_err(hdmi->dev, "hdmi audio is in disable state!\n"); + return -EINVAL; + } + dev_dbg(hdmi->dev, "codec:%d, input:%d, channel:%d, fs:%d\n", + param->aud_codec, param->aud_input_type, + param->aud_input_chan_type, param->codec_params.sample_rate); + memcpy(&hdmi->aud_param, param, sizeof(*param)); + return mtk_hdmi_aud_output_config(hdmi, &hdmi->mode); +} + +static int mtk_hdmi_output_set_display_mode(struct mtk_hdmi *hdmi, + struct drm_display_mode *mode) +{ + int ret; + + mtk_hdmi_hw_vid_black(hdmi, true); + mtk_hdmi_hw_aud_mute(hdmi); + mtk_hdmi_hw_send_av_mute(hdmi); + phy_power_off(hdmi->phy); + + ret = mtk_hdmi_video_change_vpll(hdmi, + mode->clock * 1000); + if (ret) { + dev_err(hdmi->dev, "Failed to set vpll: %d\n", ret); + return ret; + } + mtk_hdmi_video_set_display_mode(hdmi, mode); + + phy_power_on(hdmi->phy); + mtk_hdmi_aud_output_config(hdmi, mode); + + mtk_hdmi_hw_vid_black(hdmi, false); + mtk_hdmi_hw_aud_unmute(hdmi); + mtk_hdmi_hw_send_av_unmute(hdmi); + + return 0; +} + +static const char * const mtk_hdmi_clk_names[MTK_HDMI_CLK_COUNT] = { + [MTK_HDMI_CLK_HDMI_PIXEL] = "pixel", + [MTK_HDMI_CLK_HDMI_PLL] = "pll", + [MTK_HDMI_CLK_AUD_BCLK] = "bclk", + [MTK_HDMI_CLK_AUD_SPDIF] = "spdif", +}; + +static int mtk_hdmi_get_all_clk(struct mtk_hdmi *hdmi, + struct device_node *np) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mtk_hdmi_clk_names); i++) { + hdmi->clk[i] = of_clk_get_by_name(np, + mtk_hdmi_clk_names[i]); + if (IS_ERR(hdmi->clk[i])) + return PTR_ERR(hdmi->clk[i]); + } + return 0; +} + +static int mtk_hdmi_clk_enable_audio(struct mtk_hdmi *hdmi) +{ + int ret; + + ret = clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]); + if (ret) + return ret; + + ret = clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_AUD_SPDIF]); + if (ret) + goto err; + + return 0; +err: + clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]); + return ret; +} + +static void mtk_hdmi_clk_disable_audio(struct mtk_hdmi *hdmi) +{ + clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]); + clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_SPDIF]); +} + +static enum drm_connector_status +mtk_hdmi_update_plugged_status(struct mtk_hdmi *hdmi) +{ + bool connected; + + mutex_lock(&hdmi->update_plugged_status_lock); + connected = mtk_cec_hpd_high(hdmi->cec_dev); + if (hdmi->plugged_cb && hdmi->codec_dev) + hdmi->plugged_cb(hdmi->codec_dev, connected); + mutex_unlock(&hdmi->update_plugged_status_lock); + + return connected ? + connector_status_connected : connector_status_disconnected; +} + +static enum drm_connector_status mtk_hdmi_detect(struct mtk_hdmi *hdmi) +{ + return mtk_hdmi_update_plugged_status(hdmi); +} + +static enum drm_mode_status +mtk_hdmi_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + struct drm_bridge *next_bridge; + + dev_dbg(hdmi->dev, "xres=%d, yres=%d, refresh=%d, intl=%d clock=%d\n", + mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode), + !!(mode->flags & DRM_MODE_FLAG_INTERLACE), mode->clock * 1000); + + next_bridge = drm_bridge_get_next_bridge(&hdmi->bridge); + if (next_bridge) { + struct drm_display_mode adjusted_mode; + + drm_mode_copy(&adjusted_mode, mode); + if (!drm_bridge_chain_mode_fixup(next_bridge, mode, + &adjusted_mode)) + return MODE_BAD; + } + + if (hdmi->conf) { + if (hdmi->conf->cea_modes_only && !drm_match_cea_mode(mode)) + return MODE_BAD; + + if (hdmi->conf->max_mode_clock && + mode->clock > hdmi->conf->max_mode_clock) + return MODE_CLOCK_HIGH; + } + + if (mode->clock < 27000) + return MODE_CLOCK_LOW; + if (mode->clock > 297000) + return MODE_CLOCK_HIGH; + + return drm_mode_validate_size(mode, 0x1fff, 0x1fff); +} + +static void mtk_hdmi_hpd_event(bool hpd, struct device *dev) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + + if (hdmi && hdmi->bridge.encoder && hdmi->bridge.encoder->dev) { + static enum drm_connector_status status; + + status = mtk_hdmi_detect(hdmi); + drm_helper_hpd_irq_event(hdmi->bridge.encoder->dev); + drm_bridge_hpd_notify(&hdmi->bridge, status); + } +} + +/* + * Bridge callbacks + */ + +static enum drm_connector_status mtk_hdmi_bridge_detect(struct drm_bridge *bridge) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + + return mtk_hdmi_detect(hdmi); +} + +static struct edid *mtk_hdmi_bridge_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + struct edid *edid; + + if (!hdmi->ddc_adpt) + return NULL; + edid = drm_get_edid(connector, hdmi->ddc_adpt); + if (!edid) + return NULL; + hdmi->dvi_mode = !drm_detect_monitor_audio(edid); + return edid; +} + +static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + int ret; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + DRM_ERROR("%s: The flag DRM_BRIDGE_ATTACH_NO_CONNECTOR must be supplied\n", + __func__); + return -EINVAL; + } + + if (hdmi->next_bridge) { + ret = drm_bridge_attach(bridge->encoder, hdmi->next_bridge, + bridge, flags); + if (ret) + return ret; + } + + mtk_cec_set_hpd_event(hdmi->cec_dev, mtk_hdmi_hpd_event, hdmi->dev); + + return 0; +} + +static bool mtk_hdmi_bridge_mode_fixup(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void mtk_hdmi_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + + if (!hdmi->enabled) + return; + + phy_power_off(hdmi->phy); + clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_HDMI_PIXEL]); + clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]); + + hdmi->curr_conn = NULL; + + hdmi->enabled = false; +} + +static void mtk_hdmi_bridge_atomic_post_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + + if (!hdmi->powered) + return; + + mtk_hdmi_hw_1p4_version_enable(hdmi, true); + mtk_hdmi_hw_make_reg_writable(hdmi, false); + + hdmi->powered = false; +} + +static void mtk_hdmi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + + dev_dbg(hdmi->dev, "cur info: name:%s, hdisplay:%d\n", + adjusted_mode->name, adjusted_mode->hdisplay); + dev_dbg(hdmi->dev, "hsync_start:%d,hsync_end:%d, htotal:%d", + adjusted_mode->hsync_start, adjusted_mode->hsync_end, + adjusted_mode->htotal); + dev_dbg(hdmi->dev, "hskew:%d, vdisplay:%d\n", + adjusted_mode->hskew, adjusted_mode->vdisplay); + dev_dbg(hdmi->dev, "vsync_start:%d, vsync_end:%d, vtotal:%d", + adjusted_mode->vsync_start, adjusted_mode->vsync_end, + adjusted_mode->vtotal); + dev_dbg(hdmi->dev, "vscan:%d, flag:%d\n", + adjusted_mode->vscan, adjusted_mode->flags); + + drm_mode_copy(&hdmi->mode, adjusted_mode); +} + +static void mtk_hdmi_bridge_atomic_pre_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + + mtk_hdmi_hw_make_reg_writable(hdmi, true); + mtk_hdmi_hw_1p4_version_enable(hdmi, true); + + hdmi->powered = true; +} + +static void mtk_hdmi_send_infoframe(struct mtk_hdmi *hdmi, + struct drm_display_mode *mode) +{ + mtk_hdmi_setup_audio_infoframe(hdmi); + mtk_hdmi_setup_avi_infoframe(hdmi, mode); + mtk_hdmi_setup_spd_infoframe(hdmi, "mediatek", "On-chip HDMI"); + if (mode->flags & DRM_MODE_FLAG_3D_MASK) + mtk_hdmi_setup_vendor_specific_infoframe(hdmi, mode); +} + +static void mtk_hdmi_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) +{ + struct drm_atomic_state *state = old_state->base.state; + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + + /* Retrieve the connector through the atomic state. */ + hdmi->curr_conn = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + + mtk_hdmi_output_set_display_mode(hdmi, &hdmi->mode); + clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]); + clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_HDMI_PIXEL]); + phy_power_on(hdmi->phy); + mtk_hdmi_send_infoframe(hdmi, &hdmi->mode); + + hdmi->enabled = true; +} + +static const struct drm_bridge_funcs mtk_hdmi_bridge_funcs = { + .mode_valid = mtk_hdmi_bridge_mode_valid, + .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 = mtk_hdmi_bridge_attach, + .mode_fixup = mtk_hdmi_bridge_mode_fixup, + .atomic_disable = mtk_hdmi_bridge_atomic_disable, + .atomic_post_disable = mtk_hdmi_bridge_atomic_post_disable, + .mode_set = mtk_hdmi_bridge_mode_set, + .atomic_pre_enable = mtk_hdmi_bridge_atomic_pre_enable, + .atomic_enable = mtk_hdmi_bridge_atomic_enable, + .detect = mtk_hdmi_bridge_detect, + .get_edid = mtk_hdmi_bridge_get_edid, +}; + +static int mtk_hdmi_dt_parse_pdata(struct mtk_hdmi *hdmi, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *cec_np, *remote, *i2c_np; + struct platform_device *cec_pdev; + struct regmap *regmap; + struct resource *mem; + int ret; + + ret = mtk_hdmi_get_all_clk(hdmi, np); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get clocks: %d\n", ret); + + return ret; + } + + /* The CEC module handles HDMI hotplug detection */ + cec_np = of_get_compatible_child(np->parent, "mediatek,mt8173-cec"); + if (!cec_np) { + dev_err(dev, "Failed to find CEC node\n"); + return -EINVAL; + } + + cec_pdev = of_find_device_by_node(cec_np); + if (!cec_pdev) { + dev_err(hdmi->dev, "Waiting for CEC device %pOF\n", + cec_np); + of_node_put(cec_np); + return -EPROBE_DEFER; + } + of_node_put(cec_np); + hdmi->cec_dev = &cec_pdev->dev; + + /* + * The mediatek,syscon-hdmi property contains a phandle link to the + * MMSYS_CONFIG device and the register offset of the HDMI_SYS_CFG + * registers it contains. + */ + regmap = syscon_regmap_lookup_by_phandle(np, "mediatek,syscon-hdmi"); + ret = of_property_read_u32_index(np, "mediatek,syscon-hdmi", 1, + &hdmi->sys_offset); + if (IS_ERR(regmap)) + ret = PTR_ERR(regmap); + if (ret) { + dev_err(dev, + "Failed to get system configuration registers: %d\n", + ret); + goto put_device; + } + hdmi->sys_regmap = regmap; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdmi->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(hdmi->regs)) { + ret = PTR_ERR(hdmi->regs); + goto put_device; + } + + remote = of_graph_get_remote_node(np, 1, 0); + if (!remote) { + ret = -EINVAL; + goto put_device; + } + + if (!of_device_is_compatible(remote, "hdmi-connector")) { + hdmi->next_bridge = of_drm_find_bridge(remote); + if (!hdmi->next_bridge) { + dev_err(dev, "Waiting for external bridge\n"); + of_node_put(remote); + ret = -EPROBE_DEFER; + goto put_device; + } + } + + i2c_np = of_parse_phandle(remote, "ddc-i2c-bus", 0); + if (!i2c_np) { + dev_err(dev, "Failed to find ddc-i2c-bus node in %pOF\n", + remote); + of_node_put(remote); + ret = -EINVAL; + goto put_device; + } + of_node_put(remote); + + hdmi->ddc_adpt = of_find_i2c_adapter_by_node(i2c_np); + of_node_put(i2c_np); + if (!hdmi->ddc_adpt) { + dev_err(dev, "Failed to get ddc i2c adapter by node\n"); + ret = -EINVAL; + goto put_device; + } + + return 0; +put_device: + put_device(hdmi->cec_dev); + return ret; +} + +/* + * HDMI audio codec callbacks + */ + +static int mtk_hdmi_audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + struct hdmi_audio_param hdmi_params; + unsigned int chan = params->cea.channels; + + dev_dbg(hdmi->dev, "%s: %u Hz, %d bit, %d channels\n", __func__, + params->sample_rate, params->sample_width, chan); + + if (!hdmi->bridge.encoder) + return -ENODEV; + + switch (chan) { + case 2: + hdmi_params.aud_input_chan_type = HDMI_AUD_CHAN_TYPE_2_0; + break; + case 4: + hdmi_params.aud_input_chan_type = HDMI_AUD_CHAN_TYPE_4_0; + break; + case 6: + hdmi_params.aud_input_chan_type = HDMI_AUD_CHAN_TYPE_5_1; + break; + case 8: + hdmi_params.aud_input_chan_type = HDMI_AUD_CHAN_TYPE_7_1; + break; + default: + dev_err(hdmi->dev, "channel[%d] not supported!\n", chan); + return -EINVAL; + } + + switch (params->sample_rate) { + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + case 176400: + case 192000: + break; + default: + dev_err(hdmi->dev, "rate[%d] not supported!\n", + params->sample_rate); + return -EINVAL; + } + + switch (daifmt->fmt) { + case HDMI_I2S: + hdmi_params.aud_codec = HDMI_AUDIO_CODING_TYPE_PCM; + hdmi_params.aud_sampe_size = HDMI_AUDIO_SAMPLE_SIZE_16; + hdmi_params.aud_input_type = HDMI_AUD_INPUT_I2S; + hdmi_params.aud_i2s_fmt = HDMI_I2S_MODE_I2S_24BIT; + hdmi_params.aud_mclk = HDMI_AUD_MCLK_128FS; + break; + case HDMI_SPDIF: + hdmi_params.aud_codec = HDMI_AUDIO_CODING_TYPE_PCM; + hdmi_params.aud_sampe_size = HDMI_AUDIO_SAMPLE_SIZE_16; + hdmi_params.aud_input_type = HDMI_AUD_INPUT_SPDIF; + break; + default: + dev_err(hdmi->dev, "%s: Invalid DAI format %d\n", __func__, + daifmt->fmt); + return -EINVAL; + } + + memcpy(&hdmi_params.codec_params, params, + sizeof(hdmi_params.codec_params)); + + mtk_hdmi_audio_set_param(hdmi, &hdmi_params); + + return 0; +} + +static int mtk_hdmi_audio_startup(struct device *dev, void *data) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + + mtk_hdmi_audio_enable(hdmi); + + return 0; +} + +static void mtk_hdmi_audio_shutdown(struct device *dev, void *data) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + + mtk_hdmi_audio_disable(hdmi); +} + +static int +mtk_hdmi_audio_mute(struct device *dev, void *data, + bool enable, int direction) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + + if (enable) + mtk_hdmi_hw_aud_mute(hdmi); + else + mtk_hdmi_hw_aud_unmute(hdmi); + + return 0; +} + +static int mtk_hdmi_audio_get_eld(struct device *dev, void *data, uint8_t *buf, size_t len) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + + if (hdmi->enabled) + memcpy(buf, hdmi->curr_conn->eld, min(sizeof(hdmi->curr_conn->eld), len)); + else + memset(buf, 0, len); + return 0; +} + +static int mtk_hdmi_audio_hook_plugged_cb(struct device *dev, void *data, + hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + struct mtk_hdmi *hdmi = data; + + mutex_lock(&hdmi->update_plugged_status_lock); + hdmi->plugged_cb = fn; + hdmi->codec_dev = codec_dev; + mutex_unlock(&hdmi->update_plugged_status_lock); + + mtk_hdmi_update_plugged_status(hdmi); + + return 0; +} + +static const struct hdmi_codec_ops mtk_hdmi_audio_codec_ops = { + .hw_params = mtk_hdmi_audio_hw_params, + .audio_startup = mtk_hdmi_audio_startup, + .audio_shutdown = mtk_hdmi_audio_shutdown, + .mute_stream = mtk_hdmi_audio_mute, + .get_eld = mtk_hdmi_audio_get_eld, + .hook_plugged_cb = mtk_hdmi_audio_hook_plugged_cb, + .no_capture_mute = 1, +}; + +static int mtk_hdmi_register_audio_driver(struct device *dev) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + struct hdmi_codec_pdata codec_data = { + .ops = &mtk_hdmi_audio_codec_ops, + .max_i2s_channels = 2, + .i2s = 1, + .data = hdmi, + }; + struct platform_device *pdev; + + pdev = platform_device_register_data(dev, HDMI_CODEC_DRV_NAME, + PLATFORM_DEVID_AUTO, &codec_data, + sizeof(codec_data)); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + DRM_INFO("%s driver bound to HDMI\n", HDMI_CODEC_DRV_NAME); + return 0; +} + +static int mtk_drm_hdmi_probe(struct platform_device *pdev) +{ + struct mtk_hdmi *hdmi; + struct device *dev = &pdev->dev; + int ret; + + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + + hdmi->dev = dev; + hdmi->conf = of_device_get_match_data(dev); + + ret = mtk_hdmi_dt_parse_pdata(hdmi, pdev); + if (ret) + return ret; + + hdmi->phy = devm_phy_get(dev, "hdmi"); + if (IS_ERR(hdmi->phy)) { + ret = PTR_ERR(hdmi->phy); + dev_err(dev, "Failed to get HDMI PHY: %d\n", ret); + return ret; + } + + mutex_init(&hdmi->update_plugged_status_lock); + platform_set_drvdata(pdev, hdmi); + + ret = mtk_hdmi_output_init(hdmi); + if (ret) { + dev_err(dev, "Failed to initialize hdmi output\n"); + return ret; + } + + ret = mtk_hdmi_register_audio_driver(dev); + if (ret) { + dev_err(dev, "Failed to register audio driver: %d\n", ret); + return ret; + } + + hdmi->bridge.funcs = &mtk_hdmi_bridge_funcs; + hdmi->bridge.of_node = pdev->dev.of_node; + hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID + | DRM_BRIDGE_OP_HPD; + hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + drm_bridge_add(&hdmi->bridge); + + ret = mtk_hdmi_clk_enable_audio(hdmi); + if (ret) { + dev_err(dev, "Failed to enable audio clocks: %d\n", ret); + goto err_bridge_remove; + } + + return 0; + +err_bridge_remove: + drm_bridge_remove(&hdmi->bridge); + return ret; +} + +static int mtk_drm_hdmi_remove(struct platform_device *pdev) +{ + struct mtk_hdmi *hdmi = platform_get_drvdata(pdev); + + drm_bridge_remove(&hdmi->bridge); + mtk_hdmi_clk_disable_audio(hdmi); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mtk_hdmi_suspend(struct device *dev) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + + mtk_hdmi_clk_disable_audio(hdmi); + + return 0; +} + +static int mtk_hdmi_resume(struct device *dev) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + int ret = 0; + + ret = mtk_hdmi_clk_enable_audio(hdmi); + if (ret) { + dev_err(dev, "hdmi resume failed!\n"); + return ret; + } + + return 0; +} +#endif +static SIMPLE_DEV_PM_OPS(mtk_hdmi_pm_ops, + mtk_hdmi_suspend, mtk_hdmi_resume); + +static const struct mtk_hdmi_conf mtk_hdmi_conf_mt2701 = { + .tz_disabled = true, +}; + +static const struct mtk_hdmi_conf mtk_hdmi_conf_mt8167 = { + .max_mode_clock = 148500, + .cea_modes_only = true, +}; + +static const struct of_device_id mtk_drm_hdmi_of_ids[] = { + { .compatible = "mediatek,mt2701-hdmi", + .data = &mtk_hdmi_conf_mt2701, + }, + { .compatible = "mediatek,mt8167-hdmi", + .data = &mtk_hdmi_conf_mt8167, + }, + { .compatible = "mediatek,mt8173-hdmi", + }, + {} +}; +MODULE_DEVICE_TABLE(of, mtk_drm_hdmi_of_ids); + +static struct platform_driver mtk_hdmi_driver = { + .probe = mtk_drm_hdmi_probe, + .remove = mtk_drm_hdmi_remove, + .driver = { + .name = "mediatek-drm-hdmi", + .of_match_table = mtk_drm_hdmi_of_ids, + .pm = &mtk_hdmi_pm_ops, + }, +}; + +static struct platform_driver * const mtk_hdmi_drivers[] = { + &mtk_hdmi_ddc_driver, + &mtk_cec_driver, + &mtk_hdmi_driver, +}; + +static int __init mtk_hdmitx_init(void) +{ + return platform_register_drivers(mtk_hdmi_drivers, + ARRAY_SIZE(mtk_hdmi_drivers)); +} + +static void __exit mtk_hdmitx_exit(void) +{ + platform_unregister_drivers(mtk_hdmi_drivers, + ARRAY_SIZE(mtk_hdmi_drivers)); +} + +module_init(mtk_hdmitx_init); +module_exit(mtk_hdmitx_exit); + +MODULE_AUTHOR("Jie Qiu <jie.qiu@mediatek.com>"); +MODULE_DESCRIPTION("MediaTek HDMI Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.h b/drivers/gpu/drm/mediatek/mtk_hdmi.h new file mode 100644 index 000000000..472bf141c --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu <jie.qiu@mediatek.com> + */ +#ifndef _MTK_HDMI_CTRL_H +#define _MTK_HDMI_CTRL_H + +struct platform_driver; + +extern struct platform_driver mtk_cec_driver; +extern struct platform_driver mtk_hdmi_ddc_driver; + +#endif /* _MTK_HDMI_CTRL_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_ddc.c b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc.c new file mode 100644 index 000000000..6207eac88 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc.c @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu <jie.qiu@mediatek.com> + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> + +#define SIF1_CLOK (288) +#define DDC_DDCMCTL0 (0x0) +#define DDCM_ODRAIN BIT(31) +#define DDCM_CLK_DIV_OFFSET (16) +#define DDCM_CLK_DIV_MASK (0xfff << 16) +#define DDCM_CS_STATUS BIT(4) +#define DDCM_SCL_STATE BIT(3) +#define DDCM_SDA_STATE BIT(2) +#define DDCM_SM0EN BIT(1) +#define DDCM_SCL_STRECH BIT(0) +#define DDC_DDCMCTL1 (0x4) +#define DDCM_ACK_OFFSET (16) +#define DDCM_ACK_MASK (0xff << 16) +#define DDCM_PGLEN_OFFSET (8) +#define DDCM_PGLEN_MASK (0x7 << 8) +#define DDCM_SIF_MODE_OFFSET (4) +#define DDCM_SIF_MODE_MASK (0x7 << 4) +#define DDCM_START (0x1) +#define DDCM_WRITE_DATA (0x2) +#define DDCM_STOP (0x3) +#define DDCM_READ_DATA_NO_ACK (0x4) +#define DDCM_READ_DATA_ACK (0x5) +#define DDCM_TRI BIT(0) +#define DDC_DDCMD0 (0x8) +#define DDCM_DATA3 (0xff << 24) +#define DDCM_DATA2 (0xff << 16) +#define DDCM_DATA1 (0xff << 8) +#define DDCM_DATA0 (0xff << 0) +#define DDC_DDCMD1 (0xc) +#define DDCM_DATA7 (0xff << 24) +#define DDCM_DATA6 (0xff << 16) +#define DDCM_DATA5 (0xff << 8) +#define DDCM_DATA4 (0xff << 0) + +struct mtk_hdmi_ddc { + struct i2c_adapter adap; + struct clk *clk; + void __iomem *regs; +}; + +static inline void sif_set_bit(struct mtk_hdmi_ddc *ddc, unsigned int offset, + unsigned int val) +{ + writel(readl(ddc->regs + offset) | val, ddc->regs + offset); +} + +static inline void sif_clr_bit(struct mtk_hdmi_ddc *ddc, unsigned int offset, + unsigned int val) +{ + writel(readl(ddc->regs + offset) & ~val, ddc->regs + offset); +} + +static inline bool sif_bit_is_set(struct mtk_hdmi_ddc *ddc, unsigned int offset, + unsigned int val) +{ + return (readl(ddc->regs + offset) & val) == val; +} + +static inline void sif_write_mask(struct mtk_hdmi_ddc *ddc, unsigned int offset, + unsigned int mask, unsigned int shift, + unsigned int val) +{ + unsigned int tmp; + + tmp = readl(ddc->regs + offset); + tmp &= ~mask; + tmp |= (val << shift) & mask; + writel(tmp, ddc->regs + offset); +} + +static inline unsigned int sif_read_mask(struct mtk_hdmi_ddc *ddc, + unsigned int offset, unsigned int mask, + unsigned int shift) +{ + return (readl(ddc->regs + offset) & mask) >> shift; +} + +static void ddcm_trigger_mode(struct mtk_hdmi_ddc *ddc, int mode) +{ + u32 val; + + sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_SIF_MODE_MASK, + DDCM_SIF_MODE_OFFSET, mode); + sif_set_bit(ddc, DDC_DDCMCTL1, DDCM_TRI); + readl_poll_timeout(ddc->regs + DDC_DDCMCTL1, val, + (val & DDCM_TRI) != DDCM_TRI, 4, 20000); +} + +static int mtk_hdmi_ddc_read_msg(struct mtk_hdmi_ddc *ddc, struct i2c_msg *msg) +{ + struct device *dev = ddc->adap.dev.parent; + u32 remain_count, ack_count, ack_final, read_count, temp_count; + u32 index = 0; + u32 ack; + int i; + + ddcm_trigger_mode(ddc, DDCM_START); + sif_write_mask(ddc, DDC_DDCMD0, 0xff, 0, (msg->addr << 1) | 0x01); + sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_PGLEN_MASK, DDCM_PGLEN_OFFSET, + 0x00); + ddcm_trigger_mode(ddc, DDCM_WRITE_DATA); + ack = sif_read_mask(ddc, DDC_DDCMCTL1, DDCM_ACK_MASK, DDCM_ACK_OFFSET); + dev_dbg(dev, "ack = 0x%x\n", ack); + if (ack != 0x01) { + dev_err(dev, "i2c ack err!\n"); + return -ENXIO; + } + + remain_count = msg->len; + ack_count = (msg->len - 1) / 8; + ack_final = 0; + + while (remain_count > 0) { + if (ack_count > 0) { + read_count = 8; + ack_final = 0; + ack_count--; + } else { + read_count = remain_count; + ack_final = 1; + } + + sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_PGLEN_MASK, + DDCM_PGLEN_OFFSET, read_count - 1); + ddcm_trigger_mode(ddc, (ack_final == 1) ? + DDCM_READ_DATA_NO_ACK : + DDCM_READ_DATA_ACK); + + ack = sif_read_mask(ddc, DDC_DDCMCTL1, DDCM_ACK_MASK, + DDCM_ACK_OFFSET); + temp_count = 0; + while (((ack & (1 << temp_count)) != 0) && (temp_count < 8)) + temp_count++; + if (((ack_final == 1) && (temp_count != (read_count - 1))) || + ((ack_final == 0) && (temp_count != read_count))) { + dev_err(dev, "Address NACK! ACK(0x%x)\n", ack); + break; + } + + for (i = read_count; i >= 1; i--) { + int shift; + int offset; + + if (i > 4) { + offset = DDC_DDCMD1; + shift = (i - 5) * 8; + } else { + offset = DDC_DDCMD0; + shift = (i - 1) * 8; + } + + msg->buf[index + i - 1] = sif_read_mask(ddc, offset, + 0xff << shift, + shift); + } + + remain_count -= read_count; + index += read_count; + } + + return 0; +} + +static int mtk_hdmi_ddc_write_msg(struct mtk_hdmi_ddc *ddc, struct i2c_msg *msg) +{ + struct device *dev = ddc->adap.dev.parent; + u32 ack; + + ddcm_trigger_mode(ddc, DDCM_START); + sif_write_mask(ddc, DDC_DDCMD0, DDCM_DATA0, 0, msg->addr << 1); + sif_write_mask(ddc, DDC_DDCMD0, DDCM_DATA1, 8, msg->buf[0]); + sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_PGLEN_MASK, DDCM_PGLEN_OFFSET, + 0x1); + ddcm_trigger_mode(ddc, DDCM_WRITE_DATA); + + ack = sif_read_mask(ddc, DDC_DDCMCTL1, DDCM_ACK_MASK, DDCM_ACK_OFFSET); + dev_dbg(dev, "ack = %d\n", ack); + + if (ack != 0x03) { + dev_err(dev, "i2c ack err!\n"); + return -EIO; + } + + return 0; +} + +static int mtk_hdmi_ddc_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) +{ + struct mtk_hdmi_ddc *ddc = adapter->algo_data; + struct device *dev = adapter->dev.parent; + int ret; + int i; + + if (!ddc) { + dev_err(dev, "invalid arguments\n"); + return -EINVAL; + } + + sif_set_bit(ddc, DDC_DDCMCTL0, DDCM_SCL_STRECH); + sif_set_bit(ddc, DDC_DDCMCTL0, DDCM_SM0EN); + sif_clr_bit(ddc, DDC_DDCMCTL0, DDCM_ODRAIN); + + if (sif_bit_is_set(ddc, DDC_DDCMCTL1, DDCM_TRI)) { + dev_err(dev, "ddc line is busy!\n"); + return -EBUSY; + } + + sif_write_mask(ddc, DDC_DDCMCTL0, DDCM_CLK_DIV_MASK, + DDCM_CLK_DIV_OFFSET, SIF1_CLOK); + + for (i = 0; i < num; i++) { + struct i2c_msg *msg = &msgs[i]; + + dev_dbg(dev, "i2c msg, adr:0x%x, flags:%d, len :0x%x\n", + msg->addr, msg->flags, msg->len); + + if (msg->flags & I2C_M_RD) + ret = mtk_hdmi_ddc_read_msg(ddc, msg); + else + ret = mtk_hdmi_ddc_write_msg(ddc, msg); + if (ret < 0) + goto xfer_end; + } + + ddcm_trigger_mode(ddc, DDCM_STOP); + + return i; + +xfer_end: + ddcm_trigger_mode(ddc, DDCM_STOP); + dev_err(dev, "ddc failed!\n"); + return ret; +} + +static u32 mtk_hdmi_ddc_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm mtk_hdmi_ddc_algorithm = { + .master_xfer = mtk_hdmi_ddc_xfer, + .functionality = mtk_hdmi_ddc_func, +}; + +static int mtk_hdmi_ddc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_hdmi_ddc *ddc; + struct resource *mem; + int ret; + + ddc = devm_kzalloc(dev, sizeof(struct mtk_hdmi_ddc), GFP_KERNEL); + if (!ddc) + return -ENOMEM; + + ddc->clk = devm_clk_get(dev, "ddc-i2c"); + if (IS_ERR(ddc->clk)) { + dev_err(dev, "get ddc_clk failed: %p ,\n", ddc->clk); + return PTR_ERR(ddc->clk); + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ddc->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(ddc->regs)) + return PTR_ERR(ddc->regs); + + ret = clk_prepare_enable(ddc->clk); + if (ret) { + dev_err(dev, "enable ddc clk failed!\n"); + return ret; + } + + strlcpy(ddc->adap.name, "mediatek-hdmi-ddc", sizeof(ddc->adap.name)); + ddc->adap.owner = THIS_MODULE; + ddc->adap.class = I2C_CLASS_DDC; + ddc->adap.algo = &mtk_hdmi_ddc_algorithm; + ddc->adap.retries = 3; + ddc->adap.dev.of_node = dev->of_node; + ddc->adap.algo_data = ddc; + ddc->adap.dev.parent = &pdev->dev; + + ret = i2c_add_adapter(&ddc->adap); + if (ret < 0) { + dev_err(dev, "failed to add bus to i2c core\n"); + goto err_clk_disable; + } + + platform_set_drvdata(pdev, ddc); + + dev_dbg(dev, "ddc->adap: %p\n", &ddc->adap); + dev_dbg(dev, "ddc->clk: %p\n", ddc->clk); + dev_dbg(dev, "physical adr: %pa, end: %pa\n", &mem->start, + &mem->end); + + return 0; + +err_clk_disable: + clk_disable_unprepare(ddc->clk); + return ret; +} + +static int mtk_hdmi_ddc_remove(struct platform_device *pdev) +{ + struct mtk_hdmi_ddc *ddc = platform_get_drvdata(pdev); + + i2c_del_adapter(&ddc->adap); + clk_disable_unprepare(ddc->clk); + + return 0; +} + +static const struct of_device_id mtk_hdmi_ddc_match[] = { + { .compatible = "mediatek,mt8173-hdmi-ddc", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_hdmi_ddc_match); + +struct platform_driver mtk_hdmi_ddc_driver = { + .probe = mtk_hdmi_ddc_probe, + .remove = mtk_hdmi_ddc_remove, + .driver = { + .name = "mediatek-hdmi-ddc", + .of_match_table = mtk_hdmi_ddc_match, + }, +}; + +MODULE_AUTHOR("Jie Qiu <jie.qiu@mediatek.com>"); +MODULE_DESCRIPTION("MediaTek HDMI DDC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h new file mode 100644 index 000000000..2050ba45b --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h @@ -0,0 +1,230 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu <jie.qiu@mediatek.com> + */ +#ifndef _MTK_HDMI_REGS_H +#define _MTK_HDMI_REGS_H + +#define GRL_INT_MASK 0x18 +#define GRL_IFM_PORT 0x188 +#define GRL_CH_SWAP 0x198 +#define LR_SWAP BIT(0) +#define LFE_CC_SWAP BIT(1) +#define LSRS_SWAP BIT(2) +#define RLS_RRS_SWAP BIT(3) +#define LR_STATUS_SWAP BIT(4) +#define GRL_I2S_C_STA0 0x140 +#define GRL_I2S_C_STA1 0x144 +#define GRL_I2S_C_STA2 0x148 +#define GRL_I2S_C_STA3 0x14C +#define GRL_I2S_C_STA4 0x150 +#define GRL_I2S_UV 0x154 +#define I2S_UV_V BIT(0) +#define I2S_UV_U BIT(1) +#define I2S_UV_CH_EN_MASK 0x3c +#define I2S_UV_CH_EN(x) BIT((x) + 2) +#define I2S_UV_TMDS_DEBUG BIT(6) +#define I2S_UV_NORMAL_INFO_INV BIT(7) +#define GRL_ACP_ISRC_CTRL 0x158 +#define VS_EN BIT(0) +#define ACP_EN BIT(1) +#define ISRC1_EN BIT(2) +#define ISRC2_EN BIT(3) +#define GAMUT_EN BIT(4) +#define GRL_CTS_CTRL 0x160 +#define CTS_CTRL_SOFT BIT(0) +#define GRL_INT 0x14 +#define INT_MDI BIT(0) +#define INT_HDCP BIT(1) +#define INT_FIFO_O BIT(2) +#define INT_FIFO_U BIT(3) +#define INT_IFM_ERR BIT(4) +#define INT_INF_DONE BIT(5) +#define INT_NCTS_DONE BIT(6) +#define INT_CTRL_PKT_DONE BIT(7) +#define GRL_INT_MASK 0x18 +#define GRL_CTRL 0x1C +#define CTRL_GEN_EN BIT(2) +#define CTRL_SPD_EN BIT(3) +#define CTRL_MPEG_EN BIT(4) +#define CTRL_AUDIO_EN BIT(5) +#define CTRL_AVI_EN BIT(6) +#define CTRL_AVMUTE BIT(7) +#define GRL_STATUS 0x20 +#define STATUS_HTPLG BIT(0) +#define STATUS_PORD BIT(1) +#define GRL_DIVN 0x170 +#define NCTS_WRI_ANYTIME BIT(6) +#define GRL_AUDIO_CFG 0x17C +#define AUDIO_ZERO BIT(0) +#define HIGH_BIT_RATE BIT(1) +#define SACD_DST BIT(2) +#define DST_NORMAL_DOUBLE BIT(3) +#define DSD_INV BIT(4) +#define LR_INV BIT(5) +#define LR_MIX BIT(6) +#define DSD_SEL BIT(7) +#define GRL_NCTS 0x184 +#define GRL_CH_SW0 0x18C +#define GRL_CH_SW1 0x190 +#define GRL_CH_SW2 0x194 +#define CH_SWITCH(from, to) ((from) << ((to) * 3)) +#define GRL_INFOFRM_VER 0x19C +#define GRL_INFOFRM_TYPE 0x1A0 +#define GRL_INFOFRM_LNG 0x1A4 +#define GRL_MIX_CTRL 0x1B4 +#define MIX_CTRL_SRC_EN BIT(0) +#define BYPASS_VOLUME BIT(1) +#define MIX_CTRL_FLAT BIT(7) +#define GRL_AOUT_CFG 0x1C4 +#define AOUT_BNUM_SEL_MASK 0x03 +#define AOUT_24BIT 0x00 +#define AOUT_20BIT 0x02 +#define AOUT_16BIT 0x03 +#define AOUT_FIFO_ADAP_CTRL BIT(6) +#define AOUT_BURST_PREAMBLE_EN BIT(7) +#define HIGH_BIT_RATE_PACKET_ALIGN (AOUT_BURST_PREAMBLE_EN | \ + AOUT_FIFO_ADAP_CTRL) +#define GRL_SHIFT_L1 0x1C0 +#define GRL_SHIFT_R2 0x1B0 +#define AUDIO_PACKET_OFF BIT(6) +#define GRL_CFG0 0x24 +#define CFG0_I2S_MODE_MASK 0x3 +#define CFG0_I2S_MODE_RTJ 0x1 +#define CFG0_I2S_MODE_LTJ 0x0 +#define CFG0_I2S_MODE_I2S 0x2 +#define CFG0_W_LENGTH_MASK 0x30 +#define CFG0_W_LENGTH_24BIT 0x00 +#define CFG0_W_LENGTH_16BIT 0x10 +#define GRL_CFG1 0x28 +#define CFG1_EDG_SEL BIT(0) +#define CFG1_SPDIF BIT(1) +#define CFG1_DVI BIT(2) +#define CFG1_HDCP_DEBUG BIT(3) +#define GRL_CFG2 0x2c +#define CFG2_MHL_DE_SEL BIT(3) +#define CFG2_MHL_FAKE_DE_SEL BIT(4) +#define CFG2_MHL_DATA_REMAP BIT(5) +#define CFG2_NOTICE_EN BIT(6) +#define CFG2_ACLK_INV BIT(7) +#define GRL_CFG3 0x30 +#define CFG3_AES_KEY_INDEX_MASK 0x3f +#define CFG3_CONTROL_PACKET_DELAY BIT(6) +#define CFG3_KSV_LOAD_START BIT(7) +#define GRL_CFG4 0x34 +#define CFG4_AES_KEY_LOAD BIT(4) +#define CFG4_AV_UNMUTE_EN BIT(5) +#define CFG4_AV_UNMUTE_SET BIT(6) +#define CFG4_MHL_MODE BIT(7) +#define GRL_CFG5 0x38 +#define CFG5_CD_RATIO_MASK 0x8F +#define CFG5_FS128 (0x1 << 4) +#define CFG5_FS256 (0x2 << 4) +#define CFG5_FS384 (0x3 << 4) +#define CFG5_FS512 (0x4 << 4) +#define CFG5_FS768 (0x6 << 4) +#define DUMMY_304 0x304 +#define CHMO_SEL (0x3 << 2) +#define CHM1_SEL (0x3 << 4) +#define CHM2_SEL (0x3 << 6) +#define AUDIO_I2S_NCTS_SEL BIT(1) +#define AUDIO_I2S_NCTS_SEL_64 (1 << 1) +#define AUDIO_I2S_NCTS_SEL_128 (0 << 1) +#define NEW_GCP_CTRL BIT(0) +#define NEW_GCP_CTRL_MERGE BIT(0) +#define GRL_L_STATUS_0 0x200 +#define GRL_L_STATUS_1 0x204 +#define GRL_L_STATUS_2 0x208 +#define GRL_L_STATUS_3 0x20c +#define GRL_L_STATUS_4 0x210 +#define GRL_L_STATUS_5 0x214 +#define GRL_L_STATUS_6 0x218 +#define GRL_L_STATUS_7 0x21c +#define GRL_L_STATUS_8 0x220 +#define GRL_L_STATUS_9 0x224 +#define GRL_L_STATUS_10 0x228 +#define GRL_L_STATUS_11 0x22c +#define GRL_L_STATUS_12 0x230 +#define GRL_L_STATUS_13 0x234 +#define GRL_L_STATUS_14 0x238 +#define GRL_L_STATUS_15 0x23c +#define GRL_L_STATUS_16 0x240 +#define GRL_L_STATUS_17 0x244 +#define GRL_L_STATUS_18 0x248 +#define GRL_L_STATUS_19 0x24c +#define GRL_L_STATUS_20 0x250 +#define GRL_L_STATUS_21 0x254 +#define GRL_L_STATUS_22 0x258 +#define GRL_L_STATUS_23 0x25c +#define GRL_R_STATUS_0 0x260 +#define GRL_R_STATUS_1 0x264 +#define GRL_R_STATUS_2 0x268 +#define GRL_R_STATUS_3 0x26c +#define GRL_R_STATUS_4 0x270 +#define GRL_R_STATUS_5 0x274 +#define GRL_R_STATUS_6 0x278 +#define GRL_R_STATUS_7 0x27c +#define GRL_R_STATUS_8 0x280 +#define GRL_R_STATUS_9 0x284 +#define GRL_R_STATUS_10 0x288 +#define GRL_R_STATUS_11 0x28c +#define GRL_R_STATUS_12 0x290 +#define GRL_R_STATUS_13 0x294 +#define GRL_R_STATUS_14 0x298 +#define GRL_R_STATUS_15 0x29c +#define GRL_R_STATUS_16 0x2a0 +#define GRL_R_STATUS_17 0x2a4 +#define GRL_R_STATUS_18 0x2a8 +#define GRL_R_STATUS_19 0x2ac +#define GRL_R_STATUS_20 0x2b0 +#define GRL_R_STATUS_21 0x2b4 +#define GRL_R_STATUS_22 0x2b8 +#define GRL_R_STATUS_23 0x2bc +#define GRL_ABIST_CTRL0 0x2D4 +#define GRL_ABIST_CTRL1 0x2D8 +#define ABIST_EN BIT(7) +#define ABIST_DATA_FMT (0x7 << 0) +#define VIDEO_CFG_0 0x380 +#define VIDEO_CFG_1 0x384 +#define VIDEO_CFG_2 0x388 +#define VIDEO_CFG_3 0x38c +#define VIDEO_CFG_4 0x390 +#define VIDEO_SOURCE_SEL BIT(7) +#define NORMAL_PATH (1 << 7) +#define GEN_RGB (0 << 7) + +#define HDMI_SYS_CFG1C 0x000 +#define HDMI_ON BIT(0) +#define HDMI_RST BIT(1) +#define ANLG_ON BIT(2) +#define CFG10_DVI BIT(3) +#define HDMI_TST BIT(3) +#define SYS_KEYMASK1 (0xff << 8) +#define SYS_KEYMASK2 (0xff << 16) +#define AUD_OUTSYNC_EN BIT(24) +#define AUD_OUTSYNC_PRE_EN BIT(25) +#define I2CM_ON BIT(26) +#define E2PROM_TYPE_8BIT BIT(27) +#define MCM_E2PROM_ON BIT(28) +#define EXT_E2PROM_ON BIT(29) +#define HTPLG_PIN_SEL_OFF BIT(30) +#define AES_EFUSE_ENABLE BIT(31) +#define HDMI_SYS_CFG20 0x004 +#define DEEP_COLOR_MODE_MASK (3 << 1) +#define COLOR_8BIT_MODE (0 << 1) +#define COLOR_10BIT_MODE (1 << 1) +#define COLOR_12BIT_MODE (2 << 1) +#define COLOR_16BIT_MODE (3 << 1) +#define DEEP_COLOR_EN BIT(0) +#define HDMI_AUDIO_TEST_SEL BIT(8) +#define HDMI2P0_EN BIT(11) +#define HDMI_OUT_FIFO_EN BIT(16) +#define HDMI_OUT_FIFO_CLK_INV BIT(17) +#define MHL_MODE_ON BIT(28) +#define MHL_PP_MODE BIT(29) +#define MHL_SYNC_AUTO_EN BIT(30) +#define HDMI_PCLK_FREE_RUN BIT(31) + +#define MTK_SIP_SET_AUTHORIZED_SECURE_REG 0x82000001 +#endif diff --git a/drivers/gpu/drm/mediatek/mtk_mdp_rdma.c b/drivers/gpu/drm/mediatek/mtk_mdp_rdma.c new file mode 100644 index 000000000..b288bb6ee --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_mdp_rdma.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#include <drm/drm_fourcc.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/soc/mediatek/mtk-cmdq.h> + +#include "mtk_disp_drv.h" +#include "mtk_drm_drv.h" +#include "mtk_mdp_rdma.h" + +#define MDP_RDMA_EN 0x000 +#define FLD_ROT_ENABLE BIT(0) +#define MDP_RDMA_RESET 0x008 +#define MDP_RDMA_CON 0x020 +#define FLD_OUTPUT_10B BIT(5) +#define FLD_SIMPLE_MODE BIT(4) +#define MDP_RDMA_GMCIF_CON 0x028 +#define FLD_COMMAND_DIV BIT(0) +#define FLD_EXT_PREULTRA_EN BIT(3) +#define FLD_RD_REQ_TYPE GENMASK(7, 4) +#define VAL_RD_REQ_TYPE_BURST_8_ACCESS 7 +#define FLD_ULTRA_EN GENMASK(13, 12) +#define VAL_ULTRA_EN_ENABLE 1 +#define FLD_PRE_ULTRA_EN GENMASK(17, 16) +#define VAL_PRE_ULTRA_EN_ENABLE 1 +#define FLD_EXT_ULTRA_EN BIT(18) +#define MDP_RDMA_SRC_CON 0x030 +#define FLD_OUTPUT_ARGB BIT(25) +#define FLD_BIT_NUMBER GENMASK(19, 18) +#define FLD_SWAP BIT(14) +#define FLD_UNIFORM_CONFIG BIT(17) +#define RDMA_INPUT_10BIT BIT(18) +#define FLD_SRC_FORMAT GENMASK(3, 0) +#define MDP_RDMA_COMP_CON 0x038 +#define FLD_AFBC_EN BIT(22) +#define FLD_AFBC_YUV_TRANSFORM BIT(21) +#define FLD_UFBDC_EN BIT(12) +#define MDP_RDMA_MF_BKGD_SIZE_IN_BYTE 0x060 +#define FLD_MF_BKGD_WB GENMASK(22, 0) +#define MDP_RDMA_MF_SRC_SIZE 0x070 +#define FLD_MF_SRC_H GENMASK(30, 16) +#define FLD_MF_SRC_W GENMASK(14, 0) +#define MDP_RDMA_MF_CLIP_SIZE 0x078 +#define FLD_MF_CLIP_H GENMASK(30, 16) +#define FLD_MF_CLIP_W GENMASK(14, 0) +#define MDP_RDMA_SRC_OFFSET_0 0x118 +#define FLD_SRC_OFFSET_0 GENMASK(31, 0) +#define MDP_RDMA_TRANSFORM_0 0x200 +#define FLD_INT_MATRIX_SEL GENMASK(27, 23) +#define FLD_TRANS_EN BIT(16) +#define MDP_RDMA_SRC_BASE_0 0xf00 +#define FLD_SRC_BASE_0 GENMASK(31, 0) + +#define RDMA_CSC_FULL709_TO_RGB 5 +#define RDMA_CSC_BT601_TO_RGB 6 + +enum rdma_format { + RDMA_INPUT_FORMAT_RGB565 = 0, + RDMA_INPUT_FORMAT_RGB888 = 1, + RDMA_INPUT_FORMAT_RGBA8888 = 2, + RDMA_INPUT_FORMAT_ARGB8888 = 3, + RDMA_INPUT_FORMAT_UYVY = 4, + RDMA_INPUT_FORMAT_YUY2 = 5, + RDMA_INPUT_FORMAT_Y8 = 7, + RDMA_INPUT_FORMAT_YV12 = 8, + RDMA_INPUT_FORMAT_UYVY_3PL = 9, + RDMA_INPUT_FORMAT_NV12 = 12, + RDMA_INPUT_FORMAT_UYVY_2PL = 13, + RDMA_INPUT_FORMAT_Y410 = 14 +}; + +struct mtk_mdp_rdma { + void __iomem *regs; + struct clk *clk; + struct cmdq_client_reg cmdq_reg; +}; + +static unsigned int rdma_fmt_convert(unsigned int fmt) +{ + switch (fmt) { + default: + case DRM_FORMAT_RGB565: + return RDMA_INPUT_FORMAT_RGB565; + case DRM_FORMAT_BGR565: + return RDMA_INPUT_FORMAT_RGB565 | FLD_SWAP; + case DRM_FORMAT_RGB888: + return RDMA_INPUT_FORMAT_RGB888; + case DRM_FORMAT_BGR888: + return RDMA_INPUT_FORMAT_RGB888 | FLD_SWAP; + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_RGBA8888: + return RDMA_INPUT_FORMAT_ARGB8888; + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_BGRA8888: + return RDMA_INPUT_FORMAT_ARGB8888 | FLD_SWAP; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + return RDMA_INPUT_FORMAT_RGBA8888; + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return RDMA_INPUT_FORMAT_RGBA8888 | FLD_SWAP; + case DRM_FORMAT_ABGR2101010: + return RDMA_INPUT_FORMAT_RGBA8888 | FLD_SWAP | RDMA_INPUT_10BIT; + case DRM_FORMAT_ARGB2101010: + return RDMA_INPUT_FORMAT_RGBA8888 | RDMA_INPUT_10BIT; + case DRM_FORMAT_RGBA1010102: + return RDMA_INPUT_FORMAT_ARGB8888 | FLD_SWAP | RDMA_INPUT_10BIT; + case DRM_FORMAT_BGRA1010102: + return RDMA_INPUT_FORMAT_ARGB8888 | RDMA_INPUT_10BIT; + case DRM_FORMAT_UYVY: + return RDMA_INPUT_FORMAT_UYVY; + case DRM_FORMAT_YUYV: + return RDMA_INPUT_FORMAT_YUY2; + } +} + +static unsigned int rdma_color_convert(unsigned int color_encoding) +{ + switch (color_encoding) { + default: + case DRM_COLOR_YCBCR_BT709: + return RDMA_CSC_FULL709_TO_RGB; + case DRM_COLOR_YCBCR_BT601: + return RDMA_CSC_BT601_TO_RGB; + } +} + +static void mtk_mdp_rdma_fifo_config(struct device *dev, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_mdp_rdma *priv = dev_get_drvdata(dev); + + mtk_ddp_write_mask(cmdq_pkt, FLD_EXT_ULTRA_EN | VAL_PRE_ULTRA_EN_ENABLE << 16 | + VAL_ULTRA_EN_ENABLE << 12 | VAL_RD_REQ_TYPE_BURST_8_ACCESS << 4 | + FLD_EXT_PREULTRA_EN | FLD_COMMAND_DIV, &priv->cmdq_reg, + priv->regs, MDP_RDMA_GMCIF_CON, FLD_EXT_ULTRA_EN | + FLD_PRE_ULTRA_EN | FLD_ULTRA_EN | FLD_RD_REQ_TYPE | + FLD_EXT_PREULTRA_EN | FLD_COMMAND_DIV); +} + +void mtk_mdp_rdma_start(struct device *dev, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_mdp_rdma *priv = dev_get_drvdata(dev); + + mtk_ddp_write_mask(cmdq_pkt, FLD_ROT_ENABLE, &priv->cmdq_reg, + priv->regs, MDP_RDMA_EN, FLD_ROT_ENABLE); +} + +void mtk_mdp_rdma_stop(struct device *dev, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_mdp_rdma *priv = dev_get_drvdata(dev); + + mtk_ddp_write_mask(cmdq_pkt, 0, &priv->cmdq_reg, + priv->regs, MDP_RDMA_EN, FLD_ROT_ENABLE); + mtk_ddp_write(cmdq_pkt, 1, &priv->cmdq_reg, priv->regs, MDP_RDMA_RESET); + mtk_ddp_write(cmdq_pkt, 0, &priv->cmdq_reg, priv->regs, MDP_RDMA_RESET); +} + +void mtk_mdp_rdma_config(struct device *dev, struct mtk_mdp_rdma_cfg *cfg, + struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_mdp_rdma *priv = dev_get_drvdata(dev); + const struct drm_format_info *fmt_info = drm_format_info(cfg->fmt); + bool csc_enable = fmt_info->is_yuv ? true : false; + unsigned int src_pitch_y = cfg->pitch; + unsigned int offset_y = 0; + + mtk_mdp_rdma_fifo_config(dev, cmdq_pkt); + + mtk_ddp_write_mask(cmdq_pkt, FLD_UNIFORM_CONFIG, &priv->cmdq_reg, priv->regs, + MDP_RDMA_SRC_CON, FLD_UNIFORM_CONFIG); + mtk_ddp_write_mask(cmdq_pkt, rdma_fmt_convert(cfg->fmt), &priv->cmdq_reg, priv->regs, + MDP_RDMA_SRC_CON, FLD_SWAP | FLD_SRC_FORMAT | FLD_BIT_NUMBER); + + if (!csc_enable && fmt_info->has_alpha) + mtk_ddp_write_mask(cmdq_pkt, FLD_OUTPUT_ARGB, &priv->cmdq_reg, + priv->regs, MDP_RDMA_SRC_CON, FLD_OUTPUT_ARGB); + else + mtk_ddp_write_mask(cmdq_pkt, 0, &priv->cmdq_reg, priv->regs, + MDP_RDMA_SRC_CON, FLD_OUTPUT_ARGB); + + mtk_ddp_write_mask(cmdq_pkt, cfg->addr0, &priv->cmdq_reg, priv->regs, + MDP_RDMA_SRC_BASE_0, FLD_SRC_BASE_0); + + mtk_ddp_write_mask(cmdq_pkt, src_pitch_y, &priv->cmdq_reg, priv->regs, + MDP_RDMA_MF_BKGD_SIZE_IN_BYTE, FLD_MF_BKGD_WB); + + mtk_ddp_write_mask(cmdq_pkt, 0, &priv->cmdq_reg, priv->regs, MDP_RDMA_COMP_CON, + FLD_AFBC_YUV_TRANSFORM | FLD_UFBDC_EN | FLD_AFBC_EN); + mtk_ddp_write_mask(cmdq_pkt, FLD_OUTPUT_10B, &priv->cmdq_reg, priv->regs, + MDP_RDMA_CON, FLD_OUTPUT_10B); + mtk_ddp_write_mask(cmdq_pkt, FLD_SIMPLE_MODE, &priv->cmdq_reg, priv->regs, + MDP_RDMA_CON, FLD_SIMPLE_MODE); + if (csc_enable) + mtk_ddp_write_mask(cmdq_pkt, rdma_color_convert(cfg->color_encoding) << 23, + &priv->cmdq_reg, priv->regs, MDP_RDMA_TRANSFORM_0, + FLD_INT_MATRIX_SEL); + mtk_ddp_write_mask(cmdq_pkt, csc_enable << 16, &priv->cmdq_reg, priv->regs, + MDP_RDMA_TRANSFORM_0, FLD_TRANS_EN); + + offset_y = cfg->x_left * fmt_info->cpp[0] + cfg->y_top * src_pitch_y; + + mtk_ddp_write_mask(cmdq_pkt, offset_y, &priv->cmdq_reg, priv->regs, + MDP_RDMA_SRC_OFFSET_0, FLD_SRC_OFFSET_0); + mtk_ddp_write_mask(cmdq_pkt, cfg->width, &priv->cmdq_reg, priv->regs, + MDP_RDMA_MF_SRC_SIZE, FLD_MF_SRC_W); + mtk_ddp_write_mask(cmdq_pkt, cfg->height << 16, &priv->cmdq_reg, priv->regs, + MDP_RDMA_MF_SRC_SIZE, FLD_MF_SRC_H); + mtk_ddp_write_mask(cmdq_pkt, cfg->width, &priv->cmdq_reg, priv->regs, + MDP_RDMA_MF_CLIP_SIZE, FLD_MF_CLIP_W); + mtk_ddp_write_mask(cmdq_pkt, cfg->height << 16, &priv->cmdq_reg, priv->regs, + MDP_RDMA_MF_CLIP_SIZE, FLD_MF_CLIP_H); +} + +int mtk_mdp_rdma_clk_enable(struct device *dev) +{ + struct mtk_mdp_rdma *rdma = dev_get_drvdata(dev); + + return clk_prepare_enable(rdma->clk); +} + +void mtk_mdp_rdma_clk_disable(struct device *dev) +{ + struct mtk_mdp_rdma *rdma = dev_get_drvdata(dev); + + clk_disable_unprepare(rdma->clk); +} + +static int mtk_mdp_rdma_bind(struct device *dev, struct device *master, + void *data) +{ + return 0; +} + +static void mtk_mdp_rdma_unbind(struct device *dev, struct device *master, + void *data) +{ +} + +static const struct component_ops mtk_mdp_rdma_component_ops = { + .bind = mtk_mdp_rdma_bind, + .unbind = mtk_mdp_rdma_unbind, +}; + +static int mtk_mdp_rdma_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct mtk_mdp_rdma *priv; + int ret = 0; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->regs)) { + dev_err(dev, "failed to ioremap rdma\n"); + return PTR_ERR(priv->regs); + } + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "failed to get rdma clk\n"); + return PTR_ERR(priv->clk); + } + +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + ret = cmdq_dev_get_client_reg(dev, &priv->cmdq_reg, 0); + if (ret) + dev_dbg(dev, "get mediatek,gce-client-reg fail!\n"); +#endif + platform_set_drvdata(pdev, priv); + + pm_runtime_enable(dev); + + ret = component_add(dev, &mtk_mdp_rdma_component_ops); + if (ret != 0) { + pm_runtime_disable(dev); + dev_err(dev, "Failed to add component: %d\n", ret); + } + return ret; +} + +static int mtk_mdp_rdma_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &mtk_mdp_rdma_component_ops); + pm_runtime_disable(&pdev->dev); + return 0; +} + +static const struct of_device_id mtk_mdp_rdma_driver_dt_match[] = { + { .compatible = "mediatek,mt8195-vdo1-rdma", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_mdp_rdma_driver_dt_match); + +struct platform_driver mtk_mdp_rdma_driver = { + .probe = mtk_mdp_rdma_probe, + .remove = mtk_mdp_rdma_remove, + .driver = { + .name = "mediatek-mdp-rdma", + .owner = THIS_MODULE, + .of_match_table = mtk_mdp_rdma_driver_dt_match, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_mdp_rdma.h b/drivers/gpu/drm/mediatek/mtk_mdp_rdma.h new file mode 100644 index 000000000..9943ee3aa --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_mdp_rdma.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#ifndef __MTK_MDP_RDMA_H__ +#define __MTK_MDP_RDMA_H__ + +struct mtk_mdp_rdma_cfg { + unsigned int pitch; + unsigned int addr0; + unsigned int width; + unsigned int height; + unsigned int x_left; + unsigned int y_top; + int fmt; + int color_encoding; +}; + +#endif // __MTK_MDP_RDMA_H__ |