nPM2100 Cannot go into Hibernate after SHPHLD wake up if timer running.

Hi 

We are using nPM2100 with PG/RESET mapped to SHPHLD.
We are using Zephyr library to manage the nPM2100.

We put the device into hibernate with a timed wake up, if we wake it up using SHPHLD instead of allowing the timer to expire, we cannot put it back into hibernate - we get an error that the timer is busy. We attempted to reset the timer using TASKS_STOP, but this does not seem to solve the problem - we do not get the IDLE status returned from the timer.

  • Hello,

    It seems that this is the behavior, yes. I still need to investigate whether this is the intended behavior, or if there is some other way to change the timer. 

    Do you need to change the timeout for your timer, or is it an alternative to just re-enter hibernation, and then wake up on the timeout that was originally set?

    Best regards,

    Edvin

  • Hi Edvin,

    It would be better it we could change the timer so that the wake-up time is defined. 
    Alternatively we could try your suggestion, but there may be a possible race condition if the timer is close to its expiration time.

    Thanks

    Cliff

  • Hello,

    I believe that if the timer expires while you are in SHPHLD wakeup, and go back to hibernation, it should wake up immediately after trying to go to sleep. From there, the timer will be reset and you should be able to set the timeout that you want. At least that is the behavior from our tests. Can you please try to see if you observe the same?

    Best regards,

    Edvin

  • Hi Edvin

    Yes, I see the same behavior. Can you also confirm the following behavior:
    1. If the timer has not expired in SHPHLD wakeup and it is put back into hibernation, it will stay in hibernation for the balance of the timer period.
    2. We are using direct register calls not mfd_npm2100_hibernate() or mfd_npm2100_set_timer() as these both check the timer status and return errors before completing - se we write directly to the timer and ignore the busy flag.

    Can you verify that this is a reasonable approach. code below:

    Thanks 

    Cliff


    /* Enter hibernation mode */
    int pmic_enter_hibernation(uint32_t timeout_ms, bool pass_through)
    {
        int ret;

        LOG_INF("Entering hibernation for %u ms (pass-through: %s)", timeout_ms, pass_through ? "enabled" : "disabled");

        /* Save fuel gauge state before hibernation */
        ret = fuel_gauge_prepare_hibernation();
        if (ret < 0) {
            LOG_WRN("Failed to prepare fuel gauge for hibernation: %d", ret);
            // Continue with hibernation even if fuel gauge preparation fails
        }

        _timerRunning = false;

        /* Program timer and trigger hibernation via direct I2C writes, bypassing
         * the Zephyr mfd_npm2100_hibernate() API.  On a SHPHLD wake (reset
         * reasons 7/9) TIMER_STATUS is non-idle and cannot be cleared, causing
         * mfd_npm2100_set_timer() — called internally by mfd_npm2100_hibernate()
         * — to return -EBUSY.  Direct register writes skip that guard entirely. */
        i2c_handle_t *i2c_handle = i2c_devices_get_handle();
        if (i2c_handle == NULL) {
            LOG_ERR("Failed to get I2C handle for hibernation");
            return -ENODEV;
        }

        if (timeout_ms > 0) {
            int64_t ticks = TIMER_MS_TO_TICKS(timeout_ms);
            if (ticks > TIMER_MAX_TICKS || ticks < 0) {
                LOG_ERR("Hibernation timeout %u ms out of range", timeout_ms);
                return -EINVAL;
            }

            /* Write 3-byte big-endian tick count to TIMER_TARGET (0xB4..0xB6) */
            uint8_t target[3] = {
                (uint8_t)((ticks >> 16) & 0xFF),
                (uint8_t)((ticks >> 8) & 0xFF),
                (uint8_t)(ticks & 0xFF),
            };
            i2c_status_t status = i2c_write_reg(i2c_handle, NPM2100_I2C_ADDR,
                                                NPM2100_TIMER_TARGET_REG, target,
                                                sizeof(target), I2C_TIMEOUT_MS);
            if (status != I2C_STATUS_OK) {
                LOG_ERR("Failed to write timer target: %d", status);
                return -EIO;
            }

            /* Set timer mode to wakeup */
            status = i2c_write_byte(i2c_handle, NPM2100_I2C_ADDR,
                                    NPM2100_TIMER_CONFIG_REG, NPM2100_TIMER_MODE_WAKEUP,
                                    I2C_TIMEOUT_MS);
            if (status != I2C_STATUS_OK) {
                LOG_ERR("Failed to write timer config: %d", status);
                return -EIO;
            }

            /* Start the timer */
            status = i2c_write_byte(i2c_handle, NPM2100_I2C_ADDR,
                                    NPM2100_TIMER_TASKS_START, 1, I2C_TIMEOUT_MS);
            if (status != I2C_STATUS_OK) {
                LOG_ERR("Failed to start timer: %d", status);
                return -EIO;
            }
        }

        /* Trigger hibernation */
        uint8_t hib_reg = pass_through ? NPM2100_HIBERNATE_PT_REG : NPM2100_HIBERNATE_REG;
        i2c_status_t status = i2c_write_byte(i2c_handle, NPM2100_I2C_ADDR,
                                             hib_reg, 1, I2C_TIMEOUT_MS);
        if (status != I2C_STATUS_OK) {
            LOG_ERR("Failed to trigger hibernation: %d", status);
            return -EIO;
        }

        return 0;
    }
  • Hello,

    I got this last night from our nPM team:

    We had a chat to understand the timer behavior better in this case, and have a proposal for a refined workaround for this case:

    After a wakeup from hibernate mode, check the timer state.

    If timer state is Idle, it was the timer that caused the wakeup and no further action is needed.

    If timer state is busy, the shphld pin caused the wakeup. Do the following:

    Call the TIMER.TASKS_STOP task.

    Check timer state again. If timer is idle, no further action is required.

    If the timer state is still busy, it means it expired some time after the shphld pin was triggered. The busy state can be cleared by entering hibernate mode again. The PMIC will in this case wake up "immediately" (~300 ms). 

    You already got the read and write parts figured out, but this is how you would read and write:

    uint8_t timer_status = 0;
    i2c_status_t status = i2c_read_reg(i2c_handle, NPM2100_I2C_ADDR,
                                       NPM2100_TIMER_STATUS_REG,  /* 0xB7 */
                                       &timer_status,
                                       1,
                                       I2C_TIMEOUT_MS);
    if (status != I2C_STATUS_OK) {
        LOG_ERR("Failed to read timer status: %d", status);
        return -EIO;
    }
    
    if (timer_status & 0x01) {
        LOG_INF("Timer is BUSY");
    } else {
        LOG_INF("Timer is IDLE");
    }

    i2c_status_t status = i2c_write_byte(i2c_handle, NPM2100_I2C_ADDR,
                                         NPM2100_TIMER_TASKS_STOP,  /* 0xB1 */
                                         1,
                                         I2C_TIMEOUT_MS);
    if (status != I2C_STATUS_OK) {
        LOG_ERR("Failed to stop timer: %d", status);
        return -EIO;
    }

    So this means if it was the SHPHLD that woke the device up, you can stop the timer, as long as it hasn't timed out already. If it has timed out, it will still be busy, and you should enter hibernation mode, and it should wake up again (300ms).

    You should test this under controlled circumstances, and see that it behaves the same way for your case.

    Best regards,

    Edvin

Related