On 8/30/21 6:03 PM, Sean Anderson wrote: > > > On 8/30/21 5:38 AM, Michal Simek wrote: >> TTC has three modes of operations. Timer, PWM and input counters. >> >> There is already driver for timer under CADENCE_TTC_TIMER which is >> used for >> ZynqMP R5 configuration. >> This driver is targeting PWM which is for example configuration which can >> be used for fan control. >> The driver has been tested on Xilinx Kria SOM platform where fan is >> connected to one PL pin. When TTC output is connected via EMIO to PL pin >> TTC pwm can be configured and tested for example like this: >> pwm config 0 0 10000 1200 >> pwm enable 0 0 >> pwm config 0 0 10000 1400 >> pwm config 0 0 10000 1600 >> >> Signed-off-by: Michal Simek <[email protected]> >> --- >> >> Changes in v2: >> - Detect pwm-cells property for PWM driver >> - Fix all macro names >> - Use BIT and GENMASK macros >> - Introduce TTC_REG macro for reg offsets >> - Use FIELD_PREP >> - Move cadence_ttc_pwm_of_to_plat() below probe >> - Introduce struct cadence_ttc_pwm_plat >> - Read timer-width from DT >> - Use NSEC_PER_SEC macro >> - Use clock_ctrl variable instead of x - all reported by Sean >> >> MAINTAINERS | 1 + >> drivers/pwm/Kconfig | 7 + >> drivers/pwm/Makefile | 1 + >> drivers/pwm/pwm-cadence-ttc.c | 247 ++++++++++++++++++++++++++++++++++ >> 4 files changed, 256 insertions(+) >> create mode 100644 drivers/pwm/pwm-cadence-ttc.c >> >> diff --git a/MAINTAINERS b/MAINTAINERS >> index 4cf0c33c5d58..889813382249 100644 >> --- a/MAINTAINERS >> +++ b/MAINTAINERS >> @@ -600,6 +600,7 @@ F: drivers/mmc/zynq_sdhci.c >> F: drivers/mtd/nand/raw/zynq_nand.c >> F: drivers/net/phy/xilinx_phy.c >> F: drivers/net/zynq_gem.c >> +F: drivers/pwm/pwm-cadence-ttc.c >> F: drivers/serial/serial_zynq.c >> F: drivers/reset/reset-zynqmp.c >> F: drivers/rtc/zynqmp_rtc.c >> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig >> index cf7f4c6840ce..176e703c8fbb 100644 >> --- a/drivers/pwm/Kconfig >> +++ b/drivers/pwm/Kconfig >> @@ -9,6 +9,13 @@ config DM_PWM >> frequency/period can be controlled along with the proportion >> of that >> time that the signal is high. >> >> +config PWM_CADENCE_TTC >> + bool "Enable support for the Cadence TTC PWM" >> + depends on DM_PWM && !CADENCE_TTC_TIMER >> + help >> + Cadence TTC can be configured as timer which is done via >> + CONFIG_CADENCE_TTC_TIMER or as PWM. This is covering only PWM now. >> + >> config PWM_CROS_EC >> bool "Enable support for the Chrome OS EC PWM" >> depends on DM_PWM >> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile >> index 10d244bfb79d..abf5af41d2cc 100644 >> --- a/drivers/pwm/Makefile >> +++ b/drivers/pwm/Makefile >> @@ -10,6 +10,7 @@ >> >> obj-$(CONFIG_DM_PWM) += pwm-uclass.o >> >> +obj-$(CONFIG_PWM_CADENCE_TTC) += pwm-cadence-ttc.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 >> diff --git a/drivers/pwm/pwm-cadence-ttc.c >> b/drivers/pwm/pwm-cadence-ttc.c >> new file mode 100644 >> index 000000000000..99aaaa6a8ab6 >> --- /dev/null >> +++ b/drivers/pwm/pwm-cadence-ttc.c >> @@ -0,0 +1,247 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> +/* >> + * (C) Copyright 2021 Xilinx, Inc. Michal Simek >> + */ >> + >> +#include <clk.h> >> +#include <common.h> >> +#include <div64.h> >> +#include <dm.h> >> +#include <log.h> >> +#include <pwm.h> >> +#include <asm/io.h> >> +#include <log.h> >> +#include <div64.h> >> +#include <linux/bitfield.h> >> +#include <linux/math64.h> >> +#include <linux/log2.h> >> +#include <dm/device_compat.h> >> + >> +#define CLOCK_CONTROL 0 >> +#define COUNTER_CONTROL 0xc >> +#define INTERVAL_COUNTER 0x24 >> +#define MATCH_1_COUNTER 0x30 >> + >> +#define CLK_FALLING_EDGE BIT(6) >> +#define CLK_SRC_EXTERNAL BIT(5) >> +#define CLK_PRESCALE_MASK GENMASK(4, 1) >> +#define CLK_PRESCALE_ENABLE BIT(0) >> + >> +#define COUNTER_WAVE_POL BIT(6) >> +#define COUNTER_WAVE_DISABLE BIT(5) >> +#define COUNTER_RESET BIT(4) >> +#define COUNTER_MATCH_ENABLE BIT(3) >> +#define COUNTER_DECREMENT_ENABLE BIT(2) >> +#define COUNTER_INTERVAL_ENABLE BIT(1) >> +#define COUNTER_COUNTING_DISABLE BIT(0) >> + >> +#define NSEC_PER_SEC 1000000000L >> + >> +#define TTC_REG(reg, channel) ((reg) + (channel) * sizeof(u32)) >> +#define TTC_CLOCK_CONTROL(reg, channel) \ >> + TTC_REG((reg) + CLOCK_CONTROL, (channel)) >> +#define TTC_COUNTER_CONTROL(reg, channel) \ >> + TTC_REG((reg) + COUNTER_CONTROL, (channel)) >> +#define TTC_INTERVAL_COUNTER(reg, channel) \ >> + TTC_REG((reg) + INTERVAL_COUNTER, (channel)) >> +#define TTC_MATCH_1_COUNTER(reg, channel) \ >> + TTC_REG((reg) + MATCH_1_COUNTER, (channel)) >> + >> +struct cadence_ttc_pwm_plat { >> + u8 *regs; >> + u32 timer_mask; >> +}; >> + >> +struct cadence_ttc_pwm_priv { >> + u8 *regs; >> + u32 timer_mask; >> + unsigned long frequency; >> + bool invert[2]; >> +}; >> + >> +static int cadence_ttc_pwm_set_invert(struct udevice *dev, uint channel, >> + bool polarity) >> +{ >> + struct cadence_ttc_pwm_priv *priv = dev_get_priv(dev); >> + >> + if (channel > 2) { >> + dev_err(dev, "Unsupported channel number %d(max 2)\n", channel); >> + return -EINVAL; >> + } >> + >> + priv->invert[channel] = polarity; >> + >> + dev_dbg(dev, "polarity=%u. Please config PWM again\n", polarity); >> + >> + return 0; >> +} >> + >> +static int cadence_ttc_pwm_set_config(struct udevice *dev, uint channel, >> + uint period_ns, uint duty_ns) >> +{ >> + struct cadence_ttc_pwm_priv *priv = dev_get_priv(dev); >> + u32 counter_ctrl, clock_ctrl; >> + int period_clocks, duty_clocks, prescaler; >> + >> + dev_dbg(dev, "channel %d, duty %d/ period %d ns\n", channel, >> + duty_ns, period_ns); >> + >> + if (channel > 2) { >> + dev_err(dev, "Unsupported channel number %d(max 2)\n", channel); >> + return -EINVAL; >> + } >> + >> + /* Make sure counter is stopped */ >> + counter_ctrl = readl(TTC_COUNTER_CONTROL(priv->regs, channel)); >> + setbits_le32(TTC_COUNTER_CONTROL(priv->regs, channel), >> + COUNTER_COUNTING_DISABLE); > > Why disable it? it should be better to disable it then changing values with output enabled. > >> + >> + /* Calculate period, prescaler and set clock control register */ >> + period_clocks = div64_u64(((int64_t)period_ns * priv->frequency), >> + NSEC_PER_SEC); >> + >> + prescaler = ilog2(period_clocks) + 1 - 16; > > This 16 should be based off of priv->timer_mask. done in v2 > >> + if (prescaler < 0) >> + prescaler = 0; >> + >> + clock_ctrl = readl(TTC_CLOCK_CONTROL(priv->regs, channel)); >> + >> + if (!prescaler) { >> + clock_ctrl &= ~(CLK_PRESCALE_ENABLE | CLK_PRESCALE_MASK); >> + } else { >> + clock_ctrl &= ~CLK_PRESCALE_MASK; >> + clock_ctrl |= CLK_PRESCALE_ENABLE; >> + clock_ctrl |= FIELD_PREP(CLK_PRESCALE_MASK, prescaler - 1); >> + }; >> + >> + /* External source is not handled by this driver now */ >> + clock_ctrl &= ~CLK_SRC_EXTERNAL; >> + >> + writel(clock_ctrl, TTC_CLOCK_CONTROL(priv->regs, channel)); >> + >> + /* Calculate interval and set counter control value */ >> + duty_clocks = div64_u64(((int64_t)duty_ns * priv->frequency), >> + NSEC_PER_SEC); >> + >> + writel((period_clocks >> prescaler) & priv->timer_mask, >> + TTC_INTERVAL_COUNTER(priv->regs, channel)); >> + writel((duty_clocks >> prescaler) & priv->timer_mask, >> + TTC_MATCH_1_COUNTER(priv->regs, channel)); >> + >> + /* Restore/reset counter */ >> + counter_ctrl &= ~COUNTER_DECREMENT_ENABLE; >> + counter_ctrl |= COUNTER_INTERVAL_ENABLE | >> + COUNTER_RESET | >> + COUNTER_MATCH_ENABLE; >> + >> + if (priv->invert[channel]) >> + counter_ctrl |= COUNTER_WAVE_POL; >> + else >> + counter_ctrl &= ~COUNTER_WAVE_POL; >> + >> + writel(counter_ctrl, TTC_COUNTER_CONTROL(priv->regs, channel)); >> + >> + dev_dbg(dev, "%d/%d clocks, prescaler 2^%d\n", duty_clocks, >> + period_clocks, prescaler); >> + >> + return 0; >> +}; >> + >> +static int cadence_ttc_pwm_set_enable(struct udevice *dev, uint channel, >> + bool enable) >> +{ >> + struct cadence_ttc_pwm_priv *priv = dev_get_priv(dev); >> + >> + if (channel > 2) { >> + dev_err(dev, "Unsupported channel number %d(max 2)\n", channel); >> + return -EINVAL; >> + } >> + >> + dev_dbg(dev, "Enable: %d, channel %d\n", enable, channel); >> + >> + if (enable) { >> + clrbits_le32(TTC_COUNTER_CONTROL(priv->regs, channel), >> + COUNTER_COUNTING_DISABLE | >> + COUNTER_WAVE_DISABLE); >> + setbits_le32(TTC_COUNTER_CONTROL(priv->regs, channel), >> + COUNTER_RESET); >> + } else { >> + setbits_le32(TTC_COUNTER_CONTROL(priv->regs, channel), >> + COUNTER_COUNTING_DISABLE | >> + COUNTER_WAVE_DISABLE); >> + } >> + >> + return 0; >> +}; >> + >> +static int cadence_ttc_pwm_probe(struct udevice *dev) >> +{ >> + struct cadence_ttc_pwm_priv *priv = dev_get_priv(dev); >> + struct cadence_ttc_pwm_plat *plat = dev_get_plat(dev); >> + struct clk clk; >> + int ret; >> + >> + priv->regs = plat->regs; >> + priv->timer_mask = plat->timer_mask; >> + >> + ret = clk_get_by_index(dev, 0, &clk); >> + if (ret < 0) { >> + dev_err(dev, "failed to get clock\n"); >> + return ret; >> + } >> + >> + priv->frequency = clk_get_rate(&clk); >> + if (IS_ERR_VALUE(priv->frequency)) { >> + dev_err(dev, "failed to get rate\n"); >> + return priv->frequency; >> + } >> + dev_dbg(dev, "Clk frequency: %ld\n", priv->frequency); >> + >> + ret = clk_enable(&clk); >> + if (ret) { >> + dev_err(dev, "failed to enable clock\n"); >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static int cadence_ttc_pwm_of_to_plat(struct udevice *dev) >> +{ >> + struct cadence_ttc_pwm_plat *plat = dev_get_plat(dev); >> + const char *cells; >> + u32 timer_width; >> + >> + cells = dev_read_prop(dev, "#pwm-cells", NULL); >> + if (!cells) >> + return -EINVAL; > > Sorry, I was not precise enough with my feedback last time. This check > needs to happen in bind() (and return -ENODEV) so that lists_bind_fdt > tries another driver. I took a look at it and core doesn't support it without patching. When one driver returns -ENODEV core won't try to find another driver. But I have sent v3 series with small changes in the core to handle this case better. Also update cadence ttc timer driver to check pwm-cells and ignore it in that case. Even that's situation which is not a problem on arm64 which is not enabling CONFIG_TIMER. Thanks, Michal

