[PATCH] pwm: add MediaTek display PWM driver

David Lechner <[email protected]>
Newsgroups gmane.comp.boot-loaders.u-boot
Message-ID <20260814-mtk-pwm-disp-backlight-v1-1-847992f0b815__47288.2945834257$1786735848$gmane$org@baylibre.com>
Add a new driver for MediaTek display PWM controller. This is generally
used for backlight control on MediaTek SoCs.

Signed-off-by: David Lechner <[email protected]>
---
This driver will be used by MediaTek Genio 360 EVK that we are currently
working on bringing up.
---
 drivers/pwm/Kconfig        |   8 ++
 drivers/pwm/Makefile       |   1 +
 drivers/pwm/pwm-mtk-disp.c | 178 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 187 insertions(+)

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index a69fe37caff..8a2b140fe97 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -74,6 +74,14 @@ config PWM_MTK
 	  This PWM is found on MT7622, MT7623, and MT7629. It supports a
 	  programmable period and duty cycle.
 
+config PWM_MTK_DISP
+	bool "Enable support for MediaTek display PWM"
+	depends on ARCH_MEDIATEK
+	help
+	  This PWM is found on the display subsystem of several MediaTek
+	  SoCs (e.g. MT8183, MT8366). It is typically used to drive the
+	  backlight of a display panel.
+
 config PWM_ROCKCHIP
 	bool "Enable support for the Rockchip PWM"
 	depends on ARCH_ROCKCHIP
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 2682c536c6f..7c409c3c620 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_PWM_EXYNOS)	+= exynos_pwm.o
 obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
 obj-$(CONFIG_PWM_MESON)		+= pwm-meson.o
 obj-$(CONFIG_PWM_MTK)		+= pwm-mtk.o
+obj-$(CONFIG_PWM_MTK_DISP)	+= pwm-mtk-disp.o
 obj-$(CONFIG_PWM_ROCKCHIP)	+= rk_pwm.o
 obj-$(CONFIG_PWM_SANDBOX)	+= sandbox_pwm.o
 obj-$(CONFIG_PWM_SIFIVE)	+= pwm-sifive.o
diff --git a/drivers/pwm/pwm-mtk-disp.c b/drivers/pwm/pwm-mtk-disp.c
new file mode 100644
index 00000000000..540539e6167
--- /dev/null
+++ b/drivers/pwm/pwm-mtk-disp.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MediaTek display pulse-width-modulation controller driver.
+ *
+ * Copyright (c) 2015 MediaTek Inc.
+ * Copyright (C) 2026 BayLibre, SAS
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <pwm.h>
+#include <asm/io.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/time.h>
+
+#define DISP_PWM_EN		0x00
+
+#define PWM_CLKDIV_MASK		GENMASK(25, 16)
+
+#define PWM_PERIOD_BIT_WIDTH	12
+#define PWM_PERIOD_MASK		GENMASK(PWM_PERIOD_BIT_WIDTH - 1, 0)
+
+#define PWM_HIGH_WIDTH_MASK	GENMASK(28, 16)
+
+struct mtk_disp_pwm_data {
+	u32 enable_mask;
+	u32 con0;
+	u32 con0_sel;
+	u32 con1;
+	bool has_commit;
+	u32 commit;
+	u32 commit_mask;
+	u32 bls_debug;
+	u32 bls_debug_mask;
+};
+
+struct mtk_disp_pwm_priv {
+	void __iomem *base;
+	struct clk clk_main;
+	struct clk clk_mm;
+	const struct mtk_disp_pwm_data *data;
+};
+
+static void mtk_disp_pwm_update_bits(void __iomem *base, u32 offset, u32 mask, u32 data)
+{
+	u32 value = readl(base + offset);
+
+	value &= ~mask;
+	value |= data;
+
+	writel(value, base + offset);
+}
+
+static void mtk_disp_pwm_set_bits(void __iomem *base, u32 offset, u32 mask)
+{
+	mtk_disp_pwm_update_bits(base, offset, mask, mask);
+}
+
+static void mtk_disp_pwm_clear_bits(void __iomem *base, u32 offset, u32 mask)
+{
+	mtk_disp_pwm_update_bits(base, offset, mask, 0);
+}
+
+static int mtk_disp_pwm_set_config(struct udevice *dev, u32 channel,
+				   u32 period_ns, u32 duty_ns)
+{
+	struct mtk_disp_pwm_priv *priv = dev_get_priv(dev);
+	const struct mtk_disp_pwm_data *data = priv->data;
+	u64 rate, div, clk_div, period, high_width;
+
+	rate = clk_get_rate(&priv->clk_main);
+
+	/*
+	 * period_ns = 10^9 * (clk_div + 1) * (period + 1) / rate
+	 * duty_ns = 10^9 * (clk_div + 1) * high_width / rate
+	 */
+	clk_div = (((u64)period_ns * rate) / NSEC_PER_SEC) >> PWM_PERIOD_BIT_WIDTH;
+	if (!FIELD_FIT(PWM_CLKDIV_MASK, clk_div))
+		return -EINVAL;
+
+	div = NSEC_PER_SEC * (clk_div + 1);
+	period = ((u64)period_ns * rate) / div;
+	if (period > 0)
+		period--;
+
+	high_width = ((u64)duty_ns * rate) / div;
+
+	if (data->bls_debug && !data->has_commit) {
+		/* Disable double buffer and select manual mode before writing */
+		mtk_disp_pwm_set_bits(priv->base, data->bls_debug, data->bls_debug_mask);
+		mtk_disp_pwm_set_bits(priv->base, data->con0, data->con0_sel);
+	}
+
+	mtk_disp_pwm_update_bits(priv->base, data->con0, PWM_CLKDIV_MASK,
+				 FIELD_PREP(PWM_CLKDIV_MASK, clk_div));
+	mtk_disp_pwm_update_bits(priv->base, data->con1,
+				 PWM_PERIOD_MASK | PWM_HIGH_WIDTH_MASK,
+				 FIELD_PREP(PWM_PERIOD_MASK, period) |
+				 FIELD_PREP(PWM_HIGH_WIDTH_MASK, high_width));
+
+	if (data->has_commit) {
+		mtk_disp_pwm_set_bits(priv->base, data->commit, data->commit_mask);
+		mtk_disp_pwm_clear_bits(priv->base, data->commit, data->commit_mask);
+	}
+
+	return 0;
+}
+
+static int mtk_disp_pwm_set_enable(struct udevice *dev, u32 channel, bool enable)
+{
+	struct mtk_disp_pwm_priv *priv = dev_get_priv(dev);
+
+	mtk_disp_pwm_update_bits(priv->base, DISP_PWM_EN, priv->data->enable_mask,
+				 field_prep(priv->data->enable_mask, enable));
+
+	return 0;
+}
+
+static int mtk_disp_pwm_probe(struct udevice *dev)
+{
+	struct mtk_disp_pwm_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	priv->data = (const void *)dev_get_driver_data(dev);
+
+	priv->base = dev_read_addr_ptr(dev);
+	if (!priv->base)
+		return -EINVAL;
+
+	ret = clk_get_by_name(dev, "main", &priv->clk_main);
+	if (ret)
+		return ret;
+
+	ret = clk_get_by_name(dev, "mm", &priv->clk_mm);
+	if (ret)
+		return ret;
+
+	ret = clk_enable(&priv->clk_main);
+	if (ret)
+		return ret;
+
+	ret = clk_enable(&priv->clk_mm);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct pwm_ops mtk_disp_pwm_ops = {
+	.set_config = mtk_disp_pwm_set_config,
+	.set_enable = mtk_disp_pwm_set_enable,
+};
+
+static const struct mtk_disp_pwm_data mt8183_pwm_data = {
+	.enable_mask = BIT(0),
+	.con0 = 0x18,
+	.con0_sel = 0x0,
+	.con1 = 0x1c,
+	.has_commit = false,
+	.bls_debug = 0x80,
+	.bls_debug_mask = GENMASK(1, 0),
+};
+
+static const struct udevice_id mtk_disp_pwm_ids[] = {
+	{ .compatible = "mediatek,mt8183-disp-pwm", .data = (ulong)&mt8183_pwm_data },
+	{ }
+};
+
+U_BOOT_DRIVER(mtk_disp_pwm) = {
+	.name		= "mtk_disp_pwm",
+	.id		= UCLASS_PWM,
+	.of_match	= mtk_disp_pwm_ids,
+	.ops		= &mtk_disp_pwm_ops,
+	.probe		= mtk_disp_pwm_probe,
+	.priv_auto	= sizeof(struct mtk_disp_pwm_priv),
+};

---
base-commit: d3e8597e46b635ec556a057bc42f0b0859654bdf
change-id: 20260814-mtk-pwm-disp-backlight-3951aeb966f7

Best regards,
--  
David Lechner <[email protected]>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.