Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2225221/?format=api
{ "id": 2225221, "url": "http://patchwork.ozlabs.org/api/1.1/patches/2225221/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-pwm/patch/20260420-rk3576-pwm-v5-3-ae7cfbbe5427@collabora.com/", "project": { "id": 38, "url": "http://patchwork.ozlabs.org/api/1.1/projects/38/?format=api", "name": "Linux PWM development", "link_name": "linux-pwm", "list_id": "linux-pwm.vger.kernel.org", "list_email": "linux-pwm@vger.kernel.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20260420-rk3576-pwm-v5-3-ae7cfbbe5427@collabora.com>", "date": "2026-04-20T13:52:40", "name": "[v5,3/6] pwm: Add rockchip PWMv4 driver", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "baa46cd6bf5cedf753dbe82164e7534d6e490fa1", "submitter": { "id": 90188, "url": "http://patchwork.ozlabs.org/api/1.1/people/90188/?format=api", "name": "Nicolas Frattaroli", "email": "nicolas.frattaroli@collabora.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/linux-pwm/patch/20260420-rk3576-pwm-v5-3-ae7cfbbe5427@collabora.com/mbox/", "series": [ { "id": 500617, "url": "http://patchwork.ozlabs.org/api/1.1/series/500617/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-pwm/list/?series=500617", "date": "2026-04-20T13:52:37", "name": "Add Rockchip RK3576 PWM Support Through MFPWM", "version": 5, "mbox": "http://patchwork.ozlabs.org/series/500617/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2225221/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2225221/checks/", "tags": {}, "headers": { "Return-Path": "\n <linux-pwm+bounces-8655-incoming=patchwork.ozlabs.org@vger.kernel.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "linux-pwm@vger.kernel.org" ], "Delivered-To": "patchwork-incoming@legolas.ozlabs.org", "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (1024-bit key;\n unprotected) header.d=collabora.com header.i=nicolas.frattaroli@collabora.com\n header.a=rsa-sha256 header.s=zohomail header.b=e/Yd7e2o;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=2600:3c0a:e001:db::12fc:5321; helo=sea.lore.kernel.org;\n envelope-from=linux-pwm+bounces-8655-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)", "smtp.subspace.kernel.org;\n\tdkim=pass (1024-bit key) header.d=collabora.com\n header.i=nicolas.frattaroli@collabora.com header.b=\"e/Yd7e2o\"", "smtp.subspace.kernel.org;\n arc=pass smtp.client-ip=136.143.188.112", "smtp.subspace.kernel.org;\n dmarc=pass (p=none dis=none) header.from=collabora.com", "smtp.subspace.kernel.org;\n spf=pass smtp.mailfrom=collabora.com" ], "Received": [ "from sea.lore.kernel.org (sea.lore.kernel.org\n [IPv6:2600:3c0a:e001:db::12fc:5321])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fzrfw4454z1yCv\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 21 Apr 2026 02:34:32 +1000 (AEST)", "from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sea.lore.kernel.org (Postfix) with ESMTP id B6A3132BACD9\n\tfor <incoming@patchwork.ozlabs.org>; Mon, 20 Apr 2026 14:47:32 +0000 (UTC)", "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id 8F10431328C;\n\tMon, 20 Apr 2026 13:54:11 +0000 (UTC)", "from sender4-pp-f112.zoho.com (sender4-pp-f112.zoho.com\n [136.143.188.112])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id B180B30DD2F;\n\tMon, 20 Apr 2026 13:54:09 +0000 (UTC)", "by mx.zohomail.com with SMTPS id 1776693218378701.7352404504622;\n\tMon, 20 Apr 2026 06:53:38 -0700 (PDT)" ], "ARC-Seal": [ "i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1776693251; cv=pass;\n b=tth8qwNBkK0sBr80vg5qZ84v+CAAx3zOLoxFky4DA/a/AGnifoVoKM6uqp3obCU9uhSWunWd20fJvqIgIjYgaebpC/nppoapFRkvleeER3h1fN0PQr/BWyTtMRPuT1p5T//FWNQv91FAcMQriKi9XImc24v3ukGz9baTn2jPh1M=", "i=1; a=rsa-sha256; t=1776693220; cv=none;\n\td=zohomail.com; s=zohoarc;\n\tb=LpMBmZHy8W7rMaKXoS+qhGAs9ptGQaH0+NqcWWPXG+YG6T87Y2pWuGRByB9hz4ptManMMKb1z4CcjVZGNxN6NYTf4uXJAbykWqRyTfVt5u07Owv1Da99YiJtIqMWJEkJ472+5y4MrVt6Q7Azi3jVhzIOFpvaFPcxNY53XIc/0sE=" ], "ARC-Message-Signature": [ "i=2; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1776693251; c=relaxed/simple;\n\tbh=rQBjcUPKvPzXjM/u933YXowHoPlpJ8OaoPBEAugMiQ8=;\n\th=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References:\n\t In-Reply-To:To:Cc;\n b=YIXH70+ZrwCaVhpQafXaxfhLq/S40c6B0podsWrDtkengWVi+tU6HnyvQqvr0T83dnp3iMdyn8se+2fL/gwYMAsDI5l5AuEWlhylSO9ied6LdXJ+N1R0v7ZtQPPx/YUhRagCtXmgZf1PUCgPhH0735EzqODiKvd52TiOdT98csk=", "i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com;\n s=zohoarc;\n\tt=1776693220;\n h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To;\n\tbh=/x+Y3/AXdJlKE2kqXpOORKAaRXVmW9sL1jGefOVrQx0=;\n\tb=bM9Vw0NG8vevmRQnfP71Vww8cOnVVpZfj8RRmBpDsq2tafMA2zHQPZMCB5HqzWH661RDbRReTsPiGgNvO4C2chn/fql0Yj6sfxUmj1d+PggiHGK+tQs2VH9z2YLL9OiN7m22hMHx0n7EV4uAU3uPjxzxbCeSUVuNXHHdlOUvpPU=" ], "ARC-Authentication-Results": [ "i=2; smtp.subspace.kernel.org;\n dmarc=pass (p=none dis=none) header.from=collabora.com;\n spf=pass smtp.mailfrom=collabora.com;\n dkim=pass (1024-bit key) header.d=collabora.com\n header.i=nicolas.frattaroli@collabora.com header.b=e/Yd7e2o;\n arc=pass smtp.client-ip=136.143.188.112", "i=1; mx.zohomail.com;\n\tdkim=pass header.i=collabora.com;\n\tspf=pass smtp.mailfrom=nicolas.frattaroli@collabora.com;\n\tdmarc=pass header.from=<nicolas.frattaroli@collabora.com>" ], "DKIM-Signature": "v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1776693220;\n\ts=zohomail; d=collabora.com; i=nicolas.frattaroli@collabora.com;\n\th=From:From:Date:Date:Subject:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Message-Id:Message-Id:References:In-Reply-To:To:To:Cc:Cc:Reply-To;\n\tbh=/x+Y3/AXdJlKE2kqXpOORKAaRXVmW9sL1jGefOVrQx0=;\n\tb=e/Yd7e2o6VuCxLxY/UVXuQewVJjHpu0HnhF81zFoACz/DufQ14ifSZzXvDPFH4TT\n\tzZvgI8GCBdQG+wYcWETKvEFfJrFPNJRdEltyVR2bQ+uRlrTNlWT4VwvR7SclJCAc2L1\n\t4S0ZWk0Ec3rqCWHhKtseKEiUn/ljXScbkvs5YiXM=", "From": "Nicolas Frattaroli <nicolas.frattaroli@collabora.com>", "Date": "Mon, 20 Apr 2026 15:52:40 +0200", "Subject": "[PATCH v5 3/6] pwm: Add rockchip PWMv4 driver", "Precedence": "bulk", "X-Mailing-List": "linux-pwm@vger.kernel.org", "List-Id": "<linux-pwm.vger.kernel.org>", "List-Subscribe": "<mailto:linux-pwm+subscribe@vger.kernel.org>", "List-Unsubscribe": "<mailto:linux-pwm+unsubscribe@vger.kernel.org>", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=\"utf-8\"", "Content-Transfer-Encoding": "7bit", "Message-Id": "<20260420-rk3576-pwm-v5-3-ae7cfbbe5427@collabora.com>", "References": "<20260420-rk3576-pwm-v5-0-ae7cfbbe5427@collabora.com>", "In-Reply-To": "<20260420-rk3576-pwm-v5-0-ae7cfbbe5427@collabora.com>", "To": "=?utf-8?q?Uwe_Kleine-K=C3=B6nig?= <ukleinek@kernel.org>,\n Rob Herring <robh@kernel.org>, Krzysztof Kozlowski <krzk+dt@kernel.org>,\n Conor Dooley <conor+dt@kernel.org>, Heiko Stuebner <heiko@sntech.de>,\n Lee Jones <lee@kernel.org>, William Breathitt Gray <wbg@kernel.org>,\n Damon Ding <damon.ding@rock-chips.com>", "Cc": "Nicolas Frattaroli <nicolas.frattaroli@collabora.com>,\n kernel@collabora.com, Jonas Karlman <jonas@kwiboo.se>,\n Alexey Charkov <alchark@gmail.com>, linux-rockchip@lists.infradead.org,\n linux-pwm@vger.kernel.org, devicetree@vger.kernel.org,\n linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org,\n linux-iio@vger.kernel.org", "X-Mailer": "b4 0.15.2" }, "content": "The Rockchip RK3576 brings with it a new PWM IP, in downstream code\nreferred to as \"v4\". This new IP is different enough from the previous\nRockchip IP that I felt it necessary to add a new driver for it, instead\nof shoehorning it in the old one.\n\nAdd this new driver, based on the PWM core's waveform APIs. Its platform\ndevice is registered by the parent mfpwm driver, from which it also\nreceives a little platform data struct, so that mfpwm can guarantee that\nall the platform device drivers spread across different subsystems for\nthis specific hardware IP do not interfere with each other.\n\nSigned-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>\n---\n MAINTAINERS | 1 +\n drivers/pwm/Kconfig | 11 ++\n drivers/pwm/Makefile | 1 +\n drivers/pwm/pwm-rockchip-v4.c | 383 ++++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 396 insertions(+)", "diff": "diff --git a/MAINTAINERS b/MAINTAINERS\nindex d52731242a33..68bb9ee07a47 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -23179,6 +23179,7 @@ L:\tlinux-pwm@vger.kernel.org\n S:\tMaintained\n F:\tDocumentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml\n F:\tdrivers/mfd/rockchip-mfpwm.c\n+F:\tdrivers/pwm/pwm-rockchip-v4.c\n F:\tinclude/linux/mfd/rockchip-mfpwm.h\n \n ROCKCHIP RK3568 RANDOM NUMBER GENERATOR SUPPORT\ndiff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig\nindex 6f3147518376..3fe7993bf12b 100644\n--- a/drivers/pwm/Kconfig\n+++ b/drivers/pwm/Kconfig\n@@ -625,6 +625,17 @@ config PWM_ROCKCHIP\n \t Generic PWM framework driver for the PWM controller found on\n \t Rockchip SoCs.\n \n+config PWM_ROCKCHIP_V4\n+\ttristate \"Rockchip PWM v4 support\"\n+\tdepends on MFD_ROCKCHIP_MFPWM\n+\thelp\n+\t Generic PWM framework driver for the PWM controller found on\n+\t later Rockchip SoCs such as the RK3576.\n+\n+\t Uses the Rockchip Multi-function PWM controller driver infrastructure\n+\t to guarantee fearlessly concurrent operation with other functions of\n+\t the same device implemented by drivers in other subsystems.\n+\n config PWM_SAMSUNG\n \ttristate \"Samsung PWM support\"\n \tdepends on PLAT_SAMSUNG || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST\ndiff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile\nindex 0dc0d2b69025..a234027dbbc6 100644\n--- a/drivers/pwm/Makefile\n+++ b/drivers/pwm/Makefile\n@@ -56,6 +56,7 @@ obj-$(CONFIG_PWM_RENESAS_RZG2L_GPT)\t+= pwm-rzg2l-gpt.o\n obj-$(CONFIG_PWM_RENESAS_RZ_MTU3)\t+= pwm-rz-mtu3.o\n obj-$(CONFIG_PWM_RENESAS_TPU)\t+= pwm-renesas-tpu.o\n obj-$(CONFIG_PWM_ROCKCHIP)\t+= pwm-rockchip.o\n+obj-$(CONFIG_PWM_ROCKCHIP_V4)\t+= pwm-rockchip-v4.o\n obj-$(CONFIG_PWM_SAMSUNG)\t+= pwm-samsung.o\n obj-$(CONFIG_PWM_SIFIVE)\t+= pwm-sifive.o\n obj-$(CONFIG_PWM_SL28CPLD)\t+= pwm-sl28cpld.o\ndiff --git a/drivers/pwm/pwm-rockchip-v4.c b/drivers/pwm/pwm-rockchip-v4.c\nnew file mode 100644\nindex 000000000000..b7de72c433c5\n--- /dev/null\n+++ b/drivers/pwm/pwm-rockchip-v4.c\n@@ -0,0 +1,383 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/*\n+ * Copyright (c) 2025 Collabora Ltd.\n+ *\n+ * A Pulse-Width-Modulation (PWM) generator driver for the generators found in\n+ * Rockchip SoCs such as the RK3576, internally referred to as \"PWM v4\". Uses\n+ * the MFPWM infrastructure to guarantee exclusive use over the device without\n+ * other functions of the device from different drivers interfering with its\n+ * operation while it's active.\n+ *\n+ * Technical Reference Manual: Chapter 31 of the RK3506 TRM Part 1, a SoC which\n+ * uses the same PWM hardware and has a publicly available TRM.\n+ * https://opensource.rock-chips.com/images/3/36/Rockchip_RK3506_TRM_Part_1_V1.2-20250811.pdf\n+ *\n+ * Authors:\n+ * Nicolas Frattaroli <nicolas.frattaroli@collabora.com>\n+ *\n+ * Limitations:\n+ * - The hardware supports both completing the currently running period\n+ * on disable (by switching to oneshot mode with a single repetition and\n+ * only disable when the complete irq fires), and abrupt disable (freeze).\n+ * Only the latter is implemented in the driver.\n+ * - When the output is disabled, the pin will remain driven to whatever state\n+ * it last had.\n+ * - Adjustments to the duty cycle will only take effect during the next period.\n+ * - Adjustments to the period length will only take effect during the next\n+ * period.\n+ * - The hardware only supports offsets in [0, period - duty_cycle]\n+ */\n+\n+#include <linux/math64.h>\n+#include <linux/mfd/rockchip-mfpwm.h>\n+#include <linux/platform_device.h>\n+#include <linux/pwm.h>\n+\n+struct rockchip_pwm_v4 {\n+\tstruct rockchip_mfpwm_func *pwmf;\n+\tstruct pwm_chip chip;\n+};\n+\n+struct __packed rockchip_pwm_v4_wf {\n+\tu32 period;\n+\tu32 duty;\n+\tu32 offset;\n+\tunsigned long rate;\n+};\n+\n+static inline struct rockchip_pwm_v4 *to_rockchip_pwm_v4(struct pwm_chip *chip)\n+{\n+\treturn pwmchip_get_drvdata(chip);\n+}\n+\n+/**\n+ * rockchip_pwm_v4_round_single - convert a PWM parameter to hardware\n+ * @rate: clock rate of the PWM clock, as per clk_get_rate\n+ * Assumed to be <= 1GHz for overflow considerations\n+ * @in_val: parameter in nanoseconds to convert\n+ *\n+ * Returns the rounded value, saturating at U32_MAX if too large\n+ */\n+static u32 rockchip_pwm_v4_round_single(unsigned long rate, u64 in_val)\n+{\n+\tu64 tmp;\n+\n+\ttmp = mul_u64_u64_div_u64(rate, in_val, NSEC_PER_SEC);\n+\tif (tmp > U32_MAX)\n+\t\ttmp = U32_MAX;\n+\n+\treturn tmp;\n+}\n+\n+/**\n+ * rockchip_pwm_v4_round_params - convert PWM parameters to hardware\n+ * @rate: PWM clock rate to do the calculations at\n+ * @wf: pointer to the generic &struct pwm_waveform input parameters\n+ * @wfhw: pointer to the hardware-specific &struct rockchip_pwm_v4_wf output\n+ * parameters that the results will be stored in\n+ *\n+ * Convert nanosecond-based duty/period/offset parameters to the PWM hardware's\n+ * native rounded representation in number of cycles at clock rate @rate. Should\n+ * any of the input parameters be out of range for the hardware, the\n+ * corresponding output parameter is the maximum permissible value for said\n+ * parameter with considerations to the others.\n+ */\n+static void rockchip_pwm_v4_round_params(unsigned long rate,\n+\t\t\t\t\t const struct pwm_waveform *wf,\n+\t\t\t\t\t struct rockchip_pwm_v4_wf *wfhw)\n+{\n+\twfhw->period = rockchip_pwm_v4_round_single(rate, wf->period_length_ns);\n+\n+\twfhw->duty = rockchip_pwm_v4_round_single(rate, wf->duty_length_ns);\n+\n+\t/* As per TRM, PWM_OFFSET: \"The value ranges from 0 to (period-duty)\" */\n+\twfhw->offset = rockchip_pwm_v4_round_single(rate, wf->duty_offset_ns);\n+\tif (!wfhw->period) /* Don't underflow when pwm disabled */\n+\t\twfhw->offset = 0;\n+\telse if (wfhw->offset > wfhw->period - wfhw->duty)\n+\t\twfhw->offset = wfhw->period - wfhw->duty;\n+}\n+\n+static int rockchip_pwm_v4_round_wf_tohw(struct pwm_chip *chip,\n+\t\t\t\t\t struct pwm_device *pwm,\n+\t\t\t\t\t const struct pwm_waveform *wf,\n+\t\t\t\t\t void *_wfhw)\n+{\n+\tstruct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip);\n+\tstruct rockchip_pwm_v4_wf *wfhw = _wfhw;\n+\tunsigned long rate;\n+\n+\trate = clk_get_rate(pc->pwmf->core);\n+\n+\t/*\n+\t * It's unlikely this code path is ever taken, as current hardware does\n+\t * not expose a clock that comes anywhere close to 1GHz. However, in\n+\t * order to avoid even a theoretical overflow in parameter rounding,\n+\t * error out if this ever happens to be the case.\n+\t */\n+\tif (rate > NSEC_PER_SEC)\n+\t\treturn -ERANGE;\n+\n+\trockchip_pwm_v4_round_params(rate, wf, wfhw);\n+\n+\tif (wf->period_length_ns > 0)\n+\t\twfhw->rate = rate;\n+\telse\n+\t\twfhw->rate = 0;\n+\n+\tdev_dbg(&chip->dev,\n+\t\t\"tohw: pwm#%u: %lld/%lld [+%lld] @%lu -> DUTY: %08x, PERIOD: %08x, OFFSET: %08x\\n\",\n+\t\tpwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,\n+\t\trate, wfhw->duty, wfhw->period, wfhw->offset);\n+\n+\treturn 0;\n+}\n+\n+static int rockchip_pwm_v4_round_wf_fromhw(struct pwm_chip *chip,\n+\t\t\t\t\t struct pwm_device *pwm,\n+\t\t\t\t\t const void *_wfhw,\n+\t\t\t\t\t struct pwm_waveform *wf)\n+{\n+\tconst struct rockchip_pwm_v4_wf *wfhw = _wfhw;\n+\tunsigned long rate = wfhw->rate;\n+\n+\tif (rate) {\n+\t\twf->period_length_ns = DIV_ROUND_UP((u64)wfhw->period * NSEC_PER_SEC, rate);\n+\t\twf->duty_length_ns = DIV_ROUND_UP((u64)wfhw->duty * NSEC_PER_SEC, rate);\n+\t\twf->duty_offset_ns = DIV_ROUND_UP((u64)wfhw->offset * NSEC_PER_SEC, rate);\n+\t} else {\n+\t\twf->period_length_ns = 0;\n+\t\twf->duty_length_ns = 0;\n+\t\twf->duty_offset_ns = 0;\n+\t}\n+\n+\tdev_dbg(&chip->dev,\n+\t\t\"fromhw: pwm#%u: DUTY: %08x, PERIOD: %08x, OFFSET: %08x @%lu -> %lld/%lld [+%lld]\\n\",\n+\t\tpwm->hwpwm, wfhw->duty, wfhw->period, wfhw->offset, rate,\n+\t\twf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns);\n+\n+\treturn 0;\n+}\n+\n+static int rockchip_pwm_v4_read_wf(struct pwm_chip *chip, struct pwm_device *pwm,\n+\t\t\t\t void *_wfhw)\n+{\n+\tstruct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip);\n+\tstruct rockchip_pwm_v4_wf *wfhw = _wfhw;\n+\tunsigned long rate;\n+\tint ret;\n+\n+\tret = mfpwm_acquire(pc->pwmf);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\trate = clk_get_rate(pc->pwmf->core);\n+\n+\twfhw->period = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_PERIOD);\n+\twfhw->duty = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_DUTY);\n+\twfhw->offset = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_OFFSET);\n+\tif (rockchip_pwm_v4_is_enabled(mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_ENABLE)))\n+\t\twfhw->rate = rate;\n+\telse\n+\t\twfhw->rate = 0;\n+\n+\tmfpwm_release(pc->pwmf);\n+\n+\treturn 0;\n+}\n+\n+static int rockchip_pwm_v4_write_wf(struct pwm_chip *chip, struct pwm_device *pwm,\n+\t\t\t\t const void *_wfhw)\n+{\n+\tstruct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip);\n+\tconst struct rockchip_pwm_v4_wf *wfhw = _wfhw;\n+\tbool was_enabled;\n+\tint ret;\n+\n+\tret = mfpwm_acquire(pc->pwmf);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\twas_enabled = rockchip_pwm_v4_is_enabled(mfpwm_reg_read(pc->pwmf->base,\n+\t\t\t\t\t\t\t\tPWMV4_REG_ENABLE));\n+\n+\t/*\n+\t * \"But Nicolas\", you ask with valid concerns, \"why would you enable the\n+\t * PWM before setting all the parameter registers?\"\n+\t *\n+\t * Excellent question, Mr. Reader M. Strawman! The RK3576 TRM Part 1\n+\t * Section 34.6.3 specifies that this is the intended order of writes.\n+\t * Doing the PWM_EN and PWM_CLK_EN writes after the params but before\n+\t * the CTRL_UPDATE_EN, or even after the CTRL_UPDATE_EN, results in\n+\t * erratic behaviour where repeated turning on and off of the PWM may\n+\t * not turn it off under all circumstances. This is also why we don't\n+\t * use relaxed writes; it's not worth the footgun.\n+\t */\n+\tif (wfhw->rate)\n+\t\tmfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE,\n+\t\t\t\tFIELD_PREP_WM16(PWMV4_EN_BOTH_MASK,\n+\t\t\t\t\t\tPWMV4_EN_BOTH_MASK));\n+\telse\n+\t\tmfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE,\n+\t\t\t\tFIELD_PREP_WM16(PWMV4_EN_BOTH_MASK, 0));\n+\n+\tmfpwm_reg_write(pc->pwmf->base, PWMV4_REG_PERIOD, wfhw->period);\n+\tmfpwm_reg_write(pc->pwmf->base, PWMV4_REG_DUTY, wfhw->duty);\n+\tmfpwm_reg_write(pc->pwmf->base, PWMV4_REG_OFFSET, wfhw->offset);\n+\n+\tmfpwm_reg_write(pc->pwmf->base, PWMV4_REG_CTRL, PWMV4_CTRL_CONT_FLAGS);\n+\n+\t/* Commit new configuration to hardware output. */\n+\tmfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE,\n+\t\t\tPWMV4_CTRL_UPDATE_EN);\n+\n+\tif (wfhw->rate) {\n+\t\tif (!was_enabled) {\n+\t\t\tdev_dbg(&chip->dev, \"Enabling PWM output\\n\");\n+\t\t\tret = clk_enable(pc->pwmf->core);\n+\t\t\tif (ret)\n+\t\t\t\tgoto err_mfpwm_release;\n+\t\t\tret = clk_set_rate_exclusive(pc->pwmf->core, wfhw->rate);\n+\t\t\tif (ret) {\n+\t\t\t\tclk_disable(pc->pwmf->core);\n+\t\t\t\tgoto err_mfpwm_release;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * Output should be on now, acquire device to guarantee\n+\t\t\t * exclusion with other device functions while it's on.\n+\t\t\t *\n+\t\t\t * It's highly unlikely that this fails, as mfpwm has\n+\t\t\t * already been acquired before, and this is just a\n+\t\t\t * usage counter increase. Not worth the added\n+\t\t\t * complexity of clearing the PWMV4_REG_ENABLE again,\n+\t\t\t * especially considering the CTRL_UPDATE_EN behaviour.\n+\t\t\t */\n+\t\t\tret = mfpwm_acquire(pc->pwmf);\n+\t\t\tif (ret) {\n+\t\t\t\tclk_rate_exclusive_put(pc->pwmf->core);\n+\t\t\t\tclk_disable(pc->pwmf->core);\n+\t\t\t\tgoto err_mfpwm_release;\n+\t\t\t}\n+\t\t}\n+\t} else if (was_enabled) {\n+\t\tdev_dbg(&chip->dev, \"Disabling PWM output\\n\");\n+\t\tclk_rate_exclusive_put(pc->pwmf->core);\n+\t\tclk_disable(pc->pwmf->core);\n+\t\t/* Output is off now, extra release to balance extra acquire */\n+\t\tmfpwm_release(pc->pwmf);\n+\t}\n+\n+err_mfpwm_release:\n+\tmfpwm_release(pc->pwmf);\n+\n+\treturn ret;\n+}\n+\n+static const struct pwm_ops rockchip_pwm_v4_ops = {\n+\t.sizeof_wfhw = sizeof(struct rockchip_pwm_v4_wf),\n+\t.round_waveform_tohw = rockchip_pwm_v4_round_wf_tohw,\n+\t.round_waveform_fromhw = rockchip_pwm_v4_round_wf_fromhw,\n+\t.read_waveform = rockchip_pwm_v4_read_wf,\n+\t.write_waveform = rockchip_pwm_v4_write_wf,\n+};\n+\n+static bool rockchip_pwm_v4_on_and_continuous(struct rockchip_pwm_v4 *pc)\n+{\n+\tbool en;\n+\tu32 val;\n+\n+\ten = rockchip_pwm_v4_is_enabled(mfpwm_reg_read(pc->pwmf->base,\n+\t\t\t\t\t\t PWMV4_REG_ENABLE));\n+\tval = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_CTRL);\n+\n+\treturn en && ((val & PWMV4_MODE_MASK) == PWMV4_MODE_CONT);\n+}\n+\n+static int rockchip_pwm_v4_probe(struct platform_device *pdev)\n+{\n+\tstruct rockchip_mfpwm_func *pwmf = dev_get_platdata(&pdev->dev);\n+\tstruct rockchip_pwm_v4 *pc;\n+\tstruct pwm_chip *chip;\n+\tstruct device *dev = &pdev->dev;\n+\tint ret;\n+\n+\t/*\n+\t * For referencing the PWM in the DT to work, we need the parent MFD\n+\t * device's OF node.\n+\t */\n+\tdev->of_node_reused = true;\n+\tdevice_set_node(dev, of_fwnode_handle(dev->parent->of_node));\n+\n+\tchip = devm_pwmchip_alloc(dev, 1, sizeof(*pc));\n+\tif (IS_ERR(chip))\n+\t\treturn PTR_ERR(chip);\n+\n+\tpc = to_rockchip_pwm_v4(chip);\n+\tpc->pwmf = pwmf;\n+\n+\tret = mfpwm_acquire(pwmf);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret, \"Couldn't acquire mfpwm in probe\\n\");\n+\n+\tif (!rockchip_pwm_v4_on_and_continuous(pc))\n+\t\tmfpwm_release(pwmf);\n+\telse {\n+\t\tdev_dbg(dev, \"PWM was already on at probe time\\n\");\n+\t\tret = clk_enable(pwmf->core);\n+\t\tif (ret) {\n+\t\t\tdev_err_probe(dev, ret, \"Enabling pwm clock failed\\n\");\n+\t\t\tgoto err_mfpwm_release;\n+\t\t}\n+\t\tret = clk_rate_exclusive_get(pc->pwmf->core);\n+\t\tif (ret) {\n+\t\t\tdev_err_probe(dev, ret, \"Protecting pwm clock failed\\n\");\n+\t\t\tgoto err_clk_disable;\n+\t\t}\n+\t}\n+\n+\tplatform_set_drvdata(pdev, chip);\n+\n+\tchip->ops = &rockchip_pwm_v4_ops;\n+\n+\tret = devm_pwmchip_add(dev, chip);\n+\tif (ret) {\n+\t\tdev_err_probe(dev, ret, \"Failed to add PWM chip\\n\");\n+\t\tif (rockchip_pwm_v4_on_and_continuous(pc))\n+\t\t\tgoto err_rate_put;\n+\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+\n+err_rate_put:\n+\tclk_rate_exclusive_put(pwmf->core);\n+err_clk_disable:\n+\tclk_disable(pwmf->core);\n+err_mfpwm_release:\n+\tmfpwm_release(pwmf);\n+\n+\treturn ret;\n+}\n+\n+static const struct platform_device_id rockchip_pwm_v4_ids[] = {\n+\t{ .name = \"rockchip-pwm-v4\", },\n+\t{ /* sentinel */ }\n+};\n+MODULE_DEVICE_TABLE(platform, rockchip_pwm_v4_ids);\n+\n+static struct platform_driver rockchip_pwm_v4_driver = {\n+\t.probe = rockchip_pwm_v4_probe,\n+\t.driver = {\n+\t\t.name = \"rockchip-pwm-v4\",\n+\t},\n+\t.id_table = rockchip_pwm_v4_ids,\n+};\n+module_platform_driver(rockchip_pwm_v4_driver);\n+\n+MODULE_AUTHOR(\"Nicolas Frattaroli <nicolas.frattaroli@collabora.com>\");\n+MODULE_DESCRIPTION(\"Rockchip PWMv4 Driver\");\n+MODULE_LICENSE(\"GPL\");\n+MODULE_IMPORT_NS(\"ROCKCHIP_MFPWM\");\n+MODULE_ALIAS(\"platform:pwm-rockchip-v4\");\n", "prefixes": [ "v5", "3/6" ] }