All of lore.kernel.org
 help / color / mirror / Atom feed
From: Maxime Ripard <maxime.ripard-LDxbnhwyfcJBDgjK7y7TUQ@public.gmane.org>
To: Hao Zhang <hao5781286-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Cc: robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org,
	mark.rutland-5wv7dgnIgG8@public.gmane.org,
	wens-jdAy2FN1RRM@public.gmane.org,
	mturquette-rdvid1DuHRBWk0Htik3J/w@public.gmane.org,
	sboyd-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org,
	thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
	linux-pwm-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org
Subject: Re: [PATCH v3 6/6] ARM: PWM: add allwinner sun8i R40/T3/V40 PWM support.
Date: Tue, 27 Nov 2018 09:07:33 +0100	[thread overview]
Message-ID: <20181127080733.4e6rljjta3azf2rf@flea> (raw)
In-Reply-To: <20181125162319.GA5422@arx-s1>

[-- Attachment #1: Type: text/plain, Size: 12520 bytes --]

Hi!

On Mon, Nov 26, 2018 at 12:23:19AM +0800, Hao Zhang wrote:
> The sun8i R40/T3/V40 PWM has 8 PWM channals and divides to 4 PWM pairs,
> each PWM pair built-in 1 clock module, 2 timer logic module and 1
> programmable dead-time generator, it also support waveform capture.
> It has 2 clock sources OSC24M and APB1, it is different with the
> sun4i-pwm driver, Therefore add a new driver for it.
> 
> Signed-off-by: Hao Zhang <hao5781286-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
>  drivers/pwm/Kconfig     |  12 +-
>  drivers/pwm/Makefile    |   1 +
>  drivers/pwm/pwm-sun8i.c | 418 ++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 430 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/pwm/pwm-sun8i.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 504d252..6105ac8 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -426,7 +426,7 @@ config PWM_STMPE
>  	  expanders.
>  
>  config PWM_SUN4I
> -	tristate "Allwinner PWM support"
> +	tristate "Allwinner SUN4I PWM support"

That should be a separate patch.

(also, your patch series don't seem to have the threading properly
configured, you might want to fix that.)

>  	depends on ARCH_SUNXI || COMPILE_TEST
>  	depends on HAS_IOMEM && COMMON_CLK
>  	help
> @@ -435,6 +435,16 @@ config PWM_SUN4I
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-sun4i.
>  
> +config PWM_SUN8I
> +	tristate "Allwinner SUN8I (R40/V40/T3) PWM support"
> +	depends on ARCH_SUNXI || COMPILE_TEST
> +	depends on HAS_IOMEM && COMMON_CLK
> +	help
> +	  Generic PWM framework driver for Allwinner R40/V40/T3 SoCs.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm-sun8i.
> +

sun8i here (and in the rest of the driver) is too vague. There's
already plenty of SoCs part of the sun8i family that are supported by
the other driver. sun8i-r40 would be a better fit (and there's no need
to mention all the rebranding that allwinner has done with the R40,
just use R40).

>  config PWM_TEGRA
>  	tristate "NVIDIA Tegra PWM support"
>  	depends on ARCH_TEGRA
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 9c676a0..32c8d2d 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -43,6 +43,7 @@ obj-$(CONFIG_PWM_STM32)		+= pwm-stm32.o
>  obj-$(CONFIG_PWM_STM32_LP)	+= pwm-stm32-lp.o
>  obj-$(CONFIG_PWM_STMPE)		+= pwm-stmpe.o
>  obj-$(CONFIG_PWM_SUN4I)		+= pwm-sun4i.o
> +obj-$(CONFIG_PWM_SUN8I)		+= pwm-sun8i.o
>  obj-$(CONFIG_PWM_TEGRA)		+= pwm-tegra.o
>  obj-$(CONFIG_PWM_TIECAP)	+= pwm-tiecap.o
>  obj-$(CONFIG_PWM_TIEHRPWM)	+= pwm-tiehrpwm.o
> diff --git a/drivers/pwm/pwm-sun8i.c b/drivers/pwm/pwm-sun8i.c
> new file mode 100644
> index 0000000..d8597e4
> --- /dev/null
> +++ b/drivers/pwm/pwm-sun8i.c
> @@ -0,0 +1,418 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// Copyright (C) 2018 Hao Zhang <hao5781286-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> +
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/time.h>
> +#include <linux/regmap.h>
> +
> +#define PWM_IRQ_ENABLE_REG	0x0000
> +#define PCIE(ch)		BIT(ch)
> +
> +#define PWM_IRQ_STATUS_REG	0x0004
> +#define PIS(ch)			BIT(ch)
> +
> +#define CAPTURE_IRQ_ENABLE_REG	0x0010
> +#define CRIE(ch)		BIT((ch) * 2)
> +#define CFIE(ch)		BIT((ch) * 2 + 1)
> +
> +#define CAPTURE_IRQ_STATUS_REG	0x0014
> +#define CRIS(ch)		BIT((ch) * 2)
> +#define CFIS(ch)		BIT((ch) * 2 + 1)
> +
> +#define CLK_CFG_REG(ch)		(0x0020 + ((ch) >> 1) * 4)
> +#define CLK_SRC_SEL		GENMASK(8, 7)
> +#define CLK_SRC_BYPASS_SEC	BIT(6)
> +#define CLK_SRC_BYPASS_FIR	BIT(5)
> +#define CLK_GATING		BIT(4)
> +#define CLK_DIV_M		GENMASK(3, 0)
> +
> +#define PWM_DZ_CTR_REG(ch)	(0x0030 + ((ch) >> 1) * 4)
> +#define PWM_DZ_INTV		GENMASK(15, 8)
> +#define PWM_DZ_EN		BIT(0)
> +
> +#define PWM_ENABLE_REG		0x0040
> +#define PWM_EN(ch)		BIT(ch)
> +
> +#define CAPTURE_ENABLE_REG	0x0044
> +#define CAP_EN(ch)		BIT(ch)
> +
> +#define PWM_CTR_REG(ch)		(0x0060 + (ch) * 0x20)
> +#define PWM_PERIOD_RDY		BIT(11)
> +#define PWM_PUL_START		BIT(10)
> +#define PWM_MODE		BIT(9)
> +#define PWM_ACT_STA		BIT(8)
> +#define PWM_PRESCAL_K		GENMASK(7, 0)
> +
> +#define PWM_PERIOD_REG(ch)	(0x0064 + (ch) * 0x20)
> +#define PWM_ENTIRE_CYCLE	GENMASK(31, 16)
> +#define PWM_ACT_CYCLE		GENMASK(15, 0)
> +
> +#define PWM_CNT_REG(ch)		(0x0068 + (ch) * 0x20)
> +#define PWM_CNT_VAL		GENMASK(15, 0)
> +
> +#define CAPTURE_CTR_REG(ch)	(0x006c + (ch) * 0x20)
> +#define CAPTURE_CRLF		BIT(2)
> +#define CAPTURE_CFLF		BIT(1)
> +#define CAPINV			BIT(0)
> +
> +#define CAPTURE_RISE_REG(ch)	(0x0070 + (ch) * 0x20)
> +#define CAPTURE_CRLR		GENMASK(15, 0)
> +
> +#define CAPTURE_FALL_REG(ch)	(0x0074 + (ch) * 0x20)
> +#define CAPTURE_CFLR		GENMASK(15, 0)
> +
> +struct sun8i_pwm_chip {
> +	struct pwm_chip chip;
> +	struct clk *clk;
> +	void __iomem *base;
> +	const struct sun8i_pwm_data *data;
> +	struct regmap *regmap;
> +};
> +
> +static struct sun8i_pwm_chip *to_sun8i_pwm_chip(struct pwm_chip *chip)
> +{
> +	return container_of(chip, struct sun8i_pwm_chip, chip);
> +}
> +
> +static u32 sun8i_pwm_read(struct sun8i_pwm_chip *sun8i_pwm,
> +			  unsigned long offset)
> +{
> +	u32 val;
> +
> +	regmap_read(sun8i_pwm->regmap, offset, &val);
> +	return val;
> +}
> +
> +static void sun8i_pwm_set_bit(struct sun8i_pwm_chip *sun8i_pwm,
> +			      unsigned long reg, u32 bit)
> +{
> +	regmap_update_bits(sun8i_pwm->regmap, reg, bit, bit);
> +}
> +
> +static void sun8i_pwm_clear_bit(struct sun8i_pwm_chip *sun8i_pwm,
> +				unsigned long reg, u32 bit)
> +{
> +	regmap_update_bits(sun8i_pwm->regmap, reg, bit, 0);
> +}
> +
> +static void sun8i_pwm_set_value(struct sun8i_pwm_chip *sun8i_pwm,
> +				unsigned long reg, u32 mask, u32 val)
> +{
> +	regmap_update_bits(sun8i_pwm->regmap, reg, mask, val);
> +}
> +
> +static void sun8i_pwm_set_polarity(struct sun8i_pwm_chip *chip, u32 ch,
> +				   enum pwm_polarity polarity)
> +{
> +	if (polarity == PWM_POLARITY_NORMAL)
> +		sun8i_pwm_set_bit(chip, PWM_CTR_REG(ch), PWM_ACT_STA);
> +	else
> +		sun8i_pwm_clear_bit(chip, PWM_CTR_REG(ch), PWM_ACT_STA);
> +}
> +
> +static int sun8i_pwm_config(struct sun8i_pwm_chip *sun8i_pwm, u8 ch,
> +			    struct pwm_state *state)
> +{
> +	u64 clk_rate, clk_div, val;
> +	u16 prescaler = 0;
> +	u16 div_id = 0;
> +	struct clk *clk;
> +	bool is_clk;
> +	int ret;
> +
> +	clk_rate = clk_get_rate(sun8i_pwm->clk);
> +
> +	/* check period and select clock source */
> +	val = state->period * clk_rate;
> +	do_div(val, NSEC_PER_SEC);
> +	is_clk = devm_clk_name_match(sun8i_pwm->clk, "mux-0");
> +	if (val <= 1 && is_clk) {
> +		clk = devm_clk_get(sun8i_pwm->chip.dev, "mux-1");
> +		if (IS_ERR(clk)) {
> +			dev_err(sun8i_pwm->chip.dev,
> +				"Period expects a larger value\n");
> +			return -EINVAL;
> +		}
> +
> +		clk_rate = clk_get_rate(clk);
> +		val = state->period * clk_rate;
> +		do_div(val, NSEC_PER_SEC);
> +		if (val <= 1) {
> +			dev_err(sun8i_pwm->chip.dev,
> +				"Period expects a larger value\n");
> +			return -EINVAL;
> +		}
> +
> +		/* change clock source to "mux-1" */
> +		clk_disable_unprepare(sun8i_pwm->clk);
> +		devm_clk_put(sun8i_pwm->chip.dev, sun8i_pwm->clk);
> +		sun8i_pwm->clk = clk;
> +
> +		ret = clk_prepare_enable(sun8i_pwm->clk);
> +		if (ret) {
> +			dev_err(sun8i_pwm->chip.dev,
> +				"Failed to enable PWM clock\n");
> +			return ret;
> +		}
> +
> +	} else {
> +		dev_err(sun8i_pwm->chip.dev,
> +			"Period expects a larger value\n");
> +		return -EINVAL;
> +	}
> +
> +	is_clk = devm_clk_name_match(sun8i_pwm->clk, "mux-0");
> +	if (is_clk)
> +		sun8i_pwm_set_value(sun8i_pwm, CLK_CFG_REG(ch),
> +				    CLK_SRC_SEL, 0 << 7);
> +	else
> +		sun8i_pwm_set_value(sun8i_pwm, CLK_CFG_REG(ch),
> +				    CLK_SRC_SEL, 1 << 7);
> +
> +	dev_info(sun8i_pwm->chip.dev, "clock source freq:%lldHz\n", clk_rate);

This is pretty much reimplementing the clock framework. I guess you'd
be better off just modeling this clock as a clock registered in the
framework. It will take care by itself of the combination of muxing
and rate, and making sure the parent clocks are properly enabled when
needed.

> +	/* calculate and set prescaler, div table, PWM entire cycle */
> +	clk_div = val;
> +	while (clk_div > 65535) {
> +		prescaler++;
> +		clk_div = val;
> +		do_div(clk_div, 1U << div_id);
> +		do_div(clk_div, prescaler + 1);
> +
> +		if (prescaler == 255) {
> +			prescaler = 0;
> +			div_id++;
> +			if (div_id == 9) {
> +				dev_err(sun8i_pwm->chip.dev,
> +					"unsupport period value\n");
> +				return -EINVAL;
> +			}
> +		}
> +	}
> +
> +	sun8i_pwm_set_value(sun8i_pwm, PWM_PERIOD_REG(ch),
> +			    PWM_ENTIRE_CYCLE, clk_div << 16);
> +	sun8i_pwm_set_value(sun8i_pwm, PWM_CTR_REG(ch),
> +			    PWM_PRESCAL_K, prescaler << 0);
> +	sun8i_pwm_set_value(sun8i_pwm, CLK_CFG_REG(ch),
> +			    CLK_DIV_M, div_id << 0);
> +
> +	/* set duty cycle */
> +	val = state->period;
> +	do_div(val, clk_div);
> +	clk_div = state->duty_cycle;
> +	do_div(clk_div, val);
> +	if (clk_div > 65535)
> +		clk_div = 65535;
> +
> +	sun8i_pwm_set_value(sun8i_pwm, PWM_PERIOD_REG(ch),
> +			    PWM_ACT_CYCLE, clk_div << 0);
> +
> +	return 0;
> +}
> +
> +static int sun8i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> +			   struct pwm_state *state)
> +{
> +	int ret;
> +	struct sun8i_pwm_chip *sun8i_pwm = to_sun8i_pwm_chip(chip);
> +	struct pwm_state cstate;
> +
> +	pwm_get_state(pwm, &cstate);
> +	if (!cstate.enabled) {
> +		ret = clk_prepare_enable(sun8i_pwm->clk);
> +		if (ret) {
> +			dev_err(chip->dev, "Failed to enable PWM clock\n");
> +			return ret;
> +		}
> +	}
> +
> +	if ((cstate.period != state->period) ||
> +	    (cstate.duty_cycle != state->duty_cycle)) {
> +		ret = sun8i_pwm_config(sun8i_pwm, pwm->hwpwm, state);
> +		if (ret) {
> +			dev_err(chip->dev, "Failed to config PWM\n");
> +			return ret;
> +		}
> +	}
> +
> +	if (state->polarity != cstate.polarity)
> +		sun8i_pwm_set_polarity(sun8i_pwm, pwm->hwpwm, state->polarity);
> +
> +	if (state->enabled) {
> +		sun8i_pwm_set_bit(sun8i_pwm,
> +				  CLK_CFG_REG(pwm->hwpwm), CLK_GATING);
> +
> +		sun8i_pwm_set_bit(sun8i_pwm,
> +				  PWM_ENABLE_REG, PWM_EN(pwm->hwpwm));
> +	} else {
> +		sun8i_pwm_clear_bit(sun8i_pwm,
> +				    CLK_CFG_REG(pwm->hwpwm), CLK_GATING);
> +
> +		sun8i_pwm_clear_bit(sun8i_pwm,
> +				    PWM_ENABLE_REG, PWM_EN(pwm->hwpwm));
> +	}
> +
> +	return 0;
> +}
> +
> +static void sun8i_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> +				struct pwm_state *state)
> +{
> +	struct sun8i_pwm_chip *sun8i_pwm = to_sun8i_pwm_chip(chip);
> +	u64 clk_rate, tmp;
> +	u32 val;
> +	u16 clk_div, act_cycle;
> +	u8 prescal, div_id;
> +	u8 chn = pwm->hwpwm;
> +
> +	clk_rate = clk_get_rate(sun8i_pwm->clk);
> +
> +	val = sun8i_pwm_read(sun8i_pwm, PWM_CTR_REG(chn));
> +	if (PWM_ACT_STA & val)
> +		state->polarity = PWM_POLARITY_NORMAL;
> +	else
> +		state->polarity = PWM_POLARITY_INVERSED;
> +
> +	prescal = PWM_PRESCAL_K & val;
> +
> +	val = sun8i_pwm_read(sun8i_pwm, PWM_ENABLE_REG);
> +	if (PWM_EN(chn) & val)
> +		state->enabled = true;
> +	else
> +		state->enabled = false;
> +
> +	val = sun8i_pwm_read(sun8i_pwm, PWM_PERIOD_REG(chn));
> +	act_cycle = PWM_ACT_CYCLE & val;
> +	clk_div = val >> 16;
> +
> +	val = sun8i_pwm_read(sun8i_pwm, CLK_CFG_REG(chn));
> +	div_id = CLK_DIV_M & val;
> +
> +	tmp = act_cycle * prescal * (1U << div_id) * NSEC_PER_SEC;
> +	state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
> +	tmp = clk_div * prescal * (1U << div_id) * NSEC_PER_SEC;
> +	state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
> +}
> +
> +static const struct regmap_config sun8i_pwm_regmap_config = {
> +	.reg_bits = 32,
> +	.reg_stride = 4,
> +	.val_bits = 32,
> +	.max_register = CAPTURE_FALL_REG(7),
> +};
> +
> +static const struct pwm_ops sun8i_pwm_ops = {
> +	.apply = sun8i_pwm_apply,
> +	.get_state = sun8i_pwm_get_state,
> +	.owner = THIS_MODULE,
> +};
> +
> +static const struct of_device_id sun8i_pwm_dt_ids[] = {
> +	{
> +		.compatible = "allwinner,sun8i-r40-pwm",
> +		.data = NULL,

Do you really need that field if you leave it NULL?

Thanks!
Maxime

-- 
Maxime Ripard, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

WARNING: multiple messages have this Message-ID (diff)
From: Maxime Ripard <maxime.ripard@bootlin.com>
To: Hao Zhang <hao5781286@gmail.com>
Cc: robh+dt@kernel.org, mark.rutland@arm.com, wens@csie.org,
	mturquette@baylibre.com, sboyd@kernel.org,
	thierry.reding@gmail.com, linux-gpio@vger.kernel.org,
	linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org, linux-pwm@vger.kernel.org,
	linux-sunxi@googlegroups.com
Subject: Re: [PATCH v3 6/6] ARM: PWM: add allwinner sun8i R40/T3/V40 PWM support.
Date: Tue, 27 Nov 2018 09:07:33 +0100	[thread overview]
Message-ID: <20181127080733.4e6rljjta3azf2rf@flea> (raw)
In-Reply-To: <20181125162319.GA5422@arx-s1>

[-- Attachment #1: Type: text/plain, Size: 12881 bytes --]

Hi!

On Mon, Nov 26, 2018 at 12:23:19AM +0800, Hao Zhang wrote:
> The sun8i R40/T3/V40 PWM has 8 PWM channals and divides to 4 PWM pairs,
> each PWM pair built-in 1 clock module, 2 timer logic module and 1
> programmable dead-time generator, it also support waveform capture.
> It has 2 clock sources OSC24M and APB1, it is different with the
> sun4i-pwm driver, Therefore add a new driver for it.
> 
> Signed-off-by: Hao Zhang <hao5781286@gmail.com>
> ---
>  drivers/pwm/Kconfig     |  12 +-
>  drivers/pwm/Makefile    |   1 +
>  drivers/pwm/pwm-sun8i.c | 418 ++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 430 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/pwm/pwm-sun8i.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 504d252..6105ac8 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -426,7 +426,7 @@ config PWM_STMPE
>  	  expanders.
>  
>  config PWM_SUN4I
> -	tristate "Allwinner PWM support"
> +	tristate "Allwinner SUN4I PWM support"

That should be a separate patch.

(also, your patch series don't seem to have the threading properly
configured, you might want to fix that.)

>  	depends on ARCH_SUNXI || COMPILE_TEST
>  	depends on HAS_IOMEM && COMMON_CLK
>  	help
> @@ -435,6 +435,16 @@ config PWM_SUN4I
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-sun4i.
>  
> +config PWM_SUN8I
> +	tristate "Allwinner SUN8I (R40/V40/T3) PWM support"
> +	depends on ARCH_SUNXI || COMPILE_TEST
> +	depends on HAS_IOMEM && COMMON_CLK
> +	help
> +	  Generic PWM framework driver for Allwinner R40/V40/T3 SoCs.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm-sun8i.
> +

sun8i here (and in the rest of the driver) is too vague. There's
already plenty of SoCs part of the sun8i family that are supported by
the other driver. sun8i-r40 would be a better fit (and there's no need
to mention all the rebranding that allwinner has done with the R40,
just use R40).

>  config PWM_TEGRA
>  	tristate "NVIDIA Tegra PWM support"
>  	depends on ARCH_TEGRA
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 9c676a0..32c8d2d 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -43,6 +43,7 @@ obj-$(CONFIG_PWM_STM32)		+= pwm-stm32.o
>  obj-$(CONFIG_PWM_STM32_LP)	+= pwm-stm32-lp.o
>  obj-$(CONFIG_PWM_STMPE)		+= pwm-stmpe.o
>  obj-$(CONFIG_PWM_SUN4I)		+= pwm-sun4i.o
> +obj-$(CONFIG_PWM_SUN8I)		+= pwm-sun8i.o
>  obj-$(CONFIG_PWM_TEGRA)		+= pwm-tegra.o
>  obj-$(CONFIG_PWM_TIECAP)	+= pwm-tiecap.o
>  obj-$(CONFIG_PWM_TIEHRPWM)	+= pwm-tiehrpwm.o
> diff --git a/drivers/pwm/pwm-sun8i.c b/drivers/pwm/pwm-sun8i.c
> new file mode 100644
> index 0000000..d8597e4
> --- /dev/null
> +++ b/drivers/pwm/pwm-sun8i.c
> @@ -0,0 +1,418 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// Copyright (C) 2018 Hao Zhang <hao5781286@gmail.com>
> +
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/time.h>
> +#include <linux/regmap.h>
> +
> +#define PWM_IRQ_ENABLE_REG	0x0000
> +#define PCIE(ch)		BIT(ch)
> +
> +#define PWM_IRQ_STATUS_REG	0x0004
> +#define PIS(ch)			BIT(ch)
> +
> +#define CAPTURE_IRQ_ENABLE_REG	0x0010
> +#define CRIE(ch)		BIT((ch) * 2)
> +#define CFIE(ch)		BIT((ch) * 2 + 1)
> +
> +#define CAPTURE_IRQ_STATUS_REG	0x0014
> +#define CRIS(ch)		BIT((ch) * 2)
> +#define CFIS(ch)		BIT((ch) * 2 + 1)
> +
> +#define CLK_CFG_REG(ch)		(0x0020 + ((ch) >> 1) * 4)
> +#define CLK_SRC_SEL		GENMASK(8, 7)
> +#define CLK_SRC_BYPASS_SEC	BIT(6)
> +#define CLK_SRC_BYPASS_FIR	BIT(5)
> +#define CLK_GATING		BIT(4)
> +#define CLK_DIV_M		GENMASK(3, 0)
> +
> +#define PWM_DZ_CTR_REG(ch)	(0x0030 + ((ch) >> 1) * 4)
> +#define PWM_DZ_INTV		GENMASK(15, 8)
> +#define PWM_DZ_EN		BIT(0)
> +
> +#define PWM_ENABLE_REG		0x0040
> +#define PWM_EN(ch)		BIT(ch)
> +
> +#define CAPTURE_ENABLE_REG	0x0044
> +#define CAP_EN(ch)		BIT(ch)
> +
> +#define PWM_CTR_REG(ch)		(0x0060 + (ch) * 0x20)
> +#define PWM_PERIOD_RDY		BIT(11)
> +#define PWM_PUL_START		BIT(10)
> +#define PWM_MODE		BIT(9)
> +#define PWM_ACT_STA		BIT(8)
> +#define PWM_PRESCAL_K		GENMASK(7, 0)
> +
> +#define PWM_PERIOD_REG(ch)	(0x0064 + (ch) * 0x20)
> +#define PWM_ENTIRE_CYCLE	GENMASK(31, 16)
> +#define PWM_ACT_CYCLE		GENMASK(15, 0)
> +
> +#define PWM_CNT_REG(ch)		(0x0068 + (ch) * 0x20)
> +#define PWM_CNT_VAL		GENMASK(15, 0)
> +
> +#define CAPTURE_CTR_REG(ch)	(0x006c + (ch) * 0x20)
> +#define CAPTURE_CRLF		BIT(2)
> +#define CAPTURE_CFLF		BIT(1)
> +#define CAPINV			BIT(0)
> +
> +#define CAPTURE_RISE_REG(ch)	(0x0070 + (ch) * 0x20)
> +#define CAPTURE_CRLR		GENMASK(15, 0)
> +
> +#define CAPTURE_FALL_REG(ch)	(0x0074 + (ch) * 0x20)
> +#define CAPTURE_CFLR		GENMASK(15, 0)
> +
> +struct sun8i_pwm_chip {
> +	struct pwm_chip chip;
> +	struct clk *clk;
> +	void __iomem *base;
> +	const struct sun8i_pwm_data *data;
> +	struct regmap *regmap;
> +};
> +
> +static struct sun8i_pwm_chip *to_sun8i_pwm_chip(struct pwm_chip *chip)
> +{
> +	return container_of(chip, struct sun8i_pwm_chip, chip);
> +}
> +
> +static u32 sun8i_pwm_read(struct sun8i_pwm_chip *sun8i_pwm,
> +			  unsigned long offset)
> +{
> +	u32 val;
> +
> +	regmap_read(sun8i_pwm->regmap, offset, &val);
> +	return val;
> +}
> +
> +static void sun8i_pwm_set_bit(struct sun8i_pwm_chip *sun8i_pwm,
> +			      unsigned long reg, u32 bit)
> +{
> +	regmap_update_bits(sun8i_pwm->regmap, reg, bit, bit);
> +}
> +
> +static void sun8i_pwm_clear_bit(struct sun8i_pwm_chip *sun8i_pwm,
> +				unsigned long reg, u32 bit)
> +{
> +	regmap_update_bits(sun8i_pwm->regmap, reg, bit, 0);
> +}
> +
> +static void sun8i_pwm_set_value(struct sun8i_pwm_chip *sun8i_pwm,
> +				unsigned long reg, u32 mask, u32 val)
> +{
> +	regmap_update_bits(sun8i_pwm->regmap, reg, mask, val);
> +}
> +
> +static void sun8i_pwm_set_polarity(struct sun8i_pwm_chip *chip, u32 ch,
> +				   enum pwm_polarity polarity)
> +{
> +	if (polarity == PWM_POLARITY_NORMAL)
> +		sun8i_pwm_set_bit(chip, PWM_CTR_REG(ch), PWM_ACT_STA);
> +	else
> +		sun8i_pwm_clear_bit(chip, PWM_CTR_REG(ch), PWM_ACT_STA);
> +}
> +
> +static int sun8i_pwm_config(struct sun8i_pwm_chip *sun8i_pwm, u8 ch,
> +			    struct pwm_state *state)
> +{
> +	u64 clk_rate, clk_div, val;
> +	u16 prescaler = 0;
> +	u16 div_id = 0;
> +	struct clk *clk;
> +	bool is_clk;
> +	int ret;
> +
> +	clk_rate = clk_get_rate(sun8i_pwm->clk);
> +
> +	/* check period and select clock source */
> +	val = state->period * clk_rate;
> +	do_div(val, NSEC_PER_SEC);
> +	is_clk = devm_clk_name_match(sun8i_pwm->clk, "mux-0");
> +	if (val <= 1 && is_clk) {
> +		clk = devm_clk_get(sun8i_pwm->chip.dev, "mux-1");
> +		if (IS_ERR(clk)) {
> +			dev_err(sun8i_pwm->chip.dev,
> +				"Period expects a larger value\n");
> +			return -EINVAL;
> +		}
> +
> +		clk_rate = clk_get_rate(clk);
> +		val = state->period * clk_rate;
> +		do_div(val, NSEC_PER_SEC);
> +		if (val <= 1) {
> +			dev_err(sun8i_pwm->chip.dev,
> +				"Period expects a larger value\n");
> +			return -EINVAL;
> +		}
> +
> +		/* change clock source to "mux-1" */
> +		clk_disable_unprepare(sun8i_pwm->clk);
> +		devm_clk_put(sun8i_pwm->chip.dev, sun8i_pwm->clk);
> +		sun8i_pwm->clk = clk;
> +
> +		ret = clk_prepare_enable(sun8i_pwm->clk);
> +		if (ret) {
> +			dev_err(sun8i_pwm->chip.dev,
> +				"Failed to enable PWM clock\n");
> +			return ret;
> +		}
> +
> +	} else {
> +		dev_err(sun8i_pwm->chip.dev,
> +			"Period expects a larger value\n");
> +		return -EINVAL;
> +	}
> +
> +	is_clk = devm_clk_name_match(sun8i_pwm->clk, "mux-0");
> +	if (is_clk)
> +		sun8i_pwm_set_value(sun8i_pwm, CLK_CFG_REG(ch),
> +				    CLK_SRC_SEL, 0 << 7);
> +	else
> +		sun8i_pwm_set_value(sun8i_pwm, CLK_CFG_REG(ch),
> +				    CLK_SRC_SEL, 1 << 7);
> +
> +	dev_info(sun8i_pwm->chip.dev, "clock source freq:%lldHz\n", clk_rate);

This is pretty much reimplementing the clock framework. I guess you'd
be better off just modeling this clock as a clock registered in the
framework. It will take care by itself of the combination of muxing
and rate, and making sure the parent clocks are properly enabled when
needed.

> +	/* calculate and set prescaler, div table, PWM entire cycle */
> +	clk_div = val;
> +	while (clk_div > 65535) {
> +		prescaler++;
> +		clk_div = val;
> +		do_div(clk_div, 1U << div_id);
> +		do_div(clk_div, prescaler + 1);
> +
> +		if (prescaler == 255) {
> +			prescaler = 0;
> +			div_id++;
> +			if (div_id == 9) {
> +				dev_err(sun8i_pwm->chip.dev,
> +					"unsupport period value\n");
> +				return -EINVAL;
> +			}
> +		}
> +	}
> +
> +	sun8i_pwm_set_value(sun8i_pwm, PWM_PERIOD_REG(ch),
> +			    PWM_ENTIRE_CYCLE, clk_div << 16);
> +	sun8i_pwm_set_value(sun8i_pwm, PWM_CTR_REG(ch),
> +			    PWM_PRESCAL_K, prescaler << 0);
> +	sun8i_pwm_set_value(sun8i_pwm, CLK_CFG_REG(ch),
> +			    CLK_DIV_M, div_id << 0);
> +
> +	/* set duty cycle */
> +	val = state->period;
> +	do_div(val, clk_div);
> +	clk_div = state->duty_cycle;
> +	do_div(clk_div, val);
> +	if (clk_div > 65535)
> +		clk_div = 65535;
> +
> +	sun8i_pwm_set_value(sun8i_pwm, PWM_PERIOD_REG(ch),
> +			    PWM_ACT_CYCLE, clk_div << 0);
> +
> +	return 0;
> +}
> +
> +static int sun8i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> +			   struct pwm_state *state)
> +{
> +	int ret;
> +	struct sun8i_pwm_chip *sun8i_pwm = to_sun8i_pwm_chip(chip);
> +	struct pwm_state cstate;
> +
> +	pwm_get_state(pwm, &cstate);
> +	if (!cstate.enabled) {
> +		ret = clk_prepare_enable(sun8i_pwm->clk);
> +		if (ret) {
> +			dev_err(chip->dev, "Failed to enable PWM clock\n");
> +			return ret;
> +		}
> +	}
> +
> +	if ((cstate.period != state->period) ||
> +	    (cstate.duty_cycle != state->duty_cycle)) {
> +		ret = sun8i_pwm_config(sun8i_pwm, pwm->hwpwm, state);
> +		if (ret) {
> +			dev_err(chip->dev, "Failed to config PWM\n");
> +			return ret;
> +		}
> +	}
> +
> +	if (state->polarity != cstate.polarity)
> +		sun8i_pwm_set_polarity(sun8i_pwm, pwm->hwpwm, state->polarity);
> +
> +	if (state->enabled) {
> +		sun8i_pwm_set_bit(sun8i_pwm,
> +				  CLK_CFG_REG(pwm->hwpwm), CLK_GATING);
> +
> +		sun8i_pwm_set_bit(sun8i_pwm,
> +				  PWM_ENABLE_REG, PWM_EN(pwm->hwpwm));
> +	} else {
> +		sun8i_pwm_clear_bit(sun8i_pwm,
> +				    CLK_CFG_REG(pwm->hwpwm), CLK_GATING);
> +
> +		sun8i_pwm_clear_bit(sun8i_pwm,
> +				    PWM_ENABLE_REG, PWM_EN(pwm->hwpwm));
> +	}
> +
> +	return 0;
> +}
> +
> +static void sun8i_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> +				struct pwm_state *state)
> +{
> +	struct sun8i_pwm_chip *sun8i_pwm = to_sun8i_pwm_chip(chip);
> +	u64 clk_rate, tmp;
> +	u32 val;
> +	u16 clk_div, act_cycle;
> +	u8 prescal, div_id;
> +	u8 chn = pwm->hwpwm;
> +
> +	clk_rate = clk_get_rate(sun8i_pwm->clk);
> +
> +	val = sun8i_pwm_read(sun8i_pwm, PWM_CTR_REG(chn));
> +	if (PWM_ACT_STA & val)
> +		state->polarity = PWM_POLARITY_NORMAL;
> +	else
> +		state->polarity = PWM_POLARITY_INVERSED;
> +
> +	prescal = PWM_PRESCAL_K & val;
> +
> +	val = sun8i_pwm_read(sun8i_pwm, PWM_ENABLE_REG);
> +	if (PWM_EN(chn) & val)
> +		state->enabled = true;
> +	else
> +		state->enabled = false;
> +
> +	val = sun8i_pwm_read(sun8i_pwm, PWM_PERIOD_REG(chn));
> +	act_cycle = PWM_ACT_CYCLE & val;
> +	clk_div = val >> 16;
> +
> +	val = sun8i_pwm_read(sun8i_pwm, CLK_CFG_REG(chn));
> +	div_id = CLK_DIV_M & val;
> +
> +	tmp = act_cycle * prescal * (1U << div_id) * NSEC_PER_SEC;
> +	state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
> +	tmp = clk_div * prescal * (1U << div_id) * NSEC_PER_SEC;
> +	state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
> +}
> +
> +static const struct regmap_config sun8i_pwm_regmap_config = {
> +	.reg_bits = 32,
> +	.reg_stride = 4,
> +	.val_bits = 32,
> +	.max_register = CAPTURE_FALL_REG(7),
> +};
> +
> +static const struct pwm_ops sun8i_pwm_ops = {
> +	.apply = sun8i_pwm_apply,
> +	.get_state = sun8i_pwm_get_state,
> +	.owner = THIS_MODULE,
> +};
> +
> +static const struct of_device_id sun8i_pwm_dt_ids[] = {
> +	{
> +		.compatible = "allwinner,sun8i-r40-pwm",
> +		.data = NULL,

Do you really need that field if you leave it NULL?

Thanks!
Maxime

-- 
Maxime Ripard, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

WARNING: multiple messages have this Message-ID (diff)
From: maxime.ripard@bootlin.com (Maxime Ripard)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH v3 6/6] ARM: PWM: add allwinner sun8i R40/T3/V40 PWM support.
Date: Tue, 27 Nov 2018 09:07:33 +0100	[thread overview]
Message-ID: <20181127080733.4e6rljjta3azf2rf@flea> (raw)
In-Reply-To: <20181125162319.GA5422@arx-s1>

Hi!

On Mon, Nov 26, 2018 at 12:23:19AM +0800, Hao Zhang wrote:
> The sun8i R40/T3/V40 PWM has 8 PWM channals and divides to 4 PWM pairs,
> each PWM pair built-in 1 clock module, 2 timer logic module and 1
> programmable dead-time generator, it also support waveform capture.
> It has 2 clock sources OSC24M and APB1, it is different with the
> sun4i-pwm driver, Therefore add a new driver for it.
> 
> Signed-off-by: Hao Zhang <hao5781286@gmail.com>
> ---
>  drivers/pwm/Kconfig     |  12 +-
>  drivers/pwm/Makefile    |   1 +
>  drivers/pwm/pwm-sun8i.c | 418 ++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 430 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/pwm/pwm-sun8i.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 504d252..6105ac8 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -426,7 +426,7 @@ config PWM_STMPE
>  	  expanders.
>  
>  config PWM_SUN4I
> -	tristate "Allwinner PWM support"
> +	tristate "Allwinner SUN4I PWM support"

That should be a separate patch.

(also, your patch series don't seem to have the threading properly
configured, you might want to fix that.)

>  	depends on ARCH_SUNXI || COMPILE_TEST
>  	depends on HAS_IOMEM && COMMON_CLK
>  	help
> @@ -435,6 +435,16 @@ config PWM_SUN4I
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-sun4i.
>  
> +config PWM_SUN8I
> +	tristate "Allwinner SUN8I (R40/V40/T3) PWM support"
> +	depends on ARCH_SUNXI || COMPILE_TEST
> +	depends on HAS_IOMEM && COMMON_CLK
> +	help
> +	  Generic PWM framework driver for Allwinner R40/V40/T3 SoCs.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm-sun8i.
> +

sun8i here (and in the rest of the driver) is too vague. There's
already plenty of SoCs part of the sun8i family that are supported by
the other driver. sun8i-r40 would be a better fit (and there's no need
to mention all the rebranding that allwinner has done with the R40,
just use R40).

>  config PWM_TEGRA
>  	tristate "NVIDIA Tegra PWM support"
>  	depends on ARCH_TEGRA
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 9c676a0..32c8d2d 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -43,6 +43,7 @@ obj-$(CONFIG_PWM_STM32)		+= pwm-stm32.o
>  obj-$(CONFIG_PWM_STM32_LP)	+= pwm-stm32-lp.o
>  obj-$(CONFIG_PWM_STMPE)		+= pwm-stmpe.o
>  obj-$(CONFIG_PWM_SUN4I)		+= pwm-sun4i.o
> +obj-$(CONFIG_PWM_SUN8I)		+= pwm-sun8i.o
>  obj-$(CONFIG_PWM_TEGRA)		+= pwm-tegra.o
>  obj-$(CONFIG_PWM_TIECAP)	+= pwm-tiecap.o
>  obj-$(CONFIG_PWM_TIEHRPWM)	+= pwm-tiehrpwm.o
> diff --git a/drivers/pwm/pwm-sun8i.c b/drivers/pwm/pwm-sun8i.c
> new file mode 100644
> index 0000000..d8597e4
> --- /dev/null
> +++ b/drivers/pwm/pwm-sun8i.c
> @@ -0,0 +1,418 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// Copyright (C) 2018 Hao Zhang <hao5781286@gmail.com>
> +
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/time.h>
> +#include <linux/regmap.h>
> +
> +#define PWM_IRQ_ENABLE_REG	0x0000
> +#define PCIE(ch)		BIT(ch)
> +
> +#define PWM_IRQ_STATUS_REG	0x0004
> +#define PIS(ch)			BIT(ch)
> +
> +#define CAPTURE_IRQ_ENABLE_REG	0x0010
> +#define CRIE(ch)		BIT((ch) * 2)
> +#define CFIE(ch)		BIT((ch) * 2 + 1)
> +
> +#define CAPTURE_IRQ_STATUS_REG	0x0014
> +#define CRIS(ch)		BIT((ch) * 2)
> +#define CFIS(ch)		BIT((ch) * 2 + 1)
> +
> +#define CLK_CFG_REG(ch)		(0x0020 + ((ch) >> 1) * 4)
> +#define CLK_SRC_SEL		GENMASK(8, 7)
> +#define CLK_SRC_BYPASS_SEC	BIT(6)
> +#define CLK_SRC_BYPASS_FIR	BIT(5)
> +#define CLK_GATING		BIT(4)
> +#define CLK_DIV_M		GENMASK(3, 0)
> +
> +#define PWM_DZ_CTR_REG(ch)	(0x0030 + ((ch) >> 1) * 4)
> +#define PWM_DZ_INTV		GENMASK(15, 8)
> +#define PWM_DZ_EN		BIT(0)
> +
> +#define PWM_ENABLE_REG		0x0040
> +#define PWM_EN(ch)		BIT(ch)
> +
> +#define CAPTURE_ENABLE_REG	0x0044
> +#define CAP_EN(ch)		BIT(ch)
> +
> +#define PWM_CTR_REG(ch)		(0x0060 + (ch) * 0x20)
> +#define PWM_PERIOD_RDY		BIT(11)
> +#define PWM_PUL_START		BIT(10)
> +#define PWM_MODE		BIT(9)
> +#define PWM_ACT_STA		BIT(8)
> +#define PWM_PRESCAL_K		GENMASK(7, 0)
> +
> +#define PWM_PERIOD_REG(ch)	(0x0064 + (ch) * 0x20)
> +#define PWM_ENTIRE_CYCLE	GENMASK(31, 16)
> +#define PWM_ACT_CYCLE		GENMASK(15, 0)
> +
> +#define PWM_CNT_REG(ch)		(0x0068 + (ch) * 0x20)
> +#define PWM_CNT_VAL		GENMASK(15, 0)
> +
> +#define CAPTURE_CTR_REG(ch)	(0x006c + (ch) * 0x20)
> +#define CAPTURE_CRLF		BIT(2)
> +#define CAPTURE_CFLF		BIT(1)
> +#define CAPINV			BIT(0)
> +
> +#define CAPTURE_RISE_REG(ch)	(0x0070 + (ch) * 0x20)
> +#define CAPTURE_CRLR		GENMASK(15, 0)
> +
> +#define CAPTURE_FALL_REG(ch)	(0x0074 + (ch) * 0x20)
> +#define CAPTURE_CFLR		GENMASK(15, 0)
> +
> +struct sun8i_pwm_chip {
> +	struct pwm_chip chip;
> +	struct clk *clk;
> +	void __iomem *base;
> +	const struct sun8i_pwm_data *data;
> +	struct regmap *regmap;
> +};
> +
> +static struct sun8i_pwm_chip *to_sun8i_pwm_chip(struct pwm_chip *chip)
> +{
> +	return container_of(chip, struct sun8i_pwm_chip, chip);
> +}
> +
> +static u32 sun8i_pwm_read(struct sun8i_pwm_chip *sun8i_pwm,
> +			  unsigned long offset)
> +{
> +	u32 val;
> +
> +	regmap_read(sun8i_pwm->regmap, offset, &val);
> +	return val;
> +}
> +
> +static void sun8i_pwm_set_bit(struct sun8i_pwm_chip *sun8i_pwm,
> +			      unsigned long reg, u32 bit)
> +{
> +	regmap_update_bits(sun8i_pwm->regmap, reg, bit, bit);
> +}
> +
> +static void sun8i_pwm_clear_bit(struct sun8i_pwm_chip *sun8i_pwm,
> +				unsigned long reg, u32 bit)
> +{
> +	regmap_update_bits(sun8i_pwm->regmap, reg, bit, 0);
> +}
> +
> +static void sun8i_pwm_set_value(struct sun8i_pwm_chip *sun8i_pwm,
> +				unsigned long reg, u32 mask, u32 val)
> +{
> +	regmap_update_bits(sun8i_pwm->regmap, reg, mask, val);
> +}
> +
> +static void sun8i_pwm_set_polarity(struct sun8i_pwm_chip *chip, u32 ch,
> +				   enum pwm_polarity polarity)
> +{
> +	if (polarity == PWM_POLARITY_NORMAL)
> +		sun8i_pwm_set_bit(chip, PWM_CTR_REG(ch), PWM_ACT_STA);
> +	else
> +		sun8i_pwm_clear_bit(chip, PWM_CTR_REG(ch), PWM_ACT_STA);
> +}
> +
> +static int sun8i_pwm_config(struct sun8i_pwm_chip *sun8i_pwm, u8 ch,
> +			    struct pwm_state *state)
> +{
> +	u64 clk_rate, clk_div, val;
> +	u16 prescaler = 0;
> +	u16 div_id = 0;
> +	struct clk *clk;
> +	bool is_clk;
> +	int ret;
> +
> +	clk_rate = clk_get_rate(sun8i_pwm->clk);
> +
> +	/* check period and select clock source */
> +	val = state->period * clk_rate;
> +	do_div(val, NSEC_PER_SEC);
> +	is_clk = devm_clk_name_match(sun8i_pwm->clk, "mux-0");
> +	if (val <= 1 && is_clk) {
> +		clk = devm_clk_get(sun8i_pwm->chip.dev, "mux-1");
> +		if (IS_ERR(clk)) {
> +			dev_err(sun8i_pwm->chip.dev,
> +				"Period expects a larger value\n");
> +			return -EINVAL;
> +		}
> +
> +		clk_rate = clk_get_rate(clk);
> +		val = state->period * clk_rate;
> +		do_div(val, NSEC_PER_SEC);
> +		if (val <= 1) {
> +			dev_err(sun8i_pwm->chip.dev,
> +				"Period expects a larger value\n");
> +			return -EINVAL;
> +		}
> +
> +		/* change clock source to "mux-1" */
> +		clk_disable_unprepare(sun8i_pwm->clk);
> +		devm_clk_put(sun8i_pwm->chip.dev, sun8i_pwm->clk);
> +		sun8i_pwm->clk = clk;
> +
> +		ret = clk_prepare_enable(sun8i_pwm->clk);
> +		if (ret) {
> +			dev_err(sun8i_pwm->chip.dev,
> +				"Failed to enable PWM clock\n");
> +			return ret;
> +		}
> +
> +	} else {
> +		dev_err(sun8i_pwm->chip.dev,
> +			"Period expects a larger value\n");
> +		return -EINVAL;
> +	}
> +
> +	is_clk = devm_clk_name_match(sun8i_pwm->clk, "mux-0");
> +	if (is_clk)
> +		sun8i_pwm_set_value(sun8i_pwm, CLK_CFG_REG(ch),
> +				    CLK_SRC_SEL, 0 << 7);
> +	else
> +		sun8i_pwm_set_value(sun8i_pwm, CLK_CFG_REG(ch),
> +				    CLK_SRC_SEL, 1 << 7);
> +
> +	dev_info(sun8i_pwm->chip.dev, "clock source freq:%lldHz\n", clk_rate);

This is pretty much reimplementing the clock framework. I guess you'd
be better off just modeling this clock as a clock registered in the
framework. It will take care by itself of the combination of muxing
and rate, and making sure the parent clocks are properly enabled when
needed.

> +	/* calculate and set prescaler, div table, PWM entire cycle */
> +	clk_div = val;
> +	while (clk_div > 65535) {
> +		prescaler++;
> +		clk_div = val;
> +		do_div(clk_div, 1U << div_id);
> +		do_div(clk_div, prescaler + 1);
> +
> +		if (prescaler == 255) {
> +			prescaler = 0;
> +			div_id++;
> +			if (div_id == 9) {
> +				dev_err(sun8i_pwm->chip.dev,
> +					"unsupport period value\n");
> +				return -EINVAL;
> +			}
> +		}
> +	}
> +
> +	sun8i_pwm_set_value(sun8i_pwm, PWM_PERIOD_REG(ch),
> +			    PWM_ENTIRE_CYCLE, clk_div << 16);
> +	sun8i_pwm_set_value(sun8i_pwm, PWM_CTR_REG(ch),
> +			    PWM_PRESCAL_K, prescaler << 0);
> +	sun8i_pwm_set_value(sun8i_pwm, CLK_CFG_REG(ch),
> +			    CLK_DIV_M, div_id << 0);
> +
> +	/* set duty cycle */
> +	val = state->period;
> +	do_div(val, clk_div);
> +	clk_div = state->duty_cycle;
> +	do_div(clk_div, val);
> +	if (clk_div > 65535)
> +		clk_div = 65535;
> +
> +	sun8i_pwm_set_value(sun8i_pwm, PWM_PERIOD_REG(ch),
> +			    PWM_ACT_CYCLE, clk_div << 0);
> +
> +	return 0;
> +}
> +
> +static int sun8i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> +			   struct pwm_state *state)
> +{
> +	int ret;
> +	struct sun8i_pwm_chip *sun8i_pwm = to_sun8i_pwm_chip(chip);
> +	struct pwm_state cstate;
> +
> +	pwm_get_state(pwm, &cstate);
> +	if (!cstate.enabled) {
> +		ret = clk_prepare_enable(sun8i_pwm->clk);
> +		if (ret) {
> +			dev_err(chip->dev, "Failed to enable PWM clock\n");
> +			return ret;
> +		}
> +	}
> +
> +	if ((cstate.period != state->period) ||
> +	    (cstate.duty_cycle != state->duty_cycle)) {
> +		ret = sun8i_pwm_config(sun8i_pwm, pwm->hwpwm, state);
> +		if (ret) {
> +			dev_err(chip->dev, "Failed to config PWM\n");
> +			return ret;
> +		}
> +	}
> +
> +	if (state->polarity != cstate.polarity)
> +		sun8i_pwm_set_polarity(sun8i_pwm, pwm->hwpwm, state->polarity);
> +
> +	if (state->enabled) {
> +		sun8i_pwm_set_bit(sun8i_pwm,
> +				  CLK_CFG_REG(pwm->hwpwm), CLK_GATING);
> +
> +		sun8i_pwm_set_bit(sun8i_pwm,
> +				  PWM_ENABLE_REG, PWM_EN(pwm->hwpwm));
> +	} else {
> +		sun8i_pwm_clear_bit(sun8i_pwm,
> +				    CLK_CFG_REG(pwm->hwpwm), CLK_GATING);
> +
> +		sun8i_pwm_clear_bit(sun8i_pwm,
> +				    PWM_ENABLE_REG, PWM_EN(pwm->hwpwm));
> +	}
> +
> +	return 0;
> +}
> +
> +static void sun8i_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> +				struct pwm_state *state)
> +{
> +	struct sun8i_pwm_chip *sun8i_pwm = to_sun8i_pwm_chip(chip);
> +	u64 clk_rate, tmp;
> +	u32 val;
> +	u16 clk_div, act_cycle;
> +	u8 prescal, div_id;
> +	u8 chn = pwm->hwpwm;
> +
> +	clk_rate = clk_get_rate(sun8i_pwm->clk);
> +
> +	val = sun8i_pwm_read(sun8i_pwm, PWM_CTR_REG(chn));
> +	if (PWM_ACT_STA & val)
> +		state->polarity = PWM_POLARITY_NORMAL;
> +	else
> +		state->polarity = PWM_POLARITY_INVERSED;
> +
> +	prescal = PWM_PRESCAL_K & val;
> +
> +	val = sun8i_pwm_read(sun8i_pwm, PWM_ENABLE_REG);
> +	if (PWM_EN(chn) & val)
> +		state->enabled = true;
> +	else
> +		state->enabled = false;
> +
> +	val = sun8i_pwm_read(sun8i_pwm, PWM_PERIOD_REG(chn));
> +	act_cycle = PWM_ACT_CYCLE & val;
> +	clk_div = val >> 16;
> +
> +	val = sun8i_pwm_read(sun8i_pwm, CLK_CFG_REG(chn));
> +	div_id = CLK_DIV_M & val;
> +
> +	tmp = act_cycle * prescal * (1U << div_id) * NSEC_PER_SEC;
> +	state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
> +	tmp = clk_div * prescal * (1U << div_id) * NSEC_PER_SEC;
> +	state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
> +}
> +
> +static const struct regmap_config sun8i_pwm_regmap_config = {
> +	.reg_bits = 32,
> +	.reg_stride = 4,
> +	.val_bits = 32,
> +	.max_register = CAPTURE_FALL_REG(7),
> +};
> +
> +static const struct pwm_ops sun8i_pwm_ops = {
> +	.apply = sun8i_pwm_apply,
> +	.get_state = sun8i_pwm_get_state,
> +	.owner = THIS_MODULE,
> +};
> +
> +static const struct of_device_id sun8i_pwm_dt_ids[] = {
> +	{
> +		.compatible = "allwinner,sun8i-r40-pwm",
> +		.data = NULL,

Do you really need that field if you leave it NULL?

Thanks!
Maxime

-- 
Maxime Ripard, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 228 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20181127/c76c58d1/attachment-0001.sig>

  parent reply	other threads:[~2018-11-27  8:07 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-11-25 16:23 [PATCH v3 6/6] ARM: PWM: add allwinner sun8i R40/T3/V40 PWM support Hao Zhang
2018-11-25 16:23 ` Hao Zhang
2018-11-25 16:23 ` Hao Zhang
2018-11-26 21:31 ` Uwe Kleine-König
2018-11-26 21:31   ` Uwe Kleine-König
2018-11-27 10:33   ` Uwe Kleine-König
2018-11-27 10:33     ` Uwe Kleine-König
2018-12-03  9:49   ` Uwe Kleine-König
2018-12-03  9:49     ` Uwe Kleine-König
2018-11-27  8:07 ` Maxime Ripard [this message]
2018-11-27  8:07   ` Maxime Ripard
2018-11-27  8:07   ` Maxime Ripard
     [not found]   ` <CAJeuY7-U=EF_FCe2jmBB-Otuemp_rcMPs=g7ipPi=YP4qy5mtg@mail.gmail.com>
     [not found]     ` <CAJeuY7-U=EF_FCe2jmBB-Otuemp_rcMPs=g7ipPi=YP4qy5mtg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2018-12-03 10:38       ` Maxime Ripard
2018-12-03 10:38         ` Maxime Ripard
2018-12-03 10:38         ` Maxime Ripard
2018-12-20 17:57 ` Thierry Reding
2018-12-20 17:57   ` Thierry Reding
2018-12-20 17:57   ` Thierry Reding
2019-03-12  5:42   ` Hao Zhang
2019-03-12  5:42     ` Hao Zhang
2019-03-12  5:42     ` Hao Zhang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20181127080733.4e6rljjta3azf2rf@flea \
    --to=maxime.ripard-ldxbnhwyfcjbdgjk7y7tuq@public.gmane.org \
    --cc=devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=hao5781286-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org \
    --cc=linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org \
    --cc=linux-gpio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=linux-pwm-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=linux-sunxi-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org \
    --cc=mark.rutland-5wv7dgnIgG8@public.gmane.org \
    --cc=mturquette-rdvid1DuHRBWk0Htik3J/w@public.gmane.org \
    --cc=robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org \
    --cc=sboyd-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org \
    --cc=thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org \
    --cc=wens-jdAy2FN1RRM@public.gmane.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.