diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2025-10-01 11:34:12 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2025-10-01 11:34:12 -0700 |
| commit | d5f74114114cb2cdbed75b91ca2fa4482c1d5611 (patch) | |
| tree | 9a1332d112b666963a61d08fc28bbfb4bc579ae1 /drivers/pwm | |
| parent | c050daf69f3edf72e274eaa321f663b1779c4391 (diff) | |
| parent | bc061143637532c08d9fc657eec93fdc2588068e (diff) | |
| download | lwn-d5f74114114cb2cdbed75b91ca2fa4482c1d5611.tar.gz lwn-d5f74114114cb2cdbed75b91ca2fa4482c1d5611.zip | |
Merge tag 'gpio-updates-for-v6.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux
Pull gpio updates from Bartosz Golaszewski:
"There are two new drivers and support for more models in existing
ones.
The generic GPIO API has been reworked and all users converted
which allowed us to move the fields specific to the generic GPIO
implementation out of the high-level struct gpio_chip into its own
structure that wraps the gpio_chip.
Other than that, there's nothing too exciting. Mostly minor tweaks and
fixes all over the place, some refactoring and some small new features
in helper modules.
GPIO core:
- add support for sparse pin ranges to the glue between GPIO and
pinctrl
- use a common prefix across all GPIO descriptor flags for improved
namespacing
New drivers:
- add new GPIO driver for the Nuvoton NCT6694
- add new GPIO driver for MAX7360
Driver improvements:
- add support for Tegra 256 to the gpio-tegra186 driver
- add support for Loongson-2K0300 to the gpio-loongson-64bit driver
- refactor the gpio-aggregator module to expose its GPIO forwarder
API to other in-kernel users (to enable merging of a new pinctrl
driver that uses it)
- convert all remaining drivers to using the modernized generic GPIO
chip API and remove the old interface
- stop displaying global GPIO numbers in debugfs output of controller
drivers
- extend the gpio-regmap helper with a new config option and improve
its support for GPIO interrupts
- remove redundant fast_io parameter from regmap configs in GPIO
drivers that already use MMIO regmaps which imply it
- add support for a new model in gpio-mmio: ixp4xx expansion bus
- order includes alphabetically in a few drivers for better
readability
- use generic device properties where applicable
- use devm_mutex_init() where applicable
- extend build coverage of drivers by enabling more to be compiled
with COMPILE_TEST enabled
- allow building gpio-stmpe as a module
- use dev_err_probe() where it makes sense in drivers
Late driver fixes:
- fix setting GPIO direction to output in gpio-mpfs
Documentation:
- document the usage of software nodes with GPIO chips
Device-tree bindings:
- Add DT bindings documents for new hardware: Tegra256, MAX7360
- Document a new model in Loongson bindings: LS2K0300
- Document a new model using the generic GPIO binding: IXP4xx
- Convert the DT binding for fsl,mxs-pinctrl to YAML
- fix the schema ID in the "trivial" GPIO schema
- describe GPIO hogs in the generic GPIO binding"
* tag 'gpio-updates-for-v6.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux: (122 commits)
gpio: mpfs: fix setting gpio direction to output
gpio: generic: move GPIO_GENERIC_ flags to the correct header
gpio: generic: rename BGPIOF_ flags to GPIO_GENERIC_
gpio: nomadik: fix the debugfs helper stub
MAINTAINERS: Add entry on MAX7360 driver
input: misc: Add support for MAX7360 rotary
input: keyboard: Add support for MAX7360 keypad
gpio: max7360: Add MAX7360 gpio support
gpio: regmap: Allow to provide init_valid_mask callback
gpio: regmap: Allow to allocate regmap-irq device
pwm: max7360: Add MAX7360 PWM support
pinctrl: Add MAX7360 pinctrl driver
mfd: Add max7360 support
dt-bindings: mfd: gpio: Add MAX7360
rtc: Add Nuvoton NCT6694 RTC support
hwmon: Add Nuvoton NCT6694 HWMON support
watchdog: Add Nuvoton NCT6694 WDT support
can: Add Nuvoton NCT6694 CANFD support
i2c: Add Nuvoton NCT6694 I2C support
gpio: Add Nuvoton NCT6694 GPIO support
...
Diffstat (limited to 'drivers/pwm')
| -rw-r--r-- | drivers/pwm/Kconfig | 10 | ||||
| -rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
| -rw-r--r-- | drivers/pwm/pwm-max7360.c | 209 |
3 files changed, 220 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 2aa24608a8df..c2fd3f4b62d9 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -441,6 +441,16 @@ 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 diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index ff4f47e5fb7a..dfa8b4966ee1 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -38,6 +38,7 @@ 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_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 diff --git a/drivers/pwm/pwm-max7360.c b/drivers/pwm/pwm-max7360.c new file mode 100644 index 000000000000..ebf93a7aee5b --- /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 = min(MAX7360_PWM_MAX, 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"); |
