From 9b3c43d5be236f93526e6aca59466a9b92e7d012 Mon Sep 17 00:00:00 2001 From: kettenis Date: Wed, 16 Jun 2021 12:37:23 +0000 Subject: [PATCH] Add ociic(4) and dapmic(4). The first is a driver for the OpenCores I2C 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 | 6 +- sys/arch/riscv64/conf/RAMDISK | 6 +- sys/dev/fdt/dapmic.c | 275 +++++++++++++++++++++++++++++ sys/dev/fdt/files.fdt | 12 +- sys/dev/fdt/ociic.c | 314 ++++++++++++++++++++++++++++++++++ 5 files changed, 610 insertions(+), 3 deletions(-) create mode 100644 sys/dev/fdt/dapmic.c create mode 100644 sys/dev/fdt/ociic.c diff --git a/sys/arch/riscv64/conf/GENERIC b/sys/arch/riscv64/conf/GENERIC index e643ca3a556..c02789355c7 100644 --- a/sys/arch/riscv64/conf/GENERIC +++ b/sys/arch/riscv64/conf/GENERIC @@ -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? diff --git a/sys/arch/riscv64/conf/RAMDISK b/sys/arch/riscv64/conf/RAMDISK index 1c26456e505..bb621502490 100644 --- a/sys/arch/riscv64/conf/RAMDISK +++ b/sys/arch/riscv64/conf/RAMDISK @@ -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 index 00000000000..cc8d529302a --- /dev/null +++ b/sys/dev/fdt/dapmic.c @@ -0,0 +1,275 @@ +/* $OpenBSD: dapmic.c,v 1.1 2021/06/16 12:37:24 kettenis Exp $ */ +/* + * Copyright (c) 2021 Mark Kettenis + * + * 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 +#include +#include +#include + +#include +#include +#include + +#include + +#include + +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); +} diff --git a/sys/dev/fdt/files.fdt b/sys/dev/fdt/files.fdt index ebd1962aab3..3b2b83342b5 100644 --- a/sys/dev/fdt/files.fdt +++ b/sys/dev/fdt/files.fdt @@ -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 index 00000000000..736514dc535 --- /dev/null +++ b/sys/dev/fdt/ociic.c @@ -0,0 +1,314 @@ +/* $OpenBSD: ociic.c,v 1.1 2021/06/16 12:37:24 kettenis Exp $ */ +/* + * Copyright (c) 2021 Mark Kettenis + * + * 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 +#include +#include + +#include +#include +#include + +#define _I2C_PRIVATE +#include + +#include +#include +#include +#include + +/* 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", ®, sizeof(reg)) != sizeof(reg)) + continue; + + memset(&ia, 0, sizeof(ia)); + ia.ia_tag = iba->iba_tag; + ia.ia_addr = bemtoh32(®[0]); + ia.ia_name = name; + ia.ia_cookie = &node; + config_found(self, &ia, iic_print); + } +} -- 2.20.1