On Sat, May 18, 2024 at 01:54:45PM +1000, John Watts wrote: > This driver documents and handles setting up PWM on the D1. > > Signed-off-by: John Watts <cont...@jookia.org> > --- > drivers/pwm/Kconfig | 6 + > drivers/pwm/Makefile | 1 + > drivers/pwm/sunxi_pwm_d1.c | 542 > +++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 549 insertions(+)
Patch v9 of the kernel uses apb now instead of apb0, so this should be changed to fit once the kernel driver is merged. > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig > index 6e79868d0e..8c4c910ea7 100644 > --- a/drivers/pwm/Kconfig > +++ b/drivers/pwm/Kconfig > @@ -112,6 +112,12 @@ config PWM_SUNXI > This PWM is found on H3, A64 and other Allwinner SoCs. It supports a > programmable period and duty cycle. A 16-bit counter is used. > > +config PWM_SUNXI_D1 > + bool "Enable support for the Allwinner D1 Sunxi PWM" > + depends on DM_PWM > + help > + This PWM is found on D1, T113-S3 and R329 SoCs. > + > config PWM_TI_EHRPWM > bool "Enable support for EHRPWM PWM" > depends on DM_PWM && ARCH_OMAP2PLUS > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile > index e4d10c8dc3..ea96e7159b 100644 > --- a/drivers/pwm/Makefile > +++ b/drivers/pwm/Makefile > @@ -23,4 +23,5 @@ obj-$(CONFIG_PWM_SANDBOX) += sandbox_pwm.o > obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o > obj-$(CONFIG_PWM_TEGRA) += tegra_pwm.o > obj-$(CONFIG_PWM_SUNXI) += sunxi_pwm.o > +obj-$(CONFIG_PWM_SUNXI_D1) += sunxi_pwm_d1.o > obj-$(CONFIG_PWM_TI_EHRPWM) += pwm-ti-ehrpwm.o > diff --git a/drivers/pwm/sunxi_pwm_d1.c b/drivers/pwm/sunxi_pwm_d1.c > new file mode 100644 > index 0000000000..6c57bc6e85 > --- /dev/null > +++ b/drivers/pwm/sunxi_pwm_d1.c > @@ -0,0 +1,542 @@ > +// SPDX-License-Identifier: GPL-2.0 > +// Copyright 2022 Jookia <cont...@jookia.org> > +/* > + * The Allwinner D1's PWM channels are 16-bit counters with up to > + * 65537 cycles (yes, you read that correctly). > + * > + * Each channel must be programmed using three variables: > + * - The entire cycle count (used for the period) > + * - The active cycle count (the count of inactive cycles) > + * - The polarity (specifies if the signal is active high or low) > + * The cycle counts are at minimum 1 and at maximum 65536. > + * > + * The controller will output the number of entire cycles plus one > + * extra, with any cycles after the active cycle output as active. > + * > + * Consider a PWM period of 128 nanoseconds and a cycle period of 32. > + * Setting the entire cycle count to 3 and active cycle count to 4 > + * gives an output like so: > + * > + * - Cycle 1 runs 0 to 32 nanoseconds, inactive > + * - Cycle 2 runs 32 to 64 nanoseconds, inactive > + * - Cycle 3 runs 64 to 96 nanoseconds, inactive > + * - Cycle 4 runs 96 to 128 nanoseconds, inactive > + * - Cycle 5 is skipped but would run 128 to 160 nanoseconds, active > + * > + * If we set the entire count to 4, cycle 5 would run and we wouldn't be > + * able to specify it as inactive as the active count only goes up to 4. > + * > + * In practice this means we want to set the entire cycle to be one less > + * then the actual number of cycles we want, so we can set the number of > + * active cycles to be up to maximum for a fully inactive signal. > + * > + * The PWM channels are paired and clocked together, resulting in a > + * cycle time found using the following formula: > + * > + * PWM0_CYCLE_NS = 1000000000 / (BUS_CLOCK / COMMON_DIV / PWM0_PRESCALER_K) > + * PWM1_CYCLE_NS = 1000000000 / (BUS_CLOCK / COMMON_DIV / PWM1_PRESCALER_K) > + * > + * This means both clocks should ideally be set at the same time and not > + * impact each other too much. > + */ > + > +#include <dm.h> > +#include <dm/device_compat.h> > +#include <dm/devres.h> > +#include <clk.h> > +#include <reset.h> > +#include <pwm.h> > +#include <asm/io.h> > + > +/* PWM channel information */ > +struct pwm_channel { > + uint period_ns; > + uint duty_ns; > + bool polarity; > + bool enable; > + bool updated; > +}; > + > +/* Timings found for a PWM channel */ > +struct pwm_timings { > + uint cycle_ns; > + uint period_ns; > + uint duty_ns; > + uint clock_id; > + uint common_div; > + uint prescale_k; > + uint entire_cycles; > + uint active_cycles; > + uint polarity; > +}; > + > +/* Driver state */ > +struct sunxi_pwm_d1_priv { > + void *base; > + struct clk *clk_bus; > + struct clk *clk_srcs[3]; /* Last value must be NULL */ > + struct reset_ctl *reset; > + int npwm; > + struct pwm_channel *channels; > +}; > + > +/* Divides a nanosecond value, rounding up for very low values */ > +uint div_ns(uint ns, uint div) > +{ > + uint result = (ns / div); > + > + /* If the number is less than 1000, round it to the nearest digit */ > + if (result < 1000) > + result = (ns + (div - 1)) / div; > + > + if (result < 1) > + result = 1; > + > + return result; > +} > + > +/* Checks if an error is relatively too large */ > +int error_too_large(uint actual, uint target) > +{ > + /* For a target of zero we want zero */ > + if (target == 0) > + return (actual == 0); > + > + /* Don't overflow large numbers when we multiply by 100 */ > + while (actual > 1000) { > + actual /= 100; > + target /= 100; > + } > + > + int error_percent = (actual * 100) / target; > + > + return (error_percent < 80 || 120 < error_percent); > +} > + > +/* Calculates the cycle nanoseconds from clock parameters */ > +int get_cycle_ns(uint parent_hz, uint common_div, uint prescaler) > +{ > + return 1000000000 / ((parent_hz / common_div) / prescaler); > +} > + > +int find_channel_dividers(uint period_ns, > + uint parent_hz, > + struct pwm_timings *out) > +{ > + uint ideal_cycle_ns = div_ns(period_ns, 65536); > + int common_div = out->common_div; > + int prescaler = 1; > + uint cycle_ns = 0; > + > + for (;;) { > + cycle_ns = get_cycle_ns(parent_hz, common_div, prescaler); > + if (cycle_ns >= ideal_cycle_ns) > + break; > + > + prescaler *= 2; > + if (prescaler > 256) { > + if (common_div < 256) { > + prescaler = 1; > + common_div *= 2; > + } else { > + return -1; > + } > + } > + } > + > + out->common_div = common_div; > + out->prescale_k = prescaler; > + out->cycle_ns = cycle_ns; > + > + return 0; > +} > + > +int find_channel_timings(const struct pwm_channel *in, > + struct pwm_timings *out, > + uint parent_hz) > +{ > + struct pwm_timings new = *out; > + > + if (find_channel_dividers(in->period_ns, parent_hz, &new)) > + return -1; > + > + new.entire_cycles = (in->period_ns / new.cycle_ns) - 1; > + new.active_cycles = (in->duty_ns / new.cycle_ns); > + new.period_ns = (new.entire_cycles + 1) * new.cycle_ns; > + new.duty_ns = new.active_cycles * new.cycle_ns; > + new.polarity = in->polarity; > + > + if (error_too_large(new.period_ns, in->period_ns)) > + return -1; > + > + if (in->duty_ns && error_too_large(new.duty_ns, in->duty_ns)) > + return -1; > + > + *out = new; > + > + return 0; > +} > + > +int find_pair_timings(const struct pwm_channel *channel0, > + const struct pwm_channel *channel1, > + struct pwm_timings *timings0, > + struct pwm_timings *timings1, > + int clock_hz) > +{ > + struct pwm_timings new0 = *timings0; > + struct pwm_timings new1 = *timings1; > + int err0 = 0; > + int err1 = 0; > + > + new0.common_div = 1; > + new1.common_div = 1; > + > + if (channel0->enable) { > + err0 = find_channel_timings(channel0, &new0, clock_hz); > + new1.common_div = new0.common_div; > + } > + > + if (channel1->enable) { > + err1 = find_channel_timings(channel1, &new1, clock_hz); > + new0 = *timings0; > + new0.common_div = new1.common_div; > + } > + > + if (channel0->enable && channel1->enable) { > + err0 = find_channel_timings(channel0, &new0, clock_hz); > + > + if (new0.common_div != new1.common_div) > + return -1; > + } > + > + if (err0 || err1) > + return -1; > + > + *timings0 = new0; > + *timings1 = new1; > + > + return 0; > +} > + > +int find_pair_timings_clocked(struct clk **clk_srcs, > + const struct pwm_channel *channel0, > + const struct pwm_channel *channel1, > + struct pwm_timings *timings0, > + struct pwm_timings *timings1) > +{ > + struct clk *clock = *clk_srcs; > + > + for (int clock_id = 0; clock; clock = clk_srcs[++clock_id]) { > + int clock_hz = clk_get_rate(clock); > + > + if (clock_hz == 0 || IS_ERR_VALUE(clock_hz)) > + continue; > + > + timings0->clock_id = clock_id; > + timings1->clock_id = clock_id; > + > + if (find_pair_timings(channel0, channel1, > + timings0, timings1, > + clock_hz)) > + continue; > + > + return 0; > + } > + > + return -1; > +} > + > +#define PCGR(base) ((base) + 0x40) > +#define PCGR_CLK_GATE(channel) BIT(channel) > + > +#define PER(base) ((base) + 0x80) > +#define PER_ENABLE_PWM(channel) BIT(channel) > + > +#define PCCR(base, pair) ((base) + 0x20 + ((pair) * 2)) > +#define PCCR_CLK_SRC(src) ((src) << 7) > +#define PCCR_CLK_SRC_MASK GENMASK(8, 7) > +#define PCCR_CLK_DIV_M(m) (m) > +#define PCCR_CLK_DIV_M_MASK GENMASK(3, 0) > + > +#define PCR(base, channel) ((base) + 0x100 + ((channel) * 0x20)) > +#define PCR_PRESCAL_K(k) (k) > +#define PCR_PRESCAL_K_MASK GENMASK(7, 0) > +#define PCR_PWM_ACTIVE BIT(8) > + > +#define PPR(base, channel) ((base) + 0x104 + ((channel) * 0x20)) > +#define PPR_ENTIRE_CYCLE(n) ((n) << 16) > +#define PPR_ENTIRE_CYCLE_MASK GENMASK(31, 16) > +#define PPR_ACT_CYCLE(n) (n) > +#define PPR_ACT_CYCLE_MASK GENMASK(15, 0) > + > +/* Like clrsetbits_le32 but with memory barriers */ > +void clrsetreg(void *addr, u32 clear, u32 set) > +{ > + u32 val = readl(addr); > + > + val &= ~clear; > + val |= set; > + > + writel(val, addr); > +} > + > +void disable_pair(void *base, int pair) > +{ > + u32 PER_clear = (PER_ENABLE_PWM(pair) | PER_ENABLE_PWM(pair + 1)); > + u32 PCGR_clear = (PCGR_CLK_GATE(pair) | PCGR_CLK_GATE(pair + 1)); > + > + clrsetreg(PER(base), PER_clear, 0); > + clrsetreg(PCGR(base), PCGR_clear, 0); > + > + log_debug("%s: pair %i, PCGR 0x%x, PER 0x%x\n", > + __func__, pair, readl(PCGR(base)), readl(PER(base))); > +} > + > +void enable_pair(void *base, int pair, int clk_src, int clk_div) > +{ > + int div_m = fls(clk_div) - 1; > + > + u32 PCGR_set = (PCGR_CLK_GATE(pair) | PCGR_CLK_GATE(pair + 1)); > + u32 PCCR_clear = (PCCR_CLK_SRC_MASK | PCCR_CLK_DIV_M_MASK); > + u32 PCCR_set = (PCCR_CLK_SRC(clk_src) | PCCR_CLK_DIV_M(div_m)); > + > + clrsetreg(PCGR(base), 0, PCGR_set); > + clrsetreg(PCCR(base, pair), PCCR_clear, PCCR_set); > + > + log_debug("%s: pair %i, clk_src %i, div_m %i, PCCR 0x%x\n", > + __func__, pair, clk_src, div_m, readl(PCCR(base, pair))); > +} > + > +void enable_channel(void *base, int channel, struct pwm_timings *timings) > +{ > + u32 pwm_active = (timings->polarity) ? 0 : PCR_PWM_ACTIVE; > + u32 prescale = (timings->prescale_k - 1); > + u32 entire_cycles = timings->entire_cycles; > + u32 active_cycles = timings->active_cycles; > + > + u32 PCR_clear = (PCR_PRESCAL_K_MASK | PCR_PWM_ACTIVE); > + u32 PCR_set = (PCR_PRESCAL_K(prescale) | pwm_active); > + u32 PPR_clear = (PPR_ENTIRE_CYCLE_MASK | PPR_ACT_CYCLE_MASK); > + u32 PPR_set = (PPR_ENTIRE_CYCLE(entire_cycles) | > PPR_ACT_CYCLE(active_cycles)); > + u32 PER_set = PER_ENABLE_PWM(channel); > + > + clrsetreg(PCR(base, channel), PCR_clear, PCR_set); > + clrsetreg(PPR(base, channel), PPR_clear, PPR_set); > + clrsetreg(PER(base), 0, PER_set); > + > + log_debug("%s: channel %u, clock_id %u, period_ns %u, duty_ns %u, > common_div %u, prescale_k %u, entire_cycles %u, active_cycles %u, polarity > %u, PCGR 0x%x, PCR 0x%x, PPR 0x%x, PER 0x%x\n", > + __func__, channel, timings->clock_id, timings->period_ns, > + timings->duty_ns, timings->common_div, timings->prescale_k, > + timings->entire_cycles, timings->active_cycles, > + timings->polarity, readl(PCGR(base)), > + readl(PCR(base, channel)), readl(PPR(base, channel)), > + readl(PER(base))); > +} > + > +int update_channel_pair(struct sunxi_pwm_d1_priv *priv, int pair) > +{ > + struct pwm_timings timings0 = {0}; > + struct pwm_timings timings1 = {0}; > + struct pwm_channel *channel0 = &priv->channels[pair + 0]; > + struct pwm_channel *channel1 = &priv->channels[pair + 1]; > + void *base = priv->base; > + > + if (channel0->updated && channel1->updated) > + return 0; > + > + disable_pair(base, pair); > + > + if (find_pair_timings_clocked(priv->clk_srcs, channel0, channel1, > &timings0, &timings1)) > + return -1; > + > + if (channel0->enable || channel1->enable) > + enable_pair(base, pair, timings0.clock_id, timings0.common_div); > + > + if (channel0->enable) > + enable_channel(base, pair + 0, &timings0); > + > + if (channel1->enable) > + enable_channel(base, pair + 1, &timings1); > + > + channel0->updated = true; > + channel1->updated = true; > + > + return 0; > +} > + > +static int update_channels(struct udevice *dev) > +{ > + struct sunxi_pwm_d1_priv *priv = dev_get_priv(dev); > + int i; > + > + for (i = 0; i < priv->npwm; i += 2) { > + int ret = update_channel_pair(priv, i); > + > + if (ret != 0) > + return ret; > + } > + > + return 0; > +} > + > +static int sunxi_pwm_d1_set_invert(struct udevice *dev, uint channel_num, > + bool polarity) > +{ > + struct sunxi_pwm_d1_priv *priv = dev_get_priv(dev); > + struct pwm_channel *channel; > + > + if (channel_num >= priv->npwm) > + return -EINVAL; > + > + channel = &priv->channels[channel_num]; > + channel->updated = (channel->polarity == polarity); > + channel->polarity = polarity; > + > + return update_channels(dev); > +} > + > +static int sunxi_pwm_d1_set_config(struct udevice *dev, uint channel_num, > + uint period_ns, uint duty_ns) > +{ > + struct sunxi_pwm_d1_priv *priv = dev_get_priv(dev); > + struct pwm_channel *channel; > + > + if (channel_num >= priv->npwm) > + return -EINVAL; > + > + channel = &priv->channels[channel_num]; > + channel->updated = (channel->period_ns == period_ns && channel->duty_ns > == duty_ns); > + channel->period_ns = period_ns; > + channel->duty_ns = duty_ns; > + > + return update_channels(dev); > +} > + > +static int sunxi_pwm_d1_set_enable(struct udevice *dev, uint channel_num, > bool enable) > +{ > + struct sunxi_pwm_d1_priv *priv = dev_get_priv(dev); > + struct pwm_channel *channel; > + > + if (channel_num >= priv->npwm) > + return -EINVAL; > + > + channel = &priv->channels[channel_num]; > + channel->updated = (channel->enable == enable); > + channel->enable = enable; > + > + return update_channels(dev); > +} > + > +static int sunxi_pwm_d1_of_to_plat(struct udevice *dev) > +{ > + struct sunxi_pwm_d1_priv *priv = dev_get_priv(dev); > + struct clk *clk_hosc; > + struct clk *clk_apb0; > + int ret; > + > + priv->base = dev_read_addr_ptr(dev); > + > + if (!priv->base) { > + dev_err(dev, "Unset device tree offset?\n"); > + return -EINVAL; > + } > + > + priv->clk_bus = devm_clk_get(dev, "bus"); > + > + if (IS_ERR(priv->clk_bus)) { > + dev_err(dev, "failed to get bus clock: %ld", > + PTR_ERR(priv->clk_bus)); > + return PTR_ERR(priv->clk_bus); > + } > + > + ret = clk_enable(priv->clk_bus); > + > + if (ret) { > + dev_err(dev, "failed to enable bus clk: %d", ret); > + return ret; > + } > + > + clk_hosc = devm_clk_get(dev, "hosc"); > + > + if (IS_ERR(clk_hosc)) { > + dev_err(dev, "failed to get hosc clock: %ld", > + PTR_ERR(clk_hosc)); > + return PTR_ERR(clk_hosc); > + } > + > + clk_apb0 = devm_clk_get(dev, "apb0"); > + > + if (IS_ERR(clk_apb0)) { > + dev_err(dev, "failed to get apb0 clock: %ld", > + PTR_ERR(clk_apb0)); > + return PTR_ERR(clk_apb0); > + } > + > + priv->clk_srcs[0] = clk_hosc; > + priv->clk_srcs[1] = clk_apb0; > + priv->clk_srcs[2] = NULL; > + > + priv->reset = devm_reset_control_get(dev, NULL); > + > + if (IS_ERR(priv->reset)) { > + dev_err(dev, "failed to get reset: %ld", > + PTR_ERR(priv->reset)); > + return PTR_ERR(priv->reset); > + } > + > + priv->npwm = 8; > + ret = dev_read_u32(dev, "allwinner,pwm-channels", &priv->npwm); > + > + if (ret < 0 && ret != -EINVAL) { > + dev_err(dev, "failed to read allwinner,pwm-channels: %d", > + ret); > + return ret; > + } > + > + priv->channels = devm_kzalloc(dev, > + sizeof(struct pwm_channel) * priv->npwm, > + GFP_KERNEL); > + > + if (!priv->channels) { > + dev_err(dev, "failed to read allocate pwm channels"); > + return -ENOMEM; > + } > + > + return 0; > +} > + > +static int sunxi_pwm_d1_probe(struct udevice *dev) > +{ > + struct sunxi_pwm_d1_priv *priv = dev_get_priv(dev); > + int ret; > + > + ret = reset_deassert(priv->reset); > + > + if (ret < 0) { > + dev_err(dev, "failed to deassert reset: %d", ret); > + return ret; > + } > + > + return update_channels(dev); > +} > + > +static const struct pwm_ops sunxi_pwm_d1_ops = { > + .set_invert = sunxi_pwm_d1_set_invert, > + .set_config = sunxi_pwm_d1_set_config, > + .set_enable = sunxi_pwm_d1_set_enable, > +}; > + > +static const struct udevice_id sunxi_pwm_d1_ids[] = { > + { .compatible = "allwinner,sun20i-d1-pwm" }, > + { } > +}; > + > +U_BOOT_DRIVER(sunxi_pwm_d1) = { > + .name = "sunxi_pwm_d1", > + .id = UCLASS_PWM, > + .of_match = sunxi_pwm_d1_ids, > + .ops = &sunxi_pwm_d1_ops, > + .of_to_plat = sunxi_pwm_d1_of_to_plat, > + .probe = sunxi_pwm_d1_probe, > + .priv_auto = sizeof(struct sunxi_pwm_d1_priv), > +}; > > -- > 2.45.1 >