Commit 1c353aea2b3d36e59ae4b6af7cf2302d40ac96dd
Committed by
Anatolij Gustschin
1 parent
1005e4e5f6
Exists in
smarc_8mq_lf_v2020.04
and in
11 other branches
pwm: sunxi: add support for PWM found on Allwinner A64
This commit adds basic support for PWM found on Allwinner A64. It can be used for pwm_backlight driver (e.g. for Pinebook) Signed-off-by: Vasily Khoruzhick <anarsoul@gmail.com>
Showing 5 changed files with 199 additions and 0 deletions Side-by-side Diff
arch/arm/include/asm/arch-sunxi/gpio.h
arch/arm/include/asm/arch-sunxi/pwm.h
... | ... | @@ -10,9 +10,16 @@ |
10 | 10 | #define SUNXI_PWM_CH0_PERIOD (SUNXI_PWM_BASE + 4) |
11 | 11 | |
12 | 12 | #define SUNXI_PWM_CTRL_PRESCALE0(x) ((x) & 0xf) |
13 | +#define SUNXI_PWM_CTRL_PRESCALE0_MASK 0xf | |
13 | 14 | #define SUNXI_PWM_CTRL_ENABLE0 (0x5 << 4) |
14 | 15 | #define SUNXI_PWM_CTRL_POLARITY0(x) ((x) << 5) |
16 | +#define SUNXI_PWM_CTRL_CH0_ACT_STA BIT(5) | |
17 | +#define SUNXI_PWM_CTRL_CLK_GATE BIT(6) | |
15 | 18 | |
19 | +#define SUNXI_PWM_CH0_PERIOD_MAX (0xffff) | |
20 | +#define SUNXI_PWM_CH0_PERIOD_PRD(x) ((x & 0xffff) << 16) | |
21 | +#define SUNXI_PWM_CH0_PERIOD_DUTY(x) ((x) & 0xffff) | |
22 | + | |
16 | 23 | #define SUNXI_PWM_PERIOD_80PCT 0x04af03c0 |
17 | 24 | |
18 | 25 | #if defined CONFIG_MACH_SUN4I || defined CONFIG_MACH_SUN5I |
... | ... | @@ -29,6 +36,11 @@ |
29 | 36 | #define SUNXI_PWM_PIN0 SUNXI_GPH(0) |
30 | 37 | #define SUNXI_PWM_MUX SUN8I_GPH_PWM |
31 | 38 | #endif |
39 | + | |
40 | +struct sunxi_pwm { | |
41 | + u32 ctrl; | |
42 | + u32 ch0_period; | |
43 | +}; | |
32 | 44 | |
33 | 45 | #endif |
drivers/pwm/Kconfig
... | ... | @@ -43,4 +43,11 @@ |
43 | 43 | four channels with a programmable period and duty cycle. Only a |
44 | 44 | 32KHz clock is supported by the driver but the duty cycle is |
45 | 45 | configurable. |
46 | + | |
47 | +config PWM_SUNXI | |
48 | + bool "Enable support for the Allwinner Sunxi PWM" | |
49 | + depends on DM_PWM | |
50 | + help | |
51 | + This PWM is found on H3, A64 and other Allwinner SoCs. It supports a | |
52 | + programmable period and duty cycle. A 16-bit counter is used. |
drivers/pwm/Makefile
drivers/pwm/sunxi_pwm.c
1 | +// SPDX-License-Identifier: GPL-2.0+ | |
2 | +/* | |
3 | + * Copyright (c) 2017-2018 Vasily Khoruzhick <anarsoul@gmail.com> | |
4 | + */ | |
5 | + | |
6 | +#include <common.h> | |
7 | +#include <div64.h> | |
8 | +#include <dm.h> | |
9 | +#include <pwm.h> | |
10 | +#include <regmap.h> | |
11 | +#include <syscon.h> | |
12 | +#include <asm/io.h> | |
13 | +#include <asm/arch/pwm.h> | |
14 | +#include <asm/arch/gpio.h> | |
15 | +#include <power/regulator.h> | |
16 | + | |
17 | +DECLARE_GLOBAL_DATA_PTR; | |
18 | + | |
19 | +#define OSC_24MHZ 24000000 | |
20 | + | |
21 | +struct sunxi_pwm_priv { | |
22 | + struct sunxi_pwm *regs; | |
23 | + bool invert; | |
24 | + u32 prescaler; | |
25 | +}; | |
26 | + | |
27 | +static const u32 prescaler_table[] = { | |
28 | + 120, /* 0000 */ | |
29 | + 180, /* 0001 */ | |
30 | + 240, /* 0010 */ | |
31 | + 360, /* 0011 */ | |
32 | + 480, /* 0100 */ | |
33 | + 0, /* 0101 */ | |
34 | + 0, /* 0110 */ | |
35 | + 0, /* 0111 */ | |
36 | + 12000, /* 1000 */ | |
37 | + 24000, /* 1001 */ | |
38 | + 36000, /* 1010 */ | |
39 | + 48000, /* 1011 */ | |
40 | + 72000, /* 1100 */ | |
41 | + 0, /* 1101 */ | |
42 | + 0, /* 1110 */ | |
43 | + 1, /* 1111 */ | |
44 | +}; | |
45 | + | |
46 | +static int sunxi_pwm_config_pinmux(void) | |
47 | +{ | |
48 | +#ifdef CONFIG_MACH_SUN50I | |
49 | + sunxi_gpio_set_cfgpin(SUNXI_GPD(22), SUNXI_GPD_PWM); | |
50 | +#endif | |
51 | + return 0; | |
52 | +} | |
53 | + | |
54 | +static int sunxi_pwm_set_invert(struct udevice *dev, uint channel, | |
55 | + bool polarity) | |
56 | +{ | |
57 | + struct sunxi_pwm_priv *priv = dev_get_priv(dev); | |
58 | + | |
59 | + debug("%s: polarity=%u\n", __func__, polarity); | |
60 | + priv->invert = polarity; | |
61 | + | |
62 | + return 0; | |
63 | +} | |
64 | + | |
65 | +static int sunxi_pwm_set_config(struct udevice *dev, uint channel, | |
66 | + uint period_ns, uint duty_ns) | |
67 | +{ | |
68 | + struct sunxi_pwm_priv *priv = dev_get_priv(dev); | |
69 | + struct sunxi_pwm *regs = priv->regs; | |
70 | + int prescaler; | |
71 | + u32 v, period = 0, duty; | |
72 | + u64 scaled_freq = 0; | |
73 | + const u32 nsecs_per_sec = 1000000000U; | |
74 | + | |
75 | + debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns); | |
76 | + | |
77 | + for (prescaler = 0; prescaler < SUNXI_PWM_CTRL_PRESCALE0_MASK; | |
78 | + prescaler++) { | |
79 | + if (!prescaler_table[prescaler]) | |
80 | + continue; | |
81 | + scaled_freq = lldiv(OSC_24MHZ, prescaler_table[prescaler]); | |
82 | + period = lldiv(scaled_freq * period_ns, nsecs_per_sec); | |
83 | + if (period - 1 <= SUNXI_PWM_CH0_PERIOD_MAX) | |
84 | + break; | |
85 | + } | |
86 | + | |
87 | + if (period - 1 > SUNXI_PWM_CH0_PERIOD_MAX) { | |
88 | + debug("%s: failed to find prescaler value\n", __func__); | |
89 | + return -EINVAL; | |
90 | + } | |
91 | + | |
92 | + duty = lldiv(scaled_freq * duty_ns, nsecs_per_sec); | |
93 | + | |
94 | + if (priv->prescaler != prescaler) { | |
95 | + /* Mask clock to update prescaler */ | |
96 | + v = readl(®s->ctrl); | |
97 | + v &= ~SUNXI_PWM_CTRL_CLK_GATE; | |
98 | + writel(v, ®s->ctrl); | |
99 | + v &= ~SUNXI_PWM_CTRL_PRESCALE0_MASK; | |
100 | + v |= (priv->prescaler & SUNXI_PWM_CTRL_PRESCALE0_MASK); | |
101 | + writel(v, ®s->ctrl); | |
102 | + v |= SUNXI_PWM_CTRL_CLK_GATE; | |
103 | + writel(v, ®s->ctrl); | |
104 | + priv->prescaler = prescaler; | |
105 | + } | |
106 | + | |
107 | + writel(SUNXI_PWM_CH0_PERIOD_PRD(period) | | |
108 | + SUNXI_PWM_CH0_PERIOD_DUTY(duty), ®s->ch0_period); | |
109 | + | |
110 | + debug("%s: prescaler: %d, period: %d, duty: %d\n", | |
111 | + __func__, priv->prescaler, | |
112 | + period, duty); | |
113 | + | |
114 | + return 0; | |
115 | +} | |
116 | + | |
117 | +static int sunxi_pwm_set_enable(struct udevice *dev, uint channel, bool enable) | |
118 | +{ | |
119 | + struct sunxi_pwm_priv *priv = dev_get_priv(dev); | |
120 | + struct sunxi_pwm *regs = priv->regs; | |
121 | + u32 v; | |
122 | + | |
123 | + debug("%s: Enable '%s'\n", __func__, dev->name); | |
124 | + | |
125 | + v = readl(®s->ctrl); | |
126 | + if (!enable) { | |
127 | + v &= ~SUNXI_PWM_CTRL_ENABLE0; | |
128 | + writel(v, ®s->ctrl); | |
129 | + return 0; | |
130 | + } | |
131 | + | |
132 | + sunxi_pwm_config_pinmux(); | |
133 | + | |
134 | + if (priv->invert) | |
135 | + v &= ~SUNXI_PWM_CTRL_CH0_ACT_STA; | |
136 | + else | |
137 | + v |= SUNXI_PWM_CTRL_CH0_ACT_STA; | |
138 | + v |= SUNXI_PWM_CTRL_ENABLE0; | |
139 | + writel(v, ®s->ctrl); | |
140 | + | |
141 | + return 0; | |
142 | +} | |
143 | + | |
144 | +static int sunxi_pwm_ofdata_to_platdata(struct udevice *dev) | |
145 | +{ | |
146 | + struct sunxi_pwm_priv *priv = dev_get_priv(dev); | |
147 | + | |
148 | + priv->regs = (struct sunxi_pwm *)devfdt_get_addr(dev); | |
149 | + | |
150 | + return 0; | |
151 | +} | |
152 | + | |
153 | +static int sunxi_pwm_probe(struct udevice *dev) | |
154 | +{ | |
155 | + return 0; | |
156 | +} | |
157 | + | |
158 | +static const struct pwm_ops sunxi_pwm_ops = { | |
159 | + .set_invert = sunxi_pwm_set_invert, | |
160 | + .set_config = sunxi_pwm_set_config, | |
161 | + .set_enable = sunxi_pwm_set_enable, | |
162 | +}; | |
163 | + | |
164 | +static const struct udevice_id sunxi_pwm_ids[] = { | |
165 | + { .compatible = "allwinner,sun5i-a13-pwm" }, | |
166 | + { .compatible = "allwinner,sun50i-a64-pwm" }, | |
167 | + { } | |
168 | +}; | |
169 | + | |
170 | +U_BOOT_DRIVER(sunxi_pwm) = { | |
171 | + .name = "sunxi_pwm", | |
172 | + .id = UCLASS_PWM, | |
173 | + .of_match = sunxi_pwm_ids, | |
174 | + .ops = &sunxi_pwm_ops, | |
175 | + .ofdata_to_platdata = sunxi_pwm_ofdata_to_platdata, | |
176 | + .probe = sunxi_pwm_probe, | |
177 | + .priv_auto_alloc_size = sizeof(struct sunxi_pwm_priv), | |
178 | +}; |