diff mbox series

[v3,6/6] ARM: PWM: add allwinner sun8i R40/T3/V40 PWM support.

Message ID 20181125162319.GA5422@arx-s1 (mailing list archive)
State New, archived
Headers show
Series PWM support for allwinner sun8i R40/T3/V40 SOCs. | expand

Commit Message

Hao Zhang Nov. 25, 2018, 4:23 p.m. UTC
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

Comments

Uwe Kleine-König Nov. 26, 2018, 9:31 p.m. UTC | #1
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"
>  	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.
> +
>  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;

This member is only written to, but never used.

> +	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);
> +}

This function is only used once. Maybe drop the function and put the if
to the caller.

> +
> +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");

This error message looks wrong, several others, too.

> +			return -EINVAL;

return PTR_ERR(clk)?

> +		}
> +
> +		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;

sun8i_pwm is shared for all 8 PWMs, right? So if you assign mux-1 here
for the second mux, how does this influence the first PWM?

mux-0 might already be enabled, it is then never disabled.


> +
> +		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;

This looks wrong. If val is > 1 there shouldn't be a reason to abort?

> +	}
> +
> +	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);

I'd degrade this to dev_dbg.

> +
> +	/* 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;
> +			}
> +		}
> +	}

Noting the underlying formula for the calculation and the bitwidth for
the related register fields above would be good.

> +
> +	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);

Why "<< 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;
> +		}
> +	}

sun8i_pwm_config writes the registers that are relevant for period and
duty_cycle. When do these values take effect? If it's already here,
switching the polarity below might introduce a glitch.


> +	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));
> +	}

Please document how the hardware behaves when being disabled. (Does it
drive a 0? Does it drive a 1 when inverted? Or is the output high-z?)

> +
> +	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)

This looks strange to me. While syntactically equivalent it is more
usual to write this as

	if (val & PWM_ACT_STA)


> +		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;

When the PWM should be enabled, you also set the CLK_GATING bit. Should
this better be checked for in .get_state, too?

> +
> +	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,

.data doesn't need to be specified.

> +	},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, sun8i_pwm_dt_ids);
> +
> +static int sun8i_pwm_probe(struct platform_device *pdev)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct sun8i_pwm_chip *pwm;
> +	struct resource *res;
> +	int ret;
> +	const struct of_device_id *match;
> +
> +	match = of_match_device(sun8i_pwm_dt_ids, &pdev->dev);
> +	if (!match) {
> +		dev_err(&pdev->dev, "Error: No device match found\n");
> +		return -ENODEV;
> +	}

This prevents a match by driver-name, right? Other than that match is
only used to assign pwm->data below to NULL.

> +
> +	pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
> +	if (!pwm)
> +		return -ENOMEM;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	pwm->base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(pwm->base))
> +		return PTR_ERR(pwm->base);
> +
> +	pwm->regmap = devm_regmap_init_mmio(&pdev->dev, pwm->base,
> +					    &sun8i_pwm_regmap_config);
> +	if (IS_ERR(pwm->regmap)) {
> +		dev_err(&pdev->dev, "Failed to create regmap\n");
> +		return PTR_ERR(pwm->regmap);
> +	}
> +
> +	/* we use mux-0 as default clock source */
> +	pwm->clk = devm_clk_get(&pdev->dev, "mux-0");
> +	if (IS_ERR(pwm->clk)) {

You might want to handle pwm->clk == ERR_PTR(-EPROBE_DEFER) without falling
back to mux-1 and without printing an error.

> +		pwm->clk = devm_clk_get(&pdev->dev, "mux-1");
> +		if (IS_ERR(pwm->clk)) {
> +			dev_err(&pdev->dev, "Failed to get PWM clock\n");
> +			return PTR_ERR(pwm->clk);
> +		}
> +	}
> +
> +	ret = of_property_read_u32(np, "pwm-channels", &pwm->chip.npwm);
> +	if (ret && !pwm->chip.npwm) {

This is equivalent to

	if (ret)

because &pwm->chip.npwm is only modified if of_property_read_u32
returns 0 and the variable holds a 0 before.

> +		dev_err(&pdev->dev, "Can't get pwm-channels\n");
> +		return ret;
> +	}
> +
> +	dev_info(&pdev->dev, "pwm-channels:%d\n", pwm->chip.npwm);

dev_dbg?

> +	pwm->data = match->data;
> +	pwm->chip.dev = &pdev->dev;
> +	pwm->chip.ops = &sun8i_pwm_ops;
> +	pwm->chip.base = -1;
> +	pwm->chip.of_xlate = of_pwm_xlate_with_flags;
> +	pwm->chip.of_pwm_n_cells = 3;
> +
> +	ret = pwmchip_add(&pwm->chip);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "Failed to add PWM chip: %d\n", ret);
> +		return ret;
> +	}
> +
> +	platform_set_drvdata(pdev, pwm);

If you do this earlier (typically after the allocation succeeded) you
can simplify the last few lines to:

	ret = pwmchip_add(&pwm->chip);
	if (ret < 0)
		dev_err(&pdev->dev, ...);

	return ret;

> +static int sun8i_pwm_remove(struct platform_device *pdev)
> +{
> +	struct sun8i_pwm_chip *pwm = platform_get_drvdata(pdev);
> +
> +	clk_disable_unprepare(pwm->clk);
> +	return pwmchip_remove(&pwm->chip);

This is at least unusual (and maybe broken). Please call pwmchip_remove
before clk_disable_unprepare.

> +}
> +
> +static struct platform_driver sun8i_pwm_driver = {
> +	.driver = {
> +		.name = "sun8i-pwm",
> +		.of_match_table = sun8i_pwm_dt_ids,
> +	},
> +	.probe = sun8i_pwm_probe,
> +	.remove = sun8i_pwm_remove,
> +};
> +module_platform_driver(sun8i_pwm_driver);
> +
> +MODULE_ALIAS("platform: sun8i-pwm");

I think the space in the alias must be dropped. Giving that the driver
doesn't bind by driver-name I suggest to drop this completely.

> +MODULE_AUTHOR("Hao Zhang <hao5781286@gmail.com>");
> +MODULE_DESCRIPTION("Allwinner sun8i PWM driver");
> +MODULE_LICENSE("GPL v2");

Best regards
Uwe
Maxime Ripard Nov. 27, 2018, 8:07 a.m. UTC | #2
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
Uwe Kleine-König Nov. 27, 2018, 10:33 a.m. UTC | #3
Hello,

On Mon, Nov 26, 2018 at 10:31:58PM +0100, Uwe Kleine-König wrote:
> 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"
> >  	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.
> > +
> >  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>

Given that the documentation is publically available, I suggest to add a
link to it in a comment here.
(http://linux-sunxi.org/File:Allwinner_R40_User_Manual_V1.0.pdf)

> > +	/* 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;
> > +			}
> > +		}
> > +	}
> 
> Noting the underlying formula for the calculation and the bitwidth for
> the related register fields above would be good.
> 
> > +
> > +	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);
> 
> Why "<< 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;
> > +		}
> > +	}
> 
> sun8i_pwm_config writes the registers that are relevant for period and
> duty_cycle. When do these values take effect? If it's already here,
> switching the polarity below might introduce a glitch.

I think this is the case after taking a look into the reference manual.

There are two 16 bit fields in the PWM_PERIOD_REG. One specifies the
number of clock ticks defining the period ("PWM_ENTIRE_CYCLE") and the
other the duty cycle ("PWM_ACT_CYCLE").

So if you go from duty_cycle=5 + period=10 + POLARITY_NORMAL to
duty_cycle=3 + period=10 + POLARITY_INVERTED this might generate:

 ____      __           ______
/    \____/  \_________/      \__/
^         ^         ^         ^

Also there is a PWM_PERIOD_RDY bit field that probably has to be
consulted before writing to the PWM_PERIOD_REG register.

It's not entirely clear to me if the PWM_ACT_STA bit that is used for
inversion is shadowed until the next period, too. That's what I assumed
above. If it's not the wave might look as follows:

 ____      __  _____    ______
/    \____/  \/     \__/      \__/
^         ^   *     ^         ^

Where * marks the point where the inversion starts to take effect.

Best regards
Uwe
Uwe Kleine-König Dec. 3, 2018, 9:49 a.m. UTC | #4
Hello,

On Mon, Nov 26, 2018 at 10:31:58PM +0100, Uwe Kleine-König wrote:
> > +static int sun8i_pwm_config(struct sun8i_pwm_chip *sun8i_pwm, u8 ch,
> > +			    struct pwm_state *state)
> > +{
> > +[...]
> > +		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;
> 
> sun8i_pwm is shared for all 8 PWMs, right? So if you assign mux-1 here
> for the second mux, how does this influence the first PWM?

To clearify my question:

after the first pwm is used and enabled (maybe using mux-0) changing
sun8i_pwm->clk for the second pwm is broken because then when the first
pwm is disabled the wrong clock is stopped.

Best regards
Uwe
Maxime Ripard Dec. 3, 2018, 10:38 a.m. UTC | #5
Hi,

On Sat, Dec 01, 2018 at 11:23:40PM +0800, Hao Zhang wrote:
> > > 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.)
>
> Do you mean i should fix it like this ?
> config sunxi pwm
> |
> |---->config PWM_SUN4I
> |---->config PWM_SUN8I_R40

No, I meant for your mails, sorry. Each patch should be sent in reply
to your cover letter, and they are all sent as separate mails, which
makes it hard to track.

I'm not sure how you're sending your patches, but using git send-email
this would be using --no-chain-reply-to --thread if I remember well.

> > >  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.
>
> Do you mean i should move it to ccu clock framwork?

You don't need to move it anywhere, you can declare a clock in a
driver, without being in drivers/clk. We're doing that in the DRM or
the RTC drivers for example.

Maxime
Thierry Reding Dec. 20, 2018, 5:57 p.m. UTC | #6
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"
>  	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.
> +
>  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;
> +		}

You should never try to get resources at this point. You should have
requested them already at ->probe() time. Otherwise, how are you going
to handle failures here?

> +
> +		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);
> +
> +	/* 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;
> +		}
> +	}

So this isn't really how atomic is supposed to work. The whole point of
the single callback is to allow the driver to apply the changes in an
atomic way, which means that either everything is applied or nothing is
applied.

That's not what you do here. In the above you can end up with an enabled
clock but the settings not being applied. Similarly sun8i_pwm_config()
can abort in a number of places, which would leave you with a half-
configured PWM channel.

Instead, what you should be doing is precompute everything and check
that the configuration can be applied before touching any registers or
enabling clocks. Once you've validate the new state, you need to write
everything and there should be no more risk of failure.

Thierry
Hao Zhang March 12, 2019, 5:42 a.m. UTC | #7
Thierry Reding <thierry.reding@gmail.com> 于2018年12月21日周五 上午1:57写道:
>
> 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"
> >       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.
> > +
> >  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;
> > +             }
>
> You should never try to get resources at this point. You should have
> requested them already at ->probe() time. Otherwise, how are you going
> to handle failures here?
>

Something wrong here, i will fix it, thanks :)

> > +
> > +             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);
> > +
> > +     /* 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;
> > +             }
> > +     }
>
> So this isn't really how atomic is supposed to work. The whole point of
> the single callback is to allow the driver to apply the changes in an
> atomic way, which means that either everything is applied or nothing is
> applied.
>
> That's not what you do here. In the above you can end up with an enabled
> clock but the settings not being applied. Similarly sun8i_pwm_config()
> can abort in a number of places, which would leave you with a half-
> configured PWM channel.
>
> Instead, what you should be doing is precompute everything and check
> that the configuration can be applied before touching any registers or
> enabling clocks. Once you've validate the new state, you need to write
> everything and there should be no more risk of failure.
>

Got it, it is useful for me !

> Thierry
> -----BEGIN PGP SIGNATURE-----
>
> iQIzBAABCAAdFiEEiOrDCAFJzPfAjcif3SOs138+s6EFAlwb2B4ACgkQ3SOs138+
> s6GmgRAAwvTyhaFa0jWmUftz+0FS5JTXADm9VusyhbSlmHV7UNkqkQuLnaT1WoGi
> gGW5ZMLA+wSPWrXqGsw1HuzLc57c1/eDQdxLmlqBz0Lwribn75+0kisEvV3CRCEL
> P9UiTgMualhTe0DGKZmcHJvV56sEhx7b2WQ81sOn5nZvCP/iFTURp5WSyk+phAyb
> ss9i5bjwuxC34wLC0wCVNvr2XlpdD7CTy0R/nap1Za2nf6Cm0TnzGGPIYki1/gM2
> fUtYT/zCvfGp+JG5+R2755XdomXzj0vmXD7Omv75eKzMb+HivolmLPJEfUiP8c+x
> SElT/F/jIkwvdBzyeQ/sA8ybh4N0DVapnUxBjrUBLOMlrNDlh+ApjUOOpwylQBXQ
> 6JKmjoHoBg31lK9QEPxoIoNP0FdZBr6+lAaZeQNx95PeICxD21IvPtuLEp4ksbX7
> fJsOxbkBzbIMpKuxLM3vkl89/O9IhPbVoyvMASCPMmdwAIH+LSy3SvIFWbGQ0nXS
> gwjTzN4r1AWmdXIN+faT6khnNY64WoPd896AyJSQ5mQsl42IMKRi42kpfeN3/f8y
> x4d07WSTCRB4v+OehJEVP2WJNsbI3EvtClbngn66xSac7kGSbg4cpbW0NzkrFj+T
> dOG2p9wUMibPBXzS+V7JzTsDgR9JG2fzxG8PhEesaG/Bt32Hg0Y=
> =S1Wz
> -----END PGP SIGNATURE-----
diff mbox series

Patch

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"
 	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.
+
 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);
+
+	/* 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,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, sun8i_pwm_dt_ids);
+
+static int sun8i_pwm_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct sun8i_pwm_chip *pwm;
+	struct resource *res;
+	int ret;
+	const struct of_device_id *match;
+
+	match = of_match_device(sun8i_pwm_dt_ids, &pdev->dev);
+	if (!match) {
+		dev_err(&pdev->dev, "Error: No device match found\n");
+		return -ENODEV;
+	}
+
+	pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
+	if (!pwm)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	pwm->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(pwm->base))
+		return PTR_ERR(pwm->base);
+
+	pwm->regmap = devm_regmap_init_mmio(&pdev->dev, pwm->base,
+					    &sun8i_pwm_regmap_config);
+	if (IS_ERR(pwm->regmap)) {
+		dev_err(&pdev->dev, "Failed to create regmap\n");
+		return PTR_ERR(pwm->regmap);
+	}
+
+	/* we use mux-0 as default clock source */
+	pwm->clk = devm_clk_get(&pdev->dev, "mux-0");
+	if (IS_ERR(pwm->clk)) {
+		pwm->clk = devm_clk_get(&pdev->dev, "mux-1");
+		if (IS_ERR(pwm->clk)) {
+			dev_err(&pdev->dev, "Failed to get PWM clock\n");
+			return PTR_ERR(pwm->clk);
+		}
+	}
+
+	ret = of_property_read_u32(np, "pwm-channels", &pwm->chip.npwm);
+	if (ret && !pwm->chip.npwm) {
+		dev_err(&pdev->dev, "Can't get pwm-channels\n");
+		return ret;
+	}
+
+	dev_info(&pdev->dev, "pwm-channels:%d\n", pwm->chip.npwm);
+	pwm->data = match->data;
+	pwm->chip.dev = &pdev->dev;
+	pwm->chip.ops = &sun8i_pwm_ops;
+	pwm->chip.base = -1;
+	pwm->chip.of_xlate = of_pwm_xlate_with_flags;
+	pwm->chip.of_pwm_n_cells = 3;
+
+	ret = pwmchip_add(&pwm->chip);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to add PWM chip: %d\n", ret);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, pwm);
+
+	return 0;
+}
+
+static int sun8i_pwm_remove(struct platform_device *pdev)
+{
+	struct sun8i_pwm_chip *pwm = platform_get_drvdata(pdev);
+
+	clk_disable_unprepare(pwm->clk);
+	return pwmchip_remove(&pwm->chip);
+}
+
+static struct platform_driver sun8i_pwm_driver = {
+	.driver = {
+		.name = "sun8i-pwm",
+		.of_match_table = sun8i_pwm_dt_ids,
+	},
+	.probe = sun8i_pwm_probe,
+	.remove = sun8i_pwm_remove,
+};
+module_platform_driver(sun8i_pwm_driver);
+
+MODULE_ALIAS("platform: sun8i-pwm");
+MODULE_AUTHOR("Hao Zhang <hao5781286@gmail.com>");
+MODULE_DESCRIPTION("Allwinner sun8i PWM driver");
+MODULE_LICENSE("GPL v2");