* [PATCH 0/2] pwm: add PWM driver for atcpit100 @ 2024-10-28 10:27 Ben Zong-You Xie 2024-10-28 10:27 ` [PATCH 1/2] dt-bindings: pwm: add atcpit100-pwm Ben Zong-You Xie 2024-10-28 10:27 ` [PATCH 2/2] pwm: atcpit100: add Andes PWM driver support Ben Zong-You Xie 0 siblings, 2 replies; 6+ messages in thread From: Ben Zong-You Xie @ 2024-10-28 10:27 UTC (permalink / raw) To: linux-pwm, devicetree, linux-kernel Cc: ukleinek, robh, krzk+dt, conor+dt, Ben Zong-You Xie The ATCPIT100 Programmable Interval Timer (PIT) is a set of compact multi-function timers, which can be used as pulse width modulators (PWM) as well as simple timers. ATCPIT100 supports up to 4 PIT channels, and each PIT channel may be a simple timer or PWM, or a combination of the timer and the PWM. This patch series includes DT-bindings(1/2) and PWM driver(2/2). Ben Zong-You Xie (2): dt-bindings: pwm: add atcpit100-pwm pwm: atcpit100: add Andes PWM driver support .../bindings/pwm/andestech,atcpit100-pwm.yaml | 52 ++++ MAINTAINERS | 6 + drivers/pwm/Kconfig | 17 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-atcpit100.c | 240 ++++++++++++++++++ 5 files changed, 316 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/andestech,atcpit100-pwm.yaml create mode 100644 drivers/pwm/pwm-atcpit100.c -- 2.34.1 ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/2] dt-bindings: pwm: add atcpit100-pwm 2024-10-28 10:27 [PATCH 0/2] pwm: add PWM driver for atcpit100 Ben Zong-You Xie @ 2024-10-28 10:27 ` Ben Zong-You Xie 2024-10-28 13:08 ` Uwe Kleine-König 2024-10-28 14:42 ` Krzysztof Kozlowski 2024-10-28 10:27 ` [PATCH 2/2] pwm: atcpit100: add Andes PWM driver support Ben Zong-You Xie 1 sibling, 2 replies; 6+ messages in thread From: Ben Zong-You Xie @ 2024-10-28 10:27 UTC (permalink / raw) To: linux-pwm, devicetree, linux-kernel Cc: ukleinek, robh, krzk+dt, conor+dt, Ben Zong-You Xie Document devicetree bindings for Andes atcpit100-pwm. Signed-off-by: Ben Zong-You Xie <ben717@andestech.com> --- .../bindings/pwm/andestech,atcpit100-pwm.yaml | 52 +++++++++++++++++++ MAINTAINERS | 5 ++ 2 files changed, 57 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/andestech,atcpit100-pwm.yaml diff --git a/Documentation/devicetree/bindings/pwm/andestech,atcpit100-pwm.yaml b/Documentation/devicetree/bindings/pwm/andestech,atcpit100-pwm.yaml new file mode 100644 index 000000000000..6952663f134b --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/andestech,atcpit100-pwm.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/andestech,atcpit100-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: PWM driver for Andes atcpit100 + +maintainers: + - Ben Zong-You Xie <ben717@andestech.com> + +allOf: + - $ref: pwm.yaml# + +properties: + compatible: + const: andestech,atcpit100-pwm + + reg: + maxItems: 1 + + "#pwm-cells": + const: 2 + + andestech,clock-source: + description: + Clock Source for each PIT channel. + 0 - external clock + 1 - APB clock. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 4 + maxItems: 4 + items: + minimum: 0 + maximum: 1 + +required: + - compatible + - reg + - "#pwm-cells" + - andestech,clock-source + +unevaluatedProperties: false + +examples: + - | + pwm@f0400000 { + compatible = "andestech,atcpit100-pwm"; + reg = <0xf0400000 0x1000>; + #pwm-cells = <2>; + andestech,clock-source = <0 1 0 0>; + }; diff --git a/MAINTAINERS b/MAINTAINERS index a506fa4f6825..ebbc7edcf077 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3551,6 +3551,11 @@ F: drivers/power/reset/atc260x-poweroff.c F: drivers/regulator/atc260x-regulator.c F: include/linux/mfd/atc260x/* +ATCPIT100 PWM DRIVER +M: Ben Zong-You Xie <ben717@andestech.com> +S: Supported +F: Documentation/devicetree/bindings/pwm/andestech,atcpit100-pwm.yaml + ATHEROS 71XX/9XXX GPIO DRIVER M: Alban Bedel <albeu@free.fr> S: Maintained -- 2.34.1 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] dt-bindings: pwm: add atcpit100-pwm 2024-10-28 10:27 ` [PATCH 1/2] dt-bindings: pwm: add atcpit100-pwm Ben Zong-You Xie @ 2024-10-28 13:08 ` Uwe Kleine-König 2024-10-28 14:42 ` Krzysztof Kozlowski 1 sibling, 0 replies; 6+ messages in thread From: Uwe Kleine-König @ 2024-10-28 13:08 UTC (permalink / raw) To: Ben Zong-You Xie Cc: linux-pwm, devicetree, linux-kernel, robh, krzk+dt, conor+dt [-- Attachment #1: Type: text/plain, Size: 364 bytes --] On Mon, Oct 28, 2024 at 06:27:20PM +0800, Ben Zong-You Xie wrote: > + "#pwm-cells": > + const: 2 Please use 3 here. > + andestech,clock-source: > + description: > + Clock Source for each PIT channel. > + 0 - external clock > + 1 - APB clock. Is this something that could better be represented by assigned-clock-parents? Best regards Uwe [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] dt-bindings: pwm: add atcpit100-pwm 2024-10-28 10:27 ` [PATCH 1/2] dt-bindings: pwm: add atcpit100-pwm Ben Zong-You Xie 2024-10-28 13:08 ` Uwe Kleine-König @ 2024-10-28 14:42 ` Krzysztof Kozlowski 1 sibling, 0 replies; 6+ messages in thread From: Krzysztof Kozlowski @ 2024-10-28 14:42 UTC (permalink / raw) To: Ben Zong-You Xie, linux-pwm, devicetree, linux-kernel Cc: ukleinek, robh, krzk+dt, conor+dt On 28/10/2024 11:27, Ben Zong-You Xie wrote: > +--- > +$id: http://devicetree.org/schemas/pwm/andestech,atcpit100-pwm.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: PWM driver for Andes atcpit100 Driver as Linux driver? > + > +maintainers: > + - Ben Zong-You Xie <ben717@andestech.com> > + > +allOf: > + - $ref: pwm.yaml# > + > +properties: > + compatible: > + const: andestech,atcpit100-pwm > + > + reg: > + maxItems: 1 > + > + "#pwm-cells": > + const: 2 > + > + andestech,clock-source: > + description: > + Clock Source for each PIT channel. > + 0 - external clock > + 1 - APB clock. You don't take APB clock here... neither external. Where is the clocks property? Why assigned-clocks cannot work here? > + $ref: /schemas/types.yaml#/definitions/uint32-array > + minItems: 4 > + maxItems: 4 > + items: > + minimum: 0 > + maximum: 1 Best regards, Krzysztof ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 2/2] pwm: atcpit100: add Andes PWM driver support 2024-10-28 10:27 [PATCH 0/2] pwm: add PWM driver for atcpit100 Ben Zong-You Xie 2024-10-28 10:27 ` [PATCH 1/2] dt-bindings: pwm: add atcpit100-pwm Ben Zong-You Xie @ 2024-10-28 10:27 ` Ben Zong-You Xie 2024-10-28 13:38 ` Uwe Kleine-König 1 sibling, 1 reply; 6+ messages in thread From: Ben Zong-You Xie @ 2024-10-28 10:27 UTC (permalink / raw) To: linux-pwm, devicetree, linux-kernel Cc: ukleinek, robh, krzk+dt, conor+dt, Ben Zong-You Xie Add PWM driver suuport for Andes atcpit100. Signed-off-by: Ben Zong-You Xie <ben717@andestech.com> --- MAINTAINERS | 1 + drivers/pwm/Kconfig | 17 +++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-atcpit100.c | 240 ++++++++++++++++++++++++++++++++++++ 4 files changed, 259 insertions(+) create mode 100644 drivers/pwm/pwm-atcpit100.c diff --git a/MAINTAINERS b/MAINTAINERS index ebbc7edcf077..39c6e1f21339 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3555,6 +3555,7 @@ ATCPIT100 PWM DRIVER M: Ben Zong-You Xie <ben717@andestech.com> S: Supported F: Documentation/devicetree/bindings/pwm/andestech,atcpit100-pwm.yaml +F: drivers/pwm/pwm-atcpit100.c ATHEROS 71XX/9XXX GPIO DRIVER M: Alban Bedel <albeu@free.fr> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 0915c1e7df16..f45ff74fb44e 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -66,6 +66,23 @@ config PWM_APPLE To compile this driver as a module, choose M here: the module will be called pwm-apple. +config PWM_ATCPIT100 + tristate "Andes ATCPIT100 PWM support" + depends on OF && HAS_IOMEM + depends on RISCV || COMPILE_TEST + select REGMAP_MMIO + help + Generic PWM framework driver for ATCPIT100 on Andes AE350 platform + + The ATCPIT100 Programmable Interval Timer (PIT) is a set of compact + multi-function timers, which can be used as pulse width + modulators (PWM) as well as simple timers. ATCPIT100 supports up to 4 + PIT channels. Each PIT channel can be a simple timer or PWM, or a + combination of timer and PWM. + + To compile this driver as a module, choose M here: the module + will be called pwm-atcpit100. + config PWM_ATMEL tristate "Atmel PWM support" depends on ARCH_AT91 || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 9081e0c0e9e0..ad6e803f12d0 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_ADP5585) += pwm-adp5585.o obj-$(CONFIG_PWM_APPLE) += pwm-apple.o +obj-$(CONFIG_PWM_ATCPIT100) += pwm-atcpit100.o obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o diff --git a/drivers/pwm/pwm-atcpit100.c b/drivers/pwm/pwm-atcpit100.c new file mode 100644 index 000000000000..cf83e8702d60 --- /dev/null +++ b/drivers/pwm/pwm-atcpit100.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <linux/err.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> +#include <linux/time.h> +#include <linux/types.h> + +#define ATCPIT100_CHANNEL_MAX 4 +#define ATCPIT100_CHANNEL_ENABLE 0x1C +#define ATCPIT100_CHANNEL_ENABLE_PWM(ch) BIT(3 + (4 * ch)) +#define ATCPIT100_CHANNEL_CTRL(ch) (0x20 + (0x10 * ch)) +#define ATCPIT100_CHANNEL_CTRL_MODE_PWM 0x04 +#define ATCPIT100_CHANNEL_CTRL_CLK BIT(3) +#define ATCPIT100_CHANNEL_CTRL_MASK GENMASK(4, 0) +#define ATCPIT100_CHANNEL_RELOAD(ch) (0x24 + (0x10 * ch)) +#define CLK_EXTERNAL 32768 +#define CLK_APB 60000000 +#define CYCLE_MIN 0x01 +#define CYCLE_MAX 0x010000 + +struct atcpit100_pwm { + struct regmap *regmap; + u32 clk_src[ATCPIT100_CHANNEL_MAX]; +}; + +static const struct regmap_config atcpit100_pwm_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +}; + +static inline struct atcpit100_pwm *to_atcpit100_pwm(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static int of_atcpit100_pwm_set_clk_src(struct atcpit100_pwm *ap, + struct device_node *np) +{ + int ret; + + for (int i = 0; i < ATCPIT100_CHANNEL_MAX; i++) { + ret = of_property_read_u32_index(np, "andestech,clock-source", + i, &(ap->clk_src[i])); + if (ret) + return ret; + } + + return 0; +} + +static int atcpit100_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm, + bool enable) +{ + unsigned int channel = pwm->hwpwm; + unsigned int enable_bit = ATCPIT100_CHANNEL_ENABLE_PWM(channel); + struct atcpit100_pwm *ap = to_atcpit100_pwm(chip); + + pwm->state.enabled = enable; + return regmap_update_bits(ap->regmap, ATCPIT100_CHANNEL_ENABLE, + enable_bit, enable ? enable_bit : 0); +} + +static int atcpit100_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + int ret; + u64 period_cycle; + u64 duty_cycle; + u16 pwm_high; + u16 pwm_low; + struct atcpit100_pwm *ap = to_atcpit100_pwm(chip); + unsigned int ctrl_val = 0; + unsigned int channel = pwm->hwpwm; + u64 rate = ap->clk_src[channel] ? CLK_APB : CLK_EXTERNAL; + + /* cycle count = clock rate * time */ + period_cycle = mul_u64_u64_div_u64(rate, state->period, NSEC_PER_SEC); + duty_cycle = mul_u64_u64_div_u64(rate, state->duty_cycle, + NSEC_PER_SEC); + if (period_cycle < CYCLE_MIN || period_cycle > CYCLE_MAX || + duty_cycle < CYCLE_MIN || duty_cycle > CYCLE_MAX) { + dev_err(pwmchip_parent(chip), + "channel%d: period cycles = 0x%llx, duty cycles = 0x%llx\n", + channel, period_cycle, duty_cycle); + return -EINVAL; + } + + /* + * In the PWM mode, the high period is (PWM16_Hi + 1) cycles, and the + * low period is (PWM16_Lo + 1) cycles. + * For example, if period is 30 cycles and duty_cycle is 10 cycles, + * PWM16_Hi = 10 - 1 = 9, PWM16_Lo = 30 - 10 - 1 = 19. + */ + pwm_high = duty_cycle - 1; + pwm_low = period_cycle - duty_cycle - 1; + + /* Set control register. */ + ctrl_val |= ATCPIT100_CHANNEL_CTRL_MODE_PWM; + ctrl_val |= ap->clk_src[channel] ? ATCPIT100_CHANNEL_CTRL_CLK : 0; + ret = regmap_update_bits(ap->regmap, ATCPIT100_CHANNEL_CTRL(channel), + ATCPIT100_CHANNEL_CTRL_MASK, ctrl_val); + if (ret) + return ret; + + /* Set reload register. */ + ret = regmap_write(ap->regmap, ATCPIT100_CHANNEL_RELOAD(channel), + (pwm_high << 16) | pwm_low); + if (ret) + return ret; + + return 0; +} + +static int atcpit100_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + int ret; + + /* ATCPIT100 PWM driver now only supports normal polarity. */ + if (state->polarity != PWM_POLARITY_NORMAL) { + dev_err(pwmchip_parent(chip), + "only supports normal polarity now\n"); + return -EINVAL; + } + + if (!state->enabled) { + if (pwm->state.enabled) + return atcpit100_pwm_enable(chip, pwm, 0); + + return 0; + } + + ret = atcpit100_pwm_config(chip, pwm, state); + if (ret) + return ret; + + return atcpit100_pwm_enable(chip, pwm, 1); +} + +static int atcpit100_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + int ret; + unsigned int reload_val; + u16 pwm_high; + u16 pwm_low; + unsigned int channel = pwm->hwpwm; + struct atcpit100_pwm *ap = to_atcpit100_pwm(chip); + u64 rate = ap->clk_src[channel] ? CLK_APB : CLK_EXTERNAL; + + state->enabled = + regmap_test_bits(ap->regmap, ATCPIT100_CHANNEL_ENABLE, + ATCPIT100_CHANNEL_ENABLE_PWM(channel)); + state->polarity = PWM_POLARITY_NORMAL; + ret = regmap_read(ap->regmap, ATCPIT100_CHANNEL_RELOAD(channel), + &reload_val); + if (ret) + return ret; + + pwm_high = reload_val >> 16; + pwm_low = reload_val & 0xFFFF; + state->duty_cycle = mul_u64_u64_div_u64(pwm_high + 1, NSEC_PER_SEC, + rate); + state->period = mul_u64_u64_div_u64(pwm_low + pwm_high + 1, + NSEC_PER_SEC, rate); + + return 0; +} + +static const struct pwm_ops atcpit_pwm_ops = { + .apply = atcpit100_pwm_apply, + .get_state = atcpit100_pwm_get_state, +}; + +static int atcpit100_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct atcpit100_pwm *ap; + struct pwm_chip *chip; + void __iomem *base; + int ret; + + chip = devm_pwmchip_alloc(dev, ATCPIT100_CHANNEL_MAX, sizeof(*ap)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + ap = to_atcpit100_pwm(chip); + + /* + * Each channel can select two different clock sources by toggling the + * third bit in its control register. 0 means using an external clock, + * and 1 means using APB clock from APB bus. Select the clock source for + * each channel by DTS. + */ + ret = of_atcpit100_pwm_set_clk_src(ap, np); + if (ret) + return ret; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + ap->regmap = devm_regmap_init_mmio(dev, base, + &atcpit100_pwm_regmap_config); + if (IS_ERR(ap->regmap)) + return dev_err_probe(dev, PTR_ERR(ap->regmap), + "failed to init register map\n"); + + chip->ops = &atcpit_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 atcpit100_pwm_dt[] = { + { .compatible = "andestech,atcpit100-pwm" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, atcpit100_pwm_dt); + +static struct platform_driver atcpit100_pwm_driver = { + .driver = { + .name = "atcpit100-pwm", + .of_match_table = atcpit100_pwm_dt, + }, + .probe = atcpit100_pwm_probe, +}; +module_platform_driver(atcpit100_pwm_driver); +MODULE_AUTHOR("Andes Technology Corporation <ben717@andestech.com>"); +MODULE_DESCRIPTION("Andes ATCPIT100 PWM driver"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 2/2] pwm: atcpit100: add Andes PWM driver support 2024-10-28 10:27 ` [PATCH 2/2] pwm: atcpit100: add Andes PWM driver support Ben Zong-You Xie @ 2024-10-28 13:38 ` Uwe Kleine-König 0 siblings, 0 replies; 6+ messages in thread From: Uwe Kleine-König @ 2024-10-28 13:38 UTC (permalink / raw) To: Ben Zong-You Xie Cc: linux-pwm, devicetree, linux-kernel, robh, krzk+dt, conor+dt [-- Attachment #1: Type: text/plain, Size: 10098 bytes --] On Mon, Oct 28, 2024 at 06:27:21PM +0800, Ben Zong-You Xie wrote: > Add PWM driver suuport for Andes atcpit100. s/uup/upp/ > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile > index 9081e0c0e9e0..ad6e803f12d0 100644 > --- a/drivers/pwm/Makefile > +++ b/drivers/pwm/Makefile > @@ -3,6 +3,7 @@ obj-$(CONFIG_PWM) += core.o > obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o > obj-$(CONFIG_PWM_ADP5585) += pwm-adp5585.o > obj-$(CONFIG_PWM_APPLE) += pwm-apple.o > +obj-$(CONFIG_PWM_ATCPIT100) += pwm-atcpit100.o > obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o > obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o > obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o > diff --git a/drivers/pwm/pwm-atcpit100.c b/drivers/pwm/pwm-atcpit100.c > new file mode 100644 > index 000000000000..cf83e8702d60 > --- /dev/null > +++ b/drivers/pwm/pwm-atcpit100.c > @@ -0,0 +1,240 @@ > +// SPDX-License-Identifier: GPL-2.0-only Please add a comment here to list hardware limitations (such that the gist is available from the output in sed -rn '/Limitations:/,/\*\/?$/p' drivers/pwm/*.c . Check how other newer driver do it.) Questions to answer there (at least) include: - How does the hardware behave when disabled? (Typical: HighZ, constant 0, constant inactive) - Are there glitches during reconfiguration? > +#include <linux/err.h> > +#include <linux/math64.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/platform_device.h> > +#include <linux/pwm.h> > +#include <linux/regmap.h> > +#include <linux/time.h> > +#include <linux/types.h> > + > +#define ATCPIT100_CHANNEL_MAX 4 > +#define ATCPIT100_CHANNEL_ENABLE 0x1C > +#define ATCPIT100_CHANNEL_ENABLE_PWM(ch) BIT(3 + (4 * ch)) > +#define ATCPIT100_CHANNEL_CTRL(ch) (0x20 + (0x10 * ch)) > +#define ATCPIT100_CHANNEL_CTRL_MODE_PWM 0x04 > +#define ATCPIT100_CHANNEL_CTRL_CLK BIT(3) > +#define ATCPIT100_CHANNEL_CTRL_MASK GENMASK(4, 0) > +#define ATCPIT100_CHANNEL_RELOAD(ch) (0x24 + (0x10 * ch)) > +#define CLK_EXTERNAL 32768 > +#define CLK_APB 60000000 > +#define CYCLE_MIN 0x01 > +#define CYCLE_MAX 0x010000 Please give these last 4 constants a name that makes it clear that they belong to this driver. I.e. use "ATCPIT100_" as prefix. CYCLE_MIN is one because you have to write numcycles - 1 to the respective register, right? I would have use a plain 1 in the implementation then. > +struct atcpit100_pwm { > + struct regmap *regmap; > + u32 clk_src[ATCPIT100_CHANNEL_MAX]; > +}; > + > +static const struct regmap_config atcpit100_pwm_regmap_config = { > + .reg_bits = 32, > + .reg_stride = 4, > + .val_bits = 32, > +}; > + > +static inline struct atcpit100_pwm *to_atcpit100_pwm(struct pwm_chip *chip) > +{ > + return pwmchip_get_drvdata(chip); > +} > + > +static int of_atcpit100_pwm_set_clk_src(struct atcpit100_pwm *ap, > + struct device_node *np) > +{ > + int ret; > + > + for (int i = 0; i < ATCPIT100_CHANNEL_MAX; i++) { > + ret = of_property_read_u32_index(np, "andestech,clock-source", > + i, &(ap->clk_src[i])); > + if (ret) > + return ret; > + } > + > + return 0; > +} Instead of having that statically configured in the dtb, it would be beneficial to switch the parent depending on the requested setting. > +static int atcpit100_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm, > + bool enable) > +{ > + unsigned int channel = pwm->hwpwm; > + unsigned int enable_bit = ATCPIT100_CHANNEL_ENABLE_PWM(channel); > + struct atcpit100_pwm *ap = to_atcpit100_pwm(chip); > + > + pwm->state.enabled = enable; The pwm core cares for that. Please drop that assignment. > + return regmap_update_bits(ap->regmap, ATCPIT100_CHANNEL_ENABLE, > + enable_bit, enable ? enable_bit : 0); > +} > + > +static int atcpit100_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, > + const struct pwm_state *state) > +{ > + int ret; > + u64 period_cycle; > + u64 duty_cycle; > + u16 pwm_high; > + u16 pwm_low; > + struct atcpit100_pwm *ap = to_atcpit100_pwm(chip); > + unsigned int ctrl_val = 0; > + unsigned int channel = pwm->hwpwm; > + u64 rate = ap->clk_src[channel] ? CLK_APB : CLK_EXTERNAL; Huh, I would have expected a call to clk_get_rate() here. > + /* cycle count = clock rate * time */ > + period_cycle = mul_u64_u64_div_u64(rate, state->period, NSEC_PER_SEC); > + duty_cycle = mul_u64_u64_div_u64(rate, state->duty_cycle, > + NSEC_PER_SEC); > + if (period_cycle < CYCLE_MIN || period_cycle > CYCLE_MAX || > + duty_cycle < CYCLE_MIN || duty_cycle > CYCLE_MAX) { > + dev_err(pwmchip_parent(chip), > + "channel%d: period cycles = 0x%llx, duty cycles = 0x%llx\n", > + channel, period_cycle, duty_cycle); > + return -EINVAL; > + } Don't error out on period_cycle > CYCLE_MAX or duty_cycle > CYCLE_MAX. Just continue with period_cycle = CYCLE_MAX (and duty_cycle = CYCLE_MAX resp.). > + /* > + * In the PWM mode, the high period is (PWM16_Hi + 1) cycles, and the > + * low period is (PWM16_Lo + 1) cycles. > + * For example, if period is 30 cycles and duty_cycle is 10 cycles, > + * PWM16_Hi = 10 - 1 = 9, PWM16_Lo = 30 - 10 - 1 = 19. > + */ > + pwm_high = duty_cycle - 1; > + pwm_low = period_cycle - duty_cycle - 1; If period_cycle == duty_cycle surprising things happen? I guess the hardware can neither do a 0% nor a 100% relative duty cycle? That's something to document in the above mentioned Limitations paragraph. > + > + /* Set control register. */ > + ctrl_val |= ATCPIT100_CHANNEL_CTRL_MODE_PWM; > + ctrl_val |= ap->clk_src[channel] ? ATCPIT100_CHANNEL_CTRL_CLK : 0; > + ret = regmap_update_bits(ap->regmap, ATCPIT100_CHANNEL_CTRL(channel), > + ATCPIT100_CHANNEL_CTRL_MASK, ctrl_val); > + if (ret) > + return ret; What happens to the output here? I guess it might already change and so it might emit a wave form that is neither the old nor the new one? (=> Limitations) > + > + /* Set reload register. */ > + ret = regmap_write(ap->regmap, ATCPIT100_CHANNEL_RELOAD(channel), > + (pwm_high << 16) | pwm_low); Please define proper bitfield accessors. E.g. ATCPIT100_CHANNEL_RELOAD_HIGH GENMASK(31, 16) ... and then use FIELD_PREP to assign. > + if (ret) > + return ret; > + > + return 0; This can be abbreviated to return regmap_write(....); > +} > + > +static int atcpit100_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, > + const struct pwm_state *state) > +{ > + int ret; > + > + /* ATCPIT100 PWM driver now only supports normal polarity. */ > + if (state->polarity != PWM_POLARITY_NORMAL) { > + dev_err(pwmchip_parent(chip), > + "only supports normal polarity now\n"); No error message in .apply() please. Returning an error code is enough. > + return -EINVAL; > + } > + > + if (!state->enabled) { > + if (pwm->state.enabled) > + return atcpit100_pwm_enable(chip, pwm, 0); > + > + return 0; > + } > + > + ret = atcpit100_pwm_config(chip, pwm, state); > + if (ret) > + return ret; > + > + return atcpit100_pwm_enable(chip, pwm, 1); > +} > + > +static int atcpit100_pwm_get_state(struct pwm_chip *chip, > + struct pwm_device *pwm, > + struct pwm_state *state) > +{ > + int ret; > + unsigned int reload_val; > + u16 pwm_high; > + u16 pwm_low; > + unsigned int channel = pwm->hwpwm; > + struct atcpit100_pwm *ap = to_atcpit100_pwm(chip); > + u64 rate = ap->clk_src[channel] ? CLK_APB : CLK_EXTERNAL; > + > + state->enabled = > + regmap_test_bits(ap->regmap, ATCPIT100_CHANNEL_ENABLE, > + ATCPIT100_CHANNEL_ENABLE_PWM(channel)); > + state->polarity = PWM_POLARITY_NORMAL; > + ret = regmap_read(ap->regmap, ATCPIT100_CHANNEL_RELOAD(channel), > + &reload_val); > + if (ret) > + return ret; > + > + pwm_high = reload_val >> 16; > + pwm_low = reload_val & 0xFFFF; > + state->duty_cycle = mul_u64_u64_div_u64(pwm_high + 1, NSEC_PER_SEC, > + rate); > + state->period = mul_u64_u64_div_u64(pwm_low + pwm_high + 1, > + NSEC_PER_SEC, rate); > + Please enable PWM_DEBUG and test extensively. Hint: You have to round up here. > + return 0; > +} > + > +static const struct pwm_ops atcpit_pwm_ops = { > + .apply = atcpit100_pwm_apply, > + .get_state = atcpit100_pwm_get_state, > +}; > + > +static int atcpit100_pwm_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct device_node *np = dev->of_node; > + struct atcpit100_pwm *ap; > + struct pwm_chip *chip; > + void __iomem *base; > + int ret; > + > + chip = devm_pwmchip_alloc(dev, ATCPIT100_CHANNEL_MAX, sizeof(*ap)); > + if (IS_ERR(chip)) > + return PTR_ERR(chip); > + > + ap = to_atcpit100_pwm(chip); > + > + /* > + * Each channel can select two different clock sources by toggling the > + * third bit in its control register. 0 means using an external clock, > + * and 1 means using APB clock from APB bus. Select the clock source for > + * each channel by DTS. > + */ > + ret = of_atcpit100_pwm_set_clk_src(ap, np); > + if (ret) > + return ret; > + > + base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(base)) > + return PTR_ERR(base); > + > + ap->regmap = devm_regmap_init_mmio(dev, base, > + &atcpit100_pwm_regmap_config); > + if (IS_ERR(ap->regmap)) > + return dev_err_probe(dev, PTR_ERR(ap->regmap), > + "failed to init register map\n"); > + > + chip->ops = &atcpit_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 atcpit100_pwm_dt[] = { > + { .compatible = "andestech,atcpit100-pwm" }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, atcpit100_pwm_dt); > + > +static struct platform_driver atcpit100_pwm_driver = { > + .driver = { > + .name = "atcpit100-pwm", > + .of_match_table = atcpit100_pwm_dt, > + }, > + .probe = atcpit100_pwm_probe, > +}; > +module_platform_driver(atcpit100_pwm_driver); empty new line here > +MODULE_AUTHOR("Andes Technology Corporation <ben717@andestech.com>"); I would have expected your name here given that's also your email address. > +MODULE_DESCRIPTION("Andes ATCPIT100 PWM driver"); > +MODULE_LICENSE("GPL"); Best regards Uwe [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2024-10-28 14:42 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2024-10-28 10:27 [PATCH 0/2] pwm: add PWM driver for atcpit100 Ben Zong-You Xie 2024-10-28 10:27 ` [PATCH 1/2] dt-bindings: pwm: add atcpit100-pwm Ben Zong-You Xie 2024-10-28 13:08 ` Uwe Kleine-König 2024-10-28 14:42 ` Krzysztof Kozlowski 2024-10-28 10:27 ` [PATCH 2/2] pwm: atcpit100: add Andes PWM driver support Ben Zong-You Xie 2024-10-28 13:38 ` Uwe Kleine-König
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox