]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
qualcommbe: enable pwm support for linux 6.18 21506/head
authorAlexandru Gagniuc <mr.nuke.me@gmail.com>
Sun, 19 Apr 2026 00:37:10 +0000 (19:37 -0500)
committerRobert Marko <robimarko@gmail.com>
Thu, 28 May 2026 08:15:20 +0000 (10:15 +0200)
The 6.18 kernel port and PWM patches were developed independently. the
initial 6.18 port did not include the PWM patches, so add them now.

Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/21506
Signed-off-by: Robert Marko <robimarko@gmail.com>
target/linux/qualcommbe/config-6.18
target/linux/qualcommbe/patches-6.18/0400-dt-bindings-pwm-add-IPQ6018-binding.patch [new file with mode: 0644]
target/linux/qualcommbe/patches-6.18/0401-pwm-driver-for-qualcomm-ipq6018-pwm-block.patch [new file with mode: 0644]
target/linux/qualcommbe/patches-6.18/0402-arm64-dts-qcom-ipq9574-add-pwm-node.patch [new file with mode: 0644]

index 078d2f90c15f0d1a51bc8058c3c72738fc4d272f..e1fea4b6c2bf259f13d0e7be3802bbd685703132 100644 (file)
@@ -431,6 +431,8 @@ CONFIG_POWER_RESET=y
 CONFIG_POWER_SUPPLY=y
 CONFIG_PRINTK_TIME=y
 CONFIG_PTP_1588_CLOCK_OPTIONAL=y
+CONFIG_PWM=y
+CONFIG_PWM_IPQ=y
 CONFIG_QCA807X_PHY=y
 CONFIG_QCA808X_PHY=y
 # CONFIG_QCM_DISPCC_2290 is not set
diff --git a/target/linux/qualcommbe/patches-6.18/0400-dt-bindings-pwm-add-IPQ6018-binding.patch b/target/linux/qualcommbe/patches-6.18/0400-dt-bindings-pwm-add-IPQ6018-binding.patch
new file mode 100644 (file)
index 0000000..d6c647d
--- /dev/null
@@ -0,0 +1,75 @@
+From b07d37a8ee4b8e07551033fdb315bb729e5781fc Mon Sep 17 00:00:00 2001
+From: Devi Priya <quic_devipriy@quicinc.com>
+Date: Fri, 28 Nov 2025 14:29:13 +0400
+Subject: [PATCH] dt-bindings: pwm: add IPQ6018 binding
+
+DT binding for the PWM block in Qualcomm IPQ6018 SoC.
+
+Reviewed-by: Bjorn Andersson <bjorn.andersson@linaro.org>
+Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Co-developed-by: Baruch Siach <baruch.siach@siklu.com>
+Signed-off-by: Baruch Siach <baruch.siach@siklu.com>
+Signed-off-by: Devi Priya <quic_devipriy@quicinc.com>
+Signed-off-by: George Moussalem <george.moussalem@outlook.com>
+---
+ .../bindings/pwm/qcom,ipq6018-pwm.yaml        | 51 +++++++++++++++++++
+ 1 file changed, 51 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/pwm/qcom,ipq6018-pwm.yaml
+
+diff --git a/Documentation/devicetree/bindings/pwm/qcom,ipq6018-pwm.yaml b/Documentation/devicetree/bindings/pwm/qcom,ipq6018-pwm.yaml
+new file mode 100644
+index 0000000000000..f9f1f652e7527
+--- /dev/null
++++ b/Documentation/devicetree/bindings/pwm/qcom,ipq6018-pwm.yaml
+@@ -0,0 +1,51 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/pwm/qcom,ipq6018-pwm.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Qualcomm IPQ6018 PWM controller
++
++maintainers:
++  - George Moussalem <george.moussalem@outlook.com>
++
++properties:
++  compatible:
++    oneOf:
++      - items:
++          - enum:
++              - qcom,ipq5018-pwm
++              - qcom,ipq5332-pwm
++              - qcom,ipq9574-pwm
++          - const: qcom,ipq6018-pwm
++      - const: qcom,ipq6018-pwm
++
++  reg:
++    maxItems: 1
++
++  clocks:
++    maxItems: 1
++
++  "#pwm-cells":
++    const: 3
++
++required:
++  - compatible
++  - reg
++  - clocks
++  - "#pwm-cells"
++
++additionalProperties: false
++
++examples:
++  - |
++    #include <dt-bindings/clock/qcom,gcc-ipq6018.h>
++
++    pwm: pwm@1941010 {
++        compatible = "qcom,ipq6018-pwm";
++        reg = <0x01941010 0x20>;
++        clocks = <&gcc GCC_ADSS_PWM_CLK>;
++        assigned-clocks = <&gcc GCC_ADSS_PWM_CLK>;
++        assigned-clock-rates = <100000000>;
++        #pwm-cells = <3>;
++    };
diff --git a/target/linux/qualcommbe/patches-6.18/0401-pwm-driver-for-qualcomm-ipq6018-pwm-block.patch b/target/linux/qualcommbe/patches-6.18/0401-pwm-driver-for-qualcomm-ipq6018-pwm-block.patch
new file mode 100644 (file)
index 0000000..d793b7d
--- /dev/null
@@ -0,0 +1,303 @@
+From 2728124b7d4e32c32ffdc3f9091ccf8c97179f93 Mon Sep 17 00:00:00 2001
+From: Devi Priya <quic_devipriy@quicinc.com>
+Date: Wed, 4 Feb 2026 15:25:08 +0400
+Subject: [PATCH] pwm: driver for qualcomm ipq6018 pwm block
+
+Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
+driver from downstream Codeaurora kernel tree. Removed support for older
+(V1) variants because I have no access to that hardware.
+
+Tested on IPQ5018 and IPQ6010 based hardware.
+
+Co-developed-by: Baruch Siach <baruch.siach@siklu.com>
+Signed-off-by: Baruch Siach <baruch.siach@siklu.com>
+Signed-off-by: Devi Priya <quic_devipriy@quicinc.com>
+Reviewed-by: Bjorn Andersson <andersson@kernel.org>
+Signed-off-by: George Moussalem <george.moussalem@outlook.com>
+---
+ drivers/pwm/Kconfig   |  12 +++
+ drivers/pwm/Makefile  |   1 +
+ drivers/pwm/pwm-ipq.c | 239 ++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 252 insertions(+)
+ create mode 100644 drivers/pwm/pwm-ipq.c
+
+diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
+index c2fd3f4b62d9e..33ac49251b3cc 100644
+--- a/drivers/pwm/Kconfig
++++ b/drivers/pwm/Kconfig
+@@ -337,6 +337,18 @@ config PWM_INTEL_LGM
+         To compile this driver as a module, choose M here: the module
+         will be called pwm-intel-lgm.
++config PWM_IPQ
++      tristate "IPQ PWM support"
++      depends on ARCH_QCOM || COMPILE_TEST
++      depends on HAVE_CLK && HAS_IOMEM
++      help
++        Generic PWM framework driver for IPQ PWM block which supports
++        4 pwm channels. Each of the these channels can be configured
++        independent of each other.
++
++        To compile this driver as a module, choose M here: the module
++        will be called pwm-ipq.
++
+ config PWM_IQS620A
+       tristate "Azoteq IQS620A PWM support"
+       depends on MFD_IQS62X || COMPILE_TEST
+diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
+index dfa8b4966ee19..74e07f654d43d 100644
+--- a/drivers/pwm/Makefile
++++ b/drivers/pwm/Makefile
+@@ -28,6 +28,7 @@ obj-$(CONFIG_PWM_IMX1)               += pwm-imx1.o
+ obj-$(CONFIG_PWM_IMX27)               += pwm-imx27.o
+ obj-$(CONFIG_PWM_IMX_TPM)     += pwm-imx-tpm.o
+ obj-$(CONFIG_PWM_INTEL_LGM)   += pwm-intel-lgm.o
++obj-$(CONFIG_PWM_IPQ)         += pwm-ipq.o
+ obj-$(CONFIG_PWM_IQS620A)     += pwm-iqs620a.o
+ obj-$(CONFIG_PWM_JZ4740)      += pwm-jz4740.o
+ obj-$(CONFIG_PWM_KEEMBAY)     += pwm-keembay.o
+diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
+new file mode 100644
+index 0000000000000..b944ecb456d59
+--- /dev/null
++++ b/drivers/pwm/pwm-ipq.c
+@@ -0,0 +1,239 @@
++// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
++/*
++ * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
++ *
++ * Hardware notes / Limitations:
++ * - The PWM controller has no publicly available datasheet.
++ * - Each of the four channels is programmed via two 32-bit registers
++ *   (REG0 and REG1 at 8-byte stride).
++ * - Period and duty-cycle reconfiguration is fully atomic: new divider,
++ *   pre-divider, and high-duration values are latched by setting the
++ *   UPDATE bit (bit 30 in REG1). The hardware applies the new settings
++ *   at the beginning of the next period without disabling the output,
++ *   so the currently running period is always completed.
++ * - On disable (clearing the ENABLE bit 31 in REG1), the hardware
++ *   finishes the current period before stopping the output. The pin
++ *   is then driven to the inactive (low) level.
++ * - Upon disabling, the hardware resets the pre-divider (PRE_DIV) and divider
++ *   fields (PWM_DIV) in REG0 and REG1 to 0x0000 and 0x0001 respectively.
++ * - Only normal polarity is supported.
++ */
++
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/pwm.h>
++#include <linux/clk.h>
++#include <linux/io.h>
++#include <linux/of.h>
++#include <linux/math64.h>
++#include <linux/of_device.h>
++#include <linux/bitfield.h>
++#include <linux/units.h>
++
++/* The frequency range supported is 1 Hz to clock rate */
++#define IPQ_PWM_MAX_PERIOD_NS ((u64)NSEC_PER_SEC)
++
++/*
++ * Two 32-bit registers for each PWM: REG0, and REG1.
++ * Base offset for PWM #i is at 8 * #i.
++ */
++#define IPQ_PWM_REG0                  0
++#define IPQ_PWM_REG0_PWM_DIV          GENMASK(15, 0)
++#define IPQ_PWM_REG0_HI_DURATION      GENMASK(31, 16)
++
++#define IPQ_PWM_REG1                  4
++#define IPQ_PWM_REG1_PRE_DIV          GENMASK(15, 0)
++
++/*
++ * The max value specified for each field is based on the number of bits
++ * in the pwm control register for that field (16-bit)
++ */
++#define IPQ_PWM_MAX_DIV                       FIELD_MAX(IPQ_PWM_REG0_PWM_DIV)
++
++/*
++ * Enable bit is set to enable output toggling in pwm device.
++ * Update bit is set to trigger the change and is unset automatically
++ * to reflect the changed divider and high duration values in register.
++ */
++#define IPQ_PWM_REG1_UPDATE           BIT(30)
++#define IPQ_PWM_REG1_ENABLE           BIT(31)
++
++struct ipq_pwm_chip {
++      void __iomem *mem;
++      unsigned long clk_rate;
++};
++
++static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip)
++{
++      return pwmchip_get_drvdata(chip);
++}
++
++static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg)
++{
++      struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
++      unsigned int off = 8 * pwm->hwpwm + reg;
++
++      return readl(ipq_chip->mem + off);
++}
++
++static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg,
++                            unsigned int val)
++{
++      struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
++      unsigned int off = 8 * pwm->hwpwm + reg;
++
++      writel(val, ipq_chip->mem + off);
++}
++
++static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
++                       const struct pwm_state *state)
++{
++      struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
++      unsigned int pre_div, pwm_div;
++      u64 period_ns, duty_ns;
++      unsigned long val = 0;
++      unsigned long hi_dur;
++
++      if (state->polarity != PWM_POLARITY_NORMAL)
++              return -EINVAL;
++
++      if (!ipq_chip->clk_rate)
++              return -EINVAL;
++
++      if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC,
++                                             ipq_chip->clk_rate))
++              return -ERANGE;
++
++      period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
++      duty_ns = min(state->duty_cycle, period_ns);
++
++      pwm_div = IPQ_PWM_MAX_DIV - 1;
++      pre_div = mul_u64_u64_div_u64(period_ns, ipq_chip->clk_rate,
++                                    (u64)NSEC_PER_SEC * (pwm_div + 1));
++      pre_div = (pre_div > 0) ? pre_div - 1 : 0;
++
++      if (pre_div > IPQ_PWM_MAX_DIV)
++              pre_div = IPQ_PWM_MAX_DIV;
++
++      /*
++       * high duration = pwm duty * (pwm div + 1)
++       * pwm duty = duty_ns / period_ns
++       */
++      hi_dur = mul_u64_u64_div_u64(duty_ns, ipq_chip->clk_rate,
++                                   (u64)(pre_div + 1) * NSEC_PER_SEC);
++
++      val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) |
++              FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div);
++      ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val);
++
++      val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div);
++      ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
++
++      /* PWM enable toggle needs a separate write to REG1 */
++      val |= IPQ_PWM_REG1_UPDATE;
++      if (state->enabled)
++              val |= IPQ_PWM_REG1_ENABLE;
++      ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
++
++      return 0;
++}
++
++static int ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
++                           struct pwm_state *state)
++{
++      struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
++      unsigned int pre_div, pwm_div, hi_dur;
++      u64 effective_div, hi_div;
++      u32 reg0, reg1;
++
++      reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1);
++      state->enabled = reg1 & IPQ_PWM_REG1_ENABLE;
++
++      if (!state->enabled)
++              return 0;
++
++      reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0);
++
++      state->polarity = PWM_POLARITY_NORMAL;
++
++      pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0);
++      hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0);
++      pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1);
++
++      effective_div = (u64)(pre_div + 1) * (pwm_div + 1);
++      state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC,
++                                         ipq_chip->clk_rate);
++
++      hi_div = hi_dur * (pre_div + 1);
++      state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC,
++                                             ipq_chip->clk_rate);
++
++      /*
++       * ensure a valid config is passed back to PWM core in case duty_cycle
++       * is > period (>100%)
++       */
++      state->duty_cycle = min(state->duty_cycle, state->period);
++
++      return 0;
++}
++
++static const struct pwm_ops ipq_pwm_ops = {
++      .apply = ipq_pwm_apply,
++      .get_state = ipq_pwm_get_state,
++};
++
++static int ipq_pwm_probe(struct platform_device *pdev)
++{
++      struct device *dev = &pdev->dev;
++      struct ipq_pwm_chip *pwm;
++      struct pwm_chip *chip;
++      struct clk *clk;
++      int ret;
++
++      chip = devm_pwmchip_alloc(dev, 4, sizeof(*pwm));
++      if (IS_ERR(chip))
++              return PTR_ERR(chip);
++      pwm = ipq_pwm_from_chip(chip);
++
++      pwm->mem = devm_platform_ioremap_resource(pdev, 0);
++      if (IS_ERR(pwm->mem))
++              return dev_err_probe(dev, PTR_ERR(pwm->mem),
++                                   "Failed to acquire resource\n");
++
++      clk = devm_clk_get_enabled(dev, NULL);
++      if (IS_ERR(clk))
++              return dev_err_probe(dev, PTR_ERR(clk),
++                                   "Failed to get clock\n");
++
++      ret = devm_clk_rate_exclusive_get(dev, clk);
++      if (ret)
++              return dev_err_probe(dev, ret, "Failed to lock clock rate\n");
++
++      pwm->clk_rate = clk_get_rate(clk);
++
++      chip->ops = &ipq_pwm_ops;
++
++      ret = devm_pwmchip_add(dev, chip);
++      if (ret < 0)
++              return dev_err_probe(dev, ret, "Failed to add pwm chip\n");
++
++      return 0;
++}
++
++static const struct of_device_id pwm_ipq_dt_match[] = {
++      { .compatible = "qcom,ipq6018-pwm", },
++      {}
++};
++MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
++
++static struct platform_driver ipq_pwm_driver = {
++      .driver = {
++              .name = "ipq-pwm",
++              .of_match_table = pwm_ipq_dt_match,
++      },
++      .probe = ipq_pwm_probe,
++};
++
++module_platform_driver(ipq_pwm_driver);
++
++MODULE_LICENSE("GPL");
diff --git a/target/linux/qualcommbe/patches-6.18/0402-arm64-dts-qcom-ipq9574-add-pwm-node.patch b/target/linux/qualcommbe/patches-6.18/0402-arm64-dts-qcom-ipq9574-add-pwm-node.patch
new file mode 100644 (file)
index 0000000..f3add21
--- /dev/null
@@ -0,0 +1,39 @@
+From 26533a030121e69bede9757b86c30edf9f851b8c Mon Sep 17 00:00:00 2001
+From: George Moussalem <george.moussalem@outlook.com>
+Date: Wed, 4 Feb 2026 15:25:12 +0400
+Subject: [PATCH] arm64: dts: qcom: ipq9574: add pwm node
+
+Describe the PWM block on IPQ9574.
+
+Although PWM is in the TCSR area, make pwm its own node as simple-mfd
+has been removed from the bindings and as such hardware components
+should have its own node.
+
+Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+Signed-off-by: George Moussalem <george.moussalem@outlook.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
+---
+ arch/arm64/boot/dts/qcom/ipq9574.dtsi | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/arch/arm64/boot/dts/qcom/ipq9574.dtsi b/arch/arm64/boot/dts/qcom/ipq9574.dtsi
+index 83e0bd75086bc..2c4f1bc2a70ee 100644
+--- a/arch/arm64/boot/dts/qcom/ipq9574.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq9574.dtsi
+@@ -469,6 +469,16 @@ tcsr: syscon@1937000 {
+                       reg = <0x01937000 0x21000>;
+               };
++              pwm: pwm@1941010 {
++                      compatible = "qcom,ipq9574-pwm", "qcom,ipq6018-pwm";
++                      reg = <0x01941010 0x20>;
++                      clocks = <&gcc GCC_ADSS_PWM_CLK>;
++                      assigned-clocks = <&gcc GCC_ADSS_PWM_CLK>;
++                      assigned-clock-rates = <100000000>;
++                      #pwm-cells = <3>;
++                      status = "disabled";
++              };
++
+               sdhc_1: mmc@7804000 {
+                       compatible = "qcom,ipq9574-sdhci", "qcom,sdhci-msm-v5";
+                       reg = <0x07804000 0x1000>,