]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/arm/allwinner/aw_pwm.c
Copy googletest 1.8.1 from ^/vendor/google/googletest/1.8.1 to .../contrib/googletest
[FreeBSD/FreeBSD.git] / sys / arm / allwinner / aw_pwm.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2018 Emmanuel Vadot <manu@FreeBSD.org>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
22  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  * $FreeBSD$
28  */
29
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32
33 #include <sys/param.h>
34 #include <sys/systm.h>
35 #include <sys/bus.h>
36 #include <sys/kernel.h>
37 #include <sys/module.h>
38 #include <sys/rman.h>
39 #include <sys/resource.h>
40 #include <machine/bus.h>
41
42 #include <dev/ofw/ofw_bus.h>
43 #include <dev/ofw/ofw_bus_subr.h>
44
45 #include <dev/extres/clk/clk.h>
46
47 #include <dev/pwm/pwmbus.h>
48
49 #include "pwm_if.h"
50
51 #define AW_PWM_CTRL                     0x00
52 #define  AW_PWM_CTRL_PRESCALE_MASK      0xF
53 #define  AW_PWM_CTRL_EN                 (1 << 4)
54 #define  AW_PWM_CTRL_ACTIVE_LEVEL_HIGH  (1 << 5)
55 #define  AW_PWM_CTRL_GATE               (1 << 6)
56 #define  AW_PWM_CTRL_MODE_MASK          0x80
57 #define  AW_PWM_CTRL_PULSE_MODE         (1 << 7)
58 #define  AW_PWM_CTRL_CYCLE_MODE         (0 << 7)
59 #define  AW_PWM_CTRL_PULSE_START        (1 << 8)
60 #define  AW_PWM_CTRL_CLK_BYPASS         (1 << 9)
61 #define  AW_PWM_CTRL_PERIOD_BUSY        (1 << 28)
62
63 #define AW_PWM_PERIOD                   0x04
64 #define AW_PWM_PERIOD_TOTAL_MASK        0xFFFF
65 #define AW_PWM_PERIOD_TOTAL_SHIFT       16
66 #define AW_PWM_PERIOD_ACTIVE_MASK       0xFFFF
67 #define AW_PWM_PERIOD_ACTIVE_SHIFT      0
68
69 #define AW_PWM_MAX_FREQ                 24000000
70
71 #define NS_PER_SEC      1000000000
72
73 static struct ofw_compat_data compat_data[] = {
74         { "allwinner,sun5i-a13-pwm",            1 },
75         { NULL,                                 0 }
76 };
77
78 static struct resource_spec aw_pwm_spec[] = {
79         { SYS_RES_MEMORY,       0,      RF_ACTIVE },
80         { -1, 0 }
81 };
82
83 struct aw_pwm_softc {
84         device_t        dev;
85         device_t        busdev;
86         clk_t           clk;
87         struct resource *res;
88
89         uint64_t        clk_freq;
90         unsigned int    period;
91         unsigned int    duty;
92         uint32_t        flags;
93         bool            enabled;
94 };
95
96 static uint32_t aw_pwm_clk_prescaler[] = {
97         120,
98         180,
99         240,
100         360,
101         480,
102         0,
103         0,
104         0,
105         12000,
106         24000,
107         36000,
108         48000,
109         72000,
110         0,
111         0,
112         1,
113 };
114
115 #define AW_PWM_READ(sc, reg)            bus_read_4((sc)->res, (reg))
116 #define AW_PWM_WRITE(sc, reg, val)      bus_write_4((sc)->res, (reg), (val))
117
118 static int aw_pwm_probe(device_t dev);
119 static int aw_pwm_attach(device_t dev);
120 static int aw_pwm_detach(device_t dev);
121
122 static int
123 aw_pwm_probe(device_t dev)
124 {
125         if (!ofw_bus_status_okay(dev))
126                 return (ENXIO);
127
128         if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
129                 return (ENXIO);
130
131         device_set_desc(dev, "Allwinner PWM");
132         return (BUS_PROBE_DEFAULT);
133 }
134
135 static int
136 aw_pwm_attach(device_t dev)
137 {
138         struct aw_pwm_softc *sc;
139         uint64_t clk_freq;
140         uint32_t reg;
141         int error;
142
143         sc = device_get_softc(dev);
144         sc->dev = dev;
145
146         error = clk_get_by_ofw_index(dev, 0, 0, &sc->clk);
147         if (error != 0) {
148                 device_printf(dev, "cannot get clock\n");
149                 goto fail;
150         }
151         error = clk_enable(sc->clk);
152
153         error = clk_get_freq(sc->clk, &sc->clk_freq);
154
155         if (bus_alloc_resources(dev, aw_pwm_spec, &sc->res) != 0) {
156                 device_printf(dev, "cannot allocate resources for device\n");
157                 error = ENXIO;
158                 goto fail;
159         }
160
161         if ((sc->busdev = pwmbus_attach_bus(dev)) == NULL)
162                 device_printf(dev, "Cannot attach pwm bus\n");
163
164         /* Read the configuration left by U-Boot */
165         reg = AW_PWM_READ(sc, AW_PWM_CTRL);
166         if (reg & (AW_PWM_CTRL_GATE | AW_PWM_CTRL_EN))
167                 sc->enabled = true;
168
169         reg = AW_PWM_READ(sc, AW_PWM_CTRL);
170         reg &= AW_PWM_CTRL_PRESCALE_MASK;
171         if (reg > nitems(aw_pwm_clk_prescaler)) {
172                 device_printf(dev, "Bad prescaler %x, cannot guess current settings\n", reg);
173                 goto out;
174         }
175         clk_freq = sc->clk_freq / aw_pwm_clk_prescaler[reg];
176
177         reg = AW_PWM_READ(sc, AW_PWM_PERIOD);
178         sc->period = NS_PER_SEC /
179                 (clk_freq / ((reg >> AW_PWM_PERIOD_TOTAL_SHIFT) & AW_PWM_PERIOD_TOTAL_MASK));
180         sc->duty = NS_PER_SEC /
181                 (clk_freq / ((reg >> AW_PWM_PERIOD_ACTIVE_SHIFT) & AW_PWM_PERIOD_ACTIVE_MASK));
182
183 out:
184         return (0);
185
186 fail:
187         aw_pwm_detach(dev);
188         return (error);
189 }
190
191 static int
192 aw_pwm_detach(device_t dev)
193 {
194         struct aw_pwm_softc *sc;
195
196         sc = device_get_softc(dev);
197
198         bus_generic_detach(sc->dev);
199
200         bus_release_resources(dev, aw_pwm_spec, &sc->res);
201
202         return (0);
203 }
204
205 static int
206 aw_pwm_channel_max(device_t dev, int *nchannel)
207 {
208
209         *nchannel = 1;
210
211         return (0);
212 }
213
214 static int
215 aw_pwm_channel_config(device_t dev, int channel, unsigned int period, unsigned int duty)
216 {
217         struct aw_pwm_softc *sc;
218         uint64_t period_freq, duty_freq;
219         uint64_t clk_rate, div;
220         uint32_t reg;
221         int prescaler;
222         int i;
223
224         sc = device_get_softc(dev);
225
226         period_freq = NS_PER_SEC / period;
227         if (period_freq > AW_PWM_MAX_FREQ)
228                 return (EINVAL);
229         duty_freq = NS_PER_SEC / duty;
230         if (duty_freq < period_freq) {
231                 device_printf(sc->dev, "duty < period\n");
232                 return (EINVAL);
233         }
234
235         /* First test without prescaler */
236         clk_rate = AW_PWM_MAX_FREQ;
237         prescaler = AW_PWM_CTRL_PRESCALE_MASK;
238         div = AW_PWM_MAX_FREQ / period_freq;
239         if ((div - 1) > AW_PWM_PERIOD_TOTAL_MASK) {
240                 /* Test all prescaler */
241                 for (i = 0; i < nitems(aw_pwm_clk_prescaler); i++) {
242                         if (aw_pwm_clk_prescaler[i] == 0)
243                                 continue;
244                         div = (AW_PWM_MAX_FREQ * aw_pwm_clk_prescaler[i]) / period_freq;
245                         if ((div - 1) < AW_PWM_PERIOD_TOTAL_MASK ) {
246                                 prescaler = i;
247                                 clk_rate = AW_PWM_MAX_FREQ / aw_pwm_clk_prescaler[i];
248                                 break;
249                         }
250                 }
251                 if (prescaler == AW_PWM_CTRL_PRESCALE_MASK)
252                         return (EINVAL);
253         }
254
255         reg = AW_PWM_READ(sc, AW_PWM_CTRL);
256         if (reg & AW_PWM_CTRL_PERIOD_BUSY) {
257                 device_printf(sc->dev, "pwm busy\n");
258                 return (EBUSY);
259         }
260
261         /* Write the prescalar */
262         reg &= ~AW_PWM_CTRL_PRESCALE_MASK;
263         reg |= prescaler;
264         AW_PWM_WRITE(sc, AW_PWM_CTRL, reg);
265
266         /* Write the total/active cycles */
267         reg = ((clk_rate / period_freq) << AW_PWM_PERIOD_TOTAL_SHIFT) |
268           ((clk_rate / duty_freq) << AW_PWM_PERIOD_ACTIVE_SHIFT);
269         AW_PWM_WRITE(sc, AW_PWM_PERIOD, reg);
270
271         sc->period = period;
272         sc->duty = duty;
273
274         return (0);
275 }
276
277 static int
278 aw_pwm_channel_get_config(device_t dev, int channel, unsigned int *period, unsigned int *duty)
279 {
280         struct aw_pwm_softc *sc;
281
282         sc = device_get_softc(dev);
283
284         *period = sc->period;
285         *duty = sc->duty;
286
287         return (0);
288 }
289
290 static int
291 aw_pwm_channel_enable(device_t dev, int channel, bool enable)
292 {
293         struct aw_pwm_softc *sc;
294         uint32_t reg;
295
296         sc = device_get_softc(dev);
297
298         if (enable && sc->enabled)
299                 return (0);
300
301         reg = AW_PWM_READ(sc, AW_PWM_CTRL);
302         if (enable)
303                 reg |= AW_PWM_CTRL_GATE | AW_PWM_CTRL_EN;
304         else
305                 reg &= ~(AW_PWM_CTRL_GATE | AW_PWM_CTRL_EN);
306
307         AW_PWM_WRITE(sc, AW_PWM_CTRL, reg);
308
309         sc->enabled = enable;
310
311         return (0);
312 }
313
314 static int
315 aw_pwm_channel_is_enabled(device_t dev, int channel, bool *enabled)
316 {
317         struct aw_pwm_softc *sc;
318
319         sc = device_get_softc(dev);
320
321         *enabled = sc->enabled;
322
323         return (0);
324 }
325
326 static device_t
327 aw_pwm_get_bus(device_t dev)
328 {
329         struct aw_pwm_softc *sc;
330
331         sc = device_get_softc(dev);
332
333         return (sc->busdev);
334 }
335 static device_method_t aw_pwm_methods[] = {
336         /* Device interface */
337         DEVMETHOD(device_probe,         aw_pwm_probe),
338         DEVMETHOD(device_attach,        aw_pwm_attach),
339         DEVMETHOD(device_detach,        aw_pwm_detach),
340
341         /* pwm interface */
342         DEVMETHOD(pwm_get_bus,                  aw_pwm_get_bus),
343         DEVMETHOD(pwm_channel_max,              aw_pwm_channel_max),
344         DEVMETHOD(pwm_channel_config,           aw_pwm_channel_config),
345         DEVMETHOD(pwm_channel_get_config,       aw_pwm_channel_get_config),
346         DEVMETHOD(pwm_channel_enable,           aw_pwm_channel_enable),
347         DEVMETHOD(pwm_channel_is_enabled,       aw_pwm_channel_is_enabled),
348
349         DEVMETHOD_END
350 };
351
352 static driver_t aw_pwm_driver = {
353         "pwm",
354         aw_pwm_methods,
355         sizeof(struct aw_pwm_softc),
356 };
357
358 static devclass_t aw_pwm_devclass;
359
360 DRIVER_MODULE(aw_pwm, simplebus, aw_pwm_driver, aw_pwm_devclass, 0, 0);
361 SIMPLEBUS_PNP_INFO(compat_data);