diff options
Diffstat (limited to 'drivers/gpu/drm/mediatek')
22 files changed, 5763 insertions, 0 deletions
diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig new file mode 100644 index 000000000..eeefc9718 --- /dev/null +++ b/drivers/gpu/drm/mediatek/Kconfig @@ -0,0 +1,16 @@ +config DRM_MEDIATEK + tristate "DRM Support for Mediatek SoCs" + depends on DRM + depends on ARCH_MEDIATEK || (ARM && COMPILE_TEST) + select DRM_GEM_CMA_HELPER + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL + select IOMMU_DMA + select MEMORY + select MTK_SMI + 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. diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile new file mode 100644 index 000000000..5fcf58e87 --- /dev/null +++ b/drivers/gpu/drm/mediatek/Makefile @@ -0,0 +1,14 @@ +mediatek-drm-y := 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_fb.o \ + mtk_drm_gem.o \ + mtk_drm_plane.o \ + mtk_dsi.o \ + mtk_mipi_tx.o \ + mtk_dpi.o + +obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o 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..8f62671fc --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_disp_ovl.c @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/platform_device.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_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(n) (0x0f40 + 0x20 * (n)) + +#define OVL_RDMA_MEM_GMC 0x40402020 + +#define OVL_CON_BYTE_SWAP BIT(24) +#define OVL_CON_CLRFMT_RGB565 (0 << 12) +#define OVL_CON_CLRFMT_RGB888 (1 << 12) +#define OVL_CON_CLRFMT_RGBA8888 (2 << 12) +#define OVL_CON_CLRFMT_ARGB8888 (3 << 12) +#define OVL_CON_AEN BIT(8) +#define OVL_CON_ALPHA 0xff + +/** + * 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; +}; + +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 *priv = container_of(comp, struct mtk_disp_ovl, + ddp_comp); + + priv->crtc = crtc; + 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 *priv = container_of(comp, struct mtk_disp_ovl, + ddp_comp); + + priv->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) +{ + if (w != 0 && h != 0) + writel_relaxed(h << 16 | w, comp->regs + DISP_REG_OVL_ROI_SIZE); + writel_relaxed(0x0, comp->regs + DISP_REG_OVL_ROI_BGCLR); + + writel(0x1, comp->regs + DISP_REG_OVL_RST); + writel(0x0, comp->regs + DISP_REG_OVL_RST); +} + +static void mtk_ovl_layer_on(struct mtk_ddp_comp *comp, unsigned int idx) +{ + unsigned int reg; + + writel(0x1, comp->regs + DISP_REG_OVL_RDMA_CTRL(idx)); + writel(OVL_RDMA_MEM_GMC, comp->regs + DISP_REG_OVL_RDMA_GMC(idx)); + + reg = readl(comp->regs + DISP_REG_OVL_SRC_CON); + reg = reg | BIT(idx); + writel(reg, comp->regs + DISP_REG_OVL_SRC_CON); +} + +static void mtk_ovl_layer_off(struct mtk_ddp_comp *comp, unsigned int idx) +{ + unsigned int reg; + + reg = readl(comp->regs + DISP_REG_OVL_SRC_CON); + reg = reg & ~BIT(idx); + writel(reg, comp->regs + DISP_REG_OVL_SRC_CON); + + writel(0x0, comp->regs + DISP_REG_OVL_RDMA_CTRL(idx)); +} + +static unsigned int ovl_fmt_convert(unsigned int fmt) +{ + switch (fmt) { + default: + case DRM_FORMAT_RGB565: + return OVL_CON_CLRFMT_RGB565; + case DRM_FORMAT_BGR565: + return OVL_CON_CLRFMT_RGB565 | OVL_CON_BYTE_SWAP; + case DRM_FORMAT_RGB888: + return OVL_CON_CLRFMT_RGB888; + case DRM_FORMAT_BGR888: + return OVL_CON_CLRFMT_RGB888 | 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; + } +} + +static void mtk_ovl_layer_config(struct mtk_ddp_comp *comp, unsigned int idx, + struct mtk_plane_state *state) +{ + 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); + + con = ovl_fmt_convert(fmt); + if (idx != 0) + con |= OVL_CON_AEN | OVL_CON_ALPHA; + + writel_relaxed(con, comp->regs + DISP_REG_OVL_CON(idx)); + writel_relaxed(pitch, comp->regs + DISP_REG_OVL_PITCH(idx)); + writel_relaxed(src_size, comp->regs + DISP_REG_OVL_SRC_SIZE(idx)); + writel_relaxed(offset, comp->regs + DISP_REG_OVL_OFFSET(idx)); + writel_relaxed(addr, comp->regs + DISP_REG_OVL_ADDR(idx)); + + if (pending->enable) + mtk_ovl_layer_on(comp, idx); +} + +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, + .layer_on = mtk_ovl_layer_on, + .layer_off = mtk_ovl_layer_off, + .layer_config = mtk_ovl_layer_config, +}; + +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 %s: %d\n", + dev->of_node->full_name, 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; + + 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; + } + + comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DISP_OVL); + 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) { + dev_err(dev, "Failed to initialize component: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, priv); + + 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 of_device_id mtk_disp_ovl_driver_dt_match[] = { + { .compatible = "mediatek,mt8173-disp-ovl", }, + {}, +}; +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..5fb80cbe4 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_disp_rdma.c @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/platform_device.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 DISP_REG_RDMA_SIZE_CON_0 0x0014 +#define DISP_REG_RDMA_SIZE_CON_1 0x0018 +#define DISP_REG_RDMA_TARGET_LINE 0x001c +#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) + +/** + * 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; +}; + +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 *priv = container_of(comp, struct mtk_disp_rdma, + ddp_comp); + + priv->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 *priv = container_of(comp, struct mtk_disp_rdma, + ddp_comp); + + priv->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 threshold; + unsigned int reg; + + rdma_update_bits(comp, DISP_REG_RDMA_SIZE_CON_0, 0xfff, width); + rdma_update_bits(comp, DISP_REG_RDMA_SIZE_CON_1, 0xfffff, height); + + /* + * 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(SZ_8K) | + RDMA_OUTPUT_VALID_FIFO_THRESHOLD(threshold); + writel(reg, comp->regs + DISP_REG_RDMA_FIFO_CON); +} + +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, +}; + +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 %s: %d\n", + dev->of_node->full_name, 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) { + 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; + } + + 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 of_device_id mtk_disp_rdma_driver_dt_match[] = { + { .compatible = "mediatek,mt8173-disp-rdma", }, + {}, +}; +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..0186e500d --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dpi.c @@ -0,0 +1,764 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu <jie.qiu@mediatek.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <linux/kernel.h> +#include <linux/component.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/interrupt.h> +#include <linux/types.h> +#include <linux/clk.h> + +#include "mtk_dpi_regs.h" +#include "mtk_drm_ddp_comp.h" + +enum mtk_dpi_out_bit_num { + MTK_DPI_OUT_BIT_NUM_8BITS, + MTK_DPI_OUT_BIT_NUM_10BITS, + MTK_DPI_OUT_BIT_NUM_12BITS, + MTK_DPI_OUT_BIT_NUM_16BITS +}; + +enum mtk_dpi_out_yc_map { + MTK_DPI_OUT_YC_MAP_RGB, + MTK_DPI_OUT_YC_MAP_CYCY, + MTK_DPI_OUT_YC_MAP_YCYC, + MTK_DPI_OUT_YC_MAP_CY, + MTK_DPI_OUT_YC_MAP_YC +}; + +enum mtk_dpi_out_channel_swap { + MTK_DPI_OUT_CHANNEL_SWAP_RGB, + MTK_DPI_OUT_CHANNEL_SWAP_GBR, + MTK_DPI_OUT_CHANNEL_SWAP_BRG, + MTK_DPI_OUT_CHANNEL_SWAP_RBG, + MTK_DPI_OUT_CHANNEL_SWAP_GRB, + MTK_DPI_OUT_CHANNEL_SWAP_BGR +}; + +enum mtk_dpi_out_color_format { + MTK_DPI_COLOR_FORMAT_RGB, + MTK_DPI_COLOR_FORMAT_RGB_FULL, + MTK_DPI_COLOR_FORMAT_YCBCR_444, + MTK_DPI_COLOR_FORMAT_YCBCR_422, + MTK_DPI_COLOR_FORMAT_XV_YCC, + MTK_DPI_COLOR_FORMAT_YCBCR_444_FULL, + MTK_DPI_COLOR_FORMAT_YCBCR_422_FULL +}; + +struct mtk_dpi { + struct mtk_ddp_comp ddp_comp; + struct drm_encoder encoder; + 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; + 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; + bool power_sta; + u8 power_ctl; +}; + +static inline struct mtk_dpi *mtk_dpi_from_encoder(struct drm_encoder *e) +{ + return container_of(e, struct mtk_dpi, encoder); +} + +enum mtk_dpi_polarity { + MTK_DPI_POLARITY_RISING, + MTK_DPI_POLARITY_FALLING, +}; + +enum mtk_dpi_power_ctl { + DPI_POWER_START = BIT(0), + DPI_POWER_ENABLE = BIT(1), +}; + +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; +}; + +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_H_FRE_CON, H_FRE_2N, H_FRE_2N); +} + +static void mtk_dpi_config_color_format(struct mtk_dpi *dpi, + enum mtk_dpi_out_color_format format) +{ + if ((format == MTK_DPI_COLOR_FORMAT_YCBCR_444) || + (format == MTK_DPI_COLOR_FORMAT_YCBCR_444_FULL)) { + mtk_dpi_config_yuv422_enable(dpi, false); + mtk_dpi_config_csc_enable(dpi, true); + mtk_dpi_config_swap_input(dpi, false); + mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_BGR); + } else if ((format == MTK_DPI_COLOR_FORMAT_YCBCR_422) || + (format == MTK_DPI_COLOR_FORMAT_YCBCR_422_FULL)) { + mtk_dpi_config_yuv422_enable(dpi, true); + mtk_dpi_config_csc_enable(dpi, true); + mtk_dpi_config_swap_input(dpi, true); + mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_RGB); + } else { + 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, enum mtk_dpi_power_ctl pctl) +{ + dpi->power_ctl &= ~pctl; + + if ((dpi->power_ctl & DPI_POWER_START) || + (dpi->power_ctl & DPI_POWER_ENABLE)) + return; + + if (!dpi->power_sta) + return; + + mtk_dpi_disable(dpi); + clk_disable_unprepare(dpi->pixel_clk); + clk_disable_unprepare(dpi->engine_clk); + dpi->power_sta = false; +} + +static int mtk_dpi_power_on(struct mtk_dpi *dpi, enum mtk_dpi_power_ctl pctl) +{ + int ret; + + dpi->power_ctl |= pctl; + + if (!(dpi->power_ctl & DPI_POWER_START) && + !(dpi->power_ctl & DPI_POWER_ENABLE)) + return 0; + + if (dpi->power_sta) + 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_eng; + } + + ret = clk_prepare_enable(dpi->pixel_clk); + if (ret) { + dev_err(dpi->dev, "Failed to enable pixel clock: %d\n", ret); + goto err_pixel; + } + + mtk_dpi_enable(dpi); + dpi->power_sta = true; + return 0; + +err_pixel: + clk_disable_unprepare(dpi->engine_clk); +err_eng: + dpi->power_ctl &= ~pctl; + 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 }; + unsigned long pix_rate; + unsigned long pll_rate; + unsigned int factor; + + pix_rate = 1000UL * mode->clock; + if (mode->clock <= 74000) + factor = 8 * 3; + else + factor = 4 * 3; + pll_rate = pix_rate * factor; + + dev_dbg(dpi->dev, "Want PLL %lu Hz, pixel clock %lu Hz\n", + pll_rate, pix_rate); + + clk_set_rate(dpi->tvd_clk, pll_rate); + pll_rate = clk_get_rate(dpi->tvd_clk); + + pix_rate = pll_rate / factor; + clk_set_rate(dpi->pixel_clk, pix_rate); + pix_rate = clk_get_rate(dpi->pixel_clk); + + dev_dbg(dpi->dev, "Got PLL %lu Hz, pixel clock %lu Hz\n", + pll_rate, pix_rate); + + 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 = mode->flags & DRM_MODE_FLAG_PHSYNC ? + MTK_DPI_POLARITY_FALLING : MTK_DPI_POLARITY_RISING; + dpi_pol.vsync_pol = mode->flags & DRM_MODE_FLAG_PVSYNC ? + MTK_DPI_POLARITY_FALLING : MTK_DPI_POLARITY_RISING; + + hsync.sync_width = mode->hsync_end - mode->hsync_start; + hsync.back_porch = mode->htotal - mode->hsync_end; + hsync.front_porch = mode->hsync_start - mode->hdisplay; + hsync.shift_half_line = false; + + vsync_lodd.sync_width = mode->vsync_end - mode->vsync_start; + vsync_lodd.back_porch = mode->vtotal - mode->vsync_end; + vsync_lodd.front_porch = mode->vsync_start - mode->vdisplay; + vsync_lodd.shift_half_line = false; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE && + 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 (mode->flags & DRM_MODE_FLAG_INTERLACE && + !(mode->flags & DRM_MODE_FLAG_3D_MASK)) { + vsync_leven = vsync_lodd; + vsync_leven.shift_half_line = true; + } else if (!(mode->flags & DRM_MODE_FLAG_INTERLACE) && + 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, !!(mode->flags & + DRM_MODE_FLAG_INTERLACE)); + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + mtk_dpi_config_fb_size(dpi, mode->hdisplay, mode->vdisplay / 2); + else + mtk_dpi_config_fb_size(dpi, mode->hdisplay, mode->vdisplay); + + 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_sw_reset(dpi, false); + + return 0; +} + +static void mtk_dpi_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs mtk_dpi_encoder_funcs = { + .destroy = mtk_dpi_encoder_destroy, +}; + +static bool mtk_dpi_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void mtk_dpi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder); + + drm_mode_copy(&dpi->mode, adjusted_mode); +} + +static void mtk_dpi_encoder_disable(struct drm_encoder *encoder) +{ + struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder); + + mtk_dpi_power_off(dpi, DPI_POWER_ENABLE); +} + +static void mtk_dpi_encoder_enable(struct drm_encoder *encoder) +{ + struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder); + + mtk_dpi_power_on(dpi, DPI_POWER_ENABLE); + mtk_dpi_set_display_mode(dpi, &dpi->mode); +} + +static int mtk_dpi_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + return 0; +} + +static const struct drm_encoder_helper_funcs mtk_dpi_encoder_helper_funcs = { + .mode_fixup = mtk_dpi_encoder_mode_fixup, + .mode_set = mtk_dpi_encoder_mode_set, + .disable = mtk_dpi_encoder_disable, + .enable = mtk_dpi_encoder_enable, + .atomic_check = mtk_dpi_atomic_check, +}; + +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, DPI_POWER_START); +} + +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, DPI_POWER_START); +} + +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 %s: %d\n", + dev->of_node->full_name, ret); + return ret; + } + + ret = drm_encoder_init(drm_dev, &dpi->encoder, &mtk_dpi_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + if (ret) { + dev_err(dev, "Failed to initialize decoder: %d\n", ret); + goto err_unregister; + } + drm_encoder_helper_add(&dpi->encoder, &mtk_dpi_encoder_helper_funcs); + + /* Currently DPI0 is fixed to be driven by OVL1 */ + dpi->encoder.possible_crtcs = BIT(1); + + dpi->encoder.bridge->encoder = &dpi->encoder; + ret = drm_bridge_attach(dpi->encoder.dev, dpi->encoder.bridge); + 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 int mtk_dpi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_dpi *dpi; + struct resource *mem; + struct device_node *ep, *bridge_node = NULL; + int comp_id; + int ret; + + dpi = devm_kzalloc(dev, sizeof(*dpi), GFP_KERNEL); + if (!dpi) + return -ENOMEM; + + dpi->dev = dev; + + 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); + 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); + 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); + 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; + } + + ep = of_graph_get_next_endpoint(dev->of_node, NULL); + if (ep) { + bridge_node = of_graph_get_remote_port_parent(ep); + of_node_put(ep); + } + if (!bridge_node) { + dev_err(dev, "Failed to find bridge node\n"); + return -ENODEV; + } + + dev_info(dev, "Found bridge node: %s\n", bridge_node->full_name); + + dpi->encoder.bridge = of_drm_find_bridge(bridge_node); + of_node_put(bridge_node); + if (!dpi->encoder.bridge) + return -EPROBE_DEFER; + + 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); + + ret = component_add(dev, &mtk_dpi_component_ops); + if (ret) { + dev_err(dev, "Failed to add component: %d\n", ret); + return ret; + } + + return 0; +} + +static int mtk_dpi_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &mtk_dpi_component_ops); + + return 0; +} + +static const struct of_device_id mtk_dpi_of_ids[] = { + { .compatible = "mediatek,mt8173-dpi", }, + {} +}; + +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..4b6ad4751 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dpi_regs.h @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu <jie.qiu@mediatek.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __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 DPI_H_FRE_CON 0xE0 +#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..24aa3bad1 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.c @@ -0,0 +1,582 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <asm/barrier.h> +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <soc/mediatek/smi.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 mtk_drm_plane structures, one for each overlay plane + * @pending_planes: whether any plane has pending changes to be applied + * @config_regs: memory mapped mmsys configuration register space + * @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 mtk_drm_plane planes[OVL_LAYER_NR]; + bool pending_planes; + + void __iomem *config_regs; + struct mtk_disp_mutex *mutex; + unsigned int ddp_comp_nr; + struct mtk_ddp_comp **ddp_comp; +}; + +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); + int i; + + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) + clk_unprepare(mtk_crtc->ddp_comp[i]->clk); + + 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) { + if (crtc->state->mode_blob) + drm_property_unreference_blob(crtc->state->mode_blob); + + state = to_mtk_crtc_state(crtc->state); + memset(state, 0, sizeof(*state)); + } else { + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return; + crtc->state = &state->base; + } + + state->base.crtc = crtc; +} + +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 = crtc->mode.vrefresh; + wmb(); /* Make sure the above parameters are set before update */ + state->pending_config = true; +} + +int mtk_drm_crtc_enable_vblank(struct drm_device *drm, unsigned int pipe) +{ + struct mtk_drm_private *priv = drm->dev_private; + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(priv->crtc[pipe]); + struct mtk_ddp_comp *ovl = mtk_crtc->ddp_comp[0]; + + mtk_ddp_comp_enable_vblank(ovl, &mtk_crtc->base); + + return 0; +} + +void mtk_drm_crtc_disable_vblank(struct drm_device *drm, unsigned int pipe) +{ + struct mtk_drm_private *priv = drm->dev_private; + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(priv->crtc[pipe]); + struct mtk_ddp_comp *ovl = mtk_crtc->ddp_comp[0]; + + mtk_ddp_comp_disable_vblank(ovl); +} + +static int mtk_crtc_ddp_clk_enable(struct mtk_drm_crtc *mtk_crtc) +{ + int ret; + int i; + + DRM_DEBUG_DRIVER("%s\n", __func__); + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) { + ret = clk_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(mtk_crtc->ddp_comp[i]->clk); + return ret; +} + +static void mtk_crtc_ddp_clk_disable(struct mtk_drm_crtc *mtk_crtc) +{ + int i; + + DRM_DEBUG_DRIVER("%s\n", __func__); + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) + clk_disable(mtk_crtc->ddp_comp[i]->clk); +} + +static int mtk_crtc_ddp_hw_init(struct mtk_drm_crtc *mtk_crtc) +{ + struct drm_crtc *crtc = &mtk_crtc->base; + unsigned int width, height, vrefresh; + int ret; + int i; + + DRM_DEBUG_DRIVER("%s\n", __func__); + if (WARN_ON(!crtc->state)) + return -EINVAL; + + width = crtc->state->adjusted_mode.hdisplay; + height = crtc->state->adjusted_mode.vdisplay; + vrefresh = crtc->state->adjusted_mode.vrefresh; + + ret = pm_runtime_get_sync(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; + } + + DRM_DEBUG_DRIVER("mediatek_ddp_ddp_path_setup\n"); + for (i = 0; i < mtk_crtc->ddp_comp_nr - 1; i++) { + mtk_ddp_add_comp_to_path(mtk_crtc->config_regs, + 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]; + + mtk_ddp_comp_config(comp, width, height, vrefresh); + mtk_ddp_comp_start(comp); + } + + /* Initially configure all planes */ + for (i = 0; i < OVL_LAYER_NR; i++) { + struct drm_plane *plane = &mtk_crtc->planes[i].base; + struct mtk_plane_state *plane_state; + + plane_state = to_mtk_plane_state(plane->state); + mtk_ddp_comp_layer_config(mtk_crtc->ddp_comp[0], i, + plane_state); + } + + 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; + int i; + + DRM_DEBUG_DRIVER("%s\n", __func__); + for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) + mtk_ddp_comp_stop(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_ddp_remove_comp_from_path(mtk_crtc->config_regs, + 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); +} + +static void mtk_drm_crtc_enable(struct drm_crtc *crtc) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + struct mtk_ddp_comp *ovl = mtk_crtc->ddp_comp[0]; + int ret; + + DRM_DEBUG_DRIVER("%s %d\n", __func__, crtc->base.id); + + ret = mtk_smi_larb_get(ovl->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(ovl->larb_dev); + return; + } + + drm_crtc_vblank_on(crtc); + mtk_crtc->enabled = true; +} + +static void mtk_drm_crtc_disable(struct drm_crtc *crtc) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + struct mtk_ddp_comp *ovl = 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 < OVL_LAYER_NR; i++) { + struct drm_plane *plane = &mtk_crtc->planes[i].base; + 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; + + /* 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(ovl->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); + + 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); + mtk_crtc->event = state->base.event; + 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); + unsigned int pending_planes = 0; + int i; + + if (mtk_crtc->event) + mtk_crtc->pending_needs_vblank = true; + for (i = 0; i < OVL_LAYER_NR; i++) { + struct drm_plane *plane = &mtk_crtc->planes[i].base; + 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); + } + } + if (pending_planes) + mtk_crtc->pending_planes = true; +} + +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, +}; + +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, + .enable = mtk_drm_crtc_enable, + .disable = mtk_drm_crtc_disable, + .atomic_begin = mtk_drm_crtc_atomic_begin, + .atomic_flush = mtk_drm_crtc_atomic_flush, +}; + +static int mtk_drm_crtc_init(struct drm_device *drm, + struct mtk_drm_crtc *mtk_crtc, + struct drm_plane *primary, + struct drm_plane *cursor, unsigned int pipe) +{ + int ret; + + 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 *ovl) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + struct mtk_crtc_state *state = to_mtk_crtc_state(mtk_crtc->base.state); + unsigned int i; + + /* + * 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(ovl, state->pending_width, + state->pending_height, + state->pending_vrefresh); + + state->pending_config = false; + } + + if (mtk_crtc->pending_planes) { + for (i = 0; i < OVL_LAYER_NR; i++) { + struct drm_plane *plane = &mtk_crtc->planes[i].base; + struct mtk_plane_state *plane_state; + + plane_state = to_mtk_plane_state(plane->state); + + if (plane_state->pending.config) { + mtk_ddp_comp_layer_config(ovl, i, plane_state); + plane_state->pending.config = false; + } + } + mtk_crtc->pending_planes = false; + } + + mtk_drm_finish_page_flip(mtk_crtc); +} + +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; + enum drm_plane_type type; + unsigned int zpos; + int pipe = priv->num_pipes; + int ret; + int i; + + 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->config_regs = priv->config_regs; + 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); + + 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 %s not initialized\n", + node->full_name); + ret = -ENODEV; + goto unprepare; + } + + ret = clk_prepare(comp->clk); + if (ret) { + dev_err(dev, + "Failed to prepare clock for component %s: %d\n", + node->full_name, ret); + goto unprepare; + } + + mtk_crtc->ddp_comp[i] = comp; + } + + for (zpos = 0; zpos < OVL_LAYER_NR; zpos++) { + type = (zpos == 0) ? DRM_PLANE_TYPE_PRIMARY : + (zpos == 1) ? DRM_PLANE_TYPE_CURSOR : + DRM_PLANE_TYPE_OVERLAY; + ret = mtk_plane_init(drm_dev, &mtk_crtc->planes[zpos], + BIT(pipe), type, zpos); + if (ret) + goto unprepare; + } + + ret = mtk_drm_crtc_init(drm_dev, mtk_crtc, &mtk_crtc->planes[0].base, + &mtk_crtc->planes[1].base, pipe); + if (ret < 0) + goto unprepare; + + priv->crtc[pipe] = &mtk_crtc->base; + priv->num_pipes++; + + return 0; + +unprepare: + while (--i >= 0) + clk_unprepare(mtk_crtc->ddp_comp[i]->clk); + + return ret; +} 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..81e5566ec --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef 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 OVL_LAYER_NR 4 + +int mtk_drm_crtc_enable_vblank(struct drm_device *drm, unsigned int pipe); +void mtk_drm_crtc_disable_vblank(struct drm_device *drm, unsigned int pipe); +void mtk_drm_crtc_check_flush(struct drm_crtc *crtc); +void mtk_drm_crtc_commit(struct drm_crtc *crtc); +void mtk_crtc_ddp_irq(struct drm_crtc *crtc, struct mtk_ddp_comp *ovl); +int mtk_drm_crtc_create(struct drm_device *drm_dev, + const enum mtk_ddp_comp_id *path, + unsigned int path_len); + +#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..17ba9355a --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp.c @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.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 DISP_REG_CONFIG_DISP_OVL0_MOUT_EN 0x040 +#define DISP_REG_CONFIG_DISP_OVL1_MOUT_EN 0x044 +#define DISP_REG_CONFIG_DISP_OD_MOUT_EN 0x048 +#define DISP_REG_CONFIG_DISP_GAMMA_MOUT_EN 0x04c +#define DISP_REG_CONFIG_DISP_UFOE_MOUT_EN 0x050 +#define DISP_REG_CONFIG_DISP_COLOR0_SEL_IN 0x084 +#define DISP_REG_CONFIG_DISP_COLOR1_SEL_IN 0x088 +#define DISP_REG_CONFIG_DPI_SEL_IN 0x0ac +#define DISP_REG_CONFIG_DISP_RDMA1_MOUT_EN 0x0c8 +#define DISP_REG_CONFIG_MMSYS_CG_CON0 0x100 + +#define DISP_REG_MUTEX_EN(n) (0x20 + 0x20 * (n)) +#define DISP_REG_MUTEX_RST(n) (0x28 + 0x20 * (n)) +#define DISP_REG_MUTEX_MOD(n) (0x2c + 0x20 * (n)) +#define DISP_REG_MUTEX_SOF(n) (0x30 + 0x20 * (n)) + +#define MUTEX_MOD_DISP_OVL0 BIT(11) +#define MUTEX_MOD_DISP_OVL1 BIT(12) +#define MUTEX_MOD_DISP_RDMA0 BIT(13) +#define MUTEX_MOD_DISP_RDMA1 BIT(14) +#define MUTEX_MOD_DISP_RDMA2 BIT(15) +#define MUTEX_MOD_DISP_WDMA0 BIT(16) +#define MUTEX_MOD_DISP_WDMA1 BIT(17) +#define MUTEX_MOD_DISP_COLOR0 BIT(18) +#define MUTEX_MOD_DISP_COLOR1 BIT(19) +#define MUTEX_MOD_DISP_AAL BIT(20) +#define MUTEX_MOD_DISP_GAMMA BIT(21) +#define MUTEX_MOD_DISP_UFOE BIT(22) +#define MUTEX_MOD_DISP_PWM0 BIT(23) +#define MUTEX_MOD_DISP_PWM1 BIT(24) +#define MUTEX_MOD_DISP_OD BIT(25) + +#define MUTEX_SOF_SINGLE_MODE 0 +#define MUTEX_SOF_DSI0 1 +#define MUTEX_SOF_DSI1 2 +#define MUTEX_SOF_DPI0 3 + +#define OVL0_MOUT_EN_COLOR0 0x1 +#define OD_MOUT_EN_RDMA0 0x1 +#define UFOE_MOUT_EN_DSI0 0x1 +#define COLOR0_SEL_IN_OVL0 0x1 +#define OVL1_MOUT_EN_COLOR1 0x1 +#define GAMMA_MOUT_EN_RDMA1 0x1 +#define RDMA1_MOUT_DPI0 0x2 +#define DPI0_SEL_IN_RDMA1 0x1 +#define COLOR1_SEL_IN_OVL1 0x1 + +struct mtk_disp_mutex { + int id; + bool claimed; +}; + +struct mtk_ddp { + struct device *dev; + struct clk *clk; + void __iomem *regs; + struct mtk_disp_mutex mutex[10]; +}; + +static const unsigned int mutex_mod[DDP_COMPONENT_ID_MAX] = { + [DDP_COMPONENT_AAL] = MUTEX_MOD_DISP_AAL, + [DDP_COMPONENT_COLOR0] = MUTEX_MOD_DISP_COLOR0, + [DDP_COMPONENT_COLOR1] = MUTEX_MOD_DISP_COLOR1, + [DDP_COMPONENT_GAMMA] = MUTEX_MOD_DISP_GAMMA, + [DDP_COMPONENT_OD] = MUTEX_MOD_DISP_OD, + [DDP_COMPONENT_OVL0] = MUTEX_MOD_DISP_OVL0, + [DDP_COMPONENT_OVL1] = MUTEX_MOD_DISP_OVL1, + [DDP_COMPONENT_PWM0] = MUTEX_MOD_DISP_PWM0, + [DDP_COMPONENT_PWM1] = MUTEX_MOD_DISP_PWM1, + [DDP_COMPONENT_RDMA0] = MUTEX_MOD_DISP_RDMA0, + [DDP_COMPONENT_RDMA1] = MUTEX_MOD_DISP_RDMA1, + [DDP_COMPONENT_RDMA2] = MUTEX_MOD_DISP_RDMA2, + [DDP_COMPONENT_UFOE] = MUTEX_MOD_DISP_UFOE, + [DDP_COMPONENT_WDMA0] = MUTEX_MOD_DISP_WDMA0, + [DDP_COMPONENT_WDMA1] = MUTEX_MOD_DISP_WDMA1, +}; + +static unsigned int mtk_ddp_mout_en(enum mtk_ddp_comp_id cur, + enum mtk_ddp_comp_id next, + unsigned int *addr) +{ + unsigned int value; + + if (cur == DDP_COMPONENT_OVL0 && next == DDP_COMPONENT_COLOR0) { + *addr = DISP_REG_CONFIG_DISP_OVL0_MOUT_EN; + value = OVL0_MOUT_EN_COLOR0; + } else if (cur == DDP_COMPONENT_OD && next == DDP_COMPONENT_RDMA0) { + *addr = DISP_REG_CONFIG_DISP_OD_MOUT_EN; + value = OD_MOUT_EN_RDMA0; + } else if (cur == DDP_COMPONENT_UFOE && next == DDP_COMPONENT_DSI0) { + *addr = DISP_REG_CONFIG_DISP_UFOE_MOUT_EN; + value = UFOE_MOUT_EN_DSI0; + } else if (cur == DDP_COMPONENT_OVL1 && next == DDP_COMPONENT_COLOR1) { + *addr = DISP_REG_CONFIG_DISP_OVL1_MOUT_EN; + value = OVL1_MOUT_EN_COLOR1; + } else if (cur == DDP_COMPONENT_GAMMA && next == DDP_COMPONENT_RDMA1) { + *addr = DISP_REG_CONFIG_DISP_GAMMA_MOUT_EN; + value = GAMMA_MOUT_EN_RDMA1; + } else if (cur == DDP_COMPONENT_RDMA1 && next == DDP_COMPONENT_DPI0) { + *addr = DISP_REG_CONFIG_DISP_RDMA1_MOUT_EN; + value = RDMA1_MOUT_DPI0; + } else { + value = 0; + } + + return value; +} + +static unsigned int mtk_ddp_sel_in(enum mtk_ddp_comp_id cur, + enum mtk_ddp_comp_id next, + unsigned int *addr) +{ + unsigned int value; + + if (cur == DDP_COMPONENT_OVL0 && next == DDP_COMPONENT_COLOR0) { + *addr = DISP_REG_CONFIG_DISP_COLOR0_SEL_IN; + value = COLOR0_SEL_IN_OVL0; + } else if (cur == DDP_COMPONENT_RDMA1 && next == DDP_COMPONENT_DPI0) { + *addr = DISP_REG_CONFIG_DPI_SEL_IN; + value = DPI0_SEL_IN_RDMA1; + } else if (cur == DDP_COMPONENT_OVL1 && next == DDP_COMPONENT_COLOR1) { + *addr = DISP_REG_CONFIG_DISP_COLOR1_SEL_IN; + value = COLOR1_SEL_IN_OVL1; + } else { + value = 0; + } + + return value; +} + +void mtk_ddp_add_comp_to_path(void __iomem *config_regs, + enum mtk_ddp_comp_id cur, + enum mtk_ddp_comp_id next) +{ + unsigned int addr, value, reg; + + value = mtk_ddp_mout_en(cur, next, &addr); + if (value) { + reg = readl_relaxed(config_regs + addr) | value; + writel_relaxed(reg, config_regs + addr); + } + + value = mtk_ddp_sel_in(cur, next, &addr); + if (value) { + reg = readl_relaxed(config_regs + addr) | value; + writel_relaxed(reg, config_regs + addr); + } +} + +void mtk_ddp_remove_comp_from_path(void __iomem *config_regs, + enum mtk_ddp_comp_id cur, + enum mtk_ddp_comp_id next) +{ + unsigned int addr, value, reg; + + value = mtk_ddp_mout_en(cur, next, &addr); + if (value) { + reg = readl_relaxed(config_regs + addr) & ~value; + writel_relaxed(reg, config_regs + addr); + } + + value = mtk_ddp_sel_in(cur, next, &addr); + if (value) { + reg = readl_relaxed(config_regs + addr) & ~value; + writel_relaxed(reg, config_regs + addr); + } +} + +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; + + WARN_ON(&ddp->mutex[mutex->id] != mutex); + + switch (id) { + case DDP_COMPONENT_DSI0: + reg = MUTEX_SOF_DSI0; + break; + case DDP_COMPONENT_DSI1: + reg = MUTEX_SOF_DSI0; + break; + case DDP_COMPONENT_DPI0: + reg = MUTEX_SOF_DPI0; + break; + default: + reg = readl_relaxed(ddp->regs + DISP_REG_MUTEX_MOD(mutex->id)); + reg |= mutex_mod[id]; + writel_relaxed(reg, ddp->regs + DISP_REG_MUTEX_MOD(mutex->id)); + return; + } + + writel_relaxed(reg, ddp->regs + DISP_REG_MUTEX_SOF(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; + + WARN_ON(&ddp->mutex[mutex->id] != mutex); + + switch (id) { + case DDP_COMPONENT_DSI0: + case DDP_COMPONENT_DSI1: + case DDP_COMPONENT_DPI0: + writel_relaxed(MUTEX_SOF_SINGLE_MODE, + ddp->regs + DISP_REG_MUTEX_SOF(mutex->id)); + break; + default: + reg = readl_relaxed(ddp->regs + DISP_REG_MUTEX_MOD(mutex->id)); + reg &= ~mutex_mod[id]; + writel_relaxed(reg, ddp->regs + DISP_REG_MUTEX_MOD(mutex->id)); + 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)); +} + +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->clk = devm_clk_get(dev, NULL); + if (IS_ERR(ddp->clk)) { + 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,mt8173-disp-mutex" }, + {}, +}; +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..92c11752f --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MTK_DRM_DDP_H +#define MTK_DRM_DDP_H + +#include "mtk_drm_ddp_comp.h" + +struct regmap; +struct device; +struct mtk_disp_mutex; + +void mtk_ddp_add_comp_to_path(void __iomem *config_regs, + enum mtk_ddp_comp_id cur, + enum mtk_ddp_comp_id next); +void mtk_ddp_remove_comp_from_path(void __iomem *config_regs, + enum mtk_ddp_comp_id cur, + enum mtk_ddp_comp_id next); + +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); + +#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..3970fcf0f --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Authors: + * YT Shen <yt.shen@mediatek.com> + * CK Hu <ck.hu@mediatek.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <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 <drm/drmP.h> +#include "mtk_drm_drv.h" +#include "mtk_drm_plane.h" +#include "mtk_drm_ddp_comp.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_REG_UFO_START 0x0000 + +#define DISP_COLOR_CFG_MAIN 0x0400 +#define DISP_COLOR_START 0x0c00 +#define DISP_COLOR_WIDTH 0x0c50 +#define DISP_COLOR_HEIGHT 0x0c54 + +#define OD_RELAY_MODE BIT(0) + +#define UFO_BYPASS BIT(2) + +#define COLOR_BYPASS_ALL BIT(7) +#define COLOR_SEQ_SEL BIT(13) + +static void mtk_color_config(struct mtk_ddp_comp *comp, unsigned int w, + unsigned int h, unsigned int vrefresh) +{ + writel(w, comp->regs + DISP_COLOR_WIDTH); + writel(h, comp->regs + DISP_COLOR_HEIGHT); +} + +static void mtk_color_start(struct mtk_ddp_comp *comp) +{ + writel(COLOR_BYPASS_ALL | COLOR_SEQ_SEL, + comp->regs + DISP_COLOR_CFG_MAIN); + writel(0x1, comp->regs + DISP_COLOR_START); +} + +static void mtk_od_config(struct mtk_ddp_comp *comp, unsigned int w, + unsigned int h, unsigned int vrefresh) +{ + writel(w << 16 | h, comp->regs + DISP_OD_SIZE); +} + +static void mtk_od_start(struct mtk_ddp_comp *comp) +{ + writel(OD_RELAY_MODE, comp->regs + DISP_OD_CFG); + 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 const struct mtk_ddp_comp_funcs ddp_color = { + .config = mtk_color_config, + .start = mtk_color_start, +}; + +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_RDMA] = "rdma", + [MTK_DISP_WDMA] = "wdma", + [MTK_DISP_COLOR] = "color", + [MTK_DISP_AAL] = "aal", + [MTK_DISP_GAMMA] = "gamma", + [MTK_DISP_UFOE] = "ufoe", + [MTK_DSI] = "dsi", + [MTK_DPI] = "dpi", + [MTK_DISP_PWM] = "pwm", + [MTK_DISP_MUTEX] = "mutex", + [MTK_DISP_OD] = "od", +}; + +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_AAL] = { MTK_DISP_AAL, 0, NULL }, + [DDP_COMPONENT_COLOR0] = { MTK_DISP_COLOR, 0, &ddp_color }, + [DDP_COMPONENT_COLOR1] = { MTK_DISP_COLOR, 1, &ddp_color }, + [DDP_COMPONENT_DPI0] = { MTK_DPI, 0, NULL }, + [DDP_COMPONENT_DSI0] = { MTK_DSI, 0, NULL }, + [DDP_COMPONENT_DSI1] = { MTK_DSI, 1, NULL }, + [DDP_COMPONENT_GAMMA] = { MTK_DISP_GAMMA, 0, NULL }, + [DDP_COMPONENT_OD] = { MTK_DISP_OD, 0, &ddp_od }, + [DDP_COMPONENT_OVL0] = { MTK_DISP_OVL, 0, NULL }, + [DDP_COMPONENT_OVL1] = { MTK_DISP_OVL, 1, NULL }, + [DDP_COMPONENT_PWM0] = { MTK_DISP_PWM, 0, 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 }, +}; + +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; +} + +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 (comp_id < 0 || comp_id >= DDP_COMPONENT_ID_MAX) + return -EINVAL; + + comp->id = comp_id; + comp->funcs = funcs ?: mtk_ddp_matches[comp_id].funcs; + + if (comp_id == DDP_COMPONENT_DPI0 || + comp_id == DDP_COMPONENT_DSI0 || + 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)) + comp->clk = NULL; + + type = mtk_ddp_matches[comp_id].type; + + /* Only DMA capable components need the LARB property */ + comp->larb_dev = NULL; + if (type != MTK_DISP_OVL && + 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 %s node\n", + node->full_name); + return -EINVAL; + } + + larb_pdev = of_find_device_by_node(larb_node); + if (!larb_pdev) { + dev_warn(dev, "Waiting for larb device %s\n", + larb_node->full_name); + of_node_put(larb_node); + return -EPROBE_DEFER; + } + of_node_put(larb_node); + + comp->larb_dev = &larb_pdev->dev; + + 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..6b13ba970 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MTK_DRM_DDP_COMP_H +#define MTK_DRM_DDP_COMP_H + +#include <linux/io.h> + +struct device; +struct device_node; +struct drm_crtc; +struct drm_device; +struct mtk_plane_state; + +enum mtk_ddp_comp_type { + MTK_DISP_OVL, + MTK_DISP_RDMA, + MTK_DISP_WDMA, + MTK_DISP_COLOR, + MTK_DISP_AAL, + MTK_DISP_GAMMA, + MTK_DISP_UFOE, + MTK_DSI, + MTK_DPI, + MTK_DISP_PWM, + MTK_DISP_MUTEX, + MTK_DISP_OD, + MTK_DDP_COMP_TYPE_MAX, +}; + +enum mtk_ddp_comp_id { + DDP_COMPONENT_AAL, + DDP_COMPONENT_COLOR0, + DDP_COMPONENT_COLOR1, + DDP_COMPONENT_DPI0, + DDP_COMPONENT_DSI0, + DDP_COMPONENT_DSI1, + DDP_COMPONENT_GAMMA, + DDP_COMPONENT_OD, + DDP_COMPONENT_OVL0, + DDP_COMPONENT_OVL1, + DDP_COMPONENT_PWM0, + DDP_COMPONENT_PWM1, + DDP_COMPONENT_RDMA0, + DDP_COMPONENT_RDMA1, + DDP_COMPONENT_RDMA2, + DDP_COMPONENT_UFOE, + DDP_COMPONENT_WDMA0, + DDP_COMPONENT_WDMA1, + DDP_COMPONENT_ID_MAX, +}; + +struct mtk_ddp_comp; + +struct mtk_ddp_comp_funcs { + void (*config)(struct mtk_ddp_comp *comp, unsigned int w, + unsigned int h, unsigned int vrefresh); + 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); + void (*layer_on)(struct mtk_ddp_comp *comp, unsigned int idx); + void (*layer_off)(struct mtk_ddp_comp *comp, unsigned int idx); + void (*layer_config)(struct mtk_ddp_comp *comp, unsigned int idx, + struct mtk_plane_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; +}; + +static inline void mtk_ddp_comp_config(struct mtk_ddp_comp *comp, + unsigned int w, unsigned int h, + unsigned int vrefresh) +{ + if (comp->funcs && comp->funcs->config) + comp->funcs->config(comp, w, h, vrefresh); +} + +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 void mtk_ddp_comp_layer_on(struct mtk_ddp_comp *comp, + unsigned int idx) +{ + if (comp->funcs && comp->funcs->layer_on) + comp->funcs->layer_on(comp, idx); +} + +static inline void mtk_ddp_comp_layer_off(struct mtk_ddp_comp *comp, + unsigned int idx) +{ + if (comp->funcs && comp->funcs->layer_off) + comp->funcs->layer_off(comp, idx); +} + +static inline void mtk_ddp_comp_layer_config(struct mtk_ddp_comp *comp, + unsigned int idx, + struct mtk_plane_state *state) +{ + if (comp->funcs && comp->funcs->layer_config) + comp->funcs->layer_config(comp, idx, state); +} + +int mtk_ddp_comp_get_id(struct device_node *node, + enum mtk_ddp_comp_type comp_type); +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); + +#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..b1223d54d --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -0,0 +1,567 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: YT SHEN <yt.shen@mediatek.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_gem.h> +#include <drm/drm_gem_cma_helper.h> +#include <linux/component.h> +#include <linux/iommu.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.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_fb.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 void mtk_atomic_schedule(struct mtk_drm_private *private, + struct drm_atomic_state *state) +{ + private->commit.state = state; + schedule_work(&private->commit.work); +} + +static void mtk_atomic_wait_for_fences(struct drm_atomic_state *state) +{ + struct drm_plane *plane; + struct drm_plane_state *plane_state; + int i; + + for_each_plane_in_state(state, plane, plane_state, i) + mtk_fb_wait(plane->state->fb); +} + +static void mtk_atomic_complete(struct mtk_drm_private *private, + struct drm_atomic_state *state) +{ + struct drm_device *drm = private->drm; + + mtk_atomic_wait_for_fences(state); + + drm_atomic_helper_commit_modeset_disables(drm, state); + drm_atomic_helper_commit_planes(drm, state, false); + drm_atomic_helper_commit_modeset_enables(drm, state); + drm_atomic_helper_wait_for_vblanks(drm, state); + drm_atomic_helper_cleanup_planes(drm, state); + drm_atomic_state_free(state); +} + +static void mtk_atomic_work(struct work_struct *work) +{ + struct mtk_drm_private *private = container_of(work, + struct mtk_drm_private, commit.work); + + mtk_atomic_complete(private, private->commit.state); +} + +static int mtk_atomic_commit(struct drm_device *drm, + struct drm_atomic_state *state, + bool async) +{ + struct mtk_drm_private *private = drm->dev_private; + int ret; + + ret = drm_atomic_helper_prepare_planes(drm, state); + if (ret) + return ret; + + mutex_lock(&private->commit.lock); + flush_work(&private->commit.work); + + drm_atomic_helper_swap_state(drm, state); + + if (async) + mtk_atomic_schedule(private, state); + else + mtk_atomic_complete(private, state); + + mutex_unlock(&private->commit.lock); + + return 0; +} + +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 = mtk_atomic_commit, +}; + +static const enum mtk_ddp_comp_id mtk_ddp_main[] = { + DDP_COMPONENT_OVL0, + DDP_COMPONENT_COLOR0, + DDP_COMPONENT_AAL, + DDP_COMPONENT_OD, + DDP_COMPONENT_RDMA0, + DDP_COMPONENT_UFOE, + DDP_COMPONENT_DSI0, + DDP_COMPONENT_PWM0, +}; + +static const enum mtk_ddp_comp_id mtk_ddp_ext[] = { + DDP_COMPONENT_OVL1, + DDP_COMPONENT_COLOR1, + DDP_COMPONENT_GAMMA, + DDP_COMPONENT_RDMA1, + DDP_COMPONENT_DPI0, +}; + +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; + 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 %s\n", + private->mutex_node->full_name); + of_node_put(private->mutex_node); + return -EPROBE_DEFER; + } + private->mutex_dev = &pdev->dev; + + drm_mode_config_init(drm); + + 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; + + ret = component_bind_all(drm->dev, drm); + if (ret) + goto err_config_cleanup; + + /* + * 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, mtk_ddp_main, ARRAY_SIZE(mtk_ddp_main)); + if (ret < 0) + goto err_component_unbind; + /* ... and OVL1 -> COLOR1 -> GAMMA -> RDMA1 -> DPI0. */ + ret = mtk_drm_crtc_create(drm, mtk_ddp_ext, ARRAY_SIZE(mtk_ddp_ext)); + if (ret < 0) + goto err_component_unbind; + + /* Use OVL device for all DMA memory allocations */ + np = private->comp_node[mtk_ddp_main[0]] ?: + private->comp_node[mtk_ddp_ext[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; + } + + private->dma_dev = &pdev->dev; + + /* + * 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_component_unbind; + + drm_kms_helper_poll_init(drm); + drm_mode_config_reset(drm); + + return 0; + +err_component_unbind: + component_unbind_all(drm->dev, drm); +err_config_cleanup: + drm_mode_config_cleanup(drm); + + return ret; +} + +static void mtk_drm_kms_deinit(struct drm_device *drm) +{ + drm_kms_helper_poll_fini(drm); + + drm_vblank_cleanup(drm); + component_unbind_all(drm->dev, drm); + drm_mode_config_cleanup(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, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif +}; + +static struct drm_driver mtk_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME | + DRIVER_ATOMIC, + + .get_vblank_counter = drm_vblank_count, + .enable_vblank = mtk_drm_crtc_enable_vblank, + .disable_vblank = mtk_drm_crtc_disable_vblank, + + .gem_free_object = mtk_drm_gem_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .dumb_create = mtk_drm_gem_dumb_create, + .dumb_map_offset = mtk_drm_gem_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_get_sg_table = 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, + .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 (!drm) + return -ENOMEM; + + drm_dev_set_unique(drm, dev_name(dev)); + + 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; + + ret = drm_connector_register_all(drm); + if (ret < 0) + goto err_unregister; + + return 0; + +err_unregister: + drm_dev_unregister(drm); +err_deinit: + mtk_drm_kms_deinit(drm); +err_free: + drm_dev_unref(drm); + return ret; +} + +static void mtk_drm_unbind(struct device *dev) +{ + struct mtk_drm_private *private = dev_get_drvdata(dev); + + drm_put_dev(private->drm); + 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,mt8173-disp-ovl", .data = (void *)MTK_DISP_OVL }, + { .compatible = "mediatek,mt8173-disp-rdma", .data = (void *)MTK_DISP_RDMA }, + { .compatible = "mediatek,mt8173-disp-wdma", .data = (void *)MTK_DISP_WDMA }, + { .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,mt8173-dsi", .data = (void *)MTK_DSI }, + { .compatible = "mediatek,mt8173-dpi", .data = (void *)MTK_DPI }, + { .compatible = "mediatek,mt8173-disp-mutex", .data = (void *)MTK_DISP_MUTEX }, + { .compatible = "mediatek,mt8173-disp-pwm", .data = (void *)MTK_DISP_PWM }, + { .compatible = "mediatek,mt8173-disp-od", .data = (void *)MTK_DISP_OD }, + { } +}; + +static int mtk_drm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_drm_private *private; + struct resource *mem; + 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; + + mutex_init(&private->commit.lock); + INIT_WORK(&private->commit.work, mtk_atomic_work); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + private->config_regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(private->config_regs)) { + ret = PTR_ERR(private->config_regs); + dev_err(dev, "Failed to ioremap mmsys-config resource: %d\n", + ret); + return ret; + } + + /* Iterate over sibling DISP function blocks */ + for_each_child_of_node(dev->of_node->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 %s\n", + node->full_name); + 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 %s\n", + node->full_name); + continue; + } + + private->comp_node[comp_id] = of_node_get(node); + + /* + * Currently only the 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_OVL || + comp_type == MTK_DISP_RDMA || + comp_type == MTK_DSI || + comp_type == MTK_DPI) { + dev_info(dev, "Adding component match for %s\n", + node->full_name); + 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; + goto err_node; + } + + ret = mtk_ddp_comp_init(dev, node, comp, comp_id, NULL); + if (ret) + 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]); + return ret; +} + +static int mtk_drm_remove(struct platform_device *pdev) +{ + struct mtk_drm_private *private = platform_get_drvdata(pdev); + struct drm_device *drm = private->drm; + int i; + + drm_connector_unregister_all(drm); + drm_dev_unregister(drm); + mtk_drm_kms_deinit(drm); + drm_dev_unref(drm); + + 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; + + drm_kms_helper_poll_disable(drm); + + private->suspend_state = drm_atomic_helper_suspend(drm); + if (IS_ERR(private->suspend_state)) { + drm_kms_helper_poll_enable(drm); + return PTR_ERR(private->suspend_state); + } + + DRM_DEBUG_DRIVER("mtk_drm_sys_suspend\n"); + return 0; +} + +static int mtk_drm_sys_resume(struct device *dev) +{ + struct mtk_drm_private *private = dev_get_drvdata(dev); + struct drm_device *drm = private->drm; + + drm_atomic_helper_resume(drm, private->suspend_state); + drm_kms_helper_poll_enable(drm); + + DRM_DEBUG_DRIVER("mtk_drm_sys_resume\n"); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(mtk_drm_pm_ops, mtk_drm_sys_suspend, + mtk_drm_sys_resume); + +static const struct of_device_id mtk_drm_of_ids[] = { + { .compatible = "mediatek,mt8173-mmsys", }, + { } +}; + +static struct platform_driver mtk_drm_platform_driver = { + .probe = mtk_drm_probe, + .remove = mtk_drm_remove, + .driver = { + .name = "mediatek-drm", + .of_match_table = mtk_drm_of_ids, + .pm = &mtk_drm_pm_ops, + }, +}; + +static struct platform_driver * const mtk_drm_drivers[] = { + &mtk_ddp_driver, + &mtk_disp_ovl_driver, + &mtk_disp_rdma_driver, + &mtk_dpi_driver, + &mtk_drm_platform_driver, + &mtk_dsi_driver, + &mtk_mipi_tx_driver, +}; + +static int __init mtk_drm_init(void) +{ + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(mtk_drm_drivers); i++) { + ret = platform_driver_register(mtk_drm_drivers[i]); + if (ret < 0) { + pr_err("Failed to register %s driver: %d\n", + mtk_drm_drivers[i]->driver.name, ret); + goto err; + } + } + + return 0; + +err: + while (--i >= 0) + platform_driver_unregister(mtk_drm_drivers[i]); + + return ret; +} + +static void __exit mtk_drm_exit(void) +{ + int i; + + for (i = ARRAY_SIZE(mtk_drm_drivers) - 1; i >= 0; i--) + platform_driver_unregister(mtk_drm_drivers[i]); +} + +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..aa9389446 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MTK_DRM_DRV_H +#define MTK_DRM_DRV_H + +#include <linux/io.h> +#include "mtk_drm_ddp_comp.h" + +#define MAX_CRTC 2 +#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_drm_private { + struct drm_device *drm; + struct device *dma_dev; + + struct drm_crtc *crtc[MAX_CRTC]; + unsigned int num_pipes; + + struct device_node *mutex_node; + struct device *mutex_dev; + void __iomem *config_regs; + struct device_node *comp_node[DDP_COMPONENT_ID_MAX]; + struct mtk_ddp_comp *ddp_comp[DDP_COMPONENT_ID_MAX]; + + struct { + struct drm_atomic_state *state; + struct work_struct work; + struct mutex lock; + } commit; + + struct drm_atomic_state *suspend_state; +}; + +extern struct platform_driver mtk_ddp_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_fb.c b/drivers/gpu/drm/mediatek/mtk_drm_fb.c new file mode 100644 index 000000000..147df8539 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_fb.c @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem.h> +#include <linux/dma-buf.h> +#include <linux/reservation.h> + +#include "mtk_drm_drv.h" +#include "mtk_drm_fb.h" +#include "mtk_drm_gem.h" + +/* + * mtk specific framebuffer structure. + * + * @fb: drm framebuffer object. + * @gem_obj: array of gem objects. + */ +struct mtk_drm_fb { + struct drm_framebuffer base; + /* For now we only support a single plane */ + struct drm_gem_object *gem_obj; +}; + +#define to_mtk_fb(x) container_of(x, struct mtk_drm_fb, base) + +struct drm_gem_object *mtk_fb_get_gem_obj(struct drm_framebuffer *fb) +{ + struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb); + + return mtk_fb->gem_obj; +} + +static int mtk_drm_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, + unsigned int *handle) +{ + struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb); + + return drm_gem_handle_create(file_priv, mtk_fb->gem_obj, handle); +} + +static void mtk_drm_fb_destroy(struct drm_framebuffer *fb) +{ + struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb); + + drm_framebuffer_cleanup(fb); + + drm_gem_object_unreference_unlocked(mtk_fb->gem_obj); + + kfree(mtk_fb); +} + +static const struct drm_framebuffer_funcs mtk_drm_fb_funcs = { + .create_handle = mtk_drm_fb_create_handle, + .destroy = mtk_drm_fb_destroy, +}; + +static struct mtk_drm_fb *mtk_drm_framebuffer_init(struct drm_device *dev, + const struct drm_mode_fb_cmd2 *mode, + struct drm_gem_object *obj) +{ + struct mtk_drm_fb *mtk_fb; + int ret; + + if (drm_format_num_planes(mode->pixel_format) != 1) + return ERR_PTR(-EINVAL); + + mtk_fb = kzalloc(sizeof(*mtk_fb), GFP_KERNEL); + if (!mtk_fb) + return ERR_PTR(-ENOMEM); + + drm_helper_mode_fill_fb_struct(&mtk_fb->base, mode); + + mtk_fb->gem_obj = obj; + + ret = drm_framebuffer_init(dev, &mtk_fb->base, &mtk_drm_fb_funcs); + if (ret) { + DRM_ERROR("failed to initialize framebuffer\n"); + kfree(mtk_fb); + return ERR_PTR(ret); + } + + return mtk_fb; +} + +/* + * Wait for any exclusive fence in fb's gem object's reservation object. + * + * Returns -ERESTARTSYS if interrupted, else 0. + */ +int mtk_fb_wait(struct drm_framebuffer *fb) +{ + struct drm_gem_object *gem; + struct reservation_object *resv; + long ret; + + if (!fb) + return 0; + + gem = mtk_fb_get_gem_obj(fb); + if (!gem || !gem->dma_buf || !gem->dma_buf->resv) + return 0; + + resv = gem->dma_buf->resv; + ret = reservation_object_wait_timeout_rcu(resv, false, true, + MAX_SCHEDULE_TIMEOUT); + /* MAX_SCHEDULE_TIMEOUT on success, -ERESTARTSYS if interrupted */ + if (WARN_ON(ret < 0)) + return ret; + + return 0; +} + +struct drm_framebuffer *mtk_drm_mode_fb_create(struct drm_device *dev, + struct drm_file *file, + const struct drm_mode_fb_cmd2 *cmd) +{ + struct mtk_drm_fb *mtk_fb; + struct drm_gem_object *gem; + unsigned int width = cmd->width; + unsigned int height = cmd->height; + unsigned int size, bpp; + int ret; + + if (drm_format_num_planes(cmd->pixel_format) != 1) + return ERR_PTR(-EINVAL); + + gem = drm_gem_object_lookup(file, cmd->handles[0]); + if (!gem) + return ERR_PTR(-ENOENT); + + bpp = drm_format_plane_cpp(cmd->pixel_format, 0); + size = (height - 1) * cmd->pitches[0] + width * bpp; + size += cmd->offsets[0]; + + if (gem->size < size) { + ret = -EINVAL; + goto unreference; + } + + mtk_fb = mtk_drm_framebuffer_init(dev, cmd, gem); + if (IS_ERR(mtk_fb)) { + ret = PTR_ERR(mtk_fb); + goto unreference; + } + + return &mtk_fb->base; + +unreference: + drm_gem_object_unreference_unlocked(gem); + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/mediatek/mtk_drm_fb.h b/drivers/gpu/drm/mediatek/mtk_drm_fb.h new file mode 100644 index 000000000..9b2ae345a --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_fb.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MTK_DRM_FB_H +#define MTK_DRM_FB_H + +struct drm_gem_object *mtk_fb_get_gem_obj(struct drm_framebuffer *fb); +int mtk_fb_wait(struct drm_framebuffer *fb); +struct drm_framebuffer *mtk_drm_mode_fb_create(struct drm_device *dev, + struct drm_file *file, + const struct drm_mode_fb_cmd2 *cmd); + +#endif /* MTK_DRM_FB_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..fa2ec0cd0 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_gem.c @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_gem.h> +#include <linux/dma-buf.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; + + init_dma_attrs(&mtk_gem->dma_attrs); + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &mtk_gem->dma_attrs); + + if (!alloc_kmap) + dma_set_attr(DMA_ATTR_NO_KERNEL_MAPPING, &mtk_gem->dma_attrs); + + 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_unreference_unlocked(&mtk_gem->base); + + return 0; + +err_handle_create: + mtk_drm_gem_free_object(&mtk_gem->base); + return ret; +} + +int mtk_drm_gem_dumb_map_offset(struct drm_file *file_priv, + struct drm_device *dev, uint32_t handle, + uint64_t *offset) +{ + struct drm_gem_object *obj; + int ret; + + obj = drm_gem_object_lookup(file_priv, handle); + if (!obj) { + DRM_ERROR("failed to lookup gem object.\n"); + return -EINVAL; + } + + ret = drm_gem_create_mmap_offset(obj); + if (ret) + goto out; + + *offset = drm_vma_node_offset_addr(&obj->vma_node); + DRM_DEBUG_KMS("offset = 0x%llx\n", *offset); + +out: + drm_gem_object_unreference_unlocked(obj); + 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; + vma->vm_pgoff = 0; + + ret = dma_mmap_attrs(priv->dma_dev, vma, mtk_gem->cookie, + mtk_gem->dma_addr, obj->size, &mtk_gem->dma_attrs); + if (ret) + drm_gem_vm_close(vma); + + 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; + + 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; + int ret; + struct scatterlist *s; + unsigned int i; + dma_addr_t expected; + + mtk_gem = mtk_drm_gem_init(dev, attach->dmabuf->size); + + if (IS_ERR(mtk_gem)) + return ERR_PTR(PTR_ERR(mtk_gem)); + + expected = sg_dma_address(sg->sgl); + for_each_sg(sg->sgl, s, sg->nents, i) { + if (sg_dma_address(s) != expected) { + DRM_ERROR("sg_table is not contiguous"); + ret = -EINVAL; + goto err_gem_free; + } + expected = sg_dma_address(s) + sg_dma_len(s); + } + + mtk_gem->dma_addr = sg_dma_address(sg->sgl); + mtk_gem->sg = sg; + + return &mtk_gem->base; + +err_gem_free: + kfree(mtk_gem); + return ERR_PTR(ret); +} 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..3a2a5624a --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_gem.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _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; + struct dma_attrs dma_attrs; + struct sg_table *sg; +}; + +#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_dumb_map_offset(struct drm_file *file_priv, + struct drm_device *dev, uint32_t handle, + uint64_t *offset); +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); + +#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..51bc8988f --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.c @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: CK Hu <ck.hu@mediatek.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_plane_helper.h> + +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp_comp.h" +#include "mtk_drm_drv.h" +#include "mtk_drm_fb.h" +#include "mtk_drm_gem.h" +#include "mtk_drm_plane.h" + +static const u32 formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGB565, +}; + +static void mtk_plane_enable(struct mtk_drm_plane *mtk_plane, bool enable, + dma_addr_t addr, struct drm_rect *dest) +{ + struct drm_plane *plane = &mtk_plane->base; + struct mtk_plane_state *state = to_mtk_plane_state(plane->state); + unsigned int pitch, format; + int x, y; + + if (WARN_ON(!plane->state || (enable && !plane->state->fb))) + return; + + if (plane->state->fb) { + pitch = plane->state->fb->pitches[0]; + format = plane->state->fb->pixel_format; + } else { + pitch = 0; + format = DRM_FORMAT_RGBA8888; + } + + x = plane->state->crtc_x; + y = plane->state->crtc_y; + + if (x < 0) { + addr -= x * 4; + x = 0; + } + + if (y < 0) { + addr -= y * pitch; + y = 0; + } + + state->pending.enable = enable; + state->pending.pitch = pitch; + state->pending.format = format; + state->pending.addr = addr; + state->pending.x = x; + state->pending.y = y; + state->pending.width = dest->x2 - dest->x1; + state->pending.height = dest->y2 - dest->y1; + wmb(); /* Make sure the above parameters are set before update */ + state->pending.dirty = true; +} + +static void mtk_plane_reset(struct drm_plane *plane) +{ + struct mtk_plane_state *state; + + if (plane->state) { + if (plane->state->fb) + drm_framebuffer_unreference(plane->state->fb); + + 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 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; + bool visible; + struct drm_rect dest = { + .x1 = state->crtc_x, + .y1 = state->crtc_y, + .x2 = state->crtc_x + state->crtc_w, + .y2 = state->crtc_y + state->crtc_h, + }; + struct drm_rect src = { + /* 16.16 fixed point */ + .x1 = state->src_x, + .y1 = state->src_y, + .x2 = state->src_x + state->src_w, + .y2 = state->src_y + state->src_h, + }; + struct drm_rect clip = { 0, }; + + if (!fb) + return 0; + + if (!mtk_fb_get_gem_obj(fb)) { + DRM_DEBUG_KMS("buffer is null\n"); + return -EFAULT; + } + + if (!state->crtc) + return 0; + + crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + clip.x2 = crtc_state->mode.hdisplay; + clip.y2 = crtc_state->mode.vdisplay; + + return drm_plane_helper_check_update(plane, state->crtc, fb, + &src, &dest, &clip, + DRM_PLANE_HELPER_NO_SCALING, + DRM_PLANE_HELPER_NO_SCALING, + true, true, &visible); +} + +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 = state->base.crtc; + struct drm_gem_object *gem; + struct mtk_drm_gem_obj *mtk_gem; + struct mtk_drm_plane *mtk_plane = to_mtk_plane(plane); + struct drm_rect dest = { + .x1 = state->base.crtc_x, + .y1 = state->base.crtc_y, + .x2 = state->base.crtc_x + state->base.crtc_w, + .y2 = state->base.crtc_y + state->base.crtc_h, + }; + struct drm_rect clip = { 0, }; + + if (!crtc) + return; + + clip.x2 = state->base.crtc->state->mode.hdisplay; + clip.y2 = state->base.crtc->state->mode.vdisplay; + drm_rect_intersect(&dest, &clip); + + gem = mtk_fb_get_gem_obj(state->base.fb); + mtk_gem = to_mtk_gem_obj(gem); + mtk_plane_enable(mtk_plane, true, mtk_gem->dma_addr, &dest); +} + +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 const struct drm_plane_helper_funcs mtk_plane_helper_funcs = { + .atomic_check = mtk_plane_atomic_check, + .atomic_update = mtk_plane_atomic_update, + .atomic_disable = mtk_plane_atomic_disable, +}; + +int mtk_plane_init(struct drm_device *dev, struct mtk_drm_plane *mtk_plane, + unsigned long possible_crtcs, enum drm_plane_type type, + unsigned int zpos) +{ + int err; + + err = drm_universal_plane_init(dev, &mtk_plane->base, possible_crtcs, + &mtk_plane_funcs, formats, + ARRAY_SIZE(formats), type, NULL); + if (err) { + DRM_ERROR("failed to initialize plane\n"); + return err; + } + + drm_plane_helper_add(&mtk_plane->base, &mtk_plane_helper_funcs); + mtk_plane->idx = zpos; + + 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..72a7b3e4c --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: CK Hu <ck.hu@mediatek.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MTK_DRM_PLANE_H_ +#define _MTK_DRM_PLANE_H_ + +#include <drm/drm_crtc.h> +#include <linux/types.h> + +struct mtk_drm_plane { + struct drm_plane base; + unsigned int idx; +}; + +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; + bool dirty; +}; + +struct mtk_plane_state { + struct drm_plane_state base; + struct mtk_plane_pending_state pending; +}; + +static inline struct mtk_drm_plane *to_mtk_plane(struct drm_plane *plane) +{ + return container_of(plane, struct mtk_drm_plane, base); +} + +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 mtk_drm_plane *mtk_plane, + unsigned long possible_crtcs, enum drm_plane_type type, + unsigned int zpos); + +#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..769559124 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c @@ -0,0 +1,911 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/of_graph.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <video/videomode.h> + +#include "mtk_drm_ddp_comp.h" + +#define DSI_VIDEO_FIFO_DEPTH (1920 / 4) +#define DSI_HOST_FIFO_DEPTH 64 + +#define DSI_START 0x00 + +#define DSI_CON_CTRL 0x10 +#define DSI_RESET BIT(0) +#define DSI_EN BIT(1) + +#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 (2 << 0) +#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_HSA_WC 0x50 +#define DSI_HBP_WC 0x54 +#define DSI_HFP_WC 0x58 + +#define DSI_HSTX_CKL_WC 0x64 + +#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_PRPR (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_PRPR (0xff << 0) +#define CLK_HS_POST (0xff << 8) +#define CLK_HS_EXIT (0xff << 16) + +#define NS_TO_CYCLE(n, c) ((n) / (c) + (((n) % (c)) ? 1 : 0)) + +struct phy; + +struct mtk_dsi { + struct mtk_ddp_comp ddp_comp; + struct device *dev; + struct mipi_dsi_host host; + struct drm_encoder encoder; + struct drm_connector conn; + struct drm_panel *panel; + struct drm_bridge *bridge; + 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; + int refcount; + bool enabled; +}; + +static inline struct mtk_dsi *encoder_to_dsi(struct drm_encoder *e) +{ + return container_of(e, struct mtk_dsi, encoder); +} + +static inline struct mtk_dsi *connector_to_dsi(struct drm_connector *c) +{ + return container_of(c, struct mtk_dsi, conn); +} + +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 dsi_phy_timconfig(struct mtk_dsi *dsi) +{ + u32 timcon0, timcon1, timcon2, timcon3; + unsigned int ui, cycle_time; + unsigned int lpx; + + ui = 1000 / dsi->data_rate + 0x01; + cycle_time = 8000 / dsi->data_rate + 0x01; + lpx = 5; + + timcon0 = (8 << 24) | (0xa << 16) | (0x6 << 8) | lpx; + timcon1 = (7 << 24) | (5 * lpx << 16) | ((3 * lpx) / 2) << 8 | + (4 * lpx); + timcon2 = ((NS_TO_CYCLE(0x64, cycle_time) + 0xa) << 24) | + (NS_TO_CYCLE(0x150, cycle_time) << 16); + timcon3 = (2 * lpx) << 16 | NS_TO_CYCLE(80 + 52 * ui, cycle_time) << 8 | + NS_TO_CYCLE(0x40, cycle_time); + + 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(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 int mtk_dsi_poweron(struct mtk_dsi *dsi) +{ + struct device *dev = dsi->dev; + int ret; + + if (++dsi->refcount != 1) + return 0; + + /** + * data_rate = (pixel_clock / 1000) * pixel_dipth * mipi_ratio; + * pixel_clock unit is Khz, data_rata unit is MHz, so need divide 1000. + * mipi_ratio is mipi clk coefficient for balance the pixel clk in mipi. + * we set mipi_ratio is 1.05. + */ + dsi->data_rate = dsi->vm.pixelclock * 3 * 21 / (1 * 1000 * 10); + + ret = clk_set_rate(dsi->hs_clk, dsi->data_rate * 1000000); + 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); + mtk_dsi_reset(dsi); + dsi_phy_timconfig(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 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 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 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 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 dsi_clk_hs_state(struct mtk_dsi *dsi) +{ + u32 tmp_reg1; + + tmp_reg1 = readl(dsi->regs + DSI_PHY_LCCON); + return ((tmp_reg1 & LC_HS_TX_EN) == 1) ? true : false; +} + +static void dsi_clk_hs_mode(struct mtk_dsi *dsi, bool enter) +{ + if (enter && !dsi_clk_hs_state(dsi)) + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, LC_HS_TX_EN); + else if (!enter && dsi_clk_hs_state(dsi)) + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, 0); +} + +static void dsi_set_mode(struct mtk_dsi *dsi) +{ + u32 vid_mode = CMD_MODE; + + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) { + vid_mode = SYNC_PULSE_MODE; + + if ((dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) && + !(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)) + vid_mode = BURST_MODE; + } + + writel(vid_mode, dsi->regs + DSI_MODE_CTRL); +} + +static void 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 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; + } + + writel(tmp_reg, dsi->regs + DSI_TXRX_CTRL); +} + +static void dsi_ps_control(struct mtk_dsi *dsi) +{ + unsigned int 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 dsi_config_vdo_timing(struct mtk_dsi *dsi) +{ + unsigned int horizontal_sync_active_byte; + unsigned int horizontal_backporch_byte; + unsigned int horizontal_frontporch_byte; + unsigned int dsi_tmp_buf_bpp; + + 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); + + 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); + + horizontal_frontporch_byte = (vm->hfront_porch * dsi_tmp_buf_bpp - 12); + + 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); + + 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_poweroff(struct mtk_dsi *dsi) +{ + if (WARN_ON(dsi->refcount == 0)) + return; + + if (--dsi->refcount != 0) + return; + + dsi_lane0_ulp_mode_enter(dsi); + dsi_clk_ulp_mode_enter(dsi); + + mtk_dsi_disable(dsi); + + clk_disable_unprepare(dsi->engine_clk); + clk_disable_unprepare(dsi->digital_clk); + + phy_power_off(dsi->phy); +} + +static void mtk_output_dsi_enable(struct mtk_dsi *dsi) +{ + int ret; + + if (dsi->enabled) + return; + + if (dsi->panel) { + if (drm_panel_prepare(dsi->panel)) { + DRM_ERROR("failed to setup the panel\n"); + return; + } + } + + ret = mtk_dsi_poweron(dsi); + if (ret < 0) { + DRM_ERROR("failed to power on dsi\n"); + return; + } + + dsi_rxtx_control(dsi); + + dsi_clk_ulp_mode_leave(dsi); + dsi_lane0_ulp_mode_leave(dsi); + dsi_clk_hs_mode(dsi, 0); + dsi_set_mode(dsi); + + dsi_ps_control_vact(dsi); + dsi_config_vdo_timing(dsi); + + dsi_set_mode(dsi); + 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; + + if (dsi->panel) { + if (drm_panel_disable(dsi->panel)) { + DRM_ERROR("failed to disable the panel\n"); + return; + } + } + + mtk_dsi_poweroff(dsi); + + dsi->enabled = false; +} + +static void mtk_dsi_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs mtk_dsi_encoder_funcs = { + .destroy = mtk_dsi_encoder_destroy, +}; + +static bool mtk_dsi_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void mtk_dsi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + struct mtk_dsi *dsi = encoder_to_dsi(encoder); + + dsi->vm.pixelclock = adjusted->clock; + dsi->vm.hactive = adjusted->hdisplay; + dsi->vm.hback_porch = adjusted->htotal - adjusted->hsync_end; + dsi->vm.hfront_porch = adjusted->hsync_start - adjusted->hdisplay; + dsi->vm.hsync_len = adjusted->hsync_end - adjusted->hsync_start; + + dsi->vm.vactive = adjusted->vdisplay; + dsi->vm.vback_porch = adjusted->vtotal - adjusted->vsync_end; + dsi->vm.vfront_porch = adjusted->vsync_start - adjusted->vdisplay; + dsi->vm.vsync_len = adjusted->vsync_end - adjusted->vsync_start; +} + +static void mtk_dsi_encoder_disable(struct drm_encoder *encoder) +{ + struct mtk_dsi *dsi = encoder_to_dsi(encoder); + + mtk_output_dsi_disable(dsi); +} + +static void mtk_dsi_encoder_enable(struct drm_encoder *encoder) +{ + struct mtk_dsi *dsi = encoder_to_dsi(encoder); + + mtk_output_dsi_enable(dsi); +} + +static enum drm_connector_status mtk_dsi_connector_detect( + struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static int mtk_dsi_connector_get_modes(struct drm_connector *connector) +{ + struct mtk_dsi *dsi = connector_to_dsi(connector); + + return drm_panel_get_modes(dsi->panel); +} + +static struct drm_encoder *mtk_dsi_connector_best_encoder( + struct drm_connector *connector) +{ + struct mtk_dsi *dsi = connector_to_dsi(connector); + + return &dsi->encoder; +} + +static const struct drm_encoder_helper_funcs mtk_dsi_encoder_helper_funcs = { + .mode_fixup = mtk_dsi_encoder_mode_fixup, + .mode_set = mtk_dsi_encoder_mode_set, + .disable = mtk_dsi_encoder_disable, + .enable = mtk_dsi_encoder_enable, +}; + +static const struct drm_connector_funcs mtk_dsi_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .detect = mtk_dsi_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_connector_helper_funcs + mtk_dsi_connector_helper_funcs = { + .get_modes = mtk_dsi_connector_get_modes, + .best_encoder = mtk_dsi_connector_best_encoder, +}; + +static int mtk_drm_attach_bridge(struct drm_bridge *bridge, + struct drm_encoder *encoder) +{ + int ret; + + if (!bridge) + return -ENOENT; + + encoder->bridge = bridge; + bridge->encoder = encoder; + ret = drm_bridge_attach(encoder->dev, bridge); + if (ret) { + DRM_ERROR("Failed to attach bridge to drm\n"); + encoder->bridge = NULL; + bridge->encoder = NULL; + } + + return ret; +} + +static int mtk_dsi_create_connector(struct drm_device *drm, struct mtk_dsi *dsi) +{ + int ret; + + ret = drm_connector_init(drm, &dsi->conn, &mtk_dsi_connector_funcs, + DRM_MODE_CONNECTOR_DSI); + if (ret) { + DRM_ERROR("Failed to connector init to drm\n"); + return ret; + } + + drm_connector_helper_add(&dsi->conn, &mtk_dsi_connector_helper_funcs); + + dsi->conn.dpms = DRM_MODE_DPMS_OFF; + drm_mode_connector_attach_encoder(&dsi->conn, &dsi->encoder); + + if (dsi->panel) { + ret = drm_panel_attach(dsi->panel, &dsi->conn); + if (ret) { + DRM_ERROR("Failed to attach panel to drm\n"); + goto err_connector_cleanup; + } + } + + return 0; + +err_connector_cleanup: + drm_connector_cleanup(&dsi->conn); + return ret; +} + +static int mtk_dsi_create_conn_enc(struct drm_device *drm, struct mtk_dsi *dsi) +{ + int ret; + + ret = drm_encoder_init(drm, &dsi->encoder, &mtk_dsi_encoder_funcs, + DRM_MODE_ENCODER_DSI, NULL); + if (ret) { + DRM_ERROR("Failed to encoder init to drm\n"); + return ret; + } + drm_encoder_helper_add(&dsi->encoder, &mtk_dsi_encoder_helper_funcs); + + /* + * Currently display data paths are statically assigned to a crtc each. + * crtc 0 is OVL0 -> COLOR0 -> AAL -> OD -> RDMA0 -> UFOE -> DSI0 + */ + dsi->encoder.possible_crtcs = 1; + + /* If there's a bridge, attach to it and let it create the connector */ + ret = mtk_drm_attach_bridge(dsi->bridge, &dsi->encoder); + if (ret) { + /* Otherwise create our own connector and attach to a panel */ + ret = mtk_dsi_create_connector(drm, dsi); + if (ret) + goto err_encoder_cleanup; + } + + return 0; + +err_encoder_cleanup: + drm_encoder_cleanup(&dsi->encoder); + return ret; +} + +static void mtk_dsi_destroy_conn_enc(struct mtk_dsi *dsi) +{ + drm_encoder_cleanup(&dsi->encoder); + /* Skip connector cleanup if creation was delegated to the bridge */ + if (dsi->conn.dev) + drm_connector_cleanup(&dsi->conn); +} + +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; + + if (dsi->conn.dev) + drm_helper_hpd_irq_event(dsi->conn.dev); + + return 0; +} + +static int mtk_dsi_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct mtk_dsi *dsi = host_to_dsi(host); + + if (dsi->conn.dev) + drm_helper_hpd_irq_event(dsi->conn.dev); + + return 0; +} + +static const struct mipi_dsi_host_ops mtk_dsi_ops = { + .attach = mtk_dsi_host_attach, + .detach = mtk_dsi_host_detach, +}; + +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 %s: %d\n", + dev->of_node->full_name, ret); + return ret; + } + + ret = mipi_dsi_host_register(&dsi->host); + if (ret < 0) { + dev_err(dev, "failed to register DSI host: %d\n", ret); + goto err_ddp_comp_unregister; + } + + ret = mtk_dsi_create_conn_enc(drm, dsi); + if (ret) { + DRM_ERROR("Encoder create failed with %d\n", ret); + goto err_unregister; + } + + return 0; + +err_unregister: + mipi_dsi_host_unregister(&dsi->host); +err_ddp_comp_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); + + mtk_dsi_destroy_conn_enc(dsi); + mipi_dsi_host_unregister(&dsi->host); + 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 device_node *remote_node, *endpoint; + struct resource *regs; + 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; + + endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (endpoint) { + remote_node = of_graph_get_remote_port_parent(endpoint); + if (!remote_node) { + dev_err(dev, "No panel connected\n"); + return -ENODEV; + } + + dsi->bridge = of_drm_find_bridge(remote_node); + dsi->panel = of_drm_find_panel(remote_node); + of_node_put(remote_node); + if (!dsi->bridge && !dsi->panel) { + dev_info(dev, "Waiting for bridge or panel driver\n"); + return -EPROBE_DEFER; + } + } + + dsi->engine_clk = devm_clk_get(dev, "engine"); + if (IS_ERR(dsi->engine_clk)) { + ret = PTR_ERR(dsi->engine_clk); + dev_err(dev, "Failed to get engine clock: %d\n", ret); + return ret; + } + + dsi->digital_clk = devm_clk_get(dev, "digital"); + if (IS_ERR(dsi->digital_clk)) { + ret = PTR_ERR(dsi->digital_clk); + dev_err(dev, "Failed to get digital clock: %d\n", ret); + return ret; + } + + 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); + return ret; + } + + 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); + return ret; + } + + 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); + return ret; + } + + 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); + return comp_id; + } + + 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); + return ret; + } + + platform_set_drvdata(pdev, dsi); + + return component_add(&pdev->dev, &mtk_dsi_component_ops); +} + +static int mtk_dsi_remove(struct platform_device *pdev) +{ + struct mtk_dsi *dsi = platform_get_drvdata(pdev); + + mtk_output_dsi_disable(dsi); + component_del(&pdev->dev, &mtk_dsi_component_ops); + + return 0; +} + +static const struct of_device_id mtk_dsi_of_match[] = { + { .compatible = "mediatek,mt8173-dsi" }, + { }, +}; + +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_mipi_tx.c b/drivers/gpu/drm/mediatek/mtk_mipi_tx.c new file mode 100644 index 000000000..cf8f38d39 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_mipi_tx.c @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.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_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) + +struct mtk_mipi_tx { + struct device *dev; + void __iomem *regs; + unsigned int data_rate; + struct clk_hw pll_hw; + struct clk *pll; +}; + +static 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); +} + +static 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); +} + +static 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); +} + +static 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); +} + +static int mtk_mipi_tx_pll_prepare(struct clk_hw *hw) +{ + struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw); + unsigned int 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); + + 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_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 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; +} + +static 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 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 int mtk_mipi_tx_power_on_signal(struct phy *phy) +{ + struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy); + unsigned int 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); + + return 0; +} + +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 */ + mtk_mipi_tx_power_on_signal(phy); + + return 0; +} + +static void mtk_mipi_tx_power_off_signal(struct phy *phy) +{ + struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy); + unsigned int 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); +} + +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 */ + mtk_mipi_tx_power_off_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 int mtk_mipi_tx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_mipi_tx *mipi_tx; + struct resource *mem; + struct clk *ref_clk; + const char *ref_clk_name; + struct clk_init_data clk_init = { + .ops = &mtk_mipi_tx_pll_ops, + .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; + + 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; + } + 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; + } + + 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)) { + ret = PTR_ERR(phy_provider); + return ret; + } + + mipi_tx->dev = dev; + + 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,mt8173-mipi-tx", }, + {}, +}; + +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, + }, +}; |