]>
Commit | Line | Data |
---|---|---|
1c353aea VK |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Copyright (c) 2017-2018 Vasily Khoruzhick <[email protected]> | |
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; | |
c33ba7ec VK |
70 | int best_prescaler = 0; |
71 | u32 v, best_period = 0, duty; | |
72 | u64 best_scaled_freq = 0; | |
1c353aea VK |
73 | const u32 nsecs_per_sec = 1000000000U; |
74 | ||
75 | debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns); | |
76 | ||
c33ba7ec | 77 | for (int prescaler = 0; prescaler <= SUNXI_PWM_CTRL_PRESCALE0_MASK; |
1c353aea | 78 | prescaler++) { |
c33ba7ec VK |
79 | u32 period = 0; |
80 | u64 scaled_freq = 0; | |
1c353aea VK |
81 | if (!prescaler_table[prescaler]) |
82 | continue; | |
83 | scaled_freq = lldiv(OSC_24MHZ, prescaler_table[prescaler]); | |
84 | period = lldiv(scaled_freq * period_ns, nsecs_per_sec); | |
c33ba7ec VK |
85 | if ((period - 1 <= SUNXI_PWM_CH0_PERIOD_MAX) && |
86 | best_period < period) { | |
87 | best_period = period; | |
88 | best_scaled_freq = scaled_freq; | |
89 | best_prescaler = prescaler; | |
90 | } | |
1c353aea VK |
91 | } |
92 | ||
c33ba7ec | 93 | if (best_period - 1 > SUNXI_PWM_CH0_PERIOD_MAX) { |
1c353aea VK |
94 | debug("%s: failed to find prescaler value\n", __func__); |
95 | return -EINVAL; | |
96 | } | |
97 | ||
c33ba7ec | 98 | duty = lldiv(best_scaled_freq * duty_ns, nsecs_per_sec); |
1c353aea | 99 | |
c33ba7ec | 100 | if (priv->prescaler != best_prescaler) { |
1c353aea VK |
101 | /* Mask clock to update prescaler */ |
102 | v = readl(®s->ctrl); | |
103 | v &= ~SUNXI_PWM_CTRL_CLK_GATE; | |
104 | writel(v, ®s->ctrl); | |
105 | v &= ~SUNXI_PWM_CTRL_PRESCALE0_MASK; | |
c33ba7ec | 106 | v |= (best_prescaler & SUNXI_PWM_CTRL_PRESCALE0_MASK); |
1c353aea VK |
107 | writel(v, ®s->ctrl); |
108 | v |= SUNXI_PWM_CTRL_CLK_GATE; | |
109 | writel(v, ®s->ctrl); | |
c33ba7ec | 110 | priv->prescaler = best_prescaler; |
1c353aea VK |
111 | } |
112 | ||
c33ba7ec | 113 | writel(SUNXI_PWM_CH0_PERIOD_PRD(best_period) | |
1c353aea VK |
114 | SUNXI_PWM_CH0_PERIOD_DUTY(duty), ®s->ch0_period); |
115 | ||
116 | debug("%s: prescaler: %d, period: %d, duty: %d\n", | |
117 | __func__, priv->prescaler, | |
c33ba7ec | 118 | best_period, duty); |
1c353aea VK |
119 | |
120 | return 0; | |
121 | } | |
122 | ||
123 | static int sunxi_pwm_set_enable(struct udevice *dev, uint channel, bool enable) | |
124 | { | |
125 | struct sunxi_pwm_priv *priv = dev_get_priv(dev); | |
126 | struct sunxi_pwm *regs = priv->regs; | |
127 | u32 v; | |
128 | ||
129 | debug("%s: Enable '%s'\n", __func__, dev->name); | |
130 | ||
131 | v = readl(®s->ctrl); | |
132 | if (!enable) { | |
133 | v &= ~SUNXI_PWM_CTRL_ENABLE0; | |
134 | writel(v, ®s->ctrl); | |
135 | return 0; | |
136 | } | |
137 | ||
138 | sunxi_pwm_config_pinmux(); | |
139 | ||
140 | if (priv->invert) | |
141 | v &= ~SUNXI_PWM_CTRL_CH0_ACT_STA; | |
142 | else | |
143 | v |= SUNXI_PWM_CTRL_CH0_ACT_STA; | |
144 | v |= SUNXI_PWM_CTRL_ENABLE0; | |
145 | writel(v, ®s->ctrl); | |
146 | ||
147 | return 0; | |
148 | } | |
149 | ||
150 | static int sunxi_pwm_ofdata_to_platdata(struct udevice *dev) | |
151 | { | |
152 | struct sunxi_pwm_priv *priv = dev_get_priv(dev); | |
153 | ||
154 | priv->regs = (struct sunxi_pwm *)devfdt_get_addr(dev); | |
155 | ||
156 | return 0; | |
157 | } | |
158 | ||
159 | static int sunxi_pwm_probe(struct udevice *dev) | |
160 | { | |
161 | return 0; | |
162 | } | |
163 | ||
164 | static const struct pwm_ops sunxi_pwm_ops = { | |
165 | .set_invert = sunxi_pwm_set_invert, | |
166 | .set_config = sunxi_pwm_set_config, | |
167 | .set_enable = sunxi_pwm_set_enable, | |
168 | }; | |
169 | ||
170 | static const struct udevice_id sunxi_pwm_ids[] = { | |
171 | { .compatible = "allwinner,sun5i-a13-pwm" }, | |
172 | { .compatible = "allwinner,sun50i-a64-pwm" }, | |
173 | { } | |
174 | }; | |
175 | ||
176 | U_BOOT_DRIVER(sunxi_pwm) = { | |
177 | .name = "sunxi_pwm", | |
178 | .id = UCLASS_PWM, | |
179 | .of_match = sunxi_pwm_ids, | |
180 | .ops = &sunxi_pwm_ops, | |
181 | .ofdata_to_platdata = sunxi_pwm_ofdata_to_platdata, | |
182 | .probe = sunxi_pwm_probe, | |
183 | .priv_auto_alloc_size = sizeof(struct sunxi_pwm_priv), | |
184 | }; |