1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2017 Poul-Henning Kamp <phk@FreeBSD.org> 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 AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 * 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 37 #include <sys/kernel.h> 38 #include <sys/module.h> 39 #include <sys/rman.h> 40 #include <sys/lock.h> 41 #include <sys/sysctl.h> 42 43 #include <machine/bus.h> 44 #include <machine/resource.h> 45 46 #include <dev/ofw/ofw_bus.h> 47 #include <dev/ofw/ofw_bus_subr.h> 48 49 #include <arm/broadcom/bcm2835/bcm2835_clkman.h> 50 51 static struct ofw_compat_data compat_data[] = { 52 {"broadcom,bcm2835-pwm", 1}, 53 {"brcm,bcm2835-pwm", 1}, 54 {NULL, 0} 55 }; 56 57 struct bcm_pwm_softc { 58 device_t sc_dev; 59 60 struct resource * sc_mem_res; 61 bus_space_tag_t sc_m_bst; 62 bus_space_handle_t sc_m_bsh; 63 64 device_t clkman; 65 66 uint32_t freq; 67 uint32_t period; 68 uint32_t ratio; 69 uint32_t mode; 70 71 }; 72 73 #define BCM_PWM_MEM_WRITE(_sc, _off, _val) \ 74 bus_space_write_4(_sc->sc_m_bst, _sc->sc_m_bsh, _off, _val) 75 #define BCM_PWM_MEM_READ(_sc, _off) \ 76 bus_space_read_4(_sc->sc_m_bst, _sc->sc_m_bsh, _off) 77 #define BCM_PWM_CLK_WRITE(_sc, _off, _val) \ 78 bus_space_write_4(_sc->sc_c_bst, _sc->sc_c_bsh, _off, _val) 79 #define BCM_PWM_CLK_READ(_sc, _off) \ 80 bus_space_read_4(_sc->sc_c_bst, _sc->sc_c_bsh, _off) 81 82 #define W_CTL(_sc, _val) BCM_PWM_MEM_WRITE(_sc, 0x00, _val) 83 #define R_CTL(_sc) BCM_PWM_MEM_READ(_sc, 0x00) 84 #define W_STA(_sc, _val) BCM_PWM_MEM_WRITE(_sc, 0x04, _val) 85 #define R_STA(_sc) BCM_PWM_MEM_READ(_sc, 0x04) 86 #define W_RNG(_sc, _val) BCM_PWM_MEM_WRITE(_sc, 0x10, _val) 87 #define R_RNG(_sc) BCM_PWM_MEM_READ(_sc, 0x10) 88 #define W_DAT(_sc, _val) BCM_PWM_MEM_WRITE(_sc, 0x14, _val) 89 #define R_DAT(_sc) BCM_PWM_MEM_READ(_sc, 0x14) 90 91 static int 92 bcm_pwm_reconf(struct bcm_pwm_softc *sc) 93 { 94 uint32_t u; 95 96 /* Disable PWM */ 97 W_CTL(sc, 0); 98 99 /* Stop PWM clock */ 100 (void)bcm2835_clkman_set_frequency(sc->clkman, BCM_PWM_CLKSRC, 0); 101 102 if (sc->mode == 0) 103 return (0); 104 105 u = bcm2835_clkman_set_frequency(sc->clkman, BCM_PWM_CLKSRC, sc->freq); 106 if (u == 0) 107 return (EINVAL); 108 sc->freq = u; 109 110 /* Config PWM */ 111 W_RNG(sc, sc->period); 112 if (sc->ratio > sc->period) 113 sc->ratio = sc->period; 114 W_DAT(sc, sc->ratio); 115 116 /* Start PWM */ 117 if (sc->mode == 1) 118 W_CTL(sc, 0x81); 119 else 120 W_CTL(sc, 0x1); 121 122 return (0); 123 } 124 125 static int 126 bcm_pwm_pwm_freq_proc(SYSCTL_HANDLER_ARGS) 127 { 128 struct bcm_pwm_softc *sc; 129 uint32_t r; 130 int error; 131 132 sc = (struct bcm_pwm_softc *)arg1; 133 if (sc->mode == 1) 134 r = sc->freq / sc->period; 135 else 136 r = 0; 137 error = sysctl_handle_int(oidp, &r, sizeof(r), req); 138 return (error); 139 } 140 141 142 static int 143 bcm_pwm_mode_proc(SYSCTL_HANDLER_ARGS) 144 { 145 struct bcm_pwm_softc *sc; 146 uint32_t r; 147 int error; 148 149 sc = (struct bcm_pwm_softc *)arg1; 150 r = sc->mode; 151 error = sysctl_handle_int(oidp, &r, sizeof(r), req); 152 if (error != 0 || req->newptr == NULL) 153 return (error); 154 if (r > 2) 155 return (EINVAL); 156 sc->mode = r; 157 return (bcm_pwm_reconf(sc)); 158 } 159 160 static int 161 bcm_pwm_freq_proc(SYSCTL_HANDLER_ARGS) 162 { 163 struct bcm_pwm_softc *sc; 164 uint32_t r; 165 int error; 166 167 sc = (struct bcm_pwm_softc *)arg1; 168 r = sc->freq; 169 error = sysctl_handle_int(oidp, &r, sizeof(r), req); 170 if (error != 0 || req->newptr == NULL) 171 return (error); 172 if (r > 125000000) 173 return (EINVAL); 174 sc->freq = r; 175 return (bcm_pwm_reconf(sc)); 176 } 177 178 static int 179 bcm_pwm_period_proc(SYSCTL_HANDLER_ARGS) 180 { 181 struct bcm_pwm_softc *sc; 182 int error; 183 184 sc = (struct bcm_pwm_softc *)arg1; 185 error = sysctl_handle_int(oidp, &sc->period, sizeof(sc->period), req); 186 if (error != 0 || req->newptr == NULL) 187 return (error); 188 return (bcm_pwm_reconf(sc)); 189 } 190 191 static int 192 bcm_pwm_ratio_proc(SYSCTL_HANDLER_ARGS) 193 { 194 struct bcm_pwm_softc *sc; 195 uint32_t r; 196 int error; 197 198 sc = (struct bcm_pwm_softc *)arg1; 199 r = sc->ratio; 200 error = sysctl_handle_int(oidp, &r, sizeof(r), req); 201 if (error != 0 || req->newptr == NULL) 202 return (error); 203 if (r > sc->period) // XXX >= ? 204 return (EINVAL); 205 sc->ratio = r; 206 BCM_PWM_MEM_WRITE(sc, 0x14, sc->ratio); 207 return (0); 208 } 209 210 static int 211 bcm_pwm_reg_proc(SYSCTL_HANDLER_ARGS) 212 { 213 struct bcm_pwm_softc *sc; 214 uint32_t reg; 215 int error; 216 217 sc = (struct bcm_pwm_softc *)arg1; 218 reg = BCM_PWM_MEM_READ(sc, arg2 & 0xff); 219 220 error = sysctl_handle_int(oidp, ®, sizeof(reg), req); 221 if (error != 0 || req->newptr == NULL) 222 return (error); 223 224 BCM_PWM_MEM_WRITE(sc, arg2, reg); 225 return (0); 226 } 227 228 static void 229 bcm_pwm_sysctl_init(struct bcm_pwm_softc *sc) 230 { 231 struct sysctl_ctx_list *ctx; 232 struct sysctl_oid *tree_node; 233 struct sysctl_oid_list *tree; 234 235 /* 236 * Add system sysctl tree/handlers. 237 */ 238 ctx = device_get_sysctl_ctx(sc->sc_dev); 239 tree_node = device_get_sysctl_tree(sc->sc_dev); 240 tree = SYSCTL_CHILDREN(tree_node); 241 if (bootverbose) { 242 #define RR(x,y) \ 243 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, y, \ 244 CTLFLAG_RW | CTLTYPE_UINT, sc, 0x##x, \ 245 bcm_pwm_reg_proc, "IU", "Register 0x" #x " " y); 246 247 RR(24, "DAT2") 248 RR(20, "RNG2") 249 RR(18, "FIF1") 250 RR(14, "DAT1") 251 RR(10, "RNG1") 252 RR(08, "DMAC") 253 RR(04, "STA") 254 RR(00, "CTL") 255 #undef RR 256 } 257 258 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "pwm_freq", 259 CTLFLAG_RD | CTLTYPE_UINT, sc, 0, 260 bcm_pwm_pwm_freq_proc, "IU", "PWM frequency (Hz)"); 261 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "period", 262 CTLFLAG_RW | CTLTYPE_UINT, sc, 0, 263 bcm_pwm_period_proc, "IU", "PWM period (#clocks)"); 264 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "ratio", 265 CTLFLAG_RW | CTLTYPE_UINT, sc, 0, 266 bcm_pwm_ratio_proc, "IU", "PWM ratio (0...period)"); 267 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "freq", 268 CTLFLAG_RW | CTLTYPE_UINT, sc, 0, 269 bcm_pwm_freq_proc, "IU", "PWM clock (Hz)"); 270 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "mode", 271 CTLFLAG_RW | CTLTYPE_UINT, sc, 0, 272 bcm_pwm_mode_proc, "IU", "PWM mode (0=off, 1=pwm, 2=dither)"); 273 } 274 275 static int 276 bcm_pwm_probe(device_t dev) 277 { 278 279 if (!ofw_bus_status_okay(dev)) 280 return (ENXIO); 281 282 if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) 283 return (ENXIO); 284 285 device_set_desc(dev, "BCM2708/2835 PWM controller"); 286 287 return (BUS_PROBE_DEFAULT); 288 } 289 290 static int 291 bcm_pwm_attach(device_t dev) 292 { 293 struct bcm_pwm_softc *sc; 294 int rid; 295 296 if (device_get_unit(dev) != 0) { 297 device_printf(dev, "only one PWM controller supported\n"); 298 return (ENXIO); 299 } 300 301 sc = device_get_softc(dev); 302 sc->sc_dev = dev; 303 304 sc->clkman = devclass_get_device(devclass_find("bcm2835_clkman"), 0); 305 if (sc->clkman == NULL) { 306 device_printf(dev, "cannot find Clock Manager\n"); 307 return (ENXIO); 308 } 309 310 rid = 0; 311 sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, 312 RF_ACTIVE); 313 if (!sc->sc_mem_res) { 314 device_printf(dev, "cannot allocate memory window\n"); 315 return (ENXIO); 316 } 317 318 sc->sc_m_bst = rman_get_bustag(sc->sc_mem_res); 319 sc->sc_m_bsh = rman_get_bushandle(sc->sc_mem_res); 320 321 /* Add sysctl nodes. */ 322 bcm_pwm_sysctl_init(sc); 323 324 sc->freq = 125000000; 325 sc->period = 10000; 326 sc->ratio = 2500; 327 328 329 return (bus_generic_attach(dev)); 330 } 331 332 static int 333 bcm_pwm_detach(device_t dev) 334 { 335 struct bcm_pwm_softc *sc; 336 337 bus_generic_detach(dev); 338 339 sc = device_get_softc(dev); 340 sc->mode = 0; 341 (void)bcm_pwm_reconf(sc); 342 if (sc->sc_mem_res) 343 bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); 344 345 return (0); 346 } 347 348 static phandle_t 349 bcm_pwm_get_node(device_t bus, device_t dev) 350 { 351 352 return (ofw_bus_get_node(bus)); 353 } 354 355 356 static device_method_t bcm_pwm_methods[] = { 357 /* Device interface */ 358 DEVMETHOD(device_probe, bcm_pwm_probe), 359 DEVMETHOD(device_attach, bcm_pwm_attach), 360 DEVMETHOD(device_detach, bcm_pwm_detach), 361 DEVMETHOD(ofw_bus_get_node, bcm_pwm_get_node), 362 363 DEVMETHOD_END 364 }; 365 366 static devclass_t bcm_pwm_devclass; 367 368 static driver_t bcm_pwm_driver = { 369 "pwm", 370 bcm_pwm_methods, 371 sizeof(struct bcm_pwm_softc), 372 }; 373 374 DRIVER_MODULE(bcm2835_pwm, simplebus, bcm_pwm_driver, bcm_pwm_devclass, 0, 0); 375 MODULE_DEPEND(bcm2835_pwm, bcm2835_clkman, 1, 1, 1); 376