public inbox for u-boot@lists.denx.de
 help / color / mirror / Atom feed
* [U-Boot] [RESEND PATCH 1/3] pwm: sunxi: add support for PWM found on Allwinner A64 and H3
@ 2017-09-19  5:06 Vasily Khoruzhick
  2017-09-19  5:06 ` [U-Boot] [RESEND PATCH 2/3] video: pwm_backlight: make regulator optional Vasily Khoruzhick
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Vasily Khoruzhick @ 2017-09-19  5:06 UTC (permalink / raw)
  To: u-boot

This commit adds basic support for PWM found on Allwinner A64 and H3

Signed-off-by: Vasily Khoruzhick <anarsoul@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);
+#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@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),
+};
-- 
2.14.1

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [U-Boot] [RESEND PATCH 2/3] video: pwm_backlight: make regulator optional
  2017-09-19  5:06 [U-Boot] [RESEND PATCH 1/3] pwm: sunxi: add support for PWM found on Allwinner A64 and H3 Vasily Khoruzhick
@ 2017-09-19  5:06 ` Vasily Khoruzhick
  2017-09-19  5:06 ` [U-Boot] [RESEND PATCH 3/3] dts: sunxi: add PWM node for sun50i Vasily Khoruzhick
  2017-09-19  8:51 ` [U-Boot] [RESEND PATCH 1/3] pwm: sunxi: add support for PWM found on Allwinner A64 and H3 Andre Przywara
  2 siblings, 0 replies; 8+ messages in thread
From: Vasily Khoruzhick @ 2017-09-19  5:06 UTC (permalink / raw)
  To: u-boot

u-boot doesn't have dummy regulators, so pwm_backlight probe
will fail if regulator is missing. Make it optional to get this
driver working on platforms where there's no backlight regultor.

Signed-off-by: Vasily Khoruzhick <anarsoul@gmail.com>
---
 drivers/video/pwm_backlight.c | 28 ++++++++++++++++------------
 1 file changed, 16 insertions(+), 12 deletions(-)

diff --git a/drivers/video/pwm_backlight.c b/drivers/video/pwm_backlight.c
index fbd7bf7838..05e56ffead 100644
--- a/drivers/video/pwm_backlight.c
+++ b/drivers/video/pwm_backlight.c
@@ -32,16 +32,21 @@ static int pwm_backlight_enable(struct udevice *dev)
 	uint duty_cycle;
 	int ret;
 
-	plat = dev_get_uclass_platdata(priv->reg);
-	debug("%s: Enable '%s', regulator '%s'/'%s'\n", __func__, dev->name,
-	      priv->reg->name, plat->name);
-	ret = regulator_set_enable(priv->reg, true);
-	if (ret) {
-		debug("%s: Cannot enable regulator for PWM '%s'\n", __func__,
-		      dev->name);
-		return ret;
+	if (priv->reg) {
+		plat = dev_get_uclass_platdata(priv->reg);
+		debug("%s: Enable '%s', regulator '%s'/'%s'\n", __func__, dev->name,
+		      priv->reg->name, plat->name);
+		ret = regulator_set_enable(priv->reg, true);
+		if (ret) {
+			debug("%s: Cannot enable regulator for PWM '%s'\n", __func__,
+			      dev->name);
+			return ret;
+		}
+		mdelay(120);
 	}
-	mdelay(120);
+
+	debug("%s: default: %d, min: %d, max: %d\n", __func__,
+		priv->default_level, priv->min_level, priv->max_level);
 
 	duty_cycle = priv->period_ns * (priv->default_level - priv->min_level) /
 		(priv->max_level - priv->min_level + 1);
@@ -68,10 +73,9 @@ static int pwm_backlight_ofdata_to_platdata(struct udevice *dev)
 	debug("%s: start\n", __func__);
 	ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev,
 					   "power-supply", &priv->reg);
-	if (ret) {
+	if (ret)
 		debug("%s: Cannot get power supply: ret=%d\n", __func__, ret);
-		return ret;
-	}
+
 	ret = gpio_request_by_name(dev, "enable-gpios", 0, &priv->enable,
 				   GPIOD_IS_OUT);
 	if (ret) {
-- 
2.14.1

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [U-Boot] [RESEND PATCH 3/3] dts: sunxi: add PWM node for sun50i
  2017-09-19  5:06 [U-Boot] [RESEND PATCH 1/3] pwm: sunxi: add support for PWM found on Allwinner A64 and H3 Vasily Khoruzhick
  2017-09-19  5:06 ` [U-Boot] [RESEND PATCH 2/3] video: pwm_backlight: make regulator optional Vasily Khoruzhick
@ 2017-09-19  5:06 ` Vasily Khoruzhick
  2017-09-19  8:42   ` Maxime Ripard
  2017-09-19  9:05   ` Andre Przywara
  2017-09-19  8:51 ` [U-Boot] [RESEND PATCH 1/3] pwm: sunxi: add support for PWM found on Allwinner A64 and H3 Andre Przywara
  2 siblings, 2 replies; 8+ messages in thread
From: Vasily Khoruzhick @ 2017-09-19  5:06 UTC (permalink / raw)
  To: u-boot

Signed-off-by: Vasily Khoruzhick <anarsoul@gmail.com>
---
 arch/arm/dts/sun50i-a64.dtsi | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/arch/arm/dts/sun50i-a64.dtsi b/arch/arm/dts/sun50i-a64.dtsi
index 65a344d9ce..14e94bf00e 100644
--- a/arch/arm/dts/sun50i-a64.dtsi
+++ b/arch/arm/dts/sun50i-a64.dtsi
@@ -319,6 +319,14 @@
 			};
 		};
 
+		pwm: pwm at 01c21400 {
+			compatible = "allwinner,sun50i-a64-pwm";
+			reg = <0x01c21400 0x8>;
+			clocks = <&osc24M>;
+			#pwm-cells = <3>;
+			status = "disabled";
+		};
+
 		uart0: serial at 1c28000 {
 			compatible = "snps,dw-apb-uart";
 			reg = <0x01c28000 0x400>;
-- 
2.14.1

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [U-Boot] [RESEND PATCH 3/3] dts: sunxi: add PWM node for sun50i
  2017-09-19  5:06 ` [U-Boot] [RESEND PATCH 3/3] dts: sunxi: add PWM node for sun50i Vasily Khoruzhick
@ 2017-09-19  8:42   ` Maxime Ripard
  2017-09-19  9:05   ` Andre Przywara
  1 sibling, 0 replies; 8+ messages in thread
From: Maxime Ripard @ 2017-09-19  8:42 UTC (permalink / raw)
  To: u-boot

On Mon, Sep 18, 2017 at 10:06:41PM -0700, Vasily Khoruzhick wrote:
> Signed-off-by: Vasily Khoruzhick <anarsoul@gmail.com>

Commit log please. At least mention the fact that this binding has not
been accepted in Linux yet.

Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: not available
URL: <http://lists.denx.de/pipermail/u-boot/attachments/20170919/e688db64/attachment.sig>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [U-Boot] [RESEND PATCH 1/3] pwm: sunxi: add support for PWM found on Allwinner A64 and H3
  2017-09-19  5:06 [U-Boot] [RESEND PATCH 1/3] pwm: sunxi: add support for PWM found on Allwinner A64 and H3 Vasily Khoruzhick
  2017-09-19  5:06 ` [U-Boot] [RESEND PATCH 2/3] video: pwm_backlight: make regulator optional Vasily Khoruzhick
  2017-09-19  5:06 ` [U-Boot] [RESEND PATCH 3/3] dts: sunxi: add PWM node for sun50i Vasily Khoruzhick
@ 2017-09-19  8:51 ` Andre Przywara
  2017-09-19  8:53   ` Icenowy Zheng
  2 siblings, 1 reply; 8+ messages in thread
From: Andre Przywara @ 2017-09-19  8:51 UTC (permalink / raw)
  To: u-boot

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?

> 
> Signed-off-by: Vasily Khoruzhick <anarsoul@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@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),
> +};
> 

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [U-Boot] [RESEND PATCH 1/3] pwm: sunxi: add support for PWM found on Allwinner A64 and H3
  2017-09-19  8:51 ` [U-Boot] [RESEND PATCH 1/3] pwm: sunxi: add support for PWM found on Allwinner A64 and H3 Andre Przywara
@ 2017-09-19  8:53   ` Icenowy Zheng
  2017-09-19  9:31     ` Andre Przywara
  0 siblings, 1 reply; 8+ messages in thread
From: Icenowy Zheng @ 2017-09-19  8:53 UTC (permalink / raw)
  To: u-boot



于 2017年9月19日 GMT+08:00 下午4:51:32, Andre Przywara <andre.przywara@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.

>
>> 
>> Signed-off-by: Vasily Khoruzhick <anarsoul@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@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),
>> +};
>> 

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [U-Boot] [RESEND PATCH 3/3] dts: sunxi: add PWM node for sun50i
  2017-09-19  5:06 ` [U-Boot] [RESEND PATCH 3/3] dts: sunxi: add PWM node for sun50i Vasily Khoruzhick
  2017-09-19  8:42   ` Maxime Ripard
@ 2017-09-19  9:05   ` Andre Przywara
  1 sibling, 0 replies; 8+ messages in thread
From: Andre Przywara @ 2017-09-19  9:05 UTC (permalink / raw)
  To: u-boot

Hi,

On 19/09/17 06:06, Vasily Khoruzhick wrote:
> Signed-off-by: Vasily Khoruzhick <anarsoul@gmail.com>
> ---
>  arch/arm/dts/sun50i-a64.dtsi | 8 ++++++++
>  1 file changed, 8 insertions(+)
> 
> diff --git a/arch/arm/dts/sun50i-a64.dtsi b/arch/arm/dts/sun50i-a64.dtsi
> index 65a344d9ce..14e94bf00e 100644
> --- a/arch/arm/dts/sun50i-a64.dtsi
> +++ b/arch/arm/dts/sun50i-a64.dtsi
> @@ -319,6 +319,14 @@
>  			};
>  		};
>  
> +		pwm: pwm at 01c21400 {
> +			compatible = "allwinner,sun50i-a64-pwm";

So the A64 PWM seems to be fully compatible with the H3 one, which has a
documented binding (also used in the Linux driver).
So can you please make this:
		compatible = "allwinner,sun50i-a64-pwm",
			     "allwinner,sun8i-h3-pwm";

and then drop the sun50i-a64-pwm string in the driver?

And I wonder if we should have the pinctrl already in here, since there
is only one choice for PWM0? At least that would document the clash.

> +			reg = <0x01c21400 0x8>;
> +			clocks = <&osc24M>;
> +			#pwm-cells = <3>;
> +			status = "disabled";
> +		};
> +
>  		uart0: serial at 1c28000 {
>  			compatible = "snps,dw-apb-uart";
>  			reg = <0x01c28000 0x400>;
> 

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [U-Boot] [RESEND PATCH 1/3] pwm: sunxi: add support for PWM found on Allwinner A64 and H3
  2017-09-19  8:53   ` Icenowy Zheng
@ 2017-09-19  9:31     ` Andre Przywara
  0 siblings, 0 replies; 8+ messages in thread
From: Andre Przywara @ 2017-09-19  9:31 UTC (permalink / raw)
  To: u-boot

Hi,

On 19/09/17 09:53, Icenowy Zheng wrote:
> 
> 
> 于 2017年9月19日 GMT+08:00 下午4:51:32, Andre Przywara <andre.przywara@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@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@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),
>>> +};
>>>

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2017-09-19  9:31 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-09-19  5:06 [U-Boot] [RESEND PATCH 1/3] pwm: sunxi: add support for PWM found on Allwinner A64 and H3 Vasily Khoruzhick
2017-09-19  5:06 ` [U-Boot] [RESEND PATCH 2/3] video: pwm_backlight: make regulator optional Vasily Khoruzhick
2017-09-19  5:06 ` [U-Boot] [RESEND PATCH 3/3] dts: sunxi: add PWM node for sun50i Vasily Khoruzhick
2017-09-19  8:42   ` Maxime Ripard
2017-09-19  9:05   ` Andre Przywara
2017-09-19  8:51 ` [U-Boot] [RESEND PATCH 1/3] pwm: sunxi: add support for PWM found on Allwinner A64 and H3 Andre Przywara
2017-09-19  8:53   ` Icenowy Zheng
2017-09-19  9:31     ` Andre Przywara

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox