diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /drivers/pwm | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
59 files changed, 21486 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig new file mode 100644 index 000000000..63be5362f --- /dev/null +++ b/drivers/pwm/Kconfig @@ -0,0 +1,573 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig PWM + bool "Pulse-Width Modulation (PWM) Support" + help + Generic Pulse-Width Modulation (PWM) support. + + In Pulse-Width Modulation, a variation of the width of pulses + in a rectangular pulse signal is used as a means to alter the + average power of the signal. Applications include efficient + power delivery and voltage regulation. In computer systems, + PWMs are commonly used to control fans or the brightness of + display backlights. + + This framework provides a generic interface to PWM devices + within the Linux kernel. On the driver side it provides an API + to register and unregister a PWM chip, an abstraction of a PWM + controller, that supports one or more PWM devices. Client + drivers can request PWM devices and use the generic framework + to configure as well as enable and disable them. + + This generic framework replaces the legacy PWM framework which + allows only a single driver implementing the required API. Not + all legacy implementations have been ported to the framework + yet. The framework provides an API that is backward compatible + with the legacy framework so that existing client drivers + continue to work as expected. + + If unsure, say no. + +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 + help + This option enables some additional checks to help lowlevel driver + authors to get their callbacks implemented correctly. + It is expected to introduce some runtime overhead and diagnostic + output to the kernel log, so only enable while working on a driver. + +config PWM_AB8500 + tristate "AB8500 PWM support" + depends on AB8500_CORE && ARCH_U8500 + help + Generic PWM framework driver for Analog Baseband AB8500. + + To compile this driver as a module, choose M here: the module + will be called pwm-ab8500. + +config PWM_ATMEL + tristate "Atmel PWM support" + depends on OF + depends on ARCH_AT91 || COMPILE_TEST + help + Generic PWM framework driver for Atmel SoC. + + To compile this driver as a module, choose M here: the module + will be called pwm-atmel. + +config PWM_ATMEL_HLCDC_PWM + tristate "Atmel HLCDC PWM support" + depends on MFD_ATMEL_HLCDC + depends on HAVE_CLK + help + Generic PWM framework driver for the PWM output of the HLCDC + (Atmel High-end LCD Controller). This PWM output is mainly used + to control the LCD backlight. + + To compile this driver as a module, choose M here: the module + will be called pwm-atmel-hlcdc. + +config PWM_ATMEL_TCB + tristate "Atmel TC Block PWM support" + depends on ATMEL_TCLIB && OF + help + Generic PWM framework driver for Atmel Timer Counter Block. + + A Timer Counter Block provides 6 PWM devices grouped by 2. + Devices in a given group must have the same period. + + To compile this driver as a module, choose M here: the module + will be called pwm-atmel-tcb. + +config PWM_BCM_IPROC + tristate "iProc PWM support" + depends on ARCH_BCM_IPROC || COMPILE_TEST + depends on COMMON_CLK + default ARCH_BCM_IPROC + help + Generic PWM framework driver for Broadcom iProc PWM block. This + block is used in Broadcom iProc SoC's. + + To compile this driver as a module, choose M here: the module + will be called pwm-bcm-iproc. + +config PWM_BCM_KONA + tristate "Kona PWM support" + depends on ARCH_BCM_MOBILE || ARCH_BCM_CYGNUS || COMPILE_TEST + depends on HAVE_CLK && HAS_IOMEM + default ARCH_BCM_MOBILE || ARCH_BCM_CYGNUS + help + Generic PWM framework driver for Broadcom Kona PWM block. + + 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 + 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 + help + PWM framework driver for Marvell Berlin SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-berlin. + +config PWM_BRCMSTB + tristate "Broadcom STB PWM support" + depends on ARCH_BRCMSTB || BMIPS_GENERIC || COMPILE_TEST + help + Generic PWM framework driver for the Broadcom Set-top-Box + SoCs (BCM7xxx). + + To compile this driver as a module, choose M Here: the module + will be called pwm-brcmstb.c. + +config PWM_CLPS711X + tristate "CLPS711X PWM support" + depends on ARCH_CLPS711X || COMPILE_TEST + depends on HAS_IOMEM + help + Generic PWM framework driver for Cirrus Logic CLPS711X. + + To compile this driver as a module, choose M here: the module + will be called pwm-clps711x. + +config PWM_CRC + bool "Intel Crystalcove (CRC) PWM support" + depends on X86 && INTEL_SOC_PMIC + help + Generic PWM framework driver for Crystalcove (CRC) PMIC based PWM + control. + +config PWM_CROS_EC + tristate "ChromeOS EC PWM driver" + depends on CROS_EC + help + PWM driver for exposing a PWM attached to the ChromeOS Embedded + Controller. + +config PWM_EP93XX + tristate "Cirrus Logic EP93xx PWM support" + depends on ARCH_EP93XX || COMPILE_TEST + help + Generic PWM framework driver for Cirrus Logic EP93xx. + + To compile this driver as a module, choose M here: the module + will be called pwm-ep93xx. + +config PWM_FSL_FTM + tristate "Freescale FlexTimer Module (FTM) PWM support" + depends on HAS_IOMEM + depends on OF + select REGMAP_MMIO + help + Generic FTM PWM framework driver for Freescale VF610 and + Layerscape LS-1 SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-fsl-ftm. + +config PWM_HIBVT + tristate "HiSilicon BVT PWM support" + depends on ARCH_HISI || COMPILE_TEST + help + Generic PWM framework driver for HiSilicon BVT SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-hibvt. + +config PWM_IMG + tristate "Imagination Technologies PWM driver" + depends on HAS_IOMEM + depends on MFD_SYSCON + depends on COMMON_CLK + depends on MIPS || COMPILE_TEST + help + Generic PWM framework driver for Imagination Technologies + PWM block which supports 4 channels. + + To compile this driver as a module, choose M here: the module + will be called pwm-img + +config PWM_IMX1 + tristate "i.MX1 PWM support" + depends on ARCH_MXC || COMPILE_TEST + help + Generic PWM framework driver for i.MX1 and i.MX21 + + To compile this driver as a module, choose M here: the module + will be called pwm-imx1. + +config PWM_IMX27 + tristate "i.MX27 PWM support" + depends on ARCH_MXC || COMPILE_TEST + help + Generic PWM framework driver for i.MX27 and later i.MX SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-imx27. + +config PWM_IMX_TPM + tristate "i.MX TPM PWM support" + depends on ARCH_MXC || COMPILE_TEST + depends on HAVE_CLK && HAS_IOMEM + help + Generic PWM framework driver for i.MX7ULP TPM module, TPM's full + name is Low Power Timer/Pulse Width Modulation Module. + + To compile this driver as a module, choose M here: the module + will be called pwm-imx-tpm. + +config PWM_IQS620A + tristate "Azoteq IQS620A PWM support" + depends on MFD_IQS62X || COMPILE_TEST + help + Generic PWM framework driver for the Azoteq IQS620A multi-function + sensor. + + To compile this driver as a module, choose M here: the module will + be called pwm-iqs620a. + +config PWM_JZ4740 + tristate "Ingenic JZ47xx PWM support" + depends on MIPS + depends on COMMON_CLK + select MFD_SYSCON + help + Generic PWM framework driver for Ingenic JZ47xx based + machines. + + To compile this driver as a module, choose M here: the module + will be called pwm-jz4740. + +config PWM_LP3943 + tristate "TI/National Semiconductor LP3943 PWM support" + depends on MFD_LP3943 + help + Generic PWM framework driver for LP3943 which supports two PWM + channels. + + To compile this driver as a module, choose M here: the module + will be called pwm-lp3943. + +config PWM_LPC18XX_SCT + tristate "LPC18xx/43xx PWM/SCT support" + depends on ARCH_LPC18XX || COMPILE_TEST + help + Generic PWM framework driver for NXP LPC18xx PWM/SCT which + supports 16 channels. + A maximum of 15 channels can be requested simultaneously and + must have the same period. + + To compile this driver as a module, choose M here: the module + will be called pwm-lpc18xx-sct. + +config PWM_LPC32XX + tristate "LPC32XX PWM support" + depends on ARCH_LPC32XX || COMPILE_TEST + help + Generic PWM framework driver for LPC32XX. The LPC32XX SOC has two + PWM controllers. + + To compile this driver as a module, choose M here: the module + will be called pwm-lpc32xx. + +config PWM_LPSS + tristate + +config PWM_LPSS_PCI + tristate "Intel LPSS PWM PCI driver" + depends on X86 && PCI + select PWM_LPSS + help + The PCI driver for Intel Low Power Subsystem PWM controller. + + To compile this driver as a module, choose M here: the module + will be called pwm-lpss-pci. + +config PWM_LPSS_PLATFORM + tristate "Intel LPSS PWM platform driver" + depends on X86 && ACPI + select PWM_LPSS + help + The platform driver for Intel Low Power Subsystem PWM controller. + + 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 + 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 + 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_MEDIATEK + tristate "MediaTek PWM support" + depends on ARCH_MEDIATEK || RALINK || COMPILE_TEST + help + Generic PWM framework driver for Mediatek ARM SoC. + + To compile this driver as a module, choose M here: the module + will be called pwm-mediatek. + +config PWM_MXS + tristate "Freescale MXS PWM support" + depends on OF + depends on ARCH_MXS || COMPILE_TEST + select STMP_DEVICE + help + Generic PWM framework driver for Freescale MXS. + + To compile this driver as a module, choose M here: the module + will be called pwm-mxs. + +config PWM_OMAP_DMTIMER + tristate "OMAP Dual-Mode Timer PWM support" + depends on OF + depends on OMAP_DM_TIMER || COMPILE_TEST + help + Generic PWM framework driver for OMAP Dual-Mode Timer PWM output + + To compile this driver as a module, choose M here: the module + will be called pwm-omap-dmtimer + +config PWM_PCA9685 + tristate "NXP PCA9685 PWM driver" + depends on I2C + select REGMAP_I2C + help + Generic PWM framework driver for NXP PCA9685 LED controller. + + To compile this driver as a module, choose M here: the module + will be called pwm-pca9685. + +config PWM_PXA + tristate "PXA PWM support" + depends on ARCH_PXA || COMPILE_TEST + help + Generic PWM framework driver for PXA. + + To compile this driver as a module, choose M here: the module + will be called pwm-pxa. + +config PWM_RCAR + tristate "Renesas R-Car PWM support" + depends on ARCH_RENESAS || COMPILE_TEST + depends on HAS_IOMEM + help + This driver exposes the PWM Timer controller found in Renesas + R-Car chips through the PWM API. + + To compile this driver as a module, choose M here: the module + will be called pwm-rcar. + +config PWM_RENESAS_TPU + tristate "Renesas TPU PWM support" + depends on ARCH_RENESAS || COMPILE_TEST + depends on HAS_IOMEM + help + This driver exposes the Timer Pulse Unit (TPU) PWM controller found + in Renesas chips through the PWM API. + + To compile this driver as a module, choose M here: the module + will be called pwm-renesas-tpu. + +config PWM_ROCKCHIP + tristate "Rockchip PWM support" + depends on ARCH_ROCKCHIP || COMPILE_TEST + help + Generic PWM framework driver for the PWM controller found on + Rockchip SoCs. + +config PWM_SAMSUNG + tristate "Samsung PWM support" + depends on PLAT_SAMSUNG || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST + help + Generic PWM framework driver for Samsung. + + To compile this driver as a module, choose M here: the module + will be called pwm-samsung. + +config PWM_SIFIVE + tristate "SiFive PWM support" + depends on OF + depends on COMMON_CLK + depends on RISCV || COMPILE_TEST + help + Generic PWM framework driver for SiFive SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-sifive. + +config PWM_SL28CPLD + tristate "Kontron sl28cpld PWM support" + depends on MFD_SL28CPLD || COMPILE_TEST + help + Generic PWM framework driver for board management controller + found on the Kontron sl28 CPLD. + + To compile this driver as a module, choose M here: the module + will be called pwm-sl28cpld. + +config PWM_SPEAR + tristate "STMicroelectronics SPEAr PWM support" + depends on PLAT_SPEAR || COMPILE_TEST + depends on OF + help + Generic PWM framework driver for the PWM controller on ST + SPEAr SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-spear. + +config PWM_SPRD + tristate "Spreadtrum PWM support" + depends on ARCH_SPRD || COMPILE_TEST + depends on HAS_IOMEM + help + Generic PWM framework driver for the PWM controller on + Spreadtrum SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-sprd. + +config PWM_STI + tristate "STiH4xx PWM support" + depends on ARCH_STI || COMPILE_TEST + depends on OF + help + Generic PWM framework driver for STiH4xx SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-sti. + +config PWM_STM32 + tristate "STMicroelectronics STM32 PWM" + depends on MFD_STM32_TIMERS || COMPILE_TEST + help + Generic PWM framework driver for STM32 SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-stm32. + +config PWM_STM32_LP + tristate "STMicroelectronics STM32 PWM LP" + depends on MFD_STM32_LPTIMER || COMPILE_TEST + help + Generic PWM framework driver for STMicroelectronics STM32 SoCs + with Low-Power Timer (LPTIM). + + To compile this driver as a module, choose M here: the module + will be called pwm-stm32-lp. + +config PWM_STMPE + bool "STMPE expander PWM export" + depends on MFD_STMPE + help + This enables support for the PWMs found in the STMPE I/O + expanders. + +config PWM_SUN4I + tristate "Allwinner PWM support" + depends on ARCH_SUNXI || COMPILE_TEST + depends on HAS_IOMEM && COMMON_CLK + help + Generic PWM framework driver for Allwinner SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-sun4i. + +config PWM_TEGRA + tristate "NVIDIA Tegra PWM support" + depends on ARCH_TEGRA || COMPILE_TEST + help + Generic PWM framework driver for the PWFM controller found on NVIDIA + Tegra SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-tegra. + +config PWM_TIECAP + tristate "ECAP PWM support" + depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST + help + PWM driver support for the ECAP APWM controller found on TI SOCs + + To compile this driver as a module, choose M here: the module + will be called pwm-tiecap. + +config PWM_TIEHRPWM + tristate "EHRPWM PWM support" + depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_K3 || COMPILE_TEST + help + PWM driver support for the EHRPWM controller found on TI SOCs + + To compile this driver as a module, choose M here: the module + will be called pwm-tiehrpwm. + +config PWM_TWL + tristate "TWL4030/6030 PWM support" + depends on TWL4030_CORE + help + Generic PWM framework driver for TWL4030/6030. + + To compile this driver as a module, choose M here: the module + will be called pwm-twl. + +config PWM_TWL_LED + tristate "TWL4030/6030 PWM support for LED drivers" + depends on TWL4030_CORE + help + Generic PWM framework driver for TWL4030/6030 LED terminals. + + To compile this driver as a module, choose M here: the module + will be called pwm-twl-led. + +config PWM_VT8500 + tristate "vt8500 PWM support" + depends on ARCH_VT8500 || COMPILE_TEST + help + Generic PWM framework driver for vt8500. + + To compile this driver as a module, choose M here: the module + will be called pwm-vt8500. + +config PWM_ZX + tristate "ZTE ZX PWM support" + depends on ARCH_ZX || COMPILE_TEST + help + Generic PWM framework driver for ZTE ZX family SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-zx. + +endif diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile new file mode 100644 index 000000000..cbdcd55d6 --- /dev/null +++ b/drivers/pwm/Makefile @@ -0,0 +1,57 @@ +# 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_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_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_CLPS711X) += pwm-clps711x.o +obj-$(CONFIG_PWM_CRC) += pwm-crc.o +obj-$(CONFIG_PWM_CROS_EC) += pwm-cros-ec.o +obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o +obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o +obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o +obj-$(CONFIG_PWM_IMG) += pwm-img.o +obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o +obj-$(CONFIG_PWM_IMX27) += pwm-imx27.o +obj-$(CONFIG_PWM_IMX_TPM) += pwm-imx-tpm.o +obj-$(CONFIG_PWM_IQS620A) += pwm-iqs620a.o +obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.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_MEDIATEK) += pwm-mediatek.o +obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o +obj-$(CONFIG_PWM_MXS) += pwm-mxs.o +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_RCAR) += pwm-rcar.o +obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o +obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.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_SPEAR) += pwm-spear.o +obj-$(CONFIG_PWM_SPRD) += pwm-sprd.o +obj-$(CONFIG_PWM_STI) += pwm-sti.o +obj-$(CONFIG_PWM_STM32) += pwm-stm32.o +obj-$(CONFIG_PWM_STM32_LP) += pwm-stm32-lp.o +obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o +obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o +obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o +obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o +obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o +obj-$(CONFIG_PWM_TWL) += pwm-twl.o +obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o +obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o +obj-$(CONFIG_PWM_ZX) += pwm-zx.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c new file mode 100644 index 000000000..1f16f5365 --- /dev/null +++ b/drivers/pwm/core.c @@ -0,0 +1,1347 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Generic pwmlib implementation + * + * Copyright (C) 2011 Sascha Hauer <s.hauer@pengutronix.de> + * Copyright (C) 2011-2012 Avionic Design GmbH + */ + +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/pwm.h> +#include <linux/radix-tree.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#include <dt-bindings/pwm/pwm.h> + +#define CREATE_TRACE_POINTS +#include <trace/events/pwm.h> + +#define MAX_PWMS 1024 + +static DEFINE_MUTEX(pwm_lookup_lock); +static LIST_HEAD(pwm_lookup_list); +static DEFINE_MUTEX(pwm_lock); +static LIST_HEAD(pwm_chips); +static DECLARE_BITMAP(allocated_pwms, MAX_PWMS); +static RADIX_TREE(pwm_tree, GFP_KERNEL); + +static struct pwm_device *pwm_to_device(unsigned int pwm) +{ + return radix_tree_lookup(&pwm_tree, pwm); +} + +static int alloc_pwms(int pwm, unsigned int count) +{ + unsigned int from = 0; + unsigned int start; + + if (pwm >= MAX_PWMS) + return -EINVAL; + + if (pwm >= 0) + from = pwm; + + start = bitmap_find_next_zero_area(allocated_pwms, MAX_PWMS, from, + count, 0); + + if (pwm >= 0 && start != pwm) + return -EEXIST; + + if (start + count > MAX_PWMS) + return -ENOSPC; + + return start; +} + +static void free_pwms(struct pwm_chip *chip) +{ + unsigned int i; + + for (i = 0; i < chip->npwm; i++) { + struct pwm_device *pwm = &chip->pwms[i]; + + radix_tree_delete(&pwm_tree, pwm->pwm); + } + + bitmap_clear(allocated_pwms, chip->base, chip->npwm); + + kfree(chip->pwms); + chip->pwms = NULL; +} + +static struct pwm_chip *pwmchip_find_by_name(const char *name) +{ + struct pwm_chip *chip; + + if (!name) + return NULL; + + mutex_lock(&pwm_lock); + + list_for_each_entry(chip, &pwm_chips, list) { + const char *chip_name = dev_name(chip->dev); + + if (chip_name && strcmp(chip_name, name) == 0) { + mutex_unlock(&pwm_lock); + return chip; + } + } + + mutex_unlock(&pwm_lock); + + return NULL; +} + +static int pwm_device_request(struct pwm_device *pwm, const char *label) +{ + int err; + + if (test_bit(PWMF_REQUESTED, &pwm->flags)) + return -EBUSY; + + if (!try_module_get(pwm->chip->ops->owner)) + return -ENODEV; + + if (pwm->chip->ops->request) { + err = pwm->chip->ops->request(pwm->chip, pwm); + if (err) { + module_put(pwm->chip->ops->owner); + return err; + } + } + + if (pwm->chip->ops->get_state) { + pwm->chip->ops->get_state(pwm->chip, pwm, &pwm->state); + trace_pwm_get(pwm, &pwm->state); + + if (IS_ENABLED(CONFIG_PWM_DEBUG)) + pwm->last = pwm->state; + } + + set_bit(PWMF_REQUESTED, &pwm->flags); + pwm->label = label; + + return 0; +} + +struct pwm_device * +of_pwm_xlate_with_flags(struct pwm_chip *pc, const struct of_phandle_args *args) +{ + struct pwm_device *pwm; + + /* check, whether the driver supports a third cell for flags */ + if (pc->of_pwm_n_cells < 3) + return ERR_PTR(-EINVAL); + + /* flags in the third cell are optional */ + if (args->args_count < 2) + return ERR_PTR(-EINVAL); + + if (args->args[0] >= pc->npwm) + return ERR_PTR(-EINVAL); + + pwm = pwm_request_from_chip(pc, args->args[0], NULL); + if (IS_ERR(pwm)) + return pwm; + + pwm->args.period = args->args[1]; + pwm->args.polarity = PWM_POLARITY_NORMAL; + + if (args->args_count > 2 && args->args[2] & PWM_POLARITY_INVERTED) + pwm->args.polarity = PWM_POLARITY_INVERSED; + + return pwm; +} +EXPORT_SYMBOL_GPL(of_pwm_xlate_with_flags); + +static struct pwm_device * +of_pwm_simple_xlate(struct pwm_chip *pc, const struct of_phandle_args *args) +{ + struct pwm_device *pwm; + + /* sanity check driver support */ + if (pc->of_pwm_n_cells < 2) + return ERR_PTR(-EINVAL); + + /* all cells are required */ + if (args->args_count != pc->of_pwm_n_cells) + return ERR_PTR(-EINVAL); + + if (args->args[0] >= pc->npwm) + return ERR_PTR(-EINVAL); + + pwm = pwm_request_from_chip(pc, args->args[0], NULL); + if (IS_ERR(pwm)) + return pwm; + + pwm->args.period = args->args[1]; + + return pwm; +} + +static void of_pwmchip_add(struct pwm_chip *chip) +{ + if (!chip->dev || !chip->dev->of_node) + return; + + if (!chip->of_xlate) { + chip->of_xlate = of_pwm_simple_xlate; + chip->of_pwm_n_cells = 2; + } + + of_node_get(chip->dev->of_node); +} + +static void of_pwmchip_remove(struct pwm_chip *chip) +{ + if (chip->dev) + of_node_put(chip->dev->of_node); +} + +/** + * pwm_set_chip_data() - set private chip data for a PWM + * @pwm: PWM device + * @data: pointer to chip-specific data + * + * Returns: 0 on success or a negative error code on failure. + */ +int pwm_set_chip_data(struct pwm_device *pwm, void *data) +{ + if (!pwm) + return -EINVAL; + + pwm->chip_data = data; + + return 0; +} +EXPORT_SYMBOL_GPL(pwm_set_chip_data); + +/** + * pwm_get_chip_data() - get private chip data for a PWM + * @pwm: PWM device + * + * Returns: A pointer to the chip-private data for the PWM device. + */ +void *pwm_get_chip_data(struct pwm_device *pwm) +{ + return pwm ? pwm->chip_data : NULL; +} +EXPORT_SYMBOL_GPL(pwm_get_chip_data); + +static bool pwm_ops_check(const struct pwm_chip *chip) +{ + + const struct pwm_ops *ops = chip->ops; + + /* driver supports legacy, non-atomic operation */ + if (ops->config && ops->enable && ops->disable) { + if (IS_ENABLED(CONFIG_PWM_DEBUG)) + dev_warn(chip->dev, + "Driver needs updating to atomic API\n"); + + return true; + } + + if (!ops->apply) + return false; + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && !ops->get_state) + dev_warn(chip->dev, + "Please implement the .get_state() callback\n"); + + return true; +} + +/** + * pwmchip_add_with_polarity() - register a new PWM chip + * @chip: the PWM chip to add + * @polarity: initial polarity of PWM channels + * + * Register a new PWM chip. If chip->base < 0 then a dynamically assigned base + * will be used. The initial polarity for all channels is specified by the + * @polarity parameter. + * + * Returns: 0 on success or a negative error code on failure. + */ +int pwmchip_add_with_polarity(struct pwm_chip *chip, + enum pwm_polarity polarity) +{ + struct pwm_device *pwm; + unsigned int i; + int ret; + + if (!chip || !chip->dev || !chip->ops || !chip->npwm) + return -EINVAL; + + if (!pwm_ops_check(chip)) + return -EINVAL; + + mutex_lock(&pwm_lock); + + ret = alloc_pwms(chip->base, chip->npwm); + if (ret < 0) + goto out; + + chip->pwms = kcalloc(chip->npwm, sizeof(*pwm), GFP_KERNEL); + if (!chip->pwms) { + ret = -ENOMEM; + goto out; + } + + chip->base = ret; + + for (i = 0; i < chip->npwm; i++) { + pwm = &chip->pwms[i]; + + pwm->chip = chip; + pwm->pwm = chip->base + i; + pwm->hwpwm = i; + pwm->state.polarity = polarity; + + radix_tree_insert(&pwm_tree, pwm->pwm, pwm); + } + + bitmap_set(allocated_pwms, chip->base, chip->npwm); + + INIT_LIST_HEAD(&chip->list); + list_add(&chip->list, &pwm_chips); + + ret = 0; + + if (IS_ENABLED(CONFIG_OF)) + of_pwmchip_add(chip); + +out: + mutex_unlock(&pwm_lock); + + if (!ret) + pwmchip_sysfs_export(chip); + + return ret; +} +EXPORT_SYMBOL_GPL(pwmchip_add_with_polarity); + +/** + * pwmchip_add() - register a new PWM chip + * @chip: the PWM chip to add + * + * Register a new PWM chip. If chip->base < 0 then a dynamically assigned base + * will be used. The initial polarity for all channels is normal. + * + * Returns: 0 on success or a negative error code on failure. + */ +int pwmchip_add(struct pwm_chip *chip) +{ + return pwmchip_add_with_polarity(chip, PWM_POLARITY_NORMAL); +} +EXPORT_SYMBOL_GPL(pwmchip_add); + +/** + * pwmchip_remove() - remove a PWM chip + * @chip: the PWM chip to remove + * + * Removes a PWM chip. This function may return busy if the PWM chip provides + * a PWM device that is still requested. + * + * Returns: 0 on success or a negative error code on failure. + */ +int pwmchip_remove(struct pwm_chip *chip) +{ + unsigned int i; + int ret = 0; + + pwmchip_sysfs_unexport(chip); + + mutex_lock(&pwm_lock); + + for (i = 0; i < chip->npwm; i++) { + struct pwm_device *pwm = &chip->pwms[i]; + + if (test_bit(PWMF_REQUESTED, &pwm->flags)) { + ret = -EBUSY; + goto out; + } + } + + list_del_init(&chip->list); + + if (IS_ENABLED(CONFIG_OF)) + of_pwmchip_remove(chip); + + free_pwms(chip); + +out: + mutex_unlock(&pwm_lock); + return ret; +} +EXPORT_SYMBOL_GPL(pwmchip_remove); + +/** + * pwm_request() - request a PWM device + * @pwm: global PWM device index + * @label: PWM device label + * + * This function is deprecated, use pwm_get() instead. + * + * Returns: A pointer to a PWM device or an ERR_PTR()-encoded error code on + * failure. + */ +struct pwm_device *pwm_request(int pwm, const char *label) +{ + struct pwm_device *dev; + int err; + + if (pwm < 0 || pwm >= MAX_PWMS) + return ERR_PTR(-EINVAL); + + mutex_lock(&pwm_lock); + + dev = pwm_to_device(pwm); + if (!dev) { + dev = ERR_PTR(-EPROBE_DEFER); + goto out; + } + + err = pwm_device_request(dev, label); + if (err < 0) + dev = ERR_PTR(err); + +out: + mutex_unlock(&pwm_lock); + + return dev; +} +EXPORT_SYMBOL_GPL(pwm_request); + +/** + * pwm_request_from_chip() - request a PWM device relative to a PWM chip + * @chip: PWM chip + * @index: per-chip index of the PWM to request + * @label: a literal description string of this PWM + * + * Returns: A pointer to the PWM device at the given index of the given PWM + * 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) +{ + struct pwm_device *pwm; + int err; + + if (!chip || index >= chip->npwm) + return ERR_PTR(-EINVAL); + + mutex_lock(&pwm_lock); + pwm = &chip->pwms[index]; + + err = pwm_device_request(pwm, label); + if (err < 0) + pwm = ERR_PTR(err); + + mutex_unlock(&pwm_lock); + return pwm; +} +EXPORT_SYMBOL_GPL(pwm_request_from_chip); + +/** + * pwm_free() - free a PWM device + * @pwm: PWM device + * + * This function is deprecated, use pwm_put() instead. + */ +void pwm_free(struct pwm_device *pwm) +{ + pwm_put(pwm); +} +EXPORT_SYMBOL_GPL(pwm_free); + +static void pwm_apply_state_debug(struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct pwm_state *last = &pwm->last; + struct pwm_chip *chip = pwm->chip; + struct pwm_state s1, s2; + int err; + + if (!IS_ENABLED(CONFIG_PWM_DEBUG)) + return; + + /* No reasonable diagnosis possible without .get_state() */ + if (!chip->ops->get_state) + return; + + /* + * *state was just applied. Read out the hardware state and do some + * checks. + */ + + chip->ops->get_state(chip, pwm, &s1); + trace_pwm_get(pwm, &s1); + + /* + * The lowlevel driver either ignored .polarity (which is a bug) or as + * best effort inverted .polarity and fixed .duty_cycle respectively. + * Undo this inversion and fixup for further tests. + */ + if (s1.enabled && s1.polarity != state->polarity) { + s2.polarity = state->polarity; + s2.duty_cycle = s1.period - s1.duty_cycle; + s2.period = s1.period; + s2.enabled = s1.enabled; + } else { + s2 = s1; + } + + if (s2.polarity != state->polarity && + state->duty_cycle < state->period) + dev_warn(chip->dev, ".apply ignored .polarity\n"); + + if (state->enabled && + last->polarity == state->polarity && + last->period > s2.period && + last->period <= state->period) + dev_warn(chip->dev, + ".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) + dev_warn(chip->dev, + ".apply is supposed to round down period (requested: %llu, applied: %llu)\n", + state->period, s2.period); + + if (state->enabled && + last->polarity == state->polarity && + last->period == s2.period && + last->duty_cycle > s2.duty_cycle && + last->duty_cycle <= state->duty_cycle) + dev_warn(chip->dev, + ".apply didn't pick the best available duty cycle (requested: %llu/%llu, applied: %llu/%llu, possible: %llu/%llu)\n", + state->duty_cycle, state->period, + s2.duty_cycle, s2.period, + last->duty_cycle, last->period); + + if (state->enabled && state->duty_cycle < s2.duty_cycle) + dev_warn(chip->dev, + ".apply is supposed to round down duty_cycle (requested: %llu/%llu, applied: %llu/%llu)\n", + state->duty_cycle, state->period, + s2.duty_cycle, s2.period); + + if (!state->enabled && s2.enabled && s2.duty_cycle > 0) + dev_warn(chip->dev, + "requested disabled, but yielded enabled with duty > 0\n"); + + /* reapply the state that the driver reported being configured. */ + err = chip->ops->apply(chip, pwm, &s1); + if (err) { + *last = s1; + dev_err(chip->dev, "failed to reapply current setting\n"); + return; + } + + trace_pwm_apply(pwm, &s1); + + chip->ops->get_state(chip, pwm, last); + trace_pwm_get(pwm, last); + + /* reapplication of the current state should give an exact match */ + if (s1.enabled != last->enabled || + s1.polarity != last->polarity || + (s1.enabled && s1.period != last->period) || + (s1.enabled && s1.duty_cycle != last->duty_cycle)) { + dev_err(chip->dev, + ".apply is not idempotent (ena=%d pol=%d %llu/%llu) -> (ena=%d pol=%d %llu/%llu)\n", + s1.enabled, s1.polarity, s1.duty_cycle, s1.period, + last->enabled, last->polarity, last->duty_cycle, + last->period); + } +} + +/** + * pwm_apply_state() - atomically apply a new state to a PWM device + * @pwm: PWM device + * @state: new state to apply + */ +int pwm_apply_state(struct pwm_device *pwm, const struct pwm_state *state) +{ + struct pwm_chip *chip; + int err; + + if (!pwm || !state || !state->period || + state->duty_cycle > state->period) + return -EINVAL; + + chip = pwm->chip; + + if (state->period == pwm->state.period && + state->duty_cycle == pwm->state.duty_cycle && + state->polarity == pwm->state.polarity && + state->enabled == pwm->state.enabled) + return 0; + + if (chip->ops->apply) { + err = chip->ops->apply(chip, pwm, state); + if (err) + return err; + + trace_pwm_apply(pwm, state); + + pwm->state = *state; + + /* + * only do this after pwm->state was applied as some + * implementations of .get_state depend on this + */ + pwm_apply_state_debug(pwm, state); + } else { + /* + * FIXME: restore the initial state in case of error. + */ + if (state->polarity != pwm->state.polarity) { + if (!chip->ops->set_polarity) + return -ENOTSUPP; + + /* + * Changing the polarity of a running PWM is + * only allowed when the PWM driver implements + * ->apply(). + */ + if (pwm->state.enabled) { + chip->ops->disable(chip, pwm); + pwm->state.enabled = false; + } + + err = chip->ops->set_polarity(chip, pwm, + state->polarity); + if (err) + return err; + + pwm->state.polarity = state->polarity; + } + + if (state->period != pwm->state.period || + state->duty_cycle != pwm->state.duty_cycle) { + err = chip->ops->config(pwm->chip, pwm, + state->duty_cycle, + state->period); + if (err) + return err; + + pwm->state.duty_cycle = state->duty_cycle; + pwm->state.period = state->period; + } + + if (state->enabled != pwm->state.enabled) { + if (state->enabled) { + err = chip->ops->enable(chip, pwm); + if (err) + return err; + } else { + chip->ops->disable(chip, pwm); + } + + pwm->state.enabled = state->enabled; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(pwm_apply_state); + +/** + * pwm_capture() - capture and report a PWM signal + * @pwm: PWM device + * @result: structure to fill with capture result + * @timeout: time to wait, in milliseconds, before giving up on capture + * + * 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) +{ + int err; + + if (!pwm || !pwm->chip->ops) + return -EINVAL; + + if (!pwm->chip->ops->capture) + return -ENOSYS; + + mutex_lock(&pwm_lock); + err = pwm->chip->ops->capture(pwm->chip, pwm, result, timeout); + mutex_unlock(&pwm_lock); + + return err; +} +EXPORT_SYMBOL_GPL(pwm_capture); + +/** + * 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. + */ +int pwm_adjust_config(struct pwm_device *pwm) +{ + struct pwm_state state; + struct pwm_args pargs; + + pwm_get_args(pwm, &pargs); + pwm_get_state(pwm, &state); + + /* + * If the current period is zero it means that either the PWM driver + * does not support initial state retrieval or the PWM has not yet + * been configured. + * + * In either case, we setup the new period and polarity, and assign a + * duty cycle of 0. + */ + if (!state.period) { + state.duty_cycle = 0; + state.period = pargs.period; + state.polarity = pargs.polarity; + + return pwm_apply_state(pwm, &state); + } + + /* + * Adjust the PWM duty cycle/period based on the period value provided + * in PWM args. + */ + if (pargs.period != state.period) { + u64 dutycycle = (u64)state.duty_cycle * pargs.period; + + do_div(dutycycle, state.period); + state.duty_cycle = dutycycle; + state.period = pargs.period; + } + + /* + * If the polarity changed, we should also change the duty cycle. + */ + if (pargs.polarity != state.polarity) { + state.polarity = pargs.polarity; + state.duty_cycle = state.period - state.duty_cycle; + } + + return pwm_apply_state(pwm, &state); +} +EXPORT_SYMBOL_GPL(pwm_adjust_config); + +static struct pwm_chip *of_node_to_pwmchip(struct device_node *np) +{ + struct pwm_chip *chip; + + mutex_lock(&pwm_lock); + + list_for_each_entry(chip, &pwm_chips, list) + if (chip->dev && chip->dev->of_node == np) { + mutex_unlock(&pwm_lock); + return chip; + } + + mutex_unlock(&pwm_lock); + + return ERR_PTR(-EPROBE_DEFER); +} + +static struct device_link *pwm_device_link_add(struct device *dev, + struct pwm_device *pwm) +{ + struct device_link *dl; + + if (!dev) { + /* + * No device for the PWM consumer has been provided. It may + * impact the PM sequence ordering: the PWM supplier may get + * suspended before the consumer. + */ + dev_warn(pwm->chip->dev, + "No consumer device specified to create a link to\n"); + return NULL; + } + + dl = device_link_add(dev, pwm->chip->dev, DL_FLAG_AUTOREMOVE_CONSUMER); + if (!dl) { + dev_err(dev, "failed to create device link to %s\n", + dev_name(pwm->chip->dev)); + return ERR_PTR(-EINVAL); + } + + return dl; +} + +/** + * of_pwm_get() - request a PWM via the PWM framework + * @dev: device for PWM consumer + * @np: device node to get the PWM from + * @con_id: consumer name + * + * Returns the PWM device parsed from the phandle and index specified in the + * "pwms" property of a device tree node or a negative error-code on failure. + * Values parsed from the device tree are stored in the returned PWM device + * object. + * + * If con_id is NULL, the first PWM device listed in the "pwms" property will + * be requested. Otherwise the "pwm-names" property is used to do a reverse + * lookup of the PWM index. This also means that the "pwm-names" property + * becomes mandatory for devices that look up the PWM device via the con_id + * parameter. + * + * Returns: A pointer to the requested PWM device or an ERR_PTR()-encoded + * error code on failure. + */ +struct pwm_device *of_pwm_get(struct device *dev, struct device_node *np, + const char *con_id) +{ + struct pwm_device *pwm = NULL; + struct of_phandle_args args; + struct device_link *dl; + struct pwm_chip *pc; + int index = 0; + int err; + + if (con_id) { + index = of_property_match_string(np, "pwm-names", con_id); + if (index < 0) + return ERR_PTR(index); + } + + err = of_parse_phandle_with_args(np, "pwms", "#pwm-cells", index, + &args); + if (err) { + pr_err("%s(): can't parse \"pwms\" property\n", __func__); + return ERR_PTR(err); + } + + pc = of_node_to_pwmchip(args.np); + if (IS_ERR(pc)) { + if (PTR_ERR(pc) != -EPROBE_DEFER) + pr_err("%s(): PWM chip not found\n", __func__); + + pwm = ERR_CAST(pc); + goto put; + } + + pwm = pc->of_xlate(pc, &args); + if (IS_ERR(pwm)) + goto put; + + dl = pwm_device_link_add(dev, pwm); + if (IS_ERR(dl)) { + /* of_xlate ended up calling pwm_request_from_chip() */ + pwm_free(pwm); + pwm = ERR_CAST(dl); + goto put; + } + + /* + * If a consumer name was not given, try to look it up from the + * "pwm-names" property if it exists. Otherwise use the name of + * the user device node. + */ + if (!con_id) { + err = of_property_read_string_index(np, "pwm-names", index, + &con_id); + if (err < 0) + con_id = np->name; + } + + pwm->label = con_id; + +put: + of_node_put(args.np); + + return pwm; +} +EXPORT_SYMBOL_GPL(of_pwm_get); + +#if IS_ENABLED(CONFIG_ACPI) +static struct pwm_chip *device_to_pwmchip(struct device *dev) +{ + struct pwm_chip *chip; + + mutex_lock(&pwm_lock); + + list_for_each_entry(chip, &pwm_chips, list) { + struct acpi_device *adev = ACPI_COMPANION(chip->dev); + + if ((chip->dev == dev) || (adev && &adev->dev == dev)) { + mutex_unlock(&pwm_lock); + return chip; + } + } + + mutex_unlock(&pwm_lock); + + return ERR_PTR(-EPROBE_DEFER); +} +#endif + +/** + * acpi_pwm_get() - request a PWM via parsing "pwms" property in ACPI + * @fwnode: firmware node to get the "pwm" property from + * + * Returns the PWM device parsed from the fwnode and index specified in the + * "pwms" property or a negative error-code on failure. + * Values parsed from the device tree are stored in the returned PWM device + * object. + * + * This is analogous to of_pwm_get() except con_id is not yet supported. + * ACPI entries must look like + * Package () {"pwms", Package () + * { <PWM device reference>, <PWM index>, <PWM period> [, <PWM flags>]}} + * + * Returns: A pointer to the requested PWM device or an ERR_PTR()-encoded + * error code on failure. + */ +static struct pwm_device *acpi_pwm_get(struct fwnode_handle *fwnode) +{ + struct pwm_device *pwm = ERR_PTR(-ENODEV); +#if IS_ENABLED(CONFIG_ACPI) + struct fwnode_reference_args args; + struct acpi_device *acpi; + struct pwm_chip *chip; + int ret; + + memset(&args, 0, sizeof(args)); + + ret = __acpi_node_get_property_reference(fwnode, "pwms", 0, 3, &args); + if (ret < 0) + return ERR_PTR(ret); + + acpi = to_acpi_device_node(args.fwnode); + if (!acpi) + return ERR_PTR(-EINVAL); + + if (args.nargs < 2) + return ERR_PTR(-EPROTO); + + chip = device_to_pwmchip(&acpi->dev); + if (IS_ERR(chip)) + return ERR_CAST(chip); + + pwm = pwm_request_from_chip(chip, args.args[0], NULL); + if (IS_ERR(pwm)) + return pwm; + + pwm->args.period = args.args[1]; + pwm->args.polarity = PWM_POLARITY_NORMAL; + + if (args.nargs > 2 && args.args[2] & PWM_POLARITY_INVERTED) + pwm->args.polarity = PWM_POLARITY_INVERSED; +#endif + + return pwm; +} + +/** + * 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 + * + * Lookup is first attempted using DT. If the device was not instantiated from + * a device tree, a PWM chip and a relative index is looked up via a table + * supplied by board setup code (see pwm_add_table()). + * + * Once a PWM chip has been found the specified PWM device will be requested + * and is ready to be used. + * + * Returns: A pointer to the requested PWM device or an ERR_PTR()-encoded + * error code on failure. + */ +struct pwm_device *pwm_get(struct device *dev, const char *con_id) +{ + const char *dev_id = dev ? dev_name(dev) : NULL; + struct pwm_device *pwm; + struct pwm_chip *chip; + struct device_link *dl; + unsigned int best = 0; + struct pwm_lookup *p, *chosen = NULL; + unsigned int match; + int err; + + /* look up via DT first */ + if (IS_ENABLED(CONFIG_OF) && dev && dev->of_node) + return of_pwm_get(dev, dev->of_node, con_id); + + /* then lookup via ACPI */ + if (dev && is_acpi_node(dev->fwnode)) { + pwm = acpi_pwm_get(dev->fwnode); + if (!IS_ERR(pwm) || PTR_ERR(pwm) != -ENOENT) + return pwm; + } + + /* + * We look up the provider in the static table typically provided by + * board setup code. We first try to lookup the consumer device by + * name. If the consumer device was passed in as NULL or if no match + * was found, we try to find the consumer by directly looking it up + * by name. + * + * If a match is found, the provider PWM chip is looked up by name + * and a PWM device is requested using the PWM device per-chip index. + * + * The lookup algorithm was shamelessly taken from the clock + * framework: + * + * We do slightly fuzzy matching here: + * An entry with a NULL ID is assumed to be a wildcard. + * If an entry has a device ID, it must match + * If an entry has a connection ID, it must match + * 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; + + if (p->dev_id) { + if (!dev_id || strcmp(p->dev_id, dev_id)) + continue; + + match += 2; + } + + if (p->con_id) { + if (!con_id || strcmp(p->con_id, con_id)) + continue; + + match += 1; + } + + if (match > best) { + chosen = p; + + if (match != 3) + best = match; + else + break; + } + } + + mutex_unlock(&pwm_lookup_lock); + + if (!chosen) + return ERR_PTR(-ENODEV); + + chip = pwmchip_find_by_name(chosen->provider); + + /* + * If the lookup entry specifies a module, load the module and retry + * the PWM chip lookup. This can be used to work around driver load + * ordering issues if driver's can't be made to properly support the + * deferred probe mechanism. + */ + if (!chip && chosen->module) { + err = request_module(chosen->module); + if (err == 0) + chip = pwmchip_find_by_name(chosen->provider); + } + + if (!chip) + return ERR_PTR(-EPROBE_DEFER); + + pwm = pwm_request_from_chip(chip, chosen->index, con_id ?: dev_id); + if (IS_ERR(pwm)) + return pwm; + + dl = pwm_device_link_add(dev, pwm); + if (IS_ERR(dl)) { + pwm_free(pwm); + return ERR_CAST(dl); + } + + pwm->args.period = chosen->period; + pwm->args.polarity = chosen->polarity; + + return pwm; +} +EXPORT_SYMBOL_GPL(pwm_get); + +/** + * pwm_put() - release a PWM device + * @pwm: PWM device + */ +void pwm_put(struct pwm_device *pwm) +{ + if (!pwm) + return; + + mutex_lock(&pwm_lock); + + if (!test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) { + pr_warn("PWM device already freed\n"); + goto out; + } + + if (pwm->chip->ops->free) + pwm->chip->ops->free(pwm->chip, pwm); + + pwm_set_chip_data(pwm, NULL); + pwm->label = NULL; + + module_put(pwm->chip->ops->owner); +out: + mutex_unlock(&pwm_lock); +} +EXPORT_SYMBOL_GPL(pwm_put); + +static void devm_pwm_release(struct device *dev, void *res) +{ + pwm_put(*(struct pwm_device **)res); +} + +/** + * devm_pwm_get() - resource managed pwm_get() + * @dev: device for PWM consumer + * @con_id: consumer name + * + * This function performs like pwm_get() but the acquired PWM device will + * automatically be released on driver detach. + * + * Returns: A pointer to the requested PWM device or an ERR_PTR()-encoded + * error code on failure. + */ +struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id) +{ + struct pwm_device **ptr, *pwm; + + ptr = devres_alloc(devm_pwm_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + pwm = pwm_get(dev, con_id); + if (!IS_ERR(pwm)) { + *ptr = pwm; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return pwm; +} +EXPORT_SYMBOL_GPL(devm_pwm_get); + +/** + * devm_of_pwm_get() - resource managed of_pwm_get() + * @dev: device for PWM consumer + * @np: device node to get the PWM from + * @con_id: consumer name + * + * This function performs like of_pwm_get() but the acquired PWM device will + * automatically be released on driver detach. + * + * Returns: A pointer to the requested PWM device or an ERR_PTR()-encoded + * error code on failure. + */ +struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np, + const char *con_id) +{ + struct pwm_device **ptr, *pwm; + + ptr = devres_alloc(devm_pwm_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + pwm = of_pwm_get(dev, np, con_id); + if (!IS_ERR(pwm)) { + *ptr = pwm; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return pwm; +} +EXPORT_SYMBOL_GPL(devm_of_pwm_get); + +/** + * devm_fwnode_pwm_get() - request a resource managed PWM from firmware node + * @dev: device for PWM consumer + * @fwnode: firmware node to get the PWM from + * @con_id: consumer name + * + * Returns the PWM device parsed from the firmware node. See of_pwm_get() and + * acpi_pwm_get() for a detailed description. + * + * Returns: A pointer to the requested PWM device or an ERR_PTR()-encoded + * error code on failure. + */ +struct pwm_device *devm_fwnode_pwm_get(struct device *dev, + struct fwnode_handle *fwnode, + const char *con_id) +{ + struct pwm_device **ptr, *pwm = ERR_PTR(-ENODEV); + + ptr = devres_alloc(devm_pwm_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + if (is_of_node(fwnode)) + pwm = of_pwm_get(dev, to_of_node(fwnode), con_id); + else if (is_acpi_node(fwnode)) + pwm = acpi_pwm_get(fwnode); + + if (!IS_ERR(pwm)) { + *ptr = pwm; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return pwm; +} +EXPORT_SYMBOL_GPL(devm_fwnode_pwm_get); + +static int devm_pwm_match(struct device *dev, void *res, void *data) +{ + struct pwm_device **p = res; + + if (WARN_ON(!p || !*p)) + return 0; + + return *p == data; +} + +/** + * devm_pwm_put() - resource managed pwm_put() + * @dev: device for PWM consumer + * @pwm: PWM device + * + * Release a PWM previously allocated using devm_pwm_get(). Calling this + * function is usually not needed because devm-allocated resources are + * automatically released on driver detach. + */ +void devm_pwm_put(struct device *dev, struct pwm_device *pwm) +{ + WARN_ON(devres_release(dev, devm_pwm_release, devm_pwm_match, pwm)); +} +EXPORT_SYMBOL_GPL(devm_pwm_put); + +#ifdef CONFIG_DEBUG_FS +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; + + pwm_get_state(pwm, &state); + + 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_printf(s, " period: %llu ns", state.period); + seq_printf(s, " duty: %llu ns", state.duty_cycle); + seq_printf(s, " polarity: %s", + state.polarity ? "inverse" : "normal"); + + seq_puts(s, "\n"); + } +} + +static void *pwm_seq_start(struct seq_file *s, loff_t *pos) +{ + mutex_lock(&pwm_lock); + s->private = ""; + + return seq_list_start(&pwm_chips, *pos); +} + +static void *pwm_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + s->private = "\n"; + + return seq_list_next(v, &pwm_chips, pos); +} + +static void pwm_seq_stop(struct seq_file *s, void *v) +{ + mutex_unlock(&pwm_lock); +} + +static int pwm_seq_show(struct seq_file *s, void *v) +{ + struct pwm_chip *chip = list_entry(v, struct pwm_chip, list); + + seq_printf(s, "%s%s/%s, %d PWM device%s\n", (char *)s->private, + chip->dev->bus ? chip->dev->bus->name : "no-bus", + dev_name(chip->dev), chip->npwm, + (chip->npwm != 1) ? "s" : ""); + + pwm_dbg_show(chip, s); + + return 0; +} + +static const struct seq_operations pwm_debugfs_sops = { + .start = pwm_seq_start, + .next = pwm_seq_next, + .stop = pwm_seq_stop, + .show = pwm_seq_show, +}; + +DEFINE_SEQ_ATTRIBUTE(pwm_debugfs); + +static int __init pwm_debugfs_init(void) +{ + debugfs_create_file("pwm", S_IFREG | S_IRUGO, NULL, NULL, + &pwm_debugfs_fops); + + return 0; +} +subsys_initcall(pwm_debugfs_init); +#endif /* CONFIG_DEBUG_FS */ diff --git a/drivers/pwm/pwm-ab8500.c b/drivers/pwm/pwm-ab8500.c new file mode 100644 index 000000000..fdf3964db --- /dev/null +++ b/drivers/pwm/pwm-ab8500.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Arun R Murthy <arun.murthy@stericsson.com> + */ +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/pwm.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/module.h> + +/* + * PWM Out generators + * Bank: 0x10 + */ +#define AB8500_PWM_OUT_CTRL1_REG 0x60 +#define AB8500_PWM_OUT_CTRL2_REG 0x61 +#define AB8500_PWM_OUT_CTRL7_REG 0x66 + +struct ab8500_pwm_chip { + struct pwm_chip chip; +}; + +static int ab8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + int ret = 0; + unsigned int higher_val, lower_val; + u8 reg; + + /* + * get the first 8 bits that are be written to + * AB8500_PWM_OUT_CTRL1_REG[0:7] + */ + lower_val = duty_ns & 0x00FF; + /* + * get bits [9:10] that are to be written to + * AB8500_PWM_OUT_CTRL2_REG[0:1] + */ + higher_val = ((duty_ns & 0x0300) >> 8); + + reg = AB8500_PWM_OUT_CTRL1_REG + ((chip->base - 1) * 2); + + ret = abx500_set_register_interruptible(chip->dev, AB8500_MISC, + reg, (u8)lower_val); + if (ret < 0) + return ret; + ret = abx500_set_register_interruptible(chip->dev, AB8500_MISC, + (reg + 1), (u8)higher_val); + + return ret; +} + +static int ab8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + int ret; + + ret = abx500_mask_and_set_register_interruptible(chip->dev, + AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, + 1 << (chip->base - 1), 1 << (chip->base - 1)); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to enable PWM, Error %d\n", + pwm->label, ret); + return ret; +} + +static void ab8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + int ret; + + ret = abx500_mask_and_set_register_interruptible(chip->dev, + AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, + 1 << (chip->base - 1), 0); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to disable PWM, Error %d\n", + pwm->label, ret); +} + +static const struct pwm_ops ab8500_pwm_ops = { + .config = ab8500_pwm_config, + .enable = ab8500_pwm_enable, + .disable = ab8500_pwm_disable, + .owner = THIS_MODULE, +}; + +static int ab8500_pwm_probe(struct platform_device *pdev) +{ + struct ab8500_pwm_chip *ab8500; + int err; + + /* + * Nothing to be done in probe, this is required to get the + * device which is required for ab8500 read and write + */ + ab8500 = devm_kzalloc(&pdev->dev, sizeof(*ab8500), GFP_KERNEL); + if (ab8500 == NULL) + return -ENOMEM; + + ab8500->chip.dev = &pdev->dev; + ab8500->chip.ops = &ab8500_pwm_ops; + ab8500->chip.base = pdev->id; + ab8500->chip.npwm = 1; + + err = pwmchip_add(&ab8500->chip); + if (err < 0) + return err; + + dev_dbg(&pdev->dev, "pwm probe successful\n"); + platform_set_drvdata(pdev, ab8500); + + return 0; +} + +static int ab8500_pwm_remove(struct platform_device *pdev) +{ + struct ab8500_pwm_chip *ab8500 = platform_get_drvdata(pdev); + int err; + + err = pwmchip_remove(&ab8500->chip); + if (err < 0) + return err; + + dev_dbg(&pdev->dev, "pwm driver removed\n"); + + return 0; +} + +static struct platform_driver ab8500_pwm_driver = { + .driver = { + .name = "ab8500-pwm", + }, + .probe = ab8500_pwm_probe, + .remove = ab8500_pwm_remove, +}; +module_platform_driver(ab8500_pwm_driver); + +MODULE_AUTHOR("Arun MURTHY <arun.murthy@stericsson.com>"); +MODULE_DESCRIPTION("AB8500 Pulse Width Modulation Driver"); +MODULE_ALIAS("platform:ab8500-pwm"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-atmel-hlcdc.c b/drivers/pwm/pwm-atmel-hlcdc.c new file mode 100644 index 000000000..dcbc0489d --- /dev/null +++ b/drivers/pwm/pwm-atmel-hlcdc.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014 Free Electrons + * Copyright (C) 2014 Atmel + * + * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/mfd/atmel-hlcdc.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> + +#define ATMEL_HLCDC_PWMCVAL_MASK GENMASK(15, 8) +#define ATMEL_HLCDC_PWMCVAL(x) (((x) << 8) & ATMEL_HLCDC_PWMCVAL_MASK) +#define ATMEL_HLCDC_PWMPOL BIT(4) +#define ATMEL_HLCDC_PWMPS_MASK GENMASK(2, 0) +#define ATMEL_HLCDC_PWMPS_MAX 0x6 +#define ATMEL_HLCDC_PWMPS(x) ((x) & ATMEL_HLCDC_PWMPS_MASK) + +struct atmel_hlcdc_pwm_errata { + bool slow_clk_erratum; + bool div1_clk_erratum; +}; + +struct atmel_hlcdc_pwm { + struct pwm_chip chip; + struct atmel_hlcdc *hlcdc; + struct clk *cur_clk; + const struct atmel_hlcdc_pwm_errata *errata; +}; + +static inline struct atmel_hlcdc_pwm *to_atmel_hlcdc_pwm(struct pwm_chip *chip) +{ + return container_of(chip, struct atmel_hlcdc_pwm, chip); +} + +static int atmel_hlcdc_pwm_apply(struct pwm_chip *c, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct atmel_hlcdc_pwm *chip = to_atmel_hlcdc_pwm(c); + struct atmel_hlcdc *hlcdc = chip->hlcdc; + unsigned int status; + int ret; + + if (state->enabled) { + struct clk *new_clk = hlcdc->slow_clk; + u64 pwmcval = state->duty_cycle * 256; + unsigned long clk_freq; + u64 clk_period_ns; + u32 pwmcfg; + int pres; + + if (!chip->errata || !chip->errata->slow_clk_erratum) { + clk_freq = clk_get_rate(new_clk); + if (!clk_freq) + return -EINVAL; + + clk_period_ns = (u64)NSEC_PER_SEC * 256; + do_div(clk_period_ns, clk_freq); + } + + /* Errata: cannot use slow clk on some IP revisions */ + if ((chip->errata && chip->errata->slow_clk_erratum) || + clk_period_ns > state->period) { + new_clk = hlcdc->sys_clk; + clk_freq = clk_get_rate(new_clk); + if (!clk_freq) + return -EINVAL; + + clk_period_ns = (u64)NSEC_PER_SEC * 256; + do_div(clk_period_ns, clk_freq); + } + + for (pres = 0; pres <= ATMEL_HLCDC_PWMPS_MAX; pres++) { + /* Errata: cannot divide by 1 on some IP revisions */ + if (!pres && chip->errata && + chip->errata->div1_clk_erratum) + continue; + + if ((clk_period_ns << pres) >= state->period) + break; + } + + if (pres > ATMEL_HLCDC_PWMPS_MAX) + return -EINVAL; + + pwmcfg = ATMEL_HLCDC_PWMPS(pres); + + if (new_clk != chip->cur_clk) { + u32 gencfg = 0; + int ret; + + ret = clk_prepare_enable(new_clk); + if (ret) + return ret; + + clk_disable_unprepare(chip->cur_clk); + chip->cur_clk = new_clk; + + if (new_clk == hlcdc->sys_clk) + gencfg = ATMEL_HLCDC_CLKPWMSEL; + + ret = regmap_update_bits(hlcdc->regmap, + ATMEL_HLCDC_CFG(0), + ATMEL_HLCDC_CLKPWMSEL, + gencfg); + if (ret) + return ret; + } + + do_div(pwmcval, state->period); + + /* + * The PWM duty cycle is configurable from 0/256 to 255/256 of + * the period cycle. Hence we can't set a duty cycle occupying + * the whole period cycle if we're asked to. + * Set it to 255 if pwmcval is greater than 256. + */ + if (pwmcval > 255) + pwmcval = 255; + + pwmcfg |= ATMEL_HLCDC_PWMCVAL(pwmcval); + + if (state->polarity == PWM_POLARITY_NORMAL) + pwmcfg |= ATMEL_HLCDC_PWMPOL; + + ret = regmap_update_bits(hlcdc->regmap, ATMEL_HLCDC_CFG(6), + ATMEL_HLCDC_PWMCVAL_MASK | + ATMEL_HLCDC_PWMPS_MASK | + ATMEL_HLCDC_PWMPOL, + pwmcfg); + if (ret) + return ret; + + ret = regmap_write(hlcdc->regmap, ATMEL_HLCDC_EN, + ATMEL_HLCDC_PWM); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(hlcdc->regmap, ATMEL_HLCDC_SR, + status, + status & ATMEL_HLCDC_PWM, + 10, 0); + if (ret) + return ret; + } else { + ret = regmap_write(hlcdc->regmap, ATMEL_HLCDC_DIS, + ATMEL_HLCDC_PWM); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(hlcdc->regmap, ATMEL_HLCDC_SR, + status, + !(status & ATMEL_HLCDC_PWM), + 10, 0); + if (ret) + return ret; + + clk_disable_unprepare(chip->cur_clk); + chip->cur_clk = NULL; + } + + return 0; +} + +static const struct pwm_ops atmel_hlcdc_pwm_ops = { + .apply = atmel_hlcdc_pwm_apply, + .owner = THIS_MODULE, +}; + +static const struct atmel_hlcdc_pwm_errata atmel_hlcdc_pwm_at91sam9x5_errata = { + .slow_clk_erratum = true, +}; + +static const struct atmel_hlcdc_pwm_errata atmel_hlcdc_pwm_sama5d3_errata = { + .div1_clk_erratum = true, +}; + +#ifdef CONFIG_PM_SLEEP +static int atmel_hlcdc_pwm_suspend(struct device *dev) +{ + struct atmel_hlcdc_pwm *chip = dev_get_drvdata(dev); + + /* Keep the periph clock enabled if the PWM is still running. */ + if (pwm_is_enabled(&chip->chip.pwms[0])) + clk_disable_unprepare(chip->hlcdc->periph_clk); + + return 0; +} + +static int atmel_hlcdc_pwm_resume(struct device *dev) +{ + struct atmel_hlcdc_pwm *chip = dev_get_drvdata(dev); + struct pwm_state state; + int ret; + + pwm_get_state(&chip->chip.pwms[0], &state); + + /* Re-enable the periph clock it was stopped during suspend. */ + if (!state.enabled) { + ret = clk_prepare_enable(chip->hlcdc->periph_clk); + if (ret) + return ret; + } + + return atmel_hlcdc_pwm_apply(&chip->chip, &chip->chip.pwms[0], &state); +} +#endif + +static SIMPLE_DEV_PM_OPS(atmel_hlcdc_pwm_pm_ops, + atmel_hlcdc_pwm_suspend, atmel_hlcdc_pwm_resume); + +static const struct of_device_id atmel_hlcdc_dt_ids[] = { + { + .compatible = "atmel,at91sam9n12-hlcdc", + /* 9n12 has same errata as 9x5 HLCDC PWM */ + .data = &atmel_hlcdc_pwm_at91sam9x5_errata, + }, + { + .compatible = "atmel,at91sam9x5-hlcdc", + .data = &atmel_hlcdc_pwm_at91sam9x5_errata, + }, + { + .compatible = "atmel,sama5d2-hlcdc", + }, + { + .compatible = "atmel,sama5d3-hlcdc", + .data = &atmel_hlcdc_pwm_sama5d3_errata, + }, + { + .compatible = "atmel,sama5d4-hlcdc", + .data = &atmel_hlcdc_pwm_sama5d3_errata, + }, + { .compatible = "microchip,sam9x60-hlcdc", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, atmel_hlcdc_dt_ids); + +static int atmel_hlcdc_pwm_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct device *dev = &pdev->dev; + struct atmel_hlcdc_pwm *chip; + struct atmel_hlcdc *hlcdc; + int ret; + + hlcdc = dev_get_drvdata(dev->parent); + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + ret = clk_prepare_enable(hlcdc->periph_clk); + if (ret) + return ret; + + match = of_match_node(atmel_hlcdc_dt_ids, dev->parent->of_node); + if (match) + chip->errata = match->data; + + chip->hlcdc = hlcdc; + chip->chip.ops = &atmel_hlcdc_pwm_ops; + chip->chip.dev = dev; + chip->chip.base = -1; + chip->chip.npwm = 1; + chip->chip.of_xlate = of_pwm_xlate_with_flags; + chip->chip.of_pwm_n_cells = 3; + + ret = pwmchip_add_with_polarity(&chip->chip, PWM_POLARITY_INVERSED); + if (ret) { + clk_disable_unprepare(hlcdc->periph_clk); + return ret; + } + + platform_set_drvdata(pdev, chip); + + return 0; +} + +static int atmel_hlcdc_pwm_remove(struct platform_device *pdev) +{ + struct atmel_hlcdc_pwm *chip = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&chip->chip); + if (ret) + return ret; + + clk_disable_unprepare(chip->hlcdc->periph_clk); + + return 0; +} + +static const struct of_device_id atmel_hlcdc_pwm_dt_ids[] = { + { .compatible = "atmel,hlcdc-pwm" }, + { /* sentinel */ }, +}; + +static struct platform_driver atmel_hlcdc_pwm_driver = { + .driver = { + .name = "atmel-hlcdc-pwm", + .of_match_table = atmel_hlcdc_pwm_dt_ids, + .pm = &atmel_hlcdc_pwm_pm_ops, + }, + .probe = atmel_hlcdc_pwm_probe, + .remove = atmel_hlcdc_pwm_remove, +}; +module_platform_driver(atmel_hlcdc_pwm_driver); + +MODULE_ALIAS("platform:atmel-hlcdc-pwm"); +MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); +MODULE_DESCRIPTION("Atmel HLCDC PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c new file mode 100644 index 000000000..85c537019 --- /dev/null +++ b/drivers/pwm/pwm-atmel-tcb.c @@ -0,0 +1,516 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) Overkiz SAS 2012 + * + * Author: Boris BREZILLON <b.brezillon@overkiz.com> + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/clocksource.h> +#include <linux/clockchips.h> +#include <linux/interrupt.h> +#include <linux/irq.h> + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/of_device.h> +#include <linux/slab.h> +#include <soc/at91/atmel_tcb.h> + +#define NPWM 6 + +#define ATMEL_TC_ACMR_MASK (ATMEL_TC_ACPA | ATMEL_TC_ACPC | \ + ATMEL_TC_AEEVT | ATMEL_TC_ASWTRG) + +#define ATMEL_TC_BCMR_MASK (ATMEL_TC_BCPB | ATMEL_TC_BCPC | \ + ATMEL_TC_BEEVT | ATMEL_TC_BSWTRG) + +struct atmel_tcb_pwm_device { + enum pwm_polarity polarity; /* PWM polarity */ + unsigned div; /* PWM clock divider */ + unsigned duty; /* PWM duty expressed in clk cycles */ + unsigned period; /* PWM period expressed in clk cycles */ +}; + +struct atmel_tcb_channel { + u32 enabled; + u32 cmr; + u32 ra; + u32 rb; + u32 rc; +}; + +struct atmel_tcb_pwm_chip { + struct pwm_chip chip; + spinlock_t lock; + struct atmel_tc *tc; + struct atmel_tcb_pwm_device *pwms[NPWM]; + struct atmel_tcb_channel bkup[NPWM / 2]; +}; + +static inline struct atmel_tcb_pwm_chip *to_tcb_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct atmel_tcb_pwm_chip, chip); +} + +static int atmel_tcb_pwm_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); + + tcbpwm->polarity = polarity; + + return 0; +} + +static int atmel_tcb_pwm_request(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); + struct atmel_tcb_pwm_device *tcbpwm; + struct atmel_tc *tc = tcbpwmc->tc; + void __iomem *regs = tc->regs; + unsigned group = pwm->hwpwm / 2; + unsigned index = pwm->hwpwm % 2; + unsigned cmr; + int ret; + + tcbpwm = devm_kzalloc(chip->dev, sizeof(*tcbpwm), GFP_KERNEL); + if (!tcbpwm) + return -ENOMEM; + + ret = clk_prepare_enable(tc->clk[group]); + if (ret) { + devm_kfree(chip->dev, tcbpwm); + return ret; + } + + pwm_set_chip_data(pwm, tcbpwm); + tcbpwm->polarity = PWM_POLARITY_NORMAL; + tcbpwm->duty = 0; + tcbpwm->period = 0; + tcbpwm->div = 0; + + spin_lock(&tcbpwmc->lock); + cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR)); + /* + * Get init config from Timer Counter registers if + * Timer Counter is already configured as a PWM generator. + */ + if (cmr & ATMEL_TC_WAVE) { + if (index == 0) + tcbpwm->duty = + __raw_readl(regs + ATMEL_TC_REG(group, RA)); + else + tcbpwm->duty = + __raw_readl(regs + ATMEL_TC_REG(group, RB)); + + tcbpwm->div = cmr & ATMEL_TC_TCCLKS; + tcbpwm->period = __raw_readl(regs + ATMEL_TC_REG(group, RC)); + cmr &= (ATMEL_TC_TCCLKS | ATMEL_TC_ACMR_MASK | + ATMEL_TC_BCMR_MASK); + } else + cmr = 0; + + cmr |= ATMEL_TC_WAVE | ATMEL_TC_WAVESEL_UP_AUTO | ATMEL_TC_EEVT_XC0; + __raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR)); + spin_unlock(&tcbpwmc->lock); + + tcbpwmc->pwms[pwm->hwpwm] = tcbpwm; + + return 0; +} + +static void atmel_tcb_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); + struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); + struct atmel_tc *tc = tcbpwmc->tc; + + clk_disable_unprepare(tc->clk[pwm->hwpwm / 2]); + tcbpwmc->pwms[pwm->hwpwm] = NULL; + devm_kfree(chip->dev, tcbpwm); +} + +static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); + struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); + struct atmel_tc *tc = tcbpwmc->tc; + void __iomem *regs = tc->regs; + unsigned group = pwm->hwpwm / 2; + unsigned index = pwm->hwpwm % 2; + unsigned cmr; + enum pwm_polarity polarity = tcbpwm->polarity; + + /* + * If duty is 0 the timer will be stopped and we have to + * configure the output correctly on software trigger: + * - set output to high if PWM_POLARITY_INVERSED + * - set output to low if PWM_POLARITY_NORMAL + * + * This is why we're reverting polarity in this case. + */ + if (tcbpwm->duty == 0) + polarity = !polarity; + + spin_lock(&tcbpwmc->lock); + cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR)); + + /* flush old setting and set the new one */ + if (index == 0) { + cmr &= ~ATMEL_TC_ACMR_MASK; + if (polarity == PWM_POLARITY_INVERSED) + cmr |= ATMEL_TC_ASWTRG_CLEAR; + else + cmr |= ATMEL_TC_ASWTRG_SET; + } else { + cmr &= ~ATMEL_TC_BCMR_MASK; + if (polarity == PWM_POLARITY_INVERSED) + cmr |= ATMEL_TC_BSWTRG_CLEAR; + else + cmr |= ATMEL_TC_BSWTRG_SET; + } + + __raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR)); + + /* + * Use software trigger to apply the new setting. + * If both PWM devices in this group are disabled we stop the clock. + */ + if (!(cmr & (ATMEL_TC_ACPC | ATMEL_TC_BCPC))) { + __raw_writel(ATMEL_TC_SWTRG | ATMEL_TC_CLKDIS, + regs + ATMEL_TC_REG(group, CCR)); + tcbpwmc->bkup[group].enabled = 1; + } else { + __raw_writel(ATMEL_TC_SWTRG, regs + + ATMEL_TC_REG(group, CCR)); + tcbpwmc->bkup[group].enabled = 0; + } + + spin_unlock(&tcbpwmc->lock); +} + +static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); + struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); + struct atmel_tc *tc = tcbpwmc->tc; + void __iomem *regs = tc->regs; + unsigned group = pwm->hwpwm / 2; + unsigned index = pwm->hwpwm % 2; + u32 cmr; + enum pwm_polarity polarity = tcbpwm->polarity; + + /* + * If duty is 0 the timer will be stopped and we have to + * configure the output correctly on software trigger: + * - set output to high if PWM_POLARITY_INVERSED + * - set output to low if PWM_POLARITY_NORMAL + * + * This is why we're reverting polarity in this case. + */ + if (tcbpwm->duty == 0) + polarity = !polarity; + + spin_lock(&tcbpwmc->lock); + cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR)); + + /* flush old setting and set the new one */ + cmr &= ~ATMEL_TC_TCCLKS; + + if (index == 0) { + cmr &= ~ATMEL_TC_ACMR_MASK; + + /* Set CMR flags according to given polarity */ + if (polarity == PWM_POLARITY_INVERSED) + cmr |= ATMEL_TC_ASWTRG_CLEAR; + else + cmr |= ATMEL_TC_ASWTRG_SET; + } else { + cmr &= ~ATMEL_TC_BCMR_MASK; + if (polarity == PWM_POLARITY_INVERSED) + cmr |= ATMEL_TC_BSWTRG_CLEAR; + else + cmr |= ATMEL_TC_BSWTRG_SET; + } + + /* + * If duty is 0 or equal to period there's no need to register + * a specific action on RA/RB and RC compare. + * The output will be configured on software trigger and keep + * this config till next config call. + */ + if (tcbpwm->duty != tcbpwm->period && tcbpwm->duty > 0) { + if (index == 0) { + if (polarity == PWM_POLARITY_INVERSED) + cmr |= ATMEL_TC_ACPA_SET | ATMEL_TC_ACPC_CLEAR; + else + cmr |= ATMEL_TC_ACPA_CLEAR | ATMEL_TC_ACPC_SET; + } else { + if (polarity == PWM_POLARITY_INVERSED) + cmr |= ATMEL_TC_BCPB_SET | ATMEL_TC_BCPC_CLEAR; + else + cmr |= ATMEL_TC_BCPB_CLEAR | ATMEL_TC_BCPC_SET; + } + } + + cmr |= (tcbpwm->div & ATMEL_TC_TCCLKS); + + __raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR)); + + if (index == 0) + __raw_writel(tcbpwm->duty, regs + ATMEL_TC_REG(group, RA)); + else + __raw_writel(tcbpwm->duty, regs + ATMEL_TC_REG(group, RB)); + + __raw_writel(tcbpwm->period, regs + ATMEL_TC_REG(group, RC)); + + /* Use software trigger to apply the new setting */ + __raw_writel(ATMEL_TC_CLKEN | ATMEL_TC_SWTRG, + regs + ATMEL_TC_REG(group, CCR)); + tcbpwmc->bkup[group].enabled = 1; + spin_unlock(&tcbpwmc->lock); + return 0; +} + +static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); + struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); + unsigned group = pwm->hwpwm / 2; + unsigned index = pwm->hwpwm % 2; + struct atmel_tcb_pwm_device *atcbpwm = NULL; + struct atmel_tc *tc = tcbpwmc->tc; + int i; + int slowclk = 0; + unsigned period; + unsigned duty; + unsigned rate = clk_get_rate(tc->clk[group]); + unsigned long long min; + unsigned long long max; + + /* + * Find best clk divisor: + * the smallest divisor which can fulfill the period_ns requirements. + */ + for (i = 0; i < 5; ++i) { + if (atmel_tc_divisors[i] == 0) { + slowclk = i; + continue; + } + min = div_u64((u64)NSEC_PER_SEC * atmel_tc_divisors[i], rate); + max = min << tc->tcb_config->counter_width; + if (max >= period_ns) + break; + } + + /* + * If none of the divisor are small enough to represent period_ns + * take slow clock (32KHz). + */ + if (i == 5) { + i = slowclk; + rate = clk_get_rate(tc->slow_clk); + min = div_u64(NSEC_PER_SEC, rate); + max = min << tc->tcb_config->counter_width; + + /* If period is too big return ERANGE error */ + if (max < period_ns) + return -ERANGE; + } + + duty = div_u64(duty_ns, min); + period = div_u64(period_ns, min); + + if (index == 0) + atcbpwm = tcbpwmc->pwms[pwm->hwpwm + 1]; + else + atcbpwm = tcbpwmc->pwms[pwm->hwpwm - 1]; + + /* + * PWM devices provided by TCB driver are grouped by 2: + * - group 0: PWM 0 & 1 + * - group 1: PWM 2 & 3 + * - group 2: PWM 4 & 5 + * + * PWM devices in a given group must be configured with the + * same period_ns. + * + * 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) && + (atcbpwm->div != i || atcbpwm->period != period)) { + dev_err(chip->dev, + "failed to configure period_ns: PWM group already configured with a different value\n"); + return -EINVAL; + } + + tcbpwm->period = period; + tcbpwm->div = i; + tcbpwm->duty = duty; + + /* If the PWM is enabled, call enable to apply the new conf */ + if (pwm_is_enabled(pwm)) + atmel_tcb_pwm_enable(chip, pwm); + + return 0; +} + +static const struct pwm_ops atmel_tcb_pwm_ops = { + .request = atmel_tcb_pwm_request, + .free = atmel_tcb_pwm_free, + .config = atmel_tcb_pwm_config, + .set_polarity = atmel_tcb_pwm_set_polarity, + .enable = atmel_tcb_pwm_enable, + .disable = atmel_tcb_pwm_disable, + .owner = THIS_MODULE, +}; + +static int atmel_tcb_pwm_probe(struct platform_device *pdev) +{ + struct atmel_tcb_pwm_chip *tcbpwm; + struct device_node *np = pdev->dev.of_node; + struct atmel_tc *tc; + int err; + int tcblock; + + err = of_property_read_u32(np, "tc-block", &tcblock); + if (err < 0) { + dev_err(&pdev->dev, + "failed to get Timer Counter Block number from device tree (error: %d)\n", + err); + return err; + } + + tc = atmel_tc_alloc(tcblock); + if (tc == NULL) { + dev_err(&pdev->dev, "failed to allocate Timer Counter Block\n"); + return -ENOMEM; + } + + tcbpwm = devm_kzalloc(&pdev->dev, sizeof(*tcbpwm), GFP_KERNEL); + if (tcbpwm == NULL) { + err = -ENOMEM; + goto err_free_tc; + } + + tcbpwm->chip.dev = &pdev->dev; + tcbpwm->chip.ops = &atmel_tcb_pwm_ops; + tcbpwm->chip.of_xlate = of_pwm_xlate_with_flags; + tcbpwm->chip.of_pwm_n_cells = 3; + tcbpwm->chip.base = -1; + tcbpwm->chip.npwm = NPWM; + tcbpwm->tc = tc; + + err = clk_prepare_enable(tc->slow_clk); + if (err) + goto err_free_tc; + + spin_lock_init(&tcbpwm->lock); + + err = pwmchip_add(&tcbpwm->chip); + if (err < 0) + goto err_disable_clk; + + platform_set_drvdata(pdev, tcbpwm); + + return 0; + +err_disable_clk: + clk_disable_unprepare(tcbpwm->tc->slow_clk); + +err_free_tc: + atmel_tc_free(tc); + + return err; +} + +static int atmel_tcb_pwm_remove(struct platform_device *pdev) +{ + struct atmel_tcb_pwm_chip *tcbpwm = platform_get_drvdata(pdev); + int err; + + clk_disable_unprepare(tcbpwm->tc->slow_clk); + + err = pwmchip_remove(&tcbpwm->chip); + if (err < 0) + return err; + + atmel_tc_free(tcbpwm->tc); + + return 0; +} + +static const struct of_device_id atmel_tcb_pwm_dt_ids[] = { + { .compatible = "atmel,tcb-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, atmel_tcb_pwm_dt_ids); + +#ifdef CONFIG_PM_SLEEP +static int atmel_tcb_pwm_suspend(struct device *dev) +{ + struct atmel_tcb_pwm_chip *tcbpwm = dev_get_drvdata(dev); + void __iomem *base = tcbpwm->tc->regs; + int i; + + for (i = 0; i < (NPWM / 2); i++) { + struct atmel_tcb_channel *chan = &tcbpwm->bkup[i]; + + chan->cmr = readl(base + ATMEL_TC_REG(i, CMR)); + chan->ra = readl(base + ATMEL_TC_REG(i, RA)); + chan->rb = readl(base + ATMEL_TC_REG(i, RB)); + chan->rc = readl(base + ATMEL_TC_REG(i, RC)); + } + return 0; +} + +static int atmel_tcb_pwm_resume(struct device *dev) +{ + struct atmel_tcb_pwm_chip *tcbpwm = dev_get_drvdata(dev); + void __iomem *base = tcbpwm->tc->regs; + int i; + + for (i = 0; i < (NPWM / 2); i++) { + struct atmel_tcb_channel *chan = &tcbpwm->bkup[i]; + + writel(chan->cmr, base + ATMEL_TC_REG(i, CMR)); + writel(chan->ra, base + ATMEL_TC_REG(i, RA)); + writel(chan->rb, base + ATMEL_TC_REG(i, RB)); + writel(chan->rc, base + ATMEL_TC_REG(i, RC)); + if (chan->enabled) { + writel(ATMEL_TC_CLKEN | ATMEL_TC_SWTRG, + base + ATMEL_TC_REG(i, CCR)); + } + } + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(atmel_tcb_pwm_pm_ops, atmel_tcb_pwm_suspend, + atmel_tcb_pwm_resume); + +static struct platform_driver atmel_tcb_pwm_driver = { + .driver = { + .name = "atmel-tcb-pwm", + .of_match_table = atmel_tcb_pwm_dt_ids, + .pm = &atmel_tcb_pwm_pm_ops, + }, + .probe = atmel_tcb_pwm_probe, + .remove = atmel_tcb_pwm_remove, +}; +module_platform_driver(atmel_tcb_pwm_driver); + +MODULE_AUTHOR("Boris BREZILLON <b.brezillon@overkiz.com>"); +MODULE_DESCRIPTION("Atmel Timer Counter Pulse Width Modulation Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c new file mode 100644 index 000000000..d7cb0dfa2 --- /dev/null +++ b/drivers/pwm/pwm-atmel.c @@ -0,0 +1,475 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Atmel Pulse Width Modulation Controller + * + * Copyright (C) 2013 Atmel Corporation + * Bo Shen <voice.shen@atmel.com> + * + * Links to reference manuals for the supported PWM chips can be found in + * Documentation/arm/microchip.rst. + * + * Limitations: + * - Periods start with the inactive level. + * - Hardware has to be stopped in general to update settings. + * + * Software bugs/possible improvements: + * - When atmel_pwm_apply() is called with state->enabled=false a change in + * state->polarity isn't honored. + * - Instead of sleeping to wait for a completed period, the interrupt + * functionality could be used. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +/* The following is global registers for PWM controller */ +#define PWM_ENA 0x04 +#define PWM_DIS 0x08 +#define PWM_SR 0x0C +#define PWM_ISR 0x1C +/* Bit field in SR */ +#define PWM_SR_ALL_CH_ON 0x0F + +/* The following register is PWM channel related registers */ +#define PWM_CH_REG_OFFSET 0x200 +#define PWM_CH_REG_SIZE 0x20 + +#define PWM_CMR 0x0 +/* Bit field in CMR */ +#define PWM_CMR_CPOL (1 << 9) +#define PWM_CMR_UPD_CDTY (1 << 10) +#define PWM_CMR_CPRE_MSK 0xF + +/* The following registers for PWM v1 */ +#define PWMV1_CDTY 0x04 +#define PWMV1_CPRD 0x08 +#define PWMV1_CUPD 0x10 + +/* The following registers for PWM v2 */ +#define PWMV2_CDTY 0x04 +#define PWMV2_CDTYUPD 0x08 +#define PWMV2_CPRD 0x0C +#define PWMV2_CPRDUPD 0x10 + +#define PWM_MAX_PRES 10 + +struct atmel_pwm_registers { + u8 period; + u8 period_upd; + u8 duty; + u8 duty_upd; +}; + +struct atmel_pwm_config { + u32 period_bits; +}; + +struct atmel_pwm_data { + struct atmel_pwm_registers regs; + struct atmel_pwm_config cfg; +}; + +struct atmel_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + void __iomem *base; + const struct atmel_pwm_data *data; + + unsigned int updated_pwms; + /* ISR is cleared when read, ensure only one thread does that */ + struct mutex isr_lock; +}; + +static inline struct atmel_pwm_chip *to_atmel_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct atmel_pwm_chip, chip); +} + +static inline u32 atmel_pwm_readl(struct atmel_pwm_chip *chip, + unsigned long offset) +{ + return readl_relaxed(chip->base + offset); +} + +static inline void atmel_pwm_writel(struct atmel_pwm_chip *chip, + unsigned long offset, unsigned long val) +{ + writel_relaxed(val, chip->base + offset); +} + +static inline u32 atmel_pwm_ch_readl(struct atmel_pwm_chip *chip, + unsigned int ch, unsigned long offset) +{ + unsigned long base = PWM_CH_REG_OFFSET + ch * PWM_CH_REG_SIZE; + + return atmel_pwm_readl(chip, base + offset); +} + +static inline void atmel_pwm_ch_writel(struct atmel_pwm_chip *chip, + unsigned int ch, unsigned long offset, + unsigned long val) +{ + unsigned long base = PWM_CH_REG_OFFSET + ch * PWM_CH_REG_SIZE; + + atmel_pwm_writel(chip, base + offset, val); +} + +static int atmel_pwm_calculate_cprd_and_pres(struct pwm_chip *chip, + const struct pwm_state *state, + unsigned long *cprd, u32 *pres) +{ + struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); + unsigned long long cycles = state->period; + int shift; + + /* Calculate the period cycles and prescale value */ + cycles *= clk_get_rate(atmel_pwm->clk); + do_div(cycles, NSEC_PER_SEC); + + /* + * The register for the period length is cfg.period_bits bits wide. + * So for each bit the number of clock cycles is wider divide the input + * clock frequency by two using pres and shift cprd accordingly. + */ + shift = fls(cycles) - atmel_pwm->data->cfg.period_bits; + + if (shift > PWM_MAX_PRES) { + dev_err(chip->dev, "pres exceeds the maximum value\n"); + return -EINVAL; + } else if (shift > 0) { + *pres = shift; + cycles >>= *pres; + } else { + *pres = 0; + } + + *cprd = cycles; + + return 0; +} + +static void atmel_pwm_calculate_cdty(const struct pwm_state *state, + unsigned long cprd, unsigned long *cdty) +{ + unsigned long long cycles = state->duty_cycle; + + cycles *= cprd; + do_div(cycles, state->period); + *cdty = cprd - cycles; +} + +static void atmel_pwm_update_cdty(struct pwm_chip *chip, struct pwm_device *pwm, + unsigned long cdty) +{ + struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); + u32 val; + + if (atmel_pwm->data->regs.duty_upd == + atmel_pwm->data->regs.period_upd) { + val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR); + val &= ~PWM_CMR_UPD_CDTY; + atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val); + } + + atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, + atmel_pwm->data->regs.duty_upd, cdty); +} + +static void atmel_pwm_set_cprd_cdty(struct pwm_chip *chip, + struct pwm_device *pwm, + unsigned long cprd, unsigned long cdty) +{ + struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); + + atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, + atmel_pwm->data->regs.duty, cdty); + atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, + atmel_pwm->data->regs.period, cprd); +} + +static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm, + bool disable_clk) +{ + struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); + unsigned long timeout = jiffies + 2 * HZ; + + /* + * Wait for at least a complete period to have passed before disabling a + * channel to be sure that CDTY has been updated + */ + mutex_lock(&atmel_pwm->isr_lock); + atmel_pwm->updated_pwms |= atmel_pwm_readl(atmel_pwm, PWM_ISR); + + while (!(atmel_pwm->updated_pwms & (1 << pwm->hwpwm)) && + time_before(jiffies, timeout)) { + usleep_range(10, 100); + atmel_pwm->updated_pwms |= atmel_pwm_readl(atmel_pwm, PWM_ISR); + } + + mutex_unlock(&atmel_pwm->isr_lock); + atmel_pwm_writel(atmel_pwm, PWM_DIS, 1 << pwm->hwpwm); + + /* + * Wait for the PWM channel disable operation to be effective before + * stopping the clock. + */ + timeout = jiffies + 2 * HZ; + + while ((atmel_pwm_readl(atmel_pwm, PWM_SR) & (1 << pwm->hwpwm)) && + time_before(jiffies, timeout)) + usleep_range(10, 100); + + if (disable_clk) + clk_disable(atmel_pwm->clk); +} + +static int atmel_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); + struct pwm_state cstate; + unsigned long cprd, cdty; + u32 pres, val; + int ret; + + pwm_get_state(pwm, &cstate); + + if (state->enabled) { + if (cstate.enabled && + cstate.polarity == state->polarity && + cstate.period == state->period) { + cprd = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, + atmel_pwm->data->regs.period); + atmel_pwm_calculate_cdty(state, cprd, &cdty); + atmel_pwm_update_cdty(chip, pwm, cdty); + return 0; + } + + ret = atmel_pwm_calculate_cprd_and_pres(chip, state, &cprd, + &pres); + if (ret) { + dev_err(chip->dev, + "failed to calculate cprd and prescaler\n"); + return ret; + } + + atmel_pwm_calculate_cdty(state, cprd, &cdty); + + if (cstate.enabled) { + atmel_pwm_disable(chip, pwm, false); + } else { + ret = clk_enable(atmel_pwm->clk); + if (ret) { + dev_err(chip->dev, "failed to enable clock\n"); + return ret; + } + } + + /* It is necessary to preserve CPOL, inside CMR */ + val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR); + val = (val & ~PWM_CMR_CPRE_MSK) | (pres & PWM_CMR_CPRE_MSK); + if (state->polarity == PWM_POLARITY_NORMAL) + val &= ~PWM_CMR_CPOL; + else + val |= PWM_CMR_CPOL; + atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val); + atmel_pwm_set_cprd_cdty(chip, pwm, cprd, cdty); + mutex_lock(&atmel_pwm->isr_lock); + atmel_pwm->updated_pwms |= atmel_pwm_readl(atmel_pwm, PWM_ISR); + atmel_pwm->updated_pwms &= ~(1 << pwm->hwpwm); + mutex_unlock(&atmel_pwm->isr_lock); + atmel_pwm_writel(atmel_pwm, PWM_ENA, 1 << pwm->hwpwm); + } else if (cstate.enabled) { + atmel_pwm_disable(chip, pwm, true); + } + + return 0; +} + +static void atmel_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); + u32 sr, cmr; + + sr = atmel_pwm_readl(atmel_pwm, PWM_SR); + cmr = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR); + + if (sr & (1 << pwm->hwpwm)) { + unsigned long rate = clk_get_rate(atmel_pwm->clk); + u32 cdty, cprd, pres; + u64 tmp; + + pres = cmr & PWM_CMR_CPRE_MSK; + + cprd = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, + atmel_pwm->data->regs.period); + tmp = (u64)cprd * NSEC_PER_SEC; + tmp <<= pres; + state->period = DIV64_U64_ROUND_UP(tmp, rate); + + cdty = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, + atmel_pwm->data->regs.duty); + tmp = (u64)(cprd - cdty) * NSEC_PER_SEC; + tmp <<= pres; + state->duty_cycle = DIV64_U64_ROUND_UP(tmp, rate); + + state->enabled = true; + } else { + state->enabled = false; + } + + if (cmr & PWM_CMR_CPOL) + state->polarity = PWM_POLARITY_INVERSED; + else + state->polarity = PWM_POLARITY_NORMAL; +} + +static const struct pwm_ops atmel_pwm_ops = { + .apply = atmel_pwm_apply, + .get_state = atmel_pwm_get_state, + .owner = THIS_MODULE, +}; + +static const struct atmel_pwm_data atmel_sam9rl_pwm_data = { + .regs = { + .period = PWMV1_CPRD, + .period_upd = PWMV1_CUPD, + .duty = PWMV1_CDTY, + .duty_upd = PWMV1_CUPD, + }, + .cfg = { + /* 16 bits to keep period and duty. */ + .period_bits = 16, + }, +}; + +static const struct atmel_pwm_data atmel_sama5_pwm_data = { + .regs = { + .period = PWMV2_CPRD, + .period_upd = PWMV2_CPRDUPD, + .duty = PWMV2_CDTY, + .duty_upd = PWMV2_CDTYUPD, + }, + .cfg = { + /* 16 bits to keep period and duty. */ + .period_bits = 16, + }, +}; + +static const struct atmel_pwm_data mchp_sam9x60_pwm_data = { + .regs = { + .period = PWMV1_CPRD, + .period_upd = PWMV1_CUPD, + .duty = PWMV1_CDTY, + .duty_upd = PWMV1_CUPD, + }, + .cfg = { + /* 32 bits to keep period and duty. */ + .period_bits = 32, + }, +}; + +static const struct of_device_id atmel_pwm_dt_ids[] = { + { + .compatible = "atmel,at91sam9rl-pwm", + .data = &atmel_sam9rl_pwm_data, + }, { + .compatible = "atmel,sama5d3-pwm", + .data = &atmel_sama5_pwm_data, + }, { + .compatible = "atmel,sama5d2-pwm", + .data = &atmel_sama5_pwm_data, + }, { + .compatible = "microchip,sam9x60-pwm", + .data = &mchp_sam9x60_pwm_data, + }, { + /* sentinel */ + }, +}; +MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids); + +static int atmel_pwm_probe(struct platform_device *pdev) +{ + struct atmel_pwm_chip *atmel_pwm; + struct resource *res; + int ret; + + atmel_pwm = devm_kzalloc(&pdev->dev, sizeof(*atmel_pwm), GFP_KERNEL); + if (!atmel_pwm) + return -ENOMEM; + + mutex_init(&atmel_pwm->isr_lock); + atmel_pwm->data = of_device_get_match_data(&pdev->dev); + atmel_pwm->updated_pwms = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + atmel_pwm->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(atmel_pwm->base)) + return PTR_ERR(atmel_pwm->base); + + atmel_pwm->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(atmel_pwm->clk)) + return PTR_ERR(atmel_pwm->clk); + + ret = clk_prepare(atmel_pwm->clk); + if (ret) { + dev_err(&pdev->dev, "failed to prepare PWM clock\n"); + return ret; + } + + atmel_pwm->chip.dev = &pdev->dev; + atmel_pwm->chip.ops = &atmel_pwm_ops; + atmel_pwm->chip.of_xlate = of_pwm_xlate_with_flags; + atmel_pwm->chip.of_pwm_n_cells = 3; + atmel_pwm->chip.base = -1; + atmel_pwm->chip.npwm = 4; + + ret = pwmchip_add(&atmel_pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add PWM chip %d\n", ret); + goto unprepare_clk; + } + + platform_set_drvdata(pdev, atmel_pwm); + + return ret; + +unprepare_clk: + clk_unprepare(atmel_pwm->clk); + return ret; +} + +static int atmel_pwm_remove(struct platform_device *pdev) +{ + struct atmel_pwm_chip *atmel_pwm = platform_get_drvdata(pdev); + + clk_unprepare(atmel_pwm->clk); + mutex_destroy(&atmel_pwm->isr_lock); + + return pwmchip_remove(&atmel_pwm->chip); +} + +static struct platform_driver atmel_pwm_driver = { + .driver = { + .name = "atmel-pwm", + .of_match_table = of_match_ptr(atmel_pwm_dt_ids), + }, + .probe = atmel_pwm_probe, + .remove = atmel_pwm_remove, +}; +module_platform_driver(atmel_pwm_driver); + +MODULE_ALIAS("platform:atmel-pwm"); +MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>"); +MODULE_DESCRIPTION("Atmel PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-bcm-iproc.c b/drivers/pwm/pwm-bcm-iproc.c new file mode 100644 index 000000000..79b1e58e9 --- /dev/null +++ b/drivers/pwm/pwm-bcm-iproc.c @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2016 Broadcom + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +#define IPROC_PWM_CTRL_OFFSET 0x00 +#define IPROC_PWM_CTRL_TYPE_SHIFT(ch) (15 + (ch)) +#define IPROC_PWM_CTRL_POLARITY_SHIFT(ch) (8 + (ch)) +#define IPROC_PWM_CTRL_EN_SHIFT(ch) (ch) + +#define IPROC_PWM_PERIOD_OFFSET(ch) (0x04 + ((ch) << 3)) +#define IPROC_PWM_PERIOD_MIN 0x02 +#define IPROC_PWM_PERIOD_MAX 0xffff + +#define IPROC_PWM_DUTY_CYCLE_OFFSET(ch) (0x08 + ((ch) << 3)) +#define IPROC_PWM_DUTY_CYCLE_MIN 0x00 +#define IPROC_PWM_DUTY_CYCLE_MAX 0xffff + +#define IPROC_PWM_PRESCALE_OFFSET 0x24 +#define IPROC_PWM_PRESCALE_BITS 0x06 +#define IPROC_PWM_PRESCALE_SHIFT(ch) ((3 - (ch)) * \ + IPROC_PWM_PRESCALE_BITS) +#define IPROC_PWM_PRESCALE_MASK(ch) (IPROC_PWM_PRESCALE_MAX << \ + IPROC_PWM_PRESCALE_SHIFT(ch)) +#define IPROC_PWM_PRESCALE_MIN 0x00 +#define IPROC_PWM_PRESCALE_MAX 0x3f + +struct iproc_pwmc { + struct pwm_chip chip; + void __iomem *base; + struct clk *clk; +}; + +static inline struct iproc_pwmc *to_iproc_pwmc(struct pwm_chip *chip) +{ + return container_of(chip, struct iproc_pwmc, chip); +} + +static void iproc_pwmc_enable(struct iproc_pwmc *ip, unsigned int channel) +{ + u32 value; + + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + value |= 1 << IPROC_PWM_CTRL_EN_SHIFT(channel); + writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); + + /* must be a 400 ns delay between clearing and setting enable bit */ + ndelay(400); +} + +static void iproc_pwmc_disable(struct iproc_pwmc *ip, unsigned int channel) +{ + u32 value; + + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + value &= ~(1 << IPROC_PWM_CTRL_EN_SHIFT(channel)); + writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); + + /* must be a 400 ns delay between clearing and setting enable bit */ + ndelay(400); +} + +static void iproc_pwmc_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct iproc_pwmc *ip = to_iproc_pwmc(chip); + u64 tmp, multi, rate; + u32 value, prescale; + + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + + if (value & BIT(IPROC_PWM_CTRL_EN_SHIFT(pwm->hwpwm))) + state->enabled = true; + else + state->enabled = false; + + if (value & BIT(IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm))) + state->polarity = PWM_POLARITY_NORMAL; + else + state->polarity = PWM_POLARITY_INVERSED; + + rate = clk_get_rate(ip->clk); + if (rate == 0) { + state->period = 0; + state->duty_cycle = 0; + return; + } + + value = readl(ip->base + IPROC_PWM_PRESCALE_OFFSET); + prescale = value >> IPROC_PWM_PRESCALE_SHIFT(pwm->hwpwm); + prescale &= IPROC_PWM_PRESCALE_MAX; + + multi = NSEC_PER_SEC * (prescale + 1); + + value = readl(ip->base + IPROC_PWM_PERIOD_OFFSET(pwm->hwpwm)); + tmp = (value & IPROC_PWM_PERIOD_MAX) * multi; + state->period = div64_u64(tmp, rate); + + value = readl(ip->base + IPROC_PWM_DUTY_CYCLE_OFFSET(pwm->hwpwm)); + tmp = (value & IPROC_PWM_PERIOD_MAX) * multi; + state->duty_cycle = div64_u64(tmp, rate); +} + +static int iproc_pwmc_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + unsigned long prescale = IPROC_PWM_PRESCALE_MIN; + struct iproc_pwmc *ip = to_iproc_pwmc(chip); + u32 value, period, duty; + u64 rate; + + rate = clk_get_rate(ip->clk); + + /* + * Find period count, duty count and prescale to suit duty_cycle and + * period. This is done according to formulas described below: + * + * period_ns = 10^9 * (PRESCALE + 1) * PC / PWM_CLK_RATE + * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE + * + * PC = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1)) + * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1)) + */ + while (1) { + u64 value, div; + + div = NSEC_PER_SEC * (prescale + 1); + value = rate * state->period; + period = div64_u64(value, div); + value = rate * state->duty_cycle; + duty = div64_u64(value, div); + + if (period < IPROC_PWM_PERIOD_MIN) + return -EINVAL; + + if (period <= IPROC_PWM_PERIOD_MAX && + duty <= IPROC_PWM_DUTY_CYCLE_MAX) + break; + + /* Otherwise, increase prescale and recalculate counts */ + if (++prescale > IPROC_PWM_PRESCALE_MAX) + return -EINVAL; + } + + iproc_pwmc_disable(ip, pwm->hwpwm); + + /* Set prescale */ + value = readl(ip->base + IPROC_PWM_PRESCALE_OFFSET); + value &= ~IPROC_PWM_PRESCALE_MASK(pwm->hwpwm); + value |= prescale << IPROC_PWM_PRESCALE_SHIFT(pwm->hwpwm); + writel(value, ip->base + IPROC_PWM_PRESCALE_OFFSET); + + /* set period and duty cycle */ + writel(period, ip->base + IPROC_PWM_PERIOD_OFFSET(pwm->hwpwm)); + writel(duty, ip->base + IPROC_PWM_DUTY_CYCLE_OFFSET(pwm->hwpwm)); + + /* set polarity */ + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + + if (state->polarity == PWM_POLARITY_NORMAL) + value |= 1 << IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm); + else + value &= ~(1 << IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm)); + + writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); + + if (state->enabled) + iproc_pwmc_enable(ip, pwm->hwpwm); + + return 0; +} + +static const struct pwm_ops iproc_pwm_ops = { + .apply = iproc_pwmc_apply, + .get_state = iproc_pwmc_get_state, + .owner = THIS_MODULE, +}; + +static int iproc_pwmc_probe(struct platform_device *pdev) +{ + struct iproc_pwmc *ip; + struct resource *res; + unsigned int i; + u32 value; + int ret; + + ip = devm_kzalloc(&pdev->dev, sizeof(*ip), GFP_KERNEL); + if (!ip) + return -ENOMEM; + + platform_set_drvdata(pdev, ip); + + ip->chip.dev = &pdev->dev; + ip->chip.ops = &iproc_pwm_ops; + ip->chip.base = -1; + ip->chip.npwm = 4; + ip->chip.of_xlate = of_pwm_xlate_with_flags; + ip->chip.of_pwm_n_cells = 3; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ip->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ip->base)) + return PTR_ERR(ip->base); + + ip->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(ip->clk)) { + dev_err(&pdev->dev, "failed to get clock: %ld\n", + PTR_ERR(ip->clk)); + return PTR_ERR(ip->clk); + } + + ret = clk_prepare_enable(ip->clk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable clock: %d\n", ret); + return ret; + } + + /* Set full drive and normal polarity for all channels */ + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + + for (i = 0; i < ip->chip.npwm; i++) { + value &= ~(1 << IPROC_PWM_CTRL_TYPE_SHIFT(i)); + value |= 1 << IPROC_PWM_CTRL_POLARITY_SHIFT(i); + } + + writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); + + ret = pwmchip_add(&ip->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); + clk_disable_unprepare(ip->clk); + } + + return ret; +} + +static int iproc_pwmc_remove(struct platform_device *pdev) +{ + struct iproc_pwmc *ip = platform_get_drvdata(pdev); + + clk_disable_unprepare(ip->clk); + + return pwmchip_remove(&ip->chip); +} + +static const struct of_device_id bcm_iproc_pwmc_dt[] = { + { .compatible = "brcm,iproc-pwm" }, + { }, +}; +MODULE_DEVICE_TABLE(of, bcm_iproc_pwmc_dt); + +static struct platform_driver iproc_pwmc_driver = { + .driver = { + .name = "bcm-iproc-pwm", + .of_match_table = bcm_iproc_pwmc_dt, + }, + .probe = iproc_pwmc_probe, + .remove = iproc_pwmc_remove, +}; +module_platform_driver(iproc_pwmc_driver); + +MODULE_AUTHOR("Yendapally Reddy Dhananjaya Reddy <yendapally.reddy@broadcom.com>"); +MODULE_DESCRIPTION("Broadcom iProc PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-bcm-kona.c b/drivers/pwm/pwm-bcm-kona.c new file mode 100644 index 000000000..16c5898b9 --- /dev/null +++ b/drivers/pwm/pwm-bcm-kona.c @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2014 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/types.h> + +/* + * The Kona PWM has some unusual characteristics. Here are the main points. + * + * 1) There is no disable bit and the hardware docs advise programming a zero + * duty to achieve output equivalent to that of a normal disable operation. + * + * 2) Changes to prescale, duty, period, and polarity do not take effect until + * a subsequent rising edge of the trigger bit. + * + * 3) If the smooth bit and trigger bit are both low, the output is a constant + * high signal. Otherwise, the earlier waveform continues to be output. + * + * 4) If the smooth bit is set on the rising edge of the trigger bit, output + * will transition to the new settings on a period boundary (which could be + * seconds away). If the smooth bit is clear, new settings will be applied + * as soon as possible (the hardware always has a 400ns delay). + * + * 5) When the external clock that feeds the PWM is disabled, output is pegged + * high or low depending on its state at that exact instant. + */ + +#define PWM_CONTROL_OFFSET 0x00000000 +#define PWM_CONTROL_SMOOTH_SHIFT(chan) (24 + (chan)) +#define PWM_CONTROL_TYPE_SHIFT(chan) (16 + (chan)) +#define PWM_CONTROL_POLARITY_SHIFT(chan) (8 + (chan)) +#define PWM_CONTROL_TRIGGER_SHIFT(chan) (chan) + +#define PRESCALE_OFFSET 0x00000004 +#define PRESCALE_SHIFT(chan) ((chan) << 2) +#define PRESCALE_MASK(chan) (0x7 << PRESCALE_SHIFT(chan)) +#define PRESCALE_MIN 0x00000000 +#define PRESCALE_MAX 0x00000007 + +#define PERIOD_COUNT_OFFSET(chan) (0x00000008 + ((chan) << 3)) +#define PERIOD_COUNT_MIN 0x00000002 +#define PERIOD_COUNT_MAX 0x00ffffff + +#define DUTY_CYCLE_HIGH_OFFSET(chan) (0x0000000c + ((chan) << 3)) +#define DUTY_CYCLE_HIGH_MIN 0x00000000 +#define DUTY_CYCLE_HIGH_MAX 0x00ffffff + +struct kona_pwmc { + struct pwm_chip chip; + void __iomem *base; + struct clk *clk; +}; + +static inline struct kona_pwmc *to_kona_pwmc(struct pwm_chip *_chip) +{ + return container_of(_chip, struct kona_pwmc, chip); +} + +/* + * Clear trigger bit but set smooth bit to maintain old output. + */ +static void kona_pwmc_prepare_for_settings(struct kona_pwmc *kp, + unsigned int chan) +{ + unsigned int value = readl(kp->base + PWM_CONTROL_OFFSET); + + value |= 1 << PWM_CONTROL_SMOOTH_SHIFT(chan); + value &= ~(1 << PWM_CONTROL_TRIGGER_SHIFT(chan)); + writel(value, kp->base + PWM_CONTROL_OFFSET); + + /* + * There must be a min 400ns delay between clearing trigger and setting + * it. Failing to do this may result in no PWM signal. + */ + ndelay(400); +} + +static void kona_pwmc_apply_settings(struct kona_pwmc *kp, unsigned int chan) +{ + unsigned int value = readl(kp->base + PWM_CONTROL_OFFSET); + + /* Set trigger bit and clear smooth bit to apply new settings */ + value &= ~(1 << PWM_CONTROL_SMOOTH_SHIFT(chan)); + value |= 1 << PWM_CONTROL_TRIGGER_SHIFT(chan); + writel(value, kp->base + PWM_CONTROL_OFFSET); + + /* Trigger bit must be held high for at least 400 ns. */ + ndelay(400); +} + +static int kona_pwmc_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct kona_pwmc *kp = to_kona_pwmc(chip); + u64 val, div, rate; + unsigned long prescale = PRESCALE_MIN, pc, dc; + unsigned int value, chan = pwm->hwpwm; + + /* + * Find period count, duty count and prescale to suit duty_ns and + * period_ns. This is done according to formulas described below: + * + * period_ns = 10^9 * (PRESCALE + 1) * PC / PWM_CLK_RATE + * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE + * + * PC = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1)) + * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1)) + */ + + rate = clk_get_rate(kp->clk); + + while (1) { + div = 1000000000; + div *= 1 + prescale; + val = rate * period_ns; + pc = div64_u64(val, div); + val = rate * duty_ns; + dc = div64_u64(val, div); + + /* If duty_ns or period_ns are not achievable then return */ + if (pc < PERIOD_COUNT_MIN) + return -EINVAL; + + /* If pc and dc are in bounds, the calculation is done */ + if (pc <= PERIOD_COUNT_MAX && dc <= DUTY_CYCLE_HIGH_MAX) + break; + + /* Otherwise, increase prescale and recalculate pc and dc */ + if (++prescale > PRESCALE_MAX) + return -EINVAL; + } + + /* + * Don't apply settings if disabled. The period and duty cycle are + * always calculated above to ensure the new values are + * validated immediately instead of on enable. + */ + if (pwm_is_enabled(pwm)) { + kona_pwmc_prepare_for_settings(kp, chan); + + value = readl(kp->base + PRESCALE_OFFSET); + value &= ~PRESCALE_MASK(chan); + value |= prescale << PRESCALE_SHIFT(chan); + writel(value, kp->base + PRESCALE_OFFSET); + + writel(pc, kp->base + PERIOD_COUNT_OFFSET(chan)); + + writel(dc, kp->base + DUTY_CYCLE_HIGH_OFFSET(chan)); + + kona_pwmc_apply_settings(kp, chan); + } + + return 0; +} + +static int kona_pwmc_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct kona_pwmc *kp = to_kona_pwmc(chip); + unsigned int chan = pwm->hwpwm; + unsigned int value; + int ret; + + ret = clk_prepare_enable(kp->clk); + if (ret < 0) { + dev_err(chip->dev, "failed to enable clock: %d\n", ret); + return ret; + } + + kona_pwmc_prepare_for_settings(kp, chan); + + value = readl(kp->base + PWM_CONTROL_OFFSET); + + if (polarity == PWM_POLARITY_NORMAL) + value |= 1 << PWM_CONTROL_POLARITY_SHIFT(chan); + else + value &= ~(1 << PWM_CONTROL_POLARITY_SHIFT(chan)); + + writel(value, kp->base + PWM_CONTROL_OFFSET); + + kona_pwmc_apply_settings(kp, chan); + + clk_disable_unprepare(kp->clk); + + return 0; +} + +static int kona_pwmc_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct kona_pwmc *kp = to_kona_pwmc(chip); + int ret; + + ret = clk_prepare_enable(kp->clk); + if (ret < 0) { + dev_err(chip->dev, "failed to enable clock: %d\n", ret); + return ret; + } + + ret = kona_pwmc_config(chip, pwm, pwm_get_duty_cycle(pwm), + pwm_get_period(pwm)); + if (ret < 0) { + clk_disable_unprepare(kp->clk); + return ret; + } + + return 0; +} + +static void kona_pwmc_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct kona_pwmc *kp = to_kona_pwmc(chip); + unsigned int chan = pwm->hwpwm; + unsigned int value; + + kona_pwmc_prepare_for_settings(kp, chan); + + /* Simulate a disable by configuring for zero duty */ + writel(0, kp->base + DUTY_CYCLE_HIGH_OFFSET(chan)); + writel(0, kp->base + PERIOD_COUNT_OFFSET(chan)); + + /* Set prescale to 0 for this channel */ + value = readl(kp->base + PRESCALE_OFFSET); + value &= ~PRESCALE_MASK(chan); + writel(value, kp->base + PRESCALE_OFFSET); + + kona_pwmc_apply_settings(kp, chan); + + clk_disable_unprepare(kp->clk); +} + +static const struct pwm_ops kona_pwm_ops = { + .config = kona_pwmc_config, + .set_polarity = kona_pwmc_set_polarity, + .enable = kona_pwmc_enable, + .disable = kona_pwmc_disable, + .owner = THIS_MODULE, +}; + +static int kona_pwmc_probe(struct platform_device *pdev) +{ + struct kona_pwmc *kp; + struct resource *res; + unsigned int chan; + unsigned int value = 0; + int ret = 0; + + kp = devm_kzalloc(&pdev->dev, sizeof(*kp), GFP_KERNEL); + if (kp == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, kp); + + kp->chip.dev = &pdev->dev; + kp->chip.ops = &kona_pwm_ops; + kp->chip.base = -1; + kp->chip.npwm = 6; + kp->chip.of_xlate = of_pwm_xlate_with_flags; + kp->chip.of_pwm_n_cells = 3; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + kp->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(kp->base)) + return PTR_ERR(kp->base); + + kp->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(kp->clk)) { + dev_err(&pdev->dev, "failed to get clock: %ld\n", + PTR_ERR(kp->clk)); + return PTR_ERR(kp->clk); + } + + ret = clk_prepare_enable(kp->clk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable clock: %d\n", ret); + return ret; + } + + /* Set push/pull for all channels */ + for (chan = 0; chan < kp->chip.npwm; chan++) + value |= (1 << PWM_CONTROL_TYPE_SHIFT(chan)); + + writel(value, kp->base + PWM_CONTROL_OFFSET); + + clk_disable_unprepare(kp->clk); + + ret = pwmchip_add_with_polarity(&kp->chip, PWM_POLARITY_INVERSED); + if (ret < 0) + dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); + + return ret; +} + +static int kona_pwmc_remove(struct platform_device *pdev) +{ + struct kona_pwmc *kp = platform_get_drvdata(pdev); + unsigned int chan; + + for (chan = 0; chan < kp->chip.npwm; chan++) + if (pwm_is_enabled(&kp->chip.pwms[chan])) + clk_disable_unprepare(kp->clk); + + return pwmchip_remove(&kp->chip); +} + +static const struct of_device_id bcm_kona_pwmc_dt[] = { + { .compatible = "brcm,kona-pwm" }, + { }, +}; +MODULE_DEVICE_TABLE(of, bcm_kona_pwmc_dt); + +static struct platform_driver kona_pwmc_driver = { + .driver = { + .name = "bcm-kona-pwm", + .of_match_table = bcm_kona_pwmc_dt, + }, + .probe = kona_pwmc_probe, + .remove = kona_pwmc_remove, +}; +module_platform_driver(kona_pwmc_driver); + +MODULE_AUTHOR("Broadcom Corporation <bcm-kernel-feedback-list@broadcom.com>"); +MODULE_AUTHOR("Tim Kryger <tkryger@broadcom.com>"); +MODULE_DESCRIPTION("Broadcom Kona PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-bcm2835.c b/drivers/pwm/pwm-bcm2835.c new file mode 100644 index 000000000..6841dcfe2 --- /dev/null +++ b/drivers/pwm/pwm-bcm2835.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2014 Bart Tanghe <bart.tanghe@thomasmore.be> + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +#define PWM_CONTROL 0x000 +#define PWM_CONTROL_SHIFT(x) ((x) * 8) +#define PWM_CONTROL_MASK 0xff +#define PWM_MODE 0x80 /* set timer in PWM mode */ +#define PWM_ENABLE (1 << 0) +#define PWM_POLARITY (1 << 4) + +#define PERIOD(x) (((x) * 0x10) + 0x10) +#define DUTY(x) (((x) * 0x10) + 0x14) + +#define PERIOD_MIN 0x2 + +struct bcm2835_pwm { + struct pwm_chip chip; + struct device *dev; + void __iomem *base; + struct clk *clk; +}; + +static inline struct bcm2835_pwm *to_bcm2835_pwm(struct pwm_chip *chip) +{ + return container_of(chip, struct bcm2835_pwm, chip); +} + +static int bcm2835_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct bcm2835_pwm *pc = to_bcm2835_pwm(chip); + u32 value; + + value = readl(pc->base + PWM_CONTROL); + value &= ~(PWM_CONTROL_MASK << PWM_CONTROL_SHIFT(pwm->hwpwm)); + value |= (PWM_MODE << PWM_CONTROL_SHIFT(pwm->hwpwm)); + writel(value, pc->base + PWM_CONTROL); + + return 0; +} + +static void bcm2835_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct bcm2835_pwm *pc = to_bcm2835_pwm(chip); + u32 value; + + value = readl(pc->base + PWM_CONTROL); + value &= ~(PWM_CONTROL_MASK << PWM_CONTROL_SHIFT(pwm->hwpwm)); + writel(value, pc->base + PWM_CONTROL); +} + +static int bcm2835_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct bcm2835_pwm *pc = to_bcm2835_pwm(chip); + unsigned long rate = clk_get_rate(pc->clk); + unsigned long scaler; + u32 period; + + if (!rate) { + dev_err(pc->dev, "failed to get clock rate\n"); + return -EINVAL; + } + + scaler = DIV_ROUND_CLOSEST(NSEC_PER_SEC, rate); + period = DIV_ROUND_CLOSEST(period_ns, scaler); + + if (period < PERIOD_MIN) + return -EINVAL; + + writel(DIV_ROUND_CLOSEST(duty_ns, scaler), + pc->base + DUTY(pwm->hwpwm)); + writel(period, pc->base + PERIOD(pwm->hwpwm)); + + return 0; +} + +static int bcm2835_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct bcm2835_pwm *pc = to_bcm2835_pwm(chip); + u32 value; + + value = readl(pc->base + PWM_CONTROL); + value |= PWM_ENABLE << PWM_CONTROL_SHIFT(pwm->hwpwm); + writel(value, pc->base + PWM_CONTROL); + + return 0; +} + +static void bcm2835_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct bcm2835_pwm *pc = to_bcm2835_pwm(chip); + u32 value; + + value = readl(pc->base + PWM_CONTROL); + value &= ~(PWM_ENABLE << PWM_CONTROL_SHIFT(pwm->hwpwm)); + writel(value, pc->base + PWM_CONTROL); +} + +static int bcm2835_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct bcm2835_pwm *pc = to_bcm2835_pwm(chip); + u32 value; + + value = readl(pc->base + PWM_CONTROL); + + if (polarity == PWM_POLARITY_NORMAL) + value &= ~(PWM_POLARITY << PWM_CONTROL_SHIFT(pwm->hwpwm)); + else + value |= PWM_POLARITY << PWM_CONTROL_SHIFT(pwm->hwpwm); + + writel(value, pc->base + PWM_CONTROL); + + return 0; +} + +static const struct pwm_ops bcm2835_pwm_ops = { + .request = bcm2835_pwm_request, + .free = bcm2835_pwm_free, + .config = bcm2835_pwm_config, + .enable = bcm2835_pwm_enable, + .disable = bcm2835_pwm_disable, + .set_polarity = bcm2835_set_polarity, + .owner = THIS_MODULE, +}; + +static int bcm2835_pwm_probe(struct platform_device *pdev) +{ + struct bcm2835_pwm *pc; + struct resource *res; + int ret; + + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + pc->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pc->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pc->base)) + return PTR_ERR(pc->base); + + pc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pc->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk), + "clock not found\n"); + + ret = clk_prepare_enable(pc->clk); + if (ret) + return ret; + + pc->chip.dev = &pdev->dev; + pc->chip.ops = &bcm2835_pwm_ops; + pc->chip.base = -1; + pc->chip.npwm = 2; + pc->chip.of_xlate = of_pwm_xlate_with_flags; + pc->chip.of_pwm_n_cells = 3; + + platform_set_drvdata(pdev, pc); + + ret = pwmchip_add(&pc->chip); + if (ret < 0) + goto add_fail; + + return 0; + +add_fail: + clk_disable_unprepare(pc->clk); + return ret; +} + +static int bcm2835_pwm_remove(struct platform_device *pdev) +{ + struct bcm2835_pwm *pc = platform_get_drvdata(pdev); + + clk_disable_unprepare(pc->clk); + + return pwmchip_remove(&pc->chip); +} + +static const struct of_device_id bcm2835_pwm_of_match[] = { + { .compatible = "brcm,bcm2835-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, bcm2835_pwm_of_match); + +static struct platform_driver bcm2835_pwm_driver = { + .driver = { + .name = "bcm2835-pwm", + .of_match_table = bcm2835_pwm_of_match, + }, + .probe = bcm2835_pwm_probe, + .remove = bcm2835_pwm_remove, +}; +module_platform_driver(bcm2835_pwm_driver); + +MODULE_AUTHOR("Bart Tanghe <bart.tanghe@thomasmore.be>"); +MODULE_DESCRIPTION("Broadcom BCM2835 PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-berlin.c b/drivers/pwm/pwm-berlin.c new file mode 100644 index 000000000..b91c477cc --- /dev/null +++ b/drivers/pwm/pwm-berlin.c @@ -0,0 +1,306 @@ +/* + * Marvell Berlin PWM driver + * + * Copyright (C) 2015 Marvell Technology Group Ltd. + * + * Author: Antoine Tenart <antoine.tenart@free-electrons.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +#define BERLIN_PWM_EN 0x0 +#define BERLIN_PWM_ENABLE BIT(0) +#define BERLIN_PWM_CONTROL 0x4 +/* + * The prescaler claims to support 8 different moduli, configured using the + * low three bits of PWM_CONTROL. (Sequentially, they are 1, 4, 8, 16, 64, + * 256, 1024, and 4096.) However, the moduli from 4 to 1024 appear to be + * implemented by internally shifting TCNT left without adding additional + * bits. So, the max TCNT that actually works for a modulus of 4 is 0x3fff; + * for 8, 0x1fff; and so on. This means that those moduli are entirely + * useless, as we could just do the shift ourselves. The 4096 modulus is + * implemented with a real prescaler, so we do use that, but we treat it + * as a flag instead of pretending the modulus is actually configurable. + */ +#define BERLIN_PWM_PRESCALE_4096 0x7 +#define BERLIN_PWM_INVERT_POLARITY BIT(3) +#define BERLIN_PWM_DUTY 0x8 +#define BERLIN_PWM_TCNT 0xc +#define BERLIN_PWM_MAX_TCNT 65535 + +struct berlin_pwm_channel { + u32 enable; + u32 ctrl; + u32 duty; + u32 tcnt; +}; + +struct berlin_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + void __iomem *base; +}; + +static inline struct berlin_pwm_chip *to_berlin_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct berlin_pwm_chip, chip); +} + +static inline u32 berlin_pwm_readl(struct berlin_pwm_chip *chip, + unsigned int channel, unsigned long offset) +{ + return readl_relaxed(chip->base + channel * 0x10 + offset); +} + +static inline void berlin_pwm_writel(struct berlin_pwm_chip *chip, + unsigned int channel, u32 value, + unsigned long offset) +{ + writel_relaxed(value, chip->base + channel * 0x10 + offset); +} + +static int berlin_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct berlin_pwm_channel *channel; + + channel = kzalloc(sizeof(*channel), GFP_KERNEL); + if (!channel) + return -ENOMEM; + + return pwm_set_chip_data(pwm, channel); +} + +static void berlin_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct berlin_pwm_channel *channel = pwm_get_chip_data(pwm); + + kfree(channel); +} + +static int berlin_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm_dev, + int duty_ns, int period_ns) +{ + struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip); + bool prescale_4096 = false; + u32 value, duty, period; + u64 cycles; + + cycles = clk_get_rate(pwm->clk); + cycles *= period_ns; + do_div(cycles, NSEC_PER_SEC); + + if (cycles > BERLIN_PWM_MAX_TCNT) { + prescale_4096 = true; + cycles >>= 12; // Prescaled by 4096 + + if (cycles > BERLIN_PWM_MAX_TCNT) + return -ERANGE; + } + + period = cycles; + cycles *= duty_ns; + do_div(cycles, period_ns); + duty = cycles; + + value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_CONTROL); + if (prescale_4096) + value |= BERLIN_PWM_PRESCALE_4096; + else + value &= ~BERLIN_PWM_PRESCALE_4096; + berlin_pwm_writel(pwm, pwm_dev->hwpwm, value, BERLIN_PWM_CONTROL); + + berlin_pwm_writel(pwm, pwm_dev->hwpwm, duty, BERLIN_PWM_DUTY); + berlin_pwm_writel(pwm, pwm_dev->hwpwm, period, BERLIN_PWM_TCNT); + + return 0; +} + +static int berlin_pwm_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm_dev, + enum pwm_polarity polarity) +{ + struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip); + u32 value; + + value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_CONTROL); + + if (polarity == PWM_POLARITY_NORMAL) + value &= ~BERLIN_PWM_INVERT_POLARITY; + else + value |= BERLIN_PWM_INVERT_POLARITY; + + berlin_pwm_writel(pwm, pwm_dev->hwpwm, value, BERLIN_PWM_CONTROL); + + return 0; +} + +static int berlin_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm_dev) +{ + struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip); + u32 value; + + value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_EN); + value |= BERLIN_PWM_ENABLE; + berlin_pwm_writel(pwm, pwm_dev->hwpwm, value, BERLIN_PWM_EN); + + return 0; +} + +static void berlin_pwm_disable(struct pwm_chip *chip, + struct pwm_device *pwm_dev) +{ + struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip); + u32 value; + + value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_EN); + value &= ~BERLIN_PWM_ENABLE; + berlin_pwm_writel(pwm, pwm_dev->hwpwm, value, BERLIN_PWM_EN); +} + +static const struct pwm_ops berlin_pwm_ops = { + .request = berlin_pwm_request, + .free = berlin_pwm_free, + .config = berlin_pwm_config, + .set_polarity = berlin_pwm_set_polarity, + .enable = berlin_pwm_enable, + .disable = berlin_pwm_disable, + .owner = THIS_MODULE, +}; + +static const struct of_device_id berlin_pwm_match[] = { + { .compatible = "marvell,berlin-pwm" }, + { }, +}; +MODULE_DEVICE_TABLE(of, berlin_pwm_match); + +static int berlin_pwm_probe(struct platform_device *pdev) +{ + struct berlin_pwm_chip *pwm; + struct resource *res; + int ret; + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pwm->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pwm->base)) + return PTR_ERR(pwm->base); + + pwm->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pwm->clk)) + return PTR_ERR(pwm->clk); + + ret = clk_prepare_enable(pwm->clk); + if (ret) + return ret; + + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &berlin_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = 4; + pwm->chip.of_xlate = of_pwm_xlate_with_flags; + pwm->chip.of_pwm_n_cells = 3; + + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); + clk_disable_unprepare(pwm->clk); + return ret; + } + + platform_set_drvdata(pdev, pwm); + + return 0; +} + +static int berlin_pwm_remove(struct platform_device *pdev) +{ + struct berlin_pwm_chip *pwm = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&pwm->chip); + clk_disable_unprepare(pwm->clk); + + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static int berlin_pwm_suspend(struct device *dev) +{ + struct berlin_pwm_chip *pwm = dev_get_drvdata(dev); + unsigned int i; + + for (i = 0; i < pwm->chip.npwm; i++) { + struct berlin_pwm_channel *channel; + + channel = pwm_get_chip_data(&pwm->chip.pwms[i]); + if (!channel) + continue; + + channel->enable = berlin_pwm_readl(pwm, i, BERLIN_PWM_ENABLE); + channel->ctrl = berlin_pwm_readl(pwm, i, BERLIN_PWM_CONTROL); + channel->duty = berlin_pwm_readl(pwm, i, BERLIN_PWM_DUTY); + channel->tcnt = berlin_pwm_readl(pwm, i, BERLIN_PWM_TCNT); + } + + clk_disable_unprepare(pwm->clk); + + return 0; +} + +static int berlin_pwm_resume(struct device *dev) +{ + struct berlin_pwm_chip *pwm = dev_get_drvdata(dev); + unsigned int i; + int ret; + + ret = clk_prepare_enable(pwm->clk); + if (ret) + return ret; + + for (i = 0; i < pwm->chip.npwm; i++) { + struct berlin_pwm_channel *channel; + + channel = pwm_get_chip_data(&pwm->chip.pwms[i]); + if (!channel) + continue; + + berlin_pwm_writel(pwm, i, channel->ctrl, BERLIN_PWM_CONTROL); + berlin_pwm_writel(pwm, i, channel->duty, BERLIN_PWM_DUTY); + berlin_pwm_writel(pwm, i, channel->tcnt, BERLIN_PWM_TCNT); + berlin_pwm_writel(pwm, i, channel->enable, BERLIN_PWM_ENABLE); + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(berlin_pwm_pm_ops, berlin_pwm_suspend, + berlin_pwm_resume); + +static struct platform_driver berlin_pwm_driver = { + .probe = berlin_pwm_probe, + .remove = berlin_pwm_remove, + .driver = { + .name = "berlin-pwm", + .of_match_table = berlin_pwm_match, + .pm = &berlin_pwm_pm_ops, + }, +}; +module_platform_driver(berlin_pwm_driver); + +MODULE_AUTHOR("Antoine Tenart <antoine.tenart@free-electrons.com>"); +MODULE_DESCRIPTION("Marvell Berlin PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-brcmstb.c b/drivers/pwm/pwm-brcmstb.c new file mode 100644 index 000000000..fd8479cd6 --- /dev/null +++ b/drivers/pwm/pwm-brcmstb.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Broadcom BCM7038 PWM driver + * Author: Florian Fainelli + * + * Copyright (C) 2015 Broadcom Corporation + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/clk.h> +#include <linux/export.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/spinlock.h> + +#define PWM_CTRL 0x00 +#define CTRL_START BIT(0) +#define CTRL_OEB BIT(1) +#define CTRL_FORCE_HIGH BIT(2) +#define CTRL_OPENDRAIN BIT(3) +#define CTRL_CHAN_OFFS 4 + +#define PWM_CTRL2 0x04 +#define CTRL2_OUT_SELECT BIT(0) + +#define PWM_CH_SIZE 0x8 + +#define PWM_CWORD_MSB(ch) (0x08 + ((ch) * PWM_CH_SIZE)) +#define PWM_CWORD_LSB(ch) (0x0c + ((ch) * PWM_CH_SIZE)) + +/* Number of bits for the CWORD value */ +#define CWORD_BIT_SIZE 16 + +/* + * Maximum control word value allowed when variable-frequency PWM is used as a + * clock for the constant-frequency PMW. + */ +#define CONST_VAR_F_MAX 32768 +#define CONST_VAR_F_MIN 1 + +#define PWM_ON(ch) (0x18 + ((ch) * PWM_CH_SIZE)) +#define PWM_ON_MIN 1 +#define PWM_PERIOD(ch) (0x1c + ((ch) * PWM_CH_SIZE)) +#define PWM_PERIOD_MIN 0 + +#define PWM_ON_PERIOD_MAX 0xff + +struct brcmstb_pwm { + void __iomem *base; + spinlock_t lock; + struct clk *clk; + struct pwm_chip chip; +}; + +static inline u32 brcmstb_pwm_readl(struct brcmstb_pwm *p, + unsigned int offset) +{ + if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) + return __raw_readl(p->base + offset); + else + return readl_relaxed(p->base + offset); +} + +static inline void brcmstb_pwm_writel(struct brcmstb_pwm *p, u32 value, + unsigned int offset) +{ + if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) + __raw_writel(value, p->base + offset); + else + writel_relaxed(value, p->base + offset); +} + +static inline struct brcmstb_pwm *to_brcmstb_pwm(struct pwm_chip *chip) +{ + return container_of(chip, struct brcmstb_pwm, chip); +} + +/* + * Fv is derived from the variable frequency output. The variable frequency + * output is configured using this formula: + * + * W = cword, if cword < 2 ^ 15 else 16-bit 2's complement of cword + * + * Fv = W x 2 ^ -16 x 27Mhz (reference clock) + * + * The period is: (period + 1) / Fv and "on" time is on / (period + 1) + * + * The PWM core framework specifies that the "duty_ns" parameter is in fact the + * "on" time, so this translates directly into our HW programming here. + */ +static int brcmstb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct brcmstb_pwm *p = to_brcmstb_pwm(chip); + unsigned long pc, dc, cword = CONST_VAR_F_MAX; + unsigned int channel = pwm->hwpwm; + u32 value; + + /* + * If asking for a duty_ns equal to period_ns, we need to substract + * the period value by 1 to make it shorter than the "on" time and + * produce a flat 100% duty cycle signal, and max out the "on" time + */ + if (duty_ns == period_ns) { + dc = PWM_ON_PERIOD_MAX; + pc = PWM_ON_PERIOD_MAX - 1; + goto done; + } + + while (1) { + u64 rate, tmp; + + /* + * Calculate the base rate from base frequency and current + * cword + */ + rate = (u64)clk_get_rate(p->clk) * (u64)cword; + do_div(rate, 1 << CWORD_BIT_SIZE); + + tmp = period_ns * rate; + do_div(tmp, NSEC_PER_SEC); + pc = tmp; + + tmp = (duty_ns + 1) * rate; + do_div(tmp, NSEC_PER_SEC); + dc = tmp; + + /* + * We can be called with separate duty and period updates, + * so do not reject dc == 0 right away + */ + if (pc == PWM_PERIOD_MIN || (dc < PWM_ON_MIN && duty_ns)) + return -EINVAL; + + /* We converged on a calculation */ + if (pc <= PWM_ON_PERIOD_MAX && dc <= PWM_ON_PERIOD_MAX) + break; + + /* + * The cword needs to be a power of 2 for the variable + * frequency generator to output a 50% duty cycle variable + * frequency which is used as input clock to the fixed + * frequency generator. + */ + cword >>= 1; + + /* + * Desired periods are too large, we do not have a divider + * for them + */ + if (cword < CONST_VAR_F_MIN) + return -EINVAL; + } + +done: + /* + * Configure the defined "cword" value to have the variable frequency + * generator output a base frequency for the constant frequency + * generator to derive from. + */ + spin_lock(&p->lock); + brcmstb_pwm_writel(p, cword >> 8, PWM_CWORD_MSB(channel)); + brcmstb_pwm_writel(p, cword & 0xff, PWM_CWORD_LSB(channel)); + + /* Select constant frequency signal output */ + value = brcmstb_pwm_readl(p, PWM_CTRL2); + value |= CTRL2_OUT_SELECT << (channel * CTRL_CHAN_OFFS); + brcmstb_pwm_writel(p, value, PWM_CTRL2); + + /* Configure on and period value */ + brcmstb_pwm_writel(p, pc, PWM_PERIOD(channel)); + brcmstb_pwm_writel(p, dc, PWM_ON(channel)); + spin_unlock(&p->lock); + + return 0; +} + +static inline void brcmstb_pwm_enable_set(struct brcmstb_pwm *p, + unsigned int channel, bool enable) +{ + unsigned int shift = channel * CTRL_CHAN_OFFS; + u32 value; + + spin_lock(&p->lock); + value = brcmstb_pwm_readl(p, PWM_CTRL); + + if (enable) { + value &= ~(CTRL_OEB << shift); + value |= (CTRL_START | CTRL_OPENDRAIN) << shift; + } else { + value &= ~((CTRL_START | CTRL_OPENDRAIN) << shift); + value |= CTRL_OEB << shift; + } + + brcmstb_pwm_writel(p, value, PWM_CTRL); + spin_unlock(&p->lock); +} + +static int brcmstb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct brcmstb_pwm *p = to_brcmstb_pwm(chip); + + brcmstb_pwm_enable_set(p, pwm->hwpwm, true); + + return 0; +} + +static void brcmstb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct brcmstb_pwm *p = to_brcmstb_pwm(chip); + + brcmstb_pwm_enable_set(p, pwm->hwpwm, false); +} + +static const struct pwm_ops brcmstb_pwm_ops = { + .config = brcmstb_pwm_config, + .enable = brcmstb_pwm_enable, + .disable = brcmstb_pwm_disable, + .owner = THIS_MODULE, +}; + +static const struct of_device_id brcmstb_pwm_of_match[] = { + { .compatible = "brcm,bcm7038-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, brcmstb_pwm_of_match); + +static int brcmstb_pwm_probe(struct platform_device *pdev) +{ + struct brcmstb_pwm *p; + struct resource *res; + int ret; + + p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + spin_lock_init(&p->lock); + + p->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(p->clk)) { + dev_err(&pdev->dev, "failed to obtain clock\n"); + return PTR_ERR(p->clk); + } + + ret = clk_prepare_enable(p->clk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable clock: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, p); + + p->chip.dev = &pdev->dev; + p->chip.ops = &brcmstb_pwm_ops; + p->chip.base = -1; + p->chip.npwm = 2; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + p->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(p->base)) { + ret = PTR_ERR(p->base); + goto out_clk; + } + + ret = pwmchip_add(&p->chip); + if (ret) { + dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); + goto out_clk; + } + + return 0; + +out_clk: + clk_disable_unprepare(p->clk); + return ret; +} + +static int brcmstb_pwm_remove(struct platform_device *pdev) +{ + struct brcmstb_pwm *p = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&p->chip); + clk_disable_unprepare(p->clk); + + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static int brcmstb_pwm_suspend(struct device *dev) +{ + struct brcmstb_pwm *p = dev_get_drvdata(dev); + + clk_disable_unprepare(p->clk); + + return 0; +} + +static int brcmstb_pwm_resume(struct device *dev) +{ + struct brcmstb_pwm *p = dev_get_drvdata(dev); + + clk_prepare_enable(p->clk); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(brcmstb_pwm_pm_ops, brcmstb_pwm_suspend, + brcmstb_pwm_resume); + +static struct platform_driver brcmstb_pwm_driver = { + .probe = brcmstb_pwm_probe, + .remove = brcmstb_pwm_remove, + .driver = { + .name = "pwm-brcmstb", + .of_match_table = brcmstb_pwm_of_match, + .pm = &brcmstb_pwm_pm_ops, + }, +}; +module_platform_driver(brcmstb_pwm_driver); + +MODULE_AUTHOR("Florian Fainelli <f.fainelli@gmail.com>"); +MODULE_DESCRIPTION("Broadcom STB PWM driver"); +MODULE_ALIAS("platform:pwm-brcmstb"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-clps711x.c b/drivers/pwm/pwm-clps711x.c new file mode 100644 index 000000000..ba9500aca --- /dev/null +++ b/drivers/pwm/pwm-clps711x.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Cirrus Logic CLPS711X PWM driver + * Author: Alexander Shiyan <shc_work@mail.ru> + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +struct clps711x_chip { + struct pwm_chip chip; + void __iomem *pmpcon; + struct clk *clk; + spinlock_t lock; +}; + +static inline struct clps711x_chip *to_clps711x_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct clps711x_chip, chip); +} + +static void clps711x_pwm_update_val(struct clps711x_chip *priv, u32 n, u32 v) +{ + /* PWM0 - bits 4..7, PWM1 - bits 8..11 */ + u32 shift = (n + 1) * 4; + unsigned long flags; + u32 tmp; + + spin_lock_irqsave(&priv->lock, flags); + + tmp = readl(priv->pmpcon); + tmp &= ~(0xf << shift); + tmp |= v << shift; + writel(tmp, priv->pmpcon); + + spin_unlock_irqrestore(&priv->lock, flags); +} + +static unsigned int clps711x_get_duty(struct pwm_device *pwm, unsigned int v) +{ + /* Duty cycle 0..15 max */ + return DIV64_U64_ROUND_CLOSEST(v * 0xf, pwm->args.period); +} + +static int clps711x_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct clps711x_chip *priv = to_clps711x_chip(chip); + unsigned int freq = clk_get_rate(priv->clk); + + if (!freq) + return -EINVAL; + + /* Store constant period value */ + pwm->args.period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq); + + return 0; +} + +static int clps711x_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct clps711x_chip *priv = to_clps711x_chip(chip); + unsigned int duty; + + if (period_ns != pwm->args.period) + return -EINVAL; + + duty = clps711x_get_duty(pwm, duty_ns); + clps711x_pwm_update_val(priv, pwm->hwpwm, duty); + + return 0; +} + +static int clps711x_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct clps711x_chip *priv = to_clps711x_chip(chip); + unsigned int duty; + + duty = clps711x_get_duty(pwm, pwm_get_duty_cycle(pwm)); + clps711x_pwm_update_val(priv, pwm->hwpwm, duty); + + return 0; +} + +static void clps711x_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct clps711x_chip *priv = to_clps711x_chip(chip); + + clps711x_pwm_update_val(priv, pwm->hwpwm, 0); +} + +static const struct pwm_ops clps711x_pwm_ops = { + .request = clps711x_pwm_request, + .config = clps711x_pwm_config, + .enable = clps711x_pwm_enable, + .disable = clps711x_pwm_disable, + .owner = THIS_MODULE, +}; + +static struct pwm_device *clps711x_pwm_xlate(struct pwm_chip *chip, + const struct of_phandle_args *args) +{ + if (args->args[0] >= chip->npwm) + return ERR_PTR(-EINVAL); + + return pwm_request_from_chip(chip, args->args[0], NULL); +} + +static int clps711x_pwm_probe(struct platform_device *pdev) +{ + struct clps711x_chip *priv; + struct resource *res; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->pmpcon = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->pmpcon)) + return PTR_ERR(priv->pmpcon); + + priv->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->chip.ops = &clps711x_pwm_ops; + priv->chip.dev = &pdev->dev; + priv->chip.base = -1; + priv->chip.npwm = 2; + priv->chip.of_xlate = clps711x_pwm_xlate; + priv->chip.of_pwm_n_cells = 1; + + spin_lock_init(&priv->lock); + + platform_set_drvdata(pdev, priv); + + return pwmchip_add(&priv->chip); +} + +static int clps711x_pwm_remove(struct platform_device *pdev) +{ + struct clps711x_chip *priv = platform_get_drvdata(pdev); + + return pwmchip_remove(&priv->chip); +} + +static const struct of_device_id __maybe_unused clps711x_pwm_dt_ids[] = { + { .compatible = "cirrus,ep7209-pwm", }, + { } +}; +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), + }, + .probe = clps711x_pwm_probe, + .remove = clps711x_pwm_remove, +}; +module_platform_driver(clps711x_pwm_driver); + +MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); +MODULE_DESCRIPTION("Cirrus Logic CLPS711X PWM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-crc.c b/drivers/pwm/pwm-crc.c new file mode 100644 index 000000000..ecfdfac0c --- /dev/null +++ b/drivers/pwm/pwm-crc.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 Intel Corporation. All rights reserved. + * + * Author: Shobhit Kumar <shobhit.kumar@intel.com> + */ + +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/pwm.h> + +#define PWM0_CLK_DIV 0x4B +#define PWM_OUTPUT_ENABLE BIT(7) +#define PWM_DIV_CLK_0 0x00 /* DIVIDECLK = BASECLK */ +#define PWM_DIV_CLK_100 0x63 /* DIVIDECLK = BASECLK/100 */ +#define PWM_DIV_CLK_128 0x7F /* DIVIDECLK = BASECLK/128 */ + +#define PWM0_DUTY_CYCLE 0x4E +#define BACKLIGHT_EN 0x51 + +#define PWM_MAX_LEVEL 0xFF + +#define PWM_BASE_CLK_MHZ 6 /* 6 MHz */ +#define PWM_MAX_PERIOD_NS 5461334 /* 183 Hz */ + +/** + * struct crystalcove_pwm - Crystal Cove PWM controller + * @chip: the abstract pwm_chip structure. + * @regmap: the regmap from the parent device. + */ +struct crystalcove_pwm { + struct pwm_chip chip; + struct regmap *regmap; +}; + +static inline struct crystalcove_pwm *to_crc_pwm(struct pwm_chip *pc) +{ + return container_of(pc, struct crystalcove_pwm, chip); +} + +static int crc_pwm_calc_clk_div(int period_ns) +{ + int clk_div; + + clk_div = PWM_BASE_CLK_MHZ * period_ns / (256 * NSEC_PER_USEC); + /* clk_div 1 - 128, maps to register values 0-127 */ + if (clk_div > 0) + clk_div--; + + return clk_div; +} + +static int crc_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct crystalcove_pwm *crc_pwm = to_crc_pwm(chip); + struct device *dev = crc_pwm->chip.dev; + int err; + + if (state->period > PWM_MAX_PERIOD_NS) { + dev_err(dev, "un-supported period_ns\n"); + return -EINVAL; + } + + if (state->polarity != PWM_POLARITY_NORMAL) + return -EOPNOTSUPP; + + if (pwm_is_enabled(pwm) && !state->enabled) { + err = regmap_write(crc_pwm->regmap, BACKLIGHT_EN, 0); + if (err) { + dev_err(dev, "Error writing BACKLIGHT_EN %d\n", err); + return err; + } + } + + if (pwm_get_duty_cycle(pwm) != state->duty_cycle || + pwm_get_period(pwm) != state->period) { + u64 level = state->duty_cycle * PWM_MAX_LEVEL; + + do_div(level, state->period); + + err = regmap_write(crc_pwm->regmap, PWM0_DUTY_CYCLE, level); + if (err) { + dev_err(dev, "Error writing PWM0_DUTY_CYCLE %d\n", err); + return err; + } + } + + if (pwm_is_enabled(pwm) && state->enabled && + pwm_get_period(pwm) != state->period) { + /* changing the clk divisor, clear PWM_OUTPUT_ENABLE first */ + err = regmap_write(crc_pwm->regmap, PWM0_CLK_DIV, 0); + if (err) { + dev_err(dev, "Error writing PWM0_CLK_DIV %d\n", err); + return err; + } + } + + if (pwm_get_period(pwm) != state->period || + pwm_is_enabled(pwm) != state->enabled) { + int clk_div = crc_pwm_calc_clk_div(state->period); + int pwm_output_enable = state->enabled ? PWM_OUTPUT_ENABLE : 0; + + err = regmap_write(crc_pwm->regmap, PWM0_CLK_DIV, + clk_div | pwm_output_enable); + if (err) { + dev_err(dev, "Error writing PWM0_CLK_DIV %d\n", err); + return err; + } + } + + if (!pwm_is_enabled(pwm) && state->enabled) { + err = regmap_write(crc_pwm->regmap, BACKLIGHT_EN, 1); + if (err) { + dev_err(dev, "Error writing BACKLIGHT_EN %d\n", err); + return err; + } + } + + return 0; +} + +static void crc_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct crystalcove_pwm *crc_pwm = to_crc_pwm(chip); + struct device *dev = crc_pwm->chip.dev; + unsigned int clk_div, clk_div_reg, duty_cycle_reg; + int error; + + error = regmap_read(crc_pwm->regmap, PWM0_CLK_DIV, &clk_div_reg); + if (error) { + dev_err(dev, "Error reading PWM0_CLK_DIV %d\n", error); + return; + } + + error = regmap_read(crc_pwm->regmap, PWM0_DUTY_CYCLE, &duty_cycle_reg); + if (error) { + dev_err(dev, "Error reading PWM0_DUTY_CYCLE %d\n", error); + return; + } + + clk_div = (clk_div_reg & ~PWM_OUTPUT_ENABLE) + 1; + + state->period = + DIV_ROUND_UP(clk_div * NSEC_PER_USEC * 256, PWM_BASE_CLK_MHZ); + state->duty_cycle = + DIV_ROUND_UP_ULL(duty_cycle_reg * state->period, PWM_MAX_LEVEL); + state->polarity = PWM_POLARITY_NORMAL; + state->enabled = !!(clk_div_reg & PWM_OUTPUT_ENABLE); +} + +static const struct pwm_ops crc_pwm_ops = { + .apply = crc_pwm_apply, + .get_state = crc_pwm_get_state, +}; + +static int crystalcove_pwm_probe(struct platform_device *pdev) +{ + struct crystalcove_pwm *pwm; + struct device *dev = pdev->dev.parent; + struct intel_soc_pmic *pmic = dev_get_drvdata(dev); + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) + return -ENOMEM; + + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &crc_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = 1; + + /* get the PMIC regmap */ + pwm->regmap = pmic->regmap; + + platform_set_drvdata(pdev, pwm); + + return pwmchip_add(&pwm->chip); +} + +static int crystalcove_pwm_remove(struct platform_device *pdev) +{ + struct crystalcove_pwm *pwm = platform_get_drvdata(pdev); + + return pwmchip_remove(&pwm->chip); +} + +static struct platform_driver crystalcove_pwm_driver = { + .probe = crystalcove_pwm_probe, + .remove = crystalcove_pwm_remove, + .driver = { + .name = "crystal_cove_pwm", + }, +}; + +builtin_platform_driver(crystalcove_pwm_driver); diff --git a/drivers/pwm/pwm-cros-ec.c b/drivers/pwm/pwm-cros-ec.c new file mode 100644 index 000000000..d4f4a1336 --- /dev/null +++ b/drivers/pwm/pwm-cros-ec.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Expose a PWM controlled by the ChromeOS EC to the host processor. + * + * Copyright (C) 2016 Google, Inc. + */ + +#include <linux/module.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +/** + * struct cros_ec_pwm_device - Driver data for EC PWM + * + * @dev: Device node + * @ec: Pointer to EC device + * @chip: PWM controller chip + */ +struct cros_ec_pwm_device { + struct device *dev; + struct cros_ec_device *ec; + struct pwm_chip chip; +}; + +/** + * 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 *c) +{ + return container_of(c, struct cros_ec_pwm_device, chip); +} + +static int cros_ec_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct cros_ec_pwm *channel; + + channel = kzalloc(sizeof(*channel), GFP_KERNEL); + if (!channel) + return -ENOMEM; + + pwm_set_chip_data(pwm, channel); + + return 0; +} + +static void cros_ec_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct cros_ec_pwm *channel = pwm_get_chip_data(pwm); + + kfree(channel); +} + +static int cros_ec_pwm_set_duty(struct cros_ec_device *ec, u8 index, u16 duty) +{ + struct { + struct cros_ec_command msg; + struct ec_params_pwm_set_duty params; + } __packed buf; + struct ec_params_pwm_set_duty *params = &buf.params; + struct cros_ec_command *msg = &buf.msg; + + memset(&buf, 0, sizeof(buf)); + + msg->version = 0; + msg->command = EC_CMD_PWM_SET_DUTY; + msg->insize = 0; + msg->outsize = sizeof(*params); + + params->duty = duty; + params->pwm_type = EC_PWM_TYPE_GENERIC; + params->index = index; + + return cros_ec_cmd_xfer_status(ec, msg); +} + +static int cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index) +{ + struct { + struct cros_ec_command msg; + union { + struct ec_params_pwm_get_duty params; + struct ec_response_pwm_get_duty resp; + }; + } __packed buf; + struct ec_params_pwm_get_duty *params = &buf.params; + struct ec_response_pwm_get_duty *resp = &buf.resp; + struct cros_ec_command *msg = &buf.msg; + int ret; + + memset(&buf, 0, sizeof(buf)); + + msg->version = 0; + msg->command = EC_CMD_PWM_GET_DUTY; + msg->insize = sizeof(*resp); + msg->outsize = sizeof(*params); + + params->pwm_type = EC_PWM_TYPE_GENERIC; + params->index = index; + + ret = cros_ec_cmd_xfer_status(ec, msg); + if (ret < 0) + return ret; + + return resp->duty; +} + +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 = pwm_get_chip_data(pwm); + u16 duty_cycle; + int ret; + + /* The EC won't let us change the period */ + if (state->period != EC_PWM_MAX_DUTY) + return -EINVAL; + + /* + * EC doesn't separate the concept of duty cycle and enabled, but + * kernel does. Translate. + */ + duty_cycle = state->enabled ? state->duty_cycle : 0; + + ret = cros_ec_pwm_set_duty(ec_pwm->ec, pwm->hwpwm, duty_cycle); + if (ret < 0) + return ret; + + channel->duty_cycle = state->duty_cycle; + + return 0; +} + +static void 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 = pwm_get_chip_data(pwm); + int ret; + + ret = cros_ec_pwm_get_duty(ec_pwm->ec, pwm->hwpwm); + if (ret < 0) { + dev_err(chip->dev, "error getting initial duty: %d\n", ret); + return; + } + + state->enabled = (ret > 0); + 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; +} + +static struct pwm_device * +cros_ec_pwm_xlate(struct pwm_chip *pc, const struct of_phandle_args *args) +{ + struct pwm_device *pwm; + + if (args->args[0] >= pc->npwm) + return ERR_PTR(-EINVAL); + + pwm = pwm_request_from_chip(pc, 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 = { + .request = cros_ec_pwm_request, + .free = cros_ec_pwm_free, + .get_state = cros_ec_pwm_get_state, + .apply = cros_ec_pwm_apply, + .owner = THIS_MODULE, +}; + +/* + * Determine the number of supported PWMs. The EC does not return the number + * of PWMs it supports directly, so we have to read the pwm duty cycle for + * subsequent channels until we get an error. + */ +static int cros_ec_num_pwms(struct cros_ec_device *ec) +{ + int i, ret; + + /* The index field is only 8 bits */ + for (i = 0; i <= U8_MAX; i++) { + ret = cros_ec_pwm_get_duty(ec, i); + /* + * We look for SUCCESS, INVALID_COMMAND, or INVALID_PARAM + * responses; everything else is treated as an error. + * The EC error codes map to -EOPNOTSUPP and -EINVAL, + * so check for those. + */ + switch (ret) { + case -EOPNOTSUPP: /* invalid command */ + return -ENODEV; + case -EINVAL: /* invalid parameter */ + return i; + default: + if (ret < 0) + return ret; + break; + } + } + + return U8_MAX; +} + +static int cros_ec_pwm_probe(struct platform_device *pdev) +{ + struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct cros_ec_pwm_device *ec_pwm; + struct pwm_chip *chip; + int ret; + + if (!ec) { + dev_err(dev, "no parent EC device\n"); + return -EINVAL; + } + + ec_pwm = devm_kzalloc(dev, sizeof(*ec_pwm), GFP_KERNEL); + if (!ec_pwm) + return -ENOMEM; + chip = &ec_pwm->chip; + ec_pwm->ec = ec; + + /* PWM chip */ + chip->dev = dev; + chip->ops = &cros_ec_pwm_ops; + chip->of_xlate = cros_ec_pwm_xlate; + chip->of_pwm_n_cells = 1; + chip->base = -1; + ret = cros_ec_num_pwms(ec); + if (ret < 0) { + dev_err(dev, "Couldn't find PWMs: %d\n", ret); + return ret; + } + chip->npwm = ret; + dev_dbg(dev, "Probed %u PWMs\n", chip->npwm); + + ret = pwmchip_add(chip); + if (ret < 0) { + dev_err(dev, "cannot register PWM: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, ec_pwm); + + return ret; +} + +static int cros_ec_pwm_remove(struct platform_device *dev) +{ + struct cros_ec_pwm_device *ec_pwm = platform_get_drvdata(dev); + struct pwm_chip *chip = &ec_pwm->chip; + + return pwmchip_remove(chip); +} + +#ifdef CONFIG_OF +static const struct of_device_id cros_ec_pwm_of_match[] = { + { .compatible = "google,cros-ec-pwm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, cros_ec_pwm_of_match); +#endif + +static struct platform_driver cros_ec_pwm_driver = { + .probe = cros_ec_pwm_probe, + .remove = cros_ec_pwm_remove, + .driver = { + .name = "cros-ec-pwm", + .of_match_table = of_match_ptr(cros_ec_pwm_of_match), + }, +}; +module_platform_driver(cros_ec_pwm_driver); + +MODULE_ALIAS("platform:cros-ec-pwm"); +MODULE_DESCRIPTION("ChromeOS EC PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-ep93xx.c b/drivers/pwm/pwm-ep93xx.c new file mode 100644 index 000000000..4bab73073 --- /dev/null +++ b/drivers/pwm/pwm-ep93xx.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PWM framework driver for Cirrus Logic EP93xx + * + * Copyright (c) 2009 Matthieu Crapet <mcrapet@gmail.com> + * Copyright (c) 2009, 2013 H Hartley Sweeten <hsweeten@visionengravers.com> + * + * EP9301/02 have only one channel: + * platform device ep93xx-pwm.1 - PWMOUT1 (EGPIO14) + * + * EP9307 has only one channel: + * platform device ep93xx-pwm.0 - PWMOUT + * + * EP9312/15 have two channels: + * platform device ep93xx-pwm.0 - PWMOUT + * platform device ep93xx-pwm.1 - PWMOUT1 (EGPIO14) + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/pwm.h> + +#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 +#define EP93XX_PWMx_INVERT 0x0c + +struct ep93xx_pwm { + void __iomem *base; + struct clk *clk; + struct pwm_chip chip; +}; + +static inline struct ep93xx_pwm *to_ep93xx_pwm(struct pwm_chip *chip) +{ + return container_of(chip, struct ep93xx_pwm, chip); +} + +static int ep93xx_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct platform_device *pdev = to_platform_device(chip->dev); + + 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(chip->dev); + + ep93xx_pwm_release_gpio(pdev); +} + +static int ep93xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip); + void __iomem *base = ep93xx_pwm->base; + unsigned long long c; + unsigned long period_cycles; + unsigned long duty_cycles; + unsigned long term; + int ret = 0; + + /* + * The clock needs to be enabled to access the PWM registers. + * Configuration can be changed at any time. + */ + if (!pwm_is_enabled(pwm)) { + ret = clk_enable(ep93xx_pwm->clk); + if (ret) + return ret; + } + + c = clk_get_rate(ep93xx_pwm->clk); + c *= period_ns; + do_div(c, 1000000000); + period_cycles = c; + + c = period_cycles; + c *= duty_ns; + do_div(c, period_ns); + duty_cycles = c; + + if (period_cycles < 0x10000 && duty_cycles < 0x10000) { + term = readw(base + EP93XX_PWMx_TERM_COUNT); + + /* Order is important if PWM is running */ + if (period_cycles > term) { + writew(period_cycles, base + EP93XX_PWMx_TERM_COUNT); + writew(duty_cycles, base + EP93XX_PWMx_DUTY_CYCLE); + } else { + writew(duty_cycles, base + EP93XX_PWMx_DUTY_CYCLE); + writew(period_cycles, base + EP93XX_PWMx_TERM_COUNT); + } + } else { + ret = -EINVAL; + } + + if (!pwm_is_enabled(pwm)) + clk_disable(ep93xx_pwm->clk); + + return ret; +} + +static int ep93xx_pwm_polarity(struct pwm_chip *chip, struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip); + int ret; + + /* + * The clock needs to be enabled to access the PWM registers. + * Polarity can only be changed when the PWM is disabled. + */ + ret = clk_enable(ep93xx_pwm->clk); + if (ret) + return ret; + + if (polarity == PWM_POLARITY_INVERSED) + writew(0x1, ep93xx_pwm->base + EP93XX_PWMx_INVERT); + else + writew(0x0, ep93xx_pwm->base + EP93XX_PWMx_INVERT); + + clk_disable(ep93xx_pwm->clk); + + return 0; +} + +static int ep93xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip); + int ret; + + ret = clk_enable(ep93xx_pwm->clk); + if (ret) + return ret; + + writew(0x1, ep93xx_pwm->base + EP93XX_PWMx_ENABLE); + + return 0; +} + +static void ep93xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip); + + writew(0x0, ep93xx_pwm->base + EP93XX_PWMx_ENABLE); + clk_disable(ep93xx_pwm->clk); +} + +static const struct pwm_ops ep93xx_pwm_ops = { + .request = ep93xx_pwm_request, + .free = ep93xx_pwm_free, + .config = ep93xx_pwm_config, + .set_polarity = ep93xx_pwm_polarity, + .enable = ep93xx_pwm_enable, + .disable = ep93xx_pwm_disable, + .owner = THIS_MODULE, +}; + +static int ep93xx_pwm_probe(struct platform_device *pdev) +{ + struct ep93xx_pwm *ep93xx_pwm; + struct resource *res; + int ret; + + ep93xx_pwm = devm_kzalloc(&pdev->dev, sizeof(*ep93xx_pwm), GFP_KERNEL); + if (!ep93xx_pwm) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ep93xx_pwm->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ep93xx_pwm->base)) + return PTR_ERR(ep93xx_pwm->base); + + ep93xx_pwm->clk = devm_clk_get(&pdev->dev, "pwm_clk"); + if (IS_ERR(ep93xx_pwm->clk)) + return PTR_ERR(ep93xx_pwm->clk); + + ep93xx_pwm->chip.dev = &pdev->dev; + ep93xx_pwm->chip.ops = &ep93xx_pwm_ops; + ep93xx_pwm->chip.base = -1; + ep93xx_pwm->chip.npwm = 1; + + ret = pwmchip_add(&ep93xx_pwm->chip); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, ep93xx_pwm); + return 0; +} + +static int ep93xx_pwm_remove(struct platform_device *pdev) +{ + struct ep93xx_pwm *ep93xx_pwm = platform_get_drvdata(pdev); + + return pwmchip_remove(&ep93xx_pwm->chip); +} + +static struct platform_driver ep93xx_pwm_driver = { + .driver = { + .name = "ep93xx-pwm", + }, + .probe = ep93xx_pwm_probe, + .remove = ep93xx_pwm_remove, +}; +module_platform_driver(ep93xx_pwm_driver); + +MODULE_DESCRIPTION("Cirrus Logic EP93xx PWM driver"); +MODULE_AUTHOR("Matthieu Crapet <mcrapet@gmail.com>"); +MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>"); +MODULE_ALIAS("platform:ep93xx-pwm"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-fsl-ftm.c b/drivers/pwm/pwm-fsl-ftm.c new file mode 100644 index 000000000..59272a920 --- /dev/null +++ b/drivers/pwm/pwm-fsl-ftm.c @@ -0,0 +1,567 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Freescale FlexTimer Module (FTM) PWM Driver + * + * Copyright 2012-2013 Freescale Semiconductor, Inc. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/pwm.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/fsl/ftm.h> + +#define FTM_SC_CLK(c) (((c) + 1) << FTM_SC_CLK_MASK_SHIFT) + +enum fsl_pwm_clk { + FSL_PWM_CLK_SYS, + FSL_PWM_CLK_FIX, + FSL_PWM_CLK_EXT, + FSL_PWM_CLK_CNTEN, + FSL_PWM_CLK_MAX +}; + +struct fsl_ftm_soc { + bool has_enable_bits; +}; + +struct fsl_pwm_periodcfg { + enum fsl_pwm_clk clk_select; + unsigned int clk_ps; + unsigned int mod_period; +}; + +struct fsl_pwm_chip { + struct pwm_chip chip; + struct mutex lock; + struct regmap *regmap; + + /* This value is valid iff a pwm is running */ + struct fsl_pwm_periodcfg period; + + struct clk *ipg_clk; + struct clk *clk[FSL_PWM_CLK_MAX]; + + const struct fsl_ftm_soc *soc; +}; + +static inline struct fsl_pwm_chip *to_fsl_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct fsl_pwm_chip, chip); +} + +static void ftm_clear_write_protection(struct fsl_pwm_chip *fpc) +{ + u32 val; + + regmap_read(fpc->regmap, FTM_FMS, &val); + if (val & FTM_FMS_WPEN) + regmap_update_bits(fpc->regmap, FTM_MODE, FTM_MODE_WPDIS, + FTM_MODE_WPDIS); +} + +static void ftm_set_write_protection(struct fsl_pwm_chip *fpc) +{ + regmap_update_bits(fpc->regmap, FTM_FMS, FTM_FMS_WPEN, FTM_FMS_WPEN); +} + +static bool fsl_pwm_periodcfg_are_equal(const struct fsl_pwm_periodcfg *a, + const struct fsl_pwm_periodcfg *b) +{ + if (a->clk_select != b->clk_select) + return false; + if (a->clk_ps != b->clk_ps) + return false; + if (a->mod_period != b->mod_period) + return false; + return true; +} + +static int fsl_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + int ret; + struct fsl_pwm_chip *fpc = to_fsl_chip(chip); + + ret = clk_prepare_enable(fpc->ipg_clk); + if (!ret && fpc->soc->has_enable_bits) { + mutex_lock(&fpc->lock); + regmap_update_bits(fpc->regmap, FTM_SC, BIT(pwm->hwpwm + 16), + BIT(pwm->hwpwm + 16)); + mutex_unlock(&fpc->lock); + } + + return ret; +} + +static void fsl_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct fsl_pwm_chip *fpc = to_fsl_chip(chip); + + if (fpc->soc->has_enable_bits) { + mutex_lock(&fpc->lock); + regmap_update_bits(fpc->regmap, FTM_SC, BIT(pwm->hwpwm + 16), + 0); + mutex_unlock(&fpc->lock); + } + + clk_disable_unprepare(fpc->ipg_clk); +} + +static unsigned int fsl_pwm_ticks_to_ns(struct fsl_pwm_chip *fpc, + unsigned int ticks) +{ + unsigned long rate; + unsigned long long exval; + + rate = clk_get_rate(fpc->clk[fpc->period.clk_select]); + exval = ticks; + exval *= 1000000000UL; + do_div(exval, rate >> fpc->period.clk_ps); + return exval; +} + +static bool fsl_pwm_calculate_period_clk(struct fsl_pwm_chip *fpc, + unsigned int period_ns, + enum fsl_pwm_clk index, + struct fsl_pwm_periodcfg *periodcfg + ) +{ + unsigned long long c; + unsigned int ps; + + c = clk_get_rate(fpc->clk[index]); + c = c * period_ns; + do_div(c, 1000000000UL); + + if (c == 0) + return false; + + for (ps = 0; ps < 8 ; ++ps, c >>= 1) { + if (c <= 0x10000) { + periodcfg->clk_select = index; + periodcfg->clk_ps = ps; + periodcfg->mod_period = c - 1; + return true; + } + } + return false; +} + +static bool fsl_pwm_calculate_period(struct fsl_pwm_chip *fpc, + unsigned int period_ns, + struct fsl_pwm_periodcfg *periodcfg) +{ + enum fsl_pwm_clk m0, m1; + unsigned long fix_rate, ext_rate; + bool ret; + + ret = fsl_pwm_calculate_period_clk(fpc, period_ns, FSL_PWM_CLK_SYS, + periodcfg); + if (ret) + return true; + + fix_rate = clk_get_rate(fpc->clk[FSL_PWM_CLK_FIX]); + ext_rate = clk_get_rate(fpc->clk[FSL_PWM_CLK_EXT]); + + if (fix_rate > ext_rate) { + m0 = FSL_PWM_CLK_FIX; + m1 = FSL_PWM_CLK_EXT; + } else { + m0 = FSL_PWM_CLK_EXT; + m1 = FSL_PWM_CLK_FIX; + } + + ret = fsl_pwm_calculate_period_clk(fpc, period_ns, m0, periodcfg); + if (ret) + return true; + + return fsl_pwm_calculate_period_clk(fpc, period_ns, m1, periodcfg); +} + +static unsigned int fsl_pwm_calculate_duty(struct fsl_pwm_chip *fpc, + unsigned int duty_ns) +{ + unsigned long long duty; + + unsigned int period = fpc->period.mod_period + 1; + unsigned int period_ns = fsl_pwm_ticks_to_ns(fpc, period); + + duty = (unsigned long long)duty_ns * period; + do_div(duty, period_ns); + + return (unsigned int)duty; +} + +static bool fsl_pwm_is_any_pwm_enabled(struct fsl_pwm_chip *fpc, + struct pwm_device *pwm) +{ + u32 val; + + regmap_read(fpc->regmap, FTM_OUTMASK, &val); + if (~val & 0xFF) + return true; + else + return false; +} + +static bool fsl_pwm_is_other_pwm_enabled(struct fsl_pwm_chip *fpc, + struct pwm_device *pwm) +{ + u32 val; + + regmap_read(fpc->regmap, FTM_OUTMASK, &val); + if (~(val | BIT(pwm->hwpwm)) & 0xFF) + return true; + else + return false; +} + +static int fsl_pwm_apply_config(struct fsl_pwm_chip *fpc, + struct pwm_device *pwm, + const struct pwm_state *newstate) +{ + unsigned int duty; + u32 reg_polarity; + + struct fsl_pwm_periodcfg periodcfg; + bool do_write_period = false; + + if (!fsl_pwm_calculate_period(fpc, newstate->period, &periodcfg)) { + dev_err(fpc->chip.dev, "failed to calculate new period\n"); + return -EINVAL; + } + + if (!fsl_pwm_is_any_pwm_enabled(fpc, pwm)) + do_write_period = true; + /* + * The Freescale FTM controller supports only a single period for + * all PWM channels, therefore verify if the newly computed period + * is different than the current period being used. In such case + * we allow to change the period only if no other pwm is running. + */ + else if (!fsl_pwm_periodcfg_are_equal(&fpc->period, &periodcfg)) { + if (fsl_pwm_is_other_pwm_enabled(fpc, pwm)) { + dev_err(fpc->chip.dev, + "Cannot change period for PWM %u, disable other PWMs first\n", + pwm->hwpwm); + return -EBUSY; + } + if (fpc->period.clk_select != periodcfg.clk_select) { + int ret; + enum fsl_pwm_clk oldclk = fpc->period.clk_select; + enum fsl_pwm_clk newclk = periodcfg.clk_select; + + ret = clk_prepare_enable(fpc->clk[newclk]); + if (ret) + return ret; + clk_disable_unprepare(fpc->clk[oldclk]); + } + do_write_period = true; + } + + ftm_clear_write_protection(fpc); + + if (do_write_period) { + regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_CLK_MASK, + FTM_SC_CLK(periodcfg.clk_select)); + regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_PS_MASK, + periodcfg.clk_ps); + regmap_write(fpc->regmap, FTM_MOD, periodcfg.mod_period); + + fpc->period = periodcfg; + } + + duty = fsl_pwm_calculate_duty(fpc, newstate->duty_cycle); + + regmap_write(fpc->regmap, FTM_CSC(pwm->hwpwm), + FTM_CSC_MSB | FTM_CSC_ELSB); + regmap_write(fpc->regmap, FTM_CV(pwm->hwpwm), duty); + + reg_polarity = 0; + if (newstate->polarity == PWM_POLARITY_INVERSED) + reg_polarity = BIT(pwm->hwpwm); + + regmap_update_bits(fpc->regmap, FTM_POL, BIT(pwm->hwpwm), reg_polarity); + + ftm_set_write_protection(fpc); + + return 0; +} + +static int fsl_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *newstate) +{ + struct fsl_pwm_chip *fpc = to_fsl_chip(chip); + struct pwm_state *oldstate = &pwm->state; + int ret = 0; + + /* + * oldstate to newstate : action + * + * disabled to disabled : ignore + * enabled to disabled : disable + * enabled to enabled : update settings + * disabled to enabled : update settings + enable + */ + + mutex_lock(&fpc->lock); + + if (!newstate->enabled) { + if (oldstate->enabled) { + regmap_update_bits(fpc->regmap, FTM_OUTMASK, + BIT(pwm->hwpwm), BIT(pwm->hwpwm)); + clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]); + clk_disable_unprepare(fpc->clk[fpc->period.clk_select]); + } + + goto end_mutex; + } + + ret = fsl_pwm_apply_config(fpc, pwm, newstate); + if (ret) + goto end_mutex; + + /* check if need to enable */ + if (!oldstate->enabled) { + ret = clk_prepare_enable(fpc->clk[fpc->period.clk_select]); + if (ret) + goto end_mutex; + + ret = clk_prepare_enable(fpc->clk[FSL_PWM_CLK_CNTEN]); + if (ret) { + clk_disable_unprepare(fpc->clk[fpc->period.clk_select]); + goto end_mutex; + } + + regmap_update_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm), + 0); + } + +end_mutex: + mutex_unlock(&fpc->lock); + return ret; +} + +static const struct pwm_ops fsl_pwm_ops = { + .request = fsl_pwm_request, + .free = fsl_pwm_free, + .apply = fsl_pwm_apply, + .owner = THIS_MODULE, +}; + +static int fsl_pwm_init(struct fsl_pwm_chip *fpc) +{ + int ret; + + ret = clk_prepare_enable(fpc->ipg_clk); + if (ret) + return ret; + + regmap_write(fpc->regmap, FTM_CNTIN, 0x00); + regmap_write(fpc->regmap, FTM_OUTINIT, 0x00); + regmap_write(fpc->regmap, FTM_OUTMASK, 0xFF); + + clk_disable_unprepare(fpc->ipg_clk); + + return 0; +} + +static bool fsl_pwm_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FTM_FMS: + case FTM_MODE: + case FTM_CNT: + return true; + } + return false; +} + +static const struct regmap_config fsl_pwm_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = FTM_PWMLOAD, + .volatile_reg = fsl_pwm_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int fsl_pwm_probe(struct platform_device *pdev) +{ + struct fsl_pwm_chip *fpc; + struct resource *res; + void __iomem *base; + int ret; + + fpc = devm_kzalloc(&pdev->dev, sizeof(*fpc), GFP_KERNEL); + if (!fpc) + return -ENOMEM; + + mutex_init(&fpc->lock); + + fpc->soc = of_device_get_match_data(&pdev->dev); + fpc->chip.dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + fpc->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "ftm_sys", base, + &fsl_pwm_regmap_config); + if (IS_ERR(fpc->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(fpc->regmap); + } + + fpc->clk[FSL_PWM_CLK_SYS] = devm_clk_get(&pdev->dev, "ftm_sys"); + if (IS_ERR(fpc->clk[FSL_PWM_CLK_SYS])) { + dev_err(&pdev->dev, "failed to get \"ftm_sys\" clock\n"); + return PTR_ERR(fpc->clk[FSL_PWM_CLK_SYS]); + } + + fpc->clk[FSL_PWM_CLK_FIX] = devm_clk_get(fpc->chip.dev, "ftm_fix"); + if (IS_ERR(fpc->clk[FSL_PWM_CLK_FIX])) + return PTR_ERR(fpc->clk[FSL_PWM_CLK_FIX]); + + fpc->clk[FSL_PWM_CLK_EXT] = devm_clk_get(fpc->chip.dev, "ftm_ext"); + if (IS_ERR(fpc->clk[FSL_PWM_CLK_EXT])) + return PTR_ERR(fpc->clk[FSL_PWM_CLK_EXT]); + + fpc->clk[FSL_PWM_CLK_CNTEN] = + devm_clk_get(fpc->chip.dev, "ftm_cnt_clk_en"); + if (IS_ERR(fpc->clk[FSL_PWM_CLK_CNTEN])) + return PTR_ERR(fpc->clk[FSL_PWM_CLK_CNTEN]); + + /* + * ipg_clk is the interface clock for the IP. If not provided, use the + * ftm_sys clock as the default. + */ + fpc->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(fpc->ipg_clk)) + fpc->ipg_clk = fpc->clk[FSL_PWM_CLK_SYS]; + + + fpc->chip.ops = &fsl_pwm_ops; + fpc->chip.of_xlate = of_pwm_xlate_with_flags; + fpc->chip.of_pwm_n_cells = 3; + fpc->chip.base = -1; + fpc->chip.npwm = 8; + + ret = pwmchip_add(&fpc->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, fpc); + + return fsl_pwm_init(fpc); +} + +static int fsl_pwm_remove(struct platform_device *pdev) +{ + struct fsl_pwm_chip *fpc = platform_get_drvdata(pdev); + + return pwmchip_remove(&fpc->chip); +} + +#ifdef CONFIG_PM_SLEEP +static int fsl_pwm_suspend(struct device *dev) +{ + struct fsl_pwm_chip *fpc = dev_get_drvdata(dev); + int i; + + regcache_cache_only(fpc->regmap, true); + regcache_mark_dirty(fpc->regmap); + + for (i = 0; i < fpc->chip.npwm; i++) { + struct pwm_device *pwm = &fpc->chip.pwms[i]; + + if (!test_bit(PWMF_REQUESTED, &pwm->flags)) + continue; + + clk_disable_unprepare(fpc->ipg_clk); + + if (!pwm_is_enabled(pwm)) + continue; + + clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]); + clk_disable_unprepare(fpc->clk[fpc->period.clk_select]); + } + + return 0; +} + +static int fsl_pwm_resume(struct device *dev) +{ + struct fsl_pwm_chip *fpc = dev_get_drvdata(dev); + int i; + + for (i = 0; i < fpc->chip.npwm; i++) { + struct pwm_device *pwm = &fpc->chip.pwms[i]; + + if (!test_bit(PWMF_REQUESTED, &pwm->flags)) + continue; + + clk_prepare_enable(fpc->ipg_clk); + + if (!pwm_is_enabled(pwm)) + continue; + + clk_prepare_enable(fpc->clk[fpc->period.clk_select]); + clk_prepare_enable(fpc->clk[FSL_PWM_CLK_CNTEN]); + } + + /* restore all registers from cache */ + regcache_cache_only(fpc->regmap, false); + regcache_sync(fpc->regmap); + + return 0; +} +#endif + +static const struct dev_pm_ops fsl_pwm_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(fsl_pwm_suspend, fsl_pwm_resume) +}; + +static const struct fsl_ftm_soc vf610_ftm_pwm = { + .has_enable_bits = false, +}; + +static const struct fsl_ftm_soc imx8qm_ftm_pwm = { + .has_enable_bits = true, +}; + +static const struct of_device_id fsl_pwm_dt_ids[] = { + { .compatible = "fsl,vf610-ftm-pwm", .data = &vf610_ftm_pwm }, + { .compatible = "fsl,imx8qm-ftm-pwm", .data = &imx8qm_ftm_pwm }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_pwm_dt_ids); + +static struct platform_driver fsl_pwm_driver = { + .driver = { + .name = "fsl-ftm-pwm", + .of_match_table = fsl_pwm_dt_ids, + .pm = &fsl_pwm_pm_ops, + }, + .probe = fsl_pwm_probe, + .remove = fsl_pwm_remove, +}; +module_platform_driver(fsl_pwm_driver); + +MODULE_DESCRIPTION("Freescale FlexTimer Module PWM Driver"); +MODULE_AUTHOR("Xiubo Li <Li.Xiubo@freescale.com>"); +MODULE_ALIAS("platform:fsl-ftm-pwm"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-hibvt.c b/drivers/pwm/pwm-hibvt.c new file mode 100644 index 000000000..286e9b119 --- /dev/null +++ b/drivers/pwm/pwm-hibvt.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PWM Controller Driver for HiSilicon BVT SoCs + * + * Copyright (c) 2016 HiSilicon Technologies Co., Ltd. + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/reset.h> + +#define PWM_CFG0_ADDR(x) (((x) * 0x20) + 0x0) +#define PWM_CFG1_ADDR(x) (((x) * 0x20) + 0x4) +#define PWM_CFG2_ADDR(x) (((x) * 0x20) + 0x8) +#define PWM_CTRL_ADDR(x) (((x) * 0x20) + 0xC) + +#define PWM_ENABLE_SHIFT 0 +#define PWM_ENABLE_MASK BIT(0) + +#define PWM_POLARITY_SHIFT 1 +#define PWM_POLARITY_MASK BIT(1) + +#define PWM_KEEP_SHIFT 2 +#define PWM_KEEP_MASK BIT(2) + +#define PWM_PERIOD_MASK GENMASK(31, 0) +#define PWM_DUTY_MASK GENMASK(31, 0) + +struct hibvt_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + void __iomem *base; + struct reset_control *rstc; + const struct hibvt_pwm_soc *soc; +}; + +struct hibvt_pwm_soc { + u32 num_pwms; + bool quirk_force_enable; +}; + +static const struct hibvt_pwm_soc hi3516cv300_soc_info = { + .num_pwms = 4, +}; + +static const struct hibvt_pwm_soc hi3519v100_soc_info = { + .num_pwms = 8, +}; + +static const struct hibvt_pwm_soc hi3559v100_shub_soc_info = { + .num_pwms = 8, + .quirk_force_enable = true, +}; + +static const struct hibvt_pwm_soc hi3559v100_soc_info = { + .num_pwms = 2, + .quirk_force_enable = true, +}; + +static inline struct hibvt_pwm_chip *to_hibvt_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct hibvt_pwm_chip, chip); +} + +static void hibvt_pwm_set_bits(void __iomem *base, u32 offset, + u32 mask, u32 data) +{ + void __iomem *address = base + offset; + u32 value; + + value = readl(address); + value &= ~mask; + value |= (data & mask); + writel(value, address); +} + +static void hibvt_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct hibvt_pwm_chip *hi_pwm_chip = to_hibvt_pwm_chip(chip); + + hibvt_pwm_set_bits(hi_pwm_chip->base, PWM_CTRL_ADDR(pwm->hwpwm), + PWM_ENABLE_MASK, 0x1); +} + +static void hibvt_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct hibvt_pwm_chip *hi_pwm_chip = to_hibvt_pwm_chip(chip); + + hibvt_pwm_set_bits(hi_pwm_chip->base, PWM_CTRL_ADDR(pwm->hwpwm), + PWM_ENABLE_MASK, 0x0); +} + +static void hibvt_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_cycle_ns, int period_ns) +{ + struct hibvt_pwm_chip *hi_pwm_chip = to_hibvt_pwm_chip(chip); + u32 freq, period, duty; + + freq = div_u64(clk_get_rate(hi_pwm_chip->clk), 1000000); + + period = div_u64(freq * period_ns, 1000); + duty = div_u64(period * duty_cycle_ns, period_ns); + + hibvt_pwm_set_bits(hi_pwm_chip->base, PWM_CFG0_ADDR(pwm->hwpwm), + PWM_PERIOD_MASK, period); + + hibvt_pwm_set_bits(hi_pwm_chip->base, PWM_CFG1_ADDR(pwm->hwpwm), + PWM_DUTY_MASK, duty); +} + +static void hibvt_pwm_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct hibvt_pwm_chip *hi_pwm_chip = to_hibvt_pwm_chip(chip); + + if (polarity == PWM_POLARITY_INVERSED) + hibvt_pwm_set_bits(hi_pwm_chip->base, PWM_CTRL_ADDR(pwm->hwpwm), + PWM_POLARITY_MASK, (0x1 << PWM_POLARITY_SHIFT)); + else + hibvt_pwm_set_bits(hi_pwm_chip->base, PWM_CTRL_ADDR(pwm->hwpwm), + PWM_POLARITY_MASK, (0x0 << PWM_POLARITY_SHIFT)); +} + +static void hibvt_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct hibvt_pwm_chip *hi_pwm_chip = to_hibvt_pwm_chip(chip); + void __iomem *base; + u32 freq, value; + + freq = div_u64(clk_get_rate(hi_pwm_chip->clk), 1000000); + base = hi_pwm_chip->base; + + value = readl(base + PWM_CFG0_ADDR(pwm->hwpwm)); + state->period = div_u64(value * 1000, freq); + + value = readl(base + PWM_CFG1_ADDR(pwm->hwpwm)); + state->duty_cycle = div_u64(value * 1000, freq); + + value = readl(base + PWM_CTRL_ADDR(pwm->hwpwm)); + state->enabled = (PWM_ENABLE_MASK & value); + state->polarity = (PWM_POLARITY_MASK & value) ? PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL; +} + +static int hibvt_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct hibvt_pwm_chip *hi_pwm_chip = to_hibvt_pwm_chip(chip); + + if (state->polarity != pwm->state.polarity) + hibvt_pwm_set_polarity(chip, pwm, state->polarity); + + if (state->period != pwm->state.period || + state->duty_cycle != pwm->state.duty_cycle) { + hibvt_pwm_config(chip, pwm, state->duty_cycle, state->period); + + /* + * Some implementations require the PWM to be enabled twice + * each time the duty cycle is refreshed. + */ + if (hi_pwm_chip->soc->quirk_force_enable && state->enabled) + hibvt_pwm_enable(chip, pwm); + } + + if (state->enabled != pwm->state.enabled) { + if (state->enabled) + hibvt_pwm_enable(chip, pwm); + else + hibvt_pwm_disable(chip, pwm); + } + + return 0; +} + +static const struct pwm_ops hibvt_pwm_ops = { + .get_state = hibvt_pwm_get_state, + .apply = hibvt_pwm_apply, + + .owner = THIS_MODULE, +}; + +static int hibvt_pwm_probe(struct platform_device *pdev) +{ + const struct hibvt_pwm_soc *soc = + of_device_get_match_data(&pdev->dev); + struct hibvt_pwm_chip *pwm_chip; + struct resource *res; + int ret; + int i; + + pwm_chip = devm_kzalloc(&pdev->dev, sizeof(*pwm_chip), GFP_KERNEL); + if (pwm_chip == NULL) + return -ENOMEM; + + pwm_chip->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pwm_chip->clk)) { + dev_err(&pdev->dev, "getting clock failed with %ld\n", + PTR_ERR(pwm_chip->clk)); + return PTR_ERR(pwm_chip->clk); + } + + pwm_chip->chip.ops = &hibvt_pwm_ops; + pwm_chip->chip.dev = &pdev->dev; + pwm_chip->chip.base = -1; + pwm_chip->chip.npwm = soc->num_pwms; + pwm_chip->chip.of_xlate = of_pwm_xlate_with_flags; + pwm_chip->chip.of_pwm_n_cells = 3; + pwm_chip->soc = soc; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pwm_chip->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pwm_chip->base)) + return PTR_ERR(pwm_chip->base); + + ret = clk_prepare_enable(pwm_chip->clk); + if (ret < 0) + return ret; + + pwm_chip->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(pwm_chip->rstc)) { + clk_disable_unprepare(pwm_chip->clk); + return PTR_ERR(pwm_chip->rstc); + } + + reset_control_assert(pwm_chip->rstc); + msleep(30); + reset_control_deassert(pwm_chip->rstc); + + ret = pwmchip_add(&pwm_chip->chip); + if (ret < 0) { + clk_disable_unprepare(pwm_chip->clk); + return ret; + } + + for (i = 0; i < pwm_chip->chip.npwm; i++) { + hibvt_pwm_set_bits(pwm_chip->base, PWM_CTRL_ADDR(i), + PWM_KEEP_MASK, (0x1 << PWM_KEEP_SHIFT)); + } + + platform_set_drvdata(pdev, pwm_chip); + + return 0; +} + +static int hibvt_pwm_remove(struct platform_device *pdev) +{ + struct hibvt_pwm_chip *pwm_chip; + + pwm_chip = platform_get_drvdata(pdev); + + reset_control_assert(pwm_chip->rstc); + msleep(30); + reset_control_deassert(pwm_chip->rstc); + + clk_disable_unprepare(pwm_chip->clk); + + return pwmchip_remove(&pwm_chip->chip); +} + +static const struct of_device_id hibvt_pwm_of_match[] = { + { .compatible = "hisilicon,hi3516cv300-pwm", + .data = &hi3516cv300_soc_info }, + { .compatible = "hisilicon,hi3519v100-pwm", + .data = &hi3519v100_soc_info }, + { .compatible = "hisilicon,hi3559v100-shub-pwm", + .data = &hi3559v100_shub_soc_info }, + { .compatible = "hisilicon,hi3559v100-pwm", + .data = &hi3559v100_soc_info }, + { } +}; +MODULE_DEVICE_TABLE(of, hibvt_pwm_of_match); + +static struct platform_driver hibvt_pwm_driver = { + .driver = { + .name = "hibvt-pwm", + .of_match_table = hibvt_pwm_of_match, + }, + .probe = hibvt_pwm_probe, + .remove = hibvt_pwm_remove, +}; +module_platform_driver(hibvt_pwm_driver); + +MODULE_AUTHOR("Jian Yuan"); +MODULE_DESCRIPTION("HiSilicon BVT SoCs PWM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-img.c b/drivers/pwm/pwm-img.c new file mode 100644 index 000000000..37f9b6886 --- /dev/null +++ b/drivers/pwm/pwm-img.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Imagination Technologies Pulse Width Modulator driver + * + * Copyright (c) 2014-2015, Imagination Technologies + * + * Based on drivers/pwm/pwm-tegra.c, Copyright (c) 2010, NVIDIA Corporation + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/pwm.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +/* PWM registers */ +#define PWM_CTRL_CFG 0x0000 +#define PWM_CTRL_CFG_NO_SUB_DIV 0 +#define PWM_CTRL_CFG_SUB_DIV0 1 +#define PWM_CTRL_CFG_SUB_DIV1 2 +#define PWM_CTRL_CFG_SUB_DIV0_DIV1 3 +#define PWM_CTRL_CFG_DIV_SHIFT(ch) ((ch) * 2 + 4) +#define PWM_CTRL_CFG_DIV_MASK 0x3 + +#define PWM_CH_CFG(ch) (0x4 + (ch) * 4) +#define PWM_CH_CFG_TMBASE_SHIFT 0 +#define PWM_CH_CFG_DUTY_SHIFT 16 + +#define PERIP_PWM_PDM_CONTROL 0x0140 +#define PERIP_PWM_PDM_CONTROL_CH_MASK 0x1 +#define PERIP_PWM_PDM_CONTROL_CH_SHIFT(ch) ((ch) * 4) + +#define IMG_PWM_PM_TIMEOUT 1000 /* ms */ + +/* + * PWM period is specified with a timebase register, + * in number of step periods. The PWM duty cycle is also + * specified in step periods, in the [0, $timebase] range. + * In other words, the timebase imposes the duty cycle + * resolution. Therefore, let's constraint the timebase to + * a minimum value to allow a sane range of duty cycle values. + * Imposing a minimum timebase, will impose a maximum PWM frequency. + * + * The value chosen is completely arbitrary. + */ +#define MIN_TMBASE_STEPS 16 + +#define IMG_PWM_NPWM 4 + +struct img_pwm_soc_data { + u32 max_timebase; +}; + +struct img_pwm_chip { + struct device *dev; + struct pwm_chip chip; + struct clk *pwm_clk; + struct clk *sys_clk; + void __iomem *base; + struct regmap *periph_regs; + int max_period_ns; + int min_period_ns; + const struct img_pwm_soc_data *data; + u32 suspend_ctrl_cfg; + u32 suspend_ch_cfg[IMG_PWM_NPWM]; +}; + +static inline struct img_pwm_chip *to_img_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct img_pwm_chip, chip); +} + +static inline void img_pwm_writel(struct img_pwm_chip *chip, + u32 reg, u32 val) +{ + writel(val, chip->base + reg); +} + +static inline u32 img_pwm_readl(struct img_pwm_chip *chip, + u32 reg) +{ + return readl(chip->base + reg); +} + +static int img_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + u32 val, div, duty, timebase; + unsigned long mul, output_clk_hz, input_clk_hz; + struct img_pwm_chip *pwm_chip = to_img_pwm_chip(chip); + unsigned int max_timebase = pwm_chip->data->max_timebase; + int ret; + + if (period_ns < pwm_chip->min_period_ns || + period_ns > pwm_chip->max_period_ns) { + dev_err(chip->dev, "configured period not in range\n"); + return -ERANGE; + } + + input_clk_hz = clk_get_rate(pwm_chip->pwm_clk); + output_clk_hz = DIV_ROUND_UP(NSEC_PER_SEC, period_ns); + + mul = DIV_ROUND_UP(input_clk_hz, output_clk_hz); + if (mul <= max_timebase) { + div = PWM_CTRL_CFG_NO_SUB_DIV; + timebase = DIV_ROUND_UP(mul, 1); + } else if (mul <= max_timebase * 8) { + div = PWM_CTRL_CFG_SUB_DIV0; + timebase = DIV_ROUND_UP(mul, 8); + } else if (mul <= max_timebase * 64) { + div = PWM_CTRL_CFG_SUB_DIV1; + timebase = DIV_ROUND_UP(mul, 64); + } else if (mul <= max_timebase * 512) { + div = PWM_CTRL_CFG_SUB_DIV0_DIV1; + timebase = DIV_ROUND_UP(mul, 512); + } else { + dev_err(chip->dev, + "failed to configure timebase steps/divider value\n"); + return -EINVAL; + } + + duty = DIV_ROUND_UP(timebase * duty_ns, period_ns); + + ret = pm_runtime_get_sync(chip->dev); + if (ret < 0) { + pm_runtime_put_autosuspend(chip->dev); + return ret; + } + + val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); + val &= ~(PWM_CTRL_CFG_DIV_MASK << PWM_CTRL_CFG_DIV_SHIFT(pwm->hwpwm)); + val |= (div & PWM_CTRL_CFG_DIV_MASK) << + PWM_CTRL_CFG_DIV_SHIFT(pwm->hwpwm); + img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); + + val = (duty << PWM_CH_CFG_DUTY_SHIFT) | + (timebase << PWM_CH_CFG_TMBASE_SHIFT); + img_pwm_writel(pwm_chip, PWM_CH_CFG(pwm->hwpwm), val); + + pm_runtime_mark_last_busy(chip->dev); + pm_runtime_put_autosuspend(chip->dev); + + return 0; +} + +static int img_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + u32 val; + struct img_pwm_chip *pwm_chip = to_img_pwm_chip(chip); + int ret; + + ret = pm_runtime_resume_and_get(chip->dev); + if (ret < 0) + return ret; + + val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); + val |= BIT(pwm->hwpwm); + img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); + + regmap_update_bits(pwm_chip->periph_regs, PERIP_PWM_PDM_CONTROL, + PERIP_PWM_PDM_CONTROL_CH_MASK << + PERIP_PWM_PDM_CONTROL_CH_SHIFT(pwm->hwpwm), 0); + + return 0; +} + +static void img_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + u32 val; + struct img_pwm_chip *pwm_chip = to_img_pwm_chip(chip); + + val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); + val &= ~BIT(pwm->hwpwm); + img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); + + pm_runtime_mark_last_busy(chip->dev); + pm_runtime_put_autosuspend(chip->dev); +} + +static const struct pwm_ops img_pwm_ops = { + .config = img_pwm_config, + .enable = img_pwm_enable, + .disable = img_pwm_disable, + .owner = THIS_MODULE, +}; + +static const struct img_pwm_soc_data pistachio_pwm = { + .max_timebase = 255, +}; + +static const struct of_device_id img_pwm_of_match[] = { + { + .compatible = "img,pistachio-pwm", + .data = &pistachio_pwm, + }, + { } +}; +MODULE_DEVICE_TABLE(of, img_pwm_of_match); + +static int img_pwm_runtime_suspend(struct device *dev) +{ + struct img_pwm_chip *pwm_chip = dev_get_drvdata(dev); + + clk_disable_unprepare(pwm_chip->pwm_clk); + clk_disable_unprepare(pwm_chip->sys_clk); + + return 0; +} + +static int img_pwm_runtime_resume(struct device *dev) +{ + struct img_pwm_chip *pwm_chip = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(pwm_chip->sys_clk); + if (ret < 0) { + dev_err(dev, "could not prepare or enable sys clock\n"); + return ret; + } + + ret = clk_prepare_enable(pwm_chip->pwm_clk); + if (ret < 0) { + dev_err(dev, "could not prepare or enable pwm clock\n"); + clk_disable_unprepare(pwm_chip->sys_clk); + return ret; + } + + return 0; +} + +static int img_pwm_probe(struct platform_device *pdev) +{ + int ret; + u64 val; + unsigned long clk_rate; + struct resource *res; + struct img_pwm_chip *pwm; + const struct of_device_id *of_dev_id; + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) + return -ENOMEM; + + pwm->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pwm->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pwm->base)) + return PTR_ERR(pwm->base); + + of_dev_id = of_match_device(img_pwm_of_match, &pdev->dev); + if (!of_dev_id) + return -ENODEV; + pwm->data = of_dev_id->data; + + pwm->periph_regs = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "img,cr-periph"); + if (IS_ERR(pwm->periph_regs)) + return PTR_ERR(pwm->periph_regs); + + pwm->sys_clk = devm_clk_get(&pdev->dev, "sys"); + if (IS_ERR(pwm->sys_clk)) { + dev_err(&pdev->dev, "failed to get system clock\n"); + return PTR_ERR(pwm->sys_clk); + } + + pwm->pwm_clk = devm_clk_get(&pdev->dev, "pwm"); + if (IS_ERR(pwm->pwm_clk)) { + dev_err(&pdev->dev, "failed to get pwm clock\n"); + return PTR_ERR(pwm->pwm_clk); + } + + platform_set_drvdata(pdev, pwm); + + pm_runtime_set_autosuspend_delay(&pdev->dev, IMG_PWM_PM_TIMEOUT); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = img_pwm_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + clk_rate = clk_get_rate(pwm->pwm_clk); + if (!clk_rate) { + dev_err(&pdev->dev, "pwm clock has no frequency\n"); + ret = -EINVAL; + goto err_suspend; + } + + /* The maximum input clock divider is 512 */ + val = (u64)NSEC_PER_SEC * 512 * pwm->data->max_timebase; + do_div(val, clk_rate); + pwm->max_period_ns = val; + + val = (u64)NSEC_PER_SEC * MIN_TMBASE_STEPS; + do_div(val, clk_rate); + pwm->min_period_ns = val; + + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &img_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = IMG_PWM_NPWM; + + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret); + goto err_suspend; + } + + return 0; + +err_suspend: + if (!pm_runtime_enabled(&pdev->dev)) + img_pwm_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); + return ret; +} + +static int img_pwm_remove(struct platform_device *pdev) +{ + struct img_pwm_chip *pwm_chip = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + img_pwm_runtime_suspend(&pdev->dev); + + return pwmchip_remove(&pwm_chip->chip); +} + +#ifdef CONFIG_PM_SLEEP +static int img_pwm_suspend(struct device *dev) +{ + struct img_pwm_chip *pwm_chip = dev_get_drvdata(dev); + int i, ret; + + if (pm_runtime_status_suspended(dev)) { + ret = img_pwm_runtime_resume(dev); + if (ret) + return ret; + } + + for (i = 0; i < pwm_chip->chip.npwm; i++) + pwm_chip->suspend_ch_cfg[i] = img_pwm_readl(pwm_chip, + PWM_CH_CFG(i)); + + pwm_chip->suspend_ctrl_cfg = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); + + img_pwm_runtime_suspend(dev); + + return 0; +} + +static int img_pwm_resume(struct device *dev) +{ + struct img_pwm_chip *pwm_chip = dev_get_drvdata(dev); + int ret; + int i; + + ret = img_pwm_runtime_resume(dev); + if (ret) + return ret; + + for (i = 0; i < pwm_chip->chip.npwm; i++) + img_pwm_writel(pwm_chip, PWM_CH_CFG(i), + pwm_chip->suspend_ch_cfg[i]); + + img_pwm_writel(pwm_chip, PWM_CTRL_CFG, pwm_chip->suspend_ctrl_cfg); + + for (i = 0; i < pwm_chip->chip.npwm; i++) + if (pwm_chip->suspend_ctrl_cfg & BIT(i)) + regmap_update_bits(pwm_chip->periph_regs, + PERIP_PWM_PDM_CONTROL, + PERIP_PWM_PDM_CONTROL_CH_MASK << + PERIP_PWM_PDM_CONTROL_CH_SHIFT(i), + 0); + + if (pm_runtime_status_suspended(dev)) + img_pwm_runtime_suspend(dev); + + return 0; +} +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops img_pwm_pm_ops = { + SET_RUNTIME_PM_OPS(img_pwm_runtime_suspend, + img_pwm_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(img_pwm_suspend, img_pwm_resume) +}; + +static struct platform_driver img_pwm_driver = { + .driver = { + .name = "img-pwm", + .pm = &img_pwm_pm_ops, + .of_match_table = img_pwm_of_match, + }, + .probe = img_pwm_probe, + .remove = img_pwm_remove, +}; +module_platform_driver(img_pwm_driver); + +MODULE_AUTHOR("Sai Masarapu <Sai.Masarapu@imgtec.com>"); +MODULE_DESCRIPTION("Imagination Technologies PWM DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c new file mode 100644 index 000000000..871527b78 --- /dev/null +++ b/drivers/pwm/pwm-imx-tpm.c @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2018-2019 NXP. + * + * Limitations: + * - The TPM counter and period counter are shared between + * multiple channels, so all channels should use same period + * settings. + * - Changes to polarity cannot be latched at the time of the + * next period start. + * - Changing period and duty cycle together isn't atomic, + * with the wrong timing it might happen that a period is + * produced with old duty cycle but new period settings. + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +#define PWM_IMX_TPM_PARAM 0x4 +#define PWM_IMX_TPM_GLOBAL 0x8 +#define PWM_IMX_TPM_SC 0x10 +#define PWM_IMX_TPM_CNT 0x14 +#define PWM_IMX_TPM_MOD 0x18 +#define PWM_IMX_TPM_CnSC(n) (0x20 + (n) * 0x8) +#define PWM_IMX_TPM_CnV(n) (0x24 + (n) * 0x8) + +#define PWM_IMX_TPM_PARAM_CHAN GENMASK(7, 0) + +#define PWM_IMX_TPM_SC_PS GENMASK(2, 0) +#define PWM_IMX_TPM_SC_CMOD GENMASK(4, 3) +#define PWM_IMX_TPM_SC_CMOD_INC_EVERY_CLK FIELD_PREP(PWM_IMX_TPM_SC_CMOD, 1) +#define PWM_IMX_TPM_SC_CPWMS BIT(5) + +#define PWM_IMX_TPM_CnSC_CHF BIT(7) +#define PWM_IMX_TPM_CnSC_MSB BIT(5) +#define PWM_IMX_TPM_CnSC_MSA BIT(4) + +/* + * The reference manual describes this field as two separate bits. The + * semantic of the two bits isn't orthogonal though, so they are treated + * together as a 2-bit field here. + */ +#define PWM_IMX_TPM_CnSC_ELS GENMASK(3, 2) +#define PWM_IMX_TPM_CnSC_ELS_INVERSED FIELD_PREP(PWM_IMX_TPM_CnSC_ELS, 1) +#define PWM_IMX_TPM_CnSC_ELS_NORMAL FIELD_PREP(PWM_IMX_TPM_CnSC_ELS, 2) + + +#define PWM_IMX_TPM_MOD_WIDTH 16 +#define PWM_IMX_TPM_MOD_MOD GENMASK(PWM_IMX_TPM_MOD_WIDTH - 1, 0) + +struct imx_tpm_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + void __iomem *base; + struct mutex lock; + u32 user_count; + u32 enable_count; + u32 real_period; +}; + +struct imx_tpm_pwm_param { + u8 prescale; + u32 mod; + u32 val; +}; + +static inline struct imx_tpm_pwm_chip * +to_imx_tpm_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct imx_tpm_pwm_chip, chip); +} + +/* + * This function determines for a given pwm_state *state that a consumer + * might request the pwm_state *real_state that eventually is implemented + * by the hardware and the necessary register values (in *p) to achieve + * this. + */ +static int pwm_imx_tpm_round_state(struct pwm_chip *chip, + struct imx_tpm_pwm_param *p, + struct pwm_state *real_state, + const struct pwm_state *state) +{ + struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip); + u32 rate, prescale, period_count, clock_unit; + u64 tmp; + + rate = clk_get_rate(tpm->clk); + tmp = (u64)state->period * rate; + clock_unit = DIV_ROUND_CLOSEST_ULL(tmp, NSEC_PER_SEC); + if (clock_unit <= PWM_IMX_TPM_MOD_MOD) + prescale = 0; + else + prescale = ilog2(clock_unit) + 1 - PWM_IMX_TPM_MOD_WIDTH; + + if ((!FIELD_FIT(PWM_IMX_TPM_SC_PS, prescale))) + return -ERANGE; + p->prescale = prescale; + + period_count = (clock_unit + ((1 << prescale) >> 1)) >> prescale; + p->mod = period_count; + + /* calculate real period HW can support */ + tmp = (u64)period_count << prescale; + tmp *= NSEC_PER_SEC; + real_state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate); + + /* + * if eventually the PWM output is inactive, either + * duty cycle is 0 or status is disabled, need to + * make sure the output pin is inactive. + */ + if (!state->enabled) + real_state->duty_cycle = 0; + else + real_state->duty_cycle = state->duty_cycle; + + tmp = (u64)p->mod * real_state->duty_cycle; + p->val = DIV64_U64_ROUND_CLOSEST(tmp, real_state->period); + + real_state->polarity = state->polarity; + real_state->enabled = state->enabled; + + return 0; +} + +static void pwm_imx_tpm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip); + u32 rate, val, prescale; + u64 tmp; + + /* get period */ + state->period = tpm->real_period; + + /* get duty cycle */ + rate = clk_get_rate(tpm->clk); + val = readl(tpm->base + PWM_IMX_TPM_SC); + prescale = FIELD_GET(PWM_IMX_TPM_SC_PS, val); + tmp = readl(tpm->base + PWM_IMX_TPM_CnV(pwm->hwpwm)); + tmp = (tmp << prescale) * NSEC_PER_SEC; + state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate); + + /* get polarity */ + val = readl(tpm->base + PWM_IMX_TPM_CnSC(pwm->hwpwm)); + if ((val & PWM_IMX_TPM_CnSC_ELS) == PWM_IMX_TPM_CnSC_ELS_INVERSED) + state->polarity = PWM_POLARITY_INVERSED; + else + /* + * Assume reserved values (2b00 and 2b11) to yield + * normal polarity. + */ + state->polarity = PWM_POLARITY_NORMAL; + + /* get channel status */ + state->enabled = FIELD_GET(PWM_IMX_TPM_CnSC_ELS, val) ? true : false; +} + +/* this function is supposed to be called with mutex hold */ +static int pwm_imx_tpm_apply_hw(struct pwm_chip *chip, + struct imx_tpm_pwm_param *p, + struct pwm_state *state, + struct pwm_device *pwm) +{ + struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip); + bool period_update = false; + bool duty_update = false; + u32 val, cmod, cur_prescale; + unsigned long timeout; + struct pwm_state c; + + if (state->period != tpm->real_period) { + /* + * TPM counter is shared by multiple channels, so + * prescale and period can NOT be modified when + * there are multiple channels in use with different + * period settings. + */ + if (tpm->user_count > 1) + return -EBUSY; + + val = readl(tpm->base + PWM_IMX_TPM_SC); + cmod = FIELD_GET(PWM_IMX_TPM_SC_CMOD, val); + cur_prescale = FIELD_GET(PWM_IMX_TPM_SC_PS, val); + if (cmod && cur_prescale != p->prescale) + return -EBUSY; + + /* set TPM counter prescale */ + val &= ~PWM_IMX_TPM_SC_PS; + val |= FIELD_PREP(PWM_IMX_TPM_SC_PS, p->prescale); + writel(val, tpm->base + PWM_IMX_TPM_SC); + + /* + * set period count: + * if the PWM is disabled (CMOD[1:0] = 2b00), then MOD register + * is updated when MOD register is written. + * + * if the PWM is enabled (CMOD[1:0] ≠2b00), the period length + * is latched into hardware when the next period starts. + */ + writel(p->mod, tpm->base + PWM_IMX_TPM_MOD); + tpm->real_period = state->period; + period_update = true; + } + + pwm_imx_tpm_get_state(chip, pwm, &c); + + /* polarity is NOT allowed to be changed if PWM is active */ + if (c.enabled && c.polarity != state->polarity) + return -EBUSY; + + if (state->duty_cycle != c.duty_cycle) { + /* + * set channel value: + * if the PWM is disabled (CMOD[1:0] = 2b00), then CnV register + * is updated when CnV register is written. + * + * if the PWM is enabled (CMOD[1:0] ≠2b00), the duty length + * is latched into hardware when the next period starts. + */ + writel(p->val, tpm->base + PWM_IMX_TPM_CnV(pwm->hwpwm)); + duty_update = true; + } + + /* make sure MOD & CnV registers are updated */ + if (period_update || duty_update) { + timeout = jiffies + msecs_to_jiffies(tpm->real_period / + NSEC_PER_MSEC + 1); + while (readl(tpm->base + PWM_IMX_TPM_MOD) != p->mod + || readl(tpm->base + PWM_IMX_TPM_CnV(pwm->hwpwm)) + != p->val) { + if (time_after(jiffies, timeout)) + return -ETIME; + cpu_relax(); + } + } + + /* + * polarity settings will enabled/disable output status + * immediately, so if the channel is disabled, need to + * make sure MSA/MSB/ELS are set to 0 which means channel + * disabled. + */ + val = readl(tpm->base + PWM_IMX_TPM_CnSC(pwm->hwpwm)); + val &= ~(PWM_IMX_TPM_CnSC_ELS | PWM_IMX_TPM_CnSC_MSA | + PWM_IMX_TPM_CnSC_MSB); + if (state->enabled) { + /* + * set polarity (for edge-aligned PWM modes) + * + * ELS[1:0] = 2b10 yields normal polarity behaviour, + * ELS[1:0] = 2b01 yields inversed polarity. + * The other values are reserved. + */ + val |= PWM_IMX_TPM_CnSC_MSB; + val |= (state->polarity == PWM_POLARITY_NORMAL) ? + PWM_IMX_TPM_CnSC_ELS_NORMAL : + PWM_IMX_TPM_CnSC_ELS_INVERSED; + } + writel(val, tpm->base + PWM_IMX_TPM_CnSC(pwm->hwpwm)); + + /* control the counter status */ + if (state->enabled != c.enabled) { + val = readl(tpm->base + PWM_IMX_TPM_SC); + if (state->enabled) { + if (++tpm->enable_count == 1) + val |= PWM_IMX_TPM_SC_CMOD_INC_EVERY_CLK; + } else { + if (--tpm->enable_count == 0) + val &= ~PWM_IMX_TPM_SC_CMOD; + } + writel(val, tpm->base + PWM_IMX_TPM_SC); + } + + return 0; +} + +static int pwm_imx_tpm_apply(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip); + struct imx_tpm_pwm_param param; + struct pwm_state real_state; + int ret; + + ret = pwm_imx_tpm_round_state(chip, ¶m, &real_state, state); + if (ret) + return ret; + + mutex_lock(&tpm->lock); + ret = pwm_imx_tpm_apply_hw(chip, ¶m, &real_state, pwm); + mutex_unlock(&tpm->lock); + + return ret; +} + +static int pwm_imx_tpm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip); + + mutex_lock(&tpm->lock); + tpm->user_count++; + mutex_unlock(&tpm->lock); + + return 0; +} + +static void pwm_imx_tpm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip); + + mutex_lock(&tpm->lock); + tpm->user_count--; + mutex_unlock(&tpm->lock); +} + +static const struct pwm_ops imx_tpm_pwm_ops = { + .request = pwm_imx_tpm_request, + .free = pwm_imx_tpm_free, + .get_state = pwm_imx_tpm_get_state, + .apply = pwm_imx_tpm_apply, + .owner = THIS_MODULE, +}; + +static int pwm_imx_tpm_probe(struct platform_device *pdev) +{ + struct imx_tpm_pwm_chip *tpm; + int ret; + u32 val; + + tpm = devm_kzalloc(&pdev->dev, sizeof(*tpm), GFP_KERNEL); + if (!tpm) + return -ENOMEM; + + platform_set_drvdata(pdev, tpm); + + tpm->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(tpm->base)) + return PTR_ERR(tpm->base); + + tpm->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(tpm->clk)) { + ret = PTR_ERR(tpm->clk); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "failed to get PWM clock: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(tpm->clk); + if (ret) { + dev_err(&pdev->dev, + "failed to prepare or enable clock: %d\n", ret); + return ret; + } + + tpm->chip.dev = &pdev->dev; + tpm->chip.ops = &imx_tpm_pwm_ops; + tpm->chip.base = -1; + tpm->chip.of_xlate = of_pwm_xlate_with_flags; + tpm->chip.of_pwm_n_cells = 3; + + /* get number of channels */ + val = readl(tpm->base + PWM_IMX_TPM_PARAM); + tpm->chip.npwm = FIELD_GET(PWM_IMX_TPM_PARAM_CHAN, val); + + mutex_init(&tpm->lock); + + ret = pwmchip_add(&tpm->chip); + if (ret) { + dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); + clk_disable_unprepare(tpm->clk); + } + + return ret; +} + +static int pwm_imx_tpm_remove(struct platform_device *pdev) +{ + struct imx_tpm_pwm_chip *tpm = platform_get_drvdata(pdev); + int ret = pwmchip_remove(&tpm->chip); + + clk_disable_unprepare(tpm->clk); + + return ret; +} + +static int __maybe_unused pwm_imx_tpm_suspend(struct device *dev) +{ + struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev); + + if (tpm->enable_count > 0) + return -EBUSY; + + /* + * Force 'real_period' to be zero to force period update code + * can be executed after system resume back, since suspend causes + * the period related registers to become their reset values. + */ + tpm->real_period = 0; + + clk_disable_unprepare(tpm->clk); + + return 0; +} + +static int __maybe_unused 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); + if (ret) + dev_err(dev, + "failed to prepare or enable clock: %d\n", + ret); + + return ret; +} + +static SIMPLE_DEV_PM_OPS(imx_tpm_pwm_pm, + pwm_imx_tpm_suspend, pwm_imx_tpm_resume); + +static const struct of_device_id imx_tpm_pwm_dt_ids[] = { + { .compatible = "fsl,imx7ulp-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_tpm_pwm_dt_ids); + +static struct platform_driver imx_tpm_pwm_driver = { + .driver = { + .name = "imx7ulp-tpm-pwm", + .of_match_table = imx_tpm_pwm_dt_ids, + .pm = &imx_tpm_pwm_pm, + }, + .probe = pwm_imx_tpm_probe, + .remove = pwm_imx_tpm_remove, +}; +module_platform_driver(imx_tpm_pwm_driver); + +MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>"); +MODULE_DESCRIPTION("i.MX TPM PWM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-imx1.c b/drivers/pwm/pwm-imx1.c new file mode 100644 index 000000000..c17652c40 --- /dev/null +++ b/drivers/pwm/pwm-imx1.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * simple driver for PWM (Pulse Width Modulator) controller + * + * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com> + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +#define MX1_PWMC 0x00 /* PWM Control Register */ +#define MX1_PWMS 0x04 /* PWM Sample Register */ +#define MX1_PWMP 0x08 /* PWM Period Register */ + +#define MX1_PWMC_EN BIT(4) + +struct pwm_imx1_chip { + struct clk *clk_ipg; + struct clk *clk_per; + void __iomem *mmio_base; + struct pwm_chip chip; +}; + +#define to_pwm_imx1_chip(chip) container_of(chip, struct pwm_imx1_chip, chip) + +static int pwm_imx1_clk_prepare_enable(struct pwm_chip *chip) +{ + struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); + 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_imx1_clk_disable_unprepare(struct pwm_chip *chip) +{ + struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); + + clk_disable_unprepare(imx->clk_per); + clk_disable_unprepare(imx->clk_ipg); +} + +static int pwm_imx1_config(struct pwm_chip *chip, + struct pwm_device *pwm, int duty_ns, int period_ns) +{ + struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); + u32 max, p; + + /* + * The PWM subsystem allows for exact frequencies. However, + * I cannot connect a scope on my device to the PWM line and + * thus cannot provide the program the PWM controller + * exactly. Instead, I'm relying on the fact that the + * Bootloader (u-boot or WinCE+haret) has programmed the PWM + * function group already. So I'll just modify the PWM sample + * register to follow the ratio of duty_ns vs. period_ns + * accordingly. + * + * This is good enough for programming the brightness of + * the LCD backlight. + * + * The real implementation would divide PERCLK[0] first by + * both the prescaler (/1 .. /128) and then by CLKSEL + * (/2 .. /16). + */ + max = readl(imx->mmio_base + MX1_PWMP); + p = max * duty_ns / period_ns; + + writel(max - p, imx->mmio_base + MX1_PWMS); + + return 0; +} + +static int pwm_imx1_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); + u32 value; + int ret; + + ret = pwm_imx1_clk_prepare_enable(chip); + if (ret < 0) + return ret; + + value = readl(imx->mmio_base + MX1_PWMC); + value |= MX1_PWMC_EN; + writel(value, imx->mmio_base + MX1_PWMC); + + return 0; +} + +static void pwm_imx1_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); + u32 value; + + value = readl(imx->mmio_base + MX1_PWMC); + value &= ~MX1_PWMC_EN; + writel(value, imx->mmio_base + MX1_PWMC); + + pwm_imx1_clk_disable_unprepare(chip); +} + +static const struct pwm_ops pwm_imx1_ops = { + .enable = pwm_imx1_enable, + .disable = pwm_imx1_disable, + .config = pwm_imx1_config, + .owner = THIS_MODULE, +}; + +static const struct of_device_id pwm_imx1_dt_ids[] = { + { .compatible = "fsl,imx1-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pwm_imx1_dt_ids); + +static int pwm_imx1_probe(struct platform_device *pdev) +{ + struct pwm_imx1_chip *imx; + struct resource *r; + + imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); + if (!imx) + return -ENOMEM; + + platform_set_drvdata(pdev, imx); + + imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(imx->clk_ipg)) { + dev_err(&pdev->dev, "getting ipg clock failed with %ld\n", + PTR_ERR(imx->clk_ipg)); + return PTR_ERR(imx->clk_ipg); + } + + imx->clk_per = devm_clk_get(&pdev->dev, "per"); + if (IS_ERR(imx->clk_per)) { + int ret = PTR_ERR(imx->clk_per); + + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "failed to get peripheral clock: %d\n", + ret); + + return ret; + } + + imx->chip.ops = &pwm_imx1_ops; + imx->chip.dev = &pdev->dev; + imx->chip.base = -1; + imx->chip.npwm = 1; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + imx->mmio_base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(imx->mmio_base)) + return PTR_ERR(imx->mmio_base); + + return pwmchip_add(&imx->chip); +} + +static int pwm_imx1_remove(struct platform_device *pdev) +{ + struct pwm_imx1_chip *imx = platform_get_drvdata(pdev); + + return pwmchip_remove(&imx->chip); +} + +static struct platform_driver pwm_imx1_driver = { + .driver = { + .name = "pwm-imx1", + .of_match_table = pwm_imx1_dt_ids, + }, + .probe = pwm_imx1_probe, + .remove = pwm_imx1_remove, +}; +module_platform_driver(pwm_imx1_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 new file mode 100644 index 000000000..86bcafd23 --- /dev/null +++ b/drivers/pwm/pwm-imx27.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * simple driver for PWM (Pulse Width Modulator) controller + * + * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com> + * + * Limitations: + * - When disabled the output is driven to 0 independent of the configured + * polarity. + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +#define MX3_PWMCR 0x00 /* PWM Control Register */ +#define MX3_PWMSR 0x04 /* PWM Status Register */ +#define MX3_PWMSAR 0x0C /* PWM Sample Register */ +#define MX3_PWMPR 0x10 /* PWM Period Register */ + +#define MX3_PWMCR_FWM GENMASK(27, 26) +#define MX3_PWMCR_STOPEN BIT(25) +#define MX3_PWMCR_DOZEN BIT(24) +#define MX3_PWMCR_WAITEN BIT(23) +#define MX3_PWMCR_DBGEN BIT(22) +#define MX3_PWMCR_BCTR BIT(21) +#define MX3_PWMCR_HCTR BIT(20) + +#define MX3_PWMCR_POUTC GENMASK(19, 18) +#define MX3_PWMCR_POUTC_NORMAL 0 +#define MX3_PWMCR_POUTC_INVERTED 1 +#define MX3_PWMCR_POUTC_OFF 2 + +#define MX3_PWMCR_CLKSRC GENMASK(17, 16) +#define MX3_PWMCR_CLKSRC_OFF 0 +#define MX3_PWMCR_CLKSRC_IPG 1 +#define MX3_PWMCR_CLKSRC_IPG_HIGH 2 +#define MX3_PWMCR_CLKSRC_IPG_32K 3 + +#define MX3_PWMCR_PRESCALER GENMASK(15, 4) + +#define MX3_PWMCR_SWR BIT(3) + +#define MX3_PWMCR_REPEAT GENMASK(2, 1) +#define MX3_PWMCR_REPEAT_1X 0 +#define MX3_PWMCR_REPEAT_2X 1 +#define MX3_PWMCR_REPEAT_4X 2 +#define MX3_PWMCR_REPEAT_8X 3 + +#define MX3_PWMCR_EN BIT(0) + +#define MX3_PWMSR_FWE BIT(6) +#define MX3_PWMSR_CMP BIT(5) +#define MX3_PWMSR_ROV BIT(4) +#define MX3_PWMSR_FE BIT(3) + +#define MX3_PWMSR_FIFOAV GENMASK(2, 0) +#define MX3_PWMSR_FIFOAV_EMPTY 0 +#define MX3_PWMSR_FIFOAV_1WORD 1 +#define MX3_PWMSR_FIFOAV_2WORDS 2 +#define MX3_PWMSR_FIFOAV_3WORDS 3 +#define MX3_PWMSR_FIFOAV_4WORDS 4 + +#define MX3_PWMCR_PRESCALER_SET(x) FIELD_PREP(MX3_PWMCR_PRESCALER, (x) - 1) +#define MX3_PWMCR_PRESCALER_GET(x) (FIELD_GET(MX3_PWMCR_PRESCALER, \ + (x)) + 1) + +#define MX3_PWM_SWR_LOOP 5 + +/* PWMPR register value of 0xffff has the same effect as 0xfffe */ +#define MX3_PWMPR_MAX 0xfffe + +struct pwm_imx27_chip { + struct clk *clk_ipg; + struct clk *clk_per; + void __iomem *mmio_base; + struct pwm_chip chip; + + /* + * The driver cannot read the current duty cycle from the hardware if + * the hardware is disabled. Cache the last programmed duty cycle + * value to return in that case. + */ + unsigned int duty_cycle; +}; + +#define to_pwm_imx27_chip(chip) container_of(chip, struct pwm_imx27_chip, 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 void pwm_imx27_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, struct pwm_state *state) +{ + struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip); + u32 period, prescaler, pwm_clk, val; + u64 tmp; + int ret; + + ret = pwm_imx27_clk_prepare_enable(imx); + if (ret < 0) + return; + + val = readl(imx->mmio_base + MX3_PWMCR); + + if (val & MX3_PWMCR_EN) + state->enabled = true; + else + state->enabled = false; + + switch (FIELD_GET(MX3_PWMCR_POUTC, val)) { + case MX3_PWMCR_POUTC_NORMAL: + state->polarity = PWM_POLARITY_NORMAL; + break; + case MX3_PWMCR_POUTC_INVERTED: + state->polarity = PWM_POLARITY_INVERSED; + break; + default: + dev_warn(chip->dev, "can't set polarity, output disconnected"); + } + + prescaler = MX3_PWMCR_PRESCALER_GET(val); + pwm_clk = clk_get_rate(imx->clk_per); + val = readl(imx->mmio_base + MX3_PWMPR); + period = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val; + + /* PWMOUT (Hz) = PWMCLK / (PWMPR + 2) */ + tmp = NSEC_PER_SEC * (u64)(period + 2) * prescaler; + state->period = DIV_ROUND_UP_ULL(tmp, pwm_clk); + + /* + * PWMSAR can be read only if PWM is enabled. If the PWM is disabled, + * use the cached value. + */ + if (state->enabled) + val = readl(imx->mmio_base + MX3_PWMSAR); + else + val = imx->duty_cycle; + + tmp = NSEC_PER_SEC * (u64)(val) * prescaler; + state->duty_cycle = DIV_ROUND_UP_ULL(tmp, pwm_clk); + + pwm_imx27_clk_disable_unprepare(imx); +} + +static void pwm_imx27_sw_reset(struct pwm_chip *chip) +{ + struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip); + struct device *dev = chip->dev; + int wait_count = 0; + u32 cr; + + writel(MX3_PWMCR_SWR, imx->mmio_base + MX3_PWMCR); + do { + usleep_range(200, 1000); + cr = readl(imx->mmio_base + MX3_PWMCR); + } while ((cr & MX3_PWMCR_SWR) && + (wait_count++ < MX3_PWM_SWR_LOOP)); + + if (cr & MX3_PWMCR_SWR) + dev_warn(dev, "software reset timeout\n"); +} + +static void pwm_imx27_wait_fifo_slot(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip); + struct device *dev = chip->dev; + unsigned int period_ms; + int fifoav; + u32 sr; + + sr = readl(imx->mmio_base + MX3_PWMSR); + fifoav = FIELD_GET(MX3_PWMSR_FIFOAV, sr); + if (fifoav == MX3_PWMSR_FIFOAV_4WORDS) { + period_ms = DIV_ROUND_UP_ULL(pwm_get_period(pwm), + NSEC_PER_MSEC); + msleep(period_ms); + + sr = readl(imx->mmio_base + MX3_PWMSR); + if (fifoav == FIELD_GET(MX3_PWMSR_FIFOAV, sr)) + dev_warn(dev, "there is no free FIFO slot\n"); + } +} + +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; + struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip); + struct pwm_state cstate; + unsigned long long c; + unsigned long long clkrate; + int ret; + u32 cr; + + pwm_get_state(pwm, &cstate); + + clkrate = clk_get_rate(imx->clk_per); + c = clkrate * state->period; + + do_div(c, NSEC_PER_SEC); + period_cycles = c; + + prescale = period_cycles / 0x10000 + 1; + + period_cycles /= prescale; + c = clkrate * state->duty_cycle; + do_div(c, NSEC_PER_SEC); + duty_cycles = c; + duty_cycles /= prescale; + + /* + * according to imx pwm RM, the real period value should be PERIOD + * value in PWMPR plus 2. + */ + if (period_cycles > 2) + period_cycles -= 2; + else + period_cycles = 0; + + /* + * Wait for a free FIFO slot if the PWM is already enabled, and flush + * the FIFO if the PWM was disabled and is about to be enabled. + */ + if (cstate.enabled) { + pwm_imx27_wait_fifo_slot(chip, pwm); + } else { + ret = pwm_imx27_clk_prepare_enable(imx); + if (ret) + return ret; + + pwm_imx27_sw_reset(chip); + } + + writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); + writel(period_cycles, imx->mmio_base + MX3_PWMPR); + + /* + * Store the duty cycle for future reference in cases where the + * MX3_PWMSAR register can't be read (i.e. when the PWM is disabled). + */ + imx->duty_cycle = duty_cycles; + + cr = MX3_PWMCR_PRESCALER_SET(prescale) | + MX3_PWMCR_STOPEN | MX3_PWMCR_DOZEN | MX3_PWMCR_WAITEN | + FIELD_PREP(MX3_PWMCR_CLKSRC, MX3_PWMCR_CLKSRC_IPG_HIGH) | + MX3_PWMCR_DBGEN; + + if (state->polarity == PWM_POLARITY_INVERSED) + cr |= FIELD_PREP(MX3_PWMCR_POUTC, + MX3_PWMCR_POUTC_INVERTED); + + if (state->enabled) + cr |= MX3_PWMCR_EN; + + writel(cr, imx->mmio_base + MX3_PWMCR); + + if (!state->enabled) + pwm_imx27_clk_disable_unprepare(imx); + + return 0; +} + +static const struct pwm_ops pwm_imx27_ops = { + .apply = pwm_imx27_apply, + .get_state = pwm_imx27_get_state, + .owner = THIS_MODULE, +}; + +static const struct of_device_id pwm_imx27_dt_ids[] = { + { .compatible = "fsl,imx27-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pwm_imx27_dt_ids); + +static int pwm_imx27_probe(struct platform_device *pdev) +{ + struct pwm_imx27_chip *imx; + int ret; + u32 pwmcr; + + imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); + if (imx == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, imx); + + imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(imx->clk_ipg)) { + int ret = PTR_ERR(imx->clk_ipg); + + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "getting ipg clock failed with %d\n", + ret); + return ret; + } + + imx->clk_per = devm_clk_get(&pdev->dev, "per"); + if (IS_ERR(imx->clk_per)) { + int ret = PTR_ERR(imx->clk_per); + + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "failed to get peripheral clock: %d\n", + ret); + + return ret; + } + + imx->chip.ops = &pwm_imx27_ops; + imx->chip.dev = &pdev->dev; + imx->chip.base = -1; + imx->chip.npwm = 1; + + imx->chip.of_xlate = of_pwm_xlate_with_flags; + imx->chip.of_pwm_n_cells = 3; + + imx->mmio_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(imx->mmio_base)) + return PTR_ERR(imx->mmio_base); + + ret = pwm_imx27_clk_prepare_enable(imx); + 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); + + return pwmchip_add(&imx->chip); +} + +static int pwm_imx27_remove(struct platform_device *pdev) +{ + struct pwm_imx27_chip *imx; + + imx = platform_get_drvdata(pdev); + + return pwmchip_remove(&imx->chip); +} + +static struct platform_driver imx_pwm_driver = { + .driver = { + .name = "pwm-imx27", + .of_match_table = pwm_imx27_dt_ids, + }, + .probe = pwm_imx27_probe, + .remove = pwm_imx27_remove, +}; +module_platform_driver(imx_pwm_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); diff --git a/drivers/pwm/pwm-iqs620a.c b/drivers/pwm/pwm-iqs620a.c new file mode 100644 index 000000000..a2aef006c --- /dev/null +++ b/drivers/pwm/pwm-iqs620a.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620A PWM Generator + * + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com> + * + * Limitations: + * - The period is fixed to 1 ms and is generated continuously despite changes + * to the duty cycle or enable/disable state. + * - Changes to the duty cycle or enable/disable state take effect immediately + * and may result in a glitch during the period in which the change is made. + * - The device cannot generate a 0% duty cycle. For duty cycles below 1 / 256 + * ms, the output is disabled and relies upon an external pull-down resistor + * to hold the GPIO3/LTX pin low. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mfd/iqs62x.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define IQS620_PWR_SETTINGS 0xd2 +#define IQS620_PWR_SETTINGS_PWM_OUT BIT(7) + +#define IQS620_PWM_DUTY_CYCLE 0xd8 + +#define IQS620_PWM_PERIOD_NS 1000000 + +struct iqs620_pwm_private { + struct iqs62x_core *iqs62x; + struct pwm_chip chip; + struct notifier_block notifier; + struct mutex lock; + bool out_en; + u8 duty_val; +}; + +static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct iqs620_pwm_private *iqs620_pwm; + struct iqs62x_core *iqs62x; + unsigned int duty_cycle; + unsigned int duty_scale; + int ret; + + if (state->polarity != PWM_POLARITY_NORMAL) + return -ENOTSUPP; + + if (state->period < IQS620_PWM_PERIOD_NS) + return -EINVAL; + + iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip); + iqs62x = iqs620_pwm->iqs62x; + + /* + * The duty cycle generated by the device is calculated as follows: + * + * duty_cycle = (IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms + * + * ...where IQS620_PWM_DUTY_CYCLE is a register value between 0 and 255 + * (inclusive). Therefore the lowest duty cycle the device can generate + * while the output is enabled is 1 / 256 ms. + * + * For lower duty cycles (e.g. 0), the PWM output is simply disabled to + * allow an external pull-down resistor to hold the GPIO3/LTX pin low. + */ + duty_cycle = min_t(u64, state->duty_cycle, IQS620_PWM_PERIOD_NS); + duty_scale = duty_cycle * 256 / IQS620_PWM_PERIOD_NS; + + mutex_lock(&iqs620_pwm->lock); + + if (!state->enabled || !duty_scale) { + ret = regmap_update_bits(iqs62x->regmap, IQS620_PWR_SETTINGS, + IQS620_PWR_SETTINGS_PWM_OUT, 0); + if (ret) + goto err_mutex; + } + + if (duty_scale) { + u8 duty_val = duty_scale - 1; + + ret = regmap_write(iqs62x->regmap, IQS620_PWM_DUTY_CYCLE, + duty_val); + if (ret) + goto err_mutex; + + iqs620_pwm->duty_val = duty_val; + } + + if (state->enabled && duty_scale) { + ret = regmap_update_bits(iqs62x->regmap, IQS620_PWR_SETTINGS, + IQS620_PWR_SETTINGS_PWM_OUT, 0xff); + if (ret) + goto err_mutex; + } + + iqs620_pwm->out_en = state->enabled; + +err_mutex: + mutex_unlock(&iqs620_pwm->lock); + + return ret; +} + +static void iqs620_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct iqs620_pwm_private *iqs620_pwm; + + iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip); + + mutex_lock(&iqs620_pwm->lock); + + /* + * Since the device cannot generate a 0% duty cycle, requests to do so + * cause subsequent calls to iqs620_pwm_get_state to report the output + * as disabled with duty cycle equal to that which was in use prior to + * the request. This is not ideal, but is the best compromise based on + * the capabilities of the device. + */ + state->enabled = iqs620_pwm->out_en; + state->duty_cycle = DIV_ROUND_UP((iqs620_pwm->duty_val + 1) * + IQS620_PWM_PERIOD_NS, 256); + + mutex_unlock(&iqs620_pwm->lock); + + state->period = IQS620_PWM_PERIOD_NS; + state->polarity = PWM_POLARITY_NORMAL; +} + +static int iqs620_pwm_notifier(struct notifier_block *notifier, + unsigned long event_flags, void *context) +{ + struct iqs620_pwm_private *iqs620_pwm; + struct iqs62x_core *iqs62x; + int ret; + + if (!(event_flags & BIT(IQS62X_EVENT_SYS_RESET))) + return NOTIFY_DONE; + + iqs620_pwm = container_of(notifier, struct iqs620_pwm_private, + notifier); + iqs62x = iqs620_pwm->iqs62x; + + mutex_lock(&iqs620_pwm->lock); + + /* + * The parent MFD driver already prints an error message in the event + * of a device reset, so nothing else is printed here unless there is + * an additional failure. + */ + ret = regmap_write(iqs62x->regmap, IQS620_PWM_DUTY_CYCLE, + iqs620_pwm->duty_val); + if (ret) + goto err_mutex; + + ret = regmap_update_bits(iqs62x->regmap, IQS620_PWR_SETTINGS, + IQS620_PWR_SETTINGS_PWM_OUT, + iqs620_pwm->out_en ? 0xff : 0); + +err_mutex: + mutex_unlock(&iqs620_pwm->lock); + + if (ret) { + dev_err(iqs620_pwm->chip.dev, + "Failed to re-initialize device: %d\n", ret); + return NOTIFY_BAD; + } + + return NOTIFY_OK; +} + +static const struct pwm_ops iqs620_pwm_ops = { + .apply = iqs620_pwm_apply, + .get_state = iqs620_pwm_get_state, + .owner = THIS_MODULE, +}; + +static void iqs620_pwm_notifier_unregister(void *context) +{ + struct iqs620_pwm_private *iqs620_pwm = context; + int ret; + + ret = blocking_notifier_chain_unregister(&iqs620_pwm->iqs62x->nh, + &iqs620_pwm->notifier); + if (ret) + dev_err(iqs620_pwm->chip.dev, + "Failed to unregister notifier: %d\n", ret); +} + +static int iqs620_pwm_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iqs620_pwm_private *iqs620_pwm; + unsigned int val; + int ret; + + iqs620_pwm = devm_kzalloc(&pdev->dev, sizeof(*iqs620_pwm), GFP_KERNEL); + if (!iqs620_pwm) + return -ENOMEM; + + platform_set_drvdata(pdev, iqs620_pwm); + iqs620_pwm->iqs62x = iqs62x; + + ret = regmap_read(iqs62x->regmap, IQS620_PWR_SETTINGS, &val); + if (ret) + return ret; + iqs620_pwm->out_en = val & IQS620_PWR_SETTINGS_PWM_OUT; + + ret = regmap_read(iqs62x->regmap, IQS620_PWM_DUTY_CYCLE, &val); + if (ret) + return ret; + iqs620_pwm->duty_val = val; + + iqs620_pwm->chip.dev = &pdev->dev; + iqs620_pwm->chip.ops = &iqs620_pwm_ops; + iqs620_pwm->chip.base = -1; + iqs620_pwm->chip.npwm = 1; + + mutex_init(&iqs620_pwm->lock); + + iqs620_pwm->notifier.notifier_call = iqs620_pwm_notifier; + ret = blocking_notifier_chain_register(&iqs620_pwm->iqs62x->nh, + &iqs620_pwm->notifier); + if (ret) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(&pdev->dev, + iqs620_pwm_notifier_unregister, + iqs620_pwm); + if (ret) + return ret; + + ret = pwmchip_add(&iqs620_pwm->chip); + if (ret) + dev_err(&pdev->dev, "Failed to add device: %d\n", ret); + + return ret; +} + +static int iqs620_pwm_remove(struct platform_device *pdev) +{ + struct iqs620_pwm_private *iqs620_pwm = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&iqs620_pwm->chip); + if (ret) + dev_err(&pdev->dev, "Failed to remove device: %d\n", ret); + + return ret; +} + +static struct platform_driver iqs620_pwm_platform_driver = { + .driver = { + .name = "iqs620a-pwm", + }, + .probe = iqs620_pwm_probe, + .remove = iqs620_pwm_remove, +}; +module_platform_driver(iqs620_pwm_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>"); +MODULE_DESCRIPTION("Azoteq IQS620A PWM Generator"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:iqs620a-pwm"); diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c new file mode 100644 index 000000000..db951a37c --- /dev/null +++ b/drivers/pwm/pwm-jz4740.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> + * JZ4740 platform PWM support + * + * Limitations: + * - The .apply callback doesn't complete the currently running period before + * reconfiguring the hardware. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/kernel.h> +#include <linux/mfd/ingenic-tcu.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> + +struct soc_info { + unsigned int num_pwms; +}; + +struct jz4740_pwm_chip { + struct pwm_chip chip; + struct regmap *map; +}; + +static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip) +{ + return container_of(chip, struct jz4740_pwm_chip, chip); +} + +static bool jz4740_pwm_can_use_chn(struct jz4740_pwm_chip *jz, + unsigned int channel) +{ + /* Enable all TCU channels for PWM use by default except channels 0/1 */ + u32 pwm_channels_mask = GENMASK(jz->chip.npwm - 1, 2); + + device_property_read_u32(jz->chip.dev->parent, + "ingenic,pwm-channels-mask", + &pwm_channels_mask); + + return !!(pwm_channels_mask & BIT(channel)); +} + +static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct jz4740_pwm_chip *jz = to_jz4740(chip); + struct clk *clk; + char name[16]; + int err; + + if (!jz4740_pwm_can_use_chn(jz, pwm->hwpwm)) + return -EBUSY; + + snprintf(name, sizeof(name), "timer%u", pwm->hwpwm); + + clk = clk_get(chip->dev, name); + if (IS_ERR(clk)) { + dev_err(chip->dev, "error %pe: Failed to get clock\n", clk); + return PTR_ERR(clk); + } + + err = clk_prepare_enable(clk); + if (err < 0) { + clk_put(clk); + return err; + } + + pwm_set_chip_data(pwm, clk); + + return 0; +} + +static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct clk *clk = pwm_get_chip_data(pwm); + + clk_disable_unprepare(clk); + clk_put(clk); +} + +static int jz4740_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct jz4740_pwm_chip *jz = to_jz4740(chip); + + /* Enable PWM output */ + regmap_update_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), + TCU_TCSR_PWM_EN, TCU_TCSR_PWM_EN); + + /* Start counter */ + regmap_write(jz->map, TCU_REG_TESR, BIT(pwm->hwpwm)); + + return 0; +} + +static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct jz4740_pwm_chip *jz = to_jz4740(chip); + + /* + * Set duty > period. This trick allows the TCU channels in TCU2 mode to + * properly return to their init level. + */ + regmap_write(jz->map, TCU_REG_TDHRc(pwm->hwpwm), 0xffff); + regmap_write(jz->map, TCU_REG_TDFRc(pwm->hwpwm), 0x0); + + /* + * Disable PWM output. + * In TCU2 mode (channel 1/2 on JZ4750+), this must be done before the + * counter is stopped, while in TCU1 mode the order does not matter. + */ + regmap_update_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), + TCU_TCSR_PWM_EN, 0); + + /* Stop counter */ + regmap_write(jz->map, TCU_REG_TECR, BIT(pwm->hwpwm)); +} + +static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip); + unsigned long long tmp = 0xffffull * NSEC_PER_SEC; + struct clk *clk = pwm_get_chip_data(pwm); + unsigned long period, duty; + long rate; + int err; + + /* + * Limit the clock to a maximum rate that still gives us a period value + * which fits in 16 bits. + */ + do_div(tmp, state->period); + + /* + * /!\ IMPORTANT NOTE: + * ------------------- + * This code relies on the fact that clk_round_rate() will always round + * down, which is not a valid assumption given by the clk API, but only + * happens to be true with the clk drivers used for Ingenic SoCs. + * + * Right now, there is no alternative as the clk API does not have a + * round-down function (and won't have one for a while), but if it ever + * comes to light, a round-down function should be used instead. + */ + rate = clk_round_rate(clk, tmp); + if (rate < 0) { + dev_err(chip->dev, "Unable to round rate: %ld", rate); + return rate; + } + + /* Calculate period value */ + tmp = (unsigned long long)rate * state->period; + do_div(tmp, NSEC_PER_SEC); + period = tmp; + + /* Calculate duty value */ + tmp = (unsigned long long)rate * state->duty_cycle; + do_div(tmp, NSEC_PER_SEC); + duty = tmp; + + if (duty >= period) + duty = period - 1; + + jz4740_pwm_disable(chip, pwm); + + err = clk_set_rate(clk, rate); + if (err) { + dev_err(chip->dev, "Unable to set rate: %d", err); + return err; + } + + /* Reset counter to 0 */ + regmap_write(jz4740->map, TCU_REG_TCNTc(pwm->hwpwm), 0); + + /* Set duty */ + regmap_write(jz4740->map, TCU_REG_TDHRc(pwm->hwpwm), duty); + + /* Set period */ + regmap_write(jz4740->map, TCU_REG_TDFRc(pwm->hwpwm), period); + + /* Set abrupt shutdown */ + regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), + TCU_TCSR_PWM_SD, TCU_TCSR_PWM_SD); + + /* + * Set polarity. + * + * The PWM starts in inactive state until the internal timer reaches the + * duty value, then becomes active until the timer reaches the period + * value. In theory, we should then use (period - duty) as the real duty + * value, as a high duty value would otherwise result in the PWM pin + * being inactive most of the time. + * + * Here, we don't do that, and instead invert the polarity of the PWM + * when it is active. This trick makes the PWM start with its active + * state instead of its inactive state. + */ + if ((state->polarity == PWM_POLARITY_NORMAL) ^ state->enabled) + regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), + TCU_TCSR_PWM_INITL_HIGH, 0); + else + regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm), + TCU_TCSR_PWM_INITL_HIGH, + TCU_TCSR_PWM_INITL_HIGH); + + if (state->enabled) + jz4740_pwm_enable(chip, pwm); + + return 0; +} + +static const struct pwm_ops jz4740_pwm_ops = { + .request = jz4740_pwm_request, + .free = jz4740_pwm_free, + .apply = jz4740_pwm_apply, + .owner = THIS_MODULE, +}; + +static int jz4740_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct jz4740_pwm_chip *jz4740; + const struct soc_info *info; + + info = device_get_match_data(dev); + if (!info) + return -EINVAL; + + jz4740 = devm_kzalloc(dev, sizeof(*jz4740), GFP_KERNEL); + if (!jz4740) + return -ENOMEM; + + jz4740->map = device_node_to_regmap(dev->parent->of_node); + if (IS_ERR(jz4740->map)) { + dev_err(dev, "regmap not found: %ld\n", PTR_ERR(jz4740->map)); + return PTR_ERR(jz4740->map); + } + + jz4740->chip.dev = dev; + jz4740->chip.ops = &jz4740_pwm_ops; + jz4740->chip.npwm = info->num_pwms; + jz4740->chip.base = -1; + jz4740->chip.of_xlate = of_pwm_xlate_with_flags; + jz4740->chip.of_pwm_n_cells = 3; + + platform_set_drvdata(pdev, jz4740); + + return pwmchip_add(&jz4740->chip); +} + +static int jz4740_pwm_remove(struct platform_device *pdev) +{ + struct jz4740_pwm_chip *jz4740 = platform_get_drvdata(pdev); + + return pwmchip_remove(&jz4740->chip); +} + +static const struct soc_info __maybe_unused jz4740_soc_info = { + .num_pwms = 8, +}; + +static const struct soc_info __maybe_unused jz4725b_soc_info = { + .num_pwms = 6, +}; + +#ifdef CONFIG_OF +static const struct of_device_id jz4740_pwm_dt_ids[] = { + { .compatible = "ingenic,jz4740-pwm", .data = &jz4740_soc_info }, + { .compatible = "ingenic,jz4725b-pwm", .data = &jz4725b_soc_info }, + {}, +}; +MODULE_DEVICE_TABLE(of, jz4740_pwm_dt_ids); +#endif + +static struct platform_driver jz4740_pwm_driver = { + .driver = { + .name = "jz4740-pwm", + .of_match_table = of_match_ptr(jz4740_pwm_dt_ids), + }, + .probe = jz4740_pwm_probe, + .remove = jz4740_pwm_remove, +}; +module_platform_driver(jz4740_pwm_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Ingenic JZ4740 PWM driver"); +MODULE_ALIAS("platform:jz4740-pwm"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-lp3943.c b/drivers/pwm/pwm-lp3943.c new file mode 100644 index 000000000..05e4120fd --- /dev/null +++ b/drivers/pwm/pwm-lp3943.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI/National Semiconductor LP3943 PWM driver + * + * Copyright 2013 Texas Instruments + * + * Author: Milo Kim <milo.kim@ti.com> + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/mfd/lp3943.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +#define LP3943_MAX_DUTY 255 +#define LP3943_MIN_PERIOD 6250 +#define LP3943_MAX_PERIOD 1600000 + +struct lp3943_pwm { + struct pwm_chip chip; + struct lp3943 *lp3943; + struct lp3943_platform_data *pdata; +}; + +static inline struct lp3943_pwm *to_lp3943_pwm(struct pwm_chip *_chip) +{ + return container_of(_chip, struct lp3943_pwm, chip); +} + +static struct lp3943_pwm_map * +lp3943_pwm_request_map(struct lp3943_pwm *lp3943_pwm, int hwpwm) +{ + struct lp3943_platform_data *pdata = lp3943_pwm->pdata; + struct lp3943 *lp3943 = lp3943_pwm->lp3943; + struct lp3943_pwm_map *pwm_map; + int i, offset; + + pwm_map = kzalloc(sizeof(*pwm_map), GFP_KERNEL); + if (!pwm_map) + return ERR_PTR(-ENOMEM); + + pwm_map->output = pdata->pwms[hwpwm]->output; + pwm_map->num_outputs = pdata->pwms[hwpwm]->num_outputs; + + for (i = 0; i < pwm_map->num_outputs; i++) { + offset = pwm_map->output[i]; + + /* Return an error if the pin is already assigned */ + if (test_and_set_bit(offset, &lp3943->pin_used)) { + kfree(pwm_map); + return ERR_PTR(-EBUSY); + } + } + + return pwm_map; +} + +static int lp3943_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip); + struct lp3943_pwm_map *pwm_map; + + pwm_map = lp3943_pwm_request_map(lp3943_pwm, pwm->hwpwm); + if (IS_ERR(pwm_map)) + return PTR_ERR(pwm_map); + + return pwm_set_chip_data(pwm, pwm_map); +} + +static void lp3943_pwm_free_map(struct lp3943_pwm *lp3943_pwm, + struct lp3943_pwm_map *pwm_map) +{ + struct lp3943 *lp3943 = lp3943_pwm->lp3943; + int i, offset; + + for (i = 0; i < pwm_map->num_outputs; i++) { + offset = pwm_map->output[i]; + clear_bit(offset, &lp3943->pin_used); + } + + kfree(pwm_map); +} + +static void lp3943_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip); + struct lp3943_pwm_map *pwm_map = pwm_get_chip_data(pwm); + + lp3943_pwm_free_map(lp3943_pwm, pwm_map); +} + +static int lp3943_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip); + struct lp3943 *lp3943 = lp3943_pwm->lp3943; + u8 val, reg_duty, reg_prescale; + int err; + + /* + * How to configure the LP3943 PWMs + * + * 1) Period = 6250 ~ 1600000 + * 2) Prescale = period / 6250 -1 + * 3) Duty = input duty + * + * Prescale and duty are register values + */ + + if (pwm->hwpwm == 0) { + reg_prescale = LP3943_REG_PRESCALE0; + reg_duty = LP3943_REG_PWM0; + } else { + reg_prescale = LP3943_REG_PRESCALE1; + reg_duty = LP3943_REG_PWM1; + } + + period_ns = clamp(period_ns, LP3943_MIN_PERIOD, LP3943_MAX_PERIOD); + val = (u8)(period_ns / LP3943_MIN_PERIOD - 1); + + err = lp3943_write_byte(lp3943, reg_prescale, val); + if (err) + return err; + + duty_ns = min(duty_ns, period_ns); + val = (u8)(duty_ns * LP3943_MAX_DUTY / period_ns); + + return lp3943_write_byte(lp3943, reg_duty, val); +} + +static int lp3943_pwm_set_mode(struct lp3943_pwm *lp3943_pwm, + struct lp3943_pwm_map *pwm_map, + u8 val) +{ + struct lp3943 *lp3943 = lp3943_pwm->lp3943; + const struct lp3943_reg_cfg *mux = lp3943->mux_cfg; + int i, index, err; + + for (i = 0; i < pwm_map->num_outputs; i++) { + index = pwm_map->output[i]; + err = lp3943_update_bits(lp3943, mux[index].reg, + mux[index].mask, + val << mux[index].shift); + if (err) + return err; + } + + return 0; +} + +static int lp3943_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip); + struct lp3943_pwm_map *pwm_map = pwm_get_chip_data(pwm); + u8 val; + + if (pwm->hwpwm == 0) + val = LP3943_DIM_PWM0; + else + val = LP3943_DIM_PWM1; + + /* + * Each PWM generator is set to control any of outputs of LP3943. + * To enable/disable the PWM, these output pins should be configured. + */ + + return lp3943_pwm_set_mode(lp3943_pwm, pwm_map, val); +} + +static void lp3943_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip); + struct lp3943_pwm_map *pwm_map = pwm_get_chip_data(pwm); + + /* + * LP3943 outputs are open-drain, so the pin should be configured + * when the PWM is disabled. + */ + + lp3943_pwm_set_mode(lp3943_pwm, pwm_map, LP3943_GPIO_OUT_HIGH); +} + +static const struct pwm_ops lp3943_pwm_ops = { + .request = lp3943_pwm_request, + .free = lp3943_pwm_free, + .config = lp3943_pwm_config, + .enable = lp3943_pwm_enable, + .disable = lp3943_pwm_disable, + .owner = THIS_MODULE, +}; + +static int lp3943_pwm_parse_dt(struct device *dev, + struct lp3943_pwm *lp3943_pwm) +{ + static const char * const name[] = { "ti,pwm0", "ti,pwm1", }; + struct device_node *node = dev->of_node; + 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; + + if (!node) + return -EINVAL; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + /* + * Read the output map configuration from the device tree. + * Each of the two PWM generators can drive zero or more outputs. + */ + + 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) + continue; + + output = devm_kcalloc(dev, num_outputs, sizeof(*output), + GFP_KERNEL); + if (!output) + return -ENOMEM; + + err = of_property_read_u32_array(node, name[i], output, + num_outputs); + if (err) + return err; + + pwm_map = devm_kzalloc(dev, sizeof(*pwm_map), GFP_KERNEL); + if (!pwm_map) + return -ENOMEM; + + pwm_map->output = output; + pwm_map->num_outputs = num_outputs; + pdata->pwms[i] = pwm_map; + + count++; + } + + if (count == 0) + return -ENODATA; + + lp3943_pwm->pdata = pdata; + return 0; +} + +static int lp3943_pwm_probe(struct platform_device *pdev) +{ + struct lp3943 *lp3943 = dev_get_drvdata(pdev->dev.parent); + struct lp3943_pwm *lp3943_pwm; + int ret; + + lp3943_pwm = devm_kzalloc(&pdev->dev, sizeof(*lp3943_pwm), GFP_KERNEL); + if (!lp3943_pwm) + return -ENOMEM; + + lp3943_pwm->pdata = lp3943->pdata; + if (!lp3943_pwm->pdata) { + if (IS_ENABLED(CONFIG_OF)) + ret = lp3943_pwm_parse_dt(&pdev->dev, lp3943_pwm); + else + ret = -ENODEV; + + if (ret) + return ret; + } + + lp3943_pwm->lp3943 = lp3943; + lp3943_pwm->chip.dev = &pdev->dev; + lp3943_pwm->chip.ops = &lp3943_pwm_ops; + lp3943_pwm->chip.npwm = LP3943_NUM_PWMS; + lp3943_pwm->chip.base = -1; + + platform_set_drvdata(pdev, lp3943_pwm); + + return pwmchip_add(&lp3943_pwm->chip); +} + +static int lp3943_pwm_remove(struct platform_device *pdev) +{ + struct lp3943_pwm *lp3943_pwm = platform_get_drvdata(pdev); + + return pwmchip_remove(&lp3943_pwm->chip); +} + +#ifdef CONFIG_OF +static const struct of_device_id lp3943_pwm_of_match[] = { + { .compatible = "ti,lp3943-pwm", }, + { } +}; +MODULE_DEVICE_TABLE(of, lp3943_pwm_of_match); +#endif + +static struct platform_driver lp3943_pwm_driver = { + .probe = lp3943_pwm_probe, + .remove = lp3943_pwm_remove, + .driver = { + .name = "lp3943-pwm", + .of_match_table = of_match_ptr(lp3943_pwm_of_match), + }, +}; +module_platform_driver(lp3943_pwm_driver); + +MODULE_DESCRIPTION("LP3943 PWM driver"); +MODULE_ALIAS("platform:lp3943-pwm"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-lpc18xx-sct.c b/drivers/pwm/pwm-lpc18xx-sct.c new file mode 100644 index 000000000..f32a9e069 --- /dev/null +++ b/drivers/pwm/pwm-lpc18xx-sct.c @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP LPC18xx State Configurable Timer - Pulse Width Modulator driver + * + * Copyright (c) 2015 Ariel D'Alessandro <ariel@vanguardiasur.com> + * + * Notes + * ===== + * NXP LPC18xx provides a State Configurable Timer (SCT) which can be configured + * as a Pulse Width Modulator. + * + * SCT supports 16 outputs, 16 events and 16 registers. Each event will be + * triggered when its related register matches the SCT counter value, and it + * will set or clear a selected output. + * + * One of the events is preselected to generate the period, thus the maximum + * number of simultaneous channels is limited to 15. Notice that period is + * global to all the channels, thus PWM driver will refuse setting different + * values to it, unless there's only one channel requested. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +/* LPC18xx SCT registers */ +#define LPC18XX_PWM_CONFIG 0x000 +#define LPC18XX_PWM_CONFIG_UNIFY BIT(0) +#define LPC18XX_PWM_CONFIG_NORELOAD BIT(7) + +#define LPC18XX_PWM_CTRL 0x004 +#define LPC18XX_PWM_CTRL_HALT BIT(2) +#define LPC18XX_PWM_BIDIR BIT(4) +#define LPC18XX_PWM_PRE_SHIFT 5 +#define LPC18XX_PWM_PRE_MASK (0xff << LPC18XX_PWM_PRE_SHIFT) +#define LPC18XX_PWM_PRE(x) (x << LPC18XX_PWM_PRE_SHIFT) + +#define LPC18XX_PWM_LIMIT 0x008 + +#define LPC18XX_PWM_RES_BASE 0x058 +#define LPC18XX_PWM_RES_SHIFT(_ch) (_ch * 2) +#define LPC18XX_PWM_RES(_ch, _action) (_action << LPC18XX_PWM_RES_SHIFT(_ch)) +#define LPC18XX_PWM_RES_MASK(_ch) (0x3 << LPC18XX_PWM_RES_SHIFT(_ch)) + +#define LPC18XX_PWM_MATCH_BASE 0x100 +#define LPC18XX_PWM_MATCH(_ch) (LPC18XX_PWM_MATCH_BASE + _ch * 4) + +#define LPC18XX_PWM_MATCHREL_BASE 0x200 +#define LPC18XX_PWM_MATCHREL(_ch) (LPC18XX_PWM_MATCHREL_BASE + _ch * 4) + +#define LPC18XX_PWM_EVSTATEMSK_BASE 0x300 +#define LPC18XX_PWM_EVSTATEMSK(_ch) (LPC18XX_PWM_EVSTATEMSK_BASE + _ch * 8) +#define LPC18XX_PWM_EVSTATEMSK_ALL 0xffffffff + +#define LPC18XX_PWM_EVCTRL_BASE 0x304 +#define LPC18XX_PWM_EVCTRL(_ev) (LPC18XX_PWM_EVCTRL_BASE + _ev * 8) + +#define LPC18XX_PWM_EVCTRL_MATCH(_ch) _ch + +#define LPC18XX_PWM_EVCTRL_COMB_SHIFT 12 +#define LPC18XX_PWM_EVCTRL_COMB_MATCH (0x1 << LPC18XX_PWM_EVCTRL_COMB_SHIFT) + +#define LPC18XX_PWM_OUTPUTSET_BASE 0x500 +#define LPC18XX_PWM_OUTPUTSET(_ch) (LPC18XX_PWM_OUTPUTSET_BASE + _ch * 8) + +#define LPC18XX_PWM_OUTPUTCL_BASE 0x504 +#define LPC18XX_PWM_OUTPUTCL(_ch) (LPC18XX_PWM_OUTPUTCL_BASE + _ch * 8) + +/* LPC18xx SCT unified counter */ +#define LPC18XX_PWM_TIMER_MAX 0xffffffff + +/* LPC18xx SCT events */ +#define LPC18XX_PWM_EVENT_PERIOD 0 +#define LPC18XX_PWM_EVENT_MAX 16 + +/* SCT conflict resolution */ +enum lpc18xx_pwm_res_action { + LPC18XX_PWM_RES_NONE, + LPC18XX_PWM_RES_SET, + LPC18XX_PWM_RES_CLEAR, + LPC18XX_PWM_RES_TOGGLE, +}; + +struct lpc18xx_pwm_data { + unsigned int duty_event; +}; + +struct lpc18xx_pwm_chip { + struct device *dev; + struct pwm_chip chip; + void __iomem *base; + struct clk *pwm_clk; + unsigned long clk_rate; + unsigned int period_ns; + unsigned int min_period_ns; + unsigned int max_period_ns; + unsigned int period_event; + unsigned long event_map; + struct mutex res_lock; + struct mutex period_lock; +}; + +static inline struct lpc18xx_pwm_chip * +to_lpc18xx_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct lpc18xx_pwm_chip, chip); +} + +static inline void lpc18xx_pwm_writel(struct lpc18xx_pwm_chip *lpc18xx_pwm, + u32 reg, u32 val) +{ + writel(val, lpc18xx_pwm->base + reg); +} + +static inline u32 lpc18xx_pwm_readl(struct lpc18xx_pwm_chip *lpc18xx_pwm, + u32 reg) +{ + return readl(lpc18xx_pwm->base + reg); +} + +static void lpc18xx_pwm_set_conflict_res(struct lpc18xx_pwm_chip *lpc18xx_pwm, + struct pwm_device *pwm, + enum lpc18xx_pwm_res_action action) +{ + u32 val; + + mutex_lock(&lpc18xx_pwm->res_lock); + + /* + * Simultaneous set and clear may happen on an output, that is the case + * when duty_ns == period_ns. LPC18xx SCT allows to set a conflict + * resolution action to be taken in such a case. + */ + val = lpc18xx_pwm_readl(lpc18xx_pwm, LPC18XX_PWM_RES_BASE); + val &= ~LPC18XX_PWM_RES_MASK(pwm->hwpwm); + val |= LPC18XX_PWM_RES(pwm->hwpwm, action); + lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_RES_BASE, val); + + mutex_unlock(&lpc18xx_pwm->res_lock); +} + +static void lpc18xx_pwm_config_period(struct pwm_chip *chip, int period_ns) +{ + struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip); + u64 val; + + val = (u64)period_ns * lpc18xx_pwm->clk_rate; + do_div(val, NSEC_PER_SEC); + + lpc18xx_pwm_writel(lpc18xx_pwm, + LPC18XX_PWM_MATCH(lpc18xx_pwm->period_event), + (u32)val - 1); + + lpc18xx_pwm_writel(lpc18xx_pwm, + LPC18XX_PWM_MATCHREL(lpc18xx_pwm->period_event), + (u32)val - 1); +} + +static void lpc18xx_pwm_config_duty(struct pwm_chip *chip, + struct pwm_device *pwm, int duty_ns) +{ + struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip); + struct lpc18xx_pwm_data *lpc18xx_data = pwm_get_chip_data(pwm); + u64 val; + + val = (u64)duty_ns * lpc18xx_pwm->clk_rate; + do_div(val, NSEC_PER_SEC); + + lpc18xx_pwm_writel(lpc18xx_pwm, + LPC18XX_PWM_MATCH(lpc18xx_data->duty_event), + (u32)val); + + lpc18xx_pwm_writel(lpc18xx_pwm, + LPC18XX_PWM_MATCHREL(lpc18xx_data->duty_event), + (u32)val); +} + +static int lpc18xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip); + int requested_events, i; + + if (period_ns < lpc18xx_pwm->min_period_ns || + period_ns > lpc18xx_pwm->max_period_ns) { + dev_err(chip->dev, "period %d not in range\n", period_ns); + return -ERANGE; + } + + mutex_lock(&lpc18xx_pwm->period_lock); + + requested_events = bitmap_weight(&lpc18xx_pwm->event_map, + LPC18XX_PWM_EVENT_MAX); + + /* + * The PWM supports only a single period for all PWM channels. + * Once the period is set, it can only be changed if no more than one + * channel is requested at that moment. + */ + if (requested_events > 2 && lpc18xx_pwm->period_ns != period_ns && + lpc18xx_pwm->period_ns) { + dev_err(chip->dev, "conflicting period requested for PWM %u\n", + pwm->hwpwm); + mutex_unlock(&lpc18xx_pwm->period_lock); + return -EBUSY; + } + + if ((requested_events <= 2 && lpc18xx_pwm->period_ns != period_ns) || + !lpc18xx_pwm->period_ns) { + lpc18xx_pwm->period_ns = period_ns; + for (i = 0; i < chip->npwm; i++) + pwm_set_period(&chip->pwms[i], period_ns); + lpc18xx_pwm_config_period(chip, period_ns); + } + + mutex_unlock(&lpc18xx_pwm->period_lock); + + lpc18xx_pwm_config_duty(chip, pwm, duty_ns); + + return 0; +} + +static int lpc18xx_pwm_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + return 0; +} + +static int lpc18xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip); + struct lpc18xx_pwm_data *lpc18xx_data = pwm_get_chip_data(pwm); + enum lpc18xx_pwm_res_action res_action; + unsigned int set_event, clear_event; + + lpc18xx_pwm_writel(lpc18xx_pwm, + LPC18XX_PWM_EVCTRL(lpc18xx_data->duty_event), + LPC18XX_PWM_EVCTRL_MATCH(lpc18xx_data->duty_event) | + LPC18XX_PWM_EVCTRL_COMB_MATCH); + + lpc18xx_pwm_writel(lpc18xx_pwm, + LPC18XX_PWM_EVSTATEMSK(lpc18xx_data->duty_event), + LPC18XX_PWM_EVSTATEMSK_ALL); + + if (pwm_get_polarity(pwm) == PWM_POLARITY_NORMAL) { + set_event = lpc18xx_pwm->period_event; + clear_event = lpc18xx_data->duty_event; + res_action = LPC18XX_PWM_RES_SET; + } else { + set_event = lpc18xx_data->duty_event; + clear_event = lpc18xx_pwm->period_event; + res_action = LPC18XX_PWM_RES_CLEAR; + } + + lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_OUTPUTSET(pwm->hwpwm), + BIT(set_event)); + lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_OUTPUTCL(pwm->hwpwm), + BIT(clear_event)); + lpc18xx_pwm_set_conflict_res(lpc18xx_pwm, pwm, res_action); + + return 0; +} + +static void lpc18xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip); + struct lpc18xx_pwm_data *lpc18xx_data = pwm_get_chip_data(pwm); + + lpc18xx_pwm_writel(lpc18xx_pwm, + LPC18XX_PWM_EVCTRL(lpc18xx_data->duty_event), 0); + lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_OUTPUTSET(pwm->hwpwm), 0); + lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_OUTPUTCL(pwm->hwpwm), 0); +} + +static int lpc18xx_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip); + struct lpc18xx_pwm_data *lpc18xx_data = pwm_get_chip_data(pwm); + unsigned long event; + + event = find_first_zero_bit(&lpc18xx_pwm->event_map, + LPC18XX_PWM_EVENT_MAX); + + if (event >= LPC18XX_PWM_EVENT_MAX) { + dev_err(lpc18xx_pwm->dev, + "maximum number of simultaneous channels reached\n"); + return -EBUSY; + }; + + set_bit(event, &lpc18xx_pwm->event_map); + lpc18xx_data->duty_event = event; + + return 0; +} + +static void lpc18xx_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip); + struct lpc18xx_pwm_data *lpc18xx_data = pwm_get_chip_data(pwm); + + clear_bit(lpc18xx_data->duty_event, &lpc18xx_pwm->event_map); +} + +static const struct pwm_ops lpc18xx_pwm_ops = { + .config = lpc18xx_pwm_config, + .set_polarity = lpc18xx_pwm_set_polarity, + .enable = lpc18xx_pwm_enable, + .disable = lpc18xx_pwm_disable, + .request = lpc18xx_pwm_request, + .free = lpc18xx_pwm_free, + .owner = THIS_MODULE, +}; + +static const struct of_device_id lpc18xx_pwm_of_match[] = { + { .compatible = "nxp,lpc1850-sct-pwm" }, + {} +}; +MODULE_DEVICE_TABLE(of, lpc18xx_pwm_of_match); + +static int lpc18xx_pwm_probe(struct platform_device *pdev) +{ + struct lpc18xx_pwm_chip *lpc18xx_pwm; + struct pwm_device *pwm; + int ret, i; + u64 val; + + lpc18xx_pwm = devm_kzalloc(&pdev->dev, sizeof(*lpc18xx_pwm), + GFP_KERNEL); + if (!lpc18xx_pwm) + return -ENOMEM; + + lpc18xx_pwm->dev = &pdev->dev; + + lpc18xx_pwm->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(lpc18xx_pwm->base)) + return PTR_ERR(lpc18xx_pwm->base); + + lpc18xx_pwm->pwm_clk = devm_clk_get(&pdev->dev, "pwm"); + if (IS_ERR(lpc18xx_pwm->pwm_clk)) { + dev_err(&pdev->dev, "failed to get pwm clock\n"); + return PTR_ERR(lpc18xx_pwm->pwm_clk); + } + + ret = clk_prepare_enable(lpc18xx_pwm->pwm_clk); + if (ret < 0) { + dev_err(&pdev->dev, "could not prepare or enable pwm clock\n"); + return ret; + } + + lpc18xx_pwm->clk_rate = clk_get_rate(lpc18xx_pwm->pwm_clk); + if (!lpc18xx_pwm->clk_rate) { + dev_err(&pdev->dev, "pwm clock has no frequency\n"); + ret = -EINVAL; + goto disable_pwmclk; + } + + mutex_init(&lpc18xx_pwm->res_lock); + mutex_init(&lpc18xx_pwm->period_lock); + + val = (u64)NSEC_PER_SEC * LPC18XX_PWM_TIMER_MAX; + do_div(val, lpc18xx_pwm->clk_rate); + lpc18xx_pwm->max_period_ns = val; + + lpc18xx_pwm->min_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, + lpc18xx_pwm->clk_rate); + + lpc18xx_pwm->chip.dev = &pdev->dev; + lpc18xx_pwm->chip.ops = &lpc18xx_pwm_ops; + lpc18xx_pwm->chip.base = -1; + lpc18xx_pwm->chip.npwm = 16; + lpc18xx_pwm->chip.of_xlate = of_pwm_xlate_with_flags; + lpc18xx_pwm->chip.of_pwm_n_cells = 3; + + /* SCT counter must be in unify (32 bit) mode */ + lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_CONFIG, + LPC18XX_PWM_CONFIG_UNIFY); + + /* + * Everytime the timer counter reaches the period value, the related + * event will be triggered and the counter reset to 0. + */ + set_bit(LPC18XX_PWM_EVENT_PERIOD, &lpc18xx_pwm->event_map); + lpc18xx_pwm->period_event = LPC18XX_PWM_EVENT_PERIOD; + + lpc18xx_pwm_writel(lpc18xx_pwm, + LPC18XX_PWM_EVSTATEMSK(lpc18xx_pwm->period_event), + LPC18XX_PWM_EVSTATEMSK_ALL); + + val = LPC18XX_PWM_EVCTRL_MATCH(lpc18xx_pwm->period_event) | + LPC18XX_PWM_EVCTRL_COMB_MATCH; + lpc18xx_pwm_writel(lpc18xx_pwm, + LPC18XX_PWM_EVCTRL(lpc18xx_pwm->period_event), val); + + lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_LIMIT, + BIT(lpc18xx_pwm->period_event)); + + for (i = 0; i < lpc18xx_pwm->chip.npwm; i++) { + struct lpc18xx_pwm_data *data; + + pwm = &lpc18xx_pwm->chip.pwms[i]; + + data = devm_kzalloc(lpc18xx_pwm->dev, sizeof(*data), + GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto disable_pwmclk; + } + + pwm_set_chip_data(pwm, data); + } + + val = lpc18xx_pwm_readl(lpc18xx_pwm, LPC18XX_PWM_CTRL); + val &= ~LPC18XX_PWM_BIDIR; + val &= ~LPC18XX_PWM_CTRL_HALT; + val &= ~LPC18XX_PWM_PRE_MASK; + val |= LPC18XX_PWM_PRE(0); + lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_CTRL, val); + + ret = pwmchip_add(&lpc18xx_pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret); + goto disable_pwmclk; + } + + platform_set_drvdata(pdev, lpc18xx_pwm); + + return 0; + +disable_pwmclk: + clk_disable_unprepare(lpc18xx_pwm->pwm_clk); + return ret; +} + +static int lpc18xx_pwm_remove(struct platform_device *pdev) +{ + struct lpc18xx_pwm_chip *lpc18xx_pwm = platform_get_drvdata(pdev); + u32 val; + + val = lpc18xx_pwm_readl(lpc18xx_pwm, LPC18XX_PWM_CTRL); + lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_CTRL, + val | LPC18XX_PWM_CTRL_HALT); + + clk_disable_unprepare(lpc18xx_pwm->pwm_clk); + + return pwmchip_remove(&lpc18xx_pwm->chip); +} + +static struct platform_driver lpc18xx_pwm_driver = { + .driver = { + .name = "lpc18xx-sct-pwm", + .of_match_table = lpc18xx_pwm_of_match, + }, + .probe = lpc18xx_pwm_probe, + .remove = lpc18xx_pwm_remove, +}; +module_platform_driver(lpc18xx_pwm_driver); + +MODULE_AUTHOR("Ariel D'Alessandro <ariel@vanguardiasur.com.ar>"); +MODULE_DESCRIPTION("NXP LPC18xx PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-lpc32xx.c b/drivers/pwm/pwm-lpc32xx.c new file mode 100644 index 000000000..504a8f506 --- /dev/null +++ b/drivers/pwm/pwm-lpc32xx.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2012 Alexandre Pereira da Silva <aletes.xgr@gmail.com> + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +struct lpc32xx_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + void __iomem *base; +}; + +#define PWM_ENABLE BIT(31) +#define PWM_PIN_LEVEL BIT(30) + +#define to_lpc32xx_pwm_chip(_chip) \ + container_of(_chip, struct lpc32xx_pwm_chip, chip) + +static int lpc32xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); + unsigned long long c; + int period_cycles, duty_cycles; + u32 val; + c = clk_get_rate(lpc32xx->clk); + + /* The highest acceptable divisor is 256, which is represented by 0 */ + period_cycles = div64_u64(c * period_ns, + (unsigned long long)NSEC_PER_SEC * 256); + if (!period_cycles || period_cycles > 256) + return -ERANGE; + if (period_cycles == 256) + period_cycles = 0; + + /* Compute 256 x #duty/period value and care for corner cases */ + duty_cycles = div64_u64((unsigned long long)(period_ns - duty_ns) * 256, + period_ns); + if (!duty_cycles) + duty_cycles = 1; + if (duty_cycles > 255) + duty_cycles = 255; + + val = readl(lpc32xx->base); + val &= ~0xFFFF; + val |= (period_cycles << 8) | duty_cycles; + writel(val, lpc32xx->base); + + return 0; +} + +static int lpc32xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); + u32 val; + int ret; + + ret = clk_prepare_enable(lpc32xx->clk); + if (ret) + return ret; + + val = readl(lpc32xx->base); + val |= PWM_ENABLE; + writel(val, lpc32xx->base); + + return 0; +} + +static void lpc32xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); + u32 val; + + val = readl(lpc32xx->base); + val &= ~PWM_ENABLE; + writel(val, lpc32xx->base); + + clk_disable_unprepare(lpc32xx->clk); +} + +static const struct pwm_ops lpc32xx_pwm_ops = { + .config = lpc32xx_pwm_config, + .enable = lpc32xx_pwm_enable, + .disable = lpc32xx_pwm_disable, + .owner = THIS_MODULE, +}; + +static int lpc32xx_pwm_probe(struct platform_device *pdev) +{ + struct lpc32xx_pwm_chip *lpc32xx; + struct resource *res; + int ret; + u32 val; + + lpc32xx = devm_kzalloc(&pdev->dev, sizeof(*lpc32xx), GFP_KERNEL); + if (!lpc32xx) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + lpc32xx->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(lpc32xx->base)) + return PTR_ERR(lpc32xx->base); + + lpc32xx->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(lpc32xx->clk)) + return PTR_ERR(lpc32xx->clk); + + lpc32xx->chip.dev = &pdev->dev; + lpc32xx->chip.ops = &lpc32xx_pwm_ops; + lpc32xx->chip.npwm = 1; + lpc32xx->chip.base = -1; + + /* If PWM is disabled, configure the output to the default value */ + val = readl(lpc32xx->base); + val &= ~PWM_PIN_LEVEL; + writel(val, lpc32xx->base); + + ret = pwmchip_add(&lpc32xx->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add PWM chip, error %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, lpc32xx); + + return 0; +} + +static int lpc32xx_pwm_remove(struct platform_device *pdev) +{ + struct lpc32xx_pwm_chip *lpc32xx = platform_get_drvdata(pdev); + unsigned int i; + + for (i = 0; i < lpc32xx->chip.npwm; i++) + pwm_disable(&lpc32xx->chip.pwms[i]); + + return pwmchip_remove(&lpc32xx->chip); +} + +static const struct of_device_id lpc32xx_pwm_dt_ids[] = { + { .compatible = "nxp,lpc3220-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lpc32xx_pwm_dt_ids); + +static struct platform_driver lpc32xx_pwm_driver = { + .driver = { + .name = "lpc32xx-pwm", + .of_match_table = lpc32xx_pwm_dt_ids, + }, + .probe = lpc32xx_pwm_probe, + .remove = lpc32xx_pwm_remove, +}; +module_platform_driver(lpc32xx_pwm_driver); + +MODULE_ALIAS("platform:lpc32xx-pwm"); +MODULE_AUTHOR("Alexandre Pereira da Silva <aletes.xgr@gmail.com>"); +MODULE_DESCRIPTION("LPC32XX PWM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-lpss-pci.c b/drivers/pwm/pwm-lpss-pci.c new file mode 100644 index 000000000..cf749ea0d --- /dev/null +++ b/drivers/pwm/pwm-lpss-pci.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel Low Power Subsystem PWM controller PCI driver + * + * Copyright (C) 2014, Intel Corporation + * + * Derived from the original pwm-lpss.c + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pm_runtime.h> + +#include "pwm-lpss.h" + +/* BayTrail */ +static const struct pwm_lpss_boardinfo pwm_lpss_byt_info = { + .clk_rate = 25000000, + .npwm = 1, + .base_unit_bits = 16, +}; + +/* Braswell */ +static const struct pwm_lpss_boardinfo pwm_lpss_bsw_info = { + .clk_rate = 19200000, + .npwm = 1, + .base_unit_bits = 16, +}; + +/* Broxton */ +static const struct pwm_lpss_boardinfo pwm_lpss_bxt_info = { + .clk_rate = 19200000, + .npwm = 4, + .base_unit_bits = 22, + .bypass = true, +}; + +/* Tangier */ +static const struct pwm_lpss_boardinfo pwm_lpss_tng_info = { + .clk_rate = 19200000, + .npwm = 4, + .base_unit_bits = 22, +}; + +static int pwm_lpss_probe_pci(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + const struct pwm_lpss_boardinfo *info; + struct pwm_lpss_chip *lpwm; + int err; + + err = pcim_enable_device(pdev); + if (err < 0) + return err; + + info = (struct pwm_lpss_boardinfo *)id->driver_data; + lpwm = pwm_lpss_probe(&pdev->dev, &pdev->resource[0], info); + if (IS_ERR(lpwm)) + return PTR_ERR(lpwm); + + pci_set_drvdata(pdev, lpwm); + + pm_runtime_put(&pdev->dev); + pm_runtime_allow(&pdev->dev); + + return 0; +} + +static void pwm_lpss_remove_pci(struct pci_dev *pdev) +{ + struct pwm_lpss_chip *lpwm = pci_get_drvdata(pdev); + + pm_runtime_forbid(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + pwm_lpss_remove(lpwm); +} + +#ifdef CONFIG_PM +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; +} +#endif + +static const struct dev_pm_ops pwm_lpss_pci_pm = { + SET_RUNTIME_PM_OPS(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}, + { PCI_VDEVICE(INTEL, 0x0f09), (unsigned long)&pwm_lpss_byt_info}, + { PCI_VDEVICE(INTEL, 0x11a5), (unsigned long)&pwm_lpss_tng_info}, + { PCI_VDEVICE(INTEL, 0x1ac8), (unsigned long)&pwm_lpss_bxt_info}, + { PCI_VDEVICE(INTEL, 0x2288), (unsigned long)&pwm_lpss_bsw_info}, + { PCI_VDEVICE(INTEL, 0x2289), (unsigned long)&pwm_lpss_bsw_info}, + { PCI_VDEVICE(INTEL, 0x31c8), (unsigned long)&pwm_lpss_bxt_info}, + { PCI_VDEVICE(INTEL, 0x5ac8), (unsigned long)&pwm_lpss_bxt_info}, + { }, +}; +MODULE_DEVICE_TABLE(pci, pwm_lpss_pci_ids); + +static struct pci_driver pwm_lpss_driver_pci = { + .name = "pwm-lpss", + .id_table = pwm_lpss_pci_ids, + .probe = pwm_lpss_probe_pci, + .remove = pwm_lpss_remove_pci, + .driver = { + .pm = &pwm_lpss_pci_pm, + }, +}; +module_pci_driver(pwm_lpss_driver_pci); + +MODULE_DESCRIPTION("PWM PCI driver for Intel LPSS"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-lpss-platform.c b/drivers/pwm/pwm-lpss-platform.c new file mode 100644 index 000000000..c6502cf7a --- /dev/null +++ b/drivers/pwm/pwm-lpss-platform.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel Low Power Subsystem PWM controller driver + * + * Copyright (C) 2014, Intel Corporation + * + * Derived from the original pwm-lpss.c + */ + +#include <linux/acpi.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#include "pwm-lpss.h" + +/* BayTrail */ +static const struct pwm_lpss_boardinfo pwm_lpss_byt_info = { + .clk_rate = 25000000, + .npwm = 1, + .base_unit_bits = 16, +}; + +/* Braswell */ +static const struct pwm_lpss_boardinfo pwm_lpss_bsw_info = { + .clk_rate = 19200000, + .npwm = 1, + .base_unit_bits = 16, + .other_devices_aml_touches_pwm_regs = true, +}; + +/* Broxton */ +static const struct pwm_lpss_boardinfo pwm_lpss_bxt_info = { + .clk_rate = 19200000, + .npwm = 4, + .base_unit_bits = 22, + .bypass = true, +}; + +static int pwm_lpss_probe_platform(struct platform_device *pdev) +{ + const struct pwm_lpss_boardinfo *info; + const struct acpi_device_id *id; + struct pwm_lpss_chip *lpwm; + struct resource *r; + + id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev); + if (!id) + return -ENODEV; + + info = (const struct pwm_lpss_boardinfo *)id->driver_data; + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + lpwm = pwm_lpss_probe(&pdev->dev, r, info); + if (IS_ERR(lpwm)) + return PTR_ERR(lpwm); + + platform_set_drvdata(pdev, lpwm); + + dev_pm_set_driver_flags(&pdev->dev, DPM_FLAG_SMART_PREPARE); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static int pwm_lpss_remove_platform(struct platform_device *pdev) +{ + struct pwm_lpss_chip *lpwm = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + return pwm_lpss_remove(lpwm); +} + +static int pwm_lpss_prepare(struct device *dev) +{ + struct pwm_lpss_chip *lpwm = dev_get_drvdata(dev); + + /* + * If other device's AML code touches the PWM regs on suspend/resume + * force runtime-resume the PWM controller to allow this. + */ + if (lpwm->info->other_devices_aml_touches_pwm_regs) + return 0; /* Force runtime-resume */ + + return 1; /* If runtime-suspended leave as is */ +} + +static const struct dev_pm_ops pwm_lpss_platform_pm_ops = { + .prepare = pwm_lpss_prepare, +}; + +static const struct acpi_device_id pwm_lpss_acpi_match[] = { + { "80860F09", (unsigned long)&pwm_lpss_byt_info }, + { "80862288", (unsigned long)&pwm_lpss_bsw_info }, + { "80862289", (unsigned long)&pwm_lpss_bsw_info }, + { "80865AC8", (unsigned long)&pwm_lpss_bxt_info }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, pwm_lpss_acpi_match); + +static struct platform_driver pwm_lpss_driver_platform = { + .driver = { + .name = "pwm-lpss", + .acpi_match_table = pwm_lpss_acpi_match, + .pm = &pwm_lpss_platform_pm_ops, + }, + .probe = pwm_lpss_probe_platform, + .remove = pwm_lpss_remove_platform, +}; +module_platform_driver(pwm_lpss_driver_platform); + +MODULE_DESCRIPTION("PWM platform driver for Intel LPSS"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:pwm-lpss"); diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c new file mode 100644 index 000000000..3444c56b4 --- /dev/null +++ b/drivers/pwm/pwm-lpss.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel Low Power Subsystem PWM controller driver + * + * Copyright (C) 2014, Intel Corporation + * Author: Mika Westerberg <mika.westerberg@linux.intel.com> + * Author: Chew Kean Ho <kean.ho.chew@intel.com> + * Author: Chang Rebecca Swee Fun <rebecca.swee.fun.chang@intel.com> + * Author: Chew Chiau Ee <chiau.ee.chew@intel.com> + * Author: Alan Cox <alan@linux.intel.com> + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/time.h> + +#include "pwm-lpss.h" + +#define PWM 0x00000000 +#define PWM_ENABLE BIT(31) +#define PWM_SW_UPDATE BIT(30) +#define PWM_BASE_UNIT_SHIFT 8 +#define PWM_ON_TIME_DIV_MASK 0x000000ff + +/* Size of each PWM register space if multiple */ +#define PWM_SIZE 0x400 + +static inline struct pwm_lpss_chip *to_lpwm(struct pwm_chip *chip) +{ + return container_of(chip, struct pwm_lpss_chip, chip); +} + +static inline u32 pwm_lpss_read(const struct pwm_device *pwm) +{ + struct pwm_lpss_chip *lpwm = to_lpwm(pwm->chip); + + return readl(lpwm->regs + pwm->hwpwm * PWM_SIZE + PWM); +} + +static inline void pwm_lpss_write(const struct pwm_device *pwm, u32 value) +{ + struct pwm_lpss_chip *lpwm = to_lpwm(pwm->chip); + + writel(value, lpwm->regs + pwm->hwpwm * PWM_SIZE + PWM); +} + +static int pwm_lpss_wait_for_update(struct pwm_device *pwm) +{ + struct pwm_lpss_chip *lpwm = to_lpwm(pwm->chip); + const void __iomem *addr = lpwm->regs + pwm->hwpwm * PWM_SIZE + PWM; + const unsigned int ms = 500 * USEC_PER_MSEC; + u32 val; + int err; + + /* + * PWM Configuration register has SW_UPDATE bit that is set when a new + * configuration is written to the register. The bit is automatically + * cleared at the start of the next output cycle by the IP block. + * + * If one writes a new configuration to the register while it still has + * the bit enabled, PWM may freeze. That is, while one can still write + * to the register, it won't have an effect. Thus, we try to sleep long + * enough that the bit gets cleared and make sure the bit is not + * enabled while we update the configuration. + */ + err = readl_poll_timeout(addr, val, !(val & PWM_SW_UPDATE), 40, ms); + if (err) + dev_err(pwm->chip->dev, "PWM_SW_UPDATE was not cleared\n"); + + return err; +} + +static inline int pwm_lpss_is_updating(struct pwm_device *pwm) +{ + return (pwm_lpss_read(pwm) & PWM_SW_UPDATE) ? -EBUSY : 0; +} + +static void pwm_lpss_prepare(struct pwm_lpss_chip *lpwm, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + unsigned long long on_time_div; + unsigned long c = lpwm->info->clk_rate, base_unit_range; + unsigned long long base_unit, freq = NSEC_PER_SEC; + u32 ctrl; + + do_div(freq, period_ns); + + /* + * The equation is: + * base_unit = round(base_unit_range * freq / c) + */ + base_unit_range = BIT(lpwm->info->base_unit_bits); + freq *= base_unit_range; + + base_unit = DIV_ROUND_CLOSEST_ULL(freq, c); + /* base_unit must not be 0 and we also want to avoid overflowing it */ + base_unit = clamp_val(base_unit, 1, base_unit_range - 1); + + on_time_div = 255ULL * duty_ns; + do_div(on_time_div, period_ns); + on_time_div = 255ULL - on_time_div; + + ctrl = pwm_lpss_read(pwm); + ctrl &= ~PWM_ON_TIME_DIV_MASK; + ctrl &= ~((base_unit_range - 1) << PWM_BASE_UNIT_SHIFT); + ctrl |= (u32) base_unit << PWM_BASE_UNIT_SHIFT; + ctrl |= on_time_div; + + pwm_lpss_write(pwm, ctrl); + pwm_lpss_write(pwm, ctrl | PWM_SW_UPDATE); +} + +static inline void pwm_lpss_cond_enable(struct pwm_device *pwm, bool cond) +{ + if (cond) + pwm_lpss_write(pwm, pwm_lpss_read(pwm) | PWM_ENABLE); +} + +static int pwm_lpss_prepare_enable(struct pwm_lpss_chip *lpwm, + struct pwm_device *pwm, + const struct pwm_state *state) +{ + int ret; + + ret = pwm_lpss_is_updating(pwm); + if (ret) + return ret; + + pwm_lpss_prepare(lpwm, pwm, state->duty_cycle, state->period); + pwm_lpss_cond_enable(pwm, lpwm->info->bypass == false); + ret = pwm_lpss_wait_for_update(pwm); + if (ret) + return ret; + + pwm_lpss_cond_enable(pwm, lpwm->info->bypass == true); + return 0; +} + +static int pwm_lpss_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct pwm_lpss_chip *lpwm = to_lpwm(chip); + int ret = 0; + + if (state->enabled) { + if (!pwm_is_enabled(pwm)) { + pm_runtime_get_sync(chip->dev); + ret = pwm_lpss_prepare_enable(lpwm, pwm, state); + if (ret) + pm_runtime_put(chip->dev); + } else { + ret = pwm_lpss_prepare_enable(lpwm, pwm, state); + } + } else if (pwm_is_enabled(pwm)) { + pwm_lpss_write(pwm, pwm_lpss_read(pwm) & ~PWM_ENABLE); + pm_runtime_put(chip->dev); + } + + return ret; +} + +static void pwm_lpss_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct pwm_lpss_chip *lpwm = to_lpwm(chip); + unsigned long base_unit_range; + unsigned long long base_unit, freq, on_time_div; + u32 ctrl; + + pm_runtime_get_sync(chip->dev); + + base_unit_range = BIT(lpwm->info->base_unit_bits); + + ctrl = pwm_lpss_read(pwm); + on_time_div = 255 - (ctrl & PWM_ON_TIME_DIV_MASK); + base_unit = (ctrl >> PWM_BASE_UNIT_SHIFT) & (base_unit_range - 1); + + freq = base_unit * lpwm->info->clk_rate; + do_div(freq, base_unit_range); + if (freq == 0) + state->period = NSEC_PER_SEC; + else + state->period = NSEC_PER_SEC / (unsigned long)freq; + + on_time_div *= state->period; + do_div(on_time_div, 255); + state->duty_cycle = on_time_div; + + state->polarity = PWM_POLARITY_NORMAL; + state->enabled = !!(ctrl & PWM_ENABLE); + + pm_runtime_put(chip->dev); +} + +static const struct pwm_ops pwm_lpss_ops = { + .apply = pwm_lpss_apply, + .get_state = pwm_lpss_get_state, + .owner = THIS_MODULE, +}; + +struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, + const struct pwm_lpss_boardinfo *info) +{ + struct pwm_lpss_chip *lpwm; + unsigned long c; + int i, ret; + u32 ctrl; + + if (WARN_ON(info->npwm > MAX_PWMS)) + return ERR_PTR(-ENODEV); + + lpwm = devm_kzalloc(dev, sizeof(*lpwm), GFP_KERNEL); + if (!lpwm) + return ERR_PTR(-ENOMEM); + + lpwm->regs = devm_ioremap_resource(dev, r); + if (IS_ERR(lpwm->regs)) + return ERR_CAST(lpwm->regs); + + lpwm->info = info; + + c = lpwm->info->clk_rate; + if (!c) + return ERR_PTR(-EINVAL); + + lpwm->chip.dev = dev; + lpwm->chip.ops = &pwm_lpss_ops; + lpwm->chip.base = -1; + lpwm->chip.npwm = info->npwm; + + ret = pwmchip_add(&lpwm->chip); + if (ret) { + dev_err(dev, "failed to add PWM chip: %d\n", ret); + return ERR_PTR(ret); + } + + for (i = 0; i < lpwm->info->npwm; i++) { + ctrl = pwm_lpss_read(&lpwm->chip.pwms[i]); + if (ctrl & PWM_ENABLE) + pm_runtime_get(dev); + } + + return lpwm; +} +EXPORT_SYMBOL_GPL(pwm_lpss_probe); + +int pwm_lpss_remove(struct pwm_lpss_chip *lpwm) +{ + int i; + + for (i = 0; i < lpwm->info->npwm; i++) { + if (pwm_is_enabled(&lpwm->chip.pwms[i])) + pm_runtime_put(lpwm->chip.dev); + } + return pwmchip_remove(&lpwm->chip); +} +EXPORT_SYMBOL_GPL(pwm_lpss_remove); + +MODULE_DESCRIPTION("PWM driver for Intel LPSS"); +MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-lpss.h b/drivers/pwm/pwm-lpss.h new file mode 100644 index 000000000..70db7e389 --- /dev/null +++ b/drivers/pwm/pwm-lpss.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Intel Low Power Subsystem PWM controller driver + * + * Copyright (C) 2014, Intel Corporation + * + * Derived from the original pwm-lpss.c + */ + +#ifndef __PWM_LPSS_H +#define __PWM_LPSS_H + +#include <linux/device.h> +#include <linux/pwm.h> + +#define MAX_PWMS 4 + +struct pwm_lpss_chip { + struct pwm_chip chip; + void __iomem *regs; + const struct pwm_lpss_boardinfo *info; +}; + +struct pwm_lpss_boardinfo { + unsigned long clk_rate; + unsigned int npwm; + unsigned long base_unit_bits; + bool bypass; + /* + * On some devices the _PS0/_PS3 AML code of the GPU (GFX0) device + * messes with the PWM0 controllers state, + */ + bool other_devices_aml_touches_pwm_regs; +}; + +struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, + const struct pwm_lpss_boardinfo *info); +int pwm_lpss_remove(struct pwm_lpss_chip *lpwm); + +#endif /* __PWM_LPSS_H */ diff --git a/drivers/pwm/pwm-mediatek.c b/drivers/pwm/pwm-mediatek.c new file mode 100644 index 000000000..ab001ce55 --- /dev/null +++ b/drivers/pwm/pwm-mediatek.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MediaTek Pulse Width Modulator driver + * + * Copyright (C) 2015 John Crispin <blogic@openwrt.org> + * Copyright (C) 2017 Zhi Mao <zhi.mao@mediatek.com> + * + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/types.h> + +/* PWM registers and bits definitions */ +#define PWMCON 0x00 +#define PWMHDUR 0x04 +#define PWMLDUR 0x08 +#define PWMGDUR 0x0c +#define PWMWAVENUM 0x28 +#define PWMDWIDTH 0x2c +#define PWM45DWIDTH_FIXUP 0x30 +#define PWMTHRES 0x30 +#define PWM45THRES_FIXUP 0x34 + +#define PWM_CLK_DIV_MAX 7 + +struct pwm_mediatek_of_data { + unsigned int num_pwms; + bool pwm45_fixup; +}; + +/** + * struct pwm_mediatek_chip - struct representing PWM chip + * @chip: linux PWM chip representation + * @regs: base address of PWM chip + * @clk_top: the top clock generator + * @clk_main: the clock used by PWM core + * @clk_pwms: the clock used by each PWM channel + * @clk_freq: the fix clock frequency of legacy MIPS SoC + * @soc: pointer to chip's platform data + */ +struct pwm_mediatek_chip { + struct pwm_chip chip; + void __iomem *regs; + struct clk *clk_top; + struct clk *clk_main; + struct clk **clk_pwms; + const struct pwm_mediatek_of_data *soc; +}; + +static const unsigned int pwm_mediatek_reg_offset[] = { + 0x0010, 0x0050, 0x0090, 0x00d0, 0x0110, 0x0150, 0x0190, 0x0220 +}; + +static inline struct pwm_mediatek_chip * +to_pwm_mediatek_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct pwm_mediatek_chip, chip); +} + +static int pwm_mediatek_clk_enable(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip); + int ret; + + ret = clk_prepare_enable(pc->clk_top); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(pc->clk_main); + if (ret < 0) + goto disable_clk_top; + + ret = clk_prepare_enable(pc->clk_pwms[pwm->hwpwm]); + if (ret < 0) + goto disable_clk_main; + + return 0; + +disable_clk_main: + clk_disable_unprepare(pc->clk_main); +disable_clk_top: + clk_disable_unprepare(pc->clk_top); + + return ret; +} + +static void pwm_mediatek_clk_disable(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip); + + clk_disable_unprepare(pc->clk_pwms[pwm->hwpwm]); + clk_disable_unprepare(pc->clk_main); + clk_disable_unprepare(pc->clk_top); +} + +static inline u32 pwm_mediatek_readl(struct pwm_mediatek_chip *chip, + unsigned int num, unsigned int offset) +{ + return readl(chip->regs + pwm_mediatek_reg_offset[num] + offset); +} + +static inline void pwm_mediatek_writel(struct pwm_mediatek_chip *chip, + unsigned int num, unsigned int offset, + u32 value) +{ + writel(value, chip->regs + pwm_mediatek_reg_offset[num] + offset); +} + +static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip); + u32 clkdiv = 0, cnt_period, cnt_duty, reg_width = PWMDWIDTH, + reg_thres = PWMTHRES; + u64 resolution; + int ret; + + ret = pwm_mediatek_clk_enable(chip, pwm); + + if (ret < 0) + return ret; + + /* Using resolution in picosecond gets accuracy higher */ + resolution = (u64)NSEC_PER_SEC * 1000; + do_div(resolution, clk_get_rate(pc->clk_pwms[pwm->hwpwm])); + + cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution); + while (cnt_period > 8191) { + resolution *= 2; + clkdiv++; + cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, + resolution); + } + + if (clkdiv > PWM_CLK_DIV_MAX) { + pwm_mediatek_clk_disable(chip, pwm); + dev_err(chip->dev, "period %d not supported\n", period_ns); + return -EINVAL; + } + + if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) { + /* + * PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES + * from the other PWMs on MT7623. + */ + reg_width = PWM45DWIDTH_FIXUP; + reg_thres = PWM45THRES_FIXUP; + } + + cnt_duty = DIV_ROUND_CLOSEST_ULL((u64)duty_ns * 1000, resolution); + pwm_mediatek_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | clkdiv); + pwm_mediatek_writel(pc, pwm->hwpwm, reg_width, cnt_period); + pwm_mediatek_writel(pc, pwm->hwpwm, reg_thres, cnt_duty); + + pwm_mediatek_clk_disable(chip, pwm); + + return 0; +} + +static int pwm_mediatek_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip); + u32 value; + int ret; + + ret = pwm_mediatek_clk_enable(chip, pwm); + if (ret < 0) + return ret; + + value = readl(pc->regs); + value |= BIT(pwm->hwpwm); + writel(value, pc->regs); + + return 0; +} + +static void pwm_mediatek_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip); + u32 value; + + value = readl(pc->regs); + value &= ~BIT(pwm->hwpwm); + writel(value, pc->regs); + + pwm_mediatek_clk_disable(chip, pwm); +} + +static const struct pwm_ops pwm_mediatek_ops = { + .config = pwm_mediatek_config, + .enable = pwm_mediatek_enable, + .disable = pwm_mediatek_disable, + .owner = THIS_MODULE, +}; + +static int pwm_mediatek_probe(struct platform_device *pdev) +{ + struct pwm_mediatek_chip *pc; + struct resource *res; + unsigned int i; + int ret; + + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + pc->soc = of_device_get_match_data(&pdev->dev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pc->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pc->regs)) + return PTR_ERR(pc->regs); + + pc->clk_pwms = devm_kcalloc(&pdev->dev, pc->soc->num_pwms, + sizeof(*pc->clk_pwms), GFP_KERNEL); + if (!pc->clk_pwms) + return -ENOMEM; + + pc->clk_top = devm_clk_get(&pdev->dev, "top"); + if (IS_ERR(pc->clk_top)) { + dev_err(&pdev->dev, "clock: top fail: %ld\n", + PTR_ERR(pc->clk_top)); + return PTR_ERR(pc->clk_top); + } + + pc->clk_main = devm_clk_get(&pdev->dev, "main"); + if (IS_ERR(pc->clk_main)) { + dev_err(&pdev->dev, "clock: main fail: %ld\n", + PTR_ERR(pc->clk_main)); + return PTR_ERR(pc->clk_main); + } + + for (i = 0; i < pc->soc->num_pwms; i++) { + char name[8]; + + snprintf(name, sizeof(name), "pwm%d", i + 1); + + pc->clk_pwms[i] = devm_clk_get(&pdev->dev, name); + if (IS_ERR(pc->clk_pwms[i])) { + dev_err(&pdev->dev, "clock: %s fail: %ld\n", + name, PTR_ERR(pc->clk_pwms[i])); + return PTR_ERR(pc->clk_pwms[i]); + } + } + + platform_set_drvdata(pdev, pc); + + pc->chip.dev = &pdev->dev; + pc->chip.ops = &pwm_mediatek_ops; + pc->chip.base = -1; + pc->chip.npwm = pc->soc->num_pwms; + + ret = pwmchip_add(&pc->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int pwm_mediatek_remove(struct platform_device *pdev) +{ + struct pwm_mediatek_chip *pc = platform_get_drvdata(pdev); + + return pwmchip_remove(&pc->chip); +} + +static const struct pwm_mediatek_of_data mt2712_pwm_data = { + .num_pwms = 8, + .pwm45_fixup = false, +}; + +static const struct pwm_mediatek_of_data mt7622_pwm_data = { + .num_pwms = 6, + .pwm45_fixup = false, +}; + +static const struct pwm_mediatek_of_data mt7623_pwm_data = { + .num_pwms = 5, + .pwm45_fixup = true, +}; + +static const struct pwm_mediatek_of_data mt7628_pwm_data = { + .num_pwms = 4, + .pwm45_fixup = true, +}; + +static const struct pwm_mediatek_of_data mt7629_pwm_data = { + .num_pwms = 1, + .pwm45_fixup = false, +}; + +static const struct pwm_mediatek_of_data mt8516_pwm_data = { + .num_pwms = 5, + .pwm45_fixup = false, +}; + +static const struct of_device_id pwm_mediatek_of_match[] = { + { .compatible = "mediatek,mt2712-pwm", .data = &mt2712_pwm_data }, + { .compatible = "mediatek,mt7622-pwm", .data = &mt7622_pwm_data }, + { .compatible = "mediatek,mt7623-pwm", .data = &mt7623_pwm_data }, + { .compatible = "mediatek,mt7628-pwm", .data = &mt7628_pwm_data }, + { .compatible = "mediatek,mt7629-pwm", .data = &mt7629_pwm_data }, + { .compatible = "mediatek,mt8516-pwm", .data = &mt8516_pwm_data }, + { }, +}; +MODULE_DEVICE_TABLE(of, pwm_mediatek_of_match); + +static struct platform_driver pwm_mediatek_driver = { + .driver = { + .name = "pwm-mediatek", + .of_match_table = pwm_mediatek_of_match, + }, + .probe = pwm_mediatek_probe, + .remove = pwm_mediatek_remove, +}; +module_platform_driver(pwm_mediatek_driver); + +MODULE_AUTHOR("John Crispin <blogic@openwrt.org>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c new file mode 100644 index 000000000..5b5fd1671 --- /dev/null +++ b/drivers/pwm/pwm-meson.c @@ -0,0 +1,604 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * PWM controller driver for Amlogic Meson SoCs. + * + * This PWM is only a set of Gates, Dividers and Counters: + * 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 + * 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. + * Setting the duty cycle will disable and re-enable the PWM output. + * Disabling the PWM stops the output immediately (without waiting for the + * current period to complete first). + * + * The public S912 (GXM) datasheet contains some documentation for this PWM + * controller starting on page 543: + * https://dl.khadas.com/Hardware/VIM2/Datasheet/S912_Datasheet_V0.220170314publicversion-Wesion.pdf + * An updated version of this IP block is found in S922X (G12B) SoCs. The + * datasheet contains the description for this IP block revision starting at + * page 1084: + * https://dn.odroid.com/S922X/ODROID-N2/Datasheet/S922X_Public_Datasheet_V0.2.pdf + * + * Copyright (c) 2016 BayLibre, SAS. + * Author: Neil Armstrong <narmstrong@baylibre.com> + * Copyright (C) 2014 Amlogic, Inc. + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#define REG_PWM_A 0x0 +#define REG_PWM_B 0x4 +#define PWM_LOW_MASK GENMASK(15, 0) +#define PWM_HIGH_MASK GENMASK(31, 16) + +#define REG_MISC_AB 0x8 +#define MISC_B_CLK_EN BIT(23) +#define MISC_A_CLK_EN BIT(15) +#define MISC_CLK_DIV_MASK 0x7f +#define MISC_B_CLK_DIV_SHIFT 16 +#define MISC_A_CLK_DIV_SHIFT 8 +#define MISC_B_CLK_SEL_SHIFT 6 +#define MISC_A_CLK_SEL_SHIFT 4 +#define MISC_CLK_SEL_MASK 0x3 +#define MISC_B_EN BIT(1) +#define MISC_A_EN BIT(0) + +#define MESON_NUM_PWMS 2 + +static struct meson_pwm_channel_data { + u8 reg_offset; + u8 clk_sel_shift; + u8 clk_div_shift; + u32 clk_en_mask; + u32 pwm_en_mask; +} meson_pwm_per_channel_data[MESON_NUM_PWMS] = { + { + .reg_offset = REG_PWM_A, + .clk_sel_shift = MISC_A_CLK_SEL_SHIFT, + .clk_div_shift = MISC_A_CLK_DIV_SHIFT, + .clk_en_mask = MISC_A_CLK_EN, + .pwm_en_mask = MISC_A_EN, + }, + { + .reg_offset = REG_PWM_B, + .clk_sel_shift = MISC_B_CLK_SEL_SHIFT, + .clk_div_shift = MISC_B_CLK_DIV_SHIFT, + .clk_en_mask = MISC_B_CLK_EN, + .pwm_en_mask = MISC_B_EN, + } +}; + +struct meson_pwm_channel { + unsigned int hi; + unsigned int lo; + u8 pre_div; + + struct clk *clk_parent; + struct clk_mux mux; + struct clk *clk; +}; + +struct meson_pwm_data { + const char * const *parent_names; + unsigned int num_parents; +}; + +struct meson_pwm { + struct pwm_chip chip; + const struct meson_pwm_data *data; + struct meson_pwm_channel channels[MESON_NUM_PWMS]; + void __iomem *base; + /* + * Protects register (write) access to the REG_MISC_AB register + * that is shared between the two PWMs. + */ + spinlock_t lock; +}; + +static inline struct meson_pwm *to_meson_pwm(struct pwm_chip *chip) +{ + return container_of(chip, struct meson_pwm, chip); +} + +static int meson_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct meson_pwm *meson = to_meson_pwm(chip); + struct meson_pwm_channel *channel; + struct device *dev = chip->dev; + int err; + + channel = pwm_get_chip_data(pwm); + if (channel) + return 0; + + channel = &meson->channels[pwm->hwpwm]; + + if (channel->clk_parent) { + err = clk_set_parent(channel->clk, channel->clk_parent); + if (err < 0) { + dev_err(dev, "failed to set parent %s for %s: %d\n", + __clk_get_name(channel->clk_parent), + __clk_get_name(channel->clk), err); + return err; + } + } + + err = clk_prepare_enable(channel->clk); + if (err < 0) { + dev_err(dev, "failed to enable clock %s: %d\n", + __clk_get_name(channel->clk), err); + return err; + } + + return 0; +} + +static void meson_pwm_free(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]; + + if (channel) + clk_disable_unprepare(channel->clk); +} + +static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm]; + unsigned int pre_div, cnt, duty_cnt; + unsigned long fin_freq; + u64 duty, period; + + duty = state->duty_cycle; + period = state->period; + + /* + * Note this is wrong. The result is an output wave that isn't really + * inverted and so is wrongly identified by .get_state as normal. + * Fixing this needs some care however as some machines might rely on + * this. + */ + if (state->polarity == PWM_POLARITY_INVERSED) + duty = period - duty; + + fin_freq = clk_get_rate(channel->clk); + if (fin_freq == 0) { + dev_err(meson->chip.dev, "invalid source clock frequency\n"); + return -EINVAL; + } + + dev_dbg(meson->chip.dev, "fin_freq: %lu Hz\n", fin_freq); + + pre_div = div64_u64(fin_freq * period, NSEC_PER_SEC * 0xffffLL); + if (pre_div > MISC_CLK_DIV_MASK) { + dev_err(meson->chip.dev, "unable to get period pre_div\n"); + return -EINVAL; + } + + cnt = div64_u64(fin_freq * period, NSEC_PER_SEC * (pre_div + 1)); + if (cnt > 0xffff) { + dev_err(meson->chip.dev, "unable to get period cnt\n"); + return -EINVAL; + } + + dev_dbg(meson->chip.dev, "period=%llu pre_div=%u cnt=%u\n", period, + pre_div, cnt); + + if (duty == period) { + channel->pre_div = pre_div; + channel->hi = cnt; + channel->lo = 0; + } else if (duty == 0) { + channel->pre_div = pre_div; + channel->hi = 0; + channel->lo = cnt; + } else { + /* Then check is we can have the duty with the same pre_div */ + duty_cnt = div64_u64(fin_freq * duty, NSEC_PER_SEC * (pre_div + 1)); + if (duty_cnt > 0xffff) { + dev_err(meson->chip.dev, "unable to get duty cycle\n"); + return -EINVAL; + } + + dev_dbg(meson->chip.dev, "duty=%llu pre_div=%u duty_cnt=%u\n", + duty, pre_div, duty_cnt); + + channel->pre_div = pre_div; + channel->hi = duty_cnt; + channel->lo = cnt - duty_cnt; + } + + return 0; +} + +static void meson_pwm_enable(struct meson_pwm *meson, struct pwm_device *pwm) +{ + 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 &= ~(MISC_CLK_DIV_MASK << channel_data->clk_div_shift); + value |= channel->pre_div << channel_data->clk_div_shift; + value |= channel_data->clk_en_mask; + writel(value, meson->base + REG_MISC_AB); + + value = FIELD_PREP(PWM_HIGH_MASK, channel->hi) | + FIELD_PREP(PWM_LOW_MASK, channel->lo); + writel(value, meson->base + channel_data->reg_offset); + + value = readl(meson->base + REG_MISC_AB); + value |= channel_data->pwm_en_mask; + writel(value, meson->base + REG_MISC_AB); + + spin_unlock_irqrestore(&meson->lock, flags); +} + +static void meson_pwm_disable(struct meson_pwm *meson, struct pwm_device *pwm) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&meson->lock, flags); + + value = readl(meson->base + REG_MISC_AB); + value &= ~meson_pwm_per_channel_data[pwm->hwpwm].pwm_en_mask; + writel(value, meson->base + REG_MISC_AB); + + spin_unlock_irqrestore(&meson->lock, flags); +} + +static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct meson_pwm *meson = to_meson_pwm(chip); + struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm]; + int err = 0; + + if (!state) + return -EINVAL; + + if (!state->enabled) { + if (state->polarity == PWM_POLARITY_INVERSED) { + /* + * This IP block revision doesn't have an "always high" + * setting which we can use for "inverted disabled". + * Instead we achieve this using the same settings + * that we use a pre_div of 0 (to get the shortest + * possible duration for one "count") and + * "period == duty_cycle". This results in a signal + * which is LOW for one "count", while being HIGH for + * the rest of the (so the signal is HIGH for slightly + * less than 100% of the period, but this is the best + * we can achieve). + */ + channel->pre_div = 0; + channel->hi = ~0; + channel->lo = 0; + + meson_pwm_enable(meson, pwm); + } else { + meson_pwm_disable(meson, pwm); + } + } else { + err = meson_pwm_calc(meson, pwm, state); + if (err < 0) + return err; + + meson_pwm_enable(meson, pwm); + } + + return 0; +} + +static unsigned int meson_pwm_cnt_to_ns(struct pwm_chip *chip, + struct pwm_device *pwm, u32 cnt) +{ + struct meson_pwm *meson = to_meson_pwm(chip); + struct meson_pwm_channel *channel; + unsigned long fin_freq; + u32 fin_ns; + + /* 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; + + fin_ns = div_u64(NSEC_PER_SEC, fin_freq); + + return cnt * fin_ns * (channel->pre_div + 1); +} + +static void meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct meson_pwm *meson = to_meson_pwm(chip); + struct meson_pwm_channel_data *channel_data; + struct meson_pwm_channel *channel; + u32 value, tmp; + + if (!state) + return; + + channel = &meson->channels[pwm->hwpwm]; + channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; + + value = readl(meson->base + REG_MISC_AB); + + tmp = channel_data->pwm_en_mask | channel_data->clk_en_mask; + state->enabled = (value & tmp) == tmp; + + tmp = value >> channel_data->clk_div_shift; + channel->pre_div = FIELD_GET(MISC_CLK_DIV_MASK, tmp); + + 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 (channel->lo == 0) { + state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->hi); + state->duty_cycle = state->period; + } else if (channel->lo >= channel->hi) { + 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); + } else { + state->period = 0; + state->duty_cycle = 0; + } + state->polarity = PWM_POLARITY_NORMAL; +} + +static const struct pwm_ops meson_pwm_ops = { + .request = meson_pwm_request, + .free = meson_pwm_free, + .apply = meson_pwm_apply, + .get_state = meson_pwm_get_state, + .owner = THIS_MODULE, +}; + +static const char * const pwm_meson8b_parent_names[] = { + "xtal", "vid_pll", "fclk_div4", "fclk_div3" +}; + +static const struct meson_pwm_data pwm_meson8b_data = { + .parent_names = pwm_meson8b_parent_names, + .num_parents = ARRAY_SIZE(pwm_meson8b_parent_names), +}; + +static const char * const pwm_gxbb_parent_names[] = { + "xtal", "hdmi_pll", "fclk_div4", "fclk_div3" +}; + +static const struct meson_pwm_data pwm_gxbb_data = { + .parent_names = pwm_gxbb_parent_names, + .num_parents = ARRAY_SIZE(pwm_gxbb_parent_names), +}; + +/* + * Only the 2 first inputs of the GXBB AO PWMs are valid + * The last 2 are grounded + */ +static const char * const pwm_gxbb_ao_parent_names[] = { + "xtal", "clk81" +}; + +static const struct meson_pwm_data pwm_gxbb_ao_data = { + .parent_names = pwm_gxbb_ao_parent_names, + .num_parents = ARRAY_SIZE(pwm_gxbb_ao_parent_names), +}; + +static const char * const pwm_axg_ee_parent_names[] = { + "xtal", "fclk_div5", "fclk_div4", "fclk_div3" +}; + +static const struct meson_pwm_data pwm_axg_ee_data = { + .parent_names = pwm_axg_ee_parent_names, + .num_parents = ARRAY_SIZE(pwm_axg_ee_parent_names), +}; + +static const char * const pwm_axg_ao_parent_names[] = { + "xtal", "axg_ao_clk81", "fclk_div4", "fclk_div5" +}; + +static const struct meson_pwm_data pwm_axg_ao_data = { + .parent_names = pwm_axg_ao_parent_names, + .num_parents = ARRAY_SIZE(pwm_axg_ao_parent_names), +}; + +static const char * const pwm_g12a_ao_ab_parent_names[] = { + "xtal", "g12a_ao_clk81", "fclk_div4", "fclk_div5" +}; + +static const struct meson_pwm_data pwm_g12a_ao_ab_data = { + .parent_names = pwm_g12a_ao_ab_parent_names, + .num_parents = ARRAY_SIZE(pwm_g12a_ao_ab_parent_names), +}; + +static const char * const pwm_g12a_ao_cd_parent_names[] = { + "xtal", "g12a_ao_clk81", +}; + +static const struct meson_pwm_data pwm_g12a_ao_cd_data = { + .parent_names = pwm_g12a_ao_cd_parent_names, + .num_parents = ARRAY_SIZE(pwm_g12a_ao_cd_parent_names), +}; + +static const char * const pwm_g12a_ee_parent_names[] = { + "xtal", "hdmi_pll", "fclk_div4", "fclk_div3" +}; + +static const struct meson_pwm_data pwm_g12a_ee_data = { + .parent_names = pwm_g12a_ee_parent_names, + .num_parents = ARRAY_SIZE(pwm_g12a_ee_parent_names), +}; + +static const struct of_device_id meson_pwm_matches[] = { + { + .compatible = "amlogic,meson8b-pwm", + .data = &pwm_meson8b_data + }, + { + .compatible = "amlogic,meson-gxbb-pwm", + .data = &pwm_gxbb_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 + }, + {}, +}; +MODULE_DEVICE_TABLE(of, meson_pwm_matches); + +static int meson_pwm_init_channels(struct meson_pwm *meson) +{ + struct device *dev = meson->chip.dev; + struct clk_init_data init; + unsigned int i; + char name[255]; + int err; + + for (i = 0; i < meson->chip.npwm; i++) { + struct meson_pwm_channel *channel = &meson->channels[i]; + + snprintf(name, sizeof(name), "%s#mux%u", dev_name(dev), i); + + init.name = name; + init.ops = &clk_mux_ops; + init.flags = 0; + init.parent_names = meson->data->parent_names; + init.num_parents = meson->data->num_parents; + + channel->mux.reg = meson->base + REG_MISC_AB; + channel->mux.shift = + meson_pwm_per_channel_data[i].clk_sel_shift; + channel->mux.mask = MISC_CLK_SEL_MASK; + channel->mux.flags = 0; + channel->mux.lock = &meson->lock; + channel->mux.table = NULL; + channel->mux.hw.init = &init; + + channel->clk = devm_clk_register(dev, &channel->mux.hw); + if (IS_ERR(channel->clk)) { + err = PTR_ERR(channel->clk); + dev_err(dev, "failed to register %s: %d\n", name, err); + return err; + } + + snprintf(name, sizeof(name), "clkin%u", i); + + channel->clk_parent = devm_clk_get_optional(dev, name); + if (IS_ERR(channel->clk_parent)) + return PTR_ERR(channel->clk_parent); + } + + return 0; +} + +static int meson_pwm_probe(struct platform_device *pdev) +{ + struct meson_pwm *meson; + struct resource *regs; + int err; + + meson = devm_kzalloc(&pdev->dev, sizeof(*meson), GFP_KERNEL); + if (!meson) + return -ENOMEM; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + meson->base = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(meson->base)) + return PTR_ERR(meson->base); + + spin_lock_init(&meson->lock); + meson->chip.dev = &pdev->dev; + meson->chip.ops = &meson_pwm_ops; + meson->chip.base = -1; + meson->chip.npwm = MESON_NUM_PWMS; + meson->chip.of_xlate = of_pwm_xlate_with_flags; + meson->chip.of_pwm_n_cells = 3; + + meson->data = of_device_get_match_data(&pdev->dev); + + err = meson_pwm_init_channels(meson); + if (err < 0) + return err; + + err = pwmchip_add(&meson->chip); + if (err < 0) { + dev_err(&pdev->dev, "failed to register PWM chip: %d\n", err); + return err; + } + + platform_set_drvdata(pdev, meson); + + return 0; +} + +static int meson_pwm_remove(struct platform_device *pdev) +{ + struct meson_pwm *meson = platform_get_drvdata(pdev); + + return pwmchip_remove(&meson->chip); +} + +static struct platform_driver meson_pwm_driver = { + .driver = { + .name = "meson-pwm", + .of_match_table = meson_pwm_matches, + }, + .probe = meson_pwm_probe, + .remove = meson_pwm_remove, +}; +module_platform_driver(meson_pwm_driver); + +MODULE_DESCRIPTION("Amlogic Meson PWM Generator driver"); +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/pwm/pwm-mtk-disp.c b/drivers/pwm/pwm-mtk-disp.c new file mode 100644 index 000000000..327c780d4 --- /dev/null +++ b/drivers/pwm/pwm-mtk-disp.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MediaTek display pulse-width-modulation controller driver. + * Copyright (c) 2015 MediaTek Inc. + * Author: YH Huang <yh.huang@mediatek.com> + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +#define DISP_PWM_EN 0x00 + +#define PWM_CLKDIV_SHIFT 16 +#define PWM_CLKDIV_MAX 0x3ff +#define PWM_CLKDIV_MASK (PWM_CLKDIV_MAX << PWM_CLKDIV_SHIFT) + +#define PWM_PERIOD_BIT_WIDTH 12 +#define PWM_PERIOD_MASK ((1 << PWM_PERIOD_BIT_WIDTH) - 1) + +#define PWM_HIGH_WIDTH_SHIFT 16 +#define PWM_HIGH_WIDTH_MASK (0x1fff << PWM_HIGH_WIDTH_SHIFT) + +struct mtk_pwm_data { + u32 enable_mask; + unsigned int con0; + u32 con0_sel; + unsigned int con1; + + bool has_commit; + unsigned int commit; + unsigned int commit_mask; + + unsigned int bls_debug; + u32 bls_debug_mask; +}; + +struct mtk_disp_pwm { + struct pwm_chip chip; + const struct mtk_pwm_data *data; + struct clk *clk_main; + struct clk *clk_mm; + void __iomem *base; +}; + +static inline struct mtk_disp_pwm *to_mtk_disp_pwm(struct pwm_chip *chip) +{ + return container_of(chip, struct mtk_disp_pwm, chip); +} + +static void mtk_disp_pwm_update_bits(struct mtk_disp_pwm *mdp, u32 offset, + u32 mask, u32 data) +{ + void __iomem *address = mdp->base + offset; + u32 value; + + value = readl(address); + value &= ~mask; + value |= data; + writel(value, address); +} + +static int mtk_disp_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip); + u32 clk_div, period, high_width, value; + u64 div, rate; + int err; + + err = clk_prepare_enable(mdp->clk_main); + if (err < 0) { + dev_err(chip->dev, "Can't enable mdp->clk_main: %pe\n", ERR_PTR(err)); + return err; + } + + err = clk_prepare_enable(mdp->clk_mm); + if (err < 0) { + dev_err(chip->dev, "Can't enable mdp->clk_mm: %pe\n", ERR_PTR(err)); + clk_disable_unprepare(mdp->clk_main); + return err; + } + + /* + * Find period, high_width and clk_div to suit duty_ns and period_ns. + * Calculate proper div value to keep period value in the bound. + * + * period_ns = 10^9 * (clk_div + 1) * (period + 1) / PWM_CLK_RATE + * duty_ns = 10^9 * (clk_div + 1) * high_width / PWM_CLK_RATE + * + * period = (PWM_CLK_RATE * period_ns) / (10^9 * (clk_div + 1)) - 1 + * high_width = (PWM_CLK_RATE * duty_ns) / (10^9 * (clk_div + 1)) + */ + rate = clk_get_rate(mdp->clk_main); + clk_div = div_u64(rate * period_ns, NSEC_PER_SEC) >> + PWM_PERIOD_BIT_WIDTH; + if (clk_div > PWM_CLKDIV_MAX) { + clk_disable_unprepare(mdp->clk_mm); + clk_disable_unprepare(mdp->clk_main); + return -EINVAL; + } + + div = NSEC_PER_SEC * (clk_div + 1); + period = div64_u64(rate * period_ns, div); + if (period > 0) + period--; + + high_width = div64_u64(rate * duty_ns, div); + value = period | (high_width << PWM_HIGH_WIDTH_SHIFT); + + if (mdp->data->bls_debug && !mdp->data->has_commit) { + /* + * For MT2701, disable double buffer before writing register + * and select manual mode and use PWM_PERIOD/PWM_HIGH_WIDTH. + */ + mtk_disp_pwm_update_bits(mdp, mdp->data->bls_debug, + mdp->data->bls_debug_mask, + mdp->data->bls_debug_mask); + mtk_disp_pwm_update_bits(mdp, mdp->data->con0, + mdp->data->con0_sel, + mdp->data->con0_sel); + } + + mtk_disp_pwm_update_bits(mdp, mdp->data->con0, + PWM_CLKDIV_MASK, + clk_div << PWM_CLKDIV_SHIFT); + mtk_disp_pwm_update_bits(mdp, mdp->data->con1, + PWM_PERIOD_MASK | PWM_HIGH_WIDTH_MASK, + value); + + if (mdp->data->has_commit) { + mtk_disp_pwm_update_bits(mdp, mdp->data->commit, + mdp->data->commit_mask, + mdp->data->commit_mask); + mtk_disp_pwm_update_bits(mdp, mdp->data->commit, + mdp->data->commit_mask, + 0x0); + } + + clk_disable_unprepare(mdp->clk_mm); + clk_disable_unprepare(mdp->clk_main); + + return 0; +} + +static int mtk_disp_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip); + int err; + + err = clk_prepare_enable(mdp->clk_main); + if (err < 0) { + dev_err(chip->dev, "Can't enable mdp->clk_main: %pe\n", ERR_PTR(err)); + return err; + } + + err = clk_prepare_enable(mdp->clk_mm); + if (err < 0) { + dev_err(chip->dev, "Can't enable mdp->clk_mm: %pe\n", ERR_PTR(err)); + clk_disable_unprepare(mdp->clk_main); + return err; + } + + mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, mdp->data->enable_mask, + mdp->data->enable_mask); + + return 0; +} + +static void mtk_disp_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip); + + mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, mdp->data->enable_mask, + 0x0); + + clk_disable_unprepare(mdp->clk_mm); + clk_disable_unprepare(mdp->clk_main); +} + +static const struct pwm_ops mtk_disp_pwm_ops = { + .config = mtk_disp_pwm_config, + .enable = mtk_disp_pwm_enable, + .disable = mtk_disp_pwm_disable, + .owner = THIS_MODULE, +}; + +static int mtk_disp_pwm_probe(struct platform_device *pdev) +{ + struct mtk_disp_pwm *mdp; + struct resource *r; + int ret; + + mdp = devm_kzalloc(&pdev->dev, sizeof(*mdp), GFP_KERNEL); + if (!mdp) + return -ENOMEM; + + mdp->data = of_device_get_match_data(&pdev->dev); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mdp->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(mdp->base)) + return PTR_ERR(mdp->base); + + mdp->clk_main = devm_clk_get(&pdev->dev, "main"); + if (IS_ERR(mdp->clk_main)) + return PTR_ERR(mdp->clk_main); + + mdp->clk_mm = devm_clk_get(&pdev->dev, "mm"); + if (IS_ERR(mdp->clk_mm)) + return PTR_ERR(mdp->clk_mm); + + mdp->chip.dev = &pdev->dev; + mdp->chip.ops = &mtk_disp_pwm_ops; + mdp->chip.base = -1; + mdp->chip.npwm = 1; + + ret = pwmchip_add(&mdp->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %pe\n", ERR_PTR(ret)); + return ret; + } + + platform_set_drvdata(pdev, mdp); + + return 0; +} + +static int mtk_disp_pwm_remove(struct platform_device *pdev) +{ + struct mtk_disp_pwm *mdp = platform_get_drvdata(pdev); + + pwmchip_remove(&mdp->chip); + + return 0; +} + +static const struct mtk_pwm_data mt2701_pwm_data = { + .enable_mask = BIT(16), + .con0 = 0xa8, + .con0_sel = 0x2, + .con1 = 0xac, + .has_commit = false, + .bls_debug = 0xb0, + .bls_debug_mask = 0x3, +}; + +static const struct mtk_pwm_data mt8173_pwm_data = { + .enable_mask = BIT(0), + .con0 = 0x10, + .con0_sel = 0x0, + .con1 = 0x14, + .has_commit = true, + .commit = 0x8, + .commit_mask = 0x1, +}; + +static const struct mtk_pwm_data mt8183_pwm_data = { + .enable_mask = BIT(0), + .con0 = 0x18, + .con0_sel = 0x0, + .con1 = 0x1c, + .has_commit = false, + .bls_debug = 0x80, + .bls_debug_mask = 0x3, +}; + +static const struct of_device_id mtk_disp_pwm_of_match[] = { + { .compatible = "mediatek,mt2701-disp-pwm", .data = &mt2701_pwm_data}, + { .compatible = "mediatek,mt6595-disp-pwm", .data = &mt8173_pwm_data}, + { .compatible = "mediatek,mt8173-disp-pwm", .data = &mt8173_pwm_data}, + { .compatible = "mediatek,mt8183-disp-pwm", .data = &mt8183_pwm_data}, + { } +}; +MODULE_DEVICE_TABLE(of, mtk_disp_pwm_of_match); + +static struct platform_driver mtk_disp_pwm_driver = { + .driver = { + .name = "mediatek-disp-pwm", + .of_match_table = mtk_disp_pwm_of_match, + }, + .probe = mtk_disp_pwm_probe, + .remove = mtk_disp_pwm_remove, +}; +module_platform_driver(mtk_disp_pwm_driver); + +MODULE_AUTHOR("YH Huang <yh.huang@mediatek.com>"); +MODULE_DESCRIPTION("MediaTek SoC display PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-mxs.c b/drivers/pwm/pwm-mxs.c new file mode 100644 index 000000000..41bdbe71a --- /dev/null +++ b/drivers/pwm/pwm-mxs.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/stmp_device.h> + +#define SET 0x4 +#define CLR 0x8 +#define TOG 0xc + +#define PWM_CTRL 0x0 +#define PWM_ACTIVE0 0x10 +#define PWM_PERIOD0 0x20 +#define PERIOD_PERIOD(p) ((p) & 0xffff) +#define PERIOD_PERIOD_MAX 0x10000 +#define PERIOD_ACTIVE_HIGH (3 << 16) +#define PERIOD_ACTIVE_LOW (2 << 16) +#define PERIOD_INACTIVE_HIGH (3 << 18) +#define PERIOD_INACTIVE_LOW (2 << 18) +#define PERIOD_POLARITY_NORMAL (PERIOD_ACTIVE_HIGH | PERIOD_INACTIVE_LOW) +#define PERIOD_POLARITY_INVERSE (PERIOD_ACTIVE_LOW | PERIOD_INACTIVE_HIGH) +#define PERIOD_CDIV(div) (((div) & 0x7) << 20) +#define PERIOD_CDIV_MAX 8 + +static const u8 cdiv_shift[PERIOD_CDIV_MAX] = { + 0, 1, 2, 3, 4, 6, 8, 10 +}; + +struct mxs_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + void __iomem *base; +}; + +#define to_mxs_pwm_chip(_chip) container_of(_chip, struct mxs_pwm_chip, chip) + +static int mxs_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip); + int ret, div = 0; + unsigned int period_cycles, duty_cycles; + unsigned long rate; + unsigned long long c; + unsigned int pol_bits; + + /* + * If the PWM channel is disabled, make sure to turn on the + * clock before calling clk_get_rate() and writing to the + * registers. Otherwise, just keep it enabled. + */ + if (!pwm_is_enabled(pwm)) { + ret = clk_prepare_enable(mxs->clk); + if (ret) + return ret; + } + + if (!state->enabled && pwm_is_enabled(pwm)) + writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + CLR); + + rate = clk_get_rate(mxs->clk); + while (1) { + c = rate >> cdiv_shift[div]; + c = c * state->period; + do_div(c, 1000000000); + if (c < PERIOD_PERIOD_MAX) + break; + div++; + if (div >= PERIOD_CDIV_MAX) + return -EINVAL; + } + + period_cycles = c; + c *= state->duty_cycle; + do_div(c, state->period); + duty_cycles = c; + + /* + * The data sheet the says registers must be written to in + * this order (ACTIVEn, then PERIODn). Also, the new settings + * only take effect at the beginning of a new period, avoiding + * glitches. + */ + + pol_bits = state->polarity == PWM_POLARITY_NORMAL ? + PERIOD_POLARITY_NORMAL : PERIOD_POLARITY_INVERSE; + writel(duty_cycles << 16, + mxs->base + PWM_ACTIVE0 + pwm->hwpwm * 0x20); + writel(PERIOD_PERIOD(period_cycles) | pol_bits | PERIOD_CDIV(div), + mxs->base + PWM_PERIOD0 + pwm->hwpwm * 0x20); + + if (state->enabled) { + if (!pwm_is_enabled(pwm)) { + /* + * The clock was enabled above. Just enable + * the channel in the control register. + */ + writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + SET); + } + } else { + clk_disable_unprepare(mxs->clk); + } + return 0; +} + +static const struct pwm_ops mxs_pwm_ops = { + .apply = mxs_pwm_apply, + .owner = THIS_MODULE, +}; + +static int mxs_pwm_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mxs_pwm_chip *mxs; + int ret; + + mxs = devm_kzalloc(&pdev->dev, sizeof(*mxs), GFP_KERNEL); + if (!mxs) + return -ENOMEM; + + mxs->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mxs->base)) + return PTR_ERR(mxs->base); + + mxs->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mxs->clk)) + return PTR_ERR(mxs->clk); + + mxs->chip.dev = &pdev->dev; + mxs->chip.ops = &mxs_pwm_ops; + mxs->chip.of_xlate = of_pwm_xlate_with_flags; + mxs->chip.of_pwm_n_cells = 3; + mxs->chip.base = -1; + + ret = of_property_read_u32(np, "fsl,pwm-number", &mxs->chip.npwm); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get pwm number: %d\n", ret); + return ret; + } + + /* FIXME: Only do this if the PWM isn't already running */ + ret = stmp_reset_block(mxs->base); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to reset PWM\n"); + + ret = pwmchip_add(&mxs->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, mxs); + + return 0; +} + +static int mxs_pwm_remove(struct platform_device *pdev) +{ + struct mxs_pwm_chip *mxs = platform_get_drvdata(pdev); + + return pwmchip_remove(&mxs->chip); +} + +static const struct of_device_id mxs_pwm_dt_ids[] = { + { .compatible = "fsl,imx23-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_pwm_dt_ids); + +static struct platform_driver mxs_pwm_driver = { + .driver = { + .name = "mxs-pwm", + .of_match_table = mxs_pwm_dt_ids, + }, + .probe = mxs_pwm_probe, + .remove = mxs_pwm_remove, +}; +module_platform_driver(mxs_pwm_driver); + +MODULE_ALIAS("platform:mxs-pwm"); +MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>"); +MODULE_DESCRIPTION("Freescale MXS PWM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c new file mode 100644 index 000000000..358db4ff9 --- /dev/null +++ b/drivers/pwm/pwm-omap-dmtimer.c @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 Neil Armstrong <narmstrong@baylibre.com> + * Copyright (c) 2014 Joachim Eastwood <manabian@gmail.com> + * Copyright (c) 2012 NeilBrown <neilb@suse.de> + * Heavily based on earlier code which is: + * Copyright (c) 2010 Grant Erickson <marathon96@gmail.com> + * + * Also based on pwm-samsung.c + * + * Description: + * This file is the core OMAP support for the generic, Linux + * PWM driver / controller, using the OMAP's dual-mode timers + * with a timer counter that goes up. When it overflows it gets + * reloaded with the load value and the pwm output goes up. + * When counter matches with match register, the output goes down. + * Reference Manual: https://www.ti.com/lit/ug/spruh73q/spruh73q.pdf + * + * Limitations: + * - When PWM is stopped, timer counter gets stopped immediately. This + * doesn't allow the current PWM period to complete and stops abruptly. + * - When PWM is running and changing both duty cycle and period, + * we cannot prevent in software that the output might produce + * a period with mixed settings. Especially when period/duty_cyle + * is updated while the pwm pin is high, current pwm period/duty_cycle + * can get updated as below based on the current timer counter: + * - period for current cycle = current_period + new period + * - duty_cycle for current period = current period + new duty_cycle. + * - PWM OMAP DM timer cannot change the polarity when pwm is active. When + * user requests a change in polarity when in active state: + * - PWM is stopped abruptly(without completing the current cycle) + * - Polarity is changed + * - A fresh cycle is started. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <clocksource/timer-ti-dm.h> +#include <linux/platform_data/dmtimer-omap.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/time.h> + +#define DM_TIMER_LOAD_MIN 0xfffffffe +#define DM_TIMER_MAX 0xffffffff + +/** + * struct pwm_omap_dmtimer_chip - Structure representing a pwm chip + * corresponding to omap dmtimer. + * @chip: PWM chip structure representing PWM controller + * @mutex: Mutex to protect pwm apply state + * @dm_timer: Pointer to omap dm timer. + * @pdata: Pointer to omap dm timer ops. + * @dm_timer_pdev: Pointer to omap dm timer platform device + */ +struct pwm_omap_dmtimer_chip { + struct pwm_chip chip; + /* Mutex to protect pwm apply state */ + struct mutex mutex; + struct omap_dm_timer *dm_timer; + const struct omap_dm_timer_ops *pdata; + struct platform_device *dm_timer_pdev; +}; + +static inline struct pwm_omap_dmtimer_chip * +to_pwm_omap_dmtimer_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct pwm_omap_dmtimer_chip, chip); +} + +/** + * pwm_omap_dmtimer_get_clock_cycles() - Get clock cycles in a time frame + * @clk_rate: pwm timer clock rate + * @ns: time frame in nano seconds. + * + * Return number of clock cycles in a given period(ins ns). + */ +static u32 pwm_omap_dmtimer_get_clock_cycles(unsigned long clk_rate, int ns) +{ + return DIV_ROUND_CLOSEST_ULL((u64)clk_rate * ns, NSEC_PER_SEC); +} + +/** + * pwm_omap_dmtimer_start() - Start the pwm omap dm timer in pwm mode + * @omap: Pointer to pwm omap dm timer chip + */ +static void pwm_omap_dmtimer_start(struct pwm_omap_dmtimer_chip *omap) +{ + /* + * According to OMAP 4 TRM section 22.2.4.10 the counter should be + * started at 0xFFFFFFFE when overflow and match is used to ensure + * that the PWM line is toggled on the first event. + * + * Note that omap_dm_timer_enable/disable is for register access and + * not the timer counter itself. + */ + omap->pdata->enable(omap->dm_timer); + omap->pdata->write_counter(omap->dm_timer, DM_TIMER_LOAD_MIN); + omap->pdata->disable(omap->dm_timer); + + omap->pdata->start(omap->dm_timer); +} + +/** + * pwm_omap_dmtimer_is_enabled() - Detect if the pwm is enabled. + * @omap: Pointer to pwm omap dm timer chip + * + * Return true if pwm is enabled else false. + */ +static bool pwm_omap_dmtimer_is_enabled(struct pwm_omap_dmtimer_chip *omap) +{ + u32 status; + + status = omap->pdata->get_pwm_status(omap->dm_timer); + + return !!(status & OMAP_TIMER_CTRL_ST); +} + +/** + * pwm_omap_dmtimer_polarity() - Detect the polarity of pwm. + * @omap: Pointer to pwm omap dm timer chip + * + * Return the polarity of pwm. + */ +static int pwm_omap_dmtimer_polarity(struct pwm_omap_dmtimer_chip *omap) +{ + u32 status; + + status = omap->pdata->get_pwm_status(omap->dm_timer); + + return !!(status & OMAP_TIMER_CTRL_SCPWM); +} + +/** + * pwm_omap_dmtimer_config() - Update the configuration of pwm omap dm timer + * @chip: Pointer to PWM controller + * @pwm: Pointer to PWM channel + * @duty_ns: New duty cycle in nano seconds + * @period_ns: New period in nano seconds + * + * Return 0 if successfully changed the period/duty_cycle else appropriate + * error. + */ +static int pwm_omap_dmtimer_config(struct pwm_chip *chip, + struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); + u32 period_cycles, duty_cycles; + u32 load_value, match_value; + unsigned long clk_rate; + struct clk *fclk; + + dev_dbg(chip->dev, "requested duty cycle: %d ns, period: %d ns\n", + duty_ns, period_ns); + + if (duty_ns == pwm_get_duty_cycle(pwm) && + period_ns == pwm_get_period(pwm)) + return 0; + + fclk = omap->pdata->get_fclk(omap->dm_timer); + if (!fclk) { + dev_err(chip->dev, "invalid pmtimer fclk\n"); + return -EINVAL; + } + + clk_rate = clk_get_rate(fclk); + if (!clk_rate) { + dev_err(chip->dev, "invalid pmtimer fclk rate\n"); + return -EINVAL; + } + + dev_dbg(chip->dev, "clk rate: %luHz\n", clk_rate); + + /* + * Calculate the appropriate load and match values based on the + * specified period and duty cycle. The load value determines the + * period time and the match value determines the duty time. + * + * The period lasts for (DM_TIMER_MAX-load_value+1) clock cycles. + * Similarly, the active time lasts (match_value-load_value+1) cycles. + * The non-active time is the remainder: (DM_TIMER_MAX-match_value) + * clock cycles. + * + * NOTE: It is required that: load_value <= match_value < DM_TIMER_MAX + * + * References: + * OMAP4430/60/70 TRM sections 22.2.4.10 and 22.2.4.11 + * AM335x Sitara TRM sections 20.1.3.5 and 20.1.3.6 + */ + period_cycles = pwm_omap_dmtimer_get_clock_cycles(clk_rate, period_ns); + duty_cycles = pwm_omap_dmtimer_get_clock_cycles(clk_rate, duty_ns); + + if (period_cycles < 2) { + dev_info(chip->dev, + "period %d ns too short for clock rate %lu Hz\n", + period_ns, clk_rate); + return -EINVAL; + } + + if (duty_cycles < 1) { + dev_dbg(chip->dev, + "duty cycle %d ns is too short for clock rate %lu Hz\n", + duty_ns, clk_rate); + dev_dbg(chip->dev, "using minimum of 1 clock cycle\n"); + duty_cycles = 1; + } else if (duty_cycles >= period_cycles) { + dev_dbg(chip->dev, + "duty cycle %d ns is too long for period %d ns at clock rate %lu Hz\n", + duty_ns, period_ns, clk_rate); + dev_dbg(chip->dev, "using maximum of 1 clock cycle less than period\n"); + duty_cycles = period_cycles - 1; + } + + dev_dbg(chip->dev, "effective duty cycle: %lld ns, period: %lld ns\n", + DIV_ROUND_CLOSEST_ULL((u64)NSEC_PER_SEC * duty_cycles, + clk_rate), + DIV_ROUND_CLOSEST_ULL((u64)NSEC_PER_SEC * period_cycles, + clk_rate)); + + load_value = (DM_TIMER_MAX - period_cycles) + 1; + match_value = load_value + duty_cycles - 1; + + omap->pdata->set_load(omap->dm_timer, load_value); + omap->pdata->set_match(omap->dm_timer, true, match_value); + + dev_dbg(chip->dev, "load value: %#08x (%d), match value: %#08x (%d)\n", + load_value, load_value, match_value, match_value); + + return 0; +} + +/** + * pwm_omap_dmtimer_set_polarity() - Changes the polarity of the pwm dm timer. + * @chip: Pointer to PWM controller + * @pwm: Pointer to PWM channel + * @polarity: New pwm polarity to be set + */ +static void pwm_omap_dmtimer_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); + bool enabled; + + /* Disable the PWM before changing the polarity. */ + enabled = pwm_omap_dmtimer_is_enabled(omap); + if (enabled) + omap->pdata->stop(omap->dm_timer); + + omap->pdata->set_pwm(omap->dm_timer, + polarity == PWM_POLARITY_INVERSED, + true, OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE, + true); + + if (enabled) + pwm_omap_dmtimer_start(omap); +} + +/** + * pwm_omap_dmtimer_apply() - Changes the state of the pwm omap dm timer. + * @chip: Pointer to PWM controller + * @pwm: Pointer to PWM channel + * @state: New state to apply + * + * Return 0 if successfully changed the state else appropriate error. + */ +static int pwm_omap_dmtimer_apply(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); + int ret = 0; + + mutex_lock(&omap->mutex); + + if (pwm_omap_dmtimer_is_enabled(omap) && !state->enabled) { + omap->pdata->stop(omap->dm_timer); + goto unlock_mutex; + } + + if (pwm_omap_dmtimer_polarity(omap) != state->polarity) + pwm_omap_dmtimer_set_polarity(chip, pwm, state->polarity); + + ret = pwm_omap_dmtimer_config(chip, pwm, state->duty_cycle, + state->period); + if (ret) + goto unlock_mutex; + + if (!pwm_omap_dmtimer_is_enabled(omap) && state->enabled) { + omap->pdata->set_pwm(omap->dm_timer, + state->polarity == PWM_POLARITY_INVERSED, + true, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE, + true); + pwm_omap_dmtimer_start(omap); + } + +unlock_mutex: + mutex_unlock(&omap->mutex); + + return ret; +} + +static const struct pwm_ops pwm_omap_dmtimer_ops = { + .apply = pwm_omap_dmtimer_apply, + .owner = THIS_MODULE, +}; + +static int pwm_omap_dmtimer_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct dmtimer_platform_data *timer_pdata; + const struct omap_dm_timer_ops *pdata; + struct platform_device *timer_pdev; + struct pwm_omap_dmtimer_chip *omap; + struct omap_dm_timer *dm_timer; + struct device_node *timer; + int ret = 0; + u32 v; + + timer = of_parse_phandle(np, "ti,timers", 0); + if (!timer) + return -ENODEV; + + timer_pdev = of_find_device_by_node(timer); + if (!timer_pdev) { + dev_err(&pdev->dev, "Unable to find Timer pdev\n"); + ret = -ENODEV; + goto err_find_timer_pdev; + } + + timer_pdata = dev_get_platdata(&timer_pdev->dev); + if (!timer_pdata) { + dev_dbg(&pdev->dev, + "dmtimer pdata structure NULL, deferring probe\n"); + ret = -EPROBE_DEFER; + goto err_platdata; + } + + pdata = timer_pdata->timer_ops; + + if (!pdata || !pdata->request_by_node || + !pdata->free || + !pdata->enable || + !pdata->disable || + !pdata->get_fclk || + !pdata->start || + !pdata->stop || + !pdata->set_load || + !pdata->set_match || + !pdata->set_pwm || + !pdata->get_pwm_status || + !pdata->set_prescaler || + !pdata->write_counter) { + dev_err(&pdev->dev, "Incomplete dmtimer pdata structure\n"); + ret = -EINVAL; + goto err_platdata; + } + + if (!of_get_property(timer, "ti,timer-pwm", NULL)) { + dev_err(&pdev->dev, "Missing ti,timer-pwm capability\n"); + ret = -ENODEV; + goto err_timer_property; + } + + dm_timer = pdata->request_by_node(timer); + if (!dm_timer) { + ret = -EPROBE_DEFER; + goto err_request_timer; + } + + omap = devm_kzalloc(&pdev->dev, sizeof(*omap), GFP_KERNEL); + if (!omap) { + ret = -ENOMEM; + goto err_alloc_omap; + } + + omap->pdata = pdata; + omap->dm_timer = dm_timer; + omap->dm_timer_pdev = timer_pdev; + + /* + * Ensure that the timer is stopped before we allow PWM core to call + * pwm_enable. + */ + if (pm_runtime_active(&omap->dm_timer_pdev->dev)) + omap->pdata->stop(omap->dm_timer); + + if (!of_property_read_u32(pdev->dev.of_node, "ti,prescaler", &v)) + omap->pdata->set_prescaler(omap->dm_timer, v); + + /* setup dmtimer clock source */ + if (!of_property_read_u32(pdev->dev.of_node, "ti,clock-source", &v)) + omap->pdata->set_source(omap->dm_timer, v); + + omap->chip.dev = &pdev->dev; + omap->chip.ops = &pwm_omap_dmtimer_ops; + omap->chip.base = -1; + omap->chip.npwm = 1; + omap->chip.of_xlate = of_pwm_xlate_with_flags; + omap->chip.of_pwm_n_cells = 3; + + mutex_init(&omap->mutex); + + ret = pwmchip_add(&omap->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register PWM\n"); + goto err_pwmchip_add; + } + + of_node_put(timer); + + platform_set_drvdata(pdev, omap); + + return 0; + +err_pwmchip_add: + + /* + * *omap is allocated using devm_kzalloc, + * so no free necessary here + */ +err_alloc_omap: + + pdata->free(dm_timer); +err_request_timer: + +err_timer_property: +err_platdata: + + put_device(&timer_pdev->dev); +err_find_timer_pdev: + + of_node_put(timer); + + return ret; +} + +static int pwm_omap_dmtimer_remove(struct platform_device *pdev) +{ + struct pwm_omap_dmtimer_chip *omap = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&omap->chip); + if (ret) + return ret; + + if (pm_runtime_active(&omap->dm_timer_pdev->dev)) + omap->pdata->stop(omap->dm_timer); + + omap->pdata->free(omap->dm_timer); + + put_device(&omap->dm_timer_pdev->dev); + + mutex_destroy(&omap->mutex); + + return 0; +} + +static const struct of_device_id pwm_omap_dmtimer_of_match[] = { + {.compatible = "ti,omap-dmtimer-pwm"}, + {} +}; +MODULE_DEVICE_TABLE(of, pwm_omap_dmtimer_of_match); + +static struct platform_driver pwm_omap_dmtimer_driver = { + .driver = { + .name = "omap-dmtimer-pwm", + .of_match_table = of_match_ptr(pwm_omap_dmtimer_of_match), + }, + .probe = pwm_omap_dmtimer_probe, + .remove = pwm_omap_dmtimer_remove, +}; +module_platform_driver(pwm_omap_dmtimer_driver); + +MODULE_AUTHOR("Grant Erickson <marathon96@gmail.com>"); +MODULE_AUTHOR("NeilBrown <neilb@suse.de>"); +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("OMAP PWM Driver using Dual-mode Timers"); diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c new file mode 100644 index 000000000..4a55dc186 --- /dev/null +++ b/drivers/pwm/pwm-pca9685.c @@ -0,0 +1,592 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for PCA9685 16-channel 12-bit PWM LED controller + * + * Copyright (C) 2013 Steffen Trumtrar <s.trumtrar@pengutronix.de> + * Copyright (C) 2015 Clemens Gruber <clemens.gruber@pqgruber.com> + * + * 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> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/pwm.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <linux/bitmap.h> + +/* + * Because the PCA9685 has only one prescaler per chip, changing the period of + * one channel affects the period of all 16 PWM outputs! + * However, the ratio between each configured duty cycle and the chip-wide + * period remains constant, because the OFF time is set in proportion to the + * counter range. + */ + +#define PCA9685_MODE1 0x00 +#define PCA9685_MODE2 0x01 +#define PCA9685_SUBADDR1 0x02 +#define PCA9685_SUBADDR2 0x03 +#define PCA9685_SUBADDR3 0x04 +#define PCA9685_ALLCALLADDR 0x05 +#define PCA9685_LEDX_ON_L 0x06 +#define PCA9685_LEDX_ON_H 0x07 +#define PCA9685_LEDX_OFF_L 0x08 +#define PCA9685_LEDX_OFF_H 0x09 + +#define PCA9685_ALL_LED_ON_L 0xFA +#define PCA9685_ALL_LED_ON_H 0xFB +#define PCA9685_ALL_LED_OFF_L 0xFC +#define PCA9685_ALL_LED_OFF_H 0xFD +#define PCA9685_PRESCALE 0xFE + +#define PCA9685_PRESCALE_MIN 0x03 /* => max. frequency of 1526 Hz */ +#define PCA9685_PRESCALE_MAX 0xFF /* => min. frequency of 24 Hz */ + +#define PCA9685_COUNTER_RANGE 4096 +#define PCA9685_DEFAULT_PERIOD 5000000 /* Default period_ns = 1/200 Hz */ +#define PCA9685_OSC_CLOCK_MHZ 25 /* Internal oscillator with 25 MHz */ + +#define PCA9685_NUMREGS 0xFF +#define PCA9685_MAXCHAN 0x10 + +#define LED_FULL BIT(4) +#define MODE1_ALLCALL BIT(0) +#define MODE1_SUB3 BIT(1) +#define MODE1_SUB2 BIT(2) +#define MODE1_SUB1 BIT(3) +#define MODE1_SLEEP BIT(4) +#define MODE2_INVRT BIT(4) +#define MODE2_OUTDRV BIT(2) + +#define LED_N_ON_H(N) (PCA9685_LEDX_ON_H + (4 * (N))) +#define LED_N_ON_L(N) (PCA9685_LEDX_ON_L + (4 * (N))) +#define LED_N_OFF_H(N) (PCA9685_LEDX_OFF_H + (4 * (N))) +#define LED_N_OFF_L(N) (PCA9685_LEDX_OFF_L + (4 * (N))) + +struct pca9685 { + struct pwm_chip chip; + struct regmap *regmap; + int period_ns; +#if IS_ENABLED(CONFIG_GPIOLIB) + struct mutex lock; + struct gpio_chip gpio; + DECLARE_BITMAP(pwms_inuse, PCA9685_MAXCHAN + 1); +#endif +}; + +static inline struct pca9685 *to_pca(struct pwm_chip *chip) +{ + return container_of(chip, struct pca9685, chip); +} + +#if IS_ENABLED(CONFIG_GPIOLIB) +static bool pca9685_pwm_test_and_set_inuse(struct pca9685 *pca, int pwm_idx) +{ + bool is_inuse; + + mutex_lock(&pca->lock); + if (pwm_idx >= PCA9685_MAXCHAN) { + /* + * "All LEDs" channel: + * pretend already in use if any of the PWMs are requested + */ + if (!bitmap_empty(pca->pwms_inuse, PCA9685_MAXCHAN)) { + is_inuse = true; + goto out; + } + } else { + /* + * Regular channel: + * pretend already in use if the "all LEDs" channel is requested + */ + if (test_bit(PCA9685_MAXCHAN, pca->pwms_inuse)) { + is_inuse = true; + goto out; + } + } + is_inuse = test_and_set_bit(pwm_idx, pca->pwms_inuse); +out: + mutex_unlock(&pca->lock); + return is_inuse; +} + +static void pca9685_pwm_clear_inuse(struct pca9685 *pca, int pwm_idx) +{ + mutex_lock(&pca->lock); + clear_bit(pwm_idx, pca->pwms_inuse); + mutex_unlock(&pca->lock); +} + +static int pca9685_pwm_gpio_request(struct gpio_chip *gpio, unsigned int offset) +{ + struct pca9685 *pca = gpiochip_get_data(gpio); + + if (pca9685_pwm_test_and_set_inuse(pca, offset)) + return -EBUSY; + pm_runtime_get_sync(pca->chip.dev); + return 0; +} + +static int pca9685_pwm_gpio_get(struct gpio_chip *gpio, unsigned int offset) +{ + struct pca9685 *pca = gpiochip_get_data(gpio); + struct pwm_device *pwm = &pca->chip.pwms[offset]; + unsigned int value; + + regmap_read(pca->regmap, LED_N_ON_H(pwm->hwpwm), &value); + + return value & LED_FULL; +} + +static void pca9685_pwm_gpio_set(struct gpio_chip *gpio, unsigned int offset, + int value) +{ + struct pca9685 *pca = gpiochip_get_data(gpio); + struct pwm_device *pwm = &pca->chip.pwms[offset]; + unsigned int on = value ? LED_FULL : 0; + + /* Clear both OFF registers */ + regmap_write(pca->regmap, LED_N_OFF_L(pwm->hwpwm), 0); + regmap_write(pca->regmap, LED_N_OFF_H(pwm->hwpwm), 0); + + /* Set the full ON bit */ + regmap_write(pca->regmap, LED_N_ON_H(pwm->hwpwm), on); +} + +static void pca9685_pwm_gpio_free(struct gpio_chip *gpio, unsigned int offset) +{ + struct pca9685 *pca = gpiochip_get_data(gpio); + + pca9685_pwm_gpio_set(gpio, offset, 0); + pm_runtime_put(pca->chip.dev); + pca9685_pwm_clear_inuse(pca, offset); +} + +static int pca9685_pwm_gpio_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + /* Always out */ + return GPIO_LINE_DIRECTION_OUT; +} + +static int pca9685_pwm_gpio_direction_input(struct gpio_chip *gpio, + unsigned int offset) +{ + return -EINVAL; +} + +static int pca9685_pwm_gpio_direction_output(struct gpio_chip *gpio, + unsigned int offset, int value) +{ + pca9685_pwm_gpio_set(gpio, offset, value); + + return 0; +} + +/* + * The PCA9685 has a bit for turning the PWM output full off or on. Some + * boards like Intel Galileo actually uses these as normal GPIOs so we + * expose a GPIO chip here which can exclusively take over the underlying + * PWM channel. + */ +static int pca9685_pwm_gpio_probe(struct pca9685 *pca) +{ + struct device *dev = pca->chip.dev; + + mutex_init(&pca->lock); + + pca->gpio.label = dev_name(dev); + pca->gpio.parent = dev; + pca->gpio.request = pca9685_pwm_gpio_request; + pca->gpio.free = pca9685_pwm_gpio_free; + pca->gpio.get_direction = pca9685_pwm_gpio_get_direction; + 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.base = -1; + pca->gpio.ngpio = PCA9685_MAXCHAN; + pca->gpio.can_sleep = true; + + return devm_gpiochip_add_data(dev, &pca->gpio, pca); +} +#else +static inline bool pca9685_pwm_test_and_set_inuse(struct pca9685 *pca, + int pwm_idx) +{ + return false; +} + +static inline void +pca9685_pwm_clear_inuse(struct pca9685 *pca, int pwm_idx) +{ +} + +static inline int pca9685_pwm_gpio_probe(struct pca9685 *pca) +{ + return 0; +} +#endif + +static void pca9685_set_sleep_mode(struct pca9685 *pca, bool enable) +{ + regmap_update_bits(pca->regmap, PCA9685_MODE1, + MODE1_SLEEP, enable ? MODE1_SLEEP : 0); + if (!enable) { + /* Wait 500us for the oscillator to be back up */ + udelay(500); + } +} + +static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct pca9685 *pca = to_pca(chip); + unsigned long long duty; + unsigned int reg; + int prescale; + + if (period_ns != pca->period_ns) { + prescale = DIV_ROUND_CLOSEST(PCA9685_OSC_CLOCK_MHZ * period_ns, + PCA9685_COUNTER_RANGE * 1000) - 1; + + if (prescale >= PCA9685_PRESCALE_MIN && + prescale <= PCA9685_PRESCALE_MAX) { + /* + * Putting the chip briefly into SLEEP mode + * at this point won't interfere with the + * pm_runtime framework, because the pm_runtime + * state is guaranteed active here. + */ + /* Put chip into sleep mode */ + pca9685_set_sleep_mode(pca, true); + + /* Change the chip-wide output frequency */ + regmap_write(pca->regmap, PCA9685_PRESCALE, prescale); + + /* Wake the chip up */ + pca9685_set_sleep_mode(pca, false); + + pca->period_ns = period_ns; + } else { + dev_err(chip->dev, + "prescaler not set: period out of bounds!\n"); + return -EINVAL; + } + } + + if (duty_ns < 1) { + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_H; + else + reg = LED_N_OFF_H(pwm->hwpwm); + + regmap_write(pca->regmap, reg, LED_FULL); + + return 0; + } + + if (duty_ns == period_ns) { + /* Clear both OFF registers */ + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_L; + else + reg = LED_N_OFF_L(pwm->hwpwm); + + regmap_write(pca->regmap, reg, 0x0); + + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_H; + else + reg = LED_N_OFF_H(pwm->hwpwm); + + regmap_write(pca->regmap, reg, 0x0); + + /* Set the full ON bit */ + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_ON_H; + else + reg = LED_N_ON_H(pwm->hwpwm); + + regmap_write(pca->regmap, reg, LED_FULL); + + return 0; + } + + duty = PCA9685_COUNTER_RANGE * (unsigned long long)duty_ns; + duty = DIV_ROUND_UP_ULL(duty, period_ns); + + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_L; + else + reg = LED_N_OFF_L(pwm->hwpwm); + + regmap_write(pca->regmap, reg, (int)duty & 0xff); + + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_H; + else + reg = LED_N_OFF_H(pwm->hwpwm); + + regmap_write(pca->regmap, reg, ((int)duty >> 8) & 0xf); + + /* Clear the full ON bit, otherwise the set OFF time has no effect */ + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_ON_H; + else + reg = LED_N_ON_H(pwm->hwpwm); + + regmap_write(pca->regmap, reg, 0); + + return 0; +} + +static int pca9685_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pca9685 *pca = to_pca(chip); + unsigned int reg; + + /* + * The PWM subsystem does not support a pre-delay. + * So, set the ON-timeout to 0 + */ + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_ON_L; + else + reg = LED_N_ON_L(pwm->hwpwm); + + regmap_write(pca->regmap, reg, 0); + + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_ON_H; + else + reg = LED_N_ON_H(pwm->hwpwm); + + regmap_write(pca->regmap, reg, 0); + + /* + * Clear the full-off bit. + * It has precedence over the others and must be off. + */ + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_H; + else + reg = LED_N_OFF_H(pwm->hwpwm); + + regmap_update_bits(pca->regmap, reg, LED_FULL, 0x0); + + return 0; +} + +static void pca9685_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pca9685 *pca = to_pca(chip); + unsigned int reg; + + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_H; + else + reg = LED_N_OFF_H(pwm->hwpwm); + + regmap_write(pca->regmap, reg, LED_FULL); + + /* Clear the LED_OFF counter. */ + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_L; + else + reg = LED_N_OFF_L(pwm->hwpwm); + + regmap_write(pca->regmap, reg, 0x0); +} + +static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pca9685 *pca = to_pca(chip); + + if (pca9685_pwm_test_and_set_inuse(pca, pwm->hwpwm)) + return -EBUSY; + pm_runtime_get_sync(chip->dev); + + return 0; +} + +static void pca9685_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pca9685 *pca = to_pca(chip); + + pca9685_pwm_disable(chip, pwm); + pm_runtime_put(chip->dev); + pca9685_pwm_clear_inuse(pca, pwm->hwpwm); +} + +static const struct pwm_ops pca9685_pwm_ops = { + .enable = pca9685_pwm_enable, + .disable = pca9685_pwm_disable, + .config = pca9685_pwm_config, + .request = pca9685_pwm_request, + .free = pca9685_pwm_free, + .owner = THIS_MODULE, +}; + +static const struct regmap_config pca9685_regmap_i2c_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PCA9685_NUMREGS, + .cache_type = REGCACHE_NONE, +}; + +static int pca9685_pwm_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pca9685 *pca; + unsigned int reg; + int ret; + + pca = devm_kzalloc(&client->dev, sizeof(*pca), GFP_KERNEL); + if (!pca) + return -ENOMEM; + + pca->regmap = devm_regmap_init_i2c(client, &pca9685_regmap_i2c_config); + if (IS_ERR(pca->regmap)) { + ret = PTR_ERR(pca->regmap); + dev_err(&client->dev, "Failed to initialize register map: %d\n", + ret); + return ret; + } + pca->period_ns = PCA9685_DEFAULT_PERIOD; + + i2c_set_clientdata(client, pca); + + regmap_read(pca->regmap, PCA9685_MODE2, ®); + + if (device_property_read_bool(&client->dev, "invert")) + reg |= MODE2_INVRT; + else + reg &= ~MODE2_INVRT; + + if (device_property_read_bool(&client->dev, "open-drain")) + reg &= ~MODE2_OUTDRV; + else + reg |= MODE2_OUTDRV; + + regmap_write(pca->regmap, PCA9685_MODE2, reg); + + /* Disable all LED ALLCALL and SUBx addresses to avoid bus collisions */ + regmap_read(pca->regmap, PCA9685_MODE1, ®); + reg &= ~(MODE1_ALLCALL | MODE1_SUB1 | MODE1_SUB2 | MODE1_SUB3); + regmap_write(pca->regmap, PCA9685_MODE1, reg); + + /* Clear all "full off" bits */ + regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_L, 0); + regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_H, 0); + + pca->chip.ops = &pca9685_pwm_ops; + /* Add an extra channel for ALL_LED */ + pca->chip.npwm = PCA9685_MAXCHAN + 1; + + pca->chip.dev = &client->dev; + pca->chip.base = -1; + + ret = pwmchip_add(&pca->chip); + if (ret < 0) + return ret; + + ret = pca9685_pwm_gpio_probe(pca); + if (ret < 0) { + pwmchip_remove(&pca->chip); + return ret; + } + + /* The chip comes out of power-up in the active state */ + pm_runtime_set_active(&client->dev); + /* + * Enable will put the chip into suspend, which is what we + * want as all outputs are disabled at this point + */ + pm_runtime_enable(&client->dev); + + return 0; +} + +static int pca9685_pwm_remove(struct i2c_client *client) +{ + struct pca9685 *pca = i2c_get_clientdata(client); + int ret; + + ret = pwmchip_remove(&pca->chip); + if (ret) + return ret; + pm_runtime_disable(&client->dev); + return 0; +} + +static int __maybe_unused pca9685_pwm_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pca9685 *pca = i2c_get_clientdata(client); + + pca9685_set_sleep_mode(pca, true); + return 0; +} + +static int __maybe_unused pca9685_pwm_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pca9685 *pca = i2c_get_clientdata(client); + + pca9685_set_sleep_mode(pca, false); + return 0; +} + +static const struct i2c_device_id pca9685_id[] = { + { "pca9685", 0 }, + { /* 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, + pca9685_pwm_runtime_resume, NULL) +}; + +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), + .pm = &pca9685_pwm_pm, + }, + .probe = pca9685_pwm_probe, + .remove = pca9685_pwm_remove, + .id_table = pca9685_id, +}; + +module_i2c_driver(pca9685_i2c_driver); + +MODULE_AUTHOR("Steffen Trumtrar <s.trumtrar@pengutronix.de>"); +MODULE_DESCRIPTION("PWM driver for PCA9685"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c new file mode 100644 index 000000000..a2a0912c2 --- /dev/null +++ b/drivers/pwm/pwm-pxa.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/pwm/pwm-pxa.c + * + * simple driver for PWM (Pulse Width Modulator) controller + * + * 2008-02-13 initial version + * eric miao <eric.miao@marvell.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/pwm.h> +#include <linux/of_device.h> + +#include <asm/div64.h> + +#define HAS_SECONDARY_PWM 0x10 + +static const struct platform_device_id pwm_id_table[] = { + /* PWM has_secondary_pwm? */ + { "pxa25x-pwm", 0 }, + { "pxa27x-pwm", HAS_SECONDARY_PWM }, + { "pxa168-pwm", 0 }, + { "pxa910-pwm", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, pwm_id_table); + +/* PWM registers and bits definitions */ +#define PWMCR (0x00) +#define PWMDCR (0x04) +#define PWMPCR (0x08) + +#define PWMCR_SD (1 << 6) +#define PWMDCR_FD (1 << 10) + +struct pxa_pwm_chip { + struct pwm_chip chip; + struct device *dev; + + struct clk *clk; + void __iomem *mmio_base; +}; + +static inline struct pxa_pwm_chip *to_pxa_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct pxa_pwm_chip, chip); +} + +/* + * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE + * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE + */ +static int pxa_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip); + unsigned long long c; + unsigned long period_cycles, prescale, pv, dc; + unsigned long offset; + int rc; + + offset = pwm->hwpwm ? 0x10 : 0; + + c = clk_get_rate(pc->clk); + c = c * period_ns; + do_div(c, 1000000000); + period_cycles = c; + + if (period_cycles < 1) + period_cycles = 1; + prescale = (period_cycles - 1) / 1024; + pv = period_cycles / (prescale + 1) - 1; + + if (prescale > 63) + return -EINVAL; + + if (duty_ns == period_ns) + dc = PWMDCR_FD; + else + dc = (pv + 1) * duty_ns / period_ns; + + /* NOTE: the clock to PWM has to be enabled first + * before writing to the registers + */ + rc = clk_prepare_enable(pc->clk); + if (rc < 0) + return rc; + + writel(prescale, pc->mmio_base + offset + PWMCR); + writel(dc, pc->mmio_base + offset + PWMDCR); + writel(pv, pc->mmio_base + offset + PWMPCR); + + clk_disable_unprepare(pc->clk); + return 0; +} + +static int pxa_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip); + + return clk_prepare_enable(pc->clk); +} + +static void pxa_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip); + + clk_disable_unprepare(pc->clk); +} + +static const struct pwm_ops pxa_pwm_ops = { + .config = pxa_pwm_config, + .enable = pxa_pwm_enable, + .disable = pxa_pwm_disable, + .owner = THIS_MODULE, +}; + +#ifdef CONFIG_OF +/* + * Device tree users must create one device instance for each PWM channel. + * Hence we dispense with the HAS_SECONDARY_PWM and "tell" the original driver + * code that this is a single channel pxa25x-pwm. Currently all devices are + * supported identically. + */ +static const struct of_device_id pwm_of_match[] = { + { .compatible = "marvell,pxa250-pwm", .data = &pwm_id_table[0]}, + { .compatible = "marvell,pxa270-pwm", .data = &pwm_id_table[0]}, + { .compatible = "marvell,pxa168-pwm", .data = &pwm_id_table[0]}, + { .compatible = "marvell,pxa910-pwm", .data = &pwm_id_table[0]}, + { } +}; +MODULE_DEVICE_TABLE(of, pwm_of_match); +#else +#define pwm_of_match NULL +#endif + +static const struct platform_device_id *pxa_pwm_get_id_dt(struct device *dev) +{ + const struct of_device_id *id = of_match_device(pwm_of_match, dev); + + return id ? id->data : NULL; +} + +static struct pwm_device * +pxa_pwm_of_xlate(struct pwm_chip *pc, const struct of_phandle_args *args) +{ + struct pwm_device *pwm; + + pwm = pwm_request_from_chip(pc, 0, NULL); + if (IS_ERR(pwm)) + return pwm; + + pwm->args.period = args->args[0]; + + return pwm; +} + +static int pwm_probe(struct platform_device *pdev) +{ + const struct platform_device_id *id = platform_get_device_id(pdev); + struct pxa_pwm_chip *pwm; + struct resource *r; + int ret = 0; + + if (IS_ENABLED(CONFIG_OF) && id == NULL) + id = pxa_pwm_get_id_dt(&pdev->dev); + + if (id == NULL) + return -EINVAL; + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (pwm == NULL) + return -ENOMEM; + + pwm->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pwm->clk)) + return PTR_ERR(pwm->clk); + + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &pxa_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = (id->driver_data & HAS_SECONDARY_PWM) ? 2 : 1; + + if (IS_ENABLED(CONFIG_OF)) { + pwm->chip.of_xlate = pxa_pwm_of_xlate; + pwm->chip.of_pwm_n_cells = 1; + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pwm->mmio_base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(pwm->mmio_base)) + return PTR_ERR(pwm->mmio_base); + + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, pwm); + return 0; +} + +static int pwm_remove(struct platform_device *pdev) +{ + struct pxa_pwm_chip *chip; + + chip = platform_get_drvdata(pdev); + if (chip == NULL) + return -ENODEV; + + return pwmchip_remove(&chip->chip); +} + +static struct platform_driver pwm_driver = { + .driver = { + .name = "pxa25x-pwm", + .of_match_table = pwm_of_match, + }, + .probe = pwm_probe, + .remove = pwm_remove, + .id_table = pwm_id_table, +}; + +module_platform_driver(pwm_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-rcar.c b/drivers/pwm/pwm-rcar.c new file mode 100644 index 000000000..7ab9eb661 --- /dev/null +++ b/drivers/pwm/pwm-rcar.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * R-Car PWM Timer driver + * + * Copyright (C) 2015 Renesas Electronics Corporation + * + * Limitations: + * - The hardware cannot generate a 0% duty cycle. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/log2.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +#define RCAR_PWM_MAX_DIVISION 24 +#define RCAR_PWM_MAX_CYCLE 1023 + +#define RCAR_PWMCR 0x00 +#define RCAR_PWMCR_CC0_MASK 0x000f0000 +#define RCAR_PWMCR_CC0_SHIFT 16 +#define RCAR_PWMCR_CCMD BIT(15) +#define RCAR_PWMCR_SYNC BIT(11) +#define RCAR_PWMCR_SS0 BIT(4) +#define RCAR_PWMCR_EN0 BIT(0) + +#define RCAR_PWMCNT 0x04 +#define RCAR_PWMCNT_CYC0_MASK 0x03ff0000 +#define RCAR_PWMCNT_CYC0_SHIFT 16 +#define RCAR_PWMCNT_PH0_MASK 0x000003ff +#define RCAR_PWMCNT_PH0_SHIFT 0 + +struct rcar_pwm_chip { + struct pwm_chip chip; + void __iomem *base; + struct clk *clk; +}; + +static inline struct rcar_pwm_chip *to_rcar_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct rcar_pwm_chip, chip); +} + +static void rcar_pwm_write(struct rcar_pwm_chip *rp, u32 data, + unsigned int offset) +{ + writel(data, rp->base + offset); +} + +static u32 rcar_pwm_read(struct rcar_pwm_chip *rp, unsigned int offset) +{ + return readl(rp->base + offset); +} + +static void rcar_pwm_update(struct rcar_pwm_chip *rp, u32 mask, u32 data, + unsigned int offset) +{ + u32 value; + + value = rcar_pwm_read(rp, offset); + value &= ~mask; + value |= data & mask; + rcar_pwm_write(rp, value, offset); +} + +static int rcar_pwm_get_clock_division(struct rcar_pwm_chip *rp, int period_ns) +{ + unsigned long clk_rate = clk_get_rate(rp->clk); + u64 div, tmp; + + if (clk_rate == 0) + return -EINVAL; + + div = (u64)NSEC_PER_SEC * RCAR_PWM_MAX_CYCLE; + tmp = (u64)period_ns * clk_rate + div - 1; + tmp = div64_u64(tmp, div); + div = ilog2(tmp - 1) + 1; + + return (div <= RCAR_PWM_MAX_DIVISION) ? div : -ERANGE; +} + +static void rcar_pwm_set_clock_control(struct rcar_pwm_chip *rp, + unsigned int div) +{ + u32 value; + + value = rcar_pwm_read(rp, RCAR_PWMCR); + value &= ~(RCAR_PWMCR_CCMD | RCAR_PWMCR_CC0_MASK); + + if (div & 1) + value |= RCAR_PWMCR_CCMD; + + div >>= 1; + + value |= div << RCAR_PWMCR_CC0_SHIFT; + 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) +{ + unsigned long long one_cycle, tmp; /* 0.01 nanoseconds */ + unsigned long clk_rate = clk_get_rate(rp->clk); + u32 cyc, ph; + + one_cycle = (unsigned long long)NSEC_PER_SEC * 100ULL * (1 << div); + do_div(one_cycle, clk_rate); + + tmp = period_ns * 100ULL; + do_div(tmp, one_cycle); + cyc = (tmp << RCAR_PWMCNT_CYC0_SHIFT) & RCAR_PWMCNT_CYC0_MASK; + + tmp = duty_ns * 100ULL; + do_div(tmp, one_cycle); + ph = tmp & RCAR_PWMCNT_PH0_MASK; + + /* Avoid prohibited setting */ + if (cyc == 0 || ph == 0) + return -EINVAL; + + rcar_pwm_write(rp, cyc | ph, RCAR_PWMCNT); + + return 0; +} + +static int rcar_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + return pm_runtime_get_sync(chip->dev); +} + +static void rcar_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + pm_runtime_put(chip->dev); +} + +static int rcar_pwm_enable(struct rcar_pwm_chip *rp) +{ + u32 value; + + /* Don't enable the PWM device if CYC0 or PH0 is 0 */ + value = rcar_pwm_read(rp, RCAR_PWMCNT); + if ((value & RCAR_PWMCNT_CYC0_MASK) == 0 || + (value & RCAR_PWMCNT_PH0_MASK) == 0) + return -EINVAL; + + rcar_pwm_update(rp, RCAR_PWMCR_EN0, RCAR_PWMCR_EN0, RCAR_PWMCR); + + return 0; +} + +static void rcar_pwm_disable(struct rcar_pwm_chip *rp) +{ + rcar_pwm_update(rp, RCAR_PWMCR_EN0, 0, RCAR_PWMCR); +} + +static int rcar_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip); + int div, ret; + + /* This HW/driver only supports normal polarity */ + if (state->polarity != PWM_POLARITY_NORMAL) + return -ENOTSUPP; + + if (!state->enabled) { + rcar_pwm_disable(rp); + return 0; + } + + div = rcar_pwm_get_clock_division(rp, state->period); + if (div < 0) + return div; + + rcar_pwm_update(rp, RCAR_PWMCR_SYNC, RCAR_PWMCR_SYNC, RCAR_PWMCR); + + ret = rcar_pwm_set_counter(rp, div, state->duty_cycle, state->period); + if (!ret) + rcar_pwm_set_clock_control(rp, div); + + /* The SYNC should be set to 0 even if rcar_pwm_set_counter failed */ + rcar_pwm_update(rp, RCAR_PWMCR_SYNC, 0, RCAR_PWMCR); + + if (!ret) + ret = rcar_pwm_enable(rp); + + return ret; +} + +static const struct pwm_ops rcar_pwm_ops = { + .request = rcar_pwm_request, + .free = rcar_pwm_free, + .apply = rcar_pwm_apply, + .owner = THIS_MODULE, +}; + +static int rcar_pwm_probe(struct platform_device *pdev) +{ + struct rcar_pwm_chip *rcar_pwm; + struct resource *res; + int ret; + + rcar_pwm = devm_kzalloc(&pdev->dev, sizeof(*rcar_pwm), GFP_KERNEL); + if (rcar_pwm == NULL) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rcar_pwm->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(rcar_pwm->base)) + return PTR_ERR(rcar_pwm->base); + + rcar_pwm->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(rcar_pwm->clk)) { + dev_err(&pdev->dev, "cannot get clock\n"); + return PTR_ERR(rcar_pwm->clk); + } + + platform_set_drvdata(pdev, rcar_pwm); + + rcar_pwm->chip.dev = &pdev->dev; + rcar_pwm->chip.ops = &rcar_pwm_ops; + rcar_pwm->chip.base = -1; + rcar_pwm->chip.npwm = 1; + + pm_runtime_enable(&pdev->dev); + + ret = pwmchip_add(&rcar_pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register PWM chip: %d\n", ret); + pm_runtime_disable(&pdev->dev); + return ret; + } + + return 0; +} + +static int rcar_pwm_remove(struct platform_device *pdev) +{ + struct rcar_pwm_chip *rcar_pwm = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&rcar_pwm->chip); + + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static const struct of_device_id rcar_pwm_of_table[] = { + { .compatible = "renesas,pwm-rcar", }, + { }, +}; +MODULE_DEVICE_TABLE(of, rcar_pwm_of_table); + +static struct platform_driver rcar_pwm_driver = { + .probe = rcar_pwm_probe, + .remove = rcar_pwm_remove, + .driver = { + .name = "pwm-rcar", + .of_match_table = of_match_ptr(rcar_pwm_of_table), + } +}; +module_platform_driver(rcar_pwm_driver); + +MODULE_AUTHOR("Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>"); +MODULE_DESCRIPTION("Renesas PWM Timer Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:pwm-rcar"); diff --git a/drivers/pwm/pwm-renesas-tpu.c b/drivers/pwm/pwm-renesas-tpu.c new file mode 100644 index 000000000..81ad5a551 --- /dev/null +++ b/drivers/pwm/pwm-renesas-tpu.c @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * R-Mobile TPU PWM driver + * + * Copyright (C) 2012 Renesas Solutions Corp. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#define TPU_CHANNEL_MAX 4 + +#define TPU_TSTR 0x00 /* Timer start register (shared) */ + +#define TPU_TCRn 0x00 /* Timer control register */ +#define TPU_TCR_CCLR_NONE (0 << 5) +#define TPU_TCR_CCLR_TGRA (1 << 5) +#define TPU_TCR_CCLR_TGRB (2 << 5) +#define TPU_TCR_CCLR_TGRC (5 << 5) +#define TPU_TCR_CCLR_TGRD (6 << 5) +#define TPU_TCR_CKEG_RISING (0 << 3) +#define TPU_TCR_CKEG_FALLING (1 << 3) +#define TPU_TCR_CKEG_BOTH (2 << 3) +#define TPU_TMDRn 0x04 /* Timer mode register */ +#define TPU_TMDR_BFWT (1 << 6) +#define TPU_TMDR_BFB (1 << 5) +#define TPU_TMDR_BFA (1 << 4) +#define TPU_TMDR_MD_NORMAL (0 << 0) +#define TPU_TMDR_MD_PWM (2 << 0) +#define TPU_TIORn 0x08 /* Timer I/O control register */ +#define TPU_TIOR_IOA_0 (0 << 0) +#define TPU_TIOR_IOA_0_CLR (1 << 0) +#define TPU_TIOR_IOA_0_SET (2 << 0) +#define TPU_TIOR_IOA_0_TOGGLE (3 << 0) +#define TPU_TIOR_IOA_1 (4 << 0) +#define TPU_TIOR_IOA_1_CLR (5 << 0) +#define TPU_TIOR_IOA_1_SET (6 << 0) +#define TPU_TIOR_IOA_1_TOGGLE (7 << 0) +#define TPU_TIERn 0x0c /* Timer interrupt enable register */ +#define TPU_TSRn 0x10 /* Timer status register */ +#define TPU_TCNTn 0x14 /* Timer counter */ +#define TPU_TGRAn 0x18 /* Timer general register A */ +#define TPU_TGRBn 0x1c /* Timer general register B */ +#define TPU_TGRCn 0x20 /* Timer general register C */ +#define TPU_TGRDn 0x24 /* Timer general register D */ + +#define TPU_CHANNEL_OFFSET 0x10 +#define TPU_CHANNEL_SIZE 0x40 + +enum tpu_pin_state { + TPU_PIN_INACTIVE, /* Pin is driven inactive */ + TPU_PIN_PWM, /* Pin is driven by PWM */ + TPU_PIN_ACTIVE, /* Pin is driven active */ +}; + +struct tpu_device; + +struct tpu_pwm_device { + bool timer_on; /* Whether the timer is running */ + + struct tpu_device *tpu; + unsigned int channel; /* Channel number in the TPU */ + + enum pwm_polarity polarity; + unsigned int prescaler; + u16 period; + u16 duty; +}; + +struct tpu_device { + struct platform_device *pdev; + struct pwm_chip chip; + spinlock_t lock; + + void __iomem *base; + struct clk *clk; +}; + +#define to_tpu_device(c) container_of(c, struct tpu_device, chip) + +static void tpu_pwm_write(struct tpu_pwm_device *pwm, int reg_nr, u16 value) +{ + void __iomem *base = pwm->tpu->base + TPU_CHANNEL_OFFSET + + pwm->channel * TPU_CHANNEL_SIZE; + + iowrite16(value, base + reg_nr); +} + +static void tpu_pwm_set_pin(struct tpu_pwm_device *pwm, + enum tpu_pin_state state) +{ + static const char * const states[] = { "inactive", "PWM", "active" }; + + dev_dbg(&pwm->tpu->pdev->dev, "%u: configuring pin as %s\n", + pwm->channel, states[state]); + + switch (state) { + case TPU_PIN_INACTIVE: + tpu_pwm_write(pwm, TPU_TIORn, + pwm->polarity == PWM_POLARITY_INVERSED ? + TPU_TIOR_IOA_1 : TPU_TIOR_IOA_0); + break; + case TPU_PIN_PWM: + tpu_pwm_write(pwm, TPU_TIORn, + pwm->polarity == PWM_POLARITY_INVERSED ? + TPU_TIOR_IOA_0_SET : TPU_TIOR_IOA_1_CLR); + break; + case TPU_PIN_ACTIVE: + tpu_pwm_write(pwm, TPU_TIORn, + pwm->polarity == PWM_POLARITY_INVERSED ? + TPU_TIOR_IOA_0 : TPU_TIOR_IOA_1); + break; + } +} + +static void tpu_pwm_start_stop(struct tpu_pwm_device *pwm, int start) +{ + unsigned long flags; + u16 value; + + spin_lock_irqsave(&pwm->tpu->lock, flags); + value = ioread16(pwm->tpu->base + TPU_TSTR); + + if (start) + value |= 1 << pwm->channel; + else + value &= ~(1 << pwm->channel); + + iowrite16(value, pwm->tpu->base + TPU_TSTR); + spin_unlock_irqrestore(&pwm->tpu->lock, flags); +} + +static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm) +{ + int ret; + + if (!pwm->timer_on) { + /* Wake up device and enable clock. */ + pm_runtime_get_sync(&pwm->tpu->pdev->dev); + ret = clk_prepare_enable(pwm->tpu->clk); + if (ret) { + dev_err(&pwm->tpu->pdev->dev, "cannot enable clock\n"); + return ret; + } + pwm->timer_on = true; + } + + /* + * Make sure the channel is stopped, as we need to reconfigure it + * completely. First drive the pin to the inactive state to avoid + * glitches. + */ + tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE); + tpu_pwm_start_stop(pwm, false); + + /* + * - Clear TCNT on TGRB match + * - Count on rising edge + * - Set prescaler + * - Output 0 until TGRA, output 1 until TGRB (active low polarity) + * - Output 1 until TGRA, output 0 until TGRB (active high polarity + * - PWM mode + */ + tpu_pwm_write(pwm, TPU_TCRn, TPU_TCR_CCLR_TGRB | TPU_TCR_CKEG_RISING | + pwm->prescaler); + tpu_pwm_write(pwm, TPU_TMDRn, TPU_TMDR_MD_PWM); + tpu_pwm_set_pin(pwm, TPU_PIN_PWM); + tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty); + tpu_pwm_write(pwm, TPU_TGRBn, pwm->period); + + dev_dbg(&pwm->tpu->pdev->dev, "%u: TGRA 0x%04x TGRB 0x%04x\n", + pwm->channel, pwm->duty, pwm->period); + + /* Start the channel. */ + tpu_pwm_start_stop(pwm, true); + + return 0; +} + +static void tpu_pwm_timer_stop(struct tpu_pwm_device *pwm) +{ + if (!pwm->timer_on) + return; + + /* Disable channel. */ + tpu_pwm_start_stop(pwm, false); + + /* Stop clock and mark device as idle. */ + clk_disable_unprepare(pwm->tpu->clk); + pm_runtime_put(&pwm->tpu->pdev->dev); + + pwm->timer_on = false; +} + +/* ----------------------------------------------------------------------------- + * PWM API + */ + +static int tpu_pwm_request(struct pwm_chip *chip, struct pwm_device *_pwm) +{ + struct tpu_device *tpu = to_tpu_device(chip); + struct tpu_pwm_device *pwm; + + if (_pwm->hwpwm >= TPU_CHANNEL_MAX) + return -EINVAL; + + pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); + if (pwm == NULL) + return -ENOMEM; + + pwm->tpu = tpu; + pwm->channel = _pwm->hwpwm; + pwm->polarity = PWM_POLARITY_NORMAL; + pwm->prescaler = 0; + pwm->period = 0; + pwm->duty = 0; + + pwm->timer_on = false; + + pwm_set_chip_data(_pwm, pwm); + + return 0; +} + +static void tpu_pwm_free(struct pwm_chip *chip, struct pwm_device *_pwm) +{ + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + + tpu_pwm_timer_stop(pwm); + kfree(pwm); +} + +static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *_pwm, + int duty_ns, int period_ns) +{ + static const unsigned int prescalers[] = { 1, 4, 16, 64 }; + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + struct tpu_device *tpu = to_tpu_device(chip); + unsigned int prescaler; + bool duty_only = false; + u32 clk_rate; + u32 period; + u32 duty; + int ret; + + /* + * Pick a prescaler to avoid overflowing the counter. + * TODO: Pick the highest acceptable prescaler. + */ + clk_rate = clk_get_rate(tpu->clk); + + for (prescaler = 0; prescaler < ARRAY_SIZE(prescalers); ++prescaler) { + period = clk_rate / prescalers[prescaler] + / (NSEC_PER_SEC / period_ns); + if (period <= 0xffff) + break; + } + + if (prescaler == ARRAY_SIZE(prescalers) || period == 0) { + dev_err(&tpu->pdev->dev, "clock rate mismatch\n"); + return -ENOTSUPP; + } + + if (duty_ns) { + duty = clk_rate / prescalers[prescaler] + / (NSEC_PER_SEC / duty_ns); + if (duty > period) + return -EINVAL; + } else { + duty = 0; + } + + dev_dbg(&tpu->pdev->dev, + "rate %u, prescaler %u, period %u, duty %u\n", + clk_rate, prescalers[prescaler], period, duty); + + if (pwm->prescaler == prescaler && pwm->period == period) + duty_only = true; + + pwm->prescaler = prescaler; + pwm->period = period; + pwm->duty = duty; + + /* If the channel is disabled we're done. */ + if (!pwm_is_enabled(_pwm)) + return 0; + + if (duty_only && pwm->timer_on) { + /* + * If only the duty cycle changed and the timer is already + * running, there's no need to reconfigure it completely, Just + * modify the duty cycle. + */ + tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty); + dev_dbg(&tpu->pdev->dev, "%u: TGRA 0x%04x\n", pwm->channel, + pwm->duty); + } else { + /* Otherwise perform a full reconfiguration. */ + ret = tpu_pwm_timer_start(pwm); + if (ret < 0) + return ret; + } + + if (duty == 0 || duty == period) { + /* + * To avoid running the timer when not strictly required, handle + * 0% and 100% duty cycles as fixed levels and stop the timer. + */ + tpu_pwm_set_pin(pwm, duty ? TPU_PIN_ACTIVE : TPU_PIN_INACTIVE); + tpu_pwm_timer_stop(pwm); + } + + return 0; +} + +static int tpu_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *_pwm, + enum pwm_polarity polarity) +{ + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + + pwm->polarity = polarity; + + return 0; +} + +static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *_pwm) +{ + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + int ret; + + ret = tpu_pwm_timer_start(pwm); + if (ret < 0) + return ret; + + /* + * To avoid running the timer when not strictly required, handle 0% and + * 100% duty cycles as fixed levels and stop the timer. + */ + if (pwm->duty == 0 || pwm->duty == pwm->period) { + tpu_pwm_set_pin(pwm, pwm->duty ? + TPU_PIN_ACTIVE : TPU_PIN_INACTIVE); + tpu_pwm_timer_stop(pwm); + } + + return 0; +} + +static void tpu_pwm_disable(struct pwm_chip *chip, struct pwm_device *_pwm) +{ + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + + /* The timer must be running to modify the pin output configuration. */ + tpu_pwm_timer_start(pwm); + tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE); + tpu_pwm_timer_stop(pwm); +} + +static const struct pwm_ops tpu_pwm_ops = { + .request = tpu_pwm_request, + .free = tpu_pwm_free, + .config = tpu_pwm_config, + .set_polarity = tpu_pwm_set_polarity, + .enable = tpu_pwm_enable, + .disable = tpu_pwm_disable, + .owner = THIS_MODULE, +}; + +/* ----------------------------------------------------------------------------- + * Probe and remove + */ + +static int tpu_probe(struct platform_device *pdev) +{ + struct tpu_device *tpu; + struct resource *res; + int ret; + + tpu = devm_kzalloc(&pdev->dev, sizeof(*tpu), GFP_KERNEL); + if (tpu == NULL) + return -ENOMEM; + + spin_lock_init(&tpu->lock); + tpu->pdev = pdev; + + /* Map memory, get clock and pin control. */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + tpu->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(tpu->base)) + return PTR_ERR(tpu->base); + + tpu->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(tpu->clk)) { + dev_err(&pdev->dev, "cannot get clock\n"); + return PTR_ERR(tpu->clk); + } + + /* Initialize and register the device. */ + platform_set_drvdata(pdev, tpu); + + tpu->chip.dev = &pdev->dev; + tpu->chip.ops = &tpu_pwm_ops; + tpu->chip.of_xlate = of_pwm_xlate_with_flags; + tpu->chip.of_pwm_n_cells = 3; + tpu->chip.base = -1; + tpu->chip.npwm = TPU_CHANNEL_MAX; + + pm_runtime_enable(&pdev->dev); + + ret = pwmchip_add(&tpu->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register PWM chip\n"); + pm_runtime_disable(&pdev->dev); + return ret; + } + + return 0; +} + +static int tpu_remove(struct platform_device *pdev) +{ + struct tpu_device *tpu = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&tpu->chip); + + pm_runtime_disable(&pdev->dev); + + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id tpu_of_table[] = { + { .compatible = "renesas,tpu-r8a73a4", }, + { .compatible = "renesas,tpu-r8a7740", }, + { .compatible = "renesas,tpu-r8a7790", }, + { .compatible = "renesas,tpu", }, + { }, +}; + +MODULE_DEVICE_TABLE(of, tpu_of_table); +#endif + +static struct platform_driver tpu_driver = { + .probe = tpu_probe, + .remove = tpu_remove, + .driver = { + .name = "renesas-tpu-pwm", + .of_match_table = of_match_ptr(tpu_of_table), + } +}; + +module_platform_driver(tpu_driver); + +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_DESCRIPTION("Renesas TPU PWM Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:renesas-tpu-pwm"); diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c new file mode 100644 index 000000000..1f3079562 --- /dev/null +++ b/drivers/pwm/pwm-rockchip.c @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PWM driver for Rockchip SoCs + * + * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com> + * Copyright (C) 2014 ROCKCHIP, Inc. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/time.h> + +#define PWM_CTRL_TIMER_EN (1 << 0) +#define PWM_CTRL_OUTPUT_EN (1 << 3) + +#define PWM_ENABLE (1 << 0) +#define PWM_CONTINUOUS (1 << 1) +#define PWM_DUTY_POSITIVE (1 << 3) +#define PWM_DUTY_NEGATIVE (0 << 3) +#define PWM_INACTIVE_NEGATIVE (0 << 4) +#define PWM_INACTIVE_POSITIVE (1 << 4) +#define PWM_POLARITY_MASK (PWM_DUTY_POSITIVE | PWM_INACTIVE_POSITIVE) +#define PWM_OUTPUT_LEFT (0 << 5) +#define PWM_LOCK_EN (1 << 6) +#define PWM_LP_DISABLE (0 << 8) + +struct rockchip_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + struct clk *pclk; + const struct rockchip_pwm_data *data; + void __iomem *base; +}; + +struct rockchip_pwm_regs { + unsigned long duty; + unsigned long period; + unsigned long cntr; + unsigned long ctrl; +}; + +struct rockchip_pwm_data { + struct rockchip_pwm_regs regs; + unsigned int prescaler; + bool supports_polarity; + bool supports_lock; + u32 enable_conf; +}; + +static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c) +{ + return container_of(c, struct rockchip_pwm_chip, chip); +} + +static void rockchip_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + u32 enable_conf = pc->data->enable_conf; + unsigned long clk_rate; + u64 tmp; + u32 val; + int ret; + + ret = clk_enable(pc->pclk); + if (ret) + return; + + clk_rate = clk_get_rate(pc->clk); + + tmp = readl_relaxed(pc->base + pc->data->regs.period); + tmp *= pc->data->prescaler * NSEC_PER_SEC; + state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); + + tmp = readl_relaxed(pc->base + pc->data->regs.duty); + tmp *= pc->data->prescaler * NSEC_PER_SEC; + state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); + + val = readl_relaxed(pc->base + pc->data->regs.ctrl); + state->enabled = (val & enable_conf) == enable_conf; + + if (pc->data->supports_polarity && !(val & PWM_DUTY_POSITIVE)) + state->polarity = PWM_POLARITY_INVERSED; + else + state->polarity = PWM_POLARITY_NORMAL; + + clk_disable(pc->pclk); +} + +static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + unsigned long period, duty; + u64 clk_rate, div; + u32 ctrl; + + clk_rate = clk_get_rate(pc->clk); + + /* + * Since period and duty cycle registers have a width of 32 + * bits, every possible input period can be obtained using the + * default prescaler value for all practical clock rate values. + */ + div = clk_rate * state->period; + period = DIV_ROUND_CLOSEST_ULL(div, + pc->data->prescaler * NSEC_PER_SEC); + + div = clk_rate * state->duty_cycle; + duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC); + + /* + * Lock the period and duty of previous configuration, then + * change the duty and period, that would not be effective. + */ + ctrl = readl_relaxed(pc->base + pc->data->regs.ctrl); + if (pc->data->supports_lock) { + ctrl |= PWM_LOCK_EN; + writel_relaxed(ctrl, pc->base + pc->data->regs.ctrl); + } + + writel(period, pc->base + pc->data->regs.period); + writel(duty, pc->base + pc->data->regs.duty); + + if (pc->data->supports_polarity) { + ctrl &= ~PWM_POLARITY_MASK; + if (state->polarity == PWM_POLARITY_INVERSED) + ctrl |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE; + else + ctrl |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE; + } + + /* + * Unlock and set polarity at the same time, + * the configuration of duty, period and polarity + * would be effective together at next period. + */ + if (pc->data->supports_lock) + ctrl &= ~PWM_LOCK_EN; + + writel(ctrl, pc->base + pc->data->regs.ctrl); +} + +static int rockchip_pwm_enable(struct pwm_chip *chip, + struct pwm_device *pwm, + bool enable) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + u32 enable_conf = pc->data->enable_conf; + int ret; + u32 val; + + if (enable) { + ret = clk_enable(pc->clk); + if (ret) + return ret; + } + + val = readl_relaxed(pc->base + pc->data->regs.ctrl); + + if (enable) + val |= enable_conf; + else + val &= ~enable_conf; + + writel_relaxed(val, pc->base + pc->data->regs.ctrl); + + if (!enable) + clk_disable(pc->clk); + + return 0; +} + +static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + struct pwm_state curstate; + bool enabled; + int ret = 0; + + ret = clk_enable(pc->pclk); + if (ret) + return ret; + + pwm_get_state(pwm, &curstate); + enabled = curstate.enabled; + + if (state->polarity != curstate.polarity && enabled && + !pc->data->supports_lock) { + ret = rockchip_pwm_enable(chip, pwm, false); + if (ret) + goto out; + enabled = false; + } + + rockchip_pwm_config(chip, pwm, state); + if (state->enabled != enabled) { + ret = rockchip_pwm_enable(chip, pwm, state->enabled); + if (ret) + goto out; + } + +out: + clk_disable(pc->pclk); + + return ret; +} + +static const struct pwm_ops rockchip_pwm_ops = { + .get_state = rockchip_pwm_get_state, + .apply = rockchip_pwm_apply, + .owner = THIS_MODULE, +}; + +static const struct rockchip_pwm_data pwm_data_v1 = { + .regs = { + .duty = 0x04, + .period = 0x08, + .cntr = 0x00, + .ctrl = 0x0c, + }, + .prescaler = 2, + .supports_polarity = false, + .supports_lock = false, + .enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN, +}; + +static const struct rockchip_pwm_data pwm_data_v2 = { + .regs = { + .duty = 0x08, + .period = 0x04, + .cntr = 0x00, + .ctrl = 0x0c, + }, + .prescaler = 1, + .supports_polarity = true, + .supports_lock = false, + .enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | + PWM_CONTINUOUS, +}; + +static const struct rockchip_pwm_data pwm_data_vop = { + .regs = { + .duty = 0x08, + .period = 0x04, + .cntr = 0x0c, + .ctrl = 0x00, + }, + .prescaler = 1, + .supports_polarity = true, + .supports_lock = false, + .enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | + PWM_CONTINUOUS, +}; + +static const struct rockchip_pwm_data pwm_data_v3 = { + .regs = { + .duty = 0x08, + .period = 0x04, + .cntr = 0x00, + .ctrl = 0x0c, + }, + .prescaler = 1, + .supports_polarity = true, + .supports_lock = true, + .enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | + PWM_CONTINUOUS, +}; + +static const struct of_device_id rockchip_pwm_dt_ids[] = { + { .compatible = "rockchip,rk2928-pwm", .data = &pwm_data_v1}, + { .compatible = "rockchip,rk3288-pwm", .data = &pwm_data_v2}, + { .compatible = "rockchip,vop-pwm", .data = &pwm_data_vop}, + { .compatible = "rockchip,rk3328-pwm", .data = &pwm_data_v3}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rockchip_pwm_dt_ids); + +static int rockchip_pwm_probe(struct platform_device *pdev) +{ + const struct of_device_id *id; + struct rockchip_pwm_chip *pc; + struct resource *r; + u32 enable_conf, ctrl; + bool enabled; + int ret, count; + + id = of_match_device(rockchip_pwm_dt_ids, &pdev->dev); + if (!id) + return -EINVAL; + + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pc->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(pc->base)) + return PTR_ERR(pc->base); + + pc->clk = devm_clk_get(&pdev->dev, "pwm"); + if (IS_ERR(pc->clk)) { + pc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pc->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk), + "Can't get bus clk\n"); + } + + count = of_count_phandle_with_args(pdev->dev.of_node, + "clocks", "#clock-cells"); + if (count == 2) + pc->pclk = devm_clk_get(&pdev->dev, "pclk"); + else + pc->pclk = pc->clk; + + if (IS_ERR(pc->pclk)) { + ret = PTR_ERR(pc->pclk); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Can't get APB clk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(pc->clk); + if (ret) { + dev_err(&pdev->dev, "Can't prepare enable bus clk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(pc->pclk); + if (ret) { + dev_err(&pdev->dev, "Can't prepare enable APB clk: %d\n", ret); + goto err_clk; + } + + platform_set_drvdata(pdev, pc); + + pc->data = id->data; + pc->chip.dev = &pdev->dev; + pc->chip.ops = &rockchip_pwm_ops; + pc->chip.base = -1; + pc->chip.npwm = 1; + + if (pc->data->supports_polarity) { + pc->chip.of_xlate = of_pwm_xlate_with_flags; + pc->chip.of_pwm_n_cells = 3; + } + + enable_conf = pc->data->enable_conf; + ctrl = readl_relaxed(pc->base + pc->data->regs.ctrl); + enabled = (ctrl & enable_conf) == enable_conf; + + ret = pwmchip_add(&pc->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + goto err_pclk; + } + + /* Keep the PWM clk enabled if the PWM appears to be up and running. */ + if (!enabled) + clk_disable(pc->clk); + + clk_disable(pc->pclk); + + return 0; + +err_pclk: + clk_disable_unprepare(pc->pclk); +err_clk: + clk_disable_unprepare(pc->clk); + + return ret; +} + +static int rockchip_pwm_remove(struct platform_device *pdev) +{ + struct rockchip_pwm_chip *pc = platform_get_drvdata(pdev); + + clk_unprepare(pc->pclk); + clk_unprepare(pc->clk); + + return pwmchip_remove(&pc->chip); +} + +static struct platform_driver rockchip_pwm_driver = { + .driver = { + .name = "rockchip-pwm", + .of_match_table = rockchip_pwm_dt_ids, + }, + .probe = rockchip_pwm_probe, + .remove = rockchip_pwm_remove, +}; +module_platform_driver(rockchip_pwm_driver); + +MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>"); +MODULE_DESCRIPTION("Rockchip SoC PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c new file mode 100644 index 000000000..87a886f7d --- /dev/null +++ b/drivers/pwm/pwm-samsung.c @@ -0,0 +1,650 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2007 Ben Dooks + * Copyright (c) 2008 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org> + * Copyright (c) 2013 Tomasz Figa <tomasz.figa@gmail.com> + * Copyright (c) 2017 Samsung Electronics Co., Ltd. + * + * PWM driver for Samsung SoCs + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/export.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/time.h> + +/* For struct samsung_timer_variant and samsung_pwm_lock. */ +#include <clocksource/samsung_pwm.h> + +#define REG_TCFG0 0x00 +#define REG_TCFG1 0x04 +#define REG_TCON 0x08 + +#define REG_TCNTB(chan) (0x0c + ((chan) * 0xc)) +#define REG_TCMPB(chan) (0x10 + ((chan) * 0xc)) + +#define TCFG0_PRESCALER_MASK 0xff +#define TCFG0_PRESCALER1_SHIFT 8 + +#define TCFG1_MUX_MASK 0xf +#define TCFG1_SHIFT(chan) (4 * (chan)) + +/* + * Each channel occupies 4 bits in TCON register, but there is a gap of 4 + * bits (one channel) after channel 0, so channels have different numbering + * when accessing TCON register. See to_tcon_channel() function. + * + * In addition, the location of autoreload bit for channel 4 (TCON channel 5) + * in its set of bits is 2 as opposed to 3 for other channels. + */ +#define TCON_START(chan) BIT(4 * (chan) + 0) +#define TCON_MANUALUPDATE(chan) BIT(4 * (chan) + 1) +#define TCON_INVERT(chan) BIT(4 * (chan) + 2) +#define _TCON_AUTORELOAD(chan) BIT(4 * (chan) + 3) +#define _TCON_AUTORELOAD4(chan) BIT(4 * (chan) + 2) +#define TCON_AUTORELOAD(chan) \ + ((chan < 5) ? _TCON_AUTORELOAD(chan) : _TCON_AUTORELOAD4(chan)) + +/** + * struct samsung_pwm_channel - private data of PWM channel + * @period_ns: current period in nanoseconds programmed to the hardware + * @duty_ns: current duty time in nanoseconds programmed to the hardware + * @tin_ns: time of one timer tick in nanoseconds with current timer rate + */ +struct samsung_pwm_channel { + u32 period_ns; + u32 duty_ns; + u32 tin_ns; +}; + +/** + * struct samsung_pwm_chip - private data of PWM chip + * @chip: generic PWM chip + * @variant: local copy of hardware variant data + * @inverter_mask: inverter status for all channels - one bit per channel + * @disabled_mask: disabled status for all channels - one bit per channel + * @base: base address of mapped PWM registers + * @base_clk: base clock used to drive the timers + * @tclk0: external clock 0 (can be ERR_PTR if not present) + * @tclk1: external clock 1 (can be ERR_PTR if not present) + */ +struct samsung_pwm_chip { + struct pwm_chip chip; + struct samsung_pwm_variant variant; + u8 inverter_mask; + u8 disabled_mask; + + void __iomem *base; + struct clk *base_clk; + struct clk *tclk0; + struct clk *tclk1; +}; + +#ifndef CONFIG_CLKSRC_SAMSUNG_PWM +/* + * PWM block is shared between pwm-samsung and samsung_pwm_timer drivers + * and some registers need access synchronization. If both drivers are + * compiled in, the spinlock is defined in the clocksource driver, + * otherwise following definition is used. + * + * Currently we do not need any more complex synchronization method + * because all the supported SoCs contain only one instance of the PWM + * IP. Should this change, both drivers will need to be modified to + * properly synchronize accesses to particular instances. + */ +static DEFINE_SPINLOCK(samsung_pwm_lock); +#endif + +static inline +struct samsung_pwm_chip *to_samsung_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct samsung_pwm_chip, chip); +} + +static inline unsigned int to_tcon_channel(unsigned int channel) +{ + /* TCON register has a gap of 4 bits (1 channel) after channel 0 */ + return (channel == 0) ? 0 : (channel + 1); +} + +static void pwm_samsung_set_divisor(struct samsung_pwm_chip *pwm, + unsigned int channel, u8 divisor) +{ + u8 shift = TCFG1_SHIFT(channel); + unsigned long flags; + u32 reg; + u8 bits; + + bits = (fls(divisor) - 1) - pwm->variant.div_base; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + reg = readl(pwm->base + REG_TCFG1); + reg &= ~(TCFG1_MUX_MASK << shift); + reg |= bits << shift; + writel(reg, pwm->base + REG_TCFG1); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static int pwm_samsung_is_tdiv(struct samsung_pwm_chip *chip, unsigned int chan) +{ + struct samsung_pwm_variant *variant = &chip->variant; + u32 reg; + + reg = readl(chip->base + REG_TCFG1); + reg >>= TCFG1_SHIFT(chan); + reg &= TCFG1_MUX_MASK; + + return (BIT(reg) & variant->tclk_mask) == 0; +} + +static unsigned long pwm_samsung_get_tin_rate(struct samsung_pwm_chip *chip, + unsigned int chan) +{ + unsigned long rate; + u32 reg; + + rate = clk_get_rate(chip->base_clk); + + reg = readl(chip->base + REG_TCFG0); + if (chan >= 2) + reg >>= TCFG0_PRESCALER1_SHIFT; + reg &= TCFG0_PRESCALER_MASK; + + return rate / (reg + 1); +} + +static unsigned long pwm_samsung_calc_tin(struct samsung_pwm_chip *chip, + unsigned int chan, unsigned long freq) +{ + struct samsung_pwm_variant *variant = &chip->variant; + unsigned long rate; + struct clk *clk; + u8 div; + + if (!pwm_samsung_is_tdiv(chip, chan)) { + clk = (chan < 2) ? chip->tclk0 : chip->tclk1; + if (!IS_ERR(clk)) { + rate = clk_get_rate(clk); + if (rate) + return rate; + } + + dev_warn(chip->chip.dev, + "tclk of PWM %d is inoperational, using tdiv\n", chan); + } + + rate = pwm_samsung_get_tin_rate(chip, chan); + dev_dbg(chip->chip.dev, "tin parent at %lu\n", rate); + + /* + * Compare minimum PWM frequency that can be achieved with possible + * divider settings and choose the lowest divisor that can generate + * frequencies lower than requested. + */ + if (variant->bits < 32) { + /* Only for s3c24xx */ + for (div = variant->div_base; div < 4; ++div) + if ((rate >> (variant->bits + div)) < freq) + break; + } else { + /* + * Other variants have enough counter bits to generate any + * requested rate, so no need to check higher divisors. + */ + div = variant->div_base; + } + + pwm_samsung_set_divisor(chip, chan, BIT(div)); + + return rate >> div; +} + +static int pwm_samsung_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); + struct samsung_pwm_channel *our_chan; + + if (!(our_chip->variant.output_mask & BIT(pwm->hwpwm))) { + dev_warn(chip->dev, + "tried to request PWM channel %d without output\n", + pwm->hwpwm); + return -EINVAL; + } + + our_chan = kzalloc(sizeof(*our_chan), GFP_KERNEL); + if (!our_chan) + return -ENOMEM; + + pwm_set_chip_data(pwm, our_chan); + + return 0; +} + +static void pwm_samsung_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + kfree(pwm_get_chip_data(pwm)); +} + +static int pwm_samsung_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); + unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm); + unsigned long flags; + u32 tcon; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + tcon = readl(our_chip->base + REG_TCON); + + tcon &= ~TCON_START(tcon_chan); + tcon |= TCON_MANUALUPDATE(tcon_chan); + writel(tcon, our_chip->base + REG_TCON); + + tcon &= ~TCON_MANUALUPDATE(tcon_chan); + tcon |= TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan); + writel(tcon, our_chip->base + REG_TCON); + + our_chip->disabled_mask &= ~BIT(pwm->hwpwm); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); + + return 0; +} + +static void pwm_samsung_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); + unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm); + unsigned long flags; + u32 tcon; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + tcon = readl(our_chip->base + REG_TCON); + tcon &= ~TCON_AUTORELOAD(tcon_chan); + writel(tcon, our_chip->base + REG_TCON); + + our_chip->disabled_mask |= BIT(pwm->hwpwm); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static void pwm_samsung_manual_update(struct samsung_pwm_chip *chip, + struct pwm_device *pwm) +{ + unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm); + u32 tcon; + unsigned long flags; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + tcon = readl(chip->base + REG_TCON); + tcon |= TCON_MANUALUPDATE(tcon_chan); + writel(tcon, chip->base + REG_TCON); + + tcon &= ~TCON_MANUALUPDATE(tcon_chan); + writel(tcon, chip->base + REG_TCON); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static int __pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns, bool force_period) +{ + struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); + struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm); + u32 tin_ns = chan->tin_ns, tcnt, tcmp, oldtcmp; + + /* + * We currently avoid using 64bit arithmetic by using the + * fact that anything faster than 1Hz is easily representable + * by 32bits. + */ + if (period_ns > NSEC_PER_SEC) + return -ERANGE; + + tcnt = readl(our_chip->base + REG_TCNTB(pwm->hwpwm)); + oldtcmp = readl(our_chip->base + REG_TCMPB(pwm->hwpwm)); + + /* We need tick count for calculation, not last tick. */ + ++tcnt; + + /* Check to see if we are changing the clock rate of the PWM. */ + if (chan->period_ns != period_ns || force_period) { + unsigned long tin_rate; + u32 period; + + period = NSEC_PER_SEC / period_ns; + + dev_dbg(our_chip->chip.dev, "duty_ns=%d, period_ns=%d (%u)\n", + duty_ns, period_ns, period); + + tin_rate = pwm_samsung_calc_tin(our_chip, pwm->hwpwm, period); + + dev_dbg(our_chip->chip.dev, "tin_rate=%lu\n", tin_rate); + + tin_ns = NSEC_PER_SEC / tin_rate; + tcnt = period_ns / tin_ns; + } + + /* Period is too short. */ + if (tcnt <= 1) + return -ERANGE; + + /* Note that counters count down. */ + tcmp = duty_ns / tin_ns; + + /* 0% duty is not available */ + if (!tcmp) + ++tcmp; + + tcmp = tcnt - tcmp; + + /* Decrement to get tick numbers, instead of tick counts. */ + --tcnt; + /* -1UL will give 100% duty. */ + --tcmp; + + dev_dbg(our_chip->chip.dev, + "tin_ns=%u, tcmp=%u/%u\n", tin_ns, tcmp, tcnt); + + /* Update PWM registers. */ + writel(tcnt, our_chip->base + REG_TCNTB(pwm->hwpwm)); + writel(tcmp, our_chip->base + REG_TCMPB(pwm->hwpwm)); + + /* + * In case the PWM is currently at 100% duty cycle, force a manual + * update to prevent the signal staying high if the PWM is disabled + * shortly afer this update (before it autoreloaded the new values). + */ + if (oldtcmp == (u32) -1) { + dev_dbg(our_chip->chip.dev, "Forcing manual update"); + pwm_samsung_manual_update(our_chip, pwm); + } + + chan->period_ns = period_ns; + chan->tin_ns = tin_ns; + chan->duty_ns = duty_ns; + + return 0; +} + +static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + return __pwm_samsung_config(chip, pwm, duty_ns, period_ns, false); +} + +static void pwm_samsung_set_invert(struct samsung_pwm_chip *chip, + unsigned int channel, bool invert) +{ + unsigned int tcon_chan = to_tcon_channel(channel); + unsigned long flags; + u32 tcon; + + spin_lock_irqsave(&samsung_pwm_lock, flags); + + tcon = readl(chip->base + REG_TCON); + + if (invert) { + chip->inverter_mask |= BIT(channel); + tcon |= TCON_INVERT(tcon_chan); + } else { + chip->inverter_mask &= ~BIT(channel); + tcon &= ~TCON_INVERT(tcon_chan); + } + + writel(tcon, chip->base + REG_TCON); + + spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static int pwm_samsung_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); + bool invert = (polarity == PWM_POLARITY_NORMAL); + + /* Inverted means normal in the hardware. */ + pwm_samsung_set_invert(our_chip, pwm->hwpwm, invert); + + return 0; +} + +static const struct pwm_ops pwm_samsung_ops = { + .request = pwm_samsung_request, + .free = pwm_samsung_free, + .enable = pwm_samsung_enable, + .disable = pwm_samsung_disable, + .config = pwm_samsung_config, + .set_polarity = pwm_samsung_set_polarity, + .owner = THIS_MODULE, +}; + +#ifdef CONFIG_OF +static const struct samsung_pwm_variant s3c24xx_variant = { + .bits = 16, + .div_base = 1, + .has_tint_cstat = false, + .tclk_mask = BIT(4), +}; + +static const struct samsung_pwm_variant s3c64xx_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = BIT(7) | BIT(6) | BIT(5), +}; + +static const struct samsung_pwm_variant s5p64x0_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = 0, +}; + +static const struct samsung_pwm_variant s5pc100_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = BIT(5), +}; + +static const struct of_device_id samsung_pwm_matches[] = { + { .compatible = "samsung,s3c2410-pwm", .data = &s3c24xx_variant }, + { .compatible = "samsung,s3c6400-pwm", .data = &s3c64xx_variant }, + { .compatible = "samsung,s5p6440-pwm", .data = &s5p64x0_variant }, + { .compatible = "samsung,s5pc100-pwm", .data = &s5pc100_variant }, + { .compatible = "samsung,exynos4210-pwm", .data = &s5p64x0_variant }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_pwm_matches); + +static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip) +{ + struct device_node *np = chip->chip.dev->of_node; + const struct of_device_id *match; + struct property *prop; + const __be32 *cur; + u32 val; + + match = of_match_node(samsung_pwm_matches, np); + if (!match) + return -ENODEV; + + memcpy(&chip->variant, match->data, sizeof(chip->variant)); + + of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) { + if (val >= SAMSUNG_PWM_NUM) { + dev_err(chip->chip.dev, + "%s: invalid channel index in samsung,pwm-outputs property\n", + __func__); + continue; + } + chip->variant.output_mask |= BIT(val); + } + + return 0; +} +#else +static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip) +{ + return -ENODEV; +} +#endif + +static int pwm_samsung_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct samsung_pwm_chip *chip; + struct resource *res; + unsigned int chan; + int ret; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + chip->chip.dev = &pdev->dev; + chip->chip.ops = &pwm_samsung_ops; + chip->chip.base = -1; + chip->chip.npwm = SAMSUNG_PWM_NUM; + chip->inverter_mask = BIT(SAMSUNG_PWM_NUM) - 1; + + if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { + ret = pwm_samsung_parse_dt(chip); + if (ret) + return ret; + + chip->chip.of_xlate = of_pwm_xlate_with_flags; + chip->chip.of_pwm_n_cells = 3; + } else { + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "no platform data specified\n"); + return -EINVAL; + } + + memcpy(&chip->variant, pdev->dev.platform_data, + sizeof(chip->variant)); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + chip->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(chip->base)) + return PTR_ERR(chip->base); + + chip->base_clk = devm_clk_get(&pdev->dev, "timers"); + if (IS_ERR(chip->base_clk)) { + dev_err(dev, "failed to get timer base clk\n"); + return PTR_ERR(chip->base_clk); + } + + ret = clk_prepare_enable(chip->base_clk); + if (ret < 0) { + dev_err(dev, "failed to enable base clock\n"); + return ret; + } + + for (chan = 0; chan < SAMSUNG_PWM_NUM; ++chan) + if (chip->variant.output_mask & BIT(chan)) + pwm_samsung_set_invert(chip, chan, true); + + /* Following clocks are optional. */ + chip->tclk0 = devm_clk_get(&pdev->dev, "pwm-tclk0"); + chip->tclk1 = devm_clk_get(&pdev->dev, "pwm-tclk1"); + + platform_set_drvdata(pdev, chip); + + ret = pwmchip_add(&chip->chip); + if (ret < 0) { + dev_err(dev, "failed to register PWM chip\n"); + clk_disable_unprepare(chip->base_clk); + return ret; + } + + dev_dbg(dev, "base_clk at %lu, tclk0 at %lu, tclk1 at %lu\n", + clk_get_rate(chip->base_clk), + !IS_ERR(chip->tclk0) ? clk_get_rate(chip->tclk0) : 0, + !IS_ERR(chip->tclk1) ? clk_get_rate(chip->tclk1) : 0); + + return 0; +} + +static int pwm_samsung_remove(struct platform_device *pdev) +{ + struct samsung_pwm_chip *chip = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&chip->chip); + if (ret < 0) + return ret; + + clk_disable_unprepare(chip->base_clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pwm_samsung_resume(struct device *dev) +{ + struct samsung_pwm_chip *our_chip = dev_get_drvdata(dev); + struct pwm_chip *chip = &our_chip->chip; + unsigned int i; + + for (i = 0; i < SAMSUNG_PWM_NUM; i++) { + struct pwm_device *pwm = &chip->pwms[i]; + struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm); + + if (!chan) + continue; + + if (our_chip->variant.output_mask & BIT(i)) + pwm_samsung_set_invert(our_chip, i, + our_chip->inverter_mask & BIT(i)); + + if (chan->period_ns) { + __pwm_samsung_config(chip, pwm, chan->duty_ns, + chan->period_ns, true); + /* needed to make PWM disable work on Odroid-XU3 */ + pwm_samsung_manual_update(our_chip, pwm); + } + + if (our_chip->disabled_mask & BIT(i)) + pwm_samsung_disable(chip, pwm); + else + pwm_samsung_enable(chip, pwm); + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(pwm_samsung_pm_ops, NULL, pwm_samsung_resume); + +static struct platform_driver pwm_samsung_driver = { + .driver = { + .name = "samsung-pwm", + .pm = &pwm_samsung_pm_ops, + .of_match_table = of_match_ptr(samsung_pwm_matches), + }, + .probe = pwm_samsung_probe, + .remove = pwm_samsung_remove, +}; +module_platform_driver(pwm_samsung_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 new file mode 100644 index 000000000..52a55bae0 --- /dev/null +++ b/drivers/pwm/pwm-sifive.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2018 SiFive + * For SiFive's PWM IP block documentation please refer Chapter 14 of + * Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf + * + * Limitations: + * - When changing both duty cycle and period, we cannot prevent in + * software that the output might produce a period with mixed + * settings (new period length and old duty cycle). + * - The hardware cannot generate a 100% duty cycle. + * - The hardware generates only inverted output. + */ +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/bitfield.h> + +/* Register offsets */ +#define PWM_SIFIVE_PWMCFG 0x0 +#define PWM_SIFIVE_PWMCOUNT 0x8 +#define PWM_SIFIVE_PWMS 0x10 +#define PWM_SIFIVE_PWMCMP(i) (0x20 + 4 * (i)) + +/* PWMCFG fields */ +#define PWM_SIFIVE_PWMCFG_SCALE GENMASK(3, 0) +#define PWM_SIFIVE_PWMCFG_STICKY BIT(8) +#define PWM_SIFIVE_PWMCFG_ZERO_CMP BIT(9) +#define PWM_SIFIVE_PWMCFG_DEGLITCH BIT(10) +#define PWM_SIFIVE_PWMCFG_EN_ALWAYS BIT(12) +#define PWM_SIFIVE_PWMCFG_EN_ONCE BIT(13) +#define PWM_SIFIVE_PWMCFG_CENTER BIT(16) +#define PWM_SIFIVE_PWMCFG_GANG BIT(24) +#define PWM_SIFIVE_PWMCFG_IP BIT(28) + +#define PWM_SIFIVE_CMPWIDTH 16 +#define PWM_SIFIVE_DEFAULT_PERIOD 10000000 + +struct pwm_sifive_ddata { + struct pwm_chip chip; + struct mutex lock; /* lock to protect user_count and approx_period */ + struct notifier_block notifier; + struct clk *clk; + void __iomem *regs; + unsigned int real_period; + unsigned int approx_period; + int user_count; +}; + +static inline +struct pwm_sifive_ddata *pwm_sifive_chip_to_ddata(struct pwm_chip *c) +{ + return container_of(c, struct pwm_sifive_ddata, chip); +} + +static int pwm_sifive_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); + + mutex_lock(&ddata->lock); + ddata->user_count++; + mutex_unlock(&ddata->lock); + + return 0; +} + +static void pwm_sifive_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); + + mutex_lock(&ddata->lock); + ddata->user_count--; + mutex_unlock(&ddata->lock); +} + +/* Called holding ddata->lock */ +static void pwm_sifive_update_clock(struct pwm_sifive_ddata *ddata, + unsigned long rate) +{ + unsigned long long num; + unsigned long scale_pow; + int scale; + u32 val; + /* + * The PWM unit is used with pwmzerocmp=0, so the only way to modify the + * period length is using pwmscale which provides the number of bits the + * counter is shifted before being feed to the comparators. A period + * lasts (1 << (PWM_SIFIVE_CMPWIDTH + pwmscale)) clock ticks. + * (1 << (PWM_SIFIVE_CMPWIDTH + scale)) * 10^9/rate = period + */ + scale_pow = div64_ul(ddata->approx_period * (u64)rate, NSEC_PER_SEC); + scale = clamp(ilog2(scale_pow) - PWM_SIFIVE_CMPWIDTH, 0, 0xf); + + val = PWM_SIFIVE_PWMCFG_EN_ALWAYS | + FIELD_PREP(PWM_SIFIVE_PWMCFG_SCALE, scale); + writel(val, ddata->regs + PWM_SIFIVE_PWMCFG); + + /* As scale <= 15 the shift operation cannot overflow. */ + num = (unsigned long long)NSEC_PER_SEC << (PWM_SIFIVE_CMPWIDTH + scale); + ddata->real_period = div64_ul(num, rate); + dev_dbg(ddata->chip.dev, + "New real_period = %u ns\n", ddata->real_period); +} + +static void pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); + u32 duty, val; + + duty = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm)); + + state->enabled = duty > 0; + + val = readl(ddata->regs + PWM_SIFIVE_PWMCFG); + if (!(val & PWM_SIFIVE_PWMCFG_EN_ALWAYS)) + state->enabled = false; + + state->period = ddata->real_period; + state->duty_cycle = + (u64)duty * ddata->real_period >> PWM_SIFIVE_CMPWIDTH; + state->polarity = PWM_POLARITY_INVERSED; +} + +static int pwm_sifive_enable(struct pwm_chip *chip, bool enable) +{ + struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); + int ret; + + if (enable) { + ret = clk_enable(ddata->clk); + if (ret) { + dev_err(ddata->chip.dev, "Enable clk failed\n"); + return ret; + } + } + + if (!enable) + clk_disable(ddata->clk); + + return 0; +} + +static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); + struct pwm_state cur_state; + unsigned int duty_cycle; + unsigned long long num; + bool enabled; + int ret = 0; + u32 frac; + + if (state->polarity != PWM_POLARITY_INVERSED) + return -EINVAL; + + ret = clk_enable(ddata->clk); + if (ret) { + dev_err(ddata->chip.dev, "Enable clk failed\n"); + return ret; + } + + cur_state = pwm->state; + enabled = cur_state.enabled; + + duty_cycle = state->duty_cycle; + if (!state->enabled) + duty_cycle = 0; + + /* + * The problem of output producing mixed setting as mentioned at top, + * occurs here. To minimize the window for this problem, we are + * calculating the register values first and then writing them + * consecutively + */ + num = (u64)duty_cycle * (1U << PWM_SIFIVE_CMPWIDTH); + frac = DIV64_U64_ROUND_CLOSEST(num, state->period); + /* The hardware cannot generate a 100% duty cycle */ + frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1); + + mutex_lock(&ddata->lock); + if (state->period != ddata->approx_period) { + /* + * Don't let a 2nd user change the period underneath the 1st user. + * However if ddate->approx_period == 0 this is the first time we set + * any period, so let whoever gets here first set the period so other + * users who agree on the period won't fail. + */ + if (ddata->user_count != 1 && ddata->approx_period) { + mutex_unlock(&ddata->lock); + ret = -EBUSY; + goto exit; + } + ddata->approx_period = state->period; + pwm_sifive_update_clock(ddata, clk_get_rate(ddata->clk)); + } + mutex_unlock(&ddata->lock); + + writel(frac, ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm)); + + if (state->enabled != enabled) + pwm_sifive_enable(chip, state->enabled); + +exit: + clk_disable(ddata->clk); + return ret; +} + +static const struct pwm_ops pwm_sifive_ops = { + .request = pwm_sifive_request, + .free = pwm_sifive_free, + .get_state = pwm_sifive_get_state, + .apply = pwm_sifive_apply, + .owner = THIS_MODULE, +}; + +static int pwm_sifive_clock_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct clk_notifier_data *ndata = data; + struct pwm_sifive_ddata *ddata = + container_of(nb, struct pwm_sifive_ddata, notifier); + + if (event == POST_RATE_CHANGE) { + mutex_lock(&ddata->lock); + pwm_sifive_update_clock(ddata, ndata->new_rate); + mutex_unlock(&ddata->lock); + } + + return NOTIFY_OK; +} + +static int pwm_sifive_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pwm_sifive_ddata *ddata; + struct pwm_chip *chip; + struct resource *res; + int ret; + u32 val; + unsigned int enabled_pwms = 0, enabled_clks = 1; + + ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + mutex_init(&ddata->lock); + chip = &ddata->chip; + chip->dev = dev; + chip->ops = &pwm_sifive_ops; + chip->of_xlate = of_pwm_xlate_with_flags; + chip->of_pwm_n_cells = 3; + chip->base = -1; + chip->npwm = 4; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ddata->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(ddata->regs)) + return PTR_ERR(ddata->regs); + + ddata->clk = devm_clk_get(dev, NULL); + if (IS_ERR(ddata->clk)) + return dev_err_probe(dev, PTR_ERR(ddata->clk), + "Unable to find controller clock\n"); + + ret = clk_prepare_enable(ddata->clk); + if (ret) { + dev_err(dev, "failed to enable clock for pwm: %d\n", ret); + return ret; + } + + val = readl(ddata->regs + PWM_SIFIVE_PWMCFG); + if (val & PWM_SIFIVE_PWMCFG_EN_ALWAYS) { + unsigned int i; + + for (i = 0; i < chip->npwm; ++i) { + val = readl(ddata->regs + PWM_SIFIVE_PWMCMP(i)); + if (val > 0) + ++enabled_pwms; + } + } + + /* The clk should be on once for each running PWM. */ + if (enabled_pwms) { + while (enabled_clks < enabled_pwms) { + /* This is not expected to fail as the clk is already on */ + ret = clk_enable(ddata->clk); + if (unlikely(ret)) { + dev_err_probe(dev, ret, "Failed to enable clk\n"); + goto disable_clk; + } + ++enabled_clks; + } + } else { + clk_disable(ddata->clk); + enabled_clks = 0; + } + + /* Watch for changes to underlying clock frequency */ + ddata->notifier.notifier_call = pwm_sifive_clock_notifier; + ret = clk_notifier_register(ddata->clk, &ddata->notifier); + if (ret) { + dev_err(dev, "failed to register clock notifier: %d\n", ret); + goto disable_clk; + } + + ret = pwmchip_add(chip); + if (ret < 0) { + dev_err(dev, "cannot register PWM: %d\n", ret); + goto unregister_clk; + } + + platform_set_drvdata(pdev, ddata); + dev_dbg(dev, "SiFive PWM chip registered %d PWMs\n", chip->npwm); + + return 0; + +unregister_clk: + clk_notifier_unregister(ddata->clk, &ddata->notifier); +disable_clk: + while (enabled_clks) { + clk_disable(ddata->clk); + --enabled_clks; + } + clk_unprepare(ddata->clk); + + return ret; +} + +static int pwm_sifive_remove(struct platform_device *dev) +{ + struct pwm_sifive_ddata *ddata = platform_get_drvdata(dev); + struct pwm_device *pwm; + int ch; + + pwmchip_remove(&ddata->chip); + clk_notifier_unregister(ddata->clk, &ddata->notifier); + + for (ch = 0; ch < ddata->chip.npwm; ch++) { + pwm = &ddata->chip.pwms[ch]; + if (pwm->state.enabled) + clk_disable(ddata->clk); + } + + clk_unprepare(ddata->clk); + + return 0; +} + +static const struct of_device_id pwm_sifive_of_match[] = { + { .compatible = "sifive,pwm0" }, + {}, +}; +MODULE_DEVICE_TABLE(of, pwm_sifive_of_match); + +static struct platform_driver pwm_sifive_driver = { + .probe = pwm_sifive_probe, + .remove = pwm_sifive_remove, + .driver = { + .name = "pwm-sifive", + .of_match_table = pwm_sifive_of_match, + }, +}; +module_platform_driver(pwm_sifive_driver); + +MODULE_DESCRIPTION("SiFive PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-sl28cpld.c b/drivers/pwm/pwm-sl28cpld.c new file mode 100644 index 000000000..b4c651fc7 --- /dev/null +++ b/drivers/pwm/pwm-sl28cpld.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * sl28cpld PWM driver + * + * Copyright (c) 2020 Michael Walle <michael@walle.cc> + * + * There is no public datasheet available for this PWM core. But it is easy + * enough to be briefly explained. It consists of one 8-bit counter. The PWM + * supports four distinct frequencies by selecting when to reset the counter. + * With the prescaler setting you can select which bit of the counter is used + * to reset it. This implies that the higher the frequency the less remaining + * bits are available for the actual counter. + * + * Let cnt[7:0] be the counter, clocked at 32kHz: + * +-----------+--------+--------------+-----------+---------------+ + * | prescaler | reset | counter bits | frequency | period length | + * +-----------+--------+--------------+-----------+---------------+ + * | 0 | cnt[7] | cnt[6:0] | 250 Hz | 4000000 ns | + * | 1 | cnt[6] | cnt[5:0] | 500 Hz | 2000000 ns | + * | 2 | cnt[5] | cnt[4:0] | 1 kHz | 1000000 ns | + * | 3 | cnt[4] | cnt[3:0] | 2 kHz | 500000 ns | + * +-----------+--------+--------------+-----------+---------------+ + * + * Limitations: + * - The hardware cannot generate a 100% duty cycle if the prescaler is 0. + * - The hardware cannot atomically set the prescaler and the counter value, + * which might lead to glitches and inconsistent states if a write fails. + * - The counter is not reset if you switch the prescaler which leads + * to glitches, too. + * - The duty cycle will switch immediately and not after a complete cycle. + * - Depending on the actual implementation, disabling the PWM might have + * side effects. For example, if the output pin is shared with a GPIO pin + * it will automatically switch back to GPIO mode. + */ + +#include <linux/bitfield.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> + +/* + * PWM timer block registers. + */ +#define SL28CPLD_PWM_CTRL 0x00 +#define SL28CPLD_PWM_CTRL_ENABLE BIT(7) +#define SL28CPLD_PWM_CTRL_PRESCALER_MASK GENMASK(1, 0) +#define SL28CPLD_PWM_CYCLE 0x01 +#define SL28CPLD_PWM_CYCLE_MAX GENMASK(6, 0) + +#define SL28CPLD_PWM_CLK 32000 /* 32 kHz */ +#define SL28CPLD_PWM_MAX_DUTY_CYCLE(prescaler) (1 << (7 - (prescaler))) +#define SL28CPLD_PWM_PERIOD(prescaler) \ + (NSEC_PER_SEC / SL28CPLD_PWM_CLK * SL28CPLD_PWM_MAX_DUTY_CYCLE(prescaler)) + +/* + * We calculate the duty cycle like this: + * duty_cycle_ns = pwm_cycle_reg * max_period_ns / max_duty_cycle + * + * With + * max_period_ns = 1 << (7 - prescaler) / SL28CPLD_PWM_CLK * NSEC_PER_SEC + * max_duty_cycle = 1 << (7 - prescaler) + * this then simplifies to: + * duty_cycle_ns = pwm_cycle_reg / SL28CPLD_PWM_CLK * NSEC_PER_SEC + * = NSEC_PER_SEC / SL28CPLD_PWM_CLK * pwm_cycle_reg + * + * NSEC_PER_SEC is a multiple of SL28CPLD_PWM_CLK, therefore we're not losing + * precision by doing the divison first. + */ +#define SL28CPLD_PWM_TO_DUTY_CYCLE(reg) \ + (NSEC_PER_SEC / SL28CPLD_PWM_CLK * (reg)) +#define SL28CPLD_PWM_FROM_DUTY_CYCLE(duty_cycle) \ + (DIV_ROUND_DOWN_ULL((duty_cycle), NSEC_PER_SEC / SL28CPLD_PWM_CLK)) + +#define sl28cpld_pwm_read(priv, reg, val) \ + regmap_read((priv)->regmap, (priv)->offset + (reg), (val)) +#define sl28cpld_pwm_write(priv, reg, val) \ + regmap_write((priv)->regmap, (priv)->offset + (reg), (val)) + +struct sl28cpld_pwm { + struct pwm_chip pwm_chip; + struct regmap *regmap; + u32 offset; +}; +#define sl28cpld_pwm_from_chip(_chip) \ + container_of(_chip, struct sl28cpld_pwm, pwm_chip) + +static void sl28cpld_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct sl28cpld_pwm *priv = sl28cpld_pwm_from_chip(chip); + unsigned int reg; + int prescaler; + + sl28cpld_pwm_read(priv, SL28CPLD_PWM_CTRL, ®); + + state->enabled = reg & SL28CPLD_PWM_CTRL_ENABLE; + + prescaler = FIELD_GET(SL28CPLD_PWM_CTRL_PRESCALER_MASK, reg); + state->period = SL28CPLD_PWM_PERIOD(prescaler); + + sl28cpld_pwm_read(priv, SL28CPLD_PWM_CYCLE, ®); + state->duty_cycle = SL28CPLD_PWM_TO_DUTY_CYCLE(reg); + state->polarity = PWM_POLARITY_NORMAL; + + /* + * Sanitize values for the PWM core. Depending on the prescaler it + * might happen that we calculate a duty_cycle greater than the actual + * period. This might happen if someone (e.g. the bootloader) sets an + * invalid combination of values. The behavior of the hardware is + * undefined in this case. But we need to report sane values back to + * the PWM core. + */ + state->duty_cycle = min(state->duty_cycle, state->period); +} + +static int sl28cpld_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct sl28cpld_pwm *priv = sl28cpld_pwm_from_chip(chip); + unsigned int cycle, prescaler; + bool write_duty_cycle_first; + int ret; + u8 ctrl; + + /* Polarity inversion is not supported */ + if (state->polarity != PWM_POLARITY_NORMAL) + return -EINVAL; + + /* + * Calculate the prescaler. Pick the biggest period that isn't + * bigger than the requested period. + */ + prescaler = DIV_ROUND_UP_ULL(SL28CPLD_PWM_PERIOD(0), state->period); + prescaler = order_base_2(prescaler); + + if (prescaler > field_max(SL28CPLD_PWM_CTRL_PRESCALER_MASK)) + return -ERANGE; + + ctrl = FIELD_PREP(SL28CPLD_PWM_CTRL_PRESCALER_MASK, prescaler); + if (state->enabled) + ctrl |= SL28CPLD_PWM_CTRL_ENABLE; + + cycle = SL28CPLD_PWM_FROM_DUTY_CYCLE(state->duty_cycle); + cycle = min_t(unsigned int, cycle, SL28CPLD_PWM_MAX_DUTY_CYCLE(prescaler)); + + /* + * Work around the hardware limitation. See also above. Trap 100% duty + * cycle if the prescaler is 0. Set prescaler to 1 instead. We don't + * care about the frequency because its "all-one" in either case. + * + * We don't need to check the actual prescaler setting, because only + * if the prescaler is 0 we can have this particular value. + */ + if (cycle == SL28CPLD_PWM_MAX_DUTY_CYCLE(0)) { + ctrl &= ~SL28CPLD_PWM_CTRL_PRESCALER_MASK; + ctrl |= FIELD_PREP(SL28CPLD_PWM_CTRL_PRESCALER_MASK, 1); + cycle = SL28CPLD_PWM_MAX_DUTY_CYCLE(1); + } + + /* + * To avoid glitches when we switch the prescaler, we have to make sure + * we have a valid duty cycle for the new mode. + * + * Take the current prescaler (or the current period length) into + * account to decide whether we have to write the duty cycle or the new + * prescaler first. If the period length is decreasing we have to + * write the duty cycle first. + */ + write_duty_cycle_first = pwm->state.period > state->period; + + if (write_duty_cycle_first) { + ret = sl28cpld_pwm_write(priv, SL28CPLD_PWM_CYCLE, cycle); + if (ret) + return ret; + } + + ret = sl28cpld_pwm_write(priv, SL28CPLD_PWM_CTRL, ctrl); + if (ret) + return ret; + + if (!write_duty_cycle_first) { + ret = sl28cpld_pwm_write(priv, SL28CPLD_PWM_CYCLE, cycle); + if (ret) + return ret; + } + + return 0; +} + +static const struct pwm_ops sl28cpld_pwm_ops = { + .apply = sl28cpld_pwm_apply, + .get_state = sl28cpld_pwm_get_state, + .owner = THIS_MODULE, +}; + +static int sl28cpld_pwm_probe(struct platform_device *pdev) +{ + struct sl28cpld_pwm *priv; + struct pwm_chip *chip; + int ret; + + if (!pdev->dev.parent) { + dev_err(&pdev->dev, "no parent device\n"); + return -ENODEV; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!priv->regmap) { + dev_err(&pdev->dev, "could not get parent regmap\n"); + return -ENODEV; + } + + ret = device_property_read_u32(&pdev->dev, "reg", &priv->offset); + if (ret) { + dev_err(&pdev->dev, "no 'reg' property found (%pe)\n", + ERR_PTR(ret)); + return -EINVAL; + } + + /* Initialize the pwm_chip structure */ + chip = &priv->pwm_chip; + chip->dev = &pdev->dev; + chip->ops = &sl28cpld_pwm_ops; + chip->base = -1; + chip->npwm = 1; + + ret = pwmchip_add(&priv->pwm_chip); + if (ret) { + dev_err(&pdev->dev, "failed to add PWM chip (%pe)", + ERR_PTR(ret)); + return ret; + } + + platform_set_drvdata(pdev, priv); + + return 0; +} + +static int sl28cpld_pwm_remove(struct platform_device *pdev) +{ + struct sl28cpld_pwm *priv = platform_get_drvdata(pdev); + + return pwmchip_remove(&priv->pwm_chip); +} + +static const struct of_device_id sl28cpld_pwm_of_match[] = { + { .compatible = "kontron,sl28cpld-pwm" }, + {} +}; +MODULE_DEVICE_TABLE(of, sl28cpld_pwm_of_match); + +static struct platform_driver sl28cpld_pwm_driver = { + .probe = sl28cpld_pwm_probe, + .remove = sl28cpld_pwm_remove, + .driver = { + .name = "sl28cpld-pwm", + .of_match_table = sl28cpld_pwm_of_match, + }, +}; +module_platform_driver(sl28cpld_pwm_driver); + +MODULE_DESCRIPTION("sl28cpld PWM Driver"); +MODULE_AUTHOR("Michael Walle <michael@walle.cc>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-spear.c b/drivers/pwm/pwm-spear.c new file mode 100644 index 000000000..2d11ac277 --- /dev/null +++ b/drivers/pwm/pwm-spear.c @@ -0,0 +1,262 @@ +/* + * ST Microelectronics SPEAr Pulse Width Modulator driver + * + * Copyright (C) 2012 ST Microelectronics + * Shiraz Hashim <shiraz.linux.kernel@gmail.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/types.h> + +#define NUM_PWM 4 + +/* PWM registers and bits definitions */ +#define PWMCR 0x00 /* Control Register */ +#define PWMCR_PWM_ENABLE 0x1 +#define PWMCR_PRESCALE_SHIFT 2 +#define PWMCR_MIN_PRESCALE 0x00 +#define PWMCR_MAX_PRESCALE 0x3FFF + +#define PWMDCR 0x04 /* Duty Cycle Register */ +#define PWMDCR_MIN_DUTY 0x0001 +#define PWMDCR_MAX_DUTY 0xFFFF + +#define PWMPCR 0x08 /* Period Register */ +#define PWMPCR_MIN_PERIOD 0x0001 +#define PWMPCR_MAX_PERIOD 0xFFFF + +/* Following only available on 13xx SoCs */ +#define PWMMCR 0x3C /* Master Control Register */ +#define PWMMCR_PWM_ENABLE 0x1 + +/** + * struct spear_pwm_chip - struct representing pwm chip + * + * @mmio_base: base address of pwm chip + * @clk: pointer to clk structure of pwm chip + * @chip: linux pwm chip representation + */ +struct spear_pwm_chip { + void __iomem *mmio_base; + struct clk *clk; + struct pwm_chip chip; +}; + +static inline struct spear_pwm_chip *to_spear_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct spear_pwm_chip, chip); +} + +static inline u32 spear_pwm_readl(struct spear_pwm_chip *chip, unsigned int num, + unsigned long offset) +{ + return readl_relaxed(chip->mmio_base + (num << 4) + offset); +} + +static inline void spear_pwm_writel(struct spear_pwm_chip *chip, + unsigned int num, unsigned long offset, + unsigned long val) +{ + writel_relaxed(val, chip->mmio_base + (num << 4) + offset); +} + +static int spear_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct spear_pwm_chip *pc = to_spear_pwm_chip(chip); + u64 val, div, clk_rate; + unsigned long prescale = PWMCR_MIN_PRESCALE, pv, dc; + int ret; + + /* + * Find pv, dc and prescale to suit duty_ns and period_ns. This is done + * according to formulas described below: + * + * period_ns = 10^9 * (PRESCALE + 1) * PV / PWM_CLK_RATE + * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE + * + * PV = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1)) + * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1)) + */ + clk_rate = clk_get_rate(pc->clk); + while (1) { + div = 1000000000; + div *= 1 + prescale; + val = clk_rate * period_ns; + pv = div64_u64(val, div); + val = clk_rate * duty_ns; + dc = div64_u64(val, div); + + /* if duty_ns and period_ns are not achievable then return */ + if (pv < PWMPCR_MIN_PERIOD || dc < PWMDCR_MIN_DUTY) + return -EINVAL; + + /* + * if pv and dc have crossed their upper limit, then increase + * prescale and recalculate pv and dc. + */ + if (pv > PWMPCR_MAX_PERIOD || dc > PWMDCR_MAX_DUTY) { + if (++prescale > PWMCR_MAX_PRESCALE) + return -EINVAL; + continue; + } + break; + } + + /* + * NOTE: the clock to PWM has to be enabled first before writing to the + * registers. + */ + ret = clk_enable(pc->clk); + if (ret) + return ret; + + spear_pwm_writel(pc, pwm->hwpwm, PWMCR, + prescale << PWMCR_PRESCALE_SHIFT); + spear_pwm_writel(pc, pwm->hwpwm, PWMDCR, dc); + spear_pwm_writel(pc, pwm->hwpwm, PWMPCR, pv); + clk_disable(pc->clk); + + return 0; +} + +static int spear_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct spear_pwm_chip *pc = to_spear_pwm_chip(chip); + int rc = 0; + u32 val; + + rc = clk_enable(pc->clk); + if (rc) + return rc; + + val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR); + val |= PWMCR_PWM_ENABLE; + spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val); + + return 0; +} + +static void spear_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct spear_pwm_chip *pc = to_spear_pwm_chip(chip); + u32 val; + + val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR); + val &= ~PWMCR_PWM_ENABLE; + spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val); + + clk_disable(pc->clk); +} + +static const struct pwm_ops spear_pwm_ops = { + .config = spear_pwm_config, + .enable = spear_pwm_enable, + .disable = spear_pwm_disable, + .owner = THIS_MODULE, +}; + +static int spear_pwm_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct spear_pwm_chip *pc; + struct resource *r; + int ret; + u32 val; + + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pc->mmio_base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(pc->mmio_base)) + return PTR_ERR(pc->mmio_base); + + pc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pc->clk)) + return PTR_ERR(pc->clk); + + platform_set_drvdata(pdev, pc); + + pc->chip.dev = &pdev->dev; + pc->chip.ops = &spear_pwm_ops; + pc->chip.base = -1; + pc->chip.npwm = NUM_PWM; + + ret = clk_prepare(pc->clk); + if (ret) + return ret; + + if (of_device_is_compatible(np, "st,spear1340-pwm")) { + ret = clk_enable(pc->clk); + if (ret) { + clk_unprepare(pc->clk); + return ret; + } + /* + * Following enables PWM chip, channels would still be + * enabled individually through their control register + */ + val = readl_relaxed(pc->mmio_base + PWMMCR); + val |= PWMMCR_PWM_ENABLE; + writel_relaxed(val, pc->mmio_base + PWMMCR); + + clk_disable(pc->clk); + } + + ret = pwmchip_add(&pc->chip); + if (ret < 0) { + clk_unprepare(pc->clk); + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + } + + return ret; +} + +static int spear_pwm_remove(struct platform_device *pdev) +{ + struct spear_pwm_chip *pc = platform_get_drvdata(pdev); + + /* clk was prepared in probe, hence unprepare it here */ + clk_unprepare(pc->clk); + return pwmchip_remove(&pc->chip); +} + +static const struct of_device_id spear_pwm_of_match[] = { + { .compatible = "st,spear320-pwm" }, + { .compatible = "st,spear1340-pwm" }, + { } +}; + +MODULE_DEVICE_TABLE(of, spear_pwm_of_match); + +static struct platform_driver spear_pwm_driver = { + .driver = { + .name = "spear-pwm", + .of_match_table = spear_pwm_of_match, + }, + .probe = spear_pwm_probe, + .remove = spear_pwm_remove, +}; + +module_platform_driver(spear_pwm_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Shiraz Hashim <shiraz.linux.kernel@gmail.com>"); +MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.com>"); +MODULE_ALIAS("platform:spear-pwm"); diff --git a/drivers/pwm/pwm-sprd.c b/drivers/pwm/pwm-sprd.c new file mode 100644 index 000000000..b23456d38 --- /dev/null +++ b/drivers/pwm/pwm-sprd.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Spreadtrum Communications Inc. + */ + +#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> + +#define SPRD_PWM_PRESCALE 0x0 +#define SPRD_PWM_MOD 0x4 +#define SPRD_PWM_DUTY 0x8 +#define SPRD_PWM_ENABLE 0x18 + +#define SPRD_PWM_MOD_MAX GENMASK(7, 0) +#define SPRD_PWM_DUTY_MSK GENMASK(15, 0) +#define SPRD_PWM_PRESCALE_MSK GENMASK(7, 0) +#define SPRD_PWM_ENABLE_BIT BIT(0) + +#define SPRD_PWM_CHN_NUM 4 +#define SPRD_PWM_REGS_SHIFT 5 +#define SPRD_PWM_CHN_CLKS_NUM 2 +#define SPRD_PWM_CHN_OUTPUT_CLK 1 + +struct sprd_pwm_chn { + struct clk_bulk_data clks[SPRD_PWM_CHN_CLKS_NUM]; + u32 clk_rate; +}; + +struct sprd_pwm_chip { + void __iomem *base; + struct device *dev; + struct pwm_chip chip; + int num_pwms; + struct sprd_pwm_chn chn[SPRD_PWM_CHN_NUM]; +}; + +/* + * The list of clocks required by PWM channels, and each channel has 2 clocks: + * enable clock and pwm clock. + */ +static const char * const sprd_pwm_clks[] = { + "enable0", "pwm0", + "enable1", "pwm1", + "enable2", "pwm2", + "enable3", "pwm3", +}; + +static u32 sprd_pwm_read(struct sprd_pwm_chip *spc, u32 hwid, u32 reg) +{ + u32 offset = reg + (hwid << SPRD_PWM_REGS_SHIFT); + + return readl_relaxed(spc->base + offset); +} + +static void sprd_pwm_write(struct sprd_pwm_chip *spc, u32 hwid, + u32 reg, u32 val) +{ + u32 offset = reg + (hwid << SPRD_PWM_REGS_SHIFT); + + writel_relaxed(val, spc->base + offset); +} + +static void sprd_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct sprd_pwm_chip *spc = + container_of(chip, struct sprd_pwm_chip, chip); + struct sprd_pwm_chn *chn = &spc->chn[pwm->hwpwm]; + u32 val, duty, prescale; + u64 tmp; + int ret; + + /* + * The clocks to PWM channel has to be enabled first before + * reading to the registers. + */ + ret = clk_bulk_prepare_enable(SPRD_PWM_CHN_CLKS_NUM, chn->clks); + if (ret) { + dev_err(spc->dev, "failed to enable pwm%u clocks\n", + pwm->hwpwm); + return; + } + + val = sprd_pwm_read(spc, pwm->hwpwm, SPRD_PWM_ENABLE); + if (val & SPRD_PWM_ENABLE_BIT) + state->enabled = true; + else + state->enabled = false; + + /* + * The hardware provides a counter that is feed by the source clock. + * The period length is (PRESCALE + 1) * MOD counter steps. + * The duty cycle length is (PRESCALE + 1) * DUTY counter steps. + * Thus the period_ns and duty_ns calculation formula should be: + * period_ns = NSEC_PER_SEC * (prescale + 1) * mod / clk_rate + * duty_ns = NSEC_PER_SEC * (prescale + 1) * duty / clk_rate + */ + val = sprd_pwm_read(spc, pwm->hwpwm, SPRD_PWM_PRESCALE); + prescale = val & SPRD_PWM_PRESCALE_MSK; + tmp = (prescale + 1) * NSEC_PER_SEC * SPRD_PWM_MOD_MAX; + state->period = DIV_ROUND_CLOSEST_ULL(tmp, chn->clk_rate); + + val = sprd_pwm_read(spc, pwm->hwpwm, SPRD_PWM_DUTY); + duty = val & SPRD_PWM_DUTY_MSK; + tmp = (prescale + 1) * NSEC_PER_SEC * duty; + state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, chn->clk_rate); + state->polarity = PWM_POLARITY_NORMAL; + + /* Disable PWM clocks if the PWM channel is not in enable state. */ + if (!state->enabled) + clk_bulk_disable_unprepare(SPRD_PWM_CHN_CLKS_NUM, chn->clks); +} + +static int sprd_pwm_config(struct sprd_pwm_chip *spc, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct sprd_pwm_chn *chn = &spc->chn[pwm->hwpwm]; + u32 prescale, duty; + u64 tmp; + + /* + * The hardware provides a counter that is feed by the source clock. + * The period length is (PRESCALE + 1) * MOD counter steps. + * The duty cycle length is (PRESCALE + 1) * DUTY counter steps. + * + * To keep the maths simple we're always using MOD = SPRD_PWM_MOD_MAX. + * The value for PRESCALE is selected such that the resulting period + * gets the maximal length not bigger than the requested one with the + * given settings (MOD = SPRD_PWM_MOD_MAX and input clock). + */ + duty = duty_ns * SPRD_PWM_MOD_MAX / period_ns; + + tmp = (u64)chn->clk_rate * period_ns; + do_div(tmp, NSEC_PER_SEC); + prescale = DIV_ROUND_CLOSEST_ULL(tmp, SPRD_PWM_MOD_MAX) - 1; + if (prescale > SPRD_PWM_PRESCALE_MSK) + prescale = SPRD_PWM_PRESCALE_MSK; + + /* + * Note: Writing DUTY triggers the hardware to actually apply the + * values written to MOD and DUTY to the output, so must keep writing + * DUTY last. + * + * The hardware can ensures that current running period is completed + * before changing a new configuration to avoid mixed settings. + */ + sprd_pwm_write(spc, pwm->hwpwm, SPRD_PWM_PRESCALE, prescale); + sprd_pwm_write(spc, pwm->hwpwm, SPRD_PWM_MOD, SPRD_PWM_MOD_MAX); + sprd_pwm_write(spc, pwm->hwpwm, SPRD_PWM_DUTY, duty); + + return 0; +} + +static int sprd_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct sprd_pwm_chip *spc = + container_of(chip, struct sprd_pwm_chip, chip); + struct sprd_pwm_chn *chn = &spc->chn[pwm->hwpwm]; + struct pwm_state *cstate = &pwm->state; + int ret; + + if (state->enabled) { + if (!cstate->enabled) { + /* + * The clocks to PWM channel has to be enabled first + * before writing to the registers. + */ + ret = clk_bulk_prepare_enable(SPRD_PWM_CHN_CLKS_NUM, + chn->clks); + if (ret) { + dev_err(spc->dev, + "failed to enable pwm%u clocks\n", + pwm->hwpwm); + return ret; + } + } + + ret = sprd_pwm_config(spc, pwm, state->duty_cycle, + state->period); + if (ret) + return ret; + + sprd_pwm_write(spc, pwm->hwpwm, SPRD_PWM_ENABLE, 1); + } else if (cstate->enabled) { + /* + * Note: After setting SPRD_PWM_ENABLE to zero, the controller + * will not wait for current period to be completed, instead it + * will stop the PWM channel immediately. + */ + sprd_pwm_write(spc, pwm->hwpwm, SPRD_PWM_ENABLE, 0); + + clk_bulk_disable_unprepare(SPRD_PWM_CHN_CLKS_NUM, chn->clks); + } + + return 0; +} + +static const struct pwm_ops sprd_pwm_ops = { + .apply = sprd_pwm_apply, + .get_state = sprd_pwm_get_state, + .owner = THIS_MODULE, +}; + +static int sprd_pwm_clk_init(struct sprd_pwm_chip *spc) +{ + struct clk *clk_pwm; + int ret, i; + + for (i = 0; i < SPRD_PWM_CHN_NUM; i++) { + struct sprd_pwm_chn *chn = &spc->chn[i]; + int j; + + for (j = 0; j < SPRD_PWM_CHN_CLKS_NUM; ++j) + chn->clks[j].id = + sprd_pwm_clks[i * SPRD_PWM_CHN_CLKS_NUM + j]; + + ret = devm_clk_bulk_get(spc->dev, SPRD_PWM_CHN_CLKS_NUM, + chn->clks); + if (ret) { + if (ret == -ENOENT) + break; + + return dev_err_probe(spc->dev, ret, + "failed to get channel clocks\n"); + } + + clk_pwm = chn->clks[SPRD_PWM_CHN_OUTPUT_CLK].clk; + chn->clk_rate = clk_get_rate(clk_pwm); + } + + if (!i) { + dev_err(spc->dev, "no available PWM channels\n"); + return -ENODEV; + } + + spc->num_pwms = i; + + return 0; +} + +static int sprd_pwm_probe(struct platform_device *pdev) +{ + struct sprd_pwm_chip *spc; + int ret; + + spc = devm_kzalloc(&pdev->dev, sizeof(*spc), GFP_KERNEL); + if (!spc) + return -ENOMEM; + + spc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(spc->base)) + return PTR_ERR(spc->base); + + spc->dev = &pdev->dev; + platform_set_drvdata(pdev, spc); + + ret = sprd_pwm_clk_init(spc); + if (ret) + return ret; + + spc->chip.dev = &pdev->dev; + spc->chip.ops = &sprd_pwm_ops; + spc->chip.base = -1; + spc->chip.npwm = spc->num_pwms; + + ret = pwmchip_add(&spc->chip); + if (ret) + dev_err(&pdev->dev, "failed to add PWM chip\n"); + + return ret; +} + +static int sprd_pwm_remove(struct platform_device *pdev) +{ + struct sprd_pwm_chip *spc = platform_get_drvdata(pdev); + + return pwmchip_remove(&spc->chip); +} + +static const struct of_device_id sprd_pwm_of_match[] = { + { .compatible = "sprd,ums512-pwm", }, + { }, +}; +MODULE_DEVICE_TABLE(of, sprd_pwm_of_match); + +static struct platform_driver sprd_pwm_driver = { + .driver = { + .name = "sprd-pwm", + .of_match_table = sprd_pwm_of_match, + }, + .probe = sprd_pwm_probe, + .remove = sprd_pwm_remove, +}; + +module_platform_driver(sprd_pwm_driver); + +MODULE_DESCRIPTION("Spreadtrum PWM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-sti.c b/drivers/pwm/pwm-sti.c new file mode 100644 index 000000000..9b2174ada --- /dev/null +++ b/drivers/pwm/pwm-sti.c @@ -0,0 +1,685 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PWM device driver for ST SoCs + * + * Copyright (C) 2013-2016 STMicroelectronics (R&D) Limited + * + * Author: Ajit Pal Singh <ajitpal.singh@st.com> + * Lee Jones <lee.jones@linaro.org> + */ + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/math64.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/wait.h> + +#define PWM_OUT_VAL(x) (0x00 + (4 * (x))) /* Device's Duty Cycle register */ +#define PWM_CPT_VAL(x) (0x10 + (4 * (x))) /* Capture value */ +#define PWM_CPT_EDGE(x) (0x30 + (4 * (x))) /* Edge to capture on */ + +#define STI_PWM_CTRL 0x50 /* Control/Config register */ +#define STI_INT_EN 0x54 /* Interrupt Enable/Disable register */ +#define STI_INT_STA 0x58 /* Interrupt Status register */ +#define PWM_INT_ACK 0x5c +#define PWM_PRESCALE_LOW_MASK 0x0f +#define PWM_PRESCALE_HIGH_MASK 0xf0 +#define PWM_CPT_EDGE_MASK 0x03 +#define PWM_INT_ACK_MASK 0x1ff + +#define STI_MAX_CPT_DEVS 4 +#define CPT_DC_MAX 0xff + +/* Regfield IDs */ +enum { + /* Bits in PWM_CTRL*/ + PWMCLK_PRESCALE_LOW, + PWMCLK_PRESCALE_HIGH, + CPTCLK_PRESCALE, + + PWM_OUT_EN, + PWM_CPT_EN, + + PWM_CPT_INT_EN, + PWM_CPT_INT_STAT, + + /* Keep last */ + MAX_REGFIELDS +}; + +/* + * Each capture input can be programmed to detect rising-edge, falling-edge, + * either edge or neither egde. + */ +enum sti_cpt_edge { + CPT_EDGE_DISABLED, + CPT_EDGE_RISING, + CPT_EDGE_FALLING, + CPT_EDGE_BOTH, +}; + +struct sti_cpt_ddata { + u32 snapshot[3]; + unsigned int index; + struct mutex lock; + 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; + struct regmap_field *prescale_low; + struct regmap_field *prescale_high; + struct regmap_field *pwm_out_en; + struct regmap_field *pwm_cpt_en; + struct regmap_field *pwm_cpt_int_en; + struct regmap_field *pwm_cpt_int_stat; + struct pwm_chip chip; + struct pwm_device *cur; + unsigned long configured; + unsigned int en_count; + struct mutex sti_pwm_lock; /* To sync between enable/disable calls */ + void __iomem *mmio; +}; + +static const struct reg_field sti_pwm_regfields[MAX_REGFIELDS] = { + [PWMCLK_PRESCALE_LOW] = REG_FIELD(STI_PWM_CTRL, 0, 3), + [PWMCLK_PRESCALE_HIGH] = REG_FIELD(STI_PWM_CTRL, 11, 14), + [CPTCLK_PRESCALE] = REG_FIELD(STI_PWM_CTRL, 4, 8), + [PWM_OUT_EN] = REG_FIELD(STI_PWM_CTRL, 9, 9), + [PWM_CPT_EN] = REG_FIELD(STI_PWM_CTRL, 10, 10), + [PWM_CPT_INT_EN] = REG_FIELD(STI_INT_EN, 1, 4), + [PWM_CPT_INT_STAT] = REG_FIELD(STI_INT_STA, 1, 4), +}; + +static inline struct sti_pwm_chip *to_sti_pwmchip(struct pwm_chip *chip) +{ + return container_of(chip, struct sti_pwm_chip, chip); +} + +/* + * Calculate the prescaler value corresponding to the period. + */ +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; + + clk_rate = clk_get_rate(pc->pwm_clk); + if (!clk_rate) { + dev_err(pc->dev, "failed to get clock rate\n"); + return -EINVAL; + } + + /* + * prescale = ((period_ns * clk_rate) / (10^9 * (max_pwm_cnt + 1)) - 1 + */ + value = NSEC_PER_SEC / clk_rate; + value *= cdata->max_pwm_cnt + 1; + + if (period % value) + return -EINVAL; + + ps = period / value - 1; + if (ps > cdata->max_prescale) + return -EINVAL; + + *prescale = ps; + + return 0; +} + +/* + * For STiH4xx PWM IP, the PWM period is fixed to 256 local clock cycles. The + * only way to change the period (apart from changing the PWM input clock) is + * to change the PWM clock prescaler. + * + * The prescaler is of 8 bits, so 256 prescaler values and hence 256 possible + * period values are supported (for a particular clock rate). The requested + * period will be applied only if it matches one of these 256 values. + */ +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; + bool period_same = false; + int ret; + + ncfg = hweight_long(pc->configured); + if (ncfg) + period_same = (period_ns == pwm_get_period(cur)); + + /* + * Allow configuration changes if one of the following conditions + * satisfy. + * 1. No devices have been configured. + * 2. Only one device has been configured and the new request is for + * the same device. + * 3. Only one device has been configured and the new request is for + * a new device and period of the new device is same as the current + * configured period. + * 4. More than one devices are configured and period of the new + * requestis the same as the current period. + */ + if (!ncfg || + ((ncfg == 1) && (pwm->hwpwm == cur->hwpwm)) || + ((ncfg == 1) && (pwm->hwpwm != cur->hwpwm) && period_same) || + ((ncfg > 1) && period_same)) { + /* Enable clock before writing to PWM registers. */ + ret = clk_enable(pc->pwm_clk); + if (ret) + return ret; + + ret = clk_enable(pc->cpt_clk); + if (ret) + return ret; + + if (!period_same) { + ret = sti_pwm_get_prescale(pc, period_ns, &prescale); + if (ret) + goto clk_dis; + + value = prescale & PWM_PRESCALE_LOW_MASK; + + ret = regmap_field_write(pc->prescale_low, value); + if (ret) + goto clk_dis; + + value = (prescale & PWM_PRESCALE_HIGH_MASK) >> 4; + + ret = regmap_field_write(pc->prescale_high, value); + if (ret) + goto clk_dis; + } + + /* + * When PWMVal == 0, PWM pulse = 1 local clock cycle. + * When PWMVal == max_pwm_count, + * 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; + + ret = regmap_write(pc->regmap, PWM_OUT_VAL(pwm->hwpwm), value); + if (ret) + goto clk_dis; + + ret = regmap_field_write(pc->pwm_cpt_int_en, 0); + + set_bit(pwm->hwpwm, &pc->configured); + pc->cur = pwm; + + dev_dbg(dev, "prescale:%u, period:%i, duty:%i, value:%u\n", + prescale, period_ns, duty_ns, value); + } else { + return -EINVAL; + } + +clk_dis: + clk_disable(pc->pwm_clk); + clk_disable(pc->cpt_clk); + return ret; +} + +static int sti_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct sti_pwm_chip *pc = to_sti_pwmchip(chip); + struct device *dev = pc->dev; + int ret = 0; + + /* + * Since we have a common enable for all PWM devices, do not enable if + * already enabled. + */ + mutex_lock(&pc->sti_pwm_lock); + + if (!pc->en_count) { + ret = clk_enable(pc->pwm_clk); + if (ret) + goto out; + + ret = clk_enable(pc->cpt_clk); + if (ret) + goto out; + + ret = regmap_field_write(pc->pwm_out_en, 1); + if (ret) { + dev_err(dev, "failed to enable PWM device %u: %d\n", + pwm->hwpwm, ret); + goto out; + } + } + + pc->en_count++; + +out: + mutex_unlock(&pc->sti_pwm_lock); + return ret; +} + +static void sti_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct sti_pwm_chip *pc = to_sti_pwmchip(chip); + + mutex_lock(&pc->sti_pwm_lock); + + if (--pc->en_count) { + mutex_unlock(&pc->sti_pwm_lock); + return; + } + + regmap_field_write(pc->pwm_out_en, 0); + + clk_disable(pc->pwm_clk); + clk_disable(pc->cpt_clk); + + mutex_unlock(&pc->sti_pwm_lock); +} + +static void sti_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct sti_pwm_chip *pc = to_sti_pwmchip(chip); + + clear_bit(pwm->hwpwm, &pc->configured); +} + +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 device *dev = pc->dev; + unsigned int effective_ticks; + unsigned long long high, low; + int ret; + + if (pwm->hwpwm >= cdata->cpt_num_devs) { + dev_err(dev, "device %u is not valid\n", pwm->hwpwm); + return -EINVAL; + } + + mutex_lock(&ddata->lock); + ddata->index = 0; + + /* Prepare capture measurement */ + regmap_write(pc->regmap, PWM_CPT_EDGE(pwm->hwpwm), CPT_EDGE_RISING); + regmap_field_write(pc->pwm_cpt_int_en, BIT(pwm->hwpwm)); + + /* Enable capture */ + ret = regmap_field_write(pc->pwm_cpt_en, 1); + if (ret) { + dev_err(dev, "failed to enable PWM capture %u: %d\n", + pwm->hwpwm, ret); + goto out; + } + + ret = wait_event_interruptible_timeout(ddata->wait, ddata->index > 1, + msecs_to_jiffies(timeout)); + + regmap_write(pc->regmap, PWM_CPT_EDGE(pwm->hwpwm), CPT_EDGE_DISABLED); + + if (ret == -ERESTARTSYS) + goto out; + + switch (ddata->index) { + case 0: + case 1: + /* + * Getting here could mean: + * - input signal is constant of less than 1 Hz + * - there is no input signal at all + * + * In such case the frequency is rounded down to 0 + */ + result->period = 0; + result->duty_cycle = 0; + + break; + + case 2: + /* We have everying we need */ + high = ddata->snapshot[1] - ddata->snapshot[0]; + low = ddata->snapshot[2] - ddata->snapshot[1]; + + effective_ticks = clk_get_rate(pc->cpt_clk); + + result->period = (high + low) * NSEC_PER_SEC; + result->period /= effective_ticks; + + result->duty_cycle = high * NSEC_PER_SEC; + result->duty_cycle /= effective_ticks; + + break; + + default: + dev_err(dev, "internal error\n"); + break; + } + +out: + /* Disable capture */ + regmap_field_write(pc->pwm_cpt_en, 0); + + mutex_unlock(&ddata->lock); + return ret; +} + +static const struct pwm_ops sti_pwm_ops = { + .capture = sti_pwm_capture, + .config = sti_pwm_config, + .enable = sti_pwm_enable, + .disable = sti_pwm_disable, + .free = sti_pwm_free, + .owner = THIS_MODULE, +}; + +static irqreturn_t sti_pwm_interrupt(int irq, void *data) +{ + struct sti_pwm_chip *pc = data; + struct device *dev = pc->dev; + struct sti_cpt_ddata *ddata; + int devicenum; + unsigned int cpt_int_stat; + unsigned int reg; + int ret = IRQ_NONE; + + ret = regmap_field_read(pc->pwm_cpt_int_stat, &cpt_int_stat); + if (ret) + return ret; + + while (cpt_int_stat) { + devicenum = ffs(cpt_int_stat) - 1; + + ddata = &pc->cdata->ddata[devicenum]; + + /* + * Capture input: + * _______ _______ + * | | | | + * __| |_________________| |________ + * ^0 ^1 ^2 + * + * Capture start by the first available rising edge. When a + * capture event occurs, capture value (CPT_VALx) is stored, + * index incremented, capture edge changed. + * + * After the capture, if the index > 1, we have collected the + * necessary data so we signal the thread waiting for it and + * disable the capture by setting capture edge to none + */ + + regmap_read(pc->regmap, + PWM_CPT_VAL(devicenum), + &ddata->snapshot[ddata->index]); + + switch (ddata->index) { + case 0: + case 1: + regmap_read(pc->regmap, PWM_CPT_EDGE(devicenum), ®); + reg ^= PWM_CPT_EDGE_MASK; + regmap_write(pc->regmap, PWM_CPT_EDGE(devicenum), reg); + + ddata->index++; + break; + + case 2: + regmap_write(pc->regmap, + PWM_CPT_EDGE(devicenum), + CPT_EDGE_DISABLED); + wake_up(&ddata->wait); + break; + + default: + dev_err(dev, "Internal error\n"); + } + + cpt_int_stat &= ~BIT_MASK(devicenum); + + ret = IRQ_HANDLED; + } + + /* Just ACK everything */ + regmap_write(pc->regmap, PWM_INT_ACK, PWM_INT_ACK_MASK); + + return ret; +} + +static int sti_pwm_probe_dt(struct sti_pwm_chip *pc) +{ + struct device *dev = pc->dev; + const struct reg_field *reg_fields; + struct device_node *np = dev->of_node; + struct sti_pwm_compat_data *cdata = pc->cdata; + u32 num_devs; + int ret; + + ret = of_property_read_u32(np, "st,pwm-num-chan", &num_devs); + if (!ret) + cdata->pwm_num_devs = num_devs; + + ret = of_property_read_u32(np, "st,capture-num-chan", &num_devs); + if (!ret) + cdata->cpt_num_devs = num_devs; + + if (!cdata->pwm_num_devs && !cdata->cpt_num_devs) { + dev_err(dev, "No channels configured\n"); + return -EINVAL; + } + + reg_fields = cdata->reg_fields; + + pc->prescale_low = devm_regmap_field_alloc(dev, pc->regmap, + reg_fields[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]); + 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]); + 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]); + 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]); + 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]); + if (PTR_ERR_OR_ZERO(pc->pwm_cpt_int_stat)) + return PTR_ERR(pc->pwm_cpt_int_stat); + + return 0; +} + +static const struct regmap_config sti_pwm_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static int sti_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sti_pwm_compat_data *cdata; + struct sti_pwm_chip *pc; + struct resource *res; + unsigned int i; + int irq, ret; + + pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + cdata = devm_kzalloc(dev, sizeof(*cdata), GFP_KERNEL); + if (!cdata) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + pc->mmio = devm_ioremap_resource(dev, res); + if (IS_ERR(pc->mmio)) + return PTR_ERR(pc->mmio); + + pc->regmap = devm_regmap_init_mmio(dev, pc->mmio, + &sti_pwm_regmap_config); + if (IS_ERR(pc->regmap)) + return PTR_ERR(pc->regmap); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + 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; + } + + /* + * 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 = 0; + cdata->cpt_num_devs = 0; + + pc->cdata = cdata; + pc->dev = dev; + pc->en_count = 0; + mutex_init(&pc->sti_pwm_lock); + + ret = sti_pwm_probe_dt(pc); + if (ret) + return ret; + + 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 (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); + } + + 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) + return -ENOMEM; + } + + pc->chip.dev = dev; + pc->chip.ops = &sti_pwm_ops; + pc->chip.base = -1; + pc->chip.npwm = pc->cdata->pwm_num_devs; + + 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); + } + + ret = pwmchip_add(&pc->chip); + if (ret < 0) { + clk_unprepare(pc->pwm_clk); + clk_unprepare(pc->cpt_clk); + return ret; + } + + platform_set_drvdata(pdev, pc); + + return 0; +} + +static int sti_pwm_remove(struct platform_device *pdev) +{ + struct sti_pwm_chip *pc = platform_get_drvdata(pdev); + unsigned int i; + + for (i = 0; i < pc->cdata->pwm_num_devs; i++) + pwm_disable(&pc->chip.pwms[i]); + + clk_unprepare(pc->pwm_clk); + clk_unprepare(pc->cpt_clk); + + return pwmchip_remove(&pc->chip); +} + +static const struct of_device_id sti_pwm_of_match[] = { + { .compatible = "st,sti-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sti_pwm_of_match); + +static struct platform_driver sti_pwm_driver = { + .driver = { + .name = "sti-pwm", + .of_match_table = sti_pwm_of_match, + }, + .probe = sti_pwm_probe, + .remove = sti_pwm_remove, +}; +module_platform_driver(sti_pwm_driver); + +MODULE_AUTHOR("Ajit Pal Singh <ajitpal.singh@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST PWM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-stm32-lp.c b/drivers/pwm/pwm-stm32-lp.c new file mode 100644 index 000000000..c8a847fcb --- /dev/null +++ b/drivers/pwm/pwm-stm32-lp.c @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * STM32 Low-Power Timer PWM driver + * + * Copyright (C) STMicroelectronics 2017 + * + * Author: Gerald Baeza <gerald.baeza@st.com> + * + * Inspired by Gerald Baeza's pwm-stm32 driver + */ + +#include <linux/bitfield.h> +#include <linux/mfd/stm32-lptimer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +struct stm32_pwm_lp { + struct pwm_chip chip; + struct clk *clk; + struct regmap *regmap; +}; + +static inline struct stm32_pwm_lp *to_stm32_pwm_lp(struct pwm_chip *chip) +{ + return container_of(chip, struct stm32_pwm_lp, 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_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; + bool reenable; + int ret; + + pwm_get_state(pwm, &cstate); + reenable = !cstate.enabled; + + if (!state->enabled) { + if (cstate.enabled) { + /* 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); + } + return 0; + } + + /* Calculate the period and prescaler value */ + div = (unsigned long long)clk_get_rate(priv->clk) * state->period; + do_div(div, NSEC_PER_SEC); + if (!div) { + /* Clock is too slow to achieve requested period. */ + dev_dbg(priv->chip.dev, "Can't reach %llu ns\n", state->period); + return -EINVAL; + } + + prd = div; + while (div > STM32_LPTIM_MAX_ARR) { + presc++; + if ((1 << presc) > STM32_LPTIM_MAX_PRESCALER) { + dev_err(priv->chip.dev, "max prescaler exceeded\n"); + return -EINVAL; + } + div = prd >> presc; + } + prd = div; + + /* Calculate the duty cycle */ + dty = prd * state->duty_cycle; + do_div(dty, state->period); + + if (!cstate.enabled) { + /* enable clock to drive PWM counter */ + ret = clk_enable(priv->clk); + if (ret) + 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)) { + val = FIELD_PREP(STM32_LPTIM_PRESC, presc); + val |= FIELD_PREP(STM32_LPTIM_WAVPOL, state->polarity); + mask = STM32_LPTIM_PRESC | STM32_LPTIM_WAVPOL; + + /* Must disable LP timer to modify CFGR */ + reenable = true; + ret = regmap_write(priv->regmap, STM32_LPTIM_CR, 0); + if (ret) + goto err; + + ret = regmap_update_bits(priv->regmap, STM32_LPTIM_CFGR, mask, + val); + if (ret) + goto err; + } + + if (reenable) { + /* Must (re)enable LP timer to modify CMP & ARR */ + ret = regmap_write(priv->regmap, STM32_LPTIM_CR, + STM32_LPTIM_ENABLE); + if (ret) + goto err; + } + + ret = regmap_write(priv->regmap, STM32_LPTIM_ARR, prd - 1); + if (ret) + goto err; + + ret = regmap_write(priv->regmap, 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, + (val & STM32_LPTIM_CMPOK_ARROK) == STM32_LPTIM_CMPOK_ARROK, + 100, 1000); + if (ret) { + dev_err(priv->chip.dev, "ARR/CMP registers write issue\n"); + goto err; + } + ret = regmap_write(priv->regmap, STM32_LPTIM_ICR, + STM32_LPTIM_CMPOKCF_ARROKCF); + if (ret) + goto err; + + if (reenable) { + /* Start LP timer in continuous mode */ + ret = regmap_update_bits(priv->regmap, STM32_LPTIM_CR, + STM32_LPTIM_CNTSTRT, + STM32_LPTIM_CNTSTRT); + if (ret) { + regmap_write(priv->regmap, STM32_LPTIM_CR, 0); + goto err; + } + } + + return 0; +err: + if (!cstate.enabled) + clk_disable(priv->clk); + + return ret; +} + +static void stm32_pwm_lp_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct stm32_pwm_lp *priv = to_stm32_pwm_lp(chip); + unsigned long rate = clk_get_rate(priv->clk); + u32 val, presc, prd; + u64 tmp; + + regmap_read(priv->regmap, STM32_LPTIM_CR, &val); + state->enabled = !!FIELD_GET(STM32_LPTIM_ENABLE, val); + /* Keep PWM counter clock refcount in sync with PWM initial state */ + if (state->enabled) + clk_enable(priv->clk); + + regmap_read(priv->regmap, STM32_LPTIM_CFGR, &val); + presc = FIELD_GET(STM32_LPTIM_PRESC, val); + 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); + tmp = prd - val; + tmp = (tmp << presc) * NSEC_PER_SEC; + state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate); +} + +static const struct pwm_ops stm32_pwm_lp_ops = { + .owner = THIS_MODULE, + .apply = stm32_pwm_lp_apply, + .get_state = stm32_pwm_lp_get_state, +}; + +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; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = ddata->regmap; + priv->clk = ddata->clk; + priv->chip.base = -1; + priv->chip.dev = &pdev->dev; + priv->chip.ops = &stm32_pwm_lp_ops; + priv->chip.npwm = 1; + priv->chip.of_xlate = of_pwm_xlate_with_flags; + priv->chip.of_pwm_n_cells = 3; + + ret = pwmchip_add(&priv->chip); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, priv); + + return 0; +} + +static int stm32_pwm_lp_remove(struct platform_device *pdev) +{ + struct stm32_pwm_lp *priv = platform_get_drvdata(pdev); + + return pwmchip_remove(&priv->chip); +} + +static int __maybe_unused stm32_pwm_lp_suspend(struct device *dev) +{ + struct stm32_pwm_lp *priv = dev_get_drvdata(dev); + struct pwm_state state; + + pwm_get_state(&priv->chip.pwms[0], &state); + if (state.enabled) { + dev_err(dev, "The consumer didn't stop us (%s)\n", + priv->chip.pwms[0].label); + return -EBUSY; + } + + return pinctrl_pm_select_sleep_state(dev); +} + +static int __maybe_unused stm32_pwm_lp_resume(struct device *dev) +{ + return pinctrl_pm_select_default_state(dev); +} + +static SIMPLE_DEV_PM_OPS(stm32_pwm_lp_pm_ops, stm32_pwm_lp_suspend, + stm32_pwm_lp_resume); + +static const struct of_device_id stm32_pwm_lp_of_match[] = { + { .compatible = "st,stm32-pwm-lp", }, + {}, +}; +MODULE_DEVICE_TABLE(of, stm32_pwm_lp_of_match); + +static struct platform_driver stm32_pwm_lp_driver = { + .probe = stm32_pwm_lp_probe, + .remove = stm32_pwm_lp_remove, + .driver = { + .name = "stm32-pwm-lp", + .of_match_table = of_match_ptr(stm32_pwm_lp_of_match), + .pm = &stm32_pwm_lp_pm_ops, + }, +}; +module_platform_driver(stm32_pwm_lp_driver); + +MODULE_ALIAS("platform:stm32-pwm-lp"); +MODULE_DESCRIPTION("STMicroelectronics STM32 PWM LP driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c new file mode 100644 index 000000000..69b7bc604 --- /dev/null +++ b/drivers/pwm/pwm-stm32.c @@ -0,0 +1,713 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics 2016 + * + * Author: Gerald Baeza <gerald.baeza@st.com> + * + * Inspired by timer-stm32.c from Maxime Coquelin + * pwm-atmel.c from Bo Shen + */ + +#include <linux/bitfield.h> +#include <linux/mfd/stm32-timers.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +#define CCMR_CHANNEL_SHIFT 8 +#define CCMR_CHANNEL_MASK 0xFF +#define MAX_BREAKINPUT 2 + +struct stm32_breakinput { + u32 index; + u32 level; + u32 filter; +}; + +struct stm32_pwm { + struct pwm_chip chip; + struct mutex lock; /* protect pwm config/enable */ + struct clk *clk; + struct regmap *regmap; + u32 max_arr; + bool have_complementary_output; + struct stm32_breakinput breakinputs[MAX_BREAKINPUT]; + unsigned int num_breakinputs; + u32 capture[4] ____cacheline_aligned; /* DMA'able buffer */ +}; + +static inline struct stm32_pwm *to_stm32_pwm_dev(struct pwm_chip *chip) +{ + return container_of(chip, struct stm32_pwm, chip); +} + +static u32 active_channels(struct stm32_pwm *dev) +{ + u32 ccer; + + regmap_read(dev->regmap, TIM_CCER, &ccer); + + return ccer & TIM_CCER_CCXE; +} + +static int write_ccrx(struct stm32_pwm *dev, int ch, u32 value) +{ + switch (ch) { + case 0: + return regmap_write(dev->regmap, TIM_CCR1, value); + case 1: + return regmap_write(dev->regmap, TIM_CCR2, value); + case 2: + return regmap_write(dev->regmap, TIM_CCR3, value); + case 3: + return regmap_write(dev->regmap, TIM_CCR4, value); + } + return -EINVAL; +} + +#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) +#define TIM_CCER_CC34E (TIM_CCER_CC3E | TIM_CCER_CC4E) + +/* + * Capture using PWM input mode: + * ___ ___ + * TI[1, 2, 3 or 4]: ........._| |________| + * ^0 ^1 ^2 + * . . . + * . . XXXXX + * . . XXXXX | + * . XXXXX . | + * XXXXX . . | + * COUNTER: ______XXXXX . . . |_XXX + * start^ . . . ^stop + * . . . . + * v v . v + * v + * CCR1/CCR3: tx..........t0...........t2 + * CCR2/CCR4: tx..............t1......... + * + * DMA burst transfer: | | + * v v + * DMA buffer: { t0, tx } { t2, t1 } + * DMA done: ^ + * + * 0: IC1/3 snapchot on rising edge: counter value -> CCR1/CCR3 + * + DMA transfer CCR[1/3] & CCR[2/4] values (t0, tx: doesn't care) + * 1: IC2/4 snapchot on falling edge: counter value -> CCR2/CCR4 + * 2: IC1/3 snapchot on rising edge: counter value -> CCR1/CCR3 + * + DMA transfer CCR[1/3] & CCR[2/4] values (t2, t1) + * + * DMA done, compute: + * - Period = t2 - t0 + * - Duty cycle = t1 - t0 + */ +static int stm32_pwm_raw_capture(struct stm32_pwm *priv, struct pwm_device *pwm, + unsigned long tmo_ms, u32 *raw_prd, + u32 *raw_dty) +{ + struct device *parent = priv->chip.dev->parent; + enum stm32_timers_dmas dma_id; + u32 ccen, ccr; + int ret; + + /* Ensure registers have been updated, enable counter and capture */ + regmap_set_bits(priv->regmap, TIM_EGR, TIM_EGR_UG); + regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + + /* Use cc1 or cc3 DMA resp for PWM input channels 1 & 2 or 3 & 4 */ + dma_id = pwm->hwpwm < 2 ? STM32_TIMERS_DMA_CH1 : STM32_TIMERS_DMA_CH3; + ccen = pwm->hwpwm < 2 ? TIM_CCER_CC12E : TIM_CCER_CC34E; + ccr = pwm->hwpwm < 2 ? TIM_CCR1 : TIM_CCR3; + regmap_set_bits(priv->regmap, TIM_CCER, ccen); + + /* + * Timer DMA burst mode. Request 2 registers, 2 bursts, to get both + * CCR1 & CCR2 (or CCR3 & CCR4) on each capture event. + * We'll get two capture snapchots: { CCR1, CCR2 }, { CCR1, CCR2 } + * or { CCR3, CCR4 }, { CCR3, CCR4 } + */ + ret = stm32_timers_dma_burst_read(parent, priv->capture, dma_id, ccr, 2, + 2, tmo_ms); + if (ret) + goto stop; + + /* Period: t2 - t0 (take care of counter overflow) */ + if (priv->capture[0] <= priv->capture[2]) + *raw_prd = priv->capture[2] - priv->capture[0]; + else + *raw_prd = priv->max_arr - priv->capture[0] + priv->capture[2]; + + /* Duty cycle capture requires at least two capture units */ + if (pwm->chip->npwm < 2) + *raw_dty = 0; + else if (priv->capture[0] <= priv->capture[3]) + *raw_dty = priv->capture[3] - priv->capture[0]; + else + *raw_dty = priv->max_arr - priv->capture[0] + priv->capture[3]; + + if (*raw_dty > *raw_prd) { + /* + * Race beetween PWM input and DMA: it may happen + * falling edge triggers new capture on TI2/4 before DMA + * had a chance to read CCR2/4. It means capture[1] + * contains period + duty_cycle. So, subtract period. + */ + *raw_dty -= *raw_prd; + } + +stop: + regmap_clear_bits(priv->regmap, TIM_CCER, ccen); + regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + + return ret; +} + +static int stm32_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_capture *result, unsigned long tmo_ms) +{ + struct stm32_pwm *priv = to_stm32_pwm_dev(chip); + unsigned long long prd, div, dty; + unsigned long rate; + unsigned int psc = 0, icpsc, scale; + u32 raw_prd = 0, raw_dty = 0; + int ret = 0; + + mutex_lock(&priv->lock); + + if (active_channels(priv)) { + ret = -EBUSY; + goto unlock; + } + + ret = clk_enable(priv->clk); + if (ret) { + dev_err(priv->chip.dev, "failed to enable counter clock\n"); + goto unlock; + } + + rate = clk_get_rate(priv->clk); + if (!rate) { + ret = -EINVAL; + goto clk_dis; + } + + /* prescaler: fit timeout window provided by upper layer */ + div = (unsigned long long)rate * (unsigned long long)tmo_ms; + do_div(div, MSEC_PER_SEC); + prd = div; + while ((div > priv->max_arr) && (psc < MAX_TIM_PSC)) { + psc++; + div = prd; + do_div(div, psc + 1); + } + regmap_write(priv->regmap, TIM_ARR, priv->max_arr); + regmap_write(priv->regmap, TIM_PSC, psc); + + /* Map TI1 or TI2 PWM input to IC1 & IC2 (or TI3/4 to IC3 & IC4) */ + regmap_update_bits(priv->regmap, + pwm->hwpwm < 2 ? TIM_CCMR1 : TIM_CCMR2, + TIM_CCMR_CC1S | TIM_CCMR_CC2S, pwm->hwpwm & 0x1 ? + TIM_CCMR_CC1S_TI2 | TIM_CCMR_CC2S_TI2 : + TIM_CCMR_CC1S_TI1 | TIM_CCMR_CC2S_TI1); + + /* Capture period on IC1/3 rising edge, duty cycle on IC2/4 falling. */ + regmap_update_bits(priv->regmap, TIM_CCER, pwm->hwpwm < 2 ? + TIM_CCER_CC12P : TIM_CCER_CC34P, pwm->hwpwm < 2 ? + TIM_CCER_CC2P : TIM_CCER_CC4P); + + ret = stm32_pwm_raw_capture(priv, pwm, tmo_ms, &raw_prd, &raw_dty); + if (ret) + goto stop; + + /* + * Got a capture. Try to improve accuracy at high rates: + * - decrease counter clock prescaler, scale up to max rate. + * - use input prescaler, capture once every /2 /4 or /8 edges. + */ + if (raw_prd) { + u32 max_arr = priv->max_arr - 0x1000; /* arbitrary margin */ + + scale = max_arr / min(max_arr, raw_prd); + } else { + scale = priv->max_arr; /* bellow resolution, use max scale */ + } + + if (psc && scale > 1) { + /* 2nd measure with new scale */ + psc /= scale; + regmap_write(priv->regmap, TIM_PSC, psc); + ret = stm32_pwm_raw_capture(priv, pwm, tmo_ms, &raw_prd, + &raw_dty); + if (ret) + goto stop; + } + + /* Compute intermediate period not to exceed timeout at low rates */ + prd = (unsigned long long)raw_prd * (psc + 1) * NSEC_PER_SEC; + do_div(prd, rate); + + for (icpsc = 0; icpsc < MAX_TIM_ICPSC ; icpsc++) { + /* input prescaler: also keep arbitrary margin */ + if (raw_prd >= (priv->max_arr - 0x1000) >> (icpsc + 1)) + break; + if (prd >= (tmo_ms * NSEC_PER_MSEC) >> (icpsc + 2)) + break; + } + + if (!icpsc) + goto done; + + /* Last chance to improve period accuracy, using input prescaler */ + regmap_update_bits(priv->regmap, + pwm->hwpwm < 2 ? TIM_CCMR1 : TIM_CCMR2, + TIM_CCMR_IC1PSC | TIM_CCMR_IC2PSC, + FIELD_PREP(TIM_CCMR_IC1PSC, icpsc) | + FIELD_PREP(TIM_CCMR_IC2PSC, icpsc)); + + ret = stm32_pwm_raw_capture(priv, pwm, tmo_ms, &raw_prd, &raw_dty); + if (ret) + goto stop; + + if (raw_dty >= (raw_prd >> icpsc)) { + /* + * We may fall here using input prescaler, when input + * capture starts on high side (before falling edge). + * Example with icpsc to capture on each 4 events: + * + * start 1st capture 2nd capture + * v v v + * ___ _____ _____ _____ _____ ____ + * TI1..4 |__| |__| |__| |__| |__| + * v v . . . . . v v + * icpsc1/3: . 0 . 1 . 2 . 3 . 0 + * icpsc2/4: 0 1 2 3 0 + * v v v v + * CCR1/3 ......t0..............................t2 + * CCR2/4 ..t1..............................t1'... + * . . . + * Capture0: .<----------------------------->. + * Capture1: .<-------------------------->. . + * . . . + * Period: .<------> . . + * Low side: .<>. + * + * Result: + * - Period = Capture0 / icpsc + * - Duty = Period - Low side = Period - (Capture0 - Capture1) + */ + raw_dty = (raw_prd >> icpsc) - (raw_prd - raw_dty); + } + +done: + prd = (unsigned long long)raw_prd * (psc + 1) * NSEC_PER_SEC; + result->period = DIV_ROUND_UP_ULL(prd, rate << icpsc); + dty = (unsigned long long)raw_dty * (psc + 1) * NSEC_PER_SEC; + result->duty_cycle = DIV_ROUND_UP_ULL(dty, rate); +stop: + regmap_write(priv->regmap, TIM_CCER, 0); + regmap_write(priv->regmap, pwm->hwpwm < 2 ? TIM_CCMR1 : TIM_CCMR2, 0); + regmap_write(priv->regmap, TIM_PSC, 0); +clk_dis: + clk_disable(priv->clk); +unlock: + mutex_unlock(&priv->lock); + + return ret; +} + +static int stm32_pwm_config(struct stm32_pwm *priv, 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); + + write_ccrx(priv, 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, 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, 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, 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 const struct pwm_ops stm32pwm_ops = { + .owner = THIS_MODULE, + .apply = stm32_pwm_apply_locked, + .capture = IS_ENABLED(CONFIG_DMA_ENGINE) ? stm32_pwm_capture : NULL, +}; + +static int stm32_pwm_set_breakinput(struct stm32_pwm *priv, + const struct stm32_breakinput *bi) +{ + u32 shift = TIM_BDTR_BKF_SHIFT(bi->index); + u32 bke = TIM_BDTR_BKE(bi->index); + u32 bkp = TIM_BDTR_BKP(bi->index); + u32 bkf = TIM_BDTR_BKF(bi->index); + u32 mask = bkf | bkp | bke; + u32 bdtr; + + bdtr = (bi->filter & TIM_BDTR_BKF_MASK) << shift | bke; + + if (bi->level) + bdtr |= bkp; + + regmap_update_bits(priv->regmap, TIM_BDTR, mask, bdtr); + + regmap_read(priv->regmap, TIM_BDTR, &bdtr); + + return (bdtr & bke) ? 0 : -EINVAL; +} + +static int stm32_pwm_apply_breakinputs(struct stm32_pwm *priv) +{ + unsigned int i; + int ret; + + for (i = 0; i < priv->num_breakinputs; i++) { + ret = stm32_pwm_set_breakinput(priv, &priv->breakinputs[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +static int stm32_pwm_probe_breakinputs(struct stm32_pwm *priv, + struct device_node *np) +{ + int nb, ret, array_size; + unsigned int i; + + nb = of_property_count_elems_of_size(np, "st,breakinput", + sizeof(struct stm32_breakinput)); + + /* + * Because "st,breakinput" parameter is optional do not make probe + * failed if it doesn't exist. + */ + if (nb <= 0) + return 0; + + if (nb > MAX_BREAKINPUT) + return -EINVAL; + + priv->num_breakinputs = nb; + array_size = nb * sizeof(struct stm32_breakinput) / sizeof(u32); + ret = of_property_read_u32_array(np, "st,breakinput", + (u32 *)priv->breakinputs, array_size); + if (ret) + return ret; + + for (i = 0; i < priv->num_breakinputs; i++) { + if (priv->breakinputs[i].index > 1 || + priv->breakinputs[i].level > 1 || + priv->breakinputs[i].filter > 15) + return -EINVAL; + } + + return stm32_pwm_apply_breakinputs(priv); +} + +static void stm32_pwm_detect_complementary(struct stm32_pwm *priv) +{ + u32 ccer; + + /* + * If complementary bit doesn't exist writing 1 will have no + * effect so we can detect it. + */ + regmap_set_bits(priv->regmap, TIM_CCER, TIM_CCER_CC1NE); + regmap_read(priv->regmap, TIM_CCER, &ccer); + regmap_clear_bits(priv->regmap, TIM_CCER, TIM_CCER_CC1NE); + + priv->have_complementary_output = (ccer != 0); +} + +static unsigned int stm32_pwm_detect_channels(struct stm32_pwm *priv, + unsigned int *num_enabled) +{ + u32 ccer, ccer_backup; + + /* + * If channels enable bits don't exist writing 1 will have no + * effect so we can detect and count them. + */ + regmap_read(priv->regmap, TIM_CCER, &ccer_backup); + regmap_set_bits(priv->regmap, TIM_CCER, TIM_CCER_CCXE); + regmap_read(priv->regmap, TIM_CCER, &ccer); + regmap_write(priv->regmap, TIM_CCER, ccer_backup); + + *num_enabled = hweight32(ccer_backup & TIM_CCER_CCXE); + + return hweight32(ccer & TIM_CCER_CCXE); +} + +static int stm32_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct stm32_timers *ddata = dev_get_drvdata(pdev->dev.parent); + struct stm32_pwm *priv; + unsigned int num_enabled; + unsigned int i; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->lock); + priv->regmap = ddata->regmap; + priv->clk = ddata->clk; + priv->max_arr = ddata->max_arr; + priv->chip.of_xlate = of_pwm_xlate_with_flags; + priv->chip.of_pwm_n_cells = 3; + + if (!priv->regmap || !priv->clk) + return -EINVAL; + + ret = stm32_pwm_probe_breakinputs(priv, np); + if (ret) + return ret; + + stm32_pwm_detect_complementary(priv); + + priv->chip.base = -1; + priv->chip.dev = dev; + priv->chip.ops = &stm32pwm_ops; + priv->chip.npwm = stm32_pwm_detect_channels(priv, &num_enabled); + + /* Initialize clock refcount to number of enabled PWM channels. */ + for (i = 0; i < num_enabled; i++) + clk_enable(priv->clk); + + ret = pwmchip_add(&priv->chip); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, priv); + + return 0; +} + +static int stm32_pwm_remove(struct platform_device *pdev) +{ + struct stm32_pwm *priv = platform_get_drvdata(pdev); + unsigned int i; + + for (i = 0; i < priv->chip.npwm; i++) + pwm_disable(&priv->chip.pwms[i]); + + pwmchip_remove(&priv->chip); + + return 0; +} + +static int __maybe_unused stm32_pwm_suspend(struct device *dev) +{ + struct stm32_pwm *priv = dev_get_drvdata(dev); + unsigned int i; + u32 ccer, mask; + + /* Look for active channels */ + ccer = active_channels(priv); + + for (i = 0; i < priv->chip.npwm; i++) { + mask = TIM_CCER_CC1E << (i * 4); + if (ccer & mask) { + dev_err(dev, "PWM %u still in use by consumer %s\n", + i, priv->chip.pwms[i].label); + return -EBUSY; + } + } + + return pinctrl_pm_select_sleep_state(dev); +} + +static int __maybe_unused stm32_pwm_resume(struct device *dev) +{ + struct stm32_pwm *priv = dev_get_drvdata(dev); + int ret; + + ret = pinctrl_pm_select_default_state(dev); + if (ret) + return ret; + + /* restore breakinput registers that may have been lost in low power */ + return stm32_pwm_apply_breakinputs(priv); +} + +static SIMPLE_DEV_PM_OPS(stm32_pwm_pm_ops, stm32_pwm_suspend, stm32_pwm_resume); + +static const struct of_device_id stm32_pwm_of_match[] = { + { .compatible = "st,stm32-pwm", }, + { /* end node */ }, +}; +MODULE_DEVICE_TABLE(of, stm32_pwm_of_match); + +static struct platform_driver stm32_pwm_driver = { + .probe = stm32_pwm_probe, + .remove = stm32_pwm_remove, + .driver = { + .name = "stm32-pwm", + .of_match_table = stm32_pwm_of_match, + .pm = &stm32_pwm_pm_ops, + }, +}; +module_platform_driver(stm32_pwm_driver); + +MODULE_ALIAS("platform:stm32-pwm"); +MODULE_DESCRIPTION("STMicroelectronics STM32 PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-stmpe.c b/drivers/pwm/pwm-stmpe.c new file mode 100644 index 000000000..be5f6d735 --- /dev/null +++ b/drivers/pwm/pwm-stmpe.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2016 Linaro Ltd. + * + * Author: Linus Walleij <linus.walleij@linaro.org> + */ + +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/mfd/stmpe.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +#define STMPE24XX_PWMCS 0x30 +#define PWMCS_EN_PWM0 BIT(0) +#define PWMCS_EN_PWM1 BIT(1) +#define PWMCS_EN_PWM2 BIT(2) +#define STMPE24XX_PWMIC0 0x38 +#define STMPE24XX_PWMIC1 0x39 +#define STMPE24XX_PWMIC2 0x3a + +#define STMPE_PWM_24XX_PINBASE 21 + +struct stmpe_pwm { + struct stmpe *stmpe; + struct pwm_chip chip; + u8 last_duty; +}; + +static inline struct stmpe_pwm *to_stmpe_pwm(struct pwm_chip *chip) +{ + return container_of(chip, struct stmpe_pwm, chip); +} + +static int stmpe_24xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip); + u8 value; + int ret; + + ret = stmpe_reg_read(stmpe_pwm->stmpe, STMPE24XX_PWMCS); + if (ret < 0) { + dev_err(chip->dev, "error reading PWM#%u control\n", + pwm->hwpwm); + return ret; + } + + value = ret | BIT(pwm->hwpwm); + + ret = stmpe_reg_write(stmpe_pwm->stmpe, STMPE24XX_PWMCS, value); + if (ret) { + dev_err(chip->dev, "error writing PWM#%u control\n", + pwm->hwpwm); + return ret; + } + + return 0; +} + +static void stmpe_24xx_pwm_disable(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip); + u8 value; + int ret; + + ret = stmpe_reg_read(stmpe_pwm->stmpe, STMPE24XX_PWMCS); + if (ret < 0) { + dev_err(chip->dev, "error reading PWM#%u control\n", + pwm->hwpwm); + return; + } + + value = ret & ~BIT(pwm->hwpwm); + + ret = stmpe_reg_write(stmpe_pwm->stmpe, STMPE24XX_PWMCS, value); + if (ret) { + dev_err(chip->dev, "error writing PWM#%u control\n", + pwm->hwpwm); + return; + } +} + +/* STMPE 24xx PWM instructions */ +#define SMAX 0x007f +#define SMIN 0x00ff +#define GTS 0x0000 +#define LOAD BIT(14) /* Only available on 2403 */ +#define RAMPUP 0x0000 +#define RAMPDOWN BIT(7) +#define PRESCALE_512 BIT(14) +#define STEPTIME_1 BIT(8) +#define BRANCH (BIT(15) | BIT(13)) + +static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip); + unsigned int i, pin; + u16 program[3] = { + SMAX, + GTS, + GTS, + }; + u8 offset; + int ret; + + /* Make sure we are disabled */ + if (pwm_is_enabled(pwm)) { + stmpe_24xx_pwm_disable(chip, pwm); + } else { + /* Connect the PWM to the pin */ + pin = pwm->hwpwm; + + /* On STMPE2401 and 2403 pins 21,22,23 are used */ + if (stmpe_pwm->stmpe->partnum == STMPE2401 || + stmpe_pwm->stmpe->partnum == STMPE2403) + pin += STMPE_PWM_24XX_PINBASE; + + ret = stmpe_set_altfunc(stmpe_pwm->stmpe, BIT(pin), + STMPE_BLOCK_PWM); + if (ret) { + dev_err(chip->dev, "unable to connect PWM#%u to pin\n", + pwm->hwpwm); + return ret; + } + } + + /* STMPE24XX */ + switch (pwm->hwpwm) { + case 0: + offset = STMPE24XX_PWMIC0; + break; + + case 1: + offset = STMPE24XX_PWMIC1; + break; + + case 2: + offset = STMPE24XX_PWMIC2; + break; + + default: + /* Should not happen as npwm is 3 */ + return -ENODEV; + } + + dev_dbg(chip->dev, "PWM#%u: config duty %d ns, period %d ns\n", + pwm->hwpwm, duty_ns, period_ns); + + if (duty_ns == 0) { + if (stmpe_pwm->stmpe->partnum == STMPE2401) + program[0] = SMAX; /* off all the time */ + + if (stmpe_pwm->stmpe->partnum == STMPE2403) + program[0] = LOAD | 0xff; /* LOAD 0xff */ + + stmpe_pwm->last_duty = 0x00; + } else if (duty_ns == period_ns) { + if (stmpe_pwm->stmpe->partnum == STMPE2401) + program[0] = SMIN; /* on all the time */ + + if (stmpe_pwm->stmpe->partnum == STMPE2403) + program[0] = LOAD | 0x00; /* LOAD 0x00 */ + + stmpe_pwm->last_duty = 0xff; + } else { + u8 value, last = stmpe_pwm->last_duty; + unsigned long duty; + + /* + * Counter goes from 0x00 to 0xff repeatedly at 32768 Hz, + * (means a period of 30517 ns) then this is compared to the + * counter from the ramp, if this is >= PWM counter the output + * is high. With LOAD we can define how much of the cycle it + * is on. + * + * Prescale = 0 -> 2 kHz -> T = 1/f = 488281.25 ns + */ + + /* Scale to 0..0xff */ + duty = duty_ns * 256; + duty = DIV_ROUND_CLOSEST(duty, period_ns); + value = duty; + + if (value == last) { + /* Run the old program */ + if (pwm_is_enabled(pwm)) + stmpe_24xx_pwm_enable(chip, pwm); + + return 0; + } else if (stmpe_pwm->stmpe->partnum == STMPE2403) { + /* STMPE2403 can simply set the right PWM value */ + program[0] = LOAD | value; + program[1] = 0x0000; + } else if (stmpe_pwm->stmpe->partnum == STMPE2401) { + /* STMPE2401 need a complex program */ + u16 incdec = 0x0000; + + if (last < value) + /* Count up */ + incdec = RAMPUP | (value - last); + else + /* Count down */ + incdec = RAMPDOWN | (last - value); + + /* Step to desired value, smoothly */ + program[0] = PRESCALE_512 | STEPTIME_1 | incdec; + + /* Loop eternally to 0x00 */ + program[1] = BRANCH; + } + + dev_dbg(chip->dev, + "PWM#%u: value = %02x, last_duty = %02x, program=%04x,%04x,%04x\n", + pwm->hwpwm, value, last, program[0], program[1], + program[2]); + stmpe_pwm->last_duty = value; + } + + /* + * We can write programs of up to 64 16-bit words into this channel. + */ + for (i = 0; i < ARRAY_SIZE(program); i++) { + u8 value; + + value = (program[i] >> 8) & 0xff; + + ret = stmpe_reg_write(stmpe_pwm->stmpe, offset, value); + if (ret) { + dev_err(chip->dev, "error writing register %02x: %d\n", + offset, ret); + return ret; + } + + value = program[i] & 0xff; + + ret = stmpe_reg_write(stmpe_pwm->stmpe, offset, value); + if (ret) { + dev_err(chip->dev, "error writing register %02x: %d\n", + offset, ret); + return ret; + } + } + + /* If we were enabled, re-enable this PWM */ + if (pwm_is_enabled(pwm)) + stmpe_24xx_pwm_enable(chip, pwm); + + /* Sleep for 200ms so we're sure it will take effect */ + msleep(200); + + dev_dbg(chip->dev, "programmed PWM#%u, %u bytes\n", pwm->hwpwm, i); + + return 0; +} + +static const struct pwm_ops stmpe_24xx_pwm_ops = { + .config = stmpe_24xx_pwm_config, + .enable = stmpe_24xx_pwm_enable, + .disable = stmpe_24xx_pwm_disable, + .owner = THIS_MODULE, +}; + +static int __init stmpe_pwm_probe(struct platform_device *pdev) +{ + struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent); + struct stmpe_pwm *pwm; + int ret; + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) + return -ENOMEM; + + pwm->stmpe = stmpe; + pwm->chip.dev = &pdev->dev; + pwm->chip.base = -1; + + if (stmpe->partnum == STMPE2401 || stmpe->partnum == STMPE2403) { + pwm->chip.ops = &stmpe_24xx_pwm_ops; + pwm->chip.npwm = 3; + } else { + if (stmpe->partnum == STMPE1601) + dev_err(&pdev->dev, "STMPE1601 not yet supported\n"); + else + dev_err(&pdev->dev, "Unknown STMPE PWM\n"); + + return -ENODEV; + } + + ret = stmpe_enable(stmpe, STMPE_BLOCK_PWM); + if (ret) + return ret; + + ret = pwmchip_add(&pwm->chip); + if (ret) { + stmpe_disable(stmpe, STMPE_BLOCK_PWM); + return ret; + } + + platform_set_drvdata(pdev, pwm); + + return 0; +} + +static struct platform_driver stmpe_pwm_driver = { + .driver = { + .name = "stmpe-pwm", + }, +}; +builtin_platform_driver_probe(stmpe_pwm_driver, stmpe_pwm_probe); diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c new file mode 100644 index 000000000..482d5b9ce --- /dev/null +++ b/drivers/pwm/pwm-sun4i.c @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Allwinner sun4i Pulse Width Modulation Controller + * + * Copyright (C) 2014 Alexandre Belloni <alexandre.belloni@free-electrons.com> + * + * Limitations: + * - When outputing the source clock directly, the PWM logic will be bypassed + * and the currently running period is not guaranteed to be completed + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/reset.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/time.h> + +#define PWM_CTRL_REG 0x0 + +#define PWM_CH_PRD_BASE 0x4 +#define PWM_CH_PRD_OFFSET 0x4 +#define PWM_CH_PRD(ch) (PWM_CH_PRD_BASE + PWM_CH_PRD_OFFSET * (ch)) + +#define PWMCH_OFFSET 15 +#define PWM_PRESCAL_MASK GENMASK(3, 0) +#define PWM_PRESCAL_OFF 0 +#define PWM_EN BIT(4) +#define PWM_ACT_STATE BIT(5) +#define PWM_CLK_GATING BIT(6) +#define PWM_MODE BIT(7) +#define PWM_PULSE BIT(8) +#define PWM_BYPASS BIT(9) + +#define PWM_RDY_BASE 28 +#define PWM_RDY_OFFSET 1 +#define PWM_RDY(ch) BIT(PWM_RDY_BASE + PWM_RDY_OFFSET * (ch)) + +#define PWM_PRD(prd) (((prd) - 1) << 16) +#define PWM_PRD_MASK GENMASK(15, 0) + +#define PWM_DTY_MASK GENMASK(15, 0) + +#define PWM_REG_PRD(reg) ((((reg) >> 16) & PWM_PRD_MASK) + 1) +#define PWM_REG_DTY(reg) ((reg) & PWM_DTY_MASK) +#define PWM_REG_PRESCAL(reg, chan) (((reg) >> ((chan) * PWMCH_OFFSET)) & PWM_PRESCAL_MASK) + +#define BIT_CH(bit, chan) ((bit) << ((chan) * PWMCH_OFFSET)) + +static const u32 prescaler_table[] = { + 120, + 180, + 240, + 360, + 480, + 0, + 0, + 0, + 12000, + 24000, + 36000, + 48000, + 72000, + 0, + 0, + 0, /* Actually 1 but tested separately */ +}; + +struct sun4i_pwm_data { + bool has_prescaler_bypass; + bool has_direct_mod_clk_output; + unsigned int npwm; +}; + +struct sun4i_pwm_chip { + struct pwm_chip chip; + struct clk *bus_clk; + struct clk *clk; + struct reset_control *rst; + void __iomem *base; + spinlock_t ctrl_lock; + const struct sun4i_pwm_data *data; + unsigned long next_period[2]; +}; + +static inline struct sun4i_pwm_chip *to_sun4i_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct sun4i_pwm_chip, chip); +} + +static inline u32 sun4i_pwm_readl(struct sun4i_pwm_chip *chip, + unsigned long offset) +{ + return readl(chip->base + offset); +} + +static inline void sun4i_pwm_writel(struct sun4i_pwm_chip *chip, + u32 val, unsigned long offset) +{ + writel(val, chip->base + offset); +} + +static void sun4i_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip); + u64 clk_rate, tmp; + u32 val; + unsigned int prescaler; + + clk_rate = clk_get_rate(sun4i_pwm->clk); + + val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); + + /* + * PWM chapter in H6 manual has a diagram which explains that if bypass + * bit is set, no other setting has any meaning. Even more, experiment + * proved that also enable bit is ignored in this case. + */ + if ((val & BIT_CH(PWM_BYPASS, pwm->hwpwm)) && + sun4i_pwm->data->has_direct_mod_clk_output) { + state->period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, clk_rate); + state->duty_cycle = DIV_ROUND_UP_ULL(state->period, 2); + state->polarity = PWM_POLARITY_NORMAL; + state->enabled = true; + return; + } + + if ((PWM_REG_PRESCAL(val, pwm->hwpwm) == PWM_PRESCAL_MASK) && + sun4i_pwm->data->has_prescaler_bypass) + prescaler = 1; + else + prescaler = prescaler_table[PWM_REG_PRESCAL(val, pwm->hwpwm)]; + + if (prescaler == 0) + return; + + if (val & BIT_CH(PWM_ACT_STATE, pwm->hwpwm)) + state->polarity = PWM_POLARITY_NORMAL; + else + state->polarity = PWM_POLARITY_INVERSED; + + if ((val & BIT_CH(PWM_CLK_GATING | PWM_EN, pwm->hwpwm)) == + BIT_CH(PWM_CLK_GATING | PWM_EN, pwm->hwpwm)) + state->enabled = true; + else + state->enabled = false; + + val = sun4i_pwm_readl(sun4i_pwm, PWM_CH_PRD(pwm->hwpwm)); + + tmp = (u64)prescaler * NSEC_PER_SEC * PWM_REG_DTY(val); + state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); + + tmp = (u64)prescaler * NSEC_PER_SEC * PWM_REG_PRD(val); + state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); +} + +static int sun4i_pwm_calculate(struct sun4i_pwm_chip *sun4i_pwm, + const struct pwm_state *state, + u32 *dty, u32 *prd, unsigned int *prsclr, + bool *bypass) +{ + u64 clk_rate, div = 0; + unsigned int prescaler = 0; + + clk_rate = clk_get_rate(sun4i_pwm->clk); + + *bypass = sun4i_pwm->data->has_direct_mod_clk_output && + state->enabled && + (state->period * clk_rate >= NSEC_PER_SEC) && + (state->period * clk_rate < 2 * NSEC_PER_SEC) && + (state->duty_cycle * clk_rate * 2 >= NSEC_PER_SEC); + + /* Skip calculation of other parameters if we bypass them */ + if (*bypass) + return 0; + + if (sun4i_pwm->data->has_prescaler_bypass) { + /* First, test without any prescaler when available */ + prescaler = PWM_PRESCAL_MASK; + /* + * When not using any prescaler, the clock period in nanoseconds + * is not an integer so round it half up instead of + * truncating to get less surprising values. + */ + div = clk_rate * state->period + NSEC_PER_SEC / 2; + do_div(div, NSEC_PER_SEC); + if (div - 1 > PWM_PRD_MASK) + prescaler = 0; + } + + if (prescaler == 0) { + /* Go up from the first divider */ + for (prescaler = 0; prescaler < PWM_PRESCAL_MASK; prescaler++) { + unsigned int pval = prescaler_table[prescaler]; + + if (!pval) + continue; + + div = clk_rate; + do_div(div, pval); + div = div * state->period; + do_div(div, NSEC_PER_SEC); + if (div - 1 <= PWM_PRD_MASK) + break; + } + + if (div - 1 > PWM_PRD_MASK) + return -EINVAL; + } + + *prd = div; + div *= state->duty_cycle; + do_div(div, state->period); + *dty = div; + *prsclr = prescaler; + + return 0; +} + +static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip); + struct pwm_state cstate; + u32 ctrl, duty = 0, period = 0, val; + int ret; + unsigned int delay_us, prescaler = 0; + unsigned long now; + bool bypass; + + pwm_get_state(pwm, &cstate); + + if (!cstate.enabled) { + ret = clk_prepare_enable(sun4i_pwm->clk); + if (ret) { + dev_err(chip->dev, "failed to enable PWM clock\n"); + return ret; + } + } + + ret = sun4i_pwm_calculate(sun4i_pwm, state, &duty, &period, &prescaler, + &bypass); + if (ret) { + dev_err(chip->dev, "period exceeds the maximum value\n"); + if (!cstate.enabled) + clk_disable_unprepare(sun4i_pwm->clk); + return ret; + } + + spin_lock(&sun4i_pwm->ctrl_lock); + ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); + + if (sun4i_pwm->data->has_direct_mod_clk_output) { + if (bypass) { + ctrl |= BIT_CH(PWM_BYPASS, pwm->hwpwm); + /* We can skip other parameter */ + sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG); + spin_unlock(&sun4i_pwm->ctrl_lock); + return 0; + } + + ctrl &= ~BIT_CH(PWM_BYPASS, pwm->hwpwm); + } + + if (PWM_REG_PRESCAL(ctrl, pwm->hwpwm) != prescaler) { + /* Prescaler changed, the clock has to be gated */ + ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm); + sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG); + + ctrl &= ~BIT_CH(PWM_PRESCAL_MASK, pwm->hwpwm); + ctrl |= BIT_CH(prescaler, pwm->hwpwm); + } + + val = (duty & PWM_DTY_MASK) | PWM_PRD(period); + sun4i_pwm_writel(sun4i_pwm, val, PWM_CH_PRD(pwm->hwpwm)); + sun4i_pwm->next_period[pwm->hwpwm] = jiffies + + nsecs_to_jiffies(cstate.period + 1000); + + if (state->polarity != PWM_POLARITY_NORMAL) + ctrl &= ~BIT_CH(PWM_ACT_STATE, pwm->hwpwm); + else + ctrl |= BIT_CH(PWM_ACT_STATE, pwm->hwpwm); + + ctrl |= BIT_CH(PWM_CLK_GATING, pwm->hwpwm); + + if (state->enabled) + ctrl |= BIT_CH(PWM_EN, pwm->hwpwm); + + sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG); + + spin_unlock(&sun4i_pwm->ctrl_lock); + + if (state->enabled) + return 0; + + /* We need a full period to elapse before disabling the channel. */ + now = jiffies; + if (time_before(now, sun4i_pwm->next_period[pwm->hwpwm])) { + delay_us = jiffies_to_usecs(sun4i_pwm->next_period[pwm->hwpwm] - + now); + if ((delay_us / 500) > MAX_UDELAY_MS) + msleep(delay_us / 1000 + 1); + else + usleep_range(delay_us, delay_us * 2); + } + + spin_lock(&sun4i_pwm->ctrl_lock); + ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); + ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm); + ctrl &= ~BIT_CH(PWM_EN, pwm->hwpwm); + sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG); + spin_unlock(&sun4i_pwm->ctrl_lock); + + clk_disable_unprepare(sun4i_pwm->clk); + + return 0; +} + +static const struct pwm_ops sun4i_pwm_ops = { + .apply = sun4i_pwm_apply, + .get_state = sun4i_pwm_get_state, + .owner = THIS_MODULE, +}; + +static const struct sun4i_pwm_data sun4i_pwm_dual_nobypass = { + .has_prescaler_bypass = false, + .npwm = 2, +}; + +static const struct sun4i_pwm_data sun4i_pwm_dual_bypass = { + .has_prescaler_bypass = true, + .npwm = 2, +}; + +static const struct sun4i_pwm_data sun4i_pwm_single_bypass = { + .has_prescaler_bypass = true, + .npwm = 1, +}; + +static const struct sun4i_pwm_data sun50i_a64_pwm_data = { + .has_prescaler_bypass = true, + .has_direct_mod_clk_output = true, + .npwm = 1, +}; + +static const struct sun4i_pwm_data sun50i_h6_pwm_data = { + .has_prescaler_bypass = true, + .has_direct_mod_clk_output = true, + .npwm = 2, +}; + +static const struct of_device_id sun4i_pwm_dt_ids[] = { + { + .compatible = "allwinner,sun4i-a10-pwm", + .data = &sun4i_pwm_dual_nobypass, + }, { + .compatible = "allwinner,sun5i-a10s-pwm", + .data = &sun4i_pwm_dual_bypass, + }, { + .compatible = "allwinner,sun5i-a13-pwm", + .data = &sun4i_pwm_single_bypass, + }, { + .compatible = "allwinner,sun7i-a20-pwm", + .data = &sun4i_pwm_dual_bypass, + }, { + .compatible = "allwinner,sun8i-h3-pwm", + .data = &sun4i_pwm_single_bypass, + }, { + .compatible = "allwinner,sun50i-a64-pwm", + .data = &sun50i_a64_pwm_data, + }, { + .compatible = "allwinner,sun50i-h6-pwm", + .data = &sun50i_h6_pwm_data, + }, { + /* sentinel */ + }, +}; +MODULE_DEVICE_TABLE(of, sun4i_pwm_dt_ids); + +static int sun4i_pwm_probe(struct platform_device *pdev) +{ + struct sun4i_pwm_chip *pwm; + struct resource *res; + int ret; + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) + return -ENOMEM; + + pwm->data = of_device_get_match_data(&pdev->dev); + if (!pwm->data) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pwm->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pwm->base)) + return PTR_ERR(pwm->base); + + /* + * All hardware variants need a source clock that is divided and + * then feeds the counter that defines the output wave form. In the + * device tree this clock is either unnamed or called "mod". + * Some variants (e.g. H6) need another clock to access the + * hardware registers; this is called "bus". + * So we request "mod" first (and ignore the corner case that a + * parent provides a "mod" clock while the right one would be the + * unnamed one of the PWM device) and if this is not found we fall + * back to the first clock of the PWM. + */ + pwm->clk = devm_clk_get_optional(&pdev->dev, "mod"); + if (IS_ERR(pwm->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pwm->clk), + "get mod clock failed\n"); + + if (!pwm->clk) { + pwm->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pwm->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pwm->clk), + "get unnamed clock failed\n"); + } + + pwm->bus_clk = devm_clk_get_optional(&pdev->dev, "bus"); + if (IS_ERR(pwm->bus_clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pwm->bus_clk), + "get bus clock failed\n"); + + pwm->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL); + if (IS_ERR(pwm->rst)) + return dev_err_probe(&pdev->dev, PTR_ERR(pwm->rst), + "get reset failed\n"); + + /* Deassert reset */ + ret = reset_control_deassert(pwm->rst); + if (ret) { + dev_err(&pdev->dev, "cannot deassert reset control: %pe\n", + ERR_PTR(ret)); + return ret; + } + + /* + * We're keeping the bus clock on for the sake of simplicity. + * Actually it only needs to be on for hardware register accesses. + */ + ret = clk_prepare_enable(pwm->bus_clk); + if (ret) { + dev_err(&pdev->dev, "cannot prepare and enable bus_clk %pe\n", + ERR_PTR(ret)); + goto err_bus; + } + + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &sun4i_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = pwm->data->npwm; + pwm->chip.of_xlate = of_pwm_xlate_with_flags; + pwm->chip.of_pwm_n_cells = 3; + + spin_lock_init(&pwm->ctrl_lock); + + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); + goto err_pwm_add; + } + + platform_set_drvdata(pdev, pwm); + + return 0; + +err_pwm_add: + clk_disable_unprepare(pwm->bus_clk); +err_bus: + reset_control_assert(pwm->rst); + + return ret; +} + +static int sun4i_pwm_remove(struct platform_device *pdev) +{ + struct sun4i_pwm_chip *pwm = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&pwm->chip); + if (ret) + return ret; + + clk_disable_unprepare(pwm->bus_clk); + reset_control_assert(pwm->rst); + + return 0; +} + +static struct platform_driver sun4i_pwm_driver = { + .driver = { + .name = "sun4i-pwm", + .of_match_table = sun4i_pwm_dt_ids, + }, + .probe = sun4i_pwm_probe, + .remove = sun4i_pwm_remove, +}; +module_platform_driver(sun4i_pwm_driver); + +MODULE_ALIAS("platform:sun4i-pwm"); +MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner sun4i PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c new file mode 100644 index 000000000..f3528c56e --- /dev/null +++ b/drivers/pwm/pwm-tegra.c @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * drivers/pwm/pwm-tegra.c + * + * Tegra pulse-width-modulation controller driver + * + * Copyright (c) 2010-2020, NVIDIA Corporation. + * Based on arch/arm/plat-mxc/pwm.c by Sascha Hauer <s.hauer@pengutronix.de> + * + * Overview of Tegra Pulse Width Modulator Register: + * 1. 13-bit: Frequency division (SCALE) + * 2. 8-bit : Pulse division (DUTY) + * 3. 1-bit : Enable bit + * + * The PWM clock frequency is divided by 256 before subdividing it based + * on the programmable frequency division value to generate the required + * frequency for PWM output. The maximum output frequency that can be + * achieved is (max rate of source clock) / 256. + * e.g. if source clock rate is 408 MHz, maximum output frequency can be: + * 408 MHz/256 = 1.6 MHz. + * This 1.6 MHz frequency can further be divided using SCALE value in PWM. + * + * PWM pulse width: 8 bits are usable [23:16] for varying pulse width. + * To achieve 100% duty cycle, program Bit [24] of this register to + * 1’b1. In which case the other bits [23:16] are set to don't care. + * + * Limitations: + * - When PWM is disabled, the output is driven to inactive. + * - It does not allow the current PWM period to complete and + * stops abruptly. + * + * - If the register is reconfigured while PWM is running, + * it does not complete the currently running period. + * + * - If the user input duty is beyond acceptible limits, + * -EINVAL is returned. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pwm.h> +#include <linux/platform_device.h> +#include <linux/pinctrl/consumer.h> +#include <linux/slab.h> +#include <linux/reset.h> + +#define PWM_ENABLE (1 << 31) +#define PWM_DUTY_WIDTH 8 +#define PWM_DUTY_SHIFT 16 +#define PWM_SCALE_WIDTH 13 +#define PWM_SCALE_SHIFT 0 + +struct tegra_pwm_soc { + unsigned int num_channels; + + /* Maximum IP frequency for given SoCs */ + unsigned long max_frequency; +}; + +struct tegra_pwm_chip { + struct pwm_chip chip; + struct device *dev; + + struct clk *clk; + struct reset_control*rst; + + unsigned long clk_rate; + unsigned long min_period_ns; + + void __iomem *regs; + + const struct tegra_pwm_soc *soc; +}; + +static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct tegra_pwm_chip, chip); +} + +static inline u32 pwm_readl(struct tegra_pwm_chip *chip, unsigned int num) +{ + return readl(chip->regs + (num << 4)); +} + +static inline void pwm_writel(struct tegra_pwm_chip *chip, unsigned int num, + unsigned long val) +{ + writel(val, chip->regs + (num << 4)); +} + +static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip); + unsigned long long c = duty_ns, hz; + unsigned long rate, required_clk_rate; + u32 val = 0; + int err; + + /* + * Convert from duty_ns / period_ns to a fixed number of duty ticks + * per (1 << PWM_DUTY_WIDTH) cycles and make sure to round to the + * nearest integer during division. + */ + c *= (1 << PWM_DUTY_WIDTH); + c = DIV_ROUND_CLOSEST_ULL(c, period_ns); + + val = (u32)c << PWM_DUTY_SHIFT; + + /* + * min period = max clock limit >> PWM_DUTY_WIDTH + */ + if (period_ns < pc->min_period_ns) + return -EINVAL; + + /* + * Compute the prescaler value for which (1 << PWM_DUTY_WIDTH) + * cycles at the PWM clock rate will take period_ns nanoseconds. + * + * num_channels: If single instance of PWM controller has multiple + * channels (e.g. Tegra210 or older) then it is not possible to + * configure separate clock rates to each of the channels, in such + * case the value stored during probe will be referred. + * + * If every PWM controller instance has one channel respectively, i.e. + * nums_channels == 1 then only the clock rate can be modified + * dynamically (e.g. Tegra186 or Tegra194). + */ + if (pc->soc->num_channels == 1) { + /* + * Rate is multiplied with 2^PWM_DUTY_WIDTH so that it matches + * with the maximum possible rate that the controller can + * provide. Any further lower value can be derived by setting + * PFM bits[0:12]. + * + * required_clk_rate is a reference rate for source clock and + * it is derived based on user requested period. By setting the + * source clock rate as required_clk_rate, PWM controller will + * be able to configure the requested period. + */ + required_clk_rate = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC << PWM_DUTY_WIDTH, + period_ns); + + err = clk_set_rate(pc->clk, required_clk_rate); + if (err < 0) + return -EINVAL; + + /* Store the new rate for further references */ + pc->clk_rate = clk_get_rate(pc->clk); + } + + rate = pc->clk_rate >> PWM_DUTY_WIDTH; + + /* Consider precision in PWM_SCALE_WIDTH rate calculation */ + hz = DIV_ROUND_CLOSEST_ULL(100ULL * NSEC_PER_SEC, period_ns); + rate = DIV_ROUND_CLOSEST_ULL(100ULL * rate, hz); + + /* + * Since the actual PWM divider is the register's frequency divider + * field plus 1, we need to decrement to get the correct value to + * write to the register. + */ + if (rate > 0) + rate--; + + /* + * Make sure that the rate will fit in the register's frequency + * divider field. + */ + if (rate >> PWM_SCALE_WIDTH) + return -EINVAL; + + val |= rate << PWM_SCALE_SHIFT; + + /* + * If the PWM channel is disabled, make sure to turn on the clock + * before writing the register. Otherwise, keep it enabled. + */ + if (!pwm_is_enabled(pwm)) { + err = clk_prepare_enable(pc->clk); + if (err < 0) + return err; + } else + val |= PWM_ENABLE; + + pwm_writel(pc, pwm->hwpwm, val); + + /* + * If the PWM is not enabled, turn the clock off again to save power. + */ + if (!pwm_is_enabled(pwm)) + clk_disable_unprepare(pc->clk); + + return 0; +} + +static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip); + int rc = 0; + u32 val; + + rc = clk_prepare_enable(pc->clk); + if (rc < 0) + return rc; + + val = pwm_readl(pc, pwm->hwpwm); + val |= PWM_ENABLE; + pwm_writel(pc, pwm->hwpwm, val); + + return 0; +} + +static void tegra_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip); + u32 val; + + val = pwm_readl(pc, pwm->hwpwm); + val &= ~PWM_ENABLE; + pwm_writel(pc, pwm->hwpwm, val); + + clk_disable_unprepare(pc->clk); +} + +static const struct pwm_ops tegra_pwm_ops = { + .config = tegra_pwm_config, + .enable = tegra_pwm_enable, + .disable = tegra_pwm_disable, + .owner = THIS_MODULE, +}; + +static int tegra_pwm_probe(struct platform_device *pdev) +{ + struct tegra_pwm_chip *pwm; + struct resource *r; + int ret; + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) + return -ENOMEM; + + pwm->soc = of_device_get_match_data(&pdev->dev); + pwm->dev = &pdev->dev; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pwm->regs = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(pwm->regs)) + return PTR_ERR(pwm->regs); + + platform_set_drvdata(pdev, pwm); + + pwm->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pwm->clk)) + return PTR_ERR(pwm->clk); + + /* Set maximum frequency of the IP */ + ret = clk_set_rate(pwm->clk, pwm->soc->max_frequency); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to set max frequency: %d\n", ret); + return ret; + } + + /* + * The requested and configured frequency may differ due to + * clock register resolutions. Get the configured frequency + * so that PWM period can be calculated more accurately. + */ + pwm->clk_rate = clk_get_rate(pwm->clk); + + /* Set minimum limit of PWM period for the IP */ + pwm->min_period_ns = + (NSEC_PER_SEC / (pwm->soc->max_frequency >> PWM_DUTY_WIDTH)) + 1; + + pwm->rst = devm_reset_control_get_exclusive(&pdev->dev, "pwm"); + if (IS_ERR(pwm->rst)) { + ret = PTR_ERR(pwm->rst); + dev_err(&pdev->dev, "Reset control is not found: %d\n", ret); + return ret; + } + + reset_control_deassert(pwm->rst); + + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &tegra_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = pwm->soc->num_channels; + + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + reset_control_assert(pwm->rst); + return ret; + } + + return 0; +} + +static int tegra_pwm_remove(struct platform_device *pdev) +{ + struct tegra_pwm_chip *pc = platform_get_drvdata(pdev); + int err; + + if (WARN_ON(!pc)) + return -ENODEV; + + err = clk_prepare_enable(pc->clk); + if (err < 0) + return err; + + reset_control_assert(pc->rst); + clk_disable_unprepare(pc->clk); + + return pwmchip_remove(&pc->chip); +} + +#ifdef CONFIG_PM_SLEEP +static int tegra_pwm_suspend(struct device *dev) +{ + return pinctrl_pm_select_sleep_state(dev); +} + +static int tegra_pwm_resume(struct device *dev) +{ + return pinctrl_pm_select_default_state(dev); +} +#endif + +static const struct tegra_pwm_soc tegra20_pwm_soc = { + .num_channels = 4, + .max_frequency = 48000000UL, +}; + +static const struct tegra_pwm_soc tegra186_pwm_soc = { + .num_channels = 1, + .max_frequency = 102000000UL, +}; + +static const struct tegra_pwm_soc tegra194_pwm_soc = { + .num_channels = 1, + .max_frequency = 408000000UL, +}; + +static const struct of_device_id tegra_pwm_of_match[] = { + { .compatible = "nvidia,tegra20-pwm", .data = &tegra20_pwm_soc }, + { .compatible = "nvidia,tegra186-pwm", .data = &tegra186_pwm_soc }, + { .compatible = "nvidia,tegra194-pwm", .data = &tegra194_pwm_soc }, + { } +}; +MODULE_DEVICE_TABLE(of, tegra_pwm_of_match); + +static const struct dev_pm_ops tegra_pwm_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tegra_pwm_suspend, tegra_pwm_resume) +}; + +static struct platform_driver tegra_pwm_driver = { + .driver = { + .name = "tegra-pwm", + .of_match_table = tegra_pwm_of_match, + .pm = &tegra_pwm_pm_ops, + }, + .probe = tegra_pwm_probe, + .remove = tegra_pwm_remove, +}; + +module_platform_driver(tegra_pwm_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sandipan Patra <spatra@nvidia.com>"); +MODULE_DESCRIPTION("Tegra PWM controller driver"); +MODULE_ALIAS("platform:tegra-pwm"); diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c new file mode 100644 index 000000000..683804c7d --- /dev/null +++ b/drivers/pwm/pwm-tiecap.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ECAP PWM driver + * + * Copyright (C) 2012 Texas Instruments, Inc. - https://www.ti.com/ + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <linux/pwm.h> +#include <linux/of_device.h> + +/* ECAP registers and bits definitions */ +#define CAP1 0x08 +#define CAP2 0x0C +#define CAP3 0x10 +#define CAP4 0x14 +#define ECCTL2 0x2A +#define ECCTL2_APWM_POL_LOW BIT(10) +#define ECCTL2_APWM_MODE BIT(9) +#define ECCTL2_SYNC_SEL_DISA (BIT(7) | BIT(6)) +#define ECCTL2_TSCTR_FREERUN BIT(4) + +struct ecap_context { + u32 cap3; + u32 cap4; + u16 ecctl2; +}; + +struct ecap_pwm_chip { + struct pwm_chip chip; + unsigned int clk_rate; + void __iomem *mmio_base; + struct ecap_context ctx; +}; + +static inline struct ecap_pwm_chip *to_ecap_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct ecap_pwm_chip, chip); +} + +/* + * period_ns = 10^9 * period_cycles / PWM_CLK_RATE + * duty_ns = 10^9 * duty_cycles / PWM_CLK_RATE + */ +static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip); + u32 period_cycles, duty_cycles; + unsigned long long c; + u16 value; + + if (period_ns > NSEC_PER_SEC) + return -ERANGE; + + c = pc->clk_rate; + c = c * period_ns; + do_div(c, NSEC_PER_SEC); + period_cycles = (u32)c; + + if (period_cycles < 1) { + period_cycles = 1; + duty_cycles = 1; + } else { + c = pc->clk_rate; + c = c * duty_ns; + do_div(c, NSEC_PER_SEC); + duty_cycles = (u32)c; + } + + pm_runtime_get_sync(pc->chip.dev); + + value = readw(pc->mmio_base + ECCTL2); + + /* Configure APWM mode & disable sync option */ + value |= ECCTL2_APWM_MODE | ECCTL2_SYNC_SEL_DISA; + + writew(value, pc->mmio_base + ECCTL2); + + if (!pwm_is_enabled(pwm)) { + /* Update active registers if not running */ + writel(duty_cycles, pc->mmio_base + CAP2); + writel(period_cycles, pc->mmio_base + CAP1); + } else { + /* + * Update shadow registers to configure period and + * compare values. This helps current PWM period to + * complete on reconfiguring + */ + writel(duty_cycles, pc->mmio_base + CAP4); + writel(period_cycles, pc->mmio_base + CAP3); + } + + if (!pwm_is_enabled(pwm)) { + value = readw(pc->mmio_base + ECCTL2); + /* Disable APWM mode to put APWM output Low */ + value &= ~ECCTL2_APWM_MODE; + writew(value, pc->mmio_base + ECCTL2); + } + + pm_runtime_put_sync(pc->chip.dev); + + return 0; +} + +static int ecap_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip); + u16 value; + + pm_runtime_get_sync(pc->chip.dev); + + value = readw(pc->mmio_base + ECCTL2); + + if (polarity == PWM_POLARITY_INVERSED) + /* Duty cycle defines LOW period of PWM */ + value |= ECCTL2_APWM_POL_LOW; + else + /* Duty cycle defines HIGH period of PWM */ + value &= ~ECCTL2_APWM_POL_LOW; + + writew(value, pc->mmio_base + ECCTL2); + + pm_runtime_put_sync(pc->chip.dev); + + return 0; +} + +static int ecap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip); + u16 value; + + /* Leave clock enabled on enabling PWM */ + pm_runtime_get_sync(pc->chip.dev); + + /* + * Enable 'Free run Time stamp counter mode' to start counter + * and 'APWM mode' to enable APWM output + */ + value = readw(pc->mmio_base + ECCTL2); + value |= ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE; + writew(value, pc->mmio_base + ECCTL2); + + return 0; +} + +static void ecap_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip); + u16 value; + + /* + * Disable 'Free run Time stamp counter mode' to stop counter + * and 'APWM mode' to put APWM output to low + */ + value = readw(pc->mmio_base + ECCTL2); + value &= ~(ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE); + writew(value, pc->mmio_base + ECCTL2); + + /* Disable clock on PWM disable */ + pm_runtime_put_sync(pc->chip.dev); +} + +static void ecap_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + if (pwm_is_enabled(pwm)) { + dev_warn(chip->dev, "Removing PWM device without disabling\n"); + pm_runtime_put_sync(chip->dev); + } +} + +static const struct pwm_ops ecap_pwm_ops = { + .free = ecap_pwm_free, + .config = ecap_pwm_config, + .set_polarity = ecap_pwm_set_polarity, + .enable = ecap_pwm_enable, + .disable = ecap_pwm_disable, + .owner = THIS_MODULE, +}; + +static const struct of_device_id ecap_of_match[] = { + { .compatible = "ti,am3352-ecap" }, + { .compatible = "ti,am33xx-ecap" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ecap_of_match); + +static int ecap_pwm_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct ecap_pwm_chip *pc; + struct resource *r; + struct clk *clk; + int ret; + + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(clk)) { + if (of_device_is_compatible(np, "ti,am33xx-ecap")) { + dev_warn(&pdev->dev, "Binding is obsolete.\n"); + clk = devm_clk_get(pdev->dev.parent, "fck"); + } + } + + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return PTR_ERR(clk); + } + + pc->clk_rate = clk_get_rate(clk); + if (!pc->clk_rate) { + dev_err(&pdev->dev, "failed to get clock rate\n"); + return -EINVAL; + } + + pc->chip.dev = &pdev->dev; + pc->chip.ops = &ecap_pwm_ops; + pc->chip.of_xlate = of_pwm_xlate_with_flags; + pc->chip.of_pwm_n_cells = 3; + pc->chip.base = -1; + pc->chip.npwm = 1; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pc->mmio_base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(pc->mmio_base)) + return PTR_ERR(pc->mmio_base); + + ret = pwmchip_add(&pc->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, pc); + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static int ecap_pwm_remove(struct platform_device *pdev) +{ + struct ecap_pwm_chip *pc = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + return pwmchip_remove(&pc->chip); +} + +#ifdef CONFIG_PM_SLEEP +static void ecap_pwm_save_context(struct ecap_pwm_chip *pc) +{ + pm_runtime_get_sync(pc->chip.dev); + pc->ctx.ecctl2 = readw(pc->mmio_base + ECCTL2); + pc->ctx.cap4 = readl(pc->mmio_base + CAP4); + pc->ctx.cap3 = readl(pc->mmio_base + CAP3); + pm_runtime_put_sync(pc->chip.dev); +} + +static void ecap_pwm_restore_context(struct ecap_pwm_chip *pc) +{ + writel(pc->ctx.cap3, pc->mmio_base + CAP3); + writel(pc->ctx.cap4, pc->mmio_base + CAP4); + writew(pc->ctx.ecctl2, pc->mmio_base + ECCTL2); +} + +static int ecap_pwm_suspend(struct device *dev) +{ + struct ecap_pwm_chip *pc = dev_get_drvdata(dev); + struct pwm_device *pwm = pc->chip.pwms; + + ecap_pwm_save_context(pc); + + /* Disable explicitly if PWM is running */ + if (pwm_is_enabled(pwm)) + pm_runtime_put_sync(dev); + + return 0; +} + +static int ecap_pwm_resume(struct device *dev) +{ + struct ecap_pwm_chip *pc = dev_get_drvdata(dev); + struct pwm_device *pwm = pc->chip.pwms; + + /* Enable explicitly if PWM was running */ + if (pwm_is_enabled(pwm)) + pm_runtime_get_sync(dev); + + ecap_pwm_restore_context(pc); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(ecap_pwm_pm_ops, ecap_pwm_suspend, ecap_pwm_resume); + +static struct platform_driver ecap_pwm_driver = { + .driver = { + .name = "ecap", + .of_match_table = ecap_of_match, + .pm = &ecap_pwm_pm_ops, + }, + .probe = ecap_pwm_probe, + .remove = ecap_pwm_remove, +}; +module_platform_driver(ecap_pwm_driver); + +MODULE_DESCRIPTION("ECAP PWM driver"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c new file mode 100644 index 000000000..0846917ff --- /dev/null +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -0,0 +1,590 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EHRPWM PWM driver + * + * Copyright (C) 2012 Texas Instruments, Inc. - https://www.ti.com/ + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <linux/of_device.h> + +/* EHRPWM registers and bits definitions */ + +/* Time base module registers */ +#define TBCTL 0x00 +#define TBPRD 0x0A + +#define TBCTL_PRDLD_MASK BIT(3) +#define TBCTL_PRDLD_SHDW 0 +#define TBCTL_PRDLD_IMDT BIT(3) +#define TBCTL_CLKDIV_MASK (BIT(12) | BIT(11) | BIT(10) | BIT(9) | \ + BIT(8) | BIT(7)) +#define TBCTL_CTRMODE_MASK (BIT(1) | BIT(0)) +#define TBCTL_CTRMODE_UP 0 +#define TBCTL_CTRMODE_DOWN BIT(0) +#define TBCTL_CTRMODE_UPDOWN BIT(1) +#define TBCTL_CTRMODE_FREEZE (BIT(1) | BIT(0)) + +#define TBCTL_HSPCLKDIV_SHIFT 7 +#define TBCTL_CLKDIV_SHIFT 10 + +#define CLKDIV_MAX 7 +#define HSPCLKDIV_MAX 7 +#define PERIOD_MAX 0xFFFF + +/* compare module registers */ +#define CMPA 0x12 +#define CMPB 0x14 + +/* Action qualifier module registers */ +#define AQCTLA 0x16 +#define AQCTLB 0x18 +#define AQSFRC 0x1A +#define AQCSFRC 0x1C + +#define AQCTL_CBU_MASK (BIT(9) | BIT(8)) +#define AQCTL_CBU_FRCLOW BIT(8) +#define AQCTL_CBU_FRCHIGH BIT(9) +#define AQCTL_CBU_FRCTOGGLE (BIT(9) | BIT(8)) +#define AQCTL_CAU_MASK (BIT(5) | BIT(4)) +#define AQCTL_CAU_FRCLOW BIT(4) +#define AQCTL_CAU_FRCHIGH BIT(5) +#define AQCTL_CAU_FRCTOGGLE (BIT(5) | BIT(4)) +#define AQCTL_PRD_MASK (BIT(3) | BIT(2)) +#define AQCTL_PRD_FRCLOW BIT(2) +#define AQCTL_PRD_FRCHIGH BIT(3) +#define AQCTL_PRD_FRCTOGGLE (BIT(3) | BIT(2)) +#define AQCTL_ZRO_MASK (BIT(1) | BIT(0)) +#define AQCTL_ZRO_FRCLOW BIT(0) +#define AQCTL_ZRO_FRCHIGH BIT(1) +#define AQCTL_ZRO_FRCTOGGLE (BIT(1) | BIT(0)) + +#define AQCTL_CHANA_POLNORMAL (AQCTL_CAU_FRCLOW | AQCTL_PRD_FRCHIGH | \ + AQCTL_ZRO_FRCHIGH) +#define AQCTL_CHANA_POLINVERSED (AQCTL_CAU_FRCHIGH | AQCTL_PRD_FRCLOW | \ + AQCTL_ZRO_FRCLOW) +#define AQCTL_CHANB_POLNORMAL (AQCTL_CBU_FRCLOW | AQCTL_PRD_FRCHIGH | \ + AQCTL_ZRO_FRCHIGH) +#define AQCTL_CHANB_POLINVERSED (AQCTL_CBU_FRCHIGH | AQCTL_PRD_FRCLOW | \ + AQCTL_ZRO_FRCLOW) + +#define AQSFRC_RLDCSF_MASK (BIT(7) | BIT(6)) +#define AQSFRC_RLDCSF_ZRO 0 +#define AQSFRC_RLDCSF_PRD BIT(6) +#define AQSFRC_RLDCSF_ZROPRD BIT(7) +#define AQSFRC_RLDCSF_IMDT (BIT(7) | BIT(6)) + +#define AQCSFRC_CSFB_MASK (BIT(3) | BIT(2)) +#define AQCSFRC_CSFB_FRCDIS 0 +#define AQCSFRC_CSFB_FRCLOW BIT(2) +#define AQCSFRC_CSFB_FRCHIGH BIT(3) +#define AQCSFRC_CSFB_DISSWFRC (BIT(3) | BIT(2)) +#define AQCSFRC_CSFA_MASK (BIT(1) | BIT(0)) +#define AQCSFRC_CSFA_FRCDIS 0 +#define AQCSFRC_CSFA_FRCLOW BIT(0) +#define AQCSFRC_CSFA_FRCHIGH BIT(1) +#define AQCSFRC_CSFA_DISSWFRC (BIT(1) | BIT(0)) + +#define NUM_PWM_CHANNEL 2 /* EHRPWM channels */ + +struct ehrpwm_context { + u16 tbctl; + u16 tbprd; + u16 cmpa; + u16 cmpb; + u16 aqctla; + u16 aqctlb; + u16 aqsfrc; + u16 aqcsfrc; +}; + +struct ehrpwm_pwm_chip { + struct pwm_chip chip; + unsigned long clk_rate; + void __iomem *mmio_base; + unsigned long period_cycles[NUM_PWM_CHANNEL]; + enum pwm_polarity polarity[NUM_PWM_CHANNEL]; + struct clk *tbclk; + struct ehrpwm_context ctx; +}; + +static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct ehrpwm_pwm_chip, chip); +} + +static inline u16 ehrpwm_read(void __iomem *base, unsigned int offset) +{ + return readw(base + offset); +} + +static inline void ehrpwm_write(void __iomem *base, unsigned int offset, + u16 value) +{ + writew(value, base + offset); +} + +static void ehrpwm_modify(void __iomem *base, unsigned int offset, u16 mask, + u16 value) +{ + unsigned short val; + + val = readw(base + offset); + val &= ~mask; + val |= value & mask; + writew(val, base + offset); +} + +/** + * set_prescale_div - Set up the prescaler divider function + * @rqst_prescaler: prescaler value min + * @prescale_div: prescaler value set + * @tb_clk_div: Time Base Control prescaler bits + */ +static int set_prescale_div(unsigned long rqst_prescaler, u16 *prescale_div, + u16 *tb_clk_div) +{ + unsigned int clkdiv, hspclkdiv; + + for (clkdiv = 0; clkdiv <= CLKDIV_MAX; clkdiv++) { + for (hspclkdiv = 0; hspclkdiv <= HSPCLKDIV_MAX; hspclkdiv++) { + /* + * calculations for prescaler value : + * prescale_div = HSPCLKDIVIDER * CLKDIVIDER. + * HSPCLKDIVIDER = 2 ** hspclkdiv + * CLKDIVIDER = (1), if clkdiv == 0 *OR* + * (2 * clkdiv), if clkdiv != 0 + * + * Configure prescale_div value such that period + * register value is less than 65535. + */ + + *prescale_div = (1 << clkdiv) * + (hspclkdiv ? (hspclkdiv * 2) : 1); + if (*prescale_div > rqst_prescaler) { + *tb_clk_div = (clkdiv << TBCTL_CLKDIV_SHIFT) | + (hspclkdiv << TBCTL_HSPCLKDIV_SHIFT); + return 0; + } + } + } + + return 1; +} + +static void configure_polarity(struct ehrpwm_pwm_chip *pc, int chan) +{ + u16 aqctl_val, aqctl_mask; + unsigned int aqctl_reg; + + /* + * Configure PWM output to HIGH/LOW level on counter + * reaches compare register value and LOW/HIGH level + * on counter value reaches period register value and + * zero value on counter + */ + if (chan == 1) { + aqctl_reg = AQCTLB; + aqctl_mask = AQCTL_CBU_MASK; + + if (pc->polarity[chan] == PWM_POLARITY_INVERSED) + aqctl_val = AQCTL_CHANB_POLINVERSED; + else + aqctl_val = AQCTL_CHANB_POLNORMAL; + } else { + aqctl_reg = AQCTLA; + aqctl_mask = AQCTL_CAU_MASK; + + if (pc->polarity[chan] == PWM_POLARITY_INVERSED) + aqctl_val = AQCTL_CHANA_POLINVERSED; + else + aqctl_val = AQCTL_CHANA_POLNORMAL; + } + + aqctl_mask |= AQCTL_PRD_MASK | AQCTL_ZRO_MASK; + ehrpwm_modify(pc->mmio_base, aqctl_reg, aqctl_mask, aqctl_val); +} + +/* + * period_ns = 10^9 * (ps_divval * period_cycles) / PWM_CLK_RATE + * duty_ns = 10^9 * (ps_divval * duty_cycles) / PWM_CLK_RATE + */ +static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); + u32 period_cycles, duty_cycles; + u16 ps_divval, tb_divval; + unsigned int i, cmp_reg; + unsigned long long c; + + if (period_ns > NSEC_PER_SEC) + return -ERANGE; + + c = pc->clk_rate; + c = c * period_ns; + do_div(c, NSEC_PER_SEC); + period_cycles = (unsigned long)c; + + if (period_cycles < 1) { + period_cycles = 1; + duty_cycles = 1; + } else { + c = pc->clk_rate; + c = c * duty_ns; + do_div(c, NSEC_PER_SEC); + duty_cycles = (unsigned long)c; + } + + /* + * Period values should be same for multiple PWM channels as IP uses + * same period register for multiple channels. + */ + for (i = 0; i < NUM_PWM_CHANNEL; i++) { + if (pc->period_cycles[i] && + (pc->period_cycles[i] != period_cycles)) { + /* + * Allow channel to reconfigure period if no other + * channels being configured. + */ + if (i == pwm->hwpwm) + continue; + + dev_err(chip->dev, + "period value conflicts with channel %u\n", + i); + return -EINVAL; + } + } + + pc->period_cycles[pwm->hwpwm] = period_cycles; + + /* Configure clock prescaler to support Low frequency PWM wave */ + if (set_prescale_div(period_cycles/PERIOD_MAX, &ps_divval, + &tb_divval)) { + dev_err(chip->dev, "Unsupported values\n"); + return -EINVAL; + } + + pm_runtime_get_sync(chip->dev); + + /* Update clock prescaler values */ + ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CLKDIV_MASK, tb_divval); + + /* Update period & duty cycle with presacler division */ + period_cycles = period_cycles / ps_divval; + duty_cycles = duty_cycles / ps_divval; + + /* Configure shadow loading on Period register */ + ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_PRDLD_MASK, TBCTL_PRDLD_SHDW); + + ehrpwm_write(pc->mmio_base, TBPRD, period_cycles); + + /* Configure ehrpwm counter for up-count mode */ + ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CTRMODE_MASK, + TBCTL_CTRMODE_UP); + + if (pwm->hwpwm == 1) + /* Channel 1 configured with compare B register */ + cmp_reg = CMPB; + else + /* Channel 0 configured with compare A register */ + cmp_reg = CMPA; + + ehrpwm_write(pc->mmio_base, cmp_reg, duty_cycles); + + pm_runtime_put_sync(chip->dev); + + return 0; +} + +static int ehrpwm_pwm_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); + + /* Configuration of polarity in hardware delayed, do at enable */ + pc->polarity[pwm->hwpwm] = polarity; + + return 0; +} + +static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); + u16 aqcsfrc_val, aqcsfrc_mask; + int ret; + + /* Leave clock enabled on enabling PWM */ + pm_runtime_get_sync(chip->dev); + + /* Disabling Action Qualifier on PWM output */ + if (pwm->hwpwm) { + aqcsfrc_val = AQCSFRC_CSFB_FRCDIS; + aqcsfrc_mask = AQCSFRC_CSFB_MASK; + } else { + aqcsfrc_val = AQCSFRC_CSFA_FRCDIS; + aqcsfrc_mask = AQCSFRC_CSFA_MASK; + } + + /* Changes to shadow mode */ + ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK, + AQSFRC_RLDCSF_ZRO); + + ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val); + + /* Channels polarity can be configured from action qualifier module */ + configure_polarity(pc, pwm->hwpwm); + + /* Enable TBCLK */ + ret = clk_enable(pc->tbclk); + if (ret) { + dev_err(chip->dev, "Failed to enable TBCLK for %s: %d\n", + dev_name(pc->chip.dev), ret); + return ret; + } + + return 0; +} + +static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); + u16 aqcsfrc_val, aqcsfrc_mask; + + /* Action Qualifier puts PWM output low forcefully */ + if (pwm->hwpwm) { + aqcsfrc_val = AQCSFRC_CSFB_FRCLOW; + aqcsfrc_mask = AQCSFRC_CSFB_MASK; + } else { + aqcsfrc_val = AQCSFRC_CSFA_FRCLOW; + aqcsfrc_mask = AQCSFRC_CSFA_MASK; + } + + /* Update shadow register first before modifying active register */ + ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK, + AQSFRC_RLDCSF_ZRO); + ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val); + /* + * Changes to immediate action on Action Qualifier. This puts + * Action Qualifier control on PWM output from next TBCLK + */ + ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK, + AQSFRC_RLDCSF_IMDT); + + ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val); + + /* Disabling TBCLK on PWM disable */ + clk_disable(pc->tbclk); + + /* Disable clock on PWM disable */ + pm_runtime_put_sync(chip->dev); +} + +static void ehrpwm_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); + + if (pwm_is_enabled(pwm)) { + dev_warn(chip->dev, "Removing PWM device without disabling\n"); + pm_runtime_put_sync(chip->dev); + } + + /* set period value to zero on free */ + pc->period_cycles[pwm->hwpwm] = 0; +} + +static const struct pwm_ops ehrpwm_pwm_ops = { + .free = ehrpwm_pwm_free, + .config = ehrpwm_pwm_config, + .set_polarity = ehrpwm_pwm_set_polarity, + .enable = ehrpwm_pwm_enable, + .disable = ehrpwm_pwm_disable, + .owner = THIS_MODULE, +}; + +static const struct of_device_id ehrpwm_of_match[] = { + { .compatible = "ti,am3352-ehrpwm" }, + { .compatible = "ti,am33xx-ehrpwm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ehrpwm_of_match); + +static int ehrpwm_pwm_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct ehrpwm_pwm_chip *pc; + struct resource *r; + struct clk *clk; + int ret; + + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(clk)) { + if (of_device_is_compatible(np, "ti,am33xx-ecap")) { + dev_warn(&pdev->dev, "Binding is obsolete.\n"); + clk = devm_clk_get(pdev->dev.parent, "fck"); + } + } + + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return PTR_ERR(clk); + } + + pc->clk_rate = clk_get_rate(clk); + if (!pc->clk_rate) { + dev_err(&pdev->dev, "failed to get clock rate\n"); + return -EINVAL; + } + + pc->chip.dev = &pdev->dev; + pc->chip.ops = &ehrpwm_pwm_ops; + pc->chip.of_xlate = of_pwm_xlate_with_flags; + pc->chip.of_pwm_n_cells = 3; + pc->chip.base = -1; + pc->chip.npwm = NUM_PWM_CHANNEL; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pc->mmio_base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(pc->mmio_base)) + return PTR_ERR(pc->mmio_base); + + /* Acquire tbclk for Time Base EHRPWM submodule */ + pc->tbclk = devm_clk_get(&pdev->dev, "tbclk"); + if (IS_ERR(pc->tbclk)) { + dev_err(&pdev->dev, "Failed to get tbclk\n"); + return PTR_ERR(pc->tbclk); + } + + ret = clk_prepare(pc->tbclk); + if (ret < 0) { + dev_err(&pdev->dev, "clk_prepare() failed: %d\n", ret); + return ret; + } + + ret = pwmchip_add(&pc->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + goto err_clk_unprepare; + } + + platform_set_drvdata(pdev, pc); + pm_runtime_enable(&pdev->dev); + + return 0; + +err_clk_unprepare: + clk_unprepare(pc->tbclk); + + return ret; +} + +static int ehrpwm_pwm_remove(struct platform_device *pdev) +{ + struct ehrpwm_pwm_chip *pc = platform_get_drvdata(pdev); + + clk_unprepare(pc->tbclk); + + pm_runtime_disable(&pdev->dev); + + return pwmchip_remove(&pc->chip); +} + +#ifdef CONFIG_PM_SLEEP +static void ehrpwm_pwm_save_context(struct ehrpwm_pwm_chip *pc) +{ + pm_runtime_get_sync(pc->chip.dev); + + pc->ctx.tbctl = ehrpwm_read(pc->mmio_base, TBCTL); + pc->ctx.tbprd = ehrpwm_read(pc->mmio_base, TBPRD); + pc->ctx.cmpa = ehrpwm_read(pc->mmio_base, CMPA); + pc->ctx.cmpb = ehrpwm_read(pc->mmio_base, CMPB); + pc->ctx.aqctla = ehrpwm_read(pc->mmio_base, AQCTLA); + pc->ctx.aqctlb = ehrpwm_read(pc->mmio_base, AQCTLB); + pc->ctx.aqsfrc = ehrpwm_read(pc->mmio_base, AQSFRC); + pc->ctx.aqcsfrc = ehrpwm_read(pc->mmio_base, AQCSFRC); + + pm_runtime_put_sync(pc->chip.dev); +} + +static void ehrpwm_pwm_restore_context(struct ehrpwm_pwm_chip *pc) +{ + ehrpwm_write(pc->mmio_base, TBPRD, pc->ctx.tbprd); + ehrpwm_write(pc->mmio_base, CMPA, pc->ctx.cmpa); + ehrpwm_write(pc->mmio_base, CMPB, pc->ctx.cmpb); + ehrpwm_write(pc->mmio_base, AQCTLA, pc->ctx.aqctla); + ehrpwm_write(pc->mmio_base, AQCTLB, pc->ctx.aqctlb); + ehrpwm_write(pc->mmio_base, AQSFRC, pc->ctx.aqsfrc); + ehrpwm_write(pc->mmio_base, AQCSFRC, pc->ctx.aqcsfrc); + ehrpwm_write(pc->mmio_base, TBCTL, pc->ctx.tbctl); +} + +static int ehrpwm_pwm_suspend(struct device *dev) +{ + struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev); + unsigned int i; + + ehrpwm_pwm_save_context(pc); + + for (i = 0; i < pc->chip.npwm; i++) { + struct pwm_device *pwm = &pc->chip.pwms[i]; + + if (!pwm_is_enabled(pwm)) + continue; + + /* Disable explicitly if PWM is running */ + pm_runtime_put_sync(dev); + } + + return 0; +} + +static int ehrpwm_pwm_resume(struct device *dev) +{ + struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev); + unsigned int i; + + for (i = 0; i < pc->chip.npwm; i++) { + struct pwm_device *pwm = &pc->chip.pwms[i]; + + if (!pwm_is_enabled(pwm)) + continue; + + /* Enable explicitly if PWM was running */ + pm_runtime_get_sync(dev); + } + + ehrpwm_pwm_restore_context(pc); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(ehrpwm_pwm_pm_ops, ehrpwm_pwm_suspend, + ehrpwm_pwm_resume); + +static struct platform_driver ehrpwm_pwm_driver = { + .driver = { + .name = "ehrpwm", + .of_match_table = ehrpwm_of_match, + .pm = &ehrpwm_pwm_pm_ops, + }, + .probe = ehrpwm_pwm_probe, + .remove = ehrpwm_pwm_remove, +}; +module_platform_driver(ehrpwm_pwm_driver); + +MODULE_DESCRIPTION("EHRPWM PWM driver"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-twl-led.c b/drivers/pwm/pwm-twl-led.c new file mode 100644 index 000000000..630b9a578 --- /dev/null +++ b/drivers/pwm/pwm-twl-led.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for TWL4030/6030 Pulse Width Modulator used as LED driver + * + * Copyright (C) 2012 Texas Instruments + * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> + * + * This driver is a complete rewrite of the former pwm-twl6030.c authorded by: + * Hemanth V <hemanthv@ti.com> + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/mfd/twl.h> +#include <linux/slab.h> + +/* + * This driver handles the PWM driven LED terminals of TWL4030 and TWL6030. + * To generate the signal on TWL4030: + * - LEDA uses PWMA + * - LEDB uses PWMB + * TWL6030 has one LED pin with dedicated LEDPWM + */ + +#define TWL4030_LED_MAX 0x7f +#define TWL6030_LED_MAX 0xff + +/* Registers, bits and macro for TWL4030 */ +#define TWL4030_LEDEN_REG 0x00 +#define TWL4030_PWMA_REG 0x01 + +#define TWL4030_LEDXON (1 << 0) +#define TWL4030_LEDXPWM (1 << 4) +#define TWL4030_LED_PINS (TWL4030_LEDXON | TWL4030_LEDXPWM) +#define TWL4030_LED_TOGGLE(led, x) ((x) << (led)) + +/* Register, bits and macro for TWL6030 */ +#define TWL6030_LED_PWM_CTRL1 0xf4 +#define TWL6030_LED_PWM_CTRL2 0xf5 + +#define TWL6040_LED_MODE_HW 0x00 +#define TWL6040_LED_MODE_ON 0x01 +#define TWL6040_LED_MODE_OFF 0x02 +#define TWL6040_LED_MODE_MASK 0x03 + +struct twl_pwmled_chip { + struct pwm_chip chip; + struct mutex mutex; +}; + +static inline struct twl_pwmled_chip *to_twl(struct pwm_chip *chip) +{ + return container_of(chip, struct twl_pwmled_chip, chip); +} + +static int twl4030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + int duty_cycle = DIV_ROUND_UP(duty_ns * TWL4030_LED_MAX, period_ns) + 1; + u8 pwm_config[2] = { 1, 0 }; + int base, ret; + + /* + * To configure the duty period: + * On-cycle is set to 1 (the minimum allowed value) + * The off time of 0 is not configurable, so the mapping is: + * 0 -> off cycle = 2, + * 1 -> off cycle = 2, + * 2 -> off cycle = 3, + * 126 - > off cycle 127, + * 127 - > off cycle 1 + * When on cycle == off cycle the PWM will be always on + */ + if (duty_cycle == 1) + duty_cycle = 2; + else if (duty_cycle > TWL4030_LED_MAX) + duty_cycle = 1; + + base = pwm->hwpwm * 2 + TWL4030_PWMA_REG; + + pwm_config[1] = duty_cycle; + + ret = twl_i2c_write(TWL4030_MODULE_LED, pwm_config, base, 2); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label); + + return ret; +} + +static int twl4030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct twl_pwmled_chip *twl = to_twl(chip); + int ret; + u8 val; + + mutex_lock(&twl->mutex); + ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG); + if (ret < 0) { + dev_err(chip->dev, "%s: Failed to read LEDEN\n", pwm->label); + goto out; + } + + val |= TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS); + + ret = twl_i2c_write_u8(TWL4030_MODULE_LED, val, TWL4030_LEDEN_REG); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label); + +out: + mutex_unlock(&twl->mutex); + return ret; +} + +static void twl4030_pwmled_disable(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct twl_pwmled_chip *twl = to_twl(chip); + int ret; + u8 val; + + mutex_lock(&twl->mutex); + ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG); + if (ret < 0) { + dev_err(chip->dev, "%s: Failed to read LEDEN\n", pwm->label); + goto out; + } + + val &= ~TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS); + + ret = twl_i2c_write_u8(TWL4030_MODULE_LED, val, TWL4030_LEDEN_REG); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label); + +out: + mutex_unlock(&twl->mutex); +} + +static int twl6030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + int duty_cycle = (duty_ns * TWL6030_LED_MAX) / period_ns; + u8 on_time; + int ret; + + on_time = duty_cycle & 0xff; + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, on_time, + TWL6030_LED_PWM_CTRL1); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label); + + return ret; +} + +static int twl6030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct twl_pwmled_chip *twl = to_twl(chip); + int ret; + u8 val; + + mutex_lock(&twl->mutex); + ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); + if (ret < 0) { + dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n", + pwm->label); + goto out; + } + + val &= ~TWL6040_LED_MODE_MASK; + val |= TWL6040_LED_MODE_ON; + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label); + +out: + mutex_unlock(&twl->mutex); + return ret; +} + +static void twl6030_pwmled_disable(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct twl_pwmled_chip *twl = to_twl(chip); + int ret; + u8 val; + + mutex_lock(&twl->mutex); + ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); + if (ret < 0) { + dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n", + pwm->label); + goto out; + } + + val &= ~TWL6040_LED_MODE_MASK; + val |= TWL6040_LED_MODE_OFF; + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label); + +out: + mutex_unlock(&twl->mutex); +} + +static int twl6030_pwmled_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct twl_pwmled_chip *twl = to_twl(chip); + int ret; + u8 val; + + mutex_lock(&twl->mutex); + ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); + if (ret < 0) { + dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n", + pwm->label); + goto out; + } + + val &= ~TWL6040_LED_MODE_MASK; + val |= TWL6040_LED_MODE_OFF; + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to request PWM\n", pwm->label); + +out: + mutex_unlock(&twl->mutex); + return ret; +} + +static void twl6030_pwmled_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct twl_pwmled_chip *twl = to_twl(chip); + int ret; + u8 val; + + mutex_lock(&twl->mutex); + ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); + if (ret < 0) { + dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n", + pwm->label); + goto out; + } + + val &= ~TWL6040_LED_MODE_MASK; + val |= TWL6040_LED_MODE_HW; + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to free PWM\n", pwm->label); + +out: + mutex_unlock(&twl->mutex); +} + +static const struct pwm_ops twl4030_pwmled_ops = { + .enable = twl4030_pwmled_enable, + .disable = twl4030_pwmled_disable, + .config = twl4030_pwmled_config, + .owner = THIS_MODULE, +}; + +static const struct pwm_ops twl6030_pwmled_ops = { + .enable = twl6030_pwmled_enable, + .disable = twl6030_pwmled_disable, + .config = twl6030_pwmled_config, + .request = twl6030_pwmled_request, + .free = twl6030_pwmled_free, + .owner = THIS_MODULE, +}; + +static int twl_pwmled_probe(struct platform_device *pdev) +{ + struct twl_pwmled_chip *twl; + int ret; + + twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL); + if (!twl) + return -ENOMEM; + + if (twl_class_is_4030()) { + twl->chip.ops = &twl4030_pwmled_ops; + twl->chip.npwm = 2; + } else { + twl->chip.ops = &twl6030_pwmled_ops; + twl->chip.npwm = 1; + } + + twl->chip.dev = &pdev->dev; + twl->chip.base = -1; + + mutex_init(&twl->mutex); + + ret = pwmchip_add(&twl->chip); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, twl); + + return 0; +} + +static int twl_pwmled_remove(struct platform_device *pdev) +{ + struct twl_pwmled_chip *twl = platform_get_drvdata(pdev); + + return pwmchip_remove(&twl->chip); +} + +#ifdef CONFIG_OF +static const struct of_device_id twl_pwmled_of_match[] = { + { .compatible = "ti,twl4030-pwmled" }, + { .compatible = "ti,twl6030-pwmled" }, + { }, +}; +MODULE_DEVICE_TABLE(of, twl_pwmled_of_match); +#endif + +static struct platform_driver twl_pwmled_driver = { + .driver = { + .name = "twl-pwmled", + .of_match_table = of_match_ptr(twl_pwmled_of_match), + }, + .probe = twl_pwmled_probe, + .remove = twl_pwmled_remove, +}; +module_platform_driver(twl_pwmled_driver); + +MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); +MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030 LED outputs"); +MODULE_ALIAS("platform:twl-pwmled"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-twl.c b/drivers/pwm/pwm-twl.c new file mode 100644 index 000000000..aee67974f --- /dev/null +++ b/drivers/pwm/pwm-twl.c @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for TWL4030/6030 Generic Pulse Width Modulator + * + * Copyright (C) 2012 Texas Instruments + * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/mfd/twl.h> +#include <linux/slab.h> + +/* + * This driver handles the PWMs of TWL4030 and TWL6030. + * The TRM names for the PWMs on TWL4030 are: PWM0, PWM1 + * TWL6030 also have two PWMs named in the TRM as PWM1, PWM2 + */ + +#define TWL_PWM_MAX 0x7f + +/* Registers, bits and macro for TWL4030 */ +#define TWL4030_GPBR1_REG 0x0c +#define TWL4030_PMBR1_REG 0x0d + +/* GPBR1 register bits */ +#define TWL4030_PWMXCLK_ENABLE (1 << 0) +#define TWL4030_PWMX_ENABLE (1 << 2) +#define TWL4030_PWMX_BITS (TWL4030_PWMX_ENABLE | TWL4030_PWMXCLK_ENABLE) +#define TWL4030_PWM_TOGGLE(pwm, x) ((x) << (pwm)) + +/* PMBR1 register bits */ +#define TWL4030_GPIO6_PWM0_MUTE_MASK (0x03 << 2) +#define TWL4030_GPIO6_PWM0_MUTE_PWM0 (0x01 << 2) +#define TWL4030_GPIO7_VIBRASYNC_PWM1_MASK (0x03 << 4) +#define TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1 (0x03 << 4) + +/* Register, bits and macro for TWL6030 */ +#define TWL6030_TOGGLE3_REG 0x92 + +#define TWL6030_PWMXR (1 << 0) +#define TWL6030_PWMXS (1 << 1) +#define TWL6030_PWMXEN (1 << 2) +#define TWL6030_PWM_TOGGLE(pwm, x) ((x) << (pwm * 3)) + +struct twl_pwm_chip { + struct pwm_chip chip; + struct mutex mutex; + u8 twl6030_toggle3; + u8 twl4030_pwm_mux; +}; + +static inline struct twl_pwm_chip *to_twl(struct pwm_chip *chip) +{ + return container_of(chip, struct twl_pwm_chip, chip); +} + +static int twl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + int duty_cycle = DIV_ROUND_UP(duty_ns * TWL_PWM_MAX, period_ns) + 1; + u8 pwm_config[2] = { 1, 0 }; + int base, ret; + + /* + * To configure the duty period: + * On-cycle is set to 1 (the minimum allowed value) + * The off time of 0 is not configurable, so the mapping is: + * 0 -> off cycle = 2, + * 1 -> off cycle = 2, + * 2 -> off cycle = 3, + * 126 - > off cycle 127, + * 127 - > off cycle 1 + * When on cycle == off cycle the PWM will be always on + */ + if (duty_cycle == 1) + duty_cycle = 2; + else if (duty_cycle > TWL_PWM_MAX) + duty_cycle = 1; + + base = pwm->hwpwm * 3; + + pwm_config[1] = duty_cycle; + + ret = twl_i2c_write(TWL_MODULE_PWM, pwm_config, base, 2); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label); + + return ret; +} + +static int twl4030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct twl_pwm_chip *twl = to_twl(chip); + int ret; + u8 val; + + mutex_lock(&twl->mutex); + ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_GPBR1_REG); + if (ret < 0) { + dev_err(chip->dev, "%s: Failed to read GPBR1\n", pwm->label); + goto out; + } + + val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMXCLK_ENABLE); + + ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label); + + val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_ENABLE); + + ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label); + +out: + mutex_unlock(&twl->mutex); + return ret; +} + +static void twl4030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct twl_pwm_chip *twl = to_twl(chip); + int ret; + u8 val; + + mutex_lock(&twl->mutex); + ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_GPBR1_REG); + if (ret < 0) { + dev_err(chip->dev, "%s: Failed to read GPBR1\n", pwm->label); + goto out; + } + + val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_ENABLE); + + ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label); + + val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMXCLK_ENABLE); + + ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label); + +out: + mutex_unlock(&twl->mutex); +} + +static int twl4030_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct twl_pwm_chip *twl = to_twl(chip); + int ret; + u8 val, mask, bits; + + if (pwm->hwpwm == 1) { + mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK; + bits = TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1; + } else { + mask = TWL4030_GPIO6_PWM0_MUTE_MASK; + bits = TWL4030_GPIO6_PWM0_MUTE_PWM0; + } + + mutex_lock(&twl->mutex); + ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG); + if (ret < 0) { + dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label); + goto out; + } + + /* Save the current MUX configuration for the PWM */ + twl->twl4030_pwm_mux &= ~mask; + twl->twl4030_pwm_mux |= (val & mask); + + /* Select PWM functionality */ + val &= ~mask; + val |= bits; + + ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to request PWM\n", pwm->label); + +out: + mutex_unlock(&twl->mutex); + return ret; +} + +static void twl4030_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct twl_pwm_chip *twl = to_twl(chip); + int ret; + u8 val, mask; + + if (pwm->hwpwm == 1) + mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK; + else + mask = TWL4030_GPIO6_PWM0_MUTE_MASK; + + mutex_lock(&twl->mutex); + ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG); + if (ret < 0) { + dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label); + goto out; + } + + /* Restore the MUX configuration for the PWM */ + val &= ~mask; + val |= (twl->twl4030_pwm_mux & mask); + + ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to free PWM\n", pwm->label); + +out: + mutex_unlock(&twl->mutex); +} + +static int twl6030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct twl_pwm_chip *twl = to_twl(chip); + int ret; + u8 val; + + mutex_lock(&twl->mutex); + val = twl->twl6030_toggle3; + val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN); + val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR); + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG); + if (ret < 0) { + dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label); + goto out; + } + + twl->twl6030_toggle3 = val; +out: + mutex_unlock(&twl->mutex); + return ret; +} + +static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct twl_pwm_chip *twl = to_twl(chip); + int ret; + u8 val; + + mutex_lock(&twl->mutex); + val = twl->twl6030_toggle3; + val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR); + val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN); + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG); + if (ret < 0) { + dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label); + goto out; + } + + val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXEN); + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG); + if (ret < 0) { + dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label); + goto out; + } + + val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXEN); + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG); + if (ret < 0) { + dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label); + goto out; + } + + twl->twl6030_toggle3 = val; +out: + mutex_unlock(&twl->mutex); +} + +static const struct pwm_ops twl4030_pwm_ops = { + .config = twl_pwm_config, + .enable = twl4030_pwm_enable, + .disable = twl4030_pwm_disable, + .request = twl4030_pwm_request, + .free = twl4030_pwm_free, + .owner = THIS_MODULE, +}; + +static const struct pwm_ops twl6030_pwm_ops = { + .config = twl_pwm_config, + .enable = twl6030_pwm_enable, + .disable = twl6030_pwm_disable, + .owner = THIS_MODULE, +}; + +static int twl_pwm_probe(struct platform_device *pdev) +{ + struct twl_pwm_chip *twl; + int ret; + + twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL); + if (!twl) + return -ENOMEM; + + if (twl_class_is_4030()) + twl->chip.ops = &twl4030_pwm_ops; + else + twl->chip.ops = &twl6030_pwm_ops; + + twl->chip.dev = &pdev->dev; + twl->chip.base = -1; + twl->chip.npwm = 2; + + mutex_init(&twl->mutex); + + ret = pwmchip_add(&twl->chip); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, twl); + + return 0; +} + +static int twl_pwm_remove(struct platform_device *pdev) +{ + struct twl_pwm_chip *twl = platform_get_drvdata(pdev); + + return pwmchip_remove(&twl->chip); +} + +#ifdef CONFIG_OF +static const struct of_device_id twl_pwm_of_match[] = { + { .compatible = "ti,twl4030-pwm" }, + { .compatible = "ti,twl6030-pwm" }, + { }, +}; +MODULE_DEVICE_TABLE(of, twl_pwm_of_match); +#endif + +static struct platform_driver twl_pwm_driver = { + .driver = { + .name = "twl-pwm", + .of_match_table = of_match_ptr(twl_pwm_of_match), + }, + .probe = twl_pwm_probe, + .remove = twl_pwm_remove, +}; +module_platform_driver(twl_pwm_driver); + +MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); +MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030"); +MODULE_ALIAS("platform:twl-pwm"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-vt8500.c b/drivers/pwm/pwm-vt8500.c new file mode 100644 index 000000000..11d45e56a --- /dev/null +++ b/drivers/pwm/pwm-vt8500.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/pwm/pwm-vt8500.c + * + * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz> + * Copyright (C) 2010 Alexey Charkov <alchark@gmail.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/pwm.h> +#include <linux/delay.h> +#include <linux/clk.h> + +#include <asm/div64.h> + +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_address.h> + +/* + * SoC architecture allocates register space for 4 PWMs but only + * 2 are currently implemented. + */ +#define VT8500_NR_PWMS 2 + +#define REG_CTRL(pwm) (((pwm) << 4) + 0x00) +#define REG_SCALAR(pwm) (((pwm) << 4) + 0x04) +#define REG_PERIOD(pwm) (((pwm) << 4) + 0x08) +#define REG_DUTY(pwm) (((pwm) << 4) + 0x0C) +#define REG_STATUS 0x40 + +#define CTRL_ENABLE BIT(0) +#define CTRL_INVERT BIT(1) +#define CTRL_AUTOLOAD BIT(2) +#define CTRL_STOP_IMM BIT(3) +#define CTRL_LOAD_PRESCALE BIT(4) +#define CTRL_LOAD_PERIOD BIT(5) + +#define STATUS_CTRL_UPDATE BIT(0) +#define STATUS_SCALAR_UPDATE BIT(1) +#define STATUS_PERIOD_UPDATE BIT(2) +#define STATUS_DUTY_UPDATE BIT(3) +#define STATUS_ALL_UPDATE 0x0F + +struct vt8500_chip { + struct pwm_chip chip; + void __iomem *base; + struct clk *clk; +}; + +#define to_vt8500_chip(chip) container_of(chip, struct vt8500_chip, chip) + +#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) +static inline void pwm_busy_wait(struct vt8500_chip *vt8500, int nr, u8 bitmask) +{ + int loops = msecs_to_loops(10); + u32 mask = bitmask << (nr << 8); + + while ((readl(vt8500->base + REG_STATUS) & mask) && --loops) + cpu_relax(); + + if (unlikely(!loops)) + dev_warn(vt8500->chip.dev, "Waiting for status bits 0x%x to clear timed out\n", + mask); +} + +static int vt8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct vt8500_chip *vt8500 = to_vt8500_chip(chip); + unsigned long long c; + unsigned long period_cycles, prescale, pv, dc; + int err; + u32 val; + + err = clk_enable(vt8500->clk); + if (err < 0) { + dev_err(chip->dev, "failed to enable clock\n"); + return err; + } + + c = clk_get_rate(vt8500->clk); + c = c * period_ns; + do_div(c, 1000000000); + period_cycles = c; + + if (period_cycles < 1) + period_cycles = 1; + prescale = (period_cycles - 1) / 4096; + pv = period_cycles / (prescale + 1) - 1; + if (pv > 4095) + pv = 4095; + + if (prescale > 1023) { + clk_disable(vt8500->clk); + return -EINVAL; + } + + c = (unsigned long long)pv * duty_ns; + do_div(c, period_ns); + dc = c; + + writel(prescale, vt8500->base + REG_SCALAR(pwm->hwpwm)); + pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_SCALAR_UPDATE); + + writel(pv, vt8500->base + REG_PERIOD(pwm->hwpwm)); + pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_PERIOD_UPDATE); + + writel(dc, vt8500->base + REG_DUTY(pwm->hwpwm)); + pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_DUTY_UPDATE); + + val = readl(vt8500->base + REG_CTRL(pwm->hwpwm)); + val |= CTRL_AUTOLOAD; + writel(val, vt8500->base + REG_CTRL(pwm->hwpwm)); + pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE); + + clk_disable(vt8500->clk); + return 0; +} + +static int vt8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct vt8500_chip *vt8500 = to_vt8500_chip(chip); + int err; + u32 val; + + err = clk_enable(vt8500->clk); + if (err < 0) { + dev_err(chip->dev, "failed to enable clock\n"); + return err; + } + + val = readl(vt8500->base + REG_CTRL(pwm->hwpwm)); + val |= CTRL_ENABLE; + writel(val, vt8500->base + REG_CTRL(pwm->hwpwm)); + pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE); + + return 0; +} + +static void vt8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct vt8500_chip *vt8500 = to_vt8500_chip(chip); + u32 val; + + val = readl(vt8500->base + REG_CTRL(pwm->hwpwm)); + val &= ~CTRL_ENABLE; + writel(val, vt8500->base + REG_CTRL(pwm->hwpwm)); + pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE); + + clk_disable(vt8500->clk); +} + +static int vt8500_pwm_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct vt8500_chip *vt8500 = to_vt8500_chip(chip); + u32 val; + + val = readl(vt8500->base + REG_CTRL(pwm->hwpwm)); + + if (polarity == PWM_POLARITY_INVERSED) + val |= CTRL_INVERT; + else + val &= ~CTRL_INVERT; + + writel(val, vt8500->base + REG_CTRL(pwm->hwpwm)); + pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE); + + return 0; +} + +static const struct pwm_ops vt8500_pwm_ops = { + .enable = vt8500_pwm_enable, + .disable = vt8500_pwm_disable, + .config = vt8500_pwm_config, + .set_polarity = vt8500_pwm_set_polarity, + .owner = THIS_MODULE, +}; + +static const struct of_device_id vt8500_pwm_dt_ids[] = { + { .compatible = "via,vt8500-pwm", }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vt8500_pwm_dt_ids); + +static int vt8500_pwm_probe(struct platform_device *pdev) +{ + struct vt8500_chip *chip; + struct resource *r; + struct device_node *np = pdev->dev.of_node; + int ret; + + if (!np) { + dev_err(&pdev->dev, "invalid devicetree node\n"); + return -EINVAL; + } + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + chip->chip.dev = &pdev->dev; + chip->chip.ops = &vt8500_pwm_ops; + chip->chip.of_xlate = of_pwm_xlate_with_flags; + chip->chip.of_pwm_n_cells = 3; + chip->chip.base = -1; + chip->chip.npwm = VT8500_NR_PWMS; + + chip->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(chip->clk)) { + dev_err(&pdev->dev, "clock source not specified\n"); + return PTR_ERR(chip->clk); + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + chip->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(chip->base)) + return PTR_ERR(chip->base); + + ret = clk_prepare(chip->clk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to prepare clock\n"); + return ret; + } + + ret = pwmchip_add(&chip->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add PWM chip\n"); + clk_unprepare(chip->clk); + return ret; + } + + platform_set_drvdata(pdev, chip); + return ret; +} + +static int vt8500_pwm_remove(struct platform_device *pdev) +{ + struct vt8500_chip *chip; + + chip = platform_get_drvdata(pdev); + if (chip == NULL) + return -ENODEV; + + clk_unprepare(chip->clk); + + return pwmchip_remove(&chip->chip); +} + +static struct platform_driver vt8500_pwm_driver = { + .probe = vt8500_pwm_probe, + .remove = vt8500_pwm_remove, + .driver = { + .name = "vt8500-pwm", + .of_match_table = vt8500_pwm_dt_ids, + }, +}; +module_platform_driver(vt8500_pwm_driver); + +MODULE_DESCRIPTION("VT8500 PWM Driver"); +MODULE_AUTHOR("Tony Prisk <linux@prisktech.co.nz>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-zx.c b/drivers/pwm/pwm-zx.c new file mode 100644 index 000000000..3763ce531 --- /dev/null +++ b/drivers/pwm/pwm-zx.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2017 Sanechips Technology Co., Ltd. + * Copyright 2017 Linaro Ltd. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +#define ZX_PWM_MODE 0x0 +#define ZX_PWM_CLKDIV_SHIFT 2 +#define ZX_PWM_CLKDIV_MASK GENMASK(11, 2) +#define ZX_PWM_CLKDIV(x) (((x) << ZX_PWM_CLKDIV_SHIFT) & \ + ZX_PWM_CLKDIV_MASK) +#define ZX_PWM_POLAR BIT(1) +#define ZX_PWM_EN BIT(0) +#define ZX_PWM_PERIOD 0x4 +#define ZX_PWM_DUTY 0x8 + +#define ZX_PWM_CLKDIV_MAX 1023 +#define ZX_PWM_PERIOD_MAX 65535 + +struct zx_pwm_chip { + struct pwm_chip chip; + struct clk *pclk; + struct clk *wclk; + void __iomem *base; +}; + +static inline struct zx_pwm_chip *to_zx_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct zx_pwm_chip, chip); +} + +static inline u32 zx_pwm_readl(struct zx_pwm_chip *zpc, unsigned int hwpwm, + unsigned int offset) +{ + return readl(zpc->base + (hwpwm + 1) * 0x10 + offset); +} + +static inline void zx_pwm_writel(struct zx_pwm_chip *zpc, unsigned int hwpwm, + unsigned int offset, u32 value) +{ + writel(value, zpc->base + (hwpwm + 1) * 0x10 + offset); +} + +static void zx_pwm_set_mask(struct zx_pwm_chip *zpc, unsigned int hwpwm, + unsigned int offset, u32 mask, u32 value) +{ + u32 data; + + data = zx_pwm_readl(zpc, hwpwm, offset); + data &= ~mask; + data |= value & mask; + zx_pwm_writel(zpc, hwpwm, offset, data); +} + +static void zx_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct zx_pwm_chip *zpc = to_zx_pwm_chip(chip); + unsigned long rate; + unsigned int div; + u32 value; + u64 tmp; + + value = zx_pwm_readl(zpc, pwm->hwpwm, ZX_PWM_MODE); + + if (value & ZX_PWM_POLAR) + state->polarity = PWM_POLARITY_NORMAL; + else + state->polarity = PWM_POLARITY_INVERSED; + + if (value & ZX_PWM_EN) + state->enabled = true; + else + state->enabled = false; + + div = (value & ZX_PWM_CLKDIV_MASK) >> ZX_PWM_CLKDIV_SHIFT; + rate = clk_get_rate(zpc->wclk); + + tmp = zx_pwm_readl(zpc, pwm->hwpwm, ZX_PWM_PERIOD); + tmp *= div * NSEC_PER_SEC; + state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate); + + tmp = zx_pwm_readl(zpc, pwm->hwpwm, ZX_PWM_DUTY); + tmp *= div * NSEC_PER_SEC; + state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate); +} + +static int zx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + unsigned int duty_ns, unsigned int period_ns) +{ + struct zx_pwm_chip *zpc = to_zx_pwm_chip(chip); + unsigned int period_cycles, duty_cycles; + unsigned long long c; + unsigned int div = 1; + unsigned long rate; + + /* Find out the best divider */ + rate = clk_get_rate(zpc->wclk); + + while (1) { + c = rate / div; + c = c * period_ns; + do_div(c, NSEC_PER_SEC); + + if (c < ZX_PWM_PERIOD_MAX) + break; + + div++; + + if (div > ZX_PWM_CLKDIV_MAX) + return -ERANGE; + } + + /* Calculate duty cycles */ + period_cycles = c; + c *= duty_ns; + do_div(c, period_ns); + duty_cycles = c; + + /* + * If the PWM is being enabled, we have to temporarily disable it + * before configuring the registers. + */ + if (pwm_is_enabled(pwm)) + zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE, ZX_PWM_EN, 0); + + /* Set up registers */ + zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE, ZX_PWM_CLKDIV_MASK, + ZX_PWM_CLKDIV(div)); + zx_pwm_writel(zpc, pwm->hwpwm, ZX_PWM_PERIOD, period_cycles); + zx_pwm_writel(zpc, pwm->hwpwm, ZX_PWM_DUTY, duty_cycles); + + /* Re-enable the PWM if needed */ + if (pwm_is_enabled(pwm)) + zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE, + ZX_PWM_EN, ZX_PWM_EN); + + return 0; +} + +static int zx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct zx_pwm_chip *zpc = to_zx_pwm_chip(chip); + struct pwm_state cstate; + int ret; + + pwm_get_state(pwm, &cstate); + + if (state->polarity != cstate.polarity) + zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE, ZX_PWM_POLAR, + (state->polarity == PWM_POLARITY_INVERSED) ? + 0 : ZX_PWM_POLAR); + + if (state->period != cstate.period || + state->duty_cycle != cstate.duty_cycle) { + ret = zx_pwm_config(chip, pwm, state->duty_cycle, + state->period); + if (ret) + return ret; + } + + if (state->enabled != cstate.enabled) { + if (state->enabled) { + ret = clk_prepare_enable(zpc->wclk); + if (ret) + return ret; + + zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE, + ZX_PWM_EN, ZX_PWM_EN); + } else { + zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE, + ZX_PWM_EN, 0); + clk_disable_unprepare(zpc->wclk); + } + } + + return 0; +} + +static const struct pwm_ops zx_pwm_ops = { + .apply = zx_pwm_apply, + .get_state = zx_pwm_get_state, + .owner = THIS_MODULE, +}; + +static int zx_pwm_probe(struct platform_device *pdev) +{ + struct zx_pwm_chip *zpc; + struct resource *res; + unsigned int i; + int ret; + + zpc = devm_kzalloc(&pdev->dev, sizeof(*zpc), GFP_KERNEL); + if (!zpc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + zpc->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(zpc->base)) + return PTR_ERR(zpc->base); + + zpc->pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(zpc->pclk)) + return PTR_ERR(zpc->pclk); + + zpc->wclk = devm_clk_get(&pdev->dev, "wclk"); + if (IS_ERR(zpc->wclk)) + return PTR_ERR(zpc->wclk); + + ret = clk_prepare_enable(zpc->pclk); + if (ret) + return ret; + + zpc->chip.dev = &pdev->dev; + zpc->chip.ops = &zx_pwm_ops; + zpc->chip.base = -1; + zpc->chip.npwm = 4; + zpc->chip.of_xlate = of_pwm_xlate_with_flags; + zpc->chip.of_pwm_n_cells = 3; + + /* + * PWM devices may be enabled by firmware, and let's disable all of + * them initially to save power. + */ + for (i = 0; i < zpc->chip.npwm; i++) + zx_pwm_set_mask(zpc, i, ZX_PWM_MODE, ZX_PWM_EN, 0); + + ret = pwmchip_add(&zpc->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); + clk_disable_unprepare(zpc->pclk); + return ret; + } + + platform_set_drvdata(pdev, zpc); + + return 0; +} + +static int zx_pwm_remove(struct platform_device *pdev) +{ + struct zx_pwm_chip *zpc = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&zpc->chip); + clk_disable_unprepare(zpc->pclk); + + return ret; +} + +static const struct of_device_id zx_pwm_dt_ids[] = { + { .compatible = "zte,zx296718-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, zx_pwm_dt_ids); + +static struct platform_driver zx_pwm_driver = { + .driver = { + .name = "zx-pwm", + .of_match_table = zx_pwm_dt_ids, + }, + .probe = zx_pwm_probe, + .remove = zx_pwm_remove, +}; +module_platform_driver(zx_pwm_driver); + +MODULE_ALIAS("platform:zx-pwm"); +MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>"); +MODULE_DESCRIPTION("ZTE ZX PWM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c new file mode 100644 index 000000000..b8417a8d2 --- /dev/null +++ b/drivers/pwm/sysfs.c @@ -0,0 +1,546 @@ +// 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 sprintf(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_state(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 sprintf(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_state(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 sprintf(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_state(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 sprintf(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_state(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 sprintf(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 sprintf(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_state(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 __maybe_unused 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 __maybe_unused pwm_class_resume(struct device *parent) +{ + struct pwm_chip *chip = dev_get_drvdata(parent); + + return pwm_class_resume_npwm(parent, chip->npwm); +} + +static SIMPLE_DEV_PM_OPS(pwm_class_pm_ops, pwm_class_suspend, pwm_class_resume); + +static struct class pwm_class = { + .name = "pwm", + .owner = THIS_MODULE, + .dev_groups = pwm_chip_groups, + .pm = &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, chip->dev, MKDEV(0, 0), chip, + "pwmchip%d", chip->base); + if (IS_ERR(parent)) { + dev_warn(chip->dev, + "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); |