diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /drivers/gpu/drm/mediatek | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/mediatek')
30 files changed, 10238 insertions, 0 deletions
diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig new file mode 100644 index 000000000..65cd03a4b --- /dev/null +++ b/drivers/gpu/drm/mediatek/Kconfig @@ -0,0 +1,29 @@ +# 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_CMA_HELPER + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL + select MEMORY + select MTK_SMI + 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_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..77b0fd860 --- /dev/null +++ b/drivers/gpu/drm/mediatek/Makefile @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 + +mediatek-drm-y := mtk_disp_color.o \ + mtk_disp_ovl.o \ + mtk_disp_rdma.o \ + mtk_drm_crtc.o \ + mtk_drm_ddp.o \ + mtk_drm_ddp_comp.o \ + mtk_drm_drv.o \ + mtk_drm_gem.o \ + mtk_drm_plane.o \ + mtk_dsi.o \ + mtk_mipi_tx.o \ + mtk_mt8173_mipi_tx.o \ + mtk_mt8183_mipi_tx.o \ + mtk_dpi.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 diff --git a/drivers/gpu/drm/mediatek/mtk_cec.c b/drivers/gpu/drm/mediatek/mtk_cec.c new file mode 100644 index 000000000..12bf93769 --- /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/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) { + dev_err(dev, "Failed to get cec irq: %d\n", cec->irq); + 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", }, + {} +}; + +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_color.c b/drivers/gpu/drm/mediatek/mtk_disp_color.c new file mode 100644 index 000000000..3ae9c8108 --- /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_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_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 + * @ddp_comp - structure containing type enum and hardware resources + * @crtc - associated crtc to report irq events to + */ +struct mtk_disp_color { + struct mtk_ddp_comp ddp_comp; + struct drm_crtc *crtc; + const struct mtk_disp_color_data *data; +}; + +static inline struct mtk_disp_color *comp_to_color(struct mtk_ddp_comp *comp) +{ + return container_of(comp, struct mtk_disp_color, ddp_comp); +} + +static void mtk_color_config(struct mtk_ddp_comp *comp, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_color *color = comp_to_color(comp); + + mtk_ddp_write(cmdq_pkt, w, comp, DISP_COLOR_WIDTH(color)); + mtk_ddp_write(cmdq_pkt, h, comp, DISP_COLOR_HEIGHT(color)); +} + +static void mtk_color_start(struct mtk_ddp_comp *comp) +{ + struct mtk_disp_color *color = comp_to_color(comp); + + writel(COLOR_BYPASS_ALL | COLOR_SEQ_SEL, + comp->regs + DISP_COLOR_CFG_MAIN); + writel(0x1, comp->regs + DISP_COLOR_START(color)); +} + +static const struct mtk_ddp_comp_funcs mtk_disp_color_funcs = { + .config = mtk_color_config, + .start = mtk_color_start, +}; + +static int mtk_disp_color_bind(struct device *dev, struct device *master, + void *data) +{ + struct mtk_disp_color *priv = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + int ret; + + ret = mtk_ddp_comp_register(drm_dev, &priv->ddp_comp); + if (ret < 0) { + dev_err(dev, "Failed to register component %pOF: %d\n", + dev->of_node, ret); + return ret; + } + + return 0; +} + +static void mtk_disp_color_unbind(struct device *dev, struct device *master, + void *data) +{ + struct mtk_disp_color *priv = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + + mtk_ddp_comp_unregister(drm_dev, &priv->ddp_comp); +} + +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; + int comp_id; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DISP_COLOR); + if (comp_id < 0) { + dev_err(dev, "Failed to identify by alias: %d\n", comp_id); + return comp_id; + } + + ret = mtk_ddp_comp_init(dev, dev->of_node, &priv->ddp_comp, comp_id, + &mtk_disp_color_funcs); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to initialize component: %d\n", + ret); + + return ret; + } + + 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 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,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_ovl.c b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c new file mode 100644 index 000000000..faff41183 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c @@ -0,0 +1,450 @@ +// 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/soc/mediatek/mtk-cmdq.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_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; +}; + +/** + * struct mtk_disp_ovl - DISP_OVL driver structure + * @ddp_comp - structure containing type enum and hardware resources + * @crtc - associated crtc to report vblank events to + */ +struct mtk_disp_ovl { + struct mtk_ddp_comp ddp_comp; + struct drm_crtc *crtc; + const struct mtk_disp_ovl_data *data; +}; + +static inline struct mtk_disp_ovl *comp_to_ovl(struct mtk_ddp_comp *comp) +{ + return container_of(comp, struct mtk_disp_ovl, ddp_comp); +} + +static irqreturn_t mtk_disp_ovl_irq_handler(int irq, void *dev_id) +{ + struct mtk_disp_ovl *priv = dev_id; + struct mtk_ddp_comp *ovl = &priv->ddp_comp; + + /* Clear frame completion interrupt */ + writel(0x0, ovl->regs + DISP_REG_OVL_INTSTA); + + if (!priv->crtc) + return IRQ_NONE; + + mtk_crtc_ddp_irq(priv->crtc, ovl); + + return IRQ_HANDLED; +} + +static void mtk_ovl_enable_vblank(struct mtk_ddp_comp *comp, + struct drm_crtc *crtc) +{ + struct mtk_disp_ovl *ovl = comp_to_ovl(comp); + + ovl->crtc = crtc; + writel(0x0, comp->regs + DISP_REG_OVL_INTSTA); + writel_relaxed(OVL_FME_CPL_INT, comp->regs + DISP_REG_OVL_INTEN); +} + +static void mtk_ovl_disable_vblank(struct mtk_ddp_comp *comp) +{ + struct mtk_disp_ovl *ovl = comp_to_ovl(comp); + + ovl->crtc = NULL; + writel_relaxed(0x0, comp->regs + DISP_REG_OVL_INTEN); +} + +static void mtk_ovl_start(struct mtk_ddp_comp *comp) +{ + writel_relaxed(0x1, comp->regs + DISP_REG_OVL_EN); +} + +static void mtk_ovl_stop(struct mtk_ddp_comp *comp) +{ + writel_relaxed(0x0, comp->regs + DISP_REG_OVL_EN); +} + +static void mtk_ovl_config(struct mtk_ddp_comp *comp, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + if (w != 0 && h != 0) + mtk_ddp_write_relaxed(cmdq_pkt, h << 16 | w, comp, + DISP_REG_OVL_ROI_SIZE); + mtk_ddp_write_relaxed(cmdq_pkt, 0x0, comp, DISP_REG_OVL_ROI_BGCLR); + + mtk_ddp_write(cmdq_pkt, 0x1, comp, DISP_REG_OVL_RST); + mtk_ddp_write(cmdq_pkt, 0x0, comp, DISP_REG_OVL_RST); +} + +static unsigned int mtk_ovl_layer_nr(struct mtk_ddp_comp *comp) +{ + struct mtk_disp_ovl *ovl = comp_to_ovl(comp); + + return ovl->data->layer_nr; +} + +static unsigned int mtk_ovl_supported_rotations(struct mtk_ddp_comp *comp) +{ + return DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180 | + DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y; +} + +static int mtk_ovl_layer_check(struct mtk_ddp_comp *comp, 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; +} + +static void mtk_ovl_layer_on(struct mtk_ddp_comp *comp, 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 = comp_to_ovl(comp); + + mtk_ddp_write(cmdq_pkt, 0x1, comp, + 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, + comp, DISP_REG_OVL_RDMA_GMC(idx)); + mtk_ddp_write_mask(cmdq_pkt, BIT(idx), comp, + DISP_REG_OVL_SRC_CON, BIT(idx)); +} + +static void mtk_ovl_layer_off(struct mtk_ddp_comp *comp, unsigned int idx, + struct cmdq_pkt *cmdq_pkt) +{ + mtk_ddp_write_mask(cmdq_pkt, 0, comp, + DISP_REG_OVL_SRC_CON, BIT(idx)); + mtk_ddp_write(cmdq_pkt, 0, comp, + 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; + } +} + +static void mtk_ovl_layer_config(struct mtk_ddp_comp *comp, unsigned int idx, + struct mtk_plane_state *state, + struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_ovl *ovl = comp_to_ovl(comp); + 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(comp, 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, comp, + DISP_REG_OVL_CON(idx)); + mtk_ddp_write_relaxed(cmdq_pkt, pitch, comp, + DISP_REG_OVL_PITCH(idx)); + mtk_ddp_write_relaxed(cmdq_pkt, src_size, comp, + DISP_REG_OVL_SRC_SIZE(idx)); + mtk_ddp_write_relaxed(cmdq_pkt, offset, comp, + DISP_REG_OVL_OFFSET(idx)); + mtk_ddp_write_relaxed(cmdq_pkt, addr, comp, + DISP_REG_OVL_ADDR(ovl, idx)); + + mtk_ovl_layer_on(comp, idx, cmdq_pkt); +} + +static void mtk_ovl_bgclr_in_on(struct mtk_ddp_comp *comp) +{ + unsigned int reg; + + reg = readl(comp->regs + DISP_REG_OVL_DATAPATH_CON); + reg = reg | OVL_BGCLR_SEL_IN; + writel(reg, comp->regs + DISP_REG_OVL_DATAPATH_CON); +} + +static void mtk_ovl_bgclr_in_off(struct mtk_ddp_comp *comp) +{ + unsigned int reg; + + reg = readl(comp->regs + DISP_REG_OVL_DATAPATH_CON); + reg = reg & ~OVL_BGCLR_SEL_IN; + writel(reg, comp->regs + DISP_REG_OVL_DATAPATH_CON); +} + +static const struct mtk_ddp_comp_funcs mtk_disp_ovl_funcs = { + .config = mtk_ovl_config, + .start = mtk_ovl_start, + .stop = mtk_ovl_stop, + .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 int mtk_disp_ovl_bind(struct device *dev, struct device *master, + void *data) +{ + struct mtk_disp_ovl *priv = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + int ret; + + ret = mtk_ddp_comp_register(drm_dev, &priv->ddp_comp); + if (ret < 0) { + dev_err(dev, "Failed to register component %pOF: %d\n", + dev->of_node, ret); + return ret; + } + + return 0; +} + +static void mtk_disp_ovl_unbind(struct device *dev, struct device *master, + void *data) +{ + struct mtk_disp_ovl *priv = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + + mtk_ddp_comp_unregister(drm_dev, &priv->ddp_comp); +} + +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; + int comp_id; + 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->data = of_device_get_match_data(dev); + + comp_id = mtk_ddp_comp_get_id(dev->of_node, + priv->data->layer_nr == 4 ? + MTK_DISP_OVL : + MTK_DISP_OVL_2L); + if (comp_id < 0) { + dev_err(dev, "Failed to identify by alias: %d\n", comp_id); + return comp_id; + } + + ret = mtk_ddp_comp_init(dev, dev->of_node, &priv->ddp_comp, comp_id, + &mtk_disp_ovl_funcs); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to initialize component: %d\n", + ret); + + return ret; + } + + 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; + } + + ret = component_add(dev, &mtk_disp_ovl_component_ops); + if (ret) + 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); + + 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 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}, + {}, +}; +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..e04319fed --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_disp_rdma.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 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_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 + * @ddp_comp - structure containing type enum and hardware resources + * @crtc - associated crtc to report irq events to + */ +struct mtk_disp_rdma { + struct mtk_ddp_comp ddp_comp; + struct drm_crtc *crtc; + const struct mtk_disp_rdma_data *data; +}; + +static inline struct mtk_disp_rdma *comp_to_rdma(struct mtk_ddp_comp *comp) +{ + return container_of(comp, struct mtk_disp_rdma, ddp_comp); +} + +static irqreturn_t mtk_disp_rdma_irq_handler(int irq, void *dev_id) +{ + struct mtk_disp_rdma *priv = dev_id; + struct mtk_ddp_comp *rdma = &priv->ddp_comp; + + /* Clear frame completion interrupt */ + writel(0x0, rdma->regs + DISP_REG_RDMA_INT_STATUS); + + if (!priv->crtc) + return IRQ_NONE; + + mtk_crtc_ddp_irq(priv->crtc, rdma); + + return IRQ_HANDLED; +} + +static void rdma_update_bits(struct mtk_ddp_comp *comp, unsigned int reg, + unsigned int mask, unsigned int val) +{ + unsigned int tmp = readl(comp->regs + reg); + + tmp = (tmp & ~mask) | (val & mask); + writel(tmp, comp->regs + reg); +} + +static void mtk_rdma_enable_vblank(struct mtk_ddp_comp *comp, + struct drm_crtc *crtc) +{ + struct mtk_disp_rdma *rdma = comp_to_rdma(comp); + + rdma->crtc = crtc; + rdma_update_bits(comp, DISP_REG_RDMA_INT_ENABLE, RDMA_FRAME_END_INT, + RDMA_FRAME_END_INT); +} + +static void mtk_rdma_disable_vblank(struct mtk_ddp_comp *comp) +{ + struct mtk_disp_rdma *rdma = comp_to_rdma(comp); + + rdma->crtc = NULL; + rdma_update_bits(comp, DISP_REG_RDMA_INT_ENABLE, RDMA_FRAME_END_INT, 0); +} + +static void mtk_rdma_start(struct mtk_ddp_comp *comp) +{ + rdma_update_bits(comp, DISP_REG_RDMA_GLOBAL_CON, RDMA_ENGINE_EN, + RDMA_ENGINE_EN); +} + +static void mtk_rdma_stop(struct mtk_ddp_comp *comp) +{ + rdma_update_bits(comp, DISP_REG_RDMA_GLOBAL_CON, RDMA_ENGINE_EN, 0); +} + +static void mtk_rdma_config(struct mtk_ddp_comp *comp, 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 = comp_to_rdma(comp); + + mtk_ddp_write_mask(cmdq_pkt, width, comp, + DISP_REG_RDMA_SIZE_CON_0, 0xfff); + mtk_ddp_write_mask(cmdq_pkt, height, comp, + DISP_REG_RDMA_SIZE_CON_1, 0xfffff); + + /* + * 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 6 microseconds with 7/6 overhead to + * account for blanking, and with a pixel depth of 4 bytes: + */ + threshold = width * height * vrefresh * 4 * 7 / 1000000; + reg = RDMA_FIFO_UNDERFLOW_EN | + RDMA_FIFO_PSEUDO_SIZE(RDMA_FIFO_SIZE(rdma)) | + RDMA_OUTPUT_VALID_FIFO_THRESHOLD(threshold); + mtk_ddp_write(cmdq_pkt, reg, comp, 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; + } +} + +static unsigned int mtk_rdma_layer_nr(struct mtk_ddp_comp *comp) +{ + return 1; +} + +static void mtk_rdma_layer_config(struct mtk_ddp_comp *comp, unsigned int idx, + struct mtk_plane_state *state, + struct cmdq_pkt *cmdq_pkt) +{ + struct mtk_disp_rdma *rdma = comp_to_rdma(comp); + 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, comp, DISP_RDMA_MEM_CON); + + if (fmt == DRM_FORMAT_UYVY || fmt == DRM_FORMAT_YUYV) { + mtk_ddp_write_mask(cmdq_pkt, RDMA_MATRIX_ENABLE, comp, + DISP_REG_RDMA_SIZE_CON_0, + RDMA_MATRIX_ENABLE); + mtk_ddp_write_mask(cmdq_pkt, RDMA_MATRIX_INT_MTX_BT601_to_RGB, + comp, DISP_REG_RDMA_SIZE_CON_0, + RDMA_MATRIX_INT_MTX_SEL); + } else { + mtk_ddp_write_mask(cmdq_pkt, 0, comp, + DISP_REG_RDMA_SIZE_CON_0, + RDMA_MATRIX_ENABLE); + } + mtk_ddp_write_relaxed(cmdq_pkt, addr, comp, DISP_RDMA_MEM_START_ADDR); + mtk_ddp_write_relaxed(cmdq_pkt, pitch, comp, DISP_RDMA_MEM_SRC_PITCH); + mtk_ddp_write(cmdq_pkt, RDMA_MEM_GMC, comp, + DISP_RDMA_MEM_GMC_SETTING_0); + mtk_ddp_write_mask(cmdq_pkt, RDMA_MODE_MEMORY, comp, + DISP_REG_RDMA_GLOBAL_CON, RDMA_MODE_MEMORY); + +} + +static const struct mtk_ddp_comp_funcs mtk_disp_rdma_funcs = { + .config = mtk_rdma_config, + .start = mtk_rdma_start, + .stop = mtk_rdma_stop, + .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 int mtk_disp_rdma_bind(struct device *dev, struct device *master, + void *data) +{ + struct mtk_disp_rdma *priv = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + int ret; + + ret = mtk_ddp_comp_register(drm_dev, &priv->ddp_comp); + if (ret < 0) { + dev_err(dev, "Failed to register component %pOF: %d\n", + dev->of_node, ret); + return ret; + } + + return 0; + +} + +static void mtk_disp_rdma_unbind(struct device *dev, struct device *master, + void *data) +{ + struct mtk_disp_rdma *priv = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + + mtk_ddp_comp_unregister(drm_dev, &priv->ddp_comp); +} + +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; + int comp_id; + 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; + + comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DISP_RDMA); + if (comp_id < 0) { + dev_err(dev, "Failed to identify by alias: %d\n", comp_id); + return comp_id; + } + + ret = mtk_ddp_comp_init(dev, dev->of_node, &priv->ddp_comp, comp_id, + &mtk_disp_rdma_funcs); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to initialize component: %d\n", + ret); + + return ret; + } + + /* Disable and clear pending interrupts */ + writel(0x0, priv->ddp_comp.regs + DISP_REG_RDMA_INT_ENABLE); + writel(0x0, priv->ddp_comp.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); + + ret = component_add(dev, &mtk_disp_rdma_component_ops); + if (ret) + 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); + + 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 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}, + {}, +}; +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_dpi.c b/drivers/gpu/drm/mediatek/mtk_dpi.c new file mode 100644 index 000000000..aa3d472c7 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dpi.c @@ -0,0 +1,814 @@ +// 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/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.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_crtc.h> +#include <drm/drm_of.h> +#include <drm/drm_simple_kms_helper.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 +}; + +struct mtk_dpi { + struct mtk_ddp_comp ddp_comp; + struct drm_encoder encoder; + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + 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; + 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 { + unsigned int (*cal_factor)(int clock); + u32 reg_h_fre_con; + bool edge_sel_en; +}; + +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, HPW_MASK); + mtk_dpi_mask(dpi, DPI_TGEN_HPORCH, + sync->back_porch << HBP, HBP_MASK); + mtk_dpi_mask(dpi, DPI_TGEN_HPORCH, sync->front_porch << HFP, + HFP_MASK); +} + +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->sync_width << VSYNC_WIDTH_SHIFT, + VSYNC_WIDTH_MASK); + mtk_dpi_mask(dpi, width_addr, + sync->shift_half_line << VSYNC_HALF_LINE_SHIFT, + VSYNC_HALF_LINE_MASK); + mtk_dpi_mask(dpi, porch_addr, + sync->back_porch << VSYNC_BACK_PORCH_SHIFT, + VSYNC_BACK_PORCH_MASK); + mtk_dpi_mask(dpi, porch_addr, + sync->front_porch << VSYNC_FRONT_PORCH_SHIFT, + VSYNC_FRONT_PORCH_MASK); +} + +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; + + pol = (dpi_pol->ck_pol == MTK_DPI_POLARITY_RISING ? 0 : CK_POL) | + (dpi_pol->de_pol == MTK_DPI_POLARITY_RISING ? 0 : DE_POL) | + (dpi_pol->hsync_pol == MTK_DPI_POLARITY_RISING ? 0 : HSYNC_POL) | + (dpi_pol->vsync_pol == MTK_DPI_POLARITY_RISING ? 0 : VSYNC_POL); + mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, pol, + CK_POL | DE_POL | HSYNC_POL | VSYNC_POL); +} + +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, HSIZE_MASK); + mtk_dpi_mask(dpi, DPI_SIZE, height << VSIZE, VSIZE_MASK); +} + +static void mtk_dpi_config_channel_limit(struct mtk_dpi *dpi, + struct mtk_dpi_yc_limit *limit) +{ + 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 << CH_SWAP, CH_SWAP_MASK); +} + +static void mtk_dpi_config_yuv422_enable(struct mtk_dpi *dpi, bool enable) +{ + mtk_dpi_mask(dpi, DPI_CON, enable ? YUV422_EN : 0, YUV422_EN); +} + +static void mtk_dpi_config_csc_enable(struct mtk_dpi *dpi, bool enable) +{ + mtk_dpi_mask(dpi, DPI_CON, enable ? CSC_ENABLE : 0, CSC_ENABLE); +} + +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) +{ + /* only support RGB888 */ + mtk_dpi_config_yuv422_enable(dpi, false); + mtk_dpi_config_csc_enable(dpi, false); + mtk_dpi_config_swap_input(dpi, false); + mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_RGB); +} + +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_yc_limit limit; + 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); + + vm.pixelclock = pll_rate / factor; + 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); + + limit.c_bottom = 0x0010; + limit.c_top = 0x0FE0; + limit.y_bottom = 0x0010; + limit.y_top = 0x0FE0; + + 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; + hsync.sync_width = vm.hsync_len; + hsync.back_porch = vm.hback_porch; + hsync.front_porch = vm.hfront_porch; + 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, &limit); + mtk_dpi_config_bit_num(dpi, dpi->bit_num); + mtk_dpi_config_channel_swap(dpi, dpi->channel_swap); + mtk_dpi_config_yc_map(dpi, dpi->yc_map); + mtk_dpi_config_color_format(dpi, dpi->color_format); + mtk_dpi_config_2n_h_fre(dpi); + mtk_dpi_config_disable_edge(dpi); + mtk_dpi_sw_reset(dpi, false); + + 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 const struct drm_bridge_funcs mtk_dpi_bridge_funcs = { + .attach = mtk_dpi_bridge_attach, + .mode_set = mtk_dpi_bridge_mode_set, + .disable = mtk_dpi_bridge_disable, + .enable = mtk_dpi_bridge_enable, +}; + +static void mtk_dpi_start(struct mtk_ddp_comp *comp) +{ + struct mtk_dpi *dpi = container_of(comp, struct mtk_dpi, ddp_comp); + + mtk_dpi_power_on(dpi); +} + +static void mtk_dpi_stop(struct mtk_ddp_comp *comp) +{ + struct mtk_dpi *dpi = container_of(comp, struct mtk_dpi, ddp_comp); + + mtk_dpi_power_off(dpi); +} + +static const struct mtk_ddp_comp_funcs mtk_dpi_funcs = { + .start = mtk_dpi_start, + .stop = mtk_dpi_stop, +}; + +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 = mtk_ddp_comp_register(drm_dev, &dpi->ddp_comp); + if (ret < 0) { + dev_err(dev, "Failed to register component %pOF: %d\n", + dev->of_node, ret); + return 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); + goto err_unregister; + } + + dpi->encoder.possible_crtcs = mtk_drm_find_possible_crtc_by_comp(drm_dev, dpi->ddp_comp); + + ret = drm_bridge_attach(&dpi->encoder, &dpi->bridge, NULL, 0); + if (ret) { + dev_err(dev, "Failed to attach bridge: %d\n", ret); + goto err_cleanup; + } + + 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; + dpi->color_format = MTK_DPI_COLOR_FORMAT_RGB; + + return 0; + +err_cleanup: + drm_encoder_cleanup(&dpi->encoder); +err_unregister: + mtk_ddp_comp_unregister(drm_dev, &dpi->ddp_comp); + return ret; +} + +static void mtk_dpi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct mtk_dpi *dpi = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + + drm_encoder_cleanup(&dpi->encoder); + mtk_ddp_comp_unregister(drm_dev, &dpi->ddp_comp); +} + +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 const struct mtk_dpi_conf mt8173_conf = { + .cal_factor = mt8173_calculate_factor, + .reg_h_fre_con = 0xe0, +}; + +static const struct mtk_dpi_conf mt2701_conf = { + .cal_factor = mt2701_calculate_factor, + .reg_h_fre_con = 0xb0, + .edge_sel_en = true, +}; + +static const struct mtk_dpi_conf mt8183_conf = { + .cal_factor = mt8183_calculate_factor, + .reg_h_fre_con = 0xe0, +}; + +static int mtk_dpi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_dpi *dpi; + struct resource *mem; + int comp_id; + 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->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) { + dev_err(dev, "Failed to get irq: %d\n", dpi->irq); + 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); + + comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DPI); + if (comp_id < 0) { + dev_err(dev, "Failed to identify by alias: %d\n", comp_id); + return comp_id; + } + + ret = mtk_ddp_comp_init(dev, dev->of_node, &dpi->ddp_comp, comp_id, + &mtk_dpi_funcs); + if (ret) { + dev_err(dev, "Failed to initialize component: %d\n", ret); + return ret; + } + + 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, + }, + { }, +}; + +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..3a02fabe1 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dpi_regs.h @@ -0,0 +1,220 @@ +/* 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 DPI_OUTPUT_SETTING 0x14 +#define CH_SWAP 0 +#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 VSIZE 16 +#define VSIZE_MASK (0x1FFF << 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 DPI_TGEN_HPORCH 0x24 +#define HBP 0 +#define HBP_MASK (0xFFF << 0) +#define HFP 16 +#define HFP_MASK (0xFFF << 16) + +#define DPI_TGEN_VWIDTH 0x28 +#define DPI_TGEN_VPORCH 0x2C + +#define VSYNC_WIDTH_SHIFT 0 +#define VSYNC_WIDTH_MASK (0xFFF << 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 VSYNC_FRONT_PORCH_SHIFT 16 +#define VSYNC_FRONT_PORCH_MASK (0xFFF << 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) +#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..1eaf51316 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.c @@ -0,0 +1,859 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 MediaTek Inc. + */ + +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <linux/soc/mediatek/mtk-cmdq.h> +#include <linux/soc/mediatek/mtk-mmsys.h> + +#include <asm/barrier.h> +#include <soc/mediatek/smi.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_plane_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.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 + */ +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; + u32 cmdq_event; +#endif + + struct device *mmsys_dev; + struct mtk_disp_mutex *mutex; + unsigned int ddp_comp_nr; + struct mtk_ddp_comp **ddp_comp; + + /* lock for display hardware access */ + struct mutex hw_lock; +}; + +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->pending_needs_vblank) { + mtk_drm_crtc_finish_page_flip(mtk_crtc); + mtk_crtc->pending_needs_vblank = false; + } +} + +static void mtk_drm_crtc_destroy(struct drm_crtc *crtc) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + + mtk_disp_mutex_put(mtk_crtc->mutex); + + 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 = kzalloc(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; + + 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_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, &mtk_crtc->base); + + 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); +} + +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 = clk_prepare_enable(mtk_crtc->ddp_comp[i]->clk); + if (ret) { + DRM_ERROR("Failed to enable clock %d: %d\n", i, ret); + goto err; + } + } + + return 0; +err: + while (--i >= 0) + clk_disable_unprepare(mtk_crtc->ddp_comp[i]->clk); + 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++) + clk_disable_unprepare(mtk_crtc->ddp_comp[i]->clk); +} + +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 cmdq_cb_data data) +{ + cmdq_pkt_destroy(data.data); +} +#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_disp_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_disp_mutex_add_comp(mtk_crtc->mutex, + mtk_crtc->ddp_comp[i]->id); + } + mtk_disp_mutex_add_comp(mtk_crtc->mutex, mtk_crtc->ddp_comp[i]->id); + mtk_disp_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_disp_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_disp_mutex_remove_comp(mtk_crtc->mutex, + mtk_crtc->ddp_comp[i]->id); + mtk_disp_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_disp_mutex_remove_comp(mtk_crtc->mutex, + mtk_crtc->ddp_comp[i]->id); + } + mtk_disp_mutex_remove_comp(mtk_crtc->mutex, mtk_crtc->ddp_comp[i]->id); + mtk_crtc_ddp_clk_disable(mtk_crtc); + mtk_disp_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); + + 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); + 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); + + 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); + plane_state->pending.async_config = false; + } + mtk_crtc->pending_async_planes = false; + } +} + +static void mtk_drm_crtc_hw_config(struct mtk_drm_crtc *mtk_crtc) +{ +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + struct cmdq_pkt *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); + 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_disp_mutex_acquire(mtk_crtc->mutex); + mtk_crtc_ddp_config(crtc, NULL); + mtk_disp_mutex_release(mtk_crtc->mutex); + } +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + if (mtk_crtc->cmdq_client) { + mbox_flush(mtk_crtc->cmdq_client->chan, 2000); + cmdq_handle = cmdq_pkt_create(mtk_crtc->cmdq_client, PAGE_SIZE); + 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); + cmdq_pkt_flush_async(cmdq_handle, ddp_cmdq_cb, cmdq_handle); + } +#endif + mutex_unlock(&mtk_crtc->hw_lock); +} + +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_plane_state *new_state) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + const struct drm_plane_helper_funcs *plane_helper_funcs = + plane->helper_private; + + if (!mtk_crtc->enabled) + return; + + plane_helper_funcs->atomic_update(plane, new_state); + mtk_drm_crtc_hw_config(mtk_crtc); +} + +static void mtk_drm_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_crtc_state *old_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 = mtk_smi_larb_get(comp->larb_dev); + if (ret) { + DRM_ERROR("Failed to get larb: %d\n", ret); + return; + } + + ret = mtk_crtc_ddp_hw_init(mtk_crtc); + if (ret) { + mtk_smi_larb_put(comp->larb_dev); + return; + } + + drm_crtc_vblank_on(crtc); + mtk_crtc->enabled = true; +} + +static void mtk_drm_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + struct mtk_ddp_comp *comp = mtk_crtc->ddp_comp[0]; + int i; + + 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_hw_config(mtk_crtc); + /* Wait for planes to be disabled */ + drm_crtc_wait_one_vblank(crtc); + + drm_crtc_vblank_off(crtc); + mtk_crtc_ddp_hw_fini(mtk_crtc); + mtk_smi_larb_put(comp->larb_dev); + + mtk_crtc->enabled = false; +} + +static void mtk_drm_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state); + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + unsigned long flags; + + if (mtk_crtc->event && state->base.event) + DRM_ERROR("new event while there is still a pending event\n"); + + if (state->base.event) { + 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 = state->base.event; + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + + state->base.event = NULL; + } +} + +static void mtk_drm_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + int i; + + if (mtk_crtc->event) + mtk_crtc->pending_needs_vblank = true; + 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_hw_config(mtk_crtc); +} + +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, + .gamma_set = drm_atomic_helper_legacy_gamma_set, + .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; +} + +void mtk_crtc_ddp_irq(struct drm_crtc *crtc, struct mtk_ddp_comp *comp) +{ + 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) +#else + if (!priv->data->shadow_register) +#endif + mtk_crtc_ddp_config(crtc, NULL); + + mtk_drm_finish_page_flip(mtk_crtc); +} + +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; + + node = priv->comp_node[comp_id]; + if (!node) { + dev_info(dev, + "Not creating crtc %d because component %d is disabled or missing\n", + pipe, comp_id); + return 0; + } + } + + 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_disp_mutex_get(priv->mutex_dev, pipe); + 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; + struct device_node *node; + + node = priv->comp_node[comp_id]; + comp = priv->ddp_comp[comp_id]; + if (!comp) { + dev_err(dev, "Component %pOF not initialized\n", node); + ret = -ENODEV; + return ret; + } + + 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; + } + } + + 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 = + cmdq_mbox_create(mtk_crtc->mmsys_dev, + drm_crtc_index(&mtk_crtc->base), + 2000); + if (IS_ERR(mtk_crtc->cmdq_client)) { + 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 = NULL; + } + + if (mtk_crtc->cmdq_client) { + 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)); + cmdq_mbox_destroy(mtk_crtc->cmdq_client); + mtk_crtc->cmdq_client = NULL; + } + } +#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..a2b4677a4 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.h @@ -0,0 +1,27 @@ +/* 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); +void mtk_crtc_ddp_irq(struct drm_crtc *crtc, struct mtk_ddp_comp *comp); +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_plane_state *plane_state); + +#endif /* MTK_DRM_CRTC_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp.c b/drivers/gpu/drm/mediatek/mtk_drm_ddp.c new file mode 100644 index 000000000..014c1bbe1 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp.c @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 MediaTek Inc. + */ + +#include <linux/clk.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include "mtk_drm_ddp.h" +#include "mtk_drm_ddp_comp.h" + +#define MT2701_DISP_MUTEX0_MOD0 0x2c +#define MT2701_DISP_MUTEX0_SOF0 0x30 + +#define DISP_REG_MUTEX_EN(n) (0x20 + 0x20 * (n)) +#define DISP_REG_MUTEX(n) (0x24 + 0x20 * (n)) +#define DISP_REG_MUTEX_RST(n) (0x28 + 0x20 * (n)) +#define DISP_REG_MUTEX_MOD(mutex_mod_reg, n) (mutex_mod_reg + 0x20 * (n)) +#define DISP_REG_MUTEX_SOF(mutex_sof_reg, n) (mutex_sof_reg + 0x20 * (n)) +#define DISP_REG_MUTEX_MOD2(n) (0x34 + 0x20 * (n)) + +#define INT_MUTEX BIT(1) + +#define MT8173_MUTEX_MOD_DISP_OVL0 11 +#define MT8173_MUTEX_MOD_DISP_OVL1 12 +#define MT8173_MUTEX_MOD_DISP_RDMA0 13 +#define MT8173_MUTEX_MOD_DISP_RDMA1 14 +#define MT8173_MUTEX_MOD_DISP_RDMA2 15 +#define MT8173_MUTEX_MOD_DISP_WDMA0 16 +#define MT8173_MUTEX_MOD_DISP_WDMA1 17 +#define MT8173_MUTEX_MOD_DISP_COLOR0 18 +#define MT8173_MUTEX_MOD_DISP_COLOR1 19 +#define MT8173_MUTEX_MOD_DISP_AAL 20 +#define MT8173_MUTEX_MOD_DISP_GAMMA 21 +#define MT8173_MUTEX_MOD_DISP_UFOE 22 +#define MT8173_MUTEX_MOD_DISP_PWM0 23 +#define MT8173_MUTEX_MOD_DISP_PWM1 24 +#define MT8173_MUTEX_MOD_DISP_OD 25 + +#define MT2712_MUTEX_MOD_DISP_PWM2 10 +#define MT2712_MUTEX_MOD_DISP_OVL0 11 +#define MT2712_MUTEX_MOD_DISP_OVL1 12 +#define MT2712_MUTEX_MOD_DISP_RDMA0 13 +#define MT2712_MUTEX_MOD_DISP_RDMA1 14 +#define MT2712_MUTEX_MOD_DISP_RDMA2 15 +#define MT2712_MUTEX_MOD_DISP_WDMA0 16 +#define MT2712_MUTEX_MOD_DISP_WDMA1 17 +#define MT2712_MUTEX_MOD_DISP_COLOR0 18 +#define MT2712_MUTEX_MOD_DISP_COLOR1 19 +#define MT2712_MUTEX_MOD_DISP_AAL0 20 +#define MT2712_MUTEX_MOD_DISP_UFOE 22 +#define MT2712_MUTEX_MOD_DISP_PWM0 23 +#define MT2712_MUTEX_MOD_DISP_PWM1 24 +#define MT2712_MUTEX_MOD_DISP_OD0 25 +#define MT2712_MUTEX_MOD2_DISP_AAL1 33 +#define MT2712_MUTEX_MOD2_DISP_OD1 34 + +#define MT2701_MUTEX_MOD_DISP_OVL 3 +#define MT2701_MUTEX_MOD_DISP_WDMA 6 +#define MT2701_MUTEX_MOD_DISP_COLOR 7 +#define MT2701_MUTEX_MOD_DISP_BLS 9 +#define MT2701_MUTEX_MOD_DISP_RDMA0 10 +#define MT2701_MUTEX_MOD_DISP_RDMA1 12 + +#define MUTEX_SOF_SINGLE_MODE 0 +#define MUTEX_SOF_DSI0 1 +#define MUTEX_SOF_DSI1 2 +#define MUTEX_SOF_DPI0 3 +#define MUTEX_SOF_DPI1 4 +#define MUTEX_SOF_DSI2 5 +#define MUTEX_SOF_DSI3 6 + + +struct mtk_disp_mutex { + int id; + bool claimed; +}; + +enum mtk_ddp_mutex_sof_id { + DDP_MUTEX_SOF_SINGLE_MODE, + DDP_MUTEX_SOF_DSI0, + DDP_MUTEX_SOF_DSI1, + DDP_MUTEX_SOF_DPI0, + DDP_MUTEX_SOF_DPI1, + DDP_MUTEX_SOF_DSI2, + DDP_MUTEX_SOF_DSI3, +}; + +struct mtk_ddp_data { + const unsigned int *mutex_mod; + const unsigned int *mutex_sof; + const unsigned int mutex_mod_reg; + const unsigned int mutex_sof_reg; + const bool no_clk; +}; + +struct mtk_ddp { + struct device *dev; + struct clk *clk; + void __iomem *regs; + struct mtk_disp_mutex mutex[10]; + const struct mtk_ddp_data *data; +}; + +static const unsigned int mt2701_mutex_mod[DDP_COMPONENT_ID_MAX] = { + [DDP_COMPONENT_BLS] = MT2701_MUTEX_MOD_DISP_BLS, + [DDP_COMPONENT_COLOR0] = MT2701_MUTEX_MOD_DISP_COLOR, + [DDP_COMPONENT_OVL0] = MT2701_MUTEX_MOD_DISP_OVL, + [DDP_COMPONENT_RDMA0] = MT2701_MUTEX_MOD_DISP_RDMA0, + [DDP_COMPONENT_RDMA1] = MT2701_MUTEX_MOD_DISP_RDMA1, + [DDP_COMPONENT_WDMA0] = MT2701_MUTEX_MOD_DISP_WDMA, +}; + +static const unsigned int mt2712_mutex_mod[DDP_COMPONENT_ID_MAX] = { + [DDP_COMPONENT_AAL0] = MT2712_MUTEX_MOD_DISP_AAL0, + [DDP_COMPONENT_AAL1] = MT2712_MUTEX_MOD2_DISP_AAL1, + [DDP_COMPONENT_COLOR0] = MT2712_MUTEX_MOD_DISP_COLOR0, + [DDP_COMPONENT_COLOR1] = MT2712_MUTEX_MOD_DISP_COLOR1, + [DDP_COMPONENT_OD0] = MT2712_MUTEX_MOD_DISP_OD0, + [DDP_COMPONENT_OD1] = MT2712_MUTEX_MOD2_DISP_OD1, + [DDP_COMPONENT_OVL0] = MT2712_MUTEX_MOD_DISP_OVL0, + [DDP_COMPONENT_OVL1] = MT2712_MUTEX_MOD_DISP_OVL1, + [DDP_COMPONENT_PWM0] = MT2712_MUTEX_MOD_DISP_PWM0, + [DDP_COMPONENT_PWM1] = MT2712_MUTEX_MOD_DISP_PWM1, + [DDP_COMPONENT_PWM2] = MT2712_MUTEX_MOD_DISP_PWM2, + [DDP_COMPONENT_RDMA0] = MT2712_MUTEX_MOD_DISP_RDMA0, + [DDP_COMPONENT_RDMA1] = MT2712_MUTEX_MOD_DISP_RDMA1, + [DDP_COMPONENT_RDMA2] = MT2712_MUTEX_MOD_DISP_RDMA2, + [DDP_COMPONENT_UFOE] = MT2712_MUTEX_MOD_DISP_UFOE, + [DDP_COMPONENT_WDMA0] = MT2712_MUTEX_MOD_DISP_WDMA0, + [DDP_COMPONENT_WDMA1] = MT2712_MUTEX_MOD_DISP_WDMA1, +}; + +static const unsigned int mt8173_mutex_mod[DDP_COMPONENT_ID_MAX] = { + [DDP_COMPONENT_AAL0] = MT8173_MUTEX_MOD_DISP_AAL, + [DDP_COMPONENT_COLOR0] = MT8173_MUTEX_MOD_DISP_COLOR0, + [DDP_COMPONENT_COLOR1] = MT8173_MUTEX_MOD_DISP_COLOR1, + [DDP_COMPONENT_GAMMA] = MT8173_MUTEX_MOD_DISP_GAMMA, + [DDP_COMPONENT_OD0] = MT8173_MUTEX_MOD_DISP_OD, + [DDP_COMPONENT_OVL0] = MT8173_MUTEX_MOD_DISP_OVL0, + [DDP_COMPONENT_OVL1] = MT8173_MUTEX_MOD_DISP_OVL1, + [DDP_COMPONENT_PWM0] = MT8173_MUTEX_MOD_DISP_PWM0, + [DDP_COMPONENT_PWM1] = MT8173_MUTEX_MOD_DISP_PWM1, + [DDP_COMPONENT_RDMA0] = MT8173_MUTEX_MOD_DISP_RDMA0, + [DDP_COMPONENT_RDMA1] = MT8173_MUTEX_MOD_DISP_RDMA1, + [DDP_COMPONENT_RDMA2] = MT8173_MUTEX_MOD_DISP_RDMA2, + [DDP_COMPONENT_UFOE] = MT8173_MUTEX_MOD_DISP_UFOE, + [DDP_COMPONENT_WDMA0] = MT8173_MUTEX_MOD_DISP_WDMA0, + [DDP_COMPONENT_WDMA1] = MT8173_MUTEX_MOD_DISP_WDMA1, +}; + +static const unsigned int mt2712_mutex_sof[DDP_MUTEX_SOF_DSI3 + 1] = { + [DDP_MUTEX_SOF_SINGLE_MODE] = MUTEX_SOF_SINGLE_MODE, + [DDP_MUTEX_SOF_DSI0] = MUTEX_SOF_DSI0, + [DDP_MUTEX_SOF_DSI1] = MUTEX_SOF_DSI1, + [DDP_MUTEX_SOF_DPI0] = MUTEX_SOF_DPI0, + [DDP_MUTEX_SOF_DPI1] = MUTEX_SOF_DPI1, + [DDP_MUTEX_SOF_DSI2] = MUTEX_SOF_DSI2, + [DDP_MUTEX_SOF_DSI3] = MUTEX_SOF_DSI3, +}; + +static const struct mtk_ddp_data mt2701_ddp_driver_data = { + .mutex_mod = mt2701_mutex_mod, + .mutex_sof = mt2712_mutex_sof, + .mutex_mod_reg = MT2701_DISP_MUTEX0_MOD0, + .mutex_sof_reg = MT2701_DISP_MUTEX0_SOF0, +}; + +static const struct mtk_ddp_data mt2712_ddp_driver_data = { + .mutex_mod = mt2712_mutex_mod, + .mutex_sof = mt2712_mutex_sof, + .mutex_mod_reg = MT2701_DISP_MUTEX0_MOD0, + .mutex_sof_reg = MT2701_DISP_MUTEX0_SOF0, +}; + +static const struct mtk_ddp_data mt8173_ddp_driver_data = { + .mutex_mod = mt8173_mutex_mod, + .mutex_sof = mt2712_mutex_sof, + .mutex_mod_reg = MT2701_DISP_MUTEX0_MOD0, + .mutex_sof_reg = MT2701_DISP_MUTEX0_SOF0, +}; + +struct mtk_disp_mutex *mtk_disp_mutex_get(struct device *dev, unsigned int id) +{ + struct mtk_ddp *ddp = dev_get_drvdata(dev); + + if (id >= 10) + return ERR_PTR(-EINVAL); + if (ddp->mutex[id].claimed) + return ERR_PTR(-EBUSY); + + ddp->mutex[id].claimed = true; + + return &ddp->mutex[id]; +} + +void mtk_disp_mutex_put(struct mtk_disp_mutex *mutex) +{ + struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp, + mutex[mutex->id]); + + WARN_ON(&ddp->mutex[mutex->id] != mutex); + + mutex->claimed = false; +} + +int mtk_disp_mutex_prepare(struct mtk_disp_mutex *mutex) +{ + struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp, + mutex[mutex->id]); + return clk_prepare_enable(ddp->clk); +} + +void mtk_disp_mutex_unprepare(struct mtk_disp_mutex *mutex) +{ + struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp, + mutex[mutex->id]); + clk_disable_unprepare(ddp->clk); +} + +void mtk_disp_mutex_add_comp(struct mtk_disp_mutex *mutex, + enum mtk_ddp_comp_id id) +{ + struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp, + mutex[mutex->id]); + unsigned int reg; + unsigned int sof_id; + unsigned int offset; + + WARN_ON(&ddp->mutex[mutex->id] != mutex); + + switch (id) { + case DDP_COMPONENT_DSI0: + sof_id = DDP_MUTEX_SOF_DSI0; + break; + case DDP_COMPONENT_DSI1: + sof_id = DDP_MUTEX_SOF_DSI0; + break; + case DDP_COMPONENT_DSI2: + sof_id = DDP_MUTEX_SOF_DSI2; + break; + case DDP_COMPONENT_DSI3: + sof_id = DDP_MUTEX_SOF_DSI3; + break; + case DDP_COMPONENT_DPI0: + sof_id = DDP_MUTEX_SOF_DPI0; + break; + case DDP_COMPONENT_DPI1: + sof_id = DDP_MUTEX_SOF_DPI1; + break; + default: + if (ddp->data->mutex_mod[id] < 32) { + offset = DISP_REG_MUTEX_MOD(ddp->data->mutex_mod_reg, + mutex->id); + reg = readl_relaxed(ddp->regs + offset); + reg |= 1 << ddp->data->mutex_mod[id]; + writel_relaxed(reg, ddp->regs + offset); + } else { + offset = DISP_REG_MUTEX_MOD2(mutex->id); + reg = readl_relaxed(ddp->regs + offset); + reg |= 1 << (ddp->data->mutex_mod[id] - 32); + writel_relaxed(reg, ddp->regs + offset); + } + return; + } + + writel_relaxed(ddp->data->mutex_sof[sof_id], + ddp->regs + + DISP_REG_MUTEX_SOF(ddp->data->mutex_sof_reg, mutex->id)); +} + +void mtk_disp_mutex_remove_comp(struct mtk_disp_mutex *mutex, + enum mtk_ddp_comp_id id) +{ + struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp, + mutex[mutex->id]); + unsigned int reg; + unsigned int offset; + + WARN_ON(&ddp->mutex[mutex->id] != mutex); + + switch (id) { + case DDP_COMPONENT_DSI0: + case DDP_COMPONENT_DSI1: + case DDP_COMPONENT_DSI2: + case DDP_COMPONENT_DSI3: + case DDP_COMPONENT_DPI0: + case DDP_COMPONENT_DPI1: + writel_relaxed(MUTEX_SOF_SINGLE_MODE, + ddp->regs + + DISP_REG_MUTEX_SOF(ddp->data->mutex_sof_reg, + mutex->id)); + break; + default: + if (ddp->data->mutex_mod[id] < 32) { + offset = DISP_REG_MUTEX_MOD(ddp->data->mutex_mod_reg, + mutex->id); + reg = readl_relaxed(ddp->regs + offset); + reg &= ~(1 << ddp->data->mutex_mod[id]); + writel_relaxed(reg, ddp->regs + offset); + } else { + offset = DISP_REG_MUTEX_MOD2(mutex->id); + reg = readl_relaxed(ddp->regs + offset); + reg &= ~(1 << (ddp->data->mutex_mod[id] - 32)); + writel_relaxed(reg, ddp->regs + offset); + } + break; + } +} + +void mtk_disp_mutex_enable(struct mtk_disp_mutex *mutex) +{ + struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp, + mutex[mutex->id]); + + WARN_ON(&ddp->mutex[mutex->id] != mutex); + + writel(1, ddp->regs + DISP_REG_MUTEX_EN(mutex->id)); +} + +void mtk_disp_mutex_disable(struct mtk_disp_mutex *mutex) +{ + struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp, + mutex[mutex->id]); + + WARN_ON(&ddp->mutex[mutex->id] != mutex); + + writel(0, ddp->regs + DISP_REG_MUTEX_EN(mutex->id)); +} + +void mtk_disp_mutex_acquire(struct mtk_disp_mutex *mutex) +{ + struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp, + mutex[mutex->id]); + u32 tmp; + + writel(1, ddp->regs + DISP_REG_MUTEX_EN(mutex->id)); + writel(1, ddp->regs + DISP_REG_MUTEX(mutex->id)); + if (readl_poll_timeout_atomic(ddp->regs + DISP_REG_MUTEX(mutex->id), + tmp, tmp & INT_MUTEX, 1, 10000)) + pr_err("could not acquire mutex %d\n", mutex->id); +} + +void mtk_disp_mutex_release(struct mtk_disp_mutex *mutex) +{ + struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp, + mutex[mutex->id]); + + writel(0, ddp->regs + DISP_REG_MUTEX(mutex->id)); +} + +static int mtk_ddp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_ddp *ddp; + struct resource *regs; + int i; + + ddp = devm_kzalloc(dev, sizeof(*ddp), GFP_KERNEL); + if (!ddp) + return -ENOMEM; + + for (i = 0; i < 10; i++) + ddp->mutex[i].id = i; + + ddp->data = of_device_get_match_data(dev); + + if (!ddp->data->no_clk) { + ddp->clk = devm_clk_get(dev, NULL); + if (IS_ERR(ddp->clk)) { + if (PTR_ERR(ddp->clk) != -EPROBE_DEFER) + dev_err(dev, "Failed to get clock\n"); + return PTR_ERR(ddp->clk); + } + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ddp->regs = devm_ioremap_resource(dev, regs); + if (IS_ERR(ddp->regs)) { + dev_err(dev, "Failed to map mutex registers\n"); + return PTR_ERR(ddp->regs); + } + + platform_set_drvdata(pdev, ddp); + + return 0; +} + +static int mtk_ddp_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id ddp_driver_dt_match[] = { + { .compatible = "mediatek,mt2701-disp-mutex", + .data = &mt2701_ddp_driver_data}, + { .compatible = "mediatek,mt2712-disp-mutex", + .data = &mt2712_ddp_driver_data}, + { .compatible = "mediatek,mt8173-disp-mutex", + .data = &mt8173_ddp_driver_data}, + {}, +}; +MODULE_DEVICE_TABLE(of, ddp_driver_dt_match); + +struct platform_driver mtk_ddp_driver = { + .probe = mtk_ddp_probe, + .remove = mtk_ddp_remove, + .driver = { + .name = "mediatek-ddp", + .owner = THIS_MODULE, + .of_match_table = ddp_driver_dt_match, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp.h b/drivers/gpu/drm/mediatek/mtk_drm_ddp.h new file mode 100644 index 000000000..6b691a57b --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015 MediaTek Inc. + */ + +#ifndef MTK_DRM_DDP_H +#define MTK_DRM_DDP_H + +#include "mtk_drm_ddp_comp.h" + +struct regmap; +struct device; +struct mtk_disp_mutex; + +struct mtk_disp_mutex *mtk_disp_mutex_get(struct device *dev, unsigned int id); +int mtk_disp_mutex_prepare(struct mtk_disp_mutex *mutex); +void mtk_disp_mutex_add_comp(struct mtk_disp_mutex *mutex, + enum mtk_ddp_comp_id id); +void mtk_disp_mutex_enable(struct mtk_disp_mutex *mutex); +void mtk_disp_mutex_disable(struct mtk_disp_mutex *mutex); +void mtk_disp_mutex_remove_comp(struct mtk_disp_mutex *mutex, + enum mtk_ddp_comp_id id); +void mtk_disp_mutex_unprepare(struct mtk_disp_mutex *mutex); +void mtk_disp_mutex_put(struct mtk_disp_mutex *mutex); +void mtk_disp_mutex_acquire(struct mtk_disp_mutex *mutex); +void mtk_disp_mutex_release(struct mtk_disp_mutex *mutex); + +#endif /* MTK_DRM_DDP_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..e747ff7ba --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c @@ -0,0 +1,569 @@ +// 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_irq.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_drm_drv.h" +#include "mtk_drm_plane.h" +#include "mtk_drm_ddp_comp.h" +#include "mtk_drm_crtc.h" + +#define DISP_OD_EN 0x0000 +#define DISP_OD_INTEN 0x0008 +#define DISP_OD_INTSTA 0x000c +#define DISP_OD_CFG 0x0020 +#define DISP_OD_SIZE 0x0030 +#define DISP_DITHER_5 0x0114 +#define DISP_DITHER_7 0x011c +#define DISP_DITHER_15 0x013c +#define DISP_DITHER_16 0x0140 + +#define DISP_REG_UFO_START 0x0000 + +#define DISP_AAL_EN 0x0000 +#define DISP_AAL_SIZE 0x0030 +#define DISP_AAL_OUTPUT_SIZE 0x04d8 + +#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 + +#define DISP_DITHER_EN 0x0000 +#define DITHER_EN BIT(0) +#define DISP_DITHER_CFG 0x0020 +#define DITHER_RELAY_MODE BIT(0) +#define DISP_DITHER_SIZE 0x0030 + +#define DISP_GAMMA_EN 0x0000 +#define DISP_GAMMA_CFG 0x0020 +#define DISP_GAMMA_SIZE 0x0030 +#define DISP_GAMMA_LUT 0x0700 + +#define LUT_10BIT_MASK 0x03ff + +#define OD_RELAYMODE BIT(0) + +#define UFO_BYPASS BIT(2) + +#define AAL_EN BIT(0) + +#define GAMMA_EN BIT(0) +#define GAMMA_LUT_EN BIT(1) + +#define DISP_DITHERING BIT(2) +#define DITHER_LSB_ERR_SHIFT_R(x) (((x) & 0x7) << 28) +#define DITHER_OVFLW_BIT_R(x) (((x) & 0x7) << 24) +#define DITHER_ADD_LSHIFT_R(x) (((x) & 0x7) << 20) +#define DITHER_ADD_RSHIFT_R(x) (((x) & 0x7) << 16) +#define DITHER_NEW_BIT_MODE BIT(0) +#define DITHER_LSB_ERR_SHIFT_B(x) (((x) & 0x7) << 28) +#define DITHER_OVFLW_BIT_B(x) (((x) & 0x7) << 24) +#define DITHER_ADD_LSHIFT_B(x) (((x) & 0x7) << 20) +#define DITHER_ADD_RSHIFT_B(x) (((x) & 0x7) << 16) +#define DITHER_LSB_ERR_SHIFT_G(x) (((x) & 0x7) << 12) +#define DITHER_OVFLW_BIT_G(x) (((x) & 0x7) << 8) +#define DITHER_ADD_LSHIFT_G(x) (((x) & 0x7) << 4) +#define DITHER_ADD_RSHIFT_G(x) (((x) & 0x7) << 0) + +void mtk_ddp_write(struct cmdq_pkt *cmdq_pkt, unsigned int value, + struct mtk_ddp_comp *comp, unsigned int offset) +{ +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + if (cmdq_pkt) + cmdq_pkt_write(cmdq_pkt, comp->subsys, + comp->regs_pa + offset, value); + else +#endif + writel(value, comp->regs + offset); +} + +void mtk_ddp_write_relaxed(struct cmdq_pkt *cmdq_pkt, unsigned int value, + struct mtk_ddp_comp *comp, + unsigned int offset) +{ +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + if (cmdq_pkt) + cmdq_pkt_write(cmdq_pkt, comp->subsys, + comp->regs_pa + offset, value); + else +#endif + writel_relaxed(value, comp->regs + offset); +} + +void mtk_ddp_write_mask(struct cmdq_pkt *cmdq_pkt, + unsigned int value, + struct mtk_ddp_comp *comp, + unsigned int offset, + unsigned int mask) +{ +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + if (cmdq_pkt) { + cmdq_pkt_write_mask(cmdq_pkt, comp->subsys, + comp->regs_pa + offset, value, mask); + } else { +#endif + u32 tmp = readl(comp->regs + offset); + + tmp = (tmp & ~mask) | (value & mask); + writel(tmp, comp->regs + offset); +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + } +#endif +} + +void mtk_dither_set(struct mtk_ddp_comp *comp, unsigned int bpc, + unsigned int CFG, 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, comp, DISP_DITHER_5); + mtk_ddp_write(cmdq_pkt, 0, comp, DISP_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, + comp, DISP_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), + comp, DISP_DITHER_16); + mtk_ddp_write(cmdq_pkt, DISP_DITHERING, comp, CFG); + } +} + +static void mtk_od_config(struct mtk_ddp_comp *comp, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + mtk_ddp_write(cmdq_pkt, w << 16 | h, comp, DISP_OD_SIZE); + mtk_ddp_write(cmdq_pkt, OD_RELAYMODE, comp, DISP_OD_CFG); + mtk_dither_set(comp, bpc, DISP_OD_CFG, cmdq_pkt); +} + +static void mtk_od_start(struct mtk_ddp_comp *comp) +{ + writel(1, comp->regs + DISP_OD_EN); +} + +static void mtk_ufoe_start(struct mtk_ddp_comp *comp) +{ + writel(UFO_BYPASS, comp->regs + DISP_REG_UFO_START); +} + +static void mtk_aal_config(struct mtk_ddp_comp *comp, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + mtk_ddp_write(cmdq_pkt, w << 16 | h, comp, DISP_AAL_SIZE); + mtk_ddp_write(cmdq_pkt, w << 16 | h, comp, DISP_AAL_OUTPUT_SIZE); +} + +static void mtk_aal_start(struct mtk_ddp_comp *comp) +{ + writel(AAL_EN, comp->regs + DISP_AAL_EN); +} + +static void mtk_aal_stop(struct mtk_ddp_comp *comp) +{ + writel_relaxed(0x0, comp->regs + DISP_AAL_EN); +} + +static void mtk_ccorr_config(struct mtk_ddp_comp *comp, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + mtk_ddp_write(cmdq_pkt, h << 16 | w, comp, DISP_CCORR_SIZE); + mtk_ddp_write(cmdq_pkt, CCORR_ENGINE_EN, comp, DISP_CCORR_CFG); +} + +static void mtk_ccorr_start(struct mtk_ddp_comp *comp) +{ + writel(CCORR_EN, comp->regs + DISP_CCORR_EN); +} + +static void mtk_ccorr_stop(struct mtk_ddp_comp *comp) +{ + writel_relaxed(0x0, comp->regs + DISP_CCORR_EN); +} + +/* Converts a DRM S31.32 value to the HW S1.10 format. */ +static u16 mtk_ctm_s31_32_to_s1_10(u64 in) +{ + u16 r; + + /* Sign bit. */ + r = in & BIT_ULL(63) ? BIT(11) : 0; + + if ((in & GENMASK_ULL(62, 33)) > 0) { + /* identity value 0x100000000 -> 0x400, */ + /* if bigger this, set it to max 0x7ff. */ + r |= GENMASK(10, 0); + } else { + /* take the 11 most important bits. */ + r |= (in >> 22) & GENMASK(10, 0); + } + + return r; +} + +static void mtk_ccorr_ctm_set(struct mtk_ddp_comp *comp, + struct drm_crtc_state *state) +{ + 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; + + 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_10(input[i]); + + mtk_ddp_write(cmdq_pkt, coeffs[0] << 16 | coeffs[1], + comp, DISP_CCORR_COEF_0); + mtk_ddp_write(cmdq_pkt, coeffs[2] << 16 | coeffs[3], + comp, DISP_CCORR_COEF_1); + mtk_ddp_write(cmdq_pkt, coeffs[4] << 16 | coeffs[5], + comp, DISP_CCORR_COEF_2); + mtk_ddp_write(cmdq_pkt, coeffs[6] << 16 | coeffs[7], + comp, DISP_CCORR_COEF_3); + mtk_ddp_write(cmdq_pkt, coeffs[8] << 16, + comp, DISP_CCORR_COEF_4); +} + +static void mtk_dither_config(struct mtk_ddp_comp *comp, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + mtk_ddp_write(cmdq_pkt, h << 16 | w, comp, DISP_DITHER_SIZE); + mtk_ddp_write(cmdq_pkt, DITHER_RELAY_MODE, comp, DISP_DITHER_CFG); +} + +static void mtk_dither_start(struct mtk_ddp_comp *comp) +{ + writel(DITHER_EN, comp->regs + DISP_DITHER_EN); +} + +static void mtk_dither_stop(struct mtk_ddp_comp *comp) +{ + writel_relaxed(0x0, comp->regs + DISP_DITHER_EN); +} + +static void mtk_gamma_config(struct mtk_ddp_comp *comp, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt) +{ + mtk_ddp_write(cmdq_pkt, h << 16 | w, comp, DISP_GAMMA_SIZE); + mtk_dither_set(comp, bpc, DISP_GAMMA_CFG, cmdq_pkt); +} + +static void mtk_gamma_start(struct mtk_ddp_comp *comp) +{ + writel(GAMMA_EN, comp->regs + DISP_GAMMA_EN); +} + +static void mtk_gamma_stop(struct mtk_ddp_comp *comp) +{ + writel_relaxed(0x0, comp->regs + DISP_GAMMA_EN); +} + +static void mtk_gamma_set(struct mtk_ddp_comp *comp, + struct drm_crtc_state *state) +{ + unsigned int i, reg; + struct drm_color_lut *lut; + void __iomem *lut_base; + u32 word; + + if (state->gamma_lut) { + reg = readl(comp->regs + DISP_GAMMA_CFG); + reg = reg | GAMMA_LUT_EN; + writel(reg, comp->regs + DISP_GAMMA_CFG); + lut_base = comp->regs + DISP_GAMMA_LUT; + lut = (struct drm_color_lut *)state->gamma_lut->data; + for (i = 0; i < MTK_LUT_SIZE; i++) { + word = (((lut[i].red >> 6) & LUT_10BIT_MASK) << 20) + + (((lut[i].green >> 6) & LUT_10BIT_MASK) << 10) + + ((lut[i].blue >> 6) & LUT_10BIT_MASK); + writel(word, (lut_base + i * 4)); + } + } +} + +static const struct mtk_ddp_comp_funcs ddp_aal = { + .gamma_set = mtk_gamma_set, + .config = mtk_aal_config, + .start = mtk_aal_start, + .stop = mtk_aal_stop, +}; + +static const struct mtk_ddp_comp_funcs ddp_ccorr = { + .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_dither = { + .config = mtk_dither_config, + .start = mtk_dither_start, + .stop = mtk_dither_stop, +}; + +static const struct mtk_ddp_comp_funcs ddp_gamma = { + .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_od = { + .config = mtk_od_config, + .start = mtk_od_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_ufoe = { + .start = mtk_ufoe_start, +}; + +static const char * const mtk_ddp_comp_stem[MTK_DDP_COMP_TYPE_MAX] = { + [MTK_DISP_OVL] = "ovl", + [MTK_DISP_OVL_2L] = "ovl-2l", + [MTK_DISP_RDMA] = "rdma", + [MTK_DISP_WDMA] = "wdma", + [MTK_DISP_COLOR] = "color", + [MTK_DISP_CCORR] = "ccorr", + [MTK_DISP_AAL] = "aal", + [MTK_DISP_GAMMA] = "gamma", + [MTK_DISP_DITHER] = "dither", + [MTK_DISP_UFOE] = "ufoe", + [MTK_DSI] = "dsi", + [MTK_DPI] = "dpi", + [MTK_DISP_PWM] = "pwm", + [MTK_DISP_MUTEX] = "mutex", + [MTK_DISP_OD] = "od", + [MTK_DISP_BLS] = "bls", +}; + +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, NULL }, + [DDP_COMPONENT_COLOR1] = { MTK_DISP_COLOR, 1, NULL }, + [DDP_COMPONENT_DITHER] = { MTK_DISP_DITHER, 0, &ddp_dither }, + [DDP_COMPONENT_DPI0] = { MTK_DPI, 0, NULL }, + [DDP_COMPONENT_DPI1] = { MTK_DPI, 1, NULL }, + [DDP_COMPONENT_DSI0] = { MTK_DSI, 0, NULL }, + [DDP_COMPONENT_DSI1] = { MTK_DSI, 1, NULL }, + [DDP_COMPONENT_DSI2] = { MTK_DSI, 2, NULL }, + [DDP_COMPONENT_DSI3] = { MTK_DSI, 3, NULL }, + [DDP_COMPONENT_GAMMA] = { MTK_DISP_GAMMA, 0, &ddp_gamma }, + [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, NULL }, + [DDP_COMPONENT_OVL1] = { MTK_DISP_OVL, 1, NULL }, + [DDP_COMPONENT_OVL_2L0] = { MTK_DISP_OVL_2L, 0, NULL }, + [DDP_COMPONENT_OVL_2L1] = { MTK_DISP_OVL_2L, 1, NULL }, + [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, NULL }, + [DDP_COMPONENT_RDMA1] = { MTK_DISP_RDMA, 1, NULL }, + [DDP_COMPONENT_RDMA2] = { MTK_DISP_RDMA, 2, NULL }, + [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 mtk_ddp_comp ddp_comp, + const enum mtk_ddp_comp_id *path, + unsigned int path_len) +{ + unsigned int i; + + if (path == NULL) + return false; + + for (i = 0U; i < path_len; i++) + if (ddp_comp.id == path[i]) + 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 mtk_ddp_comp ddp_comp) +{ + struct mtk_drm_private *private = drm->dev_private; + unsigned int ret = 0; + + if (mtk_drm_find_comp_in_ddp(ddp_comp, private->data->main_path, private->data->main_len)) + ret = BIT(0); + else if (mtk_drm_find_comp_in_ddp(ddp_comp, private->data->ext_path, + private->data->ext_len)) + ret = BIT(1); + else if (mtk_drm_find_comp_in_ddp(ddp_comp, private->data->third_path, + private->data->third_len)) + ret = BIT(2); + else + DRM_INFO("Failed to find comp in ddp table\n"); + + return ret; +} + +int mtk_ddp_comp_init(struct device *dev, struct device_node *node, + struct mtk_ddp_comp *comp, enum mtk_ddp_comp_id comp_id, + const struct mtk_ddp_comp_funcs *funcs) +{ + enum mtk_ddp_comp_type type; + struct device_node *larb_node; + struct platform_device *larb_pdev; +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + struct resource res; + struct cmdq_client_reg cmdq_reg; + 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 = funcs ?: mtk_ddp_matches[comp_id].funcs; + + if (comp_id == DDP_COMPONENT_BLS || + comp_id == DDP_COMPONENT_DPI0 || + comp_id == DDP_COMPONENT_DPI1 || + comp_id == DDP_COMPONENT_DSI0 || + comp_id == DDP_COMPONENT_DSI1 || + comp_id == DDP_COMPONENT_DSI2 || + comp_id == DDP_COMPONENT_DSI3 || + comp_id == DDP_COMPONENT_PWM0) { + comp->regs = NULL; + comp->clk = NULL; + comp->irq = 0; + return 0; + } + + comp->regs = of_iomap(node, 0); + comp->irq = of_irq_get(node, 0); + comp->clk = of_clk_get(node, 0); + if (IS_ERR(comp->clk)) + return PTR_ERR(comp->clk); + + /* Only DMA capable components need the LARB property */ + comp->larb_dev = NULL; + if (type != MTK_DISP_OVL && + type != MTK_DISP_OVL_2L && + type != MTK_DISP_RDMA && + type != MTK_DISP_WDMA) + return 0; + + larb_node = of_parse_phandle(node, "mediatek,larb", 0); + if (!larb_node) { + dev_err(dev, + "Missing mediadek,larb phandle in %pOF node\n", node); + return -EINVAL; + } + + larb_pdev = of_find_device_by_node(larb_node); + if (!larb_pdev) { + dev_warn(dev, "Waiting for larb device %pOF\n", larb_node); + of_node_put(larb_node); + return -EPROBE_DEFER; + } + of_node_put(larb_node); + + comp->larb_dev = &larb_pdev->dev; + +#if IS_REACHABLE(CONFIG_MTK_CMDQ) + if (of_address_to_resource(node, 0, &res) != 0) { + dev_err(dev, "Missing reg in %s node\n", node->full_name); + put_device(&larb_pdev->dev); + return -EINVAL; + } + comp->regs_pa = res.start; + + ret = cmdq_dev_get_client_reg(dev, &cmdq_reg, 0); + if (ret) + dev_dbg(dev, "get mediatek,gce-client-reg fail!\n"); + else + comp->subsys = cmdq_reg.subsys; +#endif + return 0; +} + +int mtk_ddp_comp_register(struct drm_device *drm, struct mtk_ddp_comp *comp) +{ + struct mtk_drm_private *private = drm->dev_private; + + if (private->ddp_comp[comp->id]) + return -EBUSY; + + private->ddp_comp[comp->id] = comp; + return 0; +} + +void mtk_ddp_comp_unregister(struct drm_device *drm, struct mtk_ddp_comp *comp) +{ + struct mtk_drm_private *private = drm->dev_private; + + private->ddp_comp[comp->id] = NULL; +} 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..5aa52b7af --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h @@ -0,0 +1,190 @@ +/* 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-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_OVL, + MTK_DISP_OVL_2L, + MTK_DISP_RDMA, + MTK_DISP_WDMA, + MTK_DISP_COLOR, + MTK_DISP_CCORR, + MTK_DISP_DITHER, + MTK_DISP_AAL, + MTK_DISP_GAMMA, + MTK_DISP_UFOE, + MTK_DSI, + MTK_DPI, + MTK_DISP_PWM, + MTK_DISP_MUTEX, + MTK_DISP_OD, + MTK_DISP_BLS, + MTK_DDP_COMP_TYPE_MAX, +}; + +struct mtk_ddp_comp; +struct cmdq_pkt; +struct mtk_ddp_comp_funcs { + void (*config)(struct mtk_ddp_comp *comp, unsigned int w, + unsigned int h, unsigned int vrefresh, + unsigned int bpc, struct cmdq_pkt *cmdq_pkt); + void (*start)(struct mtk_ddp_comp *comp); + void (*stop)(struct mtk_ddp_comp *comp); + void (*enable_vblank)(struct mtk_ddp_comp *comp, struct drm_crtc *crtc); + void (*disable_vblank)(struct mtk_ddp_comp *comp); + unsigned int (*supported_rotations)(struct mtk_ddp_comp *comp); + unsigned int (*layer_nr)(struct mtk_ddp_comp *comp); + int (*layer_check)(struct mtk_ddp_comp *comp, + unsigned int idx, + struct mtk_plane_state *state); + void (*layer_config)(struct mtk_ddp_comp *comp, unsigned int idx, + struct mtk_plane_state *state, + struct cmdq_pkt *cmdq_pkt); + void (*gamma_set)(struct mtk_ddp_comp *comp, + struct drm_crtc_state *state); + void (*bgclr_in_on)(struct mtk_ddp_comp *comp); + void (*bgclr_in_off)(struct mtk_ddp_comp *comp); + void (*ctm_set)(struct mtk_ddp_comp *comp, + struct drm_crtc_state *state); +}; + +struct mtk_ddp_comp { + struct clk *clk; + void __iomem *regs; + int irq; + struct device *larb_dev; + enum mtk_ddp_comp_id id; + const struct mtk_ddp_comp_funcs *funcs; + resource_size_t regs_pa; + u8 subsys; +}; + +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, 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); +} + +static inline void mtk_ddp_comp_stop(struct mtk_ddp_comp *comp) +{ + if (comp->funcs && comp->funcs->stop) + comp->funcs->stop(comp); +} + +static inline void mtk_ddp_comp_enable_vblank(struct mtk_ddp_comp *comp, + struct drm_crtc *crtc) +{ + if (comp->funcs && comp->funcs->enable_vblank) + comp->funcs->enable_vblank(comp, crtc); +} + +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); +} + +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); + + 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); + + 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, 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, 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, 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); +} + +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); +} + +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, 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 mtk_ddp_comp ddp_comp); +int mtk_ddp_comp_init(struct device *dev, struct device_node *comp_node, + struct mtk_ddp_comp *comp, enum mtk_ddp_comp_id comp_id, + const struct mtk_ddp_comp_funcs *funcs); +int mtk_ddp_comp_register(struct drm_device *drm, struct mtk_ddp_comp *comp); +void mtk_ddp_comp_unregister(struct drm_device *drm, struct mtk_ddp_comp *comp); +void mtk_dither_set(struct mtk_ddp_comp *comp, unsigned int bpc, + unsigned int CFG, struct cmdq_pkt *cmdq_pkt); +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 mtk_ddp_comp *comp, unsigned int offset); +void mtk_ddp_write_relaxed(struct cmdq_pkt *cmdq_pkt, unsigned int value, + struct mtk_ddp_comp *comp, unsigned int offset); +void mtk_ddp_write_mask(struct cmdq_pkt *cmdq_pkt, unsigned int value, + struct mtk_ddp_comp *comp, 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..719c46d24 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -0,0 +1,661 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: YT SHEN <yt.shen@mediatek.com> + */ + +#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/soc/mediatek/mtk-mmsys.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_cma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp.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 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 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_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_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_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 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 (!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. + */ + if (!dma_dev->dma_parms) { + private->dma_parms_allocated = true; + dma_dev->dma_parms = + devm_kzalloc(drm->dev, sizeof(*dma_dev->dma_parms), + GFP_KERNEL); + } + if (!dma_dev->dma_parms) { + ret = -ENOMEM; + goto put_dma_dev; + } + + ret = dma_set_max_seg_size(dma_dev, (unsigned int)DMA_BIT_MASK(32)); + if (ret) { + dev_err(dma_dev, "Failed to set DMA segment size\n"); + goto err_unset_dma_parms; + } + + /* + * We don't use the drm_irq_install() helpers provided by the DRM + * core, so we need to set this manually in order to allow the + * DRM_IOCTL_WAIT_VBLANK to operate correctly. + */ + drm->irq_enabled = true; + ret = drm_vblank_init(drm, MAX_CRTC); + if (ret < 0) + goto err_unset_dma_parms; + + drm_kms_helper_poll_init(drm); + drm_mode_config_reset(drm); + + return 0; + +err_unset_dma_parms: + if (private->dma_parms_allocated) + dma_dev->dma_parms = NULL; +put_dma_dev: + put_device(private->dma_dev); +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) +{ + struct mtk_drm_private *private = drm->dev_private; + + drm_kms_helper_poll_fini(drm); + drm_atomic_helper_shutdown(drm); + + if (private->dma_parms_allocated) + private->dma_dev->dma_parms = NULL; + + component_unbind_all(drm->dev, drm); +} + +static const struct file_operations mtk_drm_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, + .mmap = mtk_drm_gem_mmap, + .poll = drm_poll, + .read = drm_read, + .compat_ioctl = drm_compat_ioctl, +}; + +/* + * We need to override this because the device used to import the memory is + * not dev->dev, as drm_gem_prime_import() expects. + */ +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 struct drm_driver mtk_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + + .gem_free_object_unlocked = mtk_drm_gem_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .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_get_sg_table = mtk_gem_prime_get_sg_table, + .gem_prime_import_sg_table = mtk_gem_prime_import_sg_table, + .gem_prime_mmap = mtk_drm_gem_mmap_buf, + .gem_prime_vmap = mtk_drm_gem_prime_vmap, + .gem_prime_vunmap = mtk_drm_gem_prime_vunmap, + .fops = &mtk_drm_fops, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, +}; + +static int compare_of(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +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,mt2701-disp-ovl", + .data = (void *)MTK_DISP_OVL }, + { .compatible = "mediatek,mt8173-disp-ovl", + .data = (void *)MTK_DISP_OVL }, + { .compatible = "mediatek,mt2701-disp-rdma", + .data = (void *)MTK_DISP_RDMA }, + { .compatible = "mediatek,mt8173-disp-rdma", + .data = (void *)MTK_DISP_RDMA }, + { .compatible = "mediatek,mt8173-disp-wdma", + .data = (void *)MTK_DISP_WDMA }, + { .compatible = "mediatek,mt2701-disp-color", + .data = (void *)MTK_DISP_COLOR }, + { .compatible = "mediatek,mt8173-disp-color", + .data = (void *)MTK_DISP_COLOR }, + { .compatible = "mediatek,mt8173-disp-aal", + .data = (void *)MTK_DISP_AAL}, + { .compatible = "mediatek,mt8173-disp-gamma", + .data = (void *)MTK_DISP_GAMMA, }, + { .compatible = "mediatek,mt8173-disp-ufoe", + .data = (void *)MTK_DISP_UFOE }, + { .compatible = "mediatek,mt2701-dsi", + .data = (void *)MTK_DSI }, + { .compatible = "mediatek,mt8173-dsi", + .data = (void *)MTK_DSI }, + { .compatible = "mediatek,mt2701-dpi", + .data = (void *)MTK_DPI }, + { .compatible = "mediatek,mt8173-dpi", + .data = (void *)MTK_DPI }, + { .compatible = "mediatek,mt2701-disp-mutex", + .data = (void *)MTK_DISP_MUTEX }, + { .compatible = "mediatek,mt2712-disp-mutex", + .data = (void *)MTK_DISP_MUTEX }, + { .compatible = "mediatek,mt8173-disp-mutex", + .data = (void *)MTK_DISP_MUTEX }, + { .compatible = "mediatek,mt2701-disp-pwm", + .data = (void *)MTK_DISP_BLS }, + { .compatible = "mediatek,mt8173-disp-pwm", + .data = (void *)MTK_DISP_PWM }, + { .compatible = "mediatek,mt8173-disp-od", + .data = (void *)MTK_DISP_OD }, + { } +}; + +static const struct of_device_id mtk_drm_of_ids[] = { + { .compatible = "mediatek,mt2701-mmsys", + .data = &mt2701_mmsys_driver_data}, + { .compatible = "mediatek,mt7623-mmsys", + .data = &mt7623_mmsys_driver_data}, + { .compatible = "mediatek,mt2712-mmsys", + .data = &mt2712_mmsys_driver_data}, + { .compatible = "mediatek,mt8173-mmsys", + .data = &mt8173_mmsys_driver_data}, + { } +}; + +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; + 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; + + private->data = of_id->data; + + /* 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 COLOR, 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_COLOR || + comp_type == MTK_DISP_OVL || + comp_type == MTK_DISP_OVL_2L || + comp_type == MTK_DISP_RDMA || + comp_type == MTK_DSI || + comp_type == MTK_DPI) { + dev_info(dev, "Adding component match for %pOF\n", + node); + drm_of_component_match_add(dev, &match, compare_of, + node); + } else { + struct mtk_ddp_comp *comp; + + comp = devm_kzalloc(dev, sizeof(*comp), GFP_KERNEL); + if (!comp) { + ret = -ENOMEM; + of_node_put(node); + goto err_node; + } + + ret = mtk_ddp_comp_init(dev->parent, node, comp, + comp_id, NULL); + if (ret) { + of_node_put(node); + goto err_node; + } + + private->ddp_comp[comp_id] = comp; + } + } + + 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]); + if (private->ddp_comp[i]) { + put_device(private->ddp_comp[i]->larb_dev); + private->ddp_comp[i] = NULL; + } + } + 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; +} + +#ifdef CONFIG_PM_SLEEP +static int mtk_drm_sys_suspend(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_suspend(drm); + + return ret; +} + +static int mtk_drm_sys_resume(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); + + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(mtk_drm_pm_ops, mtk_drm_sys_suspend, + mtk_drm_sys_resume); + +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_ddp_driver, + &mtk_disp_color_driver, + &mtk_disp_ovl_driver, + &mtk_disp_rdma_driver, + &mtk_dpi_driver, + &mtk_drm_platform_driver, + &mtk_mipi_tx_driver, + &mtk_dsi_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..b5be63e53 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h @@ -0,0 +1,59 @@ +/* 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 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_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; + + bool dma_parms_allocated; +}; + +extern struct platform_driver mtk_ddp_driver; +extern struct platform_driver mtk_disp_color_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_mipi_tx_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..b20ea5890 --- /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_prime.h> + +#include "mtk_drm_drv.h" +#include "mtk_drm_gem.h" + +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); + + 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; + + /* + * 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_PFNMAP; + + ret = dma_mmap_attrs(priv->dma_dev, vma, mtk_gem->cookie, + mtk_gem->dma_addr, obj->size, mtk_gem->dma_attrs); + + return ret; +} + +int mtk_drm_gem_mmap_buf(struct drm_gem_object *obj, struct vm_area_struct *vma) +{ + int ret; + + ret = drm_gem_mmap_obj(obj, obj->size, vma); + if (ret) + return ret; + + return mtk_drm_gem_object_mmap(obj, vma); +} + +int mtk_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct drm_gem_object *obj; + int ret; + + ret = drm_gem_mmap(filp, vma); + if (ret) + return ret; + + obj = vma->vm_private_data; + + /* + * 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; + + return mtk_drm_gem_object_mmap(obj, vma); +} + +/* + * 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; +} + +void *mtk_drm_gem_prime_vmap(struct drm_gem_object *obj) +{ + struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj); + struct sg_table *sgt; + unsigned int npages; + + if (mtk_gem->kvaddr) + return mtk_gem->kvaddr; + + sgt = mtk_gem_prime_get_sg_table(obj); + if (IS_ERR(sgt)) + return NULL; + + npages = obj->size >> PAGE_SHIFT; + mtk_gem->pages = kcalloc(npages, sizeof(*mtk_gem->pages), GFP_KERNEL); + if (!mtk_gem->pages) + goto out; + + drm_prime_sg_to_page_addr_arrays(sgt, mtk_gem->pages, NULL, npages); + + mtk_gem->kvaddr = vmap(mtk_gem->pages, npages, VM_MAP, + pgprot_writecombine(PAGE_KERNEL)); + if (!mtk_gem->kvaddr) { + kfree(sgt); + kfree(mtk_gem->pages); + return NULL; + } +out: + kfree(sgt); + + return mtk_gem->kvaddr; +} + +void mtk_drm_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr) +{ + struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj); + + 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..ff9f976d9 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_gem.h @@ -0,0 +1,51 @@ +/* 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); +int mtk_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma); +int mtk_drm_gem_mmap_buf(struct drm_gem_object *obj, + struct vm_area_struct *vma); +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); +void *mtk_drm_gem_prime_vmap(struct drm_gem_object *obj); +void mtk_drm_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr); + +#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..92141a196 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.c @@ -0,0 +1,252 @@ +// 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_fourcc.h> +#include <drm/drm_atomic_uapi.h> +#include <drm/drm_plane_helper.h> +#include <drm/drm_gem_framebuffer_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; + plane->state = &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 = kzalloc(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_plane_state *state) +{ + struct drm_crtc_state *crtc_state; + int ret; + + if (plane != state->crtc->cursor) + return -EINVAL; + + if (!plane->state) + return -EINVAL; + + if (!plane->state->fb) + return -EINVAL; + + ret = mtk_drm_crtc_plane_check(state->crtc, plane, + to_mtk_plane_state(state)); + if (ret) + return ret; + + if (state->state) + crtc_state = drm_atomic_get_existing_crtc_state(state->state, + state->crtc); + else /* Special case for asynchronous cursor updates. */ + crtc_state = state->crtc->state; + + return drm_atomic_helper_check_plane_state(plane->state, crtc_state, + DRM_PLANE_HELPER_NO_SCALING, + DRM_PLANE_HELPER_NO_SCALING, + true, true); +} + +static void mtk_plane_atomic_async_update(struct drm_plane *plane, + struct drm_plane_state *new_state) +{ + struct mtk_plane_state *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; + swap(plane->state->fb, new_state->fb); + state->pending.async_dirty = true; + + mtk_drm_crtc_async_update(new_state->crtc, plane, new_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_plane_state *state) +{ + struct drm_framebuffer *fb = state->fb; + struct drm_crtc_state *crtc_state; + int ret; + + if (!fb) + return 0; + + if (WARN_ON(!state->crtc)) + return 0; + + ret = mtk_drm_crtc_plane_check(state->crtc, plane, + to_mtk_plane_state(state)); + if (ret) + return ret; + + crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + return drm_atomic_helper_check_plane_state(state, crtc_state, + DRM_PLANE_HELPER_NO_SCALING, + DRM_PLANE_HELPER_NO_SCALING, + true, true); +} + +static void mtk_plane_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct mtk_plane_state *state = to_mtk_plane_state(plane->state); + + state->pending.enable = false; + wmb(); /* Make sure the above parameter is set before update */ + state->pending.dirty = true; +} + +static void mtk_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct mtk_plane_state *state = to_mtk_plane_state(plane->state); + struct drm_crtc *crtc = plane->state->crtc; + struct drm_framebuffer *fb = plane->state->fb; + struct drm_gem_object *gem; + struct mtk_drm_gem_obj *mtk_gem; + unsigned int pitch, format; + dma_addr_t addr; + + if (!crtc || WARN_ON(!fb)) + return; + + if (!plane->state->visible) { + mtk_plane_atomic_disable(plane, old_state); + return; + } + + 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 += (plane->state->src.x1 >> 16) * fb->format->cpp[0]; + addr += (plane->state->src.y1 >> 16) * pitch; + + state->pending.enable = true; + state->pending.pitch = pitch; + state->pending.format = format; + state->pending.addr = addr; + state->pending.x = plane->state->dst.x1; + state->pending.y = plane->state->dst.y1; + state->pending.width = drm_rect_width(&plane->state->dst); + state->pending.height = drm_rect_height(&plane->state->dst); + state->pending.rotation = plane->state->rotation; + wmb(); /* Make sure the above parameters are set before update */ + state->pending.dirty = true; +} + +static const struct drm_plane_helper_funcs mtk_plane_helper_funcs = { + .prepare_fb = drm_gem_fb_prepare_fb, + .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..d454bece9 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.h @@ -0,0 +1,44 @@ +/* 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; +}; + +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..a6e71b7b6 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c @@ -0,0 +1,1238 @@ +// 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 <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_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 mtk_ddp_comp ddp_comp; + 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; + } + + tmp_reg |= (dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) << 6; + tmp_reg |= (dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET) >> 3; + + 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; + + 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"); + } + + 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, +}; + +static void mtk_dsi_ddp_start(struct mtk_ddp_comp *comp) +{ + struct mtk_dsi *dsi = container_of(comp, struct mtk_dsi, ddp_comp); + + mtk_dsi_poweron(dsi); +} + +static void mtk_dsi_ddp_stop(struct mtk_ddp_comp *comp) +{ + struct mtk_dsi *dsi = container_of(comp, struct mtk_dsi, ddp_comp); + + mtk_dsi_poweroff(dsi); +} + +static const struct mtk_ddp_comp_funcs mtk_dsi_funcs = { + .start = mtk_dsi_ddp_start, + .stop = mtk_dsi_ddp_stop, +}; + +static int mtk_dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct mtk_dsi *dsi = host_to_dsi(host); + + dsi->lanes = device->lanes; + dsi->format = device->format; + dsi->mode_flags = device->mode_flags; + + 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, + .transfer = mtk_dsi_host_transfer, +}; + +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->ddp_comp); + + 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_ddp_comp_register(drm, &dsi->ddp_comp); + if (ret < 0) { + dev_err(dev, "Failed to register component %pOF: %d\n", + dev->of_node, ret); + return ret; + } + + ret = mtk_dsi_encoder_init(drm, dsi); + if (ret) + goto err_unregister; + + return 0; + +err_unregister: + mtk_ddp_comp_unregister(drm, &dsi->ddp_comp); + return ret; +} + +static void mtk_dsi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct drm_device *drm = data; + struct mtk_dsi *dsi = dev_get_drvdata(dev); + + drm_encoder_cleanup(&dsi->encoder); + mtk_ddp_comp_unregister(drm, &dsi->ddp_comp); +} + +static const struct component_ops mtk_dsi_component_ops = { + .bind = mtk_dsi_bind, + .unbind = mtk_dsi_unbind, +}; + +static int mtk_dsi_probe(struct platform_device *pdev) +{ + struct mtk_dsi *dsi; + struct device *dev = &pdev->dev; + struct drm_panel *panel; + struct resource *regs; + int irq_num; + int comp_id; + 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; + } + + ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0, + &panel, &dsi->next_bridge); + if (ret) + goto err_unregister_host; + + if (panel) { + dsi->next_bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(dsi->next_bridge)) { + ret = PTR_ERR(dsi->next_bridge); + goto err_unregister_host; + } + } + + 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; + } + + comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DSI); + if (comp_id < 0) { + dev_err(dev, "Failed to identify by alias: %d\n", comp_id); + ret = comp_id; + goto err_unregister_host; + } + + ret = mtk_ddp_comp_init(dev, dev->of_node, &dsi->ddp_comp, comp_id, + &mtk_dsi_funcs); + if (ret) { + dev_err(dev, "Failed to initialize component: %d\n", ret); + goto err_unregister_host; + } + + irq_num = platform_get_irq(pdev, 0); + if (irq_num < 0) { + dev_err(&pdev->dev, "failed to get dsi irq_num: %d\n", irq_num); + ret = irq_num; + goto err_unregister_host; + } + + irq_set_status_flags(irq_num, IRQ_TYPE_LEVEL_LOW); + ret = devm_request_irq(&pdev->dev, irq_num, mtk_dsi_irq, + IRQF_TRIGGER_LOW, 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; + + drm_bridge_add(&dsi->bridge); + + ret = component_add(&pdev->dev, &mtk_dsi_component_ops); + if (ret) { + dev_err(&pdev->dev, "failed to add component: %d\n", ret); + goto err_unregister_host; + } + + 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); + drm_bridge_remove(&dsi->bridge); + component_del(&pdev->dev, &mtk_dsi_component_ops); + 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 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 }, + { }, +}; + +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..97a1ff529 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi.c @@ -0,0 +1,1868 @@ +// 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_gpio.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; +}; + +struct mtk_hdmi { + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + struct drm_connector conn; + 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 inline struct mtk_hdmi *hdmi_ctx_from_conn(struct drm_connector *c) +{ + return container_of(c, struct mtk_hdmi, conn); +} + +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 int mtk_hdmi_aud_enable_packet(struct mtk_hdmi *hdmi, bool enable) +{ + mtk_hdmi_hw_send_aud_packet(hdmi, enable); + return 0; +} + +static int mtk_hdmi_aud_on_off_hw_ncts(struct mtk_hdmi *hdmi, bool on) +{ + mtk_hdmi_hw_ncts_enable(hdmi, on); + return 0; +} + +static int 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); + + return 0; +} + +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_aud_on_off_hw_ncts(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_aud_enable_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_aud_on_off_hw_ncts(hdmi, true); + mtk_hdmi_aud_enable_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->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->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_aud_enable_packet(hdmi, true); + hdmi->audio_enable = true; +} + +static void mtk_hdmi_audio_disable(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_aud_enable_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 hdmi_conn_detect(struct drm_connector *conn, + bool force) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn); + return mtk_hdmi_update_plugged_status(hdmi); +} + +static void hdmi_conn_destroy(struct drm_connector *conn) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn); + + mtk_cec_set_hpd_event(hdmi->cec_dev, NULL, NULL); + + drm_connector_cleanup(conn); +} + +static int mtk_hdmi_conn_get_modes(struct drm_connector *conn) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn); + struct edid *edid; + int ret; + + if (!hdmi->ddc_adpt) + return -ENODEV; + + edid = drm_get_edid(conn, hdmi->ddc_adpt); + if (!edid) + return -ENODEV; + + hdmi->dvi_mode = !drm_detect_monitor_audio(edid); + + drm_connector_update_edid_property(conn, edid); + + ret = drm_add_edid_modes(conn, edid); + kfree(edid); + return ret; +} + +static int mtk_hdmi_conn_mode_valid(struct drm_connector *conn, + struct drm_display_mode *mode) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn); + 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 (mode->clock < 27000) + return MODE_CLOCK_LOW; + if (mode->clock > 297000) + return MODE_CLOCK_HIGH; + + return drm_mode_validate_size(mode, 0x1fff, 0x1fff); +} + +static struct drm_encoder *mtk_hdmi_conn_best_enc(struct drm_connector *conn) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn); + + return hdmi->bridge.encoder; +} + +static const struct drm_connector_funcs mtk_hdmi_connector_funcs = { + .detect = hdmi_conn_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = hdmi_conn_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_connector_helper_funcs + mtk_hdmi_connector_helper_funcs = { + .get_modes = mtk_hdmi_conn_get_modes, + .mode_valid = mtk_hdmi_conn_mode_valid, + .best_encoder = mtk_hdmi_conn_best_enc, +}; + +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) + drm_helper_hpd_irq_event(hdmi->bridge.encoder->dev); +} + +/* + * Bridge callbacks + */ + +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("Fix bridge driver to make connector optional!"); + return -EINVAL; + } + + ret = drm_connector_init_with_ddc(bridge->encoder->dev, &hdmi->conn, + &mtk_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA, + hdmi->ddc_adpt); + if (ret) { + dev_err(hdmi->dev, "Failed to initialize connector: %d\n", ret); + return ret; + } + drm_connector_helper_add(&hdmi->conn, &mtk_hdmi_connector_helper_funcs); + + hdmi->conn.polled = DRM_CONNECTOR_POLL_HPD; + hdmi->conn.interlace_allowed = true; + hdmi->conn.doublescan_allowed = false; + + ret = drm_connector_attach_encoder(&hdmi->conn, + bridge->encoder); + if (ret) { + dev_err(hdmi->dev, + "Failed to attach connector to encoder: %d\n", ret); + return ret; + } + + if (hdmi->next_bridge) { + ret = drm_bridge_attach(bridge->encoder, hdmi->next_bridge, + bridge, flags); + if (ret) { + dev_err(hdmi->dev, + "Failed to attach external bridge: %d\n", 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_disable(struct drm_bridge *bridge) +{ + 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->enabled = false; +} + +static void mtk_hdmi_bridge_post_disable(struct drm_bridge *bridge) +{ + 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_pre_enable(struct drm_bridge *bridge) +{ + 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_enable(struct drm_bridge *bridge) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + + 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 = { + .attach = mtk_hdmi_bridge_attach, + .mode_fixup = mtk_hdmi_bridge_mode_fixup, + .disable = mtk_hdmi_bridge_disable, + .post_disable = mtk_hdmi_bridge_post_disable, + .mode_set = mtk_hdmi_bridge_mode_set, + .pre_enable = mtk_hdmi_bridge_pre_enable, + .enable = mtk_hdmi_bridge_enable, +}; + +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); + + memcpy(buf, hdmi->conn.eld, min(sizeof(hdmi->conn.eld), 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; + 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 of_device_id mtk_drm_hdmi_of_ids[] = { + { .compatible = "mediatek,mt2701-hdmi", + .data = &mtk_hdmi_conf_mt2701, + }, + { .compatible = "mediatek,mt8173-hdmi", + }, + {} +}; + +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..62dbad567 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc.c @@ -0,0 +1,350 @@ +// 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", }, + {}, +}; + +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_mipi_tx.c b/drivers/gpu/drm/mediatek/mtk_mipi_tx.c new file mode 100644 index 000000000..ccc742dc7 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_mipi_tx.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 MediaTek Inc. + */ + +#include "mtk_mipi_tx.h" + +inline struct mtk_mipi_tx *mtk_mipi_tx_from_clk_hw(struct clk_hw *hw) +{ + return container_of(hw, struct mtk_mipi_tx, pll_hw); +} + +void mtk_mipi_tx_clear_bits(struct mtk_mipi_tx *mipi_tx, u32 offset, + u32 bits) +{ + u32 temp = readl(mipi_tx->regs + offset); + + writel(temp & ~bits, mipi_tx->regs + offset); +} + +void mtk_mipi_tx_set_bits(struct mtk_mipi_tx *mipi_tx, u32 offset, + u32 bits) +{ + u32 temp = readl(mipi_tx->regs + offset); + + writel(temp | bits, mipi_tx->regs + offset); +} + +void mtk_mipi_tx_update_bits(struct mtk_mipi_tx *mipi_tx, u32 offset, + u32 mask, u32 data) +{ + u32 temp = readl(mipi_tx->regs + offset); + + writel((temp & ~mask) | (data & mask), mipi_tx->regs + offset); +} + +int mtk_mipi_tx_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw); + + dev_dbg(mipi_tx->dev, "set rate: %lu Hz\n", rate); + + mipi_tx->data_rate = rate; + + return 0; +} + +unsigned long mtk_mipi_tx_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw); + + return mipi_tx->data_rate; +} + +static int mtk_mipi_tx_power_on(struct phy *phy) +{ + struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy); + int ret; + + /* Power up core and enable PLL */ + ret = clk_prepare_enable(mipi_tx->pll); + if (ret < 0) + return ret; + + /* Enable DSI Lane LDO outputs, disable pad tie low */ + mipi_tx->driver_data->mipi_tx_enable_signal(phy); + return 0; +} + +static int mtk_mipi_tx_power_off(struct phy *phy) +{ + struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy); + + /* Enable pad tie low, disable DSI Lane LDO outputs */ + mipi_tx->driver_data->mipi_tx_disable_signal(phy); + + /* Disable PLL and power down core */ + clk_disable_unprepare(mipi_tx->pll); + + return 0; +} + +static const struct phy_ops mtk_mipi_tx_ops = { + .power_on = mtk_mipi_tx_power_on, + .power_off = mtk_mipi_tx_power_off, + .owner = THIS_MODULE, +}; + +static void mtk_mipi_tx_get_calibration_datal(struct mtk_mipi_tx *mipi_tx) +{ + struct nvmem_cell *cell; + size_t len; + u32 *buf; + + cell = nvmem_cell_get(mipi_tx->dev, "calibration-data"); + if (IS_ERR(cell)) { + dev_info(mipi_tx->dev, "can't get nvmem_cell_get, ignore it\n"); + return; + } + buf = (u32 *)nvmem_cell_read(cell, &len); + nvmem_cell_put(cell); + + if (IS_ERR(buf)) { + dev_info(mipi_tx->dev, "can't get data, ignore it\n"); + return; + } + + if (len < 3 * sizeof(u32)) { + dev_info(mipi_tx->dev, "invalid calibration data\n"); + kfree(buf); + return; + } + + mipi_tx->rt_code[0] = ((buf[0] >> 6 & 0x1f) << 5) | + (buf[0] >> 11 & 0x1f); + mipi_tx->rt_code[1] = ((buf[1] >> 27 & 0x1f) << 5) | + (buf[0] >> 1 & 0x1f); + mipi_tx->rt_code[2] = ((buf[1] >> 17 & 0x1f) << 5) | + (buf[1] >> 22 & 0x1f); + mipi_tx->rt_code[3] = ((buf[1] >> 7 & 0x1f) << 5) | + (buf[1] >> 12 & 0x1f); + mipi_tx->rt_code[4] = ((buf[2] >> 27 & 0x1f) << 5) | + (buf[1] >> 2 & 0x1f); + kfree(buf); +} + +static int mtk_mipi_tx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_mipi_tx *mipi_tx; + struct resource *mem; + const char *ref_clk_name; + struct clk *ref_clk; + struct clk_init_data clk_init = { + .num_parents = 1, + .parent_names = (const char * const *)&ref_clk_name, + .flags = CLK_SET_RATE_GATE, + }; + struct phy *phy; + struct phy_provider *phy_provider; + int ret; + + mipi_tx = devm_kzalloc(dev, sizeof(*mipi_tx), GFP_KERNEL); + if (!mipi_tx) + return -ENOMEM; + + mipi_tx->driver_data = of_device_get_match_data(dev); + if (!mipi_tx->driver_data) + return -ENODEV; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mipi_tx->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(mipi_tx->regs)) { + ret = PTR_ERR(mipi_tx->regs); + dev_err(dev, "Failed to get memory resource: %d\n", ret); + return ret; + } + + ref_clk = devm_clk_get(dev, NULL); + if (IS_ERR(ref_clk)) { + ret = PTR_ERR(ref_clk); + dev_err(dev, "Failed to get reference clock: %d\n", ret); + return ret; + } + + ret = of_property_read_u32(dev->of_node, "drive-strength-microamp", + &mipi_tx->mipitx_drive); + /* If can't get the "mipi_tx->mipitx_drive", set it default 0x8 */ + if (ret < 0) + mipi_tx->mipitx_drive = 4600; + + /* check the mipitx_drive valid */ + if (mipi_tx->mipitx_drive > 6000 || mipi_tx->mipitx_drive < 3000) { + dev_warn(dev, "drive-strength-microamp is invalid %d, not in 3000 ~ 6000\n", + mipi_tx->mipitx_drive); + mipi_tx->mipitx_drive = clamp_val(mipi_tx->mipitx_drive, 3000, + 6000); + } + + ref_clk_name = __clk_get_name(ref_clk); + + ret = of_property_read_string(dev->of_node, "clock-output-names", + &clk_init.name); + if (ret < 0) { + dev_err(dev, "Failed to read clock-output-names: %d\n", ret); + return ret; + } + + clk_init.ops = mipi_tx->driver_data->mipi_tx_clk_ops; + + mipi_tx->pll_hw.init = &clk_init; + mipi_tx->pll = devm_clk_register(dev, &mipi_tx->pll_hw); + if (IS_ERR(mipi_tx->pll)) { + ret = PTR_ERR(mipi_tx->pll); + dev_err(dev, "Failed to register PLL: %d\n", ret); + return ret; + } + + phy = devm_phy_create(dev, NULL, &mtk_mipi_tx_ops); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + dev_err(dev, "Failed to create MIPI D-PHY: %d\n", ret); + return ret; + } + phy_set_drvdata(phy, mipi_tx); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + ret = PTR_ERR(phy_provider); + return ret; + } + + mipi_tx->dev = dev; + + mtk_mipi_tx_get_calibration_datal(mipi_tx); + + return of_clk_add_provider(dev->of_node, of_clk_src_simple_get, + mipi_tx->pll); +} + +static int mtk_mipi_tx_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + return 0; +} + +static const struct of_device_id mtk_mipi_tx_match[] = { + { .compatible = "mediatek,mt2701-mipi-tx", + .data = &mt2701_mipitx_data }, + { .compatible = "mediatek,mt8173-mipi-tx", + .data = &mt8173_mipitx_data }, + { .compatible = "mediatek,mt8183-mipi-tx", + .data = &mt8183_mipitx_data }, + { }, +}; + +struct platform_driver mtk_mipi_tx_driver = { + .probe = mtk_mipi_tx_probe, + .remove = mtk_mipi_tx_remove, + .driver = { + .name = "mediatek-mipi-tx", + .of_match_table = mtk_mipi_tx_match, + }, +}; + diff --git a/drivers/gpu/drm/mediatek/mtk_mipi_tx.h b/drivers/gpu/drm/mediatek/mtk_mipi_tx.h new file mode 100644 index 000000000..c76f07c3f --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_mipi_tx.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019 MediaTek Inc. + * Author: Jitao Shi <jitao.shi@mediatek.com> + */ + +#ifndef _MTK_MIPI_TX_H +#define _MTK_MIPI_TX_H + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-consumer.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.h> +#include <linux/slab.h> + +struct mtk_mipitx_data { + const u32 mppll_preserve; + const struct clk_ops *mipi_tx_clk_ops; + void (*mipi_tx_enable_signal)(struct phy *phy); + void (*mipi_tx_disable_signal)(struct phy *phy); +}; + +struct mtk_mipi_tx { + struct device *dev; + void __iomem *regs; + u32 data_rate; + u32 mipitx_drive; + u32 rt_code[5]; + const struct mtk_mipitx_data *driver_data; + struct clk_hw pll_hw; + struct clk *pll; +}; + +struct mtk_mipi_tx *mtk_mipi_tx_from_clk_hw(struct clk_hw *hw); +void mtk_mipi_tx_clear_bits(struct mtk_mipi_tx *mipi_tx, u32 offset, u32 bits); +void mtk_mipi_tx_set_bits(struct mtk_mipi_tx *mipi_tx, u32 offset, u32 bits); +void mtk_mipi_tx_update_bits(struct mtk_mipi_tx *mipi_tx, u32 offset, u32 mask, + u32 data); +int mtk_mipi_tx_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate); +unsigned long mtk_mipi_tx_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate); + +extern const struct mtk_mipitx_data mt2701_mipitx_data; +extern const struct mtk_mipitx_data mt8173_mipitx_data; +extern const struct mtk_mipitx_data mt8183_mipitx_data; + +#endif diff --git a/drivers/gpu/drm/mediatek/mtk_mt8173_mipi_tx.c b/drivers/gpu/drm/mediatek/mtk_mt8173_mipi_tx.c new file mode 100644 index 000000000..f18db14d8 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_mt8173_mipi_tx.c @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 MediaTek Inc. + * Author: jitao.shi <jitao.shi@mediatek.com> + */ + +#include "mtk_mipi_tx.h" + +#define MIPITX_DSI_CON 0x00 +#define RG_DSI_LDOCORE_EN BIT(0) +#define RG_DSI_CKG_LDOOUT_EN BIT(1) +#define RG_DSI_BCLK_SEL (3 << 2) +#define RG_DSI_LD_IDX_SEL (7 << 4) +#define RG_DSI_PHYCLK_SEL (2 << 8) +#define RG_DSI_DSICLK_FREQ_SEL BIT(10) +#define RG_DSI_LPTX_CLMP_EN BIT(11) + +#define MIPITX_DSI_CLOCK_LANE 0x04 +#define MIPITX_DSI_DATA_LANE0 0x08 +#define MIPITX_DSI_DATA_LANE1 0x0c +#define MIPITX_DSI_DATA_LANE2 0x10 +#define MIPITX_DSI_DATA_LANE3 0x14 +#define RG_DSI_LNTx_LDOOUT_EN BIT(0) +#define RG_DSI_LNTx_CKLANE_EN BIT(1) +#define RG_DSI_LNTx_LPTX_IPLUS1 BIT(2) +#define RG_DSI_LNTx_LPTX_IPLUS2 BIT(3) +#define RG_DSI_LNTx_LPTX_IMINUS BIT(4) +#define RG_DSI_LNTx_LPCD_IPLUS BIT(5) +#define RG_DSI_LNTx_LPCD_IMINUS BIT(6) +#define RG_DSI_LNTx_RT_CODE (0xf << 8) + +#define MIPITX_DSI_TOP_CON 0x40 +#define RG_DSI_LNT_INTR_EN BIT(0) +#define RG_DSI_LNT_HS_BIAS_EN BIT(1) +#define RG_DSI_LNT_IMP_CAL_EN BIT(2) +#define RG_DSI_LNT_TESTMODE_EN BIT(3) +#define RG_DSI_LNT_IMP_CAL_CODE (0xf << 4) +#define RG_DSI_LNT_AIO_SEL (7 << 8) +#define RG_DSI_PAD_TIE_LOW_EN BIT(11) +#define RG_DSI_DEBUG_INPUT_EN BIT(12) +#define RG_DSI_PRESERVE (7 << 13) + +#define MIPITX_DSI_BG_CON 0x44 +#define RG_DSI_BG_CORE_EN BIT(0) +#define RG_DSI_BG_CKEN BIT(1) +#define RG_DSI_BG_DIV (0x3 << 2) +#define RG_DSI_BG_FAST_CHARGE BIT(4) +#define RG_DSI_VOUT_MSK (0x3ffff << 5) +#define RG_DSI_V12_SEL (7 << 5) +#define RG_DSI_V10_SEL (7 << 8) +#define RG_DSI_V072_SEL (7 << 11) +#define RG_DSI_V04_SEL (7 << 14) +#define RG_DSI_V032_SEL (7 << 17) +#define RG_DSI_V02_SEL (7 << 20) +#define RG_DSI_BG_R1_TRIM (0xf << 24) +#define RG_DSI_BG_R2_TRIM (0xf << 28) + +#define MIPITX_DSI_PLL_CON0 0x50 +#define RG_DSI_MPPLL_PLL_EN BIT(0) +#define RG_DSI_MPPLL_DIV_MSK (0x1ff << 1) +#define RG_DSI_MPPLL_PREDIV (3 << 1) +#define RG_DSI_MPPLL_TXDIV0 (3 << 3) +#define RG_DSI_MPPLL_TXDIV1 (3 << 5) +#define RG_DSI_MPPLL_POSDIV (7 << 7) +#define RG_DSI_MPPLL_MONVC_EN BIT(10) +#define RG_DSI_MPPLL_MONREF_EN BIT(11) +#define RG_DSI_MPPLL_VOD_EN BIT(12) + +#define MIPITX_DSI_PLL_CON1 0x54 +#define RG_DSI_MPPLL_SDM_FRA_EN BIT(0) +#define RG_DSI_MPPLL_SDM_SSC_PH_INIT BIT(1) +#define RG_DSI_MPPLL_SDM_SSC_EN BIT(2) +#define RG_DSI_MPPLL_SDM_SSC_PRD (0xffff << 16) + +#define MIPITX_DSI_PLL_CON2 0x58 + +#define MIPITX_DSI_PLL_TOP 0x64 +#define RG_DSI_MPPLL_PRESERVE (0xff << 8) + +#define MIPITX_DSI_PLL_PWR 0x68 +#define RG_DSI_MPPLL_SDM_PWR_ON BIT(0) +#define RG_DSI_MPPLL_SDM_ISO_EN BIT(1) +#define RG_DSI_MPPLL_SDM_PWR_ACK BIT(8) + +#define MIPITX_DSI_SW_CTRL 0x80 +#define SW_CTRL_EN BIT(0) + +#define MIPITX_DSI_SW_CTRL_CON0 0x84 +#define SW_LNTC_LPTX_PRE_OE BIT(0) +#define SW_LNTC_LPTX_OE BIT(1) +#define SW_LNTC_LPTX_P BIT(2) +#define SW_LNTC_LPTX_N BIT(3) +#define SW_LNTC_HSTX_PRE_OE BIT(4) +#define SW_LNTC_HSTX_OE BIT(5) +#define SW_LNTC_HSTX_ZEROCLK BIT(6) +#define SW_LNT0_LPTX_PRE_OE BIT(7) +#define SW_LNT0_LPTX_OE BIT(8) +#define SW_LNT0_LPTX_P BIT(9) +#define SW_LNT0_LPTX_N BIT(10) +#define SW_LNT0_HSTX_PRE_OE BIT(11) +#define SW_LNT0_HSTX_OE BIT(12) +#define SW_LNT0_LPRX_EN BIT(13) +#define SW_LNT1_LPTX_PRE_OE BIT(14) +#define SW_LNT1_LPTX_OE BIT(15) +#define SW_LNT1_LPTX_P BIT(16) +#define SW_LNT1_LPTX_N BIT(17) +#define SW_LNT1_HSTX_PRE_OE BIT(18) +#define SW_LNT1_HSTX_OE BIT(19) +#define SW_LNT2_LPTX_PRE_OE BIT(20) +#define SW_LNT2_LPTX_OE BIT(21) +#define SW_LNT2_LPTX_P BIT(22) +#define SW_LNT2_LPTX_N BIT(23) +#define SW_LNT2_HSTX_PRE_OE BIT(24) +#define SW_LNT2_HSTX_OE BIT(25) + +static int mtk_mipi_tx_pll_prepare(struct clk_hw *hw) +{ + struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw); + u8 txdiv, txdiv0, txdiv1; + u64 pcw; + + dev_dbg(mipi_tx->dev, "prepare: %u Hz\n", mipi_tx->data_rate); + + if (mipi_tx->data_rate >= 500000000) { + txdiv = 1; + txdiv0 = 0; + txdiv1 = 0; + } else if (mipi_tx->data_rate >= 250000000) { + txdiv = 2; + txdiv0 = 1; + txdiv1 = 0; + } else if (mipi_tx->data_rate >= 125000000) { + txdiv = 4; + txdiv0 = 2; + txdiv1 = 0; + } else if (mipi_tx->data_rate > 62000000) { + txdiv = 8; + txdiv0 = 2; + txdiv1 = 1; + } else if (mipi_tx->data_rate >= 50000000) { + txdiv = 16; + txdiv0 = 2; + txdiv1 = 2; + } else { + return -EINVAL; + } + + mtk_mipi_tx_update_bits(mipi_tx, MIPITX_DSI_BG_CON, + RG_DSI_VOUT_MSK | + RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN, + (4 << 20) | (4 << 17) | (4 << 14) | + (4 << 11) | (4 << 8) | (4 << 5) | + RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN); + + usleep_range(30, 100); + + mtk_mipi_tx_update_bits(mipi_tx, MIPITX_DSI_TOP_CON, + RG_DSI_LNT_IMP_CAL_CODE | RG_DSI_LNT_HS_BIAS_EN, + (8 << 4) | RG_DSI_LNT_HS_BIAS_EN); + + mtk_mipi_tx_set_bits(mipi_tx, MIPITX_DSI_CON, + RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN); + + mtk_mipi_tx_update_bits(mipi_tx, MIPITX_DSI_PLL_PWR, + RG_DSI_MPPLL_SDM_PWR_ON | + RG_DSI_MPPLL_SDM_ISO_EN, + RG_DSI_MPPLL_SDM_PWR_ON); + + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_PLL_CON0, + RG_DSI_MPPLL_PLL_EN); + + mtk_mipi_tx_update_bits(mipi_tx, MIPITX_DSI_PLL_CON0, + RG_DSI_MPPLL_TXDIV0 | RG_DSI_MPPLL_TXDIV1 | + RG_DSI_MPPLL_PREDIV, + (txdiv0 << 3) | (txdiv1 << 5)); + + /* + * PLL PCW config + * PCW bit 24~30 = integer part of pcw + * PCW bit 0~23 = fractional part of pcw + * pcw = data_Rate*4*txdiv/(Ref_clk*2); + * Post DIV =4, so need data_Rate*4 + * Ref_clk is 26MHz + */ + pcw = div_u64(((u64)mipi_tx->data_rate * 2 * txdiv) << 24, + 26000000); + writel(pcw, mipi_tx->regs + MIPITX_DSI_PLL_CON2); + + mtk_mipi_tx_set_bits(mipi_tx, MIPITX_DSI_PLL_CON1, + RG_DSI_MPPLL_SDM_FRA_EN); + + mtk_mipi_tx_set_bits(mipi_tx, MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_PLL_EN); + + usleep_range(20, 100); + + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_PLL_CON1, + RG_DSI_MPPLL_SDM_SSC_EN); + + mtk_mipi_tx_update_bits(mipi_tx, MIPITX_DSI_PLL_TOP, + RG_DSI_MPPLL_PRESERVE, + mipi_tx->driver_data->mppll_preserve); + + return 0; +} + +static void mtk_mipi_tx_pll_unprepare(struct clk_hw *hw) +{ + struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw); + + dev_dbg(mipi_tx->dev, "unprepare\n"); + + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_PLL_CON0, + RG_DSI_MPPLL_PLL_EN); + + mtk_mipi_tx_update_bits(mipi_tx, MIPITX_DSI_PLL_TOP, + RG_DSI_MPPLL_PRESERVE, 0); + + mtk_mipi_tx_update_bits(mipi_tx, MIPITX_DSI_PLL_PWR, + RG_DSI_MPPLL_SDM_ISO_EN | + RG_DSI_MPPLL_SDM_PWR_ON, + RG_DSI_MPPLL_SDM_ISO_EN); + + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_TOP_CON, + RG_DSI_LNT_HS_BIAS_EN); + + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_CON, + RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN); + + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_BG_CON, + RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN); + + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_PLL_CON0, + RG_DSI_MPPLL_DIV_MSK); +} + +static long mtk_mipi_tx_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + return clamp_val(rate, 50000000, 1250000000); +} + +static const struct clk_ops mtk_mipi_tx_pll_ops = { + .prepare = mtk_mipi_tx_pll_prepare, + .unprepare = mtk_mipi_tx_pll_unprepare, + .round_rate = mtk_mipi_tx_pll_round_rate, + .set_rate = mtk_mipi_tx_pll_set_rate, + .recalc_rate = mtk_mipi_tx_pll_recalc_rate, +}; + +static void mtk_mipi_tx_power_on_signal(struct phy *phy) +{ + struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy); + u32 reg; + + for (reg = MIPITX_DSI_CLOCK_LANE; + reg <= MIPITX_DSI_DATA_LANE3; reg += 4) + mtk_mipi_tx_set_bits(mipi_tx, reg, RG_DSI_LNTx_LDOOUT_EN); + + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_TOP_CON, + RG_DSI_PAD_TIE_LOW_EN); +} + +static void mtk_mipi_tx_power_off_signal(struct phy *phy) +{ + struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy); + u32 reg; + + mtk_mipi_tx_set_bits(mipi_tx, MIPITX_DSI_TOP_CON, + RG_DSI_PAD_TIE_LOW_EN); + + for (reg = MIPITX_DSI_CLOCK_LANE; + reg <= MIPITX_DSI_DATA_LANE3; reg += 4) + mtk_mipi_tx_clear_bits(mipi_tx, reg, RG_DSI_LNTx_LDOOUT_EN); +} + +const struct mtk_mipitx_data mt2701_mipitx_data = { + .mppll_preserve = (3 << 8), + .mipi_tx_clk_ops = &mtk_mipi_tx_pll_ops, + .mipi_tx_enable_signal = mtk_mipi_tx_power_on_signal, + .mipi_tx_disable_signal = mtk_mipi_tx_power_off_signal, +}; + +const struct mtk_mipitx_data mt8173_mipitx_data = { + .mppll_preserve = (0 << 8), + .mipi_tx_clk_ops = &mtk_mipi_tx_pll_ops, + .mipi_tx_enable_signal = mtk_mipi_tx_power_on_signal, + .mipi_tx_disable_signal = mtk_mipi_tx_power_off_signal, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_mt8183_mipi_tx.c b/drivers/gpu/drm/mediatek/mtk_mt8183_mipi_tx.c new file mode 100644 index 000000000..9f3e55aee --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_mt8183_mipi_tx.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 MediaTek Inc. + * Author: jitao.shi <jitao.shi@mediatek.com> + */ + +#include "mtk_mipi_tx.h" + +#define MIPITX_LANE_CON 0x000c +#define RG_DSI_CPHY_T1DRV_EN BIT(0) +#define RG_DSI_ANA_CK_SEL BIT(1) +#define RG_DSI_PHY_CK_SEL BIT(2) +#define RG_DSI_CPHY_EN BIT(3) +#define RG_DSI_PHYCK_INV_EN BIT(4) +#define RG_DSI_PWR04_EN BIT(5) +#define RG_DSI_BG_LPF_EN BIT(6) +#define RG_DSI_BG_CORE_EN BIT(7) +#define RG_DSI_PAD_TIEL_SEL BIT(8) + +#define MIPITX_VOLTAGE_SEL 0x0010 +#define RG_DSI_HSTX_LDO_REF_SEL (0xf << 6) + +#define MIPITX_PLL_PWR 0x0028 +#define MIPITX_PLL_CON0 0x002c +#define MIPITX_PLL_CON1 0x0030 +#define MIPITX_PLL_CON2 0x0034 +#define MIPITX_PLL_CON3 0x0038 +#define MIPITX_PLL_CON4 0x003c +#define RG_DSI_PLL_IBIAS (3 << 10) + +#define MIPITX_D2P_RTCODE 0x0100 +#define MIPITX_D2_SW_CTL_EN 0x0144 +#define MIPITX_D0_SW_CTL_EN 0x0244 +#define MIPITX_CK_CKMODE_EN 0x0328 +#define DSI_CK_CKMODE_EN BIT(0) +#define MIPITX_CK_SW_CTL_EN 0x0344 +#define MIPITX_D1_SW_CTL_EN 0x0444 +#define MIPITX_D3_SW_CTL_EN 0x0544 +#define DSI_SW_CTL_EN BIT(0) +#define AD_DSI_PLL_SDM_PWR_ON BIT(0) +#define AD_DSI_PLL_SDM_ISO_EN BIT(1) + +#define RG_DSI_PLL_EN BIT(4) +#define RG_DSI_PLL_POSDIV (0x7 << 8) + +static int mtk_mipi_tx_pll_enable(struct clk_hw *hw) +{ + struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw); + unsigned int txdiv, txdiv0; + u64 pcw; + + dev_dbg(mipi_tx->dev, "enable: %u bps\n", mipi_tx->data_rate); + + if (mipi_tx->data_rate >= 2000000000) { + txdiv = 1; + txdiv0 = 0; + } else if (mipi_tx->data_rate >= 1000000000) { + txdiv = 2; + txdiv0 = 1; + } else if (mipi_tx->data_rate >= 500000000) { + txdiv = 4; + txdiv0 = 2; + } else if (mipi_tx->data_rate > 250000000) { + txdiv = 8; + txdiv0 = 3; + } else if (mipi_tx->data_rate >= 125000000) { + txdiv = 16; + txdiv0 = 4; + } else { + return -EINVAL; + } + + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_PLL_CON4, RG_DSI_PLL_IBIAS); + + mtk_mipi_tx_set_bits(mipi_tx, MIPITX_PLL_PWR, AD_DSI_PLL_SDM_PWR_ON); + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_PLL_CON1, RG_DSI_PLL_EN); + udelay(1); + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_PLL_PWR, AD_DSI_PLL_SDM_ISO_EN); + pcw = div_u64(((u64)mipi_tx->data_rate * txdiv) << 24, 26000000); + writel(pcw, mipi_tx->regs + MIPITX_PLL_CON0); + mtk_mipi_tx_update_bits(mipi_tx, MIPITX_PLL_CON1, RG_DSI_PLL_POSDIV, + txdiv0 << 8); + mtk_mipi_tx_set_bits(mipi_tx, MIPITX_PLL_CON1, RG_DSI_PLL_EN); + + return 0; +} + +static void mtk_mipi_tx_pll_disable(struct clk_hw *hw) +{ + struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw); + + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_PLL_CON1, RG_DSI_PLL_EN); + + mtk_mipi_tx_set_bits(mipi_tx, MIPITX_PLL_PWR, AD_DSI_PLL_SDM_ISO_EN); + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_PLL_PWR, AD_DSI_PLL_SDM_PWR_ON); +} + +static long mtk_mipi_tx_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + return clamp_val(rate, 50000000, 1600000000); +} + +static const struct clk_ops mtk_mipi_tx_pll_ops = { + .enable = mtk_mipi_tx_pll_enable, + .disable = mtk_mipi_tx_pll_disable, + .round_rate = mtk_mipi_tx_pll_round_rate, + .set_rate = mtk_mipi_tx_pll_set_rate, + .recalc_rate = mtk_mipi_tx_pll_recalc_rate, +}; + +static void mtk_mipi_tx_config_calibration_data(struct mtk_mipi_tx *mipi_tx) +{ + int i, j; + + for (i = 0; i < 5; i++) { + if ((mipi_tx->rt_code[i] & 0x1f) == 0) + mipi_tx->rt_code[i] |= 0x10; + + if ((mipi_tx->rt_code[i] >> 5 & 0x1f) == 0) + mipi_tx->rt_code[i] |= 0x10 << 5; + + for (j = 0; j < 10; j++) + mtk_mipi_tx_update_bits(mipi_tx, + MIPITX_D2P_RTCODE * (i + 1) + j * 4, + 1, mipi_tx->rt_code[i] >> j & 1); + } +} + +static void mtk_mipi_tx_power_on_signal(struct phy *phy) +{ + struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy); + + /* BG_LPF_EN / BG_CORE_EN */ + writel(RG_DSI_PAD_TIEL_SEL | RG_DSI_BG_CORE_EN, + mipi_tx->regs + MIPITX_LANE_CON); + usleep_range(30, 100); + writel(RG_DSI_BG_CORE_EN | RG_DSI_BG_LPF_EN, + mipi_tx->regs + MIPITX_LANE_CON); + + /* Switch OFF each Lane */ + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_D0_SW_CTL_EN, DSI_SW_CTL_EN); + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_D1_SW_CTL_EN, DSI_SW_CTL_EN); + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_D2_SW_CTL_EN, DSI_SW_CTL_EN); + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_D3_SW_CTL_EN, DSI_SW_CTL_EN); + mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_CK_SW_CTL_EN, DSI_SW_CTL_EN); + + mtk_mipi_tx_update_bits(mipi_tx, MIPITX_VOLTAGE_SEL, + RG_DSI_HSTX_LDO_REF_SEL, + (mipi_tx->mipitx_drive - 3000) / 200 << 6); + + mtk_mipi_tx_config_calibration_data(mipi_tx); + + mtk_mipi_tx_set_bits(mipi_tx, MIPITX_CK_CKMODE_EN, DSI_CK_CKMODE_EN); +} + +static void mtk_mipi_tx_power_off_signal(struct phy *phy) +{ + struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy); + + /* Switch ON each Lane */ + mtk_mipi_tx_set_bits(mipi_tx, MIPITX_D0_SW_CTL_EN, DSI_SW_CTL_EN); + mtk_mipi_tx_set_bits(mipi_tx, MIPITX_D1_SW_CTL_EN, DSI_SW_CTL_EN); + mtk_mipi_tx_set_bits(mipi_tx, MIPITX_D2_SW_CTL_EN, DSI_SW_CTL_EN); + mtk_mipi_tx_set_bits(mipi_tx, MIPITX_D3_SW_CTL_EN, DSI_SW_CTL_EN); + mtk_mipi_tx_set_bits(mipi_tx, MIPITX_CK_SW_CTL_EN, DSI_SW_CTL_EN); + + writel(RG_DSI_PAD_TIEL_SEL | RG_DSI_BG_CORE_EN, + mipi_tx->regs + MIPITX_LANE_CON); + writel(RG_DSI_PAD_TIEL_SEL, mipi_tx->regs + MIPITX_LANE_CON); +} + +const struct mtk_mipitx_data mt8183_mipitx_data = { + .mipi_tx_clk_ops = &mtk_mipi_tx_pll_ops, + .mipi_tx_enable_signal = mtk_mipi_tx_power_on_signal, + .mipi_tx_disable_signal = mtk_mipi_tx_power_off_signal, +}; |