pwm: sunxi: add support for PWM found on Allwinner A64 and H3
Subsystems (5)
PATCH RESEND 1/3 pwm: sunxi: add support for PWM found on Allwinner A64 and H3
This commit adds basic support for PWM found on Allwinner A64 and H3
--- 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
arch/arm/include/asm/arch-sunxi/gpio.h +1 -0
@@ -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
arch/arm/include/asm/arch-sunxi/pwm.h +12 -0
@@ -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
arch/arm/mach-sunxi/board.c +11 -0
@@ -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
drivers/pwm/Kconfig +7 -0
@@ -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.
drivers/pwm/Makefile +1 -0
@@ -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
drivers/pwm/sunxi_pwm.c new file +174 -0
174 lines not on this page · raw message
Signature · 1 line
2.14.1
PATCH RESEND 2/3 video: pwm_backlight: make regulator optional
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.
--- drivers/video/pwm_backlight.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-)
drivers/video/pwm_backlight.c +16 -12
@@ -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) {
Signature · 1 line
2.14.1
PATCH RESEND 3/3 dts: sunxi: add PWM node for sun50i
In reply to Vasily Khoruzhick--- arch/arm/dts/sun50i-a64.dtsi | 8 ++++++++ 1 file changed, 8 insertions(+)
arch/arm/dts/sun50i-a64.dtsi +8 -0
@@ -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>;
Signature · 1 line
2.14.1
PATCH RESEND 3/3 dts: sunxi: add PWM node for sun50i
Quoted 1 line · Vasily Khoruzhick
Signed-off-by Vasily Khoruzhick <[email protected]>
Commit log please. At least mention the fact that this binding has not been accepted in Linux yet.
Maxime
Signature · 10 lines
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>
PATCH RESEND 3/3 dts: sunxi: add PWM node for sun50i
In reply to Vasily KhoruzhickHi,
Quoted 10 lines · Vasily Khoruzhick
Signed-off-by Vasily Khoruzhick <[email protected]>--- arch/arm/dts/sun50i-a64.dtsi | 8 ++++++++ 1 file changed, 8 insertions(+)arch/arm/dts/sun50i-a64.dtsi +0
-0
arch/arm/dts/sun50i-a64.dtsi line 323
Quoted 5 lines
arch/arm/dts/sun50i-a64.dtsi +2
-0@@ -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.
Quoted 10 lines
arch/arm/dts/sun50i-a64.dtsi +6
-0+ reg = <0x01c21400 0x8>; + clocks = <&osc24M>; + #pwm-cells = <3>; + status = "disabled"; + }; + uart0: serial at 1c28000 { compatible = "snps,dw-apb-uart"; reg = <0x01c28000 0x400>;
PATCH RESEND 1/3 pwm: sunxi: add support for PWM found on Allwinner A64 and H3
In reply to Vasily KhoruzhickHi,
Quoted 1 line · Vasily Khoruzhick
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?
Quoted 59 lines
Signed-off-by Vasily Khoruzhick <[email protected]>--- 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.carch/arm/include/asm/arch-sunxi/gpio.h +1
-0@@ -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 3arch/arm/include/asm/arch-sunxi/pwm.h +12
-0@@ -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 arch/arm/mach-sunxi/board.c +0
-0
arch/arm/mach-sunxi/board.c static int gpio_init(void) · line 148
Quoted 8 lines
arch/arm/mach-sunxi/board.c +5
-0@@ -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.
Quoted 221 lines
arch/arm/mach-sunxi/board.c +6
-0+#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 drivers/pwm/Kconfig +7
-0@@ -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.drivers/pwm/Makefile +1
-0@@ -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.odrivers/pwm/sunxi_pwm.c new file +174
-0@@ -0,0 +1,174 @@ +/* +* Copyright (c) 2017 Vasily Khoruzhick <[email protected]> +* +* 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 (®s->ctrl); + v &= ~SUNXI_PWM_CTRL_CLK_GATE ; +writel (v, ®s->ctrl); + v &= ~SUNXI_PWM_CTRL_PRESCALE0_MASK ; + v |= (priv->prescaler &SUNXI_PWM_CTRL_PRESCALE0_MASK ); +writel (v, ®s->ctrl); + v |=SUNXI_PWM_CTRL_CLK_GATE ; +writel (v, ®s->ctrl); + priv->prescaler = prescaler; + } + +writel (SUNXI_PWM_CH0_PERIOD_PRD (period) | +SUNXI_PWM_CH0_PERIOD_DUTY (duty), ®s->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 (®s->ctrl); +if (!enable) { + v &= ~SUNXI_PWM_CTRL_ENABLE0 ; +writel (v, ®s->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, ®s->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 ), +};
PATCH RESEND 1/3 pwm: sunxi: add support for PWM found on Allwinner A64 and H3
于 2017年9月19日 GMT+08:00 下午4:51:32, Andre Przywara <[email protected]> 写到:
Quoted 5 lines
Hi,
Quoted 1 line · Vasily Khoruzhick
This commit adds basic support for PWM found on Allwinner A64 and H3
Quoted 8 lines
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.
Quoted 309 lines
Quoted 10 lines
Signed-off-by Vasily Khoruzhick <[email protected]>--- 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+++++++++++++++++++++++++++++++++
Review
Quoted 4 lines
6 files changed, 206 insertions(+) create mode 100644 drivers/pwm/sunxi_pwm.c(quoted diff) +0
-0
b/arch/arm/include/asm/arch-sunxi/gpio.hReview
Quoted 12 lines
index
24f85206c8..7265d18099100644arch/arm/include/asm/arch-sunxi/gpio.h +1
-0@@ -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(quoted diff) +0
-0
b/arch/arm/include/asm/arch-sunxi/pwm.hReview
Quoted 30 lines
index
5884b5dbe7..673e0eb7b5100644arch/arm/include/asm/arch-sunxi/pwm.h +12
-0@@ -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 (quoted diff) +0
-0
b/arch/arm/mach-sunxi/board.carch/arm/mach-sunxi/board.c static int gpio_init(void) · line 148
Quoted 12 lines
index
65b1ebd837..a85f973a46100644arch/arm/mach-sunxi/board.c +5
-0@@ -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.arch/arm/mach-sunxi/board.c static int gpio_init(void)
Quoted 6 lines
arch/arm/mach-sunxi/board.c +5
-0+#endif +#endif +return 0 ; +} + #if defined(CONFIG_SPL_BOARD_LOAD_IMAGE) &&
defined(CONFIG_SPL_BUILD)Quoted 77 lines
arch/arm/mach-sunxi/board.c +1
-0static 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 drivers/pwm/Kconfig +7
-0@@ -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.drivers/pwm/Makefile +1
-0@@ -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.odrivers/pwm/sunxi_pwm.c new file +37
-0@@ -0,0 +1,174 @@ +/* +* Copyright (c) 2017 Vasily Khoruzhick <[email protected]> +* +* 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 */ drivers/pwm/sunxi_pwm.c line 47
Quoted 10 lines
drivers/pwm/sunxi_pwm.c new file +10
-0+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)
drivers/pwm/sunxi_pwm.c
Quoted 10 lines
drivers/pwm/sunxi_pwm.c new file +10
-0+{ +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,drivers/pwm/sunxi_pwm.c
Quoted 9 lines
drivers/pwm/sunxi_pwm.c new file +9
-0+ 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);drivers/pwm/sunxi_pwm.c line 68
Quoted 2 lines
drivers/pwm/sunxi_pwm.c new file +2
-0+ + for (prescaler = 0; prescaler < SUNXI_PWM_CTRL_PRESCALE0_MASK;prescaler++) {
Quoted 28 lines
drivers/pwm/sunxi_pwm.c new file +28
-0+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 (®s->ctrl); + v &= ~SUNXI_PWM_CTRL_CLK_GATE ; +writel (v, ®s->ctrl); + v &= ~SUNXI_PWM_CTRL_PRESCALE0_MASK ; + v |= (priv->prescaler &SUNXI_PWM_CTRL_PRESCALE0_MASK );drivers/pwm/sunxi_pwm.c
Quoted 10 lines
drivers/pwm/sunxi_pwm.c new file +10
-0+writel (v, ®s->ctrl); + v |=SUNXI_PWM_CTRL_CLK_GATE ; +writel (v, ®s->ctrl); + priv->prescaler = prescaler; + } + +writel (SUNXI_PWM_CH0_PERIOD_PRD (period) | +SUNXI_PWM_CH0_PERIOD_DUTY (duty), ®s->ch0_period); + + debug("%s: prescaler: %d, period: %d, duty: %d\n", __func__,
priv->prescaler,drivers/pwm/sunxi_pwm.c
Quoted 6 lines
drivers/pwm/sunxi_pwm.c new file +6
-0+ period, duty); + +return 0 ; +} + +static int sunxi_pwm_set_enable(struct udevice *dev, uint channel,bool enable)
Quoted 63 lines
drivers/pwm/sunxi_pwm.c new file +62
-0+{ +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 (®s->ctrl); +if (!enable) { + v &= ~SUNXI_PWM_CTRL_ENABLE0 ; +writel (v, ®s->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, ®s->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 ), +};
PATCH RESEND 1/3 pwm: sunxi: add support for PWM found on Allwinner A64 and H3
Hi,
Quoted 17 lines · Icenowy Zheng
于 2017年9月19日 GMT+08:00 下午4:51:32, Andre Przywara <[email protected]> 写到:
Quoted 5 lines
Hi,
Quoted 1 line · Vasily Khoruzhick
This commit adds basic support for PWM found on Allwinner A64 and H3
Quoted 8 lines
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?
Quoted 1 line
Backlight for Pinebook.
Ah, right, forgot about that missing Ethernet there ... Thanks for the heads up.
Cheers,
Andre.
Quoted 308 lines
Quoted 308 lines
Quoted 9 lines
Signed-off-by Vasily Khoruzhick <[email protected]>--- 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+++++++++++++++++++++++++++++++++
Review
Quoted 4 lines
6 files changed, 206 insertions(+) create mode 100644 drivers/pwm/sunxi_pwm.c(quoted diff) +0
-0
b/arch/arm/include/asm/arch-sunxi/gpio.hReview
Quoted 12 lines
index
24f85206c8..7265d18099100644arch/arm/include/asm/arch-sunxi/gpio.h +1
-0@@ -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(quoted diff) +0
-0
b/arch/arm/include/asm/arch-sunxi/pwm.hReview
Quoted 30 lines
index
5884b5dbe7..673e0eb7b5100644arch/arm/include/asm/arch-sunxi/pwm.h +12
-0@@ -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 (quoted diff) +0
-0
b/arch/arm/mach-sunxi/board.carch/arm/mach-sunxi/board.c static int gpio_init(void) · line 148
Quoted 12 lines
index
65b1ebd837..a85f973a46100644arch/arm/mach-sunxi/board.c +5
-0@@ -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.arch/arm/mach-sunxi/board.c static int gpio_init(void)
Quoted 6 lines
arch/arm/mach-sunxi/board.c +5
-0+#endif +#endif +return 0 ; +} + #if defined(CONFIG_SPL_BOARD_LOAD_IMAGE) &&
defined(CONFIG_SPL_BUILD)Quoted 77 lines
arch/arm/mach-sunxi/board.c +1
-0static 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 drivers/pwm/Kconfig +7
-0@@ -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.drivers/pwm/Makefile +1
-0@@ -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.odrivers/pwm/sunxi_pwm.c new file +37
-0@@ -0,0 +1,174 @@ +/* +* Copyright (c) 2017 Vasily Khoruzhick <[email protected]> +* +* 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 */ drivers/pwm/sunxi_pwm.c line 47
Quoted 10 lines
drivers/pwm/sunxi_pwm.c new file +10
-0+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)
drivers/pwm/sunxi_pwm.c
Quoted 10 lines
drivers/pwm/sunxi_pwm.c new file +10
-0+{ +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,drivers/pwm/sunxi_pwm.c
Quoted 9 lines
drivers/pwm/sunxi_pwm.c new file +9
-0+ 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);drivers/pwm/sunxi_pwm.c line 68
Quoted 2 lines
drivers/pwm/sunxi_pwm.c new file +2
-0+ + for (prescaler = 0; prescaler < SUNXI_PWM_CTRL_PRESCALE0_MASK;prescaler++) {
Quoted 28 lines
drivers/pwm/sunxi_pwm.c new file +28
-0+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 (®s->ctrl); + v &= ~SUNXI_PWM_CTRL_CLK_GATE ; +writel (v, ®s->ctrl); + v &= ~SUNXI_PWM_CTRL_PRESCALE0_MASK ; + v |= (priv->prescaler &SUNXI_PWM_CTRL_PRESCALE0_MASK );drivers/pwm/sunxi_pwm.c
Quoted 10 lines
drivers/pwm/sunxi_pwm.c new file +10
-0+writel (v, ®s->ctrl); + v |=SUNXI_PWM_CTRL_CLK_GATE ; +writel (v, ®s->ctrl); + priv->prescaler = prescaler; + } + +writel (SUNXI_PWM_CH0_PERIOD_PRD (period) | +SUNXI_PWM_CH0_PERIOD_DUTY (duty), ®s->ch0_period); + + debug("%s: prescaler: %d, period: %d, duty: %d\n", __func__,
priv->prescaler,drivers/pwm/sunxi_pwm.c
Quoted 6 lines
drivers/pwm/sunxi_pwm.c new file +6
-0+ period, duty); + +return 0 ; +} + +static int sunxi_pwm_set_enable(struct udevice *dev, uint channel,bool enable)
Quoted 63 lines
drivers/pwm/sunxi_pwm.c new file +62
-0+{ +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 (®s->ctrl); +if (!enable) { + v &= ~SUNXI_PWM_CTRL_ENABLE0 ; +writel (v, ®s->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, ®s->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 ), +};