Add a driver for the RSB controller found on various Allwinner SoCs.
authorkettenis <kettenis@openbsd.org>
Sat, 16 Dec 2017 10:22:13 +0000 (10:22 +0000)
committerkettenis <kettenis@openbsd.org>
Sat, 16 Dec 2017 10:22:13 +0000 (10:22 +0000)
Add a driver for the RTC part of the AC100 chip.
Together this turns my Cubieboard4 into a real computer by giving it
a proper clock.

ok patrick@

sys/arch/armv7/conf/GENERIC
sys/arch/armv7/conf/RAMDISK
sys/dev/fdt/acrtc.c [new file with mode: 0644]
sys/dev/fdt/files.fdt
sys/dev/fdt/rsbvar.h [new file with mode: 0644]
sys/dev/fdt/sxirsb.c [new file with mode: 0644]

index 2377eb3..72d6838 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: GENERIC,v 1.99 2017/10/24 17:00:34 kettenis Exp $
+#      $OpenBSD: GENERIC,v 1.100 2017/12/16 10:22:13 kettenis Exp $
 #
 # For further information on compiling OpenBSD kernels, see the config(8)
 # man page.
@@ -91,6 +91,8 @@ sxipio*               at fdt? early 1         # GPIO pins for leds & PHYs
 gpio*          at sxipio?
 sxiccmu*       at fdt? early 1         # Clock Control Module/Unit
 sxitimer*      at fdt? early 1
+sxirsb*                at fdt? early 1         # Reduced Serial Bus
+acrtc*         at sxirsb?
 sxidog*                at fdt?                 # watchdog timer
 sxirtc*                at fdt?                 # Real Time Clock
 sxie*          at fdt?
index 6356102..f5a98bd 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: RAMDISK,v 1.92 2017/10/24 17:00:34 kettenis Exp $
+#      $OpenBSD: RAMDISK,v 1.93 2017/12/16 10:22:13 kettenis Exp $
 
 machine                armv7 arm
 
@@ -87,6 +87,8 @@ sxipio*               at fdt? early 1         # GPIO pins for leds & PHYs
 gpio*          at sxipio?
 sxiccmu*       at fdt? early 1         # Clock Control Module/Unit
 sxitimer*      at fdt? early 1
+sxirsb*                at fdt? early 1         # Reduced Serial Bus
+acrtc*         at sxirsb?
 sxidog*                at fdt?                 # watchdog timer
 sxirtc*                at fdt?                 # Real Time Clock
 sxie*          at fdt?
diff --git a/sys/dev/fdt/acrtc.c b/sys/dev/fdt/acrtc.c
new file mode 100644 (file)
index 0000000..a150a43
--- /dev/null
@@ -0,0 +1,186 @@
+/*     $OpenBSD: acrtc.c,v 1.1 2017/12/16 10:22:13 kettenis Exp $      */
+/*
+ * Copyright (c) 2017 Mark Kettenis <kettenis@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/malloc.h>
+
+#include <dev/fdt/rsbvar.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/fdt.h>
+
+#include <dev/clock_subr.h>
+
+#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
+
+extern todr_chip_handle_t todr_handle;
+
+#define RTC_CTRL               0xc7
+#define  RTC_CTRL_12H_24H_MODE (1 << 0)
+#define RTC_SEC                        0xc8
+#define RTC_MIN                        0xc9
+#define RTC_HOU                        0xca
+#define RTC_WEE                        0xcb
+#define RTC_DAY                        0xcc
+#define RTC_MON                        0xcd
+#define RTC_YEA                        0xce
+#define  RTC_YEA_LEAP_YEAR     (1 << 15)
+#define RTC_UPD_TRIG           0xcf
+#define  RTC_UPD_TRIG_UPDATE   (1 << 15)
+
+struct acrtc_softc {
+       struct device   sc_dev;
+       void            *sc_cookie;
+       uint16_t        sc_rta;
+
+       struct todr_chip_handle sc_todr;
+};
+
+int    acrtc_match(struct device *, void *, void *);
+void   acrtc_attach(struct device *, struct device *, void *);
+
+struct cfattach acrtc_ca = {
+       sizeof(struct acrtc_softc), acrtc_match, acrtc_attach
+};
+
+struct cfdriver acrtc_cd = {
+       NULL, "acrtc", DV_DULL
+};
+
+int    acrtc_clock_read(struct acrtc_softc *, struct clock_ymdhms *);
+int    acrtc_clock_write(struct acrtc_softc *, struct clock_ymdhms *);
+int    acrtc_gettime(struct todr_chip_handle *, struct timeval *);
+int    acrtc_settime(struct todr_chip_handle *, struct timeval *);
+
+int
+acrtc_match(struct device *parent, void *match, void *aux)
+{
+       struct rsb_attach_args *ra = aux;
+
+       if (strcmp(ra->ra_name, "x-powers,ac100") == 0)
+               return 1;
+       return 0;
+}
+
+void
+acrtc_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct acrtc_softc *sc = (struct acrtc_softc *)self;
+       struct rsb_attach_args *ra = aux;
+
+       sc->sc_cookie = ra->ra_cookie;
+       sc->sc_rta = ra->ra_rta;
+
+       printf("\n");
+
+       sc->sc_todr.cookie = sc;
+       sc->sc_todr.todr_gettime = acrtc_gettime;
+       sc->sc_todr.todr_settime = acrtc_settime;
+       todr_handle = &sc->sc_todr;
+}
+
+inline uint16_t
+acrtc_read_reg(struct acrtc_softc *sc, uint8_t reg)
+{
+       return rsb_read_2(sc->sc_cookie, sc->sc_rta, reg);
+}
+
+inline void
+acrtc_write_reg(struct acrtc_softc *sc, uint8_t reg, uint16_t value)
+{
+       rsb_write_2(sc->sc_cookie, sc->sc_rta, reg, value);
+}
+
+int
+acrtc_gettime(struct todr_chip_handle *handle, struct timeval *tv)
+{
+       struct acrtc_softc *sc = handle->cookie;
+       struct clock_ymdhms dt;
+       int error;
+
+       error = acrtc_clock_read(sc, &dt);
+       if (error)
+               return error;
+
+       if (dt.dt_sec > 59 || dt.dt_min > 59 || dt.dt_hour > 23 ||
+           dt.dt_day > 31 || dt.dt_day == 0 ||
+           dt.dt_mon > 12 || dt.dt_mon == 0 ||
+           dt.dt_year < POSIX_BASE_YEAR)
+               return EINVAL;
+
+       tv->tv_sec = clock_ymdhms_to_secs(&dt);
+       tv->tv_usec = 0;
+       return 0;
+}
+
+int
+acrtc_settime(struct todr_chip_handle *handle, struct timeval *tv)
+{
+       struct acrtc_softc *sc = handle->cookie;
+       struct clock_ymdhms dt;
+
+       clock_secs_to_ymdhms(tv->tv_sec, &dt);
+
+       return acrtc_clock_write(sc, &dt);
+}
+
+int
+acrtc_clock_read(struct acrtc_softc *sc, struct clock_ymdhms *dt)
+{
+       uint16_t ctrl;
+
+       dt->dt_sec = FROMBCD(acrtc_read_reg(sc, RTC_SEC));
+       dt->dt_min = FROMBCD(acrtc_read_reg(sc, RTC_MIN));
+       dt->dt_hour = FROMBCD(acrtc_read_reg(sc, RTC_HOU));
+       dt->dt_day = FROMBCD(acrtc_read_reg(sc, RTC_DAY));
+       dt->dt_mon = FROMBCD(acrtc_read_reg(sc, RTC_MON));
+       dt->dt_year = FROMBCD(acrtc_read_reg(sc, RTC_YEA)) + 2000;
+
+#ifdef DEBUG
+       printf("%02d/%02d/%04d %02d:%02d:%0d\n", dt->dt_day, dt->dt_mon,
+           dt->dt_year, dt->dt_hour, dt->dt_min, dt->dt_sec);
+#endif
+
+       /* Consider the time to be invalid if the clock is in 12H mode. */
+       ctrl = acrtc_read_reg(sc, RTC_CTRL);
+       if ((ctrl & RTC_CTRL_12H_24H_MODE) == 0)
+               return EINVAL;
+
+       return 0;
+}
+
+int
+acrtc_clock_write(struct acrtc_softc *sc, struct clock_ymdhms *dt)
+{
+       uint16_t leap = isleap(dt->dt_year) ? RTC_YEA_LEAP_YEAR : 0;
+
+       acrtc_write_reg(sc, RTC_SEC, TOBCD(dt->dt_sec));
+       acrtc_write_reg(sc, RTC_MIN, TOBCD(dt->dt_min));
+       acrtc_write_reg(sc, RTC_HOU, TOBCD(dt->dt_hour));
+       acrtc_write_reg(sc, RTC_WEE, TOBCD(dt->dt_wday));
+       acrtc_write_reg(sc, RTC_DAY, TOBCD(dt->dt_day));
+       acrtc_write_reg(sc, RTC_MON, TOBCD(dt->dt_mon));
+       acrtc_write_reg(sc, RTC_YEA, TOBCD(dt->dt_year - 2000) | leap);
+       acrtc_write_reg(sc, RTC_UPD_TRIG, RTC_UPD_TRIG_UPDATE);
+
+       /* Switch to 24H mode to indicate the time is now valid. */
+       acrtc_write_reg(sc, RTC_CTRL, RTC_CTRL_12H_24H_MODE);
+
+       return 0;
+}
index 6df3c1f..b8b17cb 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: files.fdt,v 1.27 2017/09/21 12:01:52 patrick Exp $
+#      $OpenBSD: files.fdt,v 1.28 2017/12/16 10:22:13 kettenis Exp $
 #
 # Config file and device description for machine-independent FDT code.
 # Included by ports that need it.
@@ -15,6 +15,11 @@ device       sxipio {}: gpiobus
 attach sxipio at fdt
 file   dev/fdt/sxipio.c                sxipio
 
+define rsb
+device sxirsb {}: rsb
+attach sxirsb at fdt
+file   dev/fdt/sxirsb.c                sxirsb
+
 device sxirtc
 attach sxirtc at fdt
 file   dev/fdt/sxirtc.c                sxirtc
@@ -131,3 +136,7 @@ file        dev/fdt/if_mvneta.c             mvneta
 device dwxe: ether, ifnet, mii, ifmedia
 attach dwxe at fdt
 file   dev/fdt/if_dwxe.c               dwxe
+
+device acrtc
+attach acrtc at sxirsb
+file   dev/fdt/acrtc.c                 acrtc
\ No newline at end of file
diff --git a/sys/dev/fdt/rsbvar.h b/sys/dev/fdt/rsbvar.h
new file mode 100644 (file)
index 0000000..60060ef
--- /dev/null
@@ -0,0 +1,29 @@
+/*     $OpenBSD: rsbvar.h,v 1.1 2017/12/16 10:22:13 kettenis Exp $     */
+/*
+ * Copyright (c) 2017 Mark kettenis <kettenis@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+struct rsb_attach_args {
+       void            *ra_cookie;
+       uint16_t        ra_da;
+       uint8_t         ra_rta;
+       char            *ra_name;
+       int             ra_node;
+};
+
+int    rsb_print(void *, const char *);
+
+uint16_t rsb_read_2(void *, uint8_t, uint8_t);
+void   rsb_write_2(void *, uint8_t, uint8_t, uint16_t);
diff --git a/sys/dev/fdt/sxirsb.c b/sys/dev/fdt/sxirsb.c
new file mode 100644 (file)
index 0000000..b9809cf
--- /dev/null
@@ -0,0 +1,302 @@
+/*     $OpenBSD: sxirsb.c,v 1.1 2017/12/16 10:22:13 kettenis Exp $     */
+/*
+ * Copyright (c) 2017 Mark kettenis <kettenis@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+
+#include <machine/bus.h>
+#include <machine/fdt.h>
+
+#include <dev/fdt/rsbvar.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_clock.h>
+#include <dev/ofw/ofw_pinctrl.h>
+#include <dev/ofw/fdt.h>
+
+#define RSB_CTRL                       0x0000
+#define  RSB_CTRL_START_TRANS          (1 << 7)
+#define  RSB_CTRL_ABORT_TRANS          (1 << 6)
+#define  RSB_CTRL_GLOBAL_INT_ENB       (1 << 1)
+#define  RSB_CTRL_SOFT_RESET           (1 << 0)
+#define RSB_CCR                                0x0004
+#define  RSB_CCR_CD_ODLY_SHIFT         8
+#define  RSB_CCR_CD_ODLY_MAX           0x7
+#define  RSB_CCR_CK_DIV_SHIFT          0
+#define  RSB_CCR_CK_DIV_MAX            0xff
+#define RSB_STAT                       0x000c
+#define  RSB_STAT_TRANS_OVER           (1 << 0)
+#define RSB_AR                         0x0010
+#define RSB_DATA                       0x001c
+#define RSB_DMCR                       0x0028
+#define  RSB_DMCR_DEVICE_MODE_START    (1U << 31)
+#define  RSB_DMCR_DEVICE_MODE_DATA     0x7e3e00
+#define RSB_CMD                                0x002c
+#define RSB_DAR                                0x0030
+
+#define SRTA   0xe8
+#define RD8    0x8b
+#define RD16   0x9c
+#define RD32   0xa6
+#define WR8    0x4e
+#define WR16   0x59
+#define WR32   0x63
+
+#define HREAD4(sc, reg)                                                        \
+       (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
+#define HWRITE4(sc, reg, val)                                          \
+       bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
+#define HSET4(sc, reg, bits)                                           \
+       HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
+#define HCLR4(sc, reg, bits)                                           \
+       HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
+
+struct sxirsb_softc {
+       struct device           sc_dev;
+       bus_space_tag_t         sc_iot;
+       bus_space_handle_t      sc_ioh;
+
+       int                     sc_addr;
+};
+
+int    sxirsb_match(struct device *, void *, void *);
+void   sxirsb_attach(struct device *, struct device *, void *);
+
+struct cfattach sxirsb_ca = {
+       sizeof(struct sxirsb_softc), sxirsb_match, sxirsb_attach
+};
+
+struct cfdriver sxirsb_cd = {
+       NULL, "sxirsb", DV_DULL
+};
+
+uint8_t        sxirsb_rta(uint16_t);
+
+int
+sxirsb_match(struct device *parent, void *match, void *aux)
+{
+       struct fdt_attach_args *faa = aux;
+
+       return OF_is_compatible(faa->fa_node, "allwinner,sun8i-a23-rsb");
+}
+
+void
+sxirsb_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct sxirsb_softc *sc = (struct sxirsb_softc *)self;
+       struct fdt_attach_args *faa = aux;
+       uint32_t freq, parent_freq, div, odly;
+       struct rsb_attach_args ra;
+       char name[32];
+       uint32_t reg;
+       uint8_t rta;
+       int node;
+       int timo;
+
+       if (faa->fa_nreg < 1) {
+               printf(": no registers\n");
+               return;
+       }
+
+       sc->sc_iot = faa->fa_iot;
+       if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
+           faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
+               printf(": can't map registers\n");
+               return;
+       }
+
+       pinctrl_byname(faa->fa_node, "default");
+
+       clock_enable_all(faa->fa_node);
+       reset_deassert_all(faa->fa_node);
+
+       HWRITE4(sc, RSB_CTRL, RSB_CTRL_SOFT_RESET);
+       for (timo = 1000; timo > 0; timo--) {
+               if ((HREAD4(sc, RSB_CTRL) & RSB_CTRL_SOFT_RESET) == 0)
+                       break;
+               delay(100);
+       }
+       if (timo == 0) {
+               printf(": reset failed\n");
+               return;
+       }
+
+       freq = OF_getpropint(faa->fa_node, "clock-frequency", 3000000);
+       parent_freq = clock_get_frequency_idx(faa->fa_node, 0);
+       div = parent_freq / freq / 2;
+       if (div == 0)
+               div = 1;
+       if (div > (RSB_CCR_CK_DIV_MAX + 1))
+               div = (RSB_CCR_CK_DIV_MAX + 1);
+       odly = div >> 1;
+       if (odly == 0)
+               odly = 1;
+       if (odly > RSB_CCR_CD_ODLY_MAX)
+               odly = RSB_CCR_CD_ODLY_MAX;
+       HWRITE4(sc, RSB_CCR, (odly << RSB_CCR_CD_ODLY_SHIFT) |
+           ((div - 1) << RSB_CCR_CK_DIV_SHIFT));
+
+       HWRITE4(sc, RSB_DMCR, RSB_DMCR_DEVICE_MODE_START |
+           RSB_DMCR_DEVICE_MODE_DATA);
+       for (timo = 1000; timo > 0; timo--) {
+               if ((HREAD4(sc, RSB_DMCR) & RSB_DMCR_DEVICE_MODE_START) == 0)
+                       break;
+               delay(100);
+       }
+       if (timo == 0) {
+               printf(": mode switch failed\n");
+               return;
+       }
+
+       for (node = OF_child(faa->fa_node); node; node = OF_peer(node)) {
+               reg = OF_getpropint(node, "reg", 0);
+               if (reg == 0)
+                       continue;
+
+               rta = sxirsb_rta(reg);
+               HWRITE4(sc, RSB_CMD, SRTA);
+               HWRITE4(sc, RSB_DAR, (rta << 16 | reg));
+
+               HSET4(sc, RSB_CTRL, RSB_CTRL_START_TRANS);
+               for (timo = 1000; timo > 0; timo--) {
+                       if ((HREAD4(sc, RSB_CTRL) & RSB_CTRL_START_TRANS) == 0)
+                               break;
+                       delay(10);
+               }
+               if (timo == 0) {
+                       printf(": SRTA failed for device 0x%03x\n", reg);
+                       return;
+               }
+       }
+
+       printf("\n");
+
+       for (node = OF_child(faa->fa_node); node; node = OF_peer(node)) {
+               reg = OF_getpropint(node, "reg", 0);
+               if (reg == 0)
+                       continue;
+
+               memset(name, 0, sizeof(name));
+               if (OF_getprop(node, "compatible", name, sizeof(name)) == -1)
+                       continue;
+               if (name[0] == '\0')
+                       continue;
+
+               memset(&ra, 0, sizeof(ra));
+               ra.ra_cookie = sc;
+               ra.ra_da = reg;
+               ra.ra_rta = sxirsb_rta(reg);
+               ra.ra_name = name;
+               ra.ra_node = node;
+               config_found(self, &ra, rsb_print);
+       }
+}
+
+/*
+ * Use a fixed device address to run-time address mapping.  This keeps
+ * things simple and matches what Linux does.
+ */
+
+struct rsb_addr_map {
+       uint16_t        da;
+       uint8_t         rta;
+};
+
+struct rsb_addr_map rsb_addr_map[] = {
+       { 0x3a3, 0x2d },
+       { 0x745, 0x3a },
+       { 0xe89, 0x4e }
+};
+
+uint8_t
+sxirsb_rta(uint16_t da)
+{
+       int i;
+
+       for (i = 0; i < nitems(rsb_addr_map); i++) {
+               if (rsb_addr_map[i].da == da)
+                       return rsb_addr_map[i].rta;
+       }
+
+       return 0;
+}
+
+uint16_t
+rsb_read_2(void *cookie, uint8_t rta, uint8_t addr)
+{
+       struct sxirsb_softc *sc = cookie;
+       uint16_t stat;
+       int timo;
+
+       HWRITE4(sc, RSB_CMD, RD16);
+       HWRITE4(sc, RSB_DAR, rta << 16);
+       HWRITE4(sc, RSB_AR, addr);
+
+       HSET4(sc, RSB_CTRL, RSB_CTRL_START_TRANS);
+       for (timo = 1000; timo > 0; timo--) {
+               if ((HREAD4(sc, RSB_CTRL) & RSB_CTRL_START_TRANS) == 0)
+                       break;
+               delay(10);
+       }
+       stat = HREAD4(sc, RSB_STAT);
+       HWRITE4(sc, RSB_STAT, stat);
+       if (timo == 0 || stat != RSB_STAT_TRANS_OVER) {
+               printf(": RD16 failed for run-time address 0x%02x\n", rta);
+               return 0xff;
+       }
+
+       return HREAD4(sc, RSB_DATA);
+}
+
+void
+rsb_write_2(void *cookie, uint8_t rta, uint8_t addr, uint16_t data)
+{
+       struct sxirsb_softc *sc = cookie;
+       uint16_t stat;
+       int timo;
+
+       HWRITE4(sc, RSB_CMD, WR16);
+       HWRITE4(sc, RSB_DAR, rta << 16);
+       HWRITE4(sc, RSB_AR, addr);
+       HWRITE4(sc, RSB_DATA, data);
+
+       HSET4(sc, RSB_CTRL, RSB_CTRL_START_TRANS);
+       for (timo = 1000; timo > 0; timo--) {
+               if ((HREAD4(sc, RSB_CTRL) & RSB_CTRL_START_TRANS) == 0)
+                       break;
+               delay(10);
+       }
+       stat = HREAD4(sc, RSB_STAT);
+       HWRITE4(sc, RSB_STAT, stat);
+       if (timo == 0 || stat != RSB_STAT_TRANS_OVER) {
+               printf(": WR16 failed for run-time address 0x%02x\n", rta);
+               return;
+       }
+}
+
+int
+rsb_print(void *aux, const char *pnp)
+{
+       struct rsb_attach_args *ra = aux;
+
+       if (pnp != NULL)
+               printf("\"%s\" at %s", ra->ra_name, pnp);
+       printf(" addr 0x%x", ra->ra_da);
+
+       return (UNCONF);
+}