{"id":2223999,"url":"http://patchwork.ozlabs.org/api/patches/2223999/?format=json","web_url":"http://patchwork.ozlabs.org/project/linux-pwm/patch/20260416134037.3160537-3-richard.genoud@bootlin.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":"<20260416134037.3160537-3-richard.genoud@bootlin.com>","list_archive_url":null,"date":"2026-04-16T13:40:35","name":"[v6,2/4] pwm: sun8i: Add H616 PWM support","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"20432d3ba60e689b41efce0039d96f42c0ace3c9","submitter":{"id":88519,"url":"http://patchwork.ozlabs.org/api/people/88519/?format=json","name":"Richard GENOUD","email":"richard.genoud@bootlin.com"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/linux-pwm/patch/20260416134037.3160537-3-richard.genoud@bootlin.com/mbox/","series":[{"id":500165,"url":"http://patchwork.ozlabs.org/api/series/500165/?format=json","web_url":"http://patchwork.ozlabs.org/project/linux-pwm/list/?series=500165","date":"2026-04-16T13:40:34","name":"Introduce Allwinner H616 PWM controller","version":6,"mbox":"http://patchwork.ozlabs.org/series/500165/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2223999/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2223999/checks/","tags":{},"related":[],"headers":{"Return-Path":"\n <linux-pwm+bounces-8618-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=bootlin.com header.i=@bootlin.com header.a=rsa-sha256\n header.s=dkim header.b=lNoljgCe;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=104.64.211.4; helo=sin.lore.kernel.org;\n envelope-from=linux-pwm+bounces-8618-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)","smtp.subspace.kernel.org;\n\tdkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com\n header.b=\"lNoljgCe\"","smtp.subspace.kernel.org;\n arc=none smtp.client-ip=185.246.84.56","smtp.subspace.kernel.org;\n dmarc=pass (p=reject dis=none) header.from=bootlin.com","smtp.subspace.kernel.org;\n spf=pass smtp.mailfrom=bootlin.com"],"Received":["from sin.lore.kernel.org (sin.lore.kernel.org [104.64.211.4])\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 4fxK2q6Vzkz1yHV\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 16 Apr 2026 23:42:59 +1000 (AEST)","from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sin.lore.kernel.org (Postfix) with ESMTP id 316843034ED2\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 16 Apr 2026 13:41:17 +0000 (UTC)","from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id CBB4F3C4542;\n\tThu, 16 Apr 2026 13:41:05 +0000 (UTC)","from smtpout-02.galae.net (smtpout-02.galae.net [185.246.84.56])\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 3608C3CAE9E;\n\tThu, 16 Apr 2026 13:41:02 +0000 (UTC)","from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233])\n\tby smtpout-02.galae.net (Postfix) with ESMTPS id E175A1A3308;\n\tThu, 16 Apr 2026 13:41:00 +0000 (UTC)","from mail.galae.net (mail.galae.net [212.83.136.155])\n\tby smtpout-01.galae.net (Postfix) with ESMTPS id B101660495;\n\tThu, 16 Apr 2026 13:41:00 +0000 (UTC)","from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon)\n with ESMTPSA id 474C31045A039;\n\tThu, 16 Apr 2026 15:40:57 +0200 (CEST)"],"ARC-Seal":"i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1776346865; cv=none;\n b=bSKlZMg0GHKGOxKFJOjqA1//K1H/dXA6COGSX1vbC8Ur8tNr1guuyw7vM7IMyha4fO6qRd0/sExPygX0I5zv2ZLvfnVMpIaVbFs/Zeao+8IQh5R1qWo3mv2Rx/zb+2aKJ6L/F8K0fupZ/NJflRtpQ9WkELWdHjzd1kOePDSKYWk=","ARC-Message-Signature":"i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1776346865; c=relaxed/simple;\n\tbh=dAjKBTA+FzGQT7qB3y3Qk6qzueqTSMihdneDV3IcqiU=;\n\th=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:\n\t MIME-Version;\n b=AYtrgSUOfoaJ3TBdvPzt0KXMBKo39EdEmK2p/hQ8NBHSHSkIgn4xSvUiE3U8Gm1Lk6wYuB8X3kFiScMQtAYtqbSrVU/EP1DnWPfMFoUCb2WKDUycK1lC0/7Ydq0XLSPgqrtYmhbCo93lyJGFbrzUW0keaQbr2d33hobV72swwTw=","ARC-Authentication-Results":"i=1; smtp.subspace.kernel.org;\n dmarc=pass (p=reject dis=none) header.from=bootlin.com;\n spf=pass smtp.mailfrom=bootlin.com;\n dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com\n header.b=lNoljgCe; arc=none smtp.client-ip=185.246.84.56","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim;\n\tt=1776346859; h=from:subject:date:message-id:to:cc:mime-version:\n\t content-transfer-encoding:in-reply-to:references;\n\tbh=MNLKgEWXwzBtue+P9H9mAlP6b2GRZjiXA/12xk2iMTQ=;\n\tb=lNoljgCeVYEwO0XQ2i2KohfIaRg4UqQhVtMVge1MRI+uzpaABz/Xi+Xk+y3T5wpIeVB+yq\n\tEO/hEJsEEVH2ak05bAkxheE295MSonBvqa0WeChopCTTSryTHXwFBdqQfxZH+Me0TmEkaZ\n\t1Xm/LdH+n5vM+DMymmRD1xw9BpWS4jFBce9mqxLH8BfM+B8Oqn0UDa3wag1JJNFUfA5wL+\n\t0FgYOEB02Aa+siZiUxIp2R6PFh77fS0B3bkPAY88uavDzjjsG29TLXzLtOSPFwPWRwUc+s\n\t258w1Ba5qxFbH4yR6dUxOOM99xXEBmpHn4/keV1DfRGJZVKR25HA3AqjiTtijw==","From":"Richard Genoud <richard.genoud@bootlin.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>, Chen-Yu Tsai <wens@kernel.org>,\n Jernej Skrabec <jernej.skrabec@gmail.com>,\n Samuel Holland <samuel@sholland.org>, Philipp Zabel <p.zabel@pengutronix.de>","Cc":"Paul Kocialkowski <paulk@sys-base.io>,\n\tThomas Petazzoni <thomas.petazzoni@bootlin.com>,\n\tJohn Stultz <jstultz@google.com>,\n\tJoao Schim <joao@schimsalabim.eu>,\n\tbigunclemax@gmail.com,\n\tlinux-pwm@vger.kernel.org,\n\tdevicetree@vger.kernel.org,\n\tlinux-arm-kernel@lists.infradead.org,\n\tlinux-sunxi@lists.linux.dev,\n\tlinux-kernel@vger.kernel.org,\n\tRichard Genoud <richard.genoud@bootlin.com>","Subject":"[PATCH v6 2/4] pwm: sun8i: Add H616 PWM support","Date":"Thu, 16 Apr 2026 15:40:35 +0200","Message-ID":"<20260416134037.3160537-3-richard.genoud@bootlin.com>","X-Mailer":"git-send-email 2.47.3","In-Reply-To":"<20260416134037.3160537-1-richard.genoud@bootlin.com>","References":"<20260416134037.3160537-1-richard.genoud@bootlin.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-Transfer-Encoding":"8bit","X-Last-TLS-Session-Version":"TLSv1.3"},"content":"Add driver for Allwinner H616 PWM controller, supporting up to 6\nchannels.\nThose channels output can be either a PWM signal output or a clock\noutput, thanks to the bypass.\n\nThe channels are paired (0/1, 2/3 and 4/5) and each pair has a\nprescaler/mux/gate.\nMoreover, each channel has its own prescaler and bypass.\n\nThe clock provider part of this driver is needed not only because the\nH616 PWM controller provides also clocks when bypass is enabled, but\nreally because pwm-clock isn't fit to handle all cases here.\npwm-clock would work if the 100MHz clock is requested, but if a lower\nclock is requested (like 24MHz), it will request a 42ns period to the\nPWM driver which will happily serve, with the 100MHz clock as input a\n25MHz frequency and a duty cycle adjustable in the range [0-4]/4,\nbecause that is a sane thing to do for a PWM.\nThe information missing is that a real clock is resquested, not a PWM.\n\nTested-by: John Stultz <jstultz@google.com>\nTested-by: Joao Schim <joao@schimsalabim.eu>\nSigned-off-by: Richard Genoud <richard.genoud@bootlin.com>\n---\n drivers/pwm/Kconfig     |  12 +\n drivers/pwm/Makefile    |   1 +\n drivers/pwm/pwm-sun8i.c | 938 ++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 951 insertions(+)\n create mode 100644 drivers/pwm/pwm-sun8i.c","diff":"diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig\nindex 6f3147518376..c4fd682860d6 100644\n--- a/drivers/pwm/Kconfig\n+++ b/drivers/pwm/Kconfig\n@@ -736,6 +736,18 @@ config PWM_SUN4I\n \t  To compile this driver as a module, choose M here: the module\n \t  will be called pwm-sun4i.\n \n+config PWM_SUN8I\n+\ttristate \"Allwinner sun8i/sun50i PWM support\"\n+\tdepends on ARCH_SUNXI || COMPILE_TEST\n+\tdepends on HAS_IOMEM && COMMON_CLK\n+\thelp\n+\t  Generic PWM framework driver for Allwinner H616 SoCs.\n+\t  It supports generic PWM, but can also provides a plain clock.\n+\t  The AC300 PHY integrated in H616 SoC needs such a clock.\n+\n+\t  To compile this driver as a module, choose M here: the module\n+\t  will be called pwm-sun8i.\n+\n config PWM_SUNPLUS\n \ttristate \"Sunplus PWM support\"\n \tdepends on ARCH_SUNPLUS || COMPILE_TEST\ndiff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile\nindex 0dc0d2b69025..ba2e0ec7fc17 100644\n--- a/drivers/pwm/Makefile\n+++ b/drivers/pwm/Makefile\n@@ -67,6 +67,7 @@ obj-$(CONFIG_PWM_STM32)\t\t+= pwm-stm32.o\n obj-$(CONFIG_PWM_STM32_LP)\t+= pwm-stm32-lp.o\n obj-$(CONFIG_PWM_STMPE)\t\t+= pwm-stmpe.o\n obj-$(CONFIG_PWM_SUN4I)\t\t+= pwm-sun4i.o\n+obj-$(CONFIG_PWM_SUN8I)\t\t+= pwm-sun8i.o\n obj-$(CONFIG_PWM_SUNPLUS)\t+= pwm-sunplus.o\n obj-$(CONFIG_PWM_TEGRA)\t\t+= pwm-tegra.o\n obj-$(CONFIG_PWM_TH1520)\t+= pwm_th1520.o\ndiff --git a/drivers/pwm/pwm-sun8i.c b/drivers/pwm/pwm-sun8i.c\nnew file mode 100644\nindex 000000000000..8f1023e3a2e5\n--- /dev/null\n+++ b/drivers/pwm/pwm-sun8i.c\n@@ -0,0 +1,938 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Driver for Allwinner sun8i Pulse Width Modulation Controller\n+ *\n+ * (C) Copyright 2025 Richard Genoud, Bootlin <richard.genoud@bootlin.com>\n+ *\n+ * Based on drivers/pwm/pwm-sun4i.c with Copyright:\n+ *\n+ * Copyright (C) 2014 Alexandre Belloni <alexandre.belloni@bootlin.com>\n+ *\n+ * Limitations:\n+ * - As the channels are paired (0/1, 2/3, 4/5), they share the same clock\n+ *   source and prescaler(div_m), but they also have their own prescaler(div_k)\n+ *   and bypass.\n+ *\n+ */\n+\n+#include <linux/bitfield.h>\n+#include <linux/bits.h>\n+#include <linux/clk.h>\n+#include <linux/clk-provider.h>\n+#include <linux/delay.h>\n+#include <linux/device.h>\n+#include <linux/err.h>\n+#include <linux/io.h>\n+#include <linux/limits.h>\n+#include <linux/math64.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/reset.h>\n+#include <linux/spinlock.h>\n+#include <linux/time.h>\n+\n+/* PWM IRQ Enable Register */\n+#define SUN8I_PWM_PIER\t\t\t0x0\n+\n+/* PWM IRQ Status Register */\n+#define SUN8I_PWM_PISR\t\t\t0x4\n+\n+/* PWM Capture IRQ Enable Register */\n+#define SUN8I_PWM_CIER\t\t\t0x10\n+\n+/* PWM Capture IRQ Status Register */\n+#define SUN8I_PWM_CISR\t\t\t0x14\n+\n+/* PWMCC Pairs Clock Configuration Registers */\n+#define SUN8I_PWM_PCCR(pair)\t\t(0x20 + ((pair) * 0x4))\n+#define SUN8I_PWM_PCCR_SRC_SHIFT\t7\n+#define SUN8I_PWM_PCCR_SRC_MASK\t\t1\n+#define SUN8I_PWM_PCCR_GATE_BIT\t\t4\n+#define SUN8I_PWM_PCCR_BYPASS_BIT(chan)\t((chan) % 2 + 5)\n+#define SUN8I_PWM_PCCR_DIV_M_SHIFT\t0\n+\n+/* PWMCC Pairs Dead Zone Control Registers */\n+#define SUN8I_PWM_PDZCR(pair)\t\t(0x30 + ((pair) * 0x4))\n+\n+/* PWM Enable Register */\n+#define SUN8I_PWM_PER\t\t\t0x40\n+#define SUN8I_PWM_ENABLE(chan)\t\tBIT(chan)\n+\n+/* PWM Capture Enable Register */\n+#define SUN8I_PWM_CER\t\t\t0x44\n+\n+/* PWM Control Register */\n+#define SUN8I_PWM_PCR(chan)\t\t(0x60 + (chan) * 0x20)\n+#define SUN8I_PWM_PCR_PRESCAL_K_SHIFT\t0\n+#define SUN8I_PWM_PCR_PRESCAL_K_WIDTH\t8\n+#define SUN8I_PWM_PCR_ACTIVE_STATE\tBIT(8)\n+\n+/* PWM Period Register */\n+#define SUN8I_PWM_PPR(chan)\t\t(0x64 + (chan) * 0x20)\n+#define SUN8I_PWM_PPR_PERIOD_MASK\tGENMASK(31, 16)\n+#define SUN8I_PWM_PPR_DUTY_MASK\t\tGENMASK(15, 0)\n+#define SUN8I_PWM_PPR_PERIOD_VALUE(reg)\t(FIELD_GET(SUN8I_PWM_PPR_PERIOD_MASK, reg) + 1)\n+#define SUN8I_PWM_PPR_DUTY_VALUE(reg)\tFIELD_GET(SUN8I_PWM_PPR_DUTY_MASK, reg)\n+#define SUN8I_PWM_PPR_PERIOD(prd)\tFIELD_PREP(SUN8I_PWM_PPR_PERIOD_MASK, (prd) - 1)\n+#define SUN8I_PWM_DUTY(dty)\t\tFIELD_PREP(SUN8I_PWM_PPR_DUTY_MASK, dty)\n+#define SUN8I_PWM_PPR_PERIOD_MAX\t(FIELD_MAX(SUN8I_PWM_PPR_PERIOD_MASK) + 1)\n+\n+/* PWM Count Register */\n+#define SUN8I_PWM_PCNTR(chan)\t\t(0x68 + (chan) * 0x20)\n+\n+/* PWM Capture Control Register */\n+#define SUN8I_PWM_CCR(chan)\t\t(0x6c + (chan) * 0x20)\n+\n+/* PWM Capture Rise Lock Register */\n+#define SUN8I_PWM_CRLR(chan)\t\t(0x70 + (chan) * 0x20)\n+\n+/* PWM Capture Fall Lock Register */\n+#define SUN8I_PWM_CFLR(chan)\t\t(0x74 + (chan) * 0x20)\n+\n+#define SUN8I_PWM_PAIR_IDX(chan)\t((chan) >> 1)\n+\n+/*\n+ * Block diagram of the PWM clock controller:\n+ *\n+ *             _____      ______      ________\n+ * OSC24M --->|     |    |      |    |        |\n+ * APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> SUN8I_PWM_clock_src_xy\n+ *            |_____|    |______|    |________|\n+ *                               ________\n+ *                              |        |\n+ *                           +->| /div_k |---> SUN8I_PWM_clock_x\n+ *                           |  |________|\n+ *                           |    ______\n+ *                           |   |      |\n+ *                           +-->| Gate |----> SUN8I_PWM_bypass_clock_x\n+ *                           |   |______|\n+ * SUN8I_PWM_clock_src_xy ---+   ________\n+ *                           |  |        |\n+ *                           +->| /div_k |---> SUN8I_PWM_clock_y\n+ *                           |  |________|\n+ *                           |    ______\n+ *                           |   |      |\n+ *                           +-->| Gate |----> SUN8I_PWM_bypass_clock_y\n+ *                               |______|\n+ *\n+ * NB: when the bypass is set, all the PWM logic is bypassed.\n+ * So, the duty cycle and polarity can't be modified (we just have a clock).\n+ * The bypass in PWM mode is used to achieve a 1/2 relative duty cycle with the\n+ * fastest clock.\n+ *\n+ * SUN8I_PWM_clock_x/y serve for the PWM purpose.\n+ * SUN8I_PWM_bypass_clock_x/y serve for the clock-provider purpose.\n+ *\n+ */\n+\n+/*\n+ * Table used for /div_m (diviser before obtaining SUN8I_PWM_clock_src_xy)\n+ * It's actually CLK_DIVIDER_POWER_OF_TWO, but limited to /256\n+ */\n+#define CLK_TABLE_DIV_M_ENTRY(i) { \\\n+\t.val = (i), .div = 1 << (i) \\\n+}\n+\n+static const struct clk_div_table clk_table_div_m[] = {\n+\tCLK_TABLE_DIV_M_ENTRY(0),\n+\tCLK_TABLE_DIV_M_ENTRY(1),\n+\tCLK_TABLE_DIV_M_ENTRY(2),\n+\tCLK_TABLE_DIV_M_ENTRY(3),\n+\tCLK_TABLE_DIV_M_ENTRY(4),\n+\tCLK_TABLE_DIV_M_ENTRY(5),\n+\tCLK_TABLE_DIV_M_ENTRY(6),\n+\tCLK_TABLE_DIV_M_ENTRY(7),\n+\tCLK_TABLE_DIV_M_ENTRY(8),\n+\t{ /* sentinel */ }\n+};\n+\n+#define SUN8I_PWM_XY_SRC_GATE(_pair, _reg)\t\t\\\n+struct clk_gate gate_xy_##_pair = {\t\t\t\\\n+\t.reg = (void *)(_reg),\t\t\t\t\\\n+\t.bit_idx = SUN8I_PWM_PCCR_GATE_BIT,\t\t\\\n+\t.hw.init = &(struct clk_init_data){\t\t\\\n+\t\t.ops = &clk_gate_ops,\t\t\t\\\n+\t}\t\t\t\t\t\t\\\n+}\n+\n+#define SUN8I_PWM_XY_SRC_MUX(_pair, _reg)\t\t\\\n+struct clk_mux mux_xy_##_pair = {\t\t\t\\\n+\t.reg = (void *)(_reg),\t\t\t\t\\\n+\t.shift = SUN8I_PWM_PCCR_SRC_SHIFT,\t\t\\\n+\t.mask = SUN8I_PWM_PCCR_SRC_MASK,\t\t\\\n+\t.flags = CLK_MUX_ROUND_CLOSEST,\t\t\t\\\n+\t.hw.init = &(struct clk_init_data){\t\t\\\n+\t\t.ops = &clk_mux_ops,\t\t\t\\\n+\t}\t\t\t\t\t\t\\\n+}\n+\n+#define SUN8I_PWM_XY_SRC_DIV(_pair, _reg)\t\t\\\n+struct clk_divider rate_xy_##_pair = {\t\t\t\\\n+\t.reg = (void *)(_reg),\t\t\t\t\\\n+\t.shift = SUN8I_PWM_PCCR_DIV_M_SHIFT,\t\t\\\n+\t.table = clk_table_div_m,\t\t\t\\\n+\t.hw.init = &(struct clk_init_data){\t\t\\\n+\t\t.ops = &clk_divider_ops,\t\t\\\n+\t}\t\t\t\t\t\t\\\n+}\n+\n+#define SUN8I_PWM_X_DIV(_idx, _reg)\t\t\t\\\n+struct clk_divider rate_x_##_idx = {\t\t\t\\\n+\t.reg = (void *)(_reg),\t\t\t\t\\\n+\t.shift = SUN8I_PWM_PCR_PRESCAL_K_SHIFT,\t\t\\\n+\t.width = SUN8I_PWM_PCR_PRESCAL_K_WIDTH,\t\t\\\n+\t.hw.init = &(struct clk_init_data){\t\t\\\n+\t\t.ops = &clk_divider_ops,\t\t\\\n+\t}\t\t\t\t\t\t\\\n+}\n+\n+#define SUN8I_PWM_X_BYPASS_GATE(_idx)\t\t\t\\\n+struct clk_gate gate_x_bypass_##_idx = {\t\t\\\n+\t.reg = (void *)SUN8I_PWM_PER,\t\t\t\\\n+\t.bit_idx = _idx,\t\t\t\t\\\n+\t.hw.init = &(struct clk_init_data){\t\t\\\n+\t\t.ops = &clk_gate_ops,\t\t\t\\\n+\t}\t\t\t\t\t\t\\\n+}\n+\n+#define SUN8I_PWM_XY_CLK_SRC(_pair, _reg)\t\t\t\\\n+\tstatic SUN8I_PWM_XY_SRC_MUX(_pair, _reg);\t\t\\\n+\tstatic SUN8I_PWM_XY_SRC_GATE(_pair, _reg);\t\t\\\n+\tstatic SUN8I_PWM_XY_SRC_DIV(_pair, _reg)\n+\n+#define SUN8I_PWM_X_CLK(_idx)\t\t\t\t\t\\\n+\tstatic SUN8I_PWM_X_DIV(_idx, SUN8I_PWM_PCR(_idx))\n+\n+#define SUN8I_PWM_X_BYPASS_CLK(_idx)\t\t\t\t\\\n+\tSUN8I_PWM_X_BYPASS_GATE(_idx)\n+\n+#define REF_CLK_XY_SRC(_pair)\t\t\t\t\t\t\\\n+\t{\t\t\t\t\t\t\t\t\\\n+\t\t.name = \"pwm-clk-src\" #_pair,\t\t\t\t\\\n+\t\t.mux_hw = &mux_xy_##_pair.hw,\t\t\t\t\\\n+\t\t.gate_hw = &gate_xy_##_pair.hw,\t\t\t\t\\\n+\t\t.rate_hw = &rate_xy_##_pair.hw,\t\t\t\t\\\n+\t}\n+\n+#define REF_CLK_X(_idx, _pair)\t\t\t\t\t\t\\\n+\t{\t\t\t\t\t\t\t\t\\\n+\t\t.name = \"pwm-clk\" #_idx,\t\t\t\t\\\n+\t\t.parent_names = (const char *[]){ \"pwm-clk-src\" #_pair }, \\\n+\t\t.num_parents = 1,\t\t\t\t\t\\\n+\t\t.rate_hw = &rate_x_##_idx.hw,\t\t\t\t\\\n+\t\t.flags = CLK_SET_RATE_PARENT,\t\t\t\t\\\n+\t}\n+\n+#define REF_CLK_BYPASS(_idx, _pair)\t\t\t\t\t\\\n+\t{\t\t\t\t\t\t\t\t\\\n+\t\t.name = \"pwm-clk-bypass\" #_idx,\t\t\t\t\\\n+\t\t.parent_names = (const char *[]){ \"pwm-clk-src\" #_pair }, \\\n+\t\t.num_parents = 1,\t\t\t\t\t\\\n+\t\t.gate_hw = &gate_x_bypass_##_idx.hw,\t\t\t\\\n+\t\t.flags = CLK_SET_RATE_PARENT,\t\t\t\t\\\n+\t}\n+\n+/*\n+ * SUN8I_PWM_clock_src_xy generation:\n+ *             _____      ______      ________\n+ * OSC24M --->|     |    |      |    |        |\n+ * APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> SUN8I_PWM_clock_src_xy\n+ *            |_____|    |______|    |________|\n+ */\n+SUN8I_PWM_XY_CLK_SRC(01, SUN8I_PWM_PCCR(0));\n+SUN8I_PWM_XY_CLK_SRC(23, SUN8I_PWM_PCCR(1));\n+SUN8I_PWM_XY_CLK_SRC(45, SUN8I_PWM_PCCR(2));\n+\n+/*\n+ * SUN8I_PWM_clock_x_div generation:\n+ *                            ________\n+ *                           |        | SUN8I_PWM_clock_x/y\n+ * SUN8I_PWM_clock_src_xy -->| /div_k |--------------->\n+ *                           |________|\n+ */\n+SUN8I_PWM_X_CLK(0);\n+SUN8I_PWM_X_CLK(1);\n+SUN8I_PWM_X_CLK(2);\n+SUN8I_PWM_X_CLK(3);\n+SUN8I_PWM_X_CLK(4);\n+SUN8I_PWM_X_CLK(5);\n+\n+/*\n+ * SUN8I_PWM_bypass_clock_xy generation:\n+ *                             ______\n+ *                            |      |\n+ * SUN8I_PWM_clock_src_xy --->| Gate |-------> SUN8I_PWM_bypass_clock_x\n+ *                            |______|\n+ *\n+ * The gate is actually SUN8I_PWM_PER register.\n+ */\n+SUN8I_PWM_X_BYPASS_CLK(0);\n+SUN8I_PWM_X_BYPASS_CLK(1);\n+SUN8I_PWM_X_BYPASS_CLK(2);\n+SUN8I_PWM_X_BYPASS_CLK(3);\n+SUN8I_PWM_X_BYPASS_CLK(4);\n+SUN8I_PWM_X_BYPASS_CLK(5);\n+\n+struct clk_pwm_data {\n+\tconst char *name;\n+\tconst char **parent_names;\n+\tunsigned int num_parents;\n+\tstruct clk_hw *mux_hw;\n+\tstruct clk_hw *rate_hw;\n+\tstruct clk_hw *gate_hw;\n+\tunsigned long flags;\n+};\n+\n+/* Indexes of REF_CLK_BYPASS and REF_CLK_XY_SRC in the array */\n+#define CLK_BYPASS_IDX(sun8i_chip, chan) ((sun8i_chip)->data->npwm + (chan))\n+#define CLK_XY_SRC_IDX(sun8i_chip, chan) \\\n+\t((sun8i_chip)->data->npwm * 2 + SUN8I_PWM_PAIR_IDX(chan))\n+static struct clk_pwm_data pwmcc_data[] = {\n+\tREF_CLK_X(0, 01),\n+\tREF_CLK_X(1, 01),\n+\tREF_CLK_X(2, 23),\n+\tREF_CLK_X(3, 23),\n+\tREF_CLK_X(4, 45),\n+\tREF_CLK_X(5, 45),\n+\tREF_CLK_BYPASS(0, 01),\n+\tREF_CLK_BYPASS(1, 01),\n+\tREF_CLK_BYPASS(2, 23),\n+\tREF_CLK_BYPASS(3, 23),\n+\tREF_CLK_BYPASS(4, 45),\n+\tREF_CLK_BYPASS(5, 45),\n+\tREF_CLK_XY_SRC(01),\n+\tREF_CLK_XY_SRC(23),\n+\tREF_CLK_XY_SRC(45),\n+\t{ /* sentinel */ }\n+};\n+\n+enum sun8i_pwm_mode {\n+\tSUN8I_PWM_MODE_NONE,\n+\tSUN8I_PWM_MODE_PWM,\n+\tSUN8I_PWM_MODE_CLK,\n+};\n+\n+struct sun8i_pwm_data {\n+\tunsigned int npwm;\n+};\n+\n+struct sun8i_pwm_channel {\n+\tstruct clk *pwm_clk;\n+\tenum sun8i_pwm_mode mode;\n+};\n+\n+struct clk_pwm_pdata {\n+\tstruct clk_hw_onecell_data *hw_data;\n+\tspinlock_t lock;\n+\tvoid __iomem *reg;\n+};\n+\n+struct sun8i_pwm_chip {\n+\tstruct clk_pwm_pdata *clk_pdata;\n+\tstruct sun8i_pwm_channel *channels;\n+\tstruct clk *bus_clk;\n+\tstruct reset_control *rst;\n+\tvoid __iomem *base;\n+\tconst struct sun8i_pwm_data *data;\n+};\n+\n+struct sun8i_pwm_waveform {\n+\tu8 enabled:1;\n+\tu8 active_state:1;\n+\tu8 bypass_en:1;\n+\tu16 duty_ticks;\n+\tu32 period_ticks;\n+\tunsigned long clk_rate;\n+};\n+\n+static inline struct sun8i_pwm_chip *sun8i_pwm_from_chip(const struct pwm_chip *chip)\n+{\n+\treturn pwmchip_get_drvdata(chip);\n+}\n+\n+static inline u32 sun8i_pwm_readl(struct sun8i_pwm_chip *sun8i_chip,\n+\t\t\t\t  unsigned long offset)\n+{\n+\treturn readl(sun8i_chip->base + offset);\n+}\n+\n+static inline void sun8i_pwm_writel(struct sun8i_pwm_chip *sun8i_chip,\n+\t\t\t\t    u32 val, unsigned long offset)\n+{\n+\twritel(val, sun8i_chip->base + offset);\n+}\n+\n+static void sun8i_pwm_set_bypass(struct sun8i_pwm_chip *sun8i_chip,\n+\t\t\t\t unsigned int idx, bool en_bypass)\n+{\n+\tunsigned long flags, reg_offset;\n+\tu32 val;\n+\n+\tspin_lock_irqsave(&sun8i_chip->clk_pdata->lock, flags);\n+\n+\treg_offset = SUN8I_PWM_PCCR(SUN8I_PWM_PAIR_IDX(idx));\n+\tval = sun8i_pwm_readl(sun8i_chip, reg_offset);\n+\tif (en_bypass)\n+\t\tval |= BIT(SUN8I_PWM_PCCR_BYPASS_BIT(idx));\n+\telse\n+\t\tval &= ~BIT(SUN8I_PWM_PCCR_BYPASS_BIT(idx));\n+\n+\tsun8i_pwm_writel(sun8i_chip, val, reg_offset);\n+\n+\tspin_unlock_irqrestore(&sun8i_chip->clk_pdata->lock, flags);\n+}\n+\n+static int sun8i_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)\n+{\n+\tstruct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);\n+\tstruct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];\n+\n+\tscoped_guard(spinlock_irqsave, &sun8i_chip->clk_pdata->lock) {\n+\t\tif (chan->mode == SUN8I_PWM_MODE_CLK)\n+\t\t\treturn -EBUSY;\n+\t\tchan->mode = SUN8I_PWM_MODE_PWM;\n+\t}\n+\n+\treturn clk_prepare_enable(chan->pwm_clk);\n+}\n+\n+static void sun8i_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)\n+{\n+\tstruct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);\n+\tstruct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];\n+\n+\tclk_disable_unprepare(chan->pwm_clk);\n+\tchan->mode = SUN8I_PWM_MODE_NONE;\n+}\n+\n+static int sun8i_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 sun8i_pwm_waveform *wfhw = _wfhw;\n+\tstruct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);\n+\tstruct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];\n+\tu32 val;\n+\n+\twfhw->clk_rate = clk_get_rate(chan->pwm_clk);\n+\n+\tval = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);\n+\twfhw->enabled = !!(SUN8I_PWM_ENABLE(pwm->hwpwm) & val);\n+\n+\tval = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(SUN8I_PWM_PAIR_IDX(pwm->hwpwm)));\n+\twfhw->bypass_en = !!(val & BIT(SUN8I_PWM_PCCR_BYPASS_BIT(pwm->hwpwm)));\n+\n+\tval = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCR(pwm->hwpwm));\n+\twfhw->active_state = !!(val & SUN8I_PWM_PCR_ACTIVE_STATE);\n+\n+\tval = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PPR(pwm->hwpwm));\n+\twfhw->duty_ticks = SUN8I_PWM_PPR_DUTY_VALUE(val);\n+\twfhw->period_ticks = SUN8I_PWM_PPR_PERIOD_VALUE(val);\n+\n+\tdev_dbg(pwmchip_parent(chip),\n+\t\t\"pwm%u: %s, bypass: %s, polarity: %s, clk_rate=%lu period_ticks=%u duty_ticks=%u\\n\",\n+\t\tpwm->hwpwm,\n+\t\twfhw->enabled ? \"enabled\" : \"disabled\",\n+\t\twfhw->bypass_en ? \"enabled\" : \"disabled\",\n+\t\twfhw->active_state ? \"normal\" : \"inversed\",\n+\t\twfhw->clk_rate, wfhw->period_ticks, wfhw->duty_ticks);\n+\n+\treturn 0;\n+}\n+\n+static int sun8i_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+\tconst struct sun8i_pwm_waveform *wfhw = _wfhw;\n+\tu64 tmp, resolution;\n+\n+\tdev_dbg(pwmchip_parent(chip),\n+\t\t\"pwm%u: %s, bypass: %s, polarity: %s, clk_rate=%lu period_ticks=%u duty_ticks=%u\\n\",\n+\t\tpwm->hwpwm,\n+\t\twfhw->enabled ? \"enabled\" : \"disabled\",\n+\t\twfhw->bypass_en ? \"enabled\" : \"disabled\",\n+\t\twfhw->active_state ? \"normal\" : \"inversed\",\n+\t\twfhw->clk_rate, wfhw->period_ticks, wfhw->duty_ticks);\n+\n+\twf->duty_offset_ns = 0;\n+\n+\tif (!wfhw->enabled || !wfhw->clk_rate) {\n+\t\twf->period_length_ns = 0;\n+\t\twf->duty_length_ns = 0;\n+\t\treturn 0;\n+\t}\n+\n+\tif (wfhw->bypass_en) {\n+\t\twf->period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC,\n+\t\t\t\t\t\t\twfhw->clk_rate);\n+\t\twf->duty_length_ns = DIV_ROUND_UP_ULL(wf->period_length_ns, 2);\n+\t\treturn 0;\n+\t}\n+\n+\ttmp = NSEC_PER_SEC * (u64)wfhw->period_ticks;\n+\twf->period_length_ns = DIV_ROUND_UP_ULL(tmp, wfhw->clk_rate);\n+\n+\ttmp = NSEC_PER_SEC * (u64)wfhw->duty_ticks;\n+\twf->duty_length_ns = DIV_ROUND_UP_ULL(tmp, wfhw->clk_rate);\n+\tif (!wfhw->active_state) {\n+\t\t/*\n+\t\t * For inverted polarity, we have to fix cases where\n+\t\t * computed duty_length_ns > requested duty_length_ns\n+\t\t * For that, we subtract the actual resolution of the PWM\n+\t\t * registers\n+\t\t */\n+\t\twf->duty_offset_ns = wf->duty_length_ns;\n+\t\twf->duty_length_ns = wf->period_length_ns - wf->duty_length_ns;\n+\n+\t\tresolution = DIV_ROUND_UP_ULL(NSEC_PER_SEC, wfhw->clk_rate);\n+\n+\t\tif (wf->duty_offset_ns >= resolution)\n+\t\t\twf->duty_offset_ns -= resolution;\n+\t}\n+\n+\tdev_dbg(pwmchip_parent(chip),\n+\t\t\"pwm%u period_length_ns=%llu duty_length_ns=%llu duty_offset_ns=%llu\\n\",\n+\t\tpwm->hwpwm, wf->period_length_ns, wf->duty_length_ns,\n+\t\twf->duty_offset_ns);\n+\n+\treturn 0;\n+}\n+\n+static int sun8i_pwm_write_waveform(struct pwm_chip *chip,\n+\t\t\t\t    struct pwm_device *pwm, const void *_wfhw)\n+{\n+\tconst struct sun8i_pwm_waveform *wfhw = _wfhw;\n+\tstruct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);\n+\tstruct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];\n+\tunsigned long flags;\n+\tu32 val;\n+\tint ret;\n+\n+\tret = clk_set_rate(chan->pwm_clk, wfhw->clk_rate);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tsun8i_pwm_set_bypass(sun8i_chip, pwm->hwpwm, wfhw->bypass_en);\n+\n+\tval = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCR(pwm->hwpwm));\n+\tif (wfhw->active_state)\n+\t\tval |= SUN8I_PWM_PCR_ACTIVE_STATE;\n+\telse\n+\t\tval &= ~SUN8I_PWM_PCR_ACTIVE_STATE;\n+\tsun8i_pwm_writel(sun8i_chip, val, SUN8I_PWM_PCR(pwm->hwpwm));\n+\n+\tval = SUN8I_PWM_DUTY(wfhw->duty_ticks);\n+\tval |= SUN8I_PWM_PPR_PERIOD(wfhw->period_ticks);\n+\tsun8i_pwm_writel(sun8i_chip, val, SUN8I_PWM_PPR(pwm->hwpwm));\n+\n+\tspin_lock_irqsave(&sun8i_chip->clk_pdata->lock, flags);\n+\n+\tval = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);\n+\tif (wfhw->enabled)\n+\t\tval |= SUN8I_PWM_ENABLE(pwm->hwpwm);\n+\telse\n+\t\tval &= ~SUN8I_PWM_ENABLE(pwm->hwpwm);\n+\tsun8i_pwm_writel(sun8i_chip, val, SUN8I_PWM_PER);\n+\n+\tspin_unlock_irqrestore(&sun8i_chip->clk_pdata->lock, flags);\n+\n+\treturn 0;\n+}\n+\n+static int sun8i_pwm_round_waveform_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 sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);\n+\tstruct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];\n+\tstruct sun8i_pwm_waveform *wfhw = _wfhw;\n+\tunsigned long max_rate;\n+\tlong calc_rate;\n+\tu64 period_ratio, double_duty_ratio, freq, duty_cycle;\n+\n+\tdev_dbg(pwmchip_parent(chip),\n+\t\t\"pwm%u period_length_ns=%llu duty_length_ns=%llu duty_offset_ns=%llu\\n\",\n+\t\tpwm->hwpwm, wf->period_length_ns, wf->duty_length_ns,\n+\t\twf->duty_offset_ns);\n+\n+\tif (wf->period_length_ns == 0) {\n+\t\twfhw->enabled = 0;\n+\t\treturn 0;\n+\t}\n+\n+\twfhw->enabled = 1;\n+\n+\tduty_cycle = wf->duty_length_ns;\n+\tif (wf->duty_length_ns + wf->duty_offset_ns < wf->period_length_ns)\n+\t\twfhw->active_state = 1;\n+\telse\n+\t\twfhw->active_state = 0;\n+\n+\tdev_dbg(pwmchip_parent(chip), \"polarity: %s\\n\",\n+\t\twfhw->active_state ? \"normal\" : \"inversed\");\n+\n+\t/*\n+\t * Lowest possible period case:\n+\t * Without bypass, the lowest possible period is when:\n+\t * duty cycle = 1 and period cycle = 2 (0x10001 in period register)\n+\t * E.g. if the input clock is 100MHz, we have a lowest period of 20ns.\n+\t * Now, with the bypass, the period register is ignored and we directly\n+\t * have the 100MHz clock as PWM output, that can act as a 10ns period\n+\t * with 5ns duty.\n+\t * So, to detect this lowest period case, just get the maximum possible\n+\t * rate from chan->pwm_clk and compare it with requested period and\n+\t * duty_cycle.\n+\t *\n+\t * But, to get the maximum possible rate, we have to use U32_MAX instead\n+\t * of (unsigned long)-1.\n+\t * This is because clk_round_rate() uses ultimately DIV_ROUND_UP_ULL()\n+\t * that in turn do_div(n,base). And base is uint32_t divisor.\n+\t */\n+\tmax_rate = clk_round_rate(chan->pwm_clk, U32_MAX);\n+\n+\tdev_dbg(pwmchip_parent(chip), \"max_rate: %ld Hz\\n\", max_rate);\n+\n+\tperiod_ratio = mul_u64_u64_div_u64(wf->period_length_ns,\n+\t\t\t\t\t   max_rate, NSEC_PER_SEC);\n+\tdouble_duty_ratio = mul_u64_u64_div_u64(duty_cycle, (u64)max_rate * 2,\n+\t\t\t\t\t\tNSEC_PER_SEC);\n+\tif (period_ratio == 1) {\n+\t\tif (double_duty_ratio == 0)\n+\t\t\t/* requested period and duty are too small */\n+\t\t\treturn -EINVAL;\n+\t\t/*\n+\t\t * If the requested period is to small to be generated by the\n+\t\t * PWM, but matches the highest clock with a\n+\t\t * duty_cycle >= period*2, just bypass the PWM logic\n+\t\t */\n+\t\tfreq = div64_u64(NSEC_PER_SEC, wf->period_length_ns);\n+\t\twfhw->bypass_en = true;\n+\t} else {\n+\t\twfhw->bypass_en = false;\n+\t\tfreq = div64_u64(NSEC_PER_SEC * (u64)SUN8I_PWM_PPR_PERIOD_MAX,\n+\t\t\t\t wf->period_length_ns);\n+\t\t/*\n+\t\t * Same remark as above, this is to prevent a value to big for\n+\t\t * clk_round_rate() to handle\n+\t\t */\n+\t\tif (freq > U32_MAX)\n+\t\t\tfreq = U32_MAX;\n+\t}\n+\n+\tdev_dbg(pwmchip_parent(chip), \"bypass: %s\\n\",\n+\t\twfhw->bypass_en ? \"enabled\" : \"disabled\");\n+\n+\tcalc_rate = clk_round_rate(chan->pwm_clk, freq);\n+\tif (calc_rate <= 0)\n+\t\treturn calc_rate ? calc_rate : -EINVAL;\n+\n+\tdev_dbg(pwmchip_parent(chip), \"calc_rate: %ld Hz\\n\", calc_rate);\n+\n+\twfhw->period_ticks = mul_u64_u64_div_u64(calc_rate,\n+\t\t\t\t\t\t wf->period_length_ns,\n+\t\t\t\t\t\t NSEC_PER_SEC);\n+\tif (wfhw->period_ticks > SUN8I_PWM_PPR_PERIOD_MAX)\n+\t\twfhw->period_ticks = SUN8I_PWM_PPR_PERIOD_MAX;\n+\n+\t/* min value in period register is 1 */\n+\tif (wfhw->period_ticks == 0)\n+\t\treturn -EINVAL;\n+\n+\twfhw->duty_ticks = mul_u64_u64_div_u64(calc_rate, duty_cycle,\n+\t\t\t\t\t       NSEC_PER_SEC);\n+\n+\tif (wfhw->duty_ticks > wfhw->period_ticks)\n+\t\twfhw->duty_ticks = wfhw->period_ticks;\n+\n+\tif (!wfhw->active_state)\n+\t\twfhw->duty_ticks = wfhw->period_ticks - wfhw->duty_ticks;\n+\n+\tdev_dbg(pwmchip_parent(chip),\n+\t\t\"pwm%u period_ticks=%u duty_cycle=%llu duty_ticks=%u\\n\",\n+\t\tpwm->hwpwm, wfhw->period_ticks, duty_cycle, wfhw->duty_ticks);\n+\n+\twfhw->clk_rate = calc_rate;\n+\n+\treturn 0;\n+}\n+\n+static const struct pwm_ops sun8i_pwm_ops = {\n+\t.request = sun8i_pwm_request,\n+\t.free = sun8i_pwm_free,\n+\t.sizeof_wfhw = sizeof(struct sun8i_pwm_waveform),\n+\t.round_waveform_tohw = sun8i_pwm_round_waveform_tohw,\n+\t.round_waveform_fromhw = sun8i_pwm_round_waveform_fromhw,\n+\t.read_waveform = sun8i_pwm_read_waveform,\n+\t.write_waveform = sun8i_pwm_write_waveform,\n+};\n+\n+static struct clk_hw *sun8i_pwm_of_clk_get(struct of_phandle_args *clkspec,\n+\t\t\t\t\t   void *data)\n+{\n+\tstruct sun8i_pwm_chip *sun8i_chip = data;\n+\tstruct clk_hw_onecell_data *hw_data = sun8i_chip->clk_pdata->hw_data;\n+\tunsigned int idx = clkspec->args[0];\n+\tstruct sun8i_pwm_channel *chan;\n+\tstruct clk_hw *ret_clk = NULL;\n+\tunsigned long flags;\n+\n+\tif (idx >= sun8i_chip->data->npwm)\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tchan = &sun8i_chip->channels[idx];\n+\n+\tspin_lock_irqsave(&sun8i_chip->clk_pdata->lock, flags);\n+\n+\tif (chan->mode == SUN8I_PWM_MODE_PWM) {\n+\t\tret_clk = ERR_PTR(-EBUSY);\n+\t} else {\n+\t\tchan->mode = SUN8I_PWM_MODE_CLK;\n+\t\tret_clk = hw_data->hws[CLK_BYPASS_IDX(sun8i_chip, idx)];\n+\t}\n+\tspin_unlock_irqrestore(&sun8i_chip->clk_pdata->lock, flags);\n+\n+\tif (IS_ERR(ret_clk))\n+\t\tgoto out;\n+\n+\tsun8i_pwm_set_bypass(sun8i_chip, idx, true);\n+out:\n+\treturn ret_clk;\n+}\n+\n+static int sun8i_add_composite_clk(struct clk_pwm_data *data,\n+\t\t\t\t   void __iomem *reg, spinlock_t *lock,\n+\t\t\t\t   struct device *dev, struct clk_hw **hw)\n+{\n+\tconst struct clk_ops *mux_ops = NULL, *gate_ops = NULL, *rate_ops = NULL;\n+\tstruct clk_hw *mux_hw = NULL, *gate_hw = NULL, *rate_hw = NULL;\n+\tstruct device_node *node = dev->of_node;\n+\n+\tif (data->mux_hw) {\n+\t\tstruct clk_mux *mux;\n+\n+\t\tmux_hw = data->mux_hw;\n+\t\tmux = to_clk_mux(mux_hw);\n+\t\tmux->lock = lock;\n+\t\tmux_ops = mux_hw->init->ops;\n+\t\tmux->reg = (uintptr_t)mux->reg + reg;\n+\t}\n+\n+\tif (data->gate_hw) {\n+\t\tstruct clk_gate *gate;\n+\n+\t\tgate_hw = data->gate_hw;\n+\t\tgate = to_clk_gate(gate_hw);\n+\t\tgate->lock = lock;\n+\t\tgate_ops = gate_hw->init->ops;\n+\t\tgate->reg = (uintptr_t)gate->reg + reg;\n+\t}\n+\n+\tif (data->rate_hw) {\n+\t\tstruct clk_divider *rate;\n+\n+\t\trate_hw = data->rate_hw;\n+\t\trate = to_clk_divider(rate_hw);\n+\t\trate_ops = rate_hw->init->ops;\n+\t\trate->lock = lock;\n+\t\trate->reg = (uintptr_t)rate->reg + reg;\n+\n+\t\tif (rate->table) {\n+\t\t\tconst struct clk_div_table *clkt;\n+\t\t\tint table_size = 0;\n+\n+\t\t\tfor (clkt = rate->table; clkt->div; clkt++)\n+\t\t\t\ttable_size++;\n+\t\t\trate->width = order_base_2(table_size);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Retrieve the parent clock names from DTS for pwm-clk-srcxy\n+\t */\n+\tif (!data->parent_names) {\n+\t\tdata->num_parents = of_clk_get_parent_count(node);\n+\t\tif (data->num_parents == 0)\n+\t\t\treturn -ENOENT;\n+\n+\t\tdata->parent_names = devm_kzalloc(dev,\n+\t\t\t\t\t\t  sizeof(*data->parent_names),\n+\t\t\t\t\t\t  GFP_KERNEL);\n+\t\tfor (unsigned int i = 0; i < data->num_parents; i++)\n+\t\t\tdata->parent_names[i] = of_clk_get_parent_name(node, i);\n+\t}\n+\n+\t*hw = clk_hw_register_composite(dev, data->name, data->parent_names,\n+\t\t\t\t\tdata->num_parents, mux_hw,\n+\t\t\t\t\tmux_ops, rate_hw, rate_ops,\n+\t\t\t\t\tgate_hw, gate_ops, data->flags);\n+\n+\treturn PTR_ERR_OR_ZERO(*hw);\n+}\n+\n+static int sun8i_pwm_init_clocks(struct platform_device *pdev,\n+\t\t\t\t struct sun8i_pwm_chip *sun8i_chip)\n+{\n+\tstruct clk_pwm_pdata *pdata;\n+\tstruct device *dev = &pdev->dev;\n+\tint num_clocks = 0;\n+\tint ret;\n+\n+\tpdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);\n+\tif (!pdata)\n+\t\treturn dev_err_probe(dev, -ENOMEM,\n+\t\t\t\t     \"Failed to allocate clk_pwm_pdata\\n\");\n+\n+\twhile (pwmcc_data[num_clocks].name)\n+\t\tnum_clocks++;\n+\n+\tpdata->hw_data = devm_kzalloc(dev, struct_size(pdata->hw_data, hws, num_clocks),\n+\t\t\t\t      GFP_KERNEL);\n+\tif (!pdata->hw_data)\n+\t\treturn dev_err_probe(dev, -ENOMEM,\n+\t\t\t\t     \"Failed to allocate hw clocks\\n\");\n+\n+\tpdata->hw_data->num = num_clocks;\n+\tpdata->reg = sun8i_chip->base;\n+\n+\tspin_lock_init(&pdata->lock);\n+\n+\tfor (int i = 0; i < num_clocks; i++) {\n+\t\tstruct clk_hw **hw = &pdata->hw_data->hws[i];\n+\n+\t\tret = sun8i_add_composite_clk(&pwmcc_data[i], pdata->reg,\n+\t\t\t\t\t      &pdata->lock, dev, hw);\n+\t\tif (ret) {\n+\t\t\tdev_err_probe(dev, ret,\n+\t\t\t\t      \"Failed to register hw clock %s\\n\",\n+\t\t\t\t      pwmcc_data[i].name);\n+\t\t\tfor (i--; i >= 0; i--)\n+\t\t\t\tclk_hw_unregister_composite(pdata->hw_data->hws[i]);\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tsun8i_chip->clk_pdata = pdata;\n+\n+\treturn 0;\n+}\n+\n+static void sun8i_pwm_unregister_clk(void *data)\n+{\n+\tstruct clk_hw_onecell_data *hw_data = data;\n+\n+\tfor (unsigned int i = 0; i < hw_data->num; i++)\n+\t\tclk_hw_unregister_composite(hw_data->hws[i]);\n+}\n+\n+static int sun8i_pwm_probe(struct platform_device *pdev)\n+{\n+\tconst struct sun8i_pwm_data *data;\n+\tstruct device *dev = &pdev->dev;\n+\tstruct sun8i_pwm_chip *sun8i_chip;\n+\tstruct pwm_chip *chip;\n+\tint ret;\n+\n+\tdata = of_device_get_match_data(dev);\n+\tif (!data)\n+\t\treturn dev_err_probe(dev, -ENODEV,\n+\t\t\t\t     \"Missing specific data structure\\n\");\n+\n+\tchip = devm_pwmchip_alloc(dev, data->npwm, sizeof(*sun8i_chip));\n+\tif (IS_ERR(chip))\n+\t\treturn dev_err_probe(dev, PTR_ERR(chip),\n+\t\t\t\t     \"Failed to allocate pwmchip\\n\");\n+\n+\tsun8i_chip = sun8i_pwm_from_chip(chip);\n+\tsun8i_chip->data = data;\n+\tsun8i_chip->base = devm_platform_ioremap_resource(pdev, 0);\n+\tif (IS_ERR(sun8i_chip->base))\n+\t\treturn dev_err_probe(dev, PTR_ERR(sun8i_chip->base),\n+\t\t\t\t     \"Failed to get PWM base address\\n\");\n+\n+\tsun8i_chip->bus_clk = devm_clk_get_enabled(dev, \"bus\");\n+\tif (IS_ERR(sun8i_chip->bus_clk))\n+\t\treturn dev_err_probe(dev, PTR_ERR(sun8i_chip->bus_clk),\n+\t\t\t\t     \"Failed to get bus clock\\n\");\n+\n+\tsun8i_chip->channels = devm_kmalloc_array(dev, data->npwm,\n+\t\t\t\t\t\t  sizeof(*(sun8i_chip->channels)),\n+\t\t\t\t\t\t  GFP_KERNEL);\n+\tif (!sun8i_chip->channels)\n+\t\treturn dev_err_probe(dev, -ENOMEM,\n+\t\t\t\t     \"Failed to allocate %d channels array\\n\",\n+\t\t\t\t     data->npwm);\n+\n+\tchip->ops = &sun8i_pwm_ops;\n+\n+\tret = sun8i_pwm_init_clocks(pdev, sun8i_chip);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tfor (unsigned int i = 0; i < data->npwm; i++) {\n+\t\tstruct sun8i_pwm_channel *chan = &sun8i_chip->channels[i];\n+\t\tstruct clk_hw **hw = &sun8i_chip->clk_pdata->hw_data->hws[i];\n+\n+\t\tchan->pwm_clk = devm_clk_hw_get_clk(dev, *hw, NULL);\n+\t\tif (IS_ERR(chan->pwm_clk)) {\n+\t\t\tret = dev_err_probe(dev, PTR_ERR(chan->pwm_clk),\n+\t\t\t\t\t    \"Failed to register PWM clock %d\\n\", i);\n+\t\t\treturn ret;\n+\t\t}\n+\t\tchan->mode = SUN8I_PWM_MODE_NONE;\n+\t}\n+\n+\tret = devm_of_clk_add_hw_provider(dev, sun8i_pwm_of_clk_get, sun8i_chip);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret, \"Failed to add HW clock provider\\n\");\n+\n+\tret = devm_add_action_or_reset(dev, sun8i_pwm_unregister_clk,\n+\t\t\t\t       sun8i_chip->clk_pdata->hw_data);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret, \"Failed to add devm action\\n\");\n+\n+\t/* Deassert reset */\n+\tsun8i_chip->rst = devm_reset_control_get_shared_deasserted(dev, NULL);\n+\tif (IS_ERR(sun8i_chip->rst))\n+\t\treturn dev_err_probe(dev, PTR_ERR(sun8i_chip->rst),\n+\t\t\t\t     \"Failed to get reset control\\n\");\n+\n+\tret = devm_pwmchip_add(dev, chip);\n+\tif (ret < 0)\n+\t\treturn dev_err_probe(dev, ret, \"Failed to add PWM chip\\n\");\n+\n+\tplatform_set_drvdata(pdev, chip);\n+\n+\treturn 0;\n+}\n+\n+static const struct sun8i_pwm_data sun50i_h616_pwm_data = {\n+\t.npwm = 6,\n+};\n+\n+static const struct of_device_id sun8i_pwm_dt_ids[] = {\n+\t{\n+\t\t.compatible = \"allwinner,sun50i-h616-pwm\",\n+\t\t.data = &sun50i_h616_pwm_data,\n+\t}, {\n+\t\t/* sentinel */\n+\t}\n+};\n+MODULE_DEVICE_TABLE(of, sun8i_pwm_dt_ids);\n+\n+static struct platform_driver sun8i_pwm_driver = {\n+\t.driver = {\n+\t\t.name = \"sun8i-pwm\",\n+\t\t.of_match_table = sun8i_pwm_dt_ids,\n+\t},\n+\t.probe = sun8i_pwm_probe,\n+};\n+module_platform_driver(sun8i_pwm_driver);\n+\n+MODULE_AUTHOR(\"Richard Genoud <richard.genoud@bootlin.com>\");\n+MODULE_DESCRIPTION(\"Allwinner sun8i PWM driver\");\n+MODULE_LICENSE(\"GPL\");\n","prefixes":["v6","2/4"]}