get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/2227081/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2227081,
    "url": "http://patchwork.ozlabs.org/api/patches/2227081/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/linux-pwm/patch/bf46348985e9c89b8c3f40cd3d00f5bca9d97097.1776932336.git.andrea.porta@suse.com/",
    "project": {
        "id": 38,
        "url": "http://patchwork.ozlabs.org/api/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": "",
        "list_archive_url": "",
        "list_archive_url_format": "",
        "commit_url_format": ""
    },
    "msgid": "<bf46348985e9c89b8c3f40cd3d00f5bca9d97097.1776932336.git.andrea.porta@suse.com>",
    "list_archive_url": null,
    "date": "2026-04-23T08:30:20",
    "name": "[v3,2/3] pwm: rp1: Add RP1 PWM controller driver",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "f0cd947b7b9a393421e6df1c87454e1f7ba07fac",
    "submitter": {
        "id": 88172,
        "url": "http://patchwork.ozlabs.org/api/people/88172/?format=api",
        "name": "Andrea della Porta",
        "email": "andrea.porta@suse.com"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/linux-pwm/patch/bf46348985e9c89b8c3f40cd3d00f5bca9d97097.1776932336.git.andrea.porta@suse.com/mbox/",
    "series": [
        {
            "id": 501159,
            "url": "http://patchwork.ozlabs.org/api/series/501159/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/linux-pwm/list/?series=501159",
            "date": "2026-04-23T08:30:20",
            "name": "Add RP1 PWM controller support",
            "version": 3,
            "mbox": "http://patchwork.ozlabs.org/series/501159/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2227081/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2227081/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "\n <linux-pwm+bounces-8674-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 (2048-bit key;\n unprotected) header.d=suse.com header.i=@suse.com header.a=rsa-sha256\n header.s=google header.b=G4b6aTov;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=172.105.105.114; helo=tor.lore.kernel.org;\n envelope-from=linux-pwm+bounces-8674-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)",
            "smtp.subspace.kernel.org;\n\tdkim=pass (2048-bit key) header.d=suse.com header.i=@suse.com\n header.b=\"G4b6aTov\"",
            "smtp.subspace.kernel.org;\n arc=none smtp.client-ip=209.85.128.46",
            "smtp.subspace.kernel.org;\n dmarc=pass (p=quarantine dis=none) header.from=suse.com",
            "smtp.subspace.kernel.org;\n spf=pass smtp.mailfrom=suse.com"
        ],
        "Received": [
            "from tor.lore.kernel.org (tor.lore.kernel.org [172.105.105.114])\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 4g1Tm52q5Bz1yCv\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 23 Apr 2026 18:29:41 +1000 (AEST)",
            "from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby tor.lore.kernel.org (Postfix) with ESMTP id 4B279307ECAD\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 23 Apr 2026 08:27:38 +0000 (UTC)",
            "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id ECC1236AB7B;\n\tThu, 23 Apr 2026 08:27:37 +0000 (UTC)",
            "from mail-wm1-f46.google.com (mail-wm1-f46.google.com\n [209.85.128.46])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id ED1D43C3C1E\n\tfor <linux-pwm@vger.kernel.org>; Thu, 23 Apr 2026 08:27:31 +0000 (UTC)",
            "by mail-wm1-f46.google.com with SMTP id\n 5b1f17b1804b1-4891e86fabeso57487275e9.1\n        for <linux-pwm@vger.kernel.org>; Thu, 23 Apr 2026 01:27:31 -0700 (PDT)",
            "from localhost (host-79-33-140-232.retail.telecomitalia.it.\n [79.33.140.232])\n        by smtp.gmail.com with ESMTPSA id\n 5b1f17b1804b1-48a55b8baaesm280918355e9.10.2026.04.23.01.27.29\n        (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n        Thu, 23 Apr 2026 01:27:29 -0700 (PDT)"
        ],
        "ARC-Seal": "i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1776932857; cv=none;\n b=rsuZTSDa/MmzaOi1CLHc5qcQffA/vtTN5zKiGnZnsc2x4QDlEqeJ0HO1CYqymMeLmdxYxHKBM4XKsDPTPGxGSwbyIcumdl/AMROa0ICXdMVXEylAS2LsAbmZGtDIUg9YZ+Gwc68RPSvrFcImMUKhfDnYsIhX/PG1wdyxWVuOdno=",
        "ARC-Message-Signature": "i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1776932857; c=relaxed/simple;\n\tbh=dQQSW7BLJ1Fekwuai6OMg+wWXVtPkZuzfiSgoyBjNJU=;\n\th=From:To:Subject:Date:Message-ID:In-Reply-To:References:\n\t MIME-Version:Content-Type;\n b=NxM6bof9quYX7fPt7+oyUXIVeIsbz3wyOsRyKcxenTZO+a6UuPsuKKt4Rr3zwXCr8fbPnWOx4V4un4Yd4DRzZtR+PtFDOEU7n0XLqjmKmelQvNTeSLhCOJCcIMVtqFlhvNSfeXBCjnLmPQp1YslCFqUskqXWNfjjNmgn9w9LGXE=",
        "ARC-Authentication-Results": "i=1; smtp.subspace.kernel.org;\n dmarc=pass (p=quarantine dis=none) header.from=suse.com;\n spf=pass smtp.mailfrom=suse.com;\n dkim=pass (2048-bit key) header.d=suse.com header.i=@suse.com\n header.b=G4b6aTov; arc=none smtp.client-ip=209.85.128.46",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n        d=suse.com; s=google; t=1776932850; x=1777537650;\n darn=vger.kernel.org;\n        h=content-transfer-encoding:mime-version:references:in-reply-to\n         :message-id:date:subject:to:from:from:to:cc:subject:date:message-id\n         :reply-to;\n        bh=j/r+sxkDGMGerd40rxwnYytW5e5CyayQAfIXup8Qqkg=;\n        b=G4b6aTovlufYwUAw2J2WT01Xm5NVqrf3Fb30YMSzmhhLiqid0yZtvzAYq8cffnriWP\n         tWVhnoJQTPcOn+CvHOrhEtJ/jBEo4bNVrSBlJnS8DNhhwBy4inXRFiWK7LuU/LJUrh4O\n         3YgGu/IgcWFaXLBfa6EJYl+ZCYk0mAP/YA6D2YmN5KZ5xOgDHmhaeEsOuZc3JP+rrMo8\n         zX4h6At9aM96Ol/DUYsGDyTKc/GGOTrG37mYR+nsbDR1gOQdF+bZFE73YAyxffSndHdl\n         IlyHh4o3KhGi5VHa+kyAKg+fa02f/fl/CE9qUc6hXFZJKWF5COnjWZ5BU21zcPasvGtA\n         Tddw==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n        d=1e100.net; s=20251104; t=1776932850; x=1777537650;\n        h=content-transfer-encoding:mime-version:references:in-reply-to\n         :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to\n         :cc:subject:date:message-id:reply-to;\n        bh=j/r+sxkDGMGerd40rxwnYytW5e5CyayQAfIXup8Qqkg=;\n        b=tYDTNBVNO3CflnPD6Cr2GfYlBPzwHPKOhblBHqOcvetzXLCFP+RHhwpNNB+Kt0hiai\n         /ICir47SCcs5in6OOc81vMnw7HwgGjOj+WzgVyTCKJBiCXDhdJmxR0G3LHu4Xqw/OjIR\n         Y0+xvOLNwzwDcHxmpbGvDx+0EI2jUwrvqzV3H7kwRq1gXmazEFk4tYPZ4mi8nxzPlKQa\n         zjJ2tbIoRdCGfd+zMHr0gpwkG/q4/KHeDvW15WS0FJfo9NiPI5YNDhvRhcWkmDOxKhVL\n         8LmYZ6Ygsc6FG7WQ3kK7vMHT8hr9YdLl17k25+K1Z+bR9AWXtPgjHWTzWRuHC6Ac2r7e\n         3aYg==",
        "X-Forwarded-Encrypted": "i=1;\n AFNElJ9z9e+13wMidESLjQ40OdzBfoLDNNlj615eVVsHKUe8niAOd2A0BmeRCfP+7C+TdNCIH+4Lcf6xZXw=@vger.kernel.org",
        "X-Gm-Message-State": "AOJu0YyVT0S3Bmwq6yJ8qrvH+zc5v1owDeBNsqnsD+gQ0nIb7GR3kMuj\n\thyS8Zy9f17+0tdB9n0tFYRbYDbTFvLPv7reTMy3krtTxsrQmIXdayN9PyFpTI8D+who=",
        "X-Gm-Gg": "AeBDietpGlgd4hXNanGvaMr7Pd40eM2pcQbRfpUtPJh8qhTOcKXA6/fQFVuWywghIDN\n\t5lrI9fxd/f2/Xe6iBoDGsuNRRsonC7WOF9UZCENWOdYdwvcuKsoQCmLKLrAJZXS+4mYjo7qB1ol\n\tXG1+Ep5EOsYUnRlqBIUa9LcxWFQAh+eYFWe+iBAZ8SE0nBmHMx5xvAPs6QB/6+Zsk1foPnxeJiX\n\tT5HOraWHm7KWGxuT5H+dfN651OgZ1JUCZYyiJHdYTaH7IQyMTP61g5LvgA1tBXn6kj4S1fEfjwm\n\tNfYGDaeeOB2UKgF59gHigyEISn10gb6NgiwhhFlnR2YaPr/eJep88NGthWTBx5AVMbF9vPlJKWX\n\ttj8pJ1BJpTcv/iWpA/UjX8WqaUQdglEu76m9hv/M+CPfkileLJIaJtIjafQtTKnsUSkxvCfpqVB\n\tyk1SlGymOAjncCB5wv+RFcjhmAwJTuICjAdsgosREolAzH9XFyJg0IZ/qGaKBOkXNHR28yJTVP3\n\tglz66o=",
        "X-Received": "by 2002:a05:600c:64c7:b0:488:a2ac:a334 with SMTP id\n 5b1f17b1804b1-488fb73aa3bmr388775135e9.3.1776932850256;\n        Thu, 23 Apr 2026 01:27:30 -0700 (PDT)",
        "From": "Andrea della Porta <andrea.porta@suse.com>",
        "To": "=?utf-8?q?Uwe_Kleine-K=C3=B6nig?= <ukleinek@kernel.org>,\n linux-pwm@vger.kernel.org, Rob Herring <robh@kernel.org>,\n Krzysztof Kozlowski <krzk+dt@kernel.org>, Conor Dooley <conor+dt@kernel.org>,\n Florian Fainelli <florian.fainelli@broadcom.com>,\n Broadcom internal kernel review list <bcm-kernel-feedback-list@broadcom.com>,\n Andrea della Porta <andrea.porta@suse.com>, devicetree@vger.kernel.org,\n linux-rpi-kernel@lists.infradead.org, linux-arm-kernel@lists.infradead.org,\n linux-kernel@vger.kernel.org, Naushir Patuck <naush@raspberrypi.com>,\n Stanimir Varbanov <svarbanov@suse.de>, mbrugger@suse.com",
        "Subject": "[PATCH v3 2/3] pwm: rp1: Add RP1 PWM controller driver",
        "Date": "Thu, 23 Apr 2026 10:30:20 +0200",
        "Message-ID": "\n <bf46348985e9c89b8c3f40cd3d00f5bca9d97097.1776932336.git.andrea.porta@suse.com>",
        "X-Mailer": "git-send-email 2.51.0",
        "In-Reply-To": "<cover.1776932336.git.andrea.porta@suse.com>",
        "References": "<cover.1776932336.git.andrea.porta@suse.com>",
        "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": "8bit"
    },
    "content": "From: Naushir Patuck <naush@raspberrypi.com>\n\nThe Raspberry Pi RP1 southbridge features an embedded PWM\ncontroller with 4 output channels, alongside an RPM interface\nto read the fan speed on the Raspberry Pi 5.\n\nAdd the supporting driver.\n\nSigned-off-by: Naushir Patuck <naush@raspberrypi.com>\nCo-developed-by: Stanimir Varbanov <svarbanov@suse.de>\nSigned-off-by: Stanimir Varbanov <svarbanov@suse.de>\nSigned-off-by: Andrea della Porta <andrea.porta@suse.com>\n---\n drivers/pwm/Kconfig   |   9 +\n drivers/pwm/Makefile  |   1 +\n drivers/pwm/pwm-rp1.c | 414 ++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 424 insertions(+)\n create mode 100644 drivers/pwm/pwm-rp1.c",
    "diff": "diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig\nindex 6f3147518376a..32031f2af75af 100644\n--- a/drivers/pwm/Kconfig\n+++ b/drivers/pwm/Kconfig\n@@ -625,6 +625,15 @@ config PWM_ROCKCHIP\n \t  Generic PWM framework driver for the PWM controller found on\n \t  Rockchip SoCs.\n \n+config PWM_RASPBERRYPI_RP1\n+\tbool \"RP1 PWM support\"\n+\tdepends on MISC_RP1 || COMPILE_TEST\n+\tdepends on HAS_IOMEM\n+\tselect REGMAP_MMIO\n+\tselect MFD_SYSCON\n+\thelp\n+\t  PWM framework driver for Raspberry Pi RP1 controller.\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 0dc0d2b69025d..59f29f60f9123 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_RASPBERRYPI_RP1)\t+= pwm-rp1.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-rp1.c b/drivers/pwm/pwm-rp1.c\nnew file mode 100644\nindex 0000000000000..15087917a18e9\n--- /dev/null\n+++ b/drivers/pwm/pwm-rp1.c\n@@ -0,0 +1,414 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * pwm-rp1.c\n+ *\n+ * Raspberry Pi RP1 PWM.\n+ *\n+ * Copyright © 2026 Raspberry Pi Ltd.\n+ *\n+ * Author: Naushir Patuck (naush@raspberrypi.com)\n+ *\n+ * Based on the pwm-bcm2835 driver by:\n+ * Bart Tanghe <bart.tanghe@thomasmore.be>\n+ *\n+ * Datasheet: https://pip-assets.raspberrypi.com/categories/892-raspberry-pi-5/documents/RP-008370-DS-1-rp1-peripherals.pdf?disposition=inline\n+ *\n+ * Limitations:\n+ * - Channels can be enabled/disabled through a global update flag, while the\n+ *   period and duty per-channel registers are independently updatable, and\n+ *   they are latched on the end of (specific channel) period strobe.\n+ *   This means that period and duty changes might result in glitches if the\n+ *   period/duty is changed exactly during an end of period strobe.\n+ * - Since the duty/period registers are freely updatable (do not depend on\n+ *   the global update flag), setting one of them close to the period end and\n+ *   the other right afterwards results in a mixed output for that cycle because\n+ *   the write ops are not atomic.\n+ * - The global update flag prevents mis-sampling of multi-bit bus signals in\n+ *   the PWM clock domain. This ensures that all PWM channel settings update\n+ *   on the same PWM clock cycle. Channels start in sync only if they share the\n+ *   same period.\n+ * - If both duty and period are set to 0, the output is a constant low signal\n+ *   if polarity is normal or a constant high signal if polarity is inversed.\n+ * - When disabled the output is driven to 0 if polarity is normal, or to 1\n+ *   if polarity is inversed.\n+ * - Disabling the PWM stops the output immediately, without waiting for current\n+ *   period to complete first.\n+ * - Channels are phase-capable, but on RPi5, the firmware can use a channel\n+ *   phase register to report the RPM of the fan connected to that PWM\n+ *   channel. As a result, phase control will be ignored for now.\n+ */\n+\n+#include <linux/bitops.h>\n+#include <linux/clk.h>\n+#include <linux/err.h>\n+#include <linux/io.h>\n+#include <linux/module.h>\n+#include <linux/of.h>\n+#include <linux/platform_device.h>\n+#include <linux/pwm.h>\n+#include <linux/regmap.h>\n+#include <linux/mfd/syscon.h>\n+#include <linux/units.h>\n+\n+#define RP1_PWM_GLB_CTRL\t\t\t0x000\n+#define RP1_PWM_GLB_CTRL_CHANNEL_ENABLE(chan)\tBIT(chan)\n+#define RP1_PWM_GLB_CTRL_SET_UPDATE\t\tBIT(31)\n+\n+#define RP1_PWM_CHAN_CTRL(chan)\t\t\t(0x014 + ((chan) * 0x10))\n+#define RP1_PWM_CHAN_CTRL_POLARITY\t\tBIT(3)\n+#define RP1_PWM_CHAN_CTRL_FIFO_POP_MASK\t\tBIT(8)\n+#define RP1_PWM_CHAN_CTRL_MODE\t\t\tGENMASK(2, 0)\n+enum rp1_pwm_ctrl_mode {\n+\tRP1_PWM_CHAN_CTRL_MODE_ZERO,\n+\tRP1_PWM_CHAN_CTRL_MODE_TE_MS,\n+\tRP1_PWM_CHAN_CTRL_MODE_PC_MS,\n+\tRP1_PWM_CHAN_CTRL_MODE_PD_ENC,\n+\tRP1_PWM_CHAN_CTRL_MODE_MSB_SER,\n+\tRP1_PWM_CHAN_CTRL_MODE_PPM,\n+\tRP1_PWM_CHAN_CTRL_MODE_LE_MS,\n+\tRP1_PWM_CHAN_CTRL_MODE_LSB_SER,\n+};\n+\n+#define RP1_PWM_CHAN_CTRL_DEFAULT\t\t(RP1_PWM_CHAN_CTRL_FIFO_POP_MASK +  \\\n+\t\t\t\t\t\tFIELD_PREP(RP1_PWM_CHAN_CTRL_MODE, \\\n+\t\t\t\t\t\tRP1_PWM_CHAN_CTRL_MODE_TE_MS))\n+\n+#define RP1_PWM_RANGE(chan)\t\t\t(0x018 + ((chan) * 0x10))\n+#define RP1_PWM_PHASE(chan)\t\t\t(0x01C + ((chan) * 0x10))\n+#define RP1_PWM_DUTY(chan)\t\t\t(0x020 + ((chan) * 0x10))\n+\n+#define RP1_PWM_NUM_PWMS\t\t\t4\n+\n+struct rp1_pwm {\n+\tstruct regmap *regmap;\n+\tstruct clk *clk;\n+\tunsigned long clk_rate;\n+\tbool clk_enabled;\n+};\n+\n+struct rp1_pwm_waveform {\n+\tu32 period_ticks;\n+\tu32 duty_ticks;\n+\tbool enabled;\n+\tbool inverted_polarity;\n+};\n+\n+static const struct regmap_config rp1_pwm_regmap_config = {\n+\t.reg_bits = 32,\n+\t.val_bits = 32,\n+\t.reg_stride = 4,\n+\t.max_register = 0x60,\n+};\n+\n+static void rp1_pwm_apply_config(struct pwm_chip *chip, struct pwm_device *pwm)\n+{\n+\tstruct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);\n+\tu32 value;\n+\n+\t/* update the changed registers on the next strobe to avoid glitches */\n+\tregmap_read(rp1->regmap, RP1_PWM_GLB_CTRL, &value);\n+\tvalue |= RP1_PWM_GLB_CTRL_SET_UPDATE;\n+\tregmap_write(rp1->regmap, RP1_PWM_GLB_CTRL, value);\n+}\n+\n+static int rp1_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)\n+{\n+\tstruct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);\n+\n+\t/* init channel to reset defaults */\n+\tregmap_write(rp1->regmap, RP1_PWM_CHAN_CTRL(pwm->hwpwm), RP1_PWM_CHAN_CTRL_DEFAULT);\n+\treturn 0;\n+}\n+\n+static int rp1_pwm_round_waveform_tohw(struct pwm_chip *chip,\n+\t\t\t\t       struct pwm_device *pwm,\n+\t\t\t\t       const struct pwm_waveform *wf,\n+\t\t\t\t       void *_wfhw)\n+{\n+\tstruct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);\n+\tu64 period_ticks, duty_ticks, offset_ticks;\n+\tstruct rp1_pwm_waveform *wfhw = _wfhw;\n+\tu64 clk_rate = rp1->clk_rate;\n+\tint ret = 0;\n+\n+\tif (!wf->period_length_ns) {\n+\t\twfhw->enabled = false;\n+\t\treturn 0;\n+\t}\n+\n+\tperiod_ticks = mul_u64_u64_div_u64(wf->period_length_ns, clk_rate, NSEC_PER_SEC);\n+\n+\t/*\n+\t * The period is limited to U32_MAX - 1\n+\t * to allow 100% duty cycle.\n+\t */\n+\tif (period_ticks > U32_MAX) {\n+\t\tperiod_ticks = U32_MAX - 1;\n+\t} else if (!period_ticks) {\n+\t\tperiod_ticks = 2;\n+\t\tret = 1;\n+\t}\n+\n+\tduty_ticks = mul_u64_u64_div_u64(wf->duty_length_ns, clk_rate, NSEC_PER_SEC);\n+\toffset_ticks = mul_u64_u64_div_u64(wf->duty_offset_ns, clk_rate, NSEC_PER_SEC);\n+\tif (duty_ticks && offset_ticks &&\n+\t    duty_ticks + offset_ticks >= period_ticks) {\n+\t\twfhw->duty_ticks = period_ticks - duty_ticks;\n+\t\twfhw->inverted_polarity = true;\n+\t} else {\n+\t\twfhw->duty_ticks = duty_ticks;\n+\t\twfhw->inverted_polarity = false;\n+\t}\n+\t/* Account for the extra tick at the end of the period */\n+\twfhw->period_ticks = period_ticks - 1;\n+\n+\twfhw->enabled = true;\n+\n+\treturn ret;\n+}\n+\n+static int rp1_pwm_round_waveform_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+\tstruct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);\n+\tconst struct rp1_pwm_waveform *wfhw = _wfhw;\n+\tu64 clk_rate = rp1->clk_rate;\n+\tu32 ticks;\n+\n+\t*wf = (struct pwm_waveform){ };\n+\n+\tif (!wfhw->enabled)\n+\t\treturn 0;\n+\n+\twf->period_length_ns = DIV_ROUND_UP_ULL((u64)(wfhw->period_ticks + 1) * NSEC_PER_SEC,\n+\t\t\t\t\t\tclk_rate);\n+\n+\tif (!wfhw->inverted_polarity) {\n+\t\twf->duty_length_ns = DIV_ROUND_UP_ULL((u64)wfhw->duty_ticks * NSEC_PER_SEC,\n+\t\t\t\t\t\t      clk_rate);\n+\t} else {\n+\t\tticks = wfhw->period_ticks + 1 - wfhw->duty_ticks;\n+\t\twf->duty_length_ns = DIV_ROUND_UP_ULL((u64)ticks * NSEC_PER_SEC, clk_rate);\n+\t\twf->duty_offset_ns = wf->period_length_ns - wf->duty_length_ns;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int rp1_pwm_write_waveform(struct pwm_chip *chip,\n+\t\t\t\t  struct pwm_device *pwm,\n+\t\t\t\t  const void *_wfhw)\n+{\n+\tstruct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);\n+\tconst struct rp1_pwm_waveform *wfhw = _wfhw;\n+\tu32 value, ctrl;\n+\n+\t/* early exit if disabled */\n+\tregmap_read(rp1->regmap, RP1_PWM_GLB_CTRL, &ctrl);\n+\tif (!wfhw->enabled) {\n+\t\tctrl &= ~RP1_PWM_GLB_CTRL_CHANNEL_ENABLE(pwm->hwpwm);\n+\t\tgoto exit_disable;\n+\t}\n+\n+\t/* set period and duty cycle */\n+\tregmap_write(rp1->regmap,\n+\t\t     RP1_PWM_RANGE(pwm->hwpwm), wfhw->period_ticks);\n+\tregmap_write(rp1->regmap,\n+\t\t     RP1_PWM_DUTY(pwm->hwpwm), wfhw->duty_ticks);\n+\n+\t/* set polarity */\n+\tregmap_read(rp1->regmap, RP1_PWM_CHAN_CTRL(pwm->hwpwm), &value);\n+\tif (!wfhw->inverted_polarity)\n+\t\tvalue &= ~RP1_PWM_CHAN_CTRL_POLARITY;\n+\telse\n+\t\tvalue |= RP1_PWM_CHAN_CTRL_POLARITY;\n+\tregmap_write(rp1->regmap, RP1_PWM_CHAN_CTRL(pwm->hwpwm), value);\n+\n+\t/* enable the channel */\n+\tctrl |= RP1_PWM_GLB_CTRL_CHANNEL_ENABLE(pwm->hwpwm);\n+exit_disable:\n+\tregmap_write(rp1->regmap, RP1_PWM_GLB_CTRL, ctrl);\n+\n+\trp1_pwm_apply_config(chip, pwm);\n+\n+\treturn 0;\n+}\n+\n+static int rp1_pwm_read_waveform(struct pwm_chip *chip,\n+\t\t\t\t struct pwm_device *pwm,\n+\t\t\t\t void *_wfhw)\n+{\n+\tstruct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);\n+\tstruct rp1_pwm_waveform *wfhw = _wfhw;\n+\tu32 value;\n+\n+\tregmap_read(rp1->regmap, RP1_PWM_GLB_CTRL, &value);\n+\twfhw->enabled = !!(value & RP1_PWM_GLB_CTRL_CHANNEL_ENABLE(pwm->hwpwm));\n+\n+\tregmap_read(rp1->regmap, RP1_PWM_CHAN_CTRL(pwm->hwpwm), &value);\n+\twfhw->inverted_polarity = !!(value & RP1_PWM_CHAN_CTRL_POLARITY);\n+\n+\tif (wfhw->enabled) {\n+\t\tregmap_read(rp1->regmap, RP1_PWM_RANGE(pwm->hwpwm), &wfhw->period_ticks);\n+\t\tregmap_read(rp1->regmap, RP1_PWM_DUTY(pwm->hwpwm), &wfhw->duty_ticks);\n+\t} else {\n+\t\twfhw->period_ticks = 0;\n+\t\twfhw->duty_ticks = 0;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static const struct pwm_ops rp1_pwm_ops = {\n+\t.sizeof_wfhw = sizeof(struct rp1_pwm_waveform),\n+\t.request = rp1_pwm_request,\n+\t.round_waveform_tohw = rp1_pwm_round_waveform_tohw,\n+\t.round_waveform_fromhw = rp1_pwm_round_waveform_fromhw,\n+\t.read_waveform = rp1_pwm_read_waveform,\n+\t.write_waveform = rp1_pwm_write_waveform,\n+};\n+\n+static int rp1_pwm_probe(struct platform_device *pdev)\n+{\n+\tstruct device *dev = &pdev->dev;\n+\tstruct device_node *np = dev->of_node;\n+\tunsigned long clk_rate;\n+\tstruct pwm_chip *chip;\n+\tvoid __iomem\t*base;\n+\tstruct rp1_pwm *rp1;\n+\tint ret;\n+\n+\tchip = devm_pwmchip_alloc(dev, RP1_PWM_NUM_PWMS, sizeof(*rp1));\n+\tif (IS_ERR(chip))\n+\t\treturn PTR_ERR(chip);\n+\n+\trp1 = pwmchip_get_drvdata(chip);\n+\n+\tbase = devm_platform_ioremap_resource(pdev, 0);\n+\tif (IS_ERR(base))\n+\t\treturn PTR_ERR(base);\n+\n+\trp1->regmap = devm_regmap_init_mmio(dev, base, &rp1_pwm_regmap_config);\n+\tif (IS_ERR(rp1->regmap))\n+\t\treturn dev_err_probe(dev, PTR_ERR(rp1->regmap), \"Cannot initialize regmap\\n\");\n+\n+\tret = of_syscon_register_regmap(np, rp1->regmap);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret, \"Failed to register syscon\\n\");\n+\n+\trp1->clk = devm_clk_get(dev, NULL);\n+\tif (IS_ERR(rp1->clk))\n+\t\treturn dev_err_probe(dev, PTR_ERR(rp1->clk), \"Clock not found\\n\");\n+\n+\tret = clk_prepare_enable(rp1->clk);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret, \"Failed to enable clock\\n\");\n+\trp1->clk_enabled = true;\n+\n+\tret = devm_clk_rate_exclusive_get(dev, rp1->clk);\n+\tif (ret) {\n+\t\tdev_err_probe(dev, ret, \"Failed to get exclusive rate\\n\");\n+\t\tgoto err_disable_clk;\n+\t}\n+\n+\tclk_rate = clk_get_rate(rp1->clk);\n+\tif (!clk_rate) {\n+\t\tret = dev_err_probe(dev, -EINVAL, \"Failed to get clock rate\\n\");\n+\t\tgoto err_disable_clk;\n+\t}\n+\t/*\n+\t * To prevent u64 overflow in period calculations:\n+\t * mul_u64_u64_div_u64(period_ns, clk_rate, NSEC_PER_SEC)\n+\t * If clk_rate > 1 GHz, the result can overflow.\n+\t */\n+\tif (clk_rate > HZ_PER_GHZ) {\n+\t\tret = dev_err_probe(dev, -EINVAL, \"Clock rate > 1 GHz is not supported\\n\");\n+\t\tgoto err_disable_clk;\n+\t}\n+\trp1->clk_rate = clk_rate;\n+\n+\tchip->ops = &rp1_pwm_ops;\n+\n+\tplatform_set_drvdata(pdev, chip);\n+\n+\tret = pwmchip_add(chip);\n+\tif (ret) {\n+\t\tdev_err_probe(dev, ret, \"Failed to register PWM chip\\n\");\n+\t\tgoto err_disable_clk;\n+\t}\n+\n+\treturn 0;\n+\n+err_disable_clk:\n+\tclk_disable_unprepare(rp1->clk);\n+\n+\treturn ret;\n+}\n+\n+static void rp1_pwm_remove(struct platform_device *pdev)\n+{\n+\tstruct pwm_chip *chip = platform_get_drvdata(pdev);\n+\tstruct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);\n+\n+\tpwmchip_remove(chip);\n+\n+\tif (rp1->clk_enabled) {\n+\t\tclk_disable_unprepare(rp1->clk);\n+\t\trp1->clk_enabled = false;\n+\t}\n+}\n+\n+static int rp1_pwm_suspend(struct device *dev)\n+{\n+\tstruct rp1_pwm *rp1 = dev_get_drvdata(dev);\n+\n+\tif (rp1->clk_enabled) {\n+\t\tclk_disable_unprepare(rp1->clk);\n+\t\trp1->clk_enabled = false;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int rp1_pwm_resume(struct device *dev)\n+{\n+\tstruct rp1_pwm *rp1 = dev_get_drvdata(dev);\n+\tint ret;\n+\n+\tret = clk_prepare_enable(rp1->clk);\n+\tif (ret) {\n+\t\tdev_err(dev, \"Failed to enable clock on resume: %pe\\n\", ERR_PTR(ret));\n+\t\treturn ret;\n+\t}\n+\n+\trp1->clk_enabled = true;\n+\n+\treturn 0;\n+}\n+\n+static DEFINE_SIMPLE_DEV_PM_OPS(rp1_pwm_pm_ops, rp1_pwm_suspend, rp1_pwm_resume);\n+\n+static const struct of_device_id rp1_pwm_of_match[] = {\n+\t{ .compatible = \"raspberrypi,rp1-pwm\" },\n+\t{ /* sentinel */ }\n+};\n+MODULE_DEVICE_TABLE(of, rp1_pwm_of_match);\n+\n+static struct platform_driver rp1_pwm_driver = {\n+\t.probe = rp1_pwm_probe,\n+\t.remove = rp1_pwm_remove,\n+\t.driver = {\n+\t\t.name = \"rp1-pwm\",\n+\t\t.of_match_table = rp1_pwm_of_match,\n+\t\t.pm = pm_ptr(&rp1_pwm_pm_ops),\n+\t\t.suppress_bind_attrs = true,\n+\t},\n+};\n+module_platform_driver(rp1_pwm_driver);\n+\n+MODULE_DESCRIPTION(\"RP1 PWM driver\");\n+MODULE_AUTHOR(\"Naushir Patuck <naush@raspberrypi.com>\");\n+MODULE_AUTHOR(\"Andrea della Porta <andrea.porta@suse.com>\");\n+MODULE_LICENSE(\"GPL\");\n",
    "prefixes": [
        "v3",
        "2/3"
    ]
}