[RFC PATCH 03/16] drm/rockchip: Add EBC platform driver skeleton

Samuel Holland samuel at sholland.org
Wed Apr 13 15:19:03 PDT 2022


The Rockchip E-Book Controller (EBC) is a timing controller (TCON)
responsible for sending timing signals and pixel update waveforms to an
electrophoretic display (EPD). The EBC has several modes of operation.
In direct mode, it reads precomputed source driver polarity data from a
series of buffers in RAM. In the other modes, it reads pixel luminance
data from RAM, and uses a lookup table (LUT) to compute the source
driver polarity for each phase within the waveform.

This commit adds a platform driver skeleton for the EBC, containing the
IRQ handler and runtime PM hooks. The EBC only needs to be powered up
when the display is actively being refreshed. regcache is used to allow
configuration changes (i.e. modeset) while the EBC is powered down.

While two of the regulator consumers here actually power the display
panel, not the EBC hardware, they are consumed here because again they
are only needed during display refreshes. They do not match the normal
panel prepare/enable lifecycle.

Signed-off-by: Samuel Holland <samuel at sholland.org>
---

 drivers/gpu/drm/Makefile                |   2 +-
 drivers/gpu/drm/rockchip/Kconfig        |  11 +
 drivers/gpu/drm/rockchip/Makefile       |   2 +
 drivers/gpu/drm/rockchip/rockchip_ebc.c | 324 ++++++++++++++++++++++++
 4 files changed, 338 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/rockchip/rockchip_ebc.c

diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 49380ccfe9d6..e940f81a2acf 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -93,7 +93,7 @@ obj-$(CONFIG_DRM_VGEM)	+= vgem/
 obj-$(CONFIG_DRM_VKMS)	+= vkms/
 obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/
 obj-$(CONFIG_DRM_EXYNOS) +=exynos/
-obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/
+obj-y			+=rockchip/
 obj-$(CONFIG_DRM_GMA500) += gma500/
 obj-$(CONFIG_DRM_UDL) += udl/
 obj-$(CONFIG_DRM_AST) += ast/
diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
index fa5cfda4e90e..9d3273a5fd97 100644
--- a/drivers/gpu/drm/rockchip/Kconfig
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -91,3 +91,14 @@ config ROCKCHIP_RK3066_HDMI
 	  for the RK3066 HDMI driver. If you want to enable
 	  HDMI on RK3066 based SoC, you should select this option.
 endif
+
+config DRM_ROCKCHIP_EBC
+	tristate "DRM Support for Rockchip EBC"
+	depends on DRM
+	select DRM_GEM_SHMEM_HELPER
+	select DRM_KMS_HELPER
+	help
+	  This selects DRM/KMS support for the Rockchip E-Book Controller (EBC).
+	  Choose this option if you have a Rockchip SoC and an electrophoretic
+	  display. This hardware and driver is separate from the normal Rockchip
+	  display hardware and DRM driver.
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
index 1a56f696558c..e3accc526438 100644
--- a/drivers/gpu/drm/rockchip/Makefile
+++ b/drivers/gpu/drm/rockchip/Makefile
@@ -16,3 +16,5 @@ rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o
 rockchipdrm-$(CONFIG_ROCKCHIP_RK3066_HDMI) += rk3066_hdmi.o
 
 obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o
+
+obj-$(CONFIG_DRM_ROCKCHIP_EBC) += rockchip_ebc.o
diff --git a/drivers/gpu/drm/rockchip/rockchip_ebc.c b/drivers/gpu/drm/rockchip/rockchip_ebc.c
new file mode 100644
index 000000000000..5ed66c6cd2f0
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_ebc.c
@@ -0,0 +1,324 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021-2022 Samuel Holland <samuel at sholland.org>
+ */
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define EBC_DSP_START			0x0000
+#define EBC_DSP_START_DSP_OUT_LOW		BIT(31)
+#define EBC_DSP_START_DSP_SDCE_WIDTH(x)		((x) << 16)
+#define EBC_DSP_START_DSP_EINK_MODE		BIT(13)
+#define EBC_DSP_START_SW_BURST_CTRL		BIT(12)
+#define EBC_DSP_START_DSP_FRM_TOTAL(x)		((x) << 2)
+#define EBC_DSP_START_DSP_RST			BIT(1)
+#define EBC_DSP_START_DSP_FRM_START		BIT(0)
+#define EBC_EPD_CTRL			0x0004
+#define EBC_EPD_CTRL_EINK_MODE_SWAP		BIT(31)
+#define EBC_EPD_CTRL_DSP_GD_END(x)		((x) << 16)
+#define EBC_EPD_CTRL_DSP_GD_ST(x)		((x) << 8)
+#define EBC_EPD_CTRL_DSP_THREE_WIN_MODE		BIT(7)
+#define EBC_EPD_CTRL_DSP_SDDW_MODE		BIT(6)
+#define EBC_EPD_CTRL_EPD_AUO			BIT(5)
+#define EBC_EPD_CTRL_EPD_PWR(x)			((x) << 2)
+#define EBC_EPD_CTRL_EPD_GDRL			BIT(1)
+#define EBC_EPD_CTRL_EPD_SDSHR			BIT(0)
+#define EBC_DSP_CTRL			0x0008
+#define EBC_DSP_CTRL_DSP_SWAP_MODE(x)		((x) << 30)
+#define EBC_DSP_CTRL_DSP_DIFF_MODE		BIT(29)
+#define EBC_DSP_CTRL_DSP_LUT_MODE		BIT(28)
+#define EBC_DSP_CTRL_DSP_VCOM_MODE		BIT(27)
+#define EBC_DSP_CTRL_DSP_GDOE_POL		BIT(26)
+#define EBC_DSP_CTRL_DSP_GDSP_POL		BIT(25)
+#define EBC_DSP_CTRL_DSP_GDCLK_POL		BIT(24)
+#define EBC_DSP_CTRL_DSP_SDCE_POL		BIT(23)
+#define EBC_DSP_CTRL_DSP_SDOE_POL		BIT(22)
+#define EBC_DSP_CTRL_DSP_SDLE_POL		BIT(21)
+#define EBC_DSP_CTRL_DSP_SDCLK_POL		BIT(20)
+#define EBC_DSP_CTRL_DSP_SDCLK_DIV(x)		((x) << 16)
+#define EBC_DSP_CTRL_DSP_BACKGROUND(x)		((x) << 0)
+#define EBC_DSP_HTIMING0		0x000c
+#define EBC_DSP_HTIMING0_DSP_HTOTAL(x)		((x) << 16)
+#define EBC_DSP_HTIMING0_DSP_HS_END(x)		((x) << 0)
+#define EBC_DSP_HTIMING1		0x0010
+#define EBC_DSP_HTIMING1_DSP_HACT_END(x)	((x) << 16)
+#define EBC_DSP_HTIMING1_DSP_HACT_ST(x)		((x) << 0)
+#define EBC_DSP_VTIMING0		0x0014
+#define EBC_DSP_VTIMING0_DSP_VTOTAL(x)		((x) << 16)
+#define EBC_DSP_VTIMING0_DSP_VS_END(x)		((x) << 0)
+#define EBC_DSP_VTIMING1		0x0018
+#define EBC_DSP_VTIMING1_DSP_VACT_END(x)	((x) << 16)
+#define EBC_DSP_VTIMING1_DSP_VACT_ST(x)		((x) << 0)
+#define EBC_DSP_ACT_INFO		0x001c
+#define EBC_DSP_ACT_INFO_DSP_HEIGHT(x)		((x) << 16)
+#define EBC_DSP_ACT_INFO_DSP_WIDTH(x)		((x) << 0)
+#define EBC_WIN_CTRL			0x0020
+#define EBC_WIN_CTRL_WIN2_FIFO_THRESHOLD(x)	((x) << 19)
+#define EBC_WIN_CTRL_WIN_EN			BIT(18)
+#define EBC_WIN_CTRL_AHB_INCR_NUM_REG(x)	((x) << 13)
+#define EBC_WIN_CTRL_AHB_BURST_REG(x)		((x) << 10)
+#define EBC_WIN_CTRL_WIN_FIFO_THRESHOLD(x)	((x) << 2)
+#define EBC_WIN_CTRL_WIN_FMT_Y4			(0x0 << 0)
+#define EBC_WIN_CTRL_WIN_FMT_Y8			(0x1 << 0)
+#define EBC_WIN_CTRL_WIN_FMT_XRGB8888		(0x2 << 0)
+#define EBC_WIN_CTRL_WIN_FMT_RGB565		(0x3 << 0)
+#define EBC_WIN_MST0			0x0024
+#define EBC_WIN_MST1			0x0028
+#define EBC_WIN_VIR			0x002c
+#define EBC_WIN_VIR_WIN_VIR_HEIGHT(x)		((x) << 16)
+#define EBC_WIN_VIR_WIN_VIR_WIDTH(x)		((x) << 0)
+#define EBC_WIN_ACT			0x0030
+#define EBC_WIN_ACT_WIN_ACT_HEIGHT(x)		((x) << 16)
+#define EBC_WIN_ACT_WIN_ACT_WIDTH(x)		((x) << 0)
+#define EBC_WIN_DSP			0x0034
+#define EBC_WIN_DSP_WIN_DSP_HEIGHT(x)		((x) << 16)
+#define EBC_WIN_DSP_WIN_DSP_WIDTH(x)		((x) << 0)
+#define EBC_WIN_DSP_ST			0x0038
+#define EBC_WIN_DSP_ST_WIN_DSP_YST(x)		((x) << 16)
+#define EBC_WIN_DSP_ST_WIN_DSP_XST(x)		((x) << 0)
+#define EBC_INT_STATUS			0x003c
+#define EBC_INT_STATUS_DSP_FRM_INT_NUM(x)	((x) << 12)
+#define EBC_INT_STATUS_LINE_FLAG_INT_CLR	BIT(11)
+#define EBC_INT_STATUS_DSP_FRM_INT_CLR		BIT(10)
+#define EBC_INT_STATUS_DSP_END_INT_CLR		BIT(9)
+#define EBC_INT_STATUS_FRM_END_INT_CLR		BIT(8)
+#define EBC_INT_STATUS_LINE_FLAG_INT_MSK	BIT(7)
+#define EBC_INT_STATUS_DSP_FRM_INT_MSK		BIT(6)
+#define EBC_INT_STATUS_DSP_END_INT_MSK		BIT(5)
+#define EBC_INT_STATUS_FRM_END_INT_MSK		BIT(4)
+#define EBC_INT_STATUS_LINE_FLAG_INT_ST		BIT(3)
+#define EBC_INT_STATUS_DSP_FRM_INT_ST		BIT(2)
+#define EBC_INT_STATUS_DSP_END_INT_ST		BIT(1)
+#define EBC_INT_STATUS_FRM_END_INT_ST		BIT(0)
+#define EBC_VCOM0			0x0040
+#define EBC_VCOM1			0x0044
+#define EBC_VCOM2			0x0048
+#define EBC_VCOM3			0x004c
+#define EBC_CONFIG_DONE			0x0050
+#define EBC_CONFIG_DONE_REG_CONFIG_DONE		BIT(0)
+#define EBC_VNUM			0x0054
+#define EBC_VNUM_DSP_VCNT(x)			((x) << 16)
+#define EBC_VNUM_LINE_FLAG_NUM(x)		((x) << 0)
+#define EBC_WIN_MST2			0x0058
+#define EBC_LUT_DATA			0x1000
+
+#define EBC_NUM_LUT_REGS		0x1000
+#define EBC_NUM_SUPPLIES		3
+
+#define EBC_SUSPEND_DELAY_MS		2000
+
+struct rockchip_ebc {
+	struct clk			*dclk;
+	struct clk			*hclk;
+	struct completion		display_end;
+	struct regmap			*regmap;
+	struct regulator_bulk_data	supplies[EBC_NUM_SUPPLIES];
+};
+
+static int rockchip_ebc_runtime_suspend(struct device *dev)
+{
+	struct rockchip_ebc *ebc = dev_get_drvdata(dev);
+
+	regcache_cache_only(ebc->regmap, true);
+
+	clk_disable_unprepare(ebc->dclk);
+	clk_disable_unprepare(ebc->hclk);
+	regulator_bulk_disable(EBC_NUM_SUPPLIES, ebc->supplies);
+
+	return 0;
+}
+
+static int rockchip_ebc_runtime_resume(struct device *dev)
+{
+	struct rockchip_ebc *ebc = dev_get_drvdata(dev);
+	int ret;
+
+	ret = regulator_bulk_enable(EBC_NUM_SUPPLIES, ebc->supplies);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(ebc->hclk);
+	if (ret)
+		goto err_disable_supplies;
+
+	ret = clk_prepare_enable(ebc->dclk);
+	if (ret)
+		goto err_disable_hclk;
+
+	regcache_cache_only(ebc->regmap, false);
+	regcache_mark_dirty(ebc->regmap);
+	regcache_sync(ebc->regmap);
+
+	regmap_write(ebc->regmap, EBC_INT_STATUS,
+		     EBC_INT_STATUS_DSP_END_INT_CLR |
+		     EBC_INT_STATUS_LINE_FLAG_INT_MSK |
+		     EBC_INT_STATUS_DSP_FRM_INT_MSK |
+		     EBC_INT_STATUS_FRM_END_INT_MSK);
+
+	return 0;
+
+err_disable_hclk:
+	clk_disable_unprepare(ebc->hclk);
+err_disable_supplies:
+	regulator_bulk_disable(EBC_NUM_SUPPLIES, ebc->supplies);
+
+	return ret;
+}
+
+static const struct dev_pm_ops rockchip_ebc_dev_pm_ops = {
+	SET_RUNTIME_PM_OPS(rockchip_ebc_runtime_suspend,
+			   rockchip_ebc_runtime_resume, NULL)
+};
+
+static bool rockchip_ebc_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case EBC_DSP_START:
+	case EBC_INT_STATUS:
+	case EBC_CONFIG_DONE:
+	case EBC_VNUM:
+		return true;
+	default:
+		/* Do not cache the LUT registers. */
+		return reg > EBC_WIN_MST2;
+	}
+}
+
+static const struct regmap_config rockchip_ebc_regmap_config = {
+	.reg_bits	= 32,
+	.reg_stride	= 4,
+	.val_bits	= 32,
+	.volatile_reg	= rockchip_ebc_volatile_reg,
+	.max_register	= 0x4ffc, /* end of EBC_LUT_DATA */
+	.cache_type	= REGCACHE_FLAT,
+};
+
+static const char *const rockchip_ebc_supplies[EBC_NUM_SUPPLIES] = {
+	"panel",
+	"vcom",
+	"vdrive",
+};
+
+static irqreturn_t rockchip_ebc_irq(int irq, void *dev_id)
+{
+	struct rockchip_ebc *ebc = dev_id;
+	unsigned int status;
+
+	regmap_read(ebc->regmap, EBC_INT_STATUS, &status);
+
+	if (status & EBC_INT_STATUS_DSP_END_INT_ST) {
+		status |= EBC_INT_STATUS_DSP_END_INT_CLR;
+		complete(&ebc->display_end);
+	}
+
+	regmap_write(ebc->regmap, EBC_INT_STATUS, status);
+
+	return IRQ_HANDLED;
+}
+
+static int rockchip_ebc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rockchip_ebc *ebc;
+	void __iomem *base;
+	int i, ret;
+
+	ebc = devm_kzalloc(dev, sizeof(*ebc), GFP_KERNEL);
+	if (!ebc)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, ebc);
+	init_completion(&ebc->display_end);
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	ebc->regmap = devm_regmap_init_mmio(dev, base,
+					     &rockchip_ebc_regmap_config);
+	if (IS_ERR(ebc->regmap))
+		return PTR_ERR(ebc->regmap);
+
+	regcache_cache_only(ebc->regmap, true);
+
+	ebc->dclk = devm_clk_get(dev, "dclk");
+	if (IS_ERR(ebc->dclk))
+		return dev_err_probe(dev, PTR_ERR(ebc->dclk),
+				     "Failed to get dclk\n");
+
+	ebc->hclk = devm_clk_get(dev, "hclk");
+	if (IS_ERR(ebc->hclk))
+		return dev_err_probe(dev, PTR_ERR(ebc->hclk),
+				     "Failed to get hclk\n");
+
+	for (i = 0; i < EBC_NUM_SUPPLIES; i++)
+		ebc->supplies[i].supply = rockchip_ebc_supplies[i];
+
+	ret = devm_regulator_bulk_get(dev, EBC_NUM_SUPPLIES, ebc->supplies);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get supplies\n");
+
+	ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
+			       rockchip_ebc_irq, 0, dev_name(dev), ebc);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to request IRQ\n");
+
+	pm_runtime_set_autosuspend_delay(dev, EBC_SUSPEND_DELAY_MS);
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_enable(dev);
+	if (!pm_runtime_enabled(dev)) {
+		ret = rockchip_ebc_runtime_resume(&pdev->dev);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int rockchip_ebc_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+
+	pm_runtime_disable(dev);
+	if (!pm_runtime_status_suspended(dev))
+		rockchip_ebc_runtime_suspend(dev);
+
+	return 0;
+}
+
+static void rockchip_ebc_shutdown(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+
+	if (!pm_runtime_status_suspended(dev))
+		rockchip_ebc_runtime_suspend(dev);
+}
+
+static const struct of_device_id rockchip_ebc_of_match[] = {
+	{ .compatible = "rockchip,rk3568-ebc" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, rockchip_ebc_of_match);
+
+static struct platform_driver rockchip_ebc_driver = {
+	.probe		= rockchip_ebc_probe,
+	.remove		= rockchip_ebc_remove,
+	.shutdown	= rockchip_ebc_shutdown,
+	.driver		= {
+		.name		= "rockchip-ebc",
+		.of_match_table	= rockchip_ebc_of_match,
+		.pm		= &rockchip_ebc_dev_pm_ops,
+	},
+};
+module_platform_driver(rockchip_ebc_driver);
+
+MODULE_AUTHOR("Samuel Holland <samuel at sholland.org>");
+MODULE_DESCRIPTION("Rockchip EBC driver");
+MODULE_LICENSE("GPL");
-- 
2.35.1




More information about the linux-arm-kernel mailing list