Files
system76-embedded-controller/src/board/system76/common/smfi.c
Tim Crawford ae63a9e3fb fan: Replace interpolation, smoothing with stepping
Fan noise is one of the top complaints reported. The existing
interpolation and smoothing logic has not sufficiently addressed the
issues with fans changing speeds too quickly in response to rapid
changes in thermals (particularly from PECI).

This behavior can be observed by with very basic tasks, such as:

- Powering on a system and logging into GNOME
- Starting a GUI application such as Firefox

Replace them with a fixed step update per event interval. Fans now have
a maximum amount they change change over time (3.9%/sec) as they move
towards a target duty.

Signed-off-by: Tim Crawford <tcrawford@system76.com>
2024-08-29 08:44:23 -06:00

451 lines
13 KiB
C

// SPDX-License-Identifier: GPL-3.0-only
//
// This defines a protocol for clients on the AP (application processor) to
// communicate with the EC. The protocol is polled, and uses semaphores to
// ensure correct sequencing.
//
// The client should check that the SMFI_CMD_CMD register is set to CMD_NONE,
// indicating that the EC is waiting for a command. The client should set the
// SMFI_CMD_DATA region as necessary for the command they wish to issue. They
// should then set the SMFI_CMD_CMD byte to indicate to the EC what command to
// execute. The EC will execute the command, and then set SMFI_CMD_RES to the
// correct result. It will finally set SMFI_CMD_CMD to CMD_NONE, to indicate
// the command is complete and the result is available. The client should only
// read the SMFI_CMD_RES value when SMFI_CMD_CMD is set to CMD_NONE.
#include <board/smfi.h>
#if !defined(__SCRATCH__)
#include <board/fan.h>
#include <board/kbled.h>
#include <board/kbscan.h>
#include <board/scratch.h>
#if CONFIG_SECURITY
#include <board/security.h>
#endif // CONFIG_SECURITY
#endif // !defined(__SCRATCH__)
#include <common/command.h>
#include <common/macro.h>
#include <common/version.h>
#include <ec/etwd.h>
#include <ec/pwm.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
// Shared memory host semaphore
volatile uint8_t __xdata __at(0x1022) SMHSR;
// Host RAM window control
volatile uint8_t __xdata __at(0x105A) HRAMWC;
// Host RAM window 0 base address
volatile uint8_t __xdata __at(0x105B) HRAMW0BA;
// Host RAM window 1 base address
volatile uint8_t __xdata __at(0x105C) HRAMW1BA;
// Host RAM window 0 access allow size
volatile uint8_t __xdata __at(0x105D) HRAMW0AAS;
// Host RAM window 1 access allow size
volatile uint8_t __xdata __at(0x105E) HRAMW1AAS;
// Flash control register 3
volatile uint8_t __xdata __at(0x1063) FLHCTRL3;
// Host RAM window 2 base address
volatile uint8_t __xdata __at(0x1076) HRAMW2BA;
// Host RAM window 3 base address
volatile uint8_t __xdata __at(0x1077) HRAMW3BA;
// Host RAM window 2 access allow size
volatile uint8_t __xdata __at(0x1078) HRAMW2AAS;
// Host RAM window 3 access allow size
volatile uint8_t __xdata __at(0x1079) HRAMW3AAS;
// EC indirect flash access
volatile uint8_t __xdata __at(0x103B) ECINDAR0;
volatile uint8_t __xdata __at(0x103C) ECINDAR1;
volatile uint8_t __xdata __at(0x103D) ECINDAR2;
volatile uint8_t __xdata __at(0x103E) ECINDAR3;
volatile uint8_t __xdata __at(0x103F) ECINDDR;
// Command region - allows client to send commands to EC
#define SMFI_CMD_CMD 0x00
#define SMFI_CMD_RES 0x01
#define SMFI_CMD_DATA 0x02
static volatile uint8_t __xdata __at(0xE00) smfi_cmd[256];
// Debug region - ring buffer of EC firmware prints
#define SMFI_DBG_TAIL 0x00
static volatile uint8_t __xdata __at(0xF00) smfi_dbg[256];
#if !defined(__SCRATCH__)
void smfi_init(void) {
int16_t i;
// Clear command region
for (i = (SMFI_CMD_CMD + 1); i < ARRAY_SIZE(smfi_cmd); i++) {
smfi_cmd[i] = 0x00;
}
// Clear host command last
smfi_cmd[SMFI_CMD_CMD] = 0x00;
// Clear debug region
for (i = (SMFI_DBG_TAIL + 1); i < ARRAY_SIZE(smfi_dbg); i++) {
smfi_dbg[i] = 0x00;
}
// Clear tail last
smfi_dbg[SMFI_DBG_TAIL] = 0x00;
// H2RAM window 0 address 0xE00 - 0xEFF, read/write
HRAMW0BA = 0xE0;
HRAMW0AAS = 0x04;
// H2RAM window 1 address 0xF00 - 0xFFF, read-only
HRAMW1BA = 0xF0;
HRAMW1AAS = 0x34;
// Enable H2RAM window 0 and 1 using LPC I/O
HRAMWC |= BIT(4) | BIT(1) | BIT(0);
// Enable backup ROM access
FLHCTRL3 |= BIT(3);
}
static enum Result cmd_print(void) {
uint8_t flags = smfi_cmd[SMFI_CMD_DATA];
uint8_t len = smfi_cmd[SMFI_CMD_DATA + 1];
uint8_t i;
for (i = 0; (i < len) && ((i + SMFI_CMD_DATA + 2) < ARRAY_SIZE(smfi_cmd)); i++) {
putchar(smfi_cmd[i + SMFI_CMD_DATA + 2]);
}
smfi_cmd[SMFI_CMD_DATA + 1] = i;
return RES_OK;
}
static enum Result cmd_fan_get(void) {
switch (smfi_cmd[SMFI_CMD_DATA]) {
case 1:
smfi_cmd[SMFI_CMD_DATA + 1] = fan1_pwm_actual;
return RES_OK;
#ifdef FAN2_PWM
case 2:
smfi_cmd[SMFI_CMD_DATA + 1] = fan2_pwm_actual;
return RES_OK;
#endif
}
// Failed if fan not found
return RES_ERR;
}
static enum Result cmd_fan_set(void) {
switch (smfi_cmd[SMFI_CMD_DATA]) {
case 1:
// Set duty cycle of FAN1
fan1_pwm_target = smfi_cmd[SMFI_CMD_DATA + 1];
return RES_OK;
#ifdef FAN2_PWM
case 2:
// Set duty cycle of FAN2
fan2_pwm_target = smfi_cmd[SMFI_CMD_DATA + 1];
return RES_OK;
#endif
}
// Failed if fan not found
return RES_ERR;
}
static enum Result cmd_keymap_get(void) {
int16_t layer = smfi_cmd[SMFI_CMD_DATA];
int16_t output = smfi_cmd[SMFI_CMD_DATA + 1];
int16_t input = smfi_cmd[SMFI_CMD_DATA + 2];
uint16_t key = 0;
if (keymap_get(layer, output, input, &key)) {
smfi_cmd[SMFI_CMD_DATA + 3] = (uint8_t)key;
smfi_cmd[SMFI_CMD_DATA + 4] = (uint8_t)(key >> 8);
return RES_OK;
} else {
return RES_ERR;
}
}
static enum Result cmd_keymap_set(void) {
int16_t layer = smfi_cmd[SMFI_CMD_DATA];
int16_t output = smfi_cmd[SMFI_CMD_DATA + 1];
int16_t input = smfi_cmd[SMFI_CMD_DATA + 2];
uint16_t key = ((uint16_t)smfi_cmd[SMFI_CMD_DATA + 3]) |
(((uint16_t)smfi_cmd[SMFI_CMD_DATA + 4]) << 8);
//TODO: consider only setting if the key has changed
if (keymap_set(layer, output, input, key)) {
//TODO: should we write config on every change?
if (keymap_save_config()) {
return RES_OK;
} else {
//TODO: need a different error code?
return RES_ERR;
}
} else {
return RES_ERR;
}
}
static enum Result cmd_led_get_value(void) {
uint8_t index = smfi_cmd[SMFI_CMD_DATA];
if (index == CMD_LED_INDEX_ALL) {
smfi_cmd[SMFI_CMD_DATA + 1] = kbled_get();
smfi_cmd[SMFI_CMD_DATA + 2] = kbled_max();
return RES_OK;
} else {
return RES_ERR;
}
}
static enum Result cmd_led_set_value(void) {
uint8_t index = smfi_cmd[SMFI_CMD_DATA];
if (index == CMD_LED_INDEX_ALL) {
kbled_set(smfi_cmd[SMFI_CMD_DATA + 1]);
return RES_OK;
} else {
return RES_ERR;
}
}
static enum Result cmd_led_get_color(void) {
uint8_t index = smfi_cmd[SMFI_CMD_DATA];
if (index == CMD_LED_INDEX_ALL) {
uint32_t color = kbled_get_color();
smfi_cmd[SMFI_CMD_DATA + 1] = (uint8_t)(color >> 16);
smfi_cmd[SMFI_CMD_DATA + 2] = (uint8_t)(color >> 8);
smfi_cmd[SMFI_CMD_DATA + 3] = (uint8_t)(color >> 0);
return RES_OK;
} else {
return RES_ERR;
}
}
static enum Result cmd_led_set_color(void) {
uint8_t index = smfi_cmd[SMFI_CMD_DATA];
if (index == CMD_LED_INDEX_ALL) {
kbled_set_color(
(((uint32_t)smfi_cmd[SMFI_CMD_DATA + 1]) << 16) |
(((uint32_t)smfi_cmd[SMFI_CMD_DATA + 2]) << 8) |
(((uint32_t)smfi_cmd[SMFI_CMD_DATA + 3]) << 0)
);
return RES_OK;
} else {
return RES_ERR;
}
}
static enum Result cmd_matrix_get(void) {
smfi_cmd[SMFI_CMD_DATA] = KM_OUT;
smfi_cmd[SMFI_CMD_DATA + 1] = KM_IN;
for (uint8_t row = 0; row < KM_OUT; row++) {
smfi_cmd[SMFI_CMD_DATA + 2 + row] = kbscan_matrix[row];
}
return RES_OK;
}
#if CONFIG_SECURITY
static enum Result cmd_security_get(void) {
smfi_cmd[SMFI_CMD_DATA] = security_get();
return RES_OK;
}
static enum Result cmd_security_set(void) {
enum SecurityState state = smfi_cmd[SMFI_CMD_DATA];
if (security_set(state)) {
return RES_OK;
} else {
return RES_ERR;
}
}
#endif // CONFIG_SECURITY
#endif // !defined(__SCRATCH__)
#if defined(__SCRATCH__)
static enum Result cmd_spi_scratch(void) __critical {
uint8_t flags = smfi_cmd[SMFI_CMD_DATA];
uint8_t len = smfi_cmd[SMFI_CMD_DATA + 1];
// Enable chip
if (flags & CMD_SPI_FLAG_BACKUP) {
ECINDAR3 = 0xFF;
} else {
ECINDAR3 = 0x7F;
}
ECINDAR2 = 0xFF;
ECINDAR1 = 0xFD;
ECINDAR0 = 0x00;
// Read or write len bytes
uint8_t i;
for (i = 0; (i < len) && ((i + SMFI_CMD_DATA + 2) < ARRAY_SIZE(smfi_cmd)); i++) {
if (flags & CMD_SPI_FLAG_READ) {
smfi_cmd[i + SMFI_CMD_DATA + 2] = ECINDDR;
} else {
ECINDDR = smfi_cmd[i + SMFI_CMD_DATA + 2];
}
}
// Set actually read/written count
smfi_cmd[SMFI_CMD_DATA + 1] = i;
if (flags & CMD_SPI_FLAG_DISABLE) {
// Disable chip
ECINDAR1 = 0xFE;
ECINDDR = 0;
}
return RES_OK;
}
#endif // defined(__SCRATCH__)
static enum Result cmd_spi(void) {
#if defined(__SCRATCH__)
return cmd_spi_scratch();
#else // defined(__SCRATCH__)
#if CONFIG_SECURITY
if (security_get() != SECURITY_STATE_UNLOCK) {
// EC must be unlocked to allow flashing
return RES_ERR;
}
#endif // CONFIG_SECURITY
if (smfi_cmd[SMFI_CMD_DATA] & CMD_SPI_FLAG_SCRATCH) {
scratch_trampoline();
}
// Cannot use follow mode unless running from scratch rom
return RES_ERR;
#endif // defined(__SCRATCH__)
}
static enum Result cmd_reset(void) {
#if !defined(__SCRATCH__)
#if CONFIG_SECURITY
if (security_get() != SECURITY_STATE_UNLOCK) {
// EC must be unlocked to allow watchdog reset
return RES_ERR;
}
#endif // CONFIG_SECURITY
#endif // !defined(__SCRATCH__)
// Attempt to trigger watchdog reset
ETWCFG |= BIT(5);
EWDKEYR = 0;
// Failed if it got this far
return RES_ERR;
}
// Set a watchdog timer of 10 seconds
void smfi_watchdog(void) {
ET1CNTLLR = 0xFF;
EWDCNTLLR = 0xFF;
EWDCNTLHR = 0x04;
}
void smfi_event(void) {
if (smfi_cmd[SMFI_CMD_CMD]) {
#if defined(__SCRATCH__)
// If in scratch ROM, restart watchdog timer when command received
smfi_watchdog();
#endif
switch (smfi_cmd[SMFI_CMD_CMD]) {
#if !defined(__SCRATCH__)
case CMD_PROBE:
// Signature
smfi_cmd[SMFI_CMD_DATA + 0] = 0x76;
smfi_cmd[SMFI_CMD_DATA + 1] = 0xEC;
// Version
smfi_cmd[SMFI_CMD_DATA + 2] = 0x01;
//TODO: bitmask of implemented commands?
// Always successful
smfi_cmd[SMFI_CMD_RES] = RES_OK;
break;
case CMD_BOARD:
strncpy(&smfi_cmd[SMFI_CMD_DATA], board(), ARRAY_SIZE(smfi_cmd) - SMFI_CMD_DATA);
// Always successful
smfi_cmd[SMFI_CMD_RES] = RES_OK;
break;
case CMD_VERSION:
strncpy(&smfi_cmd[SMFI_CMD_DATA], version(), ARRAY_SIZE(smfi_cmd) - SMFI_CMD_DATA);
// Always successful
smfi_cmd[SMFI_CMD_RES] = RES_OK;
break;
case CMD_PRINT:
smfi_cmd[SMFI_CMD_RES] = cmd_print();
break;
case CMD_FAN_GET:
smfi_cmd[SMFI_CMD_RES] = cmd_fan_get();
break;
case CMD_FAN_SET:
smfi_cmd[SMFI_CMD_RES] = cmd_fan_set();
break;
case CMD_KEYMAP_GET:
smfi_cmd[SMFI_CMD_RES] = cmd_keymap_get();
break;
case CMD_KEYMAP_SET:
smfi_cmd[SMFI_CMD_RES] = cmd_keymap_set();
break;
case CMD_LED_GET_VALUE:
smfi_cmd[SMFI_CMD_RES] = cmd_led_get_value();
break;
case CMD_LED_SET_VALUE:
smfi_cmd[SMFI_CMD_RES] = cmd_led_set_value();
break;
case CMD_LED_GET_COLOR:
smfi_cmd[SMFI_CMD_RES] = cmd_led_get_color();
break;
case CMD_LED_SET_COLOR:
smfi_cmd[SMFI_CMD_RES] = cmd_led_set_color();
break;
case CMD_MATRIX_GET:
smfi_cmd[SMFI_CMD_RES] = cmd_matrix_get();
break;
#if CONFIG_SECURITY
case CMD_SECURITY_GET:
smfi_cmd[SMFI_CMD_RES] = cmd_security_get();
break;
case CMD_SECURITY_SET:
smfi_cmd[SMFI_CMD_RES] = cmd_security_set();
break;
#endif // CONFIG_SECURITY
#endif // !defined(__SCRATCH__)
case CMD_SPI:
smfi_cmd[SMFI_CMD_RES] = cmd_spi();
break;
case CMD_RESET:
smfi_cmd[SMFI_CMD_RES] = cmd_reset();
break;
default:
// Command not found
smfi_cmd[SMFI_CMD_RES] = RES_ERR;
break;
}
// Mark command as finished
smfi_cmd[SMFI_CMD_CMD] = CMD_NONE;
}
}
void smfi_debug(uint8_t byte) {
int16_t tail = (int16_t)smfi_dbg[SMFI_DBG_TAIL];
tail++;
if (tail >= ARRAY_SIZE(smfi_dbg)) {
tail = SMFI_DBG_TAIL + 1;
}
smfi_dbg[tail] = byte;
smfi_dbg[SMFI_DBG_TAIL] = (uint8_t)tail;
}