Commit 2dac7b

2026-10-30 07:29:49 Bob Green: -/-
project ideas/operating system/netbsd port.md ..
@@ 68,8 68,1103 @@
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.
- Myuart
- C
+ ```
+ /* $NetBSD$ */
+
+ /*-
+ * Copyright (c) 2026 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ /*
+ * myuart -- template driver for a simple, single-channel UART attached
+ * via Flattened Device Tree (FDT), with console and tty support.
+ *
+ * ======================================================================
+ * HOW TO USE THIS TEMPLATE
+ * ======================================================================
+ *
+ * 1. Copy this file to e.g. sys/arch/hb68k/dev/<chip>.c and replace
+ * "myuart" with your driver name throughout (keep it short; it
+ * becomes the device name, e.g. "myuart0").
+ *
+ * 2. Fill in the section marked "CHIP-SPECIFIC" below. Everything
+ * else is generic NetBSD plumbing that works for most UARTs and
+ * shouldn't need touching for a first version.
+ *
+ * 3. Hook it into the build:
+ *
+ * sys/arch/hb68k/conf/files.hb68k:
+ * device myuart: tty
+ * attach myuart at fdt
+ * file arch/hb68k/dev/myuart.c myuart
+ *
+ * sys/arch/hb68k/conf/majors.hb68k (pick an unused number < 144):
+ * device-major myuart char 14 myuart
+ *
+ * Kernel config (e.g. conf/SBC3):
+ * myuart* at fdt? pass 5
+ *
+ * 4. Describe the chip in the device tree:
+ *
+ * serial@XXXXXX {
+ * compatible = "vendor,mychip"; (must match compat_data[])
+ * reg = <0xXXXXXX 0x10>; (base, size of register block)
+ * reg-shift = <1>; (log2 of register spacing)
+ * clock-frequency = <1843200>; (UART input clock, Hz)
+ * interrupts = <3 0>; (IPL, vector; 0 = auto-vector)
+ * };
+ *
+ * To make it the console, point /chosen/stdout-path at it, e.g.
+ * stdout-path = "serial1:9600n8";
+ * with an alias "serial1" for the node. The platform code must
+ * provide a static mapping for its registers (as sbc3_machdep.c
+ * does for the I/O window), because the console is set up before
+ * the VM system is running.
+ *
+ * 5. Device nodes: MAKEDEV doesn't know about a new driver, so create
+ * them by hand on the target (char major from step 3, minor = unit):
+ * mknod /dev/ttyM0 c 14 0
+ * and add a line to /etc/ttys if you want a login prompt on it.
+ *
+ * ======================================================================
+ * HOW THE PIECES FIT TOGETHER
+ * ======================================================================
+ *
+ * Autoconfiguration myuart_match() / myuart_attach() are called when
+ * the FDT bus finds a node whose "compatible"
+ * matches compat_data[]. attach maps the registers,
+ * allocates a tty and hooks up the interrupt.
+ *
+ * Console myuart_cnattach() runs very early (from
+ * consinit(), before autoconfiguration) if the
+ * node is /chosen/stdout-path. The console uses
+ * polled I/O only: myuart_cngetc()/myuart_cnputc().
+ *
+ * Hard interrupt myuart_intr() runs at the device's hardware IPL.
+ * It must be quick: it moves received bytes into a
+ * ring buffer, feeds the transmitter, and schedules
+ * the soft interrupt. It must not call into the
+ * tty layer.
+ *
+ * Soft interrupt myuart_soft() runs later at IPL_SOFTSERIAL. It
+ * hands received bytes to the line discipline and,
+ * when a transmit burst finishes, asks the tty
+ * layer for more output.
+ *
+ * tty entry points open/close/read/write/ioctl/poll come from the
+ * cdevsw table; myuartstart() and myuartparam()
+ * are called by the tty layer to start output and
+ * to change speed/framing.
+ *
+ * Locking sc_lock is a spin mutex at the device's IPL. It
+ * protects the ring buffer, the transmit pointers,
+ * the interrupt-enable shadow and register access
+ * shared between the interrupt handler and the
+ * rest of the driver.
+ *
+ * This template deliberately leaves out modem control (carrier is
+ * assumed always present), hardware flow control, split speeds, and
+ * power management. Add them later if your hardware needs them.
+ */
+
+ #include <sys/cdefs.h>
+ __KERNEL_RCSID(0, "$NetBSD$");
+
+ #include <sys/param.h>
+ #include <sys/bus.h>
+ #include <sys/conf.h>
+ #include <sys/device.h>
+ #include <sys/fcntl.h>
+ #include <sys/intr.h>
+ #include <sys/kauth.h>
+ #include <sys/poll.h>
+ #include <sys/proc.h>
+ #include <sys/syslog.h>
+ #include <sys/systm.h>
+ #include <sys/tty.h>
+
+ #include <dev/cons.h>
+
+ #include <dev/fdt/fdtvar.h>
+ #include <dev/fdt/fdt_console.h>
+
+ /*
+ * ######################################################################
+ * CHIP-SPECIFIC: registers
+ * ######################################################################
+ *
+ * Replace these with your chip's register map. Offsets are register
+ * *numbers*; the real byte offset is (reg << reg-shift), so the same
+ * code works whether the chip sits on every byte, every other byte,
+ * or every fourth byte.
+ *
+ * The placeholder layout below is a typical simple UART:
+ * DATA read: receive holding register, write: transmit holding
+ * STATUS read-only status bits
+ * CTRL frame format and enables
+ * BAUD baud rate divisor (16 bits, split over two registers)
+ * IER interrupt enable bits
+ */
+ #define MYUART_REG_DATA 0
+ #define MYUART_REG_STATUS 1
+ #define MYUART_REG_CTRL 2
+ #define MYUART_REG_BAUDLO 3
+ #define MYUART_REG_BAUDHI 4
+ #define MYUART_REG_IER 5
+
+ /* STATUS bits */
+ #define MYUART_SR_RXRDY 0x01 /* a received byte is waiting */
+ #define MYUART_SR_TXRDY 0x02 /* transmit register is empty */
+ #define MYUART_SR_OVERRUN 0x10 /* receiver overrun */
+ #define MYUART_SR_PARITY 0x20 /* parity error on this byte */
+ #define MYUART_SR_FRAMING 0x40 /* framing error on this byte */
+ #define MYUART_SR_BREAK 0x80 /* break received */
+
+ /* CTRL bits */
+ #define MYUART_CTRL_ENABLE 0x01 /* enable receiver + transmitter */
+ #define MYUART_CTRL_8BIT 0x02 /* 8 data bits (else 7) */
+ #define MYUART_CTRL_PARENB 0x04 /* parity enable */
+ #define MYUART_CTRL_PARODD 0x08 /* odd parity (else even) */
+ #define MYUART_CTRL_2STOP 0x10 /* 2 stop bits (else 1) */
+
+ /* IER bits */
+ #define MYUART_IER_RX 0x01 /* interrupt when RXRDY */
+ #define MYUART_IER_TX 0x02 /* interrupt when TXRDY */
+
+ #define MYUART_DEFAULT_FREQ 1843200 /* if the DT has no clock-frequency */
+ #define MYUART_DEFAULT_SPEED 9600 /* if stdout-path has no speed */
+
+ /*
+ * Everything needed to talk to the registers. Shared by the console
+ * (which runs before the softc exists) and the attached device.
+ */
+ struct myuart_regs {
+ bus_space_tag_t ur_bst;
+ bus_space_handle_t ur_bsh;
+ u_int ur_shift; /* reg-shift */
+ u_int ur_freq; /* input clock, Hz */
+ };
+
+ static inline uint8_t
+ myuart_read(const struct myuart_regs *ur, u_int reg)
+ {
+ return bus_space_read_1(ur->ur_bst, ur->ur_bsh, reg << ur->ur_shift);
+ }
+
+ static inline void
+ myuart_write(const struct myuart_regs *ur, u_int reg, uint8_t val)
+ {
+ bus_space_write_1(ur->ur_bst, ur->ur_bsh, reg << ur->ur_shift, val);
+ }
+
+ /*
+ * ######################################################################
+ * CHIP-SPECIFIC: low-level operations
+ * ######################################################################
+ *
+ * These eight small functions are the whole hardware interface. The
+ * rest of the driver only ever touches the chip through them.
+ */
+
+ /* Is a received byte waiting? Returns the status bits for that byte. */
+ static inline bool
+ myuart_hw_rx_ready(const struct myuart_regs *ur, uint8_t *srp)
+ {
+ const uint8_t sr = myuart_read(ur, MYUART_REG_STATUS);
+
+ *srp = sr;
+ return (sr & MYUART_SR_RXRDY) != 0;
+ }
+
+ /* Read the received byte. Call only after rx_ready returned true. */
+ static inline uint8_t
+ myuart_hw_getc(const struct myuart_regs *ur)
+ {
+ return myuart_read(ur, MYUART_REG_DATA);
+ }
+
+ /* Can another byte be written to the transmitter? */
+ static inline bool
+ myuart_hw_tx_ready(const struct myuart_regs *ur)
+ {
+ return (myuart_read(ur, MYUART_REG_STATUS) & MYUART_SR_TXRDY) != 0;
+ }
+
+ /* Write one byte to the transmitter. */
+ static inline void
+ myuart_hw_putc(const struct myuart_regs *ur, uint8_t c)
+ {
+ myuart_write(ur, MYUART_REG_DATA, c);
+ }
+
+ /*
+ * Clear any error / break condition after it has been read. Many
+ * chips clear errors automatically when the data register is read;
+ * if yours does, leave this empty.
+ */
+ static inline void
+ myuart_hw_clear_errors(const struct myuart_regs *ur)
+ {
+ /* e.g. myuart_write(ur, MYUART_REG_CTRL, ... | RESET_ERRORS); */
+ }
+
+ /*
+ * Set the interrupt enables. `rx' and `tx' say which sources may
+ * interrupt. The transmit interrupt must only be enabled while there
+ * is something to send: most UARTs assert "transmitter empty"
+ * continuously, so leaving it enabled would interrupt forever.
+ */
+ static inline void
+ myuart_hw_set_intr(const struct myuart_regs *ur, bool rx, bool tx)
+ {
+ myuart_write(ur, MYUART_REG_IER,
+ (rx ? MYUART_IER_RX : 0) | (tx ? MYUART_IER_TX : 0));
+ }
+
+ /*
+ * Did this chip cause the current interrupt? Used to share an
+ * interrupt level with other devices. If your chip has an "interrupt
+ * pending" bit, test that instead.
+ */
+ static inline bool
+ myuart_hw_intr_pending(const struct myuart_regs *ur, bool tx_enabled)
+ {
+ const uint8_t sr = myuart_read(ur, MYUART_REG_STATUS);
+
+ return (sr & (MYUART_SR_RXRDY | MYUART_SR_BREAK)) != 0 ||
+ (tx_enabled && (sr & MYUART_SR_TXRDY) != 0);
+ }
+
+ /*
+ * Program speed and frame format, and enable the receiver and
+ * transmitter. Returns 0, or EINVAL if the speed or format can't be
+ * done. Called with interrupts for this device blocked.
+ *
+ * `cflag' uses the termios(4) bits: CSIZE (CS5..CS8), CSTOPB, PARENB,
+ * PARODD.
+ */
+ static int
+ myuart_hw_program(const struct myuart_regs *ur, int speed, tcflag_t cflag)
+ {
+ uint8_t ctrl = MYUART_CTRL_ENABLE;
+ u_int div;
+
+ if (speed <= 0) {
+ return EINVAL;
+ }
+
+ /*
+ * Example for a 16x-oversampling divisor UART:
+ * divisor = clock / (16 * speed), rounded to nearest.
+ * Reject rates more than 3% off.
+ */
+ div = (ur->ur_freq + 8 * speed) / (16 * speed);
+ if (div == 0 || div > 0xffff) {
+ return EINVAL;
+ }
+ const u_int actual = ur->ur_freq / (16 * div);
+ const u_int err = actual > (u_int)speed ?
+ actual - speed : speed - actual;
+ if (err * 100 > (u_int)speed * 3) {
+ return EINVAL;
+ }
+
+ switch (ISSET(cflag, CSIZE)) {
+ case CS8:
+ ctrl |= MYUART_CTRL_8BIT;
+ break;
+ case CS7:
+ break;
+ default:
+ return EINVAL; /* this example chip only does 7/8 */
+ }
+ if (ISSET(cflag, PARENB)) {
+ ctrl |= MYUART_CTRL_PARENB;
+ if (ISSET(cflag, PARODD)) {
+ ctrl |= MYUART_CTRL_PARODD;
+ }
+ }
+ if (ISSET(cflag, CSTOPB)) {
+ ctrl |= MYUART_CTRL_2STOP;
+ }
+
+ myuart_write(ur, MYUART_REG_CTRL, 0); /* quiesce */
+ myuart_write(ur, MYUART_REG_BAUDLO, div & 0xff);
+ myuart_write(ur, MYUART_REG_BAUDHI, (div >> 8) & 0xff);
+ myuart_write(ur, MYUART_REG_CTRL, ctrl);
+
+ return 0;
+ }
+
+ /*
+ * ######################################################################
+ * Device tree matching
+ * ######################################################################
+ *
+ * List every "compatible" string this driver handles.
+ */
+ static const struct device_compatible_entry compat_data[] = {
+ { .compat = "vendor,mychip" },
+ DEVICE_COMPAT_EOL
+ };
+
+ /*
+ * ######################################################################
+ * Generic driver below this line
+ * ######################################################################
+ */
+
+ #define MYUART_RBUF_SIZE 256 /* receive ring size, power of 2 */
+
+ struct myuart_softc {
+ device_t sc_dev;
+ struct myuart_regs sc_regs;
+ kmutex_t sc_lock; /* see "Locking" above */
+ void *sc_ih; /* hardware interrupt */
+ void *sc_si; /* soft interrupt */
+ struct tty *sc_tty;
+
+ bool sc_console; /* we are the console */
+ bool sc_rx_on; /* shadow of RX intr enable */
+ bool sc_tx_on; /* shadow of TX intr enable */
+
+ /* Receive ring: (status << 8) | data, filled by myuart_intr(). */
+ uint16_t sc_rbuf[MYUART_RBUF_SIZE];
+ u_int sc_rbput;
+ u_int sc_rbget;
+ u_int sc_rbovf; /* bytes dropped, ring full */
+
+ /* Transmit burst, set up by myuartstart(). */
+ u_char *sc_tba; /* next byte to send */
+ int sc_tbc; /* bytes left */
+ bool sc_tx_done; /* burst finished */
+ };
+
+ static int myuart_match(device_t, cfdata_t, void *);
+ static void myuart_attach(device_t, device_t, void *);
+
+ CFATTACH_DECL_NEW(myuart, sizeof(struct myuart_softc),
+ myuart_match, myuart_attach, NULL, NULL);
+
+ extern struct cfdriver myuart_cd;
+
+ static dev_type_open(myuartopen);
+ static dev_type_close(myuartclose);
+ static dev_type_read(myuartread);
+ static dev_type_write(myuartwrite);
+ static dev_type_ioctl(myuartioctl);
+ static dev_type_stop(myuartstop);
+ static dev_type_tty(myuarttty);
+ static dev_type_poll(myuartpoll);
+
+ /* The character device switch; its major number comes from majors.*. */
+ const struct cdevsw myuart_cdevsw = {
+ .d_open = myuartopen,
+ .d_close = myuartclose,
+ .d_read = myuartread,
+ .d_write = myuartwrite,
+ .d_ioctl = myuartioctl,
+ .d_stop = myuartstop,
+ .d_tty = myuarttty,
+ .d_poll = myuartpoll,
+ .d_mmap = nommap,
+ .d_kqfilter = ttykqfilter,
+ .d_discard = nodiscard,
+ .d_flag = D_TTY
+ };
+
+ static void myuartstart(struct tty *);
+ static int myuartparam(struct tty *, struct termios *);
+ static int myuart_intr(void *);
+ static void myuart_soft(void *);
+
+ /* Call with sc_lock held: update the hardware interrupt enables. */
+ static void
+ myuart_update_intr(struct myuart_softc *sc)
+ {
+ KASSERT(mutex_owned(&sc->sc_lock));
+ myuart_hw_set_intr(&sc->sc_regs, sc->sc_rx_on, sc->sc_tx_on);
+ }
+
+ /*
+ * ----------------------------------------------------------------------
+ * Console
+ * ----------------------------------------------------------------------
+ *
+ * The console is set up long before autoconfiguration, so it can't use
+ * the softc. Its state lives in this static structure instead.
+ */
+ static struct {
+ struct myuart_regs regs;
+ bus_addr_t addr; /* to recognise ourselves in attach */
+ int speed;
+ tcflag_t cflag;
+ bool active;
+ } myuart_cons;
+
+ static cnm_state_t myuart_cnm_state; /* console "magic" (break -> DDB) */
+
+ static int
+ myuart_cngetc(dev_t dev)
+ {
+ uint8_t sr;
+ int s, c;
+
+ s = splhigh();
+ while (!myuart_hw_rx_ready(&myuart_cons.regs, &sr)) {
+ continue;
+ }
+ c = myuart_hw_getc(&myuart_cons.regs);
+ splx(s);
+
+ return c;
+ }
+
+ static void
+ myuart_cnputc(dev_t dev, int c)
+ {
+ int s;
+
+ s = splhigh();
+ while (!myuart_hw_tx_ready(&myuart_cons.regs)) {
+ continue;
+ }
+ myuart_hw_putc(&myuart_cons.regs, c);
+ splx(s);
+ }
+
+ static void
+ myuart_cnpollc(dev_t dev, int on)
+ {
+ /* Nothing to do: the console routines always poll. */
+ }
+
+ static struct consdev myuart_consdev = {
+ .cn_getc = myuart_cngetc,
+ .cn_putc = myuart_cnputc,
+ .cn_pollc = myuart_cnpollc,
+ .cn_dev = NODEV, /* filled in by myuart_attach() */
+ .cn_pri = CN_NORMAL,
+ };
+
+ static int
+ myuart_console_match(int phandle)
+ {
+ return of_compatible_match(phandle, compat_data);
+ }
+
+ /*
+ * Called from consinit() when /chosen/stdout-path points at one of our
+ * nodes. `uart_freq' is the node's clock-frequency (or the platform's
+ * fallback).
+ */
+ static void
+ myuart_cnattach(struct fdt_attach_args *faa, u_int uart_freq)
+ {
+ const int phandle = faa->faa_phandle;
+ struct myuart_regs * const ur = &myuart_cons.regs;
+ bus_addr_t addr;
+ bus_size_t size;
+
+ if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+ return;
+ }
+
+ ur->ur_bst = faa->faa_bst;
+ ur->ur_shift = fdtbus_get_reg_shift(phandle, 0);
+ ur->ur_freq = uart_freq ? uart_freq : MYUART_DEFAULT_FREQ;
+
+ /* This must hit a static mapping: UVM isn't running yet. */
+ if (bus_space_map(ur->ur_bst, addr, size, 0, &ur->ur_bsh) != 0) {
+ return;
+ }
+
+ /* Speed and format from stdout-path, e.g. "serial0:9600n8". */
+ myuart_cons.speed = fdtbus_get_stdout_speed();
+ if (myuart_cons.speed < 0) {
+ myuart_cons.speed = MYUART_DEFAULT_SPEED;
+ }
+ myuart_cons.cflag = fdtbus_get_stdout_flags();
+
+ myuart_hw_set_intr(ur, false, false);
+ if (myuart_hw_program(ur, myuart_cons.speed,
+ myuart_cons.cflag) != 0) {
+ /* Fall back to something sane rather than staying silent. */
+ myuart_cons.speed = MYUART_DEFAULT_SPEED;
+ myuart_cons.cflag = CS8 | CREAD | CLOCAL;
+ if (myuart_hw_program(ur, myuart_cons.speed,
+ myuart_cons.cflag) != 0) {
+ return;
+ }
+ }
+
+ myuart_cons.addr = addr;
+ myuart_cons.active = true;
+ cn_tab = &myuart_consdev;
+ }
+
+ static const struct fdt_console myuart_fdt_console = {
+ .match = myuart_console_match,
+ .consinit = myuart_cnattach,
+ };
+
+ FDT_CONSOLE(myuart, &myuart_fdt_console);
+
+ /*
+ * ----------------------------------------------------------------------
+ * Autoconfiguration
+ * ----------------------------------------------------------------------
+ */
+
+ static int
+ myuart_match(device_t parent, cfdata_t cf, void *aux)
+ {
+ struct fdt_attach_args * const faa = aux;
+
+ return of_compatible_match(faa->faa_phandle, compat_data);
+ }
+
+ static void
+ myuart_attach(device_t parent, device_t self, void *aux)
+ {
+ struct myuart_softc * const sc = device_private(self);
+ struct fdt_attach_args * const faa = aux;
+ const int phandle = faa->faa_phandle;
+ struct myuart_regs * const ur = &sc->sc_regs;
+ char intrstr[128];
+ bus_addr_t addr;
+ bus_size_t size;
+ struct tty *tp;
+
+ sc->sc_dev = self;
+
+ /* --- Registers ------------------------------------------------ */
+ if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+ aprint_error(": couldn't get registers\n");
+ return;
+ }
+ ur->ur_bst = faa->faa_bst;
+ ur->ur_shift = fdtbus_get_reg_shift(phandle, 0);
+ if (of_getprop_uint32(phandle, "clock-frequency", &ur->ur_freq) != 0) {
+ ur->ur_freq = MYUART_DEFAULT_FREQ;
+ }
+ if (bus_space_map(ur->ur_bst, addr, size, 0, &ur->ur_bsh) != 0) {
+ aprint_error(": couldn't map registers\n");
+ return;
+ }
+
+ sc->sc_console = myuart_cons.active && myuart_cons.addr == addr;
+
+ /*
+ * The lock is a spin mutex at IPL_VM, which on hb68k blocks
+ * hardware levels 1-5. So the device's interrupt level (first
+ * cell of "interrupts") must be 5 or lower.
+ */
+ mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_VM);
+
+ aprint_naive("\n");
+ aprint_normal(": UART, %u Hz clock%s\n", ur->ur_freq,
+ sc->sc_console ? ", console" : "");
+
+ /* --- Quiet the chip (but leave the console's settings alone) - */
+ mutex_spin_enter(&sc->sc_lock);
+ sc->sc_rx_on = sc->sc_tx_on = false;
+ myuart_update_intr(sc);
+ mutex_spin_exit(&sc->sc_lock);
+
+ /* --- tty ------------------------------------------------------ */
+ tp = tty_alloc();
+ tp->t_oproc = myuartstart; /* tty layer: "start output" */
+ tp->t_param = myuartparam; /* tty layer: "change settings" */
+ tty_attach(tp);
+ sc->sc_tty = tp;
+
+ if (sc->sc_console) {
+ /* Tell the console layer which /dev entry is the console. */
+ myuart_consdev.cn_dev = makedev(
+ cdevsw_lookup_major(&myuart_cdevsw), device_unit(self));
+ aprint_normal_dev(self, "console\n");
+ }
+
+ /* --- Interrupts ----------------------------------------------- */
+ sc->sc_si = softint_establish(SOFTINT_SERIAL, myuart_soft, sc);
+ if (sc->sc_si == NULL) {
+ aprint_error_dev(self, "couldn't establish soft interrupt\n");
+ return;
+ }
+
+ if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
+ aprint_error_dev(self, "couldn't decode interrupt\n");
+ return;
+ }
+ sc->sc_ih = fdtbus_intr_establish_xname(phandle, 0, IPL_VM,
+ FDT_INTR_MPSAFE, myuart_intr, sc, device_xname(self));
+ if (sc->sc_ih == NULL) {
+ aprint_error_dev(self,
+ "couldn't establish interrupt at %s\n", intrstr);
+ return;
+ }
+ aprint_normal_dev(self, "interrupting at %s\n", intrstr);
+
+ /*
+ * The console listens all the time, so that a BREAK (or the
+ * configured magic sequence) can drop into DDB even when no
+ * process has the tty open.
+ */
+ if (sc->sc_console) {
+ cn_init_magic(&myuart_cnm_state);
+ cn_set_magic("\047\001"); /* BREAK */
+
+ mutex_spin_enter(&sc->sc_lock);
+ sc->sc_rx_on = true;
+ myuart_update_intr(sc);
+ mutex_spin_exit(&sc->sc_lock);
+ }
+ }
+
+ /*
+ * ----------------------------------------------------------------------
+ * Hardware interrupt
+ * ----------------------------------------------------------------------
+ *
+ * Runs at the device's IPL with sc_lock held. Keep it short.
+ */
+
+ static void
+ myuart_rxintr(struct myuart_softc *sc)
+ {
+ struct myuart_regs * const ur = &sc->sc_regs;
+ const bool open = ISSET(sc->sc_tty->t_state, TS_ISOPEN);
+ uint8_t sr, c;
+
+ while (myuart_hw_rx_ready(ur, &sr)) {
+ c = myuart_hw_getc(ur);
+
+ if (sr & (MYUART_SR_OVERRUN | MYUART_SR_PARITY |
+ MYUART_SR_FRAMING | MYUART_SR_BREAK)) {
+ myuart_hw_clear_errors(ur);
+ }
+
+ /* Console magic: BREAK (or a byte sequence) -> DDB. */
+ if (sc->sc_console) {
+ if (sr & MYUART_SR_BREAK) {
+ cn_check_magic(myuart_consdev.cn_dev,
+ CNC_BREAK, myuart_cnm_state);
+ } else {
+ cn_check_magic(myuart_consdev.cn_dev,
+ c, myuart_cnm_state);
+ }
+ }
+
+ if (!open || (sr & MYUART_SR_BREAK)) {
+ continue; /* nobody listening, or a BREAK */
+ }
+
+ if (sc->sc_rbput - sc->sc_rbget >= MYUART_RBUF_SIZE) {
+ sc->sc_rbovf++; /* ring full: drop it */
+ continue;
+ }
+ sc->sc_rbuf[sc->sc_rbput++ & (MYUART_RBUF_SIZE - 1)] =
+ ((uint16_t)sr << 8) | c;
+ }
+ }
+
+ static void
+ myuart_txintr(struct myuart_softc *sc)
+ {
+ struct myuart_regs * const ur = &sc->sc_regs;
+
+ /* Fill the transmitter (or its FIFO) as far as it will go. */
+ while (sc->sc_tbc > 0 && myuart_hw_tx_ready(ur)) {
+ myuart_hw_putc(ur, *sc->sc_tba++);
+ sc->sc_tbc--;
+ }
+
+ if (sc->sc_tbc == 0) {
+ /* Burst done: stop TX interrupts, let the softint refill. */
+ sc->sc_tx_on = false;
+ myuart_update_intr(sc);
+ sc->sc_tx_done = true;
+ }
+ }
+
+ static int
+ myuart_intr(void *arg)
+ {
+ struct myuart_softc * const sc = arg;
+
+ mutex_spin_enter(&sc->sc_lock);
+ if (!myuart_hw_intr_pending(&sc->sc_regs, sc->sc_tx_on)) {
+ mutex_spin_exit(&sc->sc_lock);
+ return 0; /* not ours (shared interrupt level) */
+ }
+
+ myuart_rxintr(sc);
+ if (sc->sc_tx_on) {
+ myuart_txintr(sc);
+ }
+ mutex_spin_exit(&sc->sc_lock);
+
+ softint_schedule(sc->sc_si);
+ return 1;
+ }
+
+ /*
+ * ----------------------------------------------------------------------
+ * Soft interrupt
+ * ----------------------------------------------------------------------
+ *
+ * Runs at IPL_SOFTSERIAL, where it's safe to call the tty layer.
+ */
+ static void
+ myuart_soft(void *arg)
+ {
+ struct myuart_softc * const sc = arg;
+ struct tty * const tp = sc->sc_tty;
+ uint16_t ent;
+ u_int ovf;
+ bool tx_done;
+ int c;
+
+ /* Report ring-buffer overflows (not too often: use log(9)). */
+ mutex_spin_enter(&sc->sc_lock);
+ ovf = sc->sc_rbovf;
+ sc->sc_rbovf = 0;
+ mutex_spin_exit(&sc->sc_lock);
+ if (ovf != 0) {
+ log(LOG_WARNING, "%s: %u receive overflow(s)\n",
+ device_xname(sc->sc_dev), ovf);
+ }
+
+ /* Hand received bytes to the line discipline. */
+ for (;;) {
+ mutex_spin_enter(&sc->sc_lock);
+ if (sc->sc_rbget == sc->sc_rbput) {
+ mutex_spin_exit(&sc->sc_lock);
+ break;
+ }
+ ent = sc->sc_rbuf[sc->sc_rbget++ & (MYUART_RBUF_SIZE - 1)];
+ mutex_spin_exit(&sc->sc_lock);
+
+ c = ent & 0xff;
+ if (ent & (MYUART_SR_FRAMING << 8)) {
+ SET(c, TTY_FE);
+ }
+ if (ent & (MYUART_SR_PARITY << 8)) {
+ SET(c, TTY_PE);
+ }
+ if (ISSET(tp->t_state, TS_ISOPEN)) {
+ (*tp->t_linesw->l_rint)(c, tp);
+ }
+ }
+
+ /* If a transmit burst finished, release it and ask for more. */
+ mutex_spin_enter(&sc->sc_lock);
+ tx_done = sc->sc_tx_done;
+ sc->sc_tx_done = false;
+ mutex_spin_exit(&sc->sc_lock);
+
+ if (tx_done) {
+ CLR(tp->t_state, TS_BUSY);
+ if (ISSET(tp->t_state, TS_FLUSH)) {
+ CLR(tp->t_state, TS_FLUSH);
+ } else {
+ ndflush(&tp->t_outq,
+ (int)(sc->sc_tba - tp->t_outq.c_cf));
+ }
+ (*tp->t_linesw->l_start)(tp);
+ }
+ }
+
+ /*
+ * ----------------------------------------------------------------------
+ * tty layer callbacks
+ * ----------------------------------------------------------------------
+ */
+
+ static struct myuart_softc *
+ myuart_lookup(dev_t dev)
+ {
+ struct myuart_softc *sc;
+
+ sc = device_lookup_private(&myuart_cd, TTUNIT(dev));
+ if (sc == NULL || sc->sc_si == NULL || sc->sc_ih == NULL) {
+ return NULL; /* no such unit, or attach failed */
+ }
+ return sc;
+ }
+
+ /*
+ * Start output: take a contiguous chunk of the tty's output queue and
+ * let the transmit interrupt send it.
+ */
+ static void
+ myuartstart(struct tty *tp)
+ {
+ struct myuart_softc * const sc = myuart_lookup(tp->t_dev);
+ int s;
+
+ if (sc == NULL) {
+ return;
+ }
+
+ s = spltty();
+ if (ISSET(tp->t_state, TS_BUSY | TS_TIMEOUT | TS_TTSTOP)) {
+ goto out; /* already sending, or stopped (^S) */
+ }
+ if (!ttypull(tp)) {
+ goto out; /* nothing to send */
+ }
+
+ mutex_spin_enter(&sc->sc_lock);
+ sc->sc_tba = tp->t_outq.c_cf;
+ sc->sc_tbc = ndqb(&tp->t_outq, 0);
+ SET(tp->t_state, TS_BUSY);
+ sc->sc_tx_on = true; /* interrupts as soon as TX is empty */
+ myuart_update_intr(sc);
+ mutex_spin_exit(&sc->sc_lock);
+ out:
+ splx(s);
+ }
+
+ /* Stop output (e.g. on ^S or a flush). */
+ static void
+ myuartstop(struct tty *tp, int flag)
+ {
+ struct myuart_softc * const sc = myuart_lookup(tp->t_dev);
+
+ if (sc == NULL) {
+ return;
+ }
+
+ mutex_spin_enter(&sc->sc_lock);
+ if (ISSET(tp->t_state, TS_BUSY)) {
+ sc->sc_tbc = 0; /* finish after the current byte */
+ if (!ISSET(tp->t_state, TS_TTSTOP)) {
+ SET(tp->t_state, TS_FLUSH);
+ }
+ }
+ mutex_spin_exit(&sc->sc_lock);
+ }
+
+ /* Change speed / framing (stty, tcsetattr). */
+ static int
+ myuartparam(struct tty *tp, struct termios *t)
+ {
+ struct myuart_softc * const sc = myuart_lookup(tp->t_dev);
+ int error;
+
+ if (sc == NULL) {
+ return ENXIO;
+ }
+
+ /* Unsupported in this template: split speeds, flow control. */
+ if (t->c_ispeed != 0 && t->c_ispeed != t->c_ospeed) {
+ return EINVAL;
+ }
+ if (ISSET(t->c_cflag, CRTSCTS | MDMBUF)) {
+ return EINVAL;
+ }
+
+ mutex_spin_enter(&sc->sc_lock);
+ error = myuart_hw_program(&sc->sc_regs, t->c_ospeed, t->c_cflag);
+ mutex_spin_exit(&sc->sc_lock);
+ if (error) {
+ return error;
+ }
+
+ tp->t_ispeed = 0;
+ tp->t_ospeed = t->c_ospeed;
+ tp->t_cflag = t->c_cflag;
+
+ /* No modem control: carrier is always present. */
+ (void)(*tp->t_linesw->l_modem)(tp, 1);
+
+ return 0;
+ }
+
+ /*
+ * ----------------------------------------------------------------------
+ * cdevsw entry points
+ * ----------------------------------------------------------------------
+ */
+
+ static int
+ myuartopen(dev_t dev, int flag, int mode, struct lwp *l)
+ {
+ struct myuart_softc * const sc = myuart_lookup(dev);
+ struct tty *tp;
+ int error;
+
+ if (sc == NULL) {
+ return ENXIO;
+ }
+ tp = sc->sc_tty;
+
+ if (kauth_authorize_device_tty(l->l_cred, KAUTH_DEVICE_TTY_OPEN, tp)) {
+ return EBUSY;
+ }
+
+ /* First open: set defaults and turn on receive interrupts. */
+ if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) {
+ struct termios t;
+
+ tp->t_dev = dev;
+
+ t.c_ispeed = 0;
+ if (sc->sc_console) {
+ t.c_ospeed = myuart_cons.speed;
+ t.c_cflag = myuart_cons.cflag;
+ } else {
+ t.c_ospeed = TTYDEF_SPEED;
+ t.c_cflag = TTYDEF_CFLAG;
+ }
+ SET(t.c_cflag, CLOCAL);
+ CLR(t.c_cflag, CRTSCTS | MDMBUF);
+
+ tp->t_ospeed = 0; /* force myuartparam() to program */
+ (void)myuartparam(tp, &t);
+ tp->t_iflag = TTYDEF_IFLAG;
+ tp->t_oflag = TTYDEF_OFLAG;
+ tp->t_lflag = TTYDEF_LFLAG;
+ ttychars(tp);
+ ttsetwater(tp);
+
+ mutex_spin_enter(&sc->sc_lock);
+ sc->sc_rbget = sc->sc_rbput = 0;
+ sc->sc_rx_on = true;
+ myuart_update_intr(sc);
+ mutex_spin_exit(&sc->sc_lock);
+ }
+
+ error = ttyopen(tp, TTDIALOUT(dev), ISSET(flag, O_NONBLOCK));
+ if (error == 0) {
+ error = (*tp->t_linesw->l_open)(dev, tp);
+ }
+ return error;
+ }
+
+ static int
+ myuartclose(dev_t dev, int flag, int mode, struct lwp *l)
+ {
+ struct myuart_softc * const sc = myuart_lookup(dev);
+ struct tty *tp;
+
+ if (sc == NULL) {
+ return 0;
+ }
+ tp = sc->sc_tty;
+
+ if (!ISSET(tp->t_state, TS_ISOPEN)) {
+ return 0;
+ }
+ (*tp->t_linesw->l_close)(tp, flag);
+ ttyclose(tp);
+
+ /* Last close: go quiet (the console keeps listening for BREAK). */
+ if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0 &&
+ !sc->sc_console) {
+ mutex_spin_enter(&sc->sc_lock);
+ sc->sc_rx_on = sc->sc_tx_on = false;
+ myuart_update_intr(sc);
+ mutex_spin_exit(&sc->sc_lock);
+ }
+ return 0;
+ }
+
+ static int
+ myuartread(dev_t dev, struct uio *uio, int flag)
+ {
+ struct myuart_softc * const sc = myuart_lookup(dev);
+
+ if (sc == NULL) {
+ return ENXIO;
+ }
+ return (*sc->sc_tty->t_linesw->l_read)(sc->sc_tty, uio, flag);
+ }
+
+ static int
+ myuartwrite(dev_t dev, struct uio *uio, int flag)
+ {
+ struct myuart_softc * const sc = myuart_lookup(dev);
+
+ if (sc == NULL) {
+ return ENXIO;
+ }
+ return (*sc->sc_tty->t_linesw->l_write)(sc->sc_tty, uio, flag);
+ }
+
+ static int
+ myuartpoll(dev_t dev, int events, struct lwp *l)
+ {
+ struct myuart_softc * const sc = myuart_lookup(dev);
+
+ if (sc == NULL) {
+ return POLLHUP;
+ }
+ return (*sc->sc_tty->t_linesw->l_poll)(sc->sc_tty, events, l);
+ }
+
+ static struct tty *
+ myuarttty(dev_t dev)
+ {
+ struct myuart_softc * const sc = myuart_lookup(dev);
+
+ return sc != NULL ? sc->sc_tty : NULL;
+ }
+
+ static int
+ myuartioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
+ {
+ struct myuart_softc * const sc = myuart_lookup(dev);
+ struct tty *tp;
+ int error;
+
+ if (sc == NULL) {
+ return ENXIO;
+ }
+ tp = sc->sc_tty;
+
+ /* Line discipline first, then the generic tty ioctls. */
+ error = (*tp->t_linesw->l_ioctl)(tp, cmd, data, flag, l);
+ if (error != EPASSTHROUGH) {
+ return error;
+ }
+ error = ttioctl(tp, cmd, data, flag, l);
+ if (error != EPASSTHROUGH) {
+ return error;
+ }
+
+ /*
+ * Device-specific ioctls go here, e.g. TIOCSBRK / TIOCCBRK to
+ * start and stop sending a break, or TIOCMGET / TIOCMSET for
+ * modem control lines.
+ */
+ return EPASSTHROUGH;
+ }
+ ```
### Timer
@@ 117,5 1212,437 @@
- 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.
- Randomising the statclock interval. hb68k’s clock code doesn’t do this yet either.
- Mytimer
- C
+ ```
+ /* $NetBSD$ */
+
+ /*-
+ * Copyright (c) 2026 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ /*
+ * mytimer -- template driver for a periodic hardware timer used as a
+ * NetBSD/hb68k system clock, attached via Flattened Device Tree (FDT).
+ *
+ * ======================================================================
+ * HOW TO USE THIS TEMPLATE
+ * ======================================================================
+ *
+ * 1. Copy this file to e.g. sys/arch/hb68k/dev/<chip>_timer.c and
+ * replace "mytimer" with your driver name throughout (it becomes
+ * the device name, e.g. "mytimer0").
+ *
+ * 2. Fill in the sections marked "CHIP-SPECIFIC" below. Everything
+ * else is generic hb68k plumbing.
+ *
+ * 3. Hook it into the build:
+ *
+ * sys/arch/hb68k/conf/files.hb68k:
+ * device mytimer
+ * attach mytimer at fdt
+ * file arch/hb68k/dev/mytimer.c mytimer
+ *
+ * Kernel config (e.g. conf/SBC3). Timers attach after the
+ * interrupt controller (pass 2), hence pass 3:
+ * mytimer* at fdt? pass 3
+ *
+ * A timer has no /dev entry, so it needs no major number.
+ *
+ * 4. Describe the chip in the device tree:
+ *
+ * timer@XXXXXX {
+ * compatible = "vendor,mytimer"; (must match compat_data[])
+ * reg = <0xXXXXXX 0x10>; (base, size of register block)
+ * reg-shift = <1>; (log2 of register spacing)
+ * clocks = <&osc>; (a fixed-clock node), OR
+ * clock-frequency = <1000000>; (input clock in Hz)
+ * interrupts = <6 0>; (IPL, vector; 0 = auto-vector)
+ * };
+ *
+ * and say which job it does under /chosen, using an alias:
+ *
+ * chosen {
+ * netbsd,hardclock = "timer0"; (the main system tick)
+ * netbsd,statclock = "timer1"; (optional, see below)
+ * };
+ * aliases {
+ * timer0 = "/obio/timer@XXXXXX";
+ * };
+ *
+ * Without those properties, the first timer to attach becomes the
+ * hardclock and the second (if any) the statclock.
+ *
+ * ======================================================================
+ * HOW hb68k USES A TIMER
+ * ======================================================================
+ *
+ * The machine-independent clock code in sys/arch/hb68k/hb68k/clock.c
+ * owns the policy; a timer driver only provides mechanism, through the
+ * five-member `struct clock_if' (machine/clockvar.h):
+ *
+ * clk_dev The timer's device_t.
+ *
+ * clk_init Called once, from cpu_initclocks(), late in boot. Must
+ * establish the hardware interrupt with the handler it is
+ * given (at IPL_SCHED) and return 0, or an error.
+ *
+ * clk_arm Start the timer interrupting periodically, every
+ * `interval_us' microseconds (10000 for the usual
+ * hz = 100). Called once, right after clk_init.
+ *
+ * clk_intr Called by the clock code at the start of every timer
+ * interrupt. Acknowledge the interrupt here and, if the
+ * chip doesn't reload by itself, restart the count.
+ *
+ * clk_disarm Stop the timer (used at shutdown / error paths).
+ *
+ * The driver registers itself with clock_attach() at the end of
+ * attach. The clock code then decides which role the timer plays:
+ *
+ * hardclock Drives the system tick: timekeeping, scheduling,
+ * callouts. Every system needs one.
+ *
+ * statclock Optional second timer for CPU-usage statistics and
+ * profiling. With only one timer, the hardclock does both
+ * jobs, and boot prints "No statclock; using hardclock."
+ *
+ * Interrupt level: the hardclock runs at IPL_SCHED, which on hb68k is
+ * hardware level 6, so wire the timer to IPL 6 and use 6 as the first
+ * cell of "interrupts". A lower level works, but the clock interrupt
+ * can then be delayed or blocked by other devices and by the soft
+ * interrupt level (1), so the system time will drift.
+ *
+ * hz must divide 1000000 exactly (the clock code checks this): use
+ * 50, 100, 200, 250, 400, 500 or 1000. Set it in the kernel config
+ * with "options HZ=..." if the default of 100 doesn't suit the
+ * hardware.
+ */
+
+ #include <sys/cdefs.h>
+ __KERNEL_RCSID(0, "$NetBSD$");
+
+ #include <sys/param.h>
+ #include <sys/bus.h>
+ #include <sys/device.h>
+ #include <sys/intr.h>
+ #include <sys/systm.h>
+
+ #include <machine/clockvar.h>
+
+ #include <dev/fdt/fdtvar.h>
+
+ /*
+ * ######################################################################
+ * CHIP-SPECIFIC: registers
+ * ######################################################################
+ *
+ * Replace these with your chip's register map. Offsets are register
+ * *numbers*; the real byte offset is (reg << reg-shift).
+ *
+ * The placeholder layout below is a typical simple down-counter:
+ * CTRL enable, interrupt enable, auto-reload
+ * STATUS "counter reached zero" flag, write 1 to clear
+ * RELOAD0..2 24-bit reload (preload) value, low byte first
+ */
+ #define MYTIMER_REG_CTRL 0
+ #define MYTIMER_REG_STATUS 1
+ #define MYTIMER_REG_RELOAD0 2 /* bits 7..0 */
+ #define MYTIMER_REG_RELOAD1 3 /* bits 15..8 */
+ #define MYTIMER_REG_RELOAD2 4 /* bits 23..16 */
+
+ /* CTRL bits */
+ #define MYTIMER_CTRL_RUN 0x01 /* counter running */
+ #define MYTIMER_CTRL_IRQEN 0x02 /* interrupt on reaching zero */
+ #define MYTIMER_CTRL_RELOAD 0x04 /* reload from RELOAD on zero */
+
+ /* STATUS bits */
+ #define MYTIMER_SR_ZERO 0x01 /* counter hit zero (write 1 = clear) */
+
+ /*
+ * Counter limits. MAX is the largest value the reload register can
+ * hold; PRESCALE is any fixed divider between the input clock and the
+ * counter (1 if none). Together with the input clock these decide the
+ * longest interval the timer can produce: MAX * PRESCALE / freq.
+ */
+ #define MYTIMER_COUNT_MAX 0x00ffffffU
+ #define MYTIMER_PRESCALE 1
+
+ /*
+ * Set to true if the counter reloads itself when it reaches zero
+ * (most timers can). If false, clk_intr() restarts it by hand; the
+ * time spent getting to the interrupt handler is then lost on every
+ * tick, and the clock runs slightly slow.
+ */
+ #define MYTIMER_AUTO_RELOAD true
+
+ /*
+ * ######################################################################
+ * Device tree matching
+ * ######################################################################
+ */
+ static const struct device_compatible_entry compat_data[] = {
+ { .compat = "vendor,mytimer" },
+ DEVICE_COMPAT_EOL
+ };
+
+ /*
+ * ######################################################################
+ * Driver state
+ * ######################################################################
+ */
+ struct mytimer_softc {
+ device_t sc_dev;
+ bus_space_tag_t sc_bst;
+ bus_space_handle_t sc_bsh;
+ u_int sc_shift; /* reg-shift */
+ uint32_t sc_freq; /* counter decrement rate, Hz */
+ uint32_t sc_reload; /* count for one interval */
+ void *sc_ih; /* interrupt handle */
+ struct clock_if sc_clkif; /* our interface to clock.c */
+ };
+
+ static inline uint8_t
+ mytimer_read(struct mytimer_softc *sc, u_int reg)
+ {
+ return bus_space_read_1(sc->sc_bst, sc->sc_bsh, reg << sc->sc_shift);
+ }
+
+ static inline void
+ mytimer_write(struct mytimer_softc *sc, u_int reg, uint8_t val)
+ {
+ bus_space_write_1(sc->sc_bst, sc->sc_bsh, reg << sc->sc_shift, val);
+ }
+
+ /*
+ * ######################################################################
+ * CHIP-SPECIFIC: low-level operations
+ * ######################################################################
+ *
+ * The only code that touches the hardware.
+ */
+
+ /* Stop the counter and clear any pending interrupt. */
+ static void
+ mytimer_hw_stop(struct mytimer_softc *sc)
+ {
+ mytimer_write(sc, MYTIMER_REG_CTRL, 0);
+ mytimer_write(sc, MYTIMER_REG_STATUS, MYTIMER_SR_ZERO);
+ }
+
+ /*
+ * Load `count' and start counting down, interrupting at zero. With
+ * MYTIMER_AUTO_RELOAD the chip then repeats on its own.
+ */
+ static void
+ mytimer_hw_start(struct mytimer_softc *sc, uint32_t count)
+ {
+ mytimer_write(sc, MYTIMER_REG_RELOAD0, count & 0xff);
+ mytimer_write(sc, MYTIMER_REG_RELOAD1, (count >> 8) & 0xff);
+ mytimer_write(sc, MYTIMER_REG_RELOAD2, (count >> 16) & 0xff);
+ mytimer_write(sc, MYTIMER_REG_CTRL,
+ MYTIMER_CTRL_RUN | MYTIMER_CTRL_IRQEN |
+ (MYTIMER_AUTO_RELOAD ? MYTIMER_CTRL_RELOAD : 0));
+ }
+
+ /*
+ * Acknowledge the interrupt so the chip drops its request line. If
+ * this is forgotten, the CPU takes the interrupt again the moment the
+ * handler returns, and the machine appears to hang.
+ */
+ static void
+ mytimer_hw_ack(struct mytimer_softc *sc)
+ {
+ mytimer_write(sc, MYTIMER_REG_STATUS, MYTIMER_SR_ZERO);
+ }
+
+ /*
+ * ######################################################################
+ * Generic driver below this line
+ * ######################################################################
+ */
+
+ /* Convert an interval in microseconds to counter ticks. */
+ static uint32_t
+ mytimer_us_to_ticks(struct mytimer_softc *sc, unsigned int interval_us)
+ {
+ const uint64_t ticks =
+ ((uint64_t)sc->sc_freq * interval_us + 500000) / 1000000;
+
+ if (ticks == 0 || ticks > MYTIMER_COUNT_MAX) {
+ /*
+ * The input clock is too slow (ticks == 0) or too fast
+ * (ticks too big) for this interval. Change the clock,
+ * the prescaler, or HZ.
+ */
+ panic("%s: can't make a %u us interval from %u Hz "
+ "(ticks=%llu)", device_xname(sc->sc_dev), interval_us,
+ sc->sc_freq, (unsigned long long)ticks);
+ }
+
+ return (uint32_t)ticks;
+ }
+
+ /* clock_if: establish our interrupt. `intrfunc' is clock.c's handler. */
+ static int
+ mytimer_init(struct clock_if *clk, int (*intrfunc)(void *))
+ {
+ struct mytimer_softc * const sc =
+ container_of(clk, struct mytimer_softc, sc_clkif);
+ const int phandle = devhandle_to_of(device_handle(sc->sc_dev));
+ char intrstr[128];
+
+ if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
+ aprint_error_dev(sc->sc_dev, "couldn't decode interrupt\n");
+ return ENXIO;
+ }
+
+ /*
+ * The handler's argument must be NULL: clock.c's handler then
+ * receives the interrupt frame, which hardclock() needs to tell
+ * whether the interrupted code was in user or kernel mode.
+ */
+ sc->sc_ih = fdtbus_intr_establish_xname(phandle, 0, IPL_SCHED,
+ FDT_INTR_MPSAFE, intrfunc, NULL, device_xname(sc->sc_dev));
+ if (sc->sc_ih == NULL) {
+ aprint_error_dev(sc->sc_dev,
+ "couldn't establish interrupt at %s\n", intrstr);
+ return ENXIO;
+ }
+ aprint_normal_dev(sc->sc_dev, "interrupting at %s\n", intrstr);
+
+ return 0;
+ }
+
+ /* clock_if: start periodic interrupts every `interval_us'. */
+ static int
+ mytimer_arm(struct clock_if *clk, unsigned int interval_us)
+ {
+ struct mytimer_softc * const sc =
+ container_of(clk, struct mytimer_softc, sc_clkif);
+
+ sc->sc_reload = mytimer_us_to_ticks(sc, interval_us);
+ mytimer_hw_stop(sc);
+ mytimer_hw_start(sc, sc->sc_reload);
+
+ return 0;
+ }
+
+ /* clock_if: called at the start of every timer interrupt. */
+ static void
+ mytimer_intr(struct clock_if *clk)
+ {
+ struct mytimer_softc * const sc =
+ container_of(clk, struct mytimer_softc, sc_clkif);
+
+ mytimer_hw_ack(sc);
+ if (!MYTIMER_AUTO_RELOAD) {
+ mytimer_hw_start(sc, sc->sc_reload);
+ }
+ }
+
+ /* clock_if: stop the timer. */
+ static void
+ mytimer_disarm(struct clock_if *clk)
+ {
+ struct mytimer_softc * const sc =
+ container_of(clk, struct mytimer_softc, sc_clkif);
+
+ mytimer_hw_stop(sc);
+ }
+
+ /*
+ * ----------------------------------------------------------------------
+ * Autoconfiguration
+ * ----------------------------------------------------------------------
+ */
+
+ static int
+ mytimer_match(device_t parent, cfdata_t cf, void *aux)
+ {
+ struct fdt_attach_args * const faa = aux;
+
+ return of_compatible_match(faa->faa_phandle, compat_data);
+ }
+
+ static void
+ mytimer_attach(device_t parent, device_t self, void *aux)
+ {
+ struct mytimer_softc * const sc = device_private(self);
+ struct fdt_attach_args * const faa = aux;
+ const int phandle = faa->faa_phandle;
+ bus_addr_t addr;
+ bus_size_t size;
+ struct clk *clk;
+ uint32_t freq;
+
+ sc->sc_dev = self;
+ sc->sc_bst = faa->faa_bst;
+
+ /* --- Registers ------------------------------------------------ */
+ if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+ aprint_error(": couldn't get registers\n");
+ return;
+ }
+ sc->sc_shift = fdtbus_get_reg_shift(phandle, 0);
+ if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
+ aprint_error(": couldn't map registers\n");
+ return;
+ }
+
+ /*
+ * --- Input clock ----------------------------------------------
+ * Prefer a "clocks" reference to a clock node; fall back to a
+ * plain "clock-frequency" property.
+ */
+ clk = fdtbus_clock_get_index(phandle, 0);
+ if (clk != NULL) {
+ freq = clk_get_rate(clk);
+ } else if (of_getprop_uint32(phandle, "clock-frequency", &freq) != 0) {
+ aprint_error(": no \"clocks\" or \"clock-frequency\"\n");
+ return;
+ }
+ sc->sc_freq = freq / MYTIMER_PRESCALE;
+ if (sc->sc_freq == 0) {
+ aprint_error(": invalid clock rate %u Hz\n", freq);
+ return;
+ }
+
+ /* Make sure the timer is quiet until the clock code arms it. */
+ mytimer_hw_stop(sc);
+
+ aprint_naive("\n");
+ aprint_normal(": timer, %u Hz count rate\n", sc->sc_freq);
+
+ /* --- Register with the clock code ------------------------------ */
+ sc->sc_clkif.clk_dev = self;
+ sc->sc_clkif.clk_init = mytimer_init;
+ sc->sc_clkif.clk_arm = mytimer_arm;
+ sc->sc_clkif.clk_intr = mytimer_intr;
+ sc->sc_clkif.clk_disarm = mytimer_disarm;
+ clock_attach(&sc->sc_clkif);
+ }
+
+ CFATTACH_DECL_NEW(mytimer, sizeof(struct mytimer_softc),
+ mytimer_match, mytimer_attach, NULL, NULL);
+ ```
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9