Blame
|
1 | # NetBSD Port |
||||||
|
2 | |||||||
| 3 | > [!WARNING] |
|||||||
| 4 | > This page is very much a work in progress! |
|||||||
| 5 | ||||||||
| 6 | ||||||||
| 7 | ## Building |
|||||||
| 8 | I've forked a copy of the NetBSD sources. |
|||||||
| 9 | ### Kernel |
|||||||
| 10 | NetBSD contains a port called **hb68k** which is designed for any 680x0 sbc type of board and I used that to create a variant for my SBC, which I've called **SBC3**. |
|||||||
| 11 | ||||||||
| 12 | ``` |
|||||||
| 13 | git clone https://github.com/znac049/netbsd-src.git |
|||||||
| 14 | cd netbsd-src |
|||||||
|
15 | ./build.sh -U -u -j4 -O ~/obj -m hb68k tools |
||||||
|
16 | ./build.sh -U -u -j4 -O ~/obj -m hb68k kernel=SBC3 |
||||||
| 17 | ./build.sh -U -u -j4 -O ~/obj -m hb68k distribution |
|||||||
| 18 | ``` |
|||||||
| 19 | ||||||||
|
20 | - The first step builds the tools (compiler etc) that will be used to build the kernel and userspace. |
||||||
| 21 | - The next step builds the kernel. |
|||||||
|
22 | - The final step takes a Long Time(tm) and builds the userspace, which will need to be added to the root filesystem. |
||||||
|
23 | |||||||
|
24 | ### Root File System |
||||||
| 25 | ``` |
|||||||
|
26 | ./mksbc3img.sh -k ~/obj/sys/arch/hb68k/compile/SBC3/netbsd -s 4096 sbc3.img |
||||||
|
27 | ``` |
||||||
|
28 | |||||||
| 29 | ## Device Driver Templates |
|||||||
| 30 | I asked Claude about templates for coding device drivers. Here's what it said: |
|||||||
| 31 | ||||||||
| 32 | ### Uart |
|||||||
| 33 | The template is myuart.c. It’s a complete single-channel UART driver for hb68k: it attaches through the device tree, can be the system console, and gives you a normal tty with interrupt-driven I/O. |
|||||||
| 34 | ||||||||
| 35 | What you change: the two CHIP-SPECIFIC sections near the top |
|||||||
| 36 | ||||||||
| 37 | - Register map: placeholder offsets and bit definitions for data, status, control, baud divisor and interrupt enable. The real byte offset is reg << reg-shift, so register spacing comes from the device tree rather than the code. |
|||||||
| 38 | - Eight small hardware functions, the only code that touches the chip: |
|||||||
| 39 | - `rx_ready` / `getc` |
|||||||
| 40 | - `tx_ready` / `putc` |
|||||||
| 41 | - `clear_errors` |
|||||||
| 42 | - `set_intr` |
|||||||
| 43 | - `intr_pending` |
|||||||
| 44 | - `program`, which sets speed and frame format from termios flags. The example assumes a 16× divisor UART. |
|||||||
| 45 | - The `compatible` string your device-tree node will use. |
|||||||
| 46 | ||||||||
| 47 | Everything below those sections is generic, and you shouldn’t need to touch it for a first version: console, autoconfiguration, interrupt handling, soft interrupt, and the tty and cdevsw entry points. Each part has a comment explaining its role and the locking. |
|||||||
| 48 | ||||||||
| 49 | #### How data moves: |
|||||||
| 50 | ||||||||
| 51 | - Receive: the hard interrupt copies bytes into a ring buffer. A soft interrupt later passes them to the tty layer, because the hard interrupt mustn’t call into it. |
|||||||
| 52 | - Transmit: the tty layer hands over a chunk of output. The transmit interrupt feeds it to the chip, then switches itself off when the chunk is done. |
|||||||
| 53 | - Console: the console uses polling and works from very early in boot. BREAK on the console drops into DDB. |
|||||||
| 54 | ||||||||
| 55 | Setup steps, all listed at the top of the file: |
|||||||
| 56 | ||||||||
| 57 | 1. Add the device/attach/file lines to files.hb68k. |
|||||||
| 58 | 1. Add a major number to majors.hb68k. 14 is free in your tree. |
|||||||
| 59 | 1. Add a myuart* at fdt? pass 5 line to the kernel config. |
|||||||
| 60 | 1. Add a device-tree node. |
|||||||
| 61 | 1. On the target, make the device node by hand (mknod /dev/ttyM0 c 14 0), since MAKEDEV won’t know about the driver. |
|||||||
| 62 | ||||||||
| 63 | Deliberately left out: |
|||||||
| 64 | ||||||||
| 65 | - modem control (carrier is treated as always present) |
|||||||
| 66 | - hardware flow control |
|||||||
| 67 | - split input/output speeds |
|||||||
| 68 | - sending BREAK |
|||||||
| 69 | ||||||||
| 70 | The ioctl function marks where the last of these goes. Your mc68681_duart.c is a worked example of the same structure extended to a two-channel chip, with an IVR, if you want to compare. |
|||||||
| 71 | ||||||||
| 72 | One constraint carries over from your board: the driver’s lock is a spin mutex at IPL_VM, so the chip’s interrupt level in the device tree must be 5 or lower. |
|||||||
| 73 | ||||||||
|
74 | ``` |
||||||
| 75 | /* $NetBSD$ */ |
|||||||
| 76 | ||||||||
| 77 | /*- |
|||||||
| 78 | * Copyright (c) 2026 The NetBSD Foundation, Inc. |
|||||||
| 79 | * All rights reserved. |
|||||||
| 80 | * |
|||||||
| 81 | * Redistribution and use in source and binary forms, with or without |
|||||||
| 82 | * modification, are permitted provided that the following conditions |
|||||||
| 83 | * are met: |
|||||||
| 84 | * 1. Redistributions of source code must retain the above copyright |
|||||||
| 85 | * notice, this list of conditions and the following disclaimer. |
|||||||
| 86 | * 2. Redistributions in binary form must reproduce the above copyright |
|||||||
| 87 | * notice, this list of conditions and the following disclaimer in the |
|||||||
| 88 | * documentation and/or other materials provided with the distribution. |
|||||||
| 89 | * |
|||||||
| 90 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
|||||||
| 91 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
|||||||
| 92 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|||||||
| 93 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
|||||||
| 94 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|||||||
| 95 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|||||||
| 96 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|||||||
| 97 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|||||||
| 98 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|||||||
| 99 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||||||
| 100 | * POSSIBILITY OF SUCH DAMAGE. |
|||||||
| 101 | */ |
|||||||
| 102 | ||||||||
| 103 | /* |
|||||||
| 104 | * myuart -- template driver for a simple, single-channel UART attached |
|||||||
| 105 | * via Flattened Device Tree (FDT), with console and tty support. |
|||||||
| 106 | * |
|||||||
| 107 | * ====================================================================== |
|||||||
| 108 | * HOW TO USE THIS TEMPLATE |
|||||||
| 109 | * ====================================================================== |
|||||||
| 110 | * |
|||||||
| 111 | * 1. Copy this file to e.g. sys/arch/hb68k/dev/<chip>.c and replace |
|||||||
| 112 | * "myuart" with your driver name throughout (keep it short; it |
|||||||
| 113 | * becomes the device name, e.g. "myuart0"). |
|||||||
| 114 | * |
|||||||
| 115 | * 2. Fill in the section marked "CHIP-SPECIFIC" below. Everything |
|||||||
| 116 | * else is generic NetBSD plumbing that works for most UARTs and |
|||||||
| 117 | * shouldn't need touching for a first version. |
|||||||
| 118 | * |
|||||||
| 119 | * 3. Hook it into the build: |
|||||||
| 120 | * |
|||||||
| 121 | * sys/arch/hb68k/conf/files.hb68k: |
|||||||
| 122 | * device myuart: tty |
|||||||
| 123 | * attach myuart at fdt |
|||||||
| 124 | * file arch/hb68k/dev/myuart.c myuart |
|||||||
| 125 | * |
|||||||
| 126 | * sys/arch/hb68k/conf/majors.hb68k (pick an unused number < 144): |
|||||||
| 127 | * device-major myuart char 14 myuart |
|||||||
| 128 | * |
|||||||
| 129 | * Kernel config (e.g. conf/SBC3): |
|||||||
| 130 | * myuart* at fdt? pass 5 |
|||||||
| 131 | * |
|||||||
| 132 | * 4. Describe the chip in the device tree: |
|||||||
| 133 | * |
|||||||
| 134 | * serial@XXXXXX { |
|||||||
| 135 | * compatible = "vendor,mychip"; (must match compat_data[]) |
|||||||
| 136 | * reg = <0xXXXXXX 0x10>; (base, size of register block) |
|||||||
| 137 | * reg-shift = <1>; (log2 of register spacing) |
|||||||
| 138 | * clock-frequency = <1843200>; (UART input clock, Hz) |
|||||||
| 139 | * interrupts = <3 0>; (IPL, vector; 0 = auto-vector) |
|||||||
| 140 | * }; |
|||||||
| 141 | * |
|||||||
| 142 | * To make it the console, point /chosen/stdout-path at it, e.g. |
|||||||
| 143 | * stdout-path = "serial1:9600n8"; |
|||||||
| 144 | * with an alias "serial1" for the node. The platform code must |
|||||||
| 145 | * provide a static mapping for its registers (as sbc3_machdep.c |
|||||||
| 146 | * does for the I/O window), because the console is set up before |
|||||||
| 147 | * the VM system is running. |
|||||||
| 148 | * |
|||||||
| 149 | * 5. Device nodes: MAKEDEV doesn't know about a new driver, so create |
|||||||
| 150 | * them by hand on the target (char major from step 3, minor = unit): |
|||||||
| 151 | * mknod /dev/ttyM0 c 14 0 |
|||||||
| 152 | * and add a line to /etc/ttys if you want a login prompt on it. |
|||||||
| 153 | * |
|||||||
| 154 | * ====================================================================== |
|||||||
| 155 | * HOW THE PIECES FIT TOGETHER |
|||||||
| 156 | * ====================================================================== |
|||||||
| 157 | * |
|||||||
| 158 | * Autoconfiguration myuart_match() / myuart_attach() are called when |
|||||||
| 159 | * the FDT bus finds a node whose "compatible" |
|||||||
| 160 | * matches compat_data[]. attach maps the registers, |
|||||||
| 161 | * allocates a tty and hooks up the interrupt. |
|||||||
| 162 | * |
|||||||
| 163 | * Console myuart_cnattach() runs very early (from |
|||||||
| 164 | * consinit(), before autoconfiguration) if the |
|||||||
| 165 | * node is /chosen/stdout-path. The console uses |
|||||||
| 166 | * polled I/O only: myuart_cngetc()/myuart_cnputc(). |
|||||||
| 167 | * |
|||||||
| 168 | * Hard interrupt myuart_intr() runs at the device's hardware IPL. |
|||||||
| 169 | * It must be quick: it moves received bytes into a |
|||||||
| 170 | * ring buffer, feeds the transmitter, and schedules |
|||||||
| 171 | * the soft interrupt. It must not call into the |
|||||||
| 172 | * tty layer. |
|||||||
| 173 | * |
|||||||
| 174 | * Soft interrupt myuart_soft() runs later at IPL_SOFTSERIAL. It |
|||||||
| 175 | * hands received bytes to the line discipline and, |
|||||||
| 176 | * when a transmit burst finishes, asks the tty |
|||||||
| 177 | * layer for more output. |
|||||||
| 178 | * |
|||||||
| 179 | * tty entry points open/close/read/write/ioctl/poll come from the |
|||||||
| 180 | * cdevsw table; myuartstart() and myuartparam() |
|||||||
| 181 | * are called by the tty layer to start output and |
|||||||
| 182 | * to change speed/framing. |
|||||||
| 183 | * |
|||||||
| 184 | * Locking sc_lock is a spin mutex at the device's IPL. It |
|||||||
| 185 | * protects the ring buffer, the transmit pointers, |
|||||||
| 186 | * the interrupt-enable shadow and register access |
|||||||
| 187 | * shared between the interrupt handler and the |
|||||||
| 188 | * rest of the driver. |
|||||||
| 189 | * |
|||||||
| 190 | * This template deliberately leaves out modem control (carrier is |
|||||||
| 191 | * assumed always present), hardware flow control, split speeds, and |
|||||||
| 192 | * power management. Add them later if your hardware needs them. |
|||||||
| 193 | */ |
|||||||
| 194 | ||||||||
| 195 | #include <sys/cdefs.h> |
|||||||
| 196 | __KERNEL_RCSID(0, "$NetBSD$"); |
|||||||
| 197 | ||||||||
| 198 | #include <sys/param.h> |
|||||||
| 199 | #include <sys/bus.h> |
|||||||
| 200 | #include <sys/conf.h> |
|||||||
| 201 | #include <sys/device.h> |
|||||||
| 202 | #include <sys/fcntl.h> |
|||||||
| 203 | #include <sys/intr.h> |
|||||||
| 204 | #include <sys/kauth.h> |
|||||||
| 205 | #include <sys/poll.h> |
|||||||
| 206 | #include <sys/proc.h> |
|||||||
| 207 | #include <sys/syslog.h> |
|||||||
| 208 | #include <sys/systm.h> |
|||||||
| 209 | #include <sys/tty.h> |
|||||||
| 210 | ||||||||
| 211 | #include <dev/cons.h> |
|||||||
| 212 | ||||||||
| 213 | #include <dev/fdt/fdtvar.h> |
|||||||
| 214 | #include <dev/fdt/fdt_console.h> |
|||||||
| 215 | ||||||||
| 216 | /* |
|||||||
| 217 | * ###################################################################### |
|||||||
| 218 | * CHIP-SPECIFIC: registers |
|||||||
| 219 | * ###################################################################### |
|||||||
| 220 | * |
|||||||
| 221 | * Replace these with your chip's register map. Offsets are register |
|||||||
| 222 | * *numbers*; the real byte offset is (reg << reg-shift), so the same |
|||||||
| 223 | * code works whether the chip sits on every byte, every other byte, |
|||||||
| 224 | * or every fourth byte. |
|||||||
| 225 | * |
|||||||
| 226 | * The placeholder layout below is a typical simple UART: |
|||||||
| 227 | * DATA read: receive holding register, write: transmit holding |
|||||||
| 228 | * STATUS read-only status bits |
|||||||
| 229 | * CTRL frame format and enables |
|||||||
| 230 | * BAUD baud rate divisor (16 bits, split over two registers) |
|||||||
| 231 | * IER interrupt enable bits |
|||||||
| 232 | */ |
|||||||
| 233 | #define MYUART_REG_DATA 0 |
|||||||
| 234 | #define MYUART_REG_STATUS 1 |
|||||||
| 235 | #define MYUART_REG_CTRL 2 |
|||||||
| 236 | #define MYUART_REG_BAUDLO 3 |
|||||||
| 237 | #define MYUART_REG_BAUDHI 4 |
|||||||
| 238 | #define MYUART_REG_IER 5 |
|||||||
| 239 | ||||||||
| 240 | /* STATUS bits */ |
|||||||
| 241 | #define MYUART_SR_RXRDY 0x01 /* a received byte is waiting */ |
|||||||
| 242 | #define MYUART_SR_TXRDY 0x02 /* transmit register is empty */ |
|||||||
| 243 | #define MYUART_SR_OVERRUN 0x10 /* receiver overrun */ |
|||||||
| 244 | #define MYUART_SR_PARITY 0x20 /* parity error on this byte */ |
|||||||
| 245 | #define MYUART_SR_FRAMING 0x40 /* framing error on this byte */ |
|||||||
| 246 | #define MYUART_SR_BREAK 0x80 /* break received */ |
|||||||
| 247 | ||||||||
| 248 | /* CTRL bits */ |
|||||||
| 249 | #define MYUART_CTRL_ENABLE 0x01 /* enable receiver + transmitter */ |
|||||||
| 250 | #define MYUART_CTRL_8BIT 0x02 /* 8 data bits (else 7) */ |
|||||||
| 251 | #define MYUART_CTRL_PARENB 0x04 /* parity enable */ |
|||||||
| 252 | #define MYUART_CTRL_PARODD 0x08 /* odd parity (else even) */ |
|||||||
| 253 | #define MYUART_CTRL_2STOP 0x10 /* 2 stop bits (else 1) */ |
|||||||
| 254 | ||||||||
| 255 | /* IER bits */ |
|||||||
| 256 | #define MYUART_IER_RX 0x01 /* interrupt when RXRDY */ |
|||||||
| 257 | #define MYUART_IER_TX 0x02 /* interrupt when TXRDY */ |
|||||||
| 258 | ||||||||
| 259 | #define MYUART_DEFAULT_FREQ 1843200 /* if the DT has no clock-frequency */ |
|||||||
| 260 | #define MYUART_DEFAULT_SPEED 9600 /* if stdout-path has no speed */ |
|||||||
| 261 | ||||||||
| 262 | /* |
|||||||
| 263 | * Everything needed to talk to the registers. Shared by the console |
|||||||
| 264 | * (which runs before the softc exists) and the attached device. |
|||||||
| 265 | */ |
|||||||
| 266 | struct myuart_regs { |
|||||||
| 267 | bus_space_tag_t ur_bst; |
|||||||
| 268 | bus_space_handle_t ur_bsh; |
|||||||
| 269 | u_int ur_shift; /* reg-shift */ |
|||||||
| 270 | u_int ur_freq; /* input clock, Hz */ |
|||||||
| 271 | }; |
|||||||
| 272 | ||||||||
| 273 | static inline uint8_t |
|||||||
| 274 | myuart_read(const struct myuart_regs *ur, u_int reg) |
|||||||
| 275 | { |
|||||||
| 276 | return bus_space_read_1(ur->ur_bst, ur->ur_bsh, reg << ur->ur_shift); |
|||||||
| 277 | } |
|||||||
| 278 | ||||||||
| 279 | static inline void |
|||||||
| 280 | myuart_write(const struct myuart_regs *ur, u_int reg, uint8_t val) |
|||||||
| 281 | { |
|||||||
| 282 | bus_space_write_1(ur->ur_bst, ur->ur_bsh, reg << ur->ur_shift, val); |
|||||||
| 283 | } |
|||||||
| 284 | ||||||||
| 285 | /* |
|||||||
| 286 | * ###################################################################### |
|||||||
| 287 | * CHIP-SPECIFIC: low-level operations |
|||||||
| 288 | * ###################################################################### |
|||||||
| 289 | * |
|||||||
| 290 | * These eight small functions are the whole hardware interface. The |
|||||||
| 291 | * rest of the driver only ever touches the chip through them. |
|||||||
| 292 | */ |
|||||||
| 293 | ||||||||
| 294 | /* Is a received byte waiting? Returns the status bits for that byte. */ |
|||||||
| 295 | static inline bool |
|||||||
| 296 | myuart_hw_rx_ready(const struct myuart_regs *ur, uint8_t *srp) |
|||||||
| 297 | { |
|||||||
| 298 | const uint8_t sr = myuart_read(ur, MYUART_REG_STATUS); |
|||||||
| 299 | ||||||||
| 300 | *srp = sr; |
|||||||
| 301 | return (sr & MYUART_SR_RXRDY) != 0; |
|||||||
| 302 | } |
|||||||
| 303 | ||||||||
| 304 | /* Read the received byte. Call only after rx_ready returned true. */ |
|||||||
| 305 | static inline uint8_t |
|||||||
| 306 | myuart_hw_getc(const struct myuart_regs *ur) |
|||||||
| 307 | { |
|||||||
| 308 | return myuart_read(ur, MYUART_REG_DATA); |
|||||||
| 309 | } |
|||||||
| 310 | ||||||||
| 311 | /* Can another byte be written to the transmitter? */ |
|||||||
| 312 | static inline bool |
|||||||
| 313 | myuart_hw_tx_ready(const struct myuart_regs *ur) |
|||||||
| 314 | { |
|||||||
| 315 | return (myuart_read(ur, MYUART_REG_STATUS) & MYUART_SR_TXRDY) != 0; |
|||||||
| 316 | } |
|||||||
| 317 | ||||||||
| 318 | /* Write one byte to the transmitter. */ |
|||||||
| 319 | static inline void |
|||||||
| 320 | myuart_hw_putc(const struct myuart_regs *ur, uint8_t c) |
|||||||
| 321 | { |
|||||||
| 322 | myuart_write(ur, MYUART_REG_DATA, c); |
|||||||
| 323 | } |
|||||||
| 324 | ||||||||
| 325 | /* |
|||||||
| 326 | * Clear any error / break condition after it has been read. Many |
|||||||
| 327 | * chips clear errors automatically when the data register is read; |
|||||||
| 328 | * if yours does, leave this empty. |
|||||||
| 329 | */ |
|||||||
| 330 | static inline void |
|||||||
| 331 | myuart_hw_clear_errors(const struct myuart_regs *ur) |
|||||||
| 332 | { |
|||||||
| 333 | /* e.g. myuart_write(ur, MYUART_REG_CTRL, ... | RESET_ERRORS); */ |
|||||||
| 334 | } |
|||||||
| 335 | ||||||||
| 336 | /* |
|||||||
| 337 | * Set the interrupt enables. `rx' and `tx' say which sources may |
|||||||
| 338 | * interrupt. The transmit interrupt must only be enabled while there |
|||||||
| 339 | * is something to send: most UARTs assert "transmitter empty" |
|||||||
| 340 | * continuously, so leaving it enabled would interrupt forever. |
|||||||
| 341 | */ |
|||||||
| 342 | static inline void |
|||||||
| 343 | myuart_hw_set_intr(const struct myuart_regs *ur, bool rx, bool tx) |
|||||||
| 344 | { |
|||||||
| 345 | myuart_write(ur, MYUART_REG_IER, |
|||||||
| 346 | (rx ? MYUART_IER_RX : 0) | (tx ? MYUART_IER_TX : 0)); |
|||||||
| 347 | } |
|||||||
| 348 | ||||||||
| 349 | /* |
|||||||
| 350 | * Did this chip cause the current interrupt? Used to share an |
|||||||
| 351 | * interrupt level with other devices. If your chip has an "interrupt |
|||||||
| 352 | * pending" bit, test that instead. |
|||||||
| 353 | */ |
|||||||
| 354 | static inline bool |
|||||||
| 355 | myuart_hw_intr_pending(const struct myuart_regs *ur, bool tx_enabled) |
|||||||
| 356 | { |
|||||||
| 357 | const uint8_t sr = myuart_read(ur, MYUART_REG_STATUS); |
|||||||
| 358 | ||||||||
| 359 | return (sr & (MYUART_SR_RXRDY | MYUART_SR_BREAK)) != 0 || |
|||||||
| 360 | (tx_enabled && (sr & MYUART_SR_TXRDY) != 0); |
|||||||
| 361 | } |
|||||||
| 362 | ||||||||
| 363 | /* |
|||||||
| 364 | * Program speed and frame format, and enable the receiver and |
|||||||
| 365 | * transmitter. Returns 0, or EINVAL if the speed or format can't be |
|||||||
| 366 | * done. Called with interrupts for this device blocked. |
|||||||
| 367 | * |
|||||||
| 368 | * `cflag' uses the termios(4) bits: CSIZE (CS5..CS8), CSTOPB, PARENB, |
|||||||
| 369 | * PARODD. |
|||||||
| 370 | */ |
|||||||
| 371 | static int |
|||||||
| 372 | myuart_hw_program(const struct myuart_regs *ur, int speed, tcflag_t cflag) |
|||||||
| 373 | { |
|||||||
| 374 | uint8_t ctrl = MYUART_CTRL_ENABLE; |
|||||||
| 375 | u_int div; |
|||||||
| 376 | ||||||||
| 377 | if (speed <= 0) { |
|||||||
| 378 | return EINVAL; |
|||||||
| 379 | } |
|||||||
| 380 | ||||||||
| 381 | /* |
|||||||
| 382 | * Example for a 16x-oversampling divisor UART: |
|||||||
| 383 | * divisor = clock / (16 * speed), rounded to nearest. |
|||||||
| 384 | * Reject rates more than 3% off. |
|||||||
| 385 | */ |
|||||||
| 386 | div = (ur->ur_freq + 8 * speed) / (16 * speed); |
|||||||
| 387 | if (div == 0 || div > 0xffff) { |
|||||||
| 388 | return EINVAL; |
|||||||
| 389 | } |
|||||||
| 390 | const u_int actual = ur->ur_freq / (16 * div); |
|||||||
| 391 | const u_int err = actual > (u_int)speed ? |
|||||||
| 392 | actual - speed : speed - actual; |
|||||||
| 393 | if (err * 100 > (u_int)speed * 3) { |
|||||||
| 394 | return EINVAL; |
|||||||
| 395 | } |
|||||||
| 396 | ||||||||
| 397 | switch (ISSET(cflag, CSIZE)) { |
|||||||
| 398 | case CS8: |
|||||||
| 399 | ctrl |= MYUART_CTRL_8BIT; |
|||||||
| 400 | break; |
|||||||
| 401 | case CS7: |
|||||||
| 402 | break; |
|||||||
| 403 | default: |
|||||||
| 404 | return EINVAL; /* this example chip only does 7/8 */ |
|||||||
| 405 | } |
|||||||
| 406 | if (ISSET(cflag, PARENB)) { |
|||||||
| 407 | ctrl |= MYUART_CTRL_PARENB; |
|||||||
| 408 | if (ISSET(cflag, PARODD)) { |
|||||||
| 409 | ctrl |= MYUART_CTRL_PARODD; |
|||||||
| 410 | } |
|||||||
| 411 | } |
|||||||
| 412 | if (ISSET(cflag, CSTOPB)) { |
|||||||
| 413 | ctrl |= MYUART_CTRL_2STOP; |
|||||||
| 414 | } |
|||||||
| 415 | ||||||||
| 416 | myuart_write(ur, MYUART_REG_CTRL, 0); /* quiesce */ |
|||||||
| 417 | myuart_write(ur, MYUART_REG_BAUDLO, div & 0xff); |
|||||||
| 418 | myuart_write(ur, MYUART_REG_BAUDHI, (div >> 8) & 0xff); |
|||||||
| 419 | myuart_write(ur, MYUART_REG_CTRL, ctrl); |
|||||||
| 420 | ||||||||
| 421 | return 0; |
|||||||
| 422 | } |
|||||||
| 423 | ||||||||
| 424 | /* |
|||||||
| 425 | * ###################################################################### |
|||||||
| 426 | * Device tree matching |
|||||||
| 427 | * ###################################################################### |
|||||||
| 428 | * |
|||||||
| 429 | * List every "compatible" string this driver handles. |
|||||||
| 430 | */ |
|||||||
| 431 | static const struct device_compatible_entry compat_data[] = { |
|||||||
| 432 | { .compat = "vendor,mychip" }, |
|||||||
| 433 | DEVICE_COMPAT_EOL |
|||||||
| 434 | }; |
|||||||
| 435 | ||||||||
| 436 | /* |
|||||||
| 437 | * ###################################################################### |
|||||||
| 438 | * Generic driver below this line |
|||||||
| 439 | * ###################################################################### |
|||||||
| 440 | */ |
|||||||
| 441 | ||||||||
| 442 | #define MYUART_RBUF_SIZE 256 /* receive ring size, power of 2 */ |
|||||||
| 443 | ||||||||
| 444 | struct myuart_softc { |
|||||||
| 445 | device_t sc_dev; |
|||||||
| 446 | struct myuart_regs sc_regs; |
|||||||
| 447 | kmutex_t sc_lock; /* see "Locking" above */ |
|||||||
| 448 | void *sc_ih; /* hardware interrupt */ |
|||||||
| 449 | void *sc_si; /* soft interrupt */ |
|||||||
| 450 | struct tty *sc_tty; |
|||||||
| 451 | ||||||||
| 452 | bool sc_console; /* we are the console */ |
|||||||
| 453 | bool sc_rx_on; /* shadow of RX intr enable */ |
|||||||
| 454 | bool sc_tx_on; /* shadow of TX intr enable */ |
|||||||
| 455 | ||||||||
| 456 | /* Receive ring: (status << 8) | data, filled by myuart_intr(). */ |
|||||||
| 457 | uint16_t sc_rbuf[MYUART_RBUF_SIZE]; |
|||||||
| 458 | u_int sc_rbput; |
|||||||
| 459 | u_int sc_rbget; |
|||||||
| 460 | u_int sc_rbovf; /* bytes dropped, ring full */ |
|||||||
| 461 | ||||||||
| 462 | /* Transmit burst, set up by myuartstart(). */ |
|||||||
| 463 | u_char *sc_tba; /* next byte to send */ |
|||||||
| 464 | int sc_tbc; /* bytes left */ |
|||||||
| 465 | bool sc_tx_done; /* burst finished */ |
|||||||
| 466 | }; |
|||||||
| 467 | ||||||||
| 468 | static int myuart_match(device_t, cfdata_t, void *); |
|||||||
| 469 | static void myuart_attach(device_t, device_t, void *); |
|||||||
| 470 | ||||||||
| 471 | CFATTACH_DECL_NEW(myuart, sizeof(struct myuart_softc), |
|||||||
| 472 | myuart_match, myuart_attach, NULL, NULL); |
|||||||
| 473 | ||||||||
| 474 | extern struct cfdriver myuart_cd; |
|||||||
| 475 | ||||||||
| 476 | static dev_type_open(myuartopen); |
|||||||
| 477 | static dev_type_close(myuartclose); |
|||||||
| 478 | static dev_type_read(myuartread); |
|||||||
| 479 | static dev_type_write(myuartwrite); |
|||||||
| 480 | static dev_type_ioctl(myuartioctl); |
|||||||
| 481 | static dev_type_stop(myuartstop); |
|||||||
| 482 | static dev_type_tty(myuarttty); |
|||||||
| 483 | static dev_type_poll(myuartpoll); |
|||||||
| 484 | ||||||||
| 485 | /* The character device switch; its major number comes from majors.*. */ |
|||||||
| 486 | const struct cdevsw myuart_cdevsw = { |
|||||||
| 487 | .d_open = myuartopen, |
|||||||
| 488 | .d_close = myuartclose, |
|||||||
| 489 | .d_read = myuartread, |
|||||||
| 490 | .d_write = myuartwrite, |
|||||||
| 491 | .d_ioctl = myuartioctl, |
|||||||
| 492 | .d_stop = myuartstop, |
|||||||
| 493 | .d_tty = myuarttty, |
|||||||
| 494 | .d_poll = myuartpoll, |
|||||||
| 495 | .d_mmap = nommap, |
|||||||
| 496 | .d_kqfilter = ttykqfilter, |
|||||||
| 497 | .d_discard = nodiscard, |
|||||||
| 498 | .d_flag = D_TTY |
|||||||
| 499 | }; |
|||||||
| 500 | ||||||||
| 501 | static void myuartstart(struct tty *); |
|||||||
| 502 | static int myuartparam(struct tty *, struct termios *); |
|||||||
| 503 | static int myuart_intr(void *); |
|||||||
| 504 | static void myuart_soft(void *); |
|||||||
| 505 | ||||||||
| 506 | /* Call with sc_lock held: update the hardware interrupt enables. */ |
|||||||
| 507 | static void |
|||||||
| 508 | myuart_update_intr(struct myuart_softc *sc) |
|||||||
| 509 | { |
|||||||
| 510 | KASSERT(mutex_owned(&sc->sc_lock)); |
|||||||
| 511 | myuart_hw_set_intr(&sc->sc_regs, sc->sc_rx_on, sc->sc_tx_on); |
|||||||
| 512 | } |
|||||||
| 513 | ||||||||
| 514 | /* |
|||||||
| 515 | * ---------------------------------------------------------------------- |
|||||||
| 516 | * Console |
|||||||
| 517 | * ---------------------------------------------------------------------- |
|||||||
| 518 | * |
|||||||
| 519 | * The console is set up long before autoconfiguration, so it can't use |
|||||||
| 520 | * the softc. Its state lives in this static structure instead. |
|||||||
| 521 | */ |
|||||||
| 522 | static struct { |
|||||||
| 523 | struct myuart_regs regs; |
|||||||
| 524 | bus_addr_t addr; /* to recognise ourselves in attach */ |
|||||||
| 525 | int speed; |
|||||||
| 526 | tcflag_t cflag; |
|||||||
| 527 | bool active; |
|||||||
| 528 | } myuart_cons; |
|||||||
| 529 | ||||||||
| 530 | static cnm_state_t myuart_cnm_state; /* console "magic" (break -> DDB) */ |
|||||||
| 531 | ||||||||
| 532 | static int |
|||||||
| 533 | myuart_cngetc(dev_t dev) |
|||||||
| 534 | { |
|||||||
| 535 | uint8_t sr; |
|||||||
| 536 | int s, c; |
|||||||
| 537 | ||||||||
| 538 | s = splhigh(); |
|||||||
| 539 | while (!myuart_hw_rx_ready(&myuart_cons.regs, &sr)) { |
|||||||
| 540 | continue; |
|||||||
| 541 | } |
|||||||
| 542 | c = myuart_hw_getc(&myuart_cons.regs); |
|||||||
| 543 | splx(s); |
|||||||
| 544 | ||||||||
| 545 | return c; |
|||||||
| 546 | } |
|||||||
| 547 | ||||||||
| 548 | static void |
|||||||
| 549 | myuart_cnputc(dev_t dev, int c) |
|||||||
| 550 | { |
|||||||
| 551 | int s; |
|||||||
| 552 | ||||||||
| 553 | s = splhigh(); |
|||||||
| 554 | while (!myuart_hw_tx_ready(&myuart_cons.regs)) { |
|||||||
| 555 | continue; |
|||||||
| 556 | } |
|||||||
| 557 | myuart_hw_putc(&myuart_cons.regs, c); |
|||||||
| 558 | splx(s); |
|||||||
| 559 | } |
|||||||
| 560 | ||||||||
| 561 | static void |
|||||||
| 562 | myuart_cnpollc(dev_t dev, int on) |
|||||||
| 563 | { |
|||||||
| 564 | /* Nothing to do: the console routines always poll. */ |
|||||||
| 565 | } |
|||||||
| 566 | ||||||||
| 567 | static struct consdev myuart_consdev = { |
|||||||
| 568 | .cn_getc = myuart_cngetc, |
|||||||
| 569 | .cn_putc = myuart_cnputc, |
|||||||
| 570 | .cn_pollc = myuart_cnpollc, |
|||||||
| 571 | .cn_dev = NODEV, /* filled in by myuart_attach() */ |
|||||||
| 572 | .cn_pri = CN_NORMAL, |
|||||||
| 573 | }; |
|||||||
| 574 | ||||||||
| 575 | static int |
|||||||
| 576 | myuart_console_match(int phandle) |
|||||||
| 577 | { |
|||||||
| 578 | return of_compatible_match(phandle, compat_data); |
|||||||
| 579 | } |
|||||||
| 580 | ||||||||
| 581 | /* |
|||||||
| 582 | * Called from consinit() when /chosen/stdout-path points at one of our |
|||||||
| 583 | * nodes. `uart_freq' is the node's clock-frequency (or the platform's |
|||||||
| 584 | * fallback). |
|||||||
| 585 | */ |
|||||||
| 586 | static void |
|||||||
| 587 | myuart_cnattach(struct fdt_attach_args *faa, u_int uart_freq) |
|||||||
| 588 | { |
|||||||
| 589 | const int phandle = faa->faa_phandle; |
|||||||
| 590 | struct myuart_regs * const ur = &myuart_cons.regs; |
|||||||
| 591 | bus_addr_t addr; |
|||||||
| 592 | bus_size_t size; |
|||||||
| 593 | ||||||||
| 594 | if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { |
|||||||
| 595 | return; |
|||||||
| 596 | } |
|||||||
| 597 | ||||||||
| 598 | ur->ur_bst = faa->faa_bst; |
|||||||
| 599 | ur->ur_shift = fdtbus_get_reg_shift(phandle, 0); |
|||||||
| 600 | ur->ur_freq = uart_freq ? uart_freq : MYUART_DEFAULT_FREQ; |
|||||||
| 601 | ||||||||
| 602 | /* This must hit a static mapping: UVM isn't running yet. */ |
|||||||
| 603 | if (bus_space_map(ur->ur_bst, addr, size, 0, &ur->ur_bsh) != 0) { |
|||||||
| 604 | return; |
|||||||
| 605 | } |
|||||||
| 606 | ||||||||
| 607 | /* Speed and format from stdout-path, e.g. "serial0:9600n8". */ |
|||||||
| 608 | myuart_cons.speed = fdtbus_get_stdout_speed(); |
|||||||
| 609 | if (myuart_cons.speed < 0) { |
|||||||
| 610 | myuart_cons.speed = MYUART_DEFAULT_SPEED; |
|||||||
| 611 | } |
|||||||
| 612 | myuart_cons.cflag = fdtbus_get_stdout_flags(); |
|||||||
| 613 | ||||||||
| 614 | myuart_hw_set_intr(ur, false, false); |
|||||||
| 615 | if (myuart_hw_program(ur, myuart_cons.speed, |
|||||||
| 616 | myuart_cons.cflag) != 0) { |
|||||||
| 617 | /* Fall back to something sane rather than staying silent. */ |
|||||||
| 618 | myuart_cons.speed = MYUART_DEFAULT_SPEED; |
|||||||
| 619 | myuart_cons.cflag = CS8 | CREAD | CLOCAL; |
|||||||
| 620 | if (myuart_hw_program(ur, myuart_cons.speed, |
|||||||
| 621 | myuart_cons.cflag) != 0) { |
|||||||
| 622 | return; |
|||||||
| 623 | } |
|||||||
| 624 | } |
|||||||
| 625 | ||||||||
| 626 | myuart_cons.addr = addr; |
|||||||
| 627 | myuart_cons.active = true; |
|||||||
| 628 | cn_tab = &myuart_consdev; |
|||||||
| 629 | } |
|||||||
| 630 | ||||||||
| 631 | static const struct fdt_console myuart_fdt_console = { |
|||||||
| 632 | .match = myuart_console_match, |
|||||||
| 633 | .consinit = myuart_cnattach, |
|||||||
| 634 | }; |
|||||||
| 635 | ||||||||
| 636 | FDT_CONSOLE(myuart, &myuart_fdt_console); |
|||||||
| 637 | ||||||||
| 638 | /* |
|||||||
| 639 | * ---------------------------------------------------------------------- |
|||||||
| 640 | * Autoconfiguration |
|||||||
| 641 | * ---------------------------------------------------------------------- |
|||||||
| 642 | */ |
|||||||
| 643 | ||||||||
| 644 | static int |
|||||||
| 645 | myuart_match(device_t parent, cfdata_t cf, void *aux) |
|||||||
| 646 | { |
|||||||
| 647 | struct fdt_attach_args * const faa = aux; |
|||||||
| 648 | ||||||||
| 649 | return of_compatible_match(faa->faa_phandle, compat_data); |
|||||||
| 650 | } |
|||||||
| 651 | ||||||||
| 652 | static void |
|||||||
| 653 | myuart_attach(device_t parent, device_t self, void *aux) |
|||||||
| 654 | { |
|||||||
| 655 | struct myuart_softc * const sc = device_private(self); |
|||||||
| 656 | struct fdt_attach_args * const faa = aux; |
|||||||
| 657 | const int phandle = faa->faa_phandle; |
|||||||
| 658 | struct myuart_regs * const ur = &sc->sc_regs; |
|||||||
| 659 | char intrstr[128]; |
|||||||
| 660 | bus_addr_t addr; |
|||||||
| 661 | bus_size_t size; |
|||||||
| 662 | struct tty *tp; |
|||||||
| 663 | ||||||||
| 664 | sc->sc_dev = self; |
|||||||
| 665 | ||||||||
| 666 | /* --- Registers ------------------------------------------------ */ |
|||||||
| 667 | if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { |
|||||||
| 668 | aprint_error(": couldn't get registers\n"); |
|||||||
| 669 | return; |
|||||||
| 670 | } |
|||||||
| 671 | ur->ur_bst = faa->faa_bst; |
|||||||
| 672 | ur->ur_shift = fdtbus_get_reg_shift(phandle, 0); |
|||||||
| 673 | if (of_getprop_uint32(phandle, "clock-frequency", &ur->ur_freq) != 0) { |
|||||||
| 674 | ur->ur_freq = MYUART_DEFAULT_FREQ; |
|||||||
| 675 | } |
|||||||
| 676 | if (bus_space_map(ur->ur_bst, addr, size, 0, &ur->ur_bsh) != 0) { |
|||||||
| 677 | aprint_error(": couldn't map registers\n"); |
|||||||
| 678 | return; |
|||||||
| 679 | } |
|||||||
| 680 | ||||||||
| 681 | sc->sc_console = myuart_cons.active && myuart_cons.addr == addr; |
|||||||
| 682 | ||||||||
| 683 | /* |
|||||||
| 684 | * The lock is a spin mutex at IPL_VM, which on hb68k blocks |
|||||||
| 685 | * hardware levels 1-5. So the device's interrupt level (first |
|||||||
| 686 | * cell of "interrupts") must be 5 or lower. |
|||||||
| 687 | */ |
|||||||
| 688 | mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_VM); |
|||||||
| 689 | ||||||||
| 690 | aprint_naive("\n"); |
|||||||
| 691 | aprint_normal(": UART, %u Hz clock%s\n", ur->ur_freq, |
|||||||
| 692 | sc->sc_console ? ", console" : ""); |
|||||||
| 693 | ||||||||
| 694 | /* --- Quiet the chip (but leave the console's settings alone) - */ |
|||||||
| 695 | mutex_spin_enter(&sc->sc_lock); |
|||||||
| 696 | sc->sc_rx_on = sc->sc_tx_on = false; |
|||||||
| 697 | myuart_update_intr(sc); |
|||||||
| 698 | mutex_spin_exit(&sc->sc_lock); |
|||||||
| 699 | ||||||||
| 700 | /* --- tty ------------------------------------------------------ */ |
|||||||
| 701 | tp = tty_alloc(); |
|||||||
| 702 | tp->t_oproc = myuartstart; /* tty layer: "start output" */ |
|||||||
| 703 | tp->t_param = myuartparam; /* tty layer: "change settings" */ |
|||||||
| 704 | tty_attach(tp); |
|||||||
| 705 | sc->sc_tty = tp; |
|||||||
| 706 | ||||||||
| 707 | if (sc->sc_console) { |
|||||||
| 708 | /* Tell the console layer which /dev entry is the console. */ |
|||||||
| 709 | myuart_consdev.cn_dev = makedev( |
|||||||
| 710 | cdevsw_lookup_major(&myuart_cdevsw), device_unit(self)); |
|||||||
| 711 | aprint_normal_dev(self, "console\n"); |
|||||||
| 712 | } |
|||||||
| 713 | ||||||||
| 714 | /* --- Interrupts ----------------------------------------------- */ |
|||||||
| 715 | sc->sc_si = softint_establish(SOFTINT_SERIAL, myuart_soft, sc); |
|||||||
| 716 | if (sc->sc_si == NULL) { |
|||||||
| 717 | aprint_error_dev(self, "couldn't establish soft interrupt\n"); |
|||||||
| 718 | return; |
|||||||
| 719 | } |
|||||||
| 720 | ||||||||
| 721 | if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) { |
|||||||
| 722 | aprint_error_dev(self, "couldn't decode interrupt\n"); |
|||||||
| 723 | return; |
|||||||
| 724 | } |
|||||||
| 725 | sc->sc_ih = fdtbus_intr_establish_xname(phandle, 0, IPL_VM, |
|||||||
| 726 | FDT_INTR_MPSAFE, myuart_intr, sc, device_xname(self)); |
|||||||
| 727 | if (sc->sc_ih == NULL) { |
|||||||
| 728 | aprint_error_dev(self, |
|||||||
| 729 | "couldn't establish interrupt at %s\n", intrstr); |
|||||||
| 730 | return; |
|||||||
| 731 | } |
|||||||
| 732 | aprint_normal_dev(self, "interrupting at %s\n", intrstr); |
|||||||
| 733 | ||||||||
| 734 | /* |
|||||||
| 735 | * The console listens all the time, so that a BREAK (or the |
|||||||
| 736 | * configured magic sequence) can drop into DDB even when no |
|||||||
| 737 | * process has the tty open. |
|||||||
| 738 | */ |
|||||||
| 739 | if (sc->sc_console) { |
|||||||
| 740 | cn_init_magic(&myuart_cnm_state); |
|||||||
| 741 | cn_set_magic("\047\001"); /* BREAK */ |
|||||||
| 742 | ||||||||
| 743 | mutex_spin_enter(&sc->sc_lock); |
|||||||
| 744 | sc->sc_rx_on = true; |
|||||||
| 745 | myuart_update_intr(sc); |
|||||||
| 746 | mutex_spin_exit(&sc->sc_lock); |
|||||||
| 747 | } |
|||||||
| 748 | } |
|||||||
| 749 | ||||||||
| 750 | /* |
|||||||
| 751 | * ---------------------------------------------------------------------- |
|||||||
| 752 | * Hardware interrupt |
|||||||
| 753 | * ---------------------------------------------------------------------- |
|||||||
| 754 | * |
|||||||
| 755 | * Runs at the device's IPL with sc_lock held. Keep it short. |
|||||||
| 756 | */ |
|||||||
| 757 | ||||||||
| 758 | static void |
|||||||
| 759 | myuart_rxintr(struct myuart_softc *sc) |
|||||||
| 760 | { |
|||||||
| 761 | struct myuart_regs * const ur = &sc->sc_regs; |
|||||||
| 762 | const bool open = ISSET(sc->sc_tty->t_state, TS_ISOPEN); |
|||||||
| 763 | uint8_t sr, c; |
|||||||
| 764 | ||||||||
| 765 | while (myuart_hw_rx_ready(ur, &sr)) { |
|||||||
| 766 | c = myuart_hw_getc(ur); |
|||||||
| 767 | ||||||||
| 768 | if (sr & (MYUART_SR_OVERRUN | MYUART_SR_PARITY | |
|||||||
| 769 | MYUART_SR_FRAMING | MYUART_SR_BREAK)) { |
|||||||
| 770 | myuart_hw_clear_errors(ur); |
|||||||
| 771 | } |
|||||||
| 772 | ||||||||
| 773 | /* Console magic: BREAK (or a byte sequence) -> DDB. */ |
|||||||
| 774 | if (sc->sc_console) { |
|||||||
| 775 | if (sr & MYUART_SR_BREAK) { |
|||||||
| 776 | cn_check_magic(myuart_consdev.cn_dev, |
|||||||
| 777 | CNC_BREAK, myuart_cnm_state); |
|||||||
| 778 | } else { |
|||||||
| 779 | cn_check_magic(myuart_consdev.cn_dev, |
|||||||
| 780 | c, myuart_cnm_state); |
|||||||
| 781 | } |
|||||||
| 782 | } |
|||||||
| 783 | ||||||||
| 784 | if (!open || (sr & MYUART_SR_BREAK)) { |
|||||||
| 785 | continue; /* nobody listening, or a BREAK */ |
|||||||
| 786 | } |
|||||||
| 787 | ||||||||
| 788 | if (sc->sc_rbput - sc->sc_rbget >= MYUART_RBUF_SIZE) { |
|||||||
| 789 | sc->sc_rbovf++; /* ring full: drop it */ |
|||||||
| 790 | continue; |
|||||||
| 791 | } |
|||||||
| 792 | sc->sc_rbuf[sc->sc_rbput++ & (MYUART_RBUF_SIZE - 1)] = |
|||||||
| 793 | ((uint16_t)sr << 8) | c; |
|||||||
| 794 | } |
|||||||
| 795 | } |
|||||||
| 796 | ||||||||
| 797 | static void |
|||||||
| 798 | myuart_txintr(struct myuart_softc *sc) |
|||||||
| 799 | { |
|||||||
| 800 | struct myuart_regs * const ur = &sc->sc_regs; |
|||||||
| 801 | ||||||||
| 802 | /* Fill the transmitter (or its FIFO) as far as it will go. */ |
|||||||
| 803 | while (sc->sc_tbc > 0 && myuart_hw_tx_ready(ur)) { |
|||||||
| 804 | myuart_hw_putc(ur, *sc->sc_tba++); |
|||||||
| 805 | sc->sc_tbc--; |
|||||||
| 806 | } |
|||||||
| 807 | ||||||||
| 808 | if (sc->sc_tbc == 0) { |
|||||||
| 809 | /* Burst done: stop TX interrupts, let the softint refill. */ |
|||||||
| 810 | sc->sc_tx_on = false; |
|||||||
| 811 | myuart_update_intr(sc); |
|||||||
| 812 | sc->sc_tx_done = true; |
|||||||
| 813 | } |
|||||||
| 814 | } |
|||||||
| 815 | ||||||||
| 816 | static int |
|||||||
| 817 | myuart_intr(void *arg) |
|||||||
| 818 | { |
|||||||
| 819 | struct myuart_softc * const sc = arg; |
|||||||
| 820 | ||||||||
| 821 | mutex_spin_enter(&sc->sc_lock); |
|||||||
| 822 | if (!myuart_hw_intr_pending(&sc->sc_regs, sc->sc_tx_on)) { |
|||||||
| 823 | mutex_spin_exit(&sc->sc_lock); |
|||||||
| 824 | return 0; /* not ours (shared interrupt level) */ |
|||||||
| 825 | } |
|||||||
| 826 | ||||||||
| 827 | myuart_rxintr(sc); |
|||||||
| 828 | if (sc->sc_tx_on) { |
|||||||
| 829 | myuart_txintr(sc); |
|||||||
| 830 | } |
|||||||
| 831 | mutex_spin_exit(&sc->sc_lock); |
|||||||
| 832 | ||||||||
| 833 | softint_schedule(sc->sc_si); |
|||||||
| 834 | return 1; |
|||||||
| 835 | } |
|||||||
| 836 | ||||||||
| 837 | /* |
|||||||
| 838 | * ---------------------------------------------------------------------- |
|||||||
| 839 | * Soft interrupt |
|||||||
| 840 | * ---------------------------------------------------------------------- |
|||||||
| 841 | * |
|||||||
| 842 | * Runs at IPL_SOFTSERIAL, where it's safe to call the tty layer. |
|||||||
| 843 | */ |
|||||||
| 844 | static void |
|||||||
| 845 | myuart_soft(void *arg) |
|||||||
| 846 | { |
|||||||
| 847 | struct myuart_softc * const sc = arg; |
|||||||
| 848 | struct tty * const tp = sc->sc_tty; |
|||||||
| 849 | uint16_t ent; |
|||||||
| 850 | u_int ovf; |
|||||||
| 851 | bool tx_done; |
|||||||
| 852 | int c; |
|||||||
| 853 | ||||||||
| 854 | /* Report ring-buffer overflows (not too often: use log(9)). */ |
|||||||
| 855 | mutex_spin_enter(&sc->sc_lock); |
|||||||
| 856 | ovf = sc->sc_rbovf; |
|||||||
| 857 | sc->sc_rbovf = 0; |
|||||||
| 858 | mutex_spin_exit(&sc->sc_lock); |
|||||||
| 859 | if (ovf != 0) { |
|||||||
| 860 | log(LOG_WARNING, "%s: %u receive overflow(s)\n", |
|||||||
| 861 | device_xname(sc->sc_dev), ovf); |
|||||||
| 862 | } |
|||||||
| 863 | ||||||||
| 864 | /* Hand received bytes to the line discipline. */ |
|||||||
| 865 | for (;;) { |
|||||||
| 866 | mutex_spin_enter(&sc->sc_lock); |
|||||||
| 867 | if (sc->sc_rbget == sc->sc_rbput) { |
|||||||
| 868 | mutex_spin_exit(&sc->sc_lock); |
|||||||
| 869 | break; |
|||||||
| 870 | } |
|||||||
| 871 | ent = sc->sc_rbuf[sc->sc_rbget++ & (MYUART_RBUF_SIZE - 1)]; |
|||||||
| 872 | mutex_spin_exit(&sc->sc_lock); |
|||||||
| 873 | ||||||||
| 874 | c = ent & 0xff; |
|||||||
| 875 | if (ent & (MYUART_SR_FRAMING << 8)) { |
|||||||
| 876 | SET(c, TTY_FE); |
|||||||
| 877 | } |
|||||||
| 878 | if (ent & (MYUART_SR_PARITY << 8)) { |
|||||||
| 879 | SET(c, TTY_PE); |
|||||||
| 880 | } |
|||||||
| 881 | if (ISSET(tp->t_state, TS_ISOPEN)) { |
|||||||
| 882 | (*tp->t_linesw->l_rint)(c, tp); |
|||||||
| 883 | } |
|||||||
| 884 | } |
|||||||
| 885 | ||||||||
| 886 | /* If a transmit burst finished, release it and ask for more. */ |
|||||||
| 887 | mutex_spin_enter(&sc->sc_lock); |
|||||||
| 888 | tx_done = sc->sc_tx_done; |
|||||||
| 889 | sc->sc_tx_done = false; |
|||||||
| 890 | mutex_spin_exit(&sc->sc_lock); |
|||||||
| 891 | ||||||||
| 892 | if (tx_done) { |
|||||||
| 893 | CLR(tp->t_state, TS_BUSY); |
|||||||
| 894 | if (ISSET(tp->t_state, TS_FLUSH)) { |
|||||||
| 895 | CLR(tp->t_state, TS_FLUSH); |
|||||||
| 896 | } else { |
|||||||
| 897 | ndflush(&tp->t_outq, |
|||||||
| 898 | (int)(sc->sc_tba - tp->t_outq.c_cf)); |
|||||||
| 899 | } |
|||||||
| 900 | (*tp->t_linesw->l_start)(tp); |
|||||||
| 901 | } |
|||||||
| 902 | } |
|||||||
| 903 | ||||||||
| 904 | /* |
|||||||
| 905 | * ---------------------------------------------------------------------- |
|||||||
| 906 | * tty layer callbacks |
|||||||
| 907 | * ---------------------------------------------------------------------- |
|||||||
| 908 | */ |
|||||||
| 909 | ||||||||
| 910 | static struct myuart_softc * |
|||||||
| 911 | myuart_lookup(dev_t dev) |
|||||||
| 912 | { |
|||||||
| 913 | struct myuart_softc *sc; |
|||||||
| 914 | ||||||||
| 915 | sc = device_lookup_private(&myuart_cd, TTUNIT(dev)); |
|||||||
| 916 | if (sc == NULL || sc->sc_si == NULL || sc->sc_ih == NULL) { |
|||||||
| 917 | return NULL; /* no such unit, or attach failed */ |
|||||||
| 918 | } |
|||||||
| 919 | return sc; |
|||||||
| 920 | } |
|||||||
| 921 | ||||||||
| 922 | /* |
|||||||
| 923 | * Start output: take a contiguous chunk of the tty's output queue and |
|||||||
| 924 | * let the transmit interrupt send it. |
|||||||
| 925 | */ |
|||||||
| 926 | static void |
|||||||
| 927 | myuartstart(struct tty *tp) |
|||||||
| 928 | { |
|||||||
| 929 | struct myuart_softc * const sc = myuart_lookup(tp->t_dev); |
|||||||
| 930 | int s; |
|||||||
| 931 | ||||||||
| 932 | if (sc == NULL) { |
|||||||
| 933 | return; |
|||||||
| 934 | } |
|||||||
| 935 | ||||||||
| 936 | s = spltty(); |
|||||||
| 937 | if (ISSET(tp->t_state, TS_BUSY | TS_TIMEOUT | TS_TTSTOP)) { |
|||||||
| 938 | goto out; /* already sending, or stopped (^S) */ |
|||||||
| 939 | } |
|||||||
| 940 | if (!ttypull(tp)) { |
|||||||
| 941 | goto out; /* nothing to send */ |
|||||||
| 942 | } |
|||||||
| 943 | ||||||||
| 944 | mutex_spin_enter(&sc->sc_lock); |
|||||||
| 945 | sc->sc_tba = tp->t_outq.c_cf; |
|||||||
| 946 | sc->sc_tbc = ndqb(&tp->t_outq, 0); |
|||||||
| 947 | SET(tp->t_state, TS_BUSY); |
|||||||
| 948 | sc->sc_tx_on = true; /* interrupts as soon as TX is empty */ |
|||||||
| 949 | myuart_update_intr(sc); |
|||||||
| 950 | mutex_spin_exit(&sc->sc_lock); |
|||||||
| 951 | out: |
|||||||
| 952 | splx(s); |
|||||||
| 953 | } |
|||||||
| 954 | ||||||||
| 955 | /* Stop output (e.g. on ^S or a flush). */ |
|||||||
| 956 | static void |
|||||||
| 957 | myuartstop(struct tty *tp, int flag) |
|||||||
| 958 | { |
|||||||
| 959 | struct myuart_softc * const sc = myuart_lookup(tp->t_dev); |
|||||||
| 960 | ||||||||
| 961 | if (sc == NULL) { |
|||||||
| 962 | return; |
|||||||
| 963 | } |
|||||||
| 964 | ||||||||
| 965 | mutex_spin_enter(&sc->sc_lock); |
|||||||
| 966 | if (ISSET(tp->t_state, TS_BUSY)) { |
|||||||
| 967 | sc->sc_tbc = 0; /* finish after the current byte */ |
|||||||
| 968 | if (!ISSET(tp->t_state, TS_TTSTOP)) { |
|||||||
| 969 | SET(tp->t_state, TS_FLUSH); |
|||||||
| 970 | } |
|||||||
| 971 | } |
|||||||
| 972 | mutex_spin_exit(&sc->sc_lock); |
|||||||
| 973 | } |
|||||||
| 974 | ||||||||
| 975 | /* Change speed / framing (stty, tcsetattr). */ |
|||||||
| 976 | static int |
|||||||
| 977 | myuartparam(struct tty *tp, struct termios *t) |
|||||||
| 978 | { |
|||||||
| 979 | struct myuart_softc * const sc = myuart_lookup(tp->t_dev); |
|||||||
| 980 | int error; |
|||||||
| 981 | ||||||||
| 982 | if (sc == NULL) { |
|||||||
| 983 | return ENXIO; |
|||||||
| 984 | } |
|||||||
| 985 | ||||||||
| 986 | /* Unsupported in this template: split speeds, flow control. */ |
|||||||
| 987 | if (t->c_ispeed != 0 && t->c_ispeed != t->c_ospeed) { |
|||||||
| 988 | return EINVAL; |
|||||||
| 989 | } |
|||||||
| 990 | if (ISSET(t->c_cflag, CRTSCTS | MDMBUF)) { |
|||||||
| 991 | return EINVAL; |
|||||||
| 992 | } |
|||||||
| 993 | ||||||||
| 994 | mutex_spin_enter(&sc->sc_lock); |
|||||||
| 995 | error = myuart_hw_program(&sc->sc_regs, t->c_ospeed, t->c_cflag); |
|||||||
| 996 | mutex_spin_exit(&sc->sc_lock); |
|||||||
| 997 | if (error) { |
|||||||
| 998 | return error; |
|||||||
| 999 | } |
|||||||
| 1000 | ||||||||
| 1001 | tp->t_ispeed = 0; |
|||||||
| 1002 | tp->t_ospeed = t->c_ospeed; |
|||||||
| 1003 | tp->t_cflag = t->c_cflag; |
|||||||
| 1004 | ||||||||
| 1005 | /* No modem control: carrier is always present. */ |
|||||||
| 1006 | (void)(*tp->t_linesw->l_modem)(tp, 1); |
|||||||
| 1007 | ||||||||
| 1008 | return 0; |
|||||||
| 1009 | } |
|||||||
| 1010 | ||||||||
| 1011 | /* |
|||||||
| 1012 | * ---------------------------------------------------------------------- |
|||||||
| 1013 | * cdevsw entry points |
|||||||
| 1014 | * ---------------------------------------------------------------------- |
|||||||
| 1015 | */ |
|||||||
| 1016 | ||||||||
| 1017 | static int |
|||||||
| 1018 | myuartopen(dev_t dev, int flag, int mode, struct lwp *l) |
|||||||
| 1019 | { |
|||||||
| 1020 | struct myuart_softc * const sc = myuart_lookup(dev); |
|||||||
| 1021 | struct tty *tp; |
|||||||
| 1022 | int error; |
|||||||
| 1023 | ||||||||
| 1024 | if (sc == NULL) { |
|||||||
| 1025 | return ENXIO; |
|||||||
| 1026 | } |
|||||||
| 1027 | tp = sc->sc_tty; |
|||||||
| 1028 | ||||||||
| 1029 | if (kauth_authorize_device_tty(l->l_cred, KAUTH_DEVICE_TTY_OPEN, tp)) { |
|||||||
| 1030 | return EBUSY; |
|||||||
| 1031 | } |
|||||||
| 1032 | ||||||||
| 1033 | /* First open: set defaults and turn on receive interrupts. */ |
|||||||
| 1034 | if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) { |
|||||||
| 1035 | struct termios t; |
|||||||
| 1036 | ||||||||
| 1037 | tp->t_dev = dev; |
|||||||
| 1038 | ||||||||
| 1039 | t.c_ispeed = 0; |
|||||||
| 1040 | if (sc->sc_console) { |
|||||||
| 1041 | t.c_ospeed = myuart_cons.speed; |
|||||||
| 1042 | t.c_cflag = myuart_cons.cflag; |
|||||||
| 1043 | } else { |
|||||||
| 1044 | t.c_ospeed = TTYDEF_SPEED; |
|||||||
| 1045 | t.c_cflag = TTYDEF_CFLAG; |
|||||||
| 1046 | } |
|||||||
| 1047 | SET(t.c_cflag, CLOCAL); |
|||||||
| 1048 | CLR(t.c_cflag, CRTSCTS | MDMBUF); |
|||||||
| 1049 | ||||||||
| 1050 | tp->t_ospeed = 0; /* force myuartparam() to program */ |
|||||||
| 1051 | (void)myuartparam(tp, &t); |
|||||||
| 1052 | tp->t_iflag = TTYDEF_IFLAG; |
|||||||
| 1053 | tp->t_oflag = TTYDEF_OFLAG; |
|||||||
| 1054 | tp->t_lflag = TTYDEF_LFLAG; |
|||||||
| 1055 | ttychars(tp); |
|||||||
| 1056 | ttsetwater(tp); |
|||||||
| 1057 | ||||||||
| 1058 | mutex_spin_enter(&sc->sc_lock); |
|||||||
| 1059 | sc->sc_rbget = sc->sc_rbput = 0; |
|||||||
| 1060 | sc->sc_rx_on = true; |
|||||||
| 1061 | myuart_update_intr(sc); |
|||||||
| 1062 | mutex_spin_exit(&sc->sc_lock); |
|||||||
| 1063 | } |
|||||||
| 1064 | ||||||||
| 1065 | error = ttyopen(tp, TTDIALOUT(dev), ISSET(flag, O_NONBLOCK)); |
|||||||
| 1066 | if (error == 0) { |
|||||||
| 1067 | error = (*tp->t_linesw->l_open)(dev, tp); |
|||||||
| 1068 | } |
|||||||
| 1069 | return error; |
|||||||
| 1070 | } |
|||||||
| 1071 | ||||||||
| 1072 | static int |
|||||||
| 1073 | myuartclose(dev_t dev, int flag, int mode, struct lwp *l) |
|||||||
| 1074 | { |
|||||||
| 1075 | struct myuart_softc * const sc = myuart_lookup(dev); |
|||||||
| 1076 | struct tty *tp; |
|||||||
| 1077 | ||||||||
| 1078 | if (sc == NULL) { |
|||||||
| 1079 | return 0; |
|||||||
| 1080 | } |
|||||||
| 1081 | tp = sc->sc_tty; |
|||||||
| 1082 | ||||||||
| 1083 | if (!ISSET(tp->t_state, TS_ISOPEN)) { |
|||||||
| 1084 | return 0; |
|||||||
| 1085 | } |
|||||||
| 1086 | (*tp->t_linesw->l_close)(tp, flag); |
|||||||
| 1087 | ttyclose(tp); |
|||||||
| 1088 | ||||||||
| 1089 | /* Last close: go quiet (the console keeps listening for BREAK). */ |
|||||||
| 1090 | if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0 && |
|||||||
| 1091 | !sc->sc_console) { |
|||||||
| 1092 | mutex_spin_enter(&sc->sc_lock); |
|||||||
| 1093 | sc->sc_rx_on = sc->sc_tx_on = false; |
|||||||
| 1094 | myuart_update_intr(sc); |
|||||||
| 1095 | mutex_spin_exit(&sc->sc_lock); |
|||||||
| 1096 | } |
|||||||
| 1097 | return 0; |
|||||||
| 1098 | } |
|||||||
| 1099 | ||||||||
| 1100 | static int |
|||||||
| 1101 | myuartread(dev_t dev, struct uio *uio, int flag) |
|||||||
| 1102 | { |
|||||||
| 1103 | struct myuart_softc * const sc = myuart_lookup(dev); |
|||||||
| 1104 | ||||||||
| 1105 | if (sc == NULL) { |
|||||||
| 1106 | return ENXIO; |
|||||||
| 1107 | } |
|||||||
| 1108 | return (*sc->sc_tty->t_linesw->l_read)(sc->sc_tty, uio, flag); |
|||||||
| 1109 | } |
|||||||
| 1110 | ||||||||
| 1111 | static int |
|||||||
| 1112 | myuartwrite(dev_t dev, struct uio *uio, int flag) |
|||||||
| 1113 | { |
|||||||
| 1114 | struct myuart_softc * const sc = myuart_lookup(dev); |
|||||||
| 1115 | ||||||||
| 1116 | if (sc == NULL) { |
|||||||
| 1117 | return ENXIO; |
|||||||
| 1118 | } |
|||||||
| 1119 | return (*sc->sc_tty->t_linesw->l_write)(sc->sc_tty, uio, flag); |
|||||||
| 1120 | } |
|||||||
| 1121 | ||||||||
| 1122 | static int |
|||||||
| 1123 | myuartpoll(dev_t dev, int events, struct lwp *l) |
|||||||
| 1124 | { |
|||||||
| 1125 | struct myuart_softc * const sc = myuart_lookup(dev); |
|||||||
| 1126 | ||||||||
| 1127 | if (sc == NULL) { |
|||||||
| 1128 | return POLLHUP; |
|||||||
| 1129 | } |
|||||||
| 1130 | return (*sc->sc_tty->t_linesw->l_poll)(sc->sc_tty, events, l); |
|||||||
| 1131 | } |
|||||||
| 1132 | ||||||||
| 1133 | static struct tty * |
|||||||
| 1134 | myuarttty(dev_t dev) |
|||||||
| 1135 | { |
|||||||
| 1136 | struct myuart_softc * const sc = myuart_lookup(dev); |
|||||||
| 1137 | ||||||||
| 1138 | return sc != NULL ? sc->sc_tty : NULL; |
|||||||
| 1139 | } |
|||||||
| 1140 | ||||||||
| 1141 | static int |
|||||||
| 1142 | myuartioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) |
|||||||
| 1143 | { |
|||||||
| 1144 | struct myuart_softc * const sc = myuart_lookup(dev); |
|||||||
| 1145 | struct tty *tp; |
|||||||
| 1146 | int error; |
|||||||
| 1147 | ||||||||
| 1148 | if (sc == NULL) { |
|||||||
| 1149 | return ENXIO; |
|||||||
| 1150 | } |
|||||||
| 1151 | tp = sc->sc_tty; |
|||||||
| 1152 | ||||||||
| 1153 | /* Line discipline first, then the generic tty ioctls. */ |
|||||||
| 1154 | error = (*tp->t_linesw->l_ioctl)(tp, cmd, data, flag, l); |
|||||||
| 1155 | if (error != EPASSTHROUGH) { |
|||||||
| 1156 | return error; |
|||||||
| 1157 | } |
|||||||
| 1158 | error = ttioctl(tp, cmd, data, flag, l); |
|||||||
| 1159 | if (error != EPASSTHROUGH) { |
|||||||
| 1160 | return error; |
|||||||
| 1161 | } |
|||||||
| 1162 | ||||||||
| 1163 | /* |
|||||||
| 1164 | * Device-specific ioctls go here, e.g. TIOCSBRK / TIOCCBRK to |
|||||||
| 1165 | * start and stop sending a break, or TIOCMGET / TIOCMSET for |
|||||||
| 1166 | * modem control lines. |
|||||||
| 1167 | */ |
|||||||
| 1168 | return EPASSTHROUGH; |
|||||||
| 1169 | } |
|||||||
| 1170 | ``` |
|||||||
|
1171 | |||||||
| 1172 | ### Timer |
|||||||
| 1173 | ||||||||
| 1174 | Confirmed: a NULL argument makes the dispatcher pass the interrupt frame. Now a compile test: |
|||||||
| 1175 | ||||||||
| 1176 | The timer template is mytimer.c, built the same way as the UART one. I compiled it into an SBC3 kernel twice, once each way for the auto-reload setting, and both built cleanly. Then I took it back out of the tree. |
|||||||
| 1177 | ||||||||
| 1178 | What you change: the CHIP-SPECIFIC sections |
|||||||
| 1179 | ||||||||
| 1180 | - Registers: a placeholder register map for a simple down-counter (control, status, 24-bit reload). |
|||||||
| 1181 | - Limits and reload behaviour: |
|||||||
| 1182 | - MYTIMER_COUNT_MAX is the largest reload value. |
|||||||
| 1183 | - MYTIMER_PRESCALE is any fixed divider in front of the counter. |
|||||||
| 1184 | - MYTIMER_AUTO_RELOAD says whether the chip restarts itself at zero. If it doesn’t, the driver restarts it by hand on every tick. That loses a little time per tick, so the clock runs slightly slow. |
|||||||
| 1185 | - Three hardware functions: |
|||||||
| 1186 | - hw_stop() stops the counter and clears pending interrupts. |
|||||||
| 1187 | - hw_start(count) loads the count and starts periodic interrupts. |
|||||||
| 1188 | - hw_ack() clears the interrupt. Getting this wrong makes the machine appear to hang, because the CPU re-enters the handler immediately. |
|||||||
| 1189 | ||||||||
| 1190 | How it fits into hb68k: a timer driver provides only mechanism. The clock code in hb68k/clock.c owns the policy and calls the driver through four functions in a small table (struct clock_if): |
|||||||
| 1191 | ||||||||
|
1192 | | Function | When it’s called | What it does | |
||||||
| 1193 | | ---------------------- | -------------------------- | ------------ | |
|||||||
| 1194 | | `clk_init` | once, late in boot | establishes the interrupt at IPL_SCHED | |
|||||||
| 1195 | | `clk_arm(interval_us)` | once, right after init | starts periodic interrupts, e.g. every 10 000 µs for 100 Hz | |
|||||||
| 1196 | | `clk_intr` | at the start of every tick | acknowledges the interrupt, and reloads if needed | |
|||||||
| 1197 | | `clk_disarm` | shutdown or error | stops the timer | |
|||||||
|
1198 | |||||||
| 1199 | The file header explains: |
|||||||
| 1200 | ||||||||
| 1201 | - How netbsd,hardclock / netbsd,statclock under /chosen pick each timer’s role, and what happens without them. |
|||||||
| 1202 | - Why the timer belongs at IPL 6. |
|||||||
| 1203 | - Which HZ values are allowed (they must divide 1 000 000). |
|||||||
| 1204 | - Why the interrupt handler’s argument must be NULL. I checked this in the dispatcher: with NULL, the clock code receives the interrupt frame, which it needs. |
|||||||
| 1205 | ||||||||
| 1206 | Setup is lighter than the UART’s: |
|||||||
| 1207 | ||||||||
| 1208 | - Add device/attach/file to files.hb68k. |
|||||||
| 1209 | - Add mytimer* at fdt? pass 3 to the config. |
|||||||
| 1210 | - Add a device-tree node with clocks or clock-frequency, plus an interrupts entry. |
|||||||
| 1211 | ||||||||
| 1212 | There’s no major number or /dev node, since nothing opens a timer. |
|||||||
| 1213 | ||||||||
| 1214 | Left out deliberately: |
|||||||
| 1215 | ||||||||
| 1216 | - A high-resolution timecounter. That needs a free-running counter, not a counter that resets every tick, so it depends on what your chip offers. |
|||||||
| 1217 | - Randomising the statclock interval. hb68k’s clock code doesn’t do this yet either. |
|||||||
| 1218 | ||||||||
|
1219 | ``` |
||||||
| 1220 | /* $NetBSD$ */ |
|||||||
| 1221 | ||||||||
| 1222 | /*- |
|||||||
| 1223 | * Copyright (c) 2026 The NetBSD Foundation, Inc. |
|||||||
| 1224 | * All rights reserved. |
|||||||
| 1225 | * |
|||||||
| 1226 | * Redistribution and use in source and binary forms, with or without |
|||||||
| 1227 | * modification, are permitted provided that the following conditions |
|||||||
| 1228 | * are met: |
|||||||
| 1229 | * 1. Redistributions of source code must retain the above copyright |
|||||||
| 1230 | * notice, this list of conditions and the following disclaimer. |
|||||||
| 1231 | * 2. Redistributions in binary form must reproduce the above copyright |
|||||||
| 1232 | * notice, this list of conditions and the following disclaimer in the |
|||||||
| 1233 | * documentation and/or other materials provided with the distribution. |
|||||||
| 1234 | * |
|||||||
| 1235 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
|||||||
| 1236 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
|||||||
| 1237 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|||||||
| 1238 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
|||||||
| 1239 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|||||||
| 1240 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|||||||
| 1241 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|||||||
| 1242 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|||||||
| 1243 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|||||||
| 1244 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||||||
| 1245 | * POSSIBILITY OF SUCH DAMAGE. |
|||||||
| 1246 | */ |
|||||||
| 1247 | ||||||||
| 1248 | /* |
|||||||
| 1249 | * mytimer -- template driver for a periodic hardware timer used as a |
|||||||
| 1250 | * NetBSD/hb68k system clock, attached via Flattened Device Tree (FDT). |
|||||||
| 1251 | * |
|||||||
| 1252 | * ====================================================================== |
|||||||
| 1253 | * HOW TO USE THIS TEMPLATE |
|||||||
| 1254 | * ====================================================================== |
|||||||
| 1255 | * |
|||||||
| 1256 | * 1. Copy this file to e.g. sys/arch/hb68k/dev/<chip>_timer.c and |
|||||||
| 1257 | * replace "mytimer" with your driver name throughout (it becomes |
|||||||
| 1258 | * the device name, e.g. "mytimer0"). |
|||||||
| 1259 | * |
|||||||
| 1260 | * 2. Fill in the sections marked "CHIP-SPECIFIC" below. Everything |
|||||||
| 1261 | * else is generic hb68k plumbing. |
|||||||
| 1262 | * |
|||||||
| 1263 | * 3. Hook it into the build: |
|||||||
| 1264 | * |
|||||||
| 1265 | * sys/arch/hb68k/conf/files.hb68k: |
|||||||
| 1266 | * device mytimer |
|||||||
| 1267 | * attach mytimer at fdt |
|||||||
| 1268 | * file arch/hb68k/dev/mytimer.c mytimer |
|||||||
| 1269 | * |
|||||||
| 1270 | * Kernel config (e.g. conf/SBC3). Timers attach after the |
|||||||
| 1271 | * interrupt controller (pass 2), hence pass 3: |
|||||||
| 1272 | * mytimer* at fdt? pass 3 |
|||||||
| 1273 | * |
|||||||
| 1274 | * A timer has no /dev entry, so it needs no major number. |
|||||||
| 1275 | * |
|||||||
| 1276 | * 4. Describe the chip in the device tree: |
|||||||
| 1277 | * |
|||||||
| 1278 | * timer@XXXXXX { |
|||||||
| 1279 | * compatible = "vendor,mytimer"; (must match compat_data[]) |
|||||||
| 1280 | * reg = <0xXXXXXX 0x10>; (base, size of register block) |
|||||||
| 1281 | * reg-shift = <1>; (log2 of register spacing) |
|||||||
| 1282 | * clocks = <&osc>; (a fixed-clock node), OR |
|||||||
| 1283 | * clock-frequency = <1000000>; (input clock in Hz) |
|||||||
| 1284 | * interrupts = <6 0>; (IPL, vector; 0 = auto-vector) |
|||||||
| 1285 | * }; |
|||||||
| 1286 | * |
|||||||
| 1287 | * and say which job it does under /chosen, using an alias: |
|||||||
| 1288 | * |
|||||||
| 1289 | * chosen { |
|||||||
| 1290 | * netbsd,hardclock = "timer0"; (the main system tick) |
|||||||
| 1291 | * netbsd,statclock = "timer1"; (optional, see below) |
|||||||
| 1292 | * }; |
|||||||
| 1293 | * aliases { |
|||||||
| 1294 | * timer0 = "/obio/timer@XXXXXX"; |
|||||||
| 1295 | * }; |
|||||||
| 1296 | * |
|||||||
| 1297 | * Without those properties, the first timer to attach becomes the |
|||||||
| 1298 | * hardclock and the second (if any) the statclock. |
|||||||
| 1299 | * |
|||||||
| 1300 | * ====================================================================== |
|||||||
| 1301 | * HOW hb68k USES A TIMER |
|||||||
| 1302 | * ====================================================================== |
|||||||
| 1303 | * |
|||||||
| 1304 | * The machine-independent clock code in sys/arch/hb68k/hb68k/clock.c |
|||||||
| 1305 | * owns the policy; a timer driver only provides mechanism, through the |
|||||||
| 1306 | * five-member `struct clock_if' (machine/clockvar.h): |
|||||||
| 1307 | * |
|||||||
| 1308 | * clk_dev The timer's device_t. |
|||||||
| 1309 | * |
|||||||
| 1310 | * clk_init Called once, from cpu_initclocks(), late in boot. Must |
|||||||
| 1311 | * establish the hardware interrupt with the handler it is |
|||||||
| 1312 | * given (at IPL_SCHED) and return 0, or an error. |
|||||||
| 1313 | * |
|||||||
| 1314 | * clk_arm Start the timer interrupting periodically, every |
|||||||
| 1315 | * `interval_us' microseconds (10000 for the usual |
|||||||
| 1316 | * hz = 100). Called once, right after clk_init. |
|||||||
| 1317 | * |
|||||||
| 1318 | * clk_intr Called by the clock code at the start of every timer |
|||||||
| 1319 | * interrupt. Acknowledge the interrupt here and, if the |
|||||||
| 1320 | * chip doesn't reload by itself, restart the count. |
|||||||
| 1321 | * |
|||||||
| 1322 | * clk_disarm Stop the timer (used at shutdown / error paths). |
|||||||
| 1323 | * |
|||||||
| 1324 | * The driver registers itself with clock_attach() at the end of |
|||||||
| 1325 | * attach. The clock code then decides which role the timer plays: |
|||||||
| 1326 | * |
|||||||
| 1327 | * hardclock Drives the system tick: timekeeping, scheduling, |
|||||||
| 1328 | * callouts. Every system needs one. |
|||||||
| 1329 | * |
|||||||
| 1330 | * statclock Optional second timer for CPU-usage statistics and |
|||||||
| 1331 | * profiling. With only one timer, the hardclock does both |
|||||||
| 1332 | * jobs, and boot prints "No statclock; using hardclock." |
|||||||
| 1333 | * |
|||||||
| 1334 | * Interrupt level: the hardclock runs at IPL_SCHED, which on hb68k is |
|||||||
| 1335 | * hardware level 6, so wire the timer to IPL 6 and use 6 as the first |
|||||||
| 1336 | * cell of "interrupts". A lower level works, but the clock interrupt |
|||||||
| 1337 | * can then be delayed or blocked by other devices and by the soft |
|||||||
| 1338 | * interrupt level (1), so the system time will drift. |
|||||||
| 1339 | * |
|||||||
| 1340 | * hz must divide 1000000 exactly (the clock code checks this): use |
|||||||
| 1341 | * 50, 100, 200, 250, 400, 500 or 1000. Set it in the kernel config |
|||||||
| 1342 | * with "options HZ=..." if the default of 100 doesn't suit the |
|||||||
| 1343 | * hardware. |
|||||||
| 1344 | */ |
|||||||
| 1345 | ||||||||
| 1346 | #include <sys/cdefs.h> |
|||||||
| 1347 | __KERNEL_RCSID(0, "$NetBSD$"); |
|||||||
| 1348 | ||||||||
| 1349 | #include <sys/param.h> |
|||||||
| 1350 | #include <sys/bus.h> |
|||||||
| 1351 | #include <sys/device.h> |
|||||||
| 1352 | #include <sys/intr.h> |
|||||||
| 1353 | #include <sys/systm.h> |
|||||||
| 1354 | ||||||||
| 1355 | #include <machine/clockvar.h> |
|||||||
| 1356 | ||||||||
| 1357 | #include <dev/fdt/fdtvar.h> |
|||||||
| 1358 | ||||||||
| 1359 | /* |
|||||||
| 1360 | * ###################################################################### |
|||||||
| 1361 | * CHIP-SPECIFIC: registers |
|||||||
| 1362 | * ###################################################################### |
|||||||
| 1363 | * |
|||||||
| 1364 | * Replace these with your chip's register map. Offsets are register |
|||||||
| 1365 | * *numbers*; the real byte offset is (reg << reg-shift). |
|||||||
| 1366 | * |
|||||||
| 1367 | * The placeholder layout below is a typical simple down-counter: |
|||||||
| 1368 | * CTRL enable, interrupt enable, auto-reload |
|||||||
| 1369 | * STATUS "counter reached zero" flag, write 1 to clear |
|||||||
| 1370 | * RELOAD0..2 24-bit reload (preload) value, low byte first |
|||||||
| 1371 | */ |
|||||||
| 1372 | #define MYTIMER_REG_CTRL 0 |
|||||||
| 1373 | #define MYTIMER_REG_STATUS 1 |
|||||||
| 1374 | #define MYTIMER_REG_RELOAD0 2 /* bits 7..0 */ |
|||||||
| 1375 | #define MYTIMER_REG_RELOAD1 3 /* bits 15..8 */ |
|||||||
| 1376 | #define MYTIMER_REG_RELOAD2 4 /* bits 23..16 */ |
|||||||
| 1377 | ||||||||
| 1378 | /* CTRL bits */ |
|||||||
| 1379 | #define MYTIMER_CTRL_RUN 0x01 /* counter running */ |
|||||||
| 1380 | #define MYTIMER_CTRL_IRQEN 0x02 /* interrupt on reaching zero */ |
|||||||
| 1381 | #define MYTIMER_CTRL_RELOAD 0x04 /* reload from RELOAD on zero */ |
|||||||
| 1382 | ||||||||
| 1383 | /* STATUS bits */ |
|||||||
| 1384 | #define MYTIMER_SR_ZERO 0x01 /* counter hit zero (write 1 = clear) */ |
|||||||
| 1385 | ||||||||
| 1386 | /* |
|||||||
| 1387 | * Counter limits. MAX is the largest value the reload register can |
|||||||
| 1388 | * hold; PRESCALE is any fixed divider between the input clock and the |
|||||||
| 1389 | * counter (1 if none). Together with the input clock these decide the |
|||||||
| 1390 | * longest interval the timer can produce: MAX * PRESCALE / freq. |
|||||||
| 1391 | */ |
|||||||
| 1392 | #define MYTIMER_COUNT_MAX 0x00ffffffU |
|||||||
| 1393 | #define MYTIMER_PRESCALE 1 |
|||||||
| 1394 | ||||||||
| 1395 | /* |
|||||||
| 1396 | * Set to true if the counter reloads itself when it reaches zero |
|||||||
| 1397 | * (most timers can). If false, clk_intr() restarts it by hand; the |
|||||||
| 1398 | * time spent getting to the interrupt handler is then lost on every |
|||||||
| 1399 | * tick, and the clock runs slightly slow. |
|||||||
| 1400 | */ |
|||||||
| 1401 | #define MYTIMER_AUTO_RELOAD true |
|||||||
| 1402 | ||||||||
| 1403 | /* |
|||||||
| 1404 | * ###################################################################### |
|||||||
| 1405 | * Device tree matching |
|||||||
| 1406 | * ###################################################################### |
|||||||
| 1407 | */ |
|||||||
| 1408 | static const struct device_compatible_entry compat_data[] = { |
|||||||
| 1409 | { .compat = "vendor,mytimer" }, |
|||||||
| 1410 | DEVICE_COMPAT_EOL |
|||||||
| 1411 | }; |
|||||||
| 1412 | ||||||||
| 1413 | /* |
|||||||
| 1414 | * ###################################################################### |
|||||||
| 1415 | * Driver state |
|||||||
| 1416 | * ###################################################################### |
|||||||
| 1417 | */ |
|||||||
| 1418 | struct mytimer_softc { |
|||||||
| 1419 | device_t sc_dev; |
|||||||
| 1420 | bus_space_tag_t sc_bst; |
|||||||
| 1421 | bus_space_handle_t sc_bsh; |
|||||||
| 1422 | u_int sc_shift; /* reg-shift */ |
|||||||
| 1423 | uint32_t sc_freq; /* counter decrement rate, Hz */ |
|||||||
| 1424 | uint32_t sc_reload; /* count for one interval */ |
|||||||
| 1425 | void *sc_ih; /* interrupt handle */ |
|||||||
| 1426 | struct clock_if sc_clkif; /* our interface to clock.c */ |
|||||||
| 1427 | }; |
|||||||
| 1428 | ||||||||
| 1429 | static inline uint8_t |
|||||||
| 1430 | mytimer_read(struct mytimer_softc *sc, u_int reg) |
|||||||
| 1431 | { |
|||||||
| 1432 | return bus_space_read_1(sc->sc_bst, sc->sc_bsh, reg << sc->sc_shift); |
|||||||
| 1433 | } |
|||||||
| 1434 | ||||||||
| 1435 | static inline void |
|||||||
| 1436 | mytimer_write(struct mytimer_softc *sc, u_int reg, uint8_t val) |
|||||||
| 1437 | { |
|||||||
| 1438 | bus_space_write_1(sc->sc_bst, sc->sc_bsh, reg << sc->sc_shift, val); |
|||||||
| 1439 | } |
|||||||
| 1440 | ||||||||
| 1441 | /* |
|||||||
| 1442 | * ###################################################################### |
|||||||
| 1443 | * CHIP-SPECIFIC: low-level operations |
|||||||
| 1444 | * ###################################################################### |
|||||||
| 1445 | * |
|||||||
| 1446 | * The only code that touches the hardware. |
|||||||
| 1447 | */ |
|||||||
| 1448 | ||||||||
| 1449 | /* Stop the counter and clear any pending interrupt. */ |
|||||||
| 1450 | static void |
|||||||
| 1451 | mytimer_hw_stop(struct mytimer_softc *sc) |
|||||||
| 1452 | { |
|||||||
| 1453 | mytimer_write(sc, MYTIMER_REG_CTRL, 0); |
|||||||
| 1454 | mytimer_write(sc, MYTIMER_REG_STATUS, MYTIMER_SR_ZERO); |
|||||||
| 1455 | } |
|||||||
| 1456 | ||||||||
| 1457 | /* |
|||||||
| 1458 | * Load `count' and start counting down, interrupting at zero. With |
|||||||
| 1459 | * MYTIMER_AUTO_RELOAD the chip then repeats on its own. |
|||||||
| 1460 | */ |
|||||||
| 1461 | static void |
|||||||
| 1462 | mytimer_hw_start(struct mytimer_softc *sc, uint32_t count) |
|||||||
| 1463 | { |
|||||||
| 1464 | mytimer_write(sc, MYTIMER_REG_RELOAD0, count & 0xff); |
|||||||
| 1465 | mytimer_write(sc, MYTIMER_REG_RELOAD1, (count >> 8) & 0xff); |
|||||||
| 1466 | mytimer_write(sc, MYTIMER_REG_RELOAD2, (count >> 16) & 0xff); |
|||||||
| 1467 | mytimer_write(sc, MYTIMER_REG_CTRL, |
|||||||
| 1468 | MYTIMER_CTRL_RUN | MYTIMER_CTRL_IRQEN | |
|||||||
| 1469 | (MYTIMER_AUTO_RELOAD ? MYTIMER_CTRL_RELOAD : 0)); |
|||||||
| 1470 | } |
|||||||
| 1471 | ||||||||
| 1472 | /* |
|||||||
| 1473 | * Acknowledge the interrupt so the chip drops its request line. If |
|||||||
| 1474 | * this is forgotten, the CPU takes the interrupt again the moment the |
|||||||
| 1475 | * handler returns, and the machine appears to hang. |
|||||||
| 1476 | */ |
|||||||
| 1477 | static void |
|||||||
| 1478 | mytimer_hw_ack(struct mytimer_softc *sc) |
|||||||
| 1479 | { |
|||||||
| 1480 | mytimer_write(sc, MYTIMER_REG_STATUS, MYTIMER_SR_ZERO); |
|||||||
| 1481 | } |
|||||||
| 1482 | ||||||||
| 1483 | /* |
|||||||
| 1484 | * ###################################################################### |
|||||||
| 1485 | * Generic driver below this line |
|||||||
| 1486 | * ###################################################################### |
|||||||
| 1487 | */ |
|||||||
| 1488 | ||||||||
| 1489 | /* Convert an interval in microseconds to counter ticks. */ |
|||||||
| 1490 | static uint32_t |
|||||||
| 1491 | mytimer_us_to_ticks(struct mytimer_softc *sc, unsigned int interval_us) |
|||||||
| 1492 | { |
|||||||
| 1493 | const uint64_t ticks = |
|||||||
| 1494 | ((uint64_t)sc->sc_freq * interval_us + 500000) / 1000000; |
|||||||
| 1495 | ||||||||
| 1496 | if (ticks == 0 || ticks > MYTIMER_COUNT_MAX) { |
|||||||
| 1497 | /* |
|||||||
| 1498 | * The input clock is too slow (ticks == 0) or too fast |
|||||||
| 1499 | * (ticks too big) for this interval. Change the clock, |
|||||||
| 1500 | * the prescaler, or HZ. |
|||||||
| 1501 | */ |
|||||||
| 1502 | panic("%s: can't make a %u us interval from %u Hz " |
|||||||
| 1503 | "(ticks=%llu)", device_xname(sc->sc_dev), interval_us, |
|||||||
| 1504 | sc->sc_freq, (unsigned long long)ticks); |
|||||||
| 1505 | } |
|||||||
| 1506 | ||||||||
| 1507 | return (uint32_t)ticks; |
|||||||
| 1508 | } |
|||||||
| 1509 | ||||||||
| 1510 | /* clock_if: establish our interrupt. `intrfunc' is clock.c's handler. */ |
|||||||
| 1511 | static int |
|||||||
| 1512 | mytimer_init(struct clock_if *clk, int (*intrfunc)(void *)) |
|||||||
| 1513 | { |
|||||||
| 1514 | struct mytimer_softc * const sc = |
|||||||
| 1515 | container_of(clk, struct mytimer_softc, sc_clkif); |
|||||||
| 1516 | const int phandle = devhandle_to_of(device_handle(sc->sc_dev)); |
|||||||
| 1517 | char intrstr[128]; |
|||||||
| 1518 | ||||||||
| 1519 | if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) { |
|||||||
| 1520 | aprint_error_dev(sc->sc_dev, "couldn't decode interrupt\n"); |
|||||||
| 1521 | return ENXIO; |
|||||||
| 1522 | } |
|||||||
| 1523 | ||||||||
| 1524 | /* |
|||||||
| 1525 | * The handler's argument must be NULL: clock.c's handler then |
|||||||
| 1526 | * receives the interrupt frame, which hardclock() needs to tell |
|||||||
| 1527 | * whether the interrupted code was in user or kernel mode. |
|||||||
| 1528 | */ |
|||||||
| 1529 | sc->sc_ih = fdtbus_intr_establish_xname(phandle, 0, IPL_SCHED, |
|||||||
| 1530 | FDT_INTR_MPSAFE, intrfunc, NULL, device_xname(sc->sc_dev)); |
|||||||
| 1531 | if (sc->sc_ih == NULL) { |
|||||||
| 1532 | aprint_error_dev(sc->sc_dev, |
|||||||
| 1533 | "couldn't establish interrupt at %s\n", intrstr); |
|||||||
| 1534 | return ENXIO; |
|||||||
| 1535 | } |
|||||||
| 1536 | aprint_normal_dev(sc->sc_dev, "interrupting at %s\n", intrstr); |
|||||||
| 1537 | ||||||||
| 1538 | return 0; |
|||||||
| 1539 | } |
|||||||
| 1540 | ||||||||
| 1541 | /* clock_if: start periodic interrupts every `interval_us'. */ |
|||||||
| 1542 | static int |
|||||||
| 1543 | mytimer_arm(struct clock_if *clk, unsigned int interval_us) |
|||||||
| 1544 | { |
|||||||
| 1545 | struct mytimer_softc * const sc = |
|||||||
| 1546 | container_of(clk, struct mytimer_softc, sc_clkif); |
|||||||
| 1547 | ||||||||
| 1548 | sc->sc_reload = mytimer_us_to_ticks(sc, interval_us); |
|||||||
| 1549 | mytimer_hw_stop(sc); |
|||||||
| 1550 | mytimer_hw_start(sc, sc->sc_reload); |
|||||||
| 1551 | ||||||||
| 1552 | return 0; |
|||||||
| 1553 | } |
|||||||
| 1554 | ||||||||
| 1555 | /* clock_if: called at the start of every timer interrupt. */ |
|||||||
| 1556 | static void |
|||||||
| 1557 | mytimer_intr(struct clock_if *clk) |
|||||||
| 1558 | { |
|||||||
| 1559 | struct mytimer_softc * const sc = |
|||||||
| 1560 | container_of(clk, struct mytimer_softc, sc_clkif); |
|||||||
| 1561 | ||||||||
| 1562 | mytimer_hw_ack(sc); |
|||||||
| 1563 | if (!MYTIMER_AUTO_RELOAD) { |
|||||||
| 1564 | mytimer_hw_start(sc, sc->sc_reload); |
|||||||
| 1565 | } |
|||||||
| 1566 | } |
|||||||
| 1567 | ||||||||
| 1568 | /* clock_if: stop the timer. */ |
|||||||
| 1569 | static void |
|||||||
| 1570 | mytimer_disarm(struct clock_if *clk) |
|||||||
| 1571 | { |
|||||||
| 1572 | struct mytimer_softc * const sc = |
|||||||
| 1573 | container_of(clk, struct mytimer_softc, sc_clkif); |
|||||||
| 1574 | ||||||||
| 1575 | mytimer_hw_stop(sc); |
|||||||
| 1576 | } |
|||||||
| 1577 | ||||||||
| 1578 | /* |
|||||||
| 1579 | * ---------------------------------------------------------------------- |
|||||||
| 1580 | * Autoconfiguration |
|||||||
| 1581 | * ---------------------------------------------------------------------- |
|||||||
| 1582 | */ |
|||||||
| 1583 | ||||||||
| 1584 | static int |
|||||||
| 1585 | mytimer_match(device_t parent, cfdata_t cf, void *aux) |
|||||||
| 1586 | { |
|||||||
| 1587 | struct fdt_attach_args * const faa = aux; |
|||||||
| 1588 | ||||||||
| 1589 | return of_compatible_match(faa->faa_phandle, compat_data); |
|||||||
| 1590 | } |
|||||||
| 1591 | ||||||||
| 1592 | static void |
|||||||
| 1593 | mytimer_attach(device_t parent, device_t self, void *aux) |
|||||||
| 1594 | { |
|||||||
| 1595 | struct mytimer_softc * const sc = device_private(self); |
|||||||
| 1596 | struct fdt_attach_args * const faa = aux; |
|||||||
| 1597 | const int phandle = faa->faa_phandle; |
|||||||
| 1598 | bus_addr_t addr; |
|||||||
| 1599 | bus_size_t size; |
|||||||
| 1600 | struct clk *clk; |
|||||||
| 1601 | uint32_t freq; |
|||||||
| 1602 | ||||||||
| 1603 | sc->sc_dev = self; |
|||||||
| 1604 | sc->sc_bst = faa->faa_bst; |
|||||||
| 1605 | ||||||||
| 1606 | /* --- Registers ------------------------------------------------ */ |
|||||||
| 1607 | if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { |
|||||||
| 1608 | aprint_error(": couldn't get registers\n"); |
|||||||
| 1609 | return; |
|||||||
| 1610 | } |
|||||||
| 1611 | sc->sc_shift = fdtbus_get_reg_shift(phandle, 0); |
|||||||
| 1612 | if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { |
|||||||
| 1613 | aprint_error(": couldn't map registers\n"); |
|||||||
| 1614 | return; |
|||||||
| 1615 | } |
|||||||
| 1616 | ||||||||
| 1617 | /* |
|||||||
| 1618 | * --- Input clock ---------------------------------------------- |
|||||||
| 1619 | * Prefer a "clocks" reference to a clock node; fall back to a |
|||||||
| 1620 | * plain "clock-frequency" property. |
|||||||
| 1621 | */ |
|||||||
| 1622 | clk = fdtbus_clock_get_index(phandle, 0); |
|||||||
| 1623 | if (clk != NULL) { |
|||||||
| 1624 | freq = clk_get_rate(clk); |
|||||||
| 1625 | } else if (of_getprop_uint32(phandle, "clock-frequency", &freq) != 0) { |
|||||||
| 1626 | aprint_error(": no \"clocks\" or \"clock-frequency\"\n"); |
|||||||
| 1627 | return; |
|||||||
| 1628 | } |
|||||||
| 1629 | sc->sc_freq = freq / MYTIMER_PRESCALE; |
|||||||
| 1630 | if (sc->sc_freq == 0) { |
|||||||
| 1631 | aprint_error(": invalid clock rate %u Hz\n", freq); |
|||||||
| 1632 | return; |
|||||||
| 1633 | } |
|||||||
| 1634 | ||||||||
| 1635 | /* Make sure the timer is quiet until the clock code arms it. */ |
|||||||
| 1636 | mytimer_hw_stop(sc); |
|||||||
| 1637 | ||||||||
| 1638 | aprint_naive("\n"); |
|||||||
| 1639 | aprint_normal(": timer, %u Hz count rate\n", sc->sc_freq); |
|||||||
| 1640 | ||||||||
| 1641 | /* --- Register with the clock code ------------------------------ */ |
|||||||
| 1642 | sc->sc_clkif.clk_dev = self; |
|||||||
| 1643 | sc->sc_clkif.clk_init = mytimer_init; |
|||||||
| 1644 | sc->sc_clkif.clk_arm = mytimer_arm; |
|||||||
| 1645 | sc->sc_clkif.clk_intr = mytimer_intr; |
|||||||
| 1646 | sc->sc_clkif.clk_disarm = mytimer_disarm; |
|||||||
| 1647 | clock_attach(&sc->sc_clkif); |
|||||||
| 1648 | } |
|||||||
| 1649 | ||||||||
| 1650 | CFATTACH_DECL_NEW(mytimer, sizeof(struct mytimer_softc), |
|||||||
| 1651 | mytimer_match, mytimer_attach, NULL, NULL); |
|||||||
| 1652 | ``` |
|||||||