From nobody Fri Oct 3 23:08:19 2025 Received: from smtpout-04.galae.net (smtpout-04.galae.net [185.171.202.116]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9351E2D9798; Sun, 24 Aug 2025 11:58:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.171.202.116 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756036689; cv=none; b=hn3Dvv7PbzUkP18xoEv2IlE8tBFZU/JUENPXcPoo/liGUoUSzHH+un2Lv6zYevCP4jTQxKKC4y2MkYnnB/0/JZFh+pKAYOHuRG9NM0oVuLP1bI//1zmlA/6U80fxcs1iGZ7WcylvPnT6RUASkn8hKV8+CmUP8CnveyitkFscLJA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756036689; c=relaxed/simple; bh=yfX2nqDHzs8Zv7qivUyLbESJ6tIQA1c+VBAQ9SwwYos=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=aBn2ylQKNZWTisFQ6Lw7gas3YCb7kcEcd8SrKcZQRE7uXci/VpemhJYyNu2RE/eR+lnKJDRDDgTSEn5Q7a9ZvfSbHDejnWJo9LbQkOHYHGI0myWS7DxHf+/iTpU0VDIQjcu/rCHvys1rhmIxfMTzme/cUO4yKDkeDD1oBGl/FWM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=recnOggl; arc=none smtp.client-ip=185.171.202.116 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="recnOggl" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-04.galae.net (Postfix) with ESMTPS id 5BA05C8F1CD; Sun, 24 Aug 2025 11:57:52 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 5FD98605F1; Sun, 24 Aug 2025 11:58:06 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id 49A661C22D34F; Sun, 24 Aug 2025 13:58:00 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1756036685; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=AqCypPpu4JTb6FSA0HOsVT/1shSyU1Eya7lVir3yB4k=; b=recnOggl+r2RwdqXyPuFHTEz4T1JBZvnVR4GWXJQB2uBMsTn5XKHZjC6Au8SVCJiYWFPGF IhSpxhy2CsHpYim6IuixaEKVzx1vhnMZhGCT9d6tR1jGXlfUmnO+qviX6bkGvJjDi3hPEQ 9iIQEj7Hx0tT7U3kOH4Oc8HalbEkCMlhCbrpO4aBMs0gdKSmbiY4MYFH4JlvzV8U735RJh mIfDEG0+0UBg/m8kdjfwX9z+3jRnulr3m3vfgHjVloN8X9kTtC3cQ6FACA+42/zqSPLElo 546WKMSXGftcyY2NtbWJhBnQ8NXqj7ak3nEObwVQ/+mvtJwq2+ba46hKA9TUtg== From: Mathieu Dubois-Briand Date: Sun, 24 Aug 2025 13:57:23 +0200 Subject: [PATCH v14 04/10] pwm: max7360: Add MAX7360 PWM support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250824-mdb-max7360-support-v14-4-435cfda2b1ea@bootlin.com> References: <20250824-mdb-max7360-support-v14-0-435cfda2b1ea@bootlin.com> In-Reply-To: <20250824-mdb-max7360-support-v14-0-435cfda2b1ea@bootlin.com> To: Lee Jones , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Kamel Bouhara , Linus Walleij , Bartosz Golaszewski , Dmitry Torokhov , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Michael Walle , Mark Brown , Greg Kroah-Hartman , "Rafael J. Wysocki" , Danilo Krummrich Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org, linux-input@vger.kernel.org, linux-pwm@vger.kernel.org, andriy.shevchenko@intel.com, =?utf-8?q?Gr=C3=A9gory_Clement?= , Thomas Petazzoni , Mathieu Dubois-Briand , Andy Shevchenko X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1756036647; l=8331; i=mathieu.dubois-briand@bootlin.com; s=20241219; h=from:subject:message-id; bh=yeG0/YJBesYO+9rEAropl+nAbbjvgKdFyoo9/b0IpFA=; b=JU9kh+M/Ugj6renE9fknqqutSVNqOjSCmj9Oh7th00ALeYv9QLmRRx9/YCnkV04GjsWTGQwJn P31IZsT72djB54QzE2F56WKI1VuR4r8SxLOvDVHqTqRDKO7dm5OTiVd X-Developer-Key: i=mathieu.dubois-briand@bootlin.com; a=ed25519; pk=1PVTmzPXfKvDwcPUzG0aqdGoKZJA3b9s+3DqRlm0Lww= X-Last-TLS-Session-Version: TLSv1.3 From: Kamel Bouhara Add driver for Maxim Integrated MAX7360 PWM controller, supporting up to 8 independent PWM outputs. Signed-off-by: Kamel Bouhara Co-developed-by: Mathieu Dubois-Briand Signed-off-by: Mathieu Dubois-Briand Reviewed-by: Andy Shevchenko Acked-by: Uwe Kleine-K=C3=B6nig --- drivers/pwm/Kconfig | 10 +++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-max7360.c | 209 ++++++++++++++++++++++++++++++++++++++++++= ++++ 3 files changed, 220 insertions(+) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index f00ce973dddf..f2b1ce47de7f 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -432,6 +432,16 @@ config PWM_LPSS_PLATFORM To compile this driver as a module, choose M here: the module will be called pwm-lpss-platform. =20 +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) +=3D pwm-lpc32xx.o obj-$(CONFIG_PWM_LPSS) +=3D pwm-lpss.o obj-$(CONFIG_PWM_LPSS_PCI) +=3D pwm-lpss-pci.o obj-$(CONFIG_PWM_LPSS_PLATFORM) +=3D pwm-lpss-platform.o +obj-$(CONFIG_PWM_MAX7360) +=3D pwm-max7360.o obj-$(CONFIG_PWM_MC33XS2410) +=3D pwm-mc33xs2410.o obj-$(CONFIG_PWM_MEDIATEK) +=3D pwm-mediatek.o obj-$(CONFIG_PWM_MESON) +=3D 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 + * Author: Mathieu Dubois-Briand + * + * PWM functionality of the MAX7360 multi-function device. + * https://www.analog.com/media/en/technical-documentation/data-sheets/MAX= 7360.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 beg= inning + * of the next cycle. + * - When disabled, the output is put in Hi-Z immediately. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 *p= wm) +{ + struct regmap *regmap =3D 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 =3D _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 >=3D MAX7360_PWM_PERIOD_NS) { + duty_steps =3D MAX7360_PWM_MAX; + } else { + duty_steps =3D (u32)wf->duty_length_ns * MAX7360_PWM_STEPS / MAX7360_PWM= _PERIOD_NS; + if (duty_steps =3D=3D MAX7360_PWM_MAX) + duty_steps =3D MAX7360_PWM_MAX - 1; + } + + wfhw->duty_steps =3D min(MAX7360_PWM_MAX, duty_steps); + wfhw->enabled =3D !!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 =3D _wfhw; + + wf->period_length_ns =3D wfhw->enabled ? MAX7360_PWM_PERIOD_NS : 0; + wf->duty_offset_ns =3D 0; + + if (wfhw->enabled) { + if (wfhw->duty_steps =3D=3D MAX7360_PWM_MAX) + wf->duty_length_ns =3D MAX7360_PWM_PERIOD_NS; + else + wf->duty_length_ns =3D DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERI= OD_NS, + MAX7360_PWM_STEPS); + } else { + wf->duty_length_ns =3D 0; + } + + return 0; +} + +static int max7360_pwm_write_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw) +{ + struct regmap *regmap =3D pwmchip_get_drvdata(chip); + const struct max7360_pwm_waveform *wfhw =3D _wfhw; + unsigned int val; + int ret; + + if (wfhw->enabled) { + ret =3D regmap_write(regmap, MAX7360_REG_PWM(pwm->hwpwm), wfhw->duty_ste= ps); + if (ret) + return ret; + } + + val =3D wfhw->enabled ? BIT(pwm->hwpwm) : 0; + return regmap_write_bits(regmap, MAX7360_REG_GPIOCTRL, BIT(pwm->hwpwm), v= al); +} + +static int max7360_pwm_read_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + void *_wfhw) +{ + struct regmap *regmap =3D pwmchip_get_drvdata(chip); + struct max7360_pwm_waveform *wfhw =3D _wfhw; + unsigned int val; + int ret; + + ret =3D regmap_read(regmap, MAX7360_REG_GPIOCTRL, &val); + if (ret) + return ret; + + if (val & BIT(pwm->hwpwm)) { + wfhw->enabled =3D true; + ret =3D regmap_read(regmap, MAX7360_REG_PWM(pwm->hwpwm), &val); + if (ret) + return ret; + + wfhw->duty_steps =3D val; + } else { + wfhw->enabled =3D false; + wfhw->duty_steps =3D 0; + } + + return 0; +} + +static const struct pwm_ops max7360_pwm_ops =3D { + .request =3D max7360_pwm_request, + .round_waveform_tohw =3D max7360_pwm_round_waveform_tohw, + .round_waveform_fromhw =3D max7360_pwm_round_waveform_fromhw, + .read_waveform =3D max7360_pwm_read_waveform, + .write_waveform =3D max7360_pwm_write_waveform, +}; + +static int max7360_pwm_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct pwm_chip *chip; + struct regmap *regmap; + int ret; + + regmap =3D 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 =3D devm_pwmchip_alloc(dev, MAX7360_NUM_PWMS, 0); + if (IS_ERR(chip)) + return PTR_ERR(chip); + chip->ops =3D &max7360_pwm_ops; + + pwmchip_set_drvdata(chip, regmap); + + ret =3D 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 =3D { + .driver =3D { + .name =3D "max7360-pwm", + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + }, + .probe =3D max7360_pwm_probe, +}; +module_platform_driver(max7360_pwm_driver); + +MODULE_DESCRIPTION("MAX7360 PWM driver"); +MODULE_AUTHOR("Kamel BOUHARA "); +MODULE_AUTHOR("Mathieu Dubois-Briand "); +MODULE_LICENSE("GPL"); --=20 2.39.5