diff options
Diffstat (limited to 'roms/u-boot/drivers/pwm')
-rw-r--r-- | roms/u-boot/drivers/pwm/Kconfig | 93 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/Makefile | 23 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/cros_ec_pwm.c | 84 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/exynos_pwm.c | 118 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/pwm-imx-util.c | 80 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/pwm-imx-util.h | 15 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/pwm-imx.c | 163 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/pwm-meson.c | 531 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/pwm-mtk.c | 188 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/pwm-sifive.c | 174 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/pwm-ti-ehrpwm.c | 468 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/pwm-uclass.c | 45 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/rk_pwm.c | 220 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/sandbox_pwm.c | 120 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/sunxi_pwm.c | 186 | ||||
-rw-r--r-- | roms/u-boot/drivers/pwm/tegra_pwm.c | 83 |
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(®s->tcfg0); + prescaler = (channel < 2 ? val : (val >> 8)) & 0xff; + div = (readl(®s->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, ®s->tcntb0 + offset); + writel(tcmp, ®s->tcmpb0 + offset); + } + + tcon = readl(®s->tcon); + tcon |= TCON_UPDATE(channel); + if (channel < 4) + tcon |= TCON_AUTO_RELOAD(channel); + else + tcon |= TCON4_AUTO_RELOAD; + writel(tcon, ®s->tcon); + + tcon &= ~TCON_UPDATE(channel); + writel(tcon, ®s->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(®s->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, ®s->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(®s->cr, PWMCR_EN); + else + clrbits_le32(®s->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(®s->ctrl); + v &= ~SUNXI_PWM_CTRL_CLK_GATE; + writel(v, ®s->ctrl); + v &= ~SUNXI_PWM_CTRL_PRESCALE0_MASK; + v |= (best_prescaler & SUNXI_PWM_CTRL_PRESCALE0_MASK); + writel(v, ®s->ctrl); + v |= SUNXI_PWM_CTRL_CLK_GATE; + writel(v, ®s->ctrl); + priv->prescaler = best_prescaler; + } + + writel(SUNXI_PWM_CH0_PERIOD_PRD(best_period) | + SUNXI_PWM_CH0_PERIOD_DUTY(duty), ®s->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(®s->ctrl); + if (!enable) { + v &= ~SUNXI_PWM_CTRL_ENABLE0; + writel(v, ®s->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, ®s->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, ®s[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(®s[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), +}; |