Skip to content

Commit

Permalink
atx_psu: split events, bits and status -> state, tick
Browse files Browse the repository at this point in the history
  • Loading branch information
SpComb committed Oct 19, 2024
1 parent 50f981b commit 2cac77c
Showing 1 changed file with 154 additions and 71 deletions.
225 changes: 154 additions & 71 deletions components/atx_psu/atx_psu.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,48 @@

#include <stdlib.h>

#define ATX_PSU_STATE_CLEAR_BIT (1 << 0)
#define ATX_PSU_STATE_BIT(bit) (1 << (bit + 1))
#define ATX_PSU_STATE_BITS ((1 << ATX_PSU_BIT_COUNT) - 1 - 1) // all bits, but not the clear bit
#define ATX_PSU_BIT(bit) (1 << (bit))
#define ATX_PSU_BITS ((1 << ATX_PSU_BIT_COUNT) - 1)

#define ATX_PSU_EVENTS_SET_BIT (1 << 0)
#define ATX_PSU_EVENTS_CLEAR_BIT (1 << 1)
#define ATX_PSU_EVENTS_INTR_BIT (1 << 2)

#define ATX_PSU_STATUS_POWER_ENABLE_BIT (1 << 0)
#define ATX_PSU_STATUS_POWER_GOOD_BIT (1 << 1)

// poll for PG while ACTIVATE -> ACTIVE
#define ATX_PSU_POWER_GOOD_ACTIVATE_TICKS (100 / portTICK_PERIOD_MS)

// poll for PG while ACTIVATE
#define ATX_PSU_POWER_GOOD_ACTIVE_TICKS (1000 / portTICK_PERIOD_MS)
// polling for PG
#define ATX_PSU_INACTIVE_TICKS (10000 / portTICK_PERIOD_MS)
#define ATX_PSU_ACTIVATE_TICKS (100 / portTICK_PERIOD_MS)
#define ATX_PSU_ACTIVE_TICKS (1000 / portTICK_PERIOD_MS)

enum atx_psu_state {
INACTIVE,
ACTIVATE,
ACTIVE,
DEACTIVATE,
INACTIVE, // waiting for any bit set
ACTIVATE, // waiting for power-good, or bits cleared
ACTIVE, // waiting for bits cleared, checking for power-bad
DEACTIVATE, // waiting for delayed power-off, or any bit set
};

static const char *atx_psu_state_str(enum atx_psu_state state)
{
switch (state) {
case INACTIVE: return "INACTIVE";
case ACTIVATE: return "ACTIVATE";
case ACTIVE: return "ACTIVE";
case DEACTIVATE: return "DEACTIVATE";
default: return "?";
}
}

struct atx_psu {
struct atx_psu_options options;
enum atx_psu_state state;
TickType_t tick;

EventGroupHandle_t state, status;
EventGroupHandle_t events;

// XXX: these are just atomic bitmasks
EventGroupHandle_t bits, status;

} atx_psu;

Expand All @@ -42,6 +60,10 @@ static IRAM_ATTR void atx_psu_gpio_interrupt(gpio_pins_t pins, void *arg)

LOG_ISR_DEBUG("pins=" GPIO_PINS_FMT, GPIO_PINS_ARGS(pins));

if (!xEventGroupSetBitsFromISR(atx_psu->events, ATX_PSU_EVENTS_INTR_BIT, &xHigherPriorityTaskWoken)) {
LOG_ISR_WARN("xEventGroupSetBitsFromISR");
}

#if CONFIG_IDF_TARGET_ESP8266
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
Expand All @@ -67,7 +89,12 @@ static int atx_psu_init(struct atx_psu *atx_psu, const struct atx_psu_options *o
return err;
}

if (!(atx_psu->state = xEventGroupCreate())) {
if (!(atx_psu->events = xEventGroupCreate())) {
LOG_ERROR("xEventGroupCreate");
return -1;
}

if (!(atx_psu->bits = xEventGroupCreate())) {
LOG_ERROR("xEventGroupCreate");
return -1;
}
Expand Down Expand Up @@ -150,121 +177,177 @@ static bool get_power_good(struct atx_psu *atx_psu)
}
}

static EventBits_t atx_psu_wait(struct atx_psu *atx_psu, TickType_t ticks)
static void next_state(struct atx_psu *atx_psu, enum atx_psu_state state, TickType_t ticks)
{
LOG_DEBUG("ticks=%d", ticks);
EventBits_t bits = xEventGroupGetBits(atx_psu->bits);

LOG_DEBUG("%s -> %s<%08x> @ %d", atx_psu_state_str(atx_psu->state), atx_psu_state_str(state), bits, ticks);

// wait for any bit except clear, do not clear
return xEventGroupWaitBits(atx_psu->state, ATX_PSU_STATE_BITS, pdFALSE, pdFALSE, ticks) & ATX_PSU_STATE_BITS;
atx_psu->state = state;
atx_psu->tick = (ticks == portMAX_DELAY) ? 0 : xTaskGetTickCount() + ticks;
}

static EventBits_t atx_psu_wait_clear(struct atx_psu *atx_psu, TickType_t ticks)
static EventBits_t atx_psu_wait(struct atx_psu *atx_psu)
{
LOG_DEBUG("ticks=%d", ticks);
EventBits_t events = 0;

// always follow input interrupts
events |= ATX_PSU_EVENTS_INTR_BIT;

switch(atx_psu->state) {
case INACTIVE:
// indefinite wait for any bit to be set
events |= ATX_PSU_EVENTS_SET_BIT;
break;

case ACTIVATE:
// fast wait for power-good
events |= ATX_PSU_EVENTS_CLEAR_BIT;
break;

case ACTIVE:
// slow wait for power-bad
events |= ATX_PSU_EVENTS_CLEAR_BIT;
break;

case DEACTIVATE:
// configurable wait for bits to remain cleared
events |= ATX_PSU_EVENTS_SET_BIT;
break;

default:
LOG_FATAL("state=%d", atx_psu->state);
}

// wait for clear bit and clear it
return xEventGroupWaitBits(atx_psu->state, ATX_PSU_STATE_CLEAR_BIT, pdTRUE, pdTRUE, ticks) & ATX_PSU_STATE_BITS;
// schedule
TickType_t ticks, tick = xTaskGetTickCount();

if (!atx_psu->tick) {
ticks = portMAX_DELAY; // indefinite
} else if (atx_psu->tick > tick) {
ticks = atx_psu->tick - tick;
} else {
ticks = 0; // immediate
}

LOG_DEBUG("state=%s events=%04x ticks=%d", atx_psu_state_str(atx_psu->state), events, ticks);

// wait for any bit and clear
const BaseType_t xClearOnExit = pdTRUE;
const BaseType_t xWaitForAllBits = pdFALSE;

events = xEventGroupWaitBits(atx_psu->events, events, xClearOnExit, xWaitForAllBits, ticks);

// check state tick timeout
if (!atx_psu->tick) {
return false;
} else if (xTaskGetTickCount() >= atx_psu->tick) {
return true;
} else {
return false;
}
}

void atx_psu_main(void *arg)
{
struct atx_psu *atx_psu = arg;
enum atx_psu_state state = INACTIVE;

for (;;) {
EventBits_t bits;
bool timeout = atx_psu_wait(atx_psu);
EventBits_t bits = xEventGroupGetBits(atx_psu->bits);
bool power_good = get_power_good(atx_psu);

LOG_DEBUG("state=%s bits=%04x power_good=%d timeout=%d", atx_psu_state_str(atx_psu->state), bits, power_good, timeout);

switch(state) {
switch(atx_psu->state) {
case INACTIVE:
LOG_DEBUG("INACTIVE...");
if (bits) {
next_state(atx_psu, ACTIVATE, ATX_PSU_ACTIVATE_TICKS);

// indefinite wait for any bit
if ((bits = atx_psu_wait(atx_psu, portMAX_DELAY))) {
LOG_DEBUG("INACTIVE -> ACTIVATE<%08x>", bits);
set_power_enable(atx_psu);
state = ACTIVATE;
LOG_INFO("power-on");
set_power_enable(atx_psu);

} else {
LOG_DEBUG("INACTIVE");
next_state(atx_psu, INACTIVE, ATX_PSU_INACTIVE_TICKS);
}

break;

case ACTIVATE:
LOG_DEBUG("ACTIVATE...");

// poll for PG
if (!(bits = atx_psu_wait_clear(atx_psu, ATX_PSU_POWER_GOOD_ACTIVATE_TICKS))) {
LOG_DEBUG("ACTIVATE -> DEACTIVATE");
if (!bits) {
// delay deactivation
state = DEACTIVATE;
} else if (get_power_good(atx_psu)) {
LOG_DEBUG("ACTIVATE -> ACTIVE<%08x>", bits);
state = ACTIVE;
next_state(atx_psu, DEACTIVATE, atx_psu->options.timeout);

} else if (power_good) {
LOG_INFO("power-good");
} else {

next_state(atx_psu, ACTIVE, ATX_PSU_ACTIVE_TICKS);

} else if (timeout) {
// re-ACTIVATE to test power_good
LOG_DEBUG("ACTIVATE");
next_state(atx_psu, ACTIVATE, ATX_PSU_ACTIVATE_TICKS);
}

break;

case ACTIVE:
LOG_DEBUG("ACTIVE...");

// wait for clear bit, poll for PG
if (!(bits = atx_psu_wait_clear(atx_psu, ATX_PSU_POWER_GOOD_ACTIVE_TICKS))) {
LOG_DEBUG("ACTIVE -> DEACTIVATE");
if (!bits) {
// delay deactivation
state = DEACTIVATE;
} else if (get_power_good(atx_psu)) {
LOG_DEBUG("ACTIVE<%08x>", bits);
} else {
next_state(atx_psu, DEACTIVATE, atx_psu->options.timeout);

} else if (!power_good) {
LOG_WARN("power-bad");

// re-ACTIVATE to test power_good
LOG_DEBUG("ACTIVE -> ACTIVATE");
state = ACTIVATE;
LOG_WARN("lost power-good!");
next_state(atx_psu, ACTIVATE, ATX_PSU_ACTIVATE_TICKS);

} else if (timeout) {
next_state(atx_psu, ACTIVE, ATX_PSU_ACTIVE_TICKS);
}

break;

case DEACTIVATE:
LOG_DEBUG("DEACTIVATE...");

// wait for any bit or deactivate on timeout
if (!(bits = atx_psu_wait(atx_psu, atx_psu->options.timeout))) {
LOG_DEBUG("DEACTIVATE -> INACTIVE");
clear_power_enable(atx_psu);
state = INACTIVE;
if (bits && power_good) {
// remain active
next_state(atx_psu, ACTIVE, ATX_PSU_ACTIVE_TICKS);
} else if (bits) {
// re-ACTIVATE to test power_good
next_state(atx_psu, ACTIVATE, ATX_PSU_ACTIVATE_TICKS);
} else if (timeout) {
LOG_INFO("power-off");
} else if (get_power_good(atx_psu)) {
LOG_DEBUG("DEACTIVATE -> ACTIVE<%08x>", bits);
state = ACTIVE;
clear_power_enable(atx_psu);

next_state(atx_psu, INACTIVE, ATX_PSU_INACTIVE_TICKS);
} else {
LOG_DEBUG("DEACTIVATE -> ACTIVATE<%08x>", bits);
state = ACTIVATE;
// remain in DEACTIVATE state
}

break;

default:
abort();
LOG_FATAL("state=%d", atx_psu->state);
}
}
}

void atx_psu_power_enable(struct atx_psu *atx_psu, enum atx_psu_bit bit)
{
LOG_DEBUG("bit=%08x", ATX_PSU_STATE_BIT(bit));
LOG_DEBUG("bit=%08x", ATX_PSU_BIT(bit));

xEventGroupSetBits(atx_psu->state, ATX_PSU_STATE_BIT(bit));
xEventGroupSetBits(atx_psu->bits, ATX_PSU_BIT(bit));
xEventGroupSetBits(atx_psu->events, ATX_PSU_EVENTS_SET_BIT);
}

int atx_psu_power_good(struct atx_psu *atx_psu, enum atx_psu_bit bit, TickType_t timeout)
{
EventBits_t bits;

xEventGroupSetBits(atx_psu->state, ATX_PSU_STATE_BIT(bit));
xEventGroupSetBits(atx_psu->bits, ATX_PSU_BIT(bit));
xEventGroupSetBits(atx_psu->events, ATX_PSU_EVENTS_SET_BIT);

if (!atx_psu->options.gpio_options.in_pins) {
LOG_DEBUG("power_good_gpio disabled");
Expand All @@ -281,8 +364,8 @@ int atx_psu_power_good(struct atx_psu *atx_psu, enum atx_psu_bit bit, TickType_t

void atx_psu_standby(struct atx_psu *atx_psu, enum atx_psu_bit bit)
{
LOG_DEBUG("bit=%08x", ATX_PSU_STATE_BIT(bit));
LOG_DEBUG("bit=%08x", ATX_PSU_BIT(bit));

xEventGroupClearBits(atx_psu->state, ATX_PSU_STATE_BIT(bit));
xEventGroupSetBits(atx_psu->state, ATX_PSU_STATE_CLEAR_BIT);
xEventGroupClearBits(atx_psu->bits, ATX_PSU_BIT(bit));
xEventGroupSetBits(atx_psu->events, ATX_PSU_EVENTS_CLEAR_BIT);
}

0 comments on commit 2cac77c

Please sign in to comment.