@@ -754,7 +754,8 @@ config PWM_SUN8I
depends on HAS_IOMEM && COMMON_CLK
help
PWM framework driver for Allwinner controllers whose channels share
- paired source clocks.
+ 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.
@@ -113,6 +113,9 @@
* 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. */
@@ -129,6 +132,12 @@ static const struct clk_div_table sun8i_pwm_div_m_table[] = {
{ /* sentinel */ }
};
+enum sun8i_pwm_mode {
+ SUN8I_PWM_MODE_NONE,
+ SUN8I_PWM_MODE_PWM,
+ SUN8I_PWM_MODE_CLK,
+};
+
struct sun8i_pwm_chip;
struct sun8i_pwm_pair {
@@ -142,8 +151,12 @@ struct sun8i_pwm_pair {
};
struct sun8i_pwm_channel {
+ struct clk_hw bypass_hw;
+ struct sun8i_pwm_chip *chip;
+ unsigned int index;
/* Separate CCF consumer, held only from PWM request to free. */
struct clk *pair_clk;
+ enum sun8i_pwm_mode mode;
u64 pending_period_ns;
bool rate_exclusive;
};
@@ -153,7 +166,7 @@ struct sun8i_pwm_chip {
struct sun8i_pwm_channel channels[SUN8I_PWM_NPWM];
struct clk *bus_clk;
void __iomem *base;
- /* Protects shared registers and rate_exclusive. */
+ /* Protects shared registers, channel ownership and rate_exclusive. */
spinlock_t lock;
};
@@ -317,6 +330,101 @@ static int sun8i_pwm_pair_rate_notifier(struct notifier_block *nb,
return active ? NOTIFY_BAD : NOTIFY_OK;
}
+static inline struct sun8i_pwm_channel *
+sun8i_pwm_channel_from_hw(struct clk_hw *hw)
+{
+ return container_of(hw, struct sun8i_pwm_channel, bypass_hw);
+}
+
+static int sun8i_pwm_bypass_prepare(struct clk_hw *hw)
+{
+ struct sun8i_pwm_channel *chan = sun8i_pwm_channel_from_hw(hw);
+ struct sun8i_pwm_chip *sun8i_chip = chan->chip;
+
+ guard(spinlock_irqsave)(&sun8i_chip->lock);
+ if (chan->mode != SUN8I_PWM_MODE_NONE)
+ return -EBUSY;
+
+ chan->mode = SUN8I_PWM_MODE_CLK;
+ return 0;
+}
+
+static void sun8i_pwm_bypass_unprepare(struct clk_hw *hw)
+{
+ struct sun8i_pwm_channel *chan = sun8i_pwm_channel_from_hw(hw);
+ struct sun8i_pwm_chip *sun8i_chip = chan->chip;
+
+ guard(spinlock_irqsave)(&sun8i_chip->lock);
+ if (chan->mode != SUN8I_PWM_MODE_CLK)
+ return;
+
+ chan->mode = SUN8I_PWM_MODE_NONE;
+}
+
+static int sun8i_pwm_bypass_enable(struct clk_hw *hw)
+{
+ struct sun8i_pwm_channel *chan = sun8i_pwm_channel_from_hw(hw);
+ struct sun8i_pwm_chip *sun8i_chip = chan->chip;
+ unsigned int pair = SUN8I_PWM_PAIR_IDX(chan->index);
+ u32 pccr, per;
+
+ guard(spinlock_irqsave)(&sun8i_chip->lock);
+ if (chan->mode != SUN8I_PWM_MODE_CLK)
+ return -EBUSY;
+
+ 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(chan->index))) ||
+ !(per & SUN8I_PWM_ENABLE(chan->index))) {
+ sun8i_pwm_set_enabled_locked(sun8i_chip, chan->index, false);
+ sun8i_pwm_set_bypass_locked(sun8i_chip, chan->index, true);
+ sun8i_pwm_set_enabled_locked(sun8i_chip, chan->index, true);
+ }
+
+ return 0;
+}
+
+static void sun8i_pwm_bypass_disable(struct clk_hw *hw)
+{
+ struct sun8i_pwm_channel *chan = sun8i_pwm_channel_from_hw(hw);
+ struct sun8i_pwm_chip *sun8i_chip = chan->chip;
+
+ guard(spinlock_irqsave)(&sun8i_chip->lock);
+ if (chan->mode != SUN8I_PWM_MODE_CLK)
+ return;
+
+ sun8i_pwm_set_enabled_locked(sun8i_chip, chan->index, false);
+ sun8i_pwm_set_bypass_locked(sun8i_chip, chan->index, false);
+}
+
+static int sun8i_pwm_bypass_is_enabled(struct clk_hw *hw)
+{
+ struct sun8i_pwm_channel *chan = sun8i_pwm_channel_from_hw(hw);
+ struct sun8i_pwm_chip *sun8i_chip = chan->chip;
+ unsigned int pair = SUN8I_PWM_PAIR_IDX(chan->index);
+ bool enabled;
+ u32 val;
+
+ guard(spinlock_irqsave)(&sun8i_chip->lock);
+
+ val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair));
+ enabled = (val & SUN8I_PWM_PCCR_GATE) &&
+ (val & BIT(SUN8I_PWM_PCCR_BYPASS_BIT(chan->index)));
+
+ val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);
+ enabled = enabled && (val & SUN8I_PWM_ENABLE(chan->index));
+
+ return enabled;
+}
+
+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,
+ .is_enabled = sun8i_pwm_bypass_is_enabled,
+};
+
static void sun8i_pwm_put_rate(struct sun8i_pwm_chip *sun8i_chip,
unsigned int idx)
{
@@ -342,8 +450,12 @@ static int sun8i_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
int ret;
scoped_guard(spinlock_irqsave, &sun8i_chip->lock) {
+ if (chan->mode != SUN8I_PWM_MODE_NONE)
+ return -EBUSY;
+
was_enabled =
sun8i_pwm_channel_is_enabled_locked(sun8i_chip, idx);
+ chan->mode = SUN8I_PWM_MODE_PWM;
}
chan->pair_clk = clk_hw_get_clk(parent, NULL);
@@ -372,6 +484,8 @@ static int sun8i_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
clk_put(chan->pair_clk);
err_clear_clock:
chan->pair_clk = NULL;
+ scoped_guard(spinlock_irqsave, &sun8i_chip->lock)
+ chan->mode = SUN8I_PWM_MODE_NONE;
return ret;
}
@@ -382,6 +496,9 @@ static void sun8i_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];
scoped_guard(spinlock_irqsave, &sun8i_chip->lock) {
+ if (chan->mode != SUN8I_PWM_MODE_PWM)
+ return;
+
sun8i_pwm_set_enabled_locked(sun8i_chip, pwm->hwpwm, false);
sun8i_pwm_set_bypass_locked(sun8i_chip, pwm->hwpwm, false);
}
@@ -390,6 +507,9 @@ static void sun8i_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
clk_disable_unprepare(chan->pair_clk);
clk_put(chan->pair_clk);
chan->pair_clk = NULL;
+
+ scoped_guard(spinlock_irqsave, &sun8i_chip->lock)
+ chan->mode = SUN8I_PWM_MODE_NONE;
}
static int sun8i_pwm_read_waveform(struct pwm_chip *chip,
@@ -510,7 +630,8 @@ sun8i_pwm_pair_rate_constrained(struct sun8i_pwm_chip *sun8i_chip,
unsigned int sibling = idx ^ 1;
guard(spinlock_irqsave)(&sun8i_chip->lock);
- return sun8i_chip->channels[sibling].rate_exclusive ||
+ return sun8i_chip->channels[sibling].mode == SUN8I_PWM_MODE_CLK ||
+ sun8i_chip->channels[sibling].rate_exclusive ||
sun8i_pwm_channel_is_enabled_locked(sun8i_chip, sibling);
}
@@ -984,6 +1105,42 @@ static int sun8i_pwm_register_pair_clocks(struct device *dev,
return 0;
}
+/* Register the bypass clock for each channel. */
+static int sun8i_pwm_register_bypass_clocks(struct device *dev,
+ struct sun8i_pwm_chip *sun8i_chip)
+{
+ for (unsigned int i = 0; i < SUN8I_PWM_NPWM; i++) {
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[i];
+ struct clk_hw *parent = sun8i_chip->pairs[SUN8I_PWM_PAIR_IDX(i)].hw;
+ const char *name;
+ int ret;
+
+ name = devm_kasprintf(dev, GFP_KERNEL, "%s#pwm-bypass%u",
+ dev_name(dev), i);
+ if (!name)
+ return -ENOMEM;
+
+ chan->chip = sun8i_chip;
+ chan->index = i;
+ /*
+ * Protect the shared rate while prepared. Preserve unclaimed
+ * firmware outputs through CCF's unused-clock cleanup, as for
+ * PWM waveforms; only a consumer may turn them off.
+ */
+ chan->bypass_hw.init =
+ CLK_HW_INIT_HW(name, parent, &sun8i_pwm_bypass_ops,
+ CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE |
+ CLK_IGNORE_UNUSED);
+
+ 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);
+ }
+
+ 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
@@ -1012,6 +1169,17 @@ sun8i_pwm_sanitize_disabled_pairs(struct sun8i_pwm_chip *sun8i_chip)
sun8i_pwm_writel(sun8i_chip, per, SUN8I_PWM_PER);
}
+static struct clk_hw *sun8i_pwm_get_clk_hw(struct of_phandle_args *clkspec,
+ void *data)
+{
+ struct sun8i_pwm_chip *sun8i_chip = data;
+
+ if (clkspec->args[0] >= SUN8I_PWM_NPWM)
+ return ERR_PTR(-EINVAL);
+
+ return &sun8i_chip->channels[clkspec->args[0]].bypass_hw;
+}
+
static int sun8i_pwm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -1050,6 +1218,14 @@ static int sun8i_pwm_probe(struct platform_device *pdev)
if (ret)
return ret;
+ ret = sun8i_pwm_register_bypass_clocks(dev, sun8i_chip);
+ if (ret)
+ return ret;
+
+ ret = devm_of_clk_add_hw_provider(dev, sun8i_pwm_get_clk_hw, sun8i_chip);
+ 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");