2021-05-28 21:46:27

by Sean Anderson

[permalink] [raw]
Subject: [PATCH v4 1/3] dt-bindings: pwm: Add Xilinx AXI Timer

This adds a binding for the Xilinx LogiCORE IP AXI Timer. This device is
a "soft" block, so it has many parameters which would not be
configurable in most hardware. This binding is usually automatically
generated by Xilinx's tools, so the names and values of some properties
must be kept as they are. Replacement properties have been provided for
new device trees.

Because we need to init timer devices so early in boot, the easiest way
to configure things is to use a device tree property. For the moment
this is 'xlnx,pwm', but this could be extended/renamed/etc. in the
future if these is a need for a generic property.

Signed-off-by: Sean Anderson <[email protected]>
---

Changes in v4:
- Remove references to generate polarity so this can get merged
- Predicate PWM driver on the presence of #pwm-cells
- Make some properties optional for clocksource drivers

Changes in v3:
- Mark all boolean-as-int properties as deprecated
- Add xlnx,pwm and xlnx,gen?-active-low properties.
- Make newer replacement properties mutually-exclusive with what they
replace
- Add an example with non-deprecated properties only.

Changes in v2:
- Use 32-bit addresses for example binding

.../bindings/pwm/xlnx,axi-timer.yaml | 85 +++++++++++++++++++
1 file changed, 85 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml

diff --git a/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
new file mode 100644
index 000000000000..48a280f96e63
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
@@ -0,0 +1,85 @@
+# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pwm/xlnx,axi-timer.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Xilinx LogiCORE IP AXI Timer Device Tree Binding
+
+maintainers:
+ - Sean Anderson <[email protected]>
+
+properties:
+ compatible:
+ oneOf:
+ - items:
+ - const: xlnx,axi-timer-2.0
+ - const: xlnx,xps-timer-1.00.a
+ - items:
+ - const: xlnx,xps-timer-1.00.a
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ const: s_axi_aclk
+
+ interrupts:
+ maxItems: 1
+
+ reg:
+ maxItems: 1
+
+ xlnx,count-width:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 8
+ maximum: 32
+ default: 32
+ description:
+ The width of the counter(s), in bits.
+
+ xlnx,one-timer-only:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ enum: [ 0, 1 ]
+ description:
+ Whether only one timer is present in this block.
+
+required:
+ - compatible
+ - reg
+ - xlnx,one-timer-only
+
+allOf:
+ - if:
+ required:
+ - '#pwm-cells'
+ then:
+ allOf:
+ - required:
+ - clocks
+ - properties:
+ xlnx,one-timer-only:
+ const: 0
+ else:
+ required:
+ - interrupts
+ - if:
+ required:
+ - clocks
+ then:
+ required:
+ - clock-names
+
+additionalProperties: true
+
+examples:
+ - |
+ axi_timer_0: timer@800e0000 {
+ #pwm-cells = <0>;
+ clock-names = "s_axi_aclk";
+ clocks = <&zynqmp_clk 71>;
+ compatible = "xlnx,axi-timer-2.0", "xlnx,xps-timer-1.00.a";
+ reg = <0x800e0000 0x10000>;
+ xlnx,count-width = <0x20>;
+ xlnx,one-timer-only = <0x0>;
+ };
--
2.25.1


2021-05-28 21:46:33

by Sean Anderson

[permalink] [raw]
Subject: [PATCH v4 2/3] clocksource: Rewrite Xilinx AXI timer driver

This rewrites the Xilinx AXI timer driver to be more platform agnostic.
Some common code has been split off so it can be reused. These routines
currently live in drivers/mfd. The largest changes have taken place in the
initialization:

- We now support any number of timer devices, possibly with only one
counter each. The first counter will be used as a clocksource. Every
other counter will be used as a clockevent.
- We do not use timer_of_init because we need to perform some tasks in
between different stages. For example, we must ensure that ->read and
->write are initialized before registering the irq. This can only happen
after we have gotten the register base (to detect endianness). We also
have a rather unusual clock initialization sequence in order to remain
backwards compatible. Due to this, it's ok for the initial clock request
to fail, and we do not want other initialization to be undone. Lastly, it
is more convenient to do one allocation for xilinx_clockevent_device than
to do one for timer_of and one for xilinx_timer_priv.
- We now pay attention to xlnx,count-width and handle smaller width timers.
The default remains 32.

Signed-off-by: Sean Anderson <[email protected]>
---
This has been tested on microblaze qemu.

Changes in v4:
- Break out clock* drivers into their own file

arch/microblaze/kernel/Makefile | 3 +-
arch/microblaze/kernel/timer.c | 326 -----------------------------
drivers/clocksource/Kconfig | 11 +
drivers/clocksource/Makefile | 1 +
drivers/clocksource/timer-xilinx.c | 300 ++++++++++++++++++++++++++
drivers/mfd/Makefile | 4 +
drivers/mfd/xilinx-timer.c | 147 +++++++++++++
include/linux/mfd/xilinx-timer.h | 134 ++++++++++++
8 files changed, 598 insertions(+), 328 deletions(-)
delete mode 100644 arch/microblaze/kernel/timer.c
create mode 100644 drivers/clocksource/timer-xilinx.c
create mode 100644 drivers/mfd/xilinx-timer.c
create mode 100644 include/linux/mfd/xilinx-timer.h

diff --git a/arch/microblaze/kernel/Makefile b/arch/microblaze/kernel/Makefile
index 15a20eb814ce..3b6d725398f8 100644
--- a/arch/microblaze/kernel/Makefile
+++ b/arch/microblaze/kernel/Makefile
@@ -5,7 +5,6 @@

ifdef CONFIG_FUNCTION_TRACER
# Do not trace early boot code and low level code
-CFLAGS_REMOVE_timer.o = -pg
CFLAGS_REMOVE_intc.o = -pg
CFLAGS_REMOVE_early_printk.o = -pg
CFLAGS_REMOVE_ftrace.o = -pg
@@ -17,7 +16,7 @@ extra-y := head.o vmlinux.lds
obj-y += dma.o exceptions.o \
hw_exception_handler.o irq.o \
process.o prom.o ptrace.o \
- reset.o setup.o signal.o sys_microblaze.o timer.o traps.o unwind.o
+ reset.o setup.o signal.o sys_microblaze.o traps.o unwind.o

obj-y += cpu/

diff --git a/arch/microblaze/kernel/timer.c b/arch/microblaze/kernel/timer.c
deleted file mode 100644
index f8832cf49384..000000000000
--- a/arch/microblaze/kernel/timer.c
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2007-2013 Michal Simek <[email protected]>
- * Copyright (C) 2012-2013 Xilinx, Inc.
- * Copyright (C) 2007-2009 PetaLogix
- * Copyright (C) 2006 Atmark Techno, Inc.
- *
- * This file is subject to the terms and conditions of the GNU General Public
- * License. See the file "COPYING" in the main directory of this archive
- * for more details.
- */
-
-#include <linux/interrupt.h>
-#include <linux/delay.h>
-#include <linux/sched.h>
-#include <linux/sched/clock.h>
-#include <linux/sched_clock.h>
-#include <linux/clk.h>
-#include <linux/clockchips.h>
-#include <linux/of_address.h>
-#include <linux/of_irq.h>
-#include <linux/timecounter.h>
-#include <asm/cpuinfo.h>
-
-static void __iomem *timer_baseaddr;
-
-static unsigned int freq_div_hz;
-static unsigned int timer_clock_freq;
-
-#define TCSR0 (0x00)
-#define TLR0 (0x04)
-#define TCR0 (0x08)
-#define TCSR1 (0x10)
-#define TLR1 (0x14)
-#define TCR1 (0x18)
-
-#define TCSR_MDT (1<<0)
-#define TCSR_UDT (1<<1)
-#define TCSR_GENT (1<<2)
-#define TCSR_CAPT (1<<3)
-#define TCSR_ARHT (1<<4)
-#define TCSR_LOAD (1<<5)
-#define TCSR_ENIT (1<<6)
-#define TCSR_ENT (1<<7)
-#define TCSR_TINT (1<<8)
-#define TCSR_PWMA (1<<9)
-#define TCSR_ENALL (1<<10)
-
-static unsigned int (*read_fn)(void __iomem *);
-static void (*write_fn)(u32, void __iomem *);
-
-static void timer_write32(u32 val, void __iomem *addr)
-{
- iowrite32(val, addr);
-}
-
-static unsigned int timer_read32(void __iomem *addr)
-{
- return ioread32(addr);
-}
-
-static void timer_write32_be(u32 val, void __iomem *addr)
-{
- iowrite32be(val, addr);
-}
-
-static unsigned int timer_read32_be(void __iomem *addr)
-{
- return ioread32be(addr);
-}
-
-static inline void xilinx_timer0_stop(void)
-{
- write_fn(read_fn(timer_baseaddr + TCSR0) & ~TCSR_ENT,
- timer_baseaddr + TCSR0);
-}
-
-static inline void xilinx_timer0_start_periodic(unsigned long load_val)
-{
- if (!load_val)
- load_val = 1;
- /* loading value to timer reg */
- write_fn(load_val, timer_baseaddr + TLR0);
-
- /* load the initial value */
- write_fn(TCSR_LOAD, timer_baseaddr + TCSR0);
-
- /* see timer data sheet for detail
- * !ENALL - don't enable 'em all
- * !PWMA - disable pwm
- * TINT - clear interrupt status
- * ENT- enable timer itself
- * ENIT - enable interrupt
- * !LOAD - clear the bit to let go
- * ARHT - auto reload
- * !CAPT - no external trigger
- * !GENT - no external signal
- * UDT - set the timer as down counter
- * !MDT0 - generate mode
- */
- write_fn(TCSR_TINT|TCSR_ENIT|TCSR_ENT|TCSR_ARHT|TCSR_UDT,
- timer_baseaddr + TCSR0);
-}
-
-static inline void xilinx_timer0_start_oneshot(unsigned long load_val)
-{
- if (!load_val)
- load_val = 1;
- /* loading value to timer reg */
- write_fn(load_val, timer_baseaddr + TLR0);
-
- /* load the initial value */
- write_fn(TCSR_LOAD, timer_baseaddr + TCSR0);
-
- write_fn(TCSR_TINT|TCSR_ENIT|TCSR_ENT|TCSR_ARHT|TCSR_UDT,
- timer_baseaddr + TCSR0);
-}
-
-static int xilinx_timer_set_next_event(unsigned long delta,
- struct clock_event_device *dev)
-{
- pr_debug("%s: next event, delta %x\n", __func__, (u32)delta);
- xilinx_timer0_start_oneshot(delta);
- return 0;
-}
-
-static int xilinx_timer_shutdown(struct clock_event_device *evt)
-{
- pr_info("%s\n", __func__);
- xilinx_timer0_stop();
- return 0;
-}
-
-static int xilinx_timer_set_periodic(struct clock_event_device *evt)
-{
- pr_info("%s\n", __func__);
- xilinx_timer0_start_periodic(freq_div_hz);
- return 0;
-}
-
-static struct clock_event_device clockevent_xilinx_timer = {
- .name = "xilinx_clockevent",
- .features = CLOCK_EVT_FEAT_ONESHOT |
- CLOCK_EVT_FEAT_PERIODIC,
- .shift = 8,
- .rating = 300,
- .set_next_event = xilinx_timer_set_next_event,
- .set_state_shutdown = xilinx_timer_shutdown,
- .set_state_periodic = xilinx_timer_set_periodic,
-};
-
-static inline void timer_ack(void)
-{
- write_fn(read_fn(timer_baseaddr + TCSR0), timer_baseaddr + TCSR0);
-}
-
-static irqreturn_t timer_interrupt(int irq, void *dev_id)
-{
- struct clock_event_device *evt = &clockevent_xilinx_timer;
- timer_ack();
- evt->event_handler(evt);
- return IRQ_HANDLED;
-}
-
-static __init int xilinx_clockevent_init(void)
-{
- clockevent_xilinx_timer.mult =
- div_sc(timer_clock_freq, NSEC_PER_SEC,
- clockevent_xilinx_timer.shift);
- clockevent_xilinx_timer.max_delta_ns =
- clockevent_delta2ns((u32)~0, &clockevent_xilinx_timer);
- clockevent_xilinx_timer.max_delta_ticks = (u32)~0;
- clockevent_xilinx_timer.min_delta_ns =
- clockevent_delta2ns(1, &clockevent_xilinx_timer);
- clockevent_xilinx_timer.min_delta_ticks = 1;
- clockevent_xilinx_timer.cpumask = cpumask_of(0);
- clockevents_register_device(&clockevent_xilinx_timer);
-
- return 0;
-}
-
-static u64 xilinx_clock_read(void)
-{
- return read_fn(timer_baseaddr + TCR1);
-}
-
-static u64 xilinx_read(struct clocksource *cs)
-{
- /* reading actual value of timer 1 */
- return (u64)xilinx_clock_read();
-}
-
-static struct timecounter xilinx_tc = {
- .cc = NULL,
-};
-
-static u64 xilinx_cc_read(const struct cyclecounter *cc)
-{
- return xilinx_read(NULL);
-}
-
-static struct cyclecounter xilinx_cc = {
- .read = xilinx_cc_read,
- .mask = CLOCKSOURCE_MASK(32),
- .shift = 8,
-};
-
-static int __init init_xilinx_timecounter(void)
-{
- xilinx_cc.mult = div_sc(timer_clock_freq, NSEC_PER_SEC,
- xilinx_cc.shift);
-
- timecounter_init(&xilinx_tc, &xilinx_cc, sched_clock());
-
- return 0;
-}
-
-static struct clocksource clocksource_microblaze = {
- .name = "xilinx_clocksource",
- .rating = 300,
- .read = xilinx_read,
- .mask = CLOCKSOURCE_MASK(32),
- .flags = CLOCK_SOURCE_IS_CONTINUOUS,
-};
-
-static int __init xilinx_clocksource_init(void)
-{
- int ret;
-
- ret = clocksource_register_hz(&clocksource_microblaze,
- timer_clock_freq);
- if (ret) {
- pr_err("failed to register clocksource");
- return ret;
- }
-
- /* stop timer1 */
- write_fn(read_fn(timer_baseaddr + TCSR1) & ~TCSR_ENT,
- timer_baseaddr + TCSR1);
- /* start timer1 - up counting without interrupt */
- write_fn(TCSR_TINT|TCSR_ENT|TCSR_ARHT, timer_baseaddr + TCSR1);
-
- /* register timecounter - for ftrace support */
- return init_xilinx_timecounter();
-}
-
-static int __init xilinx_timer_init(struct device_node *timer)
-{
- struct clk *clk;
- static int initialized;
- u32 irq;
- u32 timer_num = 1;
- int ret;
-
- if (initialized)
- return -EINVAL;
-
- initialized = 1;
-
- timer_baseaddr = of_iomap(timer, 0);
- if (!timer_baseaddr) {
- pr_err("ERROR: invalid timer base address\n");
- return -ENXIO;
- }
-
- write_fn = timer_write32;
- read_fn = timer_read32;
-
- write_fn(TCSR_MDT, timer_baseaddr + TCSR0);
- if (!(read_fn(timer_baseaddr + TCSR0) & TCSR_MDT)) {
- write_fn = timer_write32_be;
- read_fn = timer_read32_be;
- }
-
- irq = irq_of_parse_and_map(timer, 0);
- if (irq <= 0) {
- pr_err("Failed to parse and map irq");
- return -EINVAL;
- }
-
- of_property_read_u32(timer, "xlnx,one-timer-only", &timer_num);
- if (timer_num) {
- pr_err("Please enable two timers in HW\n");
- return -EINVAL;
- }
-
- pr_info("%pOF: irq=%d\n", timer, irq);
-
- clk = of_clk_get(timer, 0);
- if (IS_ERR(clk)) {
- pr_err("ERROR: timer CCF input clock not found\n");
- /* If there is clock-frequency property than use it */
- of_property_read_u32(timer, "clock-frequency",
- &timer_clock_freq);
- } else {
- timer_clock_freq = clk_get_rate(clk);
- }
-
- if (!timer_clock_freq) {
- pr_err("ERROR: Using CPU clock frequency\n");
- timer_clock_freq = cpuinfo.cpu_clock_freq;
- }
-
- freq_div_hz = timer_clock_freq / HZ;
-
- ret = request_irq(irq, timer_interrupt, IRQF_TIMER, "timer",
- &clockevent_xilinx_timer);
- if (ret) {
- pr_err("Failed to setup IRQ");
- return ret;
- }
-
- ret = xilinx_clocksource_init();
- if (ret)
- return ret;
-
- ret = xilinx_clockevent_init();
- if (ret)
- return ret;
-
- sched_clock_register(xilinx_clock_read, 32, timer_clock_freq);
-
- return 0;
-}
-
-TIMER_OF_DECLARE(xilinx_timer, "xlnx,xps-timer-1.00.a",
- xilinx_timer_init);
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 39aa21d01e05..d2dcde65390d 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -693,4 +693,15 @@ config MICROCHIP_PIT64B
modes and high resolution. It is used as a clocksource
and a clockevent.

+config XILINX_TIMER
+ bool "Xilinx AXI Timer support"
+ depends on HAS_IOMEM && COMMON_CLK
+ default y if MICROBLAZE
+ help
+ Clocksource/clockevent driver for Xilinx LogiCORE IP AXI
+ timers. This timer is typically a soft core which may be
+ present in Xilinx FPGAs. This device may also be present in
+ Microblaze soft processors. If you don't have this IP in your
+ design, choose N.
+
endmenu
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index c17ee32a7151..d506eeceedf4 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -88,3 +88,4 @@ obj-$(CONFIG_CSKY_MP_TIMER) += timer-mp-csky.o
obj-$(CONFIG_GX6605S_TIMER) += timer-gx6605s.o
obj-$(CONFIG_HYPERV_TIMER) += hyperv_timer.o
obj-$(CONFIG_MICROCHIP_PIT64B) += timer-microchip-pit64b.o
+obj-$(CONFIG_XILINX_TIMER) += timer-xilinx.o
diff --git a/drivers/clocksource/timer-xilinx.c b/drivers/clocksource/timer-xilinx.c
new file mode 100644
index 000000000000..3554a0cd4d9c
--- /dev/null
+++ b/drivers/clocksource/timer-xilinx.c
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Sean Anderson <[email protected]>
+ *
+ * Hardware limitations:
+ * - When in cascade mode we cannot read the full 64-bit counter in one go
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clockchips.h>
+#include <linux/clocksource.h>
+#include <linux/log2.h>
+#include <linux/mfd/xilinx-timer.h>
+#include <linux/interrupt.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <linux/sched_clock.h>
+#if IS_ENABLED(CONFIG_MICROBLAZE)
+#include <asm/cpuinfo.h>
+#endif
+
+struct xilinx_clocksource_device {
+ struct clocksource cs;
+ struct xilinx_timer_priv priv;
+};
+
+static inline struct xilinx_timer_priv
+*xilinx_clocksource_to_priv(struct clocksource *cs)
+{
+ return &container_of(cs, struct xilinx_clocksource_device, cs)->priv;
+}
+
+static u64 xilinx_clocksource_read(struct clocksource *cs)
+{
+ return xilinx_timer_read(xilinx_clocksource_to_priv(cs), TCR0);
+}
+
+static struct xilinx_clocksource_device xilinx_clocksource = {
+ .cs = {
+ .name = "xilinx_clocksource",
+ .rating = 300,
+ .read = xilinx_clocksource_read,
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+ .owner = THIS_MODULE,
+ },
+};
+
+static u64 xilinx_sched_read(void)
+{
+ return xilinx_timer_read(&xilinx_clocksource.priv, TCSR0);
+}
+
+static int xilinx_clocksource_init(struct device_node *np)
+{
+ int ret;
+ struct xilinx_timer_priv *priv = &xilinx_clocksource.priv;
+
+ xilinx_timer_write(priv, 0, TLR0);
+ /* Load TLR and clear any interrupts */
+ xilinx_timer_write(priv, TCSR_LOAD | TCSR_TINT, TCSR0);
+ /* Start the timer counting up with auto-reload */
+ xilinx_timer_write(priv, TCSR_ARHT | TCSR_ENT, TCSR0);
+
+ xilinx_clocksource.cs.mask = priv->max;
+ ret = clocksource_register_hz(&xilinx_clocksource.cs,
+ clk_get_rate(priv->clk));
+ if (!ret)
+ sched_clock_register(xilinx_sched_read, ilog2(priv->max),
+ clk_get_rate(priv->clk));
+ else
+ pr_err("%pOF: err %d: could not register clocksource\n",
+ np, ret);
+ return ret;
+}
+
+struct xilinx_clockevent_device {
+ struct clock_event_device ce;
+ struct xilinx_timer_priv priv;
+};
+
+static inline struct xilinx_timer_priv
+*xilinx_clockevent_to_priv(struct clock_event_device *ce)
+{
+ return &container_of(ce, struct xilinx_clockevent_device, ce)->priv;
+}
+
+static irqreturn_t xilinx_timer_handler(int irq, void *p)
+{
+ struct xilinx_clockevent_device *dev = p;
+ u32 tcsr1 = xilinx_timer_read(&dev->priv, TCSR0);
+
+ if (!(tcsr1 & TCSR_TINT))
+ return IRQ_NONE;
+
+ xilinx_timer_write(&dev->priv, tcsr1 | TCSR_TINT, TCSR0);
+ dev->ce.event_handler(&dev->ce);
+ return IRQ_HANDLED;
+}
+
+static int xilinx_clockevent_next_event(unsigned long evt,
+ struct clock_event_device *ce)
+{
+ struct xilinx_timer_priv *priv = xilinx_clockevent_to_priv(ce);
+
+ xilinx_timer_write(priv, evt, TLR0);
+ xilinx_timer_write(priv, TCSR_LOAD, TCSR0);
+ xilinx_timer_write(priv, TCSR_ENIT | TCSR_ENT, TCSR0);
+ return 0;
+}
+
+static int xilinx_clockevent_state_periodic(struct clock_event_device *ce)
+{
+ int ret;
+ u32 tlr1;
+ struct xilinx_timer_priv *priv = xilinx_clockevent_to_priv(ce);
+
+ ret = xilinx_timer_tlr_cycles(priv, &tlr1, 0,
+ clk_get_rate(priv->clk) / HZ);
+ if (ret)
+ return ret;
+
+ xilinx_timer_write(priv, tlr1, TLR0);
+ xilinx_timer_write(priv, TCSR_LOAD, TCSR0);
+ xilinx_timer_write(priv, TCSR_ARHT | TCSR_ENIT | TCSR_ENT, TCSR0);
+ return 0;
+}
+
+static int xilinx_clockevent_shutdown(struct clock_event_device *ce)
+{
+ xilinx_timer_write(xilinx_clockevent_to_priv(ce), 0, TCSR0);
+ return 0;
+}
+
+static const struct clock_event_device xilinx_clockevent_base = {
+ .rating = 300,
+ .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
+ .set_next_event = xilinx_clockevent_next_event,
+ .set_state_periodic = xilinx_clockevent_state_periodic,
+ .set_state_shutdown = xilinx_clockevent_shutdown,
+ .cpumask = cpu_possible_mask,
+ .owner = THIS_MODULE,
+};
+
+static int xilinx_clockevent_init(struct device_node *np,
+ struct xilinx_clockevent_device *dev)
+{
+ char *buf;
+ char fmt[] = "%pOFn@%p";
+ size_t n;
+ int irq, ret;
+
+ irq = ret = of_irq_get(np, 0);
+ if (ret < 0) {
+ pr_err("%pOF: err %d: could not get irq\n", np, ret);
+ return ret;
+ }
+
+ ret = request_irq(irq, xilinx_timer_handler, IRQF_TIMER,
+ np->full_name, dev);
+ if (ret) {
+ pr_err("%pOF: err %d: could not request irq\n", np, ret);
+ return ret;
+ }
+
+ memcpy(&dev->ce, &xilinx_clockevent_base, sizeof(dev->ce));
+ n = snprintf(NULL, 0, fmt, np, dev->priv.regs) + 1;
+ buf = kzalloc(n, GFP_KERNEL);
+ if (!buf) {
+ free_irq(irq, dev);
+ return -ENOMEM;
+ }
+ snprintf(buf, n, fmt, np, dev->priv.regs);
+ dev->ce.name = buf;
+
+ clockevents_config_and_register(&dev->ce, clk_get_rate(dev->priv.clk), 2,
+ min_t(u64, dev->priv.max + 2, ULONG_MAX));
+ return 0;
+}
+
+static bool clocksource_uninitialized = true;
+
+static int __init xilinx_timer_init(struct device_node *np)
+{
+ bool artificial_clock = false;
+ int ret;
+ struct clk_hw *hw;
+ struct clk *clk;
+ struct xilinx_timer_priv *priv;
+ struct xilinx_clockevent_device *dev;
+ u32 one_timer;
+ void __iomem *regs;
+
+ if (of_property_read_bool(np, "#pwm-cells"))
+ return 0;
+
+ regs = of_iomap(np, 0);
+ if (IS_ERR(regs)) {
+ ret = PTR_ERR(regs);
+ pr_err("%pOF: err %d: failed to map regs\n", np, ret);
+ return ret;
+ }
+
+ if (clocksource_uninitialized) {
+ priv = &xilinx_clocksource.priv;
+ } else {
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev) {
+ ret = -ENOMEM;
+ goto err_regs;
+ }
+ priv = &dev->priv;
+ }
+ priv->regs = regs;
+
+ ret = xilinx_timer_common_init(np, priv, &one_timer);
+ if (ret)
+ goto err_regs;
+
+ priv->clk = of_clk_get_by_name(np, "s_axi_aclk");
+ if (IS_ERR(priv->clk)) {
+ u32 freq;
+
+ ret = PTR_ERR(clk);
+ if (ret == -EPROBE_DEFER)
+ goto err_regs;
+
+ pr_warn("%pOF: missing s_axi_aclk, falling back to clock-frequency\n",
+ np);
+ ret = of_property_read_u32(np, "clock-frequency", &freq);
+ if (ret) {
+#if IS_ENABLED(CONFIG_MICROBLAZE)
+ pr_warn("%pOF: missing clock-frequency, falling back to /cpus/timebase-frequency\n",
+ np);
+ freq = cpuinfo.cpu_clock_freq;
+#else
+ goto err_regs;
+#endif
+ }
+
+ hw = __clk_hw_register_fixed_rate(NULL, np, np->full_name, NULL,
+ NULL, NULL, 0, freq, 0, 0);
+ if (IS_ERR(hw)) {
+ ret = PTR_ERR(hw);
+ goto err_regs;
+ }
+ priv->clk = hw->clk;
+ artificial_clock = true;
+ }
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ pr_err("%pOF: err %d: clock enable failed\n", np, ret);
+ goto err_clk_init;
+ }
+ clk_rate_exclusive_get(priv->clk);
+
+ if (clocksource_uninitialized) {
+ ret = xilinx_clocksource_init(np);
+ if (ret)
+ goto err_clk_enable;
+ clocksource_uninitialized = false;
+ } else {
+ ret = xilinx_clockevent_init(np, dev);
+ if (ret)
+ goto err_clk_enable;
+ }
+ of_node_set_flag(np, OF_POPULATED);
+
+ if (!one_timer) {
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ /*
+ * We don't support removal, so don't bother enabling
+ * the clock twice.
+ */
+ memcpy(&dev->priv, priv, sizeof(dev->priv));
+ dev->priv.regs += TCSR1;
+ return xilinx_clockevent_init(np, dev);
+ }
+
+ return 0;
+
+err_clk_enable:
+ clk_rate_exclusive_put(priv->clk);
+ clk_disable_unprepare(priv->clk);
+err_clk_init:
+ if (artificial_clock)
+ clk_unregister_fixed_rate(priv->clk);
+ else
+ clk_put(priv->clk);
+err_regs:
+ iounmap(regs);
+ return ret;
+}
+
+TIMER_OF_DECLARE(xilinx_xps_timer, "xlnx,xps-timer-1.00.a", xilinx_timer_init);
+TIMER_OF_DECLARE(xilinx_axi_timer, "xlnx,axi-timer-2.0", xilinx_timer_init);
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 834f5463af28..f0f9fbdde7dc 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -268,3 +268,7 @@ obj-$(CONFIG_MFD_ACER_A500_EC) += acer-ec-a500.o
obj-$(CONFIG_SGI_MFD_IOC3) += ioc3.o
obj-$(CONFIG_MFD_SIMPLE_MFD_I2C) += simple-mfd-i2c.o
obj-$(CONFIG_MFD_INTEL_M10_BMC) += intel-m10-bmc.o
+
+ifneq ($(CONFIG_XILINX_TIMER),)
+obj-y += xilinx-timer.o
+endif
diff --git a/drivers/mfd/xilinx-timer.c b/drivers/mfd/xilinx-timer.c
new file mode 100644
index 000000000000..3d80a3ab6626
--- /dev/null
+++ b/drivers/mfd/xilinx-timer.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Sean Anderson <[email protected]>
+ *
+ * For Xilinx LogiCORE IP AXI Timer documentation, refer to DS764:
+ * https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v1_03_a/axi_timer_ds764.pdf
+ */
+
+#include <linux/clk.h>
+#include <linux/mfd/xilinx-timer.h>
+#include <linux/of.h>
+#include <asm/io.h>
+
+#define TCSR0 0x00
+#define TLR0 0x04
+#define TCR0 0x08
+#define TCSR1 0x10
+#define TLR1 0x14
+#define TCR1 0x18
+
+#define TCSR_MDT BIT(0)
+#define TCSR_UDT BIT(1)
+#define TCSR_GENT BIT(2)
+#define TCSR_CAPT BIT(3)
+#define TCSR_ARHT BIT(4)
+#define TCSR_LOAD BIT(5)
+#define TCSR_ENIT BIT(6)
+#define TCSR_ENT BIT(7)
+#define TCSR_TINT BIT(8)
+#define TCSR_PWMA BIT(9)
+#define TCSR_ENALL BIT(10)
+#define TCSR_CASC BIT(11)
+
+/* readl/writel wrappers to support BE systems */
+
+static u32 xilinx_ioread32be(const void __iomem *addr)
+{
+ return ioread32be(addr);
+}
+
+static void xilinx_iowrite32be(u32 value, void __iomem *addr)
+{
+ iowrite32be(value, addr);
+}
+
+static u32 xilinx_ioread32(const void __iomem *addr)
+{
+ return ioread32(addr);
+}
+
+static void xilinx_iowrite32(u32 value, void __iomem *addr)
+{
+ iowrite32(value, addr);
+}
+
+int xilinx_timer_tlr_cycles(struct xilinx_timer_priv *priv, u32 *tlr,
+ u32 tcsr, u64 cycles)
+{
+ if (cycles < 2 || cycles > priv->max + 2)
+ return -ERANGE;
+
+ if (tcsr & TCSR_UDT)
+ *tlr = cycles - 2;
+ else
+ *tlr = priv->max - cycles + 2;
+
+ return 0;
+}
+
+int xilinx_timer_tlr_period(struct xilinx_timer_priv *priv, u32 *tlr,
+ u32 tcsr, unsigned int period)
+{
+ u64 cycles = DIV_ROUND_DOWN_ULL((u64)period * clk_get_rate(priv->clk),
+ NSEC_PER_SEC);
+
+ return xilinx_timer_tlr_cycles(priv, tlr, tcsr, cycles);
+}
+
+unsigned int xilinx_timer_get_period(struct xilinx_timer_priv *priv,
+ u32 tlr, u32 tcsr)
+{
+ u64 cycles;
+
+ if (tcsr & TCSR_UDT)
+ cycles = tlr + 2;
+ else
+ cycles = priv->max - tlr + 2;
+
+ return DIV_ROUND_UP_ULL(cycles * NSEC_PER_SEC,
+ clk_get_rate(priv->clk));
+}
+
+int xilinx_timer_common_init(struct device_node *np,
+ struct xilinx_timer_priv *priv,
+ u32 *one_timer)
+{
+ int ret;
+ u32 tcsr0, width;
+
+
+ priv->read = xilinx_ioread32;
+ priv->write = xilinx_iowrite32;
+ /*
+ * If PWM mode is enabled, we should try not to disturb it. Use
+ * CAPT since if PWM mode is enabled then MDT will be set as
+ * well.
+ *
+ * First, clear CAPT and verify that it has been cleared
+ */
+ tcsr0 = xilinx_timer_read(priv, TCSR0);
+ xilinx_timer_write(priv, tcsr0 & ~(TCSR_CAPT & swab(TCSR_CAPT)), TCSR0);
+ tcsr0 = xilinx_timer_read(priv, TCSR0);
+ if (tcsr0 & (TCSR_CAPT | swab(TCSR_CAPT))) {
+ pr_err("%pOF: cannot determine endianness\n", np);
+ return -EOPNOTSUPP;
+ }
+
+ /* Then check to make sure our write sticks */
+ xilinx_timer_write(priv, tcsr0 | TCSR_CAPT, TCSR0);
+ if (!(xilinx_timer_read(priv, TCSR0) & TCSR_CAPT)) {
+ priv->read = xilinx_ioread32be;
+ priv->write = xilinx_iowrite32be;
+ }
+
+ ret = of_property_read_u32(np, "xlnx,one-timer-only", one_timer);
+ if (ret) {
+ pr_err("%pOF: err %d: xlnx,one-timer-only\n", np, ret);
+ return ret;
+ } else if (*one_timer && *one_timer != 1) {
+ pr_err("%pOF: xlnx,one-timer-only must be 0 or 1\n", np);
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32(np, "xlnx,count-width", &width);
+ if (ret == -EINVAL) {
+ width = 32;
+ } else if (ret) {
+ pr_err("%pOF: err %d: xlnx,count-width\n", np, ret);
+ return ret;
+ } else if (width < 8 || width > 32) {
+ pr_err("%pOF: invalid counter width\n", np);
+ return -EINVAL;
+ }
+ priv->max = BIT_ULL(width) - 1;
+
+ return 0;
+}
diff --git a/include/linux/mfd/xilinx-timer.h b/include/linux/mfd/xilinx-timer.h
new file mode 100644
index 000000000000..d61af119655f
--- /dev/null
+++ b/include/linux/mfd/xilinx-timer.h
@@ -0,0 +1,134 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2021 Sean Anderson <[email protected]>
+ */
+
+#ifndef MFD_XILINX_TIMER_H
+#define MFD_XILINX_TIMER_H
+
+#include <linux/compiler.h>
+
+#define TCSR0 0x00
+#define TLR0 0x04
+#define TCR0 0x08
+#define TCSR1 0x10
+#define TLR1 0x14
+#define TCR1 0x18
+
+#define TCSR_MDT BIT(0)
+#define TCSR_UDT BIT(1)
+#define TCSR_GENT BIT(2)
+#define TCSR_CAPT BIT(3)
+#define TCSR_ARHT BIT(4)
+#define TCSR_LOAD BIT(5)
+#define TCSR_ENIT BIT(6)
+#define TCSR_ENT BIT(7)
+#define TCSR_TINT BIT(8)
+#define TCSR_PWMA BIT(9)
+#define TCSR_ENALL BIT(10)
+#define TCSR_CASC BIT(11)
+
+struct clk;
+struct device_node;
+
+/**
+ * struct xilinx_timer_priv - Private data for Xilinx AXI timer drivers
+ * @regs: Base address of this device
+ * @clk: Parent clock
+ * @read: Function to read a register
+ * @write: Function to write a register
+ * @max: Maximum value of the counters
+ */
+struct xilinx_timer_priv {
+ void __iomem *regs;
+ struct clk *clk;
+ u32 (*read)(const void __iomem *addr);
+ void (*write)(u32 value, void __iomem *addr);
+ u64 max;
+};
+
+/**
+ * xilinx_timer_read() - Read a word from a Xilinx AXI timer
+ * @priv: The timer's private data
+ * @offset: The offset to read from
+ *
+ * Just like readl(), possibly with endianness correction
+ *
+ * Return: The word at @priv->regs + @offset
+ */
+static inline u32 xilinx_timer_read(struct xilinx_timer_priv *priv,
+ int offset)
+{
+ return priv->read(priv->regs + offset);
+}
+
+/**
+ * xilinx_timer_write() - Write a word to a Xilinx AXI timer
+ * @priv: The timer's private data
+ * @value: The value to write
+ * @offset: The offset to write it at
+ *
+ * Just like writel(), possibly with endianness correction
+ */
+static inline void xilinx_timer_write(struct xilinx_timer_priv *priv,
+ u32 value, int offset)
+{
+ priv->write(value, priv->regs + offset);
+}
+
+/**
+ * xilinx_timer_tlr_cycles() - Calculate the TLR for a period specified
+ * in clock cycles
+ * @priv: The timer's private data
+ * @tlr: A pointer for where to write the calculated TLR value
+ * @tcsr: The value of the TCSR register for this counter
+ * @cycles: The number of cycles in this period
+ *
+ * Return: 0, or -%ERANGE if TLR cannot specify a period of @cycles
+ */
+int xilinx_timer_tlr_cycles(struct xilinx_timer_priv *priv, u32 *tlr,
+ u32 tcsr, u64 cycles);
+
+/**
+ * xilinx_timer_tlr_period() - Calculate the TLR for a given period
+ * specified in a duration
+ * @priv: The timer's private data
+ * @tlr: A pointer for where to write the calculated TLR value
+ * @tcsr: The value of the TCSR register for this counter
+ * @period: The duration of the period, in ns
+ *
+ * Return: 0, or -%ERANGE if TLR cannot specify a period of @period
+ */
+int xilinx_timer_tlr_period(struct xilinx_timer_priv *priv, u32 *tlr,
+ u32 tcsr, unsigned int period);
+
+/**
+ * xilinx_timer_get_period() - Get the current period of a counter
+ * @priv: The timer's private data
+ * @tlr: The value of TLR for this counter
+ * @tcsr: The value of TCSR for this counter
+ *
+ * Return: The period, in ns
+ */
+unsigned int xilinx_timer_get_period(struct xilinx_timer_priv *priv,
+ u32 tlr, u32 tcsr);
+
+/**
+ * xilinx_timer_common_init() - Perform common initialization for Xilinx
+ * AXI timer drivers.
+ * @priv: The timer's private data
+ * @np: The devicetree node for the timer
+ * @one_timer: Set to %1 if there is only one timer
+ *
+ * This performs common initialization, such as detecting endianness,
+ * and parsing devicetree properties. @priv->regs must be initialized
+ * before calling this function. This function initializes @priv->read,
+ * @priv->write, and @priv->width.
+ *
+ * Return: 0, or negative errno
+ */
+int xilinx_timer_common_init(struct device_node *np,
+ struct xilinx_timer_priv *priv,
+ u32 *one_timer);
+
+#endif /* MFD_XILINX_TIMER_H */
--
2.25.1

2021-05-28 21:46:40

by Sean Anderson

[permalink] [raw]
Subject: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

This adds PWM support for Xilinx LogiCORE IP AXI soft timers commonly
found on Xilinx FPGAs. At the moment clock control is very basic: we
just enable the clock during probe and pin the frequency. In the future,
someone could add support for disabling the clock when not in use.

This driver was written with reference to Xilinx DS764 for v1.03.a [1].

[1] https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v1_03_a/axi_timer_ds764.pdf

Signed-off-by: Sean Anderson <[email protected]>
---

Changes in v4:
- Remove references to properties which are not good enough for Linux.
- Don't use volatile in read/write replacements. Some arches have it and
some don't.
- Put common timer properties into their own struct to better reuse
code.

Changes in v3:
- Add clockevent and clocksource support
- Rewrite probe to only use a device_node, since timers may need to be
initialized before we have proper devices. This does bloat the code a bit
since we can no longer rely on helpers such as dev_err_probe. We also
cannot rely on device resources being free'd on failure, so we must free
them manually.
- We now access registers through xilinx_timer_(read|write). This allows us
to deal with endianness issues, as originally seen in the microblaze
driver. CAVEAT EMPTOR: I have not tested this on big-endian!
- Remove old microblaze driver

Changes in v2:
- Don't compile this module by default for arm64
- Add dependencies on COMMON_CLK and HAS_IOMEM
- Add comment explaining why we depend on !MICROBLAZE
- Add comment describing device
- Rename TCSR_(SET|CLEAR) to TCSR_RUN_(SET|CLEAR)
- Use NSEC_TO_SEC instead of defining our own
- Use TCSR_RUN_MASK to check if the PWM is enabled, as suggested by Uwe
- Cast dividends to u64 to avoid overflow
- Check for over- and underflow when calculating TLR
- Set xilinx_pwm_ops.owner
- Don't set pwmchip.base to -1
- Check range of xlnx,count-width
- Ensure the clock is always running when the pwm is registered
- Remove debugfs file :l
- Report errors with dev_error_probe

drivers/mfd/Makefile | 2 +-
drivers/pwm/Kconfig | 12 +++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-xilinx.c | 219 +++++++++++++++++++++++++++++++++++++++
4 files changed, 233 insertions(+), 1 deletion(-)
create mode 100644 drivers/pwm/pwm-xilinx.c

diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index f0f9fbdde7dc..89769affe251 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -269,6 +269,6 @@ obj-$(CONFIG_SGI_MFD_IOC3) += ioc3.o
obj-$(CONFIG_MFD_SIMPLE_MFD_I2C) += simple-mfd-i2c.o
obj-$(CONFIG_MFD_INTEL_M10_BMC) += intel-m10-bmc.o

-ifneq ($(CONFIG_XILINX_TIMER),)
+ifneq ($(CONFIG_PWM_XILINX)$(CONFIG_XILINX_TIMER),)
obj-y += xilinx-timer.o
endif
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 8ae68d6203fb..ebf8d9014758 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -620,4 +620,16 @@ config PWM_VT8500
To compile this driver as a module, choose M here: the module
will be called pwm-vt8500.

+config PWM_XILINX
+ tristate "Xilinx AXI Timer PWM support"
+ depends on HAS_IOMEM && COMMON_CLK
+ help
+ PWM driver for Xilinx LogiCORE IP AXI timers. This timer is
+ typically a soft core which may be present in Xilinx FPGAs.
+ This device may also be present in Microblaze soft processors.
+ If you don't have this IP in your design, choose N.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-xilinx.
+
endif
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index d43b1e17e8e1..655df169b895 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -58,3 +58,4 @@ obj-$(CONFIG_PWM_TWL) += pwm-twl.o
obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o
obj-$(CONFIG_PWM_VISCONTI) += pwm-visconti.o
obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o
+obj-$(CONFIG_PWM_XILINX) += pwm-xilinx.o
diff --git a/drivers/pwm/pwm-xilinx.c b/drivers/pwm/pwm-xilinx.c
new file mode 100644
index 000000000000..f05321496717
--- /dev/null
+++ b/drivers/pwm/pwm-xilinx.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Sean Anderson <[email protected]>
+ *
+ * Hardware limitations:
+ * - When changing both duty cycle and period, we may end up with one cycle
+ * with the old duty cycle and the new period.
+ * - Cannot produce 100% duty cycle.
+ * - Only produces "normal" output.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mfd/xilinx-timer.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+/*
+ * The idea here is to capture whether the PWM is actually running (e.g.
+ * because we or the bootloader set it up) and we need to be careful to ensure
+ * we don't cause a glitch. According to the data sheet, to enable the PWM we
+ * need to
+ *
+ * - Set both timers to generate mode (MDT=1)
+ * - Set both timers to PWM mode (PWMA=1)
+ * - Enable the generate out signals (GENT=1)
+ *
+ * In addition,
+ *
+ * - The timer must be running (ENT=1)
+ * - The timer must auto-reload TLR into TCR (ARHT=1)
+ * - We must not be in the process of loading TLR into TCR (LOAD=0)
+ * - Cascade mode must be disabled (CASC=0)
+ *
+ * If any of these differ from usual, then the PWM is either disabled, or is
+ * running in a mode that this driver does not support.
+ */
+#define TCSR_PWM_SET (TCSR_GENT | TCSR_ARHT | TCSR_ENT | TCSR_PWMA)
+#define TCSR_PWM_CLEAR (TCSR_MDT | TCSR_LOAD)
+#define TCSR_PWM_MASK (TCSR_PWM_SET | TCSR_PWM_CLEAR)
+
+struct xilinx_pwm_device {
+ struct pwm_chip chip;
+ struct xilinx_timer_priv priv;
+};
+
+static inline struct xilinx_timer_priv
+*xilinx_pwm_chip_to_priv(struct pwm_chip *chip)
+{
+ return &container_of(chip, struct xilinx_pwm_device, chip)->priv;
+}
+
+static bool xilinx_timer_pwm_enabled(u32 tcsr0, u32 tcsr1)
+{
+ return ((TCSR_PWM_MASK | TCSR_CASC) & tcsr0) == TCSR_PWM_SET &&
+ (TCSR_PWM_MASK & tcsr1) == TCSR_PWM_SET;
+}
+
+static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused,
+ const struct pwm_state *state)
+{
+ int ret;
+ struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
+ u32 tlr0, tlr1;
+ u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
+ u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
+ bool enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
+ if (ret)
+ return ret;
+
+ ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
+ if (ret)
+ return ret;
+
+ xilinx_timer_write(priv, tlr0, TLR0);
+ xilinx_timer_write(priv, tlr1, TLR1);
+
+ if (state->enabled) {
+ /* Only touch the TCSRs if we aren't already running */
+ if (!enabled) {
+ /* Load TLR into TCR */
+ xilinx_timer_write(priv, tcsr0 | TCSR_LOAD, TCSR0);
+ xilinx_timer_write(priv, tcsr1 | TCSR_LOAD, TCSR1);
+ /* Enable timers all at once with ENALL */
+ tcsr0 = (TCSR_PWM_SET & ~TCSR_ENT) | (tcsr0 & TCSR_UDT);
+ tcsr1 = TCSR_PWM_SET | TCSR_ENALL | (tcsr1 & TCSR_UDT);
+ xilinx_timer_write(priv, tcsr0, TCSR0);
+ xilinx_timer_write(priv, tcsr1, TCSR1);
+ }
+ } else {
+ xilinx_timer_write(priv, 0, TCSR0);
+ xilinx_timer_write(priv, 0, TCSR1);
+ }
+
+ return 0;
+}
+
+static void xilinx_pwm_get_state(struct pwm_chip *chip,
+ struct pwm_device *unused,
+ struct pwm_state *state)
+{
+ struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
+ u32 tlr0 = xilinx_timer_read(priv, TLR0);
+ u32 tlr1 = xilinx_timer_read(priv, TLR1);
+ u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
+ u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
+
+ state->period = xilinx_timer_get_period(priv, tlr0, tcsr0);
+ state->duty_cycle = xilinx_timer_get_period(priv, tlr1, tcsr1);
+ state->enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
+ state->polarity = PWM_POLARITY_NORMAL;
+}
+
+static const struct pwm_ops xilinx_pwm_ops = {
+ .apply = xilinx_pwm_apply,
+ .get_state = xilinx_pwm_get_state,
+ .owner = THIS_MODULE,
+};
+
+static int xilinx_timer_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct xilinx_timer_priv *priv;
+ struct xilinx_pwm_device *pwm;
+ u32 pwm_cells, one_timer;
+
+ ret = of_property_read_u32(np, "#pwm-cells", &pwm_cells);
+ if (ret == -EINVAL)
+ return -ENODEV;
+ else if (ret)
+ return dev_err_probe(dev, ret, "#pwm-cells\n");
+ else if (pwm_cells)
+ return dev_err_probe(dev, -EINVAL, "#pwm-cells must be 0\n");
+
+ pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
+ if (!pwm)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, pwm);
+ priv = &pwm->priv;
+
+ priv->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->regs))
+ return PTR_ERR(priv->regs);
+
+ ret = xilinx_timer_common_init(np, priv, &one_timer);
+ if (ret)
+ return ret;
+
+ if (one_timer)
+ return dev_err_probe(dev, -EINVAL,
+ "two timers required for PWM mode\n");
+
+ /*
+ * The polarity of the generate outputs must be active high for PWM
+ * mode to work. We could determine this from the device tree, but
+ * alas, such properties are not allowed to be used.
+ */
+
+ priv->clk = devm_clk_get(dev, "s_axi_aclk");
+ if (IS_ERR(priv->clk))
+ return dev_err_probe(dev, PTR_ERR(priv->clk), "clock\n");
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret)
+ return dev_err_probe(dev, ret, "clock enable failed\n");
+ clk_rate_exclusive_get(priv->clk);
+
+ pwm->chip.dev = dev;
+ pwm->chip.ops = &xilinx_pwm_ops;
+ pwm->chip.npwm = 1;
+ ret = pwmchip_add(&pwm->chip);
+ if (ret) {
+ clk_rate_exclusive_put(priv->clk);
+ clk_disable_unprepare(priv->clk);
+ return dev_err_probe(dev, ret, "could not register pwm chip\n");
+ }
+
+ return 0;
+}
+
+static int xilinx_timer_remove(struct platform_device *pdev)
+{
+ struct xilinx_pwm_device *pwm = platform_get_drvdata(pdev);
+
+ pwmchip_remove(&pwm->chip);
+ clk_rate_exclusive_put(pwm->priv.clk);
+ clk_disable_unprepare(pwm->priv.clk);
+ return 0;
+}
+
+static const struct of_device_id xilinx_timer_of_match[] = {
+ { .compatible = "xlnx,xps-timer-1.00.a", },
+ { .compatible = "xlnx,axi-timer-2.0" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, xilinx_timer_of_match);
+
+static struct platform_driver xilinx_timer_driver = {
+ .probe = xilinx_timer_probe,
+ .remove = xilinx_timer_remove,
+ .driver = {
+ .name = "xilinx-timer",
+ .of_match_table = of_match_ptr(xilinx_timer_of_match),
+ },
+};
+module_platform_driver(xilinx_timer_driver);
+
+MODULE_ALIAS("platform:xilinx-timer");
+MODULE_DESCRIPTION("Xilinx LogiCORE IP AXI Timer driver");
+MODULE_LICENSE("GPL v2");
--
2.25.1

2021-06-01 08:48:44

by Lee Jones

[permalink] [raw]
Subject: Re: [PATCH v4 2/3] clocksource: Rewrite Xilinx AXI timer driver

On Fri, 28 May 2021, Sean Anderson wrote:

> This rewrites the Xilinx AXI timer driver to be more platform agnostic.
> Some common code has been split off so it can be reused. These routines
> currently live in drivers/mfd. The largest changes have taken place in the
> initialization:
>
> - We now support any number of timer devices, possibly with only one
> counter each. The first counter will be used as a clocksource. Every
> other counter will be used as a clockevent.
> - We do not use timer_of_init because we need to perform some tasks in
> between different stages. For example, we must ensure that ->read and
> ->write are initialized before registering the irq. This can only happen
> after we have gotten the register base (to detect endianness). We also
> have a rather unusual clock initialization sequence in order to remain
> backwards compatible. Due to this, it's ok for the initial clock request
> to fail, and we do not want other initialization to be undone. Lastly, it
> is more convenient to do one allocation for xilinx_clockevent_device than
> to do one for timer_of and one for xilinx_timer_priv.
> - We now pay attention to xlnx,count-width and handle smaller width timers.
> The default remains 32.
>
> Signed-off-by: Sean Anderson <[email protected]>
> ---
> This has been tested on microblaze qemu.
>
> Changes in v4:
> - Break out clock* drivers into their own file
>
> arch/microblaze/kernel/Makefile | 3 +-
> arch/microblaze/kernel/timer.c | 326 -----------------------------
> drivers/clocksource/Kconfig | 11 +
> drivers/clocksource/Makefile | 1 +
> drivers/clocksource/timer-xilinx.c | 300 ++++++++++++++++++++++++++
> drivers/mfd/Makefile | 4 +
> drivers/mfd/xilinx-timer.c | 147 +++++++++++++

I'm confused!

> include/linux/mfd/xilinx-timer.h | 134 ++++++++++++
> 8 files changed, 598 insertions(+), 328 deletions(-)
> delete mode 100644 arch/microblaze/kernel/timer.c
> create mode 100644 drivers/clocksource/timer-xilinx.c
> create mode 100644 drivers/mfd/xilinx-timer.c
> create mode 100644 include/linux/mfd/xilinx-timer.h

[...]

> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2021 Sean Anderson <[email protected]>
> + *
> + * For Xilinx LogiCORE IP AXI Timer documentation, refer to DS764:
> + * https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v1_03_a/axi_timer_ds764.pdf
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/mfd/xilinx-timer.h>
> +#include <linux/of.h>
> +#include <asm/io.h>

RED FLAG: You are not using the MFD API here.

> +#define TCSR0 0x00
> +#define TLR0 0x04
> +#define TCR0 0x08
> +#define TCSR1 0x10
> +#define TLR1 0x14
> +#define TCR1 0x18
> +
> +#define TCSR_MDT BIT(0)
> +#define TCSR_UDT BIT(1)
> +#define TCSR_GENT BIT(2)
> +#define TCSR_CAPT BIT(3)
> +#define TCSR_ARHT BIT(4)
> +#define TCSR_LOAD BIT(5)
> +#define TCSR_ENIT BIT(6)
> +#define TCSR_ENT BIT(7)
> +#define TCSR_TINT BIT(8)
> +#define TCSR_PWMA BIT(9)
> +#define TCSR_ENALL BIT(10)
> +#define TCSR_CASC BIT(11)
> +
> +/* readl/writel wrappers to support BE systems */
> +
> +static u32 xilinx_ioread32be(const void __iomem *addr)
> +{
> + return ioread32be(addr);
> +}
> +
> +static void xilinx_iowrite32be(u32 value, void __iomem *addr)
> +{
> + iowrite32be(value, addr);
> +}
> +
> +static u32 xilinx_ioread32(const void __iomem *addr)
> +{
> + return ioread32(addr);
> +}
> +
> +static void xilinx_iowrite32(u32 value, void __iomem *addr)
> +{
> + iowrite32(value, addr);
> +}

Abstraction for the sake of abstraction, is not allowed.

Just use the io*() calls directly in-place.

> +int xilinx_timer_tlr_cycles(struct xilinx_timer_priv *priv, u32 *tlr,
> + u32 tcsr, u64 cycles)
> +{
> + if (cycles < 2 || cycles > priv->max + 2)
> + return -ERANGE;
> +
> + if (tcsr & TCSR_UDT)
> + *tlr = cycles - 2;
> + else
> + *tlr = priv->max - cycles + 2;
> +
> + return 0;
> +}
> +
> +int xilinx_timer_tlr_period(struct xilinx_timer_priv *priv, u32 *tlr,
> + u32 tcsr, unsigned int period)
> +{
> + u64 cycles = DIV_ROUND_DOWN_ULL((u64)period * clk_get_rate(priv->clk),
> + NSEC_PER_SEC);
> +
> + return xilinx_timer_tlr_cycles(priv, tlr, tcsr, cycles);
> +}
> +
> +unsigned int xilinx_timer_get_period(struct xilinx_timer_priv *priv,
> + u32 tlr, u32 tcsr)
> +{
> + u64 cycles;
> +
> + if (tcsr & TCSR_UDT)
> + cycles = tlr + 2;
> + else
> + cycles = priv->max - tlr + 2;
> +
> + return DIV_ROUND_UP_ULL(cycles * NSEC_PER_SEC,
> + clk_get_rate(priv->clk));
> +}
> +
> +int xilinx_timer_common_init(struct device_node *np,
> + struct xilinx_timer_priv *priv,
> + u32 *one_timer)
> +{
> + int ret;
> + u32 tcsr0, width;
> +
> +
> + priv->read = xilinx_ioread32;
> + priv->write = xilinx_iowrite32;
> + /*
> + * If PWM mode is enabled, we should try not to disturb it. Use
> + * CAPT since if PWM mode is enabled then MDT will be set as
> + * well.
> + *
> + * First, clear CAPT and verify that it has been cleared
> + */
> + tcsr0 = xilinx_timer_read(priv, TCSR0);
> + xilinx_timer_write(priv, tcsr0 & ~(TCSR_CAPT & swab(TCSR_CAPT)), TCSR0);
> + tcsr0 = xilinx_timer_read(priv, TCSR0);
> + if (tcsr0 & (TCSR_CAPT | swab(TCSR_CAPT))) {
> + pr_err("%pOF: cannot determine endianness\n", np);
> + return -EOPNOTSUPP;
> + }
> +
> + /* Then check to make sure our write sticks */
> + xilinx_timer_write(priv, tcsr0 | TCSR_CAPT, TCSR0);
> + if (!(xilinx_timer_read(priv, TCSR0) & TCSR_CAPT)) {
> + priv->read = xilinx_ioread32be;
> + priv->write = xilinx_iowrite32be;
> + }
> +
> + ret = of_property_read_u32(np, "xlnx,one-timer-only", one_timer);
> + if (ret) {
> + pr_err("%pOF: err %d: xlnx,one-timer-only\n", np, ret);
> + return ret;
> + } else if (*one_timer && *one_timer != 1) {
> + pr_err("%pOF: xlnx,one-timer-only must be 0 or 1\n", np);
> + return -EINVAL;
> + }
> +
> + ret = of_property_read_u32(np, "xlnx,count-width", &width);
> + if (ret == -EINVAL) {
> + width = 32;
> + } else if (ret) {
> + pr_err("%pOF: err %d: xlnx,count-width\n", np, ret);
> + return ret;
> + } else if (width < 8 || width > 32) {
> + pr_err("%pOF: invalid counter width\n", np);
> + return -EINVAL;
> + }
> + priv->max = BIT_ULL(width) - 1;
> +
> + return 0;
> +}

This is *all* timer stuff.

What is your rationale for dumping this into MFD?

--
Lee Jones [李琼斯]
Senior Technical Lead - Developer Services
Linaro.org │ Open source software for Arm SoCs
Follow Linaro: Facebook | Twitter | Blog

2021-06-01 13:33:59

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH v4 1/3] dt-bindings: pwm: Add Xilinx AXI Timer

On Fri, 28 May 2021 17:45:20 -0400, Sean Anderson wrote:
> This adds a binding for the Xilinx LogiCORE IP AXI Timer. This device is
> a "soft" block, so it has many parameters which would not be
> configurable in most hardware. This binding is usually automatically
> generated by Xilinx's tools, so the names and values of some properties
> must be kept as they are. Replacement properties have been provided for
> new device trees.
>
> Because we need to init timer devices so early in boot, the easiest way
> to configure things is to use a device tree property. For the moment
> this is 'xlnx,pwm', but this could be extended/renamed/etc. in the
> future if these is a need for a generic property.
>
> Signed-off-by: Sean Anderson <[email protected]>
> ---
>
> Changes in v4:
> - Remove references to generate polarity so this can get merged
> - Predicate PWM driver on the presence of #pwm-cells
> - Make some properties optional for clocksource drivers
>
> Changes in v3:
> - Mark all boolean-as-int properties as deprecated
> - Add xlnx,pwm and xlnx,gen?-active-low properties.
> - Make newer replacement properties mutually-exclusive with what they
> replace
> - Add an example with non-deprecated properties only.
>
> Changes in v2:
> - Use 32-bit addresses for example binding
>
> .../bindings/pwm/xlnx,axi-timer.yaml | 85 +++++++++++++++++++
> 1 file changed, 85 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>

My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
on your patch (DT_CHECKER_FLAGS is new in v5.13):

yamllint warnings/errors:
./Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml:16:10: [warning] wrong indentation: expected 10 but found 9 (indentation)
./Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml:19:10: [warning] wrong indentation: expected 10 but found 9 (indentation)

dtschema/dtc warnings/errors:

See https://patchwork.ozlabs.org/patch/1485318

This check can fail if there are any dependencies. The base for a patch
series is generally the most recent rc1.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit.

2021-06-01 14:25:23

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 2/3] clocksource: Rewrite Xilinx AXI timer driver



On 6/1/21 4:47 AM, Lee Jones wrote:
> On Fri, 28 May 2021, Sean Anderson wrote:
>
>> This rewrites the Xilinx AXI timer driver to be more platform agnostic.
>> Some common code has been split off so it can be reused. These routines
>> currently live in drivers/mfd. The largest changes have taken place in the
>> initialization:
>>
>> - We now support any number of timer devices, possibly with only one
>> counter each. The first counter will be used as a clocksource. Every
>> other counter will be used as a clockevent.
>> - We do not use timer_of_init because we need to perform some tasks in
>> between different stages. For example, we must ensure that ->read and
>> ->write are initialized before registering the irq. This can only happen
>> after we have gotten the register base (to detect endianness). We also
>> have a rather unusual clock initialization sequence in order to remain
>> backwards compatible. Due to this, it's ok for the initial clock request
>> to fail, and we do not want other initialization to be undone. Lastly, it
>> is more convenient to do one allocation for xilinx_clockevent_device than
>> to do one for timer_of and one for xilinx_timer_priv.
>> - We now pay attention to xlnx,count-width and handle smaller width timers.
>> The default remains 32.
>>
>> Signed-off-by: Sean Anderson <[email protected]>
>> ---
>> This has been tested on microblaze qemu.
>>
>> Changes in v4:
>> - Break out clock* drivers into their own file
>>
>> arch/microblaze/kernel/Makefile | 3 +-
>> arch/microblaze/kernel/timer.c | 326 -----------------------------
>> drivers/clocksource/Kconfig | 11 +
>> drivers/clocksource/Makefile | 1 +
>> drivers/clocksource/timer-xilinx.c | 300 ++++++++++++++++++++++++++
>> drivers/mfd/Makefile | 4 +
>> drivers/mfd/xilinx-timer.c | 147 +++++++++++++
>
> I'm confused!
>
>> include/linux/mfd/xilinx-timer.h | 134 ++++++++++++
>> 8 files changed, 598 insertions(+), 328 deletions(-)
>> delete mode 100644 arch/microblaze/kernel/timer.c
>> create mode 100644 drivers/clocksource/timer-xilinx.c
>> create mode 100644 drivers/mfd/xilinx-timer.c
>> create mode 100644 include/linux/mfd/xilinx-timer.h
>
> [...]
>
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Copyright (C) 2021 Sean Anderson <[email protected]>
>> + *
>> + * For Xilinx LogiCORE IP AXI Timer documentation, refer to DS764:
>> + * https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v1_03_a/axi_timer_ds764.pdf
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/mfd/xilinx-timer.h>
>> +#include <linux/of.h>
>> +#include <asm/io.h>
>
> RED FLAG: You are not using the MFD API here.

Should I be?

>
>> +#define TCSR0 0x00
>> +#define TLR0 0x04
>> +#define TCR0 0x08
>> +#define TCSR1 0x10
>> +#define TLR1 0x14
>> +#define TCR1 0x18
>> +
>> +#define TCSR_MDT BIT(0)
>> +#define TCSR_UDT BIT(1)
>> +#define TCSR_GENT BIT(2)
>> +#define TCSR_CAPT BIT(3)
>> +#define TCSR_ARHT BIT(4)
>> +#define TCSR_LOAD BIT(5)
>> +#define TCSR_ENIT BIT(6)
>> +#define TCSR_ENT BIT(7)
>> +#define TCSR_TINT BIT(8)
>> +#define TCSR_PWMA BIT(9)
>> +#define TCSR_ENALL BIT(10)
>> +#define TCSR_CASC BIT(11)
>> +
>> +/* readl/writel wrappers to support BE systems */
>> +
>> +static u32 xilinx_ioread32be(const void __iomem *addr)
>> +{
>> + return ioread32be(addr);
>> +}
>> +
>> +static void xilinx_iowrite32be(u32 value, void __iomem *addr)
>> +{
>> + iowrite32be(value, addr);
>> +}
>> +
>> +static u32 xilinx_ioread32(const void __iomem *addr)
>> +{
>> + return ioread32(addr);
>> +}
>> +
>> +static void xilinx_iowrite32(u32 value, void __iomem *addr)
>> +{
>> + iowrite32(value, addr);
>> +}
>
> Abstraction for the sake of abstraction, is not allowed.
>
> Just use the io*() calls directly in-place.

Can't. The call signatures on some arches have volatile addr and some do
not. So without these wrappers, we will get warnings about how the
function has the wrong type.

>
>> +int xilinx_timer_tlr_cycles(struct xilinx_timer_priv *priv, u32 *tlr,
>> + u32 tcsr, u64 cycles)
>> +{
>> + if (cycles < 2 || cycles > priv->max + 2)
>> + return -ERANGE;
>> +
>> + if (tcsr & TCSR_UDT)
>> + *tlr = cycles - 2;
>> + else
>> + *tlr = priv->max - cycles + 2;
>> +
>> + return 0;
>> +}
>> +
>> +int xilinx_timer_tlr_period(struct xilinx_timer_priv *priv, u32 *tlr,
>> + u32 tcsr, unsigned int period)
>> +{
>> + u64 cycles = DIV_ROUND_DOWN_ULL((u64)period * clk_get_rate(priv->clk),
>> + NSEC_PER_SEC);
>> +
>> + return xilinx_timer_tlr_cycles(priv, tlr, tcsr, cycles);
>> +}
>> +
>> +unsigned int xilinx_timer_get_period(struct xilinx_timer_priv *priv,
>> + u32 tlr, u32 tcsr)
>> +{
>> + u64 cycles;
>> +
>> + if (tcsr & TCSR_UDT)
>> + cycles = tlr + 2;
>> + else
>> + cycles = priv->max - tlr + 2;
>> +
>> + return DIV_ROUND_UP_ULL(cycles * NSEC_PER_SEC,
>> + clk_get_rate(priv->clk));
>> +}
>> +
>> +int xilinx_timer_common_init(struct device_node *np,
>> + struct xilinx_timer_priv *priv,
>> + u32 *one_timer)
>> +{
>> + int ret;
>> + u32 tcsr0, width;
>> +
>> +
>> + priv->read = xilinx_ioread32;
>> + priv->write = xilinx_iowrite32;
>> + /*
>> + * If PWM mode is enabled, we should try not to disturb it. Use
>> + * CAPT since if PWM mode is enabled then MDT will be set as
>> + * well.
>> + *
>> + * First, clear CAPT and verify that it has been cleared
>> + */
>> + tcsr0 = xilinx_timer_read(priv, TCSR0);
>> + xilinx_timer_write(priv, tcsr0 & ~(TCSR_CAPT & swab(TCSR_CAPT)), TCSR0);
>> + tcsr0 = xilinx_timer_read(priv, TCSR0);
>> + if (tcsr0 & (TCSR_CAPT | swab(TCSR_CAPT))) {
>> + pr_err("%pOF: cannot determine endianness\n", np);
>> + return -EOPNOTSUPP;
>> + }
>> +
>> + /* Then check to make sure our write sticks */
>> + xilinx_timer_write(priv, tcsr0 | TCSR_CAPT, TCSR0);
>> + if (!(xilinx_timer_read(priv, TCSR0) & TCSR_CAPT)) {
>> + priv->read = xilinx_ioread32be;
>> + priv->write = xilinx_iowrite32be;
>> + }
>> +
>> + ret = of_property_read_u32(np, "xlnx,one-timer-only", one_timer);
>> + if (ret) {
>> + pr_err("%pOF: err %d: xlnx,one-timer-only\n", np, ret);
>> + return ret;
>> + } else if (*one_timer && *one_timer != 1) {
>> + pr_err("%pOF: xlnx,one-timer-only must be 0 or 1\n", np);
>> + return -EINVAL;
>> + }
>> +
>> + ret = of_property_read_u32(np, "xlnx,count-width", &width);
>> + if (ret == -EINVAL) {
>> + width = 32;
>> + } else if (ret) {
>> + pr_err("%pOF: err %d: xlnx,count-width\n", np, ret);
>> + return ret;
>> + } else if (width < 8 || width > 32) {
>> + pr_err("%pOF: invalid counter width\n", np);
>> + return -EINVAL;
>> + }
>> + priv->max = BIT_ULL(width) - 1;
>> +
>> + return 0;
>> +}
>
> This is *all* timer stuff.
>
> What is your rationale for dumping this into MFD?

It was requested that common code for the timer and PWM drivers be
reused in some way. I stuck it in mfd because I wasn't sure where else
to put it. If you have a better location suggestion, I'm all ears

--Sean

2021-06-01 16:49:00

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 1/3] dt-bindings: pwm: Add Xilinx AXI Timer



On 6/1/21 9:32 AM, Rob Herring wrote:
> On Fri, 28 May 2021 17:45:20 -0400, Sean Anderson wrote:
>> This adds a binding for the Xilinx LogiCORE IP AXI Timer. This device is
>> a "soft" block, so it has many parameters which would not be
>> configurable in most hardware. This binding is usually automatically
>> generated by Xilinx's tools, so the names and values of some properties
>> must be kept as they are. Replacement properties have been provided for
>> new device trees.
>>
>> Because we need to init timer devices so early in boot, the easiest way
>> to configure things is to use a device tree property. For the moment
>> this is 'xlnx,pwm', but this could be extended/renamed/etc. in the
>> future if these is a need for a generic property.
>>
>> Signed-off-by: Sean Anderson <[email protected]>
>> ---
>>
>> Changes in v4:
>> - Remove references to generate polarity so this can get merged
>> - Predicate PWM driver on the presence of #pwm-cells
>> - Make some properties optional for clocksource drivers
>>
>> Changes in v3:
>> - Mark all boolean-as-int properties as deprecated
>> - Add xlnx,pwm and xlnx,gen?-active-low properties.
>> - Make newer replacement properties mutually-exclusive with what they
>> replace
>> - Add an example with non-deprecated properties only.
>>
>> Changes in v2:
>> - Use 32-bit addresses for example binding
>>
>> .../bindings/pwm/xlnx,axi-timer.yaml | 85 +++++++++++++++++++
>> 1 file changed, 85 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>
>
> My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
> on your patch (DT_CHECKER_FLAGS is new in v5.13):
>
> yamllint warnings/errors:
> ./Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml:16:10: [warning] wrong indentation: expected 10 but found 9 (indentation)
> ./Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml:19:10: [warning] wrong indentation: expected 10 but found 9 (indentation)
>
> dtschema/dtc warnings/errors:
>
> See https://patchwork.ozlabs.org/patch/1485318
>
> This check can fail if there are any dependencies. The base for a patch
> series is generally the most recent rc1.
>
> If you already ran 'make dt_binding_check' and didn't see the above
> error(s), then make sure 'yamllint' is installed and dt-schema is up to
> date:

I needed yamllint installed to get these errors. Is this requirement
documented anywhere? I don't see it in [1].

--Sean

[1] https://www.kernel.org/doc/html/latest/devicetree/bindings/writing-schema.html#testing

>
> pip3 install dtschema --upgrade
>
> Please check and re-submit.
>

2021-06-10 16:18:20

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

ping

Michal, I know you had some objections to previous spins of this series
which I have tried to address this time around. So I would appreciate if
you could review this.

--Sean

On 5/28/21 5:45 PM, Sean Anderson wrote:
> This adds PWM support for Xilinx LogiCORE IP AXI soft timers commonly
> found on Xilinx FPGAs. At the moment clock control is very basic: we
> just enable the clock during probe and pin the frequency. In the future,
> someone could add support for disabling the clock when not in use.
>
> This driver was written with reference to Xilinx DS764 for v1.03.a [1].
>
> [1] https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v1_03_a/axi_timer_ds764.pdf
>
> Signed-off-by: Sean Anderson <[email protected]>
> ---
>
> Changes in v4:
> - Remove references to properties which are not good enough for Linux.
> - Don't use volatile in read/write replacements. Some arches have it and
> some don't.
> - Put common timer properties into their own struct to better reuse
> code.
>
> Changes in v3:
> - Add clockevent and clocksource support
> - Rewrite probe to only use a device_node, since timers may need to be
> initialized before we have proper devices. This does bloat the code a bit
> since we can no longer rely on helpers such as dev_err_probe. We also
> cannot rely on device resources being free'd on failure, so we must free
> them manually.
> - We now access registers through xilinx_timer_(read|write). This allows us
> to deal with endianness issues, as originally seen in the microblaze
> driver. CAVEAT EMPTOR: I have not tested this on big-endian!
> - Remove old microblaze driver
>
> Changes in v2:
> - Don't compile this module by default for arm64
> - Add dependencies on COMMON_CLK and HAS_IOMEM
> - Add comment explaining why we depend on !MICROBLAZE
> - Add comment describing device
> - Rename TCSR_(SET|CLEAR) to TCSR_RUN_(SET|CLEAR)
> - Use NSEC_TO_SEC instead of defining our own
> - Use TCSR_RUN_MASK to check if the PWM is enabled, as suggested by Uwe
> - Cast dividends to u64 to avoid overflow
> - Check for over- and underflow when calculating TLR
> - Set xilinx_pwm_ops.owner
> - Don't set pwmchip.base to -1
> - Check range of xlnx,count-width
> - Ensure the clock is always running when the pwm is registered
> - Remove debugfs file :l
> - Report errors with dev_error_probe
>
> drivers/mfd/Makefile | 2 +-
> drivers/pwm/Kconfig | 12 +++
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-xilinx.c | 219 +++++++++++++++++++++++++++++++++++++++
> 4 files changed, 233 insertions(+), 1 deletion(-)
> create mode 100644 drivers/pwm/pwm-xilinx.c
>
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index f0f9fbdde7dc..89769affe251 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -269,6 +269,6 @@ obj-$(CONFIG_SGI_MFD_IOC3) += ioc3.o
> obj-$(CONFIG_MFD_SIMPLE_MFD_I2C) += simple-mfd-i2c.o
> obj-$(CONFIG_MFD_INTEL_M10_BMC) += intel-m10-bmc.o
>
> -ifneq ($(CONFIG_XILINX_TIMER),)
> +ifneq ($(CONFIG_PWM_XILINX)$(CONFIG_XILINX_TIMER),)
> obj-y += xilinx-timer.o
> endif
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 8ae68d6203fb..ebf8d9014758 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -620,4 +620,16 @@ config PWM_VT8500
> To compile this driver as a module, choose M here: the module
> will be called pwm-vt8500.
>
> +config PWM_XILINX
> + tristate "Xilinx AXI Timer PWM support"
> + depends on HAS_IOMEM && COMMON_CLK
> + help
> + PWM driver for Xilinx LogiCORE IP AXI timers. This timer is
> + typically a soft core which may be present in Xilinx FPGAs.
> + This device may also be present in Microblaze soft processors.
> + If you don't have this IP in your design, choose N.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called pwm-xilinx.
> +
> endif
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index d43b1e17e8e1..655df169b895 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -58,3 +58,4 @@ obj-$(CONFIG_PWM_TWL) += pwm-twl.o
> obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o
> obj-$(CONFIG_PWM_VISCONTI) += pwm-visconti.o
> obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o
> +obj-$(CONFIG_PWM_XILINX) += pwm-xilinx.o
> diff --git a/drivers/pwm/pwm-xilinx.c b/drivers/pwm/pwm-xilinx.c
> new file mode 100644
> index 000000000000..f05321496717
> --- /dev/null
> +++ b/drivers/pwm/pwm-xilinx.c
> @@ -0,0 +1,219 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2021 Sean Anderson <[email protected]>
> + *
> + * Hardware limitations:
> + * - When changing both duty cycle and period, we may end up with one cycle
> + * with the old duty cycle and the new period.
> + * - Cannot produce 100% duty cycle.
> + * - Only produces "normal" output.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/mfd/xilinx-timer.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +
> +/*
> + * The idea here is to capture whether the PWM is actually running (e.g.
> + * because we or the bootloader set it up) and we need to be careful to ensure
> + * we don't cause a glitch. According to the data sheet, to enable the PWM we
> + * need to
> + *
> + * - Set both timers to generate mode (MDT=1)
> + * - Set both timers to PWM mode (PWMA=1)
> + * - Enable the generate out signals (GENT=1)
> + *
> + * In addition,
> + *
> + * - The timer must be running (ENT=1)
> + * - The timer must auto-reload TLR into TCR (ARHT=1)
> + * - We must not be in the process of loading TLR into TCR (LOAD=0)
> + * - Cascade mode must be disabled (CASC=0)
> + *
> + * If any of these differ from usual, then the PWM is either disabled, or is
> + * running in a mode that this driver does not support.
> + */
> +#define TCSR_PWM_SET (TCSR_GENT | TCSR_ARHT | TCSR_ENT | TCSR_PWMA)
> +#define TCSR_PWM_CLEAR (TCSR_MDT | TCSR_LOAD)
> +#define TCSR_PWM_MASK (TCSR_PWM_SET | TCSR_PWM_CLEAR)
> +
> +struct xilinx_pwm_device {
> + struct pwm_chip chip;
> + struct xilinx_timer_priv priv;
> +};
> +
> +static inline struct xilinx_timer_priv
> +*xilinx_pwm_chip_to_priv(struct pwm_chip *chip)
> +{
> + return &container_of(chip, struct xilinx_pwm_device, chip)->priv;
> +}
> +
> +static bool xilinx_timer_pwm_enabled(u32 tcsr0, u32 tcsr1)
> +{
> + return ((TCSR_PWM_MASK | TCSR_CASC) & tcsr0) == TCSR_PWM_SET &&
> + (TCSR_PWM_MASK & tcsr1) == TCSR_PWM_SET;
> +}
> +
> +static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused,
> + const struct pwm_state *state)
> +{
> + int ret;
> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
> + u32 tlr0, tlr1;
> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
> + bool enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
> +
> + if (state->polarity != PWM_POLARITY_NORMAL)
> + return -EINVAL;
> +
> + ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
> + if (ret)
> + return ret;
> +
> + ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
> + if (ret)
> + return ret;
> +
> + xilinx_timer_write(priv, tlr0, TLR0);
> + xilinx_timer_write(priv, tlr1, TLR1);
> +
> + if (state->enabled) {
> + /* Only touch the TCSRs if we aren't already running */
> + if (!enabled) {
> + /* Load TLR into TCR */
> + xilinx_timer_write(priv, tcsr0 | TCSR_LOAD, TCSR0);
> + xilinx_timer_write(priv, tcsr1 | TCSR_LOAD, TCSR1);
> + /* Enable timers all at once with ENALL */
> + tcsr0 = (TCSR_PWM_SET & ~TCSR_ENT) | (tcsr0 & TCSR_UDT);
> + tcsr1 = TCSR_PWM_SET | TCSR_ENALL | (tcsr1 & TCSR_UDT);
> + xilinx_timer_write(priv, tcsr0, TCSR0);
> + xilinx_timer_write(priv, tcsr1, TCSR1);
> + }
> + } else {
> + xilinx_timer_write(priv, 0, TCSR0);
> + xilinx_timer_write(priv, 0, TCSR1);
> + }
> +
> + return 0;
> +}
> +
> +static void xilinx_pwm_get_state(struct pwm_chip *chip,
> + struct pwm_device *unused,
> + struct pwm_state *state)
> +{
> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
> + u32 tlr0 = xilinx_timer_read(priv, TLR0);
> + u32 tlr1 = xilinx_timer_read(priv, TLR1);
> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
> +
> + state->period = xilinx_timer_get_period(priv, tlr0, tcsr0);
> + state->duty_cycle = xilinx_timer_get_period(priv, tlr1, tcsr1);
> + state->enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
> + state->polarity = PWM_POLARITY_NORMAL;
> +}
> +
> +static const struct pwm_ops xilinx_pwm_ops = {
> + .apply = xilinx_pwm_apply,
> + .get_state = xilinx_pwm_get_state,
> + .owner = THIS_MODULE,
> +};
> +
> +static int xilinx_timer_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct device *dev = &pdev->dev;
> + struct device_node *np = dev->of_node;
> + struct xilinx_timer_priv *priv;
> + struct xilinx_pwm_device *pwm;
> + u32 pwm_cells, one_timer;
> +
> + ret = of_property_read_u32(np, "#pwm-cells", &pwm_cells);
> + if (ret == -EINVAL)
> + return -ENODEV;
> + else if (ret)
> + return dev_err_probe(dev, ret, "#pwm-cells\n");
> + else if (pwm_cells)
> + return dev_err_probe(dev, -EINVAL, "#pwm-cells must be 0\n");
> +
> + pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
> + if (!pwm)
> + return -ENOMEM;
> + platform_set_drvdata(pdev, pwm);
> + priv = &pwm->priv;
> +
> + priv->regs = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(priv->regs))
> + return PTR_ERR(priv->regs);
> +
> + ret = xilinx_timer_common_init(np, priv, &one_timer);
> + if (ret)
> + return ret;
> +
> + if (one_timer)
> + return dev_err_probe(dev, -EINVAL,
> + "two timers required for PWM mode\n");
> +
> + /*
> + * The polarity of the generate outputs must be active high for PWM
> + * mode to work. We could determine this from the device tree, but
> + * alas, such properties are not allowed to be used.
> + */
> +
> + priv->clk = devm_clk_get(dev, "s_axi_aclk");
> + if (IS_ERR(priv->clk))
> + return dev_err_probe(dev, PTR_ERR(priv->clk), "clock\n");
> +
> + ret = clk_prepare_enable(priv->clk);
> + if (ret)
> + return dev_err_probe(dev, ret, "clock enable failed\n");
> + clk_rate_exclusive_get(priv->clk);
> +
> + pwm->chip.dev = dev;
> + pwm->chip.ops = &xilinx_pwm_ops;
> + pwm->chip.npwm = 1;
> + ret = pwmchip_add(&pwm->chip);
> + if (ret) {
> + clk_rate_exclusive_put(priv->clk);
> + clk_disable_unprepare(priv->clk);
> + return dev_err_probe(dev, ret, "could not register pwm chip\n");
> + }
> +
> + return 0;
> +}
> +
> +static int xilinx_timer_remove(struct platform_device *pdev)
> +{
> + struct xilinx_pwm_device *pwm = platform_get_drvdata(pdev);
> +
> + pwmchip_remove(&pwm->chip);
> + clk_rate_exclusive_put(pwm->priv.clk);
> + clk_disable_unprepare(pwm->priv.clk);
> + return 0;
> +}
> +
> +static const struct of_device_id xilinx_timer_of_match[] = {
> + { .compatible = "xlnx,xps-timer-1.00.a", },
> + { .compatible = "xlnx,axi-timer-2.0" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, xilinx_timer_of_match);
> +
> +static struct platform_driver xilinx_timer_driver = {
> + .probe = xilinx_timer_probe,
> + .remove = xilinx_timer_remove,
> + .driver = {
> + .name = "xilinx-timer",
> + .of_match_table = of_match_ptr(xilinx_timer_of_match),
> + },
> +};
> +module_platform_driver(xilinx_timer_driver);
> +
> +MODULE_ALIAS("platform:xilinx-timer");
> +MODULE_DESCRIPTION("Xilinx LogiCORE IP AXI Timer driver");
> +MODULE_LICENSE("GPL v2");
>

2021-06-25 06:21:35

by Uwe Kleine-König

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

On Fri, May 28, 2021 at 05:45:22PM -0400, Sean Anderson wrote:
> This adds PWM support for Xilinx LogiCORE IP AXI soft timers commonly
> found on Xilinx FPGAs. At the moment clock control is very basic: we
> just enable the clock during probe and pin the frequency. In the future,
> someone could add support for disabling the clock when not in use.
>
> This driver was written with reference to Xilinx DS764 for v1.03.a [1].
>
> [1] https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v1_03_a/axi_timer_ds764.pdf
>
> Signed-off-by: Sean Anderson <[email protected]>
> ---
>
> Changes in v4:
> - Remove references to properties which are not good enough for Linux.
> - Don't use volatile in read/write replacements. Some arches have it and
> some don't.
> - Put common timer properties into their own struct to better reuse
> code.
>
> Changes in v3:
> - Add clockevent and clocksource support
> - Rewrite probe to only use a device_node, since timers may need to be
> initialized before we have proper devices. This does bloat the code a bit
> since we can no longer rely on helpers such as dev_err_probe. We also
> cannot rely on device resources being free'd on failure, so we must free
> them manually.
> - We now access registers through xilinx_timer_(read|write). This allows us
> to deal with endianness issues, as originally seen in the microblaze
> driver. CAVEAT EMPTOR: I have not tested this on big-endian!
> - Remove old microblaze driver
>
> Changes in v2:
> - Don't compile this module by default for arm64
> - Add dependencies on COMMON_CLK and HAS_IOMEM
> - Add comment explaining why we depend on !MICROBLAZE
> - Add comment describing device
> - Rename TCSR_(SET|CLEAR) to TCSR_RUN_(SET|CLEAR)
> - Use NSEC_TO_SEC instead of defining our own
> - Use TCSR_RUN_MASK to check if the PWM is enabled, as suggested by Uwe
> - Cast dividends to u64 to avoid overflow
> - Check for over- and underflow when calculating TLR
> - Set xilinx_pwm_ops.owner
> - Don't set pwmchip.base to -1
> - Check range of xlnx,count-width
> - Ensure the clock is always running when the pwm is registered
> - Remove debugfs file :l
> - Report errors with dev_error_probe
>
> drivers/mfd/Makefile | 2 +-
> drivers/pwm/Kconfig | 12 +++
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-xilinx.c | 219 +++++++++++++++++++++++++++++++++++++++
> 4 files changed, 233 insertions(+), 1 deletion(-)
> create mode 100644 drivers/pwm/pwm-xilinx.c
>
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index f0f9fbdde7dc..89769affe251 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -269,6 +269,6 @@ obj-$(CONFIG_SGI_MFD_IOC3) += ioc3.o
> obj-$(CONFIG_MFD_SIMPLE_MFD_I2C) += simple-mfd-i2c.o
> obj-$(CONFIG_MFD_INTEL_M10_BMC) += intel-m10-bmc.o
>
> -ifneq ($(CONFIG_XILINX_TIMER),)
> +ifneq ($(CONFIG_PWM_XILINX)$(CONFIG_XILINX_TIMER),)
> obj-y += xilinx-timer.o
> endif
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 8ae68d6203fb..ebf8d9014758 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -620,4 +620,16 @@ config PWM_VT8500
> To compile this driver as a module, choose M here: the module
> will be called pwm-vt8500.
>
> +config PWM_XILINX
> + tristate "Xilinx AXI Timer PWM support"
> + depends on HAS_IOMEM && COMMON_CLK
> + help
> + PWM driver for Xilinx LogiCORE IP AXI timers. This timer is
> + typically a soft core which may be present in Xilinx FPGAs.
> + This device may also be present in Microblaze soft processors.
> + If you don't have this IP in your design, choose N.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called pwm-xilinx.
> +
> endif
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index d43b1e17e8e1..655df169b895 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -58,3 +58,4 @@ obj-$(CONFIG_PWM_TWL) += pwm-twl.o
> obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o
> obj-$(CONFIG_PWM_VISCONTI) += pwm-visconti.o
> obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o
> +obj-$(CONFIG_PWM_XILINX) += pwm-xilinx.o
> diff --git a/drivers/pwm/pwm-xilinx.c b/drivers/pwm/pwm-xilinx.c
> new file mode 100644
> index 000000000000..f05321496717
> --- /dev/null
> +++ b/drivers/pwm/pwm-xilinx.c
> @@ -0,0 +1,219 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2021 Sean Anderson <[email protected]>
> + *
> + * Hardware limitations:
> + * - When changing both duty cycle and period, we may end up with one cycle
> + * with the old duty cycle and the new period.

That means it doesn't reset the counter when a new period is set, right?

> + * - Cannot produce 100% duty cycle.

Can it produce a 0% duty cycle? Below you're calling
xilinx_timer_tlr_period(..., ..., ..., 0) then which returns -ERANGE.

> + * - Only produces "normal" output.

Does the output emit a low level when it's disabled?

> + */
> +
> [...]
> +static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused,
> + const struct pwm_state *state)
> +{
> + int ret;
> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
> + u32 tlr0, tlr1;
> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
> + bool enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
> +
> + if (state->polarity != PWM_POLARITY_NORMAL)
> + return -EINVAL;
> +
> + ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
> + if (ret)
> + return ret;

The implementation of xilinx_timer_tlr_period (in patch 2/3) returns
-ERANGE for big periods. The good behaviour to implement is to cap to
the biggest period possible in this case.

Also note that state->period is an u64 but it is casted to unsigned int
as this is the type of the forth parameter of xilinx_timer_tlr_period.

> + ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
> + if (ret)
> + return ret;
> +
> + xilinx_timer_write(priv, tlr0, TLR0);
> + xilinx_timer_write(priv, tlr1, TLR1);
> +
> + if (state->enabled) {
> + /* Only touch the TCSRs if we aren't already running */
> + if (!enabled) {
> + /* Load TLR into TCR */
> + xilinx_timer_write(priv, tcsr0 | TCSR_LOAD, TCSR0);
> + xilinx_timer_write(priv, tcsr1 | TCSR_LOAD, TCSR1);
> + /* Enable timers all at once with ENALL */
> + tcsr0 = (TCSR_PWM_SET & ~TCSR_ENT) | (tcsr0 & TCSR_UDT);
> + tcsr1 = TCSR_PWM_SET | TCSR_ENALL | (tcsr1 & TCSR_UDT);
> + xilinx_timer_write(priv, tcsr0, TCSR0);
> + xilinx_timer_write(priv, tcsr1, TCSR1);
> + }
> + } else {
> + xilinx_timer_write(priv, 0, TCSR0);
> + xilinx_timer_write(priv, 0, TCSR1);
> + }
> +
> + return 0;
> +}
> +
> +static void xilinx_pwm_get_state(struct pwm_chip *chip,
> + struct pwm_device *unused,
> + struct pwm_state *state)
> +{
> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
> + u32 tlr0 = xilinx_timer_read(priv, TLR0);
> + u32 tlr1 = xilinx_timer_read(priv, TLR1);
> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
> +
> + state->period = xilinx_timer_get_period(priv, tlr0, tcsr0);
> + state->duty_cycle = xilinx_timer_get_period(priv, tlr1, tcsr1);
> + state->enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
> + state->polarity = PWM_POLARITY_NORMAL;

Are the values returned here sensible if the hardware isn't in PWM mode?

> +}
> +
> +static const struct pwm_ops xilinx_pwm_ops = {
> + .apply = xilinx_pwm_apply,
> + .get_state = xilinx_pwm_get_state,
> + .owner = THIS_MODULE,
> +};
> +
> +static int xilinx_timer_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct device *dev = &pdev->dev;
> + struct device_node *np = dev->of_node;
> + struct xilinx_timer_priv *priv;
> + struct xilinx_pwm_device *pwm;
> + u32 pwm_cells, one_timer;
> +
> + ret = of_property_read_u32(np, "#pwm-cells", &pwm_cells);
> + if (ret == -EINVAL)
> + return -ENODEV;
> + else if (ret)
> + return dev_err_probe(dev, ret, "#pwm-cells\n");

Very sparse error message.

> + else if (pwm_cells)
> + return dev_err_probe(dev, -EINVAL, "#pwm-cells must be 0\n");

What is the rationale here to not support #pwm-cells = <2>?

> + pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
> + if (!pwm)
> + return -ENOMEM;
> + platform_set_drvdata(pdev, pwm);
> + priv = &pwm->priv;
> +
> + priv->regs = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(priv->regs))
> + return PTR_ERR(priv->regs);
> +
> + ret = xilinx_timer_common_init(np, priv, &one_timer);
> + if (ret)
> + return ret;
> +
> + if (one_timer)
> + return dev_err_probe(dev, -EINVAL,
> + "two timers required for PWM mode\n");
> +
> + /*
> + * The polarity of the generate outputs must be active high for PWM
> + * mode to work. We could determine this from the device tree, but
> + * alas, such properties are not allowed to be used.
> + */
> +
> + priv->clk = devm_clk_get(dev, "s_axi_aclk");
> + if (IS_ERR(priv->clk))
> + return dev_err_probe(dev, PTR_ERR(priv->clk), "clock\n");

again a sparse error message.

> +
> + ret = clk_prepare_enable(priv->clk);
> + if (ret)
> + return dev_err_probe(dev, ret, "clock enable failed\n");
> + clk_rate_exclusive_get(priv->clk);
> +
> + pwm->chip.dev = dev;
> + pwm->chip.ops = &xilinx_pwm_ops;
> + pwm->chip.npwm = 1;
> + ret = pwmchip_add(&pwm->chip);
> + if (ret) {
> + clk_rate_exclusive_put(priv->clk);
> + clk_disable_unprepare(priv->clk);
> + return dev_err_probe(dev, ret, "could not register pwm chip\n");
> + }
> +
> + return 0;
> +}

--
Pengutronix e.K. | Uwe Kleine-K?nig |
Industrial Linux Solutions | https://www.pengutronix.de/ |


Attachments:
(No filename) (9.75 kB)
signature.asc (495.00 B)
Download all attachments

2021-06-25 15:15:01

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer



On 6/25/21 2:19 AM, Uwe Kleine-K?nig wrote:
> On Fri, May 28, 2021 at 05:45:22PM -0400, Sean Anderson wrote:
>> This adds PWM support for Xilinx LogiCORE IP AXI soft timers commonly
>> found on Xilinx FPGAs. At the moment clock control is very basic: we
>> just enable the clock during probe and pin the frequency. In the future,
>> someone could add support for disabling the clock when not in use.
>>
>> This driver was written with reference to Xilinx DS764 for v1.03.a [1].
>>
>> [1] https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v1_03_a/axi_timer_ds764.pdf
>>
>> Signed-off-by: Sean Anderson <[email protected]>
>> ---
>>
>> Changes in v4:
>> - Remove references to properties which are not good enough for Linux.
>> - Don't use volatile in read/write replacements. Some arches have it and
>> some don't.
>> - Put common timer properties into their own struct to better reuse
>> code.
>>
>> Changes in v3:
>> - Add clockevent and clocksource support
>> - Rewrite probe to only use a device_node, since timers may need to be
>> initialized before we have proper devices. This does bloat the code a bit
>> since we can no longer rely on helpers such as dev_err_probe. We also
>> cannot rely on device resources being free'd on failure, so we must free
>> them manually.
>> - We now access registers through xilinx_timer_(read|write). This allows us
>> to deal with endianness issues, as originally seen in the microblaze
>> driver. CAVEAT EMPTOR: I have not tested this on big-endian!
>> - Remove old microblaze driver
>>
>> Changes in v2:
>> - Don't compile this module by default for arm64
>> - Add dependencies on COMMON_CLK and HAS_IOMEM
>> - Add comment explaining why we depend on !MICROBLAZE
>> - Add comment describing device
>> - Rename TCSR_(SET|CLEAR) to TCSR_RUN_(SET|CLEAR)
>> - Use NSEC_TO_SEC instead of defining our own
>> - Use TCSR_RUN_MASK to check if the PWM is enabled, as suggested by Uwe
>> - Cast dividends to u64 to avoid overflow
>> - Check for over- and underflow when calculating TLR
>> - Set xilinx_pwm_ops.owner
>> - Don't set pwmchip.base to -1
>> - Check range of xlnx,count-width
>> - Ensure the clock is always running when the pwm is registered
>> - Remove debugfs file :l
>> - Report errors with dev_error_probe
>>
>> drivers/mfd/Makefile | 2 +-
>> drivers/pwm/Kconfig | 12 +++
>> drivers/pwm/Makefile | 1 +
>> drivers/pwm/pwm-xilinx.c | 219 +++++++++++++++++++++++++++++++++++++++
>> 4 files changed, 233 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/pwm/pwm-xilinx.c
>>
>> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
>> index f0f9fbdde7dc..89769affe251 100644
>> --- a/drivers/mfd/Makefile
>> +++ b/drivers/mfd/Makefile
>> @@ -269,6 +269,6 @@ obj-$(CONFIG_SGI_MFD_IOC3) += ioc3.o
>> obj-$(CONFIG_MFD_SIMPLE_MFD_I2C) += simple-mfd-i2c.o
>> obj-$(CONFIG_MFD_INTEL_M10_BMC) += intel-m10-bmc.o
>>
>> -ifneq ($(CONFIG_XILINX_TIMER),)
>> +ifneq ($(CONFIG_PWM_XILINX)$(CONFIG_XILINX_TIMER),)
>> obj-y += xilinx-timer.o
>> endif
>> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>> index 8ae68d6203fb..ebf8d9014758 100644
>> --- a/drivers/pwm/Kconfig
>> +++ b/drivers/pwm/Kconfig
>> @@ -620,4 +620,16 @@ config PWM_VT8500
>> To compile this driver as a module, choose M here: the module
>> will be called pwm-vt8500.
>>
>> +config PWM_XILINX
>> + tristate "Xilinx AXI Timer PWM support"
>> + depends on HAS_IOMEM && COMMON_CLK
>> + help
>> + PWM driver for Xilinx LogiCORE IP AXI timers. This timer is
>> + typically a soft core which may be present in Xilinx FPGAs.
>> + This device may also be present in Microblaze soft processors.
>> + If you don't have this IP in your design, choose N.
>> +
>> + To compile this driver as a module, choose M here: the module
>> + will be called pwm-xilinx.
>> +
>> endif
>> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
>> index d43b1e17e8e1..655df169b895 100644
>> --- a/drivers/pwm/Makefile
>> +++ b/drivers/pwm/Makefile
>> @@ -58,3 +58,4 @@ obj-$(CONFIG_PWM_TWL) += pwm-twl.o
>> obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o
>> obj-$(CONFIG_PWM_VISCONTI) += pwm-visconti.o
>> obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o
>> +obj-$(CONFIG_PWM_XILINX) += pwm-xilinx.o
>> diff --git a/drivers/pwm/pwm-xilinx.c b/drivers/pwm/pwm-xilinx.c
>> new file mode 100644
>> index 000000000000..f05321496717
>> --- /dev/null
>> +++ b/drivers/pwm/pwm-xilinx.c
>> @@ -0,0 +1,219 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Copyright (C) 2021 Sean Anderson <[email protected]>
>> + *
>> + * Hardware limitations:
>> + * - When changing both duty cycle and period, we may end up with one cycle
>> + * with the old duty cycle and the new period.
>
> That means it doesn't reset the counter when a new period is set, right?

Correct. The only way to write to the counter is to stop the timer and
restart it.

>
>> + * - Cannot produce 100% duty cycle.
>
> Can it produce a 0% duty cycle? Below you're calling
> xilinx_timer_tlr_period(..., ..., ..., 0) then which returns -ERANGE.

Yes. This is what you get when you try to specify 100% duty cycle (e.g.
TLR0 == TLR1).

>
>> + * - Only produces "normal" output.
>
> Does the output emit a low level when it's disabled?

I believe so.

>
>> + */
>> +
>> [...]
>> +static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused,
>> + const struct pwm_state *state)
>> +{
>> + int ret;
>> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
>> + u32 tlr0, tlr1;
>> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
>> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
>> + bool enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
>> +
>> + if (state->polarity != PWM_POLARITY_NORMAL)
>> + return -EINVAL;
>> +
>> + ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
>> + if (ret)
>> + return ret;
>
> The implementation of xilinx_timer_tlr_period (in patch 2/3) returns
> -ERANGE for big periods. The good behaviour to implement is to cap to
> the biggest period possible in this case.

Ok. Is this documented anywhere? And wouldn't this result in the wrong
duty cycle? E.g. say the max value is 100 and I try to apply a period of
150 and a duty_cycle of 75 (for a 50% duty cycle). If we cap at 100,
then I will instead have a 75% duty cycle, and there will be no error.
So I will silently get the wrong duty cycle, even when that duty cycle
is probably more important than the period.

>
> Also note that state->period is an u64 but it is casted to unsigned int
> as this is the type of the forth parameter of xilinx_timer_tlr_period.

Hm, it looks like I immediately cast period to a u64. I will change the
signature for this function next revision.

>
>> + ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
>> + if (ret)
>> + return ret;
>> +
>> + xilinx_timer_write(priv, tlr0, TLR0);
>> + xilinx_timer_write(priv, tlr1, TLR1);
>> +
>> + if (state->enabled) {
>> + /* Only touch the TCSRs if we aren't already running */
>> + if (!enabled) {
>> + /* Load TLR into TCR */
>> + xilinx_timer_write(priv, tcsr0 | TCSR_LOAD, TCSR0);
>> + xilinx_timer_write(priv, tcsr1 | TCSR_LOAD, TCSR1);
>> + /* Enable timers all at once with ENALL */
>> + tcsr0 = (TCSR_PWM_SET & ~TCSR_ENT) | (tcsr0 & TCSR_UDT);
>> + tcsr1 = TCSR_PWM_SET | TCSR_ENALL | (tcsr1 & TCSR_UDT);
>> + xilinx_timer_write(priv, tcsr0, TCSR0);
>> + xilinx_timer_write(priv, tcsr1, TCSR1);
>> + }
>> + } else {
>> + xilinx_timer_write(priv, 0, TCSR0);
>> + xilinx_timer_write(priv, 0, TCSR1);
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static void xilinx_pwm_get_state(struct pwm_chip *chip,
>> + struct pwm_device *unused,
>> + struct pwm_state *state)
>> +{
>> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
>> + u32 tlr0 = xilinx_timer_read(priv, TLR0);
>> + u32 tlr1 = xilinx_timer_read(priv, TLR1);
>> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
>> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
>> +
>> + state->period = xilinx_timer_get_period(priv, tlr0, tcsr0);
>> + state->duty_cycle = xilinx_timer_get_period(priv, tlr1, tcsr1);
>> + state->enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
>> + state->polarity = PWM_POLARITY_NORMAL;
>
> Are the values returned here sensible if the hardware isn't in PWM mode?

Yes. If the hardware isn't in PWM mode, then state->enabled will be
false.

>
>> +}
>> +
>> +static const struct pwm_ops xilinx_pwm_ops = {
>> + .apply = xilinx_pwm_apply,
>> + .get_state = xilinx_pwm_get_state,
>> + .owner = THIS_MODULE,
>> +};
>> +
>> +static int xilinx_timer_probe(struct platform_device *pdev)
>> +{
>> + int ret;
>> + struct device *dev = &pdev->dev;
>> + struct device_node *np = dev->of_node;
>> + struct xilinx_timer_priv *priv;
>> + struct xilinx_pwm_device *pwm;
>> + u32 pwm_cells, one_timer;
>> +
>> + ret = of_property_read_u32(np, "#pwm-cells", &pwm_cells);
>> + if (ret == -EINVAL)
>> + return -ENODEV;
>> + else if (ret)
>> + return dev_err_probe(dev, ret, "#pwm-cells\n");
>
> Very sparse error message.

Ok, will elaborate.

>
>> + else if (pwm_cells)
>> + return dev_err_probe(dev, -EINVAL, "#pwm-cells must be 0\n");
>
> What is the rationale here to not support #pwm-cells = <2>?

Only one PWM is supported. But otherwise there is no particular
reason.

>> + pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
>> + if (!pwm)
>> + return -ENOMEM;
>> + platform_set_drvdata(pdev, pwm);
>> + priv = &pwm->priv;
>> +
>> + priv->regs = devm_platform_ioremap_resource(pdev, 0);
>> + if (IS_ERR(priv->regs))
>> + return PTR_ERR(priv->regs);
>> +
>> + ret = xilinx_timer_common_init(np, priv, &one_timer);
>> + if (ret)
>> + return ret;
>> +
>> + if (one_timer)
>> + return dev_err_probe(dev, -EINVAL,
>> + "two timers required for PWM mode\n");
>> +
>> + /*
>> + * The polarity of the generate outputs must be active high for PWM
>> + * mode to work. We could determine this from the device tree, but
>> + * alas, such properties are not allowed to be used.
>> + */
>> +
>> + priv->clk = devm_clk_get(dev, "s_axi_aclk");
>> + if (IS_ERR(priv->clk))
>> + return dev_err_probe(dev, PTR_ERR(priv->clk), "clock\n");
>
> again a sparse error message.
>
>> +
>> + ret = clk_prepare_enable(priv->clk);
>> + if (ret)
>> + return dev_err_probe(dev, ret, "clock enable failed\n");
>> + clk_rate_exclusive_get(priv->clk);
>> +
>> + pwm->chip.dev = dev;
>> + pwm->chip.ops = &xilinx_pwm_ops;
>> + pwm->chip.npwm = 1;
>> + ret = pwmchip_add(&pwm->chip);
>> + if (ret) {
>> + clk_rate_exclusive_put(priv->clk);
>> + clk_disable_unprepare(priv->clk);
>> + return dev_err_probe(dev, ret, "could not register pwm chip\n");
>> + }
>> +
>> + return 0;
>> +}

Thanks for the review.

--Sean

2021-06-25 17:00:59

by Uwe Kleine-König

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

Hello Sean,

On Fri, Jun 25, 2021 at 11:13:33AM -0400, Sean Anderson wrote:
> On 6/25/21 2:19 AM, Uwe Kleine-K?nig wrote:
> > On Fri, May 28, 2021 at 05:45:22PM -0400, Sean Anderson wrote:
> >> + * Hardware limitations:

Please make this "* Limitations:" to match what the other drivers do and
so ease grepping for this info.

> >> + * - When changing both duty cycle and period, we may end up with one cycle
> >> + * with the old duty cycle and the new period.
> >
> > That means it doesn't reset the counter when a new period is set, right?
>
> Correct. The only way to write to the counter is to stop the timer and
> restart it.

ok.

> >> + * - Cannot produce 100% duty cycle.
> >
> > Can it produce a 0% duty cycle? Below you're calling
> > xilinx_timer_tlr_period(..., ..., ..., 0) then which returns -ERANGE.
>
> Yes. This is what you get when you try to specify 100% duty cycle (e.g.
> TLR0 == TLR1).

OK, so the hardware can do it, but your driver doesn't make use of it,
right?

> >> + * - Only produces "normal" output.
> >
> > Does the output emit a low level when it's disabled?
>
> I believe so.

Is there a possibility to be sure? I'd like to know that to complete my
picture about the behaviour of the supported PWMs.

> >> + */
> >> +
> >> [...]
> >> +static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused,
> >> + const struct pwm_state *state)
> >> +{
> >> + int ret;
> >> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
> >> + u32 tlr0, tlr1;
> >> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
> >> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
> >> + bool enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
> >> +
> >> + if (state->polarity != PWM_POLARITY_NORMAL)
> >> + return -EINVAL;
> >> +
> >> + ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
> >> + if (ret)
> >> + return ret;
> >
> > The implementation of xilinx_timer_tlr_period (in patch 2/3) returns
> > -ERANGE for big periods. The good behaviour to implement is to cap to
> > the biggest period possible in this case.
>
> Ok. Is this documented anywhere?

I tried but Thierry didn't like the result and I didn't retry. The
problem is also that many drivers we already have in the tree don't
behave like this (because for a long time nobody cared). That new
drivers should behave this way is my effort to get some consistent
behaviour.

> And wouldn't this result in the wrong duty cycle? E.g. say the max
> value is 100 and I try to apply a period of 150 and a duty_cycle of 75
> (for a 50% duty cycle). If we cap at 100, then I will instead have a
> 75% duty cycle, and there will be no error.

Yes that is right. That there is no feedback is a problem that we have
for a long time. I have a prototype patch that implements a
pwm_round_state() function that lets a consumer know the result of
applying a certain pwm_state in advance. But we're not there yet.

> So I will silently get the wrong duty cycle, even when that duty cycle
> is probably more important than the period.

It depends on the use case and every policy is wrong for some cases. So
I picked the policy I already explained because it is a) easy to
implement for lowlevel drivers and b) it's easy to work with for
consumers once we have pwm_round_state().

> > Also note that state->period is an u64 but it is casted to unsigned int
> > as this is the type of the forth parameter of xilinx_timer_tlr_period.
>
> Hm, it looks like I immediately cast period to a u64. I will change the
> signature for this function next revision.

Then note that period * clk_get_rate(priv->clk) might overflow.

> >> + ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
> >> + if (ret)
> >> + return ret;
> >> +
> >> + xilinx_timer_write(priv, tlr0, TLR0);
> >> + xilinx_timer_write(priv, tlr1, TLR1);
> >> +
> >> + if (state->enabled) {
> >> + /* Only touch the TCSRs if we aren't already running */
> >> + if (!enabled) {
> >> + /* Load TLR into TCR */
> >> + xilinx_timer_write(priv, tcsr0 | TCSR_LOAD, TCSR0);
> >> + xilinx_timer_write(priv, tcsr1 | TCSR_LOAD, TCSR1);
> >> + /* Enable timers all at once with ENALL */
> >> + tcsr0 = (TCSR_PWM_SET & ~TCSR_ENT) | (tcsr0 & TCSR_UDT);
> >> + tcsr1 = TCSR_PWM_SET | TCSR_ENALL | (tcsr1 & TCSR_UDT);
> >> + xilinx_timer_write(priv, tcsr0, TCSR0);
> >> + xilinx_timer_write(priv, tcsr1, TCSR1);
> >> + }
> >> + } else {
> >> + xilinx_timer_write(priv, 0, TCSR0);
> >> + xilinx_timer_write(priv, 0, TCSR1);
> >> + }
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static void xilinx_pwm_get_state(struct pwm_chip *chip,
> >> + struct pwm_device *unused,
> >> + struct pwm_state *state)
> >> +{
> >> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
> >> + u32 tlr0 = xilinx_timer_read(priv, TLR0);
> >> + u32 tlr1 = xilinx_timer_read(priv, TLR1);
> >> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
> >> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
> >> +
> >> + state->period = xilinx_timer_get_period(priv, tlr0, tcsr0);
> >> + state->duty_cycle = xilinx_timer_get_period(priv, tlr1, tcsr1);
> >> + state->enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
> >> + state->polarity = PWM_POLARITY_NORMAL;
> >
> > Are the values returned here sensible if the hardware isn't in PWM mode?
>
> Yes. If the hardware isn't in PWM mode, then state->enabled will be
> false.

Ah right. Good enough.

> >> + else if (pwm_cells)
> >> + return dev_err_probe(dev, -EINVAL, "#pwm-cells must be 0\n");
> >
> > What is the rationale here to not support #pwm-cells = <2>?
>
> Only one PWM is supported. But otherwise there is no particular
> reason.

The usual binding is to have 3 additional parameters.
1) chip-local pwm number (which can only be 0 for a pwmchip having
.npwm = 1)
2) the "typical" period
3) some flags (like PWM_POLARITY_*)

I don't care much if you implement it with or without 1), but 2) and 3)
should IMHO be here. If you don't want 1),
http://patchwork.ozlabs.org/project/linux-pwm/patch/[email protected]/
might be interesting for you. (But note, Thierry didn't give feedback to
this yet, it might be possible he wants 1)-3) for new drivers.)

Best regards
Uwe

--
Pengutronix e.K. | Uwe Kleine-K?nig |
Industrial Linux Solutions | https://www.pengutronix.de/ |


Attachments:
(No filename) (6.41 kB)
signature.asc (499.00 B)
Download all attachments

2021-06-25 17:47:13

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

On 6/25/21 12:56 PM, Uwe Kleine-K?nig wrote:
> Hello Sean,
>
> On Fri, Jun 25, 2021 at 11:13:33AM -0400, Sean Anderson wrote:
>> On 6/25/21 2:19 AM, Uwe Kleine-K?nig wrote:
>> > On Fri, May 28, 2021 at 05:45:22PM -0400, Sean Anderson wrote:
>> >> + * Hardware limitations:
>
> Please make this "* Limitations:" to match what the other drivers do and
> so ease grepping for this info.
>
>> >> + * - When changing both duty cycle and period, we may end up with one cycle
>> >> + * with the old duty cycle and the new period.
>> >
>> > That means it doesn't reset the counter when a new period is set, right?
>>
>> Correct. The only way to write to the counter is to stop the timer and
>> restart it.
>
> ok.
>
>> >> + * - Cannot produce 100% duty cycle.
>> >
>> > Can it produce a 0% duty cycle? Below you're calling
>> > xilinx_timer_tlr_period(..., ..., ..., 0) then which returns -ERANGE.
>>
>> Yes. This is what you get when you try to specify 100% duty cycle (e.g.
>> TLR0 == TLR1).
>
> OK, so the hardware can do it, but your driver doesn't make use of it,
> right?

So to clarify, I have observed the following behavior

* When TCSR == 0, the output is constant low.
* When TLR1 (duty_cycle) >= TLR0 (period), the output is constant low.

It might be possible to achieve constant high output by stopping the
counters during the high time, but leaving the PWM bit set. However, I
do not have a use case for this. I think it might be a nice follow-up
for someone who wants that feature.

That being said, is there a standard way to specify 100% or 0% duty
cycle? It seems like one would have to detect these situations and use a
different code path.

>
>> >> + * - Only produces "normal" output.
>> >
>> > Does the output emit a low level when it's disabled?
>>
>> I believe so.
>
> Is there a possibility to be sure? I'd like to know that to complete my
> picture about the behaviour of the supported PWMs.

See above.

>
>> >> + */
>> >> +
>> >> [...]
>> >> +static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused,
>> >> + const struct pwm_state *state)
>> >> +{
>> >> + int ret;
>> >> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
>> >> + u32 tlr0, tlr1;
>> >> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
>> >> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
>> >> + bool enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
>> >> +
>> >> + if (state->polarity != PWM_POLARITY_NORMAL)
>> >> + return -EINVAL;
>> >> +
>> >> + ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
>> >> + if (ret)
>> >> + return ret;
>> >
>> > The implementation of xilinx_timer_tlr_period (in patch 2/3) returns
>> > -ERANGE for big periods. The good behaviour to implement is to cap to
>> > the biggest period possible in this case.
>>
>> Ok. Is this documented anywhere?
>
> I tried but Thierry didn't like the result and I didn't retry. The
> problem is also that many drivers we already have in the tree don't
> behave like this (because for a long time nobody cared). That new
> drivers should behave this way is my effort to get some consistent
> behaviour.

Do you have a link to the thread? IMO if you would like to specify
behavior like this, is is very helpful to write it down so new authors
don't have to get to v4 before finding out about it ;)

>> And wouldn't this result in the wrong duty cycle? E.g. say the max
>> value is 100 and I try to apply a period of 150 and a duty_cycle of 75
>> (for a 50% duty cycle). If we cap at 100, then I will instead have a
>> 75% duty cycle, and there will be no error.
>
> Yes that is right. That there is no feedback is a problem that we have
> for a long time. I have a prototype patch that implements a
> pwm_round_state() function that lets a consumer know the result of
> applying a certain pwm_state in advance. But we're not there yet.

So for the moment, why not give an error? This will be legal code both
now and after round_state is implemented.

>
>> So I will silently get the wrong duty cycle, even when that duty cycle
>> is probably more important than the period.
>
> It depends on the use case and every policy is wrong for some cases. So
> I picked the policy I already explained because it is a) easy to
> implement for lowlevel drivers and b) it's easy to work with for
> consumers once we have pwm_round_state().

What about sysfs? Right now if you try to specify an inexpressible
period you get an error message. I saw [1], but unfortunately there do
not appear to be any patches associated with it. Do you have plans to
implement such an interface?

[1] https://lore.kernel.org/linux-pwm/CAO1O6seyi+1amAY5YLz0K1dkNd7ewAvot4K1eZMpAAQquz0-9g@mail.gmail.com/

>
>> > Also note that state->period is an u64 but it is casted to unsigned int
>> > as this is the type of the forth parameter of xilinx_timer_tlr_period.
>>
>> Hm, it looks like I immediately cast period to a u64. I will change the
>> signature for this function next revision.
>
> Then note that period * clk_get_rate(priv->clk) might overflow.

Ok, so is mult_frac the correct macro to use here?

>> >> + ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
>> >> + if (ret)
>> >> + return ret;

Perhaps I should add

if (tlr0 <= tlr1)
return -EINVAL;

here to prevent accidentally getting 0% duty cycle.

>> >> +
>> >> + xilinx_timer_write(priv, tlr0, TLR0);
>> >> + xilinx_timer_write(priv, tlr1, TLR1);
>> >> +
>> >> + if (state->enabled) {
>> >> + /* Only touch the TCSRs if we aren't already running */
>> >> + if (!enabled) {
>> >> + /* Load TLR into TCR */
>> >> + xilinx_timer_write(priv, tcsr0 | TCSR_LOAD, TCSR0);
>> >> + xilinx_timer_write(priv, tcsr1 | TCSR_LOAD, TCSR1);
>> >> + /* Enable timers all at once with ENALL */
>> >> + tcsr0 = (TCSR_PWM_SET & ~TCSR_ENT) | (tcsr0 & TCSR_UDT);
>> >> + tcsr1 = TCSR_PWM_SET | TCSR_ENALL | (tcsr1 & TCSR_UDT);
>> >> + xilinx_timer_write(priv, tcsr0, TCSR0);
>> >> + xilinx_timer_write(priv, tcsr1, TCSR1);
>> >> + }
>> >> + } else {
>> >> + xilinx_timer_write(priv, 0, TCSR0);
>> >> + xilinx_timer_write(priv, 0, TCSR1);
>> >> + }
>> >> +
>> >> + return 0;
>> >> +}
>> >> +
>> >> +static void xilinx_pwm_get_state(struct pwm_chip *chip,
>> >> + struct pwm_device *unused,
>> >> + struct pwm_state *state)
>> >> +{
>> >> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
>> >> + u32 tlr0 = xilinx_timer_read(priv, TLR0);
>> >> + u32 tlr1 = xilinx_timer_read(priv, TLR1);
>> >> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
>> >> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
>> >> +
>> >> + state->period = xilinx_timer_get_period(priv, tlr0, tcsr0);
>> >> + state->duty_cycle = xilinx_timer_get_period(priv, tlr1, tcsr1);
>> >> + state->enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
>> >> + state->polarity = PWM_POLARITY_NORMAL;
>> >
>> > Are the values returned here sensible if the hardware isn't in PWM mode?
>>
>> Yes. If the hardware isn't in PWM mode, then state->enabled will be
>> false.
>
> Ah right. Good enough.
>
>> >> + else if (pwm_cells)
>> >> + return dev_err_probe(dev, -EINVAL, "#pwm-cells must be 0\n");
>> >
>> > What is the rationale here to not support #pwm-cells = <2>?
>>
>> Only one PWM is supported. But otherwise there is no particular
>> reason.
>
> The usual binding is to have 3 additional parameters.
> 1) chip-local pwm number (which can only be 0 for a pwmchip having
> .npwm = 1)
> 2) the "typical" period
> 3) some flags (like PWM_POLARITY_*)
>
> I don't care much if you implement it with or without 1), but 2) and 3)
> should IMHO be here. If you don't want 1),
> http://patchwork.ozlabs.org/project/linux-pwm/patch/[email protected]/
> might be interesting for you. (But note, Thierry didn't give feedback to
> this yet, it might be possible he wants 1)-3) for new drivers.)

Ok, so since I let of_pwmchip_add set xlate, I don't need to change
anything in this driver? Then I will remove this check for v5.

--Sean

2021-06-25 17:47:46

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

On 6/25/21 12:56 PM, Uwe Kleine-K?nig wrote:
> Hello Sean,
>
> On Fri, Jun 25, 2021 at 11:13:33AM -0400, Sean Anderson wrote:
>> On 6/25/21 2:19 AM, Uwe Kleine-K?nig wrote:
>> > On Fri, May 28, 2021 at 05:45:22PM -0400, Sean Anderson wrote:
>> >> + * Hardware limitations:
>
> Please make this "* Limitations:" to match what the other drivers do and
> so ease grepping for this info.
>
>> >> + * - When changing both duty cycle and period, we may end up with one cycle
>> >> + * with the old duty cycle and the new period.
>> >
>> > That means it doesn't reset the counter when a new period is set, right?
>>
>> Correct. The only way to write to the counter is to stop the timer and
>> restart it.
>
> ok.
>
>> >> + * - Cannot produce 100% duty cycle.
>> >
>> > Can it produce a 0% duty cycle? Below you're calling
>> > xilinx_timer_tlr_period(..., ..., ..., 0) then which returns -ERANGE.
>>
>> Yes. This is what you get when you try to specify 100% duty cycle (e.g.
>> TLR0 == TLR1).
>
> OK, so the hardware can do it, but your driver doesn't make use of it,
> right?

So to clarify, I have observed the following behavior

* When TCSR == 0, the output is constant low.
* When TLR1 (duty_cycle) >= TLR0 (period), the output is constant low.

It might be possible to achieve constant high output by stopping the
counters during the high time, but leaving the PWM bit set. However, I
do not have a use case for this. I think it might be a nice follow-up
for someone who wants that feature.

That being said, is there a standard way to specify 100% or 0% duty
cycle? It seems like one would have to detect these situations and use a
different code path.

>
>> >> + * - Only produces "normal" output.
>> >
>> > Does the output emit a low level when it's disabled?
>>
>> I believe so.
>
> Is there a possibility to be sure? I'd like to know that to complete my
> picture about the behaviour of the supported PWMs.

See above.

>
>> >> + */
>> >> +
>> >> [...]
>> >> +static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused,
>> >> + const struct pwm_state *state)
>> >> +{
>> >> + int ret;
>> >> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
>> >> + u32 tlr0, tlr1;
>> >> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
>> >> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
>> >> + bool enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
>> >> +
>> >> + if (state->polarity != PWM_POLARITY_NORMAL)
>> >> + return -EINVAL;
>> >> +
>> >> + ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
>> >> + if (ret)
>> >> + return ret;
>> >
>> > The implementation of xilinx_timer_tlr_period (in patch 2/3) returns
>> > -ERANGE for big periods. The good behaviour to implement is to cap to
>> > the biggest period possible in this case.
>>
>> Ok. Is this documented anywhere?
>
> I tried but Thierry didn't like the result and I didn't retry. The
> problem is also that many drivers we already have in the tree don't
> behave like this (because for a long time nobody cared). That new
> drivers should behave this way is my effort to get some consistent
> behaviour.

Do you have a link to the thread? IMO if you would like to specify
behavior like this, is is very helpful to write it down so new authors
don't have to get to v4 before finding out about it ;)

>> And wouldn't this result in the wrong duty cycle? E.g. say the max
>> value is 100 and I try to apply a period of 150 and a duty_cycle of 75
>> (for a 50% duty cycle). If we cap at 100, then I will instead have a
>> 75% duty cycle, and there will be no error.
>
> Yes that is right. That there is no feedback is a problem that we have
> for a long time. I have a prototype patch that implements a
> pwm_round_state() function that lets a consumer know the result of
> applying a certain pwm_state in advance. But we're not there yet.

So for the moment, why not give an error? This will be legal code both
now and after round_state is implemented.

>
>> So I will silently get the wrong duty cycle, even when that duty cycle
>> is probably more important than the period.
>
> It depends on the use case and every policy is wrong for some cases. So
> I picked the policy I already explained because it is a) easy to
> implement for lowlevel drivers and b) it's easy to work with for
> consumers once we have pwm_round_state().

What about sysfs? Right now if you try to specify an inexpressible
period you get an error message. I saw [1], but unfortunately there do
not appear to be any patches associated with it. Do you have plans to
implement such an interface?

[1] https://lore.kernel.org/linux-pwm/CAO1O6seyi+1amAY5YLz0K1dkNd7ewAvot4K1eZMpAAQquz0-9g@mail.gmail.com/

>
>> > Also note that state->period is an u64 but it is casted to unsigned int
>> > as this is the type of the forth parameter of xilinx_timer_tlr_period.
>>
>> Hm, it looks like I immediately cast period to a u64. I will change the
>> signature for this function next revision.
>
> Then note that period * clk_get_rate(priv->clk) might overflow.

Ok, so is mult_frac the correct macro to use here?

>> >> + ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
>> >> + if (ret)
>> >> + return ret;

Perhaps I should add

if (tlr0 <= tlr1)
return -EINVAL;

here to prevent accidentally getting 0% duty cycle.

>> >> +
>> >> + xilinx_timer_write(priv, tlr0, TLR0);
>> >> + xilinx_timer_write(priv, tlr1, TLR1);
>> >> +
>> >> + if (state->enabled) {
>> >> + /* Only touch the TCSRs if we aren't already running */
>> >> + if (!enabled) {
>> >> + /* Load TLR into TCR */
>> >> + xilinx_timer_write(priv, tcsr0 | TCSR_LOAD, TCSR0);
>> >> + xilinx_timer_write(priv, tcsr1 | TCSR_LOAD, TCSR1);
>> >> + /* Enable timers all at once with ENALL */
>> >> + tcsr0 = (TCSR_PWM_SET & ~TCSR_ENT) | (tcsr0 & TCSR_UDT);
>> >> + tcsr1 = TCSR_PWM_SET | TCSR_ENALL | (tcsr1 & TCSR_UDT);
>> >> + xilinx_timer_write(priv, tcsr0, TCSR0);
>> >> + xilinx_timer_write(priv, tcsr1, TCSR1);
>> >> + }
>> >> + } else {
>> >> + xilinx_timer_write(priv, 0, TCSR0);
>> >> + xilinx_timer_write(priv, 0, TCSR1);
>> >> + }
>> >> +
>> >> + return 0;
>> >> +}
>> >> +
>> >> +static void xilinx_pwm_get_state(struct pwm_chip *chip,
>> >> + struct pwm_device *unused,
>> >> + struct pwm_state *state)
>> >> +{
>> >> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
>> >> + u32 tlr0 = xilinx_timer_read(priv, TLR0);
>> >> + u32 tlr1 = xilinx_timer_read(priv, TLR1);
>> >> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
>> >> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
>> >> +
>> >> + state->period = xilinx_timer_get_period(priv, tlr0, tcsr0);
>> >> + state->duty_cycle = xilinx_timer_get_period(priv, tlr1, tcsr1);
>> >> + state->enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
>> >> + state->polarity = PWM_POLARITY_NORMAL;
>> >
>> > Are the values returned here sensible if the hardware isn't in PWM mode?
>>
>> Yes. If the hardware isn't in PWM mode, then state->enabled will be
>> false.
>
> Ah right. Good enough.
>
>> >> + else if (pwm_cells)
>> >> + return dev_err_probe(dev, -EINVAL, "#pwm-cells must be 0\n");
>> >
>> > What is the rationale here to not support #pwm-cells = <2>?
>>
>> Only one PWM is supported. But otherwise there is no particular
>> reason.
>
> The usual binding is to have 3 additional parameters.
> 1) chip-local pwm number (which can only be 0 for a pwmchip having
> .npwm = 1)
> 2) the "typical" period
> 3) some flags (like PWM_POLARITY_*)
>
> I don't care much if you implement it with or without 1), but 2) and 3)
> should IMHO be here. If you don't want 1),
> http://patchwork.ozlabs.org/project/linux-pwm/patch/[email protected]/
> might be interesting for you. (But note, Thierry didn't give feedback to
> this yet, it might be possible he wants 1)-3) for new drivers.)

Ok, so since I let of_pwmchip_add set xlate, I don't need to change
anything in this driver? Then I will remove this check for v5.

--Sean

2021-06-27 18:24:55

by Uwe Kleine-König

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

Hello Sean,

On Fri, Jun 25, 2021 at 01:46:26PM -0400, Sean Anderson wrote:
> On 6/25/21 12:56 PM, Uwe Kleine-K?nig wrote:
> > On Fri, Jun 25, 2021 at 11:13:33AM -0400, Sean Anderson wrote:
> > > On 6/25/21 2:19 AM, Uwe Kleine-K?nig wrote:
> > > > On Fri, May 28, 2021 at 05:45:22PM -0400, Sean Anderson wrote:
> > > >> + * - Cannot produce 100% duty cycle.
> > > >
> > > > Can it produce a 0% duty cycle? Below you're calling
> > > > xilinx_timer_tlr_period(..., ..., ..., 0) then which returns -ERANGE.
> > >
> > > Yes. This is what you get when you try to specify 100% duty cycle (e.g.
> > > TLR0 == TLR1).
> >
> > OK, so the hardware can do it, but your driver doesn't make use of it,
> > right?
>
> So to clarify, I have observed the following behavior
>
> * When TCSR == 0, the output is constant low.
> * When TLR1 (duty_cycle) >= TLR0 (period), the output is constant low.
>
> It might be possible to achieve constant high output by stopping the
> counters during the high time, but leaving the PWM bit set. However, I
> do not have a use case for this. I think it might be a nice follow-up
> for someone who wants that feature.

A typical use case is having an LED or a backlight connected to the PWM
and and want it to be fully on.

> That being said, is there a standard way to specify 100% or 0% duty
> cycle? It seems like one would have to detect these situations and use a
> different code path.

I don't understand that question. If pwm_apply_state is called with a
state having .duty_cycle = 0 (and .enabled = true) you're supposed to
emit a 0% relative duty. If .duty_cycle = .period is passed you're
supposed to emit a 100% relative duty.

> > > >> + * - Only produces "normal" output.
> > > >
> > > > Does the output emit a low level when it's disabled?
> > >
> > > I believe so.
> >
> > Is there a possibility to be sure? I'd like to know that to complete my
> > picture about the behaviour of the supported PWMs.
>
> See above.
>
> > > >> +static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused,
> > > >> + const struct pwm_state *state)
> > > >> +{
> > > >> + int ret;
> > > >> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
> > > >> + u32 tlr0, tlr1;
> > > >> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
> > > >> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
> > > >> + bool enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
> > > >> +
> > > >> + if (state->polarity != PWM_POLARITY_NORMAL)
> > > >> + return -EINVAL;
> > > >> +
> > > >> + ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
> > > >> + if (ret)
> > > >> + return ret;
> > > >
> > > > The implementation of xilinx_timer_tlr_period (in patch 2/3) returns
> > > > -ERANGE for big periods. The good behaviour to implement is to cap to
> > > > the biggest period possible in this case.
> > >
> > > Ok. Is this documented anywhere?
> >
> > I tried but Thierry didn't like the result and I didn't retry. The
> > problem is also that many drivers we already have in the tree don't
> > behave like this (because for a long time nobody cared). That new
> > drivers should behave this way is my effort to get some consistent
> > behaviour.
>
> Do you have a link to the thread? IMO if you would like to specify
> behavior like this, is is very helpful to write it down so new authors
> don't have to get to v4 before finding out about it ;)

I misremembered, the last time I wanted to improve the documentation I
didn't write anything about the policy with the goal to improve the
documentation without hitting the a bit controversial policy stuff. The
thread is available at

https://lore.kernel.org/r/[email protected]

. Thierry didn't reply to this thread yet.

My intension was to build on this one and formulate the expected policy
for new drivers.

The situation I'm in here wanting to install this policy is: On one hand
Thierry argues that consumers don't care much about how .apply rounds
because most consumers just don't have so exact requirements. And on the
other hand he doesn't want to change the existing behaviour for already
existing drivers to not break consumers. So the best I can currently to
do work on a more consistent behaviour is to enforce this for new
drivers.

> > > And wouldn't this result in the wrong duty cycle? E.g. say the max
> > > value is 100 and I try to apply a period of 150 and a duty_cycle of 75
> > > (for a 50% duty cycle). If we cap at 100, then I will instead have a
> > > 75% duty cycle, and there will be no error.
> >
> > Yes that is right. That there is no feedback is a problem that we have
> > for a long time. I have a prototype patch that implements a
> > pwm_round_state() function that lets a consumer know the result of
> > applying a certain pwm_state in advance. But we're not there yet.
>
> So for the moment, why not give an error? This will be legal code both
> now and after round_state is implemented.

The problem is where to draw the line. To stay with your example: If a
request for period = 150 ns comes in, and let X be the biggest period <=
150 ns that the hardware can configure. For which values of X should an
error be returned and for which values the setting should be
implemented.

In my eyes the only sensible thing to implement here is to tell the
consumer about X and let it decide if it's good enough. If you have a
better idea let me hear about it.

> > > So I will silently get the wrong duty cycle, even when that duty cycle
> > > is probably more important than the period.
> >
> > It depends on the use case and every policy is wrong for some cases. So
> > I picked the policy I already explained because it is a) easy to
> > implement for lowlevel drivers and b) it's easy to work with for
> > consumers once we have pwm_round_state().
>
> What about sysfs? Right now if you try to specify an inexpressible
> period you get an error message. I saw [1], but unfortunately there do
> not appear to be any patches associated with it. Do you have plans to
> implement such an interface?
>
> [1] https://lore.kernel.org/linux-pwm/CAO1O6seyi+1amAY5YLz0K1dkNd7ewAvot4K1eZMpAAQquz0-9g@mail.gmail.com/

In my eyes we better implement a pwmctl interface that doesn't work via
sysfs but using an ioctl; similar what gpio did with gpioctl for various
reasons.

> > > > Also note that state->period is an u64 but it is casted to unsigned int
> > > > as this is the type of the forth parameter of xilinx_timer_tlr_period.
> > >
> > > Hm, it looks like I immediately cast period to a u64. I will change the
> > > signature for this function next revision.
> >
> > Then note that period * clk_get_rate(priv->clk) might overflow.
>
> Ok, so is mult_frac the correct macro to use here?
>
> > > >> + ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
> > > >> + if (ret)
> > > >> + return ret;
>
> Perhaps I should add
>
> if (tlr0 <= tlr1)
> return -EINVAL;
>
> here to prevent accidentally getting 0% duty cycle.

You can assume that duty_cycle <= period when .apply is called.

> > > >> + else if (pwm_cells)
> > > >> + return dev_err_probe(dev, -EINVAL, "#pwm-cells must be 0\n");
> > > >
> > > > What is the rationale here to not support #pwm-cells = <2>?
> > >
> > > Only one PWM is supported. But otherwise there is no particular
> > > reason.
> >
> > The usual binding is to have 3 additional parameters.
> > 1) chip-local pwm number (which can only be 0 for a pwmchip having
> > .npwm = 1)
> > 2) the "typical" period
> > 3) some flags (like PWM_POLARITY_*)
> >
> > I don't care much if you implement it with or without 1), but 2) and 3)
> > should IMHO be here. If you don't want 1),
> > http://patchwork.ozlabs.org/project/linux-pwm/patch/[email protected]/
> > might be interesting for you. (But note, Thierry didn't give feedback to
> > this yet, it might be possible he wants 1)-3) for new drivers.)
>
> Ok, so since I let of_pwmchip_add set xlate, I don't need to change
> anything in this driver? Then I will remove this check for v5.

Yes.

Best regards
Uwe

--
Pengutronix e.K. | Uwe Kleine-K?nig |
Industrial Linux Solutions | https://www.pengutronix.de/ |


Attachments:
(No filename) (8.28 kB)
signature.asc (499.00 B)
Download all attachments

2021-06-28 23:37:34

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer



On 6/27/21 2:19 PM, Uwe Kleine-K?nig wrote:
> Hello Sean,
>
> On Fri, Jun 25, 2021 at 01:46:26PM -0400, Sean Anderson wrote:
>> On 6/25/21 12:56 PM, Uwe Kleine-K?nig wrote:
>> > On Fri, Jun 25, 2021 at 11:13:33AM -0400, Sean Anderson wrote:
>> > > On 6/25/21 2:19 AM, Uwe Kleine-K?nig wrote:
>> > > > On Fri, May 28, 2021 at 05:45:22PM -0400, Sean Anderson wrote:
>> > > >> + * - Cannot produce 100% duty cycle.
>> > > >
>> > > > Can it produce a 0% duty cycle? Below you're calling
>> > > > xilinx_timer_tlr_period(..., ..., ..., 0) then which returns -ERANGE.
>> > >
>> > > Yes. This is what you get when you try to specify 100% duty cycle (e.g.
>> > > TLR0 == TLR1).
>> >
>> > OK, so the hardware can do it, but your driver doesn't make use of it,
>> > right?
>>
>> So to clarify, I have observed the following behavior
>>
>> * When TCSR == 0, the output is constant low.
>> * When TLR1 (duty_cycle) >= TLR0 (period), the output is constant low.
>>
>> It might be possible to achieve constant high output by stopping the
>> counters during the high time, but leaving the PWM bit set. However, I
>> do not have a use case for this. I think it might be a nice follow-up
>> for someone who wants that feature.
>
> A typical use case is having an LED or a backlight connected to the PWM
> and and want it to be fully on.

I mean that I personally do not need this feature for my current
project, though I could see how someone might want this.

>
>> That being said, is there a standard way to specify 100% or 0% duty
>> cycle? It seems like one would have to detect these situations and use a
>> different code path.
>
> I don't understand that question. If pwm_apply_state is called with a
> state having .duty_cycle = 0 (and .enabled = true) you're supposed to
> emit a 0% relative duty. If .duty_cycle = .period is passed you're
> supposed to emit a 100% relative duty.

Ok.

>
>> > > >> + * - Only produces "normal" output.
>> > > >
>> > > > Does the output emit a low level when it's disabled?
>> > >
>> > > I believe so.
>> >
>> > Is there a possibility to be sure? I'd like to know that to complete my
>> > picture about the behaviour of the supported PWMs.
>>
>> See above.
>>
>> > > >> +static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused,
>> > > >> + const struct pwm_state *state)
>> > > >> +{
>> > > >> + int ret;
>> > > >> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
>> > > >> + u32 tlr0, tlr1;
>> > > >> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
>> > > >> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
>> > > >> + bool enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
>> > > >> +
>> > > >> + if (state->polarity != PWM_POLARITY_NORMAL)
>> > > >> + return -EINVAL;
>> > > >> +
>> > > >> + ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
>> > > >> + if (ret)
>> > > >> + return ret;
>> > > >
>> > > > The implementation of xilinx_timer_tlr_period (in patch 2/3) returns
>> > > > -ERANGE for big periods. The good behaviour to implement is to cap to
>> > > > the biggest period possible in this case.
>> > >
>> > > Ok. Is this documented anywhere?
>> >
>> > I tried but Thierry didn't like the result and I didn't retry. The
>> > problem is also that many drivers we already have in the tree don't
>> > behave like this (because for a long time nobody cared). That new
>> > drivers should behave this way is my effort to get some consistent
>> > behaviour.
>>
>> Do you have a link to the thread? IMO if you would like to specify
>> behavior like this, is is very helpful to write it down so new authors
>> don't have to get to v4 before finding out about it ;)
>
> I misremembered, the last time I wanted to improve the documentation I
> didn't write anything about the policy with the goal to improve the
> documentation without hitting the a bit controversial policy stuff. The
> thread is available at
>
> https://lore.kernel.org/r/[email protected]

This link does not work, but I was able to find the patch at

https://patchwork.ozlabs.org/project/linux-pwm/patch/[email protected]/

However, it has no mention of rounding the rate, or what to do if a
given configuration cannot be applied.

> . Thierry didn't reply to this thread yet.

It seems the patch is marked as "Rejected" which is odd with no
feedback. Perhaps you should resend.

>
> My intension was to build on this one and formulate the expected policy
> for new drivers.
>
> The situation I'm in here wanting to install this policy is: On one hand
> Thierry argues that consumers don't care much about how .apply rounds
> because most consumers just don't have so exact requirements. And on the
> other hand he doesn't want to change the existing behaviour for already
> existing drivers to not break consumers. So the best I can currently to
> do work on a more consistent behaviour is to enforce this for new
> drivers.
>
>> > > And wouldn't this result in the wrong duty cycle? E.g. say the max
>> > > value is 100 and I try to apply a period of 150 and a duty_cycle of 75
>> > > (for a 50% duty cycle). If we cap at 100, then I will instead have a
>> > > 75% duty cycle, and there will be no error.
>> >
>> > Yes that is right. That there is no feedback is a problem that we have
>> > for a long time. I have a prototype patch that implements a
>> > pwm_round_state() function that lets a consumer know the result of
>> > applying a certain pwm_state in advance. But we're not there yet.
>>
>> So for the moment, why not give an error? This will be legal code both
>> now and after round_state is implemented.
>
> The problem is where to draw the line. To stay with your example: If a
> request for period = 150 ns comes in, and let X be the biggest period <=
> 150 ns that the hardware can configure. For which values of X should an
> error be returned and for which values the setting should be
> implemented.
>
> In my eyes the only sensible thing to implement here is to tell the
> consumer about X and let it decide if it's good enough. If you have a
> better idea let me hear about it.

Sure. And I think it's ok to tell the consumer that X is the best we can
do. But if they go along and request an unconfigurable state anyway, we
should tell them as much. IMO, this is the best way to prevent surprising
results in the API.

The real issue here is that it is impossible to determine the correct
way to round the PWM a priori, and in particular, without considering
both duty_cycle and period. If a consumer requests very small
period/duty cycle which we cannot produce, how should it be rounded?
Should we just set TLR0=1 and TLR1=0 to give them 66% duty cycle with
the least period? Or should we try and increase the period to better
approximate the % duty cycle? And both of these decisions must be made
knowing both parameters. We cannot (for example) just always round up,
since we may produce a configuration with TLR0 == TLR1, which would
produce 0% duty cycle instead of whatever was requested. Rounding rate
will introduce significant complexity into the driver. Most of the time
if a consumer requests an invalid rate, it is due to misconfiguration
which is best solved by fixing the configuration.

>> > > So I will silently get the wrong duty cycle, even when that duty cycle
>> > > is probably more important than the period.
>> >
>> > It depends on the use case and every policy is wrong for some cases. So
>> > I picked the policy I already explained because it is a) easy to
>> > implement for lowlevel drivers and b) it's easy to work with for
>> > consumers once we have pwm_round_state().
>>
>> What about sysfs? Right now if you try to specify an inexpressible
>> period you get an error message. I saw [1], but unfortunately there do
>> not appear to be any patches associated with it. Do you have plans to
>> implement such an interface?
>>
>> [1] https://lore.kernel.org/linux-pwm/CAO1O6seyi+1amAY5YLz0K1dkNd7ewAvot4K1eZMpAAQquz0-9g@mail.gmail.com/
>
> In my eyes we better implement a pwmctl interface that doesn't work via
> sysfs but using an ioctl; similar what gpio did with gpioctl for various
> reasons.
>
>> > > > Also note that state->period is an u64 but it is casted to unsigned int
>> > > > as this is the type of the forth parameter of xilinx_timer_tlr_period.
>> > >
>> > > Hm, it looks like I immediately cast period to a u64. I will change the
>> > > signature for this function next revision.
>> >
>> > Then note that period * clk_get_rate(priv->clk) might overflow.
>>
>> Ok, so is mult_frac the correct macro to use here?
>>
>> > > >> + ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
>> > > >> + if (ret)
>> > > >> + return ret;
>>
>> Perhaps I should add
>>
>> if (tlr0 <= tlr1)
>> return -EINVAL;
>>
>> here to prevent accidentally getting 0% duty cycle.
>
> You can assume that duty_cycle <= period when .apply is called.

Ok, I will only check for == then.

--Sean

2021-06-28 23:38:07

by Uwe Kleine-König

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

Hello Sean,

On Mon, Jun 28, 2021 at 11:50:33AM -0400, Sean Anderson wrote:
> On 6/27/21 2:19 PM, Uwe Kleine-K?nig wrote:
> > On Fri, Jun 25, 2021 at 01:46:26PM -0400, Sean Anderson wrote:
> > > So for the moment, why not give an error? This will be legal code both
> > > now and after round_state is implemented.
> >
> > The problem is where to draw the line. To stay with your example: If a
> > request for period = 150 ns comes in, and let X be the biggest period <=
> > 150 ns that the hardware can configure. For which values of X should an
> > error be returned and for which values the setting should be
> > implemented.
> >
> > In my eyes the only sensible thing to implement here is to tell the
> > consumer about X and let it decide if it's good enough. If you have a
> > better idea let me hear about it.
>
> Sure. And I think it's ok to tell the consumer that X is the best we can
> do. But if they go along and request an unconfigurable state anyway, we
> should tell them as much.

I have the impression you didn't understand where I see the problem. If
you request 150 ns and the controller can only do 149 ns (or 149.6667 ns)
should we refuse? If yes: This is very unusable, e.g. the led-pwm driver
expects that it can configure the duty_cycle in 1/256 steps of the
period, and then maybe only steps 27 and 213 of the 256 possible steps
work. (This example doesn't really match because the led-pwm driver
varies duty_cycle and not period, but the principle becomes clear I
assume.) If no: Should we accept 151 ns? Isn't that ridiculous?

> IMO, this is the best way to prevent surprising results in the API.

I think it's not possible in practise to refuse "near" misses and every
definition of "near" is in some case ridiculous. Also if you consider
the pwm_round_state() case you don't want to refuse any request to tell
as much as possible about your controller's capabilities. And then it's
straight forward to let apply behave in the same way to keep complexity
low.

> The real issue here is that it is impossible to determine the correct
> way to round the PWM a priori, and in particular, without considering
> both duty_cycle and period. If a consumer requests very small
> period/duty cycle which we cannot produce, how should it be rounded?

Yeah, because there is no obviously right one, I picked one that is as
wrong as the other possibilities but is easy to work with.

> Should we just set TLR0=1 and TLR1=0 to give them 66% duty cycle with
> the least period? Or should we try and increase the period to better
> approximate the % duty cycle? And both of these decisions must be made
> knowing both parameters. We cannot (for example) just always round up,
> since we may produce a configuration with TLR0 == TLR1, which would
> produce 0% duty cycle instead of whatever was requested. Rounding rate
> will introduce significant complexity into the driver. Most of the time
> if a consumer requests an invalid rate, it is due to misconfiguration
> which is best solved by fixing the configuration.

In the first step pick the biggest period not bigger than the requested
and then pick the biggest duty cycle that is not bigger than the
requested and that can be set with the just picked period. That is the
behaviour that all new drivers should do. This is somewhat arbitrary but
after quite some thought the most sensible in my eyes.

> > > Perhaps I should add
> > >
> > > if (tlr0 <= tlr1)
> > > return -EINVAL;
> > >
> > > here to prevent accidentally getting 0% duty cycle.
> >
> > You can assume that duty_cycle <= period when .apply is called.
>
> Ok, I will only check for == then.

You just have to pay attention to the case that you had to decrement
.period to the next possible value. Then .duty_cycle might be bigger
than the corrected period.

Best regards
Uwe

--
Pengutronix e.K. | Uwe Kleine-K?nig |
Industrial Linux Solutions | https://www.pengutronix.de/ |


Attachments:
(No filename) (3.96 kB)
signature.asc (499.00 B)
Download all attachments

2021-06-28 23:38:14

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer



On 6/28/21 12:24 PM, Uwe Kleine-K?nig wrote:
> Hello Sean,
>
> On Mon, Jun 28, 2021 at 11:50:33AM -0400, Sean Anderson wrote:
>> On 6/27/21 2:19 PM, Uwe Kleine-K?nig wrote:
>> > On Fri, Jun 25, 2021 at 01:46:26PM -0400, Sean Anderson wrote:
>> > > So for the moment, why not give an error? This will be legal code both
>> > > now and after round_state is implemented.
>> >
>> > The problem is where to draw the line. To stay with your example: If a
>> > request for period = 150 ns comes in, and let X be the biggest period <=
>> > 150 ns that the hardware can configure. For which values of X should an
>> > error be returned and for which values the setting should be
>> > implemented.
>> >
>> > In my eyes the only sensible thing to implement here is to tell the
>> > consumer about X and let it decide if it's good enough. If you have a
>> > better idea let me hear about it.
>>
>> Sure. And I think it's ok to tell the consumer that X is the best we can
>> do. But if they go along and request an unconfigurable state anyway, we
>> should tell them as much.
>
> I have the impression you didn't understand where I see the problem. If
> you request 150 ns and the controller can only do 149 ns (or 149.6667 ns)
> should we refuse? If yes: This is very unusable, e.g. the led-pwm driver
> expects that it can configure the duty_cycle in 1/256 steps of the
> period, and then maybe only steps 27 and 213 of the 256 possible steps
> work. (This example doesn't really match because the led-pwm driver
> varies duty_cycle and not period, but the principle becomes clear I
> assume.) If no: Should we accept 151 ns? Isn't that ridiculous?

I am fine with this sort of rounding. The part I take issue with is when
the consumer requests (e.g.) a 10ns period, but the best we can do is
20ns. Or at the other end if they request a 4s period but the best we
can do is 2s. Here, there is no obvious way to round it, so I think we
should just say "come back with a reasonable period" and let whoever
wrote the device tree pick a better period.

>> IMO, this is the best way to prevent surprising results in the API.
>
> I think it's not possible in practise to refuse "near" misses and every
> definition of "near" is in some case ridiculous. Also if you consider
> the pwm_round_state() case you don't want to refuse any request to tell
> as much as possible about your controller's capabilities. And then it's
> straight forward to let apply behave in the same way to keep complexity
> low.
>
>> The real issue here is that it is impossible to determine the correct
>> way to round the PWM a priori, and in particular, without considering
>> both duty_cycle and period. If a consumer requests very small
>> period/duty cycle which we cannot produce, how should it be rounded?
>
> Yeah, because there is no obviously right one, I picked one that is as
> wrong as the other possibilities but is easy to work with.
>
>> Should we just set TLR0=1 and TLR1=0 to give them 66% duty cycle with
>> the least period? Or should we try and increase the period to better
>> approximate the % duty cycle? And both of these decisions must be made
>> knowing both parameters. We cannot (for example) just always round up,
>> since we may produce a configuration with TLR0 == TLR1, which would
>> produce 0% duty cycle instead of whatever was requested. Rounding rate
>> will introduce significant complexity into the driver. Most of the time
>> if a consumer requests an invalid rate, it is due to misconfiguration
>> which is best solved by fixing the configuration.
>
> In the first step pick the biggest period not bigger than the requested
> and then pick the biggest duty cycle that is not bigger than the
> requested and that can be set with the just picked period. That is the
> behaviour that all new drivers should do. This is somewhat arbitrary but
> after quite some thought the most sensible in my eyes.

And if there are no periods smaller than the requested period?

Any way you slice it, there will be situations where there is nothing
reasonable to do other than return an error.

>> > > Perhaps I should add
>> > >
>> > > if (tlr0 <= tlr1)
>> > > return -EINVAL;
>> > >
>> > > here to prevent accidentally getting 0% duty cycle.
>> >
>> > You can assume that duty_cycle <= period when .apply is called.
>>
>> Ok, I will only check for == then.
>
> You just have to pay attention to the case that you had to decrement
> .period to the next possible value. Then .duty_cycle might be bigger
> than the corrected period.

This is specifically to prevent 100% duty cycle from turning into 0%. My
current draft is

/*
* If TLR0 == TLR1, then we will produce 0% duty cycle instead of 100%
* duty cycle. Try and reduce the high time to compensate. If we can't
* do that because the high time is already 0 cycles, then just error
* out.
*/
if (tlr0 == tlr1 && !tlr1--)
return -EINVAL;

--Sean

2021-06-28 23:40:34

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer



On 6/28/21 1:20 PM, Uwe Kleine-K?nig wrote:
> Hello Sean,
>
> On Mon, Jun 28, 2021 at 12:35:19PM -0400, Sean Anderson wrote:
>> On 6/28/21 12:24 PM, Uwe Kleine-K?nig wrote:
>> > On Mon, Jun 28, 2021 at 11:50:33AM -0400, Sean Anderson wrote:
>> > > On 6/27/21 2:19 PM, Uwe Kleine-K?nig wrote:
>> > > > On Fri, Jun 25, 2021 at 01:46:26PM -0400, Sean Anderson wrote:
>> > > > > So for the moment, why not give an error? This will be legal code both
>> > > > > now and after round_state is implemented.
>> > > >
>> > > > The problem is where to draw the line. To stay with your example: If a
>> > > > request for period = 150 ns comes in, and let X be the biggest period <=
>> > > > 150 ns that the hardware can configure. For which values of X should an
>> > > > error be returned and for which values the setting should be
>> > > > implemented.
>> > > >
>> > > > In my eyes the only sensible thing to implement here is to tell the
>> > > > consumer about X and let it decide if it's good enough. If you have a
>> > > > better idea let me hear about it.
>> > >
>> > > Sure. And I think it's ok to tell the consumer that X is the best we can
>> > > do. But if they go along and request an unconfigurable state anyway, we
>> > > should tell them as much.
>> >
>> > I have the impression you didn't understand where I see the problem. If
>> > you request 150 ns and the controller can only do 149 ns (or 149.6667 ns)
>> > should we refuse? If yes: This is very unusable, e.g. the led-pwm driver
>> > expects that it can configure the duty_cycle in 1/256 steps of the
>> > period, and then maybe only steps 27 and 213 of the 256 possible steps
>> > work. (This example doesn't really match because the led-pwm driver
>> > varies duty_cycle and not period, but the principle becomes clear I
>> > assume.) If no: Should we accept 151 ns? Isn't that ridiculous?
>>
>> I am fine with this sort of rounding. The part I take issue with is when
>> the consumer requests (e.g.) a 10ns period, but the best we can do is
>> 20ns. Or at the other end if they request a 4s period but the best we
>> can do is 2s. Here, there is no obvious way to round it, so I think we
>> should just say "come back with a reasonable period" and let whoever
>> wrote the device tree pick a better period.
>
> Note that giving ridiculus examples is easy, but this doesn't help to
> actually implement something sensible. Please tell us for your example
> where the driver can only implement 20 ns what is the smallest requested
> period the driver should accept.

20ns :)

In the case of this device, that would result in 0% duty cycle with a
100MHz input. So the smallest reasonable period is 30ns with a duty
cycle of 20ns.

>
>> > > IMO, this is the best way to prevent surprising results in the API.
>> >
>> > I think it's not possible in practise to refuse "near" misses and every
>> > definition of "near" is in some case ridiculous. Also if you consider
>> > the pwm_round_state() case you don't want to refuse any request to tell
>> > as much as possible about your controller's capabilities. And then it's
>> > straight forward to let apply behave in the same way to keep complexity
>> > low.
>> >
>> > > The real issue here is that it is impossible to determine the correct
>> > > way to round the PWM a priori, and in particular, without considering
>> > > both duty_cycle and period. If a consumer requests very small
>> > > period/duty cycle which we cannot produce, how should it be rounded?
>> >
>> > Yeah, because there is no obviously right one, I picked one that is as
>> > wrong as the other possibilities but is easy to work with.
>> >
>> > > Should we just set TLR0=1 and TLR1=0 to give them 66% duty cycle with
>> > > the least period? Or should we try and increase the period to better
>> > > approximate the % duty cycle? And both of these decisions must be made
>> > > knowing both parameters. We cannot (for example) just always round up,
>> > > since we may produce a configuration with TLR0 == TLR1, which would
>> > > produce 0% duty cycle instead of whatever was requested. Rounding rate
>> > > will introduce significant complexity into the driver. Most of the time
>> > > if a consumer requests an invalid rate, it is due to misconfiguration
>> > > which is best solved by fixing the configuration.
>> >
>> > In the first step pick the biggest period not bigger than the requested
>> > and then pick the biggest duty cycle that is not bigger than the
>> > requested and that can be set with the just picked period. That is the
>> > behaviour that all new drivers should do. This is somewhat arbitrary but
>> > after quite some thought the most sensible in my eyes.
>>
>> And if there are no periods smaller than the requested period?
>
> Then return -ERANGE.

Ok, so instead of

if (cycles < 2 || cycles > priv->max + 2)
return -ERANGE;

you would prefer

if (cycles < 2)
return -ERANGE;
else if (cycles > priv->max + 2)
cycles = priv->max;

But if we do the above clamping for TLR0, then we have to recalculate
the duty cycle for TLR1. Which I guess means doing something like

ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
if (ret)
return ret;

state->duty_cycle = mult_frac(state->duty_cycle,
xilinx_timer_get_period(priv, tlr0, tcsr0),
state->period);

ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
if (ret)
return ret;



>
>> Any way you slice it, there will be situations where there is nothing
>> reasonable to do other than return an error.
>
> ack.
>
>> > > > > Perhaps I should add
>> > > > >
>> > > > > if (tlr0 <= tlr1)
>> > > > > return -EINVAL;
>> > > > >
>> > > > > here to prevent accidentally getting 0% duty cycle.
>> > > >
>> > > > You can assume that duty_cycle <= period when .apply is called.
>> > >
>> > > Ok, I will only check for == then.
>> >
>> > You just have to pay attention to the case that you had to decrement
>> > .period to the next possible value. Then .duty_cycle might be bigger
>> > than the corrected period.
>>
>> This is specifically to prevent 100% duty cycle from turning into 0%. My
>> current draft is
>>
>> /*
>> * If TLR0 == TLR1, then we will produce 0% duty cycle instead of 100%
>> * duty cycle. Try and reduce the high time to compensate. If we can't
>> * do that because the high time is already 0 cycles, then just error
>> * out.
>> */
>> if (tlr0 == tlr1 && !tlr1--)
>> return -EINVAL;
>
> If you follow my suggested policy this isn't an error and you should
> yield the biggest duty_cycle here even if it is zero.

So like this?

if (tlr0 == tlr1) {
if (tlr1)
tlr1--;
else if (tlr0 != priv->max)
tlr0++;
else
return -ERANGE;
}

And I would really appreciate if you could write up some documentation
with common errors and how to handle them. It's not at all obvious to me
what all the implications of the above guidelines are.

--Sean

2021-06-29 00:00:42

by Uwe Kleine-König

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

Hello Sean,

On Mon, Jun 28, 2021 at 12:35:19PM -0400, Sean Anderson wrote:
> On 6/28/21 12:24 PM, Uwe Kleine-K?nig wrote:
> > On Mon, Jun 28, 2021 at 11:50:33AM -0400, Sean Anderson wrote:
> > > On 6/27/21 2:19 PM, Uwe Kleine-K?nig wrote:
> > > > On Fri, Jun 25, 2021 at 01:46:26PM -0400, Sean Anderson wrote:
> > > > > So for the moment, why not give an error? This will be legal code both
> > > > > now and after round_state is implemented.
> > > >
> > > > The problem is where to draw the line. To stay with your example: If a
> > > > request for period = 150 ns comes in, and let X be the biggest period <=
> > > > 150 ns that the hardware can configure. For which values of X should an
> > > > error be returned and for which values the setting should be
> > > > implemented.
> > > >
> > > > In my eyes the only sensible thing to implement here is to tell the
> > > > consumer about X and let it decide if it's good enough. If you have a
> > > > better idea let me hear about it.
> > >
> > > Sure. And I think it's ok to tell the consumer that X is the best we can
> > > do. But if they go along and request an unconfigurable state anyway, we
> > > should tell them as much.
> >
> > I have the impression you didn't understand where I see the problem. If
> > you request 150 ns and the controller can only do 149 ns (or 149.6667 ns)
> > should we refuse? If yes: This is very unusable, e.g. the led-pwm driver
> > expects that it can configure the duty_cycle in 1/256 steps of the
> > period, and then maybe only steps 27 and 213 of the 256 possible steps
> > work. (This example doesn't really match because the led-pwm driver
> > varies duty_cycle and not period, but the principle becomes clear I
> > assume.) If no: Should we accept 151 ns? Isn't that ridiculous?
>
> I am fine with this sort of rounding. The part I take issue with is when
> the consumer requests (e.g.) a 10ns period, but the best we can do is
> 20ns. Or at the other end if they request a 4s period but the best we
> can do is 2s. Here, there is no obvious way to round it, so I think we
> should just say "come back with a reasonable period" and let whoever
> wrote the device tree pick a better period.

Note that giving ridiculus examples is easy, but this doesn't help to
actually implement something sensible. Please tell us for your example
where the driver can only implement 20 ns what is the smallest requested
period the driver should accept.

> > > IMO, this is the best way to prevent surprising results in the API.
> >
> > I think it's not possible in practise to refuse "near" misses and every
> > definition of "near" is in some case ridiculous. Also if you consider
> > the pwm_round_state() case you don't want to refuse any request to tell
> > as much as possible about your controller's capabilities. And then it's
> > straight forward to let apply behave in the same way to keep complexity
> > low.
> >
> > > The real issue here is that it is impossible to determine the correct
> > > way to round the PWM a priori, and in particular, without considering
> > > both duty_cycle and period. If a consumer requests very small
> > > period/duty cycle which we cannot produce, how should it be rounded?
> >
> > Yeah, because there is no obviously right one, I picked one that is as
> > wrong as the other possibilities but is easy to work with.
> >
> > > Should we just set TLR0=1 and TLR1=0 to give them 66% duty cycle with
> > > the least period? Or should we try and increase the period to better
> > > approximate the % duty cycle? And both of these decisions must be made
> > > knowing both parameters. We cannot (for example) just always round up,
> > > since we may produce a configuration with TLR0 == TLR1, which would
> > > produce 0% duty cycle instead of whatever was requested. Rounding rate
> > > will introduce significant complexity into the driver. Most of the time
> > > if a consumer requests an invalid rate, it is due to misconfiguration
> > > which is best solved by fixing the configuration.
> >
> > In the first step pick the biggest period not bigger than the requested
> > and then pick the biggest duty cycle that is not bigger than the
> > requested and that can be set with the just picked period. That is the
> > behaviour that all new drivers should do. This is somewhat arbitrary but
> > after quite some thought the most sensible in my eyes.
>
> And if there are no periods smaller than the requested period?

Then return -ERANGE.

> Any way you slice it, there will be situations where there is nothing
> reasonable to do other than return an error.

ack.

> > > > > Perhaps I should add
> > > > >
> > > > > if (tlr0 <= tlr1)
> > > > > return -EINVAL;
> > > > >
> > > > > here to prevent accidentally getting 0% duty cycle.
> > > >
> > > > You can assume that duty_cycle <= period when .apply is called.
> > >
> > > Ok, I will only check for == then.
> >
> > You just have to pay attention to the case that you had to decrement
> > .period to the next possible value. Then .duty_cycle might be bigger
> > than the corrected period.
>
> This is specifically to prevent 100% duty cycle from turning into 0%. My
> current draft is
>
> /*
> * If TLR0 == TLR1, then we will produce 0% duty cycle instead of 100%
> * duty cycle. Try and reduce the high time to compensate. If we can't
> * do that because the high time is already 0 cycles, then just error
> * out.
> */
> if (tlr0 == tlr1 && !tlr1--)
> return -EINVAL;

If you follow my suggested policy this isn't an error and you should
yield the biggest duty_cycle here even if it is zero.

Best regards
Uwe

--
Pengutronix e.K. | Uwe Kleine-K?nig |
Industrial Linux Solutions | https://www.pengutronix.de/ |


Attachments:
(No filename) (5.75 kB)
signature.asc (499.00 B)
Download all attachments

2021-06-29 08:33:50

by Uwe Kleine-König

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

Hello Sean,

On Mon, Jun 28, 2021 at 01:41:43PM -0400, Sean Anderson wrote:
> On 6/28/21 1:20 PM, Uwe Kleine-K?nig wrote:
> > On Mon, Jun 28, 2021 at 12:35:19PM -0400, Sean Anderson wrote:
> >> On 6/28/21 12:24 PM, Uwe Kleine-K?nig wrote:
> >> > On Mon, Jun 28, 2021 at 11:50:33AM -0400, Sean Anderson wrote:
> >> > > On 6/27/21 2:19 PM, Uwe Kleine-K?nig wrote:
> >> > > > On Fri, Jun 25, 2021 at 01:46:26PM -0400, Sean Anderson wrote:
> >> > > > > So for the moment, why not give an error? This will be legal code both
> >> > > > > now and after round_state is implemented.
> >> > > >
> >> > > > The problem is where to draw the line. To stay with your example: If a
> >> > > > request for period = 150 ns comes in, and let X be the biggest period <=
> >> > > > 150 ns that the hardware can configure. For which values of X should an
> >> > > > error be returned and for which values the setting should be
> >> > > > implemented.
> >> > > >
> >> > > > In my eyes the only sensible thing to implement here is to tell the
> >> > > > consumer about X and let it decide if it's good enough. If you have a
> >> > > > better idea let me hear about it.
> >> > >
> >> > > Sure. And I think it's ok to tell the consumer that X is the best we can
> >> > > do. But if they go along and request an unconfigurable state anyway, we
> >> > > should tell them as much.
> >> >
> >> > I have the impression you didn't understand where I see the problem. If
> >> > you request 150 ns and the controller can only do 149 ns (or 149.6667 ns)
> >> > should we refuse? If yes: This is very unusable, e.g. the led-pwm driver
> >> > expects that it can configure the duty_cycle in 1/256 steps of the
> >> > period, and then maybe only steps 27 and 213 of the 256 possible steps
> >> > work. (This example doesn't really match because the led-pwm driver
> >> > varies duty_cycle and not period, but the principle becomes clear I
> >> > assume.) If no: Should we accept 151 ns? Isn't that ridiculous?
> >>
> >> I am fine with this sort of rounding. The part I take issue with is when
> >> the consumer requests (e.g.) a 10ns period, but the best we can do is
> >> 20ns. Or at the other end if they request a 4s period but the best we
> >> can do is 2s. Here, there is no obvious way to round it, so I think we
> >> should just say "come back with a reasonable period" and let whoever
> >> wrote the device tree pick a better period.
> >
> > Note that giving ridiculus examples is easy, but this doesn't help to
> > actually implement something sensible. Please tell us for your example
> > where the driver can only implement 20 ns what is the smallest requested
> > period the driver should accept.
>
> 20ns :)
>
> In the case of this device, that would result in 0% duty cycle with a
> 100MHz input. So the smallest reasonable period is 30ns with a duty
> cycle of 20ns.

I took the time to understand the hardware a bit better, also to be able
to reply to your formulae below. So to recap (and simplify slightly
assuming TCSR_UDT = 1):


TLR0 + 2
period = --------
clkrate

TLR1 + 2
duty_cycle = -------- if TLR1 < TLR0, else 0
clkrate


where TLRx has the range [0..0xffffffff] (for some devices the range is
smaller). So clkrate seems to be 100 MHz?

> >> > > IMO, this is the best way to prevent surprising results in the API.
> >> >
> >> > I think it's not possible in practise to refuse "near" misses and every
> >> > definition of "near" is in some case ridiculous. Also if you consider
> >> > the pwm_round_state() case you don't want to refuse any request to tell
> >> > as much as possible about your controller's capabilities. And then it's
> >> > straight forward to let apply behave in the same way to keep complexity
> >> > low.
> >> >
> >> > > The real issue here is that it is impossible to determine the correct
> >> > > way to round the PWM a priori, and in particular, without considering
> >> > > both duty_cycle and period. If a consumer requests very small
> >> > > period/duty cycle which we cannot produce, how should it be rounded?
> >> >
> >> > Yeah, because there is no obviously right one, I picked one that is as
> >> > wrong as the other possibilities but is easy to work with.
> >> >
> >> > > Should we just set TLR0=1 and TLR1=0 to give them 66% duty cycle with
> >> > > the least period? Or should we try and increase the period to better
> >> > > approximate the % duty cycle? And both of these decisions must be made
> >> > > knowing both parameters. We cannot (for example) just always round up,
> >> > > since we may produce a configuration with TLR0 == TLR1, which would
> >> > > produce 0% duty cycle instead of whatever was requested. Rounding rate
> >> > > will introduce significant complexity into the driver. Most of the time
> >> > > if a consumer requests an invalid rate, it is due to misconfiguration
> >> > > which is best solved by fixing the configuration.
> >> >
> >> > In the first step pick the biggest period not bigger than the requested
> >> > and then pick the biggest duty cycle that is not bigger than the
> >> > requested and that can be set with the just picked period. That is the
> >> > behaviour that all new drivers should do. This is somewhat arbitrary but
> >> > after quite some thought the most sensible in my eyes.
> >>
> >> And if there are no periods smaller than the requested period?
> >
> > Then return -ERANGE.
>
> Ok, so instead of
>
> if (cycles < 2 || cycles > priv->max + 2)
> return -ERANGE;
>
> you would prefer
>
> if (cycles < 2)
> return -ERANGE;
> else if (cycles > priv->max + 2)
> cycles = priv->max;

The actual calculation is a bit harder to handle TCSR_UDT = 0 but in
principle, yes, but see below.

> But if we do the above clamping for TLR0, then we have to recalculate
> the duty cycle for TLR1. Which I guess means doing something like
>
> ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
> if (ret)
> return ret;
>
> state->duty_cycle = mult_frac(state->duty_cycle,
> xilinx_timer_get_period(priv, tlr0, tcsr0),
> state->period);
>
> ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
> if (ret)
> return ret;

No, you need something like:

/*
* The multiplication cannot overflow as both priv_max and
* NSEC_PER_SEC fit into an u32.
*/
max_period = div64_ul((u64)priv->max * NSEC_PER_SEC, clkrate);

/* cap period to the maximal possible value */
if (state->period > max_period)
period = max_period;
else
period = state->period;

/* cap duty_cycle to the maximal possible value */
if (state->duty_cycle > max_period)
duty_cycle = max_period;
else
duty_cycle = state->duty_cycle;

period_cycles = period * clkrate / NSEC_PER_SEC;

if (period_cycles < 2)
return -ERANGE;

duty_cycles = duty_cycle * clkrate / NSEC_PER_SEC;

/*
* The hardware cannot emit a 100% relative duty cycle, if
* duty_cycle >= period_cycles is programmed the hardware emits
* a 0% relative duty cycle.
*/
if (duty_cycle == period_cycles)
duty_cycles = period_cycles - 1;

/*
* The hardware cannot emit a duty_cycle of one clk step, so
* emit 0 instead.
*/
if (duty_cycles < 2)
duty_cycles = period_cycles;

> >> > > > > Perhaps I should add
> >> > > > >
> >> > > > > if (tlr0 <= tlr1)
> >> > > > > return -EINVAL;
> >> > > > >
> >> > > > > here to prevent accidentally getting 0% duty cycle.
> >> > > >
> >> > > > You can assume that duty_cycle <= period when .apply is called.
> >> > >
> >> > > Ok, I will only check for == then.
> >> >
> >> > You just have to pay attention to the case that you had to decrement
> >> > .period to the next possible value. Then .duty_cycle might be bigger
> >> > than the corrected period.
> >>
> >> This is specifically to prevent 100% duty cycle from turning into 0%. My
> >> current draft is
> >>
> >> /*
> >> * If TLR0 == TLR1, then we will produce 0% duty cycle instead of 100%
> >> * duty cycle. Try and reduce the high time to compensate. If we can't
> >> * do that because the high time is already 0 cycles, then just error
> >> * out.
> >> */
> >> if (tlr0 == tlr1 && !tlr1--)
> >> return -EINVAL;
> >
> > If you follow my suggested policy this isn't an error and you should
> > yield the biggest duty_cycle here even if it is zero.
>
> So like this?
>
> if (tlr0 == tlr1) {
> if (tlr1)
> tlr1--;
> else if (tlr0 != priv->max)
> tlr0++;
> else
> return -ERANGE;
> }

No, this is wrong as it configures a longer period than requested in
some cases.

> And I would really appreciate if you could write up some documentation
> with common errors and how to handle them. It's not at all obvious to me
> what all the implications of the above guidelines are.

Yes, I fully agree this should be documented and doing that is on my
todo list. Until I come around to do this, enabling PWM_DEBUG should
help you getting this right (assuming you test extensively and read the
resulting kernel messages).

Best regards
Uwe

--
Pengutronix e.K. | Uwe Kleine-K?nig |
Industrial Linux Solutions | https://www.pengutronix.de/ |


Attachments:
(No filename) (9.20 kB)
signature.asc (499.00 B)
Download all attachments

2021-06-29 08:40:43

by Uwe Kleine-König

[permalink] [raw]
Subject: Re: [PATCH v4 1/3] dt-bindings: pwm: Add Xilinx AXI Timer

Hello Sean,

I wonder what tree you chose as a base here. I found tags/v5.13-rc1~44^2
as a tree that your patches can be applied to (and tags/v5.13-rc1~44 or
later doesn't work). I recommend using

git format-patch --base ...

. This makes it easier for the responsible maintainers to pick the
right base and allows easier automatic testing.

Best regards
Uwe

--
Pengutronix e.K. | Uwe Kleine-K?nig |
Industrial Linux Solutions | https://www.pengutronix.de/ |


Attachments:
(No filename) (533.00 B)
signature.asc (499.00 B)
Download all attachments

2021-06-29 14:56:19

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 1/3] dt-bindings: pwm: Add Xilinx AXI Timer



On 6/29/21 4:38 AM, Uwe Kleine-K?nig wrote:
> Hello Sean,
>
> I wonder what tree you chose as a base here. I found tags/v5.13-rc1~44^2
> as a tree that your patches can be applied to (and tags/v5.13-rc1~44 or
> later doesn't work). I recommend using

Hm, looks like it's based off of pwm/for-next from April. I will rebase
onto something newer for the next revision.

--Sean

>
> git format-patch --base ...
>
> . This makes it easier for the responsible maintainers to pick the
> right base and allows easier automatic testing.
>
> Best regards
> Uwe
>

2021-06-29 18:11:36

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer



On 6/29/21 4:31 AM, Uwe Kleine-K?nig wrote:
> Hello Sean,
>
> On Mon, Jun 28, 2021 at 01:41:43PM -0400, Sean Anderson wrote:
>> On 6/28/21 1:20 PM, Uwe Kleine-K?nig wrote:
>> > On Mon, Jun 28, 2021 at 12:35:19PM -0400, Sean Anderson wrote:
>> >> On 6/28/21 12:24 PM, Uwe Kleine-K?nig wrote:
>> >> > On Mon, Jun 28, 2021 at 11:50:33AM -0400, Sean Anderson wrote:
>> >> > > On 6/27/21 2:19 PM, Uwe Kleine-K?nig wrote:
>> >> > > > On Fri, Jun 25, 2021 at 01:46:26PM -0400, Sean Anderson wrote:
>> >> > > > > So for the moment, why not give an error? This will be legal code both
>> >> > > > > now and after round_state is implemented.
>> >> > > >
>> >> > > > The problem is where to draw the line. To stay with your example: If a
>> >> > > > request for period = 150 ns comes in, and let X be the biggest period <=
>> >> > > > 150 ns that the hardware can configure. For which values of X should an
>> >> > > > error be returned and for which values the setting should be
>> >> > > > implemented.
>> >> > > >
>> >> > > > In my eyes the only sensible thing to implement here is to tell the
>> >> > > > consumer about X and let it decide if it's good enough. If you have a
>> >> > > > better idea let me hear about it.
>> >> > >
>> >> > > Sure. And I think it's ok to tell the consumer that X is the best we can
>> >> > > do. But if they go along and request an unconfigurable state anyway, we
>> >> > > should tell them as much.
>> >> >
>> >> > I have the impression you didn't understand where I see the problem. If
>> >> > you request 150 ns and the controller can only do 149 ns (or 149.6667 ns)
>> >> > should we refuse? If yes: This is very unusable, e.g. the led-pwm driver
>> >> > expects that it can configure the duty_cycle in 1/256 steps of the
>> >> > period, and then maybe only steps 27 and 213 of the 256 possible steps
>> >> > work. (This example doesn't really match because the led-pwm driver
>> >> > varies duty_cycle and not period, but the principle becomes clear I
>> >> > assume.) If no: Should we accept 151 ns? Isn't that ridiculous?
>> >>
>> >> I am fine with this sort of rounding. The part I take issue with is when
>> >> the consumer requests (e.g.) a 10ns period, but the best we can do is
>> >> 20ns. Or at the other end if they request a 4s period but the best we
>> >> can do is 2s. Here, there is no obvious way to round it, so I think we
>> >> should just say "come back with a reasonable period" and let whoever
>> >> wrote the device tree pick a better period.
>> >
>> > Note that giving ridiculus examples is easy, but this doesn't help to
>> > actually implement something sensible. Please tell us for your example
>> > where the driver can only implement 20 ns what is the smallest requested
>> > period the driver should accept.
>>
>> 20ns :)
>>
>> In the case of this device, that would result in 0% duty cycle with a
>> 100MHz input. So the smallest reasonable period is 30ns with a duty
>> cycle of 20ns.
>
> I took the time to understand the hardware a bit better, also to be able
> to reply to your formulae below. So to recap (and simplify slightly
> assuming TCSR_UDT = 1):
>
>
> TLR0 + 2
> period = --------
> clkrate
>
> TLR1 + 2
> duty_cycle = -------- if TLR1 < TLR0, else 0
> clkrate
>
>
> where TLRx has the range [0..0xffffffff] (for some devices the range is
> smaller). So clkrate seems to be 100 MHz?

On my system, yes.

>
>> >> > > IMO, this is the best way to prevent surprising results in the API.
>> >> >
>> >> > I think it's not possible in practise to refuse "near" misses and every
>> >> > definition of "near" is in some case ridiculous. Also if you consider
>> >> > the pwm_round_state() case you don't want to refuse any request to tell
>> >> > as much as possible about your controller's capabilities. And then it's
>> >> > straight forward to let apply behave in the same way to keep complexity
>> >> > low.
>> >> >
>> >> > > The real issue here is that it is impossible to determine the correct
>> >> > > way to round the PWM a priori, and in particular, without considering
>> >> > > both duty_cycle and period. If a consumer requests very small
>> >> > > period/duty cycle which we cannot produce, how should it be rounded?
>> >> >
>> >> > Yeah, because there is no obviously right one, I picked one that is as
>> >> > wrong as the other possibilities but is easy to work with.
>> >> >
>> >> > > Should we just set TLR0=1 and TLR1=0 to give them 66% duty cycle with
>> >> > > the least period? Or should we try and increase the period to better
>> >> > > approximate the % duty cycle? And both of these decisions must be made
>> >> > > knowing both parameters. We cannot (for example) just always round up,
>> >> > > since we may produce a configuration with TLR0 == TLR1, which would
>> >> > > produce 0% duty cycle instead of whatever was requested. Rounding rate
>> >> > > will introduce significant complexity into the driver. Most of the time
>> >> > > if a consumer requests an invalid rate, it is due to misconfiguration
>> >> > > which is best solved by fixing the configuration.
>> >> >
>> >> > In the first step pick the biggest period not bigger than the requested
>> >> > and then pick the biggest duty cycle that is not bigger than the
>> >> > requested and that can be set with the just picked period. That is the
>> >> > behaviour that all new drivers should do. This is somewhat arbitrary but
>> >> > after quite some thought the most sensible in my eyes.
>> >>
>> >> And if there are no periods smaller than the requested period?
>> >
>> > Then return -ERANGE.
>>
>> Ok, so instead of
>>
>> if (cycles < 2 || cycles > priv->max + 2)
>> return -ERANGE;
>>
>> you would prefer
>>
>> if (cycles < 2)
>> return -ERANGE;
>> else if (cycles > priv->max + 2)
>> cycles = priv->max;
>
> The actual calculation is a bit harder to handle TCSR_UDT = 0 but in
> principle, yes, but see below.
>
>> But if we do the above clamping for TLR0, then we have to recalculate
>> the duty cycle for TLR1. Which I guess means doing something like
>>
>> ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
>> if (ret)
>> return ret;
>>
>> state->duty_cycle = mult_frac(state->duty_cycle,
>> xilinx_timer_get_period(priv, tlr0, tcsr0),
>> state->period);
>>
>> ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
>> if (ret)
>> return ret;
>
> No, you need something like:
>
> /*
> * The multiplication cannot overflow as both priv_max and
> * NSEC_PER_SEC fit into an u32.
> */
> max_period = div64_ul((u64)priv->max * NSEC_PER_SEC, clkrate);
>
> /* cap period to the maximal possible value */
> if (state->period > max_period)
> period = max_period;
> else
> period = state->period;
>
> /* cap duty_cycle to the maximal possible value */
> if (state->duty_cycle > max_period)
> duty_cycle = max_period;
> else
> duty_cycle = state->duty_cycle;

These caps may increase the % duty cycle. For example, consider when the
max is 100, and the user requests a period of 150 and a duty cycle of
75, for a % duty cycle of 50%. The current logic is equivalent to

period = min(state->period, max_period);
duty_cycle = min(state->duty_cycle, max_period);

Which will result in a period of 100 and a duty cycle of 75, for a 75%
duty cycle. Instead, we should do

period = min(state->period, max_period);
duty_cycle = mult_frac(state->duty_cycle, period, state->period);

which will result in a period of 100 and a duty cycle of 50.

> period_cycles = period * clkrate / NSEC_PER_SEC;
>
> if (period_cycles < 2)
> return -ERANGE;
>
> duty_cycles = duty_cycle * clkrate / NSEC_PER_SEC;
>
> /*
> * The hardware cannot emit a 100% relative duty cycle, if
> * duty_cycle >= period_cycles is programmed the hardware emits
> * a 0% relative duty cycle.
> */
> if (duty_cycle == period_cycles)
> duty_cycles = period_cycles - 1;
>
> /*
> * The hardware cannot emit a duty_cycle of one clk step, so
> * emit 0 instead.
> */
> if (duty_cycles < 2)
> duty_cycles = period_cycles;

Of course, the above may result in 100% duty cycle being rounded down to
0%. I feel like that is too big of a jump to ignore. Perhaps if we
cannot return -ERANGE we should at least dev_warn.

--Sean

>> >> > > > > Perhaps I should add
>> >> > > > >
>> >> > > > > if (tlr0 <= tlr1)
>> >> > > > > return -EINVAL;
>> >> > > > >
>> >> > > > > here to prevent accidentally getting 0% duty cycle.
>> >> > > >
>> >> > > > You can assume that duty_cycle <= period when .apply is called.
>> >> > >
>> >> > > Ok, I will only check for == then.
>> >> >
>> >> > You just have to pay attention to the case that you had to decrement
>> >> > .period to the next possible value. Then .duty_cycle might be bigger
>> >> > than the corrected period.
>> >>
>> >> This is specifically to prevent 100% duty cycle from turning into 0%. My
>> >> current draft is
>> >>
>> >> /*
>> >> * If TLR0 == TLR1, then we will produce 0% duty cycle instead of 100%
>> >> * duty cycle. Try and reduce the high time to compensate. If we can't
>> >> * do that because the high time is already 0 cycles, then just error
>> >> * out.
>> >> */
>> >> if (tlr0 == tlr1 && !tlr1--)
>> >> return -EINVAL;
>> >
>> > If you follow my suggested policy this isn't an error and you should
>> > yield the biggest duty_cycle here even if it is zero.
>>
>> So like this?
>>
>> if (tlr0 == tlr1) {
>> if (tlr1)
>> tlr1--;
>> else if (tlr0 != priv->max)
>> tlr0++;
>> else
>> return -ERANGE;
>> }
>
> No, this is wrong as it configures a longer period than requested in
> some cases.
>
>> And I would really appreciate if you could write up some documentation
>> with common errors and how to handle them. It's not at all obvious to me
>> what all the implications of the above guidelines are.
>
> Yes, I fully agree this should be documented and doing that is on my
> todo list. Until I come around to do this, enabling PWM_DEBUG should
> help you getting this right (assuming you test extensively and read the
> resulting kernel messages).
>
> Best regards
> Uwe
>

2021-06-29 20:53:47

by Uwe Kleine-König

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

Hello Sean,

On Tue, Jun 29, 2021 at 02:01:31PM -0400, Sean Anderson wrote:
> On 6/29/21 4:31 AM, Uwe Kleine-K?nig wrote:
> > Hello Sean,
> >
> > On Mon, Jun 28, 2021 at 01:41:43PM -0400, Sean Anderson wrote:
> >> On 6/28/21 1:20 PM, Uwe Kleine-K?nig wrote:
> >> > On Mon, Jun 28, 2021 at 12:35:19PM -0400, Sean Anderson wrote:
> >> >> On 6/28/21 12:24 PM, Uwe Kleine-K?nig wrote:
> >> >> > On Mon, Jun 28, 2021 at 11:50:33AM -0400, Sean Anderson wrote:
> >> >> > > On 6/27/21 2:19 PM, Uwe Kleine-K?nig wrote:
> >> >> > > > On Fri, Jun 25, 2021 at 01:46:26PM -0400, Sean Anderson wrote:
> >> >> > > > > So for the moment, why not give an error? This will be legal code both
> >> >> > > > > now and after round_state is implemented.
> >> >> > > >
> >> >> > > > The problem is where to draw the line. To stay with your example: If a
> >> >> > > > request for period = 150 ns comes in, and let X be the biggest period <=
> >> >> > > > 150 ns that the hardware can configure. For which values of X should an
> >> >> > > > error be returned and for which values the setting should be
> >> >> > > > implemented.
> >> >> > > >
> >> >> > > > In my eyes the only sensible thing to implement here is to tell the
> >> >> > > > consumer about X and let it decide if it's good enough. If you have a
> >> >> > > > better idea let me hear about it.
> >> >> > >
> >> >> > > Sure. And I think it's ok to tell the consumer that X is the best we can
> >> >> > > do. But if they go along and request an unconfigurable state anyway, we
> >> >> > > should tell them as much.
> >> >> >
> >> >> > I have the impression you didn't understand where I see the problem. If
> >> >> > you request 150 ns and the controller can only do 149 ns (or 149.6667 ns)
> >> >> > should we refuse? If yes: This is very unusable, e.g. the led-pwm driver
> >> >> > expects that it can configure the duty_cycle in 1/256 steps of the
> >> >> > period, and then maybe only steps 27 and 213 of the 256 possible steps
> >> >> > work. (This example doesn't really match because the led-pwm driver
> >> >> > varies duty_cycle and not period, but the principle becomes clear I
> >> >> > assume.) If no: Should we accept 151 ns? Isn't that ridiculous?
> >> >>
> >> >> I am fine with this sort of rounding. The part I take issue with is when
> >> >> the consumer requests (e.g.) a 10ns period, but the best we can do is
> >> >> 20ns. Or at the other end if they request a 4s period but the best we
> >> >> can do is 2s. Here, there is no obvious way to round it, so I think we
> >> >> should just say "come back with a reasonable period" and let whoever
> >> >> wrote the device tree pick a better period.
> >> >
> >> > Note that giving ridiculus examples is easy, but this doesn't help to
> >> > actually implement something sensible. Please tell us for your example
> >> > where the driver can only implement 20 ns what is the smallest requested
> >> > period the driver should accept.
> >>
> >> 20ns :)
> >>
> >> In the case of this device, that would result in 0% duty cycle with a
> >> 100MHz input. So the smallest reasonable period is 30ns with a duty
> >> cycle of 20ns.
> >
> > I took the time to understand the hardware a bit better, also to be able
> > to reply to your formulae below. So to recap (and simplify slightly
> > assuming TCSR_UDT = 1):
> >
> >
> > TLR0 + 2
> > period = --------
> > clkrate
> >
> > TLR1 + 2
> > duty_cycle = -------- if TLR1 < TLR0, else 0
> > clkrate
> >
> >
> > where TLRx has the range [0..0xffffffff] (for some devices the range is
> > smaller). So clkrate seems to be 100 MHz?
>
> On my system, yes.
>
> >
> >> >> > > IMO, this is the best way to prevent surprising results in the API.
> >> >> >
> >> >> > I think it's not possible in practise to refuse "near" misses and every
> >> >> > definition of "near" is in some case ridiculous. Also if you consider
> >> >> > the pwm_round_state() case you don't want to refuse any request to tell
> >> >> > as much as possible about your controller's capabilities. And then it's
> >> >> > straight forward to let apply behave in the same way to keep complexity
> >> >> > low.
> >> >> >
> >> >> > > The real issue here is that it is impossible to determine the correct
> >> >> > > way to round the PWM a priori, and in particular, without considering
> >> >> > > both duty_cycle and period. If a consumer requests very small
> >> >> > > period/duty cycle which we cannot produce, how should it be rounded?
> >> >> >
> >> >> > Yeah, because there is no obviously right one, I picked one that is as
> >> >> > wrong as the other possibilities but is easy to work with.
> >> >> >
> >> >> > > Should we just set TLR0=1 and TLR1=0 to give them 66% duty cycle with
> >> >> > > the least period? Or should we try and increase the period to better
> >> >> > > approximate the % duty cycle? And both of these decisions must be made
> >> >> > > knowing both parameters. We cannot (for example) just always round up,
> >> >> > > since we may produce a configuration with TLR0 == TLR1, which would
> >> >> > > produce 0% duty cycle instead of whatever was requested. Rounding rate
> >> >> > > will introduce significant complexity into the driver. Most of the time
> >> >> > > if a consumer requests an invalid rate, it is due to misconfiguration
> >> >> > > which is best solved by fixing the configuration.
> >> >> >
> >> >> > In the first step pick the biggest period not bigger than the requested
> >> >> > and then pick the biggest duty cycle that is not bigger than the
> >> >> > requested and that can be set with the just picked period. That is the
> >> >> > behaviour that all new drivers should do. This is somewhat arbitrary but
> >> >> > after quite some thought the most sensible in my eyes.
> >> >>
> >> >> And if there are no periods smaller than the requested period?
> >> >
> >> > Then return -ERANGE.
> >>
> >> Ok, so instead of
> >>
> >> if (cycles < 2 || cycles > priv->max + 2)
> >> return -ERANGE;
> >>
> >> you would prefer
> >>
> >> if (cycles < 2)
> >> return -ERANGE;
> >> else if (cycles > priv->max + 2)
> >> cycles = priv->max;
> >
> > The actual calculation is a bit harder to handle TCSR_UDT = 0 but in
> > principle, yes, but see below.
> >
> >> But if we do the above clamping for TLR0, then we have to recalculate
> >> the duty cycle for TLR1. Which I guess means doing something like
> >>
> >> ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
> >> if (ret)
> >> return ret;
> >>
> >> state->duty_cycle = mult_frac(state->duty_cycle,
> >> xilinx_timer_get_period(priv, tlr0, tcsr0),
> >> state->period);
> >>
> >> ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
> >> if (ret)
> >> return ret;
> >
> > No, you need something like:
> >
> > /*
> > * The multiplication cannot overflow as both priv_max and
> > * NSEC_PER_SEC fit into an u32.
> > */
> > max_period = div64_ul((u64)priv->max * NSEC_PER_SEC, clkrate);
> >
> > /* cap period to the maximal possible value */
> > if (state->period > max_period)
> > period = max_period;
> > else
> > period = state->period;
> >
> > /* cap duty_cycle to the maximal possible value */
> > if (state->duty_cycle > max_period)
> > duty_cycle = max_period;
> > else
> > duty_cycle = state->duty_cycle;
>
> These caps may increase the % duty cycle.

Correct.

For some usecases keeping the relative duty cycle might be better, for
others it might not. I'm still convinced that in general my solution
makes sense, is computationally cheaper and easier to work with.

>
> > period_cycles = period * clkrate / NSEC_PER_SEC;
> >
> > if (period_cycles < 2)
> > return -ERANGE;
> >
> > duty_cycles = duty_cycle * clkrate / NSEC_PER_SEC;
> >
> > /*
> > * The hardware cannot emit a 100% relative duty cycle, if
> > * duty_cycle >= period_cycles is programmed the hardware emits
> > * a 0% relative duty cycle.
> > */
> > if (duty_cycle == period_cycles)
> > duty_cycles = period_cycles - 1;
> >
> > /*
> > * The hardware cannot emit a duty_cycle of one clk step, so
> > * emit 0 instead.
> > */
> > if (duty_cycles < 2)
> > duty_cycles = period_cycles;
>
> Of course, the above may result in 100% duty cycle being rounded down to
> 0%. I feel like that is too big of a jump to ignore. Perhaps if we
> cannot return -ERANGE we should at least dev_warn.

You did it again. You picked one single case that you consider bad but
didn't provide a constructive way to make it better.

Assume there was already a pwm_round_state function (that returns the
state that pwm_apply_state would implement for a given request) Consider
a consumer that wants say a 50% relative duty together with a small
period. So it first might call:

ret = pwm_round_rate(pwm, { .period = 20, .duty_cycle = 20, ... }, &rounded_state)

to find out if .period = 20 can be implemented with the given PWM. If
this returns rounded state as:

.period = 20
.duty_cycle = 0

this says quite a lot about the pwm if the driver implements my policy.
(i.e.: The driver can do 20ns, but the biggest duty_cycle is only 0).
If however it returns -ERANGE this means (assuming the driver implements
the policy I try to convice you to be the right one) it means: The
hardware cannot implement 20 ns (or something smaller) and so the next
call probably tries 40 ns.

With your suggested semantic -ERANGE might mean:

- The driver doesn't support .period = 20 ns
(Follow up questions: What period should be tried next? 10 ns? 40
ns? What if this returns -ERANGE again?)
- The driver supports .period = 20 ns, but the biggest possible
duty_cycle is "too different from 20 ns to ignore".

Then how should the search continue?

So: As soon as there is a pwm_round_rate function this can be catched
and then it's important that the drivers adhere to the expected policy.
Implementing this is a big thing and believe me I already spend quite
some brain cycles into it. Once the core is extended accordingly I will
be happy about each driver already doing the right thing.

Best regards
Uwe

--
Pengutronix e.K. | Uwe Kleine-K?nig |
Industrial Linux Solutions | https://www.pengutronix.de/ |


Attachments:
(No filename) (10.29 kB)
signature.asc (499.00 B)
Download all attachments

2021-06-29 22:24:50

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer



On 6/29/21 4:51 PM, Uwe Kleine-K?nig wrote:
> Hello Sean,
>
> On Tue, Jun 29, 2021 at 02:01:31PM -0400, Sean Anderson wrote:
>> On 6/29/21 4:31 AM, Uwe Kleine-K?nig wrote:
>> > Hello Sean,
>> >
>> > On Mon, Jun 28, 2021 at 01:41:43PM -0400, Sean Anderson wrote:
>> >> On 6/28/21 1:20 PM, Uwe Kleine-K?nig wrote:
>> >> > On Mon, Jun 28, 2021 at 12:35:19PM -0400, Sean Anderson wrote:
>> >> >> On 6/28/21 12:24 PM, Uwe Kleine-K?nig wrote:
>> >> >> > On Mon, Jun 28, 2021 at 11:50:33AM -0400, Sean Anderson wrote:
>> >> >> > > On 6/27/21 2:19 PM, Uwe Kleine-K?nig wrote:
>> >> >> > > > On Fri, Jun 25, 2021 at 01:46:26PM -0400, Sean Anderson wrote:
>> >> >> > > IMO, this is the best way to prevent surprising results in the API.
>> >> >> >
>> >> >> > I think it's not possible in practise to refuse "near" misses and every
>> >> >> > definition of "near" is in some case ridiculous. Also if you consider
>> >> >> > the pwm_round_state() case you don't want to refuse any request to tell
>> >> >> > as much as possible about your controller's capabilities. And then it's
>> >> >> > straight forward to let apply behave in the same way to keep complexity
>> >> >> > low.
>> >> >> >
>> >> >> > > The real issue here is that it is impossible to determine the correct
>> >> >> > > way to round the PWM a priori, and in particular, without considering
>> >> >> > > both duty_cycle and period. If a consumer requests very small
>> >> >> > > period/duty cycle which we cannot produce, how should it be rounded?
>> >> >> >
>> >> >> > Yeah, because there is no obviously right one, I picked one that is as
>> >> >> > wrong as the other possibilities but is easy to work with.
>> >> >> >
>> >> >> > > Should we just set TLR0=1 and TLR1=0 to give them 66% duty cycle with
>> >> >> > > the least period? Or should we try and increase the period to better
>> >> >> > > approximate the % duty cycle? And both of these decisions must be made
>> >> >> > > knowing both parameters. We cannot (for example) just always round up,
>> >> >> > > since we may produce a configuration with TLR0 == TLR1, which would
>> >> >> > > produce 0% duty cycle instead of whatever was requested. Rounding rate
>> >> >> > > will introduce significant complexity into the driver. Most of the time
>> >> >> > > if a consumer requests an invalid rate, it is due to misconfiguration
>> >> >> > > which is best solved by fixing the configuration.
>> >> >> >
>> >> >> > In the first step pick the biggest period not bigger than the requested
>> >> >> > and then pick the biggest duty cycle that is not bigger than the
>> >> >> > requested and that can be set with the just picked period. That is the
>> >> >> > behaviour that all new drivers should do. This is somewhat arbitrary but
>> >> >> > after quite some thought the most sensible in my eyes.
>> >> >>
>> >> >> And if there are no periods smaller than the requested period?
>> >> >
>> >> > Then return -ERANGE.
>> >>
>> >> Ok, so instead of
>> >>
>> >> if (cycles < 2 || cycles > priv->max + 2)
>> >> return -ERANGE;
>> >>
>> >> you would prefer
>> >>
>> >> if (cycles < 2)
>> >> return -ERANGE;
>> >> else if (cycles > priv->max + 2)
>> >> cycles = priv->max;
>> >
>> > The actual calculation is a bit harder to handle TCSR_UDT = 0 but in
>> > principle, yes, but see below.
>> >
>> >> But if we do the above clamping for TLR0, then we have to recalculate
>> >> the duty cycle for TLR1. Which I guess means doing something like
>> >>
>> >> ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
>> >> if (ret)
>> >> return ret;
>> >>
>> >> state->duty_cycle = mult_frac(state->duty_cycle,
>> >> xilinx_timer_get_period(priv, tlr0, tcsr0),
>> >> state->period);
>> >>
>> >> ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
>> >> if (ret)
>> >> return ret;
>> >
>> > No, you need something like:
>> >
>> > /*
>> > * The multiplication cannot overflow as both priv_max and
>> > * NSEC_PER_SEC fit into an u32.
>> > */
>> > max_period = div64_ul((u64)priv->max * NSEC_PER_SEC, clkrate);
>> >
>> > /* cap period to the maximal possible value */
>> > if (state->period > max_period)
>> > period = max_period;
>> > else
>> > period = state->period;
>> >
>> > /* cap duty_cycle to the maximal possible value */
>> > if (state->duty_cycle > max_period)
>> > duty_cycle = max_period;
>> > else
>> > duty_cycle = state->duty_cycle;
>>
>> These caps may increase the % duty cycle.
>
> Correct.
>
> For some usecases keeping the relative duty cycle might be better, for
> others it might not. I'm still convinced that in general my solution
> makes sense, is computationally cheaper and easier to work with.

Can you please describe one of those use cases? Every PWM user I looked
(grepping for pwm_apply_state and pwm_config) set the duty cycle as a
percentage of the period, and not as an absolute time. Keeping the high
time the same while changing the duty cycle runs contrary to the
assumptions of all of those users.

>>
>> > period_cycles = period * clkrate / NSEC_PER_SEC;
>> >
>> > if (period_cycles < 2)
>> > return -ERANGE;
>> >
>> > duty_cycles = duty_cycle * clkrate / NSEC_PER_SEC;
>> >
>> > /*
>> > * The hardware cannot emit a 100% relative duty cycle, if
>> > * duty_cycle >= period_cycles is programmed the hardware emits
>> > * a 0% relative duty cycle.
>> > */
>> > if (duty_cycle == period_cycles)
>> > duty_cycles = period_cycles - 1;
>> >
>> > /*
>> > * The hardware cannot emit a duty_cycle of one clk step, so
>> > * emit 0 instead.
>> > */
>> > if (duty_cycles < 2)
>> > duty_cycles = period_cycles;
>>
>> Of course, the above may result in 100% duty cycle being rounded down to
>> 0%. I feel like that is too big of a jump to ignore. Perhaps if we
>> cannot return -ERANGE we should at least dev_warn.
>
> You did it again. You picked one single case that you consider bad but
> didn't provide a constructive way to make it better.

Sure I did. I suggested that we warn. Something like

if (duty_cycles == period_cycles)
if (--duty_cycles < 2)
dev_warn(chip->dev, "Rounding 100%% duty cycle down to 0%%; pick a longer period\n");

or

if (period_cycles < 2)
return -ERANGE;
else if (period_cycles < 10)
dev_notice(chip->dev,
"very short period of %u cycles; duty cycle may be rounded to 0%%\n",
period_cycles);

Because 90% of the time, if a user requests such a short period it is
due to a typo or something similar. And if they really are doing it
intentionally, then they should just set duty_cycle=0.

> Assume there was already a pwm_round_state function (that returns the
> state that pwm_apply_state would implement for a given request) Consider
> a consumer that wants say a 50% relative duty together with a small
> period. So it first might call:
>
> ret = pwm_round_rate(pwm, { .period = 20, .duty_cycle = 20, ... }, &rounded_state)
>
> to find out if .period = 20 can be implemented with the given PWM. If
> this returns rounded state as:
>
> .period = 20
> .duty_cycle = 0
>
> this says quite a lot about the pwm if the driver implements my policy.
> (i.e.: The driver can do 20ns, but the biggest duty_cycle is only 0).
> If however it returns -ERANGE this means (assuming the driver implements
> the policy I try to convice you to be the right one) it means: The
> hardware cannot implement 20 ns (or something smaller) and so the next
> call probably tries 40 ns.
>
> With your suggested semantic -ERANGE might mean:
>
> - The driver doesn't support .period = 20 ns
> (Follow up questions: What period should be tried next? 10 ns? 40
> ns? What if this returns -ERANGE again?)
> - The driver supports .period = 20 ns, but the biggest possible
> duty_cycle is "too different from 20 ns to ignore".
>
> Then how should the search continue?

round_rate does not have to use the same logic as apply_state. That is,

ret = pwm_round_rate(pwm, { .period = 20, .duty_cycle = 20, ... }, &rounded_state)

could be rounded to

{ .period = 20, .duty_cycle = 0 }

but calling

ret = pwm_apply_state(pwm, { .period = 20, .duty_cycle = 20, ... })

could return -ERANGE. However, calling

ret = pwm_apply_state(pwm, { .period = 20, .duty_cycle = 0, ... })

should work just fine, as the caller clearly knows what they are getting
into. IMO this is the best way to allow hypothetical round_rate users to
find out the edges of the PWM while still protecting existing users.

It's perfectly fine to round

{ .period = 150, .duty_cycle = 75 }

to

{ .period = 100, .duty_cycle = 75 }

in round_rate. But doing the same thing for apply_state would be very
surprising to every existing PWM user.

IMO the following invariant should hold

apply_state(round_rate(x))
assert(x == get_state())

but the following should not necessarily hold

apply_state(x)
assert(round_rate(x) == get_state())

Of course, where it is reasonable to round down, we should do so. But
where the result may be surprising, then the caller should specify the
rounded state specifically. It is better to fail loudly and noisily than
to silently accept garbage.

--Sean

>
> So: As soon as there is a pwm_round_rate function this can be catched
> and then it's important that the drivers adhere to the expected policy.
> Implementing this is a big thing and believe me I already spend quite
> some brain cycles into it. Once the core is extended accordingly I will
> be happy about each driver already doing the right thing.
>
> Best regards
> Uwe
>

2021-06-29 23:39:43

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer



On 6/29/21 6:21 PM, Sean Anderson wrote:
>
>
> On 6/29/21 4:51 PM, Uwe Kleine-K?nig wrote:
>> Hello Sean,
>>
>> On Tue, Jun 29, 2021 at 02:01:31PM -0400, Sean Anderson wrote:
>>> On 6/29/21 4:31 AM, Uwe Kleine-K?nig wrote:
>>> > Hello Sean,
>>> >
>>> > On Mon, Jun 28, 2021 at 01:41:43PM -0400, Sean Anderson wrote:
>>> >> On 6/28/21 1:20 PM, Uwe Kleine-K?nig wrote:
>>> >> > On Mon, Jun 28, 2021 at 12:35:19PM -0400, Sean Anderson wrote:
>>> >> >> On 6/28/21 12:24 PM, Uwe Kleine-K?nig wrote:
>>> >> >> > On Mon, Jun 28, 2021 at 11:50:33AM -0400, Sean Anderson wrote:
>>> >> >> > > On 6/27/21 2:19 PM, Uwe Kleine-K?nig wrote:
>>> >> >> > > > On Fri, Jun 25, 2021 at 01:46:26PM -0400, Sean Anderson wrote:
>>> >> >> > > IMO, this is the best way to prevent surprising results in the API.
>>> >> >> >
>>> >> >> > I think it's not possible in practise to refuse "near" misses and every
>>> >> >> > definition of "near" is in some case ridiculous. Also if you consider
>>> >> >> > the pwm_round_state() case you don't want to refuse any request to tell
>>> >> >> > as much as possible about your controller's capabilities. And then it's
>>> >> >> > straight forward to let apply behave in the same way to keep complexity
>>> >> >> > low.
>>> >> >> >
>>> >> >> > > The real issue here is that it is impossible to determine the correct
>>> >> >> > > way to round the PWM a priori, and in particular, without considering
>>> >> >> > > both duty_cycle and period. If a consumer requests very small
>>> >> >> > > period/duty cycle which we cannot produce, how should it be rounded?
>>> >> >> >
>>> >> >> > Yeah, because there is no obviously right one, I picked one that is as
>>> >> >> > wrong as the other possibilities but is easy to work with.
>>> >> >> >
>>> >> >> > > Should we just set TLR0=1 and TLR1=0 to give them 66% duty cycle with
>>> >> >> > > the least period? Or should we try and increase the period to better
>>> >> >> > > approximate the % duty cycle? And both of these decisions must be made
>>> >> >> > > knowing both parameters. We cannot (for example) just always round up,
>>> >> >> > > since we may produce a configuration with TLR0 == TLR1, which would
>>> >> >> > > produce 0% duty cycle instead of whatever was requested. Rounding rate
>>> >> >> > > will introduce significant complexity into the driver. Most of the time
>>> >> >> > > if a consumer requests an invalid rate, it is due to misconfiguration
>>> >> >> > > which is best solved by fixing the configuration.
>>> >> >> >
>>> >> >> > In the first step pick the biggest period not bigger than the requested
>>> >> >> > and then pick the biggest duty cycle that is not bigger than the
>>> >> >> > requested and that can be set with the just picked period. That is the
>>> >> >> > behaviour that all new drivers should do. This is somewhat arbitrary but
>>> >> >> > after quite some thought the most sensible in my eyes.
>>> >> >>
>>> >> >> And if there are no periods smaller than the requested period?
>>> >> >
>>> >> > Then return -ERANGE.
>>> >>
>>> >> Ok, so instead of
>>> >>
>>> >>???? if (cycles < 2 || cycles > priv->max + 2)
>>> >>???????? return -ERANGE;
>>> >>
>>> >> you would prefer
>>> >>
>>> >>???? if (cycles < 2)
>>> >>???????? return -ERANGE;
>>> >>???? else if (cycles > priv->max + 2)
>>> >>???????? cycles = priv->max;
>>> >
>>> > The actual calculation is a bit harder to handle TCSR_UDT = 0 but in
>>> > principle, yes, but see below.
>>> >
>>> >> But if we do the above clamping for TLR0, then we have to recalculate
>>> >> the duty cycle for TLR1. Which I guess means doing something like
>>> >>
>>> >>???? ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
>>> >>???? if (ret)
>>> >>???????? return ret;
>>> >>
>>> >>???? state->duty_cycle = mult_frac(state->duty_cycle,
>>> >>?????????????????????? xilinx_timer_get_period(priv, tlr0, tcsr0),
>>> >>?????????????????????? state->period);
>>> >>
>>> >>???? ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
>>> >>???? if (ret)
>>> >>???????? return ret;
>>> >
>>> > No, you need something like:
>>> >
>>> >???? /*
>>> >????? * The multiplication cannot overflow as both priv_max and
>>> >????? * NSEC_PER_SEC fit into an u32.
>>> >????? */
>>> >???? max_period = div64_ul((u64)priv->max * NSEC_PER_SEC, clkrate);
>>> >
>>> >???? /* cap period to the maximal possible value */
>>> >???? if (state->period > max_period)
>>> >???????? period = max_period;
>>> >???? else
>>> >???????? period = state->period;
>>> >
>>> >???? /* cap duty_cycle to the maximal possible value */
>>> >???? if (state->duty_cycle > max_period)
>>> >???????? duty_cycle = max_period;
>>> >???? else
>>> >???????? duty_cycle = state->duty_cycle;
>>>
>>> These caps may increase the % duty cycle.
>>
>> Correct.
>>
>> For some usecases keeping the relative duty cycle might be better, for
>> others it might not. I'm still convinced that in general my solution
>> makes sense, is computationally cheaper and easier to work with.
>
> Can you please describe one of those use cases? Every PWM user I looked
> (grepping for pwm_apply_state and pwm_config) set the duty cycle as a
> percentage of the period, and not as an absolute time. Keeping the high
> time the same while changing the duty cycle runs contrary to the
> assumptions of all of those users.
>
>>>
>>> >???? period_cycles = period * clkrate / NSEC_PER_SEC;
>>> >
>>> >???? if (period_cycles < 2)
>>> >???????? return -ERANGE;
>>> >
>>> >???? duty_cycles = duty_cycle * clkrate / NSEC_PER_SEC;
>>> >
>>> >???? /*
>>> >????? * The hardware cannot emit a 100% relative duty cycle, if
>>> >????? * duty_cycle >= period_cycles is programmed the hardware emits
>>> >????? * a 0% relative duty cycle.
>>> >????? */
>>> >???? if (duty_cycle == period_cycles)
>>> >???????? duty_cycles = period_cycles - 1;
>>> >
>>> >???? /*
>>> >????? * The hardware cannot emit a duty_cycle of one clk step, so
>>> >????? * emit 0 instead.
>>> >????? */
>>> >???? if (duty_cycles < 2)
>>> >???????? duty_cycles = period_cycles;
>>>
>>> Of course, the above may result in 100% duty cycle being rounded down to
>>> 0%. I feel like that is too big of a jump to ignore. Perhaps if we
>>> cannot return -ERANGE we should at least dev_warn.
>>
>> You did it again. You picked one single case that you consider bad but
>> didn't provide a constructive way to make it better.
>
> Sure I did. I suggested that we warn. Something like
>
> if (duty_cycles == period_cycles)
> ????if (--duty_cycles < 2)
> ??????? dev_warn(chip->dev, "Rounding 100%% duty cycle down to 0%%; pick a longer period\n");
>
> or
>
> if (period_cycles < 2)
> ????return -ERANGE;
> else if (period_cycles < 10)
> ????dev_notice(chip->dev,
> ?????????? "very short period of %u cycles; duty cycle may be rounded to 0%%\n",
> ?????????? period_cycles);
>
> Because 90% of the time, if a user requests such a short period it is
> due to a typo or something similar. And if they really are doing it
> intentionally, then they should just set duty_cycle=0.
>
>> Assume there was already a pwm_round_state function (that returns the
>> state that pwm_apply_state would implement for a given request) Consider
>> a consumer that wants say a 50% relative duty together with a small
>> period. So it first might call:
>>
>> ????ret = pwm_round_rate(pwm, { .period = 20, .duty_cycle = 20, ... }, &rounded_state)
>>
>> to find out if .period = 20 can be implemented with the given PWM. If
>> this returns rounded state as:
>>
>> ????.period = 20
>> ????.duty_cycle = 0
>>
>> this says quite a lot about the pwm if the driver implements my policy.
>> (i.e.: The driver can do 20ns, but the biggest duty_cycle is only 0).
>> If however it returns -ERANGE this means (assuming the driver implements
>> the policy I try to convice you to be the right one) it means: The
>> hardware cannot implement 20 ns (or something smaller) and so the next
>> call probably tries 40 ns.
>>
>> With your suggested semantic -ERANGE might mean:
>>
>> ? - The driver doesn't support .period = 20 ns
>> ??? (Follow up questions: What period should be tried next? 10 ns? 40
>> ??? ns? What if this returns -ERANGE again?)
>> ? - The driver supports .period = 20 ns, but the biggest possible
>> ??? duty_cycle is "too different from 20 ns to ignore".
>>
>> Then how should the search continue?
>
> round_rate does not have to use the same logic as apply_state. That is,
>
> ????ret = pwm_round_rate(pwm, { .period = 20, .duty_cycle = 20, ... }, &rounded_state)
>
> could be rounded to
>
> ????{ .period = 20, .duty_cycle = 0 }
>
> but calling
>
> ????ret = pwm_apply_state(pwm, { .period = 20, .duty_cycle = 20, ... })
>
> could return -ERANGE. However, calling
>
> ????ret = pwm_apply_state(pwm, { .period = 20, .duty_cycle = 0, ... })
>
> should work just fine, as the caller clearly knows what they are getting
> into. IMO this is the best way to allow hypothetical round_rate users to
> find out the edges of the PWM while still protecting existing users.
>
> It's perfectly fine to round
>
> ????{ .period = 150, .duty_cycle = 75 }
>
> to
>
> ????{ .period = 100, .duty_cycle = 75 }
>
> in round_rate. But doing the same thing for apply_state would be very
> surprising to every existing PWM user.
>
> IMO the following invariant should hold
>
> ????apply_state(round_rate(x))
> ????assert(x == get_state())

this should be

apply_state(round_rate(x))
assert(round_rate(x) == get_state())

>
> but the following should not necessarily hold
>
> ????apply_state(x)
> ????assert(round_rate(x) == get_state())
>
> Of course, where it is reasonable to round down, we should do so. But
> where the result may be surprising, then the caller should specify the
> rounded state specifically. It is better to fail loudly and noisily than
> to silently accept garbage.
>
> --Sean
>
>>
>> So: As soon as there is a pwm_round_rate function this can be catched
>> and then it's important that the drivers adhere to the expected policy.
>> Implementing this is a big thing and believe me I already spend quite
>> some brain cycles into it. Once the core is extended accordingly I will
>> be happy about each driver already doing the right thing.
>>
>> Best regards
>> Uwe
>>

2021-06-30 08:36:28

by Uwe Kleine-König

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

Hello Sean,

I often mistype the name of the rounding function as "pwm_round_rate",
the better name is "pwm_round_state" of course. That's just me thinking
about clk_round_rate where ".._rate" is the right term. I'll try harder
to get this right from now on.

On Tue, Jun 29, 2021 at 06:21:15PM -0400, Sean Anderson wrote:
> On 6/29/21 4:51 PM, Uwe Kleine-K?nig wrote:
> > On Tue, Jun 29, 2021 at 02:01:31PM -0400, Sean Anderson wrote:
> > > On 6/29/21 4:31 AM, Uwe Kleine-K?nig wrote:
> > > > On Mon, Jun 28, 2021 at 01:41:43PM -0400, Sean Anderson wrote:
> > > >> On 6/28/21 1:20 PM, Uwe Kleine-K?nig wrote:
> > > >> > On Mon, Jun 28, 2021 at 12:35:19PM -0400, Sean Anderson wrote:
> > > >> >> On 6/28/21 12:24 PM, Uwe Kleine-K?nig wrote:
> > > >> >> > On Mon, Jun 28, 2021 at 11:50:33AM -0400, Sean Anderson wrote:
> > > >> >> > > On 6/27/21 2:19 PM, Uwe Kleine-K?nig wrote:
> > > >> >> > > > On Fri, Jun 25, 2021 at 01:46:26PM -0400, Sean Anderson wrote:
> > > >> >> > > IMO, this is the best way to prevent surprising results in the API.
> > > >> >> >
> > > >> >> > I think it's not possible in practise to refuse "near" misses and every
> > > >> >> > definition of "near" is in some case ridiculous. Also if you consider
> > > >> >> > the pwm_round_state() case you don't want to refuse any request to tell
> > > >> >> > as much as possible about your controller's capabilities. And then it's
> > > >> >> > straight forward to let apply behave in the same way to keep complexity
> > > >> >> > low.
> > > >> >> >
> > > >> >> > > The real issue here is that it is impossible to determine the correct
> > > >> >> > > way to round the PWM a priori, and in particular, without considering
> > > >> >> > > both duty_cycle and period. If a consumer requests very small
> > > >> >> > > period/duty cycle which we cannot produce, how should it be rounded?
> > > >> >> >
> > > >> >> > Yeah, because there is no obviously right one, I picked one that is as
> > > >> >> > wrong as the other possibilities but is easy to work with.
> > > >> >> >
> > > >> >> > > Should we just set TLR0=1 and TLR1=0 to give them 66% duty cycle with
> > > >> >> > > the least period? Or should we try and increase the period to better
> > > >> >> > > approximate the % duty cycle? And both of these decisions must be made
> > > >> >> > > knowing both parameters. We cannot (for example) just always round up,
> > > >> >> > > since we may produce a configuration with TLR0 == TLR1, which would
> > > >> >> > > produce 0% duty cycle instead of whatever was requested. Rounding rate
> > > >> >> > > will introduce significant complexity into the driver. Most of the time
> > > >> >> > > if a consumer requests an invalid rate, it is due to misconfiguration
> > > >> >> > > which is best solved by fixing the configuration.
> > > >> >> >
> > > >> >> > In the first step pick the biggest period not bigger than the requested
> > > >> >> > and then pick the biggest duty cycle that is not bigger than the
> > > >> >> > requested and that can be set with the just picked period. That is the
> > > >> >> > behaviour that all new drivers should do. This is somewhat arbitrary but
> > > >> >> > after quite some thought the most sensible in my eyes.
> > > >> >>
> > > >> >> And if there are no periods smaller than the requested period?
> > > >> >
> > > >> > Then return -ERANGE.
> > > >>
> > > >> Ok, so instead of
> > > >>
> > > >> if (cycles < 2 || cycles > priv->max + 2)
> > > >> return -ERANGE;
> > > >>
> > > >> you would prefer
> > > >>
> > > >> if (cycles < 2)
> > > >> return -ERANGE;
> > > >> else if (cycles > priv->max + 2)
> > > >> cycles = priv->max;
> > > >
> > > > The actual calculation is a bit harder to handle TCSR_UDT = 0 but in
> > > > principle, yes, but see below.
> > > >
> > > >> But if we do the above clamping for TLR0, then we have to recalculate
> > > >> the duty cycle for TLR1. Which I guess means doing something like
> > > >>
> > > >> ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
> > > >> if (ret)
> > > >> return ret;
> > > >>
> > > >> state->duty_cycle = mult_frac(state->duty_cycle,
> > > >> xilinx_timer_get_period(priv, tlr0, tcsr0),
> > > >> state->period);
> > > >>
> > > >> ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
> > > >> if (ret)
> > > >> return ret;
> > > >
> > > > No, you need something like:
> > > >
> > > > /*
> > > > * The multiplication cannot overflow as both priv_max and
> > > > * NSEC_PER_SEC fit into an u32.
> > > > */
> > > > max_period = div64_ul((u64)priv->max * NSEC_PER_SEC, clkrate);
> > > >
> > > > /* cap period to the maximal possible value */
> > > > if (state->period > max_period)
> > > > period = max_period;
> > > > else
> > > > period = state->period;
> > > >
> > > > /* cap duty_cycle to the maximal possible value */
> > > > if (state->duty_cycle > max_period)
> > > > duty_cycle = max_period;
> > > > else
> > > > duty_cycle = state->duty_cycle;
> > >
> > > These caps may increase the % duty cycle.
> >
> > Correct.
> >
> > For some usecases keeping the relative duty cycle might be better, for
> > others it might not. I'm still convinced that in general my solution
> > makes sense, is computationally cheaper and easier to work with.
>
> Can you please describe one of those use cases? Every PWM user I looked
> (grepping for pwm_apply_state and pwm_config) set the duty cycle as a
> percentage of the period, and not as an absolute time. Keeping the high
> time the same while changing the duty cycle runs contrary to the
> assumptions of all of those users.

Indeed there is no mainline driver that relies on this. There are some
smart LED controllers (e.g. WS2812B) where the duty_cycle is more
important than the period. (I admit a PWM is not really the right driver
for that one as it could only completely enable and complete disable
white color.) Also there are some servo motor chips where the absolute
duty is relevant but the period isn't (in some range). (See
https://www.mikrocontroller.net/articles/Modellbauservo_Ansteuerung#Signalaufbau
for a article about that (in German though).)

In case you want to argue that out-of-mainline users don't count: I
think in the design of an API they do count to place the bar to enter
the mainline low. Frameworks should be generic enough to cover as much
use cases as possible.

And note that if you want a nearest to (say) 50% relative duty cycle and
don't care much about the period it doesn't really matter if you scale
duty_cycle in pwm_round_state() to the period change or not because in
general you need several calls to pwm_round_state() anyhow to find a
setting with 51% if the next lower possibility is 47%. So in the end you
save (I think) one call in generic PWM code.

In contrast the math gets quite a bit more complicated because there is
rounding involved in scaling the duty cycle. Consider a PWM that can
configure period and duty in 16.4 ns steps and you ask for

.period = 100 ns
.duty_cycle = 50 ns

Then the best period you can provide is 98.4 ns, so you return .period =
99 from pwm_round_state(). (Yes, you don't return 98, because
round-nearest is much harder to handle than round down.) To determine
the adapted duty_cycle you have to do

50 * realperiod / 100

which independently of choosing 98, 98.4 or 99 for realperiod is 49. Then
to approximate 49 without rounding up you end up with 32.8 while 49.2
would have be perfectly fine.

You might find a way around that (maybe you have to round up in the
adaption of duty_cycle, I didn't convince myself this is good enough
though).

So your suggestion to adapt the duty_cycle to keep the relative
duty_cycle constant (as good as possible within the bounds the hardware
dictates) implies additional complication at the driver level.

From a framework maintainer's point of view (and also from a low-level
driver maintainer's point of view) I prefer one complication in a
generic function over a complication that I have to care for in each and
every low-level driver by a big margin.

So unless you volunteer to complete the math above and promise to review
low-level drivers for that aspect in the future (or alternatively
convince me that math is easy and I missed something) I would like to
end this discussion here and stay with the policy I explained.

> > > > period_cycles = period * clkrate / NSEC_PER_SEC;
> > > >
> > > > if (period_cycles < 2)
> > > > return -ERANGE;
> > > >
> > > > duty_cycles = duty_cycle * clkrate / NSEC_PER_SEC;
> > > >
> > > > /*
> > > > * The hardware cannot emit a 100% relative duty cycle, if
> > > > * duty_cycle >= period_cycles is programmed the hardware emits
> > > > * a 0% relative duty cycle.
> > > > */
> > > > if (duty_cycle == period_cycles)
> > > > duty_cycles = period_cycles - 1;
> > > >
> > > > /*
> > > > * The hardware cannot emit a duty_cycle of one clk step, so
> > > > * emit 0 instead.
> > > > */
> > > > if (duty_cycles < 2)
> > > > duty_cycles = period_cycles;
> > >
> > > Of course, the above may result in 100% duty cycle being rounded down to
> > > 0%. I feel like that is too big of a jump to ignore. Perhaps if we
> > > cannot return -ERANGE we should at least dev_warn.
> >
> > You did it again. You picked one single case that you consider bad but
> > didn't provide a constructive way to make it better.
>
> Sure I did. I suggested that we warn. Something like
>
> if (duty_cycles == period_cycles)
> if (--duty_cycles < 2)
> dev_warn(chip->dev, "Rounding 100%% duty cycle down to 0%%; pick a longer period\n");
>
> or
>
> if (period_cycles < 2)
> return -ERANGE;
> else if (period_cycles < 10)
> dev_notice(chip->dev,
> "very short period of %u cycles; duty cycle may be rounded to 0%%\n",
> period_cycles);

Ah ok, so only a 100% jump warrants that warning. I think adding that
has no practical relevance, so I don't oppose to that. Add it if you
want. (But note that if it triggers indeed it might flood the kernel log
if your consumer wants to start a motor but notices it doesn't run fast
enough and so configures 100% in a tight loop. So I would recommend some
rate limiting.)

> Because 90% of the time, if a user requests such a short period it is
> due to a typo or something similar. And if they really are doing it
> intentionally, then they should just set duty_cycle=0.

Uh, don't you think that a warning that is wrong in 10% of the cases is
bad?

> > Assume there was already a pwm_round_state function (that returns the
> > state that pwm_apply_state would implement for a given request) Consider
> > a consumer that wants say a 50% relative duty together with a small
> > period. So it first might call:
> >
> > ret = pwm_round_rate(pwm, { .period = 20, .duty_cycle = 20, ... }, &rounded_state)
> >
> > to find out if .period = 20 can be implemented with the given PWM. If
> > this returns rounded state as:
> >
> > .period = 20
> > .duty_cycle = 0
> >
> > this says quite a lot about the pwm if the driver implements my policy.
> > (i.e.: The driver can do 20ns, but the biggest duty_cycle is only 0).
> > If however it returns -ERANGE this means (assuming the driver implements
> > the policy I try to convice you to be the right one) it means: The
> > hardware cannot implement 20 ns (or something smaller) and so the next
> > call probably tries 40 ns.
> >
> > With your suggested semantic -ERANGE might mean:
> >
> > - The driver doesn't support .period = 20 ns
> > (Follow up questions: What period should be tried next? 10 ns? 40
> > ns? What if this returns -ERANGE again?)
> > - The driver supports .period = 20 ns, but the biggest possible
> > duty_cycle is "too different from 20 ns to ignore".
> >
> > Then how should the search continue?
>
> round_rate does not have to use the same logic as apply_state.

I want to have .round_state() and .apply() (i.e. the driver callbacks)
to behave identically. If we indeed come to the conclusion that
pwm_apply_state needs to have some precautions, I'd like to have them
implemented in pwm_apply_state() only and not in every driver.

> However, calling
>
> ret = pwm_apply_state(pwm, { .period = 20, .duty_cycle = 0, ... })
>
> should work just fine, as the caller clearly knows what they are getting
> into. IMO this is the best way to allow hypothetical round_rate users to
> find out the edges of the PWM while still protecting existing users.
>
> It's perfectly fine to round
>
> { .period = 150, .duty_cycle = 75 }
>
> to
>
> { .period = 100, .duty_cycle = 75 }
>
> in round_rate. But doing the same thing for apply_state would be very
> surprising to every existing PWM user.
>
> IMO the following invariant should hold
>
> apply_state(round_rate(x))
> assert(round_rate(x) == get_state())

(merged your correction of the follow up mail into the quote above)

(Fun fact: Only needing this one would allow a generic implementation of
round_state, it just had to return a pwm_state that doesn't depend on x
:o)

> but the following should not necessarily hold
>
> apply_state(x)
> assert(round_rate(x) == get_state())
>
> Of course, where it is reasonable to round down, we should do so.
>
> But where the result may be surprising, then the caller should specify
> the rounded state specifically. It is better to fail loudly and
> noisily than
> to silently accept garbage.

Can you please come up with an algorithm to judge if a given deviation
is reasonable or surprising? I agree there are surprises and some of
them are obviously bad. For most cases however the judgement depends on
the use case so I fail to see how someone should program such a check
that should cover all consumers and use cases. I prefer no precautions +
an easy relation between pwm_round_state and pwm_apply_state (i.e.
behave identically) over a most of the time(?) useless precaution and
some policy defined differences between pwm_round_state and
pwm_apply_state

Best regards
Uwe

--
Pengutronix e.K. | Uwe Kleine-K?nig |
Industrial Linux Solutions | https://www.pengutronix.de/ |


Attachments:
(No filename) (14.13 kB)
signature.asc (499.00 B)
Download all attachments

2021-06-30 13:49:08

by Michal Simek

[permalink] [raw]
Subject: Re: [PATCH v4 1/3] dt-bindings: pwm: Add Xilinx AXI Timer



On 5/28/21 11:45 PM, Sean Anderson wrote:
> This adds a binding for the Xilinx LogiCORE IP AXI Timer. This device is
> a "soft" block, so it has many parameters which would not be
> configurable in most hardware. This binding is usually automatically
> generated by Xilinx's tools, so the names and values of some properties
> must be kept as they are. Replacement properties have been provided for
> new device trees.
>
> Because we need to init timer devices so early in boot, the easiest way
> to configure things is to use a device tree property. For the moment
> this is 'xlnx,pwm', but this could be extended/renamed/etc. in the
> future if these is a need for a generic property.
>
> Signed-off-by: Sean Anderson <[email protected]>
> ---
>
> Changes in v4:
> - Remove references to generate polarity so this can get merged
> - Predicate PWM driver on the presence of #pwm-cells
> - Make some properties optional for clocksource drivers
>
> Changes in v3:
> - Mark all boolean-as-int properties as deprecated
> - Add xlnx,pwm and xlnx,gen?-active-low properties.
> - Make newer replacement properties mutually-exclusive with what they
> replace
> - Add an example with non-deprecated properties only.
>
> Changes in v2:
> - Use 32-bit addresses for example binding
>
> .../bindings/pwm/xlnx,axi-timer.yaml | 85 +++++++++++++++++++
> 1 file changed, 85 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>
> diff --git a/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
> new file mode 100644
> index 000000000000..48a280f96e63
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml

I don't think this is the right location for this.

I have done some grepping and I think this should be done in a different
way. I pretty much like solution around "ti,omap3430-timer" which is
calling dmtimer_systimer_select_best() and later dmtimer_is_preferred()
which in this case would allow us to get rid of cases which are not
suitable for clocksource and clockevent.

And there is drivers/pwm/pwm-omap-dmtimer.c which has link to timer
which is providing functions for it's functionality.

I have also looked at
Documentation/devicetree/bindings/timer/nxp,tpm-timer.yaml which is also
the same device.

And sort of curious if you look at
https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v2_0/pg079-axi-timer.pdf
( Figure 1-1)
that PWM is taking input from generate out 0 and generate out 1 which is
maybe can be modeled is any output and pwm driver can register inputs
for pwm driver.


> @@ -0,0 +1,85 @@
> +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/pwm/xlnx,axi-timer.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Xilinx LogiCORE IP AXI Timer Device Tree Binding
> +
> +maintainers:
> + - Sean Anderson <[email protected]>
> +
> +properties:
> + compatible:
> + oneOf:
> + - items:
> + - const: xlnx,axi-timer-2.0
> + - const: xlnx,xps-timer-1.00.a
> + - items:
> + - const: xlnx,xps-timer-1.00.a
> +
> + clocks:
> + maxItems: 1
> +
> + clock-names:
> + const: s_axi_aclk

Origin driver is not using this clock name and it is only one that's why
it shouldn't be listed.

> +
> + interrupts:
> + maxItems: 1
> +
> + reg:
> + maxItems: 1
> +
> + xlnx,count-width:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + minimum: 8
> + maximum: 32
> + default: 32

This is not accurate. It should be enum because only 8/16/32 are valid
values here.

> + description:
> + The width of the counter(s), in bits.
> +
> + xlnx,one-timer-only:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + enum: [ 0, 1 ]
> + description:
> + Whether only one timer is present in this block.
> +
> +required:
> + - compatible
> + - reg
> + - xlnx,one-timer-only
> +
> +allOf:
> + - if:
> + required:
> + - '#pwm-cells'

Let's discussed this usage based on design.

> + then:
> + allOf:
> + - required:
> + - clocks
> + - properties:
> + xlnx,one-timer-only:
> + const: 0
> + else:
> + required:
> + - interrupts
> + - if:
> + required:
> + - clocks
> + then:
> + required:
> + - clock-names

And this checking should be removed too.

> +
> +additionalProperties: true
> +
> +examples:
> + - |
> + axi_timer_0: timer@800e0000 {

label is useless here and should be removed.

> + #pwm-cells = <0>;
> + clock-names = "s_axi_aclk";
> + clocks = <&zynqmp_clk 71>;
> + compatible = "xlnx,axi-timer-2.0", "xlnx,xps-timer-1.00.a";
> + reg = <0x800e0000 0x10000>;
> + xlnx,count-width = <0x20>;
> + xlnx,one-timer-only = <0x0>;
> + };
>

I would list example without pwm-cells first as it is valid and reflect
current status.

Thanks,
Michal


--
Michal Simek, Ing. (M.Eng), OpenPGP -> KeyID: FE3D1F91
w: http://www.monstr.eu p: +42-0-721842854
Maintainer of Linux kernel - Xilinx Microblaze
Maintainer of Linux kernel - Xilinx Zynq ARM and ZynqMP ARM64 SoCs
U-Boot custodian - Xilinx Microblaze/Zynq/ZynqMP/Versal SoCs

2021-06-30 14:02:56

by Michal Simek

[permalink] [raw]
Subject: Re: [PATCH v4 1/3] dt-bindings: pwm: Add Xilinx AXI Timer



On 6/30/21 3:47 PM, Michal Simek wrote:
>
>
> On 5/28/21 11:45 PM, Sean Anderson wrote:
>> This adds a binding for the Xilinx LogiCORE IP AXI Timer. This device is
>> a "soft" block, so it has many parameters which would not be
>> configurable in most hardware. This binding is usually automatically
>> generated by Xilinx's tools, so the names and values of some properties
>> must be kept as they are. Replacement properties have been provided for
>> new device trees.
>>
>> Because we need to init timer devices so early in boot, the easiest way
>> to configure things is to use a device tree property. For the moment
>> this is 'xlnx,pwm', but this could be extended/renamed/etc. in the
>> future if these is a need for a generic property.
>>
>> Signed-off-by: Sean Anderson <[email protected]>
>> ---
>>
>> Changes in v4:
>> - Remove references to generate polarity so this can get merged
>> - Predicate PWM driver on the presence of #pwm-cells
>> - Make some properties optional for clocksource drivers
>>
>> Changes in v3:
>> - Mark all boolean-as-int properties as deprecated
>> - Add xlnx,pwm and xlnx,gen?-active-low properties.
>> - Make newer replacement properties mutually-exclusive with what they
>> replace
>> - Add an example with non-deprecated properties only.
>>
>> Changes in v2:
>> - Use 32-bit addresses for example binding
>>
>> .../bindings/pwm/xlnx,axi-timer.yaml | 85 +++++++++++++++++++
>> 1 file changed, 85 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>> new file mode 100644
>> index 000000000000..48a280f96e63
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>
> I don't think this is the right location for this.
>
> I have done some grepping and I think this should be done in a different
> way. I pretty much like solution around "ti,omap3430-timer" which is
> calling dmtimer_systimer_select_best() and later dmtimer_is_preferred()
> which in this case would allow us to get rid of cases which are not
> suitable for clocksource and clockevent.
>
> And there is drivers/pwm/pwm-omap-dmtimer.c which has link to timer
> which is providing functions for it's functionality.
>
> I have also looked at
> Documentation/devicetree/bindings/timer/nxp,tpm-timer.yaml which is also
> the same device.
>
> And sort of curious if you look at
> https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v2_0/pg079-axi-timer.pdf
> ( Figure 1-1)
> that PWM is taking input from generate out 0 and generate out 1 which is
> maybe can be modeled is any output and pwm driver can register inputs
> for pwm driver.
>
>
>> @@ -0,0 +1,85 @@
>> +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/pwm/xlnx,axi-timer.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Xilinx LogiCORE IP AXI Timer Device Tree Binding
>> +
>> +maintainers:
>> + - Sean Anderson <[email protected]>
>> +
>> +properties:
>> + compatible:
>> + oneOf:
>> + - items:
>> + - const: xlnx,axi-timer-2.0

I am not quite sure if make sense also to list 2.0 version.
There were likely also 1.0 version which is compatible with origin xps
version which IIRC was PLB based. And the same driver was using in past
with OPB bus.

Thanks,
Michal

2021-07-01 15:35:36

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 1/3] dt-bindings: pwm: Add Xilinx AXI Timer



On 6/30/21 9:47 AM, Michal Simek wrote:
>
>
> On 5/28/21 11:45 PM, Sean Anderson wrote:
>> This adds a binding for the Xilinx LogiCORE IP AXI Timer. This device is
>> a "soft" block, so it has many parameters which would not be
>> configurable in most hardware. This binding is usually automatically
>> generated by Xilinx's tools, so the names and values of some properties
>> must be kept as they are. Replacement properties have been provided for
>> new device trees.
>>
>> Because we need to init timer devices so early in boot, the easiest way
>> to configure things is to use a device tree property. For the moment
>> this is 'xlnx,pwm', but this could be extended/renamed/etc. in the
>> future if these is a need for a generic property.
>>
>> Signed-off-by: Sean Anderson <[email protected]>
>> ---
>>
>> Changes in v4:
>> - Remove references to generate polarity so this can get merged
>> - Predicate PWM driver on the presence of #pwm-cells
>> - Make some properties optional for clocksource drivers
>>
>> Changes in v3:
>> - Mark all boolean-as-int properties as deprecated
>> - Add xlnx,pwm and xlnx,gen?-active-low properties.
>> - Make newer replacement properties mutually-exclusive with what they
>> replace
>> - Add an example with non-deprecated properties only.
>>
>> Changes in v2:
>> - Use 32-bit addresses for example binding
>>
>> .../bindings/pwm/xlnx,axi-timer.yaml | 85 +++++++++++++++++++
>> 1 file changed, 85 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>> new file mode 100644
>> index 000000000000..48a280f96e63
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>
> I don't think this is the right location for this.
>
> I have done some grepping and I think this should be done in a different
> way. I pretty much like solution around "ti,omap3430-timer" which is
> calling dmtimer_systimer_select_best() and later dmtimer_is_preferred()
> which in this case would allow us to get rid of cases which are not
> suitable for clocksource and clockevent.
>
> And there is drivers/pwm/pwm-omap-dmtimer.c which has link to timer
> which is providing functions for it's functionality.
>
> I have also looked at
> Documentation/devicetree/bindings/timer/nxp,tpm-timer.yaml which is also
> the same device.

Ok, I will move this under bindings/timer.

>
> And sort of curious if you look at
> https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v2_0/pg079-axi-timer.pdf
> ( Figure 1-1)
> that PWM is taking input from generate out 0 and generate out 1 which is
> maybe can be modeled is any output and pwm driver can register inputs
> for pwm driver.

I don't think that is a good model, since several bits (GENERATE, PWM,
etc) need to be set in the TCSR, and we need to coordinate changes
between timers closely to keep our contract for apply_state(). Although
that is how the hardware is organized, the requirements of the
clocksource and pwm subsystems are very different.

>> @@ -0,0 +1,85 @@
>> +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/pwm/xlnx,axi-timer.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Xilinx LogiCORE IP AXI Timer Device Tree Binding
>> +
>> +maintainers:
>> + - Sean Anderson <[email protected]>
>> +
>> +properties:
>> + compatible:
>> + oneOf:
>> + - items:
>> + - const: xlnx,axi-timer-2.0
>> + - const: xlnx,xps-timer-1.00.a
>> + - items:
>> + - const: xlnx,xps-timer-1.00.a
>> +
>> + clocks:
>> + maxItems: 1
>> +
>> + clock-names:
>> + const: s_axi_aclk
>
> Origin driver is not using this clock name and it is only one that's why
> it shouldn't be listed.

The old driver does not use the clocks property. So this property is
only required for new bindings. Note that if the clocks property is not
present, we fall back to clock-frequency, and if that is not present we
fall back to /cpus/timebase-frequency. However, these methods are
deprecated, so they are not documented here.

>
>> +
>> + interrupts:
>> + maxItems: 1
>> +
>> + reg:
>> + maxItems: 1
>> +
>> + xlnx,count-width:
>> + $ref: /schemas/types.yaml#/definitions/uint32
>> + minimum: 8
>> + maximum: 32
>> + default: 32
>
> This is not accurate. It should be enum because only 8/16/32 are valid
> values here.

According to the datasheet the allowable values are "8-32", so that is
what I put. Perhaps it should be updated?

>
>> + description:
>> + The width of the counter(s), in bits.
>> +
>> + xlnx,one-timer-only:
>> + $ref: /schemas/types.yaml#/definitions/uint32
>> + enum: [ 0, 1 ]
>> + description:
>> + Whether only one timer is present in this block.
>> +
>> +required:
>> + - compatible
>> + - reg
>> + - xlnx,one-timer-only
>> +
>> +allOf:
>> + - if:
>> + required:
>> + - '#pwm-cells'
>
> Let's discussed this usage based on design.

I don't understand what you mean by this.

>
>> + then:
>> + allOf:
>> + - required:
>> + - clocks
>> + - properties:
>> + xlnx,one-timer-only:
>> + const: 0
>> + else:
>> + required:
>> + - interrupts
>> + - if:
>> + required:
>> + - clocks
>> + then:
>> + required:
>> + - clock-names
>
> And this checking should be removed too.

See above.

>
>> +
>> +additionalProperties: true
>> +
>> +examples:
>> + - |
>> + axi_timer_0: timer@800e0000 {
>
> label is useless here and should be removed.

Ok.

>
>> + #pwm-cells = <0>;
>> + clock-names = "s_axi_aclk";
>> + clocks = <&zynqmp_clk 71>;
>> + compatible = "xlnx,axi-timer-2.0", "xlnx,xps-timer-1.00.a";
>> + reg = <0x800e0000 0x10000>;
>> + xlnx,count-width = <0x20>;
>> + xlnx,one-timer-only = <0x0>;
>> + };
>>
>
> I would list example without pwm-cells first as it is valid and reflect
> current status.

Ok.

--Sean

2021-07-01 15:42:37

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 1/3] dt-bindings: pwm: Add Xilinx AXI Timer



On 6/30/21 9:58 AM, Michal Simek wrote:
>
>
> On 6/30/21 3:47 PM, Michal Simek wrote:
>>
>>
>> On 5/28/21 11:45 PM, Sean Anderson wrote:
>>> This adds a binding for the Xilinx LogiCORE IP AXI Timer. This device is
>>> a "soft" block, so it has many parameters which would not be
>>> configurable in most hardware. This binding is usually automatically
>>> generated by Xilinx's tools, so the names and values of some properties
>>> must be kept as they are. Replacement properties have been provided for
>>> new device trees.
>>>
>>> Because we need to init timer devices so early in boot, the easiest way
>>> to configure things is to use a device tree property. For the moment
>>> this is 'xlnx,pwm', but this could be extended/renamed/etc. in the
>>> future if these is a need for a generic property.
>>>
>>> Signed-off-by: Sean Anderson <[email protected]>
>>> ---
>>>
>>> Changes in v4:
>>> - Remove references to generate polarity so this can get merged
>>> - Predicate PWM driver on the presence of #pwm-cells
>>> - Make some properties optional for clocksource drivers
>>>
>>> Changes in v3:
>>> - Mark all boolean-as-int properties as deprecated
>>> - Add xlnx,pwm and xlnx,gen?-active-low properties.
>>> - Make newer replacement properties mutually-exclusive with what they
>>> replace
>>> - Add an example with non-deprecated properties only.
>>>
>>> Changes in v2:
>>> - Use 32-bit addresses for example binding
>>>
>>> .../bindings/pwm/xlnx,axi-timer.yaml | 85 +++++++++++++++++++
>>> 1 file changed, 85 insertions(+)
>>> create mode 100644 Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>>
>>> diff --git a/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>> new file mode 100644
>>> index 000000000000..48a280f96e63
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>
>> I don't think this is the right location for this.
>>
>> I have done some grepping and I think this should be done in a different
>> way. I pretty much like solution around "ti,omap3430-timer" which is
>> calling dmtimer_systimer_select_best() and later dmtimer_is_preferred()
>> which in this case would allow us to get rid of cases which are not
>> suitable for clocksource and clockevent.
>>
>> And there is drivers/pwm/pwm-omap-dmtimer.c which has link to timer
>> which is providing functions for it's functionality.
>>
>> I have also looked at
>> Documentation/devicetree/bindings/timer/nxp,tpm-timer.yaml which is also
>> the same device.
>>
>> And sort of curious if you look at
>> https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v2_0/pg079-axi-timer.pdf
>> ( Figure 1-1)
>> that PWM is taking input from generate out 0 and generate out 1 which is
>> maybe can be modeled is any output and pwm driver can register inputs
>> for pwm driver.
>>
>>
>>> @@ -0,0 +1,85 @@
>>> +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
>>> +%YAML 1.2
>>> +---
>>> +$id: http://devicetree.org/schemas/pwm/xlnx,axi-timer.yaml#
>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>> +
>>> +title: Xilinx LogiCORE IP AXI Timer Device Tree Binding
>>> +
>>> +maintainers:
>>> + - Sean Anderson <[email protected]>
>>> +
>>> +properties:
>>> + compatible:
>>> + oneOf:
>>> + - items:
>>> + - const: xlnx,axi-timer-2.0
>
> I am not quite sure if make sense also to list 2.0 version.
> There were likely also 1.0 version which is compatible with origin xps
> version which IIRC was PLB based. And the same driver was using in past
> with OPB bus.

It's required to list all compatible properties which may be used in a
binding. And AFAIK it is good practice to add a new compatible string
for new releases of an IP, in case incompatibilities are discovered.

--Sean

2021-07-02 11:39:07

by Michal Simek

[permalink] [raw]
Subject: Re: [PATCH v4 1/3] dt-bindings: pwm: Add Xilinx AXI Timer



On 7/1/21 5:38 PM, Sean Anderson wrote:
>
>
> On 6/30/21 9:58 AM, Michal Simek wrote:
>>
>>
>> On 6/30/21 3:47 PM, Michal Simek wrote:
>>>
>>>
>>> On 5/28/21 11:45 PM, Sean Anderson wrote:
>>>> This adds a binding for the Xilinx LogiCORE IP AXI Timer. This
> device is
>>>> a "soft" block, so it has many parameters which would not be
>>>> configurable in most hardware. This binding is usually automatically
>>>> generated by Xilinx's tools, so the names and values of some properties
>>>> must be kept as they are. Replacement properties have been provided for
>>>> new device trees.
>>>>
>>>> Because we need to init timer devices so early in boot, the easiest way
>>>> to configure things is to use a device tree property. For the moment
>>>> this is 'xlnx,pwm', but this could be extended/renamed/etc. in the
>>>> future if these is a need for a generic property.
>>>>
>>>> Signed-off-by: Sean Anderson <[email protected]>
>>>> ---
>>>>
>>>> Changes in v4:
>>>> - Remove references to generate polarity so this can get merged
>>>> - Predicate PWM driver on the presence of #pwm-cells
>>>> - Make some properties optional for clocksource drivers
>>>>
>>>> Changes in v3:
>>>> - Mark all boolean-as-int properties as deprecated
>>>> - Add xlnx,pwm and xlnx,gen?-active-low properties.
>>>> - Make newer replacement properties mutually-exclusive with what they
>>>>   replace
>>>> - Add an example with non-deprecated properties only.
>>>>
>>>> Changes in v2:
>>>> - Use 32-bit addresses for example binding
>>>>
>>>>  .../bindings/pwm/xlnx,axi-timer.yaml          | 85 +++++++++++++++++++
>>>>  1 file changed, 85 insertions(+)
>>>>  create mode 100644
> Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>>>
>>>> diff --git
> a/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
> b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>>> new file mode 100644
>>>> index 000000000000..48a280f96e63
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>>
>>> I don't think this is the right location for this.
>>>
>>> I have done some grepping and I think this should be done in a different
>>> way. I pretty much like solution around "ti,omap3430-timer" which is
>>> calling dmtimer_systimer_select_best() and later dmtimer_is_preferred()
>>> which in this case would allow us to get rid of cases which are not
>>> suitable for clocksource and clockevent.
>>>
>>> And there is drivers/pwm/pwm-omap-dmtimer.c which has link to timer
>>> which is providing functions for it's functionality.
>>>
>>> I have also looked at
>>> Documentation/devicetree/bindings/timer/nxp,tpm-timer.yaml which is also
>>> the same device.
>>>
>>> And sort of curious if you look at
>>>
> https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v2_0/pg079-axi-timer.pdf
>
>>> ( Figure 1-1)
>>> that PWM is taking input from generate out 0 and generate out 1 which is
>>> maybe can be modeled is any output and pwm driver can register inputs
>>> for pwm driver.
>>>
>>>
>>>> @@ -0,0 +1,85 @@
>>>> +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
>>>> +%YAML 1.2
>>>> +---
>>>> +$id: http://devicetree.org/schemas/pwm/xlnx,axi-timer.yaml#
>>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>>> +
>>>> +title: Xilinx LogiCORE IP AXI Timer Device Tree Binding
>>>> +
>>>> +maintainers:
>>>> +  - Sean Anderson <[email protected]>
>>>> +
>>>> +properties:
>>>> +  compatible:
>>>> +    oneOf:
>>>> +      - items:
>>>> +         - const: xlnx,axi-timer-2.0
>>
>> I am not quite sure if make sense also to list 2.0 version.
>> There were likely also 1.0 version which is compatible with origin xps
>> version which IIRC was PLB based. And the same driver was using in past
>> with OPB bus.
>
> It's required to list all compatible properties which may be used in a
> binding. And AFAIK it is good practice to add a new compatible string
> for new releases of an IP, in case incompatibilities are discovered.

I generally agree with this but is it practical right now if we don't
have these incompatibilities.

Right now we have 2.0 but there were 1.03.a/1.02.a/1.01a variants just
for axi. In past there were variants for PLB and OPB.

https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v1_03_a/pg079-axi-timer.pdf
https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v1_02_a/axi_timer_ds764.pdf
https://japan.xilinx.com/support/documentation/ip_documentation/axi_timer_ds764.pdf

That's not big problem but seem to me not practical to keep updating
bindings docs when new version will be coming just to be listed without
any code behind it. We have that versions which is definitely great that
if something happens we can add it. But listed all combinations? I am
not convinced about it.

Thanks,
Michal

2021-07-02 12:42:27

by Michal Simek

[permalink] [raw]
Subject: Re: [PATCH v4 1/3] dt-bindings: pwm: Add Xilinx AXI Timer



On 7/1/21 5:32 PM, Sean Anderson wrote:
>
>
> On 6/30/21 9:47 AM, Michal Simek wrote:
>>
>>
>> On 5/28/21 11:45 PM, Sean Anderson wrote:
>>> This adds a binding for the Xilinx LogiCORE IP AXI Timer. This device is
>>> a "soft" block, so it has many parameters which would not be
>>> configurable in most hardware. This binding is usually automatically
>>> generated by Xilinx's tools, so the names and values of some properties
>>> must be kept as they are. Replacement properties have been provided for
>>> new device trees.
>>>
>>> Because we need to init timer devices so early in boot, the easiest way
>>> to configure things is to use a device tree property. For the moment
>>> this is 'xlnx,pwm', but this could be extended/renamed/etc. in the
>>> future if these is a need for a generic property.
>>>
>>> Signed-off-by: Sean Anderson <[email protected]>
>>> ---
>>>
>>> Changes in v4:
>>> - Remove references to generate polarity so this can get merged
>>> - Predicate PWM driver on the presence of #pwm-cells
>>> - Make some properties optional for clocksource drivers
>>>
>>> Changes in v3:
>>> - Mark all boolean-as-int properties as deprecated
>>> - Add xlnx,pwm and xlnx,gen?-active-low properties.
>>> - Make newer replacement properties mutually-exclusive with what they
>>>   replace
>>> - Add an example with non-deprecated properties only.
>>>
>>> Changes in v2:
>>> - Use 32-bit addresses for example binding
>>>
>>>  .../bindings/pwm/xlnx,axi-timer.yaml          | 85 +++++++++++++++++++
>>>  1 file changed, 85 insertions(+)
>>>  create mode 100644
> Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>>
>>> diff --git
> a/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
> b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>> new file mode 100644
>>> index 000000000000..48a280f96e63
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>
>> I don't think this is the right location for this.
>>
>> I have done some grepping and I think this should be done in a different
>> way. I pretty much like solution around "ti,omap3430-timer" which is
>> calling dmtimer_systimer_select_best() and later dmtimer_is_preferred()
>> which in this case would allow us to get rid of cases which are not
>> suitable for clocksource and clockevent.
>>
>> And there is drivers/pwm/pwm-omap-dmtimer.c which has link to timer
>> which is providing functions for it's functionality.
>>
>> I have also looked at
>> Documentation/devicetree/bindings/timer/nxp,tpm-timer.yaml which is also
>> the same device.
>
> Ok, I will move this under bindings/timer.
>
>>
>> And sort of curious if you look at
>>
> https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v2_0/pg079-axi-timer.pdf
>
>> ( Figure 1-1)
>> that PWM is taking input from generate out 0 and generate out 1 which is
>> maybe can be modeled is any output and pwm driver can register inputs
>> for pwm driver.
>
> I don't think that is a good model, since several bits (GENERATE, PWM,
> etc) need to be set in the TCSR, and we need to coordinate changes
> between timers closely to keep our contract for apply_state(). Although
> that is how the hardware is organized, the requirements of the
> clocksource and pwm subsystems are very different.

There is another upstream solution done by samsung. Where they use
samsung,pwm-outputs property to identify PWMs. I think that make sense
to consider to identify which timer should be clocksource/clockevent
because with MB SMP this has to be done to pair timer with cpu for
clockevents.

You can see drivers here.
drivers/clocksource/smasung_pwm_timer.c
drivers/pwm/pwm-samsung.c

Uwe: How does it sound to you?

Thanks,
Michal

2021-07-02 17:33:48

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 1/3] dt-bindings: pwm: Add Xilinx AXI Timer



On 7/2/21 8:40 AM, Michal Simek wrote:
>
>
> On 7/1/21 5:32 PM, Sean Anderson wrote:
>>
>>
>> On 6/30/21 9:47 AM, Michal Simek wrote:
>>>
>>>
>>> On 5/28/21 11:45 PM, Sean Anderson wrote:
>>>> This adds a binding for the Xilinx LogiCORE IP AXI Timer. This device is
>>>> a "soft" block, so it has many parameters which would not be
>>>> configurable in most hardware. This binding is usually automatically
>>>> generated by Xilinx's tools, so the names and values of some properties
>>>> must be kept as they are. Replacement properties have been provided for
>>>> new device trees.
>>>>
>>>> Because we need to init timer devices so early in boot, the easiest way
>>>> to configure things is to use a device tree property. For the moment
>>>> this is 'xlnx,pwm', but this could be extended/renamed/etc. in the
>>>> future if these is a need for a generic property.
>>>>
>>>> Signed-off-by: Sean Anderson <[email protected]>
>>>> ---
>>>>
>>>> Changes in v4:
>>>> - Remove references to generate polarity so this can get merged
>>>> - Predicate PWM driver on the presence of #pwm-cells
>>>> - Make some properties optional for clocksource drivers
>>>>
>>>> Changes in v3:
>>>> - Mark all boolean-as-int properties as deprecated
>>>> - Add xlnx,pwm and xlnx,gen?-active-low properties.
>>>> - Make newer replacement properties mutually-exclusive with what they
>>>> replace
>>>> - Add an example with non-deprecated properties only.
>>>>
>>>> Changes in v2:
>>>> - Use 32-bit addresses for example binding
>>>>
>>>> .../bindings/pwm/xlnx,axi-timer.yaml | 85 +++++++++++++++++++
>>>> 1 file changed, 85 insertions(+)
>>>> create mode 100644
>> Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>>>
>>>> diff --git
>> a/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>> b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>>> new file mode 100644
>>>> index 000000000000..48a280f96e63
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/pwm/xlnx,axi-timer.yaml
>>>
>>> I don't think this is the right location for this.
>>>
>>> I have done some grepping and I think this should be done in a different
>>> way. I pretty much like solution around "ti,omap3430-timer" which is
>>> calling dmtimer_systimer_select_best() and later dmtimer_is_preferred()
>>> which in this case would allow us to get rid of cases which are not
>>> suitable for clocksource and clockevent.
>>>
>>> And there is drivers/pwm/pwm-omap-dmtimer.c which has link to timer
>>> which is providing functions for it's functionality.
>>>
>>> I have also looked at
>>> Documentation/devicetree/bindings/timer/nxp,tpm-timer.yaml which is also
>>> the same device.
>>
>> Ok, I will move this under bindings/timer.
>>
>>>
>>> And sort of curious if you look at
>>>
>> https://www.xilinx.com/support/documentation/ip_documentation/axi_timer/v2_0/pg079-axi-timer.pdf
>>
>>> ( Figure 1-1)
>>> that PWM is taking input from generate out 0 and generate out 1 which is
>>> maybe can be modeled is any output and pwm driver can register inputs
>>> for pwm driver.
>>
>> I don't think that is a good model, since several bits (GENERATE, PWM,
>> etc) need to be set in the TCSR, and we need to coordinate changes
>> between timers closely to keep our contract for apply_state(). Although
>> that is how the hardware is organized, the requirements of the
>> clocksource and pwm subsystems are very different.
>
> There is another upstream solution done by samsung. Where they use
> samsung,pwm-outputs property to identify PWMs.

As I understand it, the samsung PWM/timer has 5 timers, four of which
may be independently configured as PWMs. To contrast, this device has at
most two timers, both of which must be used for a single PWM output.
Because of this, it is sufficient to have a single property whose
presence indicates that the device is to be configured as a PWM.

> I think that make sense to consider to identify which timer should be
> clocksource/clockevent because with MB SMP this has to be done to pair
> timer with cpu for clockevents.

This is not done by the current driver. The first timer in the system
always binds itself to CPU 0.

--Sean

2021-07-08 17:01:47

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer



On 6/30/21 4:35 AM, Uwe Kleine-K?nig wrote:
> Hello Sean,
>
> I often mistype the name of the rounding function as "pwm_round_rate",
> the better name is "pwm_round_state" of course. That's just me thinking
> about clk_round_rate where ".._rate" is the right term. I'll try harder
> to get this right from now on.
>
> On Tue, Jun 29, 2021 at 06:21:15PM -0400, Sean Anderson wrote:
>> On 6/29/21 4:51 PM, Uwe Kleine-K?nig wrote:
>> > On Tue, Jun 29, 2021 at 02:01:31PM -0400, Sean Anderson wrote:
>> > > On 6/29/21 4:31 AM, Uwe Kleine-K?nig wrote:
>> > > > On Mon, Jun 28, 2021 at 01:41:43PM -0400, Sean Anderson wrote:
>> > > >> On 6/28/21 1:20 PM, Uwe Kleine-K?nig wrote:
>> > > >> > On Mon, Jun 28, 2021 at 12:35:19PM -0400, Sean Anderson wrote:
>> > > >> >> On 6/28/21 12:24 PM, Uwe Kleine-K?nig wrote:
>> > > >> >> > On Mon, Jun 28, 2021 at 11:50:33AM -0400, Sean Anderson wrote:
>> > > >> >> > > On 6/27/21 2:19 PM, Uwe Kleine-K?nig wrote:
>> > > >> >> > > > On Fri, Jun 25, 2021 at 01:46:26PM -0400, Sean Anderson wrote:
>> > > >> >> > > IMO, this is the best way to prevent surprising results in the API.
>> > > >> >> >
>> > > >> >> > I think it's not possible in practise to refuse "near" misses and every
>> > > >> >> > definition of "near" is in some case ridiculous. Also if you consider
>> > > >> >> > the pwm_round_state() case you don't want to refuse any request to tell
>> > > >> >> > as much as possible about your controller's capabilities. And then it's
>> > > >> >> > straight forward to let apply behave in the same way to keep complexity
>> > > >> >> > low.
>> > > >> >> >
>> > > >> >> > > The real issue here is that it is impossible to determine the correct
>> > > >> >> > > way to round the PWM a priori, and in particular, without considering
>> > > >> >> > > both duty_cycle and period. If a consumer requests very small
>> > > >> >> > > period/duty cycle which we cannot produce, how should it be rounded?
>> > > >> >> >
>> > > >> >> > Yeah, because there is no obviously right one, I picked one that is as
>> > > >> >> > wrong as the other possibilities but is easy to work with.
>> > > >> >> >
>> > > >> >> > > Should we just set TLR0=1 and TLR1=0 to give them 66% duty cycle with
>> > > >> >> > > the least period? Or should we try and increase the period to better
>> > > >> >> > > approximate the % duty cycle? And both of these decisions must be made
>> > > >> >> > > knowing both parameters. We cannot (for example) just always round up,
>> > > >> >> > > since we may produce a configuration with TLR0 == TLR1, which would
>> > > >> >> > > produce 0% duty cycle instead of whatever was requested. Rounding rate
>> > > >> >> > > will introduce significant complexity into the driver. Most of the time
>> > > >> >> > > if a consumer requests an invalid rate, it is due to misconfiguration
>> > > >> >> > > which is best solved by fixing the configuration.
>> > > >> >> >
>> > > >> >> > In the first step pick the biggest period not bigger than the requested
>> > > >> >> > and then pick the biggest duty cycle that is not bigger than the
>> > > >> >> > requested and that can be set with the just picked period. That is the
>> > > >> >> > behaviour that all new drivers should do. This is somewhat arbitrary but
>> > > >> >> > after quite some thought the most sensible in my eyes.
>> > > >> >>
>> > > >> >> And if there are no periods smaller than the requested period?
>> > > >> >
>> > > >> > Then return -ERANGE.
>> > > >>
>> > > >> Ok, so instead of
>> > > >>
>> > > >> if (cycles < 2 || cycles > priv->max + 2)
>> > > >> return -ERANGE;
>> > > >>
>> > > >> you would prefer
>> > > >>
>> > > >> if (cycles < 2)
>> > > >> return -ERANGE;
>> > > >> else if (cycles > priv->max + 2)
>> > > >> cycles = priv->max;
>> > > >
>> > > > The actual calculation is a bit harder to handle TCSR_UDT = 0 but in
>> > > > principle, yes, but see below.
>> > > >
>> > > >> But if we do the above clamping for TLR0, then we have to recalculate
>> > > >> the duty cycle for TLR1. Which I guess means doing something like
>> > > >>
>> > > >> ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
>> > > >> if (ret)
>> > > >> return ret;
>> > > >>
>> > > >> state->duty_cycle = mult_frac(state->duty_cycle,
>> > > >> xilinx_timer_get_period(priv, tlr0, tcsr0),
>> > > >> state->period);
>> > > >>
>> > > >> ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
>> > > >> if (ret)
>> > > >> return ret;
>> > > >
>> > > > No, you need something like:
>> > > >
>> > > > /*
>> > > > * The multiplication cannot overflow as both priv_max and
>> > > > * NSEC_PER_SEC fit into an u32.
>> > > > */
>> > > > max_period = div64_ul((u64)priv->max * NSEC_PER_SEC, clkrate);
>> > > >
>> > > > /* cap period to the maximal possible value */
>> > > > if (state->period > max_period)
>> > > > period = max_period;
>> > > > else
>> > > > period = state->period;
>> > > >
>> > > > /* cap duty_cycle to the maximal possible value */
>> > > > if (state->duty_cycle > max_period)
>> > > > duty_cycle = max_period;
>> > > > else
>> > > > duty_cycle = state->duty_cycle;
>> > >
>> > > These caps may increase the % duty cycle.
>> >
>> > Correct.
>> >
>> > For some usecases keeping the relative duty cycle might be better, for
>> > others it might not. I'm still convinced that in general my solution
>> > makes sense, is computationally cheaper and easier to work with.
>>
>> Can you please describe one of those use cases? Every PWM user I looked
>> (grepping for pwm_apply_state and pwm_config) set the duty cycle as a
>> percentage of the period, and not as an absolute time. Keeping the high
>> time the same while changing the duty cycle runs contrary to the
>> assumptions of all of those users.
>
> Indeed there is no mainline driver that relies on this. There are some
> smart LED controllers (e.g. WS2812B) where the duty_cycle is more
> important than the period. (I admit a PWM is not really the right driver
> for that one as it could only completely enable and complete disable
> white color.) Also there are some servo motor chips where the absolute
> duty is relevant but the period isn't (in some range). (See
> https://www.mikrocontroller.net/articles/Modellbauservo_Ansteuerung#Signalaufbau
> for a article about that (in German though).)

> In case you want to argue that out-of-mainline users don't count:

They don't :)

This is a kernel-internal API.

But my point is that the vast majority of PWM users, and 100% of the
in-kernel users care about the % duty cycle and not about the absolute
high time. We should design around the common use case first and
foremost.

> I think in the design of an API they do count to place the bar to
> enter the mainline low. Frameworks should be generic enough to cover
> as much use cases as possible.

They can cover many use-cases as possible, but they should be ergonomic
firstly for the most common use case.

> And note that if you want a nearest to (say) 50% relative duty cycle and
> don't care much about the period it doesn't really matter if you scale
> duty_cycle in pwm_round_state() to the period change or not because in
> general you need several calls to pwm_round_state() anyhow to find a
> setting with 51% if the next lower possibility is 47%. So in the end you
> save (I think) one call in generic PWM code.
>
> In contrast the math gets quite a bit more complicated because there is
> rounding involved in scaling the duty cycle. Consider a PWM that can
> configure period and duty in 16.4 ns steps and you ask for
>
> .period = 100 ns
> .duty_cycle = 50 ns
>
> Then the best period you can provide is 98.4 ns, so you return .period =
> 99 from pwm_round_state(). (Yes, you don't return 98, because
> round-nearest is much harder to handle than round down.)
> To determine the adapted duty_cycle you have to do
>
> 50 * realperiod / 100
>
> which independently of choosing 98, 98.4 or 99 for realperiod is 49. Then
> to approximate 49 without rounding up you end up with 32.8 while 49.2
> would have be perfectly fine.

And what if the consumer comes and requests 49 for their period in the
first place? You have the same problem. The rescaling made it worse in
this instance, but this is just an unfortunate case study.

> You might find a way around that (maybe you have to round up in the
> adaption of duty_cycle, I didn't convince myself this is good enough
> though).
>
> So your suggestion to adapt the duty_cycle to keep the relative
> duty_cycle constant (as good as possible within the bounds the hardware
> dictates) implies additional complication at the driver level.
>
> From a framework maintainer's point of view (and also from a low-level
> driver maintainer's point of view) I prefer one complication in a
> generic function over a complication that I have to care for in each and
> every low-level driver by a big margin.

FWIW what you're suggesting is also complex for the low-level driver.

>
> So unless you volunteer to complete the math above and promise to review
> low-level drivers for that aspect in the future (or alternatively
> convince me that math is easy and I missed something) I would like to
> end this discussion here and stay with the policy I explained.

see below

>> > > > period_cycles = period * clkrate / NSEC_PER_SEC;
>> > > >
>> > > > if (period_cycles < 2)
>> > > > return -ERANGE;
>> > > >
>> > > > duty_cycles = duty_cycle * clkrate / NSEC_PER_SEC;
>> > > >
>> > > > /*
>> > > > * The hardware cannot emit a 100% relative duty cycle, if
>> > > > * duty_cycle >= period_cycles is programmed the hardware emits
>> > > > * a 0% relative duty cycle.
>> > > > */
>> > > > if (duty_cycle == period_cycles)
>> > > > duty_cycles = period_cycles - 1;
>> > > >
>> > > > /*
>> > > > * The hardware cannot emit a duty_cycle of one clk step, so
>> > > > * emit 0 instead.
>> > > > */
>> > > > if (duty_cycles < 2)
>> > > > duty_cycles = period_cycles;
>> > >
>> > > Of course, the above may result in 100% duty cycle being rounded down to
>> > > 0%. I feel like that is too big of a jump to ignore. Perhaps if we
>> > > cannot return -ERANGE we should at least dev_warn.
>> >
>> > You did it again. You picked one single case that you consider bad but
>> > didn't provide a constructive way to make it better.
>>
>> Sure I did. I suggested that we warn. Something like
>>
>> if (duty_cycles == period_cycles)
>> if (--duty_cycles < 2)
>> dev_warn(chip->dev, "Rounding 100%% duty cycle down to 0%%; pick a longer period\n");
>>
>> or
>>
>> if (period_cycles < 2)
>> return -ERANGE;
>> else if (period_cycles < 10)
>> dev_notice(chip->dev,
>> "very short period of %u cycles; duty cycle may be rounded to 0%%\n",
>> period_cycles);
>
> Ah ok, so only a 100% jump warrants that warning.
> I think adding that
> has no practical relevance, so I don't oppose to that. Add it if you
> want. (But note that if it triggers indeed it might flood the kernel log
> if your consumer wants to start a motor but notices it doesn't run fast
> enough and so configures 100% in a tight loop. So I would recommend some
> rate limiting.)
>
>> Because 90% of the time, if a user requests such a short period it is
>> due to a typo or something similar. And if they really are doing it
>> intentionally, then they should just set duty_cycle=0.
>
> Uh, don't you think that a warning that is wrong in 10% of the cases is
> bad?

Yes. Which is why I would prefer to use the first warning.

>> > Assume there was already a pwm_round_state function (that returns the
>> > state that pwm_apply_state would implement for a given request) Consider
>> > a consumer that wants say a 50% relative duty together with a small
>> > period. So it first might call:
>> >
>> > ret = pwm_round_rate(pwm, { .period = 20, .duty_cycle = 20, ... }, &rounded_state)
>> >
>> > to find out if .period = 20 can be implemented with the given PWM. If
>> > this returns rounded state as:
>> >
>> > .period = 20
>> > .duty_cycle = 0
>> >
>> > this says quite a lot about the pwm if the driver implements my policy.
>> > (i.e.: The driver can do 20ns, but the biggest duty_cycle is only 0).
>> > If however it returns -ERANGE this means (assuming the driver implements
>> > the policy I try to convice you to be the right one) it means: The
>> > hardware cannot implement 20 ns (or something smaller) and so the next
>> > call probably tries 40 ns.
>> >
>> > With your suggested semantic -ERANGE might mean:
>> >
>> > - The driver doesn't support .period = 20 ns
>> > (Follow up questions: What period should be tried next? 10 ns? 40
>> > ns? What if this returns -ERANGE again?)
>> > - The driver supports .period = 20 ns, but the biggest possible
>> > duty_cycle is "too different from 20 ns to ignore".
>> >
>> > Then how should the search continue?
>>
>> round_rate does not have to use the same logic as apply_state.
>
> I want to have .round_state() and .apply() (i.e. the driver callbacks)
> to behave identically. If we indeed come to the conclusion that
> pwm_apply_state needs to have some precautions, I'd like to have them
> implemented in pwm_apply_state() only and not in every driver.
>
>> However, calling
>>
>> ret = pwm_apply_state(pwm, { .period = 20, .duty_cycle = 0, ... })
>>
>> should work just fine, as the caller clearly knows what they are getting
>> into. IMO this is the best way to allow hypothetical round_rate users to
>> find out the edges of the PWM while still protecting existing users.
>>
>> It's perfectly fine to round
>>
>> { .period = 150, .duty_cycle = 75 }
>>
>> to
>>
>> { .period = 100, .duty_cycle = 75 }
>>
>> in round_rate. But doing the same thing for apply_state would be very
>> surprising to every existing PWM user.
>>
>> IMO the following invariant should hold
>>
>> apply_state(round_rate(x))
>> assert(round_rate(x) == get_state())
>
> (merged your correction of the follow up mail into the quote above)
>
> (Fun fact: Only needing this one would allow a generic implementation of
> round_state, it just had to return a pwm_state that doesn't depend on x
> :o)

Maybe. The only constraint is that the state returned by round_rate is
possible to implement on the hardware without modification. See below
for further discussion.

>> but the following should not necessarily hold
>>
>> apply_state(x)
>> assert(round_rate(x) == get_state())
>>
>> Of course, where it is reasonable to round down, we should do so.
>>
>> But where the result may be surprising, then the caller should specify
>> the rounded state specifically. It is better to fail loudly and
>> noisily than
>> to silently accept garbage.
>
> Can you please come up with an algorithm to judge if a given deviation
> is reasonable or surprising? I agree there are surprises and some of
> them are obviously bad. For most cases however the judgement depends on
> the use case so I fail to see how someone should program such a check
> that should cover all consumers and use cases. I prefer no precautions +
> an easy relation between pwm_round_state and pwm_apply_state (i.e.
> behave identically) over a most of the time(?) useless precaution and
> some policy defined differences between pwm_round_state and
> pwm_apply_state

After thinking it over, I believe I agree with you on most things, but I
think your proposed API has room for additional checks without any loss
of generality.

The PWM subsystem has several major players:

* Existing users of the PWM API. Most of these do not especially care
about the PWM period, usually just leaving at the default. The
exception is of course the pwm-clk driver. Many of these users care
about % duty cycle, and they all calculate the high time based on the
configured period of the PWM. I suspect that while many of these users
have substantial leeway in what accuracy they expect from the % duty
cycle, significant errors (in the 25-50% range) are probably unusual
and indicative of a misconfigured period. Unfortunately, we cannot
make a general judgement about what sort of accuracy is OK in most
cases.

* Hypothetical future users of some kind of round_state function. These
users have some kind of algorithm which determines whether a PWM state
is acceptable for the driver. Most of the time this will be some kind
of accuracy check. What the round_state function returns is not
particularly important, because users have the opportunity to revise
their request based on what the state is rounded to. However, it is
important that each round rate function is consistent in manner that
it rounds so that these users

* Existing drivers for the PWM subsystem. These drivers must implement
an apply_state function which is correct for both existing and future
users. In addition, they may implement some kind of round_state
function in the future. it is important to reduce the complexity of
the calculations these drivers perform so that it is easier to
implement and review them.

I believe the following requirements satisfy the above constraints:

* The round_state function shall round the period to the largest period
representable by the PWM less than the requested period. It shall also
round the duty cycle to the largest duty cycle representable by the
PWM less than the requested duty cycle. No attempt shall be made to
preserve the % duty cycle.
* The apply_state function shall only round the requested period down, and
may do so by no more than one unit cycle. If the requested period is
unrepresentable by the PWM, the apply_state function shall return
-ERANGE.
* The apply_state function shall only round the requested duty cycle
down. The apply_state function shall not return an error unless there
is no duty cycle less than the requested duty cycle which is
representable by the PWM.
* After applying a state returned by round_state with apply_state,
get_state must return that state.

The reason that we must return an error when the period is
unrepresentable is that generally the duty cycle is calculated based on
the period. This change has no affect on future users of round_state,
since that function will only return valid periods. Those users will
have the opportunity to detect that the period has changed and determine
if the duty cycle is still acceptable. However, for existing users, we
should also provide the same opportunity. This requirement simplifies
the behavior of apply_state, since there is no longer any chance that
the % duty cycle is rounded up. This requirement is easy to implement in
drivers as well. Instead of writing something like

period = clamp(period, min_period, max_period);

they will instead write

if (period < min_period || period > max_period)
return -ERANGE;

Instead of viewing round_state as "what get_state would return if I
passed this state to apply_state", it is better to view it as "what is
the closest exactly representable state with parameters less than this
state." I believe that this latter representation is effectively
identical for users of round_state, but it allows for implementations of
apply_state which provide saner defaults for existing users.

--Sean

2021-07-08 19:45:31

by Uwe Kleine-König

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

Hello Sean,

On Thu, Jul 08, 2021 at 12:59:18PM -0400, Sean Anderson wrote:
> On 6/30/21 4:35 AM, Uwe Kleine-K?nig wrote:
> > I often mistype the name of the rounding function as "pwm_round_rate",
> > the better name is "pwm_round_state" of course. That's just me thinking
> > about clk_round_rate where ".._rate" is the right term. I'll try harder
> > to get this right from now on.
> >
> > On Tue, Jun 29, 2021 at 06:21:15PM -0400, Sean Anderson wrote:
> > > On 6/29/21 4:51 PM, Uwe Kleine-K?nig wrote:
> > > > On Tue, Jun 29, 2021 at 02:01:31PM -0400, Sean Anderson wrote:
> > > > > On 6/29/21 4:31 AM, Uwe Kleine-K?nig wrote:
> > > > > > On Mon, Jun 28, 2021 at 01:41:43PM -0400, Sean Anderson wrote:
> > > > > >> On 6/28/21 1:20 PM, Uwe Kleine-K?nig wrote:
> > > > > >> > On Mon, Jun 28, 2021 at 12:35:19PM -0400, Sean Anderson wrote:
> > > > > >> >> On 6/28/21 12:24 PM, Uwe Kleine-K?nig wrote:
> > > > > >> >> > On Mon, Jun 28, 2021 at 11:50:33AM -0400, Sean Anderson wrote:
> > > > > >> >> > > On 6/27/21 2:19 PM, Uwe Kleine-K?nig wrote:
> > > > > >> >> > > > On Fri, Jun 25, 2021 at 01:46:26PM -0400, Sean Anderson wrote:
> > > > > >> >> > > IMO, this is the best way to prevent surprising results in the API.
> > > > > >> >> >
> > > > > >> >> > I think it's not possible in practise to refuse "near" misses and every
> > > > > >> >> > definition of "near" is in some case ridiculous. Also if you consider
> > > > > >> >> > the pwm_round_state() case you don't want to refuse any request to tell
> > > > > >> >> > as much as possible about your controller's capabilities. And then it's
> > > > > >> >> > straight forward to let apply behave in the same way to keep complexity
> > > > > >> >> > low.
> > > > > >> >> >
> > > > > >> >> > > The real issue here is that it is impossible to determine the correct
> > > > > >> >> > > way to round the PWM a priori, and in particular, without considering
> > > > > >> >> > > both duty_cycle and period. If a consumer requests very small
> > > > > >> >> > > period/duty cycle which we cannot produce, how should it be rounded?
> > > > > >> >> >
> > > > > >> >> > Yeah, because there is no obviously right one, I picked one that is as
> > > > > >> >> > wrong as the other possibilities but is easy to work with.
> > > > > >> >> >
> > > > > >> >> > > Should we just set TLR0=1 and TLR1=0 to give them 66% duty cycle with
> > > > > >> >> > > the least period? Or should we try and increase the period to better
> > > > > >> >> > > approximate the % duty cycle? And both of these decisions must be made
> > > > > >> >> > > knowing both parameters. We cannot (for example) just always round up,
> > > > > >> >> > > since we may produce a configuration with TLR0 == TLR1, which would
> > > > > >> >> > > produce 0% duty cycle instead of whatever was requested. Rounding rate
> > > > > >> >> > > will introduce significant complexity into the driver. Most of the time
> > > > > >> >> > > if a consumer requests an invalid rate, it is due to misconfiguration
> > > > > >> >> > > which is best solved by fixing the configuration.
> > > > > >> >> >
> > > > > >> >> > In the first step pick the biggest period not bigger than the requested
> > > > > >> >> > and then pick the biggest duty cycle that is not bigger than the
> > > > > >> >> > requested and that can be set with the just picked period. That is the
> > > > > >> >> > behaviour that all new drivers should do. This is somewhat arbitrary but
> > > > > >> >> > after quite some thought the most sensible in my eyes.
> > > > > >> >>
> > > > > >> >> And if there are no periods smaller than the requested period?
> > > > > >> >
> > > > > >> > Then return -ERANGE.
> > > > > >>
> > > > > >> Ok, so instead of
> > > > > >>
> > > > > >> if (cycles < 2 || cycles > priv->max + 2)
> > > > > >> return -ERANGE;
> > > > > >>
> > > > > >> you would prefer
> > > > > >>
> > > > > >> if (cycles < 2)
> > > > > >> return -ERANGE;
> > > > > >> else if (cycles > priv->max + 2)
> > > > > >> cycles = priv->max;
> > > > > >
> > > > > > The actual calculation is a bit harder to handle TCSR_UDT = 0 but in
> > > > > > principle, yes, but see below.
> > > > > >
> > > > > >> But if we do the above clamping for TLR0, then we have to recalculate
> > > > > >> the duty cycle for TLR1. Which I guess means doing something like
> > > > > >>
> > > > > >> ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
> > > > > >> if (ret)
> > > > > >> return ret;
> > > > > >>
> > > > > >> state->duty_cycle = mult_frac(state->duty_cycle,
> > > > > >> xilinx_timer_get_period(priv, tlr0, tcsr0),
> > > > > >> state->period);
> > > > > >>
> > > > > >> ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
> > > > > >> if (ret)
> > > > > >> return ret;
> > > > > >
> > > > > > No, you need something like:
> > > > > >
> > > > > > /*
> > > > > > * The multiplication cannot overflow as both priv_max and
> > > > > > * NSEC_PER_SEC fit into an u32.
> > > > > > */
> > > > > > max_period = div64_ul((u64)priv->max * NSEC_PER_SEC, clkrate);
> > > > > >
> > > > > > /* cap period to the maximal possible value */
> > > > > > if (state->period > max_period)
> > > > > > period = max_period;
> > > > > > else
> > > > > > period = state->period;
> > > > > >
> > > > > > /* cap duty_cycle to the maximal possible value */
> > > > > > if (state->duty_cycle > max_period)
> > > > > > duty_cycle = max_period;
> > > > > > else
> > > > > > duty_cycle = state->duty_cycle;
> > > > >
> > > > > These caps may increase the % duty cycle.
> > > >
> > > > Correct.
> > > >
> > > > For some usecases keeping the relative duty cycle might be better, for
> > > > others it might not. I'm still convinced that in general my solution
> > > > makes sense, is computationally cheaper and easier to work with.
> > >
> > > Can you please describe one of those use cases? Every PWM user I looked
> > > (grepping for pwm_apply_state and pwm_config) set the duty cycle as a
> > > percentage of the period, and not as an absolute time. Keeping the high
> > > time the same while changing the duty cycle runs contrary to the
> > > assumptions of all of those users.
> >
> > Indeed there is no mainline driver that relies on this. There are some
> > smart LED controllers (e.g. WS2812B) where the duty_cycle is more
> > important than the period. (I admit a PWM is not really the right driver
> > for that one as it could only completely enable and complete disable
> > white color.) Also there are some servo motor chips where the absolute
> > duty is relevant but the period isn't (in some range). (See
> > https://www.mikrocontroller.net/articles/Modellbauservo_Ansteuerung#Signalaufbau
> > for a article about that (in German though).)
>
> > In case you want to argue that out-of-mainline users don't count:
>
> They don't :)
>
> This is a kernel-internal API.
>
> But my point is that the vast majority of PWM users, and 100% of the
> in-kernel users care about the % duty cycle and not about the absolute
> high time. We should design around the common use case first and
> foremost.
>
> > I think in the design of an API they do count to place the bar to
> > enter the mainline low. Frameworks should be generic enough to cover
> > as much use cases as possible.
>
> They can cover many use-cases as possible, but they should be ergonomic
> firstly for the most common use case.

First I think going the easy way now and accepting to have to spend
quite more work later isn't a sensible approach. Second: Your approach
isn't even easier. And third: I agree that it should be easy for
consumers who care more about relative duty cycle than absolute numers.
But that doesn't imply that each and every device driver must provide
exactly that algorithm. The possible ways forward are:

a) The framework has no complexity and all lowlevel drivers have to do
complicated maths;

b) The framework contains the complexity and the lowlevel drivers are
simpler.

I'd choose b) at any time.

> > And note that if you want a nearest to (say) 50% relative duty cycle and
> > don't care much about the period it doesn't really matter if you scale
> > duty_cycle in pwm_round_state() to the period change or not because in
> > general you need several calls to pwm_round_state() anyhow to find a
> > setting with 51% if the next lower possibility is 47%. So in the end you
> > save (I think) one call in generic PWM code.
> >
> > In contrast the math gets quite a bit more complicated because there is
> > rounding involved in scaling the duty cycle. Consider a PWM that can
> > configure period and duty in 16.4 ns steps and you ask for
> >
> > .period = 100 ns
> > .duty_cycle = 50 ns
> >
> > Then the best period you can provide is 98.4 ns, so you return .period =
> > 99 from pwm_round_state(). (Yes, you don't return 98, because
> > round-nearest is much harder to handle than round down.)
> > To determine the adapted duty_cycle you have to do
> >
> > 50 * realperiod / 100
> >
> > which independently of choosing 98, 98.4 or 99 for realperiod is 49. Then
> > to approximate 49 without rounding up you end up with 32.8 while 49.2
> > would have be perfectly fine.
>
> And what if the consumer comes and requests 49 for their period in the
> first place? You have the same problem. The rescaling made it worse in
> this instance, but this is just an unfortunate case study.

I cannot follow. There are cases that are easy and others are hard.
Obviously I presented a hard case, and just because there are simpler
cases, too, doesn't mean that implementing the algorithm that must cover
all cases becomes simple, too. Maybe I just didn't understand what you
want to say?!

> > You might find a way around that (maybe you have to round up in the
> > adaption of duty_cycle, I didn't convince myself this is good enough
> > though).
> >
> > So your suggestion to adapt the duty_cycle to keep the relative
> > duty_cycle constant (as good as possible within the bounds the hardware
> > dictates) implies additional complication at the driver level.
> >
> > From a framework maintainer's point of view (and also from a low-level
> > driver maintainer's point of view) I prefer one complication in a
> > generic function over a complication that I have to care for in each and
> > every low-level driver by a big margin.
>
> FWIW what you're suggesting is also complex for the low-level driver.

Well, it is as complex as necessary and simpler than adapting the
duty_cycle as you suggested.

> [...]
> > Can you please come up with an algorithm to judge if a given deviation
> > is reasonable or surprising? I agree there are surprises and some of
> > them are obviously bad. For most cases however the judgement depends on
> > the use case so I fail to see how someone should program such a check
> > that should cover all consumers and use cases. I prefer no precautions +
> > an easy relation between pwm_round_state and pwm_apply_state (i.e.
> > behave identically) over a most of the time(?) useless precaution and
> > some policy defined differences between pwm_round_state and
> > pwm_apply_state
>
> After thinking it over, I believe I agree with you on most things, but I
> think your proposed API has room for additional checks without any loss
> of generality.

\o/

> The PWM subsystem has several major players:
>
> * Existing users of the PWM API. Most of these do not especially care
> about the PWM period, usually just leaving at the default. The
> exception is of course the pwm-clk driver. Many of these users care
> about % duty cycle, and they all calculate the high time based on the
> configured period of the PWM. I suspect that while many of these users
> have substantial leeway in what accuracy they expect from the % duty
> cycle, significant errors (in the 25-50% range) are probably unusual
> and indicative of a misconfigured period. Unfortunately, we cannot
> make a general judgement about what sort of accuracy is OK in most
> cases.

ack.

> * Hypothetical future users of some kind of round_state function. These
> users have some kind of algorithm which determines whether a PWM state
> is acceptable for the driver. Most of the time this will be some kind
> of accuracy check. What the round_state function returns is not
> particularly important, because users have the opportunity to revise
> their request based on what the state is rounded to. However, it is
> important that each round rate function is consistent in manner that
> it rounds so that these users

This sentence isn't complete, is it? One thing I consider important is
that there is a policy which of the implementable states is returned for
a given request to make it efficient to search for a best state
(depending on what the consumer driver considers best). Otherwise this
yields to too much distinctions of cases.

> * Existing drivers for the PWM subsystem. These drivers must implement
> an apply_state function which is correct for both existing and future
> users. In addition, they may implement some kind of round_state
> function in the future. it is important to reduce the complexity of
> the calculations these drivers perform so that it is easier to
> implement and review them.

It's hard to know what "correct" means. But ack for "They should not be
more complex than necessary".

> I believe the following requirements satisfy the above constraints:
>
> * The round_state function shall round the period to the largest period
> representable by the PWM less than the requested period. It shall also
> round the duty cycle to the largest duty cycle representable by the
> PWM less than the requested duty cycle. No attempt shall be made to
> preserve the % duty cycle.

ack if you replace "less" by "less or equal" twice.

> * The apply_state function shall only round the requested period down, and
> may do so by no more than one unit cycle. If the requested period is
> unrepresentable by the PWM, the apply_state function shall return
> -ERANGE.

I don't understand what you mean by "more than one unit cycle", but that
doesn't really matter for what I think is wrong with that approach:

I think this is a bad idea if with "apply_state" you mean the callback
each driver has to implement: Once you made all drivers conformant to
this, someone will argue that one unit cycle is too strict. Or that it's
ok to increase the period iff the duty_cycle is 0.

Then you have to adapt all 50 or so drivers to adapt the policy.
Better let .apply_state() do the same as .round_state() and then you can
have in the core (i.e. in a single place):

def pwm_apply_state(pwm, state):
rounded_state = pwm_round_state(pwm, state)
if some_condition(rounded_state, state):
return -ERANGE
else:
pwm->apply(pwm, state)

Having said that I think some_condition should always return False, but
independant of the discussion how some_condition should actually behave
this is definitively better than to hardcode some_condition in each
driver.

> * The apply_state function shall only round the requested duty cycle
> down. The apply_state function shall not return an error unless there
> is no duty cycle less than the requested duty cycle which is
> representable by the PWM.

ack. (Side note: Most drivers can implement duty_cycle = 0, so for them
duty_cycle isn't a critical thing.)

> * After applying a state returned by round_state with apply_state,
> get_state must return that state.

ack.

> The reason that we must return an error when the period is
> unrepresentable is that generally the duty cycle is calculated based on
> the period. This change has no affect on future users of round_state,
> since that function will only return valid periods. Those users will
> have the opportunity to detect that the period has changed and determine
> if the duty cycle is still acceptable.

ack up to here.

> However, for existing users, we
> should also provide the same opportunity.

Here you say: If the period has changed they should get a return value
of -ERANGE, right? Now what should they do with that. Either they give
up (which is bad) or they need to resort to pwm_round_state to
find a possible way forward. So they have to belong in the group of
round_state users and so they can do this from the start and then don't
need to care about some_condition at all.

> This requirement simplifies
> the behavior of apply_state, since there is no longer any chance that
> the % duty cycle is rounded up.

This is either wrong, or I didn't understand you. For my hypothetical
hardware that can implement periods and duty_cycles that are multiples
of 16.4 ns the following request:

period = 1650
duty_cycle = 164

(with relative duty_cycle = 9.9393939393939 %)
will be round to:

period = 1640
duty_cycle = 164

which has a higher relative duty_cycle (i.e. 10%).

> This requirement is easy to implement in
> drivers as well. Instead of writing something like
>
> period = clamp(period, min_period, max_period);
>
> they will instead write
>
> if (period < min_period || period > max_period)
> return -ERANGE;

Are you aware what this means for drivers that only support a single
fixed period?

I still think it should be:

if (period < min_period)
return -ERANGE;

if (period > max_period)
period = max_period;

There are two reasons for this compared to your suggestion:

a) Consider again the 16.4 ns driver and that it is capable to
implement periods up to 16400 ns. With your approach a request of
16404 ns will yield -ERANGE.
Now compare that with a different 16.4 ns driver with max_period =
164000 ns. The request of 16404 ns will yield 16400 ns, just because
this driver could also do 16416.4 ns. This is strange, because the
possibility to do 16416.4 ns is totally irrelevant here, isn't it?

b) If a consumer asked for a certain state and gets back -ENORANGE they
don't know if they should increase or decrease the period to guess a
state that might be implementable instead.

(Hmm, or are you only talking about .apply_state and only .round_state
should do if (period < min_period) return -ERANGE; if (period >
max_period) period = max_period;? If so, I'd like to have this in the
framework, not in each driver. Then .round_state and .apply_state can be
identical which is good for reducing complexity.)

> Instead of viewing round_state as "what get_state would return if I
> passed this state to apply_state", it is better to view it as "what is
> the closest exactly representable state with parameters less than this
> state."
> I believe that this latter representation is effectively identical for
> users of round_state, but it allows for implementations of apply_state
> which provide saner defaults for existing users.

I look forward to how you modify your claim here after reading my
reasoning above.

Best regards
Uwe

--
Pengutronix e.K. | Uwe Kleine-K?nig |
Industrial Linux Solutions | https://www.pengutronix.de/ |


Attachments:
(No filename) (19.07 kB)
signature.asc (499.00 B)
Download all attachments

2021-07-12 16:28:43

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer



On 7/8/21 3:43 PM, Uwe Kleine-K?nig wrote:
> Hello Sean,
>
> On Thu, Jul 08, 2021 at 12:59:18PM -0400, Sean Anderson wrote:
>> And what if the consumer comes and requests 49 for their period in the
>> first place? You have the same problem. The rescaling made it worse in
>> this instance, but this is just an unfortunate case study.
>
> I cannot follow. There are cases that are easy and others are hard.
> Obviously I presented a hard case, and just because there are simpler
> cases, too, doesn't mean that implementing the algorithm that must cover
> all cases becomes simple, too. Maybe I just didn't understand what you
> want to say?!

My point is that you cannot just pick a bad case and call the whole
process poor. I can do the same thing for your proposed process.
In any case, I don't wish to propose that drivers do rescaling in this
manner; I hoped that my below discussion had made that clear.

Though I really would like if we could pick a different name for "the
duration of the initial part of the PWM's cycle". I know "high time" is
not strictly correct for inverted polarity, but duty cycle refers to
percentage everywhere but the Linux kernel...

>> > You might find a way around that (maybe you have to round up in the
>> > adaption of duty_cycle, I didn't convince myself this is good enough
>> > though).
>> >
>> > So your suggestion to adapt the duty_cycle to keep the relative
>> > duty_cycle constant (as good as possible within the bounds the hardware
>> > dictates) implies additional complication at the driver level.
>> >
>> > From a framework maintainer's point of view (and also from a low-level
>> > driver maintainer's point of view) I prefer one complication in a
>> > generic function over a complication that I have to care for in each and
>> > every low-level driver by a big margin.
>>
>> FWIW what you're suggesting is also complex for the low-level driver.
>
> Well, it is as complex as necessary and simpler than adapting the
> duty_cycle as you suggested.
>> [...]
>> > Can you please come up with an algorithm to judge if a given deviation
>> > is reasonable or surprising? I agree there are surprises and some of
>> > them are obviously bad. For most cases however the judgement depends on
>> > the use case so I fail to see how someone should program such a check
>> > that should cover all consumers and use cases. I prefer no precautions +
>> > an easy relation between pwm_round_state and pwm_apply_state (i.e.
>> > behave identically) over a most of the time(?) useless precaution and
>> > some policy defined differences between pwm_round_state and
>> > pwm_apply_state
>>
>> After thinking it over, I believe I agree with you on most things, but I
>> think your proposed API has room for additional checks without any loss
>> of generality.
>
> \o/
>
>> The PWM subsystem has several major players:
>>
>> * Existing users of the PWM API. Most of these do not especially care
>> about the PWM period, usually just leaving at the default. The
>> exception is of course the pwm-clk driver. Many of these users care
>> about % duty cycle, and they all calculate the high time based on the
>> configured period of the PWM. I suspect that while many of these users
>> have substantial leeway in what accuracy they expect from the % duty
>> cycle, significant errors (in the 25-50% range) are probably unusual
>> and indicative of a misconfigured period. Unfortunately, we cannot
>> make a general judgement about what sort of accuracy is OK in most
>> cases.
>
> ack.
>
>> * Hypothetical future users of some kind of round_state function. These
>> users have some kind of algorithm which determines whether a PWM state
>> is acceptable for the driver. Most of the time this will be some kind
>> of accuracy check. What the round_state function returns is not
>> particularly important, because users have the opportunity to revise
>> their request based on what the state is rounded to. However, it is
>> important that each round rate function is consistent in manner that
>> it rounds so that these users
>
> This sentence isn't complete, is it?

this should be finished like

... can manipulate it programmatically.

> One thing I consider important is
> that there is a policy which of the implementable states is returned for
> a given request to make it efficient to search for a best state
> (depending on what the consumer driver considers best). Otherwise this
> yields to too much distinctions of cases.
>
>> * Existing drivers for the PWM subsystem. These drivers must implement
>> an apply_state function which is correct for both existing and future
>> users. In addition, they may implement some kind of round_state
>> function in the future. it is important to reduce the complexity of
>> the calculations these drivers perform so that it is easier to
>> implement and review them.
>
> It's hard to know what "correct" means. But ack for "They should not be
> more complex than necessary".
>
>> I believe the following requirements satisfy the above constraints:
>>
>> * The round_state function shall round the period to the largest period
>> representable by the PWM less than the requested period. It shall also
>> round the duty cycle to the largest duty cycle representable by the
>> PWM less than the requested duty cycle. No attempt shall be made to
>> preserve the % duty cycle.
>
> ack if you replace "less" by "less or equal" twice.

Yes.

>> * The apply_state function shall only round the requested period down, and
>> may do so by no more than one unit cycle. If the requested period is
>> unrepresentable by the PWM, the apply_state function shall return
>> -ERANGE.
>
> I don't understand what you mean by "more than one unit cycle", but
> that doesn't really matter for what I think is wrong with that
> approach: I think this is a bad idea if with "apply_state" you mean
> the callback each driver has to implement: Once you made all drivers
> conformant to this, someone will argue that one unit cycle is too
> strict.

The intent here is to provide guidance against drivers which round
excessively. That is, a driver which always rounded down to its minimum
period would not be very interesting. And neither would a driver which
did not make a very good effort (such as always rounding to multiples of
10 when it could round to multiples of 3 or whatever). So perhaps
s/shall/should/.

> Or that it's ok to increase the period iff the duty_cycle is 0.

IMO it doesn't matter what the period is for a duty cycle of 0 or 100.
Whatever policy we decide on, the behavior in that case will

> Then you have to adapt all 50 or so drivers to adapt the policy.

Of course, as I understand it, this must be done for your policy as
well.

> Better let .apply_state() do the same as .round_state() and then you can
> have in the core (i.e. in a single place):
>
> def pwm_apply_state(pwm, state):
> rounded_state = pwm_round_state(pwm, state)
> if some_condition(rounded_state, state):
> return -ERANGE
> else:
> pwm->apply(pwm, state)
>
> Having said that I think some_condition should always return False, but
> independant of the discussion how some_condition should actually behave
> this is definitively better than to hardcode some_condition in each
> driver.

And IMO the condition should just be "is the period different"?

I think a nice interface for many existing users would be something like

# this ignores polarity and intermediate errors, but that should
# not be terribly difficult to add
def pwm_apply_relative_duty_cycle(pwm, duty_cycle, scale):
state = pwm_get_state(pwm)
state.enabled = True
state = pwm_set_relative_duty_cycle(state, duty_cycle, scale)
rounded_state = pwm_round_state(pwm, state)
if rounded_state.period != state.period:
state = pwm_set_relative_duty_cycle(rounded_state, duty_cycle, scale)
rounded_state = pwm_round_state(pwm, state)
if duty_cycle and not rounded_state.duty_cycle:
return -ERANGE
return pwm_apply_state(pwm, rounded_state)

which of course could be implemented both with your proposed semantics
or with mine.

>> * The apply_state function shall only round the requested duty cycle
>> down. The apply_state function shall not return an error unless there
>> is no duty cycle less than the requested duty cycle which is
>> representable by the PWM.
>
> ack. (Side note: Most drivers can implement duty_cycle = 0, so for them
> duty_cycle isn't a critical thing.)

Yes, and unfortunately the decision is not as clear-cut as for period.

>> * After applying a state returned by round_state with apply_state,
>> get_state must return that state.
>
> ack.
>
>> The reason that we must return an error when the period is
>> unrepresentable is that generally the duty cycle is calculated based on
>> the period. This change has no affect on future users of round_state,
>> since that function will only return valid periods. Those users will
>> have the opportunity to detect that the period has changed and determine
>> if the duty cycle is still acceptable.
>
> ack up to here.
>
>> However, for existing users, we
>> should also provide the same opportunity.
>
> Here you say: If the period has changed they should get a return value
> of -ERANGE, right? Now what should they do with that. Either they give
> up (which is bad)

No, this is exactly what we want. Consider how period is set. Either
it is whatever the default is (e.g. set by PoR or the bootloader), in
which case it is a driver bug if we think it is unrepresentable, or it
is set from the device tree (or platform info), in which case it is a
bug in the configuration. This is not something like duty cycle where
you could make a case depending on the user, but an actual case of
misconfiguration.

> or they need to resort to pwm_round_state to
> find a possible way forward. So they have to belong in the group of
> round_state users and so they can do this from the start and then don't
> need to care about some_condition at all.
>
>> This requirement simplifies
>> the behavior of apply_state, since there is no longer any chance that
>> the % duty cycle is rounded up.
>
> This is either wrong, or I didn't understand you. For my hypothetical
> hardware that can implement periods and duty_cycles that are multiples
> of 16.4 ns the following request:
>
> period = 1650
> duty_cycle = 164
>
> (with relative duty_cycle = 9.9393939393939 %)
> will be round to:
>
> period = 1640
> duty_cycle = 164
>
> which has a higher relative duty_cycle (i.e. 10%).

This is effectively bound by the clause above to be no more than the
underlying precision of the PWM. Existing users expect to be able to
pass unrounded periods/duty cycles, so we need to round in some manner.
Any way we round is OK, as long as it is not terribly excessive (hence
the clause above). We could have chosen to round up (and in fact this is
exactly what happens for inverted polarity PWMs). But I think that for
ease of implementation is is better to mostly round in the same manner
as round_state.

>> This requirement is easy to implement in
>> drivers as well. Instead of writing something like
>>
>> period = clamp(period, min_period, max_period);
>>
>> they will instead write
>>
>> if (period < min_period || period > max_period)
>> return -ERANGE;
>
> Are you aware what this means for drivers that only support a single
> fixed period?

This is working as designed. Either the period comes from configuration
(e.g. pwm_init_state), which is specialized to the board in question, in
which case it is OK to return an error because the writer of the dts
either should leave it as the default or specify it correctly, or it
comes from pwm_get_state in which case it is a driver error for
returning a a period which that driver cannot support.

There are two exceptions to the above. First, a fixed period PWM driver
could have its period changed by the parent clock frequency changing.
But I think such driver should just clk_rate_exclusive_get because
otherwise all bets are off. You just have to hope your consumer doesn't
care about the period.

The other exception is pwm_clk. In this case, I think it is reasonable
to pass an error on if the user tries to change the frequency of what is
effectively a fixed-rate clock.

> I still think it should be:
>
> if (period < min_period)
> return -ERANGE;
>
> if (period > max_period)
> period = max_period;
>
> There are two reasons for this compared to your suggestion:
>
> a) Consider again the 16.4 ns driver and that it is capable to
> implement periods up to 16400 ns. With your approach a request of
> 16404 ns will yield -ERANGE.
> Now compare that with a different 16.4 ns driver with max_period =
> 164000 ns. The request of 16404 ns will yield 16400 ns, just because
> this driver could also do 16416.4 ns. This is strange, because the
> possibility to do 16416.4 ns is totally irrelevant here, isn't it?

Ah, it looks like I mis-specified this a little bit. My intent was

The apply_state function shall only round the requested period
down, and should do so by no more than one unit cycle. If the
period *rounded as such* is unrepresentable by the PWM, the
apply_state function shall return -ERANGE.

> b) If a consumer asked for a certain state and gets back -ENORANGE they
> don't know if they should increase or decrease the period to guess a
> state that might be implementable instead.

Because I believe this is effectively a configuration issue, it should
be obvious to the user which direction they need to go. Programmatic
users which want to automatically pick a better period would need to use
round_state instead.

> (Hmm, or are you only talking about .apply_state and only .round_state
> should do if (period < min_period) return -ERANGE; if (period >
> max_period) period = max_period;?

Yes.

> If so, I'd like to have this in the framework, not in each driver.
> Then .round_state and .apply_state can be identical which is good for
> reducing complexity.)

So you would like each PWM driver to have a "max_period" and
"min_period" parameter? And what if the clock rate changes? Otherwise,
how do you propose that the framework detect when a requested period is
out of range?

>> Instead of viewing round_state as "what get_state would return if I
>> passed this state to apply_state", it is better to view it as "what is
>> the closest exactly representable state with parameters less than this
>> state."
>> I believe that this latter representation is effectively identical for
>> users of round_state, but it allows for implementations of apply_state
>> which provide saner defaults for existing users.
>
> I look forward to how you modify your claim here after reading my
> reasoning above.

I believe it stands as-is.

--Sean

2021-07-12 19:50:28

by Uwe Kleine-König

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

Hello Sean,

On Mon, Jul 12, 2021 at 12:26:47PM -0400, Sean Anderson wrote:
> On 7/8/21 3:43 PM, Uwe Kleine-K?nig wrote:
> > Hello Sean,
> >
> > On Thu, Jul 08, 2021 at 12:59:18PM -0400, Sean Anderson wrote:
> > > And what if the consumer comes and requests 49 for their period in the
> > > first place? You have the same problem. The rescaling made it worse in
> > > this instance, but this is just an unfortunate case study.
> >
> > I cannot follow. There are cases that are easy and others are hard.
> > Obviously I presented a hard case, and just because there are simpler
> > cases, too, doesn't mean that implementing the algorithm that must cover
> > all cases becomes simple, too. Maybe I just didn't understand what you
> > want to say?!
>
> My point is that you cannot just pick a bad case and call the whole
> process poor.

Yeah, there are surprises with both approaches. My point is mostly that
if you cannot prevent these surprises with a more complicate algorithm,
then better live with the surprises and stick to the simple algorithm.

(For the sake of completeness: If the request for my 16.4 ns PWM was

.period = 100 ns
.duty_cycle = 49 ns

the hardware should be configured with

.period = 98.4 ns
.duty_cycle = 32.8 ns

. The key difference to the scaling approach is: The computation is easy
and it's clear how it should be implemented. So I don't see how my
approach yields problems.)

> I can do the same thing for your proposed process.
> In any case, I don't wish to propose that drivers do rescaling in this
> manner; I hoped that my below discussion had made that clear.

Yes, After reading the start of your mail I was positively surprised to
see you reasoning for nearly the same things as I do :-)

> Though I really would like if we could pick a different name for "the
> duration of the initial part of the PWM's cycle". I know "high time" is
> not strictly correct for inverted polarity, but duty cycle refers to
> percentage everywhere but the Linux kernel...

"active time" would be a term that should be fine. But I think
"duty-cycle" in contrast to "relative duty-cycle" is fine enough and
IMHO we should keep the terms as they are.

> > > * Hypothetical future users of some kind of round_state function. These
> > > users have some kind of algorithm which determines whether a PWM state
> > > is acceptable for the driver. Most of the time this will be some kind
> > > of accuracy check. What the round_state function returns is not
> > > particularly important, because users have the opportunity to revise
> > > their request based on what the state is rounded to. However, it is
> > > important that each round rate function is consistent in manner that
> > > it rounds so that these users
> >
> > This sentence isn't complete, is it?
>
> this should be finished like
>
> ... can manipulate it programmatically.

Then ack.

> > > * The apply_state function shall only round the requested period down, and
> > > may do so by no more than one unit cycle. If the requested period is
> > > unrepresentable by the PWM, the apply_state function shall return
> > > -ERANGE.
> >
> > I don't understand what you mean by "more than one unit cycle", but
> > that doesn't really matter for what I think is wrong with that
> > approach: I think this is a bad idea if with "apply_state" you mean
> > the callback each driver has to implement: Once you made all drivers
> > conformant to this, someone will argue that one unit cycle is too
> > strict.
>
> The intent here is to provide guidance against drivers which round
> excessively. That is, a driver which always rounded down to its minimum
> period would not be very interesting. And neither would a driver which
> did not make a very good effort (such as always rounding to multiples of
> 10 when it could round to multiples of 3 or whatever). So perhaps
> s/shall/should/.

Ah, that's what I formalized as "return the *biggest possible* period
not bigger than the request". fine.

> > Or that it's ok to increase the period iff the duty_cycle is 0.
>
> IMO it doesn't matter what the period is for a duty cycle of 0 or 100.
> Whatever policy we decide on, the behavior in that case will

So it might even be you who thinks that the request

.period = 15
.duty_cycle = 0

should not be refused just because the smallest implementable period is
16.4 ns :-)

> > Then you have to adapt all 50 or so drivers to adapt the policy.
>
> Of course, as I understand it, this must be done for your policy as
> well.

Well to be fair, yes. But the advantage of my policy is that it
returns success in more situations and so the core (and so the consumer)
can work with that in more cases.

> > Better let .apply_state() do the same as .round_state() and then you can
> > have in the core (i.e. in a single place):
> >
> > def pwm_apply_state(pwm, state):
> > rounded_state = pwm_round_state(pwm, state)
> > if some_condition(rounded_state, state):
> > return -ERANGE
> > else:
> > pwm->apply(pwm, state)
> >
> > Having said that I think some_condition should always return False, but
> > independant of the discussion how some_condition should actually behave
> > this is definitively better than to hardcode some_condition in each
> > driver.
>
> And IMO the condition should just be "is the period different"?

So a request of .period = X must result in a real period that's bigger
than X - 1 and not bigger than X, correct?

> I think a nice interface for many existing users would be something like
>
> # this ignores polarity and intermediate errors, but that should
> # not be terribly difficult to add
> def pwm_apply_relative_duty_cycle(pwm, duty_cycle, scale):
> state = pwm_get_state(pwm)
> state.enabled = True
> state = pwm_set_relative_duty_cycle(state, duty_cycle, scale)
> rounded_state = pwm_round_state(pwm, state)
> if rounded_state.period != state.period:
> state = pwm_set_relative_duty_cycle(rounded_state, duty_cycle, scale)
> rounded_state = pwm_round_state(pwm, state)

This should be rounded_state, right?! -----------------^^^^^

> if duty_cycle and not rounded_state.duty_cycle:
> return -ERANGE
> return pwm_apply_state(pwm, rounded_state)

(Fixed tabs vs space indention)

I oppose to the duty_cycle and not rounded_state.duty_cycle check. Zero
shouldn't be handled differently to other values. If it's ok to round 32
ns down to 16.4 ns, rounding down 2 ns to 0 should be fine, too.

Also for these consumers it might make sense to allow rounding up
period, so if a consumer requests .period = 32 ns, better yield 32.8 ns
instead of 16.4 ns.

> which of course could be implemented both with your proposed semantics
> or with mine.

Yeah, and each pwm_state that doesn't yield an error is an advantage.
(OK, you could argue now that period should be round up if a too small
value for period is requested. That's a weighing to reduce complexity in
the lowlevel drivers.)

> > > * The apply_state function shall only round the requested duty cycle
> > > down. The apply_state function shall not return an error unless there
> > > is no duty cycle less than the requested duty cycle which is
> > > representable by the PWM.
> >
> > ack. (Side note: Most drivers can implement duty_cycle = 0, so for them
> > duty_cycle isn't a critical thing.)
>
> Yes, and unfortunately the decision is not as clear-cut as for period.

Oh, note that up to now we consider different options as the right thing
to do with period. That's not what I would call clear-cut :-)

> > > * After applying a state returned by round_state with apply_state,
> > > get_state must return that state.
> >
> > ack.
> >
> > > The reason that we must return an error when the period is
> > > unrepresentable is that generally the duty cycle is calculated based on
> > > the period. This change has no affect on future users of round_state,
> > > since that function will only return valid periods. Those users will
> > > have the opportunity to detect that the period has changed and determine
> > > if the duty cycle is still acceptable.
> >
> > ack up to here.
> >
> > > However, for existing users, we
> > > should also provide the same opportunity.
> >
> > Here you say: If the period has changed they should get a return value
> > of -ERANGE, right? Now what should they do with that. Either they give
> > up (which is bad)
>
> No, this is exactly what we want. Consider how period is set. Either
> it is whatever the default is (e.g. set by PoR or the bootloader), in
> which case it is a driver bug if we think it is unrepresentable, or it
> is set from the device tree (or platform info), in which case it is a
> bug in the configuration. This is not something like duty cycle where
> you could make a case depending on the user, but an actual case of
> misconfiguration.

That is very little cooperative. The result is that the pwm-led driver
fails to blink because today the UART driver was probed before the
pwm-led and changed a clk in a way that the pwm-led cannot achieve the
configured period any more.

> > or they need to resort to pwm_round_state to
> > find a possible way forward. So they have to belong in the group of
> > round_state users and so they can do this from the start and then don't
> > need to care about some_condition at all.
> >
> > > This requirement simplifies
> > > the behavior of apply_state, since there is no longer any chance that
> > > the % duty cycle is rounded up.
> >
> > This is either wrong, or I didn't understand you. For my hypothetical
> > hardware that can implement periods and duty_cycles that are multiples
> > of 16.4 ns the following request:
> >
> > period = 1650
> > duty_cycle = 164
> >
> > (with relative duty_cycle = 9.9393939393939 %)
> > will be round to:
> >
> > period = 1640
> > duty_cycle = 164
> >
> > which has a higher relative duty_cycle (i.e. 10%).
>
> This is effectively bound by the clause above to be no more than the
> underlying precision of the PWM. Existing users expect to be able to
> pass unrounded periods/duty cycles, so we need to round in some manner.
> Any way we round is OK, as long as it is not terribly excessive (hence
> the clause above). We could have chosen to round up (and in fact this is
> exactly what happens for inverted polarity PWMs). But I think that for
> ease of implementation is is better to mostly round in the same manner
> as round_state.
>
> > > This requirement is easy to implement in
> > > drivers as well. Instead of writing something like
> > >
> > > period = clamp(period, min_period, max_period);
> > >
> > > they will instead write
> > >
> > > if (period < min_period || period > max_period)
> > > return -ERANGE;
> >
> > Are you aware what this means for drivers that only support a single
> > fixed period?
>
> This is working as designed. Either the period comes from configuration
> (e.g. pwm_init_state), which is specialized to the board in question, in
> which case it is OK to return an error because the writer of the dts
> either should leave it as the default or specify it correctly, or it
> comes from pwm_get_state in which case it is a driver error for
> returning a a period which that driver cannot support.
>
> There are two exceptions to the above. First, a fixed period PWM driver
> could have its period changed by the parent clock frequency changing.
> But I think such driver should just clk_rate_exclusive_get because
> otherwise all bets are off. You just have to hope your consumer doesn't
> care about the period.
>
> The other exception is pwm_clk. In this case, I think it is reasonable
> to pass an error on if the user tries to change the frequency of what is
> effectively a fixed-rate clock.
>
> > I still think it should be:
> >
> > if (period < min_period)
> > return -ERANGE;
> >
> > if (period > max_period)
> > period = max_period;
> >
> > There are two reasons for this compared to your suggestion:
> >
> > a) Consider again the 16.4 ns driver and that it is capable to
> > implement periods up to 16400 ns. With your approach a request of
> > 16404 ns will yield -ERANGE.
> > Now compare that with a different 16.4 ns driver with max_period =
> > 164000 ns. The request of 16404 ns will yield 16400 ns, just because
> > this driver could also do 16416.4 ns. This is strange, because the
> > possibility to do 16416.4 ns is totally irrelevant here, isn't it?
>
> Ah, it looks like I mis-specified this a little bit. My intent was
>
> The apply_state function shall only round the requested period
> down, and should do so by no more than one unit cycle. If the
> period *rounded as such* is unrepresentable by the PWM, the
> apply_state function shall return -ERANGE.

I don't understand "one unit cycle". What is a unit cycle for a PWM that
can implement periods in the form 10 s / X for X in [1, ... 4096]? What
is a unit cycle for a fixed period PWM?

> > b) If a consumer asked for a certain state and gets back -ENORANGE they
> > don't know if they should increase or decrease the period to guess a
> > state that might be implementable instead.
>
> Because I believe this is effectively a configuration issue, it should
> be obvious to the user which direction they need to go. Programmatic
> users which want to automatically pick a better period would need to use
> round_state instead.

You only consider consumers with a fixed period. Do you want to
explicitly configure all possible periods for a a driver that uses ~ 50%
relative duty cycle but varies period (e.g. the pwm-ir-tx driver)?
(OK, these drivers could use pwm_round_rate(), but then that argument
could be applied to all consumers and the result of an unrounded request
doesn't really matter any more.)

> > (Hmm, or are you only talking about .apply_state and only .round_state
> > should do if (period < min_period) return -ERANGE; if (period >
> > max_period) period = max_period;?
>
> Yes.

I really want to have .apply_state() and .round_state() to behave
exactly the same. Everything else I don't want to ask from driver
authors. I don't believe you can argue enough that I will drop this
claim.

> > If so, I'd like to have this in the framework, not in each driver.
> > Then .round_state and .apply_state can be identical which is good for
> > reducing complexity.)
>
> So you would like each PWM driver to have a "max_period" and
> "min_period" parameter?

No, I don't want this explicitly. What did I write to make you think
that? With .round_state() these values can be found out though.

> And what if the clock rate changes? Otherwise, how do you propose that
> the framework detect when a requested period is out of range?

I call round_rate() with

.period = requested_period
.duty_cycle = requested_period

and if that returns -ERANGE the PWM doesn't support this rate (and
smaller ones). And a big requested period is never out of range.

Best regards
Uwe

--
Pengutronix e.K. | Uwe Kleine-K?nig |
Industrial Linux Solutions | https://www.pengutronix.de/ |


Attachments:
(No filename) (15.26 kB)
signature.asc (499.00 B)
Download all attachments

2021-07-13 21:54:00

by Sean Anderson

[permalink] [raw]
Subject: Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer



On 7/12/21 3:49 PM, Uwe Kleine-K?nig wrote:
> Hello Sean,
>
> On Mon, Jul 12, 2021 at 12:26:47PM -0400, Sean Anderson wrote:
>> On 7/8/21 3:43 PM, Uwe Kleine-K?nig wrote:
>> > Hello Sean,
>> >
>> > On Thu, Jul 08, 2021 at 12:59:18PM -0400, Sean Anderson wrote:
>> > > And what if the consumer comes and requests 49 for their period in the
>> > > first place? You have the same problem. The rescaling made it worse in
>> > > this instance, but this is just an unfortunate case study.
>> >
>> > I cannot follow. There are cases that are easy and others are hard.
>> > Obviously I presented a hard case, and just because there are simpler
>> > cases, too, doesn't mean that implementing the algorithm that must cover
>> > all cases becomes simple, too. Maybe I just didn't understand what you
>> > want to say?!
>>
>> My point is that you cannot just pick a bad case and call the whole
>> process poor.
>
> Yeah, there are surprises with both approaches. My point is mostly that
> if you cannot prevent these surprises with a more complicate algorithm,
> then better live with the surprises and stick to the simple algorithm.
>
> (For the sake of completeness: If the request for my 16.4 ns PWM was
>
> .period = 100 ns
> .duty_cycle = 49 ns
>
> the hardware should be configured with
>
> .period = 98.4 ns
> .duty_cycle = 32.8 ns
>
> . The key difference to the scaling approach is: The computation is easy
> and it's clear how it should be implemented. So I don't see how my
> approach yields problems.)

It doesn't, which is why I suggested using it below.

>> I can do the same thing for your proposed process.
>> In any case, I don't wish to propose that drivers do rescaling in this
>> manner; I hoped that my below discussion had made that clear.
>
> Yes, After reading the start of your mail I was positively surprised to
> see you reasoning for nearly the same things as I do :-)
>
>> Though I really would like if we could pick a different name for "the
>> duration of the initial part of the PWM's cycle". I know "high time" is
>> not strictly correct for inverted polarity, but duty cycle refers to
>> percentage everywhere but the Linux kernel...
>
> "active time" would be a term that should be fine. But I think
> "duty-cycle" in contrast to "relative duty-cycle" is fine enough and
> IMHO we should keep the terms as they are.

It is very easy to misread one for the other, as I did above. I think
active time is a good term.

>> > > * Hypothetical future users of some kind of round_state function. These
>> > > users have some kind of algorithm which determines whether a PWM state
>> > > is acceptable for the driver. Most of the time this will be some kind
>> > > of accuracy check. What the round_state function returns is not
>> > > particularly important, because users have the opportunity to revise
>> > > their request based on what the state is rounded to. However, it is
>> > > important that each round rate function is consistent in manner that
>> > > it rounds so that these users
>> >
>> > This sentence isn't complete, is it?
>>
>> this should be finished like
>>
>> ... can manipulate it programmatically.
>
> Then ack.
>
>> > > * The apply_state function shall only round the requested period down, and
>> > > may do so by no more than one unit cycle. If the requested period is
>> > > unrepresentable by the PWM, the apply_state function shall return
>> > > -ERANGE.
>> >
>> > I don't understand what you mean by "more than one unit cycle", but
>> > that doesn't really matter for what I think is wrong with that
>> > approach: I think this is a bad idea if with "apply_state" you mean
>> > the callback each driver has to implement: Once you made all drivers
>> > conformant to this, someone will argue that one unit cycle is too
>> > strict.
>>
>> The intent here is to provide guidance against drivers which round
>> excessively. That is, a driver which always rounded down to its minimum
>> period would not be very interesting. And neither would a driver which
>> did not make a very good effort (such as always rounding to multiples of
>> 10 when it could round to multiples of 3 or whatever). So perhaps
>> s/shall/should/.
>
> Ah, that's what I formalized as "return the *biggest possible* period
> not bigger than the request". fine.
>
>> > Or that it's ok to increase the period iff the duty_cycle is 0.
>>
>> IMO it doesn't matter what the period is for a duty cycle of 0 or 100.
>> Whatever policy we decide on, the behavior in that case will
>
> So it might even be you who thinks that the request
>
> .period = 15
> .duty_cycle = 0
>
> should not be refused just because the smallest implementable period is
> 16.4 ns :-)

If the general policy is to refuse periods less than the minimum
representable, then this should be refused. Otherwise, it should be
allowed. This edge case is not worth complicating drivers.

>> > Then you have to adapt all 50 or so drivers to adapt the policy.
>>
>> Of course, as I understand it, this must be done for your policy as
>> well.
>
> Well to be fair, yes. But the advantage of my policy is that it
> returns success in more situations and so the core (and so the consumer)
> can work with that in more cases.

Failure is as important for working with an API as success is.

>> > Better let .apply_state() do the same as .round_state() and then you can
>> > have in the core (i.e. in a single place):
>> >
>> > def pwm_apply_state(pwm, state):
>> > rounded_state = pwm_round_state(pwm, state)
>> > if some_condition(rounded_state, state):
>> > return -ERANGE
>> > else:
>> > pwm->apply(pwm, state)
>> >
>> > Having said that I think some_condition should always return False, but
>> > independant of the discussion how some_condition should actually behave
>> > this is definitively better than to hardcode some_condition in each
>> > driver.
>>
>> And IMO the condition should just be "is the period different"?
>
> So a request of .period = X must result in a real period that's bigger
> than X - 1 and not bigger than X, correct?

Yes, if 1 is replaced by a suitable

>> I think a nice interface for many existing users would be something like
>>
>> # this ignores polarity and intermediate errors, but that should
>> # not be terribly difficult to add
>> def pwm_apply_relative_duty_cycle(pwm, duty_cycle, scale):
>> state = pwm_get_state(pwm)
>> state.enabled = True
>> state = pwm_set_relative_duty_cycle(state, duty_cycle, scale)
>> rounded_state = pwm_round_state(pwm, state)
>> if rounded_state.period != state.period:
>> state = pwm_set_relative_duty_cycle(rounded_state, duty_cycle, scale)
>> rounded_state = pwm_round_state(pwm, state)
>
> This should be rounded_state, right?! -----------------^^^^^

No, since we just recalculated the duty cycle on the line above.

>
>> if duty_cycle and not rounded_state.duty_cycle:
>> return -ERANGE
>> return pwm_apply_state(pwm, rounded_state)
>
> (Fixed tabs vs space indention)
>
> I oppose to the duty_cycle and not rounded_state.duty_cycle check. Zero
> shouldn't be handled differently to other values. If it's ok to round 32
> ns down to 16.4 ns, rounding down 2 ns to 0 should be fine, too.
>
> Also for these consumers it might make sense to allow rounding up
> period, so if a consumer requests .period = 32 ns, better yield 32.8 ns
> instead of 16.4 ns.

It might. Allowing apply_state to round closest while round_state rounds
down would be a good useability improvement IMO. The exact rounding
behavior of apply_state is not as important as round_state, so the
requirements detailed below could certainly be loosened.

>> which of course could be implemented both with your proposed semantics
>> or with mine.
>
> Yeah, and each pwm_state that doesn't yield an error is an advantage.
> (OK, you could argue now that period should be round up if a too small
> value for period is requested. That's a weighing to reduce complexity in
> the lowlevel drivers.)

Again, I don't think we should round period in apply_state, because that
throws % duty cycle (which most drivers care about) out the window. And
we both agree that rescaling duty cycle adds too much complexity for
drivers.

>> > > * The apply_state function shall only round the requested duty cycle
>> > > down. The apply_state function shall not return an error unless there
>> > > is no duty cycle less than the requested duty cycle which is
>> > > representable by the PWM.
>> >
>> > ack. (Side note: Most drivers can implement duty_cycle = 0, so for them
>> > duty_cycle isn't a critical thing.)
>>
>> Yes, and unfortunately the decision is not as clear-cut as for period.
>
> Oh, note that up to now we consider different options as the right thing
> to do with period. That's not what I would call clear-cut :-)
>
>> > > * After applying a state returned by round_state with apply_state,
>> > > get_state must return that state.
>> >
>> > ack.
>> >
>> > > The reason that we must return an error when the period is
>> > > unrepresentable is that generally the duty cycle is calculated based on
>> > > the period. This change has no affect on future users of round_state,
>> > > since that function will only return valid periods. Those users will
>> > > have the opportunity to detect that the period has changed and determine
>> > > if the duty cycle is still acceptable.
>> >
>> > ack up to here.
>> >
>> > > However, for existing users, we
>> > > should also provide the same opportunity.
>> >
>> > Here you say: If the period has changed they should get a return value
>> > of -ERANGE, right? Now what should they do with that. Either they give
>> > up (which is bad)
>>
>> No, this is exactly what we want. Consider how period is set. Either
>> it is whatever the default is (e.g. set by PoR or the bootloader), in
>> which case it is a driver bug if we think it is unrepresentable, or it
>> is set from the device tree (or platform info), in which case it is a
>> bug in the configuration. This is not something like duty cycle where
>> you could make a case depending on the user, but an actual case of
>> misconfiguration.
>
> That is very little cooperative. The result is that the pwm-led driver
> fails to blink because today the UART driver was probed before the
> pwm-led and changed a clk in a way that the pwm-led cannot achieve the
> configured period any more.

This is fine. There are two options for such a board. First, don't set
the period explicitly. You can always just leave the period at whatever
the default is. If you don't actually care about the period (since it
could change every boot), then you shouldn't be setting it explicitly.
Second, add an `assigned-clock-frequencies` property to the clocks node
and remove the race condition.

If you don't grab the clock rate, then you can easily have some
interaction like

pwm_round_rate()
clk_set_rate()
pwm_apply_state()

where suddenly the period you requested might be unattainable, or could
be rounded completely differently.

>> > or they need to resort to pwm_round_state to
>> > find a possible way forward. So they have to belong in the group of
>> > round_state users and so they can do this from the start and then don't
>> > need to care about some_condition at all.
>> >
>> > > This requirement simplifies
>> > > the behavior of apply_state, since there is no longer any chance that
>> > > the % duty cycle is rounded up.
>> >
>> > This is either wrong, or I didn't understand you. For my hypothetical
>> > hardware that can implement periods and duty_cycles that are multiples
>> > of 16.4 ns the following request:
>> >
>> > period = 1650
>> > duty_cycle = 164
>> >
>> > (with relative duty_cycle = 9.9393939393939 %)
>> > will be round to:
>> >
>> > period = 1640
>> > duty_cycle = 164
>> >
>> > which has a higher relative duty_cycle (i.e. 10%).
>>
>> This is effectively bound by the clause above to be no more than the
>> underlying precision of the PWM. Existing users expect to be able to
>> pass unrounded periods/duty cycles, so we need to round in some manner.
>> Any way we round is OK, as long as it is not terribly excessive (hence
>> the clause above). We could have chosen to round up (and in fact this is
>> exactly what happens for inverted polarity PWMs). But I think that for
>> ease of implementation is is better to mostly round in the same manner
>> as round_state.
>>
>> > > This requirement is easy to implement in
>> > > drivers as well. Instead of writing something like
>> > >
>> > > period = clamp(period, min_period, max_period);
>> > >
>> > > they will instead write
>> > >
>> > > if (period < min_period || period > max_period)
>> > > return -ERANGE;
>> >
>> > Are you aware what this means for drivers that only support a single
>> > fixed period?
>>
>> This is working as designed. Either the period comes from configuration
>> (e.g. pwm_init_state), which is specialized to the board in question, in
>> which case it is OK to return an error because the writer of the dts
>> either should leave it as the default or specify it correctly, or it
>> comes from pwm_get_state in which case it is a driver error for
>> returning a a period which that driver cannot support.
>>
>> There are two exceptions to the above. First, a fixed period PWM driver
>> could have its period changed by the parent clock frequency changing.
>> But I think such driver should just clk_rate_exclusive_get because
>> otherwise all bets are off. You just have to hope your consumer doesn't
>> care about the period.
>>
>> The other exception is pwm_clk. In this case, I think it is reasonable
>> to pass an error on if the user tries to change the frequency of what is
>> effectively a fixed-rate clock.
>>
>> > I still think it should be:
>> >
>> > if (period < min_period)
>> > return -ERANGE;
>> >
>> > if (period > max_period)
>> > period = max_period;
>> >
>> > There are two reasons for this compared to your suggestion:
>> >
>> > a) Consider again the 16.4 ns driver and that it is capable to
>> > implement periods up to 16400 ns. With your approach a request of
>> > 16404 ns will yield -ERANGE.
>> > Now compare that with a different 16.4 ns driver with max_period =
>> > 164000 ns. The request of 16404 ns will yield 16400 ns, just because
>> > this driver could also do 16416.4 ns. This is strange, because the
>> > possibility to do 16416.4 ns is totally irrelevant here, isn't it?
>>
>> Ah, it looks like I mis-specified this a little bit. My intent was
>>
>> The apply_state function shall only round the requested period
>> down, and should do so by no more than one unit cycle. If the
>> period *rounded as such* is unrepresentable by the PWM, the
>> apply_state function shall return -ERANGE.
>
> I don't understand "one unit cycle". What is a unit cycle for a PWM that
> can implement periods in the form 10 s / X for X in [1, ... 4096]? What
> is a unit cycle for a fixed period PWM?

One... quanta? step? detent? The idea is that the driver should give its
best approximation which doesn't just round everything down to the
maximum period. So if I were to better capture my above sentiment, I
would suggest

The apply_state function shall round the requested period down
to the largest representable period less than or equal to the
requested period. However, it may not round a period larger than
the largest representable period by more than the difference
between the largest representable period and the next largest
representable period. If no such period exists, or the period is
too large to be rounded, the apply_state function shall return
-ERANGE.

In your example, periods of 15s would be rounded down to 10s, since the
difference between the largest period (10s) and the next largest (5s) is
5s. Unfortunately, when the difference between adjacent periods is a
substantial fraction of the period itself, whatever the user requested
goes out the window if they don't calculate their duty cycle with a
period which can be represented exactly.

But the above specification would necessitate implementations like

if (period < 15ULL * NSEC_PER_SEC)
period = min(period, 10ULL * NSEC_PER_SEC);
X = 10ULL * NSEC_PER_SEC / period;

which is arbitrary and requires extra instructions to enforce. So I
would prefer this clause as originally stated, since it makes for easier
implementation. And I suspect that the number of users hitting this edge
case is fairly slim.

>> > b) If a consumer asked for a certain state and gets back -ENORANGE they
>> > don't know if they should increase or decrease the period to guess a
>> > state that might be implementable instead.
>>
>> Because I believe this is effectively a configuration issue, it should
>> be obvious to the user which direction they need to go. Programmatic
>> users which want to automatically pick a better period would need to use
>> round_state instead.
>
> You only consider consumers with a fixed period. Do you want to
> explicitly configure all possible periods for a a driver that uses ~ 50%
> relative duty cycle but varies period (e.g. the pwm-ir-tx driver)?

From what I can tell this driver doesn't really change the period very
much (mostly just to change standard). I think if you are using a
fixed-period PWM as a carrier frequency, your PWM had better be very
nearly the frequency it should. I would certainly rather get an error
about how my pwm was 40KHz instead of 36KHz than have the driver merrily
continue on. This driver is a very good candidate for round_state,
because it (should) care about what the period is. Even normal PWM
rounding could cause you to completely miss your target frequency (e.g.
request 38KHz, rounded to 36KHz). Of course, this driver doesn't even
check the result of pwm_config, so all bets are off.

And yes, I admit that if you had (e.g.) a driver with a fixed-period PWM
which you were using for IR, and you applied these rules, then it would
break your setup. But I think it is fairly easy to hold off on
converting fixed-frequency PWMs used for IR until the consumer drivers
were converted to round_state.

> (OK, these drivers could use pwm_round_rate(), but then that argument
> could be applied to all consumers and the result of an unrounded request
> doesn't really matter any more.)
>
>> > (Hmm, or are you only talking about .apply_state and only .round_state
>> > should do if (period < min_period) return -ERANGE; if (period >
>> > max_period) period = max_period;?
>>
>> Yes.
>
> I really want to have .apply_state() and .round_state() to behave
> exactly the same. Everything else I don't want to ask from driver
> authors. I don't believe you can argue enough that I will drop this
> claim.

And yet, round_state does not exist yet. Why should we match apply_state
behavior with something which is not implemented? You've noted how
certain checks can be implemented with round_state, but until then it is
better to raise an error in the driver.

>> > If so, I'd like to have this in the framework, not in each driver.
>> > Then .round_state and .apply_state can be identical which is good for
>> > reducing complexity.)
>>
>> So you would like each PWM driver to have a "max_period" and
>> "min_period" parameter?
>
> No, I don't want this explicitly. What did I write to make you think
> that? With .round_state() these values can be found out though.
>> And what if the clock rate changes?

See discussion above about exclusive get.

>> Otherwise, how do you propose that
>> the framework detect when a requested period is out of range?
>
> I call round_rate() with
>
> .period = requested_period
> .duty_cycle = requested_period
>
> and if that returns -ERANGE the PWM doesn't support this rate (and
> smaller ones). And a big requested period is never out of range.

And what do you do about e.g. pwm-sifive where the min/max can change at
any time?

--Sean