aboutsummaryrefslogtreecommitdiffstats
path: root/roms/u-boot/drivers/pwm
diff options
context:
space:
mode:
Diffstat (limited to 'roms/u-boot/drivers/pwm')
-rw-r--r--roms/u-boot/drivers/pwm/Kconfig93
-rw-r--r--roms/u-boot/drivers/pwm/Makefile23
-rw-r--r--roms/u-boot/drivers/pwm/cros_ec_pwm.c84
-rw-r--r--roms/u-boot/drivers/pwm/exynos_pwm.c118
-rw-r--r--roms/u-boot/drivers/pwm/pwm-imx-util.c80
-rw-r--r--roms/u-boot/drivers/pwm/pwm-imx-util.h15
-rw-r--r--roms/u-boot/drivers/pwm/pwm-imx.c163
-rw-r--r--roms/u-boot/drivers/pwm/pwm-meson.c531
-rw-r--r--roms/u-boot/drivers/pwm/pwm-mtk.c188
-rw-r--r--roms/u-boot/drivers/pwm/pwm-sifive.c174
-rw-r--r--roms/u-boot/drivers/pwm/pwm-ti-ehrpwm.c468
-rw-r--r--roms/u-boot/drivers/pwm/pwm-uclass.c45
-rw-r--r--roms/u-boot/drivers/pwm/rk_pwm.c220
-rw-r--r--roms/u-boot/drivers/pwm/sandbox_pwm.c120
-rw-r--r--roms/u-boot/drivers/pwm/sunxi_pwm.c186
-rw-r--r--roms/u-boot/drivers/pwm/tegra_pwm.c83
16 files changed, 2591 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/pwm/Kconfig b/roms/u-boot/drivers/pwm/Kconfig
new file mode 100644
index 000000000..cf7f4c684
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/Kconfig
@@ -0,0 +1,93 @@
+config DM_PWM
+ bool "Enable support for pulse-width modulation devices (PWM)"
+ depends on DM
+ help
+ A pulse-width modulator emits a pulse of varying width and provides
+ control over the duty cycle (high and low time) of the signal. This
+ is often used to control a voltage level. The more time the PWM
+ spends in the 'high' state, the higher the voltage. The PWM's
+ frequency/period can be controlled along with the proportion of that
+ time that the signal is high.
+
+config PWM_CROS_EC
+ bool "Enable support for the Chrome OS EC PWM"
+ depends on DM_PWM
+ help
+ This PWM is found on several Chrome OS devices and controlled by
+ the Chrome OS embedded controller. It may be used to control the
+ screen brightness and/or the keyboard backlight depending on the
+ device.
+
+config PWM_EXYNOS
+ bool "Enable support for the Exynos PWM"
+ depends on DM_PWM
+ help
+ This PWM is found on Samsung Exynos 5250 and other Samsung SoCs. It
+ supports a programmable period and duty cycle. A 32-bit counter is
+ used. It provides 5 channels which can be independently
+ programmed. Channel 4 (the last) is normally used as a timer.
+
+config PWM_IMX
+ bool "Enable support for i.MX27 and later PWM"
+ help
+ This PWM is found i.MX27 and later i.MX SoCs.
+
+config PWM_MESON
+ bool "Enable support for Amlogic Meson SoCs PWM"
+ depends on DM_PWM
+ help
+ This PWM is found on Amlogic Meson SoCs. It supports a
+ programmable period and duty cycle for 2 independant channels.
+
+config PWM_MTK
+ bool "Enable support for MediaTek PWM"
+ depends on DM_PWM
+ help
+ This PWM is found on MT7622, MT7623, and MT7629. It supports a
+ programmable period and duty cycle.
+
+config PWM_ROCKCHIP
+ bool "Enable support for the Rockchip PWM"
+ depends on DM_PWM
+ help
+ This PWM is found on RK3288 and other Rockchip SoCs. It supports a
+ programmable period and duty cycle. A 32-bit counter is used.
+ Various options provided in the hardware (such as capture mode and
+ continuous/single-shot) are not supported by the driver.
+
+config PWM_SANDBOX
+ bool "Enable support for the sandbox PWM"
+ help
+ This is a sandbox PWM used for testing. It provides 3 channels and
+ records the settings passed into it, but otherwise does nothing
+ useful. The PWM can be enabled but is not connected to any outputs
+ so this is not very useful.
+
+config PWM_SIFIVE
+ bool "Enable support for SiFive PWM"
+ depends on DM_PWM
+ help
+ This PWM is found SiFive's FU540 and other SoCs.
+
+config PWM_TEGRA
+ bool "Enable support for the Tegra PWM"
+ depends on DM_PWM
+ help
+ This PWM is found on Tegra 20 and other Nvidia SoCs. It supports
+ four channels with a programmable period and duty cycle. Only a
+ 32KHz clock is supported by the driver but the duty cycle is
+ configurable.
+
+config PWM_SUNXI
+ bool "Enable support for the Allwinner Sunxi PWM"
+ depends on DM_PWM
+ help
+ This PWM is found on H3, A64 and other Allwinner SoCs. It supports a
+ programmable period and duty cycle. A 16-bit counter is used.
+
+config PWM_TI_EHRPWM
+ bool "Enable support for EHRPWM PWM"
+ depends on DM_PWM && ARCH_OMAP2PLUS
+ default y
+ help
+ PWM driver support for the EHRPWM controller found on TI SOCs.
diff --git a/roms/u-boot/drivers/pwm/Makefile b/roms/u-boot/drivers/pwm/Makefile
new file mode 100644
index 000000000..10d244bfb
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/Makefile
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# (C) Copyright 2006
+# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+#
+# (C) Copyright 2001
+# Erik Theisen, Wave 7 Optics, etheisen@mindspring.com.
+
+#ccflags-y += -DDEBUG
+
+obj-$(CONFIG_DM_PWM) += pwm-uclass.o
+
+obj-$(CONFIG_PWM_CROS_EC) += cros_ec_pwm.o
+obj-$(CONFIG_PWM_EXYNOS) += exynos_pwm.o
+obj-$(CONFIG_PWM_IMX) += pwm-imx.o pwm-imx-util.o
+obj-$(CONFIG_PWM_MESON) += pwm-meson.o
+obj-$(CONFIG_PWM_MTK) += pwm-mtk.o
+obj-$(CONFIG_PWM_ROCKCHIP) += rk_pwm.o
+obj-$(CONFIG_PWM_SANDBOX) += sandbox_pwm.o
+obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
+obj-$(CONFIG_PWM_TEGRA) += tegra_pwm.o
+obj-$(CONFIG_PWM_SUNXI) += sunxi_pwm.o
+obj-$(CONFIG_PWM_TI_EHRPWM) += pwm-ti-ehrpwm.o
diff --git a/roms/u-boot/drivers/pwm/cros_ec_pwm.c b/roms/u-boot/drivers/pwm/cros_ec_pwm.c
new file mode 100644
index 000000000..4a39c319a
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/cros_ec_pwm.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <common.h>
+#include <cros_ec.h>
+#include <dm.h>
+#include <errno.h>
+#include <log.h>
+#include <pwm.h>
+
+struct cros_ec_pwm_priv {
+ bool enabled;
+ uint duty;
+};
+
+static int cros_ec_pwm_set_config(struct udevice *dev, uint channel,
+ uint period_ns, uint duty_ns)
+{
+ struct cros_ec_pwm_priv *priv = dev_get_priv(dev);
+ uint duty;
+ int ret;
+
+ debug("%s: period_ns=%u, duty_ns=%u asked\n", __func__,
+ period_ns, duty_ns);
+
+ /* No way to set the period, only a relative duty cycle */
+ duty = EC_PWM_MAX_DUTY * duty_ns / period_ns;
+ if (duty > EC_PWM_MAX_DUTY)
+ duty = EC_PWM_MAX_DUTY;
+
+ if (!priv->enabled) {
+ priv->duty = duty;
+ debug("%s: duty=%#x to-be-set\n", __func__, duty);
+ return 0;
+ }
+
+ ret = cros_ec_set_pwm_duty(dev->parent, channel, duty);
+ if (ret) {
+ debug("%s: duty=%#x failed\n", __func__, duty);
+ return ret;
+ }
+
+ priv->duty = duty;
+ debug("%s: duty=%#x set\n", __func__, duty);
+
+ return 0;
+}
+
+static int cros_ec_pwm_set_enable(struct udevice *dev, uint channel,
+ bool enable)
+{
+ struct cros_ec_pwm_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = cros_ec_set_pwm_duty(dev->parent, channel,
+ enable ? priv->duty : 0);
+ if (ret) {
+ debug("%s: enable=%d failed\n", __func__, enable);
+ return ret;
+ }
+
+ priv->enabled = enable;
+ debug("%s: enable=%d (duty=%#x) set\n", __func__,
+ enable, priv->duty);
+
+ return 0;
+}
+
+static const struct pwm_ops cros_ec_pwm_ops = {
+ .set_config = cros_ec_pwm_set_config,
+ .set_enable = cros_ec_pwm_set_enable,
+};
+
+static const struct udevice_id cros_ec_pwm_ids[] = {
+ { .compatible = "google,cros-ec-pwm" },
+ { }
+};
+
+U_BOOT_DRIVER(cros_ec_pwm) = {
+ .name = "cros_ec_pwm",
+ .id = UCLASS_PWM,
+ .of_match = cros_ec_pwm_ids,
+ .ops = &cros_ec_pwm_ops,
+ .priv_auto = sizeof(struct cros_ec_pwm_priv),
+};
diff --git a/roms/u-boot/drivers/pwm/exynos_pwm.c b/roms/u-boot/drivers/pwm/exynos_pwm.c
new file mode 100644
index 000000000..1afaf784d
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/exynos_pwm.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2016 Google Inc.
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <log.h>
+#include <pwm.h>
+#include <asm/io.h>
+#include <asm/arch/clk.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/pwm.h>
+
+struct exynos_pwm_priv {
+ struct s5p_timer *regs;
+};
+
+static int exynos_pwm_set_config(struct udevice *dev, uint channel,
+ uint period_ns, uint duty_ns)
+{
+ struct exynos_pwm_priv *priv = dev_get_priv(dev);
+ struct s5p_timer *regs = priv->regs;
+ unsigned int offset, prescaler;
+ uint div = 4, rate, rate_ns;
+ u32 val;
+ u32 tcnt, tcmp, tcon;
+
+ if (channel >= 5)
+ return -EINVAL;
+ debug("%s: Configure '%s' channel %u, period_ns %u, duty_ns %u\n",
+ __func__, dev->name, channel, period_ns, duty_ns);
+
+ val = readl(&regs->tcfg0);
+ prescaler = (channel < 2 ? val : (val >> 8)) & 0xff;
+ div = (readl(&regs->tcfg1) >> MUX_DIV_SHIFT(channel)) & 0xf;
+
+ rate = get_pwm_clk() / ((prescaler + 1) * (1 << div));
+ debug("%s: pwm_clk %lu, rate %u\n", __func__, get_pwm_clk(), rate);
+
+ if (channel < 4) {
+ rate_ns = 1000000000 / rate;
+ tcnt = period_ns / rate_ns;
+ tcmp = duty_ns / rate_ns;
+ debug("%s: tcnt %u, tcmp %u\n", __func__, tcnt, tcmp);
+ offset = channel * 3;
+ writel(tcnt, &regs->tcntb0 + offset);
+ writel(tcmp, &regs->tcmpb0 + offset);
+ }
+
+ tcon = readl(&regs->tcon);
+ tcon |= TCON_UPDATE(channel);
+ if (channel < 4)
+ tcon |= TCON_AUTO_RELOAD(channel);
+ else
+ tcon |= TCON4_AUTO_RELOAD;
+ writel(tcon, &regs->tcon);
+
+ tcon &= ~TCON_UPDATE(channel);
+ writel(tcon, &regs->tcon);
+
+ return 0;
+}
+
+static int exynos_pwm_set_enable(struct udevice *dev, uint channel,
+ bool enable)
+{
+ struct exynos_pwm_priv *priv = dev_get_priv(dev);
+ struct s5p_timer *regs = priv->regs;
+ u32 mask;
+
+ if (channel >= 4)
+ return -EINVAL;
+ debug("%s: Enable '%s' channel %u\n", __func__, dev->name, channel);
+ mask = TCON_START(channel);
+ clrsetbits_le32(&regs->tcon, mask, enable ? mask : 0);
+
+ return 0;
+}
+
+static int exynos_pwm_probe(struct udevice *dev)
+{
+ struct exynos_pwm_priv *priv = dev_get_priv(dev);
+ struct s5p_timer *regs = priv->regs;
+
+ writel(PRESCALER_0 | PRESCALER_1 << 8, &regs->tcfg0);
+
+ return 0;
+}
+
+static int exynos_pwm_of_to_plat(struct udevice *dev)
+{
+ struct exynos_pwm_priv *priv = dev_get_priv(dev);
+
+ priv->regs = dev_read_addr_ptr(dev);
+
+ return 0;
+}
+
+static const struct pwm_ops exynos_pwm_ops = {
+ .set_config = exynos_pwm_set_config,
+ .set_enable = exynos_pwm_set_enable,
+};
+
+static const struct udevice_id exynos_channels[] = {
+ { .compatible = "samsung,exynos4210-pwm" },
+ { }
+};
+
+U_BOOT_DRIVER(exynos_pwm) = {
+ .name = "exynos_pwm",
+ .id = UCLASS_PWM,
+ .of_match = exynos_channels,
+ .ops = &exynos_pwm_ops,
+ .probe = exynos_pwm_probe,
+ .of_to_plat = exynos_pwm_of_to_plat,
+ .priv_auto = sizeof(struct exynos_pwm_priv),
+};
diff --git a/roms/u-boot/drivers/pwm/pwm-imx-util.c b/roms/u-boot/drivers/pwm/pwm-imx-util.c
new file mode 100644
index 000000000..823a9d2d6
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/pwm-imx-util.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2014
+ * Heiko Schocher, DENX Software Engineering, hs@denx.de.
+ *
+ * Basic support for the pwm module on imx6.
+ *
+ * Based on linux:drivers/pwm/pwm-imx.c
+ * from
+ * Sascha Hauer <s.hauer@pengutronix.de>
+ */
+
+#include <common.h>
+#include <div64.h>
+#include <asm/arch/imx-regs.h>
+
+/* pwm_id from 0..7 */
+struct pwm_regs *pwm_id_to_reg(int pwm_id)
+{
+ switch (pwm_id) {
+ case 0:
+ return (struct pwm_regs *)PWM1_BASE_ADDR;
+ case 1:
+ return (struct pwm_regs *)PWM2_BASE_ADDR;
+#ifdef CONFIG_MX6
+ case 2:
+ return (struct pwm_regs *)PWM3_BASE_ADDR;
+ case 3:
+ return (struct pwm_regs *)PWM4_BASE_ADDR;
+#endif
+#ifdef CONFIG_MX6SX
+ case 4:
+ return (struct pwm_regs *)PWM5_BASE_ADDR;
+ case 5:
+ return (struct pwm_regs *)PWM6_BASE_ADDR;
+ case 6:
+ return (struct pwm_regs *)PWM7_BASE_ADDR;
+ case 7:
+ return (struct pwm_regs *)PWM8_BASE_ADDR;
+#endif
+ default:
+ printf("unknown pwm_id: %d\n", pwm_id);
+ break;
+ }
+ return NULL;
+}
+
+int pwm_imx_get_parms(int period_ns, int duty_ns, unsigned long *period_c,
+ unsigned long *duty_c, unsigned long *prescale)
+{
+ unsigned long long c;
+
+ /*
+ * we have not yet a clock framework for imx6, so add the clock
+ * value here as a define. Replace it when we have the clock
+ * framework.
+ */
+ c = CONFIG_IMX6_PWM_PER_CLK;
+ c = c * period_ns;
+ do_div(c, 1000000000);
+ *period_c = c;
+
+ *prescale = *period_c / 0x10000 + 1;
+
+ *period_c /= *prescale;
+ c = *period_c * (unsigned long long)duty_ns;
+ do_div(c, period_ns);
+ *duty_c = c;
+
+ /*
+ * according to imx pwm RM, the real period value should be
+ * PERIOD value in PWMPR plus 2.
+ */
+ if (*period_c > 2)
+ *period_c -= 2;
+ else
+ *period_c = 0;
+
+ return 0;
+}
diff --git a/roms/u-boot/drivers/pwm/pwm-imx-util.h b/roms/u-boot/drivers/pwm/pwm-imx-util.h
new file mode 100644
index 000000000..82c61d774
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/pwm-imx-util.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * (C) Copyright 2014
+ * Heiko Schocher, DENX Software Engineering, hs@denx.de.
+ *
+ * Basic support for the pwm module on imx6.
+ */
+
+#ifndef _pwm_imx_util_h_
+#define _pwm_imx_util_h_
+
+struct pwm_regs *pwm_id_to_reg(int pwm_id);
+int pwm_imx_get_parms(int period_ns, int duty_ns, unsigned long *period_c,
+ unsigned long *duty_c, unsigned long *prescale);
+#endif
diff --git a/roms/u-boot/drivers/pwm/pwm-imx.c b/roms/u-boot/drivers/pwm/pwm-imx.c
new file mode 100644
index 000000000..2008c1520
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/pwm-imx.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2014
+ * Heiko Schocher, DENX Software Engineering, hs@denx.de.
+ *
+ * Basic support for the pwm module on imx6.
+ */
+
+#include <common.h>
+#include <div64.h>
+#include <dm.h>
+#include <log.h>
+#include <pwm.h>
+#include <asm/arch/imx-regs.h>
+#include <asm/io.h>
+#include "pwm-imx-util.h"
+
+int pwm_init(int pwm_id, int div, int invert)
+{
+ struct pwm_regs *pwm = (struct pwm_regs *)pwm_id_to_reg(pwm_id);
+
+ if (!pwm)
+ return -1;
+
+ writel(0, &pwm->ir);
+ return 0;
+}
+
+int pwm_config_internal(struct pwm_regs *pwm, unsigned long period_cycles,
+ unsigned long duty_cycles, unsigned long prescale)
+{
+ u32 cr;
+
+ writel(0, &pwm->ir);
+ cr = PWMCR_PRESCALER(prescale) |
+ PWMCR_DOZEEN | PWMCR_WAITEN |
+ PWMCR_DBGEN | PWMCR_CLKSRC_IPG_HIGH;
+
+ writel(cr, &pwm->cr);
+ /* set duty cycles */
+ writel(duty_cycles, &pwm->sar);
+ /* set period cycles */
+ writel(period_cycles, &pwm->pr);
+ return 0;
+}
+
+int pwm_config(int pwm_id, int duty_ns, int period_ns)
+{
+ struct pwm_regs *pwm = (struct pwm_regs *)pwm_id_to_reg(pwm_id);
+ unsigned long period_cycles, duty_cycles, prescale;
+
+ if (!pwm)
+ return -1;
+
+ pwm_imx_get_parms(period_ns, duty_ns, &period_cycles, &duty_cycles,
+ &prescale);
+
+ return pwm_config_internal(pwm, period_cycles, duty_cycles, prescale);
+}
+
+int pwm_enable(int pwm_id)
+{
+ struct pwm_regs *pwm = (struct pwm_regs *)pwm_id_to_reg(pwm_id);
+
+ if (!pwm)
+ return -1;
+
+ setbits_le32(&pwm->cr, PWMCR_EN);
+ return 0;
+}
+
+void pwm_disable(int pwm_id)
+{
+ struct pwm_regs *pwm = (struct pwm_regs *)pwm_id_to_reg(pwm_id);
+
+ if (!pwm)
+ return;
+
+ clrbits_le32(&pwm->cr, PWMCR_EN);
+}
+
+#if defined(CONFIG_DM_PWM)
+struct imx_pwm_priv {
+ struct pwm_regs *regs;
+ bool invert;
+};
+
+static int imx_pwm_set_invert(struct udevice *dev, uint channel,
+ bool polarity)
+{
+ struct imx_pwm_priv *priv = dev_get_priv(dev);
+
+ debug("%s: polarity=%u\n", __func__, polarity);
+ priv->invert = polarity;
+
+ return 0;
+}
+
+static int imx_pwm_set_config(struct udevice *dev, uint channel,
+ uint period_ns, uint duty_ns)
+{
+ struct imx_pwm_priv *priv = dev_get_priv(dev);
+ struct pwm_regs *regs = priv->regs;
+ unsigned long period_cycles, duty_cycles, prescale;
+
+ debug("%s: Config '%s' channel: %d\n", __func__, dev->name, channel);
+
+ pwm_imx_get_parms(period_ns, duty_ns, &period_cycles, &duty_cycles,
+ &prescale);
+
+ return pwm_config_internal(regs, period_cycles, duty_cycles, prescale);
+};
+
+static int imx_pwm_set_enable(struct udevice *dev, uint channel, bool enable)
+{
+ struct imx_pwm_priv *priv = dev_get_priv(dev);
+ struct pwm_regs *regs = priv->regs;
+
+ debug("%s: Enable '%s' state: %d\n", __func__, dev->name, enable);
+
+ if (enable)
+ setbits_le32(&regs->cr, PWMCR_EN);
+ else
+ clrbits_le32(&regs->cr, PWMCR_EN);
+
+ return 0;
+};
+
+static int imx_pwm_of_to_plat(struct udevice *dev)
+{
+ struct imx_pwm_priv *priv = dev_get_priv(dev);
+
+ priv->regs = dev_read_addr_ptr(dev);
+
+ return 0;
+}
+
+static int imx_pwm_probe(struct udevice *dev)
+{
+ return 0;
+}
+
+static const struct pwm_ops imx_pwm_ops = {
+ .set_invert = imx_pwm_set_invert,
+ .set_config = imx_pwm_set_config,
+ .set_enable = imx_pwm_set_enable,
+};
+
+static const struct udevice_id imx_pwm_ids[] = {
+ { .compatible = "fsl,imx27-pwm" },
+ { }
+};
+
+U_BOOT_DRIVER(imx_pwm) = {
+ .name = "imx_pwm",
+ .id = UCLASS_PWM,
+ .of_match = imx_pwm_ids,
+ .ops = &imx_pwm_ops,
+ .of_to_plat = imx_pwm_of_to_plat,
+ .probe = imx_pwm_probe,
+ .priv_auto = sizeof(struct imx_pwm_priv),
+};
+#endif
diff --git a/roms/u-boot/drivers/pwm/pwm-meson.c b/roms/u-boot/drivers/pwm/pwm-meson.c
new file mode 100644
index 000000000..03eeacc28
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/pwm-meson.c
@@ -0,0 +1,531 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/*
+ * Copyright (C) 2020 BayLibre, SAS.
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ * Copyright (C) 2014 Amlogic, Inc.
+ *
+ * This PWM is only a set of Gates, Dividers and Counters:
+ * PWM output is achieved by calculating a clock that permits calculating
+ * two periods (low and high). The counter then has to be set to switch after
+ * N cycles for the first half period.
+ * The hardware has no "polarity" setting. This driver reverses the period
+ * cycles (the low length is inverted with the high length) for
+ * PWM_POLARITY_INVERSED.
+ * Setting the polarity will disable and re-enable the PWM output.
+ * Disabling the PWM stops the output immediately (without waiting for the
+ * current period to complete first).
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <div64.h>
+#include <dm.h>
+#include <pwm.h>
+#include <regmap.h>
+#include <linux/io.h>
+#include <linux/math64.h>
+#include <linux/bitfield.h>
+#include <linux/clk-provider.h>
+
+#define NSEC_PER_SEC 1000000000L
+
+#define REG_PWM_A 0x0
+#define REG_PWM_B 0x4
+#define PWM_LOW_MASK GENMASK(15, 0)
+#define PWM_HIGH_MASK GENMASK(31, 16)
+
+#define REG_MISC_AB 0x8
+#define MISC_B_CLK_EN BIT(23)
+#define MISC_A_CLK_EN BIT(15)
+#define MISC_CLK_DIV_MASK 0x7f
+#define MISC_B_CLK_DIV_SHIFT 16
+#define MISC_A_CLK_DIV_SHIFT 8
+#define MISC_B_CLK_SEL_SHIFT 6
+#define MISC_A_CLK_SEL_SHIFT 4
+#define MISC_CLK_SEL_MASK 0x3
+#define MISC_B_EN BIT(1)
+#define MISC_A_EN BIT(0)
+
+#define MESON_NUM_PWMS 2
+
+static struct meson_pwm_channel_data {
+ u8 reg_offset;
+ u8 clk_sel_shift;
+ u8 clk_div_shift;
+ u32 clk_en_mask;
+ u32 pwm_en_mask;
+} meson_pwm_per_channel_data[MESON_NUM_PWMS] = {
+ {
+ .reg_offset = REG_PWM_A,
+ .clk_sel_shift = MISC_A_CLK_SEL_SHIFT,
+ .clk_div_shift = MISC_A_CLK_DIV_SHIFT,
+ .clk_en_mask = MISC_A_CLK_EN,
+ .pwm_en_mask = MISC_A_EN,
+ },
+ {
+ .reg_offset = REG_PWM_B,
+ .clk_sel_shift = MISC_B_CLK_SEL_SHIFT,
+ .clk_div_shift = MISC_B_CLK_DIV_SHIFT,
+ .clk_en_mask = MISC_B_CLK_EN,
+ .pwm_en_mask = MISC_B_EN,
+ }
+};
+
+struct meson_pwm_channel {
+ unsigned int hi;
+ unsigned int lo;
+ u8 pre_div;
+ uint period_ns;
+ uint duty_ns;
+ bool configured;
+ bool enabled;
+ bool polarity;
+ struct clk clk;
+};
+
+struct meson_pwm_data {
+ const long *parent_ids;
+ unsigned int num_parents;
+};
+
+struct meson_pwm {
+ const struct meson_pwm_data *data;
+ struct meson_pwm_channel channels[MESON_NUM_PWMS];
+ void __iomem *base;
+};
+
+static int meson_pwm_set_enable(struct udevice *dev, uint channel, bool enable);
+
+static int meson_pwm_set_config(struct udevice *dev, uint channeln,
+ uint period_ns, uint duty_ns)
+{
+ struct meson_pwm *priv = dev_get_priv(dev);
+ struct meson_pwm_channel *channel;
+ struct meson_pwm_channel_data *channel_data;
+ unsigned int duty, period, pre_div, cnt, duty_cnt;
+ unsigned long fin_freq;
+
+ if (channeln >= MESON_NUM_PWMS)
+ return -ENODEV;
+
+ channel = &priv->channels[channeln];
+ channel_data = &meson_pwm_per_channel_data[channeln];
+
+ period = period_ns;
+ if (channel->polarity)
+ duty = period_ns - duty_ns;
+ else
+ duty = duty_ns;
+
+ debug("%s%d: polarity %s duty %d period %d\n", __func__, channeln,
+ channel->polarity ? "true" : "false", duty, period);
+
+ fin_freq = clk_get_rate(&channel->clk);
+ if (fin_freq == 0) {
+ printf("%s%d: invalid source clock frequency\n", __func__, channeln);
+ return -EINVAL;
+ }
+
+ debug("%s%d: fin_freq: %lu Hz\n", __func__, channeln, fin_freq);
+
+ pre_div = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * 0xffffLL);
+ if (pre_div > MISC_CLK_DIV_MASK) {
+ printf("%s%d: unable to get period pre_div\n", __func__, channeln);
+ return -EINVAL;
+ }
+
+ cnt = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * (pre_div + 1));
+ if (cnt > 0xffff) {
+ printf("%s%d: unable to get period cnt\n", __func__, channeln);
+ return -EINVAL;
+ }
+
+ debug("%s%d: period=%u pre_div=%u cnt=%u\n", __func__, channeln, period, pre_div, cnt);
+
+ if (duty == period) {
+ channel->pre_div = pre_div;
+ channel->hi = cnt;
+ channel->lo = 0;
+ } else if (duty == 0) {
+ channel->pre_div = pre_div;
+ channel->hi = 0;
+ channel->lo = cnt;
+ } else {
+ /* Then check is we can have the duty with the same pre_div */
+ duty_cnt = div64_u64(fin_freq * (u64)duty, NSEC_PER_SEC * (pre_div + 1));
+ if (duty_cnt > 0xffff) {
+ printf("%s%d: unable to get duty cycle\n", __func__, channeln);
+ return -EINVAL;
+ }
+
+ debug("%s%d: duty=%u pre_div=%u duty_cnt=%u\n",
+ __func__, channeln, duty, pre_div, duty_cnt);
+
+ channel->pre_div = pre_div;
+ channel->hi = duty_cnt;
+ channel->lo = cnt - duty_cnt;
+ }
+
+ channel->period_ns = period_ns;
+ channel->duty_ns = duty_ns;
+ channel->configured = true;
+
+ if (channel->enabled) {
+ meson_pwm_set_enable(dev, channeln, false);
+ meson_pwm_set_enable(dev, channeln, true);
+ }
+
+ return 0;
+}
+
+static int meson_pwm_set_enable(struct udevice *dev, uint channeln, bool enable)
+{
+ struct meson_pwm *priv = dev_get_priv(dev);
+ struct meson_pwm_channel *channel;
+ struct meson_pwm_channel_data *channel_data;
+ u32 value;
+
+ if (channeln >= MESON_NUM_PWMS)
+ return -ENODEV;
+
+ channel = &priv->channels[channeln];
+ channel_data = &meson_pwm_per_channel_data[channeln];
+
+ if (!channel->configured)
+ return -EINVAL;
+
+ if (enable) {
+ if (channel->enabled)
+ return 0;
+
+ value = readl(priv->base + REG_MISC_AB);
+ value &= ~(MISC_CLK_DIV_MASK << channel_data->clk_div_shift);
+ value |= channel->pre_div << channel_data->clk_div_shift;
+ value |= channel_data->clk_en_mask;
+ writel(value, priv->base + REG_MISC_AB);
+
+ value = FIELD_PREP(PWM_HIGH_MASK, channel->hi) |
+ FIELD_PREP(PWM_LOW_MASK, channel->lo);
+ writel(value, priv->base + channel_data->reg_offset);
+
+ value = readl(priv->base + REG_MISC_AB);
+ value |= channel_data->pwm_en_mask;
+ writel(value, priv->base + REG_MISC_AB);
+
+ debug("%s%d: enabled\n", __func__, channeln);
+ channel->enabled = true;
+ } else {
+ if (!channel->enabled)
+ return 0;
+
+ value = readl(priv->base + REG_MISC_AB);
+ value &= channel_data->pwm_en_mask;
+ writel(value, priv->base + REG_MISC_AB);
+
+ debug("%s%d: disabled\n", __func__, channeln);
+ channel->enabled = false;
+ }
+
+ return 0;
+}
+
+static int meson_pwm_set_invert(struct udevice *dev, uint channeln, bool polarity)
+{
+ struct meson_pwm *priv = dev_get_priv(dev);
+ struct meson_pwm_channel *channel;
+
+ if (channeln >= MESON_NUM_PWMS)
+ return -ENODEV;
+
+ debug("%s%d: set invert %s\n", __func__, channeln, polarity ? "true" : "false");
+
+ channel = &priv->channels[channeln];
+
+ channel->polarity = polarity;
+
+ if (!channel->configured)
+ return 0;
+
+ return meson_pwm_set_config(dev, channeln, channel->period_ns, channel->duty_ns);
+}
+
+static int meson_pwm_of_to_plat(struct udevice *dev)
+{
+ struct meson_pwm *priv = dev_get_priv(dev);
+
+ priv->base = dev_read_addr_ptr(dev);
+
+ return 0;
+}
+
+static int meson_pwm_probe(struct udevice *dev)
+{
+ struct meson_pwm *priv = dev_get_priv(dev);
+ struct meson_pwm_data *data;
+ unsigned int i, p;
+ char name[255];
+ int err;
+ u32 reg;
+
+ data = (struct meson_pwm_data *)dev_get_driver_data(dev);
+ if (!data)
+ return -EINVAL;
+
+ for (i = 0; i < MESON_NUM_PWMS; i++) {
+ struct meson_pwm_channel *channel = &priv->channels[i];
+ struct meson_pwm_channel_data *channel_data = &meson_pwm_per_channel_data[i];
+
+ snprintf(name, sizeof(name), "clkin%u", i);
+
+ err = clk_get_by_name(dev, name, &channel->clk);
+ /* If clock is not specified, use the already set clock */
+ if (err == -ENODATA) {
+ struct udevice *cdev;
+ struct uclass *uc;
+
+ /* Get parent from mux */
+ p = (readl(priv->base + REG_MISC_AB) >> channel_data->clk_sel_shift) &
+ MISC_CLK_SEL_MASK;
+
+ if (p >= data->num_parents) {
+ printf("%s%d: hw parent is invalid\n", __func__, i);
+ return -EINVAL;
+ }
+
+ if (data->parent_ids[p] == -1) {
+ /* Search for xtal clk */
+ const char *str;
+
+ err = uclass_get(UCLASS_CLK, &uc);
+ if (err)
+ return err;
+
+ uclass_foreach_dev(cdev, uc) {
+ if (strcmp(cdev->driver->name, "fixed_rate_clock"))
+ continue;
+
+ str = ofnode_read_string(dev_ofnode(cdev),
+ "clock-output-names");
+ if (!str)
+ continue;
+
+ if (!strcmp(str, "xtal")) {
+ err = uclass_get_device_by_ofnode(UCLASS_CLK,
+ dev_ofnode(cdev),
+ &cdev);
+ if (err) {
+ printf("%s%d: Failed to get xtal clk\n", __func__, i);
+ return err;
+ }
+
+ break;
+ }
+ }
+
+ if (!cdev) {
+ printf("%s%d: Failed to find xtal clk device\n", __func__, i);
+ return -EINVAL;
+ }
+
+ channel->clk.dev = cdev;
+ channel->clk.id = 0;
+ channel->clk.data = 0;
+ } else {
+ /* Look for parent clock */
+ err = uclass_get(UCLASS_CLK, &uc);
+ if (err)
+ return err;
+
+ uclass_foreach_dev(cdev, uc) {
+ if (strstr(cdev->driver->name, "meson_clk"))
+ break;
+ }
+
+ if (!cdev) {
+ printf("%s%d: Failed to find clk device\n", __func__, i);
+ return -EINVAL;
+ }
+
+ err = uclass_get_device_by_ofnode(UCLASS_CLK,
+ dev_ofnode(cdev),
+ &cdev);
+ if (err) {
+ printf("%s%d: Failed to get clk controller\n", __func__, i);
+ return err;
+ }
+
+ channel->clk.dev = cdev;
+ channel->clk.id = data->parent_ids[p];
+ channel->clk.data = 0;
+ }
+
+ /* We have our source clock, do not alter HW clock mux */
+ continue;
+ } else
+ return err;
+
+ /* Get id in list */
+ for (p = 0 ; p < data->num_parents ; ++p) {
+ if (!strcmp(channel->clk.dev->driver->name, "fixed_rate_clock")) {
+ if (data->parent_ids[p] == -1)
+ break;
+ } else {
+ if (data->parent_ids[p] == channel->clk.id)
+ break;
+ }
+ }
+
+ /* Invalid clock ID */
+ if (p == data->num_parents) {
+ printf("%s%d: source clock is invalid\n", __func__, i);
+ return -EINVAL;
+ }
+
+ /* switch parent in mux */
+ reg = readl(priv->base + REG_MISC_AB);
+
+ debug("%s%d: switching parent %d to %d\n", __func__, i,
+ (reg >> channel_data->clk_sel_shift) & MISC_CLK_SEL_MASK, p);
+
+ reg &= MISC_CLK_SEL_MASK << channel_data->clk_sel_shift;
+ reg |= (p & MISC_CLK_SEL_MASK) << channel_data->clk_sel_shift;
+ writel(reg, priv->base + REG_MISC_AB);
+ }
+
+ return 0;
+}
+
+static const struct pwm_ops meson_pwm_ops = {
+ .set_config = meson_pwm_set_config,
+ .set_enable = meson_pwm_set_enable,
+ .set_invert = meson_pwm_set_invert,
+};
+
+#define XTAL -1
+
+/* Local clock ids aliases to avoid define conflicts */
+#define GXBB_CLKID_HDMI_PLL 2
+#define GXBB_CLKID_FCLK_DIV3 5
+#define GXBB_CLKID_FCLK_DIV4 6
+#define GXBB_CLKID_CLK81 12
+
+static const long pwm_gxbb_parent_ids[] = {
+ XTAL, GXBB_CLKID_HDMI_PLL, GXBB_CLKID_FCLK_DIV4, GXBB_CLKID_FCLK_DIV3
+};
+
+static const struct meson_pwm_data pwm_gxbb_data = {
+ .parent_ids = pwm_gxbb_parent_ids,
+ .num_parents = ARRAY_SIZE(pwm_gxbb_parent_ids),
+};
+
+/*
+ * Only the 2 first inputs of the GXBB AO PWMs are valid
+ * The last 2 are grounded
+ */
+static const long pwm_gxbb_ao_parent_ids[] = {
+ XTAL, GXBB_CLKID_CLK81
+};
+
+static const struct meson_pwm_data pwm_gxbb_ao_data = {
+ .parent_ids = pwm_gxbb_ao_parent_ids,
+ .num_parents = ARRAY_SIZE(pwm_gxbb_ao_parent_ids),
+};
+
+/* Local clock ids aliases to avoid define conflicts */
+#define AXG_CLKID_FCLK_DIV3 3
+#define AXG_CLKID_FCLK_DIV4 4
+#define AXG_CLKID_FCLK_DIV5 5
+#define AXG_CLKID_CLK81 10
+
+static const long pwm_axg_ee_parent_ids[] = {
+ XTAL, AXG_CLKID_FCLK_DIV5, AXG_CLKID_FCLK_DIV4, AXG_CLKID_FCLK_DIV3
+};
+
+static const struct meson_pwm_data pwm_axg_ee_data = {
+ .parent_ids = pwm_axg_ee_parent_ids,
+ .num_parents = ARRAY_SIZE(pwm_axg_ee_parent_ids),
+};
+
+static const long pwm_axg_ao_parent_ids[] = {
+ AXG_CLKID_CLK81, XTAL, AXG_CLKID_FCLK_DIV4, AXG_CLKID_FCLK_DIV5
+};
+
+static const struct meson_pwm_data pwm_axg_ao_data = {
+ .parent_ids = pwm_axg_ao_parent_ids,
+ .num_parents = ARRAY_SIZE(pwm_axg_ao_parent_ids),
+};
+
+/* Local clock ids aliases to avoid define conflicts */
+#define G12A_CLKID_FCLK_DIV3 3
+#define G12A_CLKID_FCLK_DIV4 4
+#define G12A_CLKID_FCLK_DIV5 5
+#define G12A_CLKID_CLK81 10
+#define G12A_CLKID_HDMI_PLL 128
+
+static const long pwm_g12a_ao_ab_parent_ids[] = {
+ XTAL, G12A_CLKID_CLK81, G12A_CLKID_FCLK_DIV4, G12A_CLKID_FCLK_DIV5
+};
+
+static const struct meson_pwm_data pwm_g12a_ao_ab_data = {
+ .parent_ids = pwm_g12a_ao_ab_parent_ids,
+ .num_parents = ARRAY_SIZE(pwm_g12a_ao_ab_parent_ids),
+};
+
+static const long pwm_g12a_ao_cd_parent_ids[] = {
+ XTAL, G12A_CLKID_CLK81,
+};
+
+static const struct meson_pwm_data pwm_g12a_ao_cd_data = {
+ .parent_ids = pwm_g12a_ao_cd_parent_ids,
+ .num_parents = ARRAY_SIZE(pwm_g12a_ao_cd_parent_ids),
+};
+
+static const long pwm_g12a_ee_parent_ids[] = {
+ XTAL, G12A_CLKID_HDMI_PLL, G12A_CLKID_FCLK_DIV4, G12A_CLKID_FCLK_DIV3
+};
+
+static const struct meson_pwm_data pwm_g12a_ee_data = {
+ .parent_ids = pwm_g12a_ee_parent_ids,
+ .num_parents = ARRAY_SIZE(pwm_g12a_ee_parent_ids),
+};
+
+static const struct udevice_id meson_pwm_ids[] = {
+ {
+ .compatible = "amlogic,meson-gxbb-pwm",
+ .data = (ulong)&pwm_gxbb_data
+ },
+ {
+ .compatible = "amlogic,meson-gxbb-ao-pwm",
+ .data = (ulong)&pwm_gxbb_ao_data
+ },
+ {
+ .compatible = "amlogic,meson-axg-ee-pwm",
+ .data = (ulong)&pwm_axg_ee_data
+ },
+ {
+ .compatible = "amlogic,meson-axg-ao-pwm",
+ .data = (ulong)&pwm_axg_ao_data
+ },
+ {
+ .compatible = "amlogic,meson-g12a-ee-pwm",
+ .data = (ulong)&pwm_g12a_ee_data
+ },
+ {
+ .compatible = "amlogic,meson-g12a-ao-pwm-ab",
+ .data = (ulong)&pwm_g12a_ao_ab_data
+ },
+ {
+ .compatible = "amlogic,meson-g12a-ao-pwm-cd",
+ .data = (ulong)&pwm_g12a_ao_cd_data
+ },
+};
+
+U_BOOT_DRIVER(meson_pwm) = {
+ .name = "meson_pwm",
+ .id = UCLASS_PWM,
+ .of_match = meson_pwm_ids,
+ .ops = &meson_pwm_ops,
+ .of_to_plat = meson_pwm_of_to_plat,
+ .probe = meson_pwm_probe,
+ .priv_auto = sizeof(struct meson_pwm),
+};
diff --git a/roms/u-boot/drivers/pwm/pwm-mtk.c b/roms/u-boot/drivers/pwm/pwm-mtk.c
new file mode 100644
index 000000000..aee1d825a
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/pwm-mtk.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Sam Shih <sam.shih@mediatek.com>
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <pwm.h>
+#include <div64.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
+
+/* PWM registers and bits definitions */
+#define PWMCON 0x00
+#define PWMHDUR 0x04
+#define PWMLDUR 0x08
+#define PWMGDUR 0x0c
+#define PWMWAVENUM 0x28
+#define PWMDWIDTH 0x2c
+#define PWM45DWIDTH_FIXUP 0x30
+#define PWMTHRES 0x30
+#define PWM45THRES_FIXUP 0x34
+
+#define PWM_CLK_DIV_MAX 7
+#define MAX_PWM_NUM 8
+
+#define NSEC_PER_SEC 1000000000L
+
+static const unsigned int mtk_pwm_reg_offset[] = {
+ 0x0010, 0x0050, 0x0090, 0x00d0, 0x0110, 0x0150, 0x0190, 0x0220
+};
+
+struct mtk_pwm_soc {
+ unsigned int num_pwms;
+ bool pwm45_fixup;
+};
+
+struct mtk_pwm_priv {
+ void __iomem *base;
+ struct clk top_clk;
+ struct clk main_clk;
+ struct clk pwm_clks[MAX_PWM_NUM];
+ const struct mtk_pwm_soc *soc;
+};
+
+static void mtk_pwm_w32(struct udevice *dev, uint channel, uint reg, uint val)
+{
+ struct mtk_pwm_priv *priv = dev_get_priv(dev);
+ u32 offset = mtk_pwm_reg_offset[channel];
+
+ writel(val, priv->base + offset + reg);
+}
+
+static int mtk_pwm_set_config(struct udevice *dev, uint channel,
+ uint period_ns, uint duty_ns)
+{
+ struct mtk_pwm_priv *priv = dev_get_priv(dev);
+ u32 clkdiv = 0, clksel = 0, cnt_period, cnt_duty,
+ reg_width = PWMDWIDTH, reg_thres = PWMTHRES;
+ u64 resolution;
+ int ret = 0;
+
+ clk_enable(&priv->top_clk);
+ clk_enable(&priv->main_clk);
+ /* Using resolution in picosecond gets accuracy higher */
+ resolution = (u64)NSEC_PER_SEC * 1000;
+ do_div(resolution, clk_get_rate(&priv->pwm_clks[channel]));
+ cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution);
+ while (cnt_period > 8191) {
+ resolution *= 2;
+ clkdiv++;
+ cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000,
+ resolution);
+ if (clkdiv > PWM_CLK_DIV_MAX && clksel == 0) {
+ clksel = 1;
+ clkdiv = 0;
+ resolution = (u64)NSEC_PER_SEC * 1000 * 1625;
+ do_div(resolution,
+ clk_get_rate(&priv->pwm_clks[channel]));
+ cnt_period = DIV_ROUND_CLOSEST_ULL(
+ (u64)period_ns * 1000, resolution);
+ clk_enable(&priv->pwm_clks[channel]);
+ }
+ }
+ if (clkdiv > PWM_CLK_DIV_MAX && clksel == 1) {
+ printf("pwm period %u not supported\n", period_ns);
+ return -EINVAL;
+ }
+ if (priv->soc->pwm45_fixup && channel > 2) {
+ /*
+ * PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
+ * from the other PWMs on MT7623.
+ */
+ reg_width = PWM45DWIDTH_FIXUP;
+ reg_thres = PWM45THRES_FIXUP;
+ }
+ cnt_duty = DIV_ROUND_CLOSEST_ULL((u64)duty_ns * 1000, resolution);
+ if (clksel == 1)
+ mtk_pwm_w32(dev, channel, PWMCON, BIT(15) | BIT(3) | clkdiv);
+ else
+ mtk_pwm_w32(dev, channel, PWMCON, BIT(15) | clkdiv);
+ mtk_pwm_w32(dev, channel, reg_width, cnt_period);
+ mtk_pwm_w32(dev, channel, reg_thres, cnt_duty);
+
+ return ret;
+};
+
+static int mtk_pwm_set_enable(struct udevice *dev, uint channel, bool enable)
+{
+ struct mtk_pwm_priv *priv = dev_get_priv(dev);
+ u32 val = 0;
+
+ val = readl(priv->base);
+ if (enable)
+ val |= BIT(channel);
+ else
+ val &= ~BIT(channel);
+ writel(val, priv->base);
+
+ return 0;
+};
+
+static int mtk_pwm_probe(struct udevice *dev)
+{
+ struct mtk_pwm_priv *priv = dev_get_priv(dev);
+ int ret = 0;
+ int i;
+
+ priv->soc = (struct mtk_pwm_soc *)dev_get_driver_data(dev);
+ priv->base = dev_read_addr_ptr(dev);
+ if (!priv->base)
+ return -EINVAL;
+ ret = clk_get_by_name(dev, "top", &priv->top_clk);
+ if (ret < 0)
+ return ret;
+ ret = clk_get_by_name(dev, "main", &priv->main_clk);
+ if (ret < 0)
+ return ret;
+ for (i = 0; i < priv->soc->num_pwms; i++) {
+ char name[8];
+
+ snprintf(name, sizeof(name), "pwm%d", i + 1);
+ ret = clk_get_by_name(dev, name, &priv->pwm_clks[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+static const struct pwm_ops mtk_pwm_ops = {
+ .set_config = mtk_pwm_set_config,
+ .set_enable = mtk_pwm_set_enable,
+};
+
+static const struct mtk_pwm_soc mt7622_data = {
+ .num_pwms = 6,
+ .pwm45_fixup = false,
+};
+
+static const struct mtk_pwm_soc mt7623_data = {
+ .num_pwms = 5,
+ .pwm45_fixup = true,
+};
+
+static const struct mtk_pwm_soc mt7629_data = {
+ .num_pwms = 1,
+ .pwm45_fixup = false,
+};
+
+static const struct udevice_id mtk_pwm_ids[] = {
+ { .compatible = "mediatek,mt7622-pwm", .data = (ulong)&mt7622_data },
+ { .compatible = "mediatek,mt7623-pwm", .data = (ulong)&mt7623_data },
+ { .compatible = "mediatek,mt7629-pwm", .data = (ulong)&mt7629_data },
+ { }
+};
+
+U_BOOT_DRIVER(mtk_pwm) = {
+ .name = "mtk_pwm",
+ .id = UCLASS_PWM,
+ .of_match = mtk_pwm_ids,
+ .ops = &mtk_pwm_ops,
+ .probe = mtk_pwm_probe,
+ .priv_auto = sizeof(struct mtk_pwm_priv),
+};
diff --git a/roms/u-boot/drivers/pwm/pwm-sifive.c b/roms/u-boot/drivers/pwm/pwm-sifive.c
new file mode 100644
index 000000000..b9813a3b6
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/pwm-sifive.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2020 SiFive, Inc
+ * For SiFive's PWM IP block documentation please refer Chapter 14 of
+ * Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf
+ *
+ * Limitations:
+ * - When changing both duty cycle and period, we cannot prevent in
+ * software that the output might produce a period with mixed
+ * settings (new period length and old duty cycle).
+ * - The hardware cannot generate a 100% duty cycle.
+ * - The hardware generates only inverted output.
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <div64.h>
+#include <dm.h>
+#include <pwm.h>
+#include <regmap.h>
+#include <asm/global_data.h>
+#include <linux/io.h>
+#include <linux/log2.h>
+#include <linux/bitfield.h>
+
+/* PWMCFG fields */
+#define PWM_SIFIVE_PWMCFG_SCALE GENMASK(3, 0)
+#define PWM_SIFIVE_PWMCFG_STICKY BIT(8)
+#define PWM_SIFIVE_PWMCFG_ZERO_CMP BIT(9)
+#define PWM_SIFIVE_PWMCFG_DEGLITCH BIT(10)
+#define PWM_SIFIVE_PWMCFG_EN_ALWAYS BIT(12)
+#define PWM_SIFIVE_PWMCFG_EN_ONCE BIT(13)
+#define PWM_SIFIVE_PWMCFG_CENTER BIT(16)
+#define PWM_SIFIVE_PWMCFG_GANG BIT(24)
+#define PWM_SIFIVE_PWMCFG_IP BIT(28)
+
+/* PWM_SIFIVE_SIZE_PWMCMP is used to calculate offset for pwmcmpX registers */
+#define PWM_SIFIVE_SIZE_PWMCMP 4
+#define PWM_SIFIVE_CMPWIDTH 16
+
+#define PWM_SIFIVE_CHANNEL_ENABLE_VAL 0
+#define PWM_SIFIVE_CHANNEL_DISABLE_VAL 0xffff
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct pwm_sifive_regs {
+ unsigned long cfg;
+ unsigned long cnt;
+ unsigned long pwms;
+ unsigned long cmp0;
+};
+
+struct pwm_sifive_data {
+ struct pwm_sifive_regs regs;
+};
+
+struct pwm_sifive_priv {
+ void __iomem *base;
+ ulong freq;
+ const struct pwm_sifive_data *data;
+};
+
+static int pwm_sifive_set_config(struct udevice *dev, uint channel,
+ uint period_ns, uint duty_ns)
+{
+ struct pwm_sifive_priv *priv = dev_get_priv(dev);
+ const struct pwm_sifive_regs *regs = &priv->data->regs;
+ unsigned long scale_pow;
+ unsigned long long num;
+ u32 scale, val = 0, frac;
+
+ debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns);
+
+ /*
+ * The PWM unit is used with pwmzerocmp=0, so the only way to modify the
+ * period length is using pwmscale which provides the number of bits the
+ * counter is shifted before being feed to the comparators. A period
+ * lasts (1 << (PWM_SIFIVE_CMPWIDTH + pwmscale)) clock ticks.
+ * (1 << (PWM_SIFIVE_CMPWIDTH + scale)) * 10^9/rate = period
+ */
+ scale_pow = lldiv((uint64_t)priv->freq * period_ns, 1000000000);
+ scale = clamp(ilog2(scale_pow) - PWM_SIFIVE_CMPWIDTH, 0, 0xf);
+ val |= (FIELD_PREP(PWM_SIFIVE_PWMCFG_SCALE, scale) | PWM_SIFIVE_PWMCFG_EN_ALWAYS);
+
+ /*
+ * The problem of output producing mixed setting as mentioned at top,
+ * occurs here. To minimize the window for this problem, we are
+ * calculating the register values first and then writing them
+ * consecutively
+ */
+ num = (u64)duty_ns * (1U << PWM_SIFIVE_CMPWIDTH);
+ frac = DIV_ROUND_CLOSEST_ULL(num, period_ns);
+ frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1);
+ frac = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - frac;
+
+ writel(val, priv->base + regs->cfg);
+ writel(frac, priv->base + regs->cmp0 + channel *
+ PWM_SIFIVE_SIZE_PWMCMP);
+
+ return 0;
+}
+
+static int pwm_sifive_set_enable(struct udevice *dev, uint channel, bool enable)
+{
+ struct pwm_sifive_priv *priv = dev_get_priv(dev);
+ const struct pwm_sifive_regs *regs = &priv->data->regs;
+
+ debug("%s: Enable '%s'\n", __func__, dev->name);
+
+ if (enable)
+ writel(PWM_SIFIVE_CHANNEL_ENABLE_VAL, priv->base +
+ regs->cmp0 + channel * PWM_SIFIVE_SIZE_PWMCMP);
+ else
+ writel(PWM_SIFIVE_CHANNEL_DISABLE_VAL, priv->base +
+ regs->cmp0 + channel * PWM_SIFIVE_SIZE_PWMCMP);
+
+ return 0;
+}
+
+static int pwm_sifive_of_to_plat(struct udevice *dev)
+{
+ struct pwm_sifive_priv *priv = dev_get_priv(dev);
+
+ priv->base = dev_read_addr_ptr(dev);
+
+ return 0;
+}
+
+static int pwm_sifive_probe(struct udevice *dev)
+{
+ struct pwm_sifive_priv *priv = dev_get_priv(dev);
+ struct clk clk;
+ int ret = 0;
+
+ ret = clk_get_by_index(dev, 0, &clk);
+ if (ret < 0) {
+ debug("%s get clock fail!\n", __func__);
+ return -EINVAL;
+ }
+
+ priv->freq = clk_get_rate(&clk);
+ priv->data = (struct pwm_sifive_data *)dev_get_driver_data(dev);
+
+ return 0;
+}
+
+static const struct pwm_ops pwm_sifive_ops = {
+ .set_config = pwm_sifive_set_config,
+ .set_enable = pwm_sifive_set_enable,
+};
+
+static const struct pwm_sifive_data pwm_data = {
+ .regs = {
+ .cfg = 0x00,
+ .cnt = 0x08,
+ .pwms = 0x10,
+ .cmp0 = 0x20,
+ },
+};
+
+static const struct udevice_id pwm_sifive_ids[] = {
+ { .compatible = "sifive,pwm0", .data = (ulong)&pwm_data},
+ { }
+};
+
+U_BOOT_DRIVER(pwm_sifive) = {
+ .name = "pwm_sifive",
+ .id = UCLASS_PWM,
+ .of_match = pwm_sifive_ids,
+ .ops = &pwm_sifive_ops,
+ .of_to_plat = pwm_sifive_of_to_plat,
+ .probe = pwm_sifive_probe,
+ .priv_auto = sizeof(struct pwm_sifive_priv),
+};
diff --git a/roms/u-boot/drivers/pwm/pwm-ti-ehrpwm.c b/roms/u-boot/drivers/pwm/pwm-ti-ehrpwm.c
new file mode 100644
index 000000000..f09914519
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/pwm-ti-ehrpwm.c
@@ -0,0 +1,468 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * EHRPWM PWM driver
+ *
+ * Copyright (C) 2020 Dario Binacchi <dariobin@libero.it>
+ *
+ * Based on Linux kernel drivers/pwm/pwm-tiehrpwm.c
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <div64.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <pwm.h>
+#include <asm/io.h>
+
+#define NSEC_PER_SEC 1000000000L
+
+/* Time base module registers */
+#define TI_EHRPWM_TBCTL 0x00
+#define TI_EHRPWM_TBPRD 0x0A
+
+#define TI_EHRPWM_TBCTL_PRDLD_MASK BIT(3)
+#define TI_EHRPWM_TBCTL_PRDLD_SHDW 0
+#define TI_EHRPWM_TBCTL_PRDLD_IMDT BIT(3)
+#define TI_EHRPWM_TBCTL_CLKDIV_MASK GENMASK(12, 7)
+#define TI_EHRPWM_TBCTL_CTRMODE_MASK GENMASK(1, 0)
+#define TI_EHRPWM_TBCTL_CTRMODE_UP 0
+#define TI_EHRPWM_TBCTL_CTRMODE_DOWN BIT(0)
+#define TI_EHRPWM_TBCTL_CTRMODE_UPDOWN BIT(1)
+#define TI_EHRPWM_TBCTL_CTRMODE_FREEZE GENMASK(1, 0)
+
+#define TI_EHRPWM_TBCTL_HSPCLKDIV_SHIFT 7
+#define TI_EHRPWM_TBCTL_CLKDIV_SHIFT 10
+
+#define TI_EHRPWM_CLKDIV_MAX 7
+#define TI_EHRPWM_HSPCLKDIV_MAX 7
+#define TI_EHRPWM_PERIOD_MAX 0xFFFF
+
+/* Counter compare module registers */
+#define TI_EHRPWM_CMPA 0x12
+#define TI_EHRPWM_CMPB 0x14
+
+/* Action qualifier module registers */
+#define TI_EHRPWM_AQCTLA 0x16
+#define TI_EHRPWM_AQCTLB 0x18
+#define TI_EHRPWM_AQSFRC 0x1A
+#define TI_EHRPWM_AQCSFRC 0x1C
+
+#define TI_EHRPWM_AQCTL_CBU_MASK GENMASK(9, 8)
+#define TI_EHRPWM_AQCTL_CBU_FRCLOW BIT(8)
+#define TI_EHRPWM_AQCTL_CBU_FRCHIGH BIT(9)
+#define TI_EHRPWM_AQCTL_CBU_FRCTOGGLE GENMASK(9, 8)
+#define TI_EHRPWM_AQCTL_CAU_MASK GENMASK(5, 4)
+#define TI_EHRPWM_AQCTL_CAU_FRCLOW BIT(4)
+#define TI_EHRPWM_AQCTL_CAU_FRCHIGH BIT(5)
+#define TI_EHRPWM_AQCTL_CAU_FRCTOGGLE GENMASK(5, 4)
+#define TI_EHRPWM_AQCTL_PRD_MASK GENMASK(3, 2)
+#define TI_EHRPWM_AQCTL_PRD_FRCLOW BIT(2)
+#define TI_EHRPWM_AQCTL_PRD_FRCHIGH BIT(3)
+#define TI_EHRPWM_AQCTL_PRD_FRCTOGGLE GENMASK(3, 2)
+#define TI_EHRPWM_AQCTL_ZRO_MASK GENMASK(1, 0)
+#define TI_EHRPWM_AQCTL_ZRO_FRCLOW BIT(0)
+#define TI_EHRPWM_AQCTL_ZRO_FRCHIGH BIT(1)
+#define TI_EHRPWM_AQCTL_ZRO_FRCTOGGLE GENMASK(1, 0)
+
+#define TI_EHRPWM_AQCTL_CHANA_POLNORMAL (TI_EHRPWM_AQCTL_CAU_FRCLOW | \
+ TI_EHRPWM_AQCTL_PRD_FRCHIGH | \
+ TI_EHRPWM_AQCTL_ZRO_FRCHIGH)
+#define TI_EHRPWM_AQCTL_CHANA_POLINVERSED (TI_EHRPWM_AQCTL_CAU_FRCHIGH | \
+ TI_EHRPWM_AQCTL_PRD_FRCLOW | \
+ TI_EHRPWM_AQCTL_ZRO_FRCLOW)
+#define TI_EHRPWM_AQCTL_CHANB_POLNORMAL (TI_EHRPWM_AQCTL_CBU_FRCLOW | \
+ TI_EHRPWM_AQCTL_PRD_FRCHIGH | \
+ TI_EHRPWM_AQCTL_ZRO_FRCHIGH)
+#define TI_EHRPWM_AQCTL_CHANB_POLINVERSED (TI_EHRPWM_AQCTL_CBU_FRCHIGH | \
+ TI_EHRPWM_AQCTL_PRD_FRCLOW | \
+ TI_EHRPWM_AQCTL_ZRO_FRCLOW)
+
+#define TI_EHRPWM_AQSFRC_RLDCSF_MASK GENMASK(7, 6)
+#define TI_EHRPWM_AQSFRC_RLDCSF_ZRO 0
+#define TI_EHRPWM_AQSFRC_RLDCSF_PRD BIT(6)
+#define TI_EHRPWM_AQSFRC_RLDCSF_ZROPRD BIT(7)
+#define TI_EHRPWM_AQSFRC_RLDCSF_IMDT GENMASK(7, 6)
+
+#define TI_EHRPWM_AQCSFRC_CSFB_MASK GENMASK(3, 2)
+#define TI_EHRPWM_AQCSFRC_CSFB_FRCDIS 0
+#define TI_EHRPWM_AQCSFRC_CSFB_FRCLOW BIT(2)
+#define TI_EHRPWM_AQCSFRC_CSFB_FRCHIGH BIT(3)
+#define TI_EHRPWM_AQCSFRC_CSFB_DISSWFRC GENMASK(3, 2)
+#define TI_EHRPWM_AQCSFRC_CSFA_MASK GENMASK(1, 0)
+#define TI_EHRPWM_AQCSFRC_CSFA_FRCDIS 0
+#define TI_EHRPWM_AQCSFRC_CSFA_FRCLOW BIT(0)
+#define TI_EHRPWM_AQCSFRC_CSFA_FRCHIGH BIT(1)
+#define TI_EHRPWM_AQCSFRC_CSFA_DISSWFRC GENMASK(1, 0)
+
+#define TI_EHRPWM_NUM_CHANNELS 2
+
+struct ti_ehrpwm_priv {
+ fdt_addr_t regs;
+ u32 clk_rate;
+ struct clk tbclk;
+ unsigned long period_cycles[TI_EHRPWM_NUM_CHANNELS];
+ bool polarity_reversed[TI_EHRPWM_NUM_CHANNELS];
+};
+
+static void ti_ehrpwm_modify(u16 val, u16 mask, fdt_addr_t reg)
+{
+ unsigned short v;
+
+ v = readw(reg);
+ v &= ~mask;
+ v |= val & mask;
+ writew(v, reg);
+}
+
+static int ti_ehrpwm_set_invert(struct udevice *dev, uint channel,
+ bool polarity)
+{
+ struct ti_ehrpwm_priv *priv = dev_get_priv(dev);
+
+ if (channel >= TI_EHRPWM_NUM_CHANNELS)
+ return -ENOSPC;
+
+ /* Configuration of polarity in hardware delayed, do at enable */
+ priv->polarity_reversed[channel] = polarity;
+ return 0;
+}
+
+/**
+ * set_prescale_div - Set up the prescaler divider function
+ * @rqst_prescaler: prescaler value min
+ * @prescale_div: prescaler value set
+ * @tb_clk_div: Time Base Control prescaler bits
+ */
+static int set_prescale_div(unsigned long rqst_prescaler, u16 *prescale_div,
+ u16 *tb_clk_div)
+{
+ unsigned int clkdiv, hspclkdiv;
+
+ for (clkdiv = 0; clkdiv <= TI_EHRPWM_CLKDIV_MAX; clkdiv++) {
+ for (hspclkdiv = 0; hspclkdiv <= TI_EHRPWM_HSPCLKDIV_MAX;
+ hspclkdiv++) {
+ /*
+ * calculations for prescaler value :
+ * prescale_div = HSPCLKDIVIDER * CLKDIVIDER.
+ * HSPCLKDIVIDER = 2 ** hspclkdiv
+ * CLKDIVIDER = (1), if clkdiv == 0 *OR*
+ * (2 * clkdiv), if clkdiv != 0
+ *
+ * Configure prescale_div value such that period
+ * register value is less than 65535.
+ */
+
+ *prescale_div = (1 << clkdiv) *
+ (hspclkdiv ? (hspclkdiv * 2) : 1);
+ if (*prescale_div > rqst_prescaler) {
+ *tb_clk_div =
+ (clkdiv << TI_EHRPWM_TBCTL_CLKDIV_SHIFT) |
+ (hspclkdiv <<
+ TI_EHRPWM_TBCTL_HSPCLKDIV_SHIFT);
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+static void ti_ehrpwm_configure_polarity(struct udevice *dev, uint channel)
+{
+ struct ti_ehrpwm_priv *priv = dev_get_priv(dev);
+ u16 aqctl_val, aqctl_mask;
+ unsigned int aqctl_reg;
+
+ /*
+ * Configure PWM output to HIGH/LOW level on counter
+ * reaches compare register value and LOW/HIGH level
+ * on counter value reaches period register value and
+ * zero value on counter
+ */
+ if (channel == 1) {
+ aqctl_reg = TI_EHRPWM_AQCTLB;
+ aqctl_mask = TI_EHRPWM_AQCTL_CBU_MASK;
+
+ if (priv->polarity_reversed[channel])
+ aqctl_val = TI_EHRPWM_AQCTL_CHANB_POLINVERSED;
+ else
+ aqctl_val = TI_EHRPWM_AQCTL_CHANB_POLNORMAL;
+ } else {
+ aqctl_reg = TI_EHRPWM_AQCTLA;
+ aqctl_mask = TI_EHRPWM_AQCTL_CAU_MASK;
+
+ if (priv->polarity_reversed[channel])
+ aqctl_val = TI_EHRPWM_AQCTL_CHANA_POLINVERSED;
+ else
+ aqctl_val = TI_EHRPWM_AQCTL_CHANA_POLNORMAL;
+ }
+
+ aqctl_mask |= TI_EHRPWM_AQCTL_PRD_MASK | TI_EHRPWM_AQCTL_ZRO_MASK;
+ ti_ehrpwm_modify(aqctl_val, aqctl_mask, priv->regs + aqctl_reg);
+}
+
+/*
+ * period_ns = 10^9 * (ps_divval * period_cycles) / PWM_CLK_RATE
+ * duty_ns = 10^9 * (ps_divval * duty_cycles) / PWM_CLK_RATE
+ */
+static int ti_ehrpwm_set_config(struct udevice *dev, uint channel,
+ uint period_ns, uint duty_ns)
+{
+ struct ti_ehrpwm_priv *priv = dev_get_priv(dev);
+ u32 period_cycles, duty_cycles;
+ u16 ps_divval, tb_divval;
+ unsigned int i, cmp_reg;
+ unsigned long long c;
+
+ if (channel >= TI_EHRPWM_NUM_CHANNELS)
+ return -ENOSPC;
+
+ if (period_ns > NSEC_PER_SEC)
+ return -ERANGE;
+
+ c = priv->clk_rate;
+ c = c * period_ns;
+ do_div(c, NSEC_PER_SEC);
+ period_cycles = (unsigned long)c;
+
+ if (period_cycles < 1) {
+ period_cycles = 1;
+ duty_cycles = 1;
+ } else {
+ c = priv->clk_rate;
+ c = c * duty_ns;
+ do_div(c, NSEC_PER_SEC);
+ duty_cycles = (unsigned long)c;
+ }
+
+ dev_dbg(dev, "channel=%d, period_ns=%d, duty_ns=%d\n",
+ channel, period_ns, duty_ns);
+
+ /*
+ * Period values should be same for multiple PWM channels as IP uses
+ * same period register for multiple channels.
+ */
+ for (i = 0; i < TI_EHRPWM_NUM_CHANNELS; i++) {
+ if (priv->period_cycles[i] &&
+ priv->period_cycles[i] != period_cycles) {
+ /*
+ * Allow channel to reconfigure period if no other
+ * channels being configured.
+ */
+ if (i == channel)
+ continue;
+
+ dev_err(dev, "period value conflicts with channel %u\n",
+ i);
+ return -EINVAL;
+ }
+ }
+
+ priv->period_cycles[channel] = period_cycles;
+
+ /* Configure clock prescaler to support Low frequency PWM wave */
+ if (set_prescale_div(period_cycles / TI_EHRPWM_PERIOD_MAX, &ps_divval,
+ &tb_divval)) {
+ dev_err(dev, "unsupported values\n");
+ return -EINVAL;
+ }
+
+ /* Update clock prescaler values */
+ ti_ehrpwm_modify(tb_divval, TI_EHRPWM_TBCTL_CLKDIV_MASK,
+ priv->regs + TI_EHRPWM_TBCTL);
+
+ /* Update period & duty cycle with presacler division */
+ period_cycles = period_cycles / ps_divval;
+ duty_cycles = duty_cycles / ps_divval;
+
+ /* Configure shadow loading on Period register */
+ ti_ehrpwm_modify(TI_EHRPWM_TBCTL_PRDLD_SHDW, TI_EHRPWM_TBCTL_PRDLD_MASK,
+ priv->regs + TI_EHRPWM_TBCTL);
+
+ writew(period_cycles, priv->regs + TI_EHRPWM_TBPRD);
+
+ /* Configure ehrpwm counter for up-count mode */
+ ti_ehrpwm_modify(TI_EHRPWM_TBCTL_CTRMODE_UP,
+ TI_EHRPWM_TBCTL_CTRMODE_MASK,
+ priv->regs + TI_EHRPWM_TBCTL);
+
+ if (channel == 1)
+ /* Channel 1 configured with compare B register */
+ cmp_reg = TI_EHRPWM_CMPB;
+ else
+ /* Channel 0 configured with compare A register */
+ cmp_reg = TI_EHRPWM_CMPA;
+
+ writew(duty_cycles, priv->regs + cmp_reg);
+ return 0;
+}
+
+static int ti_ehrpwm_disable(struct udevice *dev, uint channel)
+{
+ struct ti_ehrpwm_priv *priv = dev_get_priv(dev);
+ u16 aqcsfrc_val, aqcsfrc_mask;
+ int err;
+
+ if (channel >= TI_EHRPWM_NUM_CHANNELS)
+ return -ENOSPC;
+
+ /* Action Qualifier puts PWM output low forcefully */
+ if (channel) {
+ aqcsfrc_val = TI_EHRPWM_AQCSFRC_CSFB_FRCLOW;
+ aqcsfrc_mask = TI_EHRPWM_AQCSFRC_CSFB_MASK;
+ } else {
+ aqcsfrc_val = TI_EHRPWM_AQCSFRC_CSFA_FRCLOW;
+ aqcsfrc_mask = TI_EHRPWM_AQCSFRC_CSFA_MASK;
+ }
+
+ /* Update shadow register first before modifying active register */
+ ti_ehrpwm_modify(TI_EHRPWM_AQSFRC_RLDCSF_ZRO,
+ TI_EHRPWM_AQSFRC_RLDCSF_MASK,
+ priv->regs + TI_EHRPWM_AQSFRC);
+
+ ti_ehrpwm_modify(aqcsfrc_val, aqcsfrc_mask,
+ priv->regs + TI_EHRPWM_AQCSFRC);
+
+ /*
+ * Changes to immediate action on Action Qualifier. This puts
+ * Action Qualifier control on PWM output from next TBCLK
+ */
+ ti_ehrpwm_modify(TI_EHRPWM_AQSFRC_RLDCSF_IMDT,
+ TI_EHRPWM_AQSFRC_RLDCSF_MASK,
+ priv->regs + TI_EHRPWM_AQSFRC);
+
+ ti_ehrpwm_modify(aqcsfrc_val, aqcsfrc_mask,
+ priv->regs + TI_EHRPWM_AQCSFRC);
+
+ /* Disabling TBCLK on PWM disable */
+ err = clk_disable(&priv->tbclk);
+ if (err) {
+ dev_err(dev, "failed to disable tbclk\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static int ti_ehrpwm_enable(struct udevice *dev, uint channel)
+{
+ struct ti_ehrpwm_priv *priv = dev_get_priv(dev);
+ u16 aqcsfrc_val, aqcsfrc_mask;
+ int err;
+
+ if (channel >= TI_EHRPWM_NUM_CHANNELS)
+ return -ENOSPC;
+
+ /* Disabling Action Qualifier on PWM output */
+ if (channel) {
+ aqcsfrc_val = TI_EHRPWM_AQCSFRC_CSFB_FRCDIS;
+ aqcsfrc_mask = TI_EHRPWM_AQCSFRC_CSFB_MASK;
+ } else {
+ aqcsfrc_val = TI_EHRPWM_AQCSFRC_CSFA_FRCDIS;
+ aqcsfrc_mask = TI_EHRPWM_AQCSFRC_CSFA_MASK;
+ }
+
+ /* Changes to shadow mode */
+ ti_ehrpwm_modify(TI_EHRPWM_AQSFRC_RLDCSF_ZRO,
+ TI_EHRPWM_AQSFRC_RLDCSF_MASK,
+ priv->regs + TI_EHRPWM_AQSFRC);
+
+ ti_ehrpwm_modify(aqcsfrc_val, aqcsfrc_mask,
+ priv->regs + TI_EHRPWM_AQCSFRC);
+
+ /* Channels polarity can be configured from action qualifier module */
+ ti_ehrpwm_configure_polarity(dev, channel);
+
+ err = clk_enable(&priv->tbclk);
+ if (err) {
+ dev_err(dev, "failed to enable tbclk\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static int ti_ehrpwm_set_enable(struct udevice *dev, uint channel, bool enable)
+{
+ if (enable)
+ return ti_ehrpwm_enable(dev, channel);
+
+ return ti_ehrpwm_disable(dev, channel);
+}
+
+static int ti_ehrpwm_of_to_plat(struct udevice *dev)
+{
+ struct ti_ehrpwm_priv *priv = dev_get_priv(dev);
+
+ priv->regs = dev_read_addr(dev);
+ if (priv->regs == FDT_ADDR_T_NONE) {
+ dev_err(dev, "invalid address\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "regs=0x%08lx\n", priv->regs);
+ return 0;
+}
+
+static int ti_ehrpwm_remove(struct udevice *dev)
+{
+ struct ti_ehrpwm_priv *priv = dev_get_priv(dev);
+
+ clk_release_all(&priv->tbclk, 1);
+ return 0;
+}
+
+static int ti_ehrpwm_probe(struct udevice *dev)
+{
+ struct ti_ehrpwm_priv *priv = dev_get_priv(dev);
+ struct clk clk;
+ int err;
+
+ err = clk_get_by_name(dev, "fck", &clk);
+ if (err) {
+ dev_err(dev, "failed to get clock\n");
+ return err;
+ }
+
+ priv->clk_rate = clk_get_rate(&clk);
+ if (IS_ERR_VALUE(priv->clk_rate) || !priv->clk_rate) {
+ dev_err(dev, "failed to get clock rate\n");
+ if (IS_ERR_VALUE(priv->clk_rate))
+ return priv->clk_rate;
+
+ return -EINVAL;
+ }
+
+ /* Acquire tbclk for Time Base EHRPWM submodule */
+ err = clk_get_by_name(dev, "tbclk", &priv->tbclk);
+ if (err) {
+ dev_err(dev, "failed to get tbclk clock\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct pwm_ops ti_ehrpwm_ops = {
+ .set_config = ti_ehrpwm_set_config,
+ .set_enable = ti_ehrpwm_set_enable,
+ .set_invert = ti_ehrpwm_set_invert,
+};
+
+static const struct udevice_id ti_ehrpwm_ids[] = {
+ {.compatible = "ti,am3352-ehrpwm"},
+ {.compatible = "ti,am33xx-ehrpwm"},
+ {}
+};
+
+U_BOOT_DRIVER(ti_ehrpwm) = {
+ .name = "ti_ehrpwm",
+ .id = UCLASS_PWM,
+ .of_match = ti_ehrpwm_ids,
+ .ops = &ti_ehrpwm_ops,
+ .of_to_plat = ti_ehrpwm_of_to_plat,
+ .probe = ti_ehrpwm_probe,
+ .remove = ti_ehrpwm_remove,
+ .priv_auto = sizeof(struct ti_ehrpwm_priv),
+};
diff --git a/roms/u-boot/drivers/pwm/pwm-uclass.c b/roms/u-boot/drivers/pwm/pwm-uclass.c
new file mode 100644
index 000000000..027181c64
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/pwm-uclass.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2016 Google, Inc
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <pwm.h>
+
+int pwm_set_invert(struct udevice *dev, uint channel, bool polarity)
+{
+ struct pwm_ops *ops = pwm_get_ops(dev);
+
+ if (!ops->set_invert)
+ return -ENOSYS;
+
+ return ops->set_invert(dev, channel, polarity);
+}
+
+int pwm_set_config(struct udevice *dev, uint channel, uint period_ns,
+ uint duty_ns)
+{
+ struct pwm_ops *ops = pwm_get_ops(dev);
+
+ if (!ops->set_config)
+ return -ENOSYS;
+
+ return ops->set_config(dev, channel, period_ns, duty_ns);
+}
+
+int pwm_set_enable(struct udevice *dev, uint channel, bool enable)
+{
+ struct pwm_ops *ops = pwm_get_ops(dev);
+
+ if (!ops->set_enable)
+ return -ENOSYS;
+
+ return ops->set_enable(dev, channel, enable);
+}
+
+UCLASS_DRIVER(pwm) = {
+ .id = UCLASS_PWM,
+ .name = "pwm",
+};
diff --git a/roms/u-boot/drivers/pwm/rk_pwm.c b/roms/u-boot/drivers/pwm/rk_pwm.c
new file mode 100644
index 000000000..071eb04fd
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/rk_pwm.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2016 Google, Inc
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <div64.h>
+#include <dm.h>
+#include <log.h>
+#include <pwm.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <asm/global_data.h>
+#include <asm/io.h>
+#include <asm/arch-rockchip/pwm.h>
+#include <linux/bitops.h>
+#include <power/regulator.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct rockchip_pwm_data {
+ struct rockchip_pwm_regs regs;
+ unsigned int prescaler;
+ bool supports_polarity;
+ bool supports_lock;
+ u32 enable_conf;
+ u32 enable_conf_mask;
+};
+
+struct rk_pwm_priv {
+ fdt_addr_t base;
+ ulong freq;
+ u32 conf_polarity;
+ const struct rockchip_pwm_data *data;
+};
+
+static int rk_pwm_set_invert(struct udevice *dev, uint channel, bool polarity)
+{
+ struct rk_pwm_priv *priv = dev_get_priv(dev);
+
+ if (!priv->data->supports_polarity) {
+ debug("%s: Do not support polarity\n", __func__);
+ return 0;
+ }
+
+ debug("%s: polarity=%u\n", __func__, polarity);
+ if (polarity)
+ priv->conf_polarity = PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSTIVE;
+ else
+ priv->conf_polarity = PWM_DUTY_POSTIVE | PWM_INACTIVE_NEGATIVE;
+
+ return 0;
+}
+
+static int rk_pwm_set_config(struct udevice *dev, uint channel, uint period_ns,
+ uint duty_ns)
+{
+ struct rk_pwm_priv *priv = dev_get_priv(dev);
+ const struct rockchip_pwm_regs *regs = &priv->data->regs;
+ unsigned long period, duty;
+ u32 ctrl;
+
+ debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns);
+
+ ctrl = readl(priv->base + regs->ctrl);
+ /*
+ * Lock the period and duty of previous configuration, then
+ * change the duty and period, that would not be effective.
+ */
+ if (priv->data->supports_lock) {
+ ctrl |= PWM_LOCK;
+ writel(ctrl, priv->base + regs->ctrl);
+ }
+
+ period = lldiv((uint64_t)priv->freq * period_ns,
+ priv->data->prescaler * 1000000000);
+ duty = lldiv((uint64_t)priv->freq * duty_ns,
+ priv->data->prescaler * 1000000000);
+
+ writel(period, priv->base + regs->period);
+ writel(duty, priv->base + regs->duty);
+
+ if (priv->data->supports_polarity) {
+ ctrl &= ~(PWM_DUTY_MASK | PWM_INACTIVE_MASK);
+ ctrl |= priv->conf_polarity;
+ }
+
+ /*
+ * Unlock and set polarity at the same time,
+ * the configuration of duty, period and polarity
+ * would be effective together at next period.
+ */
+ if (priv->data->supports_lock)
+ ctrl &= ~PWM_LOCK;
+ writel(ctrl, priv->base + regs->ctrl);
+
+ debug("%s: period=%lu, duty=%lu\n", __func__, period, duty);
+
+ return 0;
+}
+
+static int rk_pwm_set_enable(struct udevice *dev, uint channel, bool enable)
+{
+ struct rk_pwm_priv *priv = dev_get_priv(dev);
+ const struct rockchip_pwm_regs *regs = &priv->data->regs;
+ u32 ctrl;
+
+ debug("%s: Enable '%s'\n", __func__, dev->name);
+
+ ctrl = readl(priv->base + regs->ctrl);
+ ctrl &= ~priv->data->enable_conf_mask;
+
+ if (enable)
+ ctrl |= priv->data->enable_conf;
+ else
+ ctrl &= ~priv->data->enable_conf;
+
+ writel(ctrl, priv->base + regs->ctrl);
+
+ return 0;
+}
+
+static int rk_pwm_of_to_plat(struct udevice *dev)
+{
+ struct rk_pwm_priv *priv = dev_get_priv(dev);
+
+ priv->base = dev_read_addr(dev);
+
+ return 0;
+}
+
+static int rk_pwm_probe(struct udevice *dev)
+{
+ struct rk_pwm_priv *priv = dev_get_priv(dev);
+ struct clk clk;
+ int ret = 0;
+
+ ret = clk_get_by_index(dev, 0, &clk);
+ if (ret < 0) {
+ debug("%s get clock fail!\n", __func__);
+ return -EINVAL;
+ }
+
+ priv->freq = clk_get_rate(&clk);
+ priv->data = (struct rockchip_pwm_data *)dev_get_driver_data(dev);
+
+ if (priv->data->supports_polarity)
+ priv->conf_polarity = PWM_DUTY_POSTIVE | PWM_INACTIVE_NEGATIVE;
+
+ return 0;
+}
+
+static const struct pwm_ops rk_pwm_ops = {
+ .set_invert = rk_pwm_set_invert,
+ .set_config = rk_pwm_set_config,
+ .set_enable = rk_pwm_set_enable,
+};
+
+static const struct rockchip_pwm_data pwm_data_v1 = {
+ .regs = {
+ .duty = 0x04,
+ .period = 0x08,
+ .cntr = 0x00,
+ .ctrl = 0x0c,
+ },
+ .prescaler = 2,
+ .supports_polarity = false,
+ .supports_lock = false,
+ .enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN,
+ .enable_conf_mask = BIT(1) | BIT(3),
+};
+
+static const struct rockchip_pwm_data pwm_data_v2 = {
+ .regs = {
+ .duty = 0x08,
+ .period = 0x04,
+ .cntr = 0x00,
+ .ctrl = 0x0c,
+ },
+ .prescaler = 1,
+ .supports_polarity = true,
+ .supports_lock = false,
+ .enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | RK_PWM_ENABLE |
+ PWM_CONTINUOUS,
+ .enable_conf_mask = GENMASK(2, 0) | BIT(5) | BIT(8),
+};
+
+static const struct rockchip_pwm_data pwm_data_v3 = {
+ .regs = {
+ .duty = 0x08,
+ .period = 0x04,
+ .cntr = 0x00,
+ .ctrl = 0x0c,
+ },
+ .prescaler = 1,
+ .supports_polarity = true,
+ .supports_lock = true,
+ .enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | RK_PWM_ENABLE |
+ PWM_CONTINUOUS,
+ .enable_conf_mask = GENMASK(2, 0) | BIT(5) | BIT(8),
+};
+
+static const struct udevice_id rk_pwm_ids[] = {
+ { .compatible = "rockchip,rk2928-pwm", .data = (ulong)&pwm_data_v1},
+ { .compatible = "rockchip,rk3288-pwm", .data = (ulong)&pwm_data_v2},
+ { .compatible = "rockchip,rk3328-pwm", .data = (ulong)&pwm_data_v3},
+ { }
+};
+
+U_BOOT_DRIVER(rk_pwm) = {
+ .name = "rk_pwm",
+ .id = UCLASS_PWM,
+ .of_match = rk_pwm_ids,
+ .ops = &rk_pwm_ops,
+ .of_to_plat = rk_pwm_of_to_plat,
+ .probe = rk_pwm_probe,
+ .priv_auto = sizeof(struct rk_pwm_priv),
+};
diff --git a/roms/u-boot/drivers/pwm/sandbox_pwm.c b/roms/u-boot/drivers/pwm/sandbox_pwm.c
new file mode 100644
index 000000000..4df15f0a2
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/sandbox_pwm.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2015 Google, Inc
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <pwm.h>
+#include <asm/test.h>
+
+enum {
+ NUM_CHANNELS = 3,
+};
+
+/**
+ * struct sandbox_pwm_chan - a sandbox PWM channel
+ *
+ * @period_ns: Period of the PWM in nanoseconds
+ * @duty_ns: Current duty cycle of the PWM in nanoseconds
+ * @enable: true if the PWM is enabled
+ * @polarity: true if the PWM polarity is active high
+ */
+struct sandbox_pwm_chan {
+ uint period_ns;
+ uint duty_ns;
+ bool enable;
+ bool polarity;
+};
+
+struct sandbox_pwm_priv {
+ struct sandbox_pwm_chan chan[NUM_CHANNELS];
+};
+
+int sandbox_pwm_get_config(struct udevice *dev, uint channel, uint *period_nsp,
+ uint *duty_nsp, bool *enablep, bool *polarityp)
+{
+ struct sandbox_pwm_priv *priv = dev_get_priv(dev);
+ struct sandbox_pwm_chan *chan;
+
+ if (channel >= NUM_CHANNELS)
+ return -ENOSPC;
+ chan = &priv->chan[channel];
+ *period_nsp = chan->period_ns;
+ *duty_nsp = chan->duty_ns;
+ *enablep = chan->enable;
+ *polarityp = chan->polarity;
+
+ return 0;
+}
+
+static int sandbox_pwm_set_config(struct udevice *dev, uint channel,
+ uint period_ns, uint duty_ns)
+{
+ struct sandbox_pwm_priv *priv = dev_get_priv(dev);
+ struct sandbox_pwm_chan *chan;
+
+ if (channel >= NUM_CHANNELS)
+ return -ENOSPC;
+ chan = &priv->chan[channel];
+
+ if (channel == 2) {
+ /* Pretend to have some fixed period */
+ chan->period_ns = 4096;
+ chan->duty_ns = duty_ns * 4096 / period_ns;
+ } else {
+ chan->period_ns = period_ns;
+ chan->duty_ns = duty_ns;
+ }
+
+ return 0;
+}
+
+static int sandbox_pwm_set_enable(struct udevice *dev, uint channel,
+ bool enable)
+{
+ struct sandbox_pwm_priv *priv = dev_get_priv(dev);
+ struct sandbox_pwm_chan *chan;
+
+ if (channel >= NUM_CHANNELS)
+ return -ENOSPC;
+ chan = &priv->chan[channel];
+ chan->enable = enable;
+
+ return 0;
+}
+
+static int sandbox_pwm_set_invert(struct udevice *dev, uint channel,
+ bool polarity)
+{
+ struct sandbox_pwm_priv *priv = dev_get_priv(dev);
+ struct sandbox_pwm_chan *chan;
+
+ if (channel >= NUM_CHANNELS)
+ return -ENOSPC;
+ chan = &priv->chan[channel];
+ chan->polarity = polarity;
+
+ return 0;
+}
+
+static const struct pwm_ops sandbox_pwm_ops = {
+ .set_config = sandbox_pwm_set_config,
+ .set_enable = sandbox_pwm_set_enable,
+ .set_invert = sandbox_pwm_set_invert,
+};
+
+static const struct udevice_id sandbox_pwm_ids[] = {
+ { .compatible = "sandbox,pwm" },
+ { }
+};
+
+U_BOOT_DRIVER(warm_pwm_sandbox) = {
+ .name = "pwm_sandbox",
+ .id = UCLASS_PWM,
+ .of_match = sandbox_pwm_ids,
+ .ops = &sandbox_pwm_ops,
+ .priv_auto = sizeof(struct sandbox_pwm_priv),
+};
diff --git a/roms/u-boot/drivers/pwm/sunxi_pwm.c b/roms/u-boot/drivers/pwm/sunxi_pwm.c
new file mode 100644
index 000000000..e3d5ee456
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/sunxi_pwm.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2017-2018 Vasily Khoruzhick <anarsoul@gmail.com>
+ */
+
+#include <common.h>
+#include <div64.h>
+#include <dm.h>
+#include <log.h>
+#include <pwm.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <asm/global_data.h>
+#include <asm/io.h>
+#include <asm/arch/pwm.h>
+#include <asm/arch/gpio.h>
+#include <power/regulator.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define OSC_24MHZ 24000000
+
+struct sunxi_pwm_priv {
+ struct sunxi_pwm *regs;
+ bool invert;
+ u32 prescaler;
+};
+
+static const u32 prescaler_table[] = {
+ 120, /* 0000 */
+ 180, /* 0001 */
+ 240, /* 0010 */
+ 360, /* 0011 */
+ 480, /* 0100 */
+ 0, /* 0101 */
+ 0, /* 0110 */
+ 0, /* 0111 */
+ 12000, /* 1000 */
+ 24000, /* 1001 */
+ 36000, /* 1010 */
+ 48000, /* 1011 */
+ 72000, /* 1100 */
+ 0, /* 1101 */
+ 0, /* 1110 */
+ 1, /* 1111 */
+};
+
+static int sunxi_pwm_config_pinmux(void)
+{
+#ifdef CONFIG_MACH_SUN50I
+ sunxi_gpio_set_cfgpin(SUNXI_GPD(22), SUNXI_GPD_PWM);
+#endif
+ return 0;
+}
+
+static int sunxi_pwm_set_invert(struct udevice *dev, uint channel,
+ bool polarity)
+{
+ struct sunxi_pwm_priv *priv = dev_get_priv(dev);
+
+ debug("%s: polarity=%u\n", __func__, polarity);
+ priv->invert = polarity;
+
+ return 0;
+}
+
+static int sunxi_pwm_set_config(struct udevice *dev, uint channel,
+ uint period_ns, uint duty_ns)
+{
+ struct sunxi_pwm_priv *priv = dev_get_priv(dev);
+ struct sunxi_pwm *regs = priv->regs;
+ int best_prescaler = 0;
+ u32 v, best_period = 0, duty;
+ u64 best_scaled_freq = 0;
+ const u32 nsecs_per_sec = 1000000000U;
+
+ debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns);
+
+ for (int prescaler = 0; prescaler <= SUNXI_PWM_CTRL_PRESCALE0_MASK;
+ prescaler++) {
+ u32 period = 0;
+ u64 scaled_freq = 0;
+ if (!prescaler_table[prescaler])
+ continue;
+ scaled_freq = lldiv(OSC_24MHZ, prescaler_table[prescaler]);
+ period = lldiv(scaled_freq * period_ns, nsecs_per_sec);
+ if ((period - 1 <= SUNXI_PWM_CH0_PERIOD_MAX) &&
+ best_period < period) {
+ best_period = period;
+ best_scaled_freq = scaled_freq;
+ best_prescaler = prescaler;
+ }
+ }
+
+ if (best_period - 1 > SUNXI_PWM_CH0_PERIOD_MAX) {
+ debug("%s: failed to find prescaler value\n", __func__);
+ return -EINVAL;
+ }
+
+ duty = lldiv(best_scaled_freq * duty_ns, nsecs_per_sec);
+
+ if (priv->prescaler != best_prescaler) {
+ /* Mask clock to update prescaler */
+ v = readl(&regs->ctrl);
+ v &= ~SUNXI_PWM_CTRL_CLK_GATE;
+ writel(v, &regs->ctrl);
+ v &= ~SUNXI_PWM_CTRL_PRESCALE0_MASK;
+ v |= (best_prescaler & SUNXI_PWM_CTRL_PRESCALE0_MASK);
+ writel(v, &regs->ctrl);
+ v |= SUNXI_PWM_CTRL_CLK_GATE;
+ writel(v, &regs->ctrl);
+ priv->prescaler = best_prescaler;
+ }
+
+ writel(SUNXI_PWM_CH0_PERIOD_PRD(best_period) |
+ SUNXI_PWM_CH0_PERIOD_DUTY(duty), &regs->ch0_period);
+
+ debug("%s: prescaler: %d, period: %d, duty: %d\n",
+ __func__, priv->prescaler,
+ best_period, duty);
+
+ return 0;
+}
+
+static int sunxi_pwm_set_enable(struct udevice *dev, uint channel, bool enable)
+{
+ struct sunxi_pwm_priv *priv = dev_get_priv(dev);
+ struct sunxi_pwm *regs = priv->regs;
+ u32 v;
+
+ debug("%s: Enable '%s'\n", __func__, dev->name);
+
+ v = readl(&regs->ctrl);
+ if (!enable) {
+ v &= ~SUNXI_PWM_CTRL_ENABLE0;
+ writel(v, &regs->ctrl);
+ return 0;
+ }
+
+ sunxi_pwm_config_pinmux();
+
+ if (priv->invert)
+ v &= ~SUNXI_PWM_CTRL_CH0_ACT_STA;
+ else
+ v |= SUNXI_PWM_CTRL_CH0_ACT_STA;
+ v |= SUNXI_PWM_CTRL_ENABLE0;
+ writel(v, &regs->ctrl);
+
+ return 0;
+}
+
+static int sunxi_pwm_of_to_plat(struct udevice *dev)
+{
+ struct sunxi_pwm_priv *priv = dev_get_priv(dev);
+
+ priv->regs = dev_read_addr_ptr(dev);
+
+ return 0;
+}
+
+static int sunxi_pwm_probe(struct udevice *dev)
+{
+ return 0;
+}
+
+static const struct pwm_ops sunxi_pwm_ops = {
+ .set_invert = sunxi_pwm_set_invert,
+ .set_config = sunxi_pwm_set_config,
+ .set_enable = sunxi_pwm_set_enable,
+};
+
+static const struct udevice_id sunxi_pwm_ids[] = {
+ { .compatible = "allwinner,sun5i-a13-pwm" },
+ { .compatible = "allwinner,sun50i-a64-pwm" },
+ { }
+};
+
+U_BOOT_DRIVER(sunxi_pwm) = {
+ .name = "sunxi_pwm",
+ .id = UCLASS_PWM,
+ .of_match = sunxi_pwm_ids,
+ .ops = &sunxi_pwm_ops,
+ .of_to_plat = sunxi_pwm_of_to_plat,
+ .probe = sunxi_pwm_probe,
+ .priv_auto = sizeof(struct sunxi_pwm_priv),
+};
diff --git a/roms/u-boot/drivers/pwm/tegra_pwm.c b/roms/u-boot/drivers/pwm/tegra_pwm.c
new file mode 100644
index 000000000..36c35c608
--- /dev/null
+++ b/roms/u-boot/drivers/pwm/tegra_pwm.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2016 Google Inc.
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <log.h>
+#include <pwm.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/pwm.h>
+
+struct tegra_pwm_priv {
+ struct pwm_ctlr *regs;
+};
+
+static int tegra_pwm_set_config(struct udevice *dev, uint channel,
+ uint period_ns, uint duty_ns)
+{
+ struct tegra_pwm_priv *priv = dev_get_priv(dev);
+ struct pwm_ctlr *regs = priv->regs;
+ uint pulse_width;
+ u32 reg;
+
+ if (channel >= 4)
+ return -EINVAL;
+ debug("%s: Configure '%s' channel %u\n", __func__, dev->name, channel);
+ /* We ignore the period here and just use 32KHz */
+ clock_start_periph_pll(PERIPH_ID_PWM, CLOCK_ID_SFROM32KHZ, 32768);
+
+ pulse_width = duty_ns * 255 / period_ns;
+
+ reg = pulse_width << PWM_WIDTH_SHIFT;
+ reg |= 1 << PWM_DIVIDER_SHIFT;
+ writel(reg, &regs[channel].control);
+ debug("%s: pulse_width=%u\n", __func__, pulse_width);
+
+ return 0;
+}
+
+static int tegra_pwm_set_enable(struct udevice *dev, uint channel, bool enable)
+{
+ struct tegra_pwm_priv *priv = dev_get_priv(dev);
+ struct pwm_ctlr *regs = priv->regs;
+
+ if (channel >= 4)
+ return -EINVAL;
+ debug("%s: Enable '%s' channel %u\n", __func__, dev->name, channel);
+ clrsetbits_le32(&regs[channel].control, PWM_ENABLE_MASK,
+ enable ? PWM_ENABLE_MASK : 0);
+
+ return 0;
+}
+
+static int tegra_pwm_of_to_plat(struct udevice *dev)
+{
+ struct tegra_pwm_priv *priv = dev_get_priv(dev);
+
+ priv->regs = (struct pwm_ctlr *)dev_read_addr(dev);
+
+ return 0;
+}
+
+static const struct pwm_ops tegra_pwm_ops = {
+ .set_config = tegra_pwm_set_config,
+ .set_enable = tegra_pwm_set_enable,
+};
+
+static const struct udevice_id tegra_pwm_ids[] = {
+ { .compatible = "nvidia,tegra124-pwm" },
+ { .compatible = "nvidia,tegra20-pwm" },
+ { }
+};
+
+U_BOOT_DRIVER(tegra_pwm) = {
+ .name = "tegra_pwm",
+ .id = UCLASS_PWM,
+ .of_match = tegra_pwm_ids,
+ .ops = &tegra_pwm_ops,
+ .of_to_plat = tegra_pwm_of_to_plat,
+ .priv_auto = sizeof(struct tegra_pwm_priv),
+};