[U-Boot] [RESEND PATCH 1/3] pwm: sunxi: add support for PWM found on Allwinner A64 and H3

Andre Przywara andre.przywara at arm.com
Tue Sep 19 09:31:46 UTC 2017


Hi,

On 19/09/17 09:53, Icenowy Zheng wrote:
> 
> 
> 于 2017年9月19日 GMT+08:00 下午4:51:32, Andre Przywara <andre.przywara at arm.com> 写到:
>> Hi,
>>
>> On 19/09/17 06:06, Vasily Khoruzhick wrote:
>>> This commit adds basic support for PWM found on Allwinner A64 and H3
>>
>> Mmh, can you explain (for instance in a 0/3 email) what this is used
>> for? My understanding is that PWM0 (which is what you hard code here)
>> is
>> only exposed on PD22, which is also used for the MDC line to the
>> Ethernet PHY. All boards I heard of have Ethernet, so PWM0 is not
>> usable
>> there.
>> So is this for a special board?
> 
> Backlight for Pinebook.

Ah, right, forgot about that missing Ethernet there ...
Thanks for the heads up.

Cheers,
Andre.

>>>
>>> Signed-off-by: Vasily Khoruzhick <anarsoul at gmail.com>
>>> ---
>>>  arch/arm/include/asm/arch-sunxi/gpio.h |   1 +
>>>  arch/arm/include/asm/arch-sunxi/pwm.h  |  12 +++
>>>  arch/arm/mach-sunxi/board.c            |  11 +++
>>>  drivers/pwm/Kconfig                    |   7 ++
>>>  drivers/pwm/Makefile                   |   1 +
>>>  drivers/pwm/sunxi_pwm.c                | 174
>> +++++++++++++++++++++++++++++++++
>>>  6 files changed, 206 insertions(+)
>>>  create mode 100644 drivers/pwm/sunxi_pwm.c
>>>
>>> diff --git a/arch/arm/include/asm/arch-sunxi/gpio.h
>> b/arch/arm/include/asm/arch-sunxi/gpio.h
>>> index 24f85206c8..7265d18099 100644
>>> --- a/arch/arm/include/asm/arch-sunxi/gpio.h
>>> +++ b/arch/arm/include/asm/arch-sunxi/gpio.h
>>> @@ -173,6 +173,7 @@ enum sunxi_gpio_number {
>>>  #define SUN8I_GPD_SDC1		3
>>>  #define SUNXI_GPD_LCD0		2
>>>  #define SUNXI_GPD_LVDS0		3
>>> +#define SUNXI_GPD_PWM		2
>>>  
>>>  #define SUN5I_GPE_SDC2		3
>>>  #define SUN8I_GPE_TWI2		3
>>> diff --git a/arch/arm/include/asm/arch-sunxi/pwm.h
>> b/arch/arm/include/asm/arch-sunxi/pwm.h
>>> index 5884b5dbe7..673e0eb7b5 100644
>>> --- a/arch/arm/include/asm/arch-sunxi/pwm.h
>>> +++ b/arch/arm/include/asm/arch-sunxi/pwm.h
>>> @@ -11,8 +11,15 @@
>>>  #define SUNXI_PWM_CH0_PERIOD		(SUNXI_PWM_BASE + 4)
>>>  
>>>  #define SUNXI_PWM_CTRL_PRESCALE0(x)	((x) & 0xf)
>>> +#define SUNXI_PWM_CTRL_PRESCALE0_MASK	(0xf)
>>>  #define SUNXI_PWM_CTRL_ENABLE0		(0x5 << 4)
>>>  #define SUNXI_PWM_CTRL_POLARITY0(x)	((x) << 5)
>>> +#define SUNXI_PWM_CTRL_POLARITY0_MASK	(1 << 5)
>>> +#define SUNXI_PWM_CTRL_CLK_GATE		(1 << 6)
>>> +
>>> +#define SUNXI_PWM_CH0_PERIOD_MAX	(0xffff)
>>> +#define SUNXI_PWM_CH0_PERIOD_PRD(x)	((x & 0xffff) << 16)
>>> +#define SUNXI_PWM_CH0_PERIOD_DUTY(x)	((x) & 0xffff)
>>>  
>>>  #define SUNXI_PWM_PERIOD_80PCT		0x04af03c0
>>>  
>>> @@ -31,4 +38,9 @@
>>>  #define SUNXI_PWM_MUX			SUN8I_GPH_PWM
>>>  #endif
>>>  
>>> +struct sunxi_pwm {
>>> +	u32 ctrl;
>>> +	u32 ch0_period;
>>> +};
>>> +
>>>  #endif
>>> diff --git a/arch/arm/mach-sunxi/board.c
>> b/arch/arm/mach-sunxi/board.c
>>> index 65b1ebd837..a85f973a46 100644
>>> --- a/arch/arm/mach-sunxi/board.c
>>> +++ b/arch/arm/mach-sunxi/board.c
>>> @@ -141,6 +141,16 @@ static int gpio_init(void)
>>>  	return 0;
>>>  }
>>>  
>>> +static int pwm_init(void)
>>> +{
>>> +#ifdef CONFIG_PWM_SUNXI
>>> +#ifdef CONFIG_MACH_SUN50I
>>> +	sunxi_gpio_set_cfgpin(SUNXI_GPD(22), SUNXI_GPD_PWM);
>>
>> So this would probably kill Ethernet.
>> At the very least this should be somehow protected against this clash.
>> And I wonder if this should move into the PWM driver, for instance into
>> the .enable function.
>>
>> Cheers,
>> Andre.
>>
>>> +#endif
>>> +#endif
>>> +	return 0;
>>> +}
>>> +
>>>  #if defined(CONFIG_SPL_BOARD_LOAD_IMAGE) &&
>> defined(CONFIG_SPL_BUILD)
>>>  static int spl_board_load_image(struct spl_image_info *spl_image,
>>>  				struct spl_boot_device *bootdev)
>>> @@ -204,6 +214,7 @@ void s_init(void)
>>>  	clock_init();
>>>  	timer_init();
>>>  	gpio_init();
>>> +	pwm_init();
>>>  #ifndef CONFIG_DM_I2C
>>>  	i2c_init_board();
>>>  #endif
>>> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>>> index e827558052..67e3f355e7 100644
>>> --- a/drivers/pwm/Kconfig
>>> +++ b/drivers/pwm/Kconfig
>>> @@ -43,3 +43,10 @@ config PWM_TEGRA
>>>  	  four channels with a programmable period and duty cycle. Only a
>>>  	  32KHz clock is supported by the driver but the duty cycle is
>>>  	  configurable.
>>> +
>>> +config PWM_SUNXI
>>> +	bool "Enable support for the Allwinner Sunxi PWM"
>>> +	depends on DM_PWM
>>> +	help
>>> +	  This PWM is found on A64 and other Allwinner SoCs. It supports a
>>> +	  programmable period and duty cycle. A 32-bit counter is used.
>>> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
>>> index 29d59916cb..1a8f8a58bc 100644
>>> --- a/drivers/pwm/Makefile
>>> +++ b/drivers/pwm/Makefile
>>> @@ -17,3 +17,4 @@ obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o pwm-imx-util.o
>>>  obj-$(CONFIG_PWM_ROCKCHIP)	+= rk_pwm.o
>>>  obj-$(CONFIG_PWM_SANDBOX)	+= sandbox_pwm.o
>>>  obj-$(CONFIG_PWM_TEGRA)		+= tegra_pwm.o
>>> +obj-$(CONFIG_PWM_SUNXI)		+= sunxi_pwm.o
>>> diff --git a/drivers/pwm/sunxi_pwm.c b/drivers/pwm/sunxi_pwm.c
>>> new file mode 100644
>>> index 0000000000..3e6d69fa1c
>>> --- /dev/null
>>> +++ b/drivers/pwm/sunxi_pwm.c
>>> @@ -0,0 +1,174 @@
>>> +/*
>>> + * Copyright (c) 2017 Vasily Khoruzhick <anarsoul at gmail.com>
>>> + *
>>> + * SPDX-License-Identifier:	GPL-2.0+
>>> + */
>>> +
>>> +#include <common.h>
>>> +#include <div64.h>
>>> +#include <dm.h>
>>> +#include <pwm.h>
>>> +#include <regmap.h>
>>> +#include <syscon.h>
>>> +#include <asm/io.h>
>>> +#include <asm/arch/pwm.h>
>>> +#include <power/regulator.h>
>>> +
>>> +DECLARE_GLOBAL_DATA_PTR;
>>> +
>>> +struct sunxi_pwm_priv {
>>> +	struct sunxi_pwm *regs;
>>> +	ulong freq;
>>> +	bool invert;
>>> +	uint32_t prescaler;
>>> +};
>>> +
>>> +static const uint32_t prescaler_table[] = {
>>> +	120,	/* 0000 */
>>> +	180,	/* 0001 */
>>> +	240,	/* 0010 */
>>> +	360,	/* 0011 */
>>> +	480,	/* 0100 */
>>> +	0,	/* 0101 */
>>> +	0,	/* 0110 */
>>> +	0,	/* 0111 */
>>> +	12000,	/* 1000 */
>>> +	24000,	/* 1001 */
>>> +	36000,	/* 1010 */
>>> +	48000,	/* 1011 */
>>> +	72000,	/* 1100 */
>>> +	0,	/* 1101 */
>>> +	0,	/* 1110 */
>>> +	1,	/* 1111 */
>>> +};
>>> +
>>> +static const uint64_t nsecs_per_sec = 1000000000L;
>>> +
>>> +static int sunxi_pwm_set_invert(struct udevice *dev, uint channel,
>> bool polarity)
>>> +{
>>> +	struct sunxi_pwm_priv *priv = dev_get_priv(dev);
>>> +
>>> +	debug("%s: polarity=%u\n", __func__, polarity);
>>> +	priv->invert = polarity;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int sunxi_pwm_set_config(struct udevice *dev, uint channel,
>> uint period_ns,
>>> +			     uint duty_ns)
>>> +{
>>> +	struct sunxi_pwm_priv *priv = dev_get_priv(dev);
>>> +	struct sunxi_pwm *regs = priv->regs;
>>> +	int prescaler;
>>> +	u32 v, period, duty;
>>> +	uint64_t div = 0, pval = 0, scaled_freq = 0;
>>> +
>>> +	debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns,
>> duty_ns);
>>> +
>>> +	for (prescaler = 0; prescaler < SUNXI_PWM_CTRL_PRESCALE0_MASK;
>> prescaler++) {
>>> +		if (!prescaler_table[prescaler])
>>> +			continue;
>>> +		div = priv->freq;
>>> +		pval = prescaler_table[prescaler];
>>> +		scaled_freq = lldiv(div, pval);
>>> +		div = scaled_freq * period_ns;
>>> +		div = lldiv(div, nsecs_per_sec);
>>> +		if (div - 1 <= SUNXI_PWM_CH0_PERIOD_MAX)
>>> +			break;
>>> +	}
>>> +
>>> +	if (div - 1 > SUNXI_PWM_CH0_PERIOD_MAX) {
>>> +		debug("%s: failed to find prescaler value\n", __func__);
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	period = div;
>>> +	div = scaled_freq * duty_ns;
>>> +	div = lldiv(div, nsecs_per_sec);
>>> +	duty = div;
>>> +
>>> +	if (priv->prescaler != prescaler) {
>>> +		/* Mask clock to update prescaler */
>>> +		v = readl(&regs->ctrl);
>>> +		v &= ~SUNXI_PWM_CTRL_CLK_GATE;
>>> +		writel(v, &regs->ctrl);
>>> +		v &= ~SUNXI_PWM_CTRL_PRESCALE0_MASK;
>>> +		v |= (priv->prescaler & SUNXI_PWM_CTRL_PRESCALE0_MASK);
>>> +		writel(v, &regs->ctrl);
>>> +		v |= SUNXI_PWM_CTRL_CLK_GATE;
>>> +		writel(v, &regs->ctrl);
>>> +		priv->prescaler = prescaler;
>>> +	}
>>> +
>>> +	writel(SUNXI_PWM_CH0_PERIOD_PRD(period) |
>>> +	       SUNXI_PWM_CH0_PERIOD_DUTY(duty), &regs->ch0_period);
>>> +
>>> +	debug("%s: prescaler: %d, period: %d, duty: %d\n", __func__,
>> priv->prescaler,
>>> +	      period, duty);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int sunxi_pwm_set_enable(struct udevice *dev, uint channel,
>> bool enable)
>>> +{
>>> +	struct sunxi_pwm_priv *priv = dev_get_priv(dev);
>>> +	struct sunxi_pwm *regs = priv->regs;
>>> +	uint32_t v;
>>> +
>>> +	debug("%s: Enable '%s'\n", __func__, dev->name);
>>> +
>>> +	v = readl(&regs->ctrl);
>>> +	if (!enable) {
>>> +		v &= ~SUNXI_PWM_CTRL_ENABLE0;
>>> +		writel(v, &regs->ctrl);
>>> +		return 0;
>>> +	}
>>> +
>>> +	v &= ~SUNXI_PWM_CTRL_POLARITY0_MASK;
>>> +	v |= priv->invert ? SUNXI_PWM_CTRL_POLARITY0(0) :
>>> +		      SUNXI_PWM_CTRL_POLARITY0(1);
>>> +	v |= SUNXI_PWM_CTRL_ENABLE0;
>>> +	writel(v, &regs->ctrl);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int sunxi_pwm_ofdata_to_platdata(struct udevice *dev)
>>> +{
>>> +	struct sunxi_pwm_priv *priv = dev_get_priv(dev);
>>> +
>>> +	priv->regs = (struct sunxi_pwm *)devfdt_get_addr(dev);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int sunxi_pwm_probe(struct udevice *dev)
>>> +{
>>> +	struct sunxi_pwm_priv *priv = dev_get_priv(dev);
>>> +
>>> +	priv->freq = 24000000;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct pwm_ops sunxi_pwm_ops = {
>>> +	.set_invert	= sunxi_pwm_set_invert,
>>> +	.set_config	= sunxi_pwm_set_config,
>>> +	.set_enable	= sunxi_pwm_set_enable,
>>> +};
>>> +
>>> +static const struct udevice_id sunxi_pwm_ids[] = {
>>> +	{ .compatible = "allwinner,sun8i-h3-pwm" },
>>> +	{ .compatible = "allwinner,sun50i-a64-pwm" },
>>> +	{ }
>>> +};
>>> +
>>> +U_BOOT_DRIVER(sunxi_pwm) = {
>>> +	.name	= "sunxi_pwm",
>>> +	.id	= UCLASS_PWM,
>>> +	.of_match = sunxi_pwm_ids,
>>> +	.ops	= &sunxi_pwm_ops,
>>> +	.ofdata_to_platdata	= sunxi_pwm_ofdata_to_platdata,
>>> +	.probe		= sunxi_pwm_probe,
>>> +	.priv_auto_alloc_size	= sizeof(struct sunxi_pwm_priv),
>>> +};
>>>


More information about the U-Boot mailing list