diff mbox series

[v8,04/11] pwm: max7360: Add MAX7360 PWM support

Message ID 20250509-mdb-max7360-support-v8-4-bbe486f6bcb7@bootlin.com
State New
Headers show
Series Add support for MAX7360 | expand

Commit Message

Mathieu Dubois-Briand May 9, 2025, 9:14 a.m. UTC
From: Kamel Bouhara <kamel.bouhara@bootlin.com>

Add driver for Maxim Integrated MAX7360 PWM controller, supporting up to
8 independent PWM outputs.

Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
Co-developed-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
 drivers/pwm/Kconfig       |  10 +++
 drivers/pwm/Makefile      |   1 +
 drivers/pwm/pwm-max7360.c | 186 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 197 insertions(+)

Comments

Uwe Kleine-König May 13, 2025, 10:08 a.m. UTC | #1
Hello,

On Fri, May 09, 2025 at 11:14:38AM +0200, mathieu.dubois-briand@bootlin.com wrote:
> From: Kamel Bouhara <kamel.bouhara@bootlin.com>
> 
> Add driver for Maxim Integrated MAX7360 PWM controller, supporting up to
> 8 independent PWM outputs.
> 
> Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
> Co-developed-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
> Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
> ---
>  drivers/pwm/Kconfig       |  10 +++
>  drivers/pwm/Makefile      |   1 +
>  drivers/pwm/pwm-max7360.c | 186 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 197 insertions(+)
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 4731d5b90d7e..0b22141cbf85 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -755,4 +755,14 @@ config PWM_XILINX
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-xilinx.
>  
> +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.
> +
>  endif
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 539e0def3f82..9c7701d8070b 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -36,6 +36,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_MESON)		+= pwm-meson.o
>  obj-$(CONFIG_PWM_MEDIATEK)	+= pwm-mediatek.o
>  obj-$(CONFIG_PWM_MICROCHIP_CORE)	+= pwm-microchip-core.o
> diff --git a/drivers/pwm/pwm-max7360.c b/drivers/pwm/pwm-max7360.c
> new file mode 100644
> index 000000000000..af2006ec7a96
> --- /dev/null
> +++ b/drivers/pwm/pwm-max7360.c
> @@ -0,0 +1,186 @@
> +// 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>
> + *
> + * 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.
> + */
> +#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_RES			255
> +#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);
> +	int ret;
> +
> +	ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(pwm->hwpwm),
> +				MAX7360_PORT_CFG_COMMON_PWM, 0);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_write_bits(regmap, MAX7360_REG_PORTS, BIT(pwm->hwpwm), BIT(pwm->hwpwm));

What is the effect of these writes? It doesn't need to be undone in a
matching .free()?

> +}
> +
> +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.
> +	 */
> +	duty_steps = mul_u64_u64_div_u64(wf->duty_length_ns, MAX7360_PWM_MAX_RES,
> +					 MAX7360_PWM_PERIOD_NS);
> +
> +	wfhw->duty_steps = min(MAX7360_PWM_MAX_RES, duty_steps);
> +	wfhw->enabled = !!wf->duty_length_ns;
> +
> +	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;
> +	wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS,
> +					  MAX7360_PWM_MAX_RES);
> +
> +	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;
> +
> +	val = wfhw->enabled ? BIT(pwm->hwpwm) : 0;
> +	ret = regmap_write_bits(regmap, MAX7360_REG_GPIOCTRL, BIT(pwm->hwpwm), val);
> +	if (ret)
> +		return ret;
> +
> +	if (wfhw->duty_steps)
> +		return regmap_write(regmap, MAX7360_REG_PWM(pwm->hwpwm), wfhw->duty_steps);

Would it make sense to first write duty_steps and only then enable?
Otherwise it might happen that you enable and still have a wrong duty
configuration in the MAX7360_REG_PWM register and emit a wrong period?

Do you need to write duty_steps = 0 if enabled is false?

> +	return 0;
> +}

Best regards
Uwe
Mathieu Dubois-Briand May 15, 2025, 7:14 a.m. UTC | #2
On Tue May 13, 2025 at 12:08 PM CEST, Uwe Kleine-König wrote:
> Hello,
>
> On Fri, May 09, 2025 at 11:14:38AM +0200, mathieu.dubois-briand@bootlin.com wrote:
>> From: Kamel Bouhara <kamel.bouhara@bootlin.com>
>> 
>> Add driver for Maxim Integrated MAX7360 PWM controller, supporting up to
>> 8 independent PWM outputs.
>> 
>> Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
>> Co-developed-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
>> Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
>> ---
>>  drivers/pwm/Kconfig       |  10 +++
>>  drivers/pwm/Makefile      |   1 +
>>  drivers/pwm/pwm-max7360.c | 186 ++++++++++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 197 insertions(+)
>> 
>> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>> index 4731d5b90d7e..0b22141cbf85 100644
>> --- a/drivers/pwm/Kconfig
>> +++ b/drivers/pwm/Kconfig
>> @@ -755,4 +755,14 @@ config PWM_XILINX
>>  	  To compile this driver as a module, choose M here: the module
>>  	  will be called pwm-xilinx.
>>  
>> +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.
>> +
>>  endif
>> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
>> index 539e0def3f82..9c7701d8070b 100644
>> --- a/drivers/pwm/Makefile
>> +++ b/drivers/pwm/Makefile
>> @@ -36,6 +36,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_MESON)		+= pwm-meson.o
>>  obj-$(CONFIG_PWM_MEDIATEK)	+= pwm-mediatek.o
>>  obj-$(CONFIG_PWM_MICROCHIP_CORE)	+= pwm-microchip-core.o
>> diff --git a/drivers/pwm/pwm-max7360.c b/drivers/pwm/pwm-max7360.c
>> new file mode 100644
>> index 000000000000..af2006ec7a96
>> --- /dev/null
>> +++ b/drivers/pwm/pwm-max7360.c
>> @@ -0,0 +1,186 @@
>> +// 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>
>> + *
>> + * 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.
>> + */
>> +#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_RES			255
>> +#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);
>> +	int ret;
>> +
>> +	ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(pwm->hwpwm),
>> +				MAX7360_PORT_CFG_COMMON_PWM, 0);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return regmap_write_bits(regmap, MAX7360_REG_PORTS, BIT(pwm->hwpwm), BIT(pwm->hwpwm));
>
> What is the effect of these writes? It doesn't need to be undone in a
> matching .free()?
>

The first one (MAX7360_PORT_CFG_COMMON_PWM) asks to use a specific duty
cycle for this PWM output and not a value shared across all PWMs. I
believe this one have no reason to be ever reverted.

About the second one, it does switch the output value. Reading the
datasheet, it's not clear if and why setting this here is required. I
will make some tests on the hardware a bit later this week. Still, I
believe there is no need to revert it later.

>> +}
>> +
>> +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.
>> +	 */
>> +	duty_steps = mul_u64_u64_div_u64(wf->duty_length_ns, MAX7360_PWM_MAX_RES,
>> +					 MAX7360_PWM_PERIOD_NS);
>> +
>> +	wfhw->duty_steps = min(MAX7360_PWM_MAX_RES, duty_steps);
>> +	wfhw->enabled = !!wf->duty_length_ns;
>> +
>> +	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;
>> +	wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS,
>> +					  MAX7360_PWM_MAX_RES);
>> +
>> +	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;
>> +
>> +	val = wfhw->enabled ? BIT(pwm->hwpwm) : 0;
>> +	ret = regmap_write_bits(regmap, MAX7360_REG_GPIOCTRL, BIT(pwm->hwpwm), val);
>> +	if (ret)
>> +		return ret;
>> +
>> +	if (wfhw->duty_steps)
>> +		return regmap_write(regmap, MAX7360_REG_PWM(pwm->hwpwm), wfhw->duty_steps);
>
> Would it make sense to first write duty_steps and only then enable?
> Otherwise it might happen that you enable and still have a wrong duty
> configuration in the MAX7360_REG_PWM register and emit a wrong period?
>

Yes, I believe it does make sense: I will try to invert them.

> Do you need to write duty_steps = 0 if enabled is false?
>

No, this is not needed: output will be in hi-Z mode. As we have
"wfhw->enabled = !!wf->duty_length_ns", this should be correct here. But
reading this, I believe I could modify above code to be more clear with:

if (wfhw->enabled)
	return regmap_write(regmap, MAX7360_REG_PWM(pwm->hwpwm), wfhw->duty_steps);


>> +	return 0;
>> +}
>
> Best regards
> Uwe
Mathieu Dubois-Briand May 16, 2025, 12:57 p.m. UTC | #3
On Thu May 15, 2025 at 9:14 AM CEST, Mathieu Dubois-Briand wrote:
> On Tue May 13, 2025 at 12:08 PM CEST, Uwe Kleine-König wrote:
>> Hello,
>>
>> On Fri, May 09, 2025 at 11:14:38AM +0200, mathieu.dubois-briand@bootlin.com wrote:
>>> From: Kamel Bouhara <kamel.bouhara@bootlin.com>
>>> ...
>>>
>>> +
>>> +static int max7360_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
>>> +{
>>> +	struct regmap *regmap = pwmchip_get_drvdata(chip);
>>> +	int ret;
>>> +
>>> +	ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(pwm->hwpwm),
>>> +				MAX7360_PORT_CFG_COMMON_PWM, 0);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	return regmap_write_bits(regmap, MAX7360_REG_PORTS, BIT(pwm->hwpwm), BIT(pwm->hwpwm));
>>
>> What is the effect of these writes? It doesn't need to be undone in a
>> matching .free()?
>>
>
> The first one (MAX7360_PORT_CFG_COMMON_PWM) asks to use a specific duty
> cycle for this PWM output and not a value shared across all PWMs. I
> believe this one have no reason to be ever reverted.
>
> About the second one, it does switch the output value. Reading the
> datasheet, it's not clear if and why setting this here is required. I
> will make some tests on the hardware a bit later this week. Still, I
> believe there is no need to revert it later.
>

I just tested it, I confirm we can remove the second one.

>>> +}
>>>
>>> ...
>>>
>>> +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;
>>> +
>>> +	val = wfhw->enabled ? BIT(pwm->hwpwm) : 0;
>>> +	ret = regmap_write_bits(regmap, MAX7360_REG_GPIOCTRL, BIT(pwm->hwpwm), val);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	if (wfhw->duty_steps)
>>> +		return regmap_write(regmap, MAX7360_REG_PWM(pwm->hwpwm), wfhw->duty_steps);
>>
>> Would it make sense to first write duty_steps and only then enable?
>> Otherwise it might happen that you enable and still have a wrong duty
>> configuration in the MAX7360_REG_PWM register and emit a wrong period?
>>
>
> Yes, I believe it does make sense: I will try to invert them.
>

Also tested, and everything seems to be working fine: I will go this
way.

>> Do you need to write duty_steps = 0 if enabled is false?
>>
>
> No, this is not needed: output will be in hi-Z mode. As we have
> "wfhw->enabled = !!wf->duty_length_ns", this should be correct here. But
> reading this, I believe I could modify above code to be more clear with:
>
> if (wfhw->enabled)
> 	return regmap_write(regmap, MAX7360_REG_PWM(pwm->hwpwm), wfhw->duty_steps);
>
>
>>> +	return 0;
>>> +}
>>
>> Best regards
>> Uwe

Best regards,
Mathieu
diff mbox series

Patch

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 4731d5b90d7e..0b22141cbf85 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -755,4 +755,14 @@  config PWM_XILINX
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-xilinx.
 
+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.
+
 endif
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 539e0def3f82..9c7701d8070b 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -36,6 +36,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_MESON)		+= pwm-meson.o
 obj-$(CONFIG_PWM_MEDIATEK)	+= pwm-mediatek.o
 obj-$(CONFIG_PWM_MICROCHIP_CORE)	+= pwm-microchip-core.o
diff --git a/drivers/pwm/pwm-max7360.c b/drivers/pwm/pwm-max7360.c
new file mode 100644
index 000000000000..af2006ec7a96
--- /dev/null
+++ b/drivers/pwm/pwm-max7360.c
@@ -0,0 +1,186 @@ 
+// 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>
+ *
+ * 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.
+ */
+#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_RES			255
+#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);
+	int ret;
+
+	ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(pwm->hwpwm),
+				MAX7360_PORT_CFG_COMMON_PWM, 0);
+	if (ret)
+		return ret;
+
+	return regmap_write_bits(regmap, MAX7360_REG_PORTS, BIT(pwm->hwpwm), BIT(pwm->hwpwm));
+}
+
+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.
+	 */
+	duty_steps = mul_u64_u64_div_u64(wf->duty_length_ns, MAX7360_PWM_MAX_RES,
+					 MAX7360_PWM_PERIOD_NS);
+
+	wfhw->duty_steps = min(MAX7360_PWM_MAX_RES, duty_steps);
+	wfhw->enabled = !!wf->duty_length_ns;
+
+	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;
+	wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS,
+					  MAX7360_PWM_MAX_RES);
+
+	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;
+
+	val = wfhw->enabled ? BIT(pwm->hwpwm) : 0;
+	ret = regmap_write_bits(regmap, MAX7360_REG_GPIOCTRL, BIT(pwm->hwpwm), val);
+	if (ret)
+		return ret;
+
+	if (wfhw->duty_steps)
+		return regmap_write(regmap, MAX7360_REG_PWM(pwm->hwpwm), wfhw->duty_steps);
+
+	return 0;
+}
+
+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 = 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");