{"id":2221976,"url":"http://patchwork.ozlabs.org/api/patches/2221976/?format=json","web_url":"http://patchwork.ozlabs.org/project/linux-pwm/patch/0d99317b9150310dfbd98de1cb2a890f0bffe7cd.1775829499.git.andrea.porta@suse.com/","project":{"id":38,"url":"http://patchwork.ozlabs.org/api/projects/38/?format=json","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":"<0d99317b9150310dfbd98de1cb2a890f0bffe7cd.1775829499.git.andrea.porta@suse.com>","list_archive_url":null,"date":"2026-04-10T14:09:58","name":"[v2,2/3] pwm: rp1: Add RP1 PWM controller driver","commit_ref":null,"pull_url":null,"state":"changes-requested","archived":false,"hash":"ba693ce6a2ab312fe0e7e9b89e4e36b278002887","submitter":{"id":88172,"url":"http://patchwork.ozlabs.org/api/people/88172/?format=json","name":"Andrea della Porta","email":"andrea.porta@suse.com"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/linux-pwm/patch/0d99317b9150310dfbd98de1cb2a890f0bffe7cd.1775829499.git.andrea.porta@suse.com/mbox/","series":[{"id":499466,"url":"http://patchwork.ozlabs.org/api/series/499466/?format=json","web_url":"http://patchwork.ozlabs.org/project/linux-pwm/list/?series=499466","date":"2026-04-10T14:09:56","name":"Add RP1 PWM controller support","version":2,"mbox":"http://patchwork.ozlabs.org/series/499466/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2221976/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2221976/checks/","tags":{},"related":[],"headers":{"Return-Path":"\n <linux-pwm+bounces-8548-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=C0pZfns8;\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-8548-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=\"C0pZfns8\"","smtp.subspace.kernel.org;\n arc=none smtp.client-ip=209.85.128.50","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 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fsdtQ0rm1z1yGb\n\tfor <incoming@patchwork.ozlabs.org>; Sat, 11 Apr 2026 00:07:58 +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 BA5FB3047BFE\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 10 Apr 2026 14:07:17 +0000 (UTC)","from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id C71A03CCA1C;\n\tFri, 10 Apr 2026 14:07:09 +0000 (UTC)","from mail-wm1-f50.google.com (mail-wm1-f50.google.com\n [209.85.128.50])\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 0B79D3CD8B5\n\tfor <linux-pwm@vger.kernel.org>; Fri, 10 Apr 2026 14:07:06 +0000 (UTC)","by mail-wm1-f50.google.com with SMTP id\n 5b1f17b1804b1-488aa77a06eso32947065e9.0\n        for <linux-pwm@vger.kernel.org>; Fri, 10 Apr 2026 07:07:06 -0700 (PDT)","from localhost (93-41-3-120.ip79.fastwebnet.it. [93.41.3.120])\n        by smtp.gmail.com with ESMTPSA id\n 5b1f17b1804b1-488d5344e28sm81284895e9.7.2026.04.10.07.07.04\n        (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n        Fri, 10 Apr 2026 07:07:04 -0700 (PDT)"],"ARC-Seal":"i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1775830029; cv=none;\n b=I66FZIHGdKCVzo5gwuBbiuMAXklhuwPYIs4h/MsEfaLYSvR4HPKDEu1rYuoa4E+JBCqEra0/MaakoyQafOtGH9MN0CL2geyA1B7hKIhJc+RTQ5ZrIU1y2+0wqgBnGCJjekGxAqdLLuFT0EB5G6LSTrp7aAKLcGJxpnLhnUVR4vg=","ARC-Message-Signature":"i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1775830029; c=relaxed/simple;\n\tbh=jfTieWK0v3wB1u8q3YW98anrGlNYzfKbPTAkMW7RJG4=;\n\th=From:To:Subject:Date:Message-ID:In-Reply-To:References:\n\t MIME-Version:Content-Type;\n b=dv0XxqCIWofYiQIEZdVYBQswVyzcg0PoJoKcGfL61efjYkdMvwopN1+q5KC9PqK12zfB2/Vj7MVfgRd5qASfVFlu16jAKAsNYYlcK7uqr5ikdC/ivagUaFZOyFYvrMSXkdc7z7NjEwpkVkWzlJPxwzbhlRxCxcmlzym/HMtBF9U=","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=C0pZfns8; arc=none smtp.client-ip=209.85.128.50","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n        d=suse.com; s=google; t=1775830025; x=1776434825;\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=SFH9HkuqRORBaQ1DhfyanXw+/L8CLUaspI4xBL/ZFQc=;\n        b=C0pZfns8WsfgNKKUXXTsZSgKv8aDFe1jkJ+twDWMFl3Ay5flYiFltdDT+Ki42J6ee0\n         osj8G15CMpB8vQlUFcaDTCQmG2VdfXZ6JNAupsQe1lD+QhVprYAyKbI5EHEI+jzqGaaU\n         TCe6Y01q4/MNKkyDk1xsMUv2q65SlV8/axzeyOVTVti1lJL///gYMJDygn0otMjOWyDN\n         +juAL295lBFoFGJ6xGnwfIidvau3lCEztWoK/GdVs3kcW/0myN7d+Bl2nFCW7AeKAIpO\n         huax9GNnTDmOQMDdL4tNDR5KMDamwyByhp2cIPHIAqw5hllfZBuxUZH2h+OkJcz3/kNF\n         DQOw==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n        d=1e100.net; s=20251104; t=1775830025; x=1776434825;\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=SFH9HkuqRORBaQ1DhfyanXw+/L8CLUaspI4xBL/ZFQc=;\n        b=cW/lwvTE5CnA2e+WJLNLDRvXPyEePqT9JB+lhnML1jGHSFxJrBWcztkd+wI3X5SmzM\n         +UklAqvwa5IIRxbHERJyYsUlH+BjQNKTragjfDLQ7ViAHd3pbry9u9lEn5eQsqcXmCnz\n         USAoAyiLzeg2tyYhQBpmmP8HsEhcd2K4mkCM/q84G8l9zcVH/ufVgonCwOM+Rz5kz6/e\n         HMtlgAFLmatOQ2sg0fMXR6C2rv7TpzT+WbzsvHRXANXtrt+GV0Fc5lN7BzLXyMVZ19O8\n         x0HMHLMFl3dp+9E7WIfdyxhCyJsLo0yxlsonc15OjDiiELRnINM+2WuFBDGPwjq6HDJF\n         jqJw==","X-Forwarded-Encrypted":"i=1;\n AJvYcCV+Vt94m4CjM6MUGedA35sgxt8w3lgPBHs0x+9eAk77wbcQ7cnDvN8z1MPc3TByKEnwSD9ltDzoDPo=@vger.kernel.org","X-Gm-Message-State":"AOJu0YwE3MeuHgZKqGLKIA0y7Mf4gUg8VvMdDjNmOVvLnwJm1vx2M8cg\n\tz3wf0RK4rBfOLbI8FRQCc1XUgaBLFeuGX7q844y2PK4T/MztLnaw7NtWcV0ayRnI94I=","X-Gm-Gg":"AeBDieu0KFBhmFeQEQg8JTPLzxw3Il0Hf+76AQtZwGvOGoTS9/16mj9cGdmHan6747y\n\tBblxxBE2o7dj5o38Ps6WyyRmzu7oCcB81witN13XRDWCjXeyyz6cOR8jcrWchJ5H+rGJ2oaCYi9\n\tA1vOKRaEbVocPcuvPQBNPuNEaMf6Lmc7SfPWFL+EgD3j6OKtkbbBsPn9qpxtcV06P2pUVHaf0aS\n\tmtl4pXyQsn12VAXlWyfG6fPRMrG1GvBfEJ8fUdBR0LulDH/ZT/H0rpPlEOxSTf0ZgS+l0hsXAin\n\t1+IAeI1Nw3Kc59Z0nKif/M5tfsUDLkPR3JcqU6+xpIy8Xq0FfhNCXp2vo43QZgiIgfmbJ3RFZPi\n\tuHXQ02QsWtOUOg7/lSdbWdQNX/DXmkEn5OnXgC5wYAa51j4qIsCpPdy+awrvJJdn0wRNSor032/\n\t4APKVn6cuNRGYij/qCB4viCNABSa9C6dbBgKMQ0nulHGIxDMlQyg==","X-Received":"by 2002:a05:600c:5298:b0:485:3ee1:eba5 with SMTP id\n 5b1f17b1804b1-488d68820abmr45138605e9.27.1775830025295;\n        Fri, 10 Apr 2026 07:07:05 -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 v2 2/3] pwm: rp1: Add RP1 PWM controller driver","Date":"Fri, 10 Apr 2026 16:09:58 +0200","Message-ID":"\n <0d99317b9150310dfbd98de1cb2a890f0bffe7cd.1775829499.git.andrea.porta@suse.com>","X-Mailer":"git-send-email 2.51.0","In-Reply-To":"<cover.1775829499.git.andrea.porta@suse.com>","References":"<cover.1775829499.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 | 344 ++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 354 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..b88c697d9567e\n--- /dev/null\n+++ b/drivers/pwm/pwm-rp1.c\n@@ -0,0 +1,344 @@\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 and their duty cycle and period can\n+ *   be updated glitchlessly. Update are synchronized with the next strobe\n+ *   at the end of the current period of the respective channel, once the\n+ *   update bit is set. The update flag is global, not per-channel.\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+\n+#define RP1_PWM_GLOBAL_CTRL\t0x000\n+#define RP1_PWM_CHANNEL_CTRL(x)\t(0x014 + ((x) * 0x10))\n+#define RP1_PWM_RANGE(x)\t(0x018 + ((x) * 0x10))\n+#define RP1_PWM_PHASE(x)\t(0x01C + ((x) * 0x10))\n+#define RP1_PWM_DUTY(x)\t\t(0x020 + ((x) * 0x10))\n+\n+/* 8:FIFO_POP_MASK + 0:Trailing edge M/S modulation */\n+#define RP1_PWM_CHANNEL_DEFAULT\t\t(BIT(8) + BIT(0))\n+#define RP1_PWM_CHANNEL_ENABLE(x)\tBIT(x)\n+#define RP1_PWM_POLARITY\t\tBIT(3)\n+#define RP1_PWM_SET_UPDATE\t\tBIT(31)\n+#define RP1_PWM_MODE_MASK\t\tGENMASK(1, 0)\n+\n+#define RP1_PWM_NUM_PWMS\t4\n+\n+struct rp1_pwm {\n+\tstruct regmap\t*regmap;\n+\tstruct clk\t*clk;\n+\tunsigned long\tclk_rate;\n+\tbool\t\tclk_enabled;\n+};\n+\n+struct rp1_pwm_waveform {\n+\tu32\tperiod_ticks;\n+\tu32\tduty_ticks;\n+\tbool\tenabled;\n+\tbool\tinverted_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_GLOBAL_CTRL, &value);\n+\tvalue |= RP1_PWM_SET_UPDATE;\n+\tregmap_write(rp1->regmap, RP1_PWM_GLOBAL_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_CHANNEL_CTRL(pwm->hwpwm), RP1_PWM_CHANNEL_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+\tstruct rp1_pwm_waveform *wfhw = _wfhw;\n+\tu64 clk_rate = rp1->clk_rate;\n+\tu64 ticks;\n+\n+\tticks = mul_u64_u64_div_u64(wf->period_length_ns, clk_rate, NSEC_PER_SEC);\n+\n+\tif (ticks > U32_MAX)\n+\t\tticks = U32_MAX;\n+\twfhw->period_ticks = ticks;\n+\n+\tif (wf->duty_offset_ns + wf->duty_length_ns >= wf->period_length_ns) {\n+\t\tticks = mul_u64_u64_div_u64(wf->period_length_ns - wf->duty_length_ns,\n+\t\t\t\t\t    clk_rate, NSEC_PER_SEC);\n+\t\twfhw->inverted_polarity = true;\n+\t} else {\n+\t\tticks = mul_u64_u64_div_u64(wf->duty_length_ns, clk_rate, NSEC_PER_SEC);\n+\t\twfhw->inverted_polarity = false;\n+\t}\n+\n+\tif (ticks > wfhw->period_ticks)\n+\t\tticks = wfhw->period_ticks;\n+\twfhw->duty_ticks = ticks;\n+\n+\twfhw->enabled = !!wfhw->duty_ticks;\n+\n+\treturn 0;\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+\tmemset(wf, 0, sizeof(*wf));\n+\n+\tif (!wfhw->enabled)\n+\t\treturn 0;\n+\n+\twf->period_length_ns = DIV_ROUND_UP_ULL((u64)wfhw->period_ticks * NSEC_PER_SEC, clk_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\twf->duty_offset_ns = DIV_ROUND_UP_ULL((u64)wfhw->duty_ticks * NSEC_PER_SEC,\n+\t\t\t\t\t\t      clk_rate);\n+\t\tticks = wfhw->period_ticks - wfhw->duty_ticks;\n+\t\twf->duty_length_ns = DIV_ROUND_UP_ULL((u64)ticks * NSEC_PER_SEC, clk_rate);\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;\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_CHANNEL_CTRL(pwm->hwpwm), &value);\n+\tif (!wfhw->inverted_polarity)\n+\t\tvalue &= ~RP1_PWM_POLARITY;\n+\telse\n+\t\tvalue |= RP1_PWM_POLARITY;\n+\tregmap_write(rp1->regmap, RP1_PWM_CHANNEL_CTRL(pwm->hwpwm), value);\n+\n+\t/* enable/disable */\n+\tregmap_read(rp1->regmap, RP1_PWM_GLOBAL_CTRL, &value);\n+\tif (wfhw->enabled)\n+\t\tvalue |= RP1_PWM_CHANNEL_ENABLE(pwm->hwpwm);\n+\telse\n+\t\tvalue &= ~RP1_PWM_CHANNEL_ENABLE(pwm->hwpwm);\n+\tregmap_write(rp1->regmap, RP1_PWM_GLOBAL_CTRL, value);\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_GLOBAL_CTRL, &value);\n+\twfhw->enabled = !!(value & RP1_PWM_CHANNEL_ENABLE(pwm->hwpwm));\n+\n+\tregmap_read(rp1->regmap, RP1_PWM_CHANNEL_CTRL(pwm->hwpwm), &value);\n+\twfhw->inverted_polarity = !!(value & RP1_PWM_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, \"Fail 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+\trp1->clk_rate = clk_rate;\n+\n+\tchip->ops = &rp1_pwm_ops;\n+\n+\tplatform_set_drvdata(pdev, chip);\n+\n+\tret = devm_pwmchip_add(dev, 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 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: %d\\n\", 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.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":["v2","2/3"]}