lkml.org 
[lkml]   [2022]   [Jun]   [14]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    /
    Date
    From
    SubjectRe: [PATCH v2 1/2] pwm: add microchip soft ip corePWM driver
    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.

    Best regards
    Uwe

    --
    Pengutronix e.K. | Uwe Kleine-König |
    Industrial Linux Solutions | https://www.pengutronix.de/ |
    [unhandled content-type:application/pgp-signature]
    \
     
     \ /
      Last update: 2022-06-14 14:40    [W:4.597 / U:1.360 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site