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

Reply via email to