@@ -748,6 +748,18 @@ config PWM_SUN4I
To compile this driver as a module, choose M here: the module
will be called pwm-sun4i.
+config PWM_SUN8I
+ tristate "Allwinner sun8i/sun50i PWM support"
+ depends on ARCH_SUNXI || COMPILE_TEST
+ depends on HAS_IOMEM && COMMON_CLK
+ help
+ PWM framework driver for Allwinner controllers whose channels share
+ paired source clocks. In addition to PWM operation, each channel's
+ hardware bypass path can be exported as a clock output.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-sun8i.
+
config PWM_SUNPLUS
tristate "Sunplus PWM support"
depends on ARCH_SUNPLUS || COMPILE_TEST
@@ -68,6 +68,7 @@ obj-$(CONFIG_PWM_STM32) += pwm-stm32.o
obj-$(CONFIG_PWM_STM32_LP) += pwm-stm32-lp.o
obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o
obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o
+obj-$(CONFIG_PWM_SUN8I) += pwm-sun8i.o
obj-$(CONFIG_PWM_SUNPLUS) += pwm-sunplus.o
obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o
obj-$(CONFIG_PWM_TH1520) += pwm_th1520.o
new file mode 100644
@@ -0,0 +1,1542 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Allwinner sun8i Pulse Width Modulation Controller
+ *
+ * (C) Copyright 2025 Richard Genoud, Bootlin <richard.genoud@bootlin.com>
+ * (C) Copyright 2026 James Hilliard <james.hilliard1@gmail.com>
+ *
+ * Based on drivers/pwm/pwm-sun4i.c with Copyright:
+ *
+ * Copyright (C) 2014 Alexandre Belloni <alexandre.belloni@bootlin.com>
+ *
+ * Channels are paired (0/1, 2/3, 4/5). Each pair shares a clock source and
+ * first prescaler (div_m), while each channel has its own second prescaler
+ * (div_k) and bypass path.
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/limits.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/time.h>
+
+/* PWMCC Pairs Clock Configuration Registers */
+#define SUN8I_PWM_PCCR(pair) (0x20 + ((pair) * 0x4))
+#define SUN8I_PWM_PCCR_SRC_SHIFT 7
+#define SUN8I_PWM_PCCR_SRC_MASK GENMASK(1, 0)
+#define SUN8I_PWM_PCCR_GATE_BIT 4
+#define SUN8I_PWM_PCCR_GATE BIT(SUN8I_PWM_PCCR_GATE_BIT)
+#define SUN8I_PWM_PCCR_BYPASS_BIT(chan) ((chan) % 2 + 5)
+#define SUN8I_PWM_PCCR_DIV_M_SHIFT 0
+#define SUN8I_PWM_PCCR_DIV_M_WIDTH 4
+
+/* PWM Enable Register */
+#define SUN8I_PWM_PER 0x40
+#define SUN8I_PWM_ENABLE(chan) BIT(chan)
+
+/* PWM Control Register */
+#define SUN8I_PWM_PCR(chan) (0x60 + (chan) * 0x20)
+#define SUN8I_PWM_PCR_PRESCAL_K_MASK GENMASK(7, 0)
+#define SUN8I_PWM_PCR_ACTIVE_STATE BIT(8)
+#define SUN8I_PWM_PCR_MODE BIT(9)
+#define SUN8I_PWM_PCR_PULSE_START BIT(10)
+#define SUN8I_PWM_PCR_PERIOD_READY BIT(11) /* 0: ready, 1: busy */
+
+/* PWM Period Register */
+#define SUN8I_PWM_PPR(chan) (0x64 + (chan) * 0x20)
+#define SUN8I_PWM_PPR_PERIOD_MASK GENMASK(31, 16)
+#define SUN8I_PWM_PPR_DUTY_MASK GENMASK(15, 0)
+#define SUN8I_PWM_PPR_PERIOD_VALUE(reg) (FIELD_GET(SUN8I_PWM_PPR_PERIOD_MASK, reg) + 1)
+#define SUN8I_PWM_PPR_DUTY_VALUE(reg) FIELD_GET(SUN8I_PWM_PPR_DUTY_MASK, reg)
+#define SUN8I_PWM_PPR_PERIOD(prd) FIELD_PREP(SUN8I_PWM_PPR_PERIOD_MASK, (prd) - 1)
+#define SUN8I_PWM_PPR_DUTY(dty) FIELD_PREP(SUN8I_PWM_PPR_DUTY_MASK, dty)
+#define SUN8I_PWM_PPR_PERIOD_MAX (FIELD_MAX(SUN8I_PWM_PPR_PERIOD_MASK) + 1)
+
+/* PWM pair dead-zone control registers. */
+#define SUN8I_PWM_PDZCR(pair) (0x30 + ((pair) * 0x4))
+#define SUN8I_PWM_PDZCR_ENABLE BIT(0)
+
+#define SUN8I_PWM_PERIOD_READY_MARGIN_US 50
+#define SUN8I_PWM_PERIOD_READY_MIN_POLL_US 10
+#define SUN8I_PWM_PERIOD_READY_MAX_POLL_US 10000
+#define SUN8I_PWM_PERIOD_READY_POLL_COUNT 1024
+
+#define SUN8I_PWM_PAIR_IDX(chan) ((chan) >> 1)
+
+/*
+ * Block diagram of the PWM clock controller:
+ *
+ * _____ ______ ________
+ * OSC24M --->| | | | | |
+ * APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> SUN8I_PWM_clock_src_xy
+ * |_____| |______| |________|
+ * ________
+ * | |
+ * +->| /div_k |---> SUN8I_PWM_clock_x
+ * | |________|
+ * | ______
+ * | | |
+ * +-->| Gate |----> SUN8I_PWM_bypass_clock_x
+ * | |______|
+ * SUN8I_PWM_clock_src_xy ---+ ________
+ * | | |
+ * +->| /div_k |---> SUN8I_PWM_clock_y
+ * | |________|
+ * | ______
+ * | | |
+ * +-->| Gate |----> SUN8I_PWM_bypass_clock_y
+ * |______|
+ *
+ * NB: when the bypass is set, all the PWM logic is bypassed.
+ * So, the duty cycle and polarity can't be modified (we just have a clock).
+ * The bypass in PWM mode is used to achieve a 1/2 relative duty cycle with the
+ * fastest clock.
+ *
+ * SUN8I_PWM_clock_x/y serve for the PWM purpose.
+ * SUN8I_PWM_bypass_clock_x/y serve for the clock-provider purpose.
+ *
+ */
+
+/* /div_m is a power-of-two divider limited to /256. */
+static const struct clk_div_table sun8i_pwm_div_m_table[] = {
+ { .val = 0, .div = 1 },
+ { .val = 1, .div = 2 },
+ { .val = 2, .div = 4 },
+ { .val = 3, .div = 8 },
+ { .val = 4, .div = 16 },
+ { .val = 5, .div = 32 },
+ { .val = 6, .div = 64 },
+ { .val = 7, .div = 128 },
+ { .val = 8, .div = 256 },
+ { /* sentinel */ }
+};
+
+enum sun8i_pwm_mode {
+ SUN8I_PWM_MODE_NONE,
+ SUN8I_PWM_MODE_PWM,
+ SUN8I_PWM_MODE_CLK,
+};
+
+struct sun8i_pwm_data {
+ unsigned int npwm;
+};
+
+struct sun8i_pwm_chip;
+
+struct sun8i_pwm_pair {
+ struct clk_mux mux;
+ struct clk_hw gate_hw;
+ struct clk_divider divider;
+ struct clk_hw *hw;
+ struct notifier_block rate_nb;
+ struct sun8i_pwm_chip *chip;
+ unsigned int index;
+};
+
+struct sun8i_pwm_bypass {
+ struct clk_hw hw;
+ struct sun8i_pwm_chip *chip;
+ unsigned int index;
+};
+
+struct sun8i_pwm_channel {
+ struct sun8i_pwm_bypass bypass;
+ struct clk *pair_clk;
+ enum sun8i_pwm_mode mode;
+ u64 pending_period_ns;
+ bool rate_exclusive;
+};
+
+struct sun8i_pwm_chip {
+ struct clk_hw_onecell_data *hw_data;
+ struct sun8i_pwm_pair *pairs;
+ struct sun8i_pwm_channel *channels;
+ struct clk *bus_clk;
+ void __iomem *base;
+ const struct sun8i_pwm_data *data;
+ /* Protects shared registers and channel ownership. */
+ spinlock_t lock;
+};
+
+struct sun8i_pwm_waveform {
+ u32 duty_ticks;
+ u32 period_ticks;
+ u32 pair_rate;
+ u16 div_k;
+ u8 enabled:1;
+ u8 active_state:1;
+ u8 bypass_en:1;
+};
+
+static inline struct sun8i_pwm_chip *sun8i_pwm_from_chip(const struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static inline u32 sun8i_pwm_readl(struct sun8i_pwm_chip *sun8i_chip,
+ unsigned long offset)
+{
+ return readl(sun8i_chip->base + offset);
+}
+
+static inline void sun8i_pwm_writel(struct sun8i_pwm_chip *sun8i_chip,
+ u32 val, unsigned long offset)
+{
+ writel(val, sun8i_chip->base + offset);
+}
+
+static void sun8i_pwm_set_bypass_locked(struct sun8i_pwm_chip *sun8i_chip,
+ unsigned int idx, bool enable)
+{
+ unsigned long reg_offset;
+ u32 val;
+
+ lockdep_assert_held(&sun8i_chip->lock);
+
+ reg_offset = SUN8I_PWM_PCCR(SUN8I_PWM_PAIR_IDX(idx));
+ val = sun8i_pwm_readl(sun8i_chip, reg_offset);
+ if (enable)
+ val |= BIT(SUN8I_PWM_PCCR_BYPASS_BIT(idx));
+ else
+ val &= ~BIT(SUN8I_PWM_PCCR_BYPASS_BIT(idx));
+
+ sun8i_pwm_writel(sun8i_chip, val, reg_offset);
+}
+
+static void sun8i_pwm_set_enabled_locked(struct sun8i_pwm_chip *sun8i_chip,
+ unsigned int idx, bool enable)
+{
+ u32 val;
+
+ lockdep_assert_held(&sun8i_chip->lock);
+
+ val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);
+ if (enable)
+ val |= SUN8I_PWM_ENABLE(idx);
+ else
+ val &= ~SUN8I_PWM_ENABLE(idx);
+ sun8i_pwm_writel(sun8i_chip, val, SUN8I_PWM_PER);
+}
+
+static bool
+sun8i_pwm_channel_is_enabled_locked(struct sun8i_pwm_chip *sun8i_chip,
+ unsigned int idx)
+{
+ unsigned int pair = SUN8I_PWM_PAIR_IDX(idx);
+ u32 pccr, per;
+
+ lockdep_assert_held(&sun8i_chip->lock);
+
+ pccr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair));
+ per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);
+
+ return (pccr & SUN8I_PWM_PCCR_GATE) &&
+ (per & SUN8I_PWM_ENABLE(idx));
+}
+
+static u32 sun8i_pwm_pair_enable_mask(struct sun8i_pwm_chip *sun8i_chip,
+ unsigned int pair)
+{
+ unsigned int first = pair * 2;
+ u32 mask = SUN8I_PWM_ENABLE(first);
+
+ if (first + 1 < sun8i_chip->data->npwm)
+ mask |= SUN8I_PWM_ENABLE(first + 1);
+
+ return mask;
+}
+
+static u32
+sun8i_pwm_pair_owner_mask_locked(struct sun8i_pwm_chip *sun8i_chip,
+ unsigned int pair)
+{
+ unsigned int first = pair * 2;
+ u32 mask = 0;
+
+ lockdep_assert_held(&sun8i_chip->lock);
+
+ if (sun8i_chip->channels[first].mode != SUN8I_PWM_MODE_NONE)
+ mask |= SUN8I_PWM_ENABLE(first);
+ if (first + 1 < sun8i_chip->data->npwm &&
+ sun8i_chip->channels[first + 1].mode != SUN8I_PWM_MODE_NONE)
+ mask |= SUN8I_PWM_ENABLE(first + 1);
+
+ return mask;
+}
+
+static inline struct sun8i_pwm_pair *
+sun8i_pwm_pair_from_gate_hw(struct clk_hw *hw)
+{
+ return container_of(hw, struct sun8i_pwm_pair, gate_hw);
+}
+
+static int sun8i_pwm_pair_gate_enable(struct clk_hw *hw)
+{
+ struct sun8i_pwm_pair *pair = sun8i_pwm_pair_from_gate_hw(hw);
+ struct sun8i_pwm_chip *sun8i_chip = pair->chip;
+ unsigned long flags;
+ u32 pccr;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ pccr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair->index));
+ pccr |= SUN8I_PWM_PCCR_GATE;
+ sun8i_pwm_writel(sun8i_chip, pccr, SUN8I_PWM_PCCR(pair->index));
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+
+ return 0;
+}
+
+static void sun8i_pwm_pair_gate_disable(struct clk_hw *hw)
+{
+ struct sun8i_pwm_pair *pair = sun8i_pwm_pair_from_gate_hw(hw);
+ struct sun8i_pwm_chip *sun8i_chip = pair->chip;
+ unsigned long reg = SUN8I_PWM_PCCR(pair->index);
+ unsigned long flags;
+ u32 pccr, per;
+
+ /* CCF counts do not include outputs awaiting firmware-state handoff. */
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);
+ if (!(per & sun8i_pwm_pair_enable_mask(sun8i_chip, pair->index))) {
+ pccr = sun8i_pwm_readl(sun8i_chip, reg);
+ pccr &= ~SUN8I_PWM_PCCR_GATE;
+ sun8i_pwm_writel(sun8i_chip, pccr, reg);
+ }
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+}
+
+static int sun8i_pwm_pair_gate_is_enabled(struct clk_hw *hw)
+{
+ struct sun8i_pwm_pair *pair = sun8i_pwm_pair_from_gate_hw(hw);
+ struct sun8i_pwm_chip *sun8i_chip = pair->chip;
+ unsigned long reg = SUN8I_PWM_PCCR(pair->index);
+ unsigned long flags;
+ bool enabled;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ enabled = sun8i_pwm_readl(sun8i_chip, reg) & SUN8I_PWM_PCCR_GATE;
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+
+ return enabled;
+}
+
+static const struct clk_ops sun8i_pwm_pair_gate_ops = {
+ .enable = sun8i_pwm_pair_gate_enable,
+ .disable = sun8i_pwm_pair_gate_disable,
+ .is_enabled = sun8i_pwm_pair_gate_is_enabled,
+};
+
+static int sun8i_pwm_pair_rate_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct sun8i_pwm_pair *pair =
+ container_of(nb, struct sun8i_pwm_pair, rate_nb);
+ struct sun8i_pwm_chip *sun8i_chip = pair->chip;
+ unsigned long flags;
+ bool active;
+
+ if (event != PRE_RATE_CHANGE)
+ return NOTIFY_DONE;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ active = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER) &
+ sun8i_pwm_pair_enable_mask(sun8i_chip, pair->index);
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+
+ return active ? NOTIFY_BAD : NOTIFY_OK;
+}
+
+static inline struct sun8i_pwm_bypass *
+sun8i_pwm_bypass_from_hw(struct clk_hw *hw)
+{
+ return container_of(hw, struct sun8i_pwm_bypass, hw);
+}
+
+static int sun8i_pwm_bypass_prepare(struct clk_hw *hw)
+{
+ struct sun8i_pwm_bypass *bypass = sun8i_pwm_bypass_from_hw(hw);
+ struct sun8i_pwm_chip *sun8i_chip = bypass->chip;
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[bypass->index];
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ if (chan->mode != SUN8I_PWM_MODE_NONE)
+ ret = -EBUSY;
+ else
+ chan->mode = SUN8I_PWM_MODE_CLK;
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+
+ return ret;
+}
+
+static void sun8i_pwm_bypass_unprepare(struct clk_hw *hw)
+{
+ struct sun8i_pwm_bypass *bypass = sun8i_pwm_bypass_from_hw(hw);
+ struct sun8i_pwm_chip *sun8i_chip = bypass->chip;
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[bypass->index];
+ unsigned long flags;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ if (WARN_ON_ONCE(chan->mode != SUN8I_PWM_MODE_CLK))
+ goto out_unlock;
+
+ chan->mode = SUN8I_PWM_MODE_NONE;
+
+out_unlock:
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+}
+
+static int sun8i_pwm_bypass_enable(struct clk_hw *hw)
+{
+ struct sun8i_pwm_bypass *bypass = sun8i_pwm_bypass_from_hw(hw);
+ struct sun8i_pwm_chip *sun8i_chip = bypass->chip;
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[bypass->index];
+ unsigned int pair = SUN8I_PWM_PAIR_IDX(bypass->index);
+ unsigned long flags;
+ u32 pccr, per;
+ int ret = 0;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ if (WARN_ON_ONCE(chan->mode != SUN8I_PWM_MODE_CLK)) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ pccr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair));
+ per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);
+ if (!(pccr & BIT(SUN8I_PWM_PCCR_BYPASS_BIT(bypass->index))) ||
+ !(per & SUN8I_PWM_ENABLE(bypass->index))) {
+ sun8i_pwm_set_enabled_locked(sun8i_chip, bypass->index, false);
+ sun8i_pwm_set_bypass_locked(sun8i_chip, bypass->index, true);
+ sun8i_pwm_set_enabled_locked(sun8i_chip, bypass->index, true);
+ }
+
+out_unlock:
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+
+ return ret;
+}
+
+static void sun8i_pwm_bypass_disable(struct clk_hw *hw)
+{
+ struct sun8i_pwm_bypass *bypass = sun8i_pwm_bypass_from_hw(hw);
+ struct sun8i_pwm_chip *sun8i_chip = bypass->chip;
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[bypass->index];
+ unsigned long flags;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ if (WARN_ON_ONCE(chan->mode != SUN8I_PWM_MODE_CLK))
+ goto out_unlock;
+
+ sun8i_pwm_set_enabled_locked(sun8i_chip, bypass->index, false);
+ sun8i_pwm_set_bypass_locked(sun8i_chip, bypass->index, false);
+
+out_unlock:
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+}
+
+static void sun8i_pwm_bypass_disable_unused(struct clk_hw *hw)
+{
+ /* Defer inherited-output cleanup to sun8i_pwm_sync_state(). */
+}
+
+static int sun8i_pwm_bypass_is_enabled(struct clk_hw *hw)
+{
+ struct sun8i_pwm_bypass *bypass = sun8i_pwm_bypass_from_hw(hw);
+ struct sun8i_pwm_chip *sun8i_chip = bypass->chip;
+ unsigned int pair = SUN8I_PWM_PAIR_IDX(bypass->index);
+ unsigned long flags;
+ bool enabled;
+ u32 val;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+
+ val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair));
+ enabled = (val & SUN8I_PWM_PCCR_GATE) &&
+ (val & BIT(SUN8I_PWM_PCCR_BYPASS_BIT(bypass->index)));
+
+ val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);
+ enabled = enabled && (val & SUN8I_PWM_ENABLE(bypass->index));
+
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+
+ return enabled;
+}
+
+static unsigned long sun8i_pwm_bypass_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ return parent_rate;
+}
+
+static const struct clk_ops sun8i_pwm_bypass_ops = {
+ .prepare = sun8i_pwm_bypass_prepare,
+ .unprepare = sun8i_pwm_bypass_unprepare,
+ .enable = sun8i_pwm_bypass_enable,
+ .disable = sun8i_pwm_bypass_disable,
+ .disable_unused = sun8i_pwm_bypass_disable_unused,
+ .is_enabled = sun8i_pwm_bypass_is_enabled,
+ .recalc_rate = sun8i_pwm_bypass_recalc_rate,
+};
+
+static int sun8i_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];
+ unsigned int idx = pwm->hwpwm;
+ unsigned long flags;
+ bool was_enabled = false;
+ int ret;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ if (chan->mode != SUN8I_PWM_MODE_NONE) {
+ ret = -EBUSY;
+ } else {
+ was_enabled =
+ sun8i_pwm_channel_is_enabled_locked(sun8i_chip, idx);
+ chan->mode = SUN8I_PWM_MODE_PWM;
+ ret = 0;
+ }
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+ if (ret)
+ return ret;
+
+ if (was_enabled) {
+ ret = clk_rate_exclusive_get(chan->pair_clk);
+ if (ret)
+ goto err_release_channel;
+ WRITE_ONCE(chan->rate_exclusive, true);
+ }
+
+ ret = clk_prepare_enable(chan->pair_clk);
+ if (ret) {
+ if (READ_ONCE(chan->rate_exclusive)) {
+ clk_rate_exclusive_put(chan->pair_clk);
+ WRITE_ONCE(chan->rate_exclusive, false);
+ }
+ goto err_release_channel;
+ }
+
+ return 0;
+
+err_release_channel:
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ chan->mode = SUN8I_PWM_MODE_NONE;
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+
+ return ret;
+}
+
+static void sun8i_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];
+ unsigned long flags;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ if (WARN_ON_ONCE(chan->mode != SUN8I_PWM_MODE_PWM)) {
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+ return;
+ }
+
+ sun8i_pwm_set_enabled_locked(sun8i_chip, pwm->hwpwm, false);
+ sun8i_pwm_set_bypass_locked(sun8i_chip, pwm->hwpwm, false);
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+
+ if (READ_ONCE(chan->rate_exclusive)) {
+ clk_rate_exclusive_put(chan->pair_clk);
+ WRITE_ONCE(chan->rate_exclusive, false);
+ }
+ clk_disable_unprepare(chan->pair_clk);
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ chan->mode = SUN8I_PWM_MODE_NONE;
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+}
+
+static int sun8i_pwm_read_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ void *_wfhw)
+{
+ struct sun8i_pwm_waveform *wfhw = _wfhw;
+ struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];
+ unsigned int pair = SUN8I_PWM_PAIR_IDX(pwm->hwpwm);
+ unsigned long flags, pair_rate;
+ u32 pccr, pcr, pdzcr, per, ppr;
+
+ pair_rate = clk_get_rate(chan->pair_clk);
+ if (pair_rate > U32_MAX)
+ return -ERANGE;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ pccr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair));
+ per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);
+ pcr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCR(pwm->hwpwm));
+ ppr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PPR(pwm->hwpwm));
+ pdzcr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PDZCR(pair));
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+
+ *wfhw = (struct sun8i_pwm_waveform) {
+ .enabled = !!(pccr & SUN8I_PWM_PCCR_GATE) &&
+ !!(per & SUN8I_PWM_ENABLE(pwm->hwpwm)),
+ .bypass_en = !!(pccr &
+ BIT(SUN8I_PWM_PCCR_BYPASS_BIT(pwm->hwpwm))),
+ .active_state = !!(pcr & SUN8I_PWM_PCR_ACTIVE_STATE),
+ .duty_ticks = SUN8I_PWM_PPR_DUTY_VALUE(ppr),
+ .period_ticks = SUN8I_PWM_PPR_PERIOD_VALUE(ppr),
+ .pair_rate = pair_rate,
+ .div_k = FIELD_GET(SUN8I_PWM_PCR_PRESCAL_K_MASK, pcr) + 1,
+ };
+
+ if (wfhw->enabled && !wfhw->bypass_en &&
+ (pdzcr & SUN8I_PWM_PDZCR_ENABLE))
+ return -EOPNOTSUPP;
+ if (wfhw->enabled && !wfhw->bypass_en &&
+ (pcr & SUN8I_PWM_PCR_MODE))
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static u64 sun8i_pwm_ticks_to_ns(u32 ticks, u16 div_k, u32 pair_rate)
+{
+ return DIV_ROUND_UP_ULL(NSEC_PER_SEC * (u64)ticks * div_k,
+ pair_rate);
+}
+
+static void
+sun8i_pwm_waveform_to_ns(const struct sun8i_pwm_waveform *wfhw,
+ struct pwm_waveform *wf)
+{
+ u32 pair_rate = wfhw->pair_rate;
+ u16 div_k = wfhw->div_k;
+
+ wf->duty_offset_ns = 0;
+
+ if (!wfhw->enabled || !pair_rate) {
+ wf->period_length_ns = 0;
+ wf->duty_length_ns = 0;
+ return;
+ }
+
+ if (wfhw->bypass_en) {
+ wf->period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC,
+ pair_rate);
+ wf->duty_length_ns =
+ DIV_ROUND_UP_ULL(NSEC_PER_SEC,
+ 2ULL * pair_rate);
+ } else {
+ wf->period_length_ns = sun8i_pwm_ticks_to_ns(wfhw->period_ticks,
+ div_k, pair_rate);
+ if (wfhw->active_state) {
+ u32 duty_ticks = min(wfhw->duty_ticks,
+ wfhw->period_ticks);
+
+ wf->duty_length_ns =
+ sun8i_pwm_ticks_to_ns(duty_ticks, div_k, pair_rate);
+ } else if (!wfhw->duty_ticks) {
+ wf->duty_length_ns = wf->period_length_ns;
+ } else if (wfhw->duty_ticks >= wfhw->period_ticks) {
+ wf->duty_length_ns = 0;
+ } else {
+ u32 low_ticks = wfhw->duty_ticks;
+ u32 high_ticks = wfhw->period_ticks - wfhw->duty_ticks;
+
+ wf->duty_length_ns =
+ sun8i_pwm_ticks_to_ns(high_ticks, div_k, pair_rate);
+ wf->duty_offset_ns =
+ sun8i_pwm_ticks_to_ns(low_ticks, div_k, pair_rate);
+ }
+ }
+}
+
+static int sun8i_pwm_round_waveform_fromhw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw,
+ struct pwm_waveform *wf)
+{
+ const struct sun8i_pwm_waveform *wfhw = _wfhw;
+
+ sun8i_pwm_waveform_to_ns(wfhw, wf);
+
+ dev_dbg(pwmchip_parent(chip),
+ "pwm#%u: pair-rate=%u, div-k=%u, period=%u, duty=%u, bypass=%u, active-high=%u -> %llu/%llu [+%llu]\n",
+ pwm->hwpwm, wfhw->pair_rate, wfhw->div_k,
+ wfhw->period_ticks, wfhw->duty_ticks, wfhw->bypass_en,
+ wfhw->active_state,
+ wf->duty_length_ns, wf->period_length_ns,
+ wf->duty_offset_ns);
+
+ return 0;
+}
+
+static bool
+sun8i_pwm_pair_rate_constrained(struct sun8i_pwm_chip *sun8i_chip,
+ unsigned int idx)
+{
+ unsigned int sibling = idx ^ 1;
+ unsigned long flags;
+ bool constrained;
+
+ if (sibling >= sun8i_chip->data->npwm)
+ return false;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ constrained = sun8i_chip->channels[sibling].mode ==
+ SUN8I_PWM_MODE_CLK ||
+ READ_ONCE(sun8i_chip->channels[sibling].rate_exclusive) ||
+ sun8i_pwm_channel_is_enabled_locked(sun8i_chip,
+ sibling);
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+
+ return constrained;
+}
+
+static int sun8i_pwm_clear_idle_deadzone(struct sun8i_pwm_chip *sun8i_chip,
+ unsigned int idx)
+{
+ unsigned int pair = SUN8I_PWM_PAIR_IDX(idx);
+ unsigned long flags;
+ u32 pdzcr, per;
+ int ret = 0;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ pdzcr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PDZCR(pair));
+ if (!(pdzcr & SUN8I_PWM_PDZCR_ENABLE))
+ goto out_unlock;
+
+ per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);
+ if (per & sun8i_pwm_pair_enable_mask(sun8i_chip, pair)) {
+ ret = -EOPNOTSUPP;
+ goto out_unlock;
+ }
+
+ pdzcr &= ~SUN8I_PWM_PDZCR_ENABLE;
+ sun8i_pwm_writel(sun8i_chip, pdzcr, SUN8I_PWM_PDZCR(pair));
+
+out_unlock:
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+
+ return ret;
+}
+
+static u64 sun8i_pwm_max_period_ns(struct sun8i_pwm_chip *sun8i_chip,
+ unsigned int idx)
+{
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[idx];
+ unsigned long flags, pair_rate;
+ u32 pcr;
+ u16 div_k;
+
+ pair_rate = clk_get_rate(chan->pair_clk);
+ if (!pair_rate || pair_rate > U32_MAX)
+ return 0;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ pcr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCR(idx));
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+ div_k = FIELD_GET(SUN8I_PWM_PCR_PRESCAL_K_MASK, pcr) + 1;
+
+ return sun8i_pwm_ticks_to_ns(SUN8I_PWM_PPR_PERIOD_MAX, div_k,
+ pair_rate);
+}
+
+static int sun8i_pwm_wait_period_ready(struct sun8i_pwm_chip *sun8i_chip,
+ unsigned int idx, u64 *timeout_us)
+{
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[idx];
+ unsigned long poll_us;
+ u64 period_ns;
+ u32 val;
+ int ret;
+
+ /* PPR contains the pending value, not necessarily the active period. */
+ period_ns = chan->pending_period_ns;
+ if (!period_ns)
+ period_ns = sun8i_pwm_max_period_ns(sun8i_chip, idx);
+ *timeout_us = DIV_ROUND_UP_ULL(period_ns, NSEC_PER_USEC) +
+ SUN8I_PWM_PERIOD_READY_MARGIN_US;
+ poll_us = clamp_t(u64,
+ DIV_ROUND_UP_ULL(*timeout_us,
+ SUN8I_PWM_PERIOD_READY_POLL_COUNT),
+ SUN8I_PWM_PERIOD_READY_MIN_POLL_US,
+ SUN8I_PWM_PERIOD_READY_MAX_POLL_US);
+
+ ret = readl_poll_timeout(sun8i_chip->base + SUN8I_PWM_PCR(idx), val,
+ !(val & SUN8I_PWM_PCR_PERIOD_READY), poll_us,
+ *timeout_us);
+ if (!ret)
+ chan->pending_period_ns = 0;
+
+ return ret;
+}
+
+static int sun8i_pwm_write_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm, const void *_wfhw)
+{
+ const struct sun8i_pwm_waveform *wfhw = _wfhw;
+ struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];
+ struct device *dev = pwmchip_parent(chip);
+ unsigned long bus_rate, current_rate, restored_rate;
+ unsigned long flags;
+ u64 new_ns, old_ns, timeout_us;
+ bool acquired_exclusive = false;
+ bool can_restore_enable = true;
+ bool current_bypass, needs_quiesce, was_enabled;
+ u16 current_div_k;
+ u32 old_ticks, pcr, ppr, val;
+ int restore_ret, ret;
+
+ if (!wfhw->enabled) {
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ sun8i_pwm_set_enabled_locked(sun8i_chip, pwm->hwpwm, false);
+ sun8i_pwm_set_bypass_locked(sun8i_chip, pwm->hwpwm, false);
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+ if (READ_ONCE(chan->rate_exclusive)) {
+ clk_rate_exclusive_put(chan->pair_clk);
+ WRITE_ONCE(chan->rate_exclusive, false);
+ }
+
+ return 0;
+ }
+
+ if (!wfhw->pair_rate ||
+ (!wfhw->bypass_en &&
+ (!wfhw->div_k || wfhw->div_k > 256 ||
+ !wfhw->period_ticks ||
+ wfhw->period_ticks > SUN8I_PWM_PPR_PERIOD_MAX ||
+ wfhw->duty_ticks > FIELD_MAX(SUN8I_PWM_PPR_DUTY_MASK))))
+ return -EINVAL;
+
+ if (!wfhw->bypass_en) {
+ ret = sun8i_pwm_clear_idle_deadzone(sun8i_chip, pwm->hwpwm);
+ if (ret)
+ return ret;
+ }
+
+ current_rate = clk_get_rate(chan->pair_clk);
+ if (!current_rate || current_rate > U32_MAX)
+ return -ERANGE;
+ bus_rate = clk_get_rate(sun8i_chip->bus_clk);
+
+ if (!wfhw->bypass_en) {
+ /* Do not overwrite a PPR update which has not latched yet. */
+ ret = sun8i_pwm_wait_period_ready(sun8i_chip, pwm->hwpwm,
+ &timeout_us);
+ if (ret) {
+ dev_err_ratelimited(dev, "pwm#%u: update timeout after %llu us\n",
+ pwm->hwpwm, timeout_us);
+ return ret;
+ }
+ }
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ was_enabled = sun8i_pwm_channel_is_enabled_locked(sun8i_chip,
+ pwm->hwpwm);
+ val = sun8i_pwm_readl(sun8i_chip,
+ SUN8I_PWM_PCCR(SUN8I_PWM_PAIR_IDX(pwm->hwpwm)));
+ current_bypass = !!(val &
+ BIT(SUN8I_PWM_PCCR_BYPASS_BIT(pwm->hwpwm)));
+ pcr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCR(pwm->hwpwm));
+ ppr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PPR(pwm->hwpwm));
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+ current_div_k = FIELD_GET(SUN8I_PWM_PCR_PRESCAL_K_MASK, pcr) + 1;
+
+ if (current_rate != wfhw->pair_rate &&
+ sun8i_pwm_pair_rate_constrained(sun8i_chip, pwm->hwpwm))
+ return -EBUSY;
+
+ if (was_enabled && !READ_ONCE(chan->rate_exclusive)) {
+ ret = clk_rate_exclusive_get(chan->pair_clk);
+ if (ret)
+ return ret;
+ WRITE_ONCE(chan->rate_exclusive, true);
+ }
+
+ /*
+ * Updating PPR while running is safe only if the input clocks and
+ * polarity remain unchanged and PCLK is faster than the PWM clock.
+ * Otherwise stop the channel before touching its configuration.
+ */
+ needs_quiesce = was_enabled &&
+ (current_bypass != wfhw->bypass_en ||
+ current_rate != wfhw->pair_rate ||
+ (!wfhw->bypass_en &&
+ (!!(pcr & SUN8I_PWM_PCR_ACTIVE_STATE) !=
+ wfhw->active_state ||
+ current_div_k != wfhw->div_k ||
+ (pcr & SUN8I_PWM_PCR_MODE) ||
+ !bus_rate ||
+ bus_rate <= DIV_ROUND_UP_ULL(wfhw->pair_rate,
+ wfhw->div_k))));
+
+ if (needs_quiesce) {
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ sun8i_pwm_set_enabled_locked(sun8i_chip, pwm->hwpwm, false);
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+ }
+
+ if (!was_enabled && !READ_ONCE(chan->rate_exclusive)) {
+ if (current_rate == wfhw->pair_rate)
+ ret = clk_rate_exclusive_get(chan->pair_clk);
+ else
+ ret = clk_set_rate_exclusive(chan->pair_clk,
+ wfhw->pair_rate);
+ if (ret)
+ return ret;
+ WRITE_ONCE(chan->rate_exclusive, true);
+ acquired_exclusive = true;
+ } else if (current_rate != wfhw->pair_rate) {
+ ret = clk_set_rate(chan->pair_clk, wfhw->pair_rate);
+ if (ret)
+ goto restore_enable;
+ }
+
+ if (clk_get_rate(chan->pair_clk) != wfhw->pair_rate) {
+ ret = -EINVAL;
+ goto restore_rate;
+ }
+
+ if (!wfhw->bypass_en) {
+ /* Return the channel to cycle mode without replaying W1S bits. */
+ pcr &= ~(SUN8I_PWM_PCR_PRESCAL_K_MASK |
+ SUN8I_PWM_PCR_ACTIVE_STATE | SUN8I_PWM_PCR_MODE |
+ SUN8I_PWM_PCR_PULSE_START |
+ SUN8I_PWM_PCR_PERIOD_READY);
+ pcr |= FIELD_PREP(SUN8I_PWM_PCR_PRESCAL_K_MASK,
+ wfhw->div_k - 1);
+ if (wfhw->active_state)
+ pcr |= SUN8I_PWM_PCR_ACTIVE_STATE;
+ sun8i_pwm_writel(sun8i_chip, pcr, SUN8I_PWM_PCR(pwm->hwpwm));
+
+ val = SUN8I_PWM_PPR_DUTY(wfhw->duty_ticks);
+ val |= SUN8I_PWM_PPR_PERIOD(wfhw->period_ticks);
+ old_ns = 0;
+ if (was_enabled && !current_bypass) {
+ old_ticks = SUN8I_PWM_PPR_PERIOD_VALUE(ppr);
+ old_ns = sun8i_pwm_ticks_to_ns(old_ticks, current_div_k,
+ current_rate);
+ }
+ new_ns = sun8i_pwm_ticks_to_ns(wfhw->period_ticks,
+ wfhw->div_k, wfhw->pair_rate);
+ chan->pending_period_ns = max(old_ns, new_ns);
+ sun8i_pwm_writel(sun8i_chip, val, SUN8I_PWM_PPR(pwm->hwpwm));
+ }
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ sun8i_pwm_set_bypass_locked(sun8i_chip, pwm->hwpwm, wfhw->bypass_en);
+ sun8i_pwm_set_enabled_locked(sun8i_chip, pwm->hwpwm, true);
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+
+ return 0;
+
+restore_rate:
+ if (current_rate != wfhw->pair_rate) {
+ restore_ret = clk_set_rate(chan->pair_clk, current_rate);
+ restored_rate = clk_get_rate(chan->pair_clk);
+ if (restore_ret || restored_rate != current_rate) {
+ dev_err(dev,
+ "pwm#%u: failed to restore pair clock rate %lu: %d (got %lu)\n",
+ pwm->hwpwm, current_rate, restore_ret,
+ restored_rate);
+ can_restore_enable = false;
+ }
+ }
+ if (acquired_exclusive) {
+ clk_rate_exclusive_put(chan->pair_clk);
+ WRITE_ONCE(chan->rate_exclusive, false);
+ }
+restore_enable:
+ if (needs_quiesce && can_restore_enable) {
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ sun8i_pwm_set_enabled_locked(sun8i_chip, pwm->hwpwm, true);
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+ }
+
+ return ret;
+}
+
+struct sun8i_pwm_rounding {
+ const struct pwm_waveform *requested;
+ struct sun8i_pwm_waveform best;
+ struct pwm_waveform best_wf;
+ u32 current_pair_rate;
+ bool have_best;
+};
+
+static bool
+sun8i_pwm_candidate_is_better(const struct sun8i_pwm_rounding *rounding,
+ const struct sun8i_pwm_waveform *candidate,
+ const struct pwm_waveform *candidate_wf)
+{
+ const struct pwm_waveform *requested = rounding->requested;
+ bool candidate_period_down, best_period_down;
+
+ /* A zero-duty, zero-offset cycle-mode candidate always exists. */
+ if (candidate_wf->duty_length_ns > requested->duty_length_ns ||
+ candidate_wf->duty_offset_ns > requested->duty_offset_ns)
+ return false;
+
+ if (!rounding->have_best)
+ return true;
+
+ candidate_period_down =
+ candidate_wf->period_length_ns <= requested->period_length_ns;
+ best_period_down =
+ rounding->best_wf.period_length_ns <= requested->period_length_ns;
+ if (candidate_period_down != best_period_down)
+ return candidate_period_down;
+
+ if (candidate_wf->period_length_ns !=
+ rounding->best_wf.period_length_ns) {
+ if (candidate_period_down)
+ return candidate_wf->period_length_ns >
+ rounding->best_wf.period_length_ns;
+
+ return candidate_wf->period_length_ns <
+ rounding->best_wf.period_length_ns;
+ }
+
+ if (candidate_wf->duty_length_ns !=
+ rounding->best_wf.duty_length_ns)
+ return candidate_wf->duty_length_ns >
+ rounding->best_wf.duty_length_ns;
+
+ if (candidate_wf->duty_offset_ns !=
+ rounding->best_wf.duty_offset_ns)
+ return candidate_wf->duty_offset_ns >
+ rounding->best_wf.duty_offset_ns;
+
+ /* Avoid a shared pair-rate change when two settings are equivalent. */
+ return candidate->pair_rate == rounding->current_pair_rate &&
+ rounding->best.pair_rate != rounding->current_pair_rate;
+}
+
+static void
+sun8i_pwm_consider_candidate(struct sun8i_pwm_rounding *rounding,
+ const struct sun8i_pwm_waveform *candidate)
+{
+ struct pwm_waveform candidate_wf;
+
+ sun8i_pwm_waveform_to_ns(candidate, &candidate_wf);
+ if (!sun8i_pwm_candidate_is_better(rounding, candidate,
+ &candidate_wf))
+ return;
+
+ rounding->best = *candidate;
+ rounding->best_wf = candidate_wf;
+ rounding->have_best = true;
+}
+
+static void
+sun8i_pwm_consider_normal(struct sun8i_pwm_rounding *rounding, u32 pair_rate,
+ u16 div_k)
+{
+ const struct pwm_waveform *requested = rounding->requested;
+ struct sun8i_pwm_waveform candidate = {
+ .enabled = true,
+ .pair_rate = pair_rate,
+ .div_k = div_k,
+ };
+ u64 denominator = NSEC_PER_SEC * (u64)div_k;
+ u64 duty_ticks, period_ticks;
+ u64 duty_ns = requested->duty_length_ns;
+ u64 period_ns = requested->period_length_ns;
+ u32 inactive_ticks;
+
+ period_ticks = mul_u64_u64_div_u64(period_ns, pair_rate, denominator);
+ period_ticks = clamp_t(u64, period_ticks, 1, SUN8I_PWM_PPR_PERIOD_MAX);
+ candidate.period_ticks = period_ticks;
+ if (candidate.period_ticks > 1 &&
+ sun8i_pwm_ticks_to_ns(candidate.period_ticks, div_k, pair_rate) >
+ requested->period_length_ns)
+ candidate.period_ticks--;
+
+ duty_ticks = mul_u64_u64_div_u64(duty_ns, pair_rate, denominator);
+ duty_ticks = min_t(u64, duty_ticks, candidate.period_ticks);
+ if (duty_ticks &&
+ sun8i_pwm_ticks_to_ns(duty_ticks, div_k, pair_rate) >
+ requested->duty_length_ns)
+ duty_ticks--;
+
+ /*
+ * Zero active ticks encode either constant level. For a toggling
+ * waveform, active-low mode places the rising edge after the inactive
+ * part, which is the only non-zero offset supported by the hardware.
+ */
+ if (!duty_ticks) {
+ candidate.active_state = true;
+ candidate.duty_ticks = 0;
+ } else if (duty_ticks == candidate.period_ticks) {
+ candidate.active_state = false;
+ candidate.duty_ticks = 0;
+ } else {
+ inactive_ticks = candidate.period_ticks - duty_ticks;
+ if (sun8i_pwm_ticks_to_ns(inactive_ticks, div_k, pair_rate) <=
+ requested->duty_offset_ns) {
+ candidate.active_state = false;
+ candidate.duty_ticks = inactive_ticks;
+ } else {
+ candidate.active_state = true;
+ candidate.duty_ticks = duty_ticks;
+ }
+ }
+
+ sun8i_pwm_consider_candidate(rounding, &candidate);
+}
+
+static void
+sun8i_pwm_consider_pair_rate(struct sun8i_pwm_rounding *rounding,
+ u32 pair_rate)
+{
+ struct sun8i_pwm_waveform bypass = {
+ .duty_ticks = 1,
+ .period_ticks = 1,
+ .pair_rate = pair_rate,
+ .div_k = 1,
+ .enabled = true,
+ .active_state = true,
+ .bypass_en = true,
+ };
+
+ for (unsigned int div_k = 1; div_k <= 256; div_k++)
+ sun8i_pwm_consider_normal(rounding, pair_rate, div_k);
+
+ sun8i_pwm_consider_candidate(rounding, &bypass);
+}
+
+static int sun8i_pwm_round_waveform_tohw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_waveform *wf,
+ void *_wfhw)
+{
+ struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];
+ struct sun8i_pwm_pair *pair =
+ &sun8i_chip->pairs[SUN8I_PWM_PAIR_IDX(pwm->hwpwm)];
+ struct sun8i_pwm_waveform *wfhw = _wfhw;
+ struct sun8i_pwm_rounding rounding = {
+ .requested = wf,
+ };
+ unsigned long current_rate;
+
+ if (!wf->period_length_ns) {
+ *wfhw = (struct sun8i_pwm_waveform) {
+ .enabled = false,
+ };
+ return 0;
+ }
+
+ current_rate = clk_get_rate(chan->pair_clk);
+ if (!current_rate || current_rate > U32_MAX)
+ return -ERANGE;
+ rounding.current_pair_rate = current_rate;
+
+ if (sun8i_pwm_pair_rate_constrained(sun8i_chip, pwm->hwpwm)) {
+ sun8i_pwm_consider_pair_rate(&rounding, current_rate);
+ } else {
+ for (unsigned int parent_idx = 0;
+ parent_idx < clk_hw_get_num_parents(pair->hw);
+ parent_idx++) {
+ struct clk_hw *parent;
+ unsigned long parent_rate;
+
+ parent = clk_hw_get_parent_by_index(pair->hw, parent_idx);
+ if (!parent)
+ continue;
+ parent_rate = clk_hw_get_rate(parent);
+ if (!parent_rate)
+ continue;
+
+ for (unsigned int i = 0; sun8i_pwm_div_m_table[i].div;
+ i++) {
+ u16 div_m = sun8i_pwm_div_m_table[i].div;
+ u64 pair_rate;
+
+ pair_rate = DIV_ROUND_UP_ULL(parent_rate, div_m);
+ if (!pair_rate || pair_rate > U32_MAX)
+ continue;
+ sun8i_pwm_consider_pair_rate(&rounding, pair_rate);
+ }
+ }
+ }
+
+ if (!rounding.have_best)
+ return -EINVAL;
+ *wfhw = rounding.best;
+
+ dev_dbg(pwmchip_parent(chip),
+ "pwm#%u: %llu/%llu [+%llu] -> pair-rate=%u, div-k=%u, period=%u, duty=%u, bypass=%u, active-high=%u\n",
+ pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns,
+ wf->duty_offset_ns, wfhw->pair_rate, wfhw->div_k,
+ wfhw->period_ticks, wfhw->duty_ticks, wfhw->bypass_en,
+ wfhw->active_state);
+
+ return rounding.best_wf.period_length_ns > wf->period_length_ns;
+}
+
+static const struct pwm_ops sun8i_pwm_ops = {
+ .request = sun8i_pwm_request,
+ .free = sun8i_pwm_free,
+ .sizeof_wfhw = sizeof(struct sun8i_pwm_waveform),
+ .round_waveform_tohw = sun8i_pwm_round_waveform_tohw,
+ .round_waveform_fromhw = sun8i_pwm_round_waveform_fromhw,
+ .read_waveform = sun8i_pwm_read_waveform,
+ .write_waveform = sun8i_pwm_write_waveform,
+};
+
+/* Register the shared mux, gate and /div_m clock for each channel pair. */
+static int sun8i_pwm_register_pair_clocks(struct device *dev,
+ struct sun8i_pwm_chip *sun8i_chip)
+{
+ static const struct clk_parent_data parent_data[] = {
+ { .index = 0 },
+ { .index = 1 },
+ };
+ unsigned int num_pairs = DIV_ROUND_UP(sun8i_chip->data->npwm, 2);
+
+ for (unsigned int i = 0; i < num_pairs; i++) {
+ struct sun8i_pwm_pair *pair = &sun8i_chip->pairs[i];
+ void __iomem *reg = sun8i_chip->base + SUN8I_PWM_PCCR(i);
+ struct clk *pair_clk;
+ const char *name;
+ int ret;
+
+ name = devm_kasprintf(dev, GFP_KERNEL,
+ "%s#pwm-clk-src%u%u", dev_name(dev),
+ i * 2, i * 2 + 1);
+ if (!name)
+ return -ENOMEM;
+
+ pair->mux.reg = reg;
+ pair->mux.shift = SUN8I_PWM_PCCR_SRC_SHIFT;
+ pair->mux.mask = SUN8I_PWM_PCCR_SRC_MASK;
+ pair->mux.flags = CLK_MUX_ROUND_CLOSEST;
+ pair->mux.lock = &sun8i_chip->lock;
+
+ pair->chip = sun8i_chip;
+ pair->index = i;
+
+ pair->divider.reg = reg;
+ pair->divider.shift = SUN8I_PWM_PCCR_DIV_M_SHIFT;
+ pair->divider.width = SUN8I_PWM_PCCR_DIV_M_WIDTH;
+ pair->divider.table = sun8i_pwm_div_m_table;
+ pair->divider.lock = &sun8i_chip->lock;
+
+ pair->hw = devm_clk_hw_register_composite_pdata(dev, name,
+ parent_data,
+ ARRAY_SIZE(parent_data),
+ &pair->mux.hw, &clk_mux_ops,
+ &pair->divider.hw, &clk_divider_ops,
+ &pair->gate_hw, &sun8i_pwm_pair_gate_ops, 0);
+ if (IS_ERR(pair->hw))
+ return dev_err_probe(dev, PTR_ERR(pair->hw),
+ "Failed to register pair %u clock\n", i);
+
+ pair_clk = devm_clk_hw_get_clk(dev, pair->hw, NULL);
+ if (IS_ERR(pair_clk))
+ return dev_err_probe(dev, PTR_ERR(pair_clk),
+ "Failed to get pair %u clock\n", i);
+
+ pair->rate_nb.notifier_call = sun8i_pwm_pair_rate_notifier;
+ ret = devm_clk_notifier_register(dev, pair_clk, &pair->rate_nb);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to protect pair %u clock rate\n", i);
+ }
+
+ return 0;
+}
+
+/* Register a pair-clock consumer and the bypass clock for each channel. */
+static int sun8i_pwm_register_channel_clocks(struct device *dev,
+ struct sun8i_pwm_chip *sun8i_chip)
+{
+ for (unsigned int i = 0; i < sun8i_chip->data->npwm; i++) {
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[i];
+ struct clk_hw *parent = sun8i_chip->pairs[SUN8I_PWM_PAIR_IDX(i)].hw;
+ struct clk_parent_data parent_data = { .hw = parent };
+ struct clk_init_data init = {};
+ const char *name;
+ int ret;
+
+ /* Separate handles let CCF arbitrate sibling rate exclusivity. */
+ chan->pair_clk = devm_clk_hw_get_clk(dev, parent, NULL);
+ if (IS_ERR(chan->pair_clk))
+ return dev_err_probe(dev, PTR_ERR(chan->pair_clk),
+ "Failed to get pair clock for PWM %u\n", i);
+
+ name = devm_kasprintf(dev, GFP_KERNEL, "%s#pwm-bypass%u",
+ dev_name(dev), i);
+ if (!name)
+ return -ENOMEM;
+
+ chan->bypass.chip = sun8i_chip;
+ chan->bypass.index = i;
+ init.name = name;
+ init.ops = &sun8i_pwm_bypass_ops;
+ init.parent_data = &parent_data;
+ init.num_parents = 1;
+ /* Keep the shared pair rate stable for the prepared lifetime. */
+ init.flags = CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE;
+ chan->bypass.hw.init = &init;
+
+ ret = devm_clk_hw_register(dev, &chan->bypass.hw);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register bypass clock %u\n", i);
+
+ sun8i_chip->hw_data->hws[i] = &chan->bypass.hw;
+ }
+
+ return 0;
+}
+
+/*
+ * A disabled pair gate makes any set PER bits ineffective. Clear those stale
+ * enables before a clock consumer can turn the shared gate on and expose an
+ * unrequested output.
+ */
+static void
+sun8i_pwm_sanitize_disabled_pairs(struct sun8i_pwm_chip *sun8i_chip)
+{
+ unsigned int num_pairs = DIV_ROUND_UP(sun8i_chip->data->npwm, 2);
+ unsigned long flags;
+ u32 per;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);
+ for (unsigned int pair = 0; pair < num_pairs; pair++) {
+ unsigned int first = pair * 2;
+ u32 pccr;
+
+ pccr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair));
+ if (pccr & SUN8I_PWM_PCCR_GATE)
+ continue;
+
+ per &= ~SUN8I_PWM_ENABLE(first);
+ pccr &= ~BIT(SUN8I_PWM_PCCR_BYPASS_BIT(first));
+ if (first + 1 < sun8i_chip->data->npwm) {
+ per &= ~SUN8I_PWM_ENABLE(first + 1);
+ pccr &= ~BIT(SUN8I_PWM_PCCR_BYPASS_BIT(first + 1));
+ }
+ sun8i_pwm_writel(sun8i_chip, pccr, SUN8I_PWM_PCCR(pair));
+ }
+ sun8i_pwm_writel(sun8i_chip, per, SUN8I_PWM_PER);
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+}
+
+/*
+ * Keep firmware-active outputs running until all declared consumers have had
+ * a chance to claim them. Once the driver core calls sync_state(), any channel
+ * which still has no PWM or clock owner can be stopped.
+ */
+static void sun8i_pwm_sync_state(struct device *dev)
+{
+ struct sun8i_pwm_chip *sun8i_chip = dev_get_drvdata(dev);
+ unsigned int num_pairs = DIV_ROUND_UP(sun8i_chip->data->npwm, 2);
+ unsigned long empty_pairs = 0;
+ unsigned long flags;
+ u32 active, per, unclaimed = 0;
+
+ spin_lock_irqsave(&sun8i_chip->lock, flags);
+ per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);
+ for (unsigned int pair = 0; pair < num_pairs; pair++) {
+ u32 pair_mask = sun8i_pwm_pair_enable_mask(sun8i_chip, pair);
+ u32 owner_mask;
+ u32 pdzcr;
+
+ owner_mask = sun8i_pwm_pair_owner_mask_locked(sun8i_chip, pair);
+ pdzcr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PDZCR(pair));
+ /* Dead-zone mode couples both outputs into one waveform. */
+ if ((pdzcr & SUN8I_PWM_PDZCR_ENABLE) &&
+ (per & owner_mask))
+ continue;
+
+ unclaimed |= pair_mask & ~owner_mask;
+ }
+
+ active = per & unclaimed;
+ per &= ~unclaimed;
+ sun8i_pwm_writel(sun8i_chip, per, SUN8I_PWM_PER);
+
+ for (unsigned int pair = 0; pair < num_pairs; pair++) {
+ unsigned int first = pair * 2;
+ u32 pccr;
+
+ pccr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair));
+ if (unclaimed & SUN8I_PWM_ENABLE(first))
+ pccr &= ~BIT(SUN8I_PWM_PCCR_BYPASS_BIT(first));
+ if (first + 1 < sun8i_chip->data->npwm &&
+ unclaimed & SUN8I_PWM_ENABLE(first + 1))
+ pccr &= ~BIT(SUN8I_PWM_PCCR_BYPASS_BIT(first + 1));
+ sun8i_pwm_writel(sun8i_chip, pccr, SUN8I_PWM_PCCR(pair));
+
+ if (!(per & sun8i_pwm_pair_enable_mask(sun8i_chip, pair)))
+ empty_pairs |= BIT(pair);
+ }
+ spin_unlock_irqrestore(&sun8i_chip->lock, flags);
+
+ /*
+ * Reconcile empty pair gates through CCF. Taking and dropping a
+ * temporary reference leaves a gate with another CCF user enabled, but
+ * disables a firmware-enabled gate whose software count is still zero.
+ */
+ for (unsigned int pair = 0; pair < num_pairs; pair++) {
+ struct clk *pair_clk;
+ int ret;
+
+ if (!(empty_pairs & BIT(pair)))
+ continue;
+
+ pair_clk = sun8i_chip->channels[pair * 2].pair_clk;
+ ret = clk_prepare_enable(pair_clk);
+ if (ret) {
+ dev_warn(dev, "Failed to synchronize pair %u gate: %pe\n",
+ pair, ERR_PTR(ret));
+ continue;
+ }
+ clk_disable_unprepare(pair_clk);
+ }
+
+ if (active)
+ dev_dbg(dev, "disabled unclaimed firmware outputs %#x\n", active);
+}
+
+static int sun8i_pwm_init_clocks(struct device *dev,
+ struct sun8i_pwm_chip *sun8i_chip)
+{
+ unsigned int num_pairs = DIV_ROUND_UP(sun8i_chip->data->npwm, 2);
+ size_t hw_data_size;
+ int ret;
+
+ sun8i_chip->pairs = devm_kcalloc(dev, num_pairs,
+ sizeof(*sun8i_chip->pairs), GFP_KERNEL);
+ if (!sun8i_chip->pairs)
+ return -ENOMEM;
+
+ hw_data_size = struct_size(sun8i_chip->hw_data, hws,
+ sun8i_chip->data->npwm);
+ sun8i_chip->hw_data = devm_kzalloc(dev, hw_data_size, GFP_KERNEL);
+ if (!sun8i_chip->hw_data)
+ return -ENOMEM;
+ sun8i_chip->hw_data->num = sun8i_chip->data->npwm;
+ sun8i_pwm_sanitize_disabled_pairs(sun8i_chip);
+
+ ret = sun8i_pwm_register_pair_clocks(dev, sun8i_chip);
+ if (ret)
+ return ret;
+
+ return sun8i_pwm_register_channel_clocks(dev, sun8i_chip);
+}
+
+static int sun8i_pwm_probe(struct platform_device *pdev)
+{
+ const struct sun8i_pwm_data *data;
+ struct device *dev = &pdev->dev;
+ struct sun8i_pwm_chip *sun8i_chip;
+ struct reset_control *rst;
+ struct pwm_chip *chip;
+ int ret;
+
+ data = of_device_get_match_data(dev);
+ if (!data)
+ return dev_err_probe(dev, -ENODEV,
+ "Missing specific data structure\n");
+
+ chip = devm_pwmchip_alloc(dev, data->npwm, sizeof(*sun8i_chip));
+ if (IS_ERR(chip))
+ return dev_err_probe(dev, PTR_ERR(chip),
+ "Failed to allocate pwmchip\n");
+
+ sun8i_chip = sun8i_pwm_from_chip(chip);
+ sun8i_chip->data = data;
+ spin_lock_init(&sun8i_chip->lock);
+ sun8i_chip->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(sun8i_chip->base))
+ return dev_err_probe(dev, PTR_ERR(sun8i_chip->base),
+ "Failed to get PWM base address\n");
+
+ sun8i_chip->bus_clk = devm_clk_get_enabled(dev, "bus");
+ if (IS_ERR(sun8i_chip->bus_clk))
+ return dev_err_probe(dev, PTR_ERR(sun8i_chip->bus_clk),
+ "Failed to get bus clock\n");
+
+ rst = devm_reset_control_get_shared_deasserted(dev, NULL);
+ if (IS_ERR(rst))
+ return dev_err_probe(dev, PTR_ERR(rst),
+ "Failed to get reset control\n");
+
+ sun8i_chip->channels = devm_kcalloc(dev, data->npwm,
+ sizeof(*sun8i_chip->channels),
+ GFP_KERNEL);
+ if (!sun8i_chip->channels)
+ return dev_err_probe(dev, -ENOMEM,
+ "Failed to allocate %d channels array\n",
+ data->npwm);
+
+ chip->ops = &sun8i_pwm_ops;
+
+ ret = sun8i_pwm_init_clocks(dev, sun8i_chip);
+ if (ret)
+ return ret;
+
+ ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
+ sun8i_chip->hw_data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add HW clock provider\n");
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
+
+ platform_set_drvdata(pdev, sun8i_chip);
+
+ return 0;
+}
+
+static const struct sun8i_pwm_data sun50i_h616_pwm_data = {
+ .npwm = 6,
+};
+
+static const struct of_device_id sun8i_pwm_dt_ids[] = {
+ {
+ .compatible = "allwinner,sun50i-h616-pwm",
+ .data = &sun50i_h616_pwm_data,
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, sun8i_pwm_dt_ids);
+
+static struct platform_driver sun8i_pwm_driver = {
+ .driver = {
+ .name = "sun8i-pwm",
+ .of_match_table = sun8i_pwm_dt_ids,
+ .sync_state = sun8i_pwm_sync_state,
+ },
+ .probe = sun8i_pwm_probe,
+};
+module_platform_driver(sun8i_pwm_driver);
+
+MODULE_AUTHOR("Richard Genoud <richard.genoud@bootlin.com>");
+MODULE_DESCRIPTION("Allwinner sun8i PWM driver");
+MODULE_LICENSE("GPL");