Index: sys/dev/ichiic/ig4_acpi.c =================================================================== --- sys/dev/ichiic/ig4_acpi.c (revision 347228) +++ sys/dev/ichiic/ig4_acpi.c (working copy) @@ -153,6 +153,8 @@ DEVMETHOD(device_probe, ig4iic_acpi_probe), DEVMETHOD(device_attach, ig4iic_acpi_attach), DEVMETHOD(device_detach, ig4iic_acpi_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), /* iicbus interface */ DEVMETHOD(iicbus_transfer, ig4iic_transfer), Index: sys/dev/ichiic/ig4_iic.c =================================================================== --- sys/dev/ichiic/ig4_iic.c (revision 347228) +++ sys/dev/ichiic/ig4_iic.c (working copy) @@ -79,30 +79,10 @@ &ig4_dump, 0, "Dump controller registers"); /* - * Low-level inline support functions - */ -static __inline void -reg_write(ig4iic_softc_t *sc, uint32_t reg, uint32_t value) -{ - bus_write_4(sc->regs_res, reg, value); - bus_barrier(sc->regs_res, reg, 4, BUS_SPACE_BARRIER_WRITE); -} - -static __inline uint32_t -reg_read(ig4iic_softc_t *sc, uint32_t reg) -{ - uint32_t value; - - bus_barrier(sc->regs_res, reg, 4, BUS_SPACE_BARRIER_READ); - value = bus_read_4(sc->regs_res, reg); - return (value); -} - -/* * Enable or disable the controller and wait for the controller to acknowledge * the state change. */ -static int +int set_controller(ig4iic_softc_t *sc, uint32_t ctl) { int retry; Index: sys/dev/ichiic/ig4_pci.c =================================================================== --- sys/dev/ichiic/ig4_pci.c (revision 347228) +++ sys/dev/ichiic/ig4_pci.c (working copy) @@ -206,11 +206,122 @@ return (0); } +/* + * During suspend we save registers and re-initialize them + * during resume. This is a list of the registers to save. + * + * IG4_REGS_CONTEXT_SIZE is the length of this array. + * It must be updated if this list is. + * + * The regs_context field of struct ig4iic_softc holds the + * values of the saved registers. + * + * -- NOTE: IG4_REG_I2C_EN MUST be the last register in the list -- + * certain registers require it to be disabled for them to be written + */ +uint32_t regs_context_ids[IG4_REGS_CONTEXT_SIZE] = { + IG4_REG_CTL, + IG4_REG_TAR_ADD, + IG4_REG_DATA_CMD, + IG4_REG_SS_SCL_HCNT, + IG4_REG_SS_SCL_LCNT, + IG4_REG_FS_SCL_HCNT, + IG4_REG_FS_SCL_LCNT, + IG4_REG_INTR_MASK, + IG4_REG_RX_TL, + IG4_REG_TX_TL, + IG4_REG_SDA_HOLD, + IG4_REG_SLV_DATA_NACK, + IG4_REG_DMA_CTRL, + IG4_REG_DMA_TDLR, + IG4_REG_DMA_RDLR, + IG4_REG_SDA_SETUP, + IG4_REG_GENERAL, + IG4_REG_ACK_GENERAL_CALL, + IG4_REG_SW_LTR_VALUE, + IG4_REG_AUTO_LTR_VALUE, + IG4_REG_I2C_EN, +}; + +static int +ig4iic_pci_suspend(device_t dev) +{ + ig4iic_softc_t *sc; + + sc = device_get_softc(dev); + + /* RESETS spec (22.2.36) recommends saving and re-initializing registers */ + for (int i = 0; i < IG4_REGS_CONTEXT_SIZE; i++) { + sc->regs_context[i] = reg_read(sc, regs_context_ids[i]); + } + + /* + * reset the controller before we suspend + * Haswell and Skylake apparently have different RESET methods + */ + if (sc->version == IG4_HASWELL || sc->version == IG4_ATOM) { + reg_write(sc, IG4_REG_RESETS_HSW, IG4_RESETS_ASSERT_HSW); + + } else if (sc->version == IG4_SKYLAKE) { + /* + * + * set IG4_DEVICE_IDLE and IG4_RESTORE_REQUIRED + * to place the device in the idle state, just to be safe + */ + reg_write(sc, IG4_REG_DEVIDLE_CTRL, + IG4_DEVICE_IDLE | IG4_RESTORE_REQUIRED); + + reg_write(sc, IG4_REG_RESETS_SKL, IG4_RESETS_ASSERT_SKL); + + } else { + device_printf(dev, "Unable to assert reset, reset register unavailable\n"); + } + + /* suspend all children */ + return bus_generic_suspend(dev); +} + +static int +ig4iic_pci_resume(device_t dev) +{ + ig4iic_softc_t *sc; + + sc = device_get_softc(dev); + + /* wake the controller before its children */ + if (sc->version == IG4_HASWELL || sc->version == IG4_ATOM) { + reg_write(sc, IG4_REG_RESETS_HSW, IG4_RESETS_DEASSERT_HSW); + + } else if (sc->version == IG4_SKYLAKE) { + /* unset IG4_DEVICE_IDLE */ + reg_write(sc, IG4_REG_DEVIDLE_CTRL, 0); + reg_write(sc, IG4_REG_RESETS_SKL, IG4_RESETS_DEASSERT_SKL); + + } else { + device_printf(dev, "Unable to deassert reset, reset register unavailable\n"); + } + + /* disable the controller before restoring registers */ + set_controller(sc, 0); + + /* + * set registers to values previously saved in regs_context + * this will re-enable the controller. + */ + for (int i = 0; i < IG4_REGS_CONTEXT_SIZE; i++) { + reg_write(sc, regs_context_ids[i], sc->regs_context[i]); + } + + return bus_generic_resume(dev); +} + static device_method_t ig4iic_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ig4iic_pci_probe), DEVMETHOD(device_attach, ig4iic_pci_attach), DEVMETHOD(device_detach, ig4iic_pci_detach), + DEVMETHOD(device_suspend, ig4iic_pci_suspend), + DEVMETHOD(device_resume, ig4iic_pci_resume), DEVMETHOD(iicbus_transfer, ig4iic_transfer), DEVMETHOD(iicbus_reset, ig4iic_reset), Index: sys/dev/ichiic/ig4_var.h =================================================================== --- sys/dev/ichiic/ig4_var.h (revision 347228) +++ sys/dev/ichiic/ig4_var.h (working copy) @@ -49,6 +49,9 @@ enum ig4_op { IG4_IDLE, IG4_READ, IG4_WRITE }; enum ig4_vers { IG4_HASWELL, IG4_ATOM, IG4_SKYLAKE, IG4_APL }; +/* length of the register context array (regs_context_ids) */ +#define IG4_REGS_CONTEXT_SIZE 21 + struct ig4iic_softc { device_t dev; struct intr_config_hook enum_hook; @@ -55,6 +58,7 @@ device_t iicbus; struct resource *regs_res; int regs_rid; + uint32_t regs_context[IG4_REGS_CONTEXT_SIZE]; struct resource *intr_res; int intr_rid; void *intr_handle; @@ -109,4 +113,28 @@ extern iicbus_transfer_t ig4iic_transfer; extern iicbus_reset_t ig4iic_reset; +/* enable or disable the controller. */ +int set_controller(ig4iic_softc_t *sc, uint32_t ctl); + +/* + * Low-level inline support functions + * used by ig4_iic.c and ig4_pci.c + */ +static __inline void +reg_write(ig4iic_softc_t *sc, uint32_t reg, uint32_t value) +{ + bus_write_4(sc->regs_res, reg, value); + bus_barrier(sc->regs_res, reg, 4, BUS_SPACE_BARRIER_WRITE); +} + +static __inline uint32_t +reg_read(ig4iic_softc_t *sc, uint32_t reg) +{ + uint32_t value; + + bus_barrier(sc->regs_res, reg, 4, BUS_SPACE_BARRIER_READ); + value = bus_read_4(sc->regs_res, reg); + return (value); +} + #endif /* _ICHIIC_IG4_VAR_H_ */