#include "epicardium.h" #include "modules/modules.h" #include "modules/log.h" #include "card10.h" #include "pmic.h" #include "MAX77650-Arduino-Library.h" #include "max32665.h" #include "mxc_sys.h" #include "mxc_pins.h" #include "adc.h" #include "FreeRTOS.h" #include "task.h" #include "timers.h" #include #include #define LOCK_WAIT pdMS_TO_TICKS(1000) /* Task ID for the pmic handler */ static TaskHandle_t pmic_task_id = NULL; enum { /* An irq was received, probably the power button */ PMIC_NOTIFY_IRQ = 1, /* The timer has ticked and we should check the battery voltage again */ PMIC_NOTIFY_MONITOR = 2, }; void pmic_interrupt_callback(void *_) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; if (pmic_task_id != NULL) { xTaskNotifyFromISR( pmic_task_id, PMIC_NOTIFY_IRQ, eSetBits, &xHigherPriorityTaskWoken ); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } int pmic_read_amux(enum pmic_amux_signal sig, float *result) { int ret = 0; int i2c_ret = 0; if (sig > _PMIC_AMUX_MAX) { return -EINVAL; } int adc_ret = hwlock_acquire(HWLOCK_ADC, LOCK_WAIT); if (adc_ret < 0) { ret = adc_ret; goto done; } i2c_ret = hwlock_acquire(HWLOCK_I2C, LOCK_WAIT); if (i2c_ret < 0) { ret = i2c_ret; goto done; } /* Select the correct channel for this measurement. */ MAX77650_setMUX_SEL(sig); /* * According to the datasheet, the voltage will stabilize within 0.3us. * Just to be sure, we'll wait a little longer. In the meantime, * release the I2C mutex. */ hwlock_release(HWLOCK_I2C); vTaskDelay(pdMS_TO_TICKS(5)); i2c_ret = hwlock_acquire(HWLOCK_I2C, LOCK_WAIT); if (i2c_ret < 0) { ret = i2c_ret; goto done; } uint16_t adc_data; ADC_StartConvert(ADC_CH_0, 0, 0); ADC_GetData(&adc_data); /* Turn MUX back to neutral so it does not waste power. */ MAX77650_setMUX_SEL(PMIC_AMUX_DISABLED); /* Convert ADC measurement to SI Volts */ float adc_voltage = (float)adc_data / 1023.0f * 1.22f; /* * Convert value according to PMIC formulas (Table 7) */ switch (sig) { case PMIC_AMUX_CHGIN_U: *result = adc_voltage / 0.167f; break; case PMIC_AMUX_CHGIN_I: *result = adc_voltage / 2.632f; break; case PMIC_AMUX_BATT_U: *result = adc_voltage / 0.272f; break; case PMIC_AMUX_BATT_CHG_I: *result = adc_voltage / 1.25f; break; case PMIC_AMUX_BATT_NULL_I: case PMIC_AMUX_THM_U: case PMIC_AMUX_TBIAS_U: case PMIC_AMUX_AGND_U: *result = adc_voltage; break; case PMIC_AMUX_SYS_U: *result = adc_voltage / 0.26f; break; default: ret = -EINVAL; } done: if (i2c_ret == 0) { hwlock_release(HWLOCK_I2C); } if (adc_ret == 0) { hwlock_release(HWLOCK_ADC); } return ret; } /* * Read the interrupt flag register and handle all interrupts which the PMIC has * sent. In most cases this will be the buttons. */ static uint8_t pmic_poll_interrupts(void) { while (hwlock_acquire(HWLOCK_I2C, LOCK_WAIT) < 0) { LOG_WARN("pmic", "Failed to acquire I2C. Retrying ..."); vTaskDelay(pdMS_TO_TICKS(100)); } uint8_t int_flag = MAX77650_getINT_GLBL(); hwlock_release(HWLOCK_I2C); /* TODO: Remove when all interrupts are handled */ if (int_flag & ~(MAX77650_INT_nEN_F | MAX77650_INT_nEN_R)) { LOG_WARN("pmic", "Unhandled PMIC Interrupt: %x", int_flag); } return int_flag; } __attribute__((noreturn)) static void pmic_die(float u_batt) { /* Stop core 1 */ core1_stop(); /* Grab the screen */ disp_forcelock(); /* Draw an error screen */ epic_disp_clear(0x0000); epic_disp_print(0, 0, " Battery", 0xffff, 0x0000); epic_disp_print(0, 20, " critical", 0xffff, 0x0000); epic_disp_print(0, 40, " !!!!", 0xffff, 0x0000); epic_disp_update(); /* Vibrate violently */ epic_vibra_set(true); /* Wait a bit */ for (int i = 0; i < 50000000; i++) __NOP(); /* We have some of headroom to keep the RTC going. * The battery protection circuit will shut down * the system at 3.0 V */ /* TODO: Wake-up when USB is attached again */ sleep_deepsleep(); card10_reset(); while (1) ; } /* * Check the battery voltage. If it drops too low, turn card10 off. */ static void pmic_check_battery() { float u_batt; int res; res = pmic_read_amux(PMIC_AMUX_BATT_U, &u_batt); if (res < 0) { LOG_ERR("pmic", "Failed reading battery voltage: %s (%d)", strerror(-res), res); return; } LOG_DEBUG( "pmic", "Battery is at %d.%03d V", (int)u_batt, (int)(u_batt * 1000.0) % 1000 ); if (u_batt < BATTERY_CRITICAL) { pmic_die(u_batt); } } /* * API-call for battery voltage */ int epic_read_battery_voltage(float *result) { return pmic_read_amux(PMIC_AMUX_BATT_U, result); } /* * API-call for battery current */ int epic_read_battery_current(float *result) { return pmic_read_amux(PMIC_AMUX_BATT_CHG_I, result); } /* * API-call for charge voltage */ int epic_read_chargein_voltage(float *result) { return pmic_read_amux(PMIC_AMUX_CHGIN_U, result); } /* * API-call for charge voltage */ int epic_read_chargein_current(float *result) { return pmic_read_amux(PMIC_AMUX_BATT_CHG_I, result); } /* * API-call for system voltage */ int epic_read_system_voltage(float *result) { return pmic_read_amux(PMIC_AMUX_SYS_U, result); } /* * API-call for thermistor voltage * * Thermistor is as 10k at room temperature, * voltage divided with another 10k. * (50% V_bias at room temperature) */ int epic_read_thermistor_voltage(float *result) { return pmic_read_amux(PMIC_AMUX_THM_U, result); } static StaticTimer_t pmic_timer_data; static void vPmicTimerCb(TimerHandle_t xTimer) { /* * Tell the PMIC task to check the battery again. */ xTaskNotify(pmic_task_id, PMIC_NOTIFY_MONITOR, eSetBits); } void vPmicTask(void *pvParameters) { pmic_task_id = xTaskGetCurrentTaskHandle(); uint8_t interrupts = 0; uint32_t reason = 0; ADC_Init(0x9, NULL); GPIO_Config(&gpio_cfg_adc0); TickType_t button_start_tick = 0; pmic_check_battery(); TimerHandle_t pmic_timer = xTimerCreateStatic( "PMIC Timer", pdMS_TO_TICKS(60 * 1000), pdTRUE, NULL, vPmicTimerCb, &pmic_timer_data ); if (pmic_timer == NULL) { LOG_CRIT("pmic", "Could not create timer."); vTaskDelay(portMAX_DELAY); } xTimerStart(pmic_timer, 0); /* Clear all pending interrupts. */ pmic_poll_interrupts(); while (1) { interrupts |= pmic_poll_interrupts(); if (interrupts == 0) { reason |= ulTaskNotifyTake(pdTRUE, portMAX_DELAY); } /* New interrupts */ if (reason & PMIC_NOTIFY_IRQ) { reason ^= PMIC_NOTIFY_IRQ; interrupts |= pmic_poll_interrupts(); } if (interrupts & MAX77650_INT_nEN_R) { /* Ignored in this state */ /* This can happen if the button is pressed * during boot and released now. */ interrupts ^= MAX77650_INT_nEN_R; /* Mark as handled. */ } if (interrupts & MAX77650_INT_nEN_F) { /* Button was pressed */ interrupts ^= MAX77650_INT_nEN_F; /* Mark as handled. */ button_start_tick = xTaskGetTickCount(); while (true) { TickType_t duration = xTaskGetTickCount() - button_start_tick; if (duration > 1000) { disp_forcelock(); epic_disp_clear(0x0000); char buf[20]; sprintf(buf, "Off in %d", 7 - (int)(duration + 500) / 1000); epic_disp_print( 0, 0, "Sleep zZz..", 0xffff, 0x0000 ); epic_disp_print( 0, 25, buf, 0xf000, 0x0000 ); epic_disp_print( 0, 50, " Reset ->", 0xffff, 0x0000 ); epic_disp_update(); } if (duration >= pdMS_TO_TICKS(1000)) { if (epic_buttons_read( BUTTON_RIGHT_TOP)) { LOG_WARN( "pmic", "Resetting ..." ); card10_reset(); } } if (interrupts & MAX77650_INT_nEN_R) { /* Button is released */ interrupts ^= MAX77650_INT_nEN_R; /* Mark as handled. */ if (duration < pdMS_TO_TICKS(1000)) { return_to_menu(); } if (duration > pdMS_TO_TICKS(1000)) { LOG_WARN("pmic", "Poweroff"); sleep_deepsleep(); card10_reset(); } break; } reason |= ulTaskNotifyTake( pdTRUE, pdMS_TO_TICKS(200) ); if (reason & PMIC_NOTIFY_IRQ) { /* New interrupts */ reason ^= PMIC_NOTIFY_IRQ; interrupts |= pmic_poll_interrupts(); } } } if (reason & PMIC_NOTIFY_MONITOR) { reason ^= PMIC_NOTIFY_MONITOR; pmic_check_battery(); } } }