1 /* $NetBSD: exynos_pwm.c,v 1.3 2021/01/27 03:10:19 thorpej Exp $ */
2
3 /*-
4 * Copyright (c) 2018 Jared McNeill <jmcneill@invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include <sys/cdefs.h>
30
31 __KERNEL_RCSID(1, "$NetBSD: exynos_pwm.c,v 1.3 2021/01/27 03:10:19 thorpej Exp $");
32
33 #include <sys/param.h>
34 #include <sys/bus.h>
35 #include <sys/device.h>
36 #include <sys/intr.h>
37 #include <sys/systm.h>
38 #include <sys/time.h>
39
40 #include <dev/pwm/pwmvar.h>
41
42 #include <dev/fdt/fdtvar.h>
43
44 #define TCFG0 0x00
45 #define TCFG1 0x04
46 #define TCON 0x08
47 #define _TCON_OFF(n) ((n) == 0 ? 0 : (((n) + 1) * 4))
48 #define TCON_START(n) __BIT(_TCON_OFF(n) + 0)
49 #define TCON_UPDATE(n) __BIT(_TCON_OFF(n) + 1)
50 #define TCON_OUTINV(n) __BIT(_TCON_OFF(n) + 2)
51 #define TCON_AUTO_RELOAD(n) __BIT(_TCON_OFF(n) + 3)
52 #define TCON_DEADZONE_EN(n) __BIT(_TCON_OFF(n) + 4)
53 #define TCNTB(n) (0x0c + (n) * 12)
54 #define TCMPB(n) (0x10 + (n) * 12)
55 #define TCNTO(n) (0x14 + (n) * 12)
56 #define TINT_CSTAT 0x44
57
58 #define PWM_NTIMERS 5
59
60 static const struct device_compatible_entry compat_data[] = {
61 { .compat = "samsung,exynos4210-pwm" },
62 DEVICE_COMPAT_EOL
63 };
64
65 struct exynos_pwm_softc;
66
67 struct exynos_pwm_timer {
68 u_int timer_index;
69 struct pwm_controller timer_pwm;
70 };
71
72 struct exynos_pwm_softc {
73 device_t sc_dev;
74 bus_space_tag_t sc_bst;
75 bus_space_handle_t sc_bsh;
76
77 struct exynos_pwm_timer sc_timer[PWM_NTIMERS];
78 u_int sc_timermask;
79
80 u_int sc_clkfreq;
81 };
82
83 #define PWM_READ(sc, reg) \
84 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
85 #define PWM_WRITE(sc, reg, val) \
86 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
87
88 static int
exynos_pwm_enable(pwm_tag_t pwm,bool enable)89 exynos_pwm_enable(pwm_tag_t pwm, bool enable)
90 {
91 struct exynos_pwm_timer * const timer = pwm->pwm_priv;
92 struct exynos_pwm_softc * const sc = device_private(pwm->pwm_dev);
93 uint32_t tcon;
94
95 if (enable) {
96 tcon = PWM_READ(sc, TCON);
97 tcon &= ~TCON_START(timer->timer_index);
98 tcon |= TCON_UPDATE(timer->timer_index);
99 PWM_WRITE(sc, TCON, tcon);
100 tcon &= ~TCON_UPDATE(timer->timer_index);
101 tcon |= TCON_START(timer->timer_index);
102 tcon |= TCON_AUTO_RELOAD(timer->timer_index);
103 PWM_WRITE(sc, TCON, tcon);
104 } else {
105 tcon = PWM_READ(sc, TCON);
106 tcon &= ~TCON_AUTO_RELOAD(timer->timer_index);
107 PWM_WRITE(sc, TCON, tcon);
108 }
109
110 return 0;
111 }
112
113 static int
exynos_pwm_get_config(pwm_tag_t pwm,struct pwm_config * conf)114 exynos_pwm_get_config(pwm_tag_t pwm, struct pwm_config *conf)
115 {
116 struct exynos_pwm_timer * const timer = pwm->pwm_priv;
117 struct exynos_pwm_softc * const sc = device_private(pwm->pwm_dev);
118 uint32_t tcon, tcntb, tcmpb;
119
120 tcon = PWM_READ(sc, TCON);
121 tcntb = PWM_READ(sc, TCNTB(timer->timer_index));
122 tcmpb = PWM_READ(sc, TCMPB(timer->timer_index));
123
124 conf->polarity = (tcon & TCON_OUTINV(timer->timer_index)) ? PWM_ACTIVE_HIGH : PWM_ACTIVE_LOW;
125 conf->period = (u_int)(((uint64_t)tcntb * 1000000000) / sc->sc_clkfreq);
126 conf->duty_cycle = (u_int)(((uint64_t)tcmpb * 1000000000) / sc->sc_clkfreq);
127
128 return 0;
129 }
130
131 static int
exynos_pwm_set_config(pwm_tag_t pwm,const struct pwm_config * conf)132 exynos_pwm_set_config(pwm_tag_t pwm, const struct pwm_config *conf)
133 {
134 struct exynos_pwm_timer * const timer = pwm->pwm_priv;
135 struct exynos_pwm_softc * const sc = device_private(pwm->pwm_dev);
136 uint32_t tcon, tcntb, tcmpb;
137
138 tcon = PWM_READ(sc, TCON);
139 if (conf->polarity == PWM_ACTIVE_HIGH)
140 tcon |= TCON_OUTINV(timer->timer_index);
141 else
142 tcon &= ~TCON_OUTINV(timer->timer_index);
143 PWM_WRITE(sc, TCON, tcon);
144
145 tcntb = conf->period / (1000000000 / sc->sc_clkfreq);
146 tcmpb = conf->duty_cycle / (1000000000 / sc->sc_clkfreq);
147 if (tcmpb == 0)
148 tcmpb = 1;
149 tcmpb = tcntb - tcmpb;
150
151 PWM_WRITE(sc, TCNTB(timer->timer_index), tcntb - 1);
152 PWM_WRITE(sc, TCMPB(timer->timer_index), tcmpb - 1);
153
154 tcon = PWM_READ(sc, TCON);
155 tcon |= TCON_UPDATE(timer->timer_index);
156 tcon |= TCON_AUTO_RELOAD(timer->timer_index);
157 PWM_WRITE(sc, TCON, tcon);
158
159 tcon &= ~TCON_UPDATE(timer->timer_index);
160 PWM_WRITE(sc, TCON, tcon);
161
162 return 0;
163 }
164
165 static pwm_tag_t
exynos_pwm_get_tag(device_t dev,const void * data,size_t len)166 exynos_pwm_get_tag(device_t dev, const void *data, size_t len)
167 {
168 struct exynos_pwm_softc * const sc = device_private(dev);
169 struct exynos_pwm_timer *timer;
170 const u_int *pwm = data;
171 struct pwm_config conf;
172
173 if (len != 16)
174 return NULL;
175
176 const u_int index = be32toh(pwm[1]);
177 if (index >= 32 || (sc->sc_timermask & __BIT(index)) == 0)
178 return NULL;
179
180 const u_int period = be32toh(pwm[2]);
181 const u_int polarity = be32toh(pwm[3]);
182
183 timer = &sc->sc_timer[index];
184
185 /* Set initial timer polarity and period from specifier */
186 exynos_pwm_get_config(&timer->timer_pwm, &conf);
187 conf.period = period;
188 conf.polarity = polarity ? PWM_ACTIVE_LOW : PWM_ACTIVE_HIGH;
189 exynos_pwm_set_config(&timer->timer_pwm, &conf);
190
191 return &timer->timer_pwm;
192 }
193
194 static struct fdtbus_pwm_controller_func exynos_pwm_funcs = {
195 .get_tag = exynos_pwm_get_tag
196 };
197
198 static int
exynos_pwm_match(device_t parent,cfdata_t cf,void * aux)199 exynos_pwm_match(device_t parent, cfdata_t cf, void *aux)
200 {
201 struct fdt_attach_args * const faa = aux;
202
203 return of_compatible_match(faa->faa_phandle, compat_data);
204 }
205
206 static void
exynos_pwm_attach(device_t parent,device_t self,void * aux)207 exynos_pwm_attach(device_t parent, device_t self, void *aux)
208 {
209 struct exynos_pwm_softc * const sc = device_private(self);
210 struct fdt_attach_args * const faa = aux;
211 const int phandle = faa->faa_phandle;
212 const u_int *data;
213 struct clk *clk;
214 bus_addr_t addr;
215 bus_size_t size;
216 int error, len, n;
217
218 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
219 aprint_error(": couldn't get registers\n");
220 return;
221 }
222
223 clk = fdtbus_clock_get(phandle, "timers");
224 if (clk == NULL || clk_enable(clk) != 0) {
225 aprint_error(": couldn't enable timers clock\n");
226 return;
227 }
228
229 sc->sc_dev = self;
230 sc->sc_clkfreq = clk_get_rate(clk);
231 sc->sc_bst = faa->faa_bst;
232 error = bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh);
233 if (error) {
234 aprint_error(": couldn't map %#" PRIxBUSADDR ": %d", addr, error);
235 return;
236 }
237 for (n = 0; n < PWM_NTIMERS; n++) {
238 sc->sc_timer[n].timer_index = n;
239 sc->sc_timer[n].timer_pwm.pwm_enable = exynos_pwm_enable;
240 sc->sc_timer[n].timer_pwm.pwm_get_config = exynos_pwm_get_config;
241 sc->sc_timer[n].timer_pwm.pwm_set_config = exynos_pwm_set_config;
242 sc->sc_timer[n].timer_pwm.pwm_dev = self;
243 sc->sc_timer[n].timer_pwm.pwm_priv = &sc->sc_timer[n];
244 sc->sc_timermask |= __BIT(n);
245 }
246
247 data = fdtbus_get_prop(phandle, "samsung,pwm-outputs", &len);
248 if (data) {
249 sc->sc_timermask = 0;
250 for (n = 0; n < len / 4; n++) {
251 sc->sc_timermask |= __BIT(be32toh(data[n]));
252 }
253 }
254
255 aprint_naive("\n");
256 aprint_normal(": PWM\n");
257
258 fdtbus_register_pwm_controller(self, phandle,
259 &exynos_pwm_funcs);
260 }
261
262 CFATTACH_DECL_NEW(exynos_pwm, sizeof(struct exynos_pwm_softc),
263 exynos_pwm_match, exynos_pwm_attach, NULL, NULL);
264