summaryrefslogtreecommitdiff
path: root/drivers/pwm
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pwm')
-rw-r--r--drivers/pwm/Kconfig395
-rw-r--r--drivers/pwm/Makefile34
-rw-r--r--drivers/pwm/core.c2717
-rw-r--r--drivers/pwm/pwm-ab8500.c204
-rw-r--r--drivers/pwm/pwm-adp5585.c223
-rw-r--r--drivers/pwm/pwm-airoha.c622
-rw-r--r--drivers/pwm/pwm-apple.c159
-rw-r--r--drivers/pwm/pwm-argon-fan-hat.c109
-rw-r--r--drivers/pwm/pwm-atmel-hlcdc.c100
-rw-r--r--drivers/pwm/pwm-atmel-tcb.c412
-rw-r--r--drivers/pwm/pwm-atmel.c246
-rw-r--r--drivers/pwm/pwm-axi-pwmgen.c341
-rw-r--r--drivers/pwm/pwm-bcm-iproc.c94
-rw-r--r--drivers/pwm/pwm-bcm-kona.c154
-rw-r--r--drivers/pwm/pwm-bcm2835.c199
-rw-r--r--drivers/pwm/pwm-berlin.c222
-rw-r--r--drivers/pwm/pwm-brcmstb.c132
-rw-r--r--drivers/pwm/pwm-clk.c140
-rw-r--r--drivers/pwm/pwm-clps711x.c116
-rw-r--r--drivers/pwm/pwm-crc.c172
-rw-r--r--drivers/pwm/pwm-cros-ec.c257
-rw-r--r--drivers/pwm/pwm-dwc-core.c183
-rw-r--r--drivers/pwm/pwm-dwc.c170
-rw-r--r--drivers/pwm/pwm-dwc.h74
-rw-r--r--drivers/pwm/pwm-ep93xx.c158
-rw-r--r--drivers/pwm/pwm-fsl-ftm.c153
-rw-r--r--drivers/pwm/pwm-gpio.c240
-rw-r--r--drivers/pwm/pwm-hibvt.c92
-rw-r--r--drivers/pwm/pwm-img.c236
-rw-r--r--drivers/pwm/pwm-imx-tpm.c136
-rw-r--r--drivers/pwm/pwm-imx1.c94
-rw-r--r--drivers/pwm/pwm-imx27.c232
-rw-r--r--drivers/pwm/pwm-intel-lgm.c234
-rw-r--r--drivers/pwm/pwm-iqs620a.c154
-rw-r--r--drivers/pwm/pwm-jz4740.c111
-rw-r--r--drivers/pwm/pwm-keembay.c234
-rw-r--r--drivers/pwm/pwm-loongson.c290
-rw-r--r--drivers/pwm/pwm-lp3943.c105
-rw-r--r--drivers/pwm/pwm-lpc18xx-sct.c225
-rw-r--r--drivers/pwm/pwm-lpc32xx.c92
-rw-r--r--drivers/pwm/pwm-lpss-pci.c73
-rw-r--r--drivers/pwm/pwm-lpss-platform.c97
-rw-r--r--drivers/pwm/pwm-lpss.c195
-rw-r--r--drivers/pwm/pwm-lpss.h30
-rw-r--r--drivers/pwm/pwm-max7360.c209
-rw-r--r--drivers/pwm/pwm-mc33xs2410.c407
-rw-r--r--drivers/pwm/pwm-mediatek.c542
-rw-r--r--drivers/pwm/pwm-meson.c610
-rw-r--r--drivers/pwm/pwm-microchip-core.c490
-rw-r--r--drivers/pwm/pwm-mtk-disp.c212
-rw-r--r--drivers/pwm/pwm-mxs.c57
-rw-r--r--drivers/pwm/pwm-ntxec.c165
-rw-r--r--drivers/pwm/pwm-omap-dmtimer.c88
-rw-r--r--drivers/pwm/pwm-pca9685.c654
-rw-r--r--drivers/pwm/pwm-puv3.c150
-rw-r--r--drivers/pwm/pwm-pxa.c152
-rw-r--r--drivers/pwm/pwm-raspberrypi-poe.c196
-rw-r--r--drivers/pwm/pwm-rcar.c66
-rw-r--r--drivers/pwm/pwm-renesas-tpu.c349
-rw-r--r--drivers/pwm/pwm-rockchip.c156
-rw-r--r--drivers/pwm/pwm-rz-mtu3.c552
-rw-r--r--drivers/pwm/pwm-rzg2l-gpt.c456
-rw-r--r--drivers/pwm/pwm-samsung.c292
-rw-r--r--drivers/pwm/pwm-sifive.c233
-rw-r--r--drivers/pwm/pwm-sl28cpld.c263
-rw-r--r--drivers/pwm/pwm-sophgo-sg2042.c301
-rw-r--r--drivers/pwm/pwm-spear.c96
-rw-r--r--drivers/pwm/pwm-sprd.c111
-rw-r--r--drivers/pwm/pwm-sti.c268
-rw-r--r--drivers/pwm/pwm-stm32-lp.c295
-rw-r--r--drivers/pwm/pwm-stm32.c736
-rw-r--r--drivers/pwm/pwm-stmpe.c131
-rw-r--r--drivers/pwm/pwm-sun4i.c220
-rw-r--r--drivers/pwm/pwm-sunplus.c233
-rw-r--r--drivers/pwm/pwm-tegra.c235
-rw-r--r--drivers/pwm/pwm-tiecap.c136
-rw-r--r--drivers/pwm/pwm-tiehrpwm.c280
-rw-r--r--drivers/pwm/pwm-twl-led.c202
-rw-r--r--drivers/pwm/pwm-twl.c130
-rw-r--r--drivers/pwm/pwm-visconti.c176
-rw-r--r--drivers/pwm/pwm-vt8500.c163
-rw-r--r--drivers/pwm/pwm-xilinx.c300
-rw-r--r--drivers/pwm/pwm-zx.c279
-rw-r--r--drivers/pwm/pwm_th1520.rs387
-rw-r--r--drivers/pwm/sysfs.c529
85 files changed, 15395 insertions, 6768 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index cb8d739067d2..bf2d101f67a1 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -29,10 +29,6 @@ menuconfig PWM
if PWM
-config PWM_SYSFS
- bool
- default y if SYSFS
-
config PWM_DEBUG
bool "PWM lowlevel drivers additional checks and debug messages"
depends on DEBUG_KERNEL
@@ -42,6 +38,15 @@ config PWM_DEBUG
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_PROVIDE_GPIO
+ bool "Provide a GPIO chip for each PWM chip"
+ depends on GPIOLIB
+ help
+ Most PWMs can emit both a constant active high and a constant active
+ low signal and so they can be used as GPIO. Say Y here to let each
+ PWM chip provide a GPIO chip and so be easily plugged into consumers
+ that know how to handle GPIOs but not PWMs.
+
config PWM_AB8500
tristate "AB8500 PWM support"
depends on AB8500_CORE && ARCH_U8500
@@ -51,10 +56,48 @@ config PWM_AB8500
To compile this driver as a module, choose M here: the module
will be called pwm-ab8500.
+config PWM_ADP5585
+ tristate "ADP5585 PWM support"
+ depends on MFD_ADP5585
+ help
+ This option enables support for the PWM function found in the Analog
+ Devices ADP5585.
+
+config PWM_AIROHA
+ tristate "Airoha PWM support"
+ depends on ARCH_AIROHA || COMPILE_TEST
+ select REGMAP_MMIO
+ help
+ Generic PWM framework driver for Airoha SoC.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-airoha.
+
+config PWM_APPLE
+ tristate "Apple SoC PWM support"
+ depends on ARCH_APPLE || COMPILE_TEST
+ help
+ Generic PWM framework driver for PWM controller present on
+ Apple SoCs
+
+ Say Y here if you have an ARM Apple laptop, otherwise say N
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-apple.
+
+config PWM_ARGON_FAN_HAT
+ tristate "Argon40 Fan HAT support"
+ depends on I2C && OF
+ help
+ Generic PWM framework driver for Argon40 Fan HAT.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-argon-fan-hat.
+
config PWM_ATMEL
tristate "Atmel PWM support"
- depends on OF
depends on ARCH_AT91 || COMPILE_TEST
+ depends on HAS_IOMEM && OF
help
Generic PWM framework driver for Atmel SoC.
@@ -75,7 +118,8 @@ config PWM_ATMEL_HLCDC_PWM
config PWM_ATMEL_TCB
tristate "Atmel TC Block PWM support"
- depends on ATMEL_TCLIB && OF
+ depends on OF
+ select REGMAP_MMIO
help
Generic PWM framework driver for Atmel Timer Counter Block.
@@ -85,10 +129,33 @@ config PWM_ATMEL_TCB
To compile this driver as a module, choose M here: the module
will be called pwm-atmel-tcb.
+config PWM_AXI_PWMGEN
+ tristate "Analog Devices AXI PWM generator"
+ depends on MICROBLAZE || NIOS2 || ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA || COMPILE_TEST
+ select REGMAP_MMIO
+ help
+ This enables support for the Analog Devices AXI PWM generator.
+
+ This is a configurable PWM generator with variable pulse width and
+ period.
+
+ To compile this driver as a module, choose M here: the module will be
+ called pwm-axi-pwmgen.
+
+config PWM_BCM2835
+ tristate "BCM2835 PWM support"
+ depends on ARCH_BCM2835 || ARCH_BRCMSTB || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ PWM framework driver for BCM2835 controller (Raspberry Pi)
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-bcm2835.
+
config PWM_BCM_IPROC
tristate "iProc PWM support"
depends on ARCH_BCM_IPROC || COMPILE_TEST
- depends on COMMON_CLK
+ depends on COMMON_CLK && HAS_IOMEM
default ARCH_BCM_IPROC
help
Generic PWM framework driver for Broadcom iProc PWM block. This
@@ -108,18 +175,10 @@ config PWM_BCM_KONA
To compile this driver as a module, choose M here: the module
will be called pwm-bcm-kona.
-config PWM_BCM2835
- tristate "BCM2835 PWM support"
- depends on ARCH_BCM2835 || ARCH_BRCMSTB || COMPILE_TEST
- 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
+ depends on HAS_IOMEM
help
PWM framework driver for Marvell Berlin SoCs.
@@ -129,6 +188,7 @@ config PWM_BERLIN
config PWM_BRCMSTB
tristate "Broadcom STB PWM support"
depends on ARCH_BRCMSTB || BMIPS_GENERIC || COMPILE_TEST
+ depends on HAS_IOMEM
help
Generic PWM framework driver for the Broadcom Set-top-Box
SoCs (BCM7xxx).
@@ -136,6 +196,16 @@ config PWM_BRCMSTB
To compile this driver as a module, choose M Here: the module
will be called pwm-brcmstb.c.
+config PWM_CLK
+ tristate "Clock based PWM support"
+ depends on HAVE_CLK || COMPILE_TEST
+ help
+ Generic PWM framework driver for outputs that can be
+ muxed to clocks.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-clk.
+
config PWM_CLPS711X
tristate "CLPS711X PWM support"
depends on ARCH_CLPS711X || COMPILE_TEST
@@ -147,8 +217,8 @@ config PWM_CLPS711X
will be called pwm-clps711x.
config PWM_CRC
- bool "Intel Crystalcove (CRC) PWM support"
- depends on X86 && INTEL_SOC_PMIC
+ tristate "Intel Crystalcove (CRC) PWM support"
+ depends on INTEL_SOC_PMIC
help
Generic PWM framework driver for Crystalcove (CRC) PMIC based PWM
control.
@@ -160,9 +230,29 @@ config PWM_CROS_EC
PWM driver for exposing a PWM attached to the ChromeOS Embedded
Controller.
+config PWM_DWC_CORE
+ tristate
+ depends on HAS_IOMEM
+ help
+ PWM driver for Synopsys DWC PWM Controller.
+
+ To compile this driver as a module, build the dependecies as
+ modules, this will be called pwm-dwc-core.
+
+config PWM_DWC
+ tristate "DesignWare PWM Controller (PCI bus)"
+ depends on HAS_IOMEM && PCI
+ select PWM_DWC_CORE
+ help
+ PWM driver for Synopsys DWC PWM Controller attached to a PCI bus.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-dwc.
+
config PWM_EP93XX
tristate "Cirrus Logic EP93xx PWM support"
depends on ARCH_EP93XX || COMPILE_TEST
+ depends on HAS_IOMEM
help
Generic PWM framework driver for Cirrus Logic EP93xx.
@@ -181,9 +271,21 @@ config PWM_FSL_FTM
To compile this driver as a module, choose M here: the module
will be called pwm-fsl-ftm.
+config PWM_GPIO
+ tristate "GPIO PWM support"
+ depends on GPIOLIB
+ depends on HIGH_RES_TIMERS
+ help
+ Generic PWM framework driver for software PWM toggling a GPIO pin
+ from kernel high-resolution timers.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-gpio.
+
config PWM_HIBVT
tristate "HiSilicon BVT PWM support"
depends on ARCH_HISI || COMPILE_TEST
+ depends on HAS_IOMEM
help
Generic PWM framework driver for HiSilicon BVT SoCs.
@@ -206,6 +308,7 @@ config PWM_IMG
config PWM_IMX1
tristate "i.MX1 PWM support"
depends on ARCH_MXC || COMPILE_TEST
+ depends on HAS_IOMEM
help
Generic PWM framework driver for i.MX1 and i.MX21
@@ -215,6 +318,7 @@ config PWM_IMX1
config PWM_IMX27
tristate "i.MX27 PWM support"
depends on ARCH_MXC || COMPILE_TEST
+ depends on HAS_IOMEM
help
Generic PWM framework driver for i.MX27 and later i.MX SoCs.
@@ -232,6 +336,17 @@ config PWM_IMX_TPM
To compile this driver as a module, choose M here: the module
will be called pwm-imx-tpm.
+config PWM_INTEL_LGM
+ tristate "Intel LGM PWM support"
+ depends on HAS_IOMEM
+ depends on (OF && X86) || COMPILE_TEST
+ select REGMAP_MMIO
+ help
+ Generic PWM fan controller driver for LGM SoC.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-intel-lgm.
+
config PWM_IQS620A
tristate "Azoteq IQS620A PWM support"
depends on MFD_IQS62X || COMPILE_TEST
@@ -244,8 +359,8 @@ config PWM_IQS620A
config PWM_JZ4740
tristate "Ingenic JZ47xx PWM support"
- depends on MIPS
- depends on COMMON_CLK
+ depends on MACH_INGENIC || COMPILE_TEST
+ depends on COMMON_CLK && OF
select MFD_SYSCON
help
Generic PWM framework driver for Ingenic JZ47xx based
@@ -254,6 +369,28 @@ config PWM_JZ4740
To compile this driver as a module, choose M here: the module
will be called pwm-jz4740.
+config PWM_KEEMBAY
+ tristate "Intel Keem Bay PWM driver"
+ depends on ARCH_KEEMBAY || COMPILE_TEST
+ depends on COMMON_CLK && HAS_IOMEM
+ help
+ The platform driver for Intel Keem Bay PWM controller.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-keembay.
+
+config PWM_LOONGSON
+ tristate "Loongson PWM support"
+ depends on MACH_LOONGSON64 || COMPILE_TEST
+ depends on COMMON_CLK
+ help
+ Generic PWM framework driver for Loongson family.
+ It can be found on Loongson-2K series cpus and Loongson LS7A
+ bridge chips.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-loongson.
+
config PWM_LP3943
tristate "TI/National Semiconductor LP3943 PWM support"
depends on MFD_LP3943
@@ -267,6 +404,7 @@ config PWM_LP3943
config PWM_LPC18XX_SCT
tristate "LPC18xx/43xx PWM/SCT support"
depends on ARCH_LPC18XX || COMPILE_TEST
+ depends on HAS_IOMEM
help
Generic PWM framework driver for NXP LPC18xx PWM/SCT which
supports 16 channels.
@@ -279,6 +417,7 @@ config PWM_LPC18XX_SCT
config PWM_LPC32XX
tristate "LPC32XX PWM support"
depends on ARCH_LPC32XX || COMPILE_TEST
+ depends on HAS_IOMEM
help
Generic PWM framework driver for LPC32XX. The LPC32XX SOC has two
PWM controllers.
@@ -287,11 +426,13 @@ config PWM_LPC32XX
will be called pwm-lpc32xx.
config PWM_LPSS
+ depends on HAS_IOMEM
tristate
config PWM_LPSS_PCI
tristate "Intel LPSS PWM PCI driver"
- depends on X86 && PCI
+ depends on X86 || COMPILE_TEST
+ depends on HAS_IOMEM && PCI
select PWM_LPSS
help
The PCI driver for Intel Low Power Subsystem PWM controller.
@@ -301,7 +442,8 @@ config PWM_LPSS_PCI
config PWM_LPSS_PLATFORM
tristate "Intel LPSS PWM platform driver"
- depends on X86 && ACPI
+ depends on (X86 && ACPI) || COMPILE_TEST
+ depends on HAS_IOMEM
select PWM_LPSS
help
The platform driver for Intel Low Power Subsystem PWM controller.
@@ -309,16 +451,59 @@ config PWM_LPSS_PLATFORM
To compile this driver as a module, choose M here: the module
will be called pwm-lpss-platform.
+config PWM_MAX7360
+ tristate "MAX7360 PWMs"
+ depends on MFD_MAX7360
+ help
+ PWM driver for Maxim Integrated MAX7360 multifunction device, with
+ support for up to 8 PWM outputs.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-max7360.
+
+config PWM_MC33XS2410
+ tristate "MC33XS2410 PWM support"
+ depends on OF
+ depends on SPI
+ select AUXILIARY_BUS
+ help
+ NXP MC33XS2410 high-side switch driver. The MC33XS2410 is a four
+ channel high-side switch. The device is operational from 3.0 V
+ to 60 V. The device is controlled by SPI port for configuration.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-mc33xs2410.
+
+config PWM_MEDIATEK
+ tristate "MediaTek PWM support"
+ depends on ARCH_MEDIATEK || RALINK || COMPILE_TEST
+ depends on HAS_IOMEM
+ 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_MESON
tristate "Amlogic Meson PWM driver"
depends on ARCH_MESON || COMPILE_TEST
- depends on COMMON_CLK
+ depends on COMMON_CLK && HAS_IOMEM
help
The platform driver for Amlogic Meson PWM controller.
To compile this driver as a module, choose M here: the module
will be called pwm-meson.
+config PWM_MICROCHIP_CORE
+ tristate "Microchip corePWM PWM support"
+ depends on ARCH_MICROCHIP_POLARFIRE || COMPILE_TEST
+ depends on HAS_IOMEM && OF
+ help
+ PWM driver for Microchip FPGA soft IP core.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-microchip-core.
+
config PWM_MTK_DISP
tristate "MediaTek display PWM driver"
depends on ARCH_MEDIATEK || COMPILE_TEST
@@ -330,19 +515,10 @@ config PWM_MTK_DISP
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
+ depends on HAS_IOMEM && OF
select STMP_DEVICE
help
Generic PWM framework driver for Freescale MXS.
@@ -350,6 +526,14 @@ config PWM_MXS
To compile this driver as a module, choose M here: the module
will be called pwm-mxs.
+config PWM_NTXEC
+ tristate "Netronix embedded controller PWM support"
+ depends on MFD_NTXEC
+ help
+ Say yes here if you want to support the PWM output of the embedded
+ controller found in certain e-book readers designed by the original
+ design manufacturer Netronix.
+
config PWM_OMAP_DMTIMER
tristate "OMAP Dual-Mode Timer PWM support"
depends on OF
@@ -370,25 +554,26 @@ config PWM_PCA9685
To compile this driver as a module, choose M here: the module
will be called pwm-pca9685.
-config PWM_PUV3
- tristate "PKUnity NetBook-0916 PWM support"
- depends on ARCH_PUV3
- help
- Generic PWM framework driver for PKUnity NetBook-0916.
-
- To compile this driver as a module, choose M here: the module
- will be called pwm-puv3.
-
config PWM_PXA
tristate "PXA PWM support"
- depends on ARCH_PXA || COMPILE_TEST
+ depends on ARCH_PXA || ARCH_MMP || ARCH_SPACEMIT || COMPILE_TEST
+ depends on HAS_IOMEM
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
+config PWM_RASPBERRYPI_POE
+ tristate "Raspberry Pi Firmware PoE Hat PWM support"
+ # Make sure not 'y' when RASPBERRYPI_FIRMWARE is 'm'. This can only
+ # happen when COMPILE_TEST=y, hence the added !RASPBERRYPI_FIRMWARE.
+ depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE)
+ help
+ Enable Raspberry Pi firmware controller PWM bus used to control the
+ official RPI PoE hat
+
+config PWM_RENESAS_RCAR
tristate "Renesas R-Car PWM support"
depends on ARCH_RENESAS || COMPILE_TEST
depends on HAS_IOMEM
@@ -399,6 +584,28 @@ config PWM_RCAR
To compile this driver as a module, choose M here: the module
will be called pwm-rcar.
+config PWM_RENESAS_RZG2L_GPT
+ tristate "Renesas RZ/G2L General PWM Timer support"
+ depends on ARCH_RZG2L || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ This driver exposes the General PWM Timer controller found in Renesas
+ RZ/G2L like chips through the PWM API.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-rzg2l-gpt.
+
+config PWM_RENESAS_RZ_MTU3
+ tristate "Renesas RZ/G2L MTU3a PWM Timer support"
+ depends on RZ_MTU3
+ depends on HAS_IOMEM
+ help
+ This driver exposes the MTU3a PWM Timer controller found in Renesas
+ RZ/G2L like chips through the PWM API.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-rz-mtu3.
+
config PWM_RENESAS_TPU
tristate "Renesas TPU PWM support"
depends on ARCH_RENESAS || COMPILE_TEST
@@ -413,15 +620,19 @@ config PWM_RENESAS_TPU
config PWM_ROCKCHIP
tristate "Rockchip PWM support"
depends on ARCH_ROCKCHIP || COMPILE_TEST
+ depends on HAS_IOMEM
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_EXYNOS || COMPILE_TEST
+ depends on PLAT_SAMSUNG || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST
+ depends on HAS_IOMEM
help
- Generic PWM framework driver for Samsung.
+ Generic PWM framework driver for Samsung S3C24xx, S3C64xx, S5Pv210
+ and Exynos SoCs.
+ Choose Y here only if you build for such Samsung SoC.
To compile this driver as a module, choose M here: the module
will be called pwm-samsung.
@@ -429,18 +640,38 @@ config PWM_SAMSUNG
config PWM_SIFIVE
tristate "SiFive PWM support"
depends on OF
- depends on COMMON_CLK
- depends on RISCV || COMPILE_TEST
+ depends on COMMON_CLK && HAS_IOMEM
+ depends on ARCH_SIFIVE || 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_SOPHGO_SG2042
+ tristate "Sophgo SG2042 PWM support"
+ depends on ARCH_SOPHGO || COMPILE_TEST
+ help
+ PWM driver for the PWM controller on Sophgo SG2042 SoC. The PWM
+ controller supports outputing 4 channels of PWM waveforms.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm_sophgo_sg2042.
+
config PWM_SPEAR
tristate "STMicroelectronics SPEAr PWM support"
depends on PLAT_SPEAR || COMPILE_TEST
- depends on OF
+ depends on HAS_IOMEM && OF
help
Generic PWM framework driver for the PWM controller on ST
SPEAr SoCs.
@@ -462,7 +693,7 @@ config PWM_SPRD
config PWM_STI
tristate "STiH4xx PWM support"
depends on ARCH_STI || COMPILE_TEST
- depends on OF
+ depends on HAS_IOMEM && OF
help
Generic PWM framework driver for STiH4xx SoCs.
@@ -489,7 +720,7 @@ config PWM_STM32_LP
will be called pwm-stm32-lp.
config PWM_STMPE
- bool "STMPE expander PWM export"
+ tristate "STMPE expander PWM export"
depends on MFD_STMPE
help
This enables support for the PWMs found in the STMPE I/O
@@ -505,9 +736,21 @@ config PWM_SUN4I
To compile this driver as a module, choose M here: the module
will be called pwm-sun4i.
+config PWM_SUNPLUS
+ tristate "Sunplus PWM support"
+ depends on ARCH_SUNPLUS || COMPILE_TEST
+ depends on HAS_IOMEM && OF
+ help
+ Generic PWM framework driver for the PWM controller on
+ Sunplus SoCs.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-sunplus.
+
config PWM_TEGRA
tristate "NVIDIA Tegra PWM support"
depends on ARCH_TEGRA || COMPILE_TEST
+ depends on HAS_IOMEM
help
Generic PWM framework driver for the PWFM controller found on NVIDIA
Tegra SoCs.
@@ -515,9 +758,21 @@ config PWM_TEGRA
To compile this driver as a module, choose M here: the module
will be called pwm-tegra.
+config PWM_TH1520
+ tristate "TH1520 PWM support"
+ depends on RUST
+ select RUST_PWM_ABSTRACTIONS
+ help
+ This option enables the driver for the PWM controller found on the
+ T-HEAD TH1520 SoC.
+
+ To compile this driver as a module, choose M here; the module
+ will be called pwm-th1520. If you are unsure, say N.
+
config PWM_TIECAP
tristate "ECAP PWM support"
depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST
+ depends on HAS_IOMEM
help
PWM driver support for the ECAP APWM controller found on TI SOCs
@@ -527,6 +782,7 @@ config PWM_TIECAP
config PWM_TIEHRPWM
tristate "EHRPWM PWM support"
depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_K3 || COMPILE_TEST
+ depends on HAS_IOMEM
help
PWM driver support for the EHRPWM controller found on TI SOCs
@@ -551,22 +807,49 @@ config PWM_TWL_LED
To compile this driver as a module, choose M here: the module
will be called pwm-twl-led.
+config PWM_VISCONTI
+ tristate "Toshiba Visconti PWM support"
+ depends on ARCH_VISCONTI || COMPILE_TEST
+ help
+ PWM Subsystem driver support for Toshiba Visconti SoCs.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-visconti.
+
config PWM_VT8500
tristate "vt8500 PWM support"
depends on ARCH_VT8500 || COMPILE_TEST
+ depends on HAS_IOMEM
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
+config PWM_XILINX
+ tristate "Xilinx AXI Timer PWM support"
+ depends on OF_ADDRESS
+ depends on COMMON_CLK
+ select REGMAP_MMIO
help
- Generic PWM framework driver for ZTE ZX family SoCs.
+ PWM driver for Xilinx LogiCORE IP AXI timers. This timer is
+ typically a soft core which may be present in Xilinx FPGAs.
+ This device may also be present in Microblaze soft processors.
+ If you don't have this IP in your design, choose N.
To compile this driver as a module, choose M here: the module
- will be called pwm-zx.
+ will be called pwm-xilinx.
+
+ config RUST_PWM_ABSTRACTIONS
+ bool
+ depends on RUST
+ help
+ This option enables the safe Rust abstraction layer for the PWM
+ subsystem. It provides idiomatic wrappers and traits necessary for
+ writing PWM controller drivers in Rust.
+
+ The abstractions handle resource management (like memory and reference
+ counting) and provide safe interfaces to the underlying C core,
+ allowing driver logic to be written in safe Rust.
endif
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index a59c710e98c7..0dc0d2b69025 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -1,46 +1,65 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_PWM) += core.o
-obj-$(CONFIG_PWM_SYSFS) += sysfs.o
obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
+obj-$(CONFIG_PWM_ADP5585) += pwm-adp5585.o
+obj-$(CONFIG_PWM_AIROHA) += pwm-airoha.o
+obj-$(CONFIG_PWM_APPLE) += pwm-apple.o
+obj-$(CONFIG_PWM_ARGON_FAN_HAT) += pwm-argon-fan-hat.o
obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o
obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o
obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o
+obj-$(CONFIG_PWM_AXI_PWMGEN) += pwm-axi-pwmgen.o
+obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o
obj-$(CONFIG_PWM_BCM_IPROC) += pwm-bcm-iproc.o
obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o
-obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o
obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o
obj-$(CONFIG_PWM_BRCMSTB) += pwm-brcmstb.o
+obj-$(CONFIG_PWM_CLK) += pwm-clk.o
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_DWC_CORE) += pwm-dwc-core.o
+obj-$(CONFIG_PWM_DWC) += pwm-dwc.o
obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o
+obj-$(CONFIG_PWM_GPIO) += pwm-gpio.o
obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o
obj-$(CONFIG_PWM_IMG) += pwm-img.o
obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o
obj-$(CONFIG_PWM_IMX27) += pwm-imx27.o
obj-$(CONFIG_PWM_IMX_TPM) += pwm-imx-tpm.o
+obj-$(CONFIG_PWM_INTEL_LGM) += pwm-intel-lgm.o
obj-$(CONFIG_PWM_IQS620A) += pwm-iqs620a.o
obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o
+obj-$(CONFIG_PWM_KEEMBAY) += pwm-keembay.o
+obj-$(CONFIG_PWM_LOONGSON) += pwm-loongson.o
obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o
obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o
obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o
obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o
obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o
obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o
-obj-$(CONFIG_PWM_MESON) += pwm-meson.o
+obj-$(CONFIG_PWM_MAX7360) += pwm-max7360.o
+obj-$(CONFIG_PWM_MC33XS2410) += pwm-mc33xs2410.o
obj-$(CONFIG_PWM_MEDIATEK) += pwm-mediatek.o
+obj-$(CONFIG_PWM_MESON) += pwm-meson.o
+obj-$(CONFIG_PWM_MICROCHIP_CORE) += pwm-microchip-core.o
obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o
obj-$(CONFIG_PWM_MXS) += pwm-mxs.o
+obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o
obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o
obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o
-obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o
obj-$(CONFIG_PWM_PXA) += pwm-pxa.o
-obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o
+obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o
+obj-$(CONFIG_PWM_RENESAS_RCAR) += pwm-rcar.o
+obj-$(CONFIG_PWM_RENESAS_RZG2L_GPT) += pwm-rzg2l-gpt.o
+obj-$(CONFIG_PWM_RENESAS_RZ_MTU3) += pwm-rz-mtu3.o
obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o
obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
+obj-$(CONFIG_PWM_SL28CPLD) += pwm-sl28cpld.o
+obj-$(CONFIG_PWM_SOPHGO_SG2042) += pwm-sophgo-sg2042.o
obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o
obj-$(CONFIG_PWM_SPRD) += pwm-sprd.o
obj-$(CONFIG_PWM_STI) += pwm-sti.o
@@ -48,10 +67,13 @@ 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_SUNPLUS) += pwm-sunplus.o
obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o
+obj-$(CONFIG_PWM_TH1520) += pwm_th1520.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_VISCONTI) += pwm-visconti.o
obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o
-obj-$(CONFIG_PWM_ZX) += pwm-zx.o
+obj-$(CONFIG_PWM_XILINX) += pwm-xilinx.o
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 004b2ea9b5fd..cd06229db394 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -6,10 +6,13 @@
* Copyright (C) 2011-2012 Avionic Design GmbH
*/
+#define DEFAULT_SYMBOL_NAMESPACE "PWM"
+
#include <linux/acpi.h>
#include <linux/module.h>
+#include <linux/idr.h>
+#include <linux/of.h>
#include <linux/pwm.h>
-#include <linux/radix-tree.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/err.h>
@@ -20,456 +23,470 @@
#include <dt-bindings/pwm/pwm.h>
+#include <uapi/linux/pwm.h>
+
#define CREATE_TRACE_POINTS
#include <trace/events/pwm.h>
-#define MAX_PWMS 1024
+#define PWM_MINOR_COUNT 256
-static DEFINE_MUTEX(pwm_lookup_lock);
-static LIST_HEAD(pwm_lookup_list);
+/* protects access to pwm_chips */
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)
+static DEFINE_IDR(pwm_chips);
+
+static void pwmchip_lock(struct pwm_chip *chip)
{
- return radix_tree_lookup(&pwm_tree, pwm);
+ if (chip->atomic)
+ spin_lock(&chip->atomic_lock);
+ else
+ mutex_lock(&chip->nonatomic_lock);
}
-static int alloc_pwms(int pwm, unsigned int count)
+static void pwmchip_unlock(struct pwm_chip *chip)
{
- unsigned int from = 0;
- unsigned int start;
-
- if (pwm >= MAX_PWMS)
- return -EINVAL;
+ if (chip->atomic)
+ spin_unlock(&chip->atomic_lock);
+ else
+ mutex_unlock(&chip->nonatomic_lock);
+}
- if (pwm >= 0)
- from = pwm;
+DEFINE_GUARD(pwmchip, struct pwm_chip *, pwmchip_lock(_T), pwmchip_unlock(_T))
- start = bitmap_find_next_zero_area(allocated_pwms, MAX_PWMS, from,
- count, 0);
+static bool pwm_wf_valid(const struct pwm_waveform *wf)
+{
+ /*
+ * For now restrict waveforms to period_length_ns <= S64_MAX to provide
+ * some space for future extensions. One possibility is to simplify
+ * representing waveforms with inverted polarity using negative values
+ * somehow.
+ */
+ if (wf->period_length_ns > S64_MAX)
+ return false;
- if (pwm >= 0 && start != pwm)
- return -EEXIST;
+ if (wf->duty_length_ns > wf->period_length_ns)
+ return false;
- if (start + count > MAX_PWMS)
- return -ENOSPC;
+ /*
+ * .duty_offset_ns is supposed to be smaller than .period_length_ns, apart
+ * from the corner case .duty_offset_ns == 0 && .period_length_ns == 0.
+ */
+ if (wf->duty_offset_ns && wf->duty_offset_ns >= wf->period_length_ns)
+ return false;
- return start;
+ return true;
}
-static void free_pwms(struct pwm_chip *chip)
+static void pwm_wf2state(const struct pwm_waveform *wf, struct pwm_state *state)
{
- unsigned int i;
-
- for (i = 0; i < chip->npwm; i++) {
- struct pwm_device *pwm = &chip->pwms[i];
-
- radix_tree_delete(&pwm_tree, pwm->pwm);
+ if (wf->period_length_ns) {
+ if (wf->duty_length_ns + wf->duty_offset_ns < wf->period_length_ns)
+ *state = (struct pwm_state){
+ .enabled = true,
+ .polarity = PWM_POLARITY_NORMAL,
+ .period = wf->period_length_ns,
+ .duty_cycle = wf->duty_length_ns,
+ };
+ else
+ *state = (struct pwm_state){
+ .enabled = true,
+ .polarity = PWM_POLARITY_INVERSED,
+ .period = wf->period_length_ns,
+ .duty_cycle = wf->period_length_ns - wf->duty_length_ns,
+ };
+ } else {
+ *state = (struct pwm_state){
+ .enabled = false,
+ };
}
-
- 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)
+static void pwm_state2wf(const struct pwm_state *state, struct pwm_waveform *wf)
{
- 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;
- }
+ if (state->enabled) {
+ if (state->polarity == PWM_POLARITY_NORMAL)
+ *wf = (struct pwm_waveform){
+ .period_length_ns = state->period,
+ .duty_length_ns = state->duty_cycle,
+ .duty_offset_ns = 0,
+ };
+ else
+ *wf = (struct pwm_waveform){
+ .period_length_ns = state->period,
+ .duty_length_ns = state->period - state->duty_cycle,
+ .duty_offset_ns = state->duty_cycle,
+ };
+ } else {
+ *wf = (struct pwm_waveform){
+ .period_length_ns = 0,
+ };
}
-
- mutex_unlock(&pwm_lock);
-
- return NULL;
}
-static int pwm_device_request(struct pwm_device *pwm, const char *label)
+static int pwmwfcmp(const struct pwm_waveform *a, const struct pwm_waveform *b)
{
- int err;
-
- if (test_bit(PWMF_REQUESTED, &pwm->flags))
- return -EBUSY;
+ if (a->period_length_ns > b->period_length_ns)
+ return 1;
- if (!try_module_get(pwm->chip->ops->owner))
- return -ENODEV;
+ if (a->period_length_ns < b->period_length_ns)
+ return -1;
- if (pwm->chip->ops->request) {
- err = pwm->chip->ops->request(pwm->chip, pwm);
- if (err) {
- module_put(pwm->chip->ops->owner);
- return err;
- }
- }
+ if (a->duty_length_ns > b->duty_length_ns)
+ return 1;
- if (pwm->chip->ops->get_state) {
- pwm->chip->ops->get_state(pwm->chip, pwm, &pwm->state);
- trace_pwm_get(pwm, &pwm->state);
+ if (a->duty_length_ns < b->duty_length_ns)
+ return -1;
- if (IS_ENABLED(CONFIG_PWM_DEBUG))
- pwm->last = pwm->state;
- }
+ if (a->duty_offset_ns > b->duty_offset_ns)
+ return 1;
- set_bit(PWMF_REQUESTED, &pwm->flags);
- pwm->label = label;
+ if (a->duty_offset_ns < b->duty_offset_ns)
+ return -1;
return 0;
}
-struct pwm_device *
-of_pwm_xlate_with_flags(struct pwm_chip *pc, const struct of_phandle_args *args)
+static bool pwm_check_rounding(const struct pwm_waveform *wf,
+ const struct pwm_waveform *wf_rounded)
{
- 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);
+ if (!wf->period_length_ns)
+ return true;
- pwm = pwm_request_from_chip(pc, args->args[0], NULL);
- if (IS_ERR(pwm))
- return pwm;
+ if (wf->period_length_ns < wf_rounded->period_length_ns)
+ return false;
- pwm->args.period = args->args[1];
- pwm->args.polarity = PWM_POLARITY_NORMAL;
+ if (wf->duty_length_ns < wf_rounded->duty_length_ns)
+ return false;
- if (args->args_count > 2 && args->args[2] & PWM_POLARITY_INVERTED)
- pwm->args.polarity = PWM_POLARITY_INVERSED;
+ if (wf->duty_offset_ns < wf_rounded->duty_offset_ns)
+ return false;
- return pwm;
+ return true;
}
-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)
+static int __pwm_round_waveform_tohw(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_waveform *wf, void *wfhw)
{
- struct pwm_device *pwm;
-
- /* sanity check driver support */
- if (pc->of_pwm_n_cells < 2)
- return ERR_PTR(-EINVAL);
+ const struct pwm_ops *ops = chip->ops;
+ int ret;
- /* all cells are required */
- if (args->args_count != pc->of_pwm_n_cells)
- return ERR_PTR(-EINVAL);
+ ret = ops->round_waveform_tohw(chip, pwm, wf, wfhw);
+ trace_pwm_round_waveform_tohw(pwm, wf, wfhw, ret);
- if (args->args[0] >= pc->npwm)
- return ERR_PTR(-EINVAL);
+ return ret;
+}
- pwm = pwm_request_from_chip(pc, args->args[0], NULL);
- if (IS_ERR(pwm))
- return pwm;
+static int __pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
+ const void *wfhw, struct pwm_waveform *wf)
+{
+ const struct pwm_ops *ops = chip->ops;
+ int ret;
- pwm->args.period = args->args[1];
+ ret = ops->round_waveform_fromhw(chip, pwm, wfhw, wf);
+ trace_pwm_round_waveform_fromhw(pwm, wfhw, wf, ret);
- return pwm;
+ return ret;
}
-static void of_pwmchip_add(struct pwm_chip *chip)
+static int __pwm_read_waveform(struct pwm_chip *chip, struct pwm_device *pwm, void *wfhw)
{
- if (!chip->dev || !chip->dev->of_node)
- return;
+ const struct pwm_ops *ops = chip->ops;
+ int ret;
- if (!chip->of_xlate) {
- chip->of_xlate = of_pwm_simple_xlate;
- chip->of_pwm_n_cells = 2;
- }
+ ret = ops->read_waveform(chip, pwm, wfhw);
+ trace_pwm_read_waveform(pwm, wfhw, ret);
- of_node_get(chip->dev->of_node);
+ return ret;
}
-static void of_pwmchip_remove(struct pwm_chip *chip)
+static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, const void *wfhw)
{
- if (chip->dev)
- of_node_put(chip->dev->of_node);
+ const struct pwm_ops *ops = chip->ops;
+ int ret;
+
+ ret = ops->write_waveform(chip, pwm, wfhw);
+ trace_pwm_write_waveform(pwm, wfhw, ret);
+
+ return ret;
}
/**
- * pwm_set_chip_data() - set private chip data for a PWM
+ * pwm_round_waveform_might_sleep - Query hardware capabilities
+ * Cannot be used in atomic context.
* @pwm: PWM device
- * @data: pointer to chip-specific data
+ * @wf: waveform to round and output parameter
*
- * Returns: 0 on success or a negative error code on failure.
+ * Typically a given waveform cannot be implemented exactly by hardware, e.g.
+ * because hardware only supports coarse period resolution or no duty_offset.
+ * This function returns the actually implemented waveform if you pass @wf to
+ * pwm_set_waveform_might_sleep() now.
+ *
+ * Note however that the world doesn't stop turning when you call it, so when
+ * doing::
+ *
+ * pwm_round_waveform_might_sleep(mypwm, &wf);
+ * pwm_set_waveform_might_sleep(mypwm, &wf, true);
+ *
+ * the latter might fail, e.g. because an input clock changed its rate between
+ * these two calls and the waveform determined by
+ * pwm_round_waveform_might_sleep() cannot be implemented any more.
+ *
+ * Usually all values passed in @wf are rounded down to the nearest possible
+ * value (in the order period_length_ns, duty_length_ns and then
+ * duty_offset_ns). Only if this isn't possible, a value might grow. See the
+ * documentation for pwm_set_waveform_might_sleep() for a more formal
+ * description.
+ *
+ * Returns: 0 on success, 1 if at least one value had to be rounded up or a
+ * negative errno.
+ * Context: May sleep.
*/
-int pwm_set_chip_data(struct pwm_device *pwm, void *data)
+int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf)
{
- if (!pwm)
- return -EINVAL;
+ struct pwm_chip *chip = pwm->chip;
+ const struct pwm_ops *ops = chip->ops;
+ struct pwm_waveform wf_req = *wf;
+ char wfhw[PWM_WFHWSIZE];
+ int ret_tohw, ret_fromhw;
- pwm->chip_data = data;
+ BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
- return 0;
-}
-EXPORT_SYMBOL_GPL(pwm_set_chip_data);
+ if (!pwmchip_supports_waveform(chip))
+ return -EOPNOTSUPP;
-/**
- * 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);
+ if (!pwm_wf_valid(wf))
+ return -EINVAL;
-static bool pwm_ops_check(const struct pwm_chip *chip)
-{
+ guard(pwmchip)(chip);
- const struct pwm_ops *ops = chip->ops;
+ if (!chip->operational)
+ return -ENODEV;
- /* 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");
+ ret_tohw = __pwm_round_waveform_tohw(chip, pwm, wf, wfhw);
+ if (ret_tohw < 0)
+ return ret_tohw;
- return true;
- }
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_tohw > 1)
+ dev_err(&chip->dev, "Unexpected return value from __pwm_round_waveform_tohw: requested %llu/%llu [+%llu], return value %d\n",
+ wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, ret_tohw);
- if (!ops->apply)
- return false;
+ ret_fromhw = __pwm_round_waveform_fromhw(chip, pwm, wfhw, wf);
+ if (ret_fromhw < 0)
+ return ret_fromhw;
- if (IS_ENABLED(CONFIG_PWM_DEBUG) && !ops->get_state)
- dev_warn(chip->dev,
- "Please implement the .get_state() callback\n");
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_fromhw > 0)
+ dev_err(&chip->dev, "Unexpected return value from __pwm_round_waveform_fromhw: requested %llu/%llu [+%llu], return value %d\n",
+ wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, ret_fromhw);
- return true;
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) &&
+ (ret_tohw == 0) != pwm_check_rounding(&wf_req, wf))
+ dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu], ret: %d\n",
+ wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns,
+ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, ret_tohw);
+
+ return ret_tohw;
}
+EXPORT_SYMBOL_GPL(pwm_round_waveform_might_sleep);
/**
- * pwmchip_add_with_polarity() - register a new PWM chip
- * @chip: the PWM chip to add
- * @polarity: initial polarity of PWM channels
+ * pwm_get_waveform_might_sleep - Query hardware about current configuration
+ * Cannot be used in atomic context.
+ * @pwm: PWM device
+ * @wf: output parameter
*
- * 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.
+ * Stores the current configuration of the PWM in @wf. Note this is the
+ * equivalent of pwm_get_state_hw() (and not pwm_get_state()) for pwm_waveform.
*
- * Returns: 0 on success or a negative error code on failure.
+ * Returns: 0 on success or a negative errno
+ * Context: May sleep.
*/
-int pwmchip_add_with_polarity(struct pwm_chip *chip,
- enum pwm_polarity polarity)
+int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf)
{
- 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;
- }
+ struct pwm_chip *chip = pwm->chip;
+ const struct pwm_ops *ops = chip->ops;
+ char wfhw[PWM_WFHWSIZE];
+ int err;
- chip->base = ret;
+ BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
- for (i = 0; i < chip->npwm; i++) {
- pwm = &chip->pwms[i];
+ if (!pwmchip_supports_waveform(chip) || !ops->read_waveform)
+ return -EOPNOTSUPP;
- pwm->chip = chip;
- pwm->pwm = chip->base + i;
- pwm->hwpwm = i;
- pwm->state.polarity = polarity;
+ guard(pwmchip)(chip);
- radix_tree_insert(&pwm_tree, pwm->pwm, pwm);
- }
+ if (!chip->operational)
+ return -ENODEV;
- bitmap_set(allocated_pwms, chip->base, chip->npwm);
+ err = __pwm_read_waveform(chip, pwm, &wfhw);
+ if (err)
+ return err;
- INIT_LIST_HEAD(&chip->list);
- list_add(&chip->list, &pwm_chips);
+ return __pwm_round_waveform_fromhw(chip, pwm, &wfhw, wf);
+}
+EXPORT_SYMBOL_GPL(pwm_get_waveform_might_sleep);
- ret = 0;
+/* Called with the pwmchip lock held */
+static int __pwm_set_waveform(struct pwm_device *pwm,
+ const struct pwm_waveform *wf,
+ bool exact)
+{
+ struct pwm_chip *chip = pwm->chip;
+ const struct pwm_ops *ops = chip->ops;
+ char wfhw[PWM_WFHWSIZE];
+ struct pwm_waveform wf_rounded;
+ int err, ret_tohw;
- if (IS_ENABLED(CONFIG_OF))
- of_pwmchip_add(chip);
+ BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
-out:
- mutex_unlock(&pwm_lock);
+ if (!pwmchip_supports_waveform(chip))
+ return -EOPNOTSUPP;
- if (!ret)
- pwmchip_sysfs_export(chip);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(pwmchip_add_with_polarity);
+ if (!pwm_wf_valid(wf))
+ return -EINVAL;
-/**
- * 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);
+ ret_tohw = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw);
+ if (ret_tohw < 0)
+ return ret_tohw;
-/**
- * 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);
+ if ((IS_ENABLED(CONFIG_PWM_DEBUG) || exact) && wf->period_length_ns) {
+ err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded);
+ if (err)
+ return err;
- mutex_lock(&pwm_lock);
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) && (ret_tohw == 0) != pwm_check_rounding(wf, &wf_rounded))
+ dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu], ret: %d\n",
+ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
+ wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns, ret_tohw);
- for (i = 0; i < chip->npwm; i++) {
- struct pwm_device *pwm = &chip->pwms[i];
+ if (exact && pwmwfcmp(wf, &wf_rounded)) {
+ dev_dbg(&chip->dev, "Requested no rounding, but %llu/%llu [+%llu] -> %llu/%llu [+%llu]\n",
+ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
+ wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns);
- if (test_bit(PWMF_REQUESTED, &pwm->flags)) {
- ret = -EBUSY;
- goto out;
+ return 1;
}
}
- list_del_init(&chip->list);
-
- if (IS_ENABLED(CONFIG_OF))
- of_pwmchip_remove(chip);
+ err = __pwm_write_waveform(chip, pwm, &wfhw);
+ if (err)
+ return err;
- free_pwms(chip);
-
-out:
- mutex_unlock(&pwm_lock);
- return ret;
-}
-EXPORT_SYMBOL_GPL(pwmchip_remove);
+ /* update .state */
+ pwm_wf2state(wf, &pwm->state);
-/**
- * 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 (IS_ENABLED(CONFIG_PWM_DEBUG) && ops->read_waveform && wf->period_length_ns) {
+ struct pwm_waveform wf_set;
- if (pwm < 0 || pwm >= MAX_PWMS)
- return ERR_PTR(-EINVAL);
+ err = __pwm_read_waveform(chip, pwm, &wfhw);
+ if (err)
+ /* maybe ignore? */
+ return err;
- mutex_lock(&pwm_lock);
+ err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_set);
+ if (err)
+ /* maybe ignore? */
+ return err;
- dev = pwm_to_device(pwm);
- if (!dev) {
- dev = ERR_PTR(-EPROBE_DEFER);
- goto out;
+ if (pwmwfcmp(&wf_set, &wf_rounded) != 0)
+ dev_err(&chip->dev,
+ "Unexpected setting: requested %llu/%llu [+%llu], expected %llu/%llu [+%llu], set %llu/%llu [+%llu]\n",
+ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
+ wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns,
+ wf_set.duty_length_ns, wf_set.period_length_ns, wf_set.duty_offset_ns);
}
- err = pwm_device_request(dev, label);
- if (err < 0)
- dev = ERR_PTR(err);
-
-out:
- mutex_unlock(&pwm_lock);
-
- return dev;
+ return ret_tohw;
}
-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
+ * pwm_set_waveform_might_sleep - Apply a new waveform
+ * Cannot be used in atomic context.
+ * @pwm: PWM device
+ * @wf: The waveform to apply
+ * @exact: If true no rounding is allowed
*
- * 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.
+ * Typically a requested waveform cannot be implemented exactly, e.g. because
+ * you requested .period_length_ns = 100 ns, but the hardware can only set
+ * periods that are a multiple of 8.5 ns. With that hardware passing @exact =
+ * true results in pwm_set_waveform_might_sleep() failing and returning -EDOM.
+ * If @exact = false you get a period of 93.5 ns (i.e. the biggest period not
+ * bigger than the requested value).
+ * Note that even with @exact = true, some rounding by less than 1 ns is
+ * possible/needed. In the above example requesting .period_length_ns = 94 and
+ * @exact = true, you get the hardware configured with period = 93.5 ns.
+ *
+ * Let C be the set of possible hardware configurations for a given PWM device,
+ * consisting of tuples (p, d, o) where p is the period length, d is the duty
+ * length and o the duty offset.
+ *
+ * The following algorithm is implemented to pick the hardware setting
+ * (p, d, o) ∈ C for a given request (p', d', o') with @exact = false::
+ *
+ * p = max( { ṗ | ∃ ḋ, ȯ : (ṗ, ḋ, ȯ) ∈ C ∧ ṗ ≤ p' } ∪ { min({ ṗ | ∃ ḋ, ȯ : (ṗ, ḋ, ȯ) ∈ C }) })
+ * d = max( { ḋ | ∃ ȯ : (p, ḋ, ȯ) ∈ C ∧ ḋ ≤ d' } ∪ { min({ ḋ | ∃ ȯ : (p, ḋ, ȯ) ∈ C }) })
+ * o = max( { ȯ | (p, d, ȯ) ∈ C ∧ ȯ ≤ o' } ∪ { min({ ȯ | (p, d, ȯ) ∈ C }) })
+ *
+ * In words: The chosen period length is the maximal possible period length not
+ * bigger than the requested period length and if that doesn't exist, the
+ * minimal period length. The chosen duty length is the maximal possible duty
+ * length that is compatible with the chosen period length and isn't bigger than
+ * the requested duty length. Again if such a value doesn't exist, the minimal
+ * duty length compatible with the chosen period is picked. After that the duty
+ * offset compatible with the chosen period and duty length is chosen in the
+ * same way.
+ *
+ * Returns: 0 on success, -EDOM if setting failed due to the exact waveform not
+ * being possible (if @exact), or a different negative errno on failure.
+ * Context: May sleep.
*/
-struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
- unsigned int index,
- const char *label)
+int pwm_set_waveform_might_sleep(struct pwm_device *pwm,
+ const struct pwm_waveform *wf, bool exact)
{
- struct pwm_device *pwm;
+ struct pwm_chip *chip = pwm->chip;
int err;
- if (!chip || index >= chip->npwm)
- return ERR_PTR(-EINVAL);
+ might_sleep();
- mutex_lock(&pwm_lock);
- pwm = &chip->pwms[index];
+ guard(pwmchip)(chip);
- err = pwm_device_request(pwm, label);
- if (err < 0)
- pwm = ERR_PTR(err);
+ if (!chip->operational)
+ return -ENODEV;
- mutex_unlock(&pwm_lock);
- return pwm;
-}
-EXPORT_SYMBOL_GPL(pwm_request_from_chip);
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) && chip->atomic) {
+ /*
+ * Catch any drivers that have been marked as atomic but
+ * that will sleep anyway.
+ */
+ non_block_start();
+ err = __pwm_set_waveform(pwm, wf, exact);
+ non_block_end();
+ } else {
+ err = __pwm_set_waveform(pwm, wf, exact);
+ }
-/**
- * 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);
+ /*
+ * map err == 1 to -EDOM for exact requests and 0 for !exact ones. Also
+ * make sure that -EDOM is only returned in exactly that case. Note that
+ * __pwm_set_waveform() should never return -EDOM which justifies the
+ * unlikely().
+ */
+ if (unlikely(err == -EDOM))
+ err = -EINVAL;
+ else if (exact && err == 1)
+ err = -EDOM;
+ else if (err == 1)
+ err = 0;
+
+ return err;
}
-EXPORT_SYMBOL_GPL(pwm_free);
+EXPORT_SYMBOL_GPL(pwm_set_waveform_might_sleep);
-static void pwm_apply_state_debug(struct pwm_device *pwm,
- const struct pwm_state *state)
+static void pwm_apply_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;
+ struct pwm_state s1 = { 0 }, s2 = { 0 };
int err;
if (!IS_ENABLED(CONFIG_PWM_DEBUG))
@@ -480,208 +497,347 @@ static void pwm_apply_state_debug(struct pwm_device *pwm,
return;
/*
+ * If a disabled PWM was requested the result is unspecified, so nothing
+ * to check.
+ */
+ if (!state->enabled)
+ 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);
+ err = chip->ops->get_state(chip, pwm, &s1);
+ trace_pwm_get(pwm, &s1, err);
+ if (err)
+ /* If that failed there isn't much to debug */
+ return;
+
+ /*
+ * If the PWM was disabled that's maybe strange but there is nothing
+ * that can be sensibly checked then. So return early.
+ */
+ if (!s1.enabled)
+ return;
/*
* 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) {
+ if (s1.polarity != state->polarity) {
s2.polarity = state->polarity;
s2.duty_cycle = s1.period - s1.duty_cycle;
s2.period = s1.period;
- s2.enabled = s1.enabled;
+ s2.enabled = true;
} else {
s2 = s1;
}
if (s2.polarity != state->polarity &&
- state->duty_cycle < state->period)
- dev_warn(chip->dev, ".apply ignored .polarity\n");
+ s2.duty_cycle < s2.period)
+ dev_warn(pwmchip_parent(chip), ".apply ignored .polarity\n");
- if (state->enabled &&
- last->polarity == state->polarity &&
+ if (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: %u, applied: %u, possible: %u)\n",
+ dev_warn(pwmchip_parent(chip),
+ ".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: %u, applied: %u)\n",
+ /*
+ * Rounding period up is fine only if duty_cycle is 0 then, because a
+ * flat line doesn't have a characteristic period.
+ */
+ if (state->period < s2.period && s2.duty_cycle)
+ dev_warn(pwmchip_parent(chip),
+ ".apply is supposed to round down period (requested: %llu, applied: %llu)\n",
state->period, s2.period);
- if (state->enabled &&
- last->polarity == state->polarity &&
+ if (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: %u/%u, applied: %u/%u, possible: %u/%u)\n",
+ dev_warn(pwmchip_parent(chip),
+ ".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: %u/%u, applied: %u/%u)\n",
+ if (state->duty_cycle < s2.duty_cycle)
+ dev_warn(pwmchip_parent(chip),
+ ".apply is supposed to round down duty_cycle (requested: %llu/%llu, applied: %llu/%llu)\n",
state->duty_cycle, state->period,
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);
+ trace_pwm_apply(pwm, &s1, err);
if (err) {
*last = s1;
- dev_err(chip->dev, "failed to reapply current setting\n");
+ dev_err(pwmchip_parent(chip), "failed to reapply current setting\n");
return;
}
- trace_pwm_apply(pwm, &s1);
-
- chip->ops->get_state(chip, pwm, last);
- trace_pwm_get(pwm, last);
+ *last = (struct pwm_state){ 0 };
+ err = chip->ops->get_state(chip, pwm, last);
+ trace_pwm_get(pwm, last, err);
+ if (err)
+ return;
/* 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 %u/%u) -> (ena=%d pol=%d %u/%u)\n",
+ dev_err(pwmchip_parent(chip),
+ ".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)
+static bool pwm_state_valid(const struct pwm_state *state)
+{
+ /*
+ * For a disabled state all other state description is irrelevant and
+ * and supposed to be ignored. So also ignore any strange values and
+ * consider the state ok.
+ */
+ if (!state->enabled)
+ return true;
+
+ if (!state->period)
+ return false;
+
+ if (state->duty_cycle > state->period)
+ return false;
+
+ return true;
+}
+
+static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
{
struct pwm_chip *chip;
+ const struct pwm_ops *ops;
int err;
- if (!pwm || !state || !state->period ||
- state->duty_cycle > state->period)
+ if (!pwm || !state)
+ return -EINVAL;
+
+ if (!pwm_state_valid(state)) {
+ /*
+ * Allow to transition from one invalid state to another.
+ * This ensures that you can e.g. change the polarity while
+ * the period is zero. (This happens on stm32 when the hardware
+ * is in its poweron default state.) This greatly simplifies
+ * working with the sysfs API where you can only change one
+ * parameter at a time.
+ */
+ if (!pwm_state_valid(&pwm->state)) {
+ pwm->state = *state;
+ return 0;
+ }
+
return -EINVAL;
+ }
chip = pwm->chip;
+ ops = chip->ops;
if (state->period == pwm->state.period &&
state->duty_cycle == pwm->state.duty_cycle &&
state->polarity == pwm->state.polarity &&
- state->enabled == pwm->state.enabled)
+ state->enabled == pwm->state.enabled &&
+ state->usage_power == pwm->state.usage_power)
return 0;
- if (chip->ops->apply) {
- err = chip->ops->apply(chip, pwm, state);
- if (err)
- return err;
+ if (pwmchip_supports_waveform(chip)) {
+ struct pwm_waveform wf;
+ char wfhw[PWM_WFHWSIZE];
- trace_pwm_apply(pwm, state);
+ BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
- pwm->state = *state;
+ pwm_state2wf(state, &wf);
/*
- * 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.
+ * The rounding is wrong here for states with inverted polarity.
+ * While .apply() rounds down duty_cycle (which represents the
+ * time from the start of the period to the inner edge),
+ * .round_waveform_tohw() rounds down the time the PWM is high.
+ * Can be fixed if the need arises, until reported otherwise
+ * let's assume that consumers don't care.
*/
- 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;
+ err = __pwm_round_waveform_tohw(chip, pwm, &wf, &wfhw);
+ if (err) {
+ if (err > 0)
+ /*
+ * This signals an invalid request, typically
+ * the requested period (or duty_offset) is
+ * smaller than possible with the hardware.
+ */
+ return -EINVAL;
- pwm->state.polarity = state->polarity;
+ return err;
}
- 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 (IS_ENABLED(CONFIG_PWM_DEBUG)) {
+ struct pwm_waveform wf_rounded;
+
+ err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded);
if (err)
return err;
- pwm->state.duty_cycle = state->duty_cycle;
- pwm->state.period = state->period;
+ if (!pwm_check_rounding(&wf, &wf_rounded))
+ dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n",
+ wf.duty_length_ns, wf.period_length_ns, wf.duty_offset_ns,
+ wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns);
}
- 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);
- }
+ err = __pwm_write_waveform(chip, pwm, &wfhw);
+ if (err)
+ return err;
- pwm->state.enabled = state->enabled;
- }
+ pwm->state = *state;
+
+ } else {
+ err = ops->apply(chip, pwm, state);
+ trace_pwm_apply(pwm, state, err);
+ if (err)
+ return err;
+
+ pwm->state = *state;
+
+ /*
+ * only do this after pwm->state was applied as some
+ * implementations of .get_state() depend on this
+ */
+ pwm_apply_debug(pwm, state);
}
return 0;
}
-EXPORT_SYMBOL_GPL(pwm_apply_state);
/**
- * pwm_capture() - capture and report a PWM signal
+ * pwm_apply_might_sleep() - atomically apply a new state to a PWM device
+ * Cannot be used in atomic context.
* @pwm: PWM device
- * @result: structure to fill with capture result
- * @timeout: time to wait, in milliseconds, before giving up on capture
+ * @state: new state to apply
*
- * Returns: 0 on success or a negative error code on failure.
+ * Returns: 0 on success, or a negative errno
+ * Context: May sleep.
*/
-int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result,
- unsigned long timeout)
+int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
{
int err;
+ struct pwm_chip *chip = pwm->chip;
- if (!pwm || !pwm->chip->ops)
- return -EINVAL;
+ /*
+ * Some lowlevel driver's implementations of .apply() make use of
+ * mutexes, also with some drivers only returning when the new
+ * configuration is active calling pwm_apply_might_sleep() from atomic context
+ * is a bad idea. So make it explicit that calling this function might
+ * sleep.
+ */
+ might_sleep();
- if (!pwm->chip->ops->capture)
- return -ENOSYS;
+ guard(pwmchip)(chip);
- mutex_lock(&pwm_lock);
- err = pwm->chip->ops->capture(pwm->chip, pwm, result, timeout);
- mutex_unlock(&pwm_lock);
+ if (!chip->operational)
+ return -ENODEV;
+
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) && chip->atomic) {
+ /*
+ * Catch any drivers that have been marked as atomic but
+ * that will sleep anyway.
+ */
+ non_block_start();
+ err = __pwm_apply(pwm, state);
+ non_block_end();
+ } else {
+ err = __pwm_apply(pwm, state);
+ }
return err;
}
-EXPORT_SYMBOL_GPL(pwm_capture);
+EXPORT_SYMBOL_GPL(pwm_apply_might_sleep);
+
+/**
+ * pwm_apply_atomic() - apply a new state to a PWM device from atomic context
+ * Not all PWM devices support this function, check with pwm_might_sleep().
+ * @pwm: PWM device
+ * @state: new state to apply
+ *
+ * Returns: 0 on success, or a negative errno
+ * Context: Any
+ */
+int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state)
+{
+ struct pwm_chip *chip = pwm->chip;
+
+ WARN_ONCE(!chip->atomic,
+ "sleeping PWM driver used in atomic context\n");
+
+ guard(pwmchip)(chip);
+
+ if (!chip->operational)
+ return -ENODEV;
+
+ return __pwm_apply(pwm, state);
+}
+EXPORT_SYMBOL_GPL(pwm_apply_atomic);
+
+/**
+ * pwm_get_state_hw() - get the current PWM state from hardware
+ * @pwm: PWM device
+ * @state: state to fill with the current PWM state
+ *
+ * Similar to pwm_get_state() but reads the current PWM state from hardware
+ * instead of the requested state.
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ * Context: May sleep.
+ */
+int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
+{
+ struct pwm_chip *chip = pwm->chip;
+ const struct pwm_ops *ops = chip->ops;
+ int ret = -EOPNOTSUPP;
+
+ might_sleep();
+
+ guard(pwmchip)(chip);
+
+ if (!chip->operational)
+ return -ENODEV;
+
+ if (pwmchip_supports_waveform(chip) && ops->read_waveform) {
+ char wfhw[PWM_WFHWSIZE];
+ struct pwm_waveform wf;
+
+ BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
+
+ ret = __pwm_read_waveform(chip, pwm, &wfhw);
+ if (ret)
+ return ret;
+
+ ret = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf);
+ if (ret)
+ return ret;
+
+ pwm_wf2state(&wf, state);
+
+ } else if (ops->get_state) {
+ ret = ops->get_state(chip, pwm, state);
+ trace_pwm_get(pwm, state, ret);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pwm_get_state_hw);
/**
* pwm_adjust_config() - adjust the current PWM config to the PWM arguments
@@ -690,6 +846,9 @@ EXPORT_SYMBOL_GPL(pwm_capture);
* This function will adjust the PWM config to the PWM arguments provided
* by the DT or PWM lookup table. This is particularly useful to adapt
* the bootloader config to the Linux one.
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ * Context: May sleep.
*/
int pwm_adjust_config(struct pwm_device *pwm)
{
@@ -712,7 +871,7 @@ int pwm_adjust_config(struct pwm_device *pwm)
state.period = pargs.period;
state.polarity = pargs.polarity;
- return pwm_apply_state(pwm, &state);
+ return pwm_apply_might_sleep(pwm, &state);
}
/*
@@ -735,25 +894,829 @@ int pwm_adjust_config(struct pwm_device *pwm)
state.duty_cycle = state.period - state.duty_cycle;
}
- return pwm_apply_state(pwm, &state);
+ return pwm_apply_might_sleep(pwm, &state);
}
EXPORT_SYMBOL_GPL(pwm_adjust_config);
-static struct pwm_chip *of_node_to_pwmchip(struct device_node *np)
+/**
+ * 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.
+ */
+static int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result,
+ unsigned long timeout)
+{
+ struct pwm_chip *chip = pwm->chip;
+ const struct pwm_ops *ops = chip->ops;
+
+ if (!ops->capture)
+ return -ENOSYS;
+
+ /*
+ * Holding the pwm_lock is probably not needed. If you use pwm_capture()
+ * and you're interested to speed it up, please convince yourself it's
+ * really not needed, test and then suggest a patch on the mailing list.
+ */
+ guard(mutex)(&pwm_lock);
+
+ guard(pwmchip)(chip);
+
+ if (!chip->operational)
+ return -ENODEV;
+
+ return ops->capture(chip, pwm, result, timeout);
+}
+
+static struct pwm_chip *pwmchip_find_by_name(const char *name)
{
struct pwm_chip *chip;
+ unsigned long id, tmp;
- mutex_lock(&pwm_lock);
+ if (!name)
+ return NULL;
+
+ guard(mutex)(&pwm_lock);
- list_for_each_entry(chip, &pwm_chips, list)
- if (chip->dev && chip->dev->of_node == np) {
- mutex_unlock(&pwm_lock);
+ idr_for_each_entry_ul(&pwm_chips, chip, tmp, id) {
+ if (device_match_name(pwmchip_parent(chip), name))
return chip;
+ }
+
+ return NULL;
+}
+
+static int pwm_device_request(struct pwm_device *pwm, const char *label)
+{
+ int err;
+ struct pwm_chip *chip = pwm->chip;
+ const struct pwm_ops *ops = chip->ops;
+
+ if (test_bit(PWMF_REQUESTED, &pwm->flags))
+ return -EBUSY;
+
+ /*
+ * This function is called while holding pwm_lock. As .operational only
+ * changes while holding this lock, checking it here without holding the
+ * chip lock is fine.
+ */
+ if (!chip->operational)
+ return -ENODEV;
+
+ if (!try_module_get(chip->owner))
+ return -ENODEV;
+
+ if (!get_device(&chip->dev)) {
+ err = -ENODEV;
+ goto err_get_device;
+ }
+
+ if (ops->request) {
+ err = ops->request(chip, pwm);
+ if (err) {
+ put_device(&chip->dev);
+err_get_device:
+ module_put(chip->owner);
+ return err;
}
+ }
- mutex_unlock(&pwm_lock);
+ if (ops->read_waveform || ops->get_state) {
+ /*
+ * Zero-initialize state because most drivers are unaware of
+ * .usage_power. The other members of state are supposed to be
+ * set by lowlevel drivers. We still initialize the whole
+ * structure for simplicity even though this might paper over
+ * faulty implementations of .get_state().
+ */
+ struct pwm_state state = { 0, };
- return ERR_PTR(-EPROBE_DEFER);
+ err = pwm_get_state_hw(pwm, &state);
+ if (!err)
+ pwm->state = state;
+
+ if (IS_ENABLED(CONFIG_PWM_DEBUG))
+ pwm->last = pwm->state;
+ }
+
+ set_bit(PWMF_REQUESTED, &pwm->flags);
+ pwm->label = label;
+
+ return 0;
+}
+
+/**
+ * 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.
+ */
+static 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);
+
+ guard(mutex)(&pwm_lock);
+
+ pwm = &chip->pwms[index];
+
+ err = pwm_device_request(pwm, label);
+ if (err < 0)
+ return ERR_PTR(err);
+
+ return pwm;
+}
+
+struct pwm_device *
+of_pwm_xlate_with_flags(struct pwm_chip *chip, const struct of_phandle_args *args)
+{
+ struct pwm_device *pwm;
+
+ /* period in the second cell and flags in the third cell are optional */
+ if (args->args_count < 1)
+ return ERR_PTR(-EINVAL);
+
+ pwm = pwm_request_from_chip(chip, args->args[0], NULL);
+ if (IS_ERR(pwm))
+ return pwm;
+
+ if (args->args_count > 1)
+ 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);
+
+/*
+ * This callback is used for PXA PWM chips that only have a single PWM line.
+ * For such chips you could argue that passing the line number (i.e. the first
+ * parameter in the common case) is useless as it's always zero. So compared to
+ * the default xlate function of_pwm_xlate_with_flags() the first parameter is
+ * the default period and the second are flags.
+ *
+ * Note that if #pwm-cells = <3>, the semantic is the same as for
+ * of_pwm_xlate_with_flags() to allow converting the affected driver to
+ * #pwm-cells = <3> without breaking the legacy binding.
+ *
+ * Don't use for new drivers.
+ */
+struct pwm_device *
+of_pwm_single_xlate(struct pwm_chip *chip, const struct of_phandle_args *args)
+{
+ struct pwm_device *pwm;
+
+ if (args->args_count >= 3)
+ return of_pwm_xlate_with_flags(chip, args);
+
+ pwm = pwm_request_from_chip(chip, 0, NULL);
+ if (IS_ERR(pwm))
+ return pwm;
+
+ if (args->args_count > 0)
+ pwm->args.period = args->args[0];
+
+ pwm->args.polarity = PWM_POLARITY_NORMAL;
+ if (args->args_count > 1 && args->args[1] & PWM_POLARITY_INVERTED)
+ pwm->args.polarity = PWM_POLARITY_INVERSED;
+
+ return pwm;
+}
+EXPORT_SYMBOL_GPL(of_pwm_single_xlate);
+
+struct pwm_export {
+ struct device pwm_dev;
+ struct pwm_device *pwm;
+ struct mutex lock;
+ struct pwm_state suspend;
+};
+
+static inline struct pwm_chip *pwmchip_from_dev(struct device *pwmchip_dev)
+{
+ return container_of(pwmchip_dev, struct pwm_chip, dev);
+}
+
+static inline struct pwm_export *pwmexport_from_dev(struct device *pwm_dev)
+{
+ return container_of(pwm_dev, struct pwm_export, pwm_dev);
+}
+
+static inline struct pwm_device *pwm_from_dev(struct device *pwm_dev)
+{
+ struct pwm_export *export = pwmexport_from_dev(pwm_dev);
+
+ return export->pwm;
+}
+
+static ssize_t period_show(struct device *pwm_dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ const struct pwm_device *pwm = pwm_from_dev(pwm_dev);
+ struct pwm_state state;
+
+ pwm_get_state(pwm, &state);
+
+ return sysfs_emit(buf, "%llu\n", state.period);
+}
+
+static ssize_t period_store(struct device *pwm_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pwm_export *export = pwmexport_from_dev(pwm_dev);
+ struct pwm_device *pwm = export->pwm;
+ struct pwm_state state;
+ u64 val;
+ int ret;
+
+ ret = kstrtou64(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&export->lock);
+
+ pwm_get_state(pwm, &state);
+ state.period = val;
+ ret = pwm_apply_might_sleep(pwm, &state);
+
+ return ret ? : size;
+}
+
+static ssize_t duty_cycle_show(struct device *pwm_dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ const struct pwm_device *pwm = pwm_from_dev(pwm_dev);
+ struct pwm_state state;
+
+ pwm_get_state(pwm, &state);
+
+ return sysfs_emit(buf, "%llu\n", state.duty_cycle);
+}
+
+static ssize_t duty_cycle_store(struct device *pwm_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pwm_export *export = pwmexport_from_dev(pwm_dev);
+ struct pwm_device *pwm = export->pwm;
+ struct pwm_state state;
+ u64 val;
+ int ret;
+
+ ret = kstrtou64(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&export->lock);
+
+ pwm_get_state(pwm, &state);
+ state.duty_cycle = val;
+ ret = pwm_apply_might_sleep(pwm, &state);
+
+ return ret ? : size;
+}
+
+static ssize_t enable_show(struct device *pwm_dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ const struct pwm_device *pwm = pwm_from_dev(pwm_dev);
+ struct pwm_state state;
+
+ pwm_get_state(pwm, &state);
+
+ return sysfs_emit(buf, "%d\n", state.enabled);
+}
+
+static ssize_t enable_store(struct device *pwm_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pwm_export *export = pwmexport_from_dev(pwm_dev);
+ struct pwm_device *pwm = export->pwm;
+ struct pwm_state state;
+ int val, ret;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&export->lock);
+
+ pwm_get_state(pwm, &state);
+
+ switch (val) {
+ case 0:
+ state.enabled = false;
+ break;
+ case 1:
+ state.enabled = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = pwm_apply_might_sleep(pwm, &state);
+
+ return ret ? : size;
+}
+
+static ssize_t polarity_show(struct device *pwm_dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ const struct pwm_device *pwm = pwm_from_dev(pwm_dev);
+ const char *polarity = "unknown";
+ struct pwm_state state;
+
+ pwm_get_state(pwm, &state);
+
+ switch (state.polarity) {
+ case PWM_POLARITY_NORMAL:
+ polarity = "normal";
+ break;
+
+ case PWM_POLARITY_INVERSED:
+ polarity = "inversed";
+ break;
+ }
+
+ return sysfs_emit(buf, "%s\n", polarity);
+}
+
+static ssize_t polarity_store(struct device *pwm_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pwm_export *export = pwmexport_from_dev(pwm_dev);
+ struct pwm_device *pwm = export->pwm;
+ 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;
+
+ guard(mutex)(&export->lock);
+
+ pwm_get_state(pwm, &state);
+ state.polarity = polarity;
+ ret = pwm_apply_might_sleep(pwm, &state);
+
+ return ret ? : size;
+}
+
+static ssize_t capture_show(struct device *pwm_dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_device *pwm = pwm_from_dev(pwm_dev);
+ struct pwm_capture result;
+ int ret;
+
+ ret = pwm_capture(pwm, &result, jiffies_to_msecs(HZ));
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u %u\n", result.period, result.duty_cycle);
+}
+
+static DEVICE_ATTR_RW(period);
+static DEVICE_ATTR_RW(duty_cycle);
+static DEVICE_ATTR_RW(enable);
+static DEVICE_ATTR_RW(polarity);
+static DEVICE_ATTR_RO(capture);
+
+static struct attribute *pwm_attrs[] = {
+ &dev_attr_period.attr,
+ &dev_attr_duty_cycle.attr,
+ &dev_attr_enable.attr,
+ &dev_attr_polarity.attr,
+ &dev_attr_capture.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(pwm);
+
+static void pwm_export_release(struct device *pwm_dev)
+{
+ struct pwm_export *export = pwmexport_from_dev(pwm_dev);
+
+ kfree(export);
+}
+
+static int pwm_export_child(struct device *pwmchip_dev, struct pwm_device *pwm)
+{
+ struct pwm_export *export;
+ char *pwm_prop[2];
+ int ret;
+
+ if (test_and_set_bit(PWMF_EXPORTED, &pwm->flags))
+ return -EBUSY;
+
+ export = kzalloc(sizeof(*export), GFP_KERNEL);
+ if (!export) {
+ clear_bit(PWMF_EXPORTED, &pwm->flags);
+ return -ENOMEM;
+ }
+
+ export->pwm = pwm;
+ mutex_init(&export->lock);
+
+ export->pwm_dev.release = pwm_export_release;
+ export->pwm_dev.parent = pwmchip_dev;
+ export->pwm_dev.devt = MKDEV(0, 0);
+ export->pwm_dev.groups = pwm_groups;
+ dev_set_name(&export->pwm_dev, "pwm%u", pwm->hwpwm);
+
+ ret = device_register(&export->pwm_dev);
+ if (ret) {
+ clear_bit(PWMF_EXPORTED, &pwm->flags);
+ put_device(&export->pwm_dev);
+ export = NULL;
+ return ret;
+ }
+ pwm_prop[0] = kasprintf(GFP_KERNEL, "EXPORT=pwm%u", pwm->hwpwm);
+ pwm_prop[1] = NULL;
+ kobject_uevent_env(&pwmchip_dev->kobj, KOBJ_CHANGE, pwm_prop);
+ kfree(pwm_prop[0]);
+
+ return 0;
+}
+
+static int pwm_unexport_match(struct device *pwm_dev, const void *data)
+{
+ return pwm_from_dev(pwm_dev) == data;
+}
+
+static int pwm_unexport_child(struct device *pwmchip_dev, struct pwm_device *pwm)
+{
+ struct device *pwm_dev;
+ char *pwm_prop[2];
+
+ if (!test_and_clear_bit(PWMF_EXPORTED, &pwm->flags))
+ return -ENODEV;
+
+ pwm_dev = device_find_child(pwmchip_dev, pwm, pwm_unexport_match);
+ if (!pwm_dev)
+ return -ENODEV;
+
+ pwm_prop[0] = kasprintf(GFP_KERNEL, "UNEXPORT=pwm%u", pwm->hwpwm);
+ pwm_prop[1] = NULL;
+ kobject_uevent_env(&pwmchip_dev->kobj, KOBJ_CHANGE, pwm_prop);
+ kfree(pwm_prop[0]);
+
+ /* for device_find_child() */
+ put_device(pwm_dev);
+ device_unregister(pwm_dev);
+ pwm_put(pwm);
+
+ return 0;
+}
+
+static ssize_t export_store(struct device *pwmchip_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
+ struct pwm_device *pwm;
+ unsigned int hwpwm;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &hwpwm);
+ if (ret < 0)
+ return ret;
+
+ if (hwpwm >= chip->npwm)
+ return -ENODEV;
+
+ pwm = pwm_request_from_chip(chip, hwpwm, "sysfs");
+ if (IS_ERR(pwm))
+ return PTR_ERR(pwm);
+
+ ret = pwm_export_child(pwmchip_dev, pwm);
+ if (ret < 0)
+ pwm_put(pwm);
+
+ return ret ? : len;
+}
+static DEVICE_ATTR_WO(export);
+
+static ssize_t unexport_store(struct device *pwmchip_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
+ unsigned int hwpwm;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &hwpwm);
+ if (ret < 0)
+ return ret;
+
+ if (hwpwm >= chip->npwm)
+ return -ENODEV;
+
+ ret = pwm_unexport_child(pwmchip_dev, &chip->pwms[hwpwm]);
+
+ return ret ? : len;
+}
+static DEVICE_ATTR_WO(unexport);
+
+static ssize_t npwm_show(struct device *pwmchip_dev, struct device_attribute *attr,
+ char *buf)
+{
+ const struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
+
+ return sysfs_emit(buf, "%u\n", chip->npwm);
+}
+static DEVICE_ATTR_RO(npwm);
+
+static struct attribute *pwm_chip_attrs[] = {
+ &dev_attr_export.attr,
+ &dev_attr_unexport.attr,
+ &dev_attr_npwm.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(pwm_chip);
+
+/* takes export->lock on success */
+static struct pwm_export *pwm_class_get_state(struct device *pwmchip_dev,
+ struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct device *pwm_dev;
+ struct pwm_export *export;
+
+ if (!test_bit(PWMF_EXPORTED, &pwm->flags))
+ return NULL;
+
+ pwm_dev = device_find_child(pwmchip_dev, pwm, pwm_unexport_match);
+ if (!pwm_dev)
+ return NULL;
+
+ export = pwmexport_from_dev(pwm_dev);
+ put_device(pwm_dev); /* for device_find_child() */
+
+ mutex_lock(&export->lock);
+ pwm_get_state(pwm, state);
+
+ return export;
+}
+
+static int pwm_class_apply_state(struct pwm_export *export,
+ struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ int ret = pwm_apply_might_sleep(pwm, state);
+
+ /* release lock taken in pwm_class_get_state */
+ mutex_unlock(&export->lock);
+
+ return ret;
+}
+
+static int pwm_class_resume_npwm(struct device *pwmchip_dev, unsigned int npwm)
+{
+ struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
+ unsigned int i;
+ int ret = 0;
+
+ for (i = 0; i < npwm; i++) {
+ struct pwm_device *pwm = &chip->pwms[i];
+ struct pwm_state state;
+ struct pwm_export *export;
+
+ export = pwm_class_get_state(pwmchip_dev, pwm, &state);
+ if (!export)
+ continue;
+
+ /* If pwmchip was not enabled before suspend, do nothing. */
+ if (!export->suspend.enabled) {
+ /* release lock taken in pwm_class_get_state */
+ mutex_unlock(&export->lock);
+ continue;
+ }
+
+ state.enabled = export->suspend.enabled;
+ ret = pwm_class_apply_state(export, pwm, &state);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+static int pwm_class_suspend(struct device *pwmchip_dev)
+{
+ struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
+ unsigned int i;
+ int ret = 0;
+
+ for (i = 0; i < chip->npwm; i++) {
+ struct pwm_device *pwm = &chip->pwms[i];
+ struct pwm_state state;
+ struct pwm_export *export;
+
+ export = pwm_class_get_state(pwmchip_dev, pwm, &state);
+ if (!export)
+ continue;
+
+ /*
+ * If pwmchip was not enabled before suspend, save
+ * state for resume time and do nothing else.
+ */
+ export->suspend = state;
+ if (!state.enabled) {
+ /* release lock taken in pwm_class_get_state */
+ mutex_unlock(&export->lock);
+ continue;
+ }
+
+ state.enabled = false;
+ ret = pwm_class_apply_state(export, pwm, &state);
+ if (ret < 0) {
+ /*
+ * roll back the PWM devices that were disabled by
+ * this suspend function.
+ */
+ pwm_class_resume_npwm(pwmchip_dev, i);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int pwm_class_resume(struct device *pwmchip_dev)
+{
+ struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
+
+ return pwm_class_resume_npwm(pwmchip_dev, chip->npwm);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(pwm_class_pm_ops, pwm_class_suspend, pwm_class_resume);
+
+static struct class pwm_class = {
+ .name = "pwm",
+ .dev_groups = pwm_chip_groups,
+ .pm = pm_sleep_ptr(&pwm_class_pm_ops),
+};
+
+static void pwmchip_sysfs_unexport(struct pwm_chip *chip)
+{
+ unsigned int i;
+
+ for (i = 0; i < chip->npwm; i++) {
+ struct pwm_device *pwm = &chip->pwms[i];
+
+ if (test_bit(PWMF_EXPORTED, &pwm->flags))
+ pwm_unexport_child(&chip->dev, pwm);
+ }
+}
+
+#define PWMCHIP_ALIGN ARCH_DMA_MINALIGN
+
+static void *pwmchip_priv(struct pwm_chip *chip)
+{
+ return (void *)chip + ALIGN(struct_size(chip, pwms, chip->npwm), PWMCHIP_ALIGN);
+}
+
+/* This is the counterpart to pwmchip_alloc() */
+void pwmchip_put(struct pwm_chip *chip)
+{
+ put_device(&chip->dev);
+}
+EXPORT_SYMBOL_GPL(pwmchip_put);
+
+void pwmchip_release(struct device *pwmchip_dev)
+{
+ struct pwm_chip *chip = pwmchip_from_dev(pwmchip_dev);
+
+ kfree(chip);
+}
+EXPORT_SYMBOL_GPL(pwmchip_release);
+
+struct pwm_chip *pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv)
+{
+ struct pwm_chip *chip;
+ struct device *pwmchip_dev;
+ size_t alloc_size;
+ unsigned int i;
+
+ alloc_size = size_add(ALIGN(struct_size(chip, pwms, npwm), PWMCHIP_ALIGN),
+ sizeof_priv);
+
+ chip = kzalloc(alloc_size, GFP_KERNEL);
+ if (!chip)
+ return ERR_PTR(-ENOMEM);
+
+ chip->npwm = npwm;
+ chip->uses_pwmchip_alloc = true;
+ chip->operational = false;
+
+ pwmchip_dev = &chip->dev;
+ device_initialize(pwmchip_dev);
+ pwmchip_dev->class = &pwm_class;
+ pwmchip_dev->parent = parent;
+ pwmchip_dev->release = pwmchip_release;
+
+ pwmchip_set_drvdata(chip, pwmchip_priv(chip));
+
+ for (i = 0; i < chip->npwm; i++) {
+ struct pwm_device *pwm = &chip->pwms[i];
+ pwm->chip = chip;
+ pwm->hwpwm = i;
+ }
+
+ return chip;
+}
+EXPORT_SYMBOL_GPL(pwmchip_alloc);
+
+static void devm_pwmchip_put(void *data)
+{
+ struct pwm_chip *chip = data;
+
+ pwmchip_put(chip);
+}
+
+struct pwm_chip *devm_pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv)
+{
+ struct pwm_chip *chip;
+ int ret;
+
+ chip = pwmchip_alloc(parent, npwm, sizeof_priv);
+ if (IS_ERR(chip))
+ return chip;
+
+ ret = devm_add_action_or_reset(parent, devm_pwmchip_put, chip);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return chip;
+}
+EXPORT_SYMBOL_GPL(devm_pwmchip_alloc);
+
+static void of_pwmchip_add(struct pwm_chip *chip)
+{
+ if (!pwmchip_parent(chip) || !pwmchip_parent(chip)->of_node)
+ return;
+
+ if (!chip->of_xlate)
+ chip->of_xlate = of_pwm_xlate_with_flags;
+
+ of_node_get(pwmchip_parent(chip)->of_node);
+}
+
+static void of_pwmchip_remove(struct pwm_chip *chip)
+{
+ if (pwmchip_parent(chip))
+ of_node_put(pwmchip_parent(chip)->of_node);
+}
+
+static bool pwm_ops_check(const struct pwm_chip *chip)
+{
+ const struct pwm_ops *ops = chip->ops;
+
+ if (ops->write_waveform) {
+ if (!ops->round_waveform_tohw ||
+ !ops->round_waveform_fromhw ||
+ !ops->write_waveform)
+ return false;
+
+ if (PWM_WFHWSIZE < ops->sizeof_wfhw) {
+ dev_warn(pwmchip_parent(chip), "PWM_WFHWSIZE < %zu\n", ops->sizeof_wfhw);
+ return false;
+ }
+ } else {
+ if (!ops->apply)
+ return false;
+
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) && !ops->get_state)
+ dev_warn(pwmchip_parent(chip),
+ "Please implement the .get_state() callback\n");
+ }
+
+ return true;
}
static struct device_link *pwm_device_link_add(struct device *dev,
@@ -767,21 +1730,35 @@ static struct device_link *pwm_device_link_add(struct device *dev,
* impact the PM sequence ordering: the PWM supplier may get
* suspended before the consumer.
*/
- dev_warn(pwm->chip->dev,
+ dev_warn(pwmchip_parent(pwm->chip),
"No consumer device specified to create a link to\n");
return NULL;
}
- dl = device_link_add(dev, pwm->chip->dev, DL_FLAG_AUTOREMOVE_CONSUMER);
+ dl = device_link_add(dev, pwmchip_parent(pwm->chip), DL_FLAG_AUTOREMOVE_CONSUMER);
if (!dl) {
dev_err(dev, "failed to create device link to %s\n",
- dev_name(pwm->chip->dev));
+ dev_name(pwmchip_parent(pwm->chip)));
return ERR_PTR(-EINVAL);
}
return dl;
}
+static struct pwm_chip *fwnode_to_pwmchip(struct fwnode_handle *fwnode)
+{
+ struct pwm_chip *chip;
+ unsigned long id, tmp;
+
+ guard(mutex)(&pwm_lock);
+
+ idr_for_each_entry_ul(&pwm_chips, chip, tmp, id)
+ if (pwmchip_parent(chip) && device_match_fwnode(pwmchip_parent(chip), fwnode))
+ return chip;
+
+ return ERR_PTR(-EPROBE_DEFER);
+}
+
/**
* of_pwm_get() - request a PWM via the PWM framework
* @dev: device for PWM consumer
@@ -802,13 +1779,13 @@ static struct device_link *pwm_device_link_add(struct device *dev,
* 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)
+static 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;
+ struct pwm_chip *chip;
int index = 0;
int err;
@@ -818,30 +1795,29 @@ struct pwm_device *of_pwm_get(struct device *dev, struct device_node *np,
return ERR_PTR(index);
}
- err = of_parse_phandle_with_args(np, "pwms", "#pwm-cells", index,
- &args);
+ err = of_parse_phandle_with_args_map(np, "pwms", "pwm", index, &args);
if (err) {
pr_err("%s(): can't parse \"pwms\" property\n", __func__);
return ERR_PTR(err);
}
- pc = of_node_to_pwmchip(args.np);
- if (IS_ERR(pc)) {
- if (PTR_ERR(pc) != -EPROBE_DEFER)
+ chip = fwnode_to_pwmchip(of_fwnode_handle(args.np));
+ if (IS_ERR(chip)) {
+ if (PTR_ERR(chip) != -EPROBE_DEFER)
pr_err("%s(): PWM chip not found\n", __func__);
- pwm = ERR_CAST(pc);
+ pwm = ERR_CAST(chip);
goto put;
}
- pwm = pc->of_xlate(pc, &args);
+ pwm = chip->of_xlate(chip, &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_put(pwm);
pwm = ERR_CAST(dl);
goto put;
}
@@ -865,33 +1841,10 @@ put:
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
+ * @fwnode: firmware node to get the "pwms" property from
*
* Returns the PWM device parsed from the fwnode and index specified in the
* "pwms" property or a negative error-code on failure.
@@ -906,12 +1859,10 @@ static struct pwm_chip *device_to_pwmchip(struct device *dev)
* 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)
+static struct pwm_device *acpi_pwm_get(const struct fwnode_handle *fwnode)
{
- struct pwm_device *pwm = ERR_PTR(-ENODEV);
-#if IS_ENABLED(CONFIG_ACPI)
+ struct pwm_device *pwm;
struct fwnode_reference_args args;
- struct acpi_device *acpi;
struct pwm_chip *chip;
int ret;
@@ -921,14 +1872,10 @@ static struct pwm_device *acpi_pwm_get(struct fwnode_handle *fwnode)
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);
+ chip = fwnode_to_pwmchip(args.fwnode);
if (IS_ERR(chip))
return ERR_CAST(chip);
@@ -941,44 +1888,12 @@ static struct pwm_device *acpi_pwm_get(struct fwnode_handle *fwnode)
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);
-}
+static DEFINE_MUTEX(pwm_lookup_lock);
+static LIST_HEAD(pwm_lookup_list);
/**
* pwm_get() - look up and request a PWM device
@@ -997,6 +1912,7 @@ void pwm_remove_table(struct pwm_lookup *table, size_t num)
*/
struct pwm_device *pwm_get(struct device *dev, const char *con_id)
{
+ const struct fwnode_handle *fwnode = dev ? dev_fwnode(dev) : NULL;
const char *dev_id = dev ? dev_name(dev) : NULL;
struct pwm_device *pwm;
struct pwm_chip *chip;
@@ -1007,12 +1923,12 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
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);
+ if (is_of_node(fwnode))
+ return of_pwm_get(dev, to_of_node(fwnode), con_id);
/* then lookup via ACPI */
- if (dev && is_acpi_node(dev->fwnode)) {
- pwm = acpi_pwm_get(dev->fwnode);
+ if (is_acpi_node(fwnode)) {
+ pwm = acpi_pwm_get(fwnode);
if (!IS_ERR(pwm) || PTR_ERR(pwm) != -ENOENT)
return pwm;
}
@@ -1037,36 +1953,33 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
* Then we take the most specific entry - with the following order
* of precedence: dev+con > dev only > con only.
*/
- mutex_lock(&pwm_lookup_lock);
-
- list_for_each_entry(p, &pwm_lookup_list, list) {
- match = 0;
+ scoped_guard(mutex, &pwm_lookup_lock)
+ list_for_each_entry(p, &pwm_lookup_list, list) {
+ match = 0;
- if (p->dev_id) {
- if (!dev_id || strcmp(p->dev_id, dev_id))
- continue;
+ if (p->dev_id) {
+ if (!dev_id || strcmp(p->dev_id, dev_id))
+ continue;
- match += 2;
- }
+ match += 2;
+ }
- if (p->con_id) {
- if (!con_id || strcmp(p->con_id, con_id))
- continue;
+ if (p->con_id) {
+ if (!con_id || strcmp(p->con_id, con_id))
+ continue;
- match += 1;
- }
+ match += 1;
+ }
- if (match > best) {
- chosen = p;
+ if (match > best) {
+ chosen = p;
- if (match != 3)
- best = match;
- else
- break;
+ if (match != 3)
+ best = match;
+ else
+ break;
+ }
}
- }
-
- mutex_unlock(&pwm_lookup_lock);
if (!chosen)
return ERR_PTR(-ENODEV);
@@ -1094,7 +2007,7 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
dl = pwm_device_link_add(dev, pwm);
if (IS_ERR(dl)) {
- pwm_free(pwm);
+ pwm_put(pwm);
return ERR_CAST(dl);
}
@@ -1105,102 +2018,77 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
}
EXPORT_SYMBOL_GPL(pwm_get);
-/**
- * pwm_put() - release a PWM device
- * @pwm: PWM device
- */
-void pwm_put(struct pwm_device *pwm)
+static void __pwm_put(struct pwm_device *pwm)
{
- if (!pwm)
- return;
-
- mutex_lock(&pwm_lock);
+ struct pwm_chip *chip = pwm->chip;
- if (!test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
+ /*
+ * Trigger a warning if a consumer called pwm_put() twice.
+ * If the chip isn't operational, PWMF_REQUESTED was already cleared in
+ * pwmchip_remove(). So don't warn in this case.
+ */
+ if (chip->operational && !test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
pr_warn("PWM device already freed\n");
- goto out;
+ return;
}
- if (pwm->chip->ops->free)
+ if (chip->operational && chip->ops->free)
pwm->chip->ops->free(pwm->chip, pwm);
- pwm_set_chip_data(pwm, NULL);
pwm->label = NULL;
- module_put(pwm->chip->ops->owner);
-out:
- mutex_unlock(&pwm_lock);
-}
-EXPORT_SYMBOL_GPL(pwm_put);
+ put_device(&chip->dev);
-static void devm_pwm_release(struct device *dev, void *res)
-{
- pwm_put(*(struct pwm_device **)res);
+ module_put(chip->owner);
}
/**
- * 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.
+ * pwm_put() - release a PWM device
+ * @pwm: PWM device
*/
-struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id)
+void pwm_put(struct pwm_device *pwm)
{
- struct pwm_device **ptr, *pwm;
+ if (!pwm)
+ return;
- ptr = devres_alloc(devm_pwm_release, sizeof(*ptr), GFP_KERNEL);
- if (!ptr)
- return ERR_PTR(-ENOMEM);
+ guard(mutex)(&pwm_lock);
- pwm = pwm_get(dev, con_id);
- if (!IS_ERR(pwm)) {
- *ptr = pwm;
- devres_add(dev, ptr);
- } else {
- devres_free(ptr);
- }
+ __pwm_put(pwm);
+}
+EXPORT_SYMBOL_GPL(pwm_put);
- return pwm;
+static void devm_pwm_release(void *pwm)
+{
+ pwm_put(pwm);
}
-EXPORT_SYMBOL_GPL(devm_pwm_get);
/**
- * devm_of_pwm_get() - resource managed of_pwm_get()
+ * devm_pwm_get() - resource managed 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
+ * 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_of_pwm_get(struct device *dev, struct device_node *np,
- const char *con_id)
+struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id)
{
- struct pwm_device **ptr, *pwm;
+ struct pwm_device *pwm;
+ int ret;
- 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))
+ return pwm;
- pwm = of_pwm_get(dev, np, con_id);
- if (!IS_ERR(pwm)) {
- *ptr = pwm;
- devres_add(dev, ptr);
- } else {
- devres_free(ptr);
- }
+ ret = devm_add_action_or_reset(dev, devm_pwm_release, pwm);
+ if (ret)
+ return ERR_PTR(ret);
return pwm;
}
-EXPORT_SYMBOL_GPL(devm_of_pwm_get);
+EXPORT_SYMBOL_GPL(devm_pwm_get);
/**
* devm_fwnode_pwm_get() - request a resource managed PWM from firmware node
@@ -1218,76 +2106,558 @@ 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);
+ struct pwm_device *pwm = ERR_PTR(-ENODEV);
+ int ret;
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))
+ return pwm;
- if (!IS_ERR(pwm)) {
- *ptr = pwm;
- devres_add(dev, ptr);
- } else {
- devres_free(ptr);
- }
+ ret = devm_add_action_or_reset(dev, devm_pwm_release, pwm);
+ if (ret)
+ return ERR_PTR(ret);
return pwm;
}
EXPORT_SYMBOL_GPL(devm_fwnode_pwm_get);
-static int devm_pwm_match(struct device *dev, void *res, void *data)
+struct pwm_cdev_data {
+ struct pwm_chip *chip;
+ struct pwm_device *pwm[];
+};
+
+static int pwm_cdev_open(struct inode *inode, struct file *file)
{
- struct pwm_device **p = res;
+ struct pwm_chip *chip = container_of(inode->i_cdev, struct pwm_chip, cdev);
+ struct pwm_cdev_data *cdata;
- if (WARN_ON(!p || !*p))
- return 0;
+ guard(mutex)(&pwm_lock);
+
+ if (!chip->operational)
+ return -ENXIO;
+
+ cdata = kzalloc(struct_size(cdata, pwm, chip->npwm), GFP_KERNEL);
+ if (!cdata)
+ return -ENOMEM;
+
+ cdata->chip = chip;
+
+ file->private_data = cdata;
- return *p == data;
+ return nonseekable_open(inode, file);
+}
+
+static int pwm_cdev_release(struct inode *inode, struct file *file)
+{
+ struct pwm_cdev_data *cdata = file->private_data;
+ unsigned int i;
+
+ for (i = 0; i < cdata->chip->npwm; ++i) {
+ struct pwm_device *pwm = cdata->pwm[i];
+
+ if (pwm) {
+ const char *label = pwm->label;
+
+ pwm_put(cdata->pwm[i]);
+ kfree(label);
+ }
+ }
+ kfree(cdata);
+
+ return 0;
+}
+
+static int pwm_cdev_request(struct pwm_cdev_data *cdata, unsigned int hwpwm)
+{
+ struct pwm_chip *chip = cdata->chip;
+
+ if (hwpwm >= chip->npwm)
+ return -EINVAL;
+
+ if (!cdata->pwm[hwpwm]) {
+ struct pwm_device *pwm = &chip->pwms[hwpwm];
+ const char *label;
+ int ret;
+
+ label = kasprintf(GFP_KERNEL, "pwm-cdev (pid=%d)", current->pid);
+ if (!label)
+ return -ENOMEM;
+
+ ret = pwm_device_request(pwm, label);
+ if (ret < 0) {
+ kfree(label);
+ return ret;
+ }
+
+ cdata->pwm[hwpwm] = pwm;
+ }
+
+ return 0;
+}
+
+static int pwm_cdev_free(struct pwm_cdev_data *cdata, unsigned int hwpwm)
+{
+ struct pwm_chip *chip = cdata->chip;
+
+ if (hwpwm >= chip->npwm)
+ return -EINVAL;
+
+ if (cdata->pwm[hwpwm]) {
+ struct pwm_device *pwm = cdata->pwm[hwpwm];
+ const char *label = pwm->label;
+
+ __pwm_put(pwm);
+
+ kfree(label);
+
+ cdata->pwm[hwpwm] = NULL;
+ }
+
+ return 0;
+}
+
+static struct pwm_device *pwm_cdev_get_requested_pwm(struct pwm_cdev_data *cdata,
+ u32 hwpwm)
+{
+ struct pwm_chip *chip = cdata->chip;
+
+ if (hwpwm >= chip->npwm)
+ return ERR_PTR(-EINVAL);
+
+ if (cdata->pwm[hwpwm])
+ return cdata->pwm[hwpwm];
+
+ return ERR_PTR(-EINVAL);
+}
+
+static long pwm_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int ret = 0;
+ struct pwm_cdev_data *cdata = file->private_data;
+ struct pwm_chip *chip = cdata->chip;
+
+ guard(mutex)(&pwm_lock);
+
+ if (!chip->operational)
+ return -ENODEV;
+
+ switch (cmd) {
+ case PWM_IOCTL_REQUEST:
+ {
+ unsigned int hwpwm = arg;
+
+ return pwm_cdev_request(cdata, hwpwm);
+ }
+
+ case PWM_IOCTL_FREE:
+ {
+ unsigned int hwpwm = arg;
+
+ return pwm_cdev_free(cdata, hwpwm);
+ }
+
+ case PWM_IOCTL_ROUNDWF:
+ {
+ struct pwmchip_waveform cwf;
+ struct pwm_waveform wf;
+ struct pwm_device *pwm;
+
+ ret = copy_from_user(&cwf,
+ (struct pwmchip_waveform __user *)arg,
+ sizeof(cwf));
+ if (ret)
+ return -EFAULT;
+
+ if (cwf.__pad != 0)
+ return -EINVAL;
+
+ pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm);
+ if (IS_ERR(pwm))
+ return PTR_ERR(pwm);
+
+ wf = (struct pwm_waveform) {
+ .period_length_ns = cwf.period_length_ns,
+ .duty_length_ns = cwf.duty_length_ns,
+ .duty_offset_ns = cwf.duty_offset_ns,
+ };
+
+ ret = pwm_round_waveform_might_sleep(pwm, &wf);
+ if (ret < 0)
+ return ret;
+
+ cwf = (struct pwmchip_waveform) {
+ .hwpwm = cwf.hwpwm,
+ .period_length_ns = wf.period_length_ns,
+ .duty_length_ns = wf.duty_length_ns,
+ .duty_offset_ns = wf.duty_offset_ns,
+ };
+
+ return copy_to_user((struct pwmchip_waveform __user *)arg,
+ &cwf, sizeof(cwf));
+ }
+
+ case PWM_IOCTL_GETWF:
+ {
+ struct pwmchip_waveform cwf;
+ struct pwm_waveform wf;
+ struct pwm_device *pwm;
+
+ ret = copy_from_user(&cwf,
+ (struct pwmchip_waveform __user *)arg,
+ sizeof(cwf));
+ if (ret)
+ return -EFAULT;
+
+ if (cwf.__pad != 0)
+ return -EINVAL;
+
+ pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm);
+ if (IS_ERR(pwm))
+ return PTR_ERR(pwm);
+
+ ret = pwm_get_waveform_might_sleep(pwm, &wf);
+ if (ret)
+ return ret;
+
+ cwf = (struct pwmchip_waveform) {
+ .hwpwm = cwf.hwpwm,
+ .period_length_ns = wf.period_length_ns,
+ .duty_length_ns = wf.duty_length_ns,
+ .duty_offset_ns = wf.duty_offset_ns,
+ };
+
+ return copy_to_user((struct pwmchip_waveform __user *)arg,
+ &cwf, sizeof(cwf));
+ }
+
+ case PWM_IOCTL_SETROUNDEDWF:
+ case PWM_IOCTL_SETEXACTWF:
+ {
+ struct pwmchip_waveform cwf;
+ struct pwm_waveform wf;
+ struct pwm_device *pwm;
+
+ ret = copy_from_user(&cwf,
+ (struct pwmchip_waveform __user *)arg,
+ sizeof(cwf));
+ if (ret)
+ return -EFAULT;
+
+ if (cwf.__pad != 0)
+ return -EINVAL;
+
+ wf = (struct pwm_waveform){
+ .period_length_ns = cwf.period_length_ns,
+ .duty_length_ns = cwf.duty_length_ns,
+ .duty_offset_ns = cwf.duty_offset_ns,
+ };
+
+ if (!pwm_wf_valid(&wf))
+ return -EINVAL;
+
+ pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm);
+ if (IS_ERR(pwm))
+ return PTR_ERR(pwm);
+
+ ret = pwm_set_waveform_might_sleep(pwm, &wf,
+ cmd == PWM_IOCTL_SETEXACTWF);
+
+ /*
+ * If userspace cares about rounding deviations it has
+ * to check the values anyhow, so simplify handling for
+ * them and don't signal uprounding. This matches the
+ * behaviour of PWM_IOCTL_ROUNDWF which also returns 0
+ * in that case.
+ */
+ if (ret == 1)
+ ret = 0;
+
+ return ret;
+ }
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations pwm_cdev_fileops = {
+ .open = pwm_cdev_open,
+ .release = pwm_cdev_release,
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = pwm_cdev_ioctl,
+};
+
+static dev_t pwm_devt;
+
+static int pwm_gpio_request(struct gpio_chip *gc, unsigned int offset)
+{
+ struct pwm_chip *chip = gpiochip_get_data(gc);
+ struct pwm_device *pwm;
+
+ pwm = pwm_request_from_chip(chip, offset, "pwm-gpio");
+ if (IS_ERR(pwm))
+ return PTR_ERR(pwm);
+
+ return 0;
+}
+
+static void pwm_gpio_free(struct gpio_chip *gc, unsigned int offset)
+{
+ struct pwm_chip *chip = gpiochip_get_data(gc);
+
+ pwm_put(&chip->pwms[offset]);
+}
+
+static int pwm_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int pwm_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+ struct pwm_chip *chip = gpiochip_get_data(gc);
+ struct pwm_device *pwm = &chip->pwms[offset];
+ int ret;
+ struct pwm_waveform wf = {
+ .period_length_ns = 1,
+ };
+
+ ret = pwm_round_waveform_might_sleep(pwm, &wf);
+ if (ret < 0)
+ return ret;
+
+ if (value)
+ wf.duty_length_ns = wf.period_length_ns;
+ else
+ wf.duty_length_ns = 0;
+
+ return pwm_set_waveform_might_sleep(pwm, &wf, true);
}
/**
- * devm_pwm_put() - resource managed pwm_put()
- * @dev: device for PWM consumer
- * @pwm: PWM device
+ * __pwmchip_add() - register a new PWM chip
+ * @chip: the PWM chip to add
+ * @owner: reference to the module providing the chip.
*
- * 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.
+ * Register a new PWM chip. @owner is supposed to be THIS_MODULE, use the
+ * pwmchip_add wrapper to do this right.
+ *
+ * Returns: 0 on success or a negative error code on failure.
*/
-void devm_pwm_put(struct device *dev, struct pwm_device *pwm)
+int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
{
- WARN_ON(devres_release(dev, devm_pwm_release, devm_pwm_match, pwm));
+ int ret;
+
+ if (!chip || !pwmchip_parent(chip) || !chip->ops || !chip->npwm)
+ return -EINVAL;
+
+ /*
+ * a struct pwm_chip must be allocated using (devm_)pwmchip_alloc,
+ * otherwise the embedded struct device might disappear too early
+ * resulting in memory corruption.
+ * Catch drivers that were not converted appropriately.
+ */
+ if (!chip->uses_pwmchip_alloc)
+ return -EINVAL;
+
+ if (!pwm_ops_check(chip))
+ return -EINVAL;
+
+ chip->owner = owner;
+
+ if (chip->atomic)
+ spin_lock_init(&chip->atomic_lock);
+ else
+ mutex_init(&chip->nonatomic_lock);
+
+ guard(mutex)(&pwm_lock);
+
+ ret = idr_alloc(&pwm_chips, chip, 0, 0, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ chip->id = ret;
+
+ dev_set_name(&chip->dev, "pwmchip%u", chip->id);
+
+ if (IS_ENABLED(CONFIG_OF))
+ of_pwmchip_add(chip);
+
+ scoped_guard(pwmchip, chip)
+ chip->operational = true;
+
+ if (chip->ops->write_waveform) {
+ if (chip->id < PWM_MINOR_COUNT)
+ chip->dev.devt = MKDEV(MAJOR(pwm_devt), chip->id);
+ else
+ dev_warn(&chip->dev, "chip id too high to create a chardev\n");
+ }
+
+ cdev_init(&chip->cdev, &pwm_cdev_fileops);
+ chip->cdev.owner = owner;
+
+ ret = cdev_device_add(&chip->cdev, &chip->dev);
+ if (ret)
+ goto err_device_add;
+
+ if (IS_ENABLED(CONFIG_PWM_PROVIDE_GPIO) && chip->ops->write_waveform) {
+ struct device *parent = pwmchip_parent(chip);
+
+ chip->gpio = (typeof(chip->gpio)){
+ .label = dev_name(parent),
+ .parent = parent,
+ .request = pwm_gpio_request,
+ .free = pwm_gpio_free,
+ .get_direction = pwm_gpio_get_direction,
+ .set = pwm_gpio_set,
+ .base = -1,
+ .ngpio = chip->npwm,
+ .can_sleep = true,
+ };
+
+ ret = gpiochip_add_data(&chip->gpio, chip);
+ if (ret)
+ goto err_gpiochip_add;
+ }
+
+ return 0;
+
+err_gpiochip_add:
+
+ cdev_device_del(&chip->cdev, &chip->dev);
+err_device_add:
+
+ scoped_guard(pwmchip, chip)
+ chip->operational = false;
+
+ if (IS_ENABLED(CONFIG_OF))
+ of_pwmchip_remove(chip);
+
+ idr_remove(&pwm_chips, chip->id);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(__pwmchip_add);
+
+/**
+ * pwmchip_remove() - remove a PWM chip
+ * @chip: the PWM chip to remove
+ *
+ * Removes a PWM chip.
+ */
+void pwmchip_remove(struct pwm_chip *chip)
+{
+ if (IS_ENABLED(CONFIG_PWM_PROVIDE_GPIO) && chip->ops->write_waveform)
+ gpiochip_remove(&chip->gpio);
+
+ pwmchip_sysfs_unexport(chip);
+
+ scoped_guard(mutex, &pwm_lock) {
+ unsigned int i;
+
+ scoped_guard(pwmchip, chip)
+ chip->operational = false;
+
+ for (i = 0; i < chip->npwm; ++i) {
+ struct pwm_device *pwm = &chip->pwms[i];
+
+ if (test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
+ dev_warn(&chip->dev, "Freeing requested PWM #%u\n", i);
+ if (pwm->chip->ops->free)
+ pwm->chip->ops->free(pwm->chip, pwm);
+ }
+ }
+
+ if (IS_ENABLED(CONFIG_OF))
+ of_pwmchip_remove(chip);
+
+ idr_remove(&pwm_chips, chip->id);
+ }
+
+ cdev_device_del(&chip->cdev, &chip->dev);
+}
+EXPORT_SYMBOL_GPL(pwmchip_remove);
+
+static void devm_pwmchip_remove(void *data)
+{
+ struct pwm_chip *chip = data;
+
+ pwmchip_remove(chip);
+}
+
+int __devm_pwmchip_add(struct device *dev, struct pwm_chip *chip, struct module *owner)
+{
+ int ret;
+
+ ret = __pwmchip_add(chip, owner);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, devm_pwmchip_remove, chip);
+}
+EXPORT_SYMBOL_GPL(__devm_pwmchip_add);
+
+/**
+ * pwm_add_table() - register PWM device consumers
+ * @table: array of consumers to register
+ * @num: number of consumers in table
+ */
+void pwm_add_table(struct pwm_lookup *table, size_t num)
+{
+ guard(mutex)(&pwm_lookup_lock);
+
+ while (num--) {
+ list_add_tail(&table->list, &pwm_lookup_list);
+ table++;
+ }
+}
+
+/**
+ * pwm_remove_table() - unregister PWM device consumers
+ * @table: array of consumers to unregister
+ * @num: number of consumers in table
+ */
+void pwm_remove_table(struct pwm_lookup *table, size_t num)
+{
+ guard(mutex)(&pwm_lookup_lock);
+
+ while (num--) {
+ list_del(&table->list);
+ table++;
+ }
}
-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;
+ struct pwm_state state, hwstate;
pwm_get_state(pwm, &state);
+ pwm_get_state_hw(pwm, &hwstate);
seq_printf(s, " pwm-%-3d (%-20.20s):", i, pwm->label);
if (test_bit(PWMF_REQUESTED, &pwm->flags))
seq_puts(s, " requested");
- if (state.enabled)
- seq_puts(s, " enabled");
+ seq_puts(s, "\n");
- seq_printf(s, " period: %u ns", state.period);
- seq_printf(s, " duty: %u ns", state.duty_cycle);
- seq_printf(s, " polarity: %s",
+ seq_printf(s, " requested configuration: %3sabled, %llu/%llu ns, %s polarity",
+ state.enabled ? "en" : "dis", state.duty_cycle, state.period,
state.polarity ? "inverse" : "normal");
+ if (state.usage_power)
+ seq_puts(s, ", usage_power");
+ seq_puts(s, "\n");
+
+ seq_printf(s, " actual configuration: %3sabled, %llu/%llu ns, %s polarity",
+ hwstate.enabled ? "en" : "dis", hwstate.duty_cycle, hwstate.period,
+ hwstate.polarity ? "inverse" : "normal");
seq_puts(s, "\n");
}
@@ -1295,17 +2665,27 @@ static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s)
static void *pwm_seq_start(struct seq_file *s, loff_t *pos)
{
+ unsigned long id = *pos;
+ void *ret;
+
mutex_lock(&pwm_lock);
s->private = "";
- return seq_list_start(&pwm_chips, *pos);
+ ret = idr_get_next_ul(&pwm_chips, &id);
+ *pos = id;
+ return ret;
}
static void *pwm_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
+ unsigned long id = *pos + 1;
+ void *ret;
+
s->private = "\n";
- return seq_list_next(v, &pwm_chips, pos);
+ ret = idr_get_next_ul(&pwm_chips, &id);
+ *pos = id;
+ return ret;
}
static void pwm_seq_stop(struct seq_file *s, void *v)
@@ -1315,44 +2695,47 @@ static void pwm_seq_stop(struct seq_file *s, void *v)
static int pwm_seq_show(struct seq_file *s, void *v)
{
- struct pwm_chip *chip = list_entry(v, struct pwm_chip, list);
+ struct pwm_chip *chip = v;
- 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" : "");
+ seq_printf(s, "%s%u: %s/%s, npwm: %u\n",
+ (char *)s->private, chip->id,
+ pwmchip_parent(chip)->bus ? pwmchip_parent(chip)->bus->name : "no-bus",
+ dev_name(pwmchip_parent(chip)), chip->npwm);
pwm_dbg_show(chip, s);
return 0;
}
-static const struct seq_operations pwm_seq_ops = {
+static const struct seq_operations pwm_debugfs_sops = {
.start = pwm_seq_start,
.next = pwm_seq_next,
.stop = pwm_seq_stop,
.show = pwm_seq_show,
};
-static int pwm_seq_open(struct inode *inode, struct file *file)
+DEFINE_SEQ_ATTRIBUTE(pwm_debugfs);
+
+static int __init pwm_init(void)
{
- return seq_open(file, &pwm_seq_ops);
-}
+ int ret;
-static const struct file_operations pwm_debugfs_ops = {
- .owner = THIS_MODULE,
- .open = pwm_seq_open,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = seq_release,
-};
+ ret = alloc_chrdev_region(&pwm_devt, 0, PWM_MINOR_COUNT, "pwm");
+ if (ret) {
+ pr_err("Failed to initialize chrdev region for PWM usage\n");
+ return ret;
+ }
-static int __init pwm_debugfs_init(void)
-{
- debugfs_create_file("pwm", S_IFREG | S_IRUGO, NULL, NULL,
- &pwm_debugfs_ops);
+ ret = class_register(&pwm_class);
+ if (ret) {
+ pr_err("Failed to initialize PWM class (%pe)\n", ERR_PTR(ret));
+ unregister_chrdev_region(pwm_devt, 256);
+ return ret;
+ }
+
+ if (IS_ENABLED(CONFIG_DEBUG_FS))
+ debugfs_create_file("pwm", 0444, NULL, NULL, &pwm_debugfs_fops);
return 0;
}
-subsys_initcall(pwm_debugfs_init);
-#endif /* CONFIG_DEBUG_FS */
+subsys_initcall(pwm_init);
diff --git a/drivers/pwm/pwm-ab8500.c b/drivers/pwm/pwm-ab8500.c
index fdf3964db4a6..f000adab85b0 100644
--- a/drivers/pwm/pwm-ab8500.c
+++ b/drivers/pwm/pwm-ab8500.c
@@ -3,6 +3,7 @@
* Copyright (C) ST-Ericsson SA 2010
*
* Author: Arun R Murthy <arun.murthy@stericsson.com>
+ * Datasheet: https://web.archive.org/web/20130614115108/http://www.stericsson.com/developers/CD00291561_UM1031_AB8500_user_manual-rev5_CTDS_public.pdf
*/
#include <linux/err.h>
#include <linux/platform_device.h>
@@ -20,110 +21,194 @@
#define AB8500_PWM_OUT_CTRL2_REG 0x61
#define AB8500_PWM_OUT_CTRL7_REG 0x66
+#define AB8500_PWM_CLKRATE 9600000
+
struct ab8500_pwm_chip {
- struct pwm_chip chip;
+ unsigned int hwid;
};
-static int ab8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
- int duty_ns, int period_ns)
+static struct ab8500_pwm_chip *ab8500_pwm_from_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static int ab8500_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
{
- int ret = 0;
- unsigned int higher_val, lower_val;
+ int ret;
u8 reg;
+ u8 higher_val, lower_val;
+ unsigned int duty_steps, div;
+ struct ab8500_pwm_chip *ab8500 = ab8500_pwm_from_chip(chip);
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (state->enabled) {
+ /*
+ * A time quantum is
+ * q = (32 - FreqPWMOutx[3:0]) / AB8500_PWM_CLKRATE
+ * The period is always 1024 q, duty_cycle is between 1q and 1024q.
+ *
+ * FreqPWMOutx[3:0] | output frequency | output frequency | 1024q = period
+ * | (from manual) | (1 / 1024q) | = 1 / freq
+ * -----------------+------------------+------------------+--------------
+ * b0000 | 293 Hz | 292.968750 Hz | 3413333.33 ns
+ * b0001 | 302 Hz | 302.419355 Hz | 3306666.66 ns
+ * b0010 | 312 Hz | 312.500000 Hz | 3200000 ns
+ * b0011 | 323 Hz | 323.275862 Hz | 3093333.33 ns
+ * b0100 | 334 Hz | 334.821429 Hz | 2986666.66 ns
+ * b0101 | 347 Hz | 347.222222 Hz | 2880000 ns
+ * b0110 | 360 Hz | 360.576923 Hz | 2773333.33 ns
+ * b0111 | 375 Hz | 375.000000 Hz | 2666666.66 ns
+ * b1000 | 390 Hz | 390.625000 Hz | 2560000 ns
+ * b1001 | 407 Hz | 407.608696 Hz | 2453333.33 ns
+ * b1010 | 426 Hz | 426.136364 Hz | 2346666.66 ns
+ * b1011 | 446 Hz | 446.428571 Hz | 2240000 ns
+ * b1100 | 468 Hz | 468.750000 Hz | 2133333.33 ns
+ * b1101 | 493 Hz | 493.421053 Hz | 2026666.66 ns
+ * b1110 | 520 Hz | 520.833333 Hz | 1920000 ns
+ * b1111 | 551 Hz | 551.470588 Hz | 1813333.33 ns
+ *
+ *
+ * AB8500_PWM_CLKRATE is a multiple of 1024, so the division by
+ * 1024 can be done in this factor without loss of precision.
+ */
+ div = min_t(u64, mul_u64_u64_div_u64(state->period,
+ AB8500_PWM_CLKRATE >> 10,
+ NSEC_PER_SEC), 32); /* 32 - FreqPWMOutx[3:0] */
+ if (div <= 16)
+ /* requested period < 3413333.33 */
+ return -EINVAL;
+
+ duty_steps = max_t(u64, mul_u64_u64_div_u64(state->duty_cycle,
+ AB8500_PWM_CLKRATE,
+ (u64)NSEC_PER_SEC * div), 1024);
+ }
/*
- * get the first 8 bits that are be written to
+ * The hardware doesn't support duty_steps = 0 explicitly, but emits low
+ * when disabled.
+ */
+ if (!state->enabled || duty_steps == 0) {
+ ret = abx500_mask_and_set_register_interruptible(pwmchip_parent(chip),
+ AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG,
+ 1 << ab8500->hwid, 0);
+
+ if (ret < 0)
+ dev_err(pwmchip_parent(chip), "%s: Failed to disable PWM, Error %d\n",
+ pwm->label, ret);
+ return ret;
+ }
+
+ /*
+ * The lower 8 bits of duty_steps is written to ...
* AB8500_PWM_OUT_CTRL1_REG[0:7]
*/
- lower_val = duty_ns & 0x00FF;
+ lower_val = (duty_steps - 1) & 0x00ff;
/*
- * get bits [9:10] that are to be written to
- * AB8500_PWM_OUT_CTRL2_REG[0:1]
+ * The two remaining high bits to
+ * AB8500_PWM_OUT_CTRL2_REG[0:1]; together with FreqPWMOutx.
*/
- higher_val = ((duty_ns & 0x0300) >> 8);
+ higher_val = ((duty_steps - 1) & 0x0300) >> 8 | (32 - div) << 4;
- reg = AB8500_PWM_OUT_CTRL1_REG + ((chip->base - 1) * 2);
+ reg = AB8500_PWM_OUT_CTRL1_REG + (ab8500->hwid * 2);
- ret = abx500_set_register_interruptible(chip->dev, AB8500_MISC,
- reg, (u8)lower_val);
+ ret = abx500_set_register_interruptible(pwmchip_parent(chip), AB8500_MISC,
+ reg, 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_set_register_interruptible(pwmchip_parent(chip), AB8500_MISC,
+ (reg + 1), higher_val);
+ if (ret < 0)
+ return ret;
- ret = abx500_mask_and_set_register_interruptible(chip->dev,
+ /* enable */
+ ret = abx500_mask_and_set_register_interruptible(pwmchip_parent(chip),
AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG,
- 1 << (chip->base - 1), 1 << (chip->base - 1));
+ 1 << ab8500->hwid, 1 << ab8500->hwid);
if (ret < 0)
- dev_err(chip->dev, "%s: Failed to enable PWM, Error %d\n",
+ dev_err(pwmchip_parent(chip), "%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)
+static int ab8500_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
{
+ u8 ctrl7, lower_val, higher_val;
int ret;
+ struct ab8500_pwm_chip *ab8500 = ab8500_pwm_from_chip(chip);
+ unsigned int div, duty_steps;
- 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);
+ ret = abx500_get_register_interruptible(pwmchip_parent(chip), AB8500_MISC,
+ AB8500_PWM_OUT_CTRL7_REG,
+ &ctrl7);
+ if (ret)
+ return ret;
+
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ if (!(ctrl7 & 1 << ab8500->hwid)) {
+ state->enabled = false;
+ return 0;
+ }
+
+ ret = abx500_get_register_interruptible(pwmchip_parent(chip), AB8500_MISC,
+ AB8500_PWM_OUT_CTRL1_REG + (ab8500->hwid * 2),
+ &lower_val);
+ if (ret)
+ return ret;
+
+ ret = abx500_get_register_interruptible(pwmchip_parent(chip), AB8500_MISC,
+ AB8500_PWM_OUT_CTRL2_REG + (ab8500->hwid * 2),
+ &higher_val);
+ if (ret)
+ return ret;
+
+ div = 32 - ((higher_val & 0xf0) >> 4);
+ duty_steps = ((higher_val & 3) << 8 | lower_val) + 1;
+
+ state->period = DIV64_U64_ROUND_UP((u64)div << 10, AB8500_PWM_CLKRATE);
+ state->duty_cycle = DIV64_U64_ROUND_UP((u64)div * duty_steps, AB8500_PWM_CLKRATE);
+
+ return 0;
}
static const struct pwm_ops ab8500_pwm_ops = {
- .config = ab8500_pwm_config,
- .enable = ab8500_pwm_enable,
- .disable = ab8500_pwm_disable,
- .owner = THIS_MODULE,
+ .apply = ab8500_pwm_apply,
+ .get_state = ab8500_pwm_get_state,
};
static int ab8500_pwm_probe(struct platform_device *pdev)
{
+ struct pwm_chip *chip;
struct ab8500_pwm_chip *ab8500;
int err;
+ if (pdev->id < 1 || pdev->id > 31)
+ return dev_err_probe(&pdev->dev, -EINVAL, "Invalid device id %d\n", pdev->id);
+
/*
* 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;
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*ab8500));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
- ab8500->chip.dev = &pdev->dev;
- ab8500->chip.ops = &ab8500_pwm_ops;
- ab8500->chip.base = pdev->id;
- ab8500->chip.npwm = 1;
+ ab8500 = ab8500_pwm_from_chip(chip);
- 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;
-}
+ chip->ops = &ab8500_pwm_ops;
+ ab8500->hwid = pdev->id - 1;
-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);
+ err = devm_pwmchip_add(&pdev->dev, chip);
if (err < 0)
- return err;
+ return dev_err_probe(&pdev->dev, err, "Failed to add pwm chip\n");
- dev_dbg(&pdev->dev, "pwm driver removed\n");
+ dev_dbg(&pdev->dev, "pwm probe successful\n");
return 0;
}
@@ -133,7 +218,6 @@ static struct platform_driver ab8500_pwm_driver = {
.name = "ab8500-pwm",
},
.probe = ab8500_pwm_probe,
- .remove = ab8500_pwm_remove,
};
module_platform_driver(ab8500_pwm_driver);
diff --git a/drivers/pwm/pwm-adp5585.c b/drivers/pwm/pwm-adp5585.c
new file mode 100644
index 000000000000..806f8d79b0d7
--- /dev/null
+++ b/drivers/pwm/pwm-adp5585.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices ADP5585 PWM driver
+ *
+ * Copyright 2022 NXP
+ * Copyright 2024 Ideas on Board Oy
+ *
+ * Limitations:
+ * - The .apply() operation executes atomically, but may not wait for the
+ * period to complete (this is not documented and would need to be tested).
+ * - Disabling the PWM drives the output pin to a low level immediately.
+ * - The hardware can only generate normal polarity output.
+ */
+
+#include <asm/byteorder.h>
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/math64.h>
+#include <linux/mfd/adp5585.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/time.h>
+#include <linux/types.h>
+
+#define ADP5585_PWM_CHAN_NUM 1
+
+#define ADP5585_PWM_OSC_FREQ_HZ 1000000U
+#define ADP5585_PWM_MIN_PERIOD_NS (2ULL * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ)
+#define ADP5585_PWM_MAX_PERIOD_NS (2ULL * 0xffff * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ)
+
+struct adp5585_pwm_chip {
+ unsigned int pwm_cfg;
+ unsigned int pwm_offt_low;
+ unsigned int pwm_ont_low;
+};
+
+struct adp5585_pwm {
+ const struct adp5585_pwm_chip *info;
+ struct regmap *regmap;
+ unsigned int ext_cfg;
+};
+
+static int pwm_adp5585_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct adp5585_pwm *adp5585_pwm = pwmchip_get_drvdata(chip);
+
+ /* Configure the R3 pin as PWM output. */
+ return regmap_update_bits(adp5585_pwm->regmap, adp5585_pwm->ext_cfg,
+ ADP5585_R3_EXTEND_CFG_MASK,
+ ADP5585_R3_EXTEND_CFG_PWM_OUT);
+}
+
+static void pwm_adp5585_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct adp5585_pwm *adp5585_pwm = pwmchip_get_drvdata(chip);
+
+ regmap_update_bits(adp5585_pwm->regmap, adp5585_pwm->ext_cfg,
+ ADP5585_R3_EXTEND_CFG_MASK,
+ ADP5585_R3_EXTEND_CFG_GPIO4);
+}
+
+static int pwm_adp5585_apply(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct adp5585_pwm *adp5585_pwm = pwmchip_get_drvdata(chip);
+ const struct adp5585_pwm_chip *info = adp5585_pwm->info;
+ struct regmap *regmap = adp5585_pwm->regmap;
+ u64 period, duty_cycle;
+ u32 on, off;
+ __le16 val;
+ int ret;
+
+ if (!state->enabled) {
+ regmap_clear_bits(regmap, info->pwm_cfg, ADP5585_PWM_EN);
+ return 0;
+ }
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (state->period < ADP5585_PWM_MIN_PERIOD_NS)
+ return -EINVAL;
+
+ period = min(state->period, ADP5585_PWM_MAX_PERIOD_NS);
+ duty_cycle = min(state->duty_cycle, period);
+
+ /*
+ * Compute the on and off time. As the internal oscillator frequency is
+ * 1MHz, the calculation can be simplified without loss of precision.
+ */
+ on = div_u64(duty_cycle, NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
+ off = div_u64(period, NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) - on;
+
+ val = cpu_to_le16(off);
+ ret = regmap_bulk_write(regmap, info->pwm_offt_low, &val, 2);
+ if (ret)
+ return ret;
+
+ val = cpu_to_le16(on);
+ ret = regmap_bulk_write(regmap, info->pwm_ont_low, &val, 2);
+ if (ret)
+ return ret;
+
+ /* Enable PWM in continuous mode and no external AND'ing. */
+ ret = regmap_update_bits(regmap, info->pwm_cfg,
+ ADP5585_PWM_IN_AND | ADP5585_PWM_MODE |
+ ADP5585_PWM_EN, ADP5585_PWM_EN);
+ if (ret)
+ return ret;
+
+ return regmap_set_bits(regmap, info->pwm_cfg, ADP5585_PWM_EN);
+}
+
+static int pwm_adp5585_get_state(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct adp5585_pwm *adp5585_pwm = pwmchip_get_drvdata(chip);
+ const struct adp5585_pwm_chip *info = adp5585_pwm->info;
+ struct regmap *regmap = adp5585_pwm->regmap;
+ unsigned int on, off;
+ unsigned int val;
+ __le16 on_off;
+ int ret;
+
+ ret = regmap_bulk_read(regmap, info->pwm_offt_low, &on_off, 2);
+ if (ret)
+ return ret;
+ off = le16_to_cpu(on_off);
+
+ ret = regmap_bulk_read(regmap, info->pwm_ont_low, &on_off, 2);
+ if (ret)
+ return ret;
+ on = le16_to_cpu(on_off);
+
+ state->duty_cycle = on * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
+ state->period = (on + off) * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
+
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ regmap_read(regmap, info->pwm_cfg, &val);
+ state->enabled = !!(val & ADP5585_PWM_EN);
+
+ return 0;
+}
+
+static const struct pwm_ops adp5585_pwm_ops = {
+ .request = pwm_adp5585_request,
+ .free = pwm_adp5585_free,
+ .apply = pwm_adp5585_apply,
+ .get_state = pwm_adp5585_get_state,
+};
+
+static int adp5585_pwm_probe(struct platform_device *pdev)
+{
+ const struct platform_device_id *id = platform_get_device_id(pdev);
+ struct device *dev = &pdev->dev;
+ struct adp5585_dev *adp5585 = dev_get_drvdata(dev->parent);
+ struct adp5585_pwm *adp5585_pwm;
+ struct pwm_chip *chip;
+ int ret;
+
+ chip = devm_pwmchip_alloc(dev, ADP5585_PWM_CHAN_NUM,
+ sizeof(*adp5585_pwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ adp5585_pwm = pwmchip_get_drvdata(chip);
+ adp5585_pwm->regmap = adp5585->regmap;
+ adp5585_pwm->ext_cfg = adp5585->regs->ext_cfg;
+
+ adp5585_pwm->info = (const struct adp5585_pwm_chip *)id->driver_data;
+ if (!adp5585_pwm->info)
+ return -ENODEV;
+
+ device_set_of_node_from_dev(dev, dev->parent);
+
+ chip->ops = &adp5585_pwm_ops;
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add PWM chip\n");
+
+ return 0;
+}
+
+static const struct adp5585_pwm_chip adp5585_pwm_chip_info = {
+ .pwm_cfg = ADP5585_PWM_CFG,
+ .pwm_offt_low = ADP5585_PWM_OFFT_LOW,
+ .pwm_ont_low = ADP5585_PWM_ONT_LOW,
+};
+
+static const struct adp5585_pwm_chip adp5589_pwm_chip_info = {
+ .pwm_cfg = ADP5589_PWM_CFG,
+ .pwm_offt_low = ADP5589_PWM_OFFT_LOW,
+ .pwm_ont_low = ADP5589_PWM_ONT_LOW,
+};
+
+static const struct platform_device_id adp5585_pwm_id_table[] = {
+ { "adp5585-pwm", (kernel_ulong_t)&adp5585_pwm_chip_info },
+ { "adp5589-pwm", (kernel_ulong_t)&adp5589_pwm_chip_info },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, adp5585_pwm_id_table);
+
+static struct platform_driver adp5585_pwm_driver = {
+ .driver = {
+ .name = "adp5585-pwm",
+ },
+ .probe = adp5585_pwm_probe,
+ .id_table = adp5585_pwm_id_table,
+};
+module_platform_driver(adp5585_pwm_driver);
+
+MODULE_AUTHOR("Xiaoning Wang <xiaoning.wang@nxp.com>");
+MODULE_DESCRIPTION("ADP5585 PWM Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-airoha.c b/drivers/pwm/pwm-airoha.c
new file mode 100644
index 000000000000..7236e31d2f17
--- /dev/null
+++ b/drivers/pwm/pwm-airoha.c
@@ -0,0 +1,622 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2022 Markus Gothe <markus.gothe@genexis.eu>
+ * Copyright 2025 Christian Marangi <ansuelsmth@gmail.com>
+ *
+ * Limitations:
+ * - Only 8 concurrent waveform generators are available for 8 combinations of
+ * duty_cycle and period. Waveform generators are shared between 16 GPIO
+ * pins and 17 SIPO GPIO pins.
+ * - Supports only normal polarity.
+ * - On configuration the currently running period is completed.
+ * - Minimum supported period is 4 ms
+ * - Maximum supported period is 1s
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitmap.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/math64.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#define AIROHA_PWM_REG_SGPIO_LED_DATA 0x0024
+#define AIROHA_PWM_SGPIO_LED_DATA_SHIFT_FLAG BIT(31)
+#define AIROHA_PWM_SGPIO_LED_DATA_DATA GENMASK(16, 0)
+
+#define AIROHA_PWM_REG_SGPIO_CLK_DIVR 0x0028
+#define AIROHA_PWM_SGPIO_CLK_DIVR GENMASK(1, 0)
+#define AIROHA_PWM_SGPIO_CLK_DIVR_32 FIELD_PREP_CONST(AIROHA_PWM_SGPIO_CLK_DIVR, 3)
+#define AIROHA_PWM_SGPIO_CLK_DIVR_16 FIELD_PREP_CONST(AIROHA_PWM_SGPIO_CLK_DIVR, 2)
+#define AIROHA_PWM_SGPIO_CLK_DIVR_8 FIELD_PREP_CONST(AIROHA_PWM_SGPIO_CLK_DIVR, 1)
+#define AIROHA_PWM_SGPIO_CLK_DIVR_4 FIELD_PREP_CONST(AIROHA_PWM_SGPIO_CLK_DIVR, 0)
+
+#define AIROHA_PWM_REG_SGPIO_CLK_DLY 0x002c
+
+#define AIROHA_PWM_REG_SIPO_FLASH_MODE_CFG 0x0030
+#define AIROHA_PWM_SERIAL_GPIO_FLASH_MODE BIT(1)
+#define AIROHA_PWM_SERIAL_GPIO_MODE_74HC164 BIT(0)
+
+#define AIROHA_PWM_REG_GPIO_FLASH_PRD_SET(_n) (0x003c + (4 * (_n)))
+#define AIROHA_PWM_REG_GPIO_FLASH_PRD_SHIFT(_n) (16 * (_n))
+#define AIROHA_PWM_GPIO_FLASH_PRD_LOW GENMASK(15, 8)
+#define AIROHA_PWM_GPIO_FLASH_PRD_HIGH GENMASK(7, 0)
+
+#define AIROHA_PWM_REG_GPIO_FLASH_MAP(_n) (0x004c + (4 * (_n)))
+#define AIROHA_PWM_REG_GPIO_FLASH_MAP_SHIFT(_n) (4 * (_n))
+#define AIROHA_PWM_GPIO_FLASH_EN BIT(3)
+#define AIROHA_PWM_GPIO_FLASH_SET_ID GENMASK(2, 0)
+
+/* Register map is equal to GPIO flash map */
+#define AIROHA_PWM_REG_SIPO_FLASH_MAP(_n) (0x0054 + (4 * (_n)))
+
+#define AIROHA_PWM_REG_CYCLE_CFG_VALUE(_n) (0x0098 + (4 * (_n)))
+#define AIROHA_PWM_REG_CYCLE_CFG_SHIFT(_n) (8 * (_n))
+#define AIROHA_PWM_WAVE_GEN_CYCLE GENMASK(7, 0)
+
+/* GPIO/SIPO flash map handles 8 pins in one register */
+#define AIROHA_PWM_PINS_PER_FLASH_MAP 8
+/* Cycle(Period) registers handles 4 generators in one 32-bit register */
+#define AIROHA_PWM_BUCKET_PER_CYCLE_CFG 4
+/* Flash(Duty) producer handles 2 generators in one 32-bit register */
+#define AIROHA_PWM_BUCKET_PER_FLASH_PROD 2
+
+#define AIROHA_PWM_NUM_BUCKETS 8
+/*
+ * The first 16 GPIO pins, GPIO0-GPIO15, are mapped into 16 PWM channels, 0-15.
+ * The SIPO GPIO pins are 17 pins which are mapped into 17 PWM channels, 16-32.
+ * However, we've only got 8 concurrent waveform generators and can therefore
+ * only use up to 8 different combinations of duty cycle and period at a time.
+ */
+#define AIROHA_PWM_NUM_GPIO 16
+#define AIROHA_PWM_NUM_SIPO 17
+#define AIROHA_PWM_MAX_CHANNELS (AIROHA_PWM_NUM_GPIO + AIROHA_PWM_NUM_SIPO)
+
+struct airoha_pwm_bucket {
+ /* Concurrent access protected by PWM core */
+ int used;
+ u32 period_ticks;
+ u32 duty_ticks;
+};
+
+struct airoha_pwm {
+ struct regmap *regmap;
+
+ DECLARE_BITMAP(initialized, AIROHA_PWM_MAX_CHANNELS);
+
+ struct airoha_pwm_bucket buckets[AIROHA_PWM_NUM_BUCKETS];
+
+ /* Cache bucket used by each pwm channel */
+ u8 channel_bucket[AIROHA_PWM_MAX_CHANNELS];
+};
+
+/* The PWM hardware supports periods between 4 ms and 1 s */
+#define AIROHA_PWM_PERIOD_TICK_NS (4 * NSEC_PER_MSEC)
+#define AIROHA_PWM_PERIOD_MAX_NS (1 * NSEC_PER_SEC)
+/* It is represented internally as 1/250 s between 1 and 250. Unit is ticks. */
+#define AIROHA_PWM_PERIOD_MIN 1
+#define AIROHA_PWM_PERIOD_MAX 250
+/* Duty cycle is relative with 255 corresponding to 100% */
+#define AIROHA_PWM_DUTY_FULL 255
+
+static void airoha_pwm_get_flash_map_addr_and_shift(unsigned int hwpwm,
+ u32 *addr, u32 *shift)
+{
+ unsigned int offset, hwpwm_bit;
+
+ if (hwpwm >= AIROHA_PWM_NUM_GPIO) {
+ unsigned int sipohwpwm = hwpwm - AIROHA_PWM_NUM_GPIO;
+
+ offset = sipohwpwm / AIROHA_PWM_PINS_PER_FLASH_MAP;
+ hwpwm_bit = sipohwpwm % AIROHA_PWM_PINS_PER_FLASH_MAP;
+
+ /* One FLASH_MAP register handles 8 pins */
+ *shift = AIROHA_PWM_REG_GPIO_FLASH_MAP_SHIFT(hwpwm_bit);
+ *addr = AIROHA_PWM_REG_SIPO_FLASH_MAP(offset);
+ } else {
+ offset = hwpwm / AIROHA_PWM_PINS_PER_FLASH_MAP;
+ hwpwm_bit = hwpwm % AIROHA_PWM_PINS_PER_FLASH_MAP;
+
+ /* One FLASH_MAP register handles 8 pins */
+ *shift = AIROHA_PWM_REG_GPIO_FLASH_MAP_SHIFT(hwpwm_bit);
+ *addr = AIROHA_PWM_REG_GPIO_FLASH_MAP(offset);
+ }
+}
+
+static u32 airoha_pwm_get_period_ticks_from_ns(u32 period_ns)
+{
+ return period_ns / AIROHA_PWM_PERIOD_TICK_NS;
+}
+
+static u32 airoha_pwm_get_duty_ticks_from_ns(u32 period_ns, u32 duty_ns)
+{
+ return mul_u64_u32_div(duty_ns, AIROHA_PWM_DUTY_FULL, period_ns);
+}
+
+static u32 airoha_pwm_get_period_ns_from_ticks(u32 period_tick)
+{
+ return period_tick * AIROHA_PWM_PERIOD_TICK_NS;
+}
+
+static u32 airoha_pwm_get_duty_ns_from_ticks(u32 period_tick, u32 duty_tick)
+{
+ u32 period_ns = period_tick * AIROHA_PWM_PERIOD_TICK_NS;
+
+ /*
+ * Overflow can't occur in multiplication as duty_tick is just 8 bit
+ * and period_ns is clamped to AIROHA_PWM_PERIOD_MAX_NS and fit in a
+ * u64.
+ */
+ return DIV_U64_ROUND_UP(duty_tick * period_ns, AIROHA_PWM_DUTY_FULL);
+}
+
+static int airoha_pwm_get_bucket(struct airoha_pwm *pc, int bucket,
+ u64 *period_ns, u64 *duty_ns)
+{
+ struct regmap *map = pc->regmap;
+ u32 period_tick, duty_tick;
+ unsigned int offset;
+ u32 shift, val;
+ int ret;
+
+ offset = bucket / AIROHA_PWM_BUCKET_PER_CYCLE_CFG;
+ shift = bucket % AIROHA_PWM_BUCKET_PER_CYCLE_CFG;
+ shift = AIROHA_PWM_REG_CYCLE_CFG_SHIFT(shift);
+
+ ret = regmap_read(map, AIROHA_PWM_REG_CYCLE_CFG_VALUE(offset), &val);
+ if (ret)
+ return ret;
+
+ period_tick = FIELD_GET(AIROHA_PWM_WAVE_GEN_CYCLE, val >> shift);
+ *period_ns = airoha_pwm_get_period_ns_from_ticks(period_tick);
+
+ offset = bucket / AIROHA_PWM_BUCKET_PER_FLASH_PROD;
+ shift = bucket % AIROHA_PWM_BUCKET_PER_FLASH_PROD;
+ shift = AIROHA_PWM_REG_GPIO_FLASH_PRD_SHIFT(shift);
+
+ ret = regmap_read(map, AIROHA_PWM_REG_GPIO_FLASH_PRD_SET(offset),
+ &val);
+ if (ret)
+ return ret;
+
+ duty_tick = FIELD_GET(AIROHA_PWM_GPIO_FLASH_PRD_HIGH, val >> shift);
+ *duty_ns = airoha_pwm_get_duty_ns_from_ticks(period_tick, duty_tick);
+
+ return 0;
+}
+
+static int airoha_pwm_get_generator(struct airoha_pwm *pc, u32 duty_ticks,
+ u32 period_ticks)
+{
+ int best = -ENOENT, unused = -ENOENT;
+ u32 duty_ns, best_duty_ns = 0;
+ u32 best_period_ticks = 0;
+ unsigned int i;
+
+ duty_ns = airoha_pwm_get_duty_ns_from_ticks(period_ticks, duty_ticks);
+
+ for (i = 0; i < ARRAY_SIZE(pc->buckets); i++) {
+ struct airoha_pwm_bucket *bucket = &pc->buckets[i];
+ u32 bucket_period_ticks = bucket->period_ticks;
+ u32 bucket_duty_ticks = bucket->duty_ticks;
+
+ /* If found, save an unused bucket to return it later */
+ if (!bucket->used) {
+ unused = i;
+ continue;
+ }
+
+ /* We found a matching bucket, exit early */
+ if (duty_ticks == bucket_duty_ticks &&
+ period_ticks == bucket_period_ticks)
+ return i;
+
+ /*
+ * Unlike duty cycle zero, which can be handled by
+ * disabling PWM, a generator is needed for full duty
+ * cycle but it can be reused regardless of period
+ */
+ if (duty_ticks == AIROHA_PWM_DUTY_FULL &&
+ bucket_duty_ticks == AIROHA_PWM_DUTY_FULL)
+ return i;
+
+ /*
+ * With an unused bucket available, skip searching for
+ * a bucket to recycle (closer to the requested period/duty)
+ */
+ if (unused >= 0)
+ continue;
+
+ /* Ignore bucket with invalid period */
+ if (bucket_period_ticks > period_ticks)
+ continue;
+
+ /*
+ * Search for a bucket closer to the requested period
+ * that has the maximal possible period that isn't bigger
+ * than the requested period. For that period pick the maximal
+ * duty cycle that isn't bigger than the requested duty_cycle.
+ */
+ if (bucket_period_ticks >= best_period_ticks) {
+ u32 bucket_duty_ns = airoha_pwm_get_duty_ns_from_ticks(bucket_period_ticks,
+ bucket_duty_ticks);
+
+ /* Skip bucket that goes over the requested duty */
+ if (bucket_duty_ns > duty_ns)
+ continue;
+
+ if (bucket_duty_ns > best_duty_ns) {
+ best_period_ticks = bucket_period_ticks;
+ best_duty_ns = bucket_duty_ns;
+ best = i;
+ }
+ }
+ }
+
+ /* Return an unused bucket or the best one found (if ever) */
+ return unused >= 0 ? unused : best;
+}
+
+static void airoha_pwm_release_bucket_config(struct airoha_pwm *pc,
+ unsigned int hwpwm)
+{
+ int bucket;
+
+ /* Nothing to clear, PWM channel never used */
+ if (!test_bit(hwpwm, pc->initialized))
+ return;
+
+ bucket = pc->channel_bucket[hwpwm];
+ pc->buckets[bucket].used--;
+}
+
+static int airoha_pwm_apply_bucket_config(struct airoha_pwm *pc, unsigned int bucket,
+ u32 duty_ticks, u32 period_ticks)
+{
+ u32 mask, shift, val;
+ u32 offset;
+ int ret;
+
+ offset = bucket / AIROHA_PWM_BUCKET_PER_CYCLE_CFG;
+ shift = bucket % AIROHA_PWM_BUCKET_PER_CYCLE_CFG;
+ shift = AIROHA_PWM_REG_CYCLE_CFG_SHIFT(shift);
+
+ /* Configure frequency divisor */
+ mask = AIROHA_PWM_WAVE_GEN_CYCLE << shift;
+ val = FIELD_PREP(AIROHA_PWM_WAVE_GEN_CYCLE, period_ticks) << shift;
+ ret = regmap_update_bits(pc->regmap, AIROHA_PWM_REG_CYCLE_CFG_VALUE(offset),
+ mask, val);
+ if (ret)
+ return ret;
+
+ offset = bucket / AIROHA_PWM_BUCKET_PER_FLASH_PROD;
+ shift = bucket % AIROHA_PWM_BUCKET_PER_FLASH_PROD;
+ shift = AIROHA_PWM_REG_GPIO_FLASH_PRD_SHIFT(shift);
+
+ /* Configure duty cycle */
+ mask = AIROHA_PWM_GPIO_FLASH_PRD_HIGH << shift;
+ val = FIELD_PREP(AIROHA_PWM_GPIO_FLASH_PRD_HIGH, duty_ticks) << shift;
+ ret = regmap_update_bits(pc->regmap, AIROHA_PWM_REG_GPIO_FLASH_PRD_SET(offset),
+ mask, val);
+ if (ret)
+ return ret;
+
+ mask = AIROHA_PWM_GPIO_FLASH_PRD_LOW << shift;
+ val = FIELD_PREP(AIROHA_PWM_GPIO_FLASH_PRD_LOW,
+ AIROHA_PWM_DUTY_FULL - duty_ticks) << shift;
+ return regmap_update_bits(pc->regmap, AIROHA_PWM_REG_GPIO_FLASH_PRD_SET(offset),
+ mask, val);
+}
+
+static int airoha_pwm_consume_generator(struct airoha_pwm *pc,
+ u32 duty_ticks, u32 period_ticks,
+ unsigned int hwpwm)
+{
+ bool config_bucket = false;
+ int bucket, ret;
+
+ /*
+ * Search for a bucket that already satisfies duty and period
+ * or an unused one.
+ * If not found, -ENOENT is returned.
+ */
+ bucket = airoha_pwm_get_generator(pc, duty_ticks, period_ticks);
+ if (bucket < 0)
+ return bucket;
+
+ /* Release previous used bucket (if any) */
+ airoha_pwm_release_bucket_config(pc, hwpwm);
+
+ if (!pc->buckets[bucket].used)
+ config_bucket = true;
+ pc->buckets[bucket].used++;
+
+ if (config_bucket) {
+ pc->buckets[bucket].period_ticks = period_ticks;
+ pc->buckets[bucket].duty_ticks = duty_ticks;
+ ret = airoha_pwm_apply_bucket_config(pc, bucket,
+ duty_ticks,
+ period_ticks);
+ if (ret) {
+ pc->buckets[bucket].used--;
+ return ret;
+ }
+ }
+
+ return bucket;
+}
+
+static int airoha_pwm_sipo_init(struct airoha_pwm *pc)
+{
+ u32 val;
+ int ret;
+
+ ret = regmap_clear_bits(pc->regmap, AIROHA_PWM_REG_SIPO_FLASH_MODE_CFG,
+ AIROHA_PWM_SERIAL_GPIO_MODE_74HC164);
+ if (ret)
+ return ret;
+
+ /* Configure shift register chip clock timings, use 32x divisor */
+ ret = regmap_write(pc->regmap, AIROHA_PWM_REG_SGPIO_CLK_DIVR,
+ AIROHA_PWM_SGPIO_CLK_DIVR_32);
+ if (ret)
+ return ret;
+
+ /*
+ * Configure the shift register chip clock delay. This needs
+ * to be configured based on the chip characteristics when the SoC
+ * apply the shift register configuration.
+ * This doesn't affect actual PWM operation and is only specific to
+ * the shift register chip.
+ *
+ * For 74HC164 we set it to 0.
+ *
+ * For reference, the actual delay applied is the internal clock
+ * feed to the SGPIO chip + 1.
+ *
+ * From documentation is specified that clock delay should not be
+ * greater than (AIROHA_PWM_REG_SGPIO_CLK_DIVR / 2) - 1.
+ */
+ ret = regmap_write(pc->regmap, AIROHA_PWM_REG_SGPIO_CLK_DLY, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * It is necessary to explicitly shift out all zeros after muxing
+ * to initialize the shift register before enabling PWM
+ * mode because in PWM mode SIPO will not start shifting until
+ * it needs to output a non-zero value (bit 31 of led_data
+ * indicates shifting in progress and it must return to zero
+ * before led_data can be written or PWM mode can be set).
+ */
+ ret = regmap_read_poll_timeout(pc->regmap, AIROHA_PWM_REG_SGPIO_LED_DATA, val,
+ !(val & AIROHA_PWM_SGPIO_LED_DATA_SHIFT_FLAG),
+ 10, 200 * USEC_PER_MSEC);
+ if (ret)
+ return ret;
+
+ ret = regmap_clear_bits(pc->regmap, AIROHA_PWM_REG_SGPIO_LED_DATA,
+ AIROHA_PWM_SGPIO_LED_DATA_DATA);
+ if (ret)
+ return ret;
+ ret = regmap_read_poll_timeout(pc->regmap, AIROHA_PWM_REG_SGPIO_LED_DATA, val,
+ !(val & AIROHA_PWM_SGPIO_LED_DATA_SHIFT_FLAG),
+ 10, 200 * USEC_PER_MSEC);
+ if (ret)
+ return ret;
+
+ /* Set SIPO in PWM mode */
+ return regmap_set_bits(pc->regmap, AIROHA_PWM_REG_SIPO_FLASH_MODE_CFG,
+ AIROHA_PWM_SERIAL_GPIO_FLASH_MODE);
+}
+
+static int airoha_pwm_config_flash_map(struct airoha_pwm *pc,
+ unsigned int hwpwm, int index)
+{
+ unsigned int addr;
+ u32 shift;
+ int ret;
+
+ airoha_pwm_get_flash_map_addr_and_shift(hwpwm, &addr, &shift);
+
+ /* negative index means disable PWM channel */
+ if (index < 0) {
+ /*
+ * If we need to disable the PWM, we just put low the
+ * GPIO. No need to setup buckets.
+ */
+ return regmap_clear_bits(pc->regmap, addr,
+ AIROHA_PWM_GPIO_FLASH_EN << shift);
+ }
+
+ ret = regmap_update_bits(pc->regmap, addr,
+ AIROHA_PWM_GPIO_FLASH_SET_ID << shift,
+ FIELD_PREP(AIROHA_PWM_GPIO_FLASH_SET_ID, index) << shift);
+ if (ret)
+ return ret;
+
+ return regmap_set_bits(pc->regmap, addr, AIROHA_PWM_GPIO_FLASH_EN << shift);
+}
+
+static int airoha_pwm_config(struct airoha_pwm *pc, struct pwm_device *pwm,
+ u32 period_ticks, u32 duty_ticks)
+{
+ unsigned int hwpwm = pwm->hwpwm;
+ int bucket, ret;
+
+ bucket = airoha_pwm_consume_generator(pc, duty_ticks, period_ticks,
+ hwpwm);
+ if (bucket < 0)
+ return bucket;
+
+ ret = airoha_pwm_config_flash_map(pc, hwpwm, bucket);
+ if (ret) {
+ pc->buckets[bucket].used--;
+ return ret;
+ }
+
+ __set_bit(hwpwm, pc->initialized);
+ pc->channel_bucket[hwpwm] = bucket;
+
+ /*
+ * SIPO are special GPIO attached to a shift register chip. The handling
+ * of this chip is internal to the SoC that takes care of applying the
+ * values based on the flash map. To apply a new flash map, it's needed
+ * to trigger a refresh on the shift register chip.
+ * If a SIPO is getting configuring , always reinit the shift register
+ * chip to make sure the correct flash map is applied.
+ * Skip reconfiguring the shift register if the related hwpwm
+ * is disabled (as it doesn't need to be mapped).
+ */
+ if (hwpwm >= AIROHA_PWM_NUM_GPIO) {
+ ret = airoha_pwm_sipo_init(pc);
+ if (ret) {
+ airoha_pwm_release_bucket_config(pc, hwpwm);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void airoha_pwm_disable(struct airoha_pwm *pc, struct pwm_device *pwm)
+{
+ /* Disable PWM and release the bucket */
+ airoha_pwm_config_flash_map(pc, pwm->hwpwm, -1);
+ airoha_pwm_release_bucket_config(pc, pwm->hwpwm);
+
+ __clear_bit(pwm->hwpwm, pc->initialized);
+
+ /* If no SIPO is used, disable the shift register chip */
+ if (!bitmap_read(pc->initialized,
+ AIROHA_PWM_NUM_GPIO, AIROHA_PWM_NUM_SIPO))
+ regmap_clear_bits(pc->regmap, AIROHA_PWM_REG_SIPO_FLASH_MODE_CFG,
+ AIROHA_PWM_SERIAL_GPIO_FLASH_MODE);
+}
+
+static int airoha_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct airoha_pwm *pc = pwmchip_get_drvdata(chip);
+ u32 period_ticks, duty_ticks;
+ u32 period_ns, duty_ns;
+
+ if (!state->enabled) {
+ airoha_pwm_disable(pc, pwm);
+ return 0;
+ }
+
+ /* Only normal polarity is supported */
+ if (state->polarity == PWM_POLARITY_INVERSED)
+ return -EINVAL;
+
+ /* Exit early if period is less than minimum supported */
+ if (state->period < AIROHA_PWM_PERIOD_TICK_NS)
+ return -EINVAL;
+
+ /* Clamp period to MAX supported value */
+ if (state->period > AIROHA_PWM_PERIOD_MAX_NS)
+ period_ns = AIROHA_PWM_PERIOD_MAX_NS;
+ else
+ period_ns = state->period;
+
+ /* Validate duty to configured period */
+ if (state->duty_cycle > period_ns)
+ duty_ns = period_ns;
+ else
+ duty_ns = state->duty_cycle;
+
+ /* Convert period ns to ticks */
+ period_ticks = airoha_pwm_get_period_ticks_from_ns(period_ns);
+ /* Convert period ticks to ns again for cosistent duty tick calculation */
+ period_ns = airoha_pwm_get_period_ns_from_ticks(period_ticks);
+ duty_ticks = airoha_pwm_get_duty_ticks_from_ns(period_ns, duty_ns);
+
+ return airoha_pwm_config(pc, pwm, period_ticks, duty_ticks);
+}
+
+static int airoha_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct airoha_pwm *pc = pwmchip_get_drvdata(chip);
+ int ret, hwpwm = pwm->hwpwm;
+ u32 addr, shift, val;
+ u8 bucket;
+
+ airoha_pwm_get_flash_map_addr_and_shift(hwpwm, &addr, &shift);
+
+ ret = regmap_read(pc->regmap, addr, &val);
+ if (ret)
+ return ret;
+
+ state->enabled = FIELD_GET(AIROHA_PWM_GPIO_FLASH_EN, val >> shift);
+ if (!state->enabled)
+ return 0;
+
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ bucket = FIELD_GET(AIROHA_PWM_GPIO_FLASH_SET_ID, val >> shift);
+ return airoha_pwm_get_bucket(pc, bucket, &state->period,
+ &state->duty_cycle);
+}
+
+static const struct pwm_ops airoha_pwm_ops = {
+ .apply = airoha_pwm_apply,
+ .get_state = airoha_pwm_get_state,
+};
+
+static int airoha_pwm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct airoha_pwm *pc;
+ struct pwm_chip *chip;
+ int ret;
+
+ chip = devm_pwmchip_alloc(dev, AIROHA_PWM_MAX_CHANNELS, sizeof(*pc));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ chip->ops = &airoha_pwm_ops;
+ pc = pwmchip_get_drvdata(chip);
+
+ pc->regmap = device_node_to_regmap(dev_of_node(dev->parent));
+ if (IS_ERR(pc->regmap))
+ return dev_err_probe(dev, PTR_ERR(pc->regmap), "Failed to get PWM regmap\n");
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
+
+ return 0;
+}
+
+static const struct of_device_id airoha_pwm_of_match[] = {
+ { .compatible = "airoha,en7581-pwm" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, airoha_pwm_of_match);
+
+static struct platform_driver airoha_pwm_driver = {
+ .driver = {
+ .name = "pwm-airoha",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .of_match_table = airoha_pwm_of_match,
+ },
+ .probe = airoha_pwm_probe,
+};
+module_platform_driver(airoha_pwm_driver);
+
+MODULE_AUTHOR("Lorenzo Bianconi <lorenzo@kernel.org>");
+MODULE_AUTHOR("Markus Gothe <markus.gothe@genexis.eu>");
+MODULE_AUTHOR("Benjamin Larsson <benjamin.larsson@genexis.eu>");
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_DESCRIPTION("Airoha EN7581 PWM driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-apple.c b/drivers/pwm/pwm-apple.c
new file mode 100644
index 000000000000..6e58aca2f13c
--- /dev/null
+++ b/drivers/pwm/pwm-apple.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Driver for the Apple SoC PWM controller
+ *
+ * Copyright The Asahi Linux Contributors
+ *
+ * Limitations:
+ * - The writes to cycle registers are shadowed until a write to
+ * the control register.
+ * - If both OFF_CYCLES and ON_CYCLES are set to 0, the output
+ * is a constant off signal.
+ * - When APPLE_PWM_CTRL is set to 0, the output is constant low
+ */
+
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/math64.h>
+
+#define APPLE_PWM_CTRL 0x00
+#define APPLE_PWM_ON_CYCLES 0x1c
+#define APPLE_PWM_OFF_CYCLES 0x18
+
+#define APPLE_PWM_CTRL_ENABLE BIT(0)
+#define APPLE_PWM_CTRL_MODE BIT(2)
+#define APPLE_PWM_CTRL_UPDATE BIT(5)
+#define APPLE_PWM_CTRL_TRIGGER BIT(9)
+#define APPLE_PWM_CTRL_INVERT BIT(10)
+#define APPLE_PWM_CTRL_OUTPUT_ENABLE BIT(14)
+
+struct apple_pwm {
+ void __iomem *base;
+ u64 clkrate;
+};
+
+static inline struct apple_pwm *to_apple_pwm(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static int apple_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct apple_pwm *fpwm;
+
+ if (state->polarity == PWM_POLARITY_INVERSED)
+ return -EINVAL;
+
+ fpwm = to_apple_pwm(chip);
+ if (state->enabled) {
+ u64 on_cycles, off_cycles;
+
+ on_cycles = mul_u64_u64_div_u64(fpwm->clkrate,
+ state->duty_cycle, NSEC_PER_SEC);
+ if (on_cycles > 0xFFFFFFFF)
+ on_cycles = 0xFFFFFFFF;
+
+ off_cycles = mul_u64_u64_div_u64(fpwm->clkrate,
+ state->period, NSEC_PER_SEC) - on_cycles;
+ if (off_cycles > 0xFFFFFFFF)
+ off_cycles = 0xFFFFFFFF;
+
+ writel(on_cycles, fpwm->base + APPLE_PWM_ON_CYCLES);
+ writel(off_cycles, fpwm->base + APPLE_PWM_OFF_CYCLES);
+ writel(APPLE_PWM_CTRL_ENABLE | APPLE_PWM_CTRL_OUTPUT_ENABLE | APPLE_PWM_CTRL_UPDATE,
+ fpwm->base + APPLE_PWM_CTRL);
+ } else {
+ writel(0, fpwm->base + APPLE_PWM_CTRL);
+ }
+ return 0;
+}
+
+static int apple_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct apple_pwm *fpwm;
+ u32 on_cycles, off_cycles, ctrl;
+
+ fpwm = to_apple_pwm(chip);
+
+ ctrl = readl(fpwm->base + APPLE_PWM_CTRL);
+ on_cycles = readl(fpwm->base + APPLE_PWM_ON_CYCLES);
+ off_cycles = readl(fpwm->base + APPLE_PWM_OFF_CYCLES);
+
+ state->enabled = (ctrl & APPLE_PWM_CTRL_ENABLE) && (ctrl & APPLE_PWM_CTRL_OUTPUT_ENABLE);
+ state->polarity = PWM_POLARITY_NORMAL;
+ // on_cycles + off_cycles is 33 bits, NSEC_PER_SEC is 30, there is no overflow
+ state->duty_cycle = DIV64_U64_ROUND_UP((u64)on_cycles * NSEC_PER_SEC, fpwm->clkrate);
+ state->period = DIV64_U64_ROUND_UP(((u64)off_cycles + (u64)on_cycles) *
+ NSEC_PER_SEC, fpwm->clkrate);
+
+ return 0;
+}
+
+static const struct pwm_ops apple_pwm_ops = {
+ .apply = apple_pwm_apply,
+ .get_state = apple_pwm_get_state,
+};
+
+static int apple_pwm_probe(struct platform_device *pdev)
+{
+ struct pwm_chip *chip;
+ struct apple_pwm *fpwm;
+ struct clk *clk;
+ int ret;
+
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*fpwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ fpwm = to_apple_pwm(chip);
+
+ fpwm->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(fpwm->base))
+ return PTR_ERR(fpwm->base);
+
+ clk = devm_clk_get_enabled(&pdev->dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(clk), "unable to get the clock");
+
+ /*
+ * Uses the 24MHz system clock on all existing devices, can only
+ * happen if the device tree is broken
+ *
+ * This check is done to prevent an overflow in .apply
+ */
+ fpwm->clkrate = clk_get_rate(clk);
+ if (fpwm->clkrate > NSEC_PER_SEC)
+ return dev_err_probe(&pdev->dev, -EINVAL, "pwm clock out of range");
+
+ chip->ops = &apple_pwm_ops;
+
+ ret = devm_pwmchip_add(&pdev->dev, chip);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "unable to add pwm chip");
+
+ return 0;
+}
+
+static const struct of_device_id apple_pwm_of_match[] = {
+ { .compatible = "apple,s5l-fpwm" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, apple_pwm_of_match);
+
+static struct platform_driver apple_pwm_driver = {
+ .probe = apple_pwm_probe,
+ .driver = {
+ .name = "apple-pwm",
+ .of_match_table = apple_pwm_of_match,
+ },
+};
+module_platform_driver(apple_pwm_driver);
+
+MODULE_DESCRIPTION("Apple SoC PWM driver");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/pwm/pwm-argon-fan-hat.c b/drivers/pwm/pwm-argon-fan-hat.c
new file mode 100644
index 000000000000..2c59bd142d40
--- /dev/null
+++ b/drivers/pwm/pwm-argon-fan-hat.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 Marek Vasut
+ *
+ * Limitations:
+ * - no support for offset/polarity
+ * - fixed 30 kHz period
+ *
+ * Argon Fan HAT https://argon40.com/products/argon-fan-hat
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pwm.h>
+
+#define ARGON40_FAN_HAT_PERIOD_NS 33333 /* ~30 kHz */
+
+#define ARGON40_FAN_HAT_REG_DUTY_CYCLE 0x80
+
+static int argon_fan_hat_round_waveform_tohw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_waveform *wf,
+ void *_wfhw)
+{
+ u8 *wfhw = _wfhw;
+
+ if (wf->duty_length_ns > ARGON40_FAN_HAT_PERIOD_NS)
+ *wfhw = 100;
+ else
+ *wfhw = mul_u64_u64_div_u64(wf->duty_length_ns, 100, ARGON40_FAN_HAT_PERIOD_NS);
+
+ return 0;
+}
+
+static int argon_fan_hat_round_waveform_fromhw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw,
+ struct pwm_waveform *wf)
+{
+ const u8 *wfhw = _wfhw;
+
+ wf->period_length_ns = ARGON40_FAN_HAT_PERIOD_NS;
+ wf->duty_length_ns = DIV64_U64_ROUND_UP(wf->period_length_ns * *wfhw, 100);
+ wf->duty_offset_ns = 0;
+
+ return 0;
+}
+
+static int argon_fan_hat_write_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw)
+{
+ struct i2c_client *i2c = pwmchip_get_drvdata(chip);
+ const u8 *wfhw = _wfhw;
+
+ return i2c_smbus_write_byte_data(i2c, ARGON40_FAN_HAT_REG_DUTY_CYCLE, *wfhw);
+}
+
+static const struct pwm_ops argon_fan_hat_pwm_ops = {
+ .sizeof_wfhw = sizeof(u8),
+ .round_waveform_fromhw = argon_fan_hat_round_waveform_fromhw,
+ .round_waveform_tohw = argon_fan_hat_round_waveform_tohw,
+ .write_waveform = argon_fan_hat_write_waveform,
+ /*
+ * The controller does not provide any way to read info back,
+ * reading from the controller stops the fan, therefore there
+ * is no .read_waveform here.
+ */
+};
+
+static int argon_fan_hat_i2c_probe(struct i2c_client *i2c)
+{
+ struct pwm_chip *chip = devm_pwmchip_alloc(&i2c->dev, 1, 0);
+ int ret;
+
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ chip->ops = &argon_fan_hat_pwm_ops;
+ pwmchip_set_drvdata(chip, i2c);
+
+ ret = devm_pwmchip_add(&i2c->dev, chip);
+ if (ret)
+ return dev_err_probe(&i2c->dev, ret, "Could not add PWM chip\n");
+
+ return 0;
+}
+
+static const struct of_device_id argon_fan_hat_dt_ids[] = {
+ { .compatible = "argon40,fan-hat" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, argon_fan_hat_dt_ids);
+
+static struct i2c_driver argon_fan_hat_driver = {
+ .driver = {
+ .name = "argon-fan-hat",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .of_match_table = argon_fan_hat_dt_ids,
+ },
+ .probe = argon_fan_hat_i2c_probe,
+};
+
+module_i2c_driver(argon_fan_hat_driver);
+
+MODULE_AUTHOR("Marek Vasut <marek.vasut+renesas@mailbox.org>");
+MODULE_DESCRIPTION("Argon40 Fan HAT");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-atmel-hlcdc.c b/drivers/pwm/pwm-atmel-hlcdc.c
index dcbc0489dfd4..387a0d1fa4f2 100644
--- a/drivers/pwm/pwm-atmel-hlcdc.c
+++ b/drivers/pwm/pwm-atmel-hlcdc.c
@@ -10,6 +10,7 @@
#include <linux/delay.h>
#include <linux/mfd/atmel-hlcdc.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
@@ -27,7 +28,6 @@ struct atmel_hlcdc_pwm_errata {
};
struct atmel_hlcdc_pwm {
- struct pwm_chip chip;
struct atmel_hlcdc *hlcdc;
struct clk *cur_clk;
const struct atmel_hlcdc_pwm_errata *errata;
@@ -35,14 +35,14 @@ struct atmel_hlcdc_pwm {
static inline struct atmel_hlcdc_pwm *to_atmel_hlcdc_pwm(struct pwm_chip *chip)
{
- return container_of(chip, struct atmel_hlcdc_pwm, chip);
+ return pwmchip_get_drvdata(chip);
}
-static int atmel_hlcdc_pwm_apply(struct pwm_chip *c, struct pwm_device *pwm,
+static int atmel_hlcdc_pwm_apply(struct pwm_chip *chip, 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;
+ struct atmel_hlcdc_pwm *atmel = to_atmel_hlcdc_pwm(chip);
+ struct atmel_hlcdc *hlcdc = atmel->hlcdc;
unsigned int status;
int ret;
@@ -54,7 +54,7 @@ static int atmel_hlcdc_pwm_apply(struct pwm_chip *c, struct pwm_device *pwm,
u32 pwmcfg;
int pres;
- if (!chip->errata || !chip->errata->slow_clk_erratum) {
+ if (!atmel->errata || !atmel->errata->slow_clk_erratum) {
clk_freq = clk_get_rate(new_clk);
if (!clk_freq)
return -EINVAL;
@@ -64,7 +64,7 @@ static int atmel_hlcdc_pwm_apply(struct pwm_chip *c, struct pwm_device *pwm,
}
/* Errata: cannot use slow clk on some IP revisions */
- if ((chip->errata && chip->errata->slow_clk_erratum) ||
+ if ((atmel->errata && atmel->errata->slow_clk_erratum) ||
clk_period_ns > state->period) {
new_clk = hlcdc->sys_clk;
clk_freq = clk_get_rate(new_clk);
@@ -77,8 +77,8 @@ static int atmel_hlcdc_pwm_apply(struct pwm_chip *c, struct pwm_device *pwm,
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)
+ if (!pres && atmel->errata &&
+ atmel->errata->div1_clk_erratum)
continue;
if ((clk_period_ns << pres) >= state->period)
@@ -90,7 +90,7 @@ static int atmel_hlcdc_pwm_apply(struct pwm_chip *c, struct pwm_device *pwm,
pwmcfg = ATMEL_HLCDC_PWMPS(pres);
- if (new_clk != chip->cur_clk) {
+ if (new_clk != atmel->cur_clk) {
u32 gencfg = 0;
int ret;
@@ -98,8 +98,8 @@ static int atmel_hlcdc_pwm_apply(struct pwm_chip *c, struct pwm_device *pwm,
if (ret)
return ret;
- clk_disable_unprepare(chip->cur_clk);
- chip->cur_clk = new_clk;
+ clk_disable_unprepare(atmel->cur_clk);
+ atmel->cur_clk = new_clk;
if (new_clk == hlcdc->sys_clk)
gencfg = ATMEL_HLCDC_CLKPWMSEL;
@@ -160,8 +160,8 @@ static int atmel_hlcdc_pwm_apply(struct pwm_chip *c, struct pwm_device *pwm,
if (ret)
return ret;
- clk_disable_unprepare(chip->cur_clk);
- chip->cur_clk = NULL;
+ clk_disable_unprepare(atmel->cur_clk);
+ atmel->cur_clk = NULL;
}
return 0;
@@ -169,7 +169,6 @@ static int atmel_hlcdc_pwm_apply(struct pwm_chip *c, struct pwm_device *pwm,
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 = {
@@ -180,39 +179,38 @@ 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);
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct atmel_hlcdc_pwm *atmel = to_atmel_hlcdc_pwm(chip);
+ struct pwm_device *pwm = &chip->pwms[0];
/* 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);
+ if (!pwm->state.enabled)
+ clk_disable_unprepare(atmel->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;
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct atmel_hlcdc_pwm *atmel = to_atmel_hlcdc_pwm(chip);
+ struct pwm_device *pwm = &chip->pwms[0];
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 (!pwm->state.enabled) {
+ ret = clk_prepare_enable(atmel->hlcdc->periph_clk);
if (ret)
return ret;
}
- return atmel_hlcdc_pwm_apply(&chip->chip, &chip->chip.pwms[0], &state);
+ return atmel_hlcdc_pwm_apply(chip, pwm, &pwm->state);
}
-#endif
-static SIMPLE_DEV_PM_OPS(atmel_hlcdc_pwm_pm_ops,
- atmel_hlcdc_pwm_suspend, atmel_hlcdc_pwm_resume);
+static DEFINE_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[] = {
{
@@ -236,7 +234,7 @@ static const struct of_device_id atmel_hlcdc_dt_ids[] = {
.data = &atmel_hlcdc_pwm_sama5d3_errata,
},
{ .compatible = "microchip,sam9x60-hlcdc", },
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, atmel_hlcdc_dt_ids);
@@ -244,15 +242,17 @@ 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 pwm_chip *chip;
+ struct atmel_hlcdc_pwm *atmel;
struct atmel_hlcdc *hlcdc;
int ret;
hlcdc = dev_get_drvdata(dev->parent);
- chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
- if (!chip)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(dev, 1, sizeof(*atmel));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ atmel = to_atmel_hlcdc_pwm(chip);
ret = clk_prepare_enable(hlcdc->periph_clk);
if (ret)
@@ -260,17 +260,12 @@ static int atmel_hlcdc_pwm_probe(struct platform_device *pdev)
match = of_match_node(atmel_hlcdc_dt_ids, dev->parent->of_node);
if (match)
- chip->errata = match->data;
+ atmel->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;
+ atmel->hlcdc = hlcdc;
+ chip->ops = &atmel_hlcdc_pwm_ops;
- ret = pwmchip_add_with_polarity(&chip->chip, PWM_POLARITY_INVERSED);
+ ret = pwmchip_add(chip);
if (ret) {
clk_disable_unprepare(hlcdc->periph_clk);
return ret;
@@ -281,30 +276,27 @@ static int atmel_hlcdc_pwm_probe(struct platform_device *pdev)
return 0;
}
-static int atmel_hlcdc_pwm_remove(struct platform_device *pdev)
+static void 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;
+ struct pwm_chip *chip = platform_get_drvdata(pdev);
+ struct atmel_hlcdc_pwm *atmel = to_atmel_hlcdc_pwm(chip);
- clk_disable_unprepare(chip->hlcdc->periph_clk);
+ pwmchip_remove(chip);
- return 0;
+ clk_disable_unprepare(atmel->hlcdc->periph_clk);
}
static const struct of_device_id atmel_hlcdc_pwm_dt_ids[] = {
{ .compatible = "atmel,hlcdc-pwm" },
- { /* sentinel */ },
+ { /* sentinel */ }
};
+MODULE_DEVICE_TABLE(of, atmel_hlcdc_pwm_dt_ids);
static struct platform_driver atmel_hlcdc_pwm_driver = {
.driver = {
.name = "atmel-hlcdc-pwm",
.of_match_table = atmel_hlcdc_pwm_dt_ids,
- .pm = &atmel_hlcdc_pwm_pm_ops,
+ .pm = pm_ptr(&atmel_hlcdc_pwm_pm_ops),
},
.probe = atmel_hlcdc_pwm_probe,
.remove = atmel_hlcdc_pwm_remove,
diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c
index 85c53701958c..f9ff78ba122d 100644
--- a/drivers/pwm/pwm-atmel-tcb.c
+++ b/drivers/pwm/pwm-atmel-tcb.c
@@ -16,13 +16,15 @@
#include <linux/err.h>
#include <linux/ioport.h>
#include <linux/io.h>
+#include <linux/mfd/syscon.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
#include <linux/slab.h>
#include <soc/at91/atmel_tcb.h>
-#define NPWM 6
+#define NPWM 2
#define ATMEL_TC_ACMR_MASK (ATMEL_TC_ACPA | ATMEL_TC_ACPC | \
ATMEL_TC_AEEVT | ATMEL_TC_ASWTRG)
@@ -31,7 +33,6 @@
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 */
@@ -46,83 +47,67 @@ struct atmel_tcb_channel {
};
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];
+ u8 channel;
+ u8 width;
+ struct regmap *regmap;
+ struct clk *clk;
+ struct clk *gclk;
+ struct clk *slow_clk;
+ struct atmel_tcb_pwm_device pwms[NPWM];
+ struct atmel_tcb_channel bkup;
};
-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 const u8 atmel_tcb_divisors[] = { 2, 8, 32, 128, 0, };
-static int atmel_tcb_pwm_set_polarity(struct pwm_chip *chip,
- struct pwm_device *pwm,
- enum pwm_polarity polarity)
+static inline struct atmel_tcb_pwm_chip *to_tcb_chip(struct pwm_chip *chip)
{
- struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
-
- tcbpwm->polarity = polarity;
-
- return 0;
+ return pwmchip_get_drvdata(chip);
}
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;
+ struct atmel_tcb_pwm_device *tcbpwm = &tcbpwmc->pwms[pwm->hwpwm];
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);
+ ret = clk_prepare_enable(tcbpwmc->clk);
+ if (ret)
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));
+ guard(spinlock)(&tcbpwmc->lock);
+
+ regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), &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));
+ if (pwm->hwpwm == 0)
+ regmap_read(tcbpwmc->regmap,
+ ATMEL_TC_REG(tcbpwmc->channel, RA),
+ &tcbpwm->duty);
else
- tcbpwm->duty =
- __raw_readl(regs + ATMEL_TC_REG(group, RB));
+ regmap_read(tcbpwmc->regmap,
+ ATMEL_TC_REG(tcbpwmc->channel, RB),
+ &tcbpwm->duty);
tcbpwm->div = cmr & ATMEL_TC_TCCLKS;
- tcbpwm->period = __raw_readl(regs + ATMEL_TC_REG(group, RC));
+ regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, RC),
+ &tcbpwm->period);
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;
+ regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), cmr);
return 0;
}
@@ -130,24 +115,16 @@ static int atmel_tcb_pwm_request(struct pwm_chip *chip,
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);
+ clk_disable_unprepare(tcbpwmc->clk);
}
-static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm,
+ enum pwm_polarity polarity)
{
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;
+ struct atmel_tcb_pwm_device *tcbpwm = &tcbpwmc->pwms[pwm->hwpwm];
unsigned cmr;
- enum pwm_polarity polarity = tcbpwm->polarity;
/*
* If duty is 0 the timer will be stopped and we have to
@@ -160,11 +137,10 @@ static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
if (tcbpwm->duty == 0)
polarity = !polarity;
- spin_lock(&tcbpwmc->lock);
- cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR));
+ regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), &cmr);
/* flush old setting and set the new one */
- if (index == 0) {
+ if (pwm->hwpwm == 0) {
cmr &= ~ATMEL_TC_ACMR_MASK;
if (polarity == PWM_POLARITY_INVERSED)
cmr |= ATMEL_TC_ASWTRG_CLEAR;
@@ -178,35 +154,31 @@ static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
cmr |= ATMEL_TC_BSWTRG_SET;
}
- __raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR));
+ regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), 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;
+ regmap_write(tcbpwmc->regmap,
+ ATMEL_TC_REG(tcbpwmc->channel, CCR),
+ ATMEL_TC_SWTRG | ATMEL_TC_CLKDIS);
+ tcbpwmc->bkup.enabled = 1;
} else {
- __raw_writel(ATMEL_TC_SWTRG, regs +
- ATMEL_TC_REG(group, CCR));
- tcbpwmc->bkup[group].enabled = 0;
+ regmap_write(tcbpwmc->regmap,
+ ATMEL_TC_REG(tcbpwmc->channel, CCR),
+ ATMEL_TC_SWTRG);
+ tcbpwmc->bkup.enabled = 0;
}
-
- spin_unlock(&tcbpwmc->lock);
}
-static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm,
+ enum pwm_polarity polarity)
{
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;
+ struct atmel_tcb_pwm_device *tcbpwm = &tcbpwmc->pwms[pwm->hwpwm];
u32 cmr;
- enum pwm_polarity polarity = tcbpwm->polarity;
/*
* If duty is 0 the timer will be stopped and we have to
@@ -219,13 +191,12 @@ static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
if (tcbpwm->duty == 0)
polarity = !polarity;
- spin_lock(&tcbpwmc->lock);
- cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR));
+ regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), &cmr);
/* flush old setting and set the new one */
cmr &= ~ATMEL_TC_TCCLKS;
- if (index == 0) {
+ if (pwm->hwpwm == 0) {
cmr &= ~ATMEL_TC_ACMR_MASK;
/* Set CMR flags according to given polarity */
@@ -248,7 +219,7 @@ static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
* this config till next config call.
*/
if (tcbpwm->duty != tcbpwm->period && tcbpwm->duty > 0) {
- if (index == 0) {
+ if (pwm->hwpwm == 0) {
if (polarity == PWM_POLARITY_INVERSED)
cmr |= ATMEL_TC_ACPA_SET | ATMEL_TC_ACPC_CLEAR;
else
@@ -263,20 +234,24 @@ static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
cmr |= (tcbpwm->div & ATMEL_TC_TCCLKS);
- __raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR));
+ regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), cmr);
- if (index == 0)
- __raw_writel(tcbpwm->duty, regs + ATMEL_TC_REG(group, RA));
+ if (pwm->hwpwm == 0)
+ regmap_write(tcbpwmc->regmap,
+ ATMEL_TC_REG(tcbpwmc->channel, RA),
+ tcbpwm->duty);
else
- __raw_writel(tcbpwm->duty, regs + ATMEL_TC_REG(group, RB));
+ regmap_write(tcbpwmc->regmap,
+ ATMEL_TC_REG(tcbpwmc->channel, RB),
+ tcbpwm->duty);
- __raw_writel(tcbpwm->period, regs + ATMEL_TC_REG(group, RC));
+ regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, RC),
+ tcbpwm->period);
/* 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);
+ regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CCR),
+ ATMEL_TC_SWTRG | ATMEL_TC_CLKEN);
+ tcbpwmc->bkup.enabled = 1;
return 0;
}
@@ -284,30 +259,31 @@ 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;
+ struct atmel_tcb_pwm_device *tcbpwm = &tcbpwmc->pwms[pwm->hwpwm];
+ /* companion PWM sharing register values period and div */
+ struct atmel_tcb_pwm_device *atcbpwm = &tcbpwmc->pwms[pwm->hwpwm ^ 1];
+ int i = 0;
int slowclk = 0;
unsigned period;
unsigned duty;
- unsigned rate = clk_get_rate(tc->clk[group]);
+ unsigned rate = clk_get_rate(tcbpwmc->clk);
unsigned long long min;
unsigned long long max;
/*
* Find best clk divisor:
* the smallest divisor which can fulfill the period_ns requirements.
+ * If there is a gclk, the first divisor is actually the gclk selector
*/
- for (i = 0; i < 5; ++i) {
- if (atmel_tc_divisors[i] == 0) {
+ if (tcbpwmc->gclk)
+ i = 1;
+ for (; i < ARRAY_SIZE(atmel_tcb_divisors); ++i) {
+ if (atmel_tcb_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;
+ min = div_u64((u64)NSEC_PER_SEC * atmel_tcb_divisors[i], rate);
+ max = min << tcbpwmc->width;
if (max >= period_ns)
break;
}
@@ -316,11 +292,11 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* If none of the divisor are small enough to represent period_ns
* take slow clock (32KHz).
*/
- if (i == 5) {
+ if (i == ARRAY_SIZE(atmel_tcb_divisors)) {
i = slowclk;
- rate = clk_get_rate(tc->slow_clk);
+ rate = clk_get_rate(tcbpwmc->slow_clk);
min = div_u64(NSEC_PER_SEC, rate);
- max = min << tc->tcb_config->counter_width;
+ max = min << tcbpwmc->width;
/* If period is too big return ERANGE error */
if (max < period_ns)
@@ -330,27 +306,17 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
duty = div_u64(duty_ns, min);
period = div_u64(period_ns, min);
- if (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 provided by the TCB driver are grouped by 2.
* 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) &&
+ if ((atcbpwm->duty > 0 && atcbpwm->duty != atcbpwm->period) &&
(atcbpwm->div != i || atcbpwm->period != period)) {
- dev_err(chip->dev,
+ dev_err(pwmchip_parent(chip),
"failed to configure period_ns: PWM group already configured with a different value\n");
return -EINVAL;
}
@@ -359,96 +325,155 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
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 int atmel_tcb_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
+ int duty_cycle, period;
+ int ret;
+
+ guard(spinlock)(&tcbpwmc->lock);
+
+ if (!state->enabled) {
+ atmel_tcb_pwm_disable(chip, pwm, state->polarity);
+ return 0;
+ }
+
+ period = min(state->period, INT_MAX);
+ duty_cycle = min(state->duty_cycle, INT_MAX);
+
+ ret = atmel_tcb_pwm_config(chip, pwm, duty_cycle, period);
+ if (ret)
+ return ret;
+
+ return atmel_tcb_pwm_enable(chip, pwm, state->polarity);
+}
+
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,
+ .apply = atmel_tcb_pwm_apply,
+};
+
+static struct atmel_tcb_config tcb_rm9200_config = {
+ .counter_width = 16,
+};
+
+static struct atmel_tcb_config tcb_sam9x5_config = {
+ .counter_width = 32,
+};
+
+static struct atmel_tcb_config tcb_sama5d2_config = {
+ .counter_width = 32,
+ .has_gclk = 1,
+};
+
+static const struct of_device_id atmel_tcb_of_match[] = {
+ { .compatible = "atmel,at91rm9200-tcb", .data = &tcb_rm9200_config, },
+ { .compatible = "atmel,at91sam9x5-tcb", .data = &tcb_sam9x5_config, },
+ { .compatible = "atmel,sama5d2-tcb", .data = &tcb_sama5d2_config, },
+ { /* sentinel */ }
};
static int atmel_tcb_pwm_probe(struct platform_device *pdev)
{
- struct atmel_tcb_pwm_chip *tcbpwm;
+ struct pwm_chip *chip;
+ const struct of_device_id *match;
+ struct atmel_tcb_pwm_chip *tcbpwmc;
+ const struct atmel_tcb_config *config;
struct device_node *np = pdev->dev.of_node;
- struct atmel_tc *tc;
+ char clk_name[] = "t0_clk";
int err;
- int tcblock;
+ int channel;
+
+ chip = devm_pwmchip_alloc(&pdev->dev, NPWM, sizeof(*tcbpwmc));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ tcbpwmc = to_tcb_chip(chip);
- err = of_property_read_u32(np, "tc-block", &tcblock);
+ err = of_property_read_u32(np, "reg", &channel);
if (err < 0) {
dev_err(&pdev->dev,
- "failed to get Timer Counter Block number from device tree (error: %d)\n",
+ "failed to get Timer Counter Block channel 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;
+ tcbpwmc->regmap = syscon_node_to_regmap(np->parent);
+ if (IS_ERR(tcbpwmc->regmap))
+ return PTR_ERR(tcbpwmc->regmap);
+
+ tcbpwmc->slow_clk = of_clk_get_by_name(np->parent, "slow_clk");
+ if (IS_ERR(tcbpwmc->slow_clk))
+ return PTR_ERR(tcbpwmc->slow_clk);
+
+ clk_name[1] += channel;
+ tcbpwmc->clk = of_clk_get_by_name(np->parent, clk_name);
+ if (IS_ERR(tcbpwmc->clk))
+ tcbpwmc->clk = of_clk_get_by_name(np->parent, "t0_clk");
+ if (IS_ERR(tcbpwmc->clk)) {
+ err = PTR_ERR(tcbpwmc->clk);
+ goto err_slow_clk;
}
- tcbpwm = devm_kzalloc(&pdev->dev, sizeof(*tcbpwm), GFP_KERNEL);
- if (tcbpwm == NULL) {
- err = -ENOMEM;
- goto err_free_tc;
+ match = of_match_node(atmel_tcb_of_match, np->parent);
+ config = match->data;
+
+ if (config->has_gclk) {
+ tcbpwmc->gclk = of_clk_get_by_name(np->parent, "gclk");
+ if (IS_ERR(tcbpwmc->gclk)) {
+ err = PTR_ERR(tcbpwmc->gclk);
+ goto err_clk;
+ }
}
- 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;
+ chip->ops = &atmel_tcb_pwm_ops;
+ tcbpwmc->channel = channel;
+ tcbpwmc->width = config->counter_width;
- err = clk_prepare_enable(tc->slow_clk);
+ err = clk_prepare_enable(tcbpwmc->slow_clk);
if (err)
- goto err_free_tc;
+ goto err_gclk;
- spin_lock_init(&tcbpwm->lock);
+ spin_lock_init(&tcbpwmc->lock);
- err = pwmchip_add(&tcbpwm->chip);
+ err = pwmchip_add(chip);
if (err < 0)
goto err_disable_clk;
- platform_set_drvdata(pdev, tcbpwm);
+ platform_set_drvdata(pdev, chip);
return 0;
err_disable_clk:
- clk_disable_unprepare(tcbpwm->tc->slow_clk);
+ clk_disable_unprepare(tcbpwmc->slow_clk);
-err_free_tc:
- atmel_tc_free(tc);
+err_gclk:
+ clk_put(tcbpwmc->gclk);
+
+err_clk:
+ clk_put(tcbpwmc->clk);
+
+err_slow_clk:
+ clk_put(tcbpwmc->slow_clk);
return err;
}
-static int atmel_tcb_pwm_remove(struct platform_device *pdev)
+static void 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;
+ struct pwm_chip *chip = platform_get_drvdata(pdev);
+ struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
- atmel_tc_free(tcbpwm->tc);
+ pwmchip_remove(chip);
- return 0;
+ clk_disable_unprepare(tcbpwmc->slow_clk);
+ clk_put(tcbpwmc->gclk);
+ clk_put(tcbpwmc->clk);
+ clk_put(tcbpwmc->slow_clk);
}
static const struct of_device_id atmel_tcb_pwm_dt_ids[] = {
@@ -457,54 +482,49 @@ static const struct of_device_id atmel_tcb_pwm_dt_ids[] = {
};
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;
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
+ struct atmel_tcb_channel *chan = &tcbpwmc->bkup;
+ unsigned int channel = tcbpwmc->channel;
- for (i = 0; i < (NPWM / 2); i++) {
- struct atmel_tcb_channel *chan = &tcbpwm->bkup[i];
+ regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(channel, CMR), &chan->cmr);
+ regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(channel, RA), &chan->ra);
+ regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(channel, RB), &chan->rb);
+ regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(channel, RC), &chan->rc);
- 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));
- }
- }
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
+ struct atmel_tcb_channel *chan = &tcbpwmc->bkup;
+ unsigned int channel = tcbpwmc->channel;
+
+ regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(channel, CMR), chan->cmr);
+ regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(channel, RA), chan->ra);
+ regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(channel, RB), chan->rb);
+ regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(channel, RC), chan->rc);
+
+ if (chan->enabled)
+ regmap_write(tcbpwmc->regmap,
+ ATMEL_TC_CLKEN | ATMEL_TC_SWTRG,
+ ATMEL_TC_REG(channel, CCR));
+
return 0;
}
-#endif
-static SIMPLE_DEV_PM_OPS(atmel_tcb_pwm_pm_ops, atmel_tcb_pwm_suspend,
- atmel_tcb_pwm_resume);
+static DEFINE_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,
+ .pm = pm_ptr(&atmel_tcb_pwm_pm_ops),
},
.probe = atmel_tcb_pwm_probe,
.remove = atmel_tcb_pwm_remove,
diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c
index 6161e7e3e9ac..06d22d0f7b26 100644
--- a/drivers/pwm/pwm-atmel.c
+++ b/drivers/pwm/pwm-atmel.c
@@ -6,7 +6,7 @@
* Bo Shen <voice.shen@atmel.com>
*
* Links to reference manuals for the supported PWM chips can be found in
- * Documentation/arm/microchip.rst.
+ * Documentation/arch/arm/microchip.rst.
*
* Limitations:
* - Periods start with the inactive level.
@@ -24,9 +24,7 @@
#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>
@@ -37,7 +35,7 @@
#define PWM_SR 0x0C
#define PWM_ISR 0x1C
/* Bit field in SR */
-#define PWM_SR_ALL_CH_ON 0x0F
+#define PWM_SR_ALL_CH_MASK 0x0F
/* The following register is PWM channel related registers */
#define PWM_CH_REG_OFFSET 0x200
@@ -79,19 +77,25 @@ struct atmel_pwm_data {
};
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;
+ /*
+ * The hardware supports a mechanism to update a channel's duty cycle at
+ * the end of the currently running period. When such an update is
+ * pending we delay disabling the PWM until the new configuration is
+ * active because otherwise pmw_config(duty_cycle=0); pwm_disable();
+ * might not result in an inactive output.
+ * This bitmask tracks for which channels an update is pending in
+ * hardware.
+ */
+ u32 update_pending;
};
static inline struct atmel_pwm_chip *to_atmel_pwm_chip(struct pwm_chip *chip)
{
- return container_of(chip, struct atmel_pwm_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
static inline u32 atmel_pwm_readl(struct atmel_pwm_chip *chip,
@@ -123,7 +127,58 @@ static inline void atmel_pwm_ch_writel(struct atmel_pwm_chip *chip,
atmel_pwm_writel(chip, base + offset, val);
}
+static void atmel_pwm_update_pending(struct atmel_pwm_chip *chip)
+{
+ /*
+ * Each channel that has its bit in ISR set started a new period since
+ * ISR was cleared and so there is no more update pending. Note that
+ * reading ISR clears it, so this needs to handle all channels to not
+ * loose information.
+ */
+ u32 isr = atmel_pwm_readl(chip, PWM_ISR);
+
+ chip->update_pending &= ~isr;
+}
+
+static void atmel_pwm_set_pending(struct atmel_pwm_chip *chip, unsigned int ch)
+{
+ /*
+ * Clear pending flags in hardware because otherwise there might still
+ * be a stale flag in ISR.
+ */
+ atmel_pwm_update_pending(chip);
+
+ chip->update_pending |= (1 << ch);
+}
+
+static int atmel_pwm_test_pending(struct atmel_pwm_chip *chip, unsigned int ch)
+{
+ int ret = 0;
+
+ if (chip->update_pending & (1 << ch)) {
+ atmel_pwm_update_pending(chip);
+
+ if (chip->update_pending & (1 << ch))
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static int atmel_pwm_wait_nonpending(struct atmel_pwm_chip *chip, unsigned int ch)
+{
+ unsigned long timeout = jiffies + 2 * HZ;
+ int ret;
+
+ while ((ret = atmel_pwm_test_pending(chip, ch)) &&
+ time_before(jiffies, timeout))
+ usleep_range(10, 100);
+
+ return ret ? -ETIMEDOUT : 0;
+}
+
static int atmel_pwm_calculate_cprd_and_pres(struct pwm_chip *chip,
+ unsigned long clkrate,
const struct pwm_state *state,
unsigned long *cprd, u32 *pres)
{
@@ -132,7 +187,7 @@ static int atmel_pwm_calculate_cprd_and_pres(struct pwm_chip *chip,
int shift;
/* Calculate the period cycles and prescale value */
- cycles *= clk_get_rate(atmel_pwm->clk);
+ cycles *= clkrate;
do_div(cycles, NSEC_PER_SEC);
/*
@@ -143,7 +198,7 @@ static int atmel_pwm_calculate_cprd_and_pres(struct pwm_chip *chip,
shift = fls(cycles) - atmel_pwm->data->cfg.period_bits;
if (shift > PWM_MAX_PRES) {
- dev_err(chip->dev, "pres exceeds the maximum value\n");
+ dev_err(pwmchip_parent(chip), "pres exceeds the maximum value\n");
return -EINVAL;
} else if (shift > 0) {
*pres = shift;
@@ -158,12 +213,14 @@ static int atmel_pwm_calculate_cprd_and_pres(struct pwm_chip *chip,
}
static void atmel_pwm_calculate_cdty(const struct pwm_state *state,
- unsigned long cprd, unsigned long *cdty)
+ unsigned long clkrate, unsigned long cprd,
+ u32 pres, unsigned long *cdty)
{
unsigned long long cycles = state->duty_cycle;
- cycles *= cprd;
- do_div(cycles, state->period);
+ cycles *= clkrate;
+ do_div(cycles, NSEC_PER_SEC);
+ cycles >>= pres;
*cdty = cprd - cycles;
}
@@ -182,6 +239,7 @@ static void atmel_pwm_update_cdty(struct pwm_chip *chip, struct pwm_device *pwm,
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm,
atmel_pwm->data->regs.duty_upd, cdty);
+ atmel_pwm_set_pending(atmel_pwm, pwm->hwpwm);
}
static void atmel_pwm_set_cprd_cdty(struct pwm_chip *chip,
@@ -200,22 +258,10 @@ 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;
+ unsigned long timeout;
- /*
- * 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);
- }
+ atmel_pwm_wait_nonpending(atmel_pwm, pwm->hwpwm);
- mutex_unlock(&atmel_pwm->isr_lock);
atmel_pwm_writel(atmel_pwm, PWM_DIS, 1 << pwm->hwpwm);
/*
@@ -236,40 +282,43 @@ 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) {
+ unsigned long clkrate = clk_get_rate(atmel_pwm->clk);
+
+ if (pwm->state.enabled &&
+ pwm->state.polarity == state->polarity &&
+ pwm->state.period == state->period) {
+ u32 cmr = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
+
cprd = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm,
atmel_pwm->data->regs.period);
- atmel_pwm_calculate_cdty(state, cprd, &cdty);
+ pres = cmr & PWM_CMR_CPRE_MSK;
+
+ atmel_pwm_calculate_cdty(state, clkrate, cprd, pres, &cdty);
atmel_pwm_update_cdty(chip, pwm, cdty);
return 0;
}
- ret = atmel_pwm_calculate_cprd_and_pres(chip, state, &cprd,
+ ret = atmel_pwm_calculate_cprd_and_pres(chip, clkrate, state, &cprd,
&pres);
if (ret) {
- dev_err(chip->dev,
+ dev_err(pwmchip_parent(chip),
"failed to calculate cprd and prescaler\n");
return ret;
}
- atmel_pwm_calculate_cdty(state, cprd, &cdty);
+ atmel_pwm_calculate_cdty(state, clkrate, cprd, pres, &cdty);
- if (cstate.enabled) {
+ if (pwm->state.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");
+ dev_err(pwmchip_parent(chip), "failed to enable clock\n");
return ret;
}
}
@@ -283,20 +332,16 @@ static int atmel_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
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) {
+ } else if (pwm->state.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)
+static int 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;
@@ -317,9 +362,12 @@ static void atmel_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
tmp <<= pres;
state->period = DIV64_U64_ROUND_UP(tmp, rate);
+ /* Wait for an updated duty_cycle queued in hardware */
+ atmel_pwm_wait_nonpending(atmel_pwm, pwm->hwpwm);
+
cdty = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm,
atmel_pwm->data->regs.duty);
- tmp = (u64)cdty * NSEC_PER_SEC;
+ tmp = (u64)(cprd - cdty) * NSEC_PER_SEC;
tmp <<= pres;
state->duty_cycle = DIV64_U64_ROUND_UP(tmp, rate);
@@ -332,12 +380,13 @@ static void atmel_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
state->polarity = PWM_POLARITY_INVERSED;
else
state->polarity = PWM_POLARITY_NORMAL;
+
+ return 0;
}
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 = {
@@ -398,74 +447,93 @@ static const struct of_device_id atmel_pwm_dt_ids[] = {
};
MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids);
+static int atmel_pwm_enable_clk_if_on(struct pwm_chip *chip, bool on)
+{
+ struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
+ unsigned int i, cnt = 0;
+ unsigned long sr;
+ int ret = 0;
+
+ sr = atmel_pwm_readl(atmel_pwm, PWM_SR) & PWM_SR_ALL_CH_MASK;
+ if (!sr)
+ return 0;
+
+ cnt = bitmap_weight(&sr, chip->npwm);
+
+ if (!on)
+ goto disable_clk;
+
+ for (i = 0; i < cnt; i++) {
+ ret = clk_enable(atmel_pwm->clk);
+ if (ret) {
+ dev_err(pwmchip_parent(chip),
+ "failed to enable clock for pwm %pe\n",
+ ERR_PTR(ret));
+
+ cnt = i;
+ goto disable_clk;
+ }
+ }
+
+ return 0;
+
+disable_clk:
+ while (cnt--)
+ clk_disable(atmel_pwm->clk);
+
+ return ret;
+}
+
static int atmel_pwm_probe(struct platform_device *pdev)
{
struct atmel_pwm_chip *atmel_pwm;
- struct resource *res;
+ struct pwm_chip *chip;
int ret;
- atmel_pwm = devm_kzalloc(&pdev->dev, sizeof(*atmel_pwm), GFP_KERNEL);
- if (!atmel_pwm)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, 4, sizeof(*atmel_pwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
- mutex_init(&atmel_pwm->isr_lock);
+ atmel_pwm = to_atmel_pwm_chip(chip);
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);
+ atmel_pwm->update_pending = 0;
+
+ atmel_pwm->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(atmel_pwm->base))
return PTR_ERR(atmel_pwm->base);
- atmel_pwm->clk = devm_clk_get(&pdev->dev, NULL);
+ atmel_pwm->clk = devm_clk_get_prepared(&pdev->dev, NULL);
if (IS_ERR(atmel_pwm->clk))
- return PTR_ERR(atmel_pwm->clk);
+ return dev_err_probe(&pdev->dev, PTR_ERR(atmel_pwm->clk),
+ "failed to get prepared PWM clock\n");
- ret = clk_prepare(atmel_pwm->clk);
- if (ret) {
- dev_err(&pdev->dev, "failed to prepare PWM clock\n");
- return ret;
- }
+ chip->ops = &atmel_pwm_ops;
- 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 = atmel_pwm_enable_clk_if_on(chip, true);
+ if (ret < 0)
+ return ret;
- ret = pwmchip_add(&atmel_pwm->chip);
+ ret = devm_pwmchip_add(&pdev->dev, chip);
if (ret < 0) {
- dev_err(&pdev->dev, "failed to add PWM chip %d\n", ret);
- goto unprepare_clk;
+ dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n");
+ goto disable_clk;
}
- platform_set_drvdata(pdev, atmel_pwm);
+ return 0;
- return ret;
+disable_clk:
+ atmel_pwm_enable_clk_if_on(chip, false);
-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),
+ .of_match_table = atmel_pwm_dt_ids,
},
.probe = atmel_pwm_probe,
- .remove = atmel_pwm_remove,
};
module_platform_driver(atmel_pwm_driver);
diff --git a/drivers/pwm/pwm-axi-pwmgen.c b/drivers/pwm/pwm-axi-pwmgen.c
new file mode 100644
index 000000000000..b40522f01002
--- /dev/null
+++ b/drivers/pwm/pwm-axi-pwmgen.c
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Analog Devices AXI PWM generator
+ *
+ * Copyright 2024 Analog Devices Inc.
+ * Copyright 2024 Baylibre SAS
+ *
+ * Device docs: https://analogdevicesinc.github.io/hdl/library/axi_pwm_gen/index.html
+ *
+ * Limitations:
+ * - The writes to registers for period and duty are shadowed until
+ * LOAD_CONFIG is written to AXI_PWMGEN_REG_RSTN, at which point
+ * they take effect.
+ * - Writing LOAD_CONFIG also has the effect of re-synchronizing all
+ * enabled channels, which could cause glitching on other channels. It
+ * is therefore expected that channels are assigned harmonic periods
+ * and all have a single user coordinating this.
+ * - Supports normal polarity. Does not support changing polarity.
+ * - On disable, the PWM output becomes low (inactive).
+ */
+#include <linux/adi-axi-common.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define AXI_PWMGEN_REG_ID 0x04
+#define AXI_PWMGEN_REG_SCRATCHPAD 0x08
+#define AXI_PWMGEN_REG_CORE_MAGIC 0x0C
+#define AXI_PWMGEN_REG_RSTN 0x10
+#define AXI_PWMGEN_REG_RSTN_LOAD_CONFIG BIT(1)
+#define AXI_PWMGEN_REG_RSTN_RESET BIT(0)
+#define AXI_PWMGEN_REG_NPWM 0x14
+#define AXI_PWMGEN_REG_CONFIG 0x18
+#define AXI_PWMGEN_REG_CONFIG_FORCE_ALIGN BIT(1)
+#define AXI_PWMGEN_CHX_PERIOD(ch) (0x40 + (4 * (ch)))
+#define AXI_PWMGEN_CHX_DUTY(ch) (0x80 + (4 * (ch)))
+#define AXI_PWMGEN_CHX_OFFSET(ch) (0xC0 + (4 * (ch)))
+#define AXI_PWMGEN_REG_CORE_MAGIC_VAL 0x601A3471 /* Identification number to test during setup */
+
+struct axi_pwmgen_ddata {
+ struct regmap *regmap;
+ unsigned long clk_rate_hz;
+};
+
+static const struct regmap_config axi_pwmgen_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0xFC,
+};
+
+/* This represents a hardware configuration for one channel */
+struct axi_pwmgen_waveform {
+ u32 period_cnt;
+ u32 duty_cycle_cnt;
+ u32 duty_offset_cnt;
+};
+
+static struct axi_pwmgen_ddata *axi_pwmgen_ddata_from_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_waveform *wf,
+ void *_wfhw)
+{
+ struct axi_pwmgen_waveform *wfhw = _wfhw;
+ struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip);
+ int ret = 0;
+
+ if (wf->period_length_ns == 0) {
+ *wfhw = (struct axi_pwmgen_waveform){
+ .period_cnt = 0,
+ .duty_cycle_cnt = 0,
+ .duty_offset_cnt = 0,
+ };
+ } else {
+ /* With ddata->clk_rate_hz < NSEC_PER_SEC this won't overflow. */
+ wfhw->period_cnt = min_t(u64,
+ mul_u64_u32_div(wf->period_length_ns, ddata->clk_rate_hz, NSEC_PER_SEC),
+ U32_MAX);
+
+ if (wfhw->period_cnt == 0) {
+ /*
+ * The specified period is too short for the hardware.
+ * So round up .period_cnt to 1 (i.e. the smallest
+ * possible period). With .duty_cycle and .duty_offset
+ * being less than or equal to .period, their rounded
+ * value must be 0.
+ */
+ wfhw->period_cnt = 1;
+ wfhw->duty_cycle_cnt = 0;
+ wfhw->duty_offset_cnt = 0;
+ ret = 1;
+ } else {
+ wfhw->duty_cycle_cnt = min_t(u64,
+ mul_u64_u32_div(wf->duty_length_ns, ddata->clk_rate_hz, NSEC_PER_SEC),
+ U32_MAX);
+ wfhw->duty_offset_cnt = min_t(u64,
+ mul_u64_u32_div(wf->duty_offset_ns, ddata->clk_rate_hz, NSEC_PER_SEC),
+ U32_MAX);
+ }
+ }
+
+ dev_dbg(&chip->dev, "pwm#%u: %lld/%lld [+%lld] @%lu -> PERIOD: %08x, DUTY: %08x, OFFSET: %08x\n",
+ pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
+ ddata->clk_rate_hz, wfhw->period_cnt, wfhw->duty_cycle_cnt, wfhw->duty_offset_cnt);
+
+ return ret;
+}
+
+static int axi_pwmgen_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
+ const void *_wfhw, struct pwm_waveform *wf)
+{
+ const struct axi_pwmgen_waveform *wfhw = _wfhw;
+ struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip);
+
+ wf->period_length_ns = DIV64_U64_ROUND_UP((u64)wfhw->period_cnt * NSEC_PER_SEC,
+ ddata->clk_rate_hz);
+
+ wf->duty_length_ns = DIV64_U64_ROUND_UP((u64)wfhw->duty_cycle_cnt * NSEC_PER_SEC,
+ ddata->clk_rate_hz);
+
+ wf->duty_offset_ns = DIV64_U64_ROUND_UP((u64)wfhw->duty_offset_cnt * NSEC_PER_SEC,
+ ddata->clk_rate_hz);
+
+ return 0;
+}
+
+static int axi_pwmgen_write_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw)
+{
+ const struct axi_pwmgen_waveform *wfhw = _wfhw;
+ struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip);
+ struct regmap *regmap = ddata->regmap;
+ unsigned int ch = pwm->hwpwm;
+ int ret;
+
+ ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), wfhw->period_cnt);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), wfhw->duty_cycle_cnt);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(regmap, AXI_PWMGEN_CHX_OFFSET(ch), wfhw->duty_offset_cnt);
+ if (ret)
+ return ret;
+
+ return regmap_write(regmap, AXI_PWMGEN_REG_RSTN, AXI_PWMGEN_REG_RSTN_LOAD_CONFIG);
+}
+
+static int axi_pwmgen_read_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ void *_wfhw)
+{
+ struct axi_pwmgen_waveform *wfhw = _wfhw;
+ struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip);
+ struct regmap *regmap = ddata->regmap;
+ unsigned int ch = pwm->hwpwm;
+ int ret;
+
+ ret = regmap_read(regmap, AXI_PWMGEN_CHX_PERIOD(ch), &wfhw->period_cnt);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(regmap, AXI_PWMGEN_CHX_DUTY(ch), &wfhw->duty_cycle_cnt);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(regmap, AXI_PWMGEN_CHX_OFFSET(ch), &wfhw->duty_offset_cnt);
+ if (ret)
+ return ret;
+
+ if (wfhw->duty_cycle_cnt > wfhw->period_cnt)
+ wfhw->duty_cycle_cnt = wfhw->period_cnt;
+
+ /* XXX: is this the actual behaviour of the hardware? */
+ if (wfhw->duty_offset_cnt >= wfhw->period_cnt) {
+ wfhw->duty_cycle_cnt = 0;
+ wfhw->duty_offset_cnt = 0;
+ }
+
+ return 0;
+}
+
+static const struct pwm_ops axi_pwmgen_pwm_ops = {
+ .sizeof_wfhw = sizeof(struct axi_pwmgen_waveform),
+ .round_waveform_tohw = axi_pwmgen_round_waveform_tohw,
+ .round_waveform_fromhw = axi_pwmgen_round_waveform_fromhw,
+ .read_waveform = axi_pwmgen_read_waveform,
+ .write_waveform = axi_pwmgen_write_waveform,
+};
+
+static int axi_pwmgen_setup(struct regmap *regmap, struct device *dev)
+{
+ int ret;
+ u32 val;
+
+ ret = regmap_read(regmap, AXI_PWMGEN_REG_CORE_MAGIC, &val);
+ if (ret)
+ return ret;
+
+ if (val != AXI_PWMGEN_REG_CORE_MAGIC_VAL)
+ return dev_err_probe(dev, -ENODEV,
+ "failed to read expected value from register: got %08x, expected %08x\n",
+ val, AXI_PWMGEN_REG_CORE_MAGIC_VAL);
+
+ ret = regmap_read(regmap, ADI_AXI_REG_VERSION, &val);
+ if (ret)
+ return ret;
+
+ if (ADI_AXI_PCORE_VER_MAJOR(val) != 2) {
+ return dev_err_probe(dev, -ENODEV, "Unsupported peripheral version %u.%u.%u\n",
+ ADI_AXI_PCORE_VER_MAJOR(val),
+ ADI_AXI_PCORE_VER_MINOR(val),
+ ADI_AXI_PCORE_VER_PATCH(val));
+ }
+
+ /* Enable the core */
+ ret = regmap_clear_bits(regmap, AXI_PWMGEN_REG_RSTN, AXI_PWMGEN_REG_RSTN_RESET);
+ if (ret)
+ return ret;
+
+ /*
+ * Enable force align so that changes to PWM period and duty cycle take
+ * effect immediately. Otherwise, the effect of the change is delayed
+ * until the period of all channels run out, which can be long after the
+ * apply function returns.
+ */
+ ret = regmap_set_bits(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_REG_CONFIG_FORCE_ALIGN);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(regmap, AXI_PWMGEN_REG_NPWM, &val);
+ if (ret)
+ return ret;
+
+ /* Return the number of PWMs */
+ return val;
+}
+
+static int axi_pwmgen_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct regmap *regmap;
+ struct pwm_chip *chip;
+ struct axi_pwmgen_ddata *ddata;
+ struct clk *axi_clk, *clk;
+ void __iomem *io_base;
+ int ret;
+
+ io_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(io_base))
+ return PTR_ERR(io_base);
+
+ regmap = devm_regmap_init_mmio(dev, io_base, &axi_pwmgen_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap),
+ "failed to init register map\n");
+
+ ret = axi_pwmgen_setup(regmap, dev);
+ if (ret < 0)
+ return ret;
+
+ chip = devm_pwmchip_alloc(dev, ret, sizeof(*ddata));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ ddata = pwmchip_get_drvdata(chip);
+ ddata->regmap = regmap;
+
+ /*
+ * Using NULL here instead of "axi" for backwards compatibility. There
+ * are some dtbs that don't give clock-names and have the "ext" clock
+ * as the one and only clock (due to mistake in the original bindings).
+ */
+ axi_clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(axi_clk))
+ return dev_err_probe(dev, PTR_ERR(axi_clk), "failed to get axi clock\n");
+
+ clk = devm_clk_get_optional_enabled(dev, "ext");
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "failed to get ext clock\n");
+
+ /*
+ * If there is no "ext" clock, it means the HDL was compiled with
+ * ASYNC_CLK_EN=0. In this case, the AXI clock is also used for the
+ * PWM output clock.
+ */
+ if (!clk)
+ clk = axi_clk;
+
+ ret = devm_clk_rate_exclusive_get(dev, clk);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get exclusive rate\n");
+
+ ddata->clk_rate_hz = clk_get_rate(clk);
+ if (!ddata->clk_rate_hz || ddata->clk_rate_hz > NSEC_PER_SEC)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid clock rate: %lu\n", ddata->clk_rate_hz);
+
+ chip->ops = &axi_pwmgen_pwm_ops;
+ chip->atomic = true;
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "could not add PWM chip\n");
+
+ return 0;
+}
+
+static const struct of_device_id axi_pwmgen_ids[] = {
+ { .compatible = "adi,axi-pwmgen-2.00.a" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, axi_pwmgen_ids);
+
+static struct platform_driver axi_pwmgen_driver = {
+ .driver = {
+ .name = "axi-pwmgen",
+ .of_match_table = axi_pwmgen_ids,
+ },
+ .probe = axi_pwmgen_probe,
+};
+module_platform_driver(axi_pwmgen_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sergiu Cuciurean <sergiu.cuciurean@analog.com>");
+MODULE_AUTHOR("Trevor Gamblin <tgamblin@baylibre.com>");
+MODULE_DESCRIPTION("Driver for the Analog Devices AXI PWM generator");
diff --git a/drivers/pwm/pwm-bcm-iproc.c b/drivers/pwm/pwm-bcm-iproc.c
index 1f829edd8ee7..f4c9f10e490e 100644
--- a/drivers/pwm/pwm-bcm-iproc.c
+++ b/drivers/pwm/pwm-bcm-iproc.c
@@ -1,15 +1,5 @@
-/*
- * 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.
- */
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (C) 2016 Broadcom
#include <linux/clk.h>
#include <linux/delay.h>
@@ -44,14 +34,13 @@
#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);
+ return pwmchip_get_drvdata(chip);
}
static void iproc_pwmc_enable(struct iproc_pwmc *ip, unsigned int channel)
@@ -78,15 +67,13 @@ static void iproc_pwmc_disable(struct iproc_pwmc *ip, unsigned int channel)
ndelay(400);
}
-static void iproc_pwmc_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
- struct pwm_state *state)
+static int 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;
- rate = clk_get_rate(ip->clk);
-
value = readl(ip->base + IPROC_PWM_CTRL_OFFSET);
if (value & BIT(IPROC_PWM_CTRL_EN_SHIFT(pwm->hwpwm)))
@@ -99,6 +86,13 @@ static void iproc_pwmc_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
else
state->polarity = PWM_POLARITY_INVERSED;
+ rate = clk_get_rate(ip->clk);
+ if (rate == 0) {
+ state->period = 0;
+ state->duty_cycle = 0;
+ return 0;
+ }
+
value = readl(ip->base + IPROC_PWM_PRESCALE_OFFSET);
prescale = value >> IPROC_PWM_PRESCALE_SHIFT(pwm->hwpwm);
prescale &= IPROC_PWM_PRESCALE_MAX;
@@ -112,6 +106,8 @@ static void iproc_pwmc_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+
+ return 0;
}
static int iproc_pwmc_apply(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -143,8 +139,7 @@ static int iproc_pwmc_apply(struct pwm_chip *chip, struct pwm_device *pwm,
value = rate * state->duty_cycle;
duty = div64_u64(value, div);
- if (period < IPROC_PWM_PERIOD_MIN ||
- duty < IPROC_PWM_DUTY_CYCLE_MIN)
+ if (period < IPROC_PWM_PERIOD_MIN)
return -EINVAL;
if (period <= IPROC_PWM_PERIOD_MAX &&
@@ -187,74 +182,50 @@ static int iproc_pwmc_apply(struct pwm_chip *chip, struct pwm_device *pwm,
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 pwm_chip *chip;
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;
+ chip = devm_pwmchip_alloc(&pdev->dev, 4, sizeof(*ip));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ ip = to_iproc_pwmc(chip);
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;
+ chip->ops = &iproc_pwm_ops;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- ip->base = devm_ioremap_resource(&pdev->dev, res);
+ ip->base = devm_platform_ioremap_resource(pdev, 0);
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;
- }
+ ip->clk = devm_clk_get_enabled(&pdev->dev, NULL);
+ if (IS_ERR(ip->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(ip->clk),
+ "failed to get clock\n");
/* 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++) {
+ for (i = 0; i < 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);
+ ret = devm_pwmchip_add(&pdev->dev, chip);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to add PWM chip\n");
- return pwmchip_remove(&ip->chip);
+ return 0;
}
static const struct of_device_id bcm_iproc_pwmc_dt[] = {
@@ -269,7 +240,6 @@ static struct platform_driver iproc_pwmc_driver = {
.of_match_table = bcm_iproc_pwmc_dt,
},
.probe = iproc_pwmc_probe,
- .remove = iproc_pwmc_remove,
};
module_platform_driver(iproc_pwmc_driver);
diff --git a/drivers/pwm/pwm-bcm-kona.c b/drivers/pwm/pwm-bcm-kona.c
index 81da91df2529..022c078aae84 100644
--- a/drivers/pwm/pwm-bcm-kona.c
+++ b/drivers/pwm/pwm-bcm-kona.c
@@ -1,15 +1,5 @@
-/*
- * 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.
- */
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (C) 2014 Broadcom Corporation
#include <linux/clk.h>
#include <linux/delay.h>
@@ -66,14 +56,13 @@
#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)
+static inline struct kona_pwmc *to_kona_pwmc(struct pwm_chip *chip)
{
- return container_of(_chip, struct kona_pwmc, chip);
+ return pwmchip_get_drvdata(chip);
}
/*
@@ -109,10 +98,10 @@ static void kona_pwmc_apply_settings(struct kona_pwmc *kp, unsigned int chan)
}
static int kona_pwmc_config(struct pwm_chip *chip, struct pwm_device *pwm,
- int duty_ns, int period_ns)
+ u64 duty_ns, u64 period_ns)
{
struct kona_pwmc *kp = to_kona_pwmc(chip);
- u64 val, div, rate;
+ u64 div, rate;
unsigned long prescale = PRESCALE_MIN, pc, dc;
unsigned int value, chan = pwm->hwpwm;
@@ -132,13 +121,11 @@ static int kona_pwmc_config(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+ pc = mul_u64_u64_div_u64(rate, period_ns, div);
+ dc = mul_u64_u64_div_u64(rate, duty_ns, div);
/* If duty_ns or period_ns are not achievable then return */
- if (pc < PERIOD_COUNT_MIN || dc < DUTY_CYCLE_HIGH_MIN)
+ if (pc < PERIOD_COUNT_MIN)
return -EINVAL;
/* If pc and dc are in bounds, the calculation is done */
@@ -150,25 +137,18 @@ static int kona_pwmc_config(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+ 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);
+ 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(pc, kp->base + PERIOD_COUNT_OFFSET(chan));
- writel(dc, kp->base + DUTY_CYCLE_HIGH_OFFSET(chan));
+ writel(dc, kp->base + DUTY_CYCLE_HIGH_OFFSET(chan));
- kona_pwmc_apply_settings(kp, chan);
- }
+ kona_pwmc_apply_settings(kp, chan);
return 0;
}
@@ -183,7 +163,7 @@ static int kona_pwmc_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
ret = clk_prepare_enable(kp->clk);
if (ret < 0) {
- dev_err(chip->dev, "failed to enable clock: %d\n", ret);
+ dev_err(pwmchip_parent(chip), "failed to enable clock: %d\n", ret);
return ret;
}
@@ -212,14 +192,7 @@ static int kona_pwmc_enable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "failed to enable clock: %d\n", ret);
return ret;
}
@@ -248,37 +221,71 @@ static void kona_pwmc_disable(struct pwm_chip *chip, struct pwm_device *pwm)
clk_disable_unprepare(kp->clk);
}
+static int kona_pwmc_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+ struct kona_pwmc *kp = to_kona_pwmc(chip);
+ bool enabled = pwm->state.enabled;
+
+ if (state->polarity != pwm->state.polarity) {
+ if (enabled) {
+ kona_pwmc_disable(chip, pwm);
+ enabled = false;
+ }
+
+ err = kona_pwmc_set_polarity(chip, pwm, state->polarity);
+ if (err)
+ return err;
+
+ pwm->state.polarity = state->polarity;
+ }
+
+ if (!state->enabled) {
+ if (enabled)
+ kona_pwmc_disable(chip, pwm);
+ return 0;
+ } else if (!enabled) {
+ /*
+ * This is a bit special here, usually the PWM should only be
+ * enabled when duty and period are setup. But before this
+ * driver was converted to .apply it was done the other way
+ * around and so this behaviour was kept even though this might
+ * result in a glitch. This might be improvable by someone with
+ * hardware and/or documentation.
+ */
+ err = kona_pwmc_enable(chip, pwm);
+ if (err)
+ return err;
+ }
+
+ err = kona_pwmc_config(chip, pwm, state->duty_cycle, state->period);
+ if (err && !pwm->state.enabled)
+ clk_disable_unprepare(kp->clk);
+
+ return err;
+}
+
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,
+ .apply = kona_pwmc_apply,
};
static int kona_pwmc_probe(struct platform_device *pdev)
{
+ struct pwm_chip *chip;
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);
+ chip = devm_pwmchip_alloc(&pdev->dev, 6, sizeof(*kp));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ kp = to_kona_pwmc(chip);
- 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;
+ chip->ops = &kona_pwm_ops;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- kp->base = devm_ioremap_resource(&pdev->dev, res);
+ kp->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(kp->base))
return PTR_ERR(kp->base);
@@ -296,32 +303,20 @@ static int kona_pwmc_probe(struct platform_device *pdev)
}
/* Set push/pull for all channels */
- for (chan = 0; chan < kp->chip.npwm; chan++)
+ for (chan = 0; chan < 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);
+ ret = devm_pwmchip_add(&pdev->dev, chip);
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" },
{ },
@@ -334,7 +329,6 @@ static struct platform_driver kona_pwmc_driver = {
.of_match_table = bcm_kona_pwmc_dt,
},
.probe = kona_pwmc_probe,
- .remove = kona_pwmc_remove,
};
module_platform_driver(kona_pwmc_driver);
diff --git a/drivers/pwm/pwm-bcm2835.c b/drivers/pwm/pwm-bcm2835.c
index d78f86f8e462..532903da521f 100644
--- a/drivers/pwm/pwm-bcm2835.c
+++ b/drivers/pwm/pwm-bcm2835.c
@@ -24,175 +24,146 @@
#define PERIOD_MIN 0x2
struct bcm2835_pwm {
- struct pwm_chip chip;
- struct device *dev;
void __iomem *base;
struct clk *clk;
+ unsigned long rate;
};
static inline struct bcm2835_pwm *to_bcm2835_pwm(struct pwm_chip *chip)
{
- return container_of(chip, struct bcm2835_pwm, chip);
+ return pwmchip_get_drvdata(chip);
}
-static int bcm2835_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+static int bcm2835_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
{
- 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");
+ unsigned long long period_cycles;
+ u64 max_period;
+
+ u32 val;
+
+ /*
+ * period_cycles must be a 32 bit value, so period * rate / NSEC_PER_SEC
+ * must be <= U32_MAX. As U32_MAX * NSEC_PER_SEC < U64_MAX the
+ * multiplication period * rate doesn't overflow.
+ * To calculate the maximal possible period that guarantees the
+ * above inequality:
+ *
+ * round(period * rate / NSEC_PER_SEC) <= U32_MAX
+ * <=> period * rate / NSEC_PER_SEC < U32_MAX + 0.5
+ * <=> period * rate < (U32_MAX + 0.5) * NSEC_PER_SEC
+ * <=> period < ((U32_MAX + 0.5) * NSEC_PER_SEC) / rate
+ * <=> period < ((U32_MAX * NSEC_PER_SEC + NSEC_PER_SEC/2) / rate
+ * <=> period <= ceil((U32_MAX * NSEC_PER_SEC + NSEC_PER_SEC/2) / rate) - 1
+ */
+ max_period = DIV_ROUND_UP_ULL((u64)U32_MAX * NSEC_PER_SEC + NSEC_PER_SEC / 2, pc->rate) - 1;
+
+ if (state->period > max_period)
return -EINVAL;
- }
- scaler = DIV_ROUND_CLOSEST(NSEC_PER_SEC, rate);
- period = DIV_ROUND_CLOSEST(period_ns, scaler);
+ /* set period */
+ period_cycles = DIV_ROUND_CLOSEST_ULL(state->period * pc->rate, NSEC_PER_SEC);
- if (period < PERIOD_MIN)
+ /* don't accept a period that is too small */
+ if (period_cycles < 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;
-}
+ writel(period_cycles, pc->base + PERIOD(pwm->hwpwm));
-static int bcm2835_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
-{
- struct bcm2835_pwm *pc = to_bcm2835_pwm(chip);
- u32 value;
+ /* set duty cycle */
+ val = DIV_ROUND_CLOSEST_ULL(state->duty_cycle * pc->rate, NSEC_PER_SEC);
+ writel(val, pc->base + DUTY(pwm->hwpwm));
- value = readl(pc->base + PWM_CONTROL);
- value |= PWM_ENABLE << PWM_CONTROL_SHIFT(pwm->hwpwm);
- writel(value, pc->base + PWM_CONTROL);
+ /* set polarity */
+ val = readl(pc->base + PWM_CONTROL);
- return 0;
-}
+ val &= ~(PWM_CONTROL_MASK << PWM_CONTROL_SHIFT(pwm->hwpwm));
+ val |= PWM_MODE << PWM_CONTROL_SHIFT(pwm->hwpwm);
-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 (state->polarity == PWM_POLARITY_NORMAL)
+ val &= ~(PWM_POLARITY << PWM_CONTROL_SHIFT(pwm->hwpwm));
+ else
+ val |= PWM_POLARITY << PWM_CONTROL_SHIFT(pwm->hwpwm);
- if (polarity == PWM_POLARITY_NORMAL)
- value &= ~(PWM_POLARITY << PWM_CONTROL_SHIFT(pwm->hwpwm));
+ /* enable/disable */
+ if (state->enabled)
+ val |= PWM_ENABLE << PWM_CONTROL_SHIFT(pwm->hwpwm);
else
- value |= PWM_POLARITY << PWM_CONTROL_SHIFT(pwm->hwpwm);
+ val &= ~(PWM_ENABLE << PWM_CONTROL_SHIFT(pwm->hwpwm));
- writel(value, pc->base + PWM_CONTROL);
+ writel(val, 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,
+ .apply = bcm2835_pwm_apply,
};
static int bcm2835_pwm_probe(struct platform_device *pdev)
{
+ struct device *dev = &pdev->dev;
+ struct pwm_chip *chip;
struct bcm2835_pwm *pc;
- struct resource *res;
int ret;
- pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
- if (!pc)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(dev, 2, sizeof(*pc));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ pc = to_bcm2835_pwm(chip);
- pc->dev = &pdev->dev;
-
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- pc->base = devm_ioremap_resource(&pdev->dev, res);
+ pc->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(pc->base))
return PTR_ERR(pc->base);
- pc->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(pc->clk)) {
- ret = PTR_ERR(pc->clk);
- if (ret != -EPROBE_DEFER)
- dev_err(&pdev->dev, "clock not found: %d\n", ret);
-
- return ret;
- }
+ pc->clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(pc->clk))
+ return dev_err_probe(dev, PTR_ERR(pc->clk),
+ "clock not found\n");
- ret = clk_prepare_enable(pc->clk);
+ ret = devm_clk_rate_exclusive_get(dev, pc->clk);
if (ret)
- return ret;
+ return dev_err_probe(dev, ret,
+ "fail to get exclusive rate\n");
- 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;
+ pc->rate = clk_get_rate(pc->clk);
+ if (!pc->rate)
+ return dev_err_probe(dev, -EINVAL,
+ "failed to get clock rate\n");
+
+ chip->ops = &bcm2835_pwm_ops;
+ chip->atomic = true;
platform_set_drvdata(pdev, pc);
- ret = pwmchip_add(&pc->chip);
+ ret = devm_pwmchip_add(dev, chip);
if (ret < 0)
- goto add_fail;
+ return dev_err_probe(dev, ret, "failed to add pwmchip\n");
return 0;
-
-add_fail:
- clk_disable_unprepare(pc->clk);
- return ret;
}
-static int bcm2835_pwm_remove(struct platform_device *pdev)
+static int bcm2835_pwm_suspend(struct device *dev)
{
- struct bcm2835_pwm *pc = platform_get_drvdata(pdev);
+ struct bcm2835_pwm *pc = dev_get_drvdata(dev);
clk_disable_unprepare(pc->clk);
- return pwmchip_remove(&pc->chip);
+ return 0;
}
+static int bcm2835_pwm_resume(struct device *dev)
+{
+ struct bcm2835_pwm *pc = dev_get_drvdata(dev);
+
+ return clk_prepare_enable(pc->clk);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(bcm2835_pwm_pm_ops, bcm2835_pwm_suspend,
+ bcm2835_pwm_resume);
+
static const struct of_device_id bcm2835_pwm_of_match[] = {
{ .compatible = "brcm,bcm2835-pwm", },
{ /* sentinel */ }
@@ -203,9 +174,9 @@ static struct platform_driver bcm2835_pwm_driver = {
.driver = {
.name = "bcm2835-pwm",
.of_match_table = bcm2835_pwm_of_match,
+ .pm = pm_ptr(&bcm2835_pwm_pm_ops),
},
.probe = bcm2835_pwm_probe,
- .remove = bcm2835_pwm_remove,
};
module_platform_driver(bcm2835_pwm_driver);
diff --git a/drivers/pwm/pwm-berlin.c b/drivers/pwm/pwm-berlin.c
index b91c477cc84b..858d36991374 100644
--- a/drivers/pwm/pwm-berlin.c
+++ b/drivers/pwm/pwm-berlin.c
@@ -13,6 +13,7 @@
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
@@ -38,6 +39,8 @@
#define BERLIN_PWM_TCNT 0xc
#define BERLIN_PWM_MAX_TCNT 65535
+#define BERLIN_PWM_NUMPWMS 4
+
struct berlin_pwm_channel {
u32 enable;
u32 ctrl;
@@ -46,56 +49,38 @@ struct berlin_pwm_channel {
};
struct berlin_pwm_chip {
- struct pwm_chip chip;
struct clk *clk;
void __iomem *base;
+ struct berlin_pwm_channel channel[BERLIN_PWM_NUMPWMS];
};
static inline struct berlin_pwm_chip *to_berlin_pwm_chip(struct pwm_chip *chip)
{
- return container_of(chip, struct berlin_pwm_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
-static inline u32 berlin_pwm_readl(struct berlin_pwm_chip *chip,
+static inline u32 berlin_pwm_readl(struct berlin_pwm_chip *bpc,
unsigned int channel, unsigned long offset)
{
- return readl_relaxed(chip->base + channel * 0x10 + offset);
+ return readl_relaxed(bpc->base + channel * 0x10 + offset);
}
-static inline void berlin_pwm_writel(struct berlin_pwm_chip *chip,
+static inline void berlin_pwm_writel(struct berlin_pwm_chip *bpc,
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);
+ writel_relaxed(value, bpc->base + channel * 0x10 + offset);
}
-static int berlin_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm_dev,
- int duty_ns, int period_ns)
+static int berlin_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+ u64 duty_ns, u64 period_ns)
{
- struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip);
+ struct berlin_pwm_chip *bpc = to_berlin_pwm_chip(chip);
bool prescale_4096 = false;
u32 value, duty, period;
u64 cycles;
- cycles = clk_get_rate(pwm->clk);
+ cycles = clk_get_rate(bpc->clk);
cycles *= period_ns;
do_div(cycles, NSEC_PER_SEC);
@@ -112,69 +97,96 @@ static int berlin_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm_dev,
do_div(cycles, period_ns);
duty = cycles;
- value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_CONTROL);
+ value = berlin_pwm_readl(bpc, pwm->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(bpc, pwm->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);
+ berlin_pwm_writel(bpc, pwm->hwpwm, duty, BERLIN_PWM_DUTY);
+ berlin_pwm_writel(bpc, pwm->hwpwm, period, BERLIN_PWM_TCNT);
return 0;
}
static int berlin_pwm_set_polarity(struct pwm_chip *chip,
- struct pwm_device *pwm_dev,
+ struct pwm_device *pwm,
enum pwm_polarity polarity)
{
- struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip);
+ struct berlin_pwm_chip *bpc = to_berlin_pwm_chip(chip);
u32 value;
- value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_CONTROL);
+ value = berlin_pwm_readl(bpc, pwm->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);
+ berlin_pwm_writel(bpc, pwm->hwpwm, value, BERLIN_PWM_CONTROL);
return 0;
}
-static int berlin_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm_dev)
+static int berlin_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
- struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip);
+ struct berlin_pwm_chip *bpc = to_berlin_pwm_chip(chip);
u32 value;
- value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_EN);
+ value = berlin_pwm_readl(bpc, pwm->hwpwm, BERLIN_PWM_EN);
value |= BERLIN_PWM_ENABLE;
- berlin_pwm_writel(pwm, pwm_dev->hwpwm, value, BERLIN_PWM_EN);
+ berlin_pwm_writel(bpc, pwm->hwpwm, value, BERLIN_PWM_EN);
return 0;
}
static void berlin_pwm_disable(struct pwm_chip *chip,
- struct pwm_device *pwm_dev)
+ struct pwm_device *pwm)
{
- struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip);
+ struct berlin_pwm_chip *bpc = to_berlin_pwm_chip(chip);
u32 value;
- value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_EN);
+ value = berlin_pwm_readl(bpc, pwm->hwpwm, BERLIN_PWM_EN);
value &= ~BERLIN_PWM_ENABLE;
- berlin_pwm_writel(pwm, pwm_dev->hwpwm, value, BERLIN_PWM_EN);
+ berlin_pwm_writel(bpc, pwm->hwpwm, value, BERLIN_PWM_EN);
+}
+
+static int berlin_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+ bool enabled = pwm->state.enabled;
+
+ if (state->polarity != pwm->state.polarity) {
+ if (enabled) {
+ berlin_pwm_disable(chip, pwm);
+ enabled = false;
+ }
+
+ err = berlin_pwm_set_polarity(chip, pwm, state->polarity);
+ if (err)
+ return err;
+ }
+
+ if (!state->enabled) {
+ if (enabled)
+ berlin_pwm_disable(chip, pwm);
+ return 0;
+ }
+
+ err = berlin_pwm_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!enabled)
+ return berlin_pwm_enable(chip, pwm);
+
+ return 0;
}
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,
+ .apply = berlin_pwm_apply,
};
static const struct of_device_id berlin_pwm_match[] = {
@@ -185,118 +197,86 @@ MODULE_DEVICE_TABLE(of, berlin_pwm_match);
static int berlin_pwm_probe(struct platform_device *pdev)
{
- struct berlin_pwm_chip *pwm;
- struct resource *res;
+ struct pwm_chip *chip;
+ struct berlin_pwm_chip *bpc;
int ret;
- pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
- if (!pwm)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, BERLIN_PWM_NUMPWMS, sizeof(*bpc));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ bpc = to_berlin_pwm_chip(chip);
- 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);
+ bpc->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(bpc->base))
+ return PTR_ERR(bpc->base);
- pwm->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(pwm->clk))
- return PTR_ERR(pwm->clk);
+ bpc->clk = devm_clk_get_enabled(&pdev->dev, NULL);
+ if (IS_ERR(bpc->clk))
+ return PTR_ERR(bpc->clk);
- ret = clk_prepare_enable(pwm->clk);
- if (ret)
- return ret;
+ chip->ops = &berlin_pwm_ops;
- 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;
- }
+ ret = devm_pwmchip_add(&pdev->dev, chip);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n");
- platform_set_drvdata(pdev, pwm);
+ platform_set_drvdata(pdev, chip);
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);
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct berlin_pwm_chip *bpc = to_berlin_pwm_chip(chip);
unsigned int i;
- for (i = 0; i < pwm->chip.npwm; i++) {
- struct berlin_pwm_channel *channel;
+ for (i = 0; i < chip->npwm; i++) {
+ struct berlin_pwm_channel *channel = &bpc->channel[i];
- 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);
+ channel->enable = berlin_pwm_readl(bpc, i, BERLIN_PWM_EN);
+ channel->ctrl = berlin_pwm_readl(bpc, i, BERLIN_PWM_CONTROL);
+ channel->duty = berlin_pwm_readl(bpc, i, BERLIN_PWM_DUTY);
+ channel->tcnt = berlin_pwm_readl(bpc, i, BERLIN_PWM_TCNT);
}
- clk_disable_unprepare(pwm->clk);
+ clk_disable_unprepare(bpc->clk);
return 0;
}
static int berlin_pwm_resume(struct device *dev)
{
- struct berlin_pwm_chip *pwm = dev_get_drvdata(dev);
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct berlin_pwm_chip *bpc = to_berlin_pwm_chip(chip);
unsigned int i;
int ret;
- ret = clk_prepare_enable(pwm->clk);
+ ret = clk_prepare_enable(bpc->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;
+ for (i = 0; i < chip->npwm; i++) {
+ struct berlin_pwm_channel *channel = &bpc->channel[i];
- 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);
+ berlin_pwm_writel(bpc, i, channel->ctrl, BERLIN_PWM_CONTROL);
+ berlin_pwm_writel(bpc, i, channel->duty, BERLIN_PWM_DUTY);
+ berlin_pwm_writel(bpc, i, channel->tcnt, BERLIN_PWM_TCNT);
+ berlin_pwm_writel(bpc, i, channel->enable, BERLIN_PWM_EN);
}
return 0;
}
-#endif
-static SIMPLE_DEV_PM_OPS(berlin_pwm_pm_ops, berlin_pwm_suspend,
- berlin_pwm_resume);
+static DEFINE_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,
+ .pm = pm_ptr(&berlin_pwm_pm_ops),
},
};
module_platform_driver(berlin_pwm_driver);
diff --git a/drivers/pwm/pwm-brcmstb.c b/drivers/pwm/pwm-brcmstb.c
index fea612c45f20..82d27d07ba91 100644
--- a/drivers/pwm/pwm-brcmstb.c
+++ b/drivers/pwm/pwm-brcmstb.c
@@ -53,9 +53,7 @@
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,
@@ -78,7 +76,7 @@ static inline void brcmstb_pwm_writel(struct brcmstb_pwm *p, u32 value,
static inline struct brcmstb_pwm *to_brcmstb_pwm(struct pwm_chip *chip)
{
- return container_of(chip, struct brcmstb_pwm, chip);
+ return pwmchip_get_drvdata(chip);
}
/*
@@ -95,7 +93,7 @@ static inline struct brcmstb_pwm *to_brcmstb_pwm(struct pwm_chip *chip)
* "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)
+ u64 duty_ns, u64 period_ns)
{
struct brcmstb_pwm *p = to_brcmstb_pwm(chip);
unsigned long pc, dc, cword = CONST_VAR_F_MAX;
@@ -114,22 +112,17 @@ static int brcmstb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
}
while (1) {
- u64 rate, tmp;
+ u64 rate;
/*
* 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);
+ rate >>= 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;
+ pc = mul_u64_u64_div_u64(period_ns, rate, NSEC_PER_SEC);
+ dc = mul_u64_u64_div_u64(duty_ns + 1, rate, NSEC_PER_SEC);
/*
* We can be called with separate duty and period updates,
@@ -164,7 +157,6 @@ done:
* 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));
@@ -176,7 +168,6 @@ done:
/* 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;
}
@@ -187,7 +178,6 @@ static inline void brcmstb_pwm_enable_set(struct brcmstb_pwm *p,
unsigned int shift = channel * CTRL_CHAN_OFFS;
u32 value;
- spin_lock(&p->lock);
value = brcmstb_pwm_readl(p, PWM_CTRL);
if (enable) {
@@ -199,30 +189,36 @@ static inline void brcmstb_pwm_enable_set(struct brcmstb_pwm *p,
}
brcmstb_pwm_writel(p, value, PWM_CTRL);
- spin_unlock(&p->lock);
}
-static int brcmstb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+static int brcmstb_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
{
struct brcmstb_pwm *p = to_brcmstb_pwm(chip);
+ int err;
- brcmstb_pwm_enable_set(p, pwm->hwpwm, true);
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
- return 0;
-}
+ if (!state->enabled) {
+ if (pwm->state.enabled)
+ brcmstb_pwm_enable_set(p, pwm->hwpwm, false);
-static void brcmstb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
-{
- struct brcmstb_pwm *p = to_brcmstb_pwm(chip);
+ return 0;
+ }
+
+ err = brcmstb_pwm_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
- brcmstb_pwm_enable_set(p, pwm->hwpwm, false);
+ if (!pwm->state.enabled)
+ brcmstb_pwm_enable_set(p, pwm->hwpwm, true);
+
+ return 0;
}
static const struct pwm_ops brcmstb_pwm_ops = {
- .config = brcmstb_pwm_config,
- .enable = brcmstb_pwm_enable,
- .disable = brcmstb_pwm_disable,
- .owner = THIS_MODULE,
+ .apply = brcmstb_pwm_apply,
};
static const struct of_device_id brcmstb_pwm_of_match[] = {
@@ -233,72 +229,40 @@ MODULE_DEVICE_TABLE(of, brcmstb_pwm_of_match);
static int brcmstb_pwm_probe(struct platform_device *pdev)
{
+ struct pwm_chip *chip;
struct brcmstb_pwm *p;
- struct resource *res;
int ret;
- p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
- if (!p)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, 2, sizeof(*p));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ p = to_brcmstb_pwm(chip);
- 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;
- }
+ p->clk = devm_clk_get_enabled(&pdev->dev, NULL);
+ if (IS_ERR(p->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(p->clk),
+ "failed to obtain clock\n");
platform_set_drvdata(pdev, p);
- p->chip.dev = &pdev->dev;
- p->chip.ops = &brcmstb_pwm_ops;
- p->chip.base = -1;
- p->chip.npwm = 2;
+ chip->ops = &brcmstb_pwm_ops;
- 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;
- }
+ p->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(p->base))
+ return PTR_ERR(p->base);
- ret = pwmchip_add(&p->chip);
- if (ret) {
- dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
- goto out_clk;
- }
+ ret = devm_pwmchip_add(&pdev->dev, chip);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n");
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(p->clk);
+ clk_disable_unprepare(p->clk);
return 0;
}
@@ -307,22 +271,18 @@ static int brcmstb_pwm_resume(struct device *dev)
{
struct brcmstb_pwm *p = dev_get_drvdata(dev);
- clk_enable(p->clk);
-
- return 0;
+ return clk_prepare_enable(p->clk);
}
-#endif
-static SIMPLE_DEV_PM_OPS(brcmstb_pwm_pm_ops, brcmstb_pwm_suspend,
- brcmstb_pwm_resume);
+static DEFINE_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,
+ .pm = pm_ptr(&brcmstb_pwm_pm_ops),
},
};
module_platform_driver(brcmstb_pwm_driver);
diff --git a/drivers/pwm/pwm-clk.c b/drivers/pwm/pwm-clk.c
new file mode 100644
index 000000000000..f8f5af57acba
--- /dev/null
+++ b/drivers/pwm/pwm-clk.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Clock based PWM controller
+ *
+ * Copyright (c) 2021 Nikita Travkin <nikita@trvn.ru>
+ *
+ * This is an "adapter" driver that allows PWM consumers to use
+ * system clocks with duty cycle control as PWM outputs.
+ *
+ * Limitations:
+ * - Due to the fact that exact behavior depends on the underlying
+ * clock driver, various limitations are possible.
+ * - Underlying clock may not be able to give 0% or 100% duty cycle
+ * (constant off or on), exact behavior will depend on the clock.
+ * - When the PWM is disabled, the clock will be disabled as well,
+ * line state will depend on the clock.
+ * - The clk API doesn't expose the necessary calls to implement
+ * .get_state().
+ */
+
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/pwm.h>
+
+struct pwm_clk_chip {
+ struct clk *clk;
+ bool clk_enabled;
+};
+
+static inline struct pwm_clk_chip *to_pwm_clk_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static int pwm_clk_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct pwm_clk_chip *pcchip = to_pwm_clk_chip(chip);
+ int ret;
+ u32 rate;
+ u64 period = state->period;
+ u64 duty_cycle = state->duty_cycle;
+
+ if (!state->enabled) {
+ if (pwm->state.enabled) {
+ clk_disable(pcchip->clk);
+ pcchip->clk_enabled = false;
+ }
+ return 0;
+ } else if (!pwm->state.enabled) {
+ ret = clk_enable(pcchip->clk);
+ if (ret)
+ return ret;
+ pcchip->clk_enabled = true;
+ }
+
+ /*
+ * We have to enable the clk before setting the rate and duty_cycle,
+ * that however results in a window where the clk is on with a
+ * (potentially) different setting. Also setting period and duty_cycle
+ * are two separate calls, so that probably isn't atomic either.
+ */
+
+ rate = DIV64_U64_ROUND_UP(NSEC_PER_SEC, period);
+ ret = clk_set_rate(pcchip->clk, rate);
+ if (ret)
+ return ret;
+
+ if (state->polarity == PWM_POLARITY_INVERSED)
+ duty_cycle = period - duty_cycle;
+
+ return clk_set_duty_cycle(pcchip->clk, duty_cycle, period);
+}
+
+static const struct pwm_ops pwm_clk_ops = {
+ .apply = pwm_clk_apply,
+};
+
+static int pwm_clk_probe(struct platform_device *pdev)
+{
+ struct pwm_chip *chip;
+ struct pwm_clk_chip *pcchip;
+ int ret;
+
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*pcchip));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ pcchip = to_pwm_clk_chip(chip);
+
+ pcchip->clk = devm_clk_get_prepared(&pdev->dev, NULL);
+ if (IS_ERR(pcchip->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(pcchip->clk),
+ "Failed to get clock\n");
+
+ chip->ops = &pwm_clk_ops;
+
+ ret = pwmchip_add(chip);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "Failed to add pwm chip\n");
+
+ platform_set_drvdata(pdev, chip);
+ return 0;
+}
+
+static void pwm_clk_remove(struct platform_device *pdev)
+{
+ struct pwm_chip *chip = platform_get_drvdata(pdev);
+ struct pwm_clk_chip *pcchip = to_pwm_clk_chip(chip);
+
+ pwmchip_remove(chip);
+
+ if (pcchip->clk_enabled)
+ clk_disable(pcchip->clk);
+}
+
+static const struct of_device_id pwm_clk_dt_ids[] = {
+ { .compatible = "clk-pwm", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pwm_clk_dt_ids);
+
+static struct platform_driver pwm_clk_driver = {
+ .driver = {
+ .name = "pwm-clk",
+ .of_match_table = pwm_clk_dt_ids,
+ },
+ .probe = pwm_clk_probe,
+ .remove = pwm_clk_remove,
+};
+module_platform_driver(pwm_clk_driver);
+
+MODULE_ALIAS("platform:pwm-clk");
+MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>");
+MODULE_DESCRIPTION("Clock based PWM driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-clps711x.c b/drivers/pwm/pwm-clps711x.c
index 924d39a797cf..2c92ce754872 100644
--- a/drivers/pwm/pwm-clps711x.c
+++ b/drivers/pwm/pwm-clps711x.c
@@ -12,38 +12,13 @@
#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 DIV_ROUND_CLOSEST(v * 0xf, pwm->args.period);
+ return pwmchip_get_drvdata(chip);
}
static int clps711x_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
@@ -60,67 +35,49 @@ static int clps711x_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
return 0;
}
-static int clps711x_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
- int duty_ns, int period_ns)
+static int clps711x_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
{
struct clps711x_chip *priv = to_clps711x_chip(chip);
- unsigned int duty;
+ /* PWM0 - bits 4..7, PWM1 - bits 8..11 */
+ u32 shift = (pwm->hwpwm + 1) * 4;
+ u32 pmpcon, val;
- if (period_ns != pwm->args.period)
+ if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
- duty = clps711x_get_duty(pwm, duty_ns);
- clps711x_pwm_update_val(priv, pwm->hwpwm, duty);
-
- return 0;
-}
+ if (state->period != pwm->args.period)
+ return -EINVAL;
-static int clps711x_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
-{
- struct clps711x_chip *priv = to_clps711x_chip(chip);
- unsigned int duty;
+ if (state->enabled)
+ val = mul_u64_u64_div_u64(state->duty_cycle, 0xf, state->period);
+ else
+ val = 0;
- duty = clps711x_get_duty(pwm, pwm_get_duty_cycle(pwm));
- clps711x_pwm_update_val(priv, pwm->hwpwm, duty);
+ pmpcon = readl(priv->pmpcon);
+ pmpcon &= ~(0xf << shift);
+ pmpcon |= val << shift;
+ writel(pmpcon, priv->pmpcon);
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,
+ .apply = clps711x_pwm_apply,
};
-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 pwm_chip *chip;
struct clps711x_chip *priv;
- struct resource *res;
- priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, 2, sizeof(*priv));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ priv = to_clps711x_chip(chip);
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- priv->pmpcon = devm_ioremap_resource(&pdev->dev, res);
+ priv->pmpcon = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->pmpcon))
return PTR_ERR(priv->pmpcon);
@@ -128,28 +85,12 @@ static int clps711x_pwm_probe(struct platform_device *pdev)
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);
+ chip->ops = &clps711x_pwm_ops;
- return pwmchip_remove(&priv->chip);
+ return devm_pwmchip_add(&pdev->dev, chip);
}
-static const struct of_device_id __maybe_unused clps711x_pwm_dt_ids[] = {
+static const struct of_device_id clps711x_pwm_dt_ids[] = {
{ .compatible = "cirrus,ep7209-pwm", },
{ }
};
@@ -158,10 +99,9 @@ MODULE_DEVICE_TABLE(of, clps711x_pwm_dt_ids);
static struct platform_driver clps711x_pwm_driver = {
.driver = {
.name = "clps711x-pwm",
- .of_match_table = of_match_ptr(clps711x_pwm_dt_ids),
+ .of_match_table = clps711x_pwm_dt_ids,
},
.probe = clps711x_pwm_probe,
- .remove = clps711x_pwm_remove,
};
module_platform_driver(clps711x_pwm_driver);
diff --git a/drivers/pwm/pwm-crc.c b/drivers/pwm/pwm-crc.c
index 272eeb071147..98ee5cdbd0ba 100644
--- a/drivers/pwm/pwm-crc.c
+++ b/drivers/pwm/pwm-crc.c
@@ -21,115 +21,169 @@
#define PWM_MAX_LEVEL 0xFF
-#define PWM_BASE_CLK 6000000 /* 6 MHz */
-#define PWM_MAX_PERIOD_NS 21333 /* 46.875KHz */
+#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)
+static inline struct crystalcove_pwm *to_crc_pwm(struct pwm_chip *chip)
{
- return container_of(pc, struct crystalcove_pwm, chip);
+ return pwmchip_get_drvdata(chip);
}
-static int crc_pwm_enable(struct pwm_chip *c, struct pwm_device *pwm)
+static int crc_pwm_calc_clk_div(int period_ns)
{
- struct crystalcove_pwm *crc_pwm = to_crc_pwm(c);
+ int clk_div;
- regmap_write(crc_pwm->regmap, BACKLIGHT_EN, 1);
+ 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 0;
+ return clk_div;
}
-static void crc_pwm_disable(struct pwm_chip *c, struct pwm_device *pwm)
+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(c);
-
- regmap_write(crc_pwm->regmap, BACKLIGHT_EN, 0);
-}
+ struct crystalcove_pwm *crc_pwm = to_crc_pwm(chip);
+ struct device *dev = pwmchip_parent(chip);
+ int err;
-static int crc_pwm_config(struct pwm_chip *c, struct pwm_device *pwm,
- int duty_ns, int period_ns)
-{
- struct crystalcove_pwm *crc_pwm = to_crc_pwm(c);
- struct device *dev = crc_pwm->chip.dev;
- int level;
-
- if (period_ns > PWM_MAX_PERIOD_NS) {
+ if (state->period > PWM_MAX_PERIOD_NS) {
dev_err(dev, "un-supported period_ns\n");
return -EINVAL;
}
- if (pwm_get_period(pwm) != period_ns) {
- int clk_div;
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ 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;
+ }
+ }
- /* changing the clk divisor, need to disable fisrt */
- crc_pwm_disable(c, pwm);
- clk_div = PWM_BASE_CLK * period_ns / NSEC_PER_SEC;
+ 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;
+ }
+ }
- regmap_write(crc_pwm->regmap, PWM0_CLK_DIV,
- clk_div | PWM_OUTPUT_ENABLE);
+ return 0;
+}
+
+static int 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 = pwmchip_parent(chip);
+ 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;
+ }
- /* enable back */
- crc_pwm_enable(c, pwm);
+ 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 error;
}
- /* change the pwm duty cycle */
- level = duty_ns * PWM_MAX_LEVEL / period_ns;
- regmap_write(crc_pwm->regmap, PWM0_DUTY_CYCLE, level);
+ 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);
return 0;
}
static const struct pwm_ops crc_pwm_ops = {
- .config = crc_pwm_config,
- .enable = crc_pwm_enable,
- .disable = crc_pwm_disable,
+ .apply = crc_pwm_apply,
+ .get_state = crc_pwm_get_state,
};
static int crystalcove_pwm_probe(struct platform_device *pdev)
{
- struct crystalcove_pwm *pwm;
+ struct pwm_chip *chip;
+ struct crystalcove_pwm *crc_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;
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*crc_pwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ crc_pwm = to_crc_pwm(chip);
- pwm->chip.dev = &pdev->dev;
- pwm->chip.ops = &crc_pwm_ops;
- pwm->chip.base = -1;
- pwm->chip.npwm = 1;
+ chip->ops = &crc_pwm_ops;
/* 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);
+ crc_pwm->regmap = pmic->regmap;
- return pwmchip_remove(&pwm->chip);
+ return devm_pwmchip_add(&pdev->dev, chip);
}
static struct platform_driver crystalcove_pwm_driver = {
.probe = crystalcove_pwm_probe,
- .remove = crystalcove_pwm_remove,
.driver = {
.name = "crystal_cove_pwm",
},
};
+module_platform_driver(crystalcove_pwm_driver);
-builtin_platform_driver(crystalcove_pwm_driver);
+MODULE_ALIAS("platform:crystal_cove_pwm");
+MODULE_DESCRIPTION("Intel Crystalcove (CRC) PWM support");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-cros-ec.c b/drivers/pwm/pwm-cros-ec.c
index 09c08dee099e..67cfa17f58e0 100644
--- a/drivers/pwm/pwm-cros-ec.c
+++ b/drivers/pwm/pwm-cros-ec.c
@@ -6,66 +6,55 @@
*/
#include <linux/module.h>
+#include <linux/of.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>
+#include <dt-bindings/mfd/cros_ec.h>
+
/**
* struct cros_ec_pwm_device - Driver data for EC PWM
*
- * @dev: Device node
* @ec: Pointer to EC device
- * @chip: PWM controller chip
+ * @use_pwm_type: Use PWM types instead of generic channels
*/
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;
+ bool use_pwm_type;
};
-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)
+static inline struct cros_ec_pwm_device *pwm_to_cros_ec_pwm(struct pwm_chip *chip)
{
- struct cros_ec_pwm *channel;
-
- channel = kzalloc(sizeof(*channel), GFP_KERNEL);
- if (!channel)
- return -ENOMEM;
-
- pwm_set_chip_data(pwm, channel);
-
- return 0;
+ return pwmchip_get_drvdata(chip);
}
-static void cros_ec_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+static int cros_ec_dt_type_to_pwm_type(u8 dt_index, u8 *pwm_type)
{
- struct cros_ec_pwm *channel = pwm_get_chip_data(pwm);
-
- kfree(channel);
+ switch (dt_index) {
+ case CROS_EC_PWM_DT_KB_LIGHT:
+ *pwm_type = EC_PWM_TYPE_KB_LIGHT;
+ return 0;
+ case CROS_EC_PWM_DT_DISPLAY_LIGHT:
+ *pwm_type = EC_PWM_TYPE_DISPLAY_LIGHT;
+ return 0;
+ default:
+ return -EINVAL;
+ }
}
-static int cros_ec_pwm_set_duty(struct cros_ec_device *ec, u8 index, u16 duty)
+static int cros_ec_pwm_set_duty(struct cros_ec_pwm_device *ec_pwm, u8 index,
+ u16 duty)
{
- struct {
- struct cros_ec_command msg;
+ struct cros_ec_device *ec = ec_pwm->ec;
+ TRAILING_OVERLAP(struct cros_ec_command, msg, data,
struct ec_params_pwm_set_duty params;
- } __packed buf;
+ ) __packed buf;
struct ec_params_pwm_set_duty *params = &buf.params;
struct cros_ec_command *msg = &buf.msg;
+ int ret;
memset(&buf, 0, sizeof(buf));
@@ -75,22 +64,30 @@ static int cros_ec_pwm_set_duty(struct cros_ec_device *ec, u8 index, u16 duty)
msg->outsize = sizeof(*params);
params->duty = duty;
- params->pwm_type = EC_PWM_TYPE_GENERIC;
- params->index = index;
+
+ if (ec_pwm->use_pwm_type) {
+ ret = cros_ec_dt_type_to_pwm_type(index, &params->pwm_type);
+ if (ret) {
+ dev_err(ec->dev, "Invalid PWM type index: %d\n", index);
+ return ret;
+ }
+ params->index = 0;
+ } else {
+ 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,
- u32 *result)
+static int cros_ec_pwm_get_duty(struct cros_ec_device *ec, bool use_pwm_type, u8 index)
{
- struct {
- struct cros_ec_command msg;
+ TRAILING_OVERLAP(struct cros_ec_command, msg, data,
union {
struct ec_params_pwm_get_duty params;
struct ec_response_pwm_get_duty resp;
};
- } __packed buf;
+ ) __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;
@@ -103,28 +100,29 @@ static int __cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index,
msg->insize = sizeof(*resp);
msg->outsize = sizeof(*params);
- params->pwm_type = EC_PWM_TYPE_GENERIC;
- params->index = index;
+ if (use_pwm_type) {
+ ret = cros_ec_dt_type_to_pwm_type(index, &params->pwm_type);
+ if (ret) {
+ dev_err(ec->dev, "Invalid PWM type index: %d\n", index);
+ return ret;
+ }
+ params->index = 0;
+ } else {
+ params->pwm_type = EC_PWM_TYPE_GENERIC;
+ params->index = index;
+ }
ret = cros_ec_cmd_xfer_status(ec, msg);
- if (result)
- *result = msg->result;
if (ret < 0)
return ret;
return resp->duty;
}
-static int cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index)
-{
- return __cros_ec_pwm_get_duty(ec, index, NULL);
-}
-
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;
@@ -132,101 +130,79 @@ static int cros_ec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
if (state->period != EC_PWM_MAX_DUTY)
return -EINVAL;
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ 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);
+ ret = cros_ec_pwm_set_duty(ec_pwm, 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)
+static int cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
{
struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip);
- struct cros_ec_pwm *channel = pwm_get_chip_data(pwm);
int ret;
- ret = cros_ec_pwm_get_duty(ec_pwm->ec, pwm->hwpwm);
+ ret = cros_ec_pwm_get_duty(ec_pwm->ec, ec_pwm->use_pwm_type, pwm->hwpwm);
if (ret < 0) {
- dev_err(chip->dev, "error getting initial duty: %d\n", ret);
- return;
+ dev_err(pwmchip_parent(chip), "error getting initial duty: %d\n", ret);
+ return ret;
}
state->enabled = (ret > 0);
+ state->duty_cycle = ret;
state->period = EC_PWM_MAX_DUTY;
+ state->polarity = PWM_POLARITY_NORMAL;
- /*
- * Note that "disabled" and "duty cycle == 0" are treated the same. If
- * the cached duty cycle is not zero, used the cached duty cycle. This
- * ensures that the configured duty cycle is kept across a disable and
- * enable operation and avoids potentially confusing consumers.
- *
- * For the case of the initial hardware readout, channel->duty_cycle
- * will be 0 and the actual duty cycle read from the EC is used.
- */
- if (ret == 0 && channel->duty_cycle > 0)
- state->duty_cycle = channel->duty_cycle;
- else
- state->duty_cycle = ret;
-}
-
-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;
+ return 0;
}
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++) {
- u32 result = 0;
-
- ret = __cros_ec_pwm_get_duty(ec, i, &result);
- /* We want to parse EC protocol errors */
- if (ret < 0 && !(ret == -EPROTO && result))
- return ret;
-
+ /*
+ * Note that this function is only called when use_pwm_type is
+ * false. With use_pwm_type == true the number of PWMs is fixed.
+ */
+ ret = cros_ec_pwm_get_duty(ec, false, 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.
*/
- if (result == EC_RES_INVALID_COMMAND)
+ switch (ret) {
+ case -EOPNOTSUPP: /* invalid command */
return -ENODEV;
- else if (result == EC_RES_INVALID_PARAM)
+ case -EINVAL: /* invalid parameter */
return i;
- else if (result)
- return -EPROTO;
+ default:
+ if (ret < 0)
+ return ret;
+ break;
+ }
}
return U8_MAX;
@@ -236,57 +212,61 @@ 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 device_node *np = pdev->dev.of_node;
struct cros_ec_pwm_device *ec_pwm;
struct pwm_chip *chip;
+ bool use_pwm_type = false;
+ unsigned int i, npwm;
int ret;
- if (!ec) {
- dev_err(dev, "no parent EC device\n");
- return -EINVAL;
+ if (!ec)
+ return dev_err_probe(dev, -EINVAL, "no parent EC device\n");
+
+ if (of_device_is_compatible(np, "google,cros-ec-pwm-type")) {
+ use_pwm_type = true;
+ npwm = CROS_EC_PWM_DT_COUNT;
+ } else {
+ ret = cros_ec_num_pwms(ec);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Couldn't find PWMs\n");
+ npwm = ret;
}
- ec_pwm = devm_kzalloc(dev, sizeof(*ec_pwm), GFP_KERNEL);
- if (!ec_pwm)
- return -ENOMEM;
- chip = &ec_pwm->chip;
+ chip = devm_pwmchip_alloc(dev, npwm, sizeof(*ec_pwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ ec_pwm = pwm_to_cros_ec_pwm(chip);
+ ec_pwm->use_pwm_type = use_pwm_type;
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);
+ /*
+ * The device tree binding for this device is special as it only uses a
+ * single cell (for the hwid) and so doesn't provide a default period.
+ * This isn't a big problem though as the hardware only supports a
+ * single period length, it's just a bit ugly to make this fit into the
+ * pwm core abstractions. So initialize the period here, as
+ * of_pwm_xlate_with_flags() won't do that for us.
+ */
+ for (i = 0; i < npwm; ++i)
+ chip->pwms[i].args.period = EC_PWM_MAX_DUTY;
- return ret;
-}
+ dev_dbg(dev, "Probed %u PWMs\n", chip->npwm);
-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;
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "cannot register PWM\n");
- return pwmchip_remove(chip);
+ return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id cros_ec_pwm_of_match[] = {
{ .compatible = "google,cros-ec-pwm" },
+ { .compatible = "google,cros-ec-pwm-type" },
{},
};
MODULE_DEVICE_TABLE(of, cros_ec_pwm_of_match);
@@ -294,7 +274,6 @@ MODULE_DEVICE_TABLE(of, cros_ec_pwm_of_match);
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),
diff --git a/drivers/pwm/pwm-dwc-core.c b/drivers/pwm/pwm-dwc-core.c
new file mode 100644
index 000000000000..6dabec93a3c6
--- /dev/null
+++ b/drivers/pwm/pwm-dwc-core.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DesignWare PWM Controller driver core
+ *
+ * Copyright (C) 2018-2020 Intel Corporation
+ *
+ * Author: Felipe Balbi (Intel)
+ * Author: Jarkko Nikula <jarkko.nikula@linux.intel.com>
+ * Author: Raymond Tan <raymond.tan@intel.com>
+ */
+
+#define DEFAULT_SYMBOL_NAMESPACE "dwc_pwm"
+
+#include <linux/bitops.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+#include <linux/pwm.h>
+
+#include "pwm-dwc.h"
+
+static void __dwc_pwm_set_enable(struct dwc_pwm *dwc, int pwm, int enabled)
+{
+ u32 reg;
+
+ reg = dwc_pwm_readl(dwc, DWC_TIM_CTRL(pwm));
+
+ if (enabled)
+ reg |= DWC_TIM_CTRL_EN;
+ else
+ reg &= ~DWC_TIM_CTRL_EN;
+
+ dwc_pwm_writel(dwc, reg, DWC_TIM_CTRL(pwm));
+}
+
+static int __dwc_pwm_configure_timer(struct dwc_pwm *dwc,
+ struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ u64 tmp;
+ u32 ctrl;
+ u32 high;
+ u32 low;
+
+ /*
+ * Calculate width of low and high period in terms of input clock
+ * periods and check are the result within HW limits between 1 and
+ * 2^32 periods.
+ */
+ tmp = DIV_ROUND_CLOSEST_ULL(state->duty_cycle, dwc->clk_ns);
+ if (tmp < 1 || tmp > (1ULL << 32))
+ return -ERANGE;
+ low = tmp - 1;
+
+ tmp = DIV_ROUND_CLOSEST_ULL(state->period - state->duty_cycle,
+ dwc->clk_ns);
+ if (tmp < 1 || tmp > (1ULL << 32))
+ return -ERANGE;
+ high = tmp - 1;
+
+ /*
+ * Specification says timer usage flow is to disable timer, then
+ * program it followed by enable. It also says Load Count is loaded
+ * into timer after it is enabled - either after a disable or
+ * a reset. Based on measurements it happens also without disable
+ * whenever Load Count is updated. But follow the specification.
+ */
+ __dwc_pwm_set_enable(dwc, pwm->hwpwm, false);
+
+ /*
+ * Write Load Count and Load Count 2 registers. Former defines the
+ * width of low period and latter the width of high period in terms
+ * multiple of input clock periods:
+ * Width = ((Count + 1) * input clock period).
+ */
+ dwc_pwm_writel(dwc, low, DWC_TIM_LD_CNT(pwm->hwpwm));
+ dwc_pwm_writel(dwc, high, DWC_TIM_LD_CNT2(pwm->hwpwm));
+
+ /*
+ * Set user-defined mode, timer reloads from Load Count registers
+ * when it counts down to 0.
+ * Set PWM mode, it makes output to toggle and width of low and high
+ * periods are set by Load Count registers.
+ */
+ ctrl = DWC_TIM_CTRL_MODE_USER | DWC_TIM_CTRL_PWM;
+ dwc_pwm_writel(dwc, ctrl, DWC_TIM_CTRL(pwm->hwpwm));
+
+ /*
+ * Enable timer. Output starts from low period.
+ */
+ __dwc_pwm_set_enable(dwc, pwm->hwpwm, state->enabled);
+
+ return 0;
+}
+
+static int dwc_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct dwc_pwm *dwc = to_dwc_pwm(chip);
+
+ if (state->polarity != PWM_POLARITY_INVERSED)
+ return -EINVAL;
+
+ if (state->enabled) {
+ if (!pwm->state.enabled)
+ pm_runtime_get_sync(pwmchip_parent(chip));
+ return __dwc_pwm_configure_timer(dwc, pwm, state);
+ } else {
+ if (pwm->state.enabled) {
+ __dwc_pwm_set_enable(dwc, pwm->hwpwm, false);
+ pm_runtime_put_sync(pwmchip_parent(chip));
+ }
+ }
+
+ return 0;
+}
+
+static int dwc_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct dwc_pwm *dwc = to_dwc_pwm(chip);
+ u64 duty, period;
+ u32 ctrl, ld, ld2;
+
+ pm_runtime_get_sync(pwmchip_parent(chip));
+
+ ctrl = dwc_pwm_readl(dwc, DWC_TIM_CTRL(pwm->hwpwm));
+ ld = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT(pwm->hwpwm));
+ ld2 = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT2(pwm->hwpwm));
+
+ state->enabled = !!(ctrl & DWC_TIM_CTRL_EN);
+
+ /*
+ * If we're not in PWM, technically the output is a 50-50
+ * based on the timer load-count only.
+ */
+ if (ctrl & DWC_TIM_CTRL_PWM) {
+ duty = (ld + 1) * dwc->clk_ns;
+ period = (ld2 + 1) * dwc->clk_ns;
+ period += duty;
+ } else {
+ duty = (ld + 1) * dwc->clk_ns;
+ period = duty * 2;
+ }
+
+ state->polarity = PWM_POLARITY_INVERSED;
+ state->period = period;
+ state->duty_cycle = duty;
+
+ pm_runtime_put_sync(pwmchip_parent(chip));
+
+ return 0;
+}
+
+static const struct pwm_ops dwc_pwm_ops = {
+ .apply = dwc_pwm_apply,
+ .get_state = dwc_pwm_get_state,
+};
+
+struct pwm_chip *dwc_pwm_alloc(struct device *dev)
+{
+ struct pwm_chip *chip;
+ struct dwc_pwm *dwc;
+
+ chip = devm_pwmchip_alloc(dev, DWC_TIMERS_TOTAL, sizeof(*dwc));
+ if (IS_ERR(chip))
+ return chip;
+ dwc = to_dwc_pwm(chip);
+
+ dwc->clk_ns = 10;
+ chip->ops = &dwc_pwm_ops;
+
+ return chip;
+}
+EXPORT_SYMBOL_GPL(dwc_pwm_alloc);
+
+MODULE_AUTHOR("Felipe Balbi (Intel)");
+MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@linux.intel.com>");
+MODULE_AUTHOR("Raymond Tan <raymond.tan@intel.com>");
+MODULE_DESCRIPTION("DesignWare PWM Controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-dwc.c b/drivers/pwm/pwm-dwc.c
new file mode 100644
index 000000000000..b6c16139ce4a
--- /dev/null
+++ b/drivers/pwm/pwm-dwc.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DesignWare PWM Controller driver (PCI part)
+ *
+ * Copyright (C) 2018-2020 Intel Corporation
+ *
+ * Author: Felipe Balbi (Intel)
+ * Author: Jarkko Nikula <jarkko.nikula@linux.intel.com>
+ * Author: Raymond Tan <raymond.tan@intel.com>
+ *
+ * Limitations:
+ * - The hardware cannot generate a 0 % or 100 % duty cycle. Both high and low
+ * periods are one or more input clock periods long.
+ */
+
+#define DEFAULT_MOUDLE_NAMESPACE dwc_pwm
+
+#include <linux/bitops.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+#include <linux/pwm.h>
+
+#include "pwm-dwc.h"
+
+/* Elkhart Lake */
+static const struct dwc_pwm_info ehl_pwm_info = {
+ .nr = 2,
+ .size = 0x1000,
+};
+
+static int dwc_pwm_init_one(struct device *dev, struct dwc_pwm_drvdata *ddata, unsigned int idx)
+{
+ struct pwm_chip *chip;
+ struct dwc_pwm *dwc;
+ int ret;
+
+ chip = dwc_pwm_alloc(dev);
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ dwc = to_dwc_pwm(chip);
+ dwc->base = ddata->io_base + (ddata->info->size * idx);
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return ret;
+
+ ddata->chips[idx] = chip;
+ return 0;
+}
+
+static int dwc_pwm_probe(struct pci_dev *pci, const struct pci_device_id *id)
+{
+ const struct dwc_pwm_info *info;
+ struct device *dev = &pci->dev;
+ struct dwc_pwm_drvdata *ddata;
+ unsigned int idx;
+ int ret;
+
+ ret = pcim_enable_device(pci);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable device\n");
+
+ pci_set_master(pci);
+
+ info = (const struct dwc_pwm_info *)id->driver_data;
+ ddata = devm_kzalloc(dev, struct_size(ddata, chips, info->nr), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ ddata->io_base = pcim_iomap_region(pci, 0, "pwm-dwc");
+ if (IS_ERR(ddata->io_base))
+ return dev_err_probe(dev, PTR_ERR(ddata->io_base),
+ "Failed to request / iomap PCI BAR\n");
+
+ ddata->info = info;
+
+ for (idx = 0; idx < ddata->info->nr; idx++) {
+ ret = dwc_pwm_init_one(dev, ddata, idx);
+ if (ret)
+ return ret;
+ }
+
+ dev_set_drvdata(dev, ddata);
+
+ pm_runtime_put(dev);
+ pm_runtime_allow(dev);
+
+ return 0;
+}
+
+static void dwc_pwm_remove(struct pci_dev *pci)
+{
+ pm_runtime_forbid(&pci->dev);
+ pm_runtime_get_noresume(&pci->dev);
+}
+
+static int dwc_pwm_suspend(struct device *dev)
+{
+ struct dwc_pwm_drvdata *ddata = dev_get_drvdata(dev);
+ unsigned int idx;
+
+ for (idx = 0; idx < ddata->info->nr; idx++) {
+ struct pwm_chip *chip = ddata->chips[idx];
+ struct dwc_pwm *dwc = to_dwc_pwm(chip);
+ unsigned int i;
+
+ for (i = 0; i < DWC_TIMERS_TOTAL; i++) {
+ if (chip->pwms[i].state.enabled) {
+ dev_err(dev, "PWM %u in use by consumer (%s)\n",
+ i, chip->pwms[i].label);
+ return -EBUSY;
+ }
+ dwc->ctx[i].cnt = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT(i));
+ dwc->ctx[i].cnt2 = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT2(i));
+ dwc->ctx[i].ctrl = dwc_pwm_readl(dwc, DWC_TIM_CTRL(i));
+ }
+ }
+
+ return 0;
+}
+
+static int dwc_pwm_resume(struct device *dev)
+{
+ struct dwc_pwm_drvdata *ddata = dev_get_drvdata(dev);
+ unsigned int idx;
+
+ for (idx = 0; idx < ddata->info->nr; idx++) {
+ struct pwm_chip *chip = ddata->chips[idx];
+ struct dwc_pwm *dwc = to_dwc_pwm(chip);
+ unsigned int i;
+
+ for (i = 0; i < DWC_TIMERS_TOTAL; i++) {
+ dwc_pwm_writel(dwc, dwc->ctx[i].cnt, DWC_TIM_LD_CNT(i));
+ dwc_pwm_writel(dwc, dwc->ctx[i].cnt2, DWC_TIM_LD_CNT2(i));
+ dwc_pwm_writel(dwc, dwc->ctx[i].ctrl, DWC_TIM_CTRL(i));
+ }
+ }
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(dwc_pwm_pm_ops, dwc_pwm_suspend, dwc_pwm_resume);
+
+static const struct pci_device_id dwc_pwm_id_table[] = {
+ { PCI_VDEVICE(INTEL, 0x4bb7), (kernel_ulong_t)&ehl_pwm_info },
+ { } /* Terminating Entry */
+};
+MODULE_DEVICE_TABLE(pci, dwc_pwm_id_table);
+
+static struct pci_driver dwc_pwm_driver = {
+ .name = "pwm-dwc",
+ .probe = dwc_pwm_probe,
+ .remove = dwc_pwm_remove,
+ .id_table = dwc_pwm_id_table,
+ .driver = {
+ .pm = pm_sleep_ptr(&dwc_pwm_pm_ops),
+ },
+};
+
+module_pci_driver(dwc_pwm_driver);
+
+MODULE_AUTHOR("Felipe Balbi (Intel)");
+MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@linux.intel.com>");
+MODULE_AUTHOR("Raymond Tan <raymond.tan@intel.com>");
+MODULE_DESCRIPTION("DesignWare PWM Controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-dwc.h b/drivers/pwm/pwm-dwc.h
new file mode 100644
index 000000000000..1562594e7f85
--- /dev/null
+++ b/drivers/pwm/pwm-dwc.h
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DesignWare PWM Controller driver
+ *
+ * Copyright (C) 2018-2020 Intel Corporation
+ *
+ * Author: Felipe Balbi (Intel)
+ * Author: Jarkko Nikula <jarkko.nikula@linux.intel.com>
+ * Author: Raymond Tan <raymond.tan@intel.com>
+ */
+
+MODULE_IMPORT_NS("dwc_pwm");
+
+#define DWC_TIM_LD_CNT(n) ((n) * 0x14)
+#define DWC_TIM_LD_CNT2(n) (((n) * 4) + 0xb0)
+#define DWC_TIM_CUR_VAL(n) (((n) * 0x14) + 0x04)
+#define DWC_TIM_CTRL(n) (((n) * 0x14) + 0x08)
+#define DWC_TIM_EOI(n) (((n) * 0x14) + 0x0c)
+#define DWC_TIM_INT_STS(n) (((n) * 0x14) + 0x10)
+
+#define DWC_TIMERS_INT_STS 0xa0
+#define DWC_TIMERS_EOI 0xa4
+#define DWC_TIMERS_RAW_INT_STS 0xa8
+#define DWC_TIMERS_COMP_VERSION 0xac
+
+#define DWC_TIMERS_TOTAL 8
+
+/* Timer Control Register */
+#define DWC_TIM_CTRL_EN BIT(0)
+#define DWC_TIM_CTRL_MODE BIT(1)
+#define DWC_TIM_CTRL_MODE_FREE (0 << 1)
+#define DWC_TIM_CTRL_MODE_USER (1 << 1)
+#define DWC_TIM_CTRL_INT_MASK BIT(2)
+#define DWC_TIM_CTRL_PWM BIT(3)
+
+struct dwc_pwm_info {
+ unsigned int nr;
+ unsigned int size;
+};
+
+struct dwc_pwm_drvdata {
+ const struct dwc_pwm_info *info;
+ void __iomem *io_base;
+ struct pwm_chip *chips[];
+};
+
+struct dwc_pwm_ctx {
+ u32 cnt;
+ u32 cnt2;
+ u32 ctrl;
+};
+
+struct dwc_pwm {
+ void __iomem *base;
+ unsigned int clk_ns;
+ struct dwc_pwm_ctx ctx[DWC_TIMERS_TOTAL];
+};
+
+static inline struct dwc_pwm *to_dwc_pwm(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static inline u32 dwc_pwm_readl(struct dwc_pwm *dwc, u32 offset)
+{
+ return readl(dwc->base + offset);
+}
+
+static inline void dwc_pwm_writel(struct dwc_pwm *dwc, u32 value, u32 offset)
+{
+ writel(value, dwc->base + offset);
+}
+
+extern struct pwm_chip *dwc_pwm_alloc(struct device *dev);
diff --git a/drivers/pwm/pwm-ep93xx.c b/drivers/pwm/pwm-ep93xx.c
index 4bab73073ad7..994f89ac43b4 100644
--- a/drivers/pwm/pwm-ep93xx.c
+++ b/drivers/pwm/pwm-ep93xx.c
@@ -17,6 +17,7 @@
*/
#include <linux/module.h>
+#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/clk.h>
@@ -26,8 +27,6 @@
#include <asm/div64.h>
-#include <linux/soc/cirrus/ep93xx.h> /* for ep93xx_pwm_{acquire,release}_gpio() */
-
#define EP93XX_PWMx_TERM_COUNT 0x00
#define EP93XX_PWMx_DUTY_CYCLE 0x04
#define EP93XX_PWMx_ENABLE 0x08
@@ -36,57 +35,75 @@
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);
+ return pwmchip_get_drvdata(chip);
}
-static int ep93xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
- int duty_ns, int period_ns)
+static int ep93xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
{
+ int ret;
struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip);
+ bool enabled = state->enabled;
void __iomem *base = ep93xx_pwm->base;
unsigned long long c;
unsigned long period_cycles;
unsigned long duty_cycles;
unsigned long term;
- int ret = 0;
+
+ if (state->polarity != pwm->state.polarity) {
+ if (enabled) {
+ writew(0x0, ep93xx_pwm->base + EP93XX_PWMx_ENABLE);
+ clk_disable_unprepare(ep93xx_pwm->clk);
+ enabled = false;
+ }
+
+ /*
+ * The clock needs to be enabled to access the PWM registers.
+ * Polarity can only be changed when the PWM is disabled.
+ */
+ ret = clk_prepare_enable(ep93xx_pwm->clk);
+ if (ret)
+ return ret;
+
+ if (state->polarity == PWM_POLARITY_INVERSED)
+ writew(0x1, ep93xx_pwm->base + EP93XX_PWMx_INVERT);
+ else
+ writew(0x0, ep93xx_pwm->base + EP93XX_PWMx_INVERT);
+
+ clk_disable_unprepare(ep93xx_pwm->clk);
+ }
+
+ if (!state->enabled) {
+ if (enabled) {
+ writew(0x0, ep93xx_pwm->base + EP93XX_PWMx_ENABLE);
+ clk_disable_unprepare(ep93xx_pwm->clk);
+ }
+
+ return 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);
+ ret = clk_prepare_enable(ep93xx_pwm->clk);
if (ret)
return ret;
}
c = clk_get_rate(ep93xx_pwm->clk);
- c *= period_ns;
+ c *= state->period;
do_div(c, 1000000000);
period_cycles = c;
c = period_cycles;
- c *= duty_ns;
- do_div(c, period_ns);
+ c *= state->duty_cycle;
+ do_div(c, state->period);
duty_cycles = c;
if (period_cycles < 0x10000 && duty_cycles < 0x10000) {
@@ -100,84 +117,44 @@ static int ep93xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
writew(duty_cycles, base + EP93XX_PWMx_DUTY_CYCLE);
writew(period_cycles, base + EP93XX_PWMx_TERM_COUNT);
}
+ ret = 0;
} 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;
+ clk_disable_unprepare(ep93xx_pwm->clk);
- /*
- * 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;
+ if (!enabled) {
+ ret = clk_prepare_enable(ep93xx_pwm->clk);
+ if (ret)
+ return ret;
- writew(0x1, ep93xx_pwm->base + EP93XX_PWMx_ENABLE);
+ 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,
+ .apply = ep93xx_pwm_apply,
};
static int ep93xx_pwm_probe(struct platform_device *pdev)
{
+ struct pwm_chip *chip;
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;
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*ep93xx_pwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ ep93xx_pwm = to_ep93xx_pwm(chip);
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- ep93xx_pwm->base = devm_ioremap_resource(&pdev->dev, res);
+ ep93xx_pwm->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ep93xx_pwm->base))
return PTR_ERR(ep93xx_pwm->base);
@@ -185,32 +162,27 @@ static int ep93xx_pwm_probe(struct platform_device *pdev)
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;
+ chip->ops = &ep93xx_pwm_ops;
- ret = pwmchip_add(&ep93xx_pwm->chip);
+ ret = devm_pwmchip_add(&pdev->dev, 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 const struct of_device_id ep93xx_pwm_of_ids[] = {
+ { .compatible = "cirrus,ep9301-pwm" },
+ { /* sentinel */}
+};
+MODULE_DEVICE_TABLE(of, ep93xx_pwm_of_ids);
static struct platform_driver ep93xx_pwm_driver = {
.driver = {
.name = "ep93xx-pwm",
+ .of_match_table = ep93xx_pwm_of_ids,
},
.probe = ep93xx_pwm_probe,
- .remove = ep93xx_pwm_remove,
};
module_platform_driver(ep93xx_pwm_driver);
diff --git a/drivers/pwm/pwm-fsl-ftm.c b/drivers/pwm/pwm-fsl-ftm.c
index 59272a920479..35406b2e1925 100644
--- a/drivers/pwm/pwm-fsl-ftm.c
+++ b/drivers/pwm/pwm-fsl-ftm.c
@@ -3,6 +3,7 @@
* Freescale FlexTimer Module (FTM) PWM Driver
*
* Copyright 2012-2013 Freescale Semiconductor, Inc.
+ * Copyright 2020-2025 NXP
*/
#include <linux/clk.h>
@@ -10,9 +11,7 @@
#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/of.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pwm.h>
@@ -32,6 +31,8 @@ enum fsl_pwm_clk {
struct fsl_ftm_soc {
bool has_enable_bits;
+ bool has_flt_reg;
+ unsigned int npwm;
};
struct fsl_pwm_periodcfg {
@@ -41,8 +42,6 @@ struct fsl_pwm_periodcfg {
};
struct fsl_pwm_chip {
- struct pwm_chip chip;
- struct mutex lock;
struct regmap *regmap;
/* This value is valid iff a pwm is running */
@@ -56,7 +55,7 @@ struct fsl_pwm_chip {
static inline struct fsl_pwm_chip *to_fsl_chip(struct pwm_chip *chip)
{
- return container_of(chip, struct fsl_pwm_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
static void ftm_clear_write_protection(struct fsl_pwm_chip *fpc)
@@ -65,13 +64,12 @@ static void ftm_clear_write_protection(struct fsl_pwm_chip *fpc)
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);
+ regmap_set_bits(fpc->regmap, FTM_MODE, 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);
+ regmap_set_bits(fpc->regmap, FTM_FMS, FTM_FMS_WPEN);
}
static bool fsl_pwm_periodcfg_are_equal(const struct fsl_pwm_periodcfg *a,
@@ -92,12 +90,8 @@ static int fsl_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
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);
- }
+ if (!ret && fpc->soc->has_enable_bits)
+ regmap_set_bits(fpc->regmap, FTM_SC, BIT(pwm->hwpwm + 16));
return ret;
}
@@ -106,12 +100,8 @@ 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);
- }
+ if (fpc->soc->has_enable_bits)
+ regmap_clear_bits(fpc->regmap, FTM_SC, BIT(pwm->hwpwm + 16));
clk_disable_unprepare(fpc->ipg_clk);
}
@@ -123,6 +113,9 @@ static unsigned int fsl_pwm_ticks_to_ns(struct fsl_pwm_chip *fpc,
unsigned long long exval;
rate = clk_get_rate(fpc->clk[fpc->period.clk_select]);
+ if (rate >> fpc->period.clk_ps == 0)
+ return 0;
+
exval = ticks;
exval *= 1000000000UL;
do_div(exval, rate >> fpc->period.clk_ps);
@@ -195,6 +188,9 @@ static unsigned int fsl_pwm_calculate_duty(struct fsl_pwm_chip *fpc,
unsigned int period = fpc->period.mod_period + 1;
unsigned int period_ns = fsl_pwm_ticks_to_ns(fpc, period);
+ if (!period_ns)
+ return 0;
+
duty = (unsigned long long)duty_ns * period;
do_div(duty, period_ns);
@@ -225,10 +221,11 @@ static bool fsl_pwm_is_other_pwm_enabled(struct fsl_pwm_chip *fpc,
return false;
}
-static int fsl_pwm_apply_config(struct fsl_pwm_chip *fpc,
+static int fsl_pwm_apply_config(struct pwm_chip *chip,
struct pwm_device *pwm,
const struct pwm_state *newstate)
{
+ struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
unsigned int duty;
u32 reg_polarity;
@@ -236,7 +233,7 @@ static int fsl_pwm_apply_config(struct fsl_pwm_chip *fpc,
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");
+ dev_err(pwmchip_parent(chip), "failed to calculate new period\n");
return -EINVAL;
}
@@ -250,7 +247,7 @@ static int fsl_pwm_apply_config(struct fsl_pwm_chip *fpc,
*/
else if (!fsl_pwm_periodcfg_are_equal(&fpc->period, &periodcfg)) {
if (fsl_pwm_is_other_pwm_enabled(fpc, pwm)) {
- dev_err(fpc->chip.dev,
+ dev_err(pwmchip_parent(chip),
"Cannot change period for PWM %u, disable other PWMs first\n",
pwm->hwpwm);
return -EBUSY;
@@ -302,7 +299,7 @@ static int fsl_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
{
struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
struct pwm_state *oldstate = &pwm->state;
- int ret = 0;
+ int ret;
/*
* oldstate to newstate : action
@@ -313,41 +310,36 @@ static int fsl_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
* 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));
+ regmap_set_bits(fpc->regmap, FTM_OUTMASK,
+ BIT(pwm->hwpwm));
clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]);
clk_disable_unprepare(fpc->clk[fpc->period.clk_select]);
}
- goto end_mutex;
+ return 0;
}
- ret = fsl_pwm_apply_config(fpc, pwm, newstate);
+ ret = fsl_pwm_apply_config(chip, pwm, newstate);
if (ret)
- goto end_mutex;
+ return ret;
/* check if need to enable */
if (!oldstate->enabled) {
ret = clk_prepare_enable(fpc->clk[fpc->period.clk_select]);
if (ret)
- goto end_mutex;
+ return ret;
ret = clk_prepare_enable(fpc->clk[FSL_PWM_CLK_CNTEN]);
if (ret) {
clk_disable_unprepare(fpc->clk[fpc->period.clk_select]);
- goto end_mutex;
+ return ret;
}
- regmap_update_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm),
- 0);
+ regmap_clear_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm));
}
-end_mutex:
- mutex_unlock(&fpc->lock);
return ret;
}
@@ -355,7 +347,6 @@ 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)
@@ -386,6 +377,20 @@ static bool fsl_pwm_volatile_reg(struct device *dev, unsigned int reg)
return false;
}
+static bool fsl_pwm_is_reg(struct device *dev, unsigned int reg)
+{
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
+
+ if (reg >= FTM_CSC(fpc->soc->npwm) && reg < FTM_CNTIN)
+ return false;
+
+ if ((reg == FTM_FLTCTRL || reg == FTM_FLTPOL) && !fpc->soc->has_flt_reg)
+ return false;
+
+ return true;
+}
+
static const struct regmap_config fsl_pwm_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
@@ -394,26 +399,26 @@ static const struct regmap_config fsl_pwm_regmap_config = {
.max_register = FTM_PWMLOAD,
.volatile_reg = fsl_pwm_volatile_reg,
.cache_type = REGCACHE_FLAT,
+ .writeable_reg = fsl_pwm_is_reg,
+ .readable_reg = fsl_pwm_is_reg,
};
static int fsl_pwm_probe(struct platform_device *pdev)
{
+ const struct fsl_ftm_soc *soc = of_device_get_match_data(&pdev->dev);
+ struct pwm_chip *chip;
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);
+ chip = devm_pwmchip_alloc(&pdev->dev, soc->npwm, sizeof(*fpc));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ fpc = to_fsl_chip(chip);
- fpc->soc = of_device_get_match_data(&pdev->dev);
- fpc->chip.dev = &pdev->dev;
+ fpc->soc = soc;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- base = devm_ioremap_resource(&pdev->dev, res);
+ base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
@@ -430,16 +435,16 @@ static int fsl_pwm_probe(struct platform_device *pdev)
return PTR_ERR(fpc->clk[FSL_PWM_CLK_SYS]);
}
- fpc->clk[FSL_PWM_CLK_FIX] = devm_clk_get(fpc->chip.dev, "ftm_fix");
+ fpc->clk[FSL_PWM_CLK_FIX] = devm_clk_get(&pdev->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");
+ fpc->clk[FSL_PWM_CLK_EXT] = devm_clk_get(&pdev->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");
+ devm_clk_get(&pdev->dev, "ftm_cnt_clk_en");
if (IS_ERR(fpc->clk[FSL_PWM_CLK_CNTEN]))
return PTR_ERR(fpc->clk[FSL_PWM_CLK_CNTEN]);
@@ -451,42 +456,31 @@ static int fsl_pwm_probe(struct platform_device *pdev)
if (IS_ERR(fpc->ipg_clk))
fpc->ipg_clk = fpc->clk[FSL_PWM_CLK_SYS];
+ chip->ops = &fsl_pwm_ops;
- 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);
+ ret = devm_pwmchip_add(&pdev->dev, chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
return ret;
}
- platform_set_drvdata(pdev, fpc);
+ platform_set_drvdata(pdev, chip);
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);
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
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];
+ for (i = 0; i < chip->npwm; i++) {
+ struct pwm_device *pwm = &chip->pwms[i];
if (!test_bit(PWMF_REQUESTED, &pwm->flags))
continue;
@@ -505,11 +499,12 @@ static int fsl_pwm_suspend(struct device *dev)
static int fsl_pwm_resume(struct device *dev)
{
- struct fsl_pwm_chip *fpc = dev_get_drvdata(dev);
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
int i;
- for (i = 0; i < fpc->chip.npwm; i++) {
- struct pwm_device *pwm = &fpc->chip.pwms[i];
+ for (i = 0; i < chip->npwm; i++) {
+ struct pwm_device *pwm = &chip->pwms[i];
if (!test_bit(PWMF_REQUESTED, &pwm->flags))
continue;
@@ -537,15 +532,26 @@ static const struct dev_pm_ops fsl_pwm_pm_ops = {
static const struct fsl_ftm_soc vf610_ftm_pwm = {
.has_enable_bits = false,
+ .has_flt_reg = true,
+ .npwm = 8,
};
static const struct fsl_ftm_soc imx8qm_ftm_pwm = {
.has_enable_bits = true,
+ .has_flt_reg = true,
+ .npwm = 8,
+};
+
+static const struct fsl_ftm_soc s32g2_ftm_pwm = {
+ .has_enable_bits = true,
+ .has_flt_reg = false,
+ .npwm = 6,
};
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 },
+ { .compatible = "nxp,s32g2-ftm-pwm", .data = &s32g2_ftm_pwm },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fsl_pwm_dt_ids);
@@ -557,7 +563,6 @@ static struct platform_driver fsl_pwm_driver = {
.pm = &fsl_pwm_pm_ops,
},
.probe = fsl_pwm_probe,
- .remove = fsl_pwm_remove,
};
module_platform_driver(fsl_pwm_driver);
diff --git a/drivers/pwm/pwm-gpio.c b/drivers/pwm/pwm-gpio.c
new file mode 100644
index 000000000000..5f4edeb394a9
--- /dev/null
+++ b/drivers/pwm/pwm-gpio.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Generic software PWM for modulating GPIOs
+ *
+ * Copyright (C) 2020 Axis Communications AB
+ * Copyright (C) 2020 Nicola Di Lieto
+ * Copyright (C) 2024 Stefan Wahren
+ * Copyright (C) 2024 Linus Walleij
+ */
+
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/hrtimer.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/pwm.h>
+#include <linux/spinlock.h>
+#include <linux/time.h>
+#include <linux/types.h>
+
+struct pwm_gpio {
+ struct hrtimer gpio_timer;
+ struct gpio_desc *gpio;
+ struct pwm_state state;
+ struct pwm_state next_state;
+
+ /* Protect internal state between pwm_ops and hrtimer */
+ spinlock_t lock;
+
+ bool changing;
+ bool running;
+ bool level;
+};
+
+static void pwm_gpio_round(struct pwm_state *dest, const struct pwm_state *src)
+{
+ u64 dividend;
+ u32 remainder;
+
+ *dest = *src;
+
+ /* Round down to hrtimer resolution */
+ dividend = dest->period;
+ remainder = do_div(dividend, hrtimer_resolution);
+ dest->period -= remainder;
+
+ dividend = dest->duty_cycle;
+ remainder = do_div(dividend, hrtimer_resolution);
+ dest->duty_cycle -= remainder;
+}
+
+static u64 pwm_gpio_toggle(struct pwm_gpio *gpwm, bool level)
+{
+ const struct pwm_state *state = &gpwm->state;
+ bool invert = state->polarity == PWM_POLARITY_INVERSED;
+
+ gpwm->level = level;
+ gpiod_set_value(gpwm->gpio, gpwm->level ^ invert);
+
+ if (!state->duty_cycle || state->duty_cycle == state->period) {
+ gpwm->running = false;
+ return 0;
+ }
+
+ gpwm->running = true;
+ return level ? state->duty_cycle : state->period - state->duty_cycle;
+}
+
+static enum hrtimer_restart pwm_gpio_timer(struct hrtimer *gpio_timer)
+{
+ struct pwm_gpio *gpwm = container_of(gpio_timer, struct pwm_gpio,
+ gpio_timer);
+ u64 next_toggle;
+ bool new_level;
+
+ guard(spinlock_irqsave)(&gpwm->lock);
+
+ /* Apply new state at end of current period */
+ if (!gpwm->level && gpwm->changing) {
+ gpwm->changing = false;
+ gpwm->state = gpwm->next_state;
+ new_level = !!gpwm->state.duty_cycle;
+ } else {
+ new_level = !gpwm->level;
+ }
+
+ next_toggle = pwm_gpio_toggle(gpwm, new_level);
+ if (next_toggle)
+ hrtimer_forward(gpio_timer, hrtimer_get_expires(gpio_timer),
+ ns_to_ktime(next_toggle));
+
+ return next_toggle ? HRTIMER_RESTART : HRTIMER_NORESTART;
+}
+
+static int pwm_gpio_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct pwm_gpio *gpwm = pwmchip_get_drvdata(chip);
+ bool invert = state->polarity == PWM_POLARITY_INVERSED;
+
+ if (state->duty_cycle && state->duty_cycle < hrtimer_resolution)
+ return -EINVAL;
+
+ if (state->duty_cycle != state->period &&
+ (state->period - state->duty_cycle < hrtimer_resolution))
+ return -EINVAL;
+
+ if (!state->enabled) {
+ hrtimer_cancel(&gpwm->gpio_timer);
+ } else if (!gpwm->running) {
+ int ret;
+
+ /*
+ * This just enables the output, but pwm_gpio_toggle()
+ * really starts the duty cycle.
+ */
+ ret = gpiod_direction_output(gpwm->gpio, invert);
+ if (ret)
+ return ret;
+ }
+
+ guard(spinlock_irqsave)(&gpwm->lock);
+
+ if (!state->enabled) {
+ pwm_gpio_round(&gpwm->state, state);
+ gpwm->running = false;
+ gpwm->changing = false;
+
+ gpiod_set_value(gpwm->gpio, invert);
+ } else if (gpwm->running) {
+ pwm_gpio_round(&gpwm->next_state, state);
+ gpwm->changing = true;
+ } else {
+ unsigned long next_toggle;
+
+ pwm_gpio_round(&gpwm->state, state);
+ gpwm->changing = false;
+
+ next_toggle = pwm_gpio_toggle(gpwm, !!state->duty_cycle);
+ if (next_toggle)
+ hrtimer_start(&gpwm->gpio_timer, next_toggle,
+ HRTIMER_MODE_REL);
+ }
+
+ return 0;
+}
+
+static int pwm_gpio_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct pwm_gpio *gpwm = pwmchip_get_drvdata(chip);
+
+ guard(spinlock_irqsave)(&gpwm->lock);
+
+ if (gpwm->changing)
+ *state = gpwm->next_state;
+ else
+ *state = gpwm->state;
+
+ return 0;
+}
+
+static const struct pwm_ops pwm_gpio_ops = {
+ .apply = pwm_gpio_apply,
+ .get_state = pwm_gpio_get_state,
+};
+
+static void pwm_gpio_disable_hrtimer(void *data)
+{
+ struct pwm_gpio *gpwm = data;
+
+ hrtimer_cancel(&gpwm->gpio_timer);
+}
+
+static int pwm_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pwm_chip *chip;
+ struct pwm_gpio *gpwm;
+ int ret;
+
+ chip = devm_pwmchip_alloc(dev, 1, sizeof(*gpwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ gpwm = pwmchip_get_drvdata(chip);
+
+ spin_lock_init(&gpwm->lock);
+
+ gpwm->gpio = devm_gpiod_get(dev, NULL, GPIOD_ASIS);
+ if (IS_ERR(gpwm->gpio))
+ return dev_err_probe(dev, PTR_ERR(gpwm->gpio),
+ "%pfw: could not get gpio\n",
+ dev_fwnode(dev));
+
+ if (gpiod_cansleep(gpwm->gpio))
+ return dev_err_probe(dev, -EINVAL,
+ "%pfw: sleeping GPIO not supported\n",
+ dev_fwnode(dev));
+
+ chip->ops = &pwm_gpio_ops;
+ chip->atomic = true;
+
+ hrtimer_setup(&gpwm->gpio_timer, pwm_gpio_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+
+ ret = devm_add_action_or_reset(dev, pwm_gpio_disable_hrtimer, gpwm);
+ if (ret)
+ return ret;
+
+ ret = pwmchip_add(chip);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "could not add pwmchip\n");
+
+ return 0;
+}
+
+static const struct of_device_id pwm_gpio_dt_ids[] = {
+ { .compatible = "pwm-gpio" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pwm_gpio_dt_ids);
+
+static struct platform_driver pwm_gpio_driver = {
+ .driver = {
+ .name = "pwm-gpio",
+ .of_match_table = pwm_gpio_dt_ids,
+ },
+ .probe = pwm_gpio_probe,
+};
+module_platform_driver(pwm_gpio_driver);
+
+MODULE_DESCRIPTION("PWM GPIO driver");
+MODULE_AUTHOR("Vincent Whitchurch");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-hibvt.c b/drivers/pwm/pwm-hibvt.c
index ad205fdad372..e02ee6383dbc 100644
--- a/drivers/pwm/pwm-hibvt.c
+++ b/drivers/pwm/pwm-hibvt.c
@@ -10,7 +10,7 @@
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/reset.h>
@@ -33,7 +33,6 @@
#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;
@@ -65,7 +64,7 @@ static const struct hibvt_pwm_soc hi3559v100_soc_info = {
static inline struct hibvt_pwm_chip *to_hibvt_pwm_chip(struct pwm_chip *chip)
{
- return container_of(chip, struct hibvt_pwm_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
static void hibvt_pwm_set_bits(void __iomem *base, u32 offset,
@@ -128,8 +127,8 @@ static void hibvt_pwm_set_polarity(struct pwm_chip *chip,
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)
+static int 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;
@@ -146,6 +145,9 @@ static void hibvt_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
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;
+
+ return 0;
}
static int hibvt_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -182,85 +184,77 @@ 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;
+ struct pwm_chip *chip;
+ struct hibvt_pwm_chip *hi_pwm_chip;
+ int ret, i;
- pwm_chip = devm_kzalloc(&pdev->dev, sizeof(*pwm_chip), GFP_KERNEL);
- if (pwm_chip == NULL)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, soc->num_pwms, sizeof(*hi_pwm_chip));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ hi_pwm_chip = to_hibvt_pwm_chip(chip);
- pwm_chip->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(pwm_chip->clk)) {
+ hi_pwm_chip->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(hi_pwm_chip->clk)) {
dev_err(&pdev->dev, "getting clock failed with %ld\n",
- PTR_ERR(pwm_chip->clk));
- return PTR_ERR(pwm_chip->clk);
+ PTR_ERR(hi_pwm_chip->clk));
+ return PTR_ERR(hi_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;
+ chip->ops = &hibvt_pwm_ops;
+ hi_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);
+ hi_pwm_chip->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(hi_pwm_chip->base))
+ return PTR_ERR(hi_pwm_chip->base);
- ret = clk_prepare_enable(pwm_chip->clk);
+ ret = clk_prepare_enable(hi_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);
+ hi_pwm_chip->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+ if (IS_ERR(hi_pwm_chip->rstc)) {
+ clk_disable_unprepare(hi_pwm_chip->clk);
+ return PTR_ERR(hi_pwm_chip->rstc);
}
- reset_control_assert(pwm_chip->rstc);
+ reset_control_assert(hi_pwm_chip->rstc);
msleep(30);
- reset_control_deassert(pwm_chip->rstc);
+ reset_control_deassert(hi_pwm_chip->rstc);
- ret = pwmchip_add(&pwm_chip->chip);
+ ret = pwmchip_add(chip);
if (ret < 0) {
- clk_disable_unprepare(pwm_chip->clk);
+ clk_disable_unprepare(hi_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),
+ for (i = 0; i < chip->npwm; i++) {
+ hibvt_pwm_set_bits(hi_pwm_chip->base, PWM_CTRL_ADDR(i),
PWM_KEEP_MASK, (0x1 << PWM_KEEP_SHIFT));
}
- platform_set_drvdata(pdev, pwm_chip);
+ platform_set_drvdata(pdev, chip);
return 0;
}
-static int hibvt_pwm_remove(struct platform_device *pdev)
+static void hibvt_pwm_remove(struct platform_device *pdev)
{
- struct hibvt_pwm_chip *pwm_chip;
+ struct pwm_chip *chip = platform_get_drvdata(pdev);
+ struct hibvt_pwm_chip *hi_pwm_chip = to_hibvt_pwm_chip(chip);
- pwm_chip = platform_get_drvdata(pdev);
+ pwmchip_remove(chip);
- reset_control_assert(pwm_chip->rstc);
+ reset_control_assert(hi_pwm_chip->rstc);
msleep(30);
- reset_control_deassert(pwm_chip->rstc);
-
- clk_disable_unprepare(pwm_chip->clk);
+ reset_control_deassert(hi_pwm_chip->rstc);
- return pwmchip_remove(&pwm_chip->chip);
+ clk_disable_unprepare(hi_pwm_chip->clk);
}
static const struct of_device_id hibvt_pwm_of_match[] = {
@@ -282,7 +276,7 @@ static struct platform_driver hibvt_pwm_driver = {
.of_match_table = hibvt_pwm_of_match,
},
.probe = hibvt_pwm_probe,
- .remove = hibvt_pwm_remove,
+ .remove = hibvt_pwm_remove,
};
module_platform_driver(hibvt_pwm_driver);
diff --git a/drivers/pwm/pwm-img.c b/drivers/pwm/pwm-img.c
index 599a0f66a384..91e0b19f0c08 100644
--- a/drivers/pwm/pwm-img.c
+++ b/drivers/pwm/pwm-img.c
@@ -13,9 +13,9 @@
#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/property.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/slab.h>
@@ -59,8 +59,6 @@ struct img_pwm_soc_data {
};
struct img_pwm_chip {
- struct device *dev;
- struct pwm_chip chip;
struct clk *pwm_clk;
struct clk *sys_clk;
void __iomem *base;
@@ -74,19 +72,18 @@ struct img_pwm_chip {
static inline struct img_pwm_chip *to_img_pwm_chip(struct pwm_chip *chip)
{
- return container_of(chip, struct img_pwm_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
-static inline void img_pwm_writel(struct img_pwm_chip *chip,
+static inline void img_pwm_writel(struct img_pwm_chip *imgchip,
u32 reg, u32 val)
{
- writel(val, chip->base + reg);
+ writel(val, imgchip->base + reg);
}
-static inline u32 img_pwm_readl(struct img_pwm_chip *chip,
- u32 reg)
+static inline u32 img_pwm_readl(struct img_pwm_chip *imgchip, u32 reg)
{
- return readl(chip->base + reg);
+ return readl(imgchip->base + reg);
}
static int img_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -94,17 +91,17 @@ static int img_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
{
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;
+ struct img_pwm_chip *imgchip = to_img_pwm_chip(chip);
+ unsigned int max_timebase = imgchip->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");
+ if (period_ns < imgchip->min_period_ns ||
+ period_ns > imgchip->max_period_ns) {
+ dev_err(pwmchip_parent(chip), "configured period not in range\n");
return -ERANGE;
}
- input_clk_hz = clk_get_rate(pwm_chip->pwm_clk);
+ input_clk_hz = clk_get_rate(imgchip->pwm_clk);
output_clk_hz = DIV_ROUND_UP(NSEC_PER_SEC, period_ns);
mul = DIV_ROUND_UP(input_clk_hz, output_clk_hz);
@@ -121,31 +118,28 @@ static int img_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
div = PWM_CTRL_CFG_SUB_DIV0_DIV1;
timebase = DIV_ROUND_UP(mul, 512);
} else {
- dev_err(chip->dev,
+ dev_err(pwmchip_parent(chip),
"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);
+ ret = pm_runtime_resume_and_get(pwmchip_parent(chip));
+ if (ret < 0)
return ret;
- }
- val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG);
+ val = img_pwm_readl(imgchip, 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);
+ img_pwm_writel(imgchip, 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);
+ img_pwm_writel(imgchip, PWM_CH_CFG(pwm->hwpwm), val);
- pm_runtime_mark_last_busy(chip->dev);
- pm_runtime_put_autosuspend(chip->dev);
+ pm_runtime_put_autosuspend(pwmchip_parent(chip));
return 0;
}
@@ -153,20 +147,20 @@ static int img_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+ struct img_pwm_chip *imgchip = to_img_pwm_chip(chip);
int ret;
- ret = pm_runtime_get_sync(chip->dev);
+ ret = pm_runtime_resume_and_get(pwmchip_parent(chip));
if (ret < 0)
return ret;
- val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG);
+ val = img_pwm_readl(imgchip, PWM_CTRL_CFG);
val |= BIT(pwm->hwpwm);
- img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val);
+ img_pwm_writel(imgchip, 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);
+ regmap_clear_bits(imgchip->periph_regs, PERIP_PWM_PDM_CONTROL,
+ PERIP_PWM_PDM_CONTROL_CH_MASK <<
+ PERIP_PWM_PDM_CONTROL_CH_SHIFT(pwm->hwpwm));
return 0;
}
@@ -174,21 +168,42 @@ static int img_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ struct img_pwm_chip *imgchip = to_img_pwm_chip(chip);
- val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG);
+ val = img_pwm_readl(imgchip, PWM_CTRL_CFG);
val &= ~BIT(pwm->hwpwm);
- img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val);
+ img_pwm_writel(imgchip, PWM_CTRL_CFG, val);
+
+ pm_runtime_put_autosuspend(pwmchip_parent(chip));
+}
+
+static int img_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ if (pwm->state.enabled)
+ img_pwm_disable(chip, pwm);
+
+ return 0;
+ }
- pm_runtime_mark_last_busy(chip->dev);
- pm_runtime_put_autosuspend(chip->dev);
+ err = img_pwm_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!pwm->state.enabled)
+ err = img_pwm_enable(chip, pwm);
+
+ return err;
}
static const struct pwm_ops img_pwm_ops = {
- .config = img_pwm_config,
- .enable = img_pwm_enable,
- .disable = img_pwm_disable,
- .owner = THIS_MODULE,
+ .apply = img_pwm_apply,
};
static const struct img_pwm_soc_data pistachio_pwm = {
@@ -206,29 +221,31 @@ 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);
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct img_pwm_chip *imgchip = to_img_pwm_chip(chip);
- clk_disable_unprepare(pwm_chip->pwm_clk);
- clk_disable_unprepare(pwm_chip->sys_clk);
+ clk_disable_unprepare(imgchip->pwm_clk);
+ clk_disable_unprepare(imgchip->sys_clk);
return 0;
}
static int img_pwm_runtime_resume(struct device *dev)
{
- struct img_pwm_chip *pwm_chip = dev_get_drvdata(dev);
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct img_pwm_chip *imgchip = to_img_pwm_chip(chip);
int ret;
- ret = clk_prepare_enable(pwm_chip->sys_clk);
+ ret = clk_prepare_enable(imgchip->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);
+ ret = clk_prepare_enable(imgchip->pwm_clk);
if (ret < 0) {
dev_err(dev, "could not prepare or enable pwm clock\n");
- clk_disable_unprepare(pwm_chip->sys_clk);
+ clk_disable_unprepare(imgchip->sys_clk);
return ret;
}
@@ -240,43 +257,39 @@ 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;
+ struct pwm_chip *chip;
+ struct img_pwm_chip *imgchip;
- pwm->dev = &pdev->dev;
+ chip = devm_pwmchip_alloc(&pdev->dev, IMG_PWM_NPWM, sizeof(*imgchip));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ imgchip = to_img_pwm_chip(chip);
- 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);
+ imgchip->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(imgchip->base))
+ return PTR_ERR(imgchip->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;
+ imgchip->data = device_get_match_data(&pdev->dev);
- 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);
+ imgchip->periph_regs = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "img,cr-periph");
+ if (IS_ERR(imgchip->periph_regs))
+ return PTR_ERR(imgchip->periph_regs);
- pwm->sys_clk = devm_clk_get(&pdev->dev, "sys");
- if (IS_ERR(pwm->sys_clk)) {
+ imgchip->sys_clk = devm_clk_get(&pdev->dev, "sys");
+ if (IS_ERR(imgchip->sys_clk)) {
dev_err(&pdev->dev, "failed to get system clock\n");
- return PTR_ERR(pwm->sys_clk);
+ return PTR_ERR(imgchip->sys_clk);
}
- pwm->pwm_clk = devm_clk_get(&pdev->dev, "pwm");
- if (IS_ERR(pwm->pwm_clk)) {
+ imgchip->pwm_clk = devm_clk_get(&pdev->dev, "pwm");
+ if (IS_ERR(imgchip->pwm_clk)) {
dev_err(&pdev->dev, "failed to get pwm clock\n");
- return PTR_ERR(pwm->pwm_clk);
+ return PTR_ERR(imgchip->pwm_clk);
}
+ platform_set_drvdata(pdev, chip);
+
pm_runtime_set_autosuspend_delay(&pdev->dev, IMG_PWM_PM_TIMEOUT);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
@@ -286,34 +299,30 @@ static int img_pwm_probe(struct platform_device *pdev)
goto err_pm_disable;
}
- clk_rate = clk_get_rate(pwm->pwm_clk);
+ clk_rate = clk_get_rate(imgchip->pwm_clk);
if (!clk_rate) {
- dev_err(&pdev->dev, "pwm clock has no frequency\n");
+ dev_err(&pdev->dev, "imgchip 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;
+ val = (u64)NSEC_PER_SEC * 512 * imgchip->data->max_timebase;
do_div(val, clk_rate);
- pwm->max_period_ns = val;
+ imgchip->max_period_ns = val;
val = (u64)NSEC_PER_SEC * MIN_TMBASE_STEPS;
do_div(val, clk_rate);
- pwm->min_period_ns = val;
+ imgchip->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;
+ chip->ops = &img_pwm_ops;
- ret = pwmchip_add(&pwm->chip);
+ ret = pwmchip_add(chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret);
goto err_suspend;
}
- platform_set_drvdata(pdev, pwm);
return 0;
err_suspend:
@@ -325,37 +334,22 @@ err_pm_disable:
return ret;
}
-static int img_pwm_remove(struct platform_device *pdev)
+static void img_pwm_remove(struct platform_device *pdev)
{
- struct img_pwm_chip *pwm_chip = platform_get_drvdata(pdev);
- u32 val;
- unsigned int i;
- int ret;
-
- ret = pm_runtime_get_sync(&pdev->dev);
- if (ret < 0) {
- pm_runtime_put(&pdev->dev);
- return ret;
- }
-
- for (i = 0; i < pwm_chip->chip.npwm; i++) {
- val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG);
- val &= ~BIT(i);
- img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val);
- }
+ struct pwm_chip *chip = platform_get_drvdata(pdev);
- pm_runtime_put(&pdev->dev);
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
img_pwm_runtime_suspend(&pdev->dev);
- return pwmchip_remove(&pwm_chip->chip);
+ pwmchip_remove(chip);
}
#ifdef CONFIG_PM_SLEEP
static int img_pwm_suspend(struct device *dev)
{
- struct img_pwm_chip *pwm_chip = dev_get_drvdata(dev);
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct img_pwm_chip *imgchip = to_img_pwm_chip(chip);
int i, ret;
if (pm_runtime_status_suspended(dev)) {
@@ -364,11 +358,11 @@ static int img_pwm_suspend(struct device *dev)
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));
+ for (i = 0; i < chip->npwm; i++)
+ imgchip->suspend_ch_cfg[i] = img_pwm_readl(imgchip,
+ PWM_CH_CFG(i));
- pwm_chip->suspend_ctrl_cfg = img_pwm_readl(pwm_chip, PWM_CTRL_CFG);
+ imgchip->suspend_ctrl_cfg = img_pwm_readl(imgchip, PWM_CTRL_CFG);
img_pwm_runtime_suspend(dev);
@@ -377,7 +371,8 @@ static int img_pwm_suspend(struct device *dev)
static int img_pwm_resume(struct device *dev)
{
- struct img_pwm_chip *pwm_chip = dev_get_drvdata(dev);
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct img_pwm_chip *imgchip = to_img_pwm_chip(chip);
int ret;
int i;
@@ -385,19 +380,18 @@ static int img_pwm_resume(struct device *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]);
+ for (i = 0; i < chip->npwm; i++)
+ img_pwm_writel(imgchip, PWM_CH_CFG(i),
+ imgchip->suspend_ch_cfg[i]);
- img_pwm_writel(pwm_chip, PWM_CTRL_CFG, pwm_chip->suspend_ctrl_cfg);
+ img_pwm_writel(imgchip, PWM_CTRL_CFG, imgchip->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);
+ for (i = 0; i < chip->npwm; i++)
+ if (imgchip->suspend_ctrl_cfg & BIT(i))
+ regmap_clear_bits(imgchip->periph_regs,
+ PERIP_PWM_PDM_CONTROL,
+ PERIP_PWM_PDM_CONTROL_CH_MASK <<
+ PERIP_PWM_PDM_CONTROL_CH_SHIFT(i));
if (pm_runtime_status_suspended(dev))
img_pwm_runtime_suspend(dev);
diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
index 5f3d7f7e6aef..5b399de16d60 100644
--- a/drivers/pwm/pwm-imx-tpm.c
+++ b/drivers/pwm/pwm-imx-tpm.c
@@ -20,6 +20,7 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
@@ -57,7 +58,6 @@
#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;
@@ -75,7 +75,7 @@ struct imx_tpm_pwm_param {
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);
+ return pwmchip_get_drvdata(chip);
}
/*
@@ -106,7 +106,9 @@ static int pwm_imx_tpm_round_state(struct pwm_chip *chip,
p->prescale = prescale;
period_count = (clock_unit + ((1 << prescale) >> 1)) >> prescale;
- p->mod = period_count;
+ if (period_count == 0)
+ return -EINVAL;
+ p->mod = period_count - 1;
/* calculate real period HW can support */
tmp = (u64)period_count << prescale;
@@ -124,7 +126,7 @@ static int pwm_imx_tpm_round_state(struct pwm_chip *chip,
real_state->duty_cycle = state->duty_cycle;
tmp = (u64)p->mod * real_state->duty_cycle;
- p->val = DIV_ROUND_CLOSEST_ULL(tmp, real_state->period);
+ p->val = DIV64_U64_ROUND_CLOSEST(tmp, real_state->period);
real_state->polarity = state->polarity;
real_state->enabled = state->enabled;
@@ -132,9 +134,9 @@ static int pwm_imx_tpm_round_state(struct pwm_chip *chip,
return 0;
}
-static void pwm_imx_tpm_get_state(struct pwm_chip *chip,
- struct pwm_device *pwm,
- struct pwm_state *state)
+static int 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;
@@ -164,6 +166,8 @@ static void pwm_imx_tpm_get_state(struct pwm_chip *chip,
/* get channel status */
state->enabled = FIELD_GET(PWM_IMX_TPM_CnSC_ELS, val) ? true : false;
+
+ return 0;
}
/* this function is supposed to be called with mutex hold */
@@ -201,6 +205,15 @@ static int pwm_imx_tpm_apply_hw(struct pwm_chip *chip,
writel(val, tpm->base + PWM_IMX_TPM_SC);
/*
+ * if the counter is disabled (CMOD == 0), programming the new
+ * period length (MOD) will not reset the counter (CNT). If
+ * CNT.COUNT happens to be bigger than the new MOD value then
+ * the counter will end up being reset way too late. Therefore,
+ * manually reset it to 0.
+ */
+ if (!cmod)
+ writel(0x0, tpm->base + PWM_IMX_TPM_CNT);
+ /*
* set period count:
* if the PWM is disabled (CMOD[1:0] = 2b00), then MOD register
* is updated when MOD register is written.
@@ -330,100 +343,96 @@ static const struct pwm_ops imx_tpm_pwm_ops = {
.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 pwm_chip *chip;
struct imx_tpm_pwm_chip *tpm;
+ struct clk *clk;
+ void __iomem *base;
int ret;
+ unsigned int npwm;
u32 val;
- tpm = devm_kzalloc(&pdev->dev, sizeof(*tpm), GFP_KERNEL);
- if (!tpm)
- return -ENOMEM;
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
- platform_set_drvdata(pdev, tpm);
+ clk = devm_clk_get_enabled(&pdev->dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(clk),
+ "failed to get PWM clock\n");
- tpm->base = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(tpm->base))
- return PTR_ERR(tpm->base);
+ /* get number of channels */
+ val = readl(base + PWM_IMX_TPM_PARAM);
+ npwm = FIELD_GET(PWM_IMX_TPM_PARAM_CHAN, val);
- 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;
- }
+ chip = devm_pwmchip_alloc(&pdev->dev, npwm, sizeof(*tpm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ tpm = to_imx_tpm_pwm_chip(chip);
- ret = clk_prepare_enable(tpm->clk);
- if (ret) {
- dev_err(&pdev->dev,
- "failed to prepare or enable clock: %d\n", ret);
- return ret;
- }
+ platform_set_drvdata(pdev, tpm);
- 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;
+ tpm->base = base;
+ tpm->clk = clk;
- /* get number of channels */
- val = readl(tpm->base + PWM_IMX_TPM_PARAM);
- tpm->chip.npwm = FIELD_GET(PWM_IMX_TPM_PARAM_CHAN, val);
+ chip->ops = &imx_tpm_pwm_ops;
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);
+ ret = devm_pwmchip_add(&pdev->dev, chip);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n");
- return ret;
+ return 0;
}
-static int __maybe_unused pwm_imx_tpm_suspend(struct device *dev)
+static int pwm_imx_tpm_suspend(struct device *dev)
{
struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
+ int ret;
if (tpm->enable_count > 0)
return -EBUSY;
+ /*
+ * 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;
+ ret = pinctrl_pm_select_sleep_state(dev);
+ if (ret)
+ clk_prepare_enable(tpm->clk);
+
+ return ret;
}
-static int __maybe_unused pwm_imx_tpm_resume(struct device *dev)
+static int pwm_imx_tpm_resume(struct device *dev)
{
struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
int ret = 0;
- ret = clk_prepare_enable(tpm->clk);
+ ret = pinctrl_pm_select_default_state(dev);
if (ret)
- dev_err(dev,
- "failed to prepare or enable clock: %d\n",
- ret);
+ return ret;
+
+ ret = clk_prepare_enable(tpm->clk);
+ if (ret) {
+ dev_err(dev, "failed to prepare or enable clock: %d\n", ret);
+ pinctrl_pm_select_sleep_state(dev);
+ }
return ret;
}
-static SIMPLE_DEV_PM_OPS(imx_tpm_pwm_pm,
- pwm_imx_tpm_suspend, pwm_imx_tpm_resume);
+static DEFINE_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", },
@@ -435,10 +444,9 @@ 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,
+ .pm = pm_ptr(&imx_tpm_pwm_pm),
},
.probe = pwm_imx_tpm_probe,
- .remove = pwm_imx_tpm_remove,
};
module_platform_driver(imx_tpm_pwm_driver);
diff --git a/drivers/pwm/pwm-imx1.c b/drivers/pwm/pwm-imx1.c
index f8b2c2e001a7..d5535d208005 100644
--- a/drivers/pwm/pwm-imx1.c
+++ b/drivers/pwm/pwm-imx1.c
@@ -14,7 +14,6 @@
#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>
@@ -29,10 +28,12 @@ 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 inline struct pwm_imx1_chip *to_pwm_imx1_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
static int pwm_imx1_clk_prepare_enable(struct pwm_chip *chip)
{
@@ -61,7 +62,7 @@ static void pwm_imx1_clk_disable_unprepare(struct pwm_chip *chip)
}
static int pwm_imx1_config(struct pwm_chip *chip,
- struct pwm_device *pwm, int duty_ns, int period_ns)
+ struct pwm_device *pwm, u64 duty_ns, u64 period_ns)
{
struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);
u32 max, p;
@@ -84,7 +85,7 @@ static int pwm_imx1_config(struct pwm_chip *chip,
* (/2 .. /16).
*/
max = readl(imx->mmio_base + MX1_PWMP);
- p = max * duty_ns / period_ns;
+ p = mul_u64_u64_div_u64(max, duty_ns, period_ns);
writel(max - p, imx->mmio_base + MX1_PWMS);
@@ -120,11 +121,33 @@ static void pwm_imx1_disable(struct pwm_chip *chip, struct pwm_device *pwm)
pwm_imx1_clk_disable_unprepare(chip);
}
+static int pwm_imx1_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ if (pwm->state.enabled)
+ pwm_imx1_disable(chip, pwm);
+
+ return 0;
+ }
+
+ err = pwm_imx1_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!pwm->state.enabled)
+ return pwm_imx1_enable(chip, pwm);
+
+ return 0;
+}
+
static const struct pwm_ops pwm_imx1_ops = {
- .enable = pwm_imx1_enable,
- .disable = pwm_imx1_disable,
- .config = pwm_imx1_config,
- .owner = THIS_MODULE,
+ .apply = pwm_imx1_apply,
};
static const struct of_device_id pwm_imx1_dt_ids[] = {
@@ -135,54 +158,31 @@ MODULE_DEVICE_TABLE(of, pwm_imx1_dt_ids);
static int pwm_imx1_probe(struct platform_device *pdev)
{
+ struct pwm_chip *chip;
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);
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*imx));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ imx = to_pwm_imx1_chip(chip);
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);
- }
+ if (IS_ERR(imx->clk_ipg))
+ return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_ipg),
+ "getting ipg clock failed\n");
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;
- }
+ if (IS_ERR(imx->clk_per))
+ return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_per),
+ "failed to get peripheral clock\n");
- imx->chip.ops = &pwm_imx1_ops;
- imx->chip.dev = &pdev->dev;
- imx->chip.base = -1;
- imx->chip.npwm = 1;
+ chip->ops = &pwm_imx1_ops;
- r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- imx->mmio_base = devm_ioremap_resource(&pdev->dev, r);
+ imx->mmio_base = devm_platform_ioremap_resource(pdev, 0);
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);
-
- pwm_imx1_clk_disable_unprepare(&imx->chip);
-
- return pwmchip_remove(&imx->chip);
+ return devm_pwmchip_add(&pdev->dev, chip);
}
static struct platform_driver pwm_imx1_driver = {
@@ -191,9 +191,9 @@ static struct platform_driver pwm_imx1_driver = {
.of_match_table = pwm_imx1_dt_ids,
},
.probe = pwm_imx1_probe,
- .remove = pwm_imx1_remove,
};
module_platform_driver(pwm_imx1_driver);
+MODULE_DESCRIPTION("i.MX1 and i.MX21 Pulse Width Modulator driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
diff --git a/drivers/pwm/pwm-imx27.c b/drivers/pwm/pwm-imx27.c
index 732a6f3701e8..3d34cdc4a3a5 100644
--- a/drivers/pwm/pwm-imx27.c
+++ b/drivers/pwm/pwm-imx27.c
@@ -26,6 +26,7 @@
#define MX3_PWMSR 0x04 /* PWM Status Register */
#define MX3_PWMSAR 0x0C /* PWM Sample Register */
#define MX3_PWMPR 0x10 /* PWM Period Register */
+#define MX3_PWMCNR 0x14 /* PWM Counter Register */
#define MX3_PWMCR_FWM GENMASK(27, 26)
#define MX3_PWMCR_STOPEN BIT(25)
@@ -79,11 +80,13 @@
/* PWMPR register value of 0xffff has the same effect as 0xfffe */
#define MX3_PWMPR_MAX 0xfffe
+static const char * const pwm_imx27_clks[] = {"ipg", "per"};
+#define PWM_IMX27_PER 1
+
struct pwm_imx27_chip {
- struct clk *clk_ipg;
- struct clk *clk_per;
+ struct clk_bulk_data clks[ARRAY_SIZE(pwm_imx27_clks)];
+ int clks_cnt;
void __iomem *mmio_base;
- struct pwm_chip chip;
/*
* The driver cannot read the current duty cycle from the hardware if
@@ -93,42 +96,22 @@ struct pwm_imx27_chip {
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)
+static inline struct pwm_imx27_chip *to_pwm_imx27_chip(struct pwm_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;
+ return pwmchip_get_drvdata(chip);
}
-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)
+static int 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);
+ ret = clk_bulk_prepare_enable(imx->clks_cnt, imx->clks);
if (ret < 0)
- return;
+ return ret;
val = readl(imx->mmio_base + MX3_PWMCR);
@@ -145,11 +128,11 @@ static void pwm_imx27_get_state(struct pwm_chip *chip,
state->polarity = PWM_POLARITY_INVERSED;
break;
default:
- dev_warn(chip->dev, "can't set polarity, output disconnected");
+ dev_warn(pwmchip_parent(chip), "can't set polarity, output disconnected");
}
prescaler = MX3_PWMCR_PRESCALER_GET(val);
- pwm_clk = clk_get_rate(imx->clk_per);
+ pwm_clk = clk_get_rate(imx->clks[PWM_IMX27_PER].clk);
val = readl(imx->mmio_base + MX3_PWMPR);
period = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val;
@@ -169,13 +152,15 @@ static void pwm_imx27_get_state(struct pwm_chip *chip,
tmp = NSEC_PER_SEC * (u64)(val) * prescaler;
state->duty_cycle = DIV_ROUND_UP_ULL(tmp, pwm_clk);
- pwm_imx27_clk_disable_unprepare(imx);
+ clk_bulk_disable_unprepare(imx->clks_cnt, imx->clks);
+
+ return 0;
}
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;
+ struct device *dev = pwmchip_parent(chip);
int wait_count = 0;
u32 cr;
@@ -194,7 +179,7 @@ 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;
+ struct device *dev = pwmchip_parent(chip);
unsigned int period_ms;
int fifoav;
u32 sr;
@@ -202,8 +187,8 @@ static void pwm_imx27_wait_fifo_slot(struct pwm_chip *chip,
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(pwm_get_period(pwm),
- NSEC_PER_MSEC);
+ period_ms = DIV_ROUND_UP_ULL(pwm->state.period,
+ NSEC_PER_MSEC);
msleep(period_ms);
sr = readl(imx->mmio_base + MX3_PWMSR);
@@ -215,17 +200,16 @@ static void pwm_imx27_wait_fifo_slot(struct pwm_chip *chip,
static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
- unsigned long period_cycles, duty_cycles, prescale;
+ unsigned long period_cycles, duty_cycles, prescale, period_us, tmp;
struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
- struct pwm_state cstate;
unsigned long long c;
unsigned long long clkrate;
+ unsigned long flags;
+ int val;
int ret;
u32 cr;
- pwm_get_state(pwm, &cstate);
-
- clkrate = clk_get_rate(imx->clk_per);
+ clkrate = clk_get_rate(imx->clks[PWM_IMX27_PER].clk);
c = clkrate * state->period;
do_div(c, NSEC_PER_SEC);
@@ -235,8 +219,9 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm,
period_cycles /= prescale;
c = clkrate * state->duty_cycle;
- do_div(c, NSEC_PER_SEC * prescale);
+ do_div(c, NSEC_PER_SEC);
duty_cycles = c;
+ duty_cycles /= prescale;
/*
* according to imx pwm RM, the real period value should be PERIOD
@@ -251,17 +236,108 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm,
* 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) {
+ if (pwm->state.enabled) {
pwm_imx27_wait_fifo_slot(chip, pwm);
} else {
- ret = pwm_imx27_clk_prepare_enable(imx);
+ ret = clk_bulk_prepare_enable(imx->clks_cnt, imx->clks);
if (ret)
return ret;
pwm_imx27_sw_reset(chip);
}
- writel(duty_cycles, imx->mmio_base + MX3_PWMSAR);
+ val = readl(imx->mmio_base + MX3_PWMPR);
+ val = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val;
+ cr = readl(imx->mmio_base + MX3_PWMCR);
+ tmp = NSEC_PER_SEC * (u64)(val + 2) * MX3_PWMCR_PRESCALER_GET(cr);
+ tmp = DIV_ROUND_UP_ULL(tmp, clkrate);
+ period_us = DIV_ROUND_UP_ULL(tmp, 1000);
+
+ /*
+ * ERR051198:
+ * PWM: PWM output may not function correctly if the FIFO is empty when
+ * a new SAR value is programmed
+ *
+ * Description:
+ * When the PWM FIFO is empty, a new value programmed to the PWM Sample
+ * register (PWM_PWMSAR) will be directly applied even if the current
+ * timer period has not expired.
+ *
+ * If the new SAMPLE value programmed in the PWM_PWMSAR register is
+ * less than the previous value, and the PWM counter register
+ * (PWM_PWMCNR) that contains the current COUNT value is greater than
+ * the new programmed SAMPLE value, the current period will not flip
+ * the level. This may result in an output pulse with a duty cycle of
+ * 100%.
+ *
+ * Consider a change from
+ * ________
+ * / \______/
+ * ^ * ^
+ * to
+ * ____
+ * / \__________/
+ * ^ ^
+ * At the time marked by *, the new write value will be directly applied
+ * to SAR even the current period is not over if FIFO is empty.
+ *
+ * ________ ____________________
+ * / \______/ \__________/
+ * ^ ^ * ^ ^
+ * |<-- old SAR -->| |<-- new SAR -->|
+ *
+ * That is the output is active for a whole period.
+ *
+ * Workaround:
+ * Check new SAR less than old SAR and current counter is in errata
+ * windows, write extra old SAR into FIFO and new SAR will effect at
+ * next period.
+ *
+ * Sometime period is quite long, such as over 1 second. If add old SAR
+ * into FIFO unconditional, new SAR have to wait for next period. It
+ * may be too long.
+ *
+ * Turn off the interrupt to ensure that not IRQ and schedule happen
+ * during above operations. If any irq and schedule happen, counter
+ * in PWM will be out of data and take wrong action.
+ *
+ * Add a safety margin 1.5us because it needs some time to complete
+ * IO write.
+ *
+ * Use writel_relaxed() to minimize the interval between two writes to
+ * the SAR register to increase the fastest PWM frequency supported.
+ *
+ * When the PWM period is longer than 2us(or <500kHz), this workaround
+ * can solve this problem. No software workaround is available if PWM
+ * period is shorter than IO write. Just try best to fill old data
+ * into FIFO.
+ */
+ c = clkrate * 1500;
+ do_div(c, NSEC_PER_SEC);
+
+ local_irq_save(flags);
+ val = FIELD_GET(MX3_PWMSR_FIFOAV, readl_relaxed(imx->mmio_base + MX3_PWMSR));
+
+ if (duty_cycles < imx->duty_cycle && (cr & MX3_PWMCR_EN)) {
+ if (period_us < 2) { /* 2us = 500 kHz */
+ /* Best effort attempt to fix up >500 kHz case */
+ udelay(3 * period_us);
+ writel_relaxed(imx->duty_cycle, imx->mmio_base + MX3_PWMSAR);
+ writel_relaxed(imx->duty_cycle, imx->mmio_base + MX3_PWMSAR);
+ } else if (val < MX3_PWMSR_FIFOAV_2WORDS) {
+ val = readl_relaxed(imx->mmio_base + MX3_PWMCNR);
+ /*
+ * If counter is close to period, controller may roll over when
+ * next IO write.
+ */
+ if ((val + c >= duty_cycles && val < imx->duty_cycle) ||
+ val + c >= period_cycles)
+ writel_relaxed(imx->duty_cycle, imx->mmio_base + MX3_PWMSAR);
+ }
+ }
+ writel_relaxed(duty_cycles, imx->mmio_base + MX3_PWMSAR);
+ local_irq_restore(flags);
+
writel(period_cycles, imx->mmio_base + MX3_PWMPR);
/*
@@ -285,7 +361,7 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm,
writel(cr, imx->mmio_base + MX3_PWMCR);
if (!state->enabled)
- pwm_imx27_clk_disable_unprepare(imx);
+ clk_bulk_disable_unprepare(imx->clks_cnt, imx->clks);
return 0;
}
@@ -293,7 +369,6 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm,
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[] = {
@@ -304,70 +379,43 @@ MODULE_DEVICE_TABLE(of, pwm_imx27_dt_ids);
static int pwm_imx27_probe(struct platform_device *pdev)
{
+ struct pwm_chip *chip;
struct pwm_imx27_chip *imx;
int ret;
u32 pwmcr;
+ int i;
- 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;
- }
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*imx));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ imx = to_pwm_imx27_chip(chip);
- imx->clk_per = devm_clk_get(&pdev->dev, "per");
- if (IS_ERR(imx->clk_per)) {
- int ret = PTR_ERR(imx->clk_per);
+ imx->clks_cnt = ARRAY_SIZE(pwm_imx27_clks);
+ for (i = 0; i < imx->clks_cnt; ++i)
+ imx->clks[i].id = pwm_imx27_clks[i];
- if (ret != -EPROBE_DEFER)
- dev_err(&pdev->dev,
- "failed to get peripheral clock: %d\n",
- ret);
+ ret = devm_clk_bulk_get(&pdev->dev, imx->clks_cnt, imx->clks);
- return ret;
- }
-
- imx->chip.ops = &pwm_imx27_ops;
- imx->chip.dev = &pdev->dev;
- imx->chip.base = -1;
- imx->chip.npwm = 1;
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "getting clocks failed\n");
- imx->chip.of_xlate = of_pwm_xlate_with_flags;
- imx->chip.of_pwm_n_cells = 3;
+ chip->ops = &pwm_imx27_ops;
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);
+ ret = clk_bulk_prepare_enable(imx->clks_cnt, imx->clks);
if (ret)
return ret;
/* keep clks on if pwm is running */
pwmcr = readl(imx->mmio_base + MX3_PWMCR);
if (!(pwmcr & MX3_PWMCR_EN))
- pwm_imx27_clk_disable_unprepare(imx);
-
- return pwmchip_add(&imx->chip);
-}
-
-static int pwm_imx27_remove(struct platform_device *pdev)
-{
- struct pwm_imx27_chip *imx;
-
- imx = platform_get_drvdata(pdev);
+ clk_bulk_disable_unprepare(imx->clks_cnt, imx->clks);
- return pwmchip_remove(&imx->chip);
+ return devm_pwmchip_add(&pdev->dev, chip);
}
static struct platform_driver imx_pwm_driver = {
@@ -376,9 +424,9 @@ static struct platform_driver imx_pwm_driver = {
.of_match_table = pwm_imx27_dt_ids,
},
.probe = pwm_imx27_probe,
- .remove = pwm_imx27_remove,
};
module_platform_driver(imx_pwm_driver);
+MODULE_DESCRIPTION("i.MX27 and later i.MX SoCs Pulse Width Modulator driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
diff --git a/drivers/pwm/pwm-intel-lgm.c b/drivers/pwm/pwm-intel-lgm.c
new file mode 100644
index 000000000000..084c71a0a11b
--- /dev/null
+++ b/drivers/pwm/pwm-intel-lgm.c
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Intel Corporation.
+ *
+ * Limitations:
+ * - The hardware supports fixed period & configures only 2-wire mode.
+ * - Supports normal polarity. Does not support changing polarity.
+ * - When PWM is disabled, output of PWM will become 0(inactive). It doesn't
+ * keep track of running period.
+ * - When duty cycle is changed, PWM output may be a mix of previous setting
+ * and new setting for the first period. From second period, the output is
+ * based on new setting.
+ * - It is a dedicated PWM fan controller. There are no other consumers for
+ * this PWM controller.
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#define LGM_PWM_FAN_CON0 0x0
+#define LGM_PWM_FAN_EN_EN BIT(0)
+#define LGM_PWM_FAN_EN_DIS 0x0
+#define LGM_PWM_FAN_EN_MSK BIT(0)
+#define LGM_PWM_FAN_MODE_2WIRE 0x0
+#define LGM_PWM_FAN_MODE_MSK BIT(1)
+#define LGM_PWM_FAN_DC_MSK GENMASK(23, 16)
+
+#define LGM_PWM_FAN_CON1 0x4
+#define LGM_PWM_FAN_MAX_RPM_MSK GENMASK(15, 0)
+
+#define LGM_PWM_MAX_RPM (BIT(16) - 1)
+#define LGM_PWM_DEFAULT_RPM 4000
+#define LGM_PWM_MAX_DUTY_CYCLE (BIT(8) - 1)
+
+#define LGM_PWM_DC_BITS 8
+
+#define LGM_PWM_PERIOD_2WIRE_NS (40 * NSEC_PER_MSEC)
+
+struct lgm_pwm_chip {
+ struct regmap *regmap;
+ u32 period;
+};
+
+static inline struct lgm_pwm_chip *to_lgm_pwm_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static int lgm_pwm_enable(struct pwm_chip *chip, bool enable)
+{
+ struct lgm_pwm_chip *pc = to_lgm_pwm_chip(chip);
+ struct regmap *regmap = pc->regmap;
+
+ return regmap_update_bits(regmap, LGM_PWM_FAN_CON0, LGM_PWM_FAN_EN_MSK,
+ enable ? LGM_PWM_FAN_EN_EN : LGM_PWM_FAN_EN_DIS);
+}
+
+static int lgm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct lgm_pwm_chip *pc = to_lgm_pwm_chip(chip);
+ u32 duty_cycle, val;
+ int ret;
+
+ /* The hardware only supports normal polarity and fixed period. */
+ if (state->polarity != PWM_POLARITY_NORMAL || state->period < pc->period)
+ return -EINVAL;
+
+ if (!state->enabled)
+ return lgm_pwm_enable(chip, 0);
+
+ duty_cycle = min_t(u64, state->duty_cycle, pc->period);
+ val = duty_cycle * LGM_PWM_MAX_DUTY_CYCLE / pc->period;
+
+ ret = regmap_update_bits(pc->regmap, LGM_PWM_FAN_CON0, LGM_PWM_FAN_DC_MSK,
+ FIELD_PREP(LGM_PWM_FAN_DC_MSK, val));
+ if (ret)
+ return ret;
+
+ return lgm_pwm_enable(chip, 1);
+}
+
+static int lgm_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct lgm_pwm_chip *pc = to_lgm_pwm_chip(chip);
+ u32 duty, val;
+
+ state->enabled = regmap_test_bits(pc->regmap, LGM_PWM_FAN_CON0,
+ LGM_PWM_FAN_EN_EN);
+ state->polarity = PWM_POLARITY_NORMAL;
+ state->period = pc->period; /* fixed period */
+
+ regmap_read(pc->regmap, LGM_PWM_FAN_CON0, &val);
+ duty = FIELD_GET(LGM_PWM_FAN_DC_MSK, val);
+ state->duty_cycle = DIV_ROUND_UP(duty * pc->period, LGM_PWM_MAX_DUTY_CYCLE);
+
+ return 0;
+}
+
+static const struct pwm_ops lgm_pwm_ops = {
+ .get_state = lgm_pwm_get_state,
+ .apply = lgm_pwm_apply,
+};
+
+static void lgm_pwm_init(struct lgm_pwm_chip *pc)
+{
+ struct regmap *regmap = pc->regmap;
+ u32 con0_val;
+
+ con0_val = FIELD_PREP(LGM_PWM_FAN_MODE_MSK, LGM_PWM_FAN_MODE_2WIRE);
+ pc->period = LGM_PWM_PERIOD_2WIRE_NS;
+ regmap_update_bits(regmap, LGM_PWM_FAN_CON1, LGM_PWM_FAN_MAX_RPM_MSK,
+ LGM_PWM_DEFAULT_RPM);
+ regmap_update_bits(regmap, LGM_PWM_FAN_CON0, LGM_PWM_FAN_MODE_MSK,
+ con0_val);
+}
+
+static const struct regmap_config lgm_pwm_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+};
+
+static void lgm_clk_release(void *data)
+{
+ struct clk *clk = data;
+
+ clk_disable_unprepare(clk);
+}
+
+static int lgm_clk_enable(struct device *dev, struct clk *clk)
+{
+ int ret;
+
+ ret = clk_prepare_enable(clk);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, lgm_clk_release, clk);
+}
+
+static void lgm_reset_control_release(void *data)
+{
+ struct reset_control *rst = data;
+
+ reset_control_assert(rst);
+}
+
+static int lgm_reset_control_deassert(struct device *dev, struct reset_control *rst)
+{
+ int ret;
+
+ ret = reset_control_deassert(rst);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, lgm_reset_control_release, rst);
+}
+
+static int lgm_pwm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct reset_control *rst;
+ struct pwm_chip *chip;
+ struct lgm_pwm_chip *pc;
+ void __iomem *io_base;
+ struct clk *clk;
+ int ret;
+
+ chip = devm_pwmchip_alloc(dev, 1, sizeof(*pc));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ pc = to_lgm_pwm_chip(chip);
+
+ io_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(io_base))
+ return PTR_ERR(io_base);
+
+ pc->regmap = devm_regmap_init_mmio(dev, io_base, &lgm_pwm_regmap_config);
+ if (IS_ERR(pc->regmap))
+ return dev_err_probe(dev, PTR_ERR(pc->regmap),
+ "failed to init register map\n");
+
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n");
+
+ ret = lgm_clk_enable(dev, clk);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable clock\n");
+
+ rst = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(rst))
+ return dev_err_probe(dev, PTR_ERR(rst),
+ "failed to get reset control\n");
+
+ ret = lgm_reset_control_deassert(dev, rst);
+ if (ret)
+ return dev_err_probe(dev, ret, "cannot deassert reset control\n");
+
+ chip->ops = &lgm_pwm_ops;
+
+ lgm_pwm_init(pc);
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to add PWM chip\n");
+
+ return 0;
+}
+
+static const struct of_device_id lgm_pwm_of_match[] = {
+ { .compatible = "intel,lgm-pwm" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lgm_pwm_of_match);
+
+static struct platform_driver lgm_pwm_driver = {
+ .driver = {
+ .name = "intel-pwm",
+ .of_match_table = lgm_pwm_of_match,
+ },
+ .probe = lgm_pwm_probe,
+};
+module_platform_driver(lgm_pwm_driver);
+
+MODULE_DESCRIPTION("Intel LGM Pulse Width Modulator driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/pwm/pwm-iqs620a.c b/drivers/pwm/pwm-iqs620a.c
index 674f0e238ba0..13e5e138c8e9 100644
--- a/drivers/pwm/pwm-iqs620a.c
+++ b/drivers/pwm/pwm-iqs620a.c
@@ -25,37 +25,60 @@
#include <linux/regmap.h>
#include <linux/slab.h>
-#define IQS620_PWR_SETTINGS 0xD2
+#define IQS620_PWR_SETTINGS 0xd2
#define IQS620_PWR_SETTINGS_PWM_OUT BIT(7)
-#define IQS620_PWM_DUTY_CYCLE 0xD8
+#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 device *dev;
struct notifier_block notifier;
struct mutex lock;
- bool out_en;
- u8 duty_val;
+ unsigned int duty_scale;
};
+static inline struct iqs620_pwm_private *iqs620_pwm_from_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static int iqs620_pwm_init(struct iqs620_pwm_private *iqs620_pwm,
+ unsigned int duty_scale)
+{
+ struct iqs62x_core *iqs62x = iqs620_pwm->iqs62x;
+ int ret;
+
+ if (!duty_scale)
+ return regmap_clear_bits(iqs62x->regmap, IQS620_PWR_SETTINGS,
+ IQS620_PWR_SETTINGS_PWM_OUT);
+
+ ret = regmap_write(iqs62x->regmap, IQS620_PWM_DUTY_CYCLE,
+ duty_scale - 1);
+ if (ret)
+ return ret;
+
+ return regmap_set_bits(iqs62x->regmap, IQS620_PWR_SETTINGS,
+ IQS620_PWR_SETTINGS_PWM_OUT);
+}
+
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;
- int duty_scale, ret;
+ unsigned int duty_cycle;
+ unsigned int duty_scale;
+ int ret;
if (state->polarity != PWM_POLARITY_NORMAL)
- return -ENOTSUPP;
+ return -EINVAL;
if (state->period < IQS620_PWM_PERIOD_NS)
return -EINVAL;
- iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
- iqs62x = iqs620_pwm->iqs62x;
+ iqs620_pwm = iqs620_pwm_from_chip(chip);
/*
* The duty cycle generated by the device is calculated as follows:
@@ -69,73 +92,54 @@ static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
* 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_scale = state->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 = min(duty_scale - 1, 0xFF);
-
- ret = regmap_write(iqs62x->regmap, IQS620_PWM_DUTY_CYCLE,
- duty_val);
- if (ret)
- goto err_mutex;
+ duty_cycle = min_t(u64, state->duty_cycle, IQS620_PWM_PERIOD_NS);
+ duty_scale = duty_cycle * 256 / IQS620_PWM_PERIOD_NS;
- iqs620_pwm->duty_val = duty_val;
- }
+ if (!state->enabled)
+ duty_scale = 0;
- 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;
- }
+ mutex_lock(&iqs620_pwm->lock);
- iqs620_pwm->out_en = state->enabled;
+ ret = iqs620_pwm_init(iqs620_pwm, duty_scale);
+ if (!ret)
+ iqs620_pwm->duty_scale = duty_scale;
-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)
+static int 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);
+ iqs620_pwm = iqs620_pwm_from_chip(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
+ * as disabled. 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) *
+ state->enabled = iqs620_pwm->duty_scale > 0;
+ state->duty_cycle = DIV_ROUND_UP(iqs620_pwm->duty_scale *
IQS620_PWM_PERIOD_NS, 256);
mutex_unlock(&iqs620_pwm->lock);
state->period = IQS620_PWM_PERIOD_NS;
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ return 0;
}
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)))
@@ -143,7 +147,6 @@ static int iqs620_pwm_notifier(struct notifier_block *notifier,
iqs620_pwm = container_of(notifier, struct iqs620_pwm_private,
notifier);
- iqs62x = iqs620_pwm->iqs62x;
mutex_lock(&iqs620_pwm->lock);
@@ -152,20 +155,12 @@ static int iqs620_pwm_notifier(struct notifier_block *notifier,
* 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 = iqs620_pwm_init(iqs620_pwm, iqs620_pwm->duty_scale);
- 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,
+ dev_err(iqs620_pwm->dev,
"Failed to re-initialize device: %d\n", ret);
return NOTIFY_BAD;
}
@@ -176,7 +171,6 @@ err_mutex:
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)
@@ -187,38 +181,39 @@ static void iqs620_pwm_notifier_unregister(void *context)
ret = blocking_notifier_chain_unregister(&iqs620_pwm->iqs62x->nh,
&iqs620_pwm->notifier);
if (ret)
- dev_err(iqs620_pwm->chip.dev,
+ dev_err(iqs620_pwm->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 pwm_chip *chip;
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;
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*iqs620_pwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
- platform_set_drvdata(pdev, iqs620_pwm);
+ iqs620_pwm = iqs620_pwm_from_chip(chip);
+ iqs620_pwm->dev = &pdev->dev;
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;
+ if (val & IQS620_PWR_SETTINGS_PWM_OUT) {
+ ret = regmap_read(iqs62x->regmap, IQS620_PWM_DUTY_CYCLE, &val);
+ if (ret)
+ return ret;
+
+ iqs620_pwm->duty_scale = val + 1;
+ }
- iqs620_pwm->chip.dev = &pdev->dev;
- iqs620_pwm->chip.ops = &iqs620_pwm_ops;
- iqs620_pwm->chip.base = -1;
- iqs620_pwm->chip.npwm = 1;
+ chip->ops = &iqs620_pwm_ops;
mutex_init(&iqs620_pwm->lock);
@@ -236,31 +231,18 @@ static int iqs620_pwm_probe(struct platform_device *pdev)
if (ret)
return ret;
- ret = pwmchip_add(&iqs620_pwm->chip);
+ ret = devm_pwmchip_add(&pdev->dev, 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);
diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c
index 5830ac2bdf6a..6bdb01619380 100644
--- a/drivers/pwm/pwm-jz4740.c
+++ b/drivers/pwm/pwm-jz4740.c
@@ -15,7 +15,7 @@
#include <linux/mfd/ingenic-tcu.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
@@ -25,22 +25,21 @@ struct soc_info {
};
struct jz4740_pwm_chip {
- struct pwm_chip chip;
struct regmap *map;
+ struct clk *clk[];
};
static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip)
{
- return container_of(chip, struct jz4740_pwm_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
-static bool jz4740_pwm_can_use_chn(struct jz4740_pwm_chip *jz,
- unsigned int channel)
+static bool jz4740_pwm_can_use_chn(struct pwm_chip *chip, 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);
+ u32 pwm_channels_mask = GENMASK(chip->npwm - 1, 2);
- device_property_read_u32(jz->chip.dev->parent,
+ device_property_read_u32(pwmchip_parent(chip)->parent,
"ingenic,pwm-channels-mask",
&pwm_channels_mask);
@@ -54,16 +53,15 @@ static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
char name[16];
int err;
- if (!jz4740_pwm_can_use_chn(jz, pwm->hwpwm))
+ if (!jz4740_pwm_can_use_chn(chip, pwm->hwpwm))
return -EBUSY;
snprintf(name, sizeof(name), "timer%u", pwm->hwpwm);
- clk = clk_get(chip->dev, name);
+ clk = clk_get(pwmchip_parent(chip), name);
if (IS_ERR(clk)) {
- if (PTR_ERR(clk) != -EPROBE_DEFER)
- dev_err(chip->dev, "Failed to get clock: %pe", clk);
-
+ dev_err(pwmchip_parent(chip),
+ "error %pe: Failed to get clock\n", clk);
return PTR_ERR(clk);
}
@@ -73,14 +71,15 @@ static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
return err;
}
- pwm_set_chip_data(pwm, clk);
+ jz->clk[pwm->hwpwm] = clk;
return 0;
}
static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
- struct clk *clk = pwm_get_chip_data(pwm);
+ struct jz4740_pwm_chip *jz = to_jz4740(chip);
+ struct clk *clk = jz->clk[pwm->hwpwm];
clk_disable_unprepare(clk);
clk_put(clk);
@@ -91,8 +90,7 @@ 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);
+ regmap_set_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_EN);
/* Start counter */
regmap_write(jz->map, TCU_REG_TESR, BIT(pwm->hwpwm));
@@ -116,8 +114,7 @@ static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
* 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);
+ regmap_clear_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm), TCU_TCSR_PWM_EN);
/* Stop counter */
regmap_write(jz->map, TCU_REG_TECR, BIT(pwm->hwpwm));
@@ -126,9 +123,9 @@ static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ struct jz4740_pwm_chip *jz = to_jz4740(chip);
unsigned long long tmp = 0xffffull * NSEC_PER_SEC;
- struct clk *clk = pwm_get_chip_data(pwm);
+ struct clk *clk = jz->clk[pwm->hwpwm];
unsigned long period, duty;
long rate;
int err;
@@ -152,7 +149,7 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
*/
rate = clk_round_rate(clk, tmp);
if (rate < 0) {
- dev_err(chip->dev, "Unable to round rate: %ld", rate);
+ dev_err(pwmchip_parent(chip), "Unable to round rate: %ld\n", rate);
return rate;
}
@@ -173,22 +170,22 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
err = clk_set_rate(clk, rate);
if (err) {
- dev_err(chip->dev, "Unable to set rate: %d", err);
+ dev_err(pwmchip_parent(chip), "Unable to set rate: %d\n", err);
return err;
}
/* Reset counter to 0 */
- regmap_write(jz4740->map, TCU_REG_TCNTc(pwm->hwpwm), 0);
+ regmap_write(jz->map, TCU_REG_TCNTc(pwm->hwpwm), 0);
/* Set duty */
- regmap_write(jz4740->map, TCU_REG_TDHRc(pwm->hwpwm), duty);
+ regmap_write(jz->map, TCU_REG_TDHRc(pwm->hwpwm), duty);
/* Set period */
- regmap_write(jz4740->map, TCU_REG_TDFRc(pwm->hwpwm), period);
+ regmap_write(jz->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);
+ regmap_set_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm),
+ TCU_TCSR_PWM_SD);
/*
* Set polarity.
@@ -204,12 +201,11 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
* state instead of its inactive state.
*/
if ((state->polarity == PWM_POLARITY_NORMAL) ^ state->enabled)
- regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm),
- TCU_TCSR_PWM_INITL_HIGH, 0);
+ regmap_clear_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm),
+ TCU_TCSR_PWM_INITL_HIGH);
else
- regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm),
- TCU_TCSR_PWM_INITL_HIGH,
- TCU_TCSR_PWM_INITL_HIGH);
+ regmap_set_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm),
+ TCU_TCSR_PWM_INITL_HIGH);
if (state->enabled)
jz4740_pwm_enable(chip, pwm);
@@ -221,72 +217,61 @@ 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;
+ struct pwm_chip *chip;
+ struct jz4740_pwm_chip *jz;
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;
+ chip = devm_pwmchip_alloc(dev, info->num_pwms, struct_size(jz, clk, info->num_pwms));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ jz = to_jz4740(chip);
- 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);
+ jz->map = device_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(jz->map)) {
+ dev_err(dev, "regmap not found: %ld\n", PTR_ERR(jz->map));
+ return PTR_ERR(jz->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);
+ chip->ops = &jz4740_pwm_ops;
- return pwmchip_remove(&jz4740->chip);
+ return devm_pwmchip_add(dev, chip);
}
-static const struct soc_info __maybe_unused jz4740_soc_info = {
+static const struct soc_info jz4740_soc_info = {
.num_pwms = 8,
};
-static const struct soc_info __maybe_unused jz4725b_soc_info = {
+static const struct soc_info jz4725b_soc_info = {
.num_pwms = 6,
};
-#ifdef CONFIG_OF
+static const struct soc_info x1000_soc_info = {
+ .num_pwms = 5,
+};
+
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 },
+ { .compatible = "ingenic,x1000-pwm", .data = &x1000_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),
+ .of_match_table = jz4740_pwm_dt_ids,
},
.probe = jz4740_pwm_probe,
- .remove = jz4740_pwm_remove,
};
module_platform_driver(jz4740_pwm_driver);
diff --git a/drivers/pwm/pwm-keembay.c b/drivers/pwm/pwm-keembay.c
new file mode 100644
index 000000000000..35b641f3f6ed
--- /dev/null
+++ b/drivers/pwm/pwm-keembay.c
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Keem Bay PWM driver
+ *
+ * Copyright (C) 2020 Intel Corporation
+ * Authors: Lai Poey Seng <poey.seng.lai@intel.com>
+ * Vineetha G. Jaya Kumaran <vineetha.g.jaya.kumaran@intel.com>
+ *
+ * Limitations:
+ * - Upon disabling a channel, the currently running
+ * period will not be completed. However, upon
+ * reconfiguration of the duty cycle/period, the
+ * currently running period will be completed first.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+
+#define KMB_TOTAL_PWM_CHANNELS 6
+#define KMB_PWM_COUNT_MAX U16_MAX
+#define KMB_PWM_EN_BIT BIT(31)
+
+/* Mask */
+#define KMB_PWM_HIGH_MASK GENMASK(31, 16)
+#define KMB_PWM_LOW_MASK GENMASK(15, 0)
+#define KMB_PWM_LEADIN_MASK GENMASK(30, 0)
+
+/* PWM Register offset */
+#define KMB_PWM_LEADIN_OFFSET(ch) (0x00 + 4 * (ch))
+#define KMB_PWM_HIGHLOW_OFFSET(ch) (0x20 + 4 * (ch))
+
+struct keembay_pwm {
+ struct device *dev;
+ struct clk *clk;
+ void __iomem *base;
+};
+
+static inline struct keembay_pwm *to_keembay_pwm_dev(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static void keembay_clk_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int keembay_clk_enable(struct device *dev, struct clk *clk)
+{
+ int ret;
+
+ ret = clk_prepare_enable(clk);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, keembay_clk_unprepare, clk);
+}
+
+/*
+ * With gcc 10, CONFIG_CC_OPTIMIZE_FOR_SIZE and only "inline" instead of
+ * "__always_inline" this fails to compile because the compiler doesn't notice
+ * for all valid masks (e.g. KMB_PWM_LEADIN_MASK) that they are ok.
+ */
+static __always_inline void keembay_pwm_update_bits(struct keembay_pwm *priv, u32 mask,
+ u32 val, u32 offset)
+{
+ u32 buff = readl(priv->base + offset);
+
+ buff = u32_replace_bits(buff, val, mask);
+ writel(buff, priv->base + offset);
+}
+
+static void keembay_pwm_enable(struct keembay_pwm *priv, int ch)
+{
+ keembay_pwm_update_bits(priv, KMB_PWM_EN_BIT, 1,
+ KMB_PWM_LEADIN_OFFSET(ch));
+}
+
+static void keembay_pwm_disable(struct keembay_pwm *priv, int ch)
+{
+ keembay_pwm_update_bits(priv, KMB_PWM_EN_BIT, 0,
+ KMB_PWM_LEADIN_OFFSET(ch));
+}
+
+static int keembay_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct keembay_pwm *priv = to_keembay_pwm_dev(chip);
+ unsigned long long high, low;
+ unsigned long clk_rate;
+ u32 highlow;
+
+ clk_rate = clk_get_rate(priv->clk);
+
+ /* Read channel enabled status */
+ highlow = readl(priv->base + KMB_PWM_LEADIN_OFFSET(pwm->hwpwm));
+ if (highlow & KMB_PWM_EN_BIT)
+ state->enabled = true;
+ else
+ state->enabled = false;
+
+ /* Read period and duty cycle */
+ highlow = readl(priv->base + KMB_PWM_HIGHLOW_OFFSET(pwm->hwpwm));
+ low = FIELD_GET(KMB_PWM_LOW_MASK, highlow) * NSEC_PER_SEC;
+ high = FIELD_GET(KMB_PWM_HIGH_MASK, highlow) * NSEC_PER_SEC;
+ state->duty_cycle = DIV_ROUND_UP_ULL(high, clk_rate);
+ state->period = DIV_ROUND_UP_ULL(high + low, clk_rate);
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ return 0;
+}
+
+static int keembay_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct keembay_pwm *priv = to_keembay_pwm_dev(chip);
+ struct pwm_state current_state;
+ unsigned long long div;
+ unsigned long clk_rate;
+ u32 pwm_count = 0;
+ u16 high, low;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ /*
+ * Configure the pwm repeat count as infinite at (15:0) and leadin
+ * low time as 0 at (30:16), which is in terms of clock cycles.
+ */
+ keembay_pwm_update_bits(priv, KMB_PWM_LEADIN_MASK, 0,
+ KMB_PWM_LEADIN_OFFSET(pwm->hwpwm));
+
+ keembay_pwm_get_state(chip, pwm, &current_state);
+
+ if (!state->enabled) {
+ if (current_state.enabled)
+ keembay_pwm_disable(priv, pwm->hwpwm);
+ return 0;
+ }
+
+ /*
+ * The upper 16 bits and lower 16 bits of the KMB_PWM_HIGHLOW_OFFSET
+ * register contain the high time and low time of waveform accordingly.
+ * All the values are in terms of clock cycles.
+ */
+
+ clk_rate = clk_get_rate(priv->clk);
+ div = clk_rate * state->duty_cycle;
+ div = DIV_ROUND_DOWN_ULL(div, NSEC_PER_SEC);
+ if (div > KMB_PWM_COUNT_MAX)
+ return -ERANGE;
+
+ high = div;
+ div = clk_rate * state->period;
+ div = DIV_ROUND_DOWN_ULL(div, NSEC_PER_SEC);
+ div = div - high;
+ if (div > KMB_PWM_COUNT_MAX)
+ return -ERANGE;
+
+ low = div;
+
+ pwm_count = FIELD_PREP(KMB_PWM_HIGH_MASK, high) |
+ FIELD_PREP(KMB_PWM_LOW_MASK, low);
+
+ writel(pwm_count, priv->base + KMB_PWM_HIGHLOW_OFFSET(pwm->hwpwm));
+
+ if (state->enabled && !current_state.enabled)
+ keembay_pwm_enable(priv, pwm->hwpwm);
+
+ return 0;
+}
+
+static const struct pwm_ops keembay_pwm_ops = {
+ .apply = keembay_pwm_apply,
+ .get_state = keembay_pwm_get_state,
+};
+
+static int keembay_pwm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pwm_chip *chip;
+ struct keembay_pwm *priv;
+ int ret;
+
+ chip = devm_pwmchip_alloc(dev, KMB_TOTAL_PWM_CHANNELS, sizeof(*priv));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ priv = to_keembay_pwm_dev(chip);
+
+ priv->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->clk))
+ return dev_err_probe(dev, PTR_ERR(priv->clk), "Failed to get clock\n");
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ ret = keembay_clk_enable(dev, priv->clk);
+ if (ret)
+ return ret;
+
+ chip->ops = &keembay_pwm_ops;
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
+
+ return 0;
+}
+
+static const struct of_device_id keembay_pwm_of_match[] = {
+ { .compatible = "intel,keembay-pwm" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, keembay_pwm_of_match);
+
+static struct platform_driver keembay_pwm_driver = {
+ .probe = keembay_pwm_probe,
+ .driver = {
+ .name = "pwm-keembay",
+ .of_match_table = keembay_pwm_of_match,
+ },
+};
+module_platform_driver(keembay_pwm_driver);
+
+MODULE_ALIAS("platform:pwm-keembay");
+MODULE_DESCRIPTION("Intel Keem Bay PWM driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/pwm/pwm-loongson.c b/drivers/pwm/pwm-loongson.c
new file mode 100644
index 000000000000..31a57edecfd0
--- /dev/null
+++ b/drivers/pwm/pwm-loongson.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2017-2025 Loongson Technology Corporation Limited.
+ *
+ * Loongson PWM driver
+ *
+ * For Loongson's PWM IP block documentation please refer Chapter 11 of
+ * Reference Manual: https://loongson.github.io/LoongArch-Documentation/Loongson-7A1000-usermanual-EN.pdf
+ *
+ * Author: Juxin Gao <gaojuxin@loongson.cn>
+ * Further cleanup and restructuring by:
+ * Binbin Zhou <zhoubinbin@loongson.cn>
+ *
+ * Limitations:
+ * - If both DUTY and PERIOD are set to 0, the output is a constant low signal.
+ * - When disabled the output is driven to 0 independent of the configured
+ * polarity.
+ * - If the register is reconfigured while PWM is running, it does not complete
+ * the currently running period.
+ * - Disabling the PWM stops the output immediately (without waiting for current
+ * period to complete first).
+ */
+
+#include <linux/acpi.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/units.h>
+
+/* Loongson PWM registers */
+#define LOONGSON_PWM_REG_DUTY 0x4 /* Low Pulse Buffer Register */
+#define LOONGSON_PWM_REG_PERIOD 0x8 /* Pulse Period Buffer Register */
+#define LOONGSON_PWM_REG_CTRL 0xc /* Control Register */
+
+/* Control register bits */
+#define LOONGSON_PWM_CTRL_REG_EN BIT(0) /* Counter Enable Bit */
+#define LOONGSON_PWM_CTRL_REG_OE BIT(3) /* Pulse Output Enable Control Bit, Valid Low */
+#define LOONGSON_PWM_CTRL_REG_SINGLE BIT(4) /* Single Pulse Control Bit */
+#define LOONGSON_PWM_CTRL_REG_INTE BIT(5) /* Interrupt Enable Bit */
+#define LOONGSON_PWM_CTRL_REG_INT BIT(6) /* Interrupt Bit */
+#define LOONGSON_PWM_CTRL_REG_RST BIT(7) /* Counter Reset Bit */
+#define LOONGSON_PWM_CTRL_REG_CAPTE BIT(8) /* Measurement Pulse Enable Bit */
+#define LOONGSON_PWM_CTRL_REG_INVERT BIT(9) /* Output flip-flop Enable Bit */
+#define LOONGSON_PWM_CTRL_REG_DZONE BIT(10) /* Anti-dead Zone Enable Bit */
+
+/* default input clk frequency for the ACPI case */
+#define LOONGSON_PWM_FREQ_DEFAULT 50000000 /* Hz */
+
+struct pwm_loongson_ddata {
+ struct clk *clk;
+ void __iomem *base;
+ u64 clk_rate;
+};
+
+static inline __pure struct pwm_loongson_ddata *to_pwm_loongson_ddata(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static inline u32 pwm_loongson_readl(struct pwm_loongson_ddata *ddata, u32 offset)
+{
+ return readl(ddata->base + offset);
+}
+
+static inline void pwm_loongson_writel(struct pwm_loongson_ddata *ddata,
+ u32 val, u32 offset)
+{
+ writel(val, ddata->base + offset);
+}
+
+static int pwm_loongson_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
+ enum pwm_polarity polarity)
+{
+ u16 val;
+ struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
+
+ val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL);
+
+ if (polarity == PWM_POLARITY_INVERSED)
+ /* Duty cycle defines LOW period of PWM */
+ val |= LOONGSON_PWM_CTRL_REG_INVERT;
+ else
+ /* Duty cycle defines HIGH period of PWM */
+ val &= ~LOONGSON_PWM_CTRL_REG_INVERT;
+
+ pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL);
+
+ return 0;
+}
+
+static void pwm_loongson_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ u32 val;
+ struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
+
+ val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL);
+ val &= ~LOONGSON_PWM_CTRL_REG_EN;
+ pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL);
+}
+
+static int pwm_loongson_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ u32 val;
+ struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
+
+ val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL);
+ val |= LOONGSON_PWM_CTRL_REG_EN;
+ pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL);
+
+ return 0;
+}
+
+static int pwm_loongson_config(struct pwm_chip *chip, struct pwm_device *pwm,
+ u64 duty_ns, u64 period_ns)
+{
+ u64 duty, period;
+ struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
+
+ /* duty = duty_ns * ddata->clk_rate / NSEC_PER_SEC */
+ duty = mul_u64_u64_div_u64(duty_ns, ddata->clk_rate, NSEC_PER_SEC);
+ if (duty > U32_MAX)
+ duty = U32_MAX;
+
+ /* period = period_ns * ddata->clk_rate / NSEC_PER_SEC */
+ period = mul_u64_u64_div_u64(period_ns, ddata->clk_rate, NSEC_PER_SEC);
+ if (period > U32_MAX)
+ period = U32_MAX;
+
+ pwm_loongson_writel(ddata, duty, LOONGSON_PWM_REG_DUTY);
+ pwm_loongson_writel(ddata, period, LOONGSON_PWM_REG_PERIOD);
+
+ return 0;
+}
+
+static int pwm_loongson_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int ret;
+ bool enabled = pwm->state.enabled;
+
+ if (!state->enabled) {
+ if (enabled)
+ pwm_loongson_disable(chip, pwm);
+ return 0;
+ }
+
+ ret = pwm_loongson_set_polarity(chip, pwm, state->polarity);
+ if (ret)
+ return ret;
+
+ ret = pwm_loongson_config(chip, pwm, state->duty_cycle, state->period);
+ if (ret)
+ return ret;
+
+ if (!enabled && state->enabled)
+ ret = pwm_loongson_enable(chip, pwm);
+
+ return ret;
+}
+
+static int pwm_loongson_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ u32 duty, period, ctrl;
+ struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
+
+ duty = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_DUTY);
+ period = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_PERIOD);
+ ctrl = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL);
+
+ /* duty & period have a max of 2^32, so we can't overflow */
+ state->duty_cycle = DIV64_U64_ROUND_UP((u64)duty * NSEC_PER_SEC, ddata->clk_rate);
+ state->period = DIV64_U64_ROUND_UP((u64)period * NSEC_PER_SEC, ddata->clk_rate);
+ state->polarity = (ctrl & LOONGSON_PWM_CTRL_REG_INVERT) ? PWM_POLARITY_INVERSED :
+ PWM_POLARITY_NORMAL;
+ state->enabled = (ctrl & LOONGSON_PWM_CTRL_REG_EN) ? true : false;
+
+ return 0;
+}
+
+static const struct pwm_ops pwm_loongson_ops = {
+ .apply = pwm_loongson_apply,
+ .get_state = pwm_loongson_get_state,
+};
+
+static int pwm_loongson_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct pwm_chip *chip;
+ struct pwm_loongson_ddata *ddata;
+ struct device *dev = &pdev->dev;
+
+ chip = devm_pwmchip_alloc(dev, 1, sizeof(*ddata));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ ddata = to_pwm_loongson_ddata(chip);
+
+ ddata->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ddata->base))
+ return PTR_ERR(ddata->base);
+
+ ddata->clk = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(ddata->clk))
+ return dev_err_probe(dev, PTR_ERR(ddata->clk),
+ "Failed to get pwm clock\n");
+ if (ddata->clk) {
+ ret = devm_clk_rate_exclusive_get(dev, ddata->clk);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get exclusive rate\n");
+
+ ddata->clk_rate = clk_get_rate(ddata->clk);
+ if (!ddata->clk_rate)
+ return dev_err_probe(dev, -EINVAL,
+ "Failed to get frequency\n");
+ } else {
+ ddata->clk_rate = LOONGSON_PWM_FREQ_DEFAULT;
+ }
+
+ /* This check is done to prevent an overflow in .apply */
+ if (ddata->clk_rate > NSEC_PER_SEC)
+ return dev_err_probe(dev, -EINVAL, "PWM clock out of range\n");
+
+ chip->ops = &pwm_loongson_ops;
+ chip->atomic = true;
+ dev_set_drvdata(dev, chip);
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
+
+ return 0;
+}
+
+static int pwm_loongson_suspend(struct device *dev)
+{
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
+ struct pwm_device *pwm = &chip->pwms[0];
+
+ if (pwm->state.enabled)
+ return -EBUSY;
+
+ clk_disable_unprepare(ddata->clk);
+
+ return 0;
+}
+
+static int pwm_loongson_resume(struct device *dev)
+{
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
+
+ return clk_prepare_enable(ddata->clk);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(pwm_loongson_pm_ops, pwm_loongson_suspend,
+ pwm_loongson_resume);
+
+static const struct of_device_id pwm_loongson_of_ids[] = {
+ { .compatible = "loongson,ls7a-pwm" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, pwm_loongson_of_ids);
+
+static const struct acpi_device_id pwm_loongson_acpi_ids[] = {
+ { "LOON0006" },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, pwm_loongson_acpi_ids);
+
+static struct platform_driver pwm_loongson_driver = {
+ .probe = pwm_loongson_probe,
+ .driver = {
+ .name = "loongson-pwm",
+ .pm = pm_ptr(&pwm_loongson_pm_ops),
+ .of_match_table = pwm_loongson_of_ids,
+ .acpi_match_table = pwm_loongson_acpi_ids,
+ },
+};
+module_platform_driver(pwm_loongson_driver);
+
+MODULE_DESCRIPTION("Loongson PWM driver");
+MODULE_AUTHOR("Loongson Technology Corporation Limited.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-lp3943.c b/drivers/pwm/pwm-lp3943.c
index 7551253ada32..90b0733c00c1 100644
--- a/drivers/pwm/pwm-lp3943.c
+++ b/drivers/pwm/pwm-lp3943.c
@@ -8,9 +8,9 @@
*/
#include <linux/err.h>
-#include <linux/i2c.h>
#include <linux/mfd/lp3943.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
@@ -20,14 +20,14 @@
#define LP3943_MAX_PERIOD 1600000
struct lp3943_pwm {
- struct pwm_chip chip;
struct lp3943 *lp3943;
struct lp3943_platform_data *pdata;
+ struct lp3943_pwm_map pwm_map[LP3943_NUM_PWMS];
};
-static inline struct lp3943_pwm *to_lp3943_pwm(struct pwm_chip *_chip)
+static inline struct lp3943_pwm *to_lp3943_pwm(struct pwm_chip *chip)
{
- return container_of(_chip, struct lp3943_pwm, chip);
+ return pwmchip_get_drvdata(chip);
}
static struct lp3943_pwm_map *
@@ -35,13 +35,9 @@ 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;
+ struct lp3943_pwm_map *pwm_map = &lp3943_pwm->pwm_map[hwpwm];
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;
@@ -49,10 +45,8 @@ lp3943_pwm_request_map(struct lp3943_pwm *lp3943_pwm, int hwpwm)
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);
+ if (test_and_set_bit(offset, &lp3943->pin_used))
return ERR_PTR(-EBUSY);
- }
}
return pwm_map;
@@ -67,7 +61,7 @@ static int lp3943_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
if (IS_ERR(pwm_map))
return PTR_ERR(pwm_map);
- return pwm_set_chip_data(pwm, pwm_map);
+ return 0;
}
static void lp3943_pwm_free_map(struct lp3943_pwm *lp3943_pwm,
@@ -80,20 +74,18 @@ static void lp3943_pwm_free_map(struct lp3943_pwm *lp3943_pwm,
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);
+ struct lp3943_pwm_map *pwm_map = &lp3943_pwm->pwm_map[pwm->hwpwm];
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)
+ u64 duty_ns, u64 period_ns)
{
struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip);
struct lp3943 *lp3943 = lp3943_pwm->lp3943;
@@ -118,14 +110,20 @@ static int lp3943_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
reg_duty = LP3943_REG_PWM1;
}
- period_ns = clamp(period_ns, LP3943_MIN_PERIOD, LP3943_MAX_PERIOD);
- val = (u8)(period_ns / LP3943_MIN_PERIOD - 1);
+ /*
+ * Note that after this clamping, period_ns fits into an int. This is
+ * helpful because we can resort to integer division below instead of
+ * the (more expensive) 64 bit division.
+ */
+ period_ns = clamp(period_ns, (u64)LP3943_MIN_PERIOD, (u64)LP3943_MAX_PERIOD);
+ val = (u8)((int)period_ns / LP3943_MIN_PERIOD - 1);
err = lp3943_write_byte(lp3943, reg_prescale, val);
if (err)
return err;
- val = (u8)(duty_ns * LP3943_MAX_DUTY / period_ns);
+ duty_ns = min(duty_ns, period_ns);
+ val = (u8)((int)duty_ns * LP3943_MAX_DUTY / (int)period_ns);
return lp3943_write_byte(lp3943, reg_duty, val);
}
@@ -153,7 +151,7 @@ static int lp3943_pwm_set_mode(struct lp3943_pwm *lp3943_pwm,
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);
+ struct lp3943_pwm_map *pwm_map = &lp3943_pwm->pwm_map[pwm->hwpwm];
u8 val;
if (pwm->hwpwm == 0)
@@ -172,7 +170,7 @@ static int lp3943_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ struct lp3943_pwm_map *pwm_map = &lp3943_pwm->pwm_map[pwm->hwpwm];
/*
* LP3943 outputs are open-drain, so the pin should be configured
@@ -182,13 +180,34 @@ static void lp3943_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
lp3943_pwm_set_mode(lp3943_pwm, pwm_map, LP3943_GPIO_OUT_HIGH);
}
+static int lp3943_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ if (pwm->state.enabled)
+ lp3943_pwm_disable(chip, pwm);
+ return 0;
+ }
+
+ err = lp3943_pwm_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!pwm->state.enabled)
+ err = lp3943_pwm_enable(chip, pwm);
+
+ return err;
+}
+
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,
+ .apply = lp3943_pwm_apply,
};
static int lp3943_pwm_parse_dt(struct device *dev,
@@ -199,8 +218,7 @@ static int lp3943_pwm_parse_dt(struct device *dev,
struct lp3943_platform_data *pdata;
struct lp3943_pwm_map *pwm_map;
enum lp3943_pwm_output *output;
- int i, err, proplen, count = 0;
- u32 num_outputs;
+ int i, err, num_outputs, count = 0;
if (!node)
return -EINVAL;
@@ -215,11 +233,8 @@ static int lp3943_pwm_parse_dt(struct device *dev,
*/
for (i = 0; i < LP3943_NUM_PWMS; i++) {
- if (!of_get_property(node, name[i], &proplen))
- continue;
-
- num_outputs = proplen / sizeof(u32);
- if (num_outputs == 0)
+ num_outputs = of_property_count_u32_elems(node, name[i]);
+ if (num_outputs <= 0)
continue;
output = devm_kcalloc(dev, num_outputs, sizeof(*output),
@@ -253,12 +268,14 @@ static int lp3943_pwm_parse_dt(struct device *dev,
static int lp3943_pwm_probe(struct platform_device *pdev)
{
struct lp3943 *lp3943 = dev_get_drvdata(pdev->dev.parent);
+ struct pwm_chip *chip;
struct lp3943_pwm *lp3943_pwm;
int ret;
- lp3943_pwm = devm_kzalloc(&pdev->dev, sizeof(*lp3943_pwm), GFP_KERNEL);
- if (!lp3943_pwm)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, LP3943_NUM_PWMS, sizeof(*lp3943_pwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ lp3943_pwm = to_lp3943_pwm(chip);
lp3943_pwm->pdata = lp3943->pdata;
if (!lp3943_pwm->pdata) {
@@ -272,20 +289,9 @@ static int lp3943_pwm_probe(struct platform_device *pdev)
}
lp3943_pwm->lp3943 = lp3943;
- lp3943_pwm->chip.dev = &pdev->dev;
- lp3943_pwm->chip.ops = &lp3943_pwm_ops;
- lp3943_pwm->chip.npwm = LP3943_NUM_PWMS;
-
- 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);
+ chip->ops = &lp3943_pwm_ops;
- return pwmchip_remove(&lp3943_pwm->chip);
+ return devm_pwmchip_add(&pdev->dev, chip);
}
#ifdef CONFIG_OF
@@ -298,7 +304,6 @@ MODULE_DEVICE_TABLE(of, lp3943_pwm_of_match);
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),
diff --git a/drivers/pwm/pwm-lpc18xx-sct.c b/drivers/pwm/pwm-lpc18xx-sct.c
index 5ff11145c1a3..1e614b2a0227 100644
--- a/drivers/pwm/pwm-lpc18xx-sct.c
+++ b/drivers/pwm/pwm-lpc18xx-sct.c
@@ -22,6 +22,7 @@
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
@@ -76,6 +77,8 @@
#define LPC18XX_PWM_EVENT_PERIOD 0
#define LPC18XX_PWM_EVENT_MAX 16
+#define LPC18XX_NUM_PWMS 16
+
/* SCT conflict resolution */
enum lpc18xx_pwm_res_action {
LPC18XX_PWM_RES_NONE,
@@ -89,24 +92,21 @@ struct lpc18xx_pwm_data {
};
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;
+ u64 max_period_ns;
unsigned int period_event;
unsigned long event_map;
- struct mutex res_lock;
- struct mutex period_lock;
+ struct lpc18xx_pwm_data channeldata[LPC18XX_NUM_PWMS];
};
static inline struct lpc18xx_pwm_chip *
to_lpc18xx_pwm_chip(struct pwm_chip *chip)
{
- return container_of(chip, struct lpc18xx_pwm_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
static inline void lpc18xx_pwm_writel(struct lpc18xx_pwm_chip *lpc18xx_pwm,
@@ -127,8 +127,6 @@ static void lpc18xx_pwm_set_conflict_res(struct lpc18xx_pwm_chip *lpc18xx_pwm,
{
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
@@ -138,60 +136,64 @@ static void lpc18xx_pwm_set_conflict_res(struct lpc18xx_pwm_chip *lpc18xx_pwm,
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)
+static void lpc18xx_pwm_config_period(struct pwm_chip *chip, u64 period_ns)
{
struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip);
- u64 val;
+ u32 val;
- val = (u64)period_ns * lpc18xx_pwm->clk_rate;
- do_div(val, NSEC_PER_SEC);
+ /*
+ * With clk_rate < NSEC_PER_SEC this cannot overflow.
+ * With period_ns < max_period_ns this also fits into an u32.
+ * As period_ns >= min_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, lpc18xx_pwm->clk_rate);
+ * we have val >= 1.
+ */
+ val = mul_u64_u64_div_u64(period_ns, lpc18xx_pwm->clk_rate, NSEC_PER_SEC);
lpc18xx_pwm_writel(lpc18xx_pwm,
LPC18XX_PWM_MATCH(lpc18xx_pwm->period_event),
- (u32)val - 1);
+ val - 1);
lpc18xx_pwm_writel(lpc18xx_pwm,
LPC18XX_PWM_MATCHREL(lpc18xx_pwm->period_event),
- (u32)val - 1);
+ val - 1);
}
static void lpc18xx_pwm_config_duty(struct pwm_chip *chip,
- struct pwm_device *pwm, int duty_ns)
+ struct pwm_device *pwm, u64 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;
+ struct lpc18xx_pwm_data *lpc18xx_data = &lpc18xx_pwm->channeldata[pwm->hwpwm];
+ u32 val;
- val = (u64)duty_ns * lpc18xx_pwm->clk_rate;
- do_div(val, NSEC_PER_SEC);
+ /*
+ * With clk_rate <= NSEC_PER_SEC this cannot overflow.
+ * With duty_ns <= period_ns < max_period_ns this also fits into an u32.
+ */
+ val = mul_u64_u64_div_u64(duty_ns, lpc18xx_pwm->clk_rate, NSEC_PER_SEC);
lpc18xx_pwm_writel(lpc18xx_pwm,
LPC18XX_PWM_MATCH(lpc18xx_data->duty_event),
- (u32)val);
+ val);
lpc18xx_pwm_writel(lpc18xx_pwm,
LPC18XX_PWM_MATCHREL(lpc18xx_data->duty_event),
- (u32)val);
+ 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;
+ int requested_events;
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);
+ dev_err(pwmchip_parent(chip), "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);
@@ -202,38 +204,26 @@ static int lpc18xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
*/
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",
+ dev_err(pwmchip_parent(chip), "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)
+static int lpc18xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm, enum pwm_polarity polarity)
{
struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip);
- struct lpc18xx_pwm_data *lpc18xx_data = pwm_get_chip_data(pwm);
+ struct lpc18xx_pwm_data *lpc18xx_data = &lpc18xx_pwm->channeldata[pwm->hwpwm];
enum lpc18xx_pwm_res_action res_action;
unsigned int set_event, clear_event;
@@ -246,7 +236,7 @@ static int lpc18xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
LPC18XX_PWM_EVSTATEMSK(lpc18xx_data->duty_event),
LPC18XX_PWM_EVSTATEMSK_ALL);
- if (pwm_get_polarity(pwm) == PWM_POLARITY_NORMAL) {
+ if (polarity == PWM_POLARITY_NORMAL) {
set_event = lpc18xx_pwm->period_event;
clear_event = lpc18xx_data->duty_event;
res_action = LPC18XX_PWM_RES_SET;
@@ -268,7 +258,7 @@ static int lpc18xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ struct lpc18xx_pwm_data *lpc18xx_data = &lpc18xx_pwm->channeldata[pwm->hwpwm];
lpc18xx_pwm_writel(lpc18xx_pwm,
LPC18XX_PWM_EVCTRL(lpc18xx_data->duty_event), 0);
@@ -279,17 +269,17 @@ static void lpc18xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ struct lpc18xx_pwm_data *lpc18xx_data = &lpc18xx_pwm->channeldata[pwm->hwpwm];
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,
+ dev_err(pwmchip_parent(chip),
"maximum number of simultaneous channels reached\n");
return -EBUSY;
- };
+ }
set_bit(event, &lpc18xx_pwm->event_map);
lpc18xx_data->duty_event = event;
@@ -300,19 +290,42 @@ static int lpc18xx_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ struct lpc18xx_pwm_data *lpc18xx_data = &lpc18xx_pwm->channeldata[pwm->hwpwm];
clear_bit(lpc18xx_data->duty_event, &lpc18xx_pwm->event_map);
}
+static int lpc18xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+ bool enabled = pwm->state.enabled;
+
+ if (state->polarity != pwm->state.polarity && pwm->state.enabled) {
+ lpc18xx_pwm_disable(chip, pwm);
+ enabled = false;
+ }
+
+ if (!state->enabled) {
+ if (enabled)
+ lpc18xx_pwm_disable(chip, pwm);
+
+ return 0;
+ }
+
+ err = lpc18xx_pwm_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!enabled)
+ err = lpc18xx_pwm_enable(chip, pwm, state->polarity);
+
+ return err;
+}
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,
+ .apply = lpc18xx_pwm_apply,
.request = lpc18xx_pwm_request,
.free = lpc18xx_pwm_free,
- .owner = THIS_MODULE,
};
static const struct of_device_id lpc18xx_pwm_of_match[] = {
@@ -323,59 +336,43 @@ MODULE_DEVICE_TABLE(of, lpc18xx_pwm_of_match);
static int lpc18xx_pwm_probe(struct platform_device *pdev)
{
+ struct pwm_chip *chip;
struct lpc18xx_pwm_chip *lpc18xx_pwm;
- struct pwm_device *pwm;
- struct resource *res;
- int ret, i;
+ int ret;
u64 val;
- lpc18xx_pwm = devm_kzalloc(&pdev->dev, sizeof(*lpc18xx_pwm),
- GFP_KERNEL);
- if (!lpc18xx_pwm)
- return -ENOMEM;
-
- lpc18xx_pwm->dev = &pdev->dev;
+ chip = devm_pwmchip_alloc(&pdev->dev, LPC18XX_NUM_PWMS, sizeof(*lpc18xx_pwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ lpc18xx_pwm = to_lpc18xx_pwm_chip(chip);
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- lpc18xx_pwm->base = devm_ioremap_resource(&pdev->dev, res);
+ 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->pwm_clk = devm_clk_get_enabled(&pdev->dev, "pwm");
+ if (IS_ERR(lpc18xx_pwm->pwm_clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(lpc18xx_pwm->pwm_clk),
+ "failed to get pwm clock\n");
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;
- }
+ if (!lpc18xx_pwm->clk_rate)
+ return dev_err_probe(&pdev->dev,
+ -EINVAL, "pwm clock has no frequency\n");
- mutex_init(&lpc18xx_pwm->res_lock);
- mutex_init(&lpc18xx_pwm->period_lock);
+ /*
+ * If clkrate is too fast, the calculations in .apply() might overflow.
+ */
+ if (lpc18xx_pwm->clk_rate > NSEC_PER_SEC)
+ return dev_err_probe(&pdev->dev, -EINVAL, "pwm clock to fast\n");
- val = (u64)NSEC_PER_SEC * LPC18XX_PWM_TIMER_MAX;
- do_div(val, lpc18xx_pwm->clk_rate);
- lpc18xx_pwm->max_period_ns = val;
+ lpc18xx_pwm->max_period_ns =
+ mul_u64_u64_div_u64(NSEC_PER_SEC, LPC18XX_PWM_TIMER_MAX, lpc18xx_pwm->clk_rate);
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;
+ chip->ops = &lpc18xx_pwm_ops;
/* SCT counter must be in unify (32 bit) mode */
lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_CONFIG,
@@ -400,29 +397,6 @@ static int lpc18xx_pwm_probe(struct platform_device *pdev)
lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_LIMIT,
BIT(lpc18xx_pwm->period_event));
- ret = pwmchip_add(&lpc18xx_pwm->chip);
- if (ret < 0) {
- dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret);
- goto disable_pwmclk;
- }
-
- 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 remove_pwmchip;
- }
-
- pwm_set_chip_data(pwm, data);
- }
-
- platform_set_drvdata(pdev, lpc18xx_pwm);
-
val = lpc18xx_pwm_readl(lpc18xx_pwm, LPC18XX_PWM_CTRL);
val &= ~LPC18XX_PWM_BIDIR;
val &= ~LPC18XX_PWM_CTRL_HALT;
@@ -430,27 +404,26 @@ static int lpc18xx_pwm_probe(struct platform_device *pdev)
val |= LPC18XX_PWM_PRE(0);
lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_CTRL, val);
- return 0;
+ ret = pwmchip_add(chip);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "pwmchip_add failed\n");
-remove_pwmchip:
- pwmchip_remove(&lpc18xx_pwm->chip);
-disable_pwmclk:
- clk_disable_unprepare(lpc18xx_pwm->pwm_clk);
- return ret;
+ platform_set_drvdata(pdev, chip);
+
+ return 0;
}
-static int lpc18xx_pwm_remove(struct platform_device *pdev)
+static void lpc18xx_pwm_remove(struct platform_device *pdev)
{
- struct lpc18xx_pwm_chip *lpc18xx_pwm = platform_get_drvdata(pdev);
+ struct pwm_chip *chip = platform_get_drvdata(pdev);
+ struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip);
u32 val;
+ pwmchip_remove(chip);
+
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 = {
diff --git a/drivers/pwm/pwm-lpc32xx.c b/drivers/pwm/pwm-lpc32xx.c
index 710d9a207d2b..c748537e57d1 100644
--- a/drivers/pwm/pwm-lpc32xx.c
+++ b/drivers/pwm/pwm-lpc32xx.c
@@ -15,7 +15,6 @@
#include <linux/slab.h>
struct lpc32xx_pwm_chip {
- struct pwm_chip chip;
struct clk *clk;
void __iomem *base;
};
@@ -23,8 +22,10 @@ struct lpc32xx_pwm_chip {
#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 inline struct lpc32xx_pwm_chip *to_lpc32xx_pwm_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
static int lpc32xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
@@ -51,10 +52,10 @@ static int lpc32xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
if (duty_cycles > 255)
duty_cycles = 255;
- val = readl(lpc32xx->base + (pwm->hwpwm << 2));
+ val = readl(lpc32xx->base);
val &= ~0xFFFF;
val |= (period_cycles << 8) | duty_cycles;
- writel(val, lpc32xx->base + (pwm->hwpwm << 2));
+ writel(val, lpc32xx->base);
return 0;
}
@@ -69,9 +70,9 @@ static int lpc32xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
if (ret)
return ret;
- val = readl(lpc32xx->base + (pwm->hwpwm << 2));
+ val = readl(lpc32xx->base);
val |= PWM_ENABLE;
- writel(val, lpc32xx->base + (pwm->hwpwm << 2));
+ writel(val, lpc32xx->base);
return 0;
}
@@ -81,33 +82,55 @@ 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 + (pwm->hwpwm << 2));
+ val = readl(lpc32xx->base);
val &= ~PWM_ENABLE;
- writel(val, lpc32xx->base + (pwm->hwpwm << 2));
+ writel(val, lpc32xx->base);
clk_disable_unprepare(lpc32xx->clk);
}
+static int lpc32xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ if (pwm->state.enabled)
+ lpc32xx_pwm_disable(chip, pwm);
+
+ return 0;
+ }
+
+ err = lpc32xx_pwm_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!pwm->state.enabled)
+ err = lpc32xx_pwm_enable(chip, pwm);
+
+ return err;
+}
+
static const struct pwm_ops lpc32xx_pwm_ops = {
- .config = lpc32xx_pwm_config,
- .enable = lpc32xx_pwm_enable,
- .disable = lpc32xx_pwm_disable,
- .owner = THIS_MODULE,
+ .apply = lpc32xx_pwm_apply,
};
static int lpc32xx_pwm_probe(struct platform_device *pdev)
{
+ struct pwm_chip *chip;
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;
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*lpc32xx));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ lpc32xx = to_lpc32xx_pwm_chip(chip);
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- lpc32xx->base = devm_ioremap_resource(&pdev->dev, res);
+ lpc32xx->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(lpc32xx->base))
return PTR_ERR(lpc32xx->base);
@@ -115,38 +138,22 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev)
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;
+ chip->ops = &lpc32xx_pwm_ops;
+
+ /* 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);
+ ret = devm_pwmchip_add(&pdev->dev, chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip, error %d\n", ret);
return ret;
}
- /* When PWM is disable, configure the output to the default value */
- val = readl(lpc32xx->base + (lpc32xx->chip.pwms[0].hwpwm << 2));
- val &= ~PWM_PIN_LEVEL;
- writel(val, lpc32xx->base + (lpc32xx->chip.pwms[0].hwpwm << 2));
-
- 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 */ }
@@ -159,7 +166,6 @@ static struct platform_driver lpc32xx_pwm_driver = {
.of_match_table = lpc32xx_pwm_dt_ids,
},
.probe = lpc32xx_pwm_probe,
- .remove = lpc32xx_pwm_remove,
};
module_platform_driver(lpc32xx_pwm_driver);
diff --git a/drivers/pwm/pwm-lpss-pci.c b/drivers/pwm/pwm-lpss-pci.c
index cf749ea0de9f..ae25d9321d75 100644
--- a/drivers/pwm/pwm-lpss-pci.c
+++ b/drivers/pwm/pwm-lpss-pci.c
@@ -14,52 +14,26 @@
#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;
+ void __iomem *io_base;
+ struct pwm_chip *chip;
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);
+ io_base = pcim_iomap_region(pdev, 0, "pwm-lpss");
+ if (IS_ERR(io_base))
+ return PTR_ERR(io_base);
- pci_set_drvdata(pdev, lpwm);
+ info = (struct pwm_lpss_boardinfo *)id->driver_data;
+ chip = devm_pwm_lpss_probe(&pdev->dev, io_base, info);
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
pm_runtime_put(&pdev->dev);
pm_runtime_allow(&pdev->dev);
@@ -69,35 +43,10 @@ static int pwm_lpss_probe_pci(struct pci_dev *pdev,
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},
@@ -117,11 +66,9 @@ static struct pci_driver pwm_lpss_driver_pci = {
.id_table = pwm_lpss_pci_ids,
.probe = pwm_lpss_probe_pci,
.remove = pwm_lpss_remove_pci,
- .driver = {
- .pm = &pwm_lpss_pci_pm,
- },
};
module_pci_driver(pwm_lpss_driver_pci);
MODULE_DESCRIPTION("PWM PCI driver for Intel LPSS");
MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("PWM_LPSS");
diff --git a/drivers/pwm/pwm-lpss-platform.c b/drivers/pwm/pwm-lpss-platform.c
index 48f34d20aecd..653ec9d0c8bf 100644
--- a/drivers/pwm/pwm-lpss-platform.c
+++ b/drivers/pwm/pwm-lpss-platform.c
@@ -7,91 +7,57 @@
* Derived from the original pwm-lpss.c
*/
-#include <linux/acpi.h>
#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <linux/property.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;
+ struct pwm_chip *chip;
+ void __iomem *base;
- id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev);
- if (!id)
+ info = device_get_match_data(&pdev->dev);
+ if (!info)
return -ENODEV;
- info = (const struct pwm_lpss_boardinfo *)id->driver_data;
- r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
- 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);
+ chip = devm_pwm_lpss_probe(&pdev->dev, base, info);
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
/*
- * If other device's AML code touches the PWM regs on suspend/resume
- * force runtime-resume the PWM controller to allow this.
+ * On Cherry Trail devices the GFX0._PS0 AML checks if the controller
+ * is on and if it is not on it turns it on and restores what it
+ * believes is the correct state to the PWM controller.
+ * Because of this we must disallow direct-complete, which keeps the
+ * controller (runtime)suspended on resume, to avoid 2 issues:
+ * 1. The controller getting turned on without the linux-pm code
+ * knowing about this. On devices where the controller is unused
+ * this causes it to stay on during the next suspend causing high
+ * battery drain (because S0i3 is not reached)
+ * 2. The state restoring code unexpectedly messing with the controller
+ *
+ * Leaving the controller runtime-suspended (skipping runtime-resume +
+ * normal-suspend) during suspend is fine.
*/
- if (lpwm->info->other_devices_aml_touches_pwm_regs)
- return 0; /* Force runtime-resume */
+ if (info->other_devices_aml_touches_pwm_regs)
+ dev_pm_set_driver_flags(&pdev->dev, DPM_FLAG_NO_DIRECT_COMPLETE|
+ DPM_FLAG_SMART_SUSPEND);
- return 1; /* If runtime-suspended leave as is */
+ pm_runtime_set_active(&pdev->dev);
+ return devm_pm_runtime_enable(&pdev->dev);
}
-static const struct dev_pm_ops pwm_lpss_platform_pm_ops = {
- .prepare = pwm_lpss_prepare,
- SET_SYSTEM_SLEEP_PM_OPS(pwm_lpss_suspend, pwm_lpss_resume)
-};
-
static const struct acpi_device_id pwm_lpss_acpi_match[] = {
{ "80860F09", (unsigned long)&pwm_lpss_byt_info },
{ "80862288", (unsigned long)&pwm_lpss_bsw_info },
@@ -105,13 +71,12 @@ 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_IMPORT_NS("PWM_LPSS");
MODULE_ALIAS("platform:pwm-lpss");
diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c
index 9d965ffe66d1..c976ff1c8ed9 100644
--- a/drivers/pwm/pwm-lpss.c
+++ b/drivers/pwm/pwm-lpss.c
@@ -10,12 +10,16 @@
* Author: Alan Cox <alan@linux.intel.com>
*/
+#define DEFAULT_SYMBOL_NAMESPACE "PWM_LPSS"
+
+#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
+#include <linux/pwm.h>
#include <linux/time.h>
#include "pwm-lpss.h"
@@ -24,14 +28,48 @@
#define PWM_ENABLE BIT(31)
#define PWM_SW_UPDATE BIT(30)
#define PWM_BASE_UNIT_SHIFT 8
-#define PWM_ON_TIME_DIV_MASK 0x000000ff
+#define PWM_ON_TIME_DIV_MASK GENMASK(7, 0)
/* Size of each PWM register space if multiple */
#define PWM_SIZE 0x400
+/* BayTrail */
+const struct pwm_lpss_boardinfo pwm_lpss_byt_info = {
+ .clk_rate = 25000000,
+ .npwm = 1,
+ .base_unit_bits = 16,
+};
+EXPORT_SYMBOL_GPL(pwm_lpss_byt_info);
+
+/* Braswell */
+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,
+};
+EXPORT_SYMBOL_GPL(pwm_lpss_bsw_info);
+
+/* Broxton */
+const struct pwm_lpss_boardinfo pwm_lpss_bxt_info = {
+ .clk_rate = 19200000,
+ .npwm = 4,
+ .base_unit_bits = 22,
+ .bypass = true,
+};
+EXPORT_SYMBOL_GPL(pwm_lpss_bxt_info);
+
+/* Tangier */
+const struct pwm_lpss_boardinfo pwm_lpss_tng_info = {
+ .clk_rate = 19200000,
+ .npwm = 4,
+ .base_unit_bits = 22,
+};
+EXPORT_SYMBOL_GPL(pwm_lpss_tng_info);
+
static inline struct pwm_lpss_chip *to_lpwm(struct pwm_chip *chip)
{
- return container_of(chip, struct pwm_lpss_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
static inline u32 pwm_lpss_read(const struct pwm_device *pwm)
@@ -69,14 +107,19 @@ static int pwm_lpss_wait_for_update(struct pwm_device *pwm)
*/
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");
+ dev_err(pwmchip_parent(pwm->chip), "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;
+ if (pwm_lpss_read(pwm) & PWM_SW_UPDATE) {
+ dev_err(pwmchip_parent(pwm->chip), "PWM_SW_UPDATE is still set, skipping update\n");
+ return -EBUSY;
+ }
+
+ return 0;
}
static void pwm_lpss_prepare(struct pwm_lpss_chip *lpwm, struct pwm_device *pwm,
@@ -85,7 +128,7 @@ static void pwm_lpss_prepare(struct pwm_lpss_chip *lpwm, struct pwm_device *pwm,
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 orig_ctrl, ctrl;
+ u32 ctrl;
do_div(freq, period_ns);
@@ -93,26 +136,25 @@ static void pwm_lpss_prepare(struct pwm_lpss_chip *lpwm, struct pwm_device *pwm,
* The equation is:
* base_unit = round(base_unit_range * freq / c)
*/
- base_unit_range = BIT(lpwm->info->base_unit_bits) - 1;
+ 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;
- orig_ctrl = ctrl = pwm_lpss_read(pwm);
+ ctrl = pwm_lpss_read(pwm);
ctrl &= ~PWM_ON_TIME_DIV_MASK;
- ctrl &= ~(base_unit_range << PWM_BASE_UNIT_SHIFT);
- base_unit &= base_unit_range;
+ ctrl &= ~((base_unit_range - 1) << PWM_BASE_UNIT_SHIFT);
ctrl |= (u32) base_unit << PWM_BASE_UNIT_SHIFT;
ctrl |= on_time_div;
- if (orig_ctrl != ctrl) {
- pwm_lpss_write(pwm, ctrl);
- pwm_lpss_write(pwm, ctrl | PWM_SW_UPDATE);
- }
+ 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)
@@ -121,52 +163,58 @@ static inline void pwm_lpss_cond_enable(struct pwm_device *pwm, bool 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;
+ int ret = 0;
if (state->enabled) {
if (!pwm_is_enabled(pwm)) {
- pm_runtime_get_sync(chip->dev);
- ret = pwm_lpss_is_updating(pwm);
- if (ret) {
- pm_runtime_put(chip->dev);
- 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) {
- pm_runtime_put(chip->dev);
- return ret;
- }
- pwm_lpss_cond_enable(pwm, lpwm->info->bypass == true);
- } else {
- ret = pwm_lpss_is_updating(pwm);
+ pm_runtime_get_sync(pwmchip_parent(chip));
+ ret = pwm_lpss_prepare_enable(lpwm, pwm, state);
if (ret)
- return ret;
- pwm_lpss_prepare(lpwm, pwm, state->duty_cycle, state->period);
- return pwm_lpss_wait_for_update(pwm);
+ pm_runtime_put(pwmchip_parent(chip));
+ } 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);
+ pm_runtime_put(pwmchip_parent(chip));
}
- return 0;
+ return ret;
}
-static void pwm_lpss_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
- struct pwm_state *state)
+static int 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);
+ pm_runtime_get_sync(pwmchip_parent(chip));
base_unit_range = BIT(lpwm->info->base_unit_bits);
@@ -188,96 +236,57 @@ static void pwm_lpss_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
state->polarity = PWM_POLARITY_NORMAL;
state->enabled = !!(ctrl & PWM_ENABLE);
- pm_runtime_put(chip->dev);
+ pm_runtime_put(pwmchip_parent(chip));
+
+ return 0;
}
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,
+struct pwm_chip *devm_pwm_lpss_probe(struct device *dev, void __iomem *base,
const struct pwm_lpss_boardinfo *info)
{
struct pwm_lpss_chip *lpwm;
+ struct pwm_chip *chip;
unsigned long c;
int i, ret;
u32 ctrl;
- if (WARN_ON(info->npwm > MAX_PWMS))
+ if (WARN_ON(info->npwm > LPSS_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);
+ chip = devm_pwmchip_alloc(dev, info->npwm, sizeof(*lpwm));
+ if (IS_ERR(chip))
+ return chip;
+ lpwm = to_lpwm(chip);
+ lpwm->regs = base;
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;
+ chip->ops = &pwm_lpss_ops;
- ret = pwmchip_add(&lpwm->chip);
+ ret = devm_pwmchip_add(dev, 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]);
+ ctrl = pwm_lpss_read(&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);
-
-int pwm_lpss_suspend(struct device *dev)
-{
- struct pwm_lpss_chip *lpwm = dev_get_drvdata(dev);
- int i;
-
- for (i = 0; i < lpwm->info->npwm; i++)
- lpwm->saved_ctrl[i] = readl(lpwm->regs + i * PWM_SIZE + PWM);
-
- return 0;
-}
-EXPORT_SYMBOL_GPL(pwm_lpss_suspend);
-
-int pwm_lpss_resume(struct device *dev)
-{
- struct pwm_lpss_chip *lpwm = dev_get_drvdata(dev);
- int i;
-
- for (i = 0; i < lpwm->info->npwm; i++)
- writel(lpwm->saved_ctrl[i], lpwm->regs + i * PWM_SIZE + PWM);
-
- return 0;
+ return chip;
}
-EXPORT_SYMBOL_GPL(pwm_lpss_resume);
+EXPORT_SYMBOL_GPL(devm_pwm_lpss_probe);
MODULE_DESCRIPTION("PWM driver for Intel LPSS");
MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
diff --git a/drivers/pwm/pwm-lpss.h b/drivers/pwm/pwm-lpss.h
index 7909fa12fca2..60792181401e 100644
--- a/drivers/pwm/pwm-lpss.h
+++ b/drivers/pwm/pwm-lpss.h
@@ -10,34 +10,20 @@
#ifndef __PWM_LPSS_H
#define __PWM_LPSS_H
-#include <linux/device.h>
-#include <linux/pwm.h>
+#include <linux/types.h>
-#define MAX_PWMS 4
+#include <linux/platform_data/x86/pwm-lpss.h>
+
+#define LPSS_MAX_PWMS 4
struct pwm_lpss_chip {
- struct pwm_chip chip;
void __iomem *regs;
const struct pwm_lpss_boardinfo *info;
- u32 saved_ctrl[MAX_PWMS];
-};
-
-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);
-int pwm_lpss_suspend(struct device *dev);
-int pwm_lpss_resume(struct device *dev);
+extern const struct pwm_lpss_boardinfo pwm_lpss_byt_info;
+extern const struct pwm_lpss_boardinfo pwm_lpss_bsw_info;
+extern const struct pwm_lpss_boardinfo pwm_lpss_bxt_info;
+extern const struct pwm_lpss_boardinfo pwm_lpss_tng_info;
#endif /* __PWM_LPSS_H */
diff --git a/drivers/pwm/pwm-max7360.c b/drivers/pwm/pwm-max7360.c
new file mode 100644
index 000000000000..16261958ce7f
--- /dev/null
+++ b/drivers/pwm/pwm-max7360.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025 Bootlin
+ *
+ * Author: Kamel BOUHARA <kamel.bouhara@bootlin.com>
+ * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+ *
+ * PWM functionality of the MAX7360 multi-function device.
+ * https://www.analog.com/media/en/technical-documentation/data-sheets/MAX7360.pdf
+ *
+ * Limitations:
+ * - Only supports normal polarity.
+ * - The period is fixed to 2 ms.
+ * - Only the duty cycle can be changed, new values are applied at the beginning
+ * of the next cycle.
+ * - When disabled, the output is put in Hi-Z immediately.
+ */
+#include <linux/bits.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/math64.h>
+#include <linux/mfd/max7360.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/time.h>
+#include <linux/types.h>
+
+#define MAX7360_NUM_PWMS 8
+#define MAX7360_PWM_MAX 255
+#define MAX7360_PWM_STEPS 256
+#define MAX7360_PWM_PERIOD_NS (2 * NSEC_PER_MSEC)
+
+struct max7360_pwm_waveform {
+ u8 duty_steps;
+ bool enabled;
+};
+
+static int max7360_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct regmap *regmap = pwmchip_get_drvdata(chip);
+
+ /*
+ * Make sure we use the individual PWM configuration register and not
+ * the global one.
+ * We never need to use the global one, so there is no need to revert
+ * that in the .free() callback.
+ */
+ return regmap_write_bits(regmap, MAX7360_REG_PWMCFG(pwm->hwpwm),
+ MAX7360_PORT_CFG_COMMON_PWM, 0);
+}
+
+static int max7360_pwm_round_waveform_tohw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_waveform *wf,
+ void *_wfhw)
+{
+ struct max7360_pwm_waveform *wfhw = _wfhw;
+ u64 duty_steps;
+
+ /*
+ * Ignore user provided values for period_length_ns and duty_offset_ns:
+ * we only support fixed period of MAX7360_PWM_PERIOD_NS and offset of 0.
+ * Values from 0 to 254 as duty_steps will provide duty cycles of 0/256
+ * to 254/256, while value 255 will provide a duty cycle of 100%.
+ */
+ if (wf->duty_length_ns >= MAX7360_PWM_PERIOD_NS) {
+ duty_steps = MAX7360_PWM_MAX;
+ } else {
+ duty_steps = (u32)wf->duty_length_ns * MAX7360_PWM_STEPS / MAX7360_PWM_PERIOD_NS;
+ if (duty_steps == MAX7360_PWM_MAX)
+ duty_steps = MAX7360_PWM_MAX - 1;
+ }
+
+ wfhw->duty_steps = duty_steps;
+ wfhw->enabled = !!wf->period_length_ns;
+
+ if (wf->period_length_ns && wf->period_length_ns < MAX7360_PWM_PERIOD_NS)
+ return 1;
+ else
+ return 0;
+}
+
+static int max7360_pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
+ const void *_wfhw, struct pwm_waveform *wf)
+{
+ const struct max7360_pwm_waveform *wfhw = _wfhw;
+
+ wf->period_length_ns = wfhw->enabled ? MAX7360_PWM_PERIOD_NS : 0;
+ wf->duty_offset_ns = 0;
+
+ if (wfhw->enabled) {
+ if (wfhw->duty_steps == MAX7360_PWM_MAX)
+ wf->duty_length_ns = MAX7360_PWM_PERIOD_NS;
+ else
+ wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS,
+ MAX7360_PWM_STEPS);
+ } else {
+ wf->duty_length_ns = 0;
+ }
+
+ return 0;
+}
+
+static int max7360_pwm_write_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw)
+{
+ struct regmap *regmap = pwmchip_get_drvdata(chip);
+ const struct max7360_pwm_waveform *wfhw = _wfhw;
+ unsigned int val;
+ int ret;
+
+ if (wfhw->enabled) {
+ ret = regmap_write(regmap, MAX7360_REG_PWM(pwm->hwpwm), wfhw->duty_steps);
+ if (ret)
+ return ret;
+ }
+
+ val = wfhw->enabled ? BIT(pwm->hwpwm) : 0;
+ return regmap_write_bits(regmap, MAX7360_REG_GPIOCTRL, BIT(pwm->hwpwm), val);
+}
+
+static int max7360_pwm_read_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ void *_wfhw)
+{
+ struct regmap *regmap = pwmchip_get_drvdata(chip);
+ struct max7360_pwm_waveform *wfhw = _wfhw;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(regmap, MAX7360_REG_GPIOCTRL, &val);
+ if (ret)
+ return ret;
+
+ if (val & BIT(pwm->hwpwm)) {
+ wfhw->enabled = true;
+ ret = regmap_read(regmap, MAX7360_REG_PWM(pwm->hwpwm), &val);
+ if (ret)
+ return ret;
+
+ wfhw->duty_steps = val;
+ } else {
+ wfhw->enabled = false;
+ wfhw->duty_steps = 0;
+ }
+
+ return 0;
+}
+
+static const struct pwm_ops max7360_pwm_ops = {
+ .request = max7360_pwm_request,
+ .round_waveform_tohw = max7360_pwm_round_waveform_tohw,
+ .round_waveform_fromhw = max7360_pwm_round_waveform_fromhw,
+ .read_waveform = max7360_pwm_read_waveform,
+ .write_waveform = max7360_pwm_write_waveform,
+};
+
+static int max7360_pwm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pwm_chip *chip;
+ struct regmap *regmap;
+ int ret;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
+
+ /*
+ * This MFD sub-device does not have any associated device tree node:
+ * properties are stored in the device node of the parent (MFD) device
+ * and this same node is used in phandles of client devices.
+ * Reuse this device tree node here, as otherwise the PWM subsystem
+ * would be confused by this topology.
+ */
+ device_set_of_node_from_dev(dev, dev->parent);
+
+ chip = devm_pwmchip_alloc(dev, MAX7360_NUM_PWMS, 0);
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ chip->ops = &max7360_pwm_ops;
+
+ pwmchip_set_drvdata(chip, regmap);
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
+
+ return 0;
+}
+
+static struct platform_driver max7360_pwm_driver = {
+ .driver = {
+ .name = "max7360-pwm",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = max7360_pwm_probe,
+};
+module_platform_driver(max7360_pwm_driver);
+
+MODULE_DESCRIPTION("MAX7360 PWM driver");
+MODULE_AUTHOR("Kamel BOUHARA <kamel.bouhara@bootlin.com>");
+MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-mc33xs2410.c b/drivers/pwm/pwm-mc33xs2410.c
new file mode 100644
index 000000000000..6d99e3ff7239
--- /dev/null
+++ b/drivers/pwm/pwm-mc33xs2410.c
@@ -0,0 +1,407 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH
+ *
+ * Reference Manual : https://www.nxp.com/docs/en/data-sheet/MC33XS2410.pdf
+ *
+ * Limitations:
+ * - Supports frequencies between 0.5Hz and 2048Hz with following steps:
+ * - 0.5 Hz steps from 0.5 Hz to 32 Hz
+ * - 2 Hz steps from 2 Hz to 128 Hz
+ * - 8 Hz steps from 8 Hz to 512 Hz
+ * - 32 Hz steps from 32 Hz to 2048 Hz
+ * - Cannot generate a 0 % duty cycle.
+ * - Always produces low output if disabled.
+ * - Configuration isn't atomic. When changing polarity, duty cycle or period
+ * the data is taken immediately, counters not being affected, resulting in a
+ * behavior of the output pin that is neither the old nor the new state,
+ * rather something in between.
+ */
+#define DEFAULT_SYMBOL_NAMESPACE "PWM_MC33XS2410"
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/math64.h>
+#include <linux/mc33xs2410.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pwm.h>
+
+#include <linux/spi/spi.h>
+
+#define MC33XS2410_GLB_CTRL 0x00
+#define MC33XS2410_GLB_CTRL_MODE GENMASK(7, 6)
+#define MC33XS2410_GLB_CTRL_MODE_NORMAL FIELD_PREP(MC33XS2410_GLB_CTRL_MODE, 1)
+
+#define MC33XS2410_PWM_CTRL1 0x05
+/* chan in { 1 ... 4 } */
+#define MC33XS2410_PWM_CTRL1_POL_INV(chan) BIT((chan) + 1)
+
+#define MC33XS2410_PWM_CTRL3 0x07
+/* chan in { 1 ... 4 } */
+#define MC33XS2410_PWM_CTRL3_EN(chan) BIT(4 + (chan) - 1)
+
+/* chan in { 1 ... 4 } */
+#define MC33XS2410_PWM_FREQ(chan) (0x08 + (chan) - 1)
+#define MC33XS2410_PWM_FREQ_STEP GENMASK(7, 6)
+#define MC33XS2410_PWM_FREQ_COUNT GENMASK(5, 0)
+
+/* chan in { 1 ... 4 } */
+#define MC33XS2410_PWM_DC(chan) (0x0c + (chan) - 1)
+
+#define MC33XS2410_WDT 0x14
+
+#define MC33XS2410_PWM_MIN_PERIOD 488282
+/* step in { 0 ... 3 } */
+#define MC33XS2410_PWM_MAX_PERIOD(step) (2000000000 >> (2 * (step)))
+
+#define MC33XS2410_FRAME_IN_ADDR GENMASK(15, 8)
+#define MC33XS2410_FRAME_IN_DATA GENMASK(7, 0)
+#define MC33XS2410_FRAME_IN_ADDR_WR BIT(7)
+#define MC33XS2410_FRAME_IN_DATA_RD BIT(7)
+#define MC33XS2410_FRAME_OUT_DATA GENMASK(13, 0)
+
+#define MC33XS2410_MAX_TRANSFERS 5
+
+static int mc33xs2410_write_regs(struct spi_device *spi, u8 *reg, u8 *val,
+ unsigned int len)
+{
+ u16 tx[MC33XS2410_MAX_TRANSFERS];
+ int i;
+
+ if (len > MC33XS2410_MAX_TRANSFERS)
+ return -EINVAL;
+
+ for (i = 0; i < len; i++)
+ tx[i] = FIELD_PREP(MC33XS2410_FRAME_IN_DATA, val[i]) |
+ FIELD_PREP(MC33XS2410_FRAME_IN_ADDR,
+ MC33XS2410_FRAME_IN_ADDR_WR | reg[i]);
+
+ return spi_write(spi, tx, len * 2);
+}
+
+static int mc33xs2410_read_regs(struct spi_device *spi, u8 *reg, u8 flag,
+ u16 *val, unsigned int len)
+{
+ u16 tx[MC33XS2410_MAX_TRANSFERS];
+ u16 rx[MC33XS2410_MAX_TRANSFERS];
+ struct spi_transfer t = {
+ .tx_buf = tx,
+ .rx_buf = rx,
+ };
+ int i, ret;
+
+ len++;
+ if (len > MC33XS2410_MAX_TRANSFERS)
+ return -EINVAL;
+
+ t.len = len * 2;
+ for (i = 0; i < len - 1; i++)
+ tx[i] = FIELD_PREP(MC33XS2410_FRAME_IN_DATA, flag) |
+ FIELD_PREP(MC33XS2410_FRAME_IN_ADDR, reg[i]);
+
+ ret = spi_sync_transfer(spi, &t, 1);
+ if (ret < 0)
+ return ret;
+
+ for (i = 1; i < len; i++)
+ val[i - 1] = FIELD_GET(MC33XS2410_FRAME_OUT_DATA, rx[i]);
+
+ return 0;
+}
+
+static int mc33xs2410_write_reg(struct spi_device *spi, u8 reg, u8 val)
+{
+ return mc33xs2410_write_regs(spi, &reg, &val, 1);
+}
+
+static int mc33xs2410_read_reg(struct spi_device *spi, u8 reg, u16 *val, u8 flag)
+{
+ return mc33xs2410_read_regs(spi, &reg, flag, val, 1);
+}
+
+int mc33xs2410_read_reg_ctrl(struct spi_device *spi, u8 reg, u16 *val)
+{
+ return mc33xs2410_read_reg(spi, reg, val, MC33XS2410_FRAME_IN_DATA_RD);
+}
+EXPORT_SYMBOL_GPL(mc33xs2410_read_reg_ctrl);
+
+int mc33xs2410_read_reg_diag(struct spi_device *spi, u8 reg, u16 *val)
+{
+ return mc33xs2410_read_reg(spi, reg, val, 0);
+}
+EXPORT_SYMBOL_GPL(mc33xs2410_read_reg_diag);
+
+int mc33xs2410_modify_reg(struct spi_device *spi, u8 reg, u8 mask, u8 val)
+{
+ u16 tmp;
+ int ret;
+
+ ret = mc33xs2410_read_reg_ctrl(spi, reg, &tmp);
+ if (ret < 0)
+ return ret;
+
+ tmp &= ~mask;
+ tmp |= val & mask;
+
+ return mc33xs2410_write_reg(spi, reg, tmp);
+}
+EXPORT_SYMBOL_GPL(mc33xs2410_modify_reg);
+
+static u8 mc33xs2410_pwm_get_freq(u64 period)
+{
+ u8 step, count;
+
+ /*
+ * Check which step [0 .. 3] is appropriate for the given period. The
+ * period ranges for the different step values overlap. Prefer big step
+ * values as these allow more finegrained period and duty cycle
+ * selection.
+ */
+
+ switch (period) {
+ case MC33XS2410_PWM_MIN_PERIOD ... MC33XS2410_PWM_MAX_PERIOD(3):
+ step = 3;
+ break;
+ case MC33XS2410_PWM_MAX_PERIOD(3) + 1 ... MC33XS2410_PWM_MAX_PERIOD(2):
+ step = 2;
+ break;
+ case MC33XS2410_PWM_MAX_PERIOD(2) + 1 ... MC33XS2410_PWM_MAX_PERIOD(1):
+ step = 1;
+ break;
+ case MC33XS2410_PWM_MAX_PERIOD(1) + 1 ... MC33XS2410_PWM_MAX_PERIOD(0):
+ step = 0;
+ break;
+ }
+
+ /*
+ * Round up here because a higher count results in a higher frequency
+ * and so a smaller period.
+ */
+ count = DIV_ROUND_UP((u32)MC33XS2410_PWM_MAX_PERIOD(step), (u32)period);
+ return FIELD_PREP(MC33XS2410_PWM_FREQ_STEP, step) |
+ FIELD_PREP(MC33XS2410_PWM_FREQ_COUNT, count - 1);
+}
+
+static u64 mc33xs2410_pwm_get_period(u8 reg)
+{
+ u32 doubled_freq, code, doubled_steps;
+
+ /*
+ * steps:
+ * - 0 = 0.5Hz
+ * - 1 = 2Hz
+ * - 2 = 8Hz
+ * - 3 = 32Hz
+ * frequency = (code + 1) x steps.
+ *
+ * To avoid losing precision in case steps value is zero, scale the
+ * steps value for now by two and keep it in mind when calculating the
+ * period that the frequency had been doubled.
+ */
+ doubled_steps = 1 << (FIELD_GET(MC33XS2410_PWM_FREQ_STEP, reg) * 2);
+ code = FIELD_GET(MC33XS2410_PWM_FREQ_COUNT, reg);
+ doubled_freq = (code + 1) * doubled_steps;
+
+ /* Convert frequency to period, considering the doubled frequency. */
+ return DIV_ROUND_UP(2 * NSEC_PER_SEC, doubled_freq);
+}
+
+/*
+ * The hardware cannot generate a 0% relative duty cycle for normal and inversed
+ * polarity. For normal polarity, the channel must be disabled, the device then
+ * emits a constant low signal.
+ * For inverted polarity, the channel must be enabled, the polarity must be set
+ * to normal and the relative duty cylce must be set to 100%. The device then
+ * emits a constant high signal.
+ */
+static int mc33xs2410_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct spi_device *spi = pwmchip_get_drvdata(chip);
+ u8 reg[4] = {
+ MC33XS2410_PWM_FREQ(pwm->hwpwm + 1),
+ MC33XS2410_PWM_DC(pwm->hwpwm + 1),
+ MC33XS2410_PWM_CTRL1,
+ MC33XS2410_PWM_CTRL3
+ };
+ u64 period, duty_cycle;
+ int ret, rel_dc;
+ u16 rd_val[2];
+ u8 wr_val[4];
+ u8 mask;
+
+ period = min(state->period, MC33XS2410_PWM_MAX_PERIOD(0));
+ if (period < MC33XS2410_PWM_MIN_PERIOD)
+ return -EINVAL;
+
+ ret = mc33xs2410_read_regs(spi, &reg[2], MC33XS2410_FRAME_IN_DATA_RD, rd_val, 2);
+ if (ret < 0)
+ return ret;
+
+ /* Frequency */
+ wr_val[0] = mc33xs2410_pwm_get_freq(period);
+ /* Continue calculations with the possibly truncated period */
+ period = mc33xs2410_pwm_get_period(wr_val[0]);
+
+ /* Duty cycle */
+ duty_cycle = min(period, state->duty_cycle);
+ rel_dc = div64_u64(duty_cycle * 256, period) - 1;
+ if (rel_dc >= 0)
+ wr_val[1] = rel_dc;
+ else if (state->polarity == PWM_POLARITY_NORMAL)
+ wr_val[1] = 0;
+ else
+ wr_val[1] = 255;
+
+ /* Polarity */
+ mask = MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm + 1);
+ if (state->polarity == PWM_POLARITY_INVERSED && rel_dc >= 0)
+ wr_val[2] = rd_val[0] | mask;
+ else
+ wr_val[2] = rd_val[0] & ~mask;
+
+ /* Enable */
+ mask = MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm + 1);
+ if (state->enabled &&
+ !(state->polarity == PWM_POLARITY_NORMAL && rel_dc < 0))
+ wr_val[3] = rd_val[1] | mask;
+ else
+ wr_val[3] = rd_val[1] & ~mask;
+
+ return mc33xs2410_write_regs(spi, reg, wr_val, 4);
+}
+
+static int mc33xs2410_pwm_get_state(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct spi_device *spi = pwmchip_get_drvdata(chip);
+ u8 reg[4] = {
+ MC33XS2410_PWM_FREQ(pwm->hwpwm + 1),
+ MC33XS2410_PWM_DC(pwm->hwpwm + 1),
+ MC33XS2410_PWM_CTRL1,
+ MC33XS2410_PWM_CTRL3,
+ };
+ u16 val[4];
+ int ret;
+
+ ret = mc33xs2410_read_regs(spi, reg, MC33XS2410_FRAME_IN_DATA_RD, val,
+ ARRAY_SIZE(reg));
+ if (ret < 0)
+ return ret;
+
+ state->period = mc33xs2410_pwm_get_period(val[0]);
+ state->polarity = (val[2] & MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm + 1)) ?
+ PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
+ state->enabled = !!(val[3] & MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm + 1));
+ state->duty_cycle = DIV_ROUND_UP_ULL((val[1] + 1) * state->period, 256);
+
+ return 0;
+}
+
+static const struct pwm_ops mc33xs2410_pwm_ops = {
+ .apply = mc33xs2410_pwm_apply,
+ .get_state = mc33xs2410_pwm_get_state,
+};
+
+static int mc33xs2410_reset(struct device *dev)
+{
+ struct gpio_desc *reset_gpio;
+
+ reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR_OR_NULL(reset_gpio))
+ return PTR_ERR_OR_ZERO(reset_gpio);
+
+ /* Wake-up time */
+ fsleep(10000);
+
+ return 0;
+}
+
+static int mc33xs2410_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct auxiliary_device *adev;
+ struct pwm_chip *chip;
+ int ret;
+
+ chip = devm_pwmchip_alloc(dev, 4, 0);
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ spi->bits_per_word = 16;
+ spi->mode |= SPI_CS_WORD;
+ ret = spi_setup(spi);
+ if (ret < 0)
+ return ret;
+
+ pwmchip_set_drvdata(chip, spi);
+ chip->ops = &mc33xs2410_pwm_ops;
+
+ /*
+ * Deasserts the reset of the device. Shouldn't change the output signal
+ * if the device was setup prior to probing.
+ */
+ ret = mc33xs2410_reset(dev);
+ if (ret)
+ return ret;
+
+ /*
+ * Disable watchdog and keep in mind that the watchdog won't trigger a
+ * reset of the machine when running into an timeout, instead the
+ * control over the outputs is handed over to the INx input logic
+ * signals of the device. Disabling it here just deactivates this
+ * feature until a proper solution is found.
+ */
+ ret = mc33xs2410_write_reg(spi, MC33XS2410_WDT, 0x0);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to disable watchdog\n");
+
+ /* Transition to normal mode */
+ ret = mc33xs2410_modify_reg(spi, MC33XS2410_GLB_CTRL,
+ MC33XS2410_GLB_CTRL_MODE,
+ MC33XS2410_GLB_CTRL_MODE_NORMAL);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to transition to normal mode\n");
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to add pwm chip\n");
+
+ adev = devm_auxiliary_device_create(dev, "hwmon", NULL);
+ if (!adev)
+ return dev_err_probe(dev, -ENODEV, "Failed to register hwmon device\n");
+
+ return 0;
+}
+
+static const struct spi_device_id mc33xs2410_spi_id[] = {
+ { "mc33xs2410" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, mc33xs2410_spi_id);
+
+static const struct of_device_id mc33xs2410_of_match[] = {
+ { .compatible = "nxp,mc33xs2410" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mc33xs2410_of_match);
+
+static struct spi_driver mc33xs2410_driver = {
+ .driver = {
+ .name = "mc33xs2410-pwm",
+ .of_match_table = mc33xs2410_of_match,
+ },
+ .probe = mc33xs2410_probe,
+ .id_table = mc33xs2410_spi_id,
+};
+module_spi_driver(mc33xs2410_driver);
+
+MODULE_DESCRIPTION("NXP MC33XS2410 high-side switch driver");
+MODULE_AUTHOR("Dimitri Fedrau <dimitri.fedrau@liebherr.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-mediatek.c b/drivers/pwm/pwm-mediatek.c
index b94e0d09c300..9d206303404a 100644
--- a/drivers/pwm/pwm-mediatek.c
+++ b/drivers/pwm/pwm-mediatek.c
@@ -7,6 +7,7 @@
*
*/
+#include <linux/bitfield.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/ioport.h>
@@ -14,7 +15,6 @@
#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>
@@ -22,54 +22,56 @@
/* PWM registers and bits definitions */
#define PWMCON 0x00
+#define PWMCON_CLKDIV GENMASK(2, 0)
#define PWMHDUR 0x04
#define PWMLDUR 0x08
#define PWMGDUR 0x0c
#define PWMWAVENUM 0x28
#define PWMDWIDTH 0x2c
+#define PWMDWIDTH_PERIOD GENMASK(12, 0)
#define PWM45DWIDTH_FIXUP 0x30
#define PWMTHRES 0x30
+#define PWMTHRES_DUTY GENMASK(12, 0)
#define PWM45THRES_FIXUP 0x34
-
-#define PWM_CLK_DIV_MAX 7
+#define PWM_CK_26M_SEL_V3 0x74
+#define PWM_CK_26M_SEL 0x210
struct pwm_mediatek_of_data {
unsigned int num_pwms;
bool pwm45_fixup;
+ u16 pwm_ck_26m_sel_reg;
+ unsigned int chanreg_base;
+ unsigned int chanreg_width;
};
/**
* 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
+ * @clk_pwms: the clock and clkrate used by each PWM channel
*/
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
+ struct {
+ struct clk *clk;
+ unsigned long rate;
+ } clk_pwms[];
};
static inline struct pwm_mediatek_chip *
to_pwm_mediatek_chip(struct pwm_chip *chip)
{
- return container_of(chip, struct pwm_mediatek_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
-static int pwm_mediatek_clk_enable(struct pwm_chip *chip,
- struct pwm_device *pwm)
+static int pwm_mediatek_clk_enable(struct pwm_mediatek_chip *pc,
+ unsigned int hwpwm)
{
- struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
int ret;
ret = clk_prepare_enable(pc->clk_top);
@@ -80,12 +82,28 @@ static int pwm_mediatek_clk_enable(struct pwm_chip *chip,
if (ret < 0)
goto disable_clk_top;
- ret = clk_prepare_enable(pc->clk_pwms[pwm->hwpwm]);
+ ret = clk_prepare_enable(pc->clk_pwms[hwpwm].clk);
if (ret < 0)
goto disable_clk_main;
+ if (!pc->clk_pwms[hwpwm].rate) {
+ pc->clk_pwms[hwpwm].rate = clk_get_rate(pc->clk_pwms[hwpwm].clk);
+
+ /*
+ * With the clk running with not more than 1 GHz the
+ * calculations in .apply() won't overflow.
+ */
+ if (!pc->clk_pwms[hwpwm].rate ||
+ pc->clk_pwms[hwpwm].rate > 1000000000) {
+ ret = -EINVAL;
+ goto disable_clk_hwpwm;
+ }
+ }
+
return 0;
+disable_clk_hwpwm:
+ clk_disable_unprepare(pc->clk_pwms[hwpwm].clk);
disable_clk_main:
clk_disable_unprepare(pc->clk_main);
disable_clk_top:
@@ -94,225 +112,499 @@ disable_clk_top:
return ret;
}
-static void pwm_mediatek_clk_disable(struct pwm_chip *chip,
- struct pwm_device *pwm)
+static void pwm_mediatek_clk_disable(struct pwm_mediatek_chip *pc,
+ unsigned int hwpwm)
{
- struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
-
- clk_disable_unprepare(pc->clk_pwms[pwm->hwpwm]);
+ clk_disable_unprepare(pc->clk_pwms[hwpwm].clk);
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);
+ writel(value, chip->regs + chip->soc->chanreg_base +
+ num * chip->soc->chanreg_width + offset);
}
-static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm,
- int duty_ns, int period_ns)
+static inline u32 pwm_mediatek_readl(struct pwm_mediatek_chip *chip,
+ unsigned int num, unsigned int offset)
{
+ return readl(chip->regs + chip->soc->chanreg_base +
+ num * chip->soc->chanreg_width + offset);
+}
+
+struct pwm_mediatek_waveform {
+ u32 enable;
+ u32 con;
+ u32 width;
+ u32 thres;
+};
+
+static int pwm_mediatek_round_waveform_tohw(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_waveform *wf, void *_wfhw)
+{
+ struct pwm_mediatek_waveform *wfhw = _wfhw;
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;
+ u32 clkdiv, enable;
+ u64 cnt_period, cnt_duty;
+ unsigned long clk_rate;
+ int ret = 0;
- ret = pwm_mediatek_clk_enable(chip, pwm);
+ if (wf->period_length_ns == 0) {
+ *wfhw = (typeof(*wfhw)){
+ .enable = 0,
+ };
- if (ret < 0)
- return ret;
+ return 0;
+ }
+
+ if (!pc->clk_pwms[pwm->hwpwm].rate) {
+ struct clk *clk = pc->clk_pwms[pwm->hwpwm].clk;
+
+ ret = clk_prepare_enable(clk);
+ if (ret)
+ 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]));
+ pc->clk_pwms[pwm->hwpwm].rate = clk_get_rate(clk);
- 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);
+ clk_disable_unprepare(clk);
}
- if (clkdiv > PWM_CLK_DIV_MAX) {
- pwm_mediatek_clk_disable(chip, pwm);
- dev_err(chip->dev, "period %d not supported\n", period_ns);
+ clk_rate = pc->clk_pwms[pwm->hwpwm].rate;
+ if (clk_rate == 0 || clk_rate > 1000000000)
return -EINVAL;
+
+ cnt_period = mul_u64_u64_div_u64(wf->period_length_ns, clk_rate, NSEC_PER_SEC);
+ if (cnt_period == 0) {
+ cnt_period = 1;
+ ret = 1;
+ }
+
+ if (cnt_period > FIELD_MAX(PWMDWIDTH_PERIOD) + 1) {
+ if (cnt_period >= ((FIELD_MAX(PWMDWIDTH_PERIOD) + 1) << FIELD_MAX(PWMCON_CLKDIV))) {
+ clkdiv = FIELD_MAX(PWMCON_CLKDIV);
+ cnt_period = FIELD_MAX(PWMDWIDTH_PERIOD) + 1;
+ } else {
+ clkdiv = ilog2(cnt_period) - ilog2(FIELD_MAX(PWMDWIDTH_PERIOD));
+ cnt_period >>= clkdiv;
+ }
+ } else {
+ clkdiv = 0;
}
- if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) {
+ cnt_duty = mul_u64_u64_div_u64(wf->duty_length_ns, clk_rate, NSEC_PER_SEC) >> clkdiv;
+ if (cnt_duty > cnt_period)
+ cnt_duty = cnt_period;
+
+ if (cnt_duty) {
+ cnt_duty -= 1;
+ enable = BIT(pwm->hwpwm);
+ } else {
+ enable = 0;
+ }
+
+ cnt_period -= 1;
+
+ dev_dbg(&chip->dev, "pwm#%u: %lld/%lld @%lu -> ENABLE: %x, CON: %x, PERIOD: %llx, DUTY: %llx\n",
+ pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, clk_rate,
+ enable, clkdiv, cnt_period, cnt_duty);
+
+ *wfhw = (typeof(*wfhw)){
+ .enable = enable,
+ .con = clkdiv,
+ .width = cnt_period,
+ .thres = cnt_duty,
+ };
+
+ return ret;
+}
+
+static int pwm_mediatek_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
+ const void *_wfhw, struct pwm_waveform *wf)
+{
+ const struct pwm_mediatek_waveform *wfhw = _wfhw;
+ struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
+ u32 clkdiv, cnt_period, cnt_duty;
+ unsigned long clk_rate;
+
+ /*
+ * When _wfhw was populated, the clock was on, so .rate is
+ * already set appropriately.
+ */
+ clk_rate = pc->clk_pwms[pwm->hwpwm].rate;
+
+ if (wfhw->enable) {
+ clkdiv = FIELD_GET(PWMCON_CLKDIV, wfhw->con);
+ cnt_period = FIELD_GET(PWMDWIDTH_PERIOD, wfhw->width);
+ cnt_duty = FIELD_GET(PWMTHRES_DUTY, wfhw->thres);
+
/*
- * PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
- * from the other PWMs on MT7623.
+ * cnt_period is a 13 bit value, NSEC_PER_SEC is 30 bits wide
+ * and clkdiv is less than 8, so the multiplication doesn't
+ * overflow an u64.
*/
- reg_width = PWM45DWIDTH_FIXUP;
- reg_thres = PWM45THRES_FIXUP;
- }
+ *wf = (typeof(*wf)){
+ .period_length_ns =
+ DIV_ROUND_UP_ULL((u64)(cnt_period + 1) * NSEC_PER_SEC << clkdiv, clk_rate),
+ .duty_length_ns =
+ DIV_ROUND_UP_ULL((u64)(cnt_duty + 1) * NSEC_PER_SEC << clkdiv, clk_rate),
+ };
+ } else {
+ clkdiv = 0;
+ cnt_period = 0;
+ cnt_duty = 0;
- 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);
+ /*
+ * .enable = 0 is also used for too small duty_cycle values, so
+ * report the HW as being enabled to communicate the minimal
+ * period.
+ */
+ *wf = (typeof(*wf)){
+ .period_length_ns =
+ DIV_ROUND_UP_ULL(NSEC_PER_SEC, clk_rate),
+ .duty_length_ns = 0,
+ };
+ }
- pwm_mediatek_clk_disable(chip, pwm);
+ dev_dbg(&chip->dev, "pwm#%u: ENABLE: %x, CLKDIV: %x, PERIOD: %x, DUTY: %x @%lu -> %lld/%lld\n",
+ pwm->hwpwm, wfhw->enable, clkdiv, cnt_period, cnt_duty, clk_rate,
+ wf->duty_length_ns, wf->period_length_ns);
return 0;
}
-static int pwm_mediatek_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+static int pwm_mediatek_read_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm, void *_wfhw)
{
+ struct pwm_mediatek_waveform *wfhw = _wfhw;
struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
- u32 value;
+ u32 enable, clkdiv, cnt_period, cnt_duty;
+ u32 reg_width = PWMDWIDTH, reg_thres = PWMTHRES;
int ret;
- ret = pwm_mediatek_clk_enable(chip, pwm);
+ ret = pwm_mediatek_clk_enable(pc, pwm->hwpwm);
if (ret < 0)
return ret;
- value = readl(pc->regs);
- value |= BIT(pwm->hwpwm);
- writel(value, pc->regs);
+ enable = readl(pc->regs) & BIT(pwm->hwpwm);
- return 0;
+ if (enable) {
+ 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;
+ }
+
+ clkdiv = FIELD_GET(PWMCON_CLKDIV, pwm_mediatek_readl(pc, pwm->hwpwm, PWMCON));
+ cnt_period = FIELD_GET(PWMDWIDTH_PERIOD, pwm_mediatek_readl(pc, pwm->hwpwm, reg_width));
+ cnt_duty = FIELD_GET(PWMTHRES_DUTY, pwm_mediatek_readl(pc, pwm->hwpwm, reg_thres));
+
+ *wfhw = (typeof(*wfhw)){
+ .enable = enable,
+ .con = BIT(15) | clkdiv,
+ .width = cnt_period,
+ .thres = cnt_duty,
+ };
+ } else {
+ *wfhw = (typeof(*wfhw)){
+ .enable = 0,
+ };
+ }
+
+ pwm_mediatek_clk_disable(pc, pwm->hwpwm);
+
+ return ret;
}
-static void pwm_mediatek_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+static int pwm_mediatek_write_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm, const void *_wfhw)
{
+ const struct pwm_mediatek_waveform *wfhw = _wfhw;
struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
- u32 value;
+ u32 ctrl;
+ int ret;
+
+ ret = pwm_mediatek_clk_enable(pc, pwm->hwpwm);
+ if (ret < 0)
+ return ret;
+
+ ctrl = readl(pc->regs);
+
+ if (wfhw->enable) {
+ u32 reg_width = PWMDWIDTH, reg_thres = PWMTHRES;
+
+ 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;
+ }
+
+ if (!(ctrl & BIT(pwm->hwpwm))) {
+ /*
+ * The clks are already on, just increasing the usage
+ * counter doesn't fail.
+ */
+ ret = pwm_mediatek_clk_enable(pc, pwm->hwpwm);
+ if (unlikely(ret < 0))
+ goto out;
+
+ ctrl |= BIT(pwm->hwpwm);
+ writel(ctrl, pc->regs);
+ }
+
+ /* Make sure we use the bus clock and not the 26MHz clock */
+ if (pc->soc->pwm_ck_26m_sel_reg)
+ writel(0, pc->regs + pc->soc->pwm_ck_26m_sel_reg);
+
+ pwm_mediatek_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | wfhw->con);
+ pwm_mediatek_writel(pc, pwm->hwpwm, reg_width, wfhw->width);
+ pwm_mediatek_writel(pc, pwm->hwpwm, reg_thres, wfhw->thres);
+ } else {
+ if (ctrl & BIT(pwm->hwpwm)) {
+ ctrl &= ~BIT(pwm->hwpwm);
+ writel(ctrl, pc->regs);
- value = readl(pc->regs);
- value &= ~BIT(pwm->hwpwm);
- writel(value, pc->regs);
+ pwm_mediatek_clk_disable(pc, pwm->hwpwm);
+ }
+ }
- pwm_mediatek_clk_disable(chip, pwm);
+out:
+ pwm_mediatek_clk_disable(pc, pwm->hwpwm);
+
+ return ret;
}
static const struct pwm_ops pwm_mediatek_ops = {
- .config = pwm_mediatek_config,
- .enable = pwm_mediatek_enable,
- .disable = pwm_mediatek_disable,
- .owner = THIS_MODULE,
+ .sizeof_wfhw = sizeof(struct pwm_mediatek_waveform),
+ .round_waveform_tohw = pwm_mediatek_round_waveform_tohw,
+ .round_waveform_fromhw = pwm_mediatek_round_waveform_fromhw,
+ .read_waveform = pwm_mediatek_read_waveform,
+ .write_waveform = pwm_mediatek_write_waveform,
};
+static int pwm_mediatek_init_used_clks(struct pwm_mediatek_chip *pc)
+{
+ const struct pwm_mediatek_of_data *soc = pc->soc;
+ unsigned int hwpwm;
+ u32 enabled, handled = 0;
+ int ret;
+
+ ret = clk_prepare_enable(pc->clk_top);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(pc->clk_main);
+ if (ret)
+ goto err_enable_main;
+
+ enabled = readl(pc->regs) & GENMASK(soc->num_pwms - 1, 0);
+
+ while (enabled & ~handled) {
+ hwpwm = ilog2(enabled & ~handled);
+
+ ret = pwm_mediatek_clk_enable(pc, hwpwm);
+ if (ret) {
+ while (handled) {
+ hwpwm = ilog2(handled);
+
+ pwm_mediatek_clk_disable(pc, hwpwm);
+ handled &= ~BIT(hwpwm);
+ }
+
+ break;
+ }
+
+ handled |= BIT(hwpwm);
+ }
+
+ clk_disable_unprepare(pc->clk_main);
+err_enable_main:
+
+ clk_disable_unprepare(pc->clk_top);
+
+ return ret;
+}
+
static int pwm_mediatek_probe(struct platform_device *pdev)
{
+ struct pwm_chip *chip;
struct pwm_mediatek_chip *pc;
- struct resource *res;
+ const struct pwm_mediatek_of_data *soc;
unsigned int i;
int ret;
- pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
- if (!pc)
- return -ENOMEM;
+ soc = of_device_get_match_data(&pdev->dev);
+
+ chip = devm_pwmchip_alloc(&pdev->dev, soc->num_pwms,
+ struct_size(pc, clk_pwms, soc->num_pwms));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ pc = to_pwm_mediatek_chip(chip);
- pc->soc = of_device_get_match_data(&pdev->dev);
+ pc->soc = soc;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- pc->regs = devm_ioremap_resource(&pdev->dev, res);
+ pc->regs = devm_platform_ioremap_resource(pdev, 0);
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);
- }
+ if (IS_ERR(pc->clk_top))
+ return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk_top),
+ "Failed to get top clock\n");
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);
- }
+ if (IS_ERR(pc->clk_main))
+ return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk_main),
+ "Failed to get main clock\n");
- for (i = 0; i < pc->soc->num_pwms; i++) {
+ for (i = 0; i < 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]);
- }
+ pc->clk_pwms[i].clk = devm_clk_get(&pdev->dev, name);
+ if (IS_ERR(pc->clk_pwms[i].clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk_pwms[i].clk),
+ "Failed to get %s clock\n", name);
+
+ ret = devm_clk_rate_exclusive_get(&pdev->dev, pc->clk_pwms[i].clk);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to lock clock rate for %s\n", name);
}
- platform_set_drvdata(pdev, pc);
+ ret = pwm_mediatek_init_used_clks(pc);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Failed to initialize used clocks\n");
- pc->chip.dev = &pdev->dev;
- pc->chip.ops = &pwm_mediatek_ops;
- pc->chip.base = -1;
- pc->chip.npwm = pc->soc->num_pwms;
+ chip->ops = &pwm_mediatek_ops;
- ret = pwmchip_add(&pc->chip);
- if (ret < 0) {
- dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
- return ret;
- }
+ ret = devm_pwmchip_add(&pdev->dev, chip);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "pwmchip_add() failed\n");
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,
+ .chanreg_base = 0x10,
+ .chanreg_width = 0x40,
+};
+
+static const struct pwm_mediatek_of_data mt6795_pwm_data = {
+ .num_pwms = 7,
+ .pwm45_fixup = false,
+ .chanreg_base = 0x10,
+ .chanreg_width = 0x40,
};
static const struct pwm_mediatek_of_data mt7622_pwm_data = {
.num_pwms = 6,
.pwm45_fixup = false,
+ .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL,
+ .chanreg_base = 0x10,
+ .chanreg_width = 0x40,
};
static const struct pwm_mediatek_of_data mt7623_pwm_data = {
.num_pwms = 5,
.pwm45_fixup = true,
+ .chanreg_base = 0x10,
+ .chanreg_width = 0x40,
};
static const struct pwm_mediatek_of_data mt7628_pwm_data = {
.num_pwms = 4,
.pwm45_fixup = true,
+ .chanreg_base = 0x10,
+ .chanreg_width = 0x40,
};
static const struct pwm_mediatek_of_data mt7629_pwm_data = {
.num_pwms = 1,
.pwm45_fixup = false,
+ .chanreg_base = 0x10,
+ .chanreg_width = 0x40,
+};
+
+static const struct pwm_mediatek_of_data mt7981_pwm_data = {
+ .num_pwms = 3,
+ .pwm45_fixup = false,
+ .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL,
+ .chanreg_base = 0x80,
+ .chanreg_width = 0x40,
+};
+
+static const struct pwm_mediatek_of_data mt7986_pwm_data = {
+ .num_pwms = 2,
+ .pwm45_fixup = false,
+ .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL,
+ .chanreg_base = 0x10,
+ .chanreg_width = 0x40,
+};
+
+static const struct pwm_mediatek_of_data mt7988_pwm_data = {
+ .num_pwms = 8,
+ .pwm45_fixup = false,
+ .chanreg_base = 0x80,
+ .chanreg_width = 0x40,
+};
+
+static const struct pwm_mediatek_of_data mt8183_pwm_data = {
+ .num_pwms = 4,
+ .pwm45_fixup = false,
+ .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL,
+ .chanreg_base = 0x10,
+ .chanreg_width = 0x40,
+};
+
+static const struct pwm_mediatek_of_data mt8365_pwm_data = {
+ .num_pwms = 3,
+ .pwm45_fixup = false,
+ .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL,
+ .chanreg_base = 0x10,
+ .chanreg_width = 0x40,
};
static const struct pwm_mediatek_of_data mt8516_pwm_data = {
.num_pwms = 5,
.pwm45_fixup = false,
+ .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL,
+ .chanreg_base = 0x10,
+ .chanreg_width = 0x40,
+};
+
+static const struct pwm_mediatek_of_data mt6991_pwm_data = {
+ .num_pwms = 4,
+ .pwm45_fixup = false,
+ .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL_V3,
+ .chanreg_base = 0x100,
+ .chanreg_width = 0x100,
};
static const struct of_device_id pwm_mediatek_of_match[] = {
{ .compatible = "mediatek,mt2712-pwm", .data = &mt2712_pwm_data },
+ { .compatible = "mediatek,mt6795-pwm", .data = &mt6795_pwm_data },
+ { .compatible = "mediatek,mt6991-pwm", .data = &mt6991_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,mt7981-pwm", .data = &mt7981_pwm_data },
+ { .compatible = "mediatek,mt7986-pwm", .data = &mt7986_pwm_data },
+ { .compatible = "mediatek,mt7988-pwm", .data = &mt7988_pwm_data },
+ { .compatible = "mediatek,mt8183-pwm", .data = &mt8183_pwm_data },
+ { .compatible = "mediatek,mt8365-pwm", .data = &mt8365_pwm_data },
{ .compatible = "mediatek,mt8516-pwm", .data = &mt8516_pwm_data },
{ },
};
@@ -324,9 +616,9 @@ static struct platform_driver pwm_mediatek_driver = {
.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_DESCRIPTION("MediaTek general purpose Pulse Width Modulator driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c
index bd0d7336b898..8c6bf3d49753 100644
--- a/drivers/pwm/pwm-meson.c
+++ b/drivers/pwm/pwm-meson.c
@@ -6,7 +6,7 @@
* PWM output is achieved by calculating a clock that permits calculating
* two periods (low and high). The counter then has to be set to switch after
* N cycles for the first half period.
- * The hardware has no "polarity" setting. This driver reverses the period
+ * Partly the hardware has no "polarity" setting. This driver reverses the period
* cycles (the low length is inverted with the high length) for
* PWM_POLARITY_INVERSED. This means that .get_state cannot read the polarity
* from the hardware.
@@ -37,7 +37,6 @@
#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>
@@ -49,59 +48,74 @@
#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_EN_SHIFT 23
+#define MISC_A_CLK_EN_SHIFT 15
+#define MISC_CLK_DIV_WIDTH 7
#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_CONSTANT_EN BIT(29)
+#define MISC_A_CONSTANT_EN BIT(28)
+#define MISC_B_INVERT_EN BIT(27)
+#define MISC_A_INVERT_EN BIT(26)
#define MISC_B_EN BIT(1)
#define MISC_A_EN BIT(0)
#define MESON_NUM_PWMS 2
+#define MESON_NUM_MUX_PARENTS 4
static struct meson_pwm_channel_data {
u8 reg_offset;
u8 clk_sel_shift;
u8 clk_div_shift;
- u32 clk_en_mask;
+ u8 clk_en_shift;
u32 pwm_en_mask;
+ u32 const_en_mask;
+ u32 inv_en_mask;
} meson_pwm_per_channel_data[MESON_NUM_PWMS] = {
{
.reg_offset = REG_PWM_A,
.clk_sel_shift = MISC_A_CLK_SEL_SHIFT,
.clk_div_shift = MISC_A_CLK_DIV_SHIFT,
- .clk_en_mask = MISC_A_CLK_EN,
+ .clk_en_shift = MISC_A_CLK_EN_SHIFT,
.pwm_en_mask = MISC_A_EN,
+ .const_en_mask = MISC_A_CONSTANT_EN,
+ .inv_en_mask = MISC_A_INVERT_EN,
},
{
.reg_offset = REG_PWM_B,
.clk_sel_shift = MISC_B_CLK_SEL_SHIFT,
.clk_div_shift = MISC_B_CLK_DIV_SHIFT,
- .clk_en_mask = MISC_B_CLK_EN,
+ .clk_en_shift = MISC_B_CLK_EN_SHIFT,
.pwm_en_mask = MISC_B_EN,
+ .const_en_mask = MISC_B_CONSTANT_EN,
+ .inv_en_mask = MISC_B_INVERT_EN,
}
};
struct meson_pwm_channel {
+ unsigned long rate;
unsigned int hi;
unsigned int lo;
- u8 pre_div;
+ bool constant;
+ bool inverted;
- struct clk *clk_parent;
struct clk_mux mux;
+ struct clk_divider div;
+ struct clk_gate gate;
struct clk *clk;
};
struct meson_pwm_data {
- const char * const *parent_names;
- unsigned int num_parents;
+ const char *const parent_names[MESON_NUM_MUX_PARENTS];
+ int (*channels_init)(struct pwm_chip *chip);
+ bool has_constant;
+ bool has_polarity;
};
struct meson_pwm {
- struct pwm_chip chip;
const struct meson_pwm_data *data;
struct meson_pwm_channel channels[MESON_NUM_PWMS];
void __iomem *base;
@@ -114,32 +128,16 @@ struct meson_pwm {
static inline struct meson_pwm *to_meson_pwm(struct pwm_chip *chip)
{
- return container_of(chip, struct meson_pwm, chip);
+ return pwmchip_get_drvdata(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;
+ struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
+ struct device *dev = pwmchip_parent(chip);
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",
@@ -147,97 +145,98 @@ static int meson_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
return err;
}
- return pwm_set_chip_data(pwm, channel);
+ return 0;
}
static void meson_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
- struct meson_pwm_channel *channel = pwm_get_chip_data(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);
+ clk_disable_unprepare(channel->clk);
}
-static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm,
+static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
- struct meson_pwm_channel *channel = pwm_get_chip_data(pwm);
- unsigned int duty, period, pre_div, cnt, duty_cnt;
- unsigned long fin_freq;
+ struct meson_pwm *meson = to_meson_pwm(chip);
+ struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
+ unsigned int cnt, duty_cnt;
+ long fin_freq;
+ u64 duty, period, freq;
duty = state->duty_cycle;
period = state->period;
- if (state->polarity == PWM_POLARITY_INVERSED)
+ /*
+ * 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 && !meson->data->has_polarity)
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);
+ freq = div64_u64(NSEC_PER_SEC * 0xffffULL, period);
+ if (freq > ULONG_MAX)
+ freq = ULONG_MAX;
- pre_div = div64_u64(fin_freq * (u64)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;
+ fin_freq = clk_round_rate(channel->clk, freq);
+ if (fin_freq <= 0) {
+ dev_err(pwmchip_parent(chip),
+ "invalid source clock frequency %llu\n", freq);
+ return fin_freq ? fin_freq : -EINVAL;
}
- cnt = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * (pre_div + 1));
+ dev_dbg(pwmchip_parent(chip), "fin_freq: %ld Hz\n", fin_freq);
+
+ cnt = mul_u64_u64_div_u64(fin_freq, period, NSEC_PER_SEC);
if (cnt > 0xffff) {
- dev_err(meson->chip.dev, "unable to get period cnt\n");
+ dev_err(pwmchip_parent(chip), "unable to get period cnt\n");
return -EINVAL;
}
- dev_dbg(meson->chip.dev, "period=%u pre_div=%u cnt=%u\n", period,
- pre_div, cnt);
+ dev_dbg(pwmchip_parent(chip), "period=%llu cnt=%u\n", period, cnt);
if (duty == period) {
- channel->pre_div = pre_div;
channel->hi = cnt;
channel->lo = 0;
+ channel->constant = true;
} else if (duty == 0) {
- channel->pre_div = pre_div;
channel->hi = 0;
channel->lo = cnt;
+ channel->constant = true;
} else {
- /* Then check is we can have the duty with the same pre_div */
- duty_cnt = div64_u64(fin_freq * (u64)duty,
- NSEC_PER_SEC * (pre_div + 1));
- if (duty_cnt > 0xffff) {
- dev_err(meson->chip.dev, "unable to get duty cycle\n");
- return -EINVAL;
- }
+ duty_cnt = mul_u64_u64_div_u64(fin_freq, duty, NSEC_PER_SEC);
- dev_dbg(meson->chip.dev, "duty=%u pre_div=%u duty_cnt=%u\n",
- duty, pre_div, duty_cnt);
+ dev_dbg(pwmchip_parent(chip), "duty=%llu duty_cnt=%u\n", duty, duty_cnt);
- channel->pre_div = pre_div;
channel->hi = duty_cnt;
channel->lo = cnt - duty_cnt;
+ channel->constant = false;
}
+ channel->rate = fin_freq;
+
return 0;
}
-static void meson_pwm_enable(struct meson_pwm *meson, struct pwm_device *pwm)
+static void meson_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
- struct meson_pwm_channel *channel = pwm_get_chip_data(pwm);
+ struct meson_pwm *meson = to_meson_pwm(chip);
+ struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
struct meson_pwm_channel_data *channel_data;
unsigned long flags;
u32 value;
+ int err;
channel_data = &meson_pwm_per_channel_data[pwm->hwpwm];
- spin_lock_irqsave(&meson->lock, flags);
+ err = clk_set_rate(channel->clk, channel->rate);
+ if (err)
+ dev_err(pwmchip_parent(chip), "setting clock rate failed\n");
- 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);
+ spin_lock_irqsave(&meson->lock, flags);
value = FIELD_PREP(PWM_HIGH_MASK, channel->hi) |
FIELD_PREP(PWM_LOW_MASK, channel->lo);
@@ -245,20 +244,45 @@ static void meson_pwm_enable(struct meson_pwm *meson, struct pwm_device *pwm)
value = readl(meson->base + REG_MISC_AB);
value |= channel_data->pwm_en_mask;
+
+ if (meson->data->has_constant) {
+ value &= ~channel_data->const_en_mask;
+ if (channel->constant)
+ value |= channel_data->const_en_mask;
+ }
+
+ if (meson->data->has_polarity) {
+ value &= ~channel_data->inv_en_mask;
+ if (channel->inverted)
+ value |= channel_data->inv_en_mask;
+ }
+
writel(value, meson->base + REG_MISC_AB);
spin_unlock_irqrestore(&meson->lock, flags);
}
-static void meson_pwm_disable(struct meson_pwm *meson, struct pwm_device *pwm)
+static void meson_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
+ struct meson_pwm *meson = to_meson_pwm(chip);
+ struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
+ struct meson_pwm_channel_data *channel_data;
unsigned long flags;
u32 value;
+ channel_data = &meson_pwm_per_channel_data[pwm->hwpwm];
+
spin_lock_irqsave(&meson->lock, flags);
value = readl(meson->base + REG_MISC_AB);
- value &= ~meson_pwm_per_channel_data[pwm->hwpwm].pwm_en_mask;
+ value &= ~channel_data->pwm_en_mask;
+
+ if (meson->data->has_polarity) {
+ value &= ~channel_data->inv_en_mask;
+ if (channel->inverted)
+ value |= channel_data->inv_en_mask;
+ }
+
writel(value, meson->base + REG_MISC_AB);
spin_unlock_irqrestore(&meson->lock, flags);
@@ -267,105 +291,79 @@ static void meson_pwm_disable(struct meson_pwm *meson, struct pwm_device *pwm)
static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
- struct meson_pwm_channel *channel = pwm_get_chip_data(pwm);
struct meson_pwm *meson = to_meson_pwm(chip);
+ struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
int err = 0;
- if (!state)
- return -EINVAL;
+ channel->inverted = (state->polarity == PWM_POLARITY_INVERSED);
if (!state->enabled) {
- if (state->polarity == PWM_POLARITY_INVERSED) {
+ if (channel->inverted && !meson->data->has_polarity) {
/*
- * This IP block revision doesn't have an "always high"
+ * Some of IP block revisions don't have an "always high"
* setting which we can use for "inverted disabled".
- * Instead we achieve this 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
+ * Instead we achieve this by setting mux parent with
+ * highest rate and minimum divider value, resulting
+ * in 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->rate = ULONG_MAX;
channel->hi = ~0;
channel->lo = 0;
+ channel->constant = true;
- meson_pwm_enable(meson, pwm);
+ meson_pwm_enable(chip, pwm);
} else {
- meson_pwm_disable(meson, pwm);
+ meson_pwm_disable(chip, pwm);
}
} else {
- err = meson_pwm_calc(meson, pwm, state);
+ err = meson_pwm_calc(chip, pwm, state);
if (err < 0)
return err;
- meson_pwm_enable(meson, pwm);
+ meson_pwm_enable(chip, pwm);
}
return 0;
}
-static unsigned int meson_pwm_cnt_to_ns(struct pwm_chip *chip,
- struct pwm_device *pwm, u32 cnt)
+static u64 meson_pwm_cnt_to_ns(unsigned long fin_freq, u32 cnt)
{
- struct meson_pwm *meson = to_meson_pwm(chip);
- struct meson_pwm_channel *channel;
- unsigned long fin_freq;
- 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);
+ return fin_freq ? div64_ul(NSEC_PER_SEC * (u64)cnt, fin_freq) : 0;
}
-static void meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
- struct pwm_state *state)
+static int 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;
+ unsigned long fin_freq;
+ unsigned int hi, lo;
+ u32 value;
- channel = &meson->channels[pwm->hwpwm];
channel_data = &meson_pwm_per_channel_data[pwm->hwpwm];
+ fin_freq = clk_get_rate(meson->channels[pwm->hwpwm].clk);
value = readl(meson->base + REG_MISC_AB);
+ state->enabled = value & channel_data->pwm_en_mask;
- 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);
+ if (meson->data->has_polarity && (value & channel_data->inv_en_mask))
+ state->polarity = PWM_POLARITY_INVERSED;
+ else
+ state->polarity = PWM_POLARITY_NORMAL;
value = readl(meson->base + channel_data->reg_offset);
+ lo = FIELD_GET(PWM_LOW_MASK, value);
+ hi = FIELD_GET(PWM_HIGH_MASK, value);
- 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->period = meson_pwm_cnt_to_ns(fin_freq, lo + hi);
+ state->duty_cycle = meson_pwm_cnt_to_ns(fin_freq, hi);
+
+ return 0;
}
static const struct pwm_ops meson_pwm_ops = {
@@ -373,93 +371,248 @@ static const struct pwm_ops meson_pwm_ops = {
.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 int meson_pwm_init_clocks_meson8b(struct pwm_chip *chip,
+ struct clk_parent_data *mux_parent_data)
+{
+ struct meson_pwm *meson = to_meson_pwm(chip);
+ struct device *dev = pwmchip_parent(chip);
+ unsigned int i;
+ char name[255];
+ int err;
-static const struct meson_pwm_data pwm_meson8b_data = {
- .parent_names = pwm_meson8b_parent_names,
- .num_parents = ARRAY_SIZE(pwm_meson8b_parent_names),
-};
+ for (i = 0; i < MESON_NUM_PWMS; i++) {
+ struct meson_pwm_channel *channel = &meson->channels[i];
+ struct clk_parent_data div_parent = {}, gate_parent = {};
+ struct clk_init_data init = {};
-static const char * const pwm_gxbb_parent_names[] = {
- "xtal", "hdmi_pll", "fclk_div4", "fclk_div3"
-};
+ snprintf(name, sizeof(name), "%s#mux%u", dev_name(dev), i);
+
+ init.name = name;
+ init.ops = &clk_mux_ops;
+ init.flags = 0;
+ init.parent_data = mux_parent_data;
+ init.num_parents = MESON_NUM_MUX_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;
+
+ err = devm_clk_hw_register(dev, &channel->mux.hw);
+ if (err)
+ return dev_err_probe(dev, err,
+ "failed to register %s\n", name);
+
+ snprintf(name, sizeof(name), "%s#div%u", dev_name(dev), i);
+
+ init.name = name;
+ init.ops = &clk_divider_ops;
+ init.flags = CLK_SET_RATE_PARENT;
+ div_parent.index = -1;
+ div_parent.hw = &channel->mux.hw;
+ init.parent_data = &div_parent;
+ init.num_parents = 1;
+
+ channel->div.reg = meson->base + REG_MISC_AB;
+ channel->div.shift = meson_pwm_per_channel_data[i].clk_div_shift;
+ channel->div.width = MISC_CLK_DIV_WIDTH;
+ channel->div.hw.init = &init;
+ channel->div.flags = 0;
+ channel->div.lock = &meson->lock;
+
+ err = devm_clk_hw_register(dev, &channel->div.hw);
+ if (err)
+ return dev_err_probe(dev, err,
+ "failed to register %s\n", name);
+
+ snprintf(name, sizeof(name), "%s#gate%u", dev_name(dev), i);
+
+ init.name = name;
+ init.ops = &clk_gate_ops;
+ init.flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED;
+ gate_parent.index = -1;
+ gate_parent.hw = &channel->div.hw;
+ init.parent_data = &gate_parent;
+ init.num_parents = 1;
+
+ channel->gate.reg = meson->base + REG_MISC_AB;
+ channel->gate.bit_idx = meson_pwm_per_channel_data[i].clk_en_shift;
+ channel->gate.hw.init = &init;
+ channel->gate.flags = 0;
+ channel->gate.lock = &meson->lock;
+
+ err = devm_clk_hw_register(dev, &channel->gate.hw);
+ if (err)
+ return dev_err_probe(dev, err, "failed to register %s\n", name);
+
+ channel->clk = devm_clk_hw_get_clk(dev, &channel->gate.hw, NULL);
+ if (IS_ERR(channel->clk))
+ return dev_err_probe(dev, PTR_ERR(channel->clk),
+ "failed to register %s\n", name);
+ }
+
+ return 0;
+}
-static const struct meson_pwm_data pwm_gxbb_data = {
- .parent_names = pwm_gxbb_parent_names,
- .num_parents = ARRAY_SIZE(pwm_gxbb_parent_names),
+static int meson_pwm_init_channels_meson8b_legacy(struct pwm_chip *chip)
+{
+ struct clk_parent_data mux_parent_data[MESON_NUM_MUX_PARENTS] = {};
+ struct meson_pwm *meson = to_meson_pwm(chip);
+ int i;
+
+ dev_warn_once(pwmchip_parent(chip),
+ "using obsolete compatible, please consider updating dt\n");
+
+ for (i = 0; i < MESON_NUM_MUX_PARENTS; i++) {
+ mux_parent_data[i].index = -1;
+ mux_parent_data[i].name = meson->data->parent_names[i];
+ }
+
+ return meson_pwm_init_clocks_meson8b(chip, mux_parent_data);
+}
+
+static int meson_pwm_init_channels_meson8b_v2(struct pwm_chip *chip)
+{
+ struct clk_parent_data mux_parent_data[MESON_NUM_MUX_PARENTS] = {};
+ int i;
+
+ /*
+ * NOTE: Instead of relying on the hard coded names in the driver
+ * as the legacy version, this relies on DT to provide the list of
+ * clocks.
+ * For once, using input numbers actually makes more sense than names.
+ * Also DT requires clock-names to be explicitly ordered, so there is
+ * no point bothering with clock names in this case.
+ */
+ for (i = 0; i < MESON_NUM_MUX_PARENTS; i++)
+ mux_parent_data[i].index = i;
+
+ return meson_pwm_init_clocks_meson8b(chip, mux_parent_data);
+}
+
+static void meson_pwm_s4_put_clk(void *data)
+{
+ struct clk *clk = data;
+
+ clk_put(clk);
+}
+
+static int meson_pwm_init_channels_s4(struct pwm_chip *chip)
+{
+ struct device *dev = pwmchip_parent(chip);
+ struct device_node *np = dev->of_node;
+ struct meson_pwm *meson = to_meson_pwm(chip);
+ int i, ret;
+
+ for (i = 0; i < MESON_NUM_PWMS; i++) {
+ meson->channels[i].clk = of_clk_get(np, i);
+ if (IS_ERR(meson->channels[i].clk))
+ return dev_err_probe(dev,
+ PTR_ERR(meson->channels[i].clk),
+ "Failed to get clk\n");
+
+ ret = devm_add_action_or_reset(dev, meson_pwm_s4_put_clk,
+ meson->channels[i].clk);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to add clk_put action\n");
+ }
+
+ return 0;
+}
+
+static const struct meson_pwm_data pwm_meson8b_data = {
+ .parent_names = { "xtal", NULL, "fclk_div4", "fclk_div3" },
+ .channels_init = meson_pwm_init_channels_meson8b_legacy,
};
/*
* Only the 2 first inputs of the GXBB AO PWMs are valid
* The last 2 are grounded
*/
-static const 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"
+ .parent_names = { "xtal", "clk81", NULL, NULL },
+ .channels_init = meson_pwm_init_channels_meson8b_legacy,
};
static const struct meson_pwm_data pwm_axg_ee_data = {
- .parent_names = pwm_axg_ee_parent_names,
- .num_parents = ARRAY_SIZE(pwm_axg_ee_parent_names),
-};
-
-static const char * const pwm_axg_ao_parent_names[] = {
- "aoclk81", "xtal", "fclk_div4", "fclk_div5"
+ .parent_names = { "xtal", "fclk_div5", "fclk_div4", "fclk_div3" },
+ .channels_init = meson_pwm_init_channels_meson8b_legacy,
+ .has_constant = true,
+ .has_polarity = true,
};
static const struct meson_pwm_data pwm_axg_ao_data = {
- .parent_names = pwm_axg_ao_parent_names,
- .num_parents = ARRAY_SIZE(pwm_axg_ao_parent_names),
+ .parent_names = { "xtal", "axg_ao_clk81", "fclk_div4", "fclk_div5" },
+ .channels_init = meson_pwm_init_channels_meson8b_legacy,
+ .has_constant = true,
+ .has_polarity = true,
};
-static const char * const pwm_g12a_ao_ab_parent_names[] = {
- "xtal", "aoclk81", "fclk_div4", "fclk_div5"
+static const struct meson_pwm_data pwm_g12a_ee_data = {
+ .parent_names = { "xtal", NULL, "fclk_div4", "fclk_div3" },
+ .channels_init = meson_pwm_init_channels_meson8b_legacy,
+ .has_constant = true,
+ .has_polarity = true,
};
static const struct meson_pwm_data pwm_g12a_ao_ab_data = {
- .parent_names = pwm_g12a_ao_ab_parent_names,
- .num_parents = ARRAY_SIZE(pwm_g12a_ao_ab_parent_names),
+ .parent_names = { "xtal", "g12a_ao_clk81", "fclk_div4", "fclk_div5" },
+ .channels_init = meson_pwm_init_channels_meson8b_legacy,
+ .has_constant = true,
+ .has_polarity = true,
};
-static const char * const pwm_g12a_ao_cd_parent_names[] = {
- "xtal", "aoclk81",
+static const struct meson_pwm_data pwm_g12a_ao_cd_data = {
+ .parent_names = { "xtal", "g12a_ao_clk81", NULL, NULL },
+ .channels_init = meson_pwm_init_channels_meson8b_legacy,
+ .has_constant = true,
+ .has_polarity = true,
};
-static const struct meson_pwm_data pwm_g12a_ao_cd_data = {
- .parent_names = pwm_g12a_ao_cd_parent_names,
- .num_parents = ARRAY_SIZE(pwm_g12a_ao_cd_parent_names),
+static const struct meson_pwm_data pwm_meson8_v2_data = {
+ .channels_init = meson_pwm_init_channels_meson8b_v2,
};
-static const char * const pwm_g12a_ee_parent_names[] = {
- "xtal", "hdmi_pll", "fclk_div4", "fclk_div3"
+static const struct meson_pwm_data pwm_meson_axg_v2_data = {
+ .channels_init = meson_pwm_init_channels_meson8b_v2,
+ .has_constant = true,
+ .has_polarity = true,
};
-static const struct meson_pwm_data pwm_g12a_ee_data = {
- .parent_names = pwm_g12a_ee_parent_names,
- .num_parents = ARRAY_SIZE(pwm_g12a_ee_parent_names),
+static const struct meson_pwm_data pwm_s4_data = {
+ .channels_init = meson_pwm_init_channels_s4,
+ .has_constant = true,
+ .has_polarity = true,
};
static const struct of_device_id meson_pwm_matches[] = {
{
+ .compatible = "amlogic,meson8-pwm-v2",
+ .data = &pwm_meson8_v2_data
+ },
+ {
+ .compatible = "amlogic,meson-axg-pwm-v2",
+ .data = &pwm_meson_axg_v2_data
+ },
+ {
+ .compatible = "amlogic,meson-g12-pwm-v2",
+ .data = &pwm_meson_axg_v2_data
+ },
+ /* The following compatibles are obsolete */
+ {
.compatible = "amlogic,meson8b-pwm",
.data = &pwm_meson8b_data
},
{
.compatible = "amlogic,meson-gxbb-pwm",
- .data = &pwm_gxbb_data
+ .data = &pwm_meson8b_data
},
{
.compatible = "amlogic,meson-gxbb-ao-pwm",
@@ -485,109 +638,52 @@ static const struct of_device_id meson_pwm_matches[] = {
.compatible = "amlogic,meson-g12a-ao-pwm-cd",
.data = &pwm_g12a_ao_cd_data
},
+ {
+ .compatible = "amlogic,meson-s4-pwm",
+ .data = &pwm_s4_data
+ },
{},
};
MODULE_DEVICE_TABLE(of, meson_pwm_matches);
-static int meson_pwm_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 pwm_chip *chip;
struct meson_pwm *meson;
- struct resource *regs;
int err;
- meson = devm_kzalloc(&pdev->dev, sizeof(*meson), GFP_KERNEL);
- if (!meson)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, MESON_NUM_PWMS, sizeof(*meson));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ meson = to_meson_pwm(chip);
- regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- meson->base = devm_ioremap_resource(&pdev->dev, regs);
+ meson->base = devm_platform_ioremap_resource(pdev, 0);
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;
+ chip->ops = &meson_pwm_ops;
meson->data = of_device_get_match_data(&pdev->dev);
- err = meson_pwm_init_channels(meson);
+ err = meson->data->channels_init(chip);
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);
+ err = devm_pwmchip_add(&pdev->dev, chip);
+ if (err < 0)
+ return dev_err_probe(&pdev->dev, err,
+ "failed to register PWM chip\n");
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);
diff --git a/drivers/pwm/pwm-microchip-core.c b/drivers/pwm/pwm-microchip-core.c
new file mode 100644
index 000000000000..4ff32bb4c205
--- /dev/null
+++ b/drivers/pwm/pwm-microchip-core.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * corePWM driver for Microchip "soft" FPGA IP cores.
+ *
+ * Copyright (c) 2021-2023 Microchip Corporation. All rights reserved.
+ * Author: Conor Dooley <conor.dooley@microchip.com>
+ * Documentation:
+ * https://www.microsemi.com/document-portal/doc_download/1245275-corepwm-hb
+ *
+ * Limitations:
+ * - If the IP block is configured without "shadow registers", all register
+ * writes will take effect immediately, causing glitches on the output.
+ * If shadow registers *are* enabled, setting the "SYNC_UPDATE" register
+ * notifies the core that it needs to update the registers defining the
+ * waveform from the contents of the "shadow registers". Otherwise, changes
+ * will take effective immediately, even for those channels.
+ * As setting the period/duty cycle takes 4 register writes, there is a window
+ * in which this races against the start of a new period.
+ * - The IP block has no concept of a duty cycle, only rising/falling edges of
+ * the waveform. Unfortunately, if the rising & falling edges registers have
+ * the same value written to them the IP block will do whichever of a rising
+ * or a falling edge is possible. I.E. a 50% waveform at twice the requested
+ * period. Therefore to get a 0% waveform, the output is set the max high/low
+ * time depending on polarity.
+ * If the duty cycle is 0%, and the requested period is less than the
+ * available period resolution, this will manifest as a ~100% waveform (with
+ * some output glitches) rather than 50%.
+ * - The PWM period is set for the whole IP block not per channel. The driver
+ * will only change the period if no other PWM output is enabled.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/ktime.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+#define MCHPCOREPWM_PRESCALE_MAX 0xff
+#define MCHPCOREPWM_PERIOD_STEPS_MAX 0xfe
+#define MCHPCOREPWM_PERIOD_MAX 0xff00
+
+#define MCHPCOREPWM_PRESCALE 0x00
+#define MCHPCOREPWM_PERIOD 0x04
+#define MCHPCOREPWM_EN(i) (0x08 + 0x04 * (i)) /* 0x08, 0x0c */
+#define MCHPCOREPWM_POSEDGE(i) (0x10 + 0x08 * (i)) /* 0x10, 0x18, ..., 0x88 */
+#define MCHPCOREPWM_NEGEDGE(i) (0x14 + 0x08 * (i)) /* 0x14, 0x1c, ..., 0x8c */
+#define MCHPCOREPWM_SYNC_UPD 0xe4
+#define MCHPCOREPWM_TIMEOUT_MS 100u
+
+struct mchp_core_pwm_chip {
+ struct clk *clk;
+ void __iomem *base;
+ ktime_t update_timestamp;
+ u32 sync_update_mask;
+ u16 channel_enabled;
+};
+
+static inline struct mchp_core_pwm_chip *to_mchp_core_pwm(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static void mchp_core_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm,
+ bool enable, u64 period)
+{
+ struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
+ u8 channel_enable, reg_offset, shift;
+
+ /*
+ * There are two adjacent 8 bit control regs, the lower reg controls
+ * 0-7 and the upper reg 8-15. Check if the pwm is in the upper reg
+ * and if so, offset by the bus width.
+ */
+ reg_offset = MCHPCOREPWM_EN(pwm->hwpwm >> 3);
+ shift = pwm->hwpwm & 7;
+
+ channel_enable = readb_relaxed(mchp_core_pwm->base + reg_offset);
+ channel_enable &= ~(1 << shift);
+ channel_enable |= (enable << shift);
+
+ writel_relaxed(channel_enable, mchp_core_pwm->base + reg_offset);
+ mchp_core_pwm->channel_enabled &= ~BIT(pwm->hwpwm);
+ mchp_core_pwm->channel_enabled |= enable << pwm->hwpwm;
+
+ /*
+ * The updated values will not appear on the bus until they have been
+ * applied to the waveform at the beginning of the next period.
+ * This is a NO-OP if the channel does not have shadow registers.
+ */
+ if (mchp_core_pwm->sync_update_mask & (1 << pwm->hwpwm))
+ mchp_core_pwm->update_timestamp = ktime_add_ns(ktime_get(), period);
+}
+
+static void mchp_core_pwm_wait_for_sync_update(struct mchp_core_pwm_chip *mchp_core_pwm,
+ unsigned int channel)
+{
+ /*
+ * If a shadow register is used for this PWM channel, and iff there is
+ * a pending update to the waveform, we must wait for it to be applied
+ * before attempting to read its state. Reading the registers yields
+ * the currently implemented settings & the new ones are only readable
+ * once the current period has ended.
+ */
+
+ if (mchp_core_pwm->sync_update_mask & (1 << channel)) {
+ ktime_t current_time = ktime_get();
+ s64 remaining_ns;
+ u32 delay_us;
+
+ remaining_ns = ktime_to_ns(ktime_sub(mchp_core_pwm->update_timestamp,
+ current_time));
+
+ /*
+ * If the update has gone through, don't bother waiting for
+ * obvious reasons. Otherwise wait around for an appropriate
+ * amount of time for the update to go through.
+ */
+ if (remaining_ns <= 0)
+ return;
+
+ delay_us = DIV_ROUND_UP_ULL(remaining_ns, NSEC_PER_USEC);
+ fsleep(delay_us);
+ }
+}
+
+static u64 mchp_core_pwm_calc_duty(const struct pwm_state *state, u64 clk_rate,
+ u8 prescale, u8 period_steps)
+{
+ u64 duty_steps, tmp;
+
+ /*
+ * Calculate the duty cycle in multiples of the prescaled period:
+ * duty_steps = duty_in_ns / step_in_ns
+ * step_in_ns = (prescale * NSEC_PER_SEC) / clk_rate
+ * The code below is rearranged slightly to only divide once.
+ */
+ tmp = (((u64)prescale) + 1) * NSEC_PER_SEC;
+ duty_steps = mul_u64_u64_div_u64(state->duty_cycle, clk_rate, tmp);
+
+ return duty_steps;
+}
+
+static void mchp_core_pwm_apply_duty(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state, u64 duty_steps,
+ u16 period_steps)
+{
+ struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
+ u8 posedge, negedge;
+ u8 first_edge = 0, second_edge = duty_steps;
+
+ /*
+ * Setting posedge == negedge doesn't yield a constant output,
+ * so that's an unsuitable setting to model duty_steps = 0.
+ * In that case set the unwanted edge to a value that never
+ * triggers.
+ */
+ if (duty_steps == 0)
+ first_edge = period_steps + 1;
+
+ if (state->polarity == PWM_POLARITY_INVERSED) {
+ negedge = first_edge;
+ posedge = second_edge;
+ } else {
+ posedge = first_edge;
+ negedge = second_edge;
+ }
+
+ /*
+ * Set the sync bit which ensures that periods that already started are
+ * completed unaltered. At each counter reset event the values are
+ * updated from the shadow registers.
+ */
+ writel_relaxed(posedge, mchp_core_pwm->base + MCHPCOREPWM_POSEDGE(pwm->hwpwm));
+ writel_relaxed(negedge, mchp_core_pwm->base + MCHPCOREPWM_NEGEDGE(pwm->hwpwm));
+}
+
+static int mchp_core_pwm_calc_period(const struct pwm_state *state, unsigned long clk_rate,
+ u16 *prescale, u16 *period_steps)
+{
+ u64 tmp;
+
+ /*
+ * Calculate the period cycles and prescale values.
+ * The registers are each 8 bits wide & multiplied to compute the period
+ * using the formula:
+ * (prescale + 1) * (period_steps + 1)
+ * period = -------------------------------------
+ * clk_rate
+ * so the maximum period that can be generated is 0x10000 times the
+ * period of the input clock.
+ * However, due to the design of the "hardware", it is not possible to
+ * attain a 100% duty cycle if the full range of period_steps is used.
+ * Therefore period_steps is restricted to 0xfe and the maximum multiple
+ * of the clock period attainable is (0xff + 1) * (0xfe + 1) = 0xff00
+ *
+ * The prescale and period_steps registers operate similarly to
+ * CLK_DIVIDER_ONE_BASED, where the value used by the hardware is that
+ * in the register plus one.
+ * It's therefore not possible to set a period lower than 1/clk_rate, so
+ * if tmp is 0, abort. Without aborting, we will set a period that is
+ * greater than that requested and, more importantly, will trigger the
+ * neg-/pos-edge issue described in the limitations.
+ */
+ tmp = mul_u64_u64_div_u64(state->period, clk_rate, NSEC_PER_SEC);
+ if (tmp >= MCHPCOREPWM_PERIOD_MAX) {
+ *prescale = MCHPCOREPWM_PRESCALE_MAX;
+ *period_steps = MCHPCOREPWM_PERIOD_STEPS_MAX;
+
+ return 0;
+ }
+
+ /*
+ * There are multiple strategies that could be used to choose the
+ * prescale & period_steps values.
+ * Here the idea is to pick values so that the selection of duty cycles
+ * is as finegrain as possible, while also keeping the period less than
+ * that requested.
+ *
+ * A simple way to satisfy the first condition is to always set
+ * period_steps to its maximum value. This neatly also satisfies the
+ * second condition too, since using the maximum value of period_steps
+ * to calculate prescale actually calculates its upper bound.
+ * Integer division will ensure a round down, so the period will thereby
+ * always be less than that requested.
+ *
+ * The downside of this approach is a significant degree of inaccuracy,
+ * especially as tmp approaches integer multiples of
+ * MCHPCOREPWM_PERIOD_STEPS_MAX.
+ *
+ * As we must produce a period less than that requested, and for the
+ * sake of creating a simple algorithm, disallow small values of tmp
+ * that would need special handling.
+ */
+ if (tmp < MCHPCOREPWM_PERIOD_STEPS_MAX + 1)
+ return -EINVAL;
+
+ /*
+ * This "optimal" value for prescale is be calculated using the maximum
+ * permitted value of period_steps, 0xfe.
+ *
+ * period * clk_rate
+ * prescale = ------------------------- - 1
+ * NSEC_PER_SEC * (0xfe + 1)
+ *
+ *
+ * period * clk_rate
+ * ------------------- was precomputed as `tmp`
+ * NSEC_PER_SEC
+ */
+ *prescale = ((u16)tmp) / (MCHPCOREPWM_PERIOD_STEPS_MAX + 1) - 1;
+
+ /*
+ * period_steps can be computed from prescale:
+ * period * clk_rate
+ * period_steps = ----------------------------- - 1
+ * NSEC_PER_SEC * (prescale + 1)
+ *
+ * However, in this approximation, we simply use the maximum value that
+ * was used to compute prescale.
+ */
+ *period_steps = MCHPCOREPWM_PERIOD_STEPS_MAX;
+
+ return 0;
+}
+
+static int mchp_core_pwm_apply_locked(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
+ bool period_locked;
+ unsigned long clk_rate;
+ u64 duty_steps;
+ u16 prescale, period_steps;
+ int ret;
+
+ if (!state->enabled) {
+ mchp_core_pwm_enable(chip, pwm, false, pwm->state.period);
+ return 0;
+ }
+
+ /*
+ * If clk_rate is too big, the following multiplication might overflow.
+ * However this is implausible, as the fabric of current FPGAs cannot
+ * provide clocks at a rate high enough.
+ */
+ clk_rate = clk_get_rate(mchp_core_pwm->clk);
+ if (clk_rate >= NSEC_PER_SEC)
+ return -EINVAL;
+
+ ret = mchp_core_pwm_calc_period(state, clk_rate, &prescale, &period_steps);
+ if (ret)
+ return ret;
+
+ /*
+ * If the only thing that has changed is the duty cycle or the polarity,
+ * we can shortcut the calculations and just compute/apply the new duty
+ * cycle pos & neg edges
+ * As all the channels share the same period, do not allow it to be
+ * changed if any other channels are enabled.
+ * If the period is locked, it may not be possible to use a period
+ * less than that requested. In that case, we just abort.
+ */
+ period_locked = mchp_core_pwm->channel_enabled & ~(1 << pwm->hwpwm);
+
+ if (period_locked) {
+ u16 hw_prescale;
+ u16 hw_period_steps;
+
+ hw_prescale = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PRESCALE);
+ hw_period_steps = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PERIOD);
+
+ if ((period_steps + 1) * (prescale + 1) <
+ (hw_period_steps + 1) * (hw_prescale + 1))
+ return -EINVAL;
+
+ /*
+ * It is possible that something could have set the period_steps
+ * register to 0xff, which would prevent us from setting a 100%
+ * or 0% relative duty cycle, as explained above in
+ * mchp_core_pwm_calc_period().
+ * The period is locked and we cannot change this, so we abort.
+ */
+ if (hw_period_steps > MCHPCOREPWM_PERIOD_STEPS_MAX)
+ return -EINVAL;
+
+ prescale = hw_prescale;
+ period_steps = hw_period_steps;
+ }
+
+ duty_steps = mchp_core_pwm_calc_duty(state, clk_rate, prescale, period_steps);
+
+ /*
+ * Because the period is not per channel, it is possible that the
+ * requested duty cycle is longer than the period, in which case cap it
+ * to the period, IOW a 100% duty cycle.
+ */
+ if (duty_steps > period_steps)
+ duty_steps = period_steps + 1;
+
+ if (!period_locked) {
+ writel_relaxed(prescale, mchp_core_pwm->base + MCHPCOREPWM_PRESCALE);
+ writel_relaxed(period_steps, mchp_core_pwm->base + MCHPCOREPWM_PERIOD);
+ }
+
+ mchp_core_pwm_apply_duty(chip, pwm, state, duty_steps, period_steps);
+
+ mchp_core_pwm_enable(chip, pwm, true, pwm->state.period);
+
+ return 0;
+}
+
+static int mchp_core_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
+
+ mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm);
+
+ return mchp_core_pwm_apply_locked(chip, pwm, state);
+}
+
+static int mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
+ u64 rate;
+ u16 prescale, period_steps;
+ u8 duty_steps, posedge, negedge;
+
+ mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm);
+
+ if (mchp_core_pwm->channel_enabled & (1 << pwm->hwpwm))
+ state->enabled = true;
+ else
+ state->enabled = false;
+
+ rate = clk_get_rate(mchp_core_pwm->clk);
+
+ /*
+ * Calculating the period:
+ * The registers are each 8 bits wide & multiplied to compute the period
+ * using the formula:
+ * (prescale + 1) * (period_steps + 1)
+ * period = -------------------------------------
+ * clk_rate
+ *
+ * Note:
+ * The prescale and period_steps registers operate similarly to
+ * CLK_DIVIDER_ONE_BASED, where the value used by the hardware is that
+ * in the register plus one.
+ */
+ prescale = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PRESCALE);
+ period_steps = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PERIOD);
+
+ state->period = (period_steps + 1) * (prescale + 1);
+ state->period *= NSEC_PER_SEC;
+ state->period = DIV64_U64_ROUND_UP(state->period, rate);
+
+ posedge = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_POSEDGE(pwm->hwpwm));
+ negedge = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_NEGEDGE(pwm->hwpwm));
+
+ if (negedge == posedge) {
+ state->duty_cycle = state->period;
+ state->period *= 2;
+ } else {
+ duty_steps = abs((s16)posedge - (s16)negedge);
+ state->duty_cycle = duty_steps * (prescale + 1) * NSEC_PER_SEC;
+ state->duty_cycle = DIV64_U64_ROUND_UP(state->duty_cycle, rate);
+ }
+
+ state->polarity = negedge < posedge ? PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
+
+ return 0;
+}
+
+static const struct pwm_ops mchp_core_pwm_ops = {
+ .apply = mchp_core_pwm_apply,
+ .get_state = mchp_core_pwm_get_state,
+};
+
+static const struct of_device_id mchp_core_of_match[] = {
+ {
+ .compatible = "microchip,corepwm-rtl-v4",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mchp_core_of_match);
+
+static int mchp_core_pwm_probe(struct platform_device *pdev)
+{
+ struct pwm_chip *chip;
+ struct mchp_core_pwm_chip *mchp_core_pwm;
+ struct resource *regs;
+ int ret;
+
+ chip = devm_pwmchip_alloc(&pdev->dev, 16, sizeof(*mchp_core_pwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ mchp_core_pwm = to_mchp_core_pwm(chip);
+
+ mchp_core_pwm->base = devm_platform_get_and_ioremap_resource(pdev, 0, &regs);
+ if (IS_ERR(mchp_core_pwm->base))
+ return PTR_ERR(mchp_core_pwm->base);
+
+ mchp_core_pwm->clk = devm_clk_get_enabled(&pdev->dev, NULL);
+ if (IS_ERR(mchp_core_pwm->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(mchp_core_pwm->clk),
+ "failed to get PWM clock\n");
+
+ if (of_property_read_u32(pdev->dev.of_node, "microchip,sync-update-mask",
+ &mchp_core_pwm->sync_update_mask))
+ mchp_core_pwm->sync_update_mask = 0;
+
+ chip->ops = &mchp_core_pwm_ops;
+
+ mchp_core_pwm->channel_enabled = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_EN(0));
+ mchp_core_pwm->channel_enabled |=
+ readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_EN(1)) << 8;
+
+ /*
+ * Enable synchronous update mode for all channels for which shadow
+ * registers have been synthesised.
+ */
+ writel_relaxed(1U, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
+ mchp_core_pwm->update_timestamp = ktime_get();
+
+ ret = devm_pwmchip_add(&pdev->dev, chip);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Failed to add pwmchip\n");
+
+ return 0;
+}
+
+static struct platform_driver mchp_core_pwm_driver = {
+ .driver = {
+ .name = "mchp-core-pwm",
+ .of_match_table = mchp_core_of_match,
+ },
+ .probe = mchp_core_pwm_probe,
+};
+module_platform_driver(mchp_core_pwm_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>");
+MODULE_DESCRIPTION("corePWM driver for Microchip FPGAs");
diff --git a/drivers/pwm/pwm-mtk-disp.c b/drivers/pwm/pwm-mtk-disp.c
index 83b8be0209b7..bafd6b6195f6 100644
--- a/drivers/pwm/pwm-mtk-disp.c
+++ b/drivers/pwm/pwm-mtk-disp.c
@@ -5,12 +5,12 @@
* Author: YH Huang <yh.huang@mediatek.com>
*/
+#include <linux/bitfield.h>
#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>
@@ -42,16 +42,16 @@ struct mtk_pwm_data {
};
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;
+ bool enabled;
};
static inline struct mtk_disp_pwm *to_mtk_disp_pwm(struct pwm_chip *chip)
{
- return container_of(chip, struct mtk_disp_pwm, chip);
+ return pwmchip_get_drvdata(chip);
}
static void mtk_disp_pwm_update_bits(struct mtk_disp_pwm *mdp, u32 offset,
@@ -66,14 +66,44 @@ static void mtk_disp_pwm_update_bits(struct mtk_disp_pwm *mdp, u32 offset,
writel(value, address);
}
-static int mtk_disp_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
- int duty_ns, int period_ns)
+static int mtk_disp_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
{
struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip);
u32 clk_div, period, high_width, value;
u64 div, rate;
int err;
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (!state->enabled && mdp->enabled) {
+ 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);
+
+ mdp->enabled = false;
+ return 0;
+ }
+
+ if (!mdp->enabled) {
+ err = clk_prepare_enable(mdp->clk_main);
+ if (err < 0) {
+ dev_err(pwmchip_parent(chip), "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(pwmchip_parent(chip), "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.
@@ -85,27 +115,35 @@ static int mtk_disp_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* 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) >>
+ clk_div = mul_u64_u64_div_u64(state->period, rate, NSEC_PER_SEC) >>
PWM_PERIOD_BIT_WIDTH;
- if (clk_div > PWM_CLKDIV_MAX)
+ if (clk_div > PWM_CLKDIV_MAX) {
+ if (!mdp->enabled) {
+ 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);
+ period = mul_u64_u64_div_u64(state->period, rate, div);
if (period > 0)
period--;
- high_width = div64_u64(rate * duty_ns, div);
+ high_width = mul_u64_u64_div_u64(state->duty_cycle, rate, div);
value = period | (high_width << PWM_HIGH_WIDTH_SHIFT);
- err = clk_enable(mdp->clk_main);
- if (err < 0)
- return err;
-
- err = clk_enable(mdp->clk_mm);
- if (err < 0) {
- clk_disable(mdp->clk_main);
- return err;
+ 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,
@@ -124,129 +162,106 @@ static int mtk_disp_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
0x0);
}
- clk_disable(mdp->clk_mm);
- clk_disable(mdp->clk_main);
+ mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, mdp->data->enable_mask,
+ mdp->data->enable_mask);
+ mdp->enabled = true;
return 0;
}
-static int mtk_disp_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+static int mtk_disp_pwm_get_state(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ struct pwm_state *state)
{
struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip);
+ u64 rate, period, high_width;
+ u32 clk_div, pwm_en, con0, con1;
int err;
- err = clk_enable(mdp->clk_main);
- if (err < 0)
+ err = clk_prepare_enable(mdp->clk_main);
+ if (err < 0) {
+ dev_err(pwmchip_parent(chip), "Can't enable mdp->clk_main: %pe\n", ERR_PTR(err));
return err;
+ }
- err = clk_enable(mdp->clk_mm);
+ err = clk_prepare_enable(mdp->clk_mm);
if (err < 0) {
- clk_disable(mdp->clk_main);
+ dev_err(pwmchip_parent(chip), "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);
+ /*
+ * Apply DISP_PWM_DEBUG settings to choose whether to enable or disable
+ * registers double buffer and manual commit to working register before
+ * performing any read/write operation
+ */
+ if (mdp->data->bls_debug)
+ 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, DISP_PWM_EN, mdp->data->enable_mask,
- 0x0);
+ rate = clk_get_rate(mdp->clk_main);
+ con0 = readl(mdp->base + mdp->data->con0);
+ con1 = readl(mdp->base + mdp->data->con1);
+ pwm_en = readl(mdp->base + DISP_PWM_EN);
+ state->enabled = !!(pwm_en & mdp->data->enable_mask);
+ clk_div = FIELD_GET(PWM_CLKDIV_MASK, con0);
+ period = FIELD_GET(PWM_PERIOD_MASK, con1);
+ /*
+ * period has 12 bits, clk_div 11 and NSEC_PER_SEC has 30,
+ * so period * (clk_div + 1) * NSEC_PER_SEC doesn't overflow.
+ */
+ state->period = DIV64_U64_ROUND_UP(period * (clk_div + 1) * NSEC_PER_SEC, rate);
+ high_width = FIELD_GET(PWM_HIGH_WIDTH_MASK, con1);
+ state->duty_cycle = DIV64_U64_ROUND_UP(high_width * (clk_div + 1) * NSEC_PER_SEC,
+ rate);
+ state->polarity = PWM_POLARITY_NORMAL;
+ clk_disable_unprepare(mdp->clk_mm);
+ clk_disable_unprepare(mdp->clk_main);
- clk_disable(mdp->clk_mm);
- clk_disable(mdp->clk_main);
+ return 0;
}
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,
+ .apply = mtk_disp_pwm_apply,
+ .get_state = mtk_disp_pwm_get_state,
};
static int mtk_disp_pwm_probe(struct platform_device *pdev)
{
+ struct pwm_chip *chip;
struct mtk_disp_pwm *mdp;
- struct resource *r;
int ret;
- mdp = devm_kzalloc(&pdev->dev, sizeof(*mdp), GFP_KERNEL);
- if (!mdp)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*mdp));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ mdp = to_mtk_disp_pwm(chip);
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);
+ mdp->base = devm_platform_ioremap_resource(pdev, 0);
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);
+ return dev_err_probe(&pdev->dev, PTR_ERR(mdp->clk_main),
+ "Failed to get main clock\n");
mdp->clk_mm = devm_clk_get(&pdev->dev, "mm");
if (IS_ERR(mdp->clk_mm))
- return PTR_ERR(mdp->clk_mm);
+ return dev_err_probe(&pdev->dev, PTR_ERR(mdp->clk_mm),
+ "Failed to get mm clock\n");
- ret = clk_prepare(mdp->clk_main);
- if (ret < 0)
- return ret;
+ chip->ops = &mtk_disp_pwm_ops;
- ret = clk_prepare(mdp->clk_mm);
+ ret = devm_pwmchip_add(&pdev->dev, chip);
if (ret < 0)
- goto disable_clk_main;
-
- 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: %d\n", ret);
- goto disable_clk_mm;
- }
-
- platform_set_drvdata(pdev, mdp);
-
- /*
- * For MT2701, disable double buffer before writing register
- * and select manual mode and use PWM_PERIOD/PWM_HIGH_WIDTH.
- */
- if (!mdp->data->has_commit) {
- 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);
- }
+ return dev_err_probe(&pdev->dev, ret, "pwmchip_add() failed\n");
return 0;
-
-disable_clk_mm:
- clk_unprepare(mdp->clk_mm);
-disable_clk_main:
- clk_unprepare(mdp->clk_main);
- return ret;
-}
-
-static int mtk_disp_pwm_remove(struct platform_device *pdev)
-{
- struct mtk_disp_pwm *mdp = platform_get_drvdata(pdev);
- int ret;
-
- ret = pwmchip_remove(&mdp->chip);
- clk_unprepare(mdp->clk_mm);
- clk_unprepare(mdp->clk_main);
-
- return ret;
}
static const struct mtk_pwm_data mt2701_pwm_data = {
@@ -294,7 +309,6 @@ static struct platform_driver mtk_disp_pwm_driver = {
.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);
diff --git a/drivers/pwm/pwm-mxs.c b/drivers/pwm/pwm-mxs.c
index 7ce616923c52..8cad214b1c29 100644
--- a/drivers/pwm/pwm-mxs.c
+++ b/drivers/pwm/pwm-mxs.c
@@ -37,12 +37,14 @@ static const u8 cdiv_shift[PERIOD_CDIV_MAX] = {
};
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 inline struct mxs_pwm_chip *to_mxs_pwm_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
static int mxs_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
@@ -115,18 +117,26 @@ static int mxs_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
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 pwm_chip *chip;
struct mxs_pwm_chip *mxs;
+ u32 npwm;
int ret;
- mxs = devm_kzalloc(&pdev->dev, sizeof(*mxs), GFP_KERNEL);
- if (!mxs)
- return -ENOMEM;
+ ret = of_property_read_u32(np, "fsl,pwm-number", &npwm);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to get pwm number: %d\n", ret);
+ return ret;
+ }
+
+ chip = devm_pwmchip_alloc(&pdev->dev, npwm, sizeof(*mxs));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ mxs = to_mxs_pwm_chip(chip);
mxs->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(mxs->base))
@@ -136,42 +146,20 @@ static int mxs_pwm_probe(struct platform_device *pdev)
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;
+ chip->ops = &mxs_pwm_ops;
- 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);
+ ret = devm_pwmchip_add(&pdev->dev, chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret);
return ret;
}
- platform_set_drvdata(pdev, mxs);
-
- ret = stmp_reset_block(mxs->base);
- if (ret)
- goto pwm_remove;
-
return 0;
-
-pwm_remove:
- pwmchip_remove(&mxs->chip);
- return ret;
-}
-
-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[] = {
@@ -186,7 +174,6 @@ static struct platform_driver mxs_pwm_driver = {
.of_match_table = mxs_pwm_dt_ids,
},
.probe = mxs_pwm_probe,
- .remove = mxs_pwm_remove,
};
module_platform_driver(mxs_pwm_driver);
diff --git a/drivers/pwm/pwm-ntxec.c b/drivers/pwm/pwm-ntxec.c
new file mode 100644
index 000000000000..28d1c2e5a98f
--- /dev/null
+++ b/drivers/pwm/pwm-ntxec.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * The Netronix embedded controller is a microcontroller found in some
+ * e-book readers designed by the original design manufacturer Netronix, Inc.
+ * It contains RTC, battery monitoring, system power management, and PWM
+ * functionality.
+ *
+ * This driver implements PWM output.
+ *
+ * Copyright 2020 Jonathan Neuschäfer <j.neuschaefer@gmx.net>
+ *
+ * Limitations:
+ * - The get_state callback is not implemented, because the current state of
+ * the PWM output can't be read back from the hardware.
+ * - The hardware can only generate normal polarity output.
+ * - The period and duty cycle can't be changed together in one atomic action.
+ */
+
+#include <linux/mfd/ntxec.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+struct ntxec_pwm {
+ struct ntxec *ec;
+};
+
+static struct ntxec_pwm *ntxec_pwm_from_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+#define NTXEC_REG_AUTO_OFF_HI 0xa1
+#define NTXEC_REG_AUTO_OFF_LO 0xa2
+#define NTXEC_REG_ENABLE 0xa3
+#define NTXEC_REG_PERIOD_LOW 0xa4
+#define NTXEC_REG_PERIOD_HIGH 0xa5
+#define NTXEC_REG_DUTY_LOW 0xa6
+#define NTXEC_REG_DUTY_HIGH 0xa7
+
+/*
+ * The time base used in the EC is 8MHz, or 125ns. Period and duty cycle are
+ * measured in this unit.
+ */
+#define TIME_BASE_NS 125
+
+/*
+ * The maximum input value (in nanoseconds) is determined by the time base and
+ * the range of the hardware registers that hold the converted value.
+ * It fits into 32 bits, so we can do our calculations in 32 bits as well.
+ */
+#define MAX_PERIOD_NS (TIME_BASE_NS * 0xffff)
+
+static int ntxec_pwm_set_raw_period_and_duty_cycle(struct pwm_chip *chip,
+ int period, int duty)
+{
+ struct ntxec_pwm *priv = ntxec_pwm_from_chip(chip);
+
+ /*
+ * Changes to the period and duty cycle take effect as soon as the
+ * corresponding low byte is written, so the hardware may be configured
+ * to an inconsistent state after the period is written and before the
+ * duty cycle is fully written. If, in such a case, the old duty cycle
+ * is longer than the new period, the EC may output 100% for a moment.
+ *
+ * To minimize the time between the changes to period and duty cycle
+ * taking effect, the writes are interleaved.
+ */
+
+ struct reg_sequence regs[] = {
+ { NTXEC_REG_PERIOD_HIGH, ntxec_reg8(period >> 8) },
+ { NTXEC_REG_DUTY_HIGH, ntxec_reg8(duty >> 8) },
+ { NTXEC_REG_PERIOD_LOW, ntxec_reg8(period) },
+ { NTXEC_REG_DUTY_LOW, ntxec_reg8(duty) },
+ };
+
+ return regmap_multi_reg_write(priv->ec->regmap, regs, ARRAY_SIZE(regs));
+}
+
+static int ntxec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm_dev,
+ const struct pwm_state *state)
+{
+ struct ntxec_pwm *priv = ntxec_pwm_from_chip(chip);
+ unsigned int period, duty;
+ int res;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ period = min_t(u64, state->period, MAX_PERIOD_NS);
+ duty = min_t(u64, state->duty_cycle, period);
+
+ period /= TIME_BASE_NS;
+ duty /= TIME_BASE_NS;
+
+ /*
+ * Writing a duty cycle of zero puts the device into a state where
+ * writing a higher duty cycle doesn't result in the brightness that it
+ * usually results in. This can be fixed by cycling the ENABLE register.
+ *
+ * As a workaround, write ENABLE=0 when the duty cycle is zero.
+ * The case that something has previously set the duty cycle to zero
+ * but ENABLE=1, is not handled.
+ */
+ if (state->enabled && duty != 0) {
+ res = ntxec_pwm_set_raw_period_and_duty_cycle(chip, period, duty);
+ if (res)
+ return res;
+
+ res = regmap_write(priv->ec->regmap, NTXEC_REG_ENABLE, ntxec_reg8(1));
+ if (res)
+ return res;
+
+ /* Disable the auto-off timer */
+ res = regmap_write(priv->ec->regmap, NTXEC_REG_AUTO_OFF_HI, ntxec_reg8(0xff));
+ if (res)
+ return res;
+
+ return regmap_write(priv->ec->regmap, NTXEC_REG_AUTO_OFF_LO, ntxec_reg8(0xff));
+ } else {
+ return regmap_write(priv->ec->regmap, NTXEC_REG_ENABLE, ntxec_reg8(0));
+ }
+}
+
+static const struct pwm_ops ntxec_pwm_ops = {
+ .apply = ntxec_pwm_apply,
+ /*
+ * No .get_state callback, because the current state cannot be read
+ * back from the hardware.
+ */
+};
+
+static int ntxec_pwm_probe(struct platform_device *pdev)
+{
+ struct ntxec *ec = dev_get_drvdata(pdev->dev.parent);
+ struct ntxec_pwm *priv;
+ struct pwm_chip *chip;
+
+ device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent);
+
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*priv));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ priv = ntxec_pwm_from_chip(chip);
+
+ priv->ec = ec;
+ chip->ops = &ntxec_pwm_ops;
+
+ return devm_pwmchip_add(&pdev->dev, chip);
+}
+
+static struct platform_driver ntxec_pwm_driver = {
+ .driver = {
+ .name = "ntxec-pwm",
+ },
+ .probe = ntxec_pwm_probe,
+};
+module_platform_driver(ntxec_pwm_driver);
+
+MODULE_AUTHOR("Jonathan Neuschäfer <j.neuschaefer@gmx.net>");
+MODULE_DESCRIPTION("PWM driver for Netronix EC");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ntxec-pwm");
diff --git a/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c
index 0d31833db2e2..1858a77401f8 100644
--- a/drivers/pwm/pwm-omap-dmtimer.c
+++ b/drivers/pwm/pwm-omap-dmtimer.c
@@ -14,7 +14,7 @@
* 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: http://www.ti.com/lit/ug/spruh73q/spruh73q.pdf
+ * Reference Manual: https://www.ti.com/lit/ug/spruh73q/spruh73q.pdf
*
* Limitations:
* - When PWM is stopped, timer counter gets stopped immediately. This
@@ -37,7 +37,6 @@
#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>
@@ -54,16 +53,12 @@
/**
* 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
+ * @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;
@@ -72,7 +67,7 @@ struct pwm_omap_dmtimer_chip {
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);
+ return pwmchip_get_drvdata(chip);
}
/**
@@ -158,7 +153,7 @@ static int pwm_omap_dmtimer_config(struct pwm_chip *chip,
unsigned long clk_rate;
struct clk *fclk;
- dev_dbg(chip->dev, "requested duty cycle: %d ns, period: %d ns\n",
+ dev_dbg(pwmchip_parent(chip), "requested duty cycle: %d ns, period: %d ns\n",
duty_ns, period_ns);
if (duty_ns == pwm_get_duty_cycle(pwm) &&
@@ -167,17 +162,17 @@ static int pwm_omap_dmtimer_config(struct pwm_chip *chip,
fclk = omap->pdata->get_fclk(omap->dm_timer);
if (!fclk) {
- dev_err(chip->dev, "invalid pmtimer fclk\n");
+ dev_err(pwmchip_parent(chip), "invalid pmtimer fclk\n");
return -EINVAL;
}
clk_rate = clk_get_rate(fclk);
if (!clk_rate) {
- dev_err(chip->dev, "invalid pmtimer fclk rate\n");
+ dev_err(pwmchip_parent(chip), "invalid pmtimer fclk rate\n");
return -EINVAL;
}
- dev_dbg(chip->dev, "clk rate: %luHz\n", clk_rate);
+ dev_dbg(pwmchip_parent(chip), "clk rate: %luHz\n", clk_rate);
/*
* Calculate the appropriate load and match values based on the
@@ -199,27 +194,27 @@ static int pwm_omap_dmtimer_config(struct pwm_chip *chip,
duty_cycles = pwm_omap_dmtimer_get_clock_cycles(clk_rate, duty_ns);
if (period_cycles < 2) {
- dev_info(chip->dev,
+ dev_info(pwmchip_parent(chip),
"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,
+ dev_dbg(pwmchip_parent(chip),
"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");
+ dev_dbg(pwmchip_parent(chip), "using minimum of 1 clock cycle\n");
duty_cycles = 1;
} else if (duty_cycles >= period_cycles) {
- dev_dbg(chip->dev,
+ dev_dbg(pwmchip_parent(chip),
"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");
+ dev_dbg(pwmchip_parent(chip), "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",
+ dev_dbg(pwmchip_parent(chip), "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,
@@ -231,7 +226,7 @@ static int pwm_omap_dmtimer_config(struct pwm_chip *chip,
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",
+ dev_dbg(pwmchip_parent(chip), "load value: %#08x (%d), match value: %#08x (%d)\n",
load_value, load_value, match_value, match_value);
return 0;
@@ -277,13 +272,11 @@ static int pwm_omap_dmtimer_apply(struct pwm_chip *chip,
const struct pwm_state *state)
{
struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip);
- int ret = 0;
-
- mutex_lock(&omap->mutex);
+ int ret;
if (pwm_omap_dmtimer_is_enabled(omap) && !state->enabled) {
omap->pdata->stop(omap->dm_timer);
- goto unlock_mutex;
+ return 0;
}
if (pwm_omap_dmtimer_polarity(omap) != state->polarity)
@@ -292,7 +285,7 @@ static int pwm_omap_dmtimer_apply(struct pwm_chip *chip,
ret = pwm_omap_dmtimer_config(chip, pwm, state->duty_cycle,
state->period);
if (ret)
- goto unlock_mutex;
+ return ret;
if (!pwm_omap_dmtimer_is_enabled(omap) && state->enabled) {
omap->pdata->set_pwm(omap->dm_timer,
@@ -303,15 +296,11 @@ static int pwm_omap_dmtimer_apply(struct pwm_chip *chip,
pwm_omap_dmtimer_start(omap);
}
-unlock_mutex:
- mutex_unlock(&omap->mutex);
-
- return ret;
+ return 0;
}
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)
@@ -320,6 +309,7 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev)
struct dmtimer_platform_data *timer_pdata;
const struct omap_dm_timer_ops *pdata;
struct platform_device *timer_pdev;
+ struct pwm_chip *chip;
struct pwm_omap_dmtimer_chip *omap;
struct omap_dm_timer *dm_timer;
struct device_node *timer;
@@ -365,7 +355,7 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev)
goto err_platdata;
}
- if (!of_get_property(timer, "ti,timer-pwm", NULL)) {
+ if (!of_property_read_bool(timer, "ti,timer-pwm")) {
dev_err(&pdev->dev, "Missing ti,timer-pwm capability\n");
ret = -ENODEV;
goto err_timer_property;
@@ -377,11 +367,12 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev)
goto err_request_timer;
}
- omap = devm_kzalloc(&pdev->dev, sizeof(*omap), GFP_KERNEL);
- if (!omap) {
- ret = -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*omap));
+ if (IS_ERR(chip)) {
+ ret = PTR_ERR(chip);
goto err_alloc_omap;
}
+ omap = to_pwm_omap_dmtimer_chip(chip);
omap->pdata = pdata;
omap->dm_timer = dm_timer;
@@ -401,16 +392,9 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev)
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;
+ chip->ops = &pwm_omap_dmtimer_ops;
- mutex_init(&omap->mutex);
-
- ret = pwmchip_add(&omap->chip);
+ ret = pwmchip_add(chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register PWM\n");
goto err_pwmchip_add;
@@ -418,7 +402,7 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev)
of_node_put(timer);
- platform_set_drvdata(pdev, omap);
+ platform_set_drvdata(pdev, chip);
return 0;
@@ -444,14 +428,12 @@ err_find_timer_pdev:
return ret;
}
-static int pwm_omap_dmtimer_remove(struct platform_device *pdev)
+static void pwm_omap_dmtimer_remove(struct platform_device *pdev)
{
- struct pwm_omap_dmtimer_chip *omap = platform_get_drvdata(pdev);
- int ret;
+ struct pwm_chip *chip = platform_get_drvdata(pdev);
+ struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip);
- ret = pwmchip_remove(&omap->chip);
- if (ret)
- return ret;
+ pwmchip_remove(chip);
if (pm_runtime_active(&omap->dm_timer_pdev->dev))
omap->pdata->stop(omap->dm_timer);
@@ -459,10 +441,6 @@ static int pwm_omap_dmtimer_remove(struct platform_device *pdev)
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[] = {
@@ -474,10 +452,10 @@ 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),
+ .of_match_table = pwm_omap_dmtimer_of_match,
},
.probe = pwm_omap_dmtimer_probe,
- .remove = pwm_omap_dmtimer_remove,
+ .remove = pwm_omap_dmtimer_remove,
};
module_platform_driver(pwm_omap_dmtimer_driver);
diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c
index 76cd22bd6614..107bebec3546 100644
--- a/drivers/pwm/pwm-pca9685.c
+++ b/drivers/pwm/pwm-pca9685.c
@@ -8,7 +8,6 @@
* based on the pwm-twl-led.c driver
*/
-#include <linux/acpi.h>
#include <linux/gpio/driver.h>
#include <linux/i2c.h>
#include <linux/module.h>
@@ -23,11 +22,10 @@
#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.
+ * Because the PCA9685 has only one prescaler per chip, only the first channel
+ * that is enabled is allowed to change the prescale register.
+ * PWM channels requested afterwards must use a period that results in the same
+ * prescale setting as the one set by the first requested channel.
*/
#define PCA9685_MODE1 0x00
@@ -51,365 +49,326 @@
#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_OSC_CLOCK_HZ 25000000 /* Internal oscillator with 25 MHz */
+
+/*
+ * The time value of one counter tick. Note that NSEC_PER_SEC is an integer
+ * multiple of PCA9685_OSC_CLOCK_HZ, so there is no rounding involved and we're
+ * not loosing precision due to the early division.
+ */
+#define PCA9685_QUANTUM_NS(_prescale) ((NSEC_PER_SEC / PCA9685_OSC_CLOCK_HZ) * (_prescale + 1))
#define PCA9685_NUMREGS 0xFF
#define PCA9685_MAXCHAN 0x10
-#define LED_FULL (1 << 4)
-#define MODE1_SLEEP (1 << 4)
-#define MODE2_INVRT (1 << 4)
-#define MODE2_OUTDRV (1 << 2)
+#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 MODE1_AI BIT(5)
+
+#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)))
+#define REG_ON_H(C) ((C) >= PCA9685_MAXCHAN ? PCA9685_ALL_LED_ON_H : LED_N_ON_H((C)))
+#define REG_ON_L(C) ((C) >= PCA9685_MAXCHAN ? PCA9685_ALL_LED_ON_L : LED_N_ON_L((C)))
+#define REG_OFF_H(C) ((C) >= PCA9685_MAXCHAN ? PCA9685_ALL_LED_OFF_H : LED_N_OFF_H((C)))
+#define REG_OFF_L(C) ((C) >= PCA9685_MAXCHAN ? PCA9685_ALL_LED_OFF_L : LED_N_OFF_L((C)))
+
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
+ DECLARE_BITMAP(pwms_enabled, PCA9685_MAXCHAN + 1);
};
static inline struct pca9685 *to_pca(struct pwm_chip *chip)
{
- return container_of(chip, struct pca9685, chip);
+ return pwmchip_get_drvdata(chip);
}
-#if IS_ENABLED(CONFIG_GPIOLIB)
-static bool pca9685_pwm_test_and_set_inuse(struct pca9685 *pca, int pwm_idx)
+/* This function is supposed to be called with the lock mutex held */
+static bool pca9685_prescaler_can_change(struct pca9685 *pca, int channel)
{
- 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;
+ /* No PWM enabled: Change allowed */
+ if (bitmap_empty(pca->pwms_enabled, PCA9685_MAXCHAN + 1))
+ return true;
+ /* More than one PWM enabled: Change not allowed */
+ if (bitmap_weight(pca->pwms_enabled, PCA9685_MAXCHAN + 1) > 1)
+ return false;
+ /*
+ * Only one PWM enabled: Change allowed if the PWM about to
+ * be changed is the one that is already enabled
+ */
+ return test_bit(channel, pca->pwms_enabled);
}
-static void pca9685_pwm_clear_inuse(struct pca9685 *pca, int pwm_idx)
+static int pca9685_read_reg(struct pwm_chip *chip, unsigned int reg, unsigned int *val)
{
- mutex_lock(&pca->lock);
- clear_bit(pwm_idx, pca->pwms_inuse);
- mutex_unlock(&pca->lock);
-}
+ struct pca9685 *pca = to_pca(chip);
+ struct device *dev = pwmchip_parent(chip);
+ int err;
-static int pca9685_pwm_gpio_request(struct gpio_chip *gpio, unsigned int offset)
-{
- struct pca9685 *pca = gpiochip_get_data(gpio);
+ err = regmap_read(pca->regmap, reg, val);
+ if (err)
+ dev_err(dev, "regmap_read of register 0x%x failed: %pe\n", reg, ERR_PTR(err));
- if (pca9685_pwm_test_and_set_inuse(pca, offset))
- return -EBUSY;
- pm_runtime_get_sync(pca->chip.dev);
- return 0;
+ return err;
}
-static int pca9685_pwm_gpio_get(struct gpio_chip *gpio, unsigned int offset)
+static int pca9685_write_reg(struct pwm_chip *chip, unsigned int reg, unsigned int val)
{
- struct pca9685 *pca = gpiochip_get_data(gpio);
- struct pwm_device *pwm = &pca->chip.pwms[offset];
- unsigned int value;
+ struct pca9685 *pca = to_pca(chip);
+ struct device *dev = pwmchip_parent(chip);
+ int err;
- regmap_read(pca->regmap, LED_N_ON_H(pwm->hwpwm), &value);
+ err = regmap_write(pca->regmap, reg, val);
+ if (err)
+ dev_err(dev, "regmap_write to register 0x%x failed: %pe\n", reg, ERR_PTR(err));
- return value & LED_FULL;
+ return err;
}
-static void pca9685_pwm_gpio_set(struct gpio_chip *gpio, unsigned int offset,
- int value)
+static int pca9685_write_4reg(struct pwm_chip *chip, unsigned int reg, u8 val[4])
{
- 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);
-}
+ struct pca9685 *pca = to_pca(chip);
+ struct device *dev = pwmchip_parent(chip);
+ int err;
-static void pca9685_pwm_gpio_free(struct gpio_chip *gpio, unsigned int offset)
-{
- struct pca9685 *pca = gpiochip_get_data(gpio);
+ err = regmap_bulk_write(pca->regmap, reg, val, 4);
+ if (err)
+ dev_err(dev, "regmap_write to register 0x%x failed: %pe\n", reg, ERR_PTR(err));
- pca9685_pwm_gpio_set(gpio, offset, 0);
- pm_runtime_put(pca->chip.dev);
- pca9685_pwm_clear_inuse(pca, offset);
+ return err;
}
-static int pca9685_pwm_gpio_get_direction(struct gpio_chip *chip,
- unsigned int offset)
+static int pca9685_set_sleep_mode(struct pwm_chip *chip, bool enable)
{
- /* Always out */
- return GPIO_LINE_DIRECTION_OUT;
-}
+ struct pca9685 *pca = to_pca(chip);
+ int err;
-static int pca9685_pwm_gpio_direction_input(struct gpio_chip *gpio,
- unsigned int offset)
-{
- return -EINVAL;
-}
+ err = regmap_update_bits(pca->regmap, PCA9685_MODE1,
+ MODE1_SLEEP, enable ? MODE1_SLEEP : 0);
+ if (err)
+ return err;
-static int pca9685_pwm_gpio_direction_output(struct gpio_chip *gpio,
- unsigned int offset, int value)
-{
- pca9685_pwm_gpio_set(gpio, offset, value);
+ if (!enable) {
+ /* Wait 500us for the oscillator to be back up */
+ udelay(500);
+ }
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);
+struct pca9685_waveform {
+ u8 onoff[4];
+ u8 prescale;
+};
- 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)
+static int pca9685_round_waveform_tohw(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_waveform *wf, void *_wfhw)
{
- return false;
-}
+ struct pca9685_waveform *wfhw = _wfhw;
+ struct pca9685 *pca = to_pca(chip);
+ unsigned int best_prescale;
+ u8 prescale;
+ unsigned int period_ns, duty;
+ int ret_tohw = 0;
-static inline void
-pca9685_pwm_clear_inuse(struct pca9685 *pca, int pwm_idx)
-{
-}
+ if (!wf->period_length_ns) {
+ *wfhw = (typeof(*wfhw)){
+ .onoff = { 0, 0, 0, LED_FULL, },
+ .prescale = 0,
+ };
-static inline int pca9685_pwm_gpio_probe(struct pca9685 *pca)
-{
- return 0;
-}
-#endif
+ dev_dbg(&chip->dev, "pwm#%u: %lld/%lld [+%lld] -> [%hhx %hhx %hhx %hhx] PSC:%hhx\n",
+ pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
+ wfhw->onoff[0], wfhw->onoff[1], wfhw->onoff[2], wfhw->onoff[3], wfhw->prescale);
-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);
+ return 0;
}
-}
-
-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);
+ if (wf->period_length_ns >= PCA9685_COUNTER_RANGE * PCA9685_QUANTUM_NS(255)) {
+ best_prescale = 255;
+ } else if (wf->period_length_ns < PCA9685_COUNTER_RANGE * PCA9685_QUANTUM_NS(3)) {
+ best_prescale = 3;
+ ret_tohw = 1;
+ } else {
+ best_prescale = (unsigned int)wf->period_length_ns / (PCA9685_COUNTER_RANGE * (NSEC_PER_SEC / PCA9685_OSC_CLOCK_HZ)) - 1;
+ }
- /* Wake the chip up */
- pca9685_set_sleep_mode(pca, false);
+ guard(mutex)(&pca->lock);
- pca->period_ns = period_ns;
- } else {
- dev_err(chip->dev,
- "prescaler not set: period out of bounds!\n");
- return -EINVAL;
- }
- }
+ if (!pca9685_prescaler_can_change(pca, pwm->hwpwm)) {
+ unsigned int current_prescale;
+ int ret;
- if (duty_ns < 1) {
- if (pwm->hwpwm >= PCA9685_MAXCHAN)
- reg = PCA9685_ALL_LED_OFF_H;
- else
- reg = LED_N_OFF_H(pwm->hwpwm);
+ ret = regmap_read(pca->regmap, PCA9685_PRESCALE, &current_prescale);
+ if (ret)
+ return ret;
- regmap_write(pca->regmap, reg, LED_FULL);
+ if (current_prescale > best_prescale)
+ ret_tohw = 1;
- return 0;
+ prescale = current_prescale;
+ } else {
+ prescale = best_prescale;
}
- 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);
+ period_ns = PCA9685_COUNTER_RANGE * PCA9685_QUANTUM_NS(prescale);
- if (pwm->hwpwm >= PCA9685_MAXCHAN)
- reg = PCA9685_ALL_LED_OFF_H;
- else
- reg = LED_N_OFF_H(pwm->hwpwm);
+ duty = (unsigned)min_t(u64, wf->duty_length_ns, period_ns) / PCA9685_QUANTUM_NS(prescale);
- regmap_write(pca->regmap, reg, 0x0);
+ if (duty < PCA9685_COUNTER_RANGE) {
+ unsigned int on, off;
- /* Set the full ON bit */
- if (pwm->hwpwm >= PCA9685_MAXCHAN)
- reg = PCA9685_ALL_LED_ON_H;
- else
- reg = LED_N_ON_H(pwm->hwpwm);
+ on = (unsigned)min_t(u64, wf->duty_offset_ns, period_ns) / PCA9685_QUANTUM_NS(prescale);
+ off = (on + duty) % PCA9685_COUNTER_RANGE;
- regmap_write(pca->regmap, reg, LED_FULL);
+ /*
+ * With a zero duty cycle, it doesn't matter if period was
+ * rounded up
+ */
+ if (!duty)
+ ret_tohw = 0;
- return 0;
+ *wfhw = (typeof(*wfhw)){
+ .onoff = { on & 0xff, (on >> 8) & 0xf, off & 0xff, (off >> 8) & 0xf },
+ .prescale = prescale,
+ };
+ } else {
+ *wfhw = (typeof(*wfhw)){
+ .onoff = { 0, LED_FULL, 0, 0, },
+ .prescale = prescale,
+ };
}
- duty = PCA9685_COUNTER_RANGE * (unsigned long long)duty_ns;
- duty = DIV_ROUND_UP_ULL(duty, period_ns);
+ dev_dbg(&chip->dev, "pwm#%u: %lld/%lld [+%lld] -> %s[%hhx %hhx %hhx %hhx] PSC:%hhx\n",
+ pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
+ ret_tohw ? "#" : "", wfhw->onoff[0], wfhw->onoff[1], wfhw->onoff[2], wfhw->onoff[3], wfhw->prescale);
- if (pwm->hwpwm >= PCA9685_MAXCHAN)
- reg = PCA9685_ALL_LED_OFF_L;
- else
- reg = LED_N_OFF_L(pwm->hwpwm);
+ return ret_tohw;
+}
- regmap_write(pca->regmap, reg, (int)duty & 0xff);
+static int pca9685_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
+ const void *_wfhw, struct pwm_waveform *wf)
+{
+ const struct pca9685_waveform *wfhw = _wfhw;
+ struct pca9685 *pca = to_pca(chip);
+ unsigned int prescale;
- if (pwm->hwpwm >= PCA9685_MAXCHAN)
- reg = PCA9685_ALL_LED_OFF_H;
+ if (wfhw->prescale)
+ prescale = wfhw->prescale;
else
- reg = LED_N_OFF_H(pwm->hwpwm);
+ scoped_guard(mutex, &pca->lock) {
+ int ret;
- regmap_write(pca->regmap, reg, ((int)duty >> 8) & 0xf);
+ ret = regmap_read(pca->regmap, PCA9685_PRESCALE, &prescale);
+ if (ret)
+ return ret;
+ }
- /* 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);
+ wf->period_length_ns = PCA9685_COUNTER_RANGE * PCA9685_QUANTUM_NS(prescale);
+
+ if (wfhw->onoff[3] & LED_FULL) {
+ wf->duty_length_ns = 0;
+ wf->duty_offset_ns = 0;
+ } else if (wfhw->onoff[1] & LED_FULL) {
+ wf->duty_length_ns = wf->period_length_ns;
+ wf->duty_offset_ns = 0;
+ } else {
+ unsigned int on = wfhw->onoff[0] | (wfhw->onoff[1] & 0xf) << 8;
+ unsigned int off = wfhw->onoff[2] | (wfhw->onoff[3] & 0xf) << 8;
- regmap_write(pca->regmap, reg, 0);
+ wf->duty_length_ns = (off - on) % PCA9685_COUNTER_RANGE * PCA9685_QUANTUM_NS(prescale);
+ wf->duty_offset_ns = on * PCA9685_QUANTUM_NS(prescale);
+ }
+
+ dev_dbg(&chip->dev, "pwm#%u: [%hhx %hhx %hhx %hhx] PSC:%hhx -> %lld/%lld [+%lld]\n",
+ pwm->hwpwm,
+ wfhw->onoff[0], wfhw->onoff[1], wfhw->onoff[2], wfhw->onoff[3], wfhw->prescale,
+ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns);
return 0;
}
-static int pca9685_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+static int pca9685_read_waveform(struct pwm_chip *chip, struct pwm_device *pwm, void *_wfhw)
{
+ struct pca9685_waveform *wfhw = _wfhw;
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);
+ unsigned int prescale;
+ int ret;
- if (pwm->hwpwm >= PCA9685_MAXCHAN)
- reg = PCA9685_ALL_LED_ON_H;
- else
- reg = LED_N_ON_H(pwm->hwpwm);
+ guard(mutex)(&pca->lock);
- regmap_write(pca->regmap, reg, 0);
+ ret = regmap_bulk_read(pca->regmap, REG_ON_L(pwm->hwpwm), &wfhw->onoff, 4);
+ if (ret)
+ return ret;
- /*
- * 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);
+ ret = regmap_read(pca->regmap, PCA9685_PRESCALE, &prescale);
+ if (ret)
+ return ret;
- regmap_update_bits(pca->regmap, reg, LED_FULL, 0x0);
+ wfhw->prescale = prescale;
return 0;
}
-static void pca9685_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+static int pca9685_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, const void *_wfhw)
{
+ const struct pca9685_waveform *wfhw = _wfhw;
struct pca9685 *pca = to_pca(chip);
- unsigned int reg;
+ unsigned int current_prescale;
+ int ret;
- if (pwm->hwpwm >= PCA9685_MAXCHAN)
- reg = PCA9685_ALL_LED_OFF_H;
- else
- reg = LED_N_OFF_H(pwm->hwpwm);
+ guard(mutex)(&pca->lock);
- regmap_write(pca->regmap, reg, LED_FULL);
+ if (wfhw->prescale) {
+ ret = regmap_read(pca->regmap, PCA9685_PRESCALE, &current_prescale);
+ if (ret)
+ return ret;
- /* Clear the LED_OFF counter. */
- if (pwm->hwpwm >= PCA9685_MAXCHAN)
- reg = PCA9685_ALL_LED_OFF_L;
- else
- reg = LED_N_OFF_L(pwm->hwpwm);
+ if (current_prescale != wfhw->prescale) {
+ if (!pca9685_prescaler_can_change(pca, pwm->hwpwm))
+ return -EBUSY;
+
+ /* Put chip into sleep mode */
+ ret = pca9685_set_sleep_mode(chip, true);
+ if (ret)
+ return ret;
+
+ /* Change the chip-wide output frequency */
+ ret = regmap_write(pca->regmap, PCA9685_PRESCALE, wfhw->prescale);
+ if (ret)
+ return ret;
- regmap_write(pca->regmap, reg, 0x0);
+ /* Wake the chip up */
+ ret = pca9685_set_sleep_mode(chip, false);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return regmap_bulk_write(pca->regmap, REG_ON_L(pwm->hwpwm), &wfhw->onoff, 4);
}
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);
+ if (pwm->hwpwm < PCA9685_MAXCHAN) {
+ /* PWMs - except the "all LEDs" channel - default to enabled */
+ mutex_lock(&pca->lock);
+ set_bit(pwm->hwpwm, pca->pwms_enabled);
+ mutex_unlock(&pca->lock);
+ }
+
+ pm_runtime_get_sync(pwmchip_parent(chip));
return 0;
}
@@ -418,37 +377,67 @@ 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);
+ mutex_lock(&pca->lock);
+ clear_bit(pwm->hwpwm, pca->pwms_enabled);
+ mutex_unlock(&pca->lock);
+
+ pm_runtime_put(pwmchip_parent(chip));
}
static const struct pwm_ops pca9685_pwm_ops = {
- .enable = pca9685_pwm_enable,
- .disable = pca9685_pwm_disable,
- .config = pca9685_pwm_config,
+ .sizeof_wfhw = sizeof(struct pca9685_waveform),
+ .round_waveform_tohw = pca9685_round_waveform_tohw,
+ .round_waveform_fromhw = pca9685_round_waveform_fromhw,
+ .read_waveform = pca9685_read_waveform,
+ .write_waveform = pca9685_write_waveform,
.request = pca9685_pwm_request,
.free = pca9685_pwm_free,
- .owner = THIS_MODULE,
};
+static bool pca9685_readable_reg(struct device *dev, unsigned int reg)
+{
+ /* The ALL_LED registers are readable but read as zero */
+ return reg <= REG_OFF_H(15) || reg >= PCA9685_PRESCALE;
+}
+
+static bool pca9685_writeable_reg(struct device *dev, unsigned int reg)
+{
+ return reg <= REG_OFF_H(15) || reg >= PCA9685_ALL_LED_ON_L;
+}
+
+static bool pca9685_volatile_reg(struct device *dev, unsigned int reg)
+{
+ /*
+ * Writing to an ALL_LED register affects all LEDi registers, so they
+ * are not cachable. :-\
+ */
+ return reg < PCA9685_PRESCALE;
+}
+
static const struct regmap_config pca9685_regmap_i2c_config = {
.reg_bits = 8,
.val_bits = 8,
+
+ .readable_reg = pca9685_readable_reg,
+ .writeable_reg = pca9685_writeable_reg,
+ .volatile_reg = pca9685_volatile_reg,
+
.max_register = PCA9685_NUMREGS,
- .cache_type = REGCACHE_NONE,
+ .cache_type = REGCACHE_MAPLE,
};
-static int pca9685_pwm_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int pca9685_pwm_probe(struct i2c_client *client)
{
+ struct pwm_chip *chip;
struct pca9685 *pca;
+ unsigned int reg;
int ret;
- int mode2;
- pca = devm_kzalloc(&client->dev, sizeof(*pca), GFP_KERNEL);
- if (!pca)
- return -ENOMEM;
+ /* Add an extra channel for ALL_LED */
+ chip = devm_pwmchip_alloc(&client->dev, PCA9685_MAXCHAN + 1, sizeof(*pca));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ pca = to_pca(chip);
pca->regmap = devm_regmap_init_i2c(client, &pca9685_regmap_i2c_config);
if (IS_ERR(pca->regmap)) {
@@ -457,107 +446,114 @@ static int pca9685_pwm_probe(struct i2c_client *client,
ret);
return ret;
}
- pca->period_ns = PCA9685_DEFAULT_PERIOD;
- i2c_set_clientdata(client, pca);
+ i2c_set_clientdata(client, chip);
+
+ mutex_init(&pca->lock);
- regmap_read(pca->regmap, PCA9685_MODE2, &mode2);
+ /* clear MODE2_OCH */
+ reg = 0;
if (device_property_read_bool(&client->dev, "invert"))
- mode2 |= MODE2_INVRT;
+ reg |= MODE2_INVRT;
else
- mode2 &= ~MODE2_INVRT;
+ reg &= ~MODE2_INVRT;
if (device_property_read_bool(&client->dev, "open-drain"))
- mode2 &= ~MODE2_OUTDRV;
+ reg &= ~MODE2_OUTDRV;
else
- mode2 |= MODE2_OUTDRV;
+ reg |= MODE2_OUTDRV;
- regmap_write(pca->regmap, PCA9685_MODE2, mode2);
+ ret = pca9685_write_reg(chip, PCA9685_MODE2, reg);
+ if (ret)
+ return ret;
- /* 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);
+ /*
+ * Disable all LED ALLCALL and SUBx addresses to avoid bus collisions,
+ * enable Auto-Increment.
+ */
+ pca9685_read_reg(chip, PCA9685_MODE1, &reg);
+ reg &= ~(MODE1_ALLCALL | MODE1_SUB1 | MODE1_SUB2 | MODE1_SUB3);
+ reg |= MODE1_AI;
+ pca9685_write_reg(chip, PCA9685_MODE1, reg);
- pca->chip.ops = &pca9685_pwm_ops;
- /* add an extra channel for ALL_LED */
- pca->chip.npwm = PCA9685_MAXCHAN + 1;
+ /* Reset OFF/ON registers to POR default */
+ ret = pca9685_write_4reg(chip, PCA9685_ALL_LED_ON_L, (u8[]){ 0, LED_FULL, 0, LED_FULL });
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret, "Failed to reset ON/OFF registers\n");
- pca->chip.dev = &client->dev;
- pca->chip.base = -1;
+ chip->ops = &pca9685_pwm_ops;
- ret = pwmchip_add(&pca->chip);
+ ret = pwmchip_add(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);
+ if (pm_runtime_enabled(&client->dev)) {
+ /*
+ * Although the chip comes out of power-up in the sleep state,
+ * we force it to sleep in case it was woken up before
+ */
+ pca9685_set_sleep_mode(chip, true);
+ pm_runtime_set_suspended(&client->dev);
+ } else {
+ /* Wake the chip up if runtime PM is disabled */
+ pca9685_set_sleep_mode(chip, false);
+ }
+
return 0;
}
-static int pca9685_pwm_remove(struct i2c_client *client)
+static void pca9685_pwm_remove(struct i2c_client *client)
{
- struct pca9685 *pca = i2c_get_clientdata(client);
- int ret;
+ struct pwm_chip *chip = i2c_get_clientdata(client);
+
+ pwmchip_remove(chip);
+
+ if (!pm_runtime_enabled(&client->dev)) {
+ /* Put chip in sleep state if runtime PM is disabled */
+ pca9685_set_sleep_mode(chip, true);
+ }
- 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);
+ struct pwm_chip *chip = i2c_get_clientdata(client);
- pca9685_set_sleep_mode(pca, true);
+ pca9685_set_sleep_mode(chip, 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);
+ struct pwm_chip *chip = i2c_get_clientdata(client);
- pca9685_set_sleep_mode(pca, false);
+ pca9685_set_sleep_mode(chip, false);
return 0;
}
static const struct i2c_device_id pca9685_id[] = {
- { "pca9685", 0 },
- { /* sentinel */ },
+ { "pca9685" },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, pca9685_id);
-#ifdef CONFIG_ACPI
static const struct acpi_device_id pca9685_acpi_ids[] = {
{ "INT3492", 0 },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(acpi, pca9685_acpi_ids);
-#endif
-#ifdef CONFIG_OF
static const struct of_device_id pca9685_dt_ids[] = {
{ .compatible = "nxp,pca9685-pwm", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, pca9685_dt_ids);
-#endif
static const struct dev_pm_ops pca9685_pwm_pm = {
SET_RUNTIME_PM_OPS(pca9685_pwm_runtime_suspend,
@@ -567,8 +563,8 @@ static const struct dev_pm_ops pca9685_pwm_pm = {
static struct i2c_driver pca9685_i2c_driver = {
.driver = {
.name = "pca9685-pwm",
- .acpi_match_table = ACPI_PTR(pca9685_acpi_ids),
- .of_match_table = of_match_ptr(pca9685_dt_ids),
+ .acpi_match_table = pca9685_acpi_ids,
+ .of_match_table = pca9685_dt_ids,
.pm = &pca9685_pwm_pm,
},
.probe = pca9685_pwm_probe,
diff --git a/drivers/pwm/pwm-puv3.c b/drivers/pwm/pwm-puv3.c
deleted file mode 100644
index 9d0bd87a425e..000000000000
--- a/drivers/pwm/pwm-puv3.c
+++ /dev/null
@@ -1,150 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * linux/arch/unicore32/kernel/pwm.c
- *
- * Code specific to PKUnity SoC and UniCore ISA
- *
- * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn>
- * Copyright (C) 2001-2010 Guan Xuetao
- */
-
-#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 <asm/div64.h>
-#include <mach/hardware.h>
-
-struct puv3_pwm_chip {
- struct pwm_chip chip;
- void __iomem *base;
- struct clk *clk;
-};
-
-static inline struct puv3_pwm_chip *to_puv3(struct pwm_chip *chip)
-{
- return container_of(chip, struct puv3_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 puv3_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
- int duty_ns, int period_ns)
-{
- unsigned long period_cycles, prescale, pv, dc;
- struct puv3_pwm_chip *puv3 = to_puv3(chip);
- unsigned long long c;
-
- c = clk_get_rate(puv3->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 = OST_PWMDCCR_FDCYCLE;
- else
- dc = (pv + 1) * duty_ns / period_ns;
-
- /*
- * NOTE: the clock to PWM has to be enabled first
- * before writing to the registers
- */
- clk_prepare_enable(puv3->clk);
-
- writel(prescale, puv3->base + OST_PWM_PWCR);
- writel(pv - dc, puv3->base + OST_PWM_DCCR);
- writel(pv, puv3->base + OST_PWM_PCR);
-
- clk_disable_unprepare(puv3->clk);
-
- return 0;
-}
-
-static int puv3_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
-{
- struct puv3_pwm_chip *puv3 = to_puv3(chip);
-
- return clk_prepare_enable(puv3->clk);
-}
-
-static void puv3_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
-{
- struct puv3_pwm_chip *puv3 = to_puv3(chip);
-
- clk_disable_unprepare(puv3->clk);
-}
-
-static const struct pwm_ops puv3_pwm_ops = {
- .config = puv3_pwm_config,
- .enable = puv3_pwm_enable,
- .disable = puv3_pwm_disable,
- .owner = THIS_MODULE,
-};
-
-static int pwm_probe(struct platform_device *pdev)
-{
- struct puv3_pwm_chip *puv3;
- struct resource *r;
- int ret;
-
- puv3 = devm_kzalloc(&pdev->dev, sizeof(*puv3), GFP_KERNEL);
- if (!puv3)
- return -ENOMEM;
-
- puv3->clk = devm_clk_get(&pdev->dev, "OST_CLK");
- if (IS_ERR(puv3->clk))
- return PTR_ERR(puv3->clk);
-
- r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- puv3->base = devm_ioremap_resource(&pdev->dev, r);
- if (IS_ERR(puv3->base))
- return PTR_ERR(puv3->base);
-
- puv3->chip.dev = &pdev->dev;
- puv3->chip.ops = &puv3_pwm_ops;
- puv3->chip.base = -1;
- puv3->chip.npwm = 1;
-
- ret = pwmchip_add(&puv3->chip);
- if (ret < 0) {
- dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
- return ret;
- }
-
- platform_set_drvdata(pdev, puv3);
- return 0;
-}
-
-static int pwm_remove(struct platform_device *pdev)
-{
- struct puv3_pwm_chip *puv3 = platform_get_drvdata(pdev);
-
- return pwmchip_remove(&puv3->chip);
-}
-
-static struct platform_driver puv3_pwm_driver = {
- .driver = {
- .name = "PKUnity-v3-PWM",
- },
- .probe = pwm_probe,
- .remove = pwm_remove,
-};
-module_platform_driver(puv3_pwm_driver);
-
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c
index a2a0912c2dcd..0f5bdb0e395e 100644
--- a/drivers/pwm/pwm-pxa.c
+++ b/drivers/pwm/pwm-pxa.c
@@ -6,8 +6,16 @@
*
* 2008-02-13 initial version
* eric miao <eric.miao@marvell.com>
+ *
+ * Links to reference manuals for some of the supported PWM chips can be found
+ * in Documentation/arch/arm/marvell.rst.
+ *
+ * Limitations:
+ * - When PWM is stopped, the current PWM period stops abruptly at the next
+ * input clock (PWMCR_SD is set) and the output is driven to inactive.
*/
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
@@ -16,7 +24,8 @@
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/pwm.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
+#include <linux/reset.h>
#include <asm/div64.h>
@@ -41,7 +50,6 @@ MODULE_DEVICE_TABLE(platform, pwm_id_table);
#define PWMDCR_FD (1 << 10)
struct pxa_pwm_chip {
- struct pwm_chip chip;
struct device *dev;
struct clk *clk;
@@ -50,7 +58,7 @@ struct pxa_pwm_chip {
static inline struct pxa_pwm_chip *to_pxa_pwm_chip(struct pwm_chip *chip)
{
- return container_of(chip, struct pxa_pwm_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
/*
@@ -58,13 +66,12 @@ static inline struct pxa_pwm_chip *to_pxa_pwm_chip(struct pwm_chip *chip)
* 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)
+ u64 duty_ns, u64 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;
@@ -84,42 +91,50 @@ static int pxa_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
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;
+ dc = mul_u64_u64_div_u64(pv + 1, duty_ns, period_ns);
- writel(prescale, pc->mmio_base + offset + PWMCR);
+ writel(prescale | PWMCR_SD, 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)
+static int pxa_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
{
struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip);
+ u64 duty_cycle;
+ int err;
- return clk_prepare_enable(pc->clk);
-}
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
-static void pxa_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
-{
- struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip);
+ err = clk_prepare_enable(pc->clk);
+ if (err)
+ return err;
+
+ duty_cycle = state->enabled ? state->duty_cycle : 0;
+
+ err = pxa_pwm_config(chip, pwm, duty_cycle, state->period);
+ if (err) {
+ clk_disable_unprepare(pc->clk);
+ return err;
+ }
+
+ if (state->enabled && !pwm->state.enabled)
+ return 0;
clk_disable_unprepare(pc->clk);
+
+ if (!state->enabled && pwm->state.enabled)
+ clk_disable_unprepare(pc->clk);
+
+ return 0;
}
static const struct pwm_ops pxa_pwm_ops = {
- .config = pxa_pwm_config,
- .enable = pxa_pwm_enable,
- .disable = pxa_pwm_disable,
- .owner = THIS_MODULE,
+ .apply = pxa_pwm_apply,
};
#ifdef CONFIG_OF
@@ -141,82 +156,49 @@ MODULE_DEVICE_TABLE(of, pwm_of_match);
#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;
+ struct pwm_chip *chip;
+ struct pxa_pwm_chip *pc;
+ struct device *dev = &pdev->dev;
+ struct reset_control *rst;
int ret = 0;
if (IS_ENABLED(CONFIG_OF) && id == NULL)
- id = pxa_pwm_get_id_dt(&pdev->dev);
+ id = of_device_get_match_data(dev);
if (id == NULL)
return -EINVAL;
- pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
- if (pwm == NULL)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(dev, (id->driver_data & HAS_SECONDARY_PWM) ? 2 : 1,
+ sizeof(*pc));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ pc = to_pxa_pwm_chip(chip);
- pwm->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(pwm->clk))
- return PTR_ERR(pwm->clk);
+ pc->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(pc->clk))
+ return dev_err_probe(dev, PTR_ERR(pc->clk), "Failed to get clock\n");
- 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;
+ rst = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL);
+ if (IS_ERR(rst))
+ return PTR_ERR(rst);
- 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;
- }
+ chip->ops = &pxa_pwm_ops;
- platform_set_drvdata(pdev, pwm);
- return 0;
-}
+ if (IS_ENABLED(CONFIG_OF))
+ chip->of_xlate = of_pwm_single_xlate;
-static int pwm_remove(struct platform_device *pdev)
-{
- struct pxa_pwm_chip *chip;
+ pc->mmio_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(pc->mmio_base))
+ return PTR_ERR(pc->mmio_base);
- chip = platform_get_drvdata(pdev);
- if (chip == NULL)
- return -ENODEV;
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "pwmchip_add() failed\n");
- return pwmchip_remove(&chip->chip);
+ return 0;
}
static struct platform_driver pwm_driver = {
@@ -225,10 +207,10 @@ static struct platform_driver pwm_driver = {
.of_match_table = pwm_of_match,
},
.probe = pwm_probe,
- .remove = pwm_remove,
.id_table = pwm_id_table,
};
module_platform_driver(pwm_driver);
+MODULE_DESCRIPTION("PXA Pulse Width Modulator driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/pwm/pwm-raspberrypi-poe.c b/drivers/pwm/pwm-raspberrypi-poe.c
new file mode 100644
index 000000000000..8921e7ea2cea
--- /dev/null
+++ b/drivers/pwm/pwm-raspberrypi-poe.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2021 Nicolas Saenz Julienne <nsaenzjulienne@suse.de>
+ * For more information on Raspberry Pi's PoE hat see:
+ * https://www.raspberrypi.org/products/poe-hat/
+ *
+ * Limitations:
+ * - No disable bit, so a disabled PWM is simulated by duty_cycle 0
+ * - Only normal polarity
+ * - Fixed 12.5 kHz period
+ *
+ * The current period is completed when HW is reconfigured.
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+#include <soc/bcm2835/raspberrypi-firmware.h>
+#include <dt-bindings/pwm/raspberrypi,firmware-poe-pwm.h>
+
+#define RPI_PWM_MAX_DUTY 255
+#define RPI_PWM_PERIOD_NS 80000 /* 12.5 kHz */
+
+#define RPI_PWM_CUR_DUTY_REG 0x0
+
+struct raspberrypi_pwm {
+ struct rpi_firmware *firmware;
+ unsigned int duty_cycle;
+};
+
+struct raspberrypi_pwm_prop {
+ __le32 reg;
+ __le32 val;
+ __le32 ret;
+} __packed;
+
+static inline
+struct raspberrypi_pwm *raspberrypi_pwm_from_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static int raspberrypi_pwm_set_property(struct rpi_firmware *firmware,
+ u32 reg, u32 val)
+{
+ struct raspberrypi_pwm_prop msg = {
+ .reg = cpu_to_le32(reg),
+ .val = cpu_to_le32(val),
+ };
+ int ret;
+
+ ret = rpi_firmware_property(firmware, RPI_FIRMWARE_SET_POE_HAT_VAL,
+ &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ if (msg.ret)
+ return -EIO;
+
+ return 0;
+}
+
+static int raspberrypi_pwm_get_property(struct rpi_firmware *firmware,
+ u32 reg, u32 *val)
+{
+ struct raspberrypi_pwm_prop msg = {
+ .reg = cpu_to_le32(reg),
+ };
+ int ret;
+
+ ret = rpi_firmware_property(firmware, RPI_FIRMWARE_GET_POE_HAT_VAL,
+ &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ if (msg.ret)
+ return -EIO;
+
+ *val = le32_to_cpu(msg.val);
+
+ return 0;
+}
+
+static int raspberrypi_pwm_get_state(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct raspberrypi_pwm *rpipwm = raspberrypi_pwm_from_chip(chip);
+
+ state->period = RPI_PWM_PERIOD_NS;
+ state->duty_cycle = DIV_ROUND_UP(rpipwm->duty_cycle * RPI_PWM_PERIOD_NS,
+ RPI_PWM_MAX_DUTY);
+ state->enabled = !!(rpipwm->duty_cycle);
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ return 0;
+}
+
+static int raspberrypi_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct raspberrypi_pwm *rpipwm = raspberrypi_pwm_from_chip(chip);
+ unsigned int duty_cycle;
+ int ret;
+
+ if (state->period < RPI_PWM_PERIOD_NS ||
+ state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (!state->enabled)
+ duty_cycle = 0;
+ else if (state->duty_cycle < RPI_PWM_PERIOD_NS)
+ duty_cycle = DIV_ROUND_DOWN_ULL(state->duty_cycle * RPI_PWM_MAX_DUTY,
+ RPI_PWM_PERIOD_NS);
+ else
+ duty_cycle = RPI_PWM_MAX_DUTY;
+
+ if (duty_cycle == rpipwm->duty_cycle)
+ return 0;
+
+ ret = raspberrypi_pwm_set_property(rpipwm->firmware, RPI_PWM_CUR_DUTY_REG,
+ duty_cycle);
+ if (ret) {
+ dev_err(pwmchip_parent(chip), "Failed to set duty cycle: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ rpipwm->duty_cycle = duty_cycle;
+
+ return 0;
+}
+
+static const struct pwm_ops raspberrypi_pwm_ops = {
+ .get_state = raspberrypi_pwm_get_state,
+ .apply = raspberrypi_pwm_apply,
+};
+
+static int raspberrypi_pwm_probe(struct platform_device *pdev)
+{
+ struct device_node *firmware_node;
+ struct device *dev = &pdev->dev;
+ struct rpi_firmware *firmware;
+ struct pwm_chip *chip;
+ struct raspberrypi_pwm *rpipwm;
+ int ret;
+
+ firmware_node = of_get_parent(dev->of_node);
+ if (!firmware_node) {
+ dev_err(dev, "Missing firmware node\n");
+ return -ENOENT;
+ }
+
+ firmware = devm_rpi_firmware_get(&pdev->dev, firmware_node);
+ of_node_put(firmware_node);
+ if (!firmware)
+ return dev_err_probe(dev, -EPROBE_DEFER,
+ "Failed to get firmware handle\n");
+
+ chip = devm_pwmchip_alloc(&pdev->dev, RASPBERRYPI_FIRMWARE_PWM_NUM,
+ sizeof(*rpipwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ rpipwm = raspberrypi_pwm_from_chip(chip);
+
+ rpipwm->firmware = firmware;
+ chip->ops = &raspberrypi_pwm_ops;
+
+ ret = raspberrypi_pwm_get_property(rpipwm->firmware, RPI_PWM_CUR_DUTY_REG,
+ &rpipwm->duty_cycle);
+ if (ret) {
+ dev_err(dev, "Failed to get duty cycle: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ return devm_pwmchip_add(dev, chip);
+}
+
+static const struct of_device_id raspberrypi_pwm_of_match[] = {
+ { .compatible = "raspberrypi,firmware-poe-pwm", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, raspberrypi_pwm_of_match);
+
+static struct platform_driver raspberrypi_pwm_driver = {
+ .driver = {
+ .name = "raspberrypi-poe-pwm",
+ .of_match_table = raspberrypi_pwm_of_match,
+ },
+ .probe = raspberrypi_pwm_probe,
+};
+module_platform_driver(raspberrypi_pwm_driver);
+
+MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>");
+MODULE_DESCRIPTION("Raspberry Pi Firmware Based PWM Bus Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/pwm/pwm-rcar.c b/drivers/pwm/pwm-rcar.c
index 7ab9eb6616d9..578dbdd2d5a7 100644
--- a/drivers/pwm/pwm-rcar.c
+++ b/drivers/pwm/pwm-rcar.c
@@ -8,6 +8,7 @@
* - The hardware cannot generate a 0% duty cycle.
*/
+#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
@@ -38,14 +39,13 @@
#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);
+ return pwmchip_get_drvdata(chip);
}
static void rcar_pwm_write(struct rcar_pwm_chip *rp, u32 data,
@@ -103,23 +103,24 @@ static void rcar_pwm_set_clock_control(struct rcar_pwm_chip *rp,
rcar_pwm_write(rp, value, RCAR_PWMCR);
}
-static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, int duty_ns,
- int period_ns)
+static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, u64 duty_ns,
+ u64 period_ns)
{
- unsigned long long one_cycle, tmp; /* 0.01 nanoseconds */
+ unsigned long long tmp;
unsigned long clk_rate = clk_get_rate(rp->clk);
u32 cyc, ph;
- one_cycle = (unsigned long long)NSEC_PER_SEC * 100ULL * (1 << div);
- do_div(one_cycle, clk_rate);
+ /* div <= 24 == RCAR_PWM_MAX_DIVISION, so the shift doesn't overflow. */
+ tmp = mul_u64_u64_div_u64(period_ns, clk_rate, (u64)NSEC_PER_SEC << div);
+ if (tmp > FIELD_MAX(RCAR_PWMCNT_CYC0_MASK))
+ tmp = FIELD_MAX(RCAR_PWMCNT_CYC0_MASK);
- tmp = period_ns * 100ULL;
- do_div(tmp, one_cycle);
- cyc = (tmp << RCAR_PWMCNT_CYC0_SHIFT) & RCAR_PWMCNT_CYC0_MASK;
+ cyc = FIELD_PREP(RCAR_PWMCNT_CYC0_MASK, tmp);
- tmp = duty_ns * 100ULL;
- do_div(tmp, one_cycle);
- ph = tmp & RCAR_PWMCNT_PH0_MASK;
+ tmp = mul_u64_u64_div_u64(duty_ns, clk_rate, (u64)NSEC_PER_SEC << div);
+ if (tmp > FIELD_MAX(RCAR_PWMCNT_PH0_MASK))
+ tmp = FIELD_MAX(RCAR_PWMCNT_PH0_MASK);
+ ph = FIELD_PREP(RCAR_PWMCNT_PH0_MASK, tmp);
/* Avoid prohibited setting */
if (cyc == 0 || ph == 0)
@@ -132,12 +133,12 @@ static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, int duty_ns,
static int rcar_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
- return pm_runtime_get_sync(chip->dev);
+ return pm_runtime_get_sync(pwmchip_parent(chip));
}
static void rcar_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
- pm_runtime_put(chip->dev);
+ pm_runtime_put(pwmchip_parent(chip));
}
static int rcar_pwm_enable(struct rcar_pwm_chip *rp)
@@ -168,7 +169,7 @@ static int rcar_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
/* This HW/driver only supports normal polarity */
if (state->polarity != PWM_POLARITY_NORMAL)
- return -ENOTSUPP;
+ return -EINVAL;
if (!state->enabled) {
rcar_pwm_disable(rp);
@@ -198,21 +199,20 @@ 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 pwm_chip *chip;
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;
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*rcar_pwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ rcar_pwm = to_rcar_pwm_chip(chip);
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- rcar_pwm->base = devm_ioremap_resource(&pdev->dev, res);
+ rcar_pwm->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(rcar_pwm->base))
return PTR_ERR(rcar_pwm->base);
@@ -222,16 +222,13 @@ static int rcar_pwm_probe(struct platform_device *pdev)
return PTR_ERR(rcar_pwm->clk);
}
- platform_set_drvdata(pdev, rcar_pwm);
+ chip->ops = &rcar_pwm_ops;
- rcar_pwm->chip.dev = &pdev->dev;
- rcar_pwm->chip.ops = &rcar_pwm_ops;
- rcar_pwm->chip.base = -1;
- rcar_pwm->chip.npwm = 1;
+ platform_set_drvdata(pdev, chip);
pm_runtime_enable(&pdev->dev);
- ret = pwmchip_add(&rcar_pwm->chip);
+ ret = pwmchip_add(chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register PWM chip: %d\n", ret);
pm_runtime_disable(&pdev->dev);
@@ -241,16 +238,13 @@ static int rcar_pwm_probe(struct platform_device *pdev)
return 0;
}
-static int rcar_pwm_remove(struct platform_device *pdev)
+static void rcar_pwm_remove(struct platform_device *pdev)
{
- struct rcar_pwm_chip *rcar_pwm = platform_get_drvdata(pdev);
- int ret;
+ struct pwm_chip *chip = platform_get_drvdata(pdev);
- ret = pwmchip_remove(&rcar_pwm->chip);
+ pwmchip_remove(chip);
pm_runtime_disable(&pdev->dev);
-
- return ret;
}
static const struct of_device_id rcar_pwm_of_table[] = {
@@ -264,7 +258,7 @@ static struct platform_driver rcar_pwm_driver = {
.remove = rcar_pwm_remove,
.driver = {
.name = "pwm-rcar",
- .of_match_table = of_match_ptr(rcar_pwm_of_table),
+ .of_match_table = rcar_pwm_of_table,
}
};
module_platform_driver(rcar_pwm_driver);
diff --git a/drivers/pwm/pwm-renesas-tpu.c b/drivers/pwm/pwm-renesas-tpu.c
index 81ad5a551455..2196080b4177 100644
--- a/drivers/pwm/pwm-renesas-tpu.c
+++ b/drivers/pwm/pwm-renesas-tpu.c
@@ -11,7 +11,6 @@
#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>
@@ -80,80 +79,83 @@ struct tpu_pwm_device {
struct tpu_device {
struct platform_device *pdev;
- struct pwm_chip chip;
spinlock_t lock;
void __iomem *base;
struct clk *clk;
+ struct tpu_pwm_device tpd[TPU_CHANNEL_MAX];
};
-#define to_tpu_device(c) container_of(c, struct tpu_device, chip)
+static inline struct tpu_device *to_tpu_device(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
-static void tpu_pwm_write(struct tpu_pwm_device *pwm, int reg_nr, u16 value)
+static void tpu_pwm_write(struct tpu_pwm_device *tpd, int reg_nr, u16 value)
{
- void __iomem *base = pwm->tpu->base + TPU_CHANNEL_OFFSET
- + pwm->channel * TPU_CHANNEL_SIZE;
+ void __iomem *base = tpd->tpu->base + TPU_CHANNEL_OFFSET
+ + tpd->channel * TPU_CHANNEL_SIZE;
iowrite16(value, base + reg_nr);
}
-static void tpu_pwm_set_pin(struct tpu_pwm_device *pwm,
+static void tpu_pwm_set_pin(struct tpu_pwm_device *tpd,
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]);
+ dev_dbg(&tpd->tpu->pdev->dev, "%u: configuring pin as %s\n",
+ tpd->channel, states[state]);
switch (state) {
case TPU_PIN_INACTIVE:
- tpu_pwm_write(pwm, TPU_TIORn,
- pwm->polarity == PWM_POLARITY_INVERSED ?
+ tpu_pwm_write(tpd, TPU_TIORn,
+ tpd->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_pwm_write(tpd, TPU_TIORn,
+ tpd->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_pwm_write(tpd, TPU_TIORn,
+ tpd->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)
+static void tpu_pwm_start_stop(struct tpu_pwm_device *tpd, int start)
{
unsigned long flags;
u16 value;
- spin_lock_irqsave(&pwm->tpu->lock, flags);
- value = ioread16(pwm->tpu->base + TPU_TSTR);
+ spin_lock_irqsave(&tpd->tpu->lock, flags);
+ value = ioread16(tpd->tpu->base + TPU_TSTR);
if (start)
- value |= 1 << pwm->channel;
+ value |= 1 << tpd->channel;
else
- value &= ~(1 << pwm->channel);
+ value &= ~(1 << tpd->channel);
- iowrite16(value, pwm->tpu->base + TPU_TSTR);
- spin_unlock_irqrestore(&pwm->tpu->lock, flags);
+ iowrite16(value, tpd->tpu->base + TPU_TSTR);
+ spin_unlock_irqrestore(&tpd->tpu->lock, flags);
}
-static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm)
+static int tpu_pwm_timer_start(struct tpu_pwm_device *tpd)
{
int ret;
- if (!pwm->timer_on) {
+ if (!tpd->timer_on) {
/* Wake up device and enable clock. */
- pm_runtime_get_sync(&pwm->tpu->pdev->dev);
- ret = clk_prepare_enable(pwm->tpu->clk);
+ pm_runtime_get_sync(&tpd->tpu->pdev->dev);
+ ret = clk_prepare_enable(tpd->tpu->clk);
if (ret) {
- dev_err(&pwm->tpu->pdev->dev, "cannot enable clock\n");
+ dev_err(&tpd->tpu->pdev->dev, "cannot enable clock\n");
return ret;
}
- pwm->timer_on = true;
+ tpd->timer_on = true;
}
/*
@@ -161,8 +163,8 @@ static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm)
* 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);
+ tpu_pwm_set_pin(tpd, TPU_PIN_INACTIVE);
+ tpu_pwm_start_stop(tpd, false);
/*
* - Clear TCNT on TGRB match
@@ -172,142 +174,164 @@ static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm)
* - 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);
+ tpu_pwm_write(tpd, TPU_TCRn, TPU_TCR_CCLR_TGRB | TPU_TCR_CKEG_RISING |
+ tpd->prescaler);
+ tpu_pwm_write(tpd, TPU_TMDRn, TPU_TMDR_MD_PWM);
+ tpu_pwm_set_pin(tpd, TPU_PIN_PWM);
+ tpu_pwm_write(tpd, TPU_TGRAn, tpd->duty);
+ tpu_pwm_write(tpd, TPU_TGRBn, tpd->period);
- dev_dbg(&pwm->tpu->pdev->dev, "%u: TGRA 0x%04x TGRB 0x%04x\n",
- pwm->channel, pwm->duty, pwm->period);
+ dev_dbg(&tpd->tpu->pdev->dev, "%u: TGRA 0x%04x TGRB 0x%04x\n",
+ tpd->channel, tpd->duty, tpd->period);
/* Start the channel. */
- tpu_pwm_start_stop(pwm, true);
+ tpu_pwm_start_stop(tpd, true);
return 0;
}
-static void tpu_pwm_timer_stop(struct tpu_pwm_device *pwm)
+static void tpu_pwm_timer_stop(struct tpu_pwm_device *tpd)
{
- if (!pwm->timer_on)
+ if (!tpd->timer_on)
return;
/* Disable channel. */
- tpu_pwm_start_stop(pwm, false);
+ tpu_pwm_start_stop(tpd, false);
/* Stop clock and mark device as idle. */
- clk_disable_unprepare(pwm->tpu->clk);
- pm_runtime_put(&pwm->tpu->pdev->dev);
+ clk_disable_unprepare(tpd->tpu->clk);
+ pm_runtime_put(&tpd->tpu->pdev->dev);
- pwm->timer_on = false;
+ tpd->timer_on = false;
}
/* -----------------------------------------------------------------------------
* PWM API
*/
-static int tpu_pwm_request(struct pwm_chip *chip, struct pwm_device *_pwm)
+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;
+ struct tpu_pwm_device *tpd;
- if (_pwm->hwpwm >= TPU_CHANNEL_MAX)
+ if (pwm->hwpwm >= TPU_CHANNEL_MAX)
return -EINVAL;
- pwm = kzalloc(sizeof(*pwm), GFP_KERNEL);
- if (pwm == NULL)
- return -ENOMEM;
+ tpd = &tpu->tpd[pwm->hwpwm];
- pwm->tpu = tpu;
- pwm->channel = _pwm->hwpwm;
- pwm->polarity = PWM_POLARITY_NORMAL;
- pwm->prescaler = 0;
- pwm->period = 0;
- pwm->duty = 0;
+ tpd->tpu = tpu;
+ tpd->channel = pwm->hwpwm;
+ tpd->polarity = PWM_POLARITY_NORMAL;
+ tpd->prescaler = 0;
+ tpd->period = 0;
+ tpd->duty = 0;
- pwm->timer_on = false;
-
- pwm_set_chip_data(_pwm, pwm);
+ tpd->timer_on = false;
return 0;
}
-static void tpu_pwm_free(struct pwm_chip *chip, struct pwm_device *_pwm)
+static void tpu_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
- struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
+ struct tpu_device *tpu = to_tpu_device(chip);
+ struct tpu_pwm_device *tpd = &tpu->tpd[pwm->hwpwm];
- tpu_pwm_timer_stop(pwm);
- kfree(pwm);
+ tpu_pwm_timer_stop(tpd);
}
-static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *_pwm,
- int duty_ns, int period_ns)
+static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+ u64 duty_ns, u64 period_ns, bool enabled)
{
- 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);
+ struct tpu_pwm_device *tpd = &tpu->tpd[pwm->hwpwm];
unsigned int prescaler;
bool duty_only = false;
u32 clk_rate;
- u32 period;
+ u64 period;
u32 duty;
int ret;
+ clk_rate = clk_get_rate(tpu->clk);
+ if (unlikely(clk_rate > NSEC_PER_SEC)) {
+ /*
+ * This won't happen in the nearer future, so this is only a
+ * safeguard to prevent the following calculation from
+ * overflowing. With this clk_rate * period_ns / NSEC_PER_SEC is
+ * not greater than period_ns and so fits into an u64.
+ */
+ return -EINVAL;
+ }
+
+ period = mul_u64_u64_div_u64(clk_rate, period_ns, NSEC_PER_SEC);
+
/*
- * Pick a prescaler to avoid overflowing the counter.
- * TODO: Pick the highest acceptable prescaler.
+ * Find the minimal prescaler in [0..3] such that
+ *
+ * period >> (2 * prescaler) < 0x10000
+ *
+ * This could be calculated using something like:
+ *
+ * prescaler = max(ilog2(period) / 2, 7) - 7;
+ *
+ * but given there are only four allowed results and that ilog2 isn't
+ * cheap on all platforms using a switch statement is more effective.
*/
- clk_rate = clk_get_rate(tpu->clk);
+ switch (period) {
+ case 1 ... 0xffff:
+ prescaler = 0;
+ break;
- for (prescaler = 0; prescaler < ARRAY_SIZE(prescalers); ++prescaler) {
- period = clk_rate / prescalers[prescaler]
- / (NSEC_PER_SEC / period_ns);
- if (period <= 0xffff)
- break;
- }
+ case 0x10000 ... 0x3ffff:
+ prescaler = 1;
+ break;
+
+ case 0x40000 ... 0xfffff:
+ prescaler = 2;
+ break;
- if (prescaler == ARRAY_SIZE(prescalers) || period == 0) {
- dev_err(&tpu->pdev->dev, "clock rate mismatch\n");
- return -ENOTSUPP;
+ case 0x100000 ... 0x3fffff:
+ prescaler = 3;
+ break;
+
+ default:
+ return -EINVAL;
}
- if (duty_ns) {
- duty = clk_rate / prescalers[prescaler]
- / (NSEC_PER_SEC / duty_ns);
- if (duty > period)
- return -EINVAL;
- } else {
+ period >>= 2 * prescaler;
+
+ if (duty_ns)
+ duty = mul_u64_u64_div_u64(clk_rate, duty_ns,
+ (u64)NSEC_PER_SEC << (2 * prescaler));
+ else
duty = 0;
- }
dev_dbg(&tpu->pdev->dev,
"rate %u, prescaler %u, period %u, duty %u\n",
- clk_rate, prescalers[prescaler], period, duty);
+ clk_rate, 1 << (2 * prescaler), (u32)period, duty);
- if (pwm->prescaler == prescaler && pwm->period == period)
+ if (tpd->prescaler == prescaler && tpd->period == period)
duty_only = true;
- pwm->prescaler = prescaler;
- pwm->period = period;
- pwm->duty = duty;
+ tpd->prescaler = prescaler;
+ tpd->period = period;
+ tpd->duty = duty;
/* If the channel is disabled we're done. */
- if (!pwm_is_enabled(_pwm))
+ if (!enabled)
return 0;
- if (duty_only && pwm->timer_on) {
+ if (duty_only && tpd->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);
+ tpu_pwm_write(tpd, TPU_TGRAn, tpd->duty);
+ dev_dbg(&tpu->pdev->dev, "%u: TGRA 0x%04x\n", tpd->channel,
+ tpd->duty);
} else {
/* Otherwise perform a full reconfiguration. */
- ret = tpu_pwm_timer_start(pwm);
+ ret = tpu_pwm_timer_start(tpd);
if (ret < 0)
return ret;
}
@@ -317,29 +341,31 @@ static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *_pwm,
* 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);
+ tpu_pwm_set_pin(tpd, duty ? TPU_PIN_ACTIVE : TPU_PIN_INACTIVE);
+ tpu_pwm_timer_stop(tpd);
}
return 0;
}
-static int tpu_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *_pwm,
+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);
+ struct tpu_device *tpu = to_tpu_device(chip);
+ struct tpu_pwm_device *tpd = &tpu->tpd[pwm->hwpwm];
- pwm->polarity = polarity;
+ tpd->polarity = polarity;
return 0;
}
-static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *_pwm)
+static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
- struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
+ struct tpu_device *tpu = to_tpu_device(chip);
+ struct tpu_pwm_device *tpd = &tpu->tpd[pwm->hwpwm];
int ret;
- ret = tpu_pwm_timer_start(pwm);
+ ret = tpu_pwm_timer_start(tpd);
if (ret < 0)
return ret;
@@ -347,33 +373,65 @@ static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *_pwm)
* 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 ?
+ if (tpd->duty == 0 || tpd->duty == tpd->period) {
+ tpu_pwm_set_pin(tpd, tpd->duty ?
TPU_PIN_ACTIVE : TPU_PIN_INACTIVE);
- tpu_pwm_timer_stop(pwm);
+ tpu_pwm_timer_stop(tpd);
}
return 0;
}
-static void tpu_pwm_disable(struct pwm_chip *chip, struct pwm_device *_pwm)
+static void tpu_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
- struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
+ struct tpu_device *tpu = to_tpu_device(chip);
+ struct tpu_pwm_device *tpd = &tpu->tpd[pwm->hwpwm];
/* 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);
+ tpu_pwm_timer_start(tpd);
+ tpu_pwm_set_pin(tpd, TPU_PIN_INACTIVE);
+ tpu_pwm_timer_stop(tpd);
+}
+
+static int tpu_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+ bool enabled = pwm->state.enabled;
+
+ if (state->polarity != pwm->state.polarity) {
+ if (enabled) {
+ tpu_pwm_disable(chip, pwm);
+ enabled = false;
+ }
+
+ err = tpu_pwm_set_polarity(chip, pwm, state->polarity);
+ if (err)
+ return err;
+ }
+
+ if (!state->enabled) {
+ if (enabled)
+ tpu_pwm_disable(chip, pwm);
+
+ return 0;
+ }
+
+ err = tpu_pwm_config(chip, pwm,
+ state->duty_cycle, state->period, enabled);
+ if (err)
+ return err;
+
+ if (!enabled)
+ err = tpu_pwm_enable(chip, pwm);
+
+ return err;
}
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,
+ .apply = tpu_pwm_apply,
};
/* -----------------------------------------------------------------------------
@@ -382,63 +440,43 @@ static const struct pwm_ops tpu_pwm_ops = {
static int tpu_probe(struct platform_device *pdev)
{
+ struct pwm_chip *chip;
struct tpu_device *tpu;
- struct resource *res;
int ret;
- tpu = devm_kzalloc(&pdev->dev, sizeof(*tpu), GFP_KERNEL);
- if (tpu == NULL)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, TPU_CHANNEL_MAX, sizeof(*tpu));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ tpu = to_tpu_device(chip);
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);
+ tpu->base = devm_platform_ioremap_resource(pdev, 0);
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);
- }
+ if (IS_ERR(tpu->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(tpu->clk), "Failed to get clock\n");
/* 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;
+ chip->ops = &tpu_pwm_ops;
- pm_runtime_enable(&pdev->dev);
+ ret = devm_pm_runtime_enable(&pdev->dev);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "Failed to enable runtime PM\n");
- 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;
- }
+ ret = devm_pwmchip_add(&pdev->dev, chip);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "Failed to register PWM chip\n");
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", },
@@ -453,7 +491,6 @@ MODULE_DEVICE_TABLE(of, tpu_of_table);
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),
diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c
index eb8c9cb645a6..67b85bdb491b 100644
--- a/drivers/pwm/pwm-rockchip.c
+++ b/drivers/pwm/pwm-rockchip.c
@@ -8,10 +8,12 @@
#include <linux/clk.h>
#include <linux/io.h>
+#include <linux/limits.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/property.h>
#include <linux/pwm.h>
#include <linux/time.h>
@@ -30,7 +32,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;
@@ -52,16 +53,17 @@ struct rockchip_pwm_data {
u32 enable_conf;
};
-static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c)
+static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *chip)
{
- return container_of(c, struct rockchip_pwm_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
-static void rockchip_pwm_get_state(struct pwm_chip *chip,
- struct pwm_device *pwm,
- struct pwm_state *state)
+static int 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);
+ u64 prescaled_ns = (u64)pc->data->prescaler * NSEC_PER_SEC;
u32 enable_conf = pc->data->enable_conf;
unsigned long clk_rate;
u64 tmp;
@@ -70,17 +72,21 @@ static void rockchip_pwm_get_state(struct pwm_chip *chip,
ret = clk_enable(pc->pclk);
if (ret)
- return;
+ return ret;
+
+ ret = clk_enable(pc->clk);
+ if (ret)
+ return ret;
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 *= prescaled_ns;
+ state->period = DIV_U64_ROUND_UP(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);
+ tmp *= prescaled_ns;
+ state->duty_cycle = DIV_U64_ROUND_UP(tmp, clk_rate);
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
state->enabled = (val & enable_conf) == enable_conf;
@@ -90,15 +96,19 @@ static void rockchip_pwm_get_state(struct pwm_chip *chip,
else
state->polarity = PWM_POLARITY_NORMAL;
+ clk_disable(pc->clk);
clk_disable(pc->pclk);
+
+ return 0;
}
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;
+ u64 prescaled_ns = (u64)pc->data->prescaler * NSEC_PER_SEC;
+ u64 clk_rate, tmp;
+ u32 period_ticks, duty_ticks;
u32 ctrl;
clk_rate = clk_get_rate(pc->clk);
@@ -108,12 +118,15 @@ static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* 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);
+ tmp = mul_u64_u64_div_u64(clk_rate, state->period, prescaled_ns);
+ if (tmp > U32_MAX)
+ tmp = U32_MAX;
+ period_ticks = tmp;
- div = clk_rate * state->duty_cycle;
- duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC);
+ tmp = mul_u64_u64_div_u64(clk_rate, state->duty_cycle, prescaled_ns);
+ if (tmp > U32_MAX)
+ tmp = U32_MAX;
+ duty_ticks = tmp;
/*
* Lock the period and duty of previous configuration, then
@@ -125,8 +138,8 @@ static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+ writel(period_ticks, pc->base + pc->data->regs.period);
+ writel(duty_ticks, pc->base + pc->data->regs.duty);
if (pc->data->supports_polarity) {
ctrl &= ~PWM_POLARITY_MASK;
@@ -189,6 +202,10 @@ static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
if (ret)
return ret;
+ ret = clk_enable(pc->clk);
+ if (ret)
+ return ret;
+
pwm_get_state(pwm, &curstate);
enabled = curstate.enabled;
@@ -208,6 +225,7 @@ static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
}
out:
+ clk_disable(pc->clk);
clk_disable(pc->pclk);
return ret;
@@ -216,7 +234,6 @@ out:
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 = {
@@ -285,34 +302,27 @@ MODULE_DEVICE_TABLE(of, rockchip_pwm_dt_ids);
static int rockchip_pwm_probe(struct platform_device *pdev)
{
- const struct of_device_id *id;
+ struct pwm_chip *chip;
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;
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*pc));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ pc = to_rockchip_pwm_chip(chip);
- 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);
+ pc->base = devm_platform_ioremap_resource(pdev, 0);
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)) {
- ret = PTR_ERR(pc->clk);
- if (ret != -EPROBE_DEFER)
- dev_err(&pdev->dev, "Can't get bus clk: %d\n",
- ret);
- return ret;
- }
+ if (IS_ERR(pc->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk),
+ "Can't get PWM clk\n");
}
count = of_count_phandle_with_args(pdev->dev.of_node,
@@ -322,81 +332,59 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
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;
- }
+ if (IS_ERR(pc->pclk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(pc->pclk), "Can't get APB clk\n");
ret = clk_prepare_enable(pc->clk);
- if (ret) {
- dev_err(&pdev->dev, "Can't prepare enable bus clk: %d\n", ret);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Can't prepare enable PWM clk\n");
- ret = clk_prepare(pc->pclk);
+ ret = clk_prepare_enable(pc->pclk);
if (ret) {
- dev_err(&pdev->dev, "Can't prepare APB clk: %d\n", ret);
+ dev_err_probe(&pdev->dev, ret, "Can't prepare enable APB clk\n");
goto err_clk;
}
- platform_set_drvdata(pdev, pc);
+ platform_set_drvdata(pdev, chip);
- pc->data = id->data;
- pc->chip.dev = &pdev->dev;
- pc->chip.ops = &rockchip_pwm_ops;
- pc->chip.base = -1;
- pc->chip.npwm = 1;
+ pc->data = device_get_match_data(&pdev->dev);
+ chip->ops = &rockchip_pwm_ops;
- 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);
+ ret = pwmchip_add(chip);
if (ret < 0) {
- clk_unprepare(pc->clk);
- dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
+ dev_err_probe(&pdev->dev, ret, "pwmchip_add() failed\n");
goto err_pclk;
}
/* Keep the PWM clk enabled if the PWM appears to be up and running. */
- if (!pwm_is_enabled(pc->chip.pwms))
+ if (!enabled)
clk_disable(pc->clk);
+ clk_disable(pc->pclk);
+
return 0;
err_pclk:
- clk_unprepare(pc->pclk);
+ clk_disable_unprepare(pc->pclk);
err_clk:
clk_disable_unprepare(pc->clk);
return ret;
}
-static int rockchip_pwm_remove(struct platform_device *pdev)
+static void rockchip_pwm_remove(struct platform_device *pdev)
{
- struct rockchip_pwm_chip *pc = platform_get_drvdata(pdev);
+ struct pwm_chip *chip = platform_get_drvdata(pdev);
+ struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
- /*
- * Disable the PWM clk before unpreparing it if the PWM device is still
- * running. This should only happen when the last PWM user left it
- * enabled, or when nobody requested a PWM that was previously enabled
- * by the bootloader.
- *
- * FIXME: Maybe the core should disable all PWM devices in
- * pwmchip_remove(). In this case we'd only have to call
- * clk_unprepare() after pwmchip_remove().
- *
- */
- if (pwm_is_enabled(pc->chip.pwms))
- clk_disable(pc->clk);
+ pwmchip_remove(chip);
clk_unprepare(pc->pclk);
clk_unprepare(pc->clk);
-
- return pwmchip_remove(&pc->chip);
}
static struct platform_driver rockchip_pwm_driver = {
diff --git a/drivers/pwm/pwm-rz-mtu3.c b/drivers/pwm/pwm-rz-mtu3.c
new file mode 100644
index 000000000000..ab39bd37edaf
--- /dev/null
+++ b/drivers/pwm/pwm-rz-mtu3.c
@@ -0,0 +1,552 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/G2L MTU3a PWM Timer driver
+ *
+ * Copyright (C) 2023 Renesas Electronics Corporation
+ *
+ * Hardware manual for this IP can be found here
+ * https://www.renesas.com/eu/en/document/mah/rzg2l-group-rzg2lc-group-users-manual-hardware-0?language=en
+ *
+ * Limitations:
+ * - When PWM is disabled, the output is driven to Hi-Z.
+ * - While the hardware supports both polarities, the driver (for now)
+ * only handles normal polarity.
+ * - HW uses one counter and two match components to configure duty_cycle
+ * and period.
+ * - Multi-Function Timer Pulse Unit (a.k.a MTU) has 7 HW channels for PWM
+ * operations. (The channels are MTU{0..4, 6, 7}.)
+ * - MTU{1, 2} channels have a single IO, whereas all other HW channels have
+ * 2 IOs.
+ * - Each IO is modelled as an independent PWM channel.
+ * - rz_mtu3_channel_io_map table is used to map the PWM channel to the
+ * corresponding HW channel as there are difference in number of IOs
+ * between HW channels.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/limits.h>
+#include <linux/mfd/rz-mtu3.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/pwm.h>
+#include <linux/time.h>
+
+#define RZ_MTU3_MAX_PWM_CHANNELS 12
+#define RZ_MTU3_MAX_HW_CHANNELS 7
+
+/**
+ * struct rz_mtu3_channel_io_map - MTU3 pwm channel map
+ *
+ * @base_pwm_number: First PWM of a channel
+ * @num_channel_ios: number of IOs on the HW channel.
+ */
+struct rz_mtu3_channel_io_map {
+ u8 base_pwm_number;
+ u8 num_channel_ios;
+};
+
+/**
+ * struct rz_mtu3_pwm_channel - MTU3 pwm channel data
+ *
+ * @mtu: MTU3 channel data
+ * @map: MTU3 pwm channel map
+ */
+struct rz_mtu3_pwm_channel {
+ struct rz_mtu3_channel *mtu;
+ const struct rz_mtu3_channel_io_map *map;
+};
+
+/**
+ * struct rz_mtu3_pwm_chip - MTU3 pwm private data
+ *
+ * @clk: MTU3 module clock
+ * @lock: Lock to prevent concurrent access for usage count
+ * @rate: MTU3 clock rate
+ * @user_count: MTU3 usage count
+ * @enable_count: MTU3 enable count
+ * @prescale: MTU3 prescale
+ * @channel_data: MTU3 pwm channel data
+ */
+
+struct rz_mtu3_pwm_chip {
+ struct clk *clk;
+ struct mutex lock;
+ unsigned long rate;
+ u32 user_count[RZ_MTU3_MAX_HW_CHANNELS];
+ u32 enable_count[RZ_MTU3_MAX_HW_CHANNELS];
+ u8 prescale[RZ_MTU3_MAX_HW_CHANNELS];
+ struct rz_mtu3_pwm_channel channel_data[RZ_MTU3_MAX_HW_CHANNELS];
+};
+
+/*
+ * The MTU channels are {0..4, 6, 7} and the number of IO on MTU1
+ * and MTU2 channel is 1 compared to 2 on others.
+ */
+static const struct rz_mtu3_channel_io_map channel_map[] = {
+ { 0, 2 }, { 2, 1 }, { 3, 1 }, { 4, 2 }, { 6, 2 }, { 8, 2 }, { 10, 2 }
+};
+
+static inline struct rz_mtu3_pwm_chip *to_rz_mtu3_pwm_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static void rz_mtu3_pwm_read_tgr_registers(struct rz_mtu3_pwm_channel *priv,
+ u16 reg_pv_offset, u16 *pv_val,
+ u16 reg_dc_offset, u16 *dc_val)
+{
+ *pv_val = rz_mtu3_16bit_ch_read(priv->mtu, reg_pv_offset);
+ *dc_val = rz_mtu3_16bit_ch_read(priv->mtu, reg_dc_offset);
+}
+
+static void rz_mtu3_pwm_write_tgr_registers(struct rz_mtu3_pwm_channel *priv,
+ u16 reg_pv_offset, u16 pv_val,
+ u16 reg_dc_offset, u16 dc_val)
+{
+ rz_mtu3_16bit_ch_write(priv->mtu, reg_pv_offset, pv_val);
+ rz_mtu3_16bit_ch_write(priv->mtu, reg_dc_offset, dc_val);
+}
+
+static u8 rz_mtu3_pwm_calculate_prescale(struct rz_mtu3_pwm_chip *rz_mtu3,
+ u64 period_cycles)
+{
+ u32 prescaled_period_cycles;
+ u8 prescale;
+
+ /*
+ * Supported prescale values are 1, 4, 16 and 64.
+ * TODO: Support prescale values 2, 8, 32, 256 and 1024.
+ */
+ prescaled_period_cycles = period_cycles >> 16;
+ if (prescaled_period_cycles >= 16)
+ prescale = 3;
+ else
+ prescale = (fls(prescaled_period_cycles) + 1) / 2;
+
+ return prescale;
+}
+
+static struct rz_mtu3_pwm_channel *
+rz_mtu3_get_channel(struct rz_mtu3_pwm_chip *rz_mtu3_pwm, u32 hwpwm)
+{
+ struct rz_mtu3_pwm_channel *priv = rz_mtu3_pwm->channel_data;
+ unsigned int ch;
+
+ for (ch = 0; ch < RZ_MTU3_MAX_HW_CHANNELS; ch++, priv++) {
+ if (priv->map->base_pwm_number + priv->map->num_channel_ios > hwpwm)
+ break;
+ }
+
+ return priv;
+}
+
+static bool rz_mtu3_pwm_is_ch_enabled(struct rz_mtu3_pwm_chip *rz_mtu3_pwm,
+ u32 hwpwm)
+{
+ struct rz_mtu3_pwm_channel *priv;
+ bool is_channel_en;
+ u8 val;
+
+ priv = rz_mtu3_get_channel(rz_mtu3_pwm, hwpwm);
+ is_channel_en = rz_mtu3_is_enabled(priv->mtu);
+ if (!is_channel_en)
+ return false;
+
+ if (priv->map->base_pwm_number == hwpwm)
+ val = rz_mtu3_8bit_ch_read(priv->mtu, RZ_MTU3_TIORH);
+ else
+ val = rz_mtu3_8bit_ch_read(priv->mtu, RZ_MTU3_TIORL);
+
+ return val & RZ_MTU3_TIOR_IOA;
+}
+
+static int rz_mtu3_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
+ struct rz_mtu3_pwm_channel *priv;
+ bool is_mtu3_channel_available;
+ u32 ch;
+
+ priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm);
+ ch = priv - rz_mtu3_pwm->channel_data;
+
+ mutex_lock(&rz_mtu3_pwm->lock);
+ /*
+ * Each channel must be requested only once, so if the channel
+ * serves two PWMs and the other is already requested, skip over
+ * rz_mtu3_request_channel()
+ */
+ if (!rz_mtu3_pwm->user_count[ch]) {
+ is_mtu3_channel_available = rz_mtu3_request_channel(priv->mtu);
+ if (!is_mtu3_channel_available) {
+ mutex_unlock(&rz_mtu3_pwm->lock);
+ return -EBUSY;
+ }
+ }
+
+ rz_mtu3_pwm->user_count[ch]++;
+ mutex_unlock(&rz_mtu3_pwm->lock);
+
+ return 0;
+}
+
+static void rz_mtu3_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
+ struct rz_mtu3_pwm_channel *priv;
+ u32 ch;
+
+ priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm);
+ ch = priv - rz_mtu3_pwm->channel_data;
+
+ mutex_lock(&rz_mtu3_pwm->lock);
+ rz_mtu3_pwm->user_count[ch]--;
+ if (!rz_mtu3_pwm->user_count[ch])
+ rz_mtu3_release_channel(priv->mtu);
+
+ mutex_unlock(&rz_mtu3_pwm->lock);
+}
+
+static int rz_mtu3_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
+ struct rz_mtu3_pwm_channel *priv;
+ u32 ch;
+ u8 val;
+ int rc;
+
+ rc = pm_runtime_resume_and_get(pwmchip_parent(chip));
+ if (rc)
+ return rc;
+
+ priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm);
+ ch = priv - rz_mtu3_pwm->channel_data;
+ val = RZ_MTU3_TIOR_OC_IOB_TOGGLE | RZ_MTU3_TIOR_OC_IOA_H_COMP_MATCH;
+
+ rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TMDR1, RZ_MTU3_TMDR1_MD_PWMMODE1);
+ if (priv->map->base_pwm_number == pwm->hwpwm)
+ rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TIORH, val);
+ else
+ rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TIORL, val);
+
+ mutex_lock(&rz_mtu3_pwm->lock);
+ if (!rz_mtu3_pwm->enable_count[ch])
+ rz_mtu3_enable(priv->mtu);
+
+ rz_mtu3_pwm->enable_count[ch]++;
+ mutex_unlock(&rz_mtu3_pwm->lock);
+
+ return 0;
+}
+
+static void rz_mtu3_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
+ struct rz_mtu3_pwm_channel *priv;
+ u32 ch;
+
+ priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm);
+ ch = priv - rz_mtu3_pwm->channel_data;
+
+ /* Disable output pins of MTU3 channel */
+ if (priv->map->base_pwm_number == pwm->hwpwm)
+ rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TIORH, RZ_MTU3_TIOR_OC_RETAIN);
+ else
+ rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TIORL, RZ_MTU3_TIOR_OC_RETAIN);
+
+ mutex_lock(&rz_mtu3_pwm->lock);
+ rz_mtu3_pwm->enable_count[ch]--;
+ if (!rz_mtu3_pwm->enable_count[ch])
+ rz_mtu3_disable(priv->mtu);
+
+ mutex_unlock(&rz_mtu3_pwm->lock);
+
+ pm_runtime_put_sync(pwmchip_parent(chip));
+}
+
+static int rz_mtu3_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
+ int rc;
+
+ rc = pm_runtime_resume_and_get(pwmchip_parent(chip));
+ if (rc)
+ return rc;
+
+ state->enabled = rz_mtu3_pwm_is_ch_enabled(rz_mtu3_pwm, pwm->hwpwm);
+ if (state->enabled) {
+ struct rz_mtu3_pwm_channel *priv;
+ u8 prescale, val;
+ u16 dc, pv;
+ u64 tmp;
+
+ priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm);
+ if (priv->map->base_pwm_number == pwm->hwpwm)
+ rz_mtu3_pwm_read_tgr_registers(priv, RZ_MTU3_TGRA, &pv,
+ RZ_MTU3_TGRB, &dc);
+ else
+ rz_mtu3_pwm_read_tgr_registers(priv, RZ_MTU3_TGRC, &pv,
+ RZ_MTU3_TGRD, &dc);
+
+ val = rz_mtu3_8bit_ch_read(priv->mtu, RZ_MTU3_TCR);
+ prescale = FIELD_GET(RZ_MTU3_TCR_TPCS, val);
+
+ /* With prescale <= 7 and pv <= 0xffff this doesn't overflow. */
+ tmp = NSEC_PER_SEC * (u64)pv << (2 * prescale);
+ state->period = DIV_ROUND_UP_ULL(tmp, rz_mtu3_pwm->rate);
+ tmp = NSEC_PER_SEC * (u64)dc << (2 * prescale);
+ state->duty_cycle = DIV_ROUND_UP_ULL(tmp, rz_mtu3_pwm->rate);
+
+ if (state->duty_cycle > state->period)
+ state->duty_cycle = state->period;
+ }
+
+ state->polarity = PWM_POLARITY_NORMAL;
+ pm_runtime_put(pwmchip_parent(chip));
+
+ return 0;
+}
+
+static u16 rz_mtu3_pwm_calculate_pv_or_dc(u64 period_or_duty_cycle, u8 prescale)
+{
+ return min(period_or_duty_cycle >> (2 * prescale), (u64)U16_MAX);
+}
+
+static int rz_mtu3_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
+ struct rz_mtu3_pwm_channel *priv;
+ u64 period_cycles;
+ u64 duty_cycles;
+ u8 prescale;
+ u16 pv, dc;
+ u8 val;
+ u32 ch;
+
+ priv = rz_mtu3_get_channel(rz_mtu3_pwm, pwm->hwpwm);
+ ch = priv - rz_mtu3_pwm->channel_data;
+
+ period_cycles = mul_u64_u32_div(state->period, rz_mtu3_pwm->rate,
+ NSEC_PER_SEC);
+ prescale = rz_mtu3_pwm_calculate_prescale(rz_mtu3_pwm, period_cycles);
+
+ /*
+ * Prescalar is shared by multiple channels, so prescale can
+ * NOT be modified when there are multiple channels in use with
+ * different settings. Modify prescalar if other PWM is off or handle
+ * it, if current prescale value is less than the one we want to set.
+ */
+ if (rz_mtu3_pwm->enable_count[ch] > 1) {
+ if (rz_mtu3_pwm->prescale[ch] > prescale)
+ return -EBUSY;
+
+ prescale = rz_mtu3_pwm->prescale[ch];
+ }
+
+ pv = rz_mtu3_pwm_calculate_pv_or_dc(period_cycles, prescale);
+
+ duty_cycles = mul_u64_u32_div(state->duty_cycle, rz_mtu3_pwm->rate,
+ NSEC_PER_SEC);
+ dc = rz_mtu3_pwm_calculate_pv_or_dc(duty_cycles, prescale);
+
+ /*
+ * If the PWM channel is disabled, make sure to turn on the clock
+ * before writing the register.
+ */
+ if (!pwm->state.enabled) {
+ int rc;
+
+ rc = pm_runtime_resume_and_get(pwmchip_parent(chip));
+ if (rc)
+ return rc;
+ }
+
+ val = RZ_MTU3_TCR_CKEG_RISING | prescale;
+
+ /* Counter must be stopped while updating TCR register */
+ if (rz_mtu3_pwm->prescale[ch] != prescale && rz_mtu3_pwm->enable_count[ch])
+ rz_mtu3_disable(priv->mtu);
+
+ if (priv->map->base_pwm_number == pwm->hwpwm) {
+ rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TCR,
+ RZ_MTU3_TCR_CCLR_TGRA | val);
+ rz_mtu3_pwm_write_tgr_registers(priv, RZ_MTU3_TGRA, pv,
+ RZ_MTU3_TGRB, dc);
+ } else {
+ rz_mtu3_8bit_ch_write(priv->mtu, RZ_MTU3_TCR,
+ RZ_MTU3_TCR_CCLR_TGRC | val);
+ rz_mtu3_pwm_write_tgr_registers(priv, RZ_MTU3_TGRC, pv,
+ RZ_MTU3_TGRD, dc);
+ }
+
+ if (rz_mtu3_pwm->prescale[ch] != prescale) {
+ /*
+ * Prescalar is shared by multiple channels, we cache the
+ * prescalar value from first enabled channel and use the same
+ * value for both channels.
+ */
+ rz_mtu3_pwm->prescale[ch] = prescale;
+
+ if (rz_mtu3_pwm->enable_count[ch])
+ rz_mtu3_enable(priv->mtu);
+ }
+
+ /* If the PWM is not enabled, turn the clock off again to save power. */
+ if (!pwm->state.enabled)
+ pm_runtime_put(pwmchip_parent(chip));
+
+ return 0;
+}
+
+static int rz_mtu3_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
+ bool enabled = pwm->state.enabled;
+ int ret;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ if (enabled)
+ rz_mtu3_pwm_disable(chip, pwm);
+
+ return 0;
+ }
+
+ mutex_lock(&rz_mtu3_pwm->lock);
+ ret = rz_mtu3_pwm_config(chip, pwm, state);
+ mutex_unlock(&rz_mtu3_pwm->lock);
+ if (ret)
+ return ret;
+
+ if (!enabled)
+ ret = rz_mtu3_pwm_enable(chip, pwm);
+
+ return ret;
+}
+
+static const struct pwm_ops rz_mtu3_pwm_ops = {
+ .request = rz_mtu3_pwm_request,
+ .free = rz_mtu3_pwm_free,
+ .get_state = rz_mtu3_pwm_get_state,
+ .apply = rz_mtu3_pwm_apply,
+};
+
+static int rz_mtu3_pwm_pm_runtime_suspend(struct device *dev)
+{
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
+
+ clk_disable_unprepare(rz_mtu3_pwm->clk);
+
+ return 0;
+}
+
+static int rz_mtu3_pwm_pm_runtime_resume(struct device *dev)
+{
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
+
+ return clk_prepare_enable(rz_mtu3_pwm->clk);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(rz_mtu3_pwm_pm_ops,
+ rz_mtu3_pwm_pm_runtime_suspend,
+ rz_mtu3_pwm_pm_runtime_resume, NULL);
+
+static void rz_mtu3_pwm_pm_disable(void *data)
+{
+ struct pwm_chip *chip = data;
+ struct rz_mtu3_pwm_chip *rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
+
+ clk_rate_exclusive_put(rz_mtu3_pwm->clk);
+ pm_runtime_disable(pwmchip_parent(chip));
+ pm_runtime_set_suspended(pwmchip_parent(chip));
+}
+
+static int rz_mtu3_pwm_probe(struct platform_device *pdev)
+{
+ struct rz_mtu3 *parent_ddata = dev_get_drvdata(pdev->dev.parent);
+ struct rz_mtu3_pwm_chip *rz_mtu3_pwm;
+ struct pwm_chip *chip;
+ struct device *dev = &pdev->dev;
+ unsigned int i, j = 0;
+ int ret;
+
+ chip = devm_pwmchip_alloc(&pdev->dev, RZ_MTU3_MAX_PWM_CHANNELS,
+ sizeof(*rz_mtu3_pwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ rz_mtu3_pwm = to_rz_mtu3_pwm_chip(chip);
+
+ rz_mtu3_pwm->clk = parent_ddata->clk;
+
+ for (i = 0; i < RZ_MTU_NUM_CHANNELS; i++) {
+ if (i == RZ_MTU3_CHAN_5 || i == RZ_MTU3_CHAN_8)
+ continue;
+
+ rz_mtu3_pwm->channel_data[j].mtu = &parent_ddata->channels[i];
+ rz_mtu3_pwm->channel_data[j].mtu->dev = dev;
+ rz_mtu3_pwm->channel_data[j].map = &channel_map[j];
+ j++;
+ }
+
+ mutex_init(&rz_mtu3_pwm->lock);
+ platform_set_drvdata(pdev, chip);
+ ret = clk_prepare_enable(rz_mtu3_pwm->clk);
+ if (ret)
+ return dev_err_probe(dev, ret, "Clock enable failed\n");
+
+ clk_rate_exclusive_get(rz_mtu3_pwm->clk);
+
+ rz_mtu3_pwm->rate = clk_get_rate(rz_mtu3_pwm->clk);
+ /*
+ * Refuse clk rates > 1 GHz to prevent overflow later for computing
+ * period and duty cycle.
+ */
+ if (rz_mtu3_pwm->rate > NSEC_PER_SEC) {
+ ret = -EINVAL;
+ clk_rate_exclusive_put(rz_mtu3_pwm->clk);
+ goto disable_clock;
+ }
+
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ ret = devm_add_action_or_reset(&pdev->dev, rz_mtu3_pwm_pm_disable,
+ chip);
+ if (ret < 0)
+ return ret;
+
+ chip->ops = &rz_mtu3_pwm_ops;
+ ret = devm_pwmchip_add(&pdev->dev, chip);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n");
+
+ pm_runtime_idle(&pdev->dev);
+
+ return 0;
+
+disable_clock:
+ clk_disable_unprepare(rz_mtu3_pwm->clk);
+ return ret;
+}
+
+static struct platform_driver rz_mtu3_pwm_driver = {
+ .driver = {
+ .name = "pwm-rz-mtu3",
+ .pm = pm_ptr(&rz_mtu3_pwm_pm_ops),
+ },
+ .probe = rz_mtu3_pwm_probe,
+};
+module_platform_driver(rz_mtu3_pwm_driver);
+
+MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>");
+MODULE_ALIAS("platform:pwm-rz-mtu3");
+MODULE_DESCRIPTION("Renesas RZ/G2L MTU3a PWM Timer Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
new file mode 100644
index 000000000000..4856af080e8e
--- /dev/null
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/G2L General PWM Timer (GPT) driver
+ *
+ * Copyright (C) 2025 Renesas Electronics Corporation
+ *
+ * Hardware manual for this IP can be found here
+ * https://www.renesas.com/eu/en/document/mah/rzg2l-group-rzg2lc-group-users-manual-hardware-0?language=en
+ *
+ * Limitations:
+ * - Counter must be stopped before modifying Mode and Prescaler.
+ * - When PWM is disabled, the output is driven to inactive.
+ * - While the hardware supports both polarities, the driver (for now)
+ * only handles normal polarity.
+ * - General PWM Timer (GPT) has 8 HW channels for PWM operations and
+ * each HW channel have 2 IOs.
+ * - Each IO is modelled as an independent PWM channel.
+ * - When both channels are used, disabling the channel on one stops the
+ * other.
+ * - When both channels are used, the period of both IOs in the HW channel
+ * must be same (for now).
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/reset.h>
+#include <linux/time.h>
+#include <linux/units.h>
+
+#define RZG2L_GET_CH(hwpwm) ((hwpwm) / 2)
+#define RZG2L_GET_CH_OFFS(ch) (0x100 * (ch))
+
+#define RZG2L_GTCR(ch) (0x2c + RZG2L_GET_CH_OFFS(ch))
+#define RZG2L_GTUDDTYC(ch) (0x30 + RZG2L_GET_CH_OFFS(ch))
+#define RZG2L_GTIOR(ch) (0x34 + RZG2L_GET_CH_OFFS(ch))
+#define RZG2L_GTBER(ch) (0x40 + RZG2L_GET_CH_OFFS(ch))
+#define RZG2L_GTCNT(ch) (0x48 + RZG2L_GET_CH_OFFS(ch))
+#define RZG2L_GTCCR(ch, sub_ch) (0x4c + RZG2L_GET_CH_OFFS(ch) + 4 * (sub_ch))
+#define RZG2L_GTPR(ch) (0x64 + RZG2L_GET_CH_OFFS(ch))
+
+#define RZG2L_GTCR_CST BIT(0)
+#define RZG2L_GTCR_MD GENMASK(18, 16)
+#define RZG2L_GTCR_TPCS GENMASK(26, 24)
+
+#define RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE FIELD_PREP(RZG2L_GTCR_MD, 0)
+
+#define RZG2L_GTUDDTYC_UP BIT(0)
+#define RZG2L_GTUDDTYC_UDF BIT(1)
+#define RZG2L_GTUDDTYC_UP_COUNTING (RZG2L_GTUDDTYC_UP | RZG2L_GTUDDTYC_UDF)
+
+#define RZG2L_GTIOR_GTIOA GENMASK(4, 0)
+#define RZG2L_GTIOR_GTIOB GENMASK(20, 16)
+#define RZG2L_GTIOR_GTIOx(sub_ch) ((sub_ch) ? RZG2L_GTIOR_GTIOB : RZG2L_GTIOR_GTIOA)
+#define RZG2L_GTIOR_OAE BIT(8)
+#define RZG2L_GTIOR_OBE BIT(24)
+#define RZG2L_GTIOR_OxE(sub_ch) ((sub_ch) ? RZG2L_GTIOR_OBE : RZG2L_GTIOR_OAE)
+
+#define RZG2L_INIT_OUT_HI_OUT_HI_END_TOGGLE 0x1b
+#define RZG2L_GTIOR_GTIOA_OUT_HI_END_TOGGLE_CMP_MATCH \
+ (RZG2L_INIT_OUT_HI_OUT_HI_END_TOGGLE | RZG2L_GTIOR_OAE)
+#define RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH \
+ (FIELD_PREP(RZG2L_GTIOR_GTIOB, RZG2L_INIT_OUT_HI_OUT_HI_END_TOGGLE) | RZG2L_GTIOR_OBE)
+
+#define RZG2L_GTIOR_GTIOx_OUT_HI_END_TOGGLE_CMP_MATCH(sub_ch) \
+ ((sub_ch) ? RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH : \
+ RZG2L_GTIOR_GTIOA_OUT_HI_END_TOGGLE_CMP_MATCH)
+
+#define RZG2L_MAX_HW_CHANNELS 8
+#define RZG2L_CHANNELS_PER_IO 2
+#define RZG2L_MAX_PWM_CHANNELS (RZG2L_MAX_HW_CHANNELS * RZG2L_CHANNELS_PER_IO)
+#define RZG2L_MAX_SCALE_FACTOR 1024
+#define RZG2L_MAX_TICKS ((u64)U32_MAX * RZG2L_MAX_SCALE_FACTOR)
+
+struct rzg2l_gpt_chip {
+ void __iomem *mmio;
+ struct mutex lock; /* lock to protect shared channel resources */
+ unsigned long rate_khz;
+ u32 period_ticks[RZG2L_MAX_HW_CHANNELS];
+ u32 channel_request_count[RZG2L_MAX_HW_CHANNELS];
+ u32 channel_enable_count[RZG2L_MAX_HW_CHANNELS];
+};
+
+static inline struct rzg2l_gpt_chip *to_rzg2l_gpt_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static inline unsigned int rzg2l_gpt_subchannel(unsigned int hwpwm)
+{
+ return hwpwm & 0x1;
+}
+
+static inline unsigned int rzg2l_gpt_sibling(unsigned int hwpwm)
+{
+ return hwpwm ^ 0x1;
+}
+
+static void rzg2l_gpt_write(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg, u32 data)
+{
+ writel(data, rzg2l_gpt->mmio + reg);
+}
+
+static u32 rzg2l_gpt_read(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg)
+{
+ return readl(rzg2l_gpt->mmio + reg);
+}
+
+static void rzg2l_gpt_modify(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg, u32 clr,
+ u32 set)
+{
+ rzg2l_gpt_write(rzg2l_gpt, reg,
+ (rzg2l_gpt_read(rzg2l_gpt, reg) & ~clr) | set);
+}
+
+static u8 rzg2l_gpt_calculate_prescale(struct rzg2l_gpt_chip *rzg2l_gpt,
+ u64 period_ticks)
+{
+ u32 prescaled_period_ticks;
+ u8 prescale;
+
+ prescaled_period_ticks = period_ticks >> 32;
+ if (prescaled_period_ticks >= 256)
+ prescale = 5;
+ else
+ prescale = (fls(prescaled_period_ticks) + 1) / 2;
+
+ return prescale;
+}
+
+static int rzg2l_gpt_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+ u32 ch = RZG2L_GET_CH(pwm->hwpwm);
+
+ guard(mutex)(&rzg2l_gpt->lock);
+ rzg2l_gpt->channel_request_count[ch]++;
+
+ return 0;
+}
+
+static void rzg2l_gpt_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+ u32 ch = RZG2L_GET_CH(pwm->hwpwm);
+
+ guard(mutex)(&rzg2l_gpt->lock);
+ rzg2l_gpt->channel_request_count[ch]--;
+}
+
+static bool rzg2l_gpt_is_ch_enabled(struct rzg2l_gpt_chip *rzg2l_gpt, u8 hwpwm)
+{
+ u8 ch = RZG2L_GET_CH(hwpwm);
+ u32 val;
+
+ val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCR(ch));
+ if (!(val & RZG2L_GTCR_CST))
+ return false;
+
+ val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTIOR(ch));
+
+ return val & RZG2L_GTIOR_OxE(rzg2l_gpt_subchannel(hwpwm));
+}
+
+/* Caller holds the lock while calling rzg2l_gpt_enable() */
+static void rzg2l_gpt_enable(struct rzg2l_gpt_chip *rzg2l_gpt,
+ struct pwm_device *pwm)
+{
+ u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
+ u32 val = RZG2L_GTIOR_GTIOx(sub_ch) | RZG2L_GTIOR_OxE(sub_ch);
+ u8 ch = RZG2L_GET_CH(pwm->hwpwm);
+
+ /* Enable pin output */
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTIOR(ch), val,
+ RZG2L_GTIOR_GTIOx_OUT_HI_END_TOGGLE_CMP_MATCH(sub_ch));
+
+ if (!rzg2l_gpt->channel_enable_count[ch])
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), 0, RZG2L_GTCR_CST);
+
+ rzg2l_gpt->channel_enable_count[ch]++;
+}
+
+/* Caller holds the lock while calling rzg2l_gpt_disable() */
+static void rzg2l_gpt_disable(struct rzg2l_gpt_chip *rzg2l_gpt,
+ struct pwm_device *pwm)
+{
+ u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
+ u8 ch = RZG2L_GET_CH(pwm->hwpwm);
+
+ /* Stop count, Output low on GTIOCx pin when counting stops */
+ rzg2l_gpt->channel_enable_count[ch]--;
+
+ if (!rzg2l_gpt->channel_enable_count[ch])
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_CST, 0);
+
+ /* Disable pin output */
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTIOR(ch), RZG2L_GTIOR_OxE(sub_ch), 0);
+}
+
+static u64 rzg2l_gpt_calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt,
+ u32 val, u8 prescale)
+{
+ u64 tmp;
+
+ /*
+ * The calculation doesn't overflow an u64 because prescale ≤ 5 and so
+ * tmp = val << (2 * prescale) * USEC_PER_SEC
+ * < 2^32 * 2^10 * 10^6
+ * < 2^32 * 2^10 * 2^20
+ * = 2^62
+ */
+ tmp = (u64)val << (2 * prescale);
+ tmp *= USEC_PER_SEC;
+
+ return DIV64_U64_ROUND_UP(tmp, rzg2l_gpt->rate_khz);
+}
+
+static int rzg2l_gpt_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+
+ state->enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm);
+ if (state->enabled) {
+ u32 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
+ u32 ch = RZG2L_GET_CH(pwm->hwpwm);
+ u8 prescale;
+ u32 val;
+
+ val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCR(ch));
+ prescale = FIELD_GET(RZG2L_GTCR_TPCS, val);
+
+ val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTPR(ch));
+ state->period = rzg2l_gpt_calculate_period_or_duty(rzg2l_gpt, val, prescale);
+
+ val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch));
+ state->duty_cycle = rzg2l_gpt_calculate_period_or_duty(rzg2l_gpt, val, prescale);
+ if (state->duty_cycle > state->period)
+ state->duty_cycle = state->period;
+ }
+
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ return 0;
+}
+
+static u32 rzg2l_gpt_calculate_pv_or_dc(u64 period_or_duty_cycle, u8 prescale)
+{
+ return min_t(u64, DIV_ROUND_DOWN_ULL(period_or_duty_cycle, 1 << (2 * prescale)),
+ U32_MAX);
+}
+
+/* Caller holds the lock while calling rzg2l_gpt_config() */
+static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+ u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
+ u8 ch = RZG2L_GET_CH(pwm->hwpwm);
+ u64 period_ticks, duty_ticks;
+ unsigned long pv, dc;
+ u8 prescale;
+
+ /* Limit period/duty cycle to max value supported by the HW */
+ period_ticks = mul_u64_u64_div_u64(state->period, rzg2l_gpt->rate_khz, USEC_PER_SEC);
+ if (period_ticks > RZG2L_MAX_TICKS)
+ period_ticks = RZG2L_MAX_TICKS;
+ /*
+ * GPT counter is shared by the two IOs of a single channel, so
+ * prescale and period can NOT be modified when there are multiple IOs
+ * in use with different settings.
+ */
+ if (rzg2l_gpt->channel_request_count[ch] > 1) {
+ u8 sibling_ch = rzg2l_gpt_sibling(pwm->hwpwm);
+
+ if (rzg2l_gpt_is_ch_enabled(rzg2l_gpt, sibling_ch)) {
+ if (period_ticks < rzg2l_gpt->period_ticks[ch])
+ return -EBUSY;
+
+ period_ticks = rzg2l_gpt->period_ticks[ch];
+ }
+ }
+
+ prescale = rzg2l_gpt_calculate_prescale(rzg2l_gpt, period_ticks);
+ pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, prescale);
+
+ duty_ticks = mul_u64_u64_div_u64(state->duty_cycle, rzg2l_gpt->rate_khz, USEC_PER_SEC);
+ if (duty_ticks > period_ticks)
+ duty_ticks = period_ticks;
+ dc = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, prescale);
+
+ /*
+ * GPT counter is shared by multiple channels, we cache the period ticks
+ * from the first enabled channel and use the same value for both
+ * channels.
+ */
+ rzg2l_gpt->period_ticks[ch] = period_ticks;
+
+ /*
+ * Counter must be stopped before modifying mode, prescaler, timer
+ * counter and buffer enable registers. These registers are shared
+ * between both channels. So allow updating these registers only for the
+ * first enabled channel.
+ */
+ if (rzg2l_gpt->channel_enable_count[ch] <= 1) {
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_CST, 0);
+
+ /* GPT set operating mode (saw-wave up-counting) */
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_MD,
+ RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE);
+
+ /* Set count direction */
+ rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTUDDTYC(ch), RZG2L_GTUDDTYC_UP_COUNTING);
+
+ /* Select count clock */
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_TPCS,
+ FIELD_PREP(RZG2L_GTCR_TPCS, prescale));
+
+ /* Set period */
+ rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTPR(ch), pv);
+ }
+
+ /* Set duty cycle */
+ rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch), dc);
+
+ if (rzg2l_gpt->channel_enable_count[ch] <= 1) {
+ /* Set initial value for counter */
+ rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTCNT(ch), 0);
+
+ /* Set no buffer operation */
+ rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTBER(ch), 0);
+
+ /* Restart the counter after updating the registers */
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch),
+ RZG2L_GTCR_CST, RZG2L_GTCR_CST);
+ }
+
+ return 0;
+}
+
+static int rzg2l_gpt_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+ bool enabled = pwm->state.enabled;
+ int ret;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ guard(mutex)(&rzg2l_gpt->lock);
+ if (!state->enabled) {
+ if (enabled)
+ rzg2l_gpt_disable(rzg2l_gpt, pwm);
+
+ return 0;
+ }
+
+ ret = rzg2l_gpt_config(chip, pwm, state);
+ if (!ret && !enabled)
+ rzg2l_gpt_enable(rzg2l_gpt, pwm);
+
+ return ret;
+}
+
+static const struct pwm_ops rzg2l_gpt_ops = {
+ .request = rzg2l_gpt_request,
+ .free = rzg2l_gpt_free,
+ .get_state = rzg2l_gpt_get_state,
+ .apply = rzg2l_gpt_apply,
+};
+
+static int rzg2l_gpt_probe(struct platform_device *pdev)
+{
+ struct rzg2l_gpt_chip *rzg2l_gpt;
+ struct device *dev = &pdev->dev;
+ struct reset_control *rstc;
+ struct pwm_chip *chip;
+ unsigned long rate;
+ struct clk *clk;
+ int ret;
+
+ chip = devm_pwmchip_alloc(dev, RZG2L_MAX_PWM_CHANNELS, sizeof(*rzg2l_gpt));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+
+ rzg2l_gpt->mmio = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(rzg2l_gpt->mmio))
+ return PTR_ERR(rzg2l_gpt->mmio);
+
+ rstc = devm_reset_control_get_exclusive_deasserted(dev, NULL);
+ if (IS_ERR(rstc))
+ return dev_err_probe(dev, PTR_ERR(rstc), "Cannot deassert reset control\n");
+
+ clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Cannot get clock\n");
+
+ ret = devm_clk_rate_exclusive_get(dev, clk);
+ if (ret)
+ return ret;
+
+ rate = clk_get_rate(clk);
+ if (!rate)
+ return dev_err_probe(dev, -EINVAL, "The gpt clk rate is 0");
+
+ /*
+ * Refuse clk rates > 1 GHz to prevent overflow later for computing
+ * period and duty cycle.
+ */
+ if (rate > NSEC_PER_SEC)
+ return dev_err_probe(dev, -EINVAL, "The gpt clk rate is > 1GHz");
+
+ /*
+ * Rate is in MHz and is always integer for peripheral clk
+ * 2^32 * 2^10 (prescalar) * 10^6 (rate_khz) < 2^64
+ * So make sure rate is multiple of 1000.
+ */
+ rzg2l_gpt->rate_khz = rate / KILO;
+ if (rzg2l_gpt->rate_khz * KILO != rate)
+ return dev_err_probe(dev, -EINVAL, "Rate is not multiple of 1000");
+
+ mutex_init(&rzg2l_gpt->lock);
+
+ chip->ops = &rzg2l_gpt_ops;
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
+
+ return 0;
+}
+
+static const struct of_device_id rzg2l_gpt_of_table[] = {
+ { .compatible = "renesas,rzg2l-gpt", },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rzg2l_gpt_of_table);
+
+static struct platform_driver rzg2l_gpt_driver = {
+ .driver = {
+ .name = "pwm-rzg2l-gpt",
+ .of_match_table = rzg2l_gpt_of_table,
+ },
+ .probe = rzg2l_gpt_probe,
+};
+module_platform_driver(rzg2l_gpt_driver);
+
+MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>");
+MODULE_DESCRIPTION("Renesas RZ/G2L General PWM Timer (GPT) Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c
index 87a886f7dc2f..951b38ff5f8e 100644
--- a/drivers/pwm/pwm-samsung.c
+++ b/drivers/pwm/pwm-samsung.c
@@ -69,7 +69,6 @@ struct samsung_pwm_channel {
/**
* 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
@@ -77,9 +76,9 @@ struct samsung_pwm_channel {
* @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)
+ * @channel: per channel driver data
*/
struct samsung_pwm_chip {
- struct pwm_chip chip;
struct samsung_pwm_variant variant;
u8 inverter_mask;
u8 disabled_mask;
@@ -88,6 +87,7 @@ struct samsung_pwm_chip {
struct clk *base_clk;
struct clk *tclk0;
struct clk *tclk1;
+ struct samsung_pwm_channel channel[SAMSUNG_PWM_NUM];
};
#ifndef CONFIG_CLKSRC_SAMSUNG_PWM
@@ -108,7 +108,7 @@ static DEFINE_SPINLOCK(samsung_pwm_lock);
static inline
struct samsung_pwm_chip *to_samsung_pwm_chip(struct pwm_chip *chip)
{
- return container_of(chip, struct samsung_pwm_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
static inline unsigned int to_tcon_channel(unsigned int channel)
@@ -117,7 +117,21 @@ static inline unsigned int to_tcon_channel(unsigned int channel)
return (channel == 0) ? 0 : (channel + 1);
}
-static void pwm_samsung_set_divisor(struct samsung_pwm_chip *pwm,
+static void __pwm_samsung_manual_update(struct samsung_pwm_chip *our_chip,
+ struct pwm_device *pwm)
+{
+ unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm);
+ u32 tcon;
+
+ tcon = readl(our_chip->base + REG_TCON);
+ tcon |= TCON_MANUALUPDATE(tcon_chan);
+ writel(tcon, our_chip->base + REG_TCON);
+
+ tcon &= ~TCON_MANUALUPDATE(tcon_chan);
+ writel(tcon, our_chip->base + REG_TCON);
+}
+
+static void pwm_samsung_set_divisor(struct samsung_pwm_chip *our_chip,
unsigned int channel, u8 divisor)
{
u8 shift = TCFG1_SHIFT(channel);
@@ -125,39 +139,39 @@ static void pwm_samsung_set_divisor(struct samsung_pwm_chip *pwm,
u32 reg;
u8 bits;
- bits = (fls(divisor) - 1) - pwm->variant.div_base;
+ bits = (fls(divisor) - 1) - our_chip->variant.div_base;
spin_lock_irqsave(&samsung_pwm_lock, flags);
- reg = readl(pwm->base + REG_TCFG1);
+ reg = readl(our_chip->base + REG_TCFG1);
reg &= ~(TCFG1_MUX_MASK << shift);
reg |= bits << shift;
- writel(reg, pwm->base + REG_TCFG1);
+ writel(reg, our_chip->base + REG_TCFG1);
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
}
-static int pwm_samsung_is_tdiv(struct samsung_pwm_chip *chip, unsigned int chan)
+static int pwm_samsung_is_tdiv(struct samsung_pwm_chip *our_chip, unsigned int chan)
{
- struct samsung_pwm_variant *variant = &chip->variant;
+ struct samsung_pwm_variant *variant = &our_chip->variant;
u32 reg;
- reg = readl(chip->base + REG_TCFG1);
+ reg = readl(our_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,
+static unsigned long pwm_samsung_get_tin_rate(struct samsung_pwm_chip *our_chip,
unsigned int chan)
{
unsigned long rate;
u32 reg;
- rate = clk_get_rate(chip->base_clk);
+ rate = clk_get_rate(our_chip->base_clk);
- reg = readl(chip->base + REG_TCFG0);
+ reg = readl(our_chip->base + REG_TCFG0);
if (chan >= 2)
reg >>= TCFG0_PRESCALER1_SHIFT;
reg &= TCFG0_PRESCALER_MASK;
@@ -165,28 +179,29 @@ static unsigned long pwm_samsung_get_tin_rate(struct samsung_pwm_chip *chip,
return rate / (reg + 1);
}
-static unsigned long pwm_samsung_calc_tin(struct samsung_pwm_chip *chip,
+static unsigned long pwm_samsung_calc_tin(struct pwm_chip *chip,
unsigned int chan, unsigned long freq)
{
- struct samsung_pwm_variant *variant = &chip->variant;
+ struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip);
+ struct samsung_pwm_variant *variant = &our_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 (!pwm_samsung_is_tdiv(our_chip, chan)) {
+ clk = (chan < 2) ? our_chip->tclk0 : our_chip->tclk1;
if (!IS_ERR(clk)) {
rate = clk_get_rate(clk);
if (rate)
return rate;
}
- dev_warn(chip->chip.dev,
+ dev_warn(pwmchip_parent(chip),
"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);
+ rate = pwm_samsung_get_tin_rate(our_chip, chan);
+ dev_dbg(pwmchip_parent(chip), "tin parent at %lu\n", rate);
/*
* Compare minimum PWM frequency that can be achieved with possible
@@ -206,7 +221,7 @@ static unsigned long pwm_samsung_calc_tin(struct samsung_pwm_chip *chip,
div = variant->div_base;
}
- pwm_samsung_set_divisor(chip, chan, BIT(div));
+ pwm_samsung_set_divisor(our_chip, chan, BIT(div));
return rate >> div;
}
@@ -214,29 +229,19 @@ static unsigned long pwm_samsung_calc_tin(struct samsung_pwm_chip *chip,
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,
+ dev_warn(pwmchip_parent(chip),
"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);
+ memset(&our_chip->channel[pwm->hwpwm], 0, sizeof(our_chip->channel[pwm->hwpwm]));
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);
@@ -276,26 +281,26 @@ static void pwm_samsung_disable(struct pwm_chip *chip, struct pwm_device *pwm)
tcon &= ~TCON_AUTORELOAD(tcon_chan);
writel(tcon, our_chip->base + REG_TCON);
+ /*
+ * In case the PWM is at 100% duty cycle, force a manual
+ * update to prevent the signal from staying high.
+ */
+ if (readl(our_chip->base + REG_TCMPB(pwm->hwpwm)) == (u32)-1U)
+ __pwm_samsung_manual_update(our_chip, pwm);
+
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,
+static void pwm_samsung_manual_update(struct samsung_pwm_chip *our_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);
+ __pwm_samsung_manual_update(our_chip, pwm);
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
}
@@ -304,17 +309,9 @@ 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);
+ struct samsung_pwm_channel *chan = &our_chip->channel[pwm->hwpwm];
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));
@@ -328,12 +325,12 @@ static int __pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
period = NSEC_PER_SEC / period_ns;
- dev_dbg(our_chip->chip.dev, "duty_ns=%d, period_ns=%d (%u)\n",
+ dev_dbg(pwmchip_parent(chip), "duty_ns=%d, period_ns=%d (%u)\n",
duty_ns, period_ns, period);
- tin_rate = pwm_samsung_calc_tin(our_chip, pwm->hwpwm, period);
+ tin_rate = pwm_samsung_calc_tin(chip, pwm->hwpwm, period);
- dev_dbg(our_chip->chip.dev, "tin_rate=%lu\n", tin_rate);
+ dev_dbg(pwmchip_parent(chip), "tin_rate=%lu\n", tin_rate);
tin_ns = NSEC_PER_SEC / tin_rate;
tcnt = period_ns / tin_ns;
@@ -357,8 +354,7 @@ static int __pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
/* -1UL will give 100% duty. */
--tcmp;
- dev_dbg(our_chip->chip.dev,
- "tin_ns=%u, tcmp=%u/%u\n", tin_ns, tcmp, tcnt);
+ dev_dbg(pwmchip_parent(chip), "tin_ns=%u, tcmp=%u/%u\n", tin_ns, tcmp, tcnt);
/* Update PWM registers. */
writel(tcnt, our_chip->base + REG_TCNTB(pwm->hwpwm));
@@ -370,7 +366,7 @@ static int __pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
* shortly afer this update (before it autoreloaded the new values).
*/
if (oldtcmp == (u32) -1) {
- dev_dbg(our_chip->chip.dev, "Forcing manual update");
+ dev_dbg(pwmchip_parent(chip), "Forcing manual update");
pwm_samsung_manual_update(our_chip, pwm);
}
@@ -387,7 +383,7 @@ static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
return __pwm_samsung_config(chip, pwm, duty_ns, period_ns, false);
}
-static void pwm_samsung_set_invert(struct samsung_pwm_chip *chip,
+static void pwm_samsung_set_invert(struct samsung_pwm_chip *our_chip,
unsigned int channel, bool invert)
{
unsigned int tcon_chan = to_tcon_channel(channel);
@@ -396,17 +392,17 @@ static void pwm_samsung_set_invert(struct samsung_pwm_chip *chip,
spin_lock_irqsave(&samsung_pwm_lock, flags);
- tcon = readl(chip->base + REG_TCON);
+ tcon = readl(our_chip->base + REG_TCON);
if (invert) {
- chip->inverter_mask |= BIT(channel);
+ our_chip->inverter_mask |= BIT(channel);
tcon |= TCON_INVERT(tcon_chan);
} else {
- chip->inverter_mask &= ~BIT(channel);
+ our_chip->inverter_mask &= ~BIT(channel);
tcon &= ~TCON_INVERT(tcon_chan);
}
- writel(tcon, chip->base + REG_TCON);
+ writel(tcon, our_chip->base + REG_TCON);
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
}
@@ -424,14 +420,50 @@ static int pwm_samsung_set_polarity(struct pwm_chip *chip,
return 0;
}
+static int pwm_samsung_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err, enabled = pwm->state.enabled;
+
+ if (state->polarity != pwm->state.polarity) {
+ if (enabled) {
+ pwm_samsung_disable(chip, pwm);
+ enabled = false;
+ }
+
+ err = pwm_samsung_set_polarity(chip, pwm, state->polarity);
+ if (err)
+ return err;
+ }
+
+ if (!state->enabled) {
+ if (enabled)
+ pwm_samsung_disable(chip, pwm);
+
+ return 0;
+ }
+
+ /*
+ * We currently avoid using 64bit arithmetic by using the
+ * fact that anything faster than 1Hz is easily representable
+ * by 32bits.
+ */
+ if (state->period > NSEC_PER_SEC)
+ return -ERANGE;
+
+ err = pwm_samsung_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!pwm->state.enabled)
+ err = pwm_samsung_enable(chip, pwm);
+
+ return err;
+}
+
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,
+ .apply = pwm_samsung_apply,
};
#ifdef CONFIG_OF
@@ -473,34 +505,33 @@ static const struct of_device_id samsung_pwm_matches[] = {
};
MODULE_DEVICE_TABLE(of, samsung_pwm_matches);
-static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip)
+static int pwm_samsung_parse_dt(struct pwm_chip *chip)
{
- struct device_node *np = chip->chip.dev->of_node;
+ struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip);
+ struct device_node *np = pwmchip_parent(chip)->of_node;
const struct of_device_id *match;
- struct property *prop;
- const __be32 *cur;
u32 val;
match = of_match_node(samsung_pwm_matches, np);
if (!match)
return -ENODEV;
- memcpy(&chip->variant, match->data, sizeof(chip->variant));
+ memcpy(&our_chip->variant, match->data, sizeof(our_chip->variant));
- of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) {
+ of_property_for_each_u32(np, "samsung,pwm-outputs", val) {
if (val >= SAMSUNG_PWM_NUM) {
- dev_err(chip->chip.dev,
+ dev_err(pwmchip_parent(chip),
"%s: invalid channel index in samsung,pwm-outputs property\n",
__func__);
continue;
}
- chip->variant.output_mask |= BIT(val);
+ our_chip->variant.output_mask |= BIT(val);
}
return 0;
}
#else
-static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip)
+static int pwm_samsung_parse_dt(struct pwm_chip *chip)
{
return -ENODEV;
}
@@ -509,106 +540,74 @@ static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip)
static int pwm_samsung_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct samsung_pwm_chip *chip;
- struct resource *res;
+ struct samsung_pwm_chip *our_chip;
+ struct pwm_chip *chip;
unsigned int chan;
int ret;
- chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
- if (chip == NULL)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, SAMSUNG_PWM_NUM, sizeof(*our_chip));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ our_chip = to_samsung_pwm_chip(chip);
- 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;
+ chip->ops = &pwm_samsung_ops;
+ our_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;
- }
+ if (!pdev->dev.platform_data)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "no platform data specified\n");
- memcpy(&chip->variant, pdev->dev.platform_data,
- sizeof(chip->variant));
+ memcpy(&our_chip->variant, pdev->dev.platform_data,
+ sizeof(our_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);
- }
+ our_chip->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(our_chip->base))
+ return PTR_ERR(our_chip->base);
- ret = clk_prepare_enable(chip->base_clk);
- if (ret < 0) {
- dev_err(dev, "failed to enable base clock\n");
- return ret;
- }
+ our_chip->base_clk = devm_clk_get_enabled(&pdev->dev, "timers");
+ if (IS_ERR(our_chip->base_clk))
+ return dev_err_probe(dev, PTR_ERR(our_chip->base_clk),
+ "failed to get timer base clk\n");
for (chan = 0; chan < SAMSUNG_PWM_NUM; ++chan)
- if (chip->variant.output_mask & BIT(chan))
- pwm_samsung_set_invert(chip, chan, true);
+ if (our_chip->variant.output_mask & BIT(chan))
+ pwm_samsung_set_invert(our_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");
+ our_chip->tclk0 = devm_clk_get(&pdev->dev, "pwm-tclk0");
+ our_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);
+ ret = devm_pwmchip_add(&pdev->dev, chip);
if (ret < 0)
- return ret;
+ return dev_err_probe(dev, ret, "failed to register PWM chip\n");
- clk_disable_unprepare(chip->base_clk);
+ dev_dbg(dev, "base_clk at %lu, tclk0 at %lu, tclk1 at %lu\n",
+ clk_get_rate(our_chip->base_clk),
+ !IS_ERR(our_chip->tclk0) ? clk_get_rate(our_chip->tclk0) : 0,
+ !IS_ERR(our_chip->tclk1) ? clk_get_rate(our_chip->tclk1) : 0);
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;
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct samsung_pwm_chip *our_chip = to_samsung_pwm_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);
+ struct samsung_pwm_channel *chan = &our_chip->channel[i];
- if (!chan)
+ if (!test_bit(PWMF_REQUESTED, &pwm->flags))
continue;
if (our_chip->variant.output_mask & BIT(i))
@@ -630,21 +629,20 @@ static int pwm_samsung_resume(struct device *dev)
return 0;
}
-#endif
-static SIMPLE_DEV_PM_OPS(pwm_samsung_pm_ops, NULL, pwm_samsung_resume);
+static DEFINE_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,
+ .pm = pm_ptr(&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_DESCRIPTION("Samsung Pulse Width Modulator driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tomasz Figa <tomasz.figa@gmail.com>");
MODULE_ALIAS("platform:samsung-pwm");
diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c
index cc63f9baa481..4a07315b0744 100644
--- a/drivers/pwm/pwm-sifive.c
+++ b/drivers/pwm/pwm-sifive.c
@@ -4,15 +4,33 @@
* For SiFive's PWM IP block documentation please refer Chapter 14 of
* Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf
*
+ * PWM output inversion: According to the SiFive Reference manual
+ * the output of each comparator is high whenever the value of pwms is
+ * greater than or equal to the corresponding pwmcmpX[Reference Manual].
+ *
+ * Figure 29 in the same manual shows that the pwmcmpXcenter bit is
+ * hard-tied to 0 (XNOR), which effectively inverts the comparison so that
+ * the output goes HIGH when `pwms < pwmcmpX`.
+ *
+ * In other words, each pwmcmp register actually defines the **inactive**
+ * (low) period of the pulse, not the active time exactly opposite to what
+ * the documentation text implies.
+ *
+ * To compensate, this driver always **inverts** the duty value when reading
+ * or writing pwmcmp registers , so that users interact with a conventional
+ * **active-high** PWM interface.
+ *
+ *
* 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 cannot generate a 0% duty cycle.
* - The hardware generates only inverted output.
*/
#include <linux/clk.h>
#include <linux/io.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
@@ -23,7 +41,7 @@
#define PWM_SIFIVE_PWMCFG 0x0
#define PWM_SIFIVE_PWMCOUNT 0x8
#define PWM_SIFIVE_PWMS 0x10
-#define PWM_SIFIVE_PWMCMP0 0x20
+#define PWM_SIFIVE_PWMCMP(i) (0x20 + 4 * (i))
/* PWMCFG fields */
#define PWM_SIFIVE_PWMCFG_SCALE GENMASK(3, 0)
@@ -36,14 +54,12 @@
#define PWM_SIFIVE_PWMCFG_GANG BIT(24)
#define PWM_SIFIVE_PWMCFG_IP BIT(28)
-/* PWM_SIFIVE_SIZE_PWMCMP is used to calculate offset for pwmcmpX registers */
-#define PWM_SIFIVE_SIZE_PWMCMP 4
#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 */
+ struct device *parent;
+ struct mutex lock; /* lock to protect user_count and approx_period */
struct notifier_block notifier;
struct clk *clk;
void __iomem *regs;
@@ -53,9 +69,9 @@ struct pwm_sifive_ddata {
};
static inline
-struct pwm_sifive_ddata *pwm_sifive_chip_to_ddata(struct pwm_chip *c)
+struct pwm_sifive_ddata *pwm_sifive_chip_to_ddata(struct pwm_chip *chip)
{
- return container_of(c, struct pwm_sifive_ddata, chip);
+ return pwmchip_get_drvdata(chip);
}
static int pwm_sifive_request(struct pwm_chip *chip, struct pwm_device *pwm)
@@ -78,6 +94,7 @@ static void pwm_sifive_free(struct pwm_chip *chip, struct pwm_device *pwm)
mutex_unlock(&ddata->lock);
}
+/* Called holding ddata->lock */
static void pwm_sifive_update_clock(struct pwm_sifive_ddata *ddata,
unsigned long rate)
{
@@ -101,19 +118,23 @@ static void pwm_sifive_update_clock(struct pwm_sifive_ddata *ddata,
/* 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,
+ ddata->real_period = DIV_ROUND_UP_ULL(num, rate);
+ dev_dbg(ddata->parent,
"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)
+static int 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;
+ u32 duty, val, inactive;
- duty = readl(ddata->regs + PWM_SIFIVE_PWMCMP0 +
- pwm->hwpwm * PWM_SIFIVE_SIZE_PWMCMP);
+ inactive = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
+ /*
+ * PWM hardware uses 'inactive' counts in pwmcmp, so invert to get actual duty.
+ * Here, 'inactive' is the low time and we compute duty as max_count - inactive.
+ */
+ duty = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - inactive;
state->enabled = duty > 0;
@@ -122,26 +143,9 @@ static void pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+ state->duty_cycle = DIV_ROUND_UP_ULL((u64)duty * ddata->real_period,
+ (1U << PWM_SIFIVE_CMPWIDTH));
+ state->polarity = PWM_POLARITY_NORMAL;
return 0;
}
@@ -155,18 +159,12 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
unsigned long long num;
bool enabled;
int ret = 0;
- u32 frac;
+ u64 frac;
+ u32 inactive;
- if (state->polarity != PWM_POLARITY_INVERSED)
+ if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
- ret = clk_enable(ddata->clk);
- if (ret) {
- dev_err(ddata->chip.dev, "Enable clk failed\n");
- return ret;
- }
-
- mutex_lock(&ddata->lock);
cur_state = pwm->state;
enabled = cur_state.enabled;
@@ -181,29 +179,49 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
* consecutively
*/
num = (u64)duty_cycle * (1U << PWM_SIFIVE_CMPWIDTH);
- frac = DIV_ROUND_CLOSEST_ULL(num, state->period);
- /* The hardware cannot generate a 100% duty cycle */
- frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1);
+ frac = num;
+ do_div(frac, state->period);
+ /* The hardware cannot generate a 0% duty cycle */
+ frac = min(frac, (u64)(1U << PWM_SIFIVE_CMPWIDTH) - 1);
+ /* pwmcmp register must be loaded with the inactive(invert the duty) */
+ inactive = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - frac;
+ mutex_lock(&ddata->lock);
if (state->period != ddata->approx_period) {
- if (ddata->user_count != 1) {
- ret = -EBUSY;
- goto exit;
+ /*
+ * 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);
+ return -EBUSY;
}
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_PWMCMP0 +
- pwm->hwpwm * PWM_SIFIVE_SIZE_PWMCMP);
+ /*
+ * If the PWM is enabled the clk is already on. So only enable it
+ * conditionally to have it on exactly once afterwards independent of
+ * the PWM state.
+ */
+ if (!enabled) {
+ ret = clk_enable(ddata->clk);
+ if (ret) {
+ dev_err(pwmchip_parent(chip), "Enable clk failed\n");
+ return ret;
+ }
+ }
- if (state->enabled != enabled)
- pwm_sifive_enable(chip, state->enabled);
+ writel(inactive, ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
-exit:
- clk_disable(ddata->clk);
- mutex_unlock(&ddata->lock);
- return ret;
+ if (!state->enabled)
+ clk_disable(ddata->clk);
+
+ return 0;
}
static const struct pwm_ops pwm_sifive_ops = {
@@ -211,7 +229,6 @@ static const struct pwm_ops pwm_sifive_ops = {
.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,
@@ -221,8 +238,11 @@ static int pwm_sifive_clock_notifier(struct notifier_block *nb,
struct pwm_sifive_ddata *ddata =
container_of(nb, struct pwm_sifive_ddata, notifier);
- if (event == POST_RATE_CHANGE)
+ if (event == POST_RATE_CHANGE) {
+ mutex_lock(&ddata->lock);
pwm_sifive_update_clock(ddata, ndata->new_rate);
+ mutex_unlock(&ddata->lock);
+ }
return NOTIFY_OK;
}
@@ -232,40 +252,61 @@ 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;
+ chip = devm_pwmchip_alloc(dev, 4, sizeof(*ddata));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ ddata = pwm_sifive_chip_to_ddata(chip);
+ ddata->parent = dev;
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);
+ ddata->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ddata->regs))
return PTR_ERR(ddata->regs);
- ddata->clk = devm_clk_get(dev, NULL);
- if (IS_ERR(ddata->clk)) {
- if (PTR_ERR(ddata->clk) != -EPROBE_DEFER)
- dev_err(dev, "Unable to find controller clock\n");
- return PTR_ERR(ddata->clk);
- }
+ ddata->clk = devm_clk_get_prepared(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);
+ ret = clk_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);
@@ -280,7 +321,7 @@ static int pwm_sifive_probe(struct platform_device *pdev)
goto unregister_clk;
}
- platform_set_drvdata(pdev, ddata);
+ platform_set_drvdata(pdev, chip);
dev_dbg(dev, "SiFive PWM chip registered %d PWMs\n", chip->npwm);
return 0;
@@ -288,33 +329,29 @@ static int pwm_sifive_probe(struct platform_device *pdev)
unregister_clk:
clk_notifier_unregister(ddata->clk, &ddata->notifier);
disable_clk:
- clk_disable_unprepare(ddata->clk);
+ while (enabled_clks) {
+ clk_disable(ddata->clk);
+ --enabled_clks;
+ }
return ret;
}
-static int pwm_sifive_remove(struct platform_device *dev)
+static void pwm_sifive_remove(struct platform_device *dev)
{
- struct pwm_sifive_ddata *ddata = platform_get_drvdata(dev);
- bool is_enabled = false;
+ struct pwm_chip *chip = platform_get_drvdata(dev);
+ struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip);
struct pwm_device *pwm;
- int ret, ch;
+ int ch;
- for (ch = 0; ch < ddata->chip.npwm; ch++) {
- pwm = &ddata->chip.pwms[ch];
- if (pwm->state.enabled) {
- is_enabled = true;
- break;
- }
- }
- if (is_enabled)
- clk_disable(ddata->clk);
-
- clk_disable_unprepare(ddata->clk);
- ret = pwmchip_remove(&ddata->chip);
+ pwmchip_remove(chip);
clk_notifier_unregister(ddata->clk, &ddata->notifier);
- return ret;
+ for (ch = 0; ch < chip->npwm; ch++) {
+ pwm = &chip->pwms[ch];
+ if (pwm->state.enabled)
+ clk_disable(ddata->clk);
+ }
}
static const struct of_device_id pwm_sifive_of_match[] = {
diff --git a/drivers/pwm/pwm-sl28cpld.c b/drivers/pwm/pwm-sl28cpld.c
new file mode 100644
index 000000000000..934378d6a002
--- /dev/null
+++ b/drivers/pwm/pwm-sl28cpld.c
@@ -0,0 +1,263 @@
+// 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/property.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 regmap *regmap;
+ u32 offset;
+};
+
+static inline struct sl28cpld_pwm *sl28cpld_pwm_from_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static int 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, &reg);
+
+ 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, &reg);
+ 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);
+
+ return 0;
+}
+
+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,
+};
+
+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;
+ }
+
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*priv));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ priv = sl28cpld_pwm_from_chip(chip);
+
+ 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->ops = &sl28cpld_pwm_ops;
+
+ ret = devm_pwmchip_add(&pdev->dev, chip);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add PWM chip (%pe)",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
+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,
+ .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-sophgo-sg2042.c b/drivers/pwm/pwm-sophgo-sg2042.c
new file mode 100644
index 000000000000..7d07b0ca7d29
--- /dev/null
+++ b/drivers/pwm/pwm-sophgo-sg2042.c
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Sophgo SG2042 PWM Controller Driver
+ *
+ * Copyright (C) 2024 Sophgo Technology Inc.
+ * Copyright (C) 2024 Chen Wang <unicorn_wang@outlook.com>
+ *
+ * Limitations:
+ * - After reset, the output of the PWM channel is always high.
+ * The value of HLPERIOD/PERIOD is 0.
+ * - When HLPERIOD or PERIOD is reconfigured, PWM will start to
+ * output waveforms with the new configuration after completing
+ * the running period.
+ * - When PERIOD and HLPERIOD is set to 0, the PWM wave output will
+ * be stopped and the output is pulled to high.
+ * - SG2044 supports both polarities, SG2042 only normal polarity.
+ * See the datasheet [1] for more details.
+ * [1]:https://github.com/sophgo/sophgo-doc/tree/main/SG2042/TRM
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/reset.h>
+
+/*
+ * Offset RegisterName
+ * 0x0000 HLPERIOD0
+ * 0x0004 PERIOD0
+ * 0x0008 HLPERIOD1
+ * 0x000C PERIOD1
+ * 0x0010 HLPERIOD2
+ * 0x0014 PERIOD2
+ * 0x0018 HLPERIOD3
+ * 0x001C PERIOD3
+ * Four groups and every group is composed of HLPERIOD & PERIOD
+ */
+#define SG2042_PWM_HLPERIOD(chan) ((chan) * 8 + 0)
+#define SG2042_PWM_PERIOD(chan) ((chan) * 8 + 4)
+
+#define SG2044_PWM_POLARITY 0x40
+#define SG2044_PWM_PWMSTART 0x44
+#define SG2044_PWM_OE 0xd0
+
+#define SG2042_PWM_CHANNELNUM 4
+
+/**
+ * struct sg2042_pwm_ddata - private driver data
+ * @base: base address of mapped PWM registers
+ * @clk_rate_hz: rate of base clock in HZ
+ */
+struct sg2042_pwm_ddata {
+ void __iomem *base;
+ unsigned long clk_rate_hz;
+};
+
+struct sg2042_chip_data {
+ const struct pwm_ops ops;
+};
+
+/*
+ * period_ticks: PERIOD
+ * hlperiod_ticks: HLPERIOD
+ */
+static void pwm_sg2042_config(struct sg2042_pwm_ddata *ddata, unsigned int chan,
+ u32 period_ticks, u32 hlperiod_ticks)
+{
+ void __iomem *base = ddata->base;
+
+ writel(period_ticks, base + SG2042_PWM_PERIOD(chan));
+ writel(hlperiod_ticks, base + SG2042_PWM_HLPERIOD(chan));
+}
+
+static void pwm_sg2042_set_dutycycle(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip);
+ u32 hlperiod_ticks;
+ u32 period_ticks;
+
+ /*
+ * Duration of High level (duty_cycle) = HLPERIOD x Period_of_input_clk
+ * Duration of One Cycle (period) = PERIOD x Period_of_input_clk
+ */
+ period_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->period, NSEC_PER_SEC), U32_MAX);
+ hlperiod_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->duty_cycle, NSEC_PER_SEC), U32_MAX);
+
+ dev_dbg(pwmchip_parent(chip), "chan[%u]: ENABLE=%u, PERIOD=%u, HLPERIOD=%u, POLARITY=%u\n",
+ pwm->hwpwm, state->enabled, period_ticks, hlperiod_ticks, state->polarity);
+
+ pwm_sg2042_config(ddata, pwm->hwpwm, period_ticks, hlperiod_ticks);
+}
+
+static int pwm_sg2042_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip);
+
+ if (state->polarity == PWM_POLARITY_INVERSED)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ pwm_sg2042_config(ddata, pwm->hwpwm, 0, 0);
+ return 0;
+ }
+
+ pwm_sg2042_set_dutycycle(chip, pwm, state);
+
+ return 0;
+}
+
+static int pwm_sg2042_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip);
+ unsigned int chan = pwm->hwpwm;
+ u32 hlperiod_ticks;
+ u32 period_ticks;
+
+ period_ticks = readl(ddata->base + SG2042_PWM_PERIOD(chan));
+ hlperiod_ticks = readl(ddata->base + SG2042_PWM_HLPERIOD(chan));
+
+ if (!period_ticks) {
+ state->enabled = false;
+ return 0;
+ }
+
+ if (hlperiod_ticks > period_ticks)
+ hlperiod_ticks = period_ticks;
+
+ state->enabled = true;
+ state->period = DIV_ROUND_UP_ULL((u64)period_ticks * NSEC_PER_SEC, ddata->clk_rate_hz);
+ state->duty_cycle = DIV_ROUND_UP_ULL((u64)hlperiod_ticks * NSEC_PER_SEC, ddata->clk_rate_hz);
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ return 0;
+}
+
+static void pwm_sg2044_set_outputen(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm,
+ bool enabled)
+{
+ u32 pwmstart;
+
+ pwmstart = readl(ddata->base + SG2044_PWM_PWMSTART);
+
+ if (enabled)
+ pwmstart |= BIT(pwm->hwpwm);
+ else
+ pwmstart &= ~BIT(pwm->hwpwm);
+
+ writel(pwmstart, ddata->base + SG2044_PWM_PWMSTART);
+}
+
+static void pwm_sg2044_set_outputdir(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm,
+ bool enabled)
+{
+ u32 pwm_oe;
+
+ pwm_oe = readl(ddata->base + SG2044_PWM_OE);
+
+ if (enabled)
+ pwm_oe |= BIT(pwm->hwpwm);
+ else
+ pwm_oe &= ~BIT(pwm->hwpwm);
+
+ writel(pwm_oe, ddata->base + SG2044_PWM_OE);
+}
+
+static void pwm_sg2044_set_polarity(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ u32 pwm_polarity;
+
+ pwm_polarity = readl(ddata->base + SG2044_PWM_POLARITY);
+
+ if (state->polarity == PWM_POLARITY_NORMAL)
+ pwm_polarity &= ~BIT(pwm->hwpwm);
+ else
+ pwm_polarity |= BIT(pwm->hwpwm);
+
+ writel(pwm_polarity, ddata->base + SG2044_PWM_POLARITY);
+}
+
+static int pwm_sg2044_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip);
+
+ pwm_sg2044_set_polarity(ddata, pwm, state);
+
+ pwm_sg2042_set_dutycycle(chip, pwm, state);
+
+ /*
+ * re-enable PWMSTART to refresh the register period
+ */
+ pwm_sg2044_set_outputen(ddata, pwm, false);
+
+ if (!state->enabled)
+ return 0;
+
+ pwm_sg2044_set_outputdir(ddata, pwm, true);
+ pwm_sg2044_set_outputen(ddata, pwm, true);
+
+ return 0;
+}
+
+static const struct sg2042_chip_data sg2042_chip_data = {
+ .ops = {
+ .apply = pwm_sg2042_apply,
+ .get_state = pwm_sg2042_get_state,
+ },
+};
+
+static const struct sg2042_chip_data sg2044_chip_data = {
+ .ops = {
+ .apply = pwm_sg2044_apply,
+ .get_state = pwm_sg2042_get_state,
+ },
+};
+
+static const struct of_device_id sg2042_pwm_ids[] = {
+ {
+ .compatible = "sophgo,sg2042-pwm",
+ .data = &sg2042_chip_data
+ },
+ {
+ .compatible = "sophgo,sg2044-pwm",
+ .data = &sg2044_chip_data
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sg2042_pwm_ids);
+
+static int pwm_sg2042_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct sg2042_chip_data *chip_data;
+ struct sg2042_pwm_ddata *ddata;
+ struct reset_control *rst;
+ struct pwm_chip *chip;
+ struct clk *clk;
+ int ret;
+
+ chip_data = device_get_match_data(dev);
+ if (!chip_data)
+ return -ENODEV;
+
+ chip = devm_pwmchip_alloc(dev, SG2042_PWM_CHANNELNUM, sizeof(*ddata));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ ddata = pwmchip_get_drvdata(chip);
+
+ ddata->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ddata->base))
+ return PTR_ERR(ddata->base);
+
+ clk = devm_clk_get_enabled(dev, "apb");
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Failed to get base clk\n");
+
+ ret = devm_clk_rate_exclusive_get(dev, clk);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get exclusive rate\n");
+
+ ddata->clk_rate_hz = clk_get_rate(clk);
+ /* period = PERIOD * NSEC_PER_SEC / clk_rate_hz */
+ if (!ddata->clk_rate_hz || ddata->clk_rate_hz > NSEC_PER_SEC)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid clock rate: %lu\n", ddata->clk_rate_hz);
+
+ rst = devm_reset_control_get_optional_shared_deasserted(dev, NULL);
+ if (IS_ERR(rst))
+ return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n");
+
+ chip->ops = &chip_data->ops;
+ chip->atomic = true;
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to register PWM chip\n");
+
+ return 0;
+}
+
+static struct platform_driver pwm_sg2042_driver = {
+ .driver = {
+ .name = "sg2042-pwm",
+ .of_match_table = sg2042_pwm_ids,
+ },
+ .probe = pwm_sg2042_probe,
+};
+module_platform_driver(pwm_sg2042_driver);
+
+MODULE_AUTHOR("Chen Wang");
+MODULE_AUTHOR("Longbin Li <looong.bin@gmail.com>");
+MODULE_DESCRIPTION("Sophgo SG2042 PWM driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-spear.c b/drivers/pwm/pwm-spear.c
index 6c6b44fd3f43..4f372279f313 100644
--- a/drivers/pwm/pwm-spear.c
+++ b/drivers/pwm/pwm-spear.c
@@ -48,17 +48,15 @@
*
* @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);
+ return pwmchip_get_drvdata(chip);
}
static inline u32 spear_pwm_readl(struct spear_pwm_chip *chip, unsigned int num,
@@ -75,7 +73,7 @@ static inline void spear_pwm_writel(struct spear_pwm_chip *chip,
}
static int spear_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
- int duty_ns, int period_ns)
+ u64 duty_ns, u64 period_ns)
{
struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
u64 val, div, clk_rate;
@@ -163,51 +161,64 @@ static void spear_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
clk_disable(pc->clk);
}
+static int spear_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ if (pwm->state.enabled)
+ spear_pwm_disable(chip, pwm);
+ return 0;
+ }
+
+ err = spear_pwm_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!pwm->state.enabled)
+ return spear_pwm_enable(chip, pwm);
+
+ return 0;
+}
+
static const struct pwm_ops spear_pwm_ops = {
- .config = spear_pwm_config,
- .enable = spear_pwm_enable,
- .disable = spear_pwm_disable,
- .owner = THIS_MODULE,
+ .apply = spear_pwm_apply,
};
static int spear_pwm_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
+ struct pwm_chip *chip;
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;
+ chip = devm_pwmchip_alloc(&pdev->dev, NUM_PWM, sizeof(*pc));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ pc = to_spear_pwm_chip(chip);
- r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- pc->mmio_base = devm_ioremap_resource(&pdev->dev, r);
+ pc->mmio_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(pc->mmio_base))
return PTR_ERR(pc->mmio_base);
- pc->clk = devm_clk_get(&pdev->dev, NULL);
+ pc->clk = devm_clk_get_prepared(&pdev->dev, NULL);
if (IS_ERR(pc->clk))
- return PTR_ERR(pc->clk);
-
- platform_set_drvdata(pdev, pc);
+ return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk),
+ "Failed to get clock\n");
- 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;
+ chip->ops = &spear_pwm_ops;
if (of_device_is_compatible(np, "st,spear1340-pwm")) {
ret = clk_enable(pc->clk);
- if (ret) {
- clk_unprepare(pc->clk);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to enable clk\n");
+
/*
* Following enables PWM chip, channels would still be
* enabled individually through their control register
@@ -219,26 +230,11 @@ static int spear_pwm_probe(struct platform_device *pdev)
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);
- }
+ ret = devm_pwmchip_add(&pdev->dev, chip);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "pwmchip_add() failed\n");
- return ret;
-}
-
-static int spear_pwm_remove(struct platform_device *pdev)
-{
- struct spear_pwm_chip *pc = platform_get_drvdata(pdev);
- int i;
-
- for (i = 0; i < NUM_PWM; i++)
- pwm_disable(&pc->chip.pwms[i]);
-
- /* clk was prepared in probe, hence unprepare it here */
- clk_unprepare(pc->clk);
- return pwmchip_remove(&pc->chip);
+ return 0;
}
static const struct of_device_id spear_pwm_of_match[] = {
@@ -255,11 +251,11 @@ static struct platform_driver spear_pwm_driver = {
.of_match_table = spear_pwm_of_match,
},
.probe = spear_pwm_probe,
- .remove = spear_pwm_remove,
};
module_platform_driver(spear_pwm_driver);
+MODULE_DESCRIPTION("ST Microelectronics SPEAr Pulse Width Modulator driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shiraz Hashim <shiraz.linux.kernel@gmail.com>");
MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.com>");
diff --git a/drivers/pwm/pwm-sprd.c b/drivers/pwm/pwm-sprd.c
index be2394227423..4c76ca5e4cdd 100644
--- a/drivers/pwm/pwm-sprd.c
+++ b/drivers/pwm/pwm-sprd.c
@@ -7,6 +7,7 @@
#include <linux/err.h>
#include <linux/io.h>
#include <linux/math64.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
@@ -33,12 +34,14 @@ struct sprd_pwm_chn {
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];
};
+static inline struct sprd_pwm_chip* sprd_pwm_from_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
/*
* The list of clocks required by PWM channels, and each channel has 2 clocks:
* enable clock and pwm clock.
@@ -65,11 +68,10 @@ static void sprd_pwm_write(struct sprd_pwm_chip *spc, u32 hwid,
writel_relaxed(val, spc->base + offset);
}
-static void sprd_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
- struct pwm_state *state)
+static int 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_chip *spc = sprd_pwm_from_chip(chip);
struct sprd_pwm_chn *chn = &spc->chn[pwm->hwpwm];
u32 val, duty, prescale;
u64 tmp;
@@ -81,9 +83,9 @@ static void sprd_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
*/
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",
+ dev_err(pwmchip_parent(chip), "failed to enable pwm%u clocks\n",
pwm->hwpwm);
- return;
+ return ret;
}
val = sprd_pwm_read(spc, pwm->hwpwm, SPRD_PWM_ENABLE);
@@ -109,10 +111,13 @@ static void sprd_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+
+ return 0;
}
static int sprd_pwm_config(struct sprd_pwm_chip *spc, struct pwm_device *pwm,
@@ -158,12 +163,14 @@ static int sprd_pwm_config(struct sprd_pwm_chip *spc, struct pwm_device *pwm,
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_chip *spc = sprd_pwm_from_chip(chip);
struct sprd_pwm_chn *chn = &spc->chn[pwm->hwpwm];
struct pwm_state *cstate = &pwm->state;
int ret;
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
if (state->enabled) {
if (!cstate->enabled) {
/*
@@ -173,20 +180,17 @@ static int sprd_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
ret = clk_bulk_prepare_enable(SPRD_PWM_CHN_CLKS_NUM,
chn->clks);
if (ret) {
- dev_err(spc->dev,
+ dev_err(pwmchip_parent(chip),
"failed to enable pwm%u clocks\n",
pwm->hwpwm);
return ret;
}
}
- if (state->period != cstate->period ||
- state->duty_cycle != cstate->duty_cycle) {
- ret = sprd_pwm_config(spc, pwm, state->duty_cycle,
- state->period);
- if (ret)
- 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) {
@@ -206,88 +210,72 @@ static int sprd_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
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)
+static int sprd_pwm_clk_init(struct device *dev,
+ struct sprd_pwm_chn chn[SPRD_PWM_CHN_NUM])
{
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 =
+ chn[i].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);
+ ret = devm_clk_bulk_get(dev, SPRD_PWM_CHN_CLKS_NUM,
+ chn[i].clks);
if (ret) {
if (ret == -ENOENT)
break;
- if (ret != -EPROBE_DEFER)
- dev_err(spc->dev,
- "failed to get channel clocks\n");
-
- return ret;
+ return dev_err_probe(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);
+ clk_pwm = chn[i].clks[SPRD_PWM_CHN_OUTPUT_CLK].clk;
+ chn[i].clk_rate = clk_get_rate(clk_pwm);
}
- if (!i) {
- dev_err(spc->dev, "no available PWM channels\n");
- return -ENODEV;
- }
+ if (!i)
+ return dev_err_probe(dev, -ENODEV, "no available PWM channels\n");
- spc->num_pwms = i;
-
- return 0;
+ return i;
}
static int sprd_pwm_probe(struct platform_device *pdev)
{
+ struct pwm_chip *chip;
struct sprd_pwm_chip *spc;
- int ret;
+ struct sprd_pwm_chn chn[SPRD_PWM_CHN_NUM];
+ int ret, npwm;
+
+ npwm = sprd_pwm_clk_init(&pdev->dev, chn);
+ if (npwm < 0)
+ return npwm;
- spc = devm_kzalloc(&pdev->dev, sizeof(*spc), GFP_KERNEL);
- if (!spc)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, npwm, sizeof(*spc));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ spc = sprd_pwm_from_chip(chip);
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;
+ memcpy(spc->chn, chn, sizeof(chn));
- spc->chip.dev = &pdev->dev;
- spc->chip.ops = &sprd_pwm_ops;
- spc->chip.base = -1;
- spc->chip.npwm = spc->num_pwms;
+ chip->ops = &sprd_pwm_ops;
- ret = pwmchip_add(&spc->chip);
+ ret = devm_pwmchip_add(&pdev->dev, 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", },
{ },
@@ -300,7 +288,6 @@ static struct platform_driver sprd_pwm_driver = {
.of_match_table = sprd_pwm_of_match,
},
.probe = sprd_pwm_probe,
- .remove = sprd_pwm_remove,
};
module_platform_driver(sprd_pwm_driver);
diff --git a/drivers/pwm/pwm-sti.c b/drivers/pwm/pwm-sti.c
index 1508616d794c..3b702b8f0c7f 100644
--- a/drivers/pwm/pwm-sti.c
+++ b/drivers/pwm/pwm-sti.c
@@ -73,31 +73,25 @@ struct sti_cpt_ddata {
wait_queue_head_t wait;
};
-struct sti_pwm_compat_data {
- const struct reg_field *reg_fields;
- unsigned int pwm_num_devs;
- unsigned int cpt_num_devs;
- unsigned int max_pwm_cnt;
- unsigned int max_prescale;
-};
-
struct sti_pwm_chip {
struct device *dev;
struct clk *pwm_clk;
struct clk *cpt_clk;
struct regmap *regmap;
- struct sti_pwm_compat_data *cdata;
+ unsigned int pwm_num_devs;
+ unsigned int cpt_num_devs;
+ unsigned int max_pwm_cnt;
+ unsigned int max_prescale;
+ struct sti_cpt_ddata *ddata;
struct regmap_field *prescale_low;
struct regmap_field *prescale_high;
struct regmap_field *pwm_out_en;
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;
};
@@ -113,7 +107,7 @@ static const struct reg_field sti_pwm_regfields[MAX_REGFIELDS] = {
static inline struct sti_pwm_chip *to_sti_pwmchip(struct pwm_chip *chip)
{
- return container_of(chip, struct sti_pwm_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
/*
@@ -122,7 +116,6 @@ static inline struct sti_pwm_chip *to_sti_pwmchip(struct pwm_chip *chip)
static int sti_pwm_get_prescale(struct sti_pwm_chip *pc, unsigned long period,
unsigned int *prescale)
{
- struct sti_pwm_compat_data *cdata = pc->cdata;
unsigned long clk_rate;
unsigned long value;
unsigned int ps;
@@ -137,13 +130,13 @@ static int sti_pwm_get_prescale(struct sti_pwm_chip *pc, unsigned long period,
* prescale = ((period_ns * clk_rate) / (10^9 * (max_pwm_cnt + 1)) - 1
*/
value = NSEC_PER_SEC / clk_rate;
- value *= cdata->max_pwm_cnt + 1;
+ value *= pc->max_pwm_cnt + 1;
if (period % value)
return -EINVAL;
ps = period / value - 1;
- if (ps > cdata->max_prescale)
+ if (ps > pc->max_prescale)
return -EINVAL;
*prescale = ps;
@@ -164,7 +157,6 @@ static int sti_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct sti_pwm_chip *pc = to_sti_pwmchip(chip);
- struct sti_pwm_compat_data *cdata = pc->cdata;
unsigned int ncfg, value, prescale = 0;
struct pwm_device *cur = pc->cur;
struct device *dev = pc->dev;
@@ -224,7 +216,7 @@ static int sti_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* PWM pulse = (max_pwm_count + 1) local cycles,
* that is continuous pulse: signal never goes low.
*/
- value = cdata->max_pwm_cnt * duty_ns / period_ns;
+ value = pc->max_pwm_cnt * duty_ns / period_ns;
ret = regmap_write(pc->regmap, PWM_OUT_VAL(pwm->hwpwm), value);
if (ret)
@@ -251,55 +243,46 @@ 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;
+ int ret;
/*
* 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;
+ return ret;
ret = clk_enable(pc->cpt_clk);
if (ret)
- goto out;
+ return ret;
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;
+ return ret;
}
}
pc->en_count++;
-out:
- mutex_unlock(&pc->sti_pwm_lock);
- return ret;
+ return 0;
}
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);
+ if (--pc->en_count)
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)
@@ -313,14 +296,13 @@ static int sti_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_capture *result, unsigned long timeout)
{
struct sti_pwm_chip *pc = to_sti_pwmchip(chip);
- struct sti_pwm_compat_data *cdata = pc->cdata;
- struct sti_cpt_ddata *ddata = pwm_get_chip_data(pwm);
+ struct sti_cpt_ddata *ddata = &pc->ddata[pwm->hwpwm];
struct device *dev = pc->dev;
unsigned int effective_ticks;
unsigned long long high, low;
int ret;
- if (pwm->hwpwm >= cdata->cpt_num_devs) {
+ if (pwm->hwpwm >= pc->cpt_num_devs) {
dev_err(dev, "device %u is not valid\n", pwm->hwpwm);
return -EINVAL;
}
@@ -391,13 +373,43 @@ out:
return ret;
}
+static int sti_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct sti_pwm_chip *pc = to_sti_pwmchip(chip);
+ struct device *dev = pc->dev;
+ int err;
+
+ if (pwm->hwpwm >= pc->pwm_num_devs) {
+ dev_err(dev, "device %u is not valid for pwm mode\n",
+ pwm->hwpwm);
+ return -EINVAL;
+ }
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ if (pwm->state.enabled)
+ sti_pwm_disable(chip, pwm);
+
+ return 0;
+ }
+
+ err = sti_pwm_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!pwm->state.enabled)
+ err = sti_pwm_enable(chip, pwm);
+
+ return err;
+}
+
static const struct pwm_ops sti_pwm_ops = {
.capture = sti_pwm_capture,
- .config = sti_pwm_config,
- .enable = sti_pwm_enable,
- .disable = sti_pwm_disable,
+ .apply = sti_pwm_apply,
.free = sti_pwm_free,
- .owner = THIS_MODULE,
};
static irqreturn_t sti_pwm_interrupt(int irq, void *data)
@@ -417,7 +429,7 @@ static irqreturn_t sti_pwm_interrupt(int irq, void *data)
while (cpt_int_stat) {
devicenum = ffs(cpt_int_stat) - 1;
- ddata = pwm_get_chip_data(&pc->chip.pwms[devicenum]);
+ ddata = &pc->ddata[devicenum];
/*
* Capture input:
@@ -471,58 +483,37 @@ static irqreturn_t sti_pwm_interrupt(int irq, void *data)
return ret;
}
-static int sti_pwm_probe_dt(struct sti_pwm_chip *pc)
+static int sti_pwm_probe_regmap(struct sti_pwm_chip *pc)
{
struct device *dev = pc->dev;
- const struct reg_field *reg_fields;
- struct 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]);
+ sti_pwm_regfields[PWMCLK_PRESCALE_LOW]);
if (IS_ERR(pc->prescale_low))
return PTR_ERR(pc->prescale_low);
pc->prescale_high = devm_regmap_field_alloc(dev, pc->regmap,
- reg_fields[PWMCLK_PRESCALE_HIGH]);
+ sti_pwm_regfields[PWMCLK_PRESCALE_HIGH]);
if (IS_ERR(pc->prescale_high))
return PTR_ERR(pc->prescale_high);
-
pc->pwm_out_en = devm_regmap_field_alloc(dev, pc->regmap,
- reg_fields[PWM_OUT_EN]);
+ sti_pwm_regfields[PWM_OUT_EN]);
if (IS_ERR(pc->pwm_out_en))
return PTR_ERR(pc->pwm_out_en);
pc->pwm_cpt_en = devm_regmap_field_alloc(dev, pc->regmap,
- reg_fields[PWM_CPT_EN]);
+ sti_pwm_regfields[PWM_CPT_EN]);
if (IS_ERR(pc->pwm_cpt_en))
return PTR_ERR(pc->pwm_cpt_en);
pc->pwm_cpt_int_en = devm_regmap_field_alloc(dev, pc->regmap,
- reg_fields[PWM_CPT_INT_EN]);
+ sti_pwm_regfields[PWM_CPT_INT_EN]);
if (IS_ERR(pc->pwm_cpt_int_en))
return PTR_ERR(pc->pwm_cpt_int_en);
pc->pwm_cpt_int_stat = devm_regmap_field_alloc(dev, pc->regmap,
- reg_fields[PWM_CPT_INT_STAT]);
+ sti_pwm_regfields[PWM_CPT_INT_STAT]);
if (PTR_ERR_OR_ZERO(pc->pwm_cpt_int_stat))
return PTR_ERR(pc->pwm_cpt_int_stat);
@@ -538,30 +529,40 @@ static const struct regmap_config sti_pwm_regmap_config = {
static int sti_pwm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct sti_pwm_compat_data *cdata;
+ struct device_node *np = dev->of_node;
+ u32 num_devs;
+ unsigned int pwm_num_devs = 0;
+ unsigned int cpt_num_devs = 0;
+ struct pwm_chip *chip;
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;
+ ret = of_property_read_u32(np, "st,pwm-num-chan", &num_devs);
+ if (!ret)
+ pwm_num_devs = num_devs;
+
+ ret = of_property_read_u32(np, "st,capture-num-chan", &num_devs);
+ if (!ret)
+ cpt_num_devs = num_devs;
- cdata = devm_kzalloc(dev, sizeof(*cdata), GFP_KERNEL);
- if (!cdata)
- return -ENOMEM;
+ if (!pwm_num_devs && !cpt_num_devs)
+ return dev_err_probe(dev, -EINVAL, "No channels configured\n");
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ chip = devm_pwmchip_alloc(dev, max(pwm_num_devs, cpt_num_devs), sizeof(*pc));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ pc = to_sti_pwmchip(chip);
- pc->mmio = devm_ioremap_resource(dev, res);
+ pc->mmio = devm_platform_ioremap_resource(pdev, 0);
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);
+ return dev_err_probe(dev, PTR_ERR(pc->regmap),
+ "Failed to initialize regmap\n");
irq = platform_get_irq(pdev, 0);
if (irq < 0)
@@ -569,104 +570,58 @@ static int sti_pwm_probe(struct platform_device *pdev)
ret = devm_request_irq(&pdev->dev, irq, sti_pwm_interrupt, 0,
pdev->name, pc);
- if (ret < 0) {
- dev_err(&pdev->dev, "Failed to request IRQ\n");
- return ret;
- }
+ if (ret < 0)
+ dev_err_probe(&pdev->dev, ret, "Failed to request IRQ\n");
/*
* Setup PWM data with default values: some values could be replaced
* with specific ones provided from Device Tree.
*/
- cdata->reg_fields = sti_pwm_regfields;
- cdata->max_prescale = 0xff;
- cdata->max_pwm_cnt = 255;
- cdata->pwm_num_devs = 0;
- cdata->cpt_num_devs = 0;
+ pc->max_prescale = 0xff;
+ pc->max_pwm_cnt = 255;
+ pc->pwm_num_devs = pwm_num_devs;
+ pc->cpt_num_devs = cpt_num_devs;
- pc->cdata = cdata;
pc->dev = dev;
pc->en_count = 0;
- mutex_init(&pc->sti_pwm_lock);
- ret = sti_pwm_probe_dt(pc);
+ ret = sti_pwm_probe_regmap(pc);
if (ret)
- return ret;
+ return dev_err_probe(dev, ret, "Failed to initialize regmap fields\n");
- if (!cdata->pwm_num_devs)
- goto skip_pwm;
-
- pc->pwm_clk = of_clk_get_by_name(dev->of_node, "pwm");
- if (IS_ERR(pc->pwm_clk)) {
- dev_err(dev, "failed to get PWM clock\n");
- return PTR_ERR(pc->pwm_clk);
- }
-
- ret = clk_prepare(pc->pwm_clk);
- if (ret) {
- dev_err(dev, "failed to prepare clock\n");
- return ret;
+ if (pwm_num_devs) {
+ pc->pwm_clk = devm_clk_get_prepared(dev, "pwm");
+ if (IS_ERR(pc->pwm_clk))
+ return dev_err_probe(dev, PTR_ERR(pc->pwm_clk),
+ "failed to get PWM clock\n");
}
-skip_pwm:
- if (!cdata->cpt_num_devs)
- goto skip_cpt;
+ if (cpt_num_devs) {
+ pc->cpt_clk = devm_clk_get_prepared(dev, "capture");
+ if (IS_ERR(pc->cpt_clk))
+ return dev_err_probe(dev, PTR_ERR(pc->cpt_clk),
+ "failed to get PWM capture clock\n");
- 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;
- }
-
-skip_cpt:
- pc->chip.dev = dev;
- pc->chip.ops = &sti_pwm_ops;
- pc->chip.base = -1;
- pc->chip.npwm = pc->cdata->pwm_num_devs;
-
- ret = pwmchip_add(&pc->chip);
- if (ret < 0) {
- clk_unprepare(pc->pwm_clk);
- clk_unprepare(pc->cpt_clk);
- return ret;
- }
-
- for (i = 0; i < cdata->cpt_num_devs; i++) {
- struct sti_cpt_ddata *ddata;
-
- ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
- if (!ddata)
+ pc->ddata = devm_kcalloc(dev, cpt_num_devs,
+ sizeof(*pc->ddata), GFP_KERNEL);
+ if (!pc->ddata)
return -ENOMEM;
- init_waitqueue_head(&ddata->wait);
- mutex_init(&ddata->lock);
+ for (i = 0; i < cpt_num_devs; i++) {
+ struct sti_cpt_ddata *ddata = &pc->ddata[i];
- pwm_set_chip_data(&pc->chip.pwms[i], ddata);
+ init_waitqueue_head(&ddata->wait);
+ mutex_init(&ddata->lock);
+ }
}
- 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]);
+ chip->ops = &sti_pwm_ops;
- clk_unprepare(pc->pwm_clk);
- clk_unprepare(pc->cpt_clk);
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register pwm chip\n");
- return pwmchip_remove(&pc->chip);
+ return 0;
}
static const struct of_device_id sti_pwm_of_match[] = {
@@ -681,7 +636,6 @@ static struct platform_driver sti_pwm_driver = {
.of_match_table = sti_pwm_of_match,
},
.probe = sti_pwm_probe,
- .remove = sti_pwm_remove,
};
module_platform_driver(sti_pwm_driver);
diff --git a/drivers/pwm/pwm-stm32-lp.c b/drivers/pwm/pwm-stm32-lp.c
index 67fca62524dc..4789eafb8bac 100644
--- a/drivers/pwm/pwm-stm32-lp.c
+++ b/drivers/pwm/pwm-stm32-lp.c
@@ -18,26 +18,114 @@
#include <linux/pwm.h>
struct stm32_pwm_lp {
- struct pwm_chip chip;
struct clk *clk;
struct regmap *regmap;
+ unsigned int num_cc_chans;
};
static inline struct stm32_pwm_lp *to_stm32_pwm_lp(struct pwm_chip *chip)
{
- return container_of(chip, struct stm32_pwm_lp, chip);
+ return pwmchip_get_drvdata(chip);
}
/* STM32 Low-Power Timer is preceded by a configurable power-of-2 prescaler */
#define STM32_LPTIM_MAX_PRESCALER 128
+static int stm32_pwm_lp_update_allowed(struct stm32_pwm_lp *priv, int channel)
+{
+ int ret;
+ u32 ccmr1;
+ unsigned long ccmr;
+
+ /* Only one PWM on this LPTIMER: enable, prescaler and reload value can be changed */
+ if (!priv->num_cc_chans)
+ return true;
+
+ ret = regmap_read(priv->regmap, STM32_LPTIM_CCMR1, &ccmr1);
+ if (ret)
+ return ret;
+ ccmr = ccmr1 & (STM32_LPTIM_CC1E | STM32_LPTIM_CC2E);
+
+ /* More than one channel enabled: enable, prescaler or ARR value can't be changed */
+ if (bitmap_weight(&ccmr, sizeof(u32) * BITS_PER_BYTE) > 1)
+ return false;
+
+ /*
+ * Only one channel is enabled (or none): check status on the other channel, to
+ * report if enable, prescaler or ARR value can be changed.
+ */
+ if (channel)
+ return !(ccmr1 & STM32_LPTIM_CC1E);
+ else
+ return !(ccmr1 & STM32_LPTIM_CC2E);
+}
+
+static int stm32_pwm_lp_compare_channel_apply(struct stm32_pwm_lp *priv, int channel,
+ bool enable, enum pwm_polarity polarity)
+{
+ u32 ccmr1, val, mask;
+ bool reenable;
+ int ret;
+
+ /* No dedicated CC channel: nothing to do */
+ if (!priv->num_cc_chans)
+ return 0;
+
+ ret = regmap_read(priv->regmap, STM32_LPTIM_CCMR1, &ccmr1);
+ if (ret)
+ return ret;
+
+ if (channel) {
+ /* Must disable CC channel (CCxE) to modify polarity (CCxP), then re-enable */
+ reenable = (enable && FIELD_GET(STM32_LPTIM_CC2E, ccmr1)) &&
+ (polarity != FIELD_GET(STM32_LPTIM_CC2P, ccmr1));
+
+ mask = STM32_LPTIM_CC2SEL | STM32_LPTIM_CC2E | STM32_LPTIM_CC2P;
+ val = FIELD_PREP(STM32_LPTIM_CC2P, polarity);
+ val |= FIELD_PREP(STM32_LPTIM_CC2E, enable);
+ } else {
+ reenable = (enable && FIELD_GET(STM32_LPTIM_CC1E, ccmr1)) &&
+ (polarity != FIELD_GET(STM32_LPTIM_CC1P, ccmr1));
+
+ mask = STM32_LPTIM_CC1SEL | STM32_LPTIM_CC1E | STM32_LPTIM_CC1P;
+ val = FIELD_PREP(STM32_LPTIM_CC1P, polarity);
+ val |= FIELD_PREP(STM32_LPTIM_CC1E, enable);
+ }
+
+ if (reenable) {
+ u32 cfgr, presc;
+ unsigned long rate;
+ unsigned int delay_us;
+
+ ret = regmap_update_bits(priv->regmap, STM32_LPTIM_CCMR1,
+ channel ? STM32_LPTIM_CC2E : STM32_LPTIM_CC1E, 0);
+ if (ret)
+ return ret;
+ /*
+ * After a write to the LPTIM_CCMRx register, a new write operation can only be
+ * performed after a delay of at least (PRESC × 3) clock cycles
+ */
+ ret = regmap_read(priv->regmap, STM32_LPTIM_CFGR, &cfgr);
+ if (ret)
+ return ret;
+ presc = FIELD_GET(STM32_LPTIM_PRESC, cfgr);
+ rate = clk_get_rate(priv->clk) >> presc;
+ if (!rate)
+ return -EINVAL;
+ delay_us = 3 * DIV_ROUND_UP(USEC_PER_SEC, rate);
+ usleep_range(delay_us, delay_us * 2);
+ }
+
+ return regmap_update_bits(priv->regmap, STM32_LPTIM_CCMR1, mask, val);
+}
+
static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct stm32_pwm_lp *priv = to_stm32_pwm_lp(chip);
unsigned long long prd, div, dty;
struct pwm_state cstate;
- u32 val, mask, cfgr, presc = 0;
+ u32 arr, val, mask, cfgr, presc = 0;
bool reenable;
int ret;
@@ -46,10 +134,28 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm,
if (!state->enabled) {
if (cstate.enabled) {
- /* Disable LP timer */
- ret = regmap_write(priv->regmap, STM32_LPTIM_CR, 0);
+ /* Disable CC channel if any */
+ ret = stm32_pwm_lp_compare_channel_apply(priv, pwm->hwpwm, false,
+ state->polarity);
+ if (ret)
+ return ret;
+ ret = regmap_write(priv->regmap, pwm->hwpwm ?
+ STM32_LPTIM_CCR2 : STM32_LPTIM_CMP, 0);
if (ret)
return ret;
+
+ /* Check if the timer can be disabled */
+ ret = stm32_pwm_lp_update_allowed(priv, pwm->hwpwm);
+ if (ret < 0)
+ return ret;
+
+ if (ret) {
+ /* Disable LP timer */
+ ret = regmap_write(priv->regmap, STM32_LPTIM_CR, 0);
+ if (ret)
+ return ret;
+ }
+
/* disable clock to PWM counter */
clk_disable(priv->clk);
}
@@ -61,7 +167,7 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm,
do_div(div, NSEC_PER_SEC);
if (!div) {
/* Clock is too slow to achieve requested period. */
- dev_dbg(priv->chip.dev, "Can't reach %u ns\n", state->period);
+ dev_dbg(pwmchip_parent(chip), "Can't reach %llu ns\n", state->period);
return -EINVAL;
}
@@ -69,7 +175,7 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm,
while (div > STM32_LPTIM_MAX_ARR) {
presc++;
if ((1 << presc) > STM32_LPTIM_MAX_PRESCALER) {
- dev_err(priv->chip.dev, "max prescaler exceeded\n");
+ dev_err(pwmchip_parent(chip), "max prescaler exceeded\n");
return -EINVAL;
}
div = prd >> presc;
@@ -80,6 +186,23 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm,
dty = prd * state->duty_cycle;
do_div(dty, state->period);
+ ret = regmap_read(priv->regmap, STM32_LPTIM_CFGR, &cfgr);
+ if (ret)
+ return ret;
+
+ /*
+ * When there are several channels, they share the same prescaler and reload value.
+ * Check if this can be changed, or the values are the same for all channels.
+ */
+ if (!stm32_pwm_lp_update_allowed(priv, pwm->hwpwm)) {
+ ret = regmap_read(priv->regmap, STM32_LPTIM_ARR, &arr);
+ if (ret)
+ return ret;
+
+ if ((FIELD_GET(STM32_LPTIM_PRESC, cfgr) != presc) || (arr != prd - 1))
+ return -EBUSY;
+ }
+
if (!cstate.enabled) {
/* enable clock to drive PWM counter */
ret = clk_enable(priv->clk);
@@ -87,15 +210,20 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm,
return ret;
}
- ret = regmap_read(priv->regmap, STM32_LPTIM_CFGR, &cfgr);
- if (ret)
- goto err;
-
if ((FIELD_GET(STM32_LPTIM_PRESC, cfgr) != presc) ||
- (FIELD_GET(STM32_LPTIM_WAVPOL, cfgr) != state->polarity)) {
+ ((FIELD_GET(STM32_LPTIM_WAVPOL, cfgr) != state->polarity) && !priv->num_cc_chans)) {
val = FIELD_PREP(STM32_LPTIM_PRESC, presc);
- val |= FIELD_PREP(STM32_LPTIM_WAVPOL, state->polarity);
- mask = STM32_LPTIM_PRESC | STM32_LPTIM_WAVPOL;
+ mask = STM32_LPTIM_PRESC;
+
+ if (!priv->num_cc_chans) {
+ /*
+ * WAVPOL bit is only available when no capature compare channel is used,
+ * e.g. on LPTIMER instances that have only one output channel. CCMR1 is
+ * used otherwise.
+ */
+ val |= FIELD_PREP(STM32_LPTIM_WAVPOL, state->polarity);
+ mask |= STM32_LPTIM_WAVPOL;
+ }
/* Must disable LP timer to modify CFGR */
reenable = true;
@@ -121,28 +249,34 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pwm,
if (ret)
goto err;
- ret = regmap_write(priv->regmap, STM32_LPTIM_CMP, prd - (1 + dty));
+ /* Write CMP/CCRx register and ensure it's been properly written */
+ ret = regmap_write(priv->regmap, pwm->hwpwm ? STM32_LPTIM_CCR2 : STM32_LPTIM_CMP,
+ prd - (1 + dty));
if (ret)
goto err;
- /* ensure CMP & ARR registers are properly written */
- ret = regmap_read_poll_timeout(priv->regmap, STM32_LPTIM_ISR, val,
- (val & STM32_LPTIM_CMPOK_ARROK),
+ /* ensure ARR and CMP/CCRx registers are properly written */
+ ret = regmap_read_poll_timeout(priv->regmap, STM32_LPTIM_ISR, val, pwm->hwpwm ?
+ (val & STM32_LPTIM_CMP2_ARROK) == STM32_LPTIM_CMP2_ARROK :
+ (val & STM32_LPTIM_CMPOK_ARROK) == STM32_LPTIM_CMPOK_ARROK,
100, 1000);
if (ret) {
- dev_err(priv->chip.dev, "ARR/CMP registers write issue\n");
+ dev_err(pwmchip_parent(chip), "ARR/CMP registers write issue\n");
goto err;
}
- ret = regmap_write(priv->regmap, STM32_LPTIM_ICR,
- STM32_LPTIM_CMPOKCF_ARROKCF);
+ ret = regmap_write(priv->regmap, STM32_LPTIM_ICR, pwm->hwpwm ?
+ STM32_LPTIM_CMP2OKCF_ARROKCF : STM32_LPTIM_CMPOKCF_ARROKCF);
+ if (ret)
+ goto err;
+
+ ret = stm32_pwm_lp_compare_channel_apply(priv, pwm->hwpwm, true, state->polarity);
if (ret)
goto err;
if (reenable) {
/* Start LP timer in continuous mode */
- ret = regmap_update_bits(priv->regmap, STM32_LPTIM_CR,
- STM32_LPTIM_CNTSTRT,
- STM32_LPTIM_CNTSTRT);
+ ret = regmap_set_bits(priv->regmap, STM32_LPTIM_CR,
+ STM32_LPTIM_CNTSTRT);
if (ret) {
regmap_write(priv->regmap, STM32_LPTIM_CR, 0);
goto err;
@@ -157,38 +291,61 @@ err:
return ret;
}
-static void stm32_pwm_lp_get_state(struct pwm_chip *chip,
- struct pwm_device *pwm,
- struct pwm_state *state)
+static int 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;
+ u32 val, presc, prd, ccmr1;
+ bool enabled;
u64 tmp;
regmap_read(priv->regmap, STM32_LPTIM_CR, &val);
- state->enabled = !!FIELD_GET(STM32_LPTIM_ENABLE, val);
+ enabled = !!FIELD_GET(STM32_LPTIM_ENABLE, val);
+ if (priv->num_cc_chans) {
+ /* There's a CC chan, need to also check if it's enabled */
+ regmap_read(priv->regmap, STM32_LPTIM_CCMR1, &ccmr1);
+ if (pwm->hwpwm)
+ enabled &= !!FIELD_GET(STM32_LPTIM_CC2E, ccmr1);
+ else
+ enabled &= !!FIELD_GET(STM32_LPTIM_CC1E, ccmr1);
+ }
+ state->enabled = enabled;
+
/* Keep PWM counter clock refcount in sync with PWM initial state */
- if (state->enabled)
- clk_enable(priv->clk);
+ if (state->enabled) {
+ int ret = clk_enable(priv->clk);
+
+ if (ret)
+ return ret;
+ }
regmap_read(priv->regmap, STM32_LPTIM_CFGR, &val);
presc = FIELD_GET(STM32_LPTIM_PRESC, val);
- state->polarity = FIELD_GET(STM32_LPTIM_WAVPOL, val);
+ if (priv->num_cc_chans) {
+ if (pwm->hwpwm)
+ state->polarity = FIELD_GET(STM32_LPTIM_CC2P, ccmr1);
+ else
+ state->polarity = FIELD_GET(STM32_LPTIM_CC1P, ccmr1);
+ } else {
+ state->polarity = FIELD_GET(STM32_LPTIM_WAVPOL, val);
+ }
regmap_read(priv->regmap, STM32_LPTIM_ARR, &prd);
tmp = prd + 1;
tmp = (tmp << presc) * NSEC_PER_SEC;
state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
- regmap_read(priv->regmap, STM32_LPTIM_CMP, &val);
+ regmap_read(priv->regmap, pwm->hwpwm ? STM32_LPTIM_CCR2 : STM32_LPTIM_CMP, &val);
tmp = prd - val;
tmp = (tmp << presc) * NSEC_PER_SEC;
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate);
+
+ return 0;
}
static const struct pwm_ops stm32_pwm_lp_ops = {
- .owner = THIS_MODULE,
.apply = stm32_pwm_lp_apply,
.get_state = stm32_pwm_lp_get_state,
};
@@ -197,61 +354,62 @@ static int stm32_pwm_lp_probe(struct platform_device *pdev)
{
struct stm32_lptimer *ddata = dev_get_drvdata(pdev->dev.parent);
struct stm32_pwm_lp *priv;
+ struct pwm_chip *chip;
+ unsigned int npwm;
int ret;
- priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
+ if (!ddata->num_cc_chans) {
+ /* No dedicated CC channel, so there's only one PWM channel */
+ npwm = 1;
+ } else {
+ /* There are dedicated CC channels, each with one PWM output */
+ npwm = ddata->num_cc_chans;
+ }
+
+ chip = devm_pwmchip_alloc(&pdev->dev, npwm, sizeof(*priv));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ priv = to_stm32_pwm_lp(chip);
priv->regmap = ddata->regmap;
priv->clk = ddata->clk;
- priv->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);
+ priv->num_cc_chans = ddata->num_cc_chans;
+ chip->ops = &stm32_pwm_lp_ops;
+
+ ret = devm_pwmchip_add(&pdev->dev, chip);
if (ret < 0)
return ret;
- platform_set_drvdata(pdev, priv);
+ platform_set_drvdata(pdev, chip);
return 0;
}
-static int stm32_pwm_lp_remove(struct platform_device *pdev)
+static int stm32_pwm_lp_suspend(struct device *dev)
{
- struct stm32_pwm_lp *priv = platform_get_drvdata(pdev);
-
- pwm_disable(&priv->chip.pwms[0]);
-
- 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_chip *chip = 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;
+ unsigned int i;
+
+ for (i = 0; i < chip->npwm; i++) {
+ pwm_get_state(&chip->pwms[i], &state);
+ if (state.enabled) {
+ dev_err(dev, "The consumer didn't stop us (%s)\n",
+ chip->pwms[i].label);
+ return -EBUSY;
+ }
}
return pinctrl_pm_select_sleep_state(dev);
}
-static int __maybe_unused stm32_pwm_lp_resume(struct device *dev)
+static int 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 DEFINE_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", },
@@ -261,11 +419,10 @@ 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,
+ .of_match_table = stm32_pwm_lp_of_match,
+ .pm = pm_ptr(&stm32_pwm_lp_pm_ops),
},
};
module_platform_driver(stm32_pwm_lp_driver);
diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c
index d3be944f2ae9..2594fb771b04 100644
--- a/drivers/pwm/pwm-stm32.c
+++ b/drivers/pwm/pwm-stm32.c
@@ -19,6 +19,7 @@
#define CCMR_CHANNEL_SHIFT 8
#define CCMR_CHANNEL_MASK 0xFF
#define MAX_BREAKINPUT 2
+#define STM32_MAX_PWM_OUTPUT 4
struct stm32_breakinput {
u32 index;
@@ -27,7 +28,6 @@ struct stm32_breakinput {
};
struct stm32_pwm {
- struct pwm_chip chip;
struct mutex lock; /* protect pwm config/enable */
struct clk *clk;
struct regmap *regmap;
@@ -40,7 +40,7 @@ struct stm32_pwm {
static inline struct stm32_pwm *to_stm32_pwm_dev(struct pwm_chip *chip)
{
- return container_of(chip, struct stm32_pwm, chip);
+ return pwmchip_get_drvdata(chip);
}
static u32 active_channels(struct stm32_pwm *dev)
@@ -52,19 +52,382 @@ static u32 active_channels(struct stm32_pwm *dev)
return ccer & TIM_CCER_CCXE;
}
-static int write_ccrx(struct stm32_pwm *dev, int ch, u32 value)
+struct stm32_pwm_waveform {
+ u32 ccer;
+ u32 psc;
+ u32 arr;
+ u32 ccr;
+};
+
+static int stm32_pwm_round_waveform_tohw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_waveform *wf,
+ void *_wfhw)
+{
+ struct stm32_pwm_waveform *wfhw = _wfhw;
+ struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
+ unsigned int ch = pwm->hwpwm;
+ unsigned long rate;
+ u64 ccr, duty;
+ int ret;
+
+ if (wf->period_length_ns == 0) {
+ *wfhw = (struct stm32_pwm_waveform){
+ .ccer = 0,
+ };
+
+ return 0;
+ }
+
+ ret = clk_enable(priv->clk);
+ if (ret)
+ return ret;
+
+ wfhw->ccer = TIM_CCER_CCxE(ch + 1);
+ if (priv->have_complementary_output)
+ wfhw->ccer |= TIM_CCER_CCxNE(ch + 1);
+
+ rate = clk_get_rate(priv->clk);
+
+ if (active_channels(priv) & ~TIM_CCER_CCxE(ch + 1)) {
+ u64 arr;
+
+ /*
+ * Other channels are already enabled, so the configured PSC and
+ * ARR must be used for this channel, too.
+ */
+ ret = regmap_read(priv->regmap, TIM_PSC, &wfhw->psc);
+ if (ret)
+ goto out;
+
+ ret = regmap_read(priv->regmap, TIM_ARR, &wfhw->arr);
+ if (ret)
+ goto out;
+
+ arr = mul_u64_u64_div_u64(wf->period_length_ns, rate,
+ (u64)NSEC_PER_SEC * (wfhw->psc + 1));
+ if (arr <= wfhw->arr) {
+ /*
+ * requested period is smaller than the currently
+ * configured and unchangable period, report back the smallest
+ * possible period, i.e. the current state and return 1
+ * to indicate the wrong rounding direction.
+ */
+ ret = 1;
+ }
+
+ } else {
+ /*
+ * .probe() asserted that clk_get_rate() is not bigger than 1 GHz, so
+ * the calculations here won't overflow.
+ * First we need to find the minimal value for prescaler such that
+ *
+ * period_ns * clkrate
+ * ------------------------------ < max_arr + 1
+ * NSEC_PER_SEC * (prescaler + 1)
+ *
+ * This equation is equivalent to
+ *
+ * period_ns * clkrate
+ * ---------------------------- < prescaler + 1
+ * NSEC_PER_SEC * (max_arr + 1)
+ *
+ * Using integer division and knowing that the right hand side is
+ * integer, this is further equivalent to
+ *
+ * (period_ns * clkrate) // (NSEC_PER_SEC * (max_arr + 1)) ≤ prescaler
+ */
+ u64 psc = mul_u64_u64_div_u64(wf->period_length_ns, rate,
+ (u64)NSEC_PER_SEC * ((u64)priv->max_arr + 1));
+ u64 arr;
+
+ wfhw->psc = min_t(u64, psc, MAX_TIM_PSC);
+
+ arr = mul_u64_u64_div_u64(wf->period_length_ns, rate,
+ (u64)NSEC_PER_SEC * (wfhw->psc + 1));
+ if (!arr) {
+ /*
+ * requested period is too small, report back the smallest
+ * possible period, i.e. ARR = 0. The only valid CCR
+ * value is then zero, too.
+ */
+ wfhw->arr = 0;
+ wfhw->ccr = 0;
+ ret = 1;
+ goto out;
+ }
+
+ /*
+ * ARR is limited intentionally to values less than
+ * priv->max_arr to allow 100% duty cycle.
+ */
+ wfhw->arr = min_t(u64, arr, priv->max_arr) - 1;
+ }
+
+ duty = mul_u64_u64_div_u64(wf->duty_length_ns, rate,
+ (u64)NSEC_PER_SEC * (wfhw->psc + 1));
+ duty = min_t(u64, duty, wfhw->arr + 1);
+
+ if (wf->duty_length_ns && wf->duty_offset_ns &&
+ wf->duty_length_ns + wf->duty_offset_ns >= wf->period_length_ns) {
+ wfhw->ccer |= TIM_CCER_CCxP(ch + 1);
+ if (priv->have_complementary_output)
+ wfhw->ccer |= TIM_CCER_CCxNP(ch + 1);
+
+ ccr = wfhw->arr + 1 - duty;
+ } else {
+ ccr = duty;
+ }
+
+ wfhw->ccr = min_t(u64, ccr, wfhw->arr + 1);
+
+out:
+ dev_dbg(&chip->dev, "pwm#%u: %lld/%lld [+%lld] @%lu -> CCER: %08x, PSC: %08x, ARR: %08x, CCR: %08x\n",
+ pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
+ rate, wfhw->ccer, wfhw->psc, wfhw->arr, wfhw->ccr);
+
+ clk_disable(priv->clk);
+
+ return ret;
+}
+
+/*
+ * This should be moved to lib/math/div64.c. Currently there are some changes
+ * pending to mul_u64_u64_div_u64. Uwe will care for that when the dust settles.
+ */
+static u64 stm32_pwm_mul_u64_u64_div_u64_roundup(u64 a, u64 b, u64 c)
+{
+ u64 res = mul_u64_u64_div_u64(a, b, c);
+ /* Those multiplications might overflow but it doesn't matter */
+ u64 rem = a * b - c * res;
+
+ if (rem)
+ res += 1;
+
+ return res;
+}
+
+static int stm32_pwm_round_waveform_fromhw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw,
+ struct pwm_waveform *wf)
+{
+ const struct stm32_pwm_waveform *wfhw = _wfhw;
+ struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
+ unsigned long rate = clk_get_rate(priv->clk);
+ unsigned int ch = pwm->hwpwm;
+
+ if (wfhw->ccer & TIM_CCER_CCxE(ch + 1)) {
+ u64 ccr_ns;
+
+ /* The result doesn't overflow for rate >= 15259 */
+ wf->period_length_ns = stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1),
+ NSEC_PER_SEC, rate);
+
+ ccr_ns = stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * wfhw->ccr,
+ NSEC_PER_SEC, rate);
+
+ if (wfhw->ccer & TIM_CCER_CCxP(ch + 1)) {
+ wf->duty_length_ns =
+ stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1 - wfhw->ccr),
+ NSEC_PER_SEC, rate);
+
+ wf->duty_offset_ns = ccr_ns;
+ } else {
+ wf->duty_length_ns = ccr_ns;
+ wf->duty_offset_ns = 0;
+ }
+ } else {
+ *wf = (struct pwm_waveform){
+ .period_length_ns = 0,
+ };
+ }
+
+ dev_dbg(&chip->dev, "pwm#%u: CCER: %08x, PSC: %08x, ARR: %08x, CCR: %08x @%lu -> %lld/%lld [+%lld]\n",
+ pwm->hwpwm, wfhw->ccer, wfhw->psc, wfhw->arr, wfhw->ccr, rate,
+ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns);
+
+ return 0;
+}
+
+static int stm32_pwm_read_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ void *_wfhw)
{
- 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);
+ struct stm32_pwm_waveform *wfhw = _wfhw;
+ struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
+ unsigned int ch = pwm->hwpwm;
+ int ret;
+
+ ret = clk_enable(priv->clk);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(priv->regmap, TIM_CCER, &wfhw->ccer);
+ if (ret)
+ goto out;
+
+ if (wfhw->ccer & TIM_CCER_CCxE(ch + 1)) {
+ ret = regmap_read(priv->regmap, TIM_PSC, &wfhw->psc);
+ if (ret)
+ goto out;
+
+ ret = regmap_read(priv->regmap, TIM_ARR, &wfhw->arr);
+ if (ret)
+ goto out;
+
+ if (wfhw->arr == U32_MAX)
+ wfhw->arr -= 1;
+
+ ret = regmap_read(priv->regmap, TIM_CCRx(ch + 1), &wfhw->ccr);
+ if (ret)
+ goto out;
+
+ if (wfhw->ccr > wfhw->arr + 1)
+ wfhw->ccr = wfhw->arr + 1;
}
- return -EINVAL;
+
+out:
+ clk_disable(priv->clk);
+
+ return ret;
+}
+
+static int stm32_pwm_write_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw)
+{
+ const struct stm32_pwm_waveform *wfhw = _wfhw;
+ struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
+ unsigned int ch = pwm->hwpwm;
+ int ret;
+
+ ret = clk_enable(priv->clk);
+ if (ret)
+ return ret;
+
+ if (wfhw->ccer & TIM_CCER_CCxE(ch + 1)) {
+ u32 ccer, mask;
+ unsigned int shift;
+ u32 ccmr;
+
+ ret = regmap_read(priv->regmap, TIM_CCER, &ccer);
+ if (ret)
+ goto out;
+
+ /* If there are other channels enabled, don't update PSC and ARR */
+ if (ccer & ~TIM_CCER_CCxE(ch + 1) & TIM_CCER_CCXE) {
+ u32 psc, arr;
+
+ ret = regmap_read(priv->regmap, TIM_PSC, &psc);
+ if (ret)
+ goto out;
+
+ if (psc != wfhw->psc) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ret = regmap_read(priv->regmap, TIM_ARR, &arr);
+ if (ret)
+ goto out;
+
+ if (arr != wfhw->arr) {
+ ret = -EBUSY;
+ goto out;
+ }
+ } else {
+ ret = regmap_write(priv->regmap, TIM_PSC, wfhw->psc);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(priv->regmap, TIM_ARR, wfhw->arr);
+ if (ret)
+ goto out;
+
+ ret = regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE);
+ if (ret)
+ goto out;
+
+ }
+
+ /* set polarity */
+ mask = TIM_CCER_CCxP(ch + 1) | TIM_CCER_CCxNP(ch + 1);
+ ret = regmap_update_bits(priv->regmap, TIM_CCER, mask, wfhw->ccer);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(priv->regmap, TIM_CCRx(ch + 1), wfhw->ccr);
+ if (ret)
+ goto out;
+
+ /* Configure output mode */
+ shift = (ch & 0x1) * CCMR_CHANNEL_SHIFT;
+ ccmr = (TIM_CCMR_PE | TIM_CCMR_M1) << shift;
+ mask = CCMR_CHANNEL_MASK << shift;
+
+ if (ch < 2)
+ ret = regmap_update_bits(priv->regmap, TIM_CCMR1, mask, ccmr);
+ else
+ ret = regmap_update_bits(priv->regmap, TIM_CCMR2, mask, ccmr);
+ if (ret)
+ goto out;
+
+ ret = regmap_set_bits(priv->regmap, TIM_BDTR, TIM_BDTR_MOE);
+ if (ret)
+ goto out;
+
+ if (!(ccer & TIM_CCER_CCxE(ch + 1))) {
+ mask = TIM_CCER_CCxE(ch + 1) | TIM_CCER_CCxNE(ch + 1);
+
+ ret = clk_enable(priv->clk);
+ if (ret)
+ goto out;
+
+ ccer = (ccer & ~mask) | (wfhw->ccer & mask);
+ regmap_write(priv->regmap, TIM_CCER, ccer);
+
+ /* Make sure that registers are updated */
+ regmap_set_bits(priv->regmap, TIM_EGR, TIM_EGR_UG);
+
+ /* Enable controller */
+ regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN);
+ }
+
+ } else {
+ /* disable channel */
+ u32 mask, ccer;
+
+ mask = TIM_CCER_CCxE(ch + 1);
+ if (priv->have_complementary_output)
+ mask |= TIM_CCER_CCxNE(ch + 1);
+
+ ret = regmap_read(priv->regmap, TIM_CCER, &ccer);
+ if (ret)
+ goto out;
+
+ if (ccer & mask) {
+ ccer = ccer & ~mask;
+
+ ret = regmap_write(priv->regmap, TIM_CCER, ccer);
+ if (ret)
+ goto out;
+
+ if (!(ccer & TIM_CCER_CCXE)) {
+ /* When all channels are disabled, we can disable the controller */
+ ret = regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN);
+ if (ret)
+ goto out;
+ }
+
+ clk_disable(priv->clk);
+ }
+ }
+
+out:
+ clk_disable(priv->clk);
+
+ return ret;
}
#define TIM_CCER_CC12P (TIM_CCER_CC1P | TIM_CCER_CC2P)
@@ -105,24 +468,25 @@ static int write_ccrx(struct stm32_pwm *dev, int ch, u32 value)
* - Period = t2 - t0
* - Duty cycle = t1 - t0
*/
-static int stm32_pwm_raw_capture(struct stm32_pwm *priv, struct pwm_device *pwm,
+static int stm32_pwm_raw_capture(struct pwm_chip *chip, struct pwm_device *pwm,
unsigned long tmo_ms, u32 *raw_prd,
u32 *raw_dty)
{
- struct device *parent = priv->chip.dev->parent;
+ struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
+ struct device *parent = pwmchip_parent(chip)->parent;
enum stm32_timers_dmas dma_id;
u32 ccen, ccr;
int ret;
/* Ensure registers have been updated, enable counter and capture */
- regmap_update_bits(priv->regmap, TIM_EGR, TIM_EGR_UG, TIM_EGR_UG);
- regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, TIM_CR1_CEN);
+ 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_update_bits(priv->regmap, TIM_CCER, ccen, ccen);
+ regmap_set_bits(priv->regmap, TIM_CCER, ccen);
/*
* Timer DMA burst mode. Request 2 registers, 2 bursts, to get both
@@ -160,8 +524,8 @@ static int stm32_pwm_raw_capture(struct stm32_pwm *priv, struct pwm_device *pwm,
}
stop:
- regmap_update_bits(priv->regmap, TIM_CCER, ccen, 0);
- regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, 0);
+ regmap_clear_bits(priv->regmap, TIM_CCER, ccen);
+ regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN);
return ret;
}
@@ -185,7 +549,7 @@ static int stm32_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm,
ret = clk_enable(priv->clk);
if (ret) {
- dev_err(priv->chip.dev, "failed to enable counter clock\n");
+ dev_err(pwmchip_parent(chip), "failed to enable counter clock\n");
goto unlock;
}
@@ -207,6 +571,10 @@ static int stm32_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm,
regmap_write(priv->regmap, TIM_ARR, priv->max_arr);
regmap_write(priv->regmap, TIM_PSC, psc);
+ /* Reset input selector to its default input and disable slave mode */
+ regmap_write(priv->regmap, TIM_TISEL, 0x0);
+ regmap_write(priv->regmap, TIM_SMCR, 0x0);
+
/* 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,
@@ -219,7 +587,7 @@ static int stm32_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+ ret = stm32_pwm_raw_capture(chip, pwm, tmo_ms, &raw_prd, &raw_dty);
if (ret)
goto stop;
@@ -233,14 +601,14 @@ static int stm32_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm,
scale = max_arr / min(max_arr, raw_prd);
} else {
- scale = priv->max_arr; /* bellow resolution, use max scale */
+ scale = priv->max_arr; /* below resolution, use max scale */
}
if (psc && scale > 1) {
/* 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,
+ ret = stm32_pwm_raw_capture(chip, pwm, tmo_ms, &raw_prd,
&raw_dty);
if (ret)
goto stop;
@@ -268,7 +636,7 @@ static int stm32_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+ ret = stm32_pwm_raw_capture(chip, pwm, tmo_ms, &raw_prd, &raw_dty);
if (ret)
goto stop;
@@ -319,172 +687,13 @@ unlock:
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_update_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE, 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_update_bits(priv->regmap, TIM_BDTR, TIM_BDTR_MOE, 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_update_bits(priv->regmap, TIM_CCER, mask, mask);
-
- /* Make sure that registers are updated */
- regmap_update_bits(priv->regmap, TIM_EGR, TIM_EGR_UG, TIM_EGR_UG);
-
- /* Enable controller */
- regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, 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_update_bits(priv->regmap, TIM_CCER, mask, 0);
-
- /* When all channels are disabled, we can disable the controller */
- if (!active_channels(priv))
- regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, 0);
-
- 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,
+ .sizeof_wfhw = sizeof(struct stm32_pwm_waveform),
+ .round_waveform_tohw = stm32_pwm_round_waveform_tohw,
+ .round_waveform_fromhw = stm32_pwm_round_waveform_fromhw,
+ .read_waveform = stm32_pwm_read_waveform,
+ .write_waveform = stm32_pwm_write_waveform,
+
.capture = IS_ENABLED(CONFIG_DMA_ENGINE) ? stm32_pwm_capture : NULL,
};
@@ -560,49 +769,64 @@ static int stm32_pwm_probe_breakinputs(struct stm32_pwm *priv,
return stm32_pwm_apply_breakinputs(priv);
}
-static void stm32_pwm_detect_complementary(struct stm32_pwm *priv)
+static void stm32_pwm_detect_complementary(struct stm32_pwm *priv, struct stm32_timers *ddata)
{
u32 ccer;
+ if (ddata->ipidr) {
+ u32 val;
+
+ /* Simply read from HWCFGR the number of complementary outputs (MP25). */
+ regmap_read(priv->regmap, TIM_HWCFGR1, &val);
+ priv->have_complementary_output = !!FIELD_GET(TIM_HWCFGR1_NB_OF_DT, val);
+ return;
+ }
+
/*
* If complementary bit doesn't exist writing 1 will have no
* effect so we can detect it.
*/
- regmap_update_bits(priv->regmap,
- TIM_CCER, TIM_CCER_CC1NE, TIM_CCER_CC1NE);
+ regmap_set_bits(priv->regmap, TIM_CCER, TIM_CCER_CC1NE);
regmap_read(priv->regmap, TIM_CCER, &ccer);
- regmap_update_bits(priv->regmap, TIM_CCER, TIM_CCER_CC1NE, 0);
+ regmap_clear_bits(priv->regmap, TIM_CCER, TIM_CCER_CC1NE);
priv->have_complementary_output = (ccer != 0);
}
-static int stm32_pwm_detect_channels(struct stm32_pwm *priv)
+static unsigned int stm32_pwm_detect_channels(struct stm32_timers *ddata,
+ unsigned int *num_enabled)
{
- u32 ccer;
- int npwm = 0;
+ struct regmap *regmap = ddata->regmap;
+ 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_update_bits(priv->regmap,
- TIM_CCER, TIM_CCER_CCXE, TIM_CCER_CCXE);
- regmap_read(priv->regmap, TIM_CCER, &ccer);
- regmap_update_bits(priv->regmap, TIM_CCER, TIM_CCER_CCXE, 0);
+ regmap_read(regmap, TIM_CCER, &ccer_backup);
+ *num_enabled = hweight32(ccer_backup & TIM_CCER_CCXE);
- if (ccer & TIM_CCER_CC1E)
- npwm++;
+ if (ddata->ipidr) {
+ u32 hwcfgr;
+ unsigned int npwm;
- if (ccer & TIM_CCER_CC2E)
- npwm++;
+ /* Deduce from HWCFGR the number of outputs (MP25). */
+ regmap_read(regmap, TIM_HWCFGR1, &hwcfgr);
- if (ccer & TIM_CCER_CC3E)
- npwm++;
+ /*
+ * Timers may have more capture/compare channels than the
+ * actual number of PWM channel outputs (e.g. TIM_CH[1..4]).
+ */
+ npwm = FIELD_GET(TIM_HWCFGR1_NB_OF_CC, hwcfgr);
- if (ccer & TIM_CCER_CC4E)
- npwm++;
+ return npwm < STM32_MAX_PWM_OUTPUT ? npwm : STM32_MAX_PWM_OUTPUT;
+ }
- return npwm;
+ /*
+ * If channels enable bits don't exist writing 1 will have no
+ * effect so we can detect and count them.
+ */
+ regmap_set_bits(regmap, TIM_CCER, TIM_CCER_CCXE);
+ regmap_read(regmap, TIM_CCER, &ccer);
+ regmap_write(regmap, TIM_CCER, ccer_backup);
+
+ return hweight32(ccer & TIM_CCER_CCXE);
}
static int stm32_pwm_probe(struct platform_device *pdev)
@@ -610,70 +834,81 @@ 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 pwm_chip *chip;
struct stm32_pwm *priv;
+ unsigned int npwm, num_enabled;
+ unsigned int i;
int ret;
- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
+ npwm = stm32_pwm_detect_channels(ddata, &num_enabled);
+
+ chip = devm_pwmchip_alloc(dev, npwm, sizeof(*priv));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ priv = to_stm32_pwm_dev(chip);
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;
+ return dev_err_probe(dev, -EINVAL, "Failed to get %s\n",
+ priv->regmap ? "clk" : "regmap");
ret = stm32_pwm_probe_breakinputs(priv, np);
if (ret)
- return ret;
+ return dev_err_probe(dev, ret,
+ "Failed to configure breakinputs\n");
- stm32_pwm_detect_complementary(priv);
+ stm32_pwm_detect_complementary(priv, ddata);
- priv->chip.base = -1;
- priv->chip.dev = dev;
- priv->chip.ops = &stm32pwm_ops;
- priv->chip.npwm = stm32_pwm_detect_channels(priv);
-
- ret = pwmchip_add(&priv->chip);
- if (ret < 0)
- return ret;
+ ret = devm_clk_rate_exclusive_get(dev, priv->clk);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to lock clock\n");
- platform_set_drvdata(pdev, priv);
+ /*
+ * With the clk running with not more than 1 GHz the calculations in
+ * .apply() won't overflow.
+ */
+ if (clk_get_rate(priv->clk) > 1000000000)
+ return dev_err_probe(dev, -EINVAL, "Clock freq too high (%lu)\n",
+ clk_get_rate(priv->clk));
- return 0;
-}
+ chip->ops = &stm32pwm_ops;
-static int stm32_pwm_remove(struct platform_device *pdev)
-{
- struct stm32_pwm *priv = platform_get_drvdata(pdev);
- unsigned int i;
+ /* Initialize clock refcount to number of enabled PWM channels. */
+ for (i = 0; i < num_enabled; i++) {
+ ret = clk_enable(priv->clk);
+ if (ret)
+ return ret;
+ }
- for (i = 0; i < priv->chip.npwm; i++)
- pwm_disable(&priv->chip.pwms[i]);
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to register pwmchip\n");
- pwmchip_remove(&priv->chip);
+ platform_set_drvdata(pdev, chip);
return 0;
}
-static int __maybe_unused stm32_pwm_suspend(struct device *dev)
+static int stm32_pwm_suspend(struct device *dev)
{
- struct stm32_pwm *priv = dev_get_drvdata(dev);
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
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);
+ for (i = 0; i < chip->npwm; i++) {
+ mask = TIM_CCER_CCxE(i + 1);
if (ccer & mask) {
dev_err(dev, "PWM %u still in use by consumer %s\n",
- i, priv->chip.pwms[i].label);
+ i, chip->pwms[i].label);
return -EBUSY;
}
}
@@ -681,9 +916,10 @@ static int __maybe_unused stm32_pwm_suspend(struct device *dev)
return pinctrl_pm_select_sleep_state(dev);
}
-static int __maybe_unused stm32_pwm_resume(struct device *dev)
+static int stm32_pwm_resume(struct device *dev)
{
- struct stm32_pwm *priv = dev_get_drvdata(dev);
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
int ret;
ret = pinctrl_pm_select_default_state(dev);
@@ -694,21 +930,21 @@ static int __maybe_unused stm32_pwm_resume(struct device *dev)
return stm32_pwm_apply_breakinputs(priv);
}
-static SIMPLE_DEV_PM_OPS(stm32_pwm_pm_ops, stm32_pwm_suspend, stm32_pwm_resume);
+static DEFINE_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", },
+ { .compatible = "st,stm32mp25-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,
+ .pm = pm_ptr(&stm32_pwm_pm_ops),
},
};
module_platform_driver(stm32_pwm_driver);
diff --git a/drivers/pwm/pwm-stmpe.c b/drivers/pwm/pwm-stmpe.c
index be5f6d7359d4..73f12843999a 100644
--- a/drivers/pwm/pwm-stmpe.c
+++ b/drivers/pwm/pwm-stmpe.c
@@ -27,13 +27,12 @@
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);
+ return pwmchip_get_drvdata(chip);
}
static int stmpe_24xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
@@ -44,7 +43,7 @@ static int stmpe_24xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
ret = stmpe_reg_read(stmpe_pwm->stmpe, STMPE24XX_PWMCS);
if (ret < 0) {
- dev_err(chip->dev, "error reading PWM#%u control\n",
+ dev_dbg(pwmchip_parent(chip), "error reading PWM#%u control\n",
pwm->hwpwm);
return ret;
}
@@ -53,7 +52,7 @@ static int stmpe_24xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
ret = stmpe_reg_write(stmpe_pwm->stmpe, STMPE24XX_PWMCS, value);
if (ret) {
- dev_err(chip->dev, "error writing PWM#%u control\n",
+ dev_dbg(pwmchip_parent(chip), "error writing PWM#%u control\n",
pwm->hwpwm);
return ret;
}
@@ -61,8 +60,8 @@ static int stmpe_24xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
return 0;
}
-static void stmpe_24xx_pwm_disable(struct pwm_chip *chip,
- struct pwm_device *pwm)
+static int stmpe_24xx_pwm_disable(struct pwm_chip *chip,
+ struct pwm_device *pwm)
{
struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip);
u8 value;
@@ -70,19 +69,18 @@ static void stmpe_24xx_pwm_disable(struct pwm_chip *chip,
ret = stmpe_reg_read(stmpe_pwm->stmpe, STMPE24XX_PWMCS);
if (ret < 0) {
- dev_err(chip->dev, "error reading PWM#%u control\n",
+ dev_dbg(pwmchip_parent(chip), "error reading PWM#%u control\n",
pwm->hwpwm);
- return;
+ 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",
+ if (ret)
+ dev_dbg(pwmchip_parent(chip), "error writing PWM#%u control\n",
pwm->hwpwm);
- return;
- }
+ return ret;
}
/* STMPE 24xx PWM instructions */
@@ -111,7 +109,9 @@ static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
/* Make sure we are disabled */
if (pwm_is_enabled(pwm)) {
- stmpe_24xx_pwm_disable(chip, pwm);
+ ret = stmpe_24xx_pwm_disable(chip, pwm);
+ if (ret)
+ return ret;
} else {
/* Connect the PWM to the pin */
pin = pwm->hwpwm;
@@ -124,7 +124,7 @@ static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
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",
+ dev_err(pwmchip_parent(chip), "unable to connect PWM#%u to pin\n",
pwm->hwpwm);
return ret;
}
@@ -149,7 +149,7 @@ static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
return -ENODEV;
}
- dev_dbg(chip->dev, "PWM#%u: config duty %d ns, period %d ns\n",
+ dev_dbg(pwmchip_parent(chip), "PWM#%u: config duty %d ns, period %d ns\n",
pwm->hwpwm, duty_ns, period_ns);
if (duty_ns == 0) {
@@ -215,7 +215,7 @@ static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
program[1] = BRANCH;
}
- dev_dbg(chip->dev,
+ dev_dbg(pwmchip_parent(chip),
"PWM#%u: value = %02x, last_duty = %02x, program=%04x,%04x,%04x\n",
pwm->hwpwm, value, last, program[0], program[1],
program[2]);
@@ -232,7 +232,7 @@ static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
ret = stmpe_reg_write(stmpe_pwm->stmpe, offset, value);
if (ret) {
- dev_err(chip->dev, "error writing register %02x: %d\n",
+ dev_dbg(pwmchip_parent(chip), "error writing register %02x: %d\n",
offset, ret);
return ret;
}
@@ -241,7 +241,7 @@ static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
ret = stmpe_reg_write(stmpe_pwm->stmpe, offset, value);
if (ret) {
- dev_err(chip->dev, "error writing register %02x: %d\n",
+ dev_dbg(pwmchip_parent(chip), "error writing register %02x: %d\n",
offset, ret);
return ret;
}
@@ -254,62 +254,105 @@ static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *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);
+ dev_dbg(pwmchip_parent(chip), "programmed PWM#%u, %u bytes\n", pwm->hwpwm, i);
return 0;
}
+static int stmpe_24xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ if (pwm->state.enabled)
+ return stmpe_24xx_pwm_disable(chip, pwm);
+
+ return 0;
+ }
+
+ err = stmpe_24xx_pwm_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!pwm->state.enabled)
+ err = stmpe_24xx_pwm_enable(chip, pwm);
+
+ return err;
+}
+
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,
+ .apply = stmpe_24xx_pwm_apply,
};
static int __init stmpe_pwm_probe(struct platform_device *pdev)
{
struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
- struct stmpe_pwm *pwm;
+ struct pwm_chip *chip;
+ struct stmpe_pwm *stmpe_pwm;
int ret;
- pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
- if (!pwm)
- return -ENOMEM;
+ switch (stmpe->partnum) {
+ case STMPE2401:
+ case STMPE2403:
+ break;
+ case STMPE1601:
+ return dev_err_probe(&pdev->dev, -ENODEV,
+ "STMPE1601 not yet supported\n");
+ default:
+ return dev_err_probe(&pdev->dev, -ENODEV,
+ "Unknown STMPE PWM\n");
+ }
- pwm->stmpe = stmpe;
- pwm->chip.dev = &pdev->dev;
- pwm->chip.base = -1;
+ chip = devm_pwmchip_alloc(&pdev->dev, 3, sizeof(*stmpe_pwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ stmpe_pwm = to_stmpe_pwm(chip);
- 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");
+ stmpe_pwm->stmpe = stmpe;
- return -ENODEV;
- }
+ chip->ops = &stmpe_24xx_pwm_ops;
ret = stmpe_enable(stmpe, STMPE_BLOCK_PWM);
if (ret)
return ret;
- ret = pwmchip_add(&pwm->chip);
+ ret = pwmchip_add(chip);
if (ret) {
stmpe_disable(stmpe, STMPE_BLOCK_PWM);
return ret;
}
- platform_set_drvdata(pdev, pwm);
+ platform_set_drvdata(pdev, chip);
return 0;
}
-static struct platform_driver stmpe_pwm_driver = {
+static void __exit stmpe_pwm_remove(struct platform_device *pdev)
+{
+ struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+ struct pwm_chip *chip = platform_get_drvdata(pdev);
+
+ pwmchip_remove(chip);
+ stmpe_disable(stmpe, STMPE_BLOCK_PWM);
+}
+
+/*
+ * stmpe_pwm_remove() lives in .exit.text. For drivers registered via
+ * module_platform_driver_probe() this is ok because they cannot get unbound at
+ * runtime. So mark the driver struct with __refdata to prevent modpost
+ * triggering a section mismatch warning.
+ */
+static struct platform_driver stmpe_pwm_driver __refdata = {
.driver = {
.name = "stmpe-pwm",
},
+ .remove = __exit_p(stmpe_pwm_remove),
};
-builtin_platform_driver_probe(stmpe_pwm_driver, stmpe_pwm_probe);
+module_platform_driver_probe(stmpe_pwm_driver, stmpe_pwm_probe);
+
+MODULE_DESCRIPTION("STMPE expander PWM");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index 18fbbe3277d0..6c5591ca868b 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -17,12 +17,10 @@
#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
@@ -82,45 +80,44 @@ struct sun4i_pwm_data {
};
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);
+ return pwmchip_get_drvdata(chip);
}
-static inline u32 sun4i_pwm_readl(struct sun4i_pwm_chip *chip,
+static inline u32 sun4i_pwm_readl(struct sun4i_pwm_chip *sun4ichip,
unsigned long offset)
{
- return readl(chip->base + offset);
+ return readl(sun4ichip->base + offset);
}
-static inline void sun4i_pwm_writel(struct sun4i_pwm_chip *chip,
+static inline void sun4i_pwm_writel(struct sun4i_pwm_chip *sun4ichip,
u32 val, unsigned long offset)
{
- writel(val, chip->base + offset);
+ writel(val, sun4ichip->base + offset);
}
-static void sun4i_pwm_get_state(struct pwm_chip *chip,
- struct pwm_device *pwm,
- struct pwm_state *state)
+static int 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);
+ struct sun4i_pwm_chip *sun4ichip = to_sun4i_pwm_chip(chip);
u64 clk_rate, tmp;
u32 val;
unsigned int prescaler;
- clk_rate = clk_get_rate(sun4i_pwm->clk);
+ clk_rate = clk_get_rate(sun4ichip->clk);
+ if (!clk_rate)
+ return -EINVAL;
- val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
+ val = sun4i_pwm_readl(sun4ichip, PWM_CTRL_REG);
/*
* PWM chapter in H6 manual has a diagram which explains that if bypass
@@ -128,22 +125,22 @@ static void sun4i_pwm_get_state(struct pwm_chip *chip,
* 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) {
+ sun4ichip->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;
+ return 0;
}
if ((PWM_REG_PRESCAL(val, pwm->hwpwm) == PWM_PRESCAL_MASK) &&
- sun4i_pwm->data->has_prescaler_bypass)
+ sun4ichip->data->has_prescaler_bypass)
prescaler = 1;
else
prescaler = prescaler_table[PWM_REG_PRESCAL(val, pwm->hwpwm)];
if (prescaler == 0)
- return;
+ return -EINVAL;
if (val & BIT_CH(PWM_ACT_STATE, pwm->hwpwm))
state->polarity = PWM_POLARITY_NORMAL;
@@ -156,16 +153,18 @@ static void sun4i_pwm_get_state(struct pwm_chip *chip,
else
state->enabled = false;
- val = sun4i_pwm_readl(sun4i_pwm, PWM_CH_PRD(pwm->hwpwm));
+ val = sun4i_pwm_readl(sun4ichip, 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);
+
+ return 0;
}
-static int sun4i_pwm_calculate(struct sun4i_pwm_chip *sun4i_pwm,
+static int sun4i_pwm_calculate(struct sun4i_pwm_chip *sun4ichip,
const struct pwm_state *state,
u32 *dty, u32 *prd, unsigned int *prsclr,
bool *bypass)
@@ -173,9 +172,9 @@ static int sun4i_pwm_calculate(struct sun4i_pwm_chip *sun4i_pwm,
u64 clk_rate, div = 0;
unsigned int prescaler = 0;
- clk_rate = clk_get_rate(sun4i_pwm->clk);
+ clk_rate = clk_get_rate(sun4ichip->clk);
- *bypass = sun4i_pwm->data->has_direct_mod_clk_output &&
+ *bypass = sun4ichip->data->has_direct_mod_clk_output &&
state->enabled &&
(state->period * clk_rate >= NSEC_PER_SEC) &&
(state->period * clk_rate < 2 * NSEC_PER_SEC) &&
@@ -185,7 +184,7 @@ static int sun4i_pwm_calculate(struct sun4i_pwm_chip *sun4i_pwm,
if (*bypass)
return 0;
- if (sun4i_pwm->data->has_prescaler_bypass) {
+ if (sun4ichip->data->has_prescaler_bypass) {
/* First, test without any prescaler when available */
prescaler = PWM_PRESCAL_MASK;
/*
@@ -231,42 +230,39 @@ static int sun4i_pwm_calculate(struct sun4i_pwm_chip *sun4i_pwm,
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 sun4i_pwm_chip *sun4ichip = 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);
+ ret = clk_prepare_enable(sun4ichip->clk);
if (ret) {
- dev_err(chip->dev, "failed to enable PWM clock\n");
+ dev_err(pwmchip_parent(chip), "failed to enable PWM clock\n");
return ret;
}
}
- ret = sun4i_pwm_calculate(sun4i_pwm, state, &duty, &period, &prescaler,
+ ret = sun4i_pwm_calculate(sun4ichip, state, &duty, &period, &prescaler,
&bypass);
if (ret) {
- dev_err(chip->dev, "period exceeds the maximum value\n");
+ dev_err(pwmchip_parent(chip), "period exceeds the maximum value\n");
if (!cstate.enabled)
- clk_disable_unprepare(sun4i_pwm->clk);
+ clk_disable_unprepare(sun4ichip->clk);
return ret;
}
- spin_lock(&sun4i_pwm->ctrl_lock);
- ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
+ ctrl = sun4i_pwm_readl(sun4ichip, PWM_CTRL_REG);
- if (sun4i_pwm->data->has_direct_mod_clk_output) {
+ if (sun4ichip->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);
+ sun4i_pwm_writel(sun4ichip, ctrl, PWM_CTRL_REG);
return 0;
}
@@ -276,16 +272,14 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+ sun4i_pwm_writel(sun4ichip, 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 +
- usecs_to_jiffies(cstate.period / 1000 + 1);
+ sun4i_pwm_writel(sun4ichip, val, PWM_CH_PRD(pwm->hwpwm));
if (state->polarity != PWM_POLARITY_NORMAL)
ctrl &= ~BIT_CH(PWM_ACT_STATE, pwm->hwpwm);
@@ -294,39 +288,27 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
ctrl |= BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
- if (state->enabled) {
+ if (state->enabled)
ctrl |= BIT_CH(PWM_EN, pwm->hwpwm);
- } else {
- ctrl &= ~BIT_CH(PWM_EN, pwm->hwpwm);
- ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
- }
- sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG);
-
- spin_unlock(&sun4i_pwm->ctrl_lock);
+ sun4i_pwm_writel(sun4ichip, ctrl, PWM_CTRL_REG);
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);
- }
+ delay_us = DIV_ROUND_UP_ULL(cstate.period, NSEC_PER_USEC);
+ 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 = sun4i_pwm_readl(sun4ichip, 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);
+ sun4i_pwm_writel(sun4ichip, ctrl, PWM_CTRL_REG);
- clk_disable_unprepare(sun4i_pwm->clk);
+ clk_disable_unprepare(sun4ichip->clk);
return 0;
}
@@ -334,7 +316,6 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
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 = {
@@ -394,22 +375,24 @@ 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;
+ struct pwm_chip *chip;
+ const struct sun4i_pwm_data *data;
+ struct sun4i_pwm_chip *sun4ichip;
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)
+ data = of_device_get_match_data(&pdev->dev);
+ if (!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);
+ chip = devm_pwmchip_alloc(&pdev->dev, data->npwm, sizeof(*sun4ichip));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ sun4ichip = to_sun4i_pwm_chip(chip);
+
+ sun4ichip->data = data;
+ sun4ichip->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(sun4ichip->base))
+ return PTR_ERR(sun4ichip->base);
/*
* All hardware variants need a source clock that is divided and
@@ -422,42 +405,30 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
* 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)) {
- if (PTR_ERR(pwm->clk) != -EPROBE_DEFER)
- dev_err(&pdev->dev, "get mod clock failed %pe\n",
- pwm->clk);
- return PTR_ERR(pwm->clk);
- }
-
- if (!pwm->clk) {
- pwm->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(pwm->clk)) {
- if (PTR_ERR(pwm->clk) != -EPROBE_DEFER)
- dev_err(&pdev->dev, "get unnamed clock failed %pe\n",
- pwm->clk);
- return PTR_ERR(pwm->clk);
- }
+ sun4ichip->clk = devm_clk_get_optional(&pdev->dev, "mod");
+ if (IS_ERR(sun4ichip->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(sun4ichip->clk),
+ "get mod clock failed\n");
+
+ if (!sun4ichip->clk) {
+ sun4ichip->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(sun4ichip->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(sun4ichip->clk),
+ "get unnamed clock failed\n");
}
- pwm->bus_clk = devm_clk_get_optional(&pdev->dev, "bus");
- if (IS_ERR(pwm->bus_clk)) {
- if (PTR_ERR(pwm->bus_clk) != -EPROBE_DEFER)
- dev_err(&pdev->dev, "get bus clock failed %pe\n",
- pwm->bus_clk);
- return PTR_ERR(pwm->bus_clk);
- }
+ sun4ichip->bus_clk = devm_clk_get_optional(&pdev->dev, "bus");
+ if (IS_ERR(sun4ichip->bus_clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(sun4ichip->bus_clk),
+ "get bus clock failed\n");
- pwm->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
- if (IS_ERR(pwm->rst)) {
- if (PTR_ERR(pwm->rst) != -EPROBE_DEFER)
- dev_err(&pdev->dev, "get reset failed %pe\n",
- pwm->rst);
- return PTR_ERR(pwm->rst);
- }
+ sun4ichip->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
+ if (IS_ERR(sun4ichip->rst))
+ return dev_err_probe(&pdev->dev, PTR_ERR(sun4ichip->rst),
+ "get reset failed\n");
/* Deassert reset */
- ret = reset_control_deassert(pwm->rst);
+ ret = reset_control_deassert(sun4ichip->rst);
if (ret) {
dev_err(&pdev->dev, "cannot deassert reset control: %pe\n",
ERR_PTR(ret));
@@ -468,53 +439,42 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
* 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);
+ ret = clk_prepare_enable(sun4ichip->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;
+ chip->ops = &sun4i_pwm_ops;
- spin_lock_init(&pwm->ctrl_lock);
-
- ret = pwmchip_add(&pwm->chip);
+ ret = pwmchip_add(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);
+ platform_set_drvdata(pdev, chip);
return 0;
err_pwm_add:
- clk_disable_unprepare(pwm->bus_clk);
+ clk_disable_unprepare(sun4ichip->bus_clk);
err_bus:
- reset_control_assert(pwm->rst);
+ reset_control_assert(sun4ichip->rst);
return ret;
}
-static int sun4i_pwm_remove(struct platform_device *pdev)
+static void sun4i_pwm_remove(struct platform_device *pdev)
{
- struct sun4i_pwm_chip *pwm = platform_get_drvdata(pdev);
- int ret;
+ struct pwm_chip *chip = platform_get_drvdata(pdev);
+ struct sun4i_pwm_chip *sun4ichip = to_sun4i_pwm_chip(chip);
- ret = pwmchip_remove(&pwm->chip);
- if (ret)
- return ret;
+ pwmchip_remove(chip);
- clk_disable_unprepare(pwm->bus_clk);
- reset_control_assert(pwm->rst);
-
- return 0;
+ clk_disable_unprepare(sun4ichip->bus_clk);
+ reset_control_assert(sun4ichip->rst);
}
static struct platform_driver sun4i_pwm_driver = {
diff --git a/drivers/pwm/pwm-sunplus.c b/drivers/pwm/pwm-sunplus.c
new file mode 100644
index 000000000000..b342b843247b
--- /dev/null
+++ b/drivers/pwm/pwm-sunplus.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PWM device driver for SUNPLUS SP7021 SoC
+ *
+ * Links:
+ * Reference Manual:
+ * https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
+ *
+ * Reference Manual(PWM module):
+ * https://sunplus.atlassian.net/wiki/spaces/doc/pages/461144198/12.+Pulse+Width+Modulation+PWM
+ *
+ * Limitations:
+ * - Only supports normal polarity.
+ * - It output low when PWM channel disabled.
+ * - When the parameters change, current running period will not be completed
+ * and run new settings immediately.
+ * - In .apply() PWM output need to write register FREQ and DUTY. When first write FREQ
+ * done and not yet write DUTY, it has short timing gap use new FREQ and old DUTY.
+ *
+ * Author: Hammer Hsieh <hammerh0314@gmail.com>
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+#define SP7021_PWM_MODE0 0x000
+#define SP7021_PWM_MODE0_PWMEN(ch) BIT(ch)
+#define SP7021_PWM_MODE0_BYPASS(ch) BIT(8 + (ch))
+#define SP7021_PWM_MODE1 0x004
+#define SP7021_PWM_MODE1_CNT_EN(ch) BIT(ch)
+#define SP7021_PWM_FREQ(ch) (0x008 + 4 * (ch))
+#define SP7021_PWM_FREQ_MAX GENMASK(15, 0)
+#define SP7021_PWM_DUTY(ch) (0x018 + 4 * (ch))
+#define SP7021_PWM_DUTY_DD_SEL(ch) FIELD_PREP(GENMASK(9, 8), ch)
+#define SP7021_PWM_DUTY_MAX GENMASK(7, 0)
+#define SP7021_PWM_DUTY_MASK SP7021_PWM_DUTY_MAX
+#define SP7021_PWM_FREQ_SCALER 256
+#define SP7021_PWM_NUM 4
+
+struct sunplus_pwm {
+ void __iomem *base;
+ struct clk *clk;
+};
+
+static inline struct sunplus_pwm *to_sunplus_pwm(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static int sunplus_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct sunplus_pwm *priv = to_sunplus_pwm(chip);
+ u32 dd_freq, duty, mode0, mode1;
+ u64 clk_rate;
+
+ if (state->polarity != pwm->state.polarity)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ /* disable pwm channel output */
+ mode0 = readl(priv->base + SP7021_PWM_MODE0);
+ mode0 &= ~SP7021_PWM_MODE0_PWMEN(pwm->hwpwm);
+ writel(mode0, priv->base + SP7021_PWM_MODE0);
+ /* disable pwm channel clk source */
+ mode1 = readl(priv->base + SP7021_PWM_MODE1);
+ mode1 &= ~SP7021_PWM_MODE1_CNT_EN(pwm->hwpwm);
+ writel(mode1, priv->base + SP7021_PWM_MODE1);
+ return 0;
+ }
+
+ clk_rate = clk_get_rate(priv->clk);
+
+ /*
+ * The following calculations might overflow if clk is bigger
+ * than 256 GHz. In practise it's 202.5MHz, so this limitation
+ * is only theoretic.
+ */
+ if (clk_rate > (u64)SP7021_PWM_FREQ_SCALER * NSEC_PER_SEC)
+ return -EINVAL;
+
+ /*
+ * With clk_rate limited above we have dd_freq <= state->period,
+ * so this cannot overflow.
+ */
+ dd_freq = mul_u64_u64_div_u64(clk_rate, state->period, (u64)SP7021_PWM_FREQ_SCALER
+ * NSEC_PER_SEC);
+
+ if (dd_freq == 0)
+ return -EINVAL;
+
+ if (dd_freq > SP7021_PWM_FREQ_MAX)
+ dd_freq = SP7021_PWM_FREQ_MAX;
+
+ writel(dd_freq, priv->base + SP7021_PWM_FREQ(pwm->hwpwm));
+
+ /* cal and set pwm duty */
+ mode0 = readl(priv->base + SP7021_PWM_MODE0);
+ mode0 |= SP7021_PWM_MODE0_PWMEN(pwm->hwpwm);
+ mode1 = readl(priv->base + SP7021_PWM_MODE1);
+ mode1 |= SP7021_PWM_MODE1_CNT_EN(pwm->hwpwm);
+ if (state->duty_cycle == state->period) {
+ /* PWM channel output = high */
+ mode0 |= SP7021_PWM_MODE0_BYPASS(pwm->hwpwm);
+ duty = SP7021_PWM_DUTY_DD_SEL(pwm->hwpwm) | SP7021_PWM_DUTY_MAX;
+ } else {
+ mode0 &= ~SP7021_PWM_MODE0_BYPASS(pwm->hwpwm);
+ /*
+ * duty_ns <= period_ns 27 bits, clk_rate 28 bits, won't overflow.
+ */
+ duty = mul_u64_u64_div_u64(state->duty_cycle, clk_rate,
+ (u64)dd_freq * NSEC_PER_SEC);
+ duty = SP7021_PWM_DUTY_DD_SEL(pwm->hwpwm) | duty;
+ }
+ writel(duty, priv->base + SP7021_PWM_DUTY(pwm->hwpwm));
+ writel(mode1, priv->base + SP7021_PWM_MODE1);
+ writel(mode0, priv->base + SP7021_PWM_MODE0);
+
+ return 0;
+}
+
+static int sunplus_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct sunplus_pwm *priv = to_sunplus_pwm(chip);
+ u32 mode0, dd_freq, duty;
+ u64 clk_rate;
+
+ mode0 = readl(priv->base + SP7021_PWM_MODE0);
+
+ if (mode0 & BIT(pwm->hwpwm)) {
+ clk_rate = clk_get_rate(priv->clk);
+ dd_freq = readl(priv->base + SP7021_PWM_FREQ(pwm->hwpwm));
+ duty = readl(priv->base + SP7021_PWM_DUTY(pwm->hwpwm));
+ duty = FIELD_GET(SP7021_PWM_DUTY_MASK, duty);
+ /*
+ * dd_freq 16 bits, SP7021_PWM_FREQ_SCALER 8 bits
+ * NSEC_PER_SEC 30 bits, won't overflow.
+ */
+ state->period = DIV64_U64_ROUND_UP((u64)dd_freq * (u64)SP7021_PWM_FREQ_SCALER
+ * NSEC_PER_SEC, clk_rate);
+ /*
+ * dd_freq 16 bits, duty 8 bits, NSEC_PER_SEC 30 bits, won't overflow.
+ */
+ state->duty_cycle = DIV64_U64_ROUND_UP((u64)dd_freq * (u64)duty * NSEC_PER_SEC,
+ clk_rate);
+ state->enabled = true;
+ } else {
+ state->enabled = false;
+ }
+
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ return 0;
+}
+
+static const struct pwm_ops sunplus_pwm_ops = {
+ .apply = sunplus_pwm_apply,
+ .get_state = sunplus_pwm_get_state,
+};
+
+static void sunplus_pwm_clk_release(void *data)
+{
+ struct clk *clk = data;
+
+ clk_disable_unprepare(clk);
+}
+
+static int sunplus_pwm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pwm_chip *chip;
+ struct sunplus_pwm *priv;
+ int ret;
+
+ chip = devm_pwmchip_alloc(dev, SP7021_PWM_NUM, sizeof(*priv));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ priv = to_sunplus_pwm(chip);
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->clk))
+ return dev_err_probe(dev, PTR_ERR(priv->clk),
+ "get pwm clock failed\n");
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret < 0) {
+ dev_err(dev, "failed to enable clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(dev, sunplus_pwm_clk_release, priv->clk);
+ if (ret < 0) {
+ dev_err(dev, "failed to release clock: %d\n", ret);
+ return ret;
+ }
+
+ chip->ops = &sunplus_pwm_ops;
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Cannot register sunplus PWM\n");
+
+ return 0;
+}
+
+static const struct of_device_id sunplus_pwm_of_match[] = {
+ { .compatible = "sunplus,sp7021-pwm", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sunplus_pwm_of_match);
+
+static struct platform_driver sunplus_pwm_driver = {
+ .probe = sunplus_pwm_probe,
+ .driver = {
+ .name = "sunplus-pwm",
+ .of_match_table = sunplus_pwm_of_match,
+ },
+};
+module_platform_driver(sunplus_pwm_driver);
+
+MODULE_DESCRIPTION("Sunplus SoC PWM Driver");
+MODULE_AUTHOR("Hammer Hsieh <hammerh0314@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
index 1daf591025c0..172063b51d44 100644
--- a/drivers/pwm/pwm-tegra.c
+++ b/drivers/pwm/pwm-tegra.c
@@ -41,13 +41,16 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
-#include <linux/of_device.h>
+#include <linux/pm_opp.h>
#include <linux/pwm.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/consumer.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/reset.h>
+#include <soc/tegra/common.h>
+
#define PWM_ENABLE (1 << 31)
#define PWM_DUTY_WIDTH 8
#define PWM_DUTY_SHIFT 16
@@ -62,9 +65,6 @@ struct tegra_pwm_soc {
};
struct tegra_pwm_chip {
- struct pwm_chip chip;
- struct device *dev;
-
struct clk *clk;
struct reset_control*rst;
@@ -78,25 +78,24 @@ struct tegra_pwm_chip {
static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip)
{
- return container_of(chip, struct tegra_pwm_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
-static inline u32 pwm_readl(struct tegra_pwm_chip *chip, unsigned int num)
+static inline u32 pwm_readl(struct tegra_pwm_chip *pc, unsigned int offset)
{
- return readl(chip->regs + (num << 4));
+ return readl(pc->regs + (offset << 4));
}
-static inline void pwm_writel(struct tegra_pwm_chip *chip, unsigned int num,
- unsigned long val)
+static inline void pwm_writel(struct tegra_pwm_chip *pc, unsigned int offset, u32 value)
{
- writel(val, chip->regs + (num << 4));
+ writel(value, pc->regs + (offset << 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 long c = duty_ns;
unsigned long rate, required_clk_rate;
u32 val = 0;
int err;
@@ -142,10 +141,21 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* source clock rate as required_clk_rate, PWM controller will
* be able to configure the requested period.
*/
- required_clk_rate =
- (NSEC_PER_SEC / period_ns) << PWM_DUTY_WIDTH;
-
- err = clk_set_rate(pc->clk, required_clk_rate);
+ required_clk_rate = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC << PWM_DUTY_WIDTH,
+ period_ns);
+
+ if (required_clk_rate > clk_round_rate(pc->clk, required_clk_rate))
+ /*
+ * required_clk_rate is a lower bound for the input
+ * rate; for lower rates there is no value for PWM_SCALE
+ * that yields a period less than or equal to the
+ * requested period. Hence, for lower rates, double the
+ * required_clk_rate to get a clock rate that can meet
+ * the requested period.
+ */
+ required_clk_rate *= 2;
+
+ err = dev_pm_opp_set_rate(pwmchip_parent(chip), required_clk_rate);
if (err < 0)
return -EINVAL;
@@ -153,11 +163,9 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+ rate = mul_u64_u64_div_u64(pc->clk_rate, period_ns,
+ (u64)NSEC_PER_SEC << PWM_DUTY_WIDTH);
/*
* Since the actual PWM divider is the register's frequency divider
@@ -166,6 +174,8 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
*/
if (rate > 0)
rate--;
+ else
+ return -EINVAL;
/*
* Make sure that the rate will fit in the register's frequency
@@ -181,8 +191,8 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* before writing the register. Otherwise, keep it enabled.
*/
if (!pwm_is_enabled(pwm)) {
- err = clk_prepare_enable(pc->clk);
- if (err < 0)
+ err = pm_runtime_resume_and_get(pwmchip_parent(chip));
+ if (err)
return err;
} else
val |= PWM_ENABLE;
@@ -193,7 +203,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* If the PWM is not enabled, turn the clock off again to save power.
*/
if (!pwm_is_enabled(pwm))
- clk_disable_unprepare(pc->clk);
+ pm_runtime_put(pwmchip_parent(chip));
return 0;
}
@@ -204,8 +214,8 @@ static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
int rc = 0;
u32 val;
- rc = clk_prepare_enable(pc->clk);
- if (rc < 0)
+ rc = pm_runtime_resume_and_get(pwmchip_parent(chip));
+ if (rc)
return rc;
val = pwm_readl(pc, pwm->hwpwm);
@@ -224,45 +234,79 @@ static void tegra_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
val &= ~PWM_ENABLE;
pwm_writel(pc, pwm->hwpwm, val);
- clk_disable_unprepare(pc->clk);
+ pm_runtime_put_sync(pwmchip_parent(chip));
+}
+
+static int tegra_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+ bool enabled = pwm->state.enabled;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ if (enabled)
+ tegra_pwm_disable(chip, pwm);
+
+ return 0;
+ }
+
+ err = tegra_pwm_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!enabled)
+ err = tegra_pwm_enable(chip, pwm);
+
+ return err;
}
static const struct pwm_ops tegra_pwm_ops = {
- .config = tegra_pwm_config,
- .enable = tegra_pwm_enable,
- .disable = tegra_pwm_disable,
- .owner = THIS_MODULE,
+ .apply = tegra_pwm_apply,
};
static int tegra_pwm_probe(struct platform_device *pdev)
{
- struct tegra_pwm_chip *pwm;
- struct resource *r;
+ struct pwm_chip *chip;
+ struct tegra_pwm_chip *pc;
+ const struct tegra_pwm_soc *soc;
int ret;
- pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
- if (!pwm)
- return -ENOMEM;
+ soc = of_device_get_match_data(&pdev->dev);
- pwm->soc = of_device_get_match_data(&pdev->dev);
- pwm->dev = &pdev->dev;
+ chip = devm_pwmchip_alloc(&pdev->dev, soc->num_channels, sizeof(*pc));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ pc = to_tegra_pwm_chip(chip);
- 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);
+ pc->soc = soc;
- platform_set_drvdata(pdev, pwm);
+ pc->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(pc->regs))
+ return PTR_ERR(pc->regs);
- pwm->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(pwm->clk))
- return PTR_ERR(pwm->clk);
+ platform_set_drvdata(pdev, chip);
+
+ pc->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(pc->clk))
+ return PTR_ERR(pc->clk);
+
+ ret = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
+ if (ret)
+ return ret;
+
+ pm_runtime_enable(&pdev->dev);
+ ret = pm_runtime_resume_and_get(&pdev->dev);
+ if (ret)
+ return ret;
/* Set maximum frequency of the IP */
- ret = clk_set_rate(pwm->clk, pwm->soc->max_frequency);
+ ret = dev_pm_opp_set_rate(&pdev->dev, pc->soc->max_frequency);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to set max frequency: %d\n", ret);
- return ret;
+ goto put_pm;
}
/*
@@ -270,78 +314,86 @@ static int tegra_pwm_probe(struct platform_device *pdev)
* clock register resolutions. Get the configured frequency
* so that PWM period can be calculated more accurately.
*/
- pwm->clk_rate = clk_get_rate(pwm->clk);
+ pc->clk_rate = clk_get_rate(pc->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;
+ pc->min_period_ns =
+ (NSEC_PER_SEC / (pc->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);
+ pc->rst = devm_reset_control_get_exclusive(&pdev->dev, "pwm");
+ if (IS_ERR(pc->rst)) {
+ ret = PTR_ERR(pc->rst);
dev_err(&pdev->dev, "Reset control is not found: %d\n", ret);
- return ret;
+ goto put_pm;
}
- reset_control_deassert(pwm->rst);
+ reset_control_deassert(pc->rst);
- pwm->chip.dev = &pdev->dev;
- pwm->chip.ops = &tegra_pwm_ops;
- pwm->chip.base = -1;
- pwm->chip.npwm = pwm->soc->num_channels;
+ chip->ops = &tegra_pwm_ops;
- ret = pwmchip_add(&pwm->chip);
+ ret = pwmchip_add(chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
- reset_control_assert(pwm->rst);
- return ret;
+ reset_control_assert(pc->rst);
+ goto put_pm;
}
+ pm_runtime_put(&pdev->dev);
+
return 0;
+put_pm:
+ pm_runtime_put_sync_suspend(&pdev->dev);
+ pm_runtime_force_suspend(&pdev->dev);
+ return ret;
}
-static int tegra_pwm_remove(struct platform_device *pdev)
+static void tegra_pwm_remove(struct platform_device *pdev)
{
- struct tegra_pwm_chip *pc = platform_get_drvdata(pdev);
- unsigned int i;
- int err;
+ struct pwm_chip *chip = platform_get_drvdata(pdev);
+ struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
- if (WARN_ON(!pc))
- return -ENODEV;
+ pwmchip_remove(chip);
- err = clk_prepare_enable(pc->clk);
- if (err < 0)
- return err;
+ reset_control_assert(pc->rst);
- for (i = 0; i < pc->chip.npwm; i++) {
- struct pwm_device *pwm = &pc->chip.pwms[i];
+ pm_runtime_force_suspend(&pdev->dev);
+}
- if (!pwm_is_enabled(pwm))
- if (clk_prepare_enable(pc->clk) < 0)
- continue;
+static int __maybe_unused tegra_pwm_runtime_suspend(struct device *dev)
+{
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
+ int err;
- pwm_writel(pc, i, 0);
+ clk_disable_unprepare(pc->clk);
- clk_disable_unprepare(pc->clk);
+ err = pinctrl_pm_select_sleep_state(dev);
+ if (err) {
+ clk_prepare_enable(pc->clk);
+ return err;
}
- reset_control_assert(pc->rst);
- clk_disable_unprepare(pc->clk);
-
- return pwmchip_remove(&pc->chip);
+ return 0;
}
-#ifdef CONFIG_PM_SLEEP
-static int tegra_pwm_suspend(struct device *dev)
+static int __maybe_unused tegra_pwm_runtime_resume(struct device *dev)
{
- return pinctrl_pm_select_sleep_state(dev);
-}
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
+ int err;
-static int tegra_pwm_resume(struct device *dev)
-{
- return pinctrl_pm_select_default_state(dev);
+ err = pinctrl_pm_select_default_state(dev);
+ if (err)
+ return err;
+
+ err = clk_prepare_enable(pc->clk);
+ if (err) {
+ pinctrl_pm_select_sleep_state(dev);
+ return err;
+ }
+
+ return 0;
}
-#endif
static const struct tegra_pwm_soc tegra20_pwm_soc = {
.num_channels = 4,
@@ -367,7 +419,10 @@ static const struct of_device_id tegra_pwm_of_match[] = {
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)
+ SET_RUNTIME_PM_OPS(tegra_pwm_runtime_suspend, tegra_pwm_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
};
static struct platform_driver tegra_pwm_driver = {
diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c
index ab38c8203b79..67cc5e8bdb0e 100644
--- a/drivers/pwm/pwm-tiecap.c
+++ b/drivers/pwm/pwm-tiecap.c
@@ -2,7 +2,11 @@
/*
* ECAP PWM driver
*
- * Copyright (C) 2012 Texas Instruments, Inc. - http://www.ti.com/
+ * Copyright (C) 2012 Texas Instruments, Inc. - https://www.ti.com/
+ *
+ * Hardware properties:
+ * - On disable the PWM pin becomes an input, so the behaviour depends on
+ * external wiring.
*/
#include <linux/module.h>
@@ -12,7 +16,7 @@
#include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/pwm.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
/* ECAP registers and bits definitions */
#define CAP1 0x08
@@ -32,7 +36,6 @@ struct ecap_context {
};
struct ecap_pwm_chip {
- struct pwm_chip chip;
unsigned int clk_rate;
void __iomem *mmio_base;
struct ecap_context ctx;
@@ -40,7 +43,7 @@ struct ecap_pwm_chip {
static inline struct ecap_pwm_chip *to_ecap_pwm_chip(struct pwm_chip *chip)
{
- return container_of(chip, struct ecap_pwm_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
/*
@@ -48,16 +51,13 @@ static inline struct ecap_pwm_chip *to_ecap_pwm_chip(struct pwm_chip *chip)
* 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)
+ int duty_ns, int period_ns, int enabled)
{
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);
@@ -73,7 +73,7 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
duty_cycles = (u32)c;
}
- pm_runtime_get_sync(pc->chip.dev);
+ pm_runtime_get_sync(pwmchip_parent(chip));
value = readw(pc->mmio_base + ECCTL2);
@@ -82,7 +82,7 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
writew(value, pc->mmio_base + ECCTL2);
- if (!pwm_is_enabled(pwm)) {
+ if (!enabled) {
/* Update active registers if not running */
writel(duty_cycles, pc->mmio_base + CAP2);
writel(period_cycles, pc->mmio_base + CAP1);
@@ -96,14 +96,14 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
writel(period_cycles, pc->mmio_base + CAP3);
}
- if (!pwm_is_enabled(pwm)) {
+ if (!enabled) {
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);
+ pm_runtime_put_sync(pwmchip_parent(chip));
return 0;
}
@@ -114,7 +114,7 @@ static int ecap_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
u16 value;
- pm_runtime_get_sync(pc->chip.dev);
+ pm_runtime_get_sync(pwmchip_parent(chip));
value = readw(pc->mmio_base + ECCTL2);
@@ -127,7 +127,7 @@ static int ecap_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
writew(value, pc->mmio_base + ECCTL2);
- pm_runtime_put_sync(pc->chip.dev);
+ pm_runtime_put_sync(pwmchip_parent(chip));
return 0;
}
@@ -138,7 +138,7 @@ static int ecap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
u16 value;
/* Leave clock enabled on enabling PWM */
- pm_runtime_get_sync(pc->chip.dev);
+ pm_runtime_get_sync(pwmchip_parent(chip));
/*
* Enable 'Free run Time stamp counter mode' to start counter
@@ -165,24 +165,49 @@ static void ecap_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
writew(value, pc->mmio_base + ECCTL2);
/* Disable clock on PWM disable */
- pm_runtime_put_sync(pc->chip.dev);
+ pm_runtime_put_sync(pwmchip_parent(chip));
}
-static void ecap_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+static int ecap_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
{
- if (pwm_is_enabled(pwm)) {
- dev_warn(chip->dev, "Removing PWM device without disabling\n");
- pm_runtime_put_sync(chip->dev);
+ int err;
+ int enabled = pwm->state.enabled;
+
+ if (state->polarity != pwm->state.polarity) {
+
+ if (enabled) {
+ ecap_pwm_disable(chip, pwm);
+ enabled = false;
+ }
+
+ err = ecap_pwm_set_polarity(chip, pwm, state->polarity);
+ if (err)
+ return err;
+ }
+
+ if (!state->enabled) {
+ if (enabled)
+ ecap_pwm_disable(chip, pwm);
+ return 0;
}
+
+ if (state->period > NSEC_PER_SEC)
+ return -ERANGE;
+
+ err = ecap_pwm_config(chip, pwm, state->duty_cycle,
+ state->period, enabled);
+ if (err)
+ return err;
+
+ if (!enabled)
+ return ecap_pwm_enable(chip, pwm);
+
+ return 0;
}
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,
+ .apply = ecap_pwm_apply,
};
static const struct of_device_id ecap_of_match[] = {
@@ -196,13 +221,14 @@ 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 pwm_chip *chip;
struct clk *clk;
int ret;
- pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
- if (!pc)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*pc));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ pc = to_ecap_pwm_chip(chip);
clk = devm_clk_get(&pdev->dev, "fck");
if (IS_ERR(clk)) {
@@ -223,51 +249,44 @@ static int ecap_pwm_probe(struct platform_device *pdev)
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;
+ chip->ops = &ecap_pwm_ops;
- r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- pc->mmio_base = devm_ioremap_resource(&pdev->dev, r);
+ pc->mmio_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(pc->mmio_base))
return PTR_ERR(pc->mmio_base);
- ret = pwmchip_add(&pc->chip);
+ ret = devm_pwmchip_add(&pdev->dev, chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
return ret;
}
- platform_set_drvdata(pdev, pc);
+ platform_set_drvdata(pdev, chip);
pm_runtime_enable(&pdev->dev);
return 0;
}
-static int ecap_pwm_remove(struct platform_device *pdev)
+static void 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)
+static void ecap_pwm_save_context(struct pwm_chip *chip)
{
- pm_runtime_get_sync(pc->chip.dev);
+ struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
+
+ pm_runtime_get_sync(pwmchip_parent(chip));
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);
+ pm_runtime_put_sync(pwmchip_parent(chip));
}
-static void ecap_pwm_restore_context(struct ecap_pwm_chip *pc)
+static void ecap_pwm_restore_context(struct pwm_chip *chip)
{
+ struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
+
writel(pc->ctx.cap3, pc->mmio_base + CAP3);
writel(pc->ctx.cap4, pc->mmio_base + CAP4);
writew(pc->ctx.ecctl2, pc->mmio_base + ECCTL2);
@@ -275,10 +294,10 @@ static void ecap_pwm_restore_context(struct ecap_pwm_chip *pc)
static int ecap_pwm_suspend(struct device *dev)
{
- struct ecap_pwm_chip *pc = dev_get_drvdata(dev);
- struct pwm_device *pwm = pc->chip.pwms;
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct pwm_device *pwm = chip->pwms;
- ecap_pwm_save_context(pc);
+ ecap_pwm_save_context(chip);
/* Disable explicitly if PWM is running */
if (pwm_is_enabled(pwm))
@@ -289,25 +308,24 @@ static int ecap_pwm_suspend(struct device *dev)
static int ecap_pwm_resume(struct device *dev)
{
- struct ecap_pwm_chip *pc = dev_get_drvdata(dev);
- struct pwm_device *pwm = pc->chip.pwms;
+ struct pwm_chip *chip = dev_get_drvdata(dev);
+ struct pwm_device *pwm = chip->pwms;
/* Enable explicitly if PWM was running */
if (pwm_is_enabled(pwm))
pm_runtime_get_sync(dev);
- ecap_pwm_restore_context(pc);
+ ecap_pwm_restore_context(chip);
return 0;
}
-#endif
-static SIMPLE_DEV_PM_OPS(ecap_pwm_pm_ops, ecap_pwm_suspend, ecap_pwm_resume);
+static DEFINE_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,
+ .pm = pm_ptr(&ecap_pwm_pm_ops),
},
.probe = ecap_pwm_probe,
.remove = ecap_pwm_remove,
diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
index 7b4c770ce9d6..7a86cb090f76 100644
--- a/drivers/pwm/pwm-tiehrpwm.c
+++ b/drivers/pwm/pwm-tiehrpwm.c
@@ -2,7 +2,7 @@
/*
* EHRPWM PWM driver
*
- * Copyright (C) 2012 Texas Instruments, Inc. - http://www.ti.com/
+ * Copyright (C) 2012 Texas Instruments, Inc. - https://www.ti.com/
*/
#include <linux/module.h>
@@ -12,7 +12,7 @@
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/pm_runtime.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
/* EHRPWM registers and bits definitions */
@@ -36,7 +36,7 @@
#define CLKDIV_MAX 7
#define HSPCLKDIV_MAX 7
-#define PERIOD_MAX 0xFFFF
+#define PERIOD_MAX 0x10000
/* compare module registers */
#define CMPA 0x12
@@ -65,14 +65,10 @@
#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 AQCTL_CHANA_POLNORMAL (AQCTL_CAU_FRCLOW | AQCTL_ZRO_FRCHIGH)
+#define AQCTL_CHANA_POLINVERSED (AQCTL_CAU_FRCHIGH | AQCTL_ZRO_FRCLOW)
+#define AQCTL_CHANB_POLNORMAL (AQCTL_CBU_FRCLOW | AQCTL_ZRO_FRCHIGH)
+#define AQCTL_CHANB_POLINVERSED (AQCTL_CBU_FRCHIGH | AQCTL_ZRO_FRCLOW)
#define AQSFRC_RLDCSF_MASK (BIT(7) | BIT(6))
#define AQSFRC_RLDCSF_ZRO 0
@@ -105,18 +101,16 @@ struct ehrpwm_context {
};
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);
+ return pwmchip_get_drvdata(chip);
}
static inline u16 ehrpwm_read(void __iomem *base, unsigned int offset)
@@ -167,7 +161,7 @@ static int set_prescale_div(unsigned long rqst_prescaler, u16 *prescale_div,
*prescale_div = (1 << clkdiv) *
(hspclkdiv ? (hspclkdiv * 2) : 1);
- if (*prescale_div > rqst_prescaler) {
+ if (*prescale_div >= rqst_prescaler) {
*tb_clk_div = (clkdiv << TBCTL_CLKDIV_SHIFT) |
(hspclkdiv << TBCTL_HSPCLKDIV_SHIFT);
return 0;
@@ -178,51 +172,20 @@ static int set_prescale_div(unsigned long rqst_prescaler, u16 *prescale_div,
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)
+ u64 duty_ns, u64 period_ns, enum pwm_polarity polarity)
{
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;
+ u16 aqctl_val, aqctl_mask;
+ unsigned int aqctl_reg;
if (period_ns > NSEC_PER_SEC)
return -ERANGE;
@@ -232,15 +195,10 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
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;
- }
+ 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
@@ -256,7 +214,7 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
if (i == pwm->hwpwm)
continue;
- dev_err(chip->dev,
+ dev_err(pwmchip_parent(chip),
"period value conflicts with channel %u\n",
i);
return -EINVAL;
@@ -266,52 +224,73 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
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,
+ if (set_prescale_div(DIV_ROUND_UP(period_cycles, PERIOD_MAX), &ps_divval,
&tb_divval)) {
- dev_err(chip->dev, "Unsupported values\n");
+ dev_err(pwmchip_parent(chip), "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);
+ if (period_cycles < 1)
+ period_cycles = 1;
- ehrpwm_write(pc->mmio_base, TBPRD, period_cycles);
+ pm_runtime_get_sync(pwmchip_parent(chip));
- /* Configure ehrpwm counter for up-count mode */
- ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CTRMODE_MASK,
- TBCTL_CTRMODE_UP);
+ /* Update clock prescaler values */
+ ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CLKDIV_MASK, tb_divval);
- if (pwm->hwpwm == 1)
+ if (pwm->hwpwm == 1) {
/* Channel 1 configured with compare B register */
cmp_reg = CMPB;
- else
+
+ aqctl_reg = AQCTLB;
+ aqctl_mask = AQCTL_CBU_MASK;
+
+ if (polarity == PWM_POLARITY_INVERSED)
+ aqctl_val = AQCTL_CHANB_POLINVERSED;
+ else
+ aqctl_val = AQCTL_CHANB_POLNORMAL;
+
+ /* if duty_cycle is big, don't toggle on CBU */
+ if (duty_cycles > period_cycles)
+ aqctl_val &= ~AQCTL_CBU_MASK;
+
+ } else {
/* Channel 0 configured with compare A register */
cmp_reg = CMPA;
- ehrpwm_write(pc->mmio_base, cmp_reg, duty_cycles);
+ aqctl_reg = AQCTLA;
+ aqctl_mask = AQCTL_CAU_MASK;
- pm_runtime_put_sync(chip->dev);
+ if (polarity == PWM_POLARITY_INVERSED)
+ aqctl_val = AQCTL_CHANA_POLINVERSED;
+ else
+ aqctl_val = AQCTL_CHANA_POLNORMAL;
- return 0;
-}
+ /* if duty_cycle is big, don't toggle on CAU */
+ if (duty_cycles > period_cycles)
+ aqctl_val &= ~AQCTL_CAU_MASK;
+ }
-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);
+ aqctl_mask |= AQCTL_PRD_MASK | AQCTL_ZRO_MASK;
+ ehrpwm_modify(pc->mmio_base, aqctl_reg, aqctl_mask, aqctl_val);
+
+ /* 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 - 1);
+
+ /* Configure ehrpwm counter for up-count mode */
+ ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CTRMODE_MASK,
+ TBCTL_CTRMODE_UP);
+
+ if (!(duty_cycles > period_cycles))
+ ehrpwm_write(pc->mmio_base, cmp_reg, duty_cycles);
- /* Configuration of polarity in hardware delayed, do at enable */
- pc->polarity[pwm->hwpwm] = polarity;
+ pm_runtime_put_sync(pwmchip_parent(chip));
return 0;
}
@@ -323,7 +302,7 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
int ret;
/* Leave clock enabled on enabling PWM */
- pm_runtime_get_sync(chip->dev);
+ pm_runtime_get_sync(pwmchip_parent(chip));
/* Disabling Action Qualifier on PWM output */
if (pwm->hwpwm) {
@@ -340,14 +319,11 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "Failed to enable TBCLK for %s: %d\n",
+ dev_name(pwmchip_parent(chip)), ret);
return ret;
}
@@ -385,29 +361,49 @@ static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
clk_disable(pc->tbclk);
/* Disable clock on PWM disable */
- pm_runtime_put_sync(chip->dev);
+ pm_runtime_put_sync(pwmchip_parent(chip));
}
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);
+ /* Don't let a pwm without consumer block requests to the other channel */
+ pc->period_cycles[pwm->hwpwm] = 0;
+}
+
+static int ehrpwm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+ bool enabled = pwm->state.enabled;
+
+ if (state->polarity != pwm->state.polarity) {
+ if (enabled) {
+ ehrpwm_pwm_disable(chip, pwm);
+ enabled = false;
+ }
}
- /* set period value to zero on free */
- pc->period_cycles[pwm->hwpwm] = 0;
+ if (!state->enabled) {
+ if (enabled)
+ ehrpwm_pwm_disable(chip, pwm);
+ return 0;
+ }
+
+ err = ehrpwm_pwm_config(chip, pwm, state->duty_cycle, state->period, state->polarity);
+ if (err)
+ return err;
+
+ if (!enabled)
+ err = ehrpwm_pwm_enable(chip, pwm);
+
+ return err;
}
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,
+ .apply = ehrpwm_pwm_apply,
};
static const struct of_device_id ehrpwm_of_match[] = {
@@ -421,13 +417,14 @@ 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 pwm_chip *chip;
struct clk *clk;
int ret;
- pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
- if (!pc)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, NUM_PWM_CHANNEL, sizeof(*pc));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ pc = to_ehrpwm_pwm_chip(chip);
clk = devm_clk_get(&pdev->dev, "fck");
if (IS_ERR(clk)) {
@@ -437,10 +434,8 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
}
}
- if (IS_ERR(clk)) {
- dev_err(&pdev->dev, "failed to get clock\n");
- return PTR_ERR(clk);
- }
+ if (IS_ERR(clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(clk), "Failed to get fck\n");
pc->clk_rate = clk_get_rate(clk);
if (!pc->clk_rate) {
@@ -448,24 +443,16 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
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;
+ chip->ops = &ehrpwm_pwm_ops;
- r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- pc->mmio_base = devm_ioremap_resource(&pdev->dev, r);
+ pc->mmio_base = devm_platform_ioremap_resource(pdev, 0);
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);
- }
+ if (IS_ERR(pc->tbclk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(pc->tbclk), "Failed to get tbclk\n");
ret = clk_prepare(pc->tbclk);
if (ret < 0) {
@@ -473,13 +460,13 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
return ret;
}
- ret = pwmchip_add(&pc->chip);
+ ret = pwmchip_add(chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
goto err_clk_unprepare;
}
- platform_set_drvdata(pdev, pc);
+ platform_set_drvdata(pdev, chip);
pm_runtime_enable(&pdev->dev);
return 0;
@@ -490,21 +477,23 @@ err_clk_unprepare:
return ret;
}
-static int ehrpwm_pwm_remove(struct platform_device *pdev)
+static void ehrpwm_pwm_remove(struct platform_device *pdev)
{
- struct ehrpwm_pwm_chip *pc = platform_get_drvdata(pdev);
+ struct pwm_chip *chip = platform_get_drvdata(pdev);
+ struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
+
+ pwmchip_remove(chip);
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)
+static void ehrpwm_pwm_save_context(struct pwm_chip *chip)
{
- pm_runtime_get_sync(pc->chip.dev);
+ struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
+
+ pm_runtime_get_sync(pwmchip_parent(chip));
pc->ctx.tbctl = ehrpwm_read(pc->mmio_base, TBCTL);
pc->ctx.tbprd = ehrpwm_read(pc->mmio_base, TBPRD);
@@ -515,11 +504,13 @@ static void ehrpwm_pwm_save_context(struct ehrpwm_pwm_chip *pc)
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);
+ pm_runtime_put_sync(pwmchip_parent(chip));
}
-static void ehrpwm_pwm_restore_context(struct ehrpwm_pwm_chip *pc)
+static void ehrpwm_pwm_restore_context(struct pwm_chip *chip)
{
+ struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
+
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);
@@ -532,13 +523,13 @@ static void ehrpwm_pwm_restore_context(struct ehrpwm_pwm_chip *pc)
static int ehrpwm_pwm_suspend(struct device *dev)
{
- struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev);
+ struct pwm_chip *chip = dev_get_drvdata(dev);
unsigned int i;
- ehrpwm_pwm_save_context(pc);
+ ehrpwm_pwm_save_context(chip);
- for (i = 0; i < pc->chip.npwm; i++) {
- struct pwm_device *pwm = &pc->chip.pwms[i];
+ for (i = 0; i < chip->npwm; i++) {
+ struct pwm_device *pwm = &chip->pwms[i];
if (!pwm_is_enabled(pwm))
continue;
@@ -552,11 +543,11 @@ static int ehrpwm_pwm_suspend(struct device *dev)
static int ehrpwm_pwm_resume(struct device *dev)
{
- struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev);
+ struct pwm_chip *chip = dev_get_drvdata(dev);
unsigned int i;
- for (i = 0; i < pc->chip.npwm; i++) {
- struct pwm_device *pwm = &pc->chip.pwms[i];
+ for (i = 0; i < chip->npwm; i++) {
+ struct pwm_device *pwm = &chip->pwms[i];
if (!pwm_is_enabled(pwm))
continue;
@@ -565,20 +556,19 @@ static int ehrpwm_pwm_resume(struct device *dev)
pm_runtime_get_sync(dev);
}
- ehrpwm_pwm_restore_context(pc);
+ ehrpwm_pwm_restore_context(chip);
return 0;
}
-#endif
-static SIMPLE_DEV_PM_OPS(ehrpwm_pwm_pm_ops, ehrpwm_pwm_suspend,
- ehrpwm_pwm_resume);
+static DEFINE_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,
+ .pm = pm_ptr(&ehrpwm_pwm_pm_ops),
},
.probe = ehrpwm_pwm_probe,
.remove = ehrpwm_pwm_remove,
diff --git a/drivers/pwm/pwm-twl-led.c b/drivers/pwm/pwm-twl-led.c
index 630b9a578820..a555cc3be4b3 100644
--- a/drivers/pwm/pwm-twl-led.c
+++ b/drivers/pwm/pwm-twl-led.c
@@ -7,6 +7,22 @@
*
* This driver is a complete rewrite of the former pwm-twl6030.c authorded by:
* Hemanth V <hemanthv@ti.com>
+ *
+ * Reference manual for the twl6030 is available at:
+ * https://www.ti.com/lit/ds/symlink/twl6030.pdf
+ *
+ * Limitations:
+ * - The twl6030 hardware only supports two period lengths (128 clock ticks and
+ * 64 clock ticks), the driver only uses 128 ticks
+ * - The hardware doesn't support ON = 0, so the active part of a period doesn't
+ * start at its beginning.
+ * - The hardware could support inverted polarity (with a similar limitation as
+ * for normal: the last clock tick is always inactive).
+ * - The hardware emits a constant low output when disabled.
+ * - A request for .duty_cycle = 0 results in an output wave with one active
+ * clock tick per period. This should better use the disabled state.
+ * - The driver only implements setting the relative duty cycle.
+ * - The driver doesn't implement .get_state().
*/
#include <linux/module.h>
@@ -45,14 +61,9 @@
#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);
+ return pwmchip_get_drvdata(chip);
}
static int twl4030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -84,59 +95,88 @@ static int twl4030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+ dev_err(pwmchip_parent(chip), "%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;
+ dev_err(pwmchip_parent(chip), "%s: Failed to read LEDEN\n", pwm->label);
+ return ret;
}
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);
+ dev_err(pwmchip_parent(chip), "%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;
+ dev_err(pwmchip_parent(chip), "%s: Failed to read LEDEN\n", pwm->label);
+ return;
}
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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to disable PWM\n", pwm->label);
+}
+
+static int twl4030_pwmled_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int ret;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ if (pwm->state.enabled)
+ twl4030_pwmled_disable(chip, pwm);
+
+ return 0;
+ }
+
+ /*
+ * We cannot skip calling ->config even if state->period ==
+ * pwm->state.period && state->duty_cycle == pwm->state.duty_cycle
+ * because we might have exited early in the last call to
+ * pwm_apply_might_sleep because of !state->enabled and so the two values in
+ * pwm->state might not be configured in hardware.
+ */
+ ret = twl4030_pwmled_config(chip, pwm,
+ state->duty_cycle, state->period);
+ if (ret)
+ return ret;
+
+ if (!pwm->state.enabled)
+ ret = twl4030_pwmled_enable(chip, pwm);
-out:
- mutex_unlock(&twl->mutex);
+ return ret;
}
+
+static const struct pwm_ops twl4030_pwmled_ops = {
+ .apply = twl4030_pwmled_apply,
+};
+
static int twl6030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
@@ -149,23 +189,21 @@ static int twl6030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+ dev_err(pwmchip_parent(chip), "%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",
+ dev_err(pwmchip_parent(chip), "%s: Failed to read PWM_CTRL2\n",
pwm->label);
- goto out;
+ return ret;
}
val &= ~TWL6040_LED_MODE_MASK;
@@ -173,26 +211,22 @@ static int twl6030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%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",
+ dev_err(pwmchip_parent(chip), "%s: Failed to read PWM_CTRL2\n",
pwm->label);
- goto out;
+ return;
}
val &= ~TWL6040_LED_MODE_MASK;
@@ -200,24 +234,45 @@ static void twl6030_pwmled_disable(struct pwm_chip *chip,
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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to disable PWM\n", pwm->label);
+}
+
+static int twl6030_pwmled_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+
+ if (state->polarity != pwm->state.polarity)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ if (pwm->state.enabled)
+ twl6030_pwmled_disable(chip, pwm);
+
+ return 0;
+ }
+
+ err = twl6030_pwmled_config(chip, pwm,
+ state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!pwm->state.enabled)
+ err = twl6030_pwmled_enable(chip, pwm);
-out:
- mutex_unlock(&twl->mutex);
+ return err;
}
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",
+ dev_err(pwmchip_parent(chip), "%s: Failed to read PWM_CTRL2\n",
pwm->label);
- goto out;
+ return ret;
}
val &= ~TWL6040_LED_MODE_MASK;
@@ -225,25 +280,21 @@ static int twl6030_pwmled_request(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%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",
+ dev_err(pwmchip_parent(chip), "%s: Failed to read PWM_CTRL2\n",
pwm->label);
- goto out;
+ return;
}
val &= ~TWL6040_LED_MODE_MASK;
@@ -251,64 +302,36 @@ static void twl6030_pwmled_free(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to free PWM\n", pwm->label);
}
-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,
+ .apply = twl6030_pwmled_apply,
.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;
+ struct pwm_chip *chip;
+ unsigned int npwm;
+ const struct pwm_ops *ops;
if (twl_class_is_4030()) {
- twl->chip.ops = &twl4030_pwmled_ops;
- twl->chip.npwm = 2;
+ ops = &twl4030_pwmled_ops;
+ npwm = 2;
} else {
- twl->chip.ops = &twl6030_pwmled_ops;
- twl->chip.npwm = 1;
+ ops = &twl6030_pwmled_ops;
+ 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);
+ chip = devm_pwmchip_alloc(&pdev->dev, npwm, 0);
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
- return 0;
-}
-
-static int twl_pwmled_remove(struct platform_device *pdev)
-{
- struct twl_pwmled_chip *twl = platform_get_drvdata(pdev);
+ chip->ops = ops;
- return pwmchip_remove(&twl->chip);
+ return devm_pwmchip_add(&pdev->dev, chip);
}
#ifdef CONFIG_OF
@@ -326,7 +349,6 @@ static struct platform_driver twl_pwmled_driver = {
.of_match_table = of_match_ptr(twl_pwmled_of_match),
},
.probe = twl_pwmled_probe,
- .remove = twl_pwmled_remove,
};
module_platform_driver(twl_pwmled_driver);
diff --git a/drivers/pwm/pwm-twl.c b/drivers/pwm/pwm-twl.c
index aee67974f353..8f981ffff4b4 100644
--- a/drivers/pwm/pwm-twl.c
+++ b/drivers/pwm/pwm-twl.c
@@ -46,7 +46,6 @@
#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;
@@ -54,13 +53,13 @@ struct twl_pwm_chip {
static inline struct twl_pwm_chip *to_twl(struct pwm_chip *chip)
{
- return container_of(chip, struct twl_pwm_chip, chip);
+ return pwmchip_get_drvdata(chip);
}
static int twl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
- int duty_ns, int period_ns)
+ u64 duty_ns, u64 period_ns)
{
- int duty_cycle = DIV_ROUND_UP(duty_ns * TWL_PWM_MAX, period_ns) + 1;
+ int duty_cycle = DIV64_U64_ROUND_UP(duty_ns * TWL_PWM_MAX, period_ns) + 1;
u8 pwm_config[2] = { 1, 0 };
int base, ret;
@@ -86,7 +85,7 @@ static int twl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to configure PWM\n", pwm->label);
return ret;
}
@@ -100,7 +99,7 @@ static int twl4030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to read GPBR1\n", pwm->label);
goto out;
}
@@ -108,13 +107,13 @@ static int twl4030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to enable PWM\n", pwm->label);
out:
mutex_unlock(&twl->mutex);
@@ -130,7 +129,7 @@ static void twl4030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to read GPBR1\n", pwm->label);
goto out;
}
@@ -138,13 +137,13 @@ static void twl4030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to disable PWM\n", pwm->label);
out:
mutex_unlock(&twl->mutex);
@@ -167,7 +166,7 @@ static int twl4030_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to read PMBR1\n", pwm->label);
goto out;
}
@@ -181,7 +180,7 @@ static int twl4030_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to request PWM\n", pwm->label);
out:
mutex_unlock(&twl->mutex);
@@ -202,7 +201,7 @@ static void twl4030_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to read PMBR1\n", pwm->label);
goto out;
}
@@ -212,7 +211,7 @@ static void twl4030_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to free PWM\n", pwm->label);
out:
mutex_unlock(&twl->mutex);
@@ -231,7 +230,7 @@ static int twl6030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to enable PWM\n", pwm->label);
goto out;
}
@@ -254,7 +253,7 @@ static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to disable PWM\n", pwm->label);
goto out;
}
@@ -262,7 +261,7 @@ static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to disable PWM\n", pwm->label);
goto out;
}
@@ -270,7 +269,7 @@ static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ dev_err(pwmchip_parent(chip), "%s: Failed to disable PWM\n", pwm->label);
goto out;
}
@@ -279,56 +278,84 @@ out:
mutex_unlock(&twl->mutex);
}
+static int twl4030_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ if (pwm->state.enabled)
+ twl4030_pwm_disable(chip, pwm);
+
+ return 0;
+ }
+
+ err = twl_pwm_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!pwm->state.enabled)
+ err = twl4030_pwm_enable(chip, pwm);
+
+ return err;
+}
+
+static int twl6030_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (!state->enabled) {
+ if (pwm->state.enabled)
+ twl6030_pwm_disable(chip, pwm);
+
+ return 0;
+ }
+
+ err = twl_pwm_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!pwm->state.enabled)
+ err = twl6030_pwm_enable(chip, pwm);
+
+ return err;
+}
+
static const struct pwm_ops twl4030_pwm_ops = {
- .config = twl_pwm_config,
- .enable = twl4030_pwm_enable,
- .disable = twl4030_pwm_disable,
+ .apply = twl4030_pwm_apply,
.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,
+ .apply = twl6030_pwm_apply,
};
static int twl_pwm_probe(struct platform_device *pdev)
{
+ struct pwm_chip *chip;
struct twl_pwm_chip *twl;
- int ret;
- twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL);
- if (!twl)
- return -ENOMEM;
+ chip = devm_pwmchip_alloc(&pdev->dev, 2, sizeof(*twl));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ twl = to_twl(chip);
if (twl_class_is_4030())
- twl->chip.ops = &twl4030_pwm_ops;
+ 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;
+ chip->ops = &twl6030_pwm_ops;
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);
+ return devm_pwmchip_add(&pdev->dev, chip);
}
#ifdef CONFIG_OF
@@ -346,7 +373,6 @@ static struct platform_driver twl_pwm_driver = {
.of_match_table = of_match_ptr(twl_pwm_of_match),
},
.probe = twl_pwm_probe,
- .remove = twl_pwm_remove,
};
module_platform_driver(twl_pwm_driver);
diff --git a/drivers/pwm/pwm-visconti.c b/drivers/pwm/pwm-visconti.c
new file mode 100644
index 000000000000..28fae4979e3f
--- /dev/null
+++ b/drivers/pwm/pwm-visconti.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Toshiba Visconti pulse-width-modulation controller driver
+ *
+ * Copyright (c) 2020 - 2021 TOSHIBA CORPORATION
+ * Copyright (c) 2020 - 2021 Toshiba Electronic Devices & Storage Corporation
+ *
+ * Authors: Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp>
+ *
+ * Limitations:
+ * - The fixed input clock is running at 1 MHz and is divided by either 1,
+ * 2, 4 or 8.
+ * - When the settings of the PWM are modified, the new values are shadowed
+ * in hardware until the PIPGM_PCSR register is written and the currently
+ * running period is completed. This way the hardware switches atomically
+ * from the old setting to the new.
+ * - Disabling the hardware completes the currently running period and keeps
+ * the output at low level at all times.
+ */
+
+#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 PIPGM_PCSR(ch) (0x400 + 4 * (ch))
+#define PIPGM_PDUT(ch) (0x420 + 4 * (ch))
+#define PIPGM_PWMC(ch) (0x440 + 4 * (ch))
+
+#define PIPGM_PWMC_PWMACT BIT(5)
+#define PIPGM_PWMC_CLK_MASK GENMASK(1, 0)
+#define PIPGM_PWMC_POLARITY_MASK GENMASK(5, 5)
+
+struct visconti_pwm_chip {
+ void __iomem *base;
+};
+
+static inline struct visconti_pwm_chip *visconti_pwm_from_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static int visconti_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct visconti_pwm_chip *priv = visconti_pwm_from_chip(chip);
+ u32 period, duty_cycle, pwmc0;
+
+ if (!state->enabled) {
+ writel(0, priv->base + PIPGM_PCSR(pwm->hwpwm));
+ return 0;
+ }
+
+ /*
+ * The biggest period the hardware can provide is
+ * (0xffff << 3) * 1000 ns
+ * This value fits easily in an u32, so simplify the maths by
+ * capping the values to 32 bit integers.
+ */
+ if (state->period > (0xffff << 3) * 1000)
+ period = (0xffff << 3) * 1000;
+ else
+ period = state->period;
+
+ if (state->duty_cycle > period)
+ duty_cycle = period;
+ else
+ duty_cycle = state->duty_cycle;
+
+ /*
+ * The input clock runs fixed at 1 MHz, so we have only
+ * microsecond resolution and so can divide by
+ * NSEC_PER_SEC / CLKFREQ = 1000 without losing precision.
+ */
+ period /= 1000;
+ duty_cycle /= 1000;
+
+ if (!period)
+ return -ERANGE;
+
+ /*
+ * PWMC controls a divider that divides the input clk by a power of two
+ * between 1 and 8. As a smaller divider yields higher precision, pick
+ * the smallest possible one. As period is at most 0xffff << 3, pwmc0 is
+ * in the intended range [0..3].
+ */
+ pwmc0 = fls(period >> 16);
+ if (WARN_ON(pwmc0 > 3))
+ return -EINVAL;
+
+ period >>= pwmc0;
+ duty_cycle >>= pwmc0;
+
+ if (state->polarity == PWM_POLARITY_INVERSED)
+ pwmc0 |= PIPGM_PWMC_PWMACT;
+ writel(pwmc0, priv->base + PIPGM_PWMC(pwm->hwpwm));
+ writel(duty_cycle, priv->base + PIPGM_PDUT(pwm->hwpwm));
+ writel(period, priv->base + PIPGM_PCSR(pwm->hwpwm));
+
+ return 0;
+}
+
+static int visconti_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct visconti_pwm_chip *priv = visconti_pwm_from_chip(chip);
+ u32 period, duty, pwmc0, pwmc0_clk;
+
+ period = readl(priv->base + PIPGM_PCSR(pwm->hwpwm));
+ duty = readl(priv->base + PIPGM_PDUT(pwm->hwpwm));
+ pwmc0 = readl(priv->base + PIPGM_PWMC(pwm->hwpwm));
+ pwmc0_clk = pwmc0 & PIPGM_PWMC_CLK_MASK;
+
+ state->period = (period << pwmc0_clk) * NSEC_PER_USEC;
+ state->duty_cycle = (duty << pwmc0_clk) * NSEC_PER_USEC;
+ if (pwmc0 & PIPGM_PWMC_POLARITY_MASK)
+ state->polarity = PWM_POLARITY_INVERSED;
+ else
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ state->enabled = true;
+
+ return 0;
+}
+
+static const struct pwm_ops visconti_pwm_ops = {
+ .apply = visconti_pwm_apply,
+ .get_state = visconti_pwm_get_state,
+};
+
+static int visconti_pwm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pwm_chip *chip;
+ struct visconti_pwm_chip *priv;
+ int ret;
+
+ chip = devm_pwmchip_alloc(dev, 4, sizeof(*priv));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ priv = visconti_pwm_from_chip(chip);
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ chip->ops = &visconti_pwm_ops;
+
+ ret = devm_pwmchip_add(&pdev->dev, chip);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "Cannot register visconti PWM\n");
+
+ return 0;
+}
+
+static const struct of_device_id visconti_pwm_of_match[] = {
+ { .compatible = "toshiba,visconti-pwm", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, visconti_pwm_of_match);
+
+static struct platform_driver visconti_pwm_driver = {
+ .driver = {
+ .name = "pwm-visconti",
+ .of_match_table = visconti_pwm_of_match,
+ },
+ .probe = visconti_pwm_probe,
+};
+module_platform_driver(visconti_pwm_driver);
+
+MODULE_DESCRIPTION("Toshiba Visconti Pulse Width Modulator driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp>");
+MODULE_ALIAS("platform:pwm-visconti");
diff --git a/drivers/pwm/pwm-vt8500.c b/drivers/pwm/pwm-vt8500.c
index 11d45e56a923..016c82d65527 100644
--- a/drivers/pwm/pwm-vt8500.c
+++ b/drivers/pwm/pwm-vt8500.c
@@ -6,6 +6,7 @@
* Copyright (C) 2010 Alexey Charkov <alchark@gmail.com>
*/
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
@@ -18,10 +19,6 @@
#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.
@@ -48,16 +45,19 @@
#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)
+static inline struct vt8500_chip *to_vt8500_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(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)
+static inline void vt8500_pwm_busy_wait(struct pwm_chip *chip, int nr, u8 bitmask)
{
+ struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
int loops = msecs_to_loops(10);
u32 mask = bitmask << (nr << 8);
@@ -65,12 +65,12 @@ static inline void pwm_busy_wait(struct vt8500_chip *vt8500, int nr, u8 bitmask)
cpu_relax();
if (unlikely(!loops))
- dev_warn(vt8500->chip.dev, "Waiting for status bits 0x%x to clear timed out\n",
+ dev_warn(pwmchip_parent(chip), "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)
+ u64 duty_ns, u64 period_ns)
{
struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
unsigned long long c;
@@ -80,7 +80,7 @@ static int vt8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
err = clk_enable(vt8500->clk);
if (err < 0) {
- dev_err(chip->dev, "failed to enable clock\n");
+ dev_err(pwmchip_parent(chip), "failed to enable clock\n");
return err;
}
@@ -102,22 +102,22 @@ static int vt8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
}
c = (unsigned long long)pv * duty_ns;
- do_div(c, period_ns);
- dc = c;
+
+ dc = div64_u64(c, period_ns);
writel(prescale, vt8500->base + REG_SCALAR(pwm->hwpwm));
- pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_SCALAR_UPDATE);
+ vt8500_pwm_busy_wait(chip, pwm->hwpwm, STATUS_SCALAR_UPDATE);
writel(pv, vt8500->base + REG_PERIOD(pwm->hwpwm));
- pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_PERIOD_UPDATE);
+ vt8500_pwm_busy_wait(chip, pwm->hwpwm, STATUS_PERIOD_UPDATE);
writel(dc, vt8500->base + REG_DUTY(pwm->hwpwm));
- pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_DUTY_UPDATE);
+ vt8500_pwm_busy_wait(chip, 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);
+ vt8500_pwm_busy_wait(chip, pwm->hwpwm, STATUS_CTRL_UPDATE);
clk_disable(vt8500->clk);
return 0;
@@ -131,14 +131,14 @@ static int vt8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
err = clk_enable(vt8500->clk);
if (err < 0) {
- dev_err(chip->dev, "failed to enable clock\n");
+ dev_err(pwmchip_parent(chip), "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);
+ vt8500_pwm_busy_wait(chip, pwm->hwpwm, STATUS_CTRL_UPDATE);
return 0;
}
@@ -151,7 +151,7 @@ static void vt8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
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);
+ vt8500_pwm_busy_wait(chip, pwm->hwpwm, STATUS_CTRL_UPDATE);
clk_disable(vt8500->clk);
}
@@ -171,17 +171,59 @@ static int vt8500_pwm_set_polarity(struct pwm_chip *chip,
val &= ~CTRL_INVERT;
writel(val, vt8500->base + REG_CTRL(pwm->hwpwm));
- pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE);
+ vt8500_pwm_busy_wait(chip, pwm->hwpwm, STATUS_CTRL_UPDATE);
return 0;
}
+static int vt8500_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+ bool enabled = pwm->state.enabled;
+
+ if (state->polarity != pwm->state.polarity) {
+ /*
+ * Changing the polarity of a running PWM is only allowed when
+ * the PWM driver implements ->apply().
+ */
+ if (enabled) {
+ vt8500_pwm_disable(chip, pwm);
+
+ enabled = false;
+ }
+
+ err = vt8500_pwm_set_polarity(chip, pwm, state->polarity);
+ if (err)
+ return err;
+ }
+
+ if (!state->enabled) {
+ if (enabled)
+ vt8500_pwm_disable(chip, pwm);
+
+ return 0;
+ }
+
+ /*
+ * We cannot skip calling ->config even if state->period ==
+ * pwm->state.period && state->duty_cycle == pwm->state.duty_cycle
+ * because we might have exited early in the last call to
+ * pwm_apply_might_sleep because of !state->enabled and so the two values in
+ * pwm->state might not be configured in hardware.
+ */
+ err = vt8500_pwm_config(chip, pwm, state->duty_cycle, state->period);
+ if (err)
+ return err;
+
+ if (!enabled)
+ err = vt8500_pwm_enable(chip, pwm);
+
+ return err;
+}
+
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,
+ .apply = vt8500_pwm_apply,
};
static const struct of_device_id vt8500_pwm_dt_ids[] = {
@@ -192,71 +234,38 @@ 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 pwm_chip *chip;
+ struct vt8500_chip *vt8500;
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);
+ if (!np)
+ return dev_err_probe(&pdev->dev, -EINVAL, "invalid devicetree node\n");
- ret = clk_prepare(chip->clk);
- if (ret < 0) {
- dev_err(&pdev->dev, "failed to prepare clock\n");
- return ret;
- }
+ chip = devm_pwmchip_alloc(&pdev->dev, VT8500_NR_PWMS, sizeof(*vt8500));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ vt8500 = to_vt8500_chip(chip);
- ret = pwmchip_add(&chip->chip);
- if (ret < 0) {
- dev_err(&pdev->dev, "failed to add PWM chip\n");
- clk_unprepare(chip->clk);
- return ret;
- }
+ chip->ops = &vt8500_pwm_ops;
- platform_set_drvdata(pdev, chip);
- return ret;
-}
+ vt8500->clk = devm_clk_get_prepared(&pdev->dev, NULL);
+ if (IS_ERR(vt8500->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(vt8500->clk), "clock source not specified\n");
-static int vt8500_pwm_remove(struct platform_device *pdev)
-{
- struct vt8500_chip *chip;
-
- chip = platform_get_drvdata(pdev);
- if (chip == NULL)
- return -ENODEV;
+ vt8500->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(vt8500->base))
+ return PTR_ERR(vt8500->base);
- clk_unprepare(chip->clk);
+ ret = devm_pwmchip_add(&pdev->dev, chip);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n");
- return pwmchip_remove(&chip->chip);
+ return 0;
}
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,
diff --git a/drivers/pwm/pwm-xilinx.c b/drivers/pwm/pwm-xilinx.c
new file mode 100644
index 000000000000..52c241982807
--- /dev/null
+++ b/drivers/pwm/pwm-xilinx.c
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Sean Anderson <sean.anderson@seco.com>
+ *
+ * Limitations:
+ * - When changing both duty cycle and period, we may end up with one cycle
+ * with the old duty cycle and the new period. This is because the counters
+ * may only be reloaded by first stopping them, or by letting them be
+ * automatically reloaded at the end of a cycle. If this automatic reload
+ * happens after we set TLR0 but before we set TLR1 then we will have a
+ * bad cycle. This could probably be fixed by reading TCR0 just before
+ * reprogramming, but I think it would add complexity for little gain.
+ * - Cannot produce 100% duty cycle by configuring the TLRs. This might be
+ * possible by stopping the counters at an appropriate point in the cycle,
+ * but this is not (yet) implemented.
+ * - Only produces "normal" output.
+ * - Always produces low output if disabled.
+ */
+
+#include <clocksource/timer-xilinx.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+
+/*
+ * The following functions are "common" to drivers for this device, and may be
+ * exported at a future date.
+ */
+u32 xilinx_timer_tlr_cycles(struct xilinx_timer_priv *priv, u32 tcsr,
+ u64 cycles)
+{
+ WARN_ON(cycles < 2 || cycles - 2 > priv->max);
+
+ if (tcsr & TCSR_UDT)
+ return cycles - 2;
+ return priv->max - cycles + 2;
+}
+
+unsigned int xilinx_timer_get_period(struct xilinx_timer_priv *priv,
+ u32 tlr, u32 tcsr)
+{
+ u64 cycles;
+
+ if (tcsr & TCSR_UDT)
+ cycles = tlr + 2;
+ else
+ cycles = (u64)priv->max - tlr + 2;
+
+ /* cycles has a max of 2^32 + 2, so we can't overflow */
+ return DIV64_U64_ROUND_UP(cycles * NSEC_PER_SEC,
+ clk_get_rate(priv->clk));
+}
+
+/*
+ * The idea here is to capture whether the PWM is actually running (e.g.
+ * because we or the bootloader set it up) and we need to be careful to ensure
+ * we don't cause a glitch. According to the data sheet, to enable the PWM we
+ * need to
+ *
+ * - Set both timers to generate mode (MDT=1)
+ * - Set both timers to PWM mode (PWMA=1)
+ * - Enable the generate out signals (GENT=1)
+ *
+ * In addition,
+ *
+ * - The timer must be running (ENT=1)
+ * - The timer must auto-reload TLR into TCR (ARHT=1)
+ * - We must not be in the process of loading TLR into TCR (LOAD=0)
+ * - Cascade mode must be disabled (CASC=0)
+ *
+ * If any of these differ from usual, then the PWM is either disabled, or is
+ * running in a mode that this driver does not support.
+ */
+#define TCSR_PWM_SET (TCSR_GENT | TCSR_ARHT | TCSR_ENT | TCSR_PWMA)
+#define TCSR_PWM_CLEAR (TCSR_MDT | TCSR_LOAD)
+#define TCSR_PWM_MASK (TCSR_PWM_SET | TCSR_PWM_CLEAR)
+
+static inline struct xilinx_timer_priv
+*xilinx_pwm_chip_to_priv(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static bool xilinx_timer_pwm_enabled(u32 tcsr0, u32 tcsr1)
+{
+ return ((TCSR_PWM_MASK | TCSR_CASC) & tcsr0) == TCSR_PWM_SET &&
+ (TCSR_PWM_MASK & tcsr1) == TCSR_PWM_SET;
+}
+
+static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused,
+ const struct pwm_state *state)
+{
+ struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
+ u32 tlr0, tlr1, tcsr0, tcsr1;
+ u64 period_cycles, duty_cycles;
+ unsigned long rate;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ /*
+ * To be representable by TLR, cycles must be between 2 and
+ * priv->max + 2. To enforce this we can reduce the cycles, but we may
+ * not increase them. Caveat emptor: while this does result in more
+ * predictable rounding, it may also result in a completely different
+ * duty cycle (% high time) than what was requested.
+ */
+ rate = clk_get_rate(priv->clk);
+ /* Avoid overflow */
+ period_cycles = min_t(u64, state->period, U32_MAX * NSEC_PER_SEC);
+ period_cycles = mul_u64_u32_div(period_cycles, rate, NSEC_PER_SEC);
+ period_cycles = min_t(u64, period_cycles, priv->max + 2);
+ if (period_cycles < 2)
+ return -ERANGE;
+
+ /* Same thing for duty cycles */
+ duty_cycles = min_t(u64, state->duty_cycle, U32_MAX * NSEC_PER_SEC);
+ duty_cycles = mul_u64_u32_div(duty_cycles, rate, NSEC_PER_SEC);
+ duty_cycles = min_t(u64, duty_cycles, priv->max + 2);
+
+ /*
+ * If we specify 100% duty cycle, we will get 0% instead, so decrease
+ * the duty cycle count by one.
+ */
+ if (duty_cycles >= period_cycles)
+ duty_cycles = period_cycles - 1;
+
+ /* Round down to 0% duty cycle for unrepresentable duty cycles */
+ if (duty_cycles < 2)
+ duty_cycles = period_cycles;
+
+ regmap_read(priv->map, TCSR0, &tcsr0);
+ regmap_read(priv->map, TCSR1, &tcsr1);
+ tlr0 = xilinx_timer_tlr_cycles(priv, tcsr0, period_cycles);
+ tlr1 = xilinx_timer_tlr_cycles(priv, tcsr1, duty_cycles);
+ regmap_write(priv->map, TLR0, tlr0);
+ regmap_write(priv->map, TLR1, tlr1);
+
+ if (state->enabled) {
+ /*
+ * If the PWM is already running, then the counters will be
+ * reloaded at the end of the current cycle.
+ */
+ if (!xilinx_timer_pwm_enabled(tcsr0, tcsr1)) {
+ /* Load TLR into TCR */
+ regmap_write(priv->map, TCSR0, tcsr0 | TCSR_LOAD);
+ regmap_write(priv->map, TCSR1, tcsr1 | TCSR_LOAD);
+ /* Enable timers all at once with ENALL */
+ tcsr0 = (TCSR_PWM_SET & ~TCSR_ENT) | (tcsr0 & TCSR_UDT);
+ tcsr1 = TCSR_PWM_SET | TCSR_ENALL | (tcsr1 & TCSR_UDT);
+ regmap_write(priv->map, TCSR0, tcsr0);
+ regmap_write(priv->map, TCSR1, tcsr1);
+ }
+ } else {
+ regmap_write(priv->map, TCSR0, 0);
+ regmap_write(priv->map, TCSR1, 0);
+ }
+
+ return 0;
+}
+
+static int xilinx_pwm_get_state(struct pwm_chip *chip,
+ struct pwm_device *unused,
+ struct pwm_state *state)
+{
+ struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
+ u32 tlr0, tlr1, tcsr0, tcsr1;
+
+ regmap_read(priv->map, TLR0, &tlr0);
+ regmap_read(priv->map, TLR1, &tlr1);
+ regmap_read(priv->map, TCSR0, &tcsr0);
+ regmap_read(priv->map, TCSR1, &tcsr1);
+ state->period = xilinx_timer_get_period(priv, tlr0, tcsr0);
+ state->duty_cycle = xilinx_timer_get_period(priv, tlr1, tcsr1);
+ state->enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ /*
+ * 100% duty cycle results in constant low output. This may be (very)
+ * wrong if rate > 1 GHz, so fix this if you have such hardware :)
+ */
+ if (state->period == state->duty_cycle)
+ state->duty_cycle = 0;
+
+ return 0;
+}
+
+static const struct pwm_ops xilinx_pwm_ops = {
+ .apply = xilinx_pwm_apply,
+ .get_state = xilinx_pwm_get_state,
+};
+
+static const struct regmap_config xilinx_pwm_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+ .max_register = TCR1,
+};
+
+static int xilinx_pwm_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct xilinx_timer_priv *priv;
+ struct pwm_chip *chip;
+ u32 pwm_cells, one_timer, width;
+ void __iomem *regs;
+
+ /* If there are no PWM cells, this binding is for a timer */
+ ret = of_property_read_u32(np, "#pwm-cells", &pwm_cells);
+ if (ret == -EINVAL)
+ return -ENODEV;
+ if (ret)
+ return dev_err_probe(dev, ret, "could not read #pwm-cells\n");
+
+ chip = devm_pwmchip_alloc(dev, 1, sizeof(*priv));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ priv = xilinx_pwm_chip_to_priv(chip);
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ priv->map = devm_regmap_init_mmio(dev, regs,
+ &xilinx_pwm_regmap_config);
+ if (IS_ERR(priv->map))
+ return dev_err_probe(dev, PTR_ERR(priv->map),
+ "Could not create regmap\n");
+
+ ret = of_property_read_u32(np, "xlnx,one-timer-only", &one_timer);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Could not read xlnx,one-timer-only\n");
+
+ if (one_timer)
+ return dev_err_probe(dev, -EINVAL,
+ "Two timers required for PWM mode\n");
+
+ ret = of_property_read_u32(np, "xlnx,count-width", &width);
+ if (ret == -EINVAL)
+ width = 32;
+ else if (ret)
+ return dev_err_probe(dev, ret,
+ "Could not read xlnx,count-width\n");
+
+ if (width != 8 && width != 16 && width != 32)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid counter width %d\n", width);
+ priv->max = BIT_ULL(width) - 1;
+
+ /*
+ * The polarity of the Generate Out signals must be active high for PWM
+ * mode to work. We could determine this from the device tree, but
+ * alas, such properties are not allowed to be used.
+ */
+
+ priv->clk = devm_clk_get_enabled(dev, "s_axi_aclk");
+ if (IS_ERR(priv->clk))
+ return dev_err_probe(dev, PTR_ERR(priv->clk),
+ "Could not get clock\n");
+
+ ret = devm_clk_rate_exclusive_get(dev, priv->clk);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to lock clock rate\n");
+
+ chip->ops = &xilinx_pwm_ops;
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "Could not register PWM chip\n");
+
+ return 0;
+}
+
+static const struct of_device_id xilinx_pwm_of_match[] = {
+ { .compatible = "xlnx,xps-timer-1.00.a", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, xilinx_pwm_of_match);
+
+static struct platform_driver xilinx_pwm_driver = {
+ .probe = xilinx_pwm_probe,
+ .driver = {
+ .name = "xilinx-pwm",
+ .of_match_table = of_match_ptr(xilinx_pwm_of_match),
+ },
+};
+module_platform_driver(xilinx_pwm_driver);
+
+MODULE_ALIAS("platform:xilinx-pwm");
+MODULE_DESCRIPTION("PWM driver for Xilinx LogiCORE IP AXI Timer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-zx.c b/drivers/pwm/pwm-zx.c
deleted file mode 100644
index e2c21cc34a96..000000000000
--- a/drivers/pwm/pwm-zx.c
+++ /dev/null
@@ -1,279 +0,0 @@
-// 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);
- 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/pwm_th1520.rs b/drivers/pwm/pwm_th1520.rs
new file mode 100644
index 000000000000..e3b7e77356fc
--- /dev/null
+++ b/drivers/pwm/pwm_th1520.rs
@@ -0,0 +1,387 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2025 Samsung Electronics Co., Ltd.
+// Author: Michal Wilczynski <m.wilczynski@samsung.com>
+
+//! Rust T-HEAD TH1520 PWM driver
+//!
+//! Limitations:
+//! - The period and duty cycle are controlled by 32-bit hardware registers,
+//! limiting the maximum resolution.
+//! - The driver supports continuous output mode only; one-shot mode is not
+//! implemented.
+//! - The controller hardware provides up to 6 PWM channels.
+//! - Reconfiguration is glitch free - new period and duty cycle values are
+//! latched and take effect at the start of the next period.
+//! - Polarity is handled via a simple hardware inversion bit; arbitrary
+//! duty cycle offsets are not supported.
+//! - Disabling a channel is achieved by configuring its duty cycle to zero to
+//! produce a static low output. Clearing the `start` does not reliably
+//! force the static inactive level defined by the `INACTOUT` bit. Hence
+//! this method is not used in this driver.
+//!
+
+use core::ops::Deref;
+use kernel::{
+ c_str,
+ clk::Clk,
+ device::{Bound, Core, Device},
+ devres,
+ io::mem::IoMem,
+ of, platform,
+ prelude::*,
+ pwm, time,
+};
+
+const TH1520_MAX_PWM_NUM: u32 = 6;
+
+// Register offsets
+const fn th1520_pwm_chn_base(n: u32) -> usize {
+ (n * 0x20) as usize
+}
+
+const fn th1520_pwm_ctrl(n: u32) -> usize {
+ th1520_pwm_chn_base(n)
+}
+
+const fn th1520_pwm_per(n: u32) -> usize {
+ th1520_pwm_chn_base(n) + 0x08
+}
+
+const fn th1520_pwm_fp(n: u32) -> usize {
+ th1520_pwm_chn_base(n) + 0x0c
+}
+
+// Control register bits
+const TH1520_PWM_START: u32 = 1 << 0;
+const TH1520_PWM_CFG_UPDATE: u32 = 1 << 2;
+const TH1520_PWM_CONTINUOUS_MODE: u32 = 1 << 5;
+const TH1520_PWM_FPOUT: u32 = 1 << 8;
+
+const TH1520_PWM_REG_SIZE: usize = 0xB0;
+
+fn ns_to_cycles(ns: u64, rate_hz: u64) -> u64 {
+ const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64;
+
+ (match ns.checked_mul(rate_hz) {
+ Some(product) => product,
+ None => u64::MAX,
+ }) / NSEC_PER_SEC_U64
+}
+
+fn cycles_to_ns(cycles: u64, rate_hz: u64) -> u64 {
+ const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64;
+
+ // TODO: Replace with a kernel helper like `mul_u64_u64_div_u64_roundup`
+ // once available in Rust.
+ let numerator = cycles
+ .saturating_mul(NSEC_PER_SEC_U64)
+ .saturating_add(rate_hz - 1);
+
+ numerator / rate_hz
+}
+
+/// Hardware-specific waveform representation for TH1520.
+#[derive(Copy, Clone, Debug, Default)]
+struct Th1520WfHw {
+ period_cycles: u32,
+ duty_cycles: u32,
+ ctrl_val: u32,
+ enabled: bool,
+}
+
+/// The driver's private data struct. It holds all necessary devres managed resources.
+#[pin_data(PinnedDrop)]
+struct Th1520PwmDriverData {
+ #[pin]
+ iomem: devres::Devres<IoMem<TH1520_PWM_REG_SIZE>>,
+ clk: Clk,
+}
+
+// This `unsafe` implementation is a temporary necessity because the underlying `kernel::clk::Clk`
+// type does not yet expose `Send` and `Sync` implementations. This block should be removed
+// as soon as the clock abstraction provides these guarantees directly.
+// TODO: Remove those unsafe impl's when Clk will support them itself.
+
+// SAFETY: The `devres` framework requires the driver's private data to be `Send` and `Sync`.
+// We can guarantee this because the PWM core synchronizes all callbacks, preventing concurrent
+// access to the contained `iomem` and `clk` resources.
+unsafe impl Send for Th1520PwmDriverData {}
+
+// SAFETY: The same reasoning applies as for `Send`. The PWM core's synchronization
+// guarantees that it is safe for multiple threads to have shared access (`&self`)
+// to the driver data during callbacks.
+unsafe impl Sync for Th1520PwmDriverData {}
+
+impl pwm::PwmOps for Th1520PwmDriverData {
+ type WfHw = Th1520WfHw;
+
+ fn round_waveform_tohw(
+ chip: &pwm::Chip<Self>,
+ _pwm: &pwm::Device,
+ wf: &pwm::Waveform,
+ ) -> Result<pwm::RoundedWaveform<Self::WfHw>> {
+ let data = chip.drvdata();
+ let mut status = 0;
+
+ if wf.period_length_ns == 0 {
+ dev_dbg!(chip.device(), "Requested period is 0, disabling PWM.\n");
+
+ return Ok(pwm::RoundedWaveform {
+ status: 0,
+ hardware_waveform: Th1520WfHw {
+ enabled: false,
+ ..Default::default()
+ },
+ });
+ }
+
+ let rate_hz = data.clk.rate().as_hz() as u64;
+
+ let mut period_cycles = ns_to_cycles(wf.period_length_ns, rate_hz).min(u64::from(u32::MAX));
+
+ if period_cycles == 0 {
+ dev_dbg!(
+ chip.device(),
+ "Requested period {} ns is too small for clock rate {} Hz, rounding up.\n",
+ wf.period_length_ns,
+ rate_hz
+ );
+
+ period_cycles = 1;
+ status = 1;
+ }
+
+ let mut duty_cycles = ns_to_cycles(wf.duty_length_ns, rate_hz).min(u64::from(u32::MAX));
+
+ let mut ctrl_val = TH1520_PWM_CONTINUOUS_MODE;
+
+ let is_inversed = wf.duty_length_ns > 0
+ && wf.duty_offset_ns > 0
+ && wf.duty_offset_ns >= wf.period_length_ns.saturating_sub(wf.duty_length_ns);
+ if is_inversed {
+ duty_cycles = period_cycles - duty_cycles;
+ } else {
+ ctrl_val |= TH1520_PWM_FPOUT;
+ }
+
+ let wfhw = Th1520WfHw {
+ // The cast is safe because the value was clamped with `.min(u64::from(u32::MAX))`.
+ period_cycles: period_cycles as u32,
+ duty_cycles: duty_cycles as u32,
+ ctrl_val,
+ enabled: true,
+ };
+
+ dev_dbg!(
+ chip.device(),
+ "Requested: {}/{} ns [+{} ns] -> HW: {}/{} cycles, ctrl 0x{:x}, rate {} Hz\n",
+ wf.duty_length_ns,
+ wf.period_length_ns,
+ wf.duty_offset_ns,
+ wfhw.duty_cycles,
+ wfhw.period_cycles,
+ wfhw.ctrl_val,
+ rate_hz
+ );
+
+ Ok(pwm::RoundedWaveform {
+ status,
+ hardware_waveform: wfhw,
+ })
+ }
+
+ fn round_waveform_fromhw(
+ chip: &pwm::Chip<Self>,
+ _pwm: &pwm::Device,
+ wfhw: &Self::WfHw,
+ wf: &mut pwm::Waveform,
+ ) -> Result {
+ let data = chip.drvdata();
+ let rate_hz = data.clk.rate().as_hz() as u64;
+
+ if wfhw.period_cycles == 0 {
+ dev_dbg!(
+ chip.device(),
+ "HW state has zero period, reporting as disabled.\n"
+ );
+ *wf = pwm::Waveform::default();
+ return Ok(());
+ }
+
+ wf.period_length_ns = cycles_to_ns(u64::from(wfhw.period_cycles), rate_hz);
+
+ let duty_cycles = u64::from(wfhw.duty_cycles);
+
+ if (wfhw.ctrl_val & TH1520_PWM_FPOUT) != 0 {
+ wf.duty_length_ns = cycles_to_ns(duty_cycles, rate_hz);
+ wf.duty_offset_ns = 0;
+ } else {
+ let period_cycles = u64::from(wfhw.period_cycles);
+ let original_duty_cycles = period_cycles.saturating_sub(duty_cycles);
+
+ // For an inverted signal, `duty_length_ns` is the high time (period - low_time).
+ wf.duty_length_ns = cycles_to_ns(original_duty_cycles, rate_hz);
+ // The offset is the initial low time, which is what the hardware register provides.
+ wf.duty_offset_ns = cycles_to_ns(duty_cycles, rate_hz);
+ }
+
+ Ok(())
+ }
+
+ fn read_waveform(
+ chip: &pwm::Chip<Self>,
+ pwm: &pwm::Device,
+ parent_dev: &Device<Bound>,
+ ) -> Result<Self::WfHw> {
+ let data = chip.drvdata();
+ let hwpwm = pwm.hwpwm();
+ let iomem_accessor = data.iomem.access(parent_dev)?;
+ let iomap = iomem_accessor.deref();
+
+ let ctrl = iomap.try_read32(th1520_pwm_ctrl(hwpwm))?;
+ let period_cycles = iomap.try_read32(th1520_pwm_per(hwpwm))?;
+ let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?;
+
+ let wfhw = Th1520WfHw {
+ period_cycles,
+ duty_cycles,
+ ctrl_val: ctrl,
+ enabled: duty_cycles != 0,
+ };
+
+ dev_dbg!(
+ chip.device(),
+ "PWM-{}: read_waveform: Read hw state - period: {}, duty: {}, ctrl: 0x{:x}, enabled: {}",
+ hwpwm,
+ wfhw.period_cycles,
+ wfhw.duty_cycles,
+ wfhw.ctrl_val,
+ wfhw.enabled
+ );
+
+ Ok(wfhw)
+ }
+
+ fn write_waveform(
+ chip: &pwm::Chip<Self>,
+ pwm: &pwm::Device,
+ wfhw: &Self::WfHw,
+ parent_dev: &Device<Bound>,
+ ) -> Result {
+ let data = chip.drvdata();
+ let hwpwm = pwm.hwpwm();
+ let iomem_accessor = data.iomem.access(parent_dev)?;
+ let iomap = iomem_accessor.deref();
+ let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?;
+ let was_enabled = duty_cycles != 0;
+
+ if !wfhw.enabled {
+ dev_dbg!(chip.device(), "PWM-{}: Disabling channel.\n", hwpwm);
+ if was_enabled {
+ iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?;
+ iomap.try_write32(0, th1520_pwm_fp(hwpwm))?;
+ iomap.try_write32(
+ wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE,
+ th1520_pwm_ctrl(hwpwm),
+ )?;
+ }
+ return Ok(());
+ }
+
+ iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?;
+ iomap.try_write32(wfhw.period_cycles, th1520_pwm_per(hwpwm))?;
+ iomap.try_write32(wfhw.duty_cycles, th1520_pwm_fp(hwpwm))?;
+ iomap.try_write32(
+ wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE,
+ th1520_pwm_ctrl(hwpwm),
+ )?;
+
+ // The `TH1520_PWM_START` bit must be written in a separate, final transaction, and
+ // only when enabling the channel from a disabled state.
+ if !was_enabled {
+ iomap.try_write32(wfhw.ctrl_val | TH1520_PWM_START, th1520_pwm_ctrl(hwpwm))?;
+ }
+
+ dev_dbg!(
+ chip.device(),
+ "PWM-{}: Wrote {}/{} cycles",
+ hwpwm,
+ wfhw.duty_cycles,
+ wfhw.period_cycles,
+ );
+
+ Ok(())
+ }
+}
+
+#[pinned_drop]
+impl PinnedDrop for Th1520PwmDriverData {
+ fn drop(self: Pin<&mut Self>) {
+ self.clk.disable_unprepare();
+ }
+}
+
+struct Th1520PwmPlatformDriver;
+
+kernel::of_device_table!(
+ OF_TABLE,
+ MODULE_OF_TABLE,
+ <Th1520PwmPlatformDriver as platform::Driver>::IdInfo,
+ [(of::DeviceId::new(c_str!("thead,th1520-pwm")), ())]
+);
+
+impl platform::Driver for Th1520PwmPlatformDriver {
+ type IdInfo = ();
+ const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+
+ fn probe(
+ pdev: &platform::Device<Core>,
+ _id_info: Option<&Self::IdInfo>,
+ ) -> impl PinInit<Self, Error> {
+ let dev = pdev.as_ref();
+ let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
+
+ let clk = Clk::get(dev, None)?;
+
+ clk.prepare_enable()?;
+
+ // TODO: Get exclusive ownership of the clock to prevent rate changes.
+ // The Rust equivalent of `clk_rate_exclusive_get()` is not yet available.
+ // This should be updated once it is implemented.
+ let rate_hz = clk.rate().as_hz();
+ if rate_hz == 0 {
+ dev_err!(dev, "Clock rate is zero\n");
+ return Err(EINVAL);
+ }
+
+ if rate_hz > time::NSEC_PER_SEC as usize {
+ dev_err!(
+ dev,
+ "Clock rate {} Hz is too high, not supported.\n",
+ rate_hz
+ );
+ return Err(EINVAL);
+ }
+
+ let chip = pwm::Chip::new(
+ dev,
+ TH1520_MAX_PWM_NUM,
+ try_pin_init!(Th1520PwmDriverData {
+ iomem <- request.iomap_sized::<TH1520_PWM_REG_SIZE>(),
+ clk <- clk,
+ }),
+ )?;
+
+ pwm::Registration::register(dev, chip)?;
+
+ Ok(Th1520PwmPlatformDriver)
+ }
+}
+
+kernel::module_pwm_platform_driver! {
+ type: Th1520PwmPlatformDriver,
+ name: "pwm-th1520",
+ authors: ["Michal Wilczynski <m.wilczynski@samsung.com>"],
+ description: "T-HEAD TH1520 PWM driver",
+ license: "GPL v2",
+}
diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c
deleted file mode 100644
index 2389b8669846..000000000000
--- a/drivers/pwm/sysfs.c
+++ /dev/null
@@ -1,529 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * A simple sysfs interface for the generic PWM framework
- *
- * Copyright (C) 2013 H Hartley Sweeten <hsweeten@visionengravers.com>
- *
- * Based on previous work by Lars Poeschel <poeschel@lemonage.de>
- */
-
-#include <linux/device.h>
-#include <linux/mutex.h>
-#include <linux/err.h>
-#include <linux/slab.h>
-#include <linux/kdev_t.h>
-#include <linux/pwm.h>
-
-struct pwm_export {
- struct device child;
- struct pwm_device *pwm;
- struct mutex lock;
- struct pwm_state suspend;
-};
-
-static struct pwm_export *child_to_pwm_export(struct device *child)
-{
- return container_of(child, struct pwm_export, child);
-}
-
-static struct pwm_device *child_to_pwm_device(struct device *child)
-{
- struct pwm_export *export = child_to_pwm_export(child);
-
- return export->pwm;
-}
-
-static ssize_t period_show(struct device *child,
- struct device_attribute *attr,
- char *buf)
-{
- const struct pwm_device *pwm = child_to_pwm_device(child);
- struct pwm_state state;
-
- pwm_get_state(pwm, &state);
-
- return sprintf(buf, "%u\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;
- unsigned int val;
- int ret;
-
- ret = kstrtouint(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, "%u\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;
- unsigned int val;
- int ret;
-
- ret = kstrtouint(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;
-
- 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;
-
- export->suspend = state;
- 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);