get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

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

{
    "id": 2175070,
    "url": "http://patchwork.ozlabs.org/api/1.0/patches/2175070/?format=api",
    "project": {
        "id": 38,
        "url": "http://patchwork.ozlabs.org/api/1.0/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": "<20251217082504.80226-3-richard.genoud@bootlin.com>",
    "date": "2025-12-17T08:25:02",
    "name": "[v2,2/4] pwm: sun50i: Add H616 PWM support",
    "commit_ref": null,
    "pull_url": null,
    "state": "changes-requested",
    "archived": false,
    "hash": "3e59aca00fdc55ddc33e6c8fcac05d6a96b848ab",
    "submitter": {
        "id": 88519,
        "url": "http://patchwork.ozlabs.org/api/1.0/people/88519/?format=api",
        "name": "Richard Genoud",
        "email": "richard.genoud@bootlin.com"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/linux-pwm/patch/20251217082504.80226-3-richard.genoud@bootlin.com/mbox/",
    "series": [
        {
            "id": 485644,
            "url": "http://patchwork.ozlabs.org/api/1.0/series/485644/?format=api",
            "date": "2025-12-17T08:25:01",
            "name": "Introduce Allwinner H616 PWM controller",
            "version": 2,
            "mbox": "http://patchwork.ozlabs.org/series/485644/mbox/"
        }
    ],
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2175070/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "\n <linux-pwm+bounces-7804-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=hWw4oPMw;\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-7804-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=\"hWw4oPMw\"",
            "smtp.subspace.kernel.org;\n arc=none smtp.client-ip=185.171.202.116",
            "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 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 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4dWRsN1n9Fz1y0P\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 17 Dec 2025 19:33:44 +1100 (AEDT)",
            "from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sea.lore.kernel.org (Postfix) with ESMTP id C3BC0307E58D\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 17 Dec 2025 08:25:38 +0000 (UTC)",
            "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id 2C13033B6D5;\n\tWed, 17 Dec 2025 08:25:33 +0000 (UTC)",
            "from smtpout-04.galae.net (smtpout-04.galae.net [185.171.202.116])\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 200F933AD9D;\n\tWed, 17 Dec 2025 08:25:29 +0000 (UTC)",
            "from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233])\n\tby smtpout-04.galae.net (Postfix) with ESMTPS id EACAAC1A594;\n\tWed, 17 Dec 2025 08:25:03 +0000 (UTC)",
            "from mail.galae.net (mail.galae.net [212.83.136.155])\n\tby smtpout-01.galae.net (Postfix) with ESMTPS id 7307E6072F;\n\tWed, 17 Dec 2025 08:25:28 +0000 (UTC)",
            "from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon)\n with ESMTPSA id 49AA3119502E4;\n\tWed, 17 Dec 2025 09:25:22 +0100 (CET)"
        ],
        "ARC-Seal": "i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1765959933; cv=none;\n b=PFk/8UJG1pkxbqKmIP236elTx5Ns4gOOQcyVJaU1kK9OBFGGMyi5T+IXDysk+jM+js8VGDdYa6ENBkUMnvbbNVI971STqJqi8AMFsOermvDzqps0ZwdoxRk4HUx0nBAUzw/j2QMDvPPUlOxU69PMq8Mmck/E+cqVNJdTYHcx4oY=",
        "ARC-Message-Signature": "i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1765959933; c=relaxed/simple;\n\tbh=kxYGY2wt0f/EzL9aOmg+KuduxMHhPfqqrzpHBkzANjU=;\n\th=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:\n\t MIME-Version;\n b=MsLw1ln+lIdR99N7qxvYVV47CtyETpDDgSVe8Pis9Kyc5aYVvsVl92XHbubAj7+hXCl8Q84NP2B52t0Ls32jTOs/n7pHy+XxZacU7nTqWtf7kQbBwiToQtDGcxykVatvt1f0xEX2AOOeIBLIxNIj5tFwyqELkVXeV6ueDUsIdmI=",
        "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=hWw4oPMw; arc=none smtp.client-ip=185.171.202.116",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim;\n\tt=1765959923; h=from:subject:date:message-id:to:cc:mime-version:\n\t content-transfer-encoding:in-reply-to:references;\n\tbh=dhe8nxJCKy9f9jwOQBwc2zcplRwYegvYT7YHn73Ieis=;\n\tb=hWw4oPMwKFFLiTAt559Zs+x0WimV4LRBDHVXX3OjaHiERG2/qA2FOVQdl86XG7OFcYFKrd\n\tOiIj55vMjHdVpfyMhNbHvGvBXanoFtEQP7iFdYBqqrAGuN1q+JyFT2wjZlnxFbGycQlN/W\n\tyxopBSQnTmfYsVpPoUFs6tz/YRFKwUFHVk8jiRl17bR1BeOjhvrfY73xXo6ZkdMWoQ+85n\n\t6JjtAF2lc59qZVuJ1bO1tc83xBLDqVfmUkY+C6Oh7qHOBKTYncjLRoCUoVGOupXbAGj/9A\n\t/I/825TeSdnpFJg8z9v9pmRzE1sVyOG3sPeZNRkcIccB6wLMKK14iQz7Sl052g==",
        "From": "Richard Genoud <richard.genoud@bootlin.com>",
        "To": "=?utf-8?q?Uwe_Kleine-K=C3=B6nig?= <u.kleine-koenig@baylibre.com>,\n Rob Herring <robh@kernel.org>, Krzysztof Kozlowski <krzk+dt@kernel.org>,\n Conor Dooley <conor+dt@kernel.org>, Chen-Yu Tsai <wens@csie.org>,\n Jernej Skrabec <jernej.skrabec@gmail.com>,\n Samuel Holland <samuel@sholland.org>, Philipp Zabel <p.zabel@pengutronix.de>",
        "Cc": "Thomas Petazzoni <thomas.petazzoni@bootlin.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>,\n\tJoao Schim <joao@schimsalabim.eu>",
        "Subject": "[PATCH v2 2/4] pwm: sun50i: Add H616 PWM support",
        "Date": "Wed, 17 Dec 2025 09:25:02 +0100",
        "Message-ID": "<20251217082504.80226-3-richard.genoud@bootlin.com>",
        "X-Mailer": "git-send-email 2.47.3",
        "In-Reply-To": "<20251217082504.80226-1-richard.genoud@bootlin.com>",
        "References": "<20251217082504.80226-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\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-sun50i-h616.c | 892 ++++++++++++++++++++++++++++++++++\n 3 files changed, 905 insertions(+)\n create mode 100644 drivers/pwm/pwm-sun50i-h616.c",
    "diff": "diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig\nindex 6f3147518376..66534e033761 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_SUN50I_H616\n+\ttristate \"Allwinner H616 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-h616.\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..a16ae9eef9e5 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_SUN50I_H616)\t+= pwm-sun50i-h616.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-sun50i-h616.c b/drivers/pwm/pwm-sun50i-h616.c\nnew file mode 100644\nindex 000000000000..b002ea5935e4\n--- /dev/null\n+++ b/drivers/pwm/pwm-sun50i-h616.c\n@@ -0,0 +1,892 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Driver for Allwinner H616 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+ * - When outputing the source clock directly, the PWM logic is bypassed and the\n+ *   currently running period is not guaranteed to be completed.\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/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+#ifndef UINT32_MAX\n+#define UINT32_MAX 0xffffffffU\n+#endif\n+\n+/* PWM IRQ Enable Register */\n+#define PWM_IER\t\t\t\t0x0\n+\n+/* PWM IRQ Status Register */\n+#define PWM_ISR\t\t\t\t0x4\n+\n+/* PWM Capture IRQ Enable Register */\n+#define PWM_CIER\t\t\t0x10\n+\n+/* PWM Capture IRQ Status Register */\n+#define PWM_CISR\t\t\t0x14\n+\n+/* PWMCC Pairs Clock Configuration Registers */\n+#define PWM_XY_CLK_CR(pair)\t\t(0x20 + ((pair) * 0x4))\n+#define PWM_XY_CLK_CR_SRC_SHIFT\t\t7\n+#define PWM_XY_CLK_CR_SRC_MASK\t\t1\n+#define PWM_XY_CLK_CR_GATE_BIT\t\t4\n+#define PWM_XY_CLK_CR_BYPASS_BIT(chan)\t((chan) % 2 + 5)\n+#define PWM_XY_CLK_CR_DIV_M_SHIFT\t0\n+\n+/* PWMCC Pairs Dead Zone Control Registers */\n+#define PWM_XY_DZ(pair)\t\t\t(0x30 + ((pair) * 0x4))\n+\n+/* PWM Enable Register */\n+#define PWM_ENR\t\t\t\t0x40\n+#define PWM_ENABLE(x)\t\t\tBIT(x)\n+\n+/* PWM Capture Enable Register */\n+#define PWM_CER\t\t\t\t0x44\n+\n+/* PWM Control Register */\n+#define PWM_CTRL_REG(chan)\t\t(0x60 + (chan) * 0x20)\n+#define PWM_CTRL_PRESCAL_K_SHIFT\t0\n+#define PWM_CTRL_PRESCAL_K_WIDTH\t8\n+#define PWM_CTRL_ACTIVE_STATE\t\tBIT(8)\n+\n+/* PWM Period Register */\n+#define PWM_PERIOD_REG(ch)\t\t(0x64 + (ch) * 0x20)\n+#define PWM_PERIOD_MASK\t\t\tGENMASK(31, 16)\n+#define PWM_DUTY_MASK\t\t\tGENMASK(15, 0)\n+#define PWM_REG_PERIOD(reg)\t\t(FIELD_GET(PWM_PERIOD_MASK, reg) + 1)\n+#define PWM_REG_DUTY(reg)\t\tFIELD_GET(PWM_DUTY_MASK, reg)\n+#define PWM_PERIOD(prd)\t\t\tFIELD_PREP(PWM_PERIOD_MASK, (prd) - 1)\n+#define PWM_DUTY(dty)\t\t\tFIELD_PREP(PWM_DUTY_MASK, dty)\n+#define PWM_PERIOD_MAX\t\t\tFIELD_MAX(PWM_PERIOD_MASK)\n+\n+\n+/* PWM Count Register */\n+#define PWM_CNT_REG(x)\t\t\t(0x68 + (x) * 0x20)\n+\n+/* PWM Capture Control Register */\n+#define PWM_CCR(x)\t\t\t(0x6c + (x) * 0x20)\n+\n+/* PWM Capture Rise Lock Register */\n+#define PWM_CRLR(x)\t\t\t(0x70 + (x) * 0x20)\n+\n+/* PWM Capture Fall Lock Register */\n+#define PWM_CFLR(x)\t\t\t(0x74 + (x) * 0x20)\n+\n+#define PWM_PAIR_IDX(chan)\t\t((chan) >> 2)\n+\n+/*\n+ * Block diagram of the PWM clock controller:\n+ *\n+ *             _____      ______      ________\n+ * OSC24M --->|     |    |      |    |        |\n+ * APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> PWM_clock_src_xy\n+ *            |_____|    |______|    |________|\n+ *                           ________\n+ *                          |        |\n+ *                       +->| /div_k |---> PWM_clock_x\n+ *                       |  |________|\n+ *                       |    ______\n+ *                       |   |      |\n+ *                       +-->| Gate |----> PWM_bypass_clock_x\n+ *                       |   |______|\n+ * PWM_clock_src_xy -----+   ________\n+ *                       |  |        |\n+ *                       +->| /div_k |---> PWM_clock_y\n+ *                       |  |________|\n+ *                       |    ______\n+ *                       |   |      |\n+ *                       +-->| Gate |----> 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 duty cycle with the fastest\n+ * clock.\n+ *\n+ * PWM_clock_x/y serve for the PWM purpose.\n+ * PWM_bypass_clock_x/y serve for the clock-provider purpose.\n+ *\n+ */\n+\n+/*\n+ * Table used for /div_m (diviser before obtaining PWM_clock_src_xy)\n+ * It's actually CLK_DIVIDER_POWER_OF_TWO, but limited to /256\n+ */\n+static const struct clk_div_table clk_table_div_m[] = {\n+\t{ .val = 0, .div = 1, },\n+\t{ .val = 1, .div = 2, },\n+\t{ .val = 2, .div = 4, },\n+\t{ .val = 3, .div = 8, },\n+\t{ .val = 4, .div = 16, },\n+\t{ .val = 5, .div = 32, },\n+\t{ .val = 6, .div = 64, },\n+\t{ .val = 7, .div = 128, },\n+\t{ .val = 8, .div = 256, },\n+\t{ .val = 0, .div = 0, }, /* last entry */\n+};\n+\n+#define PWM_XY_SRC_GATE(_pair, _reg)\t\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 = PWM_XY_CLK_CR_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 PWM_XY_SRC_MUX(_pair, _reg)\t\t\t\\\n+struct clk_mux mux_xy_##_pair = {\t\t\t\\\n+\t.reg = (void *)_reg,\t\t\t\t\\\n+\t.shift = PWM_XY_CLK_CR_SRC_SHIFT,\t\t\\\n+\t.mask = PWM_XY_CLK_CR_SRC_MASK,\t\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 PWM_XY_SRC_DIV(_pair, _reg)\t\t\t\\\n+struct clk_divider rate_xy_##_pair = {\t\t\t\\\n+\t.reg = (void *)_reg,\t\t\t\t\\\n+\t.shift = PWM_XY_CLK_CR_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 PWM_X_DIV(_idx, _reg)\t\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 = PWM_CTRL_PRESCAL_K_SHIFT,\t\t\\\n+\t.width = PWM_CTRL_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 PWM_X_BYPASS_GATE(_idx)\t\t\t\t\\\n+struct clk_gate gate_x_bypass_##_idx = {\t\t\\\n+\t.reg = (void *)PWM_ENR,\t\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 PWM_XY_CLK_SRC(_pair, _reg)\t\t\t\\\n+\tstatic PWM_XY_SRC_MUX(_pair, _reg);\t\t\\\n+\tstatic PWM_XY_SRC_GATE(_pair, _reg);\t\t\\\n+\tstatic PWM_XY_SRC_DIV(_pair, _reg)\n+\n+#define PWM_X_CLK(_idx)\t\t\t\t\t\\\n+\tstatic PWM_X_DIV(_idx, PWM_CTRL_REG(_idx))\n+\n+#define PWM_X_BYPASS_CLK(_idx)\t\t\t\t\t\t\\\n+\tPWM_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\\\n+\t}\n+\n+/*\n+ * PWM_clock_src_xy generation:\n+ *             _____      ______      ________\n+ * OSC24M --->|     |    |      |    |        |\n+ * APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> PWM_clock_src_xy\n+ *            |_____|    |______|    |________|\n+ */\n+PWM_XY_CLK_SRC(01, PWM_XY_CLK_CR(0));\n+PWM_XY_CLK_SRC(23, PWM_XY_CLK_CR(1));\n+PWM_XY_CLK_SRC(45, PWM_XY_CLK_CR(2));\n+\n+/*\n+ * PWM_clock_x_div generation:\n+ *                       ________\n+ *                      |        | PWM_clock_x/y\n+ * PWM_clock_src_xy --->| /div_k |--------------->\n+ *                      |________|\n+ */\n+PWM_X_CLK(0);\n+PWM_X_CLK(1);\n+PWM_X_CLK(2);\n+PWM_X_CLK(3);\n+PWM_X_CLK(4);\n+PWM_X_CLK(5);\n+\n+/*\n+ * PWM_bypass_clock_xy generation:\n+ *                        ______\n+ *                       |      |\n+ * PWM_clock_src_xy ---->| Gate |-------> PWM_bypass_clock_x\n+ *                       |______|\n+ *\n+ * The gate is actually PWM_ENR register.\n+ */\n+PWM_X_BYPASS_CLK(0);\n+PWM_X_BYPASS_CLK(1);\n+PWM_X_BYPASS_CLK(2);\n+PWM_X_BYPASS_CLK(3);\n+PWM_X_BYPASS_CLK(4);\n+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+#define CLK_BYPASS(h616chip, ch) ((h616chip)->data->npwm + (ch))\n+#define CLK_XY_SRC_IDX(h616chip, ch) ((h616chip)->data->npwm * 2 + ((ch) >> 1))\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 h616_pwm_mode {\n+\tH616_PWM_MODE_NONE,\n+\tH616_PWM_MODE_PWM,\n+\tH616_PWM_MODE_CLK,\n+};\n+\n+struct h616_pwm_data {\n+\tunsigned int npwm;\n+};\n+\n+struct h616_pwm_channel {\n+\tstruct clk *pwm_clk;\n+\tunsigned long rate;\n+\tunsigned int entire_cycles;\n+\tunsigned int active_cycles;\n+\tenum h616_pwm_mode mode;\n+\tbool bypass;\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 h616_pwm_chip {\n+\tstruct clk_pwm_pdata *clk_pdata;\n+\tstruct h616_pwm_channel *channels;\n+\tstruct clk *bus_clk;\n+\tstruct reset_control *rst;\n+\tvoid __iomem *base;\n+\tconst struct h616_pwm_data *data;\n+};\n+\n+static inline struct h616_pwm_chip *to_h616_pwm_chip(struct pwm_chip *chip)\n+{\n+\treturn pwmchip_get_drvdata(chip);\n+}\n+\n+static inline u32 h616_pwm_readl(struct h616_pwm_chip *h616chip,\n+\t\t\t\t unsigned long offset)\n+{\n+\treturn readl(h616chip->base + offset);\n+}\n+\n+static inline void h616_pwm_writel(struct h616_pwm_chip *h616chip,\n+\t\t\t\t   u32 val, unsigned long offset)\n+{\n+\twritel(val, h616chip->base + offset);\n+}\n+\n+static void h616_pwm_set_bypass(struct h616_pwm_chip *h616chip, unsigned int idx,\n+\t\t\t\tbool en_bypass)\n+{\n+\tunsigned long flags;\n+\tu32 val;\n+\n+\tspin_lock_irqsave(&h616chip->clk_pdata->lock, flags);\n+\n+\tval = h616_pwm_readl(h616chip, PWM_XY_CLK_CR(PWM_PAIR_IDX(idx)));\n+\tif (en_bypass)\n+\t\tval |= BIT(PWM_XY_CLK_CR_BYPASS_BIT(idx));\n+\telse\n+\t\tval &= ~BIT(PWM_XY_CLK_CR_BYPASS_BIT(idx));\n+\n+\th616_pwm_writel(h616chip, val, PWM_XY_CLK_CR(PWM_PAIR_IDX(idx)));\n+\n+\tspin_unlock_irqrestore(&h616chip->clk_pdata->lock, flags);\n+}\n+\n+static int h616_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)\n+{\n+\tstruct h616_pwm_chip *h616chip = to_h616_pwm_chip(chip);\n+\tstruct h616_pwm_channel *chan = &h616chip->channels[pwm->hwpwm];\n+\tstruct device *dev = pwmchip_parent(chip);\n+\tunsigned long flags;\n+\tint ret = 0;\n+\n+\tspin_lock_irqsave(&h616chip->clk_pdata->lock, flags);\n+\n+\tif (chan->mode == H616_PWM_MODE_CLK)\n+\t\tret = -EBUSY;\n+\telse\n+\t\tchan->mode = H616_PWM_MODE_PWM;\n+\n+\tspin_unlock_irqrestore(&h616chip->clk_pdata->lock, flags);\n+\tif (ret)\n+\t\tgoto out;\n+\n+\tret = clk_prepare_enable(chan->pwm_clk);\n+\tif (ret < 0) {\n+\t\tdev_err(dev, \"Failed to enable clock %s: %d\\n\",\n+\t\t\t__clk_get_name(chan->pwm_clk), ret);\n+\t}\n+out:\n+\treturn ret;\n+}\n+\n+static void h616_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)\n+{\n+\tstruct h616_pwm_chip *h616chip = to_h616_pwm_chip(chip);\n+\tstruct h616_pwm_channel *chan = &h616chip->channels[pwm->hwpwm];\n+\n+\tclk_disable_unprepare(chan->pwm_clk);\n+\tchan->mode = H616_PWM_MODE_NONE;\n+}\n+\n+static int h616_pwm_get_state(struct pwm_chip *chip,\n+\t\t\t      struct pwm_device *pwm,\n+\t\t\t      struct pwm_state *state)\n+{\n+\tstruct h616_pwm_chip *h616chip = to_h616_pwm_chip(chip);\n+\tstruct h616_pwm_channel *chan = &h616chip->channels[pwm->hwpwm];\n+\tu64 clk_rate, tmp;\n+\tu32 val;\n+\n+\tclk_rate = clk_get_rate(chan->pwm_clk);\n+\tif (!clk_rate)\n+\t\treturn -EINVAL;\n+\n+\tval = h616_pwm_readl(h616chip, PWM_ENR);\n+\tstate->enabled = !!(PWM_ENABLE(pwm->hwpwm) & val);\n+\n+\tval = h616_pwm_readl(h616chip, PWM_XY_CLK_CR(PWM_PAIR_IDX(pwm->hwpwm)));\n+\tif (val & BIT(PWM_XY_CLK_CR_BYPASS_BIT(pwm->hwpwm))) {\n+\t\t/*\n+\t\t * When bypass is enabled, the PWM logic is inactive.\n+\t\t * The PWM_clock_src_xy is directly routed to PWM_clock_x\n+\t\t */\n+\t\tstate->period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, clk_rate);\n+\t\tstate->duty_cycle = DIV_ROUND_UP_ULL(state->period, 2);\n+\t\tstate->polarity = PWM_POLARITY_NORMAL;\n+\t\treturn 0;\n+\t}\n+\n+\tstate->enabled &= !!(BIT(PWM_XY_CLK_CR_GATE_BIT) & val);\n+\n+\tval = h616_pwm_readl(h616chip, PWM_CTRL_REG(pwm->hwpwm));\n+\tif (val & PWM_CTRL_ACTIVE_STATE)\n+\t\tstate->polarity = PWM_POLARITY_NORMAL;\n+\telse\n+\t\tstate->polarity = PWM_POLARITY_INVERSED;\n+\n+\tval = h616_pwm_readl(h616chip, PWM_PERIOD_REG(pwm->hwpwm));\n+\n+\ttmp = NSEC_PER_SEC * PWM_REG_DUTY(val);\n+\tstate->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);\n+\n+\ttmp = NSEC_PER_SEC * PWM_REG_PERIOD(val);\n+\tstate->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);\n+\n+\treturn 0;\n+}\n+\n+static int h616_pwm_calc(struct pwm_chip *chip, unsigned int idx,\n+\t\t\t const struct pwm_state *state)\n+{\n+\tstruct h616_pwm_chip *h616chip = to_h616_pwm_chip(chip);\n+\tstruct h616_pwm_channel *chan = &h616chip->channels[idx];\n+\tunsigned int cnt, duty_cnt;\n+\tunsigned long max_rate;\n+\tlong calc_rate;\n+\tu64 duty, period, freq;\n+\n+\tduty = state->duty_cycle;\n+\tperiod = state->period;\n+\n+\tmax_rate = clk_round_rate(chan->pwm_clk, UINT32_MAX);\n+\n+\tdev_dbg(pwmchip_parent(chip), \"max_rate: %ld Hz\\n\", max_rate);\n+\n+\tif ((period * max_rate >= NSEC_PER_SEC) &&\n+\t    (period * max_rate < 2 * NSEC_PER_SEC) &&\n+\t    (duty * max_rate * 2 >= NSEC_PER_SEC)) {\n+\t\t/*\n+\t\t * If the requested period is to small to be generated by the\n+\t\t * PWM, we can just select the highest clock and bypass the\n+\t\t * PWM logic\n+\t\t */\n+\t\tdev_dbg(pwmchip_parent(chip), \"Setting bypass (period=%lld)\\n\",\n+\t\t\tperiod);\n+\t\tfreq = div64_u64(NSEC_PER_SEC, period);\n+\t\tchan->bypass = true;\n+\t\tduty = period / 2;\n+\t} else {\n+\t\tchan->bypass = false;\n+\t\tfreq = div64_u64(NSEC_PER_SEC * (u64)PWM_PERIOD_MAX, period);\n+\t\tif (freq > UINT32_MAX)\n+\t\t\tfreq = UINT32_MAX;\n+\t}\n+\n+\tcalc_rate = clk_round_rate(chan->pwm_clk, freq);\n+\tif (calc_rate <= 0) {\n+\t\tdev_err(pwmchip_parent(chip),\n+\t\t\t\"Invalid source clock frequency %llu\\n\", freq);\n+\t\treturn calc_rate ? calc_rate : -EINVAL;\n+\t}\n+\n+\tdev_dbg(pwmchip_parent(chip), \"calc_rate: %ld Hz\\n\", calc_rate);\n+\n+\tcnt = mul_u64_u64_div_u64(calc_rate, period, NSEC_PER_SEC);\n+\tif ((cnt == 0) || (cnt > PWM_PERIOD_MAX)) {\n+\t\tdev_err(pwmchip_parent(chip), \"Period out of range\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tduty_cnt = mul_u64_u64_div_u64(calc_rate, duty, NSEC_PER_SEC);\n+\n+\tif (duty_cnt >= cnt)\n+\t\tduty_cnt = cnt - 1;\n+\n+\tdev_dbg(pwmchip_parent(chip), \"period=%llu cnt=%u duty=%llu duty_cnt=%u\\n\",\n+\t\tperiod, cnt, duty, duty_cnt);\n+\n+\tchan->active_cycles = duty_cnt;\n+\tchan->entire_cycles = cnt;\n+\n+\tchan->rate = calc_rate;\n+\n+\treturn 0;\n+}\n+\n+static int h616_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,\n+\t\t\t  const struct pwm_state *state)\n+{\n+\tstruct h616_pwm_chip *h616chip = to_h616_pwm_chip(chip);\n+\tstruct h616_pwm_channel *chan = &h616chip->channels[pwm->hwpwm];\n+\tstruct pwm_state cstate;\n+\tunsigned long flags;\n+\tu32 val;\n+\tint ret;\n+\n+\tret = h616_pwm_calc(chip, pwm->hwpwm, state);\n+\tif (ret) {\n+\t\tdev_err(pwmchip_parent(chip), \"period exceeds the maximum value\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\tpwm_get_state(pwm, &cstate);\n+\n+\tret = clk_set_rate(chan->pwm_clk, chan->rate);\n+\tif (ret) {\n+\t\tdev_err(pwmchip_parent(chip), \"failed to set PWM %d clock rate to %lu\\n\",\n+\t\t\tpwm->hwpwm, chan->rate);\n+\t\treturn ret;\n+\t}\n+\n+\th616_pwm_set_bypass(h616chip, pwm->hwpwm, chan->bypass);\n+\n+\t/*\n+\t * If bypass is set, the PWM logic (polarity, duty) can't be applied\n+\t */\n+\n+\tif (chan->bypass && (state->polarity == PWM_POLARITY_INVERSED)) {\n+\t\tdev_warn(pwmchip_parent(chip),\n+\t\t\t \"Can't set inversed polarity with bypass enabled\\n\");\n+\t} else {\n+\t\tval = h616_pwm_readl(h616chip, PWM_CTRL_REG(pwm->hwpwm));\n+\t\tval &= ~PWM_CTRL_ACTIVE_STATE;\n+\t\tif (state->polarity == PWM_POLARITY_NORMAL)\n+\t\t\tval |= PWM_CTRL_ACTIVE_STATE;\n+\t\th616_pwm_writel(h616chip, val, PWM_CTRL_REG(pwm->hwpwm));\n+\t}\n+\n+\tif (chan->bypass && (state->duty_cycle * 2 != state->period)) {\n+\t\tdev_warn(pwmchip_parent(chip),\n+\t\t\t \"Can't set a duty cycle with bypass enabled\\n\");\n+\t}\n+\n+\tif (!chan->bypass) {\n+\t\tval = PWM_DUTY(chan->active_cycles);\n+\t\tval |= PWM_PERIOD(chan->entire_cycles);\n+\t\th616_pwm_writel(h616chip, val, PWM_PERIOD_REG(pwm->hwpwm));\n+\t}\n+\n+\tif (state->enabled == cstate.enabled)\n+\t\treturn 0;\n+\n+\tif (cstate.enabled) {\n+\t\tunsigned long delay_us;\n+\n+\t\t/*\n+\t\t * We need a full period to elapse before\n+\t\t * disabling the channel.\n+\t\t */\n+\t\tdelay_us = DIV_ROUND_UP_ULL(cstate.period, NSEC_PER_USEC);\n+\t\tfsleep(delay_us);\n+\t}\n+\n+\tspin_lock_irqsave(&h616chip->clk_pdata->lock, flags);\n+\n+\tval = h616_pwm_readl(h616chip, PWM_ENR);\n+\tif (state->enabled)\n+\t\tval |= PWM_ENABLE(pwm->hwpwm);\n+\telse\n+\t\tval &= ~PWM_ENABLE(pwm->hwpwm);\n+\th616_pwm_writel(h616chip, val, PWM_ENR);\n+\n+\tspin_unlock_irqrestore(&h616chip->clk_pdata->lock, flags);\n+\n+\treturn 0;\n+}\n+\n+static const struct pwm_ops h616_pwm_ops = {\n+\t.apply = h616_pwm_apply,\n+\t.get_state = h616_pwm_get_state,\n+\t.request = h616_pwm_request,\n+\t.free = h616_pwm_free,\n+};\n+\n+static struct clk_hw *h616_pwm_of_clk_get(struct of_phandle_args *clkspec,\n+\t\t\t\t\t  void *data)\n+{\n+\tstruct h616_pwm_chip *h616chip = data;\n+\tstruct clk_hw_onecell_data *hw_data = h616chip->clk_pdata->hw_data;\n+\tunsigned int idx = clkspec->args[0];\n+\tstruct h616_pwm_channel *chan;\n+\tstruct clk_hw *ret_clk = NULL;\n+\tunsigned long flags;\n+\n+\tif (idx >= h616chip->data->npwm)\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tchan = &h616chip->channels[idx];\n+\n+\tspin_lock_irqsave(&h616chip->clk_pdata->lock, flags);\n+\n+\tif (chan->mode == H616_PWM_MODE_PWM) {\n+\t\tret_clk = ERR_PTR(-EBUSY);\n+\t} else {\n+\t\tchan->mode = H616_PWM_MODE_CLK;\n+\t\tret_clk = hw_data->hws[CLK_BYPASS(h616chip, idx)];\n+\t}\n+\tspin_unlock_irqrestore(&h616chip->clk_pdata->lock, flags);\n+\n+\tif (IS_ERR(ret_clk))\n+\t\tgoto out;\n+\n+\tchan->bypass = true;\n+\th616_pwm_set_bypass(h616chip, idx, chan->bypass);\n+out:\n+\treturn ret_clk;\n+}\n+\n+static int h616_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 = (u64)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 = (u64)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 = (u64)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 h616_pwm_init_clocks(struct platform_device *pdev,\n+\t\t\t\tstruct h616_pwm_chip *h616chip)\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 -ENOMEM;\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 -ENOMEM;\n+\n+\tpdata->hw_data->num = num_clocks;\n+\tpdata->reg = h616chip->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 = h616_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+\th616chip->clk_pdata = pdata;\n+\n+\treturn 0;\n+}\n+\n+static int h616_pwm_probe(struct platform_device *pdev)\n+{\n+\tconst struct h616_pwm_data *data;\n+\tstruct device *dev = &pdev->dev;\n+\tstruct h616_pwm_chip *h616chip;\n+\tstruct pwm_chip *chip;\n+\tint ret;\n+\n+\tdata = of_device_get_match_data(dev);\n+\tif (!data)\n+\t\treturn -ENODEV;\n+\n+\tchip = devm_pwmchip_alloc(dev, data->npwm, sizeof(*h616chip));\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+\th616chip = to_h616_pwm_chip(chip);\n+\th616chip->data = data;\n+\th616chip->base = devm_platform_ioremap_resource(pdev, 0);\n+\tif (IS_ERR(h616chip->base))\n+\t\treturn dev_err_probe(dev, PTR_ERR(h616chip->base),\n+\t\t\t\t     \"Failed to get PWM base address\\n\");\n+\n+\th616chip->bus_clk = devm_clk_get_enabled(dev, \"bus\");\n+\tif (IS_ERR(h616chip->bus_clk))\n+\t\treturn dev_err_probe(dev, PTR_ERR(h616chip->bus_clk),\n+\t\t\t\t     \"Failed to get bus clock\\n\");\n+\n+\th616chip->channels = devm_kmalloc_array(dev, data->npwm,\n+\t\t\t\t\t\tsizeof(*(h616chip->channels)),\n+\t\t\t\t\t\tGFP_KERNEL);\n+\tif (!h616chip->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+\th616chip->rst = devm_reset_control_get_shared(dev, NULL);\n+\tif (IS_ERR(h616chip->rst))\n+\t\treturn dev_err_probe(dev, PTR_ERR(h616chip->rst),\n+\t\t\t\t     \"Failed to get reset control\\n\");\n+\n+\tchip->ops = &h616_pwm_ops;\n+\n+\tret = h616_pwm_init_clocks(pdev, h616chip);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret, \"Failed to initialize clocks\\n\");\n+\n+\tfor (unsigned int i = 0; i < data->npwm; i++) {\n+\t\tstruct h616_pwm_channel *chan = &h616chip->channels[i];\n+\t\tstruct clk_hw **hw = &h616chip->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\tgoto err_get_clk;\n+\t\t}\n+\t\tchan->mode = H616_PWM_MODE_NONE;\n+\t}\n+\n+\tret = devm_of_clk_add_hw_provider(dev, h616_pwm_of_clk_get, h616chip);\n+\tif (ret) {\n+\t\tdev_err_probe(dev, ret, \"Failed to add HW clock provider\\n\");\n+\t\tgoto err_add_clk_provider;\n+\t}\n+\n+\t/* Deassert reset */\n+\tret = reset_control_deassert(h616chip->rst);\n+\tif (ret) {\n+\t\tdev_err_probe(dev, ret, \"Cannot deassert reset control\\n\");\n+\t\tgoto err_ctrl_deassert;\n+\t}\n+\n+\tret = pwmchip_add(chip);\n+\tif (ret < 0) {\n+\t\tdev_err_probe(dev, ret, \"Failed to add PWM chip\\n\");\n+\t\tgoto err_pwm_add;\n+\t}\n+\n+\tplatform_set_drvdata(pdev, chip);\n+\n+\treturn 0;\n+\n+err_pwm_add:\n+\treset_control_assert(h616chip->rst);\n+\n+err_ctrl_deassert:\n+err_add_clk_provider:\n+err_get_clk:\n+\tfor (int i = 0; i < h616chip->clk_pdata->hw_data->num; i++)\n+\t\tclk_hw_unregister_composite(h616chip->clk_pdata->hw_data->hws[i]);\n+\n+\treturn ret;\n+}\n+\n+static const struct h616_pwm_data sun50i_h616_pwm_data = {\n+\t.npwm = 6,\n+};\n+\n+static const struct of_device_id h616_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, h616_pwm_dt_ids);\n+\n+\n+static struct platform_driver h616_pwm_driver = {\n+\t.driver = {\n+\t\t.name = \"h616-pwm\",\n+\t\t.of_match_table = h616_pwm_dt_ids,\n+\t},\n+\t.probe = h616_pwm_probe,\n+};\n+module_platform_driver(h616_pwm_driver);\n+\n+MODULE_AUTHOR(\"Richard Genoud <richard.genoud@bootlin.com>\");\n+MODULE_DESCRIPTION(\"Allwinner H616 PWM driver\");\n+MODULE_LICENSE(\"GPL\");\n",
    "prefixes": [
        "v2",
        "2/4"
    ]
}