From nobody Sun Oct 5 03:36:52 2025 Received: from relay2-d.mail.gandi.net (relay2-d.mail.gandi.net [217.70.183.194]) (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 28ED42580D1; Mon, 11 Aug 2025 10:47:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.70.183.194 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1754909227; cv=none; b=hocpi7GxU0Ix0xUN6mjkmMDgsmeOfOmaDxOK5leBBG9bLa7xcxw8kUsYWlRbdbQY6nILRaO4j9hrIjov85RQm4B4DdTy7xnwk64ORoNGB5hgUzjXXG9UAeiNMshSy7Q4nQAEVIgF6MKFeBrmsx7456ry2XkaT9igm7Xr6kytWts= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1754909227; c=relaxed/simple; bh=m9GbVzpzMwU3kZF9RqH0tnfIkcNkSVMmghcWHCbaWb0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=G4Eb/N0Ehi4ugLaI2mrepVLARyXO1eGCNHkDBgiPW1r+TuLyILhCctBZAtfWtTvJ3Cu36SHY7Ida/PFYHSUNYNeZEQsRKBpbgGTgdYyeIeExe+ZgLgPvOQgWY/HLuc43Npjd/lDBdLiiyEE8sIkb/7nOILWf5ypGUTK12w/HXJ4= 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=RnMKVMjM; arc=none smtp.client-ip=217.70.183.194 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="RnMKVMjM" Received: by mail.gandi.net (Postfix) with ESMTPSA id 725B14389D; Mon, 11 Aug 2025 10:46:54 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1754909215; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=BhF0rjoQDVNfMi55bWiCri+2Ue8pJhFBkEZi8tBqJcs=; b=RnMKVMjM6vs6S0SsWF688FdSfRNVigCsQA1Sgcis9PAepfrxYKkA5Wh/jETEW/ikmLd3Jx PvN15bwwY+6wbeHWycKevFN0BrN0KPX8UZkIWTHwg+G/rwjLBlxWyHvph9lpxDp4MfFwfN WFXvj9T847GkxhS2SLEcj9pLPS3KP4rU3y7KUpHi/M8UBj16X8jTsto0NUiU1ToVAPEf+D It0GvfMW6Wk/JOC7ywwVgQcmqbl15VO5sTPvxbf9W7G7vLVWZq5+RhVftGXAolSzGZHkKk 5rN1ZEgRSm7vbRe/+KYEEUw0K5bHZgxocKoUIJM0dbM7fhhYJnzj55maicqylQ== From: Mathieu Dubois-Briand Date: Mon, 11 Aug 2025 12:46:22 +0200 Subject: [PATCH v13 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: <20250811-mdb-max7360-support-v13-4-e79fcabff386@bootlin.com> References: <20250811-mdb-max7360-support-v13-0-e79fcabff386@bootlin.com> In-Reply-To: <20250811-mdb-max7360-support-v13-0-e79fcabff386@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=1754909209; l=8307; i=mathieu.dubois-briand@bootlin.com; s=20241219; h=from:subject:message-id; bh=b+7YKpxbZjewh84DjMAWMlLpDmJpLxHzkw2AL+/nVTI=; b=EM/Kop7a56opJKdSL4qKPmTU1BB1DlqZrtWaNWmTtgItwEDw6Vil/HlX1raQAghc9F98mdx3O IRsm8eyzjMVBReZnL9KXOy1ibeiNMpXkY6cHeuD4ej9LWM5dQhOQFdu X-Developer-Key: i=mathieu.dubois-briand@bootlin.com; a=ed25519; pk=1PVTmzPXfKvDwcPUzG0aqdGoKZJA3b9s+3DqRlm0Lww= X-GND-State: clean X-GND-Score: -100 X-GND-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdefgddufedvvdehucetufdoteggodetrfdotffvucfrrhhofhhilhgvmecuifetpfffkfdpucggtfgfnhhsuhgsshgtrhhisggvnecuuegrihhlohhuthemuceftddunecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenucfjughrpefhfffugggtgffkfhgjvfevofesthejredtredtjeenucfhrhhomhepofgrthhhihgvuhcuffhusghoihhsqdeurhhirghnugcuoehmrghthhhivghurdguuhgsohhishdqsghrihgrnhgusegsohhothhlihhnrdgtohhmqeenucggtffrrghtthgvrhhnpeejleeigfehhfdvkeegueevheeijeettedujeeiteehhfdvgefhgfethfekueekteenucffohhmrghinheprghnrghlohhgrdgtohhmnecukfhppedvrgdtudemtggsudegmeehheeimeejrgdttdemfehftghfmehfsgdtugemuddviedvmedvvgejieenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepihhnvghtpedvrgdtudemtggsudegmeehheeimeejrgdttdemfehftghfmehfsgdtugemuddviedvmedvvgejiedphhgvlhhopegluddvjedrtddruddrudgnpdhmrghilhhfrhhomhepmhgrthhhihgvuhdrughusghoihhsqdgsrhhirghnugessghoohhtlhhinhdrtghomhdpnhgspghrtghpthhtohepvdegpdhrtghpthhtohepuggrkhhrsehkvghrnhgvlhdrohhrghdprhgtphhtthhopegrnhgurhhihidrshhhvghvtghhvghnkhhosehlihhnuhigr dhinhhtvghlrdgtohhmpdhrtghpthhtoheplhhinhhugidqphifmhesvhhgvghrrdhkvghrnhgvlhdrohhrghdprhgtphhtthhopehukhhlvghinhgvkheskhgvrhhnvghlrdhorhhgpdhrtghpthhtoheplhhinhhugidqkhgvrhhnvghlsehvghgvrhdrkhgvrhhnvghlrdhorhhgpdhrtghpthhtoheplhhinhhugidqihhnphhuthesvhhgvghrrdhkvghrnhgvlhdrohhrghdprhgtphhtthhopehlihhnuhhsrdifrghllhgvihhjsehlihhnrghrohdrohhrghdprhgtphhtthhopehgrhgvghhorhihrdgtlhgvmhgvnhhtsegsohhothhlihhnrdgtohhm X-GND-Sasl: mathieu.dubois-briand@bootlin.com 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 --- 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..5a0c10d2320e --- /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 < 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