diff options
Diffstat (limited to 'drivers/pwm')
56 files changed, 4990 insertions, 1667 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 4b956d661755..d9bcd1e8413e 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -29,10 +29,6 @@ menuconfig PWM if PWM -config PWM_SYSFS - bool - default y if SYSFS - config PWM_DEBUG bool "PWM lowlevel drivers additional checks and debug messages" depends on DEBUG_KERNEL @@ -51,6 +47,13 @@ config PWM_AB8500 To compile this driver as a module, choose M here: the module will be called pwm-ab8500. +config PWM_ADP5585 + tristate "ADP5585 PWM support" + depends on MFD_ADP5585 + help + This option enables support for the PWM function found in the Analog + Devices ADP5585. + config PWM_APPLE tristate "Apple SoC PWM support" depends on ARCH_APPLE || COMPILE_TEST @@ -98,6 +101,29 @@ config PWM_ATMEL_TCB To compile this driver as a module, choose M here: the module will be called pwm-atmel-tcb. +config PWM_AXI_PWMGEN + tristate "Analog Devices AXI PWM generator" + depends on MICROBLAZE || NIOS2 || ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA || COMPILE_TEST + select REGMAP_MMIO + help + This enables support for the Analog Devices AXI PWM generator. + + This is a configurable PWM generator with variable pulse width and + period. + + To compile this driver as a module, choose M here: the module will be + called pwm-axi-pwmgen. + +config PWM_BCM2835 + tristate "BCM2835 PWM support" + depends on ARCH_BCM2835 || ARCH_BRCMSTB || COMPILE_TEST + depends on HAS_IOMEM + help + PWM framework driver for BCM2835 controller (Raspberry Pi) + + To compile this driver as a module, choose M here: the module + will be called pwm-bcm2835. + config PWM_BCM_IPROC tristate "iProc PWM support" depends on ARCH_BCM_IPROC || COMPILE_TEST @@ -121,16 +147,6 @@ config PWM_BCM_KONA To compile this driver as a module, choose M here: the module will be called pwm-bcm-kona. -config PWM_BCM2835 - tristate "BCM2835 PWM support" - depends on ARCH_BCM2835 || ARCH_BRCMSTB || COMPILE_TEST - depends on HAS_IOMEM - help - PWM framework driver for BCM2835 controller (Raspberry Pi) - - To compile this driver as a module, choose M here: the module - will be called pwm-bcm2835. - config PWM_BERLIN tristate "Marvell Berlin PWM support" depends on ARCH_BERLIN || COMPILE_TEST @@ -227,6 +243,17 @@ config PWM_FSL_FTM To compile this driver as a module, choose M here: the module will be called pwm-fsl-ftm. +config PWM_GPIO + tristate "GPIO PWM support" + depends on GPIOLIB + depends on HIGH_RES_TIMERS + help + Generic PWM framework driver for software PWM toggling a GPIO pin + from kernel high-resolution timers. + + To compile this driver as a module, choose M here: the module + will be called pwm-gpio. + config PWM_HIBVT tristate "HiSilicon BVT PWM support" depends on ARCH_HISI || COMPILE_TEST @@ -324,6 +351,18 @@ config PWM_KEEMBAY To compile this driver as a module, choose M here: the module will be called pwm-keembay. +config PWM_LOONGSON + tristate "Loongson PWM support" + depends on MACH_LOONGSON64 || COMPILE_TEST + depends on COMMON_CLK + help + Generic PWM framework driver for Loongson family. + It can be found on Loongson-2K series cpus and Loongson LS7A + bridge chips. + + To compile this driver as a module, choose M here: the module + will be called pwm-loongson. + config PWM_LP3943 tristate "TI/National Semiconductor LP3943 PWM support" depends on MFD_LP3943 @@ -384,26 +423,17 @@ config PWM_LPSS_PLATFORM To compile this driver as a module, choose M here: the module will be called pwm-lpss-platform. -config PWM_MESON - tristate "Amlogic Meson PWM driver" - depends on ARCH_MESON || COMPILE_TEST - depends on COMMON_CLK && HAS_IOMEM - help - The platform driver for Amlogic Meson PWM controller. - - To compile this driver as a module, choose M here: the module - will be called pwm-meson. - -config PWM_MTK_DISP - tristate "MediaTek display PWM driver" - depends on ARCH_MEDIATEK || COMPILE_TEST - depends on HAS_IOMEM +config PWM_MC33XS2410 + tristate "MC33XS2410 PWM support" + depends on OF + depends on SPI help - Generic PWM framework driver for MediaTek disp-pwm device. - The PWM is used to control the backlight brightness for display. + NXP MC33XS2410 high-side switch driver. The MC33XS2410 is a four + channel high-side switch. The device is operational from 3.0 V + to 60 V. The device is controlled by SPI port for configuration. To compile this driver as a module, choose M here: the module - will be called pwm-mtk-disp. + will be called pwm-mc33xs2410. config PWM_MEDIATEK tristate "MediaTek PWM support" @@ -415,6 +445,16 @@ config PWM_MEDIATEK To compile this driver as a module, choose M here: the module will be called pwm-mediatek. +config PWM_MESON + tristate "Amlogic Meson PWM driver" + depends on ARCH_MESON || COMPILE_TEST + depends on COMMON_CLK && HAS_IOMEM + help + The platform driver for Amlogic Meson PWM controller. + + To compile this driver as a module, choose M here: the module + will be called pwm-meson. + config PWM_MICROCHIP_CORE tristate "Microchip corePWM PWM support" depends on ARCH_MICROCHIP_POLARFIRE || COMPILE_TEST @@ -425,6 +465,17 @@ config PWM_MICROCHIP_CORE To compile this driver as a module, choose M here: the module will be called pwm-microchip-core. +config PWM_MTK_DISP + tristate "MediaTek display PWM driver" + depends on ARCH_MEDIATEK || COMPILE_TEST + depends on HAS_IOMEM + help + Generic PWM framework driver for MediaTek disp-pwm device. + The PWM is used to control the backlight brightness for display. + + To compile this driver as a module, choose M here: the module + will be called pwm-mtk-disp. + config PWM_MXS tristate "Freescale MXS PWM support" depends on ARCH_MXS || COMPILE_TEST @@ -483,7 +534,7 @@ config PWM_RASPBERRYPI_POE Enable Raspberry Pi firmware controller PWM bus used to control the official RPI PoE hat -config PWM_RCAR +config PWM_RENESAS_RCAR tristate "Renesas R-Car PWM support" depends on ARCH_RENESAS || COMPILE_TEST depends on HAS_IOMEM @@ -494,6 +545,28 @@ config PWM_RCAR To compile this driver as a module, choose M here: the module will be called pwm-rcar. +config PWM_RENESAS_RZG2L_GPT + tristate "Renesas RZ/G2L General PWM Timer support" + depends on ARCH_RZG2L || COMPILE_TEST + depends on HAS_IOMEM + help + This driver exposes the General PWM Timer controller found in Renesas + RZ/G2L like chips through the PWM API. + + To compile this driver as a module, choose M here: the module + will be called pwm-rzg2l-gpt. + +config PWM_RENESAS_RZ_MTU3 + tristate "Renesas RZ/G2L MTU3a PWM Timer support" + depends on RZ_MTU3 + depends on HAS_IOMEM + help + This driver exposes the MTU3a PWM Timer controller found in Renesas + RZ/G2L like chips through the PWM API. + + To compile this driver as a module, choose M here: the module + will be called pwm-rz-mtu3. + config PWM_RENESAS_TPU tristate "Renesas TPU PWM support" depends on ARCH_RENESAS || COMPILE_TEST @@ -513,17 +586,6 @@ config PWM_ROCKCHIP Generic PWM framework driver for the PWM controller found on Rockchip SoCs. -config PWM_RZ_MTU3 - tristate "Renesas RZ/G2L MTU3a PWM Timer support" - depends on RZ_MTU3 - depends on HAS_IOMEM - help - This driver exposes the MTU3a PWM Timer controller found in Renesas - RZ/G2L like chips through the PWM API. - - To compile this driver as a module, choose M here: the module - will be called pwm-rz-mtu3. - config PWM_SAMSUNG tristate "Samsung PWM support" depends on PLAT_SAMSUNG || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST @@ -540,7 +602,7 @@ config PWM_SIFIVE tristate "SiFive PWM support" depends on OF depends on COMMON_CLK && HAS_IOMEM - depends on RISCV || COMPILE_TEST + depends on ARCH_SIFIVE || COMPILE_TEST help Generic PWM framework driver for SiFive SoCs. @@ -557,6 +619,16 @@ config PWM_SL28CPLD To compile this driver as a module, choose M here: the module will be called pwm-sl28cpld. +config PWM_SOPHGO_SG2042 + tristate "Sophgo SG2042 PWM support" + depends on ARCH_SOPHGO || COMPILE_TEST + help + PWM driver for the PWM controller on Sophgo SG2042 SoC. The PWM + controller supports outputing 4 channels of PWM waveforms. + + To compile this driver as a module, choose M here: the module + will be called pwm_sophgo_sg2042. + config PWM_SPEAR tristate "STMicroelectronics SPEAr PWM support" depends on PLAT_SPEAR || COMPILE_TEST @@ -609,7 +681,7 @@ config PWM_STM32_LP will be called pwm-stm32-lp. config PWM_STMPE - bool "STMPE expander PWM export" + tristate "STMPE expander PWM export" depends on MFD_STMPE help This enables support for the PWMs found in the STMPE I/O diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index c5ec9e168ee7..96160f4257fc 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,14 +1,15 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_PWM) += core.o -obj-$(CONFIG_PWM_SYSFS) += sysfs.o obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o +obj-$(CONFIG_PWM_ADP5585) += pwm-adp5585.o obj-$(CONFIG_PWM_APPLE) += pwm-apple.o obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o +obj-$(CONFIG_PWM_AXI_PWMGEN) += pwm-axi-pwmgen.o +obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o obj-$(CONFIG_PWM_BCM_IPROC) += pwm-bcm-iproc.o obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o -obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o obj-$(CONFIG_PWM_BRCMSTB) += pwm-brcmstb.o obj-$(CONFIG_PWM_CLK) += pwm-clk.o @@ -19,6 +20,7 @@ obj-$(CONFIG_PWM_DWC_CORE) += pwm-dwc-core.o obj-$(CONFIG_PWM_DWC) += pwm-dwc.o obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o +obj-$(CONFIG_PWM_GPIO) += pwm-gpio.o obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o obj-$(CONFIG_PWM_IMG) += pwm-img.o obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o @@ -28,14 +30,16 @@ obj-$(CONFIG_PWM_INTEL_LGM) += pwm-intel-lgm.o obj-$(CONFIG_PWM_IQS620A) += pwm-iqs620a.o obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_KEEMBAY) += pwm-keembay.o +obj-$(CONFIG_PWM_LOONGSON) += pwm-loongson.o obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o 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_MC33XS2410) += pwm-mc33xs2410.o obj-$(CONFIG_PWM_MEDIATEK) += pwm-mediatek.o +obj-$(CONFIG_PWM_MESON) += pwm-meson.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 @@ -44,13 +48,15 @@ obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o -obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o +obj-$(CONFIG_PWM_RENESAS_RCAR) += pwm-rcar.o +obj-$(CONFIG_PWM_RENESAS_RZG2L_GPT) += pwm-rzg2l-gpt.o +obj-$(CONFIG_PWM_RENESAS_RZ_MTU3) += pwm-rz-mtu3.o obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o -obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o obj-$(CONFIG_PWM_SL28CPLD) += pwm-sl28cpld.o +obj-$(CONFIG_PWM_SOPHGO_SG2042) += pwm-sophgo-sg2042.o obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o obj-$(CONFIG_PWM_SPRD) += pwm-sprd.o obj-$(CONFIG_PWM_STI) += pwm-sti.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index d70f793ce4b3..4d842c692194 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -6,6 +6,8 @@ * Copyright (C) 2011-2012 Avionic Design GmbH */ +#define DEFAULT_SYMBOL_NAMESPACE "PWM" + #include <linux/acpi.h> #include <linux/module.h> #include <linux/idr.h> @@ -29,6 +31,454 @@ static DEFINE_MUTEX(pwm_lock); static DEFINE_IDR(pwm_chips); +static void pwmchip_lock(struct pwm_chip *chip) +{ + if (chip->atomic) + spin_lock(&chip->atomic_lock); + else + mutex_lock(&chip->nonatomic_lock); +} + +static void pwmchip_unlock(struct pwm_chip *chip) +{ + if (chip->atomic) + spin_unlock(&chip->atomic_lock); + else + mutex_unlock(&chip->nonatomic_lock); +} + +DEFINE_GUARD(pwmchip, struct pwm_chip *, pwmchip_lock(_T), pwmchip_unlock(_T)) + +static bool pwm_wf_valid(const struct pwm_waveform *wf) +{ + /* + * For now restrict waveforms to period_length_ns <= S64_MAX to provide + * some space for future extensions. One possibility is to simplify + * representing waveforms with inverted polarity using negative values + * somehow. + */ + if (wf->period_length_ns > S64_MAX) + return false; + + if (wf->duty_length_ns > wf->period_length_ns) + return false; + + /* + * .duty_offset_ns is supposed to be smaller than .period_length_ns, apart + * from the corner case .duty_offset_ns == 0 && .period_length_ns == 0. + */ + if (wf->duty_offset_ns && wf->duty_offset_ns >= wf->period_length_ns) + return false; + + return true; +} + +static void pwm_wf2state(const struct pwm_waveform *wf, struct pwm_state *state) +{ + if (wf->period_length_ns) { + if (wf->duty_length_ns + wf->duty_offset_ns < wf->period_length_ns) + *state = (struct pwm_state){ + .enabled = true, + .polarity = PWM_POLARITY_NORMAL, + .period = wf->period_length_ns, + .duty_cycle = wf->duty_length_ns, + }; + else + *state = (struct pwm_state){ + .enabled = true, + .polarity = PWM_POLARITY_INVERSED, + .period = wf->period_length_ns, + .duty_cycle = wf->period_length_ns - wf->duty_length_ns, + }; + } else { + *state = (struct pwm_state){ + .enabled = false, + }; + } +} + +static void pwm_state2wf(const struct pwm_state *state, struct pwm_waveform *wf) +{ + if (state->enabled) { + if (state->polarity == PWM_POLARITY_NORMAL) + *wf = (struct pwm_waveform){ + .period_length_ns = state->period, + .duty_length_ns = state->duty_cycle, + .duty_offset_ns = 0, + }; + else + *wf = (struct pwm_waveform){ + .period_length_ns = state->period, + .duty_length_ns = state->period - state->duty_cycle, + .duty_offset_ns = state->duty_cycle, + }; + } else { + *wf = (struct pwm_waveform){ + .period_length_ns = 0, + }; + } +} + +static int pwmwfcmp(const struct pwm_waveform *a, const struct pwm_waveform *b) +{ + if (a->period_length_ns > b->period_length_ns) + return 1; + + if (a->period_length_ns < b->period_length_ns) + return -1; + + if (a->duty_length_ns > b->duty_length_ns) + return 1; + + if (a->duty_length_ns < b->duty_length_ns) + return -1; + + if (a->duty_offset_ns > b->duty_offset_ns) + return 1; + + if (a->duty_offset_ns < b->duty_offset_ns) + return -1; + + return 0; +} + +static bool pwm_check_rounding(const struct pwm_waveform *wf, + const struct pwm_waveform *wf_rounded) +{ + if (!wf->period_length_ns) + return true; + + if (wf->period_length_ns < wf_rounded->period_length_ns) + return false; + + if (wf->duty_length_ns < wf_rounded->duty_length_ns) + return false; + + if (wf->duty_offset_ns < wf_rounded->duty_offset_ns) + return false; + + return true; +} + +static int __pwm_round_waveform_tohw(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_waveform *wf, void *wfhw) +{ + const struct pwm_ops *ops = chip->ops; + int ret; + + ret = ops->round_waveform_tohw(chip, pwm, wf, wfhw); + trace_pwm_round_waveform_tohw(pwm, wf, wfhw, ret); + + return ret; +} + +static int __pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm, + const void *wfhw, struct pwm_waveform *wf) +{ + const struct pwm_ops *ops = chip->ops; + int ret; + + ret = ops->round_waveform_fromhw(chip, pwm, wfhw, wf); + trace_pwm_round_waveform_fromhw(pwm, wfhw, wf, ret); + + return ret; +} + +static int __pwm_read_waveform(struct pwm_chip *chip, struct pwm_device *pwm, void *wfhw) +{ + const struct pwm_ops *ops = chip->ops; + int ret; + + ret = ops->read_waveform(chip, pwm, wfhw); + trace_pwm_read_waveform(pwm, wfhw, ret); + + return ret; +} + +static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, const void *wfhw) +{ + const struct pwm_ops *ops = chip->ops; + int ret; + + ret = ops->write_waveform(chip, pwm, wfhw); + trace_pwm_write_waveform(pwm, wfhw, ret); + + return ret; +} + +#define WFHWSIZE 20 + +/** + * pwm_round_waveform_might_sleep - Query hardware capabilities + * Cannot be used in atomic context. + * @pwm: PWM device + * @wf: waveform to round and output parameter + * + * Typically a given waveform cannot be implemented exactly by hardware, e.g. + * because hardware only supports coarse period resolution or no duty_offset. + * This function returns the actually implemented waveform if you pass @wf to + * pwm_set_waveform_might_sleep() now. + * + * Note however that the world doesn't stop turning when you call it, so when + * doing:: + * + * pwm_round_waveform_might_sleep(mypwm, &wf); + * pwm_set_waveform_might_sleep(mypwm, &wf, true); + * + * the latter might fail, e.g. because an input clock changed its rate between + * these two calls and the waveform determined by + * pwm_round_waveform_might_sleep() cannot be implemented any more. + * + * Usually all values passed in @wf are rounded down to the nearest possible + * value (in the order period_length_ns, duty_length_ns and then + * duty_offset_ns). Only if this isn't possible, a value might grow. See the + * documentation for pwm_set_waveform_might_sleep() for a more formal + * description. + * + * Returns: 0 on success, 1 if at least one value had to be rounded up or a + * negative errno. + * Context: May sleep. + */ +int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf) +{ + struct pwm_chip *chip = pwm->chip; + const struct pwm_ops *ops = chip->ops; + struct pwm_waveform wf_req = *wf; + char wfhw[WFHWSIZE]; + int ret_tohw, ret_fromhw; + + BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + + if (!pwmchip_supports_waveform(chip)) + return -EOPNOTSUPP; + + if (!pwm_wf_valid(wf)) + return -EINVAL; + + guard(pwmchip)(chip); + + if (!chip->operational) + return -ENODEV; + + ret_tohw = __pwm_round_waveform_tohw(chip, pwm, wf, wfhw); + if (ret_tohw < 0) + return ret_tohw; + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_tohw > 1) + dev_err(&chip->dev, "Unexpected return value from __pwm_round_waveform_tohw: requested %llu/%llu [+%llu], return value %d\n", + wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, ret_tohw); + + ret_fromhw = __pwm_round_waveform_fromhw(chip, pwm, wfhw, wf); + if (ret_fromhw < 0) + return ret_fromhw; + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_fromhw > 0) + dev_err(&chip->dev, "Unexpected return value from __pwm_round_waveform_fromhw: requested %llu/%llu [+%llu], return value %d\n", + wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, ret_tohw); + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && + (ret_tohw == 0) != pwm_check_rounding(&wf_req, wf)) + dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu], ret: %d\n", + wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, + wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, ret_tohw); + + return ret_tohw; +} +EXPORT_SYMBOL_GPL(pwm_round_waveform_might_sleep); + +/** + * pwm_get_waveform_might_sleep - Query hardware about current configuration + * Cannot be used in atomic context. + * @pwm: PWM device + * @wf: output parameter + * + * Stores the current configuration of the PWM in @wf. Note this is the + * equivalent of pwm_get_state_hw() (and not pwm_get_state()) for pwm_waveform. + * + * Returns: 0 on success or a negative errno + * Context: May sleep. + */ +int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf) +{ + struct pwm_chip *chip = pwm->chip; + const struct pwm_ops *ops = chip->ops; + char wfhw[WFHWSIZE]; + int err; + + BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + + if (!pwmchip_supports_waveform(chip) || !ops->read_waveform) + return -EOPNOTSUPP; + + guard(pwmchip)(chip); + + if (!chip->operational) + return -ENODEV; + + err = __pwm_read_waveform(chip, pwm, &wfhw); + if (err) + return err; + + return __pwm_round_waveform_fromhw(chip, pwm, &wfhw, wf); +} +EXPORT_SYMBOL_GPL(pwm_get_waveform_might_sleep); + +/* Called with the pwmchip lock held */ +static int __pwm_set_waveform(struct pwm_device *pwm, + const struct pwm_waveform *wf, + bool exact) +{ + struct pwm_chip *chip = pwm->chip; + const struct pwm_ops *ops = chip->ops; + char wfhw[WFHWSIZE]; + struct pwm_waveform wf_rounded; + int err, ret_tohw; + + BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + + if (!pwmchip_supports_waveform(chip)) + return -EOPNOTSUPP; + + if (!pwm_wf_valid(wf)) + return -EINVAL; + + ret_tohw = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw); + if (ret_tohw < 0) + return ret_tohw; + + if ((IS_ENABLED(CONFIG_PWM_DEBUG) || exact) && wf->period_length_ns) { + err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded); + if (err) + return err; + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && (ret_tohw == 0) != pwm_check_rounding(wf, &wf_rounded)) + dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu], ret: %d\n", + wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, + wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns, ret_tohw); + + if (exact && pwmwfcmp(wf, &wf_rounded)) { + dev_dbg(&chip->dev, "Requested no rounding, but %llu/%llu [+%llu] -> %llu/%llu [+%llu]\n", + wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, + wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns); + + return 1; + } + } + + err = __pwm_write_waveform(chip, pwm, &wfhw); + if (err) + return err; + + /* update .state */ + pwm_wf2state(wf, &pwm->state); + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && ops->read_waveform && wf->period_length_ns) { + struct pwm_waveform wf_set; + + err = __pwm_read_waveform(chip, pwm, &wfhw); + if (err) + /* maybe ignore? */ + return err; + + err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_set); + if (err) + /* maybe ignore? */ + return err; + + if (pwmwfcmp(&wf_set, &wf_rounded) != 0) + dev_err(&chip->dev, + "Unexpected setting: requested %llu/%llu [+%llu], expected %llu/%llu [+%llu], set %llu/%llu [+%llu]\n", + wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, + wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns, + wf_set.duty_length_ns, wf_set.period_length_ns, wf_set.duty_offset_ns); + } + + return ret_tohw; +} + +/** + * pwm_set_waveform_might_sleep - Apply a new waveform + * Cannot be used in atomic context. + * @pwm: PWM device + * @wf: The waveform to apply + * @exact: If true no rounding is allowed + * + * Typically a requested waveform cannot be implemented exactly, e.g. because + * you requested .period_length_ns = 100 ns, but the hardware can only set + * periods that are a multiple of 8.5 ns. With that hardware passing @exact = + * true results in pwm_set_waveform_might_sleep() failing and returning -EDOM. + * If @exact = false you get a period of 93.5 ns (i.e. the biggest period not + * bigger than the requested value). + * Note that even with @exact = true, some rounding by less than 1 ns is + * possible/needed. In the above example requesting .period_length_ns = 94 and + * @exact = true, you get the hardware configured with period = 93.5 ns. + * + * Let C be the set of possible hardware configurations for a given PWM device, + * consisting of tuples (p, d, o) where p is the period length, d is the duty + * length and o the duty offset. + * + * The following algorithm is implemented to pick the hardware setting + * (p, d, o) ∈ C for a given request (p', d', o') with @exact = false:: + * + * p = max( { ṗ | ∃ ḋ, ȯ : (ṗ, ḋ, ȯ) ∈ C ∧ ṗ ≤ p' } ∪ { min({ ṗ | ∃ ḋ, ȯ : (ṗ, ḋ, ȯ) ∈ C }) }) + * d = max( { ḋ | ∃ ȯ : (p, ḋ, ȯ) ∈ C ∧ ḋ ≤ d' } ∪ { min({ ḋ | ∃ ȯ : (p, ḋ, ȯ) ∈ C }) }) + * o = max( { ȯ | (p, d, ȯ) ∈ C ∧ ȯ ≤ o' } ∪ { min({ ȯ | (p, d, ȯ) ∈ C }) }) + * + * In words: The chosen period length is the maximal possible period length not + * bigger than the requested period length and if that doesn't exist, the + * minimal period length. The chosen duty length is the maximal possible duty + * length that is compatible with the chosen period length and isn't bigger than + * the requested duty length. Again if such a value doesn't exist, the minimal + * duty length compatible with the chosen period is picked. After that the duty + * offset compatible with the chosen period and duty length is chosen in the + * same way. + * + * Returns: 0 on success, -EDOM if setting failed due to the exact waveform not + * being possible (if @exact), or a different negative errno on failure. + * Context: May sleep. + */ +int pwm_set_waveform_might_sleep(struct pwm_device *pwm, + const struct pwm_waveform *wf, bool exact) +{ + struct pwm_chip *chip = pwm->chip; + int err; + + might_sleep(); + + guard(pwmchip)(chip); + + if (!chip->operational) + return -ENODEV; + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && chip->atomic) { + /* + * Catch any drivers that have been marked as atomic but + * that will sleep anyway. + */ + non_block_start(); + err = __pwm_set_waveform(pwm, wf, exact); + non_block_end(); + } else { + err = __pwm_set_waveform(pwm, wf, exact); + } + + /* + * map err == 1 to -EDOM for exact requests and 0 for !exact ones. Also + * make sure that -EDOM is only returned in exactly that case. Note that + * __pwm_set_waveform() should never return -EDOM which justifies the + * unlikely(). + */ + if (unlikely(err == -EDOM)) + err = -EINVAL; + else if (exact && err == 1) + err = -EDOM; + else if (err == 1) + err = 0; + + return err; +} +EXPORT_SYMBOL_GPL(pwm_set_waveform_might_sleep); + static void pwm_apply_debug(struct pwm_device *pwm, const struct pwm_state *state) { @@ -73,7 +523,7 @@ static void pwm_apply_debug(struct pwm_device *pwm, state->duty_cycle < state->period) dev_warn(pwmchip_parent(chip), ".apply ignored .polarity\n"); - if (state->enabled && + if (state->enabled && s2.enabled && last->polarity == state->polarity && last->period > s2.period && last->period <= state->period) @@ -81,7 +531,11 @@ static void pwm_apply_debug(struct pwm_device *pwm, ".apply didn't pick the best available period (requested: %llu, applied: %llu, possible: %llu)\n", state->period, s2.period, last->period); - if (state->enabled && state->period < s2.period) + /* + * Rounding period up is fine only if duty_cycle is 0 then, because a + * flat line doesn't have a characteristic period. + */ + if (state->enabled && s2.enabled && state->period < s2.period && s2.duty_cycle) dev_warn(pwmchip_parent(chip), ".apply is supposed to round down period (requested: %llu, applied: %llu)\n", state->period, s2.period); @@ -97,7 +551,7 @@ static void pwm_apply_debug(struct pwm_device *pwm, s2.duty_cycle, s2.period, last->duty_cycle, last->period); - if (state->enabled && state->duty_cycle < s2.duty_cycle) + if (state->enabled && s2.enabled && state->duty_cycle < s2.duty_cycle) dev_warn(pwmchip_parent(chip), ".apply is supposed to round down duty_cycle (requested: %llu/%llu, applied: %llu/%llu)\n", state->duty_cycle, state->period, @@ -135,21 +589,53 @@ static void pwm_apply_debug(struct pwm_device *pwm, } } -/** - * __pwm_apply() - atomically apply a new state to a PWM device - * @pwm: PWM device - * @state: new state to apply - */ +static bool pwm_state_valid(const struct pwm_state *state) +{ + /* + * For a disabled state all other state description is irrelevant and + * and supposed to be ignored. So also ignore any strange values and + * consider the state ok. + */ + if (state->enabled) + return true; + + if (!state->period) + return false; + + if (state->duty_cycle > state->period) + return false; + + return true; +} + static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state) { struct pwm_chip *chip; + const struct pwm_ops *ops; int err; - if (!pwm || !state || !state->period || - state->duty_cycle > state->period) + if (!pwm || !state) + return -EINVAL; + + if (!pwm_state_valid(state)) { + /* + * Allow to transition from one invalid state to another. + * This ensures that you can e.g. change the polarity while + * the period is zero. (This happens on stm32 when the hardware + * is in its poweron default state.) This greatly simplifies + * working with the sysfs API where you can only change one + * parameter at a time. + */ + if (!pwm_state_valid(&pwm->state)) { + pwm->state = *state; + return 0; + } + return -EINVAL; + } chip = pwm->chip; + ops = chip->ops; if (state->period == pwm->state.period && state->duty_cycle == pwm->state.duty_cycle && @@ -158,18 +644,69 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state) state->usage_power == pwm->state.usage_power) return 0; - err = chip->ops->apply(chip, pwm, state); - trace_pwm_apply(pwm, state, err); - if (err) - return err; + if (pwmchip_supports_waveform(chip)) { + struct pwm_waveform wf; + char wfhw[WFHWSIZE]; - pwm->state = *state; + BUG_ON(WFHWSIZE < ops->sizeof_wfhw); - /* - * only do this after pwm->state was applied as some - * implementations of .get_state depend on this - */ - pwm_apply_debug(pwm, state); + pwm_state2wf(state, &wf); + + /* + * The rounding is wrong here for states with inverted polarity. + * While .apply() rounds down duty_cycle (which represents the + * time from the start of the period to the inner edge), + * .round_waveform_tohw() rounds down the time the PWM is high. + * Can be fixed if the need arises, until reported otherwise + * let's assume that consumers don't care. + */ + + err = __pwm_round_waveform_tohw(chip, pwm, &wf, &wfhw); + if (err) { + if (err > 0) + /* + * This signals an invalid request, typically + * the requested period (or duty_offset) is + * smaller than possible with the hardware. + */ + return -EINVAL; + + return err; + } + + if (IS_ENABLED(CONFIG_PWM_DEBUG)) { + struct pwm_waveform wf_rounded; + + err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded); + if (err) + return err; + + if (!pwm_check_rounding(&wf, &wf_rounded)) + dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n", + wf.duty_length_ns, wf.period_length_ns, wf.duty_offset_ns, + wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns); + } + + err = __pwm_write_waveform(chip, pwm, &wfhw); + if (err) + return err; + + pwm->state = *state; + + } else { + err = ops->apply(chip, pwm, state); + trace_pwm_apply(pwm, state, err); + if (err) + return err; + + pwm->state = *state; + + /* + * only do this after pwm->state was applied as some + * implementations of .get_state() depend on this + */ + pwm_apply_debug(pwm, state); + } return 0; } @@ -179,10 +716,14 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state) * Cannot be used in atomic context. * @pwm: PWM device * @state: new state to apply + * + * Returns: 0 on success, or a negative errno + * Context: May sleep. */ int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state) { int err; + struct pwm_chip *chip = pwm->chip; /* * Some lowlevel driver's implementations of .apply() make use of @@ -193,7 +734,12 @@ int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state) */ might_sleep(); - if (IS_ENABLED(CONFIG_PWM_DEBUG) && pwm->chip->atomic) { + guard(pwmchip)(chip); + + if (!chip->operational) + return -ENODEV; + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && chip->atomic) { /* * Catch any drivers that have been marked as atomic but * that will sleep anyway. @@ -214,23 +760,85 @@ EXPORT_SYMBOL_GPL(pwm_apply_might_sleep); * Not all PWM devices support this function, check with pwm_might_sleep(). * @pwm: PWM device * @state: new state to apply + * + * Returns: 0 on success, or a negative errno + * Context: Any */ int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state) { - WARN_ONCE(!pwm->chip->atomic, + struct pwm_chip *chip = pwm->chip; + + WARN_ONCE(!chip->atomic, "sleeping PWM driver used in atomic context\n"); + guard(pwmchip)(chip); + + if (!chip->operational) + return -ENODEV; + return __pwm_apply(pwm, state); } EXPORT_SYMBOL_GPL(pwm_apply_atomic); /** + * pwm_get_state_hw() - get the current PWM state from hardware + * @pwm: PWM device + * @state: state to fill with the current PWM state + * + * Similar to pwm_get_state() but reads the current PWM state from hardware + * instead of the requested state. + * + * Returns: 0 on success or a negative error code on failure. + * Context: May sleep. + */ +int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state) +{ + struct pwm_chip *chip = pwm->chip; + const struct pwm_ops *ops = chip->ops; + int ret = -EOPNOTSUPP; + + might_sleep(); + + guard(pwmchip)(chip); + + if (!chip->operational) + return -ENODEV; + + if (pwmchip_supports_waveform(chip) && ops->read_waveform) { + char wfhw[WFHWSIZE]; + struct pwm_waveform wf; + + BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + + ret = __pwm_read_waveform(chip, pwm, &wfhw); + if (ret) + return ret; + + ret = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf); + if (ret) + return ret; + + pwm_wf2state(&wf, state); + + } else if (ops->get_state) { + ret = ops->get_state(chip, pwm, state); + trace_pwm_get(pwm, state, ret); + } + + return ret; +} +EXPORT_SYMBOL_GPL(pwm_get_state_hw); + +/** * pwm_adjust_config() - adjust the current PWM config to the PWM arguments * @pwm: PWM device * * This function will adjust the PWM config to the PWM arguments provided * by the DT or PWM lookup table. This is particularly useful to adapt * the bootloader config to the Linux one. + * + * Returns: 0 on success or a negative error code on failure. + * Context: May sleep. */ int pwm_adjust_config(struct pwm_device *pwm) { @@ -288,24 +896,29 @@ EXPORT_SYMBOL_GPL(pwm_adjust_config); * * Returns: 0 on success or a negative error code on failure. */ -int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result, - unsigned long timeout) +static int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result, + unsigned long timeout) { - int err; - - if (!pwm || !pwm->chip->ops) - return -EINVAL; + struct pwm_chip *chip = pwm->chip; + const struct pwm_ops *ops = chip->ops; - if (!pwm->chip->ops->capture) + if (!ops->capture) return -ENOSYS; - mutex_lock(&pwm_lock); - err = pwm->chip->ops->capture(pwm->chip, pwm, result, timeout); - mutex_unlock(&pwm_lock); + /* + * Holding the pwm_lock is probably not needed. If you use pwm_capture() + * and you're interested to speed it up, please convince yourself it's + * really not needed, test and then suggest a patch on the mailing list. + */ + guard(mutex)(&pwm_lock); - return err; + guard(pwmchip)(chip); + + if (!chip->operational) + return -ENODEV; + + return ops->capture(chip, pwm, result, timeout); } -EXPORT_SYMBOL_GPL(pwm_capture); static struct pwm_chip *pwmchip_find_by_name(const char *name) { @@ -315,19 +928,13 @@ static struct pwm_chip *pwmchip_find_by_name(const char *name) if (!name) return NULL; - mutex_lock(&pwm_lock); + guard(mutex)(&pwm_lock); idr_for_each_entry_ul(&pwm_chips, chip, tmp, id) { - const char *chip_name = dev_name(pwmchip_parent(chip)); - - if (chip_name && strcmp(chip_name, name) == 0) { - mutex_unlock(&pwm_lock); + if (device_match_name(pwmchip_parent(chip), name)) return chip; - } } - mutex_unlock(&pwm_lock); - return NULL; } @@ -340,18 +947,33 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label) if (test_bit(PWMF_REQUESTED, &pwm->flags)) return -EBUSY; + /* + * This function is called while holding pwm_lock. As .operational only + * changes while holding this lock, checking it here without holding the + * chip lock is fine. + */ + if (!chip->operational) + return -ENODEV; + if (!try_module_get(chip->owner)) return -ENODEV; + if (!get_device(&chip->dev)) { + err = -ENODEV; + goto err_get_device; + } + if (ops->request) { err = ops->request(chip, pwm); if (err) { + put_device(&chip->dev); +err_get_device: module_put(chip->owner); return err; } } - if (ops->get_state) { + if (ops->read_waveform || ops->get_state) { /* * Zero-initialize state because most drivers are unaware of * .usage_power. The other members of state are supposed to be @@ -361,9 +983,7 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label) */ struct pwm_state state = { 0, }; - err = ops->get_state(chip, pwm, &state); - trace_pwm_get(pwm, &state, err); - + err = pwm_get_state_hw(pwm, &state); if (!err) pwm->state = state; @@ -387,9 +1007,9 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label) * chip. A negative error code is returned if the index is not valid for the * specified PWM chip or if the PWM device cannot be requested. */ -struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip, - unsigned int index, - const char *label) +static struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip, + unsigned int index, + const char *label) { struct pwm_device *pwm; int err; @@ -397,18 +1017,16 @@ struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip, if (!chip || index >= chip->npwm) return ERR_PTR(-EINVAL); - mutex_lock(&pwm_lock); + guard(mutex)(&pwm_lock); + pwm = &chip->pwms[index]; err = pwm_device_request(pwm, label); if (err < 0) - pwm = ERR_PTR(err); + return ERR_PTR(err); - mutex_unlock(&pwm_lock); return pwm; } -EXPORT_SYMBOL_GPL(pwm_request_from_chip); - struct pwm_device * of_pwm_xlate_with_flags(struct pwm_chip *chip, const struct of_phandle_args *args) @@ -434,16 +1052,32 @@ of_pwm_xlate_with_flags(struct pwm_chip *chip, const struct of_phandle_args *arg } EXPORT_SYMBOL_GPL(of_pwm_xlate_with_flags); +/* + * This callback is used for PXA PWM chips that only have a single PWM line. + * For such chips you could argue that passing the line number (i.e. the first + * parameter in the common case) is useless as it's always zero. So compared to + * the default xlate function of_pwm_xlate_with_flags() the first parameter is + * the default period and the second are flags. + * + * Note that if #pwm-cells = <3>, the semantic is the same as for + * of_pwm_xlate_with_flags() to allow converting the affected driver to + * #pwm-cells = <3> without breaking the legacy binding. + * + * Don't use for new drivers. + */ struct pwm_device * of_pwm_single_xlate(struct pwm_chip *chip, const struct of_phandle_args *args) { struct pwm_device *pwm; + if (args->args_count >= 3) + return of_pwm_xlate_with_flags(chip, args); + pwm = pwm_request_from_chip(chip, 0, NULL); if (IS_ERR(pwm)) return pwm; - if (args->args_count > 1) + if (args->args_count > 0) pwm->args.period = args->args[0]; pwm->args.polarity = PWM_POLARITY_NORMAL; @@ -454,192 +1088,625 @@ of_pwm_single_xlate(struct pwm_chip *chip, const struct of_phandle_args *args) } EXPORT_SYMBOL_GPL(of_pwm_single_xlate); -#define PWMCHIP_ALIGN ARCH_DMA_MINALIGN +struct pwm_export { + struct device pwm_dev; + struct pwm_device *pwm; + struct mutex lock; + struct pwm_state suspend; +}; -static void *pwmchip_priv(struct pwm_chip *chip) +static inline struct pwm_chip *pwmchip_from_dev(struct device *pwmchip_dev) { - return (void *)chip + ALIGN(sizeof(*chip), PWMCHIP_ALIGN); + return container_of(pwmchip_dev, struct pwm_chip, dev); } -/* This is the counterpart to pwmchip_alloc() */ -void pwmchip_put(struct pwm_chip *chip) +static inline struct pwm_export *pwmexport_from_dev(struct device *pwm_dev) { - kfree(chip); + return container_of(pwm_dev, struct pwm_export, pwm_dev); } -EXPORT_SYMBOL_GPL(pwmchip_put); -struct pwm_chip *pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv) +static inline struct pwm_device *pwm_from_dev(struct device *pwm_dev) { - struct pwm_chip *chip; - size_t alloc_size; + struct pwm_export *export = pwmexport_from_dev(pwm_dev); - alloc_size = size_add(ALIGN(sizeof(*chip), PWMCHIP_ALIGN), sizeof_priv); + return export->pwm; +} - chip = kzalloc(alloc_size, GFP_KERNEL); - if (!chip) - return ERR_PTR(-ENOMEM); +static ssize_t period_show(struct device *pwm_dev, + struct device_attribute *attr, + char *buf) +{ + const struct pwm_device *pwm = pwm_from_dev(pwm_dev); + struct pwm_state state; - chip->dev = parent; - chip->npwm = npwm; + pwm_get_state(pwm, &state); - pwmchip_set_drvdata(chip, pwmchip_priv(chip)); + return sysfs_emit(buf, "%llu\n", state.period); +} - return chip; +static ssize_t period_store(struct device *pwm_dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct pwm_export *export = pwmexport_from_dev(pwm_dev); + struct pwm_device *pwm = export->pwm; + struct pwm_state state; + u64 val; + int ret; + + ret = kstrtou64(buf, 0, &val); + if (ret) + return ret; + + guard(mutex)(&export->lock); + + pwm_get_state(pwm, &state); + state.period = val; + ret = pwm_apply_might_sleep(pwm, &state); + + return ret ? : size; } -EXPORT_SYMBOL_GPL(pwmchip_alloc); -static void devm_pwmchip_put(void *data) +static ssize_t duty_cycle_show(struct device *pwm_dev, + struct device_attribute *attr, + char *buf) { - struct pwm_chip *chip = data; + const struct pwm_device *pwm = pwm_from_dev(pwm_dev); + struct pwm_state state; - pwmchip_put(chip); + pwm_get_state(pwm, &state); + + return sysfs_emit(buf, "%llu\n", state.duty_cycle); } -struct pwm_chip *devm_pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv) +static ssize_t duty_cycle_store(struct device *pwm_dev, + struct device_attribute *attr, + const char *buf, size_t size) { - struct pwm_chip *chip; + struct pwm_export *export = pwmexport_from_dev(pwm_dev); + struct pwm_device *pwm = export->pwm; + struct pwm_state state; + u64 val; int ret; - chip = pwmchip_alloc(parent, npwm, sizeof_priv); - if (IS_ERR(chip)) - return chip; - - ret = devm_add_action_or_reset(parent, devm_pwmchip_put, chip); + ret = kstrtou64(buf, 0, &val); if (ret) - return ERR_PTR(ret); + return ret; - return chip; + guard(mutex)(&export->lock); + + pwm_get_state(pwm, &state); + state.duty_cycle = val; + ret = pwm_apply_might_sleep(pwm, &state); + + return ret ? : size; } -EXPORT_SYMBOL_GPL(devm_pwmchip_alloc); -static void of_pwmchip_add(struct pwm_chip *chip) +static ssize_t enable_show(struct device *pwm_dev, + struct device_attribute *attr, + char *buf) { - if (!pwmchip_parent(chip) || !pwmchip_parent(chip)->of_node) - return; + const struct pwm_device *pwm = pwm_from_dev(pwm_dev); + struct pwm_state state; - if (!chip->of_xlate) - chip->of_xlate = of_pwm_xlate_with_flags; + pwm_get_state(pwm, &state); - of_node_get(pwmchip_parent(chip)->of_node); + return sysfs_emit(buf, "%d\n", state.enabled); } -static void of_pwmchip_remove(struct pwm_chip *chip) +static ssize_t enable_store(struct device *pwm_dev, + struct device_attribute *attr, + const char *buf, size_t size) { - if (pwmchip_parent(chip)) - of_node_put(pwmchip_parent(chip)->of_node); + struct pwm_export *export = pwmexport_from_dev(pwm_dev); + struct pwm_device *pwm = export->pwm; + struct pwm_state state; + int val, ret; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + guard(mutex)(&export->lock); + + pwm_get_state(pwm, &state); + + switch (val) { + case 0: + state.enabled = false; + break; + case 1: + state.enabled = true; + break; + default: + return -EINVAL; + } + + ret = pwm_apply_might_sleep(pwm, &state); + + return ret ? : size; } -static bool pwm_ops_check(const struct pwm_chip *chip) +static ssize_t polarity_show(struct device *pwm_dev, + struct device_attribute *attr, + char *buf) { - const struct pwm_ops *ops = chip->ops; + const struct pwm_device *pwm = pwm_from_dev(pwm_dev); + const char *polarity = "unknown"; + struct pwm_state state; - if (!ops->apply) - return false; + pwm_get_state(pwm, &state); - if (IS_ENABLED(CONFIG_PWM_DEBUG) && !ops->get_state) - dev_warn(pwmchip_parent(chip), - "Please implement the .get_state() callback\n"); + switch (state.polarity) { + case PWM_POLARITY_NORMAL: + polarity = "normal"; + break; - return true; + case PWM_POLARITY_INVERSED: + polarity = "inversed"; + break; + } + + return sysfs_emit(buf, "%s\n", polarity); } -/** - * __pwmchip_add() - register a new PWM chip - * @chip: the PWM chip to add - * @owner: reference to the module providing the chip. - * - * Register a new PWM chip. @owner is supposed to be THIS_MODULE, use the - * pwmchip_add wrapper to do this right. - * - * Returns: 0 on success or a negative error code on failure. - */ -int __pwmchip_add(struct pwm_chip *chip, struct module *owner) +static ssize_t polarity_store(struct device *pwm_dev, + struct device_attribute *attr, + const char *buf, size_t size) { - unsigned int i; + struct pwm_export *export = pwmexport_from_dev(pwm_dev); + struct pwm_device *pwm = export->pwm; + enum pwm_polarity polarity; + struct pwm_state state; int ret; - if (!chip || !pwmchip_parent(chip) || !chip->ops || !chip->npwm) + if (sysfs_streq(buf, "normal")) + polarity = PWM_POLARITY_NORMAL; + else if (sysfs_streq(buf, "inversed")) + polarity = PWM_POLARITY_INVERSED; + else return -EINVAL; - if (!pwm_ops_check(chip)) - return -EINVAL; + guard(mutex)(&export->lock); - chip->owner = owner; + pwm_get_state(pwm, &state); + state.polarity = polarity; + ret = pwm_apply_might_sleep(pwm, &state); + + return ret ? : size; +} + +static ssize_t capture_show(struct device *pwm_dev, + struct device_attribute *attr, + char *buf) +{ + struct pwm_device *pwm = pwm_from_dev(pwm_dev); + struct pwm_capture result; + int ret; + + ret = pwm_capture(pwm, &result, jiffies_to_msecs(HZ)); + if (ret) + return ret; + + return sysfs_emit(buf, "%u %u\n", result.period, result.duty_cycle); +} + +static DEVICE_ATTR_RW(period); +static DEVICE_ATTR_RW(duty_cycle); +static DEVICE_ATTR_RW(enable); +static DEVICE_ATTR_RW(polarity); +static DEVICE_ATTR_RO(capture); + +static struct attribute *pwm_attrs[] = { + &dev_attr_period.attr, + &dev_attr_duty_cycle.attr, + &dev_attr_enable.attr, + &dev_attr_polarity.attr, + &dev_attr_capture.attr, + NULL +}; +ATTRIBUTE_GROUPS(pwm); + +static void pwm_export_release(struct device *pwm_dev) +{ + struct pwm_export *export = pwmexport_from_dev(pwm_dev); + + kfree(export); +} + +static int pwm_export_child(struct device *pwmchip_dev, struct pwm_device *pwm) +{ + struct pwm_export *export; + char *pwm_prop[2]; + int ret; + + if (test_and_set_bit(PWMF_EXPORTED, &pwm->flags)) + return -EBUSY; - chip->pwms = kcalloc(chip->npwm, sizeof(*chip->pwms), GFP_KERNEL); - if (!chip->pwms) + export = kzalloc(sizeof(*export), GFP_KERNEL); + if (!export) { + clear_bit(PWMF_EXPORTED, &pwm->flags); return -ENOMEM; + } - mutex_lock(&pwm_lock); + export->pwm = pwm; + mutex_init(&export->lock); - ret = idr_alloc(&pwm_chips, chip, 0, 0, GFP_KERNEL); - if (ret < 0) { - mutex_unlock(&pwm_lock); - kfree(chip->pwms); + export->pwm_dev.release = pwm_export_release; + export->pwm_dev.parent = pwmchip_dev; + export->pwm_dev.devt = MKDEV(0, 0); + export->pwm_dev.groups = pwm_groups; + dev_set_name(&export->pwm_dev, "pwm%u", pwm->hwpwm); + + ret = device_register(&export->pwm_dev); + if (ret) { + clear_bit(PWMF_EXPORTED, &pwm->flags); + put_device(&export->pwm_dev); + export = NULL; return ret; } + pwm_prop[0] = kasprintf(GFP_KERNEL, "EXPORT=pwm%u", pwm->hwpwm); + pwm_prop[1] = NULL; + kobject_uevent_env(&pwmchip_dev->kobj, KOBJ_CHANGE, pwm_prop); + kfree(pwm_prop[0]); - chip->id = ret; + return 0; +} + +static int pwm_unexport_match(struct device *pwm_dev, const void *data) +{ + return pwm_from_dev(pwm_dev) == data; +} + +static int pwm_unexport_child(struct device *pwmchip_dev, struct pwm_device *pwm) +{ + struct device *pwm_dev; + char *pwm_prop[2]; + + if (!test_and_clear_bit(PWMF_EXPORTED, &pwm->flags)) + return -ENODEV; + + pwm_dev = device_find_child(pwmchip_dev, pwm, pwm_unexport_match); + if (!pwm_dev) + return -ENODEV; + + pwm_prop[0] = kasprintf(GFP_KERNEL, "UNEXPORT=pwm%u", pwm->hwpwm); + pwm_prop[1] = NULL; + kobject_uevent_env(&pwmchip_dev->kobj, KOBJ_CHANGE, pwm_prop); + kfree(pwm_prop[0]); + + /* for device_find_child() */ + put_device(pwm_dev); + device_unregister(pwm_dev); + pwm_put(pwm); + + return 0; +} + +static ssize_t export_store(struct device *pwmchip_dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev); + struct pwm_device *pwm; + unsigned int hwpwm; + int ret; + + ret = kstrtouint(buf, 0, &hwpwm); + if (ret < 0) + return ret; + + if (hwpwm >= chip->npwm) + return -ENODEV; + + pwm = pwm_request_from_chip(chip, hwpwm, "sysfs"); + if (IS_ERR(pwm)) + return PTR_ERR(pwm); + + ret = pwm_export_child(pwmchip_dev, pwm); + if (ret < 0) + pwm_put(pwm); + + return ret ? : len; +} +static DEVICE_ATTR_WO(export); + +static ssize_t unexport_store(struct device *pwmchip_dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev); + unsigned int hwpwm; + int ret; + + ret = kstrtouint(buf, 0, &hwpwm); + if (ret < 0) + return ret; + + if (hwpwm >= chip->npwm) + return -ENODEV; + + ret = pwm_unexport_child(pwmchip_dev, &chip->pwms[hwpwm]); + + return ret ? : len; +} +static DEVICE_ATTR_WO(unexport); + +static ssize_t npwm_show(struct device *pwmchip_dev, struct device_attribute *attr, + char *buf) +{ + const struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev); + + return sysfs_emit(buf, "%u\n", chip->npwm); +} +static DEVICE_ATTR_RO(npwm); + +static struct attribute *pwm_chip_attrs[] = { + &dev_attr_export.attr, + &dev_attr_unexport.attr, + &dev_attr_npwm.attr, + NULL, +}; +ATTRIBUTE_GROUPS(pwm_chip); + +/* takes export->lock on success */ +static struct pwm_export *pwm_class_get_state(struct device *pwmchip_dev, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct device *pwm_dev; + struct pwm_export *export; + + if (!test_bit(PWMF_EXPORTED, &pwm->flags)) + return NULL; + + pwm_dev = device_find_child(pwmchip_dev, pwm, pwm_unexport_match); + if (!pwm_dev) + return NULL; + + export = pwmexport_from_dev(pwm_dev); + put_device(pwm_dev); /* for device_find_child() */ + + mutex_lock(&export->lock); + pwm_get_state(pwm, state); + + return export; +} + +static int pwm_class_apply_state(struct pwm_export *export, + struct pwm_device *pwm, + struct pwm_state *state) +{ + int ret = pwm_apply_might_sleep(pwm, state); + + /* release lock taken in pwm_class_get_state */ + mutex_unlock(&export->lock); + + return ret; +} + +static int pwm_class_resume_npwm(struct device *pwmchip_dev, unsigned int npwm) +{ + struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev); + unsigned int i; + int ret = 0; + + for (i = 0; i < npwm; i++) { + struct pwm_device *pwm = &chip->pwms[i]; + struct pwm_state state; + struct pwm_export *export; + + export = pwm_class_get_state(pwmchip_dev, pwm, &state); + if (!export) + continue; + + /* If pwmchip was not enabled before suspend, do nothing. */ + if (!export->suspend.enabled) { + /* release lock taken in pwm_class_get_state */ + mutex_unlock(&export->lock); + continue; + } + + state.enabled = export->suspend.enabled; + ret = pwm_class_apply_state(export, pwm, &state); + if (ret < 0) + break; + } + + return ret; +} + +static int pwm_class_suspend(struct device *pwmchip_dev) +{ + struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev); + unsigned int i; + int ret = 0; for (i = 0; i < chip->npwm; i++) { struct pwm_device *pwm = &chip->pwms[i]; + struct pwm_state state; + struct pwm_export *export; - pwm->chip = chip; - pwm->hwpwm = i; + export = pwm_class_get_state(pwmchip_dev, pwm, &state); + if (!export) + continue; + + /* + * If pwmchip was not enabled before suspend, save + * state for resume time and do nothing else. + */ + export->suspend = state; + if (!state.enabled) { + /* release lock taken in pwm_class_get_state */ + mutex_unlock(&export->lock); + continue; + } + + state.enabled = false; + ret = pwm_class_apply_state(export, pwm, &state); + if (ret < 0) { + /* + * roll back the PWM devices that were disabled by + * this suspend function. + */ + pwm_class_resume_npwm(pwmchip_dev, i); + break; + } } - mutex_unlock(&pwm_lock); + return ret; +} - if (IS_ENABLED(CONFIG_OF)) - of_pwmchip_add(chip); +static int pwm_class_resume(struct device *pwmchip_dev) +{ + struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev); - pwmchip_sysfs_export(chip); + return pwm_class_resume_npwm(pwmchip_dev, chip->npwm); +} - return 0; +static DEFINE_SIMPLE_DEV_PM_OPS(pwm_class_pm_ops, pwm_class_suspend, pwm_class_resume); + +static struct class pwm_class = { + .name = "pwm", + .dev_groups = pwm_chip_groups, + .pm = pm_sleep_ptr(&pwm_class_pm_ops), +}; + +static void pwmchip_sysfs_unexport(struct pwm_chip *chip) +{ + unsigned int i; + + for (i = 0; i < chip->npwm; i++) { + struct pwm_device *pwm = &chip->pwms[i]; + + if (test_bit(PWMF_EXPORTED, &pwm->flags)) + pwm_unexport_child(&chip->dev, pwm); + } } -EXPORT_SYMBOL_GPL(__pwmchip_add); -/** - * pwmchip_remove() - remove a PWM chip - * @chip: the PWM chip to remove - * - * Removes a PWM chip. - */ -void pwmchip_remove(struct pwm_chip *chip) +#define PWMCHIP_ALIGN ARCH_DMA_MINALIGN + +static void *pwmchip_priv(struct pwm_chip *chip) { - pwmchip_sysfs_unexport(chip); + return (void *)chip + ALIGN(struct_size(chip, pwms, chip->npwm), PWMCHIP_ALIGN); +} - if (IS_ENABLED(CONFIG_OF)) - of_pwmchip_remove(chip); +/* This is the counterpart to pwmchip_alloc() */ +void pwmchip_put(struct pwm_chip *chip) +{ + put_device(&chip->dev); +} +EXPORT_SYMBOL_GPL(pwmchip_put); - mutex_lock(&pwm_lock); +static void pwmchip_release(struct device *pwmchip_dev) +{ + struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev); - idr_remove(&pwm_chips, chip->id); + kfree(chip); +} - mutex_unlock(&pwm_lock); +struct pwm_chip *pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv) +{ + struct pwm_chip *chip; + struct device *pwmchip_dev; + size_t alloc_size; + unsigned int i; + + alloc_size = size_add(ALIGN(struct_size(chip, pwms, npwm), PWMCHIP_ALIGN), + sizeof_priv); + + chip = kzalloc(alloc_size, GFP_KERNEL); + if (!chip) + return ERR_PTR(-ENOMEM); + + chip->npwm = npwm; + chip->uses_pwmchip_alloc = true; + chip->operational = false; + + pwmchip_dev = &chip->dev; + device_initialize(pwmchip_dev); + pwmchip_dev->class = &pwm_class; + pwmchip_dev->parent = parent; + pwmchip_dev->release = pwmchip_release; + + pwmchip_set_drvdata(chip, pwmchip_priv(chip)); + + for (i = 0; i < chip->npwm; i++) { + struct pwm_device *pwm = &chip->pwms[i]; + pwm->chip = chip; + pwm->hwpwm = i; + } - kfree(chip->pwms); + return chip; } -EXPORT_SYMBOL_GPL(pwmchip_remove); +EXPORT_SYMBOL_GPL(pwmchip_alloc); -static void devm_pwmchip_remove(void *data) +static void devm_pwmchip_put(void *data) { struct pwm_chip *chip = data; - pwmchip_remove(chip); + pwmchip_put(chip); } -int __devm_pwmchip_add(struct device *dev, struct pwm_chip *chip, struct module *owner) +struct pwm_chip *devm_pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv) { + struct pwm_chip *chip; int ret; - ret = __pwmchip_add(chip, owner); + chip = pwmchip_alloc(parent, npwm, sizeof_priv); + if (IS_ERR(chip)) + return chip; + + ret = devm_add_action_or_reset(parent, devm_pwmchip_put, chip); if (ret) - return ret; + return ERR_PTR(ret); - return devm_add_action_or_reset(dev, devm_pwmchip_remove, chip); + return chip; +} +EXPORT_SYMBOL_GPL(devm_pwmchip_alloc); + +static void of_pwmchip_add(struct pwm_chip *chip) +{ + if (!pwmchip_parent(chip) || !pwmchip_parent(chip)->of_node) + return; + + if (!chip->of_xlate) + chip->of_xlate = of_pwm_xlate_with_flags; + + of_node_get(pwmchip_parent(chip)->of_node); +} + +static void of_pwmchip_remove(struct pwm_chip *chip) +{ + if (pwmchip_parent(chip)) + of_node_put(pwmchip_parent(chip)->of_node); +} + +static bool pwm_ops_check(const struct pwm_chip *chip) +{ + const struct pwm_ops *ops = chip->ops; + + if (ops->write_waveform) { + if (!ops->round_waveform_tohw || + !ops->round_waveform_fromhw || + !ops->write_waveform) + return false; + + if (WFHWSIZE < ops->sizeof_wfhw) { + dev_warn(pwmchip_parent(chip), "WFHWSIZE < %zu\n", ops->sizeof_wfhw); + return false; + } + } else { + if (!ops->apply) + return false; + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && !ops->get_state) + dev_warn(pwmchip_parent(chip), + "Please implement the .get_state() callback\n"); + } + + return true; } -EXPORT_SYMBOL_GPL(__devm_pwmchip_add); static struct device_link *pwm_device_link_add(struct device *dev, struct pwm_device *pwm) @@ -672,15 +1739,11 @@ static struct pwm_chip *fwnode_to_pwmchip(struct fwnode_handle *fwnode) struct pwm_chip *chip; unsigned long id, tmp; - mutex_lock(&pwm_lock); + guard(mutex)(&pwm_lock); idr_for_each_entry_ul(&pwm_chips, chip, tmp, id) - if (pwmchip_parent(chip) && device_match_fwnode(pwmchip_parent(chip), fwnode)) { - mutex_unlock(&pwm_lock); + if (pwmchip_parent(chip) && device_match_fwnode(pwmchip_parent(chip), fwnode)) return chip; - } - - mutex_unlock(&pwm_lock); return ERR_PTR(-EPROBE_DEFER); } @@ -721,8 +1784,7 @@ static struct pwm_device *of_pwm_get(struct device *dev, struct device_node *np, return ERR_PTR(index); } - err = of_parse_phandle_with_args(np, "pwms", "#pwm-cells", index, - &args); + err = of_parse_phandle_with_args_map(np, "pwms", "pwm", index, &args); if (err) { pr_err("%s(): can't parse \"pwms\" property\n", __func__); return ERR_PTR(err); @@ -823,40 +1885,6 @@ static DEFINE_MUTEX(pwm_lookup_lock); static LIST_HEAD(pwm_lookup_list); /** - * pwm_add_table() - register PWM device consumers - * @table: array of consumers to register - * @num: number of consumers in table - */ -void pwm_add_table(struct pwm_lookup *table, size_t num) -{ - mutex_lock(&pwm_lookup_lock); - - while (num--) { - list_add_tail(&table->list, &pwm_lookup_list); - table++; - } - - mutex_unlock(&pwm_lookup_lock); -} - -/** - * pwm_remove_table() - unregister PWM device consumers - * @table: array of consumers to unregister - * @num: number of consumers in table - */ -void pwm_remove_table(struct pwm_lookup *table, size_t num) -{ - mutex_lock(&pwm_lookup_lock); - - while (num--) { - list_del(&table->list); - table++; - } - - mutex_unlock(&pwm_lookup_lock); -} - -/** * pwm_get() - look up and request a PWM device * @dev: device for PWM consumer * @con_id: consumer name @@ -914,36 +1942,33 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id) * Then we take the most specific entry - with the following order * of precedence: dev+con > dev only > con only. */ - mutex_lock(&pwm_lookup_lock); - - list_for_each_entry(p, &pwm_lookup_list, list) { - match = 0; + scoped_guard(mutex, &pwm_lookup_lock) + list_for_each_entry(p, &pwm_lookup_list, list) { + match = 0; - if (p->dev_id) { - if (!dev_id || strcmp(p->dev_id, dev_id)) - continue; + if (p->dev_id) { + if (!dev_id || strcmp(p->dev_id, dev_id)) + continue; - match += 2; - } + match += 2; + } - if (p->con_id) { - if (!con_id || strcmp(p->con_id, con_id)) - continue; + if (p->con_id) { + if (!con_id || strcmp(p->con_id, con_id)) + continue; - match += 1; - } + match += 1; + } - if (match > best) { - chosen = p; + if (match > best) { + chosen = p; - if (match != 3) - best = match; - else - break; + if (match != 3) + best = match; + else + break; + } } - } - - mutex_unlock(&pwm_lookup_lock); if (!chosen) return ERR_PTR(-ENODEV); @@ -988,24 +2013,33 @@ EXPORT_SYMBOL_GPL(pwm_get); */ void pwm_put(struct pwm_device *pwm) { + struct pwm_chip *chip; + if (!pwm) return; - mutex_lock(&pwm_lock); + chip = pwm->chip; - if (!test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) { + guard(mutex)(&pwm_lock); + + /* + * Trigger a warning if a consumer called pwm_put() twice. + * If the chip isn't operational, PWMF_REQUESTED was already cleared in + * pwmchip_remove(). So don't warn in this case. + */ + if (chip->operational && !test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) { pr_warn("PWM device already freed\n"); - goto out; + return; } - if (pwm->chip->ops->free) + if (chip->operational && chip->ops->free) pwm->chip->ops->free(pwm->chip, pwm); pwm->label = NULL; - module_put(pwm->chip->owner); -out: - mutex_unlock(&pwm_lock); + put_device(&chip->dev); + + module_put(chip->owner); } EXPORT_SYMBOL_GPL(pwm_put); @@ -1076,32 +2110,190 @@ struct pwm_device *devm_fwnode_pwm_get(struct device *dev, } EXPORT_SYMBOL_GPL(devm_fwnode_pwm_get); -#ifdef CONFIG_DEBUG_FS +/** + * __pwmchip_add() - register a new PWM chip + * @chip: the PWM chip to add + * @owner: reference to the module providing the chip. + * + * Register a new PWM chip. @owner is supposed to be THIS_MODULE, use the + * pwmchip_add wrapper to do this right. + * + * Returns: 0 on success or a negative error code on failure. + */ +int __pwmchip_add(struct pwm_chip *chip, struct module *owner) +{ + int ret; + + if (!chip || !pwmchip_parent(chip) || !chip->ops || !chip->npwm) + return -EINVAL; + + /* + * a struct pwm_chip must be allocated using (devm_)pwmchip_alloc, + * otherwise the embedded struct device might disappear too early + * resulting in memory corruption. + * Catch drivers that were not converted appropriately. + */ + if (!chip->uses_pwmchip_alloc) + return -EINVAL; + + if (!pwm_ops_check(chip)) + return -EINVAL; + + chip->owner = owner; + + if (chip->atomic) + spin_lock_init(&chip->atomic_lock); + else + mutex_init(&chip->nonatomic_lock); + + guard(mutex)(&pwm_lock); + + ret = idr_alloc(&pwm_chips, chip, 0, 0, GFP_KERNEL); + if (ret < 0) + return ret; + + chip->id = ret; + + dev_set_name(&chip->dev, "pwmchip%u", chip->id); + + if (IS_ENABLED(CONFIG_OF)) + of_pwmchip_add(chip); + + scoped_guard(pwmchip, chip) + chip->operational = true; + + ret = device_add(&chip->dev); + if (ret) + goto err_device_add; + + return 0; + +err_device_add: + scoped_guard(pwmchip, chip) + chip->operational = false; + + if (IS_ENABLED(CONFIG_OF)) + of_pwmchip_remove(chip); + + idr_remove(&pwm_chips, chip->id); + + return ret; +} +EXPORT_SYMBOL_GPL(__pwmchip_add); + +/** + * pwmchip_remove() - remove a PWM chip + * @chip: the PWM chip to remove + * + * Removes a PWM chip. + */ +void pwmchip_remove(struct pwm_chip *chip) +{ + pwmchip_sysfs_unexport(chip); + + scoped_guard(mutex, &pwm_lock) { + unsigned int i; + + scoped_guard(pwmchip, chip) + chip->operational = false; + + for (i = 0; i < chip->npwm; ++i) { + struct pwm_device *pwm = &chip->pwms[i]; + + if (test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) { + dev_warn(&chip->dev, "Freeing requested PWM #%u\n", i); + if (pwm->chip->ops->free) + pwm->chip->ops->free(pwm->chip, pwm); + } + } + + if (IS_ENABLED(CONFIG_OF)) + of_pwmchip_remove(chip); + + idr_remove(&pwm_chips, chip->id); + } + + device_del(&chip->dev); +} +EXPORT_SYMBOL_GPL(pwmchip_remove); + +static void devm_pwmchip_remove(void *data) +{ + struct pwm_chip *chip = data; + + pwmchip_remove(chip); +} + +int __devm_pwmchip_add(struct device *dev, struct pwm_chip *chip, struct module *owner) +{ + int ret; + + ret = __pwmchip_add(chip, owner); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, devm_pwmchip_remove, chip); +} +EXPORT_SYMBOL_GPL(__devm_pwmchip_add); + +/** + * pwm_add_table() - register PWM device consumers + * @table: array of consumers to register + * @num: number of consumers in table + */ +void pwm_add_table(struct pwm_lookup *table, size_t num) +{ + guard(mutex)(&pwm_lookup_lock); + + while (num--) { + list_add_tail(&table->list, &pwm_lookup_list); + table++; + } +} + +/** + * pwm_remove_table() - unregister PWM device consumers + * @table: array of consumers to unregister + * @num: number of consumers in table + */ +void pwm_remove_table(struct pwm_lookup *table, size_t num) +{ + guard(mutex)(&pwm_lookup_lock); + + while (num--) { + list_del(&table->list); + table++; + } +} + static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s) { unsigned int i; for (i = 0; i < chip->npwm; i++) { struct pwm_device *pwm = &chip->pwms[i]; - struct pwm_state state; + struct pwm_state state, hwstate; pwm_get_state(pwm, &state); + pwm_get_state_hw(pwm, &hwstate); seq_printf(s, " pwm-%-3d (%-20.20s):", i, pwm->label); if (test_bit(PWMF_REQUESTED, &pwm->flags)) seq_puts(s, " requested"); - if (state.enabled) - seq_puts(s, " enabled"); + seq_puts(s, "\n"); - seq_printf(s, " period: %llu ns", state.period); - seq_printf(s, " duty: %llu ns", state.duty_cycle); - seq_printf(s, " polarity: %s", + seq_printf(s, " requested configuration: %3sabled, %llu/%llu ns, %s polarity", + state.enabled ? "en" : "dis", state.duty_cycle, state.period, state.polarity ? "inverse" : "normal"); - if (state.usage_power) - seq_puts(s, " usage_power"); + seq_puts(s, ", usage_power"); + seq_puts(s, "\n"); + + seq_printf(s, " actual configuration: %3sabled, %llu/%llu ns, %s polarity", + hwstate.enabled ? "en" : "dis", hwstate.duty_cycle, hwstate.period, + hwstate.polarity ? "inverse" : "normal"); seq_puts(s, "\n"); } @@ -1161,11 +2353,19 @@ static const struct seq_operations pwm_debugfs_sops = { DEFINE_SEQ_ATTRIBUTE(pwm_debugfs); -static int __init pwm_debugfs_init(void) +static int __init pwm_init(void) { - debugfs_create_file("pwm", 0444, NULL, NULL, &pwm_debugfs_fops); + int ret; + + ret = class_register(&pwm_class); + if (ret) { + pr_err("Failed to initialize PWM class (%pe)\n", ERR_PTR(ret)); + return ret; + } + + if (IS_ENABLED(CONFIG_DEBUG_FS)) + debugfs_create_file("pwm", 0444, NULL, NULL, &pwm_debugfs_fops); return 0; } -subsys_initcall(pwm_debugfs_init); -#endif /* CONFIG_DEBUG_FS */ +subsys_initcall(pwm_init); diff --git a/drivers/pwm/pwm-adp5585.c b/drivers/pwm/pwm-adp5585.c new file mode 100644 index 000000000000..d79106d12181 --- /dev/null +++ b/drivers/pwm/pwm-adp5585.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices ADP5585 PWM driver + * + * Copyright 2022 NXP + * Copyright 2024 Ideas on Board Oy + * + * Limitations: + * - The .apply() operation executes atomically, but may not wait for the + * period to complete (this is not documented and would need to be tested). + * - Disabling the PWM drives the output pin to a low level immediately. + * - The hardware can only generate normal polarity output. + */ + +#include <asm/byteorder.h> + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/math64.h> +#include <linux/mfd/adp5585.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> +#include <linux/time.h> +#include <linux/types.h> + +#define ADP5585_PWM_CHAN_NUM 1 + +#define ADP5585_PWM_OSC_FREQ_HZ 1000000U +#define ADP5585_PWM_MIN_PERIOD_NS (2ULL * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) +#define ADP5585_PWM_MAX_PERIOD_NS (2ULL * 0xffff * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) + +static int pwm_adp5585_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct regmap *regmap = pwmchip_get_drvdata(chip); + + /* Configure the R3 pin as PWM output. */ + return regmap_update_bits(regmap, ADP5585_PIN_CONFIG_C, + ADP5585_R3_EXTEND_CFG_MASK, + ADP5585_R3_EXTEND_CFG_PWM_OUT); +} + +static void pwm_adp5585_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct regmap *regmap = pwmchip_get_drvdata(chip); + + regmap_update_bits(regmap, ADP5585_PIN_CONFIG_C, + ADP5585_R3_EXTEND_CFG_MASK, + ADP5585_R3_EXTEND_CFG_GPIO4); +} + +static int pwm_adp5585_apply(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct regmap *regmap = pwmchip_get_drvdata(chip); + u64 period, duty_cycle; + u32 on, off; + __le16 val; + int ret; + + if (!state->enabled) { + regmap_clear_bits(regmap, ADP5585_GENERAL_CFG, ADP5585_OSC_EN); + regmap_clear_bits(regmap, ADP5585_PWM_CFG, ADP5585_PWM_EN); + return 0; + } + + if (state->polarity != PWM_POLARITY_NORMAL) + return -EINVAL; + + if (state->period < ADP5585_PWM_MIN_PERIOD_NS) + return -EINVAL; + + period = min(state->period, ADP5585_PWM_MAX_PERIOD_NS); + duty_cycle = min(state->duty_cycle, period); + + /* + * Compute the on and off time. As the internal oscillator frequency is + * 1MHz, the calculation can be simplified without loss of precision. + */ + on = div_u64(duty_cycle, NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ); + off = div_u64(period, NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) - on; + + val = cpu_to_le16(off); + ret = regmap_bulk_write(regmap, ADP5585_PWM_OFFT_LOW, &val, 2); + if (ret) + return ret; + + val = cpu_to_le16(on); + ret = regmap_bulk_write(regmap, ADP5585_PWM_ONT_LOW, &val, 2); + if (ret) + return ret; + + /* Enable PWM in continuous mode and no external AND'ing. */ + ret = regmap_update_bits(regmap, ADP5585_PWM_CFG, + ADP5585_PWM_IN_AND | ADP5585_PWM_MODE | + ADP5585_PWM_EN, ADP5585_PWM_EN); + if (ret) + return ret; + + ret = regmap_set_bits(regmap, ADP5585_GENERAL_CFG, ADP5585_OSC_EN); + if (ret) + return ret; + + return regmap_set_bits(regmap, ADP5585_PWM_CFG, ADP5585_PWM_EN); +} + +static int pwm_adp5585_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct regmap *regmap = pwmchip_get_drvdata(chip); + unsigned int on, off; + unsigned int val; + __le16 on_off; + int ret; + + ret = regmap_bulk_read(regmap, ADP5585_PWM_OFFT_LOW, &on_off, 2); + if (ret) + return ret; + off = le16_to_cpu(on_off); + + ret = regmap_bulk_read(regmap, ADP5585_PWM_ONT_LOW, &on_off, 2); + if (ret) + return ret; + on = le16_to_cpu(on_off); + + state->duty_cycle = on * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ); + state->period = (on + off) * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ); + + state->polarity = PWM_POLARITY_NORMAL; + + regmap_read(regmap, ADP5585_PWM_CFG, &val); + state->enabled = !!(val & ADP5585_PWM_EN); + + return 0; +} + +static const struct pwm_ops adp5585_pwm_ops = { + .request = pwm_adp5585_request, + .free = pwm_adp5585_free, + .apply = pwm_adp5585_apply, + .get_state = pwm_adp5585_get_state, +}; + +static int adp5585_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct adp5585_dev *adp5585 = dev_get_drvdata(dev->parent); + struct pwm_chip *chip; + int ret; + + chip = devm_pwmchip_alloc(dev, ADP5585_PWM_CHAN_NUM, 0); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + device_set_of_node_from_dev(dev, dev->parent); + + pwmchip_set_drvdata(chip, adp5585->regmap); + chip->ops = &adp5585_pwm_ops; + + ret = devm_pwmchip_add(dev, chip); + if (ret) + return dev_err_probe(dev, ret, "failed to add PWM chip\n"); + + return 0; +} + +static const struct platform_device_id adp5585_pwm_id_table[] = { + { "adp5585-pwm" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, adp5585_pwm_id_table); + +static struct platform_driver adp5585_pwm_driver = { + .driver = { + .name = "adp5585-pwm", + }, + .probe = adp5585_pwm_probe, + .id_table = adp5585_pwm_id_table, +}; +module_platform_driver(adp5585_pwm_driver); + +MODULE_AUTHOR("Xiaoning Wang <xiaoning.wang@nxp.com>"); +MODULE_DESCRIPTION("ADP5585 PWM Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-atmel-hlcdc.c b/drivers/pwm/pwm-atmel-hlcdc.c index 2afb302be02c..387a0d1fa4f2 100644 --- a/drivers/pwm/pwm-atmel-hlcdc.c +++ b/drivers/pwm/pwm-atmel-hlcdc.c @@ -234,7 +234,7 @@ static const struct of_device_id atmel_hlcdc_dt_ids[] = { .data = &atmel_hlcdc_pwm_sama5d3_errata, }, { .compatible = "microchip,sam9x60-hlcdc", }, - { /* sentinel */ }, + { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, atmel_hlcdc_dt_ids); @@ -288,8 +288,9 @@ static void atmel_hlcdc_pwm_remove(struct platform_device *pdev) static const struct of_device_id atmel_hlcdc_pwm_dt_ids[] = { { .compatible = "atmel,hlcdc-pwm" }, - { /* sentinel */ }, + { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, atmel_hlcdc_pwm_dt_ids); static struct platform_driver atmel_hlcdc_pwm_driver = { .driver = { @@ -298,7 +299,7 @@ static struct platform_driver atmel_hlcdc_pwm_driver = { .pm = pm_ptr(&atmel_hlcdc_pwm_pm_ops), }, .probe = atmel_hlcdc_pwm_probe, - .remove_new = atmel_hlcdc_pwm_remove, + .remove = atmel_hlcdc_pwm_remove, }; module_platform_driver(atmel_hlcdc_pwm_driver); diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c index 528e54c5999d..f9ff78ba122d 100644 --- a/drivers/pwm/pwm-atmel-tcb.c +++ b/drivers/pwm/pwm-atmel-tcb.c @@ -81,7 +81,8 @@ static int atmel_tcb_pwm_request(struct pwm_chip *chip, tcbpwm->period = 0; tcbpwm->div = 0; - spin_lock(&tcbpwmc->lock); + guard(spinlock)(&tcbpwmc->lock); + regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), &cmr); /* * Get init config from Timer Counter registers if @@ -107,7 +108,6 @@ static int atmel_tcb_pwm_request(struct pwm_chip *chip, cmr |= ATMEL_TC_WAVE | ATMEL_TC_WAVESEL_UP_AUTO | ATMEL_TC_EEVT_XC0; regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), cmr); - spin_unlock(&tcbpwmc->lock); return 0; } @@ -137,7 +137,6 @@ static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm, if (tcbpwm->duty == 0) polarity = !polarity; - spin_lock(&tcbpwmc->lock); regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), &cmr); /* flush old setting and set the new one */ @@ -172,8 +171,6 @@ static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm, ATMEL_TC_SWTRG); tcbpwmc->bkup.enabled = 0; } - - spin_unlock(&tcbpwmc->lock); } static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm, @@ -194,7 +191,6 @@ static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm, if (tcbpwm->duty == 0) polarity = !polarity; - spin_lock(&tcbpwmc->lock); regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), &cmr); /* flush old setting and set the new one */ @@ -256,7 +252,6 @@ static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm, regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CCR), ATMEL_TC_SWTRG | ATMEL_TC_CLKEN); tcbpwmc->bkup.enabled = 1; - spin_unlock(&tcbpwmc->lock); return 0; } @@ -265,7 +260,8 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, { struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); struct atmel_tcb_pwm_device *tcbpwm = &tcbpwmc->pwms[pwm->hwpwm]; - struct atmel_tcb_pwm_device *atcbpwm = NULL; + /* companion PWM sharing register values period and div */ + struct atmel_tcb_pwm_device *atcbpwm = &tcbpwmc->pwms[pwm->hwpwm ^ 1]; int i = 0; int slowclk = 0; unsigned period; @@ -310,11 +306,6 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, duty = div_u64(duty_ns, min); period = div_u64(period_ns, min); - if (pwm->hwpwm == 0) - atcbpwm = &tcbpwmc->pwms[1]; - else - atcbpwm = &tcbpwmc->pwms[0]; - /* * PWM devices provided by the TCB driver are grouped by 2. * PWM devices in a given group must be configured with the @@ -323,8 +314,7 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, * We're checking the period value of the second PWM device * in this group before applying the new config. */ - if ((atcbpwm && atcbpwm->duty > 0 && - atcbpwm->duty != atcbpwm->period) && + if ((atcbpwm->duty > 0 && atcbpwm->duty != atcbpwm->period) && (atcbpwm->div != i || atcbpwm->period != period)) { dev_err(pwmchip_parent(chip), "failed to configure period_ns: PWM group already configured with a different value\n"); @@ -341,16 +331,19 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, static int atmel_tcb_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { + struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); int duty_cycle, period; int ret; + guard(spinlock)(&tcbpwmc->lock); + if (!state->enabled) { atmel_tcb_pwm_disable(chip, pwm, state->polarity); return 0; } - period = state->period < INT_MAX ? state->period : INT_MAX; - duty_cycle = state->duty_cycle < INT_MAX ? state->duty_cycle : INT_MAX; + period = min(state->period, INT_MAX); + duty_cycle = min(state->duty_cycle, INT_MAX); ret = atmel_tcb_pwm_config(chip, pwm, duty_cycle, period); if (ret) @@ -389,17 +382,17 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev) { struct pwm_chip *chip; const struct of_device_id *match; - struct atmel_tcb_pwm_chip *tcbpwm; + struct atmel_tcb_pwm_chip *tcbpwmc; const struct atmel_tcb_config *config; struct device_node *np = pdev->dev.of_node; char clk_name[] = "t0_clk"; int err; int channel; - chip = devm_pwmchip_alloc(&pdev->dev, NPWM, sizeof(*tcbpwm)); + chip = devm_pwmchip_alloc(&pdev->dev, NPWM, sizeof(*tcbpwmc)); if (IS_ERR(chip)) return PTR_ERR(chip); - tcbpwm = to_tcb_chip(chip); + tcbpwmc = to_tcb_chip(chip); err = of_property_read_u32(np, "reg", &channel); if (err < 0) { @@ -409,20 +402,20 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev) return err; } - tcbpwm->regmap = syscon_node_to_regmap(np->parent); - if (IS_ERR(tcbpwm->regmap)) - return PTR_ERR(tcbpwm->regmap); + tcbpwmc->regmap = syscon_node_to_regmap(np->parent); + if (IS_ERR(tcbpwmc->regmap)) + return PTR_ERR(tcbpwmc->regmap); - tcbpwm->slow_clk = of_clk_get_by_name(np->parent, "slow_clk"); - if (IS_ERR(tcbpwm->slow_clk)) - return PTR_ERR(tcbpwm->slow_clk); + tcbpwmc->slow_clk = of_clk_get_by_name(np->parent, "slow_clk"); + if (IS_ERR(tcbpwmc->slow_clk)) + return PTR_ERR(tcbpwmc->slow_clk); clk_name[1] += channel; - tcbpwm->clk = of_clk_get_by_name(np->parent, clk_name); - if (IS_ERR(tcbpwm->clk)) - tcbpwm->clk = of_clk_get_by_name(np->parent, "t0_clk"); - if (IS_ERR(tcbpwm->clk)) { - err = PTR_ERR(tcbpwm->clk); + tcbpwmc->clk = of_clk_get_by_name(np->parent, clk_name); + if (IS_ERR(tcbpwmc->clk)) + tcbpwmc->clk = of_clk_get_by_name(np->parent, "t0_clk"); + if (IS_ERR(tcbpwmc->clk)) { + err = PTR_ERR(tcbpwmc->clk); goto err_slow_clk; } @@ -430,22 +423,22 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev) config = match->data; if (config->has_gclk) { - tcbpwm->gclk = of_clk_get_by_name(np->parent, "gclk"); - if (IS_ERR(tcbpwm->gclk)) { - err = PTR_ERR(tcbpwm->gclk); + tcbpwmc->gclk = of_clk_get_by_name(np->parent, "gclk"); + if (IS_ERR(tcbpwmc->gclk)) { + err = PTR_ERR(tcbpwmc->gclk); goto err_clk; } } chip->ops = &atmel_tcb_pwm_ops; - tcbpwm->channel = channel; - tcbpwm->width = config->counter_width; + tcbpwmc->channel = channel; + tcbpwmc->width = config->counter_width; - err = clk_prepare_enable(tcbpwm->slow_clk); + err = clk_prepare_enable(tcbpwmc->slow_clk); if (err) goto err_gclk; - spin_lock_init(&tcbpwm->lock); + spin_lock_init(&tcbpwmc->lock); err = pwmchip_add(chip); if (err < 0) @@ -456,16 +449,16 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev) return 0; err_disable_clk: - clk_disable_unprepare(tcbpwm->slow_clk); + clk_disable_unprepare(tcbpwmc->slow_clk); err_gclk: - clk_put(tcbpwm->gclk); + clk_put(tcbpwmc->gclk); err_clk: - clk_put(tcbpwm->clk); + clk_put(tcbpwmc->clk); err_slow_clk: - clk_put(tcbpwm->slow_clk); + clk_put(tcbpwmc->slow_clk); return err; } @@ -473,14 +466,14 @@ err_slow_clk: static void atmel_tcb_pwm_remove(struct platform_device *pdev) { struct pwm_chip *chip = platform_get_drvdata(pdev); - struct atmel_tcb_pwm_chip *tcbpwm = to_tcb_chip(chip); + struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); pwmchip_remove(chip); - clk_disable_unprepare(tcbpwm->slow_clk); - clk_put(tcbpwm->gclk); - clk_put(tcbpwm->clk); - clk_put(tcbpwm->slow_clk); + clk_disable_unprepare(tcbpwmc->slow_clk); + clk_put(tcbpwmc->gclk); + clk_put(tcbpwmc->clk); + clk_put(tcbpwmc->slow_clk); } static const struct of_device_id atmel_tcb_pwm_dt_ids[] = { @@ -492,14 +485,14 @@ MODULE_DEVICE_TABLE(of, atmel_tcb_pwm_dt_ids); static int atmel_tcb_pwm_suspend(struct device *dev) { struct pwm_chip *chip = dev_get_drvdata(dev); - struct atmel_tcb_pwm_chip *tcbpwm = to_tcb_chip(chip); - struct atmel_tcb_channel *chan = &tcbpwm->bkup; - unsigned int channel = tcbpwm->channel; + struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); + struct atmel_tcb_channel *chan = &tcbpwmc->bkup; + unsigned int channel = tcbpwmc->channel; - regmap_read(tcbpwm->regmap, ATMEL_TC_REG(channel, CMR), &chan->cmr); - regmap_read(tcbpwm->regmap, ATMEL_TC_REG(channel, RA), &chan->ra); - regmap_read(tcbpwm->regmap, ATMEL_TC_REG(channel, RB), &chan->rb); - regmap_read(tcbpwm->regmap, ATMEL_TC_REG(channel, RC), &chan->rc); + regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(channel, CMR), &chan->cmr); + regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(channel, RA), &chan->ra); + regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(channel, RB), &chan->rb); + regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(channel, RC), &chan->rc); return 0; } @@ -507,17 +500,17 @@ static int atmel_tcb_pwm_suspend(struct device *dev) static int atmel_tcb_pwm_resume(struct device *dev) { struct pwm_chip *chip = dev_get_drvdata(dev); - struct atmel_tcb_pwm_chip *tcbpwm = to_tcb_chip(chip); - struct atmel_tcb_channel *chan = &tcbpwm->bkup; - unsigned int channel = tcbpwm->channel; + struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); + struct atmel_tcb_channel *chan = &tcbpwmc->bkup; + unsigned int channel = tcbpwmc->channel; - regmap_write(tcbpwm->regmap, ATMEL_TC_REG(channel, CMR), chan->cmr); - regmap_write(tcbpwm->regmap, ATMEL_TC_REG(channel, RA), chan->ra); - regmap_write(tcbpwm->regmap, ATMEL_TC_REG(channel, RB), chan->rb); - regmap_write(tcbpwm->regmap, ATMEL_TC_REG(channel, RC), chan->rc); + regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(channel, CMR), chan->cmr); + regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(channel, RA), chan->ra); + regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(channel, RB), chan->rb); + regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(channel, RC), chan->rc); if (chan->enabled) - regmap_write(tcbpwm->regmap, + regmap_write(tcbpwmc->regmap, ATMEL_TC_CLKEN | ATMEL_TC_SWTRG, ATMEL_TC_REG(channel, CCR)); @@ -534,7 +527,7 @@ static struct platform_driver atmel_tcb_pwm_driver = { .pm = pm_ptr(&atmel_tcb_pwm_pm_ops), }, .probe = atmel_tcb_pwm_probe, - .remove_new = atmel_tcb_pwm_remove, + .remove = atmel_tcb_pwm_remove, }; module_platform_driver(atmel_tcb_pwm_driver); diff --git a/drivers/pwm/pwm-axi-pwmgen.c b/drivers/pwm/pwm-axi-pwmgen.c new file mode 100644 index 000000000000..60dcd3542373 --- /dev/null +++ b/drivers/pwm/pwm-axi-pwmgen.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AXI PWM generator + * + * Copyright 2024 Analog Devices Inc. + * Copyright 2024 Baylibre SAS + * + * Device docs: https://analogdevicesinc.github.io/hdl/library/axi_pwm_gen/index.html + * + * Limitations: + * - The writes to registers for period and duty are shadowed until + * LOAD_CONFIG is written to AXI_PWMGEN_REG_RSTN, at which point + * they take effect. + * - Writing LOAD_CONFIG also has the effect of re-synchronizing all + * enabled channels, which could cause glitching on other channels. It + * is therefore expected that channels are assigned harmonic periods + * and all have a single user coordinating this. + * - Supports normal polarity. Does not support changing polarity. + * - On disable, the PWM output becomes low (inactive). + */ +#include <linux/bits.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/fpga/adi-axi-common.h> +#include <linux/io.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define AXI_PWMGEN_REG_ID 0x04 +#define AXI_PWMGEN_REG_SCRATCHPAD 0x08 +#define AXI_PWMGEN_REG_CORE_MAGIC 0x0C +#define AXI_PWMGEN_REG_RSTN 0x10 +#define AXI_PWMGEN_REG_RSTN_LOAD_CONFIG BIT(1) +#define AXI_PWMGEN_REG_RSTN_RESET BIT(0) +#define AXI_PWMGEN_REG_NPWM 0x14 +#define AXI_PWMGEN_REG_CONFIG 0x18 +#define AXI_PWMGEN_REG_CONFIG_FORCE_ALIGN BIT(1) +#define AXI_PWMGEN_CHX_PERIOD(ch) (0x40 + (4 * (ch))) +#define AXI_PWMGEN_CHX_DUTY(ch) (0x80 + (4 * (ch))) +#define AXI_PWMGEN_CHX_OFFSET(ch) (0xC0 + (4 * (ch))) +#define AXI_PWMGEN_REG_CORE_MAGIC_VAL 0x601A3471 /* Identification number to test during setup */ + +struct axi_pwmgen_ddata { + struct regmap *regmap; + unsigned long clk_rate_hz; +}; + +static const struct regmap_config axi_pwmgen_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xFC, +}; + +/* This represents a hardware configuration for one channel */ +struct axi_pwmgen_waveform { + u32 period_cnt; + u32 duty_cycle_cnt; + u32 duty_offset_cnt; +}; + +static struct axi_pwmgen_ddata *axi_pwmgen_ddata_from_chip(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_waveform *wf, + void *_wfhw) +{ + struct axi_pwmgen_waveform *wfhw = _wfhw; + struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip); + int ret = 0; + + if (wf->period_length_ns == 0) { + *wfhw = (struct axi_pwmgen_waveform){ + .period_cnt = 0, + .duty_cycle_cnt = 0, + .duty_offset_cnt = 0, + }; + } else { + /* With ddata->clk_rate_hz < NSEC_PER_SEC this won't overflow. */ + wfhw->period_cnt = min_t(u64, + mul_u64_u32_div(wf->period_length_ns, ddata->clk_rate_hz, NSEC_PER_SEC), + U32_MAX); + + if (wfhw->period_cnt == 0) { + /* + * The specified period is too short for the hardware. + * So round up .period_cnt to 1 (i.e. the smallest + * possible period). With .duty_cycle and .duty_offset + * being less than or equal to .period, their rounded + * value must be 0. + */ + wfhw->period_cnt = 1; + wfhw->duty_cycle_cnt = 0; + wfhw->duty_offset_cnt = 0; + ret = 1; + } else { + wfhw->duty_cycle_cnt = min_t(u64, + mul_u64_u32_div(wf->duty_length_ns, ddata->clk_rate_hz, NSEC_PER_SEC), + U32_MAX); + wfhw->duty_offset_cnt = min_t(u64, + mul_u64_u32_div(wf->duty_offset_ns, ddata->clk_rate_hz, NSEC_PER_SEC), + U32_MAX); + } + } + + dev_dbg(&chip->dev, "pwm#%u: %lld/%lld [+%lld] @%lu -> PERIOD: %08x, DUTY: %08x, OFFSET: %08x\n", + pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, + ddata->clk_rate_hz, wfhw->period_cnt, wfhw->duty_cycle_cnt, wfhw->duty_offset_cnt); + + return ret; +} + +static int axi_pwmgen_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm, + const void *_wfhw, struct pwm_waveform *wf) +{ + const struct axi_pwmgen_waveform *wfhw = _wfhw; + struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip); + + wf->period_length_ns = DIV64_U64_ROUND_UP((u64)wfhw->period_cnt * NSEC_PER_SEC, + ddata->clk_rate_hz); + + wf->duty_length_ns = DIV64_U64_ROUND_UP((u64)wfhw->duty_cycle_cnt * NSEC_PER_SEC, + ddata->clk_rate_hz); + + wf->duty_offset_ns = DIV64_U64_ROUND_UP((u64)wfhw->duty_offset_cnt * NSEC_PER_SEC, + ddata->clk_rate_hz); + + return 0; +} + +static int axi_pwmgen_write_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw) +{ + const struct axi_pwmgen_waveform *wfhw = _wfhw; + struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip); + struct regmap *regmap = ddata->regmap; + unsigned int ch = pwm->hwpwm; + int ret; + + ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), wfhw->period_cnt); + if (ret) + return ret; + + ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), wfhw->duty_cycle_cnt); + if (ret) + return ret; + + ret = regmap_write(regmap, AXI_PWMGEN_CHX_OFFSET(ch), wfhw->duty_offset_cnt); + if (ret) + return ret; + + return regmap_write(regmap, AXI_PWMGEN_REG_RSTN, AXI_PWMGEN_REG_RSTN_LOAD_CONFIG); +} + +static int axi_pwmgen_read_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + void *_wfhw) +{ + struct axi_pwmgen_waveform *wfhw = _wfhw; + struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip); + struct regmap *regmap = ddata->regmap; + unsigned int ch = pwm->hwpwm; + int ret; + + ret = regmap_read(regmap, AXI_PWMGEN_CHX_PERIOD(ch), &wfhw->period_cnt); + if (ret) + return ret; + + ret = regmap_read(regmap, AXI_PWMGEN_CHX_DUTY(ch), &wfhw->duty_cycle_cnt); + if (ret) + return ret; + + ret = regmap_read(regmap, AXI_PWMGEN_CHX_OFFSET(ch), &wfhw->duty_offset_cnt); + if (ret) + return ret; + + if (wfhw->duty_cycle_cnt > wfhw->period_cnt) + wfhw->duty_cycle_cnt = wfhw->period_cnt; + + /* XXX: is this the actual behaviour of the hardware? */ + if (wfhw->duty_offset_cnt >= wfhw->period_cnt) { + wfhw->duty_cycle_cnt = 0; + wfhw->duty_offset_cnt = 0; + } + + return 0; +} + +static const struct pwm_ops axi_pwmgen_pwm_ops = { + .sizeof_wfhw = sizeof(struct axi_pwmgen_waveform), + .round_waveform_tohw = axi_pwmgen_round_waveform_tohw, + .round_waveform_fromhw = axi_pwmgen_round_waveform_fromhw, + .read_waveform = axi_pwmgen_read_waveform, + .write_waveform = axi_pwmgen_write_waveform, +}; + +static int axi_pwmgen_setup(struct regmap *regmap, struct device *dev) +{ + int ret; + u32 val; + + ret = regmap_read(regmap, AXI_PWMGEN_REG_CORE_MAGIC, &val); + if (ret) + return ret; + + if (val != AXI_PWMGEN_REG_CORE_MAGIC_VAL) + return dev_err_probe(dev, -ENODEV, + "failed to read expected value from register: got %08x, expected %08x\n", + val, AXI_PWMGEN_REG_CORE_MAGIC_VAL); + + ret = regmap_read(regmap, ADI_AXI_REG_VERSION, &val); + if (ret) + return ret; + + if (ADI_AXI_PCORE_VER_MAJOR(val) != 2) { + return dev_err_probe(dev, -ENODEV, "Unsupported peripheral version %u.%u.%u\n", + ADI_AXI_PCORE_VER_MAJOR(val), + ADI_AXI_PCORE_VER_MINOR(val), + ADI_AXI_PCORE_VER_PATCH(val)); + } + + /* Enable the core */ + ret = regmap_clear_bits(regmap, AXI_PWMGEN_REG_RSTN, AXI_PWMGEN_REG_RSTN_RESET); + if (ret) + return ret; + + /* + * Enable force align so that changes to PWM period and duty cycle take + * effect immediately. Otherwise, the effect of the change is delayed + * until the period of all channels run out, which can be long after the + * apply function returns. + */ + ret = regmap_set_bits(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_REG_CONFIG_FORCE_ALIGN); + if (ret) + return ret; + + ret = regmap_read(regmap, AXI_PWMGEN_REG_NPWM, &val); + if (ret) + return ret; + + /* Return the number of PWMs */ + return val; +} + +static int axi_pwmgen_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct regmap *regmap; + struct pwm_chip *chip; + struct axi_pwmgen_ddata *ddata; + struct clk *axi_clk, *clk; + void __iomem *io_base; + int ret; + + io_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + regmap = devm_regmap_init_mmio(dev, io_base, &axi_pwmgen_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "failed to init register map\n"); + + ret = axi_pwmgen_setup(regmap, dev); + if (ret < 0) + return ret; + + chip = devm_pwmchip_alloc(dev, ret, sizeof(*ddata)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + ddata = pwmchip_get_drvdata(chip); + ddata->regmap = regmap; + + /* + * Using NULL here instead of "axi" for backwards compatibility. There + * are some dtbs that don't give clock-names and have the "ext" clock + * as the one and only clock (due to mistake in the original bindings). + */ + axi_clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(axi_clk)) + return dev_err_probe(dev, PTR_ERR(axi_clk), "failed to get axi clock\n"); + + clk = devm_clk_get_optional_enabled(dev, "ext"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "failed to get ext clock\n"); + + /* + * If there is no "ext" clock, it means the HDL was compiled with + * ASYNC_CLK_EN=0. In this case, the AXI clock is also used for the + * PWM output clock. + */ + if (!clk) + clk = axi_clk; + + ret = devm_clk_rate_exclusive_get(dev, clk); + if (ret) + return dev_err_probe(dev, ret, "failed to get exclusive rate\n"); + + ddata->clk_rate_hz = clk_get_rate(clk); + if (!ddata->clk_rate_hz || ddata->clk_rate_hz > NSEC_PER_SEC) + return dev_err_probe(dev, -EINVAL, + "Invalid clock rate: %lu\n", ddata->clk_rate_hz); + + chip->ops = &axi_pwmgen_pwm_ops; + chip->atomic = true; + + ret = devm_pwmchip_add(dev, chip); + if (ret) + return dev_err_probe(dev, ret, "could not add PWM chip\n"); + + return 0; +} + +static const struct of_device_id axi_pwmgen_ids[] = { + { .compatible = "adi,axi-pwmgen-2.00.a" }, + { } +}; +MODULE_DEVICE_TABLE(of, axi_pwmgen_ids); + +static struct platform_driver axi_pwmgen_driver = { + .driver = { + .name = "axi-pwmgen", + .of_match_table = axi_pwmgen_ids, + }, + .probe = axi_pwmgen_probe, +}; +module_platform_driver(axi_pwmgen_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sergiu Cuciurean <sergiu.cuciurean@analog.com>"); +MODULE_AUTHOR("Trevor Gamblin <tgamblin@baylibre.com>"); +MODULE_DESCRIPTION("Driver for the Analog Devices AXI PWM generator"); diff --git a/drivers/pwm/pwm-bcm2835.c b/drivers/pwm/pwm-bcm2835.c index aa35acbb0cbc..578e95e0296c 100644 --- a/drivers/pwm/pwm-bcm2835.c +++ b/drivers/pwm/pwm-bcm2835.c @@ -124,20 +124,14 @@ static const struct pwm_ops bcm2835_pwm_ops = { .apply = bcm2835_pwm_apply, }; -static void devm_clk_rate_exclusive_put(void *data) -{ - struct clk *clk = data; - - clk_rate_exclusive_put(clk); -} - static int bcm2835_pwm_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct pwm_chip *chip; struct bcm2835_pwm *pc; int ret; - chip = devm_pwmchip_alloc(&pdev->dev, 2, sizeof(*pc)); + chip = devm_pwmchip_alloc(dev, 2, sizeof(*pc)); if (IS_ERR(chip)) return PTR_ERR(chip); pc = to_bcm2835_pwm(chip); @@ -146,24 +140,19 @@ static int bcm2835_pwm_probe(struct platform_device *pdev) if (IS_ERR(pc->base)) return PTR_ERR(pc->base); - pc->clk = devm_clk_get_enabled(&pdev->dev, NULL); + pc->clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(pc->clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk), + return dev_err_probe(dev, PTR_ERR(pc->clk), "clock not found\n"); - ret = clk_rate_exclusive_get(pc->clk); + ret = devm_clk_rate_exclusive_get(dev, pc->clk); if (ret) - return dev_err_probe(&pdev->dev, ret, + return dev_err_probe(dev, ret, "fail to get exclusive rate\n"); - ret = devm_add_action_or_reset(&pdev->dev, devm_clk_rate_exclusive_put, - pc->clk); - if (ret) - return ret; - pc->rate = clk_get_rate(pc->clk); if (!pc->rate) - return dev_err_probe(&pdev->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "failed to get clock rate\n"); chip->ops = &bcm2835_pwm_ops; @@ -171,10 +160,9 @@ static int bcm2835_pwm_probe(struct platform_device *pdev) platform_set_drvdata(pdev, pc); - ret = devm_pwmchip_add(&pdev->dev, chip); + ret = devm_pwmchip_add(dev, chip); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, - "failed to add pwmchip\n"); + return dev_err_probe(dev, ret, "failed to add pwmchip\n"); return 0; } diff --git a/drivers/pwm/pwm-clk.c b/drivers/pwm/pwm-clk.c index c19a482d7e28..f8f5af57acba 100644 --- a/drivers/pwm/pwm-clk.c +++ b/drivers/pwm/pwm-clk.c @@ -130,7 +130,7 @@ static struct platform_driver pwm_clk_driver = { .of_match_table = pwm_clk_dt_ids, }, .probe = pwm_clk_probe, - .remove_new = pwm_clk_remove, + .remove = pwm_clk_remove, }; module_platform_driver(pwm_clk_driver); diff --git a/drivers/pwm/pwm-clps711x.c b/drivers/pwm/pwm-clps711x.c index c950e1dbd2b8..04559a9de718 100644 --- a/drivers/pwm/pwm-clps711x.c +++ b/drivers/pwm/pwm-clps711x.c @@ -98,7 +98,7 @@ static int clps711x_pwm_probe(struct platform_device *pdev) return devm_pwmchip_add(&pdev->dev, chip); } -static const struct of_device_id __maybe_unused clps711x_pwm_dt_ids[] = { +static const struct of_device_id clps711x_pwm_dt_ids[] = { { .compatible = "cirrus,ep7209-pwm", }, { } }; @@ -107,7 +107,7 @@ MODULE_DEVICE_TABLE(of, clps711x_pwm_dt_ids); static struct platform_driver clps711x_pwm_driver = { .driver = { .name = "clps711x-pwm", - .of_match_table = of_match_ptr(clps711x_pwm_dt_ids), + .of_match_table = clps711x_pwm_dt_ids, }, .probe = clps711x_pwm_probe, }; diff --git a/drivers/pwm/pwm-cros-ec.c b/drivers/pwm/pwm-cros-ec.c index 606ccfdaf4cc..189301dc395e 100644 --- a/drivers/pwm/pwm-cros-ec.c +++ b/drivers/pwm/pwm-cros-ec.c @@ -20,20 +20,10 @@ * * @ec: Pointer to EC device * @use_pwm_type: Use PWM types instead of generic channels - * @channel: array with per-channel data */ struct cros_ec_pwm_device { struct cros_ec_device *ec; bool use_pwm_type; - struct cros_ec_pwm *channel; -}; - -/** - * struct cros_ec_pwm - per-PWM driver data - * @duty_cycle: cached duty cycle - */ -struct cros_ec_pwm { - u16 duty_cycle; }; static inline struct cros_ec_pwm_device *pwm_to_cros_ec_pwm(struct pwm_chip *chip) @@ -135,7 +125,6 @@ static int cros_ec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip); - struct cros_ec_pwm *channel = &ec_pwm->channel[pwm->hwpwm]; u16 duty_cycle; int ret; @@ -156,8 +145,6 @@ static int cros_ec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, if (ret < 0) return ret; - channel->duty_cycle = state->duty_cycle; - return 0; } @@ -165,7 +152,6 @@ static int cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip); - struct cros_ec_pwm *channel = &ec_pwm->channel[pwm->hwpwm]; int ret; ret = cros_ec_pwm_get_duty(ec_pwm->ec, ec_pwm->use_pwm_type, pwm->hwpwm); @@ -175,44 +161,13 @@ static int cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, } state->enabled = (ret > 0); + state->duty_cycle = ret; state->period = EC_PWM_MAX_DUTY; state->polarity = PWM_POLARITY_NORMAL; - /* - * Note that "disabled" and "duty cycle == 0" are treated the same. If - * the cached duty cycle is not zero, used the cached duty cycle. This - * ensures that the configured duty cycle is kept across a disable and - * enable operation and avoids potentially confusing consumers. - * - * For the case of the initial hardware readout, channel->duty_cycle - * will be 0 and the actual duty cycle read from the EC is used. - */ - if (ret == 0 && channel->duty_cycle > 0) - state->duty_cycle = channel->duty_cycle; - else - state->duty_cycle = ret; - return 0; } -static struct pwm_device * -cros_ec_pwm_xlate(struct pwm_chip *chip, const struct of_phandle_args *args) -{ - struct pwm_device *pwm; - - if (args->args[0] >= chip->npwm) - return ERR_PTR(-EINVAL); - - pwm = pwm_request_from_chip(chip, args->args[0], NULL); - if (IS_ERR(pwm)) - return pwm; - - /* The EC won't let us change the period */ - pwm->args.period = EC_PWM_MAX_DUTY; - - return pwm; -} - static const struct pwm_ops cros_ec_pwm_ops = { .get_state = cros_ec_pwm_get_state, .apply = cros_ec_pwm_apply, @@ -263,7 +218,7 @@ static int cros_ec_pwm_probe(struct platform_device *pdev) struct cros_ec_pwm_device *ec_pwm; struct pwm_chip *chip; bool use_pwm_type = false; - unsigned int npwm; + unsigned int i, npwm; int ret; if (!ec) @@ -289,12 +244,17 @@ static int cros_ec_pwm_probe(struct platform_device *pdev) /* PWM chip */ chip->ops = &cros_ec_pwm_ops; - chip->of_xlate = cros_ec_pwm_xlate; - ec_pwm->channel = devm_kcalloc(dev, chip->npwm, sizeof(*ec_pwm->channel), - GFP_KERNEL); - if (!ec_pwm->channel) - return -ENOMEM; + /* + * The device tree binding for this device is special as it only uses a + * single cell (for the hwid) and so doesn't provide a default period. + * This isn't a big problem though as the hardware only supports a + * single period length, it's just a bit ugly to make this fit into the + * pwm core abstractions. So initialize the period here, as + * of_pwm_xlate_with_flags() won't do that for us. + */ + for (i = 0; i < npwm; ++i) + chip->pwms[i].args.period = EC_PWM_MAX_DUTY; dev_dbg(dev, "Probed %u PWMs\n", chip->npwm); diff --git a/drivers/pwm/pwm-dwc-core.c b/drivers/pwm/pwm-dwc-core.c index 043736972cb9..6dabec93a3c6 100644 --- a/drivers/pwm/pwm-dwc-core.c +++ b/drivers/pwm/pwm-dwc-core.c @@ -9,7 +9,7 @@ * Author: Raymond Tan <raymond.tan@intel.com> */ -#define DEFAULT_SYMBOL_NAMESPACE dwc_pwm +#define DEFAULT_SYMBOL_NAMESPACE "dwc_pwm" #include <linux/bitops.h> #include <linux/export.h> @@ -172,7 +172,6 @@ struct pwm_chip *dwc_pwm_alloc(struct device *dev) dwc->clk_ns = 10; chip->ops = &dwc_pwm_ops; - dev_set_drvdata(dev, chip); return chip; } EXPORT_SYMBOL_GPL(dwc_pwm_alloc); diff --git a/drivers/pwm/pwm-dwc.c b/drivers/pwm/pwm-dwc.c index 676eaf8d7a53..b6c16139ce4a 100644 --- a/drivers/pwm/pwm-dwc.c +++ b/drivers/pwm/pwm-dwc.c @@ -31,26 +31,34 @@ static const struct dwc_pwm_info ehl_pwm_info = { .size = 0x1000, }; -static int dwc_pwm_init_one(struct device *dev, void __iomem *base, unsigned int offset) +static int dwc_pwm_init_one(struct device *dev, struct dwc_pwm_drvdata *ddata, unsigned int idx) { struct pwm_chip *chip; struct dwc_pwm *dwc; + int ret; chip = dwc_pwm_alloc(dev); if (IS_ERR(chip)) return PTR_ERR(chip); dwc = to_dwc_pwm(chip); - dwc->base = base + offset; + dwc->base = ddata->io_base + (ddata->info->size * idx); - return devm_pwmchip_add(dev, chip); + ret = devm_pwmchip_add(dev, chip); + if (ret) + return ret; + + ddata->chips[idx] = chip; + return 0; } static int dwc_pwm_probe(struct pci_dev *pci, const struct pci_device_id *id) { const struct dwc_pwm_info *info; struct device *dev = &pci->dev; - int i, ret; + struct dwc_pwm_drvdata *ddata; + unsigned int idx; + int ret; ret = pcim_enable_device(pci); if (ret) @@ -58,22 +66,26 @@ static int dwc_pwm_probe(struct pci_dev *pci, const struct pci_device_id *id) pci_set_master(pci); - ret = pcim_iomap_regions(pci, BIT(0), pci_name(pci)); - if (ret) - return dev_err_probe(dev, ret, "Failed to iomap PCI BAR\n"); - info = (const struct dwc_pwm_info *)id->driver_data; + ddata = devm_kzalloc(dev, struct_size(ddata, chips, info->nr), GFP_KERNEL); + if (!ddata) + return -ENOMEM; - for (i = 0; i < info->nr; i++) { - /* - * No need to check for pcim_iomap_table() failure, - * pcim_iomap_regions() already does it for us. - */ - ret = dwc_pwm_init_one(dev, pcim_iomap_table(pci)[0], i * info->size); + ddata->io_base = pcim_iomap_region(pci, 0, "pwm-dwc"); + if (IS_ERR(ddata->io_base)) + return dev_err_probe(dev, PTR_ERR(ddata->io_base), + "Failed to request / iomap PCI BAR\n"); + + ddata->info = info; + + for (idx = 0; idx < ddata->info->nr; idx++) { + ret = dwc_pwm_init_one(dev, ddata, idx); if (ret) return ret; } + dev_set_drvdata(dev, ddata); + pm_runtime_put(dev); pm_runtime_allow(dev); @@ -88,19 +100,24 @@ static void dwc_pwm_remove(struct pci_dev *pci) static int dwc_pwm_suspend(struct device *dev) { - struct pwm_chip *chip = dev_get_drvdata(dev); - struct dwc_pwm *dwc = to_dwc_pwm(chip); - int i; - - for (i = 0; i < DWC_TIMERS_TOTAL; i++) { - if (chip->pwms[i].state.enabled) { - dev_err(dev, "PWM %u in use by consumer (%s)\n", - i, chip->pwms[i].label); - return -EBUSY; + struct dwc_pwm_drvdata *ddata = dev_get_drvdata(dev); + unsigned int idx; + + for (idx = 0; idx < ddata->info->nr; idx++) { + struct pwm_chip *chip = ddata->chips[idx]; + struct dwc_pwm *dwc = to_dwc_pwm(chip); + unsigned int i; + + for (i = 0; i < DWC_TIMERS_TOTAL; i++) { + if (chip->pwms[i].state.enabled) { + dev_err(dev, "PWM %u in use by consumer (%s)\n", + i, chip->pwms[i].label); + return -EBUSY; + } + dwc->ctx[i].cnt = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT(i)); + dwc->ctx[i].cnt2 = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT2(i)); + dwc->ctx[i].ctrl = dwc_pwm_readl(dwc, DWC_TIM_CTRL(i)); } - dwc->ctx[i].cnt = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT(i)); - dwc->ctx[i].cnt2 = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT2(i)); - dwc->ctx[i].ctrl = dwc_pwm_readl(dwc, DWC_TIM_CTRL(i)); } return 0; @@ -108,14 +125,19 @@ static int dwc_pwm_suspend(struct device *dev) static int dwc_pwm_resume(struct device *dev) { - struct pwm_chip *chip = dev_get_drvdata(dev); - struct dwc_pwm *dwc = to_dwc_pwm(chip); - int i; - - for (i = 0; i < DWC_TIMERS_TOTAL; i++) { - dwc_pwm_writel(dwc, dwc->ctx[i].cnt, DWC_TIM_LD_CNT(i)); - dwc_pwm_writel(dwc, dwc->ctx[i].cnt2, DWC_TIM_LD_CNT2(i)); - dwc_pwm_writel(dwc, dwc->ctx[i].ctrl, DWC_TIM_CTRL(i)); + struct dwc_pwm_drvdata *ddata = dev_get_drvdata(dev); + unsigned int idx; + + for (idx = 0; idx < ddata->info->nr; idx++) { + struct pwm_chip *chip = ddata->chips[idx]; + struct dwc_pwm *dwc = to_dwc_pwm(chip); + unsigned int i; + + for (i = 0; i < DWC_TIMERS_TOTAL; i++) { + dwc_pwm_writel(dwc, dwc->ctx[i].cnt, DWC_TIM_LD_CNT(i)); + dwc_pwm_writel(dwc, dwc->ctx[i].cnt2, DWC_TIM_LD_CNT2(i)); + dwc_pwm_writel(dwc, dwc->ctx[i].ctrl, DWC_TIM_CTRL(i)); + } } return 0; diff --git a/drivers/pwm/pwm-dwc.h b/drivers/pwm/pwm-dwc.h index a8b074841ae8..1562594e7f85 100644 --- a/drivers/pwm/pwm-dwc.h +++ b/drivers/pwm/pwm-dwc.h @@ -9,7 +9,7 @@ * Author: Raymond Tan <raymond.tan@intel.com> */ -MODULE_IMPORT_NS(dwc_pwm); +MODULE_IMPORT_NS("dwc_pwm"); #define DWC_TIM_LD_CNT(n) ((n) * 0x14) #define DWC_TIM_LD_CNT2(n) (((n) * 4) + 0xb0) @@ -38,6 +38,12 @@ struct dwc_pwm_info { unsigned int size; }; +struct dwc_pwm_drvdata { + const struct dwc_pwm_info *info; + void __iomem *io_base; + struct pwm_chip *chips[]; +}; + struct dwc_pwm_ctx { u32 cnt; u32 cnt2; diff --git a/drivers/pwm/pwm-ep93xx.c b/drivers/pwm/pwm-ep93xx.c index 666f2954133c..994f89ac43b4 100644 --- a/drivers/pwm/pwm-ep93xx.c +++ b/drivers/pwm/pwm-ep93xx.c @@ -17,6 +17,7 @@ */ #include <linux/module.h> +#include <linux/mod_devicetable.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/clk.h> @@ -26,8 +27,6 @@ #include <asm/div64.h> -#include <linux/soc/cirrus/ep93xx.h> /* for ep93xx_pwm_{acquire,release}_gpio() */ - #define EP93XX_PWMx_TERM_COUNT 0x00 #define EP93XX_PWMx_DUTY_CYCLE 0x04 #define EP93XX_PWMx_ENABLE 0x08 @@ -43,20 +42,6 @@ static inline struct ep93xx_pwm *to_ep93xx_pwm(struct pwm_chip *chip) return pwmchip_get_drvdata(chip); } -static int ep93xx_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct platform_device *pdev = to_platform_device(pwmchip_parent(chip)); - - return ep93xx_pwm_acquire_gpio(pdev); -} - -static void ep93xx_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct platform_device *pdev = to_platform_device(pwmchip_parent(chip)); - - ep93xx_pwm_release_gpio(pdev); -} - static int ep93xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { @@ -155,8 +140,6 @@ static int ep93xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, } static const struct pwm_ops ep93xx_pwm_ops = { - .request = ep93xx_pwm_request, - .free = ep93xx_pwm_free, .apply = ep93xx_pwm_apply, }; @@ -188,9 +171,16 @@ static int ep93xx_pwm_probe(struct platform_device *pdev) return 0; } +static const struct of_device_id ep93xx_pwm_of_ids[] = { + { .compatible = "cirrus,ep9301-pwm" }, + { /* sentinel */} +}; +MODULE_DEVICE_TABLE(of, ep93xx_pwm_of_ids); + static struct platform_driver ep93xx_pwm_driver = { .driver = { .name = "ep93xx-pwm", + .of_match_table = ep93xx_pwm_of_ids, }, .probe = ep93xx_pwm_probe, }; diff --git a/drivers/pwm/pwm-fsl-ftm.c b/drivers/pwm/pwm-fsl-ftm.c index 2510c10ca473..c45a5fca4cbb 100644 --- a/drivers/pwm/pwm-fsl-ftm.c +++ b/drivers/pwm/pwm-fsl-ftm.c @@ -118,6 +118,9 @@ static unsigned int fsl_pwm_ticks_to_ns(struct fsl_pwm_chip *fpc, unsigned long long exval; rate = clk_get_rate(fpc->clk[fpc->period.clk_select]); + if (rate >> fpc->period.clk_ps == 0) + return 0; + exval = ticks; exval *= 1000000000UL; do_div(exval, rate >> fpc->period.clk_ps); @@ -190,6 +193,9 @@ static unsigned int fsl_pwm_calculate_duty(struct fsl_pwm_chip *fpc, unsigned int period = fpc->period.mod_period + 1; unsigned int period_ns = fsl_pwm_ticks_to_ns(fpc, period); + if (!period_ns) + return 0; + duty = (unsigned long long)duty_ns * period; do_div(duty, period_ns); diff --git a/drivers/pwm/pwm-gpio.c b/drivers/pwm/pwm-gpio.c new file mode 100644 index 000000000000..5f4edeb394a9 --- /dev/null +++ b/drivers/pwm/pwm-gpio.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generic software PWM for modulating GPIOs + * + * Copyright (C) 2020 Axis Communications AB + * Copyright (C) 2020 Nicola Di Lieto + * Copyright (C) 2024 Stefan Wahren + * Copyright (C) 2024 Linus Walleij + */ + +#include <linux/cleanup.h> +#include <linux/container_of.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/hrtimer.h> +#include <linux/math.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/pwm.h> +#include <linux/spinlock.h> +#include <linux/time.h> +#include <linux/types.h> + +struct pwm_gpio { + struct hrtimer gpio_timer; + struct gpio_desc *gpio; + struct pwm_state state; + struct pwm_state next_state; + + /* Protect internal state between pwm_ops and hrtimer */ + spinlock_t lock; + + bool changing; + bool running; + bool level; +}; + +static void pwm_gpio_round(struct pwm_state *dest, const struct pwm_state *src) +{ + u64 dividend; + u32 remainder; + + *dest = *src; + + /* Round down to hrtimer resolution */ + dividend = dest->period; + remainder = do_div(dividend, hrtimer_resolution); + dest->period -= remainder; + + dividend = dest->duty_cycle; + remainder = do_div(dividend, hrtimer_resolution); + dest->duty_cycle -= remainder; +} + +static u64 pwm_gpio_toggle(struct pwm_gpio *gpwm, bool level) +{ + const struct pwm_state *state = &gpwm->state; + bool invert = state->polarity == PWM_POLARITY_INVERSED; + + gpwm->level = level; + gpiod_set_value(gpwm->gpio, gpwm->level ^ invert); + + if (!state->duty_cycle || state->duty_cycle == state->period) { + gpwm->running = false; + return 0; + } + + gpwm->running = true; + return level ? state->duty_cycle : state->period - state->duty_cycle; +} + +static enum hrtimer_restart pwm_gpio_timer(struct hrtimer *gpio_timer) +{ + struct pwm_gpio *gpwm = container_of(gpio_timer, struct pwm_gpio, + gpio_timer); + u64 next_toggle; + bool new_level; + + guard(spinlock_irqsave)(&gpwm->lock); + + /* Apply new state at end of current period */ + if (!gpwm->level && gpwm->changing) { + gpwm->changing = false; + gpwm->state = gpwm->next_state; + new_level = !!gpwm->state.duty_cycle; + } else { + new_level = !gpwm->level; + } + + next_toggle = pwm_gpio_toggle(gpwm, new_level); + if (next_toggle) + hrtimer_forward(gpio_timer, hrtimer_get_expires(gpio_timer), + ns_to_ktime(next_toggle)); + + return next_toggle ? HRTIMER_RESTART : HRTIMER_NORESTART; +} + +static int pwm_gpio_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct pwm_gpio *gpwm = pwmchip_get_drvdata(chip); + bool invert = state->polarity == PWM_POLARITY_INVERSED; + + if (state->duty_cycle && state->duty_cycle < hrtimer_resolution) + return -EINVAL; + + if (state->duty_cycle != state->period && + (state->period - state->duty_cycle < hrtimer_resolution)) + return -EINVAL; + + if (!state->enabled) { + hrtimer_cancel(&gpwm->gpio_timer); + } else if (!gpwm->running) { + int ret; + + /* + * This just enables the output, but pwm_gpio_toggle() + * really starts the duty cycle. + */ + ret = gpiod_direction_output(gpwm->gpio, invert); + if (ret) + return ret; + } + + guard(spinlock_irqsave)(&gpwm->lock); + + if (!state->enabled) { + pwm_gpio_round(&gpwm->state, state); + gpwm->running = false; + gpwm->changing = false; + + gpiod_set_value(gpwm->gpio, invert); + } else if (gpwm->running) { + pwm_gpio_round(&gpwm->next_state, state); + gpwm->changing = true; + } else { + unsigned long next_toggle; + + pwm_gpio_round(&gpwm->state, state); + gpwm->changing = false; + + next_toggle = pwm_gpio_toggle(gpwm, !!state->duty_cycle); + if (next_toggle) + hrtimer_start(&gpwm->gpio_timer, next_toggle, + HRTIMER_MODE_REL); + } + + return 0; +} + +static int pwm_gpio_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct pwm_gpio *gpwm = pwmchip_get_drvdata(chip); + + guard(spinlock_irqsave)(&gpwm->lock); + + if (gpwm->changing) + *state = gpwm->next_state; + else + *state = gpwm->state; + + return 0; +} + +static const struct pwm_ops pwm_gpio_ops = { + .apply = pwm_gpio_apply, + .get_state = pwm_gpio_get_state, +}; + +static void pwm_gpio_disable_hrtimer(void *data) +{ + struct pwm_gpio *gpwm = data; + + hrtimer_cancel(&gpwm->gpio_timer); +} + +static int pwm_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pwm_chip *chip; + struct pwm_gpio *gpwm; + int ret; + + chip = devm_pwmchip_alloc(dev, 1, sizeof(*gpwm)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + gpwm = pwmchip_get_drvdata(chip); + + spin_lock_init(&gpwm->lock); + + gpwm->gpio = devm_gpiod_get(dev, NULL, GPIOD_ASIS); + if (IS_ERR(gpwm->gpio)) + return dev_err_probe(dev, PTR_ERR(gpwm->gpio), + "%pfw: could not get gpio\n", + dev_fwnode(dev)); + + if (gpiod_cansleep(gpwm->gpio)) + return dev_err_probe(dev, -EINVAL, + "%pfw: sleeping GPIO not supported\n", + dev_fwnode(dev)); + + chip->ops = &pwm_gpio_ops; + chip->atomic = true; + + hrtimer_setup(&gpwm->gpio_timer, pwm_gpio_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + + ret = devm_add_action_or_reset(dev, pwm_gpio_disable_hrtimer, gpwm); + if (ret) + return ret; + + ret = pwmchip_add(chip); + if (ret < 0) + return dev_err_probe(dev, ret, "could not add pwmchip\n"); + + return 0; +} + +static const struct of_device_id pwm_gpio_dt_ids[] = { + { .compatible = "pwm-gpio" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pwm_gpio_dt_ids); + +static struct platform_driver pwm_gpio_driver = { + .driver = { + .name = "pwm-gpio", + .of_match_table = pwm_gpio_dt_ids, + }, + .probe = pwm_gpio_probe, +}; +module_platform_driver(pwm_gpio_driver); + +MODULE_DESCRIPTION("PWM GPIO driver"); +MODULE_AUTHOR("Vincent Whitchurch"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-hibvt.c b/drivers/pwm/pwm-hibvt.c index 2eb0b13d4e10..e02ee6383dbc 100644 --- a/drivers/pwm/pwm-hibvt.c +++ b/drivers/pwm/pwm-hibvt.c @@ -276,7 +276,7 @@ static struct platform_driver hibvt_pwm_driver = { .of_match_table = hibvt_pwm_of_match, }, .probe = hibvt_pwm_probe, - .remove_new = hibvt_pwm_remove, + .remove = hibvt_pwm_remove, }; module_platform_driver(hibvt_pwm_driver); diff --git a/drivers/pwm/pwm-img.c b/drivers/pwm/pwm-img.c index d79a96679a26..71542956feca 100644 --- a/drivers/pwm/pwm-img.c +++ b/drivers/pwm/pwm-img.c @@ -284,9 +284,9 @@ static int img_pwm_probe(struct platform_device *pdev) return PTR_ERR(imgchip->sys_clk); } - imgchip->pwm_clk = devm_clk_get(&pdev->dev, "imgchip"); + imgchip->pwm_clk = devm_clk_get(&pdev->dev, "pwm"); if (IS_ERR(imgchip->pwm_clk)) { - dev_err(&pdev->dev, "failed to get imgchip clock\n"); + dev_err(&pdev->dev, "failed to get pwm clock\n"); return PTR_ERR(imgchip->pwm_clk); } @@ -416,7 +416,7 @@ static struct platform_driver img_pwm_driver = { .of_match_table = img_pwm_of_match, }, .probe = img_pwm_probe, - .remove_new = img_pwm_remove, + .remove = img_pwm_remove, }; module_platform_driver(img_pwm_driver); diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c index c50ddbac43c8..7ee7b65b9b90 100644 --- a/drivers/pwm/pwm-imx-tpm.c +++ b/drivers/pwm/pwm-imx-tpm.c @@ -20,6 +20,7 @@ #include <linux/io.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/pinctrl/consumer.h> #include <linux/platform_device.h> #include <linux/pwm.h> #include <linux/slab.h> @@ -105,7 +106,9 @@ static int pwm_imx_tpm_round_state(struct pwm_chip *chip, p->prescale = prescale; period_count = (clock_unit + ((1 << prescale) >> 1)) >> prescale; - p->mod = period_count; + if (period_count == 0) + return -EINVAL; + p->mod = period_count - 1; /* calculate real period HW can support */ tmp = (u64)period_count << prescale; @@ -380,6 +383,7 @@ static int pwm_imx_tpm_probe(struct platform_device *pdev) static int pwm_imx_tpm_suspend(struct device *dev) { struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev); + int ret; if (tpm->enable_count > 0) return -EBUSY; @@ -393,7 +397,11 @@ static int pwm_imx_tpm_suspend(struct device *dev) clk_disable_unprepare(tpm->clk); - return 0; + ret = pinctrl_pm_select_sleep_state(dev); + if (ret) + clk_prepare_enable(tpm->clk); + + return ret; } static int pwm_imx_tpm_resume(struct device *dev) @@ -401,9 +409,15 @@ static int pwm_imx_tpm_resume(struct device *dev) struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev); int ret = 0; - ret = clk_prepare_enable(tpm->clk); + ret = pinctrl_pm_select_default_state(dev); if (ret) + return ret; + + ret = clk_prepare_enable(tpm->clk); + if (ret) { dev_err(dev, "failed to prepare or enable clock: %d\n", ret); + pinctrl_pm_select_sleep_state(dev); + } return ret; } diff --git a/drivers/pwm/pwm-imx1.c b/drivers/pwm/pwm-imx1.c index 1d2aae2d278f..d5535d208005 100644 --- a/drivers/pwm/pwm-imx1.c +++ b/drivers/pwm/pwm-imx1.c @@ -194,5 +194,6 @@ static struct platform_driver pwm_imx1_driver = { }; module_platform_driver(pwm_imx1_driver); +MODULE_DESCRIPTION("i.MX1 and i.MX21 Pulse Width Modulator driver"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); diff --git a/drivers/pwm/pwm-imx27.c b/drivers/pwm/pwm-imx27.c index e1412116ef65..3d34cdc4a3a5 100644 --- a/drivers/pwm/pwm-imx27.c +++ b/drivers/pwm/pwm-imx27.c @@ -26,6 +26,7 @@ #define MX3_PWMSR 0x04 /* PWM Status Register */ #define MX3_PWMSAR 0x0C /* PWM Sample Register */ #define MX3_PWMPR 0x10 /* PWM Period Register */ +#define MX3_PWMCNR 0x14 /* PWM Counter Register */ #define MX3_PWMCR_FWM GENMASK(27, 26) #define MX3_PWMCR_STOPEN BIT(25) @@ -79,9 +80,12 @@ /* PWMPR register value of 0xffff has the same effect as 0xfffe */ #define MX3_PWMPR_MAX 0xfffe +static const char * const pwm_imx27_clks[] = {"ipg", "per"}; +#define PWM_IMX27_PER 1 + struct pwm_imx27_chip { - struct clk *clk_ipg; - struct clk *clk_per; + struct clk_bulk_data clks[ARRAY_SIZE(pwm_imx27_clks)]; + int clks_cnt; void __iomem *mmio_base; /* @@ -97,29 +101,6 @@ static inline struct pwm_imx27_chip *to_pwm_imx27_chip(struct pwm_chip *chip) return pwmchip_get_drvdata(chip); } -static int pwm_imx27_clk_prepare_enable(struct pwm_imx27_chip *imx) -{ - int ret; - - ret = clk_prepare_enable(imx->clk_ipg); - if (ret) - return ret; - - ret = clk_prepare_enable(imx->clk_per); - if (ret) { - clk_disable_unprepare(imx->clk_ipg); - return ret; - } - - return 0; -} - -static void pwm_imx27_clk_disable_unprepare(struct pwm_imx27_chip *imx) -{ - clk_disable_unprepare(imx->clk_per); - clk_disable_unprepare(imx->clk_ipg); -} - static int pwm_imx27_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { @@ -128,7 +109,7 @@ static int pwm_imx27_get_state(struct pwm_chip *chip, u64 tmp; int ret; - ret = pwm_imx27_clk_prepare_enable(imx); + ret = clk_bulk_prepare_enable(imx->clks_cnt, imx->clks); if (ret < 0) return ret; @@ -151,7 +132,7 @@ static int pwm_imx27_get_state(struct pwm_chip *chip, } prescaler = MX3_PWMCR_PRESCALER_GET(val); - pwm_clk = clk_get_rate(imx->clk_per); + pwm_clk = clk_get_rate(imx->clks[PWM_IMX27_PER].clk); val = readl(imx->mmio_base + MX3_PWMPR); period = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val; @@ -171,7 +152,7 @@ static int pwm_imx27_get_state(struct pwm_chip *chip, tmp = NSEC_PER_SEC * (u64)(val) * prescaler; state->duty_cycle = DIV_ROUND_UP_ULL(tmp, pwm_clk); - pwm_imx27_clk_disable_unprepare(imx); + clk_bulk_disable_unprepare(imx->clks_cnt, imx->clks); return 0; } @@ -219,14 +200,16 @@ static void pwm_imx27_wait_fifo_slot(struct pwm_chip *chip, static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { - unsigned long period_cycles, duty_cycles, prescale; + unsigned long period_cycles, duty_cycles, prescale, period_us, tmp; struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip); unsigned long long c; unsigned long long clkrate; + unsigned long flags; + int val; int ret; u32 cr; - clkrate = clk_get_rate(imx->clk_per); + clkrate = clk_get_rate(imx->clks[PWM_IMX27_PER].clk); c = clkrate * state->period; do_div(c, NSEC_PER_SEC); @@ -256,14 +239,105 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm, if (pwm->state.enabled) { pwm_imx27_wait_fifo_slot(chip, pwm); } else { - ret = pwm_imx27_clk_prepare_enable(imx); + ret = clk_bulk_prepare_enable(imx->clks_cnt, imx->clks); if (ret) return ret; pwm_imx27_sw_reset(chip); } - writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); + val = readl(imx->mmio_base + MX3_PWMPR); + val = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val; + cr = readl(imx->mmio_base + MX3_PWMCR); + tmp = NSEC_PER_SEC * (u64)(val + 2) * MX3_PWMCR_PRESCALER_GET(cr); + tmp = DIV_ROUND_UP_ULL(tmp, clkrate); + period_us = DIV_ROUND_UP_ULL(tmp, 1000); + + /* + * ERR051198: + * PWM: PWM output may not function correctly if the FIFO is empty when + * a new SAR value is programmed + * + * Description: + * When the PWM FIFO is empty, a new value programmed to the PWM Sample + * register (PWM_PWMSAR) will be directly applied even if the current + * timer period has not expired. + * + * If the new SAMPLE value programmed in the PWM_PWMSAR register is + * less than the previous value, and the PWM counter register + * (PWM_PWMCNR) that contains the current COUNT value is greater than + * the new programmed SAMPLE value, the current period will not flip + * the level. This may result in an output pulse with a duty cycle of + * 100%. + * + * Consider a change from + * ________ + * / \______/ + * ^ * ^ + * to + * ____ + * / \__________/ + * ^ ^ + * At the time marked by *, the new write value will be directly applied + * to SAR even the current period is not over if FIFO is empty. + * + * ________ ____________________ + * / \______/ \__________/ + * ^ ^ * ^ ^ + * |<-- old SAR -->| |<-- new SAR -->| + * + * That is the output is active for a whole period. + * + * Workaround: + * Check new SAR less than old SAR and current counter is in errata + * windows, write extra old SAR into FIFO and new SAR will effect at + * next period. + * + * Sometime period is quite long, such as over 1 second. If add old SAR + * into FIFO unconditional, new SAR have to wait for next period. It + * may be too long. + * + * Turn off the interrupt to ensure that not IRQ and schedule happen + * during above operations. If any irq and schedule happen, counter + * in PWM will be out of data and take wrong action. + * + * Add a safety margin 1.5us because it needs some time to complete + * IO write. + * + * Use writel_relaxed() to minimize the interval between two writes to + * the SAR register to increase the fastest PWM frequency supported. + * + * When the PWM period is longer than 2us(or <500kHz), this workaround + * can solve this problem. No software workaround is available if PWM + * period is shorter than IO write. Just try best to fill old data + * into FIFO. + */ + c = clkrate * 1500; + do_div(c, NSEC_PER_SEC); + + local_irq_save(flags); + val = FIELD_GET(MX3_PWMSR_FIFOAV, readl_relaxed(imx->mmio_base + MX3_PWMSR)); + + if (duty_cycles < imx->duty_cycle && (cr & MX3_PWMCR_EN)) { + if (period_us < 2) { /* 2us = 500 kHz */ + /* Best effort attempt to fix up >500 kHz case */ + udelay(3 * period_us); + writel_relaxed(imx->duty_cycle, imx->mmio_base + MX3_PWMSAR); + writel_relaxed(imx->duty_cycle, imx->mmio_base + MX3_PWMSAR); + } else if (val < MX3_PWMSR_FIFOAV_2WORDS) { + val = readl_relaxed(imx->mmio_base + MX3_PWMCNR); + /* + * If counter is close to period, controller may roll over when + * next IO write. + */ + if ((val + c >= duty_cycles && val < imx->duty_cycle) || + val + c >= period_cycles) + writel_relaxed(imx->duty_cycle, imx->mmio_base + MX3_PWMSAR); + } + } + writel_relaxed(duty_cycles, imx->mmio_base + MX3_PWMSAR); + local_irq_restore(flags); + writel(period_cycles, imx->mmio_base + MX3_PWMPR); /* @@ -287,7 +361,7 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm, writel(cr, imx->mmio_base + MX3_PWMCR); if (!state->enabled) - pwm_imx27_clk_disable_unprepare(imx); + clk_bulk_disable_unprepare(imx->clks_cnt, imx->clks); return 0; } @@ -309,21 +383,22 @@ static int pwm_imx27_probe(struct platform_device *pdev) struct pwm_imx27_chip *imx; int ret; u32 pwmcr; + int i; chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*imx)); if (IS_ERR(chip)) return PTR_ERR(chip); imx = to_pwm_imx27_chip(chip); - imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); - if (IS_ERR(imx->clk_ipg)) - return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_ipg), - "getting ipg clock failed\n"); + imx->clks_cnt = ARRAY_SIZE(pwm_imx27_clks); + for (i = 0; i < imx->clks_cnt; ++i) + imx->clks[i].id = pwm_imx27_clks[i]; + + ret = devm_clk_bulk_get(&pdev->dev, imx->clks_cnt, imx->clks); - imx->clk_per = devm_clk_get(&pdev->dev, "per"); - if (IS_ERR(imx->clk_per)) - return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_per), - "failed to get peripheral clock\n"); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "getting clocks failed\n"); chip->ops = &pwm_imx27_ops; @@ -331,14 +406,14 @@ static int pwm_imx27_probe(struct platform_device *pdev) if (IS_ERR(imx->mmio_base)) return PTR_ERR(imx->mmio_base); - ret = pwm_imx27_clk_prepare_enable(imx); + ret = clk_bulk_prepare_enable(imx->clks_cnt, imx->clks); if (ret) return ret; /* keep clks on if pwm is running */ pwmcr = readl(imx->mmio_base + MX3_PWMCR); if (!(pwmcr & MX3_PWMCR_EN)) - pwm_imx27_clk_disable_unprepare(imx); + clk_bulk_disable_unprepare(imx->clks_cnt, imx->clks); return devm_pwmchip_add(&pdev->dev, chip); } @@ -352,5 +427,6 @@ static struct platform_driver imx_pwm_driver = { }; module_platform_driver(imx_pwm_driver); +MODULE_DESCRIPTION("i.MX27 and later i.MX SoCs Pulse Width Modulator driver"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); diff --git a/drivers/pwm/pwm-intel-lgm.c b/drivers/pwm/pwm-intel-lgm.c index f9cc7c17c8f0..084c71a0a11b 100644 --- a/drivers/pwm/pwm-intel-lgm.c +++ b/drivers/pwm/pwm-intel-lgm.c @@ -230,4 +230,5 @@ static struct platform_driver lgm_pwm_driver = { }; module_platform_driver(lgm_pwm_driver); +MODULE_DESCRIPTION("Intel LGM Pulse Width Modulator driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index da4bf543d357..6bdb01619380 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -201,12 +201,11 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, * state instead of its inactive state. */ if ((state->polarity == PWM_POLARITY_NORMAL) ^ state->enabled) - regmap_update_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), - TCU_TCSR_PWM_INITL_HIGH, 0); + regmap_clear_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), + TCU_TCSR_PWM_INITL_HIGH); else - regmap_update_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), - TCU_TCSR_PWM_INITL_HIGH, - TCU_TCSR_PWM_INITL_HIGH); + regmap_set_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), + TCU_TCSR_PWM_INITL_HIGH); if (state->enabled) jz4740_pwm_enable(chip, pwm); diff --git a/drivers/pwm/pwm-loongson.c b/drivers/pwm/pwm-loongson.c new file mode 100644 index 000000000000..1ba16168cbb4 --- /dev/null +++ b/drivers/pwm/pwm-loongson.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2017-2025 Loongson Technology Corporation Limited. + * + * Loongson PWM driver + * + * For Loongson's PWM IP block documentation please refer Chapter 11 of + * Reference Manual: https://loongson.github.io/LoongArch-Documentation/Loongson-7A1000-usermanual-EN.pdf + * + * Author: Juxin Gao <gaojuxin@loongson.cn> + * Further cleanup and restructuring by: + * Binbin Zhou <zhoubinbin@loongson.cn> + * + * Limitations: + * - If both DUTY and PERIOD are set to 0, the output is a constant low signal. + * - When disabled the output is driven to 0 independent of the configured + * polarity. + * - If the register is reconfigured while PWM is running, it does not complete + * the currently running period. + * - Disabling the PWM stops the output immediately (without waiting for current + * period to complete first). + */ + +#include <linux/acpi.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/units.h> + +/* Loongson PWM registers */ +#define LOONGSON_PWM_REG_DUTY 0x4 /* Low Pulse Buffer Register */ +#define LOONGSON_PWM_REG_PERIOD 0x8 /* Pulse Period Buffer Register */ +#define LOONGSON_PWM_REG_CTRL 0xc /* Control Register */ + +/* Control register bits */ +#define LOONGSON_PWM_CTRL_REG_EN BIT(0) /* Counter Enable Bit */ +#define LOONGSON_PWM_CTRL_REG_OE BIT(3) /* Pulse Output Enable Control Bit, Valid Low */ +#define LOONGSON_PWM_CTRL_REG_SINGLE BIT(4) /* Single Pulse Control Bit */ +#define LOONGSON_PWM_CTRL_REG_INTE BIT(5) /* Interrupt Enable Bit */ +#define LOONGSON_PWM_CTRL_REG_INT BIT(6) /* Interrupt Bit */ +#define LOONGSON_PWM_CTRL_REG_RST BIT(7) /* Counter Reset Bit */ +#define LOONGSON_PWM_CTRL_REG_CAPTE BIT(8) /* Measurement Pulse Enable Bit */ +#define LOONGSON_PWM_CTRL_REG_INVERT BIT(9) /* Output flip-flop Enable Bit */ +#define LOONGSON_PWM_CTRL_REG_DZONE BIT(10) /* Anti-dead Zone Enable Bit */ + +/* default input clk frequency for the ACPI case */ +#define LOONGSON_PWM_FREQ_DEFAULT 50000 /* Hz */ + +struct pwm_loongson_ddata { + struct clk *clk; + void __iomem *base; + u64 clk_rate; +}; + +static inline __pure struct pwm_loongson_ddata *to_pwm_loongson_ddata(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static inline u32 pwm_loongson_readl(struct pwm_loongson_ddata *ddata, u32 offset) +{ + return readl(ddata->base + offset); +} + +static inline void pwm_loongson_writel(struct pwm_loongson_ddata *ddata, + u32 val, u32 offset) +{ + writel(val, ddata->base + offset); +} + +static int pwm_loongson_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + u16 val; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); + + if (polarity == PWM_POLARITY_INVERSED) + /* Duty cycle defines LOW period of PWM */ + val |= LOONGSON_PWM_CTRL_REG_INVERT; + else + /* Duty cycle defines HIGH period of PWM */ + val &= ~LOONGSON_PWM_CTRL_REG_INVERT; + + pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); + + return 0; +} + +static void pwm_loongson_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + u32 val; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); + val &= ~LOONGSON_PWM_CTRL_REG_EN; + pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); +} + +static int pwm_loongson_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + u32 val; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); + val |= LOONGSON_PWM_CTRL_REG_EN; + pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); + + return 0; +} + +static int pwm_loongson_config(struct pwm_chip *chip, struct pwm_device *pwm, + u64 duty_ns, u64 period_ns) +{ + u64 duty, period; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + /* duty = duty_ns * ddata->clk_rate / NSEC_PER_SEC */ + duty = mul_u64_u64_div_u64(duty_ns, ddata->clk_rate, NSEC_PER_SEC); + if (duty > U32_MAX) + duty = U32_MAX; + + /* period = period_ns * ddata->clk_rate / NSEC_PER_SEC */ + period = mul_u64_u64_div_u64(period_ns, ddata->clk_rate, NSEC_PER_SEC); + if (period > U32_MAX) + period = U32_MAX; + + pwm_loongson_writel(ddata, duty, LOONGSON_PWM_REG_DUTY); + pwm_loongson_writel(ddata, period, LOONGSON_PWM_REG_PERIOD); + + return 0; +} + +static int pwm_loongson_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + int ret; + bool enabled = pwm->state.enabled; + + if (!state->enabled) { + if (enabled) + pwm_loongson_disable(chip, pwm); + return 0; + } + + ret = pwm_loongson_set_polarity(chip, pwm, state->polarity); + if (ret) + return ret; + + ret = pwm_loongson_config(chip, pwm, state->duty_cycle, state->period); + if (ret) + return ret; + + if (!enabled && state->enabled) + ret = pwm_loongson_enable(chip, pwm); + + return ret; +} + +static int pwm_loongson_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + u32 duty, period, ctrl; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + duty = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_DUTY); + period = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_PERIOD); + ctrl = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); + + /* duty & period have a max of 2^32, so we can't overflow */ + state->duty_cycle = DIV64_U64_ROUND_UP((u64)duty * NSEC_PER_SEC, ddata->clk_rate); + state->period = DIV64_U64_ROUND_UP((u64)period * NSEC_PER_SEC, ddata->clk_rate); + state->polarity = (ctrl & LOONGSON_PWM_CTRL_REG_INVERT) ? PWM_POLARITY_INVERSED : + PWM_POLARITY_NORMAL; + state->enabled = (ctrl & LOONGSON_PWM_CTRL_REG_EN) ? true : false; + + return 0; +} + +static const struct pwm_ops pwm_loongson_ops = { + .apply = pwm_loongson_apply, + .get_state = pwm_loongson_get_state, +}; + +static int pwm_loongson_probe(struct platform_device *pdev) +{ + int ret; + struct pwm_chip *chip; + struct pwm_loongson_ddata *ddata; + struct device *dev = &pdev->dev; + + chip = devm_pwmchip_alloc(dev, 1, sizeof(*ddata)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + ddata = to_pwm_loongson_ddata(chip); + + ddata->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ddata->base)) + return PTR_ERR(ddata->base); + + ddata->clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(ddata->clk)) + return dev_err_probe(dev, PTR_ERR(ddata->clk), + "Failed to get pwm clock\n"); + if (ddata->clk) { + ret = devm_clk_rate_exclusive_get(dev, ddata->clk); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get exclusive rate\n"); + + ddata->clk_rate = clk_get_rate(ddata->clk); + if (!ddata->clk_rate) + return dev_err_probe(dev, -EINVAL, + "Failed to get frequency\n"); + } else { + ddata->clk_rate = LOONGSON_PWM_FREQ_DEFAULT; + } + + /* This check is done to prevent an overflow in .apply */ + if (ddata->clk_rate > NSEC_PER_SEC) + return dev_err_probe(dev, -EINVAL, "PWM clock out of range\n"); + + chip->ops = &pwm_loongson_ops; + chip->atomic = true; + dev_set_drvdata(dev, chip); + + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to add PWM chip\n"); + + return 0; +} + +static int pwm_loongson_suspend(struct device *dev) +{ + struct pwm_chip *chip = dev_get_drvdata(dev); + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + struct pwm_device *pwm = &chip->pwms[0]; + + if (pwm->state.enabled) + return -EBUSY; + + clk_disable_unprepare(ddata->clk); + + return 0; +} + +static int pwm_loongson_resume(struct device *dev) +{ + struct pwm_chip *chip = dev_get_drvdata(dev); + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + return clk_prepare_enable(ddata->clk); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(pwm_loongson_pm_ops, pwm_loongson_suspend, + pwm_loongson_resume); + +static const struct of_device_id pwm_loongson_of_ids[] = { + { .compatible = "loongson,ls7a-pwm" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, pwm_loongson_of_ids); + +static const struct acpi_device_id pwm_loongson_acpi_ids[] = { + { "LOON0006" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, pwm_loongson_acpi_ids); + +static struct platform_driver pwm_loongson_driver = { + .probe = pwm_loongson_probe, + .driver = { + .name = "loongson-pwm", + .pm = pm_ptr(&pwm_loongson_pm_ops), + .of_match_table = pwm_loongson_of_ids, + .acpi_match_table = pwm_loongson_acpi_ids, + }, +}; +module_platform_driver(pwm_loongson_driver); + +MODULE_DESCRIPTION("Loongson PWM driver"); +MODULE_AUTHOR("Loongson Technology Corporation Limited."); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-lp3943.c b/drivers/pwm/pwm-lp3943.c index 61189cea1046..90b0733c00c1 100644 --- a/drivers/pwm/pwm-lp3943.c +++ b/drivers/pwm/pwm-lp3943.c @@ -218,8 +218,7 @@ static int lp3943_pwm_parse_dt(struct device *dev, struct lp3943_platform_data *pdata; struct lp3943_pwm_map *pwm_map; enum lp3943_pwm_output *output; - int i, err, proplen, count = 0; - u32 num_outputs; + int i, err, num_outputs, count = 0; if (!node) return -EINVAL; @@ -234,11 +233,8 @@ static int lp3943_pwm_parse_dt(struct device *dev, */ for (i = 0; i < LP3943_NUM_PWMS; i++) { - if (!of_get_property(node, name[i], &proplen)) - continue; - - num_outputs = proplen / sizeof(u32); - if (num_outputs == 0) + num_outputs = of_property_count_u32_elems(node, name[i]); + if (num_outputs <= 0) continue; output = devm_kcalloc(dev, num_outputs, sizeof(*output), diff --git a/drivers/pwm/pwm-lpc18xx-sct.c b/drivers/pwm/pwm-lpc18xx-sct.c index 04b76d257fd8..f351baa63453 100644 --- a/drivers/pwm/pwm-lpc18xx-sct.c +++ b/drivers/pwm/pwm-lpc18xx-sct.c @@ -446,7 +446,7 @@ static struct platform_driver lpc18xx_pwm_driver = { .of_match_table = lpc18xx_pwm_of_match, }, .probe = lpc18xx_pwm_probe, - .remove_new = lpc18xx_pwm_remove, + .remove = lpc18xx_pwm_remove, }; module_platform_driver(lpc18xx_pwm_driver); diff --git a/drivers/pwm/pwm-lpss-pci.c b/drivers/pwm/pwm-lpss-pci.c index 25045c229520..ae25d9321d75 100644 --- a/drivers/pwm/pwm-lpss-pci.c +++ b/drivers/pwm/pwm-lpss-pci.c @@ -18,6 +18,7 @@ static int pwm_lpss_probe_pci(struct pci_dev *pdev, const struct pci_device_id *id) { const struct pwm_lpss_boardinfo *info; + void __iomem *io_base; struct pwm_chip *chip; int err; @@ -25,12 +26,12 @@ static int pwm_lpss_probe_pci(struct pci_dev *pdev, if (err < 0) return err; - err = pcim_iomap_regions(pdev, BIT(0), pci_name(pdev)); - if (err) - return err; + io_base = pcim_iomap_region(pdev, 0, "pwm-lpss"); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); info = (struct pwm_lpss_boardinfo *)id->driver_data; - chip = devm_pwm_lpss_probe(&pdev->dev, pcim_iomap_table(pdev)[0], info); + chip = devm_pwm_lpss_probe(&pdev->dev, io_base, info); if (IS_ERR(chip)) return PTR_ERR(chip); @@ -46,25 +47,6 @@ static void pwm_lpss_remove_pci(struct pci_dev *pdev) pm_runtime_get_sync(&pdev->dev); } -static int pwm_lpss_runtime_suspend_pci(struct device *dev) -{ - /* - * The PCI core will handle transition to D3 automatically. We only - * need to provide runtime PM hooks for that to happen. - */ - return 0; -} - -static int pwm_lpss_runtime_resume_pci(struct device *dev) -{ - return 0; -} - -static DEFINE_RUNTIME_DEV_PM_OPS(pwm_lpss_pci_pm, - pwm_lpss_runtime_suspend_pci, - pwm_lpss_runtime_resume_pci, - NULL); - static const struct pci_device_id pwm_lpss_pci_ids[] = { { PCI_VDEVICE(INTEL, 0x0ac8), (unsigned long)&pwm_lpss_bxt_info}, { PCI_VDEVICE(INTEL, 0x0f08), (unsigned long)&pwm_lpss_byt_info}, @@ -84,12 +66,9 @@ static struct pci_driver pwm_lpss_driver_pci = { .id_table = pwm_lpss_pci_ids, .probe = pwm_lpss_probe_pci, .remove = pwm_lpss_remove_pci, - .driver = { - .pm = pm_ptr(&pwm_lpss_pci_pm), - }, }; module_pci_driver(pwm_lpss_driver_pci); MODULE_DESCRIPTION("PWM PCI driver for Intel LPSS"); MODULE_LICENSE("GPL v2"); -MODULE_IMPORT_NS(PWM_LPSS); +MODULE_IMPORT_NS("PWM_LPSS"); diff --git a/drivers/pwm/pwm-lpss-platform.c b/drivers/pwm/pwm-lpss-platform.c index dbc9f5b17bdc..653ec9d0c8bf 100644 --- a/drivers/pwm/pwm-lpss-platform.c +++ b/drivers/pwm/pwm-lpss-platform.c @@ -55,14 +55,7 @@ static int pwm_lpss_probe_platform(struct platform_device *pdev) DPM_FLAG_SMART_SUSPEND); pm_runtime_set_active(&pdev->dev); - pm_runtime_enable(&pdev->dev); - - return 0; -} - -static void pwm_lpss_remove_platform(struct platform_device *pdev) -{ - pm_runtime_disable(&pdev->dev); + return devm_pm_runtime_enable(&pdev->dev); } static const struct acpi_device_id pwm_lpss_acpi_match[] = { @@ -80,11 +73,10 @@ static struct platform_driver pwm_lpss_driver_platform = { .acpi_match_table = pwm_lpss_acpi_match, }, .probe = pwm_lpss_probe_platform, - .remove_new = pwm_lpss_remove_platform, }; module_platform_driver(pwm_lpss_driver_platform); MODULE_DESCRIPTION("PWM platform driver for Intel LPSS"); MODULE_LICENSE("GPL v2"); -MODULE_IMPORT_NS(PWM_LPSS); +MODULE_IMPORT_NS("PWM_LPSS"); MODULE_ALIAS("platform:pwm-lpss"); diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index 867e2bc8c601..c976ff1c8ed9 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -10,6 +10,8 @@ * Author: Alan Cox <alan@linux.intel.com> */ +#define DEFAULT_SYMBOL_NAMESPACE "PWM_LPSS" + #include <linux/bits.h> #include <linux/delay.h> #include <linux/io.h> @@ -17,10 +19,9 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/pm_runtime.h> +#include <linux/pwm.h> #include <linux/time.h> -#define DEFAULT_SYMBOL_NAMESPACE PWM_LPSS - #include "pwm-lpss.h" #define PWM 0x00000000 diff --git a/drivers/pwm/pwm-lpss.h b/drivers/pwm/pwm-lpss.h index b5267ab5193b..60792181401e 100644 --- a/drivers/pwm/pwm-lpss.h +++ b/drivers/pwm/pwm-lpss.h @@ -10,7 +10,6 @@ #ifndef __PWM_LPSS_H #define __PWM_LPSS_H -#include <linux/pwm.h> #include <linux/types.h> #include <linux/platform_data/x86/pwm-lpss.h> diff --git a/drivers/pwm/pwm-mc33xs2410.c b/drivers/pwm/pwm-mc33xs2410.c new file mode 100644 index 000000000000..a1ac3445ccdb --- /dev/null +++ b/drivers/pwm/pwm-mc33xs2410.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH + * + * Reference Manual : https://www.nxp.com/docs/en/data-sheet/MC33XS2410.pdf + * + * Limitations: + * - Supports frequencies between 0.5Hz and 2048Hz with following steps: + * - 0.5 Hz steps from 0.5 Hz to 32 Hz + * - 2 Hz steps from 2 Hz to 128 Hz + * - 8 Hz steps from 8 Hz to 512 Hz + * - 32 Hz steps from 32 Hz to 2048 Hz + * - Cannot generate a 0 % duty cycle. + * - Always produces low output if disabled. + * - Configuration isn't atomic. When changing polarity, duty cycle or period + * the data is taken immediately, counters not being affected, resulting in a + * behavior of the output pin that is neither the old nor the new state, + * rather something in between. + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/math64.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pwm.h> + +#include <linux/spi/spi.h> + +#define MC33XS2410_GLB_CTRL 0x00 +#define MC33XS2410_GLB_CTRL_MODE GENMASK(7, 6) +#define MC33XS2410_GLB_CTRL_MODE_NORMAL FIELD_PREP(MC33XS2410_GLB_CTRL_MODE, 1) + +#define MC33XS2410_PWM_CTRL1 0x05 +/* chan in { 1 ... 4 } */ +#define MC33XS2410_PWM_CTRL1_POL_INV(chan) BIT((chan) + 1) + +#define MC33XS2410_PWM_CTRL3 0x07 +/* chan in { 1 ... 4 } */ +#define MC33XS2410_PWM_CTRL3_EN(chan) BIT(4 + (chan) - 1) + +/* chan in { 1 ... 4 } */ +#define MC33XS2410_PWM_FREQ(chan) (0x08 + (chan) - 1) +#define MC33XS2410_PWM_FREQ_STEP GENMASK(7, 6) +#define MC33XS2410_PWM_FREQ_COUNT GENMASK(5, 0) + +/* chan in { 1 ... 4 } */ +#define MC33XS2410_PWM_DC(chan) (0x0c + (chan) - 1) + +#define MC33XS2410_WDT 0x14 + +#define MC33XS2410_PWM_MIN_PERIOD 488282 +/* step in { 0 ... 3 } */ +#define MC33XS2410_PWM_MAX_PERIOD(step) (2000000000 >> (2 * (step))) + +#define MC33XS2410_FRAME_IN_ADDR GENMASK(15, 8) +#define MC33XS2410_FRAME_IN_DATA GENMASK(7, 0) +#define MC33XS2410_FRAME_IN_ADDR_WR BIT(7) +#define MC33XS2410_FRAME_IN_DATA_RD BIT(7) +#define MC33XS2410_FRAME_OUT_DATA GENMASK(13, 0) + +#define MC33XS2410_MAX_TRANSFERS 5 + +static int mc33xs2410_write_regs(struct spi_device *spi, u8 *reg, u8 *val, + unsigned int len) +{ + u16 tx[MC33XS2410_MAX_TRANSFERS]; + int i; + + if (len > MC33XS2410_MAX_TRANSFERS) + return -EINVAL; + + for (i = 0; i < len; i++) + tx[i] = FIELD_PREP(MC33XS2410_FRAME_IN_DATA, val[i]) | + FIELD_PREP(MC33XS2410_FRAME_IN_ADDR, + MC33XS2410_FRAME_IN_ADDR_WR | reg[i]); + + return spi_write(spi, tx, len * 2); +} + +static int mc33xs2410_read_regs(struct spi_device *spi, u8 *reg, u8 flag, + u16 *val, unsigned int len) +{ + u16 tx[MC33XS2410_MAX_TRANSFERS]; + u16 rx[MC33XS2410_MAX_TRANSFERS]; + struct spi_transfer t = { + .tx_buf = tx, + .rx_buf = rx, + }; + int i, ret; + + len++; + if (len > MC33XS2410_MAX_TRANSFERS) + return -EINVAL; + + t.len = len * 2; + for (i = 0; i < len - 1; i++) + tx[i] = FIELD_PREP(MC33XS2410_FRAME_IN_DATA, flag) | + FIELD_PREP(MC33XS2410_FRAME_IN_ADDR, reg[i]); + + ret = spi_sync_transfer(spi, &t, 1); + if (ret < 0) + return ret; + + for (i = 1; i < len; i++) + val[i - 1] = FIELD_GET(MC33XS2410_FRAME_OUT_DATA, rx[i]); + + return 0; +} + +static int mc33xs2410_write_reg(struct spi_device *spi, u8 reg, u8 val) +{ + return mc33xs2410_write_regs(spi, ®, &val, 1); +} + +static int mc33xs2410_read_reg(struct spi_device *spi, u8 reg, u16 *val, u8 flag) +{ + return mc33xs2410_read_regs(spi, ®, flag, val, 1); +} + +static int mc33xs2410_read_reg_ctrl(struct spi_device *spi, u8 reg, u16 *val) +{ + return mc33xs2410_read_reg(spi, reg, val, MC33XS2410_FRAME_IN_DATA_RD); +} + +static int mc33xs2410_modify_reg(struct spi_device *spi, u8 reg, u8 mask, u8 val) +{ + u16 tmp; + int ret; + + ret = mc33xs2410_read_reg_ctrl(spi, reg, &tmp); + if (ret < 0) + return ret; + + tmp &= ~mask; + tmp |= val & mask; + + return mc33xs2410_write_reg(spi, reg, tmp); +} + +static u8 mc33xs2410_pwm_get_freq(u64 period) +{ + u8 step, count; + + /* + * Check which step [0 .. 3] is appropriate for the given period. The + * period ranges for the different step values overlap. Prefer big step + * values as these allow more finegrained period and duty cycle + * selection. + */ + + switch (period) { + case MC33XS2410_PWM_MIN_PERIOD ... MC33XS2410_PWM_MAX_PERIOD(3): + step = 3; + break; + case MC33XS2410_PWM_MAX_PERIOD(3) + 1 ... MC33XS2410_PWM_MAX_PERIOD(2): + step = 2; + break; + case MC33XS2410_PWM_MAX_PERIOD(2) + 1 ... MC33XS2410_PWM_MAX_PERIOD(1): + step = 1; + break; + case MC33XS2410_PWM_MAX_PERIOD(1) + 1 ... MC33XS2410_PWM_MAX_PERIOD(0): + step = 0; + break; + } + + /* + * Round up here because a higher count results in a higher frequency + * and so a smaller period. + */ + count = DIV_ROUND_UP((u32)MC33XS2410_PWM_MAX_PERIOD(step), (u32)period); + return FIELD_PREP(MC33XS2410_PWM_FREQ_STEP, step) | + FIELD_PREP(MC33XS2410_PWM_FREQ_COUNT, count - 1); +} + +static u64 mc33xs2410_pwm_get_period(u8 reg) +{ + u32 doubled_freq, code, doubled_steps; + + /* + * steps: + * - 0 = 0.5Hz + * - 1 = 2Hz + * - 2 = 8Hz + * - 3 = 32Hz + * frequency = (code + 1) x steps. + * + * To avoid losing precision in case steps value is zero, scale the + * steps value for now by two and keep it in mind when calculating the + * period that the frequency had been doubled. + */ + doubled_steps = 1 << (FIELD_GET(MC33XS2410_PWM_FREQ_STEP, reg) * 2); + code = FIELD_GET(MC33XS2410_PWM_FREQ_COUNT, reg); + doubled_freq = (code + 1) * doubled_steps; + + /* Convert frequency to period, considering the doubled frequency. */ + return DIV_ROUND_UP(2 * NSEC_PER_SEC, doubled_freq); +} + +/* + * The hardware cannot generate a 0% relative duty cycle for normal and inversed + * polarity. For normal polarity, the channel must be disabled, the device then + * emits a constant low signal. + * For inverted polarity, the channel must be enabled, the polarity must be set + * to normal and the relative duty cylce must be set to 100%. The device then + * emits a constant high signal. + */ +static int mc33xs2410_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct spi_device *spi = pwmchip_get_drvdata(chip); + u8 reg[4] = { + MC33XS2410_PWM_FREQ(pwm->hwpwm + 1), + MC33XS2410_PWM_DC(pwm->hwpwm + 1), + MC33XS2410_PWM_CTRL1, + MC33XS2410_PWM_CTRL3 + }; + u64 period, duty_cycle; + int ret, rel_dc; + u16 rd_val[2]; + u8 wr_val[4]; + u8 mask; + + period = min(state->period, MC33XS2410_PWM_MAX_PERIOD(0)); + if (period < MC33XS2410_PWM_MIN_PERIOD) + return -EINVAL; + + ret = mc33xs2410_read_regs(spi, ®[2], MC33XS2410_FRAME_IN_DATA_RD, rd_val, 2); + if (ret < 0) + return ret; + + /* Frequency */ + wr_val[0] = mc33xs2410_pwm_get_freq(period); + /* Continue calculations with the possibly truncated period */ + period = mc33xs2410_pwm_get_period(wr_val[0]); + + /* Duty cycle */ + duty_cycle = min(period, state->duty_cycle); + rel_dc = div64_u64(duty_cycle * 256, period) - 1; + if (rel_dc >= 0) + wr_val[1] = rel_dc; + else if (state->polarity == PWM_POLARITY_NORMAL) + wr_val[1] = 0; + else + wr_val[1] = 255; + + /* Polarity */ + mask = MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm + 1); + if (state->polarity == PWM_POLARITY_INVERSED && rel_dc >= 0) + wr_val[2] = rd_val[0] | mask; + else + wr_val[2] = rd_val[0] & ~mask; + + /* Enable */ + mask = MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm + 1); + if (state->enabled && + !(state->polarity == PWM_POLARITY_NORMAL && rel_dc < 0)) + wr_val[3] = rd_val[1] | mask; + else + wr_val[3] = rd_val[1] & ~mask; + + return mc33xs2410_write_regs(spi, reg, wr_val, 4); +} + +static int mc33xs2410_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct spi_device *spi = pwmchip_get_drvdata(chip); + u8 reg[4] = { + MC33XS2410_PWM_FREQ(pwm->hwpwm + 1), + MC33XS2410_PWM_DC(pwm->hwpwm + 1), + MC33XS2410_PWM_CTRL1, + MC33XS2410_PWM_CTRL3, + }; + u16 val[4]; + int ret; + + ret = mc33xs2410_read_regs(spi, reg, MC33XS2410_FRAME_IN_DATA_RD, val, + ARRAY_SIZE(reg)); + if (ret < 0) + return ret; + + state->period = mc33xs2410_pwm_get_period(val[0]); + state->polarity = (val[2] & MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm + 1)) ? + PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL; + state->enabled = !!(val[3] & MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm + 1)); + state->duty_cycle = DIV_ROUND_UP_ULL((val[1] + 1) * state->period, 256); + + return 0; +} + +static const struct pwm_ops mc33xs2410_pwm_ops = { + .apply = mc33xs2410_pwm_apply, + .get_state = mc33xs2410_pwm_get_state, +}; + +static int mc33xs2410_reset(struct device *dev) +{ + struct gpio_desc *reset_gpio; + + reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR_OR_NULL(reset_gpio)) + return PTR_ERR_OR_ZERO(reset_gpio); + + /* Wake-up time */ + fsleep(10000); + + return 0; +} + +static int mc33xs2410_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct pwm_chip *chip; + int ret; + + chip = devm_pwmchip_alloc(dev, 4, 0); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + spi->bits_per_word = 16; + spi->mode |= SPI_CS_WORD; + ret = spi_setup(spi); + if (ret < 0) + return ret; + + pwmchip_set_drvdata(chip, spi); + chip->ops = &mc33xs2410_pwm_ops; + + /* + * Deasserts the reset of the device. Shouldn't change the output signal + * if the device was setup prior to probing. + */ + ret = mc33xs2410_reset(dev); + if (ret) + return ret; + + /* + * Disable watchdog and keep in mind that the watchdog won't trigger a + * reset of the machine when running into an timeout, instead the + * control over the outputs is handed over to the INx input logic + * signals of the device. Disabling it here just deactivates this + * feature until a proper solution is found. + */ + ret = mc33xs2410_write_reg(spi, MC33XS2410_WDT, 0x0); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to disable watchdog\n"); + + /* Transition to normal mode */ + ret = mc33xs2410_modify_reg(spi, MC33XS2410_GLB_CTRL, + MC33XS2410_GLB_CTRL_MODE, + MC33XS2410_GLB_CTRL_MODE_NORMAL); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to transition to normal mode\n"); + + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to add pwm chip\n"); + + return 0; +} + +static const struct spi_device_id mc33xs2410_spi_id[] = { + { "mc33xs2410" }, + { } +}; +MODULE_DEVICE_TABLE(spi, mc33xs2410_spi_id); + +static const struct of_device_id mc33xs2410_of_match[] = { + { .compatible = "nxp,mc33xs2410" }, + { } +}; +MODULE_DEVICE_TABLE(of, mc33xs2410_of_match); + +static struct spi_driver mc33xs2410_driver = { + .driver = { + .name = "mc33xs2410-pwm", + .of_match_table = mc33xs2410_of_match, + }, + .probe = mc33xs2410_probe, + .id_table = mc33xs2410_spi_id, +}; +module_spi_driver(mc33xs2410_driver); + +MODULE_DESCRIPTION("NXP MC33XS2410 high-side switch driver"); +MODULE_AUTHOR("Dimitri Fedrau <dimitri.fedrau@liebherr.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-mediatek.c b/drivers/pwm/pwm-mediatek.c index 19a87873ad60..7eaab5831499 100644 --- a/drivers/pwm/pwm-mediatek.c +++ b/drivers/pwm/pwm-mediatek.c @@ -121,21 +121,25 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip); u32 clkdiv = 0, cnt_period, cnt_duty, reg_width = PWMDWIDTH, reg_thres = PWMTHRES; + unsigned long clk_rate; u64 resolution; int ret; ret = pwm_mediatek_clk_enable(chip, pwm); - if (ret < 0) return ret; + clk_rate = clk_get_rate(pc->clk_pwms[pwm->hwpwm]); + if (!clk_rate) + return -EINVAL; + /* Make sure we use the bus clock and not the 26MHz clock */ if (pc->soc->has_ck_26m_sel) writel(0, pc->regs + PWM_CK_26M_SEL); /* Using resolution in picosecond gets accuracy higher */ resolution = (u64)NSEC_PER_SEC * 1000; - do_div(resolution, clk_get_rate(pc->clk_pwms[pwm->hwpwm])); + do_div(resolution, clk_rate); cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution); while (cnt_period > 8191) { @@ -395,4 +399,5 @@ static struct platform_driver pwm_mediatek_driver = { module_platform_driver(pwm_mediatek_driver); MODULE_AUTHOR("John Crispin <blogic@openwrt.org>"); +MODULE_DESCRIPTION("MediaTek general purpose Pulse Width Modulator driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index a02fdbc61256..8c6bf3d49753 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -6,7 +6,7 @@ * PWM output is achieved by calculating a clock that permits calculating * two periods (low and high). The counter then has to be set to switch after * N cycles for the first half period. - * The hardware has no "polarity" setting. This driver reverses the period + * Partly the hardware has no "polarity" setting. This driver reverses the period * cycles (the low length is inverted with the high length) for * PWM_POLARITY_INVERSED. This means that .get_state cannot read the polarity * from the hardware. @@ -56,6 +56,10 @@ #define MISC_B_CLK_SEL_SHIFT 6 #define MISC_A_CLK_SEL_SHIFT 4 #define MISC_CLK_SEL_MASK 0x3 +#define MISC_B_CONSTANT_EN BIT(29) +#define MISC_A_CONSTANT_EN BIT(28) +#define MISC_B_INVERT_EN BIT(27) +#define MISC_A_INVERT_EN BIT(26) #define MISC_B_EN BIT(1) #define MISC_A_EN BIT(0) @@ -68,6 +72,8 @@ static struct meson_pwm_channel_data { u8 clk_div_shift; u8 clk_en_shift; u32 pwm_en_mask; + u32 const_en_mask; + u32 inv_en_mask; } meson_pwm_per_channel_data[MESON_NUM_PWMS] = { { .reg_offset = REG_PWM_A, @@ -75,6 +81,8 @@ static struct meson_pwm_channel_data { .clk_div_shift = MISC_A_CLK_DIV_SHIFT, .clk_en_shift = MISC_A_CLK_EN_SHIFT, .pwm_en_mask = MISC_A_EN, + .const_en_mask = MISC_A_CONSTANT_EN, + .inv_en_mask = MISC_A_INVERT_EN, }, { .reg_offset = REG_PWM_B, @@ -82,6 +90,8 @@ static struct meson_pwm_channel_data { .clk_div_shift = MISC_B_CLK_DIV_SHIFT, .clk_en_shift = MISC_B_CLK_EN_SHIFT, .pwm_en_mask = MISC_B_EN, + .const_en_mask = MISC_B_CONSTANT_EN, + .inv_en_mask = MISC_B_INVERT_EN, } }; @@ -89,6 +99,8 @@ struct meson_pwm_channel { unsigned long rate; unsigned int hi; unsigned int lo; + bool constant; + bool inverted; struct clk_mux mux; struct clk_divider div; @@ -98,6 +110,9 @@ struct meson_pwm_channel { struct meson_pwm_data { const char *const parent_names[MESON_NUM_MUX_PARENTS]; + int (*channels_init)(struct pwm_chip *chip); + bool has_constant; + bool has_polarity; }; struct meson_pwm { @@ -147,7 +162,7 @@ static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm, struct meson_pwm *meson = to_meson_pwm(chip); struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm]; unsigned int cnt, duty_cnt; - unsigned long fin_freq; + long fin_freq; u64 duty, period, freq; duty = state->duty_cycle; @@ -159,7 +174,7 @@ static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm, * Fixing this needs some care however as some machines might rely on * this. */ - if (state->polarity == PWM_POLARITY_INVERSED) + if (state->polarity == PWM_POLARITY_INVERSED && !meson->data->has_polarity) duty = period - duty; freq = div64_u64(NSEC_PER_SEC * 0xffffULL, period); @@ -167,14 +182,15 @@ static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm, freq = ULONG_MAX; fin_freq = clk_round_rate(channel->clk, freq); - if (fin_freq == 0) { - dev_err(pwmchip_parent(chip), "invalid source clock frequency\n"); - return -EINVAL; + if (fin_freq <= 0) { + dev_err(pwmchip_parent(chip), + "invalid source clock frequency %llu\n", freq); + return fin_freq ? fin_freq : -EINVAL; } - dev_dbg(pwmchip_parent(chip), "fin_freq: %lu Hz\n", fin_freq); + dev_dbg(pwmchip_parent(chip), "fin_freq: %ld Hz\n", fin_freq); - cnt = div_u64(fin_freq * period, NSEC_PER_SEC); + cnt = mul_u64_u64_div_u64(fin_freq, period, NSEC_PER_SEC); if (cnt > 0xffff) { dev_err(pwmchip_parent(chip), "unable to get period cnt\n"); return -EINVAL; @@ -185,16 +201,19 @@ static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm, if (duty == period) { channel->hi = cnt; channel->lo = 0; + channel->constant = true; } else if (duty == 0) { channel->hi = 0; channel->lo = cnt; + channel->constant = true; } else { - duty_cnt = div_u64(fin_freq * duty, NSEC_PER_SEC); + duty_cnt = mul_u64_u64_div_u64(fin_freq, duty, NSEC_PER_SEC); dev_dbg(pwmchip_parent(chip), "duty=%llu duty_cnt=%u\n", duty, duty_cnt); channel->hi = duty_cnt; channel->lo = cnt - duty_cnt; + channel->constant = false; } channel->rate = fin_freq; @@ -225,6 +244,19 @@ static void meson_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) value = readl(meson->base + REG_MISC_AB); value |= channel_data->pwm_en_mask; + + if (meson->data->has_constant) { + value &= ~channel_data->const_en_mask; + if (channel->constant) + value |= channel_data->const_en_mask; + } + + if (meson->data->has_polarity) { + value &= ~channel_data->inv_en_mask; + if (channel->inverted) + value |= channel_data->inv_en_mask; + } + writel(value, meson->base + REG_MISC_AB); spin_unlock_irqrestore(&meson->lock, flags); @@ -233,13 +265,24 @@ static void meson_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) static void meson_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { struct meson_pwm *meson = to_meson_pwm(chip); + struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm]; + struct meson_pwm_channel_data *channel_data; unsigned long flags; u32 value; + channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; + spin_lock_irqsave(&meson->lock, flags); value = readl(meson->base + REG_MISC_AB); - value &= ~meson_pwm_per_channel_data[pwm->hwpwm].pwm_en_mask; + value &= ~channel_data->pwm_en_mask; + + if (meson->data->has_polarity) { + value &= ~channel_data->inv_en_mask; + if (channel->inverted) + value |= channel_data->inv_en_mask; + } + writel(value, meson->base + REG_MISC_AB); spin_unlock_irqrestore(&meson->lock, flags); @@ -252,10 +295,12 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm]; int err = 0; + channel->inverted = (state->polarity == PWM_POLARITY_INVERSED); + if (!state->enabled) { - if (state->polarity == PWM_POLARITY_INVERSED) { + if (channel->inverted && !meson->data->has_polarity) { /* - * This IP block revision doesn't have an "always high" + * Some of IP block revisions don't have an "always high" * setting which we can use for "inverted disabled". * Instead we achieve this by setting mux parent with * highest rate and minimum divider value, resulting @@ -269,6 +314,7 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, channel->rate = ULONG_MAX; channel->hi = ~0; channel->lo = 0; + channel->constant = true; meson_pwm_enable(chip, pwm); } else { @@ -285,21 +331,9 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } -static u64 meson_pwm_cnt_to_ns(struct pwm_chip *chip, struct pwm_device *pwm, - u32 cnt) +static u64 meson_pwm_cnt_to_ns(unsigned long fin_freq, u32 cnt) { - struct meson_pwm *meson = to_meson_pwm(chip); - struct meson_pwm_channel *channel; - unsigned long fin_freq; - - /* to_meson_pwm() can only be used after .get_state() is called */ - channel = &meson->channels[pwm->hwpwm]; - - fin_freq = clk_get_rate(channel->clk); - if (fin_freq == 0) - return 0; - - return div64_ul(NSEC_PER_SEC * (u64)cnt, fin_freq); + return fin_freq ? div64_ul(NSEC_PER_SEC * (u64)cnt, fin_freq) : 0; } static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, @@ -307,26 +341,27 @@ static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, { struct meson_pwm *meson = to_meson_pwm(chip); struct meson_pwm_channel_data *channel_data; - struct meson_pwm_channel *channel; + unsigned long fin_freq; + unsigned int hi, lo; u32 value; - if (!state) - return 0; - - channel = &meson->channels[pwm->hwpwm]; channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; + fin_freq = clk_get_rate(meson->channels[pwm->hwpwm].clk); value = readl(meson->base + REG_MISC_AB); state->enabled = value & channel_data->pwm_en_mask; - value = readl(meson->base + channel_data->reg_offset); - channel->lo = FIELD_GET(PWM_LOW_MASK, value); - channel->hi = FIELD_GET(PWM_HIGH_MASK, value); + if (meson->data->has_polarity && (value & channel_data->inv_en_mask)) + state->polarity = PWM_POLARITY_INVERSED; + else + state->polarity = PWM_POLARITY_NORMAL; - state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->lo + channel->hi); - state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, channel->hi); + value = readl(meson->base + channel_data->reg_offset); + lo = FIELD_GET(PWM_LOW_MASK, value); + hi = FIELD_GET(PWM_HIGH_MASK, value); - state->polarity = PWM_POLARITY_NORMAL; + state->period = meson_pwm_cnt_to_ns(fin_freq, lo + hi); + state->duty_cycle = meson_pwm_cnt_to_ns(fin_freq, hi); return 0; } @@ -338,86 +373,16 @@ static const struct pwm_ops meson_pwm_ops = { .get_state = meson_pwm_get_state, }; -static const struct meson_pwm_data pwm_meson8b_data = { - .parent_names = { "xtal", NULL, "fclk_div4", "fclk_div3" }, -}; - -/* - * Only the 2 first inputs of the GXBB AO PWMs are valid - * The last 2 are grounded - */ -static const struct meson_pwm_data pwm_gxbb_ao_data = { - .parent_names = { "xtal", "clk81", NULL, NULL }, -}; - -static const struct meson_pwm_data pwm_axg_ee_data = { - .parent_names = { "xtal", "fclk_div5", "fclk_div4", "fclk_div3" }, -}; - -static const struct meson_pwm_data pwm_axg_ao_data = { - .parent_names = { "xtal", "axg_ao_clk81", "fclk_div4", "fclk_div5" }, -}; - -static const struct meson_pwm_data pwm_g12a_ao_ab_data = { - .parent_names = { "xtal", "g12a_ao_clk81", "fclk_div4", "fclk_div5" }, -}; - -static const struct meson_pwm_data pwm_g12a_ao_cd_data = { - .parent_names = { "xtal", "g12a_ao_clk81", NULL, NULL }, -}; - -static const struct of_device_id meson_pwm_matches[] = { - { - .compatible = "amlogic,meson8b-pwm", - .data = &pwm_meson8b_data - }, - { - .compatible = "amlogic,meson-gxbb-pwm", - .data = &pwm_meson8b_data - }, - { - .compatible = "amlogic,meson-gxbb-ao-pwm", - .data = &pwm_gxbb_ao_data - }, - { - .compatible = "amlogic,meson-axg-ee-pwm", - .data = &pwm_axg_ee_data - }, - { - .compatible = "amlogic,meson-axg-ao-pwm", - .data = &pwm_axg_ao_data - }, - { - .compatible = "amlogic,meson-g12a-ee-pwm", - .data = &pwm_meson8b_data - }, - { - .compatible = "amlogic,meson-g12a-ao-pwm-ab", - .data = &pwm_g12a_ao_ab_data - }, - { - .compatible = "amlogic,meson-g12a-ao-pwm-cd", - .data = &pwm_g12a_ao_cd_data - }, - {}, -}; -MODULE_DEVICE_TABLE(of, meson_pwm_matches); - -static int meson_pwm_init_channels(struct pwm_chip *chip) +static int meson_pwm_init_clocks_meson8b(struct pwm_chip *chip, + struct clk_parent_data *mux_parent_data) { struct meson_pwm *meson = to_meson_pwm(chip); - struct clk_parent_data mux_parent_data[MESON_NUM_MUX_PARENTS] = {}; struct device *dev = pwmchip_parent(chip); unsigned int i; char name[255]; int err; - for (i = 0; i < MESON_NUM_MUX_PARENTS; i++) { - mux_parent_data[i].index = -1; - mux_parent_data[i].name = meson->data->parent_names[i]; - } - - for (i = 0; i < chip->npwm; i++) { + for (i = 0; i < MESON_NUM_PWMS; i++) { struct meson_pwm_channel *channel = &meson->channels[i]; struct clk_parent_data div_parent = {}, gate_parent = {}; struct clk_init_data init = {}; @@ -495,6 +460,192 @@ static int meson_pwm_init_channels(struct pwm_chip *chip) return 0; } +static int meson_pwm_init_channels_meson8b_legacy(struct pwm_chip *chip) +{ + struct clk_parent_data mux_parent_data[MESON_NUM_MUX_PARENTS] = {}; + struct meson_pwm *meson = to_meson_pwm(chip); + int i; + + dev_warn_once(pwmchip_parent(chip), + "using obsolete compatible, please consider updating dt\n"); + + for (i = 0; i < MESON_NUM_MUX_PARENTS; i++) { + mux_parent_data[i].index = -1; + mux_parent_data[i].name = meson->data->parent_names[i]; + } + + return meson_pwm_init_clocks_meson8b(chip, mux_parent_data); +} + +static int meson_pwm_init_channels_meson8b_v2(struct pwm_chip *chip) +{ + struct clk_parent_data mux_parent_data[MESON_NUM_MUX_PARENTS] = {}; + int i; + + /* + * NOTE: Instead of relying on the hard coded names in the driver + * as the legacy version, this relies on DT to provide the list of + * clocks. + * For once, using input numbers actually makes more sense than names. + * Also DT requires clock-names to be explicitly ordered, so there is + * no point bothering with clock names in this case. + */ + for (i = 0; i < MESON_NUM_MUX_PARENTS; i++) + mux_parent_data[i].index = i; + + return meson_pwm_init_clocks_meson8b(chip, mux_parent_data); +} + +static void meson_pwm_s4_put_clk(void *data) +{ + struct clk *clk = data; + + clk_put(clk); +} + +static int meson_pwm_init_channels_s4(struct pwm_chip *chip) +{ + struct device *dev = pwmchip_parent(chip); + struct device_node *np = dev->of_node; + struct meson_pwm *meson = to_meson_pwm(chip); + int i, ret; + + for (i = 0; i < MESON_NUM_PWMS; i++) { + meson->channels[i].clk = of_clk_get(np, i); + if (IS_ERR(meson->channels[i].clk)) + return dev_err_probe(dev, + PTR_ERR(meson->channels[i].clk), + "Failed to get clk\n"); + + ret = devm_add_action_or_reset(dev, meson_pwm_s4_put_clk, + meson->channels[i].clk); + if (ret) + return dev_err_probe(dev, ret, + "Failed to add clk_put action\n"); + } + + return 0; +} + +static const struct meson_pwm_data pwm_meson8b_data = { + .parent_names = { "xtal", NULL, "fclk_div4", "fclk_div3" }, + .channels_init = meson_pwm_init_channels_meson8b_legacy, +}; + +/* + * Only the 2 first inputs of the GXBB AO PWMs are valid + * The last 2 are grounded + */ +static const struct meson_pwm_data pwm_gxbb_ao_data = { + .parent_names = { "xtal", "clk81", NULL, NULL }, + .channels_init = meson_pwm_init_channels_meson8b_legacy, +}; + +static const struct meson_pwm_data pwm_axg_ee_data = { + .parent_names = { "xtal", "fclk_div5", "fclk_div4", "fclk_div3" }, + .channels_init = meson_pwm_init_channels_meson8b_legacy, + .has_constant = true, + .has_polarity = true, +}; + +static const struct meson_pwm_data pwm_axg_ao_data = { + .parent_names = { "xtal", "axg_ao_clk81", "fclk_div4", "fclk_div5" }, + .channels_init = meson_pwm_init_channels_meson8b_legacy, + .has_constant = true, + .has_polarity = true, +}; + +static const struct meson_pwm_data pwm_g12a_ee_data = { + .parent_names = { "xtal", NULL, "fclk_div4", "fclk_div3" }, + .channels_init = meson_pwm_init_channels_meson8b_legacy, + .has_constant = true, + .has_polarity = true, +}; + +static const struct meson_pwm_data pwm_g12a_ao_ab_data = { + .parent_names = { "xtal", "g12a_ao_clk81", "fclk_div4", "fclk_div5" }, + .channels_init = meson_pwm_init_channels_meson8b_legacy, + .has_constant = true, + .has_polarity = true, +}; + +static const struct meson_pwm_data pwm_g12a_ao_cd_data = { + .parent_names = { "xtal", "g12a_ao_clk81", NULL, NULL }, + .channels_init = meson_pwm_init_channels_meson8b_legacy, + .has_constant = true, + .has_polarity = true, +}; + +static const struct meson_pwm_data pwm_meson8_v2_data = { + .channels_init = meson_pwm_init_channels_meson8b_v2, +}; + +static const struct meson_pwm_data pwm_meson_axg_v2_data = { + .channels_init = meson_pwm_init_channels_meson8b_v2, + .has_constant = true, + .has_polarity = true, +}; + +static const struct meson_pwm_data pwm_s4_data = { + .channels_init = meson_pwm_init_channels_s4, + .has_constant = true, + .has_polarity = true, +}; + +static const struct of_device_id meson_pwm_matches[] = { + { + .compatible = "amlogic,meson8-pwm-v2", + .data = &pwm_meson8_v2_data + }, + { + .compatible = "amlogic,meson-axg-pwm-v2", + .data = &pwm_meson_axg_v2_data + }, + { + .compatible = "amlogic,meson-g12-pwm-v2", + .data = &pwm_meson_axg_v2_data + }, + /* The following compatibles are obsolete */ + { + .compatible = "amlogic,meson8b-pwm", + .data = &pwm_meson8b_data + }, + { + .compatible = "amlogic,meson-gxbb-pwm", + .data = &pwm_meson8b_data + }, + { + .compatible = "amlogic,meson-gxbb-ao-pwm", + .data = &pwm_gxbb_ao_data + }, + { + .compatible = "amlogic,meson-axg-ee-pwm", + .data = &pwm_axg_ee_data + }, + { + .compatible = "amlogic,meson-axg-ao-pwm", + .data = &pwm_axg_ao_data + }, + { + .compatible = "amlogic,meson-g12a-ee-pwm", + .data = &pwm_g12a_ee_data + }, + { + .compatible = "amlogic,meson-g12a-ao-pwm-ab", + .data = &pwm_g12a_ao_ab_data + }, + { + .compatible = "amlogic,meson-g12a-ao-pwm-cd", + .data = &pwm_g12a_ao_cd_data + }, + { + .compatible = "amlogic,meson-s4-pwm", + .data = &pwm_s4_data + }, + {}, +}; +MODULE_DEVICE_TABLE(of, meson_pwm_matches); + static int meson_pwm_probe(struct platform_device *pdev) { struct pwm_chip *chip; @@ -515,7 +666,7 @@ static int meson_pwm_probe(struct platform_device *pdev) meson->data = of_device_get_match_data(&pdev->dev); - err = meson_pwm_init_channels(chip); + err = meson->data->channels_init(chip); if (err < 0) return err; diff --git a/drivers/pwm/pwm-microchip-core.c b/drivers/pwm/pwm-microchip-core.c index c1f2287b8e97..12821b4bbf97 100644 --- a/drivers/pwm/pwm-microchip-core.c +++ b/drivers/pwm/pwm-microchip-core.c @@ -327,7 +327,7 @@ static int mchp_core_pwm_apply_locked(struct pwm_chip *chip, struct pwm_device * * mchp_core_pwm_calc_period(). * The period is locked and we cannot change this, so we abort. */ - if (hw_period_steps == MCHPCOREPWM_PERIOD_STEPS_MAX) + if (hw_period_steps > MCHPCOREPWM_PERIOD_STEPS_MAX) return -EINVAL; prescale = hw_prescale; diff --git a/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c index cd51c4a938f5..1858a77401f8 100644 --- a/drivers/pwm/pwm-omap-dmtimer.c +++ b/drivers/pwm/pwm-omap-dmtimer.c @@ -355,7 +355,7 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev) goto err_platdata; } - if (!of_get_property(timer, "ti,timer-pwm", NULL)) { + if (!of_property_read_bool(timer, "ti,timer-pwm")) { dev_err(&pdev->dev, "Missing ti,timer-pwm capability\n"); ret = -ENODEV; goto err_timer_property; @@ -455,7 +455,7 @@ static struct platform_driver pwm_omap_dmtimer_driver = { .of_match_table = pwm_omap_dmtimer_of_match, }, .probe = pwm_omap_dmtimer_probe, - .remove_new = pwm_omap_dmtimer_remove, + .remove = pwm_omap_dmtimer_remove, }; module_platform_driver(pwm_omap_dmtimer_driver); diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index c5da2a6ed846..eb03ccd5b688 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -8,7 +8,6 @@ * based on the pwm-twl-led.c driver */ -#include <linux/acpi.h> #include <linux/gpio/driver.h> #include <linux/i2c.h> #include <linux/module.h> @@ -264,12 +263,14 @@ static int pca9685_pwm_gpio_get(struct gpio_chip *gpio, unsigned int offset) return pca9685_pwm_get_duty(chip, offset) != 0; } -static void pca9685_pwm_gpio_set(struct gpio_chip *gpio, unsigned int offset, - int value) +static int pca9685_pwm_gpio_set(struct gpio_chip *gpio, unsigned int offset, + int value) { struct pwm_chip *chip = gpiochip_get_data(gpio); pca9685_pwm_set_duty(chip, offset, value ? PCA9685_COUNTER_RANGE : 0); + + return 0; } static void pca9685_pwm_gpio_free(struct gpio_chip *gpio, unsigned int offset) @@ -322,7 +323,7 @@ static int pca9685_pwm_gpio_probe(struct pwm_chip *chip) pca->gpio.direction_input = pca9685_pwm_gpio_direction_input; pca->gpio.direction_output = pca9685_pwm_gpio_direction_output; pca->gpio.get = pca9685_pwm_gpio_get; - pca->gpio.set = pca9685_pwm_gpio_set; + pca->gpio.set_rv = pca9685_pwm_gpio_set; pca->gpio.base = -1; pca->gpio.ngpio = PCA9685_MAXCHAN; pca->gpio.can_sleep = true; @@ -634,26 +635,22 @@ static int __maybe_unused pca9685_pwm_runtime_resume(struct device *dev) } static const struct i2c_device_id pca9685_id[] = { - { "pca9685", 0 }, - { /* sentinel */ }, + { "pca9685" }, + { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, pca9685_id); -#ifdef CONFIG_ACPI static const struct acpi_device_id pca9685_acpi_ids[] = { { "INT3492", 0 }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(acpi, pca9685_acpi_ids); -#endif -#ifdef CONFIG_OF static const struct of_device_id pca9685_dt_ids[] = { { .compatible = "nxp,pca9685-pwm", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, pca9685_dt_ids); -#endif static const struct dev_pm_ops pca9685_pwm_pm = { SET_RUNTIME_PM_OPS(pca9685_pwm_runtime_suspend, @@ -663,8 +660,8 @@ static const struct dev_pm_ops pca9685_pwm_pm = { static struct i2c_driver pca9685_i2c_driver = { .driver = { .name = "pca9685-pwm", - .acpi_match_table = ACPI_PTR(pca9685_acpi_ids), - .of_match_table = of_match_ptr(pca9685_dt_ids), + .acpi_match_table = pca9685_acpi_ids, + .of_match_table = pca9685_dt_ids, .pm = &pca9685_pwm_pm, }, .probe = pca9685_pwm_probe, diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c index bb7bb48b2e6d..8a4a3d2df30d 100644 --- a/drivers/pwm/pwm-pxa.c +++ b/drivers/pwm/pwm-pxa.c @@ -160,24 +160,24 @@ static int pwm_probe(struct platform_device *pdev) const struct platform_device_id *id = platform_get_device_id(pdev); struct pwm_chip *chip; struct pxa_pwm_chip *pc; + struct device *dev = &pdev->dev; int ret = 0; if (IS_ENABLED(CONFIG_OF) && id == NULL) - id = of_device_get_match_data(&pdev->dev); + id = of_device_get_match_data(dev); if (id == NULL) return -EINVAL; - chip = devm_pwmchip_alloc(&pdev->dev, - (id->driver_data & HAS_SECONDARY_PWM) ? 2 : 1, + chip = devm_pwmchip_alloc(dev, (id->driver_data & HAS_SECONDARY_PWM) ? 2 : 1, sizeof(*pc)); if (IS_ERR(chip)) return PTR_ERR(chip); pc = to_pxa_pwm_chip(chip); - pc->clk = devm_clk_get(&pdev->dev, NULL); + pc->clk = devm_clk_get(dev, NULL); if (IS_ERR(pc->clk)) - return PTR_ERR(pc->clk); + return dev_err_probe(dev, PTR_ERR(pc->clk), "Failed to get clock\n"); chip->ops = &pxa_pwm_ops; @@ -188,11 +188,9 @@ static int pwm_probe(struct platform_device *pdev) if (IS_ERR(pc->mmio_base)) return PTR_ERR(pc->mmio_base); - ret = devm_pwmchip_add(&pdev->dev, chip); - if (ret < 0) { - dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); - return ret; - } + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "pwmchip_add() failed\n"); return 0; } @@ -208,4 +206,5 @@ static struct platform_driver pwm_driver = { module_platform_driver(pwm_driver); +MODULE_DESCRIPTION("PXA Pulse Width Modulator driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-rcar.c b/drivers/pwm/pwm-rcar.c index 4cfecd88ede0..578dbdd2d5a7 100644 --- a/drivers/pwm/pwm-rcar.c +++ b/drivers/pwm/pwm-rcar.c @@ -8,6 +8,7 @@ * - The hardware cannot generate a 0% duty cycle. */ +#include <linux/bitfield.h> #include <linux/clk.h> #include <linux/err.h> #include <linux/io.h> @@ -102,23 +103,24 @@ static void rcar_pwm_set_clock_control(struct rcar_pwm_chip *rp, rcar_pwm_write(rp, value, RCAR_PWMCR); } -static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, int duty_ns, - int period_ns) +static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, u64 duty_ns, + u64 period_ns) { - unsigned long long one_cycle, tmp; /* 0.01 nanoseconds */ + unsigned long long tmp; unsigned long clk_rate = clk_get_rate(rp->clk); u32 cyc, ph; - one_cycle = NSEC_PER_SEC * 100ULL << div; - do_div(one_cycle, clk_rate); + /* div <= 24 == RCAR_PWM_MAX_DIVISION, so the shift doesn't overflow. */ + tmp = mul_u64_u64_div_u64(period_ns, clk_rate, (u64)NSEC_PER_SEC << div); + if (tmp > FIELD_MAX(RCAR_PWMCNT_CYC0_MASK)) + tmp = FIELD_MAX(RCAR_PWMCNT_CYC0_MASK); - tmp = period_ns * 100ULL; - do_div(tmp, one_cycle); - cyc = (tmp << RCAR_PWMCNT_CYC0_SHIFT) & RCAR_PWMCNT_CYC0_MASK; + cyc = FIELD_PREP(RCAR_PWMCNT_CYC0_MASK, tmp); - tmp = duty_ns * 100ULL; - do_div(tmp, one_cycle); - ph = tmp & RCAR_PWMCNT_PH0_MASK; + tmp = mul_u64_u64_div_u64(duty_ns, clk_rate, (u64)NSEC_PER_SEC << div); + if (tmp > FIELD_MAX(RCAR_PWMCNT_PH0_MASK)) + tmp = FIELD_MAX(RCAR_PWMCNT_PH0_MASK); + ph = FIELD_PREP(RCAR_PWMCNT_PH0_MASK, tmp); /* Avoid prohibited setting */ if (cyc == 0 || ph == 0) @@ -253,7 +255,7 @@ MODULE_DEVICE_TABLE(of, rcar_pwm_of_table); static struct platform_driver rcar_pwm_driver = { .probe = rcar_pwm_probe, - .remove_new = rcar_pwm_remove, + .remove = rcar_pwm_remove, .driver = { .name = "pwm-rcar", .of_match_table = rcar_pwm_of_table, diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c index 0fa7575dbb54..c5f50e5eaf41 100644 --- a/drivers/pwm/pwm-rockchip.c +++ b/drivers/pwm/pwm-rockchip.c @@ -386,7 +386,7 @@ static struct platform_driver rockchip_pwm_driver = { .of_match_table = rockchip_pwm_dt_ids, }, .probe = rockchip_pwm_probe, - .remove_new = rockchip_pwm_remove, + .remove = rockchip_pwm_remove, }; module_platform_driver(rockchip_pwm_driver); diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c new file mode 100644 index 000000000000..360c8bf3b190 --- /dev/null +++ b/drivers/pwm/pwm-rzg2l-gpt.c @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/G2L General PWM Timer (GPT) driver + * + * Copyright (C) 2025 Renesas Electronics Corporation + * + * Hardware manual for this IP can be found here + * https://www.renesas.com/eu/en/document/mah/rzg2l-group-rzg2lc-group-users-manual-hardware-0?language=en + * + * Limitations: + * - Counter must be stopped before modifying Mode and Prescaler. + * - When PWM is disabled, the output is driven to inactive. + * - While the hardware supports both polarities, the driver (for now) + * only handles normal polarity. + * - General PWM Timer (GPT) has 8 HW channels for PWM operations and + * each HW channel have 2 IOs. + * - Each IO is modelled as an independent PWM channel. + * - When both channels are used, disabling the channel on one stops the + * other. + * - When both channels are used, the period of both IOs in the HW channel + * must be same (for now). + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/reset.h> +#include <linux/time.h> +#include <linux/units.h> + +#define RZG2L_GET_CH(hwpwm) ((hwpwm) / 2) +#define RZG2L_GET_CH_OFFS(ch) (0x100 * (ch)) + +#define RZG2L_GTCR(ch) (0x2c + RZG2L_GET_CH_OFFS(ch)) +#define RZG2L_GTUDDTYC(ch) (0x30 + RZG2L_GET_CH_OFFS(ch)) +#define RZG2L_GTIOR(ch) (0x34 + RZG2L_GET_CH_OFFS(ch)) +#define RZG2L_GTBER(ch) (0x40 + RZG2L_GET_CH_OFFS(ch)) +#define RZG2L_GTCNT(ch) (0x48 + RZG2L_GET_CH_OFFS(ch)) +#define RZG2L_GTCCR(ch, sub_ch) (0x4c + RZG2L_GET_CH_OFFS(ch) + 4 * (sub_ch)) +#define RZG2L_GTPR(ch) (0x64 + RZG2L_GET_CH_OFFS(ch)) + +#define RZG2L_GTCR_CST BIT(0) +#define RZG2L_GTCR_MD GENMASK(18, 16) +#define RZG2L_GTCR_TPCS GENMASK(26, 24) + +#define RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE FIELD_PREP(RZG2L_GTCR_MD, 0) + +#define RZG2L_GTUDDTYC_UP BIT(0) +#define RZG2L_GTUDDTYC_UDF BIT(1) +#define RZG2L_GTUDDTYC_UP_COUNTING (RZG2L_GTUDDTYC_UP | RZG2L_GTUDDTYC_UDF) + +#define RZG2L_GTIOR_GTIOA GENMASK(4, 0) +#define RZG2L_GTIOR_GTIOB GENMASK(20, 16) +#define RZG2L_GTIOR_GTIOx(sub_ch) ((sub_ch) ? RZG2L_GTIOR_GTIOB : RZG2L_GTIOR_GTIOA) +#define RZG2L_GTIOR_OAE BIT(8) +#define RZG2L_GTIOR_OBE BIT(24) +#define RZG2L_GTIOR_OxE(sub_ch) ((sub_ch) ? RZG2L_GTIOR_OBE : RZG2L_GTIOR_OAE) + +#define RZG2L_INIT_OUT_HI_OUT_HI_END_TOGGLE 0x1b +#define RZG2L_GTIOR_GTIOA_OUT_HI_END_TOGGLE_CMP_MATCH \ + (RZG2L_INIT_OUT_HI_OUT_HI_END_TOGGLE | RZG2L_GTIOR_OAE) +#define RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH \ + (FIELD_PREP(RZG2L_GTIOR_GTIOB, RZG2L_INIT_OUT_HI_OUT_HI_END_TOGGLE) | RZG2L_GTIOR_OBE) + +#define RZG2L_GTIOR_GTIOx_OUT_HI_END_TOGGLE_CMP_MATCH(sub_ch) \ + ((sub_ch) ? RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH : \ + RZG2L_GTIOR_GTIOA_OUT_HI_END_TOGGLE_CMP_MATCH) + +#define RZG2L_MAX_HW_CHANNELS 8 +#define RZG2L_CHANNELS_PER_IO 2 +#define RZG2L_MAX_PWM_CHANNELS (RZG2L_MAX_HW_CHANNELS * RZG2L_CHANNELS_PER_IO) +#define RZG2L_MAX_SCALE_FACTOR 1024 +#define RZG2L_MAX_TICKS ((u64)U32_MAX * RZG2L_MAX_SCALE_FACTOR) + +struct rzg2l_gpt_chip { + void __iomem *mmio; + struct mutex lock; /* lock to protect shared channel resources */ + unsigned long rate_khz; + u32 period_ticks[RZG2L_MAX_HW_CHANNELS]; + u32 channel_request_count[RZG2L_MAX_HW_CHANNELS]; + u32 channel_enable_count[RZG2L_MAX_HW_CHANNELS]; +}; + +static inline struct rzg2l_gpt_chip *to_rzg2l_gpt_chip(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static inline unsigned int rzg2l_gpt_subchannel(unsigned int hwpwm) +{ + return hwpwm & 0x1; +} + +static void rzg2l_gpt_write(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg, u32 data) +{ + writel(data, rzg2l_gpt->mmio + reg); +} + +static u32 rzg2l_gpt_read(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg) +{ + return readl(rzg2l_gpt->mmio + reg); +} + +static void rzg2l_gpt_modify(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg, u32 clr, + u32 set) +{ + rzg2l_gpt_write(rzg2l_gpt, reg, + (rzg2l_gpt_read(rzg2l_gpt, reg) & ~clr) | set); +} + +static u8 rzg2l_gpt_calculate_prescale(struct rzg2l_gpt_chip *rzg2l_gpt, + u64 period_ticks) +{ + u32 prescaled_period_ticks; + u8 prescale; + + prescaled_period_ticks = period_ticks >> 32; + if (prescaled_period_ticks >= 256) + prescale = 5; + else + prescale = (fls(prescaled_period_ticks) + 1) / 2; + + return prescale; +} + +static int rzg2l_gpt_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip); + u32 ch = RZG2L_GET_CH(pwm->hwpwm); + + guard(mutex)(&rzg2l_gpt->lock); + rzg2l_gpt->channel_request_count[ch]++; + + return 0; +} + +static void rzg2l_gpt_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip); + u32 ch = RZG2L_GET_CH(pwm->hwpwm); + + guard(mutex)(&rzg2l_gpt->lock); + rzg2l_gpt->channel_request_count[ch]--; +} + +static bool rzg2l_gpt_is_ch_enabled(struct rzg2l_gpt_chip *rzg2l_gpt, u8 hwpwm) +{ + u8 ch = RZG2L_GET_CH(hwpwm); + u32 val; + + val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCR(ch)); + if (!(val & RZG2L_GTCR_CST)) + return false; + + val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTIOR(ch)); + + return val & RZG2L_GTIOR_OxE(rzg2l_gpt_subchannel(hwpwm)); +} + +/* Caller holds the lock while calling rzg2l_gpt_enable() */ +static void rzg2l_gpt_enable(struct rzg2l_gpt_chip *rzg2l_gpt, + struct pwm_device *pwm) +{ + u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm); + u32 val = RZG2L_GTIOR_GTIOx(sub_ch) | RZG2L_GTIOR_OxE(sub_ch); + u8 ch = RZG2L_GET_CH(pwm->hwpwm); + + /* Enable pin output */ + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTIOR(ch), val, + RZG2L_GTIOR_GTIOx_OUT_HI_END_TOGGLE_CMP_MATCH(sub_ch)); + + if (!rzg2l_gpt->channel_enable_count[ch]) + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), 0, RZG2L_GTCR_CST); + + rzg2l_gpt->channel_enable_count[ch]++; +} + +/* Caller holds the lock while calling rzg2l_gpt_disable() */ +static void rzg2l_gpt_disable(struct rzg2l_gpt_chip *rzg2l_gpt, + struct pwm_device *pwm) +{ + u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm); + u8 ch = RZG2L_GET_CH(pwm->hwpwm); + + /* Stop count, Output low on GTIOCx pin when counting stops */ + rzg2l_gpt->channel_enable_count[ch]--; + + if (!rzg2l_gpt->channel_enable_count[ch]) + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_CST, 0); + + /* Disable pin output */ + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTIOR(ch), RZG2L_GTIOR_OxE(sub_ch), 0); +} + +static u64 rzg2l_gpt_calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt, + u32 val, u8 prescale) +{ + u64 tmp; + + /* + * The calculation doesn't overflow an u64 because prescale ≤ 5 and so + * tmp = val << (2 * prescale) * USEC_PER_SEC + * < 2^32 * 2^10 * 10^6 + * < 2^32 * 2^10 * 2^20 + * = 2^62 + */ + tmp = (u64)val << (2 * prescale); + tmp *= USEC_PER_SEC; + + return DIV64_U64_ROUND_UP(tmp, rzg2l_gpt->rate_khz); +} + +static int rzg2l_gpt_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip); + + state->enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm); + if (state->enabled) { + u32 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm); + u32 ch = RZG2L_GET_CH(pwm->hwpwm); + u8 prescale; + u32 val; + + val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCR(ch)); + prescale = FIELD_GET(RZG2L_GTCR_TPCS, val); + + val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTPR(ch)); + state->period = rzg2l_gpt_calculate_period_or_duty(rzg2l_gpt, val, prescale); + + val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch)); + state->duty_cycle = rzg2l_gpt_calculate_period_or_duty(rzg2l_gpt, val, prescale); + if (state->duty_cycle > state->period) + state->duty_cycle = state->period; + } + + state->polarity = PWM_POLARITY_NORMAL; + + return 0; +} + +static u32 rzg2l_gpt_calculate_pv_or_dc(u64 period_or_duty_cycle, u8 prescale) +{ + return min_t(u64, DIV_ROUND_DOWN_ULL(period_or_duty_cycle, 1 << (2 * prescale)), + U32_MAX); +} + +/* Caller holds the lock while calling rzg2l_gpt_config() */ +static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip); + u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm); + u8 ch = RZG2L_GET_CH(pwm->hwpwm); + u64 period_ticks, duty_ticks; + unsigned long pv, dc; + u8 prescale; + + /* Limit period/duty cycle to max value supported by the HW */ + period_ticks = mul_u64_u64_div_u64(state->period, rzg2l_gpt->rate_khz, USEC_PER_SEC); + if (period_ticks > RZG2L_MAX_TICKS) + period_ticks = RZG2L_MAX_TICKS; + /* + * GPT counter is shared by the two IOs of a single channel, so + * prescale and period can NOT be modified when there are multiple IOs + * in use with different settings. + */ + if (rzg2l_gpt->channel_request_count[ch] > 1) { + if (period_ticks < rzg2l_gpt->period_ticks[ch]) + return -EBUSY; + else + period_ticks = rzg2l_gpt->period_ticks[ch]; + } + + prescale = rzg2l_gpt_calculate_prescale(rzg2l_gpt, period_ticks); + pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, prescale); + + duty_ticks = mul_u64_u64_div_u64(state->duty_cycle, rzg2l_gpt->rate_khz, USEC_PER_SEC); + if (duty_ticks > period_ticks) + duty_ticks = period_ticks; + dc = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, prescale); + + /* + * GPT counter is shared by multiple channels, we cache the period ticks + * from the first enabled channel and use the same value for both + * channels. + */ + rzg2l_gpt->period_ticks[ch] = period_ticks; + + /* + * Counter must be stopped before modifying mode, prescaler, timer + * counter and buffer enable registers. These registers are shared + * between both channels. So allow updating these registers only for the + * first enabled channel. + */ + if (rzg2l_gpt->channel_enable_count[ch] <= 1) { + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_CST, 0); + + /* GPT set operating mode (saw-wave up-counting) */ + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_MD, + RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE); + + /* Set count direction */ + rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTUDDTYC(ch), RZG2L_GTUDDTYC_UP_COUNTING); + + /* Select count clock */ + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_TPCS, + FIELD_PREP(RZG2L_GTCR_TPCS, prescale)); + + /* Set period */ + rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTPR(ch), pv); + } + + /* Set duty cycle */ + rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch), dc); + + if (rzg2l_gpt->channel_enable_count[ch] <= 1) { + /* Set initial value for counter */ + rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTCNT(ch), 0); + + /* Set no buffer operation */ + rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTBER(ch), 0); + + /* Restart the counter after updating the registers */ + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), + RZG2L_GTCR_CST, RZG2L_GTCR_CST); + } + + return 0; +} + +static int rzg2l_gpt_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip); + bool enabled = pwm->state.enabled; + int ret; + + if (state->polarity != PWM_POLARITY_NORMAL) + return -EINVAL; + + guard(mutex)(&rzg2l_gpt->lock); + if (!state->enabled) { + if (enabled) + rzg2l_gpt_disable(rzg2l_gpt, pwm); + + return 0; + } + + ret = rzg2l_gpt_config(chip, pwm, state); + if (!ret && !enabled) + rzg2l_gpt_enable(rzg2l_gpt, pwm); + + return ret; +} + +static const struct pwm_ops rzg2l_gpt_ops = { + .request = rzg2l_gpt_request, + .free = rzg2l_gpt_free, + .get_state = rzg2l_gpt_get_state, + .apply = rzg2l_gpt_apply, +}; + +static int rzg2l_gpt_probe(struct platform_device *pdev) +{ + struct rzg2l_gpt_chip *rzg2l_gpt; + struct device *dev = &pdev->dev; + struct reset_control *rstc; + struct pwm_chip *chip; + unsigned long rate; + struct clk *clk; + int ret; + + chip = devm_pwmchip_alloc(dev, RZG2L_MAX_PWM_CHANNELS, sizeof(*rzg2l_gpt)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + rzg2l_gpt = to_rzg2l_gpt_chip(chip); + + rzg2l_gpt->mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(rzg2l_gpt->mmio)) + return PTR_ERR(rzg2l_gpt->mmio); + + rstc = devm_reset_control_get_exclusive_deasserted(dev, NULL); + if (IS_ERR(rstc)) + return dev_err_probe(dev, PTR_ERR(rstc), "Cannot deassert reset control\n"); + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Cannot get clock\n"); + + ret = devm_clk_rate_exclusive_get(dev, clk); + if (ret) + return ret; + + rate = clk_get_rate(clk); + if (!rate) + return dev_err_probe(dev, -EINVAL, "The gpt clk rate is 0"); + + /* + * Refuse clk rates > 1 GHz to prevent overflow later for computing + * period and duty cycle. + */ + if (rate > NSEC_PER_SEC) + return dev_err_probe(dev, -EINVAL, "The gpt clk rate is > 1GHz"); + + /* + * Rate is in MHz and is always integer for peripheral clk + * 2^32 * 2^10 (prescalar) * 10^6 (rate_khz) < 2^64 + * So make sure rate is multiple of 1000. + */ + rzg2l_gpt->rate_khz = rate / KILO; + if (rzg2l_gpt->rate_khz * KILO != rate) + return dev_err_probe(dev, -EINVAL, "Rate is not multiple of 1000"); + + mutex_init(&rzg2l_gpt->lock); + + chip->ops = &rzg2l_gpt_ops; + ret = devm_pwmchip_add(dev, chip); + if (ret) + return dev_err_probe(dev, ret, "Failed to add PWM chip\n"); + + return 0; +} + +static const struct of_device_id rzg2l_gpt_of_table[] = { + { .compatible = "renesas,rzg2l-gpt", }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rzg2l_gpt_of_table); + +static struct platform_driver rzg2l_gpt_driver = { + .driver = { + .name = "pwm-rzg2l-gpt", + .of_match_table = rzg2l_gpt_of_table, + }, + .probe = rzg2l_gpt_probe, +}; +module_platform_driver(rzg2l_gpt_driver); + +MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>"); +MODULE_DESCRIPTION("Renesas RZ/G2L General PWM Timer (GPT) Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c index efb60c9f0cb3..951b38ff5f8e 100644 --- a/drivers/pwm/pwm-samsung.c +++ b/drivers/pwm/pwm-samsung.c @@ -510,8 +510,6 @@ static int pwm_samsung_parse_dt(struct pwm_chip *chip) struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); struct device_node *np = pwmchip_parent(chip)->of_node; const struct of_device_id *match; - struct property *prop; - const __be32 *cur; u32 val; match = of_match_node(samsung_pwm_matches, np); @@ -520,7 +518,7 @@ static int pwm_samsung_parse_dt(struct pwm_chip *chip) memcpy(&our_chip->variant, match->data, sizeof(our_chip->variant)); - of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) { + of_property_for_each_u32(np, "samsung,pwm-outputs", val) { if (val >= SAMSUNG_PWM_NUM) { dev_err(pwmchip_parent(chip), "%s: invalid channel index in samsung,pwm-outputs property\n", @@ -644,6 +642,7 @@ static struct platform_driver pwm_samsung_driver = { }; module_platform_driver(pwm_samsung_driver); +MODULE_DESCRIPTION("Samsung Pulse Width Modulator driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Tomasz Figa <tomasz.figa@gmail.com>"); MODULE_ALIAS("platform:samsung-pwm"); diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c index ed7957cc51fd..d5b647e6be78 100644 --- a/drivers/pwm/pwm-sifive.c +++ b/drivers/pwm/pwm-sifive.c @@ -336,7 +336,7 @@ MODULE_DEVICE_TABLE(of, pwm_sifive_of_match); static struct platform_driver pwm_sifive_driver = { .probe = pwm_sifive_probe, - .remove_new = pwm_sifive_remove, + .remove = pwm_sifive_remove, .driver = { .name = "pwm-sifive", .of_match_table = pwm_sifive_of_match, diff --git a/drivers/pwm/pwm-sophgo-sg2042.c b/drivers/pwm/pwm-sophgo-sg2042.c new file mode 100644 index 000000000000..ff4639d849ce --- /dev/null +++ b/drivers/pwm/pwm-sophgo-sg2042.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sophgo SG2042 PWM Controller Driver + * + * Copyright (C) 2024 Sophgo Technology Inc. + * Copyright (C) 2024 Chen Wang <unicorn_wang@outlook.com> + * + * Limitations: + * - After reset, the output of the PWM channel is always high. + * The value of HLPERIOD/PERIOD is 0. + * - When HLPERIOD or PERIOD is reconfigured, PWM will start to + * output waveforms with the new configuration after completing + * the running period. + * - When PERIOD and HLPERIOD is set to 0, the PWM wave output will + * be stopped and the output is pulled to high. + * See the datasheet [1] for more details. + * [1]:https://github.com/sophgo/sophgo-doc/tree/main/SG2042/TRM + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/reset.h> + +/* + * Offset RegisterName + * 0x0000 HLPERIOD0 + * 0x0004 PERIOD0 + * 0x0008 HLPERIOD1 + * 0x000C PERIOD1 + * 0x0010 HLPERIOD2 + * 0x0014 PERIOD2 + * 0x0018 HLPERIOD3 + * 0x001C PERIOD3 + * Four groups and every group is composed of HLPERIOD & PERIOD + */ +#define SG2042_PWM_HLPERIOD(chan) ((chan) * 8 + 0) +#define SG2042_PWM_PERIOD(chan) ((chan) * 8 + 4) + +#define SG2042_PWM_CHANNELNUM 4 + +/** + * struct sg2042_pwm_ddata - private driver data + * @base: base address of mapped PWM registers + * @clk_rate_hz: rate of base clock in HZ + */ +struct sg2042_pwm_ddata { + void __iomem *base; + unsigned long clk_rate_hz; +}; + +/* + * period_ticks: PERIOD + * hlperiod_ticks: HLPERIOD + */ +static void pwm_sg2042_config(struct sg2042_pwm_ddata *ddata, unsigned int chan, + u32 period_ticks, u32 hlperiod_ticks) +{ + void __iomem *base = ddata->base; + + writel(period_ticks, base + SG2042_PWM_PERIOD(chan)); + writel(hlperiod_ticks, base + SG2042_PWM_HLPERIOD(chan)); +} + +static int pwm_sg2042_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip); + u32 hlperiod_ticks; + u32 period_ticks; + + if (state->polarity == PWM_POLARITY_INVERSED) + return -EINVAL; + + if (!state->enabled) { + pwm_sg2042_config(ddata, pwm->hwpwm, 0, 0); + return 0; + } + + /* + * Duration of High level (duty_cycle) = HLPERIOD x Period_of_input_clk + * Duration of One Cycle (period) = PERIOD x Period_of_input_clk + */ + period_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->period, NSEC_PER_SEC), U32_MAX); + hlperiod_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->duty_cycle, NSEC_PER_SEC), U32_MAX); + + dev_dbg(pwmchip_parent(chip), "chan[%u]: PERIOD=%u, HLPERIOD=%u\n", + pwm->hwpwm, period_ticks, hlperiod_ticks); + + pwm_sg2042_config(ddata, pwm->hwpwm, period_ticks, hlperiod_ticks); + + return 0; +} + +static int pwm_sg2042_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip); + unsigned int chan = pwm->hwpwm; + u32 hlperiod_ticks; + u32 period_ticks; + + period_ticks = readl(ddata->base + SG2042_PWM_PERIOD(chan)); + hlperiod_ticks = readl(ddata->base + SG2042_PWM_HLPERIOD(chan)); + + if (!period_ticks) { + state->enabled = false; + return 0; + } + + if (hlperiod_ticks > period_ticks) + hlperiod_ticks = period_ticks; + + state->enabled = true; + state->period = DIV_ROUND_UP_ULL((u64)period_ticks * NSEC_PER_SEC, ddata->clk_rate_hz); + state->duty_cycle = DIV_ROUND_UP_ULL((u64)hlperiod_ticks * NSEC_PER_SEC, ddata->clk_rate_hz); + state->polarity = PWM_POLARITY_NORMAL; + + return 0; +} + +static const struct pwm_ops pwm_sg2042_ops = { + .apply = pwm_sg2042_apply, + .get_state = pwm_sg2042_get_state, +}; + +static const struct of_device_id sg2042_pwm_ids[] = { + { .compatible = "sophgo,sg2042-pwm" }, + { } +}; +MODULE_DEVICE_TABLE(of, sg2042_pwm_ids); + +static int pwm_sg2042_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sg2042_pwm_ddata *ddata; + struct reset_control *rst; + struct pwm_chip *chip; + struct clk *clk; + int ret; + + chip = devm_pwmchip_alloc(dev, SG2042_PWM_CHANNELNUM, sizeof(*ddata)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + ddata = pwmchip_get_drvdata(chip); + + ddata->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ddata->base)) + return PTR_ERR(ddata->base); + + clk = devm_clk_get_enabled(dev, "apb"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get base clk\n"); + + ret = devm_clk_rate_exclusive_get(dev, clk); + if (ret) + return dev_err_probe(dev, ret, "Failed to get exclusive rate\n"); + + ddata->clk_rate_hz = clk_get_rate(clk); + /* period = PERIOD * NSEC_PER_SEC / clk_rate_hz */ + if (!ddata->clk_rate_hz || ddata->clk_rate_hz > NSEC_PER_SEC) + return dev_err_probe(dev, -EINVAL, + "Invalid clock rate: %lu\n", ddata->clk_rate_hz); + + rst = devm_reset_control_get_optional_shared_deasserted(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n"); + + chip->ops = &pwm_sg2042_ops; + chip->atomic = true; + + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to register PWM chip\n"); + + return 0; +} + +static struct platform_driver pwm_sg2042_driver = { + .driver = { + .name = "sg2042-pwm", + .of_match_table = sg2042_pwm_ids, + }, + .probe = pwm_sg2042_probe, +}; +module_platform_driver(pwm_sg2042_driver); + +MODULE_AUTHOR("Chen Wang"); +MODULE_DESCRIPTION("Sophgo SG2042 PWM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-spear.c b/drivers/pwm/pwm-spear.c index 6c6f3b38c835..4f372279f313 100644 --- a/drivers/pwm/pwm-spear.c +++ b/drivers/pwm/pwm-spear.c @@ -255,6 +255,7 @@ static struct platform_driver spear_pwm_driver = { module_platform_driver(spear_pwm_driver); +MODULE_DESCRIPTION("ST Microelectronics SPEAr Pulse Width Modulator driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Shiraz Hashim <shiraz.linux.kernel@gmail.com>"); MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.com>"); diff --git a/drivers/pwm/pwm-sti.c b/drivers/pwm/pwm-sti.c index 39d80da0e14a..396b52672ce0 100644 --- a/drivers/pwm/pwm-sti.c +++ b/drivers/pwm/pwm-sti.c @@ -73,21 +73,16 @@ struct sti_cpt_ddata { wait_queue_head_t wait; }; -struct sti_pwm_compat_data { - const struct reg_field *reg_fields; - unsigned int pwm_num_devs; - unsigned int cpt_num_devs; - unsigned int max_pwm_cnt; - unsigned int max_prescale; - struct sti_cpt_ddata *ddata; -}; - struct sti_pwm_chip { struct device *dev; struct clk *pwm_clk; struct clk *cpt_clk; struct regmap *regmap; - struct sti_pwm_compat_data *cdata; + unsigned int pwm_num_devs; + unsigned int cpt_num_devs; + unsigned int max_pwm_cnt; + unsigned int max_prescale; + struct sti_cpt_ddata *ddata; struct regmap_field *prescale_low; struct regmap_field *prescale_high; struct regmap_field *pwm_out_en; @@ -122,7 +117,6 @@ static inline struct sti_pwm_chip *to_sti_pwmchip(struct pwm_chip *chip) static int sti_pwm_get_prescale(struct sti_pwm_chip *pc, unsigned long period, unsigned int *prescale) { - struct sti_pwm_compat_data *cdata = pc->cdata; unsigned long clk_rate; unsigned long value; unsigned int ps; @@ -137,13 +131,13 @@ static int sti_pwm_get_prescale(struct sti_pwm_chip *pc, unsigned long period, * prescale = ((period_ns * clk_rate) / (10^9 * (max_pwm_cnt + 1)) - 1 */ value = NSEC_PER_SEC / clk_rate; - value *= cdata->max_pwm_cnt + 1; + value *= pc->max_pwm_cnt + 1; if (period % value) return -EINVAL; ps = period / value - 1; - if (ps > cdata->max_prescale) + if (ps > pc->max_prescale) return -EINVAL; *prescale = ps; @@ -164,7 +158,6 @@ static int sti_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct sti_pwm_chip *pc = to_sti_pwmchip(chip); - struct sti_pwm_compat_data *cdata = pc->cdata; unsigned int ncfg, value, prescale = 0; struct pwm_device *cur = pc->cur; struct device *dev = pc->dev; @@ -224,7 +217,7 @@ static int sti_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, * PWM pulse = (max_pwm_count + 1) local cycles, * that is continuous pulse: signal never goes low. */ - value = cdata->max_pwm_cnt * duty_ns / period_ns; + value = pc->max_pwm_cnt * duty_ns / period_ns; ret = regmap_write(pc->regmap, PWM_OUT_VAL(pwm->hwpwm), value); if (ret) @@ -313,14 +306,13 @@ static int sti_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_capture *result, unsigned long timeout) { struct sti_pwm_chip *pc = to_sti_pwmchip(chip); - struct sti_pwm_compat_data *cdata = pc->cdata; - struct sti_cpt_ddata *ddata = &cdata->ddata[pwm->hwpwm]; + struct sti_cpt_ddata *ddata = &pc->ddata[pwm->hwpwm]; struct device *dev = pc->dev; unsigned int effective_ticks; unsigned long long high, low; int ret; - if (pwm->hwpwm >= cdata->cpt_num_devs) { + if (pwm->hwpwm >= pc->cpt_num_devs) { dev_err(dev, "device %u is not valid\n", pwm->hwpwm); return -EINVAL; } @@ -395,11 +387,10 @@ static int sti_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { struct sti_pwm_chip *pc = to_sti_pwmchip(chip); - struct sti_pwm_compat_data *cdata = pc->cdata; struct device *dev = pc->dev; int err; - if (pwm->hwpwm >= cdata->pwm_num_devs) { + if (pwm->hwpwm >= pc->pwm_num_devs) { dev_err(dev, "device %u is not valid for pwm mode\n", pwm->hwpwm); return -EINVAL; @@ -448,7 +439,7 @@ static irqreturn_t sti_pwm_interrupt(int irq, void *data) while (cpt_int_stat) { devicenum = ffs(cpt_int_stat) - 1; - ddata = &pc->cdata->ddata[devicenum]; + ddata = &pc->ddata[devicenum]; /* * Capture input: @@ -502,41 +493,37 @@ static irqreturn_t sti_pwm_interrupt(int irq, void *data) return ret; } -static int sti_pwm_probe_dt(struct sti_pwm_chip *pc) +static int sti_pwm_probe_regmap(struct sti_pwm_chip *pc) { struct device *dev = pc->dev; - const struct reg_field *reg_fields; - struct sti_pwm_compat_data *cdata = pc->cdata; - - reg_fields = cdata->reg_fields; pc->prescale_low = devm_regmap_field_alloc(dev, pc->regmap, - reg_fields[PWMCLK_PRESCALE_LOW]); + sti_pwm_regfields[PWMCLK_PRESCALE_LOW]); if (IS_ERR(pc->prescale_low)) return PTR_ERR(pc->prescale_low); pc->prescale_high = devm_regmap_field_alloc(dev, pc->regmap, - reg_fields[PWMCLK_PRESCALE_HIGH]); + sti_pwm_regfields[PWMCLK_PRESCALE_HIGH]); if (IS_ERR(pc->prescale_high)) return PTR_ERR(pc->prescale_high); pc->pwm_out_en = devm_regmap_field_alloc(dev, pc->regmap, - reg_fields[PWM_OUT_EN]); + sti_pwm_regfields[PWM_OUT_EN]); if (IS_ERR(pc->pwm_out_en)) return PTR_ERR(pc->pwm_out_en); pc->pwm_cpt_en = devm_regmap_field_alloc(dev, pc->regmap, - reg_fields[PWM_CPT_EN]); + sti_pwm_regfields[PWM_CPT_EN]); if (IS_ERR(pc->pwm_cpt_en)) return PTR_ERR(pc->pwm_cpt_en); pc->pwm_cpt_int_en = devm_regmap_field_alloc(dev, pc->regmap, - reg_fields[PWM_CPT_INT_EN]); + sti_pwm_regfields[PWM_CPT_INT_EN]); if (IS_ERR(pc->pwm_cpt_int_en)) return PTR_ERR(pc->pwm_cpt_int_en); pc->pwm_cpt_int_stat = devm_regmap_field_alloc(dev, pc->regmap, - reg_fields[PWM_CPT_INT_STAT]); + sti_pwm_regfields[PWM_CPT_INT_STAT]); if (PTR_ERR_OR_ZERO(pc->pwm_cpt_int_stat)) return PTR_ERR(pc->pwm_cpt_int_stat); @@ -556,7 +543,6 @@ static int sti_pwm_probe(struct platform_device *pdev) u32 num_devs; unsigned int pwm_num_devs = 0; unsigned int cpt_num_devs = 0; - struct sti_pwm_compat_data *cdata; struct pwm_chip *chip; struct sti_pwm_chip *pc; unsigned int i; @@ -570,20 +556,14 @@ static int sti_pwm_probe(struct platform_device *pdev) if (!ret) cpt_num_devs = num_devs; - if (!pwm_num_devs && !cpt_num_devs) { - dev_err(dev, "No channels configured\n"); - return -EINVAL; - } + if (!pwm_num_devs && !cpt_num_devs) + return dev_err_probe(dev, -EINVAL, "No channels configured\n"); chip = devm_pwmchip_alloc(dev, max(pwm_num_devs, cpt_num_devs), sizeof(*pc)); if (IS_ERR(chip)) return PTR_ERR(chip); pc = to_sti_pwmchip(chip); - cdata = devm_kzalloc(dev, sizeof(*cdata), GFP_KERNEL); - if (!cdata) - return -ENOMEM; - pc->mmio = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(pc->mmio)) return PTR_ERR(pc->mmio); @@ -591,7 +571,8 @@ static int sti_pwm_probe(struct platform_device *pdev) pc->regmap = devm_regmap_init_mmio(dev, pc->mmio, &sti_pwm_regmap_config); if (IS_ERR(pc->regmap)) - return PTR_ERR(pc->regmap); + return dev_err_probe(dev, PTR_ERR(pc->regmap), + "Failed to initialize regmap\n"); irq = platform_get_irq(pdev, 0); if (irq < 0) @@ -599,94 +580,61 @@ static int sti_pwm_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq, sti_pwm_interrupt, 0, pdev->name, pc); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to request IRQ\n"); - return ret; - } + if (ret < 0) + dev_err_probe(&pdev->dev, ret, "Failed to request IRQ\n"); /* * Setup PWM data with default values: some values could be replaced * with specific ones provided from Device Tree. */ - cdata->reg_fields = sti_pwm_regfields; - cdata->max_prescale = 0xff; - cdata->max_pwm_cnt = 255; - cdata->pwm_num_devs = pwm_num_devs; - cdata->cpt_num_devs = cpt_num_devs; + pc->max_prescale = 0xff; + pc->max_pwm_cnt = 255; + pc->pwm_num_devs = pwm_num_devs; + pc->cpt_num_devs = cpt_num_devs; - pc->cdata = cdata; pc->dev = dev; pc->en_count = 0; mutex_init(&pc->sti_pwm_lock); - ret = sti_pwm_probe_dt(pc); + ret = sti_pwm_probe_regmap(pc); if (ret) - return ret; + return dev_err_probe(dev, ret, "Failed to initialize regmap fields\n"); - if (cdata->pwm_num_devs) { - pc->pwm_clk = of_clk_get_by_name(dev->of_node, "pwm"); - if (IS_ERR(pc->pwm_clk)) { - dev_err(dev, "failed to get PWM clock\n"); - return PTR_ERR(pc->pwm_clk); - } - - ret = clk_prepare(pc->pwm_clk); - if (ret) { - dev_err(dev, "failed to prepare clock\n"); - return ret; - } + if (pwm_num_devs) { + pc->pwm_clk = devm_clk_get_prepared(dev, "pwm"); + if (IS_ERR(pc->pwm_clk)) + return dev_err_probe(dev, PTR_ERR(pc->pwm_clk), + "failed to get PWM clock\n"); } - if (cdata->cpt_num_devs) { - pc->cpt_clk = of_clk_get_by_name(dev->of_node, "capture"); - if (IS_ERR(pc->cpt_clk)) { - dev_err(dev, "failed to get PWM capture clock\n"); - return PTR_ERR(pc->cpt_clk); - } + if (cpt_num_devs) { + pc->cpt_clk = devm_clk_get_prepared(dev, "capture"); + if (IS_ERR(pc->cpt_clk)) + return dev_err_probe(dev, PTR_ERR(pc->cpt_clk), + "failed to get PWM capture clock\n"); - ret = clk_prepare(pc->cpt_clk); - if (ret) { - dev_err(dev, "failed to prepare clock\n"); - return ret; - } - - cdata->ddata = devm_kzalloc(dev, cdata->cpt_num_devs * sizeof(*cdata->ddata), GFP_KERNEL); - if (!cdata->ddata) + pc->ddata = devm_kcalloc(dev, cpt_num_devs, + sizeof(*pc->ddata), GFP_KERNEL); + if (!pc->ddata) return -ENOMEM; - } - chip->ops = &sti_pwm_ops; + for (i = 0; i < cpt_num_devs; i++) { + struct sti_cpt_ddata *ddata = &pc->ddata[i]; - for (i = 0; i < cdata->cpt_num_devs; i++) { - struct sti_cpt_ddata *ddata = &cdata->ddata[i]; - - init_waitqueue_head(&ddata->wait); - mutex_init(&ddata->lock); + init_waitqueue_head(&ddata->wait); + mutex_init(&ddata->lock); + } } - ret = pwmchip_add(chip); - if (ret < 0) { - clk_unprepare(pc->pwm_clk); - clk_unprepare(pc->cpt_clk); - return ret; - } + chip->ops = &sti_pwm_ops; - platform_set_drvdata(pdev, chip); + ret = devm_pwmchip_add(dev, chip); + if (ret) + return dev_err_probe(dev, ret, "Failed to register pwm chip\n"); return 0; } -static void sti_pwm_remove(struct platform_device *pdev) -{ - struct pwm_chip *chip = platform_get_drvdata(pdev); - struct sti_pwm_chip *pc = to_sti_pwmchip(chip); - - pwmchip_remove(chip); - - clk_unprepare(pc->pwm_clk); - clk_unprepare(pc->cpt_clk); -} - static const struct of_device_id sti_pwm_of_match[] = { { .compatible = "st,sti-pwm", }, { /* sentinel */ } @@ -699,7 +647,6 @@ static struct platform_driver sti_pwm_driver = { .of_match_table = sti_pwm_of_match, }, .probe = sti_pwm_probe, - .remove_new = sti_pwm_remove, }; module_platform_driver(sti_pwm_driver); diff --git a/drivers/pwm/pwm-stm32-lp.c b/drivers/pwm/pwm-stm32-lp.c index 989731256f50..4789eafb8bac 100644 --- a/drivers/pwm/pwm-stm32-lp.c +++ b/drivers/pwm/pwm-stm32-lp.c @@ -20,6 +20,7 @@ struct stm32_pwm_lp { struct clk *clk; struct regmap *regmap; + unsigned int num_cc_chans; }; static inline struct stm32_pwm_lp *to_stm32_pwm_lp(struct pwm_chip *chip) @@ -30,13 +31,101 @@ static inline struct stm32_pwm_lp *to_stm32_pwm_lp(struct pwm_chip *chip) /* STM32 Low-Power Timer is preceded by a configurable power-of-2 prescaler */ #define STM32_LPTIM_MAX_PRESCALER 128 +static int stm32_pwm_lp_update_allowed(struct stm32_pwm_lp *priv, int channel) +{ + int ret; + u32 ccmr1; + unsigned long ccmr; + + /* Only one PWM on this LPTIMER: enable, prescaler and reload value can be changed */ + if (!priv->num_cc_chans) + return true; + + ret = regmap_read(priv->regmap, STM32_LPTIM_CCMR1, &ccmr1); + if (ret) + return ret; + ccmr = ccmr1 & (STM32_LPTIM_CC1E | STM32_LPTIM_CC2E); + + /* More than one channel enabled: enable, prescaler or ARR value can't be changed */ + if (bitmap_weight(&ccmr, sizeof(u32) * BITS_PER_BYTE) > 1) + return false; + + /* + * Only one channel is enabled (or none): check status on the other channel, to + * report if enable, prescaler or ARR value can be changed. + */ + if (channel) + return !(ccmr1 & STM32_LPTIM_CC1E); + else + return !(ccmr1 & STM32_LPTIM_CC2E); +} + +static int stm32_pwm_lp_compare_channel_apply(struct stm32_pwm_lp *priv, int channel, + bool enable, enum pwm_polarity polarity) +{ + u32 ccmr1, val, mask; + bool reenable; + int ret; + + /* No dedicated CC channel: nothing to do */ + if (!priv->num_cc_chans) + return 0; + + ret = regmap_read(priv->regmap, STM32_LPTIM_CCMR1, &ccmr1); + if (ret) + return ret; + + if (channel) { + /* Must disable CC channel (CCxE) to modify polarity (CCxP), then re-enable */ + reenable = (enable && FIELD_GET(STM32_LPTIM_CC2E, ccmr1)) && + (polarity != FIELD_GET(STM32_LPTIM_CC2P, ccmr1)); + + mask = STM32_LPTIM_CC2SEL | STM32_LPTIM_CC2E | STM32_LPTIM_CC2P; + val = FIELD_PREP(STM32_LPTIM_CC2P, polarity); + val |= FIELD_PREP(STM32_LPTIM_CC2E, enable); + } else { + reenable = (enable && FIELD_GET(STM32_LPTIM_CC1E, ccmr1)) && + (polarity != FIELD_GET(STM32_LPTIM_CC1P, ccmr1)); + + mask = STM32_LPTIM_CC1SEL | STM32_LPTIM_CC1E | STM32_LPTIM_CC1P; + val = FIELD_PREP(STM32_LPTIM_CC1P, polarity); + val |= FIELD_PREP(STM32_LPTIM_CC1E, enable); + } + + if (reenable) { + u32 cfgr, presc; + unsigned long rate; + unsigned int delay_us; + + ret = regmap_update_bits(priv->regmap, STM32_LPTIM_CCMR1, + channel ? STM32_LPTIM_CC2E : STM32_LPTIM_CC1E, 0); + if (ret) + return ret; + /* + * After a write to the LPTIM_CCMRx register, a new write operation can only be + * performed after a delay of at least (PRESC × 3) clock cycles + */ + ret = regmap_read(priv->regmap, STM32_LPTIM_CFGR, &cfgr); + if (ret) + return ret; + presc = FIELD_GET(STM32_LPTIM_PRESC, cfgr); + rate = clk_get_rate(priv->clk) >> presc; + if (!rate) + return -EINVAL; + delay_us = 3 * DIV_ROUND_UP(USEC_PER_SEC, rate); + usleep_range(delay_us, delay_us * 2); + } + + return regmap_update_bits(priv->regmap, STM32_LPTIM_CCMR1, mask, val); +} + static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { struct stm32_pwm_lp *priv = to_stm32_pwm_lp(chip); unsigned long long prd, div, dty; struct pwm_state cstate; - u32 val, mask, cfgr, presc = 0; + u32 arr, val, mask, cfgr, presc = 0; bool reenable; int ret; @@ -45,10 +134,28 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm, if (!state->enabled) { if (cstate.enabled) { - /* Disable LP timer */ - ret = regmap_write(priv->regmap, STM32_LPTIM_CR, 0); + /* Disable CC channel if any */ + ret = stm32_pwm_lp_compare_channel_apply(priv, pwm->hwpwm, false, + state->polarity); + if (ret) + return ret; + ret = regmap_write(priv->regmap, pwm->hwpwm ? + STM32_LPTIM_CCR2 : STM32_LPTIM_CMP, 0); if (ret) return ret; + + /* Check if the timer can be disabled */ + ret = stm32_pwm_lp_update_allowed(priv, pwm->hwpwm); + if (ret < 0) + return ret; + + if (ret) { + /* Disable LP timer */ + ret = regmap_write(priv->regmap, STM32_LPTIM_CR, 0); + if (ret) + return ret; + } + /* disable clock to PWM counter */ clk_disable(priv->clk); } @@ -79,6 +186,23 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm, dty = prd * state->duty_cycle; do_div(dty, state->period); + ret = regmap_read(priv->regmap, STM32_LPTIM_CFGR, &cfgr); + if (ret) + return ret; + + /* + * When there are several channels, they share the same prescaler and reload value. + * Check if this can be changed, or the values are the same for all channels. + */ + if (!stm32_pwm_lp_update_allowed(priv, pwm->hwpwm)) { + ret = regmap_read(priv->regmap, STM32_LPTIM_ARR, &arr); + if (ret) + return ret; + + if ((FIELD_GET(STM32_LPTIM_PRESC, cfgr) != presc) || (arr != prd - 1)) + return -EBUSY; + } + if (!cstate.enabled) { /* enable clock to drive PWM counter */ ret = clk_enable(priv->clk); @@ -86,15 +210,20 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm, return ret; } - ret = regmap_read(priv->regmap, STM32_LPTIM_CFGR, &cfgr); - if (ret) - goto err; - if ((FIELD_GET(STM32_LPTIM_PRESC, cfgr) != presc) || - (FIELD_GET(STM32_LPTIM_WAVPOL, cfgr) != state->polarity)) { + ((FIELD_GET(STM32_LPTIM_WAVPOL, cfgr) != state->polarity) && !priv->num_cc_chans)) { val = FIELD_PREP(STM32_LPTIM_PRESC, presc); - val |= FIELD_PREP(STM32_LPTIM_WAVPOL, state->polarity); - mask = STM32_LPTIM_PRESC | STM32_LPTIM_WAVPOL; + mask = STM32_LPTIM_PRESC; + + if (!priv->num_cc_chans) { + /* + * WAVPOL bit is only available when no capature compare channel is used, + * e.g. on LPTIMER instances that have only one output channel. CCMR1 is + * used otherwise. + */ + val |= FIELD_PREP(STM32_LPTIM_WAVPOL, state->polarity); + mask |= STM32_LPTIM_WAVPOL; + } /* Must disable LP timer to modify CFGR */ reenable = true; @@ -120,20 +249,27 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm, if (ret) goto err; - ret = regmap_write(priv->regmap, STM32_LPTIM_CMP, prd - (1 + dty)); + /* Write CMP/CCRx register and ensure it's been properly written */ + ret = regmap_write(priv->regmap, pwm->hwpwm ? STM32_LPTIM_CCR2 : STM32_LPTIM_CMP, + prd - (1 + dty)); if (ret) goto err; - /* ensure CMP & ARR registers are properly written */ - ret = regmap_read_poll_timeout(priv->regmap, STM32_LPTIM_ISR, val, + /* ensure ARR and CMP/CCRx registers are properly written */ + ret = regmap_read_poll_timeout(priv->regmap, STM32_LPTIM_ISR, val, pwm->hwpwm ? + (val & STM32_LPTIM_CMP2_ARROK) == STM32_LPTIM_CMP2_ARROK : (val & STM32_LPTIM_CMPOK_ARROK) == STM32_LPTIM_CMPOK_ARROK, 100, 1000); if (ret) { dev_err(pwmchip_parent(chip), "ARR/CMP registers write issue\n"); goto err; } - ret = regmap_write(priv->regmap, STM32_LPTIM_ICR, - STM32_LPTIM_CMPOKCF_ARROKCF); + ret = regmap_write(priv->regmap, STM32_LPTIM_ICR, pwm->hwpwm ? + STM32_LPTIM_CMP2OKCF_ARROKCF : STM32_LPTIM_CMPOKCF_ARROKCF); + if (ret) + goto err; + + ret = stm32_pwm_lp_compare_channel_apply(priv, pwm->hwpwm, true, state->polarity); if (ret) goto err; @@ -161,25 +297,47 @@ static int stm32_pwm_lp_get_state(struct pwm_chip *chip, { struct stm32_pwm_lp *priv = to_stm32_pwm_lp(chip); unsigned long rate = clk_get_rate(priv->clk); - u32 val, presc, prd; + u32 val, presc, prd, ccmr1; + bool enabled; u64 tmp; regmap_read(priv->regmap, STM32_LPTIM_CR, &val); - state->enabled = !!FIELD_GET(STM32_LPTIM_ENABLE, val); + enabled = !!FIELD_GET(STM32_LPTIM_ENABLE, val); + if (priv->num_cc_chans) { + /* There's a CC chan, need to also check if it's enabled */ + regmap_read(priv->regmap, STM32_LPTIM_CCMR1, &ccmr1); + if (pwm->hwpwm) + enabled &= !!FIELD_GET(STM32_LPTIM_CC2E, ccmr1); + else + enabled &= !!FIELD_GET(STM32_LPTIM_CC1E, ccmr1); + } + state->enabled = enabled; + /* Keep PWM counter clock refcount in sync with PWM initial state */ - if (state->enabled) - clk_enable(priv->clk); + if (state->enabled) { + int ret = clk_enable(priv->clk); + + if (ret) + return ret; + } regmap_read(priv->regmap, STM32_LPTIM_CFGR, &val); presc = FIELD_GET(STM32_LPTIM_PRESC, val); - state->polarity = FIELD_GET(STM32_LPTIM_WAVPOL, val); + if (priv->num_cc_chans) { + if (pwm->hwpwm) + state->polarity = FIELD_GET(STM32_LPTIM_CC2P, ccmr1); + else + state->polarity = FIELD_GET(STM32_LPTIM_CC1P, ccmr1); + } else { + state->polarity = FIELD_GET(STM32_LPTIM_WAVPOL, val); + } regmap_read(priv->regmap, STM32_LPTIM_ARR, &prd); tmp = prd + 1; tmp = (tmp << presc) * NSEC_PER_SEC; state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate); - regmap_read(priv->regmap, STM32_LPTIM_CMP, &val); + regmap_read(priv->regmap, pwm->hwpwm ? STM32_LPTIM_CCR2 : STM32_LPTIM_CMP, &val); tmp = prd - val; tmp = (tmp << presc) * NSEC_PER_SEC; state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate); @@ -197,15 +355,25 @@ static int stm32_pwm_lp_probe(struct platform_device *pdev) struct stm32_lptimer *ddata = dev_get_drvdata(pdev->dev.parent); struct stm32_pwm_lp *priv; struct pwm_chip *chip; + unsigned int npwm; int ret; - chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*priv)); + if (!ddata->num_cc_chans) { + /* No dedicated CC channel, so there's only one PWM channel */ + npwm = 1; + } else { + /* There are dedicated CC channels, each with one PWM output */ + npwm = ddata->num_cc_chans; + } + + chip = devm_pwmchip_alloc(&pdev->dev, npwm, sizeof(*priv)); if (IS_ERR(chip)) return PTR_ERR(chip); priv = to_stm32_pwm_lp(chip); priv->regmap = ddata->regmap; priv->clk = ddata->clk; + priv->num_cc_chans = ddata->num_cc_chans; chip->ops = &stm32_pwm_lp_ops; ret = devm_pwmchip_add(&pdev->dev, chip); @@ -221,12 +389,15 @@ static int stm32_pwm_lp_suspend(struct device *dev) { struct pwm_chip *chip = dev_get_drvdata(dev); struct pwm_state state; - - pwm_get_state(&chip->pwms[0], &state); - if (state.enabled) { - dev_err(dev, "The consumer didn't stop us (%s)\n", - chip->pwms[0].label); - return -EBUSY; + unsigned int i; + + for (i = 0; i < chip->npwm; i++) { + pwm_get_state(&chip->pwms[i], &state); + if (state.enabled) { + dev_err(dev, "The consumer didn't stop us (%s)\n", + chip->pwms[i].label); + return -EBUSY; + } } return pinctrl_pm_select_sleep_state(dev); diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c index 0c028d17c075..4b148f0afeb9 100644 --- a/drivers/pwm/pwm-stm32.c +++ b/drivers/pwm/pwm-stm32.c @@ -51,6 +51,384 @@ static u32 active_channels(struct stm32_pwm *dev) return ccer & TIM_CCER_CCXE; } +struct stm32_pwm_waveform { + u32 ccer; + u32 psc; + u32 arr; + u32 ccr; +}; + +static int stm32_pwm_round_waveform_tohw(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_waveform *wf, + void *_wfhw) +{ + struct stm32_pwm_waveform *wfhw = _wfhw; + struct stm32_pwm *priv = to_stm32_pwm_dev(chip); + unsigned int ch = pwm->hwpwm; + unsigned long rate; + u64 ccr, duty; + int ret; + + if (wf->period_length_ns == 0) { + *wfhw = (struct stm32_pwm_waveform){ + .ccer = 0, + }; + + return 0; + } + + ret = clk_enable(priv->clk); + if (ret) + return ret; + + wfhw->ccer = TIM_CCER_CCxE(ch + 1); + if (priv->have_complementary_output) + wfhw->ccer |= TIM_CCER_CCxNE(ch + 1); + + rate = clk_get_rate(priv->clk); + + if (active_channels(priv) & ~TIM_CCER_CCxE(ch + 1)) { + u64 arr; + + /* + * Other channels are already enabled, so the configured PSC and + * ARR must be used for this channel, too. + */ + ret = regmap_read(priv->regmap, TIM_PSC, &wfhw->psc); + if (ret) + goto out; + + ret = regmap_read(priv->regmap, TIM_ARR, &wfhw->arr); + if (ret) + goto out; + + arr = mul_u64_u64_div_u64(wf->period_length_ns, rate, + (u64)NSEC_PER_SEC * (wfhw->psc + 1)); + if (arr <= wfhw->arr) { + /* + * requested period is smaller than the currently + * configured and unchangable period, report back the smallest + * possible period, i.e. the current state and return 1 + * to indicate the wrong rounding direction. + */ + ret = 1; + } + + } else { + /* + * .probe() asserted that clk_get_rate() is not bigger than 1 GHz, so + * the calculations here won't overflow. + * First we need to find the minimal value for prescaler such that + * + * period_ns * clkrate + * ------------------------------ < max_arr + 1 + * NSEC_PER_SEC * (prescaler + 1) + * + * This equation is equivalent to + * + * period_ns * clkrate + * ---------------------------- < prescaler + 1 + * NSEC_PER_SEC * (max_arr + 1) + * + * Using integer division and knowing that the right hand side is + * integer, this is further equivalent to + * + * (period_ns * clkrate) // (NSEC_PER_SEC * (max_arr + 1)) ≤ prescaler + */ + u64 psc = mul_u64_u64_div_u64(wf->period_length_ns, rate, + (u64)NSEC_PER_SEC * ((u64)priv->max_arr + 1)); + u64 arr; + + wfhw->psc = min_t(u64, psc, MAX_TIM_PSC); + + arr = mul_u64_u64_div_u64(wf->period_length_ns, rate, + (u64)NSEC_PER_SEC * (wfhw->psc + 1)); + if (!arr) { + /* + * requested period is too small, report back the smallest + * possible period, i.e. ARR = 0. The only valid CCR + * value is then zero, too. + */ + wfhw->arr = 0; + wfhw->ccr = 0; + ret = 1; + goto out; + } + + /* + * ARR is limited intentionally to values less than + * priv->max_arr to allow 100% duty cycle. + */ + wfhw->arr = min_t(u64, arr, priv->max_arr) - 1; + } + + duty = mul_u64_u64_div_u64(wf->duty_length_ns, rate, + (u64)NSEC_PER_SEC * (wfhw->psc + 1)); + duty = min_t(u64, duty, wfhw->arr + 1); + + if (wf->duty_length_ns && wf->duty_offset_ns && + wf->duty_length_ns + wf->duty_offset_ns >= wf->period_length_ns) { + wfhw->ccer |= TIM_CCER_CCxP(ch + 1); + if (priv->have_complementary_output) + wfhw->ccer |= TIM_CCER_CCxNP(ch + 1); + + ccr = wfhw->arr + 1 - duty; + } else { + ccr = duty; + } + + wfhw->ccr = min_t(u64, ccr, wfhw->arr + 1); + +out: + dev_dbg(&chip->dev, "pwm#%u: %lld/%lld [+%lld] @%lu -> CCER: %08x, PSC: %08x, ARR: %08x, CCR: %08x\n", + pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, + rate, wfhw->ccer, wfhw->psc, wfhw->arr, wfhw->ccr); + + clk_disable(priv->clk); + + return ret; +} + +/* + * This should be moved to lib/math/div64.c. Currently there are some changes + * pending to mul_u64_u64_div_u64. Uwe will care for that when the dust settles. + */ +static u64 stm32_pwm_mul_u64_u64_div_u64_roundup(u64 a, u64 b, u64 c) +{ + u64 res = mul_u64_u64_div_u64(a, b, c); + /* Those multiplications might overflow but it doesn't matter */ + u64 rem = a * b - c * res; + + if (rem) + res += 1; + + return res; +} + +static int stm32_pwm_round_waveform_fromhw(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw, + struct pwm_waveform *wf) +{ + const struct stm32_pwm_waveform *wfhw = _wfhw; + struct stm32_pwm *priv = to_stm32_pwm_dev(chip); + unsigned long rate = clk_get_rate(priv->clk); + unsigned int ch = pwm->hwpwm; + + if (wfhw->ccer & TIM_CCER_CCxE(ch + 1)) { + u64 ccr_ns; + + /* The result doesn't overflow for rate >= 15259 */ + wf->period_length_ns = stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1), + NSEC_PER_SEC, rate); + + ccr_ns = stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * wfhw->ccr, + NSEC_PER_SEC, rate); + + if (wfhw->ccer & TIM_CCER_CCxP(ch + 1)) { + wf->duty_length_ns = + stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1 - wfhw->ccr), + NSEC_PER_SEC, rate); + + wf->duty_offset_ns = ccr_ns; + } else { + wf->duty_length_ns = ccr_ns; + wf->duty_offset_ns = 0; + } + } else { + *wf = (struct pwm_waveform){ + .period_length_ns = 0, + }; + } + + dev_dbg(&chip->dev, "pwm#%u: CCER: %08x, PSC: %08x, ARR: %08x, CCR: %08x @%lu -> %lld/%lld [+%lld]\n", + pwm->hwpwm, wfhw->ccer, wfhw->psc, wfhw->arr, wfhw->ccr, rate, + wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns); + + return 0; +} + +static int stm32_pwm_read_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + void *_wfhw) +{ + struct stm32_pwm_waveform *wfhw = _wfhw; + struct stm32_pwm *priv = to_stm32_pwm_dev(chip); + unsigned int ch = pwm->hwpwm; + int ret; + + ret = clk_enable(priv->clk); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, TIM_CCER, &wfhw->ccer); + if (ret) + goto out; + + if (wfhw->ccer & TIM_CCER_CCxE(ch + 1)) { + ret = regmap_read(priv->regmap, TIM_PSC, &wfhw->psc); + if (ret) + goto out; + + ret = regmap_read(priv->regmap, TIM_ARR, &wfhw->arr); + if (ret) + goto out; + + if (wfhw->arr == U32_MAX) + wfhw->arr -= 1; + + ret = regmap_read(priv->regmap, TIM_CCRx(ch + 1), &wfhw->ccr); + if (ret) + goto out; + + if (wfhw->ccr > wfhw->arr + 1) + wfhw->ccr = wfhw->arr + 1; + } + +out: + clk_disable(priv->clk); + + return ret; +} + +static int stm32_pwm_write_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw) +{ + const struct stm32_pwm_waveform *wfhw = _wfhw; + struct stm32_pwm *priv = to_stm32_pwm_dev(chip); + unsigned int ch = pwm->hwpwm; + int ret; + + ret = clk_enable(priv->clk); + if (ret) + return ret; + + if (wfhw->ccer & TIM_CCER_CCxE(ch + 1)) { + u32 ccer, mask; + unsigned int shift; + u32 ccmr; + + ret = regmap_read(priv->regmap, TIM_CCER, &ccer); + if (ret) + goto out; + + /* If there are other channels enabled, don't update PSC and ARR */ + if (ccer & ~TIM_CCER_CCxE(ch + 1) & TIM_CCER_CCXE) { + u32 psc, arr; + + ret = regmap_read(priv->regmap, TIM_PSC, &psc); + if (ret) + goto out; + + if (psc != wfhw->psc) { + ret = -EBUSY; + goto out; + } + + ret = regmap_read(priv->regmap, TIM_ARR, &arr); + if (ret) + goto out; + + if (arr != wfhw->arr) { + ret = -EBUSY; + goto out; + } + } else { + ret = regmap_write(priv->regmap, TIM_PSC, wfhw->psc); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, TIM_ARR, wfhw->arr); + if (ret) + goto out; + + ret = regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE); + if (ret) + goto out; + + } + + /* set polarity */ + mask = TIM_CCER_CCxP(ch + 1) | TIM_CCER_CCxNP(ch + 1); + ret = regmap_update_bits(priv->regmap, TIM_CCER, mask, wfhw->ccer); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, TIM_CCRx(ch + 1), wfhw->ccr); + if (ret) + goto out; + + /* Configure output mode */ + shift = (ch & 0x1) * CCMR_CHANNEL_SHIFT; + ccmr = (TIM_CCMR_PE | TIM_CCMR_M1) << shift; + mask = CCMR_CHANNEL_MASK << shift; + + if (ch < 2) + ret = regmap_update_bits(priv->regmap, TIM_CCMR1, mask, ccmr); + else + ret = regmap_update_bits(priv->regmap, TIM_CCMR2, mask, ccmr); + if (ret) + goto out; + + ret = regmap_set_bits(priv->regmap, TIM_BDTR, TIM_BDTR_MOE); + if (ret) + goto out; + + if (!(ccer & TIM_CCER_CCxE(ch + 1))) { + mask = TIM_CCER_CCxE(ch + 1) | TIM_CCER_CCxNE(ch + 1); + + ret = clk_enable(priv->clk); + if (ret) + goto out; + + ccer = (ccer & ~mask) | (wfhw->ccer & mask); + regmap_write(priv->regmap, TIM_CCER, ccer); + + /* Make sure that registers are updated */ + regmap_set_bits(priv->regmap, TIM_EGR, TIM_EGR_UG); + + /* Enable controller */ + regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + } + + } else { + /* disable channel */ + u32 mask, ccer; + + mask = TIM_CCER_CCxE(ch + 1); + if (priv->have_complementary_output) + mask |= TIM_CCER_CCxNE(ch + 1); + + ret = regmap_read(priv->regmap, TIM_CCER, &ccer); + if (ret) + goto out; + + if (ccer & mask) { + ccer = ccer & ~mask; + + ret = regmap_write(priv->regmap, TIM_CCER, ccer); + if (ret) + goto out; + + if (!(ccer & TIM_CCER_CCXE)) { + /* When all channels are disabled, we can disable the controller */ + ret = regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + if (ret) + goto out; + } + + clk_disable(priv->clk); + } + } + +out: + clk_disable(priv->clk); + + return ret; +} + #define TIM_CCER_CC12P (TIM_CCER_CC1P | TIM_CCER_CC2P) #define TIM_CCER_CC12E (TIM_CCER_CC1E | TIM_CCER_CC2E) #define TIM_CCER_CC34P (TIM_CCER_CC3P | TIM_CCER_CC4P) @@ -222,7 +600,7 @@ static int stm32_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm, scale = max_arr / min(max_arr, raw_prd); } else { - scale = priv->max_arr; /* bellow resolution, use max scale */ + scale = priv->max_arr; /* below resolution, use max scale */ } if (psc && scale > 1) { @@ -308,213 +686,13 @@ unlock: return ret; } -static int stm32_pwm_config(struct stm32_pwm *priv, unsigned int ch, - int duty_ns, int period_ns) -{ - unsigned long long prd, div, dty; - unsigned int prescaler = 0; - u32 ccmr, mask, shift; - - /* Period and prescaler values depends on clock rate */ - div = (unsigned long long)clk_get_rate(priv->clk) * period_ns; - - do_div(div, NSEC_PER_SEC); - prd = div; - - while (div > priv->max_arr) { - prescaler++; - div = prd; - do_div(div, prescaler + 1); - } - - prd = div; - - if (prescaler > MAX_TIM_PSC) - return -EINVAL; - - /* - * All channels share the same prescaler and counter so when two - * channels are active at the same time we can't change them - */ - if (active_channels(priv) & ~(1 << ch * 4)) { - u32 psc, arr; - - regmap_read(priv->regmap, TIM_PSC, &psc); - regmap_read(priv->regmap, TIM_ARR, &arr); - - if ((psc != prescaler) || (arr != prd - 1)) - return -EBUSY; - } - - regmap_write(priv->regmap, TIM_PSC, prescaler); - regmap_write(priv->regmap, TIM_ARR, prd - 1); - regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE); - - /* Calculate the duty cycles */ - dty = prd * duty_ns; - do_div(dty, period_ns); - - regmap_write(priv->regmap, TIM_CCR1 + 4 * ch, dty); - - /* Configure output mode */ - shift = (ch & 0x1) * CCMR_CHANNEL_SHIFT; - ccmr = (TIM_CCMR_PE | TIM_CCMR_M1) << shift; - mask = CCMR_CHANNEL_MASK << shift; - - if (ch < 2) - regmap_update_bits(priv->regmap, TIM_CCMR1, mask, ccmr); - else - regmap_update_bits(priv->regmap, TIM_CCMR2, mask, ccmr); - - regmap_set_bits(priv->regmap, TIM_BDTR, TIM_BDTR_MOE); - - return 0; -} - -static int stm32_pwm_set_polarity(struct stm32_pwm *priv, unsigned int ch, - enum pwm_polarity polarity) -{ - u32 mask; - - mask = TIM_CCER_CC1P << (ch * 4); - if (priv->have_complementary_output) - mask |= TIM_CCER_CC1NP << (ch * 4); - - regmap_update_bits(priv->regmap, TIM_CCER, mask, - polarity == PWM_POLARITY_NORMAL ? 0 : mask); - - return 0; -} - -static int stm32_pwm_enable(struct stm32_pwm *priv, unsigned int ch) -{ - u32 mask; - int ret; - - ret = clk_enable(priv->clk); - if (ret) - return ret; - - /* Enable channel */ - mask = TIM_CCER_CC1E << (ch * 4); - if (priv->have_complementary_output) - mask |= TIM_CCER_CC1NE << (ch * 4); - - regmap_set_bits(priv->regmap, TIM_CCER, mask); - - /* Make sure that registers are updated */ - regmap_set_bits(priv->regmap, TIM_EGR, TIM_EGR_UG); - - /* Enable controller */ - regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); - - return 0; -} - -static void stm32_pwm_disable(struct stm32_pwm *priv, unsigned int ch) -{ - u32 mask; - - /* Disable channel */ - mask = TIM_CCER_CC1E << (ch * 4); - if (priv->have_complementary_output) - mask |= TIM_CCER_CC1NE << (ch * 4); - - regmap_clear_bits(priv->regmap, TIM_CCER, mask); - - /* When all channels are disabled, we can disable the controller */ - if (!active_channels(priv)) - regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); - - clk_disable(priv->clk); -} - -static int stm32_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, - const struct pwm_state *state) -{ - bool enabled; - struct stm32_pwm *priv = to_stm32_pwm_dev(chip); - int ret; - - enabled = pwm->state.enabled; - - if (enabled && !state->enabled) { - stm32_pwm_disable(priv, pwm->hwpwm); - return 0; - } - - if (state->polarity != pwm->state.polarity) - stm32_pwm_set_polarity(priv, pwm->hwpwm, state->polarity); - - ret = stm32_pwm_config(priv, pwm->hwpwm, - state->duty_cycle, state->period); - if (ret) - return ret; - - if (!enabled && state->enabled) - ret = stm32_pwm_enable(priv, pwm->hwpwm); - - return ret; -} - -static int stm32_pwm_apply_locked(struct pwm_chip *chip, struct pwm_device *pwm, - const struct pwm_state *state) -{ - struct stm32_pwm *priv = to_stm32_pwm_dev(chip); - int ret; - - /* protect common prescaler for all active channels */ - mutex_lock(&priv->lock); - ret = stm32_pwm_apply(chip, pwm, state); - mutex_unlock(&priv->lock); - - return ret; -} - -static int stm32_pwm_get_state(struct pwm_chip *chip, - struct pwm_device *pwm, struct pwm_state *state) -{ - struct stm32_pwm *priv = to_stm32_pwm_dev(chip); - int ch = pwm->hwpwm; - unsigned long rate; - u32 ccer, psc, arr, ccr; - u64 dty, prd; - int ret; - - mutex_lock(&priv->lock); - - ret = regmap_read(priv->regmap, TIM_CCER, &ccer); - if (ret) - goto out; - - state->enabled = ccer & (TIM_CCER_CC1E << (ch * 4)); - state->polarity = (ccer & (TIM_CCER_CC1P << (ch * 4))) ? - PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL; - ret = regmap_read(priv->regmap, TIM_PSC, &psc); - if (ret) - goto out; - ret = regmap_read(priv->regmap, TIM_ARR, &arr); - if (ret) - goto out; - ret = regmap_read(priv->regmap, TIM_CCR1 + 4 * ch, &ccr); - if (ret) - goto out; - - rate = clk_get_rate(priv->clk); - - prd = (u64)NSEC_PER_SEC * (psc + 1) * (arr + 1); - state->period = DIV_ROUND_UP_ULL(prd, rate); - dty = (u64)NSEC_PER_SEC * (psc + 1) * ccr; - state->duty_cycle = DIV_ROUND_UP_ULL(dty, rate); - -out: - mutex_unlock(&priv->lock); - return ret; -} - static const struct pwm_ops stm32pwm_ops = { - .apply = stm32_pwm_apply_locked, - .get_state = stm32_pwm_get_state, + .sizeof_wfhw = sizeof(struct stm32_pwm_waveform), + .round_waveform_tohw = stm32_pwm_round_waveform_tohw, + .round_waveform_fromhw = stm32_pwm_round_waveform_fromhw, + .read_waveform = stm32_pwm_read_waveform, + .write_waveform = stm32_pwm_write_waveform, + .capture = IS_ENABLED(CONFIG_DMA_ENGINE) ? stm32_pwm_capture : NULL, }; @@ -648,23 +826,41 @@ static int stm32_pwm_probe(struct platform_device *pdev) priv->max_arr = ddata->max_arr; if (!priv->regmap || !priv->clk) - return -EINVAL; + return dev_err_probe(dev, -EINVAL, "Failed to get %s\n", + priv->regmap ? "clk" : "regmap"); ret = stm32_pwm_probe_breakinputs(priv, np); if (ret) - return ret; + return dev_err_probe(dev, ret, + "Failed to configure breakinputs\n"); stm32_pwm_detect_complementary(priv); + ret = devm_clk_rate_exclusive_get(dev, priv->clk); + if (ret) + return dev_err_probe(dev, ret, "Failed to lock clock\n"); + + /* + * With the clk running with not more than 1 GHz the calculations in + * .apply() won't overflow. + */ + if (clk_get_rate(priv->clk) > 1000000000) + return dev_err_probe(dev, -EINVAL, "Clock freq too high (%lu)\n", + clk_get_rate(priv->clk)); + chip->ops = &stm32pwm_ops; /* Initialize clock refcount to number of enabled PWM channels. */ - for (i = 0; i < num_enabled; i++) - clk_enable(priv->clk); + for (i = 0; i < num_enabled; i++) { + ret = clk_enable(priv->clk); + if (ret) + return ret; + } ret = devm_pwmchip_add(dev, chip); if (ret < 0) - return ret; + return dev_err_probe(dev, ret, + "Failed to register pwmchip\n"); platform_set_drvdata(pdev, chip); @@ -682,7 +878,7 @@ static int stm32_pwm_suspend(struct device *dev) ccer = active_channels(priv); for (i = 0; i < chip->npwm; i++) { - mask = TIM_CCER_CC1E << (i * 4); + mask = TIM_CCER_CCxE(i + 1); if (ccer & mask) { dev_err(dev, "PWM %u still in use by consumer %s\n", i, chip->pwms[i].label); diff --git a/drivers/pwm/pwm-stmpe.c b/drivers/pwm/pwm-stmpe.c index bb91062d5f1d..73f12843999a 100644 --- a/drivers/pwm/pwm-stmpe.c +++ b/drivers/pwm/pwm-stmpe.c @@ -326,12 +326,33 @@ static int __init stmpe_pwm_probe(struct platform_device *pdev) return ret; } + platform_set_drvdata(pdev, chip); + return 0; } -static struct platform_driver stmpe_pwm_driver = { +static void __exit stmpe_pwm_remove(struct platform_device *pdev) +{ + struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent); + struct pwm_chip *chip = platform_get_drvdata(pdev); + + pwmchip_remove(chip); + stmpe_disable(stmpe, STMPE_BLOCK_PWM); +} + +/* + * stmpe_pwm_remove() lives in .exit.text. For drivers registered via + * module_platform_driver_probe() this is ok because they cannot get unbound at + * runtime. So mark the driver struct with __refdata to prevent modpost + * triggering a section mismatch warning. + */ +static struct platform_driver stmpe_pwm_driver __refdata = { .driver = { .name = "stmpe-pwm", }, + .remove = __exit_p(stmpe_pwm_remove), }; -builtin_platform_driver_probe(stmpe_pwm_driver, stmpe_pwm_probe); +module_platform_driver_probe(stmpe_pwm_driver, stmpe_pwm_probe); + +MODULE_DESCRIPTION("STMPE expander PWM"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c index 5c29590d1821..e60dc7d6b591 100644 --- a/drivers/pwm/pwm-sun4i.c +++ b/drivers/pwm/pwm-sun4i.c @@ -493,7 +493,7 @@ static struct platform_driver sun4i_pwm_driver = { .of_match_table = sun4i_pwm_dt_ids, }, .probe = sun4i_pwm_probe, - .remove_new = sun4i_pwm_remove, + .remove = sun4i_pwm_remove, }; module_platform_driver(sun4i_pwm_driver); diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index a3d69976148f..172063b51d44 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -432,7 +432,7 @@ static struct platform_driver tegra_pwm_driver = { .pm = &tegra_pwm_pm_ops, }, .probe = tegra_pwm_probe, - .remove_new = tegra_pwm_remove, + .remove = tegra_pwm_remove, }; module_platform_driver(tegra_pwm_driver); diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c index d6c2b1b1387e..d91b2bdc88fc 100644 --- a/drivers/pwm/pwm-tiecap.c +++ b/drivers/pwm/pwm-tiecap.c @@ -324,7 +324,7 @@ static struct platform_driver ecap_pwm_driver = { .pm = pm_ptr(&ecap_pwm_pm_ops), }, .probe = ecap_pwm_probe, - .remove_new = ecap_pwm_remove, + .remove = ecap_pwm_remove, }; module_platform_driver(ecap_pwm_driver); diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c index e5104725d9b7..0125e73b98df 100644 --- a/drivers/pwm/pwm-tiehrpwm.c +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -603,7 +603,7 @@ static struct platform_driver ehrpwm_pwm_driver = { .pm = pm_ptr(&ehrpwm_pwm_pm_ops), }, .probe = ehrpwm_pwm_probe, - .remove_new = ehrpwm_pwm_remove, + .remove = ehrpwm_pwm_remove, }; module_platform_driver(ehrpwm_pwm_driver); diff --git a/drivers/pwm/pwm-visconti.c b/drivers/pwm/pwm-visconti.c index 9e55380957be..28fae4979e3f 100644 --- a/drivers/pwm/pwm-visconti.c +++ b/drivers/pwm/pwm-visconti.c @@ -170,6 +170,7 @@ static struct platform_driver visconti_pwm_driver = { }; module_platform_driver(visconti_pwm_driver); +MODULE_DESCRIPTION("Toshiba Visconti Pulse Width Modulator driver"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp>"); MODULE_ALIAS("platform:pwm-visconti"); diff --git a/drivers/pwm/pwm-xilinx.c b/drivers/pwm/pwm-xilinx.c index 3a7deebb0d0c..52c241982807 100644 --- a/drivers/pwm/pwm-xilinx.c +++ b/drivers/pwm/pwm-xilinx.c @@ -224,7 +224,6 @@ static int xilinx_pwm_probe(struct platform_device *pdev) if (IS_ERR(chip)) return PTR_ERR(chip); priv = xilinx_pwm_chip_to_priv(chip); - platform_set_drvdata(pdev, chip); regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(regs)) @@ -263,37 +262,24 @@ static int xilinx_pwm_probe(struct platform_device *pdev) * alas, such properties are not allowed to be used. */ - priv->clk = devm_clk_get(dev, "s_axi_aclk"); + priv->clk = devm_clk_get_enabled(dev, "s_axi_aclk"); if (IS_ERR(priv->clk)) return dev_err_probe(dev, PTR_ERR(priv->clk), "Could not get clock\n"); - ret = clk_prepare_enable(priv->clk); + ret = devm_clk_rate_exclusive_get(dev, priv->clk); if (ret) - return dev_err_probe(dev, ret, "Clock enable failed\n"); - clk_rate_exclusive_get(priv->clk); + return dev_err_probe(dev, ret, + "Failed to lock clock rate\n"); chip->ops = &xilinx_pwm_ops; - ret = pwmchip_add(chip); - if (ret) { - clk_rate_exclusive_put(priv->clk); - clk_disable_unprepare(priv->clk); + ret = devm_pwmchip_add(dev, chip); + if (ret) return dev_err_probe(dev, ret, "Could not register PWM chip\n"); - } return 0; } -static void xilinx_pwm_remove(struct platform_device *pdev) -{ - struct pwm_chip *chip = platform_get_drvdata(pdev); - struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip); - - pwmchip_remove(chip); - clk_rate_exclusive_put(priv->clk); - clk_disable_unprepare(priv->clk); -} - static const struct of_device_id xilinx_pwm_of_match[] = { { .compatible = "xlnx,xps-timer-1.00.a", }, {}, @@ -302,7 +288,6 @@ MODULE_DEVICE_TABLE(of, xilinx_pwm_of_match); static struct platform_driver xilinx_pwm_driver = { .probe = xilinx_pwm_probe, - .remove_new = xilinx_pwm_remove, .driver = { .name = "xilinx-pwm", .of_match_table = of_match_ptr(xilinx_pwm_of_match), diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c deleted file mode 100644 index 3f434a771fb5..000000000000 --- a/drivers/pwm/sysfs.c +++ /dev/null @@ -1,545 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * A simple sysfs interface for the generic PWM framework - * - * Copyright (C) 2013 H Hartley Sweeten <hsweeten@visionengravers.com> - * - * Based on previous work by Lars Poeschel <poeschel@lemonage.de> - */ - -#include <linux/device.h> -#include <linux/mutex.h> -#include <linux/err.h> -#include <linux/slab.h> -#include <linux/kdev_t.h> -#include <linux/pwm.h> - -struct pwm_export { - struct device child; - struct pwm_device *pwm; - struct mutex lock; - struct pwm_state suspend; -}; - -static struct pwm_export *child_to_pwm_export(struct device *child) -{ - return container_of(child, struct pwm_export, child); -} - -static struct pwm_device *child_to_pwm_device(struct device *child) -{ - struct pwm_export *export = child_to_pwm_export(child); - - return export->pwm; -} - -static ssize_t period_show(struct device *child, - struct device_attribute *attr, - char *buf) -{ - const struct pwm_device *pwm = child_to_pwm_device(child); - struct pwm_state state; - - pwm_get_state(pwm, &state); - - return sysfs_emit(buf, "%llu\n", state.period); -} - -static ssize_t period_store(struct device *child, - struct device_attribute *attr, - const char *buf, size_t size) -{ - struct pwm_export *export = child_to_pwm_export(child); - struct pwm_device *pwm = export->pwm; - struct pwm_state state; - u64 val; - int ret; - - ret = kstrtou64(buf, 0, &val); - if (ret) - return ret; - - mutex_lock(&export->lock); - pwm_get_state(pwm, &state); - state.period = val; - ret = pwm_apply_might_sleep(pwm, &state); - mutex_unlock(&export->lock); - - return ret ? : size; -} - -static ssize_t duty_cycle_show(struct device *child, - struct device_attribute *attr, - char *buf) -{ - const struct pwm_device *pwm = child_to_pwm_device(child); - struct pwm_state state; - - pwm_get_state(pwm, &state); - - return sysfs_emit(buf, "%llu\n", state.duty_cycle); -} - -static ssize_t duty_cycle_store(struct device *child, - struct device_attribute *attr, - const char *buf, size_t size) -{ - struct pwm_export *export = child_to_pwm_export(child); - struct pwm_device *pwm = export->pwm; - struct pwm_state state; - u64 val; - int ret; - - ret = kstrtou64(buf, 0, &val); - if (ret) - return ret; - - mutex_lock(&export->lock); - pwm_get_state(pwm, &state); - state.duty_cycle = val; - ret = pwm_apply_might_sleep(pwm, &state); - mutex_unlock(&export->lock); - - return ret ? : size; -} - -static ssize_t enable_show(struct device *child, - struct device_attribute *attr, - char *buf) -{ - const struct pwm_device *pwm = child_to_pwm_device(child); - struct pwm_state state; - - pwm_get_state(pwm, &state); - - return sysfs_emit(buf, "%d\n", state.enabled); -} - -static ssize_t enable_store(struct device *child, - struct device_attribute *attr, - const char *buf, size_t size) -{ - struct pwm_export *export = child_to_pwm_export(child); - struct pwm_device *pwm = export->pwm; - struct pwm_state state; - int val, ret; - - ret = kstrtoint(buf, 0, &val); - if (ret) - return ret; - - mutex_lock(&export->lock); - - pwm_get_state(pwm, &state); - - switch (val) { - case 0: - state.enabled = false; - break; - case 1: - state.enabled = true; - break; - default: - ret = -EINVAL; - goto unlock; - } - - ret = pwm_apply_might_sleep(pwm, &state); - -unlock: - mutex_unlock(&export->lock); - return ret ? : size; -} - -static ssize_t polarity_show(struct device *child, - struct device_attribute *attr, - char *buf) -{ - const struct pwm_device *pwm = child_to_pwm_device(child); - const char *polarity = "unknown"; - struct pwm_state state; - - pwm_get_state(pwm, &state); - - switch (state.polarity) { - case PWM_POLARITY_NORMAL: - polarity = "normal"; - break; - - case PWM_POLARITY_INVERSED: - polarity = "inversed"; - break; - } - - return sysfs_emit(buf, "%s\n", polarity); -} - -static ssize_t polarity_store(struct device *child, - struct device_attribute *attr, - const char *buf, size_t size) -{ - struct pwm_export *export = child_to_pwm_export(child); - struct pwm_device *pwm = export->pwm; - enum pwm_polarity polarity; - struct pwm_state state; - int ret; - - if (sysfs_streq(buf, "normal")) - polarity = PWM_POLARITY_NORMAL; - else if (sysfs_streq(buf, "inversed")) - polarity = PWM_POLARITY_INVERSED; - else - return -EINVAL; - - mutex_lock(&export->lock); - pwm_get_state(pwm, &state); - state.polarity = polarity; - ret = pwm_apply_might_sleep(pwm, &state); - mutex_unlock(&export->lock); - - return ret ? : size; -} - -static ssize_t capture_show(struct device *child, - struct device_attribute *attr, - char *buf) -{ - struct pwm_device *pwm = child_to_pwm_device(child); - struct pwm_capture result; - int ret; - - ret = pwm_capture(pwm, &result, jiffies_to_msecs(HZ)); - if (ret) - return ret; - - return sysfs_emit(buf, "%u %u\n", result.period, result.duty_cycle); -} - -static DEVICE_ATTR_RW(period); -static DEVICE_ATTR_RW(duty_cycle); -static DEVICE_ATTR_RW(enable); -static DEVICE_ATTR_RW(polarity); -static DEVICE_ATTR_RO(capture); - -static struct attribute *pwm_attrs[] = { - &dev_attr_period.attr, - &dev_attr_duty_cycle.attr, - &dev_attr_enable.attr, - &dev_attr_polarity.attr, - &dev_attr_capture.attr, - NULL -}; -ATTRIBUTE_GROUPS(pwm); - -static void pwm_export_release(struct device *child) -{ - struct pwm_export *export = child_to_pwm_export(child); - - kfree(export); -} - -static int pwm_export_child(struct device *parent, struct pwm_device *pwm) -{ - struct pwm_export *export; - char *pwm_prop[2]; - int ret; - - if (test_and_set_bit(PWMF_EXPORTED, &pwm->flags)) - return -EBUSY; - - export = kzalloc(sizeof(*export), GFP_KERNEL); - if (!export) { - clear_bit(PWMF_EXPORTED, &pwm->flags); - return -ENOMEM; - } - - export->pwm = pwm; - mutex_init(&export->lock); - - export->child.release = pwm_export_release; - export->child.parent = parent; - export->child.devt = MKDEV(0, 0); - export->child.groups = pwm_groups; - dev_set_name(&export->child, "pwm%u", pwm->hwpwm); - - ret = device_register(&export->child); - if (ret) { - clear_bit(PWMF_EXPORTED, &pwm->flags); - put_device(&export->child); - export = NULL; - return ret; - } - pwm_prop[0] = kasprintf(GFP_KERNEL, "EXPORT=pwm%u", pwm->hwpwm); - pwm_prop[1] = NULL; - kobject_uevent_env(&parent->kobj, KOBJ_CHANGE, pwm_prop); - kfree(pwm_prop[0]); - - return 0; -} - -static int pwm_unexport_match(struct device *child, void *data) -{ - return child_to_pwm_device(child) == data; -} - -static int pwm_unexport_child(struct device *parent, struct pwm_device *pwm) -{ - struct device *child; - char *pwm_prop[2]; - - if (!test_and_clear_bit(PWMF_EXPORTED, &pwm->flags)) - return -ENODEV; - - child = device_find_child(parent, pwm, pwm_unexport_match); - if (!child) - return -ENODEV; - - pwm_prop[0] = kasprintf(GFP_KERNEL, "UNEXPORT=pwm%u", pwm->hwpwm); - pwm_prop[1] = NULL; - kobject_uevent_env(&parent->kobj, KOBJ_CHANGE, pwm_prop); - kfree(pwm_prop[0]); - - /* for device_find_child() */ - put_device(child); - device_unregister(child); - pwm_put(pwm); - - return 0; -} - -static ssize_t export_store(struct device *parent, - struct device_attribute *attr, - const char *buf, size_t len) -{ - struct pwm_chip *chip = dev_get_drvdata(parent); - struct pwm_device *pwm; - unsigned int hwpwm; - int ret; - - ret = kstrtouint(buf, 0, &hwpwm); - if (ret < 0) - return ret; - - if (hwpwm >= chip->npwm) - return -ENODEV; - - pwm = pwm_request_from_chip(chip, hwpwm, "sysfs"); - if (IS_ERR(pwm)) - return PTR_ERR(pwm); - - ret = pwm_export_child(parent, pwm); - if (ret < 0) - pwm_put(pwm); - - return ret ? : len; -} -static DEVICE_ATTR_WO(export); - -static ssize_t unexport_store(struct device *parent, - struct device_attribute *attr, - const char *buf, size_t len) -{ - struct pwm_chip *chip = dev_get_drvdata(parent); - unsigned int hwpwm; - int ret; - - ret = kstrtouint(buf, 0, &hwpwm); - if (ret < 0) - return ret; - - if (hwpwm >= chip->npwm) - return -ENODEV; - - ret = pwm_unexport_child(parent, &chip->pwms[hwpwm]); - - return ret ? : len; -} -static DEVICE_ATTR_WO(unexport); - -static ssize_t npwm_show(struct device *parent, struct device_attribute *attr, - char *buf) -{ - const struct pwm_chip *chip = dev_get_drvdata(parent); - - return sysfs_emit(buf, "%u\n", chip->npwm); -} -static DEVICE_ATTR_RO(npwm); - -static struct attribute *pwm_chip_attrs[] = { - &dev_attr_export.attr, - &dev_attr_unexport.attr, - &dev_attr_npwm.attr, - NULL, -}; -ATTRIBUTE_GROUPS(pwm_chip); - -/* takes export->lock on success */ -static struct pwm_export *pwm_class_get_state(struct device *parent, - struct pwm_device *pwm, - struct pwm_state *state) -{ - struct device *child; - struct pwm_export *export; - - if (!test_bit(PWMF_EXPORTED, &pwm->flags)) - return NULL; - - child = device_find_child(parent, pwm, pwm_unexport_match); - if (!child) - return NULL; - - export = child_to_pwm_export(child); - put_device(child); /* for device_find_child() */ - - mutex_lock(&export->lock); - pwm_get_state(pwm, state); - - return export; -} - -static int pwm_class_apply_state(struct pwm_export *export, - struct pwm_device *pwm, - struct pwm_state *state) -{ - int ret = pwm_apply_might_sleep(pwm, state); - - /* release lock taken in pwm_class_get_state */ - mutex_unlock(&export->lock); - - return ret; -} - -static int pwm_class_resume_npwm(struct device *parent, unsigned int npwm) -{ - struct pwm_chip *chip = dev_get_drvdata(parent); - unsigned int i; - int ret = 0; - - for (i = 0; i < npwm; i++) { - struct pwm_device *pwm = &chip->pwms[i]; - struct pwm_state state; - struct pwm_export *export; - - export = pwm_class_get_state(parent, pwm, &state); - if (!export) - continue; - - /* If pwmchip was not enabled before suspend, do nothing. */ - if (!export->suspend.enabled) { - /* release lock taken in pwm_class_get_state */ - mutex_unlock(&export->lock); - continue; - } - - state.enabled = export->suspend.enabled; - ret = pwm_class_apply_state(export, pwm, &state); - if (ret < 0) - break; - } - - return ret; -} - -static int pwm_class_suspend(struct device *parent) -{ - struct pwm_chip *chip = dev_get_drvdata(parent); - unsigned int i; - int ret = 0; - - for (i = 0; i < chip->npwm; i++) { - struct pwm_device *pwm = &chip->pwms[i]; - struct pwm_state state; - struct pwm_export *export; - - export = pwm_class_get_state(parent, pwm, &state); - if (!export) - continue; - - /* - * If pwmchip was not enabled before suspend, save - * state for resume time and do nothing else. - */ - export->suspend = state; - if (!state.enabled) { - /* release lock taken in pwm_class_get_state */ - mutex_unlock(&export->lock); - continue; - } - - state.enabled = false; - ret = pwm_class_apply_state(export, pwm, &state); - if (ret < 0) { - /* - * roll back the PWM devices that were disabled by - * this suspend function. - */ - pwm_class_resume_npwm(parent, i); - break; - } - } - - return ret; -} - -static int pwm_class_resume(struct device *parent) -{ - struct pwm_chip *chip = dev_get_drvdata(parent); - - return pwm_class_resume_npwm(parent, chip->npwm); -} - -static DEFINE_SIMPLE_DEV_PM_OPS(pwm_class_pm_ops, pwm_class_suspend, pwm_class_resume); - -static struct class pwm_class = { - .name = "pwm", - .dev_groups = pwm_chip_groups, - .pm = pm_sleep_ptr(&pwm_class_pm_ops), -}; - -static int pwmchip_sysfs_match(struct device *parent, const void *data) -{ - return dev_get_drvdata(parent) == data; -} - -void pwmchip_sysfs_export(struct pwm_chip *chip) -{ - struct device *parent; - - /* - * If device_create() fails the pwm_chip is still usable by - * the kernel it's just not exported. - */ - parent = device_create(&pwm_class, pwmchip_parent(chip), MKDEV(0, 0), chip, - "pwmchip%d", chip->id); - if (IS_ERR(parent)) { - dev_warn(pwmchip_parent(chip), - "device_create failed for pwm_chip sysfs export\n"); - } -} - -void pwmchip_sysfs_unexport(struct pwm_chip *chip) -{ - struct device *parent; - unsigned int i; - - parent = class_find_device(&pwm_class, NULL, chip, - pwmchip_sysfs_match); - if (!parent) - return; - - for (i = 0; i < chip->npwm; i++) { - struct pwm_device *pwm = &chip->pwms[i]; - - if (test_bit(PWMF_EXPORTED, &pwm->flags)) - pwm_unexport_child(parent, pwm); - } - - put_device(parent); - device_unregister(parent); -} - -static int __init pwm_sysfs_init(void) -{ - return class_register(&pwm_class); -} -subsys_initcall(pwm_sysfs_init); |