Messages in this thread | | | From | <> | Subject | Re: [PATCH v2 1/2] pwm: add microchip soft ip corePWM driver | Date | Tue, 14 Jun 2022 12:38:10 +0000 |
| |
On 14/06/2022 13:34, Uwe Kleine-König wrote: > Hello, > > On Mon, Jun 13, 2022 at 12:17:59PM +0100, Conor Dooley wrote: >> Add a driver that supports the Microchip FPGA "soft" PWM IP core. >> >> Signed-off-by: Conor Dooley <conor.dooley@microchip.com> >> --- >> drivers/pwm/Kconfig | 10 + >> drivers/pwm/Makefile | 1 + >> drivers/pwm/pwm-microchip-core.c | 310 +++++++++++++++++++++++++++++++ >> 3 files changed, 321 insertions(+) >> create mode 100644 drivers/pwm/pwm-microchip-core.c >> >> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig >> index 21e3b05a5153..a651848e444b 100644 >> --- a/drivers/pwm/Kconfig >> +++ b/drivers/pwm/Kconfig >> @@ -383,6 +383,16 @@ config PWM_MEDIATEK >> To compile this driver as a module, choose M here: the module >> will be called pwm-mediatek. >> >> +config PWM_MICROCHIP_CORE >> + tristate "Microchip corePWM PWM support" >> + depends on SOC_MICROCHIP_POLARFIRE || COMPILE_TEST >> + depends on HAS_IOMEM && OF >> + help >> + PWM driver for Microchip FPGA soft IP core. >> + >> + To compile this driver as a module, choose M here: the module >> + will be called pwm-microchip-core. >> + >> config PWM_MXS >> tristate "Freescale MXS PWM support" >> depends on ARCH_MXS || COMPILE_TEST >> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile >> index 708840b7fba8..d29754c20f91 100644 >> --- a/drivers/pwm/Makefile >> +++ b/drivers/pwm/Makefile >> @@ -33,6 +33,7 @@ obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o >> obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o >> obj-$(CONFIG_PWM_MESON) += pwm-meson.o >> obj-$(CONFIG_PWM_MEDIATEK) += pwm-mediatek.o >> +obj-$(CONFIG_PWM_MICROCHIP_CORE) += pwm-microchip-core.o >> obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o >> obj-$(CONFIG_PWM_MXS) += pwm-mxs.o >> obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o >> diff --git a/drivers/pwm/pwm-microchip-core.c b/drivers/pwm/pwm-microchip-core.c >> new file mode 100644 >> index 000000000000..d2abc46deec4 >> --- /dev/null >> +++ b/drivers/pwm/pwm-microchip-core.c >> @@ -0,0 +1,310 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> +/* >> + * corePWM driver for Microchip "soft" FPGA IP cores. >> + * >> + * Copyright (c) 2021-2022 Microchip Corporation. All rights reserved. >> + * Author: Conor Dooley <conor.dooley@microchip.com> >> + * Documentation: >> + * https://www.microsemi.com/document-portal/doc_download/1245275-corepwm-hb >> + * >> + * Limitations: >> + * - If the IP block is configured without "shadow registers", all register >> + * writes will take effect immediately, causing glitches on the output. >> + * If shadow registers *are* enabled, a write to the "SYNC_UPDATE" register >> + * notifies the core that it needs to update the registers defining the >> + * waveform from the contents of the "shadow registers". >> + * - The IP block has no concept of a duty cycle, only rising/falling edges of >> + * the waveform. Unfortunately, if the rising & falling edges registers have >> + * the same value written to them the IP block will do whichever of a rising >> + * or a falling edge is possible. I.E. a 50% waveform at twice the requested >> + * period. Therefore to get a 0% waveform, the output is set the max high/low >> + * time depending on polarity. >> + * - The PWM period is set for the whole IP block not per channel. The driver >> + * will only change the period if no other PWM output is enabled. >> + */ >> + >> +#include <linux/clk.h> >> +#include <linux/delay.h> >> +#include <linux/err.h> >> +#include <linux/io.h> >> +#include <linux/module.h> >> +#include <linux/of_device.h> >> +#include <linux/platform_device.h> >> +#include <linux/pwm.h> >> +#include <linux/math.h> >> + >> +#define PREG_TO_VAL(PREG) ((PREG) + 1) >> + >> +#define COREPWM_PRESCALE_REG 0x00u >> +#define COREPWM_PERIOD_REG 0x04u >> +#define COREPWM_EN_LOW_REG 0x08u >> +#define COREPWM_EN_HIGH_REG 0x0Cu >> +#define COREPWM_SYNC_UPD_REG 0xE4u >> +#define COREPWM_POSEDGE_OFFSET 0x10u >> +#define COREPWM_NEGEDGE_OFFSET 0x14u >> +#define COREPWM_CHANNEL_OFFSET 0x08u >> + >> +struct mchp_core_pwm_chip { >> + struct pwm_chip chip; >> + struct clk *clk; >> + void __iomem *base; >> +}; >> + >> +static inline struct mchp_core_pwm_chip *to_mchp_core_pwm(struct pwm_chip *chip) >> +{ >> + return container_of(chip, struct mchp_core_pwm_chip, chip); >> +} >> + >> +static void mchp_core_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm, bool enable) >> +{ >> + struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip); >> + u8 channel_enable, reg_offset, shift; >> + >> + /* >> + * There are two adjacent 8 bit control regs, the lower reg controls >> + * 0-7 and the upper reg 8-15. Check if the pwm is in the upper reg >> + * and if so, offset by the bus width. >> + */ >> + reg_offset = COREPWM_EN_LOW_REG + (pwm->hwpwm >> 3) * sizeof(u32); >> + shift = pwm->hwpwm > 7 ? pwm->hwpwm - 8 : pwm->hwpwm; >> + >> + channel_enable = readb_relaxed(mchp_core_pwm->base + reg_offset); >> + channel_enable &= ~(1 << shift); >> + channel_enable |= (enable << shift); >> + >> + writel_relaxed(channel_enable, mchp_core_pwm->base + reg_offset); >> +} >> + >> +static void mchp_core_pwm_apply_duty(struct pwm_chip *chip, struct pwm_device *pwm, >> + const struct pwm_state *state) >> +{ >> + struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip); >> + void __iomem *channel_base = mchp_core_pwm->base + pwm->hwpwm * COREPWM_CHANNEL_OFFSET; >> + u64 duty_steps, period, tmp; >> + u8 prescale, period_steps, posedge, negedge; >> + >> + prescale = PREG_TO_VAL(readb_relaxed(mchp_core_pwm->base + COREPWM_PRESCALE_REG)); >> + period_steps = PREG_TO_VAL(readb_relaxed(mchp_core_pwm->base + COREPWM_PERIOD_REG)); >> + period = period_steps * prescale * NSEC_PER_SEC; >> + period = div64_u64(period, clk_get_rate(mchp_core_pwm->clk)); >> + >> + /* >> + * Calculate the duty cycle in multiples of the prescaled period: >> + * duty_steps = duty_in_ns / step_in_ns >> + * step_in_ns = (prescale * NSEC_PER_SEC) / clk_rate >> + * The code below is rearranged slightly to only divide once. >> + * >> + * Because the period is per channel, it is possible that the requested >> + * duty cycle is longer than the period, in which case cap it to the >> + * period. >> + */ >> + if (state->duty_cycle > period) { >> + duty_steps = period_steps; >> + } else { >> + duty_steps = state->duty_cycle * clk_get_rate(mchp_core_pwm->clk); >> + tmp = prescale * NSEC_PER_SEC; >> + duty_steps = div64_u64(duty_steps, tmp); >> + } >> + >> + /* >> + * Turn the output on unless posedge == negedge, in which case the >> + * duty is intended to be 0, but limitations of the IP block don't >> + * allow a zero length duty cycle - so just set the max high/low time >> + * respectively. >> + */ >> + if (state->polarity == PWM_POLARITY_INVERSED) { >> + negedge = !duty_steps ? period_steps : 0u; >> + posedge = duty_steps; >> + } else { >> + posedge = !duty_steps ? period_steps : 0u; >> + negedge = duty_steps; >> + } >> + >> + writel_relaxed(posedge, channel_base + COREPWM_POSEDGE_OFFSET); >> + writel_relaxed(negedge, channel_base + COREPWM_NEGEDGE_OFFSET); >> +} >> + >> +static void mchp_core_pwm_apply_period(struct pwm_chip *chip, const struct pwm_state *state) >> +{ >> + struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip); >> + u64 tmp = state->period; >> + u8 prescale, period_steps; >> + >> + /* >> + * Calculate the period cycles and prescale values. >> + * The registers are each 8 bits wide & multiplied to compute the period >> + * so the maximum period that can be generated is 0xFFFF times the period >> + * of the input clock. >> + */ >> + tmp *= clk_get_rate(mchp_core_pwm->clk); >> + do_div(tmp, NSEC_PER_SEC); >> + >> + if (tmp > 0xFFFFu) { >> + prescale = 0xFFu; >> + period_steps = 0xFFu; >> + } else { >> + prescale = tmp >> 8; >> + period_steps = tmp / PREG_TO_VAL(prescale) - 1; > > Here is the 64bit division. >
Thanks :) I would prob have missed that too, since I was thinking div /by/ > int.
| |