2024-03-14 10:01:57

by Jingbao Qiu

[permalink] [raw]
Subject: [PATCH v5 0/2] riscv: pwm: sophgo: add pwm support for CV1800

The Sophgo CV1800 chip provides a set of four independent
PWM channel outputs.
This series adds PWM controller support for Sophgo cv1800.

Changes since v4:
- drop filename
- fix macro
- optimize cv1800_pwm_set_polarity()
- optimize cv1800_pwm_set_oe()
- add comment for cv1800_pwm_set_oe()
- use ticks replace tem
- fix duty_cycle larger than period_val
- use devm_clk_rate_exclusive_get() replace
clk_rate_exclusive_get()
- map linux polarity to register polarity

v4: https://lore.kernel.org/all/[email protected]/

datasheet Link: https://github.com/milkv-duo/duo-files/blob/main/duo/datasheet/CV1800B-CV1801B-Preliminary-Datasheet-full-en.pdf
page 614

Changes since v3:
- use macro instead of npwm number
- add support for polarity feature
- add support for Output-Enable/OE feature

v3: https://lore.kernel.org/all/[email protected]/

Changes since v2:
- use 0x08 instead of macro
- split if statements based on conditions
- in order to round up, first calculate the
number of high-level cycles, then subtract
it from the PERIOD to obtain the number of HLPERIOD
- use new pwmchip_alloc() API instead of old style

v2: https://lore.kernel.org/all/[email protected]/

Changes since v1:
- drop full stop from subject
- re-order maintainers and description
- pass checkpatch.pl --strict
- fix naming errors
- add "Limitations" section
- use a driver specific prefix for all defines
- using bool instead u32 in cv1800_pwm_enable
- check and set state->polarity
- use mul_u64_u64_div_u64
- use clk_rate_exclusive_get(), balance with clk_rate_exclusive_put()
- using macro definitions instead of shift operations
- remove shift operation on 0
- use priv replace cv_pwm
- hardcode npwm
- set atomic to true
- remove MODULE_ALIAS

v1: https://lore.kernel.org/all/[email protected]/

Jingbao Qiu (2):
dt-bindings: pwm: sophgo: add pwm for Sophgo CV1800 series SoC
pwm: sophgo: add pwm support for Sophgo CV1800 SoC

.../bindings/pwm/sophgo,cv1800-pwm.yaml | 45 +++
drivers/pwm/Kconfig | 10 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-cv1800.c | 315 ++++++++++++++++++
4 files changed, 371 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pwm/sophgo,cv1800-pwm.yaml
create mode 100644 drivers/pwm/pwm-cv1800.c


base-commit: 8ffc8b1bbd505e27e2c8439d326b6059c906c9dd
--
2.25.1



2024-03-14 10:02:18

by Jingbao Qiu

[permalink] [raw]
Subject: [PATCH v5 1/2] dt-bindings: pwm: sophgo: add pwm for Sophgo CV1800 series SoC

Add devicetree binding to describe the PWM for Sophgo CV1800 SoC.

Reviewed-by: Krzysztof Kozlowski <[email protected]>
Signed-off-by: Jingbao Qiu <[email protected]>
---
.../bindings/pwm/sophgo,cv1800-pwm.yaml | 45 +++++++++++++++++++
1 file changed, 45 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pwm/sophgo,cv1800-pwm.yaml

diff --git a/Documentation/devicetree/bindings/pwm/sophgo,cv1800-pwm.yaml b/Documentation/devicetree/bindings/pwm/sophgo,cv1800-pwm.yaml
new file mode 100644
index 000000000000..b5b819d780f1
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/sophgo,cv1800-pwm.yaml
@@ -0,0 +1,45 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pwm/sophgo,cv1800-pwm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sophgo CV1800 PWM controller
+
+maintainers:
+ - Jingbao Qiu <[email protected]>
+
+description:
+ The chip provides a set of four independent PWM channel outputs.
+
+allOf:
+ - $ref: pwm.yaml#
+
+properties:
+ compatible:
+ const: sophgo,cv1800-pwm
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ "#pwm-cells":
+ const: 3
+
+required:
+ - compatible
+ - reg
+ - clocks
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ pwm0: pwm@3060000 {
+ compatible = "sophgo,cv1800-pwm";
+ reg = <0x3060000 0x1000>;
+ clocks = <&clk 60>;
+ #pwm-cells = <3>;
+ };
--
2.25.1


2024-03-14 10:02:18

by Jingbao Qiu

[permalink] [raw]
Subject: [PATCH v5 2/2] pwm: sophgo: add pwm support for Sophgo CV1800 SoC

Implement the PWM driver for CV1800.

Signed-off-by: Jingbao Qiu <[email protected]>
---
drivers/pwm/Kconfig | 10 ++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-cv1800.c | 315 +++++++++++++++++++++++++++++++++++++++
3 files changed, 326 insertions(+)
create mode 100644 drivers/pwm/pwm-cv1800.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 4b956d661755..455f07af94f7 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -186,6 +186,16 @@ config PWM_CROS_EC
PWM driver for exposing a PWM attached to the ChromeOS Embedded
Controller.

+config PWM_CV1800
+ tristate "Sophgo CV1800 PWM driver"
+ depends on ARCH_SOPHGO || COMPILE_TEST
+ help
+ Generic PWM framework driver for the Sophgo CV1800 series
+ SoCs.
+
+ To compile this driver as a module, build the dependecies
+ as modules, this will be called pwm-cv1800.
+
config PWM_DWC_CORE
tristate
depends on HAS_IOMEM
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index c5ec9e168ee7..6c3c4a07a316 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_PWM_CLK) += pwm-clk.o
obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o
obj-$(CONFIG_PWM_CRC) += pwm-crc.o
obj-$(CONFIG_PWM_CROS_EC) += pwm-cros-ec.o
+obj-$(CONFIG_PWM_CV1800) += pwm-cv1800.o
obj-$(CONFIG_PWM_DWC_CORE) += pwm-dwc-core.o
obj-$(CONFIG_PWM_DWC) += pwm-dwc.o
obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
diff --git a/drivers/pwm/pwm-cv1800.c b/drivers/pwm/pwm-cv1800.c
new file mode 100644
index 000000000000..8eca07c60942
--- /dev/null
+++ b/drivers/pwm/pwm-cv1800.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Sophgo CV1800 PWM driver
+ * Author: Jingbao Qiu <[email protected]>
+ *
+ * Limitations:
+ * - It output low when PWM channel disabled.
+ * - This pwm device supports dynamic loading of PWM parameters. When PWMSTART
+ * is written from 0 to 1, the register value (HLPERIODn, PERIODn) will be
+ * temporarily stored inside the PWM. If you want to dynamically change the
+ * waveform during PWM output, after writing the new value to HLPERIODn and
+ * PERIODn, write 1 and then 0 to PWMUPDATE[n] to make the new value effective.
+ * - Supports up to Rate/2 output, and the lowest is about Rate/(2^30-1).
+ * - By setting HLPERIODn to 0, can produce 100% duty cycle.
+ * - This hardware could support inverted polarity. By default, the value of the
+ * POLARITY register is 0x0. This means that HLPERIOD represents the number
+ * of low level beats.
+ * - This hardware supports input mode and output mode, implemented through the
+ * Output-Enable/OE register. However, this driver has not yet implemented
+ * capture callback.
+ */
+
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+
+#define PWM_CV1800_HLPERIOD_BASE 0x00
+#define PWM_CV1800_PERIOD_BASE 0x04
+#define PWM_CV1800_POLARITY 0x40
+#define PWM_CV1800_START 0x44
+#define PWM_CV1800_DONE 0x48
+#define PWM_CV1800_UPDATE 0x4c
+#define PWM_CV1800_OE 0xd0
+
+#define PWM_CV1800_HLPERIOD(n) (PWM_CV1800_HLPERIOD_BASE + ((n) * 0x08))
+#define PWM_CV1800_PERIOD(n) (PWM_CV1800_PERIOD_BASE + ((n) * 0x08))
+
+#define PWM_CV1800_UPDATE_MASK(n) (BIT(0) << (n))
+#define PWM_CV1800_OE_MASK(n) (BIT(0) << (n))
+#define PWM_CV1800_START_MASK(n) (BIT(0) << (n))
+#define PWM_CV1800_POLARITY_MASK(n) (BIT(0) << (n))
+
+#define PWM_CV1800_MAXPERIOD 0x3fffffff
+#define PWM_CV1800_MINPERIOD 2
+#define PWM_CV1800_CHANNELS 4
+#define PWM_CV1800_PERIOD_RESET BIT(1)
+#define PWM_CV1800_HLPERIOD_RESET BIT(0)
+#define PWM_CV1800_REG_DISABLE 0x00U
+#define PWM_CV1800_REG_ENABLE(n) (BIT(0) << (n))
+
+struct cv1800_pwm {
+ struct regmap *map;
+ struct clk *clk;
+ unsigned long clk_rate;
+};
+
+static inline struct cv1800_pwm *to_cv1800_pwm_dev(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static const struct regmap_config cv1800_pwm_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static int cv1800_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm,
+ bool enable)
+{
+ struct cv1800_pwm *priv = to_cv1800_pwm_dev(chip);
+ u32 pwm_enable;
+
+ regmap_read(priv->map, PWM_CV1800_START, &pwm_enable);
+ pwm_enable &= PWM_CV1800_START_MASK(pwm->hwpwm);
+
+ /*
+ * If the parameters are changed during runtime, Register needs
+ * to be updated to take effect.
+ */
+ if (pwm_enable && enable) {
+ regmap_update_bits(priv->map, PWM_CV1800_UPDATE,
+ PWM_CV1800_UPDATE_MASK(pwm->hwpwm),
+ PWM_CV1800_REG_ENABLE(pwm->hwpwm));
+ regmap_update_bits(priv->map, PWM_CV1800_UPDATE,
+ PWM_CV1800_UPDATE_MASK(pwm->hwpwm),
+ PWM_CV1800_REG_DISABLE);
+ } else if (!pwm_enable && enable) {
+ regmap_update_bits(priv->map, PWM_CV1800_START,
+ PWM_CV1800_START_MASK(pwm->hwpwm),
+ PWM_CV1800_REG_ENABLE(pwm->hwpwm));
+ } else if (pwm_enable && !enable) {
+ regmap_update_bits(priv->map, PWM_CV1800_START,
+ PWM_CV1800_START_MASK(pwm->hwpwm),
+ PWM_CV1800_REG_DISABLE);
+ }
+
+ return 0;
+}
+
+static void cv1800_pwm_set_polarity(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ enum pwm_polarity polarity)
+{
+ struct cv1800_pwm *priv = to_cv1800_pwm_dev(chip);
+ u32 config_polarity = 0;
+
+ if (pwm->state.enabled)
+ cv1800_pwm_enable(chip, pwm, !pwm->state.enabled);
+
+ if (polarity == PWM_POLARITY_INVERSED)
+ config_polarity = PWM_CV1800_POLARITY_MASK(pwm->hwpwm);
+
+ regmap_update_bits(priv->map, PWM_CV1800_POLARITY,
+ PWM_CV1800_POLARITY_MASK(pwm->hwpwm),
+ config_polarity);
+}
+
+/**
+ * cv1800_pwm_set_oe() - check and config nth channal output-enable/OE mode
+ * @chip: PWM chip
+ * @pwm: PWM device
+ * @mode: The nth bit of the mode represents the output-enable/OE mode
+ * of the nth channal. 1 represents output mode, 0 represents
+ * input mode.
+ */
+static void cv1800_pwm_set_oe(struct pwm_chip *chip, struct pwm_device *pwm,
+ u32 mode)
+{
+ struct cv1800_pwm *priv = to_cv1800_pwm_dev(chip);
+ u32 state;
+
+ regmap_read(priv->map, PWM_CV1800_OE, &state);
+ state &= PWM_CV1800_OE_MASK(pwm->hwpwm);
+
+ if (state == mode)
+ return;
+
+ /* disenable pwm output before changing output mode */
+ cv1800_pwm_enable(chip, pwm, false);
+
+ regmap_update_bits(priv->map, PWM_CV1800_OE,
+ PWM_CV1800_OE_MASK(pwm->hwpwm), mode);
+}
+
+static int cv1800_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct cv1800_pwm *priv = to_cv1800_pwm_dev(chip);
+ u32 period_val, hlperiod_val;
+ u64 ticks;
+
+ cv1800_pwm_set_oe(chip, pwm, PWM_CV1800_OE_MASK(pwm->hwpwm));
+
+ if (state->polarity != pwm->state.polarity)
+ cv1800_pwm_set_polarity(chip, pwm, state->polarity);
+
+ /*
+ * This hardware use PERIOD and HLPERIOD registers to represent PWM waves.
+ *
+ * The meaning of PERIOD is how many clock cycles (from the clock source)
+ * are used to represent PWM waves.
+ * PERIOD = rate(MHz) / target(MHz)
+ * PERIOD = period(ns) * rate(Hz) / NSEC_PER_SEC
+ */
+ ticks = mul_u64_u64_div_u64(state->period, priv->clk_rate,
+ NSEC_PER_SEC);
+ if (ticks < PWM_CV1800_MINPERIOD)
+ return -EINVAL;
+
+ if (ticks > PWM_CV1800_MAXPERIOD)
+ ticks = PWM_CV1800_MAXPERIOD;
+ period_val = (u32)ticks;
+
+ /*
+ * After mapping, hlperiod represents the same polarity as duty.
+ * HLPERIOD = rate(MHz) / duty(MHz)
+ * HLPERIOD = duty(ns) * rate(Hz) / NSEC_PER_SEC
+ */
+ ticks = mul_u64_u64_div_u64(state->duty_cycle, priv->clk_rate,
+ NSEC_PER_SEC);
+ if (ticks > period_val)
+ ticks = period_val;
+ hlperiod_val = (u32)ticks;
+
+ regmap_write(priv->map, PWM_CV1800_PERIOD(pwm->hwpwm), period_val);
+ regmap_write(priv->map, PWM_CV1800_HLPERIOD(pwm->hwpwm), hlperiod_val);
+
+ cv1800_pwm_enable(chip, pwm, state->enabled);
+
+ return 0;
+}
+
+static int cv1800_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct cv1800_pwm *priv = to_cv1800_pwm_dev(chip);
+ u32 period_val, hlperiod_val;
+ u64 period_ns = 0, duty_ns = 0;
+ u32 enable = 0, polarity = 0;
+
+ regmap_read(priv->map, PWM_CV1800_PERIOD(pwm->hwpwm), &period_val);
+ regmap_read(priv->map, PWM_CV1800_HLPERIOD(pwm->hwpwm), &hlperiod_val);
+
+ if (period_val != PWM_CV1800_PERIOD_RESET ||
+ hlperiod_val != PWM_CV1800_HLPERIOD_RESET) {
+ period_ns = DIV_ROUND_UP_ULL(period_val * NSEC_PER_SEC,
+ priv->clk_rate);
+ duty_ns = DIV_ROUND_UP_ULL(hlperiod_val * NSEC_PER_SEC,
+ priv->clk_rate);
+
+ regmap_read(priv->map, PWM_CV1800_START, &enable);
+ enable &= PWM_CV1800_START_MASK(pwm->hwpwm);
+
+ regmap_read(priv->map, PWM_CV1800_POLARITY, &polarity);
+ polarity &= PWM_CV1800_POLARITY_MASK(pwm->hwpwm);
+ }
+
+ state->period = period_ns;
+ state->duty_cycle = duty_ns;
+ state->enabled = enable;
+
+ /*
+ * To ensure that duty and hlperiod represent the same polarity
+ * the following mapping needs to be completed.
+ *
+ * |----------|------------|------------|-----------|
+ * | Linux | register | duty | register |
+ * | polarity | polarity | | hlperiod |
+ * |----------|------------|------------|-----------|
+ * | 1 | 0 | low level | low level |
+ * |----------|------------|------------|-----------|
+ * | 0 | 1 | high level | high level|
+ * |----------|------------|------------|-----------|
+ */
+ state->polarity = polarity ? PWM_POLARITY_NORMAL :
+ PWM_POLARITY_INVERSED;
+
+ return 0;
+}
+
+static const struct pwm_ops cv1800_pwm_ops = {
+ .apply = cv1800_pwm_apply,
+ .get_state = cv1800_pwm_get_state,
+};
+
+static int cv1800_pwm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cv1800_pwm *priv;
+ struct pwm_chip *chip;
+ void __iomem *base;
+ int ret;
+
+ chip = devm_pwmchip_alloc(dev, PWM_CV1800_CHANNELS, sizeof(*priv));
+ if (!chip)
+ return PTR_ERR(chip);
+ priv = to_cv1800_pwm_dev(chip);
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ priv->map = devm_regmap_init_mmio(&pdev->dev, base,
+ &cv1800_pwm_regmap_config);
+ if (IS_ERR(priv->map))
+ return PTR_ERR(priv->map);
+
+ priv->clk = devm_clk_get_enabled(&pdev->dev, NULL);
+ if (IS_ERR(priv->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk),
+ "clk not found\n");
+
+ ret = devm_clk_rate_exclusive_get(dev, priv->clk);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to get exclusive rate\n");
+
+ priv->clk_rate = clk_get_rate(priv->clk);
+ if (!priv->clk_rate)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "Invalid clock rate: %lu\n",
+ priv->clk_rate);
+
+ chip->ops = &cv1800_pwm_ops;
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
+
+ return 0;
+}
+
+static const struct of_device_id cv1800_pwm_dt_ids[] = {
+ { .compatible = "sophgo,cv1800-pwm" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, cv1800_pwm_dt_ids);
+
+static struct platform_driver cv1800_pwm_driver = {
+ .probe = cv1800_pwm_probe,
+ .driver = {
+ .name = "cv1800-pwm",
+ .of_match_table = cv1800_pwm_dt_ids,
+ },
+};
+module_platform_driver(cv1800_pwm_driver);
+
+MODULE_AUTHOR("Jingbao Qiu");
+MODULE_DESCRIPTION("Sophgo cv1800 PWM Driver");
+MODULE_LICENSE("GPL");
--
2.25.1


2024-03-16 01:55:19

by Jisheng Zhang

[permalink] [raw]
Subject: Re: [PATCH v5 2/2] pwm: sophgo: add pwm support for Sophgo CV1800 SoC

On Thu, Mar 14, 2024 at 06:01:31PM +0800, Jingbao Qiu wrote:
> Implement the PWM driver for CV1800.
>
> Signed-off-by: Jingbao Qiu <[email protected]>
> ---
> drivers/pwm/Kconfig | 10 ++
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-cv1800.c | 315 +++++++++++++++++++++++++++++++++++++++
> 3 files changed, 326 insertions(+)
> create mode 100644 drivers/pwm/pwm-cv1800.c
>
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 4b956d661755..455f07af94f7 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -186,6 +186,16 @@ config PWM_CROS_EC
> PWM driver for exposing a PWM attached to the ChromeOS Embedded
> Controller.
>
> +config PWM_CV1800
> + tristate "Sophgo CV1800 PWM driver"
> + depends on ARCH_SOPHGO || COMPILE_TEST
> + help
> + Generic PWM framework driver for the Sophgo CV1800 series
> + SoCs.
> +
> + To compile this driver as a module, build the dependecies
> + as modules, this will be called pwm-cv1800.
> +
> config PWM_DWC_CORE
> tristate
> depends on HAS_IOMEM
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index c5ec9e168ee7..6c3c4a07a316 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -15,6 +15,7 @@ obj-$(CONFIG_PWM_CLK) += pwm-clk.o
> obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o
> obj-$(CONFIG_PWM_CRC) += pwm-crc.o
> obj-$(CONFIG_PWM_CROS_EC) += pwm-cros-ec.o
> +obj-$(CONFIG_PWM_CV1800) += pwm-cv1800.o
> obj-$(CONFIG_PWM_DWC_CORE) += pwm-dwc-core.o
> obj-$(CONFIG_PWM_DWC) += pwm-dwc.o
> obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
> diff --git a/drivers/pwm/pwm-cv1800.c b/drivers/pwm/pwm-cv1800.c
> new file mode 100644
> index 000000000000..8eca07c60942
> --- /dev/null
> +++ b/drivers/pwm/pwm-cv1800.c
> @@ -0,0 +1,315 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Sophgo CV1800 PWM driver
> + * Author: Jingbao Qiu <[email protected]>
> + *
> + * Limitations:
> + * - It output low when PWM channel disabled.
> + * - This pwm device supports dynamic loading of PWM parameters. When PWMSTART
> + * is written from 0 to 1, the register value (HLPERIODn, PERIODn) will be
> + * temporarily stored inside the PWM. If you want to dynamically change the
> + * waveform during PWM output, after writing the new value to HLPERIODn and
> + * PERIODn, write 1 and then 0 to PWMUPDATE[n] to make the new value effective.
> + * - Supports up to Rate/2 output, and the lowest is about Rate/(2^30-1).
> + * - By setting HLPERIODn to 0, can produce 100% duty cycle.
> + * - This hardware could support inverted polarity. By default, the value of the
> + * POLARITY register is 0x0. This means that HLPERIOD represents the number
> + * of low level beats.
> + * - This hardware supports input mode and output mode, implemented through the
> + * Output-Enable/OE register. However, this driver has not yet implemented
> + * capture callback.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/regmap.h>
> +
> +#define PWM_CV1800_HLPERIOD_BASE 0x00
> +#define PWM_CV1800_PERIOD_BASE 0x04
> +#define PWM_CV1800_POLARITY 0x40
> +#define PWM_CV1800_START 0x44
> +#define PWM_CV1800_DONE 0x48
> +#define PWM_CV1800_UPDATE 0x4c
> +#define PWM_CV1800_OE 0xd0
> +
> +#define PWM_CV1800_HLPERIOD(n) (PWM_CV1800_HLPERIOD_BASE + ((n) * 0x08))
> +#define PWM_CV1800_PERIOD(n) (PWM_CV1800_PERIOD_BASE + ((n) * 0x08))
> +
> +#define PWM_CV1800_UPDATE_MASK(n) (BIT(0) << (n))
> +#define PWM_CV1800_OE_MASK(n) (BIT(0) << (n))
> +#define PWM_CV1800_START_MASK(n) (BIT(0) << (n))
> +#define PWM_CV1800_POLARITY_MASK(n) (BIT(0) << (n))
> +
> +#define PWM_CV1800_MAXPERIOD 0x3fffffff
> +#define PWM_CV1800_MINPERIOD 2
> +#define PWM_CV1800_CHANNELS 4
> +#define PWM_CV1800_PERIOD_RESET BIT(1)
> +#define PWM_CV1800_HLPERIOD_RESET BIT(0)
> +#define PWM_CV1800_REG_DISABLE 0x00U
> +#define PWM_CV1800_REG_ENABLE(n) (BIT(0) << (n))
> +
> +struct cv1800_pwm {
> + struct regmap *map;
> + struct clk *clk;
> + unsigned long clk_rate;
> +};
> +
> +static inline struct cv1800_pwm *to_cv1800_pwm_dev(struct pwm_chip *chip)
> +{
> + return pwmchip_get_drvdata(chip);
> +}
> +
> +static const struct regmap_config cv1800_pwm_regmap_config = {
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = 4,
> +};
> +
> +static int cv1800_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm,
> + bool enable)
> +{
> + struct cv1800_pwm *priv = to_cv1800_pwm_dev(chip);
> + u32 pwm_enable;
> +
> + regmap_read(priv->map, PWM_CV1800_START, &pwm_enable);
> + pwm_enable &= PWM_CV1800_START_MASK(pwm->hwpwm);
> +
> + /*
> + * If the parameters are changed during runtime, Register needs
> + * to be updated to take effect.
> + */
> + if (pwm_enable && enable) {
> + regmap_update_bits(priv->map, PWM_CV1800_UPDATE,
> + PWM_CV1800_UPDATE_MASK(pwm->hwpwm),
> + PWM_CV1800_REG_ENABLE(pwm->hwpwm));
> + regmap_update_bits(priv->map, PWM_CV1800_UPDATE,
> + PWM_CV1800_UPDATE_MASK(pwm->hwpwm),
> + PWM_CV1800_REG_DISABLE);
> + } else if (!pwm_enable && enable) {
> + regmap_update_bits(priv->map, PWM_CV1800_START,
> + PWM_CV1800_START_MASK(pwm->hwpwm),
> + PWM_CV1800_REG_ENABLE(pwm->hwpwm));
> + } else if (pwm_enable && !enable) {
> + regmap_update_bits(priv->map, PWM_CV1800_START,
> + PWM_CV1800_START_MASK(pwm->hwpwm),
> + PWM_CV1800_REG_DISABLE);
> + }
> +
> + return 0;
> +}
> +
> +static void cv1800_pwm_set_polarity(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + enum pwm_polarity polarity)
> +{
> + struct cv1800_pwm *priv = to_cv1800_pwm_dev(chip);
> + u32 config_polarity = 0;
> +
> + if (pwm->state.enabled)
> + cv1800_pwm_enable(chip, pwm, !pwm->state.enabled);
> +
> + if (polarity == PWM_POLARITY_INVERSED)
> + config_polarity = PWM_CV1800_POLARITY_MASK(pwm->hwpwm);
> +
> + regmap_update_bits(priv->map, PWM_CV1800_POLARITY,
> + PWM_CV1800_POLARITY_MASK(pwm->hwpwm),
> + config_polarity);
> +}
> +
> +/**
> + * cv1800_pwm_set_oe() - check and config nth channal output-enable/OE mode
> + * @chip: PWM chip
> + * @pwm: PWM device
> + * @mode: The nth bit of the mode represents the output-enable/OE mode
> + * of the nth channal. 1 represents output mode, 0 represents
> + * input mode.
> + */
> +static void cv1800_pwm_set_oe(struct pwm_chip *chip, struct pwm_device *pwm,
> + u32 mode)

Did you get any information about the capture support pwm controller?

> +{
> + struct cv1800_pwm *priv = to_cv1800_pwm_dev(chip);
> + u32 state;
> +
> + regmap_read(priv->map, PWM_CV1800_OE, &state);
> + state &= PWM_CV1800_OE_MASK(pwm->hwpwm);
> +
> + if (state == mode)
> + return;
> +
> + /* disenable pwm output before changing output mode */
> + cv1800_pwm_enable(chip, pwm, false);
> +
> + regmap_update_bits(priv->map, PWM_CV1800_OE,
> + PWM_CV1800_OE_MASK(pwm->hwpwm), mode);
> +}
> +
> +static int cv1800_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> + const struct pwm_state *state)
> +{
> + struct cv1800_pwm *priv = to_cv1800_pwm_dev(chip);
> + u32 period_val, hlperiod_val;
> + u64 ticks;
> +
> + cv1800_pwm_set_oe(chip, pwm, PWM_CV1800_OE_MASK(pwm->hwpwm));

If no capture support, I don't think we need to take care OE, could it
be done during init?

> +
> + if (state->polarity != pwm->state.polarity)
> + cv1800_pwm_set_polarity(chip, pwm, state->polarity);
> +
> + /*
> + * This hardware use PERIOD and HLPERIOD registers to represent PWM waves.
> + *
> + * The meaning of PERIOD is how many clock cycles (from the clock source)
> + * are used to represent PWM waves.
> + * PERIOD = rate(MHz) / target(MHz)
> + * PERIOD = period(ns) * rate(Hz) / NSEC_PER_SEC
> + */
> + ticks = mul_u64_u64_div_u64(state->period, priv->clk_rate,
> + NSEC_PER_SEC);
> + if (ticks < PWM_CV1800_MINPERIOD)
> + return -EINVAL;
> +
> + if (ticks > PWM_CV1800_MAXPERIOD)
> + ticks = PWM_CV1800_MAXPERIOD;
> + period_val = (u32)ticks;
> +
> + /*
> + * After mapping, hlperiod represents the same polarity as duty.
> + * HLPERIOD = rate(MHz) / duty(MHz)
> + * HLPERIOD = duty(ns) * rate(Hz) / NSEC_PER_SEC
> + */
> + ticks = mul_u64_u64_div_u64(state->duty_cycle, priv->clk_rate,
> + NSEC_PER_SEC);
> + if (ticks > period_val)
> + ticks = period_val;
> + hlperiod_val = (u32)ticks;
> +
> + regmap_write(priv->map, PWM_CV1800_PERIOD(pwm->hwpwm), period_val);
> + regmap_write(priv->map, PWM_CV1800_HLPERIOD(pwm->hwpwm), hlperiod_val);
> +
> + cv1800_pwm_enable(chip, pwm, state->enabled);
> +
> + return 0;
> +}
> +
> +static int cv1800_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> + struct pwm_state *state)
> +{
> + struct cv1800_pwm *priv = to_cv1800_pwm_dev(chip);
> + u32 period_val, hlperiod_val;
> + u64 period_ns = 0, duty_ns = 0;
> + u32 enable = 0, polarity = 0;
> +
> + regmap_read(priv->map, PWM_CV1800_PERIOD(pwm->hwpwm), &period_val);
> + regmap_read(priv->map, PWM_CV1800_HLPERIOD(pwm->hwpwm), &hlperiod_val);
> +
> + if (period_val != PWM_CV1800_PERIOD_RESET ||
> + hlperiod_val != PWM_CV1800_HLPERIOD_RESET) {
> + period_ns = DIV_ROUND_UP_ULL(period_val * NSEC_PER_SEC,
> + priv->clk_rate);
> + duty_ns = DIV_ROUND_UP_ULL(hlperiod_val * NSEC_PER_SEC,
> + priv->clk_rate);
> +
> + regmap_read(priv->map, PWM_CV1800_START, &enable);
> + enable &= PWM_CV1800_START_MASK(pwm->hwpwm);
> +
> + regmap_read(priv->map, PWM_CV1800_POLARITY, &polarity);
> + polarity &= PWM_CV1800_POLARITY_MASK(pwm->hwpwm);
> + }
> +
> + state->period = period_ns;
> + state->duty_cycle = duty_ns;
> + state->enabled = enable;
> +
> + /*
> + * To ensure that duty and hlperiod represent the same polarity
> + * the following mapping needs to be completed.
> + *
> + * |----------|------------|------------|-----------|
> + * | Linux | register | duty | register |
> + * | polarity | polarity | | hlperiod |
> + * |----------|------------|------------|-----------|
> + * | 1 | 0 | low level | low level |
> + * |----------|------------|------------|-----------|
> + * | 0 | 1 | high level | high level|
> + * |----------|------------|------------|-----------|
> + */
> + state->polarity = polarity ? PWM_POLARITY_NORMAL :
> + PWM_POLARITY_INVERSED;
> +
> + return 0;
> +}
> +
> +static const struct pwm_ops cv1800_pwm_ops = {
> + .apply = cv1800_pwm_apply,
> + .get_state = cv1800_pwm_get_state,
> +};
> +
> +static int cv1800_pwm_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct cv1800_pwm *priv;
> + struct pwm_chip *chip;
> + void __iomem *base;
> + int ret;
> +
> + chip = devm_pwmchip_alloc(dev, PWM_CV1800_CHANNELS, sizeof(*priv));
> + if (!chip)
> + return PTR_ERR(chip);
> + priv = to_cv1800_pwm_dev(chip);
> +
> + base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(base))
> + return PTR_ERR(base);
> +
> + priv->map = devm_regmap_init_mmio(&pdev->dev, base,
> + &cv1800_pwm_regmap_config);
> + if (IS_ERR(priv->map))
> + return PTR_ERR(priv->map);
> +
> + priv->clk = devm_clk_get_enabled(&pdev->dev, NULL);
> + if (IS_ERR(priv->clk))
> + return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk),
> + "clk not found\n");
> +
> + ret = devm_clk_rate_exclusive_get(dev, priv->clk);
> + if (ret)
> + return dev_err_probe(&pdev->dev, ret,
> + "failed to get exclusive rate\n");
> +
> + priv->clk_rate = clk_get_rate(priv->clk);
> + if (!priv->clk_rate)
> + return dev_err_probe(&pdev->dev, -EINVAL,
> + "Invalid clock rate: %lu\n",
> + priv->clk_rate);
> +
> + chip->ops = &cv1800_pwm_ops;
> +
> + ret = devm_pwmchip_add(dev, chip);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
> +
> + return 0;
> +}
> +
> +static const struct of_device_id cv1800_pwm_dt_ids[] = {
> + { .compatible = "sophgo,cv1800-pwm" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, cv1800_pwm_dt_ids);
> +
> +static struct platform_driver cv1800_pwm_driver = {
> + .probe = cv1800_pwm_probe,
> + .driver = {
> + .name = "cv1800-pwm",
> + .of_match_table = cv1800_pwm_dt_ids,
> + },
> +};
> +module_platform_driver(cv1800_pwm_driver);
> +
> +MODULE_AUTHOR("Jingbao Qiu");
> +MODULE_DESCRIPTION("Sophgo cv1800 PWM Driver");
> +MODULE_LICENSE("GPL");
> --
> 2.25.1
>
>
> _______________________________________________
> linux-riscv mailing list
> [email protected]
> http://lists.infradead.org/mailman/listinfo/linux-riscv

2024-03-16 07:22:35

by Uwe Kleine-König

[permalink] [raw]
Subject: Re: [PATCH v5 2/2] pwm: sophgo: add pwm support for Sophgo CV1800 SoC

Hello,

On Sat, Mar 16, 2024 at 09:41:46AM +0800, Jisheng Zhang wrote:
> On Thu, Mar 14, 2024 at 06:01:31PM +0800, Jingbao Qiu wrote:
> > +static void cv1800_pwm_set_oe(struct pwm_chip *chip, struct pwm_device *pwm,
> > + u32 mode)
>
> Did you get any information about the capture support pwm controller?

Instead of adding capture support to this pwm driver, I'd rather see
support added to the counter subsystem. That's more versatile.

See https://lore.kernel.org/linux-pwm/Y3aze3B6%2Fe5uWS%2FH@fedora for
some more context.

Best regards
Uwe

--
Pengutronix e.K. | Uwe Kleine-K?nig |
Industrial Linux Solutions | https://www.pengutronix.de/ |


Attachments:
(No filename) (732.00 B)
signature.asc (499.00 B)
Download all attachments

2024-03-16 07:42:34

by Jingbao Qiu

[permalink] [raw]
Subject: Re: [PATCH v5 2/2] pwm: sophgo: add pwm support for Sophgo CV1800 SoC

On Sat, Mar 16, 2024 at 3:22 PM Uwe Kleine-König
<[email protected]> wrote:
>
> Hello,
>
> On Sat, Mar 16, 2024 at 09:41:46AM +0800, Jisheng Zhang wrote:
> > On Thu, Mar 14, 2024 at 06:01:31PM +0800, Jingbao Qiu wrote:
> > > +static void cv1800_pwm_set_oe(struct pwm_chip *chip, struct pwm_device *pwm,
> > > + u32 mode)
> >
> > Did you get any information about the capture support pwm controller?
>
> Instead of adding capture support to this pwm driver, I'd rather see
> support added to the counter subsystem. That's more versatile.
>
> See https://lore.kernel.org/linux-pwm/Y3aze3B6%2Fe5uWS%2FH@fedora for
> some more context.
>

Thank you for your suggestion. I will add support for the counter subsystem
after communicating with the hardware designer about the input function of PWM.
Do you have any suggestions for this patch?

Best regards
Jingbao Qiu

2024-03-16 07:13:43

by Jingbao Qiu

[permalink] [raw]
Subject: Re: [PATCH v5 2/2] pwm: sophgo: add pwm support for Sophgo CV1800 SoC

On Sat, Mar 16, 2024 at 9:55 AM Jisheng Zhang <[email protected]> wrote:
>
> On Thu, Mar 14, 2024 at 06:01:31PM +0800, Jingbao Qiu wrote:
> > Implement the PWM driver for CV1800.
> >
> > Signed-off-by: Jingbao Qiu <[email protected]>
> > ---
> > drivers/pwm/Kconfig | 10 ++
> > drivers/pwm/Makefile | 1 +
> > drivers/pwm/pwm-cv1800.c | 315 +++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 326 insertions(+)
> > create mode 100644 drivers/pwm/pwm-cv1800.c
> >
> > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> > index 4b956d661755..455f07af94f7 100644
> > --- a/drivers/pwm/Kconfig
> > +++ b/drivers/pwm/Kconfig
> > @@ -186,6 +186,16 @@ config PWM_CROS_EC
> > PWM driver for exposing a PWM attached to the ChromeOS Embedded
> > Controller.
> >
> > +config PWM_CV1800
> > + tristate "Sophgo CV1800 PWM driver"
> > + depends on ARCH_SOPHGO || COMPILE_TEST
> > + help
> > + Generic PWM framework driver for the Sophgo CV1800 series
> > + SoCs.
> > +
> > + To compile this driver as a module, build the dependecies
> > + as modules, this will be called pwm-cv1800.
> > +
> > config PWM_DWC_CORE
> > tristate
> > depends on HAS_IOMEM
> > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> > index c5ec9e168ee7..6c3c4a07a316 100644
> > --- a/drivers/pwm/Makefile
> > +++ b/drivers/pwm/Makefile
> > @@ -15,6 +15,7 @@ obj-$(CONFIG_PWM_CLK) += pwm-clk.o
> > obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o
> > obj-$(CONFIG_PWM_CRC) += pwm-crc.o
> > obj-$(CONFIG_PWM_CROS_EC) += pwm-cros-ec.o
> > +obj-$(CONFIG_PWM_CV1800) += pwm-cv1800.o
> > obj-$(CONFIG_PWM_DWC_CORE) += pwm-dwc-core.o
> > obj-$(CONFIG_PWM_DWC) += pwm-dwc.o
> > obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
> > diff --git a/drivers/pwm/pwm-cv1800.c b/drivers/pwm/pwm-cv1800.c
> > new file mode 100644
> > index 000000000000..8eca07c60942
> > --- /dev/null
> > +++ b/drivers/pwm/pwm-cv1800.c
> > @@ -0,0 +1,315 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Sophgo CV1800 PWM driver
> > + * Author: Jingbao Qiu <[email protected]>
> > + *
> > + * Limitations:
> > + * - It output low when PWM channel disabled.
> > + * - This pwm device supports dynamic loading of PWM parameters. When PWMSTART
> > + * is written from 0 to 1, the register value (HLPERIODn, PERIODn) will be
> > + * temporarily stored inside the PWM. If you want to dynamically change the
> > + * waveform during PWM output, after writing the new value to HLPERIODn and
> > + * PERIODn, write 1 and then 0 to PWMUPDATE[n] to make the new value effective.
> > + * - Supports up to Rate/2 output, and the lowest is about Rate/(2^30-1).
> > + * - By setting HLPERIODn to 0, can produce 100% duty cycle.
> > + * - This hardware could support inverted polarity. By default, the value of the
> > + * POLARITY register is 0x0. This means that HLPERIOD represents the number
> > + * of low level beats.
> > + * - This hardware supports input mode and output mode, implemented through the
> > + * Output-Enable/OE register. However, this driver has not yet implemented
> > + * capture callback.
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pwm.h>
> > +#include <linux/regmap.h>
> > +
> > +#define PWM_CV1800_HLPERIOD_BASE 0x00
> > +#define PWM_CV1800_PERIOD_BASE 0x04
> > +#define PWM_CV1800_POLARITY 0x40
> > +#define PWM_CV1800_START 0x44
> > +#define PWM_CV1800_DONE 0x48
> > +#define PWM_CV1800_UPDATE 0x4c
> > +#define PWM_CV1800_OE 0xd0
> > +
> > +#define PWM_CV1800_HLPERIOD(n) (PWM_CV1800_HLPERIOD_BASE + ((n) * 0x08))
> > +#define PWM_CV1800_PERIOD(n) (PWM_CV1800_PERIOD_BASE + ((n) * 0x08))
> > +
> > +#define PWM_CV1800_UPDATE_MASK(n) (BIT(0) << (n))
> > +#define PWM_CV1800_OE_MASK(n) (BIT(0) << (n))
> > +#define PWM_CV1800_START_MASK(n) (BIT(0) << (n))
> > +#define PWM_CV1800_POLARITY_MASK(n) (BIT(0) << (n))
> > +
> > +#define PWM_CV1800_MAXPERIOD 0x3fffffff
> > +#define PWM_CV1800_MINPERIOD 2
> > +#define PWM_CV1800_CHANNELS 4
> > +#define PWM_CV1800_PERIOD_RESET BIT(1)
> > +#define PWM_CV1800_HLPERIOD_RESET BIT(0)
> > +#define PWM_CV1800_REG_DISABLE 0x00U
> > +#define PWM_CV1800_REG_ENABLE(n) (BIT(0) << (n))
> > +
> > +struct cv1800_pwm {
> > + struct regmap *map;
> > + struct clk *clk;
> > + unsigned long clk_rate;
> > +};
> > +
> > +static inline struct cv1800_pwm *to_cv1800_pwm_dev(struct pwm_chip *chip)
> > +{
> > + return pwmchip_get_drvdata(chip);
> > +}
> > +
> > +static const struct regmap_config cv1800_pwm_regmap_config = {
> > + .reg_bits = 32,
> > + .val_bits = 32,
> > + .reg_stride = 4,
> > +};
> > +
> > +static int cv1800_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm,
> > + bool enable)
> > +{
> > + struct cv1800_pwm *priv = to_cv1800_pwm_dev(chip);
> > + u32 pwm_enable;
> > +
> > + regmap_read(priv->map, PWM_CV1800_START, &pwm_enable);
> > + pwm_enable &= PWM_CV1800_START_MASK(pwm->hwpwm);
> > +
> > + /*
> > + * If the parameters are changed during runtime, Register needs
> > + * to be updated to take effect.
> > + */
> > + if (pwm_enable && enable) {
> > + regmap_update_bits(priv->map, PWM_CV1800_UPDATE,
> > + PWM_CV1800_UPDATE_MASK(pwm->hwpwm),
> > + PWM_CV1800_REG_ENABLE(pwm->hwpwm));
> > + regmap_update_bits(priv->map, PWM_CV1800_UPDATE,
> > + PWM_CV1800_UPDATE_MASK(pwm->hwpwm),
> > + PWM_CV1800_REG_DISABLE);
> > + } else if (!pwm_enable && enable) {
> > + regmap_update_bits(priv->map, PWM_CV1800_START,
> > + PWM_CV1800_START_MASK(pwm->hwpwm),
> > + PWM_CV1800_REG_ENABLE(pwm->hwpwm));
> > + } else if (pwm_enable && !enable) {
> > + regmap_update_bits(priv->map, PWM_CV1800_START,
> > + PWM_CV1800_START_MASK(pwm->hwpwm),
> > + PWM_CV1800_REG_DISABLE);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void cv1800_pwm_set_polarity(struct pwm_chip *chip,
> > + struct pwm_device *pwm,
> > + enum pwm_polarity polarity)
> > +{
> > + struct cv1800_pwm *priv = to_cv1800_pwm_dev(chip);
> > + u32 config_polarity = 0;
> > +
> > + if (pwm->state.enabled)
> > + cv1800_pwm_enable(chip, pwm, !pwm->state.enabled);
> > +
> > + if (polarity == PWM_POLARITY_INVERSED)
> > + config_polarity = PWM_CV1800_POLARITY_MASK(pwm->hwpwm);
> > +
> > + regmap_update_bits(priv->map, PWM_CV1800_POLARITY,
> > + PWM_CV1800_POLARITY_MASK(pwm->hwpwm),
> > + config_polarity);
> > +}
> > +
> > +/**
> > + * cv1800_pwm_set_oe() - check and config nth channal output-enable/OE mode
> > + * @chip: PWM chip
> > + * @pwm: PWM device
> > + * @mode: The nth bit of the mode represents the output-enable/OE mode
> > + * of the nth channal. 1 represents output mode, 0 represents
> > + * input mode.
> > + */
> > +static void cv1800_pwm_set_oe(struct pwm_chip *chip, struct pwm_device *pwm,
> > + u32 mode)
>
> Did you get any information about the capture support pwm controller?
>
> > +{
> > + struct cv1800_pwm *priv = to_cv1800_pwm_dev(chip);
> > + u32 state;
> > +
> > + regmap_read(priv->map, PWM_CV1800_OE, &state);
> > + state &= PWM_CV1800_OE_MASK(pwm->hwpwm);
> > +
> > + if (state == mode)
> > + return;
> > +
> > + /* disenable pwm output before changing output mode */
> > + cv1800_pwm_enable(chip, pwm, false);
> > +
> > + regmap_update_bits(priv->map, PWM_CV1800_OE,
> > + PWM_CV1800_OE_MASK(pwm->hwpwm), mode);
> > +}
> > +
> > +static int cv1800_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > + const struct pwm_state *state)
> > +{
> > + struct cv1800_pwm *priv = to_cv1800_pwm_dev(chip);
> > + u32 period_val, hlperiod_val;
> > + u64 ticks;
> > +
> > + cv1800_pwm_set_oe(chip, pwm, PWM_CV1800_OE_MASK(pwm->hwpwm));
>
> If no capture support, I don't think we need to take care OE, could it
> be done during init?
>

Currently, there is no support for capturing callbacks. you're right,
by default, the
channel is in the output state, it can be initialized as an output
state in the probe.
I will carefully consider your opinion.

Best regards,
Jingbao Qiu