Add ociic(4) and dapmic(4). The first is a driver for the OpenCores I2C
authorkettenis <kettenis@openbsd.org>
Wed, 16 Jun 2021 12:37:23 +0000 (12:37 +0000)
committerkettenis <kettenis@openbsd.org>
Wed, 16 Jun 2021 12:37:23 +0000 (12:37 +0000)
controller and the latter is a driver for the Dialog DA9063 PMIC.  The
dapmic(4) driver currently supports the integrated RTC and also provides
support for resetting and powering down an application processor.  This
functionality is used to support rebooting the SiFive Unmatched board.

ok deraadt@

sys/arch/riscv64/conf/GENERIC
sys/arch/riscv64/conf/RAMDISK
sys/dev/fdt/dapmic.c [new file with mode: 0644]
sys/dev/fdt/files.fdt
sys/dev/fdt/ociic.c [new file with mode: 0644]

index e643ca3..c027893 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: GENERIC,v 1.19 2021/06/14 06:09:28 jsg Exp $
+#      $OpenBSD: GENERIC,v 1.20 2021/06/16 12:37:23 kettenis Exp $
 #
 # For further information on compiling OpenBSD kernels, see the config(8)
 # man page.
@@ -69,6 +69,10 @@ pci*         at dwpcie?
 pciecam*       at fdt?
 pci*           at pciecam?
 
+ociic*         at fdt?
+iic*           at ociic?
+dapmic*                at iic?
+
 # PCI
 ppb*           at pci?         # PCI-PCI bridges
 pci*           at ppb?
index 1c26456..bb62150 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: RAMDISK,v 1.20 2021/06/14 06:09:28 jsg Exp $
+#      $OpenBSD: RAMDISK,v 1.21 2021/06/16 12:37:23 kettenis Exp $
 
 machine                riscv64
 maxusers       4
@@ -65,6 +65,10 @@ pci*         at dwpcie?
 pciecam*       at fdt?
 pci*           at pciecam?
 
+ociic*         at fdt?
+iic*           at ociic?
+dapmic*                at iic?
+
 # PCI
 ppb*           at pci?         # PCI-PCI bridges
 pci*           at ppb?
diff --git a/sys/dev/fdt/dapmic.c b/sys/dev/fdt/dapmic.c
new file mode 100644 (file)
index 0000000..cc8d529
--- /dev/null
@@ -0,0 +1,275 @@
+/*     $OpenBSD: dapmic.c,v 1.1 2021/06/16 12:37:24 kettenis Exp $     */
+/*
+ * Copyright (c) 2021 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/ofw/openfirm.h>
+#include <dev/ofw/ofw_regulator.h>
+#include <dev/ofw/fdt.h>
+
+#include <dev/i2c/i2cvar.h>
+
+#include <dev/clock_subr.h>
+
+extern void (*cpuresetfn)(void);
+extern void (*powerdownfn)(void);
+
+/* Registers */
+#define EVENT_A                        0x06
+#define CONTROL_F              0x13
+#define  CONTROL_F_WAKE_UP             (1 << 2)
+#define  CONTROL_F_SHUTDOWN            (1 << 1)
+#define COUNT_S                        0x40
+#define  COUNT_S_COUNT_SEC             0x3f
+#define COUNT_MI               0x41
+#define  COUNT_MI_COUNT_MIN            0x3f
+#define COUNT_H                        0x42
+#define  COUNT_H_COUNT_HOUR            0x1f
+#define COUNT_D                        0x43
+#define  COUNT_D_COUNT_DAY             0x1f
+#define COUNT_MO               0x44
+#define  COUNT_MO_COUNT_MONTH          0x0f
+#define COUNT_Y                        0x45
+#define  COUNT_Y_MONITOR               (1 << 6)
+#define  COUNT_Y_COUNT_YEAR            0x3f
+#define ALARM_MO               0x4a
+#define  ALARM_MO_TICK_WAKE            (1 << 5)
+#define  ALARM_MO_TICK_TYPE            (1 << 4)
+#define ALARM_Y                        0x4b
+#define  ALARM_Y_TICK_ON               (1 << 7)
+
+struct dapmic_softc {
+       struct device sc_dev;
+       i2c_tag_t sc_tag;
+       i2c_addr_t sc_addr;
+
+       struct todr_chip_handle sc_todr;
+};
+
+int    dapmic_match(struct device *, void *, void *);
+void   dapmic_attach(struct device *, struct device *, void *);
+
+struct cfattach dapmic_ca = {
+       sizeof(struct dapmic_softc), dapmic_match, dapmic_attach
+};
+
+struct cfdriver dapmic_cd = {
+       NULL, "dapmic", DV_DULL
+};
+
+uint8_t        dapmic_reg_read(struct dapmic_softc *, int);
+void   dapmic_reg_write(struct dapmic_softc *, int, uint8_t);
+int    dapmic_clock_read(struct dapmic_softc *, struct clock_ymdhms *);
+int    dapmic_clock_write(struct dapmic_softc *, struct clock_ymdhms *);
+int    dapmic_gettime(struct todr_chip_handle *, struct timeval *);
+int    dapmic_settime(struct todr_chip_handle *, struct timeval *);
+void   dapmic_reset(void);
+void   dapmic_powerdown(void);
+
+int
+dapmic_match(struct device *parent, void *match, void *aux)
+{
+       struct i2c_attach_args *ia = aux;
+
+       return (strcmp(ia->ia_name, "dlg,da9063") == 0);
+}
+
+void
+dapmic_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct dapmic_softc *sc = (struct dapmic_softc *)self;
+       struct i2c_attach_args *ia = aux;
+
+       sc->sc_tag = ia->ia_tag;
+       sc->sc_addr = ia->ia_addr;
+
+       sc->sc_todr.cookie = sc;
+       sc->sc_todr.todr_gettime = dapmic_gettime;
+       sc->sc_todr.todr_settime = dapmic_settime;
+       todr_attach(&sc->sc_todr);
+
+       printf("\n");
+
+       if (cpuresetfn == NULL)
+               cpuresetfn = dapmic_reset;
+       if (powerdownfn == NULL)
+               powerdownfn = dapmic_powerdown;
+}
+
+uint8_t
+dapmic_reg_read(struct dapmic_softc *sc, int reg)
+{
+       uint8_t cmd = reg;
+       uint8_t val;
+       int error;
+
+       iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
+       error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
+           &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL);
+       iic_release_bus(sc->sc_tag, I2C_F_POLL);
+
+       if (error) {
+               printf("%s: can't read register 0x%02x\n",
+                   sc->sc_dev.dv_xname, reg);
+               val = 0xff;
+       }
+
+       return val;
+}
+
+void
+dapmic_reg_write(struct dapmic_softc *sc, int reg, uint8_t val)
+{
+       uint8_t cmd = reg;
+       int error;
+
+       iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
+       error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
+           &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL);
+       iic_release_bus(sc->sc_tag, I2C_F_POLL);
+
+       if (error) {
+               printf("%s: can't write register 0x%02x\n",
+                   sc->sc_dev.dv_xname, reg);
+       }
+}
+
+int
+dapmic_clock_read(struct dapmic_softc *sc, struct clock_ymdhms *dt)
+{
+       uint8_t regs[6];
+       uint8_t cmd = COUNT_S;
+       int error;
+
+       iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
+       error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
+           &cmd, sizeof(cmd), regs, sizeof(regs), I2C_F_POLL);
+       iic_release_bus(sc->sc_tag, I2C_F_POLL);
+
+       if (error)
+               return error;
+
+       dt->dt_sec = (regs[0] & COUNT_S_COUNT_SEC);
+       dt->dt_min = (regs[1] & COUNT_MI_COUNT_MIN);
+       dt->dt_hour = (regs[2] & COUNT_H_COUNT_HOUR);
+       dt->dt_day = (regs[3] & COUNT_D_COUNT_DAY);
+       dt->dt_mon = (regs[4] & COUNT_MO_COUNT_MONTH);
+       dt->dt_year = (regs[5] & COUNT_Y_COUNT_YEAR) + 2000;
+
+       /* Consider the time to be invalid if the MONITOR bit isn't set. */
+       if ((regs[5] & COUNT_Y_MONITOR) == 0)
+               return EINVAL;
+
+       return 0;
+}
+
+int
+dapmic_clock_write(struct dapmic_softc *sc, struct clock_ymdhms *dt)
+{
+       uint8_t regs[6];
+       uint8_t cmd = COUNT_S;
+       int error;
+
+       regs[0] = dt->dt_sec;
+       regs[1] = dt->dt_min;
+       regs[2] = dt->dt_hour;
+       regs[3] = dt->dt_day;
+       regs[4] = dt->dt_mon;
+       regs[5] = (dt->dt_year - 2000) | COUNT_Y_MONITOR;
+
+       iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
+       error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
+           &cmd, sizeof(cmd), regs, sizeof(regs), I2C_F_POLL);
+       iic_release_bus(sc->sc_tag, I2C_F_POLL);
+
+       if (error)
+               return error;
+
+       return 0;
+}
+
+int
+dapmic_gettime(struct todr_chip_handle *handle, struct timeval *tv)
+{
+       struct dapmic_softc *sc = handle->cookie;
+       struct clock_ymdhms dt;
+       int error;
+
+       error = dapmic_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
+dapmic_settime(struct todr_chip_handle *handle, struct timeval *tv)
+{
+       struct dapmic_softc *sc = handle->cookie;
+       struct clock_ymdhms dt;
+
+       clock_secs_to_ymdhms(tv->tv_sec, &dt);
+
+       return dapmic_clock_write(sc, &dt);
+}
+
+void
+dapmic_reset(void)
+{
+       struct dapmic_softc *sc = dapmic_cd.cd_devs[0];
+       uint8_t reg;
+
+       /* Enable tick alarm wakeup with a one second interval. */
+       reg = dapmic_reg_read(sc, ALARM_MO);
+       reg &= ~ALARM_MO_TICK_TYPE;
+       reg |= ALARM_MO_TICK_WAKE;
+       dapmic_reg_write(sc, ALARM_MO, reg);
+
+       /* Enable tick function. */
+       reg = dapmic_reg_read(sc, ALARM_Y);
+       reg |= ALARM_Y_TICK_ON;
+       dapmic_reg_write(sc, ALARM_Y, reg);
+
+       /* Clear events such that we wake up again. */
+       dapmic_reg_write(sc, EVENT_A, dapmic_reg_read(sc, EVENT_A));
+       dapmic_reg_write(sc, CONTROL_F, CONTROL_F_SHUTDOWN);
+}
+
+void
+dapmic_powerdown(void)
+{
+       struct dapmic_softc *sc = dapmic_cd.cd_devs[0];
+       uint8_t reg;
+
+       /* Disable tick function such that it doesn't wake us up. */
+       reg = dapmic_reg_read(sc, ALARM_Y);
+       reg &= ~ALARM_Y_TICK_ON;
+       dapmic_reg_write(sc, ALARM_Y, reg);
+
+       dapmic_reg_write(sc, CONTROL_F, CONTROL_F_SHUTDOWN);
+}
index ebd1962..3b2b833 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: files.fdt,v 1.153 2021/05/28 15:52:11 visa Exp $
+#      $OpenBSD: files.fdt,v 1.154 2021/06/16 12:37:24 kettenis Exp $
 #
 # Config file and device description for machine-independent FDT code.
 # Included by ports that need it.
@@ -161,6 +161,11 @@ device     gfrtc
 attach gfrtc at fdt
 file   dev/fdt/gfrtc.c                 gfrtc
 
+# OpenCores I2C controller
+device ociic: i2cbus
+attach ociic at fdt
+file   dev/fdt/ociic.c                 ociic
+
 # ARM PrimeCell PL061 General Purpose Input/Output
 device plgpio
 attach plgpio at fdt
@@ -574,3 +579,8 @@ file        dev/fdt/es8316ac.c              escodec
 device cwfg
 attach cwfg at i2c
 file   dev/fdt/cwfg.c                  cwfg
+
+# Dialog DA9063 PMIC
+device dapmic
+attach dapmic at i2c
+file   dev/fdt/dapmic.c                dapmic
diff --git a/sys/dev/fdt/ociic.c b/sys/dev/fdt/ociic.c
new file mode 100644 (file)
index 0000000..736514d
--- /dev/null
@@ -0,0 +1,314 @@
+/*     $OpenBSD: ociic.c,v 1.1 2021/06/16 12:37:24 kettenis Exp $      */
+/*
+ * Copyright (c) 2021 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/intr.h>
+#include <machine/bus.h>
+#include <machine/fdt.h>
+
+#define _I2C_PRIVATE
+#include <dev/i2c/i2cvar.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_clock.h>
+#include <dev/ofw/ofw_pinctrl.h>
+#include <dev/ofw/fdt.h>
+
+/* Registers */
+#define I2C_PRER_LO    0x0000
+#define I2C_PRER_HI    0x0004
+#define I2C_CTR                0x0008
+#define  I2C_CTR_EN    (1 << 7)
+#define  I2C_CTR_IEN   (1 << 6)
+#define I2C_TXR                0x000C
+#define I2C_RXR                0x000C
+#define I2C_CR         0x0010
+#define  I2C_CR_STA    (1 << 7)
+#define  I2C_CR_STO    (1 << 6)
+#define  I2C_CR_RD     (1 << 5)
+#define  I2C_CR_WR     (1 << 4)
+#define  I2C_CR_NACK   (1 << 3)
+#define  I2C_CR_IACK   (1 << 0)
+#define I2C_SR         0x0010
+#define  I2C_SR_RXNACK (1 << 7)
+#define  I2C_SR_BUSY   (1 << 6)
+#define  I2C_SR_AL     (1 << 5)
+#define  I2C_SR_TIP    (1 << 1)
+#define  I2C_SR_IF     (1 << 0)
+
+struct ociic_softc {
+       struct device           sc_dev;
+       bus_space_tag_t         sc_iot;
+       bus_space_handle_t      sc_ioh;
+
+       int                     sc_node;
+       struct i2c_controller   sc_ic;
+};
+
+static inline uint8_t
+ociic_read(struct ociic_softc *sc, bus_size_t reg)
+{
+       return bus_space_read_1(sc->sc_iot, sc->sc_ioh, reg);
+}
+
+static inline void
+ociic_write(struct ociic_softc *sc, bus_size_t reg, uint8_t value)
+{
+       bus_space_write_1(sc->sc_iot, sc->sc_ioh, reg, value);
+}
+
+static inline void
+ociic_set(struct ociic_softc *sc, bus_size_t reg, uint8_t bits)
+{
+       ociic_write(sc, reg, ociic_read(sc, reg) | bits);
+}
+
+static inline void
+ociic_clr(struct ociic_softc *sc, bus_size_t reg, uint8_t bits)
+{
+       ociic_write(sc, reg, ociic_read(sc, reg) & ~bits);
+}
+
+int ociic_match(struct device *, void *, void *);
+void ociic_attach(struct device *, struct device *, void *);
+
+struct cfattach        ociic_ca = {
+       sizeof (struct ociic_softc), ociic_match, ociic_attach
+};
+
+struct cfdriver ociic_cd = {
+       NULL, "ociic", DV_DULL
+};
+
+int    ociic_acquire_bus(void *, int);
+void   ociic_release_bus(void *, int);
+int    ociic_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t,
+           void *, size_t, int);
+
+void   ociic_bus_scan(struct device *, struct i2cbus_attach_args *, void *);
+
+int
+ociic_match(struct device *parent, void *match, void *aux)
+{
+       struct fdt_attach_args *faa = aux;
+
+       return OF_is_compatible(faa->fa_node, "sifive,i2c0");
+}
+
+void
+ociic_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct ociic_softc *sc = (struct ociic_softc *)self;
+       struct fdt_attach_args *faa = aux;
+       struct i2cbus_attach_args iba;
+       uint32_t clock_speed, bus_speed;
+       uint32_t div;
+
+       if (faa->fa_nreg < 1) {
+               printf(": no registers\n");
+               return;
+       }
+
+       sc->sc_iot = faa->fa_iot;
+       sc->sc_node = faa->fa_node;
+
+       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;
+       }
+
+       printf("\n");
+
+       pinctrl_byname(sc->sc_node, "default");
+       clock_enable_all(sc->sc_node);
+
+       ociic_clr(sc, I2C_CTR, I2C_CTR_EN);
+
+       clock_speed = clock_get_frequency(sc->sc_node, NULL);
+       bus_speed = OF_getpropint(sc->sc_node, "clock-frequency", 100000);
+
+       if (clock_speed > 0) {
+               div = (clock_speed / (5 * bus_speed));
+               if (div > 0)
+                       div -= 1;
+               if (div > 0xffff)
+                       div = 0xffff;
+
+               ociic_write(sc, I2C_PRER_LO, div & 0xff);
+               ociic_write(sc, I2C_PRER_HI, div >> 8);
+       }
+
+       sc->sc_ic.ic_cookie = sc;
+       sc->sc_ic.ic_acquire_bus = ociic_acquire_bus;
+       sc->sc_ic.ic_release_bus = ociic_release_bus;
+       sc->sc_ic.ic_exec = ociic_exec;
+
+       /* Configure its children */
+       memset(&iba, 0, sizeof(iba));
+       iba.iba_name = "iic";
+       iba.iba_tag = &sc->sc_ic;
+       iba.iba_bus_scan = ociic_bus_scan;
+       iba.iba_bus_scan_arg = &sc->sc_node;
+
+       config_found(&sc->sc_dev, &iba, iicbus_print);
+}
+
+int
+ociic_acquire_bus(void *cookie, int flags)
+{
+       struct ociic_softc *sc = cookie;
+
+       ociic_set(sc, I2C_CTR, I2C_CTR_EN);
+       return 0;
+}
+
+void
+ociic_release_bus(void *cookie, int flags)
+{
+       struct ociic_softc *sc = cookie;
+
+       ociic_clr(sc, I2C_CTR, I2C_CTR_EN);
+}
+
+int
+ociic_wait(struct ociic_softc *sc, int ack)
+{
+       uint8_t stat;
+       int timo;
+
+       for (timo = 50000; timo > 0; timo--) {
+               stat = ociic_read(sc, I2C_SR);
+               if ((stat & I2C_SR_TIP) == 0)
+                       break;
+               if ((stat & I2C_SR_AL))
+                       break;
+               delay(10);
+       }
+       if (timo == 0) {
+               ociic_write(sc, I2C_CR, I2C_CR_STO);
+               return ETIMEDOUT;
+       }
+
+       if (stat & I2C_SR_AL) {
+               ociic_write(sc, I2C_CR, I2C_CR_STO);
+               return EIO;
+       }
+       if (ack && (stat & I2C_SR_RXNACK)) {
+               ociic_write(sc, I2C_CR, I2C_CR_STO);
+               return EIO;
+       }
+
+       return 0;
+}
+
+int
+ociic_exec(void *cookie, i2c_op_t op, i2c_addr_t addr, const void *cmd,
+    size_t cmdlen, void *buf, size_t buflen, int flags)
+{
+       struct ociic_softc *sc = cookie;
+       int error, i;
+
+       if (cmdlen > 0) {
+               ociic_write(sc, I2C_TXR, addr << 1);
+               ociic_write(sc, I2C_CR, I2C_CR_STA | I2C_CR_WR);
+               error = ociic_wait(sc, 1);
+               if (error)
+                       return error;
+
+               for (i = 0; i < cmdlen; i++) {
+                       ociic_write(sc, I2C_TXR, ((uint8_t *)cmd)[i]);
+                       ociic_write(sc, I2C_CR, I2C_CR_WR);
+                       error = ociic_wait(sc, 1);
+                       if (error)
+                               return error;
+               }
+       }
+
+       if (I2C_OP_READ_P(op)) {
+               ociic_write(sc, I2C_TXR, addr << 1 | 1);
+               ociic_write(sc, I2C_CR, I2C_CR_STA | I2C_CR_WR);
+               error = ociic_wait(sc, 1);
+               if (error)
+                       return error;
+
+               for (i = 0; i < buflen; i++) {
+                       ociic_write(sc, I2C_CR, I2C_CR_RD |
+                           (i == (buflen - 1) ? I2C_CR_NACK : 0));
+                       error = ociic_wait(sc, 0);
+                       if (error)
+                               return error;
+                       ((uint8_t *)buf)[i] = ociic_read(sc, I2C_RXR);
+               }
+       } else {
+               if (cmdlen == 0) {
+                       ociic_write(sc, I2C_TXR, addr << 1);
+                       ociic_write(sc, I2C_CR, I2C_CR_STA | I2C_CR_WR);
+               }
+
+               for (i = 0; i < buflen; i++) {
+                       ociic_write(sc, I2C_TXR, ((uint8_t *)buf)[i]);
+                       ociic_write(sc, I2C_CR, I2C_CR_WR);
+                       error = ociic_wait(sc, 1);
+                       if (error)
+                               return error;
+               }
+       }
+
+       if (I2C_OP_STOP_P(op))
+               ociic_write(sc, I2C_CR, I2C_CR_STO);
+
+       return 0;
+}
+
+void
+ociic_bus_scan(struct device *self, struct i2cbus_attach_args *iba, void *arg)
+{
+       int iba_node = *(int *)arg;
+       struct i2c_attach_args ia;
+       char name[32], status[32];
+       uint32_t reg[1];
+       int node;
+
+       for (node = OF_child(iba_node); node; node = OF_peer(node)) {
+               memset(name, 0, sizeof(name));
+               memset(status, 0, sizeof(status));
+               memset(reg, 0, sizeof(reg));
+
+               if (OF_getprop(node, "compatible", name, sizeof(name)) == -1)
+                       continue;
+               if (name[0] == '\0')
+                       continue;
+
+               if (OF_getprop(node, "status", status, sizeof(status)) > 0 &&
+                   strcmp(status, "disabled") == 0)
+                       continue;
+
+               if (OF_getprop(node, "reg", &reg, sizeof(reg)) != sizeof(reg))
+                       continue;
+
+               memset(&ia, 0, sizeof(ia));
+               ia.ia_tag = iba->iba_tag;
+               ia.ia_addr = bemtoh32(&reg[0]);
+               ia.ia_name = name;
+               ia.ia_cookie = &node;
+               config_found(self, &ia, iic_print);
+       }
+}