Some time ago I wanted to get a clean poweroff from the power button on my Unmatched, so that I don't get fsck at reboot the morning after someone sleeps in the room where the machine lives. kettenis kindly provided sfgpio(4) to get interrupts working on dapmic(4) instead of my initial hack that used polling.
One issue I struggled with for a bit is that masking irqs also masks the wake-up events, particularly the events we use for dapmic_reset(). With the diff below most interrupt events are masked off during runtime, until we're shutting down/rebooting. Maybe this is too paranoid and I should let more events go through the intr handler, eg for wake-up events that I don't envision yet? (I would love to get wake on lan support in cad(4) but my attempts led nowhere so far.) Also interesting, the fault register needs to be cleared at boot, else the interrupt will keep triggering after eg a hard button-driven poweroff. We could log the faults found in FAULT_LOG at boot time to know why the machine has stopped. But I'm more concerned about what to do if we get a fault at runtime (see the XXX). When I tried to disestablish the interrupt handler with fdt_intr_disestablish(sc->sc_ih) from dapmic_reset(), I got a fault. Maybe something worth investigating. The code below is based off da9063_datasheet_2v2.pdf. I don't know of other machines we run on that use this controller, the only entry in dmesglog is matthieu's Unmatched machine. Tests, input and oks welcome. Index: dev/fdt/dapmic.c =================================================================== RCS file: /cvs/src/sys/dev/fdt/dapmic.c,v retrieving revision 1.2 diff -u -p -r1.2 dapmic.c --- dev/fdt/dapmic.c 6 Apr 2022 18:59:28 -0000 1.2 +++ dev/fdt/dapmic.c 17 Aug 2022 21:59:57 -0000 @@ -19,6 +19,9 @@ #include <sys/systm.h> #include <sys/device.h> #include <sys/malloc.h> +#include <sys/task.h> +#include <sys/proc.h> +#include <sys/signalvar.h> #include <dev/ofw/openfirm.h> #include <dev/ofw/ofw_regulator.h> @@ -28,11 +31,31 @@ #include <dev/clock_subr.h> +#include <machine/fdt.h> + extern void (*cpuresetfn)(void); extern void (*powerdownfn)(void); /* Registers */ +#define FAULT_LOG 0x05 #define EVENT_A 0x06 +#define EVENT_A_EVENTS_D (1 << 7) +#define EVENT_A_EVENTS_C (1 << 6) +#define EVENT_A_EVENTS_B (1 << 5) +#define EVENT_A_E_nONKEY (1 << 0) +#define EVENT_B 0x07 +#define EVENT_C 0x08 +#define EVENT_D 0x09 +#define IRQ_MASK_A 0x0a +#define IRQ_MASK_A_M_RESERVED ((1 << 7) | (1 << 6) | (1 << 5)) +#define IRQ_MASK_A_M_SEQ_RDY (1 << 4) +#define IRQ_MASK_A_M_ADC_RDY (1 << 3) +#define IRQ_MASK_A_M_TICK (1 << 2) +#define IRQ_MASK_A_M_ALARM (1 << 1) +#define IRQ_MASK_A_M_nONKEY (1 << 0) +#define IRQ_MASK_B 0x0b +#define IRQ_MASK_C 0x0c +#define IRQ_MASK_D 0x0d #define CONTROL_F 0x13 #define CONTROL_F_WAKE_UP (1 << 2) #define CONTROL_F_SHUTDOWN (1 << 1) @@ -55,11 +78,20 @@ extern void (*powerdownfn)(void); #define ALARM_Y 0x4b #define ALARM_Y_TICK_ON (1 << 7) +#ifdef DAPMIC_DEBUG +# define DPRINTF(args) do { printf args; } while (0) +#else +# define DPRINTF(args) do {} while (0) +#endif + struct dapmic_softc { struct device sc_dev; i2c_tag_t sc_tag; i2c_addr_t sc_addr; + int (*sc_ih)(void *); + struct task sc_task; + struct todr_chip_handle sc_todr; }; @@ -80,8 +112,11 @@ int dapmic_clock_read(struct dapmic_soft 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_irq_mask(struct dapmic_softc *); void dapmic_reset(void); void dapmic_powerdown(void); +int dapmic_intr(void *); +void dapmic_shutdown_task(void *); int dapmic_match(struct device *parent, void *match, void *aux) @@ -96,6 +131,7 @@ dapmic_attach(struct device *parent, str { struct dapmic_softc *sc = (struct dapmic_softc *)self; struct i2c_attach_args *ia = aux; + int node = *(int *)ia->ia_cookie; sc->sc_tag = ia->ia_tag; sc->sc_addr = ia->ia_addr; @@ -105,12 +141,35 @@ dapmic_attach(struct device *parent, str 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; + + task_set(&sc->sc_task, dapmic_shutdown_task, sc); + + /* Mask away events we don't care about */ + dapmic_reg_write(sc, IRQ_MASK_A, + 0xff & ~(IRQ_MASK_A_M_RESERVED | IRQ_MASK_A_M_nONKEY)); + dapmic_reg_write(sc, IRQ_MASK_B, 0xff); + dapmic_reg_write(sc, IRQ_MASK_C, 0xff); + dapmic_reg_write(sc, IRQ_MASK_D, 0xff); + + /* Clear past faults and events */ + dapmic_reg_write(sc, FAULT_LOG, dapmic_reg_read(sc, FAULT_LOG)); + dapmic_reg_write(sc, EVENT_A, dapmic_reg_read(sc, EVENT_A)); + dapmic_reg_write(sc, EVENT_B, dapmic_reg_read(sc, EVENT_B)); + dapmic_reg_write(sc, EVENT_C, dapmic_reg_read(sc, EVENT_C)); + dapmic_reg_write(sc, EVENT_D, dapmic_reg_read(sc, EVENT_D)); + + if (node != 0) { + sc->sc_ih = fdt_intr_establish_idx(node, 0, IPL_CLOCK, + dapmic_intr, sc, sc->sc_dev.dv_xname); + if (sc->sc_ih == NULL) + printf(", can't establish interrupt"); + } + + printf("\n"); } uint8_t @@ -239,11 +298,23 @@ dapmic_settime(struct todr_chip_handle * } void +dapmic_reset_irq_mask(struct dapmic_softc *sc) +{ + dapmic_reg_write(sc, IRQ_MASK_A, 0); + dapmic_reg_write(sc, IRQ_MASK_B, 0); + dapmic_reg_write(sc, IRQ_MASK_C, 0); + dapmic_reg_write(sc, IRQ_MASK_D, 0); +} + +void dapmic_reset(void) { struct dapmic_softc *sc = dapmic_cd.cd_devs[0]; uint8_t reg; + /* Re-enable irqs and the associated wake-up events */ + dapmic_reset_irq_mask(sc); + /* Enable tick alarm wakeup with a one second interval. */ reg = dapmic_reg_read(sc, ALARM_MO); reg &= ~ALARM_MO_TICK_TYPE; @@ -266,10 +337,90 @@ dapmic_powerdown(void) struct dapmic_softc *sc = dapmic_cd.cd_devs[0]; uint8_t reg; + /* Re-enable irqs and the associated wake-up events */ + dapmic_reset_irq_mask(sc); + /* 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); +} + +void +dapmic_shutdown_task(void *arg) +{ + extern int allowpowerdown; + + if (allowpowerdown == 1) { + allowpowerdown = 0; + prsignal(initprocess, SIGUSR2); + } +} + +int +dapmic_intr(void *arg) +{ + struct dapmic_softc *sc = arg; + uint8_t event_a, event_b, event_c, event_d, fault; + + event_b = event_c = event_d = 0; + + event_a = dapmic_reg_read(sc, EVENT_A); + DPRINTF(("%s: %s: event_a %#02.2hhx", sc->sc_dev.dv_xname, __func__, + event_a)); + + /* Acknowledge all events */ + if (event_a & EVENT_A_EVENTS_B) { + event_b = dapmic_reg_read(sc, EVENT_B); + DPRINTF((", event_b %#02.2hhx", event_b)); + if (event_b != 0) + dapmic_reg_write(sc, EVENT_B, event_b); + } + if (event_a & EVENT_A_EVENTS_C) { + event_c = dapmic_reg_read(sc, EVENT_C); + DPRINTF((", event_c %#02.2hhx", event_c)); + if (event_c != 0) + dapmic_reg_write(sc, EVENT_C, event_c); + } + if (event_a & EVENT_A_EVENTS_D) { + event_d = dapmic_reg_read(sc, EVENT_D); + DPRINTF((", event_d %#02.2hhx", event_d)); + if (event_d != 0) + dapmic_reg_write(sc, EVENT_D, event_d); + } + event_a &= ~(EVENT_A_EVENTS_B|EVENT_A_EVENTS_C|EVENT_A_EVENTS_D); + if (event_a != 0) + dapmic_reg_write(sc, EVENT_A, event_a); + + DPRINTF(("\n")); + + fault = dapmic_reg_read(sc, FAULT_LOG); + if (fault != 0) { + static int warned; + if (!warned) { + warned = 1; + printf("%s: FAULT_LOG %#02.2hhx\n", sc->sc_dev.dv_xname, + fault); + } + /* + * Don't blindly acknowledge the fault log bits, else we may + * prevent legit behavior like a forced poweroff with a long + * power button press. + * XXX If nothing useful can be done here, disestablish the + * handler instead? + */ +#if 0 + dapmic_reg_write(sc, FAULT_LOG, fault); +#endif + } + + if (event_a & EVENT_A_E_nONKEY) + task_add(systq, &sc->sc_task); + + if (event_a | event_b | event_c | event_d) + return 1; + + return 0; } -- jca | PGP : 0x1524E7EE / 5135 92C1 AD36 5293 2BDF DDCC 0DFA 74AE 1524 E7EE